S 3.5深度学习--卷积神经网络--代码
一·迁移学习代码逻辑的讲解
resnet_model = models.resnet18(weights=models.ResNet18_Weights.DEFAULT) # 即调用了resnet18网络,又使用了训练好的模型
# weights=models.ResNet18_Weights.DEFAULT表示使用在 ImageNet 数据集上预先训练好的权重来初始化模型参数,可进入源代码查看
1、选择预训练的模型和适当的层:通常,我们会选择在大规模图像数据集(如ImageNet)上预训练的模型,如VGG、ResNet等。然后,根据新数据集的特点,选择需要微调的模型层。对于低级特征的任务(如边缘检测),最好使用浅层模型的层,而对于高级特征的任务(如分类),则应选择更深层次的模型。
for param in resnet_model.parameters(): # 逐层去除权重参数
print(param)
param.requires_grad = False # 冻结
# 模型所有参数(即权重和偏差)的requires_grad属性设置为False,从而冻结所有模型参数。
2、冻结预训练模型的参数:保持预训练模型的权重不变,只训练新增加的层或者微调一些层,避免因为在数据集中过拟合导致预训练模型过度拟合。
import torch
import torchvision.models as models
from torch import nn
from torch.utils.data import Dataset, DataLoader
from torchvision import transforms
from PIL import Image
import numpy as np
# 不再需要自己来搭建模型了。预训练的文件也加载进去了。
'''将resnet18模型迁移到食物分类项目中''' # 残差网络是固定的网络结构,不需要你自己来类定义了。
resnet_model = models.resnet18(weights=models.ResNet18_Weights.DEFAULT) # 即调用了resnet18网络,又使用了训练好的模型
# weights=models.ResNet18_Weights.DEFAULT表示使用在 ImageNet 数据集上预先训练好的权重来初始化模型参数,可进入源代码查看
for param in resnet_model.parameters(): # 逐层去除权重参数
print(param)
param.requires_grad = False # 冻结
# 模型所有参数(即权重和偏差)的requires_grad属性设置为False,从而冻结所有模型参数。
in_features = resnet_model.fc.in_features # 获取模型原输入的特征个数
resnet_model.fc = nn.Linear(in_features, 20) # 创建一个全连接层,输入特征为in_features,输出为20
params_to_update = [] # 保存需要训练的参数,仅仅包含全连接层的参数
for param in resnet_model.parameters():
if param.requires_grad == True:
params_to_update.append(param)
# 不冻结,需要全部重头训练,注释13、14、20~23行,并修改82行代码
data_transforms = {
'train':
transforms.Compose([
transforms.Resize([300, 300]),
transforms.RandomRotation(45),
transforms.CenterCrop(244),
transforms.RandomHorizontalFlip(p=0.5),
transforms.RandomVerticalFlip(p=0.5),
transforms.RandomGrayscale(p=0.1),
transforms.ToTensor(),
transforms.Normalize([0.485, 0.456, 0.406], [0.229, 0.224, 0.225])
]),
'valid':
transforms.Compose([
transforms.Resize([244, 244]),
transforms.ToTensor(),
transforms.Normalize([0.485, 0.456, 0.406], [0.229, 0.224, 0.225])
]),
}
class food_dataset(Dataset): # food_dataset是自己创建的类名称,可以改为你需要的名称
def __init__(self, file_path, transform=None): # 类的初始化,解析数据文件txt
self.file_path = file_path
self.imgs = [] # 存储图片的路径
self.labels = [] # 存储图片的标签结果
self.transform = transform
with open(self.file_path) as f: # 是把train.txt文件中图片的路径保存在 self.imgs,train.txt文件中标签保
samples = [x.strip().split(' ') for x in f.readlines()]
for img_path, label in samples:
self.imgs.append(img_path) # 图像的路径
self.labels.append(label) # 标签,还不是tensor
# 初始化:把图片目录加载到self.
def __len__(self): # 类实例化对象后,可以使用len函数测量对象的个数 ls=[12,3,4,4] len(training_data)
return len(self.imgs)
# training_data[1]
def __getitem__(self, idx): # 关键,可通过索引的形式获取每一个图片数据及标签
image = Image.open(self.imgs[idx]) # 读取到图片数据,还不是tensor,BGR
if self.transform: # 将pil图像数据转换为tensor
image = self.transform(image) # 图像处理为256*256,转换为tenor
label = self.labels[idx] # label还不是tensor
label = torch.from_numpy(np.array(label, dtype=np.int64)) # label也转换为tensor,
return image, label
training_data = food_dataset(file_path = './trainda.txt',transform = data_transforms['train'])
test_data = food_dataset(file_path = './testda.txt',transform = data_transforms['valid'])
train_dataloader = DataLoader(training_data, batch_size=64, shuffle=True)
test_dataloader = DataLoader(test_data, batch_size=64, shuffle=True)
device = "cuda" if torch.cuda.is_available() else "mps" if torch.backends.mps.is_available() else "cpu"
print(f"Using {device} device")
model = resnet_model.to(device)
loss_fn = nn.CrossEntropyLoss()
optimizer = torch.optim.Adam(params_to_update, lr=0.001)
# optimizer = torch.optim.Adam(resnet_model.parameters(), lr=0.001)
scheduler = torch.optim.lr_scheduler.StepLR(optimizer, step_size=5, gamma=0.5) # 调整学习率函数
def train(dataloader, model, loss_fn, optimizer):
model.train()
# batch_size_num = 1
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()
loss_value = loss.item()
# if batch_size_num % 100 == 0:
# print(f"loss: {loss_value} [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():
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()
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
loss_fn = nn.CrossEntropyLoss()
optimizer = torch.optim.Adam(model.parameters(), lr=0.01)
scheduler = torch.optim.lr_scheduler.StepLR(optimizer,step_size=5, gamma=0.5)
epochs = 10
acc_s = []
loss_s = []
for t in range(epochs):
print(f"Epoch {t + 1}\n-------------------------------")
train(train_dataloader, model, loss_fn, optimizer)
scheduler.step()
print(best_acc)
test(test_dataloader, model, loss_fn)
1. 导入必要的库
代码开头导入了 PyTorch 及其相关模块,包括神经网络 (nn)、数据加载工具 (Dataset, DataLoader)、图像变换工具 (transforms) 以及 PIL 图像处理库等。
2. 迁移学习模型构建
python
运行
resnet_model = models.resnet18(weights=models.ResNet18_Weights.DEFAULT)
这里加载了预训练的 ResNet18 模型,weights参数指定使用在 ImageNet 数据集上预训练好的权重。
for param in resnet_model.parameters():
param.requires_grad = False # 冻结预训练参数
冻结了 ResNet18 的所有参数,这是迁移学习的常用策略,避免破坏预训练好的特征提取能力。
in_features = resnet_model.fc.in_features # 获取原全连接层输入特征数
resnet_model.fc = nn.Linear(in_features, 20) # 替换为新的全连接层,输出20类
替换了 ResNet18 的最后一层全连接层,将输出类别数改为 20(食物分类的类别数)
params_to_update = [] # 收集需要训练的参数(仅新的全连接层参数)
for param in resnet_model.parameters():
if param.requires_grad == True:
params_to_update.append(param)
收集需要训练的参数,由于前面冻结了所有参数,这里实际只包含新全连接层的参数。
3. 数据预处理
data_transforms = {
'train': transforms.Compose([...]), # 训练集的数据增强
'valid': transforms.Compose([...]) # 验证集的简单处理
}
定义了数据变换管道,训练集使用了多种数据增强技术(旋转、裁剪、翻转等)来提高模型泛化能力,验证集则只进行简单的尺寸调整和标准化。
4. 自定义数据集类
class food_dataset(Dataset):
def __init__(self, file_path, transform=None):
# 从文件中读取图片路径和标签
...
def __len__(self):
# 返回数据集大小
...
def __getitem__(self, idx):
# 读取图片并应用变换,返回图片和标签的tensor
...
自定义了数据集类,用于加载食物图像数据,实现了标准的__init__、__len__和__getitem__方法。
5. 数据加载器
training_data = food_dataset(file_path='./trainda.txt', transform=data_transforms['train'])
test_data = food_dataset(file_path='./testda.txt', transform=data_transforms['valid'])
train_dataloader = DataLoader(training_data, batch_size=64, shuffle=True)
test_dataloader = DataLoader(test_data, batch_size=64, shuffle=True)
创建了训练集和测试集的数据加载器,用于按批次加载数据,batch_size=64表示每批加载 64 张图片,shuffle=True表示打乱数据顺序。
6. 设备配置
device = "cuda" if torch.cuda.is_available() else "mps" if torch.backends.mps.is_available() else "cpu"
model = resnet_model.to(device)
自动选择训练设备(优先 GPU,其次是 MPS,最后是 CPU),并将模型移动到选定的设备上。
7. 训练配置
loss_fn = nn.CrossEntropyLoss() # 交叉熵损失函数,适用于分类任务
optimizer = torch.optim.Adam(params_to_update, lr=0.001) # Adam优化器
scheduler = torch.optim.lr_scheduler.StepLR(optimizer, step_size=5, gamma=0.5) # 学习率调度器
定义了损失函数、优化器和学习率调度器,学习率调度器会每 5 个 epoch 将学习率减半。
8. 训练和测试函数
train函数:实现模型的训练过程,包括前向传播、计算损失、反向传播和参数更新。test函数:实现模型的测试过程,计算测试集上的准确率和平均损失,并保存最佳准确率的模型。
9. 模型训练和评估
epochs = 10 # 训练10个epoch
for t in range(epochs):
print(f"Epoch {t + 1}\n-------------------------------")
train(train_dataloader, model, loss_fn, optimizer)
scheduler.step()
test(test_dataloader, model, loss_fn) # 最后在测试集上评估
循环训练模型 10 个 epoch,每个 epoch 结束后更新学习率,最后在测试集上评估模型性能。
更多推荐


所有评论(0)