🔦

PydanticでPydanticSchemaGenerationErrorに遭遇したので解決策をメモる

2024/05/04に公開

概要

PydanticのBaseModel派生クラスにtf.Tensor型のフィールドを使ったら下記のようなエラーに遭遇。解決策を探る。

例外が発生しました: PydanticSchemaGenerationError
Unable to generate pydantic-core schema for <class 'tensorflow.python.framework.tensor.Tensor'>. Set `arbitrary_types_allowed=True` in the model_config to ignore this error or implement `__get_pydantic_core_schema__` on your type to fully support it.

If you got this error by calling handler(<some type>) within `__get_pydantic_core_schema__` then you likely need to call `handler.generate_schema(<some type>)` since we do not call `__get_pydantic_core_schema__` on `<some type>` otherwise to avoid infinite recursion.

問題のコード

from typing import Self
from pydantic import BaseModel, field_validator
import tensorflow as tf
import numpy as np

class Test(BaseModel):
    tensor:tf.Tensor|np.ndarray
    
    @field_validator('tensor', mode='after')
    @classmethod
    def tensor_validator(cls, v):
        if v.ndim not in (3, 4, 5):
            raise ValueError('tensor.ndim must be 3 or 4 or 5')
        return v
    
    @property
    def value(self):
        return self.tensor

if __name__=='__main__':
    tensor = tf.random.normal(shape=(1,9,9,3))
    tensor = Test(tensor=tensor).value
    print(tensor)

バリデーション通したら値をもらうだけ。

Bing.Copilotいわく

Q. PydanticのBaseModel派生クラスのフィールドとしてtf.Tensorを使っているのがエラー要因な気がします。そういった情報はありますか?

A. はい、その通りです。PydanticのBaseModel派生クラスのフィールドとしてtf.Tensorを使用すると、Pydanticはその型のスキーマを生成しようとしますが、tf.TensorはPydanticが直接サポートしていない型であるため、PydanticSchemaGenerationErrorが発生します[1]

PydanticはPythonの標準的な型や一部のカスタム型をサポートしていますが、tf.Tensorのような特定の複雑な型は直接サポートしていません。そのため、tf.Tensorをフィールドとして使用すると、Pydanticはその型のスキーマを生成できずにエラーを引き起こします。
以下省略

解決策1

classにclass内class Configを追加してarbitary_types_allowed=Trueを設定(?)

class Test2(BaseModel):
    # add
    class Config:
        arbitrary_types_allowed = True

実行結果

tf.Tensor(
[[[[ 0.8375783  -0.25764948 -0.84524643]
...
   [ 0.3933775   0.4895133   0.44484222]]]], shape=(1, 9, 9, 3), dtype=float32)

どういう作用…?

解決策2

model_configにarbitary_types_allowed=Trueを設定

from pydantic import ConfigDict
class Test3(BaseModel):
    # add
    model_config = ConfigDict(arbitrary_types_allowed=True)

実行結果

tf.Tensor(
[[[[-0.59260434  0.7612173   1.5279994 ]
...
   [-0.480862    0.69436026  0.00327506]]]], shape=(1, 9, 9, 3), dtype=float32)

こちらは公式ドキュメントに記載されたコードから[2]
内部的には(解決策1のコードと)同じということなんだろうか。

振り返り

arbitrary_types_allowedとは・・?
arbitrary:任意
types:種類
allowed:許可された

公式ドキュメントではWhether arbitrary types are allowed for field types. Defaults to False.と記載されており、デフォルトでは許可されていないPydanticのサポートしてない型のフィールドを許可するってことらしい[2:1]

解決に至る経緯を言語化(文章化)できない。
公式ドキュメントのページでarbitrary_types_allowdで検索して出てきたコードを見て解決に至った、ような気がする。

エラーメッセージにもBing.Copilotのリンク先のstack overflowにもarbitrary_types_allowedに言及があるのにも関わらず経緯が説明できないと言っているのもおかしな話ではあるが、リンク先のstack overflowの回答ではインデント入れていない(なんでやねん)し昨夜これを試してもエラーが解消できなかった(もしくは別のエラーが発生したか、記憶がおぼろげ)。
エラーメッセージの内容はライブラリ開発者がユーザーにやってほしいことは伝わってくるものの、それをコードに落とせない…Set arbitrary_types_allowed=True in the model_configでConfigDict(arbitrary_types_allowed=True)すればいいんだ!ってなるか?ドキュメント読んでから使い始めていればなるのか?model_configもドキュメント読んでればわかったのか[3][4]
Configuration for the model, should be a dictionary conforming to ConfigDict.
A TypedDict for configuring Pydantic behaviour.
まあ、[2:2]のコードを見ればわかるか…?
そうか自業自得なのかごめんなさい…。

参照

Github

https://github.com/rifumi/pydantic_my_tutorial/blob/main/has_tensor.py

脚注
  1. https://stackoverflow.com/questions/72184958/pydantic-type-hinting-tensorflow-tensor ↩︎

  2. https://docs.pydantic.dev/latest/api/config/#pydantic.config.ConfigDict.arbitrary_types_allowed ↩︎ ↩︎ ↩︎

  3. https://docs.pydantic.dev/latest/api/base_model/#pydantic.BaseModel.model_config ↩︎

  4. https://docs.pydantic.dev/latest/api/config/#pydantic.config.ConfigDict ↩︎

Discussion