【机器学习实战】K-近邻算法之海伦约会

【机器学习实战】K-近邻算法之海伦约会


文章是阅读《机器学习实战》【美】Peter Harrington著 李锐李鹏 曲亚东 王斌 译 的读书笔记,具体可以去参考这本书。

1.准备数据:文本文件中解析数据

海伦将约会数据放在datingTestSet.txt中,每个样本数据占一行,总共有1000行。海伦的样本主要包含以下三种特征:

  • 每年获得的飞行常客里程数
  • 玩视频游戏所消耗时间百分比
  • 每周消费的冰淇淋公升数

在上述数据输入到分类器之前,需要将待处理数据格式改变为分类器可以接受的格式。由csv文件构建相对应的numpy数组, 输入为文件名字字符串,输出为训练样本矩阵和类标签向量。

def file2matrix(filename):
    fr = open(filename)
    arrayOLines = fr.readlines()
    # 得到文件行数
    numberOfLines = len(arrayOLines)
    # 创建返回的numpy数组,因为是海伦约会数据集有3个特征
    returnMat = zeros((numberOfLines, 3))
    classLabelVector = []
    index = 0
    # 解析文本数据到列表
    for line in arrayOLines:
        # 截取掉所有的回车字符
        line = line.strip()
        # 使用'\tab'键将整行数据转换为列表
        listFromLine = line.split('\t')
        # 将海伦约会的每一行数据转换为要返回的numpy数据行
        returnMat[index, :] = listFromLine[0:3]
        classLabelVector.append((listFromLine[-1]))
        # 将字符标签转换为数字标签
        # if listFromLine[-1] == 'didntLike':
        #     classLabelVector.append(1)
        # if listFromLine[-1] == 'smallDoses':
        #     classLabelVector.append(2)
        # if listFromLine[-1] == 'largeDoses':
        #     classLabelVector.append(3)

        index += 1
    return returnMat, classLabelVector

加载数据
在这里插入图片描述

2.标题分析数据

使用Matplotlib制作原始数据的散点图分析数据
博主陈多鱼这块代码写的很好,在这借鉴了一下,感兴趣可以去看原帖,感谢博主

https://blog.csdn.net/qq_42338771/article/details/108270830

代码及其解释

# 图像数据分析
def showData(returnMat, classLabelVector):

    fig, axs = plt.subplots(nrows=2, ncols=2, sharex=False, sharey=False, figsize=(13, 8))

    labelColors = []
    for i in datingLables:
        if i == 1:
            labelColors.append('black')
        if i == 2:
            labelColors.append('orange')
        if i == 3:
            labelColors.append('red')

    # 设置第一张图片,飞行里程数和游戏百分比, 散点大小为15, 透明度0.5
    axs[0][0].scatter(x=datingDataMat[:, 0], y=datingDataMat[:, 1], color=labelColors, s=15, alpha=0.5)
    # 设置标题和坐标轴备注
    axs0_title_text = axs[0][0].set_title('flight_play')
    axs0_xlabel_text = axs[0][0].set_xlabel('飞行里程')
    axs0_ylabel_text = axs[0][0].set_ylabel('游戏时间')
    # 中文标题正常显示
    plt.rcParams['font.sans-serif'] = ['Microsoft YaHei']
    plt.setp(axs0_title_text, size=9, weight='bold', color='red')
    plt.setp(axs0_xlabel_text, size=7, weight='bold', color='black')
    plt.setp(axs0_ylabel_text, size=7, weight='bold', color='black')

    # 设置第二张图片, 飞行里程数和冰淇淋
    axs[0][1].scatter(x=datingDataMat[:, 0], y=datingDataMat[:, 2], color=labelColors, s=15, alpha=0.5)
    axs1_title_text = axs[0][1].set_title('flight_eat')
    axs1_xlabel_text = axs[0][1].set_xlabel('飞行里程')
    axs1_ylabel_text = axs[0][1].set_ylabel('冰淇淋量')
    plt.setp(axs1_title_text, size=9, weight='bold', color='red')
    plt.setp(axs1_xlabel_text, size=7, weight='bold', color='black')
    plt.setp(axs1_ylabel_text, size=7, weight='bold', color='black')

    # 设置第三张图片,游戏时间和冰淇淋量
    axs[1][0].scatter(x=datingDataMat[:, 1], y=datingDataMat[:, 2], color=labelColors, s=15, alpha=0.5)
    axs2_title_text = axs[1][0].set_title('play_eat')
    axs2_xlabel_text = axs[1][0].set_xlabel('游戏时间')
    axs2_ylabel_text = axs[1][0].set_ylabel('冰淇淋量')
    plt.setp(axs2_title_text, size=9, weight='bold', color='red')
    plt.setp(axs2_xlabel_text, size=7, weight='bold', color='black')
    plt.setp(axs2_ylabel_text, size=7, weight='bold', color='black')

    # 设置图例
    didntLike = mlines.Line2D([], [], color='black', marker='.',
                              markersize=6, label='didntLike')
    smallDoses = mlines.Line2D([], [], color='orange', marker='.',
                               markersize=6, label='smallDoses')
    largeDoses = mlines.Line2D([], [], color='red', marker='.',
                               markersize=6, label='largeDoses')

    # 添加图例
    axs[0][0].legend(handles=[didntLike, smallDoses, largeDoses])
    axs[0][1].legend(handles=[didntLike, smallDoses, largeDoses])
    axs[1][0].legend(handles=[didntLike, smallDoses, largeDoses])

    # 画出图片
    plt.show()

