《机器学习实战》 K-近邻算法(第2章)

运行平台: Windows
Python版本: Python3.9
IDE: VS code


前言

  随着人工智能的不断发展,机器学习这门技术也越来越重要,很多人都开启了学习机器学习,本文就介绍了机器学习的K-近邻算法(KNN)。

提示:以下是本篇文章正文内容,下面案例可供参考

2.1 K-近邻算法概述

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

  它的工作原理是:存在一个样本数据集合,也称作训练样本集,并且样本集中每个数据都存在标签,即我们知道样本集中每一数据与所属分类的对应关系。输人没有标签的新数据后,将新数据的每个特征与样本集中数据对应的特征进行比较,然后算法提取样本集中特征最相似数据(最近邻)的分类标签。一般来说,我们只选择样本数据集中前k个最相似的数据,这就是k-近邻算法中k的出处,通常k是不大于20的整数。最后,选择k个最相似数据中出现次数最多的分类,作为新数据的分类。
  举个简单的案例,利用KNN算法判断电影是爱情片还是动作片

项目打斗镜头数接吻镜头数电影的类别
California Man1101爱情片
He 's Not Really into Dudes589爱情片
Beautiful Woman1085动作片
Kevin Longblade1158动作片

                  表2-1
即使不知道未知电影属于哪种类型,我们也可以通过某种方法计算出来。首先计算未知电影与样本集中其他电影的距离,如表2-2所示。此处暂时不要关心如何计算得到这些距离值,使用Python实现电影分类应用时,会提供具体的计算方法。
在这里插入图片描述

电影名称未知电影的距离
California Man5
He 's Not Really into Dudes18.7
Beautiful Woman20.3
Kevin Longblade63

现在我们得到了样本集中所有电影与未知电影的距离, 按照距离递增排序, 可以找到4个距离最近的电影。假定k=3,三个最靠近的电影依次California Man、He 's Not Really into Dudes 、Beautiful Woman和、Kevin Longblade 从 4-近邻算法按照距离最近的三部电影的类型,决定未知电影的类型,而这三部电影全是爱情片,因此我们判定未知电影是爱情片。

2.1.1创建分类器

'''创建数据集'''
def createdataset():
    group=[[1,1.2],[1,3],[0.2,0],[0.2,0.1]]
    labels=["a","a","b","b"]
    return group,labels
group,labels=createdataset()
#print(group,labels)

'''创建分类器'''
def classify(input,dateset,labels,k):  #输入的数据,训练样本集,标签,最近邻的个数
    datasize=np.shape(dateset)[0]
    input=np.tile(input,(datasize,1))  #将输入的数据扩大到和训练样本集的维度一样
    #计算距离
    ranges=input-dateset #计算差值[[-0.8 -1.2],[-0.8 -3. ],[-0.6  0. ],[ 0.  -0.1]]
    square_ranges=ranges**2 #计算差值的平方 [[0.64 1.44],[0.64 9.  ],[0.36 0.  ],[0.   0.01]]
    square_distance=square_ranges.sum(axis=1)  #求和[2.08 9.64 0.36 0.01]
    distance=square_distance**0.5  #开平方 [1.44222051 3.10483494 0.6        0.1       ]
    #对距离进行排序
    sorted_distance=np.argsort(distance) #[3 2 0 1]
    #选择最小的距离
    classcount={}
    for i in range(k): #循环k次
        votelabels=labels[sorted_distance[i]]
        classcount[votelabels]=classcount.get(votelabels, 0)+1  #获取标签的个数
        #对字典进行排序
        sorted_classcount = sorted(classcount.items(), key=operator.itemgetter(1), reverse=True)  
    print("类别为:",sorted_classcount[0][0])
    return sorted_classcount[0][0]
classify([2,0],group,labels,3) #测试结果:类别为: b


'''绘制图像'''
x=[]
y=[]
#z=[]
for g in group:
    x.append(g[0])  #x轴
    y.append(g[1])  #y轴
    #z.append(g[2])  #z轴
ax=plt.figure()
#ax = plt.figure().add_subplot(111, projection = '3d') #创建三维图的画布,参数111的意思是:将画布分割成1行1列,图像画在从左到右从上到下的第1块
plt.scatter(x,y)
plt.scatter(2,0,c="red")
plt.show()

在这里插入图片描述

