想想一下自己很久没有接触model, CNN, AI agent这些内容,但是最近自己突然由于各种原因需要重新接触这些内容,然后写出(或者是让AI写出)一个可以满足特定小需求的代码。

那当然是简洁,快速,不追求性能好不好,有个结果就可以,很符合我们今天的主题。

假如你现在处在这样的场景中,要做些什么才对呢?

environment

首先是确认自己的环境还能跑起来。

所幸第一步是非常简单的,我们有很多方法来配置当前所需要的环境,常见的环境管理方式有实际路径(当然是最不推荐的),python venv, conda, rye/uv.

后三个由于是虚拟环境,可以实现项目环境的隔离,不容易把环境整坏,而且也都可以通过requirement.txt/pyproject.toml实现环境的快速迁移和恢复,算是比较推荐的方式。我个人目前比较喜欢用rye来进行python环境的管理,这一部分内容在之前有写过,这里就不再说了,我这里都以rye作为默认python管理工具。

我们可以打开一个jupyter notebook,输入以下内容:

1
2
3
4
5
import torch
import torch.nn as nn

print(torch.__version__)
print(torch.cuda.is_available())

当然也默认是nvidia GPU

很简单,检查pytorch环境,以及GPU链路正常,保证torch可以正常(在GPU上)使用。

annoying syntax

这个确实是很让人生气的一环。我不可能每次都dir()+help()去查可恶的手册。不过这次我们相对聚焦于模型搭积木,那么相对来说要在短时间内记忆的东西不算多。

nn.module: 这个是搭积木的第一块。首先看以下格式:

1
2
3
4
5
6
7
8
class MyModel(nn.Module):
def __init__(self):
super().__init__()
# 模型搭积木

def forward(self, x):
# datapath
return x

这是一个很简单的格式,我们可以通过在def __init__(self):下完成模型的构建,然后在def forward(self, x):处写出数据处理。

model select

然后要想好要怎么选择模型,选择模型本质上是面对任务的三种姿态。

第一种,是选择从头开始,自己搭建(nn.Module)。需要了解nn.linear, nn.Conv2d,
nn.TransformerEncoderLayer这些,每一层都是在做什么,这个我们之后再说。这个方法本质上是需要比较清楚的知道自己想要做什么结构,或者是现在需要复现论文之类的,日常自己搭建模型走这条路太慢,暂时不用考虑。

第二种,是选择拿来改改给自己用(torchvision.models之类)。这种模型的结构和权重是现成的,,我们只需要稍微改改最后几层的行为来适配当前任务就行。这个是日常视觉任务最常见的姿势,resnet, efficientnet, vit等直接拿来用,换一个输出头就完了。

第三种,微调训练大模型(huggingface和modelscope)。其实本质和第二种是一样的,但是规模很大,适用范围更广,NLP,多模态等都可以在huggingface上面做,本质是AutoModel.from_pretrained()加载权重,接上自己的任务头,或者直接Trainer微调。

我们自己做一个小东西的话,一般直接选择第二种拿来主义即可。

train loop

然后我们需要对搭建好的模型进行训练。所谓训练,就是让模型反复自己计算结果,反复对答案,看看哪里对哪里错,然后修正自己,从而让自己的行为更加贴近正确行为。

调整模型一般需要记住几句关键statement,比如:

1
optimizer = torch.optim.Adam(model.parameters(), lr=1e-3)

这个很明显,就是指模型对完答案之后怎样优化自己。这里展示的是:使用”Adam”方法来调整模型的参数,每次调整的幅度是”1e-3”.

具体选择什么优化方式可以根据自己的应用场景问AI,不过一般Adam就行;lr一般不要太大,不然会模型参数会漫天乱漂,也不能太小,不然循环训练多少次改进很小学不动。这个可以问AI,不过自己运行一次也就知道该写多少了。

1
criterion = nn.CrossEntropyLoss()

