卷积神经网络的Python实现
上QQ阅读APP看书,第一时间看更新

第2章 线性分类器

2.1 线性模型

第1章介绍了机器学习模型,它的本质上是得到由属性x到标签y的映射f:y=f(x, w)(其中w是模型参数),通过最优化理论获得最优值。多分类任务中,输出 y一般是向量,称为输出向量。输出向量的维数等于类别数,其元素值是实数,表示对应类别的分值,分值越大,该样本与对应的类别就越相似,故最大元素值对应的类别就是最终预测的类别。当映射y=f(x, w)采用最简单的线性函数时,就得到线性分类器。线性函数虽然简单,却是理解神经网络和卷积神经网络的基础,希望读者重视。

假设训练集xi∈RD,每个样本对应的类别标签为yi, i=1,2, …, N, yi∈1, …, K,这表示训练集有N个样本,每个样本的维度是D(即有D个属性),共有K个类别。

例如,在MNIST中,有N=60000个训练样本,每个图像有D=28×28×1=784个维度,K=10,这是因为有10个数字。图像的每个像素是一个特征属性。线性模型和神经网络模型中,图像矩阵需要拉伸为一个向量,卷积网络不需要。

2.1.1 线性分类器

属性xi和参数w的线性函数为:

其中输出s是分值,Dwi组成参数w,也称权重向量,b是偏置参数。由于 s是标量,只能表示某个类别的分值,不能表示所有类别的分值。为了得到K个分值,则需K个线性函数:

i个方程表示第i类的分值函数,也称为第i个分类器。为了简化书写,可以写成矩阵形式。(刚开始可能不习惯矩阵形式,但一定要逐渐习惯。)分两步改写比较容易理解,第一步:

注意此时各个wi是列向量,xi是行向量,xiwi是向量内积。第二步:

注意此时 s是分值行向量,b是偏置行向量,W是权重矩阵,每列由wi个列向量构成。

在MNIST中,各向量或矩阵的维度为:s是[1×K]=[1×10], b是[1× K]=[1×10], xi是[1×D]=[1×784], W是[D×K]=[784×10]。

如果把上面矩阵形式的线性模型中的每个向量或矩阵当作标量,则线性模型就变成一元一次方程,变成最简单的线性模型了。

对于线性模型,需要强调如下几点。

第一,进行多分类,只需一个矩阵乘法xiW和矩阵加法,每个分类器就是W的一个列向量。

第二,训练集(xi, yi)是给定且不可改变的,可变的是权重W和偏置向量b。机器学习的目的就是通过优化算法得到这些参数的最优值,使得模型计算出来的分值向量和训练集中样本的真实类别标签相符。何谓相符,详见2.2节。

第三,模型训练的结果是得到最优参数Wb。一旦训练完成,就不再需要训练集。因为预测样本类别时,只需计算分值向量,不需要训练集。

2.1.2 理解线性分类器

线性分类器计算图像的所有像素值与权重的内积,从而得到该分类器的类别分值。根据权重符号,分类器对图像中的像素表现出喜爱(符号为正)或者厌恶(符号为负)。权重符号为正,乘以像素值(为正),能提高分值;权重符号为负时,则减小分值。如果图像属于某类,则分值要大;不属于某类,则分值要小。所以,可以想象“飞机”图像被大量的蓝色(对应“天空”)所包围,那么“飞机”分类器在蓝色通道上有很多的正权重(提高“飞机”的分值),而在绿色和红色通道上的权重为负的就比较多(降低“非飞机”的分值)。

将图像看作高维空间的矢量:我们把图像拉伸成为高维空间的一个列向量,就可以将其看作这个空间中的一个矢量(矢量的起始点是坐标轴原点,MNIST中的每张图像是784维空间中的一个矢量)。整个数据集就是矢量集,每个矢量都带有一个类别标签。每个样本的分值是矢量xi和权重列向量wi的内积,wi可以看作高维空间中的矢量,内积可以看作矢量xi向矢量wi的投影长度。如果把内积看作物理中两个矢量的点积,高维空间压缩为二维空间,则能可视化分类器。举个例子,把wi看作二维平面的力,xi看作速度,则内积xiTwi就是功率。分类器就是寻找最优力矢量,使速度xi与对应类别的力wi的功率最大。

线性分类器看作模板匹配:wi对应一个类别模板,分类就是找到xi和哪个模板最相似。从这个角度来看,线性分类器就是利用学习得到的模板,做模板匹配,使用负内积来计算图像与模板的距离,距离越小,说明越相似。

线性分类器只能对线性可分的样本进行有效分类,线性可分指可以用一个线性函数把多类样本分开,比如二维空间中的直线、三维空间中的平面以及高维空间中的超平面就是线性函数。线性不可分指有部分样本用线性分类器划分时会产生分类错误。对于线性不可分样本集,可以采用神经网络来分类,神经网络就是线性分类器的升级版本。

2.1.3 代码实现

NumPy是Python开源数值计算库,存储和处理大型矩阵十分方便,比Python自身的列表(list)结构要高效很多。本书的所有程序都基于NumPy实现。

为了高效地实现线性模型,可以同时计算N个样本的分值向量,公式如下:

合成一个大矩阵:

每个样本的分值向量组成分值矩阵S的一行,每个样本属性向量组成样本属性矩阵X的一行。矩阵X也称为数据矩阵,偏置矩阵B的每行都是偏置向量b。代码如下:

①   import numpy as np

    D = 784  # 数据维度
    K = 10  # 类别数
N = 128  # 样本数量

X = np.random.randn(N, D) # 数据矩阵,每行一个样本
W = 0.01 * np.random.randn(D, K)
b = np.zeros((1, K))
②   scores = np.dot(X, W) + b # 广播机制

为了方便读者运行程序,代码会生成随机数来作为样本数据。语句① 引入numpy模块,以后每个程序引入NumPy都需要采用这种写法。

细心的读者可能发现,语句②中偏置b是行向量,不是矩阵,严格按照矩阵运算法则,语句②是错误的,因为其维度不相同。但由于偏置矩阵B每行都是偏置向量b,如果非要用B,就会很麻烦,需要把b扩展成矩阵,但实质内容只有b。因此,聪明的NumPy能预测到程序的意图,自动完成矩阵扩展工作,这里运用到的内部机制是广播。NumPy广播机制对于编写高效简洁的程序十分重要,希望读者重视。NumPy的通用函数中要求输入的数组形状(shape)是一致的。当数组的shape不相等时,则会使用广播机制,扩展数组使得shape一致,满足广播规则。