K近邻与朴素贝叶斯

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/qq_30172585/article/details/79538771

任务简述

  1. 分类任务
    如下图,数据集的X是“英语语句”,Y是该语句对应的“情绪”,情绪的可能取值有6种,任务是给定一个语句,能判断出它属于什么情绪
  2. 回归任务
    如下图,数据集的X是“英语语句”,Y是该语句对应的六种情绪的可能性,情绪的可能取值有5种,任务是给定一个语句,能判断出它属于各种情绪的可能性

算法原理

A. knn分类

原理:是将训练集与测试集的数据转化成特征向量,计算测试集与训练集的向量的距离,距离越近代表特征越像,然后选取距离最近的k个向量,也就是特征最像的k个向量,取它们的标签的众数作为测试数据的预测标签
1. 训练集的x转化成相应的矩阵(例如onehot或tf),同样将测试集的x也转换成矩阵
2. 对于测试集中的一个向量,计算它与训练集的矩阵中的每一个向量进行计算得出两个向量之间的距离
3. 选出K个距离最小的向量,统计这些向量对应的标签,在这些向量对应的标签中出现最多次(多数投票原则)的那个标签即为knn分类器的预测标签。

B. knn回归

原理: 是将训练集与测试集的数据转化成特征向量,计算测试集与训练集的向量的距离,距离越近代表特征越像,然后选取距离最近的k个向量,也就是特征最像的k个向量,取它们的距离的倒数作为权,乘以自身的标签概率,将k个加和最后归一化的概率作为预测概率
1. 训练集的x转化成相应的矩阵(例如onehot或tf),同样将测试集的x也转换成矩阵
2. 对于测试集中的一个向量,计算它与训练集的矩阵中的每一个向量进行计算得出两个向量之间的距离
3. 选出K个距离最小的向量,把该距离的倒数作为权重,加和权重与训练集概率的乘积,计算测试数据属于某标签的概率
4. 对得出的概率进行归一化处理,得到最终的预测概率

C. Naive Bayes分类

  1. 贝叶斯定理、特征条件独立假设就是朴素贝叶斯的两个理论基础。
    P(B|A)=P(A|B)P(B)P(A)
  2. 对于我们的数据,我们有输入x,以及对应的概率标签y=(y1,y2,…,yk),这样的话要算出某个x属于yk的概率,就是,上述的贝叶斯公式就转化为
    P(yk|x)=P(x|yk)P(yk)P(x)
  3. 这里对于一个测试集数据x,其所有的P(yk|x)中分母都是一样的,因此对于分类器,只需要求出分子,然后取出所有标签概率中最大的就是贝叶斯模型的预测标签了
D. Naive Bayes回归
  1. 同样利用贝叶斯定理
    P(yk|x)=P(x|yk)P(yk)P(x)
  2. 上式的P(x|yk)应用特征条件独立性假设
    P(x|yk)=P(x1,x2,x3,,xn|yk)=i=1nP(xi|yk)
  3. 对求得的概率进行归一化,即可得到最后的预测概率

原始算法的结果及分析

A. knn分类

对于311个验证数据进行预测,其中蓝色的表示预测正确,红色表示预测错误(欧氏距离,参数K=21)
分析:分类准确率0.411说明knn算法作为最简单的分类算法效果还不差,但是也并不是很高,训练集的数据不平衡(joy较多)对该算法影响还是比较大

B. knn回归

其中红色为验证集给出的概率,蓝色的为算法预测出来的概率(欧式距离,k=5)
分析:相关系数为0.2多,还处于低度相关的程度,效果并不理想,在k较小的时候主要是欧氏距离的问题,因为验证集中有很多词在训练集中没出现过,这样就很容易出现最近的几个是单词量最少的文本的情况

C. Naive Bayes分类

对于311个验证数据进行预测,其中蓝色的表示预测正确,红色表示预测错误(平滑器参数为1,也就是拉普拉斯平滑)
分析:分类准确率达到0.45,可以说效果很好了,这说明特征条件独立假设在我们的数据中很有可能是成立的,也说明了多项式模式适用于该数据

D. Naive Bayes回归

其中红色为验证集给出的概率,蓝色的为算法预测出来的概率(平滑器参数为1,也就是拉普拉斯平滑)
分析:相关系数为0.26多,还处于低度相关的程度,效果并不理想,主要是拉普拉斯平滑对于这个数据的影响过大,导致频数本来为0的平滑后与不为0的相差不多

算法优化

A. knn分类

思路:训练集的数据不平衡(joy较多)对该算法影响还是比较大,因此改变计数时统一加一的方法,使用计数时加上一个权值w(w=1/distance^n)的方法,这样的话,我们假设因为数据不平衡导致21(设k=21)个最近标签中有15个joy(距离稍远),以及6个sad(距离稍近),对于这种情况,这里的权值优化法将很有可能选sad,从而减轻数据不平衡的影响

最终代码(关键部分)

def knn_cla_select(one_hot, vector_in, k):
    """
    :param one_hot: 训练集语料构成的onehot矩阵
    :param vector_in: 要预测的语句所转变成的onehot向量
    :param k: knn算法中的参数k
    :return: 预测的标签
    """
    dis2indexs = []
    for row, one_hot_line in enumerate(one_hot):  # 计算vector_in与训练集onehot的距离,放入一个list
        dis = distance(one_hot_line, vector_in, 2)
        dis2indexs.append(dis2index(dis, row))
    emotions_cnt = {}
    max_cnt = 0
    select_emotion = ""
    dis2indexs.sort()  # 对距离(附加索引)排序,python使用的是归并排序,因此是稳定的排序
    for i in range(k):  # 取出前k个距离最小的
        mind = dis2indexs[i]
        emot = emotions_train[mind.index]
        #  对于这k个最小的距离对应的标签,出现n次就加上权值*n,这里的权值取的是 1÷(距离^5)
        weight = float("inf") if mind.dis == 0 else 1 / (mind.dis ** 5)
        if emotions_cnt.get(emot, -1) == -1:
            emotions_cnt[emot] = weight
        else:
            emotions_cnt[emot] += weight
        if emotions_cnt[emot] > max_cnt:  # 记录k个中标签次数出现最多的标签,作为最后的输出
            select_emotion = emot
            max_cnt = emotions_cnt[emot]
    return select_emotion

优化后结果
分析:准确率从0.41上升到0.44,还是有较大提升

B. knn回归

思路:在k较小的时候主要是欧氏距离的问题,因为验证集中有很多词在训练集中没出现过,这样就很容易出现最近的几个是单词量最少的文本的情况,针对这种情况来自定义一个距离函数,使得能提高两个数据同时有某个词的影响力(用欧式的话,会导致同时有某个词与同时没有某个词的影响力一致);
同时,参照knn分类的思想,权值变为w(w=1/distance^n)

最终代码(关键部分)

def distance(v1, v2, dis_type):
    if dis_type == 1:
        return np.sum(np.abs(v1 - v2))  # 哈夫曼距离
    elif dis_type == 2:
        return np.sqrt(np.sum(np.square(v1 - v2)))  # 欧式距离
    elif dis_type == 3:  # 自定义距离
        vsum = v1 + v2
        cnt0 = len(np.where(vsum == 0)[0])  # 两个向量相同位置都是0的个数
        cnt1 = len(np.where(vsum == 1)[0])  # 两个向量相同位置一个是0一个是1的个数
        cnt2 = len(np.where(vsum == 2)[0])  # 两个向量相同位置都是1的个数
        res = cnt1 * 8 + cnt0 - cnt2 * 24
        return 0 if res < 0 else res

def knn_reg_select(one_hot, vector_in, k):
    """
    :param one_hot: 训练集语料构成的onehot矩阵
    :param vector_in: 要预测的语句所转变成的onehot向量
    :param k: knn算法中的参数k
    :return: 预测的标签可能性列表
    """
    dis2indexs = []
    for row, one_hot_line in enumerate(one_hot):  # 计算vector_in与训练集onehot的距离,放入一个list
        dis = distance(one_hot_line, vector_in, 3)
        dis2indexs.append(dis2index(dis, row))
    emotion_probabilities = [0] * 6
    dis2indexs.sort()  # 对距离(附加索引)排序
    for i in range(k):  # 取出前k个距离最小的
        mind = dis2indexs[i]
        # 将距离的倒数作为权值,乘以训练集中的概率,加和得到总概率(注:处理了一下分母0的情况)
        for k, p in enumerate(emotions_train_regression[mind.index]):
            emotion_probabilities[k] += 999999 if mind.dis == 0 else p / (mind.dis**55)
    p_sum = sum(emotion_probabilities)
    if p_sum > 0:
        emotion_probabilities = [p / p_sum for p in emotion_probabilities]  # 对概率归一化处理
    return emotion_probabilities

优化后结果
其中红色为验证集给出的概率,蓝色的为算法预测出来的概率
分析:相关度从0.2多升到0.35,可以说效果很好了,同时也说明这种自定义距离公式确实能很大程度消除短文本的影响

C. Naive Bayes分类

注:该部分没有进行优化

最终代码(关键部分)

def calculate_prior_probability(self, test_y):
    """
    计算先验概率
    :param test_y: 测试集的希望求得概率的y的list
    """
    train_num = len(self.train_y)
    for y in test_y:
        train_num_y = len(np.where(self.train_y == y)[0])
        # p = (train_num_y + self.alpha) / (train_num + self.y_type_num * self.alpha)  # 平滑处理
        p = train_num_y / train_num
        self.prior_probability.append(p)

def calculate_likelihood(self, test_x, test_y):
    """
    计算似然度
    :param test_x: 一条测试数据里的x的list
    :param test_y: 一条测试数据里的y,注意只是一个标签,不是一个list
    :return: 似然度scalar
    """
    likelihood = 1
    for x_feature in test_x:
        if keywordList.count(x_feature) == 0:
            x_index = -1
        else:
            x_index = keywordList.index(x_feature)
        numerator, denominator = self.get_frequency(x_index, test_y)
        numerator += self.alpha  # 平滑处理
        denominator += len(keywordList) * self.alpha  # 平滑处理
        likelihood = likelihood * (numerator / denominator)
    return likelihood

def predict(self, test_x):
    """
    朴素贝叶斯预测
    :param test_x: 测试集的一个x
    :return: 预测标签
    """
    ylist = list(set(self.train_y))  # 标签的不重复list
    self.calculate_prior_probability(ylist)  # 计算先验概率
    max_frequency = 0
    predict_y = ""
    plist = []
    for index, y in enumerate(ylist):
        likelihood = self.calculate_likelihood(test_x, y)  # 对于每个标签,计算似然度
        prior_probability = self.prior_probability[index]
        cur_frequency = likelihood * prior_probability  # 似然度乘以先验概率得到贝叶斯公式的分母
        print("标签为", y, "  概率为", cur_frequency)
        if cur_frequency > max_frequency:  # 记录概率最大的标签,作为预测的标签
            predict_y = y
            max_frequency = cur_frequency
    return predict_y

D. Naive Bayes回归

思路:拉普拉斯平滑对于数据影响过大,举一个例子,这是一个很普通的词向量(词频):

对其使用拉普拉斯平滑转换成tf矩阵将会是:

可见,对于这个数据,拉普拉斯平滑影响太大,导致原本相差很大的频率变得相差很小,这一切都因为词向量,所以分母加的值太大,导致频率被分摊太多。
解决方法:降低平滑值,拉普拉斯的平滑值为1,我们可以修改平滑值为0.01或更小的数,重要的是要保证平滑效果的前提下和对原始频率影响不要太大

最终代码(关键部分)

def nb_perdict_reg():
    regression_read_text()  # 读入数据
    cnt_matrix = get_cnt(usefulLines)  # 转成频数矩阵
    # 平滑参数为0.001时效果较好
    mnb = MultinomialNB_reg(cnt_matrix, emotions_train_regression, 0.001)  
    ...
    ...

def predict(self, test_x):
    """
    朴素贝叶斯回归预测
    :param test_x: 测试集x(list)
    :return: 预测标签概率(以训练集中y出现顺序为顺序)
    """
    test_x_indexs = [keywordList.index(x) for x in test_x]
    predict_y_line = []
    for y_i in range(len(self.train_y[0])):
        total_p = 0
        for index, tf_line in enumerate(self.smooth_tf):  # 对于tf里面的每一行
            p = 1
            for x in test_x_indexs:  # 对于test_x里面的每一项
                p = p * tf_line[x]
            p = p * self.train_y[index][y_i]
            total_p += p
        predict_y_line.append(total_p)
    p_sum = sum(predict_y_line)
    predict_y_line = [p / p_sum for p in predict_y_line]
    return predict_y_line

def get_smoothing_tf(self, train_x):
    """
    将频数矩阵使用平滑器变成对应的tf矩阵
    :param train_x:训练集的x的频数矩阵
    :return:tf矩阵
    """
    smooth_tf = []
    for x_line in train_x:
        s = sum(x_line)
        tf_line = []
        for x in x_line:
            tf_line.append((x + self.alpha) / (s + len(x_line) * self.alpha))  # 平滑处理
        smooth_tf.append(tf_line)
    smooth_tf = np.array(smooth_tf)
    return smooth_tf

优化后结果
其中红色为验证集给出的概率,蓝色的为算法预测出来的概率
分析:可见降低拉普拉斯的影响之后相关度显著提高,从0.26升到0.34,说明分析正确,平滑值为0.01也是一个较为合适的值,保证了平滑效果的前提下对原始频率影响不大

问题与思考

  1. knn中将距离的倒数作为权值,为什么是倒数呢?
    答:因为距离越远表示相似度越低,显然,相似度低的数据对结果影响所占的权重应该越小才对,因此使用距离的倒数作为权重

  2. 同一测试样本的各个情感概率总和应该为1, 如何处理?
    答:记S为当前概率的总和,钥使得概率总和为1,只需要将当前概率值除以S之后的商作为新的概率即可

  3. 在矩阵稀疏程度不同的时候, 这两者表现有什么区别, 为什么?
    答:

    • 运行性能。当矩阵较为稀疏的时候,两者计算量差不了多少;当矩阵不稀疏的时候,欧氏距离较曼哈顿距离而言会明显慢很多,这是由于曼哈顿距离只需要加减法运算,而欧氏距离还要有平方的运算过程
    • 算法结果。当矩阵较为稀疏的时候,计算结果上不会相差太大;当矩阵不稀疏的时候,曼哈顿距离与对应的欧式距离之间的差值会很大,在Knn中,如果权值与距离是有关的(例如权值=1/距离),那么曼哈顿距离对较远数据点的影响会比欧式距离大得多
  4. 这两个模型分别有什么优缺点(伯努利与多项式)?
    答:总的来说,我认为多项式模型比伯努利模型更好。
    多项式模型:充分考虑了词频的影响,显然的,一篇文章中一个词的词频越高就越有代表性,因此应该考虑词频的影响。 但是,这样的话却避免不了极端数据的影响,例如joy的行中有一行出现100个study,而其他很多行都没有出现study的情况
    伯努利模型:实现上比较容易,能降低上述的极端数据的影响
阅读更多
换一批

没有更多推荐了,返回首页