搜索 问答 Rules & Tips
1. 遵守中国大陆相关法律法规
2. 在虫部落的提问准则
3. 禁止发布代下载类求助信息
4. 特别提倡抛砖引玉的问题
5. 扩展阅读:提问的智慧

大佬们,这道题咋整,大一纯小白,选的人工智能课程布置的作业都快把我给逼疯了,时不时就爆红

查看: 1967|回复: 7
何顺南 发表于 2024-10-13 15:12:30
已解决 20Bit
  1. #%% md
  2. # # 实验说明
  3. # 本实验的主要内容为:在CIFAR-10数据集上训练一个神经网络,了解训练和推理(预测)的基本流程、探究神经网络的设计(包括网络结构、超参数)对模型性能的影响。
  4. # > CIFAR-10数据集,又称加拿大高等研究院数据集(Canadian Institute for Advanced Research)是一个常用于训练机器学习和计算机视觉算法的图像集合。它是最广泛使用的机器学习研究数据集之一。CIFAR-10数据集包含60,000张32×32像素的彩色图像,分为10个不同的类别。这10个类别分别是飞机、汽车、鸟类、猫、鹿、狗、青蛙、马、船和卡车,每个类别有6,000张图片。——维基百科
  5. #
  6. # 在进行实验前,最好对以下内容有所了解:
  7. # - 基础的Python语法;
  8. # - 机器学习领域的基本概念与基础的数学知识(参见[动手学深度学习](https://zh.d2l.ai/index.html)1-3章);
  9. # - 神经网络的基本概念(如全连接层、卷积层、池化层,参见[动手学深度学习](https://zh.d2l.ai/index.html)4-6章)。
  10. #
  11. # # 实验要求
  12. # 实验基础代码已经在下面给出。你需要保证自己在熟悉了基础代码后,在Task1~Task4中选择至少3个完成。
  13. # - 截止日期:$\text{2024.10.20 23:59}$
  14. # - 提交内容:源代码、实验报告、模型权重文件
  15. # - 报告要求:提交pdf版本实验报告,包括摘要、引言、方法、实验以及总结部分,4页左右。如果你在探索中有任何其它发现或想法,也请加入到报告中。
  16. # # 实验报告说明
  17. # 0. **摘要(Abstract)**:引⾔的⼀个⾼度浓缩版本,通过⼀段话介绍你研究的所有内容。
  18. # 1. **引⾔(Introduction)**:这是摘要的⼀个扩写版本,包含更多⼀些的细节。通过⼏段话(2-3段)总结整个报告的内容,让读者通过阅读引⾔就可以知道你做的具体内容。⼀般包括:要解决的问题、⽬标、相关技术背景、你的实验思路和主要实验结论。
  19. # 2. **方法(Methodology)**:介绍你具体的实验思路,详细定义研究问题,详细描述实验⽅法,要求简单、精炼、准确。有必要的话需要给出具体的算法。
  20. # 3. **实验(Experiments)**:包括实验设置(Experimental Setup)和实验结果(Main Results)两个部分。其中,实验设置介绍模型、数据集、超参数设置等;实验结果以图、表的形式介绍每组实验的结果和发现。
  21. # 4. **总结(Conclusion)**:以简短的⼀段话总结整个报告。
  22. # # 实验基础代码
  23. # 机器学习可大致分为三个部分:数据、模型以及算法。下面,我们按照这一顺序给出实验的基础代码。
  24. #
  25. # # 实验分数说明
  26. # 共四个子任务,每个3分,实验报告8分
  27. #%% md
  28. # # 0. 导入必要的库
  29. #%%
  30. import torch
  31. import torchvision
  32. import torchvision.transforms as transforms
  33. from torchvision.transforms import ToPILImage
  34. show = ToPILImage()
  35. #%%

  36. #%% md
  37. # # 1. 准备训练数据与测试数据
  38. # 首先我们介绍数据部分。`torchvision`库已经为我们直接提供了CIFAR-10数据集的下载,我们就不需要手动地去网上下载了。代码中的`transform`将原始的图像转换为模型易于处理的形式。`trainloader`和`testloader`分别用于加载训练集和测试集。
  39. #%% md
  40. #
  41. #%%
  42. # 设定对图片的归一化处理方式,并且下载数据集
  43. transform = transforms.Compose(
  44.     [transforms.ToTensor(),
  45.      transforms.Normalize((0.5, 0.5, 0.5), (0.5, 0.5, 0.5))
  46.      ])

  47. batch_size = 4

  48. trainset = torchvision.datasets.CIFAR10(root='./dataset', train=True,
  49.                                         download=True, transform=transform)
  50. trainloader = torch.utils.data.DataLoader(trainset, batch_size=batch_size,
  51.                                           shuffle=True, num_workers=2)

  52. testset = torchvision.datasets.CIFAR10(root='./dataset', train=False,
  53.                                        download=True, transform=transform)
  54. testloader = torch.utils.data.DataLoader(testset, batch_size=batch_size,
  55.                                          shuffle=False, num_workers=2)

  56. #%% md
  57. # 通过运行下面的代码,可得知CIFAR-10数据集的训练集包含50000张图像,每张图像的大小为32*32;数据集中的每个元素是由数据和对应的标签组成的。
  58. #%%
  59. # 观察一下数据集的内容
  60. classes = ('plane', 'car', 'bird', 'cat', 'deer', 'dog', 'frog', 'horse', 'ship', 'truck') # 类别名称
  61. print(len(trainset)) # 训练集大小
  62. print(trainset[0][0].size()) # 第 1 条数据的图像大小
  63. print(trainset[0][1]) # 第 1 条数据的标签
  64. print(classes[trainset[0][1]]) # 第 1 条数据的文本标签
  65. #%% md
  66. # 下面这段代码在训练集中随意抽出一张进行可视化,注意在展示之前我们需要将其做上述`transform`的逆变换:
  67. #%%
  68. # 1 - 4行与上一块代码意义类似
  69. (data, label) = trainset[12] # 选群训练集的一个样本展示内容,也可以改成其他数字看看
  70. print(data.size())
  71. print(label) # label是整数
  72. print(classes[label])
  73. show((data + 1) / 2).resize((100, 100)) # 还原被归一化的图片
  74. #%% md
  75. # # 2. 定义用于分类的网络结构
  76. #%% md
  77. # 这一部分我们定义用于图像分类的网络结构,实现一个早期的卷积神经网络LeNet。它由两个卷积层和三个全连接层组成。pytorch为我们提供了方便的接口定义神经网络,但我们这里不着重介绍具体的语法,只观察数据是怎样在模型中“流动”的:
  78. # - 在`__init__`方法中,我们将上述的卷积层和全连接层初始化为`conv1、conv2`和`fc1、fc2、fc3`;
  79. # - 卷积层以`conv1`为例,它的初始化为`Conv2d(3, 6, 5)`,即:3输入通道(RGB图像的三个通道)、6输出通道、5*5大小的卷积核的卷积层。
  80. # - 全连接层以`fc1`为例,它的初始化为`Linear(16 * 5 * 5, 120)`,即:从400维映射到120维。
  81. # - `forward`方法用于规定数据在模型中的计算过程。输入的形状在传播过程中的变化参见`forward`中的注释。最终,我们得到了一个大小为`[batch size, 10]`的张量(矩阵)。
  82. #%%
  83. import torch.nn as nn
  84. import torch.nn.functional as F

  85. class Net(nn.Module):
  86.     def __init__(self):
  87.         # nn.Module子类的函数必须在构造函数中执行父类的构造函数
  88.         super(Net, self).__init__()

  89.         # 卷积层 '3'表示输入图片为单通道, '6'表示输出通道数,'5'表示卷积核为5*5
  90.         self.conv1 = nn.Conv2d(3, 6, 5)
  91.         # 卷积层
  92.         self.conv2 = nn.Conv2d(6, 16, 5)
  93.         # 仿射层/全连接层,y = Wx + b
  94.         self.fc1   = nn.Linear(16*5*5, 120)
  95.         self.fc2   = nn.Linear(120, 84)
  96.         self.fc3   = nn.Linear(84, 10)

  97.     def forward(self, x):
  98.         # 卷积 -> 激活 -> 池化 (relu激活函数不改变输入的形状)
  99.         # [batch size, 3, 32, 32] -- conv1 --> [batch size, 6, 28, 28] -- maxpool --> [batch size, 6, 14, 14]
  100.         x = F.max_pool2d(F.relu(self.conv1(x)), (2, 2))
  101.         # [batch size, 6, 14, 14] -- conv2 --> [batch size, 16, 10, 10] --> maxpool --> [batch size, 16, 5, 5]
  102.         x = F.max_pool2d(F.relu(self.conv2(x)), 2)
  103.         # 把 16 * 5 * 5 的特征图展平,变为 [batch size, 16 * 5 * 5],以送入全连接层
  104.         x = x.view(x.size()[0], -1)
  105.         # [batch size, 16 * 5 * 5] -- fc1 --> [batch size, 120]
  106.         x = F.relu(self.fc1(x))
  107.         # [batch size, 120] -- fc2 --> [batch size, 84]
  108.         x = F.relu(self.fc2(x))
  109.         # [batch size, 84] -- fc3 --> [batch size, 10]
  110.         x = self.fc3(x)        
  111.         return x

  112. net = Net()
  113. print(net)
  114. #%% md
  115. # # 3. 模型训练与测试过程
  116. # 准备好数据、定义好模型后,我们开始训练过程。为了把一个随机初始化的模型优化成一个“好”的模型,我们还需要定义:
  117. # - 损失函数$\mathcal{L}$:损失函数以一般同时以模型的预测$\hat{y}$和真实的标签$y$为输入,输出一个标量。这个标量越小,说明模型在数据上拟合得越好。我们的目的就是要最小化这个损失函数$\mathcal{L}(\hat{y},y).$分类问题常使用交叉熵函数作为损失函数。
  118. # - 优化方法:为了最小化损失函数,我们就要使用数学的优化方法找到一组最优的参数(这里的参数即神经网络中卷积层、全连接层等的参数,而非batch size等超参数)。深度学习中一般使用迭代的方式求解,常用的方法有SGD(随机梯度下降)、Adam等。
  119. # pytorch库内置了各种优化器,我们无需手动实现梯度下降过程。
  120. #%%
  121. from torch import optim
  122. criterion = nn.CrossEntropyLoss() # 交叉熵损失函数
  123. optimizer = optim.SGD(net.parameters(), lr=0.001, momentum=0.9) # 使用SGD(随机梯度下降)优化
  124. num_epochs = 5 # 训练 5 个 epoch
  125. #%% md
  126. # 下面我们定义用于训练过程的代码。最外层循环控制在整个数据集上训练的次数(即epoch);内层循环按照以下流程进行:
  127. # 1. 取出数据(一次取出一个batch);
  128. # 2. 将数据送入网络,计算损失函数;
  129. # 3. 使用损失函数计算梯度,进行反向传播更新参数。
  130. #%%
  131. def train(trainloader, net, num_epochs, criterion, optimizer, save_path):
  132.     for epoch in range(num_epochs):     
  133.         running_loss = 0.0
  134.         for i, data in enumerate(trainloader, 0):
  135.    
  136.             # 1. 取出数据
  137.             inputs, labels = data
  138.    
  139.             # 梯度清零
  140.             optimizer.zero_grad()
  141.    
  142.             # 2. 前向计算和反向传播
  143.             outputs = net(inputs) # 送入网络(正向传播)
  144.             loss = criterion(outputs, labels) # 计算损失函数
  145.             
  146.             # 3. 反向传播,更新参数
  147.             loss.backward() # 反向传播
  148.             optimizer.step()

  149.             # 下面的这段代码对于训练无实际作用,仅用于观察训练状态
  150.             running_loss += loss.item()
  151.             if i % 1000 == 999: # 每2000个batch打印一下训练状态
  152.                 print('epoch %d: batch %5d loss: %.3f' \
  153.                       % (epoch+1, i+1, running_loss / 2000))
  154.                 running_loss = 0.0
  155.                
  156.         torch.save(net.state_dict(), f"{save_path}/epoch_{epoch + 1}_model.pth")
  157.         
  158.     print('Finished Training')
  159. #%%
  160. # 使用定义的网络进行训练
  161. save_path = '<input your path>'
  162. train(trainloader, net, num_epochs, criterion, optimizer, save_path)
  163. #%% md
  164. # 训练过程结束后,我们得到了一个在训练集上拟合较好的模型。下面我们要测试它在测试集上表现如何。预测的代码与训练中的正向传播类似,但是不需要计算损失函数(损失函数在实验中仅用于更新参数,预测时参数固定,也就不需要它了)。
  165. #
  166. # 预测的流程如下:
  167. # 1. 取出数据;
  168. # 2. 正向传播,得到模型的输出结果;
  169. # 3. 从输出结果中得到模型预测;
  170. # 4. 和真实标签进行比对,计算性能指标。
  171. #
  172. # 注意:模型的输出结果在第2部分中已经说明,为一个`[batch size, 10]`大小的张量(矩阵),每一行是一条数据属于10个类别的概率的相对大小(这一输出也被称为`logits`)。为了得到模型的预测,我们需要对这一输出在每行上取最大值,取得最大值的**位置**就是模型的预测。
  173. #%%
  174. def predict(testloader, net):
  175.     correct = 0 # 预测正确的图片数
  176.     total = 0 # 总共的图片数
  177.    
  178.     with torch.no_grad(): # 正向传播时不计算梯度
  179.         for data in testloader:
  180.             # 1. 取出数据
  181.             images, labels = data
  182.             # 2. 正向传播,得到输出结果
  183.             outputs = net(images)
  184.             # 3. 从输出中得到模型预测
  185.             _, predicted = torch.max(outputs, 1)
  186.             # 4. 计算性能指标
  187.             total += labels.size(0)
  188.             correct += (predicted == labels).sum()
  189.    
  190.     print('测试集中的准确率为: %d %%' % (100 * correct / total))
  191. #%%
  192. predict(testloader, net)
  193. #%% md
  194. #
  195. # # Task1:绘制损失函数曲线
  196. # 损失函数能够量化模型在数据集上的拟合程度,帮助我们了解模型训练的进程。请在`3.模型训练与测试过程`中补充代码,记录训练过程中损失`loss`的变化,使用合适的Python数据类型将其保存,并使用`matplotlib`库将其可视化。可参照以下的代码进行绘图。你可以直接用损失函数可视化的代码覆盖下面的代码块。
  197. #%%
  198. import matplotlib.pyplot as plt

  199. def draw(values):
  200.     plt.plot(values)
  201.     plt.show()

  202. draw([0.1, 0.2, 0.4, 0.8])
  203. #%% md
  204. # 请在报告中附上训练过程中损失函数的变化。训练集上的损失越小,说明模型的效果就越好吗?
  205. #%% md
  206. # # Task2: 加入正则化
  207. #%% md
  208. # - $L_2$正则化:请查阅Pytorch[有关SGD优化器的文档](https://pytorch.org/docs/stable/generated/torch.optim.SGD.html#sgd)或其它网络资料,修改`3. 模型训练与测试过程`中的代码,尝试为模型的损失函数加入一项$L_2$损失,并在报告中说明你所做的修改。
  209. # - Dropout正则化:请查阅Pytorch[有关Dropout层的文档](https://pytorch.org/docs/stable/generated/torch.nn.Dropout.html#dropout)或其它网络资料,修改`2. 定义用于分类的网络结构`中的代码,在**第一个线性层和第二个线性层之间**加入一个Dropout层,并在报告中说明你所做的修改。
  210. # - 在报告中简述两种正则化方法的基本原理。
  211. #%%
  212. # TODO: 在Dropout_Net中加入dropout层
  213. class Dropout_Net(nn.Module):
  214.     def __init__(self):
  215.         # nn.Module子类的函数必须在构造函数中执行父类的构造函数
  216.         super(Dropout_Net, self).__init__()

  217.         # 卷积层 '1'表示输入图片为单通道, '6'表示输出通道数,'5'表示卷积核为5*5
  218.         self.conv1 = nn.Conv2d(3, 6, 5)
  219.         # 卷积层
  220.         self.conv2 = nn.Conv2d(6, 16, 5)
  221.         # 仿射层/全连接层,y = Wx + b
  222.         self.fc1   = nn.Linear(16*5*5, 120)
  223.         self.fc2   = nn.Linear(120, 84)
  224.         self.fc3   = nn.Linear(84, 10)

  225.     def forward(self, x):
  226.         # 卷积 -> 激活 -> 池化
  227.         x = F.max_pool2d(F.relu(self.conv1(x)), (2, 2))
  228.         x = F.max_pool2d(F.relu(self.conv2(x)), 2)
  229.         # reshape,‘-1’表示自适应
  230.         x = x.view(x.size()[0], -1)
  231.         x = F.relu(self.fc1(x))
  232.         x = F.relu(self.fc2(x))
  233.         x = self.fc3(x)        
  234.         return x
  235.    
  236. dropout_net = Dropout_Net()
  237. #%%
  238. # TODO: 在这里重新定义optimizer,加入L2正则化项

  239. # 使用新定义的网络和优化器进行训练与测试
  240. train(trainloader, dropout_net, num_epochs, criterion, optimizer)
  241. predict(testloader, dropout_net)
  242. #%%

  243. #%% md
  244. # # Task3: 调整参数
  245. # 在`3. 模型训练与测试过程`部分中,我们定义了一些超参数(如`num_epoch`、优化器的`lr`)。调节这些参数,观察损失函数以及模型在测试集上的性能变化,在报告中简要说明这些指标的变化,尝试分析这些超参数对整个模型的影响。
  246. #%%
  247. # TODO: 在这里修改参数,重新进行训练和测试,并记录数据
  248. #%% md
  249. # # Task4: 实现自己的网络
  250. # 查阅资料(参考:[动手学深度学习](https://zh.d2l.ai/chapter_convolutional-modern/index.html)以及[`torchvision`的模型源码](https://github.com/pytorch/vision/tree/main/torchvision/models)),修改`2. 定义用于分类的网络结构`中的代码,实现一种现代卷积神经网络。与最基础的LeNet相比,你实现的神经网络在性能、训练时间上有何差异?
  251. #%%
  252. # TODO: 参考2.中Net的实现,设计自己的网络,进行训练和测试,并记录数据
