吴恩达机器学习课程-作业4-神经网络训练(python实现)

Machine Learning(Andrew) ex4-Neural Networks Learning

椰汁笔记

Neural Networks

  • 1.1 Visualizing the data
  • 1.2 Model representation

由于本次作业的数据和网络结构和上次的作业相同,因此这两步已经在上次作业中完成,这里不再赘述。

  • 1.3 Feedforward and cost function

在这里插入图片描述
当前网络的前向传播计算方法
a(1)=xa(1)a0(1)=1z(2)=Θ(1)a(1)a(2)=g(z(2))a(2)a0(2)=1z(3)=Θ(2)a(2)a(3)=g(z(3))=hθ(x) a^{(1)}=x\\ 向a^{(1)}添加偏执单元a_0^{(1)}=1\\ z^{(2)}=\Theta^{(1)}a^{(1)}\\ a^{(2)}=g(z^{(2)})\\ 向a^{(2)}添加偏执单元a_0^{(2)}=1\\ z^{(3)}=\Theta^{(2)}a^{(2)}\\ a^{(3)}=g(z^{(3)})=h_\theta(x)
最后利用输出层计算cost
J(θ)=1mi=1mk=1K[yk(i)log((hθ(x(i)))k)(1yk(i))log(1(hθ(x(i)))k)] J(\theta)=\frac{1}{m}\sum_{i=1}^m\sum_{k=1}^K[-y^{(i)}_klog((h_{\theta}(x^{(i)}))_k)-(1-y^{(i)}_k)log(1-(h_{\theta}(x^{(i)}))_k)]

这里的y需要注意代表的意义

数字 1 2 3 4 5 6 7 8 9 0
对应的向量下标 0 1 2 3 4 5 6 7 8 9
    a1 = X  # (5000,400)
    a1 = np.insert(a1, 0, 1, axis=1)  # (5000,401)
    z2 = a1.dot(theta1.T)  # (5000,25)
    a2 = sigmoid(z2)  # (5000,25)
    a2 = np.insert(a2, 0, 1, axis=1)  # (5000,26)
    z3 = a2.dot(theta2.T)  # (5000,10)
    a3 = sigmoid(z3)  # (5000,10)
    cost = np.mean(np.sum((-y) * np.log(a3) - (1 - y) * np.log(1 - a3), axis=1))
    print(cost)#0.2876291651613189

在封装装成函数时,发现这个其实可以写成一个循环去处理每层。
在传入theta时需要考虑到后面使用高级的优化方法时theta只能是一维向量,所有我们需要在传入多个theta时先展开成一个向量,在内部再恢复

def serialize(thetas):
    """
    将多个参数多维数组,映射到一个向量上
    :param thetas: tuple or list,按顺序存储每层的theta参数,每个为ndarray
    :return: ndarray,一维化的参数向量
    """
    res = np.array([0])
    for t in thetas:
        res = np.concatenate((res, t.ravel()), axis=0)
    return res[1:]


def deserialize(theta):
    """
    将向量还原为多个参数(只适用当前网络)
    :param theta: ndarray,一维化的参数向量
    :return: tuple ,按顺序存储每层的theta参数,每个为ndarray
    """
    return theta[:25 * 401].reshape(25, 401), theta[25 * 401:].reshape(10, 26)

直接使用循环进行计算,每层计算的操作都是先添加偏置单元,再计算z,再计算A。

def not_regularized_cost(thetas, X, y):
    """
    计算非正则化的损失值
    :param theta: ndarray,一维参数向量
    :param X: ndarray,输入层的输入值
    :param y: ndarray,数据的标记
    :return: float,损失值
    """
    for t in deserialize(thetas):
        X = np.insert(X, 0, 1, axis=1)
        X = X.dot(t.T)
        X = sigmoid(X)
    return np.mean(np.sum((-y) * np.log(X) - (1 - y) * np.log(1 - X), axis=1))

