欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

机器学习——K近邻算法

程序员文章站 2022-07-14 11:49:59
...

K-近邻算法(K Nearest Neighbor, KNN)

概述

KNN采用测量不同特征值之间的距离方法来进行分类。
KNN算法的核心思想是如果一个样本在特征空间中的k个最相邻的样本中的大多数属于某一个类别,则该样本也属于这个类别,并具有这个类别上样本的特性。

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

算法流程

  1. 收集数据
  2. 准备数据:距离计算所需要的数值,最好是结构化的数据格式
  3. 分析数据:可以适用任何方法
  4. 训练算法:此步骤不适用于KNN
  5. 测试算法:计算错误率
  6. 使用算法:首先需要输入样本数据和结构化的输出结果,然后运行k-近邻算法判定输入数据分别属于哪个分类,最后应用对计算出的分类执行后续的处理

KNN算法

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

import numpy as np
import operator

def createDataSet():
    '''构造数据集'''
    dataSet = np.array([[1.0, 1.1], [1.0, 1.0], [0, 0], [0, 0.1]])
    labels = ['A', 'A', 'B', 'B']
    return dataSet, labels

def classify0(inX, dataSet, labels, k):
    '''
    KNN算法
    :param inX: 用于分类的输入向量
    :param dataSet: 训练样本集
    :param labels: 训练标签向量
    :param k: 选取最近邻居的数量
    :return k个邻居里频率最高的分类
    '''
    # 为了计算输入向量与其余向量的距离,构造输入向量的矩阵,矩阵的行数为训练样本行数,列数为1
    matrix = np.tile(inX, (dataSet.shape[0], 1))
    # 通过向量计算输入向量与每个样本向量的欧式距离
    sq_distance = (matrix - dataSet) ** 2
    sum_distance = sq_distance.sum(axis=1)
    distance = sum_distance ** 0.5
    # 对距离进行按照由小到大的顺序进行排序,并返回原对象的索引
    sorted_distance_index = distance.argsort()

    class_count = {}
    for i in range(k):
        # 返回距离最近的第i个样本所对应的标签
        vote_label = labels[sorted_distance_index[i]]
        # 统计标签所出现的频率
        class_count[vote_label] = class_count.get(vote_label, 0) + 1
    # 根据class_count的频率由大到小排序
    sorted_distance_index = sorted(class_count.iteritems(), key=operator.itemgetter(1), reverse=True)
    # 返回频率最大的Label
    return sorted_distance_index[0][0]

添加算法测试代码:

dataSet, labels = createDataSet()
classify0([1, 1], dataSet, labels, 3)

返回结果为’A’

约会网站示例

本文所用到的数据下载地址为
https://github.com/AlistairChow/ML_Learning/blob/master/Arithmetic/KNN/datingTestSet2.txt
加载并解析数据
将文本数据拆分为特征值矩阵及对应的分类标签列表

def file2matrix(filename):
    '''
    加载并解析数据文本文件
    :param filename: 文件名
    :return: 特征值矩阵, 分类标签列表
    '''
    #读取文件,并转换为行文本数组
    array_of_lines = open(filename).readlines()
    #文件行数
    num_of_lines = len(array_of_lines)

    #构造返回矩阵,行数为文本行数,列数为3列。3列的原因是文本中有3个特征值
    return_matrix = np.zeros((num_of_lines, 3))
    class_label_vector = []
    index = 0
    #遍历文本
    for line in array_of_lines:
        #移除字符串头尾空格,并按制表符拆分
        list_from_line = line.strip().split('\t')
        # 赋值返回矩阵为数据特征值
        return_matrix[index, :] = list_from_line[0: 3]
        # 将分类标签添加至列表
        class_label_vector.append(int(list_from_line[-1]))
        index += 1

    return return_matrix, class_label_vector

制作散点图
根据游戏时间和冰淇淋消耗数制作散点图

dating_data, dating_labels = file2matrix('datingTestSet2.txt')

import matplotlib as mpl
import matplotlib.pyplot as plt
%matplotlib inline

mpl.rcParams['font.sans-serif'] = ['KaiTi']
mpl.rcParams['font.serif'] = ['KaiTi']
plt.figure()
ax = plt.subplot(111)
ax.scatter(dating_data[:, 1], dating_data[:, 2], 15.0*np.array(dating_labels), 15.0*np.array(dating_labels))
ax.set_xlabel(u'玩视频游戏所耗时间百分比')
ax.set_ylabel(u'每周消费的冰淇淋公升数')

机器学习——K近邻算法
上图为带有样本分类标签的约会数据散点图。虽然能够比较容易地区分数据点所属类别,但依然很难根据这张图得出结论性信息

归一化特征值
算记录时,差值最大的特征值对计算结果的影响最大。为了处理不同取值范围的特征值时,通常采用将数值归一化的方法,如将取值范围处理为0到1或-1到1之间。下面的公式可以将任意取值范围的特征值转化为0到1区间内的值:

newValue=(oldValuemin)/(maxmin)

def autoNorm(dataSet):
    '''
    归一化特征值
    :param dataSet: 数据集
    :return 归一化后的数据集, 列的差值范围, 列的最小值
    '''
    # 列的最小值
    min_vals = dataSet.min(0)
    # 列的最大值
    max_vals = dataSet.max(0)
    # 列的差值范围
    ranges = max_vals - min_vals
    # 构造返回矩阵
    normalize_dataset = np.zeros(np.shape(dataSet))
    # oldValue - min
    normalize_dataset = dataSet - np.tile(min_vals, (dataSet.shape[0], 1))
    # (oldValue - min) / (max - min)
    normalize_dataset = normalize_dataset / np.tile(ranges, (dataSet.shape[0], 1))
    return normalize_dataset, ranges, min_vals

归一化测试

norm_matrics, ranges, min_vals = autoNorm(dating_data)
print norm_matrics

输出

[[ 0.44832535  0.39805139  0.56233353]
 [ 0.15873259  0.34195467  0.98724416]
 [ 0.28542943  0.06892523  0.47449629]
 ..., 
 [ 0.29115949  0.50910294  0.51079493]
 [ 0.52711097  0.43665451  0.4290048 ]
 [ 0.47940793  0.3768091   0.78571804]]

可以发现,将所有特征值都限定在了0,1的取值区间内

测试算法
机器学习算法一个很重要的工作就是评估算法的正确率,通常只提供90%的样本数据作为训练样本,余下的10%数据作为测试样本。10%的测试数据应该是随机的。

def datingClassTest():
    ho_ratio = 0.10
    # 解析数据
    dating_data, dating_labels = file2matrix('datingTestSet2.txt')
    # 归一化数据
    normalize_matrix, ranges, min_vals = autoNorm(dating_data)
    # 拆分10%数据作为测试数据
    m = norm_matrics.shape[0]
    num_test_vecs = int(m*ho_ratio)
    error_count = 0.0
    # 对测试数据进行分类,并对比检验结果正确率
    for i in range(num_test_vecs):
        classifier_result = classify0(
            norm_matrics[i, :], 
            norm_matrics[num_test_vecs:m, :],
            dating_labels[num_test_vecs:m], 3)

        print 'the classifier came back with: %d, the real answer is: %d' % (classifier_result, dating_labels[i])
        if classifier_result != dating_labels[i]:
            error_count += 1.0

    print 'the total error rate is: %f' % (error_count / float(num_test_vecs))

执行测试

datingClassTest()

测试输出结果如下

the classifier came back with: 3, the real answer is: 3
the classifier came back with: 2, the real answer is: 2
the classifier came back with: 1, the real answer is: 1
the classifier came back with: 1, the real answer is: 1
the classifier came back with: 1, the real answer is: 1

...

the classifier came back with: 3, the real answer is: 3
the classifier came back with: 3, the real answer is: 3
the classifier came back with: 2, the real answer is: 2
the classifier came back with: 1, the real answer is: 1
the classifier came back with: 3, the real answer is: 1
the total error rate is: 0.050000

可以看出错误率为5%,可以调整ho_ratio和KNN算法的K值来检测错误率是否随着变量值变化而增加。

使用算法

def classifyPerson():
    '''
    根据输入指标,通过分类器进行预测喜欢程度
    '''
    result_list = ['not at all', 'in small doses', 'in large doses']
    percentTats = float(raw_input('percentage of time spent playing vedio games?'))
    ffMiles = float(raw_input('frequent flier miles earned per year?'))
    iceCream = float(raw_input('liters of ice cream consumed per year?'))
    dating_data, dating_labels = file2matrix('datingTestSet2.txt')
    normalize_matrix, ranges, min_vals = autoNorm(dating_data)
    # 将输入指标,归一化后代入分类器进行预测
    in_arr = np.array([ffMiles, percentTats, iceCream])
    classifierResult = classify0((in_arr-min_vals)/ranges, norm_matrics, dating_labels, 3)
    print "You will probably like this person: ", result_list[classifierResult - 1]

执行函数

classifyPerson()

输出

percentage of time spent playing vedio games?20
frequent flier miles earned per year?299
liters of ice cream consumed per year?1
You will probably like this person:  in large doses

输入某人指标后,通过分类器判断出我会对此人非常敢兴趣。

sklearn中实现
sklearn是基于 Python 语言的,简单高效的数据挖掘和数据分析工具,建立在 NumPy,SciPy 和 matplotlib 上。关于Scikit-Learn中的KNN详见官方文档

from sklearn import neighbors

def knn_classifyPerson():
    '''
    根据输入指标,通过分类器进行预测喜欢程度
    '''
    result_list = np.array(['not at all', 'in small doses', 'in large doses'])
    percentTats = float(raw_input('percentage of time spent playing vedio games?'))
    ffMiles = float(raw_input('frequent flier miles earned per year?'))
    iceCream = float(raw_input('liters of ice cream consumed per year?'))
    dating_data, dating_labels = file2matrix('datingTestSet2.txt')
    normalize_matrix, ranges, min_vals = autoNorm(dating_data)
    # 将输入指标,归一化后代入分类器进行预测
    in_arr = np.array([ffMiles, percentTats, iceCream])
    # 声明k为3的knn算法,n_neighbors即是邻居数量,默认值为5
    knn = neighbors.KNeighborsClassifier(n_neighbors=3)
    # 训练算法
    knn.fit(norm_matrics, dating_labels)
    # 预测
    classifierResult = knn.predict([(in_arr-min_vals)/ranges])
    print "You will probably like this person: ", result_list[classifierResult - 1][0]

执行函数

knn_classifyPerson()

输出

percentage of time spent playing vedio games?20
frequent flier miles earned per year?299
liters of ice cream consumed per year?1
You will probably like this person:  in large doses

可以发现与我们自己写的代码结果相同。