决策数和k近邻分布

本文又是一篇学习笔记,链接是一个很好的平台,建议自己去做一下,会很有收获,这里只是记录了自己的学习过程以及写下自己一些疑惑的点,希望自己不要忘记。

决策树

决策数是分类与回归问题常用的方法之一。

构建决策树


熵是一个在物理、信息论和其他领域中广泛应用的重要概念,可以衡量获得的信息量。对于具有N种可能状态的系统(这个系统理解为数据集不同的目标变量)而言,熵的定义如下:
在这里插入图片描述
熵可以描述为系统的不确定程度,熵越高,系统的有序性越差,反之亦然。熵可以帮助我们高效的分割数据,例如帮我们找出将哪一个特征当做根节点比较好。
玩具示例
这个示例能够很好地解释熵如何有利于构建决策树模型的。
在这里插入图片描述
这里有9个蓝球和11个黄球,如果随机选择一个球,这个球是蓝球的概率是p1=9/20,是黄球的概率是p2=11/20,这意味着熵S0=-9/20log29/20-11/20log211/20≈1。
接下来将球分为[位置大于12、位置小于等于12]这两组:
在这里插入图片描述
分组后熵的变化如下:
左边的熵为S1=− 5/13log25/13− 8/13log 28/13 ≈0.96。
右边的熵为S2=− 1/7log21/7− 6/7log 26/7 ≈0.6。
可见,两组的熵都下降了,而且右边的一组下降的更多。熵实际上是系统的不确定程度,熵的下降被称为信息增益。数学上,基于变量Q(在这个例子中是x≤12)所做的分割,得到的信息增益(IG)定义为:
在这里插入图片描述
所以在上述示例中,q=2,N1=13,N2=7,因此信息增益为:
在这里插入图片描述

按照这样的方法继续分割下去,可以构建一个基于球的位置预测球颜色的决策树,但是当我们再向里面增加一个球,这个决策树可能无法很好地工作,因为其完全拟合了训练集(过拟合)。如果想提高它的泛化能力,可以考虑减少特征值。
决策树构建算法
构建决策树的流行算法(ID3或ID4.5)的核心,是贪婪最大化信息增益:在每一步,算法都会选择在分割后能给出最大信息增益的变量。接着重复这一流程,直到熵为0(或者为了避免过拟合,直到某个熵较小的值)。不同的算法使用不同的推断,通过‘提前停止’或‘截断’以避免构建出过拟合的树。
分类问题中其他分割质量标准
上面讨论了熵是如何衡量树的分区的,还有其他指标来衡量分割的好坏:

  • 基尼不确定性(Gini uncertainty):
    在这里插入图片描述
  • 错分率(Misclassification error):
    在这里插入图片描述
    在实践中几乎不用错分率,而基尼不确定性和信息增益的效果差不多

示例
下面用一棵决策树拟合一些合成数据。这些合成数据属于两个不同的类别,两个类别的均值不同,但都呈现正态分布:

# 第一类
np.random.seed(17)
train_data = np.random.normal(size=(100, 2))
train_labels = np.zeros(100)
train_data
# 第二类
# np.r_按列连接两个矩阵
train_data = np.r_[train_data, np.random.normal(size=(100, 2), loc=2)]
train_labels = np.r_[train_labels, np.ones(100)]
train_data

plt.figure(figsize=(10, 8))
plt.scatter(train_data[:, 0], train_data[:, 1], c=train_labels, s=100,
            cmap='autumn', edgecolors='black',linewidth=1.5)
# 设置分界线
plt.plot(range(-2, 5), range(4, -3, -1))

在这里插入图片描述
训练sklearn决策树,区分这两类数据,最后可视化所得的边界。

# 分类决策树
from sklearn.tree import DecisionTreeClassifier
# 编写辅助函数,返回网格矩阵(用于显示分类器的边界)
def get_grid(data):
    x_min, x_max = data[:, 0].min()-1, data[:, 0].max()+1
    y_min, y_max = data[:, 1].min()-1, data[:, 1].max()+1
    return np.meshgrid(np.arange(x_min, x_max, 0.01), np.arange(y_min, y_max, 0.01))
