KNN算法实验报告
一、实验准备
1、实验内容和目的
- 使用KNN算法预测鸢尾花的种类,其中训练数据集为train.txt,测试集为test.txt。训练样本为120个,测试样本为30个。
2、实验原理
-
K最近邻(k-NearestNeighbor, KNN)分类算法是一个理论上比较成熟的方法,同时也是比较简明易懂的机器学习算法之一。
-
KNN算法的思路是:如果一个样本在特征空间中的k个最相似(即特征空间中最邻近)的样本中的大多数属于某一个类别,则该样本也属于这个类别。KNN算法中,所选择的邻居都是已经正确分类的对象。该方法在定类决策上只依据最近邻的一个或者多个样本的类别来决定待分类样本所属的类别。
-
由于KNN算法在做类别决策时,只与极少量的相邻样本有关,因此采用该算法能够比较好的避免样本的不平衡问题。另外,由于KNN算法主要依靠周围有限的临近样本,而不是靠判别类域的方法来确定所属类别,因此对于类域的交叉或者重叠较多的待分类样本集来说,KNN算法较其他算法更合适。
-
KNN算法的优缺点:
-
优点:精度高,对异常值不敏感,无数据输入假定
-
缺点:计算复杂度高,空间复杂度高
-
二、进行实验
1、算法思路
-
KNN算法存放所有的训练样本,在接受待分类的新样本之前不需要构造模型,而是直到新的(未标记的)样本需要分类时才建立相应的分类模型。KNN分类基于类比学习,其训练样本由N维数值属性描述,每个样本代表N维空间的一个点。如此,所有训练样本都存放在N维模式空间中。给定一个未知样本,KNN分类法搜索模式空间,找出最接近未知样本的k个训练样本,这k个训练样本即为未知样本的k个“近邻”。
-
“临近性”又称为相异度,由欧几里德距离定义,其中两个点X(x1,x2…xn)和Y(y1,y2…yn)的欧几里德距离为:D(x,y)=√(x1-y1)2 + (x2-y2)2 +…+ (xn-yn)2
-
未知样本被分配到k个最邻近者中最公共的类别。在最简单的情况下,也就是k=1时,未知样本被指定到模式空间中与之距离最近的训练样本的类别。
2、算法步骤
-
(1) 对训练数据进行处理,提出每一个样本数据和其对应的标签
-
(2) 对训练数据和测试数据进行归一化处理
-
(3) 计算并存储每一个测试样本到所有训练样本的距离
-
(4) 对得到的距离集合进行排序
-
(5) 选择距离最小的k个样本
-
(6) 找出k个样本中最公共的类别,即为该测试样本所属的类别
3、代码实现
-
注:代码中的所有函数功能已注释在函数头部
-
(1) 处理训练数据和测试数据。因为训练数据中的每个样本包含标签而测试数据中的样本不包含,因此使用两个不同的功能函数分别进行处理。我所实现的这两个函数具有兼容性,不仅仅局限于该实验的数据处理,也能够处理其他的实验数据。
def trainingFile2Matrix(filename):
"""
函数说明:
处理训练数据集
:param filename:
训练数据文件
:return:
returnMat - 处理得到的每一个训练样本的数据集合
returnLabel - 每一个训练样本所属的类别标签集合
"""
file = open(filename)
content = file.readlines()
lineCount = len(content)
returnMat = np.zeros((lineCount, 4))
returnLabel = []
index = 0
for line in content:
line = line.strip()
example = line.split(',')
returnMat[index, : ] = example[0 : 4]
index += 1
returnLabel.append(example[4])
return returnMat, returnLabel
def testFile2Matrix(filename):
"""
函数说明:
处理测试数据集
:param filename:
测试数据文件
:return:
returnMat - 处理得到的每一个测试样本的数据集合
"""
file = open(filename)
content = file.readlines()
lineCount = len(content)
returnMat = np.zeros((lineCount, 4))
index = 0
for line in content:
line = line.strip()
example = line.split(',')
returnMat[index, : ] = example[0 : 4]
index += 1
return returnMat
- (2) 数据的归一化处理。通过归一化处理,希冀预测准确率能有所提高。
def getNormolization(dataSet):
"""
函数说明:
对数据进行归一化
:param dataSet:
数据集合
:return:
normDataSet - 归一化后的数据集合
"""
minVals = dataSet.min(0)
maxVals = dataSet.max(0)
ranges = maxVals - minVals
normDataSet = np.zeros(np.shape(dataSet))
m = dataSet.shape[0]
normDataSet = dataSet - np.tile(minVals, (m,1))
normDataSet = normDataSet / np.tile(ranges, (m,1))
return normDataSet
- (3) 计算测试样本与训练样本之间的欧几里德距离。
def calculateDistance(train_example, test_example, example_length):
"""
函数说明:
计算训练样本和测试样本之间的欧几里德距离
:param train_example:
训练样本的数据
:param test_example:
测试样本的数据
:param example_length:
样本的属性长度
:return:
distance - 训练样本和测试样本之间的欧几里德距离
"""
distance = 0.0
for i in range(example_length):
distance += pow(train_example[i] - test_example[i], 2)
return distance
- (4) 取得与测试样本距离最近的k个训练样本。实现方法为:以元组的形式存储每个训练样本的标签以及测试样本到该训练样本的欧几里德距离,然后根据距离从小到大排序,最后取得前k个元组中的训练样标签。
def get_K_Neighbors(trainingSet, trainingLabel, test_example, k):
"""
函数说明:
取得与测试样本距离最近的k个训练样本
:param trainingSet:
训练样本数据集
:param trainingLabel:
训练样本标签集
:param test_example:
测试样本
:param k:
即参数k
:return:
kNeighbors - 与测试样本最近的k个训练样本的集合
"""
length = len(test_example)
distances = []
for i in range(len(trainingSet)):
dis = calculateDistance(trainingSet[i], test_example, length)
distances.append((trainingLabel[i], dis))
distances.sort(key=operator.itemgetter(1))
kNeighbors = []
for i in range(k):
kNeighbors.append(distances[i][0])
return kNeighbors
- (5) 取得与测试样本距离最近的k个训练样本中的最公共类别。根据get_K_Neighbors函数所返回的k个标签,找出其中出现次数最多的标签。
def getReasult(kNeighbors):
"""
函数说明:
取得与测试样本距离最近的k个训练样本中的最公共类别
:param kNeighbors:
与测试样本最近的k个训练样本的集合
:return:
sortedLabel[0][0] - 预测该测试样本所属的类别
"""
classLabel = {}
for i in range(len(kNeighbors)):
temp = kNeighbors[i]
if temp in classLabel:
classLabel[temp] += 1
else:
classLabel[temp] = 1
sortedLabel = sorted(classLabel.items(), key=operator.itemgetter(1), reverse=True)
return sortedLabel[0][0]
- (6) 计算预测的准确率。这里只有在训练算法的时候才会用到,做实际的测试时,由于不知道测试样本的真实类别,所以不能计算准确率。
def getAccuracy(testLabel, predictions):
"""
函数说明:
计算预测的准确率
:param testLabel:
测试数据所属的真实类别
:param predictions:
预测测试数据所属的类别
:return:
(cnt / float(len(testLabel))) * 100.0 - 准确率
"""
cnt = 0
for i in range(len(testLabel)):
if(testLabel[i] == predictions[i]):
cnt += 1
return (cnt / float(len(testLabel))) * 100.0
- (7) 将预测结果写入测试文件中。根据要求,将每个测试样本的预测类别写在了每个样本数据的末尾,最后的结果请见文件test.txt。
def write2File(filename, resultSet):
"""
函数说明:
将测试结果写入文件
:param filename:
要写入的文件
:param resultSet:
测试结果集合
"""
with open(filename, "r", encoding="utf-8") as f_read:
content = f_read.readlines()
#print(content)
index = 0
length = len(resultSet)
with open(filename, "w", encoding="utf-8") as f_write:
for i in range(length):
str = ''
temp = content[i].strip('\n')
str = temp + ',' + resultSet[i] + '\n'
index += 1
f_write.write(str)
三、完整代码
import numpy as np
import operator
def trainingFile2Matrix(filename):
"""
函数说明:
处理训练数据集
:param filename:
训练数据文件
:return:
returnMat - 处理得到的每一个训练样本的数据集合
returnLabel - 每一个训练样本所属的类别标签集合
"""
file = open(filename)
content = file.readlines()
lineCount = len(content)
returnMat = np.zeros((lineCount, 4))
returnLabel = []
index = 0
for line in content:
line = line.strip()
example = line.split(',')
returnMat[index, : ] = example[0 : 4]
index += 1
returnLabel.append(example[4])
return returnMat, returnLabel
def testFile2Matrix(filename):
"""
函数说明:
处理测试数据集
:param filename:
测试数据文件
:return:
returnMat - 处理得到的每一个测试样本的数据集合
"""
file = open(filename)
content = file.readlines()
lineCount = len(content)
returnMat = np.zeros((lineCount, 4))
index = 0
for line in content:
line = line.strip()
example = line.split(',')
returnMat[index, : ] = example[0 : 4]
index += 1
return returnMat
def calculateDistance(train_example, test_example, example_length):
"""
函数说明:
计算训练样本和测试样本之间的欧几里德距离
:param train_example:
训练样本的数据
:param test_example:
测试样本的数据
:param example_length:
样本的属性长度
:return:
distance - 训练样本和测试样本之间的欧几里德距离
"""
distance = 0.0
for i in range(example_length):
distance += pow(train_example[i] - test_example[i], 2)
return distance
def get_K_Neighbors(trainingSet, trainingLabel, test_example, k):
"""
函数说明:
取得与测试样本距离最近的k个训练样本
:param trainingSet:
训练样本数据集
:param trainingLabel:
训练样本标签集
:param test_example:
测试样本
:param k:
即参数k
:return:
kNeighbors - 与测试样本最近的k个训练样本的集合
"""
length = len(test_example)
distances = []
for i in range(len(trainingSet)):
dis = calculateDistance(trainingSet[i], test_example, length)
distances.append((trainingLabel[i], dis))
distances.sort(key=operator.itemgetter(1))
kNeighbors = []
for i in range(k):
kNeighbors.append(distances[i][0])
return kNeighbors
def getReasult(kNeighbors):
"""
函数说明:
取得与测试样本距离最近的k个训练样本中的最公共类别
:param kNeighbors:
与测试样本最近的k个训练样本的集合
:return:
sortedLabel[0][0] - 预测该测试样本所属的类别
"""
classLabel = {}
for i in range(len(kNeighbors)):
temp = kNeighbors[i]
if temp in classLabel:
classLabel[temp] += 1
else:
classLabel[temp] = 1
sortedLabel = sorted(classLabel.items(), key=operator.itemgetter(1), reverse=True)
return sortedLabel[0][0]
def getAccuracy(testLabel, predictions):
"""
函数说明:
计算预测的准确率
:param testLabel:
测试数据所属的真实类别
:param predictions:
预测测试数据所属的类别
:return:
(cnt / float(len(testLabel))) * 100.0 - 准确率
"""
cnt = 0
for i in range(len(testLabel)):
if(testLabel[i] == predictions[i]):
cnt += 1
return (cnt / float(len(testLabel))) * 100.0
def getNormolization(dataSet):
"""
函数说明:
对数据进行归一化
:param dataSet:
数据集合
:return:
normDataSet - 归一化后的数据集合
"""
minVals = dataSet.min(0)
maxVals = dataSet.max(0)
ranges = maxVals - minVals
normDataSet = np.zeros(np.shape(dataSet))
m = dataSet.shape[0]
normDataSet = dataSet - np.tile(minVals, (m,1))
normDataSet = normDataSet / np.tile(ranges, (m,1))
return normDataSet
def write2File(filename, resultSet):
"""
函数说明:
将测试结果写入文件
:param filename:
要写入的文件
:param resultSet:
测试结果集合
"""
with open(filename, "r", encoding="utf-8") as f_read:
content = f_read.readlines()
#print(content)
index = 0
length = len(resultSet)
with open(filename, "w", encoding="utf-8") as f_write:
for i in range(length):
str = ''
temp = content[i].strip('\n')
str = temp + ',' + resultSet[i] + '\n'
index += 1
f_write.write(str)
def classify():
"""
函数说明:
综合调用前面的功能函数,实现KNN算法的所有步骤
"""
#自定义测试
trainingMat, trainingLabel = trainingFile2Matrix("train.txt")
testMat = testFile2Matrix("test.txt")
norm_trainingMat = getNormolization(trainingMat)
norm_testMat = getNormolization(testMat)
#print(norm_trainingMat)
#print()
#print(norm_testMat)
result = []
for i in range(len(testMat)):
kNeighbors = get_K_Neighbors(norm_trainingMat, trainingLabel, norm_testMat[i], 3)
#print(kNeighbors)
#print()jignn
result.append(getReasult(kNeighbors))
#print(result)
#print(len(result))
#print("预测准确率:" + str(getAccuracy(testLabel, result)))
#print()
write2File("test.txt", result)
if __name__ == "__main__":
classify()