機器學習入門實戰——基於knn的airbnb房租預測

鄉間小路發表於2021-02-17

資料讀取

import pandas as pd
features=['accommodates','bathrooms','bedrooms','beds','price','minimum_nights','maximum_nights','number_of_reviews']
dc_listings=pd.read_csv('listings.csv')
dc_listings=dc_listings[features]
print(dc_listings.shape)
dc_listings.head()

執行結果:

K:候選物件個數,近鄰數(如找3個和自己最近的樣本)

 先使用可容納旅客的數量(accommodates)做一個簡單計算,統計與可容納3個旅客相減的情況(當前要估計價格的可容納旅客數為3個)
import numpy as np
our_acc_value=3#房間數為3個
dc_listings['distance']=np.abs(dc_listings.accommodates-our_acc_value)#為dc_listings新增distance列,用於儲存當前房間數與3的差值
dc_listings.distance.value_counts().sort_index()#統計各差值的情況

 輸出:

0.0      3370
1.0     17967
1.5         2
2.0      3865
3.0      1250
4.0       221
5.0       334
6.0        58
7.0       125
8.0        15
9.0        44
10.0        5
11.0       14
12.0        5
13.0       73
Name: distance, dtype: int64
原始資料統計過程中可能會存在一些規律,一般需要進行洗牌操作,打亂原有秩序(使用sample函式)
dc_listings=dc_listings.sample(frac=1,random_state=0)#洗牌 frac:抽取行的比例,1為100%  random_state:0表示不得取重複資料 1表示可以取重複資料
dc_listings=dc_listings.sort_values('distance')#統計差值(房間數-3)的情況 將dc_listings按照distance列排序 將和房間數3最近的放在最前面
dc_listings.price.head()#取前5條的價格 由於資料時亂的,所以id和price均無規律

輸出結果:

2732     $129.00 
14798    $249.00 
27309    $170.00 
20977    $169.00 
11178    $100.00 
Name: price, dtype: object

對價格進行型別轉換,去掉$符號,轉換成float,然後對前五個價格取均值,用前5個的均值來預測當前房價
dc_listings['price']=dc_listings.price.str.replace("\$|,",'').astype(float)
mean_price=dc_listings.price.iloc[:5].mean()
mean_price

輸出:163.4

拿75%的資料作為訓練集,25%的資料作為測試集來進行模型的評估,訓練集和測試集不可重複。

dc_listings.drop('distance',axis=1)#刪除distance列
train_df=dc_listings.copy().iloc[:20544]#27392*0.75行為訓練集
test_df=dc_listings.copy().iloc[20544:]#剩下的作為測試集(27392*0.25)

基於單變數預測價格

#new_listing_value:當前樣本的feature_clolumn(如accomodates)列屬性取值
#feture_column:計算房租使用的單屬性
def predict_price(new_listing_value,feature_column):
    temp_df=train_df#使用訓練集來預測測試集房租結果
    dc_listings[feature_column]=train_df[feature_column].astype(float)#統一將格式轉換成float否則會報錯
    temp_df['distance']=np.abs(train_df[feature_column]-new_listing_value)
    temp_df=temp_df.sort_values('distance')
    knn_5=temp_df.price.iloc[:5]
    predict_price=knn_5.mean()
    return(predict_price)

對測試集的每一條記錄使用accomodates屬性預測其房租價格

#new_listing_value的值即為accommodates的取值
test_df['predicted_price']=test_df.accommodates.apply(predict_price,feature_column='accommodates')
test_df.predicted_price.head()

輸出:

14122    134.0
23556    418.0
16317    134.0
26230    134.0
19769    134.0
Name: predicted_price, dtype: float64

root mean square error(RMSE)均方根誤差

得到每個樣本的預測值後計算均方根誤差用於評估模型(值越小模型越好)

test_df['squared_error']=(test_df['predicted_price']-test_df['price'])**(2)
mse=test_df['squared_error'].mean()
rmse=mse**(1/2)
rmse

輸出結果:

348.689169172284

試試不同的變數(屬性)

for feature in ['accommodates','bedrooms','bathrooms','number_of_reviews']:
    test_df['predicted_price']=test_df[feature].apply(predict_price,feature_column=feature)
    test_df['squared_error']=(test_df['predicted_price']-test_df['price'])**(2)
    mse=test_df['squared_error'].mean()
    rmse=mse**(1/2)
    print("RMSE for the {} column:{}".format(feature,rmse))

輸出:

RMSE for the accommodates column:348.689169172284
RMSE for the bedrooms column:344.64855009943
RMSE for the bathrooms column:361.1230782594195
RMSE for the number_of_reviews column:383.4946020709275

注:由於每個測試集中的樣本都要與訓練集中樣本一一比對,所以上述程式執行時間較長,需要耐心等待……

標準化

 

同時使用多個屬性——如房間數(個位數)和房間面積(幾十甚至上百)進行計算的時候,由於變數取值範圍的不同(取值範圍大的影響較大)會導致對計算結果的不良影響(如計算歐式距離時,房間面積差值平方計算結果通常較大,而房間數差值平方較小),而各個屬性是獨立的即它們是同等重要的,所以需要對資料進行標準化,如採用z-score標準化歸一化等手段進行預處理

 

z-score 標準化(Z-score normalization)

 

要求資料總體均值μ=0 標準差σ=1

轉換公式如下:

其中μ為原始資料均值,?σ為原始資料標準差,經過處理後的資料均值為0,標準差為1。 x-μ是為了讓資料以0為中心分佈,除以標準差σ是讓所有列的取值範圍相近(某一列取值範圍小,標準差也相對較小,除以標準差後值會變大,反之,某一列取值範圍大,標準差也較大,除以標準差後結果就會變小,這樣就達到了使所有列取值範圍相近的目的)。(分子和分母都是計算和均值的差值,分子是個體差值,分母是平均差值——標準差)比如讓房間面積和房屋個數的取值範圍相同。

 

標準差(又叫均方差,標準差能反映一個資料集的離散程度。平均數相同的兩組資料,標準差未必相同。)計算公式

歸一化(Min-Max scaling或normalization,也稱為0-1歸一化)

轉換公式如下:

 

歸一化將數字取值範圍轉換為0-1之間

經過上述處理後,只會改變原始資料的取值範圍,而不會改變其分佈規則(所有的資料處理都必須遵循的原則)。標準化會減小資料的取值範圍,並使資料以0為中心分佈。歸一化使資料都分佈在0-1之間。下面使用sklearn的preprocessing庫進行預處理
import pandas as pd
from sklearn.preprocessing import StandardScaler#從sklearn匯入標準化處理庫
features=['accommodates','bathrooms','bedrooms','beds','price','minimum_nights','maximum_nights','number_of_reviews']
dc_listings=pd.read_csv('listings.csv')
dc_listings=dc_listings[features]
dc_listings['price']=dc_listings.price.str.replace("\$|,",'').astype(float)
dc_listings=dc_listings.dropna()#去除空資料
dc_listings[features]=StandardScaler().fit_transform(dc_listings[features])#資料標準化
normalized_listings=dc_listings
print(dc_listings.shape)
normalized_listings.head()

輸出結果:

(26728, 8)
#獲得標準化後的測試集和訓練集
norm_train_df=normalized_listings.copy().iloc[0:20544]
norm_test_df=normalized_listings.copy().iloc[20544:]

使用scipy(一種專門用於統計學的工具庫)中的工具計算歐式距離

from scipy.spatial import distance
first_listing=normalized_listings.iloc[0][['accommodates','bathrooms']]
sixth_listing=normalized_listings.iloc[5][['accommodates','bathrooms']]
first_sixth_distance=distance.euclidean(first_listing,sixth_listing)#計算第一個樣本點和第六個樣本點之間的歐式距離
first_sixth_distance

輸出:

2.275986934945609

多變數KNN模型

def predict_price_multivariate(new_listing_value,feature_columns):
    temp_df=norm_train_df
    temp_df['distance']=distance.cdist(temp_df[feature_columns],[new_listing_value[feature_columns]])
    #cdist用於計算訓練集到測試集的向量的距離,函式每次接收一個測試集向量
    temp_df=temp_df.sort_values('distance')
    knn_5=temp_df.price.iloc[:5]
    predicted_price=knn_5.mean()
    return(predicted_price)
cols=['accommodates','bedrooms']
#axis=1表示按行計算,針對測試集的每個向量均呼叫函式一次
norm_test_df['predicted_price']=norm_test_df[cols].apply(predict_price_multivariate,feature_columns=cols,axis=1)
norm_test_df['squared_error']=(norm_test_df['predicted_price']-norm_test_df['price'])**[2]
mse=norm_test_df['squared_error'].mean()
rmse=mse**(1/2)
print(rmse)

輸出:

1.2069982434803874

使用SKlearn來實現KNN

from sklearn.neighbors import KNeighborsRegressor
cols=['accommodates','bedrooms']
knn=KNeighborsRegressor(n_neighbors=100)#例項化,引數預設值是5(預設使用5個最近鄰)該引數並不是越大越好(取500比取100的時候rmse反而增加了)
knn.fit(norm_train_df[cols],norm_train_df['price'])#使用訓練集建立模型,確定X和Y
two_features_predictions=knn.predict(norm_test_df[cols])#對測試集的Y(價格)進行預測

使用SKlearn計算RMSE

from sklearn.metrics import mean_squared_error
two_features_mse=mean_squared_error(norm_test_df['price'],two_features_predictions)
two_features_rmse=two_features_mse**(1/2)
print(two_features_rmse)

輸出:

0.9981501570648538

注:使用SKlearn實現KNN,感覺到速度比自己寫的要快許多
加入更多的特徵
cols=['accommodates','bedrooms','bathrooms','beds','minimum_nights','maximum_nights','number_of_reviews']
knn=KNeighborsRegressor(n_neighbors=100)
knn.fit(norm_train_df[cols],norm_train_df['price'])
seven_features_predictions=knn.predict(norm_test_df[cols])
seven_features_mse=mean_squared_error(norm_test_df['price'],seven_features_predictions)
seven_features_rmse=seven_features_mse**(1/2)
print(seven_features_rmse

輸出:

0.9704297561523479

可以看到,當特徵值增加到7個的時候,誤差(rmse)變小了

總結

由於KNN的計算方式,其一般只可用於資料規模較小的情況下。另外,KNN也可以用於分類任務,只不過這時候樣本的label是類別而不是數值。同樣,也可以跟每個樣本進行對比,取前n個最近鄰,看大多數屬於哪個類別,則認為當前樣本屬於哪個類別。



相關文章