# **CS188 Proj5 机器学习实验完成步骤详解** 本文档详细记录了在CS188项目5(机器学习)中,完成Q1、Q2、Q3和Q5四个部分的核心代码实现步骤。所有代码修改均在 `models.py` 文件中进行。 ## **问题一 (Q1): 感知机 (Perceptron)** 此部分的目标是实现一个二元分类感知机模型。 1. **`__init__(self, dimensions)` - 初始化** * 初始化模型的权重 `self.w`。根据要求,权重需要是一个PyTorch的 `Parameter` 对象,以便自动评分系统能够识别。 * 权重向量的维度被设置为 `(1, dimensions)`,并使用 `ones` 进行初始化。 ```python from torch.nn import Parameter from torch import ones self.w = Parameter(ones(1, dimensions)) ``` 2. **`run(self, x)` - 计算得分** * 此函数计算感知机对输入数据 `x` 的得分。 * 通过计算权重 `self.w` 和输入 `x` 的点积来实现。这里使用了 `tensordot` 函数。 ```python from torch import tensordot return tensordot(self.w, x, dims=2) ``` 3. **`get_prediction(self, x)` - 获取预测** * 根据 `run` 方法计算出的得分,判断数据点的类别。 * 如果得分大于或等于0,则预测为类别 `1`;否则预测为 `-1`。 ```python score = self.run(x) return 1 if score.item() >= 0 else -1 ``` 4. **`train(self, dataset)` - 训练模型** * 此函数的目标是训练感知机直到收敛(即在整个数据集上不再有分类错误)。 * 使用一个 `while True` 循环,在每一轮迭代中遍历整个数据集。 * 对于每一个数据点,获取模型的预测。如果预测错误,则根据感知机学习规则更新权重:`self.w.data += y.item() * x`,其中 `y` 是真实标签,`x` 是输入特征。 * 设置一个标志位 `converged`。如果在一整轮的迭代中没有任何错误发生,则 `converged` 保持为 `True`,训练循环结束。 ## **问题二 (Q2): 非线性回归 (Non-linear Regression)** 此部分要求构建一个神经网络来近似 `sin(x)` 函数。 1. **`__init__(self)` - 初始化** * 构建一个简单的两层全连接神经网络。 * 第一层 (`self.layer1`) 是一个 `Linear` 层,将1维输入映射到100维的隐藏层。 * 第二层 (`self.layer2`) 也是一个 `Linear` 层,将100维的隐藏层映射到1维的输出。 ```python from torch.nn import Linear self.layer1 = Linear(1, 100) self.layer2 = Linear(100, 1) ``` 2. **`forward(self, x)` - 前向传播** * 定义数据在网络中的流动方式。 * 输入 `x` 首先通过第一层,然后应用 `relu` 激活函数,最后通过第二层得到最终输出。 ```python from torch.nn.functional import relu x = relu(self.layer1(x)) return self.layer2(x) ``` 3. **`get_loss(self, x, y)` - 计算损失** * 使用均方误差(Mean Squared Error)作为损失函数。 * 调用 `forward(x)` 得到预测值,然后使用 `mse_loss` 计算与真实值 `y` 之间的损失。 ```python from torch.nn.functional import mse_loss return mse_loss(self.forward(x), y) ``` 4. **`train(self, dataset)` - 训练模型** * 使用 `Adam` 优化器进行梯度下降。 * 设置合适的批处理大小(`batch_size`),例如50(因为Q2数据集大小为200,可以被50整除)。 * 训练循环进行固定次数的迭代(Epoch),例如500次。在每个Epoch中,遍历数据加载器(`DataLoader`)中的所有批次,执行标准的训练步骤:梯度清零、计算损失、反向传播、更新权重。 * 在每个Epoch结束后,计算整个数据集的平均损失。如果平均损失小于0.02,则提前终止训练。 ## **问题三 (Q3): 数字分类 (Digit Classification)** 此部分要求构建一个模型来对MNIST手写数字数据集进行分类。 1. **`__init__(self)` - 初始化** * 构建一个具有两个隐藏层的多层感知机(MLP)。 * 输入层大小为 `784` (28x28像素),输出层大小为 `10` (0-9共10个类别)。 * 网络结构:`Linear(784, 256)` -> `Linear(256, 128)` -> `Linear(128, 10)`。 2. **`run(self, x)` - 前向传播** * 数据流经两个隐藏层,每个隐藏层后都应用 `relu` 激活函数。 * 输出层不使用激活函数,直接返回 logits 分数。 3. **`get_loss(self, x, y)` - 计算损失** * 使用交叉熵损失函数 `cross_entropy`。 * 一个关键点是,PyTorch的 `cross_entropy` 函数可以直接接受 one-hot 编码的浮点型标签 `y`。因此,直接将模型的输出 `self.run(x)` 和标签 `y` 传入即可。 * (注:最初尝试使用 `y.argmax(dim=1)` 将 one-hot 标签转换为索引,但这会导致自动评分器中的梯度检查失败。直接传递 one-hot 标签解决了这个问题。) 4. **`train(self, dataset)` - 训练模型** * 同样使用 `Adam` 优化器和 `DataLoader`。批处理大小设置为100(可被训练集大小60000整除)。 * 根据实验指导,训练5个Epoch就足以达到要求的准确率。 * 因此,实现一个简单的循环,迭代5次,在其中执行标准的模型训练流程。 * (注:最初尝试使用 `dataset.get_validation_accuracy(self)` 进行早停,但遇到了 `TypeError`。简化为固定Epoch次数的训练后,成功通过了测试。) ## **问题五 (Q5): 卷积神经网络 (Convolutional Neural Networks)** 此部分要求从零开始实现一个卷积函数,并构建一个简单的卷积神经网络。 1. **`Convolve(input: tensor, weight: tensor)` - 实现卷积函数** * 此函数手动实现2D卷积操作,不使用PyTorch内置的卷积层。 * 首先,根据输入张量和权重张量的尺寸,计算输出张量的尺寸(无填充,步长为1)。 * 创建一个全为零的输出张量 `Output_Tensor`。 * 使用嵌套循环遍历输出张量的每一个位置 `(y, x)`。 * 在每个位置,从输入张量中提取一个与权重张量大小相同的子张量 `sub_tensor`。 * 使用 `tensordot` 计算 `sub_tensor` 和 `weight` 的点积(即元素乘积之和),并将结果存入 `Output_Tensor[y, x]`。 * 返回计算完成的 `Output_Tensor`。 2. **`DigitConvolutionalModel` - 构建CNN模型** * **`__init__(self)`**: * 模型包含一个 `3x3` 的卷积核 `self.convolution_weights`。 * 卷积操作的输出是一个 `26x26` 的特征图,展平后大小为 `676`。 * 在卷积层之后,添加一个简单的全连接网络,例如 `Linear(676, 100)` -> `ReLU` -> `Linear(100, 10)`。 * **`forward(self, x)`**: * 函数的前半部分已提供,它会将输入的 `(batch_size, 784)` 数据重塑为 `(batch_size, 28, 28)` 的图像,然后使用我们实现的 `Convolve` 函数对批次中的每个样本进行卷积,最后将结果展平。 * 我们需要做的,是将这个展平后的一维向量传入我们定义的两层全连接网络中,并返回最终的分类分数。 * **`get_loss(self, x, y)` 和 `train(self, dataset)`**: * 这两个函数的实现与Q3中的 `DigitClassificationModel` 非常相似。 * `get_loss` 使用 `cross_entropy`。 * `train` 使用 `Adam` 优化器,训练一个较少的Epoch次数(例如3次),因为此问题的数据集较为简单,训练速度很快。 ---