梯度下降
之前我们看到一个权重的更新方法是:
- 训练数据中的每一条记录:
- 通过网络做正向传播,计算输出
- 计算输出的 error term,
- 更新权重步长
- 更新权重
η 是学习率, m 是数据点个数。 这里我们对权重步长做了平均,为的是降低训练数据中大的变化。
- 重复 e 次(epoch)。
你也可以选择每一个记录更新一下权重,而不是把所有记录都训练过之后再取平均。
这里我们还是使用 sigmoid 作为激活函数:
sigmoid 的梯度是:
h 从输入计算的输出,
用 NumPy 来实现
这里大部分都可以用 NumPy 很方便的实现。
首先你需要初始化权重。我们希望它们比较小,这样输入在 sigmoid 函数那里可以在接近 0 的位置,而不是最高或者最低处。很重要的一点是要随机地初始化它们,这样它们有不同的值,是发散且不对称的。所以我们从一个中心为 0 的正态分布来初始化权重。一个好的标准差的值是 1/√n,这里 n 是输入的个数。这样就算是输入个数变多,进到 sigmoid 的输入还能保持比较小。
weights = np.random.normal(scale=1/n_features**.5, size=n_features)
NumPy 提供了一个可以让两个序列做点乘的函数,它可以让我们方便地计算 h。点乘是把两个序列的元素对应位置相乘之后再相加。
# input to the output layer
output_in = np.dot(weights, inputs)
最后我们更新 Δwi和 wi。
编程练习
接下来,你要实现一个梯度下降,用录取数据(binary.csv)来训练它。你的目标是训练一个网络直到你达到训练数据的最小的均方差mean square error (MSE)。你需要实现:
- 网络的输出: output
- 输出误差: error
- 误差项: error_term
- 权重更新步长: del_w +=
- 更新权重: weights +=
你可以任意调节超参数 hyperparameters 来看下它对均方差 MSE 有什么影响。
train_gradient_descent.py
import numpy as np
from data_prep import features, targets, features_test, targets_test
# Practice implementing gradient descent and training the network on
# an admissions dataset. The goal of the exercise is to train the network
# until reaching the minimum mean sqauared error.
#
# Based on an exercise from Udacity.com
###################################################################
def sigmoid(x):
"""
Calculate sigmoid
"""
return 1 / (1 + np.exp(-x))
# Use to same seed to make debugging easier
np.random.seed(42)
n_records, n_features = features.shape
last_loss = None
# Initialize weights
weights = np.random.normal(scale=1 / n_features**.5, size=n_features)
# Neural Network hyperparameters
epochs = 1000
learnrate = 0.5
for e in range(epochs):
del_w = np.zeros(weights.shape)
for x, y in zip(features.values, targets):
# Loop through all records, x is the input, y is the target
output = sigmoid(np.dot(weights, x))
error = y - output
del_w += error * output * (1 - output) * x
weights += learnrate * del_w / n_records
# Printing out the mean square error on the training set
if e % (epochs / 10) == 0:
out = sigmoid(np.dot(features, weights))
loss = np.mean((out - targets) ** 2)
if last_loss and last_loss < loss:
print("Train loss: ", loss, " WARNING - Loss Increasing")
else:
print("Train loss: ", loss)
last_loss = loss
# Calculate accuracy on test data
tes_out = sigmoid(np.dot(features_test, weights))
predictions = tes_out > 0.5
accuracy = np.mean(predictions == targets_test)
print("Prediction accuracy: {:.3f}".format(accuracy))
data_prep.py
import numpy as np
import pandas as pd
admissions = pd.read_csv('binary.csv')
# Make dummy variables for rank
data = pd.concat([admissions, pd.get_dummies(admissions['rank'], prefix='rank')], axis=1)
data = data.drop('rank', axis=1)
# Standarize features
for field in ['gre', 'gpa']:
mean, std = data[field].mean(), data[field].std()
data.loc[:,field] = (data[field]-mean)/std
# Split off random 10% of the data for testing
np.random.seed(42)
sample = np.random.choice(data.index, size=int(len(data)*0.9), replace=False)
data, test_data = data.ix[sample], data.drop(sample)
# Split into features and targets
features, targets = data.drop('admit', axis=1), data['admit']
features_test, targets_test = test_data.drop('admit', axis=1), test_data['admit']
多层感知器
实现隐藏层
之前我们研究的是有一个输出节点网络,代码也很直观。但是现在我们有不同的输入,多个隐藏层,他们的权重需要有两个索引 wij,i 表示输入单位,j 表示隐藏单位。
例如在下面这个网络图中,输入被标注为 x1,x2, x3,隐藏层节点是 h1 和 h2。
注意:
这里权重的索引在上图中做了改变,与之前图片并不匹配。这是因为,在矩阵标注时行索引永远在列索引之前,所以用之前的方法做标识会引起误导。你只需要了解这跟之前的权重矩阵是一样的,只是做了转换,之前的第一列现在是第一行,之前的第二列现在是第二行。如果用之前的标记,权重矩阵是下面这个样子的:
切记,上面标注方式是不正确的,这里只是为了让你更清楚这个矩阵如何跟之前神经网络的权重匹配。
矩阵相乘最重要的是他们的维度相匹配。因为它们在点乘时需要有相同数量的元素。在第一个例子中,输入向量有三列,权重矩阵有三行;第二个例子中,权重矩阵有三列,输入向量有三行。如果维度不匹配,你会得到:
# Same weights and features as above, but swapped the order
hidden_inputs = np.dot(weights_input_to_hidden, features)
---------------------------------------------------------------------------
ValueError Traceback(most recent call last)
<ipython-input-11-1bfa0f615c45> in <module>()
----> 1 hidden_in = np.dot(weights_input_to_hidden, X)
ValueError: shapes (3,2) and (3,) not aligned: 2 (dim 1) != 3 (dim 0)
3x2 的矩阵跟 3 个元素序列是没法相乘的。因为矩阵中的两列与序列中的元素个数并不匹配。能够相乘的矩阵如下:
这里的规则是,如果是序列在左边,序列的元素个数必须与右边矩阵的行数一样。如果矩阵在左边,那么矩阵的列数,需要与右边向量的行数匹配。
构建一个列向量
看上面的介绍,你有时会需要一个列向量,尽管 NumPy 默认是行向量。你可以用 arr.T 来对序列进行转制,但对一维序列来说,转制还是行向量。所以你可以用 arr[:,None]
来创建一个列向量:
print(features)
> array([ 0.49671415, -0.1382643 , 0.64768854])
print(features.T)
> array([ 0.49671415, -0.1382643 , 0.64768854])
print(features[:, None])
> array([[ 0.49671415], [-0.1382643 ], [ 0.64768854]])
当然,你可以创建一个二维序列,然后用 arr.T 得到列向量。
np.array(features, ndmin=2)
> array([[ 0.49671415, -0.1382643 , 0.64768854]])
np.array(features, ndmin=2).T
> array([[ 0.49671415], [-0.1382643 ], [ 0.64768854]])
编程练习
实现一个 4x3x2 网络的正向传播,用 sigmoid 作为两层的激活函数。
要做的事情:
- 计算到隐藏层的输入
- 计算隐藏层输出
- 计算输出层的输入
- 计算神经网络的输出
import numpy as np
N_input = 4
N_hidden = 3
N_output = 2
np.random.seed(42)
X = np.random.randn(4)
weights_input_to_hidden = np.random.normal(0, scale=0.1, size=(N_input, N_hidden))
weights_hidden_to_output = np.random.normal(0, scale=0.1, size=(N_hidden, N_output))
hidden_layer_in = np.dot(X, weights_input_to_hidden)
hidden_layer_out = sigmoid(hidden_layer_in)
output_layer_in = np.dot(hidden_layer_out, weights_hidden_to_output)
output_layer_out = sigmoid(output_layer_in)
print out_layer_out
网友评论