0%

线性回归

从机器学习的角度,线性回归是回归问题中最简单的模型。回归问题属于监督式机器学习。

回顾线性回归

在线性代数中,我们就学过用最小二乘法做线性回归:

对于平面上的 N 个点$P_i(x_i,y_i)$,满足$min \sum {\Delta Y_i}^{2} = min \sum {|Y_i-f(x_i)|}^2$
的直线$y=f(x)$,是这 N 个点的线性拟合直线。

机器学习角度的线性回归

从机器学习的角度,线性回归是回归问题中最简单的模型。回归问题属于监督式机器学习:
给定训练样本,「学习」一个函数,每一个样本数据就是需要学习的函数的输入输出数据。
并且这个函数能够很好的泛化到不在训练集中的输入值上。可以通过测试数据来检验泛化的效果。

线性回归通常用于根据符合直线关系的连续变量估计实际数值。
其优点是快速、没有调节参数、可轻易解释、可理解;缺点是相比其他复杂一些的模型,其预测准确率不是太高,
因为它假设特征和响应之间存在确定的线性关系,这种假设对于非线性的关系,线性回归模型显然不能很好的对这种数据建模。

线性回归的两种主要类型是一元线性回归和多元线性回归。

一元线性回归的特点是只有一个自变量,即$y=y=\alpha x+\beta$.
而多元线性回归存在多个自变量,即$y=b_0+b_1x_1+…+b_px_p$

其实多项式回归可以看做是一种特殊的多元线性回归:$y=b_0+b_1x+ b_2x^2…+b_px^p$

scikit-learn糖尿病数据集

我们用 scikit-learn 的糖尿病数据集来演示线性回归。

import pandas as pd
import warnings 
# 用来忽略seaborn绘图库产生的warnings
warnings.filterwarnings("ignore")
import seaborn as sns
import matplotlib.pyplot as plt
sns.set(style="white", color_codes=True)
%pylab inline
Populating the interactive namespace from numpy and matplotlib
from sklearn import datasets  
diabetes = datasets.load_diabetes()  
dir(diabetes)
Out[10]:
['DESCR', 'data', 'feature_names', 'target']
diabetes.feature_names
# 10个特征,分别为Age(年龄)、性别(Sex)、Body mass index(体质指数)、Average Blood Pressure(平均血压)、S1~S6一年后疾病级数指标
# Target为一年后患疾病的定量指标
Out[15]:
['age', 'sex', 'bmi', 'bp', 's1', 's2', 's3', 's4', 's5', 's6']
df = pd.DataFrame(diabetes.data,columns=diabetes.feature_names)
df['target']=pd.Series(diabetes.target)
df.head()
Out[16]:
age sex bmi bp s1 s2 s3 s4 s5 s6 target
0 0.038076 0.050680 0.061696 0.021872 -0.044223 -0.034821 -0.043401 -0.002592 0.019908 -0.017646 151.0
1 -0.001882 -0.044642 -0.051474 -0.026328 -0.008449 -0.019163 0.074412 -0.039493 -0.068330 -0.092204 75.0
2 0.085299 0.050680 0.044451 -0.005671 -0.045599 -0.034194 -0.032356 -0.002592 0.002864 -0.025930 141.0
3 -0.089063 -0.044642 -0.011595 -0.036656 0.012191 0.024991 -0.036038 0.034309 0.022692 -0.009362 206.0
4 0.005383 -0.044642 -0.036385 0.021872 0.003935 0.015596 0.008142 -0.002592 -0.031991 -0.046641 135.0
sns.pairplot(df, x_vars=diabetes.feature_names, y_vars='target', size=5, aspect=0.8)
Out[21]:
<seaborn.axisgrid.PairGrid at 0x11bdbc198>
# 显然,sex、s1、s2、 s4,与 target 没有相关性

#參数kind='reg'。seaborn能够加入一条最佳拟合直线和95%的置信带。
sns.pairplot(df, x_vars=['age', 'bmi', 'bp', 's3', 's5', 's6'], y_vars='target', 
             size=5, aspect=0.8, kind='reg')
Out[25]:
<seaborn.axisgrid.PairGrid at 0x11f389550>
 

scikit-learn处理线性回归问题

LinearRegression模型在Sklearn.linear_model下,
它主要是通过 fit(x,y) 的方法来训练模型,其中x为特征,y为标记。
然后通过predict() 预测, 以fit()算出的模型参数, 预测特性 x 所对应的预测值y_pred。

