笔记整理【机器学习实战】:k-近邻算法

KNN算法

概述

k-近邻算法概述,简单地说,根据测量不同特征值之间的距离进行分类。

基本思想与算法复杂度

思想:
训练样本集中每个数据都存在标签,输入没有标签的新数据,将新数据的每个特征与样本数据的对应的特征作比较,然后算法提取样本集中特征最相似数据的标签,即是认定为新数据的标签。k就是选择n个相似的样本数据,然后统计k个数据中最多的分类即称为新数据的分类标签。

算法复杂度:
KNN属于lazy-learning算法,分类器不用使用训练集进行训练,因此训练时间复杂度为0;分类时间复杂度为O(n);最终的时间复杂度为O(n)。

优点与缺点

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

注释:

  1. 数据标称型:标称型目标变量的结果只在有限目标集中取值,如真与假(标称型目标变量主要用于分类)
  2. 计算量大的原因:因为对每一个待分类的文本都要计算它到全体已知样本的距离,才能求得它的K个最近邻点

KNN算法一般流程

  1. 收集数据
  2. 准备数据:距离计算所需要的数值,最好是结构化的数据
  3. 分析数据
  4. 训练算法:不需要
  5. 测试算法:计算错误率
  6. 使用算法:首先需要输入样本数据和机构化的输出结果,然后运行KNN算法,判定输入的新数据属于哪个分类

KNN伪代码

  1. 计算已知类别数据集中点与当前点之间的距离
  2. 赞好距离递增次序排列
  3. 选取与当前点距离最小的k个点
  4. 确定前k个点所在类别的出现频率
  5. 返回前k个点出现频率最高的类别作为当前点的预测分类

注释:
距离公式(欧氏距离):
d = ( x A 0 − x B 0 ) 2 + ( x A 1 − x B 1 ) 2 d = \sqrt{(xA_0-xB_0)^2 + (xA_1-xB_1)^2} d=(xA0xB0)2+(xA1xB1)2

# 准备数据集
import numpy as np
import operator  # 运算符模块
def createDataset():
    """创建数据集"""
    list1 = [[1.0, 1.1], [1.0, 1.0], [0, 0], [0, 0.1]]  # 4*2矩阵
    group = np.array(list1)
    labels = ['A', 'A', 'B', 'B']
    return group, labels

# knn算法
def classify0(X, dataSet, labels, k):
    """
    K近邻算法
    X -> 需要测试的数据的向量
    dataSet -> 样本集
    label -> 标签
    k -> 最近的k个值
    """
    dataSetSize = dataSet.shape[0]  #样本的个数
    diffMat = np.tile(X, (dataSetSize, 1)) - dataSet
    sqDiffMat = diffMat ** 2
    sqDistances = sqDiffMat.sum(axis = 1)  # 每行的平方和
    distances = sqDistances ** 0.5
    # 为了直接检索标签,直接返回索引值,也可以方便排序
    sortedDistIndicies = distances.argsort()  # 返回的值是从小到大的索引
    classCount = {}
    for i in range(k):
        votelabel = labels[sortedDistIndicies[i]]
        # k个数据内,统计标签的数量
        classCount[votelabel] = classCount.get(votelabel, 0) + 1
        # 找出标签数量最多的那个分类,升序
    sortedClassCount = sorted(classCount.items(), key=lambda x:x[1])
    return sortedClassCount[-1][0]
    
if __name__ == "__main__":
    X = [0,0]
    k = 3
    group, labels = createDataset()
    result = classify0(X, group, labels, k)
    print("label = %s" %result)
    # print("label = ", result)
    # print("label = {}".format(result))

示例1:改进约会网站的配对效果

工作步骤:

  1. 收集数据:提供文本文件
  2. 准备数据: 使用Python解析文本文件
  3. 分析数据:使用Matplotlib画二维扩散图
  4. 训练算法:此步骤不适用于knn算法
  5. 测试算法:使用海伦提供的部分数据作为测试样本
  6. 使用算法:产生简单的命令行程序,然后海伦可以输入一些特征数据以判断对方是否为自己喜欢的类型
