数据挖掘——scikit_learn简单实用

分类

分类是数据挖掘领域最为常用的方法之一。

对于分类问题,我们通常能拿到表示实际对象或事件的数据集,我们知道数据集中每一条数据所属的类别,这些类别把一条条数据划分为不同的类。

分类应用的目标是,根据已知类别的数据集,经过训练得到一个分类模型,再用模型对类别位置的数据进行分类。

例如:我们可以对收到的邮件进行分类,标注哪些是自己希望收到的,哪些是垃圾邮件,然后用这些数据训练分类模型,实现一个垃圾邮件过滤器,这样以后再收到邮件,就不用自己去确认它是不是垃圾邮件了,过滤器就能帮你搞定。

1. Iris植物分类

1.1. 准备数据集

Iris植物分类数据集共150条植物数据,每条数据都给出了四个特征:speal length、speal width、petal length、petal width(分别表示萼片和花瓣的长度与宽度(cm))。该数据集共有三种类别:Iris Setosa(山鸢尾)、Iris Versicolour(变色鸢尾)、Iris Virginica(维吉尼亚鸢尾)。

分类目的为将根据植物的特性推测它的种类。

Iris植物分类数据集在scikit-learn库中已经内置,可以直接导入:

# 导入Iris数据库
from sklearn.datasets import load_iris
# x 为该该植物的四个特征值
# y 为该植物对应的类别
dataset = load_iris()
x = dataset.data
y = dataset.target

数据集中各特征值为连续型,也就是有无数个可能的值。而我们即将使用的算法使用类别型特征值,因此我们需要把连续值转变为类别型,这个过程叫作离散化。

最简单的离散化算法:确定一个阈值,将低于该与之的特征值置为0,高于阈值的置为1。我们把某项特征的阈值设定为该特征所有特征值的均值。每个特征的均值计算方法如下。

# 每列求均值
attribute_means = x.mean(axis = 0)

简单数据从连续的特征值变为类别值

# 将每个数据与均值数据进行对比,将连续的特征值转换为类别型
x_d = np.array(x >= attribute_means,dtype = "int")

1.2. 实现OneR算法

  • 算法思路

    它根据已有数据中,具有相同特征值的个体最可能属于哪个类别进行分类。OneR是One Rule(一条规则)的简写,表示我们只选取四个特征中分类效果最好的一个用作分类依据

  • 算法概述

    只选取四个特征中分类效果最好的一个作为分类依据。首先遍历每个特征的每一个取值,对于每一个特征值,统计它在各个类别中的出现次数,找到它出现次数最多的类别,并统计它在其他类别中的出现次数统计完所有的特征值及其在每个类别的出现次数后,我们再来计算每个特征值的错误率,计算方法为把它的各个取值的错误率相加,选取错误率最低的特征作为唯一的分类准则。

1.3. 代码全览

from sklearn.model_selection import train_test_split
from sklearn.datasets import load_iris
from collections import defaultdict
from operator import itemgetter
import numpy as np

# 数据读取
dataset = load_iris()
x = dataset.data
y = dataset.target
# 数据求各列平均值
attribute_means = x.mean(axis = 0)
# 根据平均值将对应连续特征值变为离散的类别值
x_d = np.array(x > attribute_means,dtype = "int")

# 将数据集划分为训练集和测试集
random_state = 14
x_train,x_test,y_train,y_test = train_test_split(x_d,y,random_state = random_state)
print("There are {0} train samples".format(y_train.shape))
print("There are {0} test samples".format(y_test.shape))

# 计算某个特征索引(feature)的值为当前设置值(current_value)时对应的最多植物类型索引与其他类型计数综合
# 输入参数:
# x_train:训练集特征列表;y_train:训练集类型;
# feature:训练集特征的某个特征索引;current_value:该特征当前设置值
# 输出参数:
# most_frequesnt_class:训练集在该特征(该列表)中特征值为current_value时最多的样本类型
# error:训练集在该特征(该列表)中特征值为current_value时其他样本类型的数量之和
def train_feature_value(x_train,y_train,feature,current_value):
    # 创建类型计数器字典
    class_counts = defaultdict(int)
    for sample,sample_y in zip(x_train,y_train):
        # 如果该样本的该特征的值和需要比对的当前特征值相同,该样本类型对于该特征加一
        if sample[feature] == current_value:
            class_counts[sample_y] += 1
    # 将获得的字典样本逆序排序
    sorted_class_counts = sorted(class_counts.items(),key = itemgetter(1),reverse = True)
    # 获取该类型该值为current_value时,对应的植物类型索引
    most_frequent_class = sorted_class_counts[0][0]
    # 计算样本该特征值和当前值相同,但样本对应植物类型非正确类型(非对应值最多的类型)的错误数总和
    error = sum([class_count for class_value, class_count in class_counts.items()
                     if class_value != most_frequent_class])
    return most_frequent_class,error

