椰汁笔记
由于本次作业的数据和网络结构和上次的作业相同,因此这两步已经在上次作业中完成,这里不再赘述。
当前网络的前向传播计算方法
a ( 1 ) = x 向 a ( 1 ) 添 加 偏 执 单 元 a 0 ( 1 ) = 1 z ( 2 ) = Θ ( 1 ) a ( 1 ) a ( 2 ) = g ( z ( 2 ) ) 向 a ( 2 ) 添 加 偏 执 单 元 a 0 ( 2 ) = 1 z ( 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) a(1)=x向a(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)
最后利用输出层计算cost
J ( θ ) = 1 m ∑ i = 1 m ∑ k = 1 K [ − y k ( i ) l o g ( ( h θ ( x ( i ) ) ) k ) − ( 1 − y k ( i ) ) l o g ( 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)] J(θ)=m1i=1∑mk=1∑K[−yk(i)log((hθ(x(i)))k)−(1−yk(i))log(1−(hθ(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 ( θ ) = 1 m ∑ i = 1 m ∑ k = 1 K [ − y k ( i ) l o g ( ( h θ ( x ( i ) ) ) k ) − ( 1 − y k ( i ) ) l o g ( 1 − ( h θ ( x ( i ) ) ) k ) ] + λ 2 m [ ∑ j = 0 25 ∑ k = 1 400 ( Θ j , k ( 1 ) ) 2 + ∑ j = 0 25 ∑ k = 1 400 ( Θ 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] J(θ)=m1i=1∑mk=1∑K[−yk(i)log((hθ(x(i)))k)−(1−yk(i))log(1−(hθ(x(i)))k)]+2mλ[j=0∑25k=1∑400(Θj,k(1))2+j=0∑25k=1∑400(Θ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
反向传播算法为梯度下降的计算提供了一个方法。这里我们先不考虑起原理,直接动手实现。
反向传播算法,细节举例(这里以课程中的4层网络举例)
引 入 δ j ( l ) , 表 示 第 l 层 的 单 元 j 的 “ 损 失 ” δ ( 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)}) 引入δj(l),表示第l层的单元j的“损失”δ(4)=a(4)−yδ(3)=(Θ(3))Tδ(4)⋅g′(z(3))δ(2)=(Θ(2))Tδ(3)⋅g′(z(2))
这里没有d1要注意!!!
完整的反向传播算法
训 练 集 { ( x ( 1 ) , y ( 1 ) ) , ( x ( 2 ) , y ( 2 ) ) , … , ( x ( m ) , y ( m ) ) } 令 Δ i j ( l ) = 0 ( f o r a l l i , j ) F o r i = 1 t o m : 令 a ( 1 ) = x ( i ) 使 用 前 向 传 播 算 法 计 算 出 所 有 a ( l ) 计 算 所 有 δ ( l ) 计 算 Δ i j ( l ) = Δ i j ( l ) + a j ( l ) δ i ( l + 1 ) , ( Δ ( l ) = Δ ( l ) + δ ( l + 1 ) ( a ( l ) ) T ) D i j ( l ) = 1 m Δ i j ( l ) + λ Θ i j ( l ) , ( i f j ≠ 0 ) D i j ( l ) = 1 m Δ i j ( l ) , ( i f j = 0 ) ∂ J ( Θ ) ∂ θ i j ( l ) = D i j ( 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)} 训练集{(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)=m1Δij(l)+λΘij(l),(if j=0)Dij(l)=m1Δij(l),(if j=0)∂θij(l)∂J(Θ)=Dij(l)
在计算d时需要用到sigmoid函数的导数,这里很简单可以自己推到一下,这里我们实现
g ′ ( z ) = d d z g ( z ) = g ( z ) ( 1 − g ( z ) ) g'(z)=\frac{d}{dz}g(z)=g(z)(1-g(z)) g′(z)=dzdg(z)=g(z)(1−g(z))
def sigmoid_gradient(z):
return sigmoid(z) * (1 - sigmoid(z))
print(sigmoid_gradient(0))#0.25
神经网络的参数初始化是非常讲究的,不能像以前一样全部初始化为0,这样会导致所有隐藏单元计算的值相同高度冗余,这里选择将参数随机化到一个[-e,e]的范围内,这里的e选择方法
e = 6 L i n + L o u t e=\frac{\sqrt{6}}{\sqrt{L_{in}+L_{out}}} e=Lin+Lout6
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
直接按照反向传播算法做就好
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))
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
这个计算是非常耗时的,应用到一部分计算中对比结果,检测反向传播算法是否正确。之后要拿出。
在上面我就一并做了正则化
下面开始训练我们的模型,这里建议训练完后将参数保存一下,因为训练时间还是比较久,免得后面重跑。
# 以上是为了测试写得是否正确,所以按照提供数据更改了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)
可以看到,神经网络相较于逻辑回归拟合得更好,但是计算量确实大,我们这里只有一个隐藏层久需要计算数分钟,很难想象大型网络得计算需要多大得计算资源。当然我们这里得神经网络是最简单得全连接神经网络,参数确实多。神经网络还有其他带有非全连接得网络结构,这些结构会使参数量下降。
最后我么可以看看隐藏层输出得图像是个什么样子
注意在计算后显示要去掉偏执单元
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得数据,可以看到,输出后得图像是存在一些相似。
总结一下简单的神经网络使用方法
完整的代码会同步在我的github
欢迎指正错误