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

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

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

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