从逻辑回归那里我们知道,由于特征太多,可能会出现过拟合的现象,需要正则化来解决
J(θ)=1mi=1mk=1K[yk(i)log((hθ(x(i)))k)(1yk(i))log(1(hθ(x(i)))k)]+λ2m[j=025k=1400(Θj,k(1))2+j=025k=1400(Θj,k(2))2] J(\theta)=\frac{1}{m}\sum_{i=1}^m\sum_{k=1}^K[-y^{(i)}_klog((h_{\theta}(x^{(i)}))_k)-(1-y^{(i)}_k)log(1-(h_{\theta}(x^{(i)}))_k)]\\ +\frac{\lambda}{2m}[\sum_{j=0}^{25}\sum_{k=1}^{400}(\Theta_{j,k}^{(1)})^2+\sum_{j=0}^{25}\sum_{k=1}^{400}(\Theta_{j,k}^{(2)})^2]
直接在之前的损失函数计算中,加入惩罚项即可。注意不要惩罚偏执单元。

def regularized_cost(theta, X, y, l):
    """
    计算正则化的损失值
    :param theta: ndarray,一维参数向量
    :param X: ndarray,输入层的输入值
    :param y: ndarray,数据的标记
    :param l: float,惩罚参数
    :return: float,损失值
    """
    m = X.shape[0]
    part2 = 0.0
    for t in deserialize(theta):
        X = np.insert(X, 0, 1, axis=1)
        X = X.dot(t.T)
        X = sigmoid(X)
        t = t[..., 1:]  # 要去掉bias unit
        part2 += (l / (2 * m)) * np.sum(t * t)
    part1 = np.mean(np.sum((-y) * np.log(X) - (1 - y) * np.log(1 - X), axis=1))
    return part1 + part2
print(regularized_cost(theta, X, y, 1))#0.38376985909092365

Backpropagation

反向传播算法为梯度下降的计算提供了一个方法。这里我们先不考虑起原理,直接动手实现。
反向传播算法,细节举例(这里以课程中的4层网络举例)
δj(l)ljδ(4)=a(4)yδ(3)=(Θ(3))Tδ(4)g(z(3))δ(2)=(Θ(2))Tδ(3)g(z(2)) 引入{\delta_j^{(l)}},表示第l层的单元j的“损失”\\ \delta^{(4)}=a^{(4)}-y\\ \delta^{(3)}=(\Theta^{(3)})^T\delta^{(4)}\cdot g'(z^{(3)})\\ \delta^{(2)}=(\Theta^{(2)})^T\delta^{(3)}\cdot g'(z^{(2)})
这里没有d1要注意!!!
完整的反向传播算法
{(x(1),y(1)),(x(2),y(2)),,(x(m),y(m))}Δij(l)=0(for all i,j)For i=1 to m: a(1)=x(i)使a(l)δ(l)Δij(l)=Δij(l)+aj(l)δi(l+1),(Δ(l)=Δ(l)+δ(l+1)(a(l))T)Dij(l)=1mΔij(l)+λΘij(l),(if j0)Dij(l)=1mΔij(l),(if j=0)J(Θ)θij(l)=Dij(l) 训练集\{(x^{(1)},y^{(1)}),(x^{(2)},y^{(2)}),\dots,(x^{(m)},y^{(m)})\} \\令\Delta_{ij}^{(l)}=0(for\ all\ i,j) \\For\ i=1\ to\ m: \\令\ a^{(1)}=x^{(i)} \\使用前向传播算法计算出所有a^{(l)} \\计算所有\delta^{(l)} \\计算\Delta_{ij}^{(l)}=\Delta_{ij}^{(l)}+a_j^{(l)}\delta_i^{(l+1)},(\Delta^{(l)}=\Delta^{(l)}+\delta^{(l+1)}(a^{(l)})^T) \\D_{ij}^{(l)}=\frac{1}{m}\Delta_{ij}^{(l)}+\lambda\Theta_{ij}^{(l)},(if\ j\ne0) \\D_{ij}^{(l)}=\frac{1}{m}\Delta_{ij}^{(l)},(if\ j=0) \\\frac{\partial J(\Theta)}{\partial \theta_{ij}^{(l)}}=D_{ij}^{(l)}

  • 2.1 Sigmoid gradient

