k-NN算法介绍
kNN(k - Nearest Neighbor),中文名:k-近邻算法
工作原理:
存在一个数据集,数据集中的每个样本都有对应的标签(即该样本属于哪个类别)。当输入没有标签的新数据时,计算新数据与数据集中每个样本对应特征之间的距离之和,新数据与哪个样本距离之和最小(最近邻),就把该样本的标签作为新数据的标签。k的意思是,在实际应用中一般选取前k个与新数据距离最小的样本,在k个样本中出现最多的类别,即作为新数据的类别(标签)。
算法流程:
1. 计算新数据与数据集中每个样本对应特征之间的距离之和;
2. 将所有距离按照递增关系进行排序;
3. 选取前k个距离最小的样本;
4. 找出k个样本中出现最多的类别;
5. 将k个样本中出现最多的类别作为新数据的类别。
优点
精度高,对异常值不敏感、无数据输入假定,并且k-NN算法不知道数据的内在含义。
缺点
计算复杂度和空间复杂度高
实例:
以电影分类为示例介绍。
项目目录如图:
数据准备:
首先调用load_data.py模块中的load_data方法生成所需数据,可以输入参数以指定需要生成的数据量。load_data.py中的代码如下:
注:数据只是测试用,都是随机生成,不做真实参考。
import pandas as pd # 导入pandas库
import numpy as np
import string
def load_data(nums=0):
movies_data = [('CM', 3, 104, '爱情片'), ('HRD', 2, 100, '爱情片'), ('BW', 1, 81, '爱情片'),
('KL', 101, 10, '动作片'), ('RS', 99, 5, '动作片'), ('A', 98, 2, '动作片'),
]
for _ in range(nums):
movies_name_len = np.random.randint(2, 6)
movies_name = ''
for n in range(movies_name_len):
movies_name = movies_name + string.ascii_letters[np.random.randint(1, 26)]
fights = np.random.randint(1, 200)
kisses = np.random.randint(1, 200)
if fights >= kisses:
category = '动作片'
else:
category = '爱情片'
movies_data.append((movies_name, fights, kisses, category))
df = pd.DataFrame(data=movies_data, columns=['电影名称', '打斗镜头', '接吻镜头', '电影类别'])
df.to_csv('data/movies_data.csv', index=None)
return df
# if __name__ == '__main__':
# d = load_data(100)
# print(d)
可以调用plot.py中的模块生成电影类别分布:
手动实现kNN代码
手写代码主要是用numpy库实现,在数据量达到上千万时,这种方式速度更快。
knn.py代码如下:
import numpy as np
import collections
def kNN(new_data, train_data, labels, k=1):
"""
:param new_data: 新数据
:param train_data: 训练数据
:param labels: 训练数据的标签
:param k: k值大小
:return: 新数据的所属类别
"""
train_set_rows = train_data.shape[0] # 计算训练数据的数据量
subs = train_data - np.tile(new_data, (train_set_rows, 1))
distances = np.sqrt(np.sum(np.square(subs), axis=1)) # 计算新数据与训练集每个样本之间的欧式距离
assert distances.shape != labels.shape, '维度不一致'
sorted_distances = np.argsort(distances) # 对距离进行排序
k_labels = []
for i in range(k):
k_labels.append(labels[sorted_distances[i]][0]) # 找出排名前k的样本类别
new_label = collections.Counter(k_labels).most_common(1) # 对前k个样本类别进行统计,并输出最多的类别
return new_label[0][0]
测试及sklearn实现kNN
sklearn实现kNN操作更简单,具体方法参数,有兴趣的自行学习。
import datetime
import numpy as np
from sklearn.neighbors import KNeighborsClassifier
from load_data import *
from knn import kNN
from plot import plot
if __name__ == "__main__":
train_set = load_data(10)
plot(train_set)
new_data = ['ZL', 169, 2]
train_data = np.array(train_set[['打斗镜头', '接吻镜头']])
train_labels = np.array(train_set[['电影类别']])
time_s = datetime.datetime.now()
# ===========================手动实现======================
label = kNN(new_data[1:], train_data, train_labels, k=3)
# ===========================sklearn实现======================
# clf = KNeighborsClassifier(n_neighbors=3)
# clf.fit(train_data, train_labels)
# label = clf.predict([new_data[1:]]) # 输入是2D数据
time_e = datetime.datetime.now() - time_s
print('用时:', time_e)
print('新数据的类别:', label)