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

问题描述

海伦一直使用在线约会网站寻找适合自己的约会对象。尽管约会网站会推荐不同的人选,但她没有从中找到喜欢的人。经过一番总结,她发现曾交往过三种类型的人:

  • 不喜欢的人
  • 魅力一般的人
  • 极具魅力的人

尽管发现了上述规律,但海伦依然无法将约会网站推荐的匹配对象归人恰当的分类。她觉得可以在周一到周五约会那些魅力一般的人,而周末则更喜欢与那些极具魅力的人为伴。海伦为了可以更好将匹配对象划分到确切的分类中,收集了1000个相关样本数据。海伦的样本数据包含以下3种特征:

  • 每年获得的飞行常客里程数
  • 玩视频游戏所耗时间百分比
  • 每周消费的冰淇淋公升数

接下来将用kNN算法通过1000个相关样本的解决海伦的问题。

算法过程

  1. 收集数据:提供文本文件。
  2. 准备数据: 使用Python解析文本文件。
  3. 分析数据:使用Matplotlib画二维扩散图。
  4. 训练算法:此步驟不适用于k-近邻算法。
  5. 测试算法:使用海伦提供的部分数据作为测试样本。测试样本和非测试样本的区别在于:测试样本是已经完成分类的数据,如果预测分类与实际类别不同,则标记为一个错误。
  6. 使用算法:产生简单的命令行程序,然后海伦可以输入一些特征数据以判断对方是否为自己喜欢的类型。

算法详细步骤

准备数据:从文本文件中解析数据

在将上述特征数据输人到分类器之前,必须将待处理数据的格式改变为分类器可以接受的格式。 原始数据部分截图: 原始数据格式

def file2matrix(filename):
    """
    将文本记录转换为NumPy解析程序
    :param filename: 文件名称
    :return: 特征矩阵、样本分类向量
    """
    fr = open(filename)
    array_of_lines = fr.readlines()
    number_of_lines = len(array_of_lines)
    matrix_of_return = np.zeros((number_of_lines, 3))
    class_of_label_vector = []
    index = 0
    for line in array_of_lines:
        line = line.strip()
        list_of_line = line.split('\t')
        matrix_of_return[index, :] = list_of_line[0:3]

        # 列表中最后一项存入class_of_label_vector中
        class_of_label_vector.append(int(list_of_line[-1]))
        index += 1
    return matrix_of_return, class_of_label_vector

分析数据:散点图

使用matrix_of_return矩阵的第二、第三列数据,分别表示特征值“玩视频游戏所耗时间百分比”和“每周所消费的冰淇淋公升”绘制散点图。 散点图

def draw_scatter(matrix_of_dating, labels_of_dating, xlabel, ylabel):
    """
    绘制散点图
    """
    plt.rcParams['font.sans-serif'] = ['SimHei']
    fig = plt.figure()
    ax = fig.add_subplot(111)
    ax.scatter(x=matrix_of_dating[:, 1], y=matrix_of_dating[:, 2],
               s=15.0*np.array(labels_of_dating), c=15.0*np.array(labels_of_dating))
    ax.set_xlim([0.0, 25.0])
    ax.set_ylim([0.0, 2.0])
    ax.set_xlabel(xlabel)
    ax.set_ylabel(ylabel)
    plt.show()

准备数据:归一化数值

每年获取的飞行常客里程数对于计算结果的影响将远远大于其他两个特征(玩视频游戏的和每周消费冰洪淋公升数)的影响。而产生这种现象的唯一原因,仅仅是因为飞行常客里程数远大于其他特征值。但这三种特征是同等重要的,因此作为三个等权重的特征之一,飞行常客里程数并不应该如此严重地影响到计算结果。因此我们需要将任意取值范围的特征值转化为0到1区间内的值。这样三个等权重的特征值就在程序中变得同等重要。

def auto_norm(data_set):
    """
    归一化特征值
    此数值归一化公式:new_value = (old_value-min)/(max-min)
    :param data_set: 数据集
    """
    min_vals = data_set.min(0)  # 每一列中的最小值,data_set.min(0) 其中参数0可以使函数从列中选出最小值
    max_vals = data_set.max(0)
    ranges = max_vals - min_vals
    norm_data_set = np.zeros(np.shape(data_set))
    m = data_set.shape[0]
    norm_data_set = data_set - np.tile(min_vals, (m, 1))
    norm_data_set = norm_data_set / np.tile(ranges, (m, 1))
    return norm_data_set, ranges, min_vals

测试算法:作为完整程序验证分类器

已有数据的90%作为训练样本来训练分类器,而使用其余的10%数据去测试分类器,检测分类器的正确率。对于分类器来说,错误率就是分类器给出错误结果的次数除以测试数据的总数,完美分类器的错误率为0,而错误率为1.0的分类器不会给出任何正确的分类结果。

def dating_class_test(filename, matrix_of_dating, labels_of_dating):
    """
    分类器针对约会网站的测试代码
    错误率计算公式:分类器给出的错误结果的次数/测试数据的总数
    :param filename: 文件位置
    :return: None
    """
    ratio_of_ho = 0.10  # 10%的测试数据,90%的训练数据
    matrix_of_norm, ranges, min_vals = auto_norm(matrix_of_dating)
    m = matrix_of_norm.shape[0]
    num_of_test_vecs = int(m*ratio_of_ho)
    error_counts = 0.0
    for i in range(num_of_test_vecs):
        classifier_result = classify0(matrix_of_norm[i, :], matrix_of_norm[num_of_test_vecs:m, :],
                                      labels_of_dating[num_of_test_vecs:m], 3)
        print("the classifier came back with: %d, the real answer is: %d"
              % (classifier_result, labels_of_dating[i]))
        if classifier_result != labels_of_dating[i]:
            error_counts += 1.0
    print('the total error rate is %f' % (error_counts/float(num_of_test_vecs)))

使用算法

def classify_person(matrix_of_dating, labels_of_dating):
    """
    约会网站预测函数
    """
    print("Begin to predict\n")
    # 不喜欢的人、魅力一般的人、极具魅力的人
    result_list = ['not at all', 'in small doses', 'in large doses']
    percent_tats = float(input("percentage of time spent playing video games?\n"))  # 玩视频游戏所花时间百分比
    ff_miles = float(input("frequent filer miles earned per year?\n"))  # 每年获得的飞行常客里程数
    ice_creams = float(input("liters of ice cream consumed per year?\n"))
    matrix_of_dating, ranges, min_vals = auto_norm(data_set=matrix_of_dating)
    in_arr = np.array([ff_miles, percent_tats, ice_creams])
    classifier_result = classify0((in_arr-min_vals)/ranges, matrix_of_dating, labels_of_dating, 3)
    print("You will probably like this person: ", result_list[classifier_result-1])

全部代码

# -*- coding: utf-8 -*-
# @Function :  使用k-近邻算法改进约会网站配对效果
import numpy as np
import matplotlib.pyplot as plt
import operator


def classify0(in_x, data_set, labels, k):
    """
    k-近邻算法
    :param in_x: 用于分类的输入向量X
    :param data_set: 输入的训练样本集data_set
    :param labels: 标签向量,其元素数目与矩阵data_set的行数相同
    :param k: 选择最近邻居的数目
    :return: 发生频率最高的元素标签
    """
    dataset_size = data_set.shape[0]

    # 原型:numpy.tile(A,reps)
    # tile共有2个参数,A指待输入数组,reps则决定A重复的次数。整个函数用于重复数组A来构建新的数组。
    # 计算距离,欧式距离公式:sqrt(pow(xA0-xB0, 2) + pow(xA1-xB1, 2))
    diff_mat = np.tile(in_x, (dataset_size, 1)) - data_set
    sq_diff_mat = diff_mat ** 2
    sq_distances = sq_diff_mat.sum(axis=1)

    distances = sq_distances ** 0.5
    # numpy.argsort() 返回排好序的序列的索引
    sorted_dist_indicies = distances.argsort()

    class_count = {}
    # 选择距离最小的k个节点
    for i in range(k):
        vote_I_label = labels[sorted_dist_indicies[i]]
        class_count[vote_I_label] = class_count.get(vote_I_label, 0) + 1

    sorted_class_count = sorted(class_count.items(), key=operator.itemgetter(1), reverse=True)
    return sorted_class_count[0][0]


def file2matrix(filename):
    """
    将文本记录转换为NumPy解析程序
    :param filename: 文件名称
    :return: 特征矩阵、样本分类向量
    """
    fr = open(filename)
    array_of_lines = fr.readlines()
    number_of_lines = len(array_of_lines)
    matrix_of_return = np.zeros((number_of_lines, 3))
    class_of_label_vector = []
    index = 0
    for line in array_of_lines:
        line = line.strip()
        list_of_line = line.split('\t')
        matrix_of_return[index, :] = list_of_line[0:3]

        # 列表中最后一项存入class_of_label_vector中
        class_of_label_vector.append(int(list_of_line[-1]))
        index += 1
    return matrix_of_return, class_of_label_vector


def draw_scatter(matrix_of_dating, labels_of_dating, xlabel, ylabel):
    """
    绘制散点图
    """
    plt.rcParams['font.sans-serif'] = ['SimHei']
    fig = plt.figure()
    ax = fig.add_subplot(111)
    ax.scatter(x=matrix_of_dating[:, 1], y=matrix_of_dating[:, 2],
               s=15.0*np.array(labels_of_dating), c=15.0*np.array(labels_of_dating))
    ax.set_xlim([0.0, 25.0])
    ax.set_ylim([0.0, 2.0])
    ax.set_xlabel(xlabel)
    ax.set_ylabel(ylabel)
    plt.show()


def auto_norm(data_set):
    """
    归一化特征值
    此数值归一化公式:new_value = (old_value-min)/(max-min)
    :param data_set: 数据集
    :return:
    """
    min_vals = data_set.min(0)  # 每一列中的最小值,data_set.min(0) 其中参数0可以使函数从列中选出最小值
    max_vals = data_set.max(0)
    ranges = max_vals - min_vals
    norm_data_set = np.zeros(np.shape(data_set))
    m = data_set.shape[0]
    norm_data_set = data_set - np.tile(min_vals, (m, 1))
    norm_data_set = norm_data_set / np.tile(ranges, (m, 1))
    return norm_data_set, ranges, min_vals


def dating_class_test(filename, matrix_of_dating, labels_of_dating):
    """
    分类器针对约会网站的测试代码
    错误率计算公式:分类器给出的错误结果的次数/测试数据的总数
    :param filename: 文件位置
    :return: None
    """
    ratio_of_ho = 0.10  # 10%的测试数据,90%的训练数据
    matrix_of_norm, ranges, min_vals = auto_norm(matrix_of_dating)
    m = matrix_of_norm.shape[0]
    num_of_test_vecs = int(m*ratio_of_ho)
    error_counts = 0.0
    for i in range(num_of_test_vecs):
        classifier_result = classify0(matrix_of_norm[i, :], matrix_of_norm[num_of_test_vecs:m, :],
                                      labels_of_dating[num_of_test_vecs:m], 3)
        print("the classifier came back with: %d, the real answer is: %d"
              % (classifier_result, labels_of_dating[i]))
        if classifier_result != labels_of_dating[i]:
            error_counts += 1.0
    print('the total error rate is %f' % (error_counts/float(num_of_test_vecs)))


def classify_person(matrix_of_dating, labels_of_dating):
    """
    约会网站预测函数
    """
    print("Begin to predict\n")
    # 不喜欢的人、魅力一般的人、极具魅力的人
    result_list = ['not at all', 'in small doses', 'in large doses']
    percent_tats = float(input("percentage of time spent playing video games?\n"))  # 玩视频游戏所花时间百分比
    ff_miles = float(input("frequent filer miles earned per year?\n"))  # 每年获得的飞行常客里程数
    ice_creams = float(input("liters of ice cream consumed per year?\n"))
    matrix_of_dating, ranges, min_vals = auto_norm(data_set=matrix_of_dating)
    in_arr = np.array([ff_miles, percent_tats, ice_creams])
    classifier_result = classify0((in_arr-min_vals)/ranges, matrix_of_dating, labels_of_dating, 3)
    print("You will probably like this person: ", result_list[classifier_result-1])


if __name__ == '__main__':
    # 可视化分析数据
    filename = './Data/Ch02/datingTestSet2.txt'
    matrix_of_dating, labels_of_dating = file2matrix(filename)
    draw_scatter(matrix_of_dating, labels_of_dating,
                 '玩视频游戏所耗时间百分比', '每周消耗的冰淇淋公升数')
    dating_class_test(filename, matrix_of_dating, labels_of_dating)
    classify_person(matrix_of_dating, labels_of_dating)

运行结果

运行结果

本文内容引自《Python 机器学习实战》

猜你喜欢

转载自my.oschina.net/chenmoxuan/blog/1819600