数据分析图例
在这里插入图片描述
这三张图可以看出来,第一张图通过飞行里程数和游戏百分比,还是可以把三种约会类型比较清晰的分类出来,而后面两张图类别中互相混合,无法分类出来。

3.数据归一化

在这里插入图片描述
约会网站的原始数据,可以看出来如果使用欧氏距离的话,飞行里程数与另外两个类别的数差距比较大,影响过大,但是海伦认为这三种数据同等重要,处理不同范围的特征值的时候,一般将数值归一化,减小数级的影响,将取值范围处理到0到1或者-1到1之间,下面的公式将数值范围处理在0到1之间。
n e w v a l u e = ( o l d v a l u e − m i n ) / ( m a x − m i n ) newvalue = (oldvalue - min)/(max-min) newvalue=(oldvaluemin)/(maxmin)
数值归一化范围-1到1之间公式:
n e w v a l u e = 2 ∗ ( o l d v a l u e − m i n ) / ( m a x − m i n ) − 1 newvalue = 2*(oldvalue - min)/(max-min) - 1 newvalue=2(oldvaluemin)/(maxmin)1
max与min分别是数据集中的最小特征值和最大特征值,改变数值取值范围会增加分类器的复杂度,但是会使结果更加准确。
代码数值归一化0到1区间函数autoNorm()。

# 数值归一化函数
def autoNorm(dataSet):
    minValue = dataSet.min(0)
    maxValue = dataSet.max(0)
    ranges = maxValue - minValue
    # 同样规格的零矩阵记录数值归一化的结果
    normDataset = zeros(shape(dataSet))
    # 记录数据行数,即数据条数
    m = dataSet.shape(0)
    # 复制minvalue到同样大小,然后做数据相减操作
    normDataset = dataSet - tile(minValue, (m, 1))
    # 复制ranges到同样规格大小,然后做数据相除操作
    normDataset = normDataset/tile(ranges, (m, 1))
    # 返回归一化结果,范围,最小值
    return normDataset, ranges, minValue

这里面操作的是特征值(数值)相除,不是向量相除,在numpy库中,向量相除使用函数linalg.solve(matA, matB)。

4.分类算法:KNN算法实现

使用的是之前文章中使用的欧式距离计算的方法来判断分类,详细参考文章

https://blog.csdn.net/qq_43587460/article/details/135983033#comments_31185167

