diff --git a/docs/2.k-近邻算法.md b/docs/2.k-近邻算法.md index 708f98af..9bf47582 100644 --- a/docs/2.k-近邻算法.md +++ b/docs/2.k-近邻算法.md @@ -76,7 +76,7 @@ knn 算法按照距离最近的三部电影的类型,决定未知电影的类 ``` 收集数据:提供文本文件 准备数据:使用 Python 解析文本文件 -分析数据:使用 Matplotlib 画二维扩散图 +分析数据:使用 Matplotlib 画二维散点图 训练算法:此步骤不适用于 k-近邻算法 测试算法:使用海伦提供的部分数据作为测试样本。 测试样本和非测试样本的区别在于: @@ -94,21 +94,38 @@ knn 算法按照距离最近的三部电影的类型,决定未知电影的类 > 准备数据:使用 Python 解析文本文件 +将文本记录转换为 NumPy 的解析程序 ```Python def file2matrix(filename): + """ + Desc: + 导入训练数据 + parameters: + filename: 数据文件路径 + return: + 数据矩阵 returnMat 和对应的类别 classLabelVector + """ + fr = open(filename) + # 获得文件中的数据行的行数 + numberOfLines = len(fr.readlines()) + # 生成对应的空矩阵 + # 例如:zeros(2,3)就是生成一个 2*3的矩阵,各个位置上全是 0 + returnMat = zeros((numberOfLines, 3)) # prepare matrix to return + classLabelVector = [] # prepare labels return fr = open(filename) - arrayOLines = fr.readlines() - numberOfLines = len(arrayOLines) - returnMat = zeros((numberOfLines,3)) - classLabelVector = [] index = 0 - for line in arrayOLines: + for line in fr.readlines(): + # str.strip([chars]) --返回移除字符串头尾指定的字符生成的新字符串 line = line.strip() + # 以 '\t' 切割字符串 listFromLine = line.split('\t') - returnMat[index,:] = listFromLine[0:3] + # 每列的属性数据 + returnMat[index, :] = listFromLine[0:3] + # 每列的类别数据,就是 label 标签数据 classLabelVector.append(int(listFromLine[-1])) index += 1 - return returnMat,classLabelVector + # 返回数据矩阵returnMat和对应的类别classLabelVector + return returnMat, classLabelVector ``` > 分析数据:使用 Matplotlib 画二维散点图 @@ -118,18 +135,106 @@ import matplotlib import matplotlib.pyplot as plt fig = plt.figure() ax = fig.add_subplot(111) -ax.scatter(datingDataMat[:, 1], datingDataMat[:, 2]) +ax.scatter(datingDataMat[:, 1], datingDataMat[:, 2], 15.0*array(datingLabels), 15.0*array(datingLabels)) plt.show() ``` +下图中采用矩阵的第一和第三列属性得到很好的展示效果,清晰地标识了三个不同的样本分类区域,具有不同爱好的人其类别区域也不同。 + +![Matplotlib 散点图](/images/2.KNN/knn_matplotlib_2.png) + +> 准备数据: 归一化数据 + +归一化特征值,消除属性之间量级不同导致的影响 +```Python +def autoNorm(dataSet): + """ + Desc: + 归一化特征值,消除属性之间量级不同导致的影响 + parameter: + dataSet: 数据集 + return: + 归一化后的数据集 normDataSet. ranges和minVals即最小值与范围,并没有用到 + + 归一化公式: + Y = (X-Xmin)/(Xmax-Xmin) + 其中的 min 和 max 分别是数据集中的最小特征值和最大特征值。该函数可以自动将数字特征值转化为0到1的区间。 + """ + # 计算每种属性的最大值、最小值、范围 + minVals = dataSet.min(0) + maxVals = dataSet.max(0) + # 极差 + ranges = maxVals - minVals + normDataSet = zeros(shape(dataSet)) + m = dataSet.shape[0] + # 生成与最小值之差组成的矩阵 + normDataSet = dataSet - tile(minVals, (m, 1)) + # 将最小值之差除以范围组成矩阵 + normDataSet = normDataSet / tile(ranges, (m, 1)) # element wise divide + return normDataSet, ranges, minVals +``` + > 训练算法:此步骤不适用于 k-近邻算法 -> 测试算法:使用海伦提供的部分数据作为测试样本。 - 测试样本和非测试样本的区别在于: - 测试样本是已经完成分类的数据,如果预测分类与实际类别不同,则标记为一个错误。 +> 测试算法:使用海伦提供的部分数据作为测试样本。测试样本和非测试样本的区别在于:测试样本是已经完成分类的数据,如果预测分类与实际类别不同,则标记为一个错误。 + +kNN 分类器针对约会网站的测试代码 +```Python +def datingClassTest(): + """ + Desc: + 对约会网站的测试方法 + parameters: + none + return: + 错误数 + """ + # 设置测试数据的的一个比例(训练数据集比例=1-hoRatio) + hoRatio = 0.1 # 测试范围,一部分测试一部分作为样本 + # 从文件中加载数据 + datingDataMat, datingLabels = file2matrix('input/2.KNN/datingTestSet2.txt') # load data setfrom file + # 归一化数据 + normMat, ranges, minVals = autoNorm(datingDataMat) + # m 表示数据的行数,即矩阵的第一维 + m = normMat.shape[0] + # 设置测试的样本数量, numTestVecs:m表示训练样本的数量 + numTestVecs = int(m * hoRatio) + print 'numTestVecs=', numTestVecs + 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 +``` > 使用算法:产生简单的命令行程序,然后海伦可以输入一些特征数据以判断对方是否为自己喜欢的类型。 +约会网站预测函数 +```Python +def clasdifyPerson(): + 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 filer 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([ffMils, percentTats, iceCream]) + classifierResult = classify0((inArr-minVals)/ranges,normMat,datingLabels, 3) + print "You will probably like this person: ", resultList[classifierResult - 1] +``` + +实际运行效果如下: + +```Python +>>> kNN.classifyPerson() +percentage of time spent playing video games?10 +frequent flier miles earned per year?10000 +liters of ice cream consumed per year?0.5 +You will probably like this person: in small doses +``` [完整代码地址](https://github.com/apachecn/MachineLearning/blob/master/src/python/2.KNN/kNN.py): @@ -139,29 +244,35 @@ plt.show() ### 项目案例2: 手写数字识别系统 -> 概述 +#### 项目概述 构造一个能识别数字 0 到 9 的基于 KNN 分类器的手写识别系统。 需要识别的数字是存储在文本文件中的具有相同的色彩和大小:宽高是 32 像素 * 32 像素的黑白图像。 -> 使用 KNN 的手写识别系统 +#### 开发流程 ``` 收集数据:提供文本文件。 准备数据:编写函数 img2vector(), 将图像格式转换为分类器使用的向量格式 -分析数据:在 Python 命令提示符中检查数据,确保它符合要求。 +分析数据:在 Python 命令提示符中检查数据,确保它符合要求 训练算法:此步骤不适用于 KNN 测试算法:编写函数使用提供的部分数据集作为测试样本,测试样本与非测试样本的 区别在于测试样本是已经完成分类的数据,如果预测分类与实际类别不同, - 则标记为一个错误。 + 则标记为一个错误 使用算法:本例没有完成此步骤,若你感兴趣可以构建完整的应用程序,从图像中提取 - 数字,并完成数字识别,美国的邮件分拣系统就是一个实际运行的类似系统。 + 数字,并完成数字识别,美国的邮件分拣系统就是一个实际运行的类似系统 ``` +> 收集数据: 提供文本文件 + +目录 trainingDigits 中包含了大约 2000 个例子,每个例子内容如下图所示,每个数字大约有 200 个样本;目录 testDigits 中包含了大约 900 个测试数据。 + ![手写数字数据集的例子](/images/2.KNN/knn_2_handWriting.png) -* 将图像转换为向量 +> 准备数据: 编写函数 img2vector(), 将图像格式转换为分类器使用的向量格式 + +将图像转换为向量 ```Python def img2vector(filename): @@ -174,6 +285,56 @@ def img2vector(filename): return returnVect ``` +> 分析数据:在 Python 命令提示符中检查数据,确保它符合要求 + +在 Python 命令行中输入下列命令测试 img2vector 函数,然后与文本编辑器打开的文件进行比较: + +```Python +>>> testVector = kNN.img2vector('testDigits/0_13.txt') +>>> testVector[0,0:31] +array([0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 1., 1., 1., 1., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0.]) +>>> testVector[0,31:63] +array([0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 1., 1., 1., 1., 1., 1., 1., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0.]) +``` + +> 训练算法:此步骤不适用于 KNN + +> 测试算法:编写函数使用提供的部分数据集作为测试样本,测试样本与非测试样本的区别在于测试样本是已经完成分类的数据,如果预测分类与实际类别不同,则标记为一个错误 + +```Python +def handwritingClassTest(): + # 1. 导入数据 + hwLabels = [] + trainingFileList = listdir('input/2.KNN/trainingDigits') # load the training set + m = len(trainingFileList) + trainingMat = zeros((m, 1024)) + # hwLabels存储0~9对应的index位置, trainingMat存放的每个位置对应的图片向量 + for i in range(m): + fileNameStr = trainingFileList[i] + fileStr = fileNameStr.split('.')[0] # take off .txt + classNumStr = int(fileStr.split('_')[0]) + hwLabels.append(classNumStr) + # 将 32*32的矩阵->1*1024的矩阵 + trainingMat[i, :] = img2vector('input/2.KNN/trainingDigits/%s' % fileNameStr) + + # 2. 导入测试数据 + testFileList = listdir('input/2.KNN/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('input/2.KNN/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)) +``` + +> 使用算法:本例没有完成此步骤,若你感兴趣可以构建完整的应用程序,从图像中提取数字,并完成数字识别,美国的邮件分拣系统就是一个实际运行的类似系统 + [完整代码地址](https://github.com/apachecn/MachineLearning/blob/master/src/python/2.KNN/kNN.py): * * * diff --git a/images/2.KNN/knn_matplotlib_2.png b/images/2.KNN/knn_matplotlib_2.png new file mode 100644 index 00000000..c805ad02 Binary files /dev/null and b/images/2.KNN/knn_matplotlib_2.png differ