《机器学习实战》 笔记(一):K-近邻算法

一、K-近邻算法

1.1 k-近邻算法简介

简单的说,K-近邻算法采用测量不同特征值之间的距离的方法进行分类。
在这里插入图片描述

1.2 原理

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

1.3 流程

在这里插入图片描述

1.4 实例讲解

确定一部电影是动作片还是爱情片。首先我们需要知道位置电源的打斗镜头和接吻镜头数。计算出样本集中所有电影与位置电影的距离,找出k个距离最近的电影,统计最近的k个电影的电影类型,找出出现最多的电影类型。
在这里插入图片描述
在这里插入图片描述
假定k=3,则三个最靠近的电影依次是He’s Not Really into Dudes、 Beautiful Woman和California Man。所以可以判定出位置电影是爱情片。

1.5 Python3.x代码实现

#coding=utf-8
_date_ = '2018/10/23 14:44'
_author_ = 'Cxy'
# KNN算法实现
from numpy import *  # 导入科学计算包Numpy
import operator  # 导入运算符模块

# 创建数据集和标签
def createDataSet():
    # 四组二维特征
    group = array([[1.0,1.1],[1.0,1.0],[0,0],[0,0.1]])
    # 四组特征的标签
    labels = ['A', 'A', 'B', 'B']
    return group, labels

# print(group)
# print(labels)
'''
分类函数
inX:用于分类的输入向量
dataSet:输入的训练样本集
labels:标签向量
k:用于选择最近邻的数目
'''
def classify0(inX, dataSet, labels, k):
    # 距离计算
    dataSetSize = dataSet.shape[0]  # shape[0]返回dataSet的行数
    # tile(A,reps) tile共有2个参数,A指待输入数组,reps则决定A重复的次数
    diffMat = tile(inX, (dataSetSize, 1)) - dataSet  # 二维特征相减
    # print(diffMat)
    sqDiffMat = diffMat**2  # 二维特征相减后平方
    sqDistances = sqDiffMat.sum(axis=1)  # sum()所有元素相加,0列相加,1行相加
    distances = sqDistances**0.5  # 开方,计算出距离
    # print(distances)
    sortedDistIndicies = distances.argsort()  # 返回distances中元素从小到大排序后的索引值
    # print(sortedDistIndicies)

    classCount = {}  # 记录类别次数
    for i in range(k):
        # 提取前k个元素的类别
        voteIlabel = labels[sortedDistIndicies[i]]
        # print(sortedDistIndicies[i])
        # 计算类别出现的次数 dict.get(key,default=None),字典的get()方法,返回指定键的值
        classCount[voteIlabel] = classCount.get(voteIlabel, 0) + 1
    # itemgetter(1)按字典中的键进行排序  itemgetter(0)按字典中的值进行排序 reverse降序排序
    sortedClassCount = sorted(classCount.items(), key=operator.itemgetter(1), reverse=True)
    # print(sortedClassCount)
    return sortedClassCount[0][0] # 返回次数最多的类别
group, labels = createDataSet()
print(classify0([0,0], group, labels, 3))


运行结果,如下图所示:
在这里插入图片描述

二、使用k-近邻算法改进约会网站配对效果

2.1 背景

海伦一直使用在线约会网站寻找适合自己的约会对象。尽管约会网站会推荐不同的人选,但她没有从中找到喜欢的人。经过一番总结,她发现曾交往过三种类型的人:
 不喜欢的人
 魅力一般的人
 极具魅力的人
海伦收集约会数据已经有了一段时间,她把这些数据存放在文本文件datingTestSet.txt中,每个样本数据占据一行,总共有1000行。海伦的样本主要包含以下3种特征:
 每年获得的飞行常客里程数
 玩视频游戏所耗时间百分比
 每周消费的冰淇淋公升数
前三列为特征,最后一列为标签。
在这里插入图片描述

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

# 读取数据
def file2matrix(filename):
    fr = open(filename)
    arrayOLines = fr.readlines()  # 读取文本每行
    numberOfLines = len(arrayOLines)  # 获取文本行数
    returnMat = zeros((numberOfLines, 3))  # 创建零矩阵,文本行数 x 3列
    classLabelVector = []  # 类别标签特征
    index = 0
    for line in arrayOLines:
        line = line.strip()  # 忽略回车字符
        listFromLine = line.split('\t')  # 将每行内容进行切分成列表
        returnMat[index, :] = listFromLine[0:3]  # 将前三列元素存储到矩阵中
        # 根据喜欢的程度进行分类,1代表不喜欢,2代表魅力一般,3代表极具魅力
        if listFromLine[-1] == 'didntLike':
            classLabelVector.append(1)
        elif listFromLine[-1] == 'smallDoses':
            classLabelVector.append(2)
        elif listFromLine[-1] == 'largeDoses':
            classLabelVector.append(3)
        index += 1
    return returnMat, classLabelVector
