最近开始看李航的《统计学习方法》,一段时间下来,虽然各种公式推导看起来吊的一 B,但始终没有在头脑中形成一个画面。觉得还是边看边着手实现更好。选择了《机器学习实战》这本书配合来实现。这本书使用 Python 实现,很适合我。真本书实现的第一个算法就是大名鼎鼎的 K-近邻算法。
KNN 算法的基本原理就是在一堆样本中找出与测试样本距离(欧氏距离/非欧距离)最近的几个样本,这些个样本属于哪个类,则测试样本就属于此类,解释起来非常简单,接下来就是实现了。
书中使用了两个例子来实现算法,约会数据和手写数字识别。
def createDataSet():
group = np.array([[1.0, 1.1], [1.0, 1.0], [0, 0], [0, 0.1]]) #生成一组样本
labels = ['A', 'A', 'B', 'B'] #样本类型
return group, labels
def classify0(inX, dataSet, labels, k):
# inX 测试点, dataSet: 训练样本集, labels 标签, k: 选取最近的 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()
classCount = dict()
for i in range(k):
voteIlabel = labels[sortedDistIndicies[i]]
classCount[voteIlabel] = classCount.get(voteIlabel, 0) + 1
sortedClassCount = sorted(classCount.iteritems(), key = operator.itemgetter(1), reverse = True)
return sortedClassCount[0][0] #返回投票最多的标签类型
使用构造的测试样本集测试 classify0
KNN 分类算法已经建立,开始实战!约会网站
我的朋友海伦一直使用在线约会网站寻找适合自己的约会对象。尽管约会网站会推荐不同的 人选,但她没有从中找到喜欢的人。经过一番总结,她发现曾交往过三种类型的人:
□ 不喜欢的人
□ 魅力一般的人
□ 极具魅力的人
尽管发现了上述规律,但海伦依然无法将约会网站推荐的匹配对象归人恰当的分类。她觉得 可以在周一到周五约会那些魅力一般的人,而周末则更喜欢与那些极具魅力的人为伴。海伦希望我们的分类软件可以更好地帮助她将匹配对象划分到确切的分类中。此外海伦还收集了一些约会 网站未曾记录的数据信息,她认为这些数据更有助于匹配对象的归类。
海伦的样本主要包含以下3种特征:
□ 每年获得的飞行常客里程数
□ 玩视频游戏所耗时间百分比
□ 每周消费的冰淇淋公升数
在将上述特征数据输人到分类器之前,必须将待处理数据的格式改变为分类器可以接受的格 式 。在kNN.py中创建名为file2matrix的函数,以此来处理输人格式问题。该函数的输人为文 件名字符串 输出为训练样本矩阵和类标签向量。
def autoNorm(dataSet):
'''
将数据集规范化,使所有数据为0-1之间的数字
'''
minVals = dataSet.min(0)
maxVals = dataSet.max(0)
ranges = maxVals - minVals
normDataSet = np.zeros(np.shape(dataSet))
m = dataSet.shape[0]
normDataSet = dataSet - np.tile(minVals, (m, 1))
normDataSet = normDataSet/np.tile(ranges, (m, 1))
return normDataSet, ranges, minVals
def file2matrix(filename):
'''
该函数将约会文件内容转换成数据处理格式,返回一个测试特征集(格式二维数组),和测试集类别集(格式列表)
'''
fr = open(filename)
arrayOLines = fr.readlines()
numberOfLines = len(arrayOLines) #get the number of lines in the file
returnMat = np.zeros((numberOfLines, 3)) #prepare matrix to return
classLabelVector = list() #prepare labels to return
index = 0
for line in arrayOLines:
line = line.strip()
listFromLine = line.split('\t')
returnMat[index, :] = listFromLine[0: 3]
classLabelVector.append(int(listFromLine[-1]))
index += 1
return returnMat, classLabelVector
接下来就该测试我们的约会数据了!
def datingClassTest(fileName, k):
#测试书中给出的数据集
hoRatio = 0.10 #分割测试比例
fileName = 'datingTestSet2.txt'
datingDataMat, datingLabels = file2matrix(fileName)
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], k)
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))
运行代码,结果错误率0.05.
下面再看一个更有意思的例子——识别手写数字。书中给出的数据已经将图像信息转化为矩阵。所以我们直接将这些矩阵转化为一维数组就可以开始测试了。
def img2vector(filename):
imgvect = np.zeros((1, 1024))
fr = open(filename)
for i in range(32):
linestr = fr.readline()
for j in range(32):
imgvect[0, 32*i + j] = int(linestr[j])
return imgvect
下边是测试过程代码
def handwritingClassTest(testFile, trainingFile, k):
testFile = 'digits/testDigits'
trainingFile = 'digits/trainingDigits'
hwLabels = []
trainingFileList = os.listdir(trainingFile)
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(trainingFile+fileNameStr)
testFileList = os.listdir('digits/testDigits')
errorCount = 0.0
mTest = len(testFileList)
for i in range(mTest):
fileNameStr = trainingFileList[i]
fileStr = fileNameStr.split('.')[0]
classNumStr = int(fileStr.split('_')[0])
try: #不知道什么原因,貌似我下载的测试样例不全。所以加个异常处理。
vectorUnderTest = img2vector(testFile + fileNameStr)
except IOError:
pass
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))
运行之结果为错误率0.0127,效果还不错。
今天就到这里吧,