2.1.2 如何测试算法

  上文我们已经使用k-近邻算法构造了第一个分类器,也可以检验分类器给出的答案是否符合我们的预期。读者可能会问:“分类器何种情况下会出错?”或 者 “答案是否总是正确的?” 答案是否定的,分类器并不会得到百分百正确的结果,我们可以使用多种方法检测分类器的正确率。此外分类器的性能也会受到多种因素的影响,如分类器设置和数据集等。不同的算法在不同数据集上的表现可能完全不同,这也是本部分的6章都在讨论分类算法的原因所在。
  为了测试分类器的效果,我们可以使用已知答案的数据,当然答案不能告诉分类器,检验分类器给出的结果是否符合预期结果。通过大量的测试数据,我们可以得到分类器的错误率—— 分类器给出错误结果的次数除以测试执行的总数。错误率是常用的评估方法,主要用于评估分类器在某个数据集上的执行效果。

2.2 使用KNN算法改进约会网站的配对效果

2.2.1 文本文件解析

  我的朋友海伦一直使用在线约会网站寻找适合自己的约会对象。尽管约会网站会推荐不同的人选,但她没有从中找到喜欢的人。经过一番总结,她发现曾交往过三种类型的人:
   ○ 不喜欢的人
   ○ 魅力一般的人
  ○ 极具魅力的人
  尽管发现了上述规律,但海伦依然无法将约会网站推荐的匹配对象归人恰当的分类。她觉得
可以在周一到周五约会那些魅力一般的人,而周末则更喜欢与那些极具魅力的人为伴。海伦希望我们的分类软件可以更好地帮助她将匹配对象划分到确切的分类中。此外海伦还收集了一些约会网站未曾记录的数据信息,她认为这些数据更有助于匹配对象的归类。
  海伦收集约会数据巳经有了一段时间,她把这些数据存放在文本文件(datingTestSet2.txt)中,每个样本数据占据一行,总共有1000行。海伦的样本主要包含以下3种特征:
  ○ 每年获得的飞行常客里程数
  ○ 玩视频游戏所耗时间百分比
  ○ 每周消费的冰淇淋公升数
  在将上述特征数据输人到分类器之前,必须将待处理数据的格式改变为分类器可以接受的格式。在kNN.py中创建名为file2matrix的函数,以此来处理输人格式问题。该函数的输人为文件名字符串, 输出为训练样本矩阵和类标签向量。

'''文本文件解析数据'''
def file2matrix(filename):
    fr=open(filename)
    array_lines=fr.readlines()   #读取文件
    number_lines=len(array_lines) #获取文件的行数  
    #创建返回的Numpy矩阵
    return_mat=np.zeros((number_lines,3))  #number_lines行,3列的矩阵
    class_label_vector=[]
    index=0
    for i in array_lines:
         # 移除字符串头尾指定的字符,默认去除换行符和空格,43757   7.882601        1.332446        largeDoses
        line = i.strip() 
        # 将行内元素以'\t'为分隔符变成数组,['43757', '7.882601', '1.332446', 'largeDoses']
        list_fromline = line.split("\t")
        return_mat[index,:]=list_fromline[0:3] #将前三个元素逐个赋值给returnMat
        #print(return_mat)
        #记录每行最后一个元素,datingTestSet.txt每行最后一个元素为该行数据的类别
        class_label_vector.append(int(list_fromline[-1])) 
        index+=1
        
    # 返回特征值:[[每年获取的飞行常客里程数、玩游戏的时间所耗时间百分比、每周所消费的冰淇淋公升数]],[类别]
    return return_mat, class_label_vector
#file2matrix("datingTestSet2.txt")

数据归一化

