目录
什么是resnet残差网络
ResNet是由微软研究员提出的一种深度残差网络,采用了残差学习的方法,通过引入“跳连”(skip connection)的方式解决了在深度网络中梯度消失和梯度爆炸的问题,从而有效解决了通过增加网络深度提高网络性能所面临的难题。
其核心思想就是:设想要学习一个恒等映射,若直接拟合该映射难以学习到,则可以通过构建一个具有相同映射的更浅的网络,通过这个新的网络与旧网络之间的“跳链接”来更好地进行学习。
代码实现
文件下载
food_dataset2 train.txt test.txt
导入所需的库
# 导入Python的时间模块,用于计时和延迟
import time
# 导入PyTorch库,这是一个强大的深度学习框架
import torch
# 从torch库中导入nn模块,这是PyTorch的神经网络模块
from torch import nn
# 从torch库中导入数据相关的模块,包括数据集和数据加载器
from torch.utils.data import Dataset,DataLoader
# 导入NumPy库,这是一个用于数值计算的Python库,常用于处理多维数组和矩阵
import numpy as np
# 导入PIL库中的Image模块,用于处理图像
from PIL import Image
# 导入torchvision库中的models模块,该模块包含了一些预训练的深度学习模型
import torchvision.models as models
# 导入torchvision库中的transforms模块,该模块包含了一些用于图像预处理的方法
from torchvision import transforms
定义了ResNet18模型
# 导入ResNet18模型,使用默认的预训练权重
resnet_model = models.resnet18(weights=models.ResNet18_Weights.DEFAULT)
# 遍历模型中的所有参数,将它们的requires_grad属性设为False,这样在训练时就不会对这些参数进行梯度下降更新
for param in resnet_model.parameters(): #'#
param.requires_grad=False #'#
# 获取ResNet18模型的最后一层(全连接层)的输入特征数量
in_features = resnet_model.fc.in_features
# 将模型的最后一层替换为新的全连接层,输出特征数为20
resnet_model.fc = nn.Linear(in_features,20)
# 创建一个空列表,用于保存需要训练的参数,这里主要是指全连接层的参数
param_to_updata = [] #保存需要训练的参数,仅仅包含全连接层的参数
# 再次遍历模型中的所有参数
for param in resnet_model.parameters(): #'#'
# 如果参数的requires_grad属性为True,就将其添加到param_to_updata列表中
if param.requires_grad == True: #'#
param_to_updata.append(param) #'#
# 定义数据转换,主要是对图像数据进行一系列变换,包括大小调整、旋转、裁剪、翻转、亮度对比度调整、灰度转换以及标准化
data_transforms = { #也可以使用PIL库,smote 人工拟合出来数据
'train':
transforms.Compose([
transforms.Resize([300,300]), #是图像变换大小
transforms.RandomRotation(45),#随机旋转,-45到45度之间随机选
transforms.CenterCrop(256),#从中心开始裁剪[256,256]
transforms.RandomHorizontalFlip(p=0.5),#随机水平翻转 选择一个概率概率
transforms.RandomVerticalFlip(p=0.5),#随机垂直翻转
transforms.ColorJitter(brightness=0.2, contrast=0.1, saturation=0.1, hue=0.1),#参数1为亮度,参数2为对比度,参数3为饱和度,参数4为色相
transforms.RandomGrayscale(p=0.1),#概率转换成灰度率,3通道就是R=G=B
transforms.ToTensor(),
transforms.Normalize([0.485, 0.456, 0.406], [0.229, 0.224, 0.225])#标准化,均值,标准差
]),
'valid':
transforms.Compose([
transforms.Resize([256,256]),
transforms.ToTensor(),
transforms.Normalize([0.485, 0.456, 0.406], [0.229, 0.224, 0.225])
]),
}
加载训练和测试数据
# 定义一个名为food_dataset的自定义类,这个类继承了torch.utils.data.Dataset,这个类可以按照您的需求进行更改
class food_dataset(Dataset):
# 类的初始化方法,当创建类的实例时被调用
def __init__(self, file_path, transform=None):
# 保存文件路径
self.file_path = file_path
# 初始化空的图片列表和标签列表
self.imgs = []
self.labels = []
# 保存数据转换操作,默认为None
self.transform = transform
# 打开文件路径,并逐行读取数据,每行数据以空格分割,保存到samples列表中
with open(self.file_path) as f:
samples = [x.strip().split(' ') for x in f.readlines()]
# 遍历每个样本,将图片路径保存到imgs列表,标签保存到labels列表中
for img_path, label in samples:
self.imgs.append(img_path)
self.labels.append(label)
# 返回数据集的大小,即图片数量
def __len__(self):
return len(self.imgs)
# 通过索引获取数据集中的单个样本,返回样本数据及其对应的标签
def __getitem__(self, idx):
# 打开图片路径对应的图片,并保存到image对象中
image = Image.open(self.imgs[idx])
# 如果存在数据转换操作,则对图片进行转换操作
if self.transform:
image = self.transform(image)
# 获取标签,此处标签可能为字符串类型,将其转换为整数类型并保存到label对象中
label = self.labels[idx]
label = torch.from_numpy(np.array(label, dtype = np.int64))
# 返回图片和标签作为元组
return image, label
# 实例化food_dataset类,创建训练数据集对象,输入参数为训练数据文件路径'train.txt'和训练数据转换操作data_transforms['train']
training_data = food_dataset(file_path = 'train.txt', transform = data_transforms['train'])
# 实例化food_dataset类,创建测试数据集对象,输入参数为测试数据文件路径'test.txt'和测试数据转换操作data_transforms['valid']
test_data = food_dataset(file_path = 'test.txt', transform = data_transforms['valid'])
# 根据设备可用情况选择设备,如果CUDA可用则使用CUDA,否则如果MPS可用则使用MPS,否则使用CPU
device = "cuda" if torch.cuda.is_available() else "mps" if torch.backends.mps.is_available() else "cpu"
print(f"Using {device} device")
# 将预训练的ResNet模型加载到model对象中,此处未显示该模型的导入过程,可能在之前已经完成
model = resnet_model.to(device)
# 创建一个交叉熵损失函数对象,用于训练过程中的损失计算
loss_fn = nn.CrossEntropyLoss()
# 创建一个Adam优化器对象,参数为需要更新的参数和learning rate(学习率)0.001
optimizer =torch.optim.Adam(param_to_updata, lr=0.001)
def train(dataloader, model, loss_fn, optimizer):
model.train()
#pytorch提供2种方式来切换训练和测试的模式,分别是:model.train() 和 model.eval()。
# 一般用法是:在训练开始之前写上model.trian(),在测试时写上 model.eval() 。
# batch_size_num = 1
for X, y in dataloader: #其中batch为每一个数据的编号
X, y = X.to(device), y.to(device) #把训练数据集和标签传入cpu或GPU
pred = model.forward(X) #自动初始化 w权值
loss = loss_fn(pred, y) #通过交叉熵损失函数计算损失值loss
# Backpropagation 进来一个batch的数据,计算一次梯度,更新一次网络
optimizer.zero_grad() #梯度值清零
loss.backward() #反向传播计算得到每个参数的梯度值
optimizer.step() #根据梯度更新网络参数
# loss = loss.item() #获取损失值
# print(f"loss: {loss:>7f} [number:{batch_size_num}]")
# batch_size_num += 1
best_acc = 0
def test(dataloader, model, loss_fn):
global best_acc
size = len(dataloader.dataset)
num_batches = len(dataloader)
model.eval() #
test_loss, correct = 0, 0
with torch.no_grad(): #一个上下文管理器,关闭梯度计算。当你确认不会调用Tensor.backward()的时候。这可以减少计算所用内存消耗。
for X, y in dataloader:
X, y = X.to(device), y.to(device)
pred = model.forward(X)
test_loss += loss_fn(pred, y).item() #
correct += (pred.argmax(1) == y).type(torch.float).sum().item()
a = (pred.argmax(1) == y) #dim=1表示每一行中的最大值对应的索引号,dim=0表示每一列中的最大值对应的索引号
b = (pred.argmax(1) == y).type(torch.float)
test_loss /= num_batches
correct /= size
print(f"Test result: \n Accuracy: {(100*correct)}%, Avg loss: {test_loss}")
acc_s.append(correct)
loss_s.append(test_loss)
if correct > best_acc:
best_acc = correct
训练模型
epochs = 20
acc_s = []
loss_s = []
start_time = time.time()
for t in range(epochs):
train_dataloader = DataLoader(training_data,batch_size=64,shuffle=True)
test_dataloader = DataLoader(test_data,batch_size=64,shuffle=True)
print(f'Epoch{t+1}\n--------------正在训练------------------------')
train(train_dataloader,model,loss_fn,optimizer)
test(test_dataloader,model,loss_fn)
end_time_epochs = time.time()
all_time = end_time_epochs - start_time
print(f'all time is :{all_time:.2f} seconds')
print('最优训练结果为:',best_acc)