QiuBiaoer /
transformer
| 1 | ############################################################################################################ |
||
| 2 | # 1. 使用argparse类实现可以在训练的启动命令中指定超参数 |
||
| 3 | # 2. 可以通过在启动命令中指定 --seed 随机数种子来固定网络的初始化方式,以达到结果可复现的效果 |
||
| 4 | # 3. 使用了更高级的学习策略 cosine annealing:在训练的第一轮使用一个较小的lr(warm_up),从第二个epoch开始,随训练轮数逐渐减小lr。 |
||
| 5 | # 4. 可以通过在启动命令中指定 --model 来选择使用的模型 |
||
| 6 | # 5. 新加了weight-decay权重衰减项,防止过拟合 |
||
| 7 | # 6. 新加了记录每个epoch的loss和acc的log文件及可用于tensorboard可视化的文件 |
||
| 8 | # 7. 可以通过在启动命令中指定 --tensorboard 来进行tensorboard可视化, 默认不启用。 |
||
| 9 | # 注意,使用tensorboad之前需要使用命令 "tensorboard --logdir=log_path"来启动,结果通过网页 http://localhost:6006/'查看可视化结果 |
||
| 10 | ############################################################################################################ |
||
| 11 | # --model 可选的超参如下: |
||
| 12 | # alexnet vgg googlenet resnet densenet mobilenet shufflenet |
||
| 13 | # efficient convnext vision_transformer swin_transformer |
||
| 14 | ############################################################################################################ |
||
| 15 | # 训练命令示例: # python train.py --model resnet18 --batch_size 64 --lr 0.001 --epoch 100 --classes_num 4 |
||
| 16 | ############################################################################################################ |
||
| 17 | |||
| 18 | import argparse # 用于解析命令行参数 |
||
| 19 | import torch |
||
| 20 | import torch.optim as optim # PyTorch中的优化器 |
||
| 21 | from torch.utils.data import DataLoader # PyTorch中用于加载数据的工具 |
||
| 22 | from tqdm import tqdm # 用于在循环中显示进度条 |
||
| 23 | from torch.optim.lr_scheduler import CosineAnnealingLR # 余弦退火学习率调度器 |
||
| 24 | import torch.nn.functional as F # PyTorch中的函数库 |
||
| 25 | from torchvision import datasets # PyTorch中的视觉数据集 |
||
| 26 | import torchvision.transforms as transforms # PyTorch中的数据变换操作 |
||
| 27 | # from tensorboardX import SummaryWriter # 用于创建TensorBoard日志的工具 |
||
| 28 | import os # Python中的操作系统相关功能 |
||
| 29 | # from utils import AverageMeter, accuracy # 自定义工具模块,用于计算模型的平均值和准确度 |
||
| 30 | # from model import model_dict # 自定义模型字典,包含了各种模型的定义 |
||
| 31 | import numpy as np # NumPy库,用于数值计算 |
||
| 32 | import time # Python中的时间相关功能 |
||
| 33 | import random # Python中的随机数生成器 |
||
| 34 | |||
| 35 | parser = argparse.ArgumentParser() # 导入argparse模块,用于解析命令行参数 |
||
| 36 | parser.add_argument("--model_names", type=str, default="vit") # 添加命令行参数,指定模型名称 |
||
| 37 | parser.add_argument("--pre_trained", type=bool, default=False) #指定是否使用预训练模型,默认为False |
||
| 38 | parser.add_argument("--classes_num", type=int, default=4) # 指定类别数,默认为4 |
||
| 39 | parser.add_argument("--dataset", type=str, default="dataset\COVID_19_Radiography_Dataset") # 指定数据集名称,默认为"new_COVID_19_Radiography_Dataset" |
||
| 40 | parser.add_argument("--batch_size", type=int, default=16) # 指定批量大小,默认为64 |
||
| 41 | parser.add_argument("--epoch", type=int, default=20) # 指定训练轮次数,默认为20 |
||
| 42 | parser.add_argument("--lr", type=float, default=0.01) # 指定学习率,默认为0.01 |
||
| 43 | parser.add_argument("--momentum", type=float, default=0.9) # 优化器的动量,默认为 0.9 |
||
| 44 | parser.add_argument("--weight-decay", type=float, default=1e-4) # 权重衰减(正则化项),默认为 5e-4 |
||
| 45 | parser.add_argument("--seed", type=int, default=33) # 指定随机种子,默认为33 |
||
| 46 | parser.add_argument("--gpu-id", type=int, default=0) # 指定GPU编号,默认为0 |
||
| 47 | parser.add_argument("--print_freq", type=int, default=1) # 打印训练信息的频率,默认为 1(每个轮次打印一次) |
||
| 48 | parser.add_argument("--exp_postfix", type=str, default="seed33") # 实验结果文件夹的后缀,默认为 "seed33" |
||
| 49 | parser.add_argument("--txt_name", type=str, default="lr0.01_wd5e-4") # 文本文件名称,默认为 "lr0.01_wd5e-4" |
||
| 50 | |||
| 51 | args = parser.parse_args() |
||
| 52 | |||
| 53 | def seed_torch(seed=74): |
||
| 54 | # 设置随机数生成器的种子,确保实验的可重复性 |
||
| 55 | random.seed(seed) |
||
| 56 | np.random.seed(seed) |
||
| 57 | torch.manual_seed(seed) |
||
| 58 | torch.cuda.manual_seed(seed) |
||
| 59 | torch.cuda.manual_seed_all(seed) |
||
| 60 | |||
| 61 | seed_torch(seed=args.seed) |
||
| 62 | os.environ["CUDA_VISIBLE_DEVICES"] = str(args.gpu_id) # 设置环境变量 CUDA_VISIBLE_DEVICES,指定可见的 GPU 设备,仅在需要时使用特定的 GPU 设备进行训练 |
||
| 63 | |||
| 64 | exp_name = args.exp_postfix # 从命令行参数中获取实验名称后缀 |
||
| 65 | exp_path = "./report/{}/{}/{}".format(args.dataset, args.model_names, exp_name) # 创建实验结果文件夹的路径 |
||
| 66 | os.makedirs(exp_path, exist_ok=True) |
||
| 67 | |||
| 68 | # dataloader |
||
| 69 | transform_train = transforms.Compose([transforms.RandomRotation(90), # 随机旋转图像 |
||
| 70 | transforms.Resize([256, 256]), # # 调整图像大小为 256x256 像素 |
||
| 71 | transforms.RandomCrop(224), # 随机裁剪图像为 224x224 大小 |
||
| 72 | transforms.RandomHorizontalFlip(), # 随机水平翻转图像 |
||
| 73 | transforms.ToTensor(), |
||
| 74 | transforms.Normalize((0.3738, 0.3738, 0.3738), # # 对图像进行标准化 |
||
| 75 | (0.3240, 0.3240, 0.3240))]) |
||
| 76 | transform_test = transforms.Compose([transforms.Resize([224, 224]), |
||
| 77 | transforms.ToTensor(), |
||
| 78 | transforms.Normalize((0.3738, 0.3738, 0.3738), |
||
| 79 | (0.3240, 0.3240, 0.3240))]) |
||
| 80 | trainset = datasets.ImageFolder(root=os.path.join(r'dataset\COVID_19_Radiography_Dataset', 'train'), |
||
| 81 | transform=transform_train) |
||
| 82 | testset = datasets.ImageFolder(root=os.path.join(r'dataset\COVID_19_Radiography_Dataset', 'val'), |
||
| 83 | transform=transform_test) |
||
| 84 | |||
| 85 | # 创建训练数据加载器 |
||
| 86 | train_loader = DataLoader(trainset, batch_size=args.batch_size, num_workers=4, |
||
| 87 | # 后台工作线程数量,可以并行加载数据以提高效率 |
||
| 88 | shuffle=True, pin_memory=True) # 如果可用,将数据加载到 GPU 内存中以提高训练速度 |
||
| 89 | # 创建测试数据加载器 |
||
| 90 | test_loader = DataLoader(testset, batch_size=args.batch_size, num_workers=4, |
||
| 91 | shuffle=False, pin_memory=True) |
||
| 92 | |||
| 93 | # train |
||
| 94 | def train_one_epoch(model, optimizer, train_loader): |
||
| 95 | model.train() |
||
| 96 | acc_recorder = AverageMeter() # 用于记录精度的工具 |
||
|
0 ignored issues
–
show
Comprehensibility
Best Practice
introduced
by
Loading history...
|
|||
| 97 | loss_recorder = AverageMeter() # 用于记录损失的工具 |
||
| 98 | |||
| 99 | for (inputs, targets) in tqdm(train_loader, desc="train"): # 遍历训练数据加载器 train_loader 中的每个批次数据,使用 tqdm 包装以显示进度条。 |
||
| 100 | # for i, (inputs, targets) in enumerate(train_loader): |
||
| 101 | if torch.cuda.is_available(): # 如果当前设备支持 CUDA 加速,则将输入数据和目标数据送到 GPU 上进行计算,设置 non_blocking=True 可以使数据异步加载,提高效率。 |
||
| 102 | inputs = inputs.cuda(non_blocking=True) |
||
| 103 | targets = targets.cuda(non_blocking=True) |
||
| 104 | |||
| 105 | out = model(inputs) |
||
| 106 | loss = F.cross_entropy(out, targets) # 计算损失(交叉熵损失) |
||
| 107 | loss_recorder.update(loss.item(), n=inputs.size(0)) # 记录损失值 # 调用 update 方法,传入当前批次的损失值 loss.item() 和该批次的样本数量 inputs.size(0)。 # 这样做是为了根据样本数量加权计算损失的平均值,确保不同批次的损失贡献相等,而不受批次大小的影响。 |
||
| 108 | acc = accuracy(out, targets)[0] # 计算精度 |
||
|
0 ignored issues
–
show
Comprehensibility
Best Practice
introduced
by
|
|||
| 109 | acc_recorder.update(acc.item(), n=inputs.size(0)) # 记录精度值 |
||
| 110 | optimizer.zero_grad() # 清零之前的梯度 |
||
| 111 | loss.backward() # 反向传播,计算梯度 |
||
| 112 | optimizer.step() # 更新模型参数 |
||
| 113 | |||
| 114 | losses = loss_recorder.avg # 计算平均损失 |
||
| 115 | acces = acc_recorder.avg # 计算平均精度 |
||
| 116 | |||
| 117 | return losses, acces # 返回平均损失和平均精度 |
||
| 118 | |||
| 119 | def evaluation(model, test_loader): |
||
| 120 | # 将模型设置为评估模式,不会进行参数更新 |
||
| 121 | model.eval() |
||
| 122 | acc_recorder = AverageMeter() # 初始化两个计量器,用于记录准确度和损失 |
||
|
0 ignored issues
–
show
Comprehensibility
Best Practice
introduced
by
|
|||
| 123 | loss_recorder = AverageMeter() |
||
| 124 | |||
| 125 | with torch.no_grad(): |
||
| 126 | for img, label in tqdm(test_loader, desc="Evaluating"): |
||
| 127 | # for img, label in test_loader: # 迭代测试数据加载器中的每个批次 |
||
| 128 | if torch.cuda.is_available(): |
||
| 129 | img = img.cuda() |
||
| 130 | label = label.cuda() |
||
| 131 | |||
| 132 | out = model(img) |
||
| 133 | acc = accuracy(out, label)[0] # 计算准确度和损失 |
||
|
0 ignored issues
–
show
Comprehensibility
Best Practice
introduced
by
|
|||
| 134 | loss = F.cross_entropy(out, label) # 计算交叉熵损失 |
||
| 135 | acc_recorder.update(acc.item(), img.size(0)) # 更新准确率记录器,记录当前批次的准确率 img.size(0)表示批次中的样本数量 |
||
| 136 | loss_recorder.update(loss.item(), img.size(0)) # 更新损失记录器,记录当前批次的损失 |
||
| 137 | losses = loss_recorder.avg # 计算所有批次的平均损失 |
||
| 138 | acces = acc_recorder.avg # 计算所有批次的平均准确率 |
||
| 139 | return losses, acces # 返回平均损失和准确率 |
||
| 140 | |||
| 141 | def train(model, optimizer, train_loader, test_loader, scheduler): |
||
| 142 | since = time.time() # 记录训练开始时间 |
||
| 143 | best_acc = -1 # 初始化最佳准确度为-1,以便跟踪最佳模型 |
||
| 144 | f = open(os.path.join(exp_path, "{}.txt".format(args.txt_name)), "w") # 打开一个用于写入训练过程信息的文件 |
||
| 145 | |||
| 146 | for epoch in range(args.epoch): |
||
| 147 | # 在训练集上执行一个周期的训练,并获取训练损失和准确度 |
||
| 148 | train_losses, train_acces = train_one_epoch( |
||
| 149 | model, optimizer, train_loader |
||
| 150 | ) |
||
| 151 | # 在测试集上评估模型性能,获取测试损失和准确度 |
||
| 152 | test_losses, test_acces = evaluation(model, test_loader) |
||
| 153 | # 如果当前测试准确度高于历史最佳准确度,更新最佳准确度并保存模型参数 |
||
| 154 | if test_acces > best_acc: |
||
| 155 | best_acc = test_acces |
||
| 156 | state_dict = dict(epoch=epoch + 1, model=model.state_dict(), acc=test_acces) |
||
| 157 | name = os.path.join(exp_path, "ckpt", "best.pth") |
||
| 158 | os.makedirs(os.path.dirname(name), exist_ok=True) |
||
| 159 | torch.save(state_dict, name) |
||
| 160 | |||
| 161 | scheduler.step() # 更新学习率调度器 |
||
| 162 | |||
| 163 | tags = ['train_losses', # 定义要记录的训练信息的标签 |
||
| 164 | 'train_acces', |
||
| 165 | 'test_losses', |
||
| 166 | 'test_acces'] |
||
| 167 | tb_writer.add_scalar(tags[0], train_losses, epoch + 1) # 将训练信息写入TensorBoard |
||
| 168 | tb_writer.add_scalar(tags[1], train_acces, epoch + 1) |
||
| 169 | tb_writer.add_scalar(tags[2], test_losses, epoch + 1) |
||
| 170 | tb_writer.add_scalar(tags[3], test_acces, epoch + 1) |
||
| 171 | |||
| 172 | # 打印训练过程信息,以及将信息写入文件 |
||
| 173 | if (epoch + 1) % args.print_freq == 0: # print_freq指定为1 则每轮都打印 |
||
| 174 | msg = "epoch:{} model:{} train loss:{:.2f} acc:{:.2f} test loss{:.2f} acc:{:.2f}\n".format( |
||
| 175 | epoch + 1, |
||
| 176 | args.model_names, |
||
| 177 | train_losses, |
||
| 178 | train_acces, |
||
| 179 | test_losses, |
||
| 180 | test_acces, |
||
| 181 | ) |
||
| 182 | print(msg) |
||
| 183 | f.write(msg) |
||
| 184 | f.flush() |
||
| 185 | # 输出训练结束后的最佳准确度和总训练时间 |
||
| 186 | msg_best = "model:{} best acc:{:.2f}\n".format(args.model_names, best_acc) |
||
| 187 | time_elapsed = "traninng time: {}".format(time.time() - since) |
||
| 188 | print(msg_best) |
||
| 189 | f.write(msg_best) |
||
| 190 | f.write(time_elapsed) |
||
| 191 | f.close() |
||
| 192 | |||
| 193 | if __name__ == "__main__": |
||
| 194 | tb_path = "runs/{}/{}/{}".format(args.dataset, args.model_names, # 创建 TensorBoard 日志目录路径 |
||
| 195 | args.exp_postfix) |
||
| 196 | tb_writer = SummaryWriter(log_dir=tb_path) |
||
|
0 ignored issues
–
show
Comprehensibility
Best Practice
introduced
by
|
|||
| 197 | lr = args.lr |
||
| 198 | model = model_dict[args.model_names](num_classes=args.classes_num, pretrained=args.pre_trained) # 根据命令行参数创建神经网络模型 |
||
|
0 ignored issues
–
show
Comprehensibility
Best Practice
introduced
by
|
|||
| 199 | if torch.cuda.is_available(): |
||
| 200 | model = model.cuda() |
||
| 201 | |||
| 202 | optimizer = optim.SGD( # 创建随机梯度下降 (SGD) 优化器 |
||
| 203 | model.parameters(), |
||
| 204 | lr=lr, |
||
| 205 | momentum=args.momentum, |
||
| 206 | nesterov=True, |
||
| 207 | weight_decay=args.weight_decay, |
||
| 208 | ) |
||
| 209 | scheduler = CosineAnnealingLR(optimizer, T_max=args.epoch) # 创建余弦退火学习率调度器 自动调整lr |
||
| 210 | |||
| 211 | train(model, optimizer, train_loader, test_loader, scheduler) # 开始训练过程 |