复制代码
cress2002 发表于 2024-10-13 15:12:31


如何完成CIFAR-10图像分类作业 - 完整步骤指南

步骤1: 环境设置
1. 安装Python(如果还没有的话)
2. 安装必要的库:

  1. pip install torch torchvision matplotlib jupyter
复制代码


3. 启动Jupyter Notebook:

  1. jupyter notebook
复制代码


步骤2: 创建新的Notebook
1. 在Jupyter界面中,点击"New" -> "Python 3"创建一个新的notebook
2. 将作业中提供的基础代码复制到notebook的不同单元格中

步骤3: 运行基础代码
1. 逐个单元格运行代码(使用Shift+Enter)
2. 确保代码运行无误,能够成功训练LeNet模型

步骤4: 完成Task 1 - 绘制损失函数曲线
1. 修改训练函数,记录每个epoch的loss:

  1. def train(trainloader, net, num_epochs, criterion, optimizer):
  2.     losses = []  # 用于存储每个epoch的平均loss
  3.     for epoch in range(num_epochs):     
  4.         running_loss = 0.0
  5.         for i, data in enumerate(trainloader, 0):
  6.             inputs, labels = data
  7.             optimizer.zero_grad()
  8.             outputs = net(inputs)
  9.             loss = criterion(outputs, labels)
  10.             loss.backward()
  11.             optimizer.step()
  12.             running_loss += loss.item()
  13.         
  14.         epoch_loss = running_loss / len(trainloader)
  15.         losses.append(epoch_loss)
  16.         print(f'Epoch {epoch+1}, Loss: {epoch_loss:.3f}')
  17.    
  18.     return losses  # 返回记录的losses
