Share
Embed
Pythonによる開発をアップデートするライブラリの紹介
発表:https://event.ospn.jp/osc2022-online-kyoto/session/616338
再編集版の記事:https://daikikatsuragawa.hatenablog.com/entry/2022/08/06/120000
Daiki Katsuragawa
July 30, 2022
Transcript
97 slides
-
自己紹介 • 名前:桂川 大輝(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) -
目次 • Pythonによる開発をアップデートするライブラリ(OSS) • 型ヒントとは? • モデルの定義に基づくバリデーションを実現する“pydantic” • スキーマの定義に基づくデータフレームの バリデーションを実現する“pandera”
• 入出力に関するプロパティの定義に基づく Property-based testingを実現する“hypothesis” • 必要最小限のウェブアプリケーションを簡単に実現する“streamlit” • 本発表のまとめ 3 -
目次 • Pythonによる開発をアップデートするライブラリ(OSS) • 型ヒントとは? • モデルの定義に基づくバリデーションを実現する“pydantic” • スキーマの定義に基づくデータフレームの バリデーションを実現する“pandera”
• 入出力に関するプロパティの定義に基づく Property-based testingを実現する“hypothesis” • 必要最小限のウェブアプリケーションを簡単に実現する“streamlit” • 本発表のまとめ 4 -
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/) -
開発における理想と現実 7 理想 • 成果物が正常に 動作する • 開発の保守性が 高い(複数人での 開発が可能)
現実 • 成果物に不具合が 生じることがある • 開発の保守性が 高いという自信が 無い -
開発における理想と現実 8 理想 • 成果物が正常に 動作する • 開発の保守性が 高い(複数人での 開発が可能)
現実 • 成果物に不具合が 生じることがある • 開発の保守性が 高いという自信が 無い ギャップ 開発者が保守性の低い コーディングをする 開発者が不具合を 埋め込んでしまう 開発における理想と現実にギャップが存在 -
理想と現実のギャップを解消する手段 • 個人の能力の向上 ◦ 個人に依存するため実現の難易度が不確実 • 開発プロセスの改善 ◦ 個人に依存しない仕組みの導入により実現 9
開発プロセスの改善により理想と現実のギャップを解消 -
開発プロセスの課題の改善 11 ギャップ • 開発者が不具合を 埋め込んでしまう • 開発者が保守性の 低いコーディング をする
開発プロセスの課題 • 不具合を埋め込み にくい仕組みが 必要 • 保守性が高くなる 仕組みが必要 有用なライブラリを 導入し利用 変換 Pythonによる開発プロセスを改善 (開発をアップデート)する有用なライブラリを紹介 -
本発表で紹介するライブラリ • pydantic ◦ モデルの定義に基づくバリデーションを実現 • pandera ◦ スキーマの定義に基づくデータフレームのバリデーションを実現 •
hypothesis ◦ 入出力に関するプロパティの定義に基づく Property-based testingを実現 • streamlit ◦ 必要最小限のウェブアプリケーションを簡単に実現 12 -
本発表で話すこと/話さないこと 話すこと • ライブラリの概要 • ライブラリの利点 • ライブラリの簡単な利用例 話さないこと •
ライブラリの欠点(リスク) • ライブラリの詳細な利用例 • 紹介するライブラリ間の関連 13 本発表を“知るキッカケ”としてください! -
目次 • Pythonによる開発をアップデートするライブラリ(OSS) • 型ヒントとは? • モデルの定義に基づくバリデーションを実現する“pydantic” • スキーマの定義に基づくデータフレームの バリデーションを実現する“pandera”
• 入出力に関するプロパティの定義に基づく Property-based testingを実現する“hypothesis” • 必要最小限のウェブアプリケーションを簡単に実現する“streamlit” • 本発表のまとめ 14 -
型ヒントとは?① • 型ヒント(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 -
型ヒントの記述方法〜基本〜 • 変数:直後に「:」と型を記述 • 関数の引数:各引数の直後に「:」と型を記述 • 関数の戻り値:「:」の直前に「->」と型を記述 17 name: str
= "パイソン 太郎" def calculate_division(numerator: int, denominator: int) -> float: return numerator / denominator -
型ヒントの記述方法〜発展〜 • 辞書(Dict)、リスト(List)などの要素の型 • 複数の型(Union) • 必須ではない(Optional) • 任意(Any)の型 •
値が存在しない(None) • プリミティブではない型 ◦ ライブラリのクラス ◦ 自作のクラス 18 -
型ヒントとは?〜まとめ〜 • 型ヒント(Type Hints) ◦ 変数の定義、関数の引数や戻り値の定義に型のヒントを付与するという記法 (導入:Python3.5〜) • 型ヒントの振る舞い ◦
実行時に型のチェックはしない ◦ サードパーティーのツールと組み合わせて静的チェック (例:mypy、統合開発環境) 19 -
型ヒントとは?〜まとめ〜 • 型ヒント(Type Hints) ◦ 変数の定義、関数の引数や戻り値の定義に型のヒントを付与するという記法 (導入:Python3.5〜) • 型ヒントの振る舞い ◦
実行時に型のチェックはしない ◦ サードパーティーのツールと組み合わせて静的チェック (例:mypy、統合開発環境) 20 型ヒントを活用した“pydantic”と”pandera”を紹介 -
目次 • Pythonによる開発をアップデートするライブラリ(OSS) • 型ヒントとは? • モデルの定義に基づくバリデーションを実現する“pydantic” • スキーマの定義に基づくデータフレームの バリデーションを実現する“pandera”
• 入出力に関するプロパティの定義に基づく Property-based testingを実現する“hypothesis” • 必要最小限のウェブアプリケーションを簡単に実現する“streamlit” • 本発表のまとめ 21 -
複数の部品によって構成されるシステムを開発していますか? • 多くのシステムは複数の部品によって構成 ◦ 部品:モジュール・クラス・メソッドなど • 各部品が役割と責任を持ち連携 ◦ 各部品の入出力を把握しておけばシステムの全ての把握は不要 ◦
入出力:int、str、JSON/辞書(構造) 22 部品A 部品B int str 部品Y 部品Z JSON/辞書 JSON/辞書 -
課題②:入出力で扱うデータが正しくない • 他の部品の出力を把握しているものの 期待と異なる、正しくないデータを受け取る場面も… 24 部品Y 部品Z JSON ◯△□のデータを 格納したJSONを
渡します。 ドメイン的に ありえないデータが来た -
“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) -
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以上) -
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 -
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以上という定義を満たしていない) -
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() # 前後の半角空白を削除 デコレーターによる詳細な バリデーションの実現も可能 (例:半角空白を含むかを判定) -
pydanticのまとめ • 複数の部品によって構成されるシステムの開発 ◦ 各部品(モジュール・クラス・メソッドなど)が役割と責任を持ち連携 ◦ 課題①:入出力で扱うデータの定義がわからない ◦ 課題②:入出力で扱うデータが正しくない •
課題を解決する“pydantic” ◦ モデルを定義することでデータのバリデーションを実現するライブラリ ◦ モデルの定義により把握が可能に(課題①の解決) ◦ モデルの定義に基づくバリデーションにより正しいデータのみが存在する状態に (課題②の解決) 31 -
目次 • Pythonによる開発をアップデートするライブラリ(OSS) • 型ヒントとは? • モデルの定義に基づくバリデーションを実現する“pydantic” • スキーマの定義に基づくデータフレームの バリデーションを実現する“pandera”
• 入出力に関するプロパティの定義に基づく Property-based testingを実現する“hypothesis” • 必要最小限のウェブアプリケーションを簡単に実現する“streamlit” • 本発表のまとめ 32 -
データフレームを使っていますか? • データフレーム(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 -
課題①:意図しない値を格納してしまう 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が格納されている -
課題②:他者・未来の自分がコードから内容を読み取れない 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にはどんな情報が 格納されている? -
“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) -
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", } ) -
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 -
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 -
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(カテゴリ) ドメインに基づく 現実的な値 -
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 -
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 -
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 -
panderaの利用例〜バリデーション(エラー)〜 45 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 -
panderaのまとめ • データフレーム(pandas.DataFrame) ◦ 表形式のデータの取り込み、加工、集計、分析に利用 ◦ データ分析/機械学習などで活躍 ◦ 課題①:意図しない値を格納してしまう ◦
課題②:他者・未来の自分がコードから内容を読み取れない • 課題を解決する“pandera” ◦ データフレームのスキーマ(構造)を定義することで データフレームのバリデーションを実現するライブラリ ◦ バリデーションにより意図しない値の格納を防ぐ(課題①の解決) ◦ スキーマを明示的に記述することでコードから内容の読み取りが可能に (課題②の解決) 46 -
目次 • Pythonによる開発をアップデートするライブラリ(OSS) • 型ヒントとは? • モデルの定義に基づくバリデーションを実現する“pydantic” • スキーマの定義に基づくデータフレームの バリデーションを実現する“pandera”
• 入出力に関するプロパティの定義に基づく Property-based testingを実現する“hypothesis” • 必要最小限のウェブアプリケーションを簡単に実現する“streamlit” • 本発表のまとめ 47 -
単体テスト(Example-based testing) • 単体テスト ◦ プログラムを構成する小さな部品(関数/メソッド)が 意図した振る舞いか否かを検証するテスト ◦ Pythonではunittestやpytestというテスト用のフレームワークが有名 •
Example-based testing(一般的によくみられる単体テスト) ◦ 関数やメソッドに対して、任意の基準(例:ランダム/境界値テスト)で 入力値を選択し、出力値や事後状態の確認により振る舞いを検証 ◦ 例:除算するメソッドに対して、「3」と「12」という入力を与えて 「4」が出力されることを確認 48 -
“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) -
• 関数“calculate_subtraction” ◦ 概要:入力された2つの整数を減算 ◦ 入力:整数(int)×2 ◦ 出力:整数(int) hypothesisの利用例〜テスト対象の関数〜 53
# main.py def calculate_subtraction(a: int, b: int): return a - b -
• プロパティ ◦ 入力: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型 -
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 -
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) -
• プロパティ ◦ 入力: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型 -
• プロパティ ◦ 入力: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以上) -
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 -
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を満たさない -
• プロパティ ◦ 入力: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以上) -
• プロパティ ◦ 入力: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 -
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! -
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) -
hypothesisのまとめ • Example-based testing(一般的によくみられる単体テスト) ◦ 関数やメソッドに対して、任意の基準(例:ランダム/境界値テスト)で 入力値を選択し、出力値や事後状態の確認により振る舞いを検証 ◦ 課題:テストの品質が実装者に依存 •
Property-based testing ◦ 入出力に関するプロパティを定義し多数の入力を与え検証 • 課題を解決する“hypothesis” ◦ 入出力に関するプロパティの定義に基づく Property-based testingを実現するライブラリ ◦ 課題:Property-based testingの導入により テスト作成者に依存しないテストの品質を担保 65 -
目次 • Pythonによる開発をアップデートするライブラリ(OSS) • 型ヒントとは? • モデルの定義に基づくバリデーションを実現する“pydantic” • スキーマの定義に基づくデータフレームの バリデーションを実現する“pandera”
• 入出力に関するプロパティの定義に基づく Property-based testingを実現する“hypothesis” • 必要最小限のウェブアプリケーションを簡単に実現する“streamlit” • 本発表のまとめ 66 -
Minimum Viable Productとは? • Minimum Viable Product(MVP) ◦ ユーザーに必要最小限の価値を提供できるプロダクト ◦
提供しようとしている価値の評価に有用 (例:自動車による「移動」という価値を評価する前に 「移動」という価値を提供するスケートボードで検証) 67 早期に必要最小限の価値の提供が可能な状態にすることが大事 -
“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) -
デモンストレーション:streamlitで実現する予測アプリケーション • 概要:学習済みの機械学習モデルによりアヤメの品種を予測 • 背景(シナリオ):元々は分析・検証をNotebookで実施していたが そもそもの価値を評価するためにMVPを開発 • 仕様: ◦ 入力:がく片の長さ(sepal
length)/がく片の幅(sepal width) 花弁の長さ(petal length)/花弁の幅(petal width) ◦ 出力:最も可能性が高いアヤメの品種 (setosa/versicolor/virginica) 69 〇〇ですね💡 -
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 ロジスティック回帰により クラス分類を実現する機械学習モデル -
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 -
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 タイトル フォーム -
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 (ラベル、下限・上限、 初期値などを設定) フォームのボタン -
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 -
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 フォームのボタンを押した後に表示 データフレームを表示 (記法は文などと同じ) -
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 -
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 計算、予測の結果を表示 -
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 -
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 -
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 -
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種)と多くない -
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を実現 -
streamlitのまとめ • Minimum Viable Product(MVP) ◦ ユーザーに必要最小限の価値を提供できるプロダクト ◦ 提供しようとしている価値の評価に有用 •
必要最小限のウェブアプリケーションを簡単に実現する“streamlit” ◦ ウェブアプリケーション(特にUI)の開発を簡単に実現するライブラリ ◦ 大規模で複雑な開発におけるMVPの開発に有用 (例:機械学習を扱うソフトウェアの開発) 95 -
目次 • Pythonによる開発をアップデートするライブラリ(OSS) • 型ヒントとは? • モデルの定義に基づくバリデーションを実現する“pydantic” • スキーマの定義に基づくデータフレームの バリデーションを実現する“pandera”
• 入出力に関するプロパティの定義に基づく Property-based testingを実現する“hypothesis” • 必要最小限のウェブアプリケーションを簡単に実現する“streamlit” • 本発表のまとめ 96 -
本発表のまとめ • Pythonによる開発をアップデートするライブラリ(OSS) ◦ 開発における理想と現実のギャップの解消のために開発プロセスを改善 ◦ 開発プロセスを改善(開発をアップデート)する有用なライブラリを紹介 • pydantic ◦
モデルの定義に基づくバリデーションを実現 • pandera ◦ スキーマの定義に基づくデータフレームのバリデーションを実現 • hypothesis ◦ 入出力に関するプロパティの定義に基づく Property-based testingを実現 • streamlit ◦ 必要最小限のウェブアプリケーションを簡単に実現 97