Python k-近邻算法

1、k-近邻算法

k-近邻算法简称kNN,是一种常用的监督学习方法,它的工作机制简单来说就是:在给定的测试样本中,基于某种距离度量找出训练集中与其最靠近的k个训练样本,然后基于这k个“邻居”的信息来进行预测。

  • 在分类中可使用“投票法”,即选择这k个样本中出现最多的类别标记作为预测的结果;
  • 在回归中可使用“平均法”,即将这k个样本的实值输出标记值的平均值作为预测结果。

还可基于距离的远近进行加权平均和加权投票。

1.1、度量距离

设特征空间中Xn维实数向量Rn, xi, xjX, xi=(xi1,xi2,...,xin), xj=(xj1,xj2,...,xjn).

xi,xjLp距离为![](http://latex.codecogs.com/svg.latex? \Large $$ L_p(x_i,x_j)=(\sum_{l=1}{n}|x_i{l}-x_j{l}|{p})^{\frac{1}{p}},(p≥1)$$)

p=2时,为欧式距离(Euclidean distance

![](http://latex.codecogs.com/svg.latex? \Large $$L_2(x_i,x_j)=(\sum_{l=1}{n}|x_i{l}-x_j{l}|{2})^{\frac{1}{2}}$$)

p=1时,为曼哈顿距离(Manhattan distance

![](http://latex.codecogs.com/svg.latex? \Large $$L_1(x_i,x_j)=\sum_{l=1}{n}|x_i{l}-x_j^{l}|$$)

p=∞时,是各个坐标距离的最大值

![](http://latex.codecogs.com/svg.latex? \Large $$L_∞(x_i,x_j)=\max_{l}\sum_{l=1}{n}|x_i{l}-x_j^{l}|$$)

1.2、k值的选取

k值的选取会对k近邻法的结果产生重大影响。

如果选择较小的k值,就相当于用较小的邻域中的训练实例进行预测,“学习”的近似误差(approximation error)会减小,只有与输入实例相似的训练实例才会对预测结果产生作用。但缺点是“学习”的估计误差(estimation error)会增大,预测结果会对近邻的实例点非常敏感。如果邻近的实例点恰好是噪声,预测就会出错,亦即k值的减小就意味着整体的模型变复杂,容易发生过拟合。

如果选择较大的k值,就相当于用较大的邻域中的训练实例进行预测,其优点是可以减少学习的估计误差,但缺点是学习的近似误差会增大。这时与输入实例不相似的训练实例也会对预测产生作用,是预测发生错误。k的增大就意味着整体的模型变得简单。

2、算法实现

这次仍然使用学习决策树算法时候的数据集进行实验,

编号 色泽 根蒂 敲声 纹理 脐部 触感 好瓜
1 青绿 蜷缩 浊响 清晰 凹陷 硬滑
2 乌黑 蜷缩 沉闷 清晰 凹陷 硬滑
3 乌黑 蜷缩 浊响 清晰 凹陷 硬滑
4 青绿 蜷缩 沉闷 清晰 凹陷 硬滑
5 浅白 蜷缩 浊响 清晰 凹陷 硬滑
6 青绿 稍蜷 浊响 清晰 稍凹 软粘
7 乌黑 稍蜷 浊响 稍糊 稍凹 软粘
8 乌黑 稍蜷 浊响 清晰 稍凹 硬滑
9 乌黑 稍蜷 沉闷 稍糊 稍凹 硬滑
10 青绿 硬挺 清脆 清晰 平坦 软粘
11 浅白 硬挺 清脆 模糊 平坦 硬滑
12 浅白 蜷缩 浊响 模糊 平坦 软粘
13 青绿 稍蜷 浊响 稍糊 凹陷 硬滑
14 浅白 稍蜷 沉闷 稍糊 凹陷 硬滑
15 乌黑 稍蜷 浊响 清晰 稍凹 软粘
16 浅白 蜷缩 浊响 模糊 平坦 硬滑
17 青绿 蜷缩 沉闷 稍糊 稍凹 硬滑

由于各种距离需要使用数值进行计算,上述“色泽”、“根蒂”、“敲声”、“纹理”、“脐部”和“触感”6个特征变量都是类别变量(factor),我们需要将其转化为哑变量(dummy variables)。

例如在“色泽”中,令数值0代表“乌黑”,数值1代表“青绿”,数值2代表“浅白”,以此进行转换。

其中数据集(data1.txt)中的格式如下图所示:


下述的代码即针对上述数据集进行哑变量转换的,

filename = 'data1.txt'
def factor2dummy_variable(filename):
    fr = open(filename,encoding = 'utf-8')#以utf-8读取文件
    dataColumns = fr.readline()#去除首行
    arrayLines = fr.readlines()#读取所有行
    lines = np.array([line.replace('\n','').split('\t') for line in arrayLines]).T#按行指定分隔符,并进行转置
    lines = lines[1:,:]#实际使用的数据部分
    setFactors = [set(line) for line in lines]#set操作,只保存一种分类的集合
    k = 0
    for i in setFactors:
        dummy_num = 0
        line = lines[k]
        for j in i:
            line[line == j] = dummy_num#哑变量转换
            dummy_num += 1
        k += 1
    lines = lines.T#转置
    return lines

将转换好的数据集保存到文件(data2.txt),

lines = factor2dummy_variable(filename)
dataSet = lines
filename = 'data2.txt'
def data2txt(dataSet,filename):
    fw = open(filename,'w',encoding='utf-8')#以utf-8编码写入
    for line in dataSet:
        for element in line:
            fw.write(element)
            fw.write('\t')#tab键分割符号
        fw.write('\n')#换行
    fw.close()

转换好的数据集格式如下图所示:


处理完毕上述变量后,需要将转换好的数据文件转化成numpy可以使用的数据集形式,

filename = 'data2.txt'
def file2matrix(filename):
    fr = open(filename)
    arrayOLines = fr.readlines()#读取所有行
    numberOfLines = len(arrayOLines)#计算记录数量
    numberOfcloumns = len(arrayOLines[0].replace('\t\n','').split('\t')) - 1#计算变量数量
    returnMat = np.zeros((numberOfLines, numberOfcloumns))
    classLabelVector = []
    index = 0
    for line in arrayOLines:
        line = line.strip()#去除空格
        listFromLine = line.split('\t')#按tab键分割
        returnMat[index, :] = listFromLine[:-1]#得到分类变量数组
        classLabelVector.append(int(listFromLine[-1]))#类别变量数组
        index += 1
    return returnMat, classLabelVector

注:有时候由于各类变量之间数值差别太大,同时量纲也各不相同,这时候就需要进行归一 化操作,将各变量的范围设置在0~1之间,以消除量纲影响,加快运算速度。

​ 其中归一化的式子如下:
​ ![](http://latex.codecogs.com/svg.latex? \Large $$x' = \frac{x - \min{(x)}}{\min{(x)}-\max{(x)}}​$$)

下述代码即进行归一化操作:

def autoNorm(dataSet):
    minValue = dataSet.min(0)#得到每列的最小值
    maxValue = dataSet.max(0)#每列最大值
    ranges = maxValue - minValue#分母
    normDataSet = np.zeros(np.shape(dataSet))
    m = dataSet.shape[0]#行数
    normDataSet = dataSet - np.tile(minValue, (m,1))#分子
    normDataSet = normDataSet/np.tile(ranges, (m,1))
    return normDataSet, ranges, minValue

经过上述处理,这里通过计算欧式距离进行kNN分类,具体代码如下:

def classify0(inX, dataSet, labels, k):
    dataSetSize = dataSet.shape[0]#得到行数
    diffMat = np.tile(inX, (dataSetSize,1)) - dataSet#计算输入向量inX与训练样本的差
    sqDiffMat = diffMat**2#计算差值的平方
    sqDistances = sqDiffMat.sum(axis = 1)#距离平方和
    distances = sqDistances**0.5#开方得到距离
    sortedDistIndicies = distances.argsort()#距离进行排序,得到排序的下标
    classCount = {}
    for i in range(k):#确定前k个距离中最小元素所属分类
        voteIlabel = labels[sortedDistIndicies[i]]
        classCount[voteIlabel] = classCount.get(voteIlabel, 0) + 1#对出现的label进行计数
    sortedClassCount = sorted(classCount.items(), key = operator.itemgetter(1),
                              reverse = True)#按照计数值进行降序排序
                              #operator.itemgetter(1)确定一个函数取出classCount中的第一个域的值,即将value取出
    return sortedClassCount[0][0]#返回最大的计数值的分类

由此,即进行了kNN的分类操作,下面通过以下代码对分类效果进行测试(测试数据集仍是训练数据集合)

dataLines, datalabels = file2matrix('data2.txt')
i=0
errorCount = 0.0
for line in dataLines:
    print('记录%s的原始分类是:%d,划分分类是:%d' %(str(line), datalabels[i], 
                                     classify0(line, dataLines ,datalabels, 1))) 
    if (datalabels[i] != classify0(line, dataLines ,datalabels, 1)):
        errorCount += 1.0
    i += 1
print('错误率为: %f' %(errorCount/float(len(dataLines))))

可以看出错误率为0.000000,正确率达到了100%。可以看出准确度不错,当然此准确度也与数据量的大小有关,后续可以将数据集扩大,或者使用不同的数据集进行训练测试,来验证分类效果。

下面代码是通过Scikit - Learn库进行数据集的训练和测试过程。

import numpy as np
from sklearn import neighbors

data = []
labels = []
with open('data2.txt') as ifile:
    for line in ifile:
        tokens = line.replace('\t\n','').split('\t')
        data.append([float(tk) for tk in tokens[:-1]])
        labels.append(tokens[-1])
x = np.array(data)
y = np.array(labels)

clf = neighbors.KNeighborsClassifier(algorithm = 'kd_tree', n_neighbors = 1)
clf.fit(x,y)

answer = clf.predict(x)
print('准确率为:%f' % float(np.mean(answer == y)))#正确率

其准确率可以达到100%。

值得注意的是,上述neighbors.KNeighborsClassifier()中,存在很多可以自行调节的参数,这里可以查看官方文档(http://scikit-learn.org/stable/documentation.html) 并进行相关操作。

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

推荐阅读更多精彩内容