复制代码


2. 使用matplotlib绘制loss曲线:

  1. import matplotlib.pyplot as plt

  2. losses = train(trainloader, net, num_epochs, criterion, optimizer)

  3. plt.plot(losses)
  4. plt.title('Training Loss')
  5. plt.xlabel('Epoch')
  6. plt.ylabel('Loss')
  7. plt.show()
复制代码


步骤5: 完成Task 2 - 加入正则化
1. 添加L2正则化:

  1. optimizer = optim.SGD(net.parameters(), lr=0.001, momentum=0.9, weight_decay=0.0001)
复制代码


2. 添加Dropout:

  1. class Net(nn.Module):
  2.     def __init__(self):
  3.         super(Net, self).__init__()
  4.         self.conv1 = nn.Conv2d(3, 6, 5)
  5.         self.conv2 = nn.Conv2d(6, 16, 5)
  6.         self.fc1   = nn.Linear(16*5*5, 120)
  7.         self.dropout = nn.Dropout(0.5)  # 添加这行
  8.         self.fc2   = nn.Linear(120, 84)
  9.         self.fc3   = nn.Linear(84, 10)

  10.     def forward(self, x):
  11.         x = F.max_pool2d(F.relu(self.conv1(x)), (2, 2))
  12.         x = F.max_pool2d(F.relu(self.conv2(x)), 2)
  13.         x = x.view(x.size()[0], -1)
  14.         x = F.relu(self.fc1(x))
  15.         x = self.dropout(x)  # 添加这行
  16.         x = F.relu(self.fc2(x))
  17.         x = self.fc3(x)
  18.         return x