# 计算某个特征索引(feature)在训练集中对应的字典(该特征的特征值与对应的植物类型)与错误类型总数
# 输入参数:
# x_train:训练集特征列表;y_train:训练集类型;
# feature:训练集特征的某个特征索引
# 输出参数:
# predictors:字典(该特征的特征值与对应的植物类型)
# total_error:该特征的类型错误总数
def train_on_feature(x_train,y_train,feature):
    # 获取此特征具有的所有唯一值
    feature_vlaues = set(x_train[:,feature])
    # 存储返回的预测变量数组
    predictors = dict()
    # 存储返回的错误数量数组
    errors = []
    # 遍历该类型对应的所有唯一值,获得该类型在该值下的最多植物类型和错误数量
    for current_value in feature_vlaues:
        most_frequent_class,error = train_feature_value(x_train,y_train,feature,current_value)
        # 键为遍历中的该值,值为对应的植物类型索引
        predictors[current_value] = most_frequent_class
        errors.append(error)
    total_error = sum(errors)
    return predictors,total_error

# 计算各特征值对应的规则字典以及错误类型总数集合
all_predictors = {feature_index:train_on_feature(x_train,y_train,feature_index) for feature_index in range(x_train.shape[1])}
errors = {variable: error for variable,(featuredict,error) in all_predictors.items()}
# 按照错误数量进行顺序排序,选取错误最少的对应的植物特征索引作为模型判断依据
best_feature,best_error = sorted(errors.items(),key = itemgetter(1))[0]
print("The best model is based on feature index 【{0}】 and has error 【{1:.2f}】".format(best_feature,best_error))
# 模型字典设置
model = {"feature":best_feature,"predictor":all_predictors[best_feature][0]}

# 设置测试函数
# 输入参数:
# x_test:待判断的特征值组;model:使用的判断模型
# 输出参数:
# y_test_predict:通过训练模型判断得到的植物类型数组
def predict(x_test,model):
    feature = model["feature"]
    predictor = model["predictor"]
    y_test_predict = np.array([predictor[int(sample[feature])] for sample in x_test])
    return y_test_predict

# 输出测试值的预测结果
y_test_predict = predict(x_test,model)
# 计算测试值的正确率(判断类型和正确类型对比求平均)
accuracy = np.mean(y_test_predict == y_test) * 100
print("The test accuracy is {0:.2f}%".format(accuracy))

# 输出报告
from sklearn.metrics import classification_report
print(classification_report(y_test,y_test_predict)) 

scikit-learn

用Python语言编写的scikit-learn库,实现了一系列数据挖掘算法,提供通用编程接口、标准化的测试和调参工具,便于用户尝试不同算法对其进行充分测试和查找最优参数值。本章讲解数据挖掘通用框架的搭建方法。有了这样一个框架,后续章节就可以把讲解重点放到数据挖掘应用和技术上面。

  • 估计器(Estimator):用于分类、聚类和回归分析
  • 转换器(Transformer):用于数据预处理和数据转换
  • 流水线(Pipeline):组合数据挖掘流程,便于再次使用

1.1. scikit-leaen估计器

为帮助用户实现大量分类算法,scikit-learn把相关功能封装成所谓的估计器。估计器用于分类任务,它主要包括以下两个函数。

  • fit():训练算法,设置内部参数。该函数接受训练集及其类别两个参数。
  • predict():参数为测试机。预测测试集类别,并返回一个包含测试集各条数据类别的数组。

大多数scikit-learn估计器接受和输出的数据格式均为numpy数组或类似格式。

scikit-learn提供了大量估计器,其中有支持向量机(SVM)、随机森林、神经网络等

1.1.1. 近邻算法

近邻算法可能是标准数据挖掘算法中最为直观的一种。为了对新个体进行分类,它查找训练集,找到与新个体最相似的那些个体,看看这些个体大多属于哪个类别,就把新个体分到哪个类别。

近邻算法几乎可以对任何数据集进行分类。但因要计算数据集中每两个个体之间的距离,计算量很大;还有一个问题是在特征取离散值的数据集上表现很差。遇到这种情况应考虑使用其他算法。

1.1.2. 距离度量

