《机器学习实战》chap2. k-近邻算法+约会网站项目python详细注释

代码基本上每一行都写上了注释,帮助理解记忆。

k-近邻算法

章节内容

  1. k-近邻算法的基本理论;
  2. 如何使用距离测量的方法分类物品;
  3. 使用Python从文本文件中导入并解析数据;
  4. 当存在许多数据来源时,如何避免计算距离时可能碰到的一些常见错误;
  5. 使用k-近邻算法改进约会网站和手写数字识别系统。

k-近邻算法概述工作原理

训练样本集中每个数据都存在标签,即我们知道样本集中每一数据与所属分类的对应关系。输入没有标签的新数据后,将新数据的每个特征与样本集中数据对应的特征进行比较,然后算法提取样本集中特征最相似数据(最近邻)的分类标签。一般来说,只选择样本数据集中前k个最相似的数据,通常k是不大于20的整数。最后,选择k个最相似数据中出现次数最多的分类,作为新数据的分类。

例如电影分类,使用kNN确定它是爱情片还是动作片。

表2-1 每部电影的打斗镜头数、接吻镜头数以及电影评估类型在这里插入图片描述

开发机器学习应用的通用步骤
1. 收集数据
2. 准备输入数据, 确保数据格式符合要求,本书采用的格式是Python语言的List。
3. 分析输入数据,确保数据中没有垃圾数据。
4. 训练算法(kNN不需要此步)
5. 测试算法,评估算法,计算错误率。
6. 使用算法

使用k-近邻算法将每组数据划分到某个类中,伪代码如下:

对未知类别属性的数据集中的每个点依次执行以下操作:
(1) 计算已知类别数据集中的点与当前点之间的距离;
(2) 按照距离递增次序排序;
(3) 选取与当前点距离最小的k个点;
(4) 确定前k个点所在类别的出现频率;
(5) 返回前k个点出现频率最高的类别作为当前点的预测分类。

构建分类器——

def classify0(inX, dataSet, labels, k):
    #用于分类的输入向量是inX,输入的训练样本集为dataSet,标签向量为labels,最后的参数k表示用于选择最近邻居的数目
    dataSetSize = dataSet.shape[0]  # shape[0]是读取矩阵第一维度的长度,即数据的条数
    # 计算距离
    diffMat = tile(inX, (dataSetSize,1)) - dataSet  #将目标复制成n行,计算得目标与每个训练数值之间的数值之差。
    sqDiffMat = diffMat**2  #各个元素分别平方
    sqDistances = sqDiffMat.sum(axis=1)  #对应行的平方相加,即得到了距离的平方和
    distances = sqDistances**0.5  #开根号,得到距离
    #排序,确定前k个距离最小元素所在的主要分类
    sortedDistIndicies = distances.argsort()  #argsort函数返回的是数组值从小到大的索引值   
    classCount={}          
    for i in range(k):
        voteIlabel = labels[sortedDistIndicies[i]]
        classCount[voteIlabel] = classCount.get(voteIlabel, 0) + 1  #给字典赋值,每个标签计数
    #返回发生频率最高的元素标签
    sortedClassCount = sorted(classCount.items(), key=operator.itemgetter(1), reverse=True)
    return sortedClassCount[0][0]

错误率是常用的评估方法。错误率 = 分类器给出错误结果的次数除以测试执行的总数。

以下测试代码可直接运行——

'''
构造kNN分类器
'''

from numpy import *
import operator  #运算符模块
# 创建数据集和标签

group = array([[1.0,1.1],[1.0,1.0],[0,0],[0,0.1]])
labels = ['A','A','B','B']

