平方损失
平方损失 Square Error ,作为机器学习中最为经典,也是最为朴素的损失算法,值得进一步进行探讨。
本篇文章对背后的数学原理、最优化算法进行探讨。
 概率数学原理
 简单理解(图像)
数据如下表
| x0 | 
y | 
| 1 | 
1 | 
| 2 | 
3 | 
| 3 | 
2 | 
| 4 | 
6 | 
| 5.06 | 
7.23 | 
| 6 | 
7 | 
上图中的绿色点都是对应数据样本在二维笛卡尔坐标系中的描点。
现在需要考虑的是如何衡量数据样本与预测值的偏差。
在几何中可以通过距离表示,那么我们只需要计算距离即可。
由平面直角坐标系距离公式可知
L2=(x1−x2)2+(y1−y2)2
代入数据点(x0,y),预测点(x0,h(x0))可得
L2=(x0−x0)2+(y−h(x0))2=(y−h(x0))2
即可导出均方误差公式为
E(xi)=2m1i=0∑m(yi−θxi)2
 深入理解(概率统计)
机器学习中的很多算法并不只是表面的直观理解,其实内部还有很多的数学原理。
这部分通过极大似然法来对为什么是平方来进行理解。
对于真实世界的预测,我们可以写为
y=θx+ε
我们令 ε 为真实世界中被忽视或无法获取的特征的和,可以变形为
ε=y−θx
在生活经验中,ε 非常接近钟形分布,即高斯正态分布,于是
ε∽N(μ,σ2)
展开得
f(ε)=2πσ1⋅e−2σ2(y−θx−μ)2
我们计算出出现 f(ε) 的概率,记为 P(ε)
P(ε)=D∏f(ε)=D∏2πσ1⋅e−2σ2(y−θx−μ)2
我们选择对它取对数操作,对数是增函数
L(ε)=lnP(ε)=D∑ln2πσ1+D∑lne−2σ2(y−θx−μ)2=D∑ln2πσ1+D∑−2σ2(y−θx−μ)2
因为第一项是一个常数,所以我们要让 P(ε) 最大,于是要让 L(ε) 最大,于是要让下式最小
2σ2(y−θx−μ)2
只需要不断优化 θ 使得 (y−θx)2 最小,即
minimize (y−θx)2
这意味着接下来我们需要通过不同方法进行上述操作
 梯度下降
 定义
梯度下降法 (gradient descent) 是一个最优化算法,常用于机器学习和人工智能当中递归逼近最小偏差模型。
在已知表达式的情况下可以代替二分三分,且效率较高。
在发展过程中衍生出了随机梯度下降算法和批量梯度下降算法,本文介绍批量梯度下降算法。
 数学原理
 推导过程
以线性回归为例,不妨令 xi 为第 i 组输入数据,h(xi) 为预测结果
h(x(i))=θx(i)
(损失函数)
此处使用均方误差(又称最小二乘法)
令误差函数为E(xi,yi)得
E(xi,yi)=2m1i=0∑m(yi−h(xi))2=2m1i=0∑m(yi−θxi)2
由于我们的目的是通过数据拟合出函数,换句话说是调整 θ 的值
所以我们更换主元为 θ ,并构造损失函数 J(θ) 得
J(θ)=2m1i=0∑m(yi−θxi)2
(批量梯度下降)
并对上式求关于 θ 的偏导得
J′(θ)=∂θj∂J(θ)=m1i=0∑m(θxi−yi)xji
我们需要通过上式确定迭代的方向,并不断迭代直到收敛
于是得到递推式为
θj=θj−J′(θj)α
上式中的α为学习率,可以控制没移动一步的快慢。若α设置过大,则可能出现偏差越迭代越大的情况;若α设置过小,则训练耗时越大。需要正确选择学习率的大小。
迭代的过程如下图所示.
 过程详解
