#import "labtemplate.typ": * #show: nudtlabpaper.with( author: "程景愉", id: "202302723005", title: "机器学习", training_type: "普通本科生", grade: "2023级", major: "网络工程", department: "计算机学院", advisor: "胡罡", jobtitle: "教授", lab: "306-707", date: "2025.12.10", header_str: "机器学习实验报告", simple_cover: true, ) #set page(header: [ #set par(spacing: 6pt) #align(center)[#text(size: 11pt)[人工智能实验报告]] #v(-0.3em) #line(length: 100%, stroke: (thickness: 1pt)) ],) #show heading: it => if it.level == 1 [ #v(1em) #set text(font: hei, size: 16pt) #counter(heading).display() #h(0.5em) #it.body #v(0.5em) ] else [ #v(0.8em) #set text(font: "New Computer Modern", weight: "bold", style: "italic") #counter(heading).display(it.numbering) #h(0.5em) #it.body #v(0.5em) ] #outline(title: "目录",depth: 2, indent: 1em) #set enum(indent: 0.5em,body-indent: 0.5em,) #pagebreak() = 实验介绍 #para[ 本项目是 CS188 课程的第五个项目,主题为机器学习(Machine Learning)。实验的主要目的是通过实践操作,熟悉机器学习的基本概念、神经网络的构建过程以及 PyTorch 深度学习框架的使用。在本项目中,我们将从最基础的感知机开始,逐步构建非线性回归模型、用于手写数字识别的多层感知机(MLP),以及卷积神经网络(CNN)。通过这些任务,我们将深入理解神经网络的初始化、前向传播、损失计算、反向传播以及参数更新等核心机制。 ] = 实验内容 #para[ 本次实验主要包含以下四个核心编程任务,所有代码实现均在 `models.py` 文件中进行: ] + *Q1: Perceptron (感知机)*:实现一个简单的二元分类感知机,理解基本的线性分类器更新规则。 + *Q2: Non-linear Regression (非线性回归)*:构建一个两层的神经网络,用于拟合正弦函数 $sin(x)$,理解如何利用神经网络的非线性能力进行函数逼近。 + *Q3: Digit Classification (数字分类)*:设计并训练一个多层全连接神经网络,对 MNIST 手写数字数据集进行分类,掌握分类任务的损失函数和模型评估方法。 + *Q5: Convolutional Neural Networks (卷积神经网络)*:手动实现二维卷积操作,并基于此构建卷积神经网络,理解 CNN 在处理图像数据时的优势。 = 实验要求 #para[ 根据项目指导文档,实验的具体要求如下: ] + *文件修改*:仅允许修改 `models.py` 文件,其余文件(如 `autograder.py` 等)需保持原样,以确保自动评分器正常运行。 + *框架使用*:需熟练使用 PyTorch 提供的 `Tensor` 操作、`nn.Parameter`、`optim` 优化器等工具。 + *性能达标*: - Q1:感知机需在训练集上收敛(准确率 100%)。 - Q2:回归模型的平均训练损失(MSE)需低于 0.02。 - Q3:数字分类模型在测试集上的准确率需达到 97% 以上。 - Q5:CNN 模型在简化版数据集上的准确率需达到 80% 以上。 + *禁止事项*:Q3 的输出层不得使用 ReLU 激活函数;代码需通过自动评分器的技术正确性检查。 = 实验步骤与实现 == Q1: Perceptron (感知机) *实现思路*: 感知机是一个最简单的线性二分类模型。我们的目标是找到一个权重向量 $w$,使得对于输入 $x$,如果 $w dot x >= 0$ 则预测为类别 1,否则为 -1。 1. *初始化*:使用 `torch.nn.Parameter` 初始化权重 `self.w`,维度为 `(1, dimensions)`,初始化为全 1 向量。 2. *计算得分*:计算输入 $x$ 与权重 $w$ 的点积。使用 `tensordot` 计算。 3. *预测与训练*:`get_prediction` 根据得分符号返回 1 或 -1。`train` 方法中,我们遍历数据集。如果预测错误,则根据感知机规则更新权重:$w <- w + y dot x$。 *核心代码* (`models.py`): ```python class PerceptronModel(Module): def __init__(self, dimensions): super(PerceptronModel, self).__init__() self.w = Parameter(ones(1, dimensions)) def run(self, x): return tensordot(self.w, x, dims=2) def train(self, dataset): with no_grad(): dataloader = DataLoader(dataset, batch_size=1, shuffle=True) while True: converged = True for batch in dataloader: x = batch['x'] y = batch['label'] prediction = self.get_prediction(x) if prediction != y.item(): self.w.data += y.item() * x converged = False if converged: break ``` *测试指令*: ```fish python autograder.py -q q1 ``` == Q2: Non-linear Regression (非线性回归) *实现思路*: 为了拟合非线性的 $sin(x)$ 函数,我们需要引入非线性激活函数。我们构建了一个三层的神经网络: - 第一层:线性层 `Linear(1, 256)` + ReLU。 - 第二层:线性层 `Linear(256, 256)` + ReLU。 - 第三层:线性层 `Linear(256, 1)`,输出预测值。 训练时使用 Adam 优化器和 MSE 损失函数。为了加速收敛,我们还引入了学习率调度器 (`StepLR`),每 250 个 step 将学习率衰减为原来的 0.1。 *核心代码* (`models.py`): ```python class RegressionModel(Module): def __init__(self): super().__init__() self.layer1 = Linear(1, 256) self.layer2 = Linear(256, 256) self.layer3 = Linear(256, 1) def forward(self, x): x = relu(self.layer1(x)) x = relu(self.layer2(x)) return self.layer3(x) def train(self, dataset): batch_size = 40 dataloader = DataLoader(dataset, batch_size=batch_size, shuffle=True) optimizer = optim.Adam(self.parameters(), lr=0.001) scheduler = optim.lr_scheduler.StepLR(optimizer, step_size=250, gamma=0.1) for epoch in range(1000): total_loss = 0 num_samples = 0 for batch in dataloader: optimizer.zero_grad() loss = self.get_loss(batch['x'], batch['label']) loss.backward() optimizer.step() total_loss += loss.item() * len(batch['x']) num_samples += len(batch['x']) scheduler.step() if total_loss / num_samples < 0.02: break ``` *测试指令*: ```fish python autograder.py -q q2 ``` == Q3: Digit Classification (数字分类) *实现思路*: 针对 MNIST 28x28 的图像分类任务,我们构建了一个三层全连接网络: - 输入层:784 维(28*28 展平)。 - 隐藏层 1:`Linear(784, 256)` + ReLU。 - 隐藏层 2:`Linear(256, 128)` + ReLU。 - 输出层:`Linear(128, 10)`,直接输出 Logits。 使用 `cross_entropy` 损失函数和 Adam 优化器,批大小为 100,训练 5 个 Epoch 即可达到目标准确率。 *核心代码* (`models.py`): ```python class DigitClassificationModel(Module): def __init__(self): super().__init__() input_size = 28 * 28 output_size = 10 self.layer1 = Linear(input_size, 256) self.layer2 = Linear(256, 128) self.layer3 = Linear(128, output_size) def run(self, x): x = relu(self.layer1(x)) x = relu(self.layer2(x)) return self.layer3(x) def train(self, dataset): batch_size = 100 dataloader = DataLoader(dataset, batch_size=batch_size, shuffle=True) optimizer = optim.Adam(self.parameters(), lr=0.001) for epoch in range(5): for batch in dataloader: optimizer.zero_grad() loss = self.get_loss(batch['x'], batch['label']) loss.backward() optimizer.step() ``` *测试指令*: ```fish python autograder.py -q q3 ``` == Q5: Convolutional Neural Networks (卷积神经网络) *实现思路*: 本任务的核心是手动实现二维卷积操作。`Convolve` 函数通过双重循环遍历输出矩阵的每个位置,计算输入子矩阵与卷积核的点积(使用 `tensordot`)。 基于此,我们构建了一个简单的 CNN: 1. 卷积层:使用 `Convolve` 处理输入,卷积核大小 3x3,输出特征图大小为 26x26。 2. 展平:将特征图展平为 676 维向量。 3. 全连接层 1:`Linear(676, 100)` + ReLU。 4. 全连接层 2:`Linear(100, 10)`。 *核心代码* (`models.py`): ```python def Convolve(input: tensor, weight: tensor): input_h, input_w = input.shape weight_h, weight_w = weight.shape output_h = input_h - weight_h + 1 output_w = input_w - weight_w + 1 Output_Tensor = torch.zeros((output_h, output_w)) for y in range(output_h): for x in range(output_w): sub_tensor = input[y:y+weight_h, x:x+weight_w] Output_Tensor[y, x] = tensordot(sub_tensor, weight, dims=2) return Output_Tensor class DigitConvolutionalModel(Module): def __init__(self): super().__init__() self.convolution_weights = Parameter(ones((3, 3))) self.layer1 = Linear(26 * 26, 100) self.layer2 = Linear(100, 10) def forward(self, x): x = x.reshape(len(x), 28, 28) x = stack(list(map(lambda sample: Convolve(sample, self.convolution_weights), x))) x = x.flatten(start_dim=1) x = relu(self.layer1(x)) return self.layer2(x) ``` *测试指令*: ```fish python autograder.py -q q5 ``` = 实验结果 #para[ 本实验所有代码均通过 `autograder.py` 的测试,各项指标均达到或超过实验要求。 ] + *Q1 感知机*:模型能够迅速收敛,在训练数据上实现了 100% 的分类准确率。 + *Q2 非线性回归*:训练后的模型 Loss 稳定在 0.02 以下,能够很好地拟合 $sin(x)$ 曲线。 + *Q3 数字分类*:构建的 MLP 模型在 MNIST 测试集上的准确率超过 97%(Staff 参考值为 98%),训练过程高效稳定。 + *Q5 CNN*:手动实现的卷积层逻辑正确,通过了梯度检查。构建的 CNN 模型在测试数据集上准确率超过 80%,验证了卷积特征提取的有效性。 #figure(image("screenshot.png")) #pagebreak() = 实验总结 #para[ 通过本次 Project 5 的实验,我收获颇丰: ] #para[ 首先,我熟练掌握了 *PyTorch* 框架的基础操作。从定义 `nn.Parameter` 到构建 `nn.Linear` 层,再到使用 `optim.Adam` 和各类 Loss 函数,我对深度学习模型的代码实现流程有了清晰的认识。 ] #para[ 其次,我深入理解了*神经网络的内部机制*。通过 Q1,我复习了线性分类器的原理;通过 Q2,我直观体会到了激活函数(ReLU)引入非线性的重要性——没有它,多层网络也仅仅是线性变换的叠加;通过 Q3,我掌握了处理多分类问题的标准范式(CrossEntropy + Softmax Logits)。 ] #para[ 最后,Q5 的手动实现卷积让我对 CNN 有了更底层的理解。以前只知道调用 API,现在通过自己编写滑动窗口和点积运算,深刻理解了权重共享和局部感知野的概念。这次实验不仅锻炼了编程能力,也为后续深入学习深度学习打下了坚实基础。 ]