问题:为什么数据要归一化?
答:不同数据之间因为单位不同,导致数值差距十分大,容易产生预测结果被某一项特征值主导。
补充知识点:
  最值归一化:适用于分布有明显边界的情况。
  Xscale=(X-Xmin)/Xmax-Xmin,将所有数据映射到0`1之间。
  均值方差归一化:适用于数据分布没有明显的边界,但数据存在极端值。Xscale=(X-Xmean )/S,把所有数据归一化到均值为0,方差为1的分布中。

玩视频游戏所耗时间百分比毎年获得的飞行常客里程数毎周消费的冰淇淋公升数样本标签
0.84000.51
121340000.93
0200001.12
76320000.12

  在处理这种不同取值范围的特征值时,我们通常采用的方法是将数值归一化,如将取值范围处理为0到1或者-1到1之间。下面的公式可以将任意取值范围的特征值转化为0到1区间内的值:
  newValue = {oldValue-min)/(max-min)
  其中min和max乂分别是数据集中的最小特征值和最大特征值。虽然改变数值取值范围增加了分类器的复杂度,但为了得到准确结果,我们必须这样做。

归一化前的三维图像

在这里插入图片描述

归一化后

在这里插入图片描述
从上面的三维图像中,俩这相比之下,可以看出归一化后的数据明显更加集中了。

归一化代码

'''归一化数据,防止数据集中部分特征值过大,影响结果'''
def autonorm(dataset):
    min_val=dataset.min(0)  #每一列的最小值[0.       0.       0.001156]
    max_val=dataset.max(0)  #每一列的最大值[9.1273000e+04 2.0919349e+01 1.6955170e+00]
    ranges=max_val-min_val  #差值[9.1273000e+04 2.0919349e+01 1.6943610e+00]
    num=dataset.shape[0] #获取行数1000
    #创建最小值和差值的矩阵
    array_min=np.tile(min_val,(num,1))
    array_ranges=np.tile(ranges,(num,1))
    #归一化数据
    norm_data=(dataset-array_min)/array_ranges
    return norm_data,ranges,min_val  #返回归一化后的数据
norm_data,ranges,min_val=autonorm(mat)

x=[]
y=[]
z=[]
for g in norm_data:
    x.append(g[0])  #x轴
    y.append(g[1])  #y轴
    z.append(g[2])  #z轴
#ax=plt.figure()
ax = plt.figure().add_subplot(111, projection = '3d') #创建三维图的画布,参数111的意思是:将画布分割成1行1列,图像画在从左到右从上到下的第1块
ax.scatter(x,y,z)
#plt.scatter(2,0,c="red")
plt.show()

2.2.2 测试算法

  机器学习算法一个很重要的工作就是评估算法的正确率,通常我们只提供已有数据的90%作为训练样本来训练分类器,而使用其余的10数据去测试分类器,检测分类器的正确率。本书后续章节还会介绍一些高 级方法完成同样的任务,这里我们还是采用最原始的做法。需要注意的是,10%的测试数据应该是随机选择的,由于海伦提供的数据并没有按照特定目的来排序,所以我们可以随意选择10%数据而不影响其随机性^
  前面我们巳经提到可以使用错误率来检测分类器的性能。对于分类器来说,错误率就是分类器给出错误结果的次数除以测试数据的总数,完美分类器的错误率为0,而错误率为1.0的分类器不会给出任何正确的分类结果。代码里我们定义一个计数器变量,每次分类器错误地分类数据, 计数器就加1, 程序执行完成之后计数器的结果除以数据点总数即是错误率。

'''测试算法'''
#错误率=5%
def test():
    #归一化前的数据
    mat,labels=file("datingTestSet2.txt") #读取数据和标签
    #归一化后的数据
    norm_data,ranges,min_val=autonorm(mat) #归一化后的新数据
    #print(norm_data)
    num_test=int(mat.shape[0]*0.1) #测试的次数100
    m=mat.shape[0] #数据的行数1000
    error_count=0 #错误数
    #测试次数
    for i in range(num_test):
        classify_result=classify(norm_data[i,:], #归一化后的数据作为测试集
                                norm_data[num_test:m,], #100到10000行的数据
                                labels[num_test:m], #100到1000行数据的标签
                                3                   #k值
                                )
        #print("分类器返回的值:%d,真实值:%s"%(classify_result,labels[i]))
        if classify_result!=labels[i]:
            error_count+=1
    print("错误率={}%".format((float(error_count)/float(num_test))*100))
test()
错误率=5%

2.2.3 构建完整可用系统

  我们会给海伦一小段程序,通过该程序海伦会在约会网站上找到某个人并输入他的信息。程序会给出她对对方喜欢程度的预测值。

'''构建完整的系统'''
def person():
    result_list=["不喜欢","魅力一般","极具魅力"] #标签
    ffmiles = float(input("每年获取的飞行常客里程数"))
    percent_tats=float(input("玩视频游戏的时间百分比"))
    icecream = float(input("每年所消费的冰淇淋公升数"))
    #归一化后的的数据作为训练集
    mat,dating_labels=file("datingTestSet2.txt")
    norm_data,ranges,min_val=autonorm(mat)
    #输入的数据
    inarr = np.array([ffmiles,percent_tats,icecream])
    #放入分类器中
    result=classify((inarr-min_val)/ranges,
                    norm_data,
                    dating_labels,
                    3)
    print("你将可能喜欢上这个人:",result_list[result-1])
person()

2.2.4 构建完整的可用系统

import numpy as np
import matplotlib.pyplot as plt
import operator
import os

'''创建数据集'''
def createdataset():
    group=[[1,1.2],[1,3],[0.2,0],[0.2,0.1]]
    labels=["a","a","b","b"]
    return group,labels
#group,labels=createdataset()
#print(group,labels)

'''创建分类器'''
def classify(input,dateset,labels,k):  #输入的数据,训练样本集,标签,最近邻的个数
    datasize=np.shape(dateset)[0]
    input=np.tile(input,(datasize,1))  #将输入的数据扩大到和训练样本集的维度一样
    #计算距离
    ranges=input-dateset #计算差值[[-0.8 -1.2],[-0.8 -3. ],[-0.6  0. ],[ 0.  -0.1]]
    square_ranges=ranges**2 #计算差值的平方 [[0.64 1.44],[0.64 9.  ],[0.36 0.  ],[0.   0.01]]
    square_distance=square_ranges.sum(axis=1)  #求和[2.08 9.64 0.36 0.01]
    distance=square_distance**0.5  #开平方 [1.44222051 3.10483494 0.6        0.1       ]
    #对距离进行排序
    sorted_distance=np.argsort(distance) #[3 2 0 1]
    #选择最小的距离
    classcount={}
    for i in range(k): #循环k次
        votelabels=labels[sorted_distance[i]]
        classcount[votelabels]=classcount.get(votelabels, 0)+1  #获取标签的个数
        #对字典进行排序
        sorted_classcount = sorted(classcount.items(), key=operator.itemgetter(1), reverse=True)  
    
    print("类别为:",sorted_classcount[0][0])
    return sorted_classcount[0][0]
classify([2,0],group,labels,3)

'''k-近邻算法改进约会网站的配对效果'''
def file(filename):
    fr=open(filename)
    array_lines=fr.readlines()  #读取全部文件
    number_lines=len(array_lines)  #计算样本数
    mat=np.zeros((number_lines,3)) #形成1000x3的矩阵
    dating_labels=[]
    index=0
    for i in array_lines:
        #去除换行符
        line=i.strip()  #去除开头或者是结尾的字符
        #去除换行符形成列表
        line=line.split("\t")  
        #获取样本的前三个数据
        mat[index,:]=line[0:3]  #将前三个元素逐个赋值给index这一行
        #获取标签
        dating_labels.append(int(line[-1]))
        index+=1

    return mat,dating_labels #返回数据和标签
mat,labels=file("datingTestSet2.txt")

'''归一化数据,防止数据集中部分特征值过大,影响结果'''
def autonorm(dataset):
    min_val=dataset.min(0)  #每一列的最小值[0.       0.       0.001156]
    max_val=dataset.max(0)  #每一列的最大值[9.1273000e+04 2.0919349e+01 1.6955170e+00]
    ranges=max_val-min_val  #差值[9.1273000e+04 2.0919349e+01 1.6943610e+00]
    num=dataset.shape[0] #获取行数1000
    #创建最小值和差值的矩阵
    array_min=np.tile(min_val,(num,1))
    array_ranges=np.tile(ranges,(num,1))
    #归一化数据
    norm_data=(dataset-array_min)/array_ranges
    return norm_data,ranges,min_val  #返回归一化后的数据
norm_data,ranges,min_val=autonorm(mat)

'''测试算法'''
def test():
    #归一化前的数据
    mat,labels=file("datingTestSet2.txt") #读取数据和标签
    #归一化后的数据
    norm_data,ranges,min_val=autonorm(mat) #归一化后的新数据
    #print(norm_data)
    num_test=int(mat.shape[0]*0.1) #测试的次数100
    m=mat.shape[0] #数据的行数1000
    error_count=0 #错误数
    #测试次数
    for i in range(num_test):
        classify_result=classify(norm_data[i,:], #归一化后的数据作为测试集
                                norm_data[num_test:m,], #100到10000行的数据
                                labels[num_test:m], #100到1000行数据的标签
                                3                   #k值
                                )
        #print("分类器返回的值:%d,真实值:%s"%(classify_result,labels[i]))
        if classify_result!=labels[i]:
            error_count+=1
    print("错误率={}%".format((float(error_count)/float(num_test))*100))
#test()

'''构建完整的系统'''
def person():
    result_list=["不喜欢","魅力一般","极具魅力"] #标签
    ffmiles = float(input("每年获取的飞行常客里程数"))
    percent_tats=float(input("玩视频游戏的时间百分比"))
    icecream = float(input("每年所消费的冰淇淋公升数"))
    #归一化后的的数据作为训练集
    mat,dating_labels=file("datingTestSet2.txt")
    norm_data,ranges,min_val=autonorm(mat)
    #输入的数据
    inarr = np.array([ffmiles,percent_tats,icecream])
    #放入分类器中
    result=classify((inarr-min_val)/ranges,
                    norm_data,
                    dating_labels,
                    3)
    print("你将可能喜欢上这个人:",result_list[result-1])
person()

文件

链接: https://pan.baidu.com/s/1wm9tMX9HJ1v9TR_w5qdCfw 提取码: vnyg

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值