距离是数据挖掘的核心概念之一。我们往往需要知道两个个体之间的距离是多少。更进一步说,我们还得能够解决一对个体相对另一对个体是否更相近等问题。这类问题的解决方法,将直接影响分类结果。

  • 欧氏距离(真实距离)

    假如你在图像中画两个点,用直尺测量这两个点之间的距离,得到的结果就是欧氏距离。稍微正式点来说,它其实是两个特征向量长度平方和的平方根。

    欧氏距离确实很直观,但是如果某些特征比其他特征取值大很多,精确度就会比较差。此外,如果很多特征值为0,也就是所谓的稀疏矩阵,结果也不准确。这时可以用其他距离度量方法,常用的有曼哈顿距离和余弦距离。

  • 曼哈顿距离

    曼哈顿距离为两个特征在标准坐标系中绝对轴距之和(没有使用平方距离)。拿国际象棋中的车举例子,这样更形象。假如车每次只能走一格,那么它走到当前格子对角线那头,所走的距离就是曼哈顿距离。

  • 余弦距离

    余弦距离更适合解决异常值和数据稀疏问题。直观上讲,余弦距离指的是特征向量夹角的余弦值。

距离度量

在上面每张图中,两个灰圆与白圆之间的距离是相等的。左图是欧氏距离,因此两个灰圆都落在以白圆为圆心的同一个圆的圆周上,可以用尺子量量看。中间这幅图表示的是曼哈顿距离,它指的是从灰圆到白圆所走的横向和纵向距离之和,也叫街区距离(City Block),测量时要想象棋中车的走法。右图是余弦距离示意图,计算夹角之间的余弦值,忽略特征向量的长度。

采用哪种距离度量方法对最终结果有很大影响。例如,你的数据集有很多特征,但是如果任意一对个体之间的欧氏距离都相等,那么你就没法通过欧氏距离进行比较了!曼哈顿距离在某些情况下具有更高的稳定性,但是如果数据集中某些特征值很大,用曼哈顿距离的话,这些特征会掩盖其他特征间的邻近关系。最后,再来说说余弦距离,它适用于特征向量很多的情况,但是它丢弃了向量长度所包含的在某些场景下可能会很有用的一些信息。

本章主要介绍欧氏距离,其他距离后面章节在介绍。

1.1.3. 加载数据集

即将用到的数据集叫做电离层(Ionosphere),这些数据是由高频天线收集的。这些天线的目的是侦测在电离层和高层大气中存不存在由自由电子组成的特殊结构。如果一条数据能给出特殊结构存在的证据,这条数据就属于好的那一类("g"表示),否则就是坏的("b"表示)。我们要做的是建立分类器,自动判断这些数据的好坏。

Ionosphere数据集可以从UCI机器学习数据库下载,该数据库包含大量数据集,可用于多种数据挖掘任务。打开http://archive.ics.uci.edu/ml/datasets/Ionosphere,点击Data Folder。在随后打开的页面中,下载ionosphere.data和ionosphere.names文件。把这两个文件保存到用户主目录下的Data文件夹中。

import numpy as np
import csv
data_filename = "ionosphere.data"
# 创建Numpy数组x与y存放数据集。数据集大小为351行34列。
# 后续章节中会将如何在不知道数据集大小的情况加载它
x = np.zeros((351,34),dtype = "float")
y = np.zeros((351),dtype = "bool")
# Ionosphere数据集文件为csv(Comma-Separated Values,用逗号分隔数据项)格式
# 使用csv模块来导入数据集文件,并创建csv阅读器对象。
with open(data_filename,"r") as input_file:
    reader = csv.reader(input_file)
    # 遍历文件中的每一行数据
    for i,row in enumerate(reader):
        # 将每一个个体的前34个值强制转化为浮点型存入x中
        # 将每个个体最后一个表示类别的值和"g"做比较,相等则1,否则为0存入y中
        x[i] = [float(datum) for datum in row[:-1]]
        y[i] = (row[-1] == "g")

1.1.4. 努力实现流程标准化

从上文知,scikit-learn估计器由两大函数组成:fit()和predict()。用fit方法在训练集上完成模型的创建,用predict方法在测试集上评估效果。

# 实现流程标准化
# 创建训练集和测试集。导入并运行train_test_split函数
from sklearn.model_selection import train_test_split
x_train,x_test,y_train,y_test = train_test_split(x,y,random_state = 14)
# 导入估计器(K近邻分类器)
# 导入K近邻分类器这个类,并为其初始化一个实例。该算法默认选择5个近邻作为分类依据
# 现阶段默认参数即可,后面再讲参数调优
from sklearn.neighbors import KNeighborsClassifier
estimator = KNeighborsClassifier()
# 估计器创建好后,使用训练数据进行训练。
# K近邻估计器分析训练集中的数据,比较待分类的新数据点和训练集中的数据,找到新数据点的近邻。
estimator.fit(x_train,y_train)
# 使用测试集测试算法,评估它在测试集上的表现
y_predicted = estimator.predict(x_test)
# 将得到的预测值与实际值比较计算正确率
accuracy = np.mean(y_test == y_predicted) * 100
print("The accuracy is {0:.2f}%".format(accuracy))
# Out_put: The accuracy is 86.36%

