1.数学原理

1801年,意大利天文学家朱赛普·皮亚齐发现了第一颗小行星谷神星。经过40天的跟踪观测后,由于谷神星运行至太阳背后,使得皮亚齐失去了谷神星的位置。随后全世界的科学家利用皮亚齐的观测数据开始寻找谷神星,但是根据大多数人计算的结果来寻找谷神星都没有结果。只有时年24岁的高斯所计算的谷神星的轨道,被奥地利天文学家海因里希·奥尔伯斯的观测所证实,使天文界从此可以预测到谷神星的精确位置。同样的方法也产生了哈雷彗星等很多天文学成果。高斯使用的方法就是最小二乘法,该方法发表于1809年他的著作《天体运动论》中 。其实法国科学家勒让德于1806年独立发明“最小二乘法”,但因不为世人所知而默默无闻 。

这里涉及到的数学原理是“最小二乘法”,更底层的数学基础是 偏导数
大陆地区翻译成“最小二乘法”,台湾地区翻译成“最小平方法”。

image.png

  • 预测值拟合直线为: 01 一元线性回归原理及实现 - 图2
  • 实际值为 : 01 一元线性回归原理及实现 - 图3

为了让拟合直线和贴合数据,希望 01 一元线性回归原理及实现 - 图4的值尽量小,但是01 一元线性回归原理及实现 - 图5可能是负值, 因此01 一元线性回归原理及实现 - 图6可以表示两者的差距,但是为了方便计算采用平方的形式: 01 一元线性回归原理及实现 - 图7,因为后面要用偏导数计算。

  • 目标:使01 一元线性回归原理及实现 - 图8尽可能小。即找到a和b,使得01 一元线性回归原理及实现 - 图9尽可能小。

01 一元线性回归原理及实现 - 图10也叫损失函数(loss function)。
某类机器学习的思路:通过最优化损失函数或者效用函数,获得机器学习模型。

  • 参数表达式

导数为0的地方,就是极值的地方。(分别对a ,b求偏导为0时,可得:)
01 一元线性回归原理及实现 - 图1101 一元线性回归原理及实现 - 图12

2.编码实现

准备数据

  1. import numpy as np
  2. import matplotlib.pyplot as plt
  3. x = np.array([1., 2., 3., 4., 5.])
  4. y = np.array([1., 3., 2., 3., 5.])
  5. plt.scatter(x, y)
  6. plt.axis([0, 6, 0, 6])
  7. plt.show()

image.png

建模

  1. # 求x,y的均值
  2. x_mean = np.mean(x)
  3. y_mean = np.mean(y)
  4. # 求系数a,b
  5. num = 0.0
  6. d = 0.0
  7. for x_i, y_i in zip(x, y):
  8. num += (x_i - x_mean) * (y_i - y_mean)
  9. d += (x_i - x_mean) ** 2
  10. a = num/d
  11. b = y_mean - a * x_mean
  12. y_hat = a * x + b
  13. plt.scatter(x, y)
  14. plt.plot(x, y_hat, color='r')
  15. plt.axis([0, 6, 0, 6])
  16. plt.show()

image.png

预测

  1. x_predict = 6
  2. y_predict = a * x_predict + b
  3. print(y_predict) # 5.2

