【理論篇】K 近鄰算法 - 多變量預測

連載的上兩篇文章,小魚為大家介紹了 KNN 算法的原理,并使用 Python 實現(xiàn)了單變量 KNN 模型的租金預測。此外還介紹了訓練集和測試集的劃分,并使用 RMSE 來評估實現(xiàn)的 KNN 模型。

本節(jié),小魚將為大家?guī)矶嘧兞繉崿F(xiàn)的 KNN 模型。

讀取數(shù)據(jù)集

首先,我們讀取數(shù)據(jù)集,并進行洗牌,將 price 列的數(shù)據(jù)類型轉換為 float

import pandas as pd

features = ['accommodates','bedrooms','bathrooms','beds','price','minimum_nights','maximum_nights','number_of_reviews']
df = pd.read_csv('listings.csv')[features]
df = df.sample(frac=1, random_state=0)
df.price = df.price.str.replace('\$|,','').astype(float)
df.head()

讀取的數(shù)據(jù)集如下:

查看數(shù)據(jù)集的信息:

>> df.info()
<class 'pandas.core.frame.DataFrame'>
Int64Index: 3723 entries, 2645 to 2732
Data columns (total 8 columns):
 #   Column             Non-Null Count  Dtype  
---  ------             --------------  -----  
 0   accommodates       3723 non-null   int64  
 1   bedrooms           3702 non-null   float64
 2   bathrooms          3696 non-null   float64
 3   beds               3712 non-null   float64
 4   price              3723 non-null   float64
 5   minimum_nights     3723 non-null   int64  
 6   maximum_nights     3723 non-null   int64  
 7   number_of_reviews  3723 non-null   int64  
dtypes: float64(4), int64(4)
memory usage: 261.8 KB

通過 Non-Null Count 我們發(fā)現(xiàn)很多列的非空值個數(shù)都小于樣本數(shù) 3723 ,使用 dropna 將包含缺失值的樣本刪除:

>> df.dropna(inplace=True)
>> df.info()
<class 'pandas.core.frame.DataFrame'>
Int64Index: 3671 entries, 2645 to 2732
Data columns (total 8 columns):
 #   Column             Non-Null Count  Dtype  
---  ------             --------------  -----  
 0   accommodates       3671 non-null   int64  
 1   bedrooms           3671 non-null   float64
 2   bathrooms          3671 non-null   float64
 3   beds               3671 non-null   float64
 4   price              3671 non-null   float64
 5   minimum_nights     3671 non-null   int64  
 6   maximum_nights     3671 non-null   int64  
 7   number_of_reviews  3671 non-null   int64  
dtypes: float64(4), int64(4)
memory usage: 258.1 KB

數(shù)據(jù)預處理 - 標準化

我們接下來要進行多變量的 KNN 模型預測,那就須要平等地對待每一個特征,而不能主觀地認為某些特征重要,某些特征不重要。那如何平等地對待每個特征呢?

我們來想一下,KNN 是通過計算距離,選取離自己最近的 K 個樣本,那在計算距離的時候:

如果特征 q1 的值本身就是比較大的數(shù),比如房屋的面積,而特征 q2 的值本身都是比較小的數(shù),比如衛(wèi)生間的數(shù)量,那么在計算距離的時候,是不是 (q1-p1)**2 也要遠大于 (q2-p2)**2 的值呢?

這樣的話,我們在計算距離的時候,最終的距離基本上都是由值比較大的 q1 特征所決定的。

注:在最開始的時候,我們認為數(shù)據(jù)之間的重要層度是一樣的,并不想偏袒哪個特征,所以標準化 / 歸一化是必要的步驟。

為此,數(shù)據(jù)預處理工作中有一步非常重要,就是數(shù)據(jù)的標準化、歸一化。

數(shù)據(jù)的標準化
標準化是指將所有特征都變化為均值為 0,標準差為 1 的分布:

數(shù)據(jù)進行標準化之后,特征的分布將會關于原點對稱,并且特征之的取值范圍都是在一個較小的區(qū)間上浮動的。

數(shù)據(jù)的歸一化
另一種方法叫做歸一化:

特征經(jīng)過上述變換之后,必然會位于 0-1 的區(qū)間,這樣做可以抑制離群值對結果的影響。

下面,我們使用 sklearn 庫為我們提供的數(shù)據(jù)標準化工具 StandardScaler

>> from sklearn.preprocessing import StandardScaler
>> StandardScaler().fit_transform(df)
array([[-0.09760238, -0.2495011 , -0.43921129, ...,  1.31682367,
        -0.01660143,  0.30422279],
       [ 1.89848862,  0.94000348,  1.26516995, ..., -0.34142104,
        -0.01657476, -0.48257057],
       [-0.09760238, -0.2495011 , -0.43921129, ..., -0.06504692,
        -0.01657476,  0.03055553],
       ...,
       [-1.09564788, -0.2495011 ,  4.67393243, ...,  0.48770132,
        -0.01659621, -0.37994535],
       [-0.59662513, -0.2495011 , -0.43921129, ..., -0.06504692,
        -0.01660623, 10.1220355 ],
       [ 2.39751137,  2.12950806,  1.26516995, ..., -0.34142104,
        -0.01657476, -0.51677897]])