1.1.5. 运行算法

  • 概述

    在先前的几个实验中,我们把数据集分为训练集和测试集,用训练集训练算法,在测试集上评估效果。倘若碰巧走运,测试集很简单,我们就会觉得算法表现很出色。反之,我们可能会怀疑算法很糟糕。也许由于我们一时不走运,就把一个其实很不错的算法给无情抛弃了,这岂不是很可惜。

  • 解决方案

    交叉检验能解决上述一次性测试所带来的问题。既然只切一次有问题,那就多切几次,多进行几次实验。每次切分时,都要保证这次得到的训练集和测试集与上次不一样,还要确保每条数据都只能用来测试一次。算法描述如下。

    • 将整个大数据集分为几个部分(fold)
    • 对于每一部分执行以下操作:
      • 将其中一部分作为当前测试集
      • 用剩余部分训练算法
      • 在当前测试集上测试算法
    • 记录每次得分及平均得分
    • 在上述过程中,每条数据只能在测试集中出现一次,以减少(但不能完全规避)运气成分。

    scikit-learn提供了几种交叉检验方法。有个辅助函数(cross_val_score)实现了上述交叉检验步骤,现在把它导进来。

    cross_val_score默认使用Stratified K Fold方法切分数据集,它大体上保证切分后得到的子数据集中类别分布相同,以避免某些子数据集出现类别分布失衡的情况。这个默认做法很不错,现阶段就不再把它搞复杂了。

# 交叉检验
# scikit-learn提供了几种交叉检验方法。有个辅助函数实现了上述交叉检验步骤,现在把它导进来
# cross_val_score默认使用Stratified K Fold方法切分数据集,它大体上保
# 证切分后得到的子数据集中类别分布相同,以避免某些子数据集出现类别分布失
# 衡的情况。这个默认做法很不错,现阶段就不再把它搞复杂了。
from sklearn.model_selection import cross_val_score
# 将使用的估计器,完整的数据集和类别值传给它
scores = cross_val_score(estimator,x,y,scoring = "accuracy")
average_accuracy = np.mean(scores) * 100
print("The average accuracy is {0:.2f}%".format(average_accuracy))
# Out_put: The average accuracy is 82.62%

1.1.6. 设置参数

几乎所有的数据挖掘算法都允许用户设置参数,这样做的好处是增强算法的泛化能力。但是参数设置可是项技术活,选取好的参数值跟数据集的特征息息相关。

近邻算法有多个参数,最重要的是选取多少个近邻作为预测依据。scikit-learn管这个参数叫n_neighbors。下图给出两个极端的例子,n_neighbors过小时,分类结果容易受干扰,随机性很强。相反,如果n_neighbors过大,实际近邻的影响将削弱。

参数设置实例

左图(a)中,我们通常希望把测试数据(三角形)归到圆形类别。然而,如果n_neighbors的值为1,由于三角形附近红色菱形(很可能是噪音)的存在,导致分类结果为菱形,虽然菱形集中在右下角区域。右图(b)中,我们希望将测试数据归到菱形类别。然而,如n_neighbors值为7,三个最近的邻居(都是菱形)被四个圆形给击败了,三角形也因此被归到圆形类别。

如果想测试一系列n_neighbors的值,比如从1到20,可以重复进行多次实验,观察不同的参数值所带来的结果之间的差异。

avg_scores = []
all_scores = []
# 将n_neighbors的值从1-20进行多次实验,观察不同的参数值带来的差异
parameter_values = list(range(1,21))
for n_neighbors in parameter_values:
    estimator = KNeighborsClassifier(n_neighbors=n_neighbors)
    scores = cross_val_score(estimator,x,y,scoring = "accuracy")
    avg_scores.append(np.mean(scores))
    all_scores.append(scores)
# 通过作图直观查看n_neighbors的不同取值和分类正确率之间的关系
from matplotlib import pyplot as plt
plt.plot(parameter_values,avg_scores,"-o")

n_neighbors与准确率关系图

posted @ 2021-06-18 13:31  MO_OF  阅读(300)  评论(0编辑  收藏  举报