【第十四周】PyTorch深度学习实践1

时间:2024-09-29 12:44:19

目录

  • 摘要
  • 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 WX+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)适合序列数据处理等,并确定每一层的具体参数。第三步,定义损失函数和选择优化器是调整模型以达到最佳性能的关键,损失函数衡量预测值与真实值之间的差异,而优化器则通过反向传播算法最小化这种差异。最后,训练模型涉及迭代地将数据输入网络中,不断调整权重直至模型在训练集上表现良好,并通过验证集或测试集评估其泛化能力。掌握这一系列流程,就可以自己动手搭建绝大多数神经网络。