深度学习——模型训练篇
Yang Li Lv1

介绍

这一章节主要介绍模型如何搭建起来的,以及模型上的一些训练方法。

1.模型搭建

1.1 直接训练

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
loss_list = []
acc_list = []

class Network(nn.Module):
def __init__(self):
super(Network,self).__init__()
self.conv1 = nn.Conv2d(in_channels=3, out_channels=256, kernel_size=7,stride=2)
self.conv2 = nn.Conv2d(in_channels=256, out_channels=16, kernel_size=7,stride=2)
self.conv3 = nn.Conv2d(in_channels=16, out_channels=16, kernel_size=5)
self.conv4 = nn.Conv2d(in_channels=16, out_channels=16, kernel_size=[8,4])

self.fc1 = nn.Linear(in_features=16, out_features=16)
self.out = nn.Linear(in_features=16, out_features=2)
def forward(self, t):
#Layer 1
t = t
#Layer 2
t = self.conv1(t)
t = F.relu(t)
t = F.max_pool2d(t, kernel_size=2, stride=2)#output shape : (6,14,14)
#Layer 3
t = self.conv2(t)
t = F.relu(t)
t = F.max_pool2d(t, kernel_size=2, stride=2)#output shape : (6,14,14)
#Layer 4
t = self.conv3(t)
t = F.relu(t)
t = F.max_pool2d(t, kernel_size=2, stride=2)#output shape : (6,14,14)
#Layer 5
t = self.conv4(t)
t = F.relu(t)

#Layer 5
t=t.flatten(start_dim=1)
t = self.fc1(t)
t = F.relu(t)#output shape : (1,120)
#Layer 5
t = self.out(t)

return t

network = Network()

print(network)
optimizer = optim.Adam(network.parameters(), lr=0.001, betas=(0.9, 0.999), eps=1e-08, weight_decay=0)

我batch_size的设置更改的比较多,再调整模型参数时主要调整了卷积的层数,神经元个数,卷积核的尺寸以及stride等。这里对参数需要注意的地方逐一记录一下。

1
nn.Conv2d(in_channels=3, out_channels=256, kernel_size=7,stride=2)

每一层的卷积都跟它上一层的输入输出有关,在第一层的卷积时,有几个参数需要注意。
in_channels: 为输入的维度,是图片的通道数
out_channels: 是自己可以随意设置的第一层卷积的输出
kernel_size: 是卷积核的尺寸
stride: 是卷积步长
卷积图片尺寸的计算为:
(W - kernel——size)/stride+1
因为我的图片较大,所以设置stride为2,可以快速的提取特征,经过池化后缩小尺寸。

注意:在此可以设置kernel_size为原始图像大小,但是输出的图片为XX1*1,就无法进行池化运算了。一般卷积核大小kernel_size设置为3,5,7为宜。

在卷积层之后,输入到线性层之前,使用了t.flatten(start_dim=1),这里是因为卷积核输出后的数据格式是[xx11],相当于是一个四维数据,输入linear的时候需要是[xx]的形式,所以用了flatten展平,start_dim=1是不改变原有的尺寸。

还有一点,在输入linear的时候实际上与数据做的是矩阵运算,所以,对于[xy]维度的数据,需要[yx]的权重矩阵进行乘法运算才行。

解决t.reshape(-1,…)的问题

t.reshape(-1,…)中的第二个参数是t.size()除了第一个参数之外参数的乘积,也是接下来linear层的第一个输入。
所以想要省事可以写成

1
2
3
4
from functools import reduce