def classify0(inX, dataSet, labels, k):
    #用于分类的输入向量是inX,输入的训练样本集为dataSet,标签向量为labels,最后的参数k表示用于选择最近邻居的数目
    dataSetSize = dataSet.shape[0]  # shape[0]是读取矩阵第一维度的长度,即数据的条数
    # 计算距离
    diffMat = tile(inX, (dataSetSize,1)) - dataSet  #将目标复制成n行,计算得目标与每个训练数值之间的数值之差。
    sqDiffMat = diffMat**2  #各个元素分别平方
    sqDistances = sqDiffMat.sum(axis=1)  #对应行的平方相加,即得到了距离的平方和
    distances = sqDistances**0.5  #开根号,得到距离
    #排序,确定前k个距离最小元素所在的主要分类
    sortedDistIndicies = distances.argsort()  #argsort函数返回的是数组值从小到大的索引值
    classCount={}
    for i in range(k):
        voteIlabel = labels[sortedDistIndicies[i]]
        classCount[voteIlabel] = classCount.get(voteIlabel, 0) + 1  #给字典赋值,每个标签计数
    #返回发生频率最高的元素标签
    sortedClassCount = sorted(classCount.items(), key=operator.itemgetter(1), reverse=True)
    return sortedClassCount[0][0]

print("该数据的类别是" + classify0([0, 0],group, labels, 3))

以下是本代码中使用的一些(可能小白都知道的)基础函数——

.pyc文件
py文件作为模块被import时,python编译后产生pyc文件,目的是提高解析速度。当有别的程序再次import此模块时,python读入pyc文件即可,无需重新解析py文件。

shape函数
是numpy中的函数,功能是读取矩阵的长度。
shape[0] 读取矩阵第一维度的长度

tile函数
是numpy中的函数,将原矩阵横向、纵向地复制。tile 是瓷砖的意思,顾名思义,这个函数就是把数组像瓷砖一样铺展开来。
tile(mat, (3, 4)) 将矩阵mat复制成3*4块

** 代表乘方,开根号就是 **0.5

axis=1
第0轴沿着行的垂直往下,第1轴沿着列的方向水平延伸。
在这里插入图片描述
a = np.sum([[1, 1, 1], [2, 2, 2]], axis=0)
输出[3 3 3],每列之和

a = np.sum([[1, 1, 1], [2, 2, 2]], axis=1)
输出[3 6],每行之和

argsort()
argsort函数返回的是数组值从小到大的索引值。

range()
range()是python的内置函数, 语法:range(start, stop[, step])
range(5)即前5个数, range(0,5)实际上就是索引从0开始到4结束的5个整数。step是步长,默认步长为1。

itemgetter函数
根据某个或某几个字典字段来排序Python列表。

报错 ‘dict’ object has no attribute 'iteritems’
在python3报错,将iteritems改为items即可。iteritems是为python2环境中dict的函数。

示例:使用 k-近邻算法改进约会网站的配对效果**

目标:将约会网站推荐的匹配对象划分到确切的分类中。分类有3种:
• 不喜欢的人
• 魅力一般的人
• 极具魅力的人
数据存放在文本文件datingTestSet.txt中,每个样本数据占据一行,总共有1000行。海伦的样本主要包含以下3种特征:
• 每年获得的飞行常客里程数
• 玩视频游戏所耗时间百分比
• 每周消费的冰淇淋公升数

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

from numpy import *
#解析文本文件为Numpy
def file2matrix(filename):
    fr = open(filename)
    arrayOLines = fr.readlines()
    numberOfLines = len(arrayOLines)
    # 创建以0填充的numpy矩阵
    returnMat = zeros((numberOfLines, 3))  # 生成一个 n*3(n行3列的)的矩阵,各个位置上全是 0
    classLabelVector = [] # 返回的分类标签向量
    index = 0 # 行的索引值
    # 解析文件数据到列表
    for line in arrayOLines:
        # 按行读取数据,strip()去除首尾的空白符(包括'\n','\r','\t',' ')
        line = line.strip()
        # 将每一行的字符串根据'\t'分隔符进行切片,获得元素列表
        listFromLine = line.split('\t')
        # 选取前3个元素,存放到returnMat的NumPy矩阵中,也就是特征矩阵
        returnMat[index, :] = listFromLine[0:3]
        # -1表示最后一列,最后一列是类别,将其存储到向量classLabelVector中。
        # 要告诉解释器存储的元素值为整型,否则会当作字符串处理
        classLabelVector.append(int(listFromLine[-1]))
        index += 1
    # 返回特征矩阵和分类标签向量
    return returnMat, classLabelVector
 
datingDataMat,datingLabels=file2matrix('datingTestSet2.txt')
print(datingLabels[0:20])

