本篇文章做一个简单的机器学习引入,并且介绍机器学习中最简单的方法——线性回归。

什么是机器学习?

机器学习从数据开始:

  • 数据可以是对于人的观察(偏好、健康…)
  • 数据也可以是对于世界的观察(图像、声音…)

通过机器学习,我们可以找到相似的对象、为对象做预测、从对象身上总结知识、为对象分组…

算法!算法!

机器学习可以被认为是一个拥有不断增长的数据集的算法。
但是算法很难被应用到实际,并且在应用过程中可能会进行调整,所以理解他们是必要的。

AI -> Neural Networks -> Modelling biological neurons & Predicting stuff from data

机器学习的实例可以是谷歌、微软或亚马逊等商业公司的推荐机制…
也可以是通过学习人类基因片段来诊断病症…
可以是信息检索:新闻检索、语言模型预测、图像和视频检索…
或者是人机交互:语音识别、姿势识别…
可以与生物信息相关:预测生物与基因反应、预测基因和蛋白质网络结构、通过序列预测蛋白质功能…

监督学习(Supervised Learning)

机器学习分为三大板块:监督学习、无监督学习和强化学习,本文暂时只讨论前两种。

监督学习,顾名思义,即是基于已经标识好的数据集进行训练(训练过程像是做一本有答案的练习册)。

回归(Regression)

回归是监督学习的一种,即从数据集中总结出一种连续的函数。
例子:预测股票价格(参数可以是时间或者跟利息相关的其他变量)。

分类(Classification)

学习一种可以将不同对象区分开的规则。
例子:疾病诊断,垃圾邮件检测。

无监督学习(Unsupervised Learning)

继续上面练习册的例子,无监督学习即是做一本没有答案的练习册,自己总结规律。

聚类(Clustering)

为相似的对象划分组别。
例子:具有相同偏好的人,具有相似功能的基因。

投影(Projection)

减少参数数量。
例子:将复杂的数据可视化。

当然上述算法的介绍是十分泛泛的,以后应该会补全的…吧? —2023.08.14

回归的伊始:线性回归

我们使用1896-2000年间奥林匹克运动会一百米赛的金牌数据进行训练,随后对2004、2008、2012以及2016年的金牌时间进行预测。(具体数据可以到www.statisca.com进行查找)

为了使用线性回归做的一些假设(错误的):

  • 奥林匹克举办年份与获奖时间之间存在关系。(实则不然,年份只是训练方法、医疗条件等等进步的一个外显)
  • 关系是线性的。(实则不然,获奖时间明显是波动下降)
  • 会一直保持线性关系。(那将来的某一天获奖时间会变成负的喽?)

综上,线性回归作为新手入门手段,仍然存在许多局限性,在此先忽略这些错误。

属性(Attributes)和目标(Targets):
属性是影响预测结果的因变量,在这里即是年份。目标是要进行预测的对象,在这里即是获奖时间。

数学意义上,我们可以用变量x和变量t来表示这两个参数。
如此我们便得到了预测模型,t = f(x)。
我们有若干个带答案数据集(attribute-response pairs)。
将上面的模型展开,我们可以得到t = f(x) = w0 + w1*x
w0和w1是线性模型的两个参数。
如此我们便得到了数据和要进行调整的参数。

那么我们如何评判模型的优劣呢?下面介绍一种简单的损失函数,平方损失。

Lossn = (tn - f(xn; w0; w1))^2

平均损失即是:

Lossn = 1/N Σ1->n (tn - f(xn; w0; w1))^2

Lower is better.

因此我们要对上述损失函数进行求导,如此便可以找到损失最小的参数。
问题来了,当参数多于两个时该怎么导损失函数呢?偏微分。
中间冗长的(或许也不,只是这里不好写公式)推算先略去不表,我们最终得出了当损失最小时参数w1和参数w0的公式:

w1 = (xt_bar - x_bart_bar)/(x_square_bar - x_barx_bar)
w0 = t_bar - w1*x_bar

如此我们便得到了质量最高的线性回归模型。
以下为c++的代码实现(太简单了所以没放github):

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
#include <iostream>
#include <cmath>
#include <cstring>

using namespace std;

