【火爐煉AI】機器學習012-用隨機森林構建汽車評估模型及模型的優化提升方法

煉丹老頑童發表於2018-08-06

【火爐煉AI】機器學習012-用隨機森林構建汽車評估模型及模型的優化提升方法

(本文所使用的Python庫和版本號: Python 3.5, Numpy 1.14, scikit-learn 0.19, matplotlib 2.2 )

在前面的文章中(【火爐煉AI】機器學習007-用隨機森林構建共享單車需求預測模型 )已經介紹了用隨機森林方法構建共享單車需求預測模型,在程式碼實現層面上來講,構建隨機森林模型非常簡單。

下面我們同樣使用隨機森林演算法構建汽車評估模型,用於根據汽車的六個基本特性來評估汽車的質量。


1. 準備資料集

本專案所使用的資料集來源於加利福尼亞大學歐文分校(UCI)大學的公開資料集:https://archive.ics.uci.edu/ml/datasets/Car+Evaluation。這是專門用於解決多分類問題的一個小型資料集,該資料集的基本資訊為:

【火爐煉AI】機器學習012-用隨機森林構建汽車評估模型及模型的優化提升方法

即整個資料集專門用於多分類模型,沒有缺失值,一共有1728個樣本,每個樣本含有6個關於汽車的基本屬性,每個樣本對應於一個標記,表示汽車質量的好壞,如下所示:

【火爐煉AI】機器學習012-用隨機森林構建汽車評估模型及模型的優化提升方法

在對資料集有了基本瞭解的基礎上,可以用程式碼來具體分析,此處我用pandas來提取資料集中的原始資料,程式碼如下:

# 準備資料集
dataset_path='D:\PyProjects\DataSet\CarEvaluation/car.data'
df=pd.read_csv(dataset_path,header=None)
print(df.info()) # 載入沒有問題
# 原資料集包含有1728個樣本,每一個樣本含有6個features, 一個label
print(df.head())
raw_set=df.values
複製程式碼

-------------------------------------輸---------出--------------------------------

<class 'pandas.core.frame.DataFrame'> RangeIndex: 1728 entries, 0 to 1727 Data columns (total 7 columns): 0 1728 non-null object 1 1728 non-null object 2 1728 non-null object 3 1728 non-null object 4 1728 non-null object 5 1728 non-null object 6 1728 non-null object dtypes: object(7) memory usage: 94.6+ KB None 0 1 2 3 4 5 6 0 vhigh vhigh 2 2 small low unacc 1 vhigh vhigh 2 2 small med unacc 2 vhigh vhigh 2 2 small high unacc 3 vhigh vhigh 2 2 med low unacc 4 vhigh vhigh 2 2 med med unacc

--------------------------------------------完-------------------------------------

通過df.info()可以看出該資料集的7列都是object型別,故而難以直接應用到機器學習領域,需要做進一步的型別轉換處理,如下程式碼:

# 資料集中的特徵向量包括有多個String,故而type是object,需要轉換為數值
from sklearn import preprocessing
label_encoder=[] # 放置每一列的encoder
encoded_set = np.empty(raw_set.shape)
for i,_ in enumerate(raw_set[0]):
#     encoder=preprocessing.LabelEncoder()
#     encoder.fit(raw_set[:,i]) # 用某一列來fit這個encoder
#     encoded_set[:,i]=encoder.transform(raw_set[:,i]) # 用同樣的這一列來transform
#     label_encoder.append(encoder)
    
    # 上面fit和tranform都是在同一個向量上操作,故而可以整合
    encoder=preprocessing.LabelEncoder()
    encoded_set[:,i]=encoder.fit_transform(raw_set[:,i])
    print(encoder.classes_)
    label_encoder.append(encoder)

dataset_X = encoded_set[:, :-1].astype(int)
dataset_y = encoded_set[:, -1].astype(int)
# print(dataset_X.shape) # (1728, 6)
# print(dataset_y.shape) #(1728,)
print(dataset_X[:5]) # 可以看出每個特徵向量都將string轉變為int
print(dataset_y[:5]) # 檢查沒有問題

# 將資料集拆分為train set 和test set
from sklearn.model_selection import train_test_split
train_X, test_X, train_y, test_y=train_test_split(dataset_X,dataset_y,
                                                  test_size=0.3,random_state=42)
# print(train_X.shape) # (1209, 6)
# print(train_y.shape) # (1209,)
# print(test_X.shape) # (519, 6) 
複製程式碼