复制代码


3. 重新训练模型并比较结果

步骤6: 完成Task 3 - 调整参数
1. 尝试不同的学习率:

  1. optimizer = optim.SGD(net.parameters(), lr=0.01, momentum=0.9)
复制代码


2. 尝试不同的epoch数:

  1. num_epochs = 10
复制代码


3. 每次修改后重新训练并记录结果

步骤7: 完成Task 4 - 实现自己的网络
1. 实现一个简单的ResNet结构:

  1. class ResidualBlock(nn.Module):
  2.     def __init__(self, in_channels, out_channels, stride=1):
  3.         super(ResidualBlock, self).__init__()
  4.         self.conv1 = nn.Conv2d(in_channels, out_channels, kernel_size=3, stride=stride, padding=1)
  5.         self.bn1 = nn.BatchNorm2d(out_channels)
  6.         self.conv2 = nn.Conv2d(out_channels, out_channels, kernel_size=3, stride=1, padding=1)
  7.         self.bn2 = nn.BatchNorm2d(out_channels)
  8.         
  9.         self.shortcut = nn.Sequential()
  10.         if stride != 1 or in_channels != out_channels:
  11.             self.shortcut = nn.Sequential(
  12.                 nn.Conv2d(in_channels, out_channels, kernel_size=1, stride=stride),
  13.                 nn.BatchNorm2d(out_channels)
  14.             )

  15.     def forward(self, x):
  16.         out = F.relu(self.bn1(self.conv1(x)))
  17.         out = self.bn2(self.conv2(out))
  18.         out += self.shortcut(x)
  19.         out = F.relu(out)
  20.         return out

  21. class ResNet(nn.Module):
  22.     def __init__(self, num_classes=10):
  23.         super(ResNet, self).__init__()
  24.         self.in_channels = 64
  25.         self.conv1 = nn.Conv2d(3, 64, kernel_size=3, stride=1, padding=1)
  26.         self.bn1 = nn.BatchNorm2d(64)
  27.         self.layer1 = self.make_layer(ResidualBlock, 64, 2, stride=1)
  28.         self.layer2 = self.make_layer(ResidualBlock, 128, 2, stride=2)
  29.         self.layer3 = self.make_layer(ResidualBlock, 256, 2, stride=2)
  30.         self.avg_pool = nn.AdaptiveAvgPool2d((1, 1))
  31.         self.fc = nn.Linear(256, num_classes)

  32.     def make_layer(self, block, out_channels, num_blocks, stride):
  33.         strides = [stride] + [1] * (num_blocks - 1)
  34.         layers = []
  35.         for stride in strides:
  36.             layers.append(block(self.in_channels, out_channels, stride))
  37.             self.in_channels = out_channels
  38.         return nn.Sequential(*layers)

  39.     def forward(self, x):
  40.         out = F.relu(self.bn1(self.conv1(x)))
  41.         out = self.layer1(out)
  42.         out = self.layer2(out)
  43.         out = self.layer3(out)
  44.         out = self.avg_pool(out)
  45.         out = out.view(out.size(0), -1)
  46.         out = self.fc(out)
  47.         return out
