1. 项目概述
在计算机视觉领域,食品分类是一个具有重要应用价值的课题。传统的有监督学习方法需要大量标注数据,而数据标注往往成本高昂且耗时。基于PyTorch的半监督食品分类方法,通过利用少量标注数据和大量未标注数据,能够显著降低标注成本,同时保持较高的分类准确率。
这个项目实现了一个完整的半监督学习流程,包括数据预处理、模型构建、伪标签生成和混合训练等关键环节。核心思路是先用少量标注数据训练初始模型,然后用训练好的模型对未标注数据预测,筛选高置信度的预测结果作为伪标签,最后将有标签数据和伪标签数据一起用于模型训练。
2. 核心模块解析
2.1 数据预处理与增强
数据预处理是深度学习项目中的关键环节。在这个食品分类项目中,我们针对训练集和验证集采用了不同的预处理策略:
python复制# 训练集预处理(包含数据增强)
train_transform = transforms.Compose([
transforms.ToPILImage(),
transforms.RandomResizedCrop(224),
transforms.RandomRotation(50),
transforms.ToTensor()
])
# 验证集预处理(仅基础转换)
val_transform = transforms.Compose([
transforms.ToPILImage(),
transforms.ToTensor()
])
这种差异化的处理策略基于以下考虑:
- 训练集需要数据增强来提高模型泛化能力
- 验证集应该反映真实场景下的性能表现
- 数据增强可能会引入噪声,影响模型评估的准确性
注意:ToTensor()操作不仅将图像转换为Tensor格式,还会自动将像素值归一化到[0,1]范围,这对模型训练的稳定性很重要。
2.2 自定义数据集类
项目实现了一个灵活的数据集类food_Dataset,支持三种模式:
- 训练模式:读取有标签数据
- 验证模式:读取有标签数据
- 半监督模式:读取无标签数据
python复制class food_Dataset(Dataset):
def __init__(self, path, mode="train"):
self.mode = mode
if mode == "semi":
self.X = self.read_file(path)
else:
self.X, self.Y = self.read_file(path)
self.Y = torch.LongTensor(self.Y)
if mode == "train":
self.transform = train_transform
else:
self.transform = val_transform
数据集类的关键设计点:
- 统一接口处理不同来源的数据
- 自动应用对应的预处理策略
- 支持批量数据加载,提高训练效率
2.3 半监督学习核心逻辑
半监督学习的核心在于伪标签生成和筛选:
python复制class semiDataset(Dataset):
def __init__(self, no_label_loder, model, device, thres=0.99):
x, y = self.get_label(no_label_loder, model, device, thres)
if x == []:
self.flag = False
else:
self.flag = True
self.X = np.array(x)
self.Y = torch.LongTensor(y)
self.transform = train_transform
def get_label(self, no_label_loder, model, device, thres):
model = model.to(device)
pred_prob = []
labels = []
x = []
y = []
soft = nn.Softmax()
with torch.no_grad():
for bat_x, _ in no_label_loder:
bat_x = bat_x.to(device)
pred = model(bat_x)
pred_soft = soft(pred)
pred_max, pred_value = pred_soft.max(1)
pred_prob.extend(pred_max.cpu().numpy().tolist())
labels.extend(pred_value.cpu().numpy().tolist())
for index, prob in enumerate(pred_prob):
if prob > thres:
x.append(no_label_loder.dataset[index][1])
y.append(labels[index])
return x, y
伪标签生成的关键参数:
- 置信度阈值(thres):控制伪标签质量
- 模型预测稳定性:使用with torch.no_grad()提高效率
- Softmax归一化:将模型输出转换为概率分布
3. 模型架构与训练
3.1 模型选择与实现
项目提供了两种模型选择:
- 自定义CNN模型
- 预训练VGG模型
自定义CNN模型结构如下:
python复制class myModel(nn.Module):
def __init__(self, num_class):
super(myModel, self).__init__()
self.conv1 = nn.Conv2d(3, 64, 3, 1, 1)
self.bn1 = nn.BatchNorm2d(64)
self.relu = nn.ReLU()
self.pool1 = nn.MaxPool2d(2)
self.layer1 = nn.Sequential(nn.Conv2d(64, 128, 3, 1, 1),
nn.BatchNorm2d(128),
nn.ReLU(),
nn.MaxPool2d(2))
# 更多层...
def forward(self, x):
x = self.conv1(x)
x = self.bn1(x)
x = self.relu(x)
x = self.pool1(x)
# 更多前向传播逻辑...
return x
对于食品分类任务,预训练模型通常是更好的选择:
- 可以利用在大规模数据集上学到的通用特征
- 通常只需要微调最后几层
- 收敛更快,性能更好
3.2 训练流程设计
训练过程采用交替策略:
- 先用有标签数据训练基础模型
- 每3个epoch用模型生成伪标签
- 混合有标签和伪标签数据继续训练
python复制def train_val(model, train_loader, val_loader, no_label_loader, device, epochs, optimizer, loss, thres, save_path):
for epoch in range(epochs):
# 有监督训练
model.train()
for batch_x, batch_y in train_loader:
# 常规训练步骤...
# 半监督训练
if semi_loader != None:
for batch_x, batch_y in semi_loader:
# 使用伪标签训练...
# 验证评估
model.eval()
with torch.no_grad():
# 验证步骤...
# 定期生成伪标签
if epoch % 3 == 0 and val_acc > 0.6:
semi_loader = get_semi_loader(no_label_loader, model, device, thres)
训练过程中的关键技巧:
- 只在模型达到一定准确率(>0.6)后才开始半监督
- 定期更新伪标签,避免过时
- 监控验证集性能,防止过拟合
4. 优化与调参
4.1 优化器选择
项目使用了AdamW优化器:
python复制optimizer = torch.optim.AdamW(model.parameters(), lr=0.001, weight_decay=1e-4)
AdamW的优势:
- 结合了Adam的自适应学习率和权重衰减
- 通常比标准Adam更稳定
- 适合计算机视觉任务
4.2 学习率策略
虽然没有显式实现学习率调度,但可以通过以下方式改进:
- 初始阶段使用较大学习率
- 后期降低学习率精细调整
- 配合早停策略防止过拟合
4.3 损失函数
使用标准的交叉熵损失:
python复制loss = nn.CrossEntropyLoss()
对于食品分类任务,如果类别不平衡,可以考虑:
- 类别加权交叉熵
- Focal loss
- 标签平滑
5. 实验与评估
5.1 评估指标
主要使用准确率作为评估指标:
python复制train_acc += np.sum(np.argmax(pred.detach().cpu().numpy(), axis=1) == target.cpu().numpy())
对于更全面的评估,可以增加:
- 混淆矩阵分析
- 各类别的精确率、召回率
- F1分数
5.2 训练曲线监控
项目实现了训练/验证损失和准确率的可视化:
python复制plt.plot(plt_train_loss)
plt.plot(plt_val_loss)
plt.title("loss")
plt.legend(["train", "val"])
plt.show()
通过监控这些曲线可以:
- 判断模型是否收敛
- 检测过拟合或欠拟合
- 决定是否需要调整超参数
6. 实际应用建议
6.1 数据准备
对于实际食品分类应用:
- 收集代表性强的食品图像
- 确保标注质量
- 保持类别平衡
- 考虑不同光照、角度、背景的变化
6.2 模型部署
将训练好的模型部署到生产环境时:
- 转换为ONNX或TorchScript格式
- 优化推理速度
- 实现批量预测
- 添加异常处理
6.3 持续改进
模型上线后的改进方向:
- 收集新的标注数据
- 定期重新训练模型
- 监控模型性能衰减
- 根据用户反馈调整类别
7. 常见问题与解决方案
7.1 伪标签质量不高
可能原因:
- 初始模型性能不足
- 置信度阈值设置不当
- 数据分布不一致
解决方案:
- 提高初始有监督训练轮数
- 调整置信度阈值
- 检查数据质量
7.2 模型过拟合
症状:
- 训练准确率高但验证准确率低
- 损失曲线出现明显分歧
解决方法:
- 增加数据增强
- 添加正则化
- 早停策略
- 简化模型结构
7.3 训练不稳定
表现:
- 损失波动大
- 准确率忽高忽低
调整方法:
- 降低学习率
- 增大批量大小
- 使用梯度裁剪
- 检查数据预处理
8. 扩展与优化方向
8.1 模型架构改进
可以考虑:
- 更先进的预训练模型(如ResNet、EfficientNet)
- 注意力机制
- 模型集成
8.2 半监督策略优化
尝试:
- 一致性正则化
- 混合增强
- 课程学习策略
8.3 多模态融合
结合:
- 文本描述
- 营养成分信息
- 烹饪方法数据
在实际应用中,我发现半监督学习的效果很大程度上取决于初始有监督阶段的质量。确保初始模型具有足够强的特征提取能力,是后续伪标签生成质量的关键。另外,动态调整置信度阈值,根据模型当前性能自适应地选择伪标签,往往能取得更好的效果。
