1.1 什么是简单线性回归
KNN算法属于分类(Classification),即label为离散的类别(categorical variable) 如:颜色类别,手机品牌
而简单线性回归是否属于回归(regression) ,即label为连续数值型(continuous numerical varibale) 如房价,股票,降雨量
什么是简单线性回归?
所谓简单,是指有一个样本特征,即只有一个自变量:所谓线性,是指方程是线性的;所谓回归,是指用方程来模拟变量之间是如何关联的。
简单线性回归:思想简单(与其背后强大的数学性质相关,同时也是许多强大的非线性模型,(多项式回归、逻辑回归 SVM))的基础。并且结果具有良好的解释性。
1.2思路
示例1
玩具产量和成本的关系,如下表格
玩具个数 | 成本 |
---|---|
10 | 7.7 |
10 | 9.87 |
11 | 10.87 |
12 | 12.18 |
13 | 11.43 |
14 | 13.36 |
15 | 15.15 |
16 | 16.73 |
17 | 17.4 |
可视化:
图像中我们发现,产量和成本之间,存在者一定线性关系,似乎沿着某条直线上下随机波动
也就是说,我们需要一条直线,最大程度的拟合样本特征和样本数据标记之间的关系。在二维平面中,这条直线就是y = ax+b。
假设我们找到了最佳拟合的直线方程:y = ax + b
则对于每个样本点
x
i
x^i
xi ,根据我们的直线方程,预测值为:
y
^
\hat{y}
y^=
a
x
i
ax^i
axi +b
很显然,我们希望直线方程能够尽可能地拟合真实情况,也就是说真值
y
i
y^i
yi 和预测值
y
^
i
\hat{y} ^i
y^i 的差距尽量小。只有所有的样本的误差都小,才能证明我们找出的直线方程拟合性好。
通常来说,为了防止正误差值和负误差值相抵的情况,使用绝对值来表示距离:|
y
i
y^i
yi-
y
^
i
\hat{y}^i
y^i|,但是在线性回归中,我们需要找极值,需要函数可导,而y = |x| 不是一个处处可导的函数,因此很自然地想到可以使用:(
y
i
y^i
yi -
y
^
i
\hat{y}^i
y^i)2
考虑所有样本,我们推导出:
∑
i
=
1
n
\sum_{i=1}^{n}
∑i=1n(
y
i
y^i
yi -
y
^
i
\hat{y}^i
y^i)2
因此我们目标是:已知训练数据样本x、y ,找到a和b的值,使
∑
i
=
1
m
\sum_{i=1}^{m}
∑i=1m(
y
i
y^i
yi -
a
x
i
−
b
ax^i-b
axi−b)2 尽可能小,从而得出最佳的拟合方程。
示例2:
汽车卖家做电视广告和卖出汽车的数量;
如何练出适合简单线性回归的模型的最佳回归线?
使sum of squares最小
计算:
分子 = (1-2)(14-20)+(3-2)(24-20)+(2-2)(18-20)+(1-2)(17-20)+(3-2)(27-20)
= 6 + 4 + 0 + 3 + 7
= 20
分母 = (1-2)^2 + (3-2)^2 + (2-2)^2 + (1-2)^2 + (3-2)^2
= 1 + 1 + 0 + 1 + 1
4
b1 = 20/4 =5
b0 = 20 - 5*2 = 20 - 10 = 10
假设一周有广告数量为6,预测汽车销量多少
x_given = 6
Y_hat = 5*6 + 10 = 40
1.3 Python实现:
import numpy as np
def fitSLR(x, y):
n = len(x)
dinominator = 0
numerator = 0
for i in range(0, n):
numerator += (x[i] - np.mean(x))*(y[i] - np.mean(y))
dinominator += (x[i] - np.mean(x))**2
b1 = numerator/float(dinominator)
b0 = np.mean(y)/float(np.mean(x))
return b0, b1
def predict(x, b0, b1):
return b0 + x*b1
x = [1, 3, 2, 1, 3]
y = [14, 24, 18, 17, 27]
b0, b1 = fitSLR(x, y)
print "intercept:", b0, " slope:", b1
x_test = 6
y_test = predict(6, b0, b1)
print "y_test:", y_test
1.3基本推导思路
在上一小节中,找到一组参数,使得真实值与预测值之间的差距尽可能地小,是一种典型的机器学习算法的推导思路
我们所谓的建模过程,其实就是找到一个模型,最大程度的拟合我们的数据。 在简单线回归问题中,模型就是我们的直线方程:y = ax + b 。
要想最大的拟合数据,本质上就是找到没有拟合的部分,也就是损失的部分尽量小,就是损失函数(loss function)(也有算法是衡量拟合的程度,称函数为效用函数(utility function)):
∑
i
=
1
m
(
y
^
−
a
x
i
−
b
)
2
\displaystyle\sum_{i=1}^{m}(\hat{y} - ax^i -b)2
i=1∑m(y^−axi−b)2
因此,推导思路为:
- 通过分析问题,确定问题的损失函数或者效用函数;
- 然后通过最优化损失函数或者效用函数,获得机器学习的模型
近乎所有参数学习算法都是这样的套路,区别是模型不同,建立的目标函数不同,优化的方式也不同。
回到简单线性回归问题,目标:
已知训练数据样本、 ,找到和的值,使
∑
i
=
1
m
(
y
^
−
a
x
i
−
b
)
2
\displaystyle\sum_{i=1}^{m}(\hat{y} - ax^i -b)2
i=1∑m(y^−axi−b)2 尽可能小
这是一个典型的最小二乘法问题(最小误差的平方)
通过最小二乘法可以求出a、b的表达式
a a a = ∑ i = 1 m ( x i − x ˉ ) ( y i − y ˉ ) ∑ i = 1 m ( x i − x ˉ ) 2 \frac{\sum_{i=1}^{m}(x^i - \bar{x})(y^i-\bar{y})}{\sum_{i=1}^{m}(x^i - \bar{x})2} ∑i=1m(xi−xˉ)2∑i=1m(xi−xˉ)(yi−yˉ) b= y ˉ − a x ˉ \bar{y} -a\bar{x} yˉ−axˉ
2.1最小二乘法
2.1.1损失函数
机器学习中,所有的算法模型其实都依赖于最小化或最大化的一个函数称之为目标函数
最小化这组称之为损失函数什么是损失函数?
损失函数描述了单个样本预测值和真实值之间误差的程度。用来度量模型一次预测的好坏。
损失函数是衡量预测模型预测期望结果表现的指标。损失函数越小,模型的鲁棒性越好。
常见损失函数有:
-
0-1损失函数:用来标书分类问题,当预测分类出现错误的时候,损失函数值1,正确为0
-
平方损失函数:用来描述回归问题,用来表示连续性变量,为预测值与真实值差值的平方。(误差越大,惩罚力度越强,也就是对差值敏感)
-
绝对损失函数:用来回归模型,用距离的绝对值来衡量。
-
对数损失函数:是预测值Y和条件概率之间的衡量。事实上,该损失函数用到了极大似然估计的思想。P(Y|X)通俗的解释就是:在当前模型的基础上,对于样本X,其预测值为Y,也就是预测正确的概率,由于概率之间的同时满足需要使用乘法,为了将其转化为加法,我们将其取对数。最后由于是损失函数,所以预测正确的概率越高,其损失值越小,因此在加个负号取反。
以上损失函数是针对于单个样本的,但是一个训练数据集中存在N个样本,N个样本给出N个损失,如何进行选择呢?
这就引出了风险函数。
2.1.2期望风险
期望风险是损失函数的期望,用来表达理论上模型f(X)关于联合分布P(X,Y)的平均意义下的损失。又叫期望损失/风险函数。
2.1.3经验风险
模型f(X)关于训练数据集的平均损失,称为经验风险或经验损失。
其公式含义为:模型关于训练集的平均损失(每个样本的损失加起来,然后平均一下)
经验风险最小的模型为最优模型。在训练集上最小经验风险最小,也就意味着预测值和真实值尽可能接近,模型的效果越好。公式含义为取训练样本集中对数损失函数平均值的最小。
2.1.4 经验风险最小化和结构风险最小化
期望风险是模型关于联合分布的期望损失,经验风险是模型关于训练样本数据集的平均损失。根据大数定律,当样本容量N趋于无穷时,经验风险趋于期望风险。
因此很自然地想到用经验风险去估计期望风险。但是由于训练样本个数有限,可能会出现过度拟合的问题,即决策函数对于训练集几乎全部拟合,但是对于测试集拟合效果过差。因此需要对其进行矫正:
- 结构风险最小化:当样本容量不大的时候,经验风险最小化容易产生“过拟合”的问题,为了“减缓”过拟合问题,提出了结构风险最小理论。结构风险最小化为经验风险与复杂度同时较小。
通过公式可以看出,结构风险:在**经验风险上加上一个正则化项(regularizer),或者叫做罚项(penalty) 。正则化项是J(f)是函数的复杂度再乘一个权重系数(用以权衡经验风险和复杂度)
**3.1 简单线性回归示例代码 **
import numpy as np
class SimpleLinearRegression:
def __init__(self):
"""模型初始化函数"""
self.a_ = None
self.b_ = None
def fit(self, x_train, y_train):
"""根据训练数据集x_train,y_train训练模型"""
assert x_train.ndim ==1, \
"简单线性回归模型仅能够处理一维特征向量"
assert len(x_train) == len(y_train), \
"特征向量的长度和标签的长度相同"
x_mean = np.mean(x_train)
y_mean = np.mean(y_train)
num = (x_train - x_mean).dot(y_train - y_mean) # 分子
d = (x_train - x_mean).dot(x_train - x_mean) # 分母
self.a_ = num / d
self.b_ = y_mean - self.a_ * x_mean
return self
def predict(self, x_predict):
"""给定待预测数据集x_predict,返回表示x_predict的结果向量"""
assert x_predict.ndim == 1, \
"简单线性回归模型仅能够处理一维特征向量"
assert self.a_ is not None and self.b_ is not None, \
"先训练之后才能预测"
return np.array([self._predict(x) for x in x_predict])
def _predict(self, x_single):
"""给定单个待预测数据x_single,返回x_single的预测结果值"""
return self.a_ * x_single + self.b_
def __repr__(self):
"""返回一个可以用来表示对象的可打印字符串"""
return "SimpleLinearRegression()"
3.2 调用
下面我们在jupyter中调用我们自己写的程序:
首先创建一组数据,然后生成SimpleLinearRegression()的对象reg1,然后调用一下
from myAlgorithm.SimpleLinearRegression import SimpleLinearRegression
x = np.array([1.,2.,3.,4.,5.])
y = np.array([1.,3.,2.,3.,5,])
x_predict = np.array([6])
reg = SimpleLinearRegression()
reg.fit(x,y)
输出:SimpleLinearRegression()
reg.predict(x_predict)
reg.a_
reg.a_
输出:array([5.2]) 0.8 0.39999999999999947
y_hat = reg.predict(x)
plt.scatter(x,y)
plt.plot(x,y_hat,color='r')
plt.axis([0,6,0,6])
plt.show()
4.1多元线性回归
机器学习的建模推导思想:
- 通过分析问题,确定问题的损失函数或者效用函数,
- 然后通过最优化损失函数或者效用函数,获得机器学习模型,然后推导实现了最小二乘法,然后实现了简单线性回归,还有评价指标;均方根 MSE,均方根误差RMSE,平均对MAE和R方
对于下面的样本集
x
i
=
(
X
1
i
,
X
2
i
,
…
X
n
i
)
x^i =(X_1^i,X_2^i,…X_n^i)
xi=(X1i,X2i,…Xni)对应的是一个向量,每一行是一个样本,每列对应一个特征。对应的结果可以用如下公式:
y
=
θ
0
+
θ
1
x
1
+
θ
2
x
2
…
…
θ
n
x
n
y=θ_0+θ_1x_1+θ_2x_2^ ……θ_nx_n
y=θ0+θ1x1+θ2x2……θnxn
简单线性回归,只计算前两项,但是多元线性回归中就要学习到n+1个参数,就能求出多元线性回归预测值:
y
^
i
=
θ
0
+
θ
1
x
1
i
+
θ
2
x
2
i
…
+
θ
n
x
n
i
\hat{y}_i = θ_0+θ_1x_1^i+θ_2x_2^i…+θ_nx_n^i
y^i=θ0+θ1x1i+θ2x2i…+θnxni
也就是:第一个特征与参数一相乘,第二个特征与参数而相乘,累加后加上截距,得到预测值
求解思路与简单线性回归一直,目标同样是:
一直训练数据样本x,y找到
θ
0
,
θ
1
,
θ
2
…
θ
n
θ_0,θ_1,θ_2…θ_n
θ0,θ1,θ2…θn,使
∑
i
=
1
m
(
y
i
−
y
^
2
)
2
\frac{\sum_{i=1}^{m}}{}(y^i-\hat{y}^2)2
∑i=1m(yi−y^2)2尽可能小
其中
θ
=
(
θ
0
,
θ
1
,
θ
2
,
θ
2
…
θ
n
)
θ =(θ_0,θ_1,θ_2,θ_2…θ_n)
θ=(θ0,θ1,θ2,θ2…θn) 是指列向量列向量,而且我们也注意到,可以虚构第0个特征X0,令其恒等于1,结构更加整齐,方便:
y
^
i
=
θ
0
x
0
i
+
θ
1
x
1
i
+
θ
2
x
2
i
…
+
θ
n
x
n
i
\hat{y}_i = θ_0x_0^i+θ_1x_1^i+θ_2x_2^i…+θ_nx_n^i
y^i=θ0x0i+θ1x1i+θ2x2i…+θnxni
也可以写成向量点乘形式
此时可以得出:
y
^
=
X
b
.
θ
\hat{y} = X_b.θ
y^=Xb.θ
因此我们可以把目标写成向量化的形式:
已知训练数据样本
x
,
y
x,y
x,y,找到向量θ,使
(
y
−
x
b
.
θ
)
T
(
X
b
.
θ
)
(y-x_b.θ)T(X_b.θ)
(y−xb.θ)T(Xb.θ)尽可能小
推导出可以得到多元线性回归的正规方程解:
θ = ( X b T X b ) − 1 X b T y θ=(X_b^TX_b)^{-1}X_b^Ty θ=(XbTXb)−1XbTy
但是这种朴素的计算方法,缺点是时间复杂度较高:O(n^3),在特征比较多的时候,计算量很大。优点是不需要对数据进行归一化处理,原始数据进行计算参数,不存在量纲的问题(多选线性没必要做归一化处理)。
4.3多元线性回归实现
import numpy as np
from .metrics import r2_score
class LinearRegression:
def __init__(self):
"""初始化Linear Regression模型"""
self.coef_ = None # 系数(theta0~1 向量)
self.interception_ = None # 截距(theta0 数)
self._theta = None # 整体计算出的向量theta
def fit_normal(self, X_train, y_train):
"""根据训练数据X_train,y_train训练Linear Regression模型"""
assert X_train.shape[0] == y_train.shape[0], \
"the size of X_train must be equal to the size of y_train"
# 正规化方程求解
X_b = np.hstack([np.ones((len(X_train), 1)), X_train])
self._theta = np.linalg.inv(X_b.T.dot(X_b)).dot(X_b.T).dot(y_train)
self.interception_ = self._theta[0]
self.coef_ = self._theta[1:]
return self
def predict(self, X_predict):
"""给定待预测的数据集X_predict,返回表示X_predict的结果向量"""
assert self.interception_ is not None and self.coef_ is not None, \
"must fit before predict"
assert X_predict.shape[1] == len(self.coef_), \
"the feature number of X_predict must be equal to X_train"
X_b = np.hstack([np.ones((len(X_predict), 1)), X_predict])
y_predict = X_b.dot(self._theta)
return y_predict
def score(self, X_test, y_test):
"""很倔测试机X_test和y_test确定当前模型的准确率"""
y_predict = self.predict(self, X_test)
return r2_score(y_test, y_predict)
def __repr__(self):
return "LinearRegression()"
其实在代码中,思想很简单,就是使用公式即可。其中有一些知识点:
- 1、np.hstack(tup):参数tup可以是元组,列表,或者numpy数组,返回结果为numpy的数组。按列顺序把数组给堆叠起来(加一个新列)。
- 2、np.ones():返回一个全1的n维数组,有三个参数:shape(用来指定返回数组的大小)、dtype(数组元素的类型)、order(是否以内存中的C或Fortran连续(行或列)顺序存储多维数据)。后两个参数都是可选的,一般只需设定第一个参数。(类似的还有np.zeros()返回一个全0数组)
- 3、numpy.linalg模块包含线性代数的函数。使用这个模块,可以计算逆矩阵、求特征值、解线性方程组以及求解行列式等。inv函数计算逆矩阵
- 4、T:array的方法,对矩阵进行转置。
- 5、dot:点乘
调用:
import numpy as np
import matplotlib.pyplot as plt
from sklearn import datasets
boston = datasets.load_boston()
X = boston.data
y = boston.target
X = X[y<50.0]
y = y[y<50.0]
X.shape
输出:(490, 13)
y.shape
输出:(490, )
from myAlgorithm.model_selection import train_test_split
from myAlgorithm.LinearRegression import LinearRegression
X_train, X_test, y_train, y_test = train_test_split(X, y, seed = 666)
reg = LinearRegression()
reg.fit_normal(X_train, y_train)
reg.coef_
输出:
array([-1.18919477e-01, 3.63991462e-02, -3.56494193e-02, 5.66737830e-02,
-1.16195486e+01, 3.42022185e+00, -2.31470282e-02, -1.19509560e+00,
2.59339091e-01, -1.40112724e-02, -8.36521175e-01, 7.92283639e-03,
-3.81966137e-01])
reg.interception_
输出:
34.16143549622471
reg.score(X_test, y_test)
输出:
0.81298026026584658
我们看到,reg.coef_这一项的结果是13个系数,这13个系数有正有负。正负代表着该系数所乘的特征与预测目标是正相关还是负相关。正相关,特征越大房价越高;负相关,特征越大,房价越低。而系数绝对值的大小决定了影响程度。
下面我们对所有的系数按照数值由小到大进行排序:
np.argsort(reg.coef_)
输出:
array([ 4, 7, 10, 12, 0, 2, 6, 9, 11, 1, 3, 8, 5])
将这个返回结果作为索引,返回排序后索引所对应的特征名:
boston.feature_names[np.argsort(reg.coef_)]
输出:
array(['NOX', 'DIS', 'PTRATIO', 'LSTAT', 'CRIM', 'INDUS', 'AGE', 'TAX',
'B', 'ZN', 'CHAS', 'RAD', 'RM'], dtype='<U7')
这也说明了线性回归算法,具有可解释性。
到此为止,线性回归模型就介绍完了。线性回归模型有着比较清晰的数据推导过程,也是其他复杂模型的基础。线性回归算法是典型的参数学习。虽然线性回归只能解决回归问题,但是却是很多分类问题,如逻辑回归的基础。并且线性回归算法是假设数据是有一定的线性关系的,且线性关系越强,效果越好。
在第一节中得到的多元线性回归的正规方程解,看上去很简单,但是时间复杂度高。其实除了使用正规方程解以外,还可以使用大名鼎鼎的梯度下降法。梯度下降法不仅可以解决线性问题,更是解决机器学习的最优模型的通用算法。So,下面就是梯度下降的学习啦。