Upgrade to Pro — share decks privately, control downloads, hide ads and more …

Pythonによる開発をアップデートするライブラリの紹介

 Pythonによる開発をアップデートするライブラリの紹介

Daiki Katsuragawa

July 30, 2022
Tweet

More Decks by Daiki Katsuragawa

Other Decks in Programming

Transcript

  1. Pythonによる開発をアップデートする
    ライブラリの紹介
    (OSC2022 Online/Kyoto)
    桂川大輝

    View Slide

  2. 自己紹介
    ● 名前:桂川 大輝(GitHub:daikikatsuragawa)
    ● 職業:機械学習エンジニア他
    ○ Pythonによる開発・分析
    ● OSS活動@GitHub
    ○ Clasp Action[1]:
    ■ Google App Scriptのデプロイを実現するGitHub Actions(CI)
    ○ その他:
    ■ 主に機械学習系のライブラリへのコントリビュート
    2
    [1]Clasp Action · Actions · GitHub Marketplace(https://github.com/marketplace/actions/clasp-action)

    View Slide

  3. 目次
    ● Pythonによる開発をアップデートするライブラリ(OSS)
    ● 型ヒントとは?
    ● モデルの定義に基づくバリデーションを実現する“pydantic”
    ● スキーマの定義に基づくデータフレームの
    バリデーションを実現する“pandera”
    ● 入出力に関するプロパティの定義に基づく
    Property-based testingを実現する“hypothesis”
    ● 必要最小限のウェブアプリケーションを簡単に実現する“streamlit”
    ● 本発表のまとめ
    3

    View Slide

  4. 目次
    ● Pythonによる開発をアップデートするライブラリ(OSS)
    ● 型ヒントとは?
    ● モデルの定義に基づくバリデーションを実現する“pydantic”
    ● スキーマの定義に基づくデータフレームの
    バリデーションを実現する“pandera”
    ● 入出力に関するプロパティの定義に基づく
    Property-based testingを実現する“hypothesis”
    ● 必要最小限のウェブアプリケーションを簡単に実現する“streamlit”
    ● 本発表のまとめ
    4

    View Slide

  5. Pythonによる開発
    ● Python
    ○ 動的型付けプログラミング言語
    ○ 多様なライブラリがOSSとして公開(例:機械学習)
    ■ PyPiにより38万以上が公開[2](2022年7月30日)
    ○ 人気プログラミング言語ランキング3年連続で2位[3]
    5
    本発表ではPythonによる開発をアップデートする手段を紹介
    [2] PyPI · The Python Package Index(https://pypi.org/)
    [3] The 2021 State of the Octoverse | The GitHub Blog(https://github.blog/2021-11-16-the-2021-state-of-the-octoverse/)

    View Slide

  6. 開発における理想と現実
    6
    理想
    ● 成果物が正常に
    動作する
    ● 開発の保守性が
    高い(複数人での
    開発が可能)

    View Slide

  7. 開発における理想と現実
    7
    理想
    ● 成果物が正常に
    動作する
    ● 開発の保守性が
    高い(複数人での
    開発が可能)
    現実
    ● 成果物に不具合が
    生じることがある
    ● 開発の保守性が
    高いという自信が
    無い

    View Slide

  8. 開発における理想と現実
    8
    理想
    ● 成果物が正常に
    動作する
    ● 開発の保守性が
    高い(複数人での
    開発が可能)
    現実
    ● 成果物に不具合が
    生じることがある
    ● 開発の保守性が
    高いという自信が
    無い
    ギャップ
    開発者が保守性の低い
    コーディングをする
    開発者が不具合を
    埋め込んでしまう
    開発における理想と現実にギャップが存在

    View Slide

  9. 理想と現実のギャップを解消する手段
    ● 個人の能力の向上
    ○ 個人に依存するため実現の難易度が不確実
    ● 開発プロセスの改善
    ○ 個人に依存しない仕組みの導入により実現
    9
    開発プロセスの改善により理想と現実のギャップを解消

    View Slide

  10. 開発プロセスの課題
    ● 理想と現実のギャップを開発プロセスの課題と捉え直すと…
    ○ 「開発者が不具合を埋め込んでしまう」
    →「不具合を埋め込ませない開発プロセスの構築(ツールの導入)が必要」
    ○ 「開発者が保守性の低いコーディングをする」
    →「一定の保守性を担保する開発プロセスの構築(ツールの導入)が必要」
    10
    理想と現実のギャップ=開発プロセスの課題

    View Slide

  11. 開発プロセスの課題の改善
    11
    ギャップ
    ● 開発者が不具合を
    埋め込んでしまう
    ● 開発者が保守性の
    低いコーディング
    をする
    開発プロセスの課題
    ● 不具合を埋め込み
    にくい仕組みが
    必要
    ● 保守性が高くなる
    仕組みが必要
    有用なライブラリを
    導入し利用
    変換
    Pythonによる開発プロセスを改善
    (開発をアップデート)する有用なライブラリを紹介

    View Slide

  12. 本発表で紹介するライブラリ
    ● pydantic
    ○ モデルの定義に基づくバリデーションを実現
    ● pandera
    ○ スキーマの定義に基づくデータフレームのバリデーションを実現
    ● hypothesis
    ○ 入出力に関するプロパティの定義に基づく
    Property-based testingを実現
    ● streamlit
    ○ 必要最小限のウェブアプリケーションを簡単に実現
    12

    View Slide

  13. 本発表で話すこと/話さないこと
    話すこと
    ● ライブラリの概要
    ● ライブラリの利点
    ● ライブラリの簡単な利用例
    話さないこと
    ● ライブラリの欠点(リスク)
    ● ライブラリの詳細な利用例
    ● 紹介するライブラリ間の関連
    13
    本発表を“知るキッカケ”としてください!

    View Slide

  14. 目次
    ● Pythonによる開発をアップデートするライブラリ(OSS)
    ● 型ヒントとは?
    ● モデルの定義に基づくバリデーションを実現する“pydantic”
    ● スキーマの定義に基づくデータフレームの
    バリデーションを実現する“pandera”
    ● 入出力に関するプロパティの定義に基づく
    Property-based testingを実現する“hypothesis”
    ● 必要最小限のウェブアプリケーションを簡単に実現する“streamlit”
    ● 本発表のまとめ
    14

    View Slide

  15. 型ヒントとは?①
    ● 型ヒント(Type Hints[4])
    ○ 変数の定義、関数の引数や戻り値の定義に型のヒントを付与するという記法
    (導入:Python3.5〜)
    15
    [4] PEP 484 – Type Hints | peps.Python.org(https://peps.Python.org/pep-0484/)
    name: str = "パイソン 太郎"
    def calculate_division(numerator: int, denominator: int) -> float:
    return numerator / denominator

    View Slide

  16. 型ヒントとは?②
    ● 型ヒントの振る舞い
    ○ 実行時に型のチェックはしない
    ○ サードパーティーのツールと組み合わせて静的チェック
    (例:mypy、統合開発環境)
    16

    View Slide

  17. 型ヒントの記述方法〜基本〜
    ● 変数:直後に「:」と型を記述
    ● 関数の引数:各引数の直後に「:」と型を記述
    ● 関数の戻り値:「:」の直前に「->」と型を記述
    17
    name: str = "パイソン 太郎"
    def calculate_division(numerator: int, denominator: int) -> float:
    return numerator / denominator

    View Slide

  18. 型ヒントの記述方法〜発展〜
    ● 辞書(Dict)、リスト(List)などの要素の型
    ● 複数の型(Union)
    ● 必須ではない(Optional)
    ● 任意(Any)の型
    ● 値が存在しない(None)
    ● プリミティブではない型
    ○ ライブラリのクラス
    ○ 自作のクラス
    18

    View Slide

  19. 型ヒントとは?〜まとめ〜
    ● 型ヒント(Type Hints)
    ○ 変数の定義、関数の引数や戻り値の定義に型のヒントを付与するという記法
    (導入:Python3.5〜)
    ● 型ヒントの振る舞い
    ○ 実行時に型のチェックはしない
    ○ サードパーティーのツールと組み合わせて静的チェック
    (例:mypy、統合開発環境)
    19

    View Slide

  20. 型ヒントとは?〜まとめ〜
    ● 型ヒント(Type Hints)
    ○ 変数の定義、関数の引数や戻り値の定義に型のヒントを付与するという記法
    (導入:Python3.5〜)
    ● 型ヒントの振る舞い
    ○ 実行時に型のチェックはしない
    ○ サードパーティーのツールと組み合わせて静的チェック
    (例:mypy、統合開発環境)
    20
    型ヒントを活用した“pydantic”と”pandera”を紹介

    View Slide

  21. 目次
    ● Pythonによる開発をアップデートするライブラリ(OSS)
    ● 型ヒントとは?
    ● モデルの定義に基づくバリデーションを実現する“pydantic”
    ● スキーマの定義に基づくデータフレームの
    バリデーションを実現する“pandera”
    ● 入出力に関するプロパティの定義に基づく
    Property-based testingを実現する“hypothesis”
    ● 必要最小限のウェブアプリケーションを簡単に実現する“streamlit”
    ● 本発表のまとめ
    21

    View Slide

  22. 複数の部品によって構成されるシステムを開発していますか?
    ● 多くのシステムは複数の部品によって構成
    ○ 部品:モジュール・クラス・メソッドなど
    ● 各部品が役割と責任を持ち連携
    ○ 各部品の入出力を把握しておけばシステムの全ての把握は不要
    ○ 入出力:int、str、JSON/辞書(構造)
    22
    部品A 部品B
    int
    str
    部品Y 部品Z
    JSON/辞書
    JSON/辞書

    View Slide

  23. 課題①:入出力で扱うデータの定義がわからない
    ● 他の部品の出力を入力として受け取る時、
    他の部品の実装を深く見る必要がある場面も…
    23
    部品Y 部品Z
    JSON
    ???のデータを
    格納したJSONを
    渡します。
    何が格納されている?
    キーは?

    View Slide

  24. 課題②:入出力で扱うデータが正しくない
    ● 他の部品の出力を把握しているものの
    期待と異なる、正しくないデータを受け取る場面も…
    24
    部品Y 部品Z
    JSON
    ◯△□のデータを
    格納したJSONを
    渡します。
    ドメイン的に
    ありえないデータが来た

    View Slide

  25. “pydantic”
    ● “pydantic[5]”とは?
    ○ モデルを定義することでデータのバリデーションを実現するライブラリ
    ○ 辞書型・JSONとのシリアライズ/デシリアライズも可能で部品の入出力に有用
    ○ ライセンス:MIT License[6]
    25
    pip install pydantic
    [5] pydantic(https://pydantic-docs.helpmanual.io/)
    [6] pydantic/LICENSE at master · samuelcolvin/pydantic(https://github.com/samuelcolvin/pydantic/blob/master/LICENSE)

    View Slide

  26. 課題を解決する“pydantic”
    ● 課題①:入出力で扱うデータの定義がわからない
    ○ モデルの定義により把握が可能に
    ● 課題②:入出力で扱うデータが正しくない
    ○ モデルの定義に基づくバリデーションにより正しいデータのみが存在する状態に
    26

    View Slide

  27. pydanticの利用例〜モデルの定義とバリデーション〜
    27
    from pydantic import BaseModel, Field
    class User(BaseModel):
    id: int
    name: str
    age: int = Field(ge=20)
    external_data = {
    'name': 'パイソン 太郎',
    'age': 20
    }
    user = User.parse_obj(external_data)
    型、有効範囲の定義
    定義を満たす
    インスタンスの生成
    Userクラスの定義の
    読み取りが可能
    (例:ageは20以上)

    View Slide

  28. pydanticの利用例〜モデルの定義とバリデーション(エラー)〜
    28
    from pydantic import BaseModel, Field
    class User(BaseModel):
    id: int
    name: str
    age: int = Field(ge=20)
    external_data = {
    'name': 'パイソン 太郎',
    'age': 19
    }
    user = User.parse_obj(external_data)
    有効範囲外の値
    ValidationError

    View Slide

  29. pydanticの利用例〜ValidationErrorの中身〜
    29
    (省略)
    ValidationError: 1 validation error for User
    age
    ensure this value is greater than or equal to 20 (type=value_error.number.not_ge; limit_value=20)
    エラーの詳細を確認可能
    (例:ageの20以上という定義を満たしていない)

    View Slide

  30. pydanticの利用例〜デコレーターによる詳細なバリデーションの実現〜
    30
    from pydantic import BaseModel, Field, validator
    import unicodedata
    class User(BaseModel):
    id: int
    name: str # 半角空白を含む(※前後以外)
    age: int = Field(ge=20)
    @validator("name")
    def check_contain_space(cls, v):
    if " " not in v.strip(): # 前後の半角空白を無視
    raise ValueError("ensure this value contains spaces")
    return v.strip() # 前後の半角空白を削除
    デコレーターによる詳細な
    バリデーションの実現も可能
    (例:半角空白を含むかを判定)

    View Slide

  31. pydanticのまとめ
    ● 複数の部品によって構成されるシステムの開発
    ○ 各部品(モジュール・クラス・メソッドなど)が役割と責任を持ち連携
    ○ 課題①:入出力で扱うデータの定義がわからない
    ○ 課題②:入出力で扱うデータが正しくない
    ● 課題を解決する“pydantic”
    ○ モデルを定義することでデータのバリデーションを実現するライブラリ
    ○ モデルの定義により把握が可能に(課題①の解決)
    ○ モデルの定義に基づくバリデーションにより正しいデータのみが存在する状態に
    (課題②の解決)
    31

    View Slide

  32. 目次
    ● Pythonによる開発をアップデートするライブラリ(OSS)
    ● 型ヒントとは?
    ● モデルの定義に基づくバリデーションを実現する“pydantic”
    ● スキーマの定義に基づくデータフレームの
    バリデーションを実現する“pandera”
    ● 入出力に関するプロパティの定義に基づく
    Property-based testingを実現する“hypothesis”
    ● 必要最小限のウェブアプリケーションを簡単に実現する“streamlit”
    ● 本発表のまとめ
    32

    View Slide

  33. データフレームを使っていますか?
    ● データフレーム(pandas.DataFrame[7])
    ○ 表型式のデータの取り込み、加工、集計、分析に利用(例:機械学習)
    ○ 例:アヤメ(iris)の特徴と種類(scikit-learn[8]より)
    33
    [6] pandas - Python Data Analysis Library(https://pandas.pydata.org/)
    [7] scikit-learn: machine learning in Python — scikit-learn 1.1.1 documentation(https://scikit-learn.org/stable/)
    sepal_length sepal_width petal_length petal_width target
    0 5.1 3.5 1.4 0.2 0
    1 4.9 3.0 1.4 0.2 0
    2 4.7 3.2 1.3 0.2 0
    3 4.6 3.1 1.5 0.2 0
    4 5.0 3.6 1.4 0.2 0

    View Slide

  34. 課題①:意図しない値を格納してしまう
    34
    sepal_length sepal_width petal_length petal_width target
    146 6.3 2.5 5.00 1.9 2
    147 6.5 3.0 5.20 2.0 2
    148 6.2 3.4 5.40 2.3 2
    149 5.9 3.0 5.10 1.8 2
    150 ◯△□ 3.0 4.35 1.3 3
    数値を期待しているが…
    文字列が格納されている
    0/1/2を期待しているが…
    3が格納されている

    View Slide

  35. 課題②:他者・未来の自分がコードから内容を読み取れない
    35
    import pandas as pd
    from sklearn.datasets import load_iris
    data = load_iris()
    iris = pd.DataFrame(data.data, columns=data.feature_names)
    iris["target"] = data.target
    iris.head()
    irisにはどんな情報が
    格納されている?

    View Slide

  36. “pandera”
    ● “pandera[9]”とは?
    ○ データフレームのスキーマ(構造)を定義することで
    データフレームのバリデーションを実現するライブラリ
    ○ ライセンス:MIT License[10]

    36
    pip install pandera
    [9] pandera(https://pandera.readthedocs.io/en/stable/)
    [10] pandera/LICENSE.txt at master · unionai-oss/pandera(https://github.com/unionai-oss/pandera/blob/master/LICENSE.txt)

    View Slide

  37. 課題を解決する“pandera”
    ● 課題①:意図しない値を格納してしまう
    ○ バリデーションにより意図しない値の格納を防ぐ
    ● 課題②:他者・未来の自分がコードから内容を読み取れない
    ○ スキーマを明示的に記述することでコードから内容の読み取りが可能に
    ※大きく利用方法は2種あるが、今回は“Schema Models”を紹介
    37

    View Slide

  38. panderaの利用例〜アヤメ(iris)のデータセットの準備〜
    38
    import pandas as pd
    from sklearn.datasets import load_iris
    data = load_iris()
    iris = pd.DataFrame(data.data, columns=data.feature_names)
    iris["target"] = data.target
    iris = iris.rename(
    columns={
    "sepal length (cm)": "sepal_length",
    "sepal width (cm)": "sepal_width",
    "petal length (cm)": "petal_length",
    "petal width (cm)": "petal_width",
    }
    )

    View Slide

  39. panderaの利用例〜データの確認①〜
    39
    iris.head()
    0/1/2(カテゴリ)
    数値
    sepal_length sepal_width petal_length petal_width target
    0 5.1 3.5 1.4 0.2 0
    1 4.9 3.0 1.4 0.2 0
    2 4.7 3.2 1.3 0.2 0
    3 4.6 3.1 1.5 0.2 0
    4 5.0 3.6 1.4 0.2 0

    View Slide

  40. panderaの利用例〜データの確認②〜
    40
    iris.describe()
    sepal_length sepal_width petal_length petal_width target
    count 150.000000 150.000000 150.000000 150.000000 150.000000
    mean 5.843333 3.057333 3.758000 1.199333 1.000000
    std 0828066 0.435866 1.765298 0.762238 0.819232
    min 4.300000 2.000000 1.000000 0.100000 0.000000
    25% 5.100000 2.800000 1.600000 0.300000 0.000000
    50% 5.800000 3.000000 4.350000 1.300000 1.000000
    75% 6.400000 3.300000 5.100000 1.800000 2.000000
    max 7.900000 4.400000 6.900000 2.500000 2.000000

    View Slide

  41. import pandera as pa
    from pandera.typing import Series
    class IrisSchema(pa.SchemaModel):
    sepal_length: Series[float] = pa.Field(gt=0, le=8)
    sepal_width: Series[float] = pa.Field(gt=0, le=5)
    petal_length: Series[float] = pa.Field(gt=0, le=7)
    petal_width: Series[float] = pa.Field(gt=0, le=3)
    target: Series[int] = pa.Field(isin=[0, 1, 2])
    class Config:
    name = "BaseSchema"
    strict = True
    coerce = True
    panderaの利用例〜スキーマの定義〜
    41
    0/1/2(カテゴリ)
    ドメインに基づく
    現実的な値

    View Slide

  42. panderaの利用例〜バリデーション〜
    42
    iris = IrisSchema.validate(iris)
    iris.head()
    sepal_length sepal_width petal_length petal_width target
    0 5.1 3.5 1.4 0.2 0
    1 4.9 3.0 1.4 0.2 0
    2 4.7 3.2 1.3 0.2 0
    3 4.6 3.1 1.5 0.2 0
    4 5.0 3.6 1.4 0.2 0

    View Slide

  43. invalid_record = {
    "sepal_length": 5.8,
    "sepal_width": 3.0,
    "petal_length": 4.35,
    "petal_width": 1.3,
    "target": 3, # invalid value
    }
    invalid_iris = iris.append(invalid_record, ignore_index=True)
    invalid_iris["target"] = invalid_iris["target"].astype(int)
    panderaの利用例〜意図しないレコードの追加〜
    43

    View Slide

  44. panderaの利用例〜意図しないレコードの確認〜
    44
    invalid_iris.tail()
    sepal_length sepal_width petal_length petal_width target
    146 6.3 2.5 5.00 1.9 2
    147 6.5 3.0 5.20 2.0 2
    148 6.2 3.4 5.40 2.3 2
    149 5.9 3.0 5.10 1.8 2
    150 5.8 3.0 4.35 1.3 3

    View Slide

  45. panderaの利用例〜バリデーション(エラー)〜
    45
    invalid_iris = IrisSchema.validate(invalid_iris)
    ---------------------------------------------------------------------------
    SchemaError Traceback (most recent call last)
    (省略)
    SchemaError: failed element-wise validator 0:

    failure cases:
    index failure_case
    0 150 3
    indexが150のレコードでエラー
    SchemaError

    View Slide

  46. panderaのまとめ
    ● データフレーム(pandas.DataFrame)
    ○ 表形式のデータの取り込み、加工、集計、分析に利用
    ○ データ分析/機械学習などで活躍
    ○ 課題①:意図しない値を格納してしまう
    ○ 課題②:他者・未来の自分がコードから内容を読み取れない
    ● 課題を解決する“pandera”
    ○ データフレームのスキーマ(構造)を定義することで
    データフレームのバリデーションを実現するライブラリ
    ○ バリデーションにより意図しない値の格納を防ぐ(課題①の解決)
    ○ スキーマを明示的に記述することでコードから内容の読み取りが可能に
    (課題②の解決)
    46

    View Slide

  47. 目次
    ● Pythonによる開発をアップデートするライブラリ(OSS)
    ● 型ヒントとは?
    ● モデルの定義に基づくバリデーションを実現する“pydantic”
    ● スキーマの定義に基づくデータフレームの
    バリデーションを実現する“pandera”
    ● 入出力に関するプロパティの定義に基づく
    Property-based testingを実現する“hypothesis”
    ● 必要最小限のウェブアプリケーションを簡単に実現する“streamlit”
    ● 本発表のまとめ
    47

    View Slide

  48. 単体テスト(Example-based testing)
    ● 単体テスト
    ○ プログラムを構成する小さな部品(関数/メソッド)が
    意図した振る舞いか否かを検証するテスト
    ○ Pythonではunittestやpytestというテスト用のフレームワークが有名
    ● Example-based testing(一般的によくみられる単体テスト)
    ○ 関数やメソッドに対して、任意の基準(例:ランダム/境界値テスト)で
    入力値を選択し、出力値や事後状態の確認により振る舞いを検証
    ○ 例:除算するメソッドに対して、「3」と「12」という入力を与えて
    「4」が出力されることを確認
    48

    View Slide

  49. 課題:テストの品質がテスト作成者に依存
    ● テスト作成者の想定内の内容で不十分になってしまう危険
    ○ 例①:境界値テストなどの手法を知らず要件を満たさない検証となった
    ○ 例②:除算をするメソッドに対して、適当な入出力での検証は実施しているが
    エッジケースを見逃した(分母が「0」の場合、入力が「Null(NaN)」)
    ● 冗長なテストを書いてしまう危険
    ○ 例:複数の例を設定し検証したが内容が重複し特に意味がない
    49

    View Slide

  50. 単体テスト(Property-based testing)
    ● Property-based testing
    ○ 入出力に関するプロパティを定義し多数の入力を与え検証
    ○ 例:除算するメソッドに対して、
    「入力値はint型の自然数、ただし分母に0、入力にNullは許可しない」、
    「出力はfloat型の数値」
    50

    View Slide

  51. “hypothesis”
    ● “hypothesis[11]”とは?
    ○ 入出力に関するプロパティの定義に基づく
    Property-based testingを実現するライブラリ
    ○ ライセンス:Mozilla Public License Version 2.0[12]
    51
    pip install hypothesis
    [11] Welcome to Hypothesis! — Hypothesis 6.52.4 documentation(https://hypothesis.readthedocs.io/en/latest/)
    [12] hypothesis/LICENSE.txt at master ·HypothesisWorks/hypothesis
    (https://github.com/HypothesisWorks/hypothesis/blob/master/hypothesis-Python/LICENSE.txt)

    View Slide

  52. 課題を解決する“hypothesis”
    ● 課題:テストの品質がテスト作成者に依存
    ○ Property-based testingの導入により
    テスト作成者に依存しないテストの品質を担保
    52

    View Slide

  53. ● 関数“calculate_subtraction”
    ○ 概要:入力された2つの整数を減算
    ○ 入力:整数(int)×2
    ○ 出力:整数(int)
    hypothesisの利用例〜テスト対象の関数〜
    53
    # main.py
    def calculate_subtraction(a: int, b: int):
    return a - b

    View Slide

  54. ● プロパティ
    ○ 入力:xとy(自然数)、x>y
    ○ 出力:0より大きい整数
    hypothesisの利用例〜単体テストの実装〜
    54
    # test_main.py
    from main import calculate_subtraction
    from hypothesis import given, strategies
    @given(x=strategies.integers(), y=strategies.integers())
    def test_calculate_subtraction(x, y):
    assert 0 < calculate_subtraction(x, y) 出力:0より大きい整数
    入力:それぞれint型

    View Slide

  55. hypothesisの利用例〜エラー例①〜
    55
    pytest test_main.py
    Falsifying example: test_calculate_subtraction(
    x=0, y=0,
    )
    (省略)
    FAILED test_main.py::test_calculate_subtraction - assert 0 < 0
    メソッドの出力が要件
    (0より大きい)を
    満たさなかった事による
    エラー
    AssertionError

    View Slide

  56. hypothesisの利用例〜エラー例①〜
    56
    pytest test_main.py
    Falsifying example: test_calculate_subtraction(
    x=0, y=0,
    )
    (省略)
    FAILED test_main.py::test_calculate_subtraction - assert 0 < 0
    入力として想定していた
    自然数ではない値(0)

    View Slide

  57. ● プロパティ
    ○ 入力:xとy(それぞれ自然数かつx>y)(それぞれint型の数値)
    ○ 出力:0より大きい
    hypothesisの利用例〜単体テストの実装〜
    57
    # test_main.py
    from main import calculate_subtraction
    from hypothesis import given, strategies
    @given(x=strategies.integers(), y=strategies.integers())
    def test_calculate_subtraction(x, y):
    assert 0 < calculate_subtraction(x, y) 出力:0より大きい整数
    入力:それぞれint型

    View Slide

  58. ● プロパティ
    ○ 入力:xとy(それぞれ自然数かつx>y)(それぞれint型の数値)
    ○ 出力:0より大きい
    hypothesisの利用例〜単体テストの修正〜
    58
    # test_main.py
    from main import calculate_subtraction
    from hypothesis import given, strategies
    @given(x=strategies.integers(min_value=1), y=strategies.integers(min_value=1))
    def test_calculate_subtraction(x, y):
    assert 0 < calculate_subtraction(x, y)
    入力:自然数

    (1以上)

    View Slide

  59. hypothesisの利用例〜エラー例②〜
    59
    pytest test_main.py
    Falsifying example: test_calculate_subtraction(
    x=1, y=1,
    )
    (省略)
    FAILED test_main.py::test_calculate_subtraction - assert 0 < 0
    メソッドの出力が要件
    (0より大きい)を
    満たさなかった事による
    エラー
    AssertionError

    View Slide

  60. hypothesisの利用例〜エラー例②〜
    60
    pytest test_main.py
    Falsifying example: test_calculate_subtraction(
    x=1, y=1,
    )
    (省略)
    FAILED test_main.py::test_calculate_subtraction - assert 0 < 0
    入力として想定していた
    X>Yを満たさない

    View Slide

  61. ● プロパティ
    ○ 入力:xとy(それぞれ自然数かつx>y)
    ○ 出力:0より大きい
    hypothesisの利用例〜単体テストの修正〜
    61
    # test_main.py
    from main import calculate_subtraction
    from hypothesis import given, strategies
    @given(x=strategies.integers(min_value=1), y=strategies.integers(min_value=1))
    def test_calculate_subtraction(x, y):
    assert 0 < calculate_subtraction(x, y) 出力:0より大きい整数
    入力:自然数

    (1以上)

    View Slide

  62. ● プロパティ
    ○ 入力:xとy(それぞれ自然数かつx>y)
    ○ 出力:0より大きい
    hypothesisの利用例〜単体テストの修正〜
    62
    # test_main.py
    from main import calculate_subtraction
    from hypothesis import assume, given, strategies
    @given(x=strategies.integers(min_value=1), y=strategies.integers(min_value=1))
    def test_calculate_subtraction(x, y):
    assume(x > y)
    assert 0 < calculate_subtraction(x, y)
    入力:x>y

    View Slide

  63. hypothesisの利用例〜修正した単体テストの実行(成功)〜
    63
    # test_main.py
    from main import calculate_subtraction
    from hypothesis import assume, given, strategies
    @given(x=strategies.integers(min_value=1), y=strategies.integers(min_value=1))
    def test_calculate_subtraction(x, y):
    assume(x > y)
    assert 0 <= calculate_subtraction(x, y)
    pytest test_main.py
    # pass!

    View Slide

  64. hypothesisの利用例〜実際に検証された内容〜
    64
    # test_main.py(省略)
    @given(x=strategies.integers(min_value=1), y=strategies.integers(min_value=1))
    def test_calculate_subtraction(x, y):
    assume(x > y)
    assert 0 <= calculate_subtraction(x, y)
    施行件数:100
    例:(x: 30604, y: 30261)、(x: 109, y: 5)、
    (x: 10694612245883426162890217387823834314, y: 9)

    View Slide

  65. hypothesisのまとめ
    ● Example-based testing(一般的によくみられる単体テスト)
    ○ 関数やメソッドに対して、任意の基準(例:ランダム/境界値テスト)で
    入力値を選択し、出力値や事後状態の確認により振る舞いを検証
    ○ 課題:テストの品質が実装者に依存
    ● Property-based testing
    ○ 入出力に関するプロパティを定義し多数の入力を与え検証
    ● 課題を解決する“hypothesis”
    ○ 入出力に関するプロパティの定義に基づく
    Property-based testingを実現するライブラリ
    ○ 課題:Property-based testingの導入により
    テスト作成者に依存しないテストの品質を担保
    65

    View Slide

  66. 目次
    ● Pythonによる開発をアップデートするライブラリ(OSS)
    ● 型ヒントとは?
    ● モデルの定義に基づくバリデーションを実現する“pydantic”
    ● スキーマの定義に基づくデータフレームの
    バリデーションを実現する“pandera”
    ● 入出力に関するプロパティの定義に基づく
    Property-based testingを実現する“hypothesis”
    ● 必要最小限のウェブアプリケーションを簡単に実現する“streamlit”
    ● 本発表のまとめ
    66

    View Slide

  67. Minimum Viable Productとは?
    ● Minimum Viable Product(MVP)
    ○ ユーザーに必要最小限の価値を提供できるプロダクト
    ○ 提供しようとしている価値の評価に有用
    (例:自動車による「移動」という価値を評価する前に
       「移動」という価値を提供するスケートボードで検証)
    67
    早期に必要最小限の価値の提供が可能な状態にすることが大事

    View Slide

  68. “streamlit”
    ● “streamlit[13]”とは?
    ○ ウェブアプリケーション(特にUI)の開発を簡単に実現するライブラリ
    ○ 大規模で複雑な開発におけるMVPの開発に有用
    (例:機械学習を扱うソフトウェアの開発)
    ○ ライセンス:Apache License 2.0[14]
    68
    pip install streamlit
    [13] streamlit • The fastest way to build and share data apps(https://streamlit.io/)
    [14] streamlit/LICENSE at develop · streamlit/streamlit(https://github.com/streamlit/streamlit/blob/develop/LICENSE)

    View Slide

  69. デモンストレーション:streamlitで実現する予測アプリケーション
    ● 概要:学習済みの機械学習モデルによりアヤメの品種を予測
    ● 背景(シナリオ):元々は分析・検証をNotebookで実施していたが
    そもそもの価値を評価するためにMVPを開発
    ● 仕様:
    ○ 入力:がく片の長さ(sepal length)/がく片の幅(sepal width)
    花弁の長さ(petal length)/花弁の幅(petal width)
    ○ 出力:最も可能性が高いアヤメの品種
    (setosa/versicolor/virginica)
    69
    〇〇ですね💡

    View Slide

  70. デモンストレーションのために用意したもの&実行コマンド
    70
    streamlit run app.py
    app.py
    ひとつのPythonファイルのみでウェブアプリケーションを実現

    View Slide

  71. 71

    View Slide

  72. 72
    数値を入力(上限下限の制約も存在)
    全特徴を入力後に押下すると、…

    View Slide

  73. 73

    View Slide

  74. 74
    入力値を表示
    出力として予測される品種
    「versicolor」が出力

    View Slide

  75. 75
    再度の入力・予測が可能

    View Slide

  76. import pandas as pd
    from sklearn.datasets import load_iris
    from sklearn.linear_model import LogisticRegression
    import streamlit as st
    iris = load_iris()
    x = pd.DataFrame(iris.data, columns=iris.feature_names)
    y = pd.DataFrame(iris.target, columns=["target"])
    model = LogisticRegression(random_state=123)
    model.fit(x, y)
    streamlitの利用例〜機械学習モデルの作成〜
    76
    ロジスティック回帰により
    クラス分類を実現する機械学習モデル

    View Slide

  77. st.title("アヤメ品種予測フォーム")
    with st.form("アヤメ品種予測フォーム"):
    st.write("アヤメの詳細を入力してください。")
    sepal_length = st.number_input(
    "sepal length (cm)", min_value=0.0, max_value=8.0, value=(0.0+8.0)/2, step=1.0)
    sepal_width = st.number_input(
    "sepal width (cm)", min_value=0.0, max_value=5.0, value=(0.0+5.0)/2, step=1.0)
    petal_length = st.number_input(
    "petal length (cm)", min_value=0.0, max_value=7.0, value=(0.0+7.0)/2, step=1.0)
    petal_width = st.number_input(
    "petal width (cm)", min_value=0.0, max_value=3.0, value=(0.0+3.0)/2, step=1.0)
    submitted = st.form_submit_button("予測")
    streamlitの利用例〜入力フォームとボタンの作成〜
    77

    View Slide

  78. st.title("アヤメ品種予測フォーム")
    with st.form("アヤメ品種予測フォーム"):
    st.write("アヤメの詳細を入力してください。")
    sepal_length = st.number_input(
    "sepal length (cm)", min_value=0.0, max_value=8.0, value=(0.0+8.0)/2, step=1.0)
    sepal_width = st.number_input(
    "sepal width (cm)", min_value=0.0, max_value=5.0, value=(0.0+5.0)/2, step=1.0)
    petal_length = st.number_input(
    "petal length (cm)", min_value=0.0, max_value=7.0, value=(0.0+7.0)/2, step=1.0)
    petal_width = st.number_input(
    "petal width (cm)", min_value=0.0, max_value=3.0, value=(0.0+3.0)/2, step=1.0)
    submitted = st.form_submit_button("予測")
    streamlitの利用例〜入力フォームとボタンの作成〜
    78
    タイトル
    フォーム

    View Slide

  79. st.title("アヤメ品種予測フォーム")
    with st.form("アヤメ品種予測フォーム"):
    st.write("アヤメの詳細を入力してください。")
    sepal_length = st.number_input(
    "sepal length (cm)", min_value=0.0, max_value=8.0, value=(0.0+8.0)/2, step=1.0)
    sepal_width = st.number_input(
    "sepal width (cm)", min_value=0.0, max_value=5.0, value=(0.0+5.0)/2, step=1.0)
    petal_length = st.number_input(
    "petal length (cm)", min_value=0.0, max_value=7.0, value=(0.0+7.0)/2, step=1.0)
    petal_width = st.number_input(
    "petal width (cm)", min_value=0.0, max_value=3.0, value=(0.0+3.0)/2, step=1.0)
    submitted = st.form_submit_button("予測")
    streamlitの利用例〜入力フォームとボタンの作成〜
    79
    補足文
    数値入力欄×4
    (ラベル、下限・上限、
    初期値などを設定)
    フォームのボタン

    View Slide

  80. 80
    フォームを実現

    View Slide

  81. if submitted:
    st.write("## 予測結果")
    st.write("### 入力")
    input_df = pd.DataFrame(
    {
    "sepal length (cm)": sepal_length,
    "sepal width (cm)": sepal_width,
    "petal length (cm)": petal_length,
    "petal width (cm)": petal_width
    },
    index=["入力値"])
    st.write(input_df)
    streamlitの利用例〜予測結果の出力部分の作成(入力)〜
    81

    View Slide

  82. if submitted:
    st.write("## 予測結果")
    st.write("### 入力")
    input_df = pd.DataFrame(
    {
    "sepal length (cm)": sepal_length,
    "sepal width (cm)": sepal_width,
    "petal length (cm)": petal_length,
    "petal width (cm)": petal_width
    },
    index=["入力値"])
    st.write(input_df)
    streamlitの利用例〜予測結果の出力部分の作成(入力)〜
    82
    フォームのボタンを押した後に表示
    データフレームを表示
    (記法は文などと同じ)

    View Slide

  83. 83
    サブミット後の出力を実現

    View Slide

  84. st.write("### 出力")
    pred_df = pd.DataFrame({
    "target_name": iris.target_names.tolist(),
    "probability": model.predict_proba(input_df)[0].tolist(),
    })
    target_name = pred_df.sort_values(
    "probability", ascending=False)["target_name"].tolist()[0]
    st.write(target_name)
    streamlitの利用例〜予測結果の出力部分の作成(出力)〜
    84

    View Slide

  85. st.write("### 出力")
    pred_df = pd.DataFrame({
    "target_name": iris.target_names.tolist(),
    "probability": model.predict_proba(input_df)[0].tolist(),
    })
    target_name = pred_df.sort_values(
    "probability", ascending=False)["target_name"].tolist()[0]
    st.write(target_name)
    streamlitの利用例〜予測結果の出力部分の作成(出力)〜
    85
    計算、予測の結果を表示

    View Slide

  86. 86
    予測結果を表示

    View Slide

  87. 87
    予測結果を表示
    ウェブアプリケーションおよびMVPを実現

    View Slide

  88. streamlitによるMVP開発に望む前に把握したいこと
    88
    streamlitの導入 実現したいMVP
    + =
    元のコード

    View Slide

  89. streamlitによるMVP開発に望む前に把握したいこと
    89
    streamlitの導入 実現したいMVP
    + =
    streamlitの導入コストは?
    元のコード

    View Slide

  90. import pandas as pd
    from sklearn.datasets import load_iris
    from sklearn.linear_model import LogisticRegression
    import streamlit as st
    iris = load_iris()
    x = pd.DataFrame(iris.data, columns=iris.feature_names)
    y = pd.DataFrame(iris.target, columns=["target"])
    model = LogisticRegression(random_state=123)
    model.fit(x, y)
    streamlitに関するコード〜モデルの作成〜
    90

    View Slide

  91. st.title("アヤメ品種予測フォーム")
    with st.form("アヤメ品種予測フォーム"):
    st.write("アヤメの詳細を入力してください。")
    sepal_length = st.number_input(
    "sepal length (cm)", min_value=0.0, max_value=8.0, value=(0.0+8.0)/2, step=1.0)
    sepal_width = st.number_input(
    "sepal width (cm)", min_value=0.0, max_value=5.0, value=(0.0+5.0)/2, step=1.0)
    petal_length = st.number_input(
    "petal length (cm)", min_value=0.0, max_value=7.0, value=(0.0+7.0)/2, step=1.0)
    petal_width = st.number_input(
    "petal width (cm)", min_value=0.0, max_value=3.0, value=(0.0+3.0)/2, step=1.0)
    submitted = st.form_submit_button("予測")
    streamlitに関するコード〜入力フォームとボタンの作成〜
    91

    View Slide

  92. if submitted:
    st.write("## 予測結果")
    st.write("### 入力")
    input_df = pd.DataFrame(
    {
    "sepal length (cm)": sepal_length,
    "sepal width (cm)": sepal_width,
    "petal length (cm)": petal_length,
    "petal width (cm)": petal_width
    },
    index=["入力値"])
    st.write(input_df)
    streamlitに関するコード〜予測結果の出力部分の作成(入力)〜
    92

    View Slide

  93. st.write("### 出力")
    pred_df = pd.DataFrame({
    "target_name": iris.target_names.tolist(),
    "probability": model.predict_proba(input_df)[0].tolist(),
    })
    target_name = pred_df.sort_values(
    "probability", ascending=False)["target_name"].tolist()[0]
    st.write(target_name)
    streamlitに関するコード〜予測結果の出力部分の作成(出力)〜
    93
    streamlitに関するコードは簡単なものが15件(7種)と多くない

    View Slide

  94. st.write("### 出力")
    pred_df = pd.DataFrame({
    "target_name": iris.target_names.tolist(),
    "probability": model.predict_proba(input_df)[0].tolist(),
    })
    target_name = pred_df.sort_values(
    "probability", ascending=False)["target_name"].tolist()[0]
    st.write(target_name)
    streamlitに関するコード〜予測結果の出力部分の作成(出力)〜
    94
    元のコード+簡単かつ多くないコードでMVPを実現

    View Slide

  95. streamlitのまとめ
    ● Minimum Viable Product(MVP)
    ○ ユーザーに必要最小限の価値を提供できるプロダクト
    ○ 提供しようとしている価値の評価に有用
    ● 必要最小限のウェブアプリケーションを簡単に実現する“streamlit”
    ○ ウェブアプリケーション(特にUI)の開発を簡単に実現するライブラリ
    ○ 大規模で複雑な開発におけるMVPの開発に有用
    (例:機械学習を扱うソフトウェアの開発)
    95

    View Slide

  96. 目次
    ● Pythonによる開発をアップデートするライブラリ(OSS)
    ● 型ヒントとは?
    ● モデルの定義に基づくバリデーションを実現する“pydantic”
    ● スキーマの定義に基づくデータフレームの
    バリデーションを実現する“pandera”
    ● 入出力に関するプロパティの定義に基づく
    Property-based testingを実現する“hypothesis”
    ● 必要最小限のウェブアプリケーションを簡単に実現する“streamlit”
    ● 本発表のまとめ
    96

    View Slide

  97. 本発表のまとめ
    ● Pythonによる開発をアップデートするライブラリ(OSS)
    ○ 開発における理想と現実のギャップの解消のために開発プロセスを改善
    ○ 開発プロセスを改善(開発をアップデート)する有用なライブラリを紹介
    ● pydantic
    ○ モデルの定義に基づくバリデーションを実現
    ● pandera
    ○ スキーマの定義に基づくデータフレームのバリデーションを実現
    ● hypothesis
    ○ 入出力に関するプロパティの定義に基づく
    Property-based testingを実現
    ● streamlit
    ○ 必要最小限のウェブアプリケーションを簡単に実現
    97

    View Slide