【PyTorch】刘二大人的Pytorch深度学习实践学习笔记
写在前面
博主在去年暑假已经看过一次刘二大人的《Pytorch深度学习实践》,但是没有做任何笔记。现在突发奇想打算再过一次视频来夯实一下代码基础,顺便做下笔记来供自己以后复习使用。
01. Overview
之前基于rule的系统是通过人工设定规则实现,到了传统的机器学习方法则是通过手动设计特征(如把一段语音变成向量),最后通过一个mapping函数对应到输出。
表示学习则希望feature的提取也是通过学习的方法来得到(而不是手工获取),但是其中feature的提取和mapping映射是分开完成的。
深度学习是一种端到端的(end2end)的算法,之前的表示学习可能需要一个设计特征提取器来获取特征,而深度学习直接设计一个很大的模型完成数据从输入到输出的整体过程。
SVM的劣势。
02. 线性模型
四步:数据集,设计模型,训练模型,推理(预测)

训练集train set:训练模型的数据集;
测试集test set:预测步骤,测试模型的准确性。
训练集可能会存在过拟合问题,比如学习到图形的噪声,我们要求模型有好的泛化能力。比如训练集的猫狗图像可能是艺术照片(美颜加猫狗在中间),而实际上可能用户上传后照片是随手一拍。由此我们将训练集分为两块,将另外一块用于评估,称之为验证集valid set。
评估模型,称之为损失(loss)
loss function:对于一个样本的损失
cost function:对于所有样本(整个训练集)的平均损失。如图所示是MSE损失。
03. 梯度下降算法
观察法:维度过高时候过于困难,程序复杂度和数据量过高;
分治法:不是凸函数的情况下容易不容易得到最优值。而且维度过高也难以划分。
由此提出梯度下降算法。贪心算法,容易陷入局部最优点。但是在深度网络里面,人们发现局部最优点比较少,所以可以使用。
但是深度学习里面鞍点比较多,(比如马鞍面),也是需要解决的最大的问题。
随机梯度下降:用一个数据(或者单个样本)代替原来所有的数据计算损失。这样有助于跳过鞍点。
但是随机梯度下降无法实现并行,因为权值w有依赖关系。
在深度学习中选择折中,因为梯度下降效率高但性能差,随机梯度下降效率低但性能好。所以采用批次的随机梯度下降法。
04. 反向传播
理论
在之前讲解过程中我们很容易求得梯度的解析式,如下所示。
但是在深度学习中再求取解析式的话,过于复杂,而且嵌套了很多复合函数,例子如下。由此设计了反向传播算法,利用计算图通过链式法则来得到梯度。
关于激活函数的作用,主要是得到非线性的变化。
链式法则的步骤:
- 前馈获取loss。
- 局部梯度。这儿的f是输入x和权重w的函数。
- 获取loss对于z的偏导。 (从前面的传播过程获得)
- 反向传播,从而获得L对x、w的导数,其中z对x、对w的导数,是在计算f的函数的时候算出的。
举例说明:
正向得到loss为1后,通过正向计算过程中算得的梯度,反向传播获取最终的偏导。
这儿的loss一般会保存,用来评估模型。
代码
- forward函数里面的乘法被重载了。
- w需要计算梯度,后面的也需要计算梯度(图中蓝色方框)。
- 调用一次loss,动态产生计算图。
#训练代码
for epoch in range(100):
for x,y in zip(x_data,y_data):
l = loss(x,y) #前向传播,得到loss,构造计算图。同时将梯度存到w中
l.backward()#反向传播,释放计算图(每次反向传播释放计算图可以方便drop out)
print("grad:",x,y,w.grad.item())#w.grad.item()原理同w.grad.data
w.data = w.data - 0.01 * w.grad.data#w.grad是一个tensor,如果直接用它是在构造计算图,因此需要用.data,这样可以不需要构造计算图
w.grad.data.zero_()#梯度清零,因为梯度在反向传播中是累加的,需要手动清零
05. pytorch实现线性回归
深度学习四步。
广播机制,如下所示,这里的乘是矩阵元素对应位置相乘,不是矩阵乘。
在nn.Linear中,包括了权重w和偏置b。它也是继承自Module,所以可以自动反向传播求导。
对应完整代码如下所示:
import numpy as np
import torch
x_data = torch.Tensor([[1.0],[2.0],[3.0]])
y_data = torch.Tensor([[2.0],[4.0],[6.0]])
#nn.Module是所有神经网络的父类
class LinearModel(torch.nn.Module):
#至少需要两个函数:构造函数和forward函数。前者用来初始化,后者用来计算前缀forward过程
#如果觉得pytorch中封装的反向传播算法效率不高,可以自己写function类
def __init__(self):
super(LinearModel,self).__init__()#调用父类的构造函数
self.linear = torch.nn.Linear(1,1)#linear是一个nn.Linear的对象
def forward(self,x):
y_pred = self.linear(x)#可调用的对象(__call__)
return y_pred
def train(model):
criterion = torch.nn.MSELoss(size_average=False) # MSEloss继承自nn.Module
optimizer = torch.optim.SGD(model.parameters(), lr=0.05) # 不会构建计算图,model.parameters()记录了需要更新的参数
for epoch in range(100):
#step 1:得到y_hat
y_pred = model(x_data)
#step 2: 计算loss
loss = criterion(y_pred, y_data)
print(epoch, loss.item())
#step 3:求取梯度
optimizer.zero_grad()#梯度清零
loss.backward()#反向传播
#step 4: 权重更新
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)
if __name__ == '__main__':
model = LinearModel()
train(model)
06-08. Logistic Regression
sigmoid函数
下面都是sigmoid函数,但是由于logistic function: 1 1 + e − x \frac{1}{1+e^{-x}} 1+e−x1比较出名,因此后面sigmoid函数便就是logistic function 1 1 + e − x \frac{1}{1+e^{-x}} 1+e−x1。
交叉熵损失:输出的是分布,而不是实数距离。
8个feature为例,如下。
mini-batch实例
epoch:所有样本经过一次前馈和反向传播,便为一个epoch。
batch_size:进行一次前馈和反向传播的样本数量。
iteration:每一次epoch执行了多少个batch_size。
Dataset需要满足:1.下标访问; 2.数据集长度。
由此dataloader可以对dataset进行shuffle,然后批次输入并可以利用for循环。
详细代码如下。
import numpy as np
import torch
import torch.nn as nn
import torch.nn.functional as F
from torch.utils.data import Dataset#Dataset是一个抽象类
from torch.utils.data import DataLoader#帮助进行shuffle等操作
class MyDataset(Dataset):
def __init__(self,file_path):
xy = np.loadtxt(file_path,delimiter=',',dtype=np.float32)
self.len = xy.shape[0]#(N,9)
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):#len()
return self.len
class LogisticRegressionModel(torch.nn.Module):
def __init__(self):
super(LogisticRegressionModel, self).__init__()
self.FFN = nn.Sequential(
nn.Linear(8,4),
nn.Sigmoid(),
nn.Linear(4,2),
nn.Sigmoid(),
nn.Linear(2,1),
nn.Sigmoid()
)
def forward(self, x):
return self.FFN(x)
def train(model):
criterion = torch.nn.BCELoss(reduction='mean')
optimizer = torch.optim.SGD(model.parameters(), lr=0.05)
for epoch in range(100):
for i,data in enumerate(train_loader,0):
#prepare data
inputs,labels = data
#forward
y_pred=model(inputs)
loss = criterion(y_pred,labels)
print(epoch,i,loss.item())
#backward
optimizer.zero_grad()
loss.backward()
#update
optimizer.step()
if __name__ == '__main__':
#load data
dataset = MyDataset('data/diabetes/diabetes.csv.gz')
train_loader = DataLoader(dataset=dataset,batch_size=32,shuffle=True,num_workers=2)
#model
model = LogisticRegressionModel()
train(model)
作业
后续再补。。
09. 多分类问题
softmax函数
softmax实现多分类:保证概率和为1,每类的概率大于等于0。
举例如下。
交叉熵损失函数,如下所示。NLLLoss需要要求先对Y_hat求对数。
为方便,利用CrossEntrpyLoss,将Softmax、求对数过程、NLLLoss全部封装在其中。
这时候要求y(label)是LongTensor类型。
代码对应举例如下所示:
MNIST数据集
详细代码如下。
import torch
import torch.nn as nn
from torchvision import datasets, transforms
from torch.utils.data import DataLoader
def load_data():
transform = transforms.Compose([
transforms.ToTensor(), # 把0-255转为0-1的张量,(28,28)-》(1,28,28),即 channel * w * h
transforms.Normalize((0.1307,), (0.3081,)) # 均值 mean 和标准差 std
])
train_set = datasets.MNIST(root='./data/mnist', train=True, download=True, transform=transform)
test_set = datasets.MNIST(root='./data/mnist', train=False, download=True, transform=transform)
return train_set, test_set
class Mymodel(nn.Module):
def __init__(self):
super(Mymodel, self).__init__()
self.FFN = nn.Sequential(
nn.Linear(28*28, 512), nn.LeakyReLU(),
nn.Linear(512, 256), nn.LeakyReLU(),
nn.Linear(256, 128), nn.LeakyReLU(),
nn.Linear(128, 64), nn.LeakyReLU(),
nn.Linear(64, 10)
)
def forward(self, x):
x = x.view(-1, 784) # -1 表示自动计算第一个维度,784 是一张图片大小为 28 * 28
return self.FFN(x)
def train_valid(model, train_loader, test_loader):
criterion = nn.CrossEntropyLoss()
optimizer = torch.optim.SGD(model.parameters(), lr=0.01, momentum=0.5)
for epoch in range(10):
model.train() # 确保模型处于训练模式
running_loss = 0.0
for batch_idx, data in enumerate(train_loader):
inputs, targets = data
optimizer.zero_grad()
outputs = model(inputs)
loss = criterion(outputs, targets)
loss.backward()
optimizer.step()
running_loss += loss.item()
if batch_idx % 300 == 299:
print("[{:d}, {:5d}] loss: {:.3f}".format(epoch + 1, batch_idx + 1, running_loss / 300))
running_loss = 0.0
vaild(model, test_loader)
def vaild(model, test_loader):
correct = 0
total = 0
model.eval() # 确保模型处于评估模式
with torch.no_grad():
for data in test_loader:
images, labels = data
outputs = model(images) # (batch_size, 10)
_, predicted = torch.max(outputs.data, dim=1) # 返回最大值和最大值的下标
total += labels.size(0) # (batch_size, 1)
correct += (predicted == labels).sum().item()
print('Accuracy on test set: %d %%' % (100 * correct / total))
if __name__ == '__main__':
batch_size = 64
train_set, test_set = load_data()
train_loader = DataLoader(train_set, shuffle=True, batch_size=batch_size)
test_loader = DataLoader(test_set, shuffle=False, batch_size=batch_size)
model = Mymodel()
train_valid(model, train_loader, test_loader)
PS:这儿出现了一个问题,原函数名叫test,让pycharm误以为是测试用例,修改为valid后正常。
输出结果如下。
[1, 300] loss: 2.234
[1, 600] loss: 1.042
[1, 900] loss: 0.445
Accuracy on test set: 89 %
[2, 300] loss: 0.327
[2, 600] loss: 0.284
[2, 900] loss: 0.234
Accuracy on test set: 93 %
[3, 300] loss: 0.197
[3, 600] loss: 0.180
[3, 900] loss: 0.164
Accuracy on test set: 95 %
[4, 300] loss: 0.134
[4, 600] loss: 0.133
[4, 900] loss: 0.126
Accuracy on test set: 96 %
[5, 300] loss: 0.107
[5, 600] loss: 0.098
[5, 900] loss: 0.099
Accuracy on test set: 96 %
[6, 300] loss: 0.079
[6, 600] loss: 0.080
[6, 900] loss: 0.081
Accuracy on test set: 97 %
[7, 300] loss: 0.065
[7, 600] loss: 0.066
[7, 900] loss: 0.065
Accuracy on test set: 97 %
[8, 300] loss: 0.050
[8, 600