当前位置:   article > 正文

Pytorch迁移学习训练病变分类模型

Pytorch迁移学习训练病变分类模型

划分数据集

1.创建训练集文件夹和测试集文件夹

  1. # 创建 train 文件夹
  2. os.mkdir(os.path.join(dataset_path, 'train'))
  3. # 创建 test 文件夹
  4. os.mkdir(os.path.join(dataset_path, 'val'))
  5. # 在 train 和 test 文件夹中创建各类别子文件夹
  6. for Retinopathy in classes:
  7. os.mkdir(os.path.join(dataset_path, 'train', Retinopathy))
  8. os.mkdir(os.path.join(dataset_path, 'val', Retinopathy))

2.划分训练集、测试集,移动文件

  1. test_frac = 0.2 # 测试集比例
  2. random.seed(123) # 随机数种子,用来打乱数据集
  3. df = pd.DataFrame()
  4. print('{:^18} {:^18} {:^18}'.format('类别', '训练集数据个数', '测试集数据个数'))
  5. for Retinopathy in classes: # 遍历每个类别
  6. # 读取该类别的所有图像文件名
  7. old_dir = os.path.join(dataset_path, Retinopathy)
  8. images_filename = os.listdir(old_dir)
  9. random.shuffle(images_filename) # 随机打乱
  10. # 划分训练集和测试集
  11. testset_numer = int(len(images_filename) * test_frac) # 测试集图像个数
  12. testset_images = images_filename[:testset_numer] # 获取拟移动至 test 目录的测试集图像文件名
  13. trainset_images = images_filename[testset_numer:] # 获取拟移动至 train 目录的训练集图像文件名
  14. # 移动图像至 test 目录
  15. for image in testset_images:
  16. old_img_path = os.path.join(dataset_path, Retinopathy, image) # 获取原始文件路径
  17. new_test_path = os.path.join(dataset_path, 'val', Retinopathy, image) # 获取 test 目录的新文件路径
  18. shutil.move(old_img_path, new_test_path) # 移动文件
  19. # 移动图像至 train 目录
  20. for image in trainset_images:
  21. old_img_path = os.path.join(dataset_path, Retinopathy, image) # 获取原始文件路径
  22. new_train_path = os.path.join(dataset_path, 'train', Retinopathy, image) # 获取 train 目录的新文件路径
  23. shutil.move(old_img_path, new_train_path) # 移动文件
  24. # 删除旧文件夹
  25. assert len(os.listdir(old_dir)) == 0 # 确保旧文件夹中的所有图像都被移动走
  26. shutil.rmtree(old_dir) # 删除文件夹
  27. # 工整地输出每一类别的数据个数
  28. print('{:^18} {:^18} {:^18}'.format(Retinopathy, len(trainset_images), len(testset_images)))
  29. # 保存到表格中
  30. df = df.append({'class':Retinopathy, 'trainset':len(trainset_images), 'testset':len(testset_images)}, ignore_index=True)
  31. # 重命名数据集文件夹
  32. shutil.move(dataset_path, dataset_name+'_split')
  33. # 数据集各类别数量统计表格,导出为 csv 文件
  34. df['total'] = df['trainset'] + df['testset']
  35. df.to_csv('数据量统计.csv', index=False)

结果如下:

统计各类别数据个数柱状图

1.导入工具包

  1. import numpy as np
  2. import pandas as pd
  3. import matplotlib.pyplot as plt
  4. %matplotlib inline

2.设置matplotlib的中文字体,因为它默认无法写中文字体

  1. plt.rcParams['font.sans-serif']=['SimHei'] # 用来正常显示中文标签
  2. plt.rcParams['axes.unicode_minus']=False # 用来正常显示负号

3.指定可视化的特征

  1. feature = 'total'
  2. # feature = 'trainset'
  3. # feature = 'testset'
  4. df = df.sort_values(by=feature, ascending=False)

4.通过柱状图展示出来

  1. plt.figure(figsize=(22, 7))
  2. x = df['class']
  3. y = df[feature]
  4. plt.bar(x, y, facecolor='#1f77b4', edgecolor='k')
  5. plt.xticks(rotation=90)
  6. plt.tick_params(labelsize=15)
  7. plt.xlabel('类别', fontsize=20)
  8. plt.ylabel('图像数量', fontsize=20)
  9. # plt.savefig('各类别图片数量.pdf', dpi=120, bbox_inches='tight')
  10. plt.show()

