机器学习算法 —— KNN(Python实现)

一、简介

  KNN算法,全称为K Nearest Neighbor算法,也叫K临近算法,是一种懒惰学习的有监督分类算法。(懒惰学习是指训练后并不建立确定的模型,而是根据输入的数据与训练集的关系即时进行分类。)KNN算法作为机器学习中较基础的算法,其分类的准确性与变量K的取值有很大关系。


二、原理

  先举一个经典的例子,根据电影中接吻和打斗镜头出现的次数判断电影类型:

电影名称打斗次数接吻次数电影类型
California Man3104Romance
He’s Not Really into Dudes2100Romance
Beautiful Woman181Romance
Kevin Longblade10110Action
Robo Slayer 3000995Action
Amped II982Action
Test1890Unknown

  将上述表格中的前六组数据作为训练集,最后一组数据作为测试集,将打斗次数接吻次数作为特征,将电影类型作为标记,则可以将这六个实例看作平面上的六个点,打斗次数为x值,接吻次数为y值(若特征数大于2,则可由平面拓展至体或超平面),可得到下表:

x值y值类型
A3104Romance
B2100Romance
C181Romance
D10110Action
E995Action
F982Action
G1890Unknown

  为便于理解,将这些点绘制在平面直角坐标系中,如图:

  在KNN算法中,若要确定Test(红点)的类型,只需在坐标系中找到与点G距离最近的K个点,就可以根据这K个点的类型判断出点G的类型:若这K个点对应的类型均为Romance/Action,则点G的类型也为Romance/Action;若这K个点对应的类型中既有Romance也有Action,则根据投票法则判断点G的类型,因此常取K的值为3、5、7等奇数。
  对于点G到这K个点的距离有多种定义,如欧几里得度量(Euclidean Distance)、余弦值(cos)、相关度(correlation)、曼哈顿距离(Manhattan Distance)等,这里根据欧几里得度量计算,公式为:
E ( x , y ) = ∑ i = 0 n ( x i − y i ) 2 {E(x,y)=}\sqrt{\sum_{i=0}^{n}{(x_i-y_i)^2}} E(x,y)=i=0n(xiyi)2
  注意:训练后,进行测试时会先计算测试实例对应点到训练集中所有点的距离并存储,再根据K的取值找出与测试点距离最近的K个点。
  但当出现下图中的情况时,判断的准确率将大大降低:

  对于上图中的Y,虽然属于ω1,但因为ω2在这一侧的分布过于密集,导致极有可能将Y分类为ω2。为避免这种情况的出现,我们可以对上述算法进行改进,即在计算训练集中各点到测试点的距离后,先将每段距离乘以权重再存储,如乘以 1 d {\frac{1}{d}} d1(d为距离)。


三、应用

环境:Python 3.6

  这里使用IRIS数据集测试,以txt文件的形式存储在本地,格式如下:

5.1,3.5,1.4,0.2,Iris-setosa
7.0,3.2,4.7,1.4,Iris-versicolor
6.3,3.3,6.0,2.5,Iris-virginica
......

  具体实现如下:

import csv
import math
import operator
import random


def loadDataset(filename, split, trainingSet = [], testSet = []):
    with open(filename, 'rt') as csvfile:
        lines = csv.reader(csvfile)
        dataSet = list(lines)
        for x in range(len(dataSet) - 1):
            for y in range(4):
                dataSet[x][y] = float(dataSet[x][y])
                if random.random() < split:
                    trainingSet.append(dataSet[x])
                else:
                    testSet.append(dataSet[x])


def euclideanDistance(instance1, instance2, length):
    distance = 0
    for x in range(length):
        distance += pow((instance1[x] - instance2[x]), 2)
    return math.sqrt(distance)


def getNeighbors(trainingSet, testInstance, k):
    distances = []
    length = len(testInstance) - 1
    for x in range(len(trainingSet)):
        dist = euclideanDistance(testInstance, trainingSet[x], length)
        distances.append((trainingSet[x], dist))
    distances.sort(key=operator.itemgetter(1))
    neighbors = []
    for i in range(k):
        neighbors.append(distances[i][0])
    return neighbors


def getResponse(neighbors):
    classVotes = {}
    for x in range(len(neighbors)):
        response = neighbors[x][-1]
        if response in classVotes:
            classVotes[response] += 1
        else:
            classVotes[response] = 1
    sortedVotes = sorted(classVotes.items(), key=operator.itemgetter(1), reverse=True)
    return sortedVotes[0][0]


def getAccuracy(testSet, predictions):
    correct = 0
    for x in range(len(testSet)):
        if testSet[x][-1] == predictions[x]:
            correct += 1
    return (correct / float(len(testSet))) * 100.0


if __name__ == '__main__':
    trainingSet = []
    testSet = []
    split = 0.25
    loadDataset(r'D:\XXX\iris.txt', split, trainingSet, testSet)
    predictions = []
    k = 3
    for x in range(len(testSet)):
        neighbors = getNeighbors(trainingSet, testSet[x], k)
        result = getResponse(neighbors)
        predictions.append(result)
    accuracy = getAccuracy(testSet, predictions)
    print(accuracy, "%")

  loadDataset:从本地文件载入使用的数据集。 先用csv模块从本地文件中读取数据集中的所有实例,再将数据集转化为二维数组,形如[['5.1', '3.5', '1.4', '0.2', 'Iris-setosa'], ['4.9', '3.0', '1.4', '0.2', 'Iris-setosa'], ['4.7', '3.2', '1.3', '0.2', 'Iris-setosa'], ...]。变量split的作用是将原数据集按比例分为训练集和测试集,如当split == 0.75时,表示将原数据集的 3 4 {\frac{3}{4}} 43作为训练集, 1 4 {\frac{1}{4}} 41作为测试集。

  euclideanDistance:根据欧几里得度量计算某测试实例对应的点与训练集中点的距离。 由于该数据集中有四个特征变量,故根据公式 E ( x , y ) = ( x 1 − y 1 ) 2 + ( x 2 − y 2 ) 2 + ( x 3 − y 3 ) 2 + ( x 4 − y 4 ) 2 {E(x,y)=}\sqrt{{(x_1-y_1)^2+(x_2-y_2)^2+(x_3-y_3)^2+(x_4-y_4)^2}} E(x,y)=(x1y1)2+(x2y2)2+(x3y3)2+(x4y4)2 计算两点间的距离。
  Tips:这里的x、y不表示横坐标和纵坐标,而是表示instance1和instance2。

  getNeighbors:得到与某测试实例距离最近的k个点。 先调用euclideanDistance()计算该实例对应的点与训练集中各点的距离,再将所有距离排序,取出与测试点距离最近的k个点。
  Tips:这里返回的是实例,不是对应的类型。

  getResponse:得到对某测试实例分类结果。 遍历在getNeighbors()中得到的与测试实例距离最近的k个点并排序,取第0位的值,即最近的k个点中最多的类型作为分类结果。通过在主函数中循环调用来得到整个测试集的分类结果。
  Tips:这里的len(neighbors)即为k。

  getAccuracy:计算预测结果的准群率。 比较测试集的最后一位与分类结果,用正确的个数除以总数再乘以100.0,得到正确的百分比,即准确率。

  • 0
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

SoCalledHBY

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值