入门

安装

推荐使用 uv 安装。

uv add git+https://github.com/wliafe/mltools.git

或者使用 pip 安装。

pip install git+https://github.com/wliafe/mltools.git

编写一个项目

项目名称为 mlp,项目内容为利用 mlp 对 mnist 数据集进行分类。

项目结构

project
├── data
│   ├── MNIST
│   └── .gitignore
├── results
│   ├── <time_str>-<file_name>
│   │   ├── <file_name>.json
│   │   ├── <file_name>.log
│   │   ├── <file_name>.png
│   │   └── <file_name>.pt
│   └── .gitignore
├── src
│   ├── dataloader.py
│   └── <file_name>.ipynb
├── .gitignore
└── README.md

其中,src 目录下存放的是 mlp 的代码,results 目录下存放的是 mlp 的结果,data 目录下存放的是 mnist 数据集。

数据集获取

def mnist(path='../data', batch_size=100):
    download = False if Path(f'{path}/MNIST').exists() else True
    trans = transforms.ToTensor()  # 数据集格式转换
    train_data = datasets.MNIST(root=path, train=True, transform=trans, download=download)
    test_data = datasets.MNIST(root=path, train=False, transform=trans, download=download)
    train_data, val_data = mltools.split_data(train_data, [9, 1])  # 训练集和验证集比例9:1
    return mltools.iter_data([train_data, val_data, test_data], batch_size)  # 返回数据迭代器

使用 split_data() 按比例划分数据集。

使用 iter_data() 构建 DataLoader 类。

参数设置

device = torch.device("cuda")
train_iter, val_iter, test_iter = dl.mnist(batch_size=1000)  # 获取训练集、验证集、测试集
model = nn.Sequential(nn.Flatten(), nn.Linear(784, 256), nn.ReLU(), nn.Linear(256, 10))  # 设置模型结构
model.to(device)
loss = nn.CrossEntropyLoss()
optimizer = optim.SGD(model.parameters(), lr=1e-1)
ml = mltools.MachineLearning("mlp")
ml.add_model(model)
epoch, timer, recorder = ml.batch_create()

每次初始化 MachineLearning 类时都将生成 results/<time_str>-<file_name> 文件夹,文件夹的命名格式为 时间-文件名,其中时间格式为 年-月-日-时-分-秒,文件夹中包含的文件如下:

  • 文件名.json:训练结果

  • 文件名.log:训练日志

  • 文件名.png:训练曲线

  • 文件名.pt:模型参数

v1.0.11 新增创建 data 目录,并在 results 和 data 目录中自动创建 .gitignore 文件,以在 git 版本控制中忽略整个文件夹。 v1.0.11 模型参数文件后缀为 .pth

使用 add_model() 将模型纳入管理。

使用 batch_create() 创建 EpochTimerRecorder 实例。

使用 create_epoch()create_timer()create_recorder() 分别创建 EpochTimerRecorder 实例。

训练模型

# 训练模型
num_epochs = epoch(20)
animator = ml.create_animator(
    xlabel="epoch", xlim=[0, epoch.totol_epoch + 1], ylim=-0.1, legend=["train loss", "train acc", "val acc"]
)  # 创建动画器
for current_epoch in range(1, num_epochs + 1):
    timer.start()  # 开始计时

    # 计算训练集
    metric_train = mltools.Accumulator(3)  # 累加器:(train_loss, train_acc, train_size)
    model.train()  # 训练模式
    for x, y in train_iter:
        x, y = x.to(device), y.to(device)  # 转换x、y
        y_train = model(x)  # 计算模型
        train_loss = loss(y_train, y)  # 计算损失
        train_pred = y_train.argmax(dim=1)  # 计算预测值
        train_acc = (train_pred == y).sum()  # 计算准确数

        # 梯度更新
        optimizer.zero_grad()
        train_loss.backward()
        optimizer.step()

        metric_train.add(train_loss * y.numel(), train_acc, y.numel())  # 累加损失、准确数、样本数
    recorder[0].append(metric_train[0] / metric_train[2])  # 计算平均损失
    recorder[1].append(metric_train[1] / metric_train[2])  # 计算准确率

    # 计算验证集
    metric_val = mltools.Accumulator(2)  # 累加器:(val_loss, val_size)
    model.eval()  # 验证模式
    with torch.no_grad():
        for x, y in val_iter:
            x, y = x.to(device), y.to(device)  # 转换x、y
            y_val = model(x)  # 计算模型
            val_pred = y_val.argmax(dim=1)  # 计算预测值
            val_acc = (val_pred == y).sum()  # 计算准确数
            metric_val.add(val_acc, y.numel())  # 累加准确数、样本数
    recorder[2].append(metric_val[0] / metric_val[1])  # 计算准确率

    timer.stop()  # 停止计时

    # 打印输出值
    ml.logger.info(f"train loss {recorder[0][-1]:.3f}, train acc {recorder[1][-1]:.3f}, val acc {recorder[2][-1]:.3f}")
    ml.print_training_time_massage(timer, num_epochs, current_epoch)
    ml.logger.info(f"trained on {str(device)}")
    animator.show(recorder.data)
else:
    # 打印输出值
    ml.logger.info(f"train loss {recorder[0][-1]:.3f}, train acc {recorder[1][-1]:.3f}, val acc {recorder[2][-1]:.3f}")
    ml.print_training_time_massage(timer, num_epochs, current_epoch)
    ml.logger.info(f"trained on {str(device)}")
    animator.show(recorder.data)
ml.save()

Epoch 是循环器,用于循环训练模型。其中参数 num_epochs 为训练迭代总次数,并不是训练次数,因此当你已经迭代20次想再迭代20次时, num_epochs 应该为40。

使用 create_animator() 创建 Animator 实例。

Animator 是动画器,用于绘制训练过程中的变化趋势。

Accumulator 是累加器,这里初始化参数为3,因此创建的是三元累加器。

Recorder 是记录器,记录每一次循环的数据,例如:训练损失、训练集准确率、验证集准确率等。Recorder 记录的数据可以作为动画器画图的数据来源。

Timer 是计时器,用于记录训练时间。

所有的打印输出一律使用 logger 打印,方便记入日志。

print_training_time_massage() 是打印训练时间的函数,用于打印已训练时长、平均训练时长、预估剩余训练时长。

save() 用于保存所有 MachineLearning 创建和添加的工具的信息,具体的说就是 epoch、timer、recorder、model、animator 等信息。

使用 load() 加载之前保存的 MachineLearning 实例,可以在上次训练的基础上继续训练。

测试模型

model.eval()
metric = mltools.Accumulator(2)  # 累加器:(test_acc, test_size)
with torch.no_grad():
    for x, y in test_iter:
        x, y = x.to(device), y.to(device)  # 转换x、y
        y_test = model(x)  # 计算模型
        test_pred = y_test.argmax(dim=1)  # 计算预测值
        test_acc = (test_pred == y).sum()  # 计算准确数
        metric.add(test_acc, y.numel())  # 累加准确数、样本数
ml.logger.info(f"test acc {metric[0] / metric[1]:.3f}")  # 计算准确率并输出

预测模型

model.eval()
x, y = next(iter(test_iter))  # 从测试中取一个批量
x, y = x[:10].to(device), y[:10].to(device)  # 转换x、y
y_pred = model(x)  # 计算模型
y_pred = y_pred.argmax(dim=1)  # 计算预测值
labels = [f"real:{y[index]}\npred:{y_pred[index]}" for index in range(y.numel())]
mltools.images(x.squeeze(1), labels, shape=(2, 5))

images() 用于绘制图片。