神经网络

1. 线性逻辑回归

概念:逻辑回归

线性逻辑回归可以帮我们进行预测,比如判断一个学生是否会被录取。基于之前的统计数据

我们可以建立数学模型(一条直线),描述这一过程。

如果直线方程为 :y = 2x1 + x2 - 18 当学生的成绩在直线上方时,即:y>0 时学生被录取的概率很大 当学生的成绩在直线下方时,即:y<0 时学生被拒绝的概率很大

这里我们转换为一般的形式

y = w1x1+w2x2 + b

其中

W=(w1,w2) 表示权重
X=(x1,x2)  表示数据
b 表示偏差
y-hat =  0 if y <0 else 1

2. 感知元(Perceptrons)

感知元是组成神经网络的单元,它关注的是输入的数据,在内部对数据进行归类,最终得到一个输出结果。不同感知元输出的不同的结果,最终组成了决策。当然,感知元并不知道输入数据的权重,它需要一个学习的过程,根据输入不断调整不同数据的权重

权重

数据在输入的时候,必须乘以一个被分配给它的权重,权重在最开始的时候可以是一个随机值,基于权重,对数据进行分类,基于数据分类的结果,对权重进行调节。这个过程称之为训练

输入数据求和

每个感知元决定其自身输入数据的权重并输出数据,将所有数据进行求和,得到最终的决策结果。 将 x_test 乘以其权重 w_test 并且与 x_grades 乘以其权重 w_grades 的结果进行求和。

激活函数

通过将输出的结果传入到激活函数中,我们可以得到一个信息。单位阶跃函数(Heaviside step function)是一个典型的激活函数。 当输入的h小于0时,激活函数返回0,否则返回1

二维的情况可能是这样:

在阴影部分的数据会返回小于 0 的结果。 如果我们希望,返回小于0的结果更多一些的话, 我们可以移动这条直线,方法就是加上一个偏差(bias)

不论是权重还是偏差,开始时都是随机的,后面会通过学习来不断调整。

3. 感知元的逻辑运算

And 运算

权重和偏差必须能够使得两个感知元的运算结果符合与操作。

Or 运算

Not 运算

异或

如果要构造一个异或的感知元,我们必须要创建一个多层网络

4. 感知元的参数调整

当我们了解了分类结果之后,可能是有一些错误的分类结果,我们需要调整权重,以使得我们的模型能够正确拟合数据。

我们将原来的权重值减去错误点的坐标:

(3-4)x1 + (4-5)x2 - 10 = 0,当x1和x2一定时,显然此时的等式时可以将其归类到正确的结果的,也就是说3x1 + 4x2 - 10 = 0时,x1,x2期望被归类为小于0,那么(3-4)x1 + (4-5)x2 - 10 = 0可以满足。但是一般我们不会这样明显的移动我们的模型, 因此引入学习速率参数,例如0.1,每次只移动一小段距离,即: (3-4*0.1)x1 + (4-5*0.1)x2 - 10 = 0 这样以来,原本一次移动可以使得错误的结果调整到正确,现在就必须进行10次调整,这样可以避免误差带来的影响,只要足够次数的错误分类,才会影响到最终结果。

5. 感知元算法

  1. 使用随机的权重W和偏差b初始化
  2. 对于所有被错误分类的点,x1...xn
  3. 如果分类结果为0
    • 对于i = 1...n
      • wi = wi + alpha * xi (alpha是学习速率)
    • b = b + alpha
  4. 如果分类结果为1
    • 对于i = 1...n
      • wi = wi - alpha * xi (alpha是学习速率)
    • b = b - alpha
import numpy as np
# Setting the random seed, feel free to change it and see different solutions.
np.random.seed(23)

def stepFunction(t):
    if t >= 0:
        return 1
    return 0

def prediction(X, W, b):
    return stepFunction((np.matmul(X,W)+b)[0])

# TODO: Fill in the code below to implement the perceptron trick.
# The function should receive as inputs the data X, the labels y,
# the weights W (as an array), and the bias b,
# update the weights and bias W, b, according to the perceptron algorithm,
# and return W and b.
def perceptronStep(X, y, W, b, learn_rate = 0.1):
    for i in range(len(X)):
        y_hat = prediction(X[i],W,b)
        if y[i]-y_hat == 1:
            W[0] += X[i][0]*learn_rate
            W[1] += X[i][1]*learn_rate
            b += learn_rate
        elif y[i]-y_hat == -1:
            W[0] -= X[i][0]*learn_rate
            W[1] -= X[i][1]*learn_rate
            b -= learn_rate
    return W, b
    