datingDataMat, datingLabels = file2matrix('datingTestSet.txt')
print(datingDataMat)
print(datingLabels)

运行结果,如下图所示:
在这里插入图片描述

2.3 分析数据:创建散点图

from numpy import *  # 导入科学计算包Numpy
import matplotlib.pyplot as plt
# 读取数据
def file2matrix(filename):
    fr = open(filename)
    arrayOLines = fr.readlines()  # 读取文本每行
    numberOfLines = len(arrayOLines)  # 获取文本行数
    returnMat = zeros((numberOfLines, 3))  # 创建零矩阵,文本行数 x 3列
    classLabelVector = []  # 类别标签特征
    index = 0
    for line in arrayOLines:
        line = line.strip()  # 忽略回车字符
        listFromLine = line.split('\t')  # 将每行内容进行切分成列表
        returnMat[index, :] = listFromLine[0:3]  # 将前三列元素存储到矩阵中
        # 根据喜欢的程度进行分类,1代表不喜欢,2代表魅力一般,3代表极具魅力
        if listFromLine[-1] == 'didntLike':
            classLabelVector.append(1)
        elif listFromLine[-1] == 'smallDoses':
            classLabelVector.append(2)
        elif listFromLine[-1] == 'largeDoses':
            classLabelVector.append(3)
        index += 1
    return returnMat, classLabelVector

datingDataMat, datingLabels = file2matrix('datingTestSet.txt')
print(datingDataMat)
print(datingLabels)

# 数据可视化
fig = plt.figure()
ax = fig.add_subplot(111)
ax.scatter(datingDataMat[:,1], datingDataMat[:, 2],15.0*array(datingLabels), 15.0*array(datingLabels))
plt.show()

运行结果如下图所示:

在这里插入图片描述

2.4 准备数据:归一化数值

在这里插入图片描述
如上图所示的四组数据,若要计算样本3和4之间的距离,可是用如下方法:
在这里插入图片描述
可发现,数值差值最大的数学对计算结果影响最大。但海伦认为这三种特征是同等重要,此时我们通常采用方法是数值归一化,入江取值处理为0到1或-1到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)
其中min和max分别是数据集中的最小特征值和最大特征值。实现代码如下:

from numpy import *  # 导入科学计算包Numpy
# 读取数据
def file2matrix(filename):
    fr = open(filename)
    arrayOLines = fr.readlines()  # 读取文本每行
    numberOfLines = len(arrayOLines)  # 获取文本行数
    returnMat = zeros((numberOfLines, 3))  # 创建零矩阵,文本行数 x 3列
    classLabelVector = []  # 类别标签特征
    index = 0
    for line in arrayOLines:
        line = line.strip()  # 忽略回车字符
        listFromLine = line.split('\t')  # 将每行内容进行切分成列表
        returnMat[index, :] = listFromLine[0:3]  # 将前三列元素存储到矩阵中
        # 根据喜欢的程度进行分类,1代表不喜欢,2代表魅力一般,3代表极具魅力
        if listFromLine[-1] == 'didntLike':
            classLabelVector.append(1)
        elif listFromLine[-1] == 'smallDoses':
            classLabelVector.append(2)
        elif listFromLine[-1] == 'largeDoses':
            classLabelVector.append(3)
        index += 1
    return returnMat, classLabelVector

datingDataMat, datingLabels = file2matrix('datingTestSet.txt')
print(datingDataMat)
print(datingLabels)
# 归一化特征值
def autoNorm(dataSet):
    minVals = dataSet.min(0)  # 获取每列的最小值
    maxVals = dataSet.max(0)  # 获取每列的最大值
    ranges = maxVals - minVals  # 获取最大值减最小值的范围
    normDataSet = zeros(shape(dataSet))  # shape(dataSet)返回dataSet的矩阵行列数
    m = dataSet.shape[0]  # shape[0]返回矩阵的行数
    normDataSet = dataSet - tile(minVals, (m, 1))  # 原始值减去最小值
    normDataSet = normDataSet/tile(ranges, (m, 1))
    return normDataSet, ranges, minVals

normDataSet, ranges, minVals = autoNorm(datingDataMat)
print(normDataSet)
print(ranges)
print(minVals)