import os
import numpy as np
import pandas as pd
import matplotlib.pyplot as plt
from matplotlib.font_manager import FontProperties

class KnnAlgo():
    def __init__(self, filename):
        self.filename = filename
    
    # 准备数据   
    def file2martix(self):
        """
        将文本文件转化为需要的数据
        filename -> 文件名
        """
        path = os.getcwd()
        with open(path + '/'+ filename) as f:
            arrayOLines = f.readlines()
            numberOfLines = len(arrayOLines)
            returnMat = np.zeros((numberOfLines, 3))
            classLabelVector = []
            index = 0
            for line in arrayOLines:
                #strip参数默认,则删除空白符(\n,\t,\r,'')
                #此处删除末尾的\n
                line = line.strip()
                listFromLine = line.split('\t')
                # 样本集的特征值array矩阵
                returnMat[index, :] = listFromLine[:3]
                # 标签列表
                classLabelVector.append(int(listFromLine[-1]))
                index += 1
        return returnMat, classLabelVector
                
    # 分析数据
    def drawfigure(self):
        """
        分析数据,数据可视化
        x -> 样本数据的特征值
        y -> 样本数据的标签
        """
        #分析玩视频游戏所耗时间百分比与每周消费的冰淇淋公升数的关系
        #创建一个新的图
        # 中文显示
        x, y = self.file2martix()
        font_set = FontProperties(fname='/System/Library/Fonts/Supplemental/Songti.ttc')
        fig = plt.figure(figsize = (10,15))
        a1 = plt.subplot(311)
        #c->color s->size 这里是根据label不同的值,决定散点不同的大小和不同的颜色
        scatter = a1.scatter(x[:,0], x[:,1], c=np.array(y), s=20*np.array(y))
        # 自动生成lable的操作
        handles, labels = scatter.legend_elements()
        legend1 = a1.legend(handles, labels, loc='best', title='Classes')
        a1.add_artist(legend1) 
        a1.set_xlabel(u'每年获得的飞行常客里程数', FontProperties=font_set)
        a1.set_ylabel(u'玩视频游戏所耗时间百分比', FontProperties=font_set)
        
        a2 = plt.subplot(312)
        scatter = a2.scatter(x[:,0], x[:,2], c=np.array(y), s=20*np.array(y))
        handles, labels = scatter.legend_elements()
        legend2 = a2.legend(handles, labels, loc='best', title='Classes')
        a2.add_artist(legend2)
        a2.set_xlabel(u'每年获得的飞行常客里程数', FontProperties=font_set)
        a2.set_ylabel(u'每周消费的冰淇淋公升数', FontProperties=font_set)
        
        a3 = plt.subplot(313)
        scatter = a3.scatter(x[:,1], x[:,2], c=np.array(y), s=20*np.array(y))
        handles, labels = scatter.legend_elements()
        legend3 = a3.legend(handles, labels, loc='best', title='Classes')
        a3.add_artist(legend3)
        a3.set_xlabel(u'玩视频游戏所耗时间百分比', FontProperties=font_set)
        a3.set_ylabel(u'每周消费的冰淇淋公升数', FontProperties=font_set)
        fig.show()

    # 准备数据:归一化数值
    # 因为飞行常客里程数值远大于其他两个特征---会影响结果,因此将数据归一化处理
    # 取值范围0~1或-1~1之间
    # 公式:newValue = (oldValue-min)/(max-min)
    def autoNorm(self, dataset):
        """数据的归一化处理"""
        # 向量化思维!!取出所有列中最小的值组成array
        minVals = dataset.min(axis=0)
        maxVals = dataset.max(axis=0)
        ranges = maxVals - minVals
        normDataSet = np.zeros(shape=dataset.shape)
        row = dataset.shape[0]
        normDataSet = dataset - np.tile(minVals, reps=(row, 1))
        normDataSet = normDataSet / np.tile(ranges, reps=(row, 1))
        return normDataSet, ranges, minVals

    def classify0(self, X, dataSet, labels, k):
        """
        K近邻算法
        X -> 需要测试的数据的向量
        dataSet -> 样本集
        label -> 标签
        k -> 最近的k个值
        """
        dataSetSize = dataSet.shape[0]  #样本的个数
        # tile 将矩阵复制
        diffMat = np.tile(X, (dataSetSize, 1)) - dataSet
        sqDiffMat = diffMat ** 2
        sqDistances = sqDiffMat.sum(axis = 1)  # 每行的平方和
        distances = sqDistances ** 0.5
        # 为了直接检索标签,直接返回索引值,也可以方便排序
        sortedDistIndicies = distances.argsort()  # 返回的值是从小到大的索引
        classCount = {}
        for i in range(k):
            votelabel = labels[sortedDistIndicies[i]]
            # k个数据内,统计标签的数量
            classCount[votelabel] = classCount.get(votelabel, 0) + 1
            # 找出标签数量最多的那个分类,升序
        sortedClassCount = sorted(classCount.items(), key=lambda x:x[1])
        return sortedClassCount[-1][0]

    # 分类器针对约会网站的测试代码
    def datingTest(self):
        """测试并计算出错误率"""
        randseed = 0.1
        datingDataMat, datingLabels = self.file2martix()
        normat, _, _ = self.autoNorm(datingDataMat)
        row = normat.shape[0]
        numTestVecs = int(row * randseed)
        err = 0
        for i in range(numTestVecs):
            classifierResult = self.classify0(normat[i, :], normat[numTestVecs: ], datingLabels[numTestVecs: ], k=5)
            print("预测的分类标签为: {},真实的标签为: {}".format(classifierResult, datingLabels[i]))
            if (classifierResult != datingLabels[i]): err += 1.0
        print('错误率为:{}%'.format(err/numTestVecs))

    def classifyPerson(self):
        """根据用户输入的特征预测类别"""
        resultlist = ['not at all', 'in small doses', 'in large doses']
        game_percent = float(input("玩视频游戏所耗时间百分比:"))
        ffmiles = float(input("每年获得的飞行常客里程数"))
        ice_cream = float(input("每周消费的冰淇淋公升数:"))
        datingDataMat, datingLabels = self.file2martix()
        normat, _, _ = self.autoNorm(datingDataMat)
        input_arr = np.array([ffmiles, game_percent, ice_cream])
        classifierResult = self.classify0(input_arr, normat, datingLabels, k=5)
        print('你可能对这个人属于:{}'.format(resultlist[classifierResult]))
        