# 分类器,使用欧几里得距离
def classify0(inx, dataset, labels, k):
    '''
    :param inx: 未知标签数据特征
    :param dataset: 样本数据
    :param labels: 样本标签
    :param k: k近邻取值
    :return: 判断标签值
    '''
    # 距离计算
    datasetSetSize = dataset.shape[0]
    diffMat = tile(inx, (datasetSetSize, 1)) - dataset
    sqDiffMat = diffMat ** 2
    sqDistances = sqDiffMat.sum(axis=1)
    distance = sqDistances ** 0.5
    # 距离排序
    sortedDistIndicies = distance.argsort()

    classCount = {}
    for i in range(k):
        voteIlabel = labels[sortedDistIndicies[i]]
        classCount[voteIlabel] = classCount.get(voteIlabel, 0) + 1
    sorteClassCount = sorted(classCount.items(), key=operator.itemgetter(1), reverse=True)
    return sorteClassCount[0][0]

5.测试算法:作为完整程序验证分类器

评估算法的正确率,通常我们用90%的数据来作为训练样本来训练分类器,用10%的数据去测试分类器,检测分类器的正确率。10%的测试数据应该是随机选择的,海伦约会数据没有根据特定的目的来排序,可以随意选取10%的数据而不影响随机性。
可以使用错误率来检测分类器的性能,错误率就是分类器给出错误结果的次数除以测试数据的总数,完美分类器的错误率为0,而错误率为1.0的分类器不会给出任何正确的分类结果。
代码:

def datingClassTest():
    # 测试数据比例
    hoRatio = 0.10
    datingDataMat, datingLables = file2matrix('datingTestSet.txt')
    normMat, ranges, minValues = autoNorm(datingDataMat)
    # 获取归一化后矩阵行数
    m = normMat.shape[0]
    # 测试数据数量
    numTestVecs = int(m * hoRatio)
    errorCount = 0.0
    for i in range(numTestVecs):
        # 使用分类器分类
        classifierResult = classify0(normMat[i, :], normMat[numTestVecs:m, :],\
                                     datingLables[numTestVecs:m], 3)
        print("the classifier came back with:%d, the real answer is:%d" % (classifierResult, datingLables[i]))
        # 分类器分类数据与预测数据对比,最后计算错误率
        if(classifierResult != datingLables[i]): errorCount += 1.0
    print("the total error rate is:%f" % (errorCount/float(numTestVecs)))

测试分类器结果:
错误率是%5
可以看出,构建的分类器的错误率是5%,效果还不错,可以改进检测数据的量(hoRatio)与近邻判断的数量(k的值),来检测错误率是否会发生变化。

6.使用算法,构建完整可用的系统

写了一个人工智能,最想的还是可以帮忙来预测数据,来使用自己的构建的机器学习算法,构建预测函数。
代码及其解释如下:

# 约会网站预测函数
def classifyPerson():
    # 预测结果是数字,对应成字符串
    resultList = ['not at all', 'in small doses', 'in large doses']
    # 输入飞行里程,冰淇淋量,游戏时间来进行预测
    percentTats = float(raw_input(\
        "percentage of time spent playing video games?"))
    ffMiles = float(raw_input("frequent filer miles earned per year?"))
    iceCream = float(raw_input("liters of ice cream consumed per year?"))
    # 数据读取
    datingDataMat, datingLables = file2matrix('datingTestSet.txt')
    # 数据归一化
    normMat, ranges, minValues = autoNorm(datingDataMat)
    # 输入的数据格式整合,适配机器学习算法
    inArr = array([ffMiles, percentTats, iceCream])
    # 对数据进行归一化处理,并进行预测
    classifierResult = classify0((inArr - minValues)/ranges, normMat, datingLables, 3)
    print("You will probably like this person:", resultList[classifierResult - 1])

使用效果:
在这里插入图片描述

7.完整代码

from numpy import *
import operator
import matplotlib.pyplot as plt
import matplotlib.lines as mlines
from pip._vendor.distlib.compat import raw_input


