机器学习实战(1)——K-近邻算法(源码和参考书在最后有附)

一、概述

1、简单说,k-近邻算法(KNN)采用测量不同特征值之间的距离方法进行分类

2、原理:
(1) 数据集:存在一个样本数据集合,也称作训练样本集,并且样本集中每个数据都存在标签(也就是说,我们知道样本集中每个数据与所属分类的对应关系)
(2)分类:输入没有标签的新数据后,将新数据的每个特征与样本集中数据对应的特征进行比较,然后算法提取样本集中特征最相似(最邻近)的数据的分类标签。
(3)K:一般来说,我们只选择样本数据集中前k个最相似的数据,这就是k-近邻算法中k的出处,通常k是不大于20的整数。
(4)分类结果:最后,选择k个最相似数据中出现次数最多的分类,作为新数据的分类。

3、举例说明:以电影分类为例(爱情片和动作片)
(1)首先统计已知的6部电影(其中三部动作片,三部爱情片)场景中的打斗场景和接吻场景出现的次数-------注意这就是数据的特征了,打斗场景是动作片的特征,接吻场景是爱情片的特征,如果出现一部新的电影,就可以通过KNN来解决了,如下:
上图是统计结果,其中圈起来的问号是一部未知的新电影(其他的每个英文标题对应一个电影,同时对应一对坐标(x1,x2),这里x1是打斗镜头次数,x2是接吻镜头次数,这些英文电影的坐标就是我们的数据集了)

(2)接下来,计算未知电影(也就是问号代表的)与样本集中其他电影的距离(每个电影都对应一个点,计算电影直接的距离就是计算点之间的距离),然后找出其中排名前k项距离未知电影最近的的电影(这里k为3),然后再统计这k部电影中,种类最多的电影 数(爱情片),则未知电影的种类根据KNN算法可以归类为爱情片:


4、整个算法的大致流程


注意:KNN算法与一般的机器学习算法不同,一般机器学习算法,都是通过训练数据集来训练模型(得到模型里面的各种参数),训练好了以后,这些数据集就没有用了,要测试新的样本,直接靠训练出来的模型就可以了,但是KNN算法每次测试新的样本都需要用到原来的数据集,同时他也没有训练算法这一步骤,一般有点机器学习基础知识的,应该都明白,这里就不知详细解释了。


二、电影分类实例

Python3

#coding=utf8
#KNN.py
from numpy import * #导入科学计算包
import operator  #导入运算符模块

def createDataSet():   #创造一个数据集
    group = array([[1.0,1.1],[1.0,1.0],[0,0],[0,0.1]])   #构造特征集矩阵
    labels = ['A','A','B','B']               #输入标签
    return group,labels


"""函数功能就是根据数据集,然后预测新给定的样本数据InX的标签是什么(会在最后返回)"""
def classify(inX,dataSet,labels,k):     #参数详细解释看下面

	#第一步:求欧式距离
	dataSetSize=dataSet.shape[0]         #dataSetSize是dataSet的行数,用上面的举例就是4行
	diffMat=tile(inX,(dataSetSize,1))-dataSet   #坐标相减(详细解释看:)
	sqDiffMat=diffMat**2                 #上一行得到了坐标相减,然后这里要(x1-x2)^2,要求乘方
	sqDistances=sqDiffMat.sum(axis=1)    #axis=1是列相加,,这样得到了(x1-x2)^2+(y1-y2)^2
	distances=sqDistances**0.5           #开根号,这个之后才是距离

	#第二步:所有距离进行排序
	sortedDistIndicies=distances.argsort()         #argsort是排序,将元素按照由小到大的顺序返回下标,比如([3,1,2]),它返回的就是([1,2,0])

	#第三步:取排序前k个,这里的k就是KNN中的k,然后放入到词典中,词典的结构是:类别:个数
	classCount={}
	for i in range(k):
		voteIlabel=labels[sortedDistIndicies[i]]  #设置词典中的key值,也就是标签
		classCount[voteIlabel]=classCount.get(voteIlabel,0)+1    #设置词典中的key值对应的value值
        #get是取字典里的元素,如果之前这个voteIlabel是有的,那么就返回字典里这个voteIlabel里的值,如果没有就返回0(后面写的)

	#第四步:将字典排序,然后返回个数最多的类别
	soredClassCount=sorted(classCount.items(),key=operator.itemgetter(1),reverse=True)
    #key=operator.itemgetter(1)的意思是按照字典里的第一个排序,{A:1,B:2},要按照第1个(AB是第0个),即‘1’‘2’排序。reverse=True是降序排序
	return soredClassCount[0][0]             #返回类别最多的类别

group,labels = createDataSet()
predict = classify([0,0],group,labels,3)  #装入一个新的数据,然后开始测试类别
print(predict)

