机器学习实验1---决策树预测泰坦尼克数据集

泰坦尼克号乘客数据集分析(ID3算法决策树)

泰坦尼克问题是一个比较经典的案例,此次实验的目的在于用决策树进行乘客的生存预测,数据集中的具体字段为:

数据 含义
PassengerId 乘客编号
Survived 是否幸存
Pclass 船票等级
Name 乘客姓名
Sex 乘客性别
SibSp 亲戚数量(兄妹、配偶数)
Parch 亲戚数量(父母、子女数)
Ticket 船票号码
Fare 船票价格
Cabin 船舱
Embarked 登录港口

获取数据

直接使用老师给的数据集,也不需要进行数据分割,直接就是891条训练集和418条测试集,直接加载进来(想要清洗过后的数据的话可以留下言)

import pandas as pd

# 数据加载
train_data = pd.read_csv('./titanic/train.csv')
test_data = pd.read_csv('./titanic/test.csv')
gender_data = pd.read_csv('./titanic/gender_submission.csv')

输出一下数据的基本信息

# 数据探索
print(train_data.info())

RangeIndex: 891 entries, 0 to 890
Data columns (total 12 columns):
 #   Column       Non-Null Count  Dtype  
---  ------       --------------  -----  
 0   PassengerId  891 non-null    int64  
 1   Survived     891 non-null    int64  
 2   Pclass       891 non-null    int64  
 3   Name         891 non-null    object 
 4   Sex          891 non-null    object 
 5   Age          714 non-null    float64
 6   SibSp        891 non-null    int64  
 7   Parch        891 non-null    int64  
 8   Ticket       891 non-null    object 
 9   Fare         891 non-null    float64
 10  Cabin        204 non-null    object 
 11  Embarked     889 non-null    object 
dtypes: float64(2), int64(5), object(5)
memory usage: 83.7+ KB
None

可以看到它展示了:行数、数据完整度、数据类型、字段名、数据类型数、内存使用情况

数据清洗

可以看到数据中Age、Fare、Cabin、Embarked存在缺失,Age数值型,代表年龄。我们可以用平均值补齐,Fare是船票价格,同理。

# 数据清洗
train_data['Age'].fillna(train_data['Age'].mean(), inplace=True)
test_data['Age'].fillna(test_data['Age'].mean(), inplace=True)
train_data['Fare'].fillna(train_data['Fare'].mean, inplace=True)
test_data['Fare'].fillna(test_data['Fare'].mean, inplace=True)

Cabin为船舱信息,由于训练集和测试集中有大量的缺失数据,盲目补齐会影响决策,所以不进行补齐。
Embarked是港口信息,我们可以使用value_counts()方法进行观察:

print(train_data['Embarked'].value_counts())
S    644
C    168
Q     77
Name: Embarked, dtype: int64

S的占比超过了70%,所以我们可以用s值进行补齐

train_data['Embarked'].fillna('S', inplace=True)
test_data['Embarked'].fillna('S', inplace=True)

特征选择

通过数据探索,我们可以发现PassengerId为乘客的编号,对分类没有作用,可以舍弃,Name为乘客姓名,也可以舍弃,Cabin缺失值太多,也要舍弃,Ticket为船票号码,杂乱无章,可以舍弃,剩余的字段包括:Pclass、Sex、Age、SibSp、Parch 和 Embarked,这些属性分别表示了乘客的船票等级、性别、年龄、亲戚数量以及船仓

# 特征选择
features=['Pclass', 'Sex', 'Age', 'SibSp', 'Parch', 'Embarked']
train_features = train_data[features]
train_labels = train_data['Survived']
test_features = test_data[features]
test_labels = gender_data.values.tolist()

特征值中,有一些是字符串类型的值,不利于后续的运算,需要转成数字的类型,我们可以用klearn 特征选择中的 DictVectorizer 类进行上述操作:

from sklearn.feature_extraction import DictVectorizer
dvec = DictVectorizer(sparse=False)
# train_features = dvec.fit(train_features.to_dict(orient='records'))
train_features = dvec.fit_transform(train_features.to_dict(orient='records'))
test_features = dvec.transform(test_features.to_dict(orient='records'))
names=dvec.feature_names_
print(names)
['Age', 'Embarked=C', 'Embarked=Q', 'Embarked=S', 'Parch', 'Pclass', 'Sex=female', 'Sex=male', 'SibSp']

至此我们就得到了一个9个特征值,891个样本的9列的特征矩阵。

决策树训练

使用ID3算法构建决策树,即创建DecisionTreeClassifier的时候,设置criterion=‘entropy’

from sklearn.tree import DecisionTreeClassifier

# 构造ID3决策树
clf = DecisionTreeClassifier(criterion='entropy')
# 决策树训练
clf.fit(train_features, train_labels)
DecisionTreeClassifier(criterion='entropy')

决策树可视化

可以用Grapviz进行决策树的可视化,图片可以在本notebook的同级目录找到

# 决策树可视化
import pydot
from sklearn import tree
tree.export_graphviz(clf, out_file="./tree.dot",feature_names=names)
(graph,) = pydot.graph_from_dot_file('tree.dot')
graph.write_png('tree.png')
from PIL import Image
display(Image.open("./tree.png"))

模型的预测 & 评估

首先得到测试集的特征矩阵,然后使用训练好的clf树进行预测。

import numpy as np
# 决策树预测
pred_labels = clf.predict(test_features)
# 得到决策树准确率
cnt = 0
for i in range(len(pred_labels)):
    if pred_labels[i] == test_labels[i][1]:
        cnt += 1
print("准确率为%.2f"%(cnt/len(pred_labels)))
准确率为0.76

使用自己写的ID3训练

之前布置的作业中有写过ID3算法,此处直接使用

from math import log
features1 = ['Pclass', 'Sex', 'Age', 'SibSp', 'Parch', 'Embarked', 'Survived']
features2 = ['Pclass', 'Sex', 'Age', 'SibSp', 'Parch', 'Embarked']
train_features1 = np.array(train_data[features1]).tolist()
test_features1 = np.array(test_data[features2]).tolist()
# 计算标准熵
def calcShannonEnt(dataset):
    # 返回数据集行数
    num_data = len(dataset)
    shannonEnt = 0.0  # 经验熵
    # print(num_data)
    label_count = {}  # 保存每个标签(label)出现次数的字典
    # 对每组特征向量进行统计
    for i in dataset:
        # 当使用负数索引时,python将从右开始往左数,因此 -1 是最后一个元素的位置
        label = i[-1]  # 提取标签信息
        if label not in label_count.keys():  # 如果标签没有放入统计次数的字典,添加进去
            label_count[label] = 0
        label_count[label] += 1  # label计数
        # print(label)
    # print(label_count)  # 输出每个标签(label)的出现次数
    # 计算经验熵
    for key in label_count.keys():
        Prob = float(label_count[key]) / num_data  # 选择该标签的概率
        shannonEnt -= Prob * log(Prob, 2)  # 利用公式计算
    return shannonEnt  # 返回经验熵
# 划分子集,来求条件熵
def splitDataSet(dataset, axis, value):
    # 创建返回的数据集列表
    retDataSet = []
    # 遍历数据集
    for featVec in dataset:
        if featVec[axis] == value:
            # 去掉axis特征
            reducedFeatVec = featVec[:axis]
            # print("reducedFeatVec",reducedFeatVec)
            # 将符合条件的添加到返回的数据集
            reducedFeatVec.extend(featVec[axis + 1:])
            # print("reducedFeatVec=",reducedFeatVec)
            retDataSet.append(reducedFeatVec)
            # print("retDataSet=",retDataSet)
    # 返回划分后的数据集
    return retDataSet
