Pytorch深度学习快速入门(中)
- 一、Containers(神经网络的基本骨架)
- (一)Module 的使用
- (二)Sequential 的使用<搭建小实战>
- 二、Convolution Layers(卷积层)
- (一)torch.nn.functional 中 conv2d 的使用
- (二)torch.nn 中 Conv2d 的使用
- 三、Pooling layers(池化层)
- (一)池化层的介绍
- (二)MaxPool2d 的使用
- 四、Non-linear Activations(非线性激活)
- (一)非线性激活的目的
- (二)ReLU 与 Sigmoid 的使用
- 五、Linear Layers(线性层)及其他层
- (一)其他层简介
- (二)Linear Layers(线性层/全连接层)— Linear 的使用
- 六、损失函数&反向传播
- (一)损失函数的作用&与反向传播的联系
- (二)L1Loss、MSELoss、CrossEntropyLoss损失函数的使用
- (三)如何在之前写的神经网络中用到损失函数&反向传播
- 七、优化器
- (一)官方文档解读
- (二)如何在之前写的神经网络中用到优化器
一、Containers(神经网络的基本骨架)
(一)Module 的使用
import torch
from torch import nn
class Li(nn.Module):
def __init__(self):
super().__init__()
def forward(self,input):
output = input + 1
return output
li = Li() # 用模板创建出了一个神经网络
x = torch.tensor(1.0) # 创建一个标量张量,其值为 1.0
# 张量是python中各种维度数据形式的统称,0维就是一个数,1维是数组,2维是矩阵,3维是空间矩阵
output = li(x)
print(output) # 输出本来是2.0,但系统会自动忽略后面无意义的0,输出2.
(二)Sequential 的使用<搭建小实战>
Sequential 的作用:让代码更加简洁
# 卷积核通道数 == 输入图像的通道数
# 这是因为卷积操作是逐通道进行的,然后(可选地)通过某种方式(如求和或加权和)将各通道的结果合并
# 输出通道数 == 卷积核个数
import torch
from torch import nn
from torch.nn import Conv2d, MaxPool2d, Flatten, Linear, Sequential
from torch.utils.tensorboard import SummaryWriter
class Li(nn.Module):
def __init__(self):
super().__init__()
# 法一:奇数卷积核把中心格子对准图片第一个格子,卷积核在格子外有两层就padding=2
# 法二:如果前后尺寸不变,padding=(kernelsize-1)/2,kernelsize为卷积核(过滤器)的尺寸
# self.conv1 = Conv2d(3,32,5,padding=2)
# self.maxpool1 = MaxPool2d(2)
# self.conv2 = Conv2d(32,32,5,padding=2)
# self.maxpool2 = MaxPool2d(2)
# self.conv3 = Conv2d(32,64,5,padding=2)
# self.maxpool3 = MaxPool2d(2)
# self.flatten = Flatten() # 实质上用1x1的kernel做卷积(全卷积网络)
# self.linear1 = Linear(1024,64)
# self.linear2 = Linear(64,10)
self.model1 = Sequential(
# 输出32通道是因为发生了32次卷积,输出大小不变是因为有填充边
# kernel的内容不一样可以理解为不同的特征抓取 --> 一个核会产生一个channel
# 层数多是为了获取更多的特征而不是偏向某一特征,深度深(在模型不退化的前提下) --> 能提取高级、复杂的特征
Conv2d(3,32,5,padding=2),
MaxPool2d(2),
Conv2d(32, 32, 5, padding=2),
MaxPool2d(2),
Conv2d(32, 64, 5, padding=2),
MaxPool2d(2),
Flatten(),
Linear(1024, 64),
Linear(64, 10)
# 最后一个线性层的输出往往会经过一个softmax激活函数,以将每个元素转化成表示概率的值
)
def forward(self,x):
# x = self.conv1(x)
# x = self.maxpool1(x)
# x = self.conv2(x)
# x = self.maxpool2(x)
# x = self.conv3(x)
# x = self.maxpool3(x)
# x = self.flatten(x)
# x = self.linear1(x)
# x = self.linear2(x)
x = self.model1(x)
return x
li = Li()
print(li)
# 检验网络的书写是否正确,不写就算网络结构写错了,也不会报错
input = torch.ones((64,3,32,32))
# 可视化方式 1
output = li(input)
print(output.shape)
# 可视化方式 2
writer = SummaryWriter("./logs_seq")
writer.add_graph(li,input)
writer.close()
二、Convolution Layers(卷积层)
(一)torch.nn.functional 中 conv2d 的使用
import torch
import torch.nn.functional as F
# Tensor可以传入 size/具体数值/其他的 tensor 来构建一个 tensor
# tensor只能传入数据(可根据所传进行 dtype 设置)
input = torch.tensor([[1,2,0,3,1],
[0,1,2,3,1],
[1,2,1,0,0],
[5,2,3,1,1],
[2,1,0,1,1]])
# 卷积核一开始的大小是自己设置的,卷积核上的每一个位置相当于权重 w,其具体值正是需要学习的
kernel = torch.tensor([[1,2,1],
[0,1,0],
[2,1,0]])
# 只取一个样本->batch_size(样本数量)==1 二维矩阵->channel(图像的通道数)==1
# 4维:[图片数,图层数,宽,高]
input = torch.reshape(input,(1,1,5,5))
kernel = torch.reshape(kernel,(1,1,3,3))
print(input.shape)
print(kernel.shape)
output1 = F.conv2d(input,kernel,stride=1)
print(output1)
output2 = F.conv2d(input,kernel,stride=2)
print(output2)
# padding
# eg.把 5x5填充成 6x6(用0填充),5x5的边缘数据可以多用几次,以平均利用数据,更好的保留边缘特征,
# 防止某些像素点被多次进行特征提取,导致某些点的作用过于放大了
output3 = F.conv2d(input,kernel,stride=1,padding=1)
print(output3)
(二)torch.nn 中 Conv2d 的使用
import torch
import torchvision
from torch import nn
from torch.nn import Conv2d
from torch.utils.data import DataLoader
from torch.utils.tensorboard import SummaryWriter
# "./data.txt"存在当前目录
# "../data.txt"存在上一级目录
dataset = torchvision.datasets.CIFAR10("./dataset",train=False,transform=torchvision.transforms.ToTensor(),download=True)
dataloader = DataLoader(dataset,batch_size=64)
# 卷积核个数 == 输出通道数
class Li(nn.Module):
def __init__(self):
super().__init__()
self.conv1 = Conv2d(in_channels=3,out_channels=6,kernel_size=3,stride=1,padding=0)
def forward(self,x):
x = self.conv1(x)
return x
li = Li()
writer = SummaryWriter("./logs")
step = 0
for data in dataloader:
imgs,targets = data
output = li(imgs)
print(imgs.shape)
print(output.shape)
# torch.Size([64, 3, 32, 32])
writer.add_images("input",imgs,step)
# torch.Size([64, 6, 30, 30])
# 直接输不出来 channel=6 的图 --> reshape一下,拼接图片数
output = torch.reshape(output,(-1,3,30,30)) # -1是一个占位符,表示让Pytorch自动计算该维度的大小
writer.add_images("output",output,step)
step = step + 1
三、Pooling layers(池化层)
(一)池化层的介绍
(1)池化层的特点
- 没有需要学习的参数
- 通道数保持不变
- 对微小位置的变化具有鲁棒性
- 使用某一位置的相邻输出的总体统计特征来代替网络在该位置的输出。本质是降采样,可以大幅减少网络的参数
(2)最大池化的目的
- 保留输入的重要特征,但同时减小数据量,减小计算压力
- 防止过拟合
(3)卷积和池化的区别
卷积:提取特征 <----> 池化:降低特征数据量
(二)MaxPool2d 的使用
import torch
from torch import nn
from torch.nn import MaxPool2d
input = torch.tensor([[1,2,0,3,1],
[0,1,2,3,1],
[1,2,1,0,0],
[5,2,3,1,1],
[2,1,0,1,1]],dtype=torch.float32)
# dtype即datatype,不加这一句会报错:"max_pool2d" not implemented for 'Long'
# float类型和 float6 4类型是一样的,都需要 64个 bits,而 float32 只需要 32个 bits
input = torch.reshape(input,(-1,1,5,5))
print(input.shape)
class Li(nn.Module):
def __init__(self):
super().__init__()
# 池化中默认的 stride 和卷积核一个大小
# dilatioon(膨胀):空洞卷积
# ceil_mode:True(核部分覆盖->保留)、False(核部分覆盖->舍弃),默认为 False
self.maxpool1 = MaxPool2d(kernel_size=3,ceil_mode=False)
def forward(self, input):
output = self.maxpool1(input)
return output
li = Li()
output = li(input)
print(output)
import torchvision
from torch import nn
from torch.nn import MaxPool2d
from torch.utils.data import DataLoader
from torch.utils.tensorboard import SummaryWriter
dataset = torchvision.datasets.CIFAR10("./dataset",train=False,download=True,transform=torchvision.transforms.ToTensor())
dataloader = DataLoader(dataset,batch_size=64)
class Li(nn.Module):
def __init__(self):
super().__init__()
self.maxpool1 = MaxPool2d(kernel_size=3,ceil_mode=False)
def forward(self, input):
output = self.maxpool1(input)
return output
li = Li()
writer = SummaryWriter("logs_maxpool")
step = 0
for data in dataloader:
imgs,targets = data
writer.add_images("input",imgs,step)
output = li(imgs)
writer.add_images("output",output,step)
step = step + 1
writer.close()
四、Non-linear Activations(非线性激活)
(一)非线性激活的目的
激活后就变弯了,弯曲线可以拟合十分复杂的曲线,提高泛化能力
(二)ReLU 与 Sigmoid 的使用
import torch
from torch import nn
from torch.nn import ReLU
input = torch.tensor([[1,-0.5],
[-1,3]]) # 有负值的原因:梯度下降,反向传播
input = torch.reshape(input,(-1,1,2,2))
print(input.shape)
class Li(nn.Module):
def __init__(self):
super().__init__()
# inplace(在不在原来的位置替换)
# ==True:会更改传入的数据(更改器方法),反之不会更改原数据(访问器方法),默认是 False
self.relu1 = ReLU()
def forward(self,input):
output = self.relu1(input)
return output
li = Li()
output = li(input)
print(output)
import torchvision
from torch import nn
from torch.nn import ReLU, Sigmoid
from torch.utils.data import DataLoader
from torch.utils.tensorboard import SummaryWriter
dataset = torchvision.datasets.CIFAR10("./dataset",train=False,download=True,
transform=torchvision.transforms.ToTensor())
dataloader = DataLoader(dataset,batch_size=64)
class Li(nn.Module):
def __init__(self):
super().__init__()
self.relu1 = ReLU()
# 三个通道的取值都是 0 ~ 255,用 ReLU 等于没有变化,用 Sigmoid 才能映射到 0 ~ 1 的区间
self.sigmoid1 = Sigmoid()
def forward(self,input):
output = self.sigmoid1(input)
return output
li = Li()
writer = SummaryWriter("./logs_relu") # Alt+回车:导入库
step = 0
for data in dataloader:
imgs,targets = data
writer.add_images("input",imgs,global_step=step)
output = li(imgs)
writer.add_images("output",output,global_step=step)
step = step + 1
writer.close()
五、Linear Layers(线性层)及其他层
(一)其他层简介
(二)Linear Layers(线性层/全连接层)— Linear 的使用
import torch
import torchvision
from torch import nn
from torch.nn import Linear
from torch.utils.data import DataLoader
dataset = torchvision.datasets.CIFAR10("./dataset",train=False,
transform=torchvision.transforms.ToTensor(),download=True)
dataloader = DataLoader(dataset,batch_size=64,drop_last=True)
# 舍弃掉最后一组不满足数量的批次,不然会报错: mat1 and mat2 shapes cannot be multiplied (1x49152 and 196608x10)
class Li(nn.Module):
def __init__(self):
super().__init__()
self.linear1 = Linear(196608,10)
def forward(self,input):
output = self.linear1(input)
return output
li = Li()
for data in dataloader:
imgs,targets = data
print(imgs.shape)
# output = torch.reshape(imgs,(1,1,1,-1)) # [1,1,1,196608]
output = torch.flatten(imgs) # 摊平 -> 才可作为全连接的输入
print(output.shape)
output = li(output)
print(output.shape)
# regulation(正则化):用于惩罚较高复杂度的拟合函数,防止过拟合,加速训练
# normalization(标准化/归一化)
# Embedding(嵌入层):存 NPL 中 token 向量
现在我们会自己去搭建一个网络模型了,但有的时候,我们还可以用Pytorch给我们提供的一些网络模型
六、损失函数&反向传播
(一)损失函数的作用&与反向传播的联系
(1)损失函数
- 计算实际输出和目标之间的差距
- 为我们更新输出提供一定的依据(反向传播),即有了损失,才能计算梯度,进而才能更新参数,趋向最优
(2)反向传播
反向传播是用来计算梯度的,给神经网络中每一个需要调优的参数,即给卷积层中每一个卷积核,设置一个梯度,根据梯度来更新参数,实现 Loss 最小化
因为是从 loss 开始推导参数,和网络的顺序相反,所以叫反向传播,梯度的理解可以直接当成“斜率”
(二)L1Loss、MSELoss、CrossEntropyLoss损失函数的使用
# 注:计算机 log 默认以 e 为底,计算器 log 默认以 10 为底
import torch
from torch import nn
from torch.nn import L1Loss, MSELoss
inputs = torch.tensor([1,2,3],dtype=torch.float32)
targets = torch.tensor([1,2,5],dtype=torch.float32)
# tensor 产生时会自带维度,reshape 是为了保证维度符合你想使用函数的特定要求
inputs = torch.reshape(inputs,(1,1,1,3))
targets = torch.reshape(targets,(1,1,1,3))
loss = L1Loss(reduction='sum')
result = loss(inputs,targets)
# mean squared error (均方误差)
loss_mse = MSELoss()
result_mse = loss_mse(inputs,targets)
print(result)
print(result_mse)
# 这里不是概率,只是一个评估分数,后续要经过 softmax (正则化)(激活层) 之后,输出的才是概率,加起来才为 1
x = torch.tensor([0.1,0.2,0.3])
# 代表 x 中需要计算元素 loss 的位置(要计算损失函数的类别),又 batch size==1,这里就没再指定了
y = torch.tensor([1])
x = torch.reshape(x,(1,3)) # (batch size,number of classes)
loss_cross = nn.CrossEntropyLoss()
result_cross = loss_cross(x,y)
print(result_cross)
# 神经网络的两大类主题:回归分类,一般 mse 用于回归,crossentroy 用于分类
# 每个优化器会对应一个优化函数,不同优化函数对应不同损失函数
(三)如何在之前写的神经网络中用到损失函数&反向传播
import torchvision
from torch import nn
from torch.nn import Sequential, Conv2d, MaxPool2d, Flatten, Linear
from torch.utils.data import DataLoader
dataset = torchvision.datasets.CIFAR10("./dataset",train=False,
transform=torchvision.transforms.ToTensor(),download=True)
dataloader = DataLoader(dataset,batch_size=1)
class Li(nn.Module):
def __init__(self):
super().__init__()
self.model1 = Sequential(
Conv2d(3,32,5,padding=2),
MaxPool2d(2),
Conv2d(32, 32, 5, padding=2),
MaxPool2d(2),
Conv2d(32, 64, 5, padding=2),
MaxPool2d(2),
Flatten(),
Linear(1024, 64),
Linear(64, 10)
)
def forward(self,x):
x = self.model1(x)
return x
loss = nn.CrossEntropyLoss()
li = Li()
for data in dataloader:
imgs,targets = data
outputs = li(imgs)
# 可以先看一下 outputs 和 targets 长什么样,看选择什么样的损失函数
# print(outputs)
# print(targets)
result_loss = loss(outputs,targets)
# print(result_loss)
result_loss.backward()
# outputs 是由卷积核计算出来的,result_loss 是由 outputs 计算的,所以 result_loss 是卷积核中的参数的一个函数
七、优化器
(一)官方文档解读
(二)如何在之前写的神经网络中用到优化器
import torch
import torchvision
from torch import nn
from torch.nn import Sequential, Conv2d, MaxPool2d, Flatten, Linear
from torch.utils.data import DataLoader
dataset = torchvision.datasets.CIFAR10("./dataset",train=False,
transform=torchvision.transforms.ToTensor(),download=True)
dataloader = DataLoader(dataset,batch_size=1)
class Li(nn.Module):
def __init__(self):
super().__init__()
self.model1 = Sequential(
Conv2d(3,32,5,padding=2),
MaxPool2d(2),
Conv2d(32, 32, 5, padding=2),
MaxPool2d(2),
Conv2d(32, 64, 5, padding=2),
MaxPool2d(2),
Flatten(),
Linear(1024, 64),
Linear(64, 10)
)
def forward(self,x):
x = self.model1(x)
return x
loss = nn.CrossEntropyLoss()
li = Li()
# lr(learning rate):
# 大 -> 训练的模型很不稳定(一开始)
# 小 -> 模型训练的慢(学到后面),如过后面还用大,模型直接跑过极小值点了
optim = torch.optim.SGD(li.parameters(),lr=0.01)
# 一般要对数据进行多轮学习
for epoch in range(20):
# 关注每一轮中整体的 loss
running_loss = 0.0
for data in dataloader:
imgs,targets = data
output = li(imgs)
result_loss = loss(output,targets)
optim.zero_grad() # 要清零,上一次计算的梯度对这一次的梯度是没用的,梯度每次计算都会累积,会越来越大
result_loss.backward() # 计算每个参数对应的梯度
optim.step() # 对权重的每个参数进行更新调优
running_loss = running_loss + result_loss
print(running_loss)
完