1. 程式人生 > 實用技巧 >資料分析專案之:北京地區短租資料集分析及價格建模預測(天池大資料競賽)

資料分析專案之:北京地區短租資料集分析及價格建模預測(天池大資料競賽)

專案名稱:北京地區短租資料集分析及價格建模預測

專案概述:本專案主要是對短租資料集進行資料探索性分析,通過特徵工程提取相關特徵,在11個迴歸模型中對資料集進行建模訓練,實現房價預測。

      最後經過對比分析,選取其中表現較好的模型進一步調參優化,得到最優模型。

專案背景:共享,通過讓渡閒置資源的使用權,在有限增加邊際成本的前提下,提高了資源利用效率。隨著資訊的透明化,越來越多的共享

      發生在陌生人之間。短租,共享空間的一種模式,不論是否體驗過入住陌生人的家中,你都可以從短租的資料裡挖掘有趣的資訊。

     活動採用了短租房源相關的公開資料,包括了結構化的表格資料、非結構化的文字和地圖資料。

資料來源 :連結:https://pan.baidu.com/s/1YUHjTViaTm1Rrg_kzmFEBw 密碼:6we5

專案流程:

     1. 資料採集

      2. 資料檢視及預處理

      3. EDA

      4. 特徵工程

      5. 建模預測

      6. 模型評估與優化

導包

 1 import numpy as np
 2 import pandas as pd
 3 import seaborn as sns
 4 import matplotlib.pyplot as plt
 5 %matplotlib inline
 6 import scipy.stats as
ss 7 import missingno as miss 8 import jieba
 9 from sklearn.preprocessing import Normalizer
10 from sklearn.preprocessing import StandardScaler,LabelEncoder
11 from sklearn.preprocessing import OneHotEncoder,MinMaxScaler
12 from sklearn.neighbors import KNeighborsRegressor
13 from sklearn.linear_model import LinearRegression,Lasso,Ridge,LogisticRegression
14 from sklearn.tree import DecisionTreeRegressor,ExtraTreeRegressor 15 from sklearn.ensemble import RandomForestRegressor,ExtraTreesRegressor 16 from sklearn.ensemble import AdaBoostRegressor,GradientBoostingRegressor 17 from sklearn.metrics import mean_squared_error 18 from sklearn.svm import SVR 19 from xgboost import XGBRegressor 20 from sklearn.decomposition import PCA 21 22 from sklearn.model_selection import train_test_split 23 from sklearn.model_selection import KFold,StratifiedKFold 24 from sklearn.model_selection import GridSearchCV 25 26 import warnings 27 warnings.filterwarnings('ignore') 28 29 # 設定全域性字型 30 plt.rcParams['font.sans-serif'] = 'SimHei' 31 # 解決儲存影象是負號'-'顯示為方塊的問題 32 plt.rcParams['axes.unicode_minus'] = False

1. 資料載入

1 data = pd.read_csv('./listings.csv')
2 data1 = data.copy()   # 資料備份,防止後續的處理錯誤,及時挽回損失

2.檢視資料概況

  主要包括:檢視資料型別、缺失值、異常值、資料分佈情況等基本資訊

1 display(data1.head(),data1.columns)

資料情況如上圖所示,主要是有關可租房源的資訊,欄位含義與英文含義基本一致。

欄位含義:

  id:樣本ID

  name:房屋名稱

  host_id:房東ID

  host_name:房東姓名

  neighbourhood_group:所屬行政區

  neighbourhood:所屬行政區

  latitude:緯度

  longitude:經度

  room_type:房間型別

  price:價格

  minimum_nights:最小可租天數

  number_of_reviews:評論數量

  last_review:最後一次評論時間

  reviews_per_month:每月評論佔比

  calculated_host_listings_count:可出租房屋

  availability_365:每年可出租時長

1 miss.matrix(data1)  # 檢視資料缺失情況

通過上圖可以清楚的看到資料缺失情況。

1 data1.info()   # 通過info()方法可以快速獲取資料集的簡單描述,特別是總行 數、每個屬性的型別和非空值的數量

1 data.describe()   

總結如下:  

  1. neighbourhood_group特徵全部為空值;

  2. name、last_review、reviews_per_month這幾列存在資料缺失,稍後需要進行處理;

  3. name、host_name、neighbourhood、room_type、last_review幾個特徵資料型別為String型別,後續可考慮量化處理;

  4. room_type特徵值存在重複現象,應該是一個分類特徵。

  5. 資料集包含28452條樣本,共15個特徵(price作為目標值)

  6. price值:

    最小值為0:說明可能存在異常值

    25%分位值:235

    中位數: 389

    75%分位值:577

    最大值: 68983,可能存在極值干擾

