【火爐煉AI】機器學習013-用樸素貝葉斯分類器估算個人收入階層

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

【火爐煉AI】機器學習013-用樸素貝葉斯分類器估算個人收入階層

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

每個人都有權利追求幸福的生活,我等屌絲也不例外,但是,怎麼樣才能知道自己到底是屌絲階層還是富帥階層了?

此處,煉丹老頑童將介紹如何利用樸素貝葉斯分類器估算個人的收入階層,所使用的資料集來源於美國人口普查收入資料集,雖然美國的資料集可能不太適合中國的特色社會主義情況,但此處所使用的分類方法和資料分析方法同樣也適用於中國國情。


1. 準備資料集

本專案的資料集來源於美國人口普查收入資料集.首先我們來熟悉一下這個資料集,下面是老頑童整理的關於這個資料集的基本資訊:

【火爐煉AI】機器學習013-用樸素貝葉斯分類器估算個人收入階層

本資料集的各個屬性的含義和取值

稍微總結一下:這個資料集總共包含有32561個樣本,每個樣本含有14個屬性,一個標記(收入水平,大於50K/y,還是小於等於50K/y)。其中屬性中含有int型別和String型別,即在用Pandas讀取資料集後,資料型別有些混雜,我們後面需要進一步處理。

1.1 讀取資料集

廢話不說,直接上程式碼,讀取整個資料集到記憶體中。

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

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

<class 'pandas.core.frame.DataFrame'> RangeIndex: 32561 entries, 0 to 32560 Data columns (total 15 columns): 0 32561 non-null int64 1 32561 non-null object 2 32561 non-null int64 3 32561 non-null object 4 32561 non-null int64 5 32561 non-null object 6 32561 non-null object 7 32561 non-null object 8 32561 non-null object 9 32561 non-null object 10 32561 non-null int64 11 32561 non-null int64 12 32561 non-null int64 13 32561 non-null object 14 32561 non-null object dtypes: int64(6), object(9) memory usage: 3.7+ MB None

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

為了進一步瞭解整個資料集的資料結構,可以用下面的程式碼來列印各列數值的基本情況。

def print_col_info(dataset):
    '''print info of every column in dataset:
    detailed info includes:
    1, values
    2, value type num'''
    col_num=dataset.shape[1]
    for i in range(col_num):
        print('\ncol-{} info: '.format(i))
        temp=np.sort(list(set(dataset[:,i])))
        print('values: {}'.format(temp))
        print('values num: {}'.format(temp.shape[0]))

print_col_info(raw_set)
複製程式碼

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

col-0 info: values: [17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 90] value num: 73

col-1 info: values: [' ?' ' Federal-gov' ' Local-gov' ' Never-worked' ' Private' ' Self-emp-inc' ' Self-emp-not-inc' ' State-gov' ' Without-pay'] value num: 9

col-2 info: values: [ 12285 13769 14878 ... 1366120 1455435 1484705] value num: 21648

col-3 info: values: [' 10th' ' 11th' ' 12th' ' 1st-4th' ' 5th-6th' ' 7th-8th' ' 9th' ' Assoc-acdm' ' Assoc-voc' ' Bachelors' ' Doctorate' ' HS-grad' ' Masters' ' Preschool' ' Prof-school' ' Some-college'] value num: 16

col-4 info: values: [ 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16] value num: 16

col-5 info: values: [' Divorced' ' Married-AF-spouse' ' Married-civ-spouse' ' Married-spouse-absent' ' Never-married' ' Separated' ' Widowed'] value num: 7

col-6 info: values: [' ?' ' Adm-clerical' ' Armed-Forces' ' Craft-repair' ' Exec-managerial' ' Farming-fishing' ' Handlers-cleaners' ' Machine-op-inspct' ' Other-service' ' Priv-house-serv' ' Prof-specialty' ' Protective-serv' ' Sales' ' Tech-support' ' Transport-moving'] value num: 15

col-7 info: values: [' Husband' ' Not-in-family' ' Other-relative' ' Own-child' ' Unmarried' ' Wife'] value num: 6

col-8 info: values: [' Amer-Indian-Eskimo' ' Asian-Pac-Islander' ' Black' ' Other' ' White'] value num: 5

col-9 info: values: [' Female' ' Male'] value num: 2

col-10 info: values: [ 0 114 401 594 914 991 1055 1086 1111 1151 1173 1409 1424 1455 1471 1506 1639 1797 1831 1848 2009 2036 2050 2062 2105 2174 2176 2202 2228 2290 2329 2346 2354 2387 2407 2414 2463 2538 2580 2597 2635 2653 2829 2885 2907 2936 2961 2964 2977 2993 3103 3137 3273 3325 3411 3418 3432 3456 3464 3471 3674 3781 3818 3887 3908 3942 4064 4101 4386 4416 4508 4650 4687 4787 4865 4931 4934 5013 5060 5178 5455 5556 5721 6097 6360 6418 6497 6514 6723 6767 6849 7298 7430 7443 7688 7896 7978 8614 9386 9562 10520 10566 10605 11678 13550 14084 14344 15020 15024 15831 18481 20051 22040 25124 25236 27828 34095 41310 99999] value num: 119

col-11 info: values: [ 0 155 213 323 419 625 653 810 880 974 1092 1138 1258 1340 1380 1408 1411 1485 1504 1539 1564 1573 1579 1590 1594 1602 1617 1628 1648 1651 1668 1669 1672 1719 1721 1726 1735 1740 1741 1755 1762 1816 1825 1844 1848 1876 1887 1902 1944 1974 1977 1980 2001 2002 2042 2051 2057 2080 2129 2149 2163 2174 2179 2201 2205 2206 2231 2238 2246 2258 2267 2282 2339 2352 2377 2392 2415 2444 2457 2467 2472 2489 2547 2559 2603 2754 2824 3004 3683 3770 3900 4356] value num: 92

col-12 info: values: [ 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 70 72 73 74 75 76 77 78 80 81 82 84 85 86 87 88 89 90 91 92 94 95 96 97 98 99] value num: 94

col-13 info: values: [' ?' ' Cambodia' ' Canada' ' China' ' Columbia' ' Cuba' ' Dominican-Republic' ' Ecuador' ' El-Salvador' ' England' ' France' ' Germany' ' Greece' ' Guatemala' ' Haiti' ' Holand-Netherlands' ' Honduras' ' Hong' ' Hungary' ' India' ' Iran' ' Ireland' ' Italy' ' Jamaica' ' Japan' ' Laos' ' Mexico' ' Nicaragua' ' Outlying-US(Guam-USVI-etc)' ' Peru' ' Philippines' ' Poland' ' Portugal' ' Puerto-Rico' ' Scotland' ' South' ' Taiwan' ' Thailand' ' Trinadad&Tobago' ' United-States' ' Vietnam' ' Yugoslavia'] value num: 42

col-14 info: values: [' <=50K' ' >50K'] value num: 2

從上面列印的各列基本資訊可以看出:

1,資料集有很多列包含有很多字串型別,這些包含字串型別的列被Pandas解讀為object型別,而數值型的列被Pandas解讀為int型別(此處只有整數,故全都是int).

2,在字串列,每一個字串前面都有一個空格,這個需要後期去除掉。

3,有幾個字串列包含有" ?"這樣的缺失值,需要後期做進一步處理。

1.2 資料處理一:對字串進行歸一化處理

可以使用Pandas的Series對應的map函式,結合Lambda表示式來輕鬆處理,如下程式碼

# 資料處理一:去除字串數值前面的空格
str_cols=[1,3,5,6,7,8,9,13,14]
for col in str_cols:
    df.iloc[:,col]=df.iloc[:,col].map(lambda x: x.strip())

# print_col_info(df.values) # 檢查發現所有的字串列都已經掉了空格
複製程式碼

1.3 資料處理二:刪除包含缺失值的樣本

雖然上面的df.info()結果中顯示資料集的每一列中都是non-null,即不包含缺失值,但實際上,該資料集中包含有很多個"?"數值,即為缺失值。對資料集中的缺失值的處理方式有很多種,比如可以使用插值法來填充該缺失值,或者使用前一個值或後一個值來填充缺失值。由於此處我們的資料集樣本比較多,故而可以直接將包含有該缺失值的樣本刪除。

# 資料處理二: 刪除缺失值樣本
# 將?字串替換為NaN缺失值標誌
df.replace("?",np.nan,inplace=True)
# 此處直接刪除缺失值樣本
df.dropna(inplace=True)
# print(df2.shape) # (30162, 15)
複製程式碼

即原始資料集含有32561個樣本,刪除含有缺失值的樣本後,最終由30162個樣本

1.4 資料處理三:對字串進行編碼

字串對於人很友好,我們一眼就能看明白,但是計算機就犯難了,它不明白是啥玩意兒,所以我們有必要把字串編碼為數值型別的資料,編碼方法可以參考我的另一篇文章【火爐煉AI】機器學習002-標記編碼方法.此處直接上程式碼:

# 資料處理三:對字元資料進行編碼
from sklearn import preprocessing
label_encoder=[] # 放置每一列的encoder
encoded_set = np.empty(df.shape)
for col in range(df.shape[1]):
    encoder=None
    if df.iloc[:,col].dtype==object: # 字元型資料
        encoder=preprocessing.LabelEncoder()
        encoded_set[:,col]=encoder.fit_transform(df.iloc[:,col])
    else:  # 數值型資料
        encoded_set[:,col]=df.iloc[:,col]
    label_encoder.append(encoder)

# print_col_info(encoded_set) # 全都是數字,沒有問題
複製程式碼

1.5 資料處理四:對數值進行範圍縮放

從print_col_info()函式的結果可以看出,在某些特徵列中,數值範圍變化特別大,比如col10,其數值最小為0,最大為99999。故而需要對類似的這些特徵進行範圍縮放。關於資料的縮放方法,可以參考老頑童前面的文章【火爐煉AI】機器學習001-資料預處理技術(均值移除,範圍縮放,歸一化,二值化,獨熱編碼),此處只使用該文章中介紹的"範圍縮放"的方法。

# 資料處理四:對某些列進行範圍縮放
# print(encoded_set.dtype) # float64 沒問題

cols=[2,10,11]
data_scalers=[] # 專門用來放置scaler
for col in cols:
    data_scaler=preprocessing.MinMaxScaler(feature_range=(-1,1)) 
    encoded_set[:,col]=np.ravel(data_scaler.fit_transform(encoded_set[:,col].reshape(-1,1)))
    data_scalers.append(data_scaler)
    
# print_col_info(encoded_set) # 已經發生了改變,沒問題
複製程式碼

列印的結果比較長,可以參考原始碼

1.6 資料處理五:拆分資料集為train set和test set

拆分資料集的方法已經在我的很多文章中都講解到了,此處不再贅述,如下程式碼:

dataset_X,dataset_y=encoded_set[:,:-1],encoded_set[:,-1]
# 資料處理五:拆分資料集為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(dataset_X.shape) # (30162, 14)
# print(dataset_y.shape) # (30162,)
# print(train_X.shape) # (21113, 14)
# print(train_y.shape) # (21113,)
# print(test_X.shape) # (9049, 14)
複製程式碼

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

1. 本專案的資料集中有些特徵列是純數值,而有的列卻是字串型,故而需要單獨進行資料處理

2. 對於字串型資料,需要使用編碼器進行編碼,得到數值型資料,然後才能輸入到分類器中進行分類。

3. 對於數值型資料,需要考慮數值的變化範圍是否非常大,比如此處變換太大,那麼我們就需要首先進行範圍縮放。

4. 資料處理方式還有非常多:比如怎麼處理缺失值的問題,是否需要將各個類別樣本數轉變成一樣,是否需要對資料進行歸一化處理等。

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


2. 構建樸素貝葉斯分類器

2.1 樸素貝葉斯分類器的構建和訓練

在我前面的文章【火爐煉AI】機器學習010-用樸素貝葉斯分類器解決多分類問題中,已經詳細介紹了樸素貝葉斯的構建方法,此處還結合了模型的判斷公式,並給出判斷結果,如下程式碼:

# 建立樸素貝葉斯分類器模型
from sklearn.naive_bayes import GaussianNB
gaussianNB=GaussianNB()
gaussianNB.fit(train_X,train_y)

