K-means聚类算法研究与实例实现

K-means聚类算法研究与实例实现

(白宁超?2018年9月5日15: 01:20)

导读:k-均值算法(英文:k-means clustering),属于比较常用的算法之一,文本首先介绍聚类的理论知识包括什么是聚类、聚类的应用、聚类思想、聚类优缺点等等;然后通过k-均值聚类案例实现及其可视化有一个直观的感受,针对算法模型进行分析和结果优化提出了二分k-means算法。最后我们调用机器学习库函数,很短的代码完成聚类算法。(本文原创,转载必须注明出处:?K-means聚类算法研究与实例实现

理论介绍

聚类

什么是聚类

统计数据分析的一门技术,在许多领域受到广泛应用,包括机器学习,数据挖掘,模式识别,图像分析以及生物信息。聚类是把相似的对象通过静态分类的方法分成不同的组别或者更多的子集(subset),这样让在同一个子集中的成员对象都有相似的一些属性,常见的包括在坐标系中更加短的空间距离等。

聚类的应用

在商务上,聚类能帮助市场分析人员从客户基本库中发现不同的客户群,并且用购买模式来刻画不同的客户群的特征。在生物学上,聚类能用于推导植物和动物的分类,对基因进行分类,获得对种群中固有结构的认识。聚类在地球观测数据库中相似地区的确定,汽车保险单持有者的分组,及根据房子的类型、价值和地理位置对一个城市中房屋的分组上也可以发挥作用。聚类也能用于对Web上的文档进行分类,以发现信息。诸如此类,聚类有着广泛的实际应用。

K-means(k均值)聚类算法

什么是k-means聚类算法

k-平均算法(英文:k-means clustering)源于信号处理中的一种向量量化方法,现在则更多地作为一种聚类分析方法流行于数据挖掘领域。k-平均聚类的目的是:把 n个点划分到k个聚类中,使得每个点都属于离他最近的均值(此即聚类中心)对应的聚类,以之作为聚类的标准。k-平均聚类与k-近邻之间没有任何关系(后者是另一流行的机器学习技术)。

K-Means 是发现给定数据集的 K 个簇的聚类算法, 之所以称之为 K-均值 是因为它可以发现 K 个不同的簇, 且每个簇的中心采用簇中所含值的均值计算而成.簇个数 K 是用户指定的, 每一个簇通过其质心(centroid), 即簇中所有点的中心来描述.
聚类与分类算法的最大区别在于, 分类的目标类别已知, 而聚类的目标类别是未知的.

发展历史

虽然其思想能够追溯到1957年的Hugo Steinhaus,术语“k-均值”于1967年才被James MacQueen 首次使用。标准算法则是在1957年被Stuart Lloyd作为一种脉冲码调制的技术所提出,但直到1982年才被贝尔实验室公开出版。在1965年,E.W.Forgy发表了本质上相同的方法,所以这一算法有时被称为Lloyd-Forgy方法。更高效的版本则被Hartigan and Wong提出。

算法描述

已知观测集

其中??中所有点的均值。

k-means术语

  • 簇: 所有数据的点集合,簇中的对象是相似的。
  • 质心: 簇中所有点的中心(计算所有点的均值而来).
  • SSE: Sum of Sqared Error(误差平方和), 它被用来评估模型的好坏,SSE 值越小,表示越接近它们的质心. 聚类效果越 好。由于对误差取了平方,因此更加注重那些远离中心的点(一般为边界点或离群点)。详情见kmeans的评价标准。
    有关 簇 和 质心 术语更形象的介绍, 请参考下图:

k-means应用场景

kmeans,用于数据集内种类属性不明晰,希望能够通过数据挖掘出或自动归类出有相似特点的对象的场景。其商业界的应用场景一般为挖掘出具有相似特点的潜在客户群体以便公司能够重点研究、对症下药。

例如,在2000年和2004年的美国总统大选中,候选人的得票数比较接近或者说非常接近。任一候选人得到的普选票数的最大百分比为50.7%而最小百分比为47.9% 如果1%的选民将手中的选票投向另外的候选人,那么选举结果就会截然不同。 实际上,如果妥善加以引导与吸引,少部分选民就会转换立场。尽管这类选举者占的比例较低,但当候选人的选票接近时,这些人的立场无疑会对选举结果产生非常大的影响。如何找出这类选民,以及如何在有限的预算下采取措施来吸引他们? 答案就是聚类(Clustering)。

那么,具体如何实施呢?首先,收集用户的信息,可以同时收集用户满意或不满意的信息,这是因为任何对用户重要的内容都可能影响用户的投票结果。然后,将这些信息输入到某个聚类算法中。接着,对聚类结果中的每一个簇(最好选择最大簇 ), 精心构造能够吸引该簇选民的消息。最后, 开展竞选活动并观察上述做法是否有效。

另一个例子就是产品部门的市场调研了。为了更好的了解自己的用户,产品部门可以采用聚类的方法得到不同特征的用户群体,然后针对不同的用户群体可以对症下药,为他们提供更加精准有效的服务。

k-means算法思想

先随机选取K个对象作为初始的聚类中心。然后计算每个对象与各个种子聚类中心之间的距离,把每个对象分配给距离它最近的聚类中心。聚类中心以及分配给它们的对象就代表一个聚类。一旦全部对象都被分配了,每个聚类的聚类中心会根据聚类中现有的对象被重新计算。这个过程将不断重复直到满足某个终止条件。终止条件可以是以下任何一个:

  • 没有(或最小数目)对象被重新分配给不同的聚类。
  • 没有(或最小数目)聚类中心再发生变化。
  • 误差平方和局部最小。

得到相互分离的球状聚类,在这些聚类中,均值点趋向收敛于聚类中心。 一般会希望得到的聚类大小大致相当,这样把每个观测都分配到离它最近的聚类中心(即均值点)就是比较正确的分配方案。

k-means工作流程

创建 k 个点作为起始质心(通常是随机选择)
当任意一个点的簇分配结果发生改变时(不改变时算法结束)
    对数据集中的每个数据点
        对每个质心
            计算质心与数据点之间的距离
        将数据点分配到距其最近的簇
    对每一个簇, 计算簇中所有点的均值并将均值作为质心

k-means开发流程

收集数据:使用任意方法
准备数据:需要数值型数据类计算距离, 也可以将标称型数据映射为二值型数据再用于距离计算
分析数据:使用任意方法
训练算法:不适用于无监督学习,即无监督学习不需要训练步骤
测试算法:应用聚类算法、观察结果.可以使用量化的误差指标如误差平方和(后面会介绍)来评价算法的结果.
使用算法:可以用于所希望的任何应用.通常情况下, 簇质心可以代表整个簇的数据来做出决策.

k-means评价标准

k-means算法因为手动选取k值和初始化随机质心的缘故,每一次的结果不会完全一样,而且由于手动选取k值,我们需要知道我们选取的k值是否合理,聚类效果好不好,那么如何来评价某一次的聚类效果呢?也许将它们画在图上直接观察是最好的办法,但现实是,我们的数据不会仅仅只有两个特征,一般来说都有十几个特征,而观察十几维的空间对我们来说是一个无法完成的任务。因此,我们需要一个公式来帮助我们判断聚类的性能,这个公式就是SSE (Sum of Squared Error, 误差平方和 ),它其实就是每一个点到其簇内质心的距离的平方值的总和,这个数值对应kmeans函数中clusterAssment矩阵的第一列之和。 SSE值越小表示数据点越接近于它们的质心,聚类效果也越好。 因为对误差取了平方,因此更加重视那些远离中心的点。一种肯定可以降低SSE值的方法是增加簇的个数,但这违背了聚类的目标。聚类的目标是在保持簇数目不变的情况下提高簇的质量。

k-means优缺点

  • 优点:

    属于无监督学习,无须准备训练集
    原理简单,实现起来较为容易
    结果可解释性较好

  • 缺点:

    聚类数目k是一个输入参数。选择不恰当的k值可能会导致糟糕的聚类结果。这也是为什么要进行特征检查来决定数据集的聚类数目了。
    可能收敛到局部最小值, 在大规模数据集上收敛较慢
    对于异常点、离群点敏感

使用数据类型 : 数值型数据


k-means聚类算法实现

案例描述

我们假设这样的一个案例需求:某公司发布一批新型手机,根据客户热衷度进行投放。公司市场人员收集其中四个地区用户对手机的满意程度(由两个特征决定的)。分析哪个区域对手机产品比较热衷,对应的进行市场销售工作。这里就用到k-means聚类算法。

从文件加载数据集

上文中我们收集好四个地区用户对产品满意的特征数据值,转化为向量预先保存到文本中(关于词向量转化及其词袋模型问题,参考:决策树算法模型研究与案例分析一文)。我们加载文件并以数据矩阵形式返回数据集,代码实现如下:

'''加载数据集'''
def loadDataSet(fileName):
    dataSet = [] # 初始化一个空列表
    fr = open(fileName)
    for line in fr.readlines():
        # 切割每一行的数据
        curLine = line.strip().split('\t')
        # 将数据追加到dataMat,映射所有的元素为 float类型
        fltLine = list(map(float,curLine))    
        dataSet.append(fltLine)
    return mat(dataSet)

我们打印看下结果:

计算两个向量的欧氏距离

上文在k均值算法思想和工作流程都提到过,我们一个重要的方法就是随机设置质心,然后比较每条数据(可以理解为单一客户的特征数据)与质心之间的距离。这里距离公式包括很多,本文采用的是欧式距离计算,其代码实现如下:

'''欧氏距离计算函数'''
def distEclud(vecA, vecB):
    return sqrt(sum(power(vecA - vecB, 2)))

构建一个包含 K 个随机质心的集合

接下来,我们构建随机质心(中心点),这里的K值是经过数据观察随机设置的值,假如k=3,代表我们将数据集分为3个簇,也就是说分为3个部分。我们随机质心在整个数据集的边界之内,这可以通过找到数据集每一维的最小和最大值来完成,然后生成0到1.0之间的随机数并通过取值范围和最小值,以便确保随机点在数据的边界之内

'''
随机质心
'''
def randCent(dataMat, k):

    # 获取样本数与特征值
    m, n = shape(dataMat)
    # 初始化质心,创建(k,n)个以零填充的矩阵
    centroids = mat(zeros((k, n)))
    # 循环遍历特征值
    for j in range(n):
        # 计算每一列的最小值
        minJ = min(dataMat[:, j])
        # 计算每一列的范围值
        rangeJ = float(max(dataMat[:, j]) - minJ)
        # 计算每一列的质心,并将值赋给centroids
        centroids[:, j] = mat(minJ + rangeJ * random.rand(k, 1))
    # 返回质心
    return centroids 

我们测试下k=3的随机质心结果:

K-Means 聚类算法

我们基于以上算法构建k均值算法,该算法会创建k个质心,然后将每个点分配到最近的质心,再重新计算质心。这个过程重复数次,直到数据点的簇分配结果不再改变位置。返回类质心与点分配结果(多次运行结果可能会不一样,可以试试,原因为随机质心的影响,但总的结果是对的,因为数据足够相似,也可能会陷入局部最小值),代码实现如下:

'''
创建K个质心,然后将每个点分配到最近的质心,再重新计算质心。
这个过程重复数次,直到数据点的簇分配结果不再改变为止
'''
def kMeans(dataMat, k, distMeas=distEclud, createCent=randCent):
    # 获取样本数和特征数
    m, n = shape(dataMat)
    # 初始化一个矩阵来存储每个点的簇分配结果
    # clusterAssment包含两个列:一列记录簇索引值,第二列存储误差(误差是指当前点到簇质心的距离,后面会使用该误差来评价聚类的效果)
    clusterAssment = mat(zeros((m, 2)))
    # 创建质心,随机K个质心
    centroids = createCent(dataMat, k)
    # 初始化标志变量,用于判断迭代是否继续,如果True,则继续迭代
    clusterChanged = True
    while clusterChanged:
        clusterChanged = False
        # 遍历所有数据找到距离每个点最近的质心,
        # 可以通过对每个点遍历所有质心并计算点到每个质心的距离来完成
        for i in range(m):
            minDist = inf # 正无穷
            minIndex = -1
            for j in range(k):
                # 计算数据点到质心的距离
                # 计算距离是使用distMeas参数给出的距离公式,默认距离函数是distEclud
                distJI = distMeas(centroids[j, :], dataMat[i, :])
                # 如果距离比minDist(最小距离)还小,更新minDist(最小距离)和最小质心的index(索引)
                if distJI < minDist:
                    minDist = distJI
                    minIndex = j
            # 如果任一点的簇分配结果发生改变,则更新clusterChanged标志
            if clusterAssment[i, 0] != minIndex:
                # print(clusterAssment[i, 0],minIndex)
                clusterChanged = True
            # 更新簇分配结果为最小质心的index(索引),minDist(最小距离)的平方
            clusterAssment[i, :] = minIndex, minDist ** 2
        # print(centroids)
        # 遍历所有质心并更新它们的取值
        for cent in range(k):
            # 通过数据过滤来获得给定簇的所有点
            ptsInClust = dataMat[nonzero(clusterAssment[:, 0].A == cent)[0]]
            # 计算所有点的均值,axis=0表示沿矩阵的列方向进行均值计算
            centroids[cent, :] = mean(ptsInClust, axis=0)# axis=0列方向
    # 返回所有的类质心与点分配结果
    return centroids, clusterAssment

测试查看下运行结果:

分析数据:聚类可视化

通过上文返回的数据结果,似乎我们还不能直观感受,接下来我们采用可视化分析方法直观感受下,代码实现如下:

'''
可视化展示
'''
def kmeanShow(dataMat,centers,clusterAssment):
    plt.scatter(np.array(dataMat)[:, 0], np.array(dataMat)[:, 1], c=np.array(clusterAssment)[:, 0].T)
    plt.scatter(centers[:, 0].tolist(), centers[:, 1].tolist(), c="r")
    plt.show()

测试查看可视化结果:

结果讨论与分析

局部最小值(局部最优的结果,但不是全局最优的结果)

上文可视化结果显示,其中两个簇聚集在一起,也就说说没有达到我们预期的效果。出现这个问题有很多原因,可能是k值取的不合适,可能是距离函数不合适,可能是最初随机选取的质心靠的太近,也可能是数据本身分布的问题。

为了解决这个问题,我们可以对生成的簇进行后处理,一种方法是将具有最大SSE值的簇划分成两个簇。具体实现时可以将最大簇包含的点过滤出来并在这些点上运行K-均值算法,令k设为2。

为了保持簇总数不变,可以将某两个簇进行合并。从上图中很明显就可以看出,应该将上图下部两个出错的簇质心进行合并。那么问题来了,我们可以很容易对二维数据上的聚类进行可视化, 但是如果遇到40维的数据应该如何去做?

有两种可以量化的办法:合并最近的质心,或者合并两个使得SSE增幅最小的质心。 第一种思路通过计算所有质心之间的距离, 然后合并距离最近的两个点来实现。第二种方法需要合并两个簇然后计算总SSE值。必须在所有可能的两个簇上重复上述处理过程,直到找到合并最佳的两个簇为止。

因为上述后处理过程实在是有些繁琐,所以有更厉害的大佬提出了另一个称之为二分K-均值(bisecting K-Means)的算法.

二分 K-Means 聚类算法

算法描述

该算法首先将所有点作为一个簇,然后将该簇一分为二。之后选择其中一个簇继续进行划分,选择哪一个簇进行划分取决于对其划分时候可以最大程度降低 SSE(平方和误差)的值。上述基于 SSE 的划分过程不断重复,直到得到用户指定的簇数目为止。

二分 K-Means 聚类算法伪代码

将所有点看成一个簇
当簇数目小于 k 时
对于每一个簇
    计算总误差
    在给定的簇上面进行 KMeans 聚类(k=2)
    计算将该簇一分为二之后的总误差
选择使得误差最小的那个簇进行划分操作

另一种做法是选择 SSE 最大的簇进行划分,直到簇数目达到用户指定的数目位置。

二分 K-Means 聚类算法代码

根据算法思想,我们基于k均值算法做了少许的改动,代码实现如下:

'''在给定数据集,所期望的簇数目和距离计算方法的条件下,函数返回聚类结果'''
def biKmeans(dataMat, k, distMeas=distEclud):
    m, n = shape(dataMat)
    # 创建一个矩阵来存储数据集中每个点的簇分配结果及平方误差
    clusterAssment = mat(zeros((m, 2)))
    # 计算整个数据集的质心,并使用一个列表来保留所有的质心
    centroid0 = mean(dataMat, axis=0).tolist()[0]
    centList = [centroid0] # [-0.15772275000000002, 1.2253301166666664]
    # 遍历数据集中所有点来计算每个点到质心的误差值
    for j in range(m):
        clusterAssment[j, 1] = distMeas(mat(centroid0), dataMat[j, :]) ** 2
    # 对簇不停的进行划分,直到得到想要的簇数目为止
    while (len(centList) < k):
        # 初始化最小SSE为无穷大,用于比较划分前后的SSE
        lowestSSE = inf
        # 通过考察簇列表中的值来获得当前簇的数目,遍历所有的簇来决定最佳的簇进行划分
        for i in range(len(centList)):
            # 对每一个簇,将该簇中的所有点堪称一个小的数据集
            ptsInCurrCluster = dataMat[nonzero(clusterAssment[:, 0].A == i)[0], :]
            # 将ptsInCurrCluster输入到函数kMeans中进行处理,k=2,
            # kMeans会生成两个质心(簇),同时给出每个簇的误差值
            centroidMat, splitClustAss = kMeans(ptsInCurrCluster, 2, distMeas)
            # 将误差值与剩余数据集的误差之和作为本次划分的误差
            sseSplit = sum(splitClustAss[:, 1])
            sseNotSplit = sum(clusterAssment[nonzero(clusterAssment[:, 0].A != i)[0], 1])
            print('sseSplit, and notSplit: ', sseSplit, sseNotSplit)
            # 如果本次划分的SSE值最小,则本次划分被保存
            if (sseSplit + sseNotSplit) < lowestSSE:
                bestCentToSplit = i
                bestNewCents = centroidMat
                bestClustAss = splitClustAss.copy()
                lowestSSE = sseSplit + sseNotSplit
        # 找出最好的簇分配结果
        # 调用kmeans函数并且指定簇数为2时,会得到两个编号分别为0和1的结果簇
        bestClustAss[nonzero(bestClustAss[:, 0].A == 1)[0], 0] = len(centList)
        # 更新为最佳质心
        bestClustAss[nonzero(bestClustAss[:, 0].A == 0)[0], 0] = bestCentToSplit
        print('the bestCentToSplit is: ', bestCentToSplit)
        print('the len of bestClustAss is: ', len(bestClustAss))
        # 更新质心列表
        # 更新原质心list中的第i个质心为使用二分kMeans后bestNewCents的第一个质心
        centList[bestCentToSplit] = bestNewCents[0, :].tolist()[0]
        # 添加bestNewCents的第二个质心
        centList.append(bestNewCents[1, :].tolist()[0])
        # 重新分配最好簇下的数据(质心)以及SSE
        clusterAssment[nonzero(clusterAssment[:, 0].A == bestCentToSplit)[0], :] = bestClustAss
    return mat(centList), clusterAssment

测试二分 KMeans 聚类算法

经过改进后,我们运行biKmeans函数得到可视化结果如下:

总结:如此我们得到预想的结果,解决了局部最优的问题,聚类会收敛到全局最小值。而原始的 kMeans() 函数偶尔会陷入局部最小值。

调用机器学习库sklearn实现k-means 聚类

加载数据集

# 加载数据集
dataMat = []
fr = open("./testSet2.txt") # 注意,这个是相对路径
for line in fr.readlines():
    curLine = line.strip().split('\t')
    fltLine = list(map(float,curLine))    # 映射所有的元素为 float(浮点数)类型
    dataMat.append(fltLine)

训练k-means算法模型

km = KMeans(n_clusters=3) # 初始化
km.fit(dataMat) # 拟合
km_pred = km.predict(dataMat) # 预测
centers = km.cluster_centers_ # 质心

可视化结果

plt.scatter(np.array(dataMat)[:, 1], np.array(dataMat)[:, 0], c=km_pred)
plt.scatter(centers[:, 1], centers[:, 0], c="r")
plt.show()

聚类结果

参考文献

  1. scikit中文社区:http://sklearn.apachecn.org/cn/0.19.0/
  2. 中文维基百科:https://zh.wikipedia.org/wiki/K-%E5%B9%B3%E5%9D%87%E7%AE%97%E6%B3%95
  3. GitHub:https://github.com/BaiNingchao/MachineLearning-1
  4. 图书:《机器学习实战》
  5. 图书:《自然语言处理理论与实战》

完整代码下载

源码请进【机器学习和自然语言QQ群:436303759】文件下载:

作者声明

本文版权归作者白宁超所有,本文原创,旨在学术和科研使用。文章同步如下:

逻辑回归模型算法研究与案例分析

逻辑回归模型算法研究与案例分析

(白宁超? 2018年9月11日11:37:17)

导读:逻辑回归(Logistic regression)即逻辑模型,属于常见的一种分类算法。本文将从理论介绍开始,搞清楚什么是逻辑回归、回归系数、算法思想、工作原理及其优缺点等。进一步通过两个实际案例深化理解逻辑回归,以及在工程应用进行实现。(本文原创,转载必须注明出处:?决策树模型算法研究与案例分析)

理论介绍

逻辑回归和Sigmoid 函数

逻辑回归

回归:假设现在有一些数据点,我们用一条直线对这些点进行拟合(这条直线称为最佳拟合直线),这个拟合的过程就叫做回归。

逻辑回归(Logistic Regression)是一种用于解决二分类(0 or 1)问题的机器学习方法,用于估计某种事物的可能性。比如某用户购买某商品的可能性,某病人患有某种疾病的可能性,以及某广告被用户点击的可能性等。 注意,这里用的是“可能性”,而非数学上的“概率”,logisitc回归的结果并非数学定义中的概率值,不可以直接当做概率值来用。该结果往往用于和其他特征值加权求和,而非直接相乘。

Sigmoid 函数

Sigmoid函数是一个常见的S型数学函数,在信息科学中,由于其单增以及反函数单增等性质,Sigmoid函数常被用作神经网络的阈值函数,将变量映射到0,1之间。在逻辑回归、人工神经网络中有着广泛的应用。Sigmoid函数的数学形式是:

对x求导可以推出如下结论:

下图给出了 Sigmoid 函数在不同坐标尺度下的两条曲线图。当 x 为 0 时,Sigmoid 函数值为 0.5 。随着 x 的增大,对应的 Sigmoid 值将逼近于 1 ; 而随着 x 的减小, Sigmoid 值将逼近于 0 。如果横坐标刻度足够大, Sigmoid 函数看起来很像一个阶跃函数。

因此,为了实现 Logistic 回归分类器,我们可以在每个特征上都乘以一个回归系数,然后把所有结果值相加,将这个总和代入 Sigmoid 函数中,进而得到一个范围在 0~1 之间的数值。任何大于 0.5 的数据被分入 1 类,小于 0.5 即被归入 0 类。所以,Logistic 回归也是一种概率估计,比如这里Sigmoid 函数得出的值为0.5,可以理解为给定数据和参数,数据被分入 1 类的概率为0.5。(注意:针对二分类问题,0.5不是唯一确定分类的值,你可以根据需求调整这个概率值。)

逻辑回归与线性回归的关系

逻辑回归(Logistic Regression)与线性回归(Linear Regression)都是一种广义线性模型(generalized linear model)。逻辑回归假设因变量 y 服从伯努利分布,而线性回归假设因变量 y 服从高斯分布。 因此与线性回归有很多相同之处,去除Sigmoid映射函数的话,逻辑回归算法就是一个线性回归。可以说,逻辑回归是以线性回归为理论支持的,但是逻辑回归通过Sigmoid函数引入了非线性因素,因此可以轻松处理0/1分类问题。

最优化方法的回归系数

Sigmoid 函数的输入记为 z ,由下面公式得到:

如果采用向量的写法,上述公式可以写成 Sigmoid 函数计算公式向量形式?,它表示将这两个数值向量对应元素相乘然后全部加起来即得到 z 值。其中的向量 x 是分类器的输入数据,向量 w 也就是我们要找到的最佳参数(系数),从而使得分类器尽可能地精确。为了寻找该最佳参数,需要用到最优化理论的一些知识。我们这里使用的是——梯度上升法(Gradient Ascent)。

梯度上升与梯度下降

梯度

对于梯度这个词一些人比较陌生,我们先看看维基百科的解释:在向量微积分中,标量场(向量场)中某一点的梯度指向在这点标量场增长最快的方向(当然要比较的话必须固定方向的长度),梯度的绝对值是长度为1的方向中函数最大的增加率,也就是说?代表方向导数。

  • 在单变量的实值函数的情况,梯度只是导数,或者,对于一个线性函数,也就是线的斜率。
  • 梯度一词有时用于斜度,也就是一个曲面沿着给定方向的倾斜程度。可以通过取向量梯度和所研究的方向的内积来得到斜度。梯度的数值有时也被称为梯度。(更多梯度相关知识参照维基百科词条

梯度形式化描述

考虑一座高度在 (x, y)点是 H(x, y)的山。H这一点的梯度是在该点坡度(或者说斜度)最陡的方向。梯度的大小告诉我们坡度到底有多陡。这个现象可以如下数学的表示。山的高度函数 H的梯度点积一个单位向量给出了表面在该向量的方向上的斜率。这称为方向导数。

理解梯度

为了大家更容易理解什么是梯度,我们介意向量的概念,向量是一个矢量具有大小和方向的。同样,梯度也可以类比为具备大小和方向的这么一个概念。其两者比较如下:(这里严格意义上讲是不成立的,便于大家理解。)

向量 = 值 + 方向  
梯度 = 向量
梯度 = 梯度值 + 梯度方向

梯度上升

要找到某函数的最大值,最好的方法是沿着该函数的梯度方向探寻。如果梯度记为 ▽ ,则函数 f(x, y) 的梯度由下式表示:

这个梯度意味着要沿 x 的方向移动。其中,函数f(x, y) 必须要在待计算的点上有定义并且可微。下图是一个具体的例子。

上图展示的,梯度上升算法到达每个点后都会重新估计移动的方向。从 P0 开始,计算完该点的梯度,函数就根据梯度移动到下一点 P1。在 P1 点,梯度再次被重新计算,并沿着新的梯度方向移动到 P2 。如此循环迭代,直到满足停止条件。迭代过程中,梯度算子总是保证我们能选取到最佳的移动方向。

上图中的梯度上升算法沿梯度方向移动了一步。可以看到,梯度算子总是指向函数值增长最快的方向。这里所说的是移动方向,而未提到移动量的大小。该量值称为步长,记作 α 。用向量来表示的话,梯度上升算法的迭代公式如下:

例如:y = w0 + w1x1 + w2x2 + ... + wnxn
梯度:参考上图的例子,二维图像,x方向代表第一个系数,也就是 w1,y方向代表第二个系数也就是 w2,这样的向量就是梯度。
α:上面的梯度算法的迭代公式中的阿尔法,这个代表的是移动步长(step length)。移动步长会影响最终结果的拟合程度,最好的方法就是随着迭代次数更改移动步长。步长通俗的理解,100米,如果我一步走10米,我需要走10步;如果一步走20米,我只需要走5步。这里的一步走多少米就是步长的意思。
▽f(w):代表沿着梯度变化的方向,也可以理解该方向求导。

该公式将一直被迭代执行,直至达到某个停止条件为止,比如迭代次数达到某个指定值或者算法达到某个可以允许的误差范围。

梯度上升与梯度下降的区别

梯度下降是大家听的最多的,本质上梯度下降与梯度上升算法是一样的,只是公司中加法变减法,梯度下降的公式如下:

在求极值的问题中,有梯度上升和梯度下降两个最优化方法。梯度上升用于求最大值,梯度下降用于求最小值。如logistic回归的目标函数:代表的是概率,我们想求概率最大值,即对数似然函数的最大值,所以使用梯度上升算法。而线性回归的代价函数:代表的则是误差,我们想求误差最小值,所以用梯度下降算法。

逻辑回归分类核心思想

根据现有数据对分类边界建立回归公司,以此进行分类。回归即最佳拟合。

逻辑回归工作原理

每个回归系数初始化为 1
重复 R 次:
    计算整个数据集的梯度
    使用 步长 x 梯度 更新回归系数的向量
返回回归系数

逻辑回归算法流程

收集数据: 采用任意方法收集数据
准备数据: 由于需要进行距离计算,因此要求数据类型为数值型。另外,结构化数据格式则最佳。
分析数据: 采用任意方法对数据进行分析。
训练算法: 大部分时间将用于训练,训练的目的是为了找到最佳的分类回归系数。
测试算法: 一旦训练步骤完成,分类将会很快。
使用算法: 首先,我们需要输入一些数据,并将其转换成对应的结构化数值;接着,基于训练好的回归系数就可以对这些数值进行简单的回归计算,判定它们属于哪个类别;

逻辑回归优缺点

优点: 计算代价不高,易于理解和实现。
缺点: 容易欠拟合,分类精度可能不高。
适用数据类型: 数值型和标称型数据。  

案例分析1:Logistic回归在简单数据集上的分类

案例描述

在一个简单的数据集上,采用梯度上升法找到 Logistic 回归分类器在此数据集上的最佳回归系数

开发流程

收集数据: 可以使用任何方法
准备数据: 由于需要进行距离计算,因此要求数据类型为数值型。另外,结构化数据格式则最佳
分析数据: 画出决策边界
训练算法: 使用梯度上升找到最佳参数
测试算法: 使用 Logistic 回归进行分类
使用算法: 对简单数据集中数据进行分类

数据采集

本文采用100行的测试集文本。其中前两列是特征1,和特征2,第三类是对应的标签。(这里特征1,特征2作为测试使用没有实际意义,你可以理解为特征1 是水里游的,特征2是有鱼鳞。类别判断是否为鱼类。)

读取文本文件,加载数据集和类标签,这里将特征集第一列加1,便于后续回归系数的计算:

'''加载数据集和类标签'''
def loadDataSet(file_name):
    # dataMat为原始数据, labelMat为原始数据的标签
    dataMat,labelMat = [],[]
    fr = open(file_name)
    for line in fr.readlines():
        lineArr = line.strip().split(',')
        if len(lineArr) == 1:
            continue    # 这里如果就一个空的元素,则跳过本次循环
        # 为了方便计算,我们将每一行的开头添加一个 1.0 作为 X0
        dataMat.append([1.0, float(lineArr[0]), float(lineArr[1])])
        labelMat.append(int(lineArr[2]))
    return dataMat, labelMat

梯度上升训练算法模型

梯度上升算法

使用梯度上升训练算法模型,其代码实现如下:

''' 正常的梯度上升法,得到的最佳回归系数 '''
def gradAscent(dataMatIn, classLabels):
    dataMatrix = mat(dataMatIn)  # 转换为 NumPy 矩阵
    # 转化为矩阵[[0,1,0,1,0,1.....]],并转制[[0],[1],[0].....]
    # transpose() 行列转置函数
    # 将行向量转化为列向量   =>  矩阵的转置
    labelMat = mat(classLabels).transpose()  # 首先将数组转换为 NumPy 矩阵,然后再将行向量转置为列向量
    # m->数据量,样本数 n->特征数
    m, n = shape(dataMatrix) # 矩阵的行数和列数
    # print(m,n)
    alpha = 0.001  # alpha代表向目标移动的步长
    maxCycles = 500 # 迭代次数
    weights = ones((n, 1)) # 代表回归系数,ones((n,1)) 长度和特征数相同矩阵全是1
    for k in range(maxCycles):
        h = sigmoid(dataMatrix * weights)  # 矩阵乘法
        # labelMat是实际值
        error = (labelMat - h)  # 向量相减
        # 0.001* (3*m)*(m*1) 表示在每一个列上的一个误差情况,最后得出 x1,x2,xn的系数的偏移量
        weights = weights + alpha * dataMatrix.transpose() * error  # 矩阵乘法,最后得到回归系数
    return array(weights)

其中sigmoid函数实现如下:

''' sigmoid跳跃函数 '''
def sigmoid(ZVar):
    return 1.0 / (1 + exp(-ZVar))

代码分析:函数的两个参数是数据加载返回的特征集和标签类集合。对数据集进行mat矩阵话转化,而类标签集进行矩阵之后转置,便于行列式的计算。然后设定步长,和迭代次数。整个特征矩阵与回归系数乘积求sigmoid值,最后返回回归系数的值。运行结果如下:

[[ 4.12414349]
 [ 0.48007329]
 [-0.6168482 ]]

思考?步长和迭代次数的初始值如何设定?

随机梯度上升算法

梯度上升算法在每次更新回归系数时都需要遍历整个数据集,该方法在处理 100 个左右的数据集时尚可,但如果有数十亿样本和成千上万的特征,那么该方法的计算复杂度就太高了。一种改进方法是一次仅用一个样本点来更新回归系数,该方法称为 随机梯度上升算法。由于可以在新样本到来时对分类器进行增量式更新,因而随机梯度上升算法是一个在线学习(online learning)算法。与 “在线学习” 相对应,一次处理所有数据被称作是 “批处理” (batch) 。其伪代码是:

所有回归系数初始化为 1
对数据集中每个样本
    计算该样本的梯度
    使用 alpha x gradient 更新回归系数值
返回回归系数值

随机梯度上升算法的代码实现如下:

''' 随机梯度上升'''
# 梯度上升与随机梯度上升的区别?梯度下降在每次更新数据集时都需要遍历整个数据集,计算复杂都较高;随机梯度下降一次只用一个样本点来更新回归系数
def stocGradAscent0(dataMatrix, classLabels):
    m, n = shape(dataMatrix)
    alpha = 0.01
    weights = ones(n)  # 初始化长度为n的数组,元素全部为 1
    for i in range(m):
        # sum(dataMatrix[i]*weights)为了求 f(x)的值,f(x)=a1*x1+b2*x2+..+nn*xn
        h = sigmoid(sum(dataMatrix[i] * weights))
        # 计算真实类别与预测类别之间的差值,然后按照该差值调整回归系数
        error = classLabels[i] - h
        # 0.01*(1*1)*(1*n)
        weights = array(weights) + alpha * error * array(mat(dataMatrix[i]))
    return array(weights.transpose())

可以看到,随机梯度上升算法与梯度上升算法在代码上很相似,但也有一些区别: 第一,后者的变量 h 和误差 error 都是向量,而前者则全是数值;第二,前者没有矩阵的转换过程,所有变量的数据类型都是 NumPy 数组。

判断优化算法优劣的可靠方法是看它是否收敛,也就是说参数是否达到了稳定值,是否还会不断地变化?下图展示了随机梯度上升算法在 200 次迭代过程中回归系数的变化情况。其中的系数2,也就是 X2 只经过了 50 次迭代就达到了稳定值,但系数 1 和 0 则需要更多次的迭代。如下图所示:

针对波动问题,我们改进了之前的随机梯度上升算法,具体代码实现如下:

''' 改进版的随机梯度上升,使用随机的一个样本来更新回归系数'''
def stocGradAscent1(dataMatrix, classLabels, numIter=150):
    m, n = shape(dataMatrix)
    weights = ones(n)  # 创建与列数相同的矩阵的系数矩阵
    # 随机梯度, 循环150,观察是否收敛
    for j in range(numIter):
        dataIndex = list(range(m)) # [0, 1, 2 .. m-1]
        for i in range(m):
            # i和j的不断增大,导致alpha的值不断减少,但是不为0
            alpha = 4 / (1.0 + j + i) + 0.0001 # alpha随着迭代不断减小非0
            # random.uniform(x, y) 随机生成下一个实数,它在[x,y]范围内
            Index = int(random.uniform(0, len(dataIndex)))
            # sum(dataMatrix[i]*weights)为了求 f(x)的值, f(x)=a1*x1+b2*x2+..+nn*xn
            h = sigmoid(sum(dataMatrix[dataIndex[Index]] * weights))
            error = classLabels[dataIndex[Index]] - h
            weights = weights + alpha * error *array(mat(dataMatrix[dataIndex[Index]]))
            del (dataIndex[Index])
    # print(weights.transpose())
    return weights.transpose()

上面的改进版随机梯度上升算法改了两处代码。

  • 改进为 alpha 的值。alpha 在每次迭代的时候都会调整,这回缓解上面波动图的数据波动或者高频波动。另外,虽然 alpha 会随着迭代次数不断减少,但永远不会减小到 0,因为我们在计算公式中添加了一个常数项。
  • 修改为 randIndex 更新,这里通过随机选取样本拉来更新回归系数。这种方法将减少周期性的波动。这种方法每次随机从列表中选出一个值,然后从列表中删掉该值(再进行下一次迭代)。

分析数据:画出决策边界

边界可视化的代码实现如下:

''' 数据可视化展示 '''
def plotBestFit(dataArr, labelMat, weights):
    n = shape(dataArr)[0]
    xcord1,xcord2,ycord1,ycord2 = [],[],[],[]
    for i in range(n):
        if int(labelMat[i]) == 1:
            xcord1.append(dataArr[i, 1])
            ycord1.append(dataArr[i, 2])
        else:
            xcord2.append(dataArr[i, 1])
            ycord2.append(dataArr[i, 2])

    fig = plt.figure()
    ax = fig.add_subplot(111)
    ax.scatter(xcord1, ycord1, s=30, c='red', marker='s')
    ax.scatter(xcord2, ycord2, s=30, c='green')
    x = arange(-3.0, 3.0, 0.1)
    """
    dataMat.append([1.0, float(lineArr[0]), float(lineArr[1])])
    w0*x0+w1*x1+w2*x2=f(x)
    x0最开始就设置为1, x2就是我们画图的y值,而f(x)被我们磨合误差给算到w0,w1,w2身上去了
    所以: w0+w1*x+w2*y=0 => y = (-w0-w1*x)/w2
    """
    y = (-weights[0] - weights[1] * x) / weights[2]
    ax.plot(x, y)
    plt.xlabel('X')
    plt.ylabel('Y')
    plt.show()

运行结果分别是:

梯度上升算法可视化结果图1-1:

图1-1 梯度上升算法分类随机梯度上升算法可视化结果:


图1-2 随机梯度上升算法分类优化随机梯度上升算法可视化结果:


图1-3 优化随机梯度上升算法分类结果分析:

图1-1的梯度上升算法在每次更新回归系数时都需要遍历整个数据集,虽然分类结果还不错该方法的计算复杂度就太高了。图1-2的随机梯度上升算法虽然分类效果不是很好(分类1/3左右),但是其迭代次数远远小于图1-1迭代次数(500次)。整体性能有所改进,但是其存在局部波动现象。基于此改进后的图1-3效果显示好很多。

测试算法: 使用Logistic回归进行分类

代码实现如下:

'''数据集决策可视化'''
def simpleTest(file_name):
    # 1.收集并准备数据
    dataMat, labelMat = loadDataSet(file_name)
    # 2.训练模型,  f(x)=a1*x1+b2*x2+..+nn*xn中 (a1,b2, .., nn).T的矩阵值
    dataArr = array(dataMat)
    weights = stocGradAscent1(dataArr, labelMat)
    # 数据可视化
    plotBestFit(dataArr, labelMat, weights)

案例分析2:从病毒性流感预测病人的死亡情况

案例描述

使用 Logistic 回归来预测病毒性流感预测病人的死亡问题。这个数据集中包含了医院检测病毒性流感的一些指标,有的指标比较主观,有的指标难以测量,例如人的疼痛级别。

开发流程
收集数据: 给定数据文件
准备数据: 用 Python 解析文本文件并填充缺失值
分析数据: 可视化并观察数据
训练算法: 使用优化算法,找到最佳的系数
测试算法: 为了量化回归的效果,需要观察错误率。根据错误率决定是否回退到训练阶段,
通过改变迭代的次数和步长的参数来得到更好的回归系数
使用算法: 实现一个简单的命令行程序来收集马的症状并输出预测结果并非难事,
这可以作为留给大家的一道习题

收集数据: 给定数据文件

训练数据已经给出,这里对文件处理即可,代码如下:

'''加载数据集和类标签2'''
def loadDataSet2(file_name):
    frTrain = open(file_name)
    trainingSet,trainingLabels = [],[]
    for line in frTrain.readlines():
        currLine = line.strip().split(',')
        # print(len(currLine))
        lineArr = []
        for i in range(len(currLine)-1):
            lineArr.append(float(currLine[i]))
        trainingSet.append(lineArr)
        trainingLabels.append(float(currLine[len(currLine)-1]))
    return trainingSet,trainingLabels

准备数据: 用 Python 解析文本文件并填充缺失值

处理数据中的缺失值

假设有100个样本和20个特征,这些数据都是机器收集回来的。若机器上的某个传感器损坏导致一个特征无效时该怎么办?此时是否要扔掉整个数据?这种情况下,另外19个特征怎么办? 它们是否还可以用?答案是肯定的。因为有时候数据相当昂贵,扔掉和重新获取都是不可取的,所以必须采用一些方法来解决这个问题。下面给出了一些可选的做法:

  • 使用可用特征的均值来填补缺失值;
  • 使用特殊值来填补缺失值,如 -1;
  • 忽略有缺失值的样本;
  • 使用有相似样本的均值添补缺失值;
  • 使用另外的机器学习算法预测缺失值。

现在,我们对下一节要用的数据集进行预处理,使其可以顺利地使用分类算法。在预处理需要做两件事:所有的缺失值必须用一个实数值来替换,因为我们使用的 NumPy 数据类型不允许包含缺失值。我们这里选择实数 0 来替换所有缺失值,恰好能适用于 Logistic 回归。这样做的直觉在于,我们需要的是一个在更新时不会影响系数的值。回归系数的更新公式如下:
weights = weights + alpha * error * dataMatrix[dataIndex[randIndex]]
如果 dataMatrix 的某个特征对应值为 0,那么该特征的系数将不做更新,即:weights = weights
另外,由于 Sigmoid(0) = 0.5 ,即它对结果的预测不具有任何倾向性,因此我们上述做法也不会对误差造成任何影响。基于上述原因,将缺失值用 0 代替既可以保留现有数据,也不需要对优化算法进行修改。此外,该数据集中的特征取值一般不为 0,因此在某种意义上说它也满足 “特殊值” 这个要求。
如果在测试数据集中发现了一条数据的类别标签已经缺失,那么我们的简单做法是将该条数据丢弃。这是因为类别标签与特征不同,很难确定采用某个合适的值来替换。采用 Logistic 回归进行分类时这种做法是合理的,而如果采用类似 kNN 的方法,则保留该条数据显得更加合理。

训练算法: 使用优化算法,找到最佳的系数

训练算法模型代码如下:

'''测试Logistic算法分类'''
def testClassier():
    # 使用改进后的随机梯度上升算法 求得在此数据集上的最佳回归系数 trainWeights
    file_name = './HorseColicTraining.txt'
    trainingSet,trainingLabels = loadDataSet2(file_name)
    trainWeights = stocGradAscent1(array(trainingSet), trainingLabels, 500)
    # 根据特征向量预测结果
    teststr = '2.000000,1.000000,38.300000,40.000000,24.000000,1.000000,1.000000,3.000000,1.000000,3.000000,3.000000,1.000000,0.000000,0.000000,0.000000,1.000000,1.000000,33.000000,6.700000,0.000000,0.000000'
    currLine = teststr.strip().split(',')
    lineArr = []
    for i in range(len(currLine)):
        lineArr.append(float(currLine[i]))
    res = classifyVector(array(lineArr), trainWeights)
    # 打印预测结果
    reslut = ['死亡','存活']
    print('预测结果是:',int(res))

分类函数代码如下:

'''分类函数,根据回归系数和特征向量来计算 Sigmoid的值,大于0.5函数返回1,否则返回0'''
def classifyVector(featuresV, weights):
    prob = sigmoid(sum(featuresV * weights))
    print(prob)
    if prob > 0.9: return 1.0
    else: return 0.0

测试算法:使用决策树执行分类

为了量化回归的效果,需要观察错误率。根据错误率决定是否回退到训练阶段,通过改变迭代的次数和步长的参数来得到更好的回归系数

'''打开测试集和训练集,并对数据进行格式化处理'''
def colicTest():
    file_name = './HorseColicTraining.txt'
    trainingSet,trainingLabels = loadDataSet2(file_name)
    # 使用改进后的随机梯度上升算法 求得在此数据集上的最佳回归系数 trainWeights
    trainWeights = stocGradAscent1(array(trainingSet), trainingLabels, 500)
    frTest = open('./HorseColicTest.txt')
    errorCount = 0 ; numTestVec = 0.0
    # 读取 测试数据集 进行测试,计算分类错误的样本条数和最终的错误率
    for line in frTest.readlines():
        numTestVec += 1.0
        currLine = line.strip().split(',')
        lineArr = []
        for i in range(21):
            lineArr.append(float(currLine[i]))
        if int(classifyVector(array(lineArr), trainWeights)) != int(
                currLine[21]):
            errorCount += 1
    errorRate = (float(errorCount) / numTestVec)
    print("逻辑回归算法测试集的错误率为: %f" % errorRate)
    return errorRate


# 调用 colicTest() 10次并求结果的平均值
def multiTest():
    numTests = 10;errorSum = 0.0
    for k in range(numTests):
        errorSum += colicTest()
    print("迭代 %d 次后的平均错误率是: %f" % (numTests, errorSum / float(numTests)))

其运行结果如下:

逻辑回归算法测试集的错误率为: 0.298507

参考文献

  1. scikit中文社区:http://sklearn.apachecn.org/cn/0.19.0/
  2. 中文维基百科:https://zh.wikipedia.org/wiki/%E9%82%8F%E8%BC%AF%E8%BF%B4%E6%AD%B8
  3. GitHub:https://github.com/BaiNingchao/MachineLearning-1
  4. 图书:《机器学习实战》
  5. 图书:《自然语言处理理论与实战》

完整代码下载

源码请进【机器学习和自然语言QQ群:436303759】文件下载:

作者声明

本文版权归作者白宁超所有,本文原创,旨在学术和科研使用。文章同步如下:

深度 | 朴素贝叶斯模型算法研究与实例分析

深度 | 朴素贝叶斯模型算法研究与实例分析

(白宁超?2018年9月3日15: 56:20)

导读:朴素贝叶斯模型是机器学习常用的模型算法之一,其在文本分类方面简单易行,且取得不错的分类效果。所以很受欢迎,对于朴素贝叶斯的学习,本文首先介绍理论知识即朴素贝叶斯相关概念和公式推导,为了加深理解,采用一个维基百科上面性别分类例子进行形式化描述。然后通过编程实现朴素贝叶斯分类算法,并在屏蔽社区言论、垃圾邮件、个人广告中获取区域倾向等几个方面进行应用,包括创建数据集、数据预处理、词集模型和词袋模型、朴素贝叶斯模型训练和优化等。然后结合复旦大学新闻语料进行朴素贝叶斯的应用。最后,大家熟悉其原理和实现之后,采用机器学习sklearn包进行实现和优化。由于篇幅较长,采用理论理解、案例实现、sklearn优化三个部分进行学习。(本文原创,转载必须注明出处:朴素贝叶斯模型算法研究与实例分析)

复旦新闻语料:朴素贝叶斯中文文本分类

项目概述

本节介绍朴素贝叶斯分类算法模型在中文领域中的应用。我们对新闻语料进行多文本分类操作,本文选择艺术、文学、教育、哲学、历史五个类别的训练文本,然后采用新的测试语料进行分类预测。

收集数据

数据集是从复旦新闻语料库中抽取出来的,考虑学习使用,样本选择并不大。主要抽选艺术、文学、教育、哲学、历史五个类别各10篇文章。全部数据文档50篇。具体情况不同对收集数据要求不同,你也可以选择网络爬取,数据库导出等。这文档读取时候可能会遇到gbk,utf-8等格式共存的情况,这里建议采用BatUTF8Conv.exe点击下载)工具,进行utf-8格式批量转化。

准备数据

创建数据集代码如下:

'''创建数据集和类标签'''
def loadDataSet():
    docList = [];classList = [] # 文档列表、类别列表
    dirlist = ['C3-Art','C4-Literature','C5-Education','C6-Philosophy','C7-History']
    for j in range(5):
        for i in range(1, 11): # 总共10个文档
            # 切分,解析数据,并归类为 1 类别
            wordList = textParse(open('./fudan/%s/%d.txt' % (dirlist[j],i),encoding='UTF-8').read())
            docList.append(wordList)
            classList.append(j)
            # print(i,'\t','./fudan/%s/%d.txt' % (dirlist[j],i),'\t',j)
    return docList,classList

''' 利用jieba对文本进行分词,返回切词后的list '''
def textParse(str_doc):
    # 正则过滤掉特殊符号、标点、英文、数字等。
    import re
    r1 = '[a-zA-Z0-9’!"#$%&\'()*+,-./:;<=>?@,。?★、…【】《》?“”‘’![\$$^_`{|}~]+'
    str_doc=re.sub(r1, '', str_doc)

    # 创建停用词列表
    stwlist = set([line.strip() for line in open('./stopwords.txt', 'r', encoding='utf-8').readlines()])
    sent_list = str_doc.split('\n')
    # word_2dlist = [rm_tokens(jieba.cut(part), stwlist) for part in sent_list]  # 分词并去停用词
    word_2dlist = [rm_tokens([word+"/"+flag+" " for word, flag in pseg.cut(part) if flag in ['n','v','a','ns','nr','nt']], stwlist) for part in sent_list] # 带词性分词并去停用词
    word_list = list(itertools.chain(*word_2dlist)) # 合并列表
    return word_list



''' 去掉一些停用词、数字、特殊符号 '''
def rm_tokens(words, stwlist):
    words_list = list(words)
    for i in range(words_list.__len__())[::-1]:
        word = words_list[i]
        if word in stwlist:  # 去除停用词
            words_list.pop(i)
        elif len(word) == 1:  # 去除单个字符
            words_list.pop(i)
        elif word == " ":  # 去除空字符
            words_list.pop(i)
    return words_list

代码分析:loadDataSet()方法是遍历读取文件夹,并对每篇文档进行处理,最后返回全部文档集的列表和类标签。textParse()方法是对每篇文档字符串进行数据预处理,我们首选使用正则方法保留文本数据,然后进行带有词性的中文分词和词性选择,rm_tokens()是去掉一些停用词、数字、特殊符号。最终返回相对干净的数据集和标签集。

分析数据

前面两篇文章都介绍了,我们需要把文档进行向量化表示,首先构建全部文章的单词集合,实现代码如下:

'''获取所有文档单词的集合'''
def createVocabList(dataSet):
    vocabSet = set([])
    for document in dataSet:
        vocabSet = vocabSet | set(document)  # 操作符 | 用于求两个集合的并集
    # print(len(vocabSet),len(set(vocabSet)))
    return list(vocabSet)

基于文档模型的基础上,我们将特征向量转化为数据矩阵向量,这里使用的词袋模型,构造与实现方法如下:

'''文档词袋模型,创建矩阵数据'''
def bagOfWords2VecMN(vocabList, inputSet):
    returnVec = [0] * len(vocabList)
    for word in inputSet:
        if word in vocabList:
            returnVec[vocabList.index(word)] += 1
    return returnVec

对矩阵数据可以采用可视化分析方法或者结合NLTK进行数据分析,检查数据分布情况和特征向量构成情况及其特征选择作为参考。

训练算法

我们在前面两篇文章介绍了朴素贝叶斯模型训练方法,我们在该方法下稍微改动就得到如下实现:

'''朴素贝叶斯模型训练数据优化'''
def trainNB0(trainMatrix, trainCategory):
    numTrainDocs = len(trainMatrix) # 总文件数
    numWords = len(trainMatrix[0]) # 总单词数

    p1Num=p2Num=p3Num=p4Num=p5Num = ones(numWords) # 各类为1的矩阵
    p1Denom=p2Denom=p3Denom=p4Denom=p5Denom = 2.0 # 各类特征和
    num1=num2=num3=num4=num5 = 0 # 各类文档数目

    pNumlist=[p1Num,p2Num,p3Num,p4Num,p5Num]
    pDenomlist =[p1Denom,p2Denom,p3Denom,p4Denom,p5Denom]
    Numlist = [num1,num2,num3,num4,num5]

    for i in range(numTrainDocs): # 遍历每篇训练文档
        for j in range(5): # 遍历每个类别
            if trainCategory[i] == j: # 如果在类别下的文档
                pNumlist[j] += trainMatrix[i] # 增加词条计数值
                pDenomlist[j] += sum(trainMatrix[i]) # 增加该类下所有词条计数值
                Numlist[j] +=1 # 该类文档数目加1

    pVect,pi = [],[]
    for index in range(5):
        pVect.append(log(pNumlist[index] / pDenomlist[index]))
        pi.append(Numlist[index] / float(numTrainDocs))
    return pVect, pi

构建分类函数,其优化后的代码实现如下:

'''朴素贝叶斯分类函数,将乘法转换为加法'''
def classifyNB(vec2Classify, pVect,pi):
    # 计算公式  log(P(F1|C))+log(P(F2|C))+....+log(P(Fn|C))+log(P(C))
    bnpi = [] # 文档分类到各类的概率值列表
    for x in range(5):
        bnpi.append(sum(vec2Classify * pVect[x]) + log(pi[x]))
    # print([bnp for bnp in bnpi])
    # 分类集合
    reslist = ['Art','Literature','Education','Philosophy','History']
    # 根据最大概率,选择索引值
    index = [bnpi.index(res) for res in bnpi if res==max(bnpi)]
    return reslist[index[0]] # 返回分类值

测试算法

我们加载构建的数据集方法,然后创建单词集合,集合词袋模型进行特征向量化,构建训练模型和分类方法,最终我们从复旦新闻语料中选择一篇未加入训练集的教育类文档,进行开放测试,具体代码如下:

'''朴素贝叶斯新闻分类应用'''
def testingNB():
    # 1. 加载数据集
    dataSet,Classlabels = loadDataSet()
    # 2. 创建单词集合
    myVocabList = createVocabList(dataSet)

    # 3. 计算单词是否出现并创建数据矩阵
    trainMat = []
    for postinDoc in dataSet:
        trainMat.append(bagOfWords2VecMN(myVocabList, postinDoc))
    with open('./word-bag.txt','w') as f:
        for i in trainMat:
            f.write(str(i)+'\r\n')
    # 4. 训练数据
    pVect,pi= trainNB0(array(trainMat), array(Classlabels))
    # 5. 测试数据
    testEntry = textParse(open('./fudan/test/C5-1.txt',encoding='UTF-8').read())
    thisDoc = array(bagOfWords2VecMN(myVocabList, testEntry))
    print(testEntry[:10], '分类结果是: ', classifyNB(thisDoc, pVect,pi))

实现结果如下:

Building prefix dict from the default dictionary ...
Loading model from cache C:\Users\ADMINI~1\AppData\Local\Temp\jieba.cache
Loading model cost 0.892 seconds.
Prefix dict has been built succesfully.
['全国/n ', '举办/v ', '电影/n ', '新华社/nt ', '北京/ns ', '国家教委/nt ', '广播电影电视部/nt ', '文化部/n ', '联合/v ', '决定/v '] 分类结果是:  Literature
耗时:29.4882 s

结果分析:我们运行分类器得出结果易知,预测结果是文化类,且运行时间为29s。首先分析为什么预测错误,这里面主要是训练集样本比较少和特征选择的原因。运行时间是由于将特征矩阵存储本地后,后面直接读取文本,相当于加载缓存,大大缩短运行时间。但是这里还有值得优化的地方,比如每次运行都会加载训练模型,大大消耗时间,我们能不能训练模型加载一次,多次调用呢?当然是可以的,这个问题下文继续优化。我们重点关注下特征选择问题

特征选择问题讨论

  • 做文本分类的时候,遇到特征矩阵1.5w。在测试篇幅小的文章总是分类错误?这个时候如何做特征选择?是不是说去掉特征集中频率极高和极低的一部分,对结果有所提升?
    答:你说的这个情况是很普遍的现象,篇幅小的文章,特征小,所以模型更容易判断出错!去掉高频和低频通常是可以使得训练的模型泛化能力变强
  • 比如:艺术,文化,历史,教育。界限本来就不明显,比如测试数据“我爱艺术,艺术是我的全部”。结果会分类为文化。其实这个里面还有就是不同特征词的权重问题,采用tf-idf优化下应该会好一些?答:我个人觉得做文本特征提取,还是需要自己去分析文本本身内容的文字特点,你可以把每一类的文本的实体提取出来,然后统计一下每个词在每一类上的数量,看看数量分布,也许可以发现一些数据特点
  • 我就是按照这个思路做的,还有改进时候的停用词,其实可以分析特征文本,针对不同业务,使用自定义的停用词要比通用的好
    还有提前各类见最具表征性的词汇加权,凸显本类的权重是吧?
    答:比如,艺术类文章中,哪些词出现较多,哪些词出现少,再观察这些词的词性主要是哪些,这样可能会对你制定提取特征规则方式的时候提供一定的思路参考,我可以告诉你的是,有些词绝对会某一类文章出出现多,然后在其他类文章出现很少,这一类的词就是文章的特征词
  • 那样的思路可以是:对某类文章单独构建类内的词汇表再进行选择。最后对类间词汇表叠加就ok了。
    答:词汇表有个缺点就是,不能很好的适应新词
  • 改进思路呢
    答:我给你一个改进思路:你只提取每个文本中的名词、动词、形容词、地名,用这些词的作为文本的特征来训练试一试,用文本分类用主题模型(LDA)来向量化文本,再训练模型试一试。如果效果还是不够好,再将文本向量用PCA进行一次特征降维,然后再训练模型试一试,按常理来说,效果应该会有提高
  • 还有我之前个人写的程序分类效果不理想,后来改用sklearn内置BN运行依旧不理想。适当改进了特征提取,还是不理想。估计每类10篇文章的训练数据太少了
    答:文本本身特征提取就相对难一些,再加上训练数据少,训练出来的模型效果可想而已,正常的

sklearn:朴素贝叶斯分类调用

数据准备和数据预处理

加载文档数据集和分类集

数据准备和数据预处理上文已经介绍了,本节增加了一个全局变量存储词汇表,目的是写入到本地文本里,本地读取词汇集,避免每次都做特征向量时加载训练集,提高运行时间。

myVocabList = [] # 设置词汇表的全局变量

'''创建数据集和类标签'''
def loadDataSet():
    docList = [];classList = []  # 文档列表、类别列表、文本特征
    dirlist = ['C3-Art','C4-Literature','C5-Education','C6-Philosophy','C7-History']
    for j in range(5):
        for i in range(1, 11): # 总共10个文档
            # 切分,解析数据,并归类为 1 类别
            wordList = textParse(open('./fudan/%s/%d.txt' % (dirlist[j],i),encoding='UTF-8').read())
            docList.append(wordList)
            classList.append(j)
            # print(i,'\t','./fudan/%s/%d.txt' % (dirlist[j],i),'\t',j)
    # print(len(docList),len(classList),len(fullText))
    global myVocabList
    myVocabList = createVocabList(docList)  # 创建单词集合
    return docList,classList,myVocabList



''' 利用jieba对文本进行分词,返回切词后的list '''
def textParse(str_doc): #与上文方法一致


''' 去掉一些停用词、数字、特殊符号 '''
def rm_tokens(words, stwlist):  #与上文方法一致

文档数据集和分类集在本地读写操作

# 本地存储数据集和标签
def storedata():
    # 3. 计算单词是否出现并创建数据矩阵
    # trainMat =[[0,1,2,3],[2,3,1,5],[0,1,4,2]] # 训练集
    # classList = [0,1,2] #类标签
    docList,classList,myVocabList = loadDataSet()
    # 计算单词是否出现并创建数据矩阵
    trainMat = []
    for postinDoc in docList:
        trainMat.append(bagOfWords2VecMN(myVocabList, postinDoc))
    res = ""
    for i in range(len(trainMat)):
        res +=' '.join([str(x) for x in trainMat[i]])+' '+str(classList[i])+'\n'
    # print(res[:-1]) # 删除最后一个换行符
    with open('./word-bag.txt','w') as fw:
        fw.write(res[:-1])
    with open('./wordset.txt','w') as fw:
        fw.write(' '.join([str(v) for v in myVocabList]))


# 读取本地数据集和标签
    def grabdata():
        f = open('./word-bag.txt') # 读取本地文件
        arrayLines = f.readlines() # 行向量
        tzsize = len(arrayLines[0].split(' '))-1 # 列向量,特征个数减1即数据集
        returnMat = zeros((len(arrayLines),tzsize))    # 0矩阵数据集
        classLabelVactor = []                     # 标签集,特征最后一列

        index = 0
        for line in arrayLines: # 逐行读取
            listFromLine = line.strip().split(' ')    # 分析数据,空格处理
            # print(listFromLine)
            returnMat[index,:] = listFromLine[0:tzsize] # 数据集
            classLabelVactor.append(int(listFromLine[-1])) # 类别标签集
            index +=1
        # print(returnMat,classLabelVactor)
        myVocabList=writewordset()
        return returnMat,classLabelVactor,myVocabList

    def writewordset():
        f1 = open('./wordset.txt')
        myVocabList =f1.readline().split(' ')
        for w in myVocabList:
            if w=='':
                myVocabList.remove(w)
        return myVocabList

获取文档集合和构建词袋模型

'''获取所有文档单词的集合'''
def createVocabList(dataSet):
    vocabSet = set([])
    for document in dataSet:
        vocabSet = vocabSet | set(document)  # 操作符 | 用于求两个集合的并集
    # print(len(vocabSet),len(set(vocabSet)))
    return list(vocabSet)



'''文档词袋模型,创建矩阵数据'''
def bagOfWords2VecMN(vocabList, inputSet):
    returnVec = [0] * len(vocabList)
    for word in inputSet:
        if word in vocabList:
            returnVec[vocabList.index(word)] += 1
    return returnVec

高斯朴素贝叶斯

GaussianNB 实现了运用于分类的高斯朴素贝叶斯算法。特征的可能性(即概率)假设为高斯分布:

参数使用最大似然法估计。

高斯朴素贝叶斯实现方法代码:

'''高斯朴素贝叶斯'''
def MyGaussianNB(trainMat='',Classlabels='',testDoc=''):
    # -----sklearn GaussianNB-------
    # 训练数据
    X = np.array(trainMat)
    Y = np.array(Classlabels)
    # 高斯分布
    clf = GaussianNB()
    clf.fit(X, Y)
    # 测试预测结果
    index = clf.predict(testDoc) # 返回索引
    reslist = ['Art','Literature','Education','Philosophy','History']
    print(reslist[index[0]])

多项朴素贝叶斯

MultinomialNB 实现了服从多项分布数据的朴素贝叶斯算法,也是用于文本分类(这个领域中数据往往以词向量表示,尽管在实践中 tf-idf 向量在预测时表现良好)的两大经典朴素贝叶斯算法之一。 分布参数由每类 y 的??。

参数?使用平滑过的最大似然估计法来估计,即相对频率计数:

式中?是 训练集 T 中 特征 i 在类 y 中出现的次数,
?是类 y 中出现所有特征的计数总和。
先验平滑因子被称为利德斯通(Lidstone smoothing)。

多项朴素贝叶斯实现方法代码:

'''多项朴素贝叶斯'''
def MyMultinomialNB(trainMat='',Classlabels='',testDoc=''):
    # -----sklearn MultinomialNB-------
    # 训练数据
    X = np.array(trainMat)
    Y = np.array(Classlabels)
    # 多项朴素贝叶斯
    clf = MultinomialNB()
    clf.fit(X, Y)
    # 测试预测结果
    index = clf.predict(testDoc) # 返回索引
    reslist = ['Art','Literature','Education','Philosophy','History']
    print(reslist[index[0]])

伯努利朴素贝叶斯

BernoulliNB 实现了用于多重伯努利分布数据的朴素贝叶斯训练和分类算法,即有多个特征,但每个特征 都假设是一个二元 (Bernoulli, boolean) 变量。 因此,这类算法要求样本以二元值特征向量表示;如果样本含有其他类型的数据, 一个 BernoulliNB 实例会将其二值化(取决于 binarize 参数)。伯努利朴素贝叶斯的决策规则基于

与多项分布朴素贝叶斯的规则不同 伯努利朴素贝叶斯明确地惩罚类 y 中没有出现作为预测因子的特征 i ,而多项分布分布朴素贝叶斯只是简单地忽略没出现的特征。

在文本分类的例子中,词频向量(word occurrence vectors)(而非词数向量(word count vectors))可能用于训练和用于这个分类器。 BernoulliNB 可能在一些数据集上可能表现得更好,特别是那些更短的文档。 如果时间允许,建议对两个模型都进行评估。

伯努利朴素贝叶斯代码实现如下:

'''伯努利朴素贝叶斯'''
def MyBernoulliNB(trainMat='',Classlabels='',testDoc=''):
    # -----sklearn BernoulliNB-------
    # 训练数据
    X = np.array(trainMat)
    Y = np.array(Classlabels)
    # 多项朴素贝叶斯
    clf = BernoulliNB()
    clf.fit(X, Y)
    # 测试预测结果
    index = clf.predict(testDoc) # 返回索引
    reslist = ['Art','Literature','Education','Philosophy','History']
    print(reslist[index[0]])

各种贝叶斯模型分类测试

代码实现如下:
def testingNB():

# 加载数据集和单词集合
trainMat,Classlabels,myVocabList = grabdata() # 读取训练结果
# 测试数据
testEntry = textParse(open('./fudan/test/C6-2.txt',encoding='UTF-8').read())
testDoc = np.array(bagOfWords2VecMN(myVocabList, testEntry)) # 测试数据
# 测试预测结果
MyGaussianNB(trainMat,Classlabels,testDoc)
MyMultinomialNB(trainMat,Classlabels,testDoc)
MyBernoulliNB(trainMat,Classlabels,testDoc)

运行结果:

Building prefix dict from the default dictionary ...
Loading model from cache C:\Users\ADMINI~1\AppData\Local\Temp\jieba.cache
Loading model cost 1.014 seconds.
Prefix dict has been built succesfully.
高斯朴素贝叶斯:Education
多项朴素贝叶斯分类结果:Art
伯努利朴素贝叶斯分类结果:Literature
耗时:2.3996 s

参考文献

  1. scikit中文社区:http://sklearn.apachecn.org/cn/0.19.0/
  2. 中文维基百科:https://zh.wikipedia.org/wiki/
  3. 文本分类特征选择:https://www.cnblogs.com/june0507/p/7601001.html
  4. GitHub:https://github.com/BaiNingchao/MachineLearning-1
  5. 图书:《机器学习实战》
  6. 图书:《自然语言处理理论与实战》

完整代码下载

源码请进【机器学习和自然语言QQ群:436303759】文件下载:

作者声明

本文版权归作者白宁超所有,本文原创,旨在学术和科研使用。文章同步如下:

实现 | 朴素贝叶斯模型算法研究与实例分析

实现 | 朴素贝叶斯模型算法研究与实例分析

(白宁超?2018年9月2日 11: 16:31)

 

导读:朴素贝叶斯模型是机器学习常用的模型算法之一,其在文本分类方面简单易行,且取得不错的分类效果。所以很受欢迎,对于朴素贝叶斯的学习,本文首先介绍理论知识即朴素贝叶斯相关概念和公式推导,为了加深理解,采用一个维基百科上面性别分类例子进行形式化描述。然后通过编程实现朴素贝叶斯分类算法,并在屏蔽社区言论、垃圾邮件、个人广告中获取区域倾向等几个方面进行应用,包括创建数据集、数据预处理、词集模型和词袋模型、朴素贝叶斯模型训练和优化等。然后结合复旦大学新闻语料进行朴素贝叶斯的应用。最后,大家熟悉其原理和实现之后,采用机器学习sklearn包进行实现和优化。由于篇幅较长,采用理论理解、案例实现、sklearn优化三个部分进行学习。(本文原创,转载必须注明出处:朴素贝叶斯模型算法研究与实例分析)

案例场景1: 屏蔽社区留言板的侮辱性言论

项目概述

构建一个快速过滤器来屏蔽在线社区留言板上的侮辱性言论。如果某条留言使用了负面或者侮辱性的语言,那么就将该留言标识为内容不当。对此问题建立两个类别: 侮辱类和非侮辱类,使用 1 和 0 分别表示。

本案例开发流程如下:

  1. 收集数据: 可以是文本数据、数据库数据、网络爬取的数据、自定义数据等等
  2. 数据预处理: 对采集数据进行格式化处理,文本数据的格式一致化,网络数据的分析抽取等,包括中文分词、停用词处理、词袋模型、构建词向量等。
  3. 分析数据: 检查词条确保解析的正确性,根据特征进行模型选择、特征抽取等。
  4. 训练算法: 从词向量计算概率
  5. 测试算法: 根据现实情况修改分类器
  6. 使用算法: 对社区留言板言论进行分类

收集数据

本案例我们采用自定义的数据集,我们选择6条社区评论,然后进行数据处理后以list形式存储在文档列表postingList中。其中每个词代表一个特征。将每条评论进行分类(即1代表侮辱性文字,0代表非侮辱文字)存在在类别列表classVec中。最后返回数据集和类标签。代码实现如下:

'''创建数据集:单词列表postingList, 所属类别classVec'''
def loadDataSet():
    postingList = [['my', 'dog', 'has', 'flea', 'problems', 'help', 'please'],
                   ['maybe', 'not', 'take', 'him', 'to', 'dog', 'park', 'stupid'],
                   ['my', 'dalmation', 'is', 'so', 'cute', 'I', 'love', 'him'],
                   ['stop', 'posting', 'stupid', 'worthless', 'garbage'],
                   ['mr', 'licks', 'ate', 'my', 'steak', 'how', 'to', 'stop', 'him'],
                   ['quit', 'buying', 'worthless', 'dog', 'food', 'stupid']]
    classVec = [0, 1, 0, 1, 0, 1]  # 1代表侮辱性文字,0代表非侮辱文字
    return postingList, classVec

代码分析:postingList列表存储6条评论信息,classVec列表存储每条信息类别(1代表侮辱性文字,0代表非侮辱文字)。最后返回文档列表和类别列表。

数据预处理

数据预处理包括对样本进行分词、词性筛选、停用词处理等,最后形成规范化干净的数据样本。由于本案例收集数据时默认进行了数据预处理,所以本节不在介绍(复旦新闻语料文本分类案例会详细介绍)。目前,我们采集的数据还是文本类型,计算机还不能直接处理,需要将文本数据转化成词向量进行处理。这里面需要获取特征的词汇集合(如果暂时不理解,先看看代码实现,下面会进行形式化描述)。其实现过程如下:

'''获取所有单词的集合:返回不含重复元素的单词列表'''
def createVocabList(dataSet):
    vocabSet = set([])
    for document in dataSet:
        vocabSet = vocabSet | set(document)  # 操作符 | 用于求两个集合的并集
    # print(vocabSet)
    return list(vocabSet)

代码分析:方法参数dataSet即加载数据集返回的文档列表。vocabSet是定义的不重复的数据集合。然后for循环对文档列表每条数据进行遍历处理,将不重复的词汇添加到vocabSet中,最终形成整个文档的词汇集,然后以list形式返回。

上面的方法已经获取了整个文档词汇集合,接着构建数据矩阵,代码实现如下:

'''词集模型构建数据矩阵'''
def setOfWords2Vec(vocabList, inputSet):
    # 创建一个和词汇表等长的向量,并将其元素都设置为0
    returnVec = [0] * len(vocabList)
    # 遍历文档中的所有单词,如果出现了词汇表中的单词,则将输出的文档向量中的对应值设为1
    for word in inputSet:
        if word in vocabList:
            returnVec[vocabList.index(word)] = 1
        else:
            print("单词: %s 不在词汇表之中!" % word)
    # print(returnVec)
    return returnVec

代码分析:本方法提供两个参数分别是整个训练文档词汇集(即全部训练文档6条评论不重复的单词集合),输入的数据列表。以整个词汇集等长的0向量。我们遍历输入数据列表,如果词特征在词汇集则标记1,不在词汇集保持为0.最后返回词向量矩阵。

与词集模型对应的,有个词袋模型。两者都是构建词向量,只是方式不一样,词袋模型也是推荐使用的词向量化方法,其实现如下:

'''文档词袋模型构建数据矩阵'''
def bagOfWords2VecMN(vocabList, inputSet):
    returnVec = [0] * len(vocabList)
    for word in inputSet:
        if word in vocabList:
            returnVec[vocabList.index(word)] += 1
    # print(returnVec)
    return returnVec

分析数据

运行词集模型setOfWords2Vec(vocabList, dataSet[0])运行结果如下:

['dog', 'to', 'take', 'park', 'licks', 'has', 'help', 'stupid', 'him', 'so', 'not', 'love', 'buying', 'problems', 'cute', 'stop', 'steak', 'how', 'flea', 'maybe', 'food', 'I', 'please', 'dalmation', 'mr', 'posting', 'ate', 'garbage', 'worthless', 'my', 'is', 'quit']
[1, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 1, 0, 0]

结果分析:我们将dataSet[0]即第一条信息['my', 'dog', 'has', 'flea', 'problems', 'help', 'please']构建词集模型,词特征集为['dog', 'to', 'take', 'park', 'licks', 'has', 'help', 'stupid', 'him', 'so', 'not', 'love', 'buying', 'problems', 'cute', 'stop', 'steak', 'how', 'flea', 'maybe', 'food', 'I', 'please', 'dalmation', 'mr', 'posting', 'ate', 'garbage', 'worthless', 'my', 'is', 'quit']。结果显示[1, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 1, 0, 0]。即词特征集dog在dataSet[0]中,标记为1,to不在则保留原始的0.以此类推。我们也可以查看dataSet[1]等数据结果。

数据样本分析仅仅如上所述?当然不是,本例子中数据量比较小,容易分析。当数据量比较大,特征数以万计之时,人工分析就显得捉襟见肘了。我们可以采用图形化分析方法,根据具体业务需求,可以选择基于python自带的matplotlib可视化分析、或者其他图形可视化工具进行平面或多维数据分析,然后便于特征的选择。

如果是中文分词,我们还可以对词性进行分析,然后选择相应的词性特征,比如名词、动词、地名、人名、机构名等等,对虚词、助词等进行过滤,一方面达到数据降维另一方面防止模型训练拟合化等问题。

训练模型

现在已经知道了一个词是否出现在一篇文档中,也知道该文档所属的类别。接下来我们重写贝叶斯准则,将之前的 x, y 替换为 w. 粗体的 w 表示这是一个向量,即它由多个值组成。在这个例子中,数值个数与词汇表中的词个数相同。

我们使用上述公式,对每个类计算该值,然后比较这两个概率值的大小。根据上述公式可知,我们右边的式子等同于左边的式子,由于对于每个ci,P(w)是固定的。并且我们只需要比较左边式子值的大小来决策分类,那么我们就可以简化为通过比较右边分子值得大小来做决策分类。

首先可以通过类别 i (侮辱性留言或者非侮辱性留言)中的文档数除以总的文档数来计算概率?来计算上述概率,这样就极大地简化了计算的过程。具体代码实现如下:

'''朴素贝叶斯分类器训练函数'''
def _trainNB0(trainMatrix, trainCategory):
    numTrainDocs = len(trainMatrix) # 文件数
    numWords = len(trainMatrix[0]) # 单词数
    # 侮辱性文件的出现概率,即trainCategory中所有的1的个数,
    # 代表的就是多少个侮辱性文件,与文件的总数相除就得到了侮辱性文件的出现概率
    pAbusive = sum(trainCategory) / float(numTrainDocs)

    # 构造单词出现次数列表
    p0Num = zeros(numWords) # [0,0,0,.....]
    p1Num = zeros(numWords) # [0,0,0,.....]
    p0Denom = 0.0;p1Denom = 0.0 # 整个数据集单词出现总数
    for i in range(numTrainDocs):
        # 遍历所有的文件,如果是侮辱性文件,就计算此侮辱性文件中出现的侮辱性单词的个数
        if trainCategory[i] == 1:
            p1Num += trainMatrix[i] #[0,1,1,....]->[0,1,1,...]
            p1Denom += sum(trainMatrix[i])
        else:
            # 如果不是侮辱性文件,则计算非侮辱性文件中出现的侮辱性单词的个数
            p0Num += trainMatrix[i]
            p0Denom += sum(trainMatrix[i])
    # 类别1,即侮辱性文档的[P(F1|C1),P(F2|C1),P(F3|C1),P(F4|C1),P(F5|C1)....]列表
    # 即 在1类别下,每个单词出现次数的占比
    p1Vect = p1Num / p1Denom# [1,2,3,5]/90->[1/90,...]
    # 类别0,即正常文档的[P(F1|C0),P(F2|C0),P(F3|C0),P(F4|C0),P(F5|C0)....]列表
    # 即 在0类别下,每个单词出现次数的占比
    p0Vect = p0Num / p0Denom
    return p0Vect, p1Vect, pAbusive

代码分析:本方法参数分别是文档特征向量矩阵和文档类别向量矩阵。首先计算侮辱性文档占总文档的概率,然后计算正常文档下特征词的概率向量和侮辱性特征词的向量,为了更好理解上面的代码我们看下运行p0V,p1V,pAb=_trainNB0(trainMatrix,Classlabels)结果:

词汇表集
['I', 'cute', 'help', 'dalmation', 'please', 'has', 'my', 'him', 'worthless', 'problems', 'so', 'mr', 'flea', 'love', 'take', 'stupid', 'dog', 'park', 'how', 'quit', 'buying', 'posting', 'steak', 'maybe', 'to', 'is', 'ate', 'not', 'garbage', 'food', 'stop', 'licks']
各条评论特征向量,其中1,3,5条为类别02,4,6条为类别1
[0, 0, 1, 0, 1, 1, 1, 0, 0, 1, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]
[0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 0, 0, 0, 0, 0, 1, 1, 0, 0, 1, 0, 0, 0, 0]
[1, 1, 0, 1, 0, 0, 1, 1, 0, 0, 1, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0]
[0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 1, 0, 1, 0]
[0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 1, 0, 1, 0, 1, 0, 0, 0, 1, 1]
[0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0]
类别0下特征词条件概率
[0.04166667 0.04166667 0.04166667 0.04166667 0.04166667 0.04166667
 0.125      0.08333333 0.         0.04166667 0.04166667 0.04166667
 0.04166667 0.04166667 0.         0.         0.04166667 0.
 0.04166667 0.         0.         0.         0.04166667 0.
 0.04166667 0.04166667 0.04166667 0.         0.         0.
 0.04166667 0.04166667] 
 类别1下特征词条件概率
[0.         0.         0.         0.         0.         0.
 0.         0.05263158 0.10526316 0.         0.         0.
 0.         0.         0.05263158 0.15789474 0.10526316 0.05263158
 0.         0.05263158 0.05263158 0.05263158 0.         0.05263158
 0.05263158 0.         0.         0.05263158 0.05263158 0.05263158
 0.05263158 0.        ] 
 0.5

结果分析:结合结果我们去理解上面的训练模型代码。首先最后一个是0.5代表侮辱性文档占全部文档的50%即一半,实际上我们标记3个正常评论词条,3个非正常的,这个显然正确。其次,第一次词I在类别1中出现0次,在类别0中出现1次。对应的条件概率分别是0.04166667和0

测试算法

在利用贝叶斯分类器对文档进行分类时,要计算多个概率的乘积以获得文档属于某个类别的概率,即计算?。如果其中一个概率值为 0,那么最后的乘积也为 0。为降低这种影响,可以将所有词的出现数初始化为 1,并将分母初始化为 2 (取1 或 2 的目的主要是为了保证分子和分母不为0,大家可以根据业务需求进行更改)。

另一个遇到的问题是下溢出,这是由于太多很小的数相乘造成的。当计算乘积??时,由于大部分因子都非常小,所以程序会下溢出或者得到不正确的答案。(用 Python 尝试相乘许多很小的数,最后四舍五入后会得到 0)。一种解决办法是对乘积取自然对数。在代数中有 ln(a * b) = ln(a) + ln(b), 于是通过求对数可以避免下溢出或者浮点数舍入导致的错误。同时,采用自然对数进行处理不会有任何损失。

下图给出了函数 f(x) 与 ln(f(x)) 的曲线。可以看出,它们在相同区域内同时增加或者减少,并且在相同点上取到极值。它们的取值虽然不同,但不影响最终结果。

进行条件概率连乘时候,由于有条件概率极小或者为0,最后导致结果为0 ,显然不符合我们预期结果,因此对训练模型进行优化,其优化代码如下:

'''训练数据优化版本'''
def trainNB0(trainMatrix, trainCategory):
    numTrainDocs = len(trainMatrix) # 总文件数
    numWords = len(trainMatrix[0]) # 总单词数
    pAbusive = sum(trainCategory) / float(numTrainDocs) # 侮辱性文件的出现概率
    # 构造单词出现次数列表,p0Num 正常的统计,p1Num 侮辱的统计
    # 避免单词列表中的任何一个单词为0,而导致最后的乘积为0,所以将每个单词的出现次数初始化为 1
    p0Num = ones(numWords)#[0,0......]->[1,1,1,1,1.....],ones初始化1的矩阵
    p1Num = ones(numWords)

    # 整个数据集单词出现总数,2.0根据样本实际调查结果调整分母的值(2主要是避免分母为0,当然值可以调整)
    # p0Denom 正常的统计
    # p1Denom 侮辱的统计
    p0Denom = 2.0
    p1Denom = 2.0
    for i in range(numTrainDocs):
        if trainCategory[i] == 1:
            p1Num += trainMatrix[i]  # 累加辱骂词的频次
            p1Denom += sum(trainMatrix[i]) # 对每篇文章的辱骂的频次 进行统计汇总
        else:
            p0Num += trainMatrix[i]
            p0Denom += sum(trainMatrix[i])
    # 类别1,即侮辱性文档的[log(P(F1|C1)),log(P(F2|C1)),log(P(F3|C1)),log(P(F4|C1)),log(P(F5|C1))....]列表,取对数避免下溢出或浮点舍入出错
    p1Vect = log(p1Num / p1Denom)
    # 类别0,即正常文档的[log(P(F1|C0)),log(P(F2|C0)),log(P(F3|C0)),log(P(F4|C0)),log(P(F5|C0))....]列表
    p0Vect = log(p0Num / p0Denom)
    return p0Vect, p1Vect, pAbusive

我们再看生成条件概率结果如下:

[-2.56494936 -2.15948425 -3.25809654 -2.56494936 -3.25809654 -3.25809654
 -2.56494936 -2.56494936 -3.25809654 -2.56494936 -3.25809654 -3.25809654
 -2.56494936 -2.56494936 -2.56494936 -3.25809654 -2.56494936 -2.56494936
 -2.56494936 -2.56494936 -1.87180218 -2.56494936 -3.25809654 -2.56494936
 -2.56494936 -2.56494936 -2.56494936 -3.25809654 -3.25809654 -2.56494936
 -3.25809654 -2.56494936] 
 [-3.04452244 -2.35137526 -2.35137526 -3.04452244 -2.35137526 -2.35137526
 -3.04452244 -3.04452244 -1.94591015 -2.35137526 -2.35137526 -2.35137526
 -3.04452244 -2.35137526 -3.04452244 -1.65822808 -1.94591015 -3.04452244
 -3.04452244 -3.04452244 -3.04452244 -3.04452244 -2.35137526 -3.04452244
 -3.04452244 -3.04452244 -3.04452244 -2.35137526 -2.35137526 -3.04452244
 -2.35137526 -3.04452244] 
 0.5

使用算法对社区留言板言论进行分类

构建朴素贝叶斯分类函数

将乘法转换为加法
乘法:

加法:

'''
def classifyNB(vec2Classify, p0Vec, p1Vec, pClass1):
    # 计算公式  log(P(F1|C))+log(P(F2|C))+....+log(P(Fn|C))+log(P(C))
    # 使用 NumPy 数组来计算两个向量相乘的结果,这里的相乘是指对应元素相乘,即先将两个向量中的第一个元素相乘,然后将第2个元素相乘,以此类推。这里的 vec2Classify * p1Vec 的意思就是将每个词与其对应的概率相关联起来
    p1 = sum(vec2Classify * p1Vec) + log(pClass1)
    p0 = sum(vec2Classify * p0Vec) + log(1.0 - pClass1)
    if p1 > p0:
        return 1
    else:
        return 0

测试朴素贝叶斯算法

结合上面分析流程和实现方法,我们综合测试朴素贝叶斯对评论信息分类如下:

'''朴素贝叶斯算法屏蔽社区留言板的侮辱性言论的应用'''
def testingNB():
    # 1. 加载数据集
    dataSet, Classlabels = loadDataSet()
    # 2. 创建单词集合
    myVocabList = createVocabList(dataSet)
    # 3. 计算单词是否出现并创建数据矩阵
    trainMat = []
    for postinDoc in dataSet:
        # 返回m*len(myVocabList)的矩阵, 记录的都是0,1信息
        trainMat.append(setOfWords2Vec(myVocabList, postinDoc))
    # print('test',len(array(trainMat)[0]))
    # 4. 训练数据
    p0V, p1V, pAb = trainNB0(array(trainMat), array(Classlabels))
    # 5. 测试数据
    testEntry = ['love', 'my', 'dalmation']
    thisDoc = array(setOfWords2Vec(myVocabList, testEntry))
    print(testEntry, '分类结果是: ', classifyNB(thisDoc, p0V, p1V, pAb))
    testEntry = ['stupid', 'garbage']
    thisDoc = array(setOfWords2Vec(myVocabList, testEntry))
    print(testEntry, '分类结果是: ', classifyNB(thisDoc, p0V, p1V, pAb))

运行结果如下:

['love', 'my', 'dalmation'] 分类结果是:  0
['stupid', 'garbage'] 分类结果是:  1

案例场景2: 对社区留言板言论进行分类

项目概述

我们运行朴素贝叶斯分类进行电子邮件垃圾过滤。在文档分类中,整个文档(如一封电子邮件)是实例,而电子邮件中的某些元素则构成特征。我们可以观察文档中出现的词,并把每个词作为一个特征,而每个词的出现或者不出现作为该特征的值,这样得到的特征数目就会跟词汇表中的词的数目一样多。

本项目开发流程如下:

收集数据: 提供文本文件
准备数据: 将文本文件解析成词条向量
分析数据: 检查词条确保解析的正确性
训练算法: 使用我们之前建立的 trainNB() 函数
测试算法: 使用朴素贝叶斯进行交叉验证
使用算法: 构建一个完整的程序对一组文档进行分类,将错分的文档输出到屏幕上

收集数据并预处理

邮件格式内容如下:

对邮件进行读取实现如下:

'''读取文本'''
def testParseTest():
    print(textParse(open('./email/ham/1.txt').read()))

准备数据

对读取的文本进行词条向量化,其实现如下:

'''接收一个大字符串并将其解析为字符串列表'''
def textParse(bigString):
    import re
    # 使用正则表达式来切分句子,其中分隔符是除单词、数字外的任意字符串
    listOfTokens = re.split(r'\W*', bigString)
    return [tok.lower() for tok in listOfTokens if len(tok) > 2]

分析数据

这个部分在案例场景1进行了详细描述,此处不在赘述。

训练算法

此处,我们去调用在场景1优化过的朴素贝叶斯训练模型trainNB0() 函数,这里也是一劳永逸的方法。还可以对数据预处理等进行封装。

测试算法

本测试方法中使用的数据集,即文档可以参见下文代码下载。采用方法跟场景1基本类似,这里不作代码解析。具体实现代码如下:

'''对贝叶斯垃圾邮件分类器进行自动化处理。'''
def spamTest():
    docList = [];classList = [];fullText = [] # 文档列表、类别列表、文本特征
    for i in range(1, 26): # 总共25个文档
        # 切分,解析数据,并归类为 1 类别
        wordList = textParse(open('./email/spam/%d.txt' % i).read())
        docList.append(wordList)
        classList.append(1)
        # 切分,解析数据,并归类为 0 类别
        wordList = textParse(open('./email/ham/%d.txt' % i,encoding='UTF-8').read())
        docList.append(wordList)
        classList.append(0)
        fullText.extend(wordList)
    # 创建词汇表
    vocabList = createVocabList(docList)
    trainingSet = list(range(50)) # 词汇表文档索引
    testSet = []
    # 随机取 10 个邮件用来测试
    for i in range(10):
        # random.uniform(x, y) 随机生成一个范围为 x - y 的实数
        randIndex = int(random.uniform(0, len(trainingSet)))
        testSet.append(trainingSet[randIndex]) # 随机抽取测试样本
        del(trainingSet[randIndex]) # 训练集中删除选择为测试集的文档

    trainMat = [];trainClasses = [] # 训练集合训练标签
    for docIndex in trainingSet:
        trainMat.append(setOfWords2Vec(vocabList, docList[docIndex]))
        trainClasses.append(classList[docIndex])

    p0V, p1V, pSpam = trainNB0(array(trainMat), array(trainClasses))
    errorCount = 0
    for docIndex in testSet:
        wordVector = setOfWords2Vec(vocabList, docList[docIndex])
        if classifyNB(array(wordVector), p0V, p1V, pSpam) != classList[docIndex]:
            errorCount += 1
    print('the errorCount is: ', errorCount)
    print('the testSet length is :', len(testSet))
    print('the error rate is :', float(errorCount)/len(testSet))

运行结果如下:

the errorCount is:  2
the testSet length is : 10
the error rate is : 0.2

案例场景3: 使用朴素贝叶斯分类器从个人广告中获取区域倾向

项目概述

广告商往往想知道关于一个人的一些特定人口统计信息,以便能更好地定向推销广告。我们将分别从美国的两个城市中选取一些人,通过分析这些人发布的信息,来比较这两个城市的人们在广告用词上是否不同。如果结论确实不同,那么他们各自常用的词是哪些,从人们的用词当中,我们能否对不同城市的人所关心的内容有所了解。

开发流程如下:

收集数据: 从 RSS 源收集内容,这里需要对 RSS 源构建一个接口
准备数据: 将文本文件解析成词条向量
分析数据: 检查词条确保解析的正确性
训练算法: 使用我们之前建立的 trainNB0() 函数
测试算法: 观察错误率,确保分类器可用。可以修改切分程序,以降低错误率,提高分类结果
使用算法: 构建一个完整的程序,封装所有内容。给定两个 RSS 源,改程序会显示最常用的公共词

收集数据

从 RSS 源收集内容,这里需要对 RSS 源构建一个接口,也就是导入 RSS 源,我们使用 python 下载文本,在http://code.google.com/p/feedparser/?下浏览相关文档,安装 feedparse,首先解压下载的包,并将当前目录切换到解压文件所在的文件夹,然后在 python 提示符下输入:python setup.py install

准备数据

文档词袋模型

我们将每个词的出现与否作为一个特征,这可以被描述为 词集模型(set-of-words model)。如果一个词在文档中出现不止一次,这可能意味着包含该词是否出现在文档中所不能表达的某种信息,这种方法被称为 词袋模型(bag-of-words model)。在词袋中,每个单词可以出现多次,而在词集中,每个词只能出现一次。为适应词袋模型,需要对函数 setOfWords2Vec() 稍加修改,修改后的函数为 bagOfWords2Vec() 。

如下给出了基于词袋模型的朴素贝叶斯代码。它与函数 setOfWords2Vec() 几乎完全相同,唯一不同的是每当遇到一个单词时,它会增加词向量中的对应值,而不只是将对应的数值设为 1 。

这部分在场景1中已经构建完成,并进行了阐述。

分析数据

这个部分在案例场景1进行了详细描述,此处不在赘述。

训练算法

此处,我们去调用在场景1优化过的朴素贝叶斯训练模型trainNB0() 函数,这里也是一劳永逸的方法。还可以对数据预处理等进行封装。

测试算法

观察错误率,确保分类器可用。可以修改切分程序,以降低错误率,提高分类结果。其具体实现如下:

'''RSS源分类器及高频词去除函数'''
def calcMostFreq(vocabList,fullText):
    import operator
    freqDict={}
    for token in vocabList:  #遍历词汇表中的每个词
        freqDict[token]=fullText.count(token)  #统计每个词在文本中出现的次数
    sortedFreq=sorted(freqDict.items(),key=operator.itemgetter(1),reverse=True)  #根据每个词出现的次数从高到底对字典进行排序
    return sortedFreq[:30]   #返回出现次数最高的30个单词



def localWords(feed1,feed0):
    # import feedparser # feedparser是python中最常用的RSS程序库
    docList=[];classList=[];fullText=[]
    minLen=min(len(feed1['entries']),len(feed0['entries'])) # entries内容无法抓取,网站涉及反爬虫技术
    print(len(feed1['entries']),len(feed0['entries']))
    for i in range(minLen):
        wordList=textParse(feed1['entries'][i]['summary'])   #每次访问一条RSS源
        docList.append(wordList)
        fullText.extend(wordList)
        classList.append(1)
        wordList=textParse(feed0['entries'][i]['summary'])
        docList.append(wordList)
        fullText.extend(wordList)
        classList.append(0)
    vocabList=createVocabList(docList)
    top30Words=calcMostFreq(vocabList,fullText)
    for pairW in top30Words:
        if pairW[0] in vocabList:vocabList.remove(pairW[0])    #去掉出现次数最高的那些词
    trainingSet=range(2*minLen);testSet=[]
    for i in range(20):
        randIndex=int(random.uniform(0,len(trainingSet)))
        testSet.append(trainingSet[randIndex])
        del(trainingSet[randIndex])
    trainMat=[];trainClasses=[]
    for docIndex in trainingSet:
        trainMat.append(bagOfWords2VecMN(vocabList,docList[docIndex]))
        trainClasses.append(classList[docIndex])
    p0V,p1V,pSpam=trainNB0(array(trainMat),array(trainClasses))
    errorCount=0
    for docIndex in testSet:
        wordVector=bagOfWords2VecMN(vocabList,docList[docIndex])
        if classifyNB(array(wordVector),p0V,p1V,pSpam)!=classList[docIndex]:
            errorCount+=1
    print('the error rate is:',float(errorCount)/len(testSet))
    return vocabList,p0V,p1V

运行结果:

ny = feedparser.parse('http://newyork.craigslist.org/stp/index.rss')
sf = feedparser.parse('http://sfbay.craigslist.org/stp/index.rss')
# print(ny)
vocabList,pSF,pNY=localWords(ny,sf)

由于如上两个地址抓取,得到feed0['entries']为空,所以没有进行结果分析,读者可以试用其他rss地址进行处理。如下是采用之前网站反爬虫抓取前的分析结果:

vocabList,pSF,pNY=bayes.localWords(ny,sf)
the error rate is: 0.2
vocabList,pSF,pNY=bayes.localWords(ny,sf)
the error rate is: 0.3
vocabList,pSF,pNY=bayes.localWords(ny,sf)
the error rate is: 0.55

参考文献

  1. scikit中文社区:http://sklearn.apachecn.org/cn/0.19.0/
  2. 中文维基百科:https://zh.wikipedia.org/wiki/
  3. 文本分类特征选择:https://www.cnblogs.com/june0507/p/7601001.html
  4. GitHub:https://github.com/BaiNingchao/MachineLearning-1
  5. 图书:《机器学习实战》
  6. 图书:《自然语言处理理论与实战》

完整代码下载

源码请进【机器学习和自然语言QQ群:436303759】文件下载:

作者声明

本文版权归作者白宁超所有,本文原创,旨在学术和科研使用。文章同步如下:

理论 | 朴素贝叶斯模型算法研究与实例分析

理论 | 朴素贝叶斯模型算法研究与实例分析

(白宁超?2018年9月4日10:00:31)

导读:朴素贝叶斯模型是机器学习常用的模型算法之一,其在文本分类方面简单易行,且取得不错的分类效果。所以很受欢迎,对于朴素贝叶斯的学习,本文首先介绍理论知识即朴素贝叶斯相关概念和公式推导,为了加深理解,采用一个维基百科上面性别分类例子进行形式化描述。然后通过编程实现朴素贝叶斯分类算法,并在屏蔽社区言论、垃圾邮件、个人广告中获取区域倾向等几个方面进行应用,包括创建数据集、数据预处理、词集模型和词袋模型、朴素贝叶斯模型训练和优化等。然后结合复旦大学新闻语料进行朴素贝叶斯的应用。最后,大家熟悉其原理和实现之后,采用机器学习sklearn包进行实现和优化。由于篇幅较长,采用理论理解、案例实现、sklearn优化三个部分进行学习。(本文原创,转载必须注明出处:朴素贝叶斯模型算法研究与实例分析)

朴素贝叶斯理论

朴素贝叶斯概述

朴素贝叶斯是一种构建分类器的简单方法。该分类器模型会给问题实例分配用特征值表示的类标签,类标签取自有限集合。所有朴素贝叶斯分类器都假定样本每个特征与其他特征都不相关。

特征独立理解的例子:如果一种水果其具有红,圆,直径大概3英寸等特征,该水果可以被判定为是苹果。尽管这些特征相互依赖或者有些特征由其他特征决定,然而朴素贝叶斯分类器认为这些属性在判定该水果是否为苹果的概率分布上独立的。

尽管是带着这些朴素思想和过于简单化的假设,但朴素贝叶斯分类器在很多复杂的现实情形中仍能够获取相当好的效果。朴素贝叶斯分类器的一个优势在于只需要根据少量的训练数据估计出必要的参数(变量的均值和方差)。

朴素贝叶斯模型

朴素贝叶斯方法是基于贝叶斯定理的一组有监督学习算法,即“简单”地假设每对特征之间相互独立。 给定一个类别的相关的特征向量,贝叶斯定理阐述了以下关系:

使用简单(naive)的假设-每对特征之间都相互独立:

对于所有的 math: i ,这个关系式可以简化为

由于在给定的输入中?是一个常量,我们使用下面的分类规则:

我们可以使用最大后验概率(Maximum A Posteriori, MAP) 来估计??分布时的所做的假设不同。尽管其假设过于简单,在很多实际情况下,朴素贝叶斯工作得很好,特别是文档分类和垃圾邮件过滤。相比于其他更复杂的方法,朴素贝叶斯学习器和分类器非常快。

朴素贝叶斯算法思想

假设有一个数据集,它由两类数据组成,数据分布如下图所示:

?表示数据点 (x,y) 属于类别 2(图中三角形表示的类别)的概率,那么对于一个新数据点 (x,y),可以用下面的规则来判断它的类别:

  • 如果,那么类别为1
  • 如果??,那么类别为2

也就是说,我们会选择高概率对应的类别。这就是贝叶斯决策理论的核心思想,即选择具有最高概率的决策。

朴素贝叶斯工作原理

提取所有文档中的词条并进行去重
获取文档的所有类别
计算每个类别中的文档数目
对每篇训练文档: 
    对每个类别: 
        如果词条出现在文档中-->增加该词条的计数值(for循环或者矩阵相加)
        增加所有词条的计数值(此类别下词条总数)
对每个类别: 
    对每个词条: 
        将该词条的数目除以总词条数目得到的条件概率(P(词条|类别))
返回该文档属于每个类别的条件概率(P(类别|文档的所有词条))

朴素贝叶斯算法流程

收集数据: 可以使用任何方法。
准备数据: 需要数值型或者布尔型数据。
分析数据: 有大量特征时,绘制特征作用不大,此时使用直方图效果更好。
训练算法: 计算不同的独立特征的条件概率。
测试算法: 计算错误率。
使用算法: 一个常见的朴素贝叶斯应用是文档分类。可以在任意的分类场景中使用朴素贝叶斯分类器,不一定非要是文本。

朴素贝叶斯优缺点

优点: 在数据较少的情况下仍然有效,可以处理多类别问题。
缺点: 对于输入数据的准备方式较为敏感。
适用数据类型: 标称型数据。

案例描述:形式化理解朴素贝叶斯性别分类

问题描述

通过一些测量的特征,包括身高、体重、脚的尺寸,判定一个人是男性还是女性。

训练数据

性别 身高(英尺) 体重(磅) 脚的尺寸(英寸)
6 180 12
5.92 190 11
5.58 170 12
5.92 165 10
5 100 6
5.5 150 8
5.42 130 7
5.75 150 9

假设训练集样本的特征满足高斯分布,得到下表:

性别 均值(身高) 方差(身高) 均值(体重) 方差(体重) 均值(脚的尺寸) 方差(脚的尺寸)
男性 5.855 3.5033e-02 176.25 1.2292e+02 11.25 9.1667e-01
女性 5.4175 9.7225e-02 132.5 5.5833e+02 7.5 1.6667e+00

我们认为两种类别是等概率的,也就是P(male)= P(female) = 0.5。在没有做辨识的情况下就做这样的假设并不是一个好的点子。但我们通过数据集中两类样本出现的频率来确定P(C),我们得到的结果也是一样的。

测试数据

以下给出一个待分类是男性还是女性的样本。

性别 身高(英尺) 体重(磅) 脚的尺寸(英尺)
sample 6 130 8

我们希望得到的是男性还是女性哪类的后验概率大。男性的后验概率通过下面式子来求取

女性的后验概率通过下面式子来求取

证据因子(通常是常数)用来对各类的后验概率之和进行归一化.

证据因子是一个常数(在正态分布中通常是正数),所以可以忽略。接下来我们来判定这样样本的性别。


其中?是训练集样本的正态分布参数. 注意,这里的值大于1也是允许的 – 这里是概率密度而不是概率,因为身高是一个连续的变量.

集样本的正态分布参数. 注意,这里的值大于1也是允许的 – 这里是概率密度而不是概率,因为身高是一个连续的变量.

模型预测结果

由于女性后验概率的分子比较大,所以我们预计这个样本是女性。

参考文献

  1. scikit中文社区:http://sklearn.apachecn.org/cn/0.19.0/
  2. 中文维基百科:https://zh.wikipedia.org/wiki/
  3. 文本分类特征选择:https://www.cnblogs.com/june0507/p/7601001.html
  4. GitHub:https://github.com/BaiNingchao/MachineLearning-1
  5. 图书:《机器学习实战》
  6. 图书:《自然语言处理理论与实战》

关联文章

KNN模型算法研究与案例分析

决策树模型算法研究与案例分析

完整代码下载

源码请进【机器学习和自然语言QQ群:436303759】文件下载:

作者声明

本文版权归作者白宁超所有,本文原创,旨在学术和科研使用。文章同步如下:

KNN模型算法研究与案例分析

KNN模型算法研究与案例分析

(白宁超?2018年8月30日11:46:14)

导读:机器学习算法中KNN属于比较简单的典型算法,既可以做聚类又可以做分类使用。本文通过一个模拟的实际案例进行讲解。整个流程包括:采集数据、数据格式化处理、数据分析、数据归一化处理、构造算法模型、评估算法模型和算法模型的应用。(本文原创,转载必须注明出处)

1 理论介绍

什么是KNN?

???????k-近邻(kNN,k-NearestNeighbor)算法是一种基本分类与回归方法,我们这里只讨论分类问题中的 k-近邻算法。k-近邻算法的输入为实例的特征向量,对应于特征空间的点;输出为实例的类别,可以取多类。k-邻算法假设给定一个训练数据集,其中的实例类别已定。分类时,对新的实例,根据其 k 个最近邻的训练实例的类别,通过多数表决等方式进行预测。因此,k近邻算法不具有显式的学习过程即属于有监督学习范畴。k近邻算法实际上利用训练数据集对特征向量空间进行划分,并作为其分类的“模型”。k值的选择、距离度量以及分类决策规则是k近邻算法的三个基本要素。

KNN算法思想

1 计算已知类别中数据集的点与当前点的距离。[即计算所有样本点跟待分类样本之间的距离]
2 按照距离递增次序排序。[计算完样本距离进行排序]
3 选取与当前点距离最小的k个点。[选取距离样本最近的k个点]
4 确定前k个点所在类别的出现频率。[针对这k个点,统计下各个类别分别有多少个]
5 返回前k个点出现频率最高的类别作为当前点的预测分类。[k个点中某个类别最多,就将样本划归改点]

KNN工作原理

1 假设有一个带有标签的样本数据集(训练样本集),其中包含每条数据与所属分类的对应关系。
2 输入没有标签的新数据后,将新数据的每个特征与样本集中数据对应的特征进行比较。
3 计算新数据与样本数据集中每条数据的距离。
4 对求得的所有距离进行排序(从小到大,越小表示越相似)。
5 取前 k (k 一般小于等于 20 )个样本数据对应的分类标签。
6 求 k 个数据中出现次数最多的分类标签作为新数据的分类。

KNN算法流程

1 搜集数据:数据采集过程,其分为非结构化数据,半结构化数据和数据化数据。诸如:网络爬取,数据库,文件等。
2 准备数据:格式化处理,对不同类别的数据进行统一的格式化处理。诸如:将pdf,word,excel,sql等等统一转化为txt文本。
3 分析数据:主要看看数据特点,有没有缺失值,数据连续性还是离散型,进而选择不同模型。诸如:可视化数据分析
4 训练数据:不适用于KNN,但是在其他一些监督学习中会经常遇到,诸如:朴素贝叶斯分类等。
5 测试算法:评价指标,如计算错误率,准确率,召回率,F度量值等。
6 应用算法:针对完善的模型进行封装重构,然后进行实际应用。

KNN优缺点

优点:精度高、对异常值不敏感、无数据输入假定
缺点:计算复杂度高、空间复杂度好
适用数据范围:数值型和标称型

2 KNN算法实现与分析

2.1 数据准备

创建模拟数据集

描述:现在你来了一个新的任务,任务其实非常简单,就是根据吃冰淇淋和喝水的数量判断成都天气冷热程度。你现在要做的就是去成都春熙路街头采访记录一些游客吃了多少冰淇淋,又喝了几瓶水,他觉得成都天气怎么样(这里只考虑二分类问题,假设只有‘非常热’和‘一般热’)。其中特征向量包括两个分别是冰激凌数t1和喝水数t2,标签类别分别是非常热A和一般热B。

现在我们开始行动,随机采访4个游客(暂时不考虑样本数量问题),询问每个游客吃多少冰淇淋和喝多少水(两个整型的特征向量,不考虑特征重要程度),并记录下他们口中的成都天气感受(非常热A与一般热B)。然后通过采访数据训练一个KNN分类器,新的游客只需要说出特征向量自动判别成都天气冷热程度。创建模拟数据集代码如下:

'''KNN创建数据源,返回数据集和标签'''
def create_dataset():
    group = array(random.randint(0,10,size=(4,2))) # 数据集
    labels = ['A','A','B','B'] # 标签
    return group,labels

运行查看数据集的特征向量和分类标签:

'''1 KNN模拟数据分类算法'''
dataset,labels = create_dataset()
print('特征集:\n'+str(dataset))
print('标签集:\n'+str(labels))

运行结果:

特征集:
[[8 4]
 [7 1]
 [1 4]
 [3 0]]
标签集:
['A', 'A', 'B', 'B']

分析解读:

本段代码没有实际意义,只是帮助读者理解特征向量和分类标签。可以这么理解,A代表非常热,B代表一般热,属性1代表吃冰淇淋数量,属性2代表喝水的数量。那么样本数据可以解读为:

游客     冰淇淋    喝水    冷热程度        判断描述

小王      8       4       A           小王吃了8个冰淇淋喝了4瓶水,成都天气非常热

小张      7       1       A           小张吃了7个冰淇淋喝了1瓶水,成都天气非常热

小李      1       4       B           小王吃了1个冰淇淋喝了4瓶水,成都天气一般热

小赵      3       0       B           小王吃了3个冰淇淋喝了0瓶水,成都天气一般热

思考:

计算机是不能直接处理自然语言,往往需要将自然语言转化成特征向量,再通过计算机处理。比如这里不是吃喝看天气情况了,而是垃圾邮件自动识别,我们就需要对邮件转化成数值型特征向量再输入计算机进行处理。

规范文件数据集处理

如下是一个规范文件的数据集(已经经过数采集、数据格式化、数据预处理等),特征向量包括3个,样本属于一个多分类的情况。即我们通过周飞行里程数、玩游戏占比、吃冰激凌数量判断一个人的优秀程度。假设1代表普通,2代表比较优秀,3代表非常优秀。(ps:一个人一周都在飞机上度过忙碌的工作,又不太玩游戏,关键还注意饮食,说明优秀是有道理的。)

周飞行里程数(km)   周玩游戏占比(%)    周消耗冰激凌(公升)        样本分类
40920                8.326976            0.953952                3
14488                7.153469            1.673904                2
26052                1.441871            0.805124                1
...                  ...                 ...                    ...
75136                13.147394           0.428964                1
38344                1.669788            0.134296                1
72993                10.141740           1.032955                1

上面是处理好保存在txt文本的数据,计算机如何去识别并处理这些数据呢?这里我们分别提取特征向量和标签向量。数据集处理代码如下:

'''对文件进行格式处理,便于分类器可以理解'''
def file_matrix(filename):
    f = open(filename)
    arrayLines = f.readlines()
    returnMat = zeros((len(arrayLines),3))    # 数据集
    classLabelVactor = []                     # 标签集
    index = 0
    for line in arrayLines:
        listFromLine = line.strip().split('    ')    # 分析数据,空格处理
        returnMat[index,:] = listFromLine[0:3]
        classLabelVactor.append(int(listFromLine[-1]))
        index +=1
    return returnMat,classLabelVactor

代码说明:

1 zeros(Y,X):填充矩阵,需要导入NumPy包。Y向量代表样本行数,X向量代表样本特征数即列数。
2 returnMat[index,:]:遍历样本特征向量

运行查看数据集的特征向量和分类标签:

''' KNN针对文件的分类算法'''
filename = os.path.abspath(r'./datasource/datingTestSet2.txt')
dataset,labels = file_matrix(filename)
print('特征集:\n'+str(dataset))
print('标签集:\n'+str(labels))

运行结果:

特征集:
[[4.0920000e+04 8.3269760e+00 9.5395200e-01]
 [1.4488000e+04 7.1534690e+00 1.6739040e+00]
 [2.6052000e+04 1.4418710e+00 8.0512400e-01]
 ...
 [2.6575000e+04 1.0650102e+01 8.6662700e-01]
 [4.8111000e+04 9.1345280e+00 7.2804500e-01]
 [4.3757000e+04 7.8826010e+00 1.3324460e+00]]
标签集:
[3, 2, 1, 1, 1, 1, 3, 3, 1, 3, 1, 1, 2, 1, 1, 
 1, 1, 1, 2, 3, 2, 1, 2, 3, 2, 3, 2, 3, 2, 1, 
 3, 1, 3, 1, 2, 1, 1, 2, 3, 3, 1, 2, 3, 3, 3, 
 ...    
 3, 3, 1, 2, 3, 1, 3, 1, 2, 2, 1, 1, 1, 1, 1]

不规范数据集处理

这里我们只是提供一个思路。比如做文本分类算法,如何将一篇篇新闻转化为规范的数值型数据集呢。假设Z政治新闻100篇,T体育新闻100篇,Y娱乐新闻100篇,K科技新闻100篇。我们进行分类:

1 遍历所有文本转化统一的txt格式文件(2.2节会讲到)
2 对全部ZTYK文本进行分词和停用词处理。
3 统计全部样本词频尺寸(可以采用TF-IDF或者深度学习方法处理)。
4 每个样本进行词频统计

最终模拟效果如下:

样本      词1      词2      词3      词4      ...     词n      标签
p1       200       0       100      50       ...    20         Z
p2       100       0       80       40       ...    10         Z
p3       0         100     5        5        ...    200        T
p4       6         230     40       12       ...    670        T
p5       0         2       110      57       ...    234        Y
...      ...       ...     ...      ...      ...    ...        ...
pn       123       45      0        580      ...    24         K

2.2 数据格式化

数据文件转化

自然语言处理、数据挖掘、机器学习技术应用愈加广泛。针对大数据的预处理工作是一项庞杂、棘手的工作。首先数据采集和存储,尤其高质量数据采集往往不是那么简单。采集后的信息文件格式不一,诸如pdf,doc,docx,Excel,ppt等多种形式。然而最常见便是txt、pdf和word类型的文档。这里所谓格式转化主要对pdf和word文档进行文本格式转换成txt。格式一致化以后再进行后续预处理工作。具体详情请参照之前写的数据分析:基于Python的自定义文件格式转换系统一文。

文件格式化处理

这里可以采用多种方式,诸如上文提到的矩阵填充法,当然也可以采用现成的工具。比如百度的Echarts中表格数据转换工具。其支持纯数组的转换,数组+对象,地理坐标等方式,还支持json数据的转化,这对使用百度EChart可视化是非常友好的,也有助于可视化数据分析。文本数据格式效果如下图:

图2-1 文本数据表格转化

[
    ['40920    8.326976    0.953952    3'],
    ['14488    7.153469    1.673904    2'],
    ['26052    1.441871    0.805124    1'],
    ['75136    13.147394    0.428964    1'],
    ['38344    1.669788    0.134296    1'],
    ['72993    10.141740    1.032955    1'],
    ...
    ['35948    6.830792    1.213192    3'],
    ['42666    13.276369    0.543880    3'],
    ['67497    8.631577    0.749278    1'],
    ['35483    12.273169    1.508053    3'],
    ['50242    3.723498    0.831917    1'],
    ['63275    8.385879    1.669485    1'],
    ['5569    4.875435    0.728658    2'],
    ['15669    0.000000    1.250185    2'],
    ['28488    10.528555    1.304844    3'],
    ['6487    3.540265    0.822483    2'],
    ['37708    2.991551    0.833920    1']
]

2.3 数据归一化

数据归一化

机器学习、数据挖掘、自然语言处理等数据科学工作中,数据前期准备、数据预处理过程、特征提取等几个步骤比较花费时间。同时,数据预处理的效果也直接影响了后续模型能否有效的工作。然而,目前的很多研究主要集中在模型的构建、优化等方面,对数据预处理的理论研究甚少,很多数据预处理工作仍然是靠工程师的经验进行的。也不是所有数据都需要归一化,诸如

1.  数据类型一致且分布均匀。
2.  概率模型可以不做归一化,如决策树。

数据归一化优点

1. 归一化后加快了梯度下降求最优解的速度;
2. 归一化有可能提高精度;

归一化方法

1 sklearn线性归一化

# 线性函数将原始数据线性化的方法转换到[0, 1]的范围   
min_max_scaler = preprocessing.MinMaxScaler()
X_train_minmax = min_max_scaler.fit_transform(X_train)
X_test_minmax = min_max_scaler.transform(X_test)

2 标准差标准化

# 经过处理的数据符合标准正态分布,即均值为0,标准差为1,其转化函数为:
scaler = preprocessing.StandardScaler().fit(X_train)
scaler.transform(X_test)

3 非线性归一化

经常用在数据分化比较大的场景,有些数值很大,有些很小。通过一些数学函数,将原始值进行映射。该方法包括 log、指数,正切等。需要根据数据分布的情况,决定非线性函数的曲线,比如log(V, 2)还是log(V, 10)等。

线性归一化方法代码实现

'''数值归一化:特征值转化为0-1之间:newValue = (oldValue-min)/(max-min)'''
def norm_dataset(dataset):
    minVals = dataset.min(0)  # 参数0是取得列表中的最小值,而不是行中最小值
    maxVals = dataset.max(0)
    ranges = maxVals - minVals
    normdataset = zeros(shape(dataset)) # 生成原矩阵一样大小的0矩阵

    m = dataset.shape[0]
    # tile:复制同样大小的矩阵
    molecular = dataset - tile(minVals,(m,1))  # 分子: (oldValue-min)
    Denominator = tile(ranges,(m,1))           # 分母:(max-min)
    normdataset = molecular/Denominator     # 归一化结果。

    return normdataset,ranges,minVals

数据归一化前:

归一化的数据结果:
[[4.0920000e+04 8.3269760e+00 9.5395200e-01]
 [1.4488000e+04 7.1534690e+00 1.6739040e+00]
 [2.6052000e+04 1.4418710e+00 8.0512400e-01]
 ...
 [2.6575000e+04 1.0650102e+01 8.6662700e-01]
 [4.8111000e+04 9.1345280e+00 7.2804500e-01]
 [4.3757000e+04 7.8826010e+00 1.3324460e+00]]

展开第一条信息“40920 8.326976 0.953952 3”,其中里程40000多,而公升数才0.9.两者根本不在同一个数量级上面,也就是说,如果特征属性相同的情况下,公升数即使变动100倍对里程数的影响也微乎其微。而里程数轻微变化就直接影响公升数的结果。所以我们将其放在同一尺度下进行处理,也就是本文采用的线性缩放方,数据归一化后结果如下:

归一化的数据结果:
[[0.44832535 0.39805139 0.56233353]
 [0.15873259 0.34195467 0.98724416]
 [0.28542943 0.06892523 0.47449629]
 ...
 [0.29115949 0.50910294 0.51079493]
 [0.52711097 0.43665451 0.4290048 ]
 [0.47940793 0.3768091  0.78571804]]

分析:

经过上述归一化处理后,各个特征指标都是0-1这样一个范畴中进行比较。当然实际工作中不同特征的权重不同,这个可以通过增加权重方法处理即可,本文不在进行深入讨论。

2.4 数据分析

基于matplotlib的可视化分析

我们对数据处理后,很不容易进行数据分析。毕竟密密麻麻的数字略显冰冷无趣。我们可以将其可视化展示出来,进而查看数据稀疏程度,离散程度等等。我们查看'玩游戏所耗时间百分比','每周消耗在冰淇淋的公升数'两个属性的散点图,实现代码如下:

'''
散列表分析数据:
dataset:数据集
datingLabels:标签集
Title:列表,标题、横坐标标题、纵坐标标题。
'''
def analyze_data_plot(dataset,datingLabels,Title):
    fig = plt.figure()
    # 将画布划分为1行1列1块
    ax = fig.add_subplot(111)
    ax.scatter(dataset[:,1],dataset[:,2],15.0*array(datingLabels),15.0*array(datingLabels))

     # 设置散点图标题和横纵坐标标题
    plt.title(Title[0],fontsize=25,fontname='宋体',fontproperties=myfont)
    plt.xlabel(Title[1],fontsize=15,fontname='宋体',fontproperties=myfont)
    plt.ylabel(Title[2],fontsize=15,fontname='宋体',fontproperties=myfont)

    # 设置刻度标记大小,axis='both'参数影响横纵坐标,labelsize刻度大小
    plt.tick_params(axis='both',which='major',labelsize=10)

    # 设置每个坐标轴取值范围
    # plt.axis([-1,25,-1,2.0])

    # 截图保存图片
    # plt.savefig('datasets_plot.png',bbox_inches='tight')

    # 显示图形
    plt.show()

这里注意一个问题,横纵坐标是乱码显示,解决这个问题,添加如下代码:

#加入中文显示
import  matplotlib.font_manager as fm
# 解决中文乱码,本案例使用宋体字
myfont=fm.FontProperties(fname=r"C:\\Windows\\Fonts\\simsun.ttc")

调用可视化数据分析方法如下:

''' 文件数据图形化分析数据 '''
dataset,labels = file_matrix(filename)
noredataset = norm_dataset(dataset)[0] # 数据归一化
title = ['约会数据游戏和饮食散列点','玩游戏所耗时间百分比','每周消耗在冰淇淋的公升数']
visualplot.analyze_data_plot(noredataset,labels,title)

游戏占比与冰淇淋公升数关系散点图可视化:

图2-2 游戏占比与冰淇淋公升数关系散点图

折线图代码实现如下:

'''折线图'''
def line_chart(xvalues,yvalues):
    # 绘制折线图,c颜色设置,alpha透明度
    plt.plot(xvalues,yvalues,linewidth=0.5,alpha=0.5,c='red') # num_squares数据值,linewidth设置线条粗细

    # 设置折线图标题和横纵坐标标题
    plt.title("Python绘制折线图",fontsize=30,fontname='宋体',fontproperties=myfont)
    plt.xlabel('横坐标',fontsize=20,fontname='宋体',fontproperties=myfont)
    plt.ylabel('纵坐标',fontsize=20,fontname='宋体',fontproperties=myfont)

    # 设置刻度标记大小,axis='both'参数影响横纵坐标,labelsize刻度大小
    plt.tick_params(axis='both',labelsize=14)

    # 显示图形
    plt.show()

游戏占比与冰淇淋公升数关系折线图可视化:(此处折线图明显不合适,只是突出另一种分析方式。)

图2-3 游戏占比与冰淇淋公升数关系折线图

扩展:

更多matplotlib可视化实现效果图参考文章 70个注意的Python小Notes:完整的matplotlib可视化

基于Echart的可视化分析

我们上面采用的matplotlib可视化效果,采用该方式主要是结合python数据分析绑定比较方便。有些时候我们为了取得更加漂亮的可视化效果,可以选择百度echart进行分析,百度Echart使用简单且文档规范容易上手。我们对原数据进行分析并转化为json代码:

'''array数据转化json'''
def norm_Json(dataset):
    noredataset = norm_dataset(dataset)[0] # 数据归一化
    number1 = np.around(noredataset[:,1], decimals=4) # 获取数据集第二列
    number2 = np.around(noredataset[:,2], decimals=4) # 获取数据集第三列
    returnMat=zeros((dataset.shape[0],2))             # 二维矩阵
    returnMat[:,0] = number1
    returnMat[:,1] = number2

    file_path = os.path.abspath(r"./datasource/test.json")
    json.dump(returnMat.tolist(), codecs.open(file_path, 'w', encoding='utf-8'), separators=(',', ':'), sort_keys=True, indent=4)

生成json数据保存在指定文件中,打开文件查看数据如下:

[
    [
        0.3981,
        0.5623
    ],
    [
        0.342,
        0.9872
    ],
    [
        0.0689,
        0.4745
    ],
    [
        0.6285,
        0.2525
    ]
    ...
    [
        0.4367,
        0.429
    ],
    [
        0.3768,
        0.7857
    ]
]

从百度Echart实例中选择一种散点图并绑定json文件,其html代码如下:

<!DOCTYPE html>
<html>
<head>
    <meta charset="UTF-8">
    <title>图案例</title>
    <script src="https://cdn.bootcss.com/jquery/2.2.0/jquery.min.js"></script>
    <script type="text/javascript" src="./js/echarts.js"></script>
</head>
<body>
    <div id="chartmain" style="width:800px; height: 400px; margin:auto; ">
    </div>

    <script type="text/javascript">
        //初始化echarts实例
        var myChart = echarts.init(document.getElementById('chartmain'));
        $.getJSON('game-food.json', function (data) {
        var option = {
            title: {
                text: '玩游戏时间占比和饮食数据描述',
                left: 'center',
                top: 0
            },
            visualMap: {
                min: 15202,
                max: 159980,
                dimension: 1,
                orient: 'vertical',
                right: 10,
                top: 'center',
                text: ['优秀', '一般'],
                calculable: true,
                inRange: {
                    color: ['#f2c31a', '#24b7f2']
                }
            },
            tooltip: {
                trigger: 'item',
                axisPointer: {
                    type: 'cross'
                }
            },
            xAxis: [{
                type: 'value'
            }],
            yAxis: [{
                type: 'value'
            }],
            series: [{
                name: 'price-area',
                type: 'scatter',
                symbolSize: 5,
                data: data
            }]
        };
        myChart.setOption(option);
});
    </script>
</body>
</html>

json文件读取需要在web运行环境中,单纯的运行效果如下图所示:

 

图2-4 游戏占比与冰淇淋公升数Echarts关系散点图

数据转化工具

本文采用自己构建的方式进行json文件生成,此外我们也可以采用现有的数据转化工具进行处理。比如百度的表格数据转化工具(2.2节已经介绍了)。

另一个便是在线json验证格式工具:http://www.bejson.com/

2.5 KNN分类器实现

通过数据分析,我们查看数据样本是否偏态分别,数据规模情况等等。针对性进行数据预处理后,编写具体算法模型。本文主要是KNN分类器,其代码如下:

''' 构造KNN分类器
    vecX:输入向量,待测数据
    filename: 特征集文件路径
    isnorm:是否进行归一化处理
    k:k值的选择,默认选择3
'''
def knn_classifier(vecX,dataset,labels,isnorm='Y',k=3):
    # 距离计算(方法1)
    if isnorm == 'Y':
        normMat,ranges,minVals = norm_dataset(dataset)     # 对数据进行归一化处理
        normvecX = norm_dataset(vecX)
    else:
        normMat = dataset
        normvecX = vecX

    m = normMat.shape[0]
    # tile方法是在列向量vecX,datasetSize次,行向量vecX1次叠加
    diffMat = tile(normvecX,(m,1)) - normMat
    sqDiffMat = diffMat ** 2
    sqDistances = sqDiffMat.sum(axis=1)   # axis=0 是列相加,axis=1是行相加
    distances = sqDistances**0.5
    # print('vecX向量到数据集各点距离:\n'+str(distances))

    sortedDistIndexs = distances.argsort(axis=0)  # 距离排序,升序
    # print(sortedDistIndicies)

    classCount = {}   # 统计前k个类别出现频率
    for i in range(k):
        votelabel = labels[sortedDistIndexs[i]]
        classCount[votelabel] = classCount.get(votelabel,0) + 1 #统计键值
    # 类别频率出现最高的点,itemgetter(0)按照key排序,itemgetter(1)按照value排序
    sortedClassCount = sorted(classCount.items(),key=operator.itemgetter(1),reverse=True)
    print(str(vecX)+'KNN的投票决策结果:\n'+str(sortedClassCount[0][0]))
    return sortedClassCount[0][0]

3 KNN算法模型评估

3.1 评价指标介绍

基本知识

混淆矩阵:正元组和负元组的合计

图3-1 混淆矩阵表

评估度量:(其中P:正样本数 N:负样本数 TP:真正例 TN:真负例 FP:假正例 FN:假负例)

图3-2 评估信息度量表

注意:学习器的准确率最好在检验集上估计,检验集的由训练集模型时未使用的含有标记的元组组成数据。

各参数描述如下:

TP(真正例/真阳性):是指被学习器正确学习的正元组,令TP为真正例的个数。

TN(真负例/真阴性):是指被学习器正确学习的负元组,令TN为真负例的个数。

FP(假正例/假阳性):是被错误的标记为正元组的负元组。令FP为假正例的个数。

FN(假负例/假阴性):是被错误的标记为负元组的正元组。令FN为假负例的个数。

准确率:正确识别的元组所占的比例。

评价指标优点

一般采用精确率和召回率作为度量的方法具有以下几个优点:

(1) 准确率数值对于小数据不是特别敏感,而精确率和召回率对于这样数据比较敏感。
(2) 在相同实验环境下,F度量这种倾向和我们直观感觉是一致的,我们对目标事件很敏感,甚至返回一些垃圾数据也在所不惜。
(3) 通过精确率和找回来衡量目标事件和垃圾事件的差异。

模型评估拓展

参见《自然语言处理理论与实战》一书第13章模型评估

3.2 评估算法模型实现

本文只是对错误率进行评估,其也是knn分类器核心指标,实现代码如下:

'''测试评估算法模型'''
def test_knn_perfor(filename):
    hoRatio = 0.1
    dataset,label = file_matrix(filename)              # 获取训练数据和标签
    normMat,ranges,minVals = norm_dataset(dataset)     # 对数据进行归一化处理
    m = normMat.shape[0]
    numTestVecs = int(m*hoRatio)                       # 10%的测试数据条数

    errorCount = 0.0                                   # 统计错误分类数
    for i in range(numTestVecs):
        classifierResult = knn_classifier(normMat[i,:],normMat[numTestVecs:m,:],label[numTestVecs:m],3)          # 此处分类器可以替换不同分类模型
        print('分类结果:'+str(classifierResult)+'\t\t准确结果:'+str(label[i]))

        if classifierResult != label[i]:
            errorCount += 1.0
        Global.all_FileNum += 1
    print('总的错误率为:'+str(errorCount/float(numTestVecs))+"\n总测试数据量: "+str(Global.all_FileNum))

运行效果如下:

[0.44832535 0.39805139 0.56233353]KNN的投票决策结果:
分类结果:3      准确结果:3
[0.15873259 0.34195467 0.98724416]KNN的投票决策结果:
分类结果:2      准确结果:2
...
分类结果:3      准确结果:3
[0.19385799 0.30474213 0.01919426]KNN的投票决策结果:
分类结果:2      准确结果:2
[0.24463971 0.10813023 0.60259472]KNN的投票决策结果:
分类结果:1      准确结果:1
[0.51022756 0.27138082 0.41804137]KNN的投票决策结果:
分类结果:3      准确结果:1

总的错误率为:0.05
总测试数据量: 100
耗时:0.0300 s

评估结果分析:

本文采用封闭评估的方法,前100条数据作为测试集,后900条数据作为训练集。如上结果最后一条信息表明knn分类器的结果是3,而标准结果是1.knn分类存在错误。将所有错误占比分析出来即错误率。本文错误率5%,即准确率95%.

读者可以选取200:800、300:700等等数据进行测试查看错误率。

4 KNN算法模型的实际应用

knn分类器应用

经过如上的改进最终形成实际应用的算法模型API开发给外部程序使用,调用knn算法代码如下:

'''调用可用算法'''
def show_classifyPerson(filename):
    resultlist = ['不喜欢','还可以','特别喜欢']
    ffMiles = float(input('每年飞行的历程多少公里?\n'))
    percentTats = float(input('玩游戏时间占百分比多少?\n')) # [751,13,0.4][40920 ,8.326976,0.953952]
    iceCream = float(input('每周消费冰淇淋多少公升?\n'))

    dataset,labels = file_matrix(filename) # 数据格式化处理
    inArr = array([ffMiles,percentTats,iceCream])

    classifierResult = knn_classifier(inArr,dataset,labels,3) # 数据归一化并进行分类
    print('预测的约会结果是:'+resultlist[classifierResult-1])

运行结果如下:

每年飞行的历程多少公里?
10000
玩游戏时间占百分比多少?
10
每周消费冰淇淋多少公升?
0.5
KNN的投票决策结果:
2
预测的约会结果是:还可以

展望

我们还可以采用knn分类器进行实际应用,比如新闻分类系统。大致思路如下:

1 采集数据:选用复旦大学的文本分类新闻语料
2 准备数据:数据格式化、分词、停用词处理等
3 分析数据:看看数据特点,有没有缺失值,数据连续性还是离散型,进而选择不同模型。诸如:可视化数据分析
4 数据转化:采用IF-IDF或者神经网络的方法对词频进行处理,最终转化为机器可以处理的数值型矩阵。
5 构建模型:KNN新闻分类器模型构建。
6 测试算法:评价指标,如计算错误率,准确率,召回率,F度量值等。
7 应用算法:针对完善的模型进行封装重构,然后进行实际的新闻分类应用。

5 参考文献

  1. 归一化学习:https://blog.csdn.net/hyq3235356/article/details/78472307
  2. 归一化方法:https://blog.csdn.net/zxd1754771465/article/details/73558103
  3. 百度Echart转化工具:http://echarts.baidu.com/spreadsheet.html
  4. 在线json验证格式工具:http://www.bejson.com/
  5. 模型评估:http://www.cnblogs.com/baiboy/p/mxpg2.html

完整代码下载

源码请进【机器学习和自然语言QQ群:436303759】文件下载:

作者声明

本文版权归作者和我爱自然语言处理52NLP共有,本文原创,旨在学术和科研使用。文章同步如下:

决策树模型算法研究与案例分析

决策树模型算法研究与案例分析

(白宁超?2018年8月30日11:46:14)

导读:决策树算法是一种基本的分类与回归方法,是最经常使用的算法之一。决策树模型呈树形结构,在分类问题中,表示基于特征对实例进行分类的过程。它可以认为是基于规则的集合。本文首先介绍决策树定义、工作原理、算法流程、优缺点等,然后结合案例进行分析。(本文原创,转载必须注明出处)

理论介绍

什么是决策树

  • 维基百科:决策树(Decision Tree)是一个预测模型;他代表的是对象属性与对象值之间的一种映射关系。树中每个节点表示某个对象,而每个分叉路径则代表某个可能的属性值,而每个叶节点则对应从根节点到该叶节点所经历的路径所表示的对象的值。数据挖掘中决策树是一种经常要用到的技术,可以用于分析数据,同样也可以用来作预测。从数据产生决策树的机器学习技术叫做决策树学习,通俗说就是决策树。
  • 分类决策树模型是一种描述对实例进行分类的树形结构。决策树由结点(node)和有向边(directed edge)组成。结点有两种类型:内部结点(internal node)和叶结点(leaf node)。内部结点表示一个特征或属性(features),叶结点表示一个类(labels)。

用决策树对需要测试的实例进行分类:从根节点开始,对实例的某一特征进行测试,根据测试结果,将实例分配到其子结点;这时,每一个子结点对应着该特征的一个取值。如此递归地对实例进行测试并分配,直至达到叶结点。最后将实例分配到叶结点的类中。

什么是信息熵和信息增益

  • 熵(entropy): 熵指的是体系的混乱的程度,在不同的学科中也有引申出的更为具体的定义,是各领域十分重要的参量。
  • 信息论(information theory)中的熵(香农熵): 是一种信息的度量方式,表示信息的混乱程度,也就是说:信息越有序,信息熵越低。例如:火柴有序放在火柴盒里,熵值很低,相反,熵值很高。
  • 信息增益(information gain): 在划分数据集前后信息发生的变化称为信息增益,信息增益越大,确定性越强。

决策树工作原理

'''
决策树工作原理:基于迭代的思想。
'''
def createBranch():
    检测数据集中的所有数据的分类标签是否相同:
        If so return 类标签
        Else:
            寻找划分数据集的最好特征(划分之后信息熵最小,也就是信息增益最大的特征)
            划分数据集
            创建分支节点
                for 每个划分的子集
                    调用函数 createBranch (创建分支的函数)并增加返回结果到分支节点中
            return 分支节点

决策树算法流程

收集数据:可以使用任何方法。
准备数据:树构造算法 (这里使用的是ID3算法,只适用于标称型数据,这就是为什么数值型数据必须离散化。 还有其他的树构造算法,比如CART)
分析数据:可以使用任何方法,构造树完成之后,我们应该检查图形是否符合预期。
训练算法:构造树的数据结构。
测试算法:使用训练好的树计算错误率。
使用算法:此步骤可以适用于任何监督学习任务,而使用决策树可以更好地理解数据的内在含义。

决策树优缺点

相对于其他数据挖掘算法,决策树在以下几个方面拥有优势:

1 决策树易于理解和实现.人们在通过解释后都有能力去理解决策树所表达的意义。
2 对于决策树,数据的准备往往是简单或者是不必要的.其他的技术往往要求先把数据一般化,比如去掉多余的或者空白的属性。
3 能够同时处理数据型和常规型属性。其他的技术往往要求数据属性的单一。
4 是一个白盒模型如果给定一个观察的模型,那么根据所产生的决策树很容易推出相应的逻辑表达式。
5 易于通过静态测试来对模型进行评测。表示有可能测量该模型的可信度。
6 在相对短的时间内能够对大型数据源做出可行且效果良好的结果。
7 计算复杂度不高,输出结果易于理解,数据有缺失也能跑,可以处理不相关特征。

缺点:

1 容易过拟合。
2 对于那些各类别样本数量不一致的数据,在决策树当中信息增益的结果偏向于那些具有更多数值的特征。

适用数据类型:数值型和标称型。

1 数值型:数值型目标变量则可以从无限的数值集合中取值,如0.100,42.001等 (数值型目标变量主要用于回归分析)
2 标称型:标称型目标变量的结果只在有限目标集中取值,如真与假(标称型目标变量主要用于分类)

案例描述:加深决策树理解

案例描述

小王是一家著名高尔夫俱乐部的经理。但是他被雇员数量问题搞得心情十分不好。某些天好像所有人都来玩高尔夫,以至于所有员工都忙的团团转还是应付不过来,而有些天不知道什么原因却一个人也不来,俱乐部为雇员数量浪费了不少资金。小王的目的是通过下周天气预报寻找什么时候人们会打高尔夫,以适时调整雇员数量。因此首先他必须了解人们决定是否打球的原因。

数据采集

在2周时间内我们得到以下记录:

天气状况有晴,云和雨;气温用华氏温度表示;相对湿度用百分比;还有有无风。当然还有顾客是不是在这些日子光顾俱乐部。最终他得到了14行5列的数据表格。

构建决策树

决策树模型就被建起来用于解决问题。

结果分析

决策树是一个有向无环图。根结点代表所有数据。分类树算法可以通过变量outlook,找出最好地解释非独立变量play(打高尔夫的人)的方法。变量outlook的范畴被划分为以下三个组:晴天,多云天和雨天。

我们得出第一个结论:如果天气是多云,人们总是选择玩高尔夫,而只有少数很着迷的甚至在雨天也会玩。

接下来我们把晴天组的分为两部分,我们发现顾客不喜欢湿度高于70%的天气。最终我们还发现,如果雨天还有风的话,就不会有人打了。

这就通过分类树给出了一个解决方案。小王(老板)在晴天,潮湿的天气或者刮风的雨天解雇了大部分员工,因为这种天气不会有人打高尔夫。而其他的天气会有很多人打高尔夫,因此可以雇用一些临时员工来工作。

决策树算法实现与分析

案例: 判定鱼类和非鱼类

案例需求描述

我们采集海洋生物数据信息,选择其中5条如下表所示,从诸多特征中选择2个最主要特征,以及判定是否属于鱼类(此处我们选择二分类法即只考虑鱼类和非鱼类)。
根据这些信息如何创建一个决策树进行分类并可视化展示?

收集数据

部分数据采集信息

序号 不浮出水面是否可以生存 是否有脚蹼 属于鱼类
1
2
3
4
5

我们将自然语言数据转化为计算机输入数据,代码实现如下:

'''创建数据集,返回数据集和标签'''
def createDataSet():
    dataSet = [[1, 1, 'yes'],
               [1, 1, 'yes'],
               [1, 0, 'no'],
               [0, 1, 'no'],
               [0, 1, 'no']]
    labels = ['no surfacing', 'flippers']
    return dataSet, labels

运行查看数据集的特征向量和分类标签:

# 1 打印数据集和标签
dataset,label=createDataSet()
print(dataset)
print(label)

运行结果:

[[1, 1, 'yes'], [1, 1, 'yes'], [1, 0, 'no'], [0, 1, 'no'], [0, 1, 'no']]
['no surfacing', 'flippers']

准备数据

由于我们输入的数据已经是数据预处理后的数据,这一步不需要进行。

分析数据

我们得到数据之后,到底是按照第一个特征即(不浮出水面是否可以生存)还是第二个特征即(是否有脚蹼)进行数据划分呢?这里面就需要找到一种量化的方法判断特征的选择。在介绍具体数据划分方法之前,我们首先明白划分数据集的最大原则是:将无序的数据变得更加有序

1948 年,香农引入信息熵,将其定义为离散随机事件的出现概率。一个系统越有序,信息熵就越低;反之,一个系统越混乱,信息熵就越高。所以说,信息熵可以被认为是系统有序化程度的一个度量。

这里就要用的信息熵的概念,熵越高表示混合数据越多,度量数据集无序程度。我们看下信息熵的数学描述(具体请自行查找熵相关知识):

计算数据集的香农熵(信息期望值)

根据公式比较容易理解的实现方法1如下:

'''计算数据集的香农熵(信息期望值):熵越高表示混合数据越多,度量数据集无序程度'''
def calcShannonEnt(dataSet):
    numEntries = len(dataSet) # 计算数据集中实例总数
    labelCounts = {} # 创建字典,计算分类标签label出现的次数
    for featVec in dataSet:
        currentLabel = featVec[-1] # 记录当前实例的标签
        if currentLabel not in labelCounts.keys():# 为所有可能的分类创建字典
            labelCounts[currentLabel] = 0
        labelCounts[currentLabel] += 1
        # print(featVec, labelCounts) # 打印特征向量和字典的键值对

    # 对于label标签的占比,求出label标签的香农熵
    shannonEnt = 0.0
    for key in labelCounts:
        prob = float(labelCounts[key])/numEntries # 计算类别出现的概率。
        shannonEnt -= prob * log(prob, 2) # 计算香农熵,以 2 为底求对数
    print(Decimal(shannonEnt).quantize(Decimal('0.00000')))
    return shannonEnt

更高级的实现方法2如下:

'''计算数据集的香农熵(信息期望值):熵越高表示混合数据越多,度量数据集无序程度'''
def calcShannonEnt(dataSet):
    # 需要对 list 中的大量计数时,可以直接使用Counter,不用新建字典来计数
    label_count = Counter(data[-1] for data in dataSet) # 统计标签出现的次数
    probs = [p[1] / len(dataSet) for p in label_count.items()] # 计算概率
    shannonEnt = sum([-p * log(p, 2) for p in probs]) # 计算香农熵
    print(Decimal(shannonEnt).quantize(Decimal('0.00000')))
    return shannonEnt

调用运行如下:

# 2 计算数据集的熵
calcShannonEnt(dataset)

按照给定的特征划分数据集

我们根据信息熵度量出来的特征,进行数据集划分方法1如下:

'''划分数据集:按照特征划分'''
def splitDataSet(dataSet, index, value):
    retDataSet = []
    for featVec in dataSet:
        if featVec[index] == value:# 判断index列的值是否为value
            reducedFeatVec = featVec[:index] # [:index]表示取前index个特征
            reducedFeatVec.extend(featVec[index+1:]) # 取接下来的数据
            retDataSet.append(reducedFeatVec)
    print(retDataSet)
    return retDataSet

我们根据信息熵度量出来的特征,进行数据集划分方法2如下:

'''划分数据集:按照特征划分'''
def splitDataSet(dataSet, index, value):
    retDataSet = [data for data in dataSet for i, v in enumerate(data) if i == index and v == value]
    print(retDataSet)
    return retDataSet

指定特征的数据集划分方法调用

#3 划分数据集
splitDataSet(dataset,0,1)

运行结果如下:

[[1, 1, 'yes'], [1, 1, 'yes'], [1, 0, 'no']]选择最好的数据集划分方式

选择最好的数据集划分方式:特征选择,划分数据集、计算最好的划分数据集特征,方法1如下:

'''
注意:一是数据集列表元素具备相同数据长度,二是最后一列是标签列
'''
def chooseBestFeatureToSplit(dataSet):
    numFeatures = len(dataSet[0]) - 1 # 特征总个数, 最后一列是标签
    baseEntropy = calcShannonEnt(dataSet) # 计算数据集的信息熵
    bestInfoGain, bestFeature = 0.0, -1 # 最优的信息增益值, 和最优的Featurn编号
    for i in range(numFeatures):
        featList = [example[i] for example in dataSet] # 获取各实例第i+1个特征
        uniqueVals = set(featList) # 获取去重后的集合
        newEntropy = 0.0  # 创建一个新的信息熵
        for value in uniqueVals:
            subDataSet = splitDataSet(dataSet, i, value)
            prob = len(subDataSet)/float(len(dataSet))
            newEntropy += prob * calcShannonEnt(subDataSet)
        # 比较所有特征中的信息增益,返回最好特征划分的索引值。
        infoGain = baseEntropy - newEntropy
        print('infoGain=', infoGain, 'bestFeature=', i, baseEntropy, newEntropy)
        if (infoGain > bestInfoGain):
            bestInfoGain = infoGain
            bestFeature = i
    # print(bestFeature)
    return bestFeature

选择最好的数据集划分方式:特征选择,划分数据集、计算最好的划分数据集特征,方法2如下:

'''
注意:一是数据集列表元素具备相同数据长度,二是最后一列是标签列
'''
def chooseBestFeatureToSplit(dataSet):
    base_entropy = calcShannonEnt(dataSet) # 计算初始香农熵
    best_info_gain = 0
    best_feature = -1
    # 遍历每一个特征
    for i in range(len(dataSet[0]) - 1):
        # 对当前特征进行统计
        feature_count = Counter([data[i] for data in dataSet])
        # 计算分割后的香农熵
        new_entropy = sum(feature[1] / float(len(dataSet)) * calcShannonEnt(splitDataSet(dataSet, i, feature[0])) for feature in feature_count.items())
        # 更新值
        info_gain = base_entropy - new_entropy
        # print('No. {0} feature info gain is {1:.3f}'.format(i, info_gain))
        if info_gain > best_info_gain:
            best_info_gain = info_gain
            best_feature = i
    # print(best_feature)
    return best_feature

选择最好的数据集划分方法调用

# 4 选择最好的数据集划分方式
chooseBestFeatureToSplit(dataset))

运行结果如下:

infoGain= 0.4199730940219749 bestFeature= 0 0.9709505944546686 0.5509775004326937
infoGain= 0.17095059445466854 bestFeature= 1 0.9709505944546686 0.8
选择:0

训练算法:构造树的数据结构

创建树的函数代码如下:

'''创建决策树'''
def createTree(dataSet, labels):
    classList = [example[-1] for example in dataSet]
    # 如果数据集的最后一列的第一个值出现的次数=整个集合的数量,也就说只有一个类别,就只直接返回结果就行
    # 第一个停止条件:所有的类标签完全相同,则直接返回该类标签。
    # count() 函数是统计括号中的值在list中出现的次数
    if classList.count(classList[0]) == len(classList):
        return classList[0]
    # 如果数据集只有1列,那么最初出现label次数最多的一类,作为结果
    # 第二个停止条件:使用完了所有特征,仍然不能将数据集划分成仅包含唯一类别的分组。
    if len(dataSet[0]) == 1:
        return majorityCnt(classList)

    # 选择最优的列,得到最优列对应的label含义
    bestFeat = chooseBestFeatureToSplit(dataSet)
    # 获取label的名称
    bestFeatLabel = labels[bestFeat]
    # 初始化myTree
    myTree = {bestFeatLabel: {}}
    # 所以这行代码导致函数外的同名变量被删除了元素,造成例句无法执行,提示'no surfacing' is not in list
    # del(labels[bestFeat])
    # 取出最优列,然后它的branch做分类
    featValues = [example[bestFeat] for example in dataSet]
    uniqueVals = set(featValues)
    for value in uniqueVals:
        # 求出剩余的标签label
        subLabels = labels[:]
        # 遍历当前选择特征包含的所有属性值,在每个数据集划分上递归调用函数createTree()
        myTree[bestFeatLabel][value] = createTree(splitDataSet(dataSet, bestFeat, value), subLabels)
        # print('myTree', value, myTree)
    print(myTree)
    return myTree

其中多数表决方法决定叶子节点的分类实现如下:

'''多数表决方法决定叶子节点的分类:选择出现次数最多的一个结果'''
def majorityCnt(classList):
    # -----------多数表决实现的方式一--------------
    # classCount = {}   # 标签字典,用于统计类别频率
    # for vote in classList: # classList标签的列表集合
    #     if vote not in classCount.keys():
    #         classCount[vote] = 0
    #     classCount[vote] += 1
    # # 取出结果(yes/no),即出现次数最多的结果
    # sortedClassCount = sorted(classCount.items(), key=operator.itemgetter(1), reverse=True)
    # print('sortedClassCount:', sortedClassCount)
    # return sortedClassCount[0][0]


    # -----------多数表决实现的方式二-----------------
    major_label = Counter(classList).most_common(1)[0]
    print('sortedClassCount:', major_label[0])
    return major_label[0]

调用方法:

# 6创建决策树
createTree(dataset, label)

运行结果:

{'no surfacing': {0: 'no', 1: {'flippers': {0: 'no', 1: 'yes'}}}}

结果分析:
此时,每次生成决策树数据都需要大量的计算,并且耗时,最好是每次直接调用生成结果。这里就需要使用Python模块pickle序列化对象,其存储决策树读取决策树代码实现如下:

'''使用pickle模块存储决策树'''
def storeTree(inputTree, filename):
    import pickle
    # -------------- 第一种方法 --------------
    fw = open(filename, 'wb')
    pickle.dump(inputTree, fw)
    fw.close()

    # -------------- 第二种方法 --------------
    with open(filename, 'wb') as fw:
        pickle.dump(inputTree, fw)


def grabTree(filename):
    import pickle
    fr = open(filename,'rb')
    return pickle.load(fr)

测试算法:使用决策树执行分类

用决策树进行鱼类属于分类实现如下:

'''用决策树分类函数'''
def classify(inputTree, featLabels, testVec):
    firstStr = list(inputTree.keys())[0] # 获取tree的根节点对于的key值
    secondDict = inputTree[firstStr]  # 通过key得到根节点对应的value
    # 判断根节点名称获取根节点在label中的先后顺序,这样就知道输入的testVec怎么开始对照树来做分类
    featIndex = featLabels.index(firstStr)
    for key in secondDict.keys():
        if testVec[featIndex] == key:
            if type(secondDict[key]).__name__ == 'dict':
                classLabel = classify(secondDict[key], featLabels, testVec)
            else:
                classLabel = secondDict[key]
    print(classLabel)
    return classLabel

调用方法:

# 7 用决策树分类函数
myTree = treePlotter.retrieveTree(0)
# print(myTree)
classify(myTree,label,[1,0])

运行结果:

分类结果:no surfacing

决策树分类器实现

使用算法此步骤可以适用于任何监督学习任务,而使用决策树可以更好地理解数据的内在含义。

'''决策树判断是否是鱼'''
def fishTest():
    # 1.创建数据和结果标签
    myDat, labels = createDataSet()

    # 计算label分类标签的香农熵
    calcShannonEnt(myDat)

    # 求第0列 为 1/0的列的数据集【排除第0列】
    print('1---', splitDataSet(myDat, 0, 1))
    print('0---', splitDataSet(myDat, 0, 0))

    # 计算最好的信息增益的列
    print(chooseBestFeatureToSplit(myDat))

    import copy
    myTree = createTree(myDat, copy.deepcopy(labels))
    print(myTree)
    # [1, 1]表示要取的分支上的节点位置,对应的结果值
    print(classify(myTree, labels, [1, 1]))

    # 画图可视化展现
    treePlotter.createPlot(myTree)

调用决策树分类方法:

# 9 决策树判断是否是鱼
fishTest()

运行结果如下:

1--- [[1, 1, 'yes'], [1, 1, 'yes'], [1, 0, 'no']]
0--- [[0, 1, 'no'], [0, 1, 'no']]
{'no surfacing': {0: 'no', 1: {'flippers': {0: 'no', 1: 'yes'}}}}
yes

可视化结果

决策树实际应用:预测隐形眼镜的测试代码

项目概述

隐形眼镜类型包括硬材质、软材质以及不适合佩戴隐形眼镜。我们需要使用决策树预测患者需要佩戴的隐形眼镜类型。

开发流程

收集数据: 提供的文本文件。
解析数据: 解析 tab 键分隔的数据行
分析数据: 快速检查数据,确保正确地解析数据内容,使用 createPlot() 函数绘制最终的树形图。
训练算法: 使用 createTree() 函数。
测试算法: 编写测试函数验证决策树可以正确分类给定的数据实例。
使用算法: 存储树的数据结构,以便下次使用时无需重新构造树。
收集数据:提供的文本文件

数据读取

文本文件数据格式如下:

young    myope    no    reduced    no lenses
young    myope    no    normal    soft
young    myope    yes    reduced    no lenses
young    myope    yes    normal    hard
young    hyper    no    reduced    no lenses
young    hyper    no    normal    soft
young    hyper    yes    reduced    no lenses
young    hyper    yes    normal    hard
pre    myope    no    reduced    no lenses
pre    myope    no    normal    soft
pre    myope    yes    reduced    no lenses
pre    myope    yes    normal    hard
pre    hyper    no    reduced    no lenses
pre    hyper    no    normal    soft
pre    hyper    yes    reduced    no lenses
pre    hyper    yes    normal    no lenses
presbyopic    myope    no    reduced    no lenses
presbyopic    myope    no    normal    no lenses
presbyopic    myope    yes    reduced    no lenses
presbyopic    myope    yes    normal    hard
presbyopic    hyper    no    reduced    no lenses
presbyopic    hyper    no    normal    soft
presbyopic    hyper    yes    reduced    no lenses
presbyopic    hyper    yes    normal    no lenses

代码实现: 编写测试函数验证决策树可以正确分类给定的数据实例。

'''预测隐形眼镜的测试代码'''
def ContactLensesTest():
    # 加载隐形眼镜相关的 文本文件 数据
    fr = open('lenses.txt')
    # 解析数据,获得 features 数据
    lenses = [inst.strip().split('    ') for inst in fr.readlines()]
    # 得到数据的对应的 Labels
    lensesLabels = ['age', 'prescript', 'astigmatic', 'tearRate']
    # 使用上面的创建决策树的代码,构造预测隐形眼镜的决策树
    lensesTree = createTree(lenses, lensesLabels)
    print(lensesTree)
    # 画图可视化展现
    treePlotter.createPlot(lensesTree)

运行结果

调用方法

# 10 预测隐形眼镜类型
ContactLensesTest()

运行结果

{'tearRate': {'reduced': 'no lenses', 'normal': {'astigmatic': {'no': {'age': {'young': 'soft', 'pre': 'soft', 'presbyopic': {'prescript': {'myope': 'no lenses', 'hyper': 'soft'}}}}, 'yes': {'prescript': {'myope': 'hard', 'hyper': {'age': {'young': 'hard', 'pre': 'no lenses', 'presbyopic': 'no lenses'}}}}}}}}

决策树可视化

完整代码下载

源码请进QQ群436303759文件下载:

作者声明

本文原创,旨在学术和科研使用。转载必须注明出处【伏草惟存】文章同步如下:

Coursera上数据科学相关课程(公开课)汇总推荐

Coursera上的数据科学课程有很多,这里汇总一批。

1、 Introduction to Data Science Specialization

IBM公司推出的数据科学导论专项课程系列(Introduction to Data Science Specialization),这个系列包括4门子课程,涵盖数据科学简介,面向数据科学的开源工具,数据科学方法论,SQL基础,感兴趣的同学可以关注:Launch your career in Data Science。Data Science skills to prepare for a career or further advanced learning in Data Science.

1) What is Data Science?
2) Open Source tools for Data Science
3) Data Science Methodology
4) Databases and SQL for Data Science

2、Applied Data Science Specialization

IBM公司推出的 应用数据科学专项课程系列(Applied Data Science Specialization),这个系列包括4门子课程,涵盖面向数据科学的Python,Python数据可视化,Python数据分析,数据科学应用毕业项目,感兴趣的同学可以关注:Get hands-on skills for a Career in Data Science。Learn Python, analyze and visualize data. Apply your skills to data science and machine learning.

1) Python for Data Science
2) Data Visualization with Python
3) Data Analysis with Python
4) Applied Data Science Capstone

3、Applied Data Science with Python Specialization

密歇根大学的Python数据科学应用专项课程系列(Applied Data Science with Python),这个系列的目标主要是通过Python编程语言介绍数据科学的相关领域,包括应用统计学,机器学习,信息可视化,文本分析和社交网络分析等知识,并结合一些流行的Python工具包进行讲授,例如pandas, matplotlib, scikit-learn, nltk以及networkx等Python工具。感兴趣的同学可以关注:Gain new insights into your data-Learn to apply data science methods and techniques, and acquire analysis skills.

1) Introduction to Data Science in Python
2) Applied Plotting, Charting & Data Representation in Python
3) Applied Machine Learning in Python
4) Applied Text Mining in Python
5) Applied Social Network Analysis in Python

4、Data Science Specialization

约翰霍普金斯大学的数据科学专项课程系列(Data Science Specialization),这个系列课程有10门子课程,包括数据科学家的工具箱,R语言编程,数据清洗和获取,数据分析初探,可重复研究,统计推断,回归模型,机器学习实践,数据产品开发,数据科学毕业项目,感兴趣的同学可以关注: Launch Your Career in Data Science-A nine-course introduction to data science, developed and taught by leading professors.

1) The Data Scientist’s Toolbox
2) R Programming
3) Getting and Cleaning Data
4) Exploratory Data Analysis
5) Reproducible Research
6) Statistical Inference
7) Regression Models
8) Practical Machine Learning
9) Developing Data Products
10) Data Science Capstone

5、Data Science at Scale Specialization

华盛顿大学的大规模数据科学专项课程系列(Data Science at Scale ),这个系列包括3门子课程和1个毕业项目课程,包括大规模数据系统和算法,数据分析模型与方法,数据科学结果分析等,感兴趣的同学可以关注: Tackle Real Data Challenges-Master computational, statistical, and informational data science in three courses.

1) Data Manipulation at Scale: Systems and Algorithms
2) Practical Predictive Analytics: Models and Methods
3) Communicating Data Science Results
4) Data Science at Scale – Capstone Project

6、Advanced Data Science with IBM Specialization

IBM公司推出的高级数据科学专项课程系列(Advanced Data Science with IBM Specialization),这个系列包括4门子课程,涵盖数据科学基础,高级机器学习和信号处理,结合深度学习的人工智能应用等,感兴趣的同学可以关注:Expert in DataScience, Machine Learning and AI。Become an IBM-approved Expert in Data Science, Machine Learning and Artificial Intelligence.

1) Fundamentals of Scalable Data Science
2) Advanced Machine Learning and Signal Processing
3) Applied AI with DeepLearning
4) Advanced Data Science Capstone

7、Data Mining Specialization

伊利诺伊大学香槟分校的数据挖掘专项课程系列(Data Mining Specialization),这个系列包含5门子课程和1个毕业项目课程,涵盖数据可视化,信息检索,文本挖掘与分析,模式发现和聚类分析等,感兴趣的同学可以关注:Data Mining Specialization-Analyze Text, Discover Patterns, Visualize Data. Solve real-world data mining challenges.

1) Data Visualization
2) Text Retrieval and Search Engines
3) Text Mining and Analytics
4) Pattern Discovery in Data Mining
5) Cluster Analysis in Data Mining
6) Data Mining Project

8、Data Analysis and Interpretation Specialization

数据分析和解读专项课程系列(Data Analysis and Interpretation Specialization),该系列包括5门子课程,分别是数据管理和可视化,数据分析工具,回归模型,机器学习,毕业项目,感兴趣的同学可以关注:Learn Data Science Fundamentals-Drive real world impact with a four-course introduction to data science.

1) Data Management and Visualization
2) Data Analysis Tools
3) Regression Modeling in Practice
4) Machine Learning for Data Analysis
5) Data Analysis and Interpretation Capstone

9、Executive Data Science Specialization

可管理的数据科学专项课程系列(Executive Data Science Specialization),这个系列包含4门子课程和1门毕业项目课程,涵盖数据科学速成,数据科学小组建设,数据分析管理,现实生活中的数据科学等,感兴趣的同学可以关注:Be The Leader Your Data Team Needs-Learn to lead a data science team that generates first-rate analyses in four courses.

1)A Crash Course in Data Science
2)Building a Data Science Team
3)Managing Data Analysis
4)Data Science in Real Life
5)Executive Data Science Capstone

10、其他相关的数据科学课程

1) Data Science Math Skills
2) Data Science Ethics
3) How to Win a Data Science Competition: Learn from Top Kagglers

注:本文首发“课程图谱博客”:http://blog.coursegraph.com

同步发布到这里, 本本文链接地址:http://blog.coursegraph.com/coursera上数据科学相关课程数据科学公开课汇总推荐 http://blog.coursegraph.com/?p=851

专利文本数据挖掘之AIpatent

这两年,我花了很多时间在专利文本数据挖掘上,这是一件很好玩的事情。目前我们的产品陆续上线了,感兴趣的朋友可以关注:

AIpatent专利翻译引擎http://t.aipatent.com

AIpatent专利科技词典http://d.aipatent.com/

AIpatent专利情报信息http://x.aipatent.com/

接下来,还有好玩的AIpatent专利检索产品,敬请期待。

从零开始搭建深度学习服务器: 1080TI四卡并行(Ubuntu16.04+CUDA9.2+cuDNN7.1+TensorFlow+Keras)

这个系列写了好几篇文章,这是相关文章的索引,仅供参考:

最近公司又弄了一套4卡1080TI机器,配置基本上和之前是一致的,只是显卡换成了技嘉的伪公版1080TI:技嘉GIGABYTE GTX1080Ti 涡轮风扇108TTURBO-11GD

部件	型号	价格	链接	备注
CPU	英特尔(Intel)酷睿六核i7-6850K 盒装CPU处理器 	4599	http://item.jd.com/11814000696.html	
散热器	美商海盗船 H55 水冷	449	https://item.jd.com/10850633518.html	
主板	华硕(ASUS)华硕 X99-E WS/USB 3.1工作站主板	4759	
内存	美商海盗船(USCORSAIR) 复仇者LPX DDR4 3000 32GB(16Gx4条)  	2799 * 2	https://item.jd.com/1990572.html	
SSD	三星(SAMSUNG) 960 EVO 250G M.2 NVMe 固态硬盘	599	https://item.jd.com/3739097.html		
硬盘	希捷(SEAGATE)酷鱼系列 4TB 5900转 台式机机械硬盘 * 2 	629 * 2	https://item.jd.com/4220257.html	
电源	美商海盗船 AX1500i 全模组电源 80Plus金牌	3699	https://item.jd.com/10783917878.html
机箱	美商海盗船 AIR540 USB3.0 	949	http://item.jd.com/12173900062.html
显卡	技嘉(GIGABYTE) GTX1080Ti 11GB 非公版高端游戏显卡深度学习涡轮 * 4 7400 * 4    https://item.jd.com/10583752777.html

这台深度学习主机大概是这样的:

安装完Ubuntu16.04之后,我又开始了CUDA、cuDnn等深度学习环境和工具的安装之旅,时隔大半年,又有了很多变化,特别是CUDA9.x和cuDnn7.x已经成了标配,这里记录一下。

安装CUDA9.x

注:如果还需要安装Tensorflow1.8,建议这里安装CUDA9.0,我在另一台机器上遇到了一点问题,怀疑和我这台机器先安装CUDA9.0,再安装CUDA9.2有关。

依然从英伟达官方下载当前的CUDA版本,我选择了最新的CUDA9.2:

点选完对应Ubuntu16.04的CUDA9.2 deb版本之后,英伟达官方主页会给出安装提示:

Installation Instructions:
`sudo dpkg -i cuda-repo-ubuntu1604-9-2-local_9.2.88-1_amd64.deb`
`sudo apt-key add /var/cuda-repo-/7fa2af80.pub`
`sudo apt-get update`
`sudo apt-get install cuda`

在下载完大概1.2G的cuda deb版本之后,实际安装命令是这样的:

sudo dpkg -i cuda-repo-ubuntu1604-9-2-local_9.2.88-1_amd64.deb
sudo apt-key add /var/cuda-repo-9-2-local/7fa2af80.pub
sudo apt-get update
sudo apt-get install cuda

官方CUDA下载下载页面还附带了一个cuBLAS 9.2 Patch更新,官方强烈建议安装:

This update includes fix to cublas GEMM APIs on V100 Tensor Core GPUs when used with default algorithm CUBLAS_GEMM_DEFAULT_TENSOR_OP. We strongly recommend installing this update as part of CUDA Toolkit 9.2 installation.

可以用如下方式安装这个Patch更新:

sudo dpkg -i cuda-repo-ubuntu1604-9-2-local-cublas-update-1_1.0-1_amd64.deb 
sudo apt-get update  
sudo apt-get upgrade cuda

CUDA9.2安装完毕之后,1080TI的显卡驱动也附带安装了,可以重启机器,然后用 nvidia-smi 命令查看一下:

最后在在 ~/.bashrc 中设置环境变量:

export PATH=/usr/local/cuda/bin${PATH:+:${PATH}}
export LD_LIBRARY_PATH=/usr/local/cuda/lib64${LD_LIBRARY_PATH:+:${LD_LIBRARY_PATH}}
export CUDA_HOME=/usr/local/cuda

运行 source ~/.bashrc 使其生效。

安装cuDNN7.x

同样去英伟达官网的cuDNN下载页面:https://developer.nvidia.com/rdp/cudnn-download,最新版本是cuDNN7.1.4,有三个版本可以选择,分别面向CUDA8.0, CUDA9.0, CUDA9.2:

下载完cuDNN7.1的压缩包之后解压,然后将相关文件拷贝到cuda的系统路径下即可:

tar -zxvf cudnn-9.2-linux-x64-v7.1.tgz
sudo cp cuda/include/cudnn.h /usr/local/cuda/include/
sudo cp cuda/lib64/libcudnn* /usr/local/cuda/lib64/ -d 
sudo chmod a+r /usr/local/cuda/include/cudnn.h
sudo chmod a+r /usr/local/cuda/lib64/libcudnn*

安装TensorFlow 1.8

TensorFlow的安装变得越来越简单,现在TensorFlow的官网也有中文安装文档了:https://www.tensorflow.org/install/install_linux?hl=zh-cn , 我们Follow这个文档,用Virtualenv的安装方式进行TensorFlow的安装,不过首先要配置一下基础环境。

首先在Ubuntu16.04里安装 libcupti-dev 库:

这是 NVIDIA CUDA 分析工具接口。此库提供高级分析支持。要安装此库,请针对 CUDA 工具包 8.0 或更高版本发出以下命令:

$ sudo apt-get install cuda-command-line-tools
并将其路径添加到您的 LD_LIBRARY_PATH 环境变量中:

$ export LD_LIBRARY_PATH=$LD_LIBRARY_PATH:/usr/local/cuda/extras/CUPTI/lib64
对于 CUDA 工具包 7.5 或更低版本,请发出以下命令:

$ sudo apt-get install libcupti-dev

然而我运行“sudo apt-get install cuda-command-line-tools”命令后得到的却是:

E: 无法定位软件包 cuda-command-line-tools

Google后发现其实在安装CUDA9.2的时候,这个包已经安装了,在CUDA的路径下这个库已经有了:

/usr/local/cuda/extras/CUPTI/lib64$ ls
libcupti.so  libcupti.so.9.2  libcupti.so.9.2.88

现在只需要将其加入到环境变量中,在~/.bashrc中添加如下声明并令source ~/.bashrc另其生效即可:

export LD_LIBRARY_PATH=$LD_LIBRARY_PATH:/usr/local/cuda/extras/CUPTI/lib64

剩下的就更简单了:

sudo apt-get install python-pip python-dev python-virtualenv 
virtualenv --system-site-packages tensorflow1.8
source tensorflow1.8/bin/activate
easy_install -U pip
pip install -i https://pypi.tuna.tsinghua.edu.cn/simple --upgrade tensorflow-gpu

强烈建议将清华的pip源写到配置文件里,这样就更方便快捷了。

最后测试一下TensorFlow1.8:

Python 2.7.12 (default, Dec  4 2017, 14:50:18) 
[GCC 5.4.0 20160609] on linux2
Type "help", "copyright", "credits" or "license" for more information.
>>> import tensorflow as tf
>>> sess = tf.Session(config=tf.ConfigProto(log_device_placement=True))
2018-06-17 12:15:34.158680: I tensorflow/core/platform/cpu_feature_guard.cc:140] Your CPU supports instructions that this TensorFlow binary was not compiled to use: AVX2 FMA
2018-06-17 12:15:34.381812: I tensorflow/core/common_runtime/gpu/gpu_device.cc:1356] Found device 0 with properties: 
name: GeForce GTX 1080 Ti major: 6 minor: 1 memoryClockRate(GHz): 1.582
pciBusID: 0000:05:00.0
totalMemory: 10.91GiB freeMemory: 5.53GiB
2018-06-17 12:15:34.551451: I tensorflow/core/common_runtime/gpu/gpu_device.cc:1356] Found device 1 with properties: 
name: GeForce GTX 1080 Ti major: 6 minor: 1 memoryClockRate(GHz): 1.582
pciBusID: 0000:06:00.0
totalMemory: 10.92GiB freeMemory: 5.80GiB
2018-06-17 12:15:34.780350: I tensorflow/core/common_runtime/gpu/gpu_device.cc:1356] Found device 2 with properties: 
name: GeForce GTX 1080 Ti major: 6 minor: 1 memoryClockRate(GHz): 1.582
pciBusID: 0000:09:00.0
totalMemory: 10.92GiB freeMemory: 5.80GiB
2018-06-17 12:15:34.959199: I tensorflow/core/common_runtime/gpu/gpu_device.cc:1356] Found device 3 with properties: 
name: GeForce GTX 1080 Ti major: 6 minor: 1 memoryClockRate(GHz): 1.582
pciBusID: 0000:0a:00.0
totalMemory: 10.92GiB freeMemory: 5.80GiB
2018-06-17 12:15:34.966403: I tensorflow/core/common_runtime/gpu/gpu_device.cc:1435] Adding visible gpu devices: 0, 1, 2, 3
2018-06-17 12:15:36.373745: I tensorflow/core/common_runtime/gpu/gpu_device.cc:923] Device interconnect StreamExecutor with strength 1 edge matrix:
2018-06-17 12:15:36.373785: I tensorflow/core/common_runtime/gpu/gpu_device.cc:929]      0 1 2 3 
2018-06-17 12:15:36.373798: I tensorflow/core/common_runtime/gpu/gpu_device.cc:942] 0:   N Y Y Y 
2018-06-17 12:15:36.373804: I tensorflow/core/common_runtime/gpu/gpu_device.cc:942] 1:   Y N Y Y 
2018-06-17 12:15:36.373808: I tensorflow/core/common_runtime/gpu/gpu_device.cc:942] 2:   Y Y N Y 
2018-06-17 12:15:36.373814: I tensorflow/core/common_runtime/gpu/gpu_device.cc:942] 3:   Y Y Y N 
2018-06-17 12:15:36.374516: I tensorflow/core/common_runtime/gpu/gpu_device.cc:1053] Created TensorFlow device (/job:localhost/replica:0/task:0/device:GPU:0 with 5307 MB memory) -> physical GPU (device: 0, name: GeForce GTX 1080 Ti, pci bus id: 0000:05:00.0, compute capability: 6.1)
2018-06-17 12:15:36.444426: I tensorflow/core/common_runtime/gpu/gpu_device.cc:1053] Created TensorFlow device (/job:localhost/replica:0/task:0/device:GPU:1 with 5582 MB memory) -> physical GPU (device: 1, name: GeForce GTX 1080 Ti, pci bus id: 0000:06:00.0, compute capability: 6.1)
2018-06-17 12:15:36.506340: I tensorflow/core/common_runtime/gpu/gpu_device.cc:1053] Created TensorFlow device (/job:localhost/replica:0/task:0/device:GPU:2 with 5582 MB memory) -> physical GPU (device: 2, name: GeForce GTX 1080 Ti, pci bus id: 0000:09:00.0, compute capability: 6.1)
2018-06-17 12:15:36.614736: I tensorflow/core/common_runtime/gpu/gpu_device.cc:1053] Created TensorFlow device (/job:localhost/replica:0/task:0/device:GPU:3 with 5582 MB memory) -> physical GPU (device: 3, name: GeForce GTX 1080 Ti, pci bus id: 0000:0a:00.0, compute capability: 6.1)
Device mapping:
/job:localhost/replica:0/task:0/device:GPU:0 -> device: 0, name: GeForce GTX 1080 Ti, pci bus id: 0000:05:00.0, compute capability: 6.1
/job:localhost/replica:0/task:0/device:GPU:1 -> device: 1, name: GeForce GTX 1080 Ti, pci bus id: 0000:06:00.0, compute capability: 6.1
/job:localhost/replica:0/task:0/device:GPU:2 -> device: 2, name: GeForce GTX 1080 Ti, pci bus id: 0000:09:00.0, compute capability: 6.1
/job:localhost/replica:0/task:0/device:GPU:3 -> device: 3, name: GeForce GTX 1080 Ti, pci bus id: 0000:0a:00.0, compute capability: 6.1
2018-06-17 12:15:36.689345: I tensorflow/core/common_runtime/direct_session.cc:284] Device mapping:
/job:localhost/replica:0/task:0/device:GPU:0 -> device: 0, name: GeForce GTX 1080 Ti, pci bus id: 0000:05:00.0, compute capability: 6.1
/job:localhost/replica:0/task:0/device:GPU:1 -> device: 1, name: GeForce GTX 1080 Ti, pci bus id: 0000:06:00.0, compute capability: 6.1
/job:localhost/replica:0/task:0/device:GPU:2 -> device: 2, name: GeForce GTX 1080 Ti, pci bus id: 0000:09:00.0, compute capability: 6.1
/job:localhost/replica:0/task:0/device:GPU:3 -> device: 3, name: GeForce GTX 1080 Ti, pci bus id: 0000:0a:00.0, compute capability: 6.1

安装Keras2.1.x

Keras的后端支持TensorFlow, Theano, CNTK,在安装完TensorFlow GPU版本之后,继续安装Keras非常简单,在TensorFlow的虚拟环境中,直接"pip install keras"即可,安装的版本是Keras2.1.6:

Installing collected packages: h5py, scipy, pyyaml, keras
Successfully installed h5py-2.7.1 keras-2.1.6 pyyaml-3.12 scipy-1.1.0

测试一下:

Python 2.7.12 (default, Dec  4 2017, 14:50:18) 
[GCC 5.4.0 20160609] on linux2
Type "help", "copyright", "credits" or "license" for more information.
>>> import keras
Using TensorFlow backend.

注:原创文章,转载请注明出处及保留链接“注册赠送体验金”:http://

本文链接地址:从零开始搭建深度学习服务器: 1080TI四卡并行(Ubuntu16.04+CUDA9.2+cuDNN7.1+TensorFlow+Keras) http://m.asijv.cn/?p=10334