本博客对pytorch在深度学习上的使用进行了介绍,本博客并不会对怎么训练一个好的模型进行介绍(其实我也不会),我觉得训练一个好的模型首先得选对一个模型(关键的问题在于模型如何设计),然后再经历一遍玄学调参,大概就能得到一个比较好的模型了。我仍只有半只脚在机器学习的门内(深度学习我觉得其实就是机器学习的延伸),大佬避过。
其实现在有很多关于机器学习的库(python 的 Keras、caffe,matlab也有一个深度学习库,好像叫DeepLearning toolbox),而我对pytorch相对熟悉,所以就pytorch来介绍如何创建模型。
着眼于下面的这个python类,它继承了一个叫做Module的类,完整的导入其实是torch.nn.Module,注释中说明:这是所有神经网络模块的基类,您的模型也应该继承这个类。模块也可以包含其他模块,允许嵌套它们
树结构。您可以将子模块作为常规属性分配。简单来说,你自定义的模型中必须继承这个基类,你也可以在你的模型中使用已经定义好的模型。了解这么多我觉得是基本差不多了。
在继承了这个基类后,还必须实现forward函数,它用于正向传播,至于为什么没有反向传播,这是因为反向传播相当于在外面实现了,比如训练过程中的loss.backward(),对这个模型来说,相当于自动实现了反向传播。创建一个模型很简单,就是继承基类,实现forward函数。
class Net(nn.Module):
def __init__(self):
# 用父类的初始化方式初始化从父类继承的成员
super(Net, self).__init__()
self.l1 = nn.Linear(784, 520)
self.l2 = nn.Linear(520, 320)
self.l3 = nn.Linear(320, 240)
self.l4 = nn.Linear(240, 120)
self.l5 = nn.Linear(120, 10)
def forward(self, x):
# Flatten the data (n, 1, 28, 28) --> (n, 784)
x = x.view(-1, 784)
x = F.relu(self.l1(x))
x = F.relu(self.l2(x))
x = F.relu(self.l3(x))
x = F.relu(self.l4(x))
return F.log_softmax(self.l5(x), dim=1)
然后解释一下上面的代码,一个是Linear,就是全连接层,它也是以Module作为基类的,其实慢慢可以知道,自定义层和自定义一个完整的模型都是以Module为基类的,relu是线性整流层,其作用是作为激活函数增加非线性关系。这是一个很简单的模型,取自mnist手写数据集的图像分类代码,接下来的训练基本步骤也参照这个来。
model = Net()
optimizer = optim.SGD(model.parameters(), lr=0.01, momentum=0.5)
loss_fn = F.nll_loss
loss = loss_fn(output, target)
loss.backward()
optimizer.step()
大致过程便是如此,具体可以看代码,这个代码网上应该很多,但之所以用这个代码是因为它的数据集是可以自己下载的(只需要改变download为Ture),很适合初学者。
import torch
import torch.nn as nn
import torch.optim as optim
import torch.nn.functional as F
from torchvision import datasets, transforms
from torch.autograd import Variable
from torch.utils.data import DataLoader
import os
class Net(nn.Module):
def __init__(self):
super(Net, self).__init__()
self.l1 = nn.Linear(784, 520)
self.l2 = nn.Linear(520, 320)
self.l3 = nn.Linear(320, 240)
self.l4 = nn.Linear(240, 120)
self.l5 = nn.Linear(120, 10)
def forward(self, x):
# Flatten the data (n, 1, 28, 28) --> (n, 784)
x = x.view(-1, 784)
x = F.relu(self.l1(x))
x = F.relu(self.l2(x))
x = F.relu(self.l3(x))
x = F.relu(self.l4(x))
return F.log_softmax(self.l5(x), dim=1)
batch_size = 64
//数据加载
train_dataset = datasets.MNIST(root='./data/', train=True, download=False, transform=transforms.ToTensor())
test_dataset = datasets.MNIST(root='./data/', train=False, download=False, transform=transforms.ToTensor())
//数据加载器
train_loader = DataLoader(dataset=train_dataset, batch_size=batch_size,
shuffle=True, drop_last=True)
test_loader = DataLoader(dataset=test_dataset, batch_size=batch_size,
shuffle=True, drop_last=True)
model = Net()
optimizer = optim.SGD(model.parameters(), lr=0.01, momentum=0.5)
loss_fn = F.nll_loss
# 模型加载
if os.path.exists('./models/mnist_net.pt'):
model.load_state_dict(torch.load('./models/mnist_net.pt'))
optimizer.load_state_dict(torch.load('./results/mnist_optimizer.pt'))
for i in range(5):
i += 1
test_loss = 0
correct = 0
//模型训练
for batch_id, (data, target) in enumerate(train_loader):
data, target = Variable(data), Variable(target)
# 梯度清零
optimizer.zero_grad()
output = model(data)
loss = loss_fn(output, target)
loss.backward()
optimizer.step()
if (batch_id + 1) % 200 == 0:
print("train Epoch:{}\tloss:{}".format(i, loss.data))
print("\n")
//模型预测,取消梯度计算,将无法反向传播
with torch.no_grad():
for data, target in test_loader:
data, target = Variable(data), Variable(target)
output = model(data)
test_loss += (F.nll_loss(output, target))
# keepdim表示输入和输出维度是否一样
# print(output.data.max(dim=1, keepdim=True)[1])
predict = output.data.max(dim=1, keepdim=True)
correct += torch.eq(predict[:, 0], target).float().sum().item()
test_loss /= (batch_size*len(test_loader))
# print("Average loss:{:.4f}, Accuracy:{:.0f}%".format(test_loss, predict))
print("Average loss:{:.4f}".format(test_loss))
print("Accuracy:{:.6f}".format(correct/(len(test_loader)*batch_size)))
# print(predict)
print("\n")
# 模型保存
torch.save(model.state_dict(), './models/mnist_net.pt')
torch.save(optimizer.state_dict(), './results/mnist_optimizer.pt')
上面的代码展示的流程是比较清晰的,但其实还有着一些问题。
有关Epoch、Batch、Batch_Size、Iteration的概念
Epoch是指周期,当你训练完所有样本一次,就代表完成了一个Epoch
Batch是指一批样本,Batch_Size是指这一批样本的大小(或者说数量)
Iteration是指一次迭代,训练完一批数据后就是完成了一次Iteration
数据集能否自己定义,如果可以,如何自定义?
数据集可以自定义,自定义的方式是自定义一个数据集类并继承Dataset类,然后手动实现__getitem__和__len__方法即可,前者是获取经过处理后的一个样本,后者是获取整个数据集的样本数量,实现的时候按照这两个功能实现就好了。
数据加载器的作用是什么?
相当于一个打包器,它的最小单位相当于从一个样本变成了一批样本,这样,在训练的时候,就可以很方便地进行一次训练(一次训练一批数据)
在模型训练的时候有进行过梯度清零,为什么要梯度清零?
pytorch默认是会对梯度进行累加的,如果想要当前的梯度不被之前计算的梯度影响,就应该进行梯度清零。想象有一个平面,这个平面是loss平面,当计算完一批数据后,loss平面逐渐凸起一个或多个大包,loss平面里包含了梯度的信息,可我并不想要在这个loss平面的基础上计算新的梯度,因为它可能有了几个大包了,如果不清除梯度再计算,loss的包可能会更多,如果清楚梯度再计算,新计算出的loss平面大包数量很可能并不会超过上一次计算的loss平面的大包数量,这只是我一个蹩脚的解释。另外,梯度清零可以玩出更多的花样,比如梯度累加,让你在计算了多个batch后再进行参数更新和梯度清零,变相地一次训练了多个batch_size,可参考以下链接:
[PyTorch中在反向传播前为什么要手动将梯度清零?](PyTorch中在反向传播前为什么要手动将梯度清零? - 知乎 (zhihu.com))
torch.no_grad()的作用是什么?
其实从这个名字就可以看出是取消梯度的计算了,相同效果的方法是eval(),意思是开启预测模式,有预测模式当然还有训练模式,就是train(),model.train()或model.eval()就可以用了。如果要细究,可以参考以下链接:
Pytorch:model.train()和model.eval()用法和区别,以及model.eval()和torch.no_grad()的区别、
训练集和预测集?
训练集就是拿来训练模型的,训练过程会改变模型内部的参数,预测集就是拿来做预测任务的,这个时候模型内部的参数不会改变也不应该改变。加入有一些样本,这些样本经常会以7:3的比例划分训练集和预测集,当然其实还有验证集,还存在所谓的5折交叉验证,花样很多,感兴趣的小伙伴可以自己去尝试,本文不做探讨。
首先,什么是分类问题,假设有一些训练的样本(我差点说成一批了),这些样本都有一个标签,标签很直观地代表着类别。现在有两类评论,正面的和负面的。
负面评论如下:
simplistic , silly and tedious .
it’s so laddish and juvenile , only teenage boys could possibly find it funny .
exploitative and largely devoid of the depth or sophistication that would make watching such a graphic treatment of the crimes bearable .
[garbus] discards the potential for pathological study , exhuming instead , the skewed melodrama of the circumstantial situation .
a visually flashy but narratively opaque and emotionally vapid exercise in style and mystification .
the story is also as unoriginal as they come , already having been recycled more times than i’d care to count .
正面评论如下:
an inuit masterpiece that will give you goosebumps as its uncanny tale of love , communal discord , and justice unfolds .
this is popcorn movie fun with equal doses of action , cheese , ham and cheek ( as well as a serious debt to the road warrior ) , but it feels like unrealized potential
it’s a testament to de niro and director michael caton-jones that by movie’s end , we accept the characters and the film , flaws and all .
performances are potent , and the women’s stories are ably intercut and involving .
an enormously entertaining movie , like nothing we’ve ever seen before , and yet completely familiar .
lan yu is a genuine love story , full of traditional layers of awakening and ripening and separation and recovery .
your children will be occupied for 72 minutes .
pull[s] off the rare trick of recreating not only the look of a certain era , but also the feel .
我们获取到了这些样本并获取到对应的标签:正面和负面,我们就可以训练模型了,在训练好模型后,我们获取到类似上面的这些评论样本,就可以通过模型预测获取的评论样本是正面的还是负面的,这样,一个文本二分类问题就很简单的解决了。在代码层面,通常在模型中会使用softmax激活函数将特征(这里可能难以理解)映射成一个二维向量(预测的标签),且元素和为1,当然,样本的实际标签也会处理成一个二维向量,元素和同样为1(元素只能为0或者为1,且只能有1个1),这样在训练的时候才可以计算loss。
在这里,直观地把标签当做类别是没有问题的,因为一个类别只有一个标签,但如果一个类别可能有多个标签呢?这就是我在这次的数据结构课程设计中遇到的问题了。本质上只有两个类别,但其中一个类别有着多个标签,这就是一个多标签文本二分类问题了,这里提前说一下,在代码上,使用的sigmoid作为激活函数来激活特征,最后得到的标签向量和并不一定为1。
本次课程设计选用阿里云天池大赛中全球人工智能技术创新大赛中的赛道一的赛题:医学影像报告异常检测。本题旨在对医学影像报告进行分析从而判断出异常的区域以及异常的类型。我将该题视为一道文本多标签二分类的题目,采用多种模型对这些医学影像报告进行训练并最终预测出异常的类型和异常的区域。基于深度学习所学知识,采用卷积神经网络、密集连接的神经网络、长短期记忆神经网络等模型,基于张量网络所学知识,采用了最基本的矩阵乘积态。通过训练这些模型,找到其中最好的模型,并最终使用这个模型去预测得到最终结果。
***赛题链接:***全球人工智能技术创新大赛【赛道一】赛题与数据-天池大赛-阿里云天池 (aliyun.com)
影像科医生在工作时会观察医学影像(如CT、核磁共振影像),并对其作出描述,这些描述中包含了大量医学信息,对医疗AI具有重要意义。
医学影像报告检测任务需要参赛队伍根据医生对CT的影像描述文本数据,判断身体若干目标区域是否有异常以及异常的类型。初赛阶段仅需判断各区域是否有异常,复赛阶段除了判断有异常的区域外,还需判断异常的类型。判断的结果按照指定评价指标进行评测和排名,得分最优者获胜。简单来说,就是训练一个模型通过医学影像报告样本判断各区域是否有异常以及异常的类型。
通过问题的描述可以清楚该问题是自然语言处理中的一个文本分类问题,但是具体是什么样的文本分类问题还需要通过分析数据格式得出。
通过对赛方提供的数据对问题进一步分析:
一个样本包含report_ID、description和label,比较重要的是description和label,前者是影像描述(字符串类型),后者由两部分组成,一部分为异常区域ID,另一部分为异常类型ID,sample数据格式如下:
列名 | 类型 | 示例 |
---|---|---|
report_ID | int | 1 |
description | string,影像描述 | 右下肺野见小结节样影与软组织肿块影 |
label | 由两部分组成。第一部分为若干异常区域ID,用空格分割。第二部分为若干异常类型ID,用空格分割。两部分用逗号“,”分割。若定义中所有区域均无异常,则两部分均为空,此项为“,”。 | 4,1 2 |
Training数据脱敏后的影像描述与对应label。影像描述以字为单位脱敏,使用空格分割。初赛只进行各区域有无异常的判断,label只有异常区域ID。复赛除了判断各区域有无异常,还需要判断各区域异常的类型,因此label包含异常区域ID与异常类型ID。初赛Training集规模为10000例样本,复赛Training集规模为20000例样本。Training数据用于模型训练与预估。Training数据格式如下:
初赛train数据格式:
列名 | 类型 | 示例 |
---|---|---|
report_ID | int | 1 |
description | 脱敏后的影像描述,以字为单位使用空格分割 | 101 47 12 66 74 90 0 411 234 79 175 |
label | 由多个异常区域ID组成,以空格分隔。若此描述中无异常区域,则为空 | 3 4 |
复赛Training数据格式:
列名 | 类型 | 示例 |
---|---|---|
report_ID | int | 1 |
description | 脱敏后的影像描述,以字为单位使用空格分割 | 101 47 12 66 74 90 0 411 234 79 175 |
label | string,由两部分组成。第一部分为若干异常区域ID,用空格分割。第二部分为若干异常类型ID,用空格分割。两部分用逗号“,”分割。若定义中所有区域均无异常,则两部分均为空,此项为“,”。 | 3 4,0 2 |
Test数据脱敏后的影像描述,脱敏方法和Training相同。Test数据用于参赛选手的模型评估和排名。初赛Test集分为AB榜,规模均为3000。复赛Test集规模为5000。Test数据格式如下:
Test数据格式:
列名 | 类型 | 示例 |
---|---|---|
report_ID | int | 1 |
description | 脱敏后的影像描述,以字为单位使用空格分割 | 101 47 12 66 74 90 0 411 234 79 175 |
从以上数据可以判断出,有无异常,将是一个二分类问题,当有异常区域id或是有异常区域类型都代表有异常,除此之外都是无异常。假设有一个n维的标签向量,这个标签向量是从所得数据得出的,该向量中的元素只能取0或者1,当元素取0代表该处无异常,当元素取1则代表有异常,还可以注意到,这个标签向量里面不一定只包含1个1。
因此,该问题是一个多标签二分类问题。
开发工具:pycharm
运行环境:python+win10
(1) 数据预处理与数据集构建
对Training和Test数据的description向量进行处理,保证每一个样本的description向量长度一致(这里长度为40,这是所有description的平均长度),过长的description截断,过短的description则用description的平均值填充。对Training的label进行处理,如果有异常,则不用管,如果无异常,则将空格替换为-1,之所以不直接替换为0是因为0也是一个异常区域ID或异常类型,还需要注意的是,有异常区域ID和有异常类型都代表有异常。由于这些数据是为了训练一个判断异常区域或异常类型的模型,并不是那种类似文本评论需要的情感分类模型(需要评分的文本分类是需要进行一些去重操作的),即便有重复的也不会有太大影响,甚至还有助于模型的训练,所以无需计算不同description向量的相似度去重过于相似的description,而且所得数据本身是比较优质的,并且对我透明(都是脱了敏的),故而在数据预处理这一步只进行了前面所说的两步。
在这之后,需要构建一个数据集,该数据集需要将读入的数据转换成二元组,样本description词向量和标签,其中词向量无需动,因为在前面的数据预处理已经解决了,标签需要转换为标签向量,对于初赛的内容,需要将标签转换为一个17维的标签向量,对于复赛的内容,需要将标签转换为一个29维的向量(也尝试了拆分成17和12维向量进行处理,只不过这样做最后训练模型就要训练两次,而且这么做不太规范)。假设有一个17维零向量,向量中的每个元素从头开始分别代表着每一个异常区域id,如果某一个区域有异常,那么对应的元素值设置为1,类似的,一个29维向量也是这么处理,前17个元素的处理方式和前面一致,后12个元素如果存在某个异常类型,那么对应元素值设置为1。一旦数据集构建好,数据加载器就可以每次从数据集中取出一个batch_size的数据。
(2) 模型设计
要解决这个问题,我将使用一些深度学习模型和张量网络学习模型去训练模型并最终得出结果。图像分类比较适合使用Densenet、Resent、MPS模型,而文本分类则比较适合使用LSTM模型,Densenet和Resenet也是卷积神经网络,只不过里面有一些特殊的块。以下是我尝试的模型以及模型中可能存在的块:
需要注意的是,残差块可以有多层,上图只展示了单层残差块,lstm也可以有多层,且一层可以包含多个lstm单元,DenseBlock主要还是用在Densenet中,没有尝试用在其他模型中去。上图展示了将要使用的模型和模型中额外定义的一些块,并显示了这些模型和块的内部结构的大致情况。
(3) 模型训练和调参:
整个训练的任务可以用以下一个图阐释:
在模型训练好之后执行模型的预测任务,预测任务也可以用下面一个图来阐释:
在训练模型的时候是对前面提到过的模型进行训练,在调参的时候是对初赛的数据集进行调参,复赛的数据集太多,调参太过耗时,因此没有进行调参。
在使用初赛的训练集进行训练的时候先是对每个模型进行多次的训练并同时写入日志以及画score图,如果模型的max score达到一定的阈值,我就认为这个模型有着达到这个score的潜力,因此将对应的模型和优化器保存下来。
在使用初赛的数据集进行调参的时候,可调参数是优化器以及模型内部的参数,选用的优化器分别是:Adam、SGD、ASGD、SparseAdam。
调参的方式选用最麻烦的网格寻优。
在确定要用来调参的模型之前先确定好哪个模型可以达到最高的score,然后就该模型进行详细地调参,而要确定好是哪个模型,可以通过直接随机选择一对参数训练模型。
(4) 模型的评估
使用比赛指定的mlogloss,评估的方式为score=1-mlogloss,score越大,score曲线上升越稳定,则模型越好。
mlogloss = − 1 M ∑ m = 1 M 1 N ∑ n = 1 N ( y n , m log ( y ^ n , m ) + ( 1 − y n , m ) log ( 1 − y ^ n , m ) ) 其中 y n , m 和 y ^ n , m 分别是第n个样本第m个标签的真实值和预测值。 \begin{aligned} &\text { mlogloss }=-\frac{1}{M} \sum_{m=1}^{M} \frac{1}{N} \sum_{n=1}^{N}\left(y_{n, m} \log \left(\hat{y}_{n, m}\right)+\left(1-y_{n, m}\right) \log \left(1-\hat{y}_{n, m}\right)\right)\\ &\text { 其中 } y_{n, m} \text { 和 } \hat{y}_{n, m} \text { 分别是第n个样本第m个标签的真实值和预测值。 } \end{aligned} mlogloss =−M1m=1∑MN1n=1∑N(yn,mlog(y^n,m)+(1−yn,m)log(1−y^n,m)) 其中 yn,m 和 y^n,m 分别是第n个样本第m个标签的真实值和预测值。
(5) 激活函数设计
由于前面提到的标签向量中元素值处于0到1之间,且问题为多标签二分类问题,而sigmoid函数将值映射在0到1之间正好可以表示概率,且sigmoid正好适用于该问题,因此选取激活函数时应选择使用sigmoid函数,而为了验证我的一些想法,我对sigmoid函数进行了一些改造,其数学表达为:
S ( x ) = 1 1 + a e − x S(x)=\frac{1}{1+ae^{-x}} S(x)=1+ae−x1
当a=1,该激活函数正是sigmoid函数,通过改变参数a,可以观察到其图形的变化(下图),即随着a的增大,曲线变陡,意味着小于0.5的数据将更多。
算法:反向传播和正向传播
采用的技术:embedding(词嵌入)、卷积、池化、残差块、全连接层、batchnorm(批量规范化)、lstm(长短期记忆)、mps(矩阵乘积态)等等。
这些算法和技术就不在此阐释。
这是我自己设计的模型,所有尝试模型中最好的一个:
(1)提取特征:先将输入数据进行预处理,然后使用卷积提取特征,使用二维规范化标准化提取出的特征,以便加快收敛速度,接着使用一个一层的恒等残差块,以便loss的稳定,最后是一个最大池化,将数据降维。
(2)可选的进一步提取特征:和提取特征差不多,唯一变化的是在卷积的时候步长设置为1,以便进一步提取特征,cnn_num用于控制该块被使用的数量。
(3)lstm核心部分:这里的线性层主要是为了做线性变换来规范输入lstm和从lstm输出的数据的形状,核心部分是lstm,用于提取相邻信息,主要提取的特征是相邻数据之间的关系。
(4) 激活特征:Softplus_d是激活函数,a是用于调整激活函数Softplus_d的超参数,
一个模型最重要的部分就是向前传播了,也就是重写forward函数:
(1) 使用初赛数据进行训练所得最高score的结果表如下,故而选择lstm_cnn1。
不同模型的max_score对比:
Model | Max Score |
---|---|
CNN | 0.78 |
Linear | 0.76 |
ResidualNet | 0.67 |
Dense_Net | 0.65 |
lstm | 0.77 |
lstm_cnn1 | 0.83 |
lstm_cnn2 | 0.73 |
mps | 0.73 |
(2) 对lstm_cnn1的第一次调参结果,出现最好的结果对应的参数如下
cnn_num=1 lstm_list=[3, 1] a:4
train Epoch:1 score:0.8042891025543213
train Epoch:1 score:0.7038210391998291
score_max:tensor(0.8043)
cnn_num=2 lstm_list=[2, 2] a:5
train Epoch:1 score:0.800950825214386
train Epoch:1 score:0.7135155200958252
score_max:tensor(0.8010)
cnn_num=2 lstm_list=[1, 2] a:4
train Epoch:1 score:0.8197126984596252
train Epoch:1 score:0.7277708053588867
score_max:tensor(0.8197)
同时,经过对比,发现自定义的cnn块起到的作用基本没有,甚至在其增大的情况下网络效果还会变差,所以将cnn_num定为0,设置lstm_list=[1,2]、a=4,并进行第二次调参(改变优化器):
(3) 使用前面所得参数对模型进行训练,并将结果保存至csv文件中。
(4) 对激活函数参数a的改变是一次尝试,如果当a=1,lstm_cnn1的结果0.6356192231178284(初赛),复赛的结果为:0.6450639963150024,它们的图像分别是:
训练的epochs比较少,图像也比较震荡。
如果看到这里,其实可以发现很多很多问题,至少我是这么觉得的。
其实这里存在了很多问题,比如激活函数的设计是否存在一些问题,调参是否存在着一些问题,接下来就对存在的诸多问题进行探讨。
激活函数的设计问题
我这里通过修改激活函数的参数来强行学分布的操作是一个尝试,我想看看这么做了之后会有什么后果,然后我知道了,score曲线变化幅度变大,模型更加不稳定(a越大越不稳定),我通过增加a让特征往较小的特征进行靠拢,然后score升高,但表现出一种不稳定的状态,说明激活函数是有问题的,我通过改变a得到的激活函数根本就不适配或者只是部分适配提取出的特征,这样就导致特征没有提取出来。
所以激活函数能否自定义?我会有这个疑惑是因为一个老师曾说如果像我前面这么干就会显得很牵强,尽管score有上升,score变化幅度大也是这么干的代价。我就想,如果别人能定义一个sigmoid,那我就不能定义另一个激活函数吗,那就应该是激活函数的设计存在问题?
激活函数的作用就是激活特征,如果最后得到的结果比较好,我认为就是一个很好的激活函数,但是如果在传播到激活函数之前没有学好分布,改变激活函数参数去强行拟合就很不好了,我想这也是那位老师和我这么说的原因吧。
所以,激活函数是有问题,但是问题更在于前面的分布是否学到,模型是否能够让数据在传入激活函数前已经映射到一个足够合适的分布上去了,当前面的达成了,接着激活函数选的好一点,最后结果就应该很好了。
其实我这说的有点像是废话,因为归根到底,问题在于模型设计的是否好,而我并未提到怎样设计好一个模型(其实我也不会,不懂),但至少我探究了一下在模型训练过程中激活函数的问题,算一个尝试,也算是走的一个弯路吧。
调参的问题
对于简单的CNN、DenseNet、Linear,调参是比较方便的,因为我发现,他们的训练一开始score就是稳定的,但是引入了lstm后,一开始的score就不稳定了,只能说每次重头训练,模型内部的参数都是不一样的,这样的模型该如何调参?因为每次开始的score它都不一样,这就给调参带来了问题。我在遇到这个问题的时候采用的方式是每一种参数组合训练5次,但相当于调参时间变成原来的5倍,而调参本来就很耗时间。
模型设计的问题
目前对我来说,这是最难办的问题,模型该如何设计是个头痛的问题,也许还要将模型设计与调参结合起来,所以这是我的这次数据结构课程设计存在的最大的问题。
训练epoch不够多的问题
听老师说,如果一个模型设计的好,在一开始就能达到0.9的程度,而我的模型并不好,所以就没训练很多epoch了,之前训练过500 epoch,但后来大幅度修改了模型就把对应的日志文件和可视化图片删除了。对于一个深度学习任务,训练2个小时都是最基本的,还常常会租服务器去训练,所以我这次的数据结构课程设计都是小儿科。
在看了前面的运行结果分析,可能有小伙伴已经发现了问题,那就是不同模型的max_score对比是有问题的,因为那是我调了参数a的结果,这实际上是不可靠的。如果用正宗的sigmoid函数,最高的仍旧是lstm_cnn1,能达到0.64,结果会相对稳定一些。
模型设计的不好(感觉在说废话),但是在代码上接触了很多模型,并进行对比。
如果需要源代码,可以直接私信要,我没有整理,也不打算发github上了,毕竟做的并不是很好,但对一个初学者来说或许能够减少一些麻烦。
[1] https://zhuanlan.zhihu.com/p/79064602,2020-5-26.
[2] https://blog.csdn.net/u014380165/article/details/75142664,2017-7-15.
[3] https://zhuanlan.zhihu.com/p/43057737,2019-10-21.
[4] https://blog.csdn.net/weixin_41172694/article/details/86978647,2019-2-11.
[5] Gao Huang, Zhuang Liu, Laurens van der Maaten,etc.Densely Connected Convolutional Networks[J].arXiv.org,2018,166993v508.0(1):1.
[6] https://blog.csdn.net/xixiaoyaoww/article/details/105683320,2018-4-1.
[7] Kaiming He, Xiangyu Zhang, Shaoqing Ren, etc.Kaiming He, Xiangyu Zhang, Shaoqing Ren, Jian Sun[J].arXiv,2015,1512.03385v1(1):1.
[8] https://www.cnblogs.com/dudududu/p/9149762.html,2018-6-7.
[9] https://www.zhihu.com/question/293243905,2018.
[10] https://blog.csdn.net/dl962454/article/details/109624917,2020-11-11.
[11] https://github.com/jemisjoky/TorchMPS,2021-4. (mps代码)
[12] https://www.tensors.net/mps
[13] https://blog.csdn.net/kangyi411/article/details/78969642,2018-1-4.
[14] https://zhuanlan.zhihu.com/p/88347589,2019-10-24.
[15] https://blog.csdn.net/wzy_zju/article/details/81262453,2018-7-28.
[16] https://github.com/gpleiss/efficient_densenet_pytorch (densenet代码)