import pandas as pd
import warnings 
# 用来忽略seaborn绘图库产生的warnings
warnings.filterwarnings("ignore")
import seaborn as sns
import matplotlib.pyplot as plt
sns.set(style="white", color_codes=True)
%pylab inline
Populating the interactive namespace from numpy and matplotlib
from sklearn import datasets  
diabetes = datasets.load_diabetes()
df = pd.DataFrame(diabetes.data,columns=diabetes.feature_names)
df['target']=pd.Series(diabetes.target)
df.head()
Out[2]:
age sex bmi bp s1 s2 s3 s4 s5 s6 target
0 0.038076 0.050680 0.061696 0.021872 -0.044223 -0.034821 -0.043401 -0.002592 0.019908 -0.017646 151.0
1 -0.001882 -0.044642 -0.051474 -0.026328 -0.008449 -0.019163 0.074412 -0.039493 -0.068330 -0.092204 75.0
2 0.085299 0.050680 0.044451 -0.005671 -0.045599 -0.034194 -0.032356 -0.002592 0.002864 -0.025930 141.0
3 -0.089063 -0.044642 -0.011595 -0.036656 0.012191 0.024991 -0.036038 0.034309 0.022692 -0.009362 206.0
4 0.005383 -0.044642 -0.036385 0.021872 0.003935 0.015596 0.008142 -0.002592 -0.031991 -0.046641 135.0
# 拆分数据
from sklearn.cross_validation import train_test_split
x_train, x_test, y_train, y_test = train_test_split(df[diabetes.feature_names], df['target'], random_state=1)
len(x_train),len(x_test),len(y_train),len(y_test)
Out[33]:
(331, 111, 331, 111)
from sklearn import linear_model
linear = linear_model.LinearRegression()


# 训练模型
linear.fit(x_train, y_train)

#训练结果
print('Coefficients :\n' , linear.coef_)
print('Intercept: n', linear.intercept_)
Coefficients :
 [  -7.85951708 -245.05253542  575.11667591  323.85372717 -519.77447335
  250.61132753    0.96367294  180.50891964  614.75959394   52.10619986]
Intercept: n 150.997693786
Residual sum of square: 2903.10
variance score: 0.44
# 模型评价
#对于线性回归来说,我们一般用均方差(Mean Squared Error, MSE)
#或者均方根差(Root Mean Squared Error, RMSE)在测试集上的表现来评价模型的好坏。

y_pred = linear.predict(x_test)

# 残差平方和 方差得分  
print("Residual sum of square: %.2f" %np.mean((y_pred - y_test) ** 2))  
print("variance score: %.2f" % linear.score(x_test, y_test))

from sklearn import metrics
# 用scikit-learn计算MSE
print("MSE:",metrics.mean_squared_error(y_test, y_pred))
# 用scikit-learn计算RMSE
print("RMSE:",np.sqrt(metrics.mean_squared_error(y_test, y_pred)))
Residual sum of square: 2903.10
variance score: 0.44
MSE: 2903.10000132
RMSE: 53.8804231732
#绘图

plt.scatter(x_test['age'], y_test, color = 'black')  
#预测结果 直线表示  
#plt.plot(x_test['age'], linear.predict(y_test), color='blue', linewidth = 3)  
#plt.show()  
Out[36]:
<matplotlib.collections.PathCollection at 0x119944f60>
# 交叉验证
from sklearn.model_selection import cross_val_predict
predicted = cross_val_predict(linear, df[diabetes.feature_names], df['target'], cv=10)
# 用scikit-learn计算MSE
print("MSE:",metrics.mean_squared_error(df['target'], predicted))
# 用scikit-learn计算RMSE
print("RMSE:",np.sqrt(metrics.mean_squared_error(df['target'], predicted)))
MSE: 2999.03228568
RMSE: 54.7634210553
# 可以看出,采用交叉验证模型的MSE更大
# 主要原因是我们这里是对所有折的样本做测试集对应的预测值的MSE,
# 而前面仅仅对25%的测试集做了MSE。两者的先决条件并不同。
 

模型评价和特征选择

回归问题不能用准确率评价。对于回归这种连续数值,评价测度(evaluation metrics)主要用以下指标:

  • 平均绝对误差(Mean Absolute Error, MAE)

  • 均方误差(Mean Squared Error, MSE),即残差平方和

  • 均方根误差(Root Mean Squared Error, RMSE)

在数据探索的时候,我们就发现 sex、s1、s2、 s4 与 target 的相关度不高。现在移除这些特征,
看看回归的结果是否更好。

