朴素贝叶斯实战:人群收入预测(基于美国人口普查收入数据)

实战背景

根据一个人的14个属性建立分类器评估一个人的收入等级。

可能的输出类型是“高于50K”和“低 于或等于50K”。

数据信息

数据来源:

美国人口普查收入数据集中的数据: https://archive.ics.uci.edu/ml/datasets/Census+Income

数据标题:

(下载的数据中没有标题,标题可有可无)

'age', 'workclass', 'fnlwgt', 'education', 'education_num',
'marital_status', 'occupation', 'relationship', 'race', 'gender',
'capital_gain', 'capital_loss', 'hours_per_week', 'native_country',
'income_bracket'

数据格式:

image-20201029204158132

流程

因为数据中的特征值包含字符型数据,所以需要对数据进行编码

sklearn.preprocessing.LabelEncoder 类可以在不损失数据的情况下对数据进行编码和解码

因为数据集中收入在 50k 以上 和 50k以下 的样本数量相差较大,会使模型偏向于样本数较多的类型,所以我们需要提取出相同数量的两种类型的样本

流程大致如下:

  • 数据加载与处理
  • 创建模型
  • 训练模型
  • 评判模型
  • 使用模型预测新数据

开始编码

导入需要用到的类和方法

# 用于编码的类
from sklearn.preprocessing import LabelEncoder

# 朴素贝叶斯类
from sklearn.naive_bayes import GaussianNB

# 交叉验证方法
from sklearn.model_selection import cross_val_score

import numpy as np

数据加载与处理

less_than_50k = []
more_than_50k = []

# 记录一下含有空缺值的数据
blank_value = 0

with open('adult.data.txt', 'r') as f:
    for line in f.readlines():
        # 若某行数据有缺失值,则舍弃掉这条数据
        if ' ?' in line:
            blank_value += 1
            continue
        # data 是一行的数据,每一行的最后一个是 \n 换行符,所以是 line[:-1],表示不处理换行符
        # 注意这里分隔符是 英文逗号加一个空格,建议直接在文件中复制
        data = line[:-1].split(', ')
        if data[-1] == '<=50K':
            less_than_50k.append(data)
        # 注意这里不能使用 else ,因为文件末尾有几行没有数据
        elif data[-1] == '>50K':
            more_than_50k.append(data)

我们可以查看下含有空缺值的样本数,两种收入类型的样本数

less_than_50k_length = len(less_than_50k)
more_than_50k_length = len(more_than_50k)

print(less_than_50k_length)  # 22654
print(more_than_50k_length)  # 7508
print(blank_value)  # 2399

因为数据集中收入在 50k 以上 和 50k以下 的样本数量相差较大,会使模型偏向于样本数较多的类型,所以我们需要提取出相同数量的两种类型的样本

# 比较大于50k和不大于50k的数据量,从两个类别的数据中提取出相同数据量
if less_than_50k_length == more_than_50k_length:
    pass
elif less_than_50k_length > more_than_50k_length:
    less_than_50k = less_than_50k[:more_than_50k_length]
else:
    more_than_50k = more_than_50k[:less_than_50k_length]

我们使用的python的list保存的数据,训练模型需要使用 numpy 的 二维数组

所以,我们需要转换下数据类型

先分别将收入在50k以下和50k以上的列表转换为两个 numpy 的二维数组

# 先将两个list转换为 numpy的二维数组,再使用np.vstack将两个二维数组合并为一个大的二维数组
less_ndarray = np.array(less_than_50k)
more_ndarray = np.array(more_than_50k)

再将两个 numpy 的二维数组合并为一个大的二维数组

# 将两个二维数组合并为一个大的二维数组
data = np.vstack([less_ndarray, more_ndarray])

如果你分不清 np.hstack 和 np.vstack ,可以查看下合并后的数据的形状

# 查看合并和的二维数组的形状,检验是否合并成功
print(data.shape)
# (15016, 15)

对样本特征值进行编码

因为有多个列的数据是非数字型的,预测数据所属类型时也需要对特征值进行编码,所以为了后面对预测样本数据编码方便,我们创建一个列表,用于存放所有非数字列的编码器,当对预测样本特征值编码时,直接使用该列对应的编码器进行编码

编码器和用于预测的模型一样,也是需要训练的

例如使用“workclass”列的数据训练出来的编码器,当接受到其他列的数据时,就会编码失败

