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

データに関する堅牢性と可読性を向上させるpydanticとpanderaの活用方法の提案

Sponsored · Ship Features Fearlessly Turn features on and off without deploys. Used by thousands of Ruby developers.

 データに関する堅牢性と可読性を向上させるpydanticとpanderaの活用方法の提案

Avatar for Daiki Katsuragawa

Daiki Katsuragawa

October 15, 2022
Tweet

More Decks by Daiki Katsuragawa

Other Decks in Programming

Transcript

  1. Pythonによる開発とデータ • 多くの開発におけるデータ ◦ データの管理 ◦ データの処理 • Pythonによる開発におけるデータ ◦

    データ分析 ◦ 機械学習(データからルールやパターンを発見) 5 Pythonによる開発ではデータの扱いの幅が広い
  2. 機械学習を使った処理の例〜データに関する問題〜 1. データ収集 ◦ ドメインに基づくデータを収集 2. 前処理 ◦ 収集したデータを任意の処理に基づき学習用に変換 3.

    学習 ◦ 前処理済みデータを学習(モデルの作成) 4. 予測 ◦ 学習済みモデルにより予測 7 様々な理由により 期待しないデータを 収集してしまう可能性 (例:年齢が-1)
  3. 機械学習を使った処理の例〜データに関する問題〜 1. データ収集 ◦ ドメインに基づくデータを収集 2. 前処理 ◦ 収集したデータを任意の処理に基づき学習用に変換 3.

    学習 ◦ 前処理済みデータを学習(モデルの作成) 4. 予測 ◦ 学習済みモデルにより予測 8 防ぐことができれば問題ないが、 防げなかった場合… 後の処理に影響し、本来もたらす 価値を見出せない (場合によっては不利益も…)
  4. 機械学習を使った処理の例〜データに関する問題〜 1. データ収集 ◦ ドメインに基づくデータを収集 2. 前処理 ◦ 収集したデータを任意の処理に基づき学習用に変換 3.

    学習 ◦ 前処理済みデータを学習(モデルの作成) 4. 予測 ◦ 学習済みモデルにより予測 9 期待しない前処理を 実施してしまう可能性 (例:重要な指標の計算の誤り)
  5. 機械学習を使った処理の例〜データに関する問題〜 1. データ収集 ◦ ドメインに基づくデータを収集 2. 前処理 ◦ 収集したデータを任意の処理に基づき学習用に変換 3.

    学習 ◦ 前処理済みデータを学習(モデルの作成) 4. 予測 ◦ 学習済みモデルにより予測 10 防ぐことができれば問題ないが、 防げなかった場合… 後の処理に影響し、本来もたらす 価値を見出せない (場合によっては不利益も…)
  6. 機械学習を使った処理の例〜データに関する問題〜 1. データ収集 ◦ ドメインに基づくデータを収集 2. 前処理 ◦ 収集したデータを任意の処理に基づき学習用に変換 3.

    学習 ◦ 前処理済みデータを学習(モデルの作成) 4. 予測 ◦ 学習済みモデルにより予測 11 問題:すべてのデータが期待する内容・状態とは限らない
  7. データに関する堅牢性が低い • 期待しないデータ(型)の格納を許すコード • 期待しないデータ(値)の格納を許すコード 15 probability = 0.5 #

    float型の0~1の値を期待(OK) probability = 50 # float型の0~100の値を期待(NG) number = 1 # int型の値を期待(OK) number = "文字列" # int型の値を期待(NG) 堅牢性の向上が必要(データのバリデーションの実装など)
  8. 型ヒントとは? • 型ヒント(Type Hints[2]) ◦ 「変数」、「関数の引数」や「関数の戻り値」の定義に 型のヒントを付与するという記法 • 型ヒントの振る舞い ◦

    実行時に型のチェックはしない(誤りが存在しても動作) ◦ ツール(例:mypy[3])と組み合わせて静的チェックを実現 22 [2] PEP 484 – Type Hints | peps.Python.org(https://peps.Python.org/pep-0484/) [3] mypy - Optional Static Typing for Python(http://www.mypy-lang.org/) コードの可読性の向上 ツールと組み合わせることで堅牢性の向上 name: str = "パイソン 太郎"
  9. 型ヒントの記述方法② • 辞書型(Dict)、リスト型(List)などの要素の型の指定 • 複数の型(Union)の指定 24 from typing import Dict

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

    optional_int: Optional[int] = None # or 0 from typing import Any any: Any = 0 # or "パイソン 太郎", {"パイソン 太郎": 20} ...
  11. 型ヒントの記述方法⑤ • 自作のクラスを指定 27 class SampleClass: pass sample_class: SampleClass =

    SampleClass() def return_input(sample_class: SampleClass) -> SampleClass: return sample_class
  12. 型ヒント〜まとめ〜 • 型ヒント(Type Hints) ◦ 「変数」、「関数の引数」や「関数の戻り値」の定義に 型のヒントを付与するという記法 • 型ヒントの振る舞い ◦

    実行時に型のチェックはしない(誤りが存在しても動作) ◦ ツール(例:mypy)と組み合わせて静的チェックを実現 28 可読性と堅牢性の向上に期待
  13. 型ヒント〜まとめ〜 • 型ヒント(Type Hints) ◦ 「変数」、「関数の引数」や「関数の戻り値」の定義に 型のヒントを付与するという記法 • 型ヒントの振る舞い ◦

    実行時に型のチェックはしない(誤りが存在しても動作) ◦ ツール(例:mypy)と組み合わせて静的チェックを実現 29 型ヒントに基づいて可読性と堅牢性を向上させる pydanticとpanderaを説明
  14. 構造で表現されるデータのスキーマをpydanticで定義 • pydantic[4] ◦ 定義したスキーマに基づき構造で表現されるデータを バリデーションするライブラリ ◦ 辞書型・JSONとのシリアライズ/デシリアライズも可能(入出力に有用) ◦ GitHubのスター:11.3k[5](2022/10/15時点)

    ◦ ライセンス:MIT License[6] 34 [4] pydantic(https://pydantic-docs.helpmanual.io/) [5] pydantic/pydantic: Data parsing and validation using Python type hints(https://github.com/pydantic/pydantic) [6] pydantic/LICENSE at master · samuelcolvin/pydantic(https://github.com/samuelcolvin/pydantic/blob/master/LICENSE) pip install pydantic
  15. pydanticの利用例〜スキーマの定義とバリデーション〜 36 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) 型、有効範囲の定義 デシリアライズ データの仕様の 読み取りが可能 (例:ageは20以上) 定義を満たす インスタンスの生成
  16. pydanticの利用例〜スキーマの定義とバリデーション(エラー)〜 37 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
  17. pydanticの利用例〜バリデーション(エラー)〜 38 --------------------------------------------------------------------------- ValidationError Traceback (most recent call last) (省略)

    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以上という定義を満たしていない)
  18. pydanticの利用例〜定義したスキーマを引数とする関数〜 39 from pydantic import validate_arguments @validate_arguments def input_user(user :

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

    User) -> None: pass external_data = { 'name': 'パイソン 太郎', 'age': 19 } input_user(external_data) 有効範囲外の値 ValidationError
  20. pydanticの利用例〜定義したスキーマを返り値とする関数(エラー)〜 42 def output_user() -> User: external_data = { 'name':

    'パイソン 太郎', 'age': 19 } return User.parse_obj(external_data) output_user() 有効範囲外の値 ValidationError
  21. pydanticの利用例〜より詳細なスキーマの定義〜 43 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() # 前後の半角空白を削除 デコレーターによる詳細なスキーマの定義 (例:半角空白を含むか否かを判定)
  22. スキーマの定義によるデータの仕様の把握が可能 45 from pydantic import BaseModel, Field class User(BaseModel): name:

    str age: int = Field(ge=20) 型、有効範囲の定義 データの仕様の 読み取りが可能 (例:ageは20以上) pydanticによる可読性の向上
  23. 構造で表現されるデータのスキーマをpydanticで定義〜まとめ〜 • 複数の部品によって構成されるシステムの開発 ◦ 各部品(モジュール・クラス・メソッドなど)が役割と責任を持ち連携 ◦ 堅牢性が低い:期待しないデータを格納してしまう ◦ 可読性が低い:コードからデータの仕様を読み取れない •

    pydantic ◦ 定義したスキーマに基づき構造で表現されるデータを バリデーションするライブラリ ◦ 堅牢性の向上:スキーマの定義に基づくデータのみが存在(バリデーション) ◦ 可読性の向上:スキーマの定義によるデータの仕様の把握が可能 46 pydanticによるデータに関する堅牢性と可読性の向上
  24. データフレームを使っていますか? • データフレーム(pandas.DataFrame[7]) ◦ 表型式のデータの取り込み、加工、集計、分析に利用(例:機械学習) ◦ 例:アヤメ(iris)の特徴と種類(scikit-learn[8]より) 48 [7] pandas

    - Python Data Analysis Library (https://pandas.pydata.org/) [8] 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
  25. 堅牢性が低い:期待しないデータを格納してしまう 49 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が格納されている
  26. 可読性が低い:コードからデータの仕様を読み取れない 50 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にはどんなデータが 格納されている?
  27. データフレームのスキーマをpanderaで定義 • pandera[9] ◦ 定義したスキーマに基づきデータフレームをバリデーションするライブラリ ◦ GitHubのスター:1.7k[10](2022/10/15時点) ◦ ライセンス:MIT License[11]

    51 [9] pandera (https://pandera.readthedocs.io/en/stable/) [10] unionai-oss/pandera: A light-weight, flexible, and expressive statistical data testing library (https://github.com/unionai-oss/pandera) [11] pandera/LICENSE.txt at master · unionai-oss/pandera (https://github.com/unionai-oss/pandera/blob/master/LICENSE.txt) pip install pandera
  28. panderaの利用例〜アヤメ(iris)のデータセットの準備〜 53 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
  29. panderaの利用例〜アヤメ(iris)のデータセットの準備〜 54 iris = iris.rename( columns={ "sepal length (cm)": "sepal_length",

    "sepal width (cm)": "sepal_width", "petal length (cm)": "petal_length", "petal width (cm)": "petal_width", } ) 説明を簡単に するために改名
  30. panderaの利用例〜データセットの確認②〜 56 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
  31. panderaの利用例〜スキーマの定義〜 57 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]) 0/1/2(カテゴリ) 現実的な数値
  32. panderaの利用例〜バリデーション〜 58 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
  33. panderaの利用例〜期待しないレコードの追加〜 59 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)
  34. panderaの利用例〜期待しないレコードの確認〜 60 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
  35. panderaの利用例〜バリデーション(エラー)〜 61 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
  36. スキーマの定義に基づくデータのみが存在(バリデーション) 62 panderaによる堅牢性の向上 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 SchemaError
  37. スキーマの定義によるデータの仕様の把握が可能 63 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]) 0/1/2(カテゴリ) 現実的な数値 panderaによる可読性の向上
  38. データフレームのスキーマをpanderaで定義〜まとめ〜 • データフレーム(pandas.DataFrame) ◦ 表形式のデータの取り込み、加工、集計、分析に利用(機械学習などで活用) ◦ 堅牢性が低い:期待しないデータを格納してしまう ◦ 可読性が低い:コードからデータの仕様を読み取れない •

    pandera ◦ 定義したスキーマに基づきデータフレームをバリデーションするライブラリ ◦ 堅牢性の向上:スキーマの定義に基づくデータのみが存在(バリデーション) ◦ 可読性の向上:スキーマの定義によるデータの仕様の把握が可能 64 panderaによるデータに関する堅牢性と可読性の向上
  39. pydanticとpandera(SchemaModel)の類似した記述方法 67 # pydantic # Userのスキーマ from pydantic import BaseModel,

    Field class User(BaseModel): name: str age: int = Field(ge=20) # pandera # Userをレコードとするデータフレームのスキーマ import pandera as pa from pandera.typing import Series class UserSchema(pa.SchemaModel): name: Series[str] age: Series[int] = pa.Field(ge=20)
  40. pydanticとpandera(SchemaModel)の類似した記述方法(差分) 68 # pydantic # Userのスキーマ from pydantic import BaseModel,

    Field class User(BaseModel): name: str age: int = Field(ge=20) # pandera # Userをレコードとするデータフレームのスキーマ import pandera as pa from pandera.typing import Series class UserSchema(pa.SchemaModel): name: Series[str] age: Series[int] = pa.Field(ge=20) pydanticを理解していれば pandera(SchemaModel)の理解は容易 pandera(SchemaModel)を理解していれば pydanticの理解は容易
  41. pydanticとpandera(SchemaModel)の類似した記述方法(差分) 69 # pydantic # Userのスキーマ from pydantic import BaseModel,

    Field class User(BaseModel): name: str age: int = Field(ge=20) # pandera # Userをレコードとするデータフレームのスキーマ import pandera as pa from pandera.typing import Series class UserSchema(pa.SchemaModel): name: Series[str] age: Series[int] = pa.Field(ge=20) pydanticと併用するpanderaの利用方法は“SchemaModel”
  42. pydanticとpanderaを組み合わせた利用 70 import pandera as pa from pandera.engines.pandas_engine import pydanticModel

    # pandera 0.10.0~ from pandera.typing import Series from pydantic import BaseModel, Field class User(BaseModel): name: str age: int = Field(ge=20) class UserSchema(pa.SchemaModel): class Config: dtype = pydanticModel(User) coerce = True Userスキーマ(pydanticにより定義)に従う レコードの集合となるデータフレームの スキーマ(panderaにより定義)
  43. pydanticとpanderaを併用〜まとめ〜 • 類似した記述方法 ◦ 記述方法は対応しており差分は少ない ◦ 一方に慣れていれば他方の導入も容易 ◦ pydanticと併用するpanderaの利用方法は“SchemaModel” •

    組み合わせた利用 ◦ スキーマ(pydanticにより定義)に従うレコードの集合となる データフレームのスキーマ(panderaにより定義)の実現 71 両方を活用できる場合にpydanticとpanderaを併用
  44. pydanticとpanderaの導入前に把握・検証しておくべきリスクの例 • 学習コスト ◦ 開発者の学習コストが確保できるか? • 処理時間 ◦ バリデーションによって増加される処理時間は許容範囲に収まるか? ◦

    成果物がもたらす価値にも影響する可能性 • ライブラリの容量 ◦ 実行環境においてインストールするライブラリの容量が確保できるか? 75 pydanticとpanderaがもたらす価値とリスクを比較して意思決定
  45. まとめ • データに関する問題(解消したい問題) ◦ すべてのデータが期待する内容・状態とは限らない • 解決したい課題 ◦ データに関する堅牢性と可読性の向上 •

    提案①:構造で表現されるデータのスキーマをpydanticで定義 • 提案②:データフレームのスキーマをpanderaで定義 • 提案③:pydanticとpanderaを併用 77