运行结果:
在这里插入图片描述

2.5 测试算法:验证分类器

用已有数据的90%作为训练样本,其余10%数据去测试分类器,检测分类器的正确率。错误率即给出错误结果的次数除以测试数据的总数。
代码如下:

# KNN算法实现
from numpy import *  # 导入科学计算包Numpy
import operator  # 导入运算符模块
import matplotlib
import matplotlib.pyplot as plt
'''

# 分类函数
# inX:用于分类的输入向量
# dataSet:输入的训练样本集
# labels:标签向量
# k:用于选择最近邻的数目

def classify0(inX, dataSet, labels, k):
    # 距离计算
    dataSetSize = dataSet.shape[0]  # shape[0]返回dataSet的行数
    # tile(A,reps) tile共有2个参数,A指待输入数组,reps则决定A重复的次数
    diffMat = tile(inX, (dataSetSize, 1)) - dataSet  # 二维特征相减
    # print(diffMat)
    sqDiffMat = diffMat**2  # 二维特征相减后平方
    sqDistances = sqDiffMat.sum(axis=1)  # sum()所有元素相加,0列相加,1行相加
    distances = sqDistances**0.5  # 开方,计算出距离
    # print(distances)
    sortedDistIndicies = distances.argsort()  # 返回distances中元素从小到大排序后的索引值
    # print(sortedDistIndicies)

    classCount = {}  # 记录类别次数
    for i in range(k):
        # 提取前k个元素的类别
        voteIlabel = labels[sortedDistIndicies[i]]
        # print(sortedDistIndicies[i])
        # 计算类别出现的次数 dict.get(key,default=None),字典的get()方法,返回指定键的值
        classCount[voteIlabel] = classCount.get(voteIlabel, 0) + 1
    # itemgetter(1)按字典中的键进行排序  itemgetter(0)按字典中的值进行排序 reverse降序排序
    sortedClassCount = sorted(classCount.items(), key=operator.itemgetter(1), reverse=True)
    # print(sortedClassCount)
    return sortedClassCount[0][0] # 返回次数最多的类别
# group, labels = createDataSet()
# print(classify0([0,0], group, labels, 3))


# 读取数据
def file2matrix(filename):
    fr = open(filename)
    arrayOLines = fr.readlines()  # 读取文本每行
    numberOfLines = len(arrayOLines)  # 获取文本行数
    returnMat = zeros((numberOfLines, 3))  # 创建零矩阵,文本行数 x 3列
    classLabelVector = []  # 类别标签特征
    index = 0
    for line in arrayOLines:
        line = line.strip()  # 忽略回车字符
        listFromLine = line.split('\t')  # 将每行内容进行切分成列表
        returnMat[index, :] = listFromLine[0:3]  # 将前三列元素存储到矩阵中
        # 根据喜欢的程度进行分类,1代表不喜欢,2代表魅力一般,3代表极具魅力
        if listFromLine[-1] == 'didntLike':
            classLabelVector.append(1)
        elif listFromLine[-1] == 'smallDoses':
            classLabelVector.append(2)
        elif listFromLine[-1] == 'largeDoses':
            classLabelVector.append(3)
        index += 1
    return returnMat, classLabelVector

datingDataMat, datingLabels = file2matrix('datingTestSet.txt')
print(datingDataMat)
print(datingLabels)

# # 数据可视化
# fig = plt.figure()
# ax = fig.add_subplot(111)
# ax.scatter(datingDataMat[:,1], datingDataMat[:, 2],15.0*array(datingLabels), 15.0*array(datingLabels))
# plt.show()

# 归一化特征值
def autoNorm(dataSet):
    minVals = dataSet.min(0)  # 获取每列的最小值
    maxVals = dataSet.max(0)  # 获取每列的最大值
    ranges = maxVals - minVals  # 获取最大值减最小值的范围
    normDataSet = zeros(shape(dataSet))  # shape(dataSet)返回dataSet的矩阵行列数
    m = dataSet.shape[0]  # shape[0]返回矩阵的行数
    normDataSet = dataSet - tile(minVals, (m, 1))  # 原始值减去最小值
    normDataSet = normDataSet/tile(ranges, (m, 1))
    return normDataSet, ranges, minVals

normDataSet, ranges, minVals = autoNorm(datingDataMat)
print(normDataSet)
print(ranges)
print(minVals)
#
# 计算错误率
def datingClassTest():
    # 取所有数据的10%
    hoRatio = 0.10
    # 打开文件,获取特征矩阵和分类向量
    datingDataMat,datingLabels = file2matrix('datingTestSet.txt')
    # 数据归一化
    normMat, ranges, minVals = autoNorm(datingDataMat)
    # 获取normMat的行数
    m = normMat.shape[0]
    # 测试数据的10%
    numTestVecs = int(m*hoRatio)
    # 分类器错误计数
    errorCount = 0.0

    for i in range(numTestVecs):
        classifierResult = classify0(normMat[i, :], normMat[numTestVecs:m, :], datingLabels[numTestVecs:m], 4)
        print("分类结果:%d\t真实类别:%d" % (classifierResult, datingLabels[i]))
        if classifierResult != datingLabels[i]:
            errorCount += 1.0
    print(errorCount,numTestVecs)
    print("错误率:%f%%" % (errorCount/float(numTestVecs)*100))
datingClassTest()

运行结果如下图所示:
在这里插入图片描述

2.6 使用算法:构建完成系统

通过输入某个人的信息 ,程序会给出海伦对对方喜欢的程度。

import numpy as np
import operator

def classify0(inX, dataSet, labels, k):
    dataSetSize = dataSet.shape[0]
    diffMat = np.tile(inX, (dataSetSize, 1)) - dataSet
    sqDiffMat = diffMat**2
    sqDistances = sqDiffMat.sum(axis=1)
    distances = sqDistances**0.5
    sortedDistIndices = distances.argsort()
    classCount = {}
    for i in range(k):
        voteIlabel = labels[sortedDistIndices[i]]
        classCount[voteIlabel] = classCount.get(voteIlabel, 0) + 1
    sortedClassCount = sorted(classCount.items(), key=operator.itemgetter(1), reverse=True)
    return sortedClassCount[0][0]

def file2matrix(filename):
    fr = open(filename)
    arrayOLines = fr.readlines()  # 读取文本每行
    numberOfLines = len(arrayOLines)  # 获取文本行数
    returnMat = np.zeros((numberOfLines, 3))  # 创建零矩阵,文本行数 x 3列
    classLabelVector = []  # 类别标签特征
    index = 0
    for line in arrayOLines:
        line = line.strip()  # 忽略回车字符
        listFromLine = line.split('\t')  # 将每行内容进行切分成列表
        returnMat[index, :] = listFromLine[0:3]  # 将前三列元素存储到矩阵中
        # 根据喜欢的程度进行分类,1代表不喜欢,2代表魅力一般,3代表极具魅力
        if listFromLine[-1] == 'didntLike':
            classLabelVector.append(1)
        elif listFromLine[-1] == 'smallDoses':
            classLabelVector.append(2)
        elif listFromLine[-1] == 'largeDoses':
            classLabelVector.append(3)
        index += 1
    return returnMat, classLabelVector

def autoNorm(dataSet):
    minVals = dataSet.min(0)
    maxVals = dataSet.max(0)
    ranges = maxVals - minVals
    normDataSet = np.zeros(np.shape(dataSet))
    m = dataSet.shape[0]
    normDataSet = dataSet - np.tile(minVals, (m, 1))
    normDataSet = normDataSet / np.tile(ranges, (m, 1))
    return normDataSet, ranges, minVals

def classifyPerson():
    # 输出结果列表,分为三种
    # resultList = ['not at all', 'in small doses', 'in large doses']
    resultList = ['讨厌', '有些喜欢', '非常喜欢']
    # 用户输入三维特征
    percentTats = float(input("玩视频游戏所耗时间百分比:"))
    ffMiles = float(input("每年获得的飞行常客里程数:"))
    iceCream = float(input("每周消费的冰激淋公升数:"))
    filename = "datingTestSet.txt"
    datingDataMat, datingLabels = file2matrix(filename)
    normMat, ranges, minVals = autoNorm(datingDataMat)
    inArr = np.array([ffMiles, percentTats,iceCream])
    norminArr = (inArr - minVals) / ranges
    classifierResult = classify0(norminArr, normMat, datingLabels, 3)
    print("你可能%s这个人" % (resultList[classifierResult - 1]))

if __name__ == '__main__':
    classifyPerson()

运行结果如下图所示:
在这里插入图片描述

三、实例:手写体识别

3.1 背景

需要识别的数字已经使用图形处理软件,处理成具有相同的色彩和大小①:宽高是32像素× 32像素的黑白图像,并将图像转换成文本形式,如下图所示:
在这里插入图片描述

3.2 步骤1:图像转换为向量

编写函数,循环读出文件的前32行,并将每行的头32个字符值存储在NumPy数组中,最后返回数组。
代码如下:

import numpy as np
def img2vector(filename):
    returnVect = np.zeros((1, 1024)) # 创建1x1024零向量
    fr = open(filename) # 打开文件
    for i in range(32): # 按行读取
        lineStr = fr.readline()
        for j in range(32):   # 每一行读取的前32个元素添加到returnVect中
            returnVect[0, 32*i + j] = int(lineStr[j])
    return returnVect # 返回1x1024向量
testVector = img2vector('testDigits/0_13.txt')
print(testVector[0, 0:31]) # 测试

运行结果如下图所示:
在这里插入图片描述

3.3 测试算法:使用k-近邻算法识别手写数字

将数据输入到分类器,检测分类器的执行效果。编写函数handwritingClassTest()代码,并从os模块中导入函数listdir,其可列出给定目录的文件名。
完成代码:

import numpy as np
import operator
from os import listdir

def classify0(inX, dataSet, labels, k):
    dataSetSize = dataSet.shape[0]
    diffMat = np.tile(inX, (dataSetSize, 1)) - dataSet
    sqDiffMat = diffMat**2
    sqDistances = sqDiffMat.sum(axis=1)
    distances = sqDistances**0.5
    sortedDistIndices = distances.argsort()
    classCount = {}
    for i in range(k):
        voteIlabel = labels[sortedDistIndices[i]]
        classCount[voteIlabel] = classCount.get(voteIlabel, 0) + 1
    sortedClassCount = sorted(classCount.items(), key=operator.itemgetter(1), reverse=True)
    return sortedClassCount[0][0]

def img2vector(filename):
    returnVect = np.zeros((1, 1024)) # 创建1x1024零向量
    fr = open(filename) # 打开文件
    for i in range(32): # 按行读取
        lineStr = fr.readline()
        for j in range(32):   # 每一行读取的前32个元素添加到returnVect中
            returnVect[0, 32*i + j] = int(lineStr[j])
    return returnVect # 返回1x1024向量
testVector = img2vector('testDigits/0_13.txt')
print(testVector[0, 0:31])  # 测试

def handwritingClassTest():
    hwLabels = []  # 测试机标签列表
    trainingFileList = listdir('trainingDigits') # 返回trainingDigits目录下的文件名
    m = len(trainingFileList)  # 返回文件夹下的文件个数
    trainingMat = np.zeros((m, 1024))  # 训练矩阵m x 1024
    for i in range(m): # 获取训练集类别
        fileNameStr = trainingFileList[i]   # 获得文件名
        fileStr = fileNameStr.split('.')[0]
        classNumStr = int(fileStr.split('_')[0])  # 获取分类标签
        hwLabels.append(classNumStr)  # 将标签加入列表
        trainingMat[i, : ] = img2vector('trainingDigits/%s' % (fileNameStr)) # 将每一个文件的1x1024矩阵装进的traningMa矩阵中

    testFileList = listdir('testDigits') # 返回testDigits目录下的文件列表
    errorCount = 0.0  # 识别错误计数
    mTest = len(testFileList) # 文件个数
    for i in range(mTest): # 从文件中解析出测试集的类别并进行分类测试
        fileNameStr = testFileList[i] # 获取文件名
        fileStr = fileNameStr.split('.')[0]
        classNumStr = int(fileStr.split('_')[0]) # 获取文件类别
        vectorUnderTest = img2vector('testDigits/%s' % fileNameStr) # 获取文件向量
        classifierResult = classify0(vectorUnderTest, trainingMat, hwLabels, 3) # 获取分类结果
        print('分类返回结果为%d\t真是结果为%d' % (classifierResult, classNumStr))
        if(classifierResult != classNumStr):
            errorCount += 1.0
    print("总共错了%d个数据\n错误率%f%%" % (errorCount, errorCount/mTest * 100))
handwritingClassTest()

运行结果如下图所示:
在这里插入图片描述

四、k-近邻算法缺点

(1)k-近邻算法必须保存全部数据集,如果训练数据集的很大,必须使用大量的存储空间。
(2)由于必须对数据集中的每个数据计算距离值,实际使用时可能非常耗时。
(3)k-近邻算法的另一个缺陷是它无法给出任何数据的基础结构信息,因此我们也无法知晓平均实例样本和典型实例样本具有什么特征。

参考文献:
[1]《python机器学习实战》
[2]https://blog.csdn.net/c406495762/article/category/7029976

猜你喜欢

转载自blog.csdn.net/Cuixinyang19_/article/details/83309296