import pandas as pd
import warnings 
# 用来忽略seaborn绘图库产生的warnings
warnings.filterwarnings("ignore")
import seaborn as sns
import matplotlib.pyplot as plt
sns.set(style="white", color_codes=True)
%pylab inline
Populating the interactive namespace from numpy and matplotlib
from sklearn import datasets  
diabetes = datasets.load_diabetes()
df = pd.DataFrame(diabetes.data,columns=diabetes.feature_names)
df['target']=pd.Series(diabetes.target)
df.head()
Out[9]:
age sex bmi bp s1 s2 s3 s4 s5 s6 target
0 0.038076 0.050680 0.061696 0.021872 -0.044223 -0.034821 -0.043401 -0.002592 0.019908 -0.017646 151.0
1 -0.001882 -0.044642 -0.051474 -0.026328 -0.008449 -0.019163 0.074412 -0.039493 -0.068330 -0.092204 75.0
2 0.085299 0.050680 0.044451 -0.005671 -0.045599 -0.034194 -0.032356 -0.002592 0.002864 -0.025930 141.0
3 -0.089063 -0.044642 -0.011595 -0.036656 0.012191 0.024991 -0.036038 0.034309 0.022692 -0.009362 206.0
4 0.005383 -0.044642 -0.036385 0.021872 0.003935 0.015596 0.008142 -0.002592 -0.031991 -0.046641 135.0
from sklearn.cross_validation import train_test_split
from sklearn import linear_model
from sklearn import metrics
# 全特征回归
x_train, x_test, y_train, y_test = train_test_split(df[diabetes.feature_names], df['target'], random_state=1)
linear = linear_model.LinearRegression()
linear.fit(x_train, y_train)

#训练结果
print('Coefficients :\n' , linear.coef_)
print('Intercept: n', linear.intercept_)

# 结果评价
y_pred = linear.predict(x_test)

print("得分: %.2f" % linear.score(x_test, y_test))
print("MAE:",metrics.mean_absolute_error(y_test, y_pred))
# print("MSE: %.2f" %np.mean((y_pred - y_test) ** 2))  
print("MSE:",metrics.mean_squared_error(y_test, y_pred))
print("RMSE:",np.sqrt(metrics.mean_squared_error(y_test, y_pred)))
Coefficients :
 [  -7.85951708 -245.05253542  575.11667591  323.85372717 -519.77447335
  250.61132753    0.96367294  180.50891964  614.75959394   52.10619986]
Intercept: n 150.997693786
得分: 0.44
MAE: 41.982920292
MSE: 2903.10000132
RMSE: 53.8804231732
# 部分特征回归
x_train, x_test, y_train, y_test = train_test_split(df[['age', 'bmi', 'bp', 's3', 's5', 's6']], df['target'], random_state=1)
linear = linear_model.LinearRegression()
linear.fit(x_train, y_train)

#训练结果
print('Coefficients :\n' , linear.coef_)
print('Intercept: n', linear.intercept_)

# 结果评价
y_pred = linear.predict(x_test)

print("得分: %.2f" % linear.score(x_test, y_test))
print("MAE:",metrics.mean_absolute_error(y_test, y_pred))
# print("MSE: %.2f" %np.mean((y_pred - y_test) ** 2))  
print("MSE:",metrics.mean_squared_error(y_test, y_pred))
print("RMSE:",np.sqrt(metrics.mean_squared_error(y_test, y_pred)))
Coefficients :
 [ -46.89764846  615.70776642  264.87024712 -197.76826798  452.41426038
   22.20090217]
Intercept: n 150.95907108
得分: 0.41
MAE: 43.6662007739
MSE: 3093.0656796
RMSE: 55.61533673
# 可见,人工筛选后的结果更差,说明特征的筛选不合适
 

多项式回归

这里,用一个2次函数加上随机的扰动来生成500个点,然后尝试用1、2、100次方的多项式对该数据进行拟合。
拟合的目的是使得根据训练数据能够拟合出一个多项式函数,这个函数能够很好的拟合现有数据,并且能对未知的数据进行预测[^5]。

结果评价:

做回归分析,常用的误差主要有均方误差根(RMSE)和R-平方(R2)。

RMSE是预测值与真实值的误差平方根的均值。这种度量方法很流行(Netflix机器学习比赛的评价方法),是一种定量的权衡方法。

R2方法是将预测值跟只使用均值的情况下相比,看能好多少。其区间通常在(0,1)之间。0表示还不如什么都不预测,直接取均值的情况,而1表示所有预测跟真实结果完美匹配的情况。

