獲取案例鏈接、直播課件、數(shù)據(jù)集在本公眾號內(nèi)發(fā)送“機(jī)器學(xué)習(xí)”。
鮑魚是一種貝類,在世界許多地方被認(rèn)為是美味佳肴。是鐵和泛酸的極好來源,也是澳大利亞、美洲和東亞的營養(yǎng)食品資源和農(nóng)業(yè)。100克鮑魚就能給人體提供超過 20% 上述每日攝入營養(yǎng)素。鮑魚的經(jīng)濟(jì)價(jià)值與年齡正相關(guān)。因此,準(zhǔn)確檢測鮑魚的年齡對養(yǎng)殖戶和消費(fèi)者確定鮑魚的價(jià)格具有重要意義。
然而,目前確定鮑魚年齡的技術(shù)是相當(dāng)昂貴和低效的。農(nóng)場主通常把鮑魚的殼割下來,用顯微鏡數(shù)鮑魚環(huán)的數(shù)量,以估計(jì)鮑魚的年齡。因此判斷鮑魚的年齡很困難,主要是因?yàn)轷U魚的大小不僅取決于年齡,而且還取決于食物的供應(yīng)情況。此外,鮑魚有時(shí)會(huì)形成所謂的“發(fā)育不良”群體,這些群體的生長特性與其他鮑魚種群有很大不同。這種復(fù)雜的方法增加了成本,限制了應(yīng)用范圍。本案例的目標(biāo)是使用機(jī)器學(xué)習(xí)中的回歸模型,找出最佳的指標(biāo)來預(yù)測鮑魚環(huán)數(shù),進(jìn)而預(yù)測鮑魚的年齡。
我們首先將鮑魚數(shù)據(jù)集 abalone_dataset.csv
讀取為 Pandas
的 DataFrame
格式。
import pandas as pd
import warnings
warnings.filterwarnings('ignore')
data = pd.read_csv('./input/abalone_dataset.csv')
data.head()
查看數(shù)據(jù)集中樣本數(shù)量和特征數(shù)量。
data.shape
數(shù)據(jù)集一共有 4177 個(gè)樣本,每個(gè)樣本有 9 個(gè)特征。其中 rings
為鮑魚環(huán)數(shù),能夠代表鮑魚年齡,是預(yù)測變量。除了 sex
為離散特征,其余都為連續(xù)變量。
首先借助 seaborn
中的 countplot
函數(shù)繪制條形圖,觀察 sex
列的取值分布情況。
import seaborn as sns
import matplotlib.pyplot as plt
%matplotlib inline
sns.countplot(x = 'sex', data = data)
對于連續(xù)特征,可以使用 seaborn
的 distplot
函數(shù)繪制直方圖觀察特征取值情況。我們將 8 個(gè)連續(xù)特征的直方圖繪制在一個(gè) 4 行 2 列的子圖布局中。
i = 1 # 子圖記數(shù)
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')
從以上連續(xù)特征之間的散點(diǎn)圖我們可以看到一些基本的結(jié)果:
例如從第一行可以看到鮑魚的長度 length
和鮑魚直徑 diameter
、鮑魚高度 height
存在明顯的線性關(guān)系。鮑魚長度與鮑魚的四種重量之間存在明顯的非線性關(guān)系。
觀察最后一行,鮑魚環(huán)數(shù) rings
與各個(gè)特征均存在正相關(guān)性,其中與 height
的線性關(guān)系最為直觀。
觀察對角線上的直方圖,可以看到幼鮑魚(sex
取值為“I”)在各個(gè)特征上的取值明顯小于其他成年鮑魚。而雄性鮑魚(sex
取值為“M”)和雌性鮑魚(sex
取值為“F”)各個(gè)特征取值分布沒有明顯的差異。
為了定量地分析特征之間的線性相關(guān)性,我們計(jì)算特征之間的相關(guān)系數(shù)矩陣,并借助熱力圖將相關(guān)性可視化。
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
特征進(jìn)行 OneHot 編碼使用 Pandas
的 get_dummies
函數(shù)對 sex
特征做 OneHot 編碼處理。
sex_onehot = pd.get_dummies(data['sex'], prefix='sex')
data[sex_onehot.columns] = sex_onehot
data.head(2)
在后續(xù)實(shí)現(xiàn)線性回歸模型時(shí),需要使用設(shè)計(jì)矩陣 ,其最后一列取值為 1 ,給數(shù)據(jù)添加一列 ones
。
data['ones'] = 1
data.head(2)
一般每過一年,鮑魚就會(huì)在其殼上留下一道深深的印記,這叫生長紋,就相當(dāng)于樹木的年輪。在本數(shù)據(jù)集中,我們要預(yù)測的是鮑魚的年齡,可以通過環(huán)數(shù) rings
加上 1.5 得到。
data['age'] = data['rings'] + 1.5
data.head(2)
將預(yù)測目標(biāo)設(shè)置為 age
列,然后構(gòu)造兩組特征,一組包含 ones
,一組不包含 ones
。對于 sex
相關(guān)的列,我們只使用 sex_F
和 sex_M
。
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]
將數(shù)據(jù)集隨機(jī)劃分為訓(xùn)練集和測試集,其中 80% 樣本為訓(xùn)練集,剩余 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)
如果矩陣 為滿秩(行列式不為 0 ),則簡單線性回歸的解為 。實(shí)現(xiàn)一個(gè)函數(shù) linear_regression
,其輸入為訓(xùn)練集特征部分和標(biāo)簽部分,返回回歸系數(shù)向量。我們借助 numpy
工具中的 np.linalg.det
函數(shù)和 np.linalg.inv
函數(shù)分別求矩陣的行列式和矩陣的逆。
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
使用上述實(shí)現(xiàn)的線性回歸模型在鮑魚訓(xùn)練集上訓(xùn)練模型。
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
模塊實(shí)現(xiàn)了常見的線性模型,包括線性回歸、嶺回歸和 LASSO 等。對應(yīng)的算法和類名如下表所示。
算法 | 類名 |
---|---|
線性回歸 | linear_model.LinearRegression |
嶺回歸 | linear_model.Ridge |
LASSO | linear_model.Lasso |
下面我們使用 LinearRegression
構(gòu)建線性回歸模型。注意,此時(shí)傳給 fit
方法的訓(xùn)練集的特征部分不包括 ones
列。模型訓(xùn)練完成后,lr.coef_
屬性和 lr.intercept_
屬性分別保存了學(xué)習(xí)到的回歸系數(shù)向量和截距項(xiàng)。
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)
對比上表可以看到,在訓(xùn)練集上訓(xùn)練鮑魚年齡預(yù)測模型時(shí),我們使用 Numpy
實(shí)現(xiàn)的線性回歸模型與 sklearn
得到的結(jié)果是一致的。
嶺回歸的解為 ,其中 為正則系數(shù), 為單位矩陣。我們實(shí)現(xiàn) ridge_regression
函數(shù)來求解,它包括三個(gè)參數(shù):訓(xùn)練集特征矩陣 X
, 訓(xùn)練集標(biāo)簽向量 y
,以及正則化次數(shù) ridge_lambda
。
單位矩陣可使用 np.eye
函數(shù)自動(dòng)生成,其大小為 ,即與特征矩陣 X
的列數(shù)(X.shape[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
在鮑魚訓(xùn)練集上使用 ridge_regression
函數(shù)訓(xùn)練嶺回歸模型,正則化系數(shù)設(shè)置為 1 。
w2 = ridge_regression(X_train,y_train,1.0)
w1['numpy_ridge_w'] = w2
w1.round(decimals=2)
與 sklearn
中嶺回歸對比,同樣正則化系數(shù)設(shè)置為 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)
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_)
我們已經(jīng)使用訓(xùn)練集構(gòu)建了 lr
, ridge
和 lasso
三個(gè)模型。我們首先來看下這三個(gè)模型測試集中,均方誤差和決定系數(shù) 分別為多少。
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))
殘差圖是一種用來診斷回歸模型效果的圖。在殘差圖中,如果點(diǎn)隨機(jī)分布在0附近,則說明回歸效果較好。如果在殘差圖中發(fā)現(xiàn)了某種結(jié)構(gòu),則說明回歸效果不佳,需要重新建模。
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('系數(shù)',fontsize=15)
plt.show()
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('系數(shù)',fontsize=15)
plt.show()
在本案例中,我們使用 Numpy
分別實(shí)現(xiàn)了線性回歸和嶺回歸,并與 Sklearn
的實(shí)現(xiàn)進(jìn)行了對比。在一個(gè)鮑魚數(shù)據(jù)集上,分別使用線性回歸和正則化的線性回歸構(gòu)建了鮑魚年齡預(yù)測模型。
聯(lián)系客服