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 full-size 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 full-size slide

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

    View full-size slide

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

    View full-size 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 full-size slide

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

    View full-size slide

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

    View full-size slide

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

    View full-size slide

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

    View full-size slide

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

    View full-size slide

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

    View full-size slide

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

    View full-size slide

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

    View full-size slide

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

    View full-size 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 full-size slide

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

    View full-size slide

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

    View full-size slide

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

    View full-size slide

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

    View full-size slide

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

    View full-size slide

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

    View full-size slide

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

    View full-size slide

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

    View full-size slide

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

    View full-size 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 full-size slide

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

    View full-size 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 full-size 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 full-size 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 full-size 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 full-size slide

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

    View full-size slide

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

    View full-size 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 full-size 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 full-size 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 full-size 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 full-size slide

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

    View full-size 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 full-size 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 full-size 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 full-size 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 full-size 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 full-size 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 full-size 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 full-size 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 full-size slide

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

    View full-size slide

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

    View full-size slide

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

    View full-size slide

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

    View full-size slide

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

    View full-size 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 full-size slide

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

    View full-size slide

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

    View full-size 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 full-size 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 full-size 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 full-size 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 full-size 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 full-size 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 full-size 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 full-size 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 full-size 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 full-size 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 full-size 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 full-size slide

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

    View full-size slide

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

    View full-size slide

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

    View full-size 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 full-size slide

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

    View full-size slide

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

    View full-size slide

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

    View full-size slide

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

    View full-size slide

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

    View full-size slide

  74. 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 full-size slide

  75. 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 full-size slide

  76. 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 full-size 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の利用例〜入力フォームとボタンの作成〜
    79
    補足文
    数値入力欄×4
    (ラベル、下限・上限、
    初期値などを設定)
    フォームのボタン

    View full-size slide

  78. 80
    フォームを実現

    View full-size slide

  79. 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 full-size slide

  80. 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 full-size slide

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

    View full-size slide

  82. 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 full-size slide

  83. 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 full-size slide

  84. 86
    予測結果を表示

    View full-size slide

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

    View full-size slide

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

    View full-size slide

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

    View full-size slide

  88. 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 full-size slide

  89. 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 full-size slide

  90. 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 full-size slide

  91. 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 full-size slide

  92. 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 full-size slide

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

    View full-size slide

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

    View full-size slide

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

    View full-size slide