Python之ML--回归分析预测连续型目标变量

Python之ML–回归分析预测连续型目标变量

监督学习的另一个分支:回归分析(regression analysis).回归模型(regression model)可用于连续型目标变量的预测分析

主要知识点如下:

  • 数据集的探索与可视化
  • 实现线性回归模型的不同方法
  • 训练可处理异常值的回归模型
  • 回归模型的评估及常见问题
  • 基于非线性数据拟合回归模型

一.简单线性回归模型

简单(单变量)线性回归的目标是:通过模型来描述某一特征(解释变量x)与连续输出(目标变量y)之间的关系.当只有一个解释变量时,线性模型的函数定义如下:



其中,权值w0为函数在y轴上的截距,w1为解释变量的系数.我们的目标是通过学习得到线性方程的这两个权值,并用它们描述解释变量与目标变量之间的关系

我们可以将线性回归模型扩展为多个解释变量.即为所谓的多元线性回归(multiple linear regression)

from IPython.display import Image

output_5_0.png

其中,w0为x0=1时在y轴上的截距

二.波士顿房屋数据集

波士顿房屋数据集(Housing Dataset),可以在UCI机器学习数据库中下载:https://archive.ics.uci.edu/ml/datasets/Housing

数据集中506个样本的特征描述如下:

  • CRIM:房屋所在镇的犯罪率
  • ZN:用地面积远大于25000平方英尺的住宅所占比例
  • INDUS:房屋所在镇无零售业务区域所占比例
  • CHAS:与查尔斯河有关的虚拟变量(如果房屋位于河边则为1,否则为0)
  • NOX:一氧化碳浓度
  • RM:每处寓所的平均房间数
  • AGE:业主自住房屋中,建于1940年之前的房屋所占比例
  • DIS:房屋距离波士顿五大就业中心的加权距离
  • RAD:距离房屋最近公路入口编号
  • TAX:每一万美元全额财产税金额
  • PTRATIO:房屋所在镇的师生比
  • B:计算公式为1000(Bk-0.63)^2,其中Bk为房屋所在镇非裔美籍人口所占比例
  • LSTAT:弱势群体人口所占比例
  • MEDV:业主自住房屋的平均价格

先从UCI库中把Housing Dataset导入到pandas的DataFrame中:

import pandas as pd

df = pd.read_csv('https://archive.ics.uci.edu/ml/machine-learning-databases/'
                 'housing/housing.data',
                 header=None,
                 sep='\s+')

df.columns = ['CRIM', 'ZN', 'INDUS', 'CHAS', 
              'NOX', 'RM', 'AGE', 'DIS', 'RAD', 
              'TAX', 'PTRATIO', 'B', 'LSTAT', 'MEDV']
df.head()
CRIMZNINDUSCHASNOXRMAGEDISRADTAXPTRATIOBLSTATMEDV
00.0063218.02.3100.5386.57565.24.09001296.015.3396.904.9824.0
10.027310.07.0700.4696.42178.94.96712242.017.8396.909.1421.6
20.027290.07.0700.4697.18561.14.96712242.017.8392.834.0334.7
30.032370.02.1800.4586.99845.86.06223222.018.7394.632.9433.4
40.069050.02.1800.4587.14754.26.06223222.018.7396.905.3336.2
df = pd.read_csv('https://raw.githubusercontent.com/rasbt/python-machine-learning-book/master/code/datasets/housing/housing.data',
                  header=None, sep='\s+')

df.columns = ['CRIM', 'ZN', 'INDUS', 'CHAS', 
              'NOX', 'RM', 'AGE', 'DIS', 'RAD', 
              'TAX', 'PTRATIO', 'B', 'LSTAT', 'MEDV']
df.head()
CRIMZNINDUSCHASNOXRMAGEDISRADTAXPTRATIOBLSTATMEDV
00.0063218.02.3100.5386.57565.24.09001296.015.3396.904.9824.0
10.027310.07.0700.4696.42178.94.96712242.017.8396.909.1421.6
20.027290.07.0700.4697.18561.14.96712242.017.8392.834.0334.7
30.032370.02.1800.4586.99845.86.06223222.018.7394.632.9433.4
40.069050.02.1800.4587.14754.26.06223222.018.7396.905.3336.2

首先,借助散点图矩阵,我们以可视化的方法汇总显示各不同特征两两之间的关系.为了绘制散点图矩阵,我们需要用到seaborn库中的pairplot函数,它是在matplotlib基础上绘制统计图像的python库

import matplotlib.pyplot as plt
import seaborn as sns


sns.set(style='whitegrid', context='notebook')
cols = ['LSTAT', 'INDUS', 'NOX', 'RM', 'MEDV']

sns.pairplot(df[cols], size=2.5)
plt.tight_layout()
plt.show()

output_14_1.png

通过此散点图矩阵,我们可以快速了解数据是如何分布的,以及其中是否包含异常值.我们可直观看出RM和房屋价格MEDV(第5列和第4行)之间存在线性关系.从MEDV直方图中可以发现:MEDV看似呈正态分布,但包含几个异常值

使用Numpy的corrcoef函数计算前面散点图矩阵中5个特征间的相关系数矩阵,并使用seaborn的heatmap函数绘制其对应的热度图

import numpy as np


cm = np.corrcoef(df[cols].values.T)
sns.set(font_scale=1.5)
hm = sns.heatmap(cm,
                 cbar=True,
                 annot=True,
                 square=True,
                 fmt='.2f',
                 annot_kws={'size': 15},
                 yticklabels=cols,
                 xticklabels=cols)



plt.show()

output_17_0.png

为了拟合线性回归模型,我们主要关注那些更目标变量MEDV高度相关的特征.观察前面的相关系数矩阵,可以发现MEDV与变量LSTAT的相关性最大(-0.74)。前面的散点图矩阵显示LSTAT和MEDV之间存在明显的非线性关系.另一方面,RM和MEDV间的相关性也较高(0.70)

导入seaborn库后,会覆盖当前python会话中matplotlib默认的图像显示方式.如果不希望使用seaborn风格的设置,可以通过如下命令重设为matplotlib的风格:

sns.reset_orig()

二.基于最小二乘法构建线性回归模型

通过最小二乘法(Ordinary Least Squares,OLS)估计回归曲线的参数,使得回归曲线到样本点垂直距离(残差或误差)的平方和最小

1.通过梯度下降计算回归参数

Adaline中的代价函数就是误差平方和(Sum of Squared Error,SSE),它等同于我们定义的OLS代价函数.本质上,OLS线性回归可以理解为无单位阶跃函数的Adaline,这样我们得到的是连续型的输出值,而不是以-1和1来代表的类标.为了说明两者的相似程度,使用前面实现的GD方法,并且移除其单位阶跃函数来实现我们第一个线性回归模型:

class LinearRegressionGD(object):

    def __init__(self, eta=0.001, n_iter=20):
        self.eta = eta
        self.n_iter = n_iter

    def fit(self, X, y):
        self.w_ = np.zeros(1 + X.shape[1])
        self.cost_ = []

        for i in range(self.n_iter):
            output = self.net_input(X)
            errors = (y - output)
            self.w_[1:] += self.eta * X.T.dot(errors)
            self.w_[0] += self.eta * errors.sum()
            cost = (errors**2).sum() / 2.0
            self.cost_.append(cost)
        return self

    def net_input(self, X):
        return np.dot(X, self.w_[1:]) + self.w_[0]

    def predict(self, X):
        return self.net_input(X)

我们使用房屋数据集中的RM(房间数量)作为解释变量来训练模型以预测MEDV(房屋价格).为了使得梯度下降算法收敛性更佳,在此对相关变量做了标准化处理

X = df[['RM']].values
y = df['MEDV'].values
from sklearn.preprocessing import StandardScaler

sc_x = StandardScaler()
sc_y = StandardScaler()
X_std = sc_x.fit_transform(X)
y_std = sc_y.fit_transform(y[:, np.newaxis]).flatten()
lr = LinearRegressionGD()
lr.fit(X_std, y_std)
<__main__.LinearRegressionGD at 0x278bdee8908>

我们将再次绘制迭代次数对应的代价函数的值,以检查线性回归是否收敛

plt.plot(range(1, lr.n_iter+1), lr.cost_)
plt.ylabel('SSE')
plt.xlabel('Epoch')
plt.tight_layout()

plt.show()

output_31_0.png

我们使用lin_regplot函数绘制房间数与房屋价格之间的关系图

def lin_regplot(X,y,model):
    plt.scatter(X,y,c='blue')
    plt.plot(X,model.predict(X),color='red')
    return None
lin_regplot(X_std,y_std,lr)

plt.xlabel('Average number of rows [RM] (Standardized)')
plt.ylabel('Price in $1000\'s [MEDV] (standardized)')

plt.show()

output_34_0.png

为了将预测价格缩放到以1000美元为价格单位的坐标轴中,我们使用了StandardScaler的inverse_transform方法

num_rooms_std=sc_x.transform(np.array([[5.0]]))

price_std=lr.predict(num_rooms_std)

print("Price in $1000's:%.3f"%sc_y.inverse_transform(price_std))
Price in $1000's:10.840
print('Slope:%.3f'%lr.w_[1])
print('Intercept:.%.3f'%lr.w_[0])
Slope:0.695
Intercept:.-0.000

2.使用scikit-learn估计回归模型的系数

from sklearn.linear_model import LinearRegression

slr=LinearRegression()
slr.fit(X,y)

print('Slope:%.3f'%slr.coef_[0])
print('Intercept:%.3f'%slr.intercept_)
Slope:9.102
Intercept:-34.671
lin_regplot(X,y,slr)

plt.xlabel('Average number of rows [RM] (Standardized)')
plt.ylabel('Price in $1000\'s [MEDV] (standardized)')

plt.show()

output_40_0.png

上述代码绘制出了训练数据和拟合模型的图像,总体效果与GD算法实现的模型是一致的

四.使用RANSAC拟合高鲁棒性回归模型

作为清除异常值的一种高鲁棒性回归方法,在此我们将学习随机抽样一致性(RANdom SAmple Consensus,RANSAC)算法,使用数据的一个子集(即所谓的内点,inlier)来进行回归模型的拟合

我们将RANSAC算法的工作流程总结如下:

  1. 从数据集中随机抽取样本构建内点集合来拟合模型
  2. 使用剩余数据对上一步得到的模型进行测试,并将落在预定公差范围内的样本点增至内点集合中
  3. 使用全部的内点集合数据再次进行模型的拟合
  4. 使用内点集合来估计模型的误差
  5. 如果模型性能达到了用户设定的特定阈值或者迭代达到了预定次数,则算法终止,否则跳转到第1步
from sklearn.linear_model import RANSACRegressor

ransac=RANSACRegressor(LinearRegression(),
                      max_trials=100,
                      min_samples=50,
                      loss='absolute_loss',
                      residual_threshold=5.0,
                      random_state=0)

ransac.fit(X,y)
RANSACRegressor(base_estimator=LinearRegression(copy_X=True, fit_intercept=True, n_jobs=None,
         normalize=False),
        is_data_valid=None, is_model_valid=None, loss='absolute_loss',
        max_skips=inf, max_trials=100, min_samples=50, random_state=0,
        residual_threshold=5.0, stop_n_inliers=inf, stop_probability=0.99,
        stop_score=inf)

我们将RANSACRegression的最大迭代次数设定为100,设定参数min_samples=50,即随机抽取作为内点的最小样本数量设定为50.将residual_threshold参数的值设定为5.0,这样只有与拟合曲线垂直距离小于5个单位长度的样本点才能加入到内点集合,此设置在特定数据集上表现良好

inlier_mask = ransac.inlier_mask_
outlier_mask = np.logical_not(inlier_mask)

line_X = np.arange(3, 10, 1)
line_y_ransac = ransac.predict(line_X[:, np.newaxis])
plt.scatter(X[inlier_mask], y[inlier_mask],
            c='blue', marker='o', label='Inliers')
plt.scatter(X[outlier_mask], y[outlier_mask],
            c='lightgreen', marker='s', label='Outliers')
plt.plot(line_X, line_y_ransac, color='red')   
plt.xlabel('Average number of rooms [RM]')
plt.ylabel('Price in $1000\'s [MEDV]')
plt.legend(loc='upper left')

plt.tight_layout()

plt.show()

output_47_0.png

显示模型的斜率和截距

print('Slope:%.3f'%ransac.estimator_.coef_[0])
print('Intercept:.%.3f'%ransac.estimator_.intercept_)
Slope:10.735
Intercept:.-44.089

五.线性回归模型性能的评估

使用数据集中的所有变量训练多元回归模型

from sklearn.model_selection import train_test_split

X=df.iloc[:,:-1].values
y=df['MEDV'].values

X_train,X_test,y_train,y_test=train_test_split(X,y,test_size=0.3,random_state=0)

slr=LinearRegression()

slr.fit(X_train,y_train)

y_train_pred=slr.predict(X_train)
y_test_pred=slr.predict(X_test)

我们绘制得到残差图,其中通过将预测结果减去对应目标变量真实值,便可获得残差的值

plt.scatter(y_train_pred,y_train_pred-y_train,c='blue',marker='o',label='Training data')
plt.scatter(y_test_pred,y_test_pred-y_test,c='lightgreen',marker='s',label='Test data')

