《机器学习实战》第二章 k-近邻算法

算法理解:
K-近邻算法测量待分类样本的特征值与已经分好类的样本对应的特征值之间的距离,最邻近的一个或者几个训练样本的类别决定了待分类样本所属的类别。

工作原理:
存在一个样本数据集合(训练样本集),并且每个训练样本都有已知对应的所属分类(标签)。输入待分类的新样本后,将新样本的每个特征与训练样本集中数据对应的特征进行比较。然后算法提取样本集中特征最相似数据(最近邻)的分类标签。一般选前k个最相似的数据(一般k不大于20)。最后,选择k个最相似数据中出现次数最多的分类,作为新样本的分类。

距离计算:
在KNN中,通过计算待测样本和训练样本的特征值之间的差异(距离)作为对象之间的相似性指标,一般使用欧氏距离或曼哈顿距离:

2.jpg

k-近邻算法优点是精度高、对异常值不敏感、无数据输入假定。缺点是计算复杂度高、空间复杂度高。适用数据范围为数值型和标称型。

算法的描述:
1)计算测试数据与各个训练数据之间的距离;
2)按照距离的递增关系进行排序;
3)选取距离最小的K个点;
4)确定前K个点所在类别的出现频率;
5)返回前K个点中出现频率最高的类别作为测试数据的预测分类。

# -*- coding: utf-8 -*-
""" k-Nearest Neighbors
Created on Mon Jan 23 12:56:00 2017
 
@author: xieyuxi
"""
from numpy import *
import operator 
import numpy as np
 
def createDataSet():
    group = array([[1.0, 1.1],[1.0,1.0],[0,0],[0,0.1]])
    lables = ['A','A','B','B']
    return group, lables
    print group, lables
 
 
createDataSet()
 
""" Classify the unknown vector input into defined classes
1、calculate the distance between inX and the current point
2、sort the distances in increasing order
3、take k items with lowest distances to inX
4、find the majority class among these items
5、return the majority class as our prediction for the class of inX 
 
Args:
     inX : the input vector to classify 
     dataSet : the full matrix of training examples
     labels : a vector of labels from the training examples
     k : the number of nearest neighbors to use in the voting
 
Attention:
    The labels vector should have as many elements in it 
    as there are rows in the dataSet matrix.
 
Returns:
     the majority class as our prediction for the class of inX
 
Raises:
    None
"""
def classify0(inX, dataSet, labels, k):
    # shape是numpy的数组的一个属性,表示数组的维度
    # 比如一个 n x m的数组 Y(n 行 m列),Y.shape表示(n,m)
    # 所以 Y.shape[0]等于 n, Y.shape[1]等于 m
    # dataSet是4X2的数组,所以此处dataSetSize等于4
    dataSetSize = dataSet.shape[0]      # 4
    # 1、先将一维矩阵扩展和测试矩阵的维度一样
    # 2、将新生成的待测矩阵同测试矩阵进行矩阵减法,再将矩阵的差的平方和开根得到距离
    # 3、将距离排序并返回