在计算d时需要用到sigmoid函数的导数,这里很简单可以自己推到一下,这里我们实现
g(z)=ddzg(z)=g(z)(1g(z)) g'(z)=\frac{d}{dz}g(z)=g(z)(1-g(z))

def sigmoid_gradient(z):
    return sigmoid(z) * (1 - sigmoid(z))
print(sigmoid_gradient(0))#0.25
  • 2.2 Random initialization

神经网络的参数初始化是非常讲究的,不能像以前一样全部初始化为0,这样会导致所有隐藏单元计算的值相同高度冗余,这里选择将参数随机化到一个[-e,e]的范围内,这里的e选择方法
e=6Lin+Lout e=\frac{\sqrt{6}}{\sqrt{L_{in}+L_{out}}}

def random_initialize_weights(shape, e=0.12):
    """
    随机初始化参数,范围为[-e, e]
    :param shape: tuple or list,需要初始化的参数的规格
    :param e: float,边界
    :return: ndarray,参数矩阵
    """
    return (np.random.rand(shape[0], shape[1]) - 0.5) * 2 * e
  • 2.3 Backpropagation

直接按照反向传播算法做就好

def back(theta, X, y, l):
    """
    反向传播算法
    :param theta: ndarray,一维参数向量
    :param X: ndarray,输入层的输入值
    :param y: ndarray,数据的标签
    :param l: float,惩罚参数
    :return: ndarray,下降后一维参数向量
    """
    A, Z = feedforward(theta, X)
    a1, a2, a3 = A  # a1(5000,401), a2(5000,26), a3(5000,10)
    z2, z3 = Z  # z2(5000,25), z3(5000,10)
    theta1, theta2 = deserialize(theta)  # theta1(25,401), theta2(10,26)
    m = X.shape[0]

    d3 = a3 - y  # d3(5000,10)
    d2 = d3.dot(theta2)[..., 1:] * sigmoid_gradient(z2)  # d2(5000,25)

    theta1 = np.insert(np.delete(theta1, 0, axis=1), 0, 0, axis=1)
    theta2 = np.insert(np.delete(theta2, 0, axis=1), 0, 0, axis=1)
    D1 = (1 / m) * d2.T.dot(a1) + (l / m) * theta1  # D1(25,401)
    D2 = (1 / m) * d3.T.dot(a2) + (l / m) * theta2  # D2(10,26)
    return serialize((D1, D2))
  • 2.4 Gradient checking
    为了方便检测我们的梯度下降算法是否正确,我们采用近似计算的方式来对比
    let θ(i+)=θ+[00ϵ0],θ(i)=θ[00ϵ0]fi(θ)J(θ(i+))J(θ(i))2ϵ let\ \theta^{(i+)}=\theta+\left[\begin{array}{ccc} 0 \\\\ 0 \\\\ \vdots \\\\ \epsilon \\\\ \vdots \\\\ 0 \\\\\end{array}\right],\theta^{(i-)}=\theta-\left[\begin{array}{ccc} 0 \\\\ 0 \\\\ \vdots \\\\ \epsilon \\\\ \vdots \\\\ 0 \\\\\end{array}\right] \\f_i(\theta)\approx\frac{J(\theta^{(i+)})-J(\theta^{(i-)})}{2\epsilon}
def gradient_checking(theta, X, y, l, e=10 ** -4):
    """
    检测反向传播算法是否正确运行
    :param theta: ndarray,一维参数向量
    :param X: ndarray,输入层的输入值
    :param y: ndarray,数据的标签
    :param l: float,惩罚参数
    :param e: float,微小扰动
    :return: ndarray,下降后一维参数向量
    """
    res = np.zeros(theta.shape)
    for i in range(len(theta)):
        left = np.array(theta)
        left[i] -= e
        right = np.array(theta)
        right[i] += e
        gradient = (regularized_cost(right, X, y, l) - regularized_cost(left, X, y, l)) / (2 * e)
        res[i] = gradient
    return res

