Slide 1

Slide 1 text

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

Slide 2

Slide 2 text

自己紹介 ● 名前:桂川 大輝(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)

Slide 3

Slide 3 text

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

Slide 4

Slide 4 text

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

Slide 5

Slide 5 text

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/)

Slide 6

Slide 6 text

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

Slide 7

Slide 7 text

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

Slide 8

Slide 8 text

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

Slide 9

Slide 9 text

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

Slide 10

Slide 10 text

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

Slide 11

Slide 11 text

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

Slide 12

Slide 12 text

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

Slide 13

Slide 13 text

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

Slide 14

Slide 14 text

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

Slide 15

Slide 15 text

型ヒントとは?① ● 型ヒント(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

Slide 16

Slide 16 text

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

Slide 17

Slide 17 text

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

Slide 18

Slide 18 text

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

Slide 19

Slide 19 text

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

Slide 20

Slide 20 text

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

Slide 21

Slide 21 text

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

Slide 22

Slide 22 text

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

Slide 23

Slide 23 text

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

Slide 24

Slide 24 text

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

Slide 25

Slide 25 text

“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)

Slide 26

Slide 26 text

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

Slide 27

Slide 27 text

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以上)

Slide 28

Slide 28 text

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

Slide 29

Slide 29 text

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以上という定義を満たしていない)

Slide 30

Slide 30 text

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() # 前後の半角空白を削除 デコレーターによる詳細な バリデーションの実現も可能 (例:半角空白を含むかを判定)

Slide 31

Slide 31 text

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

Slide 32

Slide 32 text

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

Slide 33

Slide 33 text

データフレームを使っていますか? ● データフレーム(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

Slide 34

Slide 34 text

課題①:意図しない値を格納してしまう 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が格納されている

Slide 35

Slide 35 text

課題②:他者・未来の自分がコードから内容を読み取れない 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にはどんな情報が 格納されている?

Slide 36

Slide 36 text

“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)

Slide 37

Slide 37 text

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

Slide 38

Slide 38 text

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", } )

Slide 39

Slide 39 text

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

Slide 40

Slide 40 text

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

Slide 41

Slide 41 text

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(カテゴリ) ドメインに基づく 現実的な値

Slide 42

Slide 42 text

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

Slide 43

Slide 43 text

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

Slide 44

Slide 44 text

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

Slide 45

Slide 45 text

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

Slide 46

Slide 46 text

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

Slide 47

Slide 47 text

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

Slide 48

Slide 48 text

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

Slide 49

Slide 49 text

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

Slide 50

Slide 50 text

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

Slide 51

Slide 51 text

“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)

Slide 52

Slide 52 text

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

Slide 53

Slide 53 text

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

Slide 54

Slide 54 text

● プロパティ ○ 入力: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型

Slide 55

Slide 55 text

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

Slide 56

Slide 56 text

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)

Slide 57

Slide 57 text

● プロパティ ○ 入力: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型

Slide 58

Slide 58 text

● プロパティ ○ 入力: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以上)

Slide 59

Slide 59 text

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

Slide 60

Slide 60 text

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を満たさない

Slide 61

Slide 61 text

● プロパティ ○ 入力: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以上)

Slide 62

Slide 62 text

● プロパティ ○ 入力: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

Slide 63

Slide 63 text

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!

Slide 64

Slide 64 text

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)

Slide 65

Slide 65 text

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

Slide 66

Slide 66 text

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

Slide 67

Slide 67 text

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

Slide 68

Slide 68 text

“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)

Slide 69

Slide 69 text

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

Slide 70

Slide 70 text

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

Slide 71

Slide 71 text

71

Slide 72

Slide 72 text

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

Slide 73

Slide 73 text

73

Slide 74

Slide 74 text

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

Slide 75

Slide 75 text

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

Slide 76

Slide 76 text

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 ロジスティック回帰により クラス分類を実現する機械学習モデル

Slide 77

Slide 77 text

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

Slide 78

Slide 78 text

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 タイトル フォーム

Slide 79

Slide 79 text

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 (ラベル、下限・上限、 初期値などを設定) フォームのボタン

Slide 80

Slide 80 text

80 フォームを実現

Slide 81

Slide 81 text

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

Slide 82

Slide 82 text

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 フォームのボタンを押した後に表示 データフレームを表示 (記法は文などと同じ)

Slide 83

Slide 83 text

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

Slide 84

Slide 84 text

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

Slide 85

Slide 85 text

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 計算、予測の結果を表示

Slide 86

Slide 86 text

86 予測結果を表示

Slide 87

Slide 87 text

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

Slide 88

Slide 88 text

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

Slide 89

Slide 89 text

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

Slide 90

Slide 90 text

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

Slide 91

Slide 91 text

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

Slide 92

Slide 92 text

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

Slide 93

Slide 93 text

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種)と多くない

Slide 94

Slide 94 text

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を実現

Slide 95

Slide 95 text

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

Slide 96

Slide 96 text

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

Slide 97

Slide 97 text

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