-------------------------------------輸---------出--------------------------------

['high' 'low' 'med' 'vhigh'] ['high' 'low' 'med' 'vhigh'] ['2' '3' '4' '5more'] ['2' '4' 'more'] ['big' 'med' 'small'] ['high' 'low' 'med'] ['acc' 'good' 'unacc' 'vgood'] [[3 3 0 0 2 1] [3 3 0 0 2 2] [3 3 0 0 2 0] [3 3 0 0 1 1] [3 3 0 0 1 2]] [2 2 2 2 2]

--------------------------------------------完-------------------------------------

可以看出轉換之後的資料集都是int型,故而可以輸入到模型中進行訓練和預測。同時,為了訓練和測試的方便,將整個資料集劃分為訓練集(佔比70%,即1209個樣本)和測試集(佔比30%,即519個樣本)。

########################小**********結###############################

1,由於本次資料集的屬性和標記都是string型別,故而需要先轉變為數值型。轉變是通過LabelEncoder()函式完成的。

2,這裡使用的轉變器(即LabelEncoder()例項)需要儲存,便於以後對新樣本屬性進行轉換,或者對預測出來的標記再反向轉變成string,此處將其儲存到label_encoder這個list中。

#################################################################


2. 構建隨機森林分類模型和模型評估

2.1 隨機森林分類模型的構建

隨機森林分類模型的構建非常簡單,可以參考【火爐煉AI】機器學習007-用隨機森林構建共享單車需求預測模型 。如下程式碼先構建一個隨機森林分類器,然後用訓練集來訓練該分類器,最後用測試集來檢查模型的好壞,列印出模型評價指標。關於模型評價指標的具體含義和計算方法,可以參考【火爐煉AI】機器學習011-分類模型的評估:準確率,精確率,召回率,F1值

# 建立隨機森林分類器
from sklearn.ensemble import RandomForestClassifier
rf_classifier=RandomForestClassifier(n_estimators=200,max_depth=8,random_state=37)
rf_classifier.fit(train_X,train_y) # 用訓練集進行訓練

# 用測試集評估模型的準確率,精確率,召回率,F1值:
def print_model_evaluations(classifier,test_X, test_y,cv=5):
    '''print evaluation indicators of classifier on test_set.
    those indicators include: accuracy, precision, recall F1-measure'''
    from sklearn.cross_validation import cross_val_score
    accuracy=cross_val_score(classifier,test_X,test_y,
                             scoring='accuracy',cv=cv)
    print('準確率:{:.2f}%'.format(accuracy.mean()*100))
    precision=cross_val_score(classifier,test_X,test_y,
                             scoring='precision_weighted',cv=cv)
    print('精確度:{:.2f}%'.format(precision.mean()*100))
    recall=cross_val_score(classifier,test_X,test_y,
                             scoring='recall_weighted',cv=cv)
    print('召回率:{:.2f}%'.format(recall.mean()*100))
    f1=cross_val_score(classifier,test_X,test_y,
                             scoring='f1_weighted',cv=cv)
    print('F1  值:{:.2f}%'.format(f1.mean()*100))

print_model_evaluations(rf_classifier,test_X,test_y)    
複製程式碼

-------------------------------------輸---------出--------------------------------

準確率:89.19% 精確度:88.49% 召回率:89.19% F1 值:88.32%

--------------------------------------------完-------------------------------------

2.2 隨機森林分類模型的全面評估

更進一步的,為了更全面的評估該模型,可以將模型在測試集上的混淆矩陣和分類報告列印出來,關於混淆矩陣和分類報告,可以參考【火爐煉AI】機器學習011-分類模型的評估:準確率,精確率,召回率,F1值 。如下所示:

# 列印模型的混淆矩陣和各個類別的評價指標
# 使用sklearn 模組計算混淆矩陣
from sklearn.metrics import confusion_matrix
test_y_pred=rf_classifier.predict(test_X)
confusion_mat = confusion_matrix(test_y, test_y_pred)
print(confusion_mat) #看看混淆矩陣長啥樣
print('*'*50)
from sklearn.metrics import classification_report
print(classification_report(test_y, test_y_pred))
複製程式碼

-------------------------------------輸---------出--------------------------------

[[108 2 7 1] [ 9 8 0 2] [ 3 0 355 0] [ 3 0 0 21]]

precision recall f1-score support

