proj5 finished

This commit is contained in:
2025-11-14 10:46:33 +08:00
parent e71ae34bd1
commit 4da40089f5
4 changed files with 226 additions and 10 deletions

View File

@ -37,7 +37,7 @@ class PerceptronModel(Module):
"""
super(PerceptronModel, self).__init__()
"*** YOUR CODE HERE ***"
self.w = Parameter(ones(1, dimensions))
def get_weights(self):
@ -56,7 +56,7 @@ class PerceptronModel(Module):
The pytorch function `tensordot` may be helpful here.
"""
"*** YOUR CODE HERE ***"
return tensordot(self.w, x, dims=2)
def get_prediction(self, x):
@ -65,7 +65,8 @@ class PerceptronModel(Module):
Returns: 1 or -1
"""
"*** YOUR CODE HERE ***"
score = self.run(x)
return 1 if score.item() >= 0 else -1
@ -80,7 +81,17 @@ class PerceptronModel(Module):
"""
with no_grad():
dataloader = DataLoader(dataset, batch_size=1, shuffle=True)
"*** YOUR CODE HERE ***"
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
@ -94,6 +105,9 @@ class RegressionModel(Module):
# Initialize your model parameters here
"*** YOUR CODE HERE ***"
super().__init__()
self.layer1 = Linear(1, 256)
self.layer2 = Linear(256, 256)
self.layer3 = Linear(256, 1)
@ -107,6 +121,9 @@ class RegressionModel(Module):
A node with shape (batch_size x 1) containing predicted y-values
"""
"*** YOUR CODE HERE ***"
x = relu(self.layer1(x))
x = relu(self.layer2(x))
return self.layer3(x)
def get_loss(self, x, y):
@ -120,6 +137,7 @@ class RegressionModel(Module):
Returns: a tensor of size 1 containing the loss
"""
"*** YOUR CODE HERE ***"
return mse_loss(self.forward(x), y)
@ -138,6 +156,27 @@ class RegressionModel(Module):
"""
"*** YOUR CODE HERE ***"
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): # Train for 1000 epochs, should be enough
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()
avg_loss = total_loss / num_samples
if avg_loss < 0.02:
break
@ -167,6 +206,9 @@ class DigitClassificationModel(Module):
input_size = 28 * 28
output_size = 10
"*** YOUR CODE HERE ***"
self.layer1 = Linear(input_size, 256)
self.layer2 = Linear(256, 128)
self.layer3 = Linear(128, output_size)
@ -186,6 +228,9 @@ class DigitClassificationModel(Module):
(also called logits)
"""
""" YOUR CODE HERE """
x = relu(self.layer1(x))
x = relu(self.layer2(x))
return self.layer3(x)
@ -203,6 +248,7 @@ class DigitClassificationModel(Module):
Returns: a loss tensor
"""
""" YOUR CODE HERE """
return cross_entropy(self.run(x), y)
@ -212,6 +258,16 @@ class DigitClassificationModel(Module):
Trains the model.
"""
""" YOUR CODE HERE """
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): # Train for 5 epochs
for batch in dataloader:
optimizer.zero_grad()
loss = self.get_loss(batch['x'], batch['label'])
loss.backward()
optimizer.step()
@ -314,13 +370,18 @@ def Convolve(input: tensor, weight: tensor):
This returns a subtensor who's first element is tensor[y,x] and has height 'height, and width 'width'
"""
input_tensor_dimensions = input.shape
weight_dimensions = weight.shape
Output_Tensor = tensor(())
"*** YOUR CODE HERE ***"
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
"*** End Code ***"
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
@ -345,6 +406,10 @@ class DigitConvolutionalModel(Module):
self.convolution_weights = Parameter(ones((3, 3)))
""" YOUR CODE HERE """
conv_output_size = 26 * 26
hidden_size = 100
self.layer1 = Linear(conv_output_size, hidden_size)
self.layer2 = Linear(hidden_size, output_size)
@ -361,6 +426,8 @@ class DigitConvolutionalModel(Module):
x = stack(list(map(lambda sample: Convolve(sample, self.convolution_weights), x)))
x = x.flatten(start_dim=1)
""" YOUR CODE HERE """
x = relu(self.layer1(x))
return self.layer2(x)
def get_loss(self, x, y):
@ -377,6 +444,7 @@ class DigitConvolutionalModel(Module):
Returns: a loss tensor
"""
""" YOUR CODE HERE """
return cross_entropy(self.forward(x), y)
@ -386,6 +454,16 @@ class DigitConvolutionalModel(Module):
Trains the model.
"""
""" YOUR CODE HERE """
batch_size = 64
dataloader = DataLoader(dataset, batch_size=batch_size, shuffle=True)
optimizer = optim.Adam(self.parameters(), lr=0.001)
for epoch in range(3):
for batch in dataloader:
optimizer.zero_grad()
loss = self.get_loss(batch['x'], batch['label'])
loss.backward()
optimizer.step()

View File

@ -0,0 +1,138 @@
# **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次因为此问题的数据集较为简单训练速度很快。
---