int main(){
float w1, w0, x_bar, t_bar, xt_bar, x_square_bar, pre_2004, pre_2008, pre_2012, pre_2016;

t_bar = (131 + 121.2 + 116 + 112.8 + 111.9 + 113.4 + 112.4 + 111.8
+ 109.8 + 112.9 + 109.2 + 109.2 + 107.7 + 106.3 + 105.1 + 104.3
+ 105.86 + 103.5 + 105.4 + 103 + 103.55 + 103.66 + 102.58 + 105.08)/24;

x_bar = (1896 + 1900 + 1904 + 1908 + 1912 + 1920 + 1924
+ 1928 + 1932 + 1936 + 1948 + 1952 + 1956 + 1960 + 1964 + 1968
+ 1972 + 1976 + 1980 + 1984 + 1988 + 1992 + 1996 + 2000)/24;

xt_bar = (131*1896 + 121.2*1900 + 116*1904 + 112.8*1908 + 111.9*1912
+ 113.4*1920 + 112.4*1924 + 111.8*1928 + 109.8*1932 + 112.9*1936 + 109.2*1948
+ 109.2*1952 + 107.7*1956 + 106.3*1960 + 105.1*1964 + 104.3*1968 + 105.86*1972
+ 103.5*1976 + 105.4*1980 + 103*1984 + 103.55*1988 + 103.66*1992 + 102.58*1996
+ 105.08*2000)/24;

x_square_bar = (1896*1896 + 1900*1900 + 1904*1904 + 1908*1908 + 1912*1912 + 1920*1920 + 1924*1924
+ 1928*1928 + 1932*1932 + 1936*1936 + 1948*1948 + 1952*1952 + 1956*1956 + 1960*1960 + 1964*1964 + 1968*1968
+ 1972*1972 + 1976*1976 + 1980*1980 + 1984*1984 + 1988*1988 + 1992*1992 + 1996*1996 + 2000*2000)/24;

w1 = (xt_bar-x_bar*t_bar)/(x_square_bar-x_bar*x_bar);
w0 = t_bar - w1*x_bar;

pre_2004 = w0 + w1*2004;
pre_2008 = w0 + w1*2008;
pre_2012 = w0 + w1*2012;
pre_2016 = w0 + w1*2016;

printf("x_bar = %.2f\n", x_bar);
printf("t_bar = %.2f\n", t_bar);
printf("xt_bar = %.2f\n", xt_bar);
printf("x_square_bar = %.2f\n", x_square_bar);
printf("The two parameters are: w0 = %.2f, w1 = %.2f\n", w0, w1);
printf("The equation is t = f(x) = %.2f + %.2fx\n", w0, w1);
printf("The predicted time of gold medal in 800m race in 2004 is %.2f seconds\n", pre_2004);
printf("The real time of gold medal in 800m race in 2004 is 104.45 seconds. \n");
printf("The predicted time of gold medal in 800m race in 2008 is %.2f seconds\n", pre_2008);
printf("The real time of gold medal in 800m race in 2008 is 104.65 seconds. \n");
printf("The predicted time of gold medal in 800m race in 2012 is %.2f seconds\n", pre_2012);
printf("The real time of gold medal in 800m race in 2012 is 100.91 seconds. \n");
printf("The predicted time of gold medal in 800m race in 2016 is %.2f seconds\n", pre_2016);
printf("The real time of gold medal in 800m race in 2016 is 102.15 seconds. \n");
return 0;
}

向量表示方法

到这里一个简单的线性回归程序已经编写好了,但是我们还需要进一步的完善。
我们可以发现在上面定义的模型实际上只是一条直线,为了提高模型能力,我们可以增加参数的次数。
增加函数的次数可以让函数的拟合能力变强,因为整个函数会变得十分灵活,模型越复杂(次数越高)则模型的Loss越低。但是这里要注意一点,损失的减少并不意味着模型泛化(generalization, predictive ability)能力一定提高,要找到合适的模型复杂度,一味提高次数会导致过拟合(over-fitting, decreasing the loss)。

因为参数增加了,在使用上面最高二次的表示方法未免会显得很蠢,所以我们在这里引用向量表示方法。
我们令w表示一个2 * 1的向量,包含w0和w1,令x同样表示为一个2 * 1的向量,包括x0和x1。如此我们便可以将上面的模型:
t = w0 + w1*x = Σ0->k wk * xk
简化为:
t = w ^ T * x
损失为:
Ln = (tn - w ^ T * xn)^2
平均损失为:
L = 1/N Σ1->N (tn - w ^ T * xn)^2

优雅的表示方法!
但这样就足够了吗?

我们令qn = (tn - w ^ T * xn),也就是一个N * 1的向量
如此我们又可以对损失公式进行简化:
L = 1/N q ^ T * q

我们接着设X为N * 1的矩阵,包括x1到xn。(矩阵是粗体大写字母,向量是粗体小写字母)
于是我们可以得到q = t - X * w
而平均损失函数也可以更新为:
L = 1/N (t - X * w) ^ T * (t - X * w)

对于损失函数的偏微分计算同样略去不表,这里给出最后参数的公式:
w = (X ^ T * X)^-1 * X ^ T * t

本篇文章到此结束。