我之前在论坛发过一些机器学习的资源,但是这些资源比较难直接下手,必须要先学高数和概率论。那么我今天就把复杂的机器学习算法,写成高中数学形式。
问题:我有1000张猫的照片,1000张狗的照片,请训练一个系统,令其能够分辨猫和狗。
假如每张照片的大小是32x32,那么一张照片就有1024个像素,每个像素的亮度从0到1可变。我们把这1024个像素标记为x0, x1, x2....x1023. 这样,我们就用1024个变量表示了一张输入图片。
这样就可以定义一个1024元函数:y0,y1 = f(x0,x1,x2.......x1023),其中y0是图片为猫的概率p(猫),y1是图片为狗的概率p(狗)。
我们的任务是,找到合适的函数 f(),使得计算f(猫的图片)得到 y0>y1,计算f(狗的图片)得到 y1>y0. 如果找到了这个函数f(),我们就可以计算 f(某张图片) = f(x0,x1,x2....x1023),如果得到的结果[y0,y1]中 y0>y1,比如[0.9, 0.3]或者[1.4, 0.8]或者[3.2, 1.6],就可以确定这张图片是猫。
所谓机器学习的训练过程,就是寻找函数f()的过程。
为了简化描述,我们把y0和y1称为二维矢量Y,把x0,x1,x2...x1023称为1024维矢量X。
表达式就可以简化为: Y = f(X)。
在下文中,我用大写字母表示矢量和矩阵。矢量和矩阵都是表示一组变量的简便方式(比如用矢量X来表示x0...x1023),是《线性代数》的教学内容。这是本教程涉及的唯一大学内容。
把图片中的1024个像素,转换成两个代表概率的数字,这中间的计算过程一定是非常复杂的。对于人脑和肉眼来说,猫或者狗的影像先通过晶状体汇聚到视网膜,再通过视神经传递到大脑的视神经中枢,再经过几次传递,最终得出猫或者狗的结论。这么复杂的神经传递过程,怎么用数学表达呢?人工智能领域的研究者提出,可以用神经元函数模拟神经元的行为。
下面是一个真正的人类神经元。
刺激从dendrites(树突)传入神经元,然后从axon(轴突)传出。
怎么用数学公式描述它的特性呢?下面我介绍目前机器学习领域最常用的神经元函数。
a = max(w0 * x0 + w1 * x1 + w2 * x2...+ w1023 * x1023 + b, 0)
或者简写为
a = max(W * X + b, 0)
其中a是神经元的输出,X(也就是x0...x1023)是神经元的输入,W(分别是w0...w1023)是神经元的权重,b是神经元的偏置,max是最大值函数。
我们注意到,每个神经元的输入可以有很多,但是输出连接只有一个,这和大脑中神经元的特性是类似的。
权重w的作用,是控制每个输入x对神经元的影响。如果w为负,则每个输入x对神经元的贡献就是负数。通过调节每一个w的大小,我们就可以让神经元对不同的输入x,产生不同的反应。如果某个w等于零,那么对应的输入x对后面的神经元就没有影响,相当于这个神经连接断掉了。
调节权重W(以及偏置b)的过程,称为学习或者训练。
神经元的输出有一个max函数,如果计算结果为正就直接输出,如果计算结果小于0就输出0。这是因为人类神经元只能输出正的刺激信号(脉冲),不能输出负的刺激信号。这只是一种直观的说法,实际上选择这个函数的原因非常复杂,可以去看geoff hinton的教程,里面有更加详细的数学解释。
由于f()的输出是两个实数,我们至少需要两个神经元。这两个神经元和图像的1024个输入都是相连接的。
这样我们就实现了函数f()。上图中,两个神经元的输入权重w的序号重复了,为了明确,我们把第一个神经元的偏置和权重分别称为 b0, w0_0, w0_1, w0_2... 把第二个神经元的偏置和权重分别称为b1, w1_0, w1_1, w1_2...
于是f()的计算方法如下:
y0 = a0 = max(x0 * w0_0 + x1 * w0_1 + x2 * w0_2 ...+ x1023 * w0_1023 + b0, 0)
y1 = a1 = max(x0 * w1_0 + x1 * w1_1 + x2 * w1_2 ...+ x1023 * w1_1023 + b1, 0)
如果把x0...x1023称为X,把w0_0...w0_1023称为W0,把w1_0...w1_1023称为W1,上式可以简写为:
y0 = max(X * W0 + b0, 0)
y1 = max(X * W1 + b1, 0)
如果把y0...y1称为Y,把W0和W1称为W(这时W就有1024 * 2=2048个元素了,是一个1024 * 2尺寸的矩阵),b0和b1称为B,上式可以简写为:
Y = f(X) = max(X * W + B, 0)
于是,研究者们把W设为2048个随机数,把B设为两个随机数,然后给函数f()输入一张猫的图片。
f(X) = f(猫的图片) = max(猫的图片 * W + B) = [0.5, 0.5]
结果是y0 = 0.5, y1 = 0.5,也就是我们的神经网络分不清这张图片到底是更像猫还是更像狗。这是很正常的,因为我们的W和B是随机设定的。
(由于W和B都是函数f()的参数,下面我们把W和B统称为W。)
研究者随后对W进行了一点随机微调,并重新计算f(猫的图片)。如果调完之后y0增长了或者y1下降了,就说明调节有效;如果调完之后y1增长了或者y0下降了,就说明调的方向错了。
经过一段时间的调节,研究者发现这样很慢,W一共有2048个元素,每调一次,输出只动一点点,要调到什么时候?于是研究者就想了一个办法:用一个函数E来计算误差的大小。
首先,对输出[y0, y1]应用softmax函数。
$$\sigma(z) _{j}={\frac {e^{z _{j}}}{\sum _{k=1}^{K}e^{z _{k}}}}, j=1,2...K$$
用高中数学来写就是 softmax(Y) = softmax([y0,y1]) = [e^y0, e^y1] / (e^y0 + e^y1)
= [e^y0 / (e^y0 + e^y1), e^y1 / (e^y0 + e^y1)]
经过softmax函数之后,y0和y1的相对大小关系不变,但是他们的和保证等于1。比如说原来求得的猫狗概率是[1.2, 0.6],softmax函数之后就得到[0.65, 0.35]。
然后定义误差函数E:
Y = f(X)
E = - log(softmax(Y)) * y_true
E = - log(softmax(f(X))) * y_true
其中 y_true 代表的是输入图像的真正类别,当输入为猫时为[1,0],当输入为狗时为[0,1]。
我们注意到,因为softmax的结果都在0和1之间,应用log函数之后会得到一个负数,softmax所得的结果越小,log得到负数的绝对值就越大。如果softmax后所得结果接近零,log之后会得到一个非常大的负数。前面再加个负号,就得到一个非常大的正数。
所以E越大,说明我们的判断结果错得越离谱。这是一个很合适的误差函数。
当输入为猫时,输出y0(相对于y1)越小,误差函数E的值就越大,如果输出y0远小于y1,说明错的很严重,误差函数E的值会非常大。我们调节W的时候,就可以以E为参考。
上面讲到,研究者想了一个加速的办法。要怎样调节W,才能使得误差E不断降低呢?对了,可以利用E对W的导数。
我们在高中学过y对x的导数,可以写作$$\frac{dy}{dx}$$
上图中x0处的导数,也就是绿色切线的斜率,是个负数,大概是 -2 。如果把点x0朝导数所指的下坡方向移动,y的值会越来越小。就像下面这样:
如果用公式来描述这个过程:
$$x_1 = x_0 - \frac{dy}{dx}(x_0)$$
$$x_2 = x_1 - \frac{dy}{dx}(x_1)$$
$$x_3 = x_2 - \frac{dy}{dx}(x_2)$$
这相当于不断执行下面的操作:
$$x = x - \frac{dy}{dx}(x)$$
这种【按照y对x在x点的导数来调节x,使得y越来越小】的方法,称为导数下降法。
上面y对x的图像中,y是一维的,我们在一维函数上寻找最小值。如果y是二维的,有两个参数呢?下面是一张2维平面上的图像:
上图中 theta0 和 theta1就是待调节的参数,而J(theta0,theta1)是误差函数。图中演示了两个点是如何通过梯度下降法,找到J()的局部最低点的。
原题中的W是2048个变量,E对W的导数要怎么写?难道要写2048条公式吗?
$$\frac{dE}{dw_{0,0}},\frac{dE}{dw_{0,1}}, ... ,\frac{dE}{dw_{1,1023}}$$
上面这种写法太繁琐了(而且也不正确)。通常我们这样写:
$$\frac{\partial E}{\partial w_{ij}}(W)$$
其中i=0,1 j=0,1,2...1023 称为E对\(w_{ij}\)在W处的偏导数。
之前抛物线函数的例子中,函数y上点x处的斜率是一个数字。而这次函数E上点W的斜率,共有2048个数字,可以记作一个2048维矢量:
$$\left(\frac{\partial E}{\partial w_{0,0}}(W),\frac{\partial E}{\partial w_{0,1}}(W), ... ,\frac{\partial E}{\partial w_{1,1023}}(W)\right)$$
上面这个矢量简称函数E在W处的梯度,它和之前所说的y在x处的斜率是一个意思,只不过由很多个数组成。上面的梯度可以简写为:
$$\nabla E(W)=\left({\frac {\partial E}{\partial w_{0,1}}}(W),\ldots ,{\frac {\partial f}{\partial w_{1,1023}}}(W)\right)$$
因此,只要不断执行:
$$W = W - \alpha \nabla E(W)$$
就可以使得E的值越来越小(假定E是个比较凸的函数)。这种【按照W对E在W处的偏导数来调节W,使得E越来越小】的方法,称为梯度下降法。梯度下降法的一个变种,随机梯度下降法,是目前机器学习领域应用最广泛的优化方法。
其中α是一个可调的量,称为学习率,它决定下降的速度和稳定性。
至此,我们可以把整个训练过程概括为:
- 确定Y = f(X)的形式,为了与人脑架构匹配,我们在这里用的是神经网络函数,函数的参数(偏置和权重)为W和B,以下将它们统一称为W;
- 对于给定的一批训练图片X(1000张猫,1000张狗),以及当前的W,计算误差 E = -log(softmax(f(X))) * y_true,其中 y_true 是代表对应图片猫狗分类的标签。
- 使用梯度下降法,根据E对W在W处的偏导调节W
- 重复第2步,直到误差E不再下降为止。
- 用经过训练的f()函数,输入猫狗图片并观察其输出,统计正确率。
为了让例子显得简单,我们只使用了2个神经元,而且从输入直接到输出,中间没有隐藏层,最终得到的分类效果是很差的。
如果你想玩这种最简单的神经网络,可以直接访问google的tensorflow神经网络演示页面,那里的分类器虽然不能分类猫和狗,但可以做一些同样有趣的事情。XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX/
在实际的视觉分类应用中,神经元的数量会多得多,而且会一层叠一层(警告:以下是大学内容),比如上面就是一个四层的神经网络,有两个隐藏层。
通过增加层数,网络可以表达更加复杂的非线性关系,从而能更好地概括输入数据中隐含的复杂关系。
对于视觉应用,每一层的输入,也不是直接取输入像素的值,而是用一组二维模板,对像素的值分别进行二维卷积,再叠加求和,层和层之间还有pooling(求局部最大值并组成更小的新图像)操作。
(LeNet架构,它20年前就学会了怎样分辨人类手写的数字,准确率超过99%)
使用二维卷积和pooling,可以令图像中关键特征获得位移无关性(比如猫的脸在图像中可能会左右移动,这不应该影响最终判断的结果)。
读到这里的同学,以上介绍完了机器学习的基本概念和方法,谢谢你们。如果你觉得这太简单了,下一步的两个可选的教程分别是Andrew Ng的 Machine Learning,以及Geoff Hinton的Neural Networks for Machine Learning。
200字以内,仅用于支线交流,主线讨论请采用回复功能。