KNN实战 —— 约会网站配对效果判定

(公众号:落叶归根的猪。获取更多资源干货,交个朋友也可)

二、k-近邻算法实战之约会网站配对效果判定

上一小结学习了简单的k-近邻算法的实现方法,但是这并不是完整的k-近邻算法流程,k-近邻算法的一般流程:

    1. 收集数据:可以使用爬虫进行数据的收集,也可以使用第三方提供的免费或收费的数据。一般来讲,数据放在txt文本文件中,按照一定的格式进行存储,便于解析及处理。

    2. 准备数据:使用Python解析、预处理数据。

    3. 分析数据:可以使用很多方法对数据进行分析,例如使用Matplotlib将数据可视化。

    4. 测试算法:计算错误率。

    5. 使用算法:错误率在可接受范围内,就可以运行k-近邻算法进行分类。

已经了解了k-近邻算法的一般流程,下面开始进入实战内容。

 

1、实战背景

海伦女士一直使用在线约会网站寻找适合自己的约会对象。尽管约会网站会推荐不同的任选,但她并不是喜欢每一个人。经过一番总结,她发现自己交往过的人可以进行如下分类:

    1. 不喜欢的人

    2. 魅力一般的人

    3. 极具魅力的人

海伦收集约会数据已经有了一段时间,她把这些数据存放在文本文件datingTestSet.txt中,每个样本数据占据一行,总共有1000行。datingTestSet.txt数据下载: 数据集下载

海伦收集的样本数据主要包含以下3种特征:

    1. 每年获得的飞行常客里程数

    2. 玩视频游戏所消耗时间百分比

    3. 每周消费的冰淇淋公升数

这里不得不吐槽一句,海伦是个小吃货啊,冰淇淋公斤数都影响自己择偶标准。打开txt文本文件,数据格式如图所示。

 

2、准备数据:数据解析

    在将上述特征数据输入到分类器前,必须将待处理的数据的格式改变为分类器可以接收的格式。分类器接收的数据是什么格式的?从上小结已经知道,要将数据分类两部分,即特征矩阵和对应的分类标签向量。在kNN_test02.py文件中创建名为file2matrix的函数,以此来处理输入格式问题。 将datingTestSet.txt放到与kNN_test02.py相同目录下,编写代码如下:

# -*- coding: UTF-8 -*-

import numpy as np

"""

函数说明:打开并解析文件,对数据进行分类:1代表不喜欢,2代表魅力一般,3代表极具魅力

Parameters:

    filename - 文件名

Returns:

    returnMat - 特征矩阵

    classLabelVector - 分类Label向量

"""

def file2matrix(filename):

 

    # 打开文件

    fr = open(filename)

    # 读取文件所有内容

    arrayOLines = fr.readlines()  # [‘40920\t7.32\t0.95\n’,...‘’]

 

    # 得到文件行数

    numberOfLines = len(arrayOLines)

 

    #返回的NumPy矩阵,解析完成的数据:numberOfLines行,3列

    returnMat = np.zeros((numberOfLines,3))

    #返回的分类标签向量

    classLabelVector = []

    #行的索引值

    index = 0

 

    for line in arrayOLines:

 

        # ['40920\t8.326976\t0.953952\tlargeDoses\n',...'']

        # s.strip(rm),当rm空时,默认删除空白符(包括'\n','\r','\t',' ')

        line = line.strip()

        # 40920  8.326976  0.953952  largeDoses

        # 使用s.split(str="",num=string,cout(str))将字符串根据'\t'分隔符进行切片。

        listFromLine = line.split('\t')

        # ['40920', '8.326976', '0.953952', 'largeDoses']

        # 将数据前三列提取出来,存放到returnMat的NumPy矩阵中,也就是特征矩阵

        returnMat[index,:] = listFromLine[0:3]

        #根据文本中标记的喜欢的程度进行分类,1代表不喜欢,2代表魅力一般,3代表极具魅力

        if listFromLine[-1] == 'didntLike':

            classLabelVector.append(1)

        elif listFromLine[-1] == 'smallDoses':

            classLabelVector.append(2)

        elif listFromLine[-1] == 'largeDoses':

            classLabelVector.append(3)

        index += 1

    return returnMat, classLabelVector