结果如下:

由此可见,数据集是比较均衡的。

5.将训练集与测试集的比例展示出来

  1. plt.figure(figsize=(22, 7))
  2. x = df['class']
  3. y1 = df['testset']
  4. y2 = df['trainset']
  5. width = 0.55 # 柱状图宽度
  6. plt.xticks(rotation=90) # 横轴文字旋转
  7. plt.bar(x, y1, width, label='测试集')
  8. plt.bar(x, y2, width, label='训练集', bottom=y1)
  9. plt.xlabel('类别', fontsize=20)
  10. plt.ylabel('图像数量', fontsize=20)
  11. plt.tick_params(labelsize=13) # 设置坐标文字大小
  12. plt.legend(fontsize=16) # 图例
  13. # 保存为高清的 pdf 文件
  14. plt.savefig('各类别图像数量.pdf', dpi=120, bbox_inches='tight')
  15. plt.show()

结果如下:

处理完数据集后,就可以开始通过迁移学习训练病变分类模型。

安装配置环境

1.numpy、pandas、matplotlib、seaborn、plotly、requests、tqdm、opencv-python、pillow、wandb和pytorch均已安装完成

2.创建三个文件夹

  1. import os
  2. # 存放结果文件
  3. os.mkdir('output')
  4. # 存放训练得到的模型权重
  5. os.mkdir('checkpoint')
  6. # 存放生成的图表
  7. os.mkdir('图表')

迁移学习训练过程与前处理

1.导入包

  1. import time
  2. import os
  3. import numpy as np
  4. from tqdm import tqdm
  5. import torch
  6. import torchvision
  7. import torch.nn as nn
  8. import torch.nn.functional as F
  9. import matplotlib.pyplot as plt
  10. %matplotlib inline
  11. import warnings
  12. warnings.filterwarnings("ignore")

2.获取计算机的硬件,使用CPU还是GPU

  1. # 有 GPU 就用 GPU,没有就用 CPU
  2. device = torch.device('cuda:0' if torch.cuda.is_available() else 'cpu')
  3. print('device', device)

3.图像预处理

  1. from torchvision import transforms
  2. # 训练集图像预处理:缩放裁剪、图像增强、转 Tensor、归一化
  3. train_transform = transforms.Compose([transforms.RandomResizedCrop(224),
  4. transforms.RandomHorizontalFlip(),
  5. transforms.ToTensor(),
  6. transforms.Normalize([0.485, 0.456, 0.406], [0.229, 0.224, 0.225])
  7. ])
  8. # 测试集图像预处理:缩放、裁剪、转 Tensor、归一化
  9. test_transform = transforms.Compose([transforms.Resize(256),
  10. transforms.CenterCrop(224),
  11. transforms.ToTensor(),
  12. transforms.Normalize(
  13. mean=[0.485, 0.456, 0.406],
  14. std=[0.229, 0.224, 0.225])
  15. ])

对训练集和测试集分别进行预处理。

训练集的预处理中,RandomResizedCrop(224)表示随机选择一个面积比例,并在该比例下随机裁剪图像,然后将裁剪后的图像缩放到指定的尺寸,参数 224 指定了裁剪并缩放后的图像尺寸应该是 224x224 像素。RandomHorizontalFlip()是进行随机的水平翻转,目的是图像增强。最后转成pytorch的tensor格式进行归一化。归一化的6个参数约定俗成。

4.载入图像分类数据集

  1. from torchvision import datasets
  2. # 数据集文件夹路径
  3. dataset_dir = 'E:\科研实验\Train_Custom_Dataset-main\图像分类\dataset_split'
  4. train_path = os.path.join(dataset_dir, 'train')
  5. test_path = os.path.join(dataset_dir, 'val')
  6. # 载入训练集
  7. train_dataset = datasets.ImageFolder(train_path, train_transform)
  8. # 载入测试集
  9. test_dataset = datasets.ImageFolder(test_path, test_transform)

结果如下:

