1.1.4 反向传递与优化器
深度学习训练过程如图1-7所示。
在深度学习模型里面,经常需要使用梯度算法,针对损失函数的反馈不断调整各层的参数,使得损失函数最小化。在训练阶段,真实值就是样本对应的真实标签,预测值就是机器学习模型预测的标签值,这些都是明确的,所以损失函数是可以定义和计算的。机器学习模型训练的过程就是不断调整参数追求损失函数最小的过程。梯度可以理解为多元函数的指定点上升的坡度,假设多元函数可以表示为f(x,y),那么对应的梯度的定义为:
可见梯度可以用偏导数来定义,通常损失函数就是这个多元函数,特征向量就可以看成这个多元函数的某个点。在训练过程中,针对参数的调整可以使用梯度和学习率来定义,其中学习率也叫作学习步长,物理含义就是变量在梯度方向上移动的长度,学习率是一个非常重要的参数,学习率过大会导致损失函数的震荡难以收敛,过小会导致计算缓慢,目前还没有很成熟的理论来推倒最合适的学习率,经验值是0.001~0.1。以表示学习率,那么迭代更新参数x的方法为:
图1-7 深度学习训练过程
在求函数的最大值时,我们会向梯度向上的方向移动,使用加号,也称为梯度向上算法。如果我们想求函数的最小值,则需要向梯度向下的方向移动,使用减号,也称为梯度下降算法,比如求损失函数最小值时,对应迭代求解的方法为:
我们通过一个非常简单的例子演示这个过程,假设我们只有一个变量x,对应的损失函数定义为:
根据梯度的定义,可以获得对应的梯度为:
我们随机初始化x,将学习率设置为0.1,整个过程如下:
def demo(): import random a=0.1 x=random.randint(1,10) y = x * x + 2 index=1 while index < 100 and abs(y-2) > 0.01 : y=x*x+2 print "batch={} x={} y={}".format(index,x,y) x=x-2*x*a index+=1
整个迭代过程最多100步,由于我们预先知道函数的最小值为2,所以如果当计算获得的函数值非常接近2,我们也可以提前退出迭代过程,比如绝对值相差不超过0.01。最后果然没让我们失望,在迭代20次后就找到了接近理论上的最小点:
batch=14 x=0.329853488333 y=2.10880332377 batch=15 x=0.263882790666 y=2.06963412721 batch=16 x=0.211106232533 y=2.04456584141 batch=17 x=0.168884986026 y=2.02852213851 batch=18 x=0.135107988821 y=2.01825416864 batch=19 x=0.108086391057 y=2.01168266793 batch=20 x=0.0864691128455 y=2.00747690748
Keras里面提供相应的工具返回loss函数关于variables的梯度,variables为张量变量的列表,这里的loss函数即损失函数:
from keras import backend as K k.gradients(loss, variables)
Keras也提供了function用于实例化一个Keras函数,inputs是输入张量的列表,其元素为占位符或张量变量,outputs为输出张量的列表:
k.function(inputs, outputs, updates=[])
常用的优化器包括SGD、RMSprop和Adam。
1.SGD
SGD即随机梯度下降法,是最基础的优化方法。普通的训练方法需要重复不断地把整套数据放入神经网络中训练,这会消耗大量计算资源。SGD则会把数据拆分后再分批不断地放入神经网络中来计算。每次使用批数据,虽然不能反映整体数据的情况,不过却在很大程度上加速了神经网络的训练过程,而且也不会丢失太多准确率。
SGD支持动量参数,支持学习衰减率,函数的定义如下:
keras.optimizers.SGD(lr=0.01, momentum=0.0, decay=0.0, nesterov=False)
其中比较重要的参数如下:
·lr:学习率。
·momentum:动量参数。
·decay:每次更新后的学习率衰减值。
2.RMSprop
RMSprop是面对递归神经网络时的一个良好选择,函数的定义如下:
keras.optimizers.RMSprop(lr=0.001, rho=0.9, epsilon=1e-06)
其中比较重要的参数如下:
·lr:学习率。
·epsilon:大于或等于0的小浮点数,防止除0错误。
3.Adam
Adam是一种可以替代SGD的一阶优化算法,它能基于训练数据迭代地更新神经网络权重,是目前最受欢迎的优化算法之一,定义如下:
keras.optimizers.Adam(lr=0.001, beta_1=0.9, beta_2=0.999, epsilon=1e-08)
其中比较重要的参数如下:
·lr:学习率。
·epsilon:大于或等于0的小浮点数,防止除0错误。
下面我们以迭代生成对抗样本的例子来感性认识一下不同优化器的计算收敛速度,代码路径为:
https://github.com/duoergun0729/adversarial_examples/code/1-case1-pytorch.ipynb
首先,定义全局变量,其中adam_original_loss、sdg_original_loss和RMSprop_original_loss分别代表迭代过程中不同优化算法对应的损失函数的值:
sdg_original_loss=[] RMSprop_original_loss=[] epoch_range=[]
加载测试图片,并缩放到长和宽均为224:
#获取计算设备,默认是CPU device = torch.device("cuda" if torch.cuda.is_available() else "cpu") #图像加载以及预处理 image_path="../picture/cropped_panda.jpg" orig = cv2.imread(image_path)[..., ::-1] orig = cv2.resize(orig, (224, 224)) img = orig.copy().astype(np.float32)
对图像数据进行标准化处理,由于攻击的图像分类模型是基于ImageNet2012数据集进行预训练的,因此需要使用ImageNet2012数据集特有的均值mean和标准差std进行标准化:
mean = [0.485, 0.456, 0.406] std = [0.229, 0.224, 0.225] img /= 255.0 img = (img - mean) / std img = img.transpose(2, 0, 1) img=np.expand_dims(img, axis=0) img = Variable(torch.from_numpy(img).to(device).float())
实例化alexnet模型并加载预训练的参数。在使用迭代优化的过程中,整个模型的参数不变化,反向传递仅调整原始图像的内容:
#使用预测模式主要影响dropout和BN层的行为 model = models.alexnet(pretrained=True).to(device).eval() #获取分类标签 label=np.argmax(model(img).data.cpu().numpy()) print("label={}".format(label)) #图像数据梯度可以获取 img.requires_grad = True #设置为不保存梯度值,自然也无法修改 for param in model.parameters(): param.requires_grad = False
使用定向攻击,攻击目标的标签值为288,最大迭代次数为100:
loss_func = torch.nn.CrossEntropyLoss() epochs=100 target=288 target=Variable(torch.Tensor([float(target)]).to(device).long())
迭代优化的计算过程中,根据预测结果与定向攻击目标计算损失值,并通过手工调用反向传递过程,更新原始图像:
for epoch in range(epochs): # 梯度清零 optimizer.zero_grad() # forward + backward output = model(img) loss = loss_func(output, target) label=np.argmax(output.data.cpu().numpy()) adam_original_loss+=[loss] epoch_range += [epoch] #手工调用反向传递计算,更新原始图像 loss.backward() optimizer.step()
分别实例化不同的优化器,记录100次迭代优化过程中损失值的变化,如图1-8所示,当使用相同的学习速率对同一图片进行迭代优化生成定向攻击样本时,RMSprop和Adam明显快于SGD:
fig, ax = plt.subplots() ax.plot(np.array(epoch_range), np.array(adam_original_loss), 'b--', label='Adam') ax.plot(np.array(epoch_range), np.array(RMSprop_original_loss), 'b-', label='RMSprop') ax.plot(np.array(epoch_range), np.array(sdg_original_loss), 'b:', label='SGD') legend = ax.legend(loc='best', shadow=True, fontsize='large') legend.get_frame().set_facecolor('#FFFFFF') plt.xlabel('Iteration Step ') plt.ylabel('Loss') plt.show()
图1-8 相同条件下不同优化算法的收敛速度