# **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次），因为此问题的数据集较为简单，训练速度很快。

---
