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