【第十四周】PyTorch深度学习实践1
目录
- 摘要
- Abstract
- 1.反向传播
- 2.线性回归
- 2.1.准备数据集
- 2.2.设计模型
- 2.3.定义损失函数和优化器
- 2.4.模型训练
- 3.逻辑回归
- 4.处理多维特征的输入
- 5.加载数据集
- 5.1.导入必要的库
- 5.2.准备数据集
- 5.3.定义模型
- 5.4.构建损失函数和优化器
- 5.5.训练模型
- 总结
摘要
本周主要通过B站刘二大人的视频对 PyTorch 进行实践学习,了解了PyTorch 中各个常用的类以及方法,学习了搭建神经网络的四个步骤,为后面手动复现模型提供了实践基础。其中,最重要的是学习到了看文档的能力,对于新学习到的类和方法能够通过查询文档去理解用法和作用。
Abstract
This week, I mainly focused on practical learning with PyTorch, understanding the various commonly used classes and methods within it. I learned the four steps to building a neural network, which provided a practical foundation for manually reproducing models in the future. Most importantly, I developed the ability to read documentation, allowing me to understand the usage and function of newly learned classes and methods by consulting the documentation.
本周学习内容来自于《PyTorch深度学习实践》完结合集 1~8课
参考资料来自于PyTorch 英文文档
1.反向传播
对于简单的线性模型可以通过解析式来求对参数的导数。
一个复杂的神经网络中含有成千上百个参数,分别对这些参数求解析是一个很繁重的任务,因此我们需要一个新的办法——反向传播算法。
线性函数叠加之后总是可以化简成
W
⋅
X
+
b
W·X+b
W⋅X+b的形式,这样子多层叠加就没有意义了,所以要在每一层的结果处加上一个非线性函数。
前馈过程求解中间值和局部导数,反向传播过程求解对各个参数的偏导。
PyTorch中 Tensor(张量)包含两个成员,一个是保存参数的 Data,一个是保存导数的 Grad。同样这两个成员也是 Tensor。
torch.Tensor 和 torch.tensor的区别
代码如下:
import torch
x_data = [1.0, 2.0, 3.0]
y_data = [2.0, 4.0, 6.0]
# 创建一个 Tensor 类型,方便求梯度
w = torch.tensor([1.0])
w.requires_grad = True
# 前向传播
def forward(x):
return x * w
# 计算损失,创建计算图
def loss(x, y):
y_pred = forward(x)
return (y_pred - y) ** 2
print("predict before training", 4, forward(4).item())
for epoch in range(100):
for x, y in zip(x_data, y_data): # 选择一个样本更新梯度值
l = loss(x, y)
l.backward()
print('\tgrad:', x, y, w.grad.item())
w.data = w.data - 0.01 * w.grad.data
# 每次更新完梯度要清除梯度
w.grad.data.zero_()
print("progress:", epoch, l.item())
print("predict after training", 4, forward(4).item())
结果如下:
2.线性回归
神经网络模型的搭建遵循上图的四个流程。
2.1.准备数据集
x_data = [1.0, 2.0, 3.0]
y_data = [2.0, 4.0, 6.0]
2.2.设计模型
定义 LinearModel 类
class LinearModel(torch.nn.Module):
这里定义了一个名为 LinearModel 的类,它继承自 torch.nn.Module。torch.nn.Module 是所有神经网络模块的基类,包含了一些有用的属性和方法,用于管理模型参数、前向传播等。
def __init__(self):
super(LinearModel, self).__init__()
self.linear = torch.nn.Linear(1, 1)
- _init_ 方法是 Python 中类的构造函数,当创建类的实例时会自动调用。
- super(LinearModel, self).init() 调用了父类 torch.nn.Module 的构造函数,这是必要的步骤,以确保父类能够正确地初始化。
- self.linear = torch.nn.Linear(1, 1) 创建了一个线性层(全连接层),输入维度为1,输出维度也为1。这意味着该模型将接受一个标量作为输入,并产生一个标量作为输出。
前向传播
def forward(self, x):
y_pred = self.linear(x)
return y_pred
- forward 方法定义了模型的前向传播逻辑,即如何从输入数据 x 计算出输出 y_pred。
- self.linear(x) 使用之前定义的线性层对输入 x 进行变换。
- return y_pred 返回前向传播的结果,即预测值 y_pred。
2.3.定义损失函数和优化器
定义损失和优化器
criterion = torch.nn.MSELoss(size_average=False)
optimizer = torch.optim.SGD(model.parameters(), lr=0.01)
model.parameters() 返回模型中所有需要优化的参数。
2.4.模型训练
for epoch in range(100):
y_pred = model(x_data)
loss = criterion(y_pred, y_data)
print(epoch, loss.item())
optimizer.zero_grad()
loss.backward()
optimizer.step()
print('w = ', model.linear.weight.item())
print('b = ', model.linear.bias.item())
x_test = torch.tensor([[4.0]])
y_test = model(x_test)
print('y_pred = ', y_test.data)
结果如下:
3.逻辑回归
为什么交叉熵可以衡量两个分布的差异呢?
交叉熵
H
(
P
,
Q
)
H(P,Q)
H(P,Q) 可以理解为使用概率分布
Q
Q
Q编码来自概率分布P的数据所需的平均比特数。具体来说:如果
P
(
x
)
P(x)
P(x) 和
Q
(
x
)
Q(x)
Q(x) 完全相同,即
P
(
x
)
=
Q
(
x
)
P(x)=Q(x)
P(x)=Q(x),那么
H
(
P
,
Q
)
H(P,Q)
H(P,Q) 等于
H
(
P
)
H(P)
H(P),也就是真实分布的熵。如果
P
(
x
)
P(x)
P(x) 和
Q
(
x
)
Q(x)
Q(x) 不同,那么
H
(
P
,
Q
)
H(P,Q)
H(P,Q) 会大于
H
(
P
)
H(P)
H(P),因为使用不准确的概率分布Q来编码数据会导致更多的冗余信息,从而需要更多的比特数。
代码如下:
import torch
import torch.nn.functional as F
# prepare dataset
x_data = torch.Tensor([[1.0], [2.0], [3.0]])
y_data = torch.Tensor([[0], [0], [1]])
# design model using class
class LogisticRegressionModel(torch.nn.Module):
def __init__(self):
super(LogisticRegressionModel, self).__init__()
self.linear = torch.nn.Linear(1, 1)
def forward(self, x):
y_pred = F.sigmoid(self.linear(x))
return y_pred
model = LogisticRegressionModel()
# construct loss and optimizer
# 默认情况下,loss会基于element平均,如果size_average=False的话,loss会被累加。
criterion = torch.nn.BCELoss(size_average=False)
optimizer = torch.optim.SGD(model.parameters(), lr=0.01)
# training cycle forward, backward, update
for epoch in range(1000):
y_pred = model(x_data)
loss = criterion(y_pred, y_data)
print(epoch, loss.item())
optimizer.zero_grad()
loss.backward()
optimizer.step()
print('w = ', model.linear.weight.item())
print('b = ', model.linear.bias.item())
x_test = torch.Tensor([[4.0]])
y_test = model(x_test)
print('y_pred = ', y_test.data)
结果如下:
4.处理多维特征的输入
上图为预测一个人在一年之后糖尿病病情加重概率的例子,这时每个样本会有八个不同的特征,通过八个不同的特征来预测是否病情会加重。
对于多维的特征输入我们需要把每一个特征x乘以相应的权重。在进行逻辑回归时,把每一个维度的x乘相应的权值的和加上一个偏置量。然后再把每个样本的式子写成矩阵乘法的形式。
import numpy as np
import torch
import matplotlib.pyplot as plt
# prepare dataset
xy = np.loadtxt('diabetes.csv', delimiter=',', dtype=np.float32)
x_data = torch.from_numpy(xy[:, :-1]) # 第一个‘:’是指读取所有行,第二个‘:’是指从第一列开始,最后一列不要
y_data = torch.from_numpy(xy[:, [-1]]) # [-1] 最后得到的是个矩阵
# design model using class
class Model(torch.nn.Module):
def __init__(self):
super(Model, self).__init__()
self.linear1 = torch.nn.Linear(8, 6) # 输入数据x的特征是8维,x有8个特征
self.linear2 = torch.nn.Linear(6, 4)
self.linear3 = torch.nn.Linear(4, 1)
self.sigmoid = torch.nn.Sigmoid() # 将其看作是网络的一层,而不是简单的函数使用
def forward(self, x):
x = self.sigmoid(self.linear1(x))
x = self.sigmoid(self.linear2(x))
x = self.sigmoid(self.linear3(x)) # y hat
return x
model = Model()
# construct loss and optimizer
# criterion = torch.nn.BCELoss(size_average = True)
criterion = torch.nn.BCELoss(reduction='mean')
optimizer = torch.optim.SGD(model.parameters(), lr=0.1)
epoch_list = []
loss_list = []
# training cycle forward, backward, update
for epoch in range(100):
y_pred = model(x_data)
loss = criterion(y_pred, y_data)
print(epoch, loss.item())
epoch_list.append(epoch)
loss_list.append(loss.item())
optimizer.zero_grad()
loss.backward()
optimizer.step()
plt.plot(epoch_list, loss_list)
plt.ylabel('loss')
plt.xlabel('epoch')
plt.show()
torch.sigmoid、torch.nn.Sigmoid和torch.nn.functional.sigmoid的区别
结果如下:
5.加载数据集
5.1.导入必要的库
import torch
import numpy as np
from torch.utils.data import Dataset
from torch.utils.data import DataLoader
5.2.准备数据集
class DiabetesDataset(Dataset):
def __init__(self, filepath):
xy = np.loadtxt(filepath, delimiter=',', dtype=np.float32)
self.len = xy.shape[0]
self.x_data = torch.from_numpy(xy[:, :-1])
self.y_data = torch.from_numpy(xy[:, [-1]])
def __getitem__(self, index):
return self.x_data[index], self.y_data[index]
def __len__(self):
return self.len
- DiabetesDataset 继承自 Dataset 类,其中 Dataset 是抽象类,不可被实例化。
- _init_ 方法:
读取CSV文件并将其转换为NumPy数组。
self.len 存储数据集的样本数量。
self.x_data存储特征数据,self.y_data 存储标签数据。- _getitem_ 方法:
返回指定索引处的特征和标签。- _len_ 方法:
返回数据集的长度(样本数量)。
创建数据加载器
dataset = DiabetesDataset('diabetes.csv')
train_loader = DataLoader(dataset=dataset, batch_size=32, shuffle=True, num_workers=0)
5.3.定义模型
class Model(torch.nn.Module):
def __init__(self):
super(Model, self).__init__()
self.linear1 = torch.nn.Linear(8, 6)
self.linear2 = torch.nn.Linear(6, 4)
self.linear3 = torch.nn.Linear(4, 1)
self.sigmoid = torch.nn.Sigmoid()
def forward(self, x):
x = self.sigmoid(self.linear1(x))
x = self.sigmoid(self.linear2(x))
x = self.sigmoid(self.linear3(x))
return x
Model 继承自 torch.nn.Module 类。
_init_ 方法:
定义了三个全连接层(Linear)和一个Sigmoid激活函数。forward 方法:
定义了前向传播过程,输入数据依次通过三个全连接层和Sigmoid激活函数。
5.4.构建损失函数和优化器
criterion = torch.nn.BCELoss(reduction='mean')
optimizer = torch.optim.SGD(model.parameters(), lr=0.01)
5.5.训练模型
if __name__ == '__main__':
for epoch in range(100):
for i, data in enumerate(train_loader, 0):
inputs, labels = data
y_pred = model(inputs)
loss = criterion(y_pred, labels)
print(epoch, i, loss.item())
optimizer.zero_grad()
loss.backward()
optimizer.step()
运行结果和上一小节类似,这一小节主要学习的是 dataloader 的具体作用和理解 epoch、batch 和 iteration 之间的关系。
总结
构建一个通用的神经网络模型是一个系统而精细的过程,大致可以分为四个关键步骤。首先,准备数据集是整个流程的基石,这一步骤不仅包括收集所需的数据,还需要对数据进行清洗、预处理以及划分训练集与测试集,确保数据的质量和适用性。接着,模型搭建阶段需要根据任务需求选择合适的网络架构,如卷积神经网络(CNN)用于图像识别,循环神经网络(RNN)适合序列数据处理等,并确定每一层的具体参数。第三步,定义损失函数和选择优化器是调整模型以达到最佳性能的关键,损失函数衡量预测值与真实值之间的差异,而优化器则通过反向传播算法最小化这种差异。最后,训练模型涉及迭代地将数据输入网络中,不断调整权重直至模型在训练集上表现良好,并通过验证集或测试集评估其泛化能力。掌握这一系列流程,就可以自己动手搭建绝大多数神经网络。