proj5 finished
This commit is contained in:
BIN
proj5/machinelearning/__pycache__/backend.cpython-313.pyc
Normal file
BIN
proj5/machinelearning/__pycache__/backend.cpython-313.pyc
Normal file
Binary file not shown.
BIN
proj5/machinelearning/__pycache__/models.cpython-313.pyc
Normal file
BIN
proj5/machinelearning/__pycache__/models.cpython-313.pyc
Normal file
Binary file not shown.
@ -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()
|
||||
|
||||
|
||||
|
||||
|
||||
138
proj5/machinelearning/solution_steps.md
Normal file
138
proj5/machinelearning/solution_steps.md
Normal 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次),因为此问题的数据集较为简单,训练速度很快。
|
||||
|
||||
---
|
||||
Reference in New Issue
Block a user