# 2 用交叉驗證來檢驗模型的準確性,只是在test set上驗證準確性
from sklearn.cross_validation import cross_val_score
num_validations=5
accuracy=cross_val_score(gaussianNB,test_X,test_y,
                         scoring='accuracy',cv=num_validations)
print('準確率:{:.2f}%'.format(accuracy.mean()*100))
precision=cross_val_score(gaussianNB,test_X,test_y,
                         scoring='precision_weighted',cv=num_validations)
print('精確度:{:.2f}%'.format(precision.mean()*100))
recall=cross_val_score(gaussianNB,test_X,test_y,
                         scoring='recall_weighted',cv=num_validations)
print('召回率:{:.2f}%'.format(recall.mean()*100))
f1=cross_val_score(gaussianNB,test_X,test_y,
                         scoring='f1_weighted',cv=num_validations)
print('F1  值:{:.2f}%'.format(f1.mean()*100))
                   
# 3 列印效能報告
from sklearn.metrics import confusion_matrix
y_pred=gaussianNB.predict(test_X)
confusion_mat = confusion_matrix(test_y, y_pred)
print(confusion_mat) #看看混淆矩陣長啥樣

from sklearn.metrics import classification_report
# 直接使用sklearn列印精度,召回率和F1值
target_names = ['<=50K', '>50K']
print(classification_report(test_y, y_pred, target_names=target_names))
複製程式碼

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

準確率:79.84% 精確度:78.45% 召回率:79.84% F1 值:77.21% [[6420 347] [1518 764]]

precision recall f1-score support

<=50K 0.81 0.95 0.87 6767

50K 0.69 0.33 0.45 2282

avg / total 0.78 0.79 0.77 9049

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

2.2 用訓練好的模型來預測新樣本

用訓練好的模型來預測新樣本在我以前的文章(【火爐煉AI】機器學習012-用隨機森林構建汽車評估模型及模型的優化提升方法)已經介紹過,此處只需要注意:對新樣本資料的處理也要採用訓練資料集一樣的處理方式,即使用訓練集所用字元型編碼器來對新樣本資料進行編碼,且使用相同的資料範圍縮放器來進行縮放。如下為實現程式碼:

### 2.2 用訓練好的模型來預測新樣本
new_sample=[39, 'State-gov', 77516, 'Bachelors', 13, 
            'Never-married', 'Adm-clerical', 'Not-in-family', 
            'White', 'Male', 2174, 0, 40, 'United-States'] 
# 先對新樣本中的字元型資料編碼
encoded_sample=np.empty(np.array(new_sample).shape)
for i,item in enumerate(new_sample):
    if label_encoder[i] is not None:
        encoded_sample[i]=float(label_encoder[i].transform([item]))
    else:
        encoded_sample[i]=item

# print(encoded_sample) # 貌似沒有錯

# 再對數值較大列進行範圍縮放
cols=[2,10,11]
for i,col in enumerate(cols):
    encoded_sample[col]=np.ravel(data_scalers[i].transform(encoded_sample[col].reshape(-1,1)))
print(encoded_sample) # 檢查沒有錯

# 將新資料放入模型中進行預測
output=gaussianNB.predict(encoded_sample.reshape(1,-1))
print('output: {}, class: {}'.format(output,
       label_encoder[-1].inverse_transform(int(output))))
複製程式碼

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

[39. 5. -0.91332458 9. 13. 4. 0. 1. 4. 1. -0.95651957 -1. 40. 38. ] output: [0.], class: <=50K

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

即新樣本經過樸素貝葉斯分類器得到的分類結果為"<=50K"的收入水平。

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

1. 本專案構建的樸素貝葉斯分類器在測試集上的準確率,精確率,召回率等指標只有80%左右,還有優化空間。

2. 從效能報告中可以看出,測試集中<=50K的樣本有6767個,而>50K的樣本只有2282,兩者的樣本數不一樣,這個可能是導致>50K這一類別各種指標比較低的原因,所以一個優化方向是準備各個類別中樣本數完全一樣的資料集,用於訓練或測試。

3. 另外一個優化方向是,優化樸素貝葉斯分類器的各種超引數,取得這些超引數的最佳值,但同時要注意模型的過擬合現象。

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


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

參考資料:

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

相關文章