在介紹決策樹雕沉、隨機森林算法時,小魚使用的一直都是分類任務(wù)去件。從本篇文章開始坡椒,小魚會在連載中更新基于隨機森林的氣溫預(yù)測任務(wù),根據(jù)歷史數(shù)據(jù)來預(yù)測某一天的氣溫尤溜。
注:氣溫為連續(xù)值倔叼,接下來關(guān)于氣溫預(yù)測的任務(wù)也是回歸任務(wù)。
讀取數(shù)據(jù)
讀取本次任務(wù)的數(shù)據(jù)集:
import pandas as pd
import os
df = pd.read_csv("data" + os.sep + "temps.csv")
df.head()
數(shù)據(jù)集中的前 5 條數(shù)據(jù)如下:
數(shù)據(jù)特征介紹:
-
year
moth
day
week
表示日期 -
temp_2
前天的溫度 -
temp_1
昨天的溫度 -
average
在歷史當(dāng)中宫莱,每年這一天的平均溫度 -
actual
當(dāng)天的真實溫度梢睛,也是我們本次任務(wù)預(yù)測的標(biāo)簽 -
friend
湊熱鬧的一列绝葡,相當(dāng)于噪聲
當(dāng)前數(shù)據(jù)集一共 348 個樣本藏畅,9 列中包含了 8 個特征和 1 個標(biāo)簽值愉阎。
>> df.shape
(348, 9)
觀察數(shù)值型指標(biāo)的統(tǒng)計特性:
df.describe()
其中年份的標(biāo)準(zhǔn)差為 0 榜旦,這是因為所有樣本都是 2016 年的數(shù)據(jù)溅呢。此外,對于時間特征绩蜻,在預(yù)處理時室埋,我們往往需要組合或者拆分姚淆。
拆分是為了提取特征,而組合則是為了繪圖分析所服務(wù)的昔驱。在繪圖或者計算的過程中骤肛,需要標(biāo)準(zhǔn)的時間格式:
from datetime import datetime
dates = [datetime(year,month,day) for year,month,day in zip(df.year, df.month, df.day)]
得到的 datetime 日期如下:
>> dates[:5]
[datetime.datetime(2016, 1, 1, 0, 0),
datetime.datetime(2016, 1, 2, 0, 0),
datetime.datetime(2016, 1, 3, 0, 0),
datetime.datetime(2016, 1, 4, 0, 0),
datetime.datetime(2016, 1, 5, 0, 0)]
觀察特征
導(dǎo)入畫圖工具,并設(shè)置繪圖風(fēng)格:
import matplotlib.pyplot as plt
# plt.style.available
# 指定默認(rèn)風(fēng)格
plt.style.use("seaborn-whitegrid")
%matplotlib inline
繪圖觀察真實氣溫吓笙、前一天氣溫以及 2 天前氣溫面睛、噪音列和日期的關(guān)系叁鉴。
# 設(shè)置布局
fig, ((ax1, ax2), (ax3, ax4)) = plt.subplots(nrows=2, ncols=2, figsize=(10,6), dpi=80)
fig.autofmt_xdate(rotation = 45)
# 標(biāo)簽值
ax1.plot(dates, df.actual, linewidth=1, color='red')
ax1.set_xlabel('')
ax1.set_ylabel('Temperature')
ax1.set_title('Temp')
# 昨天
ax2.plot(dates, df.temp_1, linewidth=1, color='red')
ax2.set_xlabel('')
ax2.set_ylabel('Temperature')
ax2.set_title('Previous Temp')
# 前天
ax3.plot(dates, df.temp_2,linewidth=1, color='red')
ax3.set_xlabel('Date')
ax3.set_ylabel('Temperature')
ax3.set_title('Two Days Prior Temp')
# 噪聲
ax4.plot(dates, df.friend, linewidth=1, color='red')
ax4.set_xlabel('Date')
ax4.set_ylabel('Temperature')
ax4.set_title('Friend Estimate')
繪制結(jié)果:
溫度相關(guān)的特征無異常情況但壮,分布基本上是一致的蜡饵。friend
列雖然也是氣溫溯祸,但其分布與 actual
temp_1
和 temp_2
明顯不同, friend
列可以作為噪聲剔除鸟召。
數(shù)據(jù)預(yù)處理
原始數(shù)據(jù)中在 week
列中并不是一些數(shù)值特征,而是表示周幾的字符串仆抵,這些計算機可不認(rèn)識镣丑,需要我們來轉(zhuǎn)換一下 莺匠。
>> df.info()
<class 'pandas.core.frame.DataFrame'>
RangeIndex: 348 entries, 0 to 347
Data columns (total 9 columns):
# Column Non-Null Count Dtype
--- ------ -------------- -----
0 year 348 non-null int64
1 month 348 non-null int64
2 day 348 non-null int64
3 week 348 non-null object
4 temp_2 348 non-null int64
5 temp_1 348 non-null int64
6 average 348 non-null float64
7 actual 348 non-null int64
8 friend 348 non-null int64
dtypes: float64(1), int64(7), object(1)
memory usage: 24.6+ KB
該列中的值為 object 類型,存放的都是星期幾這樣的屬性值:
week |
---|
Mon |
Tue |
Wed |
Thu |
Fri |
需要使用獨熱編碼進(jìn)行轉(zhuǎn)換:
Mon | Tue | Wed | Thu | Fri |
---|---|---|---|---|
1 | 0 | 0 | 0 | 0 |
0 | 1 | 0 | 0 | 0 |
0 | 0 | 1 | 0 | 0 |
0 | 0 | 0 | 1 | 0 |
0 | 0 | 0 | 0 | 1 |
使用獨熱編碼處理之后旱物, week
列將被拆分成由不同屬性值構(gòu)成的所列单匣。對于某個特定的樣本宝穗,只有樣本屬性對應(yīng)的屬性列為 1逮矛,其余列全部為 0 橱鹏,所以又稱為獨熱編碼。
>> df= pd.get_dummies(df)
>> df.head()
度熱編碼之后的數(shù)據(jù)集:
劃分訓(xùn)練集與測試集:
from sklearn.model_selection import train_test_split
y = df.actual
X = df.drop("actual", axis=1)
feature_list = list(df.columns)
X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.25, random_state=0)
訓(xùn)練集與測試集樣本個數(shù):
>> print('訓(xùn)練集特征:', X_train.shape)
訓(xùn)練集特征: (261, 14)
>> print('訓(xùn)練集標(biāo)簽:', y_train.shape)
訓(xùn)練集標(biāo)簽: (261,)
>> print('測試集特征:', X_test.shape)
測試集特征: (87, 14)
>> print('測試集標(biāo)簽:', y_test.shape)
測試集標(biāo)簽: (87,)
隨機森林建模
下面,我們先不進(jìn)行調(diào)參杉辙,先從建模捶朵、訓(xùn)練、預(yù)測岖食、評估舞吭、可視化整體來一遍蔑穴,了解一下流程存和。
from sklearn.ensemble import RandomForestRegressor
rfr = RandomForestRegressor(n_estimators=1000, random_state=0)
rfr.fit(X_train, y_train)
在測試集進(jìn)行預(yù)測捐腿,并使用 MAPE 絕對百分比誤差來評估此回歸任務(wù)的結(jié)果叙量。
# 預(yù)測結(jié)果
>> predictions = rfr.predict(X_test)
# 計算誤差
>> errors = abs(predictions - y_test)
# MAPE(Mean Absolute Percentage Error)
>> mape = errors / y_test
>> print(f'MAPE:{mape.mean():.2%}')
MAPE:6.08%
絕對百分比誤差 MAPE 可以表示當(dāng)前模型的絕對誤差有多少绞佩,MAPE 越小則損失越小品山,模型越優(yōu)秀肘交。
隨機森林有一個非常大的好處涯呻,經(jīng)過隨機森林建模之后复罐,通過模型可以得到特征的重要性排名效诅。
feature_df = pd.DataFrame({
'features': rfr.feature_names_in_,
'importances': rfr.feature_importances_
})
feature_df.sort_values(by='importances',ascending=False)
從輸出結(jié)果來看咽笼,前 3 個最重要的特征分別是 temp_1
average
temp_2
戚炫。
通過柱形圖叛甫,直觀地展示特征的重要性萌腿。
feature_df.plot(kind='bar')
# X軸名字
plt.xticks(feature_df.index, feature_df.features, rotation="vertical")
# 圖名
plt.xlabel('Features')
plt.title('Features Importances')
昨天的溫度 temp_1
和歷史中這一天的平均溫度 average
是兩個重要層度非常突出的指標(biāo)毁菱。
有的時候峦筒,我們?yōu)榱颂岣咝饰锱纾矔x擇最重要的幾個特征來進(jìn)行建模峦失。下面尉辑,小魚極端地使用兩個特征進(jìn)行建模隧魄,來看看絕對百分比誤差 MAPE 的變化隘蝎。
# 選擇最重要的那2個特征來試試
rfr_most_important = RandomForestRegressor(n_estimators=1000, random_state=0)
important_feature = ['temp_1', 'average']
# 重新訓(xùn)練模型
rfr_most_important.fit(X_train.loc[:,important_feature], y_train)
# 預(yù)測結(jié)果
predictions = rfr_most_important.predict(X_test.loc[:, important_feature])
# 評估結(jié)果
errors = abs(predictions - y_test)
mape = errors / y_test
print(f'mape:{mape.mean():.2%}')
為了公平起見购啄,建模時使用了相同的隨機數(shù)種子。使用最重要的兩個特征建模嘱么,得到的絕對百分比誤差為:
MAPE:6.38%
使用少數(shù)重要特征建模雖然比使用全部特征建模的損失增大了闸溃,但增加的損失并不多,但可以換來時間成本。
最后辉川,我們還可以通過繪圖的方式表蝙,來直觀地看到目前真實值同預(yù)測值之間的差異情況。
定義兩個 DataFrame 分別存儲日期和真實值乓旗、日期和預(yù)測值:
test_dates = [datetime(year,month,day) for year,month,day in zip(X_test.year, X_test.month, X_test.day)]
predictions_data = pd.DataFrame({'date':test_dates, 'prediction':predictions})
true_data = pd.DataFrame({'date':dates, 'actual':y})
繪圖:
plt.plot(true_data['date'], true_data['actual'], color='blue', linestyle='-', linewidth=1.5, label='actual')
plt.plot(predictions_data['date'], predictions_data['prediction'], 'ro', label='prediction')
plt.xticks(rotation=60)
plt.legend()
plt.xlabel('Date')
plt.ylabel('Maximum Temperature (F)')
plt.title('Actual and Predicted Values')
繪制結(jié)果:
看起來還可以府蛇,這個走勢我們的模型已經(jīng)基本能夠掌控了汇跨,接下來我們要再深入到數(shù)據(jù)中了蚪黑,考慮幾個問題:
1、如果可以利用的數(shù)據(jù)量增大郊愧,會對結(jié)果產(chǎn)生什么影響呢?
2、加入新的特征會改進(jìn)模型效果嗎?此時的時間效率又會怎樣?
我們下節(jié)見 (* ̄︶ ̄)
附:隨機森林中樹的可視化展示
上述我們訓(xùn)練隨機森林模型時,構(gòu)造了 1000 棵樹仍律,下面草则,小魚來展示其中的一顆決策樹。
from sklearn.tree import export_graphviz
from IPython.display import Image
import pydotplus
# 拿到其中一棵樹
tree = rfr.estimators_[0]
# 導(dǎo)出成dot文件
export_graphviz(tree, out_file="tree.dot", feature_names=X.columns, rounded=True, precision=1)
# 繪圖
graph = pydotplus.graph_from_dot_file("tree.dot")
# 在notebook中展示
Image(graph.create_png())
由于樹還是比較龐大的轮纫,在 Jupyter NoteBook 中無法清晰地呈現(xiàn):
可以保存成 PNG 文件在電腦的圖像軟件中查看:
graph.write_png("tree.png")
下面我們?yōu)榱烁玫卦?Notebook 中展示,小魚只選擇 3 個特征構(gòu)建隨機森林撩扒,將樹的深度限制在 3:
>> rfr_small = RandomForestRegressor(n_estimators=1000, max_depth=3, random_state=0)
>> rfr_small.fit(X_train, y_train)
RandomForestRegressor(max_depth=3, n_estimators=1000, random_state=0)
選取隨機森林中的一棵樹進(jìn)行展示:
# 提取一棵樹
tree_small = rfr_small.estimators_[0]
# 保存
graph = export_graphviz(
tree_small, label="root", proportion=True, out_file=None,
feature_names=X.columns, filled=True, rounded=True)
graph = pydotplus.graph_from_dot_data(graph)
# 展示
Image(graph.create_png())
繪制結(jié)果: