线性模型的sklearn应用


线性模型利用输入特征的线性函数(linear function)进行预测。

一. linear models for regression

用于回归的线性模型:对单一特征的预测结果是一条直线,两个特征时是一个平面,更高维度(即更多特征)时是一个超平面。
假设目标y 是特征的线性组合,这是一个非常强的(也有点不现实的)假设。对于有多个特征的数据集而言,线性模型可以非常强大(过拟合的可能性也会变大)。特别地,如果特征数量大于训练数据点的数量,任何目标y 都可以(在训练集上)用线性函数完美拟合。

1.线性回归 linear regression

also (ordinary least squares,OLS,普通最小二乘法)
目标:使得对训练集的预测值与真实的目标值y之间的均方误差最小。
均方误差(mean squared error):预测值与真实值之差的平方和除以样本数。
特点:线性回归没有参数,这是一个优点,但也因此无法控制模型的复杂度。
一般格式:

from sklearn.linear_model import LinearRegression

X_train, X_test, y_train, y_test = train_test_split(X, y, random_state=42)
# 实例化模型并拟合训练集,得到拟合后的模型
lr = LinearRegression().fit(X_train, y_train)
# 查看模型参数  
lr.coef_       # 系数W
lr.intercept_  # 截距或偏移b
# 查看拟合优度
lr.score(X_train,y_train)  # 训练集的R方
lr.score(X_test, y_test)   # 测试集的R方
  1. lr.coef_ ,lr.intercept_:结尾处奇怪的下划线。scikit-learn总是将从训练数据中得出的值保存在以下划线结尾的属性中。为了与用户设置的参数区分开。
  2. 训练集和测试集上的分数非常接近。这说明可能存在欠拟合,而不是过拟合。
应用:波士顿房价数据集

506 个样本和13个特征。

from sklearn.datasets import load_boston  # 函数名
from sklearn.model_selection import train_test_split

data = load_boston()
X_train,X_test,y_train,y_test = train_test_split(data.data,data.target,random_state=42)
data.keys()
print(data.DESCR)
## 拟合模型,查看参数
lr = LinearRegression().fit(X_train, y_train)  
print(lr.score(X_train,y_train))  # 0.7480872598623441
print(lr.score(X_test, y_test))   # 0.6844267283527123
print(lr.coef_)  # 13个特征的系数组成的array
print(lr.intercept_)

在这里插入图片描述
过拟合的明显标志:训练集和测试集之间的性能差别较大,
一种解决方法:找到一个可以控制复杂度的模型。例如岭回归和lasso

2. 岭回归 ridge regression

标准线性回归最常用的替代方法之一,适用于明显过拟合的回归。
产生结果:测试集上拟合效果明显增加,训练集上效果略低。

特点: 岭回归中,对系数(w)的选择不仅要拟合训练数据,还要拟合附加约束,还希望系数尽量小。即:w 的所有元素都应接近于0(即模型更简单,所以更不容易过拟合)。
直观上来看,这意味着每个特征对输出的影响应尽可能小(即斜率很小),同时仍给出很好的预测结果。这种约束是正则化的一个例子。

正则化(regularization): 对模型做显式约束,降低模型复杂度,以避免过拟合。(约束模型,让模型不那么复杂,是对模型复杂的惩罚)

岭回归用到的这种被称为L2 正则化
从数学的观点来看,Ridge 惩罚了系数的L2 范数或w 的欧式长度。

原理:Ridge 是一种约束更强的模型,所以更不容易过拟合。复杂度更小的模型意味着在训练集上的性能更差,但泛化性能更好。 如果我们只对泛化性能感兴趣,所以应该选择Ridge 模型而不是LinearRegression 模型。

应用

仍使用上述数据集

from sklearn.linear_model import Ridge

ridge = Ridge().fit(X_train,y_train)
print(ridge.score(X_train,y_train))  # 0.7461161787884156
print(ridge.score(X_test, y_test))   # 0.6789748327846077

Ridge 模型可以人为在模型的简单性与训练集性能之间做出权衡。通过设置alpha 参数。

alpha 参数:简单性和训练集性能二者对于模型的重要程度。
默认alpha=1.0。alpha 的最佳设定取决于具体数据集。
增大alpha 会增强正则化的约束,避免过拟合,使得系数更加趋向于0,降低训练集性能, 但可能 会提高泛化性能,只是可能。
对于非常小的alpha 值,系数几乎没有受到限制,会得到与LinearRegression 类似的模型:没有做正则化的线性回归(即alpha=0)

调整alpha:

## alpha=10
ridge10 = Ridge(alpha=10).fit(X_train,y_train)  # 增大alpha使得岭回归的约束效果更强
print(ridge10.score(X_train,y_train))  # 0.7398240895568371
print(ridge10.score(X_test, y_test))   # 0.6724237562438147

## alpha=0.1
ridge01 = Ridge(alpha=0.1).fit(X_train,y_train)  # 增大alpha使得岭回归的约束效果更强
print(ridge01.score(X_train,y_train))  # 0.7480303017255328
print(ridge01.score(X_test, y_test))   # 0.6838049959091365
数据量对模型性能的影响

将模型性能作为数据集大小的函数进行绘图,这样的图像叫作学习曲线

由于岭回归是正则化的,因此训练分数要整体低于线性回归,但岭回归的测试分数更高,特别是对较小的子数据集。如果少于400 个数据点,线性回归学不到任何内容。随着模型可用的数据越来越多,两个模型的性能都在提升,最终线性回归的性能可以追上岭回归。
All in all,如果有足够多的训练数据,正则化变得不那么重要,岭回归和线性回归将具有相同的性能。(因为如果添加更多数据,模型将更加难以过拟合或记住所有的数据)

3. Lasso

使用L1 正则化来约束系数使其接近于0。
L1 正则化的结果是,使用lasso 时某些系数刚好为0。这说明某些特征被模型完全忽略。
可以看作是一种自动化的特征选择:模型更容易解释,也可以呈现模型最重要的特征。

由于用到的特征数少,所以训练集和测试集性能都低于前两种模型。

from sklearn.linear_model import Lasso

lasso = Lasso().fit(X_train,y_train)
print(lasso.score(X_train,y_train))  # 0.6948040743556284
print(lasso.score(X_test, y_test))   # 0.6516957380017043
print(np.sum(lasso.coef_ != 0))  # 用到了几个特征  10
  1. 正则化参数 alpha:表示约束的程度,控制系数趋于0的强度。默认alpha=1.0。
    α 越小越接近普通回归。欠拟合时减小 α,过拟合时增大。
  2. 参数 max_iter:迭代的最大次数

调整参数:

# alpha变小,模型变复杂,更接近于普通回归
lasso001 = Lasso(alpha=0.01,max_iter=100000).fit(X_train,y_train)  
print(lasso001.score(X_train,y_train))  # 0.7476750179795366
print(lasso001.score(X_test, y_test))   # 0.6828293208366181
print(np.sum(lasso001.coef_ != 0))      # 13

Conclusion:
实践中,一般首选岭回归。
何时考虑Lasso?

  1. 特征很多,你认为只有几个是重要的,选择Lasso 可能更好。
  2. 想要一个容易解释的模型,因为Lasso只选择了一部分输入特征(根本原因是使用了L1正则化)。

scikit-learn 还提供了ElasticNet类,结合了Lasso 和Ridge 的惩罚项。在实践中,这种结合的效果最好,不过代价是要调节两个参数:一个用于L1 正则化,一个用于L2 正则化。

二. linear models for classification

二分类:ŷ = w[0] * x[0] + w[1] * x[1] + …+ w[p] * x[p] + b > 0
前面类似线性回归公式,但没有返回特征的加权求和,而是为预测设置了阈值0。如果函数值小于0,预测类别-1;
如果函数值大于0,预测类别+1。
对于所有用于分类的线性模型,这个预测规则都是通用的。

回归与分类的模型区别:对于用于回归的线性模型,输出ŷ 是特征的线性函数,是直线、平面或超平面(对于更高维的数据集)。对于用于分类的线性模型,决策边界是输入的线性函数。换句话说,(二元)线性分类器是利用直线、平面或超平面来分开两个类别的分类器。

最常见的两种线性分类算法

  1. Logistic 回归(logistic regression):在linear_model.LogisticRegression 中实现。虽然名字中含有regression,但它是一种分类算法。
  2. 线性支持向量机(linear support vector machine, 线性SVM), 后者在svm.LinearSVC(SVC 代表支持向量分类器)中实现。

