K-邻近算法示例:使用k-邻近算法改进约会网站的配对效果

import numpy as np
import operator

"""
函数说明:打开并解析文件,对数据进行分类
Parameters:
    filename - 文件名
Returns:
    returnMat - 特征矩阵
    classLabelVector - 分类标签向量
Modify:
    2019-03-02
"""


def file2matrix(filename):
    # 打开文件
    file = open(filename)
    # 获取文件内容
    arrayOfLines = file.readlines()
    # 文件中数据的行数(即数据的条数)
    numberOfLines = len(arrayOfLines)
    # 创建一个numpy矩阵:numberOfLines行,3列,用“0”填充
    returnMat = np.zeros((numberOfLines, 3))
    # 存储分类标签的向量
    classLabelVevtor = []
    # 行索引
    index = 0
    # 扫描每一条数据,并做一些处理
    for line in arrayOfLines:
        # Python strip(rm)方法用于移除字符串头尾指定的字符(默认为空格或换行符)或字符序列。
        # 注意:该方法只能删除开头或是结尾的字符,不能删除中间部分的字符。
        line = line.strip()
        listFromLine = line.split('\t')
        returnMat[index, :] = listFromLine[0:3]
        if (listFromLine[-1] == 'didntLike'):
            classLabelVevtor.append(1)
        elif (listFromLine[-1] == 'smallDoses'):
            classLabelVevtor.append(2)
        elif (listFromLine[-1] == 'largeDoses'):
            classLabelVevtor.append(3)
        index += 1
    return returnMat, classLabelVevtor


"""
函数作用:KNN算法,分类器
Parameters:
    inX - 用于分类的数据(测试集)
    dataSet - 用于训练的数据(训练集)
    labels - 分类标签
    k - KNN算法参数,选择距离最小的k个点
Returns:
    sortedClassCount[0][0] - 分类结果
Modify:
    2019-03-02
"""


def classify0(inX, dataSet, labels, k):
    # 训练集数据条数
    dataSetSize = dataSet.shape[0]
    # np.tile(data,(y,x)):以矩阵data为基础单元,形成一个y行x列的矩阵:
    #               |X     Y|
    #   |X    Y| -> |X     Y|
    #               |X     Y|
    #
    #   |X    Y|   |x1   y1|
    #   |X    Y| - |x2   y2|
    #   |X    Y|   |x3   y3|
    diffMat = np.tile(inX, (dataSetSize, 1)) - dataSet
    # | ( X - x1 )**2  ( Y - y1 )**2 |
    # | ( X - x2 )**2  ( Y - y2 )**2 |
    # | ( X - x3 )**2  ( Y - y3 )**2 |
    sqDiffMat = diffMat ** 2
    # | ( X - x1 )**2  +  ( Y - y1 )**2 |
    # | ( X - x2 )**2  +  ( Y - y2 )**2 |
    # | ( X - x3 )**2  +  ( Y - y3 )**2 |
    sqDistance = sqDiffMat.sum(1)
    # | sqr{ ( X - x1 )**2  +  ( Y - y1 )**2 }|
    # | sqr{ ( X - x2 )**2  +  ( Y - y2 )**2 }|
    # | sqr{ ( X - x3 )**2  +  ( Y - y3 )**2 }|
    distances = sqDistance ** 0.5
    # 返回distances中所有元素从小到大排序的索引值
    sortedDistIndices = distances.argsort()
    # 字典classCount{标签1:次数 , 标签2:次数}
    classCount = {}
    # 统计前K个元素中各标签出现的次数
    for i in range(k):
        # 离(X ,Y)第i近的点的标签
        voteIlabel = labels[sortedDistIndices[i]]
        # 将该标签的数量+1
        # (字典名.get( key ,x )) 获取字典中键名key的键值对的值,为如果字典中没有这个键名对应的键值对,则在字典中添加本键值对,且令其值为:x
        classCount[voteIlabel] = classCount.get(voteIlabel, 0) + 1
    # sorted( 可迭代序列 ,key ,是否逆序 ) key参数的值为一个函数,此函数只有一个参数且返回一个值用来进行比较
    # operator.itemgetter(维度):函数的返回值是一个函数,这个函数的作用为返回某对象指定维度的数据
    # 例:operator.itemgetter( x )([0,1,2,3,4,5,6,7,8,9])
    #     返回元组中下标为x的元素
    sortedClassCount = sorted(classCount.items(), key=operator.itemgetter(1), reverse=True)
    return sortedClassCount[0][0]


"""
函数说明:对数据进行归一化
Parameters:
    dataSet - 特征矩阵
Returns:
    normDataSet - 归一化后的特征矩阵
    ranges - 数据范围
    minVals - 数据最小值
Modify - 2019-03-03
"""


def autoNorm(dataSet):
    # numpy数组.min()/max() :返回矩阵中所有元素的最小或最大值
    # numpy数组.min(0)/max(0) :返回矩阵中每一列的最小或最大值
    # numpy数组.min(1)/max(1) :返回矩阵中每一行的最小或最大值
    #           | 1 2 |   npArray.min() == 0  npArray.max() == 7
    # npArray = | 7 0 |   npArray.min(0) == [ 1 0 ]  npArray.max(0) == [ 7 6 ]
    #           | 5 6 |   npArray.min(1) == [ 1 0 5 ]  npArray.max(1) == [ 2 7 6 ]
    minVals = dataSet.min(0)
    maxVals = dataSet.max(0)
    # 最大值和最小值的差值
    range = maxVals - minVals
    # np.shape(矩阵)返回值:(矩阵行数,矩阵列数)
    # np.zeros((矩阵行数,矩阵列数))返回值:以0填充的指定行数、列数的矩阵
    normalDataSet = np.zeros(np.shape(dataSet))
    # 训练集数据条数
    m = dataSet.shape[0]
    normalDataSet = dataSet - np.tile(minVals, (m, 1))
    normalDataSet = normalDataSet / np.tile(range, (m, 1))
    return normalDataSet, range, minVals


"""
函数说明:分类器测试函数
Parameters:
    null
Returns:
    normalDataSet - 数值归一后的特征矩阵
    ranges - 数据范围(max-min)
    minVals - 数据最小值
Modify:
    2019-03-03
"""


def datingClassTest(k):
    filename = "datingTestSet.txt"
    # 获取特征矩阵和标签向量
    datingDataMat, datingLabels = file2matrix(filename)
    # 数值归一后的特征矩阵  数据范围  数据最小值
    normalMat, ranges, minVals = autoNorm(datingDataMat)
    # 特征矩阵行数
    m = normalMat.shape[0]
    # 测试数据占特征矩阵中数据的比例
    hoRatio = 0.10
    # 测试数据的条数
    numTextVecs = int(m * hoRatio)
    # 统计错误次数
    errorCount = 0.0
    for i in range(numTextVecs):
        # 前numTextVecs条数据作为测试集,剩下的m-numtextVecs条数据作为训练集
        classifierResult = classify0(normalMat[i, :], normalMat[numTextVecs:m, :], datingLabels[numTextVecs:m], k)
        print("分类结果:%d\t真实类别:%d" % (classifierResult, datingLabels[i]))
        if (classifierResult != datingLabels[i]):
            errorCount += 1.0
    print("错误率为:%f%%" % (errorCount / float(numTextVecs) * 100))
    return errorCount / float(numTextVecs) * 100


"""
函数作用:main函数
Parameters:
    null
Returns:
    null
Modify:
    2019-03-03
"""
if __name__ == '__main__':
    # 存储k值从1到20时分类器的错误率
    k_err = []
    for k in range(20):
        k_err.append(datingClassTest(k + 1))
    print(k_err)
    # python元组转为numpy数组
    print(np.array(k_err, dtype=float))
    # 返回numpy数组中元素从小到大排序后,元素的索引值
    print(np.array(k_err, dtype=float).argsort())
    print("k值最小设为", np.array(k_err, dtype=float).argsort()[0] + 1, "时分类器错误率能够达到最低。")

猜你喜欢

转载自blog.csdn.net/qq_39514033/article/details/88088353