根据最优特征进行分类并创建决策树

当你获得一组数据

  头发  声音  性别
[['长', '粗', '男'],
 ['短', '粗', '男'],
 ['短', '粗', '男'],
 ['长', '细', '女'],
 ['短', '细', '女'],
 ['短', '粗', '女'],
 ['长', '粗', '女'],
 ['长', '粗', '女']]

该如何对它按照特征进行分类

当然,最简单的,可以利用groupby进行分组

import pandas as pd
dataset = [['长', '粗', '男'],
           ['短', '粗', '男'],
           ['短', '粗', '男'],
           ['长', '细', '女'],
           ['短', '细', '女'],
           ['短', '粗', '女'],
           ['长', '粗', '女'],
           ['长', '粗', '女']]
hair_list = []
voice_list = []
sex_list = []
for i in range(len(dataset)):
    hair_list.append(dataset[i][0])
    voice_list.append(dataset[i][1])
    sex_list.append(dataset[i][2])
data = pd.DataFrame({
    "hair": hair_list,
    "voice": voice_list,
    "sex": sex_list
})
group = data.groupby("sex")   # 按照sex进行分类
for i in list(group):         # 如果不追求美观的话此处循环也可用print(list(group))代替
    print(i)
    if i == ")":
        print("\n")

可以得到这样的输出结果

(‘女’,   hair voice sex
3    长     细   女
4    短     细   女
5    短     粗   女
6    长     粗   女
7    长     粗   女)
(‘男’,   hair voice sex
0    长     粗   男
1    短     粗   男
2    短     粗   男) 

但这毕竟是人为给定的分类结果,存在着数据量过大不好认为分析或分析错误的情况(不一定选择的特征就是最优特征),那么,此处就要根据各个特征所蕴含的信息熵来进行自动化分析

首先是库的导入

from math import log
import operator

再者是创建原始数据(当然也可以采用直接导入的方式 ,但此示例仅以少量数据做示范)

def create_dataset():                                   # 创造示例数据
    dataset = [['长', '粗', '男'],
               ['短', '粗', '男'],
               ['短', '粗', '男'],
               ['长', '细', '女'],
               ['短', '细', '女'],
               ['短', '粗', '女'],
               ['长', '粗', '女'],
               ['长', '粗', '女']]
    labels = ['头发', '声音']                           # 两个特征
    return dataset, labels                             # 返回数据集和特征表

然后是一些辅助类的函数

如切片函数(姑且这么叫吧)

def split_dataset(dataset, axis, value):                  # 按某个特征分类后的数据
    ret_dataset = []                                      # 设置初始返回数据集为空列表
    for featVec in dataset:                               # 遍历数据集
        if featVec[axis] == value:       # 如果数据集元组第axis列的值等于value,则对其进行切片
            reduced_feat_vec = featVec[:axis]
            reduced_feat_vec.extend(featVec[axis + 1:])
            ret_dataset.append(reduced_feat_vec)
    return ret_dataset                                    # 返回切片结果

多数特征选择函数

def majority_cnt(class_list):                                           # 按分类后类别数量排序,比如:最后分类为2男1女,则判定为男;
    class_count = {}                                                    # 设置类别字典初始值为空
    for vote in class_list:                                             # 遍历类别列表
        if vote not in class_count.keys():                              # 如果该类别不在类别字典中,则添加该类别,并设置该类别初始数量值为0
            class_count[vote] = 0
        class_count[vote] += 1                                          # 统计各类别对应的类别数量
    sorted_class_count = sorted(class_count.items(), key=operator.itemgetter(1), reverse=True)      # 按照类别数量对类别字典进行降序排序
    return sorted_class_count[0][0]                                     # 返回类别字典第一个类别,即数量最大的类别

 然后是一些较为重要的函数了

比如计算信息熵

def calc_shannon_entropy(dataset):                      # 计算数据的熵(entropy)
    num_entries = len(dataset)                          # 数据条数
    label_counts = {}                                   # 创建一个关于性别及其数量的字典
    for featVec in dataset:                             # 遍历数据集
        current_label = featVec[-1]                     # 获取每行数据的最后一个字(类别),即性别
        if current_label not in label_counts.keys():    # 如果性别不在label_counts字典的关键字里
            label_counts[current_label] = 0             # label_counts字典中current_label(性别)关键字取初始值0
        label_counts[current_label] += 1                # 统计有多少个类以及每个类的数量
    shannon_ent = 0                                     # 设置信息熵的初始值为0
    for key in label_counts:                            # 遍历label_counts字典
        prob = float(label_counts[key]) / num_entries   # 计算单个类的熵值
        shannon_ent -= prob * log(prob, 2)              # 累加每个类的熵值
    return shannon_ent                                  # 返回信息熵的值

