Atri Website

Back

《神经网络与深度学习》课程实验作业#

实验内容:计算机视觉基础

注意事项:

① 本次实验包含一道题,共计 20 分;

② 部分问题根据学号不同需进行不同的配置,若完成内容与实验要求不对应,则该项记为 0 分;

③ 所有实验结果需以实验报告的形式进行提交,文件命名格式:实验二_姓名_学号.pdf;

④ 实验报告中可插入代码片段,完整代码无需放在实验报告中,以压缩包的形式添加即可,压缩包命名格式:实验二代码_姓名_学号.zip;

⑤ 作业提交截止时间:2025 年 12 月 31 日 24:00 前。

卷积神经网络与经典卷积神经网络模型(20 分)

food-11 数据集是深度学习中常用的一类数据集,其中包含 11 类食物的图片,分别是:Bread, Dairy product, Dessert, Egg, Fried food, Meat, Noodles/Pasta, Rice, Seafood, Soup, and Vegetable/Fruit. 数据集中包含 9866 张训练集,3430 张验证集,3347 张测试集。其中 training 和 validation 目录下的照片命名格式为“[类别]_[编号].jpg”

请基于该数据集,完成以下实验内容:

(1) 请根据已有数据集,对 training 和 validation 进行处理,构造训练数据集;(3 分)

(2) 为了保证数据的训练性能,请利用 torchvision.transforms 设计属于你的 train_transform,train_transform 模版已事先给出。你所设计的 train_transform 需要指定你所选择的特征图大小,并至少由五种 transform 组成;(3 分)

(3) 对你所设计的 train_transform 进行可视化,将变换后的结果进行可视化展示;(2 分)

(4) 搭建好个人设计的训练模型,并利用 tensorboard 对过程进行可视化展示;(2 分)

(5) 在验证集上显示你的准确性和混淆矩阵;(2 分)

(6) 将个人设计模型对测试集预测结果输出到 ans_ours.csv 中;(3 分)

(7) 请自行查询资料,搭建 VGG 系列模型,并打印模型参数;(2 分)

(8) 使用 VGG 系列模型对测试集进行预测,并将结果输出到 ans_vgg.csv 中;(3 分)

Food11 数据集链接:https://www.kaggle.com/datasets/vermaavi/food11

提示:

①该数据集的标签需要自行根据文件名进行提取;

②数据集中图片大小并不统一;

③VGG 系列模型主要包含 VGG16 和 VGG19,你可以从中任选一个进行实现

题目分析#

第一阶段:数据工程#

