7
AI 实战项目综合
End-to-End Projects & Deployment
学完零件,该拼整车了。端到端项目的标准路径:数据获取 → 清洗 → 分析 → 建模 → 评估 → 部署。下面两个项目覆盖经典 ML 和深度学习两大方向。
项目一:电影推荐系统
论思路
拿 MovieLens 数据集(用户-电影评分表),用协同过滤:找"和你口味相似的用户喜欢什么,就推荐给你"。Pandas 做数据清洗,Scikit-learn 的 cosine_similarity 算用户相似度。
from sklearn.metrics.pairwise import cosine_similarity
# 假设 ratings 是个 DataFrame:user_id, movie_id, rating
# 透视为用户×电影矩阵
user_movie = ratings.pivot_table(index="user_id",
columns="movie_id", values="rating")
# 缺失值填 0(没看过=没打分)
user_movie = user_movie.fillna(0)
# 算用户之间的相似度
sim = cosine_similarity(user_movie)
# 给用户 42 推荐:找和他最像的 5 个用户看过的电影
target = 42
similar_users = sim[target].argsort()[-6:-1]
# 然后取这些用户看过、target 没看过的电影,按评分排
论这个项目的三个看点
① pivot_table:把长表转成宽表,这是协同过滤的标准预处理。
② cosine_similarity:不用自己算向量夹角,sklearn 一行搞定。
③ 冷启动问题:新用户没评分,相似度算不出来——这是真实推荐系统要解决的,入门阶段知道有这回事就行。
项目二:图像分类 CIFAR-10
import torch.nn as nn
# 用 CNN(卷积神经网络)处理图像,比全连接强
class CNN(nn.Module):
def __init__(self):
super().__init__()
self.conv1 = nn.Conv2d(3, 32, kernel_size=3, padding=1)
self.conv2 = nn.Conv2d(32, 64, kernel_size=3, padding=1)
self.pool = nn.MaxPool2d(2, 2)
self.fc = nn.Linear(64 * 8 * 8, 10)
def forward(self, x):
x = self.pool(torch.relu(self.conv1(x)))
x = self.pool(torch.relu(self.conv2(x)))
x = x.view(-1, 64 * 8 * 8)
return self.fc(x)
# 同样的四步训练循环,跑 20 轮左右能到 75% 准确率
论CNN 为什么比全连接强
全连接网络把图片拉成一维向量,丢掉了"像素之间的位置关系"。CNN 用卷积核在图片上滑动,能识别"边缘、纹理、形状"这些局部特征——第一层学边缘,第二层学纹理,后面几层学物体部件。这就是为什么处理图像要用 CNN 而不是全连接。
迁移学习:别从零开始训
论为什么用预训练模型
自己从零训一个 ResNet 要几百块 GPU 几星期。但 PyTorch Hub 里有"别人在 ImageNet 上训好的"权重——你拿来,改一下最后几层,用自己几百张图微调几轮,效果就比从零训好得多。这就是迁移学习:让模型先学会"看世界",再教它看你的特定任务。
from torchvision import models
# 加载预训练 ResNet18
model = models.resnet18(weights=models.ResNet18_Weights.DEFAULT)
# 冻结前面的层(只训最后一层)
for param in model.parameters():
param.requires_grad = False
# 把最后一层改成你自己的类别数(比如 5 类)
model.fc = nn.Linear(model.fc.in_features, 5)
model = model.to(device)
# 然后照常训练,几百张图就能出不错的效果
模型保存与加载
# PyTorch:只存权重(推荐)
torch.save(model.state_dict(), "model.pth")
# 加载:要先 new 一个同结构的模型
model = MyNet()
model.load_state_dict(torch.load("model.pth", weights_only=True))
model.eval() # 切到评估模式
# Scikit-learn 模型用 joblib
import joblib
joblib.dump(model, "rf.joblib")
model = joblib.load("rf.joblib")
简单部署:FastAPI 推理接口
from fastapi import FastAPI
from pydantic import BaseModel
app = FastAPI()
model.eval()
class Input(BaseModel):
features: list[float]
@app.post("/predict")
def predict(data: Input):
x = torch.tensor(data.features).float()
with torch.no_grad(): # 推理时不用算梯度,省内存
out = model(x)
pred = out.argmax().item()
return {"prediction": pred}
# 跑起来:uvicorn main:app --reload
# 访问 http://127.0.0.1:8000/docs 有自动生成的接口文档
论部署三步走
① 存模型:训练好后 torch.save(model.state_dict(), "model.pth")。
② 起服务:FastAPI 包一个 /predict 接口,接 JSON 输入返回 JSON 结果。
③ 上线:用 uvicorn 跑,生产环境前面套个 Nginx。入门阶段本地跑通就够,Docker、K8s 是后话。
部署常见问题
① 加载慢:模型文件几百 MB,启动时别每次请求都 load 一次——服务启动时 load 到内存。
② 预处理要一致:训练时怎么 normalize,推理时就得怎么 normalize,别让前端传原始像素、后端忘了 normalize。
③ 别阻塞:FastAPI 是异步的,但 PyTorch 推理本身是阻塞的——真要高并发得多开几个 worker。
推理时的正确姿势
model.eval()
with torch.no_grad():
# 前向传播不计算梯度,省显存省时间
outputs = model(new_data.to(device))
pred_class = outputs.argmax(dim=1).item()
# 如果要概率,过个 softmax
probs = torch.softmax(outputs, dim=1)
常见报错速查
| 报错 | 大概率原因 |
RuntimeError: Expected all tensors on same device | 数据在 CPU,模型在 GPU。两边都 .to(device)。 |
RuntimeError: mat1 and mat2 shapes cannot be multiplied | 输入维度和第一层不匹配,检查 .view(-1, 784)。 |
loss 变 nan | 学习率太大、除零、数据有 NaN。先降 lr 到 0.0001。 |
CUDA out of memory | batch_size 太大,或者忘了 no_grad()。 |
TypeError: can't convert cuda tensor | GPU 张量要 .cpu() 才能转 numpy。 |