知识点总结
-
知识点1:神经网络的基本骨架nn.Module的使用
-
-
import torch.nn as nn import torch.nn.functional as F class Model(nn.Module): #搭建的神经网络 Model继承了 Module类(父类) def __init__(self): #初始化函数 super(Model, self).__init__() #必须要这一步,调用父类的初始化函数 self.conv1 = nn.Conv2d(1, 20, 5) self.conv2 = nn.Conv2d(20, 20, 5) def forward(self, x): #前向传播(为输入和输出中间的处理过程),x为输入 x = F.relu(self.conv1(x)) #conv为卷积,relu为非线性处理 return F.relu(self.conv2(x))
-
-
-
知识点2:卷积操作
-
-
-
stride(步进)
可以是单个数,或元组(sH,sW) — 控制横向步进和纵向步进
-
#reshape函数 # 尺寸只有高和宽,不符合要求 print(input.shape) #5×5 print(kernel.shape) #3×3 # 尺寸变换为四个数字 input = torch.reshape(input,(1,1,5,5)) #通道数为1,batch大小为1 kernel = torch.reshape(kernel,(1,1,3,3))
-
input:尺寸要求是batch,几个通道,高,宽(4个参数)
-
weight:尺寸要求是输出,in_channels(groups一般为1),高,宽(4个参数)
-
padding(填充)
在输入图像左右两边进行填充,决定填充有多大。可以为一个数或一个元组(分别指定高和宽,即纵向和横向每次填充的大小)。默认情况下不进行填充
padding=1:将输入图像左右上下两边都拓展一个像素,空的地方默认为0
-
#二维卷积 CLASS torch.nn.Conv2d(in_channels, out_channels, kernel_size, stride=1, padding=0, dilation=1, groups=1, bias=True, padding_mode='zeros', device=None, dtype=None) # in_channels 输入通道数 # out_channels 输出通道数 # kernel_size 卷积核大小 #以上参数需要设置 #以下参数提供了默认值 # stride=1 卷积过程中的步进大小 # padding=0 卷积过程中对原始图像进行padding的选项 # dilation=1 每一个卷积核对应位的距离 # groups=1 一般设置为1,很少改动,改动的话为分组卷积 # bias=True 通常为True,对卷积后的结果是否加减一个常数的偏置 # padding_mode='zeros' 选择padding填充的模式
-
-
kernel_size
定义了一个卷积核的大小,若为3则生成一个3×3的卷积核
-
卷积核的参数是从一些分布中进行采样得到的
-
实际训练过程中,卷积核中的值会不断进行调整
-
-
in_channels & out_channels in_channels:输入图片的channel数(彩色图像 in_channels 值为3) out_channels:输出图片的channel数 in_channels 和 out_channels 都为 1 时,拿一个卷积核在输入图像中进行卷积
out_channels 为 2 时,卷积层会生成两个卷积核(不一定一样),得到两个输出,叠加后作为最后输出
-
-
-
知识点3: 神经网络 - 最大池化的使用
-
MaxPool:最大池化(下采样)
-
MaxUnpool:上采样
-
AvgPool:平均池化
-
AdaptiveMaxPool2d:自适应最大池化
-
CLASS torch.nn.MaxPool2d(kernel_size, stride=None, padding=0, dilation=1, return_indices=False, ceil_mode=False) #注意,卷积中stride默认为1,而池化中stride默认为kernel_size #Ceil_mode 默认情况下为 False,对于最大池化一般只需设置 kernel_size 即可
-
-
-
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) #最大池化无法对long数据类型进行实现,将input变成浮点数的tensor数据类型 input = torch.reshape(input,(-1,1,5,5)) #-1表示torch计算batch_size #要求的 input 必须是四维的,参数依次是:batch_size、channel、高、宽 print(input.shape) # 搭建神经网络 class Tudui(nn.Module): def __init__(self): super(Tudui, self).__init__() self.maxpool1 = MaxPool2d(kernel_size=3,ceil_mode=True) def forward(self,input): output = self.maxpool1(input) return output # 创建神经网络 tudui = Tudui() output = tudui(input) print(output)
-
为什么要进行最大池化?最大池化的作用是什么? 最大池化的目的是保留输入的特征,同时把数据量减小(数据维度变小),对于整个网络来说,进行计算的参数变少,会训练地更快
如上面案例中输入是5x5的,但输出是3x3的,甚至可以是1x1的 类比:1080p的视频为输入图像,经过池化可以得到720p,也能满足绝大多数需求,传达视频内容的同时,文件尺寸会大大缩小 池化一般跟在卷积后,卷积层是用来提取特征的,一般有相应特征的位置是比较大的数字,最大池化可以提取出这一部分有相应特征的信息
池化不影响通道数
池化后一般再进行非线性激活
-
-
知识点4:非线性激活
-
非线性激活:给神经网络引入一些非线性的特征
非线性越多,才能训练出符合各种曲线或特征的模型(提高泛化能力)
-
最常见:RELU
-
-
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)) #input必须要指定batch_size,-1表示batch_size自己算,1表示是1维的 print(input.shape) #torch.Size([1, 1, 2, 2]) # 搭建神经网络 class Tudui(nn.Module): def __init__(self): super(Tudui, self).__init__() self.relu1 = ReLU() #inplace默认为False def forward(self,input): output = self.relu1(input) return output # 创建网络 tudui = Tudui() output = tudui(input) print(output)
-
-
Sigmoid
-
-
class Tudui(nn.Module): def __init__(self): super(Tudui, self).__init__() self.sigmoid1 = Sigmoid() #inplace默认为False,input处理完后仍为原值 def forward(self,input): output = self.sigmoid1(input) return output
-
-
-
知识点5:神经网络 - 线性层及其他层介绍
-
Linear Layers
-
-
-
#flatten摊平,变成一行 # Example >>> t = torch.tensor([[[1, 2], [3, 4]], [[5, 6], [7, 8]]]) #3个中括号,所以是3维的 >>> torch.flatten(t) #摊平 tensor([1, 2, 3, 4, 5, 6, 7, 8]) >>> torch.flatten(t, start_dim=1) #变为1行 tensor([[1, 2, 3, 4], [5, 6, 7, 8]])
-
-
-
知识点6:损失函数
-
torch.nn 里的 loss function 衡量误差,在使用过程中根据需求使用,注意输入形状和输出形状即可
loss 衡量实际神经网络输出 output 与真实想要结果 target 的差距,越小越好
作用:
计算实际输出和目标之间的差距 为我们更新输出提供一定的依据(反向传播):给每一个卷积核中的参数提供了梯度 grad,采用反向传播时,每一个要更新的参数都会计算出对应的梯度,优化过程中根据梯度对参数进行优化,最终达到整个 loss 进行降低的目的
-
L1LOSS
-
-
#input:(N,*),N是batch_size,即有多少个数据;*可以是任意维度 CLASS torch.nn.L1Loss(size_average=None, reduce=None, reduction='mean')
-
loss = L1Loss() #求和 #loss = L1Loss(reduction='sum') result = loss(inputs,targets)
-
-
MSELOSS(均方误差)
-
-
#nput:(N,*)N是batch_size,即有多少个数据;*可以是任意维度 CLASS torch.nn.MSELoss(size_average=None, reduce=None, reduction='mean')
-
loss_mse = nn.MSELoss() result_mse = loss_mse(inputs,targets)
-
-
CROSSENTROPYLOSS(交叉熵):适用于训练分类问题,有C个类别
-
-
x = torch.tensor([0.1,0.2,0.3]) y = torch.tensor([1]) x = torch.reshape(x,(1,3)) loss_cross = nn.CrossEntropyLoss() result_cross = loss_cross(x,y) print(result_cross)
-
-
-
知识点7:backward 反向传播
-
计算出每一个节点参数的梯度
在上述代码后加一行:
result_loss.backward() # backward反向传播,是对result_loss,而不是对loss
-
-
知识点8:优化器
-
当使用损失函数时,可以调用损失函数的 backward,得到反向传播,反向传播可以求出每个需要调节的参数对应的梯度,有了梯度就可以利用优化器,优化器根据梯度对参数进行调整,以达到整体误差降低的目的
-
# Example: # SGD为构造优化器的算法,Stochastic Gradient Descent 随机梯度下降 #构造 optimizer = optim.SGD(model.parameters(), lr=0.01, momentum=0.9) #模型参数、学习速率、特定优化器算法中需要设定的参数 optimizer = optim.Adam([var1, var2], lr=0.0001) #调用优化器step方法 for input, target in dataset: optimizer.zero_grad() #把上一步训练的每个参数的梯度清零 output = model(input) loss = loss_fn(output, target) # 输出跟真实的target计算loss loss.backward() #调用反向传播得到每个要更新参数的梯度 optimizer.step() #每个参数根据上一步得到的梯度进行优化
-
算法
如Adadelta、Adagrad、Adam、RMSProp、SGD等等,不同算法前两个参数:params、lr 都是一致的,后面的参数不同
params为模型的参数、lr为学习速率(learning rate)
-
2. 搭建小实战和 Sequential 的使用
-
Sequential 的使用
-
# Using Sequential to create a small model. When `model` is run, # input will first be passed to `Conv2d(1,20,5)`. The output of # `Conv2d(1,20,5)` will be used as the input to the first # `ReLU`; the output of the first `ReLU` will become the input # for `Conv2d(20,64,5)`. Finally, the output of # `Conv2d(20,64,5)` will be used as input to the second `ReLU` model = nn.Sequential( nn.Conv2d(1,20,5), nn.ReLU(), nn.Conv2d(20,64,5), nn.ReLU() ) # Using Sequential with OrderedDict. This is functionally the # same as the above code model = nn.Sequential(OrderedDict([ ('conv1', nn.Conv2d(1,20,5)), ('relu1', nn.ReLU()), ('conv2', nn.Conv2d(20,64,5)), ('relu2', nn.ReLU()) ]))
-
-
对 CIFAR10 进行分类的简单神经网络
-
-
-
from torch import nn from torch.nn import Conv2d, MaxPool2d, Flatten, Linear class Tudui(nn.Module): def __init__(self): super(Tudui, self).__init__() self.conv1 = Conv2d(in_channels=3, out_channels=32, kernel_size=5, padding=2) #第一个卷积 self.maxpool1 = MaxPool2d(kernel_size=2) #池化 self.conv2 = Conv2d(32,32,5,padding=2) #维持尺寸不变,所以padding仍为2 self.maxpool2 = MaxPool2d(2) self.conv3 = Conv2d(32,64,5,padding=2) self.maxpool3 = MaxPool2d(2) self.flatten = Flatten() #展平为64x4x4=1024个数据 # 经过两个线性层:第一个线性层(1024为in_features,64为out_features)、第二个线性层(64为in_features,10为out_features) self.linear1 = Linear(1024,64) self.linear2 = Linear(64,10) #10为10个类别,若预测的是概率,则取最大概率对应的类别,为该图片网络预测到的类别 def forward(self,x): #x为input 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) return x tudui = Tudui() print(tudui)
-
3. 网络模型的保存与读取
-
两种方式保存模型
-
import torch import torchvision.models vgg16 = torchvision.models.vgg16(pretrained=False) # 网络中模型的参数是没有经过训练的、初始化的参数 #方式1:不仅保存了网络模型的结构,也保存了网络模型的参数 torch.save(vgg16,"vgg16_method1.pth") #方式2:网络模型的参数保存为字典,不保存网络模型的结构(官方推荐的保存方式,用的空间小) torch.save(vgg16.state_dict(),"vgg16_method2.pth")
-
-
两种方式加载模型
-
#方式1:对应保存方式1,打印出的是网络模型的结构 model = torch.load("vgg16_method1.pth",) print(model) # 打印出的只是模型的结构,其实它的参数也被保存下来了 #方式2:对应保存方式2,打印出的是参数的字典形式 model = torch.load("vgg16_method2.pth") print(model)
-
-
用方式1保存的话,加载时要让程序能够访问到其定义模型的一种方式
-
需要将 model_save.py 中的网络结构复制到 model_load.py 中,即下列代码需要复制到 model_load.py 中(为了确保加载的网络模型是想要的网络模型)
-
但是不需要创建了,即在 model_load.py 中不需要写:
tudui = Tudui()
-
-
4.使用GPU训练的方式
-
-
-
-
device = torch.device("cuda" if torch.cuda.is_available() else "cpu")
4.复现CIFAR10模型
import torch import torchvision.datasets from torch.utils.tensorboard import SummaryWriter from torch import nn from torch.utils.data import DataLoader # 准备数据集,CIFAR10 数据集是PIL Image,要转换为tensor数据类型 train_data = torchvision.datasets.CIFAR10(root="dataset", train=True, transform=torchvision.transforms.ToTensor(), download=True) test_data = torchvision.datasets.CIFAR10(root="dataset", train=False, transform=torchvision.transforms.ToTensor(), download=True) # 看一下训练数据集和测试数据集都有多少张(如何获得数据集的长度) train_data_size = len(train_data) # length 长度 test_data_size = len(test_data) # 如果train_data_size=10,那么打印出的字符串为:训练数据集的长度为:10 print("训练数据集的长度为:{}".format(train_data_size)) # 字符串格式化,把format中的变量替换{} print("测试数据集的长度为:{}".format(test_data_size)) # 利用 DataLoader 来加载数据集 train_dataloader = DataLoader(train_data, batch_size=64) test_dataloader = DataLoader(test_data, batch_size=64) # 创建网络模型 class Tudui(nn.Module): def __init__(self): super(Tudui, self).__init__()#继承父类 self.model = nn.Sequential( nn.Conv2d(3, 32, 5, 1, 2), nn.MaxPool2d(2), nn.Conv2d(32, 32, 5, 1, 2), nn.MaxPool2d(2), nn.Conv2d(32, 64, 5, 1, 2), nn.MaxPool2d(2), nn.Flatten(), nn.Linear(64 * 4 * 4, 64), nn.Linear(64, 10) ) def forward(self, x): x = self.model(x) return x tudui = Tudui() if torch.cuda.is_available(): tudui = tudui.cuda() # 创建损失函数 loss_fn = nn.CrossEntropyLoss() # 分类问题可以用交叉熵 if torch.cuda.is_available(): loss_fn = loss_fn.cuda() # 定义优化器 learning_rate = 0.01 # 另一写法:1e-2,即1x 10^(-2)=0.01 optimizer = torch.optim.SGD(tudui.parameters(), lr=learning_rate) # SGD 随机梯度下降 # 设置训练网络的一些参数 total_train_step = 0 # 记录训练次数 total_test_step = 0 # 记录测试次数 epoch = 100 # 训练轮数 # 添加tensorboard writer = SummaryWriter("logs_train_common") for i in range(epoch): print("----------第{}轮训练开始-----------".format(i + 1)) # i从0-9 # 训练步骤开始 for data in train_dataloader: imgs, targets = data if torch.cuda.is_available(): imgs = imgs.cuda() targets = targets.cuda() outputs = tudui(imgs) loss = loss_fn(outputs, targets) # 优化器优化模型 optimizer.zero_grad() # 首先要梯度清零 loss.backward() # 反向传播得到每一个参数节点的梯度 optimizer.step() # 对参数进行优化 total_train_step += 1 if total_train_step % 100 == 0: # 逢百才打印记录 print("训练次数:{},loss:{}".format(total_train_step, loss.item())) writer.add_scalar("train_loss_100", loss.item(), total_train_step) # 测试步骤开始 tudui.eval() total_test_loss = 0 total_accuracy = 0 with torch.no_grad(): # 无梯度,不进行调优 for data in test_dataloader: imgs, targets = data if torch.cuda.is_available(): imgs = imgs.cuda() targets = targets.cuda() outputs = tudui(imgs) loss = loss_fn(outputs, targets) # 该loss为部分数据在网络模型上的损失,为tensor数据类型 # 求整体测试数据集上的误差或正确率 total_test_loss = total_test_loss + loss.item() # loss为tensor数据类型,而total_test_loss为普通数字 # 求整体测试数据集上的误差或正确率 accuracy = (outputs.argmax(1) == targets).sum() # 1:横向比较,==:True或False,sum:计算True或False个数 total_accuracy = total_accuracy + accuracy print("整体测试集上的Loss:{}".format(total_test_loss)) print("整体测试集上的正确率:{}".format(total_accuracy / test_data_size)) writer.add_scalar("test_loss_100", total_test_loss, total_test_step) writer.add_scalar("test_accuracy_100", total_accuracy / test_data_size, total_test_step) total_test_step += 1 torch.save(tudui, "tudui_{}.pth".format(i)) # 每一轮保存一个结果 print("模型已保存") writer.close()