(十八)重放预处理,再调用同版权重
1. 装载时核对文件与解释方式
# 项目文件:model_project/src/modeldelivery/predict.py
import json
from pathlib import Path
import numpy as np
import torch
from .data import FEATURES
from .model import ReadingRegressor
from .releases import verify, version_path
class Predictor:
def __init__(self, root, version=None):
# 先选完整版本,再读取同版缩放与权重,关闭数组文件后仍保留副本。
root = Path(root).resolve()
version = (
version
or json.loads((root / "current.json").read_text(encoding="utf-8"))[
"version"
]
)
folder = version_path(root, version)
verify(folder)
self.metadata = json.loads(
(folder / "metadata.json").read_text(encoding="utf-8")
)
if (
self.metadata["features"] != FEATURES
or self.metadata["architecture"] != "linear-2-1"
):
raise ValueError("特征顺序或模型结构与程序不一致")
self.version = version
with np.load(folder / "preprocess.npz", allow_pickle=False) as archive:
self.scaler = {name: archive[name].copy() for name in archive.files}
self.model = ReadingRegressor()
self.model.load_state_dict(
torch.load(folder / "weights.pt", map_location="cpu", weights_only=True)
)
self.model.eval()没有显式版本时,读取 current.json;指定版本时直接选择相应目录。先检查摘要,再检查特征顺序和结构。np.load(..., allow_pickle=False) 只读取数组,关闭文件前复制进内存。torch.load(..., weights_only=True, map_location="cpu") 读取权重,接着放入同样结构并切到 eval()。
2. 跟踪一条请求的形状与单位
# 续写同一文件:model_project/src/modeldelivery/predict.py
def pages(self, minutes, sessions):
# 特征顺序由交付元数据约定,NumPy 与训练使用相同转换。
x = np.asarray([[minutes, sessions]], dtype=np.float32)
if not np.isfinite(x).all() or (x <= 0).any():
raise ValueError("分钟数和次数需要有限正数")
standardized = ((x - self.scaler["x_mean"]) / self.scaler["x_scale"]).astype(
np.float32
)
with torch.inference_mode():
prediction = self.model(torch.from_numpy(standardized)).numpy()
pages = prediction * self.scaler["y_scale"] + self.scaler["y_mean"]
return float(pages.item())输入 minutes=35、sessions=3,先得到 [[35,3]],形状 (1,2)。逐列减去训练均值、除以训练标准差,再转 Tensor。模型输出形状 (1,1);乘 y_scale、加 y_mean 之后,才得到页数。
这里不重新对单条请求求均值和标准差。单行自己求均值会把输入解释方式改掉,权重却仍是按训练组缩放学习的。同样,若把次数和分钟数对调,数组形状虽然仍正确,含义已经改变;特征顺序检查就是为此保留的。