"""

函数说明:main函数

Parameters:

    无

Returns:

    无

"""

if __name__ == '__main__':

    #打开的文件名

    filename = "/home/anaconda2/桌面/机器学习/算法/KNN/DatingTestSet.txt"

    #打开并处理数据

    datingDataMat, datingLabels = file2matrix(filename)

    print(datingDataMat)

    print(datingLabels)

 

 

3、分析数据:数据可视化

在kNN_test02.py文件中编写名为showdatas的函数,用来将数据可视化。编写代码如下:

 

 

4、准备数据:数据归一化

表2.1给出了四组样本,如果想要计算样本3和样本4之间的距离,可以使用欧式距离公式计算。

newValue = (oldValue - min) / (max - min)

其中min和max分别是数据集中的最小特征值和最大特征值。虽然改变数值取值范围增加了分类器的复杂度,但为了得到准确结果,我们必须这样做。

def autoNorm(dataSet):

 

    # 获得数据的最小值

    # min(0)返回该矩阵中每一列的最小值, min(1)返回该矩阵中每一行的最小值

    # max(0)返回该矩阵中每一列的最大值, max(1)返回该矩阵中每一行的最大值

    minVals = dataSet.min(0)

    maxVals = dataSet.max(0)

    # 最大值和最小值的范围

    ranges = maxVals -minVals 

 

    # shape(dataSet)返回dataSet的矩阵行列数

    normDataSet = np.zeros(np.shape(dataSet)) # (1000,3)

    # 返回dataSet的行数 

    m = dataSet.shape[0] # 1000 

 

    #原始值减去最小值

    normDataSet = dataSet - np.tile(minVals, (m, 1))

    #除以最大和最小值的差,得到归一化数据

    normDataSet = normDataSet / np.tile(ranges, (m, 1))

    #返回归一化数据结果,数据范围,最小值

    return normDataSet, ranges, minVals

 

 

5、测试算法:验证分类器

机器学习算法一个很重要的工作就是评估算法的正确率,通常我们只提供已有数据的90%作为训练样本来训练分类器,而使用其余的10%数据去测试分类器,检测分类器的正确率。需要注意的是,10%的测试数据应该是随机选择的,由于海伦提供的数据并没有按照特定目的来排序,所以我们可以随意选择10%数据而不影响其随机性。

为了测试分类器效果,在kNN_test02.py文件中创建函数datingClassTest,编写代码如下:

def classify0(inX, dataSet, labels, k):

 

    # numpy函数shape[0]返回dataSet的行数

    dataSetSize = dataSet.shape[0]

    # 在列向量方向上重复inX共1次(横向),行向量方向上重复inX共dataSetSize次(纵向)

    diffMat = np.tile(inX, (dataSetSize, 1)) - dataSet

    # 二维特征相减后平方

    sqDiffMat = diffMat**2

    # sum()所有元素相加,sum(0)列相加,sum(1)行相加

    sqDistances = sqDiffMat.sum(axis=1)

    # 开方,计算出距离

    distances = sqDistances**0.5

 

    # 返回distances中元素从小到大排序后的索引值

    sortedDistIndices = distances.argsort() 

    # 比如sortedDistIndices[0] = 360,意味着第零位的值为360。360意味着从小到大排序后最小值在第360位

 

    # 定一个记录类别次数的字典

    classCount = {}    # {[1]:"", [2]:"", ......}

 

    for i in range(k):

        #取出前k个元素的类别

        # sortedDistIndices[0]=360,距离最小值排在第360位。 sortedDistIndices[0]=360, labels[360] = '1','1' = 'didntlike'

        voteIlabel = labels[sortedDistIndices[i]]   

        # dict.get(key,default=None),字典的get()方法,返回指定键的值,如果值不在字典中返回默认值。

        # 计算类别次数

        classCount[voteIlabel] = classCount.get(voteIlabel,0) + 1

 

    # python中字典的get方法,其中有两个参数,表示如果第一个值是字典的键,那么返回该键对应的值,

    # 如果该值不是字典的键,那么返回第二个值,如果只有第一个值时,表示取该键对应的值,如果该键不存在则返回None

    print("voting result:\n",classCount)

 

    # python3中用items()替换python2中的iteritems()

    # key=operator.itemgetter(1)根据字典的值进行排序

    # key=operator.itemgetter(0)根据字典的键进行排序

    # reverse降序排序字典

    # A.sort()|sorted(A). sort是会改变原来list,sorted不会改变原来list.

    sortedClassCount = sorted(classCount.items(),key=operator.itemgetter(1),reverse=True)

    # 返回次数最多的类别,即所要分类的类别

    return sortedClassCount[0][0]

 