说一下上面代码的构成,主要有两个函数:createDataSet()和classify()

1、createDataSet()

用来创建一个新的数据集,其中group是特征集,上面代码中的例子,一共举了四个样本,每个样本都有两个特征(如果以电影为例的话,一个特征是接吻片段个数,一个特征是打斗场景个数),当然这个样本个数和样本的特征个数都是可以更改的,要根据你的具体问题来进行更改,列举的只是最简单的例子

labels则是样本对于的标签,group中一共有4个样本,所以这里labels就有4个元素,每个样本对应一个标签,这些都是用来输入到KNN模型中的

其中array函数问题,不懂的可以参考:Python中数组的基本操作(numpy.array)

注意:一个机器学习算法的好坏,很大程度上取决于你的数据的好坏,上面列举的例子是最简单的,所以样本非常少

2、classify0()

(1)输入参数

  • inX是指要分类的样本(注意这是一个向量)
  • dataSet是已知的数据集(这是一个矩阵,每个样本数据是一行,多个样本就是一个矩阵)
  • labels是上面数据集对应的标签,这也是一个向量,向量的长度与dataSet的行数一样(样本数)
  • k参数是KNN里面的k,也就是要选择的前k个样本。
(2)第一步求距离

这里用的距离度量是欧氏距离,当然你也可以用其他的距离度量方法(其他距离可以参考:机器学习的5种距离度量),当然我看到的算法中,KNN一般都是用欧氏距离,因为样本提取特征以后,这些特征总是可以表现为一个向量(每个特征占一个维度,几个特征就是几维向量),而这个向量可以在空间中表示为一个点,欧式距离就表示空间中点的距离,举个例子,计算两个向量点xA和xB的距离:


相信只要上过高中的同学,都会非常熟悉上面的公式,这里就不再多说了。

上面代码中,第一步就是用来计算输入的未分类样本(即inX)与其他数据样本(即dataSet)之间的距离

这里说一下,这一步中第二行代码,也就是坐标相减,它的原理是求出未分类样本与分类样本的距离,比如未分类样本是【1,2,1】,而分类样本分别是[1,2,4]、[6,5,3]和[7,4,3];首先将未分类样本扩展成一个矩阵,然后和已分类样本相减,如下图:


其中tile函数解释:numpy中的tile函数

(3)第二步距离排序

这个很简单,上面求出未分类样本与已分类样本的距离,现在按这些距离的大小进行排序,然后选出距离最短的前K个点就可以了,这K个点就是对未分类样本做出分类的依据

(4)取前K个样本

这个也很简单,这里上面已经介绍过了,不再多说

(5)排序与返回

返回的值就是对未分类样本做出的预测值。

3、编码问题

(1)导入模块

参考我之前写的博客:第三方模块,其中第二个标题中记载了如何导入第三方模块

注意:最好的导入方法,是先在你下载的Python版本中导入模块,然后再在编程项目虚拟解释器中导入模块,不懂可以参考:解释器,当然这个都是次要的,初学者只要导入进去就可以了

(2)出现大量波浪线

我的代码写完以后,发现出现了大量的波浪线,原来是格式有问题,不符合Python的写作规则,详细解决方案,可以参考:如何去除波浪线

(3)from numpy import * 与 import numpy的区别

我写的代码是参考自机器学习实战,但是写的时候,我当时直接写的import numpy,我觉得这两个是一样的啊,加星号,不就是全部导入嘛,但是谁知道出现了报错,网上查了一下,发现确实有区别,而且区别还很大,区别可以参考:区别

于是我就把代码改了一下,如下:

import numpy as np #导入科学计算包

group = np.array([[1.0,1.1],[1.0,1.0],[0,0],[0,0.1]])  #这里改了一下,这是第7行代码

diffMat = np.tile(inX,(dataSetSize,1))-dataSet  #这里改了一下,这是第17行代码
一般业界都是将numpy改成np的,以后见了习以为常就可以了

Java代码

package KNN_Alogrithm;

import java.util.HashMap;
import java.util.Map;

public class KNN {

	/*
	 * 这是一个K-近邻算法,用java实现的,其主要是一个函数,里面的主函数是测试用的 KNN函数是参考Python写的,里面有四个参数:
	 * inX是需要进行分类的样本,dataSet是样本数据集,labels是样本数据集的标签 k是算法中的K,也就是排序的前k个样本
	 */

