搭建ALextNet网络,这里我们采用和上一篇搭建DNN神经网络不同的方式,采用管道流的方式搭建。集体网络结构如下:
卷积层5层:
第一层:
(0): Conv2d(3,48,kernel_size=(11,11),stride=(4,4),padding=(2,2),bias=False)
(1): BatchNorm2d(48,eps=1e-05,momentum=0.1,affine=True,track_running_stats=True)
(2): ReLu(inplace=True)
(3): MaxPool2d(kernel_size=3,stride=2,padding=0,dilation=1,ceil_mode=False)
第二层:
(4): Conv2d(48,128,kernel_size=(5,5), stride=(1,1),padding=(2,2))
(5): BatchNorm2d(128,eps=1e-05,momentum=0.1,affine=True,track_running_stats=True)
(6): ReLU( inplace=True)
(7):MaxPool2d(kernel_size=3,stride=2,padding=0, dilation=1, ceil_mode=False)
第三层:
(8): Conv2d(128,192,kernel_size=(3,3),stride=(1,1), padding=(1,1))
(9): BatchNorm2d(192,eps=1e-05,momentum=0.1,affine=True,track_running_stats=True)
(10): ReLU( inplace=True)
第四层:
(11): Conv2d(192,192,kernel_size=(3,3),stride=(1,1),padding=(1,1))
(12): BatchNorm2d(192,eps=1e-05,momentum=0.1, affine=True,track_running_stats=True)
(13): ReLu( inplace=True)
第五层:
(14): Conv2d(192,128,kernel_size=(3,3),stride=(1,1), padding=(1,1))
(15): BatchNorm2d(128,eps=1e-05,momentum=0.1, affine=True,track_running_stats=True)
(16): ReLU(inplace=True)
(17):MaxPool2d(kernel_size=3,stride=2,padding=0, dilation=1, ceil_mode=False)
全连接层3层
第一层:
nn.Dropout(0.2),
nn.Linear(2048,2048)
nn.Sigmoid()
第二层:
nn.Dropout(0.3),
nn.Linear(2048,2048)
nn.Sigmoid()
第三层:
nn.Dropout()默认值为0.5
nn.Linear(2048,1000)
导入必要的包和模块
'''先导入必要的包'''
import torch
import json
from torchsummary import summary#查看参数个数的模块
torch.manual_seed(4)#设定随机数种子
# import warnings#这两个是处理警告的机制
# warnings.filterwarnings('ignore')
import torch.nn as nn#优化器包#搭建网络的层要用到,(卷积,全连接都在这里)
网络搭建代码实现如下:
'''
基本结构:
1、搭建网络
2、获取,加载数据
3、训练模型
4、测试模型
5、保存模型
6、模型部署
'''
'''搭建网络
1、所有网络结构都要继承Module类,必须继承
2、可以用dir(nn.module)查看类中的方法
3、搭建网络层级结构,有两种方式:
一种是在实例方法中定义好传递通道,在前馈传播的时候直接传递参数
一种是实例化方法中定义网络种类,在前馈传播的时候一一列出网络层
4、这里我们用第一种方式
定义两个通道,一个是特征提取器,一个是分类器
'''
class Axlenet(nn.Module):
#定义构造方法
def __init__(self,numclass):#numclass:分类的个数
#一定要继承上边的父类的构造方法
super(Axlenet, self).__init__()
#定义特征提取器(卷积层)
'''
在特征提取器内,需要定义卷积的具体结构了,包括:
1、卷积层(1输入通道数,2输出通道数,3卷积核大小,4步长 5.填充6、是否加入偏置项)
除了卷积层,还要有:
2、归一化层
3、激活
4、池化层
'''
# 图片的输入:H,W,C = 224*224*3
self.featrues = nn.Sequential(
# 第一层卷积:输入通道数=3,输出通道数 = 48 = 卷积核个数,卷积核大小 = (11,11),步长 = 4,padding=2,偏置项=True
nn.Conv2d(3, 48, kernel_size=(11, 11), stride=(4, 4), padding=2, bias=True), # 卷积
nn.BatchNorm2d(48), # 批归一化
nn.ReLU(inplace=True), # 激活,这里的True(覆盖)或者False作用是决定激活之前的值是被保留还是被激活之后的值覆盖掉
nn.MaxPool2d(kernel_size=3, stride=2, padding=0, dilation=1, ceil_mode=False), # 池化dilation=1——>隔空取
nn.Conv2d(48, 128, kernel_size=(5, 5), stride=(1, 1), padding=2,bias=True), # 卷积
nn.BatchNorm2d(128), # 批归一化
nn.ReLU(inplace=True), # 激活
nn.MaxPool2d(kernel_size=3, stride=(2, 2), padding=0, dilation=1, ceil_mode=False), # 池化
nn.Conv2d(128, 192, kernel_size=(3, 3), stride=(1, 1), padding=1,bias=True), # 卷积
nn.BatchNorm2d(192), # 批归一化
nn.ReLU(inplace=True), # 激活
nn.Conv2d(192, 192, kernel_size=(3, 3), stride=(1, 1), padding=1,bias=True), # 卷积
nn.BatchNorm2d(192), # 批归一化
nn.ReLU(inplace=True), # 激活
nn.Conv2d(192, 128, kernel_size=(3, 3), stride=(1, 1), padding=1,bias=True), # 卷积
nn.BatchNorm2d(128), # 批归一化
nn.ReLU(inplace=True), # 激活
nn.MaxPool2d(kernel_size=3, stride=2, padding=0, dilation=1, ceil_mode=False) # 池化
)
#定义分类器(全连接层)
self.fc = nn.Sequential(
nn.Dropout(0.2), # Dropout以一定比例抑制神经元的活跃度
nn.Linear(4608,2048),#输入,输出
nn.Sigmoid(),
nn.Dropout(0.2), # Dropout以一定比例抑制神经元的活跃度
nn.Linear(2048,2048),
nn.Sigmoid(),
nn.Linear(2048,numclass)
)
#调用参数初始化方法
self.init_weight()
"""参数初始化
卷积层参数,全连接层参数,
"""
def init_weight(self):
for m in self.modules():
if isinstance(m,nn.Conv2d):#卷积层初始化
nn.init.kaiming_normal_(m.weight)#kaiming初始化权重
if m.bias is not False:
nn.init.constant_(m.bias,1)#常数初始化偏置项
elif isinstance(m,nn.Linear):#全连接层初始化
nn.init.xavier_normal_(m.weight)#正态分布初始化
nn.init.constant_(m.bias, 1)
"""前向传播"""
def forward(self,x):
x = self.featrues(x)
#卷积完之后,进入全连接层之前要进行拉平操作
x = torch.flatten(x,1)#第一个参数是拉平对象,第二个参数是开始拉平的维度
out = self.fc(x)
return out
#输出的就是一个1*numclass的
"""
到这步我们先测试一下上边的模型能不能跑通,有任何一层的参数定义错误都不能跑通
"""
# if __name__ == "__main__":
# #制造一个数据(B,C,H,W)思维:批次,图像通道数,高,宽
# image = torch.ones((3,3,224,224))
# #实例化一个网络,返回:批次*numcalss维度
# net = Axlenet(numclass=10)
# #传入数据
# net(image)
# print(net(image))
# summary(net,(3,224,224),3,device='cpu')#参数(网络,输入,批次,cpu还是GPU)传入网络,查看参数
训练数据使用同一个开源的数据集cifar10数据集
使用datasize下载数据源
CIFAR-10是由Hinton的学生 Alex Krizhevsky和llya Sutskever整理的一个用于识别普适物体的小型数据集。一共包含10个类别的RGB彩色图片:飞机 ( alane ) 、汽车 ( automobile ) 、鸟类( bird ) 、猫( cat )、鹿( deer ) 、狗 ( dog )、蛙类(frog)、马( horse )、船( ship )和卡车(truck )。图片的尺寸为32×32,数据集中一共有50000张训练圄片和10000张测试图片。CIFAR-10的图片样例如图所示。
这个数据集中的图片数据是3232的,所以要做数据的预处理,把图片处理成224224的结构,才能把数据放到网络中去。
"""
1、我们先造一批假数据,用来训练模型,把模型跑通保证数据可以进入模型
用快速的方法验证程序
2、下载数据
3、调整成模型适合的数据格式
4、加入模型训练模型
1、训练
2、测试
"""
"""
在训练的时候我们更新梯度的时候会用到优化器,这里的优化器我们用SGD+动量的形式
普通的SGD方式;
theat = theta - lr*grad
SGD+动量:在grad上加上一个记忆参数,用来传递上一次grad的信息
初始化:
v0
monentum = 0.9 一般都设置成0.9
theta0
第一次迭代:
v1 = v0+monentum*grade
theta1 = theta0 - lr*v1
第二次迭代:
v2 = v2+monentum*grade
theta2 = theta1 - lr*v2
是这样迭代更新的
"""
"""
设定随机数种子,用于初始化操作,如果不设定随机数种子那么每次初始化的结果都不一样,训练模型就导致每次训练的结果都不同
"""
#导包
import os
os.environ["KMP_DUPLICATE_LIB_OK"] = "TRUE"
import torch
import torch.nn as nn
from Alexnet import Axlenet#导入我们之前构造好的模型
torch.manual_seed(4)#设定随机数种子
#torch.cuda.manual_seed_all(4)#为GPU设置随机数种子
from datetime import datetime
from torchvision import datasets,transforms#下载、转换数据
from torch.utils.data import DataLoader#加载数据到DataLoader中
from matplotlib import pyplot as plt#画图
import torch.optim as opitm#优化器包
import pickle#保存pickle文件
if __name__ == "__main__":
# #构造一批假的训练数据(这个数据的格式要和模型要求的格式一样),用torch.rand(),4维(B,C,H,W)
# train_image = torch.rand((4,3,224,224))#训练数据
# train_y = torch.LongTensor([1,0,2,1])#训练数据的标签值,4个批次,则有4个标签值
# test_image = torch.rand((4,3,224,224))#测试数据
# test_y = torch.LongTensor([1,0,2,1])#测试数据的标签值
"""1、下载+转换+加载 数据"""
"""1.1定义一个通道,下载的时候用这个模块对图像进行变形转换,转换成适合模型的Tensor类型"""
transform = transforms.Compose([
#transforms.RandomResizedCrop(244),#裁剪
#transforms.RandomHorizontalFlip(),#翻转,这两个都是在做数据增强
transforms.Resize(224),#图像大小转换
transforms.ToTensor()#转换成Tensor类型
#transforms.Normalize((0.5,0.5,0.5),(0.5,0.5,0.5))#对数据做归一化
])
"""1.2下载数据"""
#datasets.ImageFolder()#如果用我们自己的文件夹的数据就用这个,这里我们用Cifar10的数据
train_data = datasets.CIFAR10('E:\深度学习\AlexNet\imgdata', train=True, transform=transform, target_transform=None,
download=True) # 添加下载的路径,只要训练数据
test_data = datasets.CIFAR10('E:\深度学习\AlexNet\imgdata', train=False, transform=transform, target_transform=None,
download=True)
"""1.3、加载下载好的数据到DataLoader中,
可以打乱数据,可以分批次加载,多线程加载
参数:批次:batch_size,是否打乱shuffle,是否丢弃最后不够一个批次的数据drop_last
"""
train_dataloader = DataLoader(train_data,batch_size=128,shuffle=True,drop_last=True)
test_dataloader = DataLoader(test_data,batch_size=100,shuffle=True,drop_last=False)
"""1.4保存索引和类别对应的字典映射关系"""
idx_to_class = {value:key for key,value in train_data.class_to_idx.items()}
with open(r'image_label.pkl','wb') as f:
pickle.dump(idx_to_class,f)
"""2、模型实例化"""
net = Axlenet(numclass=10)
"""
3、有了数据,首先要判断是在CPU还是在GPU上进行训练
如果有GPU的话),数据就会加载到GPU中,
"""
device = 'cuda' if torch.cuda.is_available() else 'cpu'#判断是否有GPU
net = net.to(device)#加载模型到GPU或CPU中
print(f"模型在{device}中训练......")
"""4、定义损失函数"""
loss_function = nn.CrossEntropyLoss()#用交叉熵
"""5、定义优化器
必要参数:模型参数,学习率lr,动量
"""
lr = 0.001#定义学习率
momentum = 0.9#定义动量
optimizer = opitm.SGD(net.parameters(),lr=lr, momentum=momentum)
"""6、定义训练的轮次
一轮就是把训练数据(一个批次)都训练一遍
"""
max_epoch = 10
"""8、定义两个个记录损失的列表,用于记录每轮次训练、测试的损失、测试准确率"""
train_loss = []
test_loss = []
test_acc = []
# 定义变量,用于选择最好模型
best_acc = 0.000
"""7、训练数据
这里我们采用训练一轮测试一次的策略
"""
for epoch in range(max_epoch):
train(train_dataloader,device,net,loss_function,optimizer)
test(test_dataloader,device,net)
"""
8、画损失折线图
"""
show_loss(train_loss)
show_loss(test_loss)
获取到数据以后,将数据放到GPU中进行训练,分批次迭代更新参数
同时训练的时候要评估模型效果,这里我们用准确率进行评估。
注意:训练的时候要加:net.train()目的是使dropout生效。
def train(train_dataloader,device,net,loss_function,optimizer):
net.train()#训练的时候要加上这个,目的是使得dropout,BN等生效,同时测试的时候要加net.eval()作用是使其不生效
train_epoch_loss = 0.000#定义变量,存储每轮次次的损失和
iter_cont = len(train_dataloader)#求一轮有多少批次(一轮迭代多少次)
#1、把数据放到GPU或者CPU中
#1.1先把数据for循环分成输入和类别目标
for index,(train_x,train_y) in enumerate(train_dataloader):
if index == 100:
break
train_x = train_x.to(device)
train_y = train_y.to(device)
#2、数据放到模型中进行前向传播,得到预测结果
y_predict = net(train_x)
#3、计算损失
loss = loss_function(y_predict,train_y)
#4、优化器梯度清零
optimizer.zero_grad()
#5、拿到损失进行反向传播,得到梯度
loss.backward()
#6、更新梯度
optimizer.step()
#7、记录损失
print(f'训练轮次:{epoch+1}\t批次:{index+1}\t训练误差:{loss.item()}')
train_epoch_loss += loss.item()
train_loss.append(train_epoch_loss/iter_cont)#一轮的平均误差=误差和/一轮迭代次数
测试的时候,只有正向评估,用训练好的模型对册数数据进行正向传播即可。
注意:训练的时候要加net.eval(),目的是使dropout失效。
"""
定义测试模型的方法
"""
def test(test_dataloader,device,net):
global best_acc
net.eval()
num = 0#定义预测正确的数量和
test_epoch_loss = 0.000
# 1、把数据放到GPU或者CPU中
for index,(test_x,test_y) in enumerate(test_dataloader):
# if index == 100:
# break
test_x = test_x.to(device)
test_y = test_y.to(device)
# 2、数据放到模型中进行前向传播,得到预测结果
y_predict = net(test_x)
# 3、返回概率再大的值的索引
y_predict_index = torch.argmax(y_predict,1)#torch.argmax(目标,维度)
# 4、记录测试误差
test_loss_ = loss_function(y_predict,test_y)
test_epoch_loss += test_loss_.item()
# 5计算准确率
# 利用列表推导式求相同位置元素相同的个数再除以总个数
# 一个批次预测正确的数量
predict_correct_num = sum([1 for index01 in range(len(test_y)) if test_y[index01] == y_predict_index[index01]])
num += predict_correct_num
acc = num/10000#可以再函数外用len(test_data)求出
test_acc.append(acc)
test_loss.append(test_epoch_loss/len(test_dataloader))#测试误差
print("准确率:",acc)
"""保存模型"""
if test_acc[-1] > best_acc:
best_acc = test_acc[-1]
torch.save(net.state_dict(),f'best_model.pth')
保存模型的方法加到验证模型的方法里,因为我们肯定要验证数据集最好的模型进行保存
if test_acc[-1] > best_acc:
best_acc = test_acc[-1]
torch.save(net.state_dict(),f'best_model.pth')
'''
def show_loss(train_loss):
plt.plot(range(len(train_loss)),train_loss)
plt.show()
#导包
import torch
import torch.nn as nn
from Alexnet import Axlenet#导入我们之前构造好的模型
from torchvision import datasets,transforms#下载、转换数据
from torch.utils.data import DataLoader#加载数据到DataLoader中
from PIL import Image
import pickle
"""1、读取-类别文件"""
with open('image_label.pkl','rb') as f:
idx_to_class = pickle.load(f)
test_image = r'1.jpg'#图片路径
"""2、读取图片"""
image = Image.open(test_image)
"""3、图片格式处理成符合模型的格式"""
transform = transforms.Compose([
transforms.Resize((224,224)),#图像大小转换
transforms.ToTensor()#转换成Tensor类型
])
image = transform(image)
"""4、一张图片要进行扩围,因为没有批次这一维度"""
image = torch.unsqueeze(image,0)#扩充维度
"""5、加载模型+参数"""
net = Axlenet(10)#实例化模型
#torch.load('best_model.pth')#
net.load_state_dict(torch.load('best_model.pth'))#加载模型参数
device = 'cuda' if torch.cuda.is_available() else 'cpu'
"""6、图片放入模型"""
result = net(image)#图像加入模型
"""7、根据预测值对应输出类别"""
result_index = torch.argmax(result,1)
predict = idx_to_class[result_index.item()]#变标量
print(predict)