复制代码


2. 使用新网络训练并比较结果:

  1. resnet = ResNet()
  2. train(trainloader, resnet, num_epochs, criterion, optimizer)
  3. predict(testloader, resnet)
复制代码


步骤8: 编写实验报告
总结你的实验过程、结果和分析,包括:
  • 损失函数曲线分析
  • 正则化的效果
  • 不同参数对模型性能的影响
  • 新网络架构与LeNet的比较


cress2002 发表于 2024-10-13 17:04:53
步骤1: 环境设置
1. 安装Python(如果还没有的话)
2. 安装必要的库:
   打开命令行,运行以下命令:
   ```
   pip install torch torchvision matplotlib
   ```
3. 安装Jupyter Notebook:
   ```
   pip install jupyter
   ```
4. 启动Jupyter Notebook:
   在命令行中输入:
   ```
   jupyter notebook
   ```
   这会在你的浏览器中打开Jupyter Notebook界面。

步骤2: 创建新的Notebook
1. 在Jupyter界面中,点击"New" -> "Python 3"创建一个新的notebook
2. 将作业中提供的基础代码复制到notebook的不同单元格中

步骤3: 运行基础代码
1. 逐个单元格运行代码(使用Shift+Enter)
2. 确保代码运行无误,能够成功训练LeNet模型

