梯度下降

  • 权重:一个较大的权重意味着神经网络认为这个输入比其它输入更重要,较小的权重意味着数据不是那么重要。
  • 偏置:更多可能性返回某一个结果(或者说更倾向于某种结果)
  • 激活函数:将求和的结果转换成某种输出信号
  • 误差 (error):一个普遍的指标是误差平方和 sum of the squared errors (SSE)

SSE的公式如下: image SSE的公式中,使用平方而不使用绝对值的原因是:平方后误差大的得到的惩罚也越大,平方后误差小的得到的惩罚也越小;平方也可以简化后续的计算。1/2 也是为了简化后续步骤计算。

下图是简化的图形:

image

(我们的目的就是得到最小值时的权重值)

从图中我们知道:

  • 误差只与权重有关,通过一步步改变权重,可以得到误差的最小值。
  • 权重的更新与梯度的方向恰好相反。
  • 学习速率用来控制梯度下降中更新步长的大小。

image

梯度下降的代码实现

#现在假设只有一个输出单元,来把这个写成代码。我们还是用 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

反向传播

image

image

image

image

从这个例子中你可以看到 sigmoid 做激活函数的一个缺点。sigmoid 函数导数的最大值是 0.25,因此输出层的误差被减少了至少 75%(1-0.25),隐藏层的误差被减少了至少 93.75%(1 - 0.25 * 0.25)!如果你的神经网络有很多层,使用 sigmoid 激活函数会很快把靠近输入层的权重步长降为很小的值,该问题称作梯度消失。


反向传播实例 1
import numpy as np


def sigmoid(x):
    """
    Calculate sigmoid
    """
    return 1 / (1 + np.exp(-x))
    
def sigmoid_prim(x):
    """
    Calculate sigmoid
    """
    return sigmoid(x) * (1 - sigmoid(x))

x = np.array([0.5, 0.1, -0.2])
target = 0.6
learnrate = 0.5

weights_input_hidden = np.array([[0.5, -0.6],
                                 [0.1, -0.2],
                                 [0.1, 0.7]])

weights_hidden_output = np.array([0.1, -0.3])

## Forward pass
hidden_layer_input = np.dot(x, weights_input_hidden)
hidden_layer_output = sigmoid(hidden_layer_input)

output_layer_in = np.dot(hidden_layer_output, weights_hidden_output)

output = sigmoid(output_layer_in)

## Backwards pass
## TODO: Calculate output error
error = target - output

# TODO: Calculate error term for output layer
output_error_term = error * sigmoid_prim(output_layer_in)
print(output_error_term)
print(weights_hidden_output)
# TODO: Calculate error term for hidden layer
hidden_error = np.dot(output_error_term, weights_hidden_output)
hidden_error_term = hidden_error  * sigmoid_prim(hidden_layer_input)
print("hidden_error_term:" ,hidden_error_term)
print("                  ",x[:,None])
# TODO: Calculate change in weights for hidden layer to output layer
delta_w_h_o = learnrate * output_error_term * hidden_layer_output
print("delta_w_h_o:",delta_w_h_o)
# TODO: Calculate change in weights for input layer to hidden layer
delta_w_i_h = learnrate * x[:,None] * hidden_error_term  

print('Change in weights for hidden layer to output layer:')
print(delta_w_h_o)
print('Change in weights for input layer to hidden layer:')
print(delta_w_i_h)


反向传播实例 2
import numpy as np
from data_prep import features, targets, features_test, targets_test #data import

np.random.seed(21)

def sigmoid(x):
    """
    Calculate sigmoid
    """
    return 1 / (1 + np.exp(-x))
def sigmoid_prime(x):
    """
    Calculate sigmoid
    """
    return sigmoid(x) * (1 - sigmoid(x))

# Hyperparameters
n_hidden = 2  # number of hidden units
epochs = 900
learnrate = 0.005

n_records, n_features = features.shape
last_loss = None
# Initialize weights
weights_input_hidden = np.random.normal(scale=1 / n_features ** .5,
                                        size=(n_features, n_hidden))
weights_hidden_output = np.random.normal(scale=1 / n_features ** .5,
                                         size=n_hidden)