def file2matrix(filename):
    fr = open(filename)
    arrayOLines = fr.readlines()
    # 得到文件行数
    numberOfLines = len(arrayOLines)
    # 创建返回的numpy数组,因为是海伦约会数据集有3个特征
    returnMat = zeros((numberOfLines, 3))
    classLabelVector = []
    index = 0
    # 解析文本数据到列表
    for line in arrayOLines:
        # 截取掉所有的回车字符
        line = line.strip()
        # 使用'\tab'键将整行数据转换为列表
        listFromLine = line.split('\t')
        # 将海伦约会的每一行数据转换为要返回的numpy数据行
        returnMat[index, :] = listFromLine[0:3]
        # 文本标签收集
        # classLabelVector.append((listFromLine[-1]))
        # 将字符标签转换为数字标签
        if listFromLine[-1] == 'didntLike':
            classLabelVector.append(1)
        if listFromLine[-1] == 'smallDoses':
            classLabelVector.append(2)
        if listFromLine[-1] == 'largeDoses':
            classLabelVector.append(3)

        index += 1
    return returnMat, classLabelVector

# 加载数据
datingDataMat, datingLables = file2matrix('datingTestSet.txt')
# print(datingDataMat)
# print('------------')
# print(datingLables[0:20])

# 图像数据分析
def showData(returnMat, classLabelVector):

    fig, axs = plt.subplots(nrows=2, ncols=2, sharex=False, sharey=False, figsize=(13, 8))

    labelColors = []
    for i in datingLables:
        if i == 1:
            labelColors.append('black')
        if i == 2:
            labelColors.append('orange')
        if i == 3:
            labelColors.append('red')

    # 设置第一张图片,飞行里程数和游戏百分比, 散点大小为15, 透明度0.5
    axs[0][0].scatter(x=datingDataMat[:, 0], y=datingDataMat[:, 1], color=labelColors, s=15, alpha=0.5)
    # 设置标题和坐标轴备注
    axs0_title_text = axs[0][0].set_title('flight_play')
    axs0_xlabel_text = axs[0][0].set_xlabel('飞行里程')
    axs0_ylabel_text = axs[0][0].set_ylabel('游戏时间')
    # 中文标题正常显示
    plt.rcParams['font.sans-serif'] = ['Microsoft YaHei']
    plt.setp(axs0_title_text, size=9, weight='bold', color='red')
    plt.setp(axs0_xlabel_text, size=7, weight='bold', color='black')
    plt.setp(axs0_ylabel_text, size=7, weight='bold', color='black')

    # 设置第二张图片, 飞行里程数和冰淇淋
    axs[0][1].scatter(x=datingDataMat[:, 0], y=datingDataMat[:, 2], color=labelColors, s=15, alpha=0.5)
    axs1_title_text = axs[0][1].set_title('flight_eat')
    axs1_xlabel_text = axs[0][1].set_xlabel('飞行里程')
    axs1_ylabel_text = axs[0][1].set_ylabel('冰淇淋量')
    plt.setp(axs1_title_text, size=9, weight='bold', color='red')
    plt.setp(axs1_xlabel_text, size=7, weight='bold', color='black')
    plt.setp(axs1_ylabel_text, size=7, weight='bold', color='black')

    # 设置第三张图片,游戏时间和冰淇淋量
    axs[1][0].scatter(x=datingDataMat[:, 1], y=datingDataMat[:, 2], color=labelColors, s=15, alpha=0.5)
    axs2_title_text = axs[1][0].set_title('play_eat')
    axs2_xlabel_text = axs[1][0].set_xlabel('游戏时间')
    axs2_ylabel_text = axs[1][0].set_ylabel('冰淇淋量')
    plt.setp(axs2_title_text, size=9, weight='bold', color='red')
    plt.setp(axs2_xlabel_text, size=7, weight='bold', color='black')
    plt.setp(axs2_ylabel_text, size=7, weight='bold', color='black')

    # 设置图例
    didntLike = mlines.Line2D([], [], color='black', marker='.',
                              markersize=6, label='didntLike')
    smallDoses = mlines.Line2D([], [], color='orange', marker='.',
                               markersize=6, label='smallDoses')
    largeDoses = mlines.Line2D([], [], color='red', marker='.',
                               markersize=6, label='largeDoses')

    # 添加图例
    axs[0][0].legend(handles=[didntLike, smallDoses, largeDoses])
    axs[0][1].legend(handles=[didntLike, smallDoses, largeDoses])
    axs[1][0].legend(handles=[didntLike, smallDoses, largeDoses])

    # 画出图片
    plt.show()