步骤4: 完成Task 1 - 绘制损失函数曲线
1. 修改训练函数,记录每个epoch的loss:
   ```python
   def train(trainloader, net, num_epochs, criterion, optimizer):
       losses = []  # 用于存储每个epoch的平均loss
       for epoch in range(num_epochs):     
           running_loss = 0.0
           for i, data in enumerate(trainloader, 0):
               # ... (保持原有代码不变)
               running_loss += loss.item()
           
           epoch_loss = running_loss / len(trainloader)
           losses.append(epoch_loss)
           print(f'Epoch {epoch+1}, Loss: {epoch_loss:.3f}')
      
       return losses  # 返回记录的losses
   ```

2. 使用matplotlib绘制loss曲线:
   ```python
   import matplotlib.pyplot as plt

   losses = train(trainloader, net, num_epochs, criterion, optimizer)

   plt.plot(losses)
   plt.title('Training Loss')
   plt.xlabel('Epoch')
   plt.ylabel('Loss')
   plt.show()
   ```

步骤5: 完成Task 2 - 加入正则化
1. 添加L2正则化:
   修改优化器的定义:
   ```python
   optimizer = optim.SGD(net.parameters(), lr=0.001, momentum=0.9, weight_decay=0.0001)
   ```

2. 添加Dropout:
   修改网络定义,在fc1和fc2之间添加Dropout:
   ```python
   class Net(nn.Module):
       def __init__(self):
           super(Net, self).__init__()
           self.conv1 = nn.Conv2d(3, 6, 5)
           self.conv2 = nn.Conv2d(6, 16, 5)
           self.fc1   = nn.Linear(16*5*5, 120)
           self.dropout = nn.Dropout(0.5)  # 添加这行
           self.fc2   = nn.Linear(120, 84)
           self.fc3   = nn.Linear(84, 10)

       def forward(self, x):
           x = F.max_pool2d(F.relu(self.conv1(x)), (2, 2))
           x = F.max_pool2d(F.relu(self.conv2(x)), 2)
           x = x.view(x.size()[0], -1)
           x = F.relu(self.fc1(x))
           x = self.dropout(x)  # 添加这行
           x = F.relu(self.fc2(x))
           x = self.fc3(x)
           return x
   ```

