Issues (9)

train.py (6 issues)

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
The variable AverageMeter does not seem to be defined.
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
The variable accuracy does not seem to be defined.
Loading history...
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
The variable AverageMeter does not seem to be defined.
Loading history...
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
The variable accuracy does not seem to be defined.
Loading history...
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
The variable SummaryWriter does not seem to be defined.
Loading history...
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
The variable model_dict does not seem to be defined.
Loading history...
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)  # 开始训练过程