机器学习实战笔记(1)——k-近邻算法
1. 写在前面
近来感觉机器学习,深度学习神马的是越来越火了,从AlphaGo到Master,所谓的人工智能越来越NB,而我又是一个热爱新潮事物的人,于是也来凑个热闹学习学习。最近在看《Machine Learning IN ACTION》(作者:Peter Harrington)这本书,感觉非常不错。该书不是单纯的进行理论讲解,而是结合了许多小例子深度浅出地进行实战介绍。本博文作为学习笔记,用来记录书中重点内容和稍微地进行知识点的补充,也希望给看到的人带来一些帮助。
目前只看到了第2章,由于是刚开始,因此难度不大。在数学知识上,该章节主要涉及到了初级的线性代数(矩阵的运算)和概率论知识。书中的代码都是用Python 2.7写的,主要用的是NumPy库。本章介绍的是k-Nearest Neighbors(kNN,k-近邻)算法,通过以下3个例子进行说明。
2. kNN简介
对于一个能够采用kNN算法求解的问题的前提通常是:首先需要有一些关于该问题的样本数据集,称为训练集合;然后每条数据都有对应的标签,即我们知道每条数据所属的分类。该类问题的提法通常是:当给定一条未知标签的数据时,我们能够根据样本数据分析出该数据的标签。kNN算法的做法是:将未知标签的数据和样本数据集中的数据相比较,把和未知标签数据最“相近”的一些数据的标签作为未知标签数据的标签。由于“相近”的一些数据的标签可能各自都不相同,因此通常选取k个最为“相近”的标签,统计各标签出现的次数,选取频数最高者。这里“相近”的定义还需要给出。
下面结合3个实战例子进行讲解。
3. 爱情片还是动作片
第一个例子是如何判断一部我们还没有看过的电影是爱情片还是动作片。直觉告诉我们,爱情片的亲吻镜头较多,而动作片的打斗镜头较多。为此我们统计了几部爱情片和动作片的亲吻和打斗的镜头数,同时也对待判断的电影(记为电影X)做了统计,我们希望借助于kNN算法判别出电影X是爱情片还是动作片。
如下是统计结果:
电影名称 | 打斗镜头数 | 亲吻镜头数 | 电影类型 |
---|---|---|---|
California Man | 3 | 104 | 爱情 |
He’s Not Really into Dudes | 2 | 100 | 爱情 |
Beautiful Woman | 1 | 81 | 爱情 |
Kevin Longblade | 101 | 10 | 动作 |
Robo Slayer 3000 | 99 | 5 | 动作 |
Amped II | 98 | 2 | 动作 |
X | 18 | 90 | ? |
根据kNN算法,我们需要计算出电影X与其他电影的“距离”,以此作为“相近”的标准。最简单的是采用欧式距离,即我们通常采用的计算平面直角坐标系中两点距离的方法。为此,我们将每部电影的打斗镜头数和亲吻镜头数组成一个平面向量:(打斗镜头数, 亲吻镜头数),对应坐标系中的一点,如下图:
距离计算公式是
\]
可以求得各部电影与电影X的距离如下表:
电影名称 | 与电影X的距离 |
---|---|
California Man | 20.5 |
He’s Not Really into Dudes | 18.7 |
Beautiful Woman | 19.2 |
Kevin Longblade | 115.3 |
Robo Slayer 3000 | 117.4 |
Amped II | 118.9 |
根据kNN算法,假定k=3,即选择3个“最近”的电影,He’s Not Really into Dudes、Beautiful Woman 、California Man,统计其中电影类型出现的频数,爱情片:3;动作片:0。因此我们有理由相信电影X是爱情片。
下面用Python来实现以上的计算过程:
from numpy import *
import operator
def classify(input, train_set, labels, k):
data_size = train_set.shape[0]
diff_mat = tile(input, (data_size, 1)) - train_set
square_diff_mat = diff_mat ** 2
square_distances = square_diff_mat.sum(axis=1)
distances = square_distances ** 0.5
sorted_distances = distances.argsort()
class_count = {}
for i in range(k):
label = labels[sorted_distances[i]]
class_count[label] = class_count.get(label, 0) + 1
sorted_class_count = sorted(class_count.iteritems(), key=operator.itemgetter(1), reverse=True)
return sorted_class_count[0][0]
代码中首先导入了NumPy库,因此需要提前安装(Windows安装可能会出现问题):
pip install numpy
代码不难,主要是调用了Numpy的一些api。其中array.shape属性是二维元组,正如shape的意思“形状”一样,第一维是行数,第二维是列数。tile函数是将一个数组按行或按列进行复制。sum(axis=1)函数是将数组每行的数据的所有列相加这样得到一个新数组。argsort函数是对数组进行排序,但是得到的排序数组是由数组元素的下标组成的。
下面进行测试:
def test():
data_set = array([[3, 104], [2, 100], [1, 81], [101, 10], [99, 5], [98, 2]])
labels = ['Romance', 'Romance', 'Romance', 'Action', 'Action', 'Action']
print classify([18, 90], data_set, labels, 3)
最终打印Romance,符合我们的预期。
4. 约不约
小明是一个善于交际的人,Ta每年都会结识大量的朋友。Ta将结识的朋友按魅力程度分为了三类: 令人厌恶的人、普通的人、极具魅力的人。根据Ta多年的交际经验,Ta发现一个人的魅力程度可能与那个人的以下3个特征有关:
- 每年获得的飞行常客的里程数
- 玩游戏所消耗的时间百分比
- 每周消费的冰淇淋公升数
于是Ta统计了自己的1000个朋友的上述3个特征的值,按如下格式记录在datingTestSet.txt文件中:
40920 8.326976 0.953952 3
14488 7.153469 1.673904 2
26052 1.441871 0.805124 1
...
数据集中的一行代表一个人的相关数据,前3列是以上所说的3种特征的值,第4列代表魅力程度,值越大表示越具有魅力。
小明希望通过以上的样本数据和一个陌生人的以上3个特征的值来估计这个人的魅力程度,以便让Ta交到更具魅力的朋友。我们可以借助kNN算法来帮助小明结交更具魅力的朋友。
首先我们需要从文件中读入数据:
def read(filename):
fr = open(filename)
line = len(fr.readlines())
data_set = zeros((line, 3))
labels = []
fr = open(filename)
index = 0
for line in fr.readlines():
line_list = line.strip().split('\t')
data_set[index, :] = line_list[0:3]
labels.append(int(line_list[-1]))
index += 1;
return data_set, labels
和之前的例子一样,我们同样用欧式距离来定义“相近”概念,只是本例中多了一个维度。还值得注意的问题是,在上例中,我们直接采用数据的绝对量来计算欧氏距离,那是因为上例中各维度的数据的数量级相同,而在该例中,显然飞行常客里程数远大于另外两个变量的数据,直接计算导致的后果是另外两个变量对距离的影响微乎其微,因此必须统一各变量的数量级。统一的方式是采用如下公式,即转化为相对量:
\]
用format
函数来完成这件事:
def format(data_set):
min = data_set.min(0)
max = data_set.max(0)
step = max - min
new_data_set = zeros(shape(data_set))
line = data_set.shape[0]
new_data_set = data_set - tile(min, (line, 1))
new_data_set /= tile(step, (line, 1))
return new_data_set
接下来需要分类,即计算距离,这步我们直接复用第一个例子的classfy
函数。
最后可以进行测试了。一共统计了1000条数据,我们将90%的数据作为样本数据集,剩余10%的数据用来做测试。下面是测试的代码:
def test():
test_radio = 0.1
data_set, labels = read('datingTestSet.txt')
data_set = format(data_set)
data_count = data_set.shape[0]
test_count = int(test_radio * data_count)
train_set = data_set[test_count:, :]
label_set = labels[test_count:]
error_count = 0
for i in range(test_count):
test_list = data_set[i, :]
label = classify(test_list, train_set, label_set, 3)
if not label == labels[i]:
error_count += 1
return error_count / float(test_count)
通过以上测试得出错误率是5%,还算不错。因此我们可以使用judge
函数来判断一个人的魅力程度,其中x1,x2,x3分别表示飞行常客里程数、玩游戏所耗时间百分比、每周消费冰淇淋升数:
def judge(x1, x2, x3):
data_set, labels = read('datingTestSet.txt')
min = data_set.min(0)
max = data_set.max(0)
input = (array([x1, x2, x3]) - min) / (max - min)
return classify(input, format(data_set), labels, 3)
5. 手写识别
第3个例子是采用kNN算法来识别手写数字。手写数字图片已经经过去噪、二值化处理,现储存在文本文件中,比如一张数字0的图片,存储格式如下:
00000000000001100000000000000000
00000000000011111100000000000000
00000000000111111111000000000000
00000000011111111111000000000000
00000001111111111111100000000000
00000000111111100011110000000000
00000001111110000001110000000000
00000001111110000001110000000000
00000011111100000001110000000000
00000011111100000001111000000000
00000011111100000000011100000000
00000011111100000000011100000000
00000011111000000000001110000000
00000011111000000000001110000000
00000001111100000000000111000000
00000001111100000000000111000000
00000001111100000000000111000000
00000011111000000000000111000000
00000011111000000000000111000000
00000000111100000000000011100000
00000000111100000000000111100000
00000000111100000000000111100000
00000000111100000000001111100000
00000000011110000000000111110000
00000000011111000000001111100000
00000000011111000000011111100000
00000000011111000000111111000000
00000000011111100011111111000000
00000000000111111111111110000000
00000000000111111111111100000000
00000000000011111111110000000000
00000000000000111110000000000000
直接看可能看的不是太清楚,我们可以将1替换为空格,并且调整字符的间距,这样可能清楚一些:
我们的样本数据集共有1934条数据,测试数据集共有946条数据,每条数据对应一个文本文件,文本内容格式如上。每个文件的命名方式是:数字_索引.txt。如第一张数字0的图片对应文件的名称是0_1.txt,第3张数字2的图片对应的名称是2_3.txt,因此我们可以通过文件名来知道图片的“标准答案”。
每张图片可以看成一个32x32的矩阵,为了能够满足kNN算法的前提条件,我们需要将每张图片的所有行连接起来,变为1行,这样就变成了1024列;而其对应的标签就是对应的数字。我们用如下read
函数去做以上的工作:
def read(train_dir):
pic_list = os.listdir(train_dir)
train_set = zeros((len(pic_list), 1024))
labels = []
for index, img in enumerate(pic_list):
train_set[index, :] = read_img(os.path.join(train_dir, img))
labels.append(int(img[0]))
return train_set, labels
read
函数有调用了read_img
函数,它用来读取一张图片,并把该图片转换为一个1x1024的向量。
下面是测试函数:
def test(test_dir):
train_set, labels = read('trainingDigits')
pic_list = os.listdir(test_dir)
error_count = 0
for index, img in enumerate(pic_list):
vector = read_img(os.path.join(test_dir, img))
label = classify(vector, train_set, labels, 3)
if not label == int(img[0]): error_count += 1
return error_count / float(len(pic_list))
最后的测试结果为错误率是0.0126849894292,还算不错。
6. 总结
通过以上的3个例子,我们可以感觉到kNN算法具有简单,编程较容易,精确度还比较高的等特点,并且可以想象它对异常值不敏感的;但它的缺点也很明显,比如它的数据应用范围有限,时间以及空间的复杂度都比较高(在第3个例子中明显感觉到)。
机器学习实战笔记(1)——k-近邻算法的更多相关文章
- 机器学习(1)——K近邻算法
KNN的函数写法 import numpy as np from math import sqrt from collections import Counter def KNN_classify(k ...
- SIGAI机器学习第七集 k近邻算法
讲授K近邻思想,kNN的预测算法,距离函数,距离度量学习,kNN算法的实际应用. KNN是有监督机器学习算法,K-means是一个聚类算法,都依赖于距离函数.没有训练过程,只有预测过程. 大纲: k近 ...
- 机器学习实战笔记-11-Apriori与FP-Growth算法
Apriori算法 优点:易编码实现:缺点:大数据集上较慢:适用于:数值型或标称型数据. 关联分析:寻找频繁项集(经常出现在一起的物品的集合)或关联规则(两种物品之间的关联关系). 概念:支持度:数据 ...
- 机器学习实战 - python3 学习笔记(一) - k近邻算法
一. 使用k近邻算法改进约会网站的配对效果 k-近邻算法的一般流程: 收集数据:可以使用爬虫进行数据的收集,也可以使用第三方提供的免费或收费的数据.一般来讲,数据放在txt文本文件中,按照一定的格式进 ...
- 机器学习实战笔记--k近邻算法
#encoding:utf-8 from numpy import * import operator import matplotlib import matplotlib.pyplot as pl ...
- 机器学习实战笔记(Python实现)-01-K近邻算法(KNN)
--------------------------------------------------------------------------------------- 本系列文章为<机器 ...
- 02机器学习实战之K近邻算法
第2章 k-近邻算法 KNN 概述 k-近邻(kNN, k-NearestNeighbor)算法是一种基本分类与回归方法,我们这里只讨论分类问题中的 k-近邻算法. 一句话总结:近朱者赤近墨者黑! k ...
- 《机器学习实战》学习笔记一K邻近算法
一. K邻近算法思想:存在一个样本数据集合,称为训练样本集,并且每个数据都存在标签,即我们知道样本集中每一数据(这里的数据是一组数据,可以是n维向量)与所属分类的对应关系.输入没有标签的新数据后,将 ...
- 机器学习实战笔记-k-近邻算法
机器学习实战笔记-k-近邻算法 目录 1. k-近邻算法概述 2. 示例:使用k-近邻算法改进约会网站的配对效果 3. 示例:手写识别系统 4. 小结 本章介绍了<机器学习实战>这本书中的 ...
随机推荐
- 2016"百度之星" - 资格赛(Astar Round1) Problem C
字典树. 插入的时候update一下节点出现的次数. delete的时候,先把前缀之后的全删了.然后看前缀最后一个节点出现了几次,然后前缀上每个节点的次数都减去这个次数. 前缀从上到下再检查一遍,如果 ...
- Lua打印Table的数据结构工具类
--这是quick中的工具,作用就是打印Lua中强大的table的结构, 当table的嵌套层级比较多的时候,这个工具非常方便,开发中必备的工具. --具体使用方法:local debug = req ...
- jquery 如何动态添加、删除class样式方法介绍_jquery_脚本之家
获取与设置样式 获取class和设置class都可以使用attr()方法来完成.例如使用attr()方法来获取p元素的class 取与设置样式 获取class和设置class都可以使用attr()方法 ...
- linux shell命令之 xargs
1 简介 xargs是一条Unix和类Unix操作系统的常用命令.它的作用是将参数列表转换成小块分段传递给其他命令,以避免参数列表过长的问题. 2 特点 (1) 处理文件/目录名中的空格 find ...
- OPENCV条形码检测与识别
条形码是当前超市和部分工厂使用比较普遍的物品,产品标识技术,使用摄像头检测一张图片的条形码包含有两个步骤,第一是定位条形码的位置,定位之后剪切出条形码,并且识别出条形码对应的字符串,然后就可以调用网络 ...
- (中等) POJ 2528 Mayor's posters , 离散+线段树。
Description The citizens of Bytetown, AB, could not stand that the candidates in the mayoral electio ...
- DP题目列表/弟屁专题
声明: 1.这份列表不是我原创的,放到这里便于自己浏览和查找题目. ※最近更新:Poj斜率优化题目 1180,2018,3709 列表一:经典题目题号:容易: 1018, 1050, 1083, 10 ...
- SpringMVC强大的数据绑定(2)——第六章 注解式控制器详解
SpringMVC强大的数据绑定(2)——第六章 注解式控制器详解 博客分类: 跟开涛学SpringMVC 6.6.2.@RequestParam绑定单个请求参数值 @RequestParam用于 ...
- RoundedImageView使用吐槽心得(RoundedImageView与Glide加载图片,第一次加载无法圆角问题)
最近使用的时候发现一个问题, RoundedImageView与Glide搭配使用的时候,第一次加载图片(内存中没有),后图片无法圆角,后来尝试各种改,最后想到了一个办法,就是让Glide加载图片的 ...
- PHP7新特性
重写ZenVM,性能比PHP5.6提升300% 新特性: 1.变量类型(为PHP7.1的JIT特性做准备)function test(int $a, string $b, array $c) : in ...