def datingClassTest():

    # 打开的文件名

    filename = "/home/anaconda2/桌面/机器学习/算法/KNN/DatingTestSet.txt"

    # 将返回的特征矩阵和分类向量分别存储到datingDataMat和datingLabels中

    datingDataMat, datingLabels = file2matrix(filename)

    # 取所有数据的百分之十

    hoRatio = 0.10

 

    # 数据归一化,返回归一化后的矩阵,数据范围,数据最小值

    normMat, ranges, minVals = autoNorm(datingDataMat)

    # 获得normMat的行数

    m = normMat.shape[0]

    # 百分之十的测试数据的个数

    numTestVecs = int(m * hoRatio)

    # 分类错误计数

    errorCount = 0.0

    for i in range(numTestVecs):

        # 前numTestVecs个数据作为测试集,后m-numTestVecs个数据作为训练集

        classifierResult = classify0(normMat[i,:], normMat[numTestVecs:m,:],

            datingLabels[numTestVecs:m], 4)

 

        print("分类结果:%d\t真实类别:%d" % (classifierResult, datingLabels[i]))

        if classifierResult != datingLabels[i]:

            errorCount += 1.0

    print("错误率:%f%%" %(errorCount/float(numTestVecs)*100))

 

-------------------------------------------------------------------------------------

完整全代码:

# -*- coding: UTF-8 -*-

import numpy as np

import operator

from matplotlib.font_manager import FontProperties

import matplotlib.lines as mlines

import matplotlib.pyplot as plt

"""

函数说明:打开并解析文件,对数据进行分类:1代表不喜欢,2代表魅力一般,3代表极具魅力

Parameters:

    filename - 文件名

Returns:

    returnMat - 特征矩阵

    classLabelVector - 分类Label向量

"""

def file2matrix(filename):

 

    # 打开文件

    fr = open(filename)

    # 读取文件所有内容

    arrayOLines = fr.readlines()  # [‘40920\t7.32\t0.95\n’,...‘’]

    # 得到文件行数

    numberOfLines = len(arrayOLines)

 

    #返回的NumPy矩阵,解析完成的数据:numberOfLines行,3列

    returnMat = np.zeros((numberOfLines,3))

    #返回的分类标签向量

    classLabelVector = []

    #行的索引值

    index = 0

 

    for line in arrayOLines:

 

        # ['40920\t8.326976\t0.953952\tlargeDoses\n',...'']

        # s.strip(rm),当rm空时,默认删除空白符(包括'\n','\r','\t',' ')

        line = line.strip()

        # 40920  8.326976  0.953952  largeDoses

        # 使用s.split(str="",num=string,cout(str))将字符串根据'\t'分隔符进行切片。

        listFromLine = line.split('\t')

        # ['40920', '8.326976', '0.953952', 'largeDoses']

        # 将数据前三列提取出来,存放到returnMat的NumPy矩阵中,也就是特征矩阵

        returnMat[index,:] = listFromLine[0:3]

        #根据文本中标记的喜欢的程度进行分类,1代表不喜欢,2代表魅力一般,3代表极具魅力

        if listFromLine[-1] == 'didntLike':

            classLabelVector.append(1)

        elif listFromLine[-1] == 'smallDoses':

            classLabelVector.append(2)

        elif listFromLine[-1] == 'largeDoses':

            classLabelVector.append(3)

        index += 1

    return returnMat, classLabelVector