这一步的含义是,用来计算模型本次的回答”有多错”,可以理解为打分。分类任务可以默认用这个,回归任务换成nn.MSELoss(),当然也可以让AI帮助选择。

1
2
3
4
5
6
for epoch in range(10):
optimizer.zero_grad()
output = model(x)
loss = criterion(output, y)
loss.backward()
optimizer.step()

optimizer.zero_grad()意味着每次循环,先把上次的梯度清空,保证本次训练没有被之前的数据污染。

output = model(x)就是把数据x喂给模型,让它给你预计结果。

loss = criterion(output, y)让模型把自己计算出来的结果和真实结果对比,看看相差多少,怎么改进。

loss.backward()把错误分数往回推算,看看每个参数应该往什么方向调整(对于模型参数改进很重要)。

optimizer.step()执行调整。(上一步是算出调整方向,这次整的调整模型参数)

流程就是:清空->前向计算->计算loss->反向计算->更新参数。

训练的时候有两个坑,第一个是要清空梯度,不然训练结果会非常诡异;第二个是只做inference的时候最好加上一层:

1
2
with torch.no_grad():
output = model(x)

如果不加的话pytorch还会在后台自己算梯度,会浪费显存,数据大的时候还会OOM.

OOM:CUDA Out Of Memory.

simple routine

需要完整记住的东西就以上这么多,其他小细节可以在写模型的过程中得知。来看一个简单的例子:

packages

1
2
3
4
5
6
import torch
import torch.nn as nn
import torchvision.models as models
import torchvision.transforms as transforms
from torchvision.datasets import CIFAR10
from torch.utils.data import DataLoader

这里就是引入需要的package.

transforms用于修整输入数据为模型可接受的格式,DataLoader用于加载数据,CIFAR10是一个torchvision内置的图像分类数据集,有10种图片,总共6w张图片,每张是32x32彩色图片。

当然可以输出看一看模型长什么样子。

select device

1
device = torch.device("cuda" if torch.cuda.is_available() else "cpu")

检测cuda是否可用,如果可用就在GPU上完成计算。

load data

1
2
3
4
5
6
7
8
transform = transforms.Compose([
transforms.Resize(224),
transforms.ToTensor(),
])

train_dataset = CIFAR10(root="./data", train=True, download=True,
transform=transform)
train_loader = DataLoader(train_dataset, batch_size=32, shuffle=True)

这里把数据整理成224x224,原因是之后用了resnet,需要数据是224x224才能喂进去,否则卷积池化之后特征图会变成负尺寸。具体什么模型需要什么输入,可以问AI.

train_dataset这一行,意味着获取数据集。root参数指的是数据存放路径,如果没有会创建文件夹;train=True是要下载训练集,如果是False就是下载测试集。

train_loader这里,DataLoader是把数据分批次,打乱顺序向外发送。

select model

1
2
3
model = models.resnet18(weights=None)
model.fc = nn.Linear(512, 10)
model = model.to(device)

这里选择的模型是resnet18,weights=None的含义是不导入已有权重,随机初始化权重,也就是从头训练;如果是迁移训练,就给weights传入已有权重。

model.fc = nn.Linear(512, 10)含义为,本来的resnet18最后一层是nn.Linear(512, 1000),但是这个数据集只有10中数据类型,所以输出头换为1.