if __name__ == "__main__":
    filename = 'datingTestSet2.txt'
    Knn_algo = KnnAlgo(filename)
    Knn_algo.datingTest()
    Knn_algo.drawfigure()
    Knn_algo.classifyPerson()

结果

预测的分类标签为: 3,真实的标签为: 3
预测的分类标签为: 2,真实的标签为: 2
预测的分类标签为: 1,真实的标签为: 1
预测的分类标签为: 1,真实的标签为: 1
预测的分类标签为: 1,真实的标签为: 1
预测的分类标签为: 1,真实的标签为: 1
预测的分类标签为: 3,真实的标签为: 3
预测的分类标签为: 3,真实的标签为: 3

预测的分类标签为: 1,真实的标签为: 1
错误率为:0.04%
玩视频游戏所耗时间百分比:20
每年获得的飞行常客里程数3000
每周消费的冰淇淋公升数:12
你可能对这个人属于:in small doses

使用sklearn

class sklearn.neighbors.KNeighborsClassifier(n_neighbors=5, weights='uniform', algorithm='auto', leaf_size=30, p=2, metric='minkowski', metric_params=None, n_jobs=None, **kwargs)


参数注释:

  1. n_neighbor:临近节点数量,默认值是5
  2. weights:权重,默认值是uniform
    uniform:表示每个数据点的权重相同;
    distance:离一个簇中心越近,权重越高;
    callable:用户定义函数,用于表示每个数据点的权重
  3. algorithm
    auto:根据值选择最合适的算法
    ball_tree:使用BallTree
    kd_tree:使用KDTree
    brute:使用Brute-Force查找
  4. leaf_size:leaf_size传递给BallTree或KDTree,表示构造书的大小,用于影响模型构建的的速度和树需要的内存数量,最佳值是根据数据来确定的,默认值是30
  5. p:用于设置Minkowski距离的Power参数,当p=1时等价于Manhattan距离;当p=2等价于euclidean距离;当p>2时,是Minkowski距离(闵可夫斯基距离),默认值是2
  6. metric:树使用的距离度量,默认是Minkowsi距离
  7. metric_params:度量函数的其他关键字参数
  8. n_jobs:并发执行的job的数量,默认值是1。-1则使用所有的cpu。

