机器学习入门——K-近邻算法实例练习(代码详细注解)

按照《机器学习实战》一书中的顺序进行学习,总结自己的理解。

一、K-近邻算法的思想

简单说,k近邻算法采用测量不同特征值之间的距离方法进行分类。

即本算法是一个分类算法。
问题格式
当输入一部电影M,包含特征值 (亲吻次数=5,打斗次数=100),问电影M的所属类别(爱情片or动作片)?
算法思路
1.首选要有样本测试集,即n部包含对应特征值和标签的电影列表,如:

机器学习入门——K-近邻算法实例练习(代码详细注解)_第1张图片
2.将电影M与n部电影作比较(通过欧氏距离公式来计算特征值之间的距离
3.将计算结果由小到大排序,找出距离最近的前3部(一般不大于20条)电影,如:机器学习入门——K-近邻算法实例练习(代码详细注解)_第2张图片
4.判断这三部已知电影的标签:
都是爱情片 => M为爱情片
2部是动作片,1部是爱情片 =>:M为动作片
……

二、代码案例
测试一

代码注解1:python中List、Array和numpy.array与数组的关系
代码注解2:tile函数的作用
代码注解3:argsort函数的作用
代码注解4:字典的get方法
代码注解5:max函数搭配字典用法

#导入科学计算包numpy
from numpy import *

#创建已知数据集
def createDataSet():
	#注解1:python中List、Array和numpy.array与数组的关系
    group = array([[1.0, 1.1], [1.0, 1.0], [0, 0], [0, 0.1]])
    labels = ['A', 'A', 'B', 'B']
    return group, labels

#Func:将所给新纪录进行分类;
#Param:
#inX-新纪录;
#dataSet-已有数据集;
#labels-已有数据集中每条记录的对应标签
#k:从前k个距离最近的点中进行判断类型
def classify0(inX, dataSet, labels, k):
    # 获取旧样本集的行数(记录数)
    dataSetSize = dataSet.shape[0]

    """一、根据欧式距离公式,计算输入记录与样本集中每条数据之间的距离 √(特征值a1-特征值a2)^2+(特征值b1-特征值b2)^2+(特征值c1-特征值c2)^2"""
    # 1.使用tile将输入的一条记录转为dataSetSize条(注解2:tile函数)
    # 2.通过矩阵来计算相减结果
    diffMat = tile(inX, (dataSetSize, 1)) - dataSet

    # 3.对相减结果取平方
    sqDiffMat = diffMat ** 2
    # 4.对每条记录,将不同特征值相减结果的平方求和
    sqDistances = sqDiffMat.sum(axis=1)
    # 5.开方,得到输入记录与样本集中每条记录的距离
    distances = sqDistances ** 0.5

    """二、从计算的结果中选取距离最小的前k个,判断这k个当中属于哪个标签类别的最多"""
    # 1.根据距离排序从小到大的排序,并返回其在原矩阵数组对应的下标值(注解3:argsort函數)
    sortedDistIndicies = distances.argsort()
    # 2.选择距离最小的前k个,并算出其中的每个标签类别的出现次数
    classCount = {}
    for i in range(k):
        # 获取第i个所属的标签类别
        voteIlabel = labels[sortedDistIndicies[i]]
        # 在标签类别字典中将该类别数目加1(注解4:字典的get方法)
        classCount[voteIlabel] = classCount.get(voteIlabel, 0) + 1
        
    # 3. 返回出现最多的那个类型
    # 利用max函数,直接返回字典中value最大的key(注解5:max函数搭配字典用法)
    maxClassCount = max(classCount, key=classCount.get)
    return maxClassCount


def test1():
    """
    第一个例子演示
    """
    group, labels = createDataSet()
    print(str(group))
    print(str(labels))
    print(classify0([0.1, 0.1], group, labels, 3))

运行结果:根据计算,前三个距离最近的记录中B类2个,A类1个,因此得出特征值为[0.1,0.1]的记录标签分类应该是B。

距离结果:[1.3453624  1.27279221 0.14142136 0.1]
距离最近3个中类别数目:{'B': 2, 'A': 1}
新样本分类:B
注解1:python中List、Array和numpy.array与数组的关系

 Python中的List可以当做数组使用。其中元素类型可能不同,因此保存的是对象的指针;即使保存同数据类型元素的列表[1,2,3],也需要三个指针和三个整数对象。当我们主要用数组进行数值运算时,这种结构显然不够高效。
 Python中的Array模块只支持一维数组,不支持多维数组,也没有各种运算函数。因而也不适合数值运算。
 NumPy中的array则支持多维数组,且可以使用多种索引方式和计算,因而经常在机器学习中被使用。

注解2:tile函数的作用

tile(x,y)函数的作用为:根据原矩阵将其扩充为x行y列的新矩阵,如:

对arr=[1,2,3],tile(inx, (3, 1))为

array([[1, 2, 3],
[1, 2, 3],
[1, 2, 3]])

对arr=[1,2,3],tile(inx, (3, 2))为

array([[1, 2, 3, 1, 2, 3],
[1, 2, 3, 1, 2, 3],
[1, 2, 3, 1, 2, 3]])

注解3:argsort函数的作用

argsort(a) 函数的作用是:将矩阵a中的元素从小到大排列,并根据其在原矩阵数组中的下标,输出到y。

例如:a=[3,-1,0], y=a.argsort(a) ,则y=[1,2,0]
a[1]最小,所以y[0]=1;
a[0]最大,所以y[2]=0…

注解4:字典的get方法

字典的map.get(k,d)方法,相当于从字典中查找是否存在参数k:如果存在,则返回对应的value值;不存在,则返回d.

例如:# map = {5:2,3:4}
map.get(3,0)返回的值是4;
map.get(1,0)返回值是0;

注解5:max函数搭配字典用法

1、max() 函数中没有 key 参数时,求的是 key 的最大值
2、max() 函数中有 key 参数时,求的是 value 最大的key值

例如:map = {5:2,3:4;1:6}
max(map) = 5
max(map,k=map.get) = 1

案例二 约会网站的测试方法

代码注解1:python中一次open后 “只能使用一次readlines ”的误解
代码注解2:numpy中对多维数组求最大最小值
代码注解3:numpy.array 的shape属性理解

from numpy import *

#分类函数,同上
def classify0(inX, dataSet, labels, k):
    # 获取旧样本集的行数(记录数)
    dataSetSize = dataSet.shape[0]

    """一、根据欧式距离公式,计算输入记录与样本集中每条数据之间的距离 √(特征值a1-特征值a2)^2+(特征值b1-特征值b2)^2+(特征值c1-特征值c2)^2"""
    # 1.使用tile将输入的一条记录转为dataSetSize条(注解2:tile函数)
    # 2.通过矩阵来计算相减结果
    diffMat = tile(inX, (dataSetSize, 1)) - dataSet

    # 3.对相减结果取平方
    sqDiffMat = diffMat ** 2
    # 4.对每条记录,将不同特征值相减结果的平方求和
    sqDistances = sqDiffMat.sum(axis=1)
    # 5.开方,得到输入记录与样本集中每条记录的距离
    distances = sqDistances ** 0.5

    """二、从计算的结果中选取距离最小的前k个,判断这k个当中属于哪个标签类别的最多"""
    # 1.根据距离排序从小到大的排序,并返回其在原矩阵数组对应的下标值(注解3:argsort函數)
    sortedDistIndicies = distances.argsort()
    # 2.选择距离最小的前k个,并算出其中的每个标签类别的出现次数
    classCount = {}
    for i in range(k):
        # 获取第i个所属的标签类别
        voteIlabel = labels[sortedDistIndicies[i]]
        # 在标签类别字典中将该类别数目加1(注解4:字典的get方法)
        classCount[voteIlabel] = classCount.get(voteIlabel, 0) + 1

    # 3. 返回出现最多的那个类型
    # 利用max函数,直接返回字典中value最大的key(注解5:max函数搭配字典用法)
    maxClassCount = max(classCount, key=classCount.get)
    return maxClassCount

# 读取文件中的数据并转为已有数据集矩阵
def file2matrix(filename):
    """
    导入训练数据
    :param filename: 数据文件路径
    :return: 数据矩阵returnMat和对应的类别classLabelVector
    """
    fr = open(filename)
    # 获得文件中已有数据集的行数(记录数)
    numberOfLines = len(fr.readlines())
    # 生成对应的空矩阵,zeros(2,3)就是生成一个 2*3的矩阵,各个位置上全是 0
    returnMat = zeros((numberOfLines, 3))
    classLabelVector = []  # 存储将要返回的标签列表
    # readlines后指针位于文档末尾,使用seek(0)将指针返回至文件首
    fr.seek(0)
    index = 0
    for line in fr.readlines():
        # str.strip()用于去除字符串首尾空格
        line = line.strip()
        # 以 '\t' 切割字符串
        listFromLine = line.split('\t')
        # 将每条记录的各个属性特征值存入矩阵
        returnMat[index, :] = listFromLine[0:3]
        # 每列的类别数据,就是 label 标签数据
        classLabelVector.append(int(listFromLine[-1]))
        index += 1
    # 返回数据矩阵returnMat和对应的类别classLabelVector
    return returnMat, classLabelVector


def autoNorm(dataSet):
    """
	将所给数据集的各条记录的特征值归一化,消除属性之间量级不同导致的影响
    :param dataSet: 数据集
    :return: 归一化后的数据集normDataSet,ranges和minVals即最小值与范围,并没有用到

    归一化公式:
    Y = (X-Xmin)/(Xmax-Xmin)
    其中的 min 和 max 分别是数据集中的最小特征值和最大特征值。该函数可以自动将数字特征值转化为0到1的区间。
    """
    # 计算每种属性的最大值、最小值、范围
    minVals = dataSet.min(0)
    maxVals = dataSet.max(0)
    # 极差
    ranges = maxVals - minVals

    #新建全0矩阵
    normDataSet = zeros(shape(dataSet))
    #获取原数据集行数
    m = dataSet.shape[0]
    # 生成与最小值之差组成的矩阵
    normDataSet = dataSet - tile(minVals, (m, 1))
    # 将最小值之差除以范围组成矩阵
    normDataSet = normDataSet / tile(ranges, (m, 1))  

    return normDataSet, ranges, minVals

# 测试约会网站,返回错误率
def datingClassTest():

    # 数据集中测试集的比例为hoRatio(训练集比例=1-hoRatio)
    hoRatio = 0.1
    # 从文件中加载数据
    datingDataMat, datingLabels = file2matrix('datingTestSet2.txt') 
    # 归一化数据
    normMat, ranges, minVals = autoNorm(datingDataMat)
    # m 表示数据的行数
    m = normMat.shape[0]
    # 设置用于测试的样本数量
    numTestVecs = int(m * hoRatio)
    print('测试样本数目为:', numTestVecs)
    errorCount = 0.0
    for i in range(numTestVecs):
        # 根据已有数据集,对100条数据逐个测试:
        #    计算每个数据与原数据集中每条记录的距离,取k=3来确定分类
        classifierResult = classify0(normMat[i, :], normMat[numTestVecs:m, :], datingLabels[numTestVecs:m], 3)
        print("分类器分类结果为 %d, 真实分类为 %d" % (classifierResult, datingLabels[i]))
        #将分类结果与真实分类情况作比,分了错误则错误次数+1
        if (classifierResult != datingLabels[i]): errorCount += 1.0
    #统计分类错误率
    print("分类错误率为: %f %%" % (errorCount / float(numTestVecs)*100))
    
if __name__ == '__main__':
    datingClassTest()

你可能感兴趣的:(机器学习,机器学习,算法)