所以我们使用一个列表存放所有的非数字列的编码器

当对预测样本特征值编码时,直接使用该列对应的编码器进行编码

# 标签编码器列表
label_encoder = []

# 存放编码后的数据,先指定为空的二维数组,再进行赋值
data_encoder = np.empty(data.shape)

# index是data每一个元素的下标,item是data每一个元素的内容
for index, item in enumerate(data[0]):
    # 若该列数据是是数字,直接赋值,否则先进行编码
    if item.isdigit():
        data_encoder[:, index] = data[:, index]
    else:
        # 对于每一个非数字的列,分别创建一个标签编码器,便于后期用来预测样本
        # 每一个标签编码器分别使用各自列的数据进行编码,这样预测数据时可以不用再训练标签分类器,直接对需要预测的样本数据进行编码
        label_encoder.append(LabelEncoder())
        data_encoder[:, index] = label_encoder[-1].fit_transform(data[:, index])

合并后的大的二维数组包含了样本的特征数据和标签

将样本的特征数据和标签值分开(标签值是二维数组的最后一列)

分开特征值和标签时,将数据格式转换为int型

X_train = data_encoder[:, :-1].astype(int)
y_train = data_encoder[:, -1].astype(int)

模型的创建与训练

# 创建朴素贝叶斯分类器
gnb_clf = GaussianNB()
# 使用样本数据训练朴素贝叶斯模型
gnb_clf.fit(X_train, y_train)

评判模型

交叉验证返回的是一个 numpy 的一维数组

包含了每一组交叉验证测试的评分,这里我们直接查看各组评分的平均值

cv = 10 表示交叉验证将数据分为10组

# 使用交叉验证对模型进行评判
scores = cross_val_score(gnb_clf, X_train, y_train, cv=10)

print(f"交叉验证得分:{round(np.mean(scores), 3)}")
# 交叉验证得分:0.627

使用模型预测新数据

对新数据特征值进行编码

# 创建一个新样本,使用训练好的模型对其进行预测,预测该样本的收入类别
input_data = ['39', 'State-gov', '77516', 'Bachelors', '13', 'Never-married', 'Adm-clerical', 'Not-in-family', 'White',
              'Male', '2174', '0', '40', 'United-States']

# 先初始化一个列表,用于存放新样本数据编码后的结果
input_data_encoder = [-1] * len(input_data)

# 对新样本的数据进行编码

# count表示新样本数据中非数字的列的顺序
# 在上面我们有一个训练好的编码器列表,通过count使用非数值的列对应的编码器对新样本数据进行编码
count = 0
for index, item in enumerate(input_data):
    if item.isdigit():
        input_data_encoder[index] = int(item)
    else:
        # tranform()对数据进行编码,参数是一个 numpy 的 ndarray,所以需要使用 np.array() 构建一个ndarray
        temp = label_encoder[count].transform(np.array([item]))
        input_data_encoder[index] = int(temp)
        count += 1

# 查看一下编码后的样本数据
print(input_data_encoder)
# [39, 5, 77516, 9, 13, 4, 0, 1, 4, 1, 2174, 0, 40, 37]

print(type(input_data_encoder))
# <class 'list'>

使用编码后的特征值进行预测

对新样本进行预测
需要注意,predict 的参数是编码后的数据
predict 的参数是一个 numpy 的二维数组,所以需要使用 reshape() 将其转换为二维数组

y_predict = gnb_clf.predict(np.array([input_data_encoder]).reshape(1,-1))

reshape(1,-1) 表示只有一个样本
reshape(-1,1) 表示样本只有一个特征值

y_predict 是经过编码后的数据,需要使用编码器解码
inverse_transform() 将编码后的数据进行解码,返回的是一个一维数组

print(label_encoder[-1].inverse_transform(y_predict)[0])

# <=50K

编码器LabelEncoder小结

编码器和用于预测的模型一样,也是需要训练的

导入用于编码的类

from sklearn.preprocessing import LabelEncoder

创建编码器类的对象

label = LabelEncoder()

训练编码器并将数据进行编码

label.fit_transform(X_data)

训练编码器

label.fit(X_data)

对数据进行编码

label.transform(X_data)

查看编码前的原始数据

inverse_transform(X_encoder)

由此可见:

label.fit_transform(X_data) = label.fit(X_data) + label.transform(X_data)
©️2020 CSDN 皮肤主题: 大白 设计师:CSDN官方博客 返回首页