0 0.88 0.92 0.90 118 1 0.80 0.42 0.55 19 2 0.98 0.99 0.99 358 3 0.88 0.88 0.88 24

avg / total 0.95 0.95 0.94 519

--------------------------------------------完-------------------------------------

從上面的分類報告中可以看出,這個模型在類別2上表現最好,精確率和召回率均在98%以上,但在類別1,雖然精確率有80%,但召回率卻低至42%,所得到的F1值也只有55%,表明這個模型還有進一步優化的空間(出現這種結果也有可能是test set中類別2的樣本數最多,而類別1的樣本數最少導致的)。

2.3 用該分類模型預測新樣本資料

一個模型經過訓練和優化之後,一旦達到了我們的分類要求,就可以用來預測新樣本資料,如下我們自己構建了一個新的汽車樣本,這個樣本汽車的購買價格和維護價格都非常高,有2個車門,載人數2人,後備箱比較小,安全性比較低(很有可能是那種2座的豪華車吧。。。)。看看這個分類模型對這種車的質量評估怎麼樣。

# 看起來該隨機森林分類器的分類效果還是很不錯的,
# 那麼可以用這個比較理想的模型來預測新資料,
new_sample=['vhigh','vhigh','2','2','small','low']
# 在把這個樣本輸入模型之前,需要將樣本中的string轉變為int
# 採用和上面train set相同的encoder來編碼
encoded_sample=np.empty(np.array(new_sample).shape)
for i,item in enumerate(new_sample):
    encoded_sample[i]=int(label_encoder[i].transform([item])) 
    # 這兒的item一定要加【】,否則報錯。而且要轉變為int型別
print(encoded_sample.reshape(1,-1)) # 和上面列印的print(encoder.classes_)對應一致

# 用成熟分類模型對該新樣本進行分類,得到分類結果:
output=rf_classifier.predict(encoded_sample.reshape(1,-1))
print('output: {}, class: {}'.format(output,
       label_encoder[-1].inverse_transform(output)[0]))
複製程式碼

-------------------------------------輸---------出--------------------------------

[[3. 3. 0. 0. 2. 1.]] output: [2], class: unacc

--------------------------------------------完-------------------------------------

在將新樣本資料輸入模型之前,需要對樣本資料進行轉換(即對特徵向量進行編碼,將人可以閱讀的字串轉變為機器可以閱讀的數值),注意此時的轉換要用到和前面訓練集相同的轉換方法,即使用前面放置到label_encoder這個list中的encoder來轉換,可以將轉換之後的數值列印出來進行驗證。分類模型根據該樣本的六個屬性,判斷出該汽車的質量為2,此時我們需要將2再反向轉換為字串(即反編碼,或解碼,即將機器可以閱讀的數值轉變為人可以閱讀的字串),經過解碼後,發現該汽車的質量為“unacc”,即unacceptable。

可以想象一下,一輛價格老貴老貴,維護起來也老貴老貴,後備箱又小,只能坐兩個人,而且安全性還非常低的汽車,你能接收嗎???屌絲沒錢不能接受,土豪雖然可以用這種車來泡妞,但是安全性太低,土豪也接收不了吧。。。

########################小**********結###############################

1,隨機森林分類模型的構建非常簡單,直接呼叫sklearn模組中的RandomForestClassifier 類即可。

2,對分類模型的評估可以直接列印其整體的準確率,精確率,召回率,F1值,也可以列印該模型在各個不同類別上的評價指標,列印其混淆矩陣和分類報告。

#################################################################


3. 模型的優化提升方法

上面的分類模型貌似在測試集上的表現還不錯,但是還有提升空間,主要有以下兩個方面的優化提升。

3.1 模型超引數的優化—驗證曲線

前面在定義隨機森林分類器時,我們隨機地定義該分類器的引數為:n_estimators=200,max_depth=8,但是這些隨機定義的引數真的是最優引數組合嗎?怎麼獲取這些引數的最優值了?這就是驗證曲線的作用了。下面首先優化n_estimators引數,看看取不同值時,該模型的準確率是否有明確的改善。

如下程式碼,使用sklearn中的validation_curve可以驗證不同引數取值時模型的準確率。

# 提升模型的分類效果:優化模型的某個引數,
# 第一步:優化n_estimators引數
from sklearn.model_selection import validation_curve
optimize_classifier1=RandomForestClassifier(max_depth=4,random_state=37)
parameter_grid=np.linspace(20,400,20).astype(int)
train_scores,valid_scores=validation_curve(optimize_classifier1,train_X,train_y,
                                           'n_estimators',parameter_grid,cv=5) 