5.类别和索引号一一对应,方便后续的查询

  1. # 映射关系:索引号 到 类别
  2. idx_to_labels = {y:x for x,y in train_dataset.class_to_idx.items()}
  3. # 保存为本地的 npy 文件
  4. np.save('idx_to_labels.npy', idx_to_labels)
  5. np.save('labels_to_idx.npy', train_dataset.class_to_idx)

6.定义数据加载器DataLoader

  1. from torch.utils.data import DataLoader
  2. BATCH_SIZE = 32
  3. # 训练集的数据加载器
  4. train_loader = DataLoader(train_dataset,
  5. batch_size=BATCH_SIZE,
  6. shuffle=True,
  7. num_workers=4
  8. )
  9. # 测试集的数据加载器
  10. test_loader = DataLoader(test_dataset,
  11. batch_size=BATCH_SIZE,
  12. shuffle=False,
  13. num_workers=4
  14. )

7.可视化一个batch的图像和标注

  1. # 将数据集中的Tensor张量转为numpy的array数据类型
  2. images = images.numpy()

举个例子,images[5].shape展示的是一个批次中第五张图片的信息,结果如下:

images[5]的像素分布如下所示:

显示上图所用代码为:

  1. plt.hist(images[5].flatten(), bins=50)
  2. plt.show()

之前通过预处理归一化,已经将每一个像素都减去它所在通道的均值,再除以它所在通道的标准差了,所以现在的像素不再分布在0~255的整数范围内,而是一个以0为均值的,有正有负的分布。这样的分布更容易被神经网络处理,正如上图所示。

归一化后的图像如下所示:

显示上图所用代码为:

  1. # batch 中经过预处理的图像
  2. idx = 5
  3. plt.imshow(images[idx].transpose((1,2,0))) # 转为(224, 224, 3)
  4. plt.title('label:'+str(labels[idx].item()))

此图的原图像为:

显示上图所用代码为:

  1. # 原始图像
  2. idx = 5
  3. mean = np.array([0.485, 0.456, 0.406])
  4. std = np.array([0.229, 0.224, 0.225])
  5. plt.imshow(np.clip(images[idx].transpose((1,2,0)) * std + mean, 0, 1))
  6. plt.title('label:'+ pred_classname)
  7. plt.show()

8.选择迁移学习训练的方式

视网膜图像和ImageNet的分布不是很一致,所以这里采用“微调训练所有层”的方式

①调整训练所有层

  1. model = model.to(device)
  2. # 交叉熵损失函数
  3. criterion = nn.CrossEntropyLoss()
  4. # 训练轮次 Epoch
  5. EPOCHS = 30
  6. # 学习率降低策略
  7. lr_scheduler = lr_scheduler.StepLR(optimizer, step_size=5, gamma=0.5)

②函数:在训练集上训练

  1. from sklearn.metrics import precision_score
  2. from sklearn.metrics import recall_score
  3. from sklearn.metrics import accuracy_score
  4. from sklearn.metrics import f1_score
  5. from sklearn.metrics import roc_auc_score
  6. def train_one_batch(images, labels):
  7. '''
  8. 运行一个 batch 的训练,返回当前 batch 的训练日志
  9. '''
  10. # 获得一个 batch 的数据和标注
  11. images = images.to(device)
  12. labels = labels.to(device)
  13. outputs = model(images) # 输入模型,执行前向预测
  14. loss = criterion(outputs, labels) # 计算当前 batch 中,每个样本的平均交叉熵损失函数值
  15. # 优化更新权重
  16. optimizer.zero_grad()
  17. loss.backward()
  18. optimizer.step()
  19. # 获取当前 batch 的标签类别和预测类别
  20. _, preds = torch.max(outputs, 1) # 获得当前 batch 所有图像的预测类别
  21. preds = preds.cpu().numpy()
  22. loss = loss.detach().cpu().numpy()
  23. outputs = outputs.detach().cpu().numpy()
  24. labels = labels.detach().cpu().numpy()
  25. log_train = {}
  26. log_train['epoch'] = epoch
  27. log_train['batch'] = batch_idx
  28. # 计算分类评估指标
  29. log_train['train_loss'] = loss
  30. log_train['train_accuracy'] = accuracy_score(labels, preds)
  31. # log_train['train_precision'] = precision_score(labels, preds, average='macro')
  32. # log_train['train_recall'] = recall_score(labels, preds, average='macro')
  33. # log_train['train_f1-score'] = f1_score(labels, preds, average='macro')
  34. return log_train