R2的计算方法,不同的文献稍微有不同。如本文中函数R2是依据scikit-learn官网文档实现的,跟clf.score函数结果一致。

而R22函数的实现来自Conway的著作《机器学习使用案例解析》,不同在于他用的是2个RMSE的比值来计算R2。

我们看到多项式次数为1的时候,虽然拟合的不太好,R2也能达到0.82。2次多项式提高到了0.88。
而次数提高到100次,R2也只提高到了0.89。

import pandas as pd
import warnings 
# 用来忽略seaborn绘图库产生的warnings
warnings.filterwarnings("ignore")
import seaborn as sns
import matplotlib.pyplot as plt
sns.set(style="white", color_codes=True)
%pylab inline
Populating the interactive namespace from numpy and matplotlib
# 生成数据
import scipy as sp  
from scipy.stats import norm  

x = np.arange(0, 1, 0.002)  
y = norm.rvs(0, size=500, scale=0.1)  
y = y + x**5
df = pd.DataFrame()
df['x']=x
df['y']=y
df.head()
#plt.scatter(x, y, s=5,figsize=12)
#df..plot(figsize=(12,9))
sns.jointplot(x="x", y="y", data=df, kind='reg', size=9)
Out[51]:
<seaborn.axisgrid.JointGrid at 0x103f20b38>
# 评价函数
''''' 均方误差根 '''  
def rmse(y_test, y):  
    return sp.sqrt(sp.mean((y_test - y) ** 2))  
  
''''' 与均值相比的优秀程度,介于[0~1]。0表示不如均值。1表示完美预测.这个版本的实现是参考scikit-learn官网文档  '''  
def R2(y_test, y_true):  
    return 1 - ((y_test - y_true)**2).sum() / ((y_true - y_true.mean())**2).sum()  
  
  
''''' 这是Conway&White《机器学习使用案例解析》里的版本 '''  
def R22(y_test, y_true):  
    y_mean = np.array(y_true)  
    y_mean[:] = y_mean.mean()  
    return 1 - rmse(y_test, y_true) / rmse(y_mean, y_true)  
plt.figure(figsize=(12,9))
# 回归级数
degree = [1,2,5,10,20,50,100]
plt.scatter(x, y, s=5)

# 线性回归
from sklearn.linear_model import LinearRegression
regressor = LinearRegression()

# 多项式回归
from sklearn.pipeline import Pipeline
from sklearn.preprocessing import PolynomialFeatures  
from sklearn import linear_model  
for d in degree:  
    clf = Pipeline([('poly', PolynomialFeatures(degree=d)),  
                    ('linear', LinearRegression(fit_intercept=False))])  
    clf.fit(x[:, np.newaxis], y)  
    y_test = clf.predict(x[:, np.newaxis])  
  
    # 拟合结果
    #print(clf.named_steps['linear'].coef_)  
    # 评价
    print('level=%d,rmse=%.2f, R2=%.2f, R22=%.2f, clf.score=%.2f' % 
       (d,
        rmse(y_test, y),  
        R2(y_test, y),  
        R22(y_test, y),  
        clf.score(x[:, np.newaxis], y)))      
       
    plt.plot(x, y_test, linewidth=2)  
       
plt.grid()  
plt.legend(degree, loc='upper left')  
level=1,rmse=0.18, R2=0.57, R22=0.34, clf.score=0.57
level=2,rmse=0.11, R2=0.83, R22=0.58, clf.score=0.83
level=5,rmse=0.10, R2=0.86, R22=0.62, clf.score=0.86
level=10,rmse=0.10, R2=0.86, R22=0.62, clf.score=0.86
level=20,rmse=0.10, R2=0.86, R22=0.63, clf.score=0.86
level=50,rmse=0.10, R2=0.87, R22=0.63, clf.score=0.87
level=100,rmse=0.10, R2=0.87, R22=0.63, clf.score=0.87
Out[54]:
<matplotlib.legend.Legend at 0x10f46f908>
# 结果评价
# 注意,次数过高,容易发生过拟合
 

参考资料

[^1]: Linear Regression Example
[^2]: scikit-learn : 线性回归,多元回归,多项式回归
[^3]: Coursera公开课笔记: 斯坦福大学机器学习第二课“单变量线性回归(Linear regression with one variable)”
[^4]: 用scikit-learn和pandas学习线性回归
[^5]: 用Python开始机器学习(3:数据拟合与广义线性回归)
[^6]: Coursera公开课笔记: 斯坦福大学机器学习第四课“多变量线性回归(Linear Regression with Multiple Variables)”
[^7]: sklearn学习笔记之简单线性回归