任务 (1): 构造训练数据集 (3 分)

  • 核心要求:编写代码读取 trainingvalidation 文件夹下的图片
  • 关键动作:
    • 需要自定义一个 Dataset 类FoodDataset(继承 torch.utils.data.Dataset
    • 文件名解析:图片名格式为 [类别]_[编号].jpg(例如 Bread_001.jpg)。需要写代码解析字符串,把 Bread 映射成数字标签(例如 0
    • 注意:要确保训练集和验证集的类别映射关系是一致的(比如 Bread 在训练集是 0,在验证集也必须是 0)

任务 (2): 设计 Train Transform (3 分)

  • 核心要求:利用 torchvision.transforms 对图片进行预处理和增强
  • 硬性指标:
    1. 指定特征图大小:必须包含 Resize(例如 Resize((224, 224))),因为题目提示图片大小并不统一
    2. 至少五种 Transform:少于 5 种扣分
  • 建议组合:
    1. Resize((224, 224)) (必选)
    2. RandomHorizontalFlip() (水平翻转)
    3. RandomRotation() (随机旋转)
    4. ColorJitter() (颜色抖动)
    5. RandomAffine(): 随机仿射变换 (平移、缩放)
    6. ToTensor() (转张量,必选)
    7. Normalize() (归一化,必选)

任务 (3): Transform 可视化 (2 分)

  • 核心要求:展示图片在经过你设计的 Transform 处理后变成了什么样
  • 操作:取一张原始图片,应用你的 transform,然后用 matplotlib.pyplot.imshow 画出来
  • 坑点:如果 transform 里包含了 Normalize,图片颜色会变得很奇怪(因为数值被标准化了)。在可视化时,建议先展示不带 Normalize 的增强效果,或者编写一个反归一化函数再显示

第二阶段:自定义模型#

任务 (4): 搭建个人模型 & TensorBoard 可视化 (2 分)

  • 核心要求:
    1. 自定义一个简单的 CNN 类(比如 3-4 层卷积层)
    2. 使用 SummaryWriter 记录训练过程中的 Loss 和 Accuracy
  • 交付物:报告中必须要有 TensorBoard 的截图(Loss 曲线下降,Accuracy 曲线上升)

任务 (5): 验证集评估 & 混淆矩阵 (2 分)

  • 核心要求:模型训练完后,在验证集(Validation Set)上跑一遍
  • 交付物:
    1. 准确率数值:例如 “Validation Accuracy: 65.4%”
    2. 混淆矩阵图:这是一个 11x11 的热力图。它可以告诉你模型是不是把“Bread(面包)”错误地分类成了“Dessert(甜点)”。需要使用 sklearn.metrics.confusion_matrixseaborn.heatmap

任务 (6): 测试集预测输出 (3 分)

  • 核心要求:用训练好的模型对无标签的 testing 文件夹里的图片进行预测
  • 交付物:生成 ans_ours.csv 文件
  • 格式:CSV 需要两列:Id (文件名) 和 Category (预测的类别数字)

第三阶段:经典模型 (VGG)#

任务 (7): 搭建 VGG 模型 & 打印参数 (2 分)

  • 核心要求:实现 VGG16 或 VGG19

  • 选择策略:

    使用 torchvision.models.vgg16()重要修改点:VGG 原生模型是输出 1000 类(ImageNet),需要把最后一层全连接层 (classifier) 修改为输出 11 类

  • 交付物:在代码中运行 print(model) 或使用 torchsummary 打印网络结构和参数量

任务 (8): VGG 预测输出 (3 分)

  • 核心要求:用训练好的 VGG 模型再次对测试集进行预测
  • 交付物:生成 ans_vgg.csv 文件
  • 预期结果:VGG 的效果理论上应该比自己随便写的简单模型要好

(1) 请根据已有数据集,对 training 和 validation 进行处理,构造训练数据集#

1.1. 数据检查#

观察数据集名字,发现名字统一命名为 “数字_数字”。根据数据集介绍,前一个数字为类别编号,下划线后的数字为图片在类别里的编号。即 [类别]_[编号]

a

上网查找类别编号对应的标签,得到编号从 0 到 10 分别对应于:

# 11个食物类别
CLASS_NAMES = [
    "Bread", "Dairy product", "Dessert", "Egg", "Fried food",
    "Meat", "Noodles/Pasta", "Rice", "Seafood", "Soup",
    "Vegetable/Fruit"
]
python

了解了数据集基本结构后,开始构建数据集。

由图片命名格式可知,需要将图片名字的字符串使用 .spilt 进行切分,然后将对应的类别名字映射为图片名字前面的数字(类别编号)。

1.2. 定义数据集类#

class FoodDataset(Dataset):
    """
    Food-11 数据集的自定义 Dataset 类
    文件名格式: [类别]_[编号].jpg
    例如: 0_123.jpg 表示类别0(Bread)的第123张图片
    """
python

初始化时获取所有图片,并且解析需要的类别标签(训练集和验证集的标签)。

    def __init__(self, root, transform=None, mode='train'):
        # 获取所有图片文件
        self.images = sorted([f for f in os.listdir(root) if f.endswith('.jpg')])

        # 如果不是测试集,解析标签
        if mode != 'test':
            self.labels = []
            for img_name in self.images:
                # 文件名格式: [类别]_[编号].jpg
                label = int(img_name.split('_')[0])
                self.labels.append(label)
python

使用内置的.__getitem__方法,处理具体的每一张图片:

  1. 将图片转换为 RGB 数组
  2. 对图片应用transform变换
  3. 然后根据是否是测试集,返回图片的文件名或者图片的标签名
    def __getitem__(self, idx):
        # 读取图片
        img_path = os.path.join(self.root, self.images[idx])
        image = Image.open(img_path).convert('RGB')

        # 应用变换
        if self.transform:
            image = self.transform(image)

        # 返回数据
        if self.mode == 'test':
            return image, self.images[idx]  # 测试集返回图片和文件名
        else:
            return image, self.labels[idx]  # 训练/验证集返回图片和标签
python

再定义一个外置方法,返回图片的类别名字:

    def get_class_name(self, label):
        """根据标签获取类别名称"""
        return CLASS_NAMES[label]
python

1.3. 创建数据集进行测试#

创建数据集测试效果:

data_dir = 'datasets/food11'
    simple_transform = transforms.Compose([
        transforms.Resize((224, 224)),
        transforms.ToTensor()
    ])
    # 创建训练数据集
    train_dataset = FoodDataset(
        root=os.path.join(data_dir, 'training'),
        transform=simple_transform,
        mode='train'
    )
python

测试结果

1.4. 创建数据加载器方法#

创建数据加载器方法,便于后续加载数据集进行模型训练。

def get_dataloader(data_dir, batch_size=64, train_transform=None, test_transform=None):
    """
    创建训练集、验证集和测试集的 DataLoader

    Args:
        data_dir: 数据集根目录 (如 datasets/food11)
        batch_size: 批次大小
        train_transform: 训练集变换
        test_transform: 验证集/测试集变换

    Returns:
        train_loader, val_loader, test_loader
    """
python

(2) 为了保证数据的训练性能,请利用 torchvision.transforms 设计属于你的 train_transform,train_transform 模版已事先给出。你所设计的 train_transform 需要指定你所选择的特征图大小,并至少由五种 transform 组成#

该部分代码位于 transforms.py

由于每一张图片的大小并不统一,并且后续要使用 VGG16 模型进行训练,因此使用transforms.resize()方法,将图片大小统一为(224,224)

查看transforms库的 API 接口,可以使用以下的方法扩展数据集,提高模型的性能。

  • .Resize:令所有图片尺寸统一为 VGG 模型需要的 (224,224)
  • .RandomHorizontalFlip:以一个概率 pp 进行随机的水平翻转。
  • .RandomRotation:随机旋转一个特定的角度,此处选择degree=15
  • .ColorJitter:随机改变图像的亮度、对比度、饱和度和色调。此处依次选择 0.2,0.2,0.2,0.1。
  • RandomAffine:随机进行仿射变换

注意:后续可视化展示时,如果 transform 里包含了 Normalize,图片颜色会变得很奇怪(因为数值被标准化)。在可视化时,可进行”是否选择Normalize”的对比。

因此定义两个数据集:实际使用的训练集train_transform和可视化展示的数据集visual_transform。此外顺便将验证集和测试集也变换为合适的尺寸大小,方便后续进行模型验证。

变换结构显示

Train Transform 包含以下变换:
  1. Resize
  2. RandomHorizontalFlip
  3. RandomRotation
  4. ColorJitter
  5. RandomAffine
  6. ToTensor
  7. Normalize

 7 种变换
bash

(3) 对你所设计的 train_transform 进行可视化,将变换后的结果进行可视化展示#

方法设计#

1. 设计一个反归一化函数,将 Normalize 后的图片还原用于显示

2. 设计一 visualize_transforms 函数,展示 transform 的效果

关键代码#

  1. denormalize()

    def denormalize(tensor):
        """反归一化,将 Normalize 后的图片还原用于显示"""
        mean = [0.485, 0.456, 0.406]
        std = [0.229, 0.224, 0.225]
        for t, m, s in zip(tensor, mean, std):
            t.mul_(s).add_(m)
        return tensor
    python

    均值和方差使用著名的 ImageNet 数据集的统计值。后续对预训练的 VGG 模型进行二次训练时需要使用此数据,确保数据预处理的统一性。

  2. visualize_transforms()

效果展示#

增强效果=[Resize,随机水平翻转,随机旋转(15°),颜色抖动,随机仿射变换]
bash

1. 未进行反归一化

2

第二行是经过设计的 transform 后未进行反归一化的图片。可以看到,相对于上方 transform 最后一层没加 Normalize 的图片,第二行包含 Normalize 变换后的图片,颜色变得很奇怪。图像呈现出奇怪的偏色:偏品红、偏绿,图片严重失真

2. 进行反归一化

3

第二行是经过设计的 transform 后并进行反归一化的图片。可以看到,相比于上一幅图的第二行,这幅图的第二行显得十分正常。原因就是经过了反归一化函数denormalize

    for i in range(num_samples):
        transformed = train_transform(origin_img).clone()
        # 反归一化
        img_denorm = denormalize(transformed)
        img_denorm = img_denorm.clamp(0, 1)  # 限制在 [0, 1]
        img_np = img_denorm.permute(1, 2, 0).numpy()
python

(4) 搭建好个人设计的训练模型,并利用 tensorboard 对过程进行可视化展示#

CNN 网络结构#

模型展示(位于model.py):

class FoodCNN(nn.Module):
    """
    自定义 CNN 模型用于 Food-11 分类
    结构: 4层卷积 + 3层全连接
    """
python

训练流程#

  1. 导入预先定义的变换和数据加载器

    from dataloader import get_dataloader, CLASS_NAMES
    from transforms import train_transform, test_transform
    from model import FoodCNN
    python
  2. 定义每一个 epoch 的训练循环

    def train_one_epoch(model, train_loader, loss_fn, optimizer, device):
        """
        一个 epoch 的训练
        :return: 
        epoch_loss: 该批次的平均损失
        epoch_acc: 该批次的平均准确率
        """
    python

    具体循环:

        for images, labels in tqdm(train_loader, desc='Training', leave=False):
            images, labels = images.to(device), labels.to(device)
            
            optimizer.zero_grad()
            outputs = model(images)
    
            loss = loss_fn(outputs, labels)
            loss.backward()
            optimizer.step()
    
            running_loss += loss.item()
            _, predicted = outputs.max(1)
            total += labels.size(0)
            correct += predicted.eq(labels).sum().item()
    python
  3. 定义验证函数

    def validate(model, val_loader, criterion, device):
        """验证模型"""
        """
        epoch_loss: 该批次的平均损失
        epoch_acc: 该批次的平均准确率
        """
         with torch.no_grad():
                ...
    python
  4. 定义完整的训练流程

    def train(config):
        """完整训练流程"""
    python
    1. 选择训练设备
    2. 加载训练集、验证集
    3. 实例化模型
    4. 使用 Tensorboard 记录模型结构
    5. 进入训练循环
      1. 进入训练集循环
      2. 进入验证集循环
      3. 学习率调度
      4. Tensorboard 记录训练损失值、准确率、学习率
      5. 保存最优模型

结果展示#

准确率

4

学习率

5

由于本地 4060 显卡跑得太慢,故转移到华为云使用 T4 卡进行训练。

印象里就算使用 T4 也跑了 30 分钟以上,跑模型还是太烧钱了。

6

(5) 在验证集上显示你的准确性和混淆矩阵#

def evaluate(model, val_loader, device):
    """评估模型,返回预测结果和真实标签"""
def plot_confusion_matrix(y_true, y_pred, class_names, save_path='confusion_matrix.png'):
    """绘制混淆矩阵"""
python

输出结果

7

可以看到,该自定义 CNN 模型表现出明显的偏科。它能准确识别汤(Soup)、肉(Meat)等特征鲜明的食物,但对奶制品(Dairy product)、鸡蛋(Egg)和米饭(Rice)等类别则出现严重混淆,特别是将大量奶制品误判为甜点。这表明该模型结构虽合理,但对于学习视觉上相似或概念重叠的细粒度特征能力不足。

(6) 将个人设计模型对测试集预测结果输出到 ans_ours.csv 中#

def main():
	output_path = 'ans_ours.csv'
	model = FoodCNN(num_classes=11).to(device)
    model.load_state_dict(torch.load(model_path))
    print(f"已加载模型: {model_path}")
    model = model.to(device)
	# 预测结果
    filenames, predictions = predict(model, test_loader, device)

    df = pd.DataFrame({
        'Id': filenames,
        'Category': predictions
    })
    df.to_csv(output_path, index=False)
python
预测结果已保存到: ans_ours.csv
 3347 条预测

前10条预测结果:
          Id  Category
0    0_0.jpg         0
1    0_1.jpg         3
2   0_10.jpg         0
3  0_100.jpg         5
4  0_101.jpg         2
5  0_102.jpg         0
6  0_103.jpg         2
7  0_104.jpg         0
8  0_105.jpg         0
9  0_106.jpg         3
bash

“ans_ours.csv”会放到附件中上传。

(7) 请自行查询资料,搭建 VGG 系列模型,并打印模型参数#

查资料得

VGG 网络是计算机视觉领域的经典卷积神经网络架构。其核心特点是结构规整,全网统一使用 3×33\times3 的小卷积核和 2×22\times2 的最大池化层,通过重复堆叠卷积层块(Block)来增加网络深度,从而提取更高级的语义特征。

  • VGG16:由 5 个卷积块组成,包含 13 个卷积层3 个全连接层(共 16 层权重层)。从日志可见,其总参数量约为 1.34 亿
  • VGG19:在 VGG16 的基础上增加了卷积深度,包含 16 个卷积层3 个全连接层(共 19 层权重层),参数量增至约 1.4 亿

为适配 Food-11 数据集,将两个模型的最终全连接层均修改为 11 维输出。由统计信息可知,VGG 系列模型的参数主要集中在第一个全连接层(约 1 亿参数),导致模型体积较大(>500MB),对计算资源有较高要求。

(8) 使用 VGG 系列模型对测试集进行预测,并将结果输出到 ans_vgg.csv 中#

模型二次训练#

训练代码和训练自定义 CNN 模型时一模一样,只给出区别代码,不过多重复。

def train(config):
   # VGG16 模型 (使用预训练权重)
    model = VGG16Food(num_classes=11, pretrained=True).to(device)
    print("已加载 VGG16 预训练权重")
if __name__ == '__main__':
    config = {
        'data_dir': 'datasets/food11',
        'batch_size': 64,
        'epochs': 15, # 二次训练轮数可以低一点(显卡照样顶不住)
        'lr': 0.0001,
        'log_dir': 'runs/vgg16',
        'save_path': 'best_vgg16.pth'
    }
python

模型预测#

代码和预测自定义 CNN 模型时一样。

    data_dir = 'datasets/food11'
    model_path = 'best_vgg16.pth'
    output_path = 'ans_vgg.csv'
python

预测结果输出到 “ans_vgg.csv”中。

预测结果

深度学习实验三
Author Juyao Huang
Published at November 29, 2025
Comment seems to stuck. Try to refresh?✨