现在的位置: 首页 > 在线配资 > 正文
A- A+
深度神经网络的梯度消失(动画)-普大喜奔什么意思

还是延续我们这个系列博文。之前,我们讲解了:

张觉非:神经网络反向传播算法​zhuanlan.zhihu.com张觉非:计算图反向传播的原理及实现​zhuanlan.zhihu.com张觉非:运用计算图搭建 LR、NN、Wide & Deep、FM、FFM 和 DeepFM​zhuanlan.zhihu.com

现在,我们用我们的计算图框架(VectorSlow)搭建一些深度各异的多层全连接神经网络,并观察梯度消失导致的“训不动”现象。首先看一下代码(gradient_show.py):

import matplotlib.pyplot as plt
from matplotlib import animation
from sklearn.metrics import accuracy_score
import os

# 自己的包
from optimizer import *
from util import get_data, construct_pow2
from nn import neural_network

n = 5  # 特征数
classes = 2
epoches = 3

# 构造训练数据
train_x, train_y, test_x, test_y = get_data(number_of_classes=classes, number_of_features=n)

# 构造神经网络计算图
X, logits = neural_network(n, classes, (6, 6, ), "ReLU")

# 对模型输出的 logits 施加 SoftMax 得到多分类概率
prob = SoftMax(logits)

# 训练标签
label = Variable((classes, 1), trainable=False)

# 交叉熵损失
loss = CrossEntropyWithSoftMax(logits, label)  # 注意第一个父节点是 logits

# Adam 优化器
optimizer = Adam(default_graph, loss, 0.02, batch_size=32)

# 绘制动画
fig = plt.figure(figsize=(11, 6))
ax = fig.add_subplot(1, 1, 1)

# 动画初始化函数
def init():
    global ax
    default_graph.draw(ax)


# 动画更新函数
c = 0
e = 0
val_acc = 0


def update(idx):
    global c, e, loss, ax, val_acc
    X.set_value(np.mat(train_x[c, :]).T)
    label.set_value(np.mat(train_y[c, :]).T)

    # 执行一步优化
    optimizer.one_step()

    # 训练样本上的 loss
    if loss.value is None:
        loss.forward()
    train_loss = loss.value[0, 0]

    probs = []
    for i in range(len(test_x)):
        X.set_value(np.mat(test_x[i, :]).T)
        label.set_value(np.mat(test_y[i, :]).T)

        # 前向传播计算概率
        prob.forward()
        probs.append(prob.value.A1)

    # 取概率最大的类别为预测类别
    pred = np.argmax(np.array(probs), axis=1)
    truth = np.argmax(test_y, axis=1)
    val_acc = accuracy_score(truth, pred)

    default_graph.draw(ax)

    msg = "epoches:{:d}, iteration:{:d}, loss:{:.8f}, validation accuracy:{:.3f}%".format(e + 1, c + 1, train_loss,
                                                                                          val_acc * 100)
    ax.set_title(msg)
    print(msg)

    c = (c + 1) % len(train_x)
    if c % len(train_x) == 0:
        e += 1


file = 'video/gradient_show.mp4'

if os.path.exists(file):
    os.remove(file)

anim = animation.FuncAnimation(fig, update, init_func=init, frames=epoches * len(train_x), interval=50, blit=False)
anim.save(file)
# plt.show()

这份代码的前半部分与上一篇博文中的代码类似,搭建计算图,后半部分将一步迭代的逻辑放进动画 update 函数。我们用这份代码绘制几个不同深度的全连接神经网络以及非全连接神经网络,并观察它们的训练过程。

n = 5  # 特征数
classes = 2
epoches = 5

特征数和类别数一般不用改,epoch 数量可以改改试试。注意代码最后一句:

anim.save(file)
# plt.show()

决定是将动画保存成 .mp4 文件还是在运行过程中展示出来。构造神经网络的计算图的代码是:

# 构造神经网络计算图
X, logits = neural_network(n, classes, (6,), "ReLU")

我们主要通过这句话构造不同深度、每层的神经元个数、以及激活函数的神经网络。neural_network 函数的代码如下(nn.py):

from node import *


