一、前言
如何区分一部片是爱情片还是动作片,爱情片中也有动作,动作片中也有亲吻的镜头。但是,动作片中打斗的场景更多,爱情片中亲吻的镜头更多。所以,可以引入“统计概率”的概念,基于某类场景出现的次数来分类。
二、k近邻算法概述
(1)原理:
给定一堆数据集,对于新输入的测试样例,通过计算其与数据集不同样本特征值之间的距离,从中找出与测试样例最相近的k个邻居。通过判断这k个邻居中哪种类别占多数,则预测该测试样例也属于该类。
(2)优点:
精度高,对异常值不敏感(只与相近的k个值相关),无数据输入假定
(3)缺点:
计算复杂度高,空间复杂度高,学习效率低(算不上学习,只是统计)
(4)特别注意:
其中,最重要的参数是K的选择。
如下图所示,输入绿色圆圈为测试样例。当K=3时,根据原理,最近的3个邻居中,红色三角占多数,则预测绿色圆圈属于红色三角类;当K=5时,同理,5个邻居中,蓝色方块占多数,则预测绿色圆圈属于蓝色方块类。
因此,K的选择将直接影响了预测的结果。如果K值过小,则对于邻近的训练数据特别敏感,容易学习到噪点数据,而造成过拟合;但是如果K值过大,把所有数据都当做邻居来预测,那就减弱了学习能力,只是简单地统计数值而已。
因此,根据李航的《统计学习方法》书中所说,K的选择,应该先以一个小值开始,利用交叉验证的方法,通过不断调参来选取最优值。通常k是不大于20的整数。
其次,对于距离公式的选择,一般是选择欧式距离,若p(p1,p2,…,pn)和q(q1,q2,…,qn)是空间的两点,则他们的距离为:
三、实践出真知
(1)核心算法
创建一个kNN.py文件,根据以上原理,写出核心算法代码。
def kNN_classify(test_datas, train_data_set, labels, k):
""" k近邻分类算法 """
data_set_size = train_data_set.shape[0] # 数据集个数,行数
# 1、计算新数据与训练集的欧氏距离
# tile是将测试集复制成与训练集同行数,一列
diff_mat = tile(test_datas, (data_set_size, 1)) - train_data_set #(x1-x2)
sq_diff_mat = diff_mat ** 2 #(x1-x2)平方
sq_distances = sq_diff_mat.sum(axis=1) # 按行累加
distances = sq_distances ** 0.5 # 开根号算出欧式距离
# 2、按照距离递增排序
sorted_dist_indicies = distances.argsort()
# 3、选取距离最近的前 k个,计算类别个数
class_count = {} # key是类别,value是个数
for i in range(k):
vote_label = labels[sorted_dist_indicies[i]]
# 这里字典的get函数是将找key为vote_label的值,若找不到,则用0
class_count[vote_label] = class_count.get(vote_label, 0) + 1 # 计算类别个数
# 4、排序找出类别个数最高的作为预测结果
sorted_class_count = sorted(class_count.items(), key=operator.itemgetter(1),
reverse=True) # 按照value(类的个数)逆序排序,找出类别最多的
return sorted_class_count[0][0] # [0][0]是预测的标签,[0][1]为k中最多的类别数
完整的验证kNN算法的程序文件为:
from numpy import *
import operator
def create_dataset():
""" 生成数据集 """
train_datas = array([[1.0, 1.1], [1.0, 1.0], [0, 0], [0, 0.1]])
labels = ['动作片', '动作片', '爱情片', '爱情片']
return train_datas, labels
def kNN_classify(test_datas, train_data_set, labels, k):
""" k近邻分类算法 """
data_set_size = train_data_set.shape[0] # 数据集个数,行数
# 1、计算新数据与训练集的欧氏距离
# tile是将测试集复制成与训练集同行数,一列
diff_mat = tile(test_datas, (data_set_size, 1)) - train_data_set #(x1-x2)
sq_diff_mat = diff_mat ** 2 #(x1-x2)平方
sq_distances = sq_diff_mat.sum(axis=1) # 按行累加
distances = sq_distances ** 0.5 # 开根号算出欧式距离
# 2、按照距离递增排序
sorted_dist_indicies = distances.argsort()
# 3、选取距离最近的前 k个,计算类别个数
class_count = {} # key是类别,value是个数
for i in range(k):
vote_label = labels[sorted_dist_indicies[i]]
# 这里字典的get函数是将找key为vote_label的值,若找不到,则用0
class_count[vote_label] = class_count.get(vote_label, 0) + 1 # 计算类别个数
# 4、排序找出类别个数最高的作为预测结果
sorted_class_count = sorted(class_count.items(), key=operator.itemgetter(1),
reverse=True) # 按照value(类的个数)逆序排序,找出类别最多的
return sorted_class_count[0][0] # [0][0]是预测的标签,[0][1]为k中最多的类别数
if __name__ == '__main__':
train_datas, labels = create_dataset()
test_1 = [1.0, 0.9]
pred_class = kNN_classify(test_1, train_datas, labels, 3)
print("test_1预测为:", pred_class)
test_2 = [0.2, 0.2]
pred_class = kNN_classify(test_2, train_datas, labels, 3)
print("test_2预测为:", pred_class)
结果为:
test_1预测为: 动作片
test_2预测为: 爱情片
(2)约会人员喜好预测
注释已经很详细,所以简单粗暴上代码:
from numpy import *
from kNN import kNN_classify
import matplotlib.pyplot as plt
import sys
def file_to_dataset(filename):
""" 将文件的数据集转换成分类器的输入格式 """
try:
with open(filename) as f_obj:
# 一次性读整个文件,自动将内容分析成一个行的字符串列表
list_of_lines = f_obj.readlines()
except FileNotFoundError:
print("Sorry, the file " + filename + " does not exist.")
sys.exit()
numbers_of_lines = len(list_of_lines) # 获取文件行数
dataset = zeros((numbers_of_lines, 3))
labels = []
index = 0
for line in list_of_lines:
line = line.strip() # 去掉头尾的指定字符,默认是空格或换行符
list_from_line = line.split('\t') # 以'\t'分割行,形成列表
# 取前三列放进矩阵中, matrix[index, :]表示index行所有列
dataset[index, :] = list_from_line[0:3]
labels.append(int(list_from_line[-1])) # 取最后一列作为标签放进标签列表
index += 1
return dataset, labels
def show_dataset(dataset, labels):
""" 可视化数据 """
fig = plt.figure() # 创建一个画板对象
ax = fig.add_subplot(111) # 创建子画板,位于第一行第一列第一个位置
colors = []
for i in labels:
if i == 1:
colors.append('red')
elif i== 2:
colors.append('green')
else :
colors.append('blue')
ax.scatter(x=dataset[:, 0], y=dataset[:, 1],
c=colors, s=15) # 取第2、3列作为x、y画散列图
plt.show()
def normalization(dataset):
"""
将数据集归一化到(0, 1)之间
new_value = (old_value-min)/(max-min)
"""
min_values = dataset.min(0) # 参数0确保取该矩阵每一列的最小值,1是每一行的最小值
max_values = dataset.max(0)
ranges = max_values - min_values
norm_dateset = zeros(shape(dataset))
num_of_lines = dataset.shape[0]
# tile是复制功能
norm_dateset = dataset - tile(min_values, (num_of_lines, 1))
norm_dateset = norm_dateset / tile(ranges, (num_of_lines, 1))
return norm_dateset, ranges, min_values
def dating_test():
""" 测试分类器效率 """
ratio = 0.10 # 测试集占 10%
train_datas, labels = file_to_dataset('ch02_kNN\datingTestSet2.txt')
norm_dataset, ranges, min_values = normalization(train_datas)
num_of_lines = norm_dataset.shape[0] # 总行数
num_test_data = int(num_of_lines * ratio) # 测试行数
error_count = 0.0
for i in range(num_test_data):
res = kNN_classify(norm_dataset[i, :],
norm_dataset[num_test_data: num_of_lines, :],
labels[num_test_data: num_of_lines], 4)
print("the classifier prediction is: " + str(res) +
", the real answer is:" + str(labels[i]))
if res != labels[i] :
error_count += 1.0
print("Error rate : " + str((error_count/float(num_test_data)*100)) + "%")
print("Accuracy rate : "+str((1-(error_count/float(num_test_data)))*100)+"%")
def classify_person():
""" 输出完整结果 """
res_lists = ['not at all', 'in small doses', 'in large doses']
percent_tats = float(input("percentage of time spend playing vodeo games?"))
ff_miles = float(input("frequent flier miles earned per year?"))
ice_cream = float(input("liters of ice cream consumed per year?"))
train_datas, labels = file_to_dataset('ch02_kNN\datingTestSet2.txt')
norm_dataset, ranges, min_values = normalization(train_datas)
new_test = array([ff_miles, percent_tats, ice_cream])
new_test = (new_test-min_values)/ranges # 对新数据归一化
res = kNN_classify(new_test, norm_dataset, labels, 4)
print("You will probably like this person: ", res_lists[res-1])
if __name__ == '__main__':
'''
# 准备数据
train_datas, labels = file_to_dataset('ch02_kNN\datingTestSet2.txt')
print("train_datas:\n", train_datas)
print("labels:\n", labels[0:20])
# 可视化
show_dataset(train_datas, labels)
# 归一化
norm_dataset, ranges, min_values = normalization(train_datas)
print("归一化后数据集:\n", norm_dataset)
print("每列的数值范围:\n", ranges)
print("每列最小值:\n", min_values)
# 测试
dating_test()
'''
# 使用
classify_person()
结果为:
percentage of time spend playing vodeo games?10
frequent flier miles earned per year?10000
liters of ice cream consumed per year?0.5
You will probably like this person: in small doses
(3)手写数字识别
简单粗暴上代码:
from numpy import *
from kNN import kNN_classify
import sys, os
def img_to_vector(filename):
""" 将一张数字图像转成向量格式 """
digit_vector = zeros((1, 1024))
try:
with open(filename) as f_obj:
for i in range(32):
list_of_line = f_obj.readline()
for j in range(32):
digit_vector[0, 32*i+j] = int(list_of_line[j])
return digit_vector
except FileNotFoundError:
print("Sorry, the file " + filename + " does not exist.")
sys.exit()
def create_train_set():
""" 将文件数据转为输入格式 """
labels = [] # 存放训练集的标签
train_file_list = os.listdir(r"ch02_KNN\digits\trainingDigits") # 得到所有文件名
num_train_set = len(train_file_list) # 训练的图片个数
train_datas = zeros((num_train_set, 1024)) # 创建训练集矩阵
# 遍历处理每一张图片
for i in range(num_train_set):
full_file_name = train_file_list[i] # 0_13.txt
file_name = full_file_name.split('.')[0] # 以‘。’分割,取第一个,即:0_13
label = int(file_name.split('_')[0]) # 以‘_’分割,取第一个,0就是标签
labels.append(label)
train_datas[i,:] = img_to_vector(
r"ch02_KNN\digits\trainingDigits\%s" % full_file_name
)
return train_datas, labels
def hand_writing_test():
""" 使用kNN算法识别手写数字 """
# 获取训练集
train_datas, labels = create_train_set()
# 处理测试集
test_file_list = os.listdir(r"ch02_KNN\digits\testDigits")
error_count = 0.0 # 统计错误率
num_test_set = len(test_file_list)
for i in range(num_test_set):
full_file_name = test_file_list[i]
file_name = full_file_name.split('.')[0]
label = int(file_name.split('_')[0])
test_data = img_to_vector(
r"ch02_KNN\digits\testDigits\%s" % full_file_name
)
# 使用 kNN分类器分类
pred_res = kNN_classify(test_data, train_datas, labels, 3)
print("the classifier prediction is: " + str(pred_res) +
", the real answer is:" + str(label))
if pred_res != label:
error_count += 1.0
print("Error rate : {:.2f}%".format(error_count/float(num_test_set)*100))
print("Accuracy rate : {:.2f}%".format((1-(error_count/float(num_test_set)))
*100))
if __name__ == "__main__":
hand_writing_test()
结果为:
the classifier prediction is: 0, the real answer is:0
the classifier prediction is: 0, the real answer is:0
the classifier prediction is: 0, the real answer is:0
。。。
the classifier prediction is: 9, the real answer is:9
the classifier prediction is: 9, the real answer is:9
the classifier prediction is: 9, the real answer is:9
the classifier prediction is: 9, the real answer is:9
Error rate : 1.06%
Accuracy rate : 98.94%
以上,就是这个算法的介绍和实践,有错误的希望留言指正,有疑问的也欢迎留言一起交流学习。