两个模型都默认使用L2 正则化,就像Ridge 回归。
决定正则化强度的参数:C。
C 值越大,正则化越弱,模型将尽可能拟合训练集,强调每个数据点都分类正确的重要性,但可能无法掌握类别的整体分布,有过拟合的倾向,避免欠拟合。
C 值越小,正则化越强,模型更强调使系数向量(w)接近于0,尽量适应“大多数”数据点,避免过拟合,但是可能欠拟合。

from sklearn.linear_model import LogisticRegression
from sklearn.svm import LinearSVC

与回归类似,用于分类的线性模型在低维空间中可能非常受限,在高维空间中,会变得非常强大。注意:当考虑更多特征时,避免过拟合变得越来越重要。

1. Logistic回归-乳腺癌数据集
from sklearn.datasets import load_breast_cancer
cancer = load_breast_cancer()

X_train,X_test,y_train,y_test = train_test_split(cancer.data,cancer.target,stratify=cancer.target,random_state=42)
logreg = LogisticRegression().fit(X_train,y_train)
print(logreg.score(X_train,y_train))  # 0.9553990610328639
print(logreg.score(X_test,y_test))    # 0.958041958041958

C=1 的默认值给出了相当好的性能,但由于训练集和测试集的性能非常接近,所以模型很可能是欠拟合的。增大C试试:

## C=100
logreg100 = LogisticRegression(C=100).fit(X_train,y_train)
print(logreg100.score(X_train,y_train))  # 0.971830985915493
print(logreg100.score(X_test,y_test))    # 0.965034965034965

增大C,得到更高的训练集精度,也得到了稍高的测试集精度,这也证实了我们的直觉,即更复杂的模型应该性能更好。

减小C,可能欠拟合

## C=0.01
logreg001 = LogisticRegression(C=0.01).fit(X_train,y_train)
print(logreg001.score(X_train,y_train))  # 0.9342723004694836
print(logreg001.score(X_test,y_test))    # 0.9300699300699301

画图查看三种参数对系数的效果:

logreg.coef_.shape  # (1, 30)

## 正则化参数C 取三个不同的值时模型学到的系数
%matplotlib notebook  # 比inline显示的清晰

fig = plt.figure(figsize=(8,6))
plt.plot(logreg.coef_.T,marker='o',label='C=1',linestyle='')  # 30个特征的系数矩阵转置 30*1
plt.plot(logreg100.coef_.T,marker='^',label='C=100',linestyle='')  # 点是上三角形
plt.plot(logreg001.coef_.T,marker='s',label='C=0.01',linestyle='')  # 点是方形
plt.hlines(y=0,xmin=0,xmax=cancer.data.shape[1])  # 添加水平线
plt.xticks(range(cancer.data.shape[1]),cancer.feature_names,rotation=90,fontsize=8)  # x轴刻度范围,标签,角度,字号
plt.ylim(-5,5)
plt.xlabel('Coefficient index')
plt.ylabel('Coefficient magnitude')
plt.legend()
# 由于x坐标标签很长,notebook显示不全,需要调整下方比例
plt.subplots_adjust(bottom=0.32)  # left=0.18, wspace=0.25, hspace=0.25,, top=0.91   # 周边在子图中占多大比例

在这里插入图片描述
可以看出,第三个系数在不同的C值下正负不同,说明该系数在不同的C值下被当做不同类的指标,说明对系数的解释不是绝对的,跟具体模型有关。所以对线性模型系数的解释应该始终持保留态度。

?plt.hlines :Plot horizontal lines at each y from xmin to xmax. 画水平线
plt.hlines(y, xmin, xmax, colors=‘k’, linestyles=‘solid’, label=’’)

用于二分类的线性模型:penalty 参数,选择哪种正则化方式,
‘l1’: 模型只使用特征的一个子集
‘l2’:使用全部特征

如果想要一个可解释性更强的模型,使用L1 正则化可能更好,因为它约束模型只使用少数几个特征。

## 参数penalty选择正则化方式
%matplotlib notebook

