定义神经网络模型模型
这段代码定义了一个简单的卷积神经网络模型,用于图像分类任务。特征提取网络部分通过卷积和池化层提取图像特征,分类网络部分通过全连接层将提取的特征映射到类别空间。
import torch
import torch.nn as nn
import torch.nn.functional as F
num_classes = 10 # 图片的类别数
class Model(nn.Module):
def __init__(self):
super().__init__()
# 特征提取网络
self.conv1 = nn.Conv2d(1, 32, kernel_size=3)
#输入图像的通道数、输出图像的通道数、卷积核大小 (RGB图像的输入通道数为3)
self.pool1 = nn.MaxPool2d(2)
#设置池化层,池化核大小为2*2
self.conv2 = nn.Conv2d(32, 64, kernel_size=3)
#第二层卷积,卷积核大小为3*3
self.pool2 = nn.MaxPool2d(2)
# 分类网络
self.fc1 = nn.Linear(1600,64)
self.fc2 = nn.Linear(64,num_classes)
# 前向传播
def forward(self,x):
x = self.pool1(F.relu(self.conv1(x)))
x = self.pool2(F.relu(self.conv2(x)))
x = torch.flatten(x,start_dim=1)
x = F.relu(self.fc1(x))
x = self.fc2(x)
return x
if __name__ == "__main__":
model=Model()
print(model)
首先是定义了一个模型类,在此模型类中,定义了特征提取网络和分类网络部分。特征提取网络包括两个卷积层和池化层,分类网络包括两个全连接层。在前向传播方法中,对输入数据进行卷积、激活函数ReLU、池化、展平、全连接和输出操作。
class Model(nn.Module):
def __init__(self):
super().__init__()
# 特征提取网络
self.conv1 = nn.Conv2d(1, 32, kernel_size=3) # 输入通道数为1,输出通道数为32,卷积核大小为3*3
self.pool1 = nn.MaxPool2d(2) # 最大池化层,池化核大小为2*2
self.conv2 = nn.Conv2d(32, 64, kernel_size=3) # 输入通道数为32,输出通道数为64,卷积核大小为3*3
self.pool2 = nn.MaxPool2d(2)
# 分类网络
self.fc1 = nn.Linear(1600, 64) # 全连接层,输入特征数为1600,输出特征数为64
self.fc2 = nn.Linear(64, num_classes) # 全连接层,输入特征数为64,输出类别数为10
# 前向传播
def forward(self, x):
x = self.pool1(F.relu(self.conv1(x))) # 第一层卷积、激活函数ReLU、池化
x = self.pool2(F.relu(self.conv2(x))) # 第二层卷积、激活函数ReLU、池化
x = torch.flatten(x, start_dim=1) # 将特征张量展平
x = F.relu(self.fc1(x)) # 全连接层、激活函数ReLU
x = self.fc2(x) # 输出层
return x
剩下的这部分代码创建了模型实例,并打印出模型的结构信息,包括每一层的参数设置和连接方式。
if __name__ == "__main__":
model = Model()
print(model)
模型训练
下列代码是一个完整的深度学习训练流程,用于训练一个用于识别MNIST手写数字数据集的神经网络模型。我们将对此代码进行拆解并分段讲解其作用。
import torch
device = torch.device('cuda' if torch.cuda.is_available() else 'cpu')
print(device)
import torchvision
# 训练集数据
train_ds = torchvision.datasets.MNIST('data',
train=True,
transform=torchvision.transforms.ToTensor(),
download=True)
# 测试集数据
test_ds = torchvision.datasets.MNIST('data',
train=False,
transform=torchvision.transforms.ToTensor(),
download=True)
batch_size = 32 # 每批加载样本的大小
# 加载训练集数据
train_dl = torch.utils.data.DataLoader(train_ds,
batch_size=batch_size,
shuffle=True) # 每个epoch重新排列数据
# 加载测试集数据
test_dl = torch.utils.data.DataLoader(test_ds,
batch_size=batch_size)
imgs,labels = next(iter(train_dl))
print(imgs.shape) # 得到结果是 torch.Size([32,1,28,28])
import torch.nn as nn
from model2 import Model
# 打印并加载模型
model = Model().to(device)
print(model)
loss_fn = nn.CrossEntropyLoss() # 创建损失函数
learn_rate = 1e-1 # 学习率
opt = torch.optim.SGD(model.parameters(), lr=learn_rate)
# 训练循环
print('准备进入----训练集里面')
def train(dataloader, model, loss_fn, optimizer):
size = len(dataloader.dataset) # 训练集的大小,一共60000张图片
num_batches = len(dataloader) # 批次数目,1875(60000/32)
train_loss, train_acc = 0, 0 # 初始化训练损失和正确率
for X, y in dataloader: # 获取图片及其标签
X, y = X.to(device), y.to(device)
# 计算预测误差
pred = model(X) # 网络输出
loss = loss_fn(pred, y) # 计算网络输出和真实值之间的差距,targets为真实值,计算二者差值,即为损失。
# 反向传播 (以下三个基本上是固定的)
optimizer.zero_grad() # grade属性归零
loss.backward() # 反向传播
optimizer.step() # 每一步自动更新
# 记录acc与loss
train_acc += (pred.argmax(1) == y).type(
torch.float).sum().item() # 表示计算预测正确的样本数量,并将其作为一个标量值返回。这通常用于评估分类模型的准确率或计算分类问题的正确预测数量。
train_loss += loss.item()
train_acc /= size
train_loss /= num_batches
return train_acc, train_loss
def test(dataloader, model, loss_fn):
size = len(dataloader.dataset) # 测试集的大小,一共10000张图片
num_batches = len(dataloader) # 批次数目313 (10000/32=321.5 向上取整)
test_loss, test_acc = 0, 0
# 当不进行训练时,停止梯度更新,节省计算内存消耗
with torch.no_grad():
for imgs, target in dataloader:
imgs, target = imgs.to(device), target.to(device)
# 计算loss
target_pred = model(imgs)
loss = loss_fn(target_pred, target)
test_loss += loss.item()
test_acc += (target_pred.argmax(1) == target).type(torch.float).sum().item()
test_acc /= size
test_loss /= num_batches
return test_acc, test_loss
if __name__ == "__main__":
epochs = 5
train_loss = []
train_acc = []
test_loss = []
test_acc = []
for epoch in range(epochs): # epoch 索引值
model.train() # 启用Batch Normalization和Dropout
epoch_train_acc, epoch_train_loss = train(train_dl, model, loss_fn, opt)
model.eval() # 不启用Batch Normalization 和Dropout
epoch_test_acc, epoch_test_loss = test(test_dl, model, loss_fn)
train_acc.append(epoch_train_acc)
train_loss.append(epoch_train_loss)
test_acc.append(epoch_test_acc)
test_loss.append(epoch_test_loss)
template = 'Epoch:{:2d}, Train_acc:{:.1f}%, Train_loss:{:.3f},Test_acc:{:.1f}%,Test_loss:{:.3f}'
print(template.format(epoch + 1, epoch_train_acc * 100, epoch_train_loss, epoch_test_acc * 100, epoch_test_loss))
print('Done')
import matplotlib.pyplot as plt
# plt.rcParams['font.sans-serif'] = ['SimHei'] # 用来正常显示中文标签
plt.rc('font', family='SimHei')
plt.rcParams['axes.unicode_minus'] = False # 用来正常显示负号
plt.rcParams['figure.dpi'] = 100 # 分辨率
epochs_range = range(epochs)
plt.figure(figsize=(12, 3))
plt.subplot(1, 2, 1)
plt.plot(epochs_range, train_acc, label='Training Accuracy')
plt.plot(epochs_range, test_acc, label='Test Accuracy')
plt.legend(loc='lower right')
plt.title('Training and Validation Accuracy')
plt.subplot(1, 2, 2)
plt.plot(epochs_range, train_loss, label='Train Loss')
plt.plot(epochs_range, test_loss, label="Test Loss")
plt.legend(loc='upper right')
plt.title('Training and Validation Loss')
plt.show()
首先是使用PyTorch框架进行深度学习的一个基本示例,主要目的是加载MNIST数据集,设置训练和测试数据加载器,并初始化模型、损失函数和优化器。
import torch
device = torch.device('cuda' if torch.cuda.is_available() else 'cpu')
print(device)
import torchvision
#训练集数据
train_ds = torchvision.datasets.MNIST('data',
train=True,
transform=torchvision.transforms.ToTensor(),
download=True)
#测试集数据
test_ds = torchvision.datasets.MNIST('data',
train=False,
transform=torchvision.transforms.ToTensor(),
download=True)
batch_size = 32 # 每批加载样本的大小
#加载训练集数据
train_dl = torch.utils.data.DataLoader(train_ds,
batch_size=batch_size,
shuffle=True) # 每个epoch重新排列数据
#加载测试集数据
test_dl = torch.utils.data.DataLoader(test_ds,
batch_size=batch_size)
imgs,labels = next(iter(train_dl))
print(imgs.shape) #得到结果是 torch.Size([32,1,28,28])
import torch.nn as nn
from model2 import Model
#打印并加载模型
model = Model().to(device)
print(model)
loss_fn = nn.CrossEntropyLoss() #创建损失函数
learn_rate = 1e-1 #学习率
opt = torch.optim.SGD(model.parameters(), lr=learn_rate)
这部分代码使用Torchvision库加载MNIST数据集,包括训练集和测试集,并对数据进行转换为张量形式。并且定义了训练集和测试集的数据加载器,用于批量加载数据并设置了每批次加载的样本数量以及是否重新排列数据。最终通过迭代器获取训练集的一个批次数据,然后打印出批次数据的形状,这里输出的形状为 torch.Size([32, 1, 28, 28])
,表示每批次包含32张大小为28x28的灰度图像。
# 训练集数据
train_ds = torchvision.datasets.MNIST('data',
train=True,
transform=torchvision.transforms.ToTensor(),
download=True)
# 测试集数据
test_ds = torchvision.datasets.MNIST('data',
train=False,
transform=torchvision.transforms.ToTensor(),
download=True)
batch_size = 32 # 每批加载样本的大小
# 加载训练集数据
train_dl = torch.utils.data.DataLoader(train_ds,
batch_size=batch_size,
shuffle=True) # 每个epoch重新排列数据
# 加载测试集数据
test_dl = torch.utils.data.DataLoader(test_ds,
batch_size=batch_size)
imgs,labels = next(iter(train_dl))
print(imgs.shape) # 得到结果是 torch.Size([32,1,28,28])
这部分代码导入模型类 Model
,创建模型实例并将其移动到指定设备上。然后定义了交叉熵损失函数和随机梯度下降(SGD)优化器,用于模型训练过程中的损失计算和参数优化。
import torch.nn as nn
from model2 import Model
#打印并加载模型
model = Model().to(device)
print(model)
loss_fn = nn.CrossEntropyLoss() #创建损失函数
learn_rate = 1e-1 #学习率
opt = torch.optim.SGD(model.parameters(), lr=learn_rate)
该段代码在循环中累积了训练准确率和损失。并在循环外部计算了平均训练准确率和损失,确保在整个训练集上进行累积计算后再计算平均值,最后返回训练准确率和损失。
其中:
1、optimizer.zero_grad() 函数会遍历模型的所有参数,通过内置方法截断反向传播的梯度流,再将每个参数的梯度值设为0,即上次的梯度记录会被清空。
2、loss.backward() Pytorch的反向传播(即:tensor.backward())是通过autograd包来实现的,autograd包会根据tensor进行过的数学运算来自动计算其对应的梯度。
3、optimizer.step() step() 函数的作用是执行一次优化步骤,通过梯度下降法来更新参数的值。
因为梯度下降是基于梯度的,所以在执行optimizer.step() 函数前应先指向那个loss.backward()函数来计算梯度。
4、pred.argmax(1)返回数组pred在第一个轴(即行)上最大值所在的索引。这通常用于多分类问题中,其中pred是一个包含预测概率的二维数组,每行表示一个样本的预测概率分布。
5、pred.argmax(1) == y是一个布尔值,其中等号是否成立代表对应样本的预测是否正确。(True表示正确,False表示错误)
6、type(torch.float)是将布尔数组的数据类型转换为浮点数类型,即将True转换为1.0;将False转换为0.0
7、sum() 是对数组中的元素进行求和,计算出预测正确的样本数量。
8、item() 将求和结果转换为标量值,以便在Python中使用或打印。
#训练循环
print('准备进入----训练集里面')
def train(dataloader, model, loss_fn, optimizer):
size = len(dataloader.dataset) # 训练集的大小,总共60000张图片
num_batches = len(dataloader) # 批次数目,1875(60000/32)
train_loss, train_acc = 0, 0 # 初始化训练损失和正确率
for X, y in dataloader: # 获取图片及其标签
X, y = X.to(device), y.to(device)
# 计算预测误差
pred = model(X) # 网络输出
loss = loss_fn(pred, y) # 计算损失
# 反向传播
optimizer.zero_grad() # 梯度归零
loss.backward() # 反向传播
optimizer.step() # 更新参数
# 记录准确率和损失
train_acc += (pred.argmax(1) == y).type(torch.float).sum().item()
train_loss += loss.item()
# 计算平均准确率和损失
train_acc /= size
train_loss /= num_batches
return train_acc, train_loss
本段代码定义了一个遍历测试集数据加载器,对每个批次的图片和标签进行测试。在测试过程中使用torch.no_grad()
上下文管理器,确保不会计算梯度,从而节省内存。并且计算模型在测试集上的损失和准确率,最后返回测试集上的平均准确率和损失。
如果模型中有BN(Batch Normalization)和Dropout ,需要在训练时添加model.train() 。
model.train() 是保证BN层能够用到每一批数据的均值和方差。
对于Dropout ,model.train() 是随机取一部分网络连接来训练更新参数。
如果模型中有BN(Batch Normalization)和Dropout ,需要在测试时添加model.eval() . model.eval() 是保证BN层能够用全部训练数据的均值和方差,即:测试过程中要保证BN层的均值和方差不变。
对于Dropout, model.eval() 是利用到了所有网络连接,即:不进行随机舍弃神经元。
训练完train样本后,生成的模型model要用来测试样本。在model(test)之前,需要加上model.eval(),否则的话,有输入数据,即使不训练,它也会改变权值。
这是model中还有BN层和Dropout所带来的性质。
#测试集测试
def test(dataloader, model, loss_fn):
size = len(dataloader.dataset) # 测试集的大小,一共10000张图片
num_batches = len(dataloader) # 批次数目313 (10000/32=321.5 向上取整)
test_loss, test_acc = 0, 0
#当不进行训练时,停止梯度更新,节省计算内存消耗
with torch.no_grad():
for imgs, target in dataloader:
imgs, target = imgs.to(device), target.to(device)
#计算loss
target_pred = model(imgs)
loss = loss_fn(target_pred, target)
test_loss += loss.item()
test_acc += (target_pred.argmax(1) == target).type(torch.float).sum().item()
test_acc /= size
test_loss /= num_batches
return test_acc, test_loss
下列这段代码的功能包括:
- 设置训练的轮数为5轮,并初始化存储训练和测试损失、准确率的列表。
- 对每个训练轮次:
- 将模型设置为训练模式,进行训练并记录训练准确率和损失。
- 将模型设置为评估模式,对模型在测试集上进行测试,并记录测试准确率和损失。
- 将每轮的训练和测试准确率、损失添加到相应的列表中。
- 打印当前轮次的训练和测试准确率以及损失。
- 训练完成后输出'Done'。
通过这段代码,可以完成整个训练和测试过程,并了解每个训练轮次的性能表现。
if __name__ == "__main__":
epochs = 5
train_loss = []
train_acc = []
test_loss = []
test_acc = []
for epoch in range(epochs): #epoch 索引值
model.train() #启用Batch Normalization和Dropout
epoch_train_acc, epoch_train_loss = train(train_dl, model, loss_fn, opt)
model.eval() #不启用Batch Normalization 和Dropout
epoch_test_acc, epoch_test_loss = test(test_dl, model, loss_fn)
train_acc.append(epoch_train_acc)
train_loss.append(epoch_train_loss)
test_acc.append(epoch_test_acc)
test_loss.append(epoch_test_loss)
template = 'Epoch:{:2d}, Train_acc:{:.1f}%, Train_loss:{:.3f},Test_acc:{:.1f}%,Test_loss:{:.3f}'
print(template.format(epoch + 1, epoch_train_acc * 100, epoch_train_loss, epoch_test_acc * 100, epoch_test_loss))
print('Done')
下列这段代码的功能包括:
- 导入Matplotlib库用于绘图。
- 设置中文显示和负号显示,以确保图中的中文标签和负号正常显示。
- 设置图像分辨率。
- 创建一个包含两个子图的图像,分别用于绘制准确率和损失的变化。
- 在第一个子图中绘制训练准确率和测试准确率随训练轮次变化的折线图,并添加图例和标题。
- 在第二个子图中绘制训练损失和测试损失随训练轮次变化的折线图,并添加图例和标题。
- 最后调用
plt.show()
来显示图像。
通过这段代码,可以直观地查看模型在训练和测试过程中准确率和损失的变化情况,帮助分析模型的性能表现。
import matplotlib.pyplot as plt
# plt.rcParams['font.sans-serif'] = ['SimHei'] # 用来正常显示中文标签
plt.rc('font', family='SimHei')
plt.rcParams['axes.unicode_minus'] = False # 用来正常显示负号
plt.rcParams['figure.dpi'] = 100 # 分辨率
epochs_range = range(epochs)
plt.figure(figsize=(12, 3))
plt.subplot(1, 2, 1)
plt.plot(epochs_range, train_acc, label='Training Accuracy')
plt.plot(epochs_range, test_acc, label='Test Accuracy')
plt.legend(loc='lower right')
plt.title('Training and Validation Accuracy')
plt.subplot(1, 2, 2)
plt.plot(epochs_range, train_loss, label='Train Loss')
plt.plot(epochs_range, test_loss, label="Test Loss")
plt.legend(loc='upper right')
plt.title('Training and Validation Loss')
plt.show()
模型训练过程及结果展示:
第一个子图中绘制了训练准确率和测试准确率随训练轮次变化的折线图
第二个子图中绘制了训练损失和测试损失随训练轮次变化的折线图
引用模型进行识别
此段代码是一个完整的流程,用于加载上面代码中训练好的识别手写数据集模型,并使用这个模型对一张图像进行识别
import torch
from model2 import Model
import torchvision.transforms as transforms
from PIL import Image
def load_model(model_path):
model = Model()
model.load_state_dict(torch.load(model_path))
model.eval()
return model
#前处理
def preprocess_image(image_path):
image = Image.open(image_path).convert('L')
transform = transforms.Compose([
transforms.Resize((28, 28)),
transforms.ToTensor(),
transforms.Normalize((0.1307,), (0.3081,))
])
return transform(image).unsqueeze(0)
def predict(model, input_tensor):
with torch.no_grad():
output = model(input_tensor)
_, predicted = torch.max(output.data, 1)
return predicted.item()
if __name__ == "__main__":
model_path = r'C:\Users\Admin\Desktop\zhangshuhan\python\best_model.pth'
image_path = r'C:\Users\Admin\Desktop\zhangshuhan\python\36eb9974a11bd2945900def51cc77ad.jpg'
model = load_model(model_path)
input_tensor = preprocess_image(image_path)
prediction = predict(model, input_tensor)
print(f"Predicted digit: {prediction}")
导入了必要的库和模块:
torch
: PyTorch库,用于构建和训练神经网络。Model
: 自定义的神经网络模型类。torchvision.transforms
: 包含图像预处理操作的模块。Image
: 用于处理图像的PIL库。
创建了一个模型实例 model
,这个模型是根据自定义的 Model
类创建的。
- 使用
torch.load(model_path)
加载了训练好的模型参数。- 将模型设为评估模式,即
model.eval()
,这是因为在推断时不需要进行梯度计算。
import torch
from model2 import Model
import torchvision.transforms as transforms
from PIL import Image
def load_model(model_path):
model = Model()
model.load_state_dict(torch.load(model_path))
model.eval()
return model
这段代码定义了一个函数 preprocess_image(image_path)
,用于对输入的图像进行预处理,包括调整大小、转换为张量和归一化,以便用于神经网络模型的预测,此为前处理。
- 使用
Image.open(image_path)
打开指定路径的图像文件,并将其转换为灰度图像('L' 表示灰度图像)。- 创建一个
transforms.Compose
对象,用于按顺序应用一系列的图像转换操作。- 在这个
Compose
对象中,定义了以下三个转换操作:
transforms.Resize((28, 28))
:将图像调整为 28x28 的大小。transforms.ToTensor()
:将图像转换为 PyTorch 张量。transforms.Normalize((0.1307,), (0.3081,))
:对图像进行归一化,均值为 0.1307,标准差为 0.3081。- 最后,对经过转换的图像应用
unsqueeze(0)
操作,将其形状从 (28, 28) 调整为 (1, 28, 28),以符合神经网络模型的输入要求。- 返回预处理后的图像张量。
def preprocess_image(image_path):
image = Image.open(image_path).convert('L')
transform = transforms.Compose([
transforms.Resize((28, 28)),
transforms.ToTensor(),
transforms.Normalize((0.1307,), (0.3081,))
])
return transform(image).unsqueeze(0)
代码定义了一个函数 predict(model, input_tensor)
,用于利用给定的模型对输入张量进行预测并返回预测的类别索引。
- 使用
with torch.no_grad()
上下文管理器,确保在进行预测时不会计算梯度,因为在推断阶段不需要进行反向传播。- 将输入张量
input_tensor
传递给模型model
,并获取模型的输出。- 使用
torch.max(output.data, 1)
函数找到输出张量中每行的最大值及其对应的索引,即预测的类别。- 返回预测的类别索引(使用
item()
方法获取索引值)。
def predict(model, input_tensor):
with torch.no_grad():
output = model(input_tensor)
_, predicted = torch.max(output.data, 1)
return predicted.item()
此段代码是一个主程序,包含了整个流程:加载模型、预处理图像、进行预测,并输出预测结果
if __name__ == "__main__":
:
- 这是 Python 中的惯用写法,表示如果当前脚本被直接运行(而不是被导入为模块),则执行以下代码块。
指定模型路径和图像路径:
model_path
:模型文件的路径image_path
:待预测图像的路径加载模型和预测:
- 使用
load_model(model_path)
函数加载模型文件,得到模型对象model
。- 使用
preprocess_image(image_path)
函数对指定路径的图像进行预处理,得到输入张量input_tensor
。- 使用
predict(model, input_tensor)
函数对输入张量进行预测,得到预测结果prediction
。输出预测结果:
- 最后,打印出预测的数字结果,即预测的类别。
if __name__ == "__main__":
model_path = r'C:\Users\Admin\Desktop\zhangshuhan\python\best_model.pth'
image_path = r'C:\Users\Admin\Desktop\zhangshuhan\python\36eb9974a11bd2945900def51cc77ad.jpg'
model = load_model(model_path)
input_tensor = preprocess_image(image_path)
prediction = predict(model, input_tensor)
print(f"Predicted digit: {prediction}")