	public String classify(double[] inX, double[][] dataSet, String[] labels, int k) {
		int dataSetSize = dataSet.length;
		int i, j = 0;

		double[] Nlength = new double[dataSetSize]; // 记录每个已知样本与未知样本的距离
		for (i = 0; i < dataSetSize; i++) {
			double sum = 0;
			for (j = 0; j < dataSet[i].length; j++) {
				sum = sum + Math.pow(dataSet[i][j] - inX[j], 2);
			}
			Nlength[i] = Math.sqrt(sum);
			System.out.println(i + "  :" + Nlength[i]);
		}

		do { // do-while是要进行冒泡排序,找出前k个数据
			i = 0;
			double temp = 0;
			String temp_label;
			for (j = 1; j < Nlength.length; j++) {
				if (Nlength[j - 1] > Nlength[j]) {
					temp = Nlength[j - 1];
					Nlength[j - 1] = Nlength[j];
					Nlength[j] = temp;

					temp_label = labels[j - 1]; // 注意你最终要找的是前k个labels,所以也要跟着变
					labels[j - 1] = labels[j];
					labels[j] = temp_label;
					i++;
				}
			}
		} while (i != 0);

		// 下面是对前k个类型进行分类,然后放入map集合中,一个有多少个类,每个类有多少个
		Map<String, Integer> labels_kind = new HashMap<String, Integer>();
		labels_kind.put(labels[0], 1);
		for (i = 1; i < k; i++) {
			if (labels_kind.containsKey(labels[i])) {
				labels_kind.put(labels[i], labels_kind.get(labels[i]) + 1);
			} else {
				labels_kind.put(labels[i], 1);
			}

		}

		// 接下来就是要进行找出数量最多的类型了,然后返回!
		String most_key = "";
		int most_value = 0;
		for (String key : labels_kind.keySet()) {
			if (labels_kind.get(key) > most_value) {
				most_value = labels_kind.get(key);
				most_key = key;
			}

		}

		return most_key; // 返回最多的类型

	}

	public static void main(String[] args) {
		// TODO Auto-generated method stub
		double[][] dataset = { { 1.0, 1.1 }, { 1.0, 1.0 }, { 0, 0 }, { 0, 0.1 } };
		String[] labels = { "A", "A", "B", "B" };
		double[] inX = { 0, 0 };
		KNN knn = new KNN();
		String a = knn.classify(inX, dataset, labels, 3);
		System.out.println(a);
	}

}

三、约会网站的配对