3.模型封装

  1. class SimpleLinearRegression1:
  2. def __init__(self):
  3. """初始化Simple Linear Regression 模型"""
  4. self.a_ = None
  5. self.b_ = None
  6. def fit(self, x_train, y_train):
  7. """根据训练数据集x_train,y_train训练Simple Linear Regression模型"""
  8. assert x_train.ndim == 1, \
  9. "Simple Linear Regressor can only solve single feature training data."
  10. assert len(x_train) == len(y_train), \
  11. "the size of x_train must be equal to the size of y_train"
  12. x_mean = np.mean(x_train)
  13. y_mean = np.mean(y_train)
  14. num = 0.0 # num为表达式的分子
  15. d = 0.0 # d 为表达式的分母
  16. for x, y in zip(x_train, y_train):
  17. num += (x - x_mean) * (y - y_mean)
  18. d += (x - x_mean) ** 2
  19. self.a_ = num / d
  20. self.b_ = y_mean - self.a_ * x_mean
  21. return self
  22. def predict(self, x_predict):
  23. """给定待预测数据集x_predict,返回表示x_predict的结果向量"""
  24. assert x_predict.ndim == 1, \
  25. "Simple Linear Regressor can only solve single feature training data."
  26. assert self.a_ is not None and self.b_ is not None, \
  27. "must fit before predict!"
  28. return np.array([self._predict(x) for x in x_predict])
  29. def _predict(self, x_single):
  30. """给定单个待预测数据x,返回x的预测结果值"""
  31. return self.a_ * x_single + self.b_
  32. def __repr__(self):
  33. return "SimpleLinearRegression1()"
  1. # 使用封装的模型
  2. from playML.SimpleLinearRegression import SimpleLinearRegression1
  3. x = np.array([1., 2., 3., 4., 5.])
  4. y = np.array([1., 3., 2., 3., 5.])
  5. reg1 = SimpleLinearRegression1()
  6. reg1.fit(x, y) # 建模:得到参数 reg1.a_ = 0.8 , reg1.b_ = 0.4
  7. # 预测单个值
  8. x_predict = 6
  9. reg1.predict(np.array([x_predict])) # array([5.2])
  10. y_hat1 = reg1.predict(x)
  11. # 预测多个值
  12. y_hat1 = reg1.predict(x) # array([1.2, 2. , 2.8, 3.6, 4.4])
  13. # 可视化
  14. plt.scatter(x, y)
  15. plt.plot(x, y_hat1, color='r')
  16. plt.axis([0, 6, 0, 6])
  17. plt.show()

image.png

4.模型优化

提升性能:向量化代替for循环(计算机的算法优化)

  1. class SimpleLinearRegression2:
  2. def __init__(self):
  3. """初始化Simple Linear Regression模型"""
  4. self.a_ = None
  5. self.b_ = None
  6. def fit(self, x_train, y_train):
  7. """根据训练数据集x_train,y_train训练Simple Linear Regression模型"""
  8. assert x_train.ndim == 1, \
  9. "Simple Linear Regressor can only solve single feature training data."
  10. assert len(x_train) == len(y_train), \
  11. "the size of x_train must be equal to the size of y_train"
  12. x_mean = np.mean(x_train)
  13. y_mean = np.mean(y_train)
  14. # 向量化代替for循环
  15. self.a_ = (x_train - x_mean).dot(y_train - y_mean) / (x_train - x_mean).dot(x_train - x_mean)
  16. self.b_ = y_mean - self.a_ * x_mean
  17. return self
  18. def predict(self, x_predict):
  19. """给定待预测数据集x_predict,返回表示x_predict的结果向量"""
  20. assert x_predict.ndim == 1, \
  21. "Simple Linear Regressor can only solve single feature training data."
  22. assert self.a_ is not None and self.b_ is not None, \
  23. "must fit before predict!"
  24. return np.array([self._predict(x) for x in x_predict])
  25. def _predict(self, x_single):
  26. """给定单个待预测数据x_single,返回x_single的预测结果值"""
  27. return self.a_ * x_single + self.b_
  28. def __repr__(self):
  29. return "SimpleLinearRegression2()"
  1. from playML.SimpleLinearRegression import SimpleLinearRegression2
  2. reg2 = SimpleLinearRegression2()
  3. reg2.fit(x, y) # 建模:得到参数 reg1.a_ = 0.8 , reg1.b_ = 0.4
  4. # 预测多个值
  5. y_hat2 = reg2.predict(x)
  6. # 可视化
  7. plt.scatter(x, y)
  8. plt.plot(x, y_hat2, color='r')
  9. plt.axis([0, 6, 0, 6])
  10. plt.show()

image.png

5.性能测试

  1. m = 1000000 # 百万级数据
  2. big_x = np.random.random(size=m)
  3. big_y = big_x * 2 + 3 + np.random.normal(size=m)
  4. %timeit reg1.fit(big_x, big_y) # 耗时1秒
  5. # 1.02 s ± 26.9 ms per loop (mean ± std. dev. of 7 runs, 1 loop each)
  6. %timeit reg2.fit(big_x, big_y) # 耗时14毫秒
  7. # 14.2 ms ± 91.4 µs per loop (mean ± std. dev. of 7 runs, 100 loops each)
  8. reg1.a_ # 2.0001613808322065
  9. reg1.b_ # 3.002296137768425
  10. reg2.a_ # 2.000161380832136
  11. reg2.b_ # 3.00229613776846