#==============================================================================
#     1、 tile(A, reps):reps的数字从后往前分别对应A的第N个维度的重复次数。
#        如(A,2)表示A的列变量重复2遍,
#           tile(A,(2,3))表示A的行变量重复2遍,列变量重复3遍
#           tile(A,(2,2,3))表示A的第一个维度重复2遍,第二个维度重复2遍,
#           第三个维度重复3遍。
#           Examples:
#     ----------------------------------------
#     # a = np.array([0, 1, 2])
#     # np.tile(a, 2)
#     array([0, 1, 2, 0, 1, 2])
#     # np.tile(a, (2, 3))
#     array([[0, 1, 2, 0, 1, 2, 0, 1, 2],
#            [0, 1, 2, 0, 1, 2, 0, 1, 2]])
#     ----------------------------------------
#     # b = np.array([[1, 2], [3, 4]])
#     # np.tile(b, 2)
#     array([[1, 2, 1, 2],
#            [3, 4, 3, 4]])
#     # np.tile(b, (2, 1))
#     array([[1, 2],
#            [3, 4],
#            [1, 2],
#            [3, 4]])
# 
#     2、diffMat矩阵:先将输入的值复制到和训练样本同行,再做减法
# 以[0,0]为例:
# [0,0] to array([[0, 0]       [[0, 0]   [[1.0,1.1]           [[-1.  -1.1]
#                 [0, 0]  to   [0, 0] -  [1.0,1.0]    to       [-1.  -1. ]
#                 [0, 0]       [0, 0]    [0,0]                 [ 0.   0. ]
#                 [0, 0]])     [0, 0]]   [0,0.1]]              [ 0.  -0.1]]
#==============================================================================
 
    diffMat = tile(inX, (dataSetSize,1)) - dataSet
    # 其中矩阵的每一个元素都做平方操作
    #    [[ 1.    1.21]
    #     [ 1.    1.  ]
    #     [ 0.    0.  ]
    #     [ 0.    0.01]]
    sqDiffMat = diffMat**2
    # axis=0, 表示列。 axis=1, 表示行。
    # example:
    # c = np.array([[0, 2, 1], [3, 5, 6], [0, 1, 1]])
    # c.sum() = 19
    # c.sum(axis=0) =[3 8 8],即将矩阵的行向量想加后得到一个新的一维向量
    # c.sum(axis=1) =[3,14,2], 即每个向量将自己内部元素加和作新一维向量里的一个元素 
    # 此处已经将每一个行向量中的每一个元素都做了平方操作,并求和得到每一个向量之和
    # 得到一个一维数组,其中有四个元素分别对应了输入元素与 4个测试样本的平方距离
    sqDistances = sqDiffMat.sum(axis=1)     # [ 2.21  2.    0.    0.01]
    # 将平方距离开方
    distances = sqDistances**0.5 # [ 1.48660687  1.41421356  0.          0.1 ]
    # argsort函数返回的是数组值从小到大的索引值
    # examples:
    #     # x = np.array([3, 1, 2])
    #     # np.argsort(x)
    #         array([1, 2, 0]) 数值1对应下表1,数值2对应下表2,数值3,对应下标0
    # [1.48660687  1.41421356  0.0  0.1 ] to [2 3 1 0]
    sortedDistIndex = distances.argsort()   # [2 3 1 0]
    # 建造一个名为classCount的字典,Key是label,value是这个label出现的次数
    classCount={}
    for i in range(k):
        # 这里很巧妙的地方在于,一开始测试数组和Label的位置是一一对应的
        # 所以这里可以通过上面获得的测试数组的下标index获得对应的分类labels[index]
        voteLabel = labels[sortedDistIndex[i]]
        # 这里将每一个[label : Value]赋值,Value为计算VoteLabel出现的次数
        # 如果Label不在字典classCount中时,get()返回 0,存在则value加 1
        classCount[voteLabel] = classCount.get(voteLabel,0) + 1
 
    # 根据字典classCount中的Value值对字典进行排序,选出出现次数最多的Label
    #       sorted(iterable, cmp=None, key=None, reverse=False) 
    #       return new sorted list
    #       1、iterable:是可迭代类型的对象(这里的字典通过iteritems()进行迭代)
    #       2、cmp:用于比较的函数,比较什么由key决定,一般用Lamda函数
    #       3、key:用列表元素的某个属性或函数进行作为关键字,迭代集合中的一项; 
    #               operator.itemgetter(1)表示获得对象的第一个域的值(这里指value)
    #       4、reverse:排序规则。reverse = True 降序 或者 reverse = False 升序。
    #       
    #       return: 一个经过排序的可迭代类型对象,与iterable一样。
    #               这里返回排好序的[Label:value], 即 [('B', 2), ('A', 1)]
    sortedClassCount = sorted(classCount.iteritems(),
                              key=operator.itemgetter(1), reverse=True)
    # 返回可迭代对象的第一个域的第一值,也就是出现次数最多的Label
    # sortedClassCount[0][1] 表示第一个域的第二个值,就是出现最多次数Label出现次数
    return sortedClassCount[0][0]

