第四周周报:深度学习经典网络模型(二)

目录

摘要

Abstract

一、Transformer

1.1 编码器

1.2 解码器

1.2.1 自回归解码器

1.2.2 非自回归解码器

1.2.3 编码器-解码器注意力

1.3 如何更好训练Transformer

二、贝叶斯

2.1 贝叶斯定理

2.2 朴素贝叶斯的应用

2.3 代码

总结


摘要

本周跟着李宏毅老师的课程,主要学习了序列到序列的神经网络模型Transformer,以及在机器学习中应用广泛的贝叶斯定理。详细地学习了Transformer的运行原理,以及通过西瓜检测的例子深刻理解了朴素贝叶斯的运行原理,并通过代码实现。

Abstract

This week, I followed Professor Li Hongyi's course and mainly learned the sequence to sequence neural network model Transformer, as well as the widely used Bayes' theorem in machine learning. I have studied the operating principle of Transformer in detail, and deeply understood the operating principle of Naive Bayes through the example of watermelon detection, and implemented it through code.

一、Transformer

Transformer是一个基于自注意力的序列到序列的神经网络模型,广泛应用于NLP领域,能够并行计算处理速度快。

Transformer大体结构如下:

ac11388eba04460b9ff0117621555db3.png

 Transformer详细结构如下:

69e0e20dd30b4b9b9ae7705462198077.png

前馈神经网络:信息仅朝一个方向传播,即从输入到输出。 

1.1 编码器

Transformer的编码器使用的是自注意力,输入一排向量,输出另外一个同样长度的向量。如下图所示:

19606a2c348248c898f58455cbf5e49a.png

 然后在编码器中有很多块相连,每个块都是输入一排向量,输出一排向量。如下图所示:

e9b82a27b7c949839668308d21a96dcd.png

上面的每个块并不是神经网络的一层,每个块里输入一排向量后做自注意力,考虑整个序列的信息,输出另外一排向量。接下来将这排向量“丢”到全连接网络网络里面,输出另外一排向量,这一排向量就是块的输出。如下图所示:

954bf471feb24d98984d87558d379943.png

 在上述参数传递过程中,Transformer还加入了残差连接,即输入b通过自注意力后的输出a两者相加,a+b就是残差连接的结果。将残差结果a+b做一次层归一化后,即对同一特征、同一样本里面不同的维度去计算均值eq?%5Cmu和标准差eq?%5Csigma。最后将层归一化的结果(如公式 1 所示)再残差连接至全连接网络。

残差连接如下图所示:

4e326593cd4047a4b5d59f15e4b7a582.png

公式 1 : 

eq?X_%7Bi%7D%27%3D%5Cfrac%7BX_%7Bi%7D-%5Cmu%20%7D%7B%5Csigma%20%7D

批量归一化:对不同样本不同特征的同一个维度去计算均值跟标准差。 

1.2 解码器

解码器运作过程如下图所示:

dde327d5cb8f4e30b1ebb9d0afbd8c6d.png

1.2.1 自回归解码器

自回归解码器的输出是一个一个的产生的,看到<BOS>开始,<EOS>结束。输入是其前一个时间点的输出。自回归解码器运行过程如下图所示:

5bcf807c88294cbfbbd0ddd73f966be0.png

正因为输出是一个一个产生的,所以有可能导致误差传播,即它把机器的“器”识别错成天气的“气”,接下来解码器会根据错误的识别结果产生它想要产生的期待是正确的输出。如下图所示:

3b1c6e475c2e404aac12f36b7a5cbb26.png

 在理解了自回归解码器的运行原理之后,我们来看看解码器内部结构,如下图所示:

6c06de14d9ba4422951ab5e3f7a420ef.png

我们不难发现解码器中是掩码自注意力,于编码器中的自注意力是有区别的。正因为解码器的输入是前一时间的输出,是一个一个输入的,所以解码器只能考虑左边的信息,无法考虑右边的信息。这时就需要通过一个掩码来阻止每个位置选择其后面的输入信息。

自注意力与掩码自注意力对比如下图所示:

15782b75b53548d18bb1bd6061512de9.png

自注意力图 

5ee6600145204dcdbc01488f1a7dd50b.png

掩码自注意力图 

 掩蔽自注意力具体计算过程如下图所示:

32121f6004884633ab3d9fabf4442d20.png

1.2.2 非自回归解码器

