问题描述
- 说明:
KNN算法的核心是要获取k个近邻,衡量的指标通常用的是欧氏距离。 - 步骤
(1)获取到所有训练样本的特征向量矩阵
(2)利用待测样本的特征向量,与所有训练样本的特征向量计算欧式距离
(3)对得到的距离进行排序,根据指定的k获取前k个距离最近的索引(即训练样本原本在特征矩阵中的索引)
(4)根据获取到的索引来获得该训练样本的标签
(5)统计前k个近邻的标签出现的次数,返回出现次数最多的标签,并将其作为待测样本的预测标签
代码示例
#!/usr/bin/env python
# -*- coding: utf-8 -*-
"""
@Author : Sword
@Date : 2020/5/28
@Time : 下午 14:22
@Version : v1.0
@File : knn_new.py
@Describe :
"""
import numpy as np
import operator
def create_dataset():
# 创建4个样本,特征矩阵为 4 * 2
group = np.array([[1.0, 1.1], [1.0, 1.0], [0, 0], [0, 0.1]])
labels = ['A', 'A', 'B', 'B']
return group, labels
def classify(test_x, dataset, labels, k):
"""
:param test_x: 待测样本的特征向量
:param dataset: 训练样本集的特征矩阵
:param labels: 训练样本的标签
:param k: 前k个最近邻
"""
dataset_size = dataset.shape[0]
# 计算待测样本test_x与每个训练样本的距离
# 计算每一维特征的差值,diff_mat中每个元素表示(x1 - x2)
# 然后对diff_mat中的每个元素求平方,再按行累加再开根号得到与当前
# 样本的距离
diff_mat = np.tile(test_x, (dataset_size, 1)) - dataset
distance_mat = (diff_mat ** 2).sum(axis=1) ** 0.5
# 根据距离排序,返回索引值
sorted_dist_index = distance_mat.argsort()
class_count = {}
for i in range(k):
# 根据距离对比返回的前k个近邻获取其索引值
# 然后根据索引值获取该近邻的标签
# 并利用字典来统计k个近邻的标签出现次数
vote_label = labels[sorted_dist_index[i]]
class_count[vote_label] = class_count.get(vote_label, 0) + 1
# 将字典按照值进行排序,返回一个(label, count)元组,然后返回第一个元组对应的标签
# 第一个元组存放着出现次数最多的标签
sorted_class_count = sorted(class_count.items(), key=operator.itemgetter(1), reverse=True)
return sorted_class_count[0][0]
def file_2_matrix(path):
"""
根据文件路径将文件信息存储为特征向量矩阵和标签列表
文件内容的格式为: 特征1 特征2 特征3 标签
大小为:(1000, 3)
"""
with open(path, 'r') as f:
lines = f.readlines()
# 声明一个全零矩阵,用于存放特征值
# 首先获取该矩阵的大小,总共3列特征
size = len(lines)
feature_matrix = np.zeros((size, 3))
# 声明一个列表,存放标签
label_list = []
# 按行读取数据,每一行的前3维是特征,最后一维是标签
# 声明一个变量来记录当前是第几行
index = 0
for line in lines:
value = line.strip().split('\t')
# 取出前3维的特征,存放到特征矩阵中
feature_matrix[index, :] = value[0:3]
# 取出最后一维的标签,存放到标签列表中
label_list.append(int(value[-1]))
# 存完一行(即表示存了一个样本)就增加1
index += 1
return feature_matrix, label_list
def normalize(dataset):
"""
将特征值转化为0-1区间内的值
new_value = (value - min) / (max - min)
"""
# 获取特征矩阵中每一列的最小值和最大值
# 0表示获取每一列的最小值
# 1表示获取每一行的最小值
# 因为对象是特征矩阵(1000, 3), 所以返回的min_value和max_value的大小为(1, 3)
min_value = dataset.min(0)
max_value = dataset.max(0)
range = max_value - min_value
# 将特征矩阵的每个元素都按照公式进行转化
matirx_size = dataset.shape[0]
# 矩阵中每个元素 - min_value
feature_matirx = dataset - np.tile(min_value, (matirx_size, 1))
# 矩阵中每个元素 / (max - min) ==> 分母相当于range
feature_matirx = feature_matirx / np.tile(range, (matirx_size, 1))
return feature_matirx
def predict(test_size=0.1):
"""
对测试样本进行测试,测试样本从数据集中的第一个开始选取,直到满足条件为止
这样方便将后面的所有样本作为训练数据
:param test_size: 测试样本的比例
"""
# 根据文件路径获取训练数据和标签
# 并对特征值进行转换
file_path = './data/datingTestSet.txt'
train_data, train_label = file_2_matrix(file_path)
train_data = normalize(train_data)
# 获取训练数据的数量以及测试样本的数量
train_data_num = train_data.shape[0]
test_num = int(test_size * train_data_num)
# 声明一个变量用于记录分类错误的样本数
error_count = 0
# 根据测试样本的数量进行循环
# 每次取出一个样本进行测试
for i in range(test_num):
predict_result = classify(train_data[i, :], train_data[test_num:train_data_num, :],
train_label[test_num:train_data_num], 3)
print("the classifier came back with:%d, the real answer is:%d" % (predict_result, train_label[i]))
if predict_result != train_label[i]:
error_count += 1
accuracy = 100. * (1 - (error_count / test_num))
print("test accuracy is:%.2f%%" % accuracy)
if __name__ == '__main__':
file_path = './data/datingTestSet.txt'
predict()