6.评价指标

常用指标

MSERMSEMAE
同样一个线性回归任务,使01 一元线性回归原理及实现 - 图17尽可能小,就能评价算法的优劣吗?
显然不能,因为算法的数据规模不同,误差也因此可能不同,因此得到的模型也无法比较优劣。

MSE

Mean Squared Errod 均方误差
01 一元线性回归原理及实现 - 图18

RMSE

Root Mean Squared Errod 均方根误差
01 一元线性回归原理及实现 - 图19
优点:相比MSE的单位为平方,RMSE的单位和原数据保持一致。

MAE

Mean Absolute Errod 平均绝对误差
01 一元线性回归原理及实现 - 图20
优点:相比MSE的单位为平方,MAE的单位和原数据保持一致。

编码实现

  1. # model_selection.py
  2. # 作用: 切分训练集和测试集
  3. import numpy as np
  4. def train_test_split(X, y, test_ratio=0.2, seed=None):
  5. """将数据 X 和 y 按照test_ratio分割成X_train, X_test, y_train, y_test"""
  6. assert X.shape[0] == y.shape[0], \
  7. "the size of X must be equal to the size of y"
  8. assert 0.0 <= test_ratio <= 1.0, \
  9. "test_ration must be valid"
  10. if seed:
  11. np.random.seed(seed)
  12. shuffled_indexes = np.random.permutation(len(X))
  13. test_size = int(len(X) * test_ratio)
  14. test_indexes = shuffled_indexes[:test_size]
  15. train_indexes = shuffled_indexes[test_size:]
  16. X_train = X[train_indexes]
  17. y_train = y[train_indexes]
  18. X_test = X[test_indexes]
  19. y_test = y[test_indexes]
  20. return X_train, X_test, y_train, y_test
  1. import numpy as np
  2. import matplotlib.pyplot as plt
  3. from sklearn import datasets
  4. # 波士顿房产数据
  5. boston = datasets.load_boston()
  6. boston.keys() # dict_keys(['data', 'target', 'feature_names', 'DESCR'])
  7. # print(boston.DESCR) # 查看数据集详情
  8. boston.feature_names # array(['CRIM', 'ZN', 'INDUS', 'CHAS', 'NOX', 'RM', 'AGE', 'DIS', 'RAD', 'TAX', 'PTRATIO', 'B', 'LSTAT'], dtype='<U7')
  9. x = boston.data[:,5] # 只使用房间数量这个特征
  10. x.shape # (506,)
  11. # 可视化
  12. plt.scatter(x, y)
  13. plt.show()

image.png

  1. np.max(y) # 50.0
  2. x = x[y < 50.0]
  3. y = y[y < 50.0]
  4. x.shape # (490,)
  5. plt.scatter(x, y)
  6. plt.show()

image.png

  1. from playML.model_selection import train_test_split
  2. # 切分数据集
  3. x_train, x_test, y_train, y_test = train_test_split(x, y, seed=666)
  4. x_train.shape # (392,)
  5. y_train.shape # (392,)
  6. x_test.shape # (98,)
  7. y_test.shape # (98,)
  8. # 建模
  9. from playML.SimpleLinearRegression import SimpleLinearRegression
  10. reg = SimpleLinearRegression()
  11. reg.fit(x_train, y_train)
  12. reg.a_ # 7.8608543562689555
  13. reg.b_ # -27.459342806705543
  14. # 可视化
  15. plt.scatter(x_train, y_train)
  16. plt.plot(x_train, reg.predict(x_train), color='r')
  17. plt.show()

image.png

  1. # MSE
  2. mse_test = np.sum((y_predict - y_test)**2) / len(y_test)
  3. mse_test # 24.156602134387438
  1. # RMSE
  2. from math import sqrt
  3. rmse_test = sqrt(mse_test)
  4. rmse_test # 4.914936635846635
  1. # MAE
  2. mae_test = np.sum(np.absolute(y_predict - y_test))/len(y_test)
  3. mae_test # 3.5430974409463873

