梯度下降法及多层感知器

梯度下降

之前我们看到一个权重的更新方法是:

​​

这里 error term δ 是指

​​

记住,上面公式中

是输出误差,f' (h) 是激活函数 f(h) 的导函数,我们把这个导函数称做输出的梯度。

现在假设只有一个输出,把这个写成代码。我们还是用 sigmoid 来作为激活函数 f(h)。

# Defining the sigmoid function for activations 
# 定义 sigmoid 激活函数
def sigmoid(x):
    return 1/(1+np.exp(-x))

# Derivative of the sigmoid function
# 激活函数的导数
def sigmoid_prime(x):
    return sigmoid(x) * (1 - sigmoid(x))

# Input data
# 输入数据
x = np.array([0.1, 0.3])
# Target
# 目标
y = 0.2
# Input to output weights
# 输入到输出的权重
weights = np.array([-0.8, 0.5])

# The learning rate, eta in the weight step equation
# 权重更新的学习率
learnrate = 0.5

# the linear combination performed by the node (h in f(h) and f'(h))
# 输入和权重的线性组合
h = x[0]*weights[0] + x[1]*weights[1]
# or h = np.dot(x, weights)

# The neural network output (y-hat)
# 神经网络输出
nn_output = sigmoid(h)

# output error (y - y-hat)
# 输出误差
error = y - nn_output

# output gradient (f'(h))
# 输出梯度
output_grad = sigmoid_prime(h)

# error term (lowercase delta)
error_term = error * output_grad

# Gradient descent step 
# 梯度下降一步
del_w = [ learnrate * error_term * x[0],
          learnrate * error_term * x[1]]
# or del_w = learnrate * error_term * x

实现梯度下降

我们拿一个研究生学院录取数据来用梯度下降训练一个网络。数据可以在这里找到。数据有三个输入特征,GRE分数,GPA,和本科院校排名(从1到4)。数字1代表最好,数字4代表最差。

我们的目标是基于这些特征来预测一个学生能否被研究生院录取。这里,我们用有一个输出层的网络。用 sigmoid 做为激活函数。

数据清理
你也许认为有三个输入特征,但是首先我们要做数据转换。rank是类别特征,数字并不包含任何想对的值。排名第 2 并不是排名第 1 的两倍;排名第 3 也不是排名第 2 的 1.5 倍。因此,我们需要用 dummy variables 来对rank进行编码。把数据分成 4 个新列,用 0 或 1 表示。排名第一的行 rank 1那一列的值是 1 ,其它是 0;排名第二的行 rank 2 那一列的值是 1 ,其它是 0,以此类推。
我们还需要把 GRE 和 GPA 分数标准化,也就是说使得他们的平均值是 0,标准差是 1。因为 sigmoid 函数会挤压很大或者很小的输入,所以这一步是必要的。很大或者很小输入的梯度是 0 意味着梯度下降的步长也会是 0。因为 GRE 和 GPA 的值都相对较大,我们在初始化权重的时候要非常小心,否则梯度下降就会死亡,网络也没发训练了。如果我们对数据做了标准化处理,我们能更容易地对权重进行初始化。
这只是一个简单介绍,你之后还会学到如何预处理数据,如果你想了解我是怎么做的,可以查看下面编程练习中的 data_prep.py文件。

现在数据已经准备好了,我们看到有六个输入特征:gre, gpa,和四个rank的虚拟变量 (dummy variables)。

梯度下降法训练过程

这是用梯度下降来更新权重的算法概述:

  1. 权重更新初始为 0:
  1. 训练数据中的每一条记录:
  • 通过网络做正向传播,计算输出
  • 计算输出的 error term,
  • 更新权重步长
  1. 更新权重

η 是学习率, m 是数据点个数。 这里我们对权重步长做了平均,为的是降低训练数据中大的变化。

  1. 重复 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)

最后我们更新 Δw​i​​和 w​i​​。

编程练习