期间报错:invalid literal for int() with base 10: ‘largeDoses’
错误语句:classLabelVector.append(int(listFromLine[-1]))
使用 int 对一个字符类型的数据进行强制类型转换时,要求输入的字符类型只能为整数,不能为浮点数或其他。
把数据文件datingTestSet.tx改为datingTestSet2.txt即可, 这两个文件除了最后一列其他数据完全一致。前者最末列是字符串, 后者最末列是整数。

2. 分析数据:使用 Matplotlib 创建散点图

ax=fig.add_subplot(111)
这些是作为单个整数编码的子绘图网格参数。例如,“111”表示“1×1网格,第一子图”,“234”表示“2×3网格,第四子图”。

fig = plt.figure()
ax = fig.add_subplot(111)
#以datingDataMat矩阵的第一(飞行常客例程)、第二列(玩游戏)数据画散点数据
#ax.scatter(datingDataMat[:,1],datingDataMat[:,2])
#上一行画的图无颜色区分,
#可利用变量datingLabels存储的类标签属性,在散点图上绘制了色彩不等、尺寸不同的点
ax.scatter(datingDataMat[:,1],datingDataMat[:,2],15.0*array(datingLabels), 15.0*array(datingLabels))
 
plt.show()

使用matplotlib的字体管理器指定字体文件

plt.rcParams['font.sans-serif']=['SimHei']    #指定默认字体 SimHei为黑体
plt.rcParams['axes.unicode_minus']=False   #用来正常显示负号

常用字体:
黑体 SimHei 微软雅黑 Microsoft YaHei
微软正黑体 Microsoft JhengHei 新宋体 NSimSun
新细明体 PMingLiU 细明体 MingLiU
标楷体 DFKai-SB 仿宋 FangSong
楷体 KaiTi 仿宋_GB2312 FangSong_GB2312
楷体_GB2312 KaiTi_GB2312

设置散点颜色
可以利用变量datingLabels存储的类标签属性,在散点图上绘制了色彩不等、尺寸不同的点

ax.scatter(datingDataMat[:,1],datingDataMat[:,2],15.0*array(datingLabels), 15.0*array(datingLabels))

在这里插入图片描述

也可以自定义颜色

LabelsColors = []
for i in datingLabels:


# 为每个类别分别设置颜色
    if i == 1:
        LabelsColors.append('black')
    if i == 2:
        LabelsColors.append('orange')
    if i == 3:
        LabelsColors.append('red')
#散点大小为15,透明度为0.5
ax.scatter(datingDataMat[:,1], datingDataMat[:,2], color=LabelsColors, s=15, alpha=.5)

在这里插入图片描述
在这里插入图片描述

3. 准备数据:归一化数值
由于提取的特征数值范围大小不一,比如有的数值是1~100,有的是10000以上,那么在计算距离的时候,前者的影响作用远远小于后者。但每一种特征同等重要,因此我们需要将数值归一化,
如将取值范围处理为0到1或者-1到1之间。

# 数值归一化
def autoNorm(dataSet):
    # 归一化公式:Y = (X-Xmin)/(Xmax-Xmin)
    # min和max是1*3的矩阵,存着每一个特征的最值
    minVals = dataSet.min(0)
    maxVals = dataSet.max(0)
    ranges = maxVals - minVals # 极差
    normDataSet = zeros(shape(dataSet))
    m = dataSet.shape[0]  # 读取矩阵第一维度的长度
    normDataSet = dataSet - tile(minVals, (m,1))
    normDataSet = normDataSet / tile(ranges, (m,1))
    return normDataSet, ranges, minVals

4. 测试算法:验证分类器
计算错误率。

#验证分类器,计算错误率
def datingClassTest():
    hoRatio = 0.10  # 取数据集中的10%
    datingDataMat, datingLabels = file2matrix('datingTestSet2.txt')
    drawing(datingLabels, datingDataMat)
    # 数据归一化,返回归一化后的矩阵,数据范围,数据最小值
    normMat, ranges, minVals = autoNorm(datingDataMat)
    m = normMat.shape[0]  # 获得normMat的行数
    numTestVecs = int(m*hoRatio)  #10%的数量
    errorCount = 0.0
    for i in range(numTestVecs):
        # 前numTestVecs个数据作为测试集,后m-numTestVecs个数据作为训练集
        classifierResult = classify0(normMat[i, :], normMat[numTestVecs:m, :],
                                     datingLabels[numTestVecs:m], 3)
        print("预测数值为 %d, 实际数值为: %d"
              % (classifierResult, datingLabels[i]))
        if classifierResult != datingLabels[i]:
            errorCount += 1.0
    print("错误率为 %f" % (errorCount/float(numTestVecs)))