plt.xlabel('Predicted values')
plt.ylabel('Residuals')

plt.legend(loc='upper left')

plt.hlines(y=0,xmin=-10,xmax=50,lw=2,color='red')
plt.xlim([-10,50])

plt.show()

output_54_0.png

from sklearn.metrics import mean_squared_error

print('MSE train:%.3f,test:%.3f'%(mean_squared_error(y_train,y_train_pred),mean_squared_error(y_test,y_test_pred)))
MSE train:19.958,test:27.196
from sklearn.metrics import r2_score

print('R^2 train:%.3f,test:%.3f'%(r2_score(y_train,y_train_pred),r2_score(y_test,y_test_pred)))
R^2 train:0.765,test:0.673

六.线性回归模型的曲线化–多项式回归

使用scikit-learn中的PolynomialFeatures转换类在只含一个解释变量的简单回归问题中加入二次项(d=2),并且将多项式回归与线性回归进行线性拟合

1.增加一个二次多项式项

from sklearn.preprocessing import PolynomialFeatures

X=np.array([258.0,270.0,294.0,320.0,342.0,368.0,
           396.0,446.0,480.0,586.0])[:,np.newaxis]

y=np.array([236.4,234.4,252.8,298.6,314.2,342.2,
           360.8,368.0,391.2,390.8])

lr=LinearRegression()

pr=LinearRegression()

quadratic=PolynomialFeatures(degree=2)
X_quad=quadratic.fit_transform(X)

2.拟合一个用于对比的简单线性回归模型

lr.fit(X,y)

X_fit=np.arange(250,600,10)[:,np.newaxis]
y_lin_fit=lr.predict(X_fit)

3.使用经过转换后的特征针对多项式回归拟合一个多元线性回归模型

pr.fit(X_quad, y)
y_quad_fit = pr.predict(quadratic.fit_transform(X_fit))

# plot results
plt.scatter(X, y, label='training points')
plt.plot(X_fit, y_lin_fit, label='linear fit', linestyle='--')
plt.plot(X_fit, y_quad_fit, label='quadratic fit')
plt.legend(loc='upper left')

plt.tight_layout()

plt.show()

output_65_0.png

从图像中可以看出,与线性拟合相比,多项式拟合可以更好地捕获到解释变量与响应变量之间的关系

y_lin_pred = lr.predict(X)
y_quad_pred = pr.predict(X_quad)
print('Training MSE linear: %.3f, quadratic: %.3f' % (
        mean_squared_error(y, y_lin_pred),
        mean_squared_error(y, y_quad_pred)))
print('Training R^2 linear: %.3f, quadratic: %.3f' % (
        r2_score(y, y_lin_pred),
        r2_score(y, y_quad_pred)))
Training MSE linear: 569.780, quadratic: 61.330
Training R^2 linear: 0.832, quadratic: 0.982

1.房屋数据集中的非线性关系建模

我们将使用二次和三次多项式对房屋价格和LSTAT(弱势群体人口所占比例)之间的关系进行建模,并与线性拟合进行对比

X = df[['LSTAT']].values
y = df['MEDV'].values

regr = LinearRegression()

# create quadratic features
quadratic = PolynomialFeatures(degree=2)
cubic = PolynomialFeatures(degree=3)
X_quad = quadratic.fit_transform(X)
X_cubic = cubic.fit_transform(X)

# fit features
X_fit = np.arange(X.min(), X.max(), 1)[:, np.newaxis]

regr = regr.fit(X, y)
y_lin_fit = regr.predict(X_fit)
linear_r2 = r2_score(y, regr.predict(X))

regr = regr.fit(X_quad, y)
y_quad_fit = regr.predict(quadratic.fit_transform(X_fit))
quadratic_r2 = r2_score(y, regr.predict(X_quad))

regr = regr.fit(X_cubic, y)
y_cubic_fit = regr.predict(cubic.fit_transform(X_fit))
cubic_r2 = r2_score(y, regr.predict(X_cubic))


# plot results
plt.scatter(X, y, label='training points', color='lightgray')

plt.plot(X_fit, y_lin_fit, 
         label='linear (d=1), $R^2=%.2f$' % linear_r2, 
         color='blue', 
         lw=2, 
         linestyle=':')

plt.plot(X_fit, y_quad_fit, 
         label='quadratic (d=2), $R^2=%.2f$' % quadratic_r2,
         color='red', 
         lw=2,
         linestyle='-')

plt.plot(X_fit, y_cubic_fit, 
         label='cubic (d=3), $R^2=%.2f$' % cubic_r2,
         color='green', 
         lw=2, 
         linestyle='--')

