前向传播
神经网络每层都包含有若干神经元,当信息传递时,第i层神经元接受上层的输入,经激励函数作用后,会产生一个激活向量,此向量将作为下一层神经元的输入值,以此规律向下不断传递。整个过程因为发生顺序是不断地将刺激由前一层传向下一层,故而称之为前向传递。
TensorFlow中添加一层的代码如下:
def add_layer(inputs, in_size, out_size, activation_function=None):
W = tf.Variable(tf.random_normal([in_size, out_size])) # 权值矩阵
b = tf.Variable(tf.zeros([1, out_size]) + 0.1) # 偏移
f = tf.matmul(inputs, W) + b # 输出
if activation_function is None:
outputs = f
else:
outputs = activation_function(f) # 激励函数
return outputs
假设隐藏层只有一层,核心代码如下:
l1 = add_layer(x, 1, 10, tf.nn.relu) # 隐藏层, 输入向量为x,有10个神经元,激励函数为ReLU
prediction = add_layer(l1, 10, 1, None) # 将隐藏层的输出作为输入,输出预测值
反向传播
在神经网络中,我们也需要通过最小化代价函数来优化预测精度。但由于神经网络各层的神经元都会产出预测,因此,不能直接利用传统的梯度下降法来最小化,而需要逐层考虑预测误差,并且逐层优化。为此,在多层神经网络中,使用反向传播算法来优化预测。本层的误差由下一层的误差反向推导。
loss = tf.reduce_mean(tf.reduce_sum(tf.square(y - prediction), axis=1)) # 损失函数,y为训练数据
train = tf.train.GradientDescentOptimizer(0.1).minimize(loss)
表面上看,代码似乎只构建了正向的传播,而整个反向传播过程其实都是在GradientDescentOptimizer函数中完成的。具体过程可查看参考资料。
示例
从训练集的5000个样本中随机选取100个样本进行可视化。每个样本是一个400维的向量,需要在可视化时重组为20×20的像素网格。
data = loadmat('ex4data1.mat')
X = data['X']
y = data['y'].flatten()
images = X[np.random.choice(range(5000), 100)] # 随机取100个数字
fig, ax_array = plt.subplots(10, 10, sharex=True, sharey=True, figsize=(8, 8)) # 生成10*10个子图
for r in range(10):
for c in range(10):
ax_array[r, c].matshow(images[r * 10 + c].reshape(20, 20), cmap='gray_r') # 每一个子图显示一个图片数据
plt.xticks([]) # 坐标轴内容设置为空
plt.yticks([])
plt.show()
我们需要对标签进行独热编码,将其转换为长度向量。本例有十个标签值,那么像数字6就要转化为[0,0,0,0,0,1,0,0,0,0]。使用scikit-learn中OneHotEncoder函数解决。
encoder = OneHotEncoder(sparse=False, categories='auto')
y = encoder.fit_transform(y.reshape(-1, 1))
从文件读取已经训练好的参数$/Theta1$,$/Theta2$,利用前向传播计算输出结果。本例有一个输入层、一个隐藏层和一个输出层,注意添加偏置单元。
data2 = loadmat('ex4weights.mat')
theta1 = data2['Theta1']
theta2 = data2['Theta2']
def sigmoid(z): # 激励函数
return 1 / (1 + np.exp(-z))
def forward(X, theta1, theta2):
a1 = np.insert(X, 0, 1, axis=1) # 添加偏置单元
z2 = np.dot(a1, theta1.T)
a2 = np.insert(sigmoid(z2), 0, 1, axis=1) # 添加偏置单元
z3 = np.dot(a2, theta2.T)
h = sigmoid(z3)
return a1, z2, a2, z3, h
计算代价函数并添加正则化,注意不要将偏置项正则化。前向传播算法就到此结束了。
m = len(X)
def cost(theta, X, y):
a1, z2, a2, z3, h = forward(theta, X)
J = np.sum(-y * np.log(h) - (1 - y) * np.log(1 - h)) / m
return J
def regularized_cost(theta, X, y, l=1.):
theta1, theta2 = deserialize(theta)
reg = np.sum(theta1[:, 1:] ** 2) + np.sum(theta2[:, 1:] ** 2)
return cost(theta, X, y) + l / (2 * m) * reg
接下来是反向传播算法,首先计算输出层的误差向量,然后反向计算其他层的误差向量。再求各层权值参数(theta1和theta2)的梯度,进而得到更新增量。正则化时注意不处理偏置单元的参数,可将参数置零以统一正则化公式。
def sigmoid_gradient(z):
return sigmoid(z) * (1 - sigmoid(z))
def gradient(theta, X, y):
theta1, theta2 = deserialize(theta)
a1, z2, a2, z3, h = forward(theta, X)
# 误差向量
d3 = h - y
d2 = np.dot(d3, theta2[:, 1:]) * sigmoid_gradient(z2)
# 梯度
D2 = np.dot(d3.T, a2)
D1 = np.dot(d2.T, a1)
# 更新增量
D = (1 / m) * serialize(D1, D2)
return D
def regularized_gradient(theta, X, y, l=1.):
D1, D2 = deserialize(gradient(theta, X, y))
theta1[:, 0] = 0 # 偏置单元置0
theta2[:, 0] = 0
reg_D1 = D1 + (l / m) * theta1
reg_D2 = D2 + (l / m) * theta2
return serialize(reg_D1, reg_D2)
进行梯度校验,比较实际值和估测值的相似程度。
def gradient_checking(X, y, epsilon):
grad_approx = []
for i in range(len(theta)): # 计算grad_approx
plus = theta.copy()
minus = theta.copy()
plus[i] += epsilon
minus[i] -= epsilon
approx = (regularized_cost(plus, X, y) - regularized_cost(minus, X, y)) / (epsilon * 2)
grad_approx.append(approx)
grad_approx = np.array(grad_approx)
grad_compute = regularized_gradient(theta, X, y)
diff = np.linalg.norm(grad_approx - grad_compute) / np.linalg.norm(grad_approx + grad_compute) # 比较相似程度,使用欧式距离
return diff
参考资料: 前向传播与反向传播 建造我们第一个神经网络 tensorflow的自动求导具体是在哪部分代码里实现的? 数据预处理:独热编码(One-Hot Encoding)