# max_depth参数限制决策树的深度
clf_tree = DecisionTreeClassifier(criterion='entropy', max_depth=3, random_state=17)
# 训练决策树
clf_tree.fit(train_data, train_labels)
xx, yy = get_grid(train_data)
# 预测结果,将网格矩阵的每一个点送入决策树模型进行预测
predicted = clf_tree.predict(np.c_[xx.ravel(), yy.ravel()]).reshape(xx.shape)
import matplotlib.pyplot as plt
# 根据预测的结果绘制分类图
plt.pcolormesh(xx, yy, predicted, cmap='autumn')
plt.scatter(train_data[:, 0], train_data[:, 1], c=train_labels,
            s=100, cmap='autumn', edgecolors='black', linewidths=1.5)
plt.show()

当决策树的深度为5时的分类结果:
在这里插入图片描述
当决策树的深度为3时,决策树的分类结果如下:
在这里插入图片描述
决策树可视化决策树可视化
使用sklearn.tree的export_graphviz()方法。使用Graphvia可视化dot文件。
通过pydotplus和export_graphviz库我们可以方便的看到决策树本身是怎样的。使用StringIO()函数开辟一个缓存空间保存决策树,通过export_graphviz()函数以DOT格式导出决策树的GraphViz表示,然后将其写入out_file中。使用graph_from_dot_data()函数读入数据并通过Image()函数显示决策树。

# 使用StringIO开辟一个缓存空间用于保存决策树
dot_data = StringIO()
# export_graphvi函数以dot格式导出决策树的图形化表示
export_graphviz(clf_tree, feature_names=['x1', 'x2'], out_file=dot_data, filled=True)
# 使用graph_from_dot_data函数读入数据
graph = pydotplus.graph_from_dot_data(dot_data.getvalue())
# 使用Image函数显示数据
Image(value=graph.create_png())

在这里插入图片描述

在scikit-learn版本21.0开始,可以使用scikit-learn的tree.plot_tree方法来利用matplotlib将决策树可视化,不再需要依赖于dot库。

from sklearn import tree
tree.plot_tree(clf_tree, feature_names=['x1', 'x2'], 
               class_names=['0', '1'], filled=True)
plt.show()

在这里插入图片描述
读懂决策树
上述示例中,总共有200个合成样本,每个分类各有100个合成数据。初始状态的熵是最大的S=1。通过将向x2与1.211的大小进行比较进行第一次分割,基于这一次分割,将样本分成两组,这时这两组的熵都下降了,这样的过程持续进行,直到决策树的深度为3。在上图中,属于第一类的样本越多,橙色就越深,属于第二类的样本越多,该节点的蓝色就越深,若两类样本的数量相等,则为白色。

运用启发式算法来限制选择的属性数量

import pandas as pd
data = pd.DataFrame({'age': [17, 64, 18, 20, 38, 49, 55, 25, 29, 31, 33],
                     'Loan Default': [1, 0, 1, 0, 1, 0, 0, 1, 1, 0, 1]})
# data = data.sort_values(by='age')
# 训练模型并可视化
import numpy as np
from sklearn.tree import DecisionTreeClassifier
clf_tree = DecisionTreeClassifier(random_state=17)
clf_tree.fit(data['age'].values.reshape(-1, 1), data['Loan Default'].values)
import matplotlib.pyplot as plt
from sklearn import tree
plt.figure(figsize=(10, 8))
tree.plot_tree(clf_tree, feature_names='age', filled=True, fontsize=12)
plt.show()

在这里插入图片描述

data.sort_values('Age')

在这里插入图片描述
那么上面的决策树使用5个值来评估年龄:43.5、19、22.5、30、32。其实也就是上图中按照年龄进行升序排列时从1变到0或从0变到1时年龄的均值,决策树以此作为分割的阈值。
所以决策树处理数值特征最简单的启发式算法是升序排列它的值,只关注目标变量(Loan Dedault)发生改变的那些值。当数据集具有大量数值特征,且每个特征具有大量唯一值时(不重复的值),只选择最高信息增益的前N个阈值(如阈值x≤43.5,N指决策树的深度),这一过程也可以理解为:首先构建了一个深度为1的决策树,计算不同分组的熵,然后选择最佳阈值进行比较。比如,我们根据【薪水≤34.5】分割,左子组的熵为0,右子组的熵为0.954,信息增益大概是0.3。根据【薪水≤95】分割,左边的子组的熵是0.97,右边的熵是0,信息增益大概是0.11.以这样的方式计算每种分区的信息增益,那么在使用所用特征(包括数值和非数值)构造一棵大决策树之前就可以选出每个数值特征的阈值。