注:sklearn 庫是最受歡迎的機器學習庫,它為我們提供了非常多實用的機器學習模型、數(shù)據(jù)預處理工具等等。網(wǎng)址附上:https://scikit-learn.org/stable/

經(jīng)過標準化之后,數(shù)據(jù)變得有正有負,并且整體都比較小了。這里,我們需要注意的是 StandardScaler().fit_transform(df) 返回的是 ndarray 數(shù)組結構,我們可以借助賦值的技巧,轉換為 DataFrame

scaler = StandardScaler()
df[features] = scaler.fit_transform(df[features])
df.head()

數(shù)據(jù)預處理結果:

這個時候,我們可能會有一個疑問?數(shù)據(jù)標準化之后,預測到的價格似乎也是個沒有實際意義的數(shù)字,那還能轉換成本來的價格嗎?

使用 scaler.inverse_transform 即可得到標準化之前的數(shù)值:

>> scaler.inverse_transform(df)[0]
array([  3.,   1.,   1.,   1.,  75.,   7., 180.,  24.])

使用 Python 實現(xiàn)多變量 KNN

數(shù)據(jù)預處理完成之后,我們就可以將數(shù)據(jù)集劃分為訓練集和測試集,來進行模型的構建了。

下面代碼中的 pridict_price 函數(shù)實現(xiàn)了 KNN 模型,接收一個樣本和選用的用本特征,返回預測的結果值。

from scipy.spatial import distance

norm_train_df = df.copy().iloc[:2792]
norm_test_df = df.copy().iloc[2792:]

def predict_price(my_sample, features):
    norm_train_df['distance'] = distance.cdist(
        norm_train_df[features], 
        [my_sample[features]]
    )
    knn_5 = norm_train_df.sort_values('distance').price.iloc[:5]
    return knn_5.mean()

其中距離的計算,使用了 scipy 提供的工具 dictance.cdist ,cdist 的第二個參數(shù)外面加了一層 [] ,這是因為 cdict 的參數(shù)必須是兩個 2 維的數(shù)組或集合。

cols = ['accommodates', 'bathrooms']

norm_test_df['predict_price'] = norm_test_df[cols].apply(
    predict_price, 
    features=cols, 
    axis=1
)

norm_test_df[['price', 'predict_price']].head()

apply 方法將 predict_price 函數(shù)應用到測試集中的每個樣本上,計算測試集樣本的預測接歌。最終得到的測試集預測價格如下:

最后,通過預測值和真實值之間的 RMSE 來評估上述 KNN 模型:

>> norm_test_df['squared_error'] = (norm_test_df.predict_price - norm_test_df.price) ** 2
>> mse = norm_test_df.squared_error.mean()
>> rmse = mse ** (1/2)
>> rmse
0.8112504245382595

使用 Sklearn 實現(xiàn) KNN

使用 sklearn 庫實現(xiàn) KNN 模型的步驟非常簡單:首先我們此處的任務是預測租金,因此是一個回歸問題,導入 KNeighborsRegressor 創(chuàng)建回歸問題的 KNN 模型。

from sklearn.neighbors import KNeighborsRegressor

knn = KNeighborsRegressor()

訓練模型:分別傳入訓練集的特征數(shù)據(jù)集和預測數(shù)據(jù)

cols = ['accommodates','bedrooms']
knn.fit(norm_train_df[cols], norm_train_df['price'])

使用測試集預測結果:

>> knn.predict(norm_test_df[cols])[:5]
array([ 0.6293575 ,  1.03653751,  0.49266135, -0.24026267, -0.42058525])

評估模型:使用 sklearn.metrics 提供的 mean_squared_error 計算均方誤差 SME,開根號就是均方根誤差 RMSE:

>> from sklearn.metrics import mean_squared_error
>> mean_squared_error(norm_test_df['price'], knn.predict(norm_test_df[cols])) ** (1/2)
0.8305992928886629

下面,我們嘗試加入更多的特征來構建 KNN 回歸模型:

>> knn = KNeighborsRegressor()
>> cols = ['accommodates','bedrooms','bathrooms','beds','minimum_nights','maximum_nights','number_of_reviews']
>> knn.fit(norm_train_df[cols], norm_train_df.price)
>> mean_squared_error(norm_test_df['price'], knn.predict(norm_test_df[cols]) )** (1/2)
0.7326399776714614

加入更多特征之后,RMSE 由之前兩個特征時的 0.83 下降到了 0.73,誤差變小了,模型的準確度得到了提高。

KNN 算法的缺陷

K 近鄰算法的簡單之處在于不需要訓練模型,數(shù)據(jù)做好,直接拿來用就行。

但卻有一個致命的缺點,每次來一條數(shù)據(jù),都要遍歷一次訓練集,找到最接近輸入樣本的數(shù)據(jù),當訓練集樣本數(shù)非常龐大的時候,KNN 的速度將會非常慢,這也是實際應用場景中使用受限的原因。

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯(lián)系作者
【社區(qū)內容提示】社區(qū)部分內容疑似由AI輔助生成,瀏覽時請結合常識與多方信息審慎甄別。
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發(fā)布,文章內容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務。

相關閱讀更多精彩內容

友情鏈接更多精彩內容