我想将神经网络表示为 REST API 可用的资源。每当客户想要创建神经网络模型时,他们都可以 POST 神经网络的 JSON 表示形式,该表示形式应该是一个
Model
对象,其中包含 Layer
对象列表。
A
Model
的主要数据是 Layer
对象列表,其中每个 Layer
可以是完全连接的 Linear
层、某种激活层(如 ReLU
)或其他有效的 PyTorch 网络像Conv2d
一样分层。然而,每一层都需要一组不同的参数。 Linear
层需要节点数量,Conv2d
需要内核大小和通道数,而ReLU
实际上不需要任何东西。
我的问题是,我应该如何表示这个
Layer
列表,特别是在 FastAPI 中?
我有两个想法,但我都犹豫不决。
params
变量的枚举参数在这里,我们有一个代表一切的
Layer
模型,其中“类型”由 type
变量处理。
from enum import Enum
from pydantic import BaseModel
class LayerType(str, Enum):
linear = "Linear"
relu = "ReLU"
conv2d = "Conv2d"
# ... other layer types
class Layer(BaseModel):
type: LayerType
params: dict # ?
class Model(BaseModel): # main representation of the neural network
name: str
layers: list[Layer]
有了这个想法,客户端将指定一个 JSON 对象列表,指定
type
并在 params
中填写正确的信息。然而,这会使类型检查变得极其乏味,因为我必须自己验证 params
和/或指定一个长 @model_validator()
方法,对吗?
相反,我可以为每个图层类型指定一个模型,如下所示:
from pydantic import BaseModel
from typing import Union
from typing_extensions import Self
class LinearLayer(BaseModel):
size: int
@model_validator(mode='after')
def check_params(self) -> Self:
assert self.size > 0 and self.size < 256, "`size` must be between 1 and 255, inclusive."
return self
class DropoutLayer(BaseModel):
dropout_prob: float = 0.5
@model_validator(mode='after')
def check_params(self) -> Self:
assert self.dropout_prob >= 0.0 and self.dropout_prob <= 1.0, \
"`dropout_prob` must be between 0.0 and 1.0, inclusive."
return self
class Conv2DLayer(BaseModel):
num_channels: int
kernel: int | None = 3
@model_validator(mode='after')
def check_params(self) -> Self:
assert self.num_channels > 0 and self.num_channels < 256, \
"`num_channels` must be in the interval [1, 255]"
assert self.kernel > 0 and self.kernel < 4, \
"`kernel` must be in [1, 3]"
return self
Layer = Union[LinearLayer, DropoutLayer, Conv2DLayer] # ***
class Model(BaseModel): # main representation of the neural network
name: str
layers: list[Layer]
在这两个想法之间,这个想法似乎肯定要好得多,因为我可以检查每个字段并限制每个图层类型的范围。一个小问题是,如果我想要几个不同的
Layer
,则 Union
类型需要非常长的 Layer
。但我对这个想法的主要问题是让它正确匹配每一层。如果我有一个简单的 API POST 方法,例如:
from fastapi import FastAPI
from .schemas import * # the Model schema and layer stuff
app = FastAPI()
@app.post("/models/")
async def create_model(model: Model) -> Model:
return model
并使用这个简单的 JSON 测试此 POST 方法:
{
"name": "test_model",
"layers": [
{
"size": 0
},
{
"dropout_prob": 0.5
},
{
"num_channels": 0
}
]
}
我收到以下回复:
{
"name": "test_model",
"layers": [
{
"dropout_prob": 0.5
},
{
"dropout_prob": 0.5
},
{
"dropout_prob": 0.5
}
]
}
我认为我不理解 FastAPI 如何将 JSON 对象与相应的 pydantic BaseModel 相匹配的基本原理。这是我第一次使用 REST API。
我可以使用某种 pydantic 继承/多态性来解决这个问题吗?或者我应该去实施想法#1而不是#2?我没有太多运气浏览 FastAPI 和 pydantic 文档,也没有找到任何提及这样的情况。
您的最终解决方案将取决于您的最终需求。
如果您只想存储模型层的数据,并且除了验证之外不需要任何特定的业务逻辑,第二个解决方案看起来不错,您只需要填充response_model即可。
但是,如果您需要为不同类型的模型添加不同的业务逻辑,那么通过一个端点来执行此操作会很不舒服,并且您可能需要为不同的模型类型提供单独的端点:
POST /layers/linear
POST /layers/conv2d
...
这个解决方案会更加宁静
在这两种情况下,我建议您了解 FastAPI 如何生成 /docs 端点,这可能有助于更好地理解请求和响应输出