这个计算是非常耗时的,应用到一部分计算中对比结果,检测反向传播算法是否正确。之后要拿出。

  • 2.5 Regularized Neural Networks

在上面我就一并做了正则化

  • 2.6 Learning parameters using fmincg

下面开始训练我们的模型,这里建议训练完后将参数保存一下,因为训练时间还是比较久,免得后面重跑。

    # 以上是为了测试写得是否正确,所以按照提供数据更改了y,下面我们将使用最自然的y表示方式
    y = convert(data['y'])
    theta1 = random_initialize_weights((25, 401))
    theta2 = random_initialize_weights((10, 26))
    theta = serialize((theta1, theta2))
    # 参数初始化得不同,有时会导致溢出
    res = opt.minimize(fun=regularized_cost, x0=theta, args=(X, y, 1), method="TNC", jac=back)
    print(res)
    theta1, theta2 = deserialize(res.x)
    sio.savemat("parametersWeights.mat", {"theta1": theta1, "theta2": theta2})

接着评价一下结果

def predict(theta, X):
    a3 = feedforward(theta, X)[0][-1]
    p = np.zeros((1, 10))
    for i in a3:
        index = np.argmax(i)
        temp = np.zeros((1, 10))
        temp[0][index] = 1
        p = np.concatenate((p, temp), axis=0)
    return p[1:]
print(classification_report(y, predict(res.x, X)))
visualizing_the_hidden_layer(res.x, X)

可以看到,神经网络相较于逻辑回归拟合得更好,但是计算量确实大,我们这里只有一个隐藏层久需要计算数分钟,很难想象大型网络得计算需要多大得计算资源。当然我们这里得神经网络是最简单得全连接神经网络,参数确实多。神经网络还有其他带有非全连接得网络结构,这些结构会使参数量下降。
在这里插入图片描述

Visualizing the hidden layer

最后我么可以看看隐藏层输出得图像是个什么样子
注意在计算后显示要去掉偏执单元

def visualizing_the_hidden_layer(theta, X):
    """
    可视化显示隐藏层的输入和输出
    :param theta: ndarray,一维参数向量
    :param X: ndarray,输入层的输入值
    :return: None
    """
    A, _ = feedforward(theta, X)
    a1, a2, a3 = A
    # 要去掉bias unit
    input = a1[..., 1:][:25]
    output = a2[..., 1:][:25]
    input = mapping(input, 5)
    output = mapping(output, 5)
    plt.subplot(1, 2, 1)
    plt.axis('off')
    plt.imshow(input.T)
    plt.title("hidden layer input")

    plt.subplot(1, 2, 2)
    plt.axis('off')
    plt.imshow(output)
    plt.title("hidden layer output")
    plt.show()

只观察0得数据,可以看到,输出后得图像是存在一些相似。
在这里插入图片描述
总结一下简单的神经网络使用方法

  • 1.选择一个网络结构
    • 将当前得特征数量作为输入层得单元数量
    • 将分类得类数作为输出单元数量
    • 当隐藏层大于1层时,通常所有隐藏层得单元数一样
  • 2.训练网络
    • 随机初始化单元连接之间得权重
    • 实现前向传播
    • 实现计算损失值
    • 实现反向传播
    • 实现梯度下降
    • 最小化损失值
  • 3.评价模型
    • 在训练网络前,需要将数据随机分为三部分,作为训练集、交叉验证集和测试集
    • 使用训练集训练网络
    • 交叉验证集用于,当需要从多个网络模型中择优时,利用交叉验证集计算误差,通过量化的方式选择,模型
    • 测试集用于最终评价模型

完整的代码会同步在我的github

欢迎指正错误

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章