for e in range(epochs):
    del_w_input_hidden = np.zeros(weights_input_hidden.shape)
    del_w_hidden_output = np.zeros(weights_hidden_output.shape)
    for x, y in zip(features.values, targets):
        ## Forward pass ##
        # TODO: Calculate the output
        hidden_input = np.dot(x,weights_input_hidden)
        hidden_output = sigmoid(hidden_input)
        o_input = np.dot(hidden_output,weights_hidden_output)
        output = sigmoid(o_input)

        ## Backward pass ##
        # TODO: Calculate the network's prediction error
        error = y - output

        # TODO: Calculate error term for the output unit
        output_error_term = error * sigmoid_prime(o_input)

        ## propagate errors to hidden layer

        # TODO: Calculate the hidden layer's contribution to the error
        hidden_error = np.dot(output_error_term , weights_hidden_output)
        
        # TODO: Calculate the error term for the hidden layer
        hidden_error_term = hidden_error * sigmoid_prime(hidden_input)
        
        # TODO: Update the change in weights
        del_w_hidden_output += output_error_term * hidden_output
        del_w_input_hidden += hidden_error_term * x[:,None] 

    # TODO: Update weights
    weights_input_hidden += learnrate * del_w_input_hidden / n_records
    weights_hidden_output += learnrate * del_w_hidden_output / n_records

    # Printing out the mean square error on the training set
    if e % (epochs / 10) == 0:
        hidden_output = sigmoid(np.dot(x, weights_input_hidden))
        out = sigmoid(np.dot(hidden_output,
                             weights_hidden_output))
        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
hidden = sigmoid(np.dot(features_test, weights_input_hidden))
out = sigmoid(np.dot(hidden, weights_hidden_output))
predictions = out > 0.5
accuracy = np.mean(predictions == targets_test)
print("Prediction accuracy: {:.3f}".format(accuracy))



矩阵数学和NumPy

Python 很方便,但也会很慢。不过,它允许你访问执行用 C 等语言编写的代码的库。NumPy 就是这样一个库:它为 Python 中的数学运算提供了一个更快速的替代方案,可以与数字组高效搭配使用 - 如矩阵。

数据类型

NumPy可以存储任意维度的数据类型:标量(0维)、向量(1维)、矩阵(2维)、张量(多维)。

>>> import numpy as np # 导入numpy命名为np
>>> s = np.array(3)    #创建零维标量
>>> s
array(3)
>>> s.shape
()   # 0维
>>> s = np.array([1, 2, 3]) # 创建一维向量
>>> s
array([1, 2, 3])
>>> s.shape
(3,) # 1维
>>> s = np.array([[1,1], [2, 2]]) # 创建二维矩阵
>>> s
array([[1, 1],
       [2, 2]])
>>> s.shape
(2, 2) # 2 维
>>> s = np.array([[[1,1], [2,2]],[[3,3], [4,4]] ]) #创建三维张量
>>> s
array([[[1, 1],
        [2, 2]],

       [[3, 3],
        [4, 4]]])
>>> s.shape
(2, 2, 2) # 3 维
>>>

更改形状

有时,你需要更改数据的形状,而无需实际更改其内容。例如,你可能有一个一维的向量,但是需要一个二维的矩阵。

>>> v = np.array([1, 2, 3]) # 创建一维向量
>>> v.shape
(3,) # 1维
>>> v
array([1, 2, 3])
>>> x = v.reshape(1, 3) #方式1: 更改形状为 1 x 3 的二维矩阵
>>> x.shape
(1, 3)# 2维
>>> x
array([[1, 2, 3]])
>>> x = v[None, :]  #方式2: 更改形状为 1 x 3 的二维矩阵
>>> x
array([[1, 2, 3]])
>>> x.shape
(1, 3)# 2维
>>> x = v.reshape(3,1)  #方式1: 更改形状为 3 x 1 的二维矩阵
>>> x.shape
(3, 1)# 2 维
>>> x
array([[1],
       [2],
       [3]])
>>> x = v[:, None]  #方式2: 更改形状为 3 x 1 的二维矩阵
>>> x.shape
(3, 1)# 2 维
>>> x
array([[1],
       [2],
       [3]])
>>> 

x = v[None, :]或者x = v[:, None] 将会创建一个切片,查看 v 的所有项目,要求 NumPy 为相关轴添加大小为 1 的新维度(在None的地方增加一个维度)。

>>> n = np.array([[1,2],[3,4]]) #创建一个二维矩阵
>>> n.shape
(2, 2)# 2 维
>>> n
array([[1, 2],
       [3, 4]])