繪製各特徵的直方圖(可以看到資料的分佈情況)

1 data1.hist(bins=50,figsize=(20,15))

從上圖可以看出很多特徵分佈呈長尾形,說明存在極值干擾,需要進一步處理

3. 特徵工程(含EDA)

3.1 資料預處理:空值處理

1 # 建立資料表,分析資料缺失
2 def draw_missing_data_table(data):
3     total = data.isnull().sum().sort_values(ascending=False)
4     percent = (data.isnull().sum()/data.isnull().count()).sort_values(ascending=False)
5     missing_data = pd.concat([total,percent],axis=1,keys=['Total','Percent'])
6     
7     return missing_data
1 null_ret = draw_missing_data_table(data1)
2 null_ret

由以上資訊總結如下:

1. neighbourhood_group列全部為空 --- (刪除此列)
2. last_review、reviews_per_month兩列存在比較大的資料缺失 --- (填值)
3. name列只有一個空值,含義是房屋名稱,無法進行填充 --- (刪除)

1 # 刪除neighbourhood_group列
2 data1.drop(labels='neighbourhood_group',axis=1,inplace=True)
1 # name列
2 cond = data1['name'].isnull()
3 data1[cond]   # 檢視到name列只有一個空值,含義是房屋名稱,無法進行填充,因此選擇刪除此條樣本

1 # 刪除name列空資料
2 cond = data1['name'].notnull()
3 data1 = data1[cond]
1 # last_review列空值
2 cond = data1['last_review'].isnull()
3 data1[cond]   # 共有11157個空值
1 # 插值 0
2 data1['last_review'].fillna(0,inplace=True)
1 # reviews_per_month列空值
2 cond = data1['reviews_per_month'].isnull()
3 data1[cond]   # 共11157個空值
1 mean = data1['reviews_per_month'].mean()
2 mean = round(mean,2)   # 保留兩位小數
3 mean
4 
5 # 插值 mean
6 data1['reviews_per_month'].fillna(value=mean,inplace=True)
1 data1.info()   # 檢視已經不存在空值

自定義函式,規範neighbourhood列,使neighbourhood特徵列的資料只包含中文名

 1 def neighbourhood_str(data):
 2     neighbourhoods = []
 3     list = data['neighbourhood'].str.findall('\w+').tolist()
 4     for i in list:
 5         neighbourhoods.append(i[0])
 6     return neighbourhoods
 7 
 8 
 9 data1['neighbourhood'] = neighbourhood_str(data1)
10 data1.head()

3.2 探索性資料分析(EDA)

 1 # 取出其中要分析的特徵,單獨觀察各特徵的資料分佈情況
 2 subsets = ['price','minimum_nights','number_of_reviews','reviews_per_month',
 3            'calculated_host_listings_count','availability_365']
 4 
 5 
 6 
 7 # 畫出箱型圖,觀察資料集中的異常值
 8 
 9 fig,ax = plt.subplots(len(subsets),1,figsize=(20,10))
10 
11 plt.subplots_adjust(hspace=1)   # 設定子圖間的高度距離為100%
12 for i,subset in enumerate(subsets):
13     sns.boxplot(data1[subset],ax=ax[i],whis=2,orient='h')
14     

 1 # 自定義函式,展示觀察資料集的各統計計量指標
 2 
 3 '''
 4 變異係數:標準差與平均數的比值,記為C·V。
 5 
 6     1. 變異係數是衡量資料中各觀測值變異程度的另一個統計計量。它可以消除單位和平均數不同對兩個或對多個資料變異程度比較的影響。
 7     
 8     2. 當進行兩個或多個資料變異程度的比較時,如果度量單位與平均數相同,可以直接利用標準差來比較。如果度量單位和平均數不同時,
 9        比較其變異程度就不能採用標準差,而需採用標準差與平均數的比值來比較。
10     
11     3. 計算公式:C·V =( 標準偏差 SD / 平均值Mean )× 100%
12     
13     4. 在進行資料統計分析時,如果變異係數大於15%,則要考慮該資料可能不正常,應該刪除。
14 '''
15 
16 def EDA(df):
17     data = {}
18     for i in subsets:
19         data.setdefault(i,[])          # setdefault(key,default=None)如果鍵不存在於字典中,將會新增鍵並將值設為預設值
20         data[i].append(df[i].skew())   # 資料偏度
21         data[i].append(df[i].kurt())   # 資料峰度
22         data[i].append(df[i].mean())   # 資料均值
23         data[i].append(df[i].std())    # 資料方差
24         data[i].append(df[i].std()/df[i].mean())   # 變異係數
25         data[i].append(df[i].max()-df[i].min())    # 極值
26         data[i].append(df[i].quantile(0.25))       # 第一分位數 Q1
27         data[i].append(df[i].quantile(0.75))       # 第四分位數 Q3
28         data[i].append(df[i].quantile(0.75)-df[i].quantile(0.25))   # 四分位數間距IQR=Q3-Q1
29         data[i].append(df[i].median())   # 中位數
30     
31     data_df = pd.DataFrame(data,index=['偏度','峰度','均值','標準差','變異係數','極差','第一分位數','第四分位數',
32                                        '四分位距','中位數'],columns=subsets)
33     return data_df.T
34                          
1 EDA(data1)