这个fc是哪来的呢?我怎么知道最后一层是什么?我们可以通过print(model)看到模型长这个样子:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
ResNet(
(conv1): Conv2d(3, 64, kernel_size=(7, 7), stride=(2, 2), padding=(3, 3), bias=False)
(bn1): BatchNorm2d(64, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True)
(relu): ReLU(inplace=True)
(maxpool): MaxPool2d(kernel_size=3, stride=2, padding=1, dilation=1, ceil_mode=False)
(layer1): Sequential(
(0): BasicBlock(
(conv1): Conv2d(64, 64, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1), bias=False)
(bn1): BatchNorm2d(64, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True)
(relu): ReLU(inplace=True)
(conv2): Conv2d(64, 64, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1), bias=False)
(bn2): BatchNorm2d(64, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True)
)
(1): BasicBlock(
(conv1): Conv2d(64, 64, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1), bias=False)
(bn1): BatchNorm2d(64, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True)
(relu): ReLU(inplace=True)
(conv2): Conv2d(64, 64, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1), bias=False)
(bn2): BatchNorm2d(64, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True)
)
)
(layer2): Sequential(
(0): BasicBlock(
(conv1): Conv2d(64, 128, kernel_size=(3, 3), stride=(2, 2), padding=(1, 1), bias=False)
(bn1): BatchNorm2d(128, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True)
(relu): ReLU(inplace=True)
(conv2): Conv2d(128, 128, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1), bias=False)
(bn2): BatchNorm2d(128, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True)
(downsample): Sequential(
(0): Conv2d(64, 128, kernel_size=(1, 1), stride=(2, 2), bias=False)
(1): BatchNorm2d(128, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True)
)
)
(1): BasicBlock(
(conv1): Conv2d(128, 128, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1), bias=False)
(bn1): BatchNorm2d(128, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True)
(relu): ReLU(inplace=True)
(conv2): Conv2d(128, 128, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1), bias=False)
(bn2): BatchNorm2d(128, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True)
)
)
(layer3): Sequential(
(0): BasicBlock(
(conv1): Conv2d(128, 256, kernel_size=(3, 3), stride=(2, 2), padding=(1, 1), bias=False)
(bn1): BatchNorm2d(256, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True)
(relu): ReLU(inplace=True)
(conv2): Conv2d(256, 256, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1), bias=False)
(bn2): BatchNorm2d(256, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True)
(downsample): Sequential(
(0): Conv2d(128, 256, kernel_size=(1, 1), stride=(2, 2), bias=False)
(1): BatchNorm2d(256, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True)
)
)
(1): BasicBlock(
(conv1): Conv2d(256, 256, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1), bias=False)
(bn1): BatchNorm2d(256, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True)
(relu): ReLU(inplace=True)
(conv2): Conv2d(256, 256, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1), bias=False)
(bn2): BatchNorm2d(256, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True)
)
)
(layer4): Sequential(
(0): BasicBlock(
(conv1): Conv2d(256, 512, kernel_size=(3, 3), stride=(2, 2), padding=(1, 1), bias=False)
(bn1): BatchNorm2d(512, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True)
(relu): ReLU(inplace=True)
(conv2): Conv2d(512, 512, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1), bias=False)
(bn2): BatchNorm2d(512, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True)
(downsample): Sequential(
(0): Conv2d(256, 512, kernel_size=(1, 1), stride=(2, 2), bias=False)
(1): BatchNorm2d(512, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True)
)
)
(1): BasicBlock(
(conv1): Conv2d(512, 512, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1), bias=False)
(bn1): BatchNorm2d(512, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True)
(relu): ReLU(inplace=True)
(conv2): Conv2d(512, 512, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1), bias=False)
(bn2): BatchNorm2d(512, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True)
)
)
(avgpool): AdaptiveAvgPool2d(output_size=(1, 1))
(fc): Linear(in_features=512, out_features=10, bias=True)
)

看着挺吓人,但是其实结构还是挺清楚的。:前面的是该层的名字,后面是类似函数调用的表达式。

之后我们想要改哪一层就用哪个名字即可。比如我想要改第一层,第一层叫model.conv1,默认接收三通道RGB,但是我的图片是灰度图(假设),就把通道数改为1:

1
mnodel.conv1 = nn.Conv2d(1, 64, kernel_size=7, stride=2, padding-3, bias=False)

中间层一般不会改,如果要改的话不如从头自己搭(逃)

实际常见的改法除了直接修改输出结果类别数,还可以把最后改成多层,比如:

1
2
3
4
5
model.fc = nn.Sequential(
nn.Linear(512, 256),
nn.ReLU(),
nn.Linear(256,10)
)