封装评价指标

  1. import numpy as np
  2. from math import sqrt
  3. def accuracy_score(y_true, y_predict):
  4. """计算y_true和y_predict之间的准确率"""
  5. assert len(y_true) == len(y_predict), \
  6. "the size of y_true must be equal to the size of y_predict"
  7. return np.sum(y_true == y_predict) / len(y_true)
  8. def mean_squared_error(y_true, y_predict):
  9. """计算y_true和y_predict之间的MSE"""
  10. assert len(y_true) == len(y_predict), \
  11. "the size of y_true must be equal to the size of y_predict"
  12. return np.sum((y_true - y_predict)**2) / len(y_true)
  13. def root_mean_squared_error(y_true, y_predict):
  14. """计算y_true和y_predict之间的RMSE"""
  15. return sqrt(mean_squared_error(y_true, y_predict))
  16. def mean_absolute_error(y_true, y_predict):
  17. """计算y_true和y_predict之间的MAE"""
  18. return np.sum(np.absolute(y_true - y_predict)) / len(y_true)
  1. from playML.metrics import mean_squared_error
  2. from playML.metrics import root_mean_squared_error
  3. from playML.metrics import mean_absolute_error
  4. mean_squared_error(y_test, y_predict) # 24.156602134387438
  5. root_mean_squared_error(y_test, y_predict) # 4.914936635846635
  6. mean_absolute_error(y_test, y_predict) # 3.5430974409463873

scikit-learn中的MSE和MAE

scikit-learn 中没有实现 RMSE

  1. from sklearn.metrics import mean_squared_error
  2. from sklearn.metrics import mean_absolute_error
  3. mean_squared_error(y_test, y_predict) # 24.156602134387438
  4. mean_absolute_error(y_test, y_predict) # 3.5430974409463873

最好指标

R Squared

最好的衡量线性回归法的指标。
01 一元线性回归原理及实现 - 图24

01 一元线性回归原理及实现 - 图25 使用我们的模型预测产生的错误
01 一元线性回归原理及实现 - 图26 基准模型:使用 01 一元线性回归原理及实现 - 图27 预测产生的错误(不考虑x,预测值均为01 一元线性回归原理及实现 - 图28,误差肯定比上式大)

  • R2越大越好。当我们的预测模型不犯任何错误是,R^2得到最大值1
  • 当我们的模型等于基准模型时,R^2为0
  • 如果R2<0,说明我们学习到的模型还不如基准模型。此时,很有可能我们的数据不存在任何线性关系。

01 一元线性回归原理及实现 - 图29

封装R Score

  1. # metrics.py
  2. import numpy as np
  3. from math import sqrt
  4. def accuracy_score(y_true, y_predict):
  5. """计算y_true和y_predict之间的准确率"""
  6. assert len(y_true) == len(y_predict), \
  7. "the size of y_true must be equal to the size of y_predict"
  8. return np.sum(y_true == y_predict) / len(y_true)
  9. def mean_squared_error(y_true, y_predict):
  10. """计算y_true和y_predict之间的MSE"""
  11. assert len(y_true) == len(y_predict), \
  12. "the size of y_true must be equal to the size of y_predict"
  13. return np.sum((y_true - y_predict)**2) / len(y_true)
  14. def root_mean_squared_error(y_true, y_predict):
  15. """计算y_true和y_predict之间的RMSE"""
  16. return sqrt(mean_squared_error(y_true, y_predict))
  17. def mean_absolute_error(y_true, y_predict):
  18. """计算y_true和y_predict之间的MAE"""
  19. assert len(y_true) == len(y_predict), \
  20. "the size of y_true must be equal to the size of y_predict"
  21. return np.sum(np.absolute(y_true - y_predict)) / len(y_true)
  22. def r2_score(y_true, y_predict):
  23. """计算y_true和y_predict之间的R Square"""
  24. return 1 - mean_squared_error(y_true, y_predict)/np.var(y_true)
  1. from playML.metrics import r2_score
  2. r2_score(y_test, y_predict) # 0.61293168039373225

scikit-learn中的 r2_score

scikit-learn中的LinearRegression中的score返回r2_score:http://scikit-learn.org/stable/modules/generated/sklearn.linear_model.LinearRegression.html

  1. from sklearn.metrics import r2_score
  2. r2_score(y_test, y_predict) # 0.61293168039373236