3. 重新训练模型并比较结果

步骤6: 完成Task 3 - 调整参数
1. 尝试不同的学习率,例如:
   ```python
   optimizer = optim.SGD(net.parameters(), lr=0.01, momentum=0.9)
   ```

2. 尝试不同的epoch数:
   ```python
   num_epochs = 10
   ```

3. 每次修改后重新训练并记录结果

步骤7: 完成Task 4 - 实现自己的网络
1. 实现一个简单的ResNet结构:
   ```python
   class ResidualBlock(nn.Module):
       def __init__(self, in_channels, out_channels, stride=1):
           super(ResidualBlock, self).__init__()
           self.conv1 = nn.Conv2d(in_channels, out_channels, kernel_size=3, stride=stride, padding=1)
           self.bn1 = nn.BatchNorm2d(out_channels)
           self.conv2 = nn.Conv2d(out_channels, out_channels, kernel_size=3, stride=1, padding=1)
           self.bn2 = nn.BatchNorm2d(out_channels)
           
           self.shortcut = nn.Sequential()
           if stride != 1 or in_channels != out_channels:
               self.shortcut = nn.Sequential(
                   nn.Conv2d(in_channels, out_channels, kernel_size=1, stride=stride),
                   nn.BatchNorm2d(out_channels)
               )

       def forward(self, x):
           out = F.relu(self.bn1(self.conv1(x)))
           out = self.bn2(self.conv2(out))
           out += self.shortcut(x)
           out = F.relu(out)
           return out

   class ResNet(nn.Module):
       def __init__(self, num_classes=10):
           super(ResNet, self).__init__()
           self.in_channels = 64
           self.conv1 = nn.Conv2d(3, 64, kernel_size=3, stride=1, padding=1)
           self.bn1 = nn.BatchNorm2d(64)
           self.layer1 = self.make_layer(ResidualBlock, 64, 2, stride=1)
           self.layer2 = self.make_layer(ResidualBlock, 128, 2, stride=2)
           self.layer3 = self.make_layer(ResidualBlock, 256, 2, stride=2)
           self.avg_pool = nn.AdaptiveAvgPool2d((1, 1))
           self.fc = nn.Linear(256, num_classes)

       def make_layer(self, block, out_channels, num_blocks, stride):
           strides = [stride] + [1] * (num_blocks - 1)
           layers = []
           for stride in strides:
               layers.append(block(self.in_channels, out_channels, stride))
               self.in_channels = out_channels
           return nn.Sequential(*layers)

       def forward(self, x):
           out = F.relu(self.bn1(self.conv1(x)))
           out = self.layer1(out)
           out = self.layer2(out)
           out = self.layer3(out)
           out = self.avg_pool(out)
           out = out.view(out.size(0), -1)
           out = self.fc(out)
           return out
   ```