再比如最优特征值的选择函数(之所以选择老长串的英文也是方便大家理解嘛(O~O))

def choose_best_feature_to_split(dataset):                              # 选择最优的分类特征
    num_features = len(dataset[0]) - 1                                  # 计算特征的数量
    base_entropy = calc_shannon_entropy(dataset)                        # 数据集原始的熵
    best_info_gain = 0                                                  # 设置最优信息增益初始值为0
    best_feature = -1                                                   # 设置最优特征初始值为-1
    for i in range(num_features):                                       # 遍历所有特征
        feat_list = [example[i] for example in dataset]                 # 获取第i列特征列表
        unique_values = set(feat_list)                                  # 剔除该列重复特征,保留该列所有不重复的特征
        new_entropy = 0                                                 # 设置新的熵值为0
        for value in unique_values:                                     # 遍历该列所有不重复的特征
            sub_dataset = split_dataset(dataset, i, value)              # 对数据集第i列按特征进行分类
            prob = len(sub_dataset) / float(len(dataset))
            new_entropy += prob * calc_shannon_entropy(sub_dataset)     # 按特征分类后的熵
        info_gain = base_entropy - new_entropy                          # 原始熵与按特征分类后的熵的差值,即信息增益(info_gain)
        if info_gain > best_info_gain:                                  # 获取信息增益最大的特征作为最优特征
            best_info_gain = info_gain
            best_feature = i
    return best_feature                                                 # 返回最优特征

最后,合成大法——创建决策树

def create_tree(dataset, labels):                               # 创造决策树
    class_list = [example[-1] for example in dataset]           # 类别:男或女,将数据集最后一列单独取出,构成类别列表
    if class_list.count(class_list[0]) == len(class_list):      # 如果类别列表中类别唯一,则返回该唯一类别
        return class_list[0]
    if len(dataset[0]) == 1:                                    # 如果数据集中第一个元组只有一个元素,则返回majority_cnt函数结果
        return majority_cnt(class_list)                         # 返回类别列表的主要类别
    best_feat = choose_best_feature_to_split(dataset)           # 选择最优特征
    beat_feat_label = labels[best_feat]
    my_tree = {beat_feat_label: {}}                             # 设置决策树为以最优特征为键的字典,分类结果以字典形式保存
    del (labels[best_feat])                                     # 删除特征列表里的最优特征,保留余下的特征
    feat_values = [example[best_feat] for example in dataset]   # 获取数据集里的最优特征列,并创建特征值列表feat_values
    unique_values = set(feat_values)                            # 剔除特征值列表的重复数据,获取其中所有的唯一元素,并保存在unique_values中
    for value in unique_values:                                 # 遍历唯一特征值列表unique_values
        sub_labels = labels[:]                                  # 设置暂存特征列表为余下特征列表labels
        # 以数据集dataset中按照最优特征best_feat分类后的数据和暂存特征列表,进行递归调用create_tree函数,不断完善决策树my_tree字典
        my_tree[beat_feat_label][value]\
            = create_tree(split_dataset(dataset, best_feat, value), sub_labels)
        return my_tree                                          # 返回my_tree决策树

再把主函数添上

if __name__ == "__main__":
    dataset_, labels_ = create_dataset()
    print(create_tree(dataset_, labels_))

 来看看输出结果

{‘声音’: {‘粗’: {‘头发’: {‘短’: ‘男’}}}}

或者

{‘声音’: {‘细’: ‘女’}}

或者

{‘声音’: {‘粗’: {‘头发’: {‘长’: ‘女’}}}}

不用太过去纠结结果与现实的正确性(谁能拒绝程序员写出一个声音粗犷的小姐姐呢╰( ̄▽ ̄)╮) 

但是为什么同样一个决策树的输出结果会不一样呢???

文章出处登录后可见!

已经登录?立即刷新

共计人评分,平均

到目前为止还没有投票!成为第一位评论此文章。

(0)
青葱年少的头像青葱年少普通用户
上一篇 2022年5月11日
下一篇 2022年5月11日

相关推荐