本篇为《机器学习完备流程总结+实践经验细节+代码工具书(4)》,使用经过数据处理阶段处理过的数据,进行神经网络机器学习模型的搭建及训练阶段的相关流程(代码基于pytorch):
什么是好的机器学习模型/如何得到好的模型+数据分析阶段:https://blog.csdn.net/weixin_44563688/article/details/86535274
前置数据处理阶段: https://blog.csdn.net/weixin_44563688/article/details/86558939
非神经网络机器学习模型的搭建,训练及评价阶段: https://blog.csdn.net/weixin_44563688/article/details/86568400
神经网络模型的搭建,训练及评价阶段:(you are here)
1.神经网络的特点
神经网络的独特性在于能力十分强大,模型上限很高,理论上可以对任意复杂的函数在任意精度上进行模拟,因此在处理复杂,庞大的数据时神经网络有着其他机器学习模型无法匹敌的优越性。
同时神经网络还有着灵活性高,方便自主设计,改进和添加模块实现特定功能的特点,通过设计不同的神经网络结构,我们可以处理各种不同复杂结构的数据(图像,自然语言,3D点云…),对数据中的信息进行高效的抓取和利用。这是大多其他机器学习模型所不具备的。
而神经网络模型在实际应用中最大的优势或许就是可以十分方便地使用迁移学习(transfer learning)策略,关于这部分在推荐策略里进行详细的解释。
2.神经网络的适用数据类型
由于神经网络高variance低bias的特点,神经网络模型一般更适用于处理结构式数据(暂不清楚这种类型数据的学术名称,因此使用结构式数据代替),比如图像,视频或者自然语言,这种类型的数据有一个典型特征就是数据的信息主要以feature和feature之间的某种结构来体现。单个数据feature数量巨大,feature和feature之间没有本质不同,我们要挖掘的信息更多存在于feature和feature之间的关系结构中,而这种复杂的结构信息往往难以被其他机器学习模型发现和利用,因此对于结构式数据,我们应首先尝试使用神经网络技术进行处理。比如对于图像数据,所有的feature均为以特定color space表示的颜色,单个pixel(feature)的值并不能提供给我们太多的信息,我们真正感兴趣的地方在于pixel和pixel之间以什么样结构组织在一起从而形成了这个图像。
对于feature与feature之间互不相同的一般数据(比如典型的房价估计数据等)我们使用单纯全连接神经网络也可以进行处理。但是需要意识到神经网络是一种十分强力的模型,非结构式数据往往包含的信息并不足够复杂,一般的机器学习模型对于非结构性数据也可以十分高效地进行利用,而对这种简单非结构性数据内在的条件概率分布使用神经网络进行拟合很容易会产生比较严重的过拟合问题。因此对于不足够复杂的非结构性数据,相比于集成其他机器学习模型的方法,神经网络并无明显优越性。
3.神经网络搭建和训练的推荐策略:
在上一节介绍传统机器学习模型时,推荐使用soft-voting(boosting,stacking的集成方式也可以尝试)的方式集成多个不同的机器学习模型来降低模型的Bias,在显著提高模型表现的同时节省了大量用于尝试不同模型与精调模型参数的时间。而对于神经网络模型一般情况下不推荐使用任何的复杂集成方式。这是由于神经网络模型的本质就是众多感知机通过多连接(对于FCN则是全连接)Stacking的集成方式组成的巨型集成模型,因此天生具有着low bias high variance的特性,使用集成学习的方式进一步降低神经网络的bias一般情况下不如单纯的加深加宽神经网络或者加入某些module(使用Auxiliary classifiers结构的神经网络可以通过self-to-self集成方式在提高模型性能同时不显著提高过拟合风险)。
迁移学习即是使用其他人训练的现成的神经网络模型和参数来训练自己的神经网络,根据原数据集和自身数据集的分布差距情况采取不同方法进行模型训练,达到使用少量训练数据也可以又快又好地获得优秀神经网络模型的训练策略。具体使用迁移学习的方法有两种:
为什么使用迁移学习:
推荐使用迁移学习和现成经典神经网络结构来搭建和训练神经网络模型的原因总结如下:
4.实践经验:
实践经验分为两部分,搭建/训练经验与经典神经网络和学术前沿(将持续更新),主要面向CNN类模型。
0.简单的神经网络能力也足够强大,如无必要尽量不要使用resNet,inception-resNet等超级网络。在使用神经网络模型前要对自己数据的复杂程度和完成任务目标所需要从数据中抽象出的特征的高级程度有一个大概的感觉,然后使用复杂度相匹配的机器学习模型(神经网络模型),在一个简单的图像分类问题上使用深度学习是不明智的做法,不仅过拟合问题严重,模型的训练难度也很大。
1.尽可能使用经典网络结构并采用迁移学习的方式来进行模型的搭建和训练。具体原因可见上方推荐策略的部分,需要注意的是使用2015年之前的网络时需对网络进行加入BN等改进。主要代码会提供于下方代码部分,这里主要介绍下在具体实施迁移学习时应采用的策略(主要针对结构性数据作为输入时的网络):
2.需要增强神经网络性能的时候,加深网络深度的性价比优于加宽网络宽度。这里的深度指神经网络的层数,宽度指神经网络每一层的神经元数量。简单来说,如果我们的数据比较复杂,需要设计(不推荐自己设计)一个强大的神经网络时,那么我们应该尽量加深这个网络的深度而不是增多网络每一层的神经元数量。背后的原因主要有三点:
建造更强力神经网络的一种思路:增强神经网络性能最显而易见的方式是搭建更加庞大的网络结构—让网络更深更宽。加大网络的深度我们就可以提取和利用抽象程度更高的特征,而深度上的提高可以借助res module来实现;增大网络的宽度,本质是上是希望网络能够更加充分地利用某一指定层级(某一具体抽象程度)特征中的信息,宽度的增加除了单纯使用更多的神经元外,更有效率的一种方式是使用inception module(原始的inception module通过组合比较简单的抽象特征,比较复杂的抽象特征,和简化的简单特征,保留不同层次的高阶特征来丰富网络的表达能力,值得注意的是DIY模型时如需使用inception module,不要在网络太前面使用),inception module + res module的组合使用是目前产生超级网络的主流方式之一,也是我们希望加深加宽网络时的重要选择。(2017年提出的DenseNet脱离了加深网络层数(ResNet)和加宽网络结构(Inception)来提升网络性能的定式思维,通过特征重用和旁路(Bypass)设置,在大幅度减少了网络的参数量的同时,又在一定程度上缓解了gradient vanishing问题的产生)。
对于巨型网络,存在着容易过拟合和训练速度过慢的问题,因此我们希望在不较大影响模型提取feature的抽象与丰富程度的前提下,尽可能使用更少的参数。一个常常使用的有效方法是将卷积层中的filter进行拆解,将一个N * N的fitler拆解成为一个N * 1的filter后接一个1 * N的filter(交换顺序也可),如此一来,这个filter在感受野不变的前提下有效降低了参数数量,减少了过拟合风险的同时加速了模型训练,同时把一个filter分解成了两个,增加了非线性激活的次数,有助于提高模型的表现。但是有研究指出,多层小size filter的计算量其实是要多于单层大size filter的,我们不应该仅仅用总的参数数量衡量模型的计算成本。
最后插句题外话,resNet的本质或许并不能单纯理解成一般的线性深度神经网络, resNet可能只是很多浅层网络的集成:《Residual Networks Behave Like Ensembles of Relatively Shallow Networks》:
https://arxiv.org/abs/1605.06431
3.adam(Nadam)与SGD的取舍:先说结论,不考虑训练速度的时候,adam一般不如加入动量或者Nesterov的SGD,adam训练速度一般更快但是收敛效果不好(very poor solution),SGD训练速度可能不如adam但是往往收敛效果更好(结论来自于《Improving Generalization Performance by Switching from Adam to SGD》)。
关于adam和sgd的对比,UC Berkeley的文章《The Marginal Value of Adaptive Gradient Methods in Machine Learning》中有着这样的结论:
Despite the fact that our experimental evidence demonstrates that adaptive methods are not advantageous for machine learning, the Adam algorithm remains incredibly popular. We are not sure exactly as to why
目前TSoA(The-State-of-Art)级别的神经网络模型也基本都采用最基本的SGD训练获得,大家不用adam而更喜欢选择SGD的主要原因在于:
但是需要注意的是,在部分研究中,研究者们用adam也可以获得非常不错的收敛效果,尤其是在稀疏的数据中,adam常常有着十分不错的综合表现。因此adam与SGD孰优孰劣并没有定论。
那么一般情况如何使神经网络又快又好地进行训练呢?这里提供两种前沿深度学习的常用方法:
4.积极跟踪训练进展:确保我们的模型训练正常,具体的方式有:
5.根据激活函数使用Xavier(sigmoid),He(ReLU)等初始化方法,默认使用BN,谨慎使用dropout,非最终层的所有层使用ReLU作为激活函数。
model.train()
model.eval()
6.跟踪模型在validation set上的loss曲线,每当曲线趋于收敛时降低学习率1/3并适当加大batchsize来加快训练同时更好地收敛。从SGD的原理出发,这么做的原因显而易见,因此这里不做过多解释。
7.在数据噪音较大,或者希望模型只利用数据中的部分信息来进行分类或者回归的时候可以使用attention module:加入attention module(对于CNN分为channel-wise和map-wise两类attention module)可以降低模型对于无用信息的利用率,使用真正重要的信息来处理数据问题,具体有两个好处:
8.在CNN中使用spatial transform来使得模型更加transform invariant:在无人驾驶,手写数字,签名识别等要求模型精度较高的使用场合,STN(Spatial Transformer Network)是十分有效的模型组件,可以显著提高模型的表现。具体原理是通过一部分额外的挂件网络可以让模型学习到一个2D仿射变换,将输入中的物体旋转拉伸平移至模型更‘熟悉’的样子,降低模型的特征提取难度。
9.对于分类问题使用cross-entropy,对于回归问题推荐使用smoothL1loss:
10.改变feature map的大小(长宽与channel数量):
11.一般使用Max pooling,最后一层卷机层可以采用平均池化:池化层的使用可以增加后续神经元的感受野,简化计算,同时提高模型对于噪音的抵抗能力和对局部关键信息的提取能力。一般推荐使用max pooling即可,但是也有研究者指出对最后一层卷积层使用平均池化可以获得更好的效果。
下方提到的方法均来自于比较前沿(2015 - present)的神经网络研究,不是神经网络的常规结构,但是我自己比较欣赏它们背后的学术原理,因此这里进行简单介绍,这部分将作为之后持续更新的重点。
1.Auxiliary classifiers结构使得神经网络可以进行另一种高效地集成学习(self-to-self式集成)。的使用深度学习时,可以使用辅助分类节点auxiliary classifiers来实现模型集成,这种方法不光给网络增加了反向传播的梯度信号,同时也提供了额外的正则化,当然它最厉害的思想在于可以通过自己集成自己的方式实现对于神经网络的集成学习:《Rethinking the Inception Architecture for Computer Vision》
https://arxiv.org/abs/1512.00567
2.Label smooth:我们给一张图片某个label的时候,其实是抹杀了这张照片中的其他类别的成分,是一种hard assignment。比如一只狮子,它其实有部分猫的成分在其中,也有部分狗的成分,如果我们将这只狮子分类成狮子,那么就相当于否认了狮子包含的猫的成分与狗的成分,但是这种成分确是客观存在的,所以hard assignment式的label给模型的分类造成了困难,这个原因可以理解为使用label smooth的部分动机:我们希望获得data的概率表示。
3.Dense→Sparse→Dense (DSD)训练法:简单来说,使用裁剪之后的模型为初始值,再次进行训练调优所有参数。其中使用的稀疏(Sparse)相当于一种正则化,有机会把解从局部极小中解放出来。这种训练方式有点人类学习模式的意味:初次学习知识→复习时回顾加深理解加深记忆。
一般可以通过完全自己搭建和使用现成模型搭建(直接使用/微调/只使用现成网络的部分/全部结构)两种方式来获得神经网络模型。
1.完全自己搭建:这里提供了一个基本包含了所有神经网络基本组件的CNN网络模型,在自己搭建模型的时候只用调整组件的位置,数量,替换使用相同功能的其他组件以及改变网络的结构即可获得一个经典的简单CNN网络,网络中各层(layer)与操作(operation)的相对次序已经调整至合理。
class Net(nn.Module):
def __init__(self):
super(Net, self).__init__()
self.conv0 = nn.Conv2d (1,32,5,stride = 1,padding = 2)
torch.nn.init.kaiming_normal_(self.conv0.weight)
self.conv0_bn = nn.BatchNorm2d(32)
self.conv1 = nn.Conv2d (32,64,5,stride = 1,padding = 2)
torch.nn.init.kaiming_normal_(self.conv1.weight)
self.conv1_bn = nn.BatchNorm2d(64)
self.fc1 = nn.Linear(3136,1024)
torch.nn.init.kaiming_normal_(self.fc1.weight)
self.fc1_bn = nn.BatchNorm2d(1024)
#使用bn时一般不使用dropout,若使用dropout应该加于bn之后
#self.fc1_drop = nn.Dropout(0.4)
self.fc2 = nn.Linear(1024,10)
torch.nn.init.kaiming_normal_(self.fc2.weight)
def forward(self, x):
x = F.max_pool2d(F.relu(self.conv0_bn(self.conv0(x))),(2,2),stride= 2)
x = F.max_pool2d(F.relu(self.conv1_bn(self.conv1(x))),(2,2),stride= 2)
x = x.view(x.shape[0], -1)
x = F.relu(self.fc1_bn(self.fc1(x)))
#x = self.fc1_drop(F.relu(self.fc1(x)))
x = self.fc2(x)
#若使用cross-entropy损失函数则不需要对输出sigmoid或者softmax激活
return x
print(net)
2.使用现成模型搭建:我们一般不需要完全自己进行网络设计,使用现成经典神经网络会让搭建过程高效很多。首先我们需要下载具体网络结构以及参数:我们可以从github中寻找开源网络结构和参数进行下载,也可以直接从torch.models中加载常用结构(使用现成模型需要我们提前了解各个经典网络的大概特性,之后会尝试进行总结,目前来说denseNet和denseNet的后续版本应该是性能最好的CNN深度学习模型):
#torch.models中包含的常用神经网络结构
#models.VGG
#models.AlexNet
#models.resnet
#models.inception_v3
#models.SqueezeNet
#models.densenet
#以models.resnet152为例
#pretrained设置为True,会自动下载模型所对应权重,并加载到模型中
model = models.resnet152(pretrained=True)
print(model)
之后,我们可以直接使用现成网络,对现成网络的部分结构进行更改(替换/删除)或者将现成网络的部分结构作为自己神经网络的部分结构使用。值得一提的是,不论使用哪种策略一般情况下都需要对模型的最后一层进行替换,因为模型的最后一层体现着具体的任务目标,往往不会相同。
若直接使用现成网络,则上一步下载得到的模型除了替换最后一层之外不需要进行任何更改;如果想要修改模型的部分结构后再进行使用,可以先print出网络的结构,找到目标结构的位置和名字后直接进行修改:
#比如:
model.conv1 = nn.Conv2d(4, 64, kernel_size=7, stride=2, padding=3, bias=False)
model.fc = nn.Linear(2048, 21)
如果想加入现成模型的部分结构到我们自己的神经网络中,可以进行类似这样的处理:
class Net(nn.Module):
#传入的model为我们提前下载的resnet
def __init__(self,model):
super(Net, self).__init__()
#取掉model的后两层(fc层和pooling层),并新添加一个反卷积层、池化层和分类层
self.resnet_layer = nn.Sequential(*list(model.children())[:-2])
self.transion_layer = nn.ConvTranspose2d(2048, 2048, kernel_size=14, stride=3)
self.pool_layer = nn.MaxPool2d(32)
self.Linear_layer = nn.Linear(2048, 8)
def forward(self, x):
x = self.resnet_layer(x)
x = self.transion_layer(x)
x = self.pool_layer(x)
x = x.view(x.size(0), -1)
x = self.Linear_layer(x)
resnet = models.resnet50(pretrained=True)
model = Net(resnet)
首先编写一个train函数来方便对训练进行管理,这里提供的train函数可以:
def train(trainset,val_set,val_label,net,to_train,lr_step_size,lr_gamma,log_dir = None,opt = 'adam',lr = 0.001,epoch_size = 20,batch_size = 50,init = True):
from tensorboardX import SummaryWriter
from torch.optim.lr_scheduler import StepLR
from torch.utils.data import DataLoader
if init == True:
losslist = []
acclist=[]
losslist_val = []
acclist_val = []
else:
hist = np.load('histroy.npz')
losslist = hist['loss']
acclist = hist['acc']
losslist_val = hist['loss_val']
acclist_val = hist['acc_val']
net.load_state_dict(torch.load('params.pkl'))
criterion = nn.CrossEntropyLoss()
if opt=='adam':
optimizer = optim.Adam(to_train,lr=lr)
else:
optimizer = optim.SGD(to_train,lr=lr,momentum = 0.5)
#learning rate schedule
scheduler = StepLR(optimizer, step_size=lr_step_size,
gamma=lr_gamma)
loader = Data.DataLoader(
dataset=trainset,
batch_size=batch_size,
shuffle=True,
num_workers=2 # 多线程来读数据
)
#tensorboardx summarywriter
if log_dir != None:
if not os.path.exists(log_dir):
os.mkdir(log_dir)
summary_writer = SummaryWriter(log_dir)
#选择使用的GPU
#with torch.cuda.device(gpu_id):
for epoch in range(epoch_size):
net.train()
net.cuda()
for i, data in enumerate(loader):
inputs,targets = data
inputs = Variable(inputs.float().cuda())
targets = Variable(targets.cuda())
###################
predict = net(inputs.reshape(50,1,28,28))
loss = criterion(predict, targets)
optimizer.zero_grad()
loss.backward()
optimizer.step()
#总的step数
step = epoch * len(loader) + i
if log_dir != None:
#summary_writer
summary_writer.add_scalar('train/loss', loss.data, step)
valid_loss = []
losslist.append(loss.item())
predict = np.argmax(predict.data.cpu().numpy(), 1)
acc = np.mean(predict == targets.cpu().numpy())
acclist.append(acc)
if step%10 == 0:
net.eval()
net.cuda()
inds=np.random.randint(0,len(val_label)-1,batch_size)
inputs = Variable(val_set[inds].float().cuda())
targets = Variable(val_label[inds].long().cuda())
###########################
predict = net(inputs.reshape(50,1,28,28))
loss = criterion(predict, targets)
losslist_val.append(loss.item())
predict = np.argmax(predict.data.cpu().numpy(), 1)
acc = np.mean(predict == targets.cpu().numpy())
acclist_val.append(acc)
print("EPOCH %d valid_loss: %.4f, train_loss: %.4f" %
(epoch, loss.data, losslist[-1]))
print('valid_acc:%f'%acc)
if log_dir != None:
summary_writer.add_scalar('valid/loss',
losslist_val[-1], (epoch+1)*len(loader)+step)
np.savez('histroy',loss = losslist,acc = acclist,loss_val = losslist_val,acc_val = acclist_val)
torch.save(net.cpu().state_dict(), 'params.pkl')
net.train()
net.cuda()
torch.save(net.cpu().state_dict(),
"./params_epoch/net_{}.pth".format(epoch+1))
#lr scheduler
scheduler.step()
return losslist,acclist,losslist_val,acclist_val
class data_():
def __init__(self,data,label):
self.data_train = data
self.data_label = label.long()
def __getitem__(self, index):
return [self.data_train[index],self.data_label[index]]
def __len__(self):
return self.data_train.size(0)
简单的测试代码:
train_set = train_dataset.data.reshape(60000,1,28,28)
train_lab = train_dataset.targets
data_val = test_dataset.data.reshape(10000,1,28,28)
val_label = test_dataset.targets
data = data_(train_set,train_lab)
model = Net()
model = model.cuda()
# Training settings
batch_size = 64
train_dataset = datasets.MNIST(root='./data/',
train=True,
transform=transforms.ToTensor(),
download=True)
test_dataset = datasets.MNIST(root='./data/',
train=False,
transform=transforms.ToTensor())
#train funciton:
#trainset:torch.Tensor,[train_data,train_label]
#for exp:
#val_set:torch.Tensor,val_data
#val_label:torch.Tensor,val_label
#to_train:a list contains all parameters you want to update:
#for exp:
to_train = []
for i in model.children():
for j in i.parameters():
to_train.append(j)
#log_dir:存放tensorboard中使用的graph和scalar数据的位置
#lr_step_size:lr_schedule中每过lr_step_size降低lr
#for exp:
log_dir = None
lr_step_size = 50
lr_gamma = 1
#test:
a,b,c,d = train(data,data_val,val_label,model,to_train,lr_step_size,lr_gamma,log_dir,opt = 'adam',lr = 0.001,epoch_size = 100,batch_size = 50,init = True)
#tensorboard --logdir=yourpath/train_dir --port=6008
#num:产生的增强数据数量
#path:产生增强图像的原图像路径,之后产生的增强图像会出现在这个路径下的output文件夹中
def augmentor(num,path):
p = Augmentor.Pipeline(path)
p.random_distortion(probability=0.5, grid_width=4, grid_height=4, magnitude=8)
p.rotate(probability=0.7, max_left_rotation=10, max_right_rotation=10)
p.flip_left_right(probability=1)
p.zoom(probability=0.5, min_factor=1.2, max_factor=1.3)
p.gaussian_distortion(probability = 1,grid_width = 5,grid_height = 5,magnitude = 5,corner = 'bell',method = 'in')
p.skew(probability = 0.5)
p.process()
p.sample(num)
类似,我们可以写一个test函数方便对模型表现的检验。
*如果使用迁移学习:使用迁移学习策略时我们不需要更新神经网络中的所有参数:我们需要在训练中fix部分层的参数,在pytorch中我们有两种常用方法来实现参数的冻结:
1.将不需要进行更新的参数的.requires_grad属性设置为False,之后将网络的所有参数传至optimizer中。
2.将需要更新的所有参数放于某个list中,之后将这个list传入optimizer中。
实际情况中两种方法选择一种即可,下方为了方便介绍同时使用了两种方法。
#model.children():返回模型各个‘最外层’(一般是使用同一个sequential搭建的所有层)的生成器
#model.modules():返回模型各个层的生成器,关于这里的层是指所有在网络构造函数中定义的层级结构。
#如果我们只希望更新模型第六个‘最外层’之后的所有参数:
count = 0
para_optim = []
for k in model.children():
count += 1
if count > 6:
for param in k.parameters():
para_optim.append(param)
else:
for param in k.parameters():
param.requires_grad = False
optimizer = optim.RMSprop(para_optim, lr)
使用pytorch我们可以十分方便地对模型中的参数进行提取和管理,一些实用代码可以在这段代码展示的例子中看到:
#若想使用预训练模型中的参数直接更新自己模型的现有层(如果预训练模型中的层数多于自己的模型且有部份层和自己的模型参数完全一样)
vgg16 = models.vgg16(pretrained=True)
pretrained_dict = vgg16.state_dict()
model_dict = model.state_dict()
# 1. filter out unnecessary keys
pretrained_dict = {k: v for k, v in pretrained_dict.items() if k in model_dict}
# 2. overwrite entries in the existing state dict
model_dict.update(pretrained_dict)
# 3. load the new state dict
model.load_state_dict(model_dict)
#值得注意的是,optimizer后的params可以接生成器也可以接列表
optimizer = optim.SGD(params=[resnet_model.fc.weight, resnet_model.fc.bias], lr=1e-3)
将训练数据转换为TensorDataset,放入DataLoader中方便后续的使用(validation set不需要放入dataloader,直接传入train函数即可
):pytorch提供了十分方便的Dataloader方法供我们在训练时使用数据,首先需要在函数外部将数据转换为torch能识别的TensorDataset:
# 先转换成torch能识别的 Dataset
torch_dataset = Data.TensorDataset(x,y)
#如果使用经典数据集,以mnist举例:
transform = transforms.ToTensor()
torch_dataset = tv.datasets.MNIST(
root='./data/',
train=True,
download=True,
transform = transform
)
之后只要设置好超参数并调用train()即可。这里提供了部分常用超参数,如之前所说,随着网络的训练每当loss基本收敛时应该:
to_train = #你要训练的模型参数,若不使用迁移学习则为model.parameters()
opt = 'adam'
lr = 0.001
epoch_size = 20
batch_size = 50
init = True
loss,acc,loss_val,acc_val = train(torch_dataset,model,to_train,opt,lr,epoch_size,batch_size,init)
关于进行跟踪训练的原因与具体分析方法更多说明于上方的实践经验部分,这里主要展示代码。
plot模型在trainset和validation set上的loss与acc曲线
通过train函数返回的四个训练历史数据我们可以直接进行四条曲线的plot。同时,由于在train函数中我们实现了历史数据的实时保存,所以可以通过其他方法读取保存的数据更加方便地进行训练跟踪。
#plot section
plt.plot(range(len(loss)),loss,'b')
plt.plot(range(len(loss_val)),loss_val,'r')
plt.ylim(15,100)
plt.xlabel('number of batchs/##batch_size')
plt.ylabel('averaged loss per sample')
plt.title('the variation of loss with model training')
plt.legend(['training set', 'validation set'])
plt.show()
plt.plot(range(len(acc)),acc,'y')
plt.plot(range(len(acc_val)),acc_val,'r')
plt.ylim(15,100)
plt.xlabel('number of batchs/##batch_size')
plt.ylabel('accuracy')
plt.title('accuracy variation with model training')
plt.legend(['training set', 'validation set'])
plt.show()
#获得该卷积层所有filter的参数,shape为filter数量 * filter维度 * filter长*宽
dd = list(net.conv1.parameters())[0].detach().numpy()
plt.figure()
for i in range(dd.shape[0]):
plt.subplot(3,2,i+1)
plt.imshow(dd[i,:,:,:].squeeze())
saliency map: 让模型对输入求导,通过累计在各个位置上的梯度大小来定性估计模型对于该部分信息的利用程度,这里提供一段简单代码以及展示部分我在之前项目中利用saliency map技术获得的成果(该模型用于对图像中人物的发色与发型进行分类)。
X_var = Variable(test_1,requires_grad=True)
y_var = Variable(test_1_y)
out = net(X_var)
loss = loss_func(out,y_var)
loss.backward()
grads = X_var.grad
grads = grads.abs()
mx, index_mx = torch.max(grads,1)
saliency = np.moveaxis(mx.data.numpy(),0,-1).squeeze()
plt.imshow(saliency, cmap=plt.get_cmap('gray'))
plt.show()
使用降维技术估计模型feature提取层的feature提取能力: 可以运用的降维技术有PCA,LDA,t-SNE等,相关代码与使用经验介绍与博客part(2)中,这里我们只需要将训练后的模型的输出改为分类器的输入,获得这部分特征后使用某种降维手段降至2/3维然后可视化即可。下方展示一部分我对上述发色发型分类模型产生的feature可分性的降维可视化结果(左图/右图为进入发色/发型分类器之中feature的降维可视化图像)。
《机器学习完备流程总结+实践经验细节+代码工具书》博客系列至此完结。之后有计划写类似形式,关于computer vision与图像处理方面技术的系列博客。