plt.xlabel('% lower status of the population [LSTAT]')
plt.ylabel('Price in $1000\'s [MEDV]')
plt.legend(loc='upper right')

plt.tight_layout()

plt.show()

output_71_0.png

X = df[['LSTAT']].values
y = df['MEDV'].values

# transform features
X_log = np.log(X)
y_sqrt = np.sqrt(y)

# fit features
X_fit = np.arange(X_log.min()-1, X_log.max()+1, 1)[:, np.newaxis]

regr = regr.fit(X_log, y_sqrt)
y_lin_fit = regr.predict(X_fit)
linear_r2 = r2_score(y_sqrt, regr.predict(X_log))

# plot results
plt.scatter(X_log, y_sqrt, label='training points', color='lightgray')

plt.plot(X_fit, y_lin_fit, 
         label='linear (d=1), $R^2=%.2f$' % linear_r2, 
         color='blue', 
         lw=2)

plt.xlabel('log(% lower status of the population [LSTAT])')
plt.ylabel('$\sqrt{Price \; in \; \$1000\'s [MEDV]}$')
plt.legend(loc='lower left')

plt.tight_layout()

plt.show()

output_72_0.png

2.使用随机森林处处理非线性关系

随机森林(random forest)是多棵决策树(decision tree)的集合,与先前介绍的线性回归和多项式回归不同,它可以被理解为分段线性函数的集成.换句话说,通过决策树算法,我们把输入空间细分更小的区域以更好地管理

决策树回归

决策树算法的一个优点在于:如果我们处理的是非线性数据,无需对其进行特征转换

使用DecisionTreeRegressor类对MEDV和LSTAT两个变量之间的非线性关系进行建模

from sklearn.tree import DecisionTreeRegressor

X = df[['LSTAT']].values
y = df['MEDV'].values

tree = DecisionTreeRegressor(max_depth=3)
tree.fit(X, y)

sort_idx = X.flatten().argsort()

lin_regplot(X[sort_idx], y[sort_idx], tree)
plt.xlabel('% lower status of the population [LSTAT]')
plt.ylabel('Price in $1000\'s [MEDV]')

plt.show()

output_78_0.png

随机森林回归

由于随机性有助于降低模型的方差,与单棵决策树相比,随机森林通常具有更好的泛化性能.随机森林的另一个优势在于:它对数据集中的异常值不敏感,且无需过多的参数调优.随机森林中唯一需要我们通过实验来获得的参数就是待集成决策树的数量.唯一区别在于:随机森林回归使用MSE作为单棵决策树生成的标准,同时所有决策树预测值的平均数作为预测目标变量的值

我们使用房屋数据集中的所有特征来拟合一个随机森林回归模型,其中60%的样本用于模型的训练,剩余的40%用来对模型进行评估

X = df.iloc[:, :-1].values
y = df['MEDV'].values

X_train, X_test, y_train, y_test = train_test_split(
    X, y, test_size=0.4, random_state=1)
from sklearn.ensemble import RandomForestRegressor

forest = RandomForestRegressor(n_estimators=1000, 
                               criterion='mse', 
                               random_state=1, 
                               n_jobs=-1)
forest.fit(X_train, y_train)
y_train_pred = forest.predict(X_train)
y_test_pred = forest.predict(X_test)

print('MSE train: %.3f, test: %.3f' % (
        mean_squared_error(y_train, y_train_pred),
        mean_squared_error(y_test, y_test_pred)))
print('R^2 train: %.3f, test: %.3f' % (
        r2_score(y_train, y_train_pred),
        r2_score(y_test, y_test_pred)))
MSE train: 1.641, test: 11.056
R^2 train: 0.979, test: 0.878
plt.scatter(y_train_pred,  
            y_train_pred - y_train, 
            c='black', 
            marker='o', 
            s=35,
            alpha=0.5,
            label='Training data')
plt.scatter(y_test_pred,  
            y_test_pred - y_test, 
            c='lightgreen', 
            marker='s', 
            s=35,
            alpha=0.7,
            label='Test data')

plt.xlabel('Predicted values')
plt.ylabel('Residuals')
plt.legend(loc='upper left')
plt.hlines(y=0, xmin=-10, xmax=50, lw=2, color='red')
plt.xlim([-10, 50])
plt.tight_layout()

plt.show()

output_84_0.png

转载于:https://www.cnblogs.com/LQ6H/p/10547851.html

  • 0
    点赞
  • 8
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值