K-近邻算法
K-近邻算法的直观理解:
给定一个训练集和, 对于新输入的实例,在这个集合中找k个与该实例最近的邻居,然后判断这k个邻居大多数归属某一类,于是这个新输入的实例就划分为这一类。
K-近邻算法的三个要素
K-近邻算法有三要素,k值的选取,邻居距离的度量和分类决策的制定。
k值的选取
k较小,分类算法的鲁棒性较差,也很容易发生过拟合现象。k较大,分类错误率很快回升。所以人们通常采取交叉验证。
邻居距离的度量
归一化:
为公平起见,样本的不同特征需要做归一化处理,即把特征值都映射在[0, 1]范围之内。
归一化的公式有很多,最简单的方法是:对于给定的特征,首先找到他的最大值和最小值,然后对于某个特征值x,归一化公式可表示为:
x
=
x
−
M
I
N
M
A
X
−
M
I
N
x = \frac{x - MIN}{MAX - MIN}
x=MAX−MINx−MIN
计算距离公式:
计算样本中的距离公式有很多,其中最常用的是欧几里得距离:
L
2
=
∑
(
X
i
−
X
j
)
2
L2 = \sqrt{\sum{(X_i - X_j)^2}}
L2=∑(Xi−Xj)2
分类决策的制定:
一般使用加权投票原则:距离越近的邻居,权重越大。
K-近邻算法实战
我们使用鸢尾花会数据集进行实战
数据的获取
下载地址: 鸢尾花数据集下载.
获取本地数据的代码实现:
# 获得训练集和测试集
def loadDataset(filename, split, trainingSet=[], testSet=[]):
with open(filename, 'r') as csvfile:
lines = csv.reader(csvfile)
dataset = list(lines)
for x in range(len(dataset)):
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 EuclidDist(instance1, instance2, length):
# length是集合的长度
distance = 0.0
for x in range(length):
distance += pow((instance1[x] - instance2[x]), 2)
return math.sqrt(distance)
寻找最近的邻居
有了相似性的度量标准,下面我们就可以为一个给定的位置样本,找到它的k个最邻近的邻居。其过程是:对于这个未知的新样本,计算他与训练集合中的所有样本距离。然后再挑选k个最小的作为他的邻居。
# 寻找未知点的邻居
def getNeighbors(trainSet, testInstance, k):
distances = []
length = len(testInstance) # 因为其中包含种类信息,计算时不使用,所以长度需要-1
for x in range(len(trainSet)):
dist = EuclidDist(testInstance, trainSet[x], length-1)
distances.append((trainSet[x], dist))
distances.sort(key=operator.itemgetter(1))
neighbors = []
for x in range(k):
neighbors.append(distances[x][0])
return neighbors
分类
一旦确定了某个测试实例最近的k个邻居,那么我们就将依据“少数服从多数”的原则,决定该类别的所属分类。
# 判断分类
def getClass(neighbors):
classVotes = {}
for x in range(len(neighbors)):
instance_class = neighbors[x][-1]
if instance_class in classVotes:
classVotes[instance_class] += 1
else:
classVotes[instance_class] = 1
sortedVotes = sorted(classVotes.items(), key=operator.itemgetter(1), reverse=True)
# classVotes.items()返回可用于迭代操作的字典元素,按照字典的value排序, 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
综合案例
import csv
import math
import random
import operator
# 获得训练集和测试集
def loadDataset(filename, split, trainingSet=[], testSet=[]):
with open(filename, 'r') as csvfile:
lines = csv.reader(csvfile)
dataset = list(lines)
for x in range(len(dataset)):
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 EuclidDist(instance1, instance2, length):
# length是集合的长度
distance = 0.0
for x in range(length):
distance += pow((instance1[x] - instance2[x]), 2)
return math.sqrt(distance)
# 寻找未知点的邻居
def getNeighbors(trainSet, testInstance, k):
distances = []
length = len(testInstance) # 因为其中包含种类信息,计算时不使用,所以长度需要-1
for x in range(len(trainSet)):
dist = EuclidDist(testInstance, trainSet[x], length-1)
distances.append((trainSet[x], dist))
distances.sort(key=operator.itemgetter(1))
neighbors = []
for x in range(k):
neighbors.append(distances[x][0])
return neighbors
# 判断分类
def getClass(neighbors):
classVotes = {}
for x in range(len(neighbors)):
instance_class = neighbors[x][-1]
if instance_class in classVotes:
classVotes[instance_class] += 1
else:
classVotes[instance_class] = 1
sortedVotes = sorted(classVotes.items(), key=operator.itemgetter(1), reverse=True)
# classVotes.Items()返回可用于迭代操作的字典元素,按照字典的value排序, 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.70
loadDataset('iris.data', split, trainingSet, testSet)
print('训练结合样本数:' + repr(len(trainingSet)))
print('测试集合样本数:' + repr(len(testSet)))
predictions = []
k = 1
for x in range(len(testSet)):
neighbors = getNeighbors(trainingSet, testSet[x], k)
result = getClass(neighbors)
predictions.append(result)
print('>预测=' + repr(result) + ', 实际=' + repr(testSet[x][-1]))
accuracy = getAccuracy(testSet, predictions)
print('精确度为:' + repr(accuracy) + '%')
运行结果:
训练结合样本数:106
测试集合样本数:44
>预测='Iris-setosa', 实际='Iris-setosa'
>预测='Iris-setosa', 实际='Iris-setosa'
>预测='Iris-setosa', 实际='Iris-setosa'
……
>预测='Iris-virginica', 实际='Iris-virginica'
>预测='Iris-virginica', 实际='Iris-virginica'
>预测='Iris-virginica', 实际='Iris-virginica'
精确度为:93.18181818181817%