2. 使用新网络训练并比较结果:
   ```python
   resnet = ResNet()
   train(trainloader, resnet, num_epochs, criterion, optimizer)
   predict(testloader, resnet)
   ```

步骤8: 编写实验报告
总结你的实验过程、结果和分析,包括:
- 损失函数曲线分析
- 正则化的效果
- 不同参数对模型性能的影响
- 新网络架构与LeNet的比较

 楼主| 何顺南 发表于 2024-10-13 18:35:22
cress2002 发表于 2024-10-13 17:08
如何完成CIFAR-10图像分类作业 - 完整步骤指南

步骤1: 环境设置

大佬nb,我现在就去试试
漂流瓶 发表于 2024-10-13 19:09:43
nb,我去玩一下,谢谢分享
 楼主| 何顺南 发表于 2024-10-14 19:01:47
cress2002 发表于 2024-10-13 17:08
如何完成CIFAR-10图像分类作业 - 完整步骤指南

步骤1: 环境设置

运行这个单元格等了好久都没有输出"Finished Training"

补充内容 (2024-10-14 19:22):
补充一个问题,怎么保存模型权重文件啊,谢谢
屏幕截图 2024-10-14 185553.png
cress2002 发表于 2024-10-15 09:27:28
问题
训练时间长: CIFAR-10数据集较大,训练需要较长时间
输出频率低: 当前代码每2000个batch才打印一次状态
可能的无限循环: 代码中可能存在bug导致训练无法正常结束

修改:

减少训练轮数(epochs)或使用较小的数据子集进行测试。
增加打印频率,比如每100个batch打印一次:

  1. if i % 100 == 99: # 每100个batch打印一次
  2. print('epoch %d: batch %5d loss: %.3f'
  3. % (epoch+1, i+1, running_loss / 100))
  4. running_loss = 0.0
复制代码


添加进度条,让您更清楚地看到训练进度:

  1. from tqdm import tqdm

  2. for epoch in range(num_epochs):
  3. pbar = tqdm(enumerate(trainloader), total=len(trainloader))
  4. for i, data in pbar:
  5. # ... 训练代码 ...
  6. pbar.set_description(f"Epoch {epoch+1}")
复制代码


关于保存模型权重文件

代码已经包含了保存逻辑:


  1. torch.save(net.state_dict(), f"{save_path}/epoch_{epoch + 1}_model.pth")
复制代码


这行代码在每个epoch结束后保存模型权重。确保您已经定义了save_path变量,指向一个有写入权限的目录。

如果想在训练结束后单独保存最终模型,可以在训练循环外添加:



  1. 训练结束后保存最终模型
  2. final_save_path = f"{save_path}/final_model.pth"
  3. torch.save(net.state_dict(), final_save_path)
  4. print(f"Final model saved to {final_save_path}")
复制代码


确保在运行代码前已创建save_path指定的目录。如果遇到权限问题,尝试使用绝对路径或更改为有写入权限的目录。
 楼主| 何顺南 发表于 2024-10-15 09:48:52
cress2002 发表于 2024-10-15 09:27
问题
训练时间长: CIFAR-10数据集较大,训练需要较长时间
输出频率低: 当前代码每2000个batch才打印一次状态 ...

感谢
您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

虫部落 陕ICP备14001577号-1川公网安备 51019002003015号联系我们FAQ关于虫部落免责声明虫部落生存法则社区广场RSS

Build with for "make search easier" Copyright © 2013-2025. Powered by Discuz! GMT+8, 2025-5-3 00:08

快速回复 返回顶部 返回列表