【火炉炼AI】机器学习013-用朴素贝叶斯分类器估算个人收入阶层

【火炉炼AI】机器学习013-用朴素贝叶斯分类器估算个人收入阶层

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

每个人都有权利追求幸福的生活,我等屌丝也不例外,但是,怎么样才能知道自己到底是屌丝阶层还是富帅阶层了?

此处,炼丹老顽童将介绍如何利用朴素贝叶斯分类器估算个人的收入阶层,所使用的数据集来源于美国人口普查收入数据集,虽然美国的数据集可能不太适合中国的特色社会主义情况,但此处所使用的分类方法和数据分析方法同样也适用于中国国情。


1. 准备数据集

本项目的数据集来源于美国人口普查收入数据集.首先我们来熟悉一下这个数据集,下面是老顽童整理的关于这个数据集的基本信息:

image
本数据集的各个属性的含义和取值

稍微总结一下:这个数据集总共包含有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.

  1.      1.          4.          1.         -0.95651957 -1.
    
  2.     38.        ]
    

output: [0.], class: <=50K

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

即新样本经过朴素贝叶斯分类器得到的分类结果为"<=50K"的收入水平。

########################小**********结###############################

1. 本项目构建的朴素贝叶斯分类器在测试集上的准确率,精确率,召回率等指标只有80%左右,还有优化空间。

2. 从性能报告中可以看出,测试集中<=50K的样本有6767个,而>50K的样本只有2282,两者的样本数不一样,这个可能是导致>50K这一类别各种指标比较低的原因,所以一个优化方向是准备各个类别中样本数完全一样的数据集,用于训练或测试。

3. 另外一个优化方向是,优化朴素贝叶斯分类器的各种超参数,取得这些超参数的最佳值,但同时要注意模型的过拟合现象。

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


注:本部分代码已经全部上传到(我的github)上,欢迎下载。

参考资料:

1, Python机器学习经典实例,Prateek Joshi著,陶俊杰,陈小莉译

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 216,496评论 6 501
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 92,407评论 3 392
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 162,632评论 0 353
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 58,180评论 1 292
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 67,198评论 6 388
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,165评论 1 299
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,052评论 3 418
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,910评论 0 274
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,324评论 1 310
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,542评论 2 332
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,711评论 1 348
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,424评论 5 343
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 41,017评论 3 326
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,668评论 0 22
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,823评论 1 269
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,722评论 2 368
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,611评论 2 353

推荐阅读更多精彩内容

  • pyspark.sql模块 模块上下文 Spark SQL和DataFrames的重要类: pyspark.sql...
    mpro阅读 9,451评论 0 13
  • 1. Java基础部分 基础部分的顺序:基本语法,类相关的语法,内部类的语法,继承相关的语法,异常的语法,线程的语...
    子非鱼_t_阅读 31,623评论 18 399
  • 现在想象一个平躺着的英文字母 S,曲线一开始往下走,到达谷底后慢慢爬升,然后成指数型增涨,之后增涨几乎为零,在到达...
    雪兆峰年阅读 578评论 0 1
  • 鹅湖会讲,朱熹嘲笑陆九渊过简近禅,陆九渊讥讽朱熹支离失本。陆九渊寄诗给朱熹: 易简功夫终久大,支离事业竟浮...
    梅篆儒阅读 5,590评论 2 1
  • 2009年这么快就过去了。想想去年的这个时候,我还冻得哆哆嗦嗦地呆在租住的成都抚琴街抚琴市场楼上的房子里,坐在桌前...
    猫在江湖_6489阅读 334评论 0 1