通過箱型圖我們可以清晰的看到資料的異常值:

  1. 因為是短租場景,價格price中存在上萬的租金可能就是異常值;

  2. 最少租住天數也存在很多異常值。

異常值處理

1 # 刪除price特徵中的異常值(刪除價格為0的資料)
2 cond = data1['price'] > 0
3 data1 = data1[cond]
4 data1.shape

特徵選擇1

1 # 剔除與分析目標不相關的屬性 latitude,longitude
2 # 對於短租來說,在一個地區裡,價格差距不會太大,一般會和房子型別等其他因素有關
3 
4 data1.drop(labels=['latitude','longitude'],axis=1,inplace=True)
1 # 使用 sns的 violinplot()函式繪製小提琴圖,檢視價格集中在哪個階段
2 
3 plt.figure(figsize=(15,6))
4 plt.subplot(1,2,1)
5 sns.violinplot(data1['price'])
6 
7 plt.subplot(1,2,2)
8 sns.violinplot(np.log(data1['price']))   # 使用log函式對特徵進行平滑處理

觀察小提琴圖可以發現:

  1. 資料比較集中,只存在極少部分高價格資料;

  2. 使用log函式對特徵進行平滑處理後,將價格之間的差距縮小,得到了一個更合理的分佈圖。

特徵選擇2

1 # 對價格進行單獨分析,觀察價格的具體分佈
2 
3 sns.distplot(data1[data1['price'] < 3000]['price'])

觀察上圖可以發現:

  [0,1000]區間內的資料分佈接近正態分佈,在預測價格時取此區間資料進行建模即可。
1 # 取出價格在[0,1000]區間的資料
2 
3 data2 = data1[data1['price'] <= 1000]
4 data2.shape   # (25977, 13)

檢視房源總體分佈情況

1 # 檢視房源總體分佈情況
2 
3 # data2['neighbourhood'].value_countsounts()   # AttributeError: 'Series' object has no attribute 'value_countsounts'
4 listing = data2.neighbourhood.value_counts()   # 這種寫法就不會報錯
5 listing = pd.DataFrame(listing)
6 listing['percent'] = (listing['neighbourhood'].values/np.sum(listing.values)).round(3)
7 listing

 1 # 繪製餅圖,檢視總體分佈情況
 2 
 3 listing = data2.neighbourhood.value_counts()   
 4 labels = listing.index
 5 
 6 sns.set(font_scale=1.5)
 7 plt.figure(figsize=(15,15))
 8 plt.title('各區listing分佈佔比',fontdict={'fontsize':18})
 9 
10 # 朝陽區、海淀區、東城區3個佔比較高的區使用 explode突出顯示
11 plt.pie(listing,
12        labels=labels,
13        autopct='%0.2f%%',
14        explode=[0.07 if i in ['東城區','朝陽區','海淀區'] else 0 for i in labels],
15        startangle=90,
16        counterclock=False,
17        textprops={'fontsize':10,'color':'black'},
18        colors=sns.color_palette('summer_r',n_colors=18))
19 
20 plt.legend(loc='best',shadow=True,fontsize=11)

通過上圖可以知道,房源整體分佈中,朝陽、海淀、東城三區佔比較大,其他各區佔比較小

1 # 對各區房源價格均值進行對比(透視表)
2 
3 price_pair = pd.pivot_table(data2,index='neighbourhood',columns='room_type',
4                            values='price',aggfunc=np.mean)
5 
6 price_pair