showData(datingDataMat, datingLables)

# 分类器,使用欧几里得距离
def classify0(inx, dataset, labels, k):
    '''
    :param inx: 未知标签数据特征
    :param dataset: 样本数据
    :param labels: 样本标签
    :param k: k近邻取值
    :return: 判断标签值
    '''
    # 距离计算
    datasetSetSize = dataset.shape[0]
    diffMat = tile(inx, (datasetSetSize, 1)) - dataset
    sqDiffMat = diffMat ** 2
    sqDistances = sqDiffMat.sum(axis=1)
    distance = sqDistances ** 0.5
    # 距离排序
    sortedDistIndicies = distance.argsort()

    classCount = {}
    for i in range(k):
        voteIlabel = labels[sortedDistIndicies[i]]
        classCount[voteIlabel] = classCount.get(voteIlabel, 0) + 1
    sorteClassCount = sorted(classCount.items(), key=operator.itemgetter(1), reverse=True)
    return sorteClassCount[0][0]

# 数值归一化函数
def autoNorm(dataSet):
    minValue = dataSet.min(0)
    maxValue = dataSet.max(0)
    ranges = maxValue - minValue
    # 同样规格的零矩阵记录数值归一化的结果
    normDataset = zeros(shape(dataSet))
    # 记录数据行数,即数据条数
    m = dataSet.shape[0]
    # 复制minvalue到同样大小,然后做数据相减操作
    normDataset = dataSet - tile(minValue, (m, 1))
    # 复制ranges到同样规格大小,然后做数据相除操作
    normDataset = normDataset/tile(ranges, (m, 1))
    # 返回归一化结果,范围,最小值
    return normDataset, ranges, minValue

# 归一化实例
normMat, ranges, minValues = autoNorm(datingDataMat)
# print(normMat)
# print('-----------')
# print(ranges)
# print('-------------')
# print(minValues)

# 错误率测试代码
def datingClassTest():
    # 测试数据比例
    hoRatio = 0.10
    datingDataMat, datingLables = file2matrix('datingTestSet.txt')
    normMat, ranges, minValues = autoNorm(datingDataMat)
    # 获取归一化后矩阵行数
    m = normMat.shape[0]
    # 测试数据数量
    numTestVecs = int(m * hoRatio)
    errorCount = 0.0
    for i in range(numTestVecs):
        # 使用分类器分类
        classifierResult = classify0(normMat[i, :], normMat[numTestVecs:m, :],\
                                     datingLables[numTestVecs:m], 3)
        print("the classifier came back with:%d, the real answer is:%d" % (classifierResult, datingLables[i]))
        # 分类器分类数据与预测数据对比,最后计算错误率
        if(classifierResult != datingLables[i]): errorCount += 1.0
    print("the total error rate is:%f" % (errorCount/float(numTestVecs)))

# 测试分类器
# datingClassTest()

# 约会网站预测函数
def classifyPerson():
    # 预测结果是数字,对应成字符串
    resultList = ['not at all', 'in small doses', 'in large doses']
    # 输入飞行里程,冰淇淋量,游戏时间来进行预测
    percentTats = float(raw_input(\
        "percentage of time spent playing video games?"))
    ffMiles = float(raw_input("frequent filer miles earned per year?"))
    iceCream = float(raw_input("liters of ice cream consumed per year?"))
    # 数据读取
    datingDataMat, datingLables = file2matrix('datingTestSet.txt')
    # 数据归一化
    normMat, ranges, minValues = autoNorm(datingDataMat)
    # 输入的数据格式整合,适配机器学习算法
    inArr = array([ffMiles, percentTats, iceCream])
    # 对数据进行归一化处理,并进行预测
    classifierResult = classify0((inArr - minValues)/ranges, normMat, datingLables, 3)
    print("You will probably like this person:", resultList[classifierResult - 1])

if __name__ == '__main__':
    classifyPerson()

从简单的开始,一步步进步吧。如有错误,欢迎指正,积极学习。

  • 20
    点赞
  • 21
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值