训练集结果:
19 Train Loss: 0.0019 Train Acc: 0.9991
19 Val Loss: 0.0005 Val Acc: 1.0000
训练和验证耗费的时间14m40s
测试结果
单个种类的识别率
但是输入单张图片进行检测,结果是可以正确检测出来的
这是为什么呢?好困惑,有懂得大佬,还希望能帮帮忙!
起初以为是测试集和训练集的名称一样的原因,进行改名称之后,重新检测,结果还是没变。
完整代码
模型代码,以LeNet-5训练为例
#导入库,约定俗称的一些库
import torch
from torch import nn ##导入nn,表示神经网络中的一些层,比如卷积层,池化层,激活函数等,之所以深度学习火的原因,不用太过于关注内部结构,就当成搭积木即可
from torchsummary import summary #summary展示模型的一个参数用的,前向传播的输入,输出特征图大小是多少
##约定俗称的定义网络的代码(类似于原材料)
class LeNet(nn.Module): #定义一个类,nn.Module这样就可以用nn里面所有的网络层了
def __init__(self): #先进行初始化,告诉模型我们需要具备的都有了,剩下的就是开始搭建模型了。
super(LeNet,self).__init__() ##7-9行是约定俗成的网络结构的命令行,值得注意class LeNet和super LeNet 保持一致。
##开始定义第一层,后面相当于按照规划使用所需的材料,输入层不算,尽可能按照网络结构的顺序来定义每一层,不要改池化和卷积层的顺序
#卷积层的名称可以自己去定义,以下面的c1为例,卷积函数用Conv2d(所需要传入的参数)
#in_channels=1,表示为输入数据的通道数,1表示输入的为灰度图,3表示输入的为彩色图,out_channels=6,表示输出的通道数,由卷积核数量决定
# kernel_size=5,代表卷积核的大小,padding=2,代表填充的大小,这里的步长为1,默认即可,不用去写
self.c1 = nn.Conv2d(in_channels=3,out_channels=6,kernel_size=5,padding=2)
#定义激活函数
self.sig = nn.Sigmoid()
#定义池化层,用的是平均池化,不改变通道数,只传入感受野大小,步长,填充等参数
self.s2 = nn.AvgPool2d(kernel_size=2,stride=2)
self.c3 = nn.Conv2d(in_channels=6,out_channels=16,kernel_size=5) #步长为1,默认就不写,默认填充表示为0
self.s4 = nn.AvgPool2d(kernel_size=2,stride=2)
#定义平展层
self.flatten = nn.Flatten()
#定义全连接层,在pytorch中全连接层表示为linear(需要传入的输入的数据大小和输出的大小,即输入为平展之后的400个,输出为120个神经元)
self.f5 = nn.Linear(400,120) #写具体的数字会自动弹出in_features:和out_features:)
self.f6 = nn.Linear(120,84)
self.f7 = nn.Linear(84,4)
##定义前向传播并进行调用工具,self是python的语法,会自动弹出,如果没有,就要考虑是否出现问题,x表示为输入
def forward(self,x):
#输入数据经过了第一层卷积和激活函数
x = self.sig(self.c1(x))
#通过池化层
x = self.s2(x)
x = self.sig(self.c3(x))
x = self.s4(x)
x = self.flatten(x)
x = self.f5(x)
x = self.f6(x)
x = self.f7(x)
return x
#主函数,搭建模型
if __name__=="__main__": #这里告诉要开始使用主函数了,这个冒号:不能少
#在有gpu的情况下,使用pytorch这个库来判别cuda是否激活了,如果激活了,返回cuda,如果没有则返回cpu
device = torch.device("cuda" if torch.cuda.is_available() else "cpu")
#模型实例化之后放到设备中
model = LeNet().to(device)
#打印模型的详细内容
print(summary(model,(1,28,28)))
模型训练代码
#加载数据,并进行可视化,loss,准确率等的展示
import copy
import time
import pandas as pd
import torch
from torchvision.datasets import ImageFolder #导入库,专门处理自己的数据集
from torchvision import transforms
import torch.utils.data as Data
import torch.nn as nn
import numpy as np
import matplotlib.pyplot as plt
#从model.py导入我们的模型
from model import LeNet
#训练集和验证集的定义,名称尽可能一目了然,用于处理训练集和验证集
def train_val_data_process():
# 相对路径,反斜杠,注意需要大写,前面有一个r,数据大小224*224,格式为ToTensor()
# 定义数据集的路径
ROOT_TRAIN = r'data\train'
normalize = transforms.Normalize([0.012, 0.024, 0.187], [0.008, 0.0178, 0.054])
# 定义数据集处理方法变量
train_transform = transforms.Compose([transforms.Resize((28, 28)), transforms.ToTensor()])
# 加载数据集,传入数据集的路径,及处理数据的方法
train_data = ImageFolder(ROOT_TRAIN, transform=train_transform)
#把数据集划分为训练集和验证集=8:2
train_data,val_data = Data.random_split(train_data,[round(0.8*len(train_data)),round(0.2*len(train_data))])
#先放入数据,一批次数量(数据量比较小,6G运存应该是够的),shuffle是否打乱数据,进程是8,电脑垃圾的话,就改小一点
train_dataloader = Data.DataLoader(dataset=train_data,
batch_size=32,
shuffle=True,
num_workers=2)
##最好训练和验证集的操作是一样的
val_dataloader = Data.DataLoader(dataset=val_data,
batch_size=32,
shuffle=True,
num_workers=2)
return train_dataloader , val_dataloader #返回训练集和验证集
#模型训练过程,传入模型,训练,验证集,训练的轮次
def train_model_process(model,train_dataloader,val_dataloader,num_epochs):
#设定训练所用到的设备,有GPU用GPU,没有GPU用CPU
#告诉设备情况,这一步用到了torch,就需要在前面进行导入,就是这么一个过程,一开始不知道都用那些库,写着需要了就进行导入
device = torch.device("cuda" if torch.cuda.is_available() else "cpu")
#使用Adam优化器,便于后续的参数进行更新的,lr表示为学习率,Adam可以理解是梯度下降法的一种优化改良
optimizer = torch.optim.Adam(model.parameters(),lr=0.001)
#定义损失函数,在分类问题中一般损失函数用交叉熵损失函数,回归一般用均方差损失函数,来利用损失值来更新参数w和b
criterion = nn.CrossEntropyLoss()
#将模型放入到训练设备中
model = model.to(device)
#复制当前模型的参数,w,b会有一个初始化的值,随着反向传播的进行,w,b在进行不断的更新。后续把 best_model_wts保存下来(也即是权重)后续测试时可以进行加载
best_model_wts = copy.deepcopy(model.state_dict())
##初始化参数
##最高准确度,初始化为0,后续会覆盖掉
best_acc = 0.0
#训练集损失值列表,改的地方
# 训练集损失值列表
train_loss_all = []
# 验证集损失值列表
val_loss_all = []
# 训练集精度值列表
train_acc_all = []
# 验证集精度值/准确度列表
val_acc_all = []
#保存当前时间,方便知道一轮会用时多久,time.time()不要自己全部手敲,会报错,就time.等弹出选择即可
since = time.time()
#利用循环进行训练,打印信息的阶段
for epoch in range(num_epochs):
print("Epoch{}/{}".format(epoch,num_epochs-1)) #减1的原因在于,是从0开始99结束,共100轮(如果以100为例情况下)
print("-"*10)
#初始化参数,方便每一轮的开始值都是0,从而可以计算每一轮的loss和准确率,需要计算平均loss值,就需要定义样本数量
#训练集损失函数
train_loss = 0.0
#训练集准确度
train_corrects = 0
# 验证集损失函数
val_loss = 0.0
# 验证集准确度
val_corrects = 0
#定义该轮次的训练集样本数量
train_num = 0
#验证集样本数量
val_num = 0
#取数据过程,模型和数据需要放在同一个设备,否则会出错
#对每一个mini-batch训练和计算
for step, (b_x, b_y) in enumerate(train_dataloader):
#将特征放入到训练设备中
b_x = b_x.to(device)
#将标签值放入到训练设备中
b_y = b_y.to(device)
#设置模型的训练模式
model.train()
#前向传播过程,输入为一个batch,输出为一个batch中对应的预测,output不是最终的结果而是一个向量
output = model(b_x)
#使用softmax取概率最大的作为标签,查找每一行中最大值对应的行标(类别)
pre_lab = torch.argmax(output,dim=1)
#利用模型的输出和标签来计算损失,不是通过softmax之后的结果和标签计算哦
loss = criterion(output,b_y)
#每一轮开始前将梯度初始化为0,防止梯度累计从而对参数更新产生干扰,只有这样每一轮结束之后才能更新参数w和b
optimizer.zero_grad()
#反向传播计算
loss.backward()
#根据网络反向传播的梯度信息来更新网络参数,以起到降低loss值函数计算值的作用,利用Adam优化器进行更新参数
optimizer.step()
#对损失函数进行累加
train_loss += loss.item() * b_x.size(0)
#如果预测正确,则准确度train_corrects+1
train_corrects += torch.sum(pre_lab == b_y.data)
#当前该轮次用于训练的样本数量
train_num += b_x.size(0)
#模型验证代码部分撰写,基本和训练过程是差不多的操作,是不参与模型的训练,单纯计算loss和准确度,没有反向传播过程
# 对每一个mini-batch训练和计算,直到最后一批次结束,意味着一轮结束。
for step, (b_x, b_y) in enumerate(val_dataloader):
#将特征放入到验证设备中
b_x = b_x.to(device)
#将标签值放入到验证设备中
b_y = b_y.to(device)
#设置模型的评估模式
model.eval()
#前向传播过程,输入为一个batch,输出为一个batch中对应的预测
output = model(b_x)
# 使用softmax取概率最大的作为标签,查找每一行中最大值对应的行标(类别)
pre_lab = torch.argmax(output, dim=1)
# 利用模型的输出和标签来计算损失,不是通过softmax之后的结果和标签计算哦
##相比较于训练过程,是没有反向传播,梯度更新,参数更新的过程。
loss = criterion(output, b_y)
#对损失函数进行累加,批次的平均loss*批次数量
val_loss += loss.item() * b_x.size(0)
#如果预测正确,则准确度train_corrects+1
val_corrects += torch.sum(pre_lab == b_y.data)
#当前用于评估/评估的样本数量
val_num += b_x.size(0)
#计算并保存每一次迭代(轮次epoch?)的平均loss和准确度,后续需要进行打印或者绘图
train_loss_all.append(train_loss / train_num)
#计算并保存训练集的准确率
train_acc_all.append(train_corrects.double().item() / train_num)
# 计算并保存验证集的loss值
val_loss_all.append(val_loss / val_num)
# 计算并保存验证集的准确率
val_acc_all.append(val_corrects.double().item() / val_num)
##将上述结果进行打印[-1]表示取当前轮次的数值,print缩进的话,把内部训练的每一次都会进行打印的。
print('{} Train Loss: {:.4f} Train Acc: {:.4f}'.format(epoch,train_loss_all[-1],train_acc_all[-1]))
print('{} Val Loss: {:.4f} Val Acc: {:.4f}'.format(epoch,val_loss_all[-1], val_acc_all[-1]))
#寻找最高准确度(当前准确度和最佳准确度进行比较,有点排序算法的感觉)
if val_acc_all[-1] > best_acc:
#保存当前的最高准确度
best_acc = val_acc_all[-1]
#在当前最高的准确度情况下,保存对应的模型权重
best_model_wts = copy.deepcopy(model.state_dict())
#训练耗费的时间
time_use = time.time() - since
print("训练和验证耗费的时间{:.0f}m{:.0f}s".format(time_use//60,time_use%60))
#选择最优参数
#加载最高准确率下的模型参数,并进行保存到一个文件地址,项目文件夹下产生一个best_model.pth,.pth是权重的独特后缀
#model.load_state_dict(best_model_wts),这行代码是没有用的。下面的两种保存权重方法都是可以的
torch.save(best_model_wts, 'F:/LeNet/best_model.pth')
#将训练的数值保存为一种文件格式,最重要的就是训练完之后,会保存最优模型的权重。
train_process = pd.DataFrame(data={"epoch":range(num_epochs),
"train_loss_all":train_loss_all,
"val_loss_all":val_loss_all,
"train_acc_all":train_acc_all,
"val_acc_all": val_acc_all})
return train_process
#2024年7月22日继续学习
##定义一个绘制loss和acc的绘制图的函数,将train_process作为输入传进去
def matplot_acc_loss(train_process):
plt.figure(figsize=(12,4)) #设置图形的大小
plt.subplot(1,2,1) #子图可以按照matlab的画法进行理解
plt.plot(train_process["epoch"], train_process.train_loss_all, 'ro-' , label = "train loss") #绘制训练集损失曲线,并加上曲线等设置
plt.plot(train_process["epoch"], train_process.val_loss_all, 'bs-', label="val loss") # 绘制验证集损失曲线,并加上曲线等设置
plt.legend() #标签
plt.xlabel("epoch")
plt.ylabel("loss")
plt.subplot(1, 2, 2) # 子图可以按照matlab的画法进行理解
plt.plot(train_process["epoch"], train_process.train_acc_all, 'ro-', label="train acc") # 绘制训练集损失曲线,并加上曲线等设置
plt.plot(train_process["epoch"], train_process.val_acc_all, 'bs-', label="val acc") # 绘制验证集损失曲线,并加上曲线等设置
plt.legend() # 图例
plt.xlabel("epoch")
plt.ylabel("acc")
plt.legend() # 图例
plt.show() #绘制
#对数据开始训练
#主函数,开始运行所有的代码 注意点1:双等号。2.要有冒号:
if __name__ == "__main__":
#将模型进行实例化,如果后续是别的模型呢?from model import LeNet,先进行搭建我们的model.py,进而将model导入进去,然后在下面进行实例化。将模型放到训练代码就可以了。有个别的模型可能数据的预处理需要改一点。
LeNet = LeNet()
#加载数据集,分为训练集和验证集
train_dataloader,val_dataloader = train_val_data_process()
#将训练集,验证集,模型,和训练次数放进训练代码中,
train_process = train_model_process(LeNet,train_dataloader,val_dataloader,20) #自己进行设定训练的次数,假如这里的训练次数是20轮,并保留中间值
##绘制损失和准确率的图形
matplot_acc_loss(train_process)
模型测试。模型推理及单个图片的识别
import torch #用不用都先导入进来
import torch.utils.data as Data #导入数据处理的库
from torchvision import transforms
from torchvision.datasets import ImageFolder #导入库,专门处理自己的数据集
from model import LeNet #导入需要测试的模型
from PIL import Image ##导入图片的一个库
##数据就处理好了。
##导入数据进行模型的测试,从模型训练的代码拿一部分,改改即可,batch_size=1,希望是一张一张的去进行测试,不需要什么进程,改为num_workers=0即可
def test_data_process():
##数据加载进来,transforms.Compose((transforms.Resize(size=28),大小设置按照模型的输入要求来
# 相对路径,反斜杠,注意需要大写,前面有一个r,数据大小224*224,格式为ToTensor()
# 定义数据集的路径
ROOT_TEST = r'data\test'
# 定义归一化的方法
normalize = transforms.Normalize([0.012, 0.024, 0.187], [0.008, 0.0178, 0.054])
# 定义数据集处理方法变量,大小224*224,ToTensor格式,并且进行正态分布归一化处理
test_transform = transforms.Compose([transforms.Resize((28, 28)), transforms.ToTensor(), normalize])
# 加载数据集,传入数据集的路径,及处理数据的方法
test_data = ImageFolder(ROOT_TEST, transform=test_transform)
#先放入数据,一批次数量(数据量比较小,6G运存应该是够的),shuffle是否打乱数据,进程是8,电脑垃圾的话,就改小一点
test_dataloader = Data.DataLoader(dataset=test_data,
batch_size=1,
shuffle=True,
num_workers=0)
return test_dataloader
##测试,只需要导入模型和数据集,得到一个测试的结果即可
def test_model_process(model,test_dataloader):
#设定测试用到的设备,有GPU用GPU,没有GPU用CPU
device = "cuda" if torch.cuda.is_available() else "cpu"
#将模型放入到测试设备当中
model = model.to(device)
#初始化模型参数,准确度即可,虽然已经知道测试的数据集有10000张,但为了后续不知道张数的情况下,也能进行测试。
test_corrects = 0.0
test_num = 0
##开始模型的测试,不存在反向传播,因此将梯度置为0,只有前向传播,不计算梯度,从而节省内存,加快运行速度
with torch.no_grad():
#直接一批次一批次的获取就好了。
for test_data_x,test_data_y in test_dataloader:
#将特征放入到测试设备中
test_data_x = test_data_x.to(device)
#将标签也放入到测试设备中
test_data_y = test_data_y.to(device)
#模型测试状态
model.eval()
#前向传播过程,输入为测试数据集,输出为对每个样本的预测值,它其实是一个10*1的矩阵,并不能看出结果来,需要经过一个softmax得到结果。最大概率对应的下标
output = model(test_data_x)
#预测的类别,查找每一行中最大值对应的行标
pre_lab = torch.argmax(output,dim=1)
#预测的结果和标签一样+1,如果预测正确,则 test_corrects加1,直到循环结束
test_corrects += torch.sum(pre_lab == test_data_y.data)
#将所有的测试样本进行累加
test_num += test_data_x.size(0)
#计算测试准确率,不在循环里面哦,注意缩进
test_acc = test_corrects.double().item() / test_num
print("测试的准确率为:" , test_acc)
#主函数进行运行程序
if __name__ == "__main__" :
#加载模型
model = LeNet()
#torch.load('best_model.pth')加载训练好的模型参数,权重以字符串的形式, model.load_state_dict模型训练化,将实例化之后的模型加载上权重,规定的做法
model.load_state_dict(torch.load('best_model.pth'))
#导入测试集的数据集,加载测试数据
test_dataloader = test_data_process()
#加载模型测试的函数
test_model_process(model, test_dataloader)
##进行模型推理过程
#设定测试所用到的设备,有GPU用GPU,没有就用CPU
device = "cuda" if torch.cuda.is_available() else 'cpu'
model = model.to(device)
# #使用列表取值的形式设置类别
classes= ['gs','m2p0','m2p1','m3p0']
# #测试部分,没有梯度,不存在反向传播
# with torch.no_grad():
# for b_x,b_y in test_dataloader:
# b_x = b_x.to(device)
# b_y = b_y.to(device)
#
# #设置模型为验证模式
# model.eval()
# output = model(b_x)
# ##10个通过神经元得到数值,经过softmax函数获得对应的概率,从而获得最大值的下标
# #ToTensor()将数据格式转换为张量的形式,只有张量的数据格式,才可以去做梯度运算,反向传播等
# pre_lab = torch.argmax(output, dim=1)
# # pre_lab.item()表示将张量中的数值取出来,以便做其他的操作(比如通过列表设置的类别,取整型方便运算,张量形式是没法进行的。
# result = pre_lab.item()
# label = b_y.item()
# #不仅打印数值,更应该打印出对应的类别,推理之前设置有列表,用下标指向类别
# print("预测值:", classes[result], "------", "真实值:",classes[label])
# #单个图片的推理D
# image = Image.open('Image1.jpg')
# # 对图片进行处理
# normalize = transforms.Normalize([0.162, 0.151, 0.138], [0.058, 0.052, 0.048])
# # 定义数据集处理方法变量,大小224*224,ToTensor格式,并且进行正态分布归一化处理
# test_transform = transforms.Compose([transforms.Resize((28, 28)), transforms.ToTensor(), normalize])
# image = test_transform(image)
# ##打印图的格式
# # print(image.shape)
# # 添加批次维度之后的数据,才可以送到模型当中去
# image = image.unsqueeze(0)
# # print(image.shape) 查看图片的输入格式情况
# # 进行单张图片的模型推理
# with torch.no_grad():
# # 模型已经放到设备上,并且打开了模型的验证模式
# model.eval()
# # 将输入数据放到设备中
# image = image.to(device)
# output = model(image)
# pre_lab = torch.argmax(output, dim=1)
# # 将数据从tensor格式转化为数字形式
# result = pre_lab.item()
# print("预测值:", classes[result])
归一化处理程序
from PIL import Image
import os
import numpy as np
# 文件夹路径,包含所有图片文件
folder_path = 'data'
# 初始化累积变量
total_pixels = 0
sum_normalized_pixel_values = np.zeros(3) # 如果是RGB图像,需要三个通道的均值和方差
# 遍历文件夹中的图片文件
for root, dirs, files in os.walk(folder_path):
for filename in files:
if filename.endswith(('.jpg', '.jpeg', '.png', '.bmp')): # 可根据实际情况添加其他格式
image_path = os.path.join(root, filename)
image = Image.open(image_path)
image_array = np.array(image)
# 归一化像素值到0-1之间
normalized_image_array = image_array / 255.0
# print(image_path)
# print(normalized_image_array.shape)
# 累积归一化后的像素值和像素数量
total_pixels += normalized_image_array.size
sum_normalized_pixel_values += np.sum(normalized_image_array, axis=(0, 1))
# 计算均值和方差
mean = sum_normalized_pixel_values / total_pixels
sum_squared_diff = np.zeros(3)
for root, dirs, files in os.walk(folder_path):
for filename in files:
if filename.endswith(('.jpg', '.jpeg', '.png', '.bmp')):
image_path = os.path.join(root, filename)
image = Image.open(image_path)
image_array = np.array(image)
# 归一化像素值到0-1之间
normalized_image_array = image_array / 255.0
# print(normalized_image_array.shape)
# print(mean.shape)
# print(image_path)
try:
diff = (normalized_image_array - mean) ** 2
sum_squared_diff += np.sum(diff, axis=(0, 1))
except:
print(f"捕获到自定义异常")
# diff = (normalized_image_array - mean) ** 2
# sum_squared_diff += np.sum(diff, axis=(0, 1))
variance = sum_squared_diff / total_pixels
print("Mean:", mean)
print("Variance:", variance)