示例2:手写识别系统

# 手写是示例,使用sklearn
import os
import numpy as np 
from sklearn.neighbors import KNeighborsClassifier
import matplotlib.pyplot as plt

class HdKnn():
   def img2vector(self, doc_name, filename):
       """
       将文件的图转换为需要的向量
       32*32 ---> 1*1024
       """
       path = os.getcwd()
       with open(path + '/digits/' + doc_name + '/' + filename) as f:
           returnVector = np.zeros(shape=(1, 1024))  #[[0,0,0,0,...0]] 1*1024
           for i in range(32):
               linestr = f.readline()
               for j in range(32):
                   returnVector[0, 32*i+j] = int(linestr[j])
       return returnVector

   def generation_data(self, doc_name):
       """
       将文件夹内所有的文件生成需要的数据样本集
       """
       path = os.getcwd()
       hwlabels = []
       dataFileList = os.listdir(path + '/digits/' + doc_name)
       row = len(dataFileList)
       dataMat = np.zeros(shape=(row, 1024))
       for i in range(row):
           # 特征值
           file_name = dataFileList[i]
           dataMat[i,:] = self.img2vector(doc_name, file_name)
           # 标签
           class_name = int(file_name.split('_')[0])
           hwlabels.append(class_name)
       return dataMat, hwlabels
       
   def classifer(self, k_list, x_train, y_train, x_test, y_test):
       """
       训练模型以及模型评估
       """
       training_accuracy = []
       test_accuracy = []
       for k in k_list:
           knn = KNeighborsClassifier(n_neighbors=k)
           knn.fit(x_train, y_train)
           training_accuracy.append(knn.score(x_train, y_train))
           test_accuracy.append(knn.score(x_test, y_test))
       
       plt.plot(k_list, training_accuracy, label="Training Accuracy")
       plt.plot(k_list, test_accuracy, label="Test Accuracy")
       plt.xlabel('k_values')
       plt.ylabel('Accuracy')
       plt.legend()
       

if __name__ == "__main__":
   train_doc_name = 'trainingDigits'
   test_doc_name = 'testDigits'
   k_list = range(1, 11)
   hdknn = HdKnn()
   trainingdata, trainlabel = hdknn.generation_data(train_doc_name)
   testdata, testlabel = hdknn.generation_data(test_doc_name)
   hdknn.classifer(k_list, x_train=trainingdata, y_train=trainlabel, x_test=testdata, y_test=testlabel)

fig.1

总结

KNN算法是分类数据最简单最有效的算法。KNN算法必须保存全部数据集,如果训练数据集的很大,必须使用大量的存储空间。此外,由于必须对数据集中的每个数据计算距离值,实际使用时可能非常耗时。另一个缺点:无法给出任何数据的基础结构信息,因此我们也无法知晓平均实力样本和典型实例样本具有什么特征。

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值