# This function runs the perceptron algorithm repeatedly on the dataset,
# and returns a few of the boundary lines obtained in the iterations,
# for plotting purposes.
# Feel free to play with the learning rate and the num_epochs,
# and see your results plotted below.
def trainPerceptronAlgorithm(X, y, learn_rate = 0.1, num_epochs = 25):
    x_min, x_max = min(X.T[0]), max(X.T[0])
    y_min, y_max = min(X.T[1]), max(X.T[1])
    W = np.array(np.random.rand(2,1))
    b = np.random.rand(1)[0] + x_max
    # These are the solution lines that get plotted below.
    boundary_lines = []
    for i in range(num_epochs):
        # In each epoch, we apply the perceptron step.
        W, b = perceptronStep(X, y, W, b, learn_rate)
        boundary_lines.append((-W[0]/W[1], -b/W[1]))
    return boundary_lines

6. 误差函数和损失误差函数(loss function)

误差函数让我们了解如何改进我们的结果,是我们不断尝试缩小的一个目标值,例如我们要到达山底,则山的高度是我们要克服的误差。我们希望不断减少这个误差以达到目标。

使用梯度下降法来缩小误差,有两个前提条件

  • 误差函数的可以微分的
  • 误差函数是连续的

在之前的问题中,我们期望调节权重来对结果进行正确的分类

但是可能的结果是,不论我们怎么调节,正确分类的点数可能不变,这样算法就无法进行下去了,这是因为我们的误差函数是正确分类点的个数,这是一个离散的函数,其结果只能是1,2,3...这样的整数。 为了使用梯度下降的方法,我们必须构造出连续的误差函数

我们为每个点分配一个惩罚值(不论正确与否),正确分类的点有较小的惩罚值,反之较大。当直线的移动趋向于将某个点正确分类,则惩罚值减小。将所有的点的惩罚值相加,我们就得到了一个连续的误差函数。

我们只要利用梯度下降法,即总是尝试让误差函数减小,那么最终就可以解决问题。

7. 连续的激活函数

当预测函数为离散时,结果是离散的,例如yes或no。但是连续的预测函数告诉我们的是得到yes或no的概率(注意仍然是两种结果)

Sigmoid function

4x1 + 5x2 - 9 = score计算出结果再应用S函数 sigmoid(x) = 1/(1+e^-x )可以得到其概率。

例如要使结果概率为50%,则score要等于0

8.Softmax函数

The Softmax Function

9. One-Hot Encoding

10. 最大似然度

The basics of Likelihood 假定我们有两个模型,如何评价其好坏呢,我们认为,数据能够尽可能的符合模型,则模型更好。这个也是似然度的含义。

如上的两个模型,我们可以求得,数据点如图分布时的概率。显然左侧的模型概率远远低于右侧的模型,因此右侧的模型更好。而我们在设计模型时,就是期望这个总体概率能够尽可能高。

因此,我们可以从左侧模型开始,并不断调整模型使其概率提高到30%

根据之前的方法,我们总是试图减小误差函数,那么这里就提供了减小误差函数的一个思路——最大化模型的概率。这样看来,每个点的概率可以看作是给它的惩罚值,但是之前的误差函数是一个求和的形式,求乘积太敏感。

11. 交叉熵

通过log函数我们把乘积转换成了求和,此外,我们可以得到每个点的log值,也就是其熵值,这就是之前所说的惩罚值,越正确归类的,其熵值越小,而我们的总体目标就是不断减小模型总体熵值,这个log函数就是误差函数,也叫做交叉熵。

当有一系列的事件时,如我们取每个事件发生的最大概率,求其交叉熵,一定是最小的,因为这种情况是最有可能发生的,如下:

第二行有着最大的概率和最小的交叉熵

#calculate the cross-entropy of Y and P
def cross_entropy(Y, P):
    Y = np.float_(Y)
    P = np.float_(P)
    return -np.sum(Y * np.log(P) + (1 - Y) * np.log(1 - P))

Multi-Class Cross Entropy

y:indicate variable(指示变量),表示该概率值是否需要相加 例如:概率pij表示的是出现的概率,y=1表示出现,y=0表示不出现

12. Logistic Regression(逻辑回归)

  • Take your data
  • Pick a random model
  • Calculate the error
  • Minimize the error, and obtain a better model

以下是二维和多维形式的误差函数 其中y-hat = sigmoid(Wx + b) 而我们的目标是不断减小误差函数的值(利用梯度下降法,找到能够使的函数值变小的权重W和b)

13. 梯度下降法

####梯度公式推导

误差函数可知,梯度是某个点坐标的一个标量倍数,这个倍数就是标记值和预测值之间的差值的相反数

标记越接近于预测,梯度越小,反之越大

梯度下降法更新权重和偏差公式

注意这里的alpha实际上还要乘以一个学习速率1/m

代码实现