1 # 將最終結果進行視覺化展示(熱圖)
2 
3 plt.figure(figsize=(12,12))
4 sns.heatmap(price_pair,cmap=sns.color_palette('PiYG',n_colors=32),annot=True,fmt='.0f')

通過觀察發現:

  1. Entire home/apt型別:東城、密雲、平谷、延慶、懷柔區價格較高;

  2. Private room型別:延慶、懷柔、門頭溝區價格較高;

  3. Shared room型別:延慶、懷柔區價格較高。

  總體延慶縣和懷柔區價格較高。

繪製詞雲圖

 1 # 分析受關注程度高的房源與受關注程度低的房源之間關於名字屬性有什麼區別
 2 
 3 '''獲取各區評論數量top或者bottom的數量,根據 num 引數獲取排名靠前的資料以及排名靠後的資料'''
 4 def get_review_tb(df,num):   
 5     result = []
 6     groups = df.groupby('neighbourhood')
 7     for x,group in groups:
 8         if num>0:
 9             result.append(group.sort_values(by='number_of_reviews',ascending=False)[:num])
10         if num<0:
11             result.append(group.sort_values(by='number_of_reviews',ascending=False)[num:])
12     result = pd.concat(result)
13     
14     return result
15     

1 reviews_top10 = get_review_tb(data2,10)
2 reviews_bottom10 = get_review_tb(data2,-10)
3 
4 display(reviews_top10.head(),reviews_bottom10.head())

1 # 列印停用詞詞彙表
2 
3 with open('./stopwords.txt','rt',encoding='utf8') as f:
4     result = f.read().split()
5 
6 print(result)

1 # 匯入相關分析包
2 
3 import jieba
4 from wordcloud import WordCloud
5 from imageio import imread

 1 '''獲取房源名字中的關鍵字'''
 2 
 3 def get_words(df):
 4     s = []
 5     words_dic = {}
 6     with open('./stopwords.txt','rt',encoding='utf8') as f:   # 根據停用詞過濾詞彙
 7         result = f.read().split()
 8     for i in df:
 9         words = jieba.lcut(i)         # 利用jieba對房屋名稱進行拆詞分析,獲取高頻詞彙
10         word = [x for x in words if x not in result]
11         s.extend(word)
12     for word in s:
13         if word != ' ':                    # 去掉字串為空格的資料
14             words_dic.setdefault(word,0)   # 為該字典設定預設值,如果不存在則新增,如果該鍵存在,則跳過並忽略
15             words_dic[word] += 1
16     
17     return words_dic,s
18 
19 
20 
21 '''刪除無關字元'''
22 
23 def select_word(dic):
24     new_dic = {}
25     for key,val in dic.items():
26         if key not in ['','','','']:
27             if val > 6:
28                 new_dic[key] = val
29     
30     return new_dic
top_words,s1 = get_words(reviews_top10.name.astype('str'))         # 強制轉化為字串格式
bottom_words,s2 = get_words(reviews_bottom10.name.astype('str'))   # 強制轉化為字串格式

# 轉換成Series,方便繪圖視覺化
top_words_s = pd.Series(select_word(top_words)).sort_values(ascending=False)
bottom_words_s = pd.Series(select_word(bottom_words)).sort_values(ascending=False)

print(top_words_s)
print('*'*100)
print(bottom_words_s)

1 # 繪圖進行比較,受關注度較小和受關注度較大的房源名稱中詞的分佈情況
2 
3 fig,ax = plt.subplots(2,1,figsize=(10,5))
4 ax[0].set_title('受關注較大房源資訊裡name中詞的出現次數分佈圖')
5 ax[1].set_title('受關注較小房源資訊裡name中詞的出現次數分佈圖')
6 
7 # pd.Series(top_words_s)
8 top_words_s.plot(kind='bar',ylim=[0,40],color='r')
9 bottom_words_s.plot(kind='bar',ylim=[0,40],color='r')

觀察上圖發現:

  1. 無論是受關注小還是受關注大的房源,大部分詞彙都集中在地鐵、公寓、北京、溫馨等詞彙中;

  2. 總體來看,受關注程度與房屋描述相關性不大。

 1 '''繪製詞雲圖'''
 2 
 3 def cloud_s(datas):
 4     new_data = []
 5     for data in datas:
 6         if data != ' ':
 7             if data not in ['','','','']:
 8                 new_data.append(data)
 9     
10     return new_data
1 s1 = cloud_s(s1)
2 s1 = ' '.join(s1)
3 s1