mul_t = reduce(lambda x,y:x*y,list(t.size()))//batch_size
t = t.reshape(-1,(mul_t)

reshape后的数据input=mul_t的维度即可,output可以自行设置。

1.2 Finetune模型微调

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
# 冻结模型参数的函数
def set_parameter_requires_grad(model, feature_extracting):
if feature_extracting:
for param in model.parameters():
param.requires_grad = False

# 冻结参数的梯度
feature_extract = True
# pretrained=True 为使用原有的模型参数进行初始化训练,为False的话就只使用模型结构。
model = models.resnet18(pretrained=True)
set_parameter_requires_grad(model, feature_extract)

# 修改模型
num_ftrs = model.fc.in_features
model.fc = nn.Linear(in_features=num_ftrs, out_features=128, bias=True)
model.conv3 = nn.Conv2d(in_channels=3, out_channels=4, kernel_size=3,stride=2)
model.bn3 = nn.BatchNorm2d(4, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True)
model.relu
model.conv4 = nn.Conv2d(in_channels=3, out_channels=2, kernel_size=3,stride=2)

指修改部分层的参数

Fine-tune可以只保留部分层的参数,可是使用下面代码提取

1
2
# 去掉model的后5层
self.resnet_layer = nn.Sequential(*list(model.children())[:-5])

可以修改原始resnet模型当中的参数,使用resnet_layer[][]进行提取,print(network)的时候有一些会给出某一层或者某一sequence的标号。

1
2
3
self.resnet_layer[0] = nn.Conv2d(in_channels=1, out_channels=8, kernel_size=3,stride=1)
self.resnet_layer[1] = nn.BatchNorm2d(8)
self.resnet_layer[4][0] = nn.Conv2d(in_channels=8, out_channels=8, kernel_size=3,stride=1)

修改模型是可以修改已有的resnet18当中的模型结构和模型参数的,可以直接使用model.进行修改。
一般图像类的模型,修改后几层的参数,而一般语义类的模型,修改前几层的参数,这于模型训练时先后提取的特征有关

2.自定义损失函数

针对样本不均衡的问题,使用focal_loss作为损失函数

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
class focal_loss(nn.Module):
def __init__(self, alpha=0.25, gamma=2, num_classes = 3, size_average=True):
"""
focal_loss损失函数, -α(1-yi)**γ *ce_loss(xi,yi)
步骤详细的实现了 focal_loss损失函数.
:param alpha: 阿尔法α,类别权重. 当α是列表时,为各类别权重,当α为常数时,类别权重为[α, 1-α, 1-α, ....],常用于 目标检测算法中抑制背景类 , retainnet中设置为0.25
:param gamma: 伽马γ,难易样本调节参数. retainnet中设置为2
:param num_classes: 类别数量
:param size_average: 损失计算方式,默认取均值
"""
super(focal_loss,self).__init__()
self.size_average = size_average
if isinstance(alpha,list):
assert len(alpha)==num_classes # α可以以list方式输入,size:[num_classes] 用于对不同类别精细地赋予权重
#print(" --- Focal_loss alpha = {}, 将对每一类权重进行精细化赋值 --- ".format(alpha))
self.alpha = torch.Tensor(alpha)
else:
assert alpha<1 #如果α为一个常数,则降低第一类的影响,在目标检测中为第一类
#print(" --- Focal_loss alpha = {} ,将对背景类进行衰减,请在目标检测任务中使用 --- ".format(alpha))
self.alpha = torch.zeros(num_classes)
self.alpha[0] += alpha
self.alpha[1:] += (1-alpha) # α 最终为 [ α, 1-α, 1-α, 1-α, 1-α, ...] size:[num_classes]

self.gamma = gamma

def forward(self, preds, labels):
"""
focal_loss损失计算
:param preds: 预测类别. size:[B,N,C] or [B,C] 分别对应与检测与分类任务, B 批次, N检测框数, C类别数
:param labels: 实际类别. size:[B,N] or [B]
:return:
"""
# assert preds.dim()==2 and labels.dim()==1
preds = preds.view(-1,preds.size(-1))
self.alpha = self.alpha.to(preds.device)
preds_logsoft = F.log_softmax(preds, dim=1) # log_softmax
preds_softmax = torch.exp(preds_logsoft) # softmax

preds_softmax = preds_softmax.gather(1,labels.view(-1,1)) # 这部分实现nll_loss ( crossempty = log_softmax + nll )
preds_logsoft = preds_logsoft.gather(1,labels.view(-1,1))
self.alpha = self.alpha.gather(0,labels.view(-1))
loss = -torch.mul(torch.pow((1-preds_softmax), self.gamma), preds_logsoft) # torch.pow((1-preds_softmax), self.gamma) 为focal loss中 (1-pt)**γ

loss = torch.mul(self.alpha, loss.t())
if self.size_average:
loss = loss.mean()
else:
loss = loss.sum()
return loss

注意,想要使用focal_loss函数,在训练的时候loss不能是F.的形式,要进行修改。
原始格式:

1
2
3
4
5
6
7
preds = network(images)  # Pass batch
loss = F.cross_entropy(preds, labels) # Calculate Loss

optimizer.zero_grad()
loss.backward() # Calculate gradients
optimizer.step() # Update weights

需要修改成:

1
2
3
4
5
6
7
preds = network(images)  # Pass batch
criteria1 = nn.Cross_entropy() # 这里不能用F.需要用nn.的形式
# criterial = focal_loss # 替换损失函数
loss = criteria1(preds, labels)
optimizer.zero_grad()
loss.backward() # Calculate gradients
optimizer.step() # Update weights

3.实际训练

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
loss_list = []
acc_list = []

print(network)
optimizer = optim.Adam(network.parameters(), lr=0.0001, betas=(0.9, 0.999), eps=1e-08, weight_decay=0)
#schedule = torch.optim.lr_scheduler.StepLR(optimizer, step_size=30,gamma=0.5)

time_start = time.time()
for epoch in range(epochs):

total_correct = 0
total_loss = 0
for batch in train_loader: # Get batch
images, labels = batch # Unpack the batch into images and labels

preds = network(images) # Pass batch
loss = F.cross_entropy(preds, labels) # Calculate Loss

optimizer.zero_grad()
loss.backward() # Calculate gradients
optimizer.step() # Update weights

total_loss += loss.item()
total_correct += preds.argmax(dim=1).eq(labels).sum().item()
loss_list.append(total_loss)
acc_list.append(total_correct/(len(train_loader)*batch_size))
print('epoch:', epoch, "total_correct:", total_correct/(len(train_loader)*batch_size), "loss:", total_loss)
time_end = time.time() - time_start
print(time_end)
print('>>> Training Complete >>>')

训练过程就是把数据放到我们已经搭建好的模型里面跑一下,这里需要注意的是损失函数的设置,将会影响分类的准确度。

这里的total_correct是整体准确的个数,用它除以总数就是准确率了。

  • Post title:深度学习——模型训练篇
  • Post author:Yang Li
  • Create time:2022-08-24 21:12:00
  • Post link:https://yangli-os.github.io//2022/08/24/深度学习-模型训练/
  • Copyright Notice:All articles in this blog are licensed under BY-NC-SA unless stating additionally.
 Comments