非自回归解码器一次产生一排词元,比如输入4个<BOS>就输出4个词元。自回归解码器与非自回归解码器对比如下图所示:

bb1678aa37a74cadbd96d425d846a1f1.png

因为输出是未知的,所以要通过下面两种方法解决:

  • 分类器:用分类器“吃”编码器的输入,输出是一个数字,该数字代表解码器应该要输出的长度。比如分类器输出4,非自回归的解码器就会“吃”4个<BOS>的词元,产生4个中文的字。
  • 给编码器一堆<BOS>的词元。假设输出的句子的长度有上限,绝对不会超过300个字。给编码器300个<BOS>,就会输出300个字,输出<EOS>右边的的输出就当它没有输出。 

 非自回归解码器具有平行化,不需要做多次解码,一个步骤就可以产生完整句子。所以比自回归解码器跑得快。还可以通过分类器控制输出的长度,但是总体性能不如自回归解码器。

1.2.3 编码器-解码器注意力

编码器-解码器注意力是连接编码器和解码器的桥梁,编码器-解码器注意力在Transformer中的位置如下图所示:

1a165f029758418480d54f01a5601685.png

 解码器中的编码器-解码器注意力的键和值来自编码器的输出,查询q来自解码器前一层的输出。

解码器凭借q去编码器抽取信息,如下图所示:

a7226fbfae954053899864d372132366.png

 eq?v%3D%5Calpha_%7B1%7D%27%5Ctimes%20v%5E%7B1%7D&plus;%5Calpha_%7B2%7D%27%5Ctimes%20v%5E%7B2%7D&plus;%5Calpha_%7B3%7D%27%5Ctimes%20v%5E%7B3%7D

1.3 如何更好训练Transformer

在优化Transformer时,通常计算标准答案与输出分布之间的交叉熵。如下图所示:

57a81d054c714c8b85dfc602a0de6d33.png

交叉熵:用于度量两个概率分布间的差异性信息。p表示真实分布,q表示模型预测分布,交叉熵H(p,q)公式如下:

eq?H%28p%2Cq%29%3D-%5Csum_%7Bi%3D1%7D%5E%7Bn%7Dp_%7Bi%7Dlog%28q_%7Bi%7D%29

损失函数如下:

eq?L%3D-%5Cfrac%7B1%7D%7Bm%7D%5Csum_%7Bi%3D1%7D%5E%7Bm%7D%5Csum_%7Bj%3D1%7D%5E%7Bk%7Dt_%7Bij%7Dlog%28y_%7Bij%7D%29 

 还可以通过复杂机制、束搜索、加入噪声、强化学习、计划采样(输入一些错误信息)、引导注意力(注意力权重从左到右,如下图所示)等去优化Transformer的训练。

8496f90086b64b5abdf871b3bebd05e4.png

二、贝叶斯

贝叶斯神经网络结合了贝叶斯方法和神经网络模型。网络中的参数不是固定值,而是概率分布。

2.1 贝叶斯定理

eq?P%28A%7CB%29%3D%5Cfrac%7BP%28B%7CA%29%5Ccdot%20P%28A%29%7D%7BP%28B%29%7D

  •  eq?P%28A%7CB%29是后验概率;
  • eq?P%28B%7CA%29是条件概率;
  • eq?P%28A%29是先验概率;
  • B通常是测试样本,在判断大小时作为公共的分母,可省略eq?P%28B%29的计算。

即利用已知的先验概率和条件概率来更新对某个事件发生的概率估计。

2.2 朴素贝叶斯的应用

朴素贝叶斯认为特征之间相互独立,将上式B中的各特征记为B_{1}B_{2}B_{3}...... 即:

P(B|A)=P(B_{1},B_{2},...,B_{n}|A)=\prod_{i=1}^{n}P(B_{i}|A)

  • 先验概率:P(A)=\frac{|D_{A}|}{|D|} 
  • 条件概率:
  1. 离散型:P(B_{i}|A)=\frac{|D_{A,B_{i}}|}{|D_{A}|}
  2. 连续型:P(B_{i}|A)\sim N(\mu _{A,i},\sigma ^{2}_{A,i})=\frac{1}{\sqrt{2\pi }\sigma }e^{-\frac{(x-\mu )^{2}}{2\sigma ^{2}}} ;
  3. 特征离散且数量较多时,采用多项式分布;
  4. 特征为二元变量时,采用伯努利分布。

在使用过程中,可以利用半朴素贝叶斯分类器,以减少特征独立性假设的限制。

2.3 代码

通过train表格的数据去判断test表格中西瓜的好坏。

import pandas as pd
import numpy as np

# 将数据集分别保存在excel表中的不同工作表中,用pandas导入,其余都用numpy来做
def load_data():
    # 导入数据
    train_data = pd.read_excel(r'./data/NaiveBayesClassifier/WatermelonData.xlsx', sheet_name='train')
    test_data = pd.read_excel(r'./data/NaiveBayesClassifier/WatermelonData.xlsx', sheet_name='test')
    # ['色泽', '根蒂', '敲声', '纹理', '脐部', '触感', '密度', '含糖率', '好瓜‘]
    train_data = np.array(train_data)[:, 1:]  #所有行(默认第二行),从第二列到最后一列
    test_data = np.array(test_data)[:, 1:]

    return train_data, test_data

# 训练贝叶斯分类器,其实就是计算离散属性的先验概率和条件概率、连续属性的均值和方差
def train_bayes(train_data):  # 13行9列
    # 先计算先验概率P(c),即好瓜和坏瓜的个数分别占总训练集样本个数的比例
    good_num = 0
    bad_num = 0  # 好瓜与坏瓜的个数,后面拉普拉斯修正也要用
    for i in range(train_data.shape[0]):  # 一行一行地看,shape[0]指行数
        if train_data[i, -1] == "是":
            good_num += 1
        elif train_data[i, -1] == "否":
            bad_num += 1
    # 得到好瓜6个,坏瓜7个
    # 计算先验概率
    pc_good = good_num / (good_num+bad_num)
    pc_bad = bad_num / (good_num+bad_num)

    # 将分类结果的好瓜与坏瓜分开,典的第一个键值对保存该属性的取值个数,例如本训练集中色泽有三种取值(青绿,乌黑,浅白),就保存每一个属性的取值个数,为了进行拉普拉斯修正
    good_melon = [{'sumType': 0} for i in range(8)]  #代表8列表格元素名称,每个字典元素都包含一个键值对:'sumType': 0
    bad_melon = [{'sumType': 0} for i in range(8)]

    # 计算条件概率P(xi | c),例如计算在好瓜中色泽为青绿的个数占好瓜总数的比例
    for j in range(train_data.shape[1] - 3):  # 一列一列地看,shape[1]指列数,最后三列不看
        # 一行一行地看,这两行正反都一样
        for i in range(train_data.shape[0]):
            # 首先保证是好瓜
            if train_data[i, -1] == "是":
                # 如果字典数组中已经有了这个属性对应的值(如青绿)就直接加一
                if train_data[i, j] in good_melon[j]:
                    good_melon[j][train_data[i, j]] += 1
                else:
                    good_melon[j][train_data[i, j]] = 1  # 如果没有就创建一个键值对并赋值为1
                    good_melon[j]['sumType'] += 1  # 该属性增加一个取值,相当于多一个特征

            else:  # 如果是坏瓜,把上面good_melon换成bad_melon就行
                if train_data[i, j] in bad_melon[j]:  # 如果字典数组中已经有了这个属性对应的值(如青绿)就直接加一
                    bad_melon[j][train_data[i, j]] += 1
                else:
                    bad_melon[j][train_data[i, j]] = 1  # 如果没有就创建一个键值对并赋值为1
                    bad_melon[j]['sumType'] += 1  # 该属性增加一个取值

    # 因为拉普拉斯修正中每一个属性的取值是整个训练集的取值,上面只是单独收集好瓜与坏瓜
    for i in range(len(good_melon) - 2):
        # if或者elif成立说明有属性只在好瓜和坏瓜中存在,要统一一下
        if good_melon[i]['sumType'] > bad_melon[i]['sumType']:
            # 统一属性取值个数
            bad_melon[i]['sumType'] = good_melon[i]['sumType']
            # 统一取值
            key = good_melon[i].keys() - bad_melon[i].keys()
            bad_melon[i][key] = 0
            print(bad_melon[i][key])
        elif good_melon[i]['sumType'] < bad_melon[i]['sumType']:
            # 统一属性取值个数
            good_melon[i]['sumType'] = bad_melon[i]['sumType']
            # 统一取值
            key = list(bad_melon[i].keys() - good_melon[i].keys())
            for j in key:
                good_melon[i][j] = 0

    # 上面只是统计了个数,下面才是计算条件概率,直接用统计出来的数值除以好瓜或者坏瓜的个数
    for i in range(train_data.shape[1] - 3):  # 有train_data.shape[0] - 3个是离散属性,需要进行拉普拉斯修正
        for key, value in good_melon[i].items():  # 遍历每一个键值对,好瓜
            if key != "sumType":  # 除了字典的第一个值
                good_melon[i][key] = (good_melon[i][key] + 1) / (good_num + good_melon[i]['sumType'])
        for key, value in good_melon[i].items():  # 遍历每一个键值对,坏瓜
            if key != "sumType":  # 除了字典的第一个值
                bad_melon[i][key] = (bad_melon[i][key] + 1) / (bad_num + bad_melon[i]['sumType'])

    # 以上是离散属性的先验概率和条件概率
    # 下面是连续属性的均值和方差 -1是含糖率,-2是密度
    good_melon[-1]['mean'] = np.mean(train_data[:6, -2], axis=0)  #mean键的值是第2-6行的倒数第2列的平均值
    good_melon[-1]['var'] = np.var(train_data[:6, -2], axis=0)  #var键的值是第2-6行的倒数第3列的均值
    bad_melon[-1]['mean'] = np.mean(train_data[6:, -2], axis=0)
    bad_melon[-1]['var'] = np.var(train_data[6:, -2], axis=0)

    good_melon[-2]['mean'] = np.mean(train_data[:6, -3], axis=0)
    good_melon[-2]['var'] = np.var(train_data[:6, -3], axis=0)
    bad_melon[-2]['mean'] = np.mean(train_data[6:, -3], axis=0)
    bad_melon[-2]['var'] = np.var(train_data[6:, -3], axis=0)

    return pc_good, pc_bad, good_melon, bad_melon

# 开始对测试集分类
def classify_bayes(pc_good, pc_bad, good_melon, bad_melon, test_data):
    # 对每一个测试数据进行计算好瓜与坏瓜的概率
    for i in range(test_data.shape[0]):
        #每一个测试数据都要先令其等于先验概率的对数,后面全部取对数直接相加
        good_probability = np.log(pc_good)
        bad_probability = np.log(pc_bad)
        for j in range(test_data.shape[1] - 3):  # 先处理离散属性
            if test_data[i][j] in good_melon[j]:  # 如果这个特征训练集没有就跳过
                good_probability += np.log(good_melon[j][test_data[i][j]])  # 转化为对数相加
            if test_data[i][j] in bad_melon[j]:
                bad_probability += np.log(bad_melon[j][test_data[i][j]])
        for j in range(test_data.shape[1] - 3, test_data.shape[1] - 1):  # 处理连续属性
            good_probability += np.log((2 * np.pi * good_melon[j]['var']) ** (-1 / 2)) + (-1 / 2) * ((test_data[i][j] - good_melon[j]['mean']) ** 2) / (good_melon[j]['var'] ** (-2))
            bad_probability += np.log((2 * np.pi * bad_melon[j]['var']) ** (-1 / 2)) + (-1 / 2) * ((test_data[i][j] - bad_melon[j]['mean']) ** 2) / (bad_melon[j]['var'] ** (-2))

        print(f'测试数据{i + 1}是好西瓜的概率为{good_probability}\n测试数据{i + 1}是坏西瓜的概率为{bad_probability}')
        if good_probability > bad_probability:
            print(f'所以测试数据{i + 1}是好西瓜!\n')
        else:
            print(f'所以测试数据{i + 1}是坏西瓜!\n')

if __name__ == "__main__":
    train_data, test_data = load_data()
    pc_good, pc_bad, good_melon, bad_melon = train_bayes(train_data)
    classify_bayes(pc_good, pc_bad, good_melon, bad_melon, test_data)

 代码运行预测结果如下:

22603f7d627247b7a6d343660d8ea3d3.png

代码中在很多处计算次数的位置都进行了+1操作,这是拉普拉斯修正,即在每个可能的事件发生次数都加一个常数,确保概率不为0,适用于处理小样本数据的情况。

总结

本周的学习到此结束,下周将完成深度学习经典神经网络模型的学习。主要会涉及生成式对抗网络和自监督学习,后期会通过代码从零敲写以上模型。

如有错误,请各位大佬指出,谢谢!

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值