def neural_network(n, classes=2, hiddens=(12,), activation="ReLU"):
    """
    构造一个多层全连接神经网络的计算图。
    """

    # x 是一个 n 维向量变量,不初始化,不参与训练
    x = Variable((n, 1), init=False, trainable=False)

    # 构造全连接层
    input_size = n
    input_vector = x
    for l, h_size in zip(np.ar ange(len(hiddens)), hiddens):

        output = Add(
            MatMul(
                Variable((h_size, input_size), True),
                input_vector
            ),
            Variable((h_size, 1), True)
        )

        # 隐藏层的输出
        if activation == "ReLU":
            output = ReLU(output)
        elif activation == "Logistic":
            output = Logistic(output)
        else:
            output = output

        input_size = h_size
        input_vector = output

    # 输出层的神经元
    logits = Add(
        MatMul(
            Variable((classes, input_size), True),
            input_vector
        ),
        Variable((classes, 1), True)
    )

    # 返回输入和 logits
    return x, logits

我们首先试试 2 个隐藏层,每层 6 个神经元,激活函数为 ReLU 的简单神经网络:

# 构造神经网络计算图
X, logits = neural_network(n, classes, (6, 6, ), "ReLU")

将 epoches 设为 3 ,运行 gradient_show.py,得到动画(流量大慎点,本文的视频都是 4 至 6 M):

2 隐藏层,每层 6 神经元,ReLUhttps://www.zhihu.com/video/1131926728960053248

解释一下这个视频,它将 VectorSlow 的计算图绘制成图,每个节点是一个圆圈。节点的类型标记在圈中,例如 Variable ,Add 等。每一个节点的值都是矩阵,Variable 类型节点将其保存的矩阵的形状标记在括号中。

训练运行起来后,每一次反向传播都计算最终结果节点(具体说就是交叉熵 loss)对每个计算图节点的“雅可比矩阵”。在最终目标是标量( 矩阵)的情况下,这些雅可比矩阵都是 矩阵, 是节点的值的形状。目标是标量时雅可比矩阵是行向量,它的转置就是目标对中间节点的梯度。

每一次反向传播将节点的梯度的长度(模)显示在节点名称下方的方括号中(例如 [0.003]),同时用颜色的深浅表示梯度的模的大小。每一次反向传播中,若节点的梯度较大,则颜色较深,变量节点(Variable)的值也就得到较大的更新。灰色的节点是不参与训练的节点。

随机梯度下降每次用一个样本前向传播计算 loss ,反向传播计算梯度,故每一次迭代中每一个节点的梯度有较大随机性,但如果看到节点的颜色在闪烁,就说明它(不时)获得了较大的梯度,得到了较显著的更新(实际上这里说得有些简化,VectorSlow 是积累一个 mini batch 的梯度,用 mini batch 内全部样本的平均梯度更新变量)。

动画的标题展示了 epoch 数、每个 epoch 内的迭代数、单个训练样本上的 loss 值(所以随机浮动较大,但整体应呈下降趋势)以及验证集上的正确率。


接下来我们将隐藏层数增加到 3 并增加每层的神经元个数:

# 构造神经网络计算图
X, logits = neural_network(n, classes, (8, 8, 8,), "ReLU")

运行 3 个 epoch :

3 隐藏层,每层 8 神经元,ReLUhttps://www.zhihu.com/video/1131931047398690816

可以看到一开始时网络前部的层(节点)没有得到大的梯度,几乎无更新,这反映在损失值和正确率中。随着训练的进行,网络前部节点逐渐获得了较大的梯度。


接下来,我们再次加大网络的深度(5 层,每层 24 个神经元):

# 构造神经网络计算图
X, logits = neural_network(n, classes, (24, 24, 24, 24, 24), "ReLU")

当层数达到 5 层时训练比较艰难,但终于整个网络还是“点亮”了:

5 隐藏层,每层 24 神经元,ReLUhttps://www.zhihu.com/video/1131940053508923392


现在我们保持网络结构不变,将激活函数换成 Logistic 试试:

5 层,每层 24 神经元,Logistichttps://www.zhihu.com/video/1131945546616836096

发生梯度消失,网络前部的层一直没有获得较大的梯度,训不动。不玩了,有兴趣的读者可以自己试试更多更深的网络结构,也可以用我们的计算图框架添加一些像跳跃连接这种结构。


最后,再做一下广告:《深入理解神经网络——从逻辑回归回到CNN》马上就要上市啦。

暂定封面

关于本文

相关文章


×