人工智能——卷积神经网络自定义模型全流程初识

用卷积神经网络,自行编写一个模型,实现模型的训练,验证,测试

主要步骤:

1、先自定义一个卷积神经网路模型

2、对模型进行训练

3、模型验证

4、测试

一、定义模型

我们需要定义一个多层的卷积神经网络,先定义一层卷积,对其使用激活函数激活,然后进行池化操作。多定义几层后进行全连接层定义,将卷积后得到的局部特征图像传入全连接神经网路进行整体识别。

代码:

python 复制代码
#定义一个多层的卷积神经网络
import torch
import torch.nn as nn

class 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方法返回的元组(数据, 标签)元素加上序号。

例如:

python 复制代码
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)

|-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
| 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=1False=0)并求和,结果是 "当前批次中预测正确的样本数量" |

7、acc_total累计总的正确样本数

|-----------------------------------------------------------------------------------------------------------------|
| 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(), "输入路径")

python 复制代码
#定义模型后进行模型的训练
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 NumberModel

transform = 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 = 0
    loss_total = 0

    for 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 += acc
        loss_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、这里就不用再定义轮次、优化器什么的了,因为已经训练好模型的参数了,只需要将模型训练好的模型权重参数导入即可

python 复制代码
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)

代码:

python 复制代码
#模型验证
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 = 0
    for 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、将转为张量的图片放入模型进行预测,打印预测得分最大的索引

python 复制代码
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))