学习网站:《PyTorch深度学习实践》完结合集_哔哩哔哩_bilibili
NLPnote/1.2.7 使用pytorch实现手写数字识别.md at master · SpringMagnolia/NLPnote (github.com)
import torch
import numpy as np
#张量tensor
#创建张量
t=torch.tensor([[1.,-1.],[1.,-1.]])#使用python中的列表或者序列创建tensor
print(t)
t=torch.tensor(np.array([[1,2,3],[4,5,6]]))#使用numpy中的数组创建tensor
print(t)
#torch.empty(3,4)创建3行4列的空的tensor,会用无用数据进行填充
#torch.ones([3,4]) 创建3行4列的全为1的tensor
#torch.zeros([3,4])创建3行4列的全为0的tensor
#torch.rand([3,4]) 创建3行4列的随机值的tensor,随机值的区间是[0, 1)
#torch.randn([3,4]) 创建3行4列的随机数的tensor,随机值的分布式均值为0,方差为1
t=torch.rand([3,4])#随机值
print(t)
t=torch.randint(low=0,high=10,size=[3,4])#随机整数,区间【low,high】
print(t)
#常用方法
#1.获取tensor中只有1个元素数据:tensor.item()
a=torch.randn(1)
t=a.item()
print(t)
#2.转化为numpy数组:z.numpy()
#3.获取形状:tensor.size()
#4.形状改变:tensor.view((3,4))
#5.获取阶数:tensor.dim()
#6.获取最大值:tensor.max()
#7.转置:tensor.t()
#8.tensor[1,3] 获取tensor中第一行第三列的值
#9.tensor[1,3]=100 对tensor中第一行第三列的位置进行赋值100
#10.tensor的切片
a = torch.rand(4, 3, 28, 28)
print(a[:2].shape) # 在第一个维度上取后0和1
print(a[:2, :1, :, :].shape) # 在第一个维度上取0和1,在第二个维度上取0
print(a[:2, 1:, :, :].shape) # 在第一个维度上取0和1,在第二个维度上取1,2
print(a[:2, -2:, :, :].shape) # 在第一个维度上取0和1,在第二个维度上取1,2
print(a[:, :, 0:28:2, 0:28:2].shape) # step=2隔行采样
print(a[:, :, ::2, ::2].shape) # 等同于这个
#tensor数据类型
#获取tensor的数据类型:tensor.dtype 创建数据的时候指定类型:torch.ones([2,3],dtype=torch.float32) 类型的修改
#tensor相加: torch.add(x,y) 或者 x.add(y) 以及 x.add_(y)带下划线对x修改,以及和数字相加
#torch.cuda这个模块增加了对CUDA tensor的支持,能够在cpu和gpu上使用相同的方法操作tensor
#通过.to方法能够把一个tensor转移到另外一个设备(比如从CPU转到GPU)
梯度下降和反向传播
梯度:是一个向量,导数+变化最快的方向(学习的前进方向),需要取梯度下降的方向即梯度的反方向作为变化方向。
梯度下降算法:
梯度就是多元函数参数的变化趋势(参数学习的方向),只有一个自变量时称为导数
计算图:通过图的方式来描述函数的图形
用目标函数对权重求导,求到上升方向;
下降方向求倒数的负方向:
是学习率,表示往前走多远,一般取值会比较小,如果取得特别大可能无法收敛。
“贪心算法”:每次迭代都朝着下降最快的方向去往前走一步
随机梯度下降:
选择的是单个样本点的损失为标准
梯度更新公式
梯度下降算法很难达到全局最优,使用随机梯度下降算法,性能好,但时间复杂度高。
综合梯度下降和随机梯度下降算法,折中:patch(mini-patch),批量的随机梯度下降,将若干样本分成一组,记录一组的梯度用来代替随机梯度下降中的单个样本
总结:前馈算损失,反馈算梯度,用梯度下降算法更新权重
l.backward()会把计算图中所有需要梯度(grad)的地方都会求出来,然后把梯度都存在对应的待求的参数中,最终计算图被释放。
前向传播求损失函数
y=w*x+b,假设 x=1,w=1,y=2(实际值),b=0,则
反向传播更新梯度
假设学习率η=0.03,则由上文梯度更新公式可得,权重w=1-0.03*(-2)=1.006,一次迭代结束。可以推测,在后续的梯度下降过程中,w会不断接近准确的权重值2,适当增加学习率可以加快这一过程。
在pytorch中,反向传播的过程用loss.backward()方法完成。该函数可以自动计算反向传播阶段的各个梯度。w的梯度用w.grad.data获取。则梯度更新的代码为:
自动微分
MindSpore计算一阶导数方法mindspore.ops.GradOperation
(get_all=False,
get_by_list=False,
sens_param=False),其中get_all为False时,只会对第一个输入求导,为True时,会对所有输入求导;get_by_list为False时,不会对权重求导,为True时,会对权重求导;sens_param对网络的输出值做缩放以改变最终梯度。
对输入求一阶导
定义网络结构:
MatMul算子构成的网络f(x,y)=z∗x∗yf(x,y)=z∗x∗y
class Net(nn.Cell):
def __init__(self):
super(Net, self).__init__()
self.matmul = ops.MatMul()
self.z = Parameter(Tensor(np.array([1.0], np.float32)), name='z')
def construct(self, x, y):
x = x * self.z
out = self.matmul(x, y)
return out
定义求导网络:
__init__函数中定义需要求导的网络self.net和ops.GradOperation操作,construct函数中对self.net进行求导
可以通过sens_param参数对网络的输出值做缩放以改变最终梯度
通用数据处理
数据处理算子 | 算子说明 |
shuffle | 对数据集进行混洗,随机打乱数据顺序。 |
map | 提供自定义函数或算子,作用于数据集的指定列数据。 |
batch | 对数据集进行分批,可以减少训练轮次,加速训练过程。 |
repeat | 对数据集进行重复,达到扩充数据量的目的。 |
zip | 将两个数据集进行列拼接,合并为一个数据集。 |
concat | 将两个数据集进行行拼接,合并为一个数据集。 |
shuffle
对数据集进行混洗,随机打乱数据顺序。
buffer_size越大,混洗程度越大,但时间、计算资源消耗也会更大。
map
将指定的函数或算子作用于数据集的指定列数据,实现数据映射操作。
batch
将数据集分批,分别输入到训练系统中进行训练,可以减少训练轮次,达到加速训练过程的目的。
repeat
repeat和batch操作的顺序会影响训练batch的数量,建议将repeat置于batch之后。
zip
将两个数据集进行列拼接,合并为一个数据集。
如果两个数据集的列名相同,则不会合并,请注意列的命名。
如果两个数据集的行数不同,合并后的行数将和较小行数保持一致。
concat
将两个数据集进行行拼接,合并为一个数据集。
输入数据集中的列名,列数据类型和列数据的排列应相同。
Pytorch完成基础的模型
nn.Modul是pytorch自定义网络的一个基类
- __init__需要调用super方法,继承父类的属性和方法
- forward方法必须实现,用来定义我们的网络的向前计算的过程
nn.Linear为torch预定义好的线性模型,也被称为全连接层,传入的参数为输入的数量,输出的数量(in_features, out_features),是不算(batch_size的列数)
优化器(optimizer):
- torch.optim.SGD(参数,学习率)
- torch.optim.Adam(参数,学习率)
参数:model.parameters()获取
优化器的使用方法:
- 实例化
- 所有参数的梯度,将其值置为0
- 反向传播计算梯度
- 更新参数值
示例:
optimizer = optim.SGD(model.parameters(), lr=1e-3) #1. 实例化
optimizer.zero_grad() #2. 梯度置为0
loss.backward() #3. 计算梯度
optimizer.step() #4. 更新参数的值
损失函数:
- 均方误差 : nn.MSELoss(),常用于回归问题
- 交叉熵损失 :nn.CrossEntropyLoss(),常用于分类问题
model.eval()表示设置模型为评估模式,即预测模式
model.train(mode=True) 表示设置模型为训练模式
优化算法:
- 梯度下降算法
- 随机梯度下降法
- 小批量梯度下降
- 动量法
- ADAGRAD
- RMSPROP
- ADAM
数据加载
数据集的原始地址:http://yann.lecun.com/exdb/mnist/
使用Pytorch实现手写数字识别
- 准备数据2.构建模型3.模型训练4.模型保存5.模型评估
- 准备训练集和测试集
1.1torchvision.transforms
的图形数据处理方法
1.1.1torchvision.transforms.ToTensor
把一个取值范围是[0,255]
的PIL.Image
或者shape
为(H,W,C)
的numpy.ndarray
,转换成形状为[C,H,W]
,其中
[C,H,W]
为(高,宽,通道数),黑白图片的通道数为
1
,每个像素点的取值为【
0
,
255
】,彩色图片通道为(
r
,
g
,
b
),每个通道的像素点取值为【
0,255
】。
1.1.2torchvision.transforms.Normalize(mean, std)
给定均值:mean,shape和图片的通道数相同(指的是每个通道的均值),方差:std,和图片的通道数相同
1.1.3torchvision.transforms.Compose(transforms)
将多个transform
组合起来使用。
1.2准备MNIST数据集的Dataset和DataLoader
二.构建模型
使用全连接层:当前一层的神经元和前一层的神经元相互链接,其核心操作就是y = wx,即矩阵的乘法,实现对前一层的数据的变换
使用三层的神经网络,包括两个全连接层和一个输出层,第一个全连接经过激活函数处理
激活函数使用:常用RELU激活函数
2.2模型中数据的形状:
- 原始输入数据为的形状:[batch_size,1,28,28]
- 进行形状的修改:[batch_size,28*28] ,(全连接层是在进行矩阵的乘法操作)
- 第一个全连接层的输出形状:[batch_size,28],这里的28是个人设定的,你也可以设置为别的
- 激活函数不会修改数据的形状
- 第二个全连接层的输出形状:[batch_size,10],因为手写数字有10个类别
2.3损失函数:
使用softmax函数
Softmax概率传入对数似然损失得到的损失函数称为交叉熵损失
三.模型的训练
- 实例化模型,设置模型为训练模式
- 实例化优化器类,实例化损失函数
- 获取,遍历dataloader
- 梯度置为0
- 进行向前计算
- 计算损失
- 反向传播
- 更新参数
手写数字识别
import torch
import torch.nn as nn
import torch.nn.functional as F
import torch.optim as optim
from torchvision import datasets, transforms
import torchvision
from torch.autograd import Variable
from torch.utils.data import DataLoader
import cv2
# 下载训练集
train_dataset = datasets.MNIST(root='./num/',
train=True,
transform=transforms.ToTensor(),
download=True)
# 下载测试集
test_dataset = datasets.MNIST(root='./num/',
train=False,
transform=transforms.ToTensor(),
download=True)
# dataset 参数用于指定我们载入的数据集名称
# batch_size参数设置了每个包中的图片数据个数
# 在装载的过程会将数据随机打乱顺序并进打包
batch_size = 64
#建立一个数据迭代器
# 装载训练集
train_loader = torch.utils.data.DataLoader(dataset=train_dataset,
batch_size=batch_size,
shuffle=True)
# 装载测试集
test_loader = torch.utils.data.DataLoader(dataset=test_dataset,
batch_size=batch_size,
shuffle=True)
# 实现单张图片可视化
images, labels = next(iter(train_loader))
img = torchvision.utils.make_grid(images)
img = img.numpy().transpose(1, 2, 0)
std = [0.5, 0.5, 0.5]
mean = [0.5, 0.5, 0.5]
img = img * std + mean
print(labels)
# 卷积层使用 torch.nn.Conv2d
# 激活层使用 torch.nn.ReLU
# 池化层使用 torch.nn.MaxPool2d
# 全连接层使用 torch.nn.Linear
class LeNet(nn.Module):
def __init__(self):
super(LeNet, self).__init__()
self.conv1 = nn.Sequential(nn.Conv2d(1, 6, 3, 1, 2), nn.ReLU(),
nn.MaxPool2d(2, 2))
self.conv2 = nn.Sequential(nn.Conv2d(6, 16, 5), nn.ReLU(),
nn.MaxPool2d(2, 2))
self.fc1 = nn.Sequential(nn.Linear(16 * 5 * 5, 120),
nn.BatchNorm1d(120), nn.ReLU())
self.fc2 = nn.Sequential(
nn.Linear(120, 84),
nn.BatchNorm1d(84),
nn.ReLU(),
nn.Linear(84, 10))
# 最后的结果一定要变为 10,因为数字的选项是 0 ~ 9
def forward(self, x):
x = self.conv1(x)
x = self.conv2(x)
x = x.view(x.size()[0], -1)
x = self.fc1(x)
x = self.fc2(x)
return x
device = torch.device('cuda' if torch.cuda.is_available() else 'cpu')
LR = 0.001
net = LeNet().to(device)
# 损失函数使用交叉熵
criterion = nn.CrossEntropyLoss()
# 优化函数使用 Adam 自适应优化算法
optimizer = optim.Adam(
net.parameters(),
lr=LR,
)
epoch = 1
if __name__ == '__main__':
for epoch in range(epoch):
sum_loss = 0.0
for i, data in enumerate(train_loader):
inputs, labels = data
inputs, labels = Variable(inputs).cpu(), Variable(labels).cpu()
optimizer.zero_grad() #将梯度归零
outputs = net(inputs) #将数据传入网络进行前向运算
loss = criterion(outputs, labels) #得到损失函数
loss.backward() #反向传播
optimizer.step() #通过梯度做一步参数更新
# print(loss)
sum_loss += loss.item()
if i % 100 == 99:
print('[%d,%d] loss:%.03f' %
(epoch + 1, i + 1, sum_loss / 100))
sum_loss = 0.0
net.eval() #将模型变换为测试模式
correct = 0
total = 0
for data_test in test_loader:
images, labels = data_test
images, labels = Variable(images).cpu(), Variable(labels).cpu()
output_test = net(images)
_, predicted = torch.max(output_test, 1)
total += labels.size(0)
correct += (predicted == labels).sum()
print("correct1: ", correct)
print("Test acc: {0}".format(correct.item() /
len(test_dataset)))