>>> z = n[:, :, None]  #增加一维变为三维
>>> z
array([[[1],
        [2]],

       [[3],
        [4]]])
>>> z.shape
(2, 2, 1) # 3维
>>> z = n[None, :, :] #增加一维变为三维
>>> z
array([[[1, 2],
        [3, 4]]])
>>> z.shape
(1, 2, 2) # 3维
>>> z = n[:, None, :] #增加一维变为三维
>>> z
array([[[1, 2]],

       [[3, 4]]])
>>> z.shape
(2, 1, 2) # 3维
>>> 

NumPy 矩阵乘法
元素级乘法

你可以使用 multiply 函数或 * 运算符来实现。回顾一下,它看起来是这样的:

m = np.array([[1,2,3],[4,5,6]])
m
# 显示以下结果:
# array([[1, 2, 3],
#        [4, 5, 6]])

n = m * 0.25
n
# 显示以下结果:
# array([[ 0.25,  0.5 ,  0.75],
#        [ 1.  ,  1.25,  1.5 ]])

m * n
# 显示以下结果:
# array([[ 0.25,  1.  ,  2.25],
#        [ 4.  ,  6.25,  9.  ]])

np.multiply(m, n)   # 相当于 m * n
# 显示以下结果:
# array([[ 0.25,  1.  ,  2.25],
#        [ 4.  ,  6.25,  9.  ]])
矩阵乘积

要获得矩阵乘积,你可以使用 NumPy 的 matmul 函数。

如果你有兼容的形状,那就像这样简单:

a = np.array([[1,2,3,4],[5,6,7,8]])
a
# 显示以下结果:
# array([[1, 2, 3, 4],
#        [5, 6, 7, 8]])
a.shape
# 显示以下结果:
# (2, 4)

b = np.array([[1,2,3],[4,5,6],[7,8,9],[10,11,12]])
b
# 显示以下结果:
# array([[ 1,  2,  3],
#        [ 4,  5,  6],
#        [ 7,  8,  9],
#        [10, 11, 12]])
b.shape
# 显示以下结果:
# (4, 3)

c = np.matmul(a, b)
c
# 显示以下结果:
# array([[ 70,  80,  90],
#        [158, 184, 210]])
c.shape
# 显示以下结果:
# (2, 3)

如果你的矩阵具有不兼容的形状,则会出现以下错误:

np.matmul(b, a)
# 显示以下错误:
# ValueError: shapes (4,3) and (2,4) not aligned: 3 (dim 1) != 2 (dim 0)
NumPy 的 dot 函数

有时候,在你以为要用 matmul 函数的地方,你可能会看到 NumPy 的 dot 函数。事实证明,如果矩阵是二维的,那么 dot 和 matmul 函数的结果是相同的。

所以这两个结果是等价的:

a = np.array([[1,2],[3,4]])
a
# 显示以下结果:
# array([[1, 2],
#        [3, 4]])

np.dot(a,a)
# 显示以下结果:
# array([[ 7, 10],
#        [15, 22]])

a.dot(a)  # you can call你可以直接对 `ndarray` 调用 `dot` 
# 显示以下结果:
# array([[ 7, 10],
#        [15, 22]])

np.matmul(a,a)
# array([[ 7, 10],
#        [15, 22]])

虽然这两个函数对于二维数据返回相同的结果,但在用于其他数据形状时,你应该谨慎选择。

转置

在 NumPy 中获得矩阵的转置非常容易。只需访问其 T 属性即可。

NumPy 在进行转置时不会实际移动内存中的任何数据 - 只是改变对原始矩阵的索引方式 - 所以是非常高效的。

但是,这也意味着你要特别注意修改对象的方式,因为它们共享相同的数据。例如,对于上面同一个矩阵 m,我们来创建一个新的变量 m_t 来存储 m 的转置。然后看看如果我们修改 m_t 中的值,会发生什么:

m_t = m.T
m_t[3][1] = 200
m_t
# 显示以下结果:
# array([[ 1,   5, 9],
#        [ 2,   6, 10],
#        [ 3,   7, 11],
#        [ 4, 200, 12]])

m
# 显示以下结果:
# array([[ 1,  2,  3,   4],
#        [ 5,  6,  7, 200],
#        [ 9, 10, 11,  12]])

注意它是如何同时修改转置和原始矩阵的!这是因为它们共享相同的数据副本。所以记住,将转置视为矩阵的不同视图,而不是完全不同的矩阵。