fig = plt.figure(figsize=(7,6))
plt.hlines(0,xmin=0,xmax=cancer.data.shape[1])
plt.xticks(range(cancer.data.shape[1]),cancer.feature_names,rotation=90,fontsize=8)  # x轴刻度范围标签角度
plt.ylim(-5,5)
plt.xlabel('Coefficient index')
plt.ylabel('Coefficient magnitude')
plt.subplots_adjust(bottom=0.32)  

for C,marker in zip([0.001,1,100],['o','^','s']):
    lr_l1 = LogisticRegression(C=C, penalty="l1").fit(X_train, y_train)
    print(lr_l1.score(X_train, y_train))
    print(lr_l1.score(X_test, y_test))
    
    plt.plot(lr_l1.coef_.T,marker=marker,linestyle='',label='C={:.3f}'.format(C))
    
plt.legend()

在这里插入图片描述
在这里插入图片描述

2. 用于多分类-多分类Logistic与线性SVM

将二分类算法推广到多分类算法的一种常见方法是“一对其余”(one-vs.-rest)方 法。在“一对其余”方法中,对每个类别都学习一个二分类模型,将这个类别与所有其 他类别尽量分开,这样就生成了与类别个数一样多的二分类模型。在测试点上运行所有 二类分类器来进行预测。在对应类别上分数最高的分类器“胜出”,将这个类别标签返回作为预测结果。

多分类Logistic 回归的数学原理稍有不同,但也是对每个类别都有一个系数向量和一个截距,也使用了相同的预测方法。

线性SVM应用

研究一个简单的二维三分类数据集,每个类别的数据都是从一个高斯分布中采样得出的。

?make_blobs :Generate isotropic Gaussian blobs for clustering.生成各向同性的高斯点以进行聚类。
make_blobs(n_samples=100, n_features=2, centers=None, cluster_std=1.0, center_box=(-10.0, 10.0), shuffle=True, random_state=None)

from sklearn.datasets import make_blobs
from sklearn.svm import LinearSVC

# 生成各向同性的高斯点以进行聚类。
X,y = make_blobs(random_state=42)

画出数据:

?sns.scatterplot :Draw a scatter plot with possibility of several semantic groupings.绘制散点图,可能会出现多个语义分组。
sns.scatterplot(x=None, y=None, hue=None, style=None,size=None,data=None, palette=None)
详见docstring

import seaborn as sns
%matplotlib inline

# 不需要构建数据框,画分类散点图
sns.scatterplot(X[:,0],X[:,1],hue=y,hue_order=[0,1,2],palette=['orange','steelblue','green'])  # 分类散点图,hue标注类别,palette调色板
plt.xlabel('Feature0')
plt.ylabel('Feature1')
# plt.legend(['class 0','class 1','class 2'])  设图例不对

报错警示:一开始写错了,将plt.xlabel(name)写成了plt.xlabel=name,这样后面再改回正确的就报错了,因为已经对xlabel函数赋值了,只能重启kernel或者重新导入matplotlib

在这里插入图片描述
训练一个LinearSVC 分类器

linear_svm = LinearSVC().fit(X,y)
print(linear_svm.coef_.shape)  # (3, 2) 因为有三类,每一类有一个系数向量,一共三个,2个特征
print(linear_svm.intercept_.shape)
linear_svm.score(X,y)  # 1.0

?np.linspace : Return evenly spaced numbers over a specified interval.
返回一段区间内均匀分布的点
np.linspace(start, stop, num=50, endpoint=True, retstep=False, # 是否返回间隔
dtype=None, axis=0)

补充:方法链(Method Chaining)

对于返回self对象的方法,可以同时依次调用多个方法,省去中间变量,起到简化代码的作用。但是不建议过度使用,为了代码可读性和必要的嫌疑保存的变量。

class Person:
    def name(self, value):
        self.name = value
        return self
    def age(self, value):
        self.age = value
        return self
    def introduce(self):
        print ("Hello, my name is", self.name, "and I am", self.age, "years old.")

person = Person()
person.name("EarlGrey").age(21).introduce()
# Hello, my name is EarlGrey and I am 21 years old.

例如上面的用一行代码初始化模型同时拟合模型:

logreg = LogisticRegression().fit(X_train,y_train)

——来自于《Python机器学习基础教程》的实践。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值