树的关键参数

一般来说,决策树的深度过大,容易过拟合,导致在新的数据上表现不佳。而且在树的最深处,可能会存在无关紧要的特征组成的分区。
但在两种情况下,树可以被构建到最大深度(每个叶节点只有一个实例):

  • 随机森林。它将构建为最大深度的单个树的响应进行平均
  • 决策树修剪。在这种方法中,树首先被构造成最大深度。然后,从底部开始,基于交叉验证来比较有分区或无分区的情形下树的质量状况,进而移除树的一些节点。

常见的解决决策树过拟合的方法为:

  • 人工限制深度或叶节点的最少样本数。
  • 对数进行剪枝。

scikit-learn的DecisionTreeClassifier类的主要参数为:

  • max_depth树的最大深度;
  • max_features搜索最佳分区时的最大特征数(特征很多时,设置这个参数很有必要,因为基于所有特征搜索分区会很昂贵)。
  • min_samples_leaf叶节点的最少样本数。

树的参数可以根据输入数据设定,通常通过交叉验证可以确定参数范围。

回归问题中的决策树

回归树将特征空间划分为若干单元,每一个划分单元有一个特定的输出。回归树有两个核心问题:切分点(即建立决策树的点),输出值的确定。切分点的选择使用最小二乘法,输出值得选择是单元内的均值。
切分点理解如下:
在这里插入图片描述
对特征空间的划分采用启发式的方法,每次划分考量当前集合中所有的特征的所有取值,根据平方误差最小化选择最优的一个作为切分点。如选择当前训练集的第j个特征变量的xj和它的取值s作为切分变量和切分点,并定义两个区域:
在这里插入图片描述
为了找出最优的j和s,对下式求解:
在这里插入图片描述
找到最优的切分点(j,s)后,将输入空间划分为两个区域,接着对每个区域重复上述划分过程,直到划分完成,形成一棵回归树,这样的回归树被称为最小二乘回归树。
可见,对数值变量进行预测,构造决策树的的思路和分类问题是一样的,但是衡量决策树好坏的质量标准改变如下:
在这里插入图片描述
通过最小化方差D,使得该叶节点的目标特征的值大致相等,以此来划分训练集的特征。

from sklearn.tree import DecisionTreeRegressor
n_train = 150
n_test = 1000
noise = 0.1


def f(x):
    x = x.ravel()
    return np.exp(-x ** 2) + 1.5 * np.exp(-(x - 2) ** 2)


def generate(n_samples, noise):
    X = np.random.rand(n_samples) * 10 - 5
    X = np.sort(X).ravel()
    y = np.exp(-X ** 2) + 1.5 * np.exp(-(X - 2) ** 2) + \
        np.random.normal(0.0, noise, n_samples)
    X = X.reshape((n_samples, 1))
    return X, y


X_train, y_train = generate(n_samples=n_train, noise=noise)
X_test, y_test = generate(n_samples=n_test, noise=noise)

# 训练模型时使用的是
reg_tree = DecisionTreeRegressor(max_depth=5, random_state=17)
# 训练模型时使用的是添加噪声的训练集
reg_tree.fit(X_train, y_train)
# 所以模型预测的数据也是添加噪声之后的结果
reg_tree_pred = reg_tree.predict(X_test)

plt.figure(figsize=(10, 6))
# 画出的曲线是不添加噪声的情况
plt.plot(X_test, f(X_test), "b")
# 画出训练集和加了噪声的结果的散点图 
plt.scatter(X_train, y_train, c="b", s=20)
# 画出的曲线是测试集和模型训练出来的结果
plt.plot(X_test, reg_tree_pred, "g", lw=2)
plt.xlim([-5, 5])
# 均方误差计算的是y_test(带有噪声的目标值),reg_tree_pred(模型训练的数据)
# 这个是模型测试结果和测试集真实值之间的均方误差,
# 用于衡量模型的训练结果,除此之外没有别的意义
plt.title("Decision tree regressor, MSE = %.2f" %
          (np.sum((y_test - reg_tree_pred) ** 2) / n_test))