这就相当于是多加了一层。具体怎么加,可以往下多看一会,了解一下”某一层”的结构是什么,或者也可以print(model)之后把model结构给AI,让它教我们怎么改。

对于小数据集,还有一种操作是,冻结之前的所有层,只训练最后一层用于适配任务要求即可:

1
2
3
for param in model.parameters():
param.requires_grad = False
model.fc = nn.Linear(512, 10)

也就是不计算所有层参数的梯度(当然也就不会更新模型参数),但是最后一层是之后修改的,默认是可训练的。

prepare train

1
2
optimizer = torch.optim.Adam(model.parameters(), lr=1e-3)
criterion = nn.CrossEntropyLoss()

经典Adam和CrossEntropyLoss,这两个在本期搭模型篇暂不解释。

train loops

1
2
3
4
5
6
7
8
9
10
11
model.train()
for epoch in range(5):
for x, y in train_loader:
x, y = x.to(device), y.to(device)

optimizer.zero_grad()
output = model(x)
loss = criterion(output, y)
loss.backward()
optimizer.step()
print(f"epoch {epoch + 1} done, loss: {loss.item():.4f}")

model.train()是切换到train mode. batchnorm, dropout这类层的行为会在不同mode下发生变化。

之后是一个双层循环,外层每跑完一次意味着训练所有数据被喂过一轮;内层每次循环都是取一个batch用作训练,直到训练集用完。

inference

1
2
3
4
5
6
7
model.eval()
with torch.no_grad():
sample, label = next(iter(train_loader))
sample = sample.to(device)
pred = model(sample)
print(f"pred class: {pred.argmax(dim=1)[:5]}")
print(f"real class: {label[:5]}")

推理部分,其实就是看看最后训练出来的model计算结果到底对不对。

model.eval()切换到eval mode,同时with torch.no_grad():暂时关闭梯度计算节省显存。

sample, label = next(iter(train_loader))的含义是取出一个batch的训练集来测试,这里是偷懒。当然正式应该是用测试集,也就是里面换成test_loader.

1
2
3
test_dataset = CIFAR10(root="./data", train=False, download=True,
transform=transform)
test_loader = DataLoader(test_dataset, batch_size=32, shuffle=False)

省一点流量求求了

最后就是print的含义是取出测试batch的前5个结果。

argmax就是取分数最大的类别作为最后预测类别(dim=1, 就是沿着第一个维度取最大值;pred的shape是[32,10],32为样本数bath_size, 10是10个类别)。

extension1 : model archtecture

看模型结构,除了print(model),还有其他方式。

print只能看到层的名字和结构,看不到输出尺寸,局限性很大,这个适用于(大改的时候)你对模型比较熟,或者要求不很高只改最后一层就可以。

对于pytorch来说,有看参数的方式:

1
2
3
4
5
6
total = sum(p.numel() for p in model.parameters())
print(f"parameter size: {total:,}")

#只看可训练参数
trainable = sum(p.numel() for p in model.parameters() if p.requires_grad)
print(f"trainable parameter size: {trainable:,}")

但是这还是有点原始了。隔壁keras就有很现代model.summary看输出尺寸。

有的兄弟,有的!不过要额外下载:rye add torchinfo.

之后就可以通过:

1
2
from torchinfo import summary
summary(model, input_size=(1,3,224,224))