5. 构建完整可用系统
写一段交互程序,使海伦可以输入某人的信息,程序输出她对对方喜欢程度的预测值。

#预测
def classifyPerson():
    resultList = ['not at all', 'in small doses', 'in large doses']
    percentTats = float(input("percentage of time spent playing video games?"))
    ffMiles = float(input("frequent flier miles earned per year?"))
    iceCream = float(input("liters of ice cream consumed per year?"))
    datingDataMat, datingLabels = file2matrix('datingTestSet2.txt')
    normMat, ranges, minVals = autoNorm(datingDataMat)
    inArr = array([ffMiles, percentTats, iceCream])
    classifierResult = classify0((inArr-minVals)/ranges, normMat, datingLabels, 3)
    print("you will probably like this person: ", resultList[classifierResult-1])

示例:手写识别

任务
使用kNN分类器识别出数字0到9,需要识别的数字已经使用图形处理软件,处理成具有相同的色彩和大小:宽高是32像素×32像素的黑白图像。
目录trainingDigits中包含了大约2000个例子,每个例子的内容如图2-6所示,每个数字大约有200个样本;目录testDigits中包含了大约900个测试数据。
在这里插入图片描述
尽管采用文本格式存储图像不能有效地利用内存空间,但是为了方便理解,图像将转换为文本格式。
使用的kNN分类器未改变,增加了二进制矩阵转化为向量的函数以及导入数据并验证的函数。

# 将图像文本数据转换为向量
def img2vector(filename):
    returnVect = zeros((1,1024))
    fr = open(filename)
    for i in range(32):
        lineStr = fr.readline()
        for j in range(32):
            returnVect[0,32*i+j] = int(lineStr[j])
    return returnVect
# 测试算法
def handwritingClassTest():
    # 1. 导入训练数据
    hwLabels = []
    trainingFileList = listdir('trainingDigits')  # load the training set
    m = len(trainingFileList)
    trainingMat = zeros((m, 1024))
    # hwLabels存储0~9对应的index位置, trainingMat存放的每个位置对应的图片向量
    for i in range(m):
        fileNameStr = trainingFileList[i]
        fileStr = fileNameStr.split('.')[0]  # take off .txt
        classNumStr = int(fileStr.split('_')[0])
        hwLabels.append(classNumStr)
        # 将 32*32的矩阵->1*1024的矩阵
        trainingMat[i, :] = img2vector('trainingDigits/%s' % fileNameStr)
# 2. 导入测试数据
    testFileList = listdir('testDigits')  # iterate through the test set
    errorCount = 0.0
    mTest = len(testFileList)
    for i in range(mTest):
        fileNameStr = testFileList[i]
        fileStr = fileNameStr.split('.')[0]  # take off .txt
        classNumStr = int(fileStr.split('_')[0])
        vectorUnderTest = img2vector('testDigits/%s' % fileNameStr)
        classifierResult = classify0(vectorUnderTest, trainingMat, hwLabels, 3)
        print( "the classifier came back with: %d, the real answer is: %d" % (classifierResult, classNumStr))
        if (classifierResult != classNumStr): errorCount += 1.0
    print("\nthe total number of errors is: %d" % errorCount)
    print("\nthe total error rate is: %f" % (errorCount / float(mTest)))

总结

k-近邻算法是分类数据最简单最有效的算法。它必须保存全部数据集,如果训练数据集的很大,会占用大量的存储空间。此外,由于必须对数据集中的每个数据计算距离值,实际使用时可能非常耗时。
k-近邻算法的另一个缺陷是它无法给出任何数据的基础结构信息,因此我们也无法知晓平均
实例样本和典型实例样本具有什么特征。下一章(第3章 决策树)我们将使用概率测量方法处理分类问题,该算法可以解决这个问题。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值