用神经网络实现逻辑门(下)
与、与非和异或门
大家好!!在开始使用神经网络实现逻辑门的第 2 部分之前,您可能想先浏览一下第 1 部分。
从第 1 部分中,我们已经知道我们有两个输入神经元或 x 向量,值为 x1 和 x2,1 是偏差值。输入值,即 x1、x2 和 1,乘以它们各自的权重矩阵,即 W1、W2 和 W0。相应的值然后被馈送到求和神经元,在那里我们得到求和值,即
作者图片
现在,这个值被馈送到一个神经元,该神经元具有一个非线性函数(在我们的例子中为 sigmoid ),用于将输出缩放到期望的范围。如果输出小于 0.5,则 sigmoid 的比例输出为 0,如果输出大于 0.5,则为 1。我们的主要目的是找到权重值或权重向量,这将使系统能够充当特定的门。
实现与门
与门运算是输入之间的简单乘法运算。如果任何输入为 0,则输出为 0。为了实现 1 作为输出,两个输入都应该是 1。下面的真值表传达了同样的信息。
“与”门的真值表和使系统充当“与”和“与非”门的权值,由作者形象
由于我们有 4 个输入选择,权重必须满足所有输入点的与门条件。
(0,0)案例
考虑输入或 x 向量为(0,0)的情况。在这种情况下,Z 的值就是 W0。现在,W0 必须小于 0,因此 z 小于 0.5,输出或ŷ为 0,满足与门的定义。如果大于 0,则 Z 通过 sigmoid 函数后的值将为 1,这违反了与门条件。因此,我们可以说,对于分辨率,W0 必须是负值。但是 W0 值多少?继续阅读…
(0,1)案例
现在,考虑输入或 x 向量为(0,1)的情况。这里 Z 的值将是 W0+0+W2*1。这是 sigmoid 函数的输入,应该具有小于 0 的值,使得输出小于 0.5 并被分类为 0。从今以后,W0+W2<0。如果我们把 W0 的值取为-3(记住 W0 的值必须是负的),把 W2 的值取为+2,那么结果出来就是-3+2,也就是-1,这似乎满足上面的不等式,与 and 门的条件相当。
(1,0)案例
类似地,对于(1,0)的情况,W0 的值将是-3,W1 的值可以是+2。请记住,只要不等式不变,您可以采用权重 W0、W1 和 W2 的任何值。
(1,1)案例
在这种情况下,输入或 x 向量是(1,1)。在这种情况下,Z 的值就是 W0+W1+W2。现在,总输出必须大于 0,以便输出为 1,满足与门的定义。从前面的场景中,我们发现 W0,W1,W2 的值分别为-3,2,2。将这些值放入 Z 等式中会产生输出-3+2+2,它是 1 且大于 0。因此,在通过 sigmoid 函数后,这将被分类为 1。
关于“与”和“与非”实现的最后一点说明
因此,分隔上述四点的直线是一个等式 W0+W1x1+W2x2=0,其中 W0 是-3,W1 和 W2 都是+2。因此,四点分隔线的等式为 x1+x2=3/2。因此,或非门的实现将类似于将权重改变为 W0 等于 3,以及 W1 和 W2 等于-2
继续讨论异或门
对于 XOR 门,下图左侧的真值表显示,如果有两个补码输入,则只有输出为 1。如果输入相同(0,0 或 1,1),则输出将为 0。绘制在右侧 x-y 平面上的点给我们的信息是,它们不像 OR 和 and 门那样是线性可分的(至少在二维上)。
XOR 门真值表和 x-y 平面上数值的绘制,图片由作者提供
两种可能解决方案
为了解决上述可分离性问题,可以采用两种技术,即添加非线性特征(也称为核心技巧)或添加额外的层(也称为深度网络)
XOR(x1,x2)可以被认为是 NOR(或非(x1,x2)和(x1,x2))
实现异或门的解决方案
异或网络的权重
在这里,我们可以看到该层从 2 增加到 3,因为我们添加了一个正在计算 AND 和 NOR 运算的层。输入保持不变,外加偏置输入 1。右下方的表格显示了作为输入的 4 个输入的输出。这里需要注意的一个有趣的事情是,权重的总数增加到了 9。从输入层到第二层共有 6 个权重,从第二层到输出层共有 3 个权重。第二层也称为隐藏层。
网络的权重使其充当异或门,图片由作者提供
谈到整个网络的权重,从上面和第 1 部分的内容中,我们已经推导出系统作为与门和或非门的权重。我们将使用这些权重来实现 XOR 门。对于层 1,总共 6 个权重中的 3 个将与或非门的权重相同,剩余的 3 个将与与门的权重相同。因此,NOR 门的输入权重将是[1,-2,-2],and 门的输入权重将是[-3,2,2]。现在,从第 2 层到最后一层的权重将与或非门的权重相同,即[1,-2,-2]。
通用逼近定理
它指出,任何函数都可以表示为具有一个隐藏层的神经网络,以达到期望的精度
几何解释
两类 3D 图像的线性可分性
有了这个,我们可以把增加额外的层想象成增加额外的维度。在三维可视化后,X 和 O 现在看起来是可分的。红色平面现在可以分隔这两个点或类。这样的平面叫做超平面。综上所述,以上几点在更高维度上是线性可分的。
交叉:资产价格的有利可图的交叉点
使用 Python 创建移动平均线交易策略
作为一个经济学出身的人,我不禁从金融的角度来看待这个世界。当我决定转向技术分析时,我仍然希望将我的金融领域知识融入到技术部分。交易中的技术分析对我这个位置的很多人来说是一个非常热门的领域,所以我想建立一个带回溯测试的量化交易策略来结合这两个领域。这个项目的完整存储库可以在这里找到。
- 免责声明:本博客和交易策略用于教育目的,我不建议用于专业投资*
建立投资组合
我们的交易策略将采用各种版本的移动平均线公式,使我们的交易决策适应投资组合中资产价格的行为。我们将从选择多样化的资产组合开始:特斯拉(TSLA)、强生(JNJ)和苹果(AAPL)。这三种资产是蓝筹股,分别是汽车、医疗保健和科技行业的主要驱动力。我们首先使用 yfinance 库从一个确定的起点和终点收集历史价格数据。这些作为单独的序列引入,然后合并到一个具有日期时间索引的数据帧中。
我们现在有超过五年的收盘价(1377 个条目)用于我们的策略。
简单移动平均线
因为这是我建立的第一个技术交易策略,我想从一个最古老和直接的方法开始,我们将使用价格的移动平均时间序列来对抗实际价格的趋势。通过对一个时间序列进行移动平均,我们能够大大减少序列中的噪声或方差。这将允许我们以更简单的方式理解资产的长期行为。
使用高频算法,交易可以在几分之一秒内发生,或者以长期方式持续多年。我们将采用短期和长期的移动平均线。
让我们来看看,对于我们投资组合中的一种资产:特斯拉,同一时间序列的这些变化是如何随着时间的推移而变化的。注意简单移动平均线(SMA)是如何随着时间的增长而变得平滑的,这表明价格变动的可变性减少了。
重要的是要记住,当我们使用 SMA 时间序列时,我们实际上落后于原始价格序列。注意橙色的 20 天 SMA 是如何在灰色的 TSLA 价格线的 01/15 开始日期之后开始的。蓝绿色 100 天 SMA 开始得更晚。滞后是由于简单移动平均线的数学本质,但我们不能简单地忽略这种滞后的存在,因为当谈到交易时,时间是最敏感的。那么我们如何解释这种滞后呢?指数移动平均线(EMA)。
指数移动平均线
EMA 和 SMA 非常相似,除了它有一个额外的衰减参数,α (alpha)。均线有助于限制滞后,因为它更重视最近的观察。SMA 只是对观察值进行平均加权。
我们可以看到,使用均线可以更同步、更精确地捕捉方差减少的时间序列。现在我们有了一个好的数据结构,我们将继续发展我们的交易策略。
制定贸易战略
如上所述,均线和均线都滞后于资产的实际价格。换句话说,资产的实际价格比移动平均价格变化快得多。因为实际价格更敏感,我们可以用两者的交叉点(实际价格和均线)来表示做空(卖出)或做多(买入)。
- 当原始资产价格从低于的穿过均线系列时,我们将卖出所有空头头寸,买入一个单位的多头头寸
- 当原始资产从上方的穿过均线系列时,我们会平仓,通过卖出一个单位的资产来做空
**注意:**我们不考虑仓位大小。我们假设我们的可用资本被完全使用,并且在我们投资组合的三种资产中平均分配(1/3)。在多头条件下,每项资产的权重为 1/3,而空头条件下的权重为-1/3。
我们用原始价格和均线序列之间的差异来表示我们所有的资金都被使用了,不管是多头还是空头。
这将为我们提供一个包含价格差异的新数据框架,但我们确实希望将价格差异反映为“买入”或“卖出”的信号。我们可以使用一个 numpy 方法来分配这些。
重要提示:因为我们使用的是原始时间序列中资产的“收盘”价格,我们实际上并不知道该价格高于均线价格。假设我们有一个多头头寸实际上与我们模型的数学不一致。只能假设收盘时我们会做多,也就是说明天的开仓也会做多。明天做多可以通过将我们的头寸向前移动一天来表示。
让我们看看我们的均线和原始资产价格是什么样的。我们还将绘制信号的时间序列,看看我们的头寸在日期窗口的过程中是如何变化的。
记住,我们的多头买入头寸用正的 1/3 (0.333)来表示,而我们的卖出头寸用负的 1/3(0.333)来表示(T2)。我们现在有了整个系列的投资组合交易头寸的可视化表示。
回溯测试我们的策略
我们的策略独立对待每项资产,我们对一项资产的头寸不会影响其他资产的头寸。我们将使用这些信息,通过连续对数观测值的差异来获得我们的投资组合回报。
我们在整个时间段的总回报要求我们将持仓数据框架乘以资产的对数回报数据框架。
我们的数据框架现在包含了每个单个条目的回报,但是我们必须使用累计总和来查看总回报。然后,我们使用 numpy 将这些回报转换成相对回报。然后,我们将绘制出我们每项资产的累积对数收益以及它们的相对收益。
我们将把所有的对数收益相加,然后把它们转换成相对收益,从而得到一个统计上精确的收益近似值。绘制时,请记住这些值代表百分比,因此不要忘记将系列 y 值乘以 100 以获得准确的数字。
决赛成绩
现在我们有了回报,我们现在可以使用复合回报假设来返回我们的终身投资组合回报以及我们的平均年回报。
- 终身投资组合回报率= 79.31%
- 平均。年回报率= 11.66%
自 1962 年以来,美国股票的平均年回报率约为 11%。这意味着我们的移动平均策略将允许您的投资组合每年获得“平均”回报。现在,这些只是市场上市值最大的三只股票。一个真正的投资组合将更加多样化,并需要尽职调查来保证高回报。我们的交易策略也做了很多假设,可以改进,但希望这是对金融工具进行技术分析的一个强有力的起点。
来自《走向数据科学》编辑的提示: 虽然我们允许独立作者根据我们的 规则和指导方针 发表文章,但我们并不认可每个作者的贡献。你不应该在没有寻求专业建议的情况下依赖一个作者的作品。详见我们的 读者术语 。
从零开始实现朴素贝叶斯算法。
在本文中,我将展示朴素贝叶斯分类器的一个基本实现,以及它是如何工作的。
图片来自 www.i2tutorials.com
简介
朴素贝叶斯分类器是一种非常流行的基于贝叶斯定理的监督机器学习算法。这是一种简单但非常强大的算法,适用于大型数据集和稀疏矩阵,如预处理的文本数据,它根据字典中的单词数创建了数千个向量。它在文本数据项目(如情感数据分析)中工作得非常好,在文档分类项目中表现良好,并且在预测分类数据(如垃圾邮件分类)中也非常出色。
它用于解决许多不同的问题陈述,并且在训练模型时非常快,因为朴素贝叶斯分类器完全在概率上工作,所以转换发生得很快。
理解背后的数学和统计学
贝叶斯定理描述了一个事件发生的概率,基于可能与该事件相关的条件的先验知识。
首先,我们来看一个条件概率的公式,并尝试推导贝叶斯定理:
P(B|A) = P(A∩B)/P(B),
其中,给定 A 的 B 的概率,即事件 A 已经发生时事件 B 的概率,等于交集 B 的概率(即 A 和 B 事件都发生的概率)除以 B 的概率。
或者 P(A|B) = P(B∩A)/P(A),
其中,给定 B 的概率,即事件 B 已经发生时事件 A 的概率,等于 B 交集 A 的概率(即 B 和 A 事件都发生的概率)除以 A 的概率。
我们再仔细看看,我们看到 P(A∩B)和 P(B∩A)基本相同,可以写成 P(A∩B) = P(B∩A)。由于它们是相同的,我们可以得到两个公式,并将分母移到等式的左边:
P(A∩B) = P(A|B) * P(B),P(B∩A) = P(B|A) * P(A)并使它们相等:
P(A|B) * P(B) = P(B|A) * P(A)。
所以,当我们想找到给定 B 的概率时,我们可以这样写方程:
P(A|B) = P(B|A) * P(A) / P(B) ,这是贝叶斯定理的方程式。
贝叶斯定理方程在算法中的应用
让我们打破我们的等式,了解它是如何工作的:
- P(A|B) 是给定预测器 ( 属性)的类 ( 目标)的后验概率。
- P(B) 是类的先验概率。
- P(B|A) 为似然性,即预测器给定类的概率。
- P(A) 是预测器的先验概率。
为了计算后验概率,首先我们需要计算每个属性相对于目标的频率。然后,将频率转换为似然值,最后使用朴素贝叶斯方程计算每类的后验概率。具有最高后验概率的类是预测的结果。
在我的例子中,我使用了两个数据集:
由于两个数据集都有连续的属性,我选择了高斯分布来估计朴素贝叶斯分类器中似然概率的参数。该分布由两个参数表征,均值和方差。高斯密度函数的公式,来源于维基百科,看起来是这样的:(1/√2pi σ) exp((-1/2)*((x-μ))/(2 *σ)),其中μ是均值,σ是方差,σ是方差的平方根(标准差)。
首先,我们需要计算每一列的平均值和方差,并将其转换为 numPy 数组,以便将来计算:
计算每列的平均值和方差
接下来,让我们将高斯密度函数转换为代码:
从高斯密度函数计算概率
最后一步是计算先验和后验概率:
做预测
最后,所有的助手方法现在都可以在 fit 和 predict 方法中使用了:
拟合和预测方法
在对鸢尾花分类数据训练我的模型后,我得到了非常好的 92%的准确率。
鸢尾花分类中的真实与预测
我将它与 scikit-learn 高斯贝叶斯分类器进行了比较,得到了相同的结果,这非常令人印象深刻,因为我的算法只是准系统,需要很多改进。
对于电子邮件垃圾分类问题,我的模型仅执行了 78%的准确率,然而它与 scikit-learn 高斯贝叶斯分类器的准确率 81.8%相差不远。
垃圾邮件分类中的真实值与预测值
结论
我的朴素贝叶斯分类器算法实现远非理想,它需要许多改进和修改才能做出更好的预测,尤其是对文本数据,然而,与 sklearn 库的算法相比,它的性能仍然非常好。
注意:
这个项目是为 Lambda 学校(Bloom Technology now) 做的,作为计算机 科学课程的一部分。
代码实现的 Github 链接:从头开始的朴素贝叶斯分类器
用 Python 在 2 分钟内实现朴素贝叶斯
我不知道这么容易!
在本文中,我将提供著名的朴素贝叶斯算法的一个非常简短和直观的实现。为了理解这个简单的概念,理解下图的意思就够了:)满足贝叶斯定理!
在实现高斯朴素贝叶斯分类器之前,我们应该写 2 个简单的假设:
- 我们的数据呈正态分布
- 我们希望我们的数据列有条件地相互独立
这个模型最好的一点就是朴素贝叶斯效果很好,即使这些条件都不满足!让我们通过 3 个简单的步骤最终实现这个模型!
- 准备数据集
首先,和往常一样,我会准备资料。今天,我将使用一个众所周知的虹膜数据集,我们将使用它进行多类分类。
2。计算列车组统计数据
现在,我将按类对我们的训练集进行分组,并计算按类分组的每一列的平均值和标准差。此外,我将计算类先验概率,它简单地是类元素的数量除以训练集中的元素总数。
哇,这似乎是我们的适应功能。
3。计算验证集的标签
最后一步是计算贝叶斯定理的概率。对于每个元素,我将计算它属于每个类的概率。注意,这里我没有除以 P(B),因此,我没有得到真实的概率值。
另外,请注意,我在以下面的方式执行乘法时使用了独立性假设。
现在让我们看看我们的预测的准确性和来自 Sklearn 库的 GaussianNB 分类器的预测的准确性。
哇,看起来完全一样!感谢您的阅读:)
推荐书籍
你可以在我的 网站 上查看其他帖子
使用 keras 实现神经机器翻译
在 keras 中使用序列到序列算法逐步实现神经机器翻译。
在本文中,您将逐步了解如何使用英语作为源语言,西班牙语作为目标语言来构建神经机器翻译器。我们将在 keras 中使用长短期记忆(LSTM)单位
先决条件:
简单解释用于神经机器翻译的序列到序列模型(NMT)
towardsdatascience.com](/intuitive-explanation-of-neural-machine-translation-129789e3c59f)
在本文中,我们将使用编码器和解码器框架,以 LSTM 为基本块,创建一个序列到序列模型
序列到序列模型将源序列映射到目标序列。源序列是机器翻译系统的输入语言,目标序列是输出语言。
序列对序列模型
使用无注意机制的教师强制逐步实现神经机器翻译(NMT)。实现是使用 keras 库,以 LSTM 为基本块
实施 NMT 的高级步骤包括
- 从包含源句子和目标句子的文件中读取数据
- 通过转换为小写、删除空格、特殊字符、数字和引号来清除数据
- 分别使用 START_ 和 _END 标记目标句子的开头和结尾,用于训练和推理
- 创建唯一源词和目标词的字典,以进行向量转换,反之亦然
- 打乱数据以便更好地归纳
- 将数据集拆分为训练和测试数据
- 创建数据;我们将使用 fit_generator()将数据拟合到模型中
- 使用嵌入和 LSTM 图层构建编码器
- 使用嵌入层和 LSTM 层构建解码器,并从嵌入层和编码器状态获取输入。
- 编译模型并训练模型
- 根据模型进行预测
导入所需的库
**import pandas as pd
import numpy as np
import string
from string import digits
import matplotlib.pyplot as plt
%matplotlib inline
import re
from sklearn.utils import shuffle
from sklearn.model_selection import train_test_split
from keras.layers import Input, LSTM, Embedding, Dense
from keras.models import Model**
从文件中读取数据
在这里 阅读包含我们从 下载的英语-西班牙语翻译的文件
# Path to the data txt file on disk.**data_path = "\\NMT\\spa-eng\\spa.txt"**# open the file eng-spa.txt and read
**lines= pd.read_table(data_path, names =['source', 'target', 'comments'])**#printing sample data from lines
lines.sample(6)
清理源句和目标句。
我们应用以下文本清理
- 将文本转换为小写
- 删除引号
- 删除所有特殊字符,如“@,!, *, $, #, ?、%等。”
- 清除源句子和目标句子中的数字。如果源语言或目标语言对数字使用不同的符号,那么删除这些符号
- 删除空格
# convert source and target text to Lowercase
**lines.source=lines.source.apply(lambda x: x.lower())
lines.target=lines.target.apply(lambda x: x.lower())**# Remove quotes from source and target text
**lines.source=lines.source.apply(lambda x: re.sub("'", '', x))
lines.target=lines.target.apply(lambda x: re.sub("'", '', x))**# create a set of all special characters
**special_characters= set(string.punctuation)**# Remove all the special characters
**lines.source = lines.source.apply(lambda x: ''.join(char1 for char1 in x if char1 not in special_characters))
lines.target = lines.target.apply(lambda x: ''.join(char1 for char1 in x if char1 not in special_characters))**# Remove digits from source and target sentences
**num_digits= str.maketrans('','', digits)
lines.source=lines.source.apply(lambda x: x.translate(num_digits))
lines.target= lines.target.apply(lambda x: x.translate(num_digits))**# Remove extra spaces
**lines.source=lines.source.apply(lambda x: x.strip())
lines.target=lines.target.apply(lambda x: x.strip())
lines.source=lines.source.apply(lambda x: re.sub(" +", " ", x))
lines.target=lines.target.apply(lambda x: re.sub(" +", " ", x))**
给目标句子添加开始和结束标记。
向目标句子添加 START_ 和 _END 标记对于训练和推理非常有用。这些标签有助于知道何时开始翻译,何时结束翻译。
# Add start and end tokens to target sequences
**lines.target = lines.target.apply(lambda x : 'START_ '+ x + ' _END')**
lines.sample(6)
START_ tag 标记目标句子的开始,而 _END 标记标记目标句子的结束。
从数据集中为源语言和目标语言创建一组独特的单词,并按字母顺序对它们进行排序
# Find all the source and target words and sort them
# Vocabulary of Source language
**all_source_words=set()
for source in lines.source:
for word in source.split():
if word not in all_source_words:
all_source_words.add(word)**# Vocabulary of Target
**all_target_words=set()
for target in lines.target:
for word in target.split():
if word not in all_target_words:
all_target_words.add(word)**
# sort all unique source and target words
**source_words= sorted(list(all_source_words))
target_words=sorted(list(all_target_words))**
未排序的源词汇集
找出数据集中源句子和目标句子的最大长度
#Find maximum sentence length in the source and target data
**source_length_list=[]
for l in lines.source:
source_length_list.append(len(l.split(' ')))
max_source_length= max(source_length_list)**
**print(" Max length of the source sentence",max_source_length**)**target_length_list=[]
for l in lines.target:
target_length_list.append(len(l.split(' ')))
max_target_length= max(target_length_list)****print(" Max length of the target sentence",max_target_length**)
为数据集中所有唯一的源词和目标词创建词索引词典和词索引词典。
要向量的单词的大小将基于源和目标词汇的长度
# creating a word to index(word2idx) for source and target
**source_word2idx= dict([(word, i+1) for i,word in enumerate(source_words)])
target_word2idx=dict([(word, i+1) for i, word in enumerate(target_words)])**
索引词典的源词
#creating a dictionary for index to word for source and target vocabulary
**source_idx2word= dict([(i, word) for word, i in source_word2idx.items()])** print(source_idx2word)**target_idx2word =dict([(i, word) for word, i in target_word2idx.items()])**
源词汇的词典索引
混洗数据
洗牌有助于
- 减少方差
- 确保模型保持通用,减少过度拟合
- 不同时期之间的批次看起来不一样
- 使模型更加健壮
#Shuffle the data
**lines = shuffle(lines)**
创建训练和测试数据集
# Train - Test Split
**X, y = lines.source, lines.target**
**X_train, X_test, y_train, y_test = train_test_split(X, y, test_size = 0.1)**
X_train.shape, X_test.shape
训练和测试数据集中的样本
创建用于训练编码器-解码器模型的数据。
我们将使用***fit _ generator()***而不是 fit() 方法,因为我们的数据太大,无法放入内存。 fit_generator() 需要一个底层函数来生成数据。
我们创建底层函数***generate _ batch()***用于批量生成数据
***fit _ generator()***将从底层函数 接受一批数据,generate_batch()
为了训练序列到序列模型,我们需要为
- 编码器输入:2D 数组的形状为 (batch_size,最大源语句长度)。对于 128 的 batch_size 和 47 的最大源句子长度, encoder_input 的形状将是(128,47)
- 解码器输入:2D 数组将为 *(batch_size,最大目标句子长度)*的形状。对于 128 的 batch_size 和 55 的最大目标句子长度, 解码器输入的形状将是(128,55)
- 解码器输出:3D 数组的形状为 (batch_size,最大目标句子长度,目标句子中唯一单词的数量)。对于 128 的 batch_size 和 55 的最大目标句子长度, 解码器输出的形状将是(128,55, 27200 )。
target_sentence 中唯一单词的数量是 27199,我们用零填充,因此解码器输出中的第三个参数是 27200
# Input tokens for encoder
**num_encoder_tokens=len(source_words)**# Input tokens for decoder zero padded
**num_decoder_tokens=len(target_words) +1**
我们现在创建 生成器 _ 批处理 函数()
**def generate_batch(X = X_train, y = y_train, batch_size = 128):**
''' Generate a batch of data '''
** while True:
for j in range(0, len(X), batch_size):**
**encoder_input_data** = np.zeros(**(batch_size, max_source_length)**,dtype='float32')
**decoder_input_data** = np.zeros((**batch_size, max_target_length**),dtype='float32')
**decoder_target_data** = np.zeros((**batch_size, max_target_length, num_decoder_tokens**),dtype='float32')
**for i, (input_text, target_text) in enumerate(zip(X[j:j+batch_size], y[j:j+batch_size])):**
**for t, word in enumerate(input_text.split()):****encoder_input_data[i, t] = source_word2idx[word]**
for t, word in enumerate(target_text.split()):
**if t<len(target_text.split())-1:
decoder_input_data[i, t] = target_word2idx[word]** # decoder input seq
if t>0:
# decoder target sequence (one hot encoded)
# does not include the START_ token
# Offset by one timestep
#print(word)
**decoder_target_data[i, t - 1, target_word2idx[word]] = 1.**
**yield([encoder_input_data, decoder_input_data], decoder_target_data)**
我们将使用教师强制来训练序列到序列模型,以便更快更有效地训练解码器。
教师强制算法通过提供前一个时间戳的实际输出而不是上一个时间步的预测输出作为训练期间的输入来训练解码器。
解码器学习在 t+1 时间步长生成一个字,考虑时间步长 t 的实际输出和编码器的内部状态;因此,我们将解码器输出偏移一个时间步长。
构建序列对序列模型
设置基本参数
我们设置必要的参数,如
- 训练样本数量
- 验证样本的数量
- 用于创建训练数据的 batch_size
- 训练的时代
- 编码空间的潜在维度
**train_samples = len(X_train)
val_samples = len(X_test)
batch_size = 128
epochs = 50
latent_dim=256**
构建模型
使用 LSTM 构建编码器和解码器。编码器将对源语言的输入句子进行编码。编码器的隐藏状态和单元状态将作为输入与实际目标序列一起传递给解码器。
构建编码器
编码器将对输入序列进行编码。我们通过输入层传递输入。第一个隐藏层将是嵌入层。嵌入将大的稀疏向量转换成密集的低维空间,保持语义关系。
将三个参数传递给Embedding();第一个参数是词汇量的大小;第二个参数是密集嵌入的维度。我们将 mask_zero 设置为 True ,因为这意味着输入值 0 是一个特殊的“填充”值,应该被屏蔽掉。
创建 LSTM 层,仅将 return_state 设置为 True ,因为我们希望保留编码器的隐藏状态和单元状态。我们丢弃 encoder_output,并保留要传递给解码器的 LSTM 的隐藏状态和单元状态
# Define an input sequence and process it.
**encoder_inputs = Input(shape=(None,))**
**enc_emb = Embedding(num_encoder_tokens, latent_dim, mask_zero = True)(encoder_inputs)**
**encoder_lstm = LSTM(latent_dim, return_state=True)****encoder_outputs, state_h, state_c = encoder_lstm(enc_emb)**# We discard `encoder_outputs` and only keep the states.
**encoder_states = [state_h, state_c]**
编码器
构建解码器
我们为 decoder_inputs 创建输入层;嵌入又是解码器中的第一个隐藏层。
LSTM 层将返回输出序列以及内部状态。内部状态仅在推断阶段使用,在训练阶段不使用。
解码器中的 LSTM 从嵌入层和编码器状态获取输入。我们将 softmax 激活应用于密集层,然后最终生成解码器输出
# Set up the decoder, using `encoder_states` as initial state.
**decoder_inputs = Input(shape=(None,))**
**dec_emb_layer = Embedding(num_decoder_tokens, latent_dim, mask_zero = True)
dec_emb = dec_emb_layer(decoder_inputs)**# We set up our decoder to return full output sequences,
# and to return internal states as well. We don't use the
# return states in the training model, but we will use them in inference.
**decoder_lstm = LSTM(latent_dim, return_sequences=True, return_state=True)**
**decoder_outputs, _, _ = decoder_lstm(dec_emb,
initial_state=encoder_states)**
**decoder_dense = Dense(num_decoder_tokens, activation='softmax')
decoder_outputs = decoder_dense(decoder_outputs)**
定义模型
序列到序列模型将编码器和解码器输入转换为解码器输出
# Define the model that takes encoder and decoder input
# to output decoder_outputs
**model = Model([encoder_inputs, decoder_inputs], decoder_outputs)**
训练模型
为了训练模型,我们首先编译模型,然后将数据拟合到模型中
我们使用“rmsprop”优化器编译模型;使用 categorical _ crossentropy,就像我们使用 categorial 标签一样,这是一个热编码的向量
model.compile(optimizer=’rmsprop’, loss=’categorical_crossentropy’, metrics=[‘acc’])
设置参数以适应模型
train_samples = len(X_train) # Total Training samples
val_samples = len(X_test) # Total validation or test samples
batch_size = 128
epochs = 100
使用***【fit _ generator()***拟合模型。我们已经创建了用于生成数据的底层函数, generate_batch() 用于生成训练和测试数据集。
steps_per_epoch 通过将训练样本总数除以批量大小来计算。当我们达到步数时,我们开始一个新的纪元
**model.fit_generator(generator = generate_batch(X_train, y_train, batch_size = batch_size),
steps_per_epoch = train_samples//batch_size,
epochs=epochs,
validation_data = generate_batch(X_test, y_test, batch_size = batch_size),
validation_steps = val_samples//batch_size)**
保存权重,以便以后加载它们进行推理
model.save_weights(‘nmt_weights_100epochs.h5’)
可以从保存的权重文件中加载权重
model.load_weights('nmt_weights_100epochs.h5')
从模型中做出推论
在推理过程中,我们希望解码未知的输入序列来预测输出。
推理步骤
- 将输入序列编码成 LSTM 的隐藏状态和单元状态
- 解码器将一次预测一个序列。解码器的第一个输入将是编码器的隐藏状态和单元状态以及 START_ tag
- 如下图所示,解码器的输出将作为下一时间步的输入提供给解码器
- 在每个时间步,解码器输出一个我们应用 np.argmax 的独热编码向量,并将该向量转换为存储单词索引的字典中的单词
- 不断追加在每个时间步生成的目标词
- 重复这些步骤,直到我们遇到 _END 标签或单词限制
定义推理模型
# Encode the input sequence to get the "Context vectors"
**encoder_model = Model(encoder_inputs, encoder_states)**# Decoder setup
# Below tensors will hold the states of the previous time step
**decoder_state_input_h = Input(shape=(latent_dim,))
decoder_state_input_c = Input(shape=(latent_dim,))
decoder_state_input = [decoder_state_input_h, decoder_state_input_c]**# Get the embeddings of the decoder sequence
**dec_emb2= dec_emb_layer(decoder_inputs)**# To predict the next word in the sequence, set the initial states to the states from the previous time step
**decoder_outputs2, state_h2, state_c2 = decoder_lstm(dec_emb2, initial_state=decoder_state_input)
decoder_states2 = [state_h2, state_c2]**
# A dense softmax layer to generate prob dist. over the target vocabulary
**decoder_outputs2 = decoder_dense(decoder_outputs2)**# Final decoder model
**decoder_model = Model(
[decoder_inputs] + decoder_state_input,
[decoder_outputs2] + decoder_states2)**
创建一个用于推理查找的函数
**def decode_sequence(input_seq):**
# Encode the input as state vectors.
**states_value = encoder_model.predict(input_seq)**
# Generate empty target sequence of length 1.
** target_seq = np.zeros((1,1))**
# Populate the first character of
#target sequence with the start character.
** target_seq[0, 0] = target_word2idx['START_']**# Sampling loop for a batch of sequences
# (to simplify, here we assume a batch of size 1).
**stop_condition = False
decoded_sentence = ''
while not stop_condition:
output_tokens, h, c = decoder_model.predict([target_seq] + states_value)**# Sample a token
**sampled_token_index = np.argmax(output_tokens[0, -1, :])
sampled_word =target_idx2word[sampled_token_index]
decoded_sentence += ' '+ sampled_word**# Exit condition: either hit max length
# or find stop character.
**if (sampled_word == '_END' or
len(decoded_sentence) > 50):
stop_condition = True**# Update the target sequence (of length 1).
**target_seq = np.zeros((1,1))
target_seq[0, 0] = sampled_token_index**# Update states
**states_value = [h, c]****return decoded_sentence**
对训练数据集进行预测
**train_gen = generate_batch(X_train, y_train, batch_size = 1)
k=-1**
传递一个源句子,然后将预测输出与实际输出进行比较
**k+=1
(input_seq, actual_output), _ = next(train_gen)
decoded_sentence = decode_sequence(input_seq)
print(‘Input Source sentence:’, X_train[k:k+1].values[0])
print(‘Actual Target Translation:’, y_train[k:k+1].values[0][6:-4])
print(‘Predicted Target Translation:’, decoded_sentence[:-4])**
对测试数据集进行预测
**test_gen = generate_batch(X_test, y_test, batch_size = 1)**
k=10
k+=1
**(input_seq, actual_output), _ = next(test_gen)
decoded_sentence = decode_sequence(input_seq)**
**print('Input Source sentence:', X_test[k:k+1].values[0])
print('Actual Target Translation:', y_test[k:k+1].values[0][6:-4])
print('Predicted Target Translation:', decoded_sentence[:-4])**
有些预测很好,有些合理,有些不正确。
模型的其他增强功能
我们可以替换 LSTM 的威尔·GRU,增加更多的 LSTM/GRU 节点,为更多的纪元训练并使用注意机制
参考:
[## 序列到序列—培训— Keras 文档
这个脚本演示了如何实现一个基本的字符级序列到序列模型。我们将它应用于…
keras.io](https://keras.io/examples/lstm_seq2seq/)
https://blog . keras . io/a-ten-minute-introduction-to-sequence-to-sequence-learning-in-keras . html
用 Tensorflow 实现带注意机制的神经机器翻译
使用 Bahdanau 注意力的神经机器翻译(NMT)的 Tensorflow 实现的逐步解释。
在本文中,您将学习如何使用 Bahdanau 的注意力机制实现序列到序列(seq2seq)神经机器翻译(NMT)。我们将使用门控递归单元(GRU)在 Tensorflow 2.0 中实现代码。
照片由 Aaron Burden 在 Unsplash
先决条件
神经机器翻译(NMT)是使用深度神经网络将源语言(如英语)的单词序列转换为目标语言(如印地语或西班牙语)的单词序列的任务。
使用由编码器和解码器组成的序列到序列(seq2seq)模型来实现 NMT。编码器将源序列的完整信息编码成单个实值向量,也称为上下文向量,传递给解码器以产生输出序列,该输出序列是类似于印地语或西班牙语的目标语言。
上下文向量负责将整个输入序列总结成单个向量,这是低效的,所以我们使用注意机制。
注意机制的基本思想是避免试图学习每个句子的单一向量表示;相反,它基于关注权重关注输入序列的特定输入向量。
出于实施目的,我们将使用英语作为源语言,西班牙语作为目标语言。代码将使用 TensorFlow 2.0 实现,数据可以从这里下载。
实现具有注意机制的 NMT 的步骤
- 加载数据并通过删除空格、特殊字符等对其进行预处理。
- 创建数据集
- 创建编码器、注意力层和解码器
- 创建优化器和损失函数
- 训练模型
- 做出推论
导入所需的库
import pandas as pd
import numpy as np
import string
from string import digits
import matplotlib.pyplot as plt
%matplotlib inline
import tensorflow as tf
import matplotlib.ticker as ticker
from sklearn.model_selection import train_test_split
import re
import os
import io
import time
从文件中读取数据
阅读可以从这里下载的英语-西班牙语翻译文件。
我已将文件存储在“spa.txt”中
**data_path = "spa.txt"**#Read the data
**lines_raw= pd.read_table(data_path,names=['source', 'target', 'comments'])
lines_raw.sample(5)**
对源语句和目标语句进行清洗和预处理。
我们应用以下文本清理
- 将文本转换为小写
- 删除引号
- 清除源句子和目标句子中的数字。如果源语言或目标语言对数字使用不同的符号,那么删除这些符号
- 删除空格
- 在单词和标点符号之间加一个空格,比如“?”
- 在句首添加“start_”标记,在句尾添加“_end”标记
**def preprocess_sentence(sentence):**
num_digits= str.maketrans('','', digits)
**sentence= sentence.lower()
sentence= re.sub(" +", " ", sentence)
sentence= re.sub("'", '', sentence)
sentence= sentence.translate(num_digits)
sentence= re.sub(r"([?.!,¿])", r" \1 ", sentence)
sentence = sentence.rstrip().strip()
sentence= 'start_ ' + sentence + ' _end'**
**return sentence**
让我们选取英语中的一个句子并对其进行预处理
print(preprocess_sentence(“Can you do it in thirty minutes?”))
预处理源句子和目标句子,使单词对的格式为:[英语,西班牙语]
**def create_dataset(path, num_examples):**
**lines = io.open(path, encoding='UTF-8').read().strip().split('\n')**
** word_pairs = [[preprocess_sentence(w) for w in l.split('\t')] for l in lines[:num_examples]]**
**return zip(*word_pairs)****sample_size=60000
source, target = create_dataset(data_path, sample_size)**
标记源句子和目标句子
我们需要对文本语料库进行矢量化,将文本转换成整数序列。
我们首先创建分词器,然后在源句子上应用分词器
# create a tokenizer for source sentence
**source_sentence_tokenizer= tf.keras.preprocessing.text.Tokenizer(filters='')**# Fit the source sentences to the source tokenizer
**source_sentence_tokenizer.fit_on_texts(source)**
我们现在将源句子中的每个单词转换成一个整数序列,用相应的整数值替换这个单词。
只有分词器知道的单词才会被考虑
#Transforms each text in texts to a sequence of integers.
**source_tensor = source_sentence_tokenizer.texts_to_sequences(source)**
我们需要创建长度相同的序列,所以我们用“0”来填充长度较短的序列
#Sequences that are shorter than num_timesteps, padded with 0 at the end.
**source_tensor= tf.keras.preprocessing.sequence.pad_sequences(source_tensor,padding='post' )**
以类似的方式标记目标句子
# create the target sentence tokenizer **target_sentence_tokenizer= # Fit tf.keras.preprocessing.text.Tokenizer(filters='')**# Fit the tokenizer on target sentences **target_sentence_tokenizer.fit_on_texts(target)**#conver target text to sequnec of integers **target_tensor = target_sentence_tokenizer.texts_to_sequences(target)**# Post pad the shorter sequences with 0 **target_tensor= tf.keras.preprocessing.sequence.pad_sequences(target_tensor,padding='post' )**
创建训练和测试数据集
将数据集拆分为测试和训练。80%的数据用于训练,20%用于测试模型
**source_train_tensor, source_test_tensor, target_train_tensor, target_test_tensor= train_test_split(source_tensor, target_tensor,test_size=0.2)**
当数据集很大时,我们希望在内存中创建数据集以提高效率。我们将使用***TF . data . dataset . from _ tensor _ slices()***方法以对象的形式获取数组的切片。
数据集以 64 个为一批创建。
#setting the BATCH SIZE
**BATCH_SIZE = 64**#Create data in memeory **dataset=tf.data.Dataset.from_tensor_slices((source_train_tensor, target_train_tensor)).shuffle(BATCH_SIZE)**# shuffles the data in the batch
**dataset = dataset.batch(BATCH_SIZE, drop_remainder=True)**
我们遍历数据集中的所有元素。返回的迭代器实现了 Python 迭代器协议,因此只能在急切模式下使用
#Creates an Iterator for enumerating the elements of this dataset.
#Extract the next element from the dataset
**source_batch, target_batch =next(iter(dataset))
print(source_batch.shape)**
每批源数据的大小为 (BATCH_SIZE,max_source_length), ,目标数据的批量为 (BATCH_SIZE,max_target_length)。 在我们的例子中,最大源长度是 11,最大目标长度是 16
在 Bahdanau 的关注下,使用门控循环单元(GRU)创建序列对序列模型
有注意的 seq2seq 模型和无注意的 seq2seq 模型的区别
- 编码器和解码器的所有隐藏状态(向前和向后)用于生成上下文向量,不像 eq2seq 没有注意,它使用最后的编码器隐藏状态。
- 注意机制利用由前馈网络参数化的比对分数来比对输入和输出序列。它有助于注意源序列中最相关的信息。
- Seq2Seq 注意模型基于与源位置相关联的上下文向量来预测目标单词,并且与 seq2seq 不同,先前生成的目标单词在没有注意的情况下将所有源序列编码到单个上下文向量中
为模型设置一些参数
**BUFFER_SIZE = len(source_train_tensor)
steps_per_epoch= len(source_train_tensor)//BATCH_SIZE
embedding_dim=256
units=1024
source_vocab_size= len(source_sentence_tokenizer.word_index)+1
target_vocab_size= len(target_sentence_tokenizer.word_index)+1**
创建编码器
编码器将输入作为源令牌,将它们传递给嵌入层以获得矢量的密集表示,然后传递给 GRU。
为 GRU 设置返回序列和返回状态为真。默认情况下,return _ sequneces 设置为 False。当 return_sequences 设置为真时,则返回编码器中所有单元的整个输出序列。当 return_sequences 设置为 False 时,我们只返回最后一个编码器单元的隐藏状态。
seq2seq 在没有注意的情况下会将编码器的 return_sequences 设置为 False。Seq2seq 将把编码器的 return_sequences 设置为 True。
为了返回 GRU 的内部状态,我们将 retrun_state 设置为 True
**class Encoder(tf.keras.Model):
def __init__(self, vocab_size, embedding_dim, encoder_units, batch_size):
super(Encoder, self).__init__()
self.batch_size= batch_size
self.encoder_units=encoder_units
self.embedding=tf.keras.layers.Embedding(vocab_size, embedding_dim)
self.gru= tf.keras.layers.GRU(encoder_units,
return_sequences=True,
return_state=True, recurrent_initializer='glorot_uniform'
)**
**def call(self, x, hidden):**
#pass the input x to the embedding layer
** x= self.embedding(x)**
# pass the embedding and the hidden state to GRU
**output, state = self.gru(x, initial_state=hidden)
return output, state**
**def initialize_hidden_state(self):
return tf.zeros((self.batch_size, self.encoder_units))**
测试编码器类并打印编码器输出和隐藏状态的尺寸
**encoder = Encoder(source_vocab_size, embedding_dim, units, BATCH_SIZE)****sample_hidden = encoder.initialize_hidden_state()****sample_output, sample_hidden= encoder(source_batch, sample_hidden)**print ('Encoder output shape: (batch size, sequence length, units) {}'.format(sample_output.shape))
print ('Encoder Hidden state shape: (batch size, units) {}'.format(sample_hidden.shape))
创建 Bahdanau 注意力图层
注意力层包括
- 比对分数
- 注意力权重
- 上下文向量
我们将在注意力层实现这些简化的等式
巴赫达瑙注意方程式
**class BahdanauAttention(tf.keras.layers.Layer):
def __init__(self, units):
super( BahdanauAttention, self).__init__()
self.W1= tf.keras.layers.Dense(units) # encoder output
self.W2= tf.keras.layers.Dense(units) # Decoder hidden
self.V= tf.keras.layers.Dense(1)**
**def call(self, query, values):**
#calculate the Attention score
**score= self.V(tf.nn.tanh(self.W1(values) + self.W2(hidden_with_time_axis)))**
# attention_weights shape == (batch_size, max_length, 1)
** attention_weights= tf.nn.softmax(score, axis=1)**
#context_vector
**context_vector= attention_weights * values**
#Computes the sum of elements across dimensions of a tensor
** context_vector = tf.reduce_sum(context_vector, axis=1)
return context_vector, attention_weights**
用十个单位测试 Bahdanau 注意层
a**ttention_layer= BahdanauAttention(10)**
**attention_result, attention_weights = attention_layer(sample_hidden, sample_output)**print("Attention result shape: (batch size, units) {}".format(attention_result.shape))
print("Attention weights shape: (batch_size, sequence_length, 1) {}".format(attention_weights.shape))
创建解码器
解码器具有嵌入层、GRU 层和全连接层。
来预测解码器使用的目标字
- 上下文向量:注意力权重和编码器输出的总和
- 前一时间步的解码器输出和
- 前一个解码器的隐藏状态
**class Decoder(tf.keras.Model):
def __init__(self, vocab_size, embedding_dim, decoder_units, batch_sz):
super (Decoder,self).__init__()
self.batch_sz= batch_sz
self.decoder_units = decoder_units
self.embedding = tf.keras.layers.Embedding(vocab_size,
embedding_dim)
self.gru= tf.keras.layers.GRU(decoder_units,
return_sequences= True,
return_state=True,
recurrent_initializer='glorot_uniform')**
# Fully connected layer
**self.fc= tf.keras.layers.Dense(vocab_size)**
# attention
**self.attention = BahdanauAttention(self.decoder_units)**
** def call(self, x, hidden, encoder_output):**
**context_vector, attention_weights = self.attention(hidden,
encoder_output)**
# pass output sequnece thru the input layers
**x= self.embedding(x)**
# concatenate context vector and embedding for output sequence
**x= tf.concat([tf.expand_dims( context_vector, 1), x],
axis=-1)**
# passing the concatenated vector to the GRU
** output, state = self.gru(x)**
# output shape == (batch_size * 1, hidden_size)
** output= tf.reshape(output, (-1, output.shape[2]))**
# pass the output thru Fc layers
**x= self.fc(output)
return x, state, attention_weights**
测试解码器
**decoder= Decoder(target_vocab_size, embedding_dim, units, BATCH_SIZE)
sample_decoder_output, _, _= decoder(tf.random.uniform((BATCH_SIZE,1)), sample_hidden, sample_output)**
print ('Decoder output shape: (batch_size, vocab size) {}'.format(sample_decoder_output.shape))
定义优化器
我们在这里使用 Adam 优化器;你也可以试试 Rmsprop
#Define the optimizer and the loss function
optimizer = tf.keras.optimizers.Adam()
定义损失函数
使用SparseCategoricalCrossentropy计算实际和预测输出之间的损失。
如果输出是一次性编码的向量,那么使用 categorical _ crossentropy。对包含整数的 word2index 向量使用SparseCategoricalCrossentropyloss。
稀疏分类交叉熵在计算和内存上是高效的,因为它使用单个整数,而不是整个向量[0 0 1]
**loss_object = tf.keras.losses.SparseCategoricalCrossentropy(
from_logits=True, reduction='none')****def loss_function(real, pred):
mask = tf.math.logical_not(tf.math.equal(real, 0))
loss_ = loss_object(real, pred)****mask = tf.cast(mask, dtype=loss_.dtype)
loss_ *= mask****return tf.reduce_mean(loss_)**
训练数据集
使用编码器-解码器模型训练数据集
- 将编码后的源句子通过编码器,并返回编码器输出序列和隐藏状态
- 编码器输出、编码器隐藏状态和解码器输入被传递给解码器。在时间步长=0 时,解码器将“start_”作为输入。
- 解码器返回预测字和解码器隐藏状态
- 解码器隐藏状态被传递回模型,并且预测的字被用于计算损失
- 为了训练,我们使用教师强制,在每个时间步将实际单词传递给解码器。
- 在推断过程中,我们将前一时间步的预测单词作为输入传递给解码器
- 计算梯度下降,将其应用于优化器并反向传播
注意机制
Tensorflow 跟踪每个 tf.Variable 上每个计算的每个梯度。为了训练,我们使用梯度带,因为我们需要控制需要梯度信息的代码区域。对于具有注意机制的 seq2seq,我们仅计算解码器输出的梯度。
**def train_step(inp, targ, enc_hidden):
loss = 0
with tf.GradientTape() as tape:**
#create encoder
** enc_output, enc_hidden = encoder(inp, enc_hidden)
dec_hidden = enc_hidden**
#first input to decode is start_
**dec_input = tf.expand_dims(
[target_sentence_tokenizer.word_index['start_']] * BATCH_SIZE, 1)**
# Teacher forcing - feeding the target as the next input
**for t in range(1, targ.shape[1]):**
# passing enc_output to the decoder
**predictions, dec_hidden, _ = decoder(dec_input, dec_hidden, enc_output)**
# calculate loss based on predictions
**loss += tf.keras.losses.sparse_categorical_crossentropy(targ[:, t], predictions)**
# using teacher forcing
**dec_input = tf.expand_dims(targ[:, t], 1)
batch_loss = (loss / int(targ.shape[1]))
variables = encoder.trainable_variables + decoder.trainable_variables
gradients = tape.gradient(loss, variables)
optimizer.apply_gradients(zip(gradients, variables))
return batch_loss**
使用多个历元集中训练编码器-解码器模型
**EPOCHS=20
for epoch in range(EPOCHS):
start = time.time()****enc_hidden = encoder.initialize_hidden_state()
total_loss = 0**
# train the model using data in bataches
f**or (batch, (inp, targ)) in enumerate(dataset.take(steps_per_epoch)):
batch_loss = train_step(inp, targ, enc_hidden)
total_loss += batch_loss****if batch % 100 == 0:
print('Epoch {} Batch {} Loss {}'.format(epoch + 1,
batch,
batch_loss.numpy()))
print('Epoch {} Loss {}'.format(epoch + 1,
total_loss / steps_per_epoch))
print('Time taken for 1 epoch {} sec\n'.format(time.time() - start))**
对测试数据进行推断
进行推理类似于训练,只是我们不知道教师强制中使用的实际单词,所以我们将来自前一时间步的预测单词作为输入传递给解码器。
我们计算每个时间步的注意力权重,因为它有助于关注用于进行预测的源序列中最相关的信息。
当我们达到最大目标句子长度时,或者当我们遇到“stop_”标签时,我们停止预测单词。
#Calculating the max length of the source and target sentences
**max_target_length= max(len(t) for t in target_tensor)
max_source_length= max(len(t) for t in source_tensor)**
做出这样的推断
- 传源句,
- 预处理句子以转换成小写,删除空格,特殊字符,在单词和标点符号之间加一个空格,等等。
- 对句子进行标记以创建 word2index 词典
- Post 用 0 填充源序列,使其长度与最大源句子的长度相同
- 创建输入张量
- 创建编码器并传递输入向量和隐藏状态。初始隐藏状态被设置为零
- 解码器的第一个输入将是“start_”标签。解码器的初始隐藏状态是编码器隐藏状态
- 创建解码器,向其传递解码器输入、解码器隐藏状态和编码器输出
- 存储注意力权重,并使用解码器输入、隐藏和上下文向量,找到具有最大概率的单词的整数。
- 将整数转换为单词,并不断追加预测的单词以形成目标句子,直到我们遇到“end_”标签或达到最大目标序列长度
**def evaluate(sentence):**
**attention_plot= np.zeros((max_target_length, max_source_length))**
#preprocess the sentnece
**sentence = preprocess_sentence(sentence)**
#convert the sentence to index based on word2index dictionary
**inputs= [source_sentence_tokenizer.word_index[i] for i in sentence.split(' ')]**
# pad the sequence
**inputs = tf.keras.preprocessing.sequence.pad_sequences([inputs], maxlen=max_source_length, padding='post')**
#conver to tensors
**inputs = tf.convert_to_tensor(inputs)**
**result= ''**
# creating encoder
**hidden = [tf.zeros((1, units))]**
**encoder_output, encoder_hidden= encoder(inputs, hidden)**
# creating decoder
**decoder_hidden = encoder_hidden**
**decoder_input = tf.expand_dims([target_sentence_tokenizer.word_index['start_']], 0)**
**for t in range(max_target_length):**
**predictions, decoder_hidden, attention_weights= decoder(decoder_input, decoder_hidden, encoder_output)**
# storing attention weight for plotting it
** attention_weights = tf.reshape(attention_weights, (-1,))
attention_plot[t] = attention_weights.numpy()
prediction_id= tf.argmax(predictions[0]).numpy()**
**result += target_sentence_tokenizer.index_word[prediction_id] + ' '
if target_sentence_tokenizer.index_word[prediction_id] == '_end':
return result,sentence, attention_plot**
# predicted id is fed back to as input to the decoder
** decoder_input = tf.expand_dims([prediction_id], 0)**
**return result,sentence, attention_plot**
函数来绘制源单词和目标单词之间的注意力权重。该图将帮助我们理解哪个源词在预测目标词时被给予了更多的关注
**def plot_attention(attention, sentence, predicted_sentence):
fig = plt.figure(figsize=(10,10))
ax= fig.add_subplot(1,1,1)
ax.matshow(attention, cmap='Greens')
fontdict={'fontsize':10}
ax.set_xticklabels([''] + sentence, fontdict=fontdict, rotation=90)
ax.set_yticklabels([''] + predicted_sentence, fontdict=fontdict)****ax.xaxis.set_major_locator(ticker.MultipleLocator(1))
ax.yaxis.set_major_locator(ticker.MultipleLocator(1))****plt.show()**
将源句翻译成目标句
为了将源句子翻译成目标语言,我们调用 evaluate 函数来创建编码器、解码器和关注层
**def translate(sentence):
result, sentence, attention_plot = evaluate(sentence)
print('Input : %s' % (sentence))
print('predicted sentence :{}'.format(result))
attention_plot= attention_plot[:len(result.split(' ')), :len(sentence.split(' '))]
plot_attention(attention_plot, sentence.split(' '), result.split(' '))**
最后的预测
**translate(u'I am going to work.')**
翻译句子的注意情节
注意力图
在翻译过程中,我们发现“去”比“去”更能预测“去”,同样,“工作”比“工作”更能预测“去”
可从 Github 获得的代码
参考
巴丹瑙注意了-【https://arxiv.org/pdf/1409.0473.pdf
https://www . tensor flow . org/tutorials/text/NMT _ with _ attention
https://www.tensorflow.org/guide/keras/rnn
使用 PyTorch 的神经类型转移
简介
神经风格转移是基于深层神经网络生成艺术图像的人工系统。这种方法使用两个随机图像,内容和样式图像。它从内容图像中提取结构特征,而从风格图像中提取风格特征。
神经风格转移(作者 GIF)
内容和风格表现
内容表示
卷积神经网络沿着处理层次开发图像的表示。随着我们向网络的更深处移动,表示将更关心结构特征或实际内容,而不是详细的像素数据。为了获得这些表示,我们可以使用该层的特征图来重建图像。从较低层的重建将再现精确的图像。相反,较高层的重建将捕获高级内容,因此我们将来自较高层的特征响应称为内容表示。
ConvNet 不同层次的内容重构(图片来自艺术风格的神经算法(2015) )
上图显示了从层“conv1_1”、“conv2_1”、“conv3_1”、“conv4_1”和“conv5_1”重建输入图像。我们发现,从较低层重建的图像几乎与输入图像相同,但随着我们深入网络,详细的像素信息会丢失。相比之下,图像的高级内容被保留。
风格表现
为了提取样式内容的表示,我们在每个网络层的过滤器响应的顶部构建了一个特征空间。它包括在特征图的空间范围内不同滤波器响应之间的相关性。不同层的滤波器相关性捕获输入图像的纹理信息。这在丢弃全局排列的信息的同时,创建了与给定图像的风格越来越匹配的图像。这种多尺度表示被称为风格表示。
ConvNet 不同层次的风格重构(图片来自艺术风格的神经算法(2015) )
上图显示了代表 CNN 不同层中不同特征之间相关性的每个卷积层上方的特征空间。随着我们深入到网络中,我们可以看到全局排列或结构特征被丢弃了。
模型架构
论文《艺术风格的神经算法》中提出的模型的体系结构是
模型架构(图片来自艺术风格的神经算法(2015) )
这里,我们使用预训练的 VGG19 网络的卷积神经网络,并执行内容和风格重建。通过将来自内容表示的结构信息和来自风格表示的纹理/风格信息纠缠在一起,我们生成了艺术图像。我们可以强调重建风格或内容。对风格的强烈强调将导致图像与艺术品的外观相匹配,有效地给出了它的纹理版本,但几乎没有显示照片的任何内容。当把重点放在内容上时,人们可以识别照片,但绘画风格并不匹配。我们对生成的图像执行梯度下降,以找到与原始图像的特征响应相匹配的另一个图像。
实施
导入库
我们将从导入所需的库开始。我们将进口火炬,火炬视觉和 PIL,以实现使用 PyTorch 的风格转移。
加载模型
在这种情况下,我们将从 *torchvision.models()加载预训练的 VGG19 模型。*vgg 19 模型有三个组件特性,avgpool 和分类器。
- 该功能包含所有卷积层、最大池层和 ReLu 层
- avgpool 保存平均池层。
- 分类器保存密集层。
我们将只使用卷积神经网络来实现风格转换,因此导入 vgg19 特性。如果可能的话,不要忘记使用 GPU。这会节省训练时间。
图像预处理
为了使图像与模型兼容,必须对图像进行预处理。使用 torch.transforms() 我们将执行一些基本的预处理,包括以下步骤:
- 调整大小:将所有图像的大小调整为 512 x 512
- 将图像转换成张量
对于预训练的 vgg19 模型,还可以使用平均值(0.485,0.456,0.406)和标准差(0.229,0.224,0.225)对张量进行归一化。但是别忘了把它转换回原来的比例。因此,定义一个使用 PIL 库加载图像并对其进行预处理的函数。在第 0 个索引处添加一个额外的维度,使用 unsqueeze() 表示批量大小,然后将其加载到设备并返回。
现在,使用 image_loader 函数从本地磁盘加载样式和内容图像。通常的做法是使用内容映像克隆作为输入基础映像或生成的映像。由于梯度下降会改变生成图像的像素值,我们将为 require_grads()传递参数 true。
获取特征表示
让我们定义一个类,它将提供中间层的特征表示。使用中间层是因为这些层充当复杂的特征提取器。因此,这些可以描述输入图像的风格和内容。在本课程中,我们将通过删除 vgg19 模型中未使用的层(conv5_1 以外的层)来初始化模型,并提取“conv1_1”、“conv2_1”、“conv3_1”、“conv4_1”和“conv5_1”层的激活或特征表示(索引值[0,5,10,19,28])。将 5 个卷积层的激活存储在一个数组中,并返回该数组。
定义损失
风格转移的净损失定义为:
全损(图片来自一种艺术风格的神经算法(2015) )
在上面的等式中,L ₜₒₜₐₗ 是总损失,l𝒸ₒₙₜₑₙₜ是所有中间层的内容损失,lₛₜᵧₗₑt13】是所有中间层的风格损失。这里,α和β分别是内容和风格损失的加权系数。p、a 和 x 是内容图像、风格图像和生成图像或基本输入图像。我们对损失函数执行梯度下降,并且代替模型参数,我们更新输入图像 x 的像素值以最小化损失。这将使输入图像与内容和样式图像相似。我们可以通过改变α和β的值来强调风格或内容的损失。对风格的强烈强调将导致图像与艺术品的外观相匹配,有效地给出了它的纹理版本,但几乎没有显示照片的任何内容。当把重点放在内容上时,人们可以识别照片,但绘画风格并不匹配。****
内容丢失
内容图像和输入基本图像被传递到我们的模型,中间层的输出(上面列为“conv1_1”、“conv2_1”、“conv3_1”、“conv4_1”和“conv5_1”)使用上面定义的类提取。然后,我们计算内容图像的中间表示和输入基础图像之间的欧几里德距离。因此,层 l 的内容损失由下式定义:
内容丢失(图片来自艺术风格的神经算法(2015) )
在上面的等式中,我们计算内容图像§的特征表示和层(l)的输入基础图像(x)之间的平方误差。这里,nˡₕ、nˡ𝓌、nˡ𝒸是层 l 的高度、宽度和通道。为了计算内容损失,维度 nˡₕ x nˡ𝓌 x nˡ𝒸的中间表示被展开成维度 nˡ𝒸 x nˡₕ ***** n 的向量。展开要素制图表达不是强制步骤,但这是一个很好的做法。下图将有助于我们将这种转变形象化。
向 2D 展开 3D 特征地图(图片由作者提供)
Fˡᵢⱼ 和 Pˡᵢⱼ是代表输入基础图像和内容图像的中间表示的 nˡ𝒸×nˡₕ*****nˡ𝓌维度向量。
风格丧失
我们在网络的每一层上构建一个特征空间,表示不同滤波器响应之间的相关性。Gram matrix 计算这些特征相关性**。**
gram 矩阵表示中间表示中每个过滤器之间的相关性。通过取展开的中间表示及其转置的点积来计算格拉姆矩阵。格拉姆矩阵 g 的维数是 nˡ𝒸×nˡ𝒸,其中 nˡ𝒸是层 l 的中间表示中的通道数
Gram Matrix(图片来自一种艺术风格的神经算法(2015) )
上式中,Gˡᵢⱼ 是 l 层的矢量化特征图 I 和 j 的内积,一个 gram 矩阵的矢量化方程如下图所示,其中 g 是中间表示 a 的 gram 矩阵
Gram 矩阵计算(图片由作者提供)
Gram 矩阵矢量化方程(图片来自一种艺术风格的神经算法(2015) )
层 l 的风格损失是风格图像的中间表示和输入基础图像的 gram 矩阵之间的平方误差。
l 层的风格损失(图片来自艺术风格的神经算法(2015) )
其中 Eₗ 是层 l 的风格损失,Nₗ和 Mₗ分别是层 l 的特征表示中的通道数和高度乘以宽度。Gˡᵢⱼ和 Aˡᵢⱼ 分别是风格图像(a)和输入基础图像(x)的中间表示的克矩阵。
总风格损失为:
总体风格损失(图片来自艺术风格的神经算法(2015) )
其中 wˡ是每层对总风格损失的贡献的权重因子。
这里,我们没有将样式损失乘以常数,因为它使损失非常小。合计所有中间层的内容和样式损失,并计算总损失。
现在让我们继续训练模型。
训练
初始化我们训练模型所需的变量。因此,在继续训练之前,我们需要设置超参数。
在这里,为类 *VGG 创建一个对象。*初始化对象将调用构造函数,它将返回前 29 层的模型并将其加载到设备。Epoch 为 1000,学习率为 0.01,alpha(内容损失加权系数)为 1,beta(风格损失加权系数)为 0.01。
Adam 被用作优化器。生成的图像的像素数据将被优化,以将生成的图像作为优化器参数传递。
使用 for 循环迭代历元数。使用模型提取内容、风格和生成的图像的中间层的特征表示。在向模型传递图像时,它将返回一个长度为 5 的数组。每个元素对应于每个中间层的特征表示。
使用上面定义的函数计算总损耗。用 *optimizer.zero_grads(),*将渐变设置为零,用 total_loss.backward() 反向传播总损失,然后用 *optimizer.step()更新生成图像的像素值。*我们将在每 100 个历元后保存生成的图像,并打印总损失。
经过训练,你可以在你当前的工作目录中找到艺术形象。你的图像会像这样。
内容图片(左:图片来自维基百科 ) +风格图片(中:图片来自USGSon UNSP lash)=生成的图片(右:图片来自作者)
请随意使用超参数。有时你可能会得到预期的结果,但有时你可能会努力实现预期的结果。玩得开心!!!
结论
在本教程中,您已经学习了神经类型转移的基础知识,并建立了一些直觉。您已经加载了一个预训练的 vgg19 模型,冻结了它的重量并定制了模型层。加载图像并执行一些基本的预处理。然后定义了内容损失和风格损失,两者结合起来计算总损失。最后你跑了模型,做了一个艺术形象,是内容和风格形象的交融。
你可以在这里找到完整的代码。
参考
- 莱昂·A·加蒂丝,亚历山大·s·埃克,马蒂亚斯·贝奇,一种艺术风格的神经算法 (2015),arXiv
在 TensorFlow 中实现点柱
点柱是一个非常著名的三维物体检测算法,由于其快速的推理速度,在激光雷达上生成点云。在本文中,我们将在 TensorFlow 中浏览它的实现代码。
如果你想开始 3D 物体检测,更具体地说是点柱,我有一系列的帖子专门为此写的。下面是环节。此外,直接阅读的要点文章会对更好地理解这篇文章很有帮助,然而,这不是先决条件。
本教程 github Repo:【https://github.com/tyagi-iiitv/PointPillars】T4。感谢弗朗西斯科·费乐理提供的初始代码。
内容
- 定义初始配置
- 建筑点柱网络
- 损失函数
- Kitti 数据集的数据读取器
- 培养
- 推理
定义初始配置(config.py)
在 config.py 文件中定义配置
我们定义初始配置,包括 x,y,z 维度的限制。这些限制定义了我们将要预测边界框的感兴趣区域。由于激光雷达的范围通常很大,我们将重点放在这个空间中由这些限制定义的较小区域。其他配置包括支柱的最大数量、每个支柱的最大点数等。
关于点柱网络的配置。
这些配置通常用于将点云从 3D 坐标转换为点柱检测管道中的点柱坐标,如下一节所述。现在,只要记住所有的配置都是在这个文件中定义的。
构建点柱网络(network.py)
点柱的整体架构包含三个组件:一个柱特征网、一个主干 2D CNN 和一个探测头。我们将分别讨论这三个组件。
点柱式建筑。它由三部分组成:一个支柱特征网,一个骨干 2D CNN 和一个探测头。(来源)
支柱特征网
支柱特征网络架构。
- 柱子/输入(输入层)(4,12000,100,7)指——批量大小(4),最大柱子数(12000),每个柱子最大点数(100),7 dim 向量:[x,y,z,intensity,Xc,Yc,Zc]。 X、y、z 是点云中某个点的 3D 坐标,激光雷达设备测得的该点的强度存储在“强度”中,Xc、Yc、Zc 存储该点在每个维度上与柱子中心的距离。
- 这个输入现在从 7 维转换到 64 维向量,使用 CNN 提取特征。
- 柱子/索引(InputLayer) (4,12000,3)指的是— **批量大小,最大柱子数,以及每个柱子的中心,即 x,y 和 z 坐标。**该层包含每个支柱的中心,用于将每个支柱的学习特征映射回*支柱/散射 _ nd(λ)*层中的 x-y 网格。
- pillars/scatter _ nd(Lambda)-使用上述图层中的 X,y 坐标,将点云空间划分为(504 X 504) x-y 格网,每个格网单元包含相应支柱的 64 个要素。
骨干 2D 有线电视新闻网
这是一个非常简单的图层,输入是编码在 x-y 格网中的支柱要素,如前一节所述。该网格被视为一幅图像,并被转换成不同的尺度,在这些尺度上提取特征并最终连接起来。我们试图从不同尺度的点云中捕捉特征,并将它们连接成一个单一的张量。
骨干 2D CNN。具有编码的柱子特征的 x-y 网格被变换成不同的尺度,其特征被提取和连接。
探测头
探测头网络结构。
在这一部分中,我们预测锚盒的回归目标,如下所述。所有这些预测都是针对 x-y 坐标(252 X 252)中的每个网格单元的。在这种情况下,边界框不是直接预测的,而是调整初始锚框的大小以生成预测的边界框。初始定位框在 config.py 文件中提供。
- 占用-预测标注是否包含元素。1 —是或否。因此,我们只需要在占用率为 1 时检查其他预测(位置、大小、角度、航向和 clf)。
- Loc 包含相应锚定框的 x,y,z 增量。
- 大小—包含相应锚定框的长度、宽度和高度的增量。
- 角度-包含偏航差值。
- 航向—如果角度> 90 度,则包含此选项,以检查对象的航向角度。
- Clf 包含每个预测的分类 id 和置信度得分。
注意,检测头预测每个网格单元 4 个边界框,并且每个边界框具有上面列出的属性。
损失函数(loss.py)
一旦我们决定了网络体系结构,我们就创建损失函数来完成训练过程定义。正如您在 loss.py 文件中看到的,我们正在计算每个回归目标的损失值,并将它们与分配给每个误差的权重相加。因此,最终损失值将是每个损失值的加权和。
分配给每个损失值的权重。(Config.py 文件)
focal_weight*focal_loss + loc_weight*loc_loss + size_weight*size_loss + angle_weight*angle_loss +
...
Kitti 数据集的数据读取器(readers.py)
我们设计了一个类来读取 Kitti 数据集中的文件,并从标签(txt)文件中提取 3D 边界框尺寸。标签文件中的每一行都包含以下信息:
Kitti 数据集中的标签文件
- 物体的类型(汽车、卡车等。)
- 截断浮点数,0 表示截断,1 表示未截断
- 遮挡状态,0 =完全可见,1 =部分遮挡,2 =大部分遮挡,3 =未知
- 阿尔法观察角
- 4 2D 边界框参数(中心、长度、宽度)
- 3D 对象尺寸(高度、宽度、长度)
- 相机坐标中的 3D 对象位置(x,y,z)
- 偏航角
培训(point_pillars_training_run.py,processors.py,src/point_pillars.cpp)
在为 Kitti 数据集中的每个数据点定义损失函数、配置和数据生成器之后, processor.py 文件中的 SimpleDataGenerator 生成训练示例。这一过程的主要步骤是:
- 生成立柱和立柱 id(data processor . make _ point _ Pillars在 processor.py 文件中)
- 从标签文件和 config.py 文件中给定的锚定框生成回归目标(如上所述)。(data processor . make _ ground _ truthinside processor . py 文件)
这两个任务的助手函数在 src/ point_pillars.cpp 文件中提供,该文件包含在使用 Pybind 的当前 python 代码中。
- create pillars(src/point _ pillars . CPP)-生成最大数量的支柱,并为每个支柱指定支柱中心。如果柱内的点数小于最大点数,则使用零填充,否则使用随机采样。每个支柱将有 100 个点,每个点有 7 个值(x,y,z,强度,Xc,Yc,Zc)。我们从(0,0)激光雷达坐标开始,并在视野中径向扩展,直到我们达到最大数量的柱子。
我们从视野中的起点开始扩展,为每个网格单元生成支柱。重复这个过程,直到我们编码了最大数量的柱子。
- createPillarsTarget(src/point _ pillars . CPP)-生成可用于稍后计算损失值的回归目标。对于标签文件中给定的每个 3D 边界框,它将与锚框进行比较。首先,基于对象尺寸(下面网格上的红色圆圈)在原始边界框周围创建一个搜索半径。然后,对于该半径内的每个网格单元,放置锚框,并在每个锚框和原始边界框之间计算 IoU。
给定实际边界框,生成回归目标的过程。
计算并集上的交集。这是通过在训练期间比较锚框和实际边界框来生成回归目标的关键步骤。(来源:pyimagesearch.com)
如果 IoU 值高于某个阈值,则目标(x、y、z、长度、宽度、高度、偏航等。)被存储用于该边界框。否则,如果没有锚超过阈值,则为该网格单元选择并存储基于 IoU 分数的最佳锚。如上所述,这些值随后用于计算损失值。
推论(point_pillars_prediction.py)
这个代码库的最后一部分是用于推理的point _ pillars _ prediction . py文件。在我们已经基于前面描述的损失函数训练了网络之后,在推断阶段,网络的输出是每个预测的回归目标、占用指数和分类置信度值。您可以验证对于每个格网像元,网络输出 4 个边界框及其各自的参数。
我们可以基于占用率指数移除大多数预测的框,因为我们只想要占用率为 1 的框,这意味着网络实际上已经预测了该网格单元上的边界框。尽管如此,对于相同的对象,可能有许多由网络预测的边界框。为了过滤这些,我们可以使用非最大值抑制,并得出最终的边界框预测。非最大值抑制基于 IoU 及其分类置信度值对边界框进行排序,并选择具有最佳值的边界框作为最终边界框。参考这个帖子阅读更多关于非最大抑制。
当多个边界框被对象检测算法预测时,非最大值抑制收敛到最终的边界框。(来源:towardsdatascience.com)
结论
这篇文章详细介绍了 Tensorflow 上点支柱的实现。包括配置文件、模型实现、数据生成器、训练和推理在内的所有代码细节都用代码示例进行了解释。代码可以在这个 Github repo 中找到,可以用来试验本文中给出的细节。作为下一步,我们正致力于在 Kitti 数据上生成推理结果,这些结果将发布在 Github repo 上。
如果你有兴趣参与这个项目,请查看 GitHub repo 上的问题。
用 Numpy 实现递归神经网络
入门
关于如何使用 Numpy 实现递归神经网络的综合教程
介绍
递归神经网络(RNN)是最早能够在自然语言处理领域提供突破的神经网络之一。这个网络的美妙之处在于它能够存储以前序列的记忆,因此它们也被广泛用于时间序列任务。像 Tensorflow 和 PyTorch 这样的高级框架抽象了这些神经网络背后的数学,使得任何人工智能爱好者都难以用正确的参数和层知识来编码深度学习架构。为了解决这些类型的低效率,这些网络背后的数学知识是必要的。通过帮助人工智能爱好者理解研究论文中的不同符号并在实践中实现它们,从头开始编写这些算法具有额外的优势。
如果你对 RNN 的概念不熟悉,请参考 MIT 6。S191 课程,这是最好的讲座之一,让你直观地了解 RNN 是如何运作的。这些知识将帮助您理解本教程中解释的不同符号和概念实现。
最终目标
这个博客的最终目标是让 AI 爱好者对他们从深度学习领域的研究论文中获得的理论知识进行编码。
参数初始化
与传统的神经网络不同,RNN 拥有 3 个权参数,即输入权、内部状态权(用于存储记忆)和输出权。我们首先用随机值初始化这些参数。我们将 word_embedding 维数和 output 维数分别初始化为 100 和 80。输出维度是词汇表中出现的唯一单词的总数。
权重初始化器
变量 prev_memory 指的是 internal_state(这些是前面序列的内存)。用于更新权重的梯度等其他参数也已初始化。输入 _ 权重梯度、内部 _ 状态 _ 权重梯度和输出 _ 权重梯度分别被命名为杜 、 dW 和 dV 。变量bptt _ truncate指的是网络在反向传播时必须回顾的时间戳的数量,这样做是为了克服消失梯度问题。
正向传播直觉:
输入和输出向量:
考虑到我们有一句话 “我喜欢玩。”。词汇表中的 假设 I 映射到索引 2, 像 映射到索引 45 , 到 在索引 10 和 在索引 64 和标点符号在索引 1 处。为了获得从输入到输出的真实场景,让我们为每个单词随机初始化 word_embedding。
注意 : 你也可以尝试对每个单词使用一个 hot 编码向量,并将其作为输入传递。
现在我们已经完成了输入,我们需要考虑每个单词输入的输出。RNN 单元应该输出当前输入的下一个最可能的单词。为了训练 RNN,我们提供了第 t+1 个 字作为第 个 输入值的输出,例如:RNN 单元格应该输出字 ,就像给定的输入字一样**
既然输入是以嵌入向量的形式,那么计算损失所需的输出格式应该是一次性编码的向量。对于输入字符串中除第一个单词之外的每个单词都要这样做,因为我们只考虑这个神经网络要学习的一个例句,而初始输入是句子的第一个单词。**
我们为什么要对输出的字进行一次性编码?
原因是,原始输出只是每个单词的分数,它们对我们来说并不重要。相反,我们需要每个单词相对于前一个单词的 概率 。
我们如何从原始输出值中找到概率?
为了解决这个问题,在得分向量上使用了一个 softmax 激活函数,使得所有这些概率加起来等于 1。 Img 1 显示单个时间戳的输入输出管道。顶行是 ground _truth 输出,第二行表示预测输出。
img 1:RNN 的输入和输出管道,图片由作者提供
注意: 在实现过程中我们需要注意 output_mapper 的键值。我们需要用其时间戳值重置键值,以便算法知道在特定时间戳需要使用哪个基本事实字来计算损失。
***Before reset:
45: array([0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0.,
0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0.,
0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 1., 0., 0., 0., 0., 0.,
0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0.,
0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0.])After reset:
{0: array([0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0.,
0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0.,
0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 1., 0., 0., 0., 0., 0.,
0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0.,
0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0.])***
RNN 盒子计算:
现在我们有了权重,也知道了如何传递输入,也知道了预期的输出,我们将从正向传播计算开始。训练神经网络需要以下计算。
Img 2:作者图片
***这里的 U 表示输入 _ 权重, W 表示内部 _ 状态 _ 权重, V 表示输出权重。输入权重乘以输入( x ),内部 _ 状态 _ 权重乘以之前的激活,在我们的符号中是 prev_memory 。层间使用的激活函数是 *Tanh。它提供非线性,最终有助于学习。
注意: 在本教程中,不使用 RNN 计算的偏差术语,因为它会导致更复杂的理解。
由于上面的代码只计算一个特定时间戳的输出,我们现在必须对整个单词序列的前向传播进行编码。
在下面的代码中, 输出字符串 包含每个时间戳的输出向量列表。 内存 是一个字典,其中包含每个时间戳的参数,这些参数在反向传播期间是必不可少的。
损失计算:
我们还将损失或误差定义为交叉熵损失*,由下式给出:***
Img 3:作者图片
最重要的是,我们需要在上面的代码中查看第 行和第 行。众所周知,地面真实输出( y )的形式为[0,0,…。,1,…0]和 predicted _ output()的形式是[0.34,0.03,…,0.45],我们需要损失是一个单一的值来从中推断总损失。为此,我们使用 求和函数 来获得该特定时间戳的 y 和 y^hat 向量中每个值的差值/误差之和。total_loss 是包括所有时间戳在内的整个模型的损失。**
反向传播
如果你听说过反向传播,那么你一定听说过链式法则,它是计算梯度的重要方面。
Img 4:作者图片
Img 5:作者图片
根据上面的 Img 4 ,成本 C 表示误差,这是 y^hat 达到 y 所需的变化。由于成本是激活 a、 的函数输出,激活反映的变化由 dCost/da 表示。 实际上是指从激活节点 的角度看的变化(误差)值。 类似地,激活相对于的变化由 da/dz 和 z 相对于wdw/dz 给出。我们关心的是重量的变化(误差)有多大。由于重量和成本之间没有直接关系,所以从成本到重量的中间变化值会相乘(如上述等式所示)。********
RNN 反向传播:
因为 RNN 有三个重量,我们需要三个梯度。input _ weights(dLoss/dU)、internal _ state _ weights(dLoss/dW)和 output _ weights(dLoss/dV)的渐变。
这三个梯度的链可以表示如下:
:
img 6:RNN 使用的权重梯度方程,图片由作者提供
注意 :这里的 T 代表转置。
dLoss/dy _ unactivated编码如下:
为了了解更多关于损失衍生品的信息,请参考这篇博客。我们将计算两个梯度函数,一个是乘法 _ 后退,另一个是加法 _ 后退。在乘法 _ 反向的情况下,我们返回 2 个参数,一个是关于权重的梯度( dLoss/dV ),另一个是链梯度,其将是链的一部分以计算另一个权重梯度。在加法向后的情况下,在计算导数时,我们发现加法函数(ht _ unactivated)中单个分量的导数为 1。例如:DH _ unactivated/dU _ frd= 1 as(h _ unactivated=U _ frd+W _ frd _)以及dU _ frd/dU _ frd**= 1。但是 1 的个数是相对于 U_frd 的维数而言的。要了解更多关于渐变的信息,你可以参考这个源。就是这样,计算梯度只需要这两个函数。 乘法 _ 后退 函数用于包含矢量点积的方程,而 加法 _ 后退 函数用于包含两个矢量相加的方程。****
Img 7:下面编码的梯度函数背后的数学直觉,图片由作者提供
现在,您已经分析并理解了 RNN 的反向传播,是时候为单个时间戳实现它了,这将在以后用于计算所有时间戳的梯度。如下面的代码所示, forward_params_t 是一个包含网络在特定时间步长的前向参数的列表。变量 ds 是一个至关重要的部分,因为这行代码考虑了先前时间戳的隐藏状态,这将有助于提取反向传播时所需的足够有用的信息。
对于 RNN,由于消失梯度问题,我们将使用截断反向传播,而不是使用普通反向传播。在这种技术中,当前单元将向后看 k 个时间戳,而不是只向后看一个时间戳,其中 k 表示要向后看的先前单元的数量,以便检索更多的知识。
更新权重:
一旦我们使用反向传播计算了梯度,我们必须更新权重,这是使用批量梯度下降方法完成的。
训练序列:
一旦我们把所有的功能都准备好了,我们就可以进入高潮了,也就是训练神经网络。考虑用于训练的学习率是静态的,您甚至可以使用基于使用步长衰减的动态方法来改变学习率。
Img 7:损失-产出,按作者分类的图像
结论:
现在你已经实现了一个递归神经网络,是时候向前迈出一步,使用像 LSTM 和 GRU 这样的高级架构,以更有效的方式利用隐藏状态来保留更长序列的含义。还有很长的路要走。随着 NLP 领域的大量进步,出现了高度复杂的算法,如 Elmo 和 Bert。理解它们,并尝试自己实现。它遵循同样的记忆概念,但引入了加权单词的元素。由于这些模型高度复杂,使用 Numpy 是不够的,而是灌输 PyTorch 或 TensorFlow 的技能来实现它们,并构建可以服务于社区的惊人的 AI 系统。
创建本教程的灵感来自这个 github 博客。
你可以在这里访问本教程的笔记本。
希望你们都喜欢这个教程!
参考文献:
[1]萨古尔·斯里哈里,RNN-Gradients,https://cedar . buffalo . edu/~斯里哈里/CSE 676/10 . 2 . 2% 20 rnn-Gradients . pdf
[2]https://github.com/pangolulu/rnn-from-scratch 龚玉【RNN】从无到有
从头开始实施 SGD
无 SKlearn 的随机梯度下降的自定义实现
在实现随机梯度下降之前,让我们来谈谈什么是梯度下降。
梯度下降算法是一种用于求解优化问题的迭代算法。在几乎每个机器学习和深度学习模型中,梯度下降都被积极地用于改善我们算法的学习。
读完这篇博客后,你会知道梯度下降算法实际上是如何工作的。在这篇博客的最后,我们将比较我们的自定义 SGD 实现和 SKlearn 的 SGD 实现。
梯度下降算法是如何工作的?
- 选取一个初始随机点 x0。
- x1 = x0 - r [(df/dx) of x0]
- x2 = x1- r [(df/dx) of x1]
- 类似地,我们发现对于 x0,x1,x2 ……。x[k-1]
这里 r 是学习率,df/dx 是最小化我们损失的梯度函数。
用小批量实现线性 SGD
在小批量 SGD 中,在计算关于训练集子集的误差梯度之后,更新参数。
让我们以 Kaggle 的波士顿住房数据集为例。
首先,我们将导入所有必需的库。
**import** **warnings**
warnings.filterwarnings("ignore")
**from** **sklearn.datasets** **import** load_boston
**from** **random** **import** seed
**from** **random** **import** randrange
**from** **csv** **import** reader
**from** **math** **import** sqrt
**from** **sklearn** **import** preprocessing
**import** **pandas** **as** **pd**
**import** **numpy** **as** **np**
**import** **matplotlib.pyplot** **as** **plt**
**from** **prettytable** **import** PrettyTable
**from** **sklearn.linear_model** **import** SGDRegressor
**from** **sklearn** **import** preprocessing
**from** **sklearn.metrics** **import** mean_squared_error
**from** **sklearn.model_selection** **import** train_test_split
现在,我们将加载数据集。这里,X 包含我们拥有的数据集,Y 包含我们需要预测的标签。
X = load_boston().data
Y = load_boston().target
记得在缩放之前拆分数据,以避免数据泄漏问题。
*# split the data set into train and test*
X_train, X_test, y_train, y_test = train_test_split(X, Y, test_size=0.3, random_state=0)
使用标准标量函数来标准化数据集。在这里,我们只拟合训练数据,因为我们不希望我们的模型在此之前看到这些数据,以避免过度拟合。
scaler = preprocessing.StandardScaler()
X_train = scaler.fit_transform(X_train)
X_test = scaler.transform(X_test)
使用 pandas 创建数据框架。
X_train = pd.DataFrame(data = X_train, columns=load_boston().feature_names)
X_train['Price'] = list(y_train)
X_test = pd.DataFrame(data = X_test, columns=load_boston().feature_names)
X_test['Price'] = list(y_test)
让我们看看我们的 X_train 是什么样子的。
X_train.head()
下面是我们需要最小化的线性模型的损失函数。
现在,我们计算损失函数 L w.r.t 权重(W)和截距(b)的梯度。以下是计算梯度的方程式,
在计算梯度后,我们不断改变我们的权重和截距值。
最后,我们将实现 SGD 函数。
**def** sgd_regressor(X, y, learning_rate=0.2, n_epochs=1000, k=40):
w = np.random.randn(1,13) *# Randomly initializing weights*
b = np.random.randn(1,1) *# Random intercept value*
epoch=1
**while** epoch <= n_epochs:
temp = X.sample(k)
X_tr = temp.iloc[:,0:13].values
y_tr = temp.iloc[:,-1].values
Lw = w
Lb = b
loss = 0
y_pred = []
sq_loss = []
**for** i **in** range(k):
Lw = (-2/k * X_tr[i]) * (y_tr[i] - np.dot(X_tr[i],w.T) - b)
Lb = (-2/k) * (y_tr[i] - np.dot(X_tr[i],w.T) - b)
w = w - learning_rate * Lw
b = b - learning_rate * Lb
y_predicted = np.dot(X_tr[i],w.T)
y_pred.append(y_predicted)
loss = mean_squared_error(y_pred, y_tr)
print("Epoch: **%d**, Loss: **%.3f**" %(epoch, loss))
epoch+=1
learning_rate = learning_rate/1.02
**return** w,b
我们保持学习率= 0.2,次数= 1000,批量= 40。我们可以相应地改变参数来最小化我们的 MSE。
我们通过除以 1.02 来不断降低我们的学习率,你可以选择任何你想要的值。
我选择了 MSE 作为我的误差度量,我们也可以选择 RMSE。
现在,我们创建一个预测函数并计算我们的预测值。
**def** predict(x,w,b):
y_pred=[]
**for** i **in** range(len(x)):
temp_ = x
X_test = temp_.iloc[:,0:13].values
y = np.asscalar(np.dot(w,X_test[i])+b)
y_pred.append(y)
**return** np.array(y_pred)w,b = sgd_regressor(X_train,y_train)
y_pred_customsgd = predict(X_test,w,b)
比较我们的预测值和实际值
**from** **matplotlib.pyplot** **import** figure
plt.figure(figsize=(25,6))
plt.plot(y_test, label='Actual')
plt.plot(y_pred_customsgd, label='Predicted')
plt.legend(prop={'size': 16})
plt.show()
print('Mean Squared Error :',mean_squared_error(y_test, y_pred_customsgd))
正如我们看到的,我们得到了 26.8 的 MSE,这是相当不错的。
实施 SKlearn 的 SGD 回归器
**from** **sklearn.linear_model** **import** SGDRegressor
clf = SGDRegressor(max_iter=1000, tol=1e-3)
clf.fit(X_train, y_train)
y_pred = clf.predict(X_test)
比较我们的预测值和实际值
**import** **matplotlib.pyplot** **as** **plt**
**from** **matplotlib.pyplot** **import** figure
plt.figure(figsize=(25,6))
plt.plot(y_test, label='Actual')
plt.plot(y_pred, label='Predicted')
plt.legend(prop={'size': 16})
plt.show()
print('Mean Squared Error :',mean_squared_error(y_test, y_pred))
SKlearn 的 SGD 实现比我们的自定义实现要好得多。
比较两个 SGD 分类器的预测值
**import** **matplotlib.pyplot** **as** **plt**
**from** **matplotlib.pyplot** **import** figure
plt.figure(figsize=(25,6))
plt.plot(y_pred, label='SGD')
plt.plot(y_pred_customsgd, label='Custom SGD')
plt.legend(prop={'size': 16})
plt.show()
print('Mean Squared Error of Custom SGD :',mean_squared_error(y_test, y_pred_customsgd))
print("Mean Squared Error of SKlearn's SGD :",mean_squared_error(y_test, y_pred))
嗯,我们的自定义 SGD 做了一个相当不错的工作相比,SKlearn 的,我们总是可以做一些超参数调整,以改善我们的自定义模型。
从头开始实现算法需要很多时间,但是如果我们使用库,那么它对我们来说仍然是黑箱。请在评论区告诉我你对此的看法。
感谢你阅读这篇博客,希望你对 SGD 的实际运作有所了解。
在 PyTorch 中实现 TabNet
https://unsplash.com/photos/Wpnoqo2plFA
深度学习已经接管了视觉、自然语言处理、语音识别和许多其他领域,取得了惊人的成果,甚至在某些领域取得了超人的表现。然而,使用深度学习对表格数据进行建模相对有限。
对于表格数据,最常见的方法是使用基于树的模型及其集成。基于树的模型全局选择减少熵最多的特征。像 bagging、boosting 这样的集成方法通过减少模型方差来进一步改进这些基于树的方法。最近基于树的组合,如 XGBoost 和 LightGBM,已经主导了 Kaggle 竞赛。
TabNet 是谷歌云 AI 研究团队开发的神经架构。它能够在回归和分类问题中的几个数据集上实现最先进的结果。它结合了神经网络的特性以适应非常复杂的函数和基于树的算法的特性选择属性。换句话说,模型在训练过程中学习只选择相关的特征。此外,与只能进行全局特征选择的基于树的模型相反,TabNet 中的特征选择过程是基于实例的。TabNet 的另一个令人满意的特性是可解释性。与大多数深度学习相反,在深度学习中,神经网络的行为就像黑盒一样,我们可以解释模型在 TabNet 的情况下选择了哪些特征。
在这篇博客中,我将带您一步一步地体验 PyTorch 中对初学者友好的 TabNet 实现。我们开始吧!!
TabNet 架构。
1)资料来源:https://arxiv.org/pdf/1908.07442v1.pdf
图(1)取自最初的 TabNet 论文。我们将单独构建图像的每个组件,并在最后将它们组装起来。首先,让我们来看一下这个模型中使用的两个基本概念- Ghost 批处理规范化(GBN) 和 Sparsemax 。
Ghost 批量标准化(GBN):
GBN 让我们可以训练大批量的数据,同时也可以更好地进行归纳。简而言之,我们将输入批次分成大小相等的子批次(虚拟批次大小),并对它们应用相同的批次规范化层。除了应用于输入要素的第一个批处理规范化图层之外,模型中使用的所有批处理规范化图层都是 GBN 图层。它可以在 PyTorch 中实现,如下所示:
class **GBN**(nn.Module):
def __init__(self,inp,vbs=128,momentum=0.01):
super().__init__()
self.bn = nn.BatchNorm1d(inp,momentum=momentum)
self.vbs = vbs
def forward(self,x):
chunk = torch.chunk(x,x.size(0)//self.vbs,0)
res = [self.bn(y) for y **in** chunk]
return torch.cat(res,0)
稀疏最大值:
就像 softmax 一样,Sparsemax 是一个非线性归一化函数,但是顾名思义,分布是“更稀疏”的。也就是说,与 softmax 相比,输出概率分布中的一些数字更接近 1,而其他数字更接近 0。这使得模型能够在每个决策步骤中更有效地选择相关特征。我们将使用 sparsemax 将特征选择步骤的掩码投影到一个更稀疏的空间。sparsemax 的实现可以在 https://github.com/gokceneraslan/SparseMax.torch的找到
为了进一步增加遮罩中的稀疏性,我们还将添加稀疏性正则化技术来惩罚不太稀疏的遮罩。这可以在每个决策步骤中实现,如下所示:
(mask*torch.log(mask+1e-10)).mean() #F(**x**)= -**∑x**log(**x+eps**)
所有决策步骤上的该值的总和可以被添加到总损失中(在乘以正则化常数λ之后)。
注意力转换器:
这是模型学习相关特征之间的关系并决定将哪些特征传递给当前决策步骤的特征转换器的地方。每个注意力转换器由一个全连接层、一个 Ghost 批处理规范化层和一个 Sparsemax 层组成。每个决策步骤中的注意力转换器接收输入特征、来自前一步骤的已处理特征以及关于已用特征的先验信息。先验信息由大小为 batch_size x input_features 的矩阵表示。它被初始化为 1,并在每个决策步骤的注意力转换器处被传递和更新。还有一个松弛参数,用于限制某个特性在向前传递中可以使用的次数。较大的值意味着模型可以多次重复使用同一要素。我认为守则把一切都讲清楚了。
class **AttentionTransformer**(nn.Module):
def __init__(self,d_a,inp_dim,relax,vbs=128):
super().__init__()
self.fc = nn.Linear(d_a,inp_dim)
self.bn = GBN(out_dim,vbs=vbs)
self.smax = Sparsemax()
self.r = relax #a:feature from previous decision step
def forward(self,a,priors):
a = self.bn(self.fc(a))
mask = self.smax(a*priors)
priors =priors*(self.r-mask) #updating the prior
return mask
然后将该掩膜乘以(按元素)归一化的输入要素。
特征转换器:
要素转换器是处理所有选定要素以生成最终输出的地方。每个特征变换器由多个门控线性单元块组成。GLU 控制哪些信息必须被允许在网络中进一步流动。为了实现一个 GLU 模块,首先我们使用一个完全连接的层将 GLU 的输入特征的尺寸加倍。我们使用一个 GBN 层来标准化结果矩阵。然后,我们将一个 sigmoid 应用于结果特征的第二部分,并将结果乘以第一部分。结果乘以一个比例因子(本例中为 sqrt(0.5))并添加到输入中。该求和结果是序列中下一个 GLU 模块的输入。
在所有决策步骤中共享一定数量的 GLU 块,以提高模型容量和效率(可选)。第一共享 GLU 块(或者第一独立块,如果没有块被共享的话)是唯一的,因为它将输入特征的维数减少到等于 n_a+n_d 的维数。n_a 是输入到下一步骤的注意力转换器的特征的维数,n_d 是用于计算最终结果的特征的维数。这些特征被一起处理,直到它们到达分割器。ReLU 激活应用于 n_d 维向量。所有决策步骤的输出相加在一起,并通过完全连接的层将它们映射到输出维度。
class **GLU**(nn.Module):
def __init__(self,inp_dim,out_dim,fc=None,vbs=128):
super().__init__()
if fc:
self.fc = fc
else:
self.fc = nn.Linear(inp_dim,out_dim*2)
self.bn = GBN(out_dim*2,vbs=vbs)
self.od = out_dim
def forward(self,x):
x = self.bn(self.fc(x))
return x[:,:self.od]*torch.sigmoid(x[:,self.od:])class **FeatureTransformer**(nn.Module):
def __init__(self,inp_dim,out_dim,shared,n_ind,vbs=128):
super().__init__()
first = True
self.shared = nn.ModuleList()
if shared:
self.shared.append(GLU(inp_dim,out_dim,shared[0],vbs=vbs))
first= False
for fc **in** shared[1:]:
self.shared.append(GLU(out_dim,out_dim,fc,vbs=vbs))
else:
self.shared = None
self.independ = nn.ModuleList()
if first:
self.independ.append(GLU(inp,out_dim,vbs=vbs))
for x **in** range(first, n_ind):
self.independ.append(GLU(out_dim,out_dim,vbs=vbs))
self.scale = torch.sqrt(torch.tensor([.5],device=device))
def forward(self,x):
if self.shared:
x = self.shared[0](x)
for glu **in** self.shared[1:]:
x = torch.add(x, glu(x))
x = x*self.scale
for glu **in** self.independ:
x = torch.add(x, glu(x))
x = x*self.scale
return x
接下来,让我们将注意力转换器和特征转换器结合成一个决策步骤:
class **DecisionStep**(nn.Module):
def __init__(self,inp_dim,n_d,n_a,shared,n_ind,relax,vbs=128):
super().__init__()
self.fea_tran = FeatureTransformer(inp_dim,n_d+n_a,shared,n_ind,vbs)
self.atten_tran = AttentionTransformer(n_a,inp_dim,relax,vbs)
def forward(self,x,a,priors):
mask = self.atten_tran(a,priors)
sparse_loss = ((-1)*mask*torch.log(mask+1e-10)).mean()
x = self.fea_tran(x*mask)
return x,sparse_loss
最后,我们可以通过将几个决策步骤结合在一起来完成模型:
class **TabNet**(nn.Module):
def __init__(self,inp_dim,final_out_dim,n_d=64,n_a=64,
n_shared=2,n_ind=2,n_steps=5,relax=1.2,vbs=128):
super().__init__()
if n_shared>0:
self.shared = nn.ModuleList()
self.shared.append(nn.Linear(inp_dim,2*(n_d+n_a)))
for x **in** range(n_shared-1):
self.shared.append(nn.Linear(n_d+n_a,2*(n_d+n_a)))
else:
self.shared=None
self.first_step = FeatureTransformer(inp_dim,n_d+n_a,self.shared,n_ind)
self.steps = nn.ModuleList()
for x **in** range(n_steps-1):
self.steps.append(DecisionStep(inp_dim,n_d,n_a,self.shared,n_ind,relax,vbs))
self.fc = nn.Linear(n_d,final_out_dim)
self.bn = nn.BatchNorm1d(inp_dim)
self.n_d = n_d
def forward(self,x):
x = self.bn(x)
x_a = self.first_step(x)[:,self.n_d:]
sparse_loss = torch.zeros(1).to(x.device)
out = torch.zeros(x.size(0),self.n_d).to(x.device)
priors = torch.ones(x.shape).to(x.device)
for step **in** self.steps:
x_te,l = step(x,x_a,priors)
out += F.relu(x_te[:,:self.n_d])
x_a = x_te[:,self.n_d:]
sparse_loss += l
return self.fc(out),sparse_loss
模型超参数的近似范围: n_d,n_a: 8 到 512
批量:256 到 32768
虚拟批量:128 到 2048
稀疏正则化常数:0 到 0.00001
共享 GLU 块数:2 到 10
独立判决块数:2 到 10
松弛常数:1 到 2.5
判决步数)
这个 TabNet 模块可以扩展用于分类和回归任务。您可以为分类变量添加嵌入,将 Sigmoid 或 Softmax 函数应用于输出,等等。尝试一下,看看什么最适合你。在示例笔记本中,我尝试用 Sigmoid 替换 Sparsemax,并能够获得稍好的精度。
关于行动机制(MoA)预测数据集的用例示例,你可以在这里找到我的笔记本:https://www.kaggle.com/samratthapa/drug-prediction。这是目前正在 Kaggle 上举行的一场比赛的数据集。
我对 TabNet 的实现是对 DreamQuark 慷慨的人们的工作的一个简短的改编。他们对 TabNet 的完整实现可以在:
https://github . com/dream quark-ai/TabNet/tree/develop/py torch _ TabNet找到。
你应该考虑阅读论文以获得对 TabNet 更详细的描述。
感谢您的阅读。我希望我能帮上忙。
参考文献:
1)塞尔詹·奥鲁克,托马斯·菲斯特。2020.TabNet:专注的可解释表格学习https://arxiv.org/abs/1908.07442v4
2)扬·n·多芬、安吉拉·范、迈克尔·奥利和大卫·格兰吉尔。2016.用门控卷积网络进行语言建模。https://arxiv.org/pdf/1612.08083.pdf、埃拉德·霍弗、伊泰·胡巴拉和丹尼尔·苏德里。2017.训练时间越长,推广效果越好:缩小神经网络大批量训练中的推广差距。
https://arxiv.org/pdf/1705.08741.pdf
4)安德烈·马丁斯和拉蒙·费尔南德斯·阿斯图迪略。2016.从 Softmax 到 Sparsemax:注意力和多标签分类的稀疏模型。
https://arxiv.org/abs/1602.02068
5)Sparsemax 实现https://github.com/gokceneraslan/SparseMax.torch
6)完成 PyTorch TabNet 实现
https://github . com/dream quark-ai/TabNet/tree/develop/py torch _ TabNet
在 python 中实现通用树和深度优先搜索(DFS )!(从头开始)
信用:Pixabay
最近,我在《走向数据科学》上发表了一篇关于从零开始实现链表的文章。阅读之前的故事对于理解这里的核心概念是不必要的,但是之前使用的代码将被引用;因此,我将把 GitHub Gists 链接到链表和一般的树实现。
作为一名数据科学领域的研究生,我经常发现统计概念得到了大量的课程介绍,但基本的 CS 概念往往被抽象掉了。也许有充分的理由;知道如何实现一棵树对于训练一个随机的森林来说是不必要的。更高级的 API 允许我们利用基本概念,而不会被细节所困扰。
那么什么是一般的树——你为什么要关心呢?
到目前为止,二叉树在 CS 基础中更常见。每棵树都有三个强制元素:一个值、一个左子引用和一个右子引用。实现的任何其他东西都是为了满足程序员预见的独特目的。
另一方面,一般的树不限于有确切地说是两个孩子。这尤其有用,因为分层数据在 web 上非常普遍。**例如,考虑探索 API 查询结果的任务。XML 和 JSON 数据都是树状数据。**通用树会给你工具来快速搜索特定字符串、数值等的查询结果。
我们的数据结构不局限于两个或零个孩子,将比二叉树灵活得多,但它也将迫使我们做出重要的设计决策。其中最重要的是——树节点必须有有限数量的属性。如果任何节点都可以有任意数量的子节点,我们该如何解决这个难题呢?列表、元组和字典是自然的选择;单个通用树节点可以将其children
属性设置为单个变量,该变量可以引用可变数量的通用树节点。
但是我承诺从头实现它,因为我最近从头实现了链表,我们将继续以同样的势头向前冲。简单回顾一下——链表是由节点组成的。每个节点引用序列中的下一个节点(也可以是前一个节点——也就是双向链表)。)第一个节点是头,因此,必须从头遍历到序列中的任何其他节点。在头部位置的插入是微不足道的(与动态数组相比),您只需实例化一个引用头部的新节点,并将 head 属性设置为链表的新头部。
我们将以两种方式构建链表:我们将直接使用链表来跟踪我们已经访问过的树节点;我们将借用链表的精神来指导我们的设计选择。
考虑下图。典型的通用树将允许任何子节点被给定的父节点访问。因为我们的数据结构将建立在一个链表上,一个给定的父节点将只引用一个子节点,,特别是最左边的子节点。要访问它的其他子节点,我们必须遍历兄弟节点。只要我们意识到这一点,并且我们的设计选择考虑到这一点,这就不是问题。另外,我们没有借用 python 的内置数据类型,这意味着我们最大化了我们的学习体验。
让我们从一般的树节点开始,它将有以下参数:值、父节点、左兄弟节点、右兄弟节点和子节点。这本质上是一个双向链表,因为它在水平和垂直方向上都是双向链接的。很像链表,我们需要定义getter和setter。这些函数将设置一个节点对另一个节点的引用属性,或者获取这个值的值(如果存在的话)。
到目前为止,我们已经讨论了体系结构,但是通用树的真正用途来自于搜索它的能力。有多种策略来遍历一般的树;最常见的两种是广度优先搜索(BFS)和深度优先搜索(DFS)。我们今天将只实现后者。在高层次上,策略是尽可能深入第一个分支;当我们不能再往下走时,我们退回到前面的父节点,并遍历它的第一个兄弟节点。我们能下去吗?如果是这样,我们探索这个分支的深度。如果不能,我们探索下一个兄弟姐妹。这听起来极其复杂,但是当我们从基于条件的策略的角度来考虑时,就容易多了。
DFS 的五个场景:
- 如果一个子节点存在并且还没有被浏览,那么遍历到这个子节点。
- 如果右兄弟存在且尚未被浏览,则向右遍历。
- 如果我们在最右边的孩子,我们可以退回到左边,那么遍历到左边。 【撤退】
- 如果我们不在最左边的子节点上,但是我们已经在右边的和上找到了的兄弟节点,那么遍历到左边。 (撤退)
- 否则,遍历父项。 【撤退】
这里真正的技巧是使用递归,这是每个程序员都应该努力掌握的技能。递归函数有两个元素:终止条件和其他一切。如果终止条件被触发,函数将存在并返回一些期望的输出。如果终止条件不满足,该函数将采取措施向终止条件推进,然后调用自身。
出于我们的目的,我们需要对和有一些概念,为此我们将定义current_node
和current_value
变量。每当我们遍历到另一个节点时,我们就更新这些值。我们的终止条件是这样的:如果当前值是 根节点值 或 搜索值 ,我们退出 。(显然,每个节点有一个唯一的名称很重要,让我们过早退出。)如果不满足这个条件,我们观察上面的五个条件规则,根据当前节点执行任何合适的规则/策略,并重复直到满足终止条件。这就是全部了。
我们将在下面定义三种截然不同但非常相似的方法。第一个是depth_first_traversal
。这里,我们将根据 DFS 协议研究整个树。没有搜索值,因此我们仅在到达根节点时终止(即,我们已经探索了所有子节点的所有子节点。)其次我们来定义depth_first_search
。这里,我们将提供一个搜索值。如果达到这个值,我们将退出循环。如果我们一直追溯到根节点,这个值在树中就不存在了。值得注意的是,这两个函数将通知程序员节点*实际被遍历的顺序。*换句话说,兄弟遍历将被考虑。
我们需要定义第三个函数,它对真实的层次数据有用。例如,在文件存储系统中。一旦您将目录更改为给定的文件夹,您就不需要遍历所有文件和文件夹,直到您到达所需的文件夹后再打开它。相反,您只需遍历parent -> child
并重复,直到您到达所需的子目录。对我们的影响:使用我们的架构,我们需要清除兄弟遍历,这样我们的输出就和任何其他通用树的输出一样。
这种改变没有你想象的那么困难。但是在我们讨论它之前,我们需要谈谈上面讨论的所有三种方法的共性。需要创建一个visited
类属性。正如前面所讨论的,这将是一个链表,其中一旦一个树节点被访问,它的值将被追加到链表的末尾。这使得这三种方法避免了从父到子到父的无限遍历…一次又一次。
后两种方法使用了 path 类属性的变体,这是——您猜对了——另一个链表(持续给予的礼物!)这里的想法是只维护不涉及撤退的节点,不像被访问的属性。path
和child_path
最大的区别在于,前者包括对兄弟姐妹的遍历,而后者不包括。子路径是最令人感兴趣的,因为它在真实世界的数据中很有用,而 path 属性只在我们程序的上下文中有用。在任一情况下,使用我们上面的五个 DFS 条件规则/策略,条件 3、4、& 5 都是撤退,这意味着当撤退时我们从我们的路径/子路径中清除这些节点值。(如果推理还不明显,当我们回顾代码时,它会更有意义。简单地说,这些分支是死胡同,我们从路径/子路径变量中剪除。)
重新考虑终止条件,我们做的最后一件事是为将来的迭代重置。这是通过将我们的路径复制到一个临时变量,将所有其他变量重新定义到它们的起始条件,然后返回临时变量来实现的。过一会儿这就更有意义了——我保证!(如果我们不重置,我们将需要重新实例化我们的通用树类,以便搜索不同的值,因为我们的变量将存在于非启动条件下,从而触发错误。)
现在——代码!让我们回顾一下链表。
并在一般的树(最后!!)
考虑下面的树:
a1
|
b1 - b2 - b3
| |
d1 c1
如果我们运行下面的单元格,我们将把这个树实例化到通用树类对象中,然后找到从根到节点 c1 的最短路径(有和没有兄弟)。
a1 = GeneralTreeNode(value='a1')
b1 = GeneralTreeNode(value='b1')
b2 = GeneralTreeNode(value='b2')
b3 = GeneralTreeNode(value='b3')
a1.set_child(b1)
b1.set_parent(a1)
b1.set_right(b2)
b2.set_left(b1)
b2.set_right(b3)
b3.set_left(b2)c1 = GeneralTreeNode(value='c1')
c1.set_parent(b3)
b3.set_child(c1)d1 = GeneralTreeNode(value='d1')
d1.set_parent(b1)
b1.set_child(d1)r = GeneralTree(root=a1)r.depth_first_search(search_val='c1')
>>>>
Node: a1
Node: b1
Node: b2
Node: b3
Node: c1r.child_depth_first_search(search_val='c1')
>>>>
Node: a1
Node: b3
Node: c1
看到depth_first_search
和child_depth_first_search
的区别了吗?在后者中,节点 b1 和 b2 被省略,因为它们在一般树的传统定义中不是必需的遍历。后一种方法是您想要在现实世界问题中使用的方法(就像我们之前的文件系统导航示例一样。)
如果你对本教程如何更有帮助有想法,对扩展有想法,请求合作等,请联系我。名义上,我计划要么创建一个 XML 解析器、JSON 解析器,要么使用树来搜索嵌套字典,等等。
我希望你喜欢今天和我一起深入研究基本面!
用 python 实现链表和选择排序!(从头开始)
信用:PixaBay.com
在这篇文章中,我将从头开始实现链表,并构建重要的功能,如插入(和 insert_at)、删除、交换和排序。作为数据科学的研究生,我发现大部分材料都集中在机器学习背后的统计学和数学上。毫无疑问,在这里取得成功对数据科学领域的职业生涯至关重要。然而,计算机科学基础的重要性往往被边缘化。
那么什么是链表呢?
链表是一个由节点组成的对象,节点(你猜对了)也是对象。这两者我们都需要实现。每个节点都包含一个值和一个到后续节点的链接。这是强制性的。一个双向链表(我们在这里只涉及定性术语)是一个类似的对象,它的节点也包含到它们之前节点的链接。一个头是必不可少的。它是链表中的第一个条目。默认情况下,它的下一个节点属性设置为“无”。当我们添加第二个项目时,我们实例化一个节点对象并设置 heads 的 next 属性来引用我们刚刚创建的(新)节点。如果我们向链表中添加第三个节点,我们将实例化一个新的节点对象,并将第二个节点的 next 属性设置为引用新创建的节点。并且对于我们想要插入到链表中的任意数量的值,重复这个过程。
当我们想在列表中间插入一个值时,事情变得更加棘手。假设我们有一个由三个元素0,2,3
组成的链表,我们希望将值1
插入到索引位置1
。目前,头部是0
,它的下一个属性引用了2
。我们需要实例化一个节点对象,将值设置为1
。从这里开始,我们需要将头部的 next 属性设置为1
,将1
的 next 属性设置为2
。
听起来很简单,对吗?您会注意到,为了通过索引到达一个节点,我们需要遍历所有以前的节点,从头部开始。虽然没有明确说明,但是我们需要定义一个名为get_next()
的类方法,它将引用下面的节点。当我们在最后一个节点时会发生什么?除非我们考虑到这一点,否则我们会触发一个错误。有必要考虑链表的长度以及从头部开始我们已经走了多远。我们将通过创建一个名为count
的类属性来做到这一点,它将引用链表的长度或其中最后一项的索引(在这个实现中,我选择了后一个选项。)
当我们删除一个值时,我们需要将计数减 1;同样,当我们插入一个值时,我们需要将计数增加 1。当我们交换值时,这个过程变得更加棘手。我们需要删除和插入值;因此,链表中的计数和相对位置可以根据我们执行这些过程的顺序而变化。跟踪我们所处的位置需要注意细节。
最困难的任务是对链表进行排序。我选择使用选择排序作为算法的选择。优秀的排序算法确实存在,但是我们将把重点放在选择排序上,因为它是排序算法中相对简单的。该算法通过建立排序值的索引位置来工作。当我们开始时,这个索引被设置为 0。我们将 selection 设置为后续值,并遍历列表的剩余部分,在和sorted_idx
的右边寻找最小值。如果一个节点的值小于selection
,那么我们将选择设置为这个值,并跟踪它的索引。遍历链表并找到最小值后,我们删除这个节点,实例化一个值等于选择的新节点,并将这个节点插入到sorted_idx
。最后,我们将sorted_idx
增加 1,并重复直到这个索引等于 count 属性减 1(因为在我们到达最后一个节点后,没有节点可遍历。)
您会注意到,这个过程从将sorted_idx
设置为 0 开始,并将selection
设置为紧跟着头部的节点的值。在 head 是最大值的情况下,当进程终止时,它将被推到列表的末尾,这没有问题。但如果头不是最大值,还是会被推到底;但这是一个不完整的排序。我们需要构建最后一个步骤来计算最后一个条目的值,并遍历整个链表。在每一步,节点的值将与最终条目进行比较。如果大于节点的值,我们将遍历到下一个节点并再次比较。这个过程一直重复,直到我们到达一个节点,这个节点的值大于条目的最终值,这是我们前面提到的。当达到一个更高的值时(并且因为所有先前的值都更小),我们现在知道最后一个条目的真正家在哪里。我们删除最后一个条目,并将其值插入索引中,直到第一个值大于我们的选择。如果我们遍历列表的长度(除了最后一个条目),并且所有的值都较小,那么我们可以有把握地得出结论,最后一个条目是最大值,并且已经在它适当的位置。咻!
让我们来看看代码!
https://gist . github . com/jdmoore 7/6 F9 FBC 76139 DDB 0 c 7d 485 f 97349 B3 bbf
完整的资源库可以在这里找到:https://github . com/jdmoore 7/linked _ list/blob/master/linked list . py
感谢阅读!
为语言建模实现转换器
使用 Fairseq 训练变压器模型
图片由作者提供(Fairseq logo: 来源
介绍
自然语言处理的最新趋势是建立在该领域历史上最大的突破之一之上的: 转换器 。Transformer 是主要由 Google Brain 和 Google Research 研究的模型架构。它最初被证明在翻译任务中达到了最先进的水平,但后来当它被大规模采用时,被证明在几乎任何 NLP 任务中都是有效的。transformer 架构由一系列编码器和解码器组成,具有自我关注层,有助于模型关注各自的输入。你可以在原文这里了解更多关于变形金刚的知识。
在这篇文章中,我们将向您展示如何实现语言建模任务的转换器。语言建模是给语言中的句子分配概率的任务。语言建模的目标是让模型将高概率分配给我们数据集中的真实句子,以便它能够通过解码器方案生成接近人类水平的流畅句子。我们将使用 Fairseq 库来实现转换器。
第一步:准备数据集(来自我之前的博文)
使用 Pytorch 和 Huggingface 微调用于文本生成的 GPT2。我们在 CMU 图书摘要数据集上进行训练,以生成…
towardsdatascience.com](/fine-tuning-gpt2-for-text-generation-using-pytorch-2ee61a4f1ba7)
在本文中,我们将再次使用 CMU 图书摘要数据集来训练 Transformer 模型。可以参考博文的步骤 1 来获取和准备数据集。准备好数据集后,您应该准备好与数据集的三个分区相对应的 train.txt 、 valid.txt 和 test.txt 文件。
步骤 2:下载并安装 Fairseq
如果你没有听说过 Fairseq ,它是一个流行的 NLP 库,由脸书 AI 开发,用于实现翻译、摘要、语言建模和其他生成任务的定制模型。你可以在这里查看我对 Fairseq 的评论。
现在,为了下载并安装 Fairseq,运行以下命令:
git clone https://github.com/pytorch/fairseq
cd fairseq
pip install --editable ./
如果您的 GPU 允许,您还可以选择安装 NVIDIA 的 apex 库来加快训练速度:
git clone https://github.com/NVIDIA/apex
cd apex
pip install -v --no-cache-dir --global-option="--cpp_ext" --global-option="--cuda_ext" \
--global-option="--deprecated_fused_adam" --global-option="--xentropy" \
--global-option="--fast_multihead_attn" ./
现在,您已经成功安装了 Fairseq,我们终于可以开始了!
步骤 3:预处理数据集
为了预处理数据集,我们可以使用 fairseq 命令行工具,这使得开发人员和研究人员可以很容易地从终端直接运行操作。为了预处理我们的数据,我们可以使用fairseq-preprocess
来构建我们的词汇表,并对训练数据进行二进制化。
cd fairseq/
DATASET=/path/to/dataset
fairseq-preprocess \
--only-source \
--trainpref $DATASET/train.txt \
--validpref $DATASET/valid.txt \
--testpref $DATASET/test.txt \
--destdir data-bin/summary \
--workers 20
预处理的命令输出
执行上述命令后,预处理后的数据将保存在--destdir
指定的目录下。
步骤 4:训练变压器
终于可以开始训练变形金刚了!要训练一个模型,我们可以使用fairseq-train
命令:
CUDA_VISIBLE_DEVICES=0 fairseq-train --task language_modeling \
data-bin/summary \
--save-dir checkpoints/transformer_summary \
--arch transformer_lm --share-decoder-input-output-embed \
--dropout 0.1 \
--optimizer adam --adam-betas '(0.9, 0.98)' --weight-decay 0.01 --clip-norm 0.0 \
--lr 0.0005 --lr-scheduler inverse_sqrt --warmup-updates 4000 --warmup-init-lr 1e-07 \
--tokens-per-sample 512 --sample-break-mode none \
--max-tokens 2048 --update-freq 16 \
--fp16 \
--max-update 50000 \
--max-epoch 12
在我们的例子中,我们将 GPU 指定为第 0 个(CUDA_VISIBLE_DEVICES
),将任务指定为语言建模(--task
),将数据指定为data-bin/summary
,将架构指定为转换器语言模型(--arch
),将历元数指定为 12 ( --max-epoch
,以及其他超参数。训练完成后,模型的最佳检查点将保存在--save-dir
指定的目录下。
12 个纪元需要一段时间,所以当你的模型训练时请坐好!当然,你也可以根据自己的需要减少历元数来训练。训练完成后,将显示以下输出:
训练结束时的命令输出
请注意,在每个历元中,会显示相关的数字,例如丢失和困惑。这些可能有助于在训练过程中评估模型。
步骤 5:评估语言模型
在您的模型完成训练之后,您可以使用fairseq-eval-lm
评估生成的语言模型:
fairseq-eval-lm data-bin/summary \
--path checkpoints/transformer_summary/checkpoint_best.pt \
--max-sentences 2 \
--tokens-per-sample 512 \
--context-window 400
在这里,将评估测试数据以对语言模型进行评分(在训练阶段使用训练和验证数据来寻找模型的优化超参数)。下面显示了评估后的命令输出:
用于评估的命令输出
如你所见,我们模型的损失是 9.8415,困惑度是 917.48(以 2 为基数)。
第六步:终于!让我们生成一些文本:D
在训练模型之后,我们可以尝试使用我们的语言模型生成一些样本。要生成,我们可以使用fairseq-interactive
命令创建一个交互会话来生成:
fairseq-interactive data-bin/summary \
--task language_modeling \
--path checkpoints/transformer_summary/checkpoint_best.pt \
--beam 5
在交互式会话期间,程序将提示您输入文本。输入文本后,模型将在输入后生成标记。一个代样给定书发生地作为输入T7 是这样的:
这本书发生在故事的故事,故事的故事,故事的故事,故事的人物…
生成是重复的,这意味着模型需要用更好的参数来训练。上述命令使用光束尺寸为 5 的光束搜索。我们还可以使用采样技术,如 top-k 采样:
fairseq-interactive data-bin/summary \
--task language_modeling \
--path checkpoints/transformer_summary/checkpoint_best.pt \
--sampling --beam 1 --sampling-topk 10
以及 top-p 采样:
fairseq-interactive data-bin/summary \
--task language_modeling \
--path checkpoints/transformer_summary/checkpoint_best.pt \
--sampling --beam 1 --sampling-topp 0.8
注意,当使用 top-k 或 top-sampling 时,我们必须添加beam=1
来抑制当--beam
不等于--nbest
时产生的误差。这好像是个 bug。
结论
在这篇博文中,我们使用流行的 Fairseq 库在书籍摘要上训练了一个经典的 transformer 模型!虽然生成示例是重复的,但是本文可以作为一个指南,带您在语言建模上运行一个转换器。如果:D 感兴趣,看看我的其他帖子
AllenNLP,Fast.ai,Spacy,NLTK,TorchText,Huggingface,Gensim,OpenNMT,ParlAI,DeepPavlov
towardsdatascience.com](/top-nlp-libraries-to-use-2020-4f700cdb841f) [## 使用 Pytorch 微调用于文本生成的 GPT2
使用 Pytorch 和 Huggingface 微调用于文本生成的 GPT2。我们在 CMU 图书摘要数据集上进行训练,以生成…
towardsdatascience.com](/fine-tuning-gpt2-for-text-generation-using-pytorch-2ee61a4f1ba7) [## 控制语言模型的文本生成
控制机器生成文本的样式和内容的实际操作方法
towardsdatascience.com](/controlling-text-generation-from-language-models-6334935e80cf) [## 使用 Pytorch 的 BERT 文本分类
文本分类是自然语言处理中的一项常见任务。我们应用 BERT,一个流行的变压器模型,对假新闻检测使用…
towardsdatascience.com](/bert-text-classification-using-pytorch-723dfb8b6b5b)
参考
[1] A .瓦斯瓦尼,n .沙泽尔,n .帕尔马等。,注意力是你需要的全部 (2017),第 31 届神经信息处理系统会议
[2] L .邵、s .古乌斯、d .布里兹等。,用序列对序列模型生成高质量、信息量大的会话响应 (2017),自然语言处理中的经验方法
[3] A. Fan,M. Lewis,Y. Dauphin,【分层神经故事生成】 (2018),计算语言学协会
[4] A .霍尔茨曼、j .买斯、l .杜等。,神经文本退化的好奇案例 (2019),国际学习表征会议
[5] Fairseq Github ,脸书艾研究
[6] Fairseq 文档,脸书艾研究