转载请注明作者和出处:http://blog.csdn.net/u013829973
系统版本:window 7 (64bit)
python版本:python 3.5
IDE:Spyder (一个比较方便的办法是安装anaconda,那么Spyder和jupyter以及python几个常用的包都有了,甚至可以方便的安装TensorFlow等,安装方法链接)
实战一:k近邻算法改进约会网站的配对效果
案例背景:海伦同学一直使用在线约会网站寻找适合自己的约会对象。尽管约会网站会推荐不同的任选,但她并不是喜欢每一个人。经过一番总结,她发现自己交往过的人可以进行如下分类:
1.不喜欢的人
2. 魅力一般的人
3. 极具魅力的人
尽管发现了上述规律,但是海伦依然无法将约会网站推荐的匹配对象归入恰当的分类。海伦收集了一些约会数据信息,并存放在文本文件datingTestSet.txt中,每行一个样本,每个样本3个特征和一个类别,数据集维数1000x3 ,三个特征如下:
1.每年获得的飞行常客里程数
2.玩视频游戏所消耗时间百分比
3.每周消费的冰淇淋公升数
下面进入实战,主要分为以下几个步骤:数据集和完整代码
1.1 准备数据:从文本文件中解析数据
将得到的txt文本文件转换成训练样本矩阵和标签向量,代码如下:
import numpy as np
import operator
'''
将文本程序转化为numpy矩阵
Input: filename: 文件名
Output: returnMat: 特征矩阵
classLabelVector:标签向量
'''
def file2matrix(filename):
fr = open(filename)
arrayOLines = fr.readlines()
numberOfLines = len(arrayOLines) #得到文件行数
returnMat = np.zeros((numberOfLines,3)) #创建返回的numpy矩阵
classLabelVector = [] #类别标签初始化
index = 0
for line in arrayOLines:
line = line.strip() #截取掉所有的回车字符
listFromLine = line.split('\t') #使用tab字符\t将上一行得到的整行数据分割成一个元素列表
returnMat[index,:] = listFromLine[0:3]#截取前三个元素,存储到特征矩阵中
if listFromLine[-1] == 'largeDoses': #极具魅力的人记为1
classLabelVector.append(1)
if listFromLine[-1] == 'smallDoses': #极具魅力的人记为2
classLabelVector.append(2)
if listFromLine[-1] == 'didntLike': #极具魅力的人记为3
classLabelVector.append(3)
index += 1
return returnMat,classLabelVector
'''
主程序
'''
if __name__ =='__main__':
datingDataMat,datingLabels = file2matrix('datingTestSet.txt')
print('datingDataMat:\n',datingDataMat)
print('datingLabels\n',datingLabels)
程序运行结果:
这样打印出来的结果是一堆数字,根本观察不出什么规律,因此,我们接下来对数据可视化,以便直观形象的观察出规律。
1.2分析数据:创建散点图
编写可视化函数:
import numpy as np
import matplotlib
import matplotlib.pyplot as plt
'''
可视化程序
'''
def datavisualization(datingDataMat,datingLabels):
zhfont = matplotlib.font_manager.FontProperties(fname=r'c:\windows\fonts\simsun.ttc')#设置中文字体路径
fig = plt.figure(figsize=(13,8))#新建画布,并定义大小
ax1 = fig.add_subplot(221)#画布切分成2x2,第一个位置添加子图
ax1.scatter(datingDataMat[:,1], datingDataMat[:,2], 15.0*np.array(datingLabels), 15.0*np.array(datingLabels))
ax1.axis([-2,25,-0.2,2.0])
#利用datingLabel变量,绘制色彩不等,尺寸不同的点;指定坐标轴范围
plt.xlabel('玩视频游戏所消耗时间占百分比',fontproperties=zhfont)#横轴标签
plt.ylabel('每周消费的冰激凌公升数',fontproperties=zhfont) #纵轴标签
ax2 = fig.add_subplot(222)#在画布上添加第二个子图
ax2.scatter(datingDataMat[:,0], datingDataMat[:,1], 15.0*np.array(datingLabels), 15.0*np.array(datingLabels))
ax2.axis([-5000,100000,-2,23])
plt.xlabel('每年获得的飞行常客里程数',fontproperties=zhfont)#横轴标签
plt.ylabel('玩视频游戏所消耗时间占百分比',fontproperties=zhfont)#纵轴标签
ax3 = fig.add_subplot(223)#在画布上添加第三个子图
ax3.scatter(datingDataMat[:,0], datingDataMat[:,2], 15.0*np.array(datingLabels), 15.0*np.array(datingLabels))
ax3.axis([-5000,100000,-0.2,2.0])
plt.xlabel('每年获得的飞行常客里程数',fontproperties=zhfont)#横轴标签
plt.ylabel('每周消费的冰激凌公升数',fontproperties=zhfont)#纵轴标签
plt.show()
'''
主程序
'''
if __name__ =='__main__':
datingDataMat,datingLabels = file2matrix('datingTestSet.txt')
print('datingDataMat:\n',datingDataMat)
print('datingLabels:\n',datingLabels)
datavisualization(datingDataMat,datingLabels)
程序运行结果:
通过可视化数据,可以看出,训练数据的列1和列2(也就是图中的第二个子图)得到了较好的效果,清晰的标识了三个不同类别的样本分类区域,具有不同爱好的人其类别区域也不一样。
1.3 准备数据:归一化数值
下表给出了提取的四组数据,如果想要计算样本3和样本4之间的距离,可以使用下面方法:
我们很容易发现,上面方程中的数字差值最大的属性对计算结果影响最大,也就是说差值最大对应的特征对计算结果的影响远远大于其他两个特征的影响。但是海伦认为三个同等重要,这个最大的特征不应该严重影响计算结果。
序号 | 玩视频游戏所耗时间百分比 | 每年飞行常客里程数 | 每周消费的冰激凌公升数 | 样本分类 |
---|---|---|---|---|
1 | 0.8 | 400 | 0.5 | 1 |
2 | 12 | 13400 | 0.9 | 3 |
3 | 0 | 20000 | 1.1 | 2 |
4 | 67 | 32000 | 0.1 | 4 |
在处理这种不同取值范围的特征值的时候,我们通常采用的方法是将数值归一化,如将取值范围处理为0到1或者-1到1之间。下面公式是将任意取值范围的特征值转化为0到1之间:
newValue = (OolValue - min )/(max - min)
下面给出归一化程序:
'''
函数功能:归一化特征值
Input: dataSet: 特征矩阵
Output: normDataSet: 归一化后的特征矩阵
ranges: 取值范围(最大值与最小值之差)
minVals: 最小值
'''
def autoNorm(dataSet):
minVals = dataSet.min(0)#从列中选取最小值
maxVals = dataSet.max(0)#从列中选取最大值
ranges = maxVals - minVals#计算可能的取值范围
normDataSet = np.zeros(np.shape(dataSet))#初始化矩阵,维数(样本数x特征数)
m = dataSet.shape[0]#获取dataSet的行数
normDataSet = dataSet - np.tile(minVals, (m,1)) #归一化公式的分子
normDataSet = normDataSet/np.tile(ranges, (m,1)) #归一化公式
return normDataSet, ranges, minVals
1.4 测试算法:作为完整程序验证分类器
本节将测试分类效果,如果分类器的正确率满足要求,海伦就可以使用这个软件来处理约会的网站提高的约会名单了。机器学习的一个重要工作就是评估算法的正确率,通常我们将已提供数据的90%作为训练集,剩下的10%作为测试集(需要注意的是10%的数据应该随机选择)
下面我们给出python程序清单:
'''
分类器对约会网站的测试函数
'''
def datingClassTest():
hoRatio = 0.10 #将数据集且分为训练集和测试集的比例
datingDataMat,datingLabels = file2matrix('datingTestSet.txt') #加载原始数据集
normMat, ranges, minVals = autoNorm(datingDataMat)#归一化
m = normMat.shape[0] #取矩阵行数
numTestVecs = int(m*hoRatio) #取整
errorCount = 0.0 #分错的个数
for i in range(numTestVecs):
classifierResult = classify0(normMat[i,:],normMat[numTestVecs:m,:],datingLabels[numTestVecs:m],3)
print ('the classifier came back with: %d, the real answer is: %d' % (classifierResult, datingLabels[i]))
if (classifierResult != datingLabels[i]): errorCount += 1.0
#计算分错的样本数
print ('the total error rate is: %f' % (errorCount/float(numTestVecs)))
print ('分错的个数:',errorCount)
程序运行结果:
图1.4 分类器测试结果
改变程序的hoRatio变量和k值,错误率也会发生改变。
1.5 使用算法:构建完整系统
我们给海伦一个小程序,通过该程序海伦会在约会网站上找到某个人并输入他的信息,程序会给出她对对方喜欢程度的预测值。
预测函数代码:
'''
函数功能:通过输入一个人的三维特征,进行分类输出
Input:
Output:
'''
def classifyPerson():
#输出结果
resultList = ['讨厌','有些喜欢','非常喜欢']
#三维特征用户输入
precentTats = float(input("玩视频游戏所耗时间百分比:"))
ffMiles = float(input("每年获得的飞行常客里程数:"))
iceCream = float(input("每周消费的冰激淋公升数:"))
#打开的文件名
filename = "datingTestSet.txt"
#打开并处理数据
datingDataMat, datingLabels = file2matrix(filename)
#训练集归一化
normMat, ranges, minVals = autoNorm(datingDataMat)
#生成NumPy数组,测试集
inArr = np.array([precentTats, ffMiles, iceCream])
#测试集归一化
norminArr = (inArr - minVals) / ranges
#返回分类结果
classifierResult = classify0(norminArr, normMat, datingLabels, 3)
#打印结果
print("你可能%s这个人" % (resultList[classifierResult-1]))
接下来,我们给出上述海伦例子的完整程序:
'''
Created on Sep 10, 2017
kNN: k近邻(k Nearest Neighbors)
实战:使用k近邻算法改进约会网站的配对效果
author:weepon
'''
import numpy as np
import operator
import matplotlib
import matplotlib.pyplot as plt
'''
k近邻算法
Input: inX: 测试集 (1xN)
dataSet: 已知数据的特征(NxM)
labels: 已知数据的标签或类别(1xM vector)
k: k近邻算法中的k
Output: 测试样本最可能所属的标签
'''
def classify0(inX, dataSet, labels, k):
dataSetSize = dataSet.shape[0] # shape[0]返回dataSet的行数
diffMat = np.tile(inX, (dataSetSize,1)) - dataSet # tile(inX,(a,b))函数将inX重复a行,重复b列
sqDiffMat = diffMat**2 #作差后平方
sqDistances = sqDiffMat.sum(axis=1)#sum()求和函数,sum(0)每列所有元素相加,sum(1)每行所有元素相加
distances = sqDistances**0.5 #开平方,求欧式距离
sortedDistIndicies = distances.argsort() #argsort函数返回的是数组值从小到大的索引值
classCount={}
for i in range(k):
voteIlabel = labels[sortedDistIndicies[i]] #取出前k个距离对应的标签
classCount[voteIlabel] = classCount.get(voteIlabel,0) + 1#计算每个类别的样本数。字典get()函数返回指定键的值,如果值不在字典中返回默认值0
sortedClassCount = sorted(classCount.items(), key=operator.itemgetter(1), reverse=True)
#reverse降序排列字典
#python2版本中的iteritems()换成python3的items()
#key=operator.itemgetter(1)按照字典的值(value)进行排序
#key=operator.itemgetter(0)按照字典的键(key)进行排序
return sortedClassCount[0][0] #返回字典的第一条的key,也即是测试样本所属类别
'''
将文本程序转化为numpy矩阵
Input: filename: 文件名
Output: returnMat: 特征矩阵
classLabelVector:标签向量
'''
def file2matrix(filename):
fr = open(filename)
arrayOLines = fr.readlines()
numberOfLines = len(arrayOLines) #得到文件行数
returnMat = np.zeros((numberOfLines,3)) #创建返回的numpy矩阵
classLabelVector = [] #类别标签初始化
index = 0
for line in arrayOLines:
line = line.strip() #截取掉所有的回车字符
listFromLine = line.split('\t') #使用tab字符\t将上一行得到的整行数据分割成一个元素列表
returnMat[index,:] = listFromLine[0:3]#截取前三个元素,存储到特征矩阵中
if listFromLine[-1] == 'largeDoses': #极具魅力的人记为1
classLabelVector.append(1)
if listFromLine[-1] == 'smallDoses': #极具魅力的人记为2
classLabelVector.append(2)
if listFromLine[-1] == 'didntLike': #极具魅力的人记为3
classLabelVector.append(3)
index += 1
return returnMat,classLabelVector
'''
可视化程序
'''
def datavisualization(datingDataMat,datingLabels):
zhfont = matplotlib.font_manager.FontProperties(fname=r'c:\windows\fonts\simsun.ttc')#设置中文字体路径
fig = plt.figure(figsize=(13,8))#新建画布,并定义大小
ax1 = fig.add_subplot(221)#画布切分成2x2,第一个位置添加子图
ax1.scatter(datingDataMat[:,1], datingDataMat[:,2], 15.0*np.array(datingLabels), 15.0*np.array(datingLabels))
ax1.axis([-2,25,-0.2,2.0])#利用datingLabel变量,绘制色彩不等,尺寸不同的点;指定坐标轴范围
plt.xlabel('玩视频游戏所消耗时间占百分比',fontproperties=zhfont)#横轴标签
plt.ylabel('每周消费的冰激凌公升数',fontproperties=zhfont) #纵轴标签
ax2 = fig.add_subplot(222)#在画布上添加第二个子图
ax2.scatter(datingDataMat[:,0], datingDataMat[:,1], 15.0*np.array(datingLabels), 15.0*np.array(datingLabels))
ax2.axis([-5000,100000,-2,23])
plt.xlabel('每年获得的飞行常客里程数',fontproperties=zhfont)#横轴标签
plt.ylabel('玩视频游戏所消耗时间占百分比',fontproperties=zhfont)#纵轴标签
ax3 = fig.add_subplot(223)#在画布上添加第三个子图
ax3.scatter(datingDataMat[:,0], datingDataMat[:,2], 15.0*np.array(datingLabels), 15.0*np.array(datingLabels))
ax3.axis([-5000,100000,-0.2,2.0])
plt.xlabel('每年获得的飞行常客里程数',fontproperties=zhfont)#横轴标签
plt.ylabel('每周消费的冰激凌公升数',fontproperties=zhfont)#纵轴标签
plt.show()
'''
函数功能:归一化特征值
Input: dataSet: 特征矩阵
Output: normDataSet: 归一化后的特征矩阵
ranges: 取值范围(最大值与最小值之差)
minVals: 最小值
'''
def autoNorm(dataSet):
minVals = dataSet.min(0)#从列中选取最小值
maxVals = dataSet.max(0)#从列中选取最大值
ranges = maxVals - minVals#计算可能的取值范围
normDataSet = np.zeros(np.shape(dataSet))#初始化矩阵,维数(样本数x特征数)
m = dataSet.shape[0]#获取dataSet的行数
normDataSet = dataSet - np.tile(minVals, (m,1)) #归一化公式的分子
normDataSet = normDataSet/np.tile(ranges, (m,1)) #归一化公式
return normDataSet, ranges, minVals
'''
分类器对约会网站的测试函数
'''
def datingClassTest():
hoRatio = 0.10 #将数据集且分为训练集和测试集的比例
datingDataMat,datingLabels = file2matrix('datingTestSet.txt') #加载原始数据集
normMat, ranges, minVals = autoNorm(datingDataMat)#归一化
m = normMat.shape[0] #取矩阵行数
numTestVecs = int(m*hoRatio) #取整
errorCount = 0.0 #分错的个数
for i in range(numTestVecs):
classifierResult = classify0(normMat[i,:],normMat[numTestVecs:m,:],datingLabels[numTestVecs:m],3)
print ('the classifier came back with: %d, the real answer is: %d' % (classifierResult, datingLabels[i]))
if (classifierResult != datingLabels[i]): errorCount += 1.0 #计算分错的样本数
print ('the total error rate is: %f' % (errorCount/float(numTestVecs)))
print ('分错的个数:',errorCount)
'''
函数功能:通过输入一个人的三维特征,进行分类输出
Input:
Output:
'''
def classifyPerson():
#输出结果
resultList = ['讨厌','有些喜欢','非常喜欢']
#三维特征用户输入
precentTats = float(input("玩视频游戏所耗时间百分比:"))
ffMiles = float(input("每年获得的飞行常客里程数:"))
iceCream = float(input("每周消费的冰激淋公升数:"))
#打开的文件名
filename = "datingTestSet.txt"
#打开并处理数据
datingDataMat, datingLabels = file2matrix(filename)
#训练集归一化
normMat, ranges, minVals = autoNorm(datingDataMat)
#生成NumPy数组,测试集
inArr = np.array([precentTats, ffMiles, iceCream])
#测试集归一化
norminArr = (inArr - minVals) / ranges
#返回分类结果
classifierResult = classify0(norminArr, normMat, datingLabels, 3)
#打印结果
print("你可能%s这个人" % (resultList[classifierResult-1]))
'''
主程序
'''
if __name__ =='__main__':
datingDataMat,datingLabels = file2matrix('datingTestSet.txt')
print('datingDataMat:\n',datingDataMat)
print('datingLabels:\n',datingLabels)
datavisualization(datingDataMat,datingLabels)
datingClassTest()
classifyPerson()
程序运行结果:
实战二:手写识别系统
2.1 准备数据 (数据集和完整代码)
需要识别的数字已经使用图形处理软件,处理成具有相同的色彩和大小:宽高是32像素x32像素。尽管采用本文格式存储图像不能有效地利用内存空间,但是为了方便理解,我们将图片转换为文本格式。目录trainingDigits中包含了大约2000个例子,每个例子的内容如图2.1,每个数字大约有200个样本
我们可以将32x32的二进制图像转换为1x1024的向量,这样就可以使用前面编写的分类器进行分类了。
函数如下:
'''
函数功能:将32x32的二进制图像转换为1x1024向量
Input: filename :文件名
Output: 二进制图像的1x1024向量
'''
def img2vector(filename):
returnVect = np.zeros((1,1024)) #创建空numpy数组
fr = open(filename) #打开文件
for i in range(32):
lineStr = fr.readline() #读取每一行内容
for j in range(32):
returnVect[0,32*i+j] = int(lineStr[j])#将每行前32个字符值存储在numpy数组中
return returnVect
2.2 测试算法
测试函数代码:
'''
函数功能:手写数字分类测试
'''
def handwritingClassTest():
hwLabels = []
trainingFileList = listdir('trainingDigits') #加载训练集
m = len(trainingFileList) #计算文件夹下文件的个数,因为每一个文件是一个手写体数字
trainingMat = np.zeros((m,1024)) #初始化训练向量矩阵
for i in range(m):
fileNameStr = trainingFileList[i] #获取文件名
fileStr = fileNameStr.split('.')[0] #从文件名中解析出分类的数字
classNumStr = int(fileStr.split('_')[0])
hwLabels.append(classNumStr)
trainingMat[i,:] = img2vector('trainingDigits/%s' % fileNameStr)
testFileList = listdir('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('testDigits/%s' % fileNameStr)
classifierResult = classify0(vectorUnderTest, trainingMat, hwLabels, 3) #开始分类
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)))
2.3 完整代码
整合上面的代码,我们运行以下完整的代码
'''
Created on Sep 10, 2017
kNN: k近邻(k Nearest Neighbors)
实战:手写识别系统
author:weepon
'''
import numpy as np
import operator
from os import listdir
'''
k近邻算法
Input: inX: 测试集 (1xN)
dataSet: 已知数据的特征(NxM)
labels: 已知数据的标签或类别(1xM vector)
k: k近邻算法中的k
Output: 测试样本最可能所属的标签
'''
def classify0(inX, dataSet, labels, k):
dataSetSize = dataSet.shape[0] # shape[0]返回dataSet的行数
diffMat = np.tile(inX, (dataSetSize,1)) - dataSet # tile(inX,(a,b))函数将inX重复a行,重复b列
sqDiffMat = diffMat**2 #作差后平方
sqDistances = sqDiffMat.sum(axis=1)#sum()求和函数,sum(0)每列所有元素相加,sum(1)每行所有元素相加
distances = sqDistances**0.5 #开平方,求欧式距离
sortedDistIndicies = distances.argsort() #argsort函数返回的是数组值从小到大的索引值
classCount={}
for i in range(k):
voteIlabel = labels[sortedDistIndicies[i]] #取出前k个距离对应的标签
classCount[voteIlabel] = classCount.get(voteIlabel,0) + 1#计算每个类别的样本数。字典get()函数返回指定键的值,如果值不在字典中返回默认值0
sortedClassCount = sorted(classCount.items(), key=operator.itemgetter(1), reverse=True)
#reverse降序排列字典
#python2版本中的iteritems()换成python3的items()
#key=operator.itemgetter(1)按照字典的值(value)进行排序
#key=operator.itemgetter(0)按照字典的键(key)进行排序
return sortedClassCount[0][0] #返回字典的第一条的key,也即是测试样本所属类别
'''
函数功能:将32x32的二进制图像转换为1x1024向量
Input: filename :文件名
Output: 二进制图像的1x1024向量
'''
def img2vector(filename):
returnVect = np.zeros((1,1024)) #创建空numpy数组
fr = open(filename) #打开文件
for i in range(32):
lineStr = fr.readline() #读取每一行内容
for j in range(32):
returnVect[0,32*i+j] = int(lineStr[j])#将每行前32个字符值存储在numpy数组中
return returnVect
'''
函数功能:手写数字分类测试
'''
def handwritingClassTest():
hwLabels = []
trainingFileList = listdir('trainingDigits') #加载训练集
m = len(trainingFileList) #计算文件夹下文件的个数,因为每一个文件是一个手写体数字
trainingMat = np.zeros((m,1024)) #初始化训练向量矩阵
for i in range(m):
fileNameStr = trainingFileList[i] #获取文件名
fileStr = fileNameStr.split('.')[0] #从文件名中解析出分类的数字
classNumStr = int(fileStr.split('_')[0])
hwLabels.append(classNumStr)
trainingMat[i,:] = img2vector('trainingDigits/%s' % fileNameStr)
testFileList = listdir('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('testDigits/%s' % fileNameStr)
classifierResult = classify0(vectorUnderTest, trainingMat, hwLabels, 3) #开始分类
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)))
'''
主函数
'''
if __name__ == '__main__':
handwritingClassTest()
运行结果:
如有不当之处,请留言指出,谢谢!