plt.show()

在这里插入图片描述

可以看出,决策树使用分段的常用函数逼近数据。

K近邻

k近邻或K-NN是另一个非常流行的分类方法。当然,也可以用于回归问题。这一方法遵循紧密假说:如果样本间的距离能以足够好的方法衡量,那么相似的样本更可能属于同一分类。

在K近邻方法中,为了对测试集中的每个样本进行分类,需要依次进行如下操作:

  • 计算测试集中样本训练集中每个样本之间的距离
  • 从训练集中选取k个距离最近的样本
  • 测试样本的类别将是这k个样本中比例最大的分类
    在回归上应用k近邻也很简单,需要将上述的第三部不返回分类,而是一个数字,即目标变量(要求的值)在这k个邻居中的均值或中位数。

k近邻方法需要对测试样本进行分类时,不需要训练数据产生训练模型。被认为是没有模型的算法,也可以认为训练集就是模型本身。

K近邻方法的的分类/回归效果取决于一些参数:

  • 邻居数k
  • 样本之间距离的度量,常见包括Hamming,欧几里得,余弦和Minkowski距离,而且要注意特征的归一化处理
  • 邻居的权重(每个邻居贡献不同的权重,如样本越远,权重越低)

sciki-learn的KNeighborsClassifier类
sklearn.neighbors.KNeighborsClassifier 类的主要参数为:

  • weights:可设为uniform(所有权重相等),distance(权重和测试样本的距离成反比),还可以设定其他用户自定义的函数。
  • algorithm(可选,设定寻找最近邻的方法):可设为brute(通过训练集上的网格搜索来计算每个测试样本的最近邻)、ball_tree、KD_tree(这两个样本间的距离储存在树中,以加速寻找最近邻)、auto(将基于训练集自动选择合适的寻找最近邻的方法)。
  • leaf_size(可选):若寻找最近邻的算法是BallTree或KDTree,则切换为网格搜索的所用的阈值。
  • metric:可设为minkowski、manhattan、euclidean、chebyshev或其他。

选择模型参数和交叉验证
机器学习算法的主要任务是可以泛化未见过的数据。因为无法立刻得知模型在新数据上的表现,因此需要牺牲一部分训练集来验证模型的质量。通常使用下述两种方法来验证模型的质量:

  • 留置法:保留一小部分数据作为留置集来验证模型质量。
  • 交叉验证:最常见的情形是K折交叉验证。
    在这里插入图片描述
    在k折交叉验证中,模型在原数据集的K-1个子集上进行训练(上图白色部分),然后在剩下的1个子集上验证表现,重复训练和验证过程,每次使用不同的子集进行验证,总进行k次,由此得到k个模型质量评估指数,通常使用这些评估指数的求和平均数来衡量分类/回归模型的总体质量。
    相比置留法,交叉验证更好的评估模型在新数据上的表现,当然,数据集非常大时,交叉验证对机器的计算能力要求会非常高。
from sklearn.model_selection import GridSearchCV, cross_val_score
tree_grid = GridSearchCV(model_name, '要进行调优的参数', cv=n('使用n折交叉验证')
, n_jobs=-1, verbose=True))
tree_grid.fit(X_train, y_train)
# 列出交叉验证得出的最佳参数
tree_grid.best_params_
# 列出相应准确率均值
tree_grid.best_score_

在这里插入图片描述

经过交叉验证的模型所得出的一些参数(部分):
在这里插入图片描述

from sklearn.pipeline import Pipeline
from sklearn.preprocessing import StandardScaler
# Pipeline是使用 (key,value) 对的列表构建的,其中key是包含要提供此步骤名称的字符串,而value是一个估计器对象
# n_jobs:同时工作的cpu个数(-1代表全部)
knn_pipe = Pipeline([('scaler', StandardScaler()),
                     ('knn', KNeighborsClassifier(n_jobs=-1))])

在这里插入图片描述