"""

函数说明:对数据进行归一化

Parameters:

    dataSet - 特征矩阵

Returns:

    normDataSet - 归一化后的特征矩阵

    ranges - 数据范围

    minVals - 数据最小值

"""

def autoNorm(dataSet):

 

    # 获得数据的最小值

    # min(0)返回该矩阵中每一列的最小值, min(1)返回该矩阵中每一行的最小值

    # max(0)返回该矩阵中每一列的最大值, max(1)返回该矩阵中每一行的最大值

    minVals = dataSet.min(0)

    maxVals = dataSet.max(0)

    # 最大值和最小值的范围

    ranges = maxVals -minVals 

 

    # shape(dataSet)返回dataSet的矩阵行列数

    normDataSet = np.zeros(np.shape(dataSet)) # (1000,3)

    # 返回dataSet的行数 

    m = dataSet.shape[0] # 1000 

 

    #原始值减去最小值

    normDataSet = dataSet - np.tile(minVals, (m, 1))

    #除以最大和最小值的差,得到归一化数据

    normDataSet = normDataSet / np.tile(ranges, (m, 1))

    #返回归一化数据结果,数据范围,最小值

    return normDataSet, ranges, minVals

def showdatas(datingDataMat, datingLabels):

    #设置汉字格式

    font = FontProperties(fname="/home/anaconda2/桌面/机器学习/算法/KNN/simsun.ttc", size=14)

    #将fig画布分隔成1行1列,不共享x轴和y轴,fig画布的大小为(13,8)

    #当nrow=2,nclos=2时,代表fig画布被分为四个区域,axs[0][0]表示第一行第一个区域

    fig, axs = plt.subplots(nrows=2, ncols=2,sharex=False, sharey=False, figsize=(13,8))

    numberOfLabels = len(datingLabels)

    LabelsColors = []

    for i in datingLabels:

        if i == 1:

            LabelsColors.append('black')

        if i == 2:

            LabelsColors.append('orange')

        if i == 3:

            LabelsColors.append('red')

    #画出散点图,以datingDataMat矩阵的第一(飞行常客例程)、第二列(玩游戏)数据画散点数据,散点大小为15,透明度为0.5

    axs[0][0].scatter(x=datingDataMat[:,0], y=datingDataMat[:,1], color=LabelsColors,s=15, alpha=.5)

    #设置标题,x轴label,y轴label

    axs0_title_text = axs[0][0].set_title(u'每年获得的飞行常客里程数与玩视频游戏所消耗时间占比',FontProperties=font)

    axs0_xlabel_text = axs[0][0].set_xlabel(u'每年获得的飞行常客里程数',FontProperties=font)

    axs0_ylabel_text = axs[0][0].set_ylabel(u'玩视频游戏所消耗时间占',FontProperties=font)

    plt.setp(axs0_title_text, size=9, weight='bold', color='red')

    plt.setp(axs0_xlabel_text, size=7, weight='bold', color='black')

    plt.setp(axs0_ylabel_text, size=7, weight='bold', color='black')

    #画出散点图,以datingDataMat矩阵的第一(飞行常客例程)、第三列(冰激凌)数据画散点数据,散点大小为15,透明度为0.5

    axs[0][1].scatter(x=datingDataMat[:,0], y=datingDataMat[:,2], color=LabelsColors,s=15, alpha=.5)

    #设置标题,x轴label,y轴label

    axs1_title_text = axs[0][1].set_title(u'每年获得的飞行常客里程数与每周消费的冰激淋公升数',FontProperties=font)

    axs1_xlabel_text = axs[0][1].set_xlabel(u'每年获得的飞行常客里程数',FontProperties=font)

    axs1_ylabel_text = axs[0][1].set_ylabel(u'每周消费的冰激淋公升数',FontProperties=font)

    plt.setp(axs1_title_text, size=9, weight='bold', color='red')

    plt.setp(axs1_xlabel_text, size=7, weight='bold', color='black')

    plt.setp(axs1_ylabel_text, size=7, weight='bold', color='black')

    #画出散点图,以datingDataMat矩阵的第二(玩游戏)、第三列(冰激凌)数据画散点数据,散点大小为15,透明度为0.5

    axs[1][0].scatter(x=datingDataMat[:,1], y=datingDataMat[:,2], color=LabelsColors,s=15, alpha=.5)

    #设置标题,x轴label,y轴label

    axs2_title_text = axs[1][0].set_title(u'玩视频游戏所消耗时间占比与每周消费的冰激淋公升数',FontProperties=font)

    axs2_xlabel_text = axs[1][0].set_xlabel(u'玩视频游戏所消耗时间占比',FontProperties=font)

    axs2_ylabel_text = axs[1][0].set_ylabel(u'每周消费的冰激淋公升数',FontProperties=font)

    plt.setp(axs2_title_text, size=9, weight='bold', color='red')

    plt.setp(axs2_xlabel_text, size=7, weight='bold', color='black')

    plt.setp(axs2_ylabel_text, size=7, weight='bold', color='black')

    #设置图例

    didntLike = mlines.Line2D([], [], color='black', marker='.',

                      markersize=6, label='didntLike')

    smallDoses = mlines.Line2D([], [], color='orange', marker='.',

                      markersize=6, label='smallDoses')

    largeDoses = mlines.Line2D([], [], color='red', marker='.',

                      markersize=6, label='largeDoses')

    #添加图例

    axs[0][0].legend(handles=[didntLike,smallDoses,largeDoses])

    axs[0][1].legend(handles=[didntLike,smallDoses,largeDoses])

    axs[1][0].legend(handles=[didntLike,smallDoses,largeDoses])

    #显示图片

    plt.show()

 

 

def classify0(inX, dataSet, labels, k):

 

    # numpy函数shape[0]返回dataSet的行数

    dataSetSize = dataSet.shape[0]

    # 在列向量方向上重复inX共1次(横向),行向量方向上重复inX共dataSetSize次(纵向)

    diffMat = np.tile(inX, (dataSetSize, 1)) - dataSet

    # 二维特征相减后平方

    sqDiffMat = diffMat**2

    # sum()所有元素相加,sum(0)列相加,sum(1)行相加

    sqDistances = sqDiffMat.sum(axis=1)

    # 开方,计算出距离

    distances = sqDistances**0.5

 

    # 返回distances中元素从小到大排序后的索引值

    sortedDistIndices = distances.argsort() 

    # 比如sortedDistIndices[0] = 360,意味着第零位的值为360。360意味着从小到大排序后最小值在第360位

 

    # 定一个记录类别次数的字典

    classCount = {}    # {[1]:"", [2]:"", ......}

 

    for i in range(k):

        #取出前k个元素的类别

        # sortedDistIndices[0]=360,距离最小值排在第360位。 sortedDistIndices[0]=360, labels[360] = '1','1' = 'didntlike'

        voteIlabel = labels[sortedDistIndices[i]]   

        # dict.get(key,default=None),字典的get()方法,返回指定键的值,如果值不在字典中返回默认值。

        # 计算类别次数

        classCount[voteIlabel] = classCount.get(voteIlabel,0) + 1

 

    # python中字典的get方法,其中有两个参数,表示如果第一个值是字典的键,那么返回该键对应的值,

    # 如果该值不是字典的键,那么返回第二个值,如果只有第一个值时,表示取该键对应的值,如果该键不存在则返回None

    print("voting result:\n",classCount)

 

    # python3中用items()替换python2中的iteritems()

    # key=operator.itemgetter(1)根据字典的值进行排序

    # key=operator.itemgetter(0)根据字典的键进行排序

    # reverse降序排序字典

    # A.sort()|sorted(A). sort是会改变原来list,sorted不会改变原来list.

    sortedClassCount = sorted(classCount.items(),key=operator.itemgetter(1),reverse=True)

    # 返回次数最多的类别,即所要分类的类别

    return sortedClassCount[0][0]

 

"""

函数说明:分类器测试函数

Parameters:

    无

Returns:

    normDataSet - 归一化后的特征矩阵

    ranges - 数据范围

    minVals - 数据最小值

"""

def datingClassTest():

    # 打开的文件名

    filename = "/home/anaconda2/桌面/机器学习/算法/KNN/DatingTestSet.txt"

    # 将返回的特征矩阵和分类向量分别存储到datingDataMat和datingLabels中

    datingDataMat, datingLabels = file2matrix(filename)

    # 取所有数据的百分之十

    hoRatio = 0.10

 

    # 数据归一化,返回归一化后的矩阵,数据范围,数据最小值

    normMat, ranges, minVals = autoNorm(datingDataMat)

    # 获得normMat的行数

    m = normMat.shape[0]

    # 百分之十的测试数据的个数

    numTestVecs = int(m * hoRatio)

    # 分类错误计数

    errorCount = 0.0

    for i in range(numTestVecs):

        # 前numTestVecs个数据作为测试集,后m-numTestVecs个数据作为训练集

        classifierResult = classify0(normMat[i,:], normMat[numTestVecs:m,:],

            datingLabels[numTestVecs:m], 4)

 

        print("分类结果:%d\t真实类别:%d" % (classifierResult, datingLabels[i]))

        if classifierResult != datingLabels[i]:

            errorCount += 1.0

    print("错误率:%f%%" %(errorCount/float(numTestVecs)*100))

 

if __name__ == '__main__':

    #打开的文件名

    filename = "/home/anaconda2/桌面/机器学习/算法/KNN/DatingTestSet.txt"

    #打开并处理数据

    datingDataMat, datingLabels = file2matrix(filename)

    showdatas(datingDataMat, datingLabels)

    datingClassTest()

  • 0
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
在这个示例中,我们将使用KNN算法来预测某个人是否适合约会。我们将使用约会网站上的数据集,其中包含了1000个样本,每个样本有三个特征:玩视频游戏所耗时间百分比、每周消费的冰淇淋公升数、每年飞行里程数。 1. 准备数据 首先,我们需要将数据从文本文件中读取并进行预处理。在预处理过程中,我们将对特征进行归一化处理,以便于算法的收敛和准确性。 ```matlab % Load dating dataset data = load('datingTestSet.txt'); % Split features and labels X = data(:, 1:3); Y = data(:, 4); % Normalize features X_norm = (X - min(X)) ./ (max(X) - min(X)); ``` 2. 训练模型 我们将使用ClassificationKNN函数来训练模型。在这个示例中,我们将K值设置为5。 ```matlab % Train KNN classifier mdl = fitcknn(X_norm,Y,'NumNeighbors',5); ``` 3. 测试模型 使用测试集来测试模型的准确性。 ```matlab % Load test dataset test_data = load('datingTestSet2.txt'); % Split features and labels X_test = test_data(:, 1:3); Y_test = test_data(:, 4); % Normalize features X_test_norm = (X_test - min(X)) ./ (max(X) - min(X)); % Test the model Y_pred = predict(mdl,X_test_norm); % Calculate classification accuracy accuracy = sum(Y_pred == Y_test)/length(Y_test); fprintf('Classification accuracy: %.2f%%\n', accuracy*100); ``` 完整代码如下: ```matlab % Load dating dataset data = load('datingTestSet.txt'); % Split features and labels X = data(:, 1:3); Y = data(:, 4); % Normalize features X_norm = (X - min(X)) ./ (max(X) - min(X)); % Train KNN classifier mdl = fitcknn(X_norm,Y,'NumNeighbors',5); % Load test dataset test_data = load('datingTestSet2.txt'); % Split features and labels X_test = test_data(:, 1:3); Y_test = test_data(:, 4); % Normalize features X_test_norm = (X_test - min(X)) ./ (max(X) - min(X)); % Test the model Y_pred = predict(mdl,X_test_norm); % Calculate classification accuracy accuracy = sum(Y_pred == Y_test)/length(Y_test); fprintf('Classification accuracy: %.2f%%\n', accuracy*100); ``` 这个示例中,KNN算法的准确性为94.00%。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值