目录python
不知道有没有喜欢看电影的同窗,今天咱们先不讲咱们的 k-近邻算法,咱们来说讲电影。git
可能有的同窗喜欢看恐怖片,可能男生比较喜欢看爱情片,也有可能咱们的女同窗喜欢看动做片。那同窗们大家有没有想过,咱们所说的恐怖片、爱情片和动做片都是以什么来划分的呢?。。。对,有些同窗已经讲到重点了,动做片中可能打斗场景较多;爱情片会存在接吻的镜头。可是,可能有些同窗已经想到了。。。对,虽然动做片中会有较多的打斗场景,那么大家有没有想过某些动做片中会有接吻的镜头,爱情片也是这样。可是,有一点咱们是须要清楚的,假设电影只有两个分类——动做片和爱情片二分类问题适合入门,动做片的打斗场景相对于爱情片必定是较多的,而爱情片的接吻镜头相对于动做片是较多的,肯定这一点后,经过这一点咱们就能判断一部电影的类型了。程序员
k-近邻算法:测量不一样特征值之间的距离方法进行分类算法
优势:精度高、对异常值不敏感、无数据输入假定。 缺点:计算复杂度高、空间复杂度高。 使用数据类型:数值型和标称型。
k-近邻算法(kNN)工做原理:小程序
相信你们对 k-近邻算法有了一个大概的了解,对他须要作什么有了必定的了解,可是因为他的抽象,大家可能仍是似懂非懂,这个时候咱们来到咱们以前所叙述的电影分类的例子中,刚刚咱们得出了一个结论——动做片的打斗场景多余爱情片;爱情片的接吻场景大于动做片,那如今咱们有一部没有看过的电影,咱们如何肯定它是爱情片仍是动做片呢?固然,有的同窗已经想到了。。。使用咱们的 kNN 来解决这个问题。windows
图2-1 使用打斗和接吻镜头数分类数组
经过图2-1咱们能很清晰的看到每一个电影纯在多少个打斗镜头和接吻镜头。app
表2-1 每部电影的打头镜头和接吻镜头次数和电影类型机器学习
序号 | 电影名称 | 打斗镜头 | 接吻镜头 | 电影类型 |
---|---|---|---|---|
1 | California Man | 3 | 104 | 爱情片 |
2 | He’s Not Really into Dudes | 2 | 100 | 爱情片 |
3 | Beautiful Woman | 1 | 81 | 爱情片 |
4 | Kevin Longblade | 101 | 10 | 动做片 |
5 | Robo Slayer 3000 | 99 | 5 | 动做片 |
6 | Amped II | 98 | 2 | 动做片 |
7 | ? | 18 | 90 | 未知 |
很明显经过表2-1咱们没法得知’?’是什么类型的电影。可是咱们能够按照刚刚的思路计算未知电影与其余电影的距离。如表2-2所示。暂时不要关心这个数据是怎么算出来的,你目前只须要跟着个人思路走,等下一切自会揭晓。函数
表2-2 已知电影与未知电影的距离
序号 | 电影名称 | 与未知电影的距离 |
---|---|---|
1 | California Man | 20.5 |
2 | He’s Not Really into Dudes | 18.7 |
3 | Beautiful Woman | 19.2 |
4 | Kevin Longblade | 115.3 |
5 | Robo Slayer 3000 | 117.4 |
6 | Amped II | 118.9 |
咱们能够从表2-2中找到 k 个距离’?’最近的电影。咱们假设 k=3,则这三个最靠近的电影依次是He’s Not Really into Dudes、Beautiful Woman和 California Man。经过 k-近邻算法的结论,咱们发现这3部电影都是爱情片,所以咱们断定未知电影是爱情片。
经过对电影类型的判断,相信同窗们对 k-近邻算法有了一个初步的认识。
下面我将带你们简单的了解下 k-近邻算法的流程:
1. 收集数据:提供文本文件 2. 准备数据:对文本文件的数据作处理 3. 分析数据:检查数据确保它符合要求 4. 训练算法:此步骤不适用于 k-近邻算法 5. 测试算法:使用测试样本测试 6. 使用算法:构建一个完整的应用程序
# kNN.py from numpy import * import operator def create_data_set(): """ 初始化数据,其中group 数组的函数应该和标记向量 labels 的元素数目相同。 :return: 返回训练样本集和标记向量 """ group = array([[1.0, 1.1], [1.0, 1.0], [0, 0], [0, 0.1]]) # 建立数据集 labels = ['A', 'A', 'B', 'B'] # 建立标记 return group, labels
因为咱们大脑的限制,咱们一般只能处理可视化为三维如下的事务,固然也为了之后课程的易于理解,咱们对于每一个数据点一般只使用两个特征。主要使用多个特征就须要常用线性代数的知识,只要你对一个特征、两个特征把握准确了,特征多了也不过是多加几个参数而已。
数组中的每一组数据对应一个标记,即[1.0, 1.1]对应’A’、[0, 0.1]对应’B’,固然,例子中的数值是你能够定制化设计的。咱们能够经过四组数据画出他们的图像。
图2-2 k-近邻算法_带有四个数据点的简单例子
数据准备好了,下面就是咱们的动手时间了。
# 伪代码 1. 计算已知类别数据集中的点与当前点之间的距离。 2. 按照距离递增次序排序。 3. 选取与当前点距离最小的 k 个点。 4. 肯定前 k 个点多在类别的出现频率。 5. 返回前 k 个点出现频率最高的类别做为当前点的预测分类。
1️⃣在kNN.py中使用欧氏距离计算两个向量\(x_A\)和\(x_B\)之间的距离:
\[d=\sqrt{(x_{A_0}-x_{B_0})^2+(x_{A_1}-{x_{B_1}})^2}\]
例如,点\((0,0)\)与\((1,2)\)之间的距离计算为:
\(\sqrt{(1-0)^2+(2-0)^2}\)
若是数据集存在4个特征值,则点\((1,0,0,1)\)与\((7,6,9,4)\)之间的距离计算为:
\(\sqrt{(7-1)^2+(6-0)^2+(9-0)^2+(4-1)^2}\)
2️⃣计算完全部点的距离后,对数据从小到大排序后肯定 k 个距离最小元素所在的主要分类。输入的 k 是正整数。
3️⃣最后将 class_count 字典分解为元祖列表,而后导入 operator.itemgetter 方法按照第二个元素的次序对元组进行从大到小的排序,以后返回发生频率最高的元素标签。
目前咱们已经成功构造了一个分类器,相信在接下来的旅途中,咱们构造使用分类算法将会更加容易。
# kNN.py from numpy import * import operator def create_data_set(): """ 初始化数据,其中group 数组的函数应该和标记向量 labels 的元素数目相同。 :return: 返回训练样本集和标记向量 """ group = array([[1.0, 1.1], [1.0, 1.0], [0, 0], [0, 0.1]]) # 建立数据集 labels = ['A', 'A', 'B', 'B'] # 建立标记 return group, labels def classify0(in_x, data_set, labels, k): """ 对上述 create_data_set 的数据使用 k-近邻算法分类。 :param in_x: 用于分类的向量 :param data_set: 训练样本集 :param labels: 标记向量 :param k: 选择最近的数据的数目 :return: """ data_set_size = data_set.shape[0] # 计算训练集的大小 # 4 # 距离计算 # tile(inX, (a, b)) tile函将 inX 重复 a 行,重复 b 列 # … - data_set 每一个对应的元素相减,至关于欧式距离开平房内的减法运算 diff_mat = tile(in_x, (data_set_size, 1)) - data_set ''' [[-1. -1.1] [-1. -1. ] [ 0. 0. ] [ 0. -0.1]] ''' # 对 diff_mat 内部的每一个元素平方 sq_diff_mat = diff_mat ** 2 ''' [[1. 1.21] [1. 1. ] [0. 0. ] [0. 0.01]] ''' # sum(axis=0) 每列元素相加,sum(axis=1) 每行元素相加 sq_distances = sq_diff_mat.sum(axis=1) # [2.21 2. 0. 0.01] # 每一个元素开平方求欧氏距离 distances = sq_distances ** 0.5 # [1.48660687 1.41421356 0. 0.1 ] # argsort函数返回的是数组值从小到大的索引值 sorted_dist_indicies = distances.argsort() # [2 3 1 0] # 选择距离最小的 k 个点 class_count = {} # type:dict for i in range(k): # 取出前 k 个对应的标签 vote_ilabel = labels[sorted_dist_indicies[i]] # 计算每一个类别的样本数 class_count[vote_ilabel] = class_count.get(vote_ilabel, 0) + 1 # operator.itemgetter(0) 按照键 key 排序,operator.itemgetter(1) 按照值 value 排序 # reverse 倒序取出频率最高的分类 sorted_class_count = sorted(class_count.items(), key=operator.itemgetter(1), reverse=True) # [('B', 2), ('A', 1)] # 取出频率最高的分类结果 return sorted_class_count[0][0] if __name__ == '__main__': group, labels = create_data_set() print(classify0([0, 0], group, labels, 3)) 7
上文咱们已经使用 k-近邻算法构造了一个分类器分类的概念是在已有数据的基础上学会一个分类函数或构造出一个分类模型,即分类器。上一章节我已经讲到,机器学习并非真正的预言家,k-近邻算法也是机器学习算法中的一种,所以它的答案并不老是正确的,正如上章节所讲,他会受到多种因素的影响,如训练集的个数、训练数据的特征等。上述的 k-近邻算法因为训练集的个数以及训练数据的特征远远不够的,所以他并无太大的实际用处,下面将带你们深刻 k-近邻算法。
工做一段时间后的你寂寞难耐,因此你准备去相亲网站找男/女友。在某个在线约会网站上,通过几个月总结,你发现你曾交往过三种类型的人:
1. 不喜欢的人 2. 魅力通常的人 3. 极具魅力的人
虽然你本身总结出了这三类人,可是约会网站没法对你接触的人经过这三种分类帮你作出确切的判断。也许你周一至周五想约会那些魅力通常的人,而周末想约会那些极具魅力的人,因此作出确切的判断颇有必要。所以你收集了一些约会网站不曾记录的数据信息,想本身作个分类软件给相亲网站的产品经理,让他帮你把你的分类软件部署到他们网站上。下面就让咱们来动手实现…
1. 收集数据:提供文本文件 2. 准备数据:使用 Python 解析文本文件 3. 分析数据:使用 Matplotlib 画二维散点图 4. 训练算法:此步骤不适用于 k-近邻算法 5. 测试算法:使用你提供的部分数据做为测试样本。 6. 使用算法:对心得约会对象进行预测。 测试样本:测试样本是已经完成分类的数据,既有标记,而非测试样本没有标记,所以使用你的测试算法去判断你的测试数据,若是预测类别与实际类别不一样,则标记为一个错误。
其实应该叫作采集数据更专业,不然你也能够私底下称为爬虫?
你能够从个人 git 上第二章下载 datingTestSet.txt的文件,该文件每一个样本数据占据一行,总共有1000行。每一个样本包含三个特征:
1. 每一年的飞行里程数 2. 玩视频游戏所耗时间百分比 3. 每周消费的冰淇淋公升数。
在把上述特征输入到分类器以前,咱们须要新建file2matrix函数先处理输入格式问题。
# kNN.py def file2matrix(filename): with open(filename, 'r', encoding='utf-8') as fr: # 获取文件的行数 array_0_lines = fr.readlines() # type:list number_of_lines = len(array_0_lines) # 建立以零填充的的 NumPy 矩阵,并将矩阵的另外一维度设置为固定值3 return_mat = zeros((number_of_lines, 3)) # 建立一个1000行3列的0零矩阵 # 解析文件数据到列表 class_label_vector = [] # 把结果存储成列向量 index = 0 # 书本内容(错误) # for line in fr.readlines(): # line = line.strip() # list_from_line = line.split("\t") # return_mat[index, :] = list_from_line[0:3] # class_label_vector.append(int(list_from_line[-1])) # index += 1 # 本身编写 for line in array_0_lines: line = line.strip() list_from_line = line.split("\t") # return_mat 存储每一行数据的特征值 return_mat[index, :] = list_from_line[0:3] # 经过数据的标记作分类 if list_from_line[-1] == "didntLike": class_label_vector.append(int(1)) elif list_from_line[-1] == "smallDoses": class_label_vector.append(int(2)) elif list_from_line[-1] == "largeDoses": class_label_vector.append(int(3)) index += 1 return return_mat, class_label_vector
话很少说,直接上代码
# kNN.py from numpy import * import operator def create_data_set(): """ 初始化数据,其中group 数组的函数应该和标记向量 labels 的元素数目相同。 :return: 返回训练样本集和标记向量 """ group = array([[1.0, 1.1], [1.0, 1.0], [0, 0], [0, 0.1]]) # 建立数据集 labels = ['A', 'A', 'B', 'B'] # 建立标记 return group, labels def classify0(in_x, data_set, labels, k): """ 对上述 create_data_set 的数据使用 k-近邻算法分类。 :param in_x: 用于分类的向量 :param data_set: 训练样本集 :param labels: 标记向量 :param k: 选择最近的数据的数目 :return: """ data_set_size = data_set.shape[0] # 计算训练集的大小 # 4 # 距离计算 # tile(inX, (a, b)) tile函将 inX 重复 a 行,重复 b 列 # … - data_set 每一个对应的元素相减,至关于欧式距离开平房内的减法运算 diff_mat = tile(in_x, (data_set_size, 1)) - data_set ''' [[-1. -1.1] [-1. -1. ] [ 0. 0. ] [ 0. -0.1]] ''' # 对 diff_mat 内部的每一个元素平方 sq_diff_mat = diff_mat ** 2 ''' [[1. 1.21] [1. 1. ] [0. 0. ] [0. 0.01]] ''' # sum(axis=0) 每列元素相加,sum(axis=1) 每行元素相加 sq_distances = sq_diff_mat.sum(axis=1) # [2.21 2. 0. 0.01] # 每一个元素开平方求欧氏距离 distances = sq_distances ** 0.5 # [1.48660687 1.41421356 0. 0.1 ] # argsort函数返回的是数组值从小到大的索引值 sorted_dist_indicies = distances.argsort() # [2 3 1 0] # 选择距离最小的 k 个点 class_count = {} # type:dict for i in range(k): # 取出前 k 个对应的标签 vote_i_label = labels[sorted_dist_indicies[i]] # 计算每一个类别的样本数 class_count[vote_i_label] = class_count.get(vote_i_label, 0) + 1 # operator.itemgetter(0) 按照键 key 排序,operator.itemgetter(1) 按照值 value 排序 # reverse 倒序取出频率最高的分类 sorted_class_count = sorted(class_count.items(), key=operator.itemgetter(1), reverse=True) # [('B', 2), ('A', 1)] # 取出频率最高的分类结果 classify_result = sorted_class_count[0][0] return classify_result def file2matrix(filename): with open(filename, 'r', encoding='utf-8') as fr: # 获取文件的行数 array_0_lines = fr.readlines() # type:list number_of_lines = len(array_0_lines) # 建立以零填充的的 NumPy 矩阵,并将矩阵的另外一维度设置为固定值3 return_mat = zeros((number_of_lines, 3)) # 建立一个1000行3列的0零矩阵 # 解析文件数据到列表 class_label_vector = [] # 把结果存储成列向量 index = 0 # 书本内容(报错) # for line in fr.readlines(): # line = line.strip() # list_from_line = line.split("\t") # return_mat[index, :] = list_from_line[0:3] # class_label_vector.append(int(list_from_line[-1])) # index += 1 # 本身编写 for line in array_0_lines: line = line.strip() list_from_line = line.split("\t") # return_mat 存储每一行数据的特征值 return_mat[index, :] = list_from_line[0:3] # 经过数据的标记作分类 if list_from_line[-1] == "didntLike": class_label_vector.append(int(1)) elif list_from_line[-1] == "smallDoses": class_label_vector.append(int(2)) elif list_from_line[-1] == "largeDoses": class_label_vector.append(int(3)) index += 1 return return_mat, class_label_vector def scatter_diagram(dating_data_mat, dating_labels, diagram_type=1): import matplotlib.pyplot as plt from matplotlib.font_manager import FontProperties # windows下配置 font 为中文字体 # font = FontProperties(fname=r"c:\windows\fonts\simsun.ttc", size=14) # mac下配置 font 为中文字体 font = FontProperties(fname='/System/Library/Fonts/STHeiti Medium.ttc') # 经过 dating_labels 的索引获取不一样分类在矩阵内的行数 index = 0 index_1 = [] index_2 = [] index_3 = [] for i in dating_labels: if i == 1: index_1.append(index) elif i == 2: index_2.append(index) elif i == 3: index_3.append(index) index += 1 # 对不一样分类在矩阵内不一样的行数构造每一个分类的矩阵 type_1 = dating_data_mat[index_1, :] type_2 = dating_data_mat[index_2, :] type_3 = dating_data_mat[index_3, :] fig = plt.figure() ax = fig.add_subplot(111) # 就是1行一列一张画布一张图, if diagram_type == 1: # 经过对特征0、1比较的散点图 type_1 = ax.scatter(type_1[:, 0], type_1[:, 1], c='red') type_2 = ax.scatter(type_2[:, 0], type_2[:, 1], c='blue') type_3 = ax.scatter(type_3[:, 0], type_3[:, 1], c='green') plt.xlabel('每一年的飞行里程数', fontproperties=font) plt.ylabel('玩视频游戏所耗时间百分比', fontproperties=font) elif diagram_type == 2: # 经过对特征一、2比较的散点图 type_1 = ax.scatter(type_1[:, 1], type_1[:, 2], c='red') type_2 = ax.scatter(type_2[:, 1], type_2[:, 2], c='blue') type_3 = ax.scatter(type_3[:, 1], type_3[:, 2], c='green') plt.xlabel('玩视频游戏所耗时间百分比', fontproperties=font) plt.ylabel('每周所消费的冰淇淋公升数', fontproperties=font) elif diagram_type == 3: # 经过对特征0、2比较的散点图 type_1 = ax.scatter(type_1[:, 0], type_1[:, 2], c='red') type_2 = ax.scatter(type_2[:, 0], type_2[:, 2], c='blue') type_3 = ax.scatter(type_3[:, 0], type_3[:, 2], c='green') plt.xlabel('每一年的飞行里程数', fontproperties=font) plt.ylabel('每周所消费的冰淇淋公升数', fontproperties=font) plt.legend((type_1, type_2, type_3), ('不喜欢的人', '魅力通常的人', '极具魅力的人'), loc=4, prop=font) plt.show() if __name__ == '__main__': group, labels = create_data_set() classify0([0, 0], group, labels, 3) import os filename = os.path.join(os.path.dirname(os.path.dirname(__file__)), 'dating_test_set.txt') dating_data_mat, dating_labels = file2matrix(filename) # 须要画图演示开启 ''' diagram_type = 1, 比较特征(0, 1); diagram_type = 2, 比较特征(1, 2); diagram_type = 3, 比较特征(0, 2) ''' # scatter_diagram(dating_data_mat, dating_labels, diagram_type=1) norm_mat, ranges, min_vals = auto_norm(dating_data_mat)
图2-3 玩视频游戏和每一年飞行里程数特征比较
表2-3 四条约会网站原始数据
玩视频游戏所耗时间百分比 | 每一年飞行里程数 | 每周消费的冰淇淋公升数 | 样本分类 | |
---|---|---|---|---|
1 | 0.8 | 400 | 0.5 | 1 |
2 | 12 | 134000 | 0.9 | 3 |
3 | 0 | 20000 | 1.1 | 2 |
4 | 67 | 32000 | 0.1 | 2 |
若是咱们要计算表2-3中样本三和样本4的距离,可使用下面的方法:
\(\sqrt{(0-67)^2+(20000-32000)^2+(1.1-0.1)^2}\)
可是上面方程汇总差值最大的属性对计算结果的影响很大,而且是远远大于其余两个特征的差值。可是你可能会认为以上三种特征是同等重要的,所以做为三个等权重的特征之一,第二个特征不该该严重地影响到计算结果。
为了处理这种不一样取值范围的特征值时,咱们一般采用归一化数值法,将特征值的取值范围处理为\(0\)到\(1\)或者\(-1\)到\(1\)之间。咱们可使用下面的公式把特征值的取值范围转化为\(0\)到\(1\)区间内的值:
\(new_value=(old_value-min)/(max-min)\)其中\(min\) 和 \({max}\) 分别是数据集汇总的最小特征值和最大特征值。
所以咱们须要在 kNN.py 文件中增长一个新函数auto_norm(),该函数能够自动将数字特征值转化为\(0\)到\(1\)的区间。
# kNN.py def auto_norm(data_set): # min(0)使得函数从列中选取最小值,min(1)使得函数从行中选取最小值 min_vals = data_set.min(0) max_vals = data_set.max(0) ranges = max_vals - min_vals # 获取 data_set 的总行数 m = data_set.shape[0] # 特征值相除 # 至关于公式里的old_value-min # tile函数至关于将 min_vals 重复 m 行,重复1列 norm_data_set = data_set - tile(min_vals, (m, 1)) # 至关于公式里的(old_value-min)/(max-min) norm_data_set = norm_data_set / tile(ranges, (m, 1)) return norm_data_set, ranges, min_vals
上节咱们已经将数据按照需求作了归一化数值处理,本节咱们将测试分类器的效果。以前讲到过机器学习算法一般将已有数据的\(80\%\)做为训练样本,其他的\(20\%\)做为测试数据去测试分类测试数据应该是随机选择的,检测分类器的正确率。
所以咱们须要在 kNN.py 文件中建立函数dating_class_test()
# kNN.py from numpy import * import operator def create_data_set(): """ 初始化数据,其中group 数组的函数应该和标记向量 labels 的元素数目相同。 :return: 返回训练样本集和标记向量 """ group = array([[1.0, 1.1], [1.0, 1.0], [0, 0], [0, 0.1]]) # 建立数据集 labels = ['A', 'A', 'B', 'B'] # 建立标记 return group, labels def classify0(in_x, data_set, labels, k): """ 对上述 create_data_set 的数据使用 k-近邻算法分类。 :param in_x: 用于分类的向量 :param data_set: 训练样本集 :param labels: 标记向量 :param k: 选择最近的数据的数目 :return: """ data_set_size = data_set.shape[0] # 计算训练集的大小 # 4 # 距离计算 # tile(inX, (a, b)) tile函将 inX 重复 a 行,重复 b 列 # … - data_set 每一个对应的元素相减,至关于欧式距离开平房内的减法运算 diff_mat = tile(in_x, (data_set_size, 1)) - data_set ''' [[-1. -1.1] [-1. -1. ] [ 0. 0. ] [ 0. -0.1]] ''' # 对 diff_mat 内部的每一个元素平方 sq_diff_mat = diff_mat ** 2 ''' [[1. 1.21] [1. 1. ] [0. 0. ] [0. 0.01]] ''' # sum(axis=0) 每列元素相加,sum(axis=1) 每行元素相加 sq_distances = sq_diff_mat.sum(axis=1) # [2.21 2. 0. 0.01] # 每一个元素开平方求欧氏距离 distances = sq_distances ** 0.5 # [1.48660687 1.41421356 0. 0.1 ] # argsort函数返回的是数组值从小到大的索引值 sorted_dist_indicies = distances.argsort() # [2 3 1 0] # 选择距离最小的 k 个点 class_count = {} # type:dict for i in range(k): # 取出前 k 个对应的标签 vote_i_label = labels[sorted_dist_indicies[i]] # 计算每一个类别的样本数 class_count[vote_i_label] = class_count.get(vote_i_label, 0) + 1 # operator.itemgetter(0) 按照键 key 排序,operator.itemgetter(1) 按照值 value 排序 # reverse 倒序取出频率最高的分类 sorted_class_count = sorted(class_count.items(), key=operator.itemgetter(1), reverse=True) # [('B', 2), ('A', 1)] # 取出频率最高的分类结果 classify_result = sorted_class_count[0][0] return classify_result def file2matrix(filename): with open(filename, 'r', encoding='utf-8') as fr: # 获取文件的行数 array_0_lines = fr.readlines() # type:list number_of_lines = len(array_0_lines) # 建立以零填充的的 NumPy 矩阵,并将矩阵的另外一维度设置为固定值3 return_mat = zeros((number_of_lines, 3)) # 建立一个1000行3列的0零矩阵 # 解析文件数据到列表 class_label_vector = [] # 把结果存储成列向量 index = 0 # 书本内容(报错) # for line in fr.readlines(): # line = line.strip() # list_from_line = line.split("\t") # return_mat[index, :] = list_from_line[0:3] # class_label_vector.append(int(list_from_line[-1])) # index += 1 # 本身编写 for line in array_0_lines: line = line.strip() list_from_line = line.split("\t") # return_mat 存储每一行数据的特征值 return_mat[index, :] = list_from_line[0:3] # 经过数据的标记作分类 if list_from_line[-1] == "didntLike": class_label_vector.append(int(1)) elif list_from_line[-1] == "smallDoses": class_label_vector.append(int(2)) elif list_from_line[-1] == "largeDoses": class_label_vector.append(int(3)) index += 1 return return_mat, class_label_vector def scatter_diagram(dating_data_mat, dating_labels, diagram_type=1): import matplotlib.pyplot as plt from matplotlib.font_manager import FontProperties # windows下配置 font 为中文字体 # font = FontProperties(fname=r"c:\windows\fonts\simsun.ttc", size=14) # mac下配置 font 为中文字体 font = FontProperties(fname='/System/Library/Fonts/STHeiti Medium.ttc') # 经过 dating_labels 的索引获取不一样分类在矩阵内的行数 index = 0 index_1 = [] index_2 = [] index_3 = [] for i in dating_labels: if i == 1: index_1.append(index) elif i == 2: index_2.append(index) elif i == 3: index_3.append(index) index += 1 # 对不一样分类在矩阵内不一样的行数构造每一个分类的矩阵 type_1 = dating_data_mat[index_1, :] type_2 = dating_data_mat[index_2, :] type_3 = dating_data_mat[index_3, :] fig = plt.figure() ax = fig.add_subplot(111) # 就是1行一列一张画布一张图, if diagram_type == 1: # 经过对特征0、1比较的散点图 type_1 = ax.scatter(type_1[:, 0], type_1[:, 1], c='red') type_2 = ax.scatter(type_2[:, 0], type_2[:, 1], c='blue') type_3 = ax.scatter(type_3[:, 0], type_3[:, 1], c='green') plt.xlabel('每一年的飞行里程数', fontproperties=font) plt.ylabel('玩视频游戏所耗时间百分比', fontproperties=font) elif diagram_type == 2: # 经过对特征一、2比较的散点图 type_1 = ax.scatter(type_1[:, 1], type_1[:, 2], c='red') type_2 = ax.scatter(type_2[:, 1], type_2[:, 2], c='blue') type_3 = ax.scatter(type_3[:, 1], type_3[:, 2], c='green') plt.xlabel('玩视频游戏所耗时间百分比', fontproperties=font) plt.ylabel('每周所消费的冰淇淋公升数', fontproperties=font) elif diagram_type == 3: # 经过对特征0、2比较的散点图 type_1 = ax.scatter(type_1[:, 0], type_1[:, 2], c='red') type_2 = ax.scatter(type_2[:, 0], type_2[:, 2], c='blue') type_3 = ax.scatter(type_3[:, 0], type_3[:, 2], c='green') plt.xlabel('每一年的飞行里程数', fontproperties=font) plt.ylabel('每周所消费的冰淇淋公升数', fontproperties=font) plt.legend((type_1, type_2, type_3), ('不喜欢的人', '魅力通常的人', '极具魅力的人'), loc=4, prop=font) plt.show() def auto_norm(data_set): # min(0)使得函数从列中选取最小值,min(1)使得函数从行中选取最小值 min_vals = data_set.min(0) max_vals = data_set.max(0) ranges = max_vals - min_vals # 获取 data_set 的总行数 m = data_set.shape[0] # 特征值相除 # 至关于公式里的old_value-min # tile函数至关于将 min_vals 重复 m 行,重复1列 norm_data_set = data_set - tile(min_vals, (m, 1)) # 至关于公式里的(old_value-min)/(max-min) norm_data_set = norm_data_set / tile(ranges, (m, 1)) return norm_data_set, ranges, min_vals def dating_class_test(): import os # 测试样本比率 ho_ratio = 0.20 # 读取文本数据 filename = os.path.join(os.path.dirname(os.path.dirname(__file__)), 'dating_test_set.txt') dating_data_mat, dating_labels = file2matrix(filename) # 对数据归一化特征值处理 norm_mat, ranges, min_vals = auto_norm(dating_data_mat) m = norm_mat.shape[0] num_test_vecs = int(m * ho_ratio) error_count = 0 for i in range(num_test_vecs): # 由于你的数据原本就是随机的,因此直接选择前20%的数据做为测试数据 classifier_result = classify0(norm_mat[i, :], norm_mat[num_test_vecs:m, :], dating_labels[num_test_vecs:m], 3) if classifier_result != dating_labels[i]: error_count += 1 print("the total error rate is: {}".format(error_count / float(num_test_vecs))) # the total error rate is: 0.08 def main(): import os group, labels = create_data_set() classify0([0, 0], group, labels, 3) filename = os.path.join(os.path.dirname(os.path.dirname(__file__)), 'dating_test_set.txt') dating_data_mat, dating_labels = file2matrix(filename) # 须要画图演示开启 ''' diagram_type = 1, 比较特征(0, 1); diagram_type = 2, 比较特征(1, 2); diagram_type = 3, 比较特征(0, 2) ''' # scatter_diagram(dating_data_mat, dating_labels, diagram_type=1) auto_norm(dating_data_mat) if __name__ == '__main__': main() dating_class_test()
运行整个算法,最后得出分类器处理约会数据集的错误率是\(8\%\),这是一个至关不错的结果。咱们也能够改变测试集的比率即 ho_ratio 的值来检测错误率的变化。
刚刚已经讲到咱们的算法错误率只有\(8\%\),这是一个很不错的算法了。如今咱们手动实现一个小程序让咱们找到某我的并输入他/她的信息,让小程序给出咱们对对方喜欢程度的预测值。
# kNN.py from numpy import * import operator def create_data_set(): """ 初始化数据,其中group 数组的函数应该和标记向量 labels 的元素数目相同。 :return: 返回训练样本集和标记向量 """ group = array([[1.0, 1.1], [1.0, 1.0], [0, 0], [0, 0.1]]) # 建立数据集 labels = ['A', 'A', 'B', 'B'] # 建立标记 return group, labels def classify0(in_x, data_set, labels, k): """ 对上述 create_data_set 的数据使用 k-近邻算法分类。 :param in_x: 用于分类的向量 :param data_set: 训练样本集 :param labels: 标记向量 :param k: 选择最近的数据的数目 :return: """ data_set_size = data_set.shape[0] # 计算训练集的大小 # 4 # 距离计算 # tile(inX, (a, b)) tile函将 inX 重复 a 行,重复 b 列 # … - data_set 每一个对应的元素相减,至关于欧式距离开平房内的减法运算 diff_mat = tile(in_x, (data_set_size, 1)) - data_set ''' [[-1. -1.1] [-1. -1. ] [ 0. 0. ] [ 0. -0.1]] ''' # 对 diff_mat 内部的每一个元素平方 sq_diff_mat = diff_mat ** 2 ''' [[1. 1.21] [1. 1. ] [0. 0. ] [0. 0.01]] ''' # sum(axis=0) 每列元素相加,sum(axis=1) 每行元素相加 sq_distances = sq_diff_mat.sum(axis=1) # [2.21 2. 0. 0.01] # 每一个元素开平方求欧氏距离 distances = sq_distances ** 0.5 # [1.48660687 1.41421356 0. 0.1 ] # argsort函数返回的是数组值从小到大的索引值 sorted_dist_indicies = distances.argsort() # [2 3 1 0] # 选择距离最小的 k 个点 class_count = {} # type:dict for i in range(k): # 取出前 k 个对应的标签 vote_i_label = labels[sorted_dist_indicies[i]] # 计算每一个类别的样本数 class_count[vote_i_label] = class_count.get(vote_i_label, 0) + 1 # operator.itemgetter(0) 按照键 key 排序,operator.itemgetter(1) 按照值 value 排序 # reverse 倒序取出频率最高的分类 sorted_class_count = sorted(class_count.items(), key=operator.itemgetter(1), reverse=True) # [('B', 2), ('A', 1)] # 取出频率最高的分类结果 classify_result = sorted_class_count[0][0] return classify_result def file2matrix(filename): with open(filename, 'r', encoding='utf-8') as fr: # 获取文件的行数 array_0_lines = fr.readlines() # type:list number_of_lines = len(array_0_lines) # 建立以零填充的的 NumPy 矩阵,并将矩阵的另外一维度设置为固定值3 return_mat = zeros((number_of_lines, 3)) # 建立一个1000行3列的0零矩阵 # 解析文件数据到列表 class_label_vector = [] # 把结果存储成列向量 index = 0 # 书本内容(报错) # for line in fr.readlines(): # line = line.strip() # list_from_line = line.split("\t") # return_mat[index, :] = list_from_line[0:3] # class_label_vector.append(int(list_from_line[-1])) # index += 1 # 本身编写 for line in array_0_lines: line = line.strip() list_from_line = line.split("\t") # return_mat 存储每一行数据的特征值 return_mat[index, :] = list_from_line[0:3] # 经过数据的标记作分类 if list_from_line[-1] == "didntLike": class_label_vector.append(int(1)) elif list_from_line[-1] == "smallDoses": class_label_vector.append(int(2)) elif list_from_line[-1] == "largeDoses": class_label_vector.append(int(3)) index += 1 return return_mat, class_label_vector def scatter_diagram(dating_data_mat, dating_labels, diagram_type=1): import matplotlib.pyplot as plt from matplotlib.font_manager import FontProperties # windows下配置 font 为中文字体 # font = FontProperties(fname=r"c:\windows\fonts\simsun.ttc", size=14) # mac下配置 font 为中文字体 font = FontProperties(fname='/System/Library/Fonts/STHeiti Medium.ttc') # 经过 dating_labels 的索引获取不一样分类在矩阵内的行数 index = 0 index_1 = [] index_2 = [] index_3 = [] for i in dating_labels: if i == 1: index_1.append(index) elif i == 2: index_2.append(index) elif i == 3: index_3.append(index) index += 1 # 对不一样分类在矩阵内不一样的行数构造每一个分类的矩阵 type_1 = dating_data_mat[index_1, :] type_2 = dating_data_mat[index_2, :] type_3 = dating_data_mat[index_3, :] fig = plt.figure() ax = fig.add_subplot(111) # 就是1行一列一张画布一张图, if diagram_type == 1: # 经过对特征0、1比较的散点图 type_1 = ax.scatter(type_1[:, 0], type_1[:, 1], c='red') type_2 = ax.scatter(type_2[:, 0], type_2[:, 1], c='blue') type_3 = ax.scatter(type_3[:, 0], type_3[:, 1], c='green') plt.xlabel('每一年的飞行里程数', fontproperties=font) plt.ylabel('玩视频游戏所耗时间百分比', fontproperties=font) elif diagram_type == 2: # 经过对特征一、2比较的散点图 type_1 = ax.scatter(type_1[:, 1], type_1[:, 2], c='red') type_2 = ax.scatter(type_2[:, 1], type_2[:, 2], c='blue') type_3 = ax.scatter(type_3[:, 1], type_3[:, 2], c='green') plt.xlabel('玩视频游戏所耗时间百分比', fontproperties=font) plt.ylabel('每周所消费的冰淇淋公升数', fontproperties=font) elif diagram_type == 3: # 经过对特征0、2比较的散点图 type_1 = ax.scatter(type_1[:, 0], type_1[:, 2], c='red') type_2 = ax.scatter(type_2[:, 0], type_2[:, 2], c='blue') type_3 = ax.scatter(type_3[:, 0], type_3[:, 2], c='green') plt.xlabel('每一年的飞行里程数', fontproperties=font) plt.ylabel('每周所消费的冰淇淋公升数', fontproperties=font) plt.legend((type_1, type_2, type_3), ('不喜欢的人', '魅力通常的人', '极具魅力的人'), loc=4, prop=font) plt.show() def auto_norm(data_set): # min(0)使得函数从列中选取最小值,min(1)使得函数从行中选取最小值 min_vals = data_set.min(0) max_vals = data_set.max(0) ranges = max_vals - min_vals # 获取 data_set 的总行数 m = data_set.shape[0] # 特征值相除 # 至关于公式里的old_value-min # tile函数至关于将 min_vals 重复 m 行,重复1列 norm_data_set = data_set - tile(min_vals, (m, 1)) # 至关于公式里的(old_value-min)/(max-min) norm_data_set = norm_data_set / tile(ranges, (m, 1)) return norm_data_set, ranges, min_vals def dating_class_test(): import os # 测试样本比率 ho_ratio = 0.20 # 读取文本数据 filename = os.path.join(os.path.dirname(os.path.dirname(__file__)), 'dating_test_set.txt') dating_data_mat, dating_labels = file2matrix(filename) # 对数据归一化特征值处理 norm_mat, ranges, min_vals = auto_norm(dating_data_mat) m = norm_mat.shape[0] num_test_vecs = int(m * ho_ratio) error_count = 0 for i in range(num_test_vecs): # 由于你的数据原本就是随机的,因此直接选择前20%的数据做为测试数据 classifier_result = classify0(norm_mat[i, :], norm_mat[num_test_vecs:m, :], dating_labels[num_test_vecs:m], 3) if classifier_result != dating_labels[i]: error_count += 1 # print("the total error rate is: {}".format(error_count / float(num_test_vecs))) # the total error rate is: 0.08 def main(): import os group, labels = create_data_set() classify0([0, 0], group, labels, 3) filename = os.path.join(os.path.dirname(os.path.dirname(__file__)), 'dating_test_set.txt') dating_data_mat, dating_labels = file2matrix(filename) # 须要画图演示开启 ''' diagram_type = 1, 比较特征(0, 1); diagram_type = 2, 比较特征(1, 2); diagram_type = 3, 比较特征(0, 2) ''' # scatter_diagram(dating_data_mat, dating_labels, diagram_type=1) auto_norm(dating_data_mat) def classify_person(): import os result_list = ['讨厌', '有点喜欢', '很是喜欢'] ff_miles = float(input("每一年的出行千米数(km)?例如:1000\n")) percent_tats = float(input("每一年玩游戏的时间占比(.%)?例如:10\n")) ice_cream = float(input("每一年消费多少零食(kg)?例如:1\n")) filename = os.path.join(os.path.dirname(os.path.dirname(__file__)), 'dating_test_set.txt') dating_data_mat, dating_labels = file2matrix(filename) norm_mat, ranges, min_vals = auto_norm(dating_data_mat) in_arr = array([ff_miles, percent_tats, ice_cream]) classifier_result = classify0((in_arr - min_vals) / ranges, norm_mat, dating_labels, 3) print("你可能对他/她的印象:\n{}".format(result_list[classifier_result - 1])) if __name__ == '__main__': main() dating_class_test() classify_person()
图2-4 约会-终
从图2-4中能够看出咱们经过输入特征值获得了小程序给咱们预测的结果,算是一个小小的结束。咱们也实现了咱们的第一个算法,我能够很自信的告诉你,你能够把这个小程序让约会网站的产品经理部署了。
聪明的同窗已经发现咱们这个约会小程序处理的数据都是较为容易让人理解的数据,那咱们如何对不容易让人理解的数据构造一个分类器呢?接下来咱们就要实现咱们的第二个算法——手写识别系统。
如今让咱们手动构造一个简单的手写识别系统,该系统只能识别数字\(0-9\)。
如下是咱们使用 k-近邻算法实现手写识别系统须要的步骤:
1. 收集数据:提供文本文件 2. 准备数据:编写函数 calssify(),将图像格式转换为分类器使用的 list 格式 3. 分析数据:检查数据确保它符合要求 4. 训练算法:此步骤不适用于 k-近邻算法 5. 测试算法:使用测试样本测试 6. 使用算法:构建一个完整的应用程序
在 digits 文件夹内有两个子目录:目录 traininigDigits 中大约有2000个例子,每一个例子的内容如图2-5所示,么个数字大约有200个样本;目录 testDigits 中包含了了大约900个测试数据,而且两组数据没有重叠。
图2-5 数字0的文本图
为了使用前面约会例子的分类器,咱们把图像格式处理为一个向量。图像在计算机上是由一个一个像素点组成的。咱们能够把本例中32*32的二进制图像矩阵转换为1*1024的向量。
下面我就来实现一个 img2vector 函数,将图像转换为向量。
# kNN.py def img2vector(filename): # 构造一个一行有1024个元素的矩阵 return_vect = zeros((1, 1024)) with open(filename, 'r', encoding='utf-8') as fr: # 读取文件的每一行的全部元素 for i in range(32): line_str = fr.readline() # 把文件每一行的全部元素按照顺序写入构造的1*1024的零矩阵 for j in range(32): return_vect[0, 32 * i + j] = int(line_str[j]) return return_vect
咱们已经能够把单个图像的文本文件格式转化为分类器能够识别的格式了,咱们接下来的工做就是要把咱们现有的数据输入到分类器,检查分类器的执行效果了。所以咱们来构造一个 hand_writing_class_test 方法来实现该功能。
# kNN.py def hand_writing_class_test(): import os # 获取训练集和测试集数据的根路径 training_digits_path = os.path.join(os.path.dirname(os.path.dirname(__file__)), 'digits/trainingDigits') test_digits_path = os.path.join(os.path.dirname(os.path.dirname(__file__)), 'digits/testDigits') # 对训练集数据作处理,构造一个 m*1024 的矩阵,m 是训练集数据的个数 hw_labels = [] training_file_list = os.listdir(training_digits_path) # type:list m = training_file_list.__len__() training_mat = zeros((m, 1024)) # 对训练集中的单个数据作处理 for i in range(m): # 取出文件中包含的数字 file_name_str = training_file_list[i] # type:str file_str = file_name_str.split('.')[0] class_num_str = int(file_str.split('_')[0]) # 添加标记 hw_labels.append(class_num_str) # 把该文件中的全部元素构形成 1*1024 的矩阵后存入以前构造的 m*1024 的矩阵中对应的行 training_mat[i, :] = img2vector(os.path.join(training_digits_path, file_name_str)) # 对测试集数据作处理,构造一个 m*1024 的矩阵,m 是测试集数据的个数 test_file_list = os.listdir(test_digits_path) error_count = 0 m_test = test_file_list.__len__() # 对测试集中的单个数据作处理 for i in range(m_test): # 取出文件中包含的数字 file_name_str = test_file_list[i] file_str = file_name_str.split('.')[0] class_num_str = int(file_str.split('_')[0]) # 把该文件中的全部元素构形成一个 1*1024 的矩阵 vector_under_test = img2vector(os.path.join(test_digits_path, file_name_str)) # 对刚刚构造的 1*1024 的矩阵进行分类处理判断结果 classifier_result = classify0(vector_under_test, training_mat, hw_labels, 3) # 对判断错误的计数加 1 if classifier_result != class_num_str: error_count += 1 print("错误率: {}".format(error_count / float(m_test))) # 错误率: 0.010570824524312896
k-近邻算法识别手写数字数据集,错误率为1%。如约会的例子,若是咱们改变 k 的值,修改训练样本或者测试样本的数据,都会对 k-近邻算法的准确率产生必定的影响,感兴趣的能够本身测试。
既然咱们刚刚实现的算法错误率仅有1%。那为何咱们不手动实现一个系统经过输入图片而后识别图片上的数字呢?那就让咱们开动吧!仅作参考,涉及知识点过多,不感兴趣的同窗能够跳过。为了实现该系统,首先咱们要手写一个img_binaryzation 方法对图片的大小修改为咱们须要的 32*32px,而后对图片进行二值化处理生成一个.txt文件,以后咱们把该 .txt文件传入咱们的 hand_writing_test 方法中获得结果。
# kNN.py from numpy import * import operator def create_data_set(): """ 初始化数据,其中group 数组的函数应该和标记向量 labels 的元素数目相同。 :return: 返回训练样本集和标记向量 """ group = array([[1.0, 1.1], [1.0, 1.0], [0, 0], [0, 0.1]]) # 建立数据集 labels = ['A', 'A', 'B', 'B'] # 建立标记 return group, labels def classify0(in_x, data_set, labels, k): """ 对上述 create_data_set 的数据使用 k-近邻算法分类。 :param in_x: 用于分类的向量 :param data_set: 训练样本集 :param labels: 标记向量 :param k: 选择最近的数据的数目 :return: """ data_set_size = data_set.shape[0] # 计算训练集的大小 # 4 # 距离计算 # tile(inX, (a, b)) tile函将 inX 重复 a 行,重复 b 列 # … - data_set 每一个对应的元素相减,至关于欧式距离开平房内的减法运算 diff_mat = tile(in_x, (data_set_size, 1)) - data_set ''' [[-1. -1.1] [-1. -1. ] [ 0. 0. ] [ 0. -0.1]] ''' # 对 diff_mat 内部的每一个元素平方 sq_diff_mat = diff_mat ** 2 ''' [[1. 1.21] [1. 1. ] [0. 0. ] [0. 0.01]] ''' # sum(axis=0) 每列元素相加,sum(axis=1) 每行元素相加 sq_distances = sq_diff_mat.sum(axis=1) # [2.21 2. 0. 0.01] # 每一个元素开平方求欧氏距离 distances = sq_distances ** 0.5 # [1.48660687 1.41421356 0. 0.1 ] # argsort函数返回的是数组值从小到大的索引值 sorted_dist_indicies = distances.argsort() # [2 3 1 0] # 选择距离最小的 k 个点 class_count = {} # type:dict for i in range(k): # 取出前 k 个对应的标签 vote_i_label = labels[sorted_dist_indicies[i]] # 计算每一个类别的样本数 class_count[vote_i_label] = class_count.get(vote_i_label, 0) + 1 # operator.itemgetter(0) 按照键 key 排序,operator.itemgetter(1) 按照值 value 排序 # reverse 倒序取出频率最高的分类 sorted_class_count = sorted(class_count.items(), key=operator.itemgetter(1), reverse=True) # [('B', 2), ('A', 1)] # 取出频率最高的分类结果 classify_result = sorted_class_count[0][0] return classify_result def file2matrix(filename): with open(filename, 'r', encoding='utf-8') as fr: # 获取文件的行数 array_0_lines = fr.readlines() # type:list number_of_lines = array_0_lines.__len__() # 建立以零填充的的 NumPy 矩阵,并将矩阵的另外一维度设置为固定值3 return_mat = zeros((number_of_lines, 3)) # 建立一个1000行3列的0零矩阵 # 解析文件数据到列表 class_label_vector = [] # 把结果存储成列向量 index = 0 # 书本内容(报错) # for line in fr.readlines(): # line = line.strip() # list_from_line = line.split("\t") # return_mat[index, :] = list_from_line[0:3] # class_label_vector.append(int(list_from_line[-1])) # index += 1 # 本身编写 for line in array_0_lines: line = line.strip() list_from_line = line.split("\t") # return_mat 存储每一行数据的特征值 return_mat[index, :] = list_from_line[0:3] # 经过数据的标记作分类 if list_from_line[-1] == "didntLike": class_label_vector.append(int(1)) elif list_from_line[-1] == "smallDoses": class_label_vector.append(int(2)) elif list_from_line[-1] == "largeDoses": class_label_vector.append(int(3)) index += 1 return return_mat, class_label_vector def scatter_diagram(dating_data_mat, dating_labels, diagram_type=1): import matplotlib.pyplot as plt from matplotlib.font_manager import FontProperties # windows下配置 font 为中文字体 # font = FontProperties(fname=r"c:\windows\fonts\simsun.ttc", size=14) # mac下配置 font 为中文字体 font = FontProperties(fname='/System/Library/Fonts/STHeiti Medium.ttc') # 经过 dating_labels 的索引获取不一样分类在矩阵内的行数 index = 0 index_1 = [] index_2 = [] index_3 = [] for i in dating_labels: if i == 1: index_1.append(index) elif i == 2: index_2.append(index) elif i == 3: index_3.append(index) index += 1 # 对不一样分类在矩阵内不一样的行数构造每一个分类的矩阵 type_1 = dating_data_mat[index_1, :] type_2 = dating_data_mat[index_2, :] type_3 = dating_data_mat[index_3, :] fig = plt.figure() ax = fig.add_subplot(111) # 就是1行一列一张画布一张图, if diagram_type == 1: # 经过对特征0、1比较的散点图 type_1 = ax.scatter(type_1[:, 0], type_1[:, 1], c='red') type_2 = ax.scatter(type_2[:, 0], type_2[:, 1], c='blue') type_3 = ax.scatter(type_3[:, 0], type_3[:, 1], c='green') plt.xlabel('每一年的飞行里程数', fontproperties=font) plt.ylabel('玩视频游戏所耗时间百分比', fontproperties=font) elif diagram_type == 2: # 经过对特征一、2比较的散点图 type_1 = ax.scatter(type_1[:, 1], type_1[:, 2], c='red') type_2 = ax.scatter(type_2[:, 1], type_2[:, 2], c='blue') type_3 = ax.scatter(type_3[:, 1], type_3[:, 2], c='green') plt.xlabel('玩视频游戏所耗时间百分比', fontproperties=font) plt.ylabel('每周所消费的冰淇淋公升数', fontproperties=font) elif diagram_type == 3: # 经过对特征0、2比较的散点图 type_1 = ax.scatter(type_1[:, 0], type_1[:, 2], c='red') type_2 = ax.scatter(type_2[:, 0], type_2[:, 2], c='blue') type_3 = ax.scatter(type_3[:, 0], type_3[:, 2], c='green') plt.xlabel('每一年的飞行里程数', fontproperties=font) plt.ylabel('每周所消费的冰淇淋公升数', fontproperties=font) plt.legend((type_1, type_2, type_3), ('不喜欢的人', '魅力通常的人', '极具魅力的人'), loc=4, prop=font) plt.show() def auto_norm(data_set): # min(0)使得函数从列中选取最小值,min(1)使得函数从行中选取最小值 min_vals = data_set.min(0) max_vals = data_set.max(0) ranges = max_vals - min_vals # 获取 data_set 的总行数 m = data_set.shape[0] # 特征值相除 # 至关于公式里的old_value-min # tile函数至关于将 min_vals 重复 m 行,重复1列 norm_data_set = data_set - tile(min_vals, (m, 1)) # 至关于公式里的(old_value-min)/(max-min) norm_data_set = norm_data_set / tile(ranges, (m, 1)) return norm_data_set, ranges, min_vals def dating_class_test(): import os # 测试样本比率 ho_ratio = 0.20 # 读取文本数据 filename = os.path.join(os.path.dirname(os.path.dirname(__file__)), 'dating_test_set.txt') dating_data_mat, dating_labels = file2matrix(filename) # 对数据归一化特征值处理 norm_mat, ranges, min_vals = auto_norm(dating_data_mat) m = norm_mat.shape[0] num_test_vecs = int(m * ho_ratio) error_count = 0 for i in range(num_test_vecs): # 由于你的数据原本就是随机的,因此直接选择前20%的数据做为测试数据 classifier_result = classify0(norm_mat[i, :], norm_mat[num_test_vecs:m, :], dating_labels[num_test_vecs:m], 3) if classifier_result != dating_labels[i]: error_count += 1 # print("the total error rate is: {}".format(error_count / float(num_test_vecs))) # the total error rate is: 0.08 def matplotlib_run(): import os group, labels = create_data_set() classify0([0, 0], group, labels, 3) filename = os.path.join(os.path.dirname(os.path.dirname(__file__)), 'dating_test_set.txt') dating_data_mat, dating_labels = file2matrix(filename) # 须要画图演示开启 ''' diagram_type = 1, 比较特征(0, 1); diagram_type = 2, 比较特征(1, 2); diagram_type = 3, 比较特征(0, 2) ''' scatter_diagram(dating_data_mat, dating_labels, diagram_type=2) auto_norm(dating_data_mat) def classify_person(): import os result_list = ['讨厌', '有点喜欢', '很是喜欢'] ff_miles = float(input("每一年的出行千米数(km)?例如:1000\n")) percent_tats = float(input("每日玩游戏的时间占比(.%)?例如:10\n")) ice_cream = float(input("每周消费多少零食(kg)?例如:1\n")) filename = os.path.join(os.path.dirname(os.path.dirname(__file__)), 'dating_test_set.txt') dating_data_mat, dating_labels = file2matrix(filename) norm_mat, ranges, min_vals = auto_norm(dating_data_mat) in_arr = array([ff_miles, percent_tats, ice_cream]) classifier_result = classify0((in_arr - min_vals) / ranges, norm_mat, dating_labels, 3) print("你可能对他/她的印象:\n{}".format(result_list[classifier_result - 1])) def img2vector(filename): # 构造一个一行有1024个元素的即 1*1024 的矩阵 return_vect = zeros((1, 1024)) with open(filename, 'r', encoding='utf-8') as fr: # 读取文件的每一行的全部元素 for i in range(32): line_str = fr.readline() # 把文件每一行的全部元素按照顺序写入构造的 1*1024 的零矩阵 for j in range(32): return_vect[0, 32 * i + j] = int(line_str[j]) return return_vect def hand_writing_class_test(): import os # 获取训练集和测试集数据的根路径 training_digits_path = os.path.join(os.path.dirname(os.path.dirname(__file__)), 'digits/trainingDigits') test_digits_path = os.path.join(os.path.dirname(os.path.dirname(__file__)), 'digits/testDigits') # 对训练集数据作处理,构造一个 m*1024 的矩阵,m 是训练集数据的个数 hw_labels = [] training_file_list = os.listdir(training_digits_path) # type:list m = training_file_list.__len__() training_mat = zeros((m, 1024)) # 对训练集中的单个数据作处理 for i in range(m): # 取出文件中包含的数字 file_name_str = training_file_list[i] # type:str file_str = file_name_str.split('.')[0] class_num_str = int(file_str.split('_')[0]) # 添加标记 hw_labels.append(class_num_str) # 把该文件中的全部元素构形成 1*1024 的矩阵后存入以前构造的 m*1024 的矩阵中对应的行 training_mat[i, :] = img2vector(os.path.join(training_digits_path, file_name_str)) # 对测试集数据作处理,构造一个 m*1024 的矩阵,m 是测试集数据的个数 test_file_list = os.listdir(test_digits_path) error_count = 0 m_test = test_file_list.__len__() # 对测试集中的单个数据作处理 for i in range(m_test): # 取出文件中包含的数字 file_name_str = test_file_list[i] file_str = file_name_str.split('.')[0] class_num_str = int(file_str.split('_')[0]) # 把该文件中的全部元素构形成一个 1*1024 的矩阵 vector_under_test = img2vector(os.path.join(test_digits_path, file_name_str)) # 对刚刚构造的 1*1024 的矩阵进行分类处理判断结果 classifier_result = classify0(vector_under_test, training_mat, hw_labels, 3) # 对判断错误的计数加 1 if classifier_result != class_num_str: error_count += 1 print("错误率: {}".format(error_count / float(m_test))) def hand_writing_run(): import os test_digits_0_13_filename = os.path.join(os.path.dirname(os.path.dirname(__file__)), 'digits/testDigits/0_13.txt') img2vector(test_digits_0_13_filename) hand_writing_class_test() def img_binaryzation(img_filename): import os import numpy as np from PIL import Image import pylab # 修改图片的路径 img_filename = os.path.join(os.path.dirname(os.path.dirname(__file__)), img_filename) # 调整图片的大小为 32*32px img = Image.open(img_filename) out = img.resize((32, 32), Image.ANTIALIAS) out.save(img_filename) # RGB 转为二值化图 img = Image.open(img_filename) lim = img.convert('1') lim.save(img_filename) img = Image.open(img_filename) # 将图像转化为数组并将像素转换到0-1之间 img_ndarray = np.asarray(img, dtype='float64') / 256 # 将图像的矩阵形式转化成一位数组保存到 data 中 data = np.ndarray.flatten(img_ndarray) # 将一维数组转化成矩阵 a_matrix = np.array(data).reshape(32, 32) # 将矩阵保存到 txt 文件中转化为二进制0,1存储 img_filename_list = img_filename.split('.') # type:list img_filename_list[-1] = 'jpg' txt_filename = '.'.join(img_filename_list) pylab.savetxt(txt_filename, a_matrix, fmt="%.0f", delimiter='') # 把 .txt 文件中的0和1调换 with open(txt_filename, 'r') as fr: data = fr.read() data = data.replace('1', '2') data = data.replace('0', '1') data = data.replace('2', '0') with open(txt_filename, 'w') as fw: fw.write(data) return txt_filename def hand_writing_test(img_filename): txt_filename = img_binaryzation(img_filename) import os # 获取训练集和测试集数据的根路径 training_digits_path = os.path.join(os.path.dirname(os.path.dirname(__file__)), 'digits/trainingDigits') # 对训练集数据作处理,构造一个 m*1024 的矩阵,m 是训练集数据的个数 hw_labels = [] training_file_list = os.listdir(training_digits_path) # type:list m = training_file_list.__len__() training_mat = zeros((m, 1024)) # 对训练集中的单个数据作处理 for i in range(m): # 取出文件中包含的数字 file_name_str = training_file_list[i] # type:str file_str = file_name_str.split('.')[0] class_num_str = int(file_str.split('_')[0]) # 添加标记 hw_labels.append(class_num_str) # 把该文件中的全部元素构形成 1*1024 的矩阵后存入以前构造的 m*1024 的矩阵中对应的行 training_mat[i, :] = img2vector(os.path.join(training_digits_path, file_name_str)) # 把该文件中的全部元素构形成一个 1*1024 的矩阵 vector_under_test = img2vector(txt_filename) # 对刚刚构造的 1*1024 的矩阵进行分类处理判断结果 classifier_result = classify0(vector_under_test, training_mat, hw_labels, 3) return classifier_result if __name__ == '__main__': # matplotlib_run() # dating_class_test() # classify_person() # hand_writing_run() classifier_result = hand_writing_test(img_filename='2.jpg') print(classifier_result)
好了,咱们已经实现了咱们的手写识别系统,恭喜你,完成了第一个算法的学习。
k-近邻算法是分类数据最简单最有效的算法,没有复杂的过程和数学公式,相信经过两个例子同窗们对 k-近邻算法有了较为深刻的了解。可是细心的同窗运行这两个算法的时候已经发现了运行该算法的是很是耗时间的。拿识别手写系统举例,由于该算法须要为每一个测试向量作2000次距离计算,每一个距离包括了1024个维度浮点运算,总计要执行900次,此外,咱们还须要为测试向量准备2MB的存储空间。既然有了问题,做为程序员的咱们是必定要去解决的,那么是否存在一种算法减小存储空间和计算时间的开销呢?下一章揭晓答案——决策树。