1 s2 = cloud_s(s2)
2 s2 = ' '.join(s2)
3 s2

 1 mask= imread('./pic.jpg')      # 此處為使用遮罩的情況,即生成的詞雲形狀
 2 
 3 def draw_cloude(mask, s, num):
 4     wordcloud = WordCloud(background_color = '#FFFFFF',     # 詞雲圖片的背景顏色
 5                            width = 800,   # 詞雲圖片的寬度,預設400畫素
 6                            height = 800,    # 詞雲圖片的高度,預設200畫素
 7                            font_path = '/opt/anaconda3/lib/python3.8/site-packages/wordcloud/DroidSansMono.ttf',  # 詞雲指定字型檔案的完整路徑
 8     #                        max_words = 200,     #詞雲圖中最大詞數,預設200
 9     #                        max_font_size = 80,   # 詞雲圖中最大的字型字號,預設None,根據高度自動調節
10     #                        min_font_size = 20,   # 詞雲圖中最小的字型字號,預設4號
11                            font_step = 1,   # 詞雲圖中字號步進間隔,預設1
12                            mask = mask,  # 詞雲形狀,預設None,即方形圖
13                            ).generate(s) # 由txt文字生成詞雲
14     #返回物件
15     image_produce = wordcloud.to_image()
16     #顯示影象
17 #     image_produce.show()
18     # 將詞雲圖儲存為名為sample的檔案
19     wordcloud.to_file("sample%d.png" % num)
20 
21 
22 draw_cloude(mask, s1, 1)
23 draw_cloude(mask, s2, 2)
1 sample1 = plt.imread('./sample1.png')
2 plt.imshow(sample1)
3 
4 
5 sample2 = plt.imread('./sample2.png')
6 plt.imshow(sample2)

效果如圖:

3.2 資料轉換

  name、host_name、neighbourhood、room_type、last_review幾個特徵資料型別為String型別。其中:

  1. neighbourhood、room_type是對price(價格)有較大影響的特徵,選擇量化處理;

  2. last_review是時間資料,選擇轉換為時間型別或刪除

3.2.1 neighbourhood、room_type量化處理

1 # LabelEncoder
2 le = LabelEncoder()
3 
4 labels = ['neighbourhood','room_type']
5 for col in labels:
6     data2[col] = le.fit_transform(data2[col])
7     
8 data2.head()

3.2.2 剔除 last_review、reviews_per_month 兩個特徵

# 剔除 last_review、reviews_per_month 兩個特徵

drop_labels = ['last_review','reviews_per_month']
data2.drop(labels=drop_labels,axis=1,inplace=True)

特徵選擇3

1 # 檢視各特徵之間相關性係數
2 # 相關性係數越靠近 1 或 -1 ,則代表特徵之間越有相關性
3 pair_cols = ['price', 'neighbourhood', 'room_type', 'minimum_nights', 'number_of_reviews',
4              'calculated_host_listings_count', 'availability_365']
5 
6 correlation = data2[pair_cols].corr()
7 
8 correlation

1 # 視覺化
2 
3 plt.figure(figsize=(12,12))
4 sns.pairplot(data2[pair_cols])

1 other_feature = data2[['id','name','host_id','host_name']]   # ID等屬性先取出來,方便後面合併表格使用
2 
3 # 刪除['id','name','host_id','host_name']特徵
4 data3 = data2.drop(['id','name','host_id','host_name'],axis=1)
1 data3.head()

觀察上圖可以看到,房屋型別和價格相關性最高(-0.495083),其他特徵或多或少有一定相關性

4. 建模分析---多種迴歸模型下簡單預測的比較分析

  一:不做處理

    x_train, x_test, y_train, y_test = train_test_split(x_dum, y, test_size = 0.25, random_state = 1)

  二:嘗試平滑處理預測值y,即平滑處理y值,x不處理。(x代表特徵,y代表預測值)

    y_log = np.log(y)

    x_train, x_test, y_train_log, y_test_log = train_test_split(x_dum,y_log,test_size = 0.25,random_state = 1)

  三:再整理出一組標準化的資料,通過對比可以看出模型的效果有沒有提高

1 # 資料
2 X = data3.iloc[:,[0,1,3,4,5,6]]
3 
4 # 目標值
5 y = data3.iloc[:,2]
1 # 啞編碼
2 one = OneHotEncoder()
3 
4 X = one.fit_transform(X)

一、不做處理

1 # 資料分割
2 X_train,X_test,y_train,y_test = train_test_split(X,y,test_size=0.2)
1 # 建立模型物件
2 models=[LinearRegression(),KNeighborsRegressor(),SVR(),Ridge(),Lasso(),DecisionTreeRegressor(),
3         ExtraTreeRegressor(),XGBRegressor(),RandomForestRegressor(),AdaBoostRegressor(),GradientBoostingRegressor()]
4 
5 models_str=['LinearRegression','KNNRegressor','SVR','Ridge','Lasso','DecisionTree',
6             'ExtraTree','XGBoost','RandomForest','AdaBoost','GradientBoost']
 1 # 迴圈出每一個模型,進行模型的構建
 2 
 3 big_score = []
 4 big_mean_score = []
 5 
 6 for name,model in zip(models_str,models):
 7     score_ = []
 8     mean_score = []
 9     print('開始訓練模型:' + name)
10     model = model   #建立模型
11     model.fit(X_train, y_train)
12     y_pred = model.predict(X_test)  
13     score = model.score(X_test, y_test)
14     score_.append(str(score)[:5])
15     mean_score.append(mean_squared_error(y_test, y_pred))
16     
17     big_score.append(score_)
18     big_mean_score.append(mean_score)
19     print(name +' 得分:'+str(score))
20     print('均方誤差:',mean_squared_error(y_test, y_pred))

1 df = pd.DataFrame(np.array(big_score).reshape(-1,1),index=models_str, columns=['score'])
2 df['mean_score'] = np.array(big_mean_score).reshape(-1,1)
3 df

通過觀察結果:

  1. DecisionTree、ExtraTree、AdaBoost得分最低,SVR得分也不高,原因是SVR訓練資料需要標準化處理;

  2. XGBoost、RandomForest、GradientBoost得分較高,這幾個都是整合演算法,後續對其進行引數調優,效果應該更好。

二:嘗試平滑處理預測值y,即平滑處理y值,x不處理。

1 # 平滑處理
2 y_log = np.log(y)
3 
4 # 資料分割
5 X_train,X_test,y_train_log,y_test_log = train_test_split(X,y_log,test_size=0.2)
 1 # 迴圈出每一個模型,進行模型的構建
 2 
 3 big_score = []
 4 big_mean_score = []
 5 
 6 for name,model in zip(models_str,models):
 7     score_ = []
 8     mean_score = []
 9     print('開始訓練模型:' + name)
10     model = model   #建立模型
11     model.fit(X_train, y_train_log)
12     y_pred = model.predict(X_test)  
13     score = model.score(X_test, y_test_log)
14     score_.append(str(score)[:5])
15     mean_score.append(mean_squared_error(y_test_log, y_pred))
16     
17     big_score.append(score_)
18     big_mean_score.append(mean_score)
19     print(name +' 得分:'+str(score))
20     print('均方誤差:',mean_squared_error(y_test_log, y_pred))

1 df_log = pd.DataFrame(np.array(big_score).reshape(-1,1),index=models_str, columns=['score_log'])
2 df_log['mean_score_log'] = np.array(big_mean_score).reshape(-1,1)
3 df_log

通過觀察結果:

  1. 部分模型預測效果得到了很好的提升;

  2. XGBoost有很明顯的提升。

三:整理出一組標準化的資料,通過對比可以看出模型的效果有沒有提高

1 # 對資料進行標準化處理後,再進行訓練
2 
3 scale_X = StandardScaler(with_mean=False)
4 X1 = scale_X.fit_transform(X)
5 scale_y = StandardScaler()
6 y = np.array(y).reshape(-1,1)
7 y1 = scale_y.fit_transform(y)
8 y1 = y1.ravel()       # 扁平化資料
9 X_train1, X_test1, y_train1, y_test1 = train_test_split(X1, y1, test_size =0.2)
 1 # 迴圈出每一個模型,進行模型的構建
 2 
 3 big_score_reg = []
 4 big_mean_score_reg = []
 5 
 6 for name,model in zip(models_str,models):
 7     score_ = []
 8     mean_score = []
 9     print('開始訓練模型:' + name)
10     model = model   #建立模型
11     model.fit(X_train1, y_train1)
12     y_pred = model.predict(X_test1)  
13     score = model.score(X_test1, y_test1)
14     score_.append(str(score)[:5])
15     mean_score.append(mean_squared_error(y_test1, y_pred))
16     
17     big_score_reg.append(score_)
18     big_mean_score_reg.append(mean_score)
19     print(name +' 得分:'+str(score))
20     print('均方誤差:',mean_squared_error(y_test1, y_pred))