有必要说一下,上面那个KNN算法,虽然可以运行,但是不能算的上标准的机器学习算法,原有有两个:

  • 其数据集是要靠手写打进去的,这样太麻烦了,要知道一个好的机器学习算法必须要有大量的数据(一个个手写到代码太麻烦了),一般都是写到文本中,然后通过程序读取文本数据集到内存,而上面是没有这一步的
  • 数据集的处理,上面算法中也没有这一步,得到数据集后,并不是就可以直接使用,尤其是特征值很多的时候,我们需要对特征数据进行一系列化处理(可以参考:特征处理
下面这个实战,会对上面的KNN算法进行一下改善,使之可以实际应用于现实生活中(只要你有合适的数据集,就可以进行尝试)
1、问题描述

海伦经常使用在线约会网站寻找适合自己的约会对象,但是她发现尽管约会网站会推荐不同的人,但是这些人并不是她都喜欢的,她遇到的所有人可以分为三类:不喜欢的、一般喜欢的、特别喜欢的;

现在我们要编写一个分类器,将这些人正确分类,当然会提供给我们这些人的特征(每年飞向里程数、玩视频游戏消耗时间百分比、每周消费的冰淇淋公升数),根据这三个特征还有标签,我们要制作分类器,功能是再给你一个人的特征,你要分辨出:这个人是海伦特别喜欢的、一般喜欢的还是不喜欢的!

2、大致流程


这个流程比上面那个流程多了两个,但是总体而言都是一样的

3、直接上代码

#coding=utf8
#KNN.py
import numpy as np #导入科学计算包
import operator  #导入运算符模块

"""
函数1:读取数据集(一般是从文本中)
这个函数其实是createDataSet的改进版,createDataSet只是随机创建了一个非常简单的特征数据集和对应的标签
而这里的file2matrix函数,则是读取文本中的数据集,然后转换成Python可以识别的数据集,
其中returnMat相当于group,是特征数据集;而classLabelVector是labels是标签集   
"""
def file2matrix(filename):

    #第一步:读取指定的文本,然后将文本一行行拆分成列表
    fr = open(filename)
    arrayOLines = fr.readlines()  #将所有数据都读取出来,然后划分成列表,每个元素是一行
    numberOfLines = len(arrayOLines)  #返回列表arrayOLines的元素个数,也就是文本行数
    returnMat = np.zeros((numberOfLines,3)) #创造一个numberOFLines行3列的矩阵,每个元素皆为0
    classLabelVector = []  #分类标签向量
    index = 0

    #第二步:针对每一行制成矩阵中的一行元素,和标签向量中的一个元素
    for line in arrayOLines:   #依次对文本每一行进行操作
        line = line.strip()  #删除每行文本中的回车符
        listFromLine = line.split('\t') #切割line字符串,以\t为分割点,这是制表符
        returnMat[index,:] = listFromLine[0:3]  #截取listFormLine第一位到第三位的字符,然后赋值给矩阵,index代表行数
        classLabelVector.append(int(listFromLine[-1]))  #将每一行最后一位(也就是标签),赋值给类标签向量,注意要强制转换
        index += 1

    #第三步:最后返回特征数据集和分类标签
    return returnMat,classLabelVector

'''
函数2:数据处理
归一化特征值,使所有特征值变化范围一致,这里是使得数据集都为0-1之间
'''
def autoNorm(dataSet):
    minVals = dataSet.min(0)   #将每列中最小值赋给minVals,注意minVals是矩阵1*3(因为上面数据集只有3列)
    maxVals = dataSet.max(0)   #同上面,只是这里取出的是最大值
    ranges = maxVals - minVals  #这里是每列的最大波动范围(最大值-最小值),ranges的规模与minVals是一致的
    normDataSet = np.zeros(np.shape(dataSet))  #创造一个0矩阵,规模和数据集一致
    m = dataSet.shape[0]   #求数据集的行数
    normDataSet = dataSet - np.tile(minVals,(m,1))  #后面的tile函数是创建一个规模和dataSet一样的矩阵,每行都是minVals
    normDataSet = normDataSet/np.tile(ranges,(m,1)) #两个规模相同的矩阵对应元素相除,使得每个元素范围在0-1之间
    return normDataSet,ranges,minVals    #返回归一后的数据集,波动范围,还有最小值,可以根据这三个值对数据集进行还原


"""
函数3:分类器(核心)
函数功能就是根据数据集,然后预测新给定的样本数据InX的标签是什么(会在最后返回)
"""
def classify0(inX,dataSet,labels,k):

	#第一步:求欧式距离
	dataSetSize=dataSet.shape[0]
	diffMat = np.tile(inX,(dataSetSize,1))-dataSet  #这里改了一下
	sqDiffMat=diffMat**2
	sqDistances=sqDiffMat.sum(axis=1)
	distances=sqDistances**0.5

	#第二步:所有距离进行排序
	sortedDistIndicies=distances.argsort()

	#第三步:取排序前k个,这里的k就是KNN中的k,然后放入到词典中,词典的结构是:类别:个数
	classCount={}
	for i in range(k):
		voteIlabel=labels[sortedDistIndicies[i]]
		classCount[voteIlabel]=classCount.get(voteIlabel,0)+1


	#第四步:将字典排序,然后返回个数最多的类别
	soredClassCount=sorted(classCount.items(),key=operator.itemgetter(1),reverse=True)
	return soredClassCount[0][0]

'''
函数4:测试函数
这里其实不算KNN核心之中了,因为它对于算法没有任何提升,只是告诉你:你的算法准确率多少
这个算是辅助函数吧,让你看清你的算法效率怎么样,准确率低,就改善算法
如果准确率高,那么恭喜你,你的算法可以用于商业了
'''
def datingClassTest():

    #第一步:准备测试数据集
    hoRatio = 0.10     #海伦给出的数据集要有一部分用于测试集,这是比例
    datingDataMat,datingLabels = file2matrix('datingTestSet.txt')  #读取文本数据集,然后返回可以用数据集和标签
    normMat,ranges,minVals = nutoNorm(datingDataMat)      #数据处理:自动归一化
    m = normMat.shape[0]        #数据集的行数,也可以说是样本数,或者曾经约会过的人数
    numTestVecs = int(m*hoRatio)   #测试集的数量:比例*总数量

    errorCount = 0.0   #这个是错误数量,机器学习算法都要有一个正确率或者错误率

    #第二步:开始测试这个算法了,说白了还是测试classify0函数,一共测试了numTeestVecs
    for i in range(numTestVecs):
        classifierResult = classify0(normMat[i,:],normMat[numTestVecs:m,:],datingLabels[numTestVecs:m],3)  #调用分类器,返回分类结果
        print('分类器预测的结果是: %d,真实结果是: %d' % (classifierResult,datingLabels[i]))
        if(classifierResult != datingLabels[i]):
            errorCount += 1.0      #每当有一个预测结果与真实结果(也称标签)不符,错误数量就加1,
    print('总的错误率是: %f' % (errorCount/float(numTestVecs)))  #错误数量除以总数量,就是错误率了


"""
函数5:预测新样本
根据上面的函数1,2,3组成一个KNN分类器,给你一个新的男人特征,你就可以得出这个男人
是海伦特别喜欢的、一般喜欢的还是不喜欢的
可以说这个函数是功能的主函数
"""
def classifyPerson():
    
    #第一步:指定结果标签
    resultList = ['不喜欢','一般喜欢','特别喜欢']  #结果标签
   
   #第二步:通过你的输入得到新样本特征
    percentTats = float(input("玩游戏百分比?"))  #读取你的输入数据
    ffMiles = float(input('每年飞行里程数?'))   #问个问题,你输入数据,它读入
    iceCream = float(input('每周消耗冰淇淋公升数?'))  #输入数据赋值,并类型转换
    
    #第三步:得到已知标签的数据集
    datingDataMat,datingLabels = file2matrix('datingTestSet2.txt')  #读取数据集
    normMat,ranges,minVals = autoNorm(datingDataMat)   #数据自动归一化
    inArr = np.array([ffMiles,percentTats,iceCream])   #将读入的数据转换成一个向量
   
   #第四步:将新样本和已知标签的样本数据集输入分类器,得到预测结果
    classifierResult = classify0((inArr - minVals)/ranges,normMat,datingLabels,3) #调用分类器
    print("你对这个人的喜欢程度可能是:",resultList[classifierResult - 1])  #输出结果

classifyPerson()   #这是主函数

输出结果:


在上面代码中的注释中,我已经解释的很详细了,这里就大概再讲解一下,同时说一下里面的困难语句函数
(1)函数1:读取数据集
这个函数说白了,就是制作数据集的过程(制作的是Python解释器可以识别的格式),同时也是将数据集导入内存的过程,本质就是用Python解析文本文档,先读取文档,然后将文档每行制成字符串,再进行分割(跟你的数据行数进行分割),这里说一下这个函数的注意事项:

  • 如果你要更改数据集,那么代码可能要改一下:第19行,这里设置的矩阵列数为3,这个数字是根据数据集来设定的(因为我们事先已经知道是3个特征数据,游戏比、飞行里程、冰淇淋),如果你要用其他数据集,这里要改一下
  • 文本路径,这个分为绝对路径和相对路径,这里就不在多说了
(2)函数2:数据处理

先说一下,为什么要进行处理,这里针对这个数据来解释一下,这里进行的是数据归一化,也就是将三个特征值数据的范围都按照比例缩放到0-1(当然-1到1之间也可以,具体问题具体分析),那么为什么要这么做呢?

假如当前有两个样本数据A(0,  20000,  1.1)和B(67,  32000,  0.1),也就是男人A几乎不玩游戏,每年飞行2万里,每周消费冰淇淋1.1公升,男人B每天玩游戏时间67%,每年飞行3万2千里,每周消耗0.1公升冰淇淋,那么这两个样本的距离是多少呢?


上面是计算公式,通过这个公式,我们可以看出3个特征数据对于这个距离的得出贡献是不一样的,其中消耗冰淇淋这一项(蓝笔)和玩游戏(黑笔),相比飞行日程来说,几乎可以忽略不计(不是一个级数),那么它其实就是相当于只采用了一个数据特征,这其实就是一个权重问题了,产生这一个现象的唯一原因就是:飞行日程数据值远大于其他特征值

但是海伦认为这三种特征是同等重要的,也就是说这三个特征影响因子是一样的(以投票为例,每个特征都只有一票),处理这种不同取值范围的特征值,我们通常采用将数值归一化,这样他们的影响权重就一样了,归一化公式也很简单:


含义是:当前值减去最小值,然后除以波动范围,就是新的取值(绝对在0-1之间),至于代码具体含义,我在解释中已经介绍的非常清楚了,这里不再说了

(3)函数3:分类器

这个函数一个字母都没有改变,就是上面电影分类中的那个函数,所以这里不再多说什么了

(4)函数4:测试函数

测试函数,这是一个辅助函数,也可以说是一个过程函数(在算法实现过程中会用到,但是到最后就没有了,功能整合时,可以删掉),这是一个测试工具,就好像你画了一幅大作,然后请专家来测评一下,专家给你一个评价,这里的专家就是测试函数,评价就是错误率

(5)函数5:预测新样本

这就是功能实现了,上面函数1,2,3合起来组成一个分类器,函数5则是用来测试了,就好像你锻造了一把宝刀(函数1, 2,3),宝刀锻造成了以后,不可能封存起来,而是要使用的,接下来你要找一个目标(新样本)来试一下这把宝刀的锋利程度了,这个测试过程就是函数5

其中遇到的问题就是如何读入命令行数据了,Python3和Python2有些不同,具体可以参考:输入键盘读写

本质上,函数4和函数5是差不多的,只是使用的数据集不同而已(还有目的也不同)

4、数据分析

本来这是应该是函数3的,排在读取数据集之后,因为你读取的数据集可能并不是你想要的(你事先不知道这个数据集是怎么样,当然如果是你自己写的数据集,那么这个分析就可有可无了),但是我想在上面直接整体写下来,所以这个数据分析就放在了最后,大家了解一下就可以

为什么要进行数据分析呢?

很多时候,我们得到的数据集量非常大,单单打开文档直接看数据,会眼花缭乱,很难观察出什么来,这就需要我们调用一些可视化工具来帮助我们分析一下这个数据集是不是满足我们的需要呢?如果不满足的话,通过可视化数据,我们也很容易的找到问题所在,然后针对具体问题对特征数据进行处理,下面我们就针对上面的数据集来分析一波吧

(1)

import matplotlib
import matplotlib.pyplot as plt
from matplotlib.font_manager import FontProperties   #导入matplotlib模块中的字体设置
font = FontProperties(fname=r"c:\windows\fonts\simsun.ttc", size=14)  #设置字体(这里设置楷体),因为matplotlib默认不显示中文

fig = plt.figure()   #设置一个窗体
ax = fig.add_subplot(111)
#解释一下这个add_subplot(nmi)中的参数
#将fig窗体划分为n行m列的子窗体,其中ax为第i个窗体位置
#注意这三个参数也可以用逗号隔开——有些参数为两位数

datingDataMat,datingLabels = file2matrix('datingTestSet2.txt')  #读取数据集

ax.scatter(datingDataMat[:,1], datingDataMat[:,2], 15.0*np.array(datingLabels), 25.0*np.array(datingLabels))
#上面这行得好好说一下,一开始没有搞懂
#第一个参数是取出数据集第一列,然后作为图中的纵坐标
#第二个参数是取出数据集第二列所有数据,作为图中的横坐标
#第三个参数没有看懂,只是知道数字15和点的大小有关,数字越大,点越大
#第四个参数设置了点的颜色

ax.axis([-2,25,-0.2,2.0])
#这里说一下上面四个参数的意义
#-2和25表示x轴显示的范围,-0.2和2.0表示y轴显示的范围


plt.xlabel('玩游戏占比',fontproperties=font)   #设置x轴,第二个参数是允许中文显示,并设置其字体
plt.ylabel('每周消耗冰淇淋公升数',fontproperties=font)  #设置y轴
plt.show()   #图片显示

显示结果:


从上图可以看到三种不同颜色的点,每种颜色代表一种喜欢程度

(2)

from numpy import *
import matplotlib
import matplotlib.pyplot as plt
from matplotlib.patches import Rectangle


n = 1000 #number of points to create
xcord1 = []; ycord1 = []
xcord2 = []; ycord2 = []
xcord3 = []; ycord3 = []
markers =[]
colors =[]
fw = open('testSet.txt','w')
for i in range(n):
    [r0,r1] = random.standard_normal(2)
    myClass = random.uniform(0,1)
    if (myClass <= 0.16):
        fFlyer = random.uniform(22000, 60000)
        tats = 3 + 1.6*r1
        markers.append(20)
        colors.append(2.1)
        classLabel = 1 #'didntLike'
        xcord1.append(fFlyer); ycord1.append(tats)
    elif ((myClass > 0.16) and (myClass <= 0.33)):
        fFlyer = 6000*r0 + 70000
        tats = 10 + 3*r1 + 2*r0
        markers.append(20)
        colors.append(1.1)
        classLabel = 1 #'didntLike'
        if (tats < 0): tats =0
        if (fFlyer < 0): fFlyer =0
        xcord1.append(fFlyer); ycord1.append(tats)
    elif ((myClass > 0.33) and (myClass <= 0.66)):
        fFlyer = 5000*r0 + 10000
        tats = 3 + 2.8*r1
        markers.append(30)
        colors.append(1.1)
        classLabel = 2 #'smallDoses'
        if (tats < 0): tats =0
        if (fFlyer < 0): fFlyer =0
        xcord2.append(fFlyer); ycord2.append(tats)
    else:
        fFlyer = 10000*r0 + 35000
        tats = 10 + 2.0*r1
        markers.append(50)
        colors.append(0.1)
        classLabel = 3 #'largeDoses'
        if (tats < 0): tats =0
        if (fFlyer < 0): fFlyer =0
        xcord3.append(fFlyer); ycord3.append(tats)    

fw.close()
fig = plt.figure()
ax = fig.add_subplot(111)
#ax.scatter(xcord,ycord, c=colors, s=markers)
type1 = ax.scatter(xcord1, ycord1, s=20, c='red')
type2 = ax.scatter(xcord2, ycord2, s=30, c='green')
type3 = ax.scatter(xcord3, ycord3, s=50, c='blue')
ax.legend([type1, type2, type3], ["Did Not Like", "Liked in Small Doses", "Liked in Large Doses"], loc=2)
ax.axis([-5000,100000,-2,25])
plt.xlabel('Frequent Flyier Miles Earned Per Year')
plt.ylabel('Percentage of Time Spent Playing Video Games')
plt.show()

上面这个代码直接照搬的机器学习实战源码,这里就不注释了

结果输出:


遇到的问题:

  • 数据集读入有问题


因为数据集读入有误,具体解决方案,可以查看:ValueError: invalid literal for int() with base 10,只要将数据集文本换一下就可以了(我上面的代码是改正后的)

  • malplotlib模块中文乱码


其实这个模块是默认不显示中文的,我打印x轴和y轴的时候,设置为中文,结果显示了乱码,具体解决方法可以参考如下:

简单解决:两种方法解决画图乱码

具体方法:Python3解决中文乱码

  • 第一个图中对于散点图(即scatter函数)后两个参数我一直没有搞懂

我查了一些资料,除了x与y轴参数外,其他都是要进行配置,比如颜色,大小等,具体大家可以参考:Python中scatter函数参数详解,还有Matplotlib的官方文档说明:Matplotlib tutorial

约会网站需要的数据集:链接:https://pan.baidu.com/s/1G0oABxsbUso5C8gT_mLUGA 密码:zua0

四、手写数字识别实例

1、概述

首先,我一开始对于图像识别领域并不是很了解,对于首先看到这个实例的时候,我有些懵逼,KNN的原理我大致理解了:通过样本特征距离的大小,来进行分类;但这图片的特征怎么找啊,仔细看了看书里面的介绍我才彻底理解了,只要能够将一个领域的东西转换为向量,KNN就可以进行分类,而世界万物都有特征,都是可以用向量表示,所以几乎所有分类都可以用KNN来进行分类,只是分类效果的好坏不同而已,所以很多时候,不能局限了我们的思维,从最本质的地方出发:只要有特征向量,KNN就可以进行分类!!

2、如何将图片转换为向量


图片在电脑中都是用0和1表示的(不止是图片,所有的数据都是这样),上面的图片可以很清楚的看到,阴影部分为1,浅色部分为0,这就构成了数字,也是图片的本质(咳咳,我自己延伸想的,想要了解,自己去看书吧)

所以我们要将这些数字图片转换为向量很简单,只要将这些0和1存储起来就可以了,把图片看出一个矩阵:32*32(其实就是像素了),然后构造一个1*1024的向量,将这个矩阵所有数字都存储起来,就可以进行分类了

3、难点

这个实例比较难的地方就在于数据集还有将数据集转换为向量,其他的都是现成的(分类器的核心代码,上面两个例子都有,不用改变),很多机器学习算法都是这样的,要用机器学习算法解决一个问题,最大的难点永远是数据集问题(数据集是否够大,是否够随机),这也是为什么机器学习算法上个世纪就有了,但是为什么现在才火起来的重要因素之一(大数据时代来了)

手写数字数据集:链接:https://pan.baidu.com/s/1BkiCC2xxzjdkT2ikwTn7rg密码:wlo9

4、源代码

#coding=utf8
#KNN.py
import numpy as np #导入科学计算包
import operator  #导入运算符模块
from os import listdir
"""
函数1:图像转换成向量
这个函数首先读取文本数据,注意:一个图片代表一个文本,所以要读取很多个文本
这个函数要循环运行很多次,每次将一个32*32的图像矩阵,转换成1*1024的一维向量
最后将这个一维向量返回
"""
def img2vector(filename):
    returnVect = np.zeros((1,1024))  #创建一个1*1024的矩阵,即一维向量
    fr = open(filename)
    for i in range(32):           #先读取行,共有32行
        lineStr = fr.readline()   #每次读取一行
        for j in range(32):       #针对每一行进行读取
            returnVect[0,32*i+j] = int(lineStr[j]) #类型转换,读取时是字符串,要转换成数值类型
    return returnVect

"""
函数2:分类器(核心)
函数功能就是根据数据集,然后预测新给定的样本数据InX的标签是什么(会在最后返回)
"""
def classify0(inX,dataSet,labels,k):

	#第一步:求欧式距离
	dataSetSize=dataSet.shape[0]
	diffMat = np.tile(inX,(dataSetSize,1))-dataSet  #这里改了一下
	sqDiffMat=diffMat**2
	sqDistances=sqDiffMat.sum(axis=1)
	distances=sqDistances**0.5

	#第二步:所有距离进行排序
	sortedDistIndicies=distances.argsort()

	#第三步:取排序前k个,这里的k就是KNN中的k,然后放入到词典中,词典的结构是:类别:个数
	classCount={}
	for i in range(k):
		voteIlabel=labels[sortedDistIndicies[i]]
		classCount[voteIlabel]=classCount.get(voteIlabel,0)+1


	#第四步:将字典排序,然后返回个数最多的类别
	soredClassCount=sorted(classCount.items(),key=operator.itemgetter(1),reverse=True)
	return soredClassCount[0][0]


"""
函数3:系统识别代码
这个函数是将上述两个代码整合,进而形成一个完整的识别系统
"""
def handwritingClassTest():

    #第一步:制造数据集和标签集
    hwLabels = []  #这个是数据集的标签
    trainingFileList = listdir('digits/trainingDigits')
    #上面这个函数说一下,这个是列出给定目录的文件名,因为我们有很多样本,每个样本都要转换为向量
    #每次转换都要调用函数1,调用过程中你要知道样本文件的名字或者位置,所以这里要得到文件清单
    #然后按照这个清单来依次调用,而且这个函数很形象,dir不就是调出当前目录的所有文件吗,list是返回列表

    m = len(trainingFileList)   #查看一共有多个样本文件,trainingFileList是一个list
    trainingMat = np.zeros((m,1024))   #制造一个m行1024列的矩阵,每个元素都是0
    for i in range(m):
        fileNameStr = trainingFileList[i]   #得到每个文件的名字字符串
        fileStr = fileNameStr.split('.')[0]   #删除.txt字样
        classNumStr = int(fileStr.split('_')[0])
        # 以破折号为分界线将字符串分割,然后得到第一位字符转换成数值
        #这个和它的数据文本格式有很大关系,它的文本格式是:i_n,
        # 其中i是文本标签,比如这个文本是数字3,那么i就是3
        #记录3的文本有很多,而n则表示是第几个表示3的文本

        hwLabels.append(classNumStr)  #将文本标签单独放入一个集合中
        trainingMat[i,:] = img2vector('digits/trainingDigits/%s' % fileNameStr)
        #上面这个是根据文本路径将图片文本转换成向量

    #第二步:得到测试集,步骤基本一致,唯一区别在,每得到一个样本向量都要进行预测
    testFileList = listdir('digits/testDigits')        #iterate through the test set
    errorCount = 0.0
    mTest = len(testFileList)
    for i in range(mTest):
        fileNameStr = testFileList[i]
        fileStr = fileNameStr.split('.')[0]     #take off .txt
        classNumStr = int(fileStr.split('_')[0])
        vectorUnderTest = img2vector('digits/testDigits/%s' % fileNameStr)
        classifierResult = classify0(vectorUnderTest, trainingMat, hwLabels, 3)
        print('分类器预测的结果: %d, 真实结果是: %d' % (classifierResult, classNumStr))
        if (classifierResult != classNumStr): errorCount += 1.0

    #第三步:输出错误个数和错误率
    print('\n总的错误个数: %d' % errorCount)
    print('\n总的错误率: %f' % (errorCount/float(mTest)))

handwritingClassTest()#运行代码

结果输出(所有输出太长了,这里就截取最后一部分):


上面一共有三个函数,第一个函数是将一个文本转换成一个向量,下面介绍一下这个文本数据集(否则大家可能很难理解):

  • 总的数据集:

其中,testDigits是测试集,而trainingDigits是训练集

  • 训练集:


注意:每个文本的格式i_n,i表示文本里面的数字,而n表示这是关于i的第几个文本,这个表示方法很重要(当然你用其他表示方法也可以,只要你可以得到相关标签就可以)

  • 训练集的文本


OK,训练集和测试集里面的文本内容都差不多,这里就不再一一介绍了。

注意:建议大家好好把数据集和代码下载下来,然后自己独立运行一下,当然最好的还是自己有独立的项目,然后运用KNN去解决一下

五、资源参考

1、首先自然是《机器学习实战》这本电子书了,链接为:链接:https://pan.baidu.com/s/1nfJuwI2JQ6OAjM5Jbi7MOg 密码:l5xv(高清彩色版本)

2、这本书附带的源代码与数据集(这里的源代码是这本书附带的,不是我上面写的,有一部分是Python2格式):链接:https://pan.baidu.com/s/1mDqTlRVPAZBkok4E7ToVHQ  密码:162r

3、一些参考书:

用Python做科学计算:链接:https://pan.baidu.com/s/1hEwKT4k3jAqEDslla2L1eQ 密码:0k5l

笨方法学Python:链接:https://pan.baidu.com/s/1MKCKoRZjPV0Q4rQoa2LEpg 密码:enfk

流畅的Python:链接:https://pan.baidu.com/s/1Ln28HA3ITarp4sCPtT85VA 密码:elpc

猜你喜欢

转载自blog.csdn.net/yuangan1529/article/details/80819199