线性回归算法简介
- 解决回归问题
- 思想简单,容易实现
- 许多强大的非线性模型的基础
- 结果具有很好的
线性回归分为一元线性回归和多元线性回归。
- 一元线性回归类似于y = ax+b
- 多元线性回归类似于y = a1x1+a2x2+a3x3+...+b
映射到数据中就是该数据集的特征(列)是一个还是多个的。
线性回归算法以一个坐标系里一个维度为结果,其他维度为特征(如 二维平面坐标系中横轴为特征,纵轴为结果),无数的训练集放在坐标系中,发现他们是围绕着一条执行分布。线性回归算法的期望,就是寻找一条直线,最大程度的“拟合”样本特征和样本输出标记的关系
样本特征只有一个的线性回归问题,为简单线性回归,如房屋价格-房屋面积。
将横坐标作为x轴,纵坐标作为y轴,每一个点为(X(i) ,y(i)),那么我们期望寻找的直线就是y=ax+b,当给出一个新的点x(j)的时候,我们希望预测的y^(j)=ax(j)+b
- 不适用相减的方式,由于差值有正有负,正负会抵消,有可能会降低。
- 不使用绝对值的方式,因为在数学中求最值时的方式之一就是求导,绝对值存在不可导的点,所以不能用。
- 最好使用平方的方式。
上述的a和b按照公式都是可以计算的。
通过上面的推导,我们可以归纳出一类机器学习算法的基本思路,如下图;其中损失函数是计算期望值和预测值的差值,期望其差值(也就是损失)越来越小,而效用函数则是描述拟合度,期望契合度越来越好
2.简单线性回归的最小二乘法推导过程
1.公式推导过程
1.将损失函数看作以a,b为未知量的一个名为J的函数
2.要求损失函数的最小值,因为其有两个未知量,所以要对每一个未知量求偏导(将另一个未知量看作常量),使偏导结果=0。
数学知识:复合函数求导时,将函数分为内外函数,内函数看作一个未知量整体t,函数整体对t求导后的值乘以内函数t对其内部的未知量的求导。例如:
y = (2x-3)*2
求导结果:y(导) = 2(2x-3)2 = 8x-12
3.损失函数J(a,b)对b求偏导:
4.分解b偏导函数:
那么b的值就计算出来了:
接下来求a,b就成为了已知条件了,求a的时候需要带入。
5.J(a,b)对a求偏导
6.对a得偏导函数解析:
那么最终:
2.代码实现简单线性回归法
1.正常逻辑实现
- 创建数据集x,y,组织数据结构--(list(zip(x,y))),方便获取数据
- 计算平均值(也可以在下一步中得公式中计算)
- 根据公式计算模型参数a,b
- 根据a,b的值获取预测值y_head
- 画出y_head和x,y和x的散点图和直线图。
import numpy as np
import matplotlib.pyplot as plt
# 创建数据集
x = np.array([1., 2., 3., 4., 5.])
y = np.array([1., 3., 2., 3., 5.])
# 执行拉链操作
z = list(zip(x,y))
print(z)
# [(1.0, 1.0), (2.0, 3.0), (3.0, 2.0), (4.0, 3.0), (5.0, 5.0)]
# x中的平均值
x_mean = np.mean(x)
# y中的平均值
y_mean = np.mean(y)
# 计算线性回归得参数a
num = 0.0 # 分子
d = 0.0 # 分母
for x_i,y_i in z:
num += (x_i-x_mean) * (y_i - y_mean)
d += (x_i - x_mean)**2
a = num / d
print(a) # 0.8
# 计算b
b = y_mean - a*x_mean
print(b)
# 0.39999999999999947
# 预测值y_head
y_head = a*x + b
print(y_head)
# 0.39999999999999947
# 画出真实值得散点图
plt.scatter(x,y)
plt.plot(x,y_head,color='r')
plt.axis([0, 6, 0, 6]) # 设置x,y轴的范围
plt.show()
2.封装为类实现
在封装为类的时候,我们所想要的结果不是画图,而是获取预测值y_head,无论是单个点还是多个点,结果是根据训练出来的线性回归方程计算出对应点的y值---y_head.
- fit(X_train,y_train):根据训练数据计算斜率和截距,也就是训练线性回归模型。
- perdict(x_test):根据模型(回归方程),带入测试数据,进行预测。
from lineCome.line_2_ import SimpleLinearRegression1
import numpy as np
import matplotlib.pyplot as plt
reg1 = SimpleLinearRegression1()
# 训练数据集
x = np.array([1., 2., 3., 4., 5.])
y = np.array([1., 3., 2., 3., 5.])
reg1.fit(x,y)
# 测试数据
test = np.array([2,4])
result = reg1.perdict(test)
print(result) # x = np.array([1., 2., 3., 4., 5.])
# 打印斜率
print(reg1.a_)
# 打印截距
print(reg1.b_)
# 计算预测曲线
y_hat = reg1.a_ * x + reg1.b_
plt.scatter(x,y)
plt.plot(x,y_hat,color='r')
plt.axis([0,6,0,6])
plt.show()
3.向量化
在上边的步骤中我们计算a的值使用的是for循环,for循环的效率非常的低,但是如果numpy中的向量进行操作时,效率会变得很好。
假设w,v是一个行向量,一个列向量,在线性代数中两个向量的点乘计算w*v(w和v的点乘)就是:
w(.)v = w1*v1+w2*v2+w3*v3+....wi*vi
那么上述的表达式也就是:
接下来我们要分解a得表达式,用向量的方式表达出来:
因为x(i)是一维向量,那么它广播操作减去x(均)后还是一维向量,y也是如此,在根据上边的推导,所以:
这样的话我们直接算出w和v,在使用点乘,就可以计算出a。
4.向量化实现SimpleLinearRegression
import numpy as np
class SimpleLinearRegression2:
def __init__(self):
self.a_ = None
self.b_ = None
# 训练模型
def fit(self,X_train,y_train):
assert X_train.ndim == 1, \
"简单线性回归只能解决单个特征对应的线性回归"
assert len(X_train) == len(y_train), \
"X_train和y_train的长度必须相同,一一对应"
# 计算均值
x_mean = np.mean(X_train)
y_mean = np.mean(y_train)
w = X_train - x_mean
v = y_train - y_mean
self.a_ = w.dot(v) / w.dot(w)
self.b_ = y_mean - self.a_ * x_mean
# 预测函数
def perdict(self, X_predict):
'''
:param X_predict: 多个点的x轴的值
:return: 多个点对应的预测值
'''
"""给定待预测集x_predict,返回x_predict对应的预测结果值"""
assert X_predict.ndim == 1, \
"简单线性回归只能解决单个特征对应的线性回归"
assert self.a_ is not None and self.b_ is not None, \
"先执行fit方法进行训练"
Y_head = [self._perdict(x) for x in X_predict]
return np.array(Y_head)
def _perdict(self, x_perdict):
'''
:param x_perdict: 单个点的x值
:return: y_head:该店对应的预测值
'''
y_head = self.a_ * x_perdict + self.b_
return y_head
def __repr__(self):
return "SimpleLinearRegression1"
Mian:
from lineCome.line_4_ import SimpleLinearRegression2
import numpy as np
import matplotlib.pyplot as plt
reg1 = SimpleLinearRegression2()
# 训练数据集
x = np.array([1., 2., 3., 4., 5.])
y = np.array([1., 3., 2., 3., 5.])
reg1.fit(x,y)
# 测试数据
test = np.array([2,4])
result = reg1.perdict(test)
print(result) # x = np.array([1., 2., 3., 4., 5.])
# 打印斜率
print(reg1.a_)
# 打印截距
print(reg1.b_)
# 计算预测曲线
y_hat = reg1.a_ * x + reg1.b_
plt.scatter(x,y)
plt.plot(x,y_hat,color='r')
plt.axis([0,6,0,6])
plt.show()
结果:
5.向量化性能测试
我们不使用向量化的计算公式与使用向量化计算公式之间对比:
他们之间的区别就是在各自类中的fit方法中计算a和b时计算方式不同,所以我们测试一下各自的时间:
不使用--SimpleLinearRegression1类:
import time
import numpy as np
from lineCome.line_2_ import SimpleLinearRegression1
m = 1000000
# 随机数x
big_x = np.random.random(size=m)
print(big_x[:20])
# 设置线性回归方程--------y = 2x+3+noise
big_y = big_x * 2 + 3 + np.random.normal(size=m)
# print(big_y[:20])
s1 = SimpleLinearRegression1()
start = time.time()
s1.fit(big_x,big_y)
end = time.time()
print(end-start) # 1.46022367477417
re = s1.a_
print(re) # 1.9973255866627704
使用----SimpleLinearRegression2()
import time
import numpy as np
from lineCome.line_4_ import SimpleLinearRegression2
m = 1000000
# 随机数x
big_x = np.random.random(size=m)
print(big_x[:20])
# 设置线性回归方程--------y = 2x+3+noise
big_y = big_x * 2 + 3 + np.random.normal(size=m)
# print(big_y[:20])
s2 = SimpleLinearRegression2()
start = time.time()
s2.fit(big_x,big_y)
end = time.time()
print(end-start) # 0.04913735389709473
re = s2.a_
print(re) # 2.001026468973155
肉眼可见他们呢两个的时间差别很大,虽然结果都对,但是效率差别很大。
3. 衡量线性回归算法的指标
那么我们在算出预测数据后,如果怎么衡量这个预测数据和真实数据之间的误差呢?
y_head为预测数据,y为真实数据,那么就要计算m个数据的总误差为多少,该误差越小,说明这个正确率越高。
其中衡量标准是和m有关的,因为越多的数据量产生的误差和可能会更大,但是毫无疑问越多的数据量训练出来的模型更好,这是一个矛盾,为此需要一个取消误差的方法,如下:
为了消除矛盾,让结果除样本个数m:
但是MSE 的缺点:量纲不准确,如果y的单位是万元,平方后就变成了万元的平方,这可能会给我们带来一些麻烦。
那么对其直接开方:
RMSE 平方累加后再开根号,如果某些预测结果和真实结果相差非常大,那么RMSE的结果会相对变大,所以RMSE有放大误差的趋势,而MAE没有,他直接就反应的是预测结果和真实结果直接的差距,正因如此,从某种程度上来说,想办法我们让RMSE变的更小小对于我们来说比较有意义,因为这意味着整个样本的错误中,那个最值相对比较小,而且我们之前训练样本的目标,就是RMSE根号里面1/m的这一部分,而这一部分的本质和优化RMSE是一样的.
1.衡量回归算法的标准,MSE vs RMSEvs MAE
1.波士顿房价数据
import numpy as np
import matplotlib.pyplot as plt
from sklearn import datasets
# 加载boston房价
boston = datasets.load_boston()
key = boston.keys()
print(key)
# dict_keys(['data', 'target', 'feature_names', 'DESCR', 'filename'])
descr = boston.DESCR
# print(descr)
# 选择rm作为x轴:- RM average number of rooms per dwelling
# 获取房间数量那一列,也就是x轴
x = boston.data[:,5]
print(x.shape) # (506, 13)
# 获取结果---房价target,也就是y轴
y = boston.target
print(y.shape) # (506,)
plt.scatter(x,y)
plt.show()
运行:
去掉上边一行的不合理的数据:
x = x[y < 50.0]
y = y[y < 50.0]
print(x.shape)
print(y.shape)
# (490,)
# (490,)
plt.scatter(x,y)
plt.show()
2.使用简单线性回归测试
我们使用之前的向量化的计算方法--SimpleLinearRegression2和之前的切割方法train_test_split()
from KNN.Knn_2iris02.train_split_def import train_test_split
from lineCome.line_4_ import SimpleLinearRegression2
x_train, y_train,x_test,y_test = train_test_split(x, y,seed=666)
slr = SimpleLinearRegression2()
slr.fit(x_train,y_train)
slr.perdict(x_test)
print(slr.a_)
print(slr.b_)
# 8.114503987379523
# -29.063884965339557
# 训练集训练出来的模型直线
y_head = slr.perdict(x_train)
#
plt.scatter(x_train,y_train)
plt.scatter(x_test,y_test)
plt.plot(x_train,y_head,color='r')
plt.show()
运行:
使用测试集预测的直线:
y_head1 = slr.perdict(x_test)
plt.scatter(x_train,y_train)
plt.scatter(x_test,y_test)
plt.plot(x_test,y_head1,color='r')
plt.show()
运行:
测试三种误差标准:
我们要算的误差是测试集的真实数据与测试集的训练模型直线之间的误差。
1.MSE
mse_test = np.sum((y_head1 - y_test)**2) / len(y_test)
print(mse_test)
# 19.90740355057513
2.RMSE
rmse_test = sqrt(mse_test)
print(rmse_test)
# 4.461771346738325
3.MAE
# np.absolute():求绝对值
mae_test = np.sum(np.absolute(y_head1 - y_test))/len(y_test)
print(mae_test)
# 3.2564875706918865
3.将三种误差计算封装为函数
我们可以新建一个py文件直接定义封装这些函数,也可以在线性回归算法中封装为成员方法:
class SimpleLinearRegression2:
...
...
def mean_squared_error(self,y_true, y_predict):
"""计算y_true和y_predict之间的MSE"""
assert len(y_true) == len(y_predict), \
"the size of y_true must be equal to the size of y_predict"
return np.sum((y_true - y_predict) ** 2) / len(y_true)
def root_mean_squared_error(self,y_true, y_predict):
"""计算y_true和y_predict之间的RMSE"""
return sqrt(self.mean_squared_error(y_true, y_predict))
def mean_absolute_error(self,y_true, y_predict):
"""计算y_true和y_predict之间的MAE"""
return np.sum(np.absolute(y_true - y_predict)) / len(y_true)
调用:
'''MSE'''
mse_test = np.sum((y_head1 - y_test)**2) / len(y_test)
print(mse_test)
# 19.90740355057513
result = slr.mean_squared_error(y_test,y_head1)
print(result)
# 19.90740355057513
'''RMSE'''
from math import sqrt
rmse_test = sqrt(mse_test)
print(rmse_test)
# 4.461771346738325
result = slr.root_mean_squared_error(y_test,y_head1)
print(result)
# 4.461771346738325
'''MAE'''
# np.absolute():求绝对值
mae_test = np.sum(np.absolute(y_head1 - y_test))/len(y_test)
print(mae_test)
# 3.2564875706918865
result = slr.mean_absolute_error(y_test,y_head1)
print(result)
# 3.2564875706918865
4.scikit-learn中的MSE和MAE
from sklearn.metrics import mean_squared_error
from sklearn.metrics import mean_absolute_error
'''MSE'''
mse_test = np.sum((y_head1 - y_test)**2) / len(y_test)
print(mse_test)
# 19.90740355057513
result = slr.mean_squared_error(y_test,y_head1)
print(result)
# 19.90740355057513
re = mean_squared_error(y_test, y_head1)
print(re)
# 19.90740355057513
'''MAE'''
# np.absolute():求绝对值
mae_test = np.sum(np.absolute(y_head1 - y_test))/len(y_test)
print(mae_test)
# 3.2564875706918865
result = slr.mean_absolute_error(y_test,y_head1)
print(result)
# 3.2564875706918865
re = mean_absolute_error(y_test, y_head1)
print(re)
# 3.2564875706918865
可以看到结果都是一样的,但是这个结果我们看不出来好与不好,只知道越小越好。
2.最好的衡量线性回归法的指标 R Squared
RMSE 和 MAE的局限性
可能预测房源准确度,RMSE或者MAE的值为5,预测学生的分数,结果的误差是10,这个5和10没有判断性,因为5和10对应不同的单位和量纲,无法比较
1.解决办法-R Squared简介
R Squared 公式的分析:
分式上边的是MSE*m,下边的是使用平均值减去预测值的预测方法。
由此可知:分式下边的值,也就是使用BaseLine Model产生的错误会很大,使用我们的模型(上边)预测产生的错误会相对少些(因为我们的模型充分的考虑了y和x之间的关系),所以,当R^2越趋近于1,说明我们的模型越好。
对公式进行变换,便于代码实现:
2.实现R Squared (R^2)
class SimpleLinearRegression2:
......
......
'''R^2'''
def r2_score(self,y_true,y_perdict):
r2_score = 1 - self.mean_squared_error(y_true, y_perdict) / np.var(y_true)
return r2_score
'''准确度---R^2'''
def score(self, x_test, y_test):
"""根据测试数据集 x_test 和 y_test 确定当前模型的准确度"""
y_predict = self.perdict(x_test)
return self.r2_score(y_test, y_predict)
我们直接将计算R^2 的公式封装在这个线性回归的类中---r2_score(),该方法接受测试集的预测值和测试集的真实值,返回R^2的值
再添加一个正确率函数--score(),score()实际上就是r2_score()的再此封装,该方法接受测试集数据和测试集真实值,直接返回R^2的值。
调用:
还有一种就是sklearn中自带的r2_score(),该方法与我们自定义的方法参数列表一样,只是我们定义在类中,它定义在外边,可以直接调用。
r2_score = 1-slr.mean_squared_error(y_test,y_head_test) / np.var(y_test)
print(r2_score)
# 0.4970173772960619
r2_score = slr.r2_score(y_test,y_head_test)
print(r2_score)
# 0.4970173772960619
from sklearn.metrics import r2_score
r2_score = r2_score(y_test,y_head_test)
print(r2_score)
# 0.4970173772960619
result = slr.score(x_test,y_test)
print(result)
# 0.4970173772960619
可以看到结果都一样低的可怕,,满分一百的话,这个分数刚及格,所以,我们可以知道这个房间数量和房价之间的线性回归并不明显,因为影响房价的并不只是房间数量,所以分数不会高。
接下来我们学习多元线性回归。