1. 误差函数 f为激活函数sigmoid 2. 权重

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 neural network output (y-hat)
nn_output = sigmoid(x[0]*weights[0] + x[1]*weights[1])
# or nn_output = sigmoid(np.dot(x, weights))

# output error (y - y-hat)
error = y - nn_output

# error term (lowercase delta)
error_term = error * sigmoid_prime(np.dot(x,weights))

# Gradient descent step 
del_w = [ learnrate * error_term * x[0],
                 learnrate * error_term * x[1]]
# or del_w = learnrate * error_term * x

14. 非线性模型

非线性模型可以用多个线性模型进行叠加组成

###15. 神经网络结构

使用多个线性的模型叠加称为一个非线性模型,实际上是对神经网络的一个扩展,左图可以简化成如下形式:

我们一般会把bia和激活函数单列出来:

多层结构

一般神经网络分为:

  • 输入层
  • 隐藏层
  • 输出层

输入层可以有多个输入,表明输入的维度更高,那么隐藏层中的模型维度也就更高,如果输出层有多个节点,则表明可以进行多类别分类。

如果增加层数,就变成了深度神经网络

多类别分类

如果输出层有多个节点的话,我们可以计算出每个节点的分数,然后通过Sigmoid函数,我们就可以计算出每种分类的概率。

15. Feedforward

神经网络由输入得到输出的过程就是一次feedforward

误差函数

我们目标是训练神经网络以确定W和b的值,这样就需要我们定义误差函数

16. 多层感知元及其计算

之前我们接触到的神经网络,都只有一个输出,很简单。当引入多个输出节点的时候,就需要用到矩阵相关用到矩阵相关知识了 当输入为xi,输出为hj时,权重表示为wij,权重矩阵W表示为上图中的矩阵,其中:

  • 矩阵行向量为输出的节点的个数
  • 矩阵列向量的维数为输入的节点的个数,因为输入节点上一个相同维数的行向量

因此,当输入为行向量(x1,x2,x3)时,左乘矩阵W即可得到输出(h1,h2),即:h = xW

我们可以使用矩阵乘法求得:h1 = x1w11+x2w21+x3w31

hidden_inputs = np.dot(inputs, weights_input_to_hidden)

同样的,我们也通过如下的矩阵W,右乘进行计算:

但此时wij的含义和之前相比发生了变化: 此时i表示hi,j表示xj,也即是之前的(w11,w21,w31)变成了现在的(w11,w12,w13)

# 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))

创建列向量的方法

通常,numpy创建一个行向量比较方便,但是有时候我们也需要一个列向量

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]])
       
# 数组也可以是二维的
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 激活函数,实现如下目标:

  • Calculate the input to the hidden layer.
  • Calculate the hidden layer output.
  • Calculate the input to the output layer.
  • Calculate the output of the network.

16. Backpropagation(反向传播)

反向传播算法包含以下步骤:

  • 进行一次feedforward操作
  • 将模型的输出结果和期望输出结果进行比较
  • 计算误差
  • 逆向执行一次feedforward操作(反向传播),将误差传递到权重部分
  • 修正权重得到更好的模型
  • 重复上述步骤直到模型完善

一维的情形

基于模型的误差函数,求其梯度(误差函数对所有权重分别求偏导数),调整权重(需要乘以一个学习速率alpha)

多维情形

误差函数是一样的,只不过其中的y-hat,也就是预测函数变复杂了。误差函数的梯度也从向量变成了矩阵

矩阵的上标表示神经网络的层数 注意W(1) W(2)的元素个数和向量维数,列向量的维数是输入节点的个数,行向量的维数是其输出节点的个数,元素个数是边的个数。

此处:第一层输入层,3个输入节点,得到两个输出,W(1)为3x2矩阵,随后,通过一个 3x1的矩阵W(2),将三个隐藏层的节点,输出为一个最后的输出节点。

并使用该矩阵来更新权重(需要乘以一个学习速率alpha)

导数计算之链式法则

复合函数的导数,就是函数导数的乘积。如图,A=f(x),B = gf(x),那么对B求x的偏导数,实际上相当于B对A求偏导数,乘以 A 对x求偏导数。 因为前向反馈就是多个函数的符合函数,那么做反向传播时,就是算出每一步的导数,然后相乘。

上图是前向反馈求复合函数的过程,下图是反向传播求解各部分函数导数并最终相乘得到对第一层矩阵求导数的结果

我们来看神经网络的具体的一层

它的符合函数就是矩阵乘以h1带入激活函数sigmoid,那么对h1求偏导数,后面两项都是0,第一项,因为W11(2)是一个常量,那么对h1求偏导数,其结果就是W11(2) 乘以 sigmoid(h1)的导数: sigmoid(h1)(1- sigmoid(h1)),即最终结果如上图所示