# 计算信息增益最大特征
def chooseBestFeatureToSplit(dataset, label):
    num_data = float(len(dataset))  # 数据集行数
    num_label = len(dataset[0]) - 1  # 特征数量
    # 计数数据集的香农熵,即什么都没有做时根据已知数据集计算出来的熵
    shannonEnt = calcShannonEnt(dataset)
    best_information_value = 0.0  # 将最佳信息增益初始化为0
    best_label_axis = -1  # 最优特征的索引值
    # 遍历所有特征
    for i in range(num_label):
        # 获取dataSet的第i个所有特征
        label_list = [example[i] for example in dataset]
        # 创建set集合{},元素不可重复
        label_set = set(label_list)
        # print(f'label_list = {label_list}')
        # print(f'label_set = {label_set}')
        condition_Ent = 0.0  # #经验条件熵,初始化条件熵为0
        # 计算信息增益
        for label in label_set:
            # set_after_split划分后的子集
            set_after_split = splitDataSet(dataset, i, label)
            # 计算子集的概率
            Prob = len(set_after_split) / num_data
            # 根据公式计算经验条件熵
            condition_Ent += Prob * calcShannonEnt(set_after_split)
        # 计算信息增益的公式
        imformation_value = shannonEnt - condition_Ent
        # 打印出每个特征的信息增益
#         print("第%d个特征%s的增益为%.3f" % (i, label[i], imformation_value))
        # print("第%d个特征的增益为%.3f" % (i,information_value))
        if imformation_value > best_information_value:  # 比较出最佳信息增益
            # 更新信息增益,找到最大的信息增益
            best_information_value = imformation_value
            # 记录信息增益最大的特征的索引值
            best_label_axis = i
            # 返回信息增益最大特征的索引值
    return best_label_axis
# ID3算法核心:以每个结点上的信息增益为选择的标准来递归的构建决策树
def createTree(dataSet, label):
    # 取分类标签
    class_list = [example[-1] for example in dataSet]
    # 如果类别完全相同,则停止继续划分
    if class_list.count(class_list[0]) == len(class_list):  # count() 方法用于统计某个元素在列表中出现的次数。
        return class_list[0]
    bestFeat = chooseBestFeatureToSplit(dataSet, label)  # 选择最优特征
    bestFeatLabel = label[bestFeat]  # 最优特征的标签
    # print(bestFeat,bestFeatLabel)
    # 根据最优特征的标签生成树
    mytree = {bestFeatLabel: {}}
    # 删除已经使用的特征标签
    del (label[bestFeat])
    # 得到训练集中所有最优特征的属性值
    clasify_label_value = [example[bestFeat] for example in dataSet]
    #  set 可以去掉重复的属性值
    set_clasify_label_value = set(clasify_label_value)
    # 遍历特征,创建决策树
    for value in set_clasify_label_value:
        new_label = label[:]  # 子集合
        # 构建数据的子集合,并进行递归
        mytree[bestFeatLabel][value] = createTree(splitDataSet(dataSet, bestFeat, value), new_label)
    return mytree
# 测试效果函数
import random
def classify(inputTree, testVec, labels):
    # 获取决策树节点
    firstStr = next(iter(inputTree))  # iter() 函数用来生成迭代器。iter(object[, sentinel])object -- 支持迭代的集合对象。
    # 下一个字典
    if firstStr == 'Survived':
        return random.randrange(0, 1, 1) # 对于特殊情况随机给出判断
    secondDict = inputTree[firstStr]
    featIndex = labels.index(firstStr)
    for key in secondDict.keys():
        if testVec[featIndex] == key:
            if type(secondDict[key]).__name__ == 'dict':
                classLabel = classify(secondDict[key], testVec,labels)
            else:
                classLabel = secondDict[key]
        else:
            return random.randrange(0, 1, 1)# 对于特殊情况随机给出判断
    return classLabel
clf1 = createTree(train_features1, features1)
i = 0
cnt1 = 0
# 得到决策树准确率
for test in test_features1:
    if test_labels[i][1] == classify(clf1,test,features2):
        cnt1 += 1
    i+=1
print("准确率为%.2f"%(cnt/len(test_features1)))
准确率为0.76

你可能感兴趣的:(机器学习,决策树,机器学习,数据挖掘)