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

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

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

4b46b5f1acab7f5a64c0036c1fc39f31?s=128

Daiki Katsuragawa

July 30, 2022
Tweet

More Decks by Daiki Katsuragawa

Other Decks in Programming

Transcript

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

  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)
  3. 目次 • Pythonによる開発をアップデートするライブラリ(OSS) • 型ヒントとは? • モデルの定義に基づくバリデーションを実現する“pydantic” • スキーマの定義に基づくデータフレームの バリデーションを実現する“pandera”

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

    • 入出力に関するプロパティの定義に基づく Property-based testingを実現する“hypothesis” • 必要最小限のウェブアプリケーションを簡単に実現する“streamlit” • 本発表のまとめ 4
  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/)
  6. 開発における理想と現実 6 理想 • 成果物が正常に 動作する • 開発の保守性が 高い(複数人での 開発が可能)

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

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

    現実 • 成果物に不具合が 生じることがある • 開発の保守性が 高いという自信が 無い ギャップ 開発者が保守性の低い コーディングをする 開発者が不具合を 埋め込んでしまう 開発における理想と現実にギャップが存在
  9. 理想と現実のギャップを解消する手段 • 個人の能力の向上 ◦ 個人に依存するため実現の難易度が不確実 • 開発プロセスの改善 ◦ 個人に依存しない仕組みの導入により実現 9

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

    理想と現実のギャップ=開発プロセスの課題
  11. 開発プロセスの課題の改善 11 ギャップ • 開発者が不具合を 埋め込んでしまう • 開発者が保守性の 低いコーディング をする

    開発プロセスの課題 • 不具合を埋め込み にくい仕組みが 必要 • 保守性が高くなる 仕組みが必要 有用なライブラリを 導入し利用 変換 Pythonによる開発プロセスを改善 (開発をアップデート)する有用なライブラリを紹介
  12. 本発表で紹介するライブラリ • pydantic ◦ モデルの定義に基づくバリデーションを実現 ◦ 堅牢性と可読性の向上 • pandera ◦

    スキーマの定義に基づくデータフレームのバリデーションを実現 ◦ 堅牢性と可読性の向上 • hypothesis ◦ 入出力に関するプロパティの定義に基づく Property-based testingを実現 ◦ 堅牢性と可読性の向上 • streamlit ◦ 必要最小限のウェブアプリケーションを簡単に実現 ◦ 早期に提供価値の評価を実現 12
  13. 本発表で紹介するライブラリ(話すこと/話さないこと) • 話すこと ◦ ライブラリの概要 ◦ ライブラリの利点 ◦ 簡単な利用例 •

    話さないこと ◦ 詳細な利用例 13 本発表を“知るキッカケ”としてください!
  14. 目次 • Pythonによる開発をアップデートするライブラリ(OSS) • 型ヒントとは? • モデルの定義に基づくバリデーションを実現する“pydantic” • スキーマの定義に基づくデータフレームの バリデーションを実現する“pandera”

    • 入出力に関するプロパティの定義に基づく Property-based testingを実現する“hypothesis” • 必要最小限のウェブアプリケーションを簡単に実現する“streamlit” • 本発表のまとめ 14
  15. 型ヒントとは?① • 型ヒント(Type Hints[4]) ◦ 変数の定義、関数の引数や戻り値の定義に型のヒントを付与すると、いう記法 (導入:Python3.5〜) 15 [4] PEP

    484 – Type Hints | peps.Python.org(https://peps.Python.org/pep-0484/) name: str = "パイソン 太郎" def divide(numerator: int, denominator: int) -> float: return numerator / denominator コードの可読性の向上
  16. 型ヒントとは?② • 型ヒントの振る舞い ◦ 実行時に型のチェックはしない(誤っていても動作) ◦ サードパーティーのツール(例:mypy、統合開発環境)と 組み合わせることで静的チェックを実現 16 サードパーティーのツールと組み合わせることで堅牢性の向上

  17. 型ヒントの記述方法〜基本〜 • 変数:直後に「:」と型を記述 • 関数の引数:各引数の直後に「:」と型を記述 • 関数の戻り値:「:」の直前に「->」と型を記述 17 name: str

    = "パイソン 太郎" def divide(numerator: int, denominator: int) -> float: return numerator / denominator
  18. 型ヒントの記述方法〜複雑な型の指定①〜 • 辞書、リスト型などの要素の型の指定 • 複数の型の指定 18 from typing import Dict

    user_and_age: Dict[str, int] = {'パイソン 太郎': 20} from typing import Union str_or_int: Union[str, int] = 0 # or パイソン 太郎"
  19. 型ヒントの記述方法〜複雑な型の指定②〜 • 必須ではない(Optional)という指定 • 任意(Any)という指定 19 from typing import Optional

    optional_int: Optional[int] = None # or 0 from typing import Any any: Any = 0 # or "パイソン 太郎", None, {"パイソン 太郎": 20} ...
  20. 型ヒントの記述方法〜複雑な型の指定③〜 • 関数の返り値が存在しない(None)という指定 20 from typing import Any def return_none(input:

    Any) -> None: pass
  21. 型ヒントの記述方法〜発展〜 • プリミティブな型以外の指定も可能 ◦ ライブラリのクラス(例:先ほどまでの例におけるimportしたもの) ◦ 自作のクラス 21 class SampleClass:

    pass sample_class: SampleClass = SampleClass() def return_input(input: SampleClass) -> SampleClass: return input
  22. 型ヒントとは?〜まとめ〜 • 型ヒント(Type Hints) ◦ 変数の定義、関数の引数や戻り値の定義に型のヒントを付与すると、いう記法 (導入:Python3.5〜) • 型ヒントの振る舞い ◦

    実行時に型のチェックはしない(誤っていても動作) ◦ サードパーティーのツール(例:mypy、統合開発環境)と 組み合わせることで静的チェックを実現 22 可読性と堅牢性の向上に期待
  23. 型ヒントとは?〜まとめ〜 • 型ヒント(Type Hints) ◦ 変数の定義、関数の引数や戻り値の定義に型のヒントを付与すると、いう記法 (導入:Python3.5〜) • 型ヒントの振る舞い ◦

    実行時に型のチェックはしない(誤っていても動作) ◦ サードパーティーのツール(例:mypy、統合開発環境)と 組み合わせることで静的チェックを実現 23 型ヒントを活かして可読性と堅牢性を向上させる “pydantic”と”pandera”を紹介
  24. 目次 • Pythonによる開発をアップデートするライブラリ(OSS) • 型ヒントとは? • モデルの定義に基づくバリデーションを実現する“pydantic” • スキーマの定義に基づくデータフレームの バリデーションを実現する“pandera”

    • 入出力に関するプロパティの定義に基づく Property-based testingを実現する“hypothesis” • 必要最小限のウェブアプリケーションを簡単に実現する“streamlit” • 本発表のまとめ 24
  25. 複数の部品によって構成されるシステムを開発していますか? • 多くのシステムは複数の部品によって構成 ◦ 部品:モジュール・クラス・メソッドなど • 各部品が役割と責任を持ち連携 ◦ 各部品の入出力を把握しておけばシステムの全ての把握は不要 ◦

    入出力:int、str、JSON/辞書(構造) 25 部品A 部品B int str 部品Y 部品Z JSON/辞書 JSON/辞書
  26. 課題①:入出力で扱うデータの定義がわからない(可読性の課題) • 他の部品の出力を入力として受け取る時、 他の部品の実装を深く見る必要がある場面も… (例:JSON/辞書といった型まではわかるが構造はわからない) 26 部品Y 部品Z JSON ???のデータを

    格納したJSONを 渡します。 何が格納されている? キーは?
  27. 課題②:入出力で扱うデータが正しくない(堅牢性の課題) • 他の部品の出力を把握しているものの 期待と異なる、正しくないデータを受け取る場面も… (例:年齢が「-1」、型が異なる、期待しているデータがない) 27 部品Y 部品Z JSON ◯△□のデータを

    格納したJSONを 渡します。 ドメイン的に ありえないデータが来た
  28. “pydantic” • “pydantic[5]”とは ◦ モデルを定義することでデータのバリデーションを実現するライブラリ ◦ 辞書型・JSONとのシリアライズ/デシリアライズも可能で部品の入出力に有用 ◦ ライセンス:MIT License[6]

    28 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)
  29. 課題を解決する“pydantic” • 課題①:入出力で扱うデータの定義がわからない(可読性) ◦ モデルの定義により把握が可能に • 課題②:入出力で扱うデータが正しくない(堅牢性) ◦ 定義に基づくバリデーションにより正しいデータのみが存在する状態に 29

  30. pydanticの利用例〜モデル(Userクラス)の定義①〜 30 from pydantic import BaseModel, Field class User(BaseModel): name:

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

    str age: int = Field(ge=20) external_data = { 'name': 'パイソン 太郎', 'age': 19 } user = User.parse_obj(external_data) 有効範囲外の値 ValidationError
  32. pydanticの利用例〜ValidationErrorの中身〜 32 (省略) 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以上という定義を満たしていない)
  33. pydanticの利用例〜Userクラスを引数とする関数①〜 33 from pydantic import validate_arguments @validate_arguments def input_user(user :

    User) -> None: pass external_data = { 'name': 'パイソン 太郎', 'age': 20 } input_user(external_data) 引数のバリデーションを 実行するデコレーター pydanticで定義した クラスも引数として指定
  34. pydanticの利用例〜Userクラスを引数とする関数②〜 34 from pydantic import validate_arguments @validate_arguments def input_user(user :

    User) -> None: pass external_data = { 'name': 'パイソン 太郎', 'age': 19 } input_user(external_data) 有効範囲外の値 ValidationError
  35. pydanticの利用例〜Userクラスを返り値(辞書型)とする関数①〜 35 output_user().dict() def output_user() -> User: external_data = {

    'name': 'パイソン 太郎', 'age': 20 } return User.parse_obj(external_data) やりとりしやすい型式(辞書型)に変換 {'age': 20, 'name': 'パイソン 太郎'}
  36. pydanticの利用例〜Userクラスを返り値(辞書型)とする関数②〜 36 def output_user() -> User: external_data = { 'name':

    'パイソン 太郎', 'age': 19 } return User.parse_obj(external_data) output_user().dict() 有効範囲外の値 ValidationError
  37. pydanticの利用例〜デコレーターによる詳細なバリデーションの実現〜 37 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() # 前後の半角空白を削除 デコレーターによる詳細な バリデーションの実現も可能 (例:半角空白を含むかを判定)
  38. pydanticのまとめ • 複数の部品によって構成されるシステムの開発 ◦ 各部品(モジュール・クラス・メソッドなど)が役割と責任を持ち連携 ◦ 課題①:入出力で扱うデータの定義がわからない(可読性の課題) ◦ 課題②:入出力で扱うデータが正しくない(堅牢性の課題) •

    課題を解決する“pydantic” ◦ モデルを定義することでデータのバリデーションを実現するライブラリ ◦ 課題①:モデルの定義により把握が可能に ◦ 課題②:定義に基づくバリデーションにより正しいデータのみが存在する状態に 38
  39. 目次 • Pythonによる開発をアップデートするライブラリ(OSS) • 型ヒントとは? • モデルの定義に基づくバリデーションを実現する“pydantic” • スキーマの定義に基づくデータフレームの バリデーションを実現する“pandera”

    • 入出力に関するプロパティの定義に基づく Property-based testingを実現する“hypothesis” • 必要最小限のウェブアプリケーションを簡単に実現する“streamlit” • 本発表のまとめ 39
  40. データフレームを使っていますか? • データフレーム(pandas.DataFrame[7]) ◦ 表型式のデータの取り込み、加工、集計、分析に利用(例:機械学習) ◦ 例:アヤメ(iris)の特徴と種類(scikit-learn[8]より) 40 [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
  41. 課題①:意図しない値を格納してしまう 41 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が格納されている
  42. 課題②:他者・未来の自分がコードから内容を読み取れない 42 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にはどんな情報が 格納されている?
  43. “pandera” • “pandera[9]”とは ◦ データフレームのスキーマ(構造)を定義することで データフレームのバリデーションを実現するライブラリ ◦ ライセンス:MIT License[10] ◦

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

    Models”を紹介 44
  45. panderaの利用例〜アヤメ(iris)のデータセットの準備〜 45 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", } )
  46. panderaの利用例〜データの確認①〜 46 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
  47. panderaの利用例〜データの確認②〜 47 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
  48. 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の利用例〜スキーマの定義〜 48 0/1/2(カテゴリ) ドメインに基づく 現実的な値
  49. panderaの利用例〜バリデーション〜 49 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
  50. 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の利用例〜意図しないレコードの追加〜 50
  51. panderaの利用例〜意図しないレコードの確認〜 51 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
  52. panderaの利用例〜バリデーション(エラー)〜 52 invalid_iris = IrisSchema.validate(invalid_iris) --------------------------------------------------------------------------- SchemaError Traceback (most recent

    call last) (省略) SchemaError: <Schema Column(name=target, type=DataType(int64))> failed element-wise validator 0: <Check isin: isin({0, 1, 2})> failure cases: index failure_case 0 150 3 indexが150のレコードでエラー SchemaError
  53. panderaのまとめ • データフレーム(pandas.DataFrame) ◦ 表形式のデータの取り込み、加工、集計、分析に利用 ◦ データ分析/機械学習などで活躍 ◦ 課題①:意図しない値を格納してしまう ◦

    課題②:他者・未来の自分がコードから内容を読み取れない • 課題を解決する“pandera” ◦ データフレームのスキーマ(構造)を定義することで データフレームのバリデーションを実現するライブラリ ◦ 課題①:バリデーションにより意図しない値の格納を防ぐ ◦ 課題②:スキーマを明示的に記述することでコードから内容の読み取りが可能に 53
  54. 目次 • Pythonによる開発をアップデートするライブラリ(OSS) • 型ヒントとは? • モデルの定義に基づくバリデーションを実現する“pydantic” • スキーマの定義に基づくデータフレームの バリデーションを実現する“pandera”

    • 入出力に関するプロパティの定義に基づく Property-based testingを実現する“hypothesis” • 必要最小限のウェブアプリケーションを簡単に実現する“streamlit” • 本発表のまとめ 54
  55. 単体テスト(Example-based testing) • 単体テスト ◦ プログラムを構成する小さな部品(関数/メソッド)が 意図した振る舞いか否かを検証するテスト ◦ Pythonではunittestやpytestというテスト用のフレームワークが有名 •

    Example-based testing(一般的によくみられる単体テスト) ◦ 関数やメソッドに対して、任意の基準(例:ランダム/境界値テスト)で 入力値を選択し、出力値や事後状態の確認により振る舞いを検証 ◦ 例:除算するメソッドに対して、「3」と「12」という入力を与えて 「4」が出力されることを確認 55
  56. 課題:テストの品質がテスト作成者に依存 • テスト作成者の想定内の内容で不十分になってしまう危険 ◦ 例①:境界値テストなどの手法を知らず要件を満たさない検証となった ◦ 例②:除算をするメソッドに対して、適当な入出力での検証は実施しているが エッジケースを見逃した(分母が「0」の場合、入力が「Null(NaN)」) • 冗長なテストを書いてしまう危険

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

    57
  58. “hypothesis” • “hypothesis[11]”とは ◦ 入出力に関するプロパティの定義に基づく Property-based testingを実現するライブラリ ◦ ライセンス:Mozilla Public

    License Version 2.0[12] 58 !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)
  59. 課題を解決する“hypothesis” • 課題:テストの品質がテスト作成者に依存 ◦ Property-based testingの導入により テスト作成者に依存しないテストの品質を担保 59

  60. • 関数“calculate_subtraction” ◦ 概要:入力された2つの整数を減算 ◦ 入力:整数(int)×2 ◦ 出力:整数(int) hypothesisの利用例〜テスト対象の関数〜 60

    # main.py def calculate_subtraction(a: int, b: int): return a - b
  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(), y=strategies.integers()) def test_calculate_subtraction(x, y): assert 0 < calculate_subtraction(x, y) 出力:0より大きい整数 入力:それぞれint型
  62. • hypothesisの利用例〜エラー例①〜 62 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
  63. • hypothesisの利用例〜エラー例①〜 63 pytest test_main.py Falsifying example: test_calculate_subtraction( x=0, y=0,

    ) (省略) FAILED test_main.py::test_calculate_subtraction - assert 0 < 0 入力として想定していた 自然数ではない値(0)
  64. • プロパティ ◦ 入力:xとy(それぞれ自然数かつx>y)(それぞれint型の数値) ◦ 出力:0より大きい hypothesisの利用例〜単体テストの実装〜 64 # 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型
  65. • プロパティ ◦ 入力:xとy(それぞれ自然数かつx>y)(それぞれint型の数値) ◦ 出力:0より大きい hypothesisの利用例〜単体テストの修正〜 65 # 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以上)
  66. hypothesisの利用例〜エラー例②〜 66 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
  67. hypothesisの利用例〜エラー例②〜 67 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を満たさない
  68. • プロパティ ◦ 入力:xとy(それぞれ自然数かつx>y) ◦ 出力:0より大きい hypothesisの利用例〜単体テストの修正〜 68 # 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以上)
  69. • プロパティ ◦ 入力:xとy(それぞれ自然数かつx>y) ◦ 出力:0より大きい hypothesisの利用例〜単体テストの修正〜 69 # 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
  70. ◦ hypothesisの利用例〜修正した単体テストの実行(成功)〜 70 # 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!
  71. hypothesisの利用例〜実際に検証された内容〜 71 # 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)
  72. hypothesisのまとめ • Example-based testing(一般的によくみられる単体テスト) ◦ 関数やメソッドに対して、任意の基準(例:ランダム/境界値テスト)で 入力値を選択し、出力値や事後状態の確認により振る舞いを検証 ◦ 課題:テストの品質が実装者に依存 •

    Property-based testing ◦ 入出力に関するプロパティを定義し多数の入力を与え検証 • 課題を解決する“hypothesis” ◦ 入出力に関するプロパティの定義に基づく Property-based testingを実現するライブラリ ◦ 課題:Property-based testingの導入により テスト作成者に依存しないテストの品質を担保 72
  73. 目次 • Pythonによる開発をアップデートするライブラリ(OSS) • 型ヒントとは? • モデルの定義に基づくバリデーションを実現する“pydantic” • スキーマの定義に基づくデータフレームの バリデーションを実現する“pandera”

    • 入出力に関するプロパティの定義に基づく Property-based testingを実現する“hypothesis” • 必要最小限のウェブアプリケーションを簡単に実現する“streamlit” • 本発表のまとめ 73
  74. Minimum Viable Productとは? • Minimum Viable Product(MVP) ◦ ユーザーに必要最小限の価値を提供できるプロダクト ◦

    提供しようとしている価値の評価に有用 (例:自動車による「移動」という価値を評価する前に    「移動」という価値を提供するスケートボードで検証) 74 早期に必要最小限の価値の提供が可能な状態にすることが大事
  75. “streamlit” • “streamlit[13]”とは ◦ ウェブアプリケーション(特にUI)の開発を簡単に実現するライブラリ ◦ 大規模で複雑な開発におけるMVPの開発に有用 (例:機械学習を扱うソフトウェアの開発) ◦ ライセンス:Apache

    License 2.0[14] 75 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)
  76. デモンストレーション:streamlitで実現する予測アプリケーション • 概要:学習済みの機械学習モデルによりアヤメの品種を予測 • 背景(シナリオ):元々は分析・検証をNotebookで実施していたが そもそもの価値を評価するためにMVPを開発 • 仕様: ◦ 入力:がく片の長さ(sepal

    length)/がく片の幅(sepal width) 花弁の長さ(petal length)/花弁の幅(petal width) ◦ 出力:最も可能性が高いアヤメの品種 (setosa/versicolor/virginica) 76 〇〇ですね💡
  77. デモンストレーションのために用意したもの&実行コマンド 77 streamlit run app.py app.py ひとつのPythonファイルのみでウェブアプリケーションを実現

  78. 78

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

  80. 80

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

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

  83. 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の利用例〜機械学習モデルの作成〜 83 ロジスティック回帰により クラス分類を実現する機械学習モデル
  84. 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の利用例〜入力フォームとボタンの作成〜 84
  85. 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の利用例〜入力フォームとボタンの作成〜 85 タイトル フォーム
  86. 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の利用例〜入力フォームとボタンの作成〜 86 補足文 数値入力欄×4 (ラベル、下限・上限、 初期値などを設定) フォームのボタン
  87. 87 フォームを実現

  88. 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の利用例〜予測結果の出力部分の作成(入力)〜 88
  89. 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の利用例〜予測結果の出力部分の作成(入力)〜 89 フォームのボタンを押した後に表示 データフレームを表示 (記法は文などと同じ)
  90. 90 サブミット後の出力を実現

  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の利用例〜予測結果の出力部分の作成(出力)〜 91
  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の利用例〜予測結果の出力部分の作成(出力)〜 92 計算、予測の結果を表示
  93. 93 予測結果を表示

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

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

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

  97. 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に関するコード〜モデルの作成〜 97
  98. 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に関するコード〜入力フォームとボタンの作成〜 98
  99. 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に関するコード〜予測結果の出力部分の作成(入力)〜 99
  100. 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に関するコード〜予測結果の出力部分の作成(出力)〜 100 streamlitに関するコードは簡単なものが15件(7種)と多くない
  101. 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に関するコード〜予測結果の出力部分の作成(出力)〜 101 元のコード+簡単かつ多くないコードでMVPを実現
  102. streamlitのまとめ • Minimum Viable Product(MVP) ◦ ユーザーに必要最小限の価値を提供できるプロダクト ◦ 提供しようとしている価値の評価に有用 •

    必要最小限のウェブアプリケーションを簡単に実現する“streamlit” ◦ ウェブアプリケーション(特にUI)の開発を簡単に実現するライブラリ ◦ 大規模で複雑な開発におけるMVPの開発に有用 (例:機械学習を扱うソフトウェアの開発) 102
  103. 目次 • Pythonによる開発をアップデートするライブラリ(OSS) • 型ヒントとは? • モデルの定義に基づくバリデーションを実現する“pydantic” • スキーマの定義に基づくデータフレームの バリデーションを実現する“pandera”

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

    モデルの定義に基づくバリデーションを実現 • pandera ◦ スキーマの定義に基づくデータフレームのバリデーションを実現 • hypothesis ◦ 入出力に関するプロパティの定義に基づく Property-based testingを実現 • streamlit ◦ 必要最小限のウェブアプリケーションを簡単に実現 104