1 df_reg = pd.DataFrame(np.array(big_score_reg).reshape(-1,1),index=models_str, columns=['score_reg'])
2 df_reg['mean_score_reg'] = np.array(big_mean_score_reg).reshape(-1,1)
3 df_reg

通過觀察結果:

  1. 標準化後的資料對Lasso模型不太友好。

1 # 將所有資料得分進行對比分析
2 
3 df1 = df.join(df_log)
4 df2 = df1.join(df_reg)
5 df2

經過整體對比,選擇較好的模型,進行單獨訓練,並通過調參優化,得到更好的結果。

  選擇: XGBoost 模型

5. 模型優化 --- XGBoost

1 # 平滑處理
2 y_log = np.log(y)
3 
4 # 資料分割
5 X_train,X_test,y_train_log,y_test_log = train_test_split(X,y_log,test_size=0.2)

使用 GridSearchCV 交叉驗證

 1 # 先對n_estimators引數進行除錯
 2 
 3 cv_params = {'n_estimators':[i for i in range(250,300)]}
 4 other_params = {'learning_rate': 0.1, 'n_estimators': 500, 'max_depth': 5, 'min_child_weight': 1, 'seed': 0,
 5                 'subsample': 0.8, 'colsample_bytree': 0.8, 'gamma': 0, 'reg_alpha': 0, 'reg_lambda': 1}
 6 
 7 xgb = XGBRegressor(**other_params)
 8 clf = GridSearchCV(estimator=xgb, param_grid=cv_params, scoring='r2', cv=5, verbose=1, n_jobs=4)
 9 clf.fit(X_train,y_train_log)
10 evalute_result = clf.cv_results_
11 
12 print('每輪迭代執行結果:{0}'.format(evalute_result))
13 print('引數的最佳取值:{0}'.format(clf.best_params_))
14 print('最佳模型得分:{0}'.format(clf.best_score_))

觀察結果可知:

  n_estimators 最好引數為 273,更新引數,繼續調參

 1 # 優化max_depth和min_child_weight引數
 2 
 3 cv_params = {'max_depth': [3, 4, 5, 6, 7, 8, 9, 10], 'min_child_weight': [1, 2, 3, 4, 5, 6]}
 4 
 5 other_params = {'learning_rate': 0.1, 'n_estimators': 273, 'max_depth': 5, 'min_child_weight': 1, 'seed': 0,
 6                 'subsample': 0.8, 'colsample_bytree': 0.8, 'gamma': 0, 'reg_alpha': 0, 'reg_lambda': 1}
 7 
 8 xgb = XGBRegressor(**other_params)
 9 clf = GridSearchCV(estimator=xgb, param_grid=cv_params, scoring='r2', cv=5, verbose=1, n_jobs=4)
10 clf.fit(X_train,y_train_log)
11 evalute_result = clf.cv_results_
12 
13 print('每輪迭代執行結果:{0}'.format(evalute_result))
14 print('引數的最佳取值:{0}'.format(clf.best_params_))
15 print('最佳模型得分:{0}'.format(clf.best_score_))

觀察結果可知:

  max_depth 最好引數為 9,min_child_weight 最好引數為 2,更新引數,繼續調參

 1 # 進一步優化gamma
 2 
 3 cv_params = {'gamma': [0.1, 0.2, 0.3, 0.4, 0.5, 0.6]}
 4 
 5 other_params = {'learning_rate': 0.1, 'n_estimators': 273, 'max_depth': 9, 'min_child_weight': 2, 'seed': 0,
 6                 'subsample': 0.8, 'colsample_bytree': 0.8, 'gamma': 0, 'reg_alpha': 0, 'reg_lambda': 1}
 7 
 8 xgb = XGBRegressor(**other_params)
 9 clf = GridSearchCV(estimator=xgb, param_grid=cv_params, scoring='r2', cv=5, verbose=1, n_jobs=5)
10 clf.fit(X_train, y_train_log)
11 evalute_result = clf.cv_results_
12 print('每輪迭代執行結果:{0}'.format(evalute_result))
13 print('引數的最佳取值:{0}'.format(clf.best_params_))
14 print('最佳模型得分:{0}'.format(clf.best_score_))

