人工智能——卷积神经网络自定义模型全流程初识
用卷积神经网络,自行编写一个模型,实现模型的训练,验证,测试
主要步骤:
1、先自定义一个卷积神经网路模型
2、对模型进行训练
3、模型验证
4、测试
一、定义模型
我们需要定义一个多层的卷积神经网络,先定义一层卷积,对其使用激活函数激活,然后进行池化操作。多定义几层后进行全连接层定义,将卷积后得到的局部特征图像传入全连接神经网路进行整体识别。
代码:
#定义一个多层的卷积神经网络
import torch
import torch.nn as nnclass NumberModel(nn.Module):def __init__(self):super(NumberModel, self).__init__()#调用父类的构造函数#进行第一层卷积、激活、池化self.conv1 = nn.Sequential(nn.Conv2d(in_channels=1,out_channels=6,kernel_size=5,stride=1,),nn.ReLU(),)self.pool1 = nn.AdaptiveMaxPool2d(output_size=14)#进行第二层卷积、激活、池化self.conv2 = nn.Sequential(nn.Conv2d(in_channels=6,out_channels=16,kernel_size=5,stride=1,),nn.ReLU(),)self.pool2 = nn.AdaptiveMaxPool2d(output_size=5)#全连接层self.fc1 = nn.Sequential(nn.Linear(5*5*16,120),nn.ReLU(),)self.fc2 = nn.Sequential(nn.Linear(120,84),nn.ReLU(),)self.fc3 = nn.Linear(84,10)def forward(self, x):#卷积层、池化层前向传播x = self.conv1(x)x = self.pool1(x)x =self.conv2(x)x = self.pool2(x)#将卷积池化后得到的(N,C,H,W)转为全连接需要的二维张量(N,in_features)x = x.view(x.shape[0],-1)#x.shape[0]为batch_size,-1表示自动计算实际上就是把C*H*W计算一张图像上的像素点个数#全连接层前向传播x = self.fc1(x)x = self.fc2(x)out = self.fc3(x)return out
二、训练模型(难点!)
1、训练模型前,一般需要对数据进行数据预处理和数据增强 -----> transforms.Compose
2、读入数据
3、对数据进行加载 ——> DataLoader
DataLoader可以将数据集(Dataset)包装成一个可迭代的对象,方便在训练时按批次加载数据,还支持多进程加速、数据打乱等功能
经过
DataLoader
输出的对象是批次数据(batch data),通常是一个元组。在绝大多数情况下(例如处理图像、文本等常见数据),DataLoader
每次迭代会返回一个包含输入数据(data) 和标签(target) 的元组(data, target):
data
:批次输入数据,形状为[batch_size, ...]
(...
表示数据本身的维度,如图像的[通道数, 高, 宽]
,文本的[序列长度]
等),类型通常是torch.Tensor
。- target:批次标签数据,形状为
[batch_size]
,类型通常是torch.Tensor
(整数型,对应分类任务的类别索引)
4、定义设备,使用GPU运行还是CPU运行 ——> torch.device
5、用优化器优化 ——> opt.Adam
6、定义损失函数(交叉熵损失函数)
7、定义训练轮次
8、训练
1、理清楚循环步骤
总共要训练epochs轮,所以要进行循环
每一个轮次当中,又要对多个批次的数据进行处理——>因此一轮中对数据集不同批次也要进行循环。经过DataLoader方法后,整个数据集划分为了多个批次,每次迭代返回(数据, 标签)。
处理时,用enumerate函数,给DataLoader方法返回的元组(数据, 标签)元素加上序号。
例如:
fruits = ['苹果', '香蕉', '橘子'] for idx, fruit in enumerate(fruits):print(idx, fruit)输出: 0 苹果 1 香蕉 2 橘子
在搞清楚怎么循环处理后,对每个批次进行处理
2、首先是训练、前向传播,将数据传给model
3、将输出output(预测)和标签target传给损失函数计算该批次下的损失值
4、因为我们计算的是整个数据集平均的损失值,也就是每个批次的损失值之和除以所有数据,因此我们要将每个批次得到的损失值累加——>loss = loss_fn(output, targets),loss_total += loss
5、计算准确率,首先要得到每个批次预测准确的个数acc,然后累计所有批次预测准确的个数acc_total,然后用所有批次预测准确的累计个数除以所有数据的个数,就可以得到数据集平均准确值
6、acc怎么算?用这个方法:
pre = output.argmax(dim=1)
acc = torch.sum(pre == targets)
7、acc_total累计总的正确样本数
torch.argmax(..., dim=1)
在干嘛?假如一个批次有32张图片,想办法取到每张图片在不同类别下面得分最高的索引——
torch.argmax(..., dim=1)
argmax
是 “取最大值的索引”。结合dim=1
,就是对每张图片的 10 个得分取最大值所在的位置(即类别索引)
- 第 1 张图的得分中,最大值是 3.2(对应索引 3)→ 预测为数字 3;
- 第 2 张图的得分中,最大值是 4.1(对应索引 4)→ 预测为数字 4;
因此
pre
的结果是:tensor([3, 4]) # 第1张图预测为3,第2张图预测为4
pre
的形状是(N,)
(即(32,)
当batch_size=32
时),每个元素是对应图片的预测类别。
torch.sum(pre == target)
和torch.sum(pre == target)在干嘛
:
pre
是模型预测的类别(如[3,4]
),target
是真实标签(如[3,5]
)。pre == target
会逐元素比较,返回布尔值
sum
会把布尔值转为数字(True=1
,False=0
)并求和,结果是 “当前批次中预测正确的样本数量”
print(f"{epoch + 1}/{epochs}:该轮次平均损失值:loss_total / len(train_dataset),该轮次平均准确率:acc_total / len(train_dataset)")
该轮次平均损失值:loss_total / len(train_dataset)
该轮次平均准确率:acc_total / len(train_dataset)")
9、保存模型——>torch.save(model.state_dict(), "输入路径")
#定义模型后进行模型的训练
import torch
import torch.nn as nn
import torch.optim as opt
from torch.utils.data import DataLoader
from torchvision import transforms
from torchvision.datasets import MNIST
from model import NumberModeltransform = transforms.Compose([transforms.ToTensor(),transforms.Normalize((0.5,), (0.5,)),#均值和标准差transforms.Resize((32, 32)),#随机旋转transforms.RandomRotation(30),#随机水平翻转transforms.RandomHorizontalFlip(p=0.5),#随机垂直翻转transforms.RandomVerticalFlip(p=0.5),#随机裁剪transforms.RandomCrop(32, padding=4),
])
#读取数据集
train_dataset = MNIST(root='../data/MNIST',train=True,transform=transform,download=False
)
#加载数据集
data_loader = DataLoader(train_dataset,batch_size=32,shuffle=True,
)
#定义设备
device = torch.device('cuda' if torch.cuda.is_available() else 'cpu')
model = NumberModel().to(device)
#定义优化器
optimizer = opt.Adam(model.parameters(), lr=0.001)
#轮次
epochs = 10
#损失函数
loss_fn = nn.CrossEntropyLoss(reduction="sum")#对当前批次的所有样本的损失求和model.train()
model_path = '../weight/model1.pth'#训练
for epoch in range(epochs):acc_total = 0loss_total = 0for batch_idx, (data,target) in enumerate(data_loader):data, targets = data.to(device), targets.to(device)output = model(data)loss = loss_fn(output, targets)pre = output.argmax(dim=1)acc = torch.sum(pre == targets)acc_total += accloss_total += loss#反向传播optimizer.zero_grad()loss.backward()optimizer.step()print(f"{epoch + 1}/{epochs}:该轮次平均损失值:loss_total / len(train_dataset),该轮次平均准确率:acc_total / len(train_dataset)")#保存模型
torch.save(model.state_dict(), model_path)
print("保存模型成功!")
三、验证
前面步骤与训练差不多,也是:
1、训练模型前,一般需要对数据进行数据预处理和数据增强 -----> transforms.Compose
2、读入数据
3、对数据进行加载 ——> DataLoader
4、定义设备,使用GPU运行还是CPU运行 ——> torch.device
5、这里就不用再定义轮次、优化器什么的了,因为已经训练好模型的参数了,只需要将模型训练好的模型权重参数导入即可
model_path="../weight/model.pth"
model.load_state_dict(torch.load(model_path))
6、验证——>model.eval()
7、在进行训练前,关闭梯度计算,因为已经导入了训练好的模型——>with torch.no_grad():
8、计算测试的准确率(和验证类似,只是不再需要关注损失值)
9、打印总的准确率:acc_total.item()/len(val_data)
代码:
#模型验证
import torch
import torch.nn as nn
import torchvision.transforms as transforms
from torch.utils.data import DataLoader
from torchvision.datasets import MNIST
from model1 import NumberModel
#数据预处理
transform1 = transforms.Compose([transforms.ToTensor(),transforms.Normalize((0.1307,), (0.3081,))
])
#数据集
val_data = MNIST(root='../data/MNIST', train=False, transform=transform1, download=False)#加载数据集
val_loader = DataLoader(dataset=val_data,batch_size=32,shuffle=False
)
device = torch.device('cuda' if torch.cuda.is_available() else 'cpu')
model = NumberModel()
#加载预训练好的权重参数
model_path="../weight/model.pth"
model.load_state_dict(torch.load(model_path))model.to(device)
#验证
model.eval()
#关闭梯度计算
with torch.no_grad():acc_total = 0for idx,(data,target) in enumerate(val_loader):#数据移动到设备data, target = data.to(device), target.to(device)#用模型训练输出类别预测output = model(data)#预测结果,output返回的是(N,10)N表示图片数,10 对应 MNIST 的 10 个类别的得分,# dim取1就是取10这个维度,经过argmax后得到得分值最大得到的索引值,也就是预测的类别pre = torch.argmax(output, dim=1)#利用布尔运算,判断torch.sum(pre==target)acc_total += torch.sum(pre==target)print(f'总准确率为:{acc_total.item()/len(val_data):.3f}')
四、预测
1、先把训练好的模型加载进来
2、加载我们需要预测的图片
3、在预测前对图片进行一个预处理:用cv2读出来的图片是数组类型,且维度不一样,所以要先将nmupy转为张量,然后再升维到和(N,C,H,W)一直
4、将转为张量的图片放入模型进行预测,打印预测得分最大的索引
from model import NumberModel
import torch
import numpy as np
import cv2 as cv# 加载模型
model = NumberModel()
model.load_state_dict(torch.load('../weight/model.pth'))# 加载图片
img = cv.imread('../data/image.png')
# 转为灰度图
img = cv.cvtColor(img, cv.COLOR_BGR2GRAY)
print(img.shape)
# 转为张量,并升维
img = torch.tensor(img, dtype=torch.float32).unsqueeze(0).unsqueeze(0)
# 预测
out = model(img)
print(out.argmax(dim=1))