# cv=4,會輸出4列結果,cv=5,會輸出5列結果,
# 故而輸出的結果train_scores 的shape為(parameter_grid.shape[0],cv)

# 列印優化結果
print('n_estimators optimization results-------->>>')
print('train scores: \n ',train_scores)
print('-'*80)
print('valid scores: \n ',valid_scores)
複製程式碼

-------------------------------------輸---------出--------------------------------

n_estimators optimization results-------->>> train scores: [[0.78549223 0.80144778 0.80785124 0.79338843 0.80165289] [0.8 0.80972079 0.81095041 0.81921488 0.83057851] [0.8134715 0.81075491 0.81095041 0.81404959 0.81714876]

......

valid scores: [[0.77459016 0.79338843 0.80082988 0.76763485 0.80497925] [0.79918033 0.79338843 0.80497925 0.80082988 0.8340249 ] [0.81967213 0.80578512 0.80082988 0.78008299 0.82572614] [0.79918033 0.80991736 0.7966805 0.78838174 0.82572614]

......

--------------------------------------------完-------------------------------------

得到的trains_scores和valid_scores矩陣很大,此處只顯示一部分,可以在本文末尾我的github中找到原始程式碼和結果。雖然此處得到了驗證曲線的結果,但是難以直接觀察結果的好壞,故而我自己定義一個繪圖函式,將驗證曲線的結果繪製成圖,程式碼如下:

# 定義一個繪圖函式,繪製train scores 和valid scores
def plot_valid_curve(grid_arr,train_scores,valid_scores,
                     title=None,x_label=None,y_label=None):
    '''plot train_scores and valid_scores into a line graph'''
    assert train_scores.shape==valid_scores.shape, \
        'expect train_scores and valid_scores have same shape'
    assert grid_arr.shape[0]==train_scores.shape[0], \
        'expect grid_arr has the same first dim with train_scores'
    plt.figure()
    plt.plot(grid_arr, 100*np.average(train_scores, axis=1), 
             color='blue',marker='v',label='train_scores')
    plt.plot(grid_arr, 100*np.average(valid_scores, axis=1), 
             color='red',marker='s',label='valid_scores')
    plt.title(title) if title is not None else None
    plt.xlabel(x_label) if x_label is not None else None
    plt.ylabel(y_label) if y_label is not None else None
    plt.legend()
    plt.show()

plot_valid_curve(parameter_grid,train_scores,valid_scores,
                 title='n_estimators optimization graph',
                 x_label='Num of estimators',y_label='Accuracy%')
複製程式碼

優化n_estimators的結果圖

上圖中可以看出,在estimators取值為50附近時,能夠得到最高的準確率,故而我們可以進一步優化estimators在50附近的取值。如下程式碼:

# 第二步:對n_estimators做進一步細緻優化
# 圖中可以看出,n_estimators在100以內所得到的準確率最高,故而需要進一步做更精細的優化
parameter_grid2=np.linspace(20,120,20).astype(int)
train_scores,valid_scores=validation_curve(optimize_classifier1,train_X,train_y,
                                           'n_estimators',parameter_grid2,cv=5) 
plot_valid_curve(parameter_grid2,train_scores,valid_scores,
                 title='2nd n_estimators optimization graph',
                 x_label='Num of estimators',y_label='Accuracy%')
# 從圖中可以看出準確率最高的點是第6,7,12附近,對應的estimators是46,51,77,
# 故而後面暫定為50
複製程式碼

精細化優化n_estimators的結果圖

從上圖中可以看出,準確率的最高點對應的estimators大約為46,51,77,故而我們確定最優的estimators引數的取值為50.

對於max_depth,可以採用同樣的驗證曲線來優化,得到最優值,如下程式碼和圖:

# 第三步:對max_depth進行優化:
optimize_classifier2=RandomForestClassifier(n_estimators=50,random_state=37)
parameter_grid3=np.linspace(2,13,11).astype(int)
print(parameter_grid3) # [ 2  3  4  5  6  7  8  9 10 11 13]
train_scores3,valid_scores3=validation_curve(optimize_classifier2,train_X,train_y,
                                           'max_depth',parameter_grid3,cv=5) 
plot_valid_curve(parameter_grid3,train_scores3,valid_scores3,
                 title='max_depth optimization graph',
                 x_label='Num of max_depth',y_label='Accuracy%')
# 從圖中可以看出,取max_depth=10,11,13時準確率一樣,故而取max_depth=10
複製程式碼

對max_depth優化後的結果

從上圖中可以看出,準確率的最高點對應的max_depth大約為10,11,13,這幾個點處的結果幾乎一樣,故而我們確定最優的max_depth引數的取值為10.

3.2 訓練集大小對模型的影響—學習曲線

前面我們通過驗證曲線優化了模型中各種引數,得到了引數的最佳取值,但有的時候,訓練集的大小也會對模型的效果有影響,此時我們可以用學習曲線來判斷最佳的訓練集大小。程式碼如下:

# 前面都是優化隨機森林分類器的內建引數,但是沒有考慮訓練集的大小對模型效果的影響
# 前面都是用traiin_X來優化模型,train_X含有1209個樣本,
# 下面考察一下訓練集樣本大小對模型效果的影響--即學習曲線
from sklearn.model_selection import learning_curve
# optimize_classifier3=RandomForestClassifier(random_state=37)
optimize_classifier3=RandomForestClassifier(n_estimators=50,
                                            max_depth=10,
                                            random_state=37)
parameter_grid4=np.array([0.1,0.2,0.3,0.4,0.5,0.6,0.7,.8,.9,1.]) # dataset最多有1728個樣本
train_sizes,train_scores4,valid_scores4=learning_curve(optimize_classifier3,
                                                       dataset_X,dataset_y,
                                          train_sizes=parameter_grid4,cv=5) 
# print(train_sizes) # [ 138  276  414  552  691  829  967 1105 1243 1382]
# 最大也只能到dataset_X樣本數的80%,即1728*0.8=1382
plot_valid_curve(parameter_grid4,train_scores4,valid_scores4,
                 title='train_size optimization graph',
                 x_label='Num of train_size',y_label='Accuracy%')
# 可以看出,在train_size=1382時得到的準確率最大,約為80%左右。
複製程式碼

訓練集大小對準確率的影響

可以從圖中看出,訓練集大小貌似越大越好,因為訓練集越大,模型訓練的越充分,得到的valid_scores與train_scores的差距越小,這裡的差距實際上就是“過擬合”現象。而此處通過提高訓練集的大小,可以減小過擬合現象。

還有一點,learning_curve裡面貌似把最大取值固定為整個資料集的80%,這個能修改嗎?

3.3 用最優引數重新建立模型,判斷模型的質量

前面我們花了好長時間來優化模型,得到了最佳超引數和最佳訓練集大小,那麼,如果用這些引數來訓練模型,得到模型的質量會怎麼樣了?非常好還是非常差?如下直接上程式碼。

# 用所有最優引數來重新構建模型,並判斷此模型的好壞
train_X, test_X, train_y, test_y=train_test_split(dataset_X,dataset_y,
                                                  test_size=0.2,random_state=42)
# 最佳訓練集大小為80%

rf_classifier=RandomForestClassifier(n_estimators=50,max_depth=10,random_state=37)
rf_classifier.fit(train_X,train_y) # 用訓練集進行訓練
print_model_evaluations(rf_classifier,test_X,test_y)    
test_y_pred=rf_classifier.predict(test_X)
confusion_mat = confusion_matrix(test_y, test_y_pred)
print('confusion_mat: ------->>>>>')
print(confusion_mat) #看看混淆矩陣長啥樣
print('*'*50)
print('classification report: -------->>>>>>')
print(classification_report(test_y, test_y_pred))
複製程式碼

-------------------------------------輸---------出--------------------------------

準確率:89.32% 精確度:88.49% 召回率:89.32% F1 值:88.45% confusion_mat: ------->>>>> [[ 71 7 5 0] [ 1 9 0 1] [ 0 0 235 0] [ 1 0 0 16]]


classification report: -------->>>>>> precision recall f1-score support

0 0.97 0.86 0.91 83 1 0.56 0.82 0.67 11 2 0.98 1.00 0.99 235 3 0.94 0.94 0.94 17

avg / total 0.96 0.96 0.96 346

--------------------------------------------完-------------------------------------

貌似比第一次定義的模型在效能上提高了一點點。。。。


注:本部分程式碼已經全部上傳到(我的github)上,歡迎下載。

參考資料:

1, Python機器學習經典例項,Prateek Joshi著,陶俊傑,陳小莉譯

相關文章