这个输出和隔壁keras几乎一样。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
==========================================================================================
Layer (type:depth-idx) Output Shape Param #
==========================================================================================
ResNet [1, 10] --
├─Conv2d: 1-1 [1, 64, 112, 112] 9,408
├─BatchNorm2d: 1-2 [1, 64, 112, 112] 128
├─ReLU: 1-3 [1, 64, 112, 112] --
├─MaxPool2d: 1-4 [1, 64, 56, 56] --
├─Sequential: 1-5 [1, 64, 56, 56] --
│ └─BasicBlock: 2-1 [1, 64, 56, 56] --
│ │ └─Conv2d: 3-1 [1, 64, 56, 56] 36,864
│ │ └─BatchNorm2d: 3-2 [1, 64, 56, 56] 128
│ │ └─ReLU: 3-3 [1, 64, 56, 56] --
│ │ └─Conv2d: 3-4 [1, 64, 56, 56] 36,864
│ │ └─BatchNorm2d: 3-5 [1, 64, 56, 56] 128
│ │ └─ReLU: 3-6 [1, 64, 56, 56] --
│ └─BasicBlock: 2-2 [1, 64, 56, 56] --
│ │ └─Conv2d: 3-7 [1, 64, 56, 56] 36,864
│ │ └─BatchNorm2d: 3-8 [1, 64, 56, 56] 128
│ │ └─ReLU: 3-9 [1, 64, 56, 56] --
│ │ └─Conv2d: 3-10 [1, 64, 56, 56] 36,864
│ │ └─BatchNorm2d: 3-11 [1, 64, 56, 56] 128
│ │ └─ReLU: 3-12 [1, 64, 56, 56] --
├─Sequential: 1-6 [1, 128, 28, 28] --
│ └─BasicBlock: 2-3 [1, 128, 28, 28] --
│ │ └─Conv2d: 3-13 [1, 128, 28, 28] 73,728
│ │ └─BatchNorm2d: 3-14 [1, 128, 28, 28] 256
│ │ └─ReLU: 3-15 [1, 128, 28, 28] --
│ │ └─Conv2d: 3-16 [1, 128, 28, 28] 147,456
│ │ └─BatchNorm2d: 3-17 [1, 128, 28, 28] 256
│ │ └─Sequential: 3-18 [1, 128, 28, 28] 8,448
│ │ └─ReLU: 3-19 [1, 128, 28, 28] --
│ └─BasicBlock: 2-4 [1, 128, 28, 28] --
│ │ └─Conv2d: 3-20 [1, 128, 28, 28] 147,456
│ │ └─BatchNorm2d: 3-21 [1, 128, 28, 28] 256
│ │ └─ReLU: 3-22 [1, 128, 28, 28] --
│ │ └─Conv2d: 3-23 [1, 128, 28, 28] 147,456
│ │ └─BatchNorm2d: 3-24 [1, 128, 28, 28] 256
│ │ └─ReLU: 3-25 [1, 128, 28, 28] --
├─Sequential: 1-7 [1, 256, 14, 14] --
│ └─BasicBlock: 2-5 [1, 256, 14, 14] --
│ │ └─Conv2d: 3-26 [1, 256, 14, 14] 294,912
│ │ └─BatchNorm2d: 3-27 [1, 256, 14, 14] 512
│ │ └─ReLU: 3-28 [1, 256, 14, 14] --
│ │ └─Conv2d: 3-29 [1, 256, 14, 14] 589,824
│ │ └─BatchNorm2d: 3-30 [1, 256, 14, 14] 512
│ │ └─Sequential: 3-31 [1, 256, 14, 14] 33,280
│ │ └─ReLU: 3-32 [1, 256, 14, 14] --
│ └─BasicBlock: 2-6 [1, 256, 14, 14] --
│ │ └─Conv2d: 3-33 [1, 256, 14, 14] 589,824
│ │ └─BatchNorm2d: 3-34 [1, 256, 14, 14] 512
│ │ └─ReLU: 3-35 [1, 256, 14, 14] --
│ │ └─Conv2d: 3-36 [1, 256, 14, 14] 589,824
│ │ └─BatchNorm2d: 3-37 [1, 256, 14, 14] 512
│ │ └─ReLU: 3-38 [1, 256, 14, 14] --
├─Sequential: 1-8 [1, 512, 7, 7] --
│ └─BasicBlock: 2-7 [1, 512, 7, 7] --
│ │ └─Conv2d: 3-39 [1, 512, 7, 7] 1,179,648
│ │ └─BatchNorm2d: 3-40 [1, 512, 7, 7] 1,024
│ │ └─ReLU: 3-41 [1, 512, 7, 7] --
│ │ └─Conv2d: 3-42 [1, 512, 7, 7] 2,359,296
│ │ └─BatchNorm2d: 3-43 [1, 512, 7, 7] 1,024
│ │ └─Sequential: 3-44 [1, 512, 7, 7] 132,096
│ │ └─ReLU: 3-45 [1, 512, 7, 7] --
│ └─BasicBlock: 2-8 [1, 512, 7, 7] --
│ │ └─Conv2d: 3-46 [1, 512, 7, 7] 2,359,296
│ │ └─BatchNorm2d: 3-47 [1, 512, 7, 7] 1,024
│ │ └─ReLU: 3-48 [1, 512, 7, 7] --
│ │ └─Conv2d: 3-49 [1, 512, 7, 7] 2,359,296
│ │ └─BatchNorm2d: 3-50 [1, 512, 7, 7] 1,024
│ │ └─ReLU: 3-51 [1, 512, 7, 7] --
├─AdaptiveAvgPool2d: 1-9 [1, 512, 1, 1] --
├─Linear: 1-10 [1, 10] 5,130
==========================================================================================
Total params: 11,181,642
Trainable params: 11,181,642
Non-trainable params: 0
Total mult-adds (Units.GIGABYTES): 1.81
==========================================================================================
Input size (MB): 0.60
Forward/backward pass size (MB): 39.74
Params size (MB): 44.73
Estimated Total Size (MB): 85.07
==========================================================================================

input_size(batch_size, channel, hight, width). 这个和隔壁的NHWC不一样,隔壁是通道数在最后。

可以稍微分析一下模型结构:

第一步是前四层:

1
2
Conv2d → BatchNorm2d → ReLU → MaxPool2d
224x224 → 112x112 → 56x56

这个其实是一个快速预处理,减小尺寸,减小计算量。

第二步是四组sequential,每组两个basicblock(这个后面会说模型层级结构,可以之后再理解),每个basicblock都是:Conv2d → BN → ReLU → Conv2d → BN → ReLU.

四组操作之后尺寸大大缩小,特征增加。(经典的空间换信息,这个之后也会说)

第三步就是输出头,将[1,512,1,1],这里最后把7x7的图压缩成1x1,相当于只保留最后的信息,通过这个信息(512种信息中的一种),判断最后是哪个类别。

最后就是分类头,最终输出的结果应该是分成几个类。

除了模型结构,下面几行也是很重要的:

  • total params: 11,181,642,大约有11M个参数。
  • estimated total size: 85MB,大约需要85MB显存,如果换成batch_size=32的话还要乘32,看看会不会OOM.
  • Non-trainable params:0这里会显示冻结了多少层。

extension2 : tqdm

我最讨厌没有任何反馈的干等了,所以要给train加上进度条。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
from tqdm import tqdm

model.train()
for epoch in range(5):
loop = tqdm(train_loader, desc=f"epoch {epoch+1}/5", leave=True)

running_loss = 0.0
for i, (x, y) in enumerate(loop):
x, y = x.to(device), y.to(device)
optimizer.zero_grad()
output = model(x)
loss = criterion(output, y)
loss.backward()
optimizer.step()

running_loss +=loss.item()
avg_loss = running_loss/(i+1)

loop.set_postfix(loss=f"{avg_loss:.4f}")

其实只需要用tqdm(train_loader)把dataloader包一层就行。

desc是最左边的标题。leave的含义是每个epoch跑完之后进度条不消失;set_postfix是实时更新进度条右侧显示的内容,跑起来会让人感觉程序还在蠕动。

layers

之前我们讲述了什么是拿来主义,现在来看看搭建模型的第一种方式:如何自己从头自定义模型。

containerlayers

component layers

advanced layers

loss

optimizer

Large Models From Here homepage

click here to homepage.