在 MNIST 手写数字识别任务中应用决策树和 k-NN

# 使用决策树和K-NN算法来实现手写数字数据集mnist的分类问题
# 在scikit-learn库中有内置的mnist数据集该数据集中的图片为8*8的矩阵,矩阵中的每个值表示每个像素的白色亮度
from sklearn.datasets import load_digits
datas = load_digits()
x, y = datas.data, datas.target
# print(x.shape)  (1797, 64) 说明数据集中有1797张图片
# fig, axes = plt.subplots(1, 4, figsize=(10, 5))
# for i in range(4):
#     axes[i].imshow(x[i, :].reshape((8, 8)), cmap='Greys')
# plt.show()
# 使用train_test_split()方法分割数据集,其中的70%作为训练集,30%作为留置集
from sklearn.model_selection import train_test_split
x_train, x_test, y_train, y_test = train_test_split(x, y, test_size=0.3, random_state=17)
# 将数据集打乱,避免在测试集中出现训练集中从未见过的分类类型
# 其实就是该组随机数的编号,在需要重复试验的时候,保证得到一组一样的随机数。
# 比如你每次都填1,其他参数一样的情况下你得到的随机数组是一样的。
# 但填0或不填,每次都会不一样。
# 训练决策树和K-NN
from sklearn.tree import DecisionTreeClassifier
from sklearn.neighbors import KNeighborsClassifier
from sklearn.pipeline import Pipeline
from sklearn.preprocessing import StandardScaler
tree_model = DecisionTreeClassifier(random_state=17, max_depth=5)
tree_model.fit(x_train, y_train)
# knn_model = KNeighborsClassifier(n_neighbors=10)


tree_pre = tree_model.predict(x_test)
# 现对数据集进行线性化处理,在使用K-NN模型
knn_model = Pipeline([('scaler', StandardScaler()),
                   ('knn', KNeighborsClassifier(n_neighbors=10))])
knn_model.fit(x_train, y_train)
knn_pre = knn_model.predict(x_test)
# pipeline模块
# pipeline是使用(key, value)对的列表构建的,
# 其中key是包含要提供此步骤的名称的字符串,value是一个估计器对象

from sklearn.metrics import accuracy_score
tree_sco = accuracy_score(y_test, tree_pre)
knn_sco = accuracy_score(y_test, knn_pre)

# print(tree_sco, knn_sco)0.6666666666666666 0.975925925925926
# 由此可见,在mnist数据上,knn表现的更好


# 下面使用交叉验证优化模型

from sklearn.model_selection import GridSearchCV, cross_val_score
tree_params = {'max_depth': range(10,40, 10), 'max_features': [30, 50, 64]}
tree_grid = GridSearchCV(tree_model, tree_params, cv=5)
tree_grid.fit(x_train,y_train)
# print(tree_grid.best_params_, tree_grid.best_score_)
# {'max_depth': 10, 'max_features': 50} 0.8568203376968316
# 提升效果还不错啊

# 使用交叉验证来优化K-NN模型
print(np.mean(cross_val_score(KNeighborsClassifier(n_neighbors=1), x_train, y_train, cv=5)))
# 0.9864858028204642

GridSearchCV和cross_val_score的区别???
网格搜索

  • GridSearchCV是网格交叉验证,使用给定的参数(即上面的tree_params ),进行穷举搜索,选中其中能使模型最优的一组参数。
  • cross_val_score是交叉验证,将模型在k-1折上训练模型,并在剩余的1折上测试模型,输出平均值作为模型的性能。
# 信息熵计算
import math
def entropy(array):
    sample_array = pd.Series(array)
    value_list = sample_array.value_counts()
    array_len = sample_array.shape[0]
    entropy_value = 0
    for i in value_list:
        entropy_value += (-i/array_len)*math.log(i/array_len, 2)
    return entropy_value

# 信息增益计算
def information_gain(root, left, right):
    entropy_root = entropy(root)
    entropy_left = entropy(left)
    entropy_right = entropy(right)
    root_len, left_len, right_len = len(root), len(left), len(right)
    i_gain_value = entropy_root - (left_len/root_len)*entropy_left - (right_len/root_len)*entropy_right
    return i_gain_value
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值