接下来,你要实现一个梯度下降,用录取数据(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']


多层感知器

实现隐藏层

之前我们研究的是有一个输出节点网络,代码也很直观。但是现在我们有不同的输入,多个隐藏层,他们的权重需要有两个索引 w​ij​​,i 表示输入单位,j 表示隐藏单位。
例如在下面这个网络图中,输入被标注为 x​1​​,x​2​​, x​3​​,隐藏层节点是 h​1​​ 和 h​2​​。


指向 h​1​​ 的权重被标成了红色,这样更好区分。
为了定位权重,我们把输入节点的索引 ​i​​ 和输出节点的索引 ​j​​ 结合,得到:w​11​​,代表从 x​1​​ 到 h​1​​ 的权重。
w​12​​:代表从 x​1​​ 到 h​2​​ 的权重。
下图包括了从输入层到隐藏层用 w​ij​​ 来标注的所有权重:


之前我们可以把权重写成序列,标记为 w​i​​。
现在,权重被储存在矩阵中,由 w​ij​​ 来标记。每一表示从输入层发出的权重,每一表示从输入到隐藏层的权重。这里我们由三个输入,两个因此节点,权重矩阵标示为:

三个输入两个隐藏
三个输入两个隐藏

记得比较一下上面的示意图来确定你了解不同的权重在矩阵中神经网络中的对应关系。
用 NumPy 来初始化权重,我们需要提供矩阵的维度,如果特征是包含输入的二维序列:

# Number of records and input units
# 数据点以及每个数据点有多少输入的个数
n_records, n_inputs = features.shape
# Number of hidden units
# 隐藏层个数
n_hidden = 2
weights_input_to_hidden = np.random.normal(0, n_inputs**-0.5, size=(n_inputs, n_hidden))

这样创建了一个 名为 weights_input_to_hidden 的 2D 序列,维度是 n_inputs乘 n_hidden。记住,输入到隐藏层是所有输入乘以隐藏层权重的和。所以对每一个隐藏层节点 h​j​​,我们需要计算:


为了实现这点,我们需要运用矩阵乘法,如果你对线性代数有点忘了,我们建议你看下之前先修部分的资料。这里你只需要了解矩阵如何相乘。
在这里,我们把输入(一个行向量)与权重相乘。要实现这个,要把输入点乘(内积)以权重矩阵的每一列。例如要计算到第一个隐藏节点的输入 j=1,你需要把这个输入与权重矩阵的第一列做点乘:

用输入与权重矩阵的第一列相乘得出到隐藏层第一个节点的输入
用输入与权重矩阵的第一列相乘得出到隐藏层第一个节点的输入

用输入与权重矩阵的第一列相乘得出到隐藏层第一个节点的输入

针对第二个隐藏节点的输入,你需要计算输入与第二列的点积,以此类推。
在 NumPy 中,你可以直接使用 np.dot

hidden_inputs = np.dot(inputs, weights_input_to_hidden)

你可以定义你的权重矩阵是 n_hidden乘 n_inputs,然后把输入作为竖向量相乘:

注意:
这里权重的索引在上图中做了改变,与之前图片并不匹配。这是因为,在矩阵标注时行索引永远在列索引之前,所以用之前的方法做标识会引起误导。你只需要了解这跟之前的权重矩阵是一样的,只是做了转换,之前的第一列现在是第一行,之前的第二列现在是第二行。如果用之前的标记,权重矩阵是下面这个样子的:

用之前的标记标注的权重矩阵
用之前的标记标注的权重矩阵

切记,上面标注方式是不正确的,这里只是为了让你更清楚这个矩阵如何跟之前神经网络的权重匹配。

矩阵相乘最重要的是他们的维度相匹配。因为它们在点乘时需要有相同数量的元素。在第一个例子中,输入向量有三列,权重矩阵有三行;第二个例子中,权重矩阵有三列,输入向量有三行。如果维度不匹配,你会得到:

# 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
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 204,921评论 6 478
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 87,635评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 151,393评论 0 338
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,836评论 1 277
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,833评论 5 368
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,685评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,043评论 3 399
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,694评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 42,671评论 1 300
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,670评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,779评论 1 332
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,424评论 4 321
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,027评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,984评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,214评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,108评论 2 351
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,517评论 2 343

推荐阅读更多精彩内容