"""
参考文章
http://www.jianshu.com/p/a8ae004cf4f4
https://www.shiyanlou.com/courses/777实验楼
http://blog.csdn.net/niuwei22007/article/details/49703719
http://blog.csdn.net/quincuntial/article/details/50471423
http://blog.csdn.net/sunshine_duoy/article/details/52824775
http://www.jianshu.com/p/4cc0869f84b9代码流程
"""
#coding=UTF8
from numpy import * #科学计算包
import operator #运算符模块
from os import listdir#从OS模块中导入listdir,它可以列出给定目录的文件名
# 创建数据集,并返回数据集和分类标签
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
# 对新数据进行分类
"""
4个输入参数
inX 是输入的测试样本(用于分类的输入向量),是一个[x, y]样式的
dataset 是训练样本集
labels 是训练样本标签
k 是top k最相近的,即用于选择最近邻的数目k
假设inX=inX = [0,1],dataset=[[1.0, 1.1], [1.0, 1.0], [0, 0], [0, 0.1]], labels = ['A', 'A', 'B', 'B'],k=3
下面的求距离过程就是按照欧氏距离的公式计算的。 即 根号(x^2+y^2)
1、先将一维矩阵扩展和测试矩阵的维度一样
2、将新生成的待测矩阵同测试矩阵进行矩阵减法,再将矩阵的差的平方和开根得到距离
3、将距离排序并返回
"""
def classify0(inX, dataSet, labels, k):
# ==============================================================================
# shape返回矩阵的[行数,列数],那么shape[0]获取数据集的行数,行数就是样本的数量
#此处在假设条件下dataSetSize=4
# ==============================================================================
dataSetSize = dataSet.shape[0] # 读取数据集行数,shape[1]则为列数
# ==============================================================================
# 1、tile属于numpy模块下边的函数
# tile(A, reps)返回一个shape=reps的矩阵,矩阵的每个元素是A(reps的数字从后往前分别对应A的第N个维度的重复次数)
# 比如 A=[0,1,2] 那么,tile(A, 2)= [0, 1, 2, 0, 1, 2]
# tile(A,(2,2)) = [[0, 1, 2, 0, 1, 2],
# [0, 1, 2, 0, 1, 2]]
#---------------------------------------------------------------------------------------------------
# tile(A,(2,1,2)) = [[[0, 1, 2, 0, 1, 2]],
# [[0, 1, 2, 0, 1, 2]]]
# 上边那个结果的分开理解就是:
# 最外层是2个元素,即最外边的[]中包含2个元素,类似于[C,D],而此处的C=D,因为是复制出来的
# 然后C包含1个元素,即C=[E],同理D=[E]
# 最后E包含2个元素,即E=[F,G],此处F=G,因为是复制出来的
# F就是A了,基础元素
# 综合起来就是(2,1,2)= [C, C] = [[E], [E]] = [[[F, F]], [[F, F]]] = [[[A, A]], [[A, A]]]
# 这个地方就是为了把输入的测试样本扩展为和dataset的shape一样,然后就可以直接做矩阵减法了。
# ------------------------------------------------------------------------------------------------------
# 比如,dataset有4个样本,就是4*2的矩阵,输入测试样本肯定是一个了,就是1*2,为了计算输入样本与训练样本的距离
# 那么,需要对这个数据进行作差。这是一次比较,因为训练样本有n个,那么就要进行n次比较;
# 为了方便计算,把输入样本复制n次,然后直接与训练样本作矩阵差运算,就可以一次性比较了n个样本。
# ------------------------------------------------------------------------------------------------------
# 比如inX = [0,1],dataset就用函数返回的结果,那么
# tile(inX, (4,1))= [[ 0.0, 1.0],
# [ 0.0, 1.0],
# [ 0.0, 1.0],
# [ 0.0, 1.0]]
# 作差之后(dataSet是[[1.0, 1.1], [1.0, 1.0], [0, 0], [0, 0.1]])相减之后为下面的值
# diffMat = [[-1.0,-0.1],
# [-1.0, 0.0],
# [ 0.0, 1.0],
# [ 0.0, 0.9]]
# ==============================================================================
diffMat = tile(inX, (dataSetSize, 1)) - dataSet# 要分类的新数据与原始数据做差
# ==============================================================================
# diffMat就是输入样本与每个训练样本的差值,然后对其每个x和y的差值进行平方运算。
# diffMat是一个矩阵,矩阵**2表示对矩阵中的每个元素进行**2操作,即平方。
# sqDiffMat = [[1.0, 0.01],
# [1.0, 0.0 ],
# [0.0, 1.0 ],
# [0.0, 0.81]]
# 也就是上一步得到的矩阵的值的每一个元素都做平方
# ==============================================================================
sqDiffMat = diffMat ** 2 # 求差的平方
# ==============================================================================
# axis=0, 表示列。 axis=1, 表示行。sum(axis=x)即每个向量将自己内部元素的所有行或者列加和作新一维向量里的一个元素
# example:
# c = np.array([[0, 2, 1], [3, 5, 6], [0, 1, 1]])
# c.sum() = 19,即所有元素的和
# c.sum(axis=0) =[3 8 8],即将矩阵的列向量想加后得到一个新的一维向量
# c.sum(axis=1) =[3,14,2], 同理
# ------------------------------------------------------------------------------------------------------
# axis=1表示按照横轴(行),sum表示累加,即按照行进行累加。
# sqDistance = [[1.01],
# [1.0 ],
# [1.0 ],
# [0.81]]
# ==============================================================================
sqDistance = sqDiffMat.sum(axis=1)# 求差的平方的和,此处是每一个列向量相加,axis=0为行相加
# ==============================================================================
# 对平方和进行开根号,得到
# sqDistance = [[1.004987562],
# [1.0 ],
# [1.0 ],
# [0.9]]
# ==============================================================================
distances = sqDistance ** 0.5 # 求标准差,得到距离
# ==============================================================================
# argsort函数返回的是数组值从小到大的索引值,即按照升序进行快速排序,返回的是原数组的下标
# examples:
# # x = np.array([3, 1, 2])
# # np.argsort(x)
# array([1, 2, 0]) 数值1对应下表1,数值2对应下表2,数值3,对应下标0
# ------------------------------------------------------------------------------------------------------
# [1.004987562 1 1 0.9 ] to [3 1 2 0]
# ==============================================================================
sortDistIndicies = distances.argsort() # 距离升序排序
#存放最终的分类结果及相应的结果投票数
classCount = {} # 建造一个名为classCount的元字典{key,value},Key是label,value是这个label出现的次数
# 投票过程,就是统计前面计算的距离最近的前k个最近的样本所属类别包含的样本个数,此处k=3
for i in range(k): # 遍历前k个元素
# ==============================================================================
# 这里很巧妙的地方在于,一开始测试数组和Label的位置是一一对应的
# 所以这里可以通过上面获得的测试数组的下标index获得对应的分类labels[index]
# index = sortedDistIndicies[i]是第i个最相近的样本下标
# voteIlabel = labels[index]是样本index对应的分类结果('A' or 'B')
# ------------------------------------------------------------------------------------------------------
# [0.9 1 1 ] to ['B' 'A' 'B']
# ==============================================================================
voteIlabel = labels[sortDistIndicies[i]] # 获得排名靠前的前k个元素的标签
# ==============================================================================
# 这里将每一个[label : Value]赋值,Value为计算VoteLabel出现的次数即classCount
# 如果Label不在字典classCount中时,get()返回 0并存入字典,字典存在则读取当前value值并+1
# ------------------------------------------------------------------------------------------------------
#{'B'=2,'A'=1}
# ==============================================================================
classCount[voteIlabel] = classCount.get(voteIlabel, 0) + 1 # 计算前k个数据标签出现的次数,get(key,default=None),就是造字典
# ==============================================================================
#把分类结果进行排序,然后返回得票数最多的分类结果
# 根据字典classCount中的Value值对字典进行排序,选出出现次数最多的Label
# sorted(iterable, cmp=None, key=None, reverse=False)
# return new sorted list
# 1、iterable:是可迭代类型的对象(这里的字典通过iteritems()进行迭代)
# 2、cmp:用于比较的函数,比较什么由key决定,一般用Lamda函数
# 3、key:用列表元素的某个属性或函数进行作为关键字,迭代集合中的一项;
# operator.itemgetter(1)表示获得对象的第一个域的值(这里指value)
# 4、reverse:排序规则。reverse = True 降序 或者 reverse = False 升序。
#
# return: 一个经过排序的可迭代类型对象,与iterable一样。
# ------------------------------------------------------------------------------------------------------
# 这里返回排好序的[Label:value], 即 [('B', 2), ('A', 1)]
# ==============================================================================
sortedClassCount = sorted(classCount.iteritems(), key=operator.itemgetter(1), reverse=True) # 对得到的标签字典按降序排列
# ==============================================================================
# 返回可迭代对象的第一个域的第一值,也就是出现次数最多的Label----B
# sortedClassCount[0][1] 表示第一个域的第二个值,就是出现最多次数Label出现次数-----2
# ------------------------------------------------------------------------------------------------------
# 因为是降序排序,此处返回第一个值也就是最大值B
# ==============================================================================
return sortedClassCount[0][0] # 返回出现次数最多的标签,出现次数最多的value的key
# 读取文本文件中的数据
"""
改进一:从文件中读入数据
要做一个稍微复杂一点的测试,就需要多准备点样本数据,而在第三节中,训练样本只有4个,而且是固定生成的。
在新的算法里边加入了可以从文件中读取训练数据。代码有详细注释。
本案例的数据被置于txt文件中,所以需要我们将数据从txt文件中抽出并做处理。
所以我们准备函数file2matrix()将txt的数据转换为矩阵方便我们后面做运算。
从文件中读入训练数据,并存储为矩阵
datingTestSet.txt存放了该网站关于个人信息的集合。每一行代表一个人,一共有三个特征属性,和一个标签属性用来标识是哪一类人。
datingTestSet2.txt 存放的是处理过的datingTestSet数据,将类别标签处理成数字
"""
def file2matrix(filename):
# 根据文件名打开文件
fr = open(filename)# 打开文件,按行读入
# 读取每一行的数据并得到得到文件行数
# txt文件中的数据类似:"40920 8.326976 0.953952 largeDoses
numberOfLines = len(fr.readlines())# 计算文本文件的行数
# ==============================================================================
# 创建返回的Numpy矩阵
# zeros(a) 仅有一个参数时,创建一个一维矩阵,一行 a 列
# zeros(a, b) 创建一个 a X b 矩阵, a 行 b 列
# 这里取了数据的行数和前三个特征值(前三个是特征,第四个个是类别)创建一个矩阵
# 创建一个2维矩阵用于存放训练样本数据,一共有n行,每一行存放3个数据
# ==============================================================================
returnMat = zeros((numberOfLines, 3))# 创建返回的零矩阵
# 创建一个1维数组用于存放训练样本标签。即# 建造一个名为 classLabelVector 的 List 用来存放最后列的Label
classLabelVector = [] # 创建类标签
fr = open(filename)# 打开文件
# 这里的 index 指的是矩阵的第几行,从 0 开始
index = 0# 定义索引
#( 以下三行) 解析文件数据到列表(循环处理文件中每行数据)
for line in fr.readlines():
# # 去掉文本中句子中的换行符"/n",但依然保持一行四个元素
line = line.strip() # 去除行的尾部的换行符
# ==============================================================================
# 以 ‘\t’(tab符号)分个字符串,文本中的数据是以tab分开的
# 另外需要注意 split() 函数返回一个 List对象
# 如:['30254', '11.592723', '0.286188', '3']
# ==============================================================================
listFromLine = line.split('\t')# 将一行数据按空进行分割,使用tab字符\t将上步得到的整行数据分割成一个元素列表
# ==============================================================================
# 把分割好的数据放至数据集,其中index是该样本数据的下标,就是放到第几行
# 选取前listFromLine的三个元素,存储在特征矩阵中
# returnMat[index,:]这里需要注意,因为returnMat是一个矩阵
# 其中的 index指的是矩阵中的第几个list
# 例如 listMat = [[list1]
# [list2]
# [list3]
# ......
# [listn]]
# listMat[0]表示的是矩阵listMat中的第1行,即 [list1]
# listMat[2,:] 表示的是矩阵listMat中的第3行的所有元素
# listMat[2,0:2] 表示矩阵listMat中的第3行下标为 0和1两个元素
#
# listFromLine[0:3]切片开始于0、停止于3,也就是取了下标为0,1,2的元素
# 将listFromLine[0:3]的第0号到2号元素赋值给特征矩阵returnMat对应特征中
# ==============================================================================
returnMat[index, :] = listFromLine[0:3]# #取得每一行的内容存起来(选取前三个元素,将它们存储到特征矩阵中)
# ==============================================================================
# 把该样本对应的标签放至标签集,顺序与样本集对应。
# listFromLine[-1] 取listFromLine的最后一列标签类(将其强行转换为int类型)
# 同时将该类别标签按顺序加入到标签向量classLabelVector中
# ==============================================================================
classLabelVector.append(int(listFromLine[-1]))# 最后一列为数据的分类标签,Python语言可以使用索引值-1表示列表中最后一个元素,很方便的将列表最后一列存储到向量classLabelVector中)
#(需要注意的是,必须明确通知解释器,告诉它,列表中存储的元素值为整型,否则,Python将它们视为字符串处理)
# index加 1,矩阵存取下一个list
index += 1 # 索引加1
return returnMat, classLabelVector # 返回数据集和对应的类标签
# 归一化函数
"""
改进二:数值归一化
当遇到特征值相差很大的时候,分类效果就不好了。
比如,原来的dataSet中有一个这样的样本:[1, 10000]。同时输入样本仍然为第三节中测试的[0.1, 0.1]。所以很明显,
当求这两个样本之间的距离的时候就出问题,由于那个10000太大,对最终的结果影响最大,其他的0.1和1可以忽略不计了。
因此就用到了数值归一化。
观察依据特征值做的散点图我们不难发现一个问题,如果当某一个特征值与其他的特征值的差异过大的话,如果不加权重或各特征权重均衡的话,
很可能会影响到最后的分类,比如例子中的飞行距离,远远大于其他两个特征值。
在这种情况下,特征的距离就会受飞行距离特征值得影响。所以我们需要将数据进行归一化,即将所有数据都变为 0~1 之间的数,
目的是让数据的影响都能比较均衡。
newValue = (oldValue-min)/(max-min)即当前值减去最小值/最大值减最小值
"""
def autoNorm(dataSet):
# ==============================================================================
# min(0)取矩阵每一列的最小值
# min(1)取矩阵每一行的最小值
# Examples:
# array([[1,2]
# [3,4]
# [5,6]])
# min(array,0) = [1,2]
# min(array,1) = [1,3,5]
# 这里同: minVals = np.min(dataSet,0)
# 返回的是一个一维矩阵
# ==============================================================================
minVals = dataSet.min(0)# 求数据矩阵每一列的最小值 1*3
maxVals = dataSet.max(0)# 求数据矩阵每一列的最大值 1*3
# ranges也是一个一维矩阵
ranges = maxVals - minVals # 求数据矩阵每一列的最大最小值差值 1*3
# ==============================================================================
# shape(dataSet) 返回一个记录有矩阵维度的tuple
# 例如:np.shape([[1, 2]]) = (1, 2)
# zeros(shape)返回一个具有 shape 维度的矩阵,并且所有元素用 0 填充
# ==============================================================================
normDataSet = zeros(shape(dataSet)) #创建新的返回矩阵
# m 表示 dataSet 矩阵的行数,shape[1] 表示矩阵的列数
m = dataSet.shape[0]# 返回数据矩阵第一维的数目
# ==============================================================================
# tile(minVals, (m,1)) 首先将 minVals 复制为 m 行的矩阵
# examples:
# if minVals = [[1,2,3]]
# tile(minVals, (3,1))(行数复制 3 遍,列保持不变)
# return [[1,2,3]
# [1,2,3]
# [1,2,3]]
#因特征值矩阵有1000*3个值,而minVals和range的值都为1*3,所以用tile将变量内容复制成输入矩阵同样大小的矩阵
# ==============================================================================
normDataSet = dataSet - tile(minVals, (m, 1))# 求矩阵每一列减去该列最小值,得出差值
# ==============================================================================
# 同上,将 一维矩阵 ranges 复制 m行,再让矩阵中的元素做商
# ------------------------------------------------------------------------------------------------------
# 把最大最小差值扩充为dataSet同shape,然后作商,是指对应元素进行除法运算,而不是矩阵除法。
# 矩阵除法在numpy中要用linalg.solve(A,B)
# ==============================================================================
normDataSet = normDataSet / tile(ranges, (m, 1))# 用求的差值除以最大最小值差值,即数据的变化范围,即归一化
# 返回归一化的矩阵,最大与最小值的差值矩阵,和最小值矩阵
return normDataSet, ranges, minVals # 返回归一化后的数据,最大最小值差值,最小值
# 分类器测试函数
"""
测试分类器效果,在kNN.py中创建函数datingClassTest,该函数是自包含的,可任何时候在Python运行环境中使用该函数测试分类器效果
注意:文本为datingTestSet.txt
"""
def datingClassTest():
# 测试集所占的比例
hoRatio = 0.10 # 将数据集中10%的数据留作测试用,其余的90%用于训练(设定用于测试的数据比例)
datingDataMat, datingLabels = file2matrix('datingTestSet.txt') # 从文件中读取数据
normMat, ranges, minVals = autoNorm(datingDataMat) # 对数据进行归一化
m = normMat.shape[0] # 求数据的条数(获取训练集行数)
# 计算测试向量的数量,决定normMat中哪些数据用于测试,哪些用于训练(用于测试的条目)
numTestVecs = int(m * hoRatio)# 求测试集的数据数目, 取10%的行数作为测试集
# 初始化错误率
errorCount = 0.0 # 定义误判数目
# 对测试数据进行遍历
for i in range(numTestVecs): # 循环读取每行数据
# 分类算法
classifierResult = classify0(normMat[i, :], normMat[numTestVecs:m, :], datingLabels[numTestVecs:m], 2)# 对每一条数据进行分类
# 输出分类结果和实际的类别
print
"the classifer came back with: %d,the real answer is: %d" % (classifierResult, datingLabels[i])
# 如果分类结果与实际结果不一致
if (classifierResult != datingLabels[i]):
errorCount += 1.0# 误分类数加1
# 输出错误率
print
"the total error rate is: %f" % (errorCount / float(numTestVecs))
# 对人分类
"""
注意文本数据为datingTestSet2.txt
"""
def classiyPerson():
# 定义分类结果的类别, #结果列表:不喜欢、魅力一般、极具魅力
resultList = ['not at all', 'in small doses', 'in large doses']
# 读取输入数据, 输入每年获得的飞行常客里程数
percentTats = float(raw_input("percentage of time spent playing video games?"))
# 读取输入数据,输入视频游戏所耗时间百分比
ffMiles = float(raw_input("frequent flier miles earned per year?"))
# 读取输入数据,输入每周消费的冰淇淋公升数
iceCream = float(raw_input("liters of ice cream consumed per year?"))
# 从文件中读取已有数据,调用文本转换矩阵函数
datingDataMat, datingLabels = file2matrix('datingTestSet2.txt')
# 对数据进行归一化,对数据进行归一化
normMat, ranges, minVals = autoNorm(datingDataMat)
# 将单个输入数据定义成一条数据,将输入数据写入数组中
inArr = array([ffMiles, percentTats, iceCream])
# 对输入数据进行分类,调用分类器
classifierResult = classify0(inArr, datingDataMat, datingLabels, 3)
# 输出预测的分类类别,这边减1是由于最后分类的数据是1,2,3对应到数组中是0,1,2
print
"You will probably like this person:", resultList[classifierResult - 1]
# 将单个手写字符文件变成向量
"""
将图片数据转换为01矩阵。
每张图片是32*32像素,也就是一共1024个字节。
因此转换的时候,每行表示一个样本,每个样本含1024个字节。
为了使用kNN模型,我们需要将图片转化为一个行向量。由于图片大小事32*32,
我们需要一个1*1024大小的行向量即可存储。
"""
def img2vector(filename):
# 创建向量
returnVect = zeros((1, 1024)) # 定义要返回的向量,创建1*24的numpy数组,每个样本数据是1024=32*32个字节
# 打开文件,读取每行内容
fr = open(filename)
# 遍历文件中的每一行和每一列,循环读取文件前32行,并将每行头32个字符存储在numpy数组中,最后返还给数组
for i in range(32):# 循环读取32行,32列。
# 每次读取一行
lineStr = fr.readline()
# 将每行前32字符转成int存入向量,对读取数据赋值到returnVect中
for j in range(32):
returnVect[0, 32 * i + j] = int(lineStr[j]) #将每行头32个字符值存在numpy数组中
# 返回向量
return returnVect
# 手写字符识别测试
"""
确保在文件起始位置加上from os import listdir功能是从os模块导入函数listdir
将trainingDigits目录中的文件内容存储在列表中,然后可以得到目录中有多少文件,并将其存储在变量m中。
接着,代码创建一个m行1024列的训练矩阵,该矩阵的每行数据存储一个图像。我们可以从文件名中解析出分类数字。
该目录下的文件按照规则命名,如文件9_45.txt的分类是9,它是数字9的第45个实例。然后我们可以将类代码存储在hwLabels向量中,
使用前面讨论的img2vector函数载入图像。在下一步中,我们对testDigits目录中的文件执行相似的操作,不同之处是我们并不将这个目录下的文件载入矩阵中,
而是使用classify0()函数测试该目录下的每个文件。
"""
def handwritingClassTest():
# =======================训练数据
"""样本数据的类标签列表"""
# 定义手写字符标签(类别)列表
hwLabels = [] #列表
# 列出目录下所有的文件(样本数据文件列表)
trainingFileList = listdir('digits/trainingDigits')# 加载训练数据,获取文件内容,将目录文件内容存储到列表
# 计算文件的数目
m = len(trainingFileList) #目录中有多少文件
# 初始化样本数据矩阵(M*1024)(定义手写字符数据矩阵)
trainingMat = zeros((m, 1024)) #创建m行,1024列训练矩阵,每行数据存储一个图像
# 依次读取每个文件(所有样本数据到数据矩阵)
for i in range(m):
"""提取文件名中的数字"""
# 定义文件名
fileNameStr = trainingFileList[i] # 从文件名中解析出当前图像的标签,也就是数字是几, 读取文件名,切割得到标签 从文件名称解析得到分类数据
# 对文件名进行分割
fileStr = fileNameStr.split('.')[0] # 0_13.txt是 0 的第十三个特例
# 获得文件名中的类标签
classNumStr = int(fileStr.split('_')[0]) # 0_13.txt 的分类是 0
# 把类标签放到hwLabels中
hwLabels.append(classNumStr)#将类代码存储在向量 hwLabels中
# 把文件变成向量并赋值到trainingMat中(将样本数据存入矩阵)
trainingMat[i, :] = img2vector('digits/trainingDigits/%s' % fileNameStr)#载入图像
# 列出测试目录下的所有文件
# =======================训练数据
testFileList = listdir('digits/testDigits')# 加载测试数据(循环读取测试数据)
# 初始化错误率
errorCount = 0.0
# 定义测试文件数目
mTest = len(testFileList)
# 遍历测试,循环测试每个测试数据文件
for i in range(mTest):
# 定义测试文件名(提取文件名中的数字)
fileNameStr = testFileList[i]
# 对测试文件名进行分割
fileStr = fileNameStr.split('.')[0]
# 获得测试文件的类标签
classNumStr = int(fileStr.split('_')[0])
# 将测试文件转换成向量(提取数据向量)
vectorUnderTest = img2vector('digits/testDigits/%s' % fileNameStr)
# 进行分类(对数据文件进行分类)
classifierResult = classify0(vectorUnderTest, trainingMat, hwLabels, 3)
# 输出预测类别和实际类别(打印KNN算法分类结果和真实的分类)
print
"the classifier came back with:%d,the real answer is %d" % (classifierResult, classNumStr)
# 如果二者不一致,累加错误数量
if (classifierResult != classNumStr):
errorCount += 1.0
# 输出分类错误的数目
print
"\nthe total number of errors is:%d" % errorCount
# 输出分类的错误率
print
"\nthe total error rate is:%f" % (errorCount / float(mTest))