返回的log_train是训练日志

③函数:在整个测试集上评估

  1. def evaluate_testset():
  2. '''
  3. 在整个测试集上评估,返回分类评估指标日志
  4. '''
  5. loss_list = []
  6. labels_list = []
  7. preds_list = []
  8. with torch.no_grad():
  9. for images, labels in test_loader: # 生成一个 batch 的数据和标注
  10. images = images.to(device)
  11. labels = labels.to(device)
  12. outputs = model(images) # 输入模型,执行前向预测
  13. # 获取整个测试集的标签类别和预测类别
  14. _, preds = torch.max(outputs, 1) # 获得当前 batch 所有图像的预测类别
  15. preds = preds.cpu().numpy()
  16. loss = criterion(outputs, labels) # 由 logit,计算当前 batch 中,每个样本的平均交叉熵损失函数值
  17. loss = loss.detach().cpu().numpy()
  18. outputs = outputs.detach().cpu().numpy()
  19. labels = labels.detach().cpu().numpy()
  20. loss_list.append(loss)
  21. labels_list.extend(labels)
  22. preds_list.extend(preds)
  23. log_test = {}
  24. log_test['epoch'] = epoch
  25. # 计算分类评估指标
  26. log_test['test_loss'] = np.mean(loss_list)
  27. log_test['test_accuracy'] = accuracy_score(labels_list, preds_list)
  28. log_test['test_precision'] = precision_score(labels_list, preds_list, average='macro')
  29. log_test['test_recall'] = recall_score(labels_list, preds_list, average='macro')
  30. log_test['test_f1-score'] = f1_score(labels_list, preds_list, average='macro')
  31. return log_test

返回的log_test是测试日志

④登录wandb(可在网页、手机、iPad上实时监控日志)

安装 wandb:pip install wandb

登录 wandb:在命令行中运行wandb login

按提示复制粘贴API Key至命令行中

⑤创建wandb可视化项目

  1. import wandb
  2. wandb.init(project='视网膜病变', name=time.strftime('%m%d%H%M%S'))

⑥运行训练

  1. for epoch in range(1, EPOCHS+1):
  2. print(f'Epoch {epoch}/{EPOCHS}')
  3. ## 训练阶段
  4. model.train()
  5. for images, labels in tqdm(train_loader): # 获得一个 batch 的数据和标注
  6. batch_idx += 1
  7. log_train = train_one_batch(images, labels)
  8. df_train_log = df_train_log.append(log_train, ignore_index=True)
  9. wandb.log(log_train)
  10. lr_scheduler.step()
  11. ## 测试阶段
  12. model.eval()
  13. log_test = evaluate_testset()
  14. df_test_log = df_test_log.append(log_test, ignore_index=True)
  15. wandb.log(log_test)
  16. # 保存最新的最佳模型文件
  17. if log_test['test_accuracy'] > best_test_accuracy:
  18. # 删除旧的最佳模型文件(如有)
  19. old_best_checkpoint_path = 'checkpoint/best-{:.3f}.pth'.format(best_test_accuracy)
  20. if os.path.exists(old_best_checkpoint_path):
  21. os.remove(old_best_checkpoint_path)
  22. # 保存新的最佳模型文件
  23. best_test_accuracy = log_test['test_accuracy']
  24. new_best_checkpoint_path = 'checkpoint/best-{:.3f}.pth'.format(log_test['test_accuracy'])
  25. torch.save(model, new_best_checkpoint_path)
  26. print('保存新的最佳模型', 'checkpoint/best-{:.3f}.pth'.format(best_test_accuracy))
  27. # best_test_accuracy = log_test['test_accuracy']
  28. df_train_log.to_csv('训练日志-训练集.csv', index=False)
  29. df_test_log.to_csv('训练日志-测试集.csv', index=False)

wandb的监控结果如下所示:

声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/我家小花儿/article/detail/511650
推荐阅读
相关标签
  

闽ICP备14008679号