博雅大数据机器学习十讲第二讲
矩阵的逆
-
对于n*n方阵A,如果存在矩阵B使得AB = BA = I,则称B为A的逆矩阵,记为A-1
-
若A为可逆矩阵,则其逆矩阵是唯一的
-
如何判断矩阵是否可逆?
- 行列式不等于0
- 满秩
- 行(或列)向量组线性无关
- ...
import numpy as np
# 格式化numpy输出
np.set_printoptions(formatter={'float': '{:0.2f}'.format})
A = np.array([[4, 5, 1], [0, 8, 3], [9, 4, 7]])
# 求行列式
print("行列式" + str(np.linalg.det(A)))
B = np.linalg.inv(A) # 求逆
print("A的逆矩阵为B:")
print(B)
print("BA:") # 结果验证
print(B.dot(A))
print("AB:")
print(A.dot(B))
回归:
- 回归如今指的用一个或多个自变量来预测因变量的数学方法
- 在机器学习中,回归指的是一类预测变量为连续值的有监督学习方法
在回归模型中,需要预测的变量叫做因变量,用来解释因变量变化的变量叫做自变量
一元线性回归:
- 模型为y = w1x + w0,其中w0,w1为回归系数
- 给定训练集D = {(x1,y1),...(xn,yn)},我们的目标是找到一条直线y = w1x + w0使得所有样本尽可能落在它的附近。
多元线性回归:
-
y = w1x1 + w2x 2+...+ wdxd +w0
-
训练集D = {(x1,y1),...(xn,yn)}
-
...
-
假设训练集的特征部分记为n*(d+1)矩阵X,其中最后一列取值全为1
-
标签部分记为...
-
最小二乘的参数估计,如果变量之间存在较强的共线性,则XTX近似奇异,对参数的估计变得不准确,造成过度拟合现象。
-
解决方法:正则化,主成分回归、偏最小二乘回归
- 正则化可以减小线性回归的过度拟合和多重共线性等问题
岭回归:
- 岭回归:线性回归目标函数加上对W的惩罚函数\(\lambda||w||^2_2\)
- 线性回归目标函数:\((Xw-y)^T(Xw-y)\)
- 岭回归目标函数:\((Xw-y)^T(Xw-y)+\lambda||w||^2_2\)
- 对w求导并令导数等于零易得:\(\widehat x^{ridge} = (X^TX+\lambda I)^{-1}X^Ty\)
- 根据岭迹做超参数\(\lambda\)的选择(岭迹分析)
LASSO:
- LASSO是一种系数压缩估计法,它的基本思想是通过追求稀疏性自动选择重要的变量
- LASSO的目标函数:\((Xw-y)^T(Xw-y)+\lambda||w||_1\)
- LASSO的解\(\widehat x^{LASSO}\)没有解析表达式,常用的求解算法包括坐标下降法、LARS算法和LSTA算法等
回归模型的评价指标
- 均方误差:\(MSE(y,\widehat y) = \frac{1}{n}\sum^n_{i = 1}(y_i-\widehat y_i)^2\)
- 均方根误差:\(RMSE(y,\widehat y) = \sqrt{\frac{1}{n}\sum^n_{i = 1}(y_i-\widehat y_i)^2}\)
- 平均绝对误差:\(MAE(y,\widehat y) = \frac{1}{n}\sum^n_{i = 1}|y_i-\widehat y_i|\)
- 决定系数:\(R^2(y,\widehat y) = 1 - \frac{SS_{res}}{SS_{tot}} = 1 - \frac{\sum^n_{i = 1}(y_i-\widehat y_i)^2}{\sum^n_{i = 1}(y_i-\bar y_i)^2}\)
案例:使用回归预测模型预测鲍鱼年龄
- 使用Python实现线性回归和岭回归算法,并与Sklearn中的实现进行对比
- 借助Sklearn工具,对线性回归、岭回归和LASSO三种模型的预测效果使用MAE和决定系数进行效果评估
- 残差图和正则化路径对模型表现进行分析
-
读入数据:
-
鲍鱼是一种贝类,在世界许多地方被认为是美味佳肴。是铁和泛酸的极好来源,也是澳大利亚、美洲和东亚的营养食品资源和农业。100克鲍鱼就能给人体提供超过 20% 上述每日摄入营养素。鲍鱼的经济价值与年龄正相关。因此,准确检测鲍鱼的年龄对养殖户和消费者确定鲍鱼的价格具有重要意义。
然而,目前确定鲍鱼年龄的技术是相当昂贵和低效的。农场主通常把鲍鱼的壳割下来,用显微镜数鲍鱼环的数量,以估计鲍鱼的年龄。因此判断鲍鱼的年龄很困难,主要是因为鲍鱼的大小不仅取决于年龄,而且还取决于食物的供应情况。此外,鲍鱼有时会形成所谓的“发育不良”群体,这些群体的生长特性与其他鲍鱼种群有很大不同。这种复杂的方法增加了成本,限制了应用范围。本案例的目标是使用机器学习中的回归模型,找出最佳的指标来预测鲍鱼环数,进而预测鲍鱼的年龄。
import pandas as pd import warnings warnings.filterwarnings('ignore') data = pd.read_csv("./input/abalone_dataset.csv") data.head()
数据集一共有 4177 个样本,每个样本有 9 个特征。其中
rings
为鲍鱼环数,能够代表鲍鱼年龄,是预测变量。除了sex
为离散特征,其余都为连续变量。首先借助
seaborn
中的countplot
函数绘制条形图,观察sex
列的取值分布情况。import seaborn as sns import matplotlib.pyplot as plt %matplotlib inline sns.countplot(x = "sex", data = data)
对于连续特征,可以使用
seaborn
的distplot
函数绘制直方图观察特征取值情况。我们将 8 个连续特征的直方图绘制在一个 4 行 2 列的子图布局中。i = 1 # 子图记数 plt.figure(figsize=(16, 8)) for col in data.columns[1:]: plt.subplot(4,2,i) i = i + 1 sns.distplot(data[col]) plt.tight_layout()
sns.pairplot(data,hue="sex")
-
从以上连续特征之间的散点图我们可以看到一些基本的结果:
- 例如从第一行可以看到鲍鱼的长度
length
和鲍鱼直径diameter
、鲍鱼高度height
存在明显的线性关系。鲍鱼长度与鲍鱼的四种重量之间存在明显的非线性关系。 - 观察最后一行,鲍鱼环数
rings
与各个特征均存在正相关性,其中与height
的线性关系最为直观。 - 观察对角线上的直方图,可以看到幼鲍鱼(
sex
取值为“I”)在各个特征上的取值明显小于其他成年鲍鱼。而雄性鲍鱼(sex
取值为“M”)和雌性鲍鱼(sex
取值为“F”)各个特征取值分布没有明显的差异。
为了定量地分析特征之间的线性相关性,我们计算特征之间的相关系数矩阵,并借助热力图将相关性可视化。
corr_df = data.corr()
corr_df
fig, ax = plt.subplots(figsize=(12, 12))
## 绘制热力图
ax = sns.heatmap(corr_df,linewidths=.5,cmap="Greens",annot=True,xticklabels=corr_df.columns, yticklabels=corr_df.index)
ax.xaxis.set_label_position('top')
ax.xaxis.tick_top()
-
鲍鱼数据预处理
- 对
sex
特征进行 OneHot 编码:使用Pandas
的get_dummies
函数对sex
特征做 OneHot 编码处理。
sex_onehot = pd.get_dummies(data["sex"], prefix="sex") data[sex_onehot.columns] = sex_onehot data.head(2)
- 添加取值为 1 的特征:在后续实现线性回归模型时,需要使用设计矩阵 \(\mathbf{X}\),其最后一列取值为 1 ,给数据添加一列
ones
。
data["ones"] = 1 data.head(2)
- 根据鲍鱼环计算年龄
一般每过一年,鲍鱼就会在其壳上留下一道深深的印记,这叫生长纹,就相当于树木的年轮。在本数据集中,我们要预测的是鲍鱼的年龄,可以通过环数
rings
加上 1.5 得到。data["age"] = data["rings"] + 1.5
- 对
data.head(2)
![image-20210203110022724](https://img2020.cnblogs.com/blog/1717261/202102/1717261-20210203113416563-1298425857.png)
- 构造两组特征集
- 将预测目标设置为 `age` 列,然后构造两组特征,一组包含 `ones`,一组不包含 `ones` 。对于 `sex` 相关的列,我们只使用 `sex_F` 和 `sex_M`。
```python
y = data["age"]
features_with_ones = ["length","diameter","height","whole_weight","shucked_weight","viscera_weight","shell_weight","sex_F","sex_M","ones"]
features_without_ones=["length","diameter","height","whole_weight","shucked_weight","viscera_weight","shell_weight","sex_F","sex_M"]
X = data[features_with_ones]
将数据集随机划分为训练集和测试集,其中 80% 样本为训练集,剩余 20% 样本为测试集。
from sklearn import model_selection
X_train,X_test,y_train,y_test = model_selection.train_test_split(X,y,test_size=0.2, random_state=111)
-
接下来实现线性回归和岭回归
- 如果矩阵 \(X^{\rm T}X\) 为满秩(行列式不为 0 ),则简单线性回归的解为 \(\hat{\mathbf{w}} = (X^{\rm T}X)^{-1}X^{\rm T}\mathbf{y}\) 。实现一个函数
linear_regression
,其输入为训练集特征部分和标签部分,返回回归系数向量。 我们借助numpy
工具中的np.linalg.det
函数和np.linalg.inv
函数分别求矩阵的行列式和矩阵的逆。
- 如果矩阵 \(X^{\rm T}X\) 为满秩(行列式不为 0 ),则简单线性回归的解为 \(\hat{\mathbf{w}} = (X^{\rm T}X)^{-1}X^{\rm T}\mathbf{y}\) 。实现一个函数
import numpy as np
def linear_regression(X,y):
w = np.zeros_like(X.shape[1])
if np.linalg.det(X.T.dot(X)) != 0 :
w = np.linalg.inv(X.T.dot(X)).dot(X.T).dot(y)
return w
w1 = linear_regression(X_train,y_train)
w1 = pd.DataFrame(data = w1,index = X.columns,columns = ["numpy_w"])
w1.round(decimals=2)
可见我们求得的模型为:
sklearn
中的 linear_model
模块实现了常见的线性模型,包括线性回归、岭回归和 LASSO 等。对应的算法和类名如下表所示。
算法 | 类名 |
---|---|
线性回归 | linear_model.LinearRegression |
岭回归 | linear_model.Ridge |
LASSO | linear_model.Lasso |
下面我们使用 LinearRegression
构建线性回归模型。注意,此时传给 fit
方法的训练集的特征部分不包括 ones
列。模型训练完成后,lr.coef_
属性和 lr.intercept_
属性分别保存了学习到的回归系数向量和截距项。
from sklearn import linear_model
lr = linear_model.LinearRegression()
lr.fit(X_train[features_without_ones],y_train)
w_lr = []
w_lr.extend(lr.coef_)
w_lr.append(lr.intercept_)
w1["lr_sklearn_w"] = w_lr
w1.round(decimals=2)
岭回归的解为 \(\hat{\mathbf{w}}^{Ridge} = (X^{\rm T}X + \lambda I)^{-1}X^{\rm T}\mathbf{y}\) ,其中 \(\lambda\) 为正则系数,\(I\) 为单位矩阵。我们实现 ridge_regression
函数来求解,它包括三个参数:训练集特征矩阵 X
, 训练集标签向量 y
,以及正则化次数 ridge_lambda
。
单位矩阵可使用 np.eye
函数自动生成,其大小为 \((d+1)\),即与特征矩阵 X
的列数(X.shape[1]
)相同。
在鲍鱼训练集上使用 ridge_regression
函数训练岭回归模型,正则化系数设置为 1 。
def ridge_regression(X,y,ridge_lambda):
penalty_matrix = np.eye(X.shape[1])
penalty_matrix[X.shape[1] - 1][X.shape[1] - 1] = 0
w = np.linalg.inv(X.T.dot(X) + ridge_lambda * penalty_matrix).dot(X.T).dot(y)
return w
w2 = ridge_regression(X_train,y_train,1.0)
w1["numpy_ridge_w"] = w2
w1.round(decimals=2)
#与 sklearn 中岭回归对比,同样正则化系数设置为 1 。
from sklearn.linear_model import Ridge
ridge = linear_model.Ridge(alpha=1.0)
ridge.fit(X_train[features_without_ones],y_train)
w_ridge = []
w_ridge.extend(ridge.coef_)
w_ridge.append(ridge.intercept_)
w1["ridge_sklearn_w"] = w_ridge
w1.round(decimals=2)
使用 LASSO 构建鲍鱼年龄预测模型
from sklearn.linear_model import Lasso
lasso = linear_model.Lasso(alpha=0.01)
lasso.fit(X_train[features_without_ones],y_train)
print(lasso.coef_)
print(lasso.intercept_)
鲍鱼年龄预测模型效果评估
我们已经使用训练集构建了 lr
, ridge
和 lasso
三个模型。我们首先来看下这三个模型测试集中,均方误差和决定系数 \(R^2\) 分别为多少。
from sklearn.metrics import mean_squared_error
from sklearn.metrics import mean_absolute_error
y_test_pred_lr = lr.predict(X_test.iloc[:,:-1])
print(round(mean_absolute_error(y_test,y_test_pred_lr),4))
y_test_pred_ridge = ridge.predict(X_test[features_without_ones])
print(round(mean_absolute_error(y_test,y_test_pred_ridge),4))
y_test_pred_lasso = lasso.predict(X_test[features_without_ones])
print(round(mean_absolute_error(y_test,y_test_pred_lasso),4))
from sklearn.metrics import r2_score
print(round(r2_score(y_test,y_test_pred_lr),4))
print(round(r2_score(y_test,y_test_pred_ridge),4))
print(round(r2_score(y_test,y_test_pred_lasso),4))
#残差图
plt.figure(figsize=(9, 6))
y_train_pred_ridge = ridge.predict(X_train[features_without_ones])
plt.scatter(y_train_pred_ridge, y_train_pred_ridge - y_train, c="g", alpha=0.6)
plt.scatter(y_test_pred_ridge, y_test_pred_ridge - y_test, c="r",alpha=0.6)
plt.hlines(y=0, xmin=0, xmax=30,color="b",alpha=0.6)
plt.ylabel("Residuals")
plt.xlabel("Predict")
#岭迹
alphas = np.logspace(-10,10,20)
coef = pd.DataFrame()
for alpha in alphas:
ridge_clf = Ridge(alpha=alpha)
ridge_clf.fit(X_train[features_without_ones],y_train)
df = pd.DataFrame([ridge_clf.coef_],columns=X_train[features_without_ones].columns)
df['alpha'] = alpha
coef = coef.append(df,ignore_index=True)
coef.head().round(decimals=2)
#绘图
plt.rcParams['figure.dpi'] = 300 #分辨率
plt.figure(figsize=(9, 6))
coef['alpha'] = coef['alpha']
for feature in X_train.columns[:-1]:
plt.plot('alpha',feature,data=coef)
ax = plt.gca()
ax.set_xscale('log')
plt.legend(loc='upper right')
plt.xlabel(r'$\alpha$',fontsize=15)
plt.ylabel('系数',fontsize=15)
plt.show()
#LASSO 的正则化路径
coef = pd.DataFrame()
for alpha in np.linspace(0.0001,0.2,20):
lasso_clf = Lasso(alpha=alpha)
lasso_clf.fit(X_train[features_without_ones],y_train)
df = pd.DataFrame([lasso_clf.coef_],columns=X_train[features_without_ones].columns)
df['alpha'] = alpha
coef = coef.append(df,ignore_index=True)
coef.head()
#绘图
plt.figure(figsize=(9, 6))
for feature in X_train.columns[:-1]:
plt.plot('alpha',feature,data=coef)
plt.legend(loc='upper right')
plt.xlabel(r'$\alpha$',fontsize=15)
plt.ylabel('系数',fontsize=15)
plt.show()
在本案例中,我们使用 Numpy
分别实现了线性回归和岭回归,并与 Sklearn
的实现进行了对比。在一个鲍鱼数据集上,分别使用线性回归和正则化的线性回归构建了鲍鱼年龄预测模型。