回顾之前学的模型,无论是线性回归还是逻辑回归都有一个缺点,即:当特征太多 时,计算的负荷会非常大。而有时候,我们又希望用高次多项式来拟合更复杂的情形,此时特征数更是成倍增长。
在计算机视觉(Computer Vision)领域,数据的输入往往是一张张由像素(pixel)构成的图片。一张 的图片中包含 个像素点,如果算上 RGB 色值则有 个特征,更别提包含平方、立方项特征的非线性假设 了。而神经网络则是适合学习复杂的非线性假设的一类算法。
神经网络 | Neural Network神经网络起源于科学家对人脑神经元的模拟,早期应用十分广泛,后来由于计算量过大而逐渐没落,直到近些年硬件的增强,大规模的神经网络才得以训练和应用。
神经元模型 | Neuron Model神经网络模型建立在许多神经元之上,每个神经元也被称为激活单元 (Activation unit),它采纳一些特征作为输入 (input),并且根据本身的模型提供一个输出 (output)。神经网络就是大量神经元相互连接形成的一个网络。
单个神经元模型
激活单元(图中黄色结点)就是一个函数 ,根据若干输入信息 以及其权重 ,得到一个输出信息 ,即: 也称作激活函数 (Activation Function),一般可以采取 函数 .
注:上图省略了 这一偏置项 (Bias unit),偏置项不仅可以是输入层 的人为特征,也可以是隐藏层 的一个常量单元 。
为什么 函数可以作为激活函数?事实上激活函数有很多种,他们的共同特点都是引入了非线性假设 。早期的神经网络使用 的原因还有:输出可映射到 Bernoulli 分布,可以作为概率解决分类问题;求导计算方便。
网络的表示 | Presentation神经网络是许多神经元按照不同层级组织起来的网络。第一层被称作输入层 (Input Layer),最后一层被称作输出层 (Output Layer),中间其他层被称作隐藏层 (Hidden Layer),意味着「不可见」。隐藏层和输出层具备激活函数功能,而输入层仅仅是特征的拷贝。
多层神经元组成的神经网络
表示输入层的第 个输入特征,其中 偏置项省略; 表示第 层的第 个激活单元,其中 偏置单元省略; 表示第 层的神经元数量(不包含偏置项 ), 表示加上偏置项 后的 ; 表示第 层到第 层的权重矩阵 , 表示所有权重矩阵的集合 。所以,由上图我们可以列出: 简写作矩阵形式,可得到前向传播 (Forward Propagation)公式:
是不是像极了逻辑回归 ,只不过特征 换成了上一层 ,假说 变成了当前层 。当然,由于每一层的关联性,最终的假说 依旧是特征 的非线性组合 。
而随着每一层的深入,特征会变得越来越「抽象 」,这些新特征远比单纯 的多项式来得强大,也能更好的预测数据。这就是神经网络相比于逻辑回归和线性回归的优势。
有的地方会把偏置项对应的偏置向量 单独拿出来,使得 ,以求形式的统一:
实现逻辑门神经网络与逻辑门有何种联系?我们知道 函数具有将数值映射到 和 的能力,而逻辑门也是类似。那么就可以把逻辑门看作一个简化的二分类 问题,用神经网络对其训练。通过这个例子能够直观地理解神经网络中参数的作用,首先来看最简单的与门 :
与门的实现
考虑一个输入层有 个特征且取值 、输出层有 个神经元且取值 的神经网络。如果我们将权重参数设置为:,那么我们有:
这样实现了一个与门的功能,同理,或门 、非门 也可以用两层网络(一个激活单元 )实现。但是异或门 和同或门 则需要三层网络——由数理逻辑知, 可以表示成前三者的组合 ,那么就可以用三个激活单元 实现异或。
同时,我们也可以发现,与前三种逻辑门不同,仅用一条直线是无法画出 决策边界的:
逻辑门的决策边界
这也意味着:需要更复杂的特征(更深层的网络)来表达更高级的模型。
多分类问题在介绍 逻辑回归 时,我们曾说对于多分类问题,可以实现多个标准的逻辑回归分类器,每个分类器的输出作为「属于某类 」的概率,取其最大值作为预测结果即可。
在神经网络中,输出层的每一个神经元 也可以视为一个逻辑回归分类器 ,对于 个类的问题( 时用一个神经元即可),最终可以得到 的预测向量 ,取其最大值作为预测结果即可。
K=4的多分类问题
同理,在训练时也需要把标签 用独热 (One-Hot)编码为向量 的形式,仅有对应类的预测值为 ,再通过反向传播从输出层开始计算。
代码实现下面以 Coursera 上的多分类数据集 ex3data1.mat
为例,这是一个手写数字的数据集。本节中忽略训练过程,使用题目提供的权重参数 ex3weight.mat
作预测。
给定的数据为 .mat
格式,是 Matlab 数据二进制存储 的标准格式,在 Matlab 交互窗中输入 save xxx
即可保存所有变量到 xxx.mat
文件中。Python 中使用 SciPy 的 loadmat
方法可以读入数据:
1 2 3 4 5 6 7 import numpy as npimport scipy.io as scio data = scio.loadmat('ex3data1.mat' )print (data)print (data['X' ].shape)print (data['y' ].shape)
PYTHON
读取的文件在 Python 中以字典 存储,将其打印出来为:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 {'__header__' : b'MATLAB 5.0 MAT-file, Platform: GLNXA64, Created on: Sun Oct 16 13:09:09 2011' , '__version__' : '1.0' , '__globals__' : [], 'X' : array([[0. , 0. , 0. , ..., 0. , 0. , 0. ], [0. , 0. , 0. , ..., 0. , 0. , 0. ], [0. , 0. , 0. , ..., 0. , 0. , 0. ], ..., [0. , 0. , 0. , ..., 0. , 0. , 0. ], [0. , 0. , 0. , ..., 0. , 0. , 0. ], [0. , 0. , 0. , ..., 0. , 0. , 0. ]]), 'y' : array([[10 ], [10 ], [10 ], ..., [ 9 ], [ 9 ], [ 9 ]], dtype=uint8)} (5000 , 400 ) (5000 , 1 )
PYTHON
文件中一共有 个样本,每个样本的输入是一个长为 的向量,由 的灰度矩阵 压缩而来;输出是一个数字,表示样本图像代表的数字。
注意:为了更好地兼容 Octave/Matlab 索引(其中没有零索引),数字 被标记为了 ,使用时可以把 换回成 ,但题目给的 ex3weight.mat
没有考虑转换;另外,数据是按列压缩的,还原回 的矩阵后其实转置了一下,这里提前转置回去方便后续编码,但题目给的 ex3weight.mat
也没有考虑。
1 2 3 4 5 6 7 data = scio.loadmat('ex3data1.mat' ) X = data['X' ] X = np.transpose(X.reshape((5000 , 20 , 20 )), [0 , 2 , 1 ]).reshape(5000 , 400 ) y = data['y' ].flatten() y[y==10 ] = 0
PYTHON
现在我们随机挑选 个图像显示出来:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 import matplotlibimport matplotlib.pyplot as pltdef plot_100_image (X ): """ sample 100 image and show them X : (5000, 400) """ sample_idx = np.random.choice(np.arange(X.shape[0 ]), 100 ) sample_images = X[sample_idx, :] fig, ax_array = plt.subplots(10 , 10 , sharey=True , sharex=True , figsize=(8 , 8 )) for r in range (10 ): for c in range (10 ): ax_array[r, c].matshow(sample_images[10 * r + c].reshape((20 , 20 )), cmap=matplotlib.cm.binary) plt.xticks(np.array([])) plt.yticks(np.array([])) plot_100_image(X) plt.show()
PYTHON
随机挑选100张图像
下面搭建神经网络,利用已知的 实现前向传播预测:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 import numpy as npimport scipy.io as sciofrom sklearn.metrics import classification_report data = scio.loadmat('ex3data1.mat' ) X = data['X' ] y = data['y' ].flatten() (m, n) = (5000 , 401 ) weight = scio.loadmat('ex3weights.mat' ) Theta1 = weight['Theta1' ] Theta2 = weight['Theta2' ] def sigmoid (z ): return 1 / (1 + np.exp(-z)) a1 = np.c_[np.ones(m), X].T a2 = sigmoid(Theta1 @ a1) a2 = np.r_[np.ones((1 , m)), a2] a3 = sigmoid(Theta2 @ a2) y_pred = np.argmax(a3, axis=0 ) + 1 print (classification_report(y, y_pred, digits=3 ))
PYTHON
调用 SciKit-Learn 库中的 metrics
,生成 精度和召回率 ,预测结果如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 precision recall f1-score support 1 0.968 0.982 0.975 500 2 0.982 0.970 0.976 500 3 0.978 0.960 0.969 500 4 0.970 0.968 0.969 500 5 0.972 0.984 0.978 500 6 0.978 0.986 0.982 500 7 0.978 0.970 0.974 500 8 0.978 0.982 0.980 500 9 0.966 0.958 0.962 500 10 0.982 0.992 0.987 500 accuracy 0.975 5000 macro avg 0.975 0.975 0.975 5000 weighted avg 0.975 0.975 0.975 5000
TEXT