數據分析項目之:北京地區短租數據集分析及價格建模預測(天池大數據競賽)
項目名稱: 北京地區短租數據集分析及價格建模預測
項目概述: 本項目主要是對短租數據集進行數據探索性分析,通過特徵工程提取相關特徵,在11個回歸模型中對數據集進行建模訓練,實現房價預測。
最後經過
對比分析,選取其中表現較好的模型進一步調參優化,得到最優模型。
項目背景: 共享,通過讓渡閑置資源的使用權,在有限增加邊際成本的前提下,提高了資源利用效率。隨著資訊的透明化,越來越多的共享
發生在陌生人之間。
短租,共享空間的一種模式,不論是否體驗過入住陌生人的家中,你都可以從短租的數據里挖掘有趣的資訊。
活動採用了短租房源相關的公開數據,包括了結構化的表格數據、非結構化的文本和地圖數據。
數據源 : 鏈接://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. 數據集特徵與價格特徵之間的相關性比較小。