1. Introduction

本文基于前文说的朴素贝叶斯原理,参考圣地亚哥州立大学的实验编写了一个简单的朴素贝叶斯分类器,并利用测试数据进行了测试。

项目地址:

2. 分类器编写

2.1数据说明

采用“adult”数据集,输入文件是adult.data,测试文件是adult.test。数据中一行为一个条目,表示一个人

数据集中的变量

变量名

意义

age
年龄
type_employer
职业类型,个体,政府等等
fnlwgt
该变量将被我们忽略
education
学历
education_num
学历的数字等级(和上一个一样,将被忽略)
marital
婚姻状况
occupation
职业
relationship
不清楚如何表述
race
人种
sex
生理性别
capital_gain
资本收益
capital_loss
资本支出
hr_per_week
每周工作时长
country
国籍
income
年收入是否>50k

由于参考文章中使用的是R语言进行处理,R语言在数据挖掘和统计上优势极大,几乎就是为其而生。python中也有numpy库,但是这里仅用了numpy库的median取中位数功能,其他还是以python原生类型来处理。

2.2 框架规划

参考使用Python编写朴素贝叶斯分类器,我们也主要使用字典来实现统计,但是可以分成两个字典,一个是>50k的dataset_high,一个是<=50k的dataset_low。

class DataSet:
def __init__(self):
# 存储读入的原始数据
self.data = []
# 支出的中位数
self.loss_mid = 0
# 收入的中位数
self.gain_mid = 0
# 工作时长的中位数
self.hours_mid = 0
# 年龄的中位数
self.age_mid = 0
# 统计的数据,主要部分
self.classfied_dataset = None
# 总数据条目
self.len_data = 0
最后的计算是前文说的:

输入数据的特征年龄职业类型国籍输入数据的特征输入数据的特征年龄职业类型国籍输入数据的特征

最后两者取大者,则就是所建模型的判定,是否大于50k。

公式化简:

由于P(输入数据的特征)对于一条数据,两个公式来说,是相同的,所以略去计算。

2.3 输入数据预处理

a = ["age", "type_employer", "fnlwgt", "education", "education_num", "marital", "occupation", "relationship", "race",
"sex", "capital_gain", "capital_loss", "hr_per_week", "country", "income"]
classfiled_data = {}
loss_median = loss
gain_median = gain
for node in a:
classfiled_data[node] = {}
for line in data:
if len(line) < 10:
continue
for node in a:
if line[a.index(node)] in classfiled_data[node]:
classfiled_data[node][line[a.index(node)]] += 1
else:
classfiled_data[node][line[a.index(node)]] = 1
列表a就是所有的字段,将所有的数据都按照对应字段,统计到classfiled_data上去,最后形成的形式如下:
# 打印classfiled_data的输出,这是已经简化过的输出
,
marital:
{'Widowed': 908, 'Never-married': 10192, 'not-married': 5323, 'Married': 8297},
country:
{'other': 133, 'United-States': 21999, 'British-Commonwealth': 230, 'SE-Asia': 242, 'Euro_1': 159, 'Euro_2': 122, '?': 437, 'South': 64, 'China': 100, 'Latin-America': 1027, 'South-America': 207},
income:
{'<=50K': 24720},
capital_gain:
{'low': 0, 'none': 23685, 'high': 1035},
relationship:
{'Not-in-family': 7449, 'Own-child': 5001, 'Other-relative': 944, 'Husband': 7275, 'Wife': 823, 'Unmarried': 3228},}

即classfiled_data的第一层字段是a里面的字段,每个字段又对应不同类型的子字段,数字是统计所有数据的出现次数。

2.4 字段简化

舍弃没用的fnlwgt和重复的education_num字段

对于职业类型字段,Never-worked和without-Pay可以合并为Not-working字段,类似的,也可以把其他一些字段进行合并,合并的步骤是先在classfiled_data['type_employer']里新建一个'not-working'的key,然后其value就是原来['Never-worked', 'Without-pay']的数值之和。在写了很长的代码以后,我将其提取出来做成了一个函数:

def tiny(a_list, category, new_name):
if new_name not in classfiled_data[category]:
classfiled_data[category][new_name] = 0
for key in list(classfiled_data[category]):
if key in a_list and key != new_name:
classfiled_data[category][new_name] += classfiled_data[category][key]
del classfiled_data[category][key]
tiny(['Never-worked', 'Without-pay'], 'type_employer', 'not-working')
tiny(['Local-gov', 'State-gov'], 'type_employer', 'other-govt')
tiny(['Self-emp-inc', 'Self-emp-not-inc'], 'type_employer', 'self-employed')
同样对其他字段也进行了类似的化简。

这里有这样几个字段需要单独处理:

capital_gain 利用中位数划分成三部分:(-INF, 0] (0, mid] (mid, INF]

capital_loss 同上

hr_per_week 工作时间按照10小时间隔划分了。最大值99,映射到100s上

age 按照5为间隔划分了20组,

3. 测试数据

由于前面对数据进行了化简,所以测试数据的输入也需要按照上面的划分进行映射,我代码里直接使用生成好的字典进行映射。

针对每条数据,计算P(输入数据的特征|>50k) 和P(输入数据的特征|<=50k),取大的返回结果。