可以用盲人爬山来简单形容。
盲人爬山闭着眼,要用脚试一下是往下的,那就走,一直这样操作,最终下山。当然这也就暴露出了其问题,无法一次性找到全局最优解。
可以用图像来简单说明。
这是损失函数(两个 θ )的大致图像,我们需要找到最低点时的 θj 的值。
直接观察三维图像可能比较抽象,我们不妨先看二维,再拓展到三维。
在图中横截一条线如下图
注:曲线为函数图像;直线为对应点的切线(即 J′(θ1) 项的值)
y=(x−3)2+1
y1′=6x−26
y2′=3.6x−13.04
y3′=2x−6
y4′=x−2.25
数据如下表
| x | 
y | 
J′(θ) | 
| 6 | 
10 | 
6 | 
| 4.8 | 
4.24 | 
3.6 | 
| 4 | 
2 | 
2 | 
| 3.5 | 
1.25 | 
1 | 
| 3.2 | 
1.04 | 
0.4 | 
| 3.1 | 
1.01 | 
0.2 | 
| 3.05 | 
1.0025 | 
0.1 | 
| 3 | 
1 | 
0 | 
当 k>0 时,说明 θ1 位于 θ 的右侧,即要减去一个值
当 k<0 时,说明 θ1 位于 θ 的左侧,即要加上一个值
于是 θ1=θ1−J′(θ1)α
实际上 n 元线性回归(逻辑回归)是 n+1 维的图像,我们可以类比理解。
 实现
 线性回归
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 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58
   | 
  #include <iostream> #include <algorithm> #include <ctime> using namespace std; int n,m; double pre(double a[],double b[]){     double h=0;     for(int i=1;i<=m;i++){         h=h+a[i]*b[i];     }     return h; } int main(){          double x[105][15],y[105],w[105],k=0.001,lam=0;     w[0]=0;     cin>>n>>m;     for(int i=1;i<=n;i++){         x[i][0]=1;         for(int j=1;j<=m;j++)             cin>>x[i][j];         cin>>y[i];     }         int iteration=200,q=0;     double regular=lam/((double)n);          while(iteration--){         for(int p=0;p<=m;p++){             double s=0;             for(int i=0;i<=n;i++){                 double predict=pre(x[i],w);                 double loss=predict-y[i];                 s=s+loss*x[i][p];             }             s=s/n;                         w[p]=w[p]-s*k-regular*w[p];         }         if(iteration%1==0){             printf("Try %d:\t",++q);             for(int i=0;i<=m;i++) printf("%.3lf  ",w[i]);             printf("\n");         }     }     printf("Result:  ");     for(int i=0;i<=m;i++) printf("%.3lf  ",w[i]);     printf("\n");                    double xl[105];     for(int i=1;i<=m;i++) cin>>xl[i];     printf("%.0lf",pre(w,xl));     return 0; }
 
  | 
 
 逻辑回归
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 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53
   | 
  #include <iostream> #include <algorithm> #include <cmath>
  using namespace std;
  int n,m; double w[105];
  double z(double xx[]){ 	double h=0;     for(int i=1;i<=m;i++){         h=h+xx[i]*w[i];     }     return h; } double f(double zz){     double tm1=exp(-zz);     double tm2=1+tm1;     double fin=1/tm2;     return fin; } int main() {     double x[20][105],y[200],aph=0.0001,lam=0;     cin>>m>>n;     for(int i=1;i<=m;i++){         x[i][0]=1;         for(int j=1;j<=n;j++) cin>>x[i][j];         cin>>y[i];     }     int iter=2000;     double regular=lam/((double)m); 	while(iter--){         for(int j=0;j<=n;j++){             double loss=0.00;       		for(int i=0;i<=m;i++)             	loss=loss+(f(z(x[i]))-y[i])*x[i][j];             loss=loss/m;             w[j]=w[j]-loss*aph-regular*w[j];         }         if(iter%100==0){         	for(int k=0;k<=n;k++)         		printf("%.3lf ",w[k]);             printf("\n"); 		}     }     printf("train over\n");     for(int i=0;i<=n;i++) printf("%.3lf ",w[i]);     printf("\n");     return 0; }
 
  | 
 
 正规方程
 定义
正规方程 (Normal Equation) ,是一个快速计算局部极值的方法。在数据量不大( n<1000 )左右时优于梯度下降算法(取决于个人机器性能限制)。
在知道表达式的情况下可以代替二分查找或三分查找。现在多用于s人工智能(机器学习)领域。
结合曾经的竞赛题目,这个技巧不太可能考到,但学习它还是需要的。
 数学原理
不妨令
X=⎣⎢⎢⎡111x11x12x1mx21x22...x2m.........xn1xn1xnm⎦⎥⎥⎤
y=⎣⎢⎢⎡y1y2…ym⎦⎥⎥⎤,θ=⎣⎢⎢⎡θ1θ2…θn⎦⎥⎥⎤
则预测函数为
y=Xθ
J(θ)=(y−Xθ)2
∂θ∂J(θ)=2(y−Xθ)XT
令上式为 0 即得最小值
2(y−Xθ)XTyXT−XθXTXTθX(XTX)−1XTXθIθθ=0=0=yXT=yXT(XTX)−1=yXT(XTX)−1=(XTX)−1XTy
 实现
C++ 可以使用矩阵计算库 Eigen 或手写矩阵计算
Python 可以使用 Numpy 库或手写
此处使用 C++ 中的 Eigen 库实现
 C++
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 29 30 31 32 33
   |  #include <iostream> #include "eigen/Eigen/Eigen" using namespace std; using namespace Eigen;
  Matrix<double,Dynamic,Dynamic> X; Matrix<double,Dynamic,1> w; Matrix<double,Dynamic,1> y;
  int main() {     int m,d;     cin>>m>>d;     X.resize(m,d+1);     w.resize(d+1,1);     y.resize(m,1);     X.fill(1);     for(int i=0;i<m;i++){         printf("\nX=\n");         for(int j=1;j<=d;j++){             int q;             cin>>q;             X(i,j)=q;         }         printf("y=");         int q;         cin>>q;         y(i,0)=q;     }     w=(X*X.transpose()).inverse()*X.transpose()*y;     cout<<w;     return 0; }
 
  |