目录
0x00 简单的线性回归
0x01 最小二乘法(高数复习)
0x02 简单线性回归的编程实现
0x03 向量化
0x04 衡量线性回归法的指标:MSE,RMSE,MAE
0x05 最好的衡量线性回归发的指标R Squared
0x06 多元线性回归
0x07 实现多元线性回归
0x08 使用 scikit-learn 解决回归问题
0x09 线性回归模型的可解释性
线性回归算法总结:
0x00 简单的线性回归
思路:
假设样本特征 和 样本标记 之间存在线性关系,我们只要寻找到一条直线,可以最大程度的拟合样本特征和样本标记之间的关系,就可以使用该直线 对一个新样本的标记进行预测
例如:房屋面积和价格之间就可能存在这样一条直线,我们只要找到该直线方程,输入新样本的房屋面积就可以预测出房屋的价格
注意线性回归问题和线性分类问题坐标轴的区别:
分类问题中,横轴和纵轴都是样本特征,
回归问题中,纵轴是样本标记,横轴是样本特征
因为在回归问题中,样本特征并不是离散的,而是连续的数值,所以不能用不同颜色来表示,只能用一个坐标轴来表示
那么什么是简单线性回归呢?
样本特征只有一个的线性回归,我们称为简单线性回归
以房屋面积和房屋价格之间的线性回归问题为例,我们求解下该问题的最佳拟合直线方程
这里之所以用平方距离,而不是绝对值距离,是因为绝对值难以求导,不容易求极值。
以上过程体现了一类机器学习算法的基本思路:
机器学习建模其实就是找到一个模型最大程度拟合我们的数据,而找到一个模型就是在找一个最优化的目标函数。
而目标函数可以分为两类:
损失函数:衡量 准确度损失,我们希望尽可能的小
效用函数:衡量 算法的准确度,我们希望尽可能的大
通过分析问题,我们可以确定问题的损失函数或者效用函数,通过最优化损失函数或者效用函数,获得机器学习的模型。
其中最优化损失函数,往往是在求该损失函数的最小值。
最优化效用函数,往往是在求该效用函数的最大值。
近乎所有参数学习算法(线性回归、多项式回归、逻辑回归、SVM、神经网络等)都是这样的套路。
0x01 最小二乘法(高数复习)
因为线性回归问题具有唯一的极值点,所以求该函数的最小值就可以直接转化为求该函数的极值点的问题:
多元函数求极值点,只要对每个元求偏导,让偏导等于 0, 即可求出该多元函数的极值点处 每个元对应的值
先给出推导结果如下:
以下是具体的推导过程:
0x02 简单线性回归的编程实现
封装成类库:
# 简单线性回归
import numpy as np
class SimpleLinearRegression1:
def __init__(self):
# 命名规范:下划线结尾,因为a和b不是用户送进来的参数,而是我们算法计算出来的
self.a_ = None
self.b_ = None
def fit(self, x_train, y_train):
assert 1 == x_train.ndim,\
"x_train必须是一个向量"
assert len(x_train) == len(y_train),\
"x_train 和 y_train 元素个数必须相等"
num = 0.0 # 分子初始化
d = 0.0 # 分母初始化
x_mean = np.mean(x_train)
y_mean = np.mean(y_train)
for x_i, y_i in zip(x_train, y_train): # 将x,y先打包成元组列表
num += (x_i - x_mean)*(y_i-y_mean)
d += (x_i - x_mean)**2
self.a_ = num / d
self.b_ = y_mean - self.a_ * x_mean
return self
# 给定预测数据集x_predict,返回表示y_predict 的结果向量
def predict(self, x_predict):
assert 1 == x_predict.ndim,\
"x_predict必须是一个向量"
assert self.a_ is not None and self.b_ is not None,\
"predict前请先fit"
return np.array([self._predict(x) for x in x_predict])
def _predict(self,x_single):
y_predict = self.a_ * x_single +self.b_
return y_predict
测试类库:
0x03 向量化
向量点乘 = 两个向量对应的元素相乘再相加 ,其实就是 向量版的矩阵乘法
线性代数(同济版)p31页,一个1xs行的矩阵 和 一个sx1列的矩阵相乘 就是一个1阶方阵,也就是一个数
这样一来就可以将需要用for循环来算的累加 转化为 numpy中向量点乘,而后者的速度要比前者快很多倍
只需要修改fit函数中的for循环部分:
def fit(self, x_train, y_train):
assert 1 == x_train.ndim,\
"x_train必须是一个向量"
assert len(x_train) == len(y_train),\
"x_train 和 y_train 元素个数必须相等"
num = 0.0 # 分子初始化
d = 0.0 # 分母初始化
x_mean = np.mean(x_train)
y_mean = np.mean(y_train)
# for x_i, y_i in zip(x_train, y_train): # 将x,y先打包成元组列表
# num += (x_i - x_mean)*(y_i-y_mean)
# d += (x_i - x_mean)**2
num = (x_train-x_mean).dot(y_train-y_mean) #1xs的行向量 * sx1的列向量
d = (x_train-x_mean).dot(x_train-x_mean)
self.a_ = num / d
self.b_ = y_mean - self.a_ * x_mean
return self
性能测试:性能上几乎提升了150倍
0x04 衡量线性回归法的指标:MSE,RMSE,MAE
对于简单线性回归来说:
但是上述衡量标准是和m相关的,所以我们需要将m消掉
但是上述衡量标准也有一个问题,比如说y的量纲是元,那么最终MES的量纲就是 元的平方,很不舒服
所以我们再开根号,让衡量标准的量纲和y的量纲保持一致,让误差的意义更加明显
除此之外,还有一个更加直白的评测标准:
编程实现:
封装成函数:metrics.py
import numpy as np
from math import sqrt
#计算y_true和y_predict的重合度
def accuracy_score(y_true,y_predict):
return sum(y_true == y_predict)/len(y_true)
#mse
def mean_squared_error(y_true,y_predict):
return np.sum((y_true-y_predict)**2) / len(y_true)
#rmse
def root_mean_squared_error(y_true,y_predict):
return sqrt(mean_squared_error(y_true,y_predict))
#mae
def mean_absolute_error(y_true,y_predict):
return np.sum(np.absolute(y_true-y_predict)) / len(y_true)
为什么RMSE 比MAE 大?
因为RMSE 先做了平方,所以RMSE有放大误差的趋势,特别是最大误差值的占比会扩大
0x05 最好的衡量线性回归发的指标R Squared
我们之前衡量分类准确度:1 最好 0 最差,但是mse、amse、mae等衡量指标却没有这样的性质
def r2_score(y_true,y_predict):
return 1-mean_squared_error(y_true,y_predict) / np.var(y_true)
0x06 多元线性回归
多元线性回归可以理解为简单线性回归的推广,将原来 y = ax +b
推广到 :
求解多元线性回归的思路 和简单线性回归也是一样的:
使预测结果 结果和真实结果 之间的差距尽可能的小
只不过:
为了使这个式子推导起来更加方便,我们可以给x(i) 虚构一个第0个特征
该特征恒等于1,这样就可以将下式子变形为:
将θ 和 X(i)都表示成向量形式之后,我们就可以发现:
y(i)冒 不就是是 θ 和X(i) 进行矩阵乘法的结果吗?
对于第i个样本的预测值有如上公式,那么我们还可以进一步推广到所有样本上
注意:这里我们将 原来的参数矩阵称为X,在原来参数的基础上添加一列1得到的矩阵称为Xb
这里的推倒过程已经超过了本科线性代数的范围,这里暂时省略推倒过程
直接给出结果,这个结果不需要背,需要用的时候百度就可以
0x07 实现多元线性回归
import numpy as np
from .metrics import r2_score
class LinearRegression:
def __init__(self):
self.coef_ = None #系数
self.interception = None # 截距
self._theta = None #θ
def fit_normal(self,X_train,y_train):
assert X_train.shape[0] == y_train.shape[0],\
"X_train中的样本数量和y_train中的标记数量必须相等"
X_b =np.hstack([np.ones((len(X_train),1)),X_train])
#给X_train左边加上1列,全是1 => np.ones( (len(X_train),1) ) 行数和X_train一样,列数为1
#直接套用公式
self._theta = np.linalg.inv(X_b.T.dot(X_b)).dot(X_b.T).dot(y_train)
#复习一下:np.linalg.inv() 求矩阵的逆
self.interception = self._theta[0] #截距就是θ的第一个元素
self.coef_ = self._theta[1:]
return self
def predict(self,X_predict):
assert self.interception is not None and self.coef_ is not None,\
"在predict之前请先fit"
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):
y_predict = self.predict(X_test)
return r2_score(y_test,y_predict)
def __repr__(self):
return "LinearRegression()"
0x08 使用 scikit-learn 解决回归问题
使用knn算法同样可以解决线性回归问题:
knn算法有许多超参数,我们用网格搜索 选出最佳超参数,看能不能提高预测准确度
注意:网格搜索在选择最佳超参数的时候评价标准并不是 r2_score ,而是使用交叉验证的方法,所以
选择出来的超参数不见得是r2_score标准下预测准确率最高的超参数。
0x09 线性回归模型的可解释性
系数的正负表示正相关还是负相关,系数的大小代表了该特征对结果影响程度的大小
具体各个特征表示什么意义如下:
线性回归算法总结:
线性回归算法是典型的参数学习
而kNN算法是典型的非参数学习
线性回归算法只能解决回归问题,虽然很多分类方法中,线性回归是基础(例如:逻辑回归)
对比kNN算法:既可以解决分类问题 又可以解决回归问题
我们在使用线性回归算法时,其实是对数据有一个假设的:我们假设特征矩阵 和 结果向量之间存在一个线性关系。
对比kNN算法:我们对数据没有任何假设
线性回归算法是一个白盒子算法:线性回归算法的结果具有很强的解释性,我们可以真正从中学到知识
上述使用的线性回归算法使用的是 正规方程解:
可以用 梯度下降法 进一步优化