觀察結果可知:

  gamma 最好引數為 0.1,更新引數,繼續調參

 1 # 優化subsample 和 colsample_bytree
 2 
 3 cv_params = {'subsample': [0.6, 0.7, 0.8, 0.9], 'colsample_bytree': [0.6, 0.7, 0.8, 0.9]}
 4 
 5 other_params = {'learning_rate': 0.1, 'n_estimators': 273, 'max_depth': 9, 'min_child_weight': 2, 'seed': 0,
 6                 'subsample': 0.8, 'colsample_bytree': 0.8, 'gamma': 0.1, 'reg_alpha': 0, 'reg_lambda': 1}
 7 
 8 xgb = XGBRegressor(**other_params)
 9 clf = GridSearchCV(estimator=xgb, param_grid=cv_params, scoring='r2', cv=5, verbose=1, n_jobs=5)
10 clf.fit(X_train, y_train_log)
11 evalute_result = clf.cv_results_
12 print('每輪迭代執行結果:{0}'.format(evalute_result))
13 print('引數的最佳取值:{0}'.format(clf.best_params_))
14 print('最佳模型得分:{0}'.format(clf.best_score_))

觀察結果可知:

  colsample_bytree 最好引數為 0.8,subsample 最好引數為 0.9,更新引數,繼續調參

 1 # 優化 reg_alpha 和 reg_lambda
 2 
 3 cv_params = {'reg_alpha': [0.05, 0.1, 0.2, 0.5, 1], 'reg_lambda': [0.01, 0.05, 0.1, 1, 2, 3]}
 4 
 5 other_params = {'learning_rate': 0.1, 'n_estimators': 273, 'max_depth': 9, 'min_child_weight': 2, 'seed': 0,
 6                 'subsample': 0.9, 'colsample_bytree': 0.8, 'gamma': 0.1, 'reg_alpha': 0, 'reg_lambda': 1}
 7 
 8 xgb = XGBRegressor(**other_params)
 9 clf = GridSearchCV(estimator=xgb, param_grid=cv_params, scoring='r2', cv=5, verbose=1, n_jobs=5)
10 clf.fit(X_train, y_train_log)
11 evalute_result = clf.cv_results_
12 print('每輪迭代執行結果:{0}'.format(evalute_result))
13 print('引數的最佳取值:{0}'.format(clf.best_params_))
14 print('最佳模型得分:{0}'.format(clf.best_score_))

觀察結果可知:

  reg_alpha 最好引數為 1,reg_lambda 最好引數為 0.05,更新引數,繼續調參

 1 # 優化 learning_rate
 2 
 3 cv_params = {'learning_rate': [0.01, 0.05, 0.07, 0.1, 0.2]}
 4 
 5 other_params = {'learning_rate': 0.1, 'n_estimators': 273, 'max_depth': 9, 'min_child_weight': 2, 'seed': 0,
 6                 'subsample': 0.9, 'colsample_bytree': 0.8, 'gamma': 0.1, 'reg_alpha': 1, 'reg_lambda': 0.05}
 7 
 8 xgb = XGBRegressor(**other_params)
 9 clf = GridSearchCV(estimator=xgb, param_grid=cv_params, scoring='r2', cv=5, verbose=1, n_jobs=5)
10 clf.fit(X_train, y_train_log)
11 evalute_result = clf.cv_results_
12 print('每輪迭代執行結果:{0}'.format(evalute_result))
13 print('引數的最佳取值:{0}'.format(clf.best_params_))
14 print('最佳模型得分:{0}'.format(clf.best_score_))

觀察結果可知:

  learning_rate 最好引數為 0.1

 1 # 整合所有的引數,進行訓練
 2 
 3 other_params = {'learning_rate': 0.1, 'n_estimators': 273, 'max_depth': 9, 'min_child_weight': 2, 'seed': 0,
 4                 'subsample': 0.9, 'colsample_bytree': 0.8, 'gamma': 0.1, 'reg_alpha': 1, 'reg_lambda': 0.05}
 5 
 6 print('開始訓練模型')
 7 # 建模
 8 xgb = XGBRegressor(learning_rate=0.1, n_estimators=273, max_depth=9, min_child_weight=2, seed=0,
 9                      subsample=0.9, colsample_bytree=0.8, gamma=0.1, reg_alpha= 1, reg_lambda=0.05)
10 xgb.fit(X_train,y_train_log)
11 y_pre = xgb.predict(X_test)
12 score = xgb.score(X_test,y_test_log)
13 
14 print('得分:' + str(score))
15 print('均方誤差:',mean_squared_error(y_test_log,y_pre))

6. 總結

最終得到的結果雖然有提升,但得分並不算高,分析原因有:

  1. 資料集特徵太少;

  2. 資料集特徵與價格特徵之間的相關性比較小。