本案例的数据被置于txt文件中,所以需要我们将数据从txt文件中抽出并做处理。所以我们准备函数file2matrix()将txt的数据转换为矩阵方便我们后面做运算。

""" Parsing data from a text file
Change the text file data to the format so that the classifier can accept and use it 
 
Args:
     filename string 
 
Returns:
    A matrix of training examples,
    A vector of class labels
 
Raises:
    None
"""
def file2matrix(filename):
    # 根据文件名打开文件
    fr = open(filename)
    # 读取每一行的数据
    # txt文件中的数据类似:"40920      8.326976    0.953952    largeDoses"W
    arrayOlines = fr.readlines()
    # 得到文件行数
    numberOfLines = len(arrayOlines)
    # 创建返回的Numpy矩阵
    # zeros(a) 仅有一个参数时,创建一个一维矩阵,一行 a 列
    # zeros(a, b) 创建一个 a X b 矩阵, a 行 b 列
    # 这里取了数据的行数和前三个特征值(前三个是特征,第四个个是类别)创建一个矩阵
    returnMat = zeros((numberOfLines,3)) 
    # 建造一个名为 classLabelVector 的 List 用来存放最后列的Label
    classLabelVector = []
    # 这里的 index 指的是矩阵的第几行,从 0 开始
    index = 0
    #( 以下三行) 解析文件数据到列表
    for line in arrayOlines:
        # 去掉文本中句子中的换行符"/n",但依然保持一行四个元素
        line = line.strip()
        # 以 ‘\t’(tab符号)分个字符串,文本中的数据是以tab分开的
        # 另外需要注意 split() 函数返回一个 List对象
        #       如:['30254', '11.592723', '0.286188', '3']
        listFromLine = line.split('\t')
        # 选取前listFromLine的三个元素,存储在特征矩阵中
        # returnMat[index,:]这里需要注意,因为returnMat是一个矩阵
        #       其中的 index指的是矩阵中的第几个list
        #       例如 listMat = [[list1] 
        #                       [list2] 
        #                       [list3]
        #                       ......
        #                       [listn]]
        #       listMat[0]表示的是矩阵listMat中的第1行,即 [list1]
        #       listMat[2,:] 表示的是矩阵listMat中的第3行的所有元素
        #       listMat[2,0:2] 表示矩阵listMat中的第3行下标为 0和1两个元素
        #
        # listFromLine[0:3]切片开始于0、停止于3,也就是取了下标为0,1,2的元素
        # 将listFromLine[0:3]的第0号到2号元素赋值给特征矩阵returnMat对应特征中
        returnMat[index,:] = listFromLine[0:3]
        # listFromLine[-1] 取listFromLine的最后一列标签类(将其强行转换为int类型)
        # 同时将该类别标签按顺序加入到标签向量classLabelVector中
        classLabelVector.append(int(listFromLine[-1]))
        # index加 1,矩阵存取下一个list
        index += 1
 
    return returnMat,classLabelVector

在这先根据特征值,利用Matplotlib绘制类别的散点图。

import matplotlib
# matplotlib的pyplot子库提供了和matlab类似的绘图API,方便用户快速绘制2D图表。
import matplotlib.pyplot as plt
# 我们先利用写好的KNN.py文件中的file2matrix()函数将数据转为矩阵
datingDataMat, datingLabels = KNN.file2matrix(filename)
# 这里创建了一个新的 figure,我们要在上面作图
fig = plt.figure()
# add_subplot(111)相当于在figure上添加子图
# 其中 111 的意思是,将子图切割成 1行 1列的图,并在第 1 块上作画
# 所以这里只有一副画
# 再举个例子 add_subplot(22x),将子图切割成 2行 2列的图,并在第 X 块上作画
ax = fig.add_subplot(111) 
# 利用scatter()函数分别取已经处理好的矩阵datingDataMat的第一列和第二列数据
# 并用不同颜色将类别标识出来
ax.scatter(datingDataMat[:,0], datingDataMat[:,1],
15.0*array(datingLabels), 15.0*array(datingLabels))
# 展示我们做好的 figure
plt.show()

下图是很好地解释add_subplot(22x):


5 (1).png

根据矩阵第一列和第二列数据可以获得以下带有颜色类别区分的散点图:


3.png

(红色的点表示非常喜欢,绿色的点表示一般喜欢,蓝色的点表示并不喜欢)

观察上图我们不难发现一个问题,如果当某一个特征值与其他的特征值的差异过大的话,如果不加权重或各特征权重均衡的话,很可能会影响到最后的分类,比如例子中的飞行距离,远远大于其他两个特征值,在这种情况下,特征的距离就会受飞行距离特征值得影响。所以我们需要将数据进行归一化,即将所有数据都变为 0~1 之间的数,目的是让数据的影响都能比较均衡。

""" Normalizing numeric values
Normalize the data to values between 0 and 1.
 
Formula:
    newValue = (oldValue-min)/(max-min)
In the normalization procedure, the variables min and max are the 
smallest and largest values in the dataset. 
 
Args:
     dataSet: the data set to be normalized 
 
Returns:
    A normalized data set
 
Raises:
    None
"""
def autoNorm(dataSet):
    # min(0)取矩阵每一列的最小值
    # min(1)取矩阵每一行的最小值
    # Examples:
    #     array([[1,2]
    #            [3,4]
    #            [5,6]])
    #       min(array,0) = [1,2]
    #       min(array,1) = [1,3,5]
    # 这里同: minVals = np.min(dataSet,0)
    # 返回的是一个一维矩阵
    minVals = dataSet.min(0)    
    # max(0)取矩阵每一列的最大值
    # max(1)取矩阵每一列的最大值
    # 返回的是一个一维矩阵    
    maxVals = dataSet.max(0)
    # ranges也是一个一维矩阵
    ranges = maxVals - minVals
    # shape(dataSet) 返回一个记录有矩阵维度的tuple
    # 例如:np.shape([[1, 2]]) = (1, 2)
    # zeros(shape)返回一个具有 shape 维度的矩阵,并且所有元素用 0 填充
    normDataSet = zeros(shape(dataSet))
    # m 表示 dataSet 矩阵的行数
    # dataSet.shape[1] 表示矩阵的列数
    m = dataSet.shape[0]
    # tile(minVals, (m,1)) 首先将 minVals 复制为 m 行的矩阵
    # examples:
    #    if   minVals = [[1,2,3]]
    #         tile(minVals, (3,1))(行数复制 3 遍,列保持不变)
    #    return [[1,2,3]
    #            [1,2,3]
    #            [1,2,3]]
    normDataSet = dataSet - tile(minVals, (m,1))
    # 同上,将 一维矩阵 ranges 复制 m行,再让矩阵中的元素做商
    normDataSet = normDataSet/tile(ranges, (m,1))
    # 返回归一化的矩阵,最大与最小值的差值矩阵,和最小值矩阵
    return normDataSet, ranges, minVals
4.png

(上图为归一化后的特征值)

我自己的博客地址为:谢雨熹的学习博客
欢迎大家来交流!

Talk is cheap, show me your code!

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 210,914评论 6 490
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 89,935评论 2 383
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 156,531评论 0 345
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 56,309评论 1 282
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 65,381评论 5 384
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,730评论 1 289
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,882评论 3 404
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,643评论 0 266
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,095评论 1 303
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,448评论 2 325
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,566评论 1 339
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,253评论 4 328
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,829评论 3 312
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,715评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,945评论 1 264
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,248评论 2 360
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,440评论 2 348

推荐阅读更多精彩内容