0%

Python 机器学习算法一之线性回归的推导及实战

线性回归是机器学习中最基本的算法了,一般要学习机器学习都要从线性回归开始讲起,本节就对线性回归做一个详细的解释。

实例引入

在讲解线性回归之前,我们首先引入一个实例,张三、李四、王五、赵六都要贷款了,贷款时银行调查了他们的月薪和住房面积等因素,月薪越高,住房面积越大,可贷款金额越多,下面列出来了他们四个人的工资情况、住房面积和可贷款金额的具体情况:

姓名

工资(元)

房屋面积(平方)

可贷款金额(元)

张三

6000

58

30000

李四

9000

77

55010

王五

11000

89

73542

赵六

15000

54

63201

看到了这样的数据,又来了一位孙七,他工资是 12000 元,房屋面积是 60 平,那他大约能贷款多少呢?

思路探索

那这时候应该往哪方面考虑呢?如果我们假定可贷款金额和工资、房屋面积都是线性相关的,要解决这个问题,首先我们想到的应该就是初高中所学的一次函数吧,它的一般表达方式是 $ y = wx + b $,$ x $ 就是自变量,$ y $ 就是因变量,$ w $ 是自变量的系数,$ b $ 是偏移量,这个式子表明 $ y $ 和 $ x $ 是线性相关的,$ y $ 会随着 $ x $ 的变化而呈现线性变化。 现在回到我们的问题中,情况稍微不太一样,这个例子中是可贷款金额会随着工资和房屋面积而呈现线性变化,此时如果我们将工资定义为 $ x1 $,房屋面积定义为 $ x_2 $,可贷款金额定义为 $ y $,那么它们三者的关系就可以表示为: $ y = w_1x_1 + w_2x_2 + b $,这里的自变量就不再是一个了,而是两个,分别是 $ x_1 $ 和 $ x_2 $,自变量系数就表示为了 $ w_1 $ 和 $ w_2 $,我们将其转化为表达的形式,同时将变量的名字换一下,就成了这个样子: $$ h{\theta}(x) = \theta_0 + \theta_1x_1 + \theta_2x_2 $$ 这里只不过是将原表达式转为函数形式,换了个表示名字,另外参数名称从 $ w $ 换成了 $ \theta $,$ b $ 换成了 $ \theta_0 $,为什么要换?因为在机器学习算法中 $ \theta $ 用的更广泛一些,约定俗成。 然后这个问题怎么解?我们只需要求得一组近似的 $ \theta $ 参数使得我们的函数可以拟合已有的数据,然后整个函数表达式就可以表示出来了,然后再将孙七的工资和房屋面积代入进去,就求出来他可以贷款的金额了。

思路拓展

那假如此时情景变一变,变得更复杂一些,可贷款金额不仅仅和工资、房屋面积有关,还有当前存款数、年龄等等因素有关,那我们的表达式应该怎么写?不用担心,我们有几个影响因素,就写定义几个变量,比如我们可以将存款数定义为 $ x3 $,年龄定义为 $ x_4 $,如果还有其他影响因素我们可以继续接着定义,如果一共有 $ n $ 个影响因素,我们就定义到 $ x_n $,这时候函数表达式就可以变成这样子了: $$ h{\theta}(x) = \theta0 + \theta_1x_1 + \theta_2x_2 + … + \theta_nx_n h{\theta}(x) = \sum_{i=0}^{n}\theta_ix_i = \theta^Tx $$ 如果要使得这个公式成立,这里需要满足一个条件就是 $ x_0 = 1 $,其实在实际场景中 $ x_0 $ 是不存在的,因为第一个影响因素我们用 $ x_1 $ 来表示了,第二个影响因素我们用 $ x_2 $ 来表示了,依次类推。所以这里我们直接指定 $ x_0 = 1 $ 即可。 后来我们又将公式简化为线性代数的向量表示,这里 $ \theta^T $ 是 $ \theta $ 向量转置的结果,而 $ \theta $ 向量又表示为 $ (\theta_0, \theta_1, …, \theta_n) $,同样地,$ x $ 向量可以表示为 $ (x_0, x_1, …, x_n) $,总之,表达成后面的式子,看起来更简洁明了。 好了,这就是最基本的线性判别解析函数的写法,是不是很简单。

实际求解

那接下来我们怎样实际求解这个问题呢?比如拿张三的数据代入到这个函数表达式中,这里还是假设有两个影响因素,张三的数据我们可以表示为 $ x1^{(1)} = 6000, x_2^{(1)} = 58, y^{(1)} = 30000 $,注意这里我们在数据的右上角加了一个小括号,里面带有数字,如果我们把张三的数据看成一个条目,那么这个数字就代表了这个条目的序号,1 就代表第一条数据,2 就代表第二条数据,为啥这么写?也是约定俗成,以后也会经常采用这样的写法,记住就好了。 所以,我们的愿景是要使得我们的函数能够拟合当前的这条数据,所以我们希望是这样的情况: $$ y^{(1)} = \sum{i=0}^{n}\theta{i}x{i}^{(1)} = \theta^Tx^{(1)} h{\theta}(x^{(1)}) = \sum{i=0}^{n}\theta{i}x{i}^{(1)} = \theta^Tx^{(1)} (h{\theta}(x^{(1)}) - y^{(1)})^2 (h{\theta}(x^{(2)}) - y^{(2)})^2 (h{\theta}(x^{(i)}) - y^{(i)})^2 J(\theta) = \dfrac{1}{2m}\sum{i=1}^{m}(h*\theta(x^{(i)}) - y^{(i)})^2 $$ 注意这里 $ i $ 指的是第几条数据,是从 1 开始的,一直到 $ m $ 为止,然后使用了求和公式对每一条数据的误差进行累加和,最后除以了 $ 2m $,我们的最终目的就是找出合适的 $ \theta $,使得这个 $ J(\theta ) $ 的值最小,即误差最小,在机器学习中,我们就把 $ J(\theta) $ 称为损失函数(Loss Function),即我们要使得损失值最小。 有的小伙伴可能好奇损失函数前面为什么是 $ 2m $,而不是 $ m $?因为我们后面要用到这个算式的导数,所以这里多了个 2 是为了便于求导计算。况且一个表达式要求最小值,前面乘一个常数是对结果没影响的。

求解过程

由于我们求解的是线性回归问题,所以整个损失函数的图像非常简单清晰,如果只有 $ \theta1 $ 和 $ \theta_2 $ 两个参数,我们甚至可以直接画出其图像,整个损失函数大小随 $ \theta_1 $ 和 $ \theta_2 $ 的变化实际上类似于这样子: 可以看到这是一个凸函数,竖轴代表损失函数的大小,横纵两轴代表 $ \theta_1 $ 和 $ \theta_2 $ 的变化,可见在中间的最低谷损失函数取得最小值,这时候损失函数在 $ \theta_1 $ 和 $ \theta_2 $ 上的导数都是 0,因此我们可以一步到位,直接用偏导置零的方式来求解损失函数取得最小值时的 $ \theta $ 值的大小。 所以我们可以先对每个 $ \theta $ 求解其偏导结果,这里 $ \theta $ 表示为 $ \theta_j $,代表 $ \theta $ 中的某一维: $$ \dfrac{\partial{J(\theta)}}{\partial{\theta_j}} = \dfrac{1}{2m} \dfrac{\partial({\sum{i=1}^{m}{(y^{(i)} - h{\theta}(x^{(i)}))^2}})}{\partial{\theta_j}} \\\\ =\dfrac{1}{m}\sum{i=1}^{m}((h{\theta}(x^{(i)}) - y^{(i)})x_j^{(i)}) \sum{i=1}^{m} {h{\theta}(x^{(i)})x_j^{(i)}} - \sum{i=1}^{m}y^{(i)}xj^{(i)} = 0 \theta_j = \theta_j - \alpha\dfrac{\partial{J(\theta)}}{\partial{\theta_j}} \\\\ = \theta_j - \dfrac{\alpha}{m}\sum{i=1}^{m}((h_{\theta}(x^{(i)}) - y^{(i)})x_j^{(i)}) $$ 这里 $ \alpha $ 就是学习率,$ \theta_j $ 每经过一步都会进行一次更新,得到新的结果,经过梯度下降过程,$ \theta_j $ 都会更新为使得梯度最小化的数值,最后就完成了 $ \theta $ 的求解。 以上便是线性回归的整个推导和求解过程。

实战操作

现在呢,我们想要根据前面的数据来求解这个真实的问题,为了解决这个问题,我们在这里用 Python 的 Sklearn 库来实现。 对于线性回归来说,Sklearn 已经做好了封装,直接使用 LinearRegression 即可。 它的 API 如下:

1
class sklearn.linear_model.LinearRegression(fit_intercept=True, normalize=False, copy_X=True, n_jobs=None)

参数解释如下:

  • fit_intercept : 布尔值,是否使用偏置项,默认是 True。
  • normalize : 布尔值,是否启用归一化,默认是 False。当 fit_intercept 被置为 False 的时候,这个参数会被忽略。当该参数为 True 时,数据会被归一化处理。
  • copy_X : 布尔值,默认是 True,如果为 True,x 参数会被拷贝不会影响原来的值,否则会被复写。
  • n_jobs:数值或者布尔,如果设置了,则多核并行处理。

属性如下:

  • coef_:x 的权重系数大小
  • intercept_:偏置项大小

代码实现如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
from sklearn.linear_model import LinearRegression

x_data = [
[6000, 58],
[9000, 77],
[11000, 89],
[15000, 54]
]
y_data = [
30000, 55010, 73542, 63201
]

lr = LinearRegression()
lr.fit(x_data, y_data)
print('方程为:y={w1}x1+{w2}x2+{b}'.format(w1=round(lr.coef_[0], 2),
w2=round(lr.coef_[1], 2),
b=lr.intercept_))
x_test = [[12000, 60]]
print('贷款金额为:', lr.predict(x_test)[0])

运行结果:

1
2
方程为:y=4.06x1+743.15x2+-37831.862532707615
贷款金额为:55484.33779181102

在这里我们首先声明了 LinearRegression 对象,然后将数据整合成 xdata 和 y_data 的形式,然后通过调用 fit() 方法来对数据进行拟合。 拟合完毕之后,LinearRegression 的 coef 对象就是各个 x 变量的权重大小,即对应着 $ \theta1, \theta_2 $,intercept 则是偏移量,对应着 $ \theta_0 $,这样我们就可以得到一个线性回归表达式了。 然后我们再调用 predict() 方法,将新的测试数据传入,便可以得到其预测结果,最终结果为 55484.34,即孙七的可贷款额度为 55484.34 元。 以上便是机器学习中线性回归算法的推导解析和相关调用实现。