介绍背景介绍这里有11类食物其中带标签的数据 280 *11不带标签的训练数据 6786验证集30*11测试集3347核心思想数据准备加载有标签的训练数据、验证数据和无标签数据模型初始化使用预训练的VGG模型初始训练先用有标签数据训练模型半监督迭代使用当前模型对无标签数据进行预测选择置信度高的预测结果作为伪标签将这些高质量的无标签数据加入训练继续训练模型利用更多数据提升性能半监督学习的定义半监督学习是在同时拥有少量有标签数据和大量无标签数据的情况下进行学习的方法。它的核心思想是利用无标签数据来提升模型性能特别是在标签数据稀缺或昂贵的情况下。传统学习方法对比1. 监督学习数据特点全部都有标签例子1000张图片每张都有类别标签猫、狗、鸟等优点可以直接训练模型缺点需要大量人工标注成本高2. 无监督学习数据特点全部都没有标签例子10000张图片没有任何标签优点不需要标注缺点无法直接用于分类任务3. 半监督学习数据特点少量有标签 大量无标签例子1000张有标签图片 10000张无标签图片优点充分利用数据降低成本应用场景标签获取困难但数据容易获取的场景代码解读第一部分库导入和随机种子设置1.1 库导入import random import torch import torch.nn as nn import numpy as np import os from PIL import Image #这个包用于读取图片数据 from torch.utils.data import Dataset, DataLoader from tqdm import tqdm #显示循环进度 from torchvision import transforms #可以进行数据增广 import time import matplotlib.pyplot as plt from model_utils.model import initialize_model #传入模型名字和分类数 返回你想要的模型random: Python随机数生成库torch及子模块: PyTorch深度学习框架核心库numpy: 数值计算库用于数组操作os: 操作系统接口用于文件路径操作PIL.Image: Python图像处理库用于读取和处理图片torch.utils.data.Dataset/DataLoader: PyTorch数据处理工具Dataset是一个抽象基类用于表示数据集的接口。它定义了如何访问数据集中的样本。DataLoader是一个数据加载器用于批量加载数据、打乱数据顺序等。tqdm: 进度条显示库torchvision.transforms: 图像变换库matplotlib.pyplot: 数据可视化库initialize_model: 自定义模型初始化函数1.2 随机种子固定函数def seed_everything(seed): torch.manual_seed(seed) torch.cuda.manual_seed(seed) torch.cuda.manual_seed_all(seed) torch.backends.cudnn.benchmark False torch.backends.cudnn.deterministic True random.seed(seed) np.random.seed(seed) os.environ[PYTHONHASHSEED] str(seed) ################################################################# seed_everything(0) ###############################################固定所有相关的随机种子确保实验结果可重现包括PyTorch CPU/GPU随机种子、Python随机种子、NumPy随机种子等设置CUDA后端为确定性模式第二部分数据预处理定义2.1 图片尺寸设置HW 224 # 设置图片宽高均为224像素统一所有输入图片的尺寸为224×224这是大多数CNN模型的标准输入尺寸2.2 训练数据增强预处理train_transform transforms.Compose([ #训练集的数据变换训练的时候让模型尽量见过防止以后分类没见过 transforms.ToPILImage(), # 将numpy数组或tensor转换为PIL图片对象 transforms.RandomResizedCrop(224), # 随机裁剪并调整到224×224 transforms.RandomRotation(50), # 随机旋转角度范围±50度 transforms.ToTensor() # 转换为tensor并归一化到[0,1] ])训练时的数据增强策略通过随机变换增加数据多样性提高模型泛化能力2.3 验证数据预处理val_transform transforms.Compose( #验证集不需要那么多的数据变换降低准确率验证和测试的时候要用原图训练的时候就变换图片 [ transforms.ToPILImage(), #比如本来格式是224,224,3把其转化为模型能接受的格式3,224,224 transforms.ToTensor() #模型运行都是张量运行的所以最后变为张量 ] )验证时仅进行基本转换不进行数据增强保持数据真实性transforms.ToTensor()是 PyTorch torchvision 中的一个数据预处理变换主要作用是将 PIL 图片或 NumPy 数组转换为 PyTorch 张量Tensor并且还会进行数值归一化。第三部分数据集类定义3.1 主要数据集类 food_Datasetclass food_Dataset(Dataset): def __init__(self, path, modetrain): 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) # 标签转为LongTensor类型 # 根据模式选择预处理方式 if mode train: # 训练模式 self.transform train_transform # 使用数据增强 else: self.transform val_transform # 验证模式使用基本转换继承PyTorch的Dataset基类支持三种模式train训练、val验证、semi半监督在初始化时根据模式读取不同数据执行流程分析情况1mode train结果: 有标签 训练预处理情况2mode val结果: 有标签 验证预处理情况3mode semi结果: 无标签 验证预处理3.2 文件读取方法def read_file(self, path): if self.mode semi: # 无标签数据读取 file_list os.listdir(path) # 获取目录下所有文件名 xi np.zeros((len(file_list), HW, HW, 3), dtypenp.uint8) # 预分配内存 xi表示哪一类的图片 dtypenp.unit8表示读成整型 for j, img_name in enumerate(file_list): img_path os.path.join(path, img_name) img Image.open(img_path) # 打开图片 img img.resize((HW, HW)) # 调整尺寸 xi[j, ...] img # 存储图片数据 print(读到了%d个数据 % len(xi)) return xi else: # 有标签数据读取 for i in tqdm(range(11)): # 遍历11个类别00-10 file_dir path /%02d % i # 构造类别文件夹路径保留两位整数 file_list os.listdir(file_dir) # 获取该类别的所有图片 xi np.zeros((len(file_list), HW, HW, 3), dtypenp.uint8) # 图片数据 yi np.zeros(len(file_list), dtypenp.uint8) # 标签数据 for j, img_name in enumerate(file_list): img_path os.path.join(file_dir, img_name) img Image.open(img_path) img img.resize((HW, HW)) xi[j, ...] img yi[j] i # 标签为类别编号 # 累积存储所有类别的数据 if i 0: # 如果是第零类就把大x和大Y指定为第零类如果不是第零类就合并起来 X xi Y yi else: X np.concatenate((X, xi), axis0) # 沿着第一个维度拼接 Y np.concatenate((Y, yi), axis0) # axis0表示竖着合并 print(读到了%d个数据 % len(Y)) return X, Y半监督模式只读取图片数据无标签有标签模式读取图片数据和对应的标签共11个类别使用tqdm显示进度条最终返回完整的图片数据和标签数据详细分析整体方法结构def read_file(self, path): if self.mode semi: # 根据模式选择不同的读取逻辑 # 半监督模式只读取图片无标签 else: # 有标签模式读取图片和标签半监督模式详细分析1. 条件判断if self.mode semi: # 无标签数据读取作用: 检查当前数据集实例是否为半监督模式无标签数据2. 获取文件列表file_list os.listdir(path) # 获取目录下所有文件名详细说明:path: 输入的文件夹路径os.listdir(path): 返回该目录下所有文件和子目录的名称列表示例:[apple_001.jpg, bread_002.png, cake_003.jpg]结果:file_list包含所有图片文件名3. 预分配内存xi np.zeros((len(file_list), HW, HW, 3), dtypenp.uint8) # 预分配内存详细说明:len(file_list): 图片总数HW, HW, 3: 每张图片的尺寸 (高度, 宽度, 通道数)dtypenp.uint8: 8位无符号整数范围[0,255]创建数组形状:(图片数量, 224, 224, 3)目的: 预先分配足够空间避免动态扩容提高效率4. 遍历处理每张图片for j, img_name in enumerate(file_list): img_path os.path.join(path, img_name) img Image.open(img_path) # 打开图片 img img.resize((HW, HW)) # 调整尺寸 xi[j, ...] img # 存储图片数据4.1 循环变量j: 图片索引 (0, 1, 2, ...)img_name: 图片文件名 (apple_001.jpg, bread_002.png, ...)4.2 构建完整路径img_path os.path.join(path, img_name)示例:/path/to/unlabeled/apple_001.jpg4.3 打开图片img Image.open(img_path) # 打开图片功能: 读取图片文件到PIL Image对象输出: PIL Image对象4.4 调整尺寸img img.resize((HW, HW)) # 调整尺寸功能: 将图片调整为统一的224×224尺寸输入: 任意尺寸的PIL Image输出: 224×224的PIL Image4.5 存储到数组xi[j, ...] img # 存储图片数据j: 当前图片在数组中的位置...: 表示所有维度 ([j, :, :, :])功能: 将第j张图片的所有像素数据存储到数组的第j行5. 输出信息print(读到了%d个数据 % len(xi))功能: 打印成功读取的图片数量示例输出: 读到了5000个数据6. 返回结果return xi返回: 包含所有图片数据的4D数组 (数量, 224, 224, 3)有标签模式详细分析1. 循环遍历所有类别for i in tqdm(range(11)): # 遍历11个类别00-10详细说明:range(11): [0, 1, 2, ..., 10] 共11个类别tqdm: 显示进度条i: 当前类别编号 (0-10)2. 构造类别路径file_dir path /%02d % i # 构造类别文件夹路径详细说明:%02d: 格式化为两位数字不足补0示例:path /00,path /01, ...,path /10实际路径:/path/to/data/00,/path/to/data/01, ...3. 获取该类别的图片列表file_list os.listdir(file_dir) # 获取该类别的所有图片示例:类别0:[apple_001.jpg, apple_002.jpg, ...]类别1:[bread_001.jpg, bread_002.jpg, ...]4. 为当前类别预分配内存xi np.zeros((len(file_list), HW, HW, 3), dtypenp.uint8) # 图片数据 yi np.zeros(len(file_list), dtypenp.uint8) # 标签数据详细说明:xi: 存储当前类别所有图片的数组yi: 存储当前类别标签的数组dtypenp.uint8: 节省内存标签值0-255足够5. 处理当前类别的每张图片for j, img_name in enumerate(file_list): img_path os.path.join(file_dir, img_name) img Image.open(img_path) img img.resize((HW, HW)) xi[j, ...] img yi[j] i # 标签为类别编号5.1-5.4: 与半监督模式相同打开、调整、存储图片5.5 存储标签yi[j] i # 标签为类别编号i: 当前类别编号 (0-10)j: 当前图片在当前类别中的索引结果: 第j张图片的标签就是它所属的类别编号6. 累积所有类别的数据if i 0: X xi Y yi else: X np.concatenate((X, xi), axis0) # 沿着第一个维度拼接 Y np.concatenate((Y, yi), axis0)6.1 第一个类别 (i0)if i 0: X xi # 直接赋值 Y yi功能: 初始化最终的数据数组6.2 后续类别 (i0)else: X np.concatenate((X, xi), axis0) # 沿着第一个维度拼接 Y np.concatenate((Y, yi), axis0)np.concatenate: 数组拼接函数axis0: 沿着第一个维度样本数量维度拼接示例:原X: (100, 224, 224, 3)新xi: (80, 224, 224, 3)拼接后X: (180, 224, 224, 3)7. 最终输出print(读到了%d个数据 % len(Y)) return X, YX: 所有图片数据 (总数量, 224, 224, 3)Y: 所有标签数据 (总数量,)3.3 数据获取方法def __getitem__(self, item): #这个函数的作用就是return self.X和self.Y对应下标的值,并再加一层图片增广 if self.mode semi: return self.transform(self.X[item]), self.X[item] # 返回增强后图片和原始图片 else: return self.transform(self.X[item]), self.Y[item] # 返回增强后图片和标签 def __len__(self): return len(self.X) #self.transform(self.X[item])返回变换后的图片self.X[item]返回原图__getitem__: 获取指定索引的数据项__len__: 返回数据集长度