用 Python 生成 5 行二维码
或许在你的简历中使用它来链接到你的网站或 LinkedIn 个人资料
米蒂亚·伊万诺夫在 Unsplash 上拍摄的照片
快速响应码或 QR 码是一种二维条形码,由白色背景上的黑色小方块组成。由于其存储更多信息的能力和快速可读性,它比由黑条和空白组成的传统条形码更受欢迎。
Python 提供了一个 QRCode 包,这使得实现这个功能变得非常容易。我们将从下载以下 python 包开始。
pip install pillow
pip install qrcode
为了这个演示的目的,我使用了我的一篇文章的页面链接(10 行中的人脸检测)。我们将创建一个二维码来存储这篇文章的链接。
现在,让我们看看实现它有多简单。
import qrcode# Link for website
input_data = "[https://towardsdatascience.com/face-detection-in-10-lines-for-beginners-1787aa1d9127](/face-detection-in-10-lines-for-beginners-1787aa1d9127)"#Creating an instance of qrcode
qr = qrcode.QRCode(
version=1,
box_size=10,
border=5)qr.add_data(input_data)
qr.make(fit=True)img = qr.make_image(fill='black', back_color='white')
img.save('qrcode001.png')
上面使用的 QRCode 函数接受以下参数:
版本 :此参数定义生成的二维码的大小。它接受 1 到 40 范围内的整数值。该值越高,生成的 QR 码图像的尺寸越大。
box_size :该参数定义每个方框的大小,以像素为单位。
边框 :边框定义了边框的粗细。在这个例子中,值 5 意味着边框等于 5 个小黑盒的厚度。
add_data 方法用于传递我们输入的文本,在我们的例子中是文章的超链接。将 make 函数与 (fit=True) 配合使用,可确保二维码的整个维度都得到利用,即使我们的输入数据可以放入更少的框中。
最后一步是将其转换为图像文件并存储。为此,使用 make_image 功能,我们可以指定前景和背景颜色。我分别使用了黑色和白色,但是你可以根据你的喜好来改变它们。 保存 功能将图像保存为当前目录下的. png 文件。这里的也给出了代码和依赖关系的详细信息,以供参考。
这是我们为链接生成的二维码!
图片:阿林多姆·巴塔查尔吉
总之,我们研究了如何将文本值转换成相应的 QR 码。也许你可以为你自己的网址或 LinkedIn 个人资料创建一个二维码,并把它放在你的名片或简历中。干杯!
使用 Python OpenCV 在图像和视频中检测人脸的介绍。
towardsdatascience.com](/face-detection-in-10-lines-for-beginners-1787aa1d9127) [## 绝对初学者的神经网络
用简单的英语介绍感知器
medium.com](https://medium.com/swlh/artificial-neural-networks-for-absolute-beginners-a75bc1522e1d)
参考文献&延伸阅读:
【1】https://pypi.org/project/qrcode/
【2】【https://en.wikipedia.org/wiki/QR_code】T21*
生成样本数据集—数据科学家的必备技能。
制作 powerpoint 幻灯片和从理论上谈论你将如何处理数据是一回事。但创建一个样本数据集并展示一个已经在工作的仪表板、可视化或数据模型是另一回事。虽然可以在互联网上找到许多样本数据集,但它们通常并不完全符合您的需求。因此,在本文结束时,您将知道如何为您的特定目的创建一个漂亮的样本 CSV 数据集。一旦在 Jupyter 笔记本中设置了代码,您就可以反复使用它。
我们将如何生成样本数据集?
作为最终结果,一个样本 CSV 数据集与各种变量,如数字,货币,日期,名称和字符串,是可取的。这些变量有不同的类型,彼此独立或相关。首先,理解我们如何使用基本的“随机”函数来生成样本数据集是至关重要的。之后,我们将在一个数据框中组合变量。出于我们的目的,数据帧将最终导出为 CSV 格式。CSV 是一种非常容易与各种工具连接的格式,足以用于采样目的。
我们使用的工具
创建样本数据集的工具。
好的方面是,我们主要需要访问 python,使用它的一些库和工具。首先,我们使用 Jupyter Notebook,这是一个用于实时编码的开源应用程序,它允许我们用代码讲述一个故事。在我们的例子中,它简化了修改的过程,并且以后只使用很少的代码元素。此外,我们使用 NumPy,尤其是 NumPy 的随机函数来生成我们的变量。虽然这是一种选择,但是一个非常方便的名为 pydbgen 的库可以帮助我们加速样本数据集的生成。此外,我们导入了熊猫,这将我们的数据放在一个易于使用的结构中,用于数据分析和数据转换。
随机数据的基本函数
- “随机”模块 用 Python 创建随机数用的最多的模块大概就是 random.random()函数的 random 模块了。当导入模块并调用函数时,将生成一个介于 0.0 和 1.0 之间的浮点数,如下面的代码所示。
>>>import random>>>random.random()
0.18215964678315466
此外,还有设置种子的选项。这使我们能够复制代码中生成的随机数。
>>> random.seed(123)
>>> random.random()
0.052363598850944326>>> random.seed(123)
>>> random.random()
0.052363598850944326
如果您想为浮点数或整数考虑一个特定的范围,您可以如下定义函数的参数。
>>> random.randint(10,100)
21>>> random.uniform(10,100)
79.20607497283636
2。日期 在熟悉 random 方法的同时,我们现在可以应用这些知识在我们的样本数据集中创建随机日期。
>>> import numpy as np>>> monthly_days = np.arange(0, 30)
>>> base_date = np.datetime64('2020-01-01')
>>> random_date = base_date + np.random.choice(monthly_days)>>> print(random_date)
2020-01-07
有无数的变化来调整这段代码。将月天数范围更改为双月或每年。当然,基准日期应该符合您的要求。
3。字符串 当我们的样本数据集有了随机数和日期后,是时候看看字符串了。当查看下面的 randomString 函数时,变量“letters”包含小写的整个字母表。在“随机”方法的帮助下,一个随机数被创建并分配给 letter。就这么简单!
import random
import stringdef randomString(stringLength=15):
letters = string.ascii_lowercase
return ''.join(random.choice(letters) for i in range(stringLength))print ("The generated string is", randomString())The generated string is celwcujpdcdppzx
4。名字 虽然我们的样本数据集以前是基于随机方法的,但是仅仅通过随机地把东西放在一起是不可能生成有意义的表达式的。因此,我们使用 PyPi 上可用的名称包。这个包非常方便,提供了各种选项,如下面的代码片段所示。
>>> import names
>>> names.get_full_name()
‘Margaret Wigfield’>>> names.get_first_name(gender='female')
'Patricia'
5。地点、电话号码、地址或文本 在简短介绍了随机化的原则后用“random”作为基本方法,可能值得注意的是,有完整的软件包可以满足您的大部分需求。通过 pip 安装很简单,你可以在 Github 库上找到所有需要的信息。有两个非常有用的方法绝对值得一提:
6。机器学习的样本数据怎么样 当涉及到机器学习问题时,这项工作并不是通过生成任何随机数据来完成的。大多数情况下,您希望样本数据集的变量/特征之间存在某种关联或关系。Scikit 让你在几秒钟内创建这样的数据集。看看下面的示例代码:
import pandas as pd
from sklearn.datasets import make_regression# Generate fetures, outputs, and true coefficient of 100 samples
features, output, coef = make_regression(n_samples = 100,
# three features
n_features = 3,
# two features are useful,
n_informative = 2,
# one target
n_targets = 1,
# 0.0 standard deviation
noise = 0.0,
# show the true coefficient
coef = True)
# View the features of the first 10 rows
pd.DataFrame(features, columns=['Customer A', 'Customer B', 'Customer C']).head(10)# View the output of the first 10 rows
pd.DataFrame(output, columns=['Sales']).head(10)# View the actual, true coefficients used to generate the data
pd.DataFrame(coef, columns=['Coefficient Values'])
生成并导出您的 CSV 样本数据集
在探索、试验和生成我们的样本数据集之后,我们最终想要导出并使用它们。虽然文件格式没有限制,但由于我们没有大量的数据,并且希望将数据集插入到各种工具中,因此简单的 CSV 文件非常方便。但是首先必须创建一个数据帧。
import pandas as pd
import numpy as np
import random#using numpy's randint
df = pd.DataFrame(np.random.randint(0,100,size=(15, 4)), columns=list('ABCD'))# create a new column
df['AminusB'] = df['A'] - (0.1 * df['B'])
df['names'] = 'Laura'df.head(5)
虽然我们已经生成了一些随机的相关和不相关的数字,但是每行中的名称列是相同的。为了获得随机名称,我们必须对行进行迭代,并使用我们的随机名称生成器。
import namesfor index, row in df.iterrows():
df.at[index,'names'] = names.get_first_name(gender='female')
df.head(5)
请注意,您可以使用任何其他包或方法来创建示例 CSV 数据集,而不是名称。没有任何限制。现在,让我们将其导出为 CSV 格式,我们可以开始了。
#Export to csvexport_csv = df.to_csv (r'/Users/SampleDataset.csv', header=True) #Don't forget to add '.csv' at the end of the path
你成功了——祝贺你!现在,更令人兴奋的部分开始了,您可以开始处理您生成的 CSV 样本数据集。祝你好运。
您可能也喜欢阅读的文章:
用 PolyGen 和 PyTorch 生成三维模型
(论文中的图 6)使用 top-p=0.9 的 nucleus 采样和地面真实网格(蓝色)生成的图像条件样本(黄色)。
介绍
有一个新兴的深度学习研究领域,专注于将 DL 技术应用于 3D 几何和计算机图形应用,正如这个冗长的近期研究集合所证明的那样。如果你对这个主题感兴趣,看看吧。真是个兔子洞。对于希望自己尝试一些 3D 深度学习的 PyTorch 用户来说,高岭土库值得一看。对于 TensorFlow 用户,还有 TensorFlow 图形。一个特别热门的子领域是 3D 模型的生成。创造性地组合 3D 模型,从图像中快速生成 3D 模型,以及为其他机器学习应用和模拟创建合成数据只是 3D 模型生成的无数用例中的一小部分。
然而,在 3D 深度学习研究领域,为您的数据选择一种合适的表示方法是成功的一半。在计算机视觉中,数据的结构非常简单:图像由密集的像素组成,这些像素整齐而均匀地排列成一个精确的网格。3D 数据的世界没有这样的一致性。3D 模型可以表示为体素、点云、网格、多视图图像集等等。这些输入表示也有各自的缺点。例如,体素尽管计算成本高,但输出分辨率低。点云没有对表面或它们的法线进行编码,所以拓扑不能仅仅从点云推断出来。网格也不唯一地编码拓扑,因为任何网格都可以细分以产生相似的表面。这些缺点促使 DeepMind 的研究人员创建了 PolyGen ,这是一种用于网格的神经生成模型,它联合估计模型的面和顶点,以直接生成网格。官方实现可在 DeepMind GitHub 上获得。
研究
现在经典的 PointNet 论文提供了建模点云数据的蓝图,比如 3D 模型的顶点。它是一种通用算法,不会对 3D 模型的面或占用进行建模,因此单独使用 PointNet 不可能生成唯一的防水网格。 3D-R2N2 采用的体素方法将我们都熟悉的 2D 卷积扩展到 3D,并从 RGB 图像中自然产生无缝网格。然而,在更高的空间分辨率下,体素表示在计算上变得昂贵,有效地限制了它可以产生的网格的大小。
Pixel2Mesh 可以通过变形模板网格(通常是椭球体)从单幅图像中预测 3D 模型的顶点和面。目标模型必须与模板网格同胚,因此使用凸模板网格(如椭球体)会在高度非凸的对象(如椅子和灯)上引入许多虚假面。拓扑修改网络 (TMN)通过引入两个新阶段对 Pixel2Mesh 进行迭代:一个拓扑修改阶段,用于修剪增加模型重建误差的错误面;一个边界细化阶段,用于平滑由面修剪引入的锯齿状边界。如果你感兴趣,我强烈建议你也去看看地图集和分层表面预测。
同胚现象的经典例子(来源:维基百科)
虽然变形和细化模板网格的常见方法表现良好,但它始于关于模型拓扑的主要假设。在它的核心,一个 3D 模型只是一个 3D 空间中的顶点的集合,由单独的面组合和连接在一起。有没有可能避开中间的表示,直接预测这些顶点和面?
多种价元素
PolyGen 架构(本文主要关注蓝色虚线框内的部分)。
PolyGen 采用了一种相当独特的方法来完成模型生成任务,将 3D 模型表示为严格有序的顶点和面序列,而不是图像、体素或点云。这种严格的排序使他们能够应用基于注意力的序列建模方法来生成 3D 网格,就像伯特或 GPT 模型对文本所做的那样。
PolyGen 的总体目标有两个:首先为 3D 模型生成一组可信的顶点(可能受图像、体素或类别标签的限制),然后逐个生成一系列面,这些面将顶点连接在一起,并为该模型提供一个可信的表面。组合模型将网格上的分布 p(M) 表示为两个模型之间的联合分布:顶点模型 p(V) 表示顶点,面部模型 p(F|V) 表示以顶点为条件的面部。
顶点模型是一个解码器,它试图预测序列中的下一个标记,以前面的标记为条件(并且可选地以图像、体素字段或类标签为条件)。人脸模型包括一个编码器,后面跟着一个解码器指针网络,表示顶点序列上的分布。该指针网络有效地一次“选择”一个顶点,以添加到当前的面序列中,并构建模型的面。该模型同时以先前的面序列和整个顶点序列为条件。因为 PolyGen 架构非常复杂,并且依赖于各种各样的概念,这篇文章将仅限于顶点模型。如果这篇文章获得了关注,我将在后续文章中介绍人脸模型。
预处理顶点
流行的 ShapeNetCore 数据集中的每个模型都可以表示为顶点和面的集合。每个顶点由一个(x,y,z)坐标组成,该坐标描述了 3D 网格中的一个点。每个面都是一个索引列表,这些索引指向构成该面的角的顶点。对于三角形面,这个列表有 3 个索引长。对于 n 边形面,该列表是可变长度的。原始数据集相当大,所以为了节省时间,我提供了数据集的一个更轻量级的预处理子集,这里是供您试验。该子集仅由来自 5 个形状类别的模型组成,并且在被转换成 n 边形(如下所述)后具有少于 800 个顶点。
为了使序列建模方法起作用,数据必须以一种受约束的、确定性的方式来表示,以尽可能地消除可变性。出于这个原因,作者对数据集进行了一些简化。首先,他们将所有输入模型从三角形(连接 3 个顶点的面)转换为 n 边形(连接 n 个顶点的面),通过使用 Blender 的平面抽取修改器合并面。由于大型网格并不总是具有唯一的三角剖分,因此这使得相同拓扑的表示更加紧凑,并减少了三角剖分的不确定性。出于篇幅原因,我不会在这篇文章中详细讨论 Blender 脚本,但是许多资源,包括官方文档和GitHub上的优秀示例集,都很好地涵盖了这个主题。我提供的数据集已经过预抽取。
在平面模式的 Blender 中应用抽取修改器前后的 3D 模型,角度限制为 1.0 度。
要继续学习,请随意下载这个样本 cube.obj 文件。这个模型是一个有 8 个顶点和 6 个面的基本立方体。下面的简单代码片段从单个。对象文件。
def load_obj(filename):
"""Load vertices from .obj wavefront format file."""
vertices = []
with open(filename, 'r') as mesh:
for line in mesh:
data = line.split()
if len(data) > 0 and data[0] == 'v':
vertices.append(data[1:])
return np.array(vertices, dtype=np.float32)verts = load_obj(cube_path)
print('Cube Vertices')
print(verts)
其次,顶点从 z 轴(本例中为垂直轴)开始按升序排序,然后是 y 轴,最后是 x 轴。这样,模型顶点是自下而上表示的。在普通多边形模型中,顶点然后被连接成一维序列向量,对于较大的模型来说,这可能会以非常长的序列向量结束。作者在论文的附录 E 中描述了几个减轻这一负担的修改。
为了对顶点序列进行排序,我们可以使用字典排序。这与在字典中对单词进行排序时采用的方法相同。要对两个单词进行排序,你应该先看第一个字母,如果有并列的话,再看第二个,依此类推。对于单词“aardvark”和“apple”,第一个字母是“a”和“a”,所以我们移到第二个字母“a”和“p”来告诉我们“aardvark”在“apple”之前。在这种情况下,我们的“字母”依次是 z、y 和 x 坐标。
verts_keys = [verts[..., i] for i in range(verts.shape[-1])]
sort_idxs = np.lexsort(verts_keys)
verts_sorted = verts[sort_idxs]
最后,将顶点坐标归一化,然后量化,将其转换为离散的 8 位值。这种方法已经在像素递归神经网络和波网中用于模拟音频信号,使它们能够在顶点值上施加分类分布。在最初的 WaveNet 论文中,作者评论说“分类分布更灵活,可以更容易地模拟任意分布,因为它不对它们的形状做任何假设。”这种品质对于建模复杂的依赖关系(如 3D 模型中顶点之间的对称性)非常重要。
# normalize vertices to range [0.0, 1.0]
lims = [-1.0, 1.0]
norm_verts = (verts - lims[0]) / (lims[1] - lims[0])# quantize vertices to integers in range [0, 255]
n_vals = 2 ** 8
delta = 1\. / n_vals
quant_verts = np.maximum(np.minimum((norm_verts // delta), n_vals - 1), 0).astype(np.int32)
顶点模型
顶点模型由一个解码器网络组成,该网络具有转换器模型的所有标准特征:输入嵌入、18 个转换器解码器层的堆栈、层标准化,以及最后在所有可能的序列令牌上表达的 softmax 分布。给定长度为 N 的展平顶点序列 Vseq ,其目标是在给定模型参数的情况下最大化数据序列的对数似然性:
与 LSTM 不同,transformer 模型能够以并行方式处理顺序输入,同时仍然支持来自序列一部分的信息为另一部分提供上下文。这都要归功于他们的注意力模块。3D 模型的顶点包含各种各样的对称性和远距离点之间的复杂依赖关系。例如,想象一个典型的桌子,模型对角的桌腿是彼此的镜像版本。注意力模块允许对这些类型的模式进行建模。
输入嵌入
嵌入层是序列建模中使用的一种常见技术,用于将有限数量的表征转换为特征集。在一个语言模型中,“国家”和“民族”这两个词的含义可能非常相似,但与“苹果”这两个词的含义非常遥远。当单词用独特的符号表示时,就没有固有的相似或不同的概念。嵌入层将这些标记转换成矢量表示,在矢量表示中可以模拟有意义的距离感。
多边形将同样的原理应用于顶点。该模型利用了三种类型的嵌入图层:坐标图层用于指示输入令牌是 x、y 还是 z 坐标,值图层用于指示令牌的值,位置图层用于编码顶点的顺序。每一个都向模型传递一条关于令牌的信息。因为我们的顶点是一次在一个轴上输入的,所以坐标嵌入为模型提供了重要的坐标信息,让它知道给定值对应哪种类型的坐标。
coord_tokens = np.concatenate(([0], np.arange(len(quant_verts)) % 3 + 1, (n_padding + 1) * [0]))
值嵌入对我们之前创建的量化顶点值进行编码。我们还需要一些序列控制点:额外的开始和停止标记来分别标记序列的开始和结束,以及填充标记直到最大序列长度。
TOKENS = {
'<pad>': 0,
'<sos>': 1,
'<eos>': 2
}max_verts = 12 # set low for prototyping
max_seq_len = 3 * max_verts + 2 # num coords + start & stop tokens
n_tokens = len(TOKENS)
seq_len = len(quant_verts) + 2
n_padding = max_seq_len - seq_lenval_tokens = np.concatenate((
[TOKENS['<sos>']],
quant_verts + n_tokens,
[TOKENS['<eos>']],
n_padding * [TOKENS['<pad>']]
))
由于并行化而丢失的给定序列位置 n 的位置信息通过位置嵌入来恢复。也可以使用位置编码,一种不需要学习的封闭形式的表达式。在经典的 transformer 论文“注意力是你所需要的全部”中,作者定义了一种由不同频率的正弦和余弦函数组成的位置编码。他们通过实验确定位置嵌入的表现和位置编码一样好,但是位置编码的优势是可以外推得到比训练中遇到的序列更长的序列。关于位置编码的精彩视觉解释,请看这篇博文。
pos_tokens = np.arange(len(quant_tokens), dtype=np.int32)
生成所有这些令牌序列后,最后要做的是创建一些嵌入层并组合它们。每个嵌入层都需要知道预期的输入字典的大小和要输出的嵌入维度。每一层的嵌入维数是 256,这意味着我们可以用加法将它们组合起来。字典的大小取决于一个输入可以拥有的唯一值的数量。对于值嵌入,它是量化值的数量加上控制标记的数量。对于坐标嵌入,对于每个坐标 x、y 和 z 是一个,对于以上任何一个都是一个(控制标记)。最后,对于每个可能的位置或最大序列长度,位置嵌入需要一个。
n_embedding_channels = 256# initialize value embedding layer
n_embeddings_value = 2 ** n_bits + n_tokens
value_embedding = torch.nn.Embedding(n_embeddings_value,
n_embedding_channels, padding_idx=TOKENS['<pad>'])# initialize coordinate embedding layer
n_embeddings_coord = 4
coord_embedding = torch.nn.Embedding(n_embeddings_coord,
n_embedding_channels)# initialize position embedding layer
n_embeddings_pos = max_seq_len
pos_embedding = torch.nn.Embedding(n_embeddings_pos,
n_embedding_channels)# pass through layers
value_embed = self.value_embedding(val_tokens)
coord_embed = self.coord_embedding(coord_tokens)
pos_embed = self.pos_embedding(pos_tokens)# merge
x = value_embed + coord_embed + pos_embed
序列屏蔽
transformer 模型如此并行化的另一个结果是什么?对于在时间 n 的给定输入令牌,模型实际上可以“看到”序列中稍后的目标值,当您试图仅根据先前的序列值来调整模型时,这就成了一个问题。为了防止模型关注无效的未来目标值,可以在自我关注层中的 softmax 步骤之前用-Inf
屏蔽未来位置。
n_seq = len(val_tokens)
mask_dims = (n_seq, n_seq)
target_mask = torch.from_numpy(
(val_tokens != TOKENS['<pad>'])[..., np.newaxis] \
& (np.triu(np.ones(mask_dims), k=1).astype('uint8') == 0))
PolyGen 还大量使用无效预测遮罩来确保它生成的顶点和面序列编码有效的 3D 模型。例如,必须执行诸如“z 坐标不递减”和“停止标记只能出现在完整的顶点(z、y 和 x 标记的三元组)”之类的规则,以防止模型产生无效的网格。作者在论文的附录 F 中提供了他们使用的掩蔽的详细列表。这些约束仅在预测时强制执行,因为它们实际上会损害训练性能。
细胞核取样
与许多序列预测模型一样,该模型是自回归的,这意味着给定时间步长的输出是下一个时间步长的可能值的分布。一次预测一个标记的整个序列,模型在每一步浏览来自先前时间步骤的所有标记以选择其下一个标记。解码策略决定了它如何从这个分布中选择下一个令牌。
如果使用次优解码策略,生成模型有时会陷入重复循环,或者会产生质量差的序列。我们都见过生成的文本看起来像废话。PolyGen 采用一种叫做核取样的解码策略来产生高质量的序列。原始论文在文本生成上下文中应用了这种方法,但是它也可以应用于顶点。前提很简单:仅从 softmax 分布中共享 top-p 概率质量的令牌中随机抽取下一个令牌。这在推理时应用,以产生网格,同时避免序列退化。关于核采样的 PyTorch 实现,请参考本要点。
条件输入
除了无条件生成模型,PolyGen 还支持使用类别标签、图像和体素的输入条件。这些可以指导生成具有特定类型、外观或形状的网格。类别标签通过嵌入进行投影,然后添加到每个关注块的自我关注层之后。对于图像和体素,编码器创建一组嵌入,然后用于与变换器解码器的交叉注意。
结论
PolyGen 模型描述了一个强大、高效和灵活的框架,用于有条件地生成 3D 网格。序列生成可以在各种条件和输入类型下完成,从图像到体素到简单的类标签,甚至除了起始标记什么都不是。顶点模型表示网格顶点上的分布,它只是联合分布难题的一部分。我打算在以后的文章中介绍人脸模型。与此同时,我鼓励您查看来自 DeepMind 的 TensorFlow 实现,并尝试一下条件模型生成!
使用 StyleGAN2 生成动画角色
了解如何生成这个很酷的动画人脸插值
生成的 StyleGAN 插值[图片由作者提供]
生成对抗网络
生成对抗网络是一种能够生成新内容的生成模型。由于其有趣的应用,如生成合成训练数据、创建艺术、风格转换、图像到图像的翻译等,该主题在机器学习社区中变得非常流行。
甘建筑[图片由作者提供]
GAN 由两个网络组成,发生器、和鉴别器。生成器将尝试生成假样本,并欺骗鉴别器,使其相信这是真样本。鉴别器将尝试从真样本和假样本中检测生成的样本。这个有趣的对抗性概念是由 Ian Goodfellow 在 2014 年提出的。已经有很多资源可用于学习 GAN,因此我将不解释 GAN 以避免冗余。
我推荐阅读约瑟夫·罗卡的这篇优美的文章来了解甘。
一步一步地建立导致 GANs 的推理。
towardsdatascience.com](/understanding-generative-adversarial-networks-gans-cd6e4651a29)
StyleGAN2
StyleGAN 论文《一种基于风格的 GANs 架构》,由 NVIDIA 于 2018 年发布。该论文为 GAN 提出了一种新的生成器架构,允许他们控制所生成样本的不同层次的细节,从粗略细节(如头部形状)到精细细节(如眼睛颜色)。
StyleGAN 还结合了来自 Progressive GAN 的想法,其中网络最初在较低的分辨率(4x4)上训练,然后在稳定后逐渐增加更大的层。这样做,训练时间变得快了很多,训练也稳定了很多。
渐进成长的甘【来源:莎拉·沃尔夫】
StyleGAN 通过添加一个映射网络对其进行了进一步改进,该映射网络将输入向量编码到一个中间潜在空间 w 中,然后这些潜在空间将有单独的值用于控制不同级别的细节。
StyleGAN 生成器架构[图片由作者提供]
为什么要添加映射网络?
甘的问题之一是它的纠缠潜在表象(输入向量, z )。例如,假设我们有二维潜在代码,它代表脸的大小和眼睛的大小。在这种情况下,脸的大小与眼睛的大小密切相关(眼睛越大,脸也越大)。另一方面,我们可以通过存储面部和眼睛的比例来简化这一点,这将使我们的模型更简单,因为非纠缠的表示更容易被模型解释。
对于纠缠表示,数据分布可能不一定遵循我们想要对输入向量 z 进行采样的正态分布。例如,数据分布会有一个像这样的缺角,它表示眼睛和脸的比例变得不真实的区域。
如果我们从正态分布中对 z 进行采样,我们的模型也会尝试生成缺失区域,其中的比率是不现实的,并且因为没有具有这种特征的训练数据,所以生成器生成的图像会很差。因此,映射网络旨在解开潜在表示并扭曲潜在空间,以便能够从正态分布中采样。
此外,在每个级别上具有单独的输入向量 w ,允许生成器控制不同级别的视觉特征。前几层(4x4,8x8)将控制更高层次(更粗糙)的细节,如头型、姿势和发型。最后几层(512x512,1024x1024)将控制细节的精细程度,如头发和眼睛的颜色。
粗糙层次细节的变化(头型、发型、姿势、眼镜)【来源:论文】
精细层次细节的变化(头发颜色)【来源:纸张】
关于 StyleGAN 架构的全部细节,我推荐你阅读 NVIDIA 关于其实现的官方论文。这是论文中完整架构的插图。
随机变化
StyleGAN 还允许您通过在相应的层给出噪声来控制不同细节级别的随机变化。随机变化是图像上的微小随机性,不会改变我们的感知或图像的身份,如不同的梳理头发,不同的头发放置等。你可以在下面的动画图像中看到变化的效果。
粗略随机变化【来源:论文】
精细随机变异【来源:论文】
StyleGAN 还做了其他几个改进,我不会在这些文章中讨论,比如 AdaIN 规范化和其他规范化。你可以阅读官方文件、乔纳森·惠的这篇文章,或者拉尼·霍雷夫的这篇文章来了解更多细节。
截断绝招
当训练样本中存在未充分表示的数据时,生成器可能无法学习样本,并且生成的样本很差。为了避免这种情况,StyleGAN 使用了一种“截断技巧”,即截断中间潜在向量 w ,迫使其接近平均值。
𝚿 (psi)是用于截断和重新采样高于阈值的潜在向量的阈值。因此,随着更高的𝚿,你可以在生成的图像上获得更高的多样性,但它也有更高的机会生成怪异或破碎的脸。根据 Gwern ,对于该网络,0.5 至 0.7 的𝚿值似乎给出了具有足够多样性的良好图像。不过,请随意试验阈值。
在 0.3 磅/平方英寸(左)和 0.7 磅/平方英寸(中)和 1.3 磅/平方英寸(右)下生成的 3×3 网格图像
生成动漫人物
我将使用 Aaron Gokaslan 预先训练的动画 StyleGAN2,这样我们可以直接加载模型并生成动画脸。所以,打开你的 Jupyter 笔记本或者 Google Colab,让我们开始编码吧。
注: 如果卡住了可以参考我的 Colab 笔记本
所以首先要克隆 styleGAN 回购。
*$ git clone https://github.com/NVlabs/stylegan2.git*
**如果你使用的是 Google Colab,你可以在命令前面加上“!”作为命令运行它:!https://github.com/NVlabs/stylegan2.gitgit 克隆
接下来,我们需要下载预先训练的权重并加载模型。当您使用 Google Colab 时,请确保您正在运行 GPU 运行时,因为模型被配置为使用 GPU。
该代码修改自本笔记本
*现在,我们需要生成随机向量, *z,作为生成器的输入。让我们创建一个函数来从给定的种子生成潜在代码 z。
然后,我们可以创建一个函数来获取生成的随机向量 z 并生成图像。
现在,我们可以尝试生成一些图像,看看结果。
该函数将返回一个数组PIL.Image
。在 Google Colab 中,你可以通过打印变量直接显示图像。这是第一个生成的图像。
作者图片
让我们用图像网格来显示它,这样我们就可以一次看到多个图像。
然后我们可以在一个 3x3 的网格中显示生成的图像。
作者图片
GAN 的一个优点是它有一个平滑连续的潜在空间,不像 VAE(自动变分编码器)那样有间隙。因此,当您在潜在空间中取两个点(这将生成两个不同的面)时,您可以通过在这两个点之间取一条线性路径来创建两个面的过渡或插值。
潜在空间的插值【来源:约瑟夫·罗卡】
让我们用代码实现它,并创建一个函数在 z 向量的两个值之间进行插值。
我们来看看插值结果。你可以看到第一个图像逐渐过渡到第二个图像。
作者图片
现在我们已经完成了插值。我们终于可以尝试制作上面缩略图中的插值动画了。我们将使用moviepy
库来创建视频或 GIF 文件。
当你运行代码时,它会生成一个插值的 GIF 动画。您还可以使用顶部的变量修改持续时间、网格大小或 fps。
生成的 StyleGAN2 插值 GIF[图片由作者提供]
如果你能走到这一步,恭喜你!您已经使用 StyleGAN2 生成了动画人脸,并学习了 GAN 和 StyleGAN 架构的基础知识。
接下来是什么?
现在我们已经完成了,你还能做些什么来进一步改进呢?这里有一些你可以做的事情。
其他数据集 显然,StyleGAN 不仅限于动漫数据集,还有许多可用的预训练数据集可供您使用,如真实人脸、猫、艺术和绘画的图像。查看此 GitHub repo 了解可用的预训练重量。另一方面,您也可以使用自己选择的数据集来训练 StyleGAN。
有条件的 GAN 目前,我们无法真正控制我们想要生成的特征,如头发颜色、眼睛颜色、发型和配饰。条件 GAN 允许您在输入向量 z 旁边给出一个标签,从而将生成的图像调整为我们想要的样子。或者,你可以试着通过回归或手动来理解潜在空间。如果你想往这个方向发展, Snow Halcy repo 也许能帮到你,就像他在这个 J upyter 笔记本里做的那样,甚至让它互动起来。
确认
我要感谢Gwern Branwen他的广泛的文章和我在文章中强烈提到的关于用 StyleGAN 生成动画脸的解释。我强烈建议你访问他的网站,因为他的作品是知识的宝库。此外,请访问这个网站,该网站托管了生成动画人脸的 StyleGAN 模型和生成动画情节的 GPT 模型。
如果你喜欢我的文章,请随意查看我的其他文章!
先睹为快数字艺术的未来
towardsdatascience.com](/animating-yourself-as-a-disney-character-with-ai-78af337d4081) [## 在没有数据集的情况下生成新内容
重写 GAN 中的规则:上下文相关的复制和粘贴特性
towardsdatascience.com](/generating-novel-content-without-dataset-544107da4cc8)
参考
[1]t .卡拉斯、s .莱恩和 t .艾拉(2019 年)。一种基于风格的生成对抗网络生成器体系结构。在IEEE 计算机视觉和模式识别会议论文集(第 4401–4410 页)中。
[2]https://www . gwern . net/Faces # style gan-2
为 PyTorch 生成批处理数据
由肯尼斯·贝里奥斯·阿尔瓦雷斯在 Unsplash 上拍摄
实践中的深度学习
为 PyTorch 创建定制数据加载器——变得简单!
我正在创建一个定制的 PyTorch 培训模块,这个模块过于复杂,尤其是在生成培训批次并确保这些批次在培训期间不会重复的时候。“这是一个已解决的问题”,我在实验室的深处疯狂地编码时心想。
从数据集中选择项目时,不希望只增加索引是有原因的。1)这无法扩展到多个员工。2)你需要随机化你的序列以最大化训练效果。
这就是 Torch 的数据工具(torch.utils.data
)派上用场的地方。您不应该从头开始创建批处理生成器。
您可以采取两种方法。1)在创建数据集之前移动所有预处理,并仅使用数据集生成项目,或者 2)在数据集的初始化步骤中执行所有预处理(缩放、移位、整形等)。如果你只使用火炬,方法#2 是有意义的。我使用多个后端,所以我使用方法 1。
步伐
- 创建自定义数据集类。你重写了
__len__()
和__getitem__()
方法。 - 创建一个使用
torch.utils.data.dataloader
的迭代器 - 在训练循环中使用这个迭代器。
简单。瞧啊。
关于如何使用的示例,请参见附加的代码。
摘要
在本文中,我们回顾了向 PyTorch 训练循环提供数据的最佳方法。这打开了许多感兴趣的数据访问模式,有助于更轻松、更快速的培训,例如:
- 使用多个进程读取数据
- 更加标准化的数据预处理
- 在多台机器上使用数据加载器进行分布式培训
感谢阅读!
如果你喜欢这个,你可能会喜欢:
演示如何使用 LSTM 自动编码器分析多维时间序列
towardsdatascience.com](/using-lstm-autoencoders-on-multidimensional-time-series-data-f5a7a51b29a1)
使用 T5 文本到文本转换器模型从任何内容生成布尔型(是/否)问题
使用 BoolQ 数据集和 T5 文本到文本转换器模型的问题生成算法的训练脚本和预训练模型。
图片来自 Pixabay
投入
我们程序的输入将是任何一般的内容/段落
**Months earlier, Coca-Cola had begun “Project Kansas.” It sounds like a nuclear experiment but it was just a testing project for the new flavor. In individual surveys, they’d found that more than 75% of respondents loved the taste, 15% were indifferent, and 10% had a strong aversion to the taste to the point that they were angry.**
输出
输出将是从上述输入生成的**布尔(是/否)**问题。
**布尔(是/否)**从 T5 模型生成的问题:
**1: Does coca cola have a kansas flavor?
2: Is project kansas a new coca cola flavor?
3: Is project kansas the same as coca cola?**
今天我们将看看如何从 Huggingface 的变形金刚库中训练一个 T5 模型来生成这些布尔问题。我们还将看到如何使用提供的预训练模型来生成这些**布尔(是/否)**问题。
实际使用案例(学习聊天机器人)
来自平面图标的图标
我想象一个场景,作为一名学生,你和一个聊天机器人互动学习一个概念**。聊天机器人会根据你的回答向你展示教科书章节中相关的字节大小的片段。它还想让实时评估你是否已经理解了提出的主题。为课本章节的每个片段手动预生成评估是不切实际的。聊天机器人可以利用这个算法实时生成布尔(是/否)问题,评估你对题目的理解。**
让我们开始吧—
资料组
来自平面图标的图标
我用BoolQ数据集收集了段落、问题和答案三元组,准备了训练集和验证集。****
boolQ 数据集具有以下格式-
**{
**"question"**: "is france the same timezone as the uk",
**"passage"**: "At the Liberation of France in the summer of 1944, Metropolitan France kept GMT+2 as it was the time then used by the Allies (British Double Summer Time). In the winter of 1944--1945, Metropolitan France switched to GMT+1, same as in the United Kingdom, and switched again to GMT+2 in April 1945 like its British ally. In September 1945, Metropolitan France returned to GMT+1 (pre-war summer time), which the British had already done in July 1945\. Metropolitan France was officially scheduled to return to GMT+0 on November 18, 1945 (the British returned to GMT+0 in on October 7, 1945), but the French government canceled the decision on November 5, 1945, and GMT+1 has since then remained the official time of Metropolitan France."
**"answer"**: false,
**"title"**: "Time in France",
}**
有一段“短文,有对应的“问题和正确的布尔“答案”——对或错。
我们会详细讨论你如何-
- 使用我的预训练的模型为任何给定的内容生成布尔问题。
- 使用我的训练代码和数据集在你自己的 GPU 机器上复制结果。
训练算法— T5
用扁平图标生成的图标
T5 是 Google 的一个新的 transformer 模型,它以端到端的方式进行训练,将文本作为输入,将修改后的文本作为输出。你可以在这里了解更多。
它使用在大型文本语料库上训练的文本到文本转换器,在多个 NLP 任务上实现了最先进的结果,如摘要、问题回答、机器翻译等。
我将“通道”和“答案”作为输入给我的 T5 变压器模型,并训练它生成“问题”作为输出。****
密码
使用预训练模型和训练具有给定数据的模型的所有代码可在-
**** [## ramsrigouthamg/generate _ boolean _ questions _ using _ T5 _ transformer
使用这个程序,您可以从任何内容中生成布尔型(是/否)问题。一篇详细的媒体博文解释了…
github.com](https://github.com/ramsrigouthamg/generate_boolean_questions_using_T5_transformer)
使用预先训练的模型
Python 文件 t5_inference.py 包含了下面给出的所有代码。
首先,安装必要的库-
!pip install torch==1.4.0
!pip install transformers==2.9.0
!pip install pytorch_lightning==0.7.5
以任何文本/段落作为输入运行推理,查看生成的布尔问题
上述代码的输出为-
**Context: ** Months earlier, Coca-Cola had begun “Project Kansas.” It sounds like a nuclear experiment but it was just a testing project for the new flavor. In individual surveys, they’d found that more than 75% of respondents loved the taste, 15% were indifferent, and 10% had a strong aversion to the taste to the point that they were angry.**Beam decoding [Most accurate questions] ::**Does coca cola have a kansas flavor?
Is project kansas the same as coca cola?
Is project kansas a new coca cola flavor?**TopKP decoding [Not very accurate but more variety in questions] ::**Does coca cola have a koala flavor?
Is kakao the same as project kansas?
Was project ksoda a real thing?**Time elapsed 1.2351574897766113**
训练你自己的模型
所有用于训练的训练代码和数据集都可以在提到的 Github repo 中获得。我们将经历我用来训练模型的步骤。
1.数据准备
文件boolQ _ prepare _ train _ validation _ dataset . ipynb包含准备训练和验证数据集的所有代码。我将 boolQ 数据集作为 JSON 文件,并将其转换为 csv 文件。
2.培养
感谢 Suraj Patil 给了我们一个神奇的 Colab 笔记本来训练 T5 完成任何文本到文本的任务。我从 Colab 笔记本上借用了大部分训练代码,只更改了数据集类和训练参数。我使 dataset 类适应了我们的 boolQ 数据集。
培训代码可作为培训使用。Github 回购中的 py。
你需要做的就是在任一台 GPU 机器上克隆repo,安装 requirements.txt ,运行 train.py 来训练 T5 模型。
在 p2.xlarge (AWS ec2)上训练该模型 4 个时期(默认)需要大约 5-6 个小时。
数据集类如下所示—
关键是我们如何向 T5 模型培训师提供我们的输入和输出。我将“通道”和“答案”作为输入给我的 T5 变压器模型,并训练它生成**“问题”作为输出**,如下所示-
输入格式到 T5 进行训练
**truefalse**: yes **passage**: At the Liberation of France in the summer of 1944, Metropolitan France kept GMT+2 as it was the time then used by the Allies (British Double Summer Time). In the winter of 1944--1945, Metropolitan France switched to GMT+1, same as in the United Kingdom, and switched again to GMT+2 in April 1945 like its British ally. In September 1945, Metropolitan France returned to GMT+1 (pre-war summer time), which the British had already done in July 1945\. Metropolitan France was officially scheduled to return to GMT+0 on November 18, 1945 (the British returned to GMT+0 in on October 7, 1945), but the French government canceled the decision on November 5, 1945, and GMT+1 has since then remained the official time of Metropolitan France. **</s>**
输出格式到 T5 进行训练
Is france the same timezone as the uk? **</s>**
注:文本" truefalse : yes “或” truefalse : no "应生成一个适当的布尔问题,其答案为文本中给出的" yes “或” no "。但是我尝试训练 T5 时,效果并不好。所以确保你不要依赖对生成的布尔问题给出的答案的初始标记“是”或“否”。
总数据集只有大约 ~10k 个样本,因此生成的问题质量有时不是很好。请注意。****
祝 NLP 探索愉快,如果你喜欢它的内容,请随时在 Twitter 上找到我。
如果你想学习使用变形金刚的现代自然语言处理,看看我的课程使用自然语言处理的问题生成
用神经网络产生啁啾
将模型嫁接在一起并迭代调用生成器
鸟鸣的声音变化多样,优美,令人放松。在前 Covid 时代,我制作了一个聚焦定时器,它会在休息时播放一些录制的鸟鸣声,我总是想知道是否能产生这样的声音。经过反复试验,我找到了一个概念验证的架构,它既可以成功地再现单个啁啾声,又可以调整参数来改变生成的声音。
由于生成鸟鸣似乎是一个有点新奇的应用,我认为这种方法值得分享。一路走来,我还学会了如何将 TensorFlow 模型拆开,并将它们的一部分嫁接在一起。下面的代码块展示了这是如何实现的。完整的代码可以在这里找到。
理论上的方法
发电机将由两部分组成。第一部分将把整个声音和关于其整体形状的关键信息编码成少量的参数。
第二部分将获取一小段声音,以及关于整体形状的信息,并预测下一小段声音。
第二部分可以使用调整后的参数对其自身进行迭代调用,以产生全新的啁啾!
编码参数
一个自动编码器结构用于导出声音的关键参数。在从一系列扩展(解码)层完全再现声音之前,这种结构采用整个声波,并通过一系列(编码)层将其减少到少量组件(腰部)。一旦经过训练,自动编码器模型在腰部被切断,因此它所做的只是将完整的声音降低到关键参数。
为了验证概念,使用了单个啁啾;这唧唧声:
使用啁啾的声波表示。
它来自康奈尔大学的鸟鸣指南:北美必备套装。用于鸟鸣铬实验的同一套。
仅使用单一声音的一个问题是,自动编码器可能会简单地在解码层的偏差中隐藏关于声音的所有信息,从而使腰部的权重全部为零。为了减轻这一点,在训练过程中通过改变声音的振幅和稍微移动来改变声音。
自动编码器的编码器部分由一系列卷积层组成,这些卷积层将 3000 多长的声波压缩成大约 20 个数字,有望保留沿途的重要信息。由于声音由许多不同的正弦波组成,允许许多不同大小的卷积滤波器通过声音在理论上可以捕获关于复合波的关键信息。选择 20 的腰围尺寸主要是因为这似乎是一个可调节参数的可克服的数目。
在第一种方法中,各层被顺序堆叠。在未来的版本中,使用类似于 inception-net 块的结构来并行运行不同大小的卷积可能是有利的。
模型的解码器部分由两个密集层组成,一个长度为 400,另一个长度为 3000,与输入声音的长度相同。最后一层的激活函数是 tanh,因为声波表示的值在-1 和 1 之间。
这看起来是这样的:
自动编码器网络的表示。用 PlotNeuralNet 制作。
这是代码:
培训发电机
发生器的结构从自动编码器网络的编码部分开始。腰部的输出与一些新的输入相结合,这些新的输入表示即将被预测的声波之前的声波比特。在这种情况下,声波的前 200 个值被用作输入,然后预测下 10 个值。
组合的输入被输入到一系列密集层中。连续的密集层允许网络学习先前的值、关于声音的整体形状的信息和随后的值之间的关系。最终的致密层长度为 10,并且用双曲正切函数激活。
这是这个网络的样子:
带有自动编码器网络嫁接部分的发电机网络。用 PlotNeuralNet 制作。
来自自动编码器网络的层被冻结,使得额外的训练资源不花费在它们上面。
发出一些声音
训练这个网络只需要几分钟,因为数据变化不大,因此相对容易学习,特别是对于自动编码器网络。最后一个亮点是从训练好的模型中产生两个新的网络。
第一个只是自动编码器的编码器部分,但现在是分开的。我们需要这部分来产生一些初始的好参数。
第二个模型与发生器网络相同,但自动编码器网络的部分被新的输入源所取代。这样做是为了使经过训练的发生器不再需要整个声波作为输入,而只需要捕获声音关键信息的编码参数。将这些分离出来作为新的输入,我们可以在产生啁啾时自由地操纵它们。
以下声音是在没有修改参数的情况下生成的,它们非常接近原始声音,但不是完美的再现。发电机网络只能达到 60%到 70%之间的精度,因此预计会有一些变化。
不修改编码参数而生成的声音。
修改参数
产生鸟鸣的好处部分在于可以产生主题的新变化。这可以通过修改编码器网络产生的参数来实现。在上面的例子中,编码器产生了这些参数:
不是所有的 20 个节点都产生非零参数,但是有足够多的节点可以实验。有 12 个可调参数,可以在两个方向上调整到任意程度,这是一个非常复杂的问题。由于这是一个概念验证,因此在每种情况下,只需调整一个参数,就可以产生一些选择声音:
每次修改其中一个编码参数后产生的声音。
以下是三个例子的声波表示:
产生啁啾的声波表示。
后续步骤
使用神经网络来产生鸟的声音似乎是可能的,尽管其可行性还有待观察。上述方法仅使用单一声音,因此下一步将尝试在多种不同声音上训练模型。从一开始就不清楚这是否可行。然而,如果构建的模型在多种声音上失败,仍然可以在不同的声音上训练不同的模型,并简单地将它们堆叠起来以产生不同的声音。
一个更大的问题是,并非所有产生的声音都是可行的,尤其是在修改参数时。相当一部分产生的声音更像电脑发出的哔哔声,而不是鸟鸣声。有些听起来像一台愤怒的计算机,它真的不想让你做你刚刚试图做的事情。减轻这种情况的一种方法是训练一个单独的模型来检测鸟的声音(也许沿着这些线),并使用它来拒绝或接受产生的输出。
计算成本也是当前方法的一个限制因素;生成啁啾声比播放声音需要多一个数量级的时间,如果是为了动态生成美丽的声景,这并不理想。这里想到的主要缓解措施是增加每次预测的长度,可能会以准确性为代价。当然,人们也可以简单地花时间预先生成可接受的声音场景。
结论
自动编码器网络和短期预测网络的组合可以被嫁接在一起,以产生具有一些可调节参数的鸟声发生器,这些参数可以被操纵以产生新的和有趣的鸟声。
和许多项目一样,部分动机是在过程中学习。特别是,我不知道如何把训练好的模型拆开,然后把它们的一部分移植到一起。上面使用的模型可以作为一个例子来指导其他想要尝试这种方法的学习者。
使用张量流和 LSTM 递归神经网络生成烹饪食谱:一步一步指南
照片由 home_full_of_recipes 拍摄(Instagram 频道)
TL;速度三角形定位法(dead reckoning)
我用 TensorFlow 在 ~100k 食谱数据集上训练了一个人物级别的 LSTM (长短期记忆)**,它建议我做“奶油苏打加洋葱”、、【松饼草莓汤】、、、、、【三文鱼牛肉慕斯和墨西哥胡椒斯蒂尔顿沙拉】**
在这里,你可能会找到更多我最后得到的例子:
- 🎨 烹饪食谱生成器演示——在你的浏览器中交互式地尝试该模型。
- 🏋🏻 LSTM 模型训练流程——看看模型是怎么训练出来的。
- 🤖交互式机器学习实验 知识库——查看更多“物体检测”、“草图识别”、“图像分类”等实验。
本文包含了 LSTM 模型如何在 Python 上使用 TensorFlow 2 和 Keras API 进行实际训练的细节。
来自机器学习实验的屏幕记录
我们的模型最终会学到什么
经过几个小时的训练,我们的角色级 RNN 模型将学习英语语法和标点符号的基本概念(我希望我能这么快学会英语!).它还将学习如何生成配方的不同部分,如📗【配方名称】,🥕【配方成分】和📝【食谱说明】。有时候食谱名称、配料和说明会很有趣,有时候很愚蠢,有时候很有趣。
下面是几个生成的配方示例:
*📗 [NAME]Orange Club Tea Sandwich Cookies🥕 [INGREDIENTS]• 1 cup (2 sticks) unsalted butter, softened
• 1 cup confectioners' sugar
• 1/2 cup flaxseed meal
• 1/2 cup shelled pumpkin seeds (pecans, blanched and sliced)
• 2 teaspoons vanilla extract📝 [INSTRUCTIONS]▪︎ Preheat oven to 350 degrees F.
▪︎ Combine cake mix, milk, egg and sugar in a large bowl. Stir until combined and smooth but not sticky. Using a spatula, sprinkle the dough biscuits over the bottom of the pan. Sprinkle with sugar, and spread evenly. Bake for 20 minutes. Remove from the oven and cool on a rack. To serve, add the chocolate.*
或者另一个:
*📗 [NAME]Mushrooms with Lentil Stewed Shallots and Tomatoes🥕 [INGREDIENTS]• 1 tablespoon olive oil
• 3 cloves garlic, smashed
• Kosher salt
• 1 1/2 pounds lean ground turkey
• 1 cup coarsely peeled tart apples
• 2 tablespoons chopped garlic
• 1 teaspoon ground cumin
• 1/2 teaspoon cayenne pepper
• 1 teaspoon chopped fresh thyme
• 3/4 cup chopped fresh basil
• 1/2 small carrot, halved lengthwise and cut into 1/2-inch pieces
• 1 roasted red pepper, halved and sliced vertically diced and separated into rough chops
• 3 tablespoons unsalted butter
• 2 cups shredded mozzarella
• 1/4 cup grated parmesan cheese
• 1/4 cup prepared basil pesto📝 [INSTRUCTIONS]▪︎ Stir the olive oil, garlic, thyme and 1 teaspoon salt in a saucepan; bring to a simmer over medium heat. Remove from the heat. Add the basil and toast the soup for 2 minutes.
▪︎ Meanwhile, heat 4 to 4 inches vegetable oil in the skillet over medium-high heat. Add the olive oil, garlic, 1/2 teaspoon salt and 1/2 teaspoon pepper and cook, stirring often, until cooked through, a*
⚠️ 本文中的食谱只是为了娱乐和学习目的而制作的。食谱是 而不是 用于实际烹饪!
先验知识
假设你已经熟悉递归神经网络的概念,特别是长短期记忆(LSTM) 架构。
ℹ️,如果这些概念对你来说是新的,我强烈推荐你参加 Coursera 的深度学习专业课程。浏览一下 Andrej Karpathy 的文章中的循环神经网络的不合理的有效性可能也是有益的。**
在高层次上,递归神经网络(RNN) 是一类深度神经网络,最常用于基于序列的数据,如语音、声音、文本或音乐。它们用于机器翻译、语音识别、语音合成等。rnn 的关键特征是它们是有状态的,并且它们具有内部存储器,其中可以存储序列的一些上下文。例如,如果序列的第一个单词是He
,RNN 可能向speaks
建议下一个单词,而不仅仅是speak
(以形成He speaks
短语),因为关于第一个单词He
的先验知识已经在内存中。
图片来源:递归神经网络维基百科上的文章
图片来源:LSTM 和 GRU关于数据科学的图文并茂的文章
令人兴奋的是,RNN(尤其是 LSTM)不仅能记住单词对单词的依存关系,还能记住字符对字符的依存关系!序列由什么组成并不重要:可能是单词,也可能是字符。重要的是它们形成了一个时间分布的序列。例如,我们有一个字符序列['H', 'e']
。如果我们问 LSTM 下一步可能会做什么,它可能会建议一个<stop_word>
(意思是,组成单词He
的序列已经完成,我们可以停止),或者它也可能会建议一个字符l
(意思是,它试图为我们建立一个Hello
序列)。这种类型的 rnn 被称为字符级 rnn(与单词级 rnn相对)。
在本教程中,我们将依靠 RNN 网络的记忆功能,我们将使用一个角色级别的 LSTM 版本来生成烹饪食谱。
探索数据集
让我们浏览几个可用的数据集,并探讨它们的优缺点。我希望数据集满足的一个要求是,它不仅应该有一个配料列表,还应该有烹饪说明。我还希望它有一个措施和每种成分的数量。
以下是我发现的几个烹饪食谱数据集:
- 🤷食谱配料数据集 (没有配料比例)
- 🤷 Recipe1M+ (食谱很多但需要注册才能下载)
- 🤷美食家——带评级和营养的食谱 (仅约 20k 份食谱,最好能找到更多)
- 👍🏻食谱框(~ 12.5 万份食谱搭配食材比例,不错)**
让我们尝试使用“配方箱”数据集。菜谱的数量看起来足够多,而且它既包含配料又包含烹饪说明。看看 RNN 是否能够了解配料和说明之间的联系是很有趣的。
为训练设置 TensorFlow/Python 沙盒
在本教程中,您可以使用几个选项来试验代码:
- *你可以在你的浏览器 *(不需要本地设置)中使用 GoogleColab 进行实验。
- 你可以在你的浏览器中使用 Jupyter 笔记本 (不需要本地设置)。
- 你可以在本地建立一个 Jupyter 笔记本。
我会建议使用 GoogleColab 选项,因为它不需要对您进行任何本地设置(您可以在您的浏览器中进行实验),并且它还提供了强大的 GPU 支持,可以使模型训练更快。您也可以尝试训练参数。
导入依赖关系
让我们从导入一些我们以后会用到的包开始。
**# Packages for training the model and working with the dataset.*
**import** tensorflow **as** tf
**import** matplotlib.pyplot **as** plt
**import** numpy **as** np
**import** json*# Utility/helper packages.*
**import** platform
**import** time
**import** pathlib
**import** os*
首先,让我们确保我们的环境设置正确,并且我们使用的是 Tensorflow 的 2nd 版本。
*print('Python version:', platform.python_version())
print('Tensorflow version:', tf.__version__)
print('Keras version:', tf.keras.__version__)*
➔输出:
*Python version: 3.7.6
Tensorflow version: 2.1.0
Keras version: 2.2.4-tf*
加载数据集
让我们使用TF . keras . utils . get _ file加载数据集。使用get_file()
实用程序很方便,因为它为您处理现成的缓存。这意味着您将只下载一次数据集文件,即使您再次在笔记本中启动相同的代码块,它也会使用缓存,并且代码块的执行速度会更快。
如果缓存文件夹不存在,则创建它:
*CACHE_DIR = './tmp'
pathlib.Path(CACHE_DIR).mkdir(exist_ok=**True**)*
下载并解包数据集:
*dataset_file_name = 'recipes_raw.zip'
dataset_file_origin = 'https://storage.googleapis.com/recipe-box/recipes_raw.zip'dataset_file_path = tf.keras.utils.get_file(
fname=dataset_file_name,
origin=dataset_file_origin,
cache_dir=CACHE_DIR,
extract=**True**,
archive_format='zip'
)print(dataset_file_path)*
以下是下载后数据集文件的路径:
➔输出:
*./tmp/datasets/recipes_raw.zip*
让我们打印缓存文件夹,看看到底下载了什么:
*!ls -la ./tmp/datasets/*
➔输出:
*total 521128
drwxr-xr-x 7 224 May 13 18:10 .
drwxr-xr-x 4 128 May 18 18:00 ..
-rw-r--r-- 1 20437 May 20 06:46 LICENSE
-rw-r--r-- 1 53355492 May 13 18:10 recipes_raw.zip
-rw-r--r-- 1 49784325 May 20 06:46 recipes_raw_nosource_ar.json
-rw-r--r-- 1 61133971 May 20 06:46 recipes_raw_nosource_epi.json
-rw-r--r-- 1 93702755 May 20 06:46 recipes_raw_nosource_fn.json*
如您所见,数据集由 3 个文件组成。稍后,我们需要将这些文件中的信息合并成一个数据集。
让我们从json
文件中加载数据集数据,并从中预览示例。
***def** **load_dataset**(silent=False):
*# List of dataset files we want to merge.*
dataset_file_names = [
'recipes_raw_nosource_ar.json',
'recipes_raw_nosource_epi.json',
'recipes_raw_nosource_fn.json',
]
dataset = [] **for** dataset_file_name **in** dataset_file_names:
dataset_file_path = f'{CACHE_DIR}/datasets/{dataset_file_name}' **with** open(dataset_file_path) **as** dataset_file:
json_data_dict = json.load(dataset_file)
json_data_list = list(json_data_dict.values())
dict_keys = [key **for** key **in** json_data_list[0]]
dict_keys.sort()
dataset += json_data_list *# This code block outputs the summary for each dataset.*
**if** silent == **False**:
print(dataset_file_path)
print('===========================================')
print('Number of examples: ', len(json_data_list), '\n')
print('Example object keys:\n', dict_keys, '\n')
print('Example object:\n', json_data_list[0], '\n')
print('Required keys:\n')
print(' title: ', json_data_list[0]['title'], '\n')
print(' ingredients: ', json_data_list[0]['ingredients'], '\n')
print(' instructions: ', json_data_list[0]['instructions'])
print('\n\n') **return** dataset dataset_raw = load_dataset()*
➔输出:
*./tmp/datasets/recipes_raw_nosource_ar.json
===========================================
Number of examples: 39802 Example object keys:
['ingredients', 'instructions', 'picture_link', 'title'] Example object:
{'title': 'Slow Cooker Chicken and Dumplings', 'ingredients': ['4 skinless, boneless chicken breast halves ADVERTISEMENT', '2 tablespoons butter ADVERTISEMENT', '2 (10.75 ounce) cans condensed cream of chicken soup ADVERTISEMENT', '1 onion, finely diced ADVERTISEMENT', '2 (10 ounce) packages refrigerated biscuit dough, torn into pieces ADVERTISEMENT', 'ADVERTISEMENT'], 'instructions': 'Place the chicken, butter, soup, and onion in a slow cooker, and fill with enough water to cover.\nCover, and cook for 5 to 6 hours on High. About 30 minutes before serving, place the torn biscuit dough in the slow cooker. Cook until the dough is no longer raw in the center.\n', 'picture_link': '55lznCYBbs2mT8BTx6BTkLhynGHzM.S'} Required keys: title: Slow Cooker Chicken and Dumplings ingredients: ['4 skinless, boneless chicken breast halves ADVERTISEMENT', '2 tablespoons butter ADVERTISEMENT', '2 (10.75 ounce) cans condensed cream of chicken soup ADVERTISEMENT', '1 onion, finely diced ADVERTISEMENT', '2 (10 ounce) packages refrigerated biscuit dough, torn into pieces ADVERTISEMENT', 'ADVERTISEMENT'] instructions: Place the chicken, butter, soup, and onion in a slow cooker, and fill with enough water to cover.
Cover, and cook for 5 to 6 hours on High. About 30 minutes before serving, place the torn biscuit dough in the slow cooker. Cook until the dough is no longer raw in the center. ./tmp/datasets/recipes_raw_nosource_epi.json
===========================================
Number of examples: 25323 Example object keys:
['ingredients', 'instructions', 'picture_link', 'title'] Example object:
{'ingredients': ['12 egg whites', '12 egg yolks', '1 1/2 cups sugar', '3/4 cup rye whiskey', '12 egg whites', '3/4 cup brandy', '1/2 cup rum', '1 to 2 cups heavy cream, lightly whipped', 'Garnish: ground nutmeg'], 'picture_link': None, 'instructions': 'Beat the egg whites until stiff, gradually adding in 3/4 cup sugar. Set aside. Beat the egg yolks until they are thick and pale and add the other 3/4 cup sugar and stir in rye whiskey. Blend well. Fold the egg white mixture into the yolk mixture and add the brandy and the rum. Beat the mixture well. To serve, fold the lightly whipped heavy cream into the eggnog. (If a thinner mixture is desired, add the heavy cream unwhipped.) Sprinkle the top of the eggnog with the nutmeg to taste.\nBeat the egg whites until stiff, gradually adding in 3/4 cup sugar. Set aside. Beat the egg yolks until they are thick and pale and add the other 3/4 cup sugar and stir in rye whiskey. Blend well. Fold the egg white mixture into the yolk mixture and add the brandy and the rum. Beat the mixture well. To serve, fold the lightly whipped heavy cream into the eggnog. (If a thinner mixture is desired, add the heavy cream unwhipped.) Sprinkle the top of the eggnog with the nutmeg to taste.', 'title': 'Christmas Eggnog '} Required keys: title: Christmas Eggnog ingredients: ['12 egg whites', '12 egg yolks', '1 1/2 cups sugar', '3/4 cup rye whiskey', '12 egg whites', '3/4 cup brandy', '1/2 cup rum', '1 to 2 cups heavy cream, lightly whipped', 'Garnish: ground nutmeg'] instructions: Beat the egg whites until stiff, gradually adding in 3/4 cup sugar. Set aside. Beat the egg yolks until they are thick and pale and add the other 3/4 cup sugar and stir in rye whiskey. Blend well. Fold the egg white mixture into the yolk mixture and add the brandy and the rum. Beat the mixture well. To serve, fold the lightly whipped heavy cream into the eggnog. (If a thinner mixture is desired, add the heavy cream unwhipped.) Sprinkle the top of the eggnog with the nutmeg to taste.
Beat the egg whites until stiff, gradually adding in 3/4 cup sugar. Set aside. Beat the egg yolks until they are thick and pale and add the other 3/4 cup sugar and stir in rye whiskey. Blend well. Fold the egg white mixture into the yolk mixture and add the brandy and the rum. Beat the mixture well. To serve, fold the lightly whipped heavy cream into the eggnog. (If a thinner mixture is desired, add the heavy cream unwhipped.) Sprinkle the top of the eggnog with the nutmeg to taste../tmp/datasets/recipes_raw_nosource_fn.json
===========================================
Number of examples: 60039 Example object keys:
['ingredients', 'instructions', 'picture_link', 'title'] Example object:
{'instructions': 'Toss ingredients lightly and spoon into a buttered baking dish. Top with additional crushed cracker crumbs, and brush with melted butter. Bake in a preheated at 350 degrees oven for 25 to 30 minutes or until delicately browned.', 'ingredients': ['1/2 cup celery, finely chopped', '1 small green pepper finely chopped', '1/2 cup finely sliced green onions', '1/4 cup chopped parsley', '1 pound crabmeat', '1 1/4 cups coarsely crushed cracker crumbs', '1/2 teaspoon salt', '3/4 teaspoons dry mustard', 'Dash hot sauce', '1/4 cup heavy cream', '1/2 cup melted butter'], 'title': "Grammie Hamblet's Deviled Crab", 'picture_link': None} Required keys: title: Grammie Hamblet's Deviled Crab ingredients: ['1/2 cup celery, finely chopped', '1 small green pepper finely chopped', '1/2 cup finely sliced green onions', '1/4 cup chopped parsley', '1 pound crabmeat', '1 1/4 cups coarsely crushed cracker crumbs', '1/2 teaspoon salt', '3/4 teaspoons dry mustard', 'Dash hot sauce', '1/4 cup heavy cream', '1/2 cup melted butter'] instructions: Toss ingredients lightly and spoon into a buttered baking dish. Top with additional crushed cracker crumbs, and brush with melted butter. Bake in a preheated at 350 degrees oven for 25 to 30 minutes or until delicately browned.*
让我们统计一下合并文件后的示例总数:
*print('Total number of raw examples: ', len(dataset_raw))*
➔输出:
*Total number of raw examples: 125164*
预处理数据集
过滤掉不完整的例子
有可能有些菜谱没有一些必填字段(名称、配料或说明*)。我们需要从这些不完整的例子中清理出我们的数据集。*
以下函数将帮助我们筛选出既没有标题也没有配料或说明的食谱:
***def** **recipe_validate_required_fields**(recipe):
required_keys = ['title', 'ingredients', 'instructions']
**if** **not** recipe:
**return** **False**
**for** required_key **in** required_keys:
**if** **not** recipe[required_key]:
**return** **False**
**if** type(recipe[required_key]) == list **and** len(recipe[required_key]) == 0:
**return** **False**
**return** **True***
现在让我们使用recipe_validate_required_fields()
函数进行过滤:
*dataset_validated = [recipe **for** recipe **in** dataset_raw **if** recipe_validate_required_fields(recipe)]print('Dataset size BEFORE validation', len(dataset_raw))
print('Dataset size AFTER validation', len(dataset_validated))
print('Number of incomplete recipes', len(dataset_raw) - len(dataset_validated))*
➔输出:
*Dataset size BEFORE validation 125164
Dataset size AFTER validation 122938
Number of incomplete recipes 2226*
正如你可能看到的,我们的2226
食谱有些不完整。
将配方对象转换为字符串
RNN 不理解物体。因此,我们需要将 recipes 对象转换为字符串,然后转换为数字(索引)。让我们从将 recipes 对象转换成字符串开始。
为了帮助我们的 RNN 更快地学习文章的结构,让我们给它添加 3 个“地标”。我们将使用这些独特的“标题”、“配料”和“说明”标志来分隔每个食谱的逻辑部分。
*STOP_WORD_TITLE = '📗 '
STOP_WORD_INGREDIENTS = '\n🥕\n\n'
STOP_WORD_INSTRUCTIONS = '\n📝\n\n'*
下面的函数将 recipe 对象转换为字符串(字符序列),以便以后在 RNN 输入中使用。
***def** **recipe_to_string**(recipe):
*# This string is presented as a part of recipes so we need to clean it up.*
noize_string = 'ADVERTISEMENT'
title = recipe['title']
ingredients = recipe['ingredients']
instructions = recipe['instructions'].split('\n')
ingredients_string = ''
**for** ingredient **in** ingredients:
ingredient = ingredient.replace(noize_string, '')
**if** ingredient:
ingredients_string += f'• {ingredient}\n'
instructions_string = ''
**for** instruction **in** instructions:
instruction = instruction.replace(noize_string, '')
**if** instruction:
instructions_string += f'▪︎ {instruction}\n'
**return** f'{STOP_WORD_TITLE}{title}\n{STOP_WORD_INGREDIENTS}{ingredients_string}{STOP_WORD_INSTRUCTIONS}{instructions_string}'*
让我们将recipe_to_string()
函数应用于dataset_validated
:
*dataset_stringified = [recipe_to_string(recipe) **for** recipe **in** dataset_validated]print('Stringified dataset size: ', len(dataset_stringified))*
➔输出:
*Stringified dataset size: 122938*
让我们先来预览几个食谱:
***for** recipe_index, recipe_string **in** enumerate(dataset_stringified[:3]):
print('Recipe #{}\n---------'.format(recipe_index + 1))
print(recipe_string)
print('\n')*
➔输出:
*Recipe #1
---------
📗 Slow Cooker Chicken and Dumplings🥕• 4 skinless, boneless chicken breast halves
• 2 tablespoons butter
• 2 (10.75 ounce) cans condensed cream of chicken soup
• 1 onion, finely diced
• 2 (10 ounce) packages refrigerated biscuit dough, torn into pieces 📝▪︎ Place the chicken, butter, soup, and onion in a slow cooker, and fill with enough water to cover.
▪︎ Cover, and cook for 5 to 6 hours on High. About 30 minutes before serving, place the torn biscuit dough in the slow cooker. Cook until the dough is no longer raw in the center.Recipe #2
---------
📗 Awesome Slow Cooker Pot Roast🥕• 2 (10.75 ounce) cans condensed cream of mushroom soup
• 1 (1 ounce) package dry onion soup mix
• 1 1/4 cups water
• 5 1/2 pounds pot roast 📝▪︎ In a slow cooker, mix cream of mushroom soup, dry onion soup mix and water. Place pot roast in slow cooker and coat with soup mixture.
▪︎ Cook on High setting for 3 to 4 hours, or on Low setting for 8 to 9 hours.Recipe #3
---------
📗 Brown Sugar Meatloaf🥕• 1/2 cup packed brown sugar
• 1/2 cup ketchup
• 1 1/2 pounds lean ground beef
• 3/4 cup milk
• 2 eggs
• 1 1/2 teaspoons salt
• 1/4 teaspoon ground black pepper
• 1 small onion, chopped
• 1/4 teaspoon ground ginger
• 3/4 cup finely crushed saltine cracker crumbs 📝▪︎ Preheat oven to 350 degrees F (175 degrees C). Lightly grease a 5x9 inch loaf pan.
▪︎ Press the brown sugar in the bottom of the prepared loaf pan and spread the ketchup over the sugar.
▪︎ In a mixing bowl, mix thoroughly all remaining ingredients and shape into a loaf. Place on top of the ketchup.
▪︎ Bake in preheated oven for 1 hour or until juices are clear.*
出于好奇,让我们从数据集的中间预览一下菜谱,看看它是否具有预期的数据结构:
*print(dataset_stringified[50000])*
➔输出:
*📗 Herbed Bean Ragoût 🥕• 6 ounces haricots verts (French thin green beans), trimmed and halved crosswise
• 1 (1-pound) bag frozen edamame (soybeans in the pod) or 1 1/4 cups frozen shelled edamame, not thawed
• 2/3 cup finely chopped onion
• 2 garlic cloves, minced
• 1 Turkish bay leaf or 1/2 California bay leaf
• 2 (3-inch) fresh rosemary sprigs
• 1/2 teaspoon salt
• 1/4 teaspoon black pepper
• 1 tablespoon olive oil
• 1 medium carrot, cut into 1/8-inch dice
• 1 medium celery rib, cut into 1/8-inch dice
• 1 (15- to 16-ounces) can small white beans, rinsed and drained
• 1 1/2 cups chicken stock or low-sodium broth
• 2 tablespoons unsalted butter
• 2 tablespoons finely chopped fresh flat-leaf parsley
• 1 tablespoon finely chopped fresh chervil (optional)
• Garnish: fresh chervil sprigs📝▪︎ Cook haricots verts in a large pot of boiling salted water until just tender, 3 to 4 minutes. Transfer with a slotted spoon to a bowl of ice and cold water, then drain. Add edamame to boiling water and cook 4 minutes. Drain in a colander, then rinse under cold water. If using edamame in pods, shell them and discard pods. Cook onion, garlic, bay leaf, rosemary, salt, and pepper in oil in a 2- to 4-quart heavy saucepan over moderately low heat, stirring, until softened, about 3 minutes. Add carrot and celery and cook, stirring, until softened, about 3 minutes. Add white beans and stock and simmer, covered, stirring occasionally, 10 minutes. Add haricots verts and edamame and simmer, uncovered, until heated through, 2 to 3 minutes. Add butter, parsley, and chervil (if using) and stir gently until butter is melted. Discard bay leaf and rosemary sprigs.
▪︎ Cook haricots verts in a large pot of boiling salted water until just tender, 3 to 4 minutes. Transfer with a slotted spoon to a bowl of ice and cold water, then drain.
▪︎ Add edamame to boiling water and cook 4 minutes. Drain in a colander, then rinse under cold water. If using edamame in pods, shell them and discard pods.
▪︎ Cook onion, garlic, bay leaf, rosemary, salt, and pepper in oil in a 2- to 4-quart heavy saucepan over moderately low heat, stirring, until softened, about 3 minutes. Add carrot and celery and cook, stirring, until softened, about 3 minutes.
▪︎ Add white beans and stock and simmer, covered, stirring occasionally, 10 minutes. Add haricots verts and edamame and simmer, uncovered, until heated through, 2 to 3 minutes. Add butter, parsley, and chervil (if using) and stir gently until butter is melted. Discard bay leaf and rosemary sprigs.*
过滤掉大量食谱
食谱长短不一。在将配方序列输入 RNN 之前,我们需要有一个硬编码序列长度限制。我们需要找出什么样的配方长度可以覆盖大多数的配方用例,同时我们希望它尽可能的短,以加快训练过程。
*recipes_lengths = []
**for** recipe_text **in** dataset_stringified:
recipes_lengths.append(len(recipe_text))plt.hist(recipes_lengths, bins=50)
plt.show()*
➔输出:
配方长度(代码生成的图像)
大多数食谱的长度小于5000
个字符。让我们放大来看更详细的图片:
*plt.hist(recipes_lengths, range=(0, 8000), bins=50)
plt.show()*
➔输出:
配方长度(代码生成的图像)
看起来食谱的字符限制将覆盖大多数情况。我们可以试着用这个最大食谱长度限制来训练 RNN。
*MAX_RECIPE_LENGTH = 2000*
因此,我们来过滤掉所有长于MAX_RECIPE_LENGTH
的菜谱:
***def** **filter_recipes_by_length**(recipe_test):
**return** len(recipe_test) <= MAX_RECIPE_LENGTH dataset_filtered = [recipe_text **for** recipe_text **in** dataset_stringified **if** filter_recipes_by_length(recipe_text)]print('Dataset size BEFORE filtering: ', len(dataset_stringified))
print('Dataset size AFTER filtering: ', len(dataset_filtered))
print('Number of eliminated recipes: ', len(dataset_stringified) - len(dataset_filtered))*
➔输出:
*Dataset size BEFORE filtering: 122938
Dataset size AFTER filtering: 100212
Number of eliminated recipes: 22726*
在过滤过程中,我们丢失了22726
配方,但现在配方的数据更加密集。
汇总数据集参数
*TOTAL_RECIPES_NUM = len(dataset_filtered)print('MAX_RECIPE_LENGTH: ', MAX_RECIPE_LENGTH)
print('TOTAL_RECIPES_NUM: ', TOTAL_RECIPES_NUM)*
➔输出:
*MAX_RECIPE_LENGTH: 2000
TOTAL_RECIPES_NUM: 100212*
最后,我们以~100k
食谱告终。每个配方都有2000
字符长度。
创造词汇
递归神经网络不理解字符或单词。相反,它理解数字。因此,我们需要将食谱文本转换成数字。
在这个实验中,我们将使用基于多层 LSTM(长短期记忆)网络的字符级语言模型(与单词级语言模型相对)。这意味着我们将为字符创建唯一的索引,而不是为单词创建唯一的索引。通过这样做,我们让网络预测序列中的下一个字符*,而不是下一个单词。*
ℹ️你可以在 Andrej Karpathy 的文章中找到更多关于字符级 RNNs 的解释:
为了从食谱文本中创建词汇表,我们将使用TF . keras . preprocessing . text . tokenizer。
我们还需要来一些独特的字符,将被视为一个停止字符*,将表明一个食谱的结束。我们需要它来生成菜谱,因为如果没有这个停止字符,我们就不知道正在生成的菜谱的结尾在哪里。*
*STOP_SIGN = '␣'tokenizer = tf.keras.preprocessing.text.Tokenizer(
char_level=**True**,
filters='',
lower=**False**,
split=''
)*# Stop word is not a part of recipes, but tokenizer must know about it as well.*
tokenizer.fit_on_texts([STOP_SIGN])tokenizer.fit_on_texts(dataset_filtered)tokenizer.get_config()*
➔输出:
*{'num_words': None,
'filters': '',
'lower': False,
'split': '',
'char_level': True,
'oov_token': None,
'document_count': 100213, 'word_counts': '{"\\u2423": 1, "\\ud83d\\udcd7": 100212, " ": 17527888, "S": 270259, "l": 3815150, "o": 5987496, "w": 964459, "C": 222831, "k": 890982, "e": 9296022, "r": 4760887, "h": 2922100, "i": 4911812, "c": 2883507, "n": 5304396, "a": 6067157, "d": 3099679, "D": 63999, "u": 2717050, "m": 1794411, "p": 2679164, "g": 1698670, "s": 4704222, "\\n": 1955281, "\\ud83e\\udd55": 100212, "\\u2022": 922813, "4": 232607, ",": 1130487, "b": 1394803, "t": 5997722, "v": 746785, "2": 493933, "(": 144985, "1": 853931, "0": 145119, ".": 1052548, "7": 31098, "5": 154071, ")": 144977, "f": 1042981, "y": 666553, "\\ud83d\\udcdd": 100212, "\\u25aa": 331058, "\\ufe0e": 331058, "P": 200597, "6": 51398, "H": 43936, "A": 134274, "3": 213519, "R": 101253, "x": 201286, "/": 345257, "I": 81591, "L": 46138, "8": 55352, "9": 17697, "B": 123813, "M": 78684, "F": 104359, "j": 110008, "-": 219160, "W": 61616, "\\u00ae": 10159, "N": 12808, "q": 69654, "T": 101371, ";": 72045, "\'": 26831, "Z": 2428, "z": 115883, "G": 52043, ":": 31318, "E": 18582, "K": 18421, "X": 385, "\\"": 6445, "O": 28971, "Y": 6064, "\\u2122": 538, "Q": 3904, "J": 10269, "!": 3014, "U": 14132, "V": 12172, "&": 1039, "+": 87, "=": 113, "%": 993, "*": 3243, "\\u00a9": 99, "[": 30, "]": 31, "\\u00e9": 6727, "<": 76, ">": 86, "\\u00bd": 166, "#": 168, "\\u00f1": 891, "?": 327, "\\u2019": 111, "\\u00b0": 6808, "\\u201d": 6, "$": 84, "@": 5, "{": 8, "}": 9, "\\u2013": 1228, "\\u0096": 7, "\\u00e0": 26, "\\u00e2": 106, "\\u00e8": 846, "\\u00e1": 74, "\\u2014": 215, "\\u2044": 16, "\\u00ee": 415, "\\u00e7": 171, "_": 26, "\\u00fa": 48, "\\u00ef": 43, "\\u201a": 20, "\\u00fb": 36, "\\u00f3": 74, "\\u00ed": 130, "\\u25ca": 4, "\\u00f9": 12, "\\u00d7": 6, "\\u00ec": 8, "\\u00fc": 29, "\\u2031": 4, "\\u00ba": 19, "\\u201c": 4, "\\u00ad": 25, "\\u00ea": 27, "\\u00f6": 9, "\\u0301": 11, "\\u00f4": 8, "\\u00c1": 2, "\\u00be": 23, "\\u00bc": 95, "\\u00eb": 2, "\\u0097": 2, "\\u215b": 3, "\\u2027": 4, "\\u00e4": 15, "\\u001a": 2, "\\u00f8": 2, "\\ufffd": 20, "\\u02da": 6, "\\u00bf": 264, "\\u2153": 2, "|": 2, "\\u00e5": 3, "\\u00a4": 1, "\\u201f": 1, "\\u00a7": 5, "\\ufb02": 3, "\\u00a0": 1, "\\u01b0": 2, "\\u01a1": 1, "\\u0103": 1, "\\u0300": 1, "\\u00bb": 6, "`": 3, "\\u0092": 2, "\\u215e": 1, "\\u202d": 4, "\\u00b4": 2, "\\u2012": 2, "\\u00c9": 40, "\\u00da": 14, "\\u20ac": 1, "\\\\": 5, "~": 1, "\\u0095": 1, "\\u00c2": 2}', 'word_docs': '{"\\u2423": 1, "k": 97316, "0": 61954, "o": 100205, "r": 100207, "d": 100194, "u": 100161, "S": 89250, "\\u25aa": 100212, "D": 40870, "1": 99320, "g": 99975, "n": 100198, "b": 99702, "t": 100202, ".": 100163, " ": 100212, "7": 24377, "3": 79135, "\\ud83d\\udcd7": 100212, "i": 100207, "5": 65486, "f": 98331, "c": 100190, "4": 82453, "a": 100205, "2": 96743, "v": 97848, "C": 83328, "s": 100204, "\\n": 100212, "6": 35206, "\\ud83d\\udcdd": 100212, ",": 98524, "\\ufe0e": 100212, "l": 100206, "e": 100212, "y": 96387, ")": 67614, "p": 100046, "H": 31908, "\\ud83e\\udd55": 100212, "m": 99988, "w": 99227, "(": 67627, "A": 60900, "h": 100161, "\\u2022": 100212, "P": 79364, "R": 54040, "9": 14114, "8": 37000, "L": 32101, "x": 72133, "I": 46675, "/": 89051, "j": 47438, "F": 57940, "B": 64278, "M": 48332, "-": 74711, "T": 53758, "\\u00ae": 5819, "N": 9981, "W": 38981, "q": 36538, ";": 33863, "G": 35355, "\'": 18120, "z": 42430, "Z": 2184, ":": 18214, "E": 12161, "K": 14834, "X": 321, "\\"": 2617, "O": 20103, "Y": 5148, "\\u2122": 448, "Q": 3142, "J": 8225, "!": 2428, "U": 10621, "V": 9710, "&": 749, "+": 32, "=": 48, "%": 717, "*": 1780, "\\u00a9": 91, "]": 26, "[": 25, "\\u00e9": 2462, ">": 33, "<": 27, "\\u00bd": 81, "#": 139, "\\u00f1": 423, "?": 207, "\\u2019": 64, "\\u00b0": 3062, "\\u201d": 3, "@": 4, "$": 49, "{": 7, "}": 8, "\\u2013": 491, "\\u0096": 7, "\\u00e0": 22, "\\u00e2": 45, "\\u00e8": 335, "\\u00e1": 38, "\\u2014": 95, "\\u2044": 9, "\\u00ee": 122, "\\u00e7": 120, "_": 8, "\\u00fa": 25, "\\u00ef": 24, "\\u201a": 10, "\\u00fb": 29, "\\u00f3": 40, "\\u00ed": 52, "\\u25ca": 2, "\\u00f9": 6, "\\u00d7": 4, "\\u00ec": 4, "\\u00fc": 19, "\\u2031": 2, "\\u00ba": 9, "\\u201c": 2, "\\u00ad": 11, "\\u00ea": 4, "\\u00f6": 4, "\\u0301": 6, "\\u00f4": 5, "\\u00c1": 2, "\\u00be": 18, "\\u00bc": 55, "\\u00eb": 2, "\\u0097": 1, "\\u215b": 2, "\\u2027": 3, "\\u00e4": 8, "\\u001a": 1, "\\u00f8": 1, "\\ufffd": 4, "\\u02da": 3, "\\u00bf": 191, "\\u2153": 1, "|": 2, "\\u00e5": 1, "\\u00a4": 1, "\\u201f": 1, "\\u00a7": 3, "\\ufb02": 1, "\\u0300": 1, "\\u01a1": 1, "\\u00a0": 1, "\\u01b0": 1, "\\u0103": 1, "\\u00bb": 2, "`": 3, "\\u0092": 2, "\\u215e": 1, "\\u202d": 1, "\\u00b4": 1, "\\u2012": 1, "\\u00c9": 15, "\\u00da": 5, "\\u20ac": 1, "\\\\": 5, "~": 1, "\\u0095": 1, "\\u00c2": 1}', 'index_docs': '{"1": 100212, "165": 1, "25": 97316, "41": 61954, "5": 100205, "8": 100207, "11": 100194, "14": 100161, "33": 89250, "31": 100212, "58": 40870, "26": 99320, "18": 99975, "6": 100198, "19": 99702, "4": 100202, "21": 100163, "66": 24377, "37": 79135, "51": 100212, "7": 100207, "40": 65486, "22": 98331, "13": 100190, "34": 82453, "3": 100205, "29": 96743, "27": 97848, "35": 83328, "9": 100204, "16": 100212, "62": 35206, "53": 100212, "20": 98524, "32": 100212, "10": 100206, "2": 100212, "28": 96387, "43": 67614, "15": 100046, "64": 31908, "52": 100212, "17": 99988, "23": 99227, "42": 67627, "44": 60900, "12": 100161, "24": 100212, "39": 79364, "50": 54040, "71": 14114, "60": 37000, "63": 32101, "38": 72133, "54": 46675, "30": 89051, "47": 47438, "48": 57940, "45": 64278, "55": 48332, "36": 74711, "49": 53758, "76": 5819, "73": 9981, "59": 38981, "57": 36538, "56": 33863, "61": 35355, "68": 18120, "46": 42430, "84": 2184, "65": 18214, "69": 12161, "70": 14834, "92": 321, "79": 2617, "67": 20103, "80": 5148, "90": 448, "81": 3142, "75": 8225, "83": 2428, "72": 10621, "74": 9710, "86": 749, "105": 32, "100": 48, "87": 717, "82": 1780, "103": 91, "115": 26, "116": 25, "78": 2462, "106": 33, "108": 27, "98": 81, "97": 139, "88": 423, "93": 207, "101": 64, "77": 3062, "137": 3, "141": 4, "107": 49, "133": 7, "131": 8, "85": 491, "136": 7, "119": 22, "102": 45, "89": 335, "109": 38, "95": 95, "126": 9, "91": 122, "96": 120, "120": 8, "111": 25, "112": 24, "123": 10, "114": 29, "110": 40, "99": 52, "144": 2, "129": 6, "138": 4, "134": 4, "117": 19, "145": 2, "125": 9, "146": 2, "121": 11, "118": 4, "132": 4, "130": 6, "135": 5, "153": 2, "122": 18, "104": 55, "154": 2, "155": 1, "149": 2, "147": 3, "127": 8, "156": 1, "157": 1, "124": 4, "139": 3, "94": 191, "158": 1, "159": 2, "150": 1, "166": 1, "167": 1, "142": 3, "151": 1, "171": 1, "169": 1, "168": 1, "160": 1, "170": 1, "140": 2, "152": 3, "161": 2, "172": 1, "148": 1, "162": 1, "163": 1, "113": 15, "128": 5, "173": 1, "143": 5, "174": 1, "175": 1, "164": 1}', 'index_word': '{"1": " ", "2": "e", "3": "a", "4": "t", "5": "o", "6": "n", "7": "i", "8": "r", "9": "s", "10": "l", "11": "d", "12": "h", "13": "c", "14": "u", "15": "p", "16": "\\n", "17": "m", "18": "g", "19": "b", "20": ",", "21": ".", "22": "f", "23": "w", "24": "\\u2022", "25": "k", "26": "1", "27": "v", "28": "y", "29": "2", "30": "/", "31": "\\u25aa", "32": "\\ufe0e", "33": "S", "34": "4", "35": "C", "36": "-", "37": "3", "38": "x", "39": "P", "40": "5", "41": "0", "42": "(", "43": ")", "44": "A", "45": "B", "46": "z", "47": "j", "48": "F", "49": "T", "50": "R", "51": "\\ud83d\\udcd7", "52": "\\ud83e\\udd55", "53": "\\ud83d\\udcdd", "54": "I", "55": "M", "56": ";", "57": "q", "58": "D", "59": "W", "60": "8", "61": "G", "62": "6", "63": "L", "64": "H", "65": ":", "66": "7", "67": "O", "68": "\'", "69": "E", "70": "K", "71": "9", "72": "U", "73": "N", "74": "V", "75": "J", "76": "\\u00ae", "77": "\\u00b0", "78": "\\u00e9", "79": "\\"", "80": "Y", "81": "Q", "82": "*", "83": "!", "84": "Z", "85": "\\u2013", "86": "&", "87": "%", "88": "\\u00f1", "89": "\\u00e8", "90": "\\u2122", "91": "\\u00ee", "92": "X", "93": "?", "94": "\\u00bf", "95": "\\u2014", "96": "\\u00e7", "97": "#", "98": "\\u00bd", "99": "\\u00ed", "100": "=", "101": "\\u2019", "102": "\\u00e2", "103": "\\u00a9", "104": "\\u00bc", "105": "+", "106": ">", "107": "$", "108": "<", "109": "\\u00e1", "110": "\\u00f3", "111": "\\u00fa", "112": "\\u00ef", "113": "\\u00c9", "114": "\\u00fb", "115": "]", "116": "[", "117": "\\u00fc", "118": "\\u00ea", "119": "\\u00e0", "120": "_", "121": "\\u00ad", "122": "\\u00be", "123": "\\u201a", "124": "\\ufffd", "125": "\\u00ba", "126": "\\u2044", "127": "\\u00e4", "128": "\\u00da", "129": "\\u00f9", "130": "\\u0301", "131": "}", "132": "\\u00f6", "133": "{", "134": "\\u00ec", "135": "\\u00f4", "136": "\\u0096", "137": "\\u201d", "138": "\\u00d7", "139": "\\u02da", "140": "\\u00bb", "141": "@", "142": "\\u00a7", "143": "\\\\", "144": "\\u25ca", "145": "\\u2031", "146": "\\u201c", "147": "\\u2027", "148": "\\u202d", "149": "\\u215b", "150": "\\u00e5", "151": "\\ufb02", "152": "`", "153": "\\u00c1", "154": "\\u00eb", "155": "\\u0097", "156": "\\u001a", "157": "\\u00f8", "158": "\\u2153", "159": "|", "160": "\\u01b0", "161": "\\u0092", "162": "\\u00b4", "163": "\\u2012", "164": "\\u00c2", "165": "\\u2423", "166": "\\u00a4", "167": "\\u201f", "168": "\\u00a0", "169": "\\u01a1", "170": "\\u0103", "171": "\\u0300", "172": "\\u215e", "173": "\\u20ac", "174": "~", "175": "\\u0095"}', 'word_index': '{" ": 1, "e": 2, "a": 3, "t": 4, "o": 5, "n": 6, "i": 7, "r": 8, "s": 9, "l": 10, "d": 11, "h": 12, "c": 13, "u": 14, "p": 15, "\\n": 16, "m": 17, "g": 18, "b": 19, ",": 20, ".": 21, "f": 22, "w": 23, "\\u2022": 24, "k": 25, "1": 26, "v": 27, "y": 28, "2": 29, "/": 30, "\\u25aa": 31, "\\ufe0e": 32, "S": 33, "4": 34, "C": 35, "-": 36, "3": 37, "x": 38, "P": 39, "5": 40, "0": 41, "(": 42, ")": 43, "A": 44, "B": 45, "z": 46, "j": 47, "F": 48, "T": 49, "R": 50, "\\ud83d\\udcd7": 51, "\\ud83e\\udd55": 52, "\\ud83d\\udcdd": 53, "I": 54, "M": 55, ";": 56, "q": 57, "D": 58, "W": 59, "8": 60, "G": 61, "6": 62, "L": 63, "H": 64, ":": 65, "7": 66, "O": 67, "\'": 68, "E": 69, "K": 70, "9": 71, "U": 72, "N": 73, "V": 74, "J": 75, "\\u00ae": 76, "\\u00b0": 77, "\\u00e9": 78, "\\"": 79, "Y": 80, "Q": 81, "*": 82, "!": 83, "Z": 84, "\\u2013": 85, "&": 86, "%": 87, "\\u00f1": 88, "\\u00e8": 89, "\\u2122": 90, "\\u00ee": 91, "X": 92, "?": 93, "\\u00bf": 94, "\\u2014": 95, "\\u00e7": 96, "#": 97, "\\u00bd": 98, "\\u00ed": 99, "=": 100, "\\u2019": 101, "\\u00e2": 102, "\\u00a9": 103, "\\u00bc": 104, "+": 105, ">": 106, "$": 107, "<": 108, "\\u00e1": 109, "\\u00f3": 110, "\\u00fa": 111, "\\u00ef": 112, "\\u00c9": 113, "\\u00fb": 114, "]": 115, "[": 116, "\\u00fc": 117, "\\u00ea": 118, "\\u00e0": 119, "_": 120, "\\u00ad": 121, "\\u00be": 122, "\\u201a": 123, "\\ufffd": 124, "\\u00ba": 125, "\\u2044": 126, "\\u00e4": 127, "\\u00da": 128, "\\u00f9": 129, "\\u0301": 130, "}": 131, "\\u00f6": 132, "{": 133, "\\u00ec": 134, "\\u00f4": 135, "\\u0096": 136, "\\u201d": 137, "\\u00d7": 138, "\\u02da": 139, "\\u00bb": 140, "@": 141, "\\u00a7": 142, "\\\\": 143, "\\u25ca": 144, "\\u2031": 145, "\\u201c": 146, "\\u2027": 147, "\\u202d": 148, "\\u215b": 149, "\\u00e5": 150, "\\ufb02": 151, "`": 152, "\\u00c1": 153, "\\u00eb": 154, "\\u0097": 155, "\\u001a": 156, "\\u00f8": 157, "\\u2153": 158, "|": 159, "\\u01b0": 160, "\\u0092": 161, "\\u00b4": 162, "\\u2012": 163, "\\u00c2": 164, "\\u2423": 165, "\\u00a4": 166, "\\u201f": 167, "\\u00a0": 168, "\\u01a1": 169, "\\u0103": 170, "\\u0300": 171, "\\u215e": 172, "\\u20ac": 173, "~": 174, "\\u0095": 175}'}*
为了得到一个完整的词汇表,我们需要将+1
加到已经注册的字符数上,因为索引 [0](https://www.tensorflow.org/api_docs/python/tf/keras/preprocessing/text/Tokenizer)
是一个保留索引,不会分配给任何单词。
*VOCABULARY_SIZE = len(tokenizer.word_counts) + 1print('VOCABULARY_SIZE: ', VOCABULARY_SIZE)*
➔输出:
*VOCABULARY_SIZE: 176*
让我们尝试一下记号化器字典,看看如何将字符转换成索引,反之亦然:
*print(tokenizer.index_word[5])
print(tokenizer.index_word[20])*
➔输出:
*o
,*
让我们尝试将字符转换为索引:
*tokenizer.word_index['r']*
➔输出:
*8*
为了说明哪种字符构成了我们数据集中的所有食谱,我们可以将它们打印成一个数组:
*array_vocabulary = tokenizer.sequences_to_texts([[word_index] **for** word_index **in** range(VOCABULARY_SIZE)])
print([char **for** char **in** array_vocabulary])*
➔输出:
*['', ' ', 'e', 'a', 't', 'o', 'n', 'i', 'r', 's', 'l', 'd', 'h', 'c', 'u', 'p', '\n', 'm', 'g', 'b', ',', '.', 'f', 'w', '•', 'k', '1', 'v', 'y', '2', '/', '▪', '︎', 'S', '4', 'C', '-', '3', 'x', 'P', '5', '0', '(', ')', 'A', 'B', 'z', 'j', 'F', 'T', 'R', '📗', '🥕', '📝', 'I', 'M', ';', 'q', 'D', 'W', '8', 'G', '6', 'L', 'H', ':', '7', 'O', "'", 'E', 'K', '9', 'U', 'N', 'V', 'J', '®', '°', 'é', '"', 'Y', 'Q', '*', '!', 'Z', '–', '&', '%', 'ñ', 'è', '™', 'î', 'X', '?', '¿', '—', 'ç', '#', '½', 'í', '=', '’', 'â', '©', '¼', '+', '>', '$', '<', 'á', 'ó', 'ú', 'ï', 'É', 'û', ']', '[', 'ü', 'ê', 'à', '_', '\xad', '¾', '‚', '�', 'º', '⁄', 'ä', 'Ú', 'ù', '́', '}', 'ö', '{', 'ì', 'ô', '\x96', '”', '×', '˚', '»', '@', '§', '\\', '◊', '‱', '“', '‧', '\u202d', '⅛', 'å', 'fl', '`', 'Á', 'ë', '\x97', '\x1a', 'ø', '⅓', '|', 'ư', '\x92', '´', '‒', 'Â', '␣', '¤', '‟', '\xa0', 'ơ', 'ă', '̀', '⅞', '€', '~', '\x95']*
这些都是我们的 RNN 模型将要处理的角色。它将尝试学习如何将这些字符组合成看起来像食谱的序列。
让我们看看如何使用tokenizer
函数将文本转换成索引:
*tokenizer.texts_to_sequences(['📗 yes'])*
➔输出:
*[[51, 1, 28, 2, 9]]*
对数据集进行矢量化
现在,一旦我们有了词汇表(character --code
和code --character
关系),我们就可以将食谱从文本转换成数字(RNN 将数字作为输入,而不是文本)。
*dataset_vectorized = tokenizer.texts_to_sequences(dataset_filtered)print('Vectorized dataset size', len(dataset_vectorized))*
➔输出:
*Vectorized dataset size 100212*
这是第一个矢量化食谱的开头:
*print(dataset_vectorized[0][:10], '...')*
➔输出:
*[51, 1, 33, 10, 5, 23, 1, 35, 5, 5] ...*
让我们看看如何将矢量化的食谱转换回文本表示:
***def** **recipe_sequence_to_string**(recipe_sequence):
recipe_stringified = tokenizer.sequences_to_texts([recipe_sequence])[0]
print(recipe_stringified)recipe_sequence_to_string(dataset_vectorized[0])*
➔输出:
*📗 Slow Cooker Chicken and Dumplings🥕• 4 skinless, boneless chicken breast halves
• 2 tablespoons butter
• 2 (10.75 ounce) cans condensed cream of chicken soup
• 1 onion, finely diced
• 2 (10 ounce) packages refrigerated biscuit dough, torn into pieces 📝▪︎ Place the chicken, butter, soup, and onion in a slow cooker, and fill with enough water to cover.
▪︎ Cover, and cook for 5 to 6 hours on High. About 30 minutes before serving, place the torn biscuit dough in the slow cooker. Cook until the dough is no longer raw in the center.*
给序列添加填充
我们需要所有的食谱都有相同的训练长度。为此,我们将使用TF . keras . preprocessing . sequence . pad _ sequences实用程序在每个配方的末尾添加一个停止字,并使它们具有相同的长度。
让我们检查一下食谱的长度:
***for** recipe_index, recipe **in** enumerate(dataset_vectorized[:10]):
print('Recipe #{} length: {}'.format(recipe_index + 1, len(recipe)))*
➔输出:
*Recipe #1 length: 546
Recipe #2 length: 401
Recipe #3 length: 671
Recipe #4 length: 736
Recipe #5 length: 1518
Recipe #6 length: 740
Recipe #7 length: 839
Recipe #8 length: 667
Recipe #9 length: 1264
Recipe #10 length: 854*
让我们把所有的食谱都加上一个STOP_SIGN
:
*dataset_vectorized_padded_without_stops = tf.keras.preprocessing.sequence.pad_sequences(
dataset_vectorized,
padding='post',
truncating='post',
*# We use -1 here and +1 in the next step to make sure*
*# that all recipes will have at least 1 stops sign at the end,*
*# since each sequence will be shifted and truncated afterwards*
*# (to generate X and Y sequences).*
maxlen=MAX_RECIPE_LENGTH-1,
value=tokenizer.texts_to_sequences([STOP_SIGN])[0]
)dataset_vectorized_padded = tf.keras.preprocessing.sequence.pad_sequences(
dataset_vectorized_padded_without_stops,
padding='post',
truncating='post',
maxlen=MAX_RECIPE_LENGTH+1,
value=tokenizer.texts_to_sequences([STOP_SIGN])[0]
)**for** recipe_index, recipe **in** enumerate(dataset_vectorized_padded[:10]):
print('Recipe #{} length: {}'.format(recipe_index, len(recipe)))*
➔输出:
*Recipe #0 length: 2001
Recipe #1 length: 2001
Recipe #2 length: 2001
Recipe #3 length: 2001
Recipe #4 length: 2001
Recipe #5 length: 2001
Recipe #6 length: 2001
Recipe #7 length: 2001
Recipe #8 length: 2001
Recipe #9 length: 2001*
填充后,数据集中的所有食谱现在都具有相同的长度,RNN 也将能够知道每个食谱在哪里停止(通过观察STOP_SIGN
的出现)。
以下是填充后第一个配方的外观示例。
*recipe_sequence_to_string(dataset_vectorized_padded[0])*
➔输出:
*📗 Slow Cooker Chicken and Dumplings🥕• 4 skinless, boneless chicken breast halves
• 2 tablespoons butter
• 2 (10.75 ounce) cans condensed cream of chicken soup
• 1 onion, finely diced
• 2 (10 ounce) packages refrigerated biscuit dough, torn into pieces 📝▪︎ Place the chicken, butter, soup, and onion in a slow cooker, and fill with enough water to cover.
▪︎ Cover, and cook for 5 to 6 hours on High. About 30 minutes before serving, place the torn biscuit dough in the slow cooker. Cook until the dough is no longer raw in the center.
␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣*
现在所有的食谱都以一个或多个␣
符号结尾。我们希望我们的 LSTM 模型知道,每当它看到␣
停止字符时,它就意味着食谱结束了。一旦网络学会了这个概念,它就会在每个新生成的菜谱的末尾加上停止字符。
创建张量流数据集
到目前为止,我们是像处理 NumPy 数组一样处理数据集的。如果我们将数据集 NumPy 数组转换成 TensorFlow 数据集,在训练过程中会更方便。它将赋予我们使用batch()
、shuffle()
、repeat()
、prefecth()
等助手功能的能力。:
*dataset = tf.data.Dataset.from_tensor_slices(dataset_vectorized_padded)print(dataset)*
➔输出:
*<TensorSliceDataset shapes: (2001,), types: tf.int32>*
让我们这次通过使用 TensorFlow 数据集 API 来看看数据集中的第一个配方是什么样的:
***for** recipe **in** dataset.take(1):
print('Raw recipe:\n', recipe.numpy(), '\n\n\n')
print('Stringified recipe:\n')
recipe_sequence_to_string(recipe.numpy())*
➔输出:
*Raw recipe:
[ 51 1 33 ... 165 165 165] Stringified recipe:📗 Slow Cooker Chicken and Dumplings🥕• 4 skinless, boneless chicken breast halves
• 2 tablespoons butter
• 2 (10.75 ounce) cans condensed cream of chicken soup
• 1 onion, finely diced
• 2 (10 ounce) packages refrigerated biscuit dough, torn into pieces 📝▪︎ Place the chicken, butter, soup, and onion in a slow cooker, and fill with enough water to cover.
▪︎ Cover, and cook for 5 to 6 hours on High. About 30 minutes before serving, place the torn biscuit dough in the slow cooker. Cook until the dough is no longer raw in the center.
␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣*
input
和target
文本上的拆分示例
对于每个序列,我们需要复制并移动它,以形成input
和target
文本。例如,假设sequence_length
是4
,我们的文本是Hello
。输入序列是Hell
,目标序列是ello
。
***def** **split_input_target**(recipe):
input_text = recipe[:-1]
target_text = recipe[1:]
**return** input_text, target_textdataset_targeted = dataset.map(split_input_target)print(dataset_targeted)*
➔输出:
*<MapDataset shapes: ((2000,), (2000,)), types: (tf.int32, tf.int32)>*
您可能会从上面的行中注意到,现在数据集中的每个示例都由两个元组组成:输入和目标。让我们打印一个例子:
***for** input_example, target_example **in** dataset_targeted.take(1):
print('Input sequence size:', repr(len(input_example.numpy())))
print('Target sequence size:', repr(len(target_example.numpy())))
print()
input_stringified = tokenizer.sequences_to_texts([input_example.numpy()[:50]])[0]
target_stringified = tokenizer.sequences_to_texts([target_example.numpy()[:50]])[0]
print('Input: ', repr(''.join(input_stringified)))
print('Target: ', repr(''.join(target_stringified)))*
➔输出:
*Input sequence size: 2000
Target sequence size: 2000Input: '📗 S l o w C o o k e r C h i c k e n a n d D u m p l i n g s \n \n 🥕 \n \n • 4 s k i n l e'
Target: ' S l o w C o o k e r C h i c k e n a n d D u m p l i n g s \n \n 🥕 \n \n • 4 s k i n l e s'*
RNN 将这些向量的每个索引作为一个时间步长进行处理。对于时间步长0
的输入,模型接收📗
的索引,并尝试预测`` `(一个空格字符)的索引作为下一个字符。在下一个时间步,它做同样的事情,但是 RNN 除了考虑当前输入字符外,还考虑前一步的上下文。
***for** i, (input_idx, target_idx) **in** enumerate(zip(input_example[:10], target_example[:10])):
print('Step {:2d}'.format(i + 1))
print(' input: {} ({:s})'.format(input_idx, repr(tokenizer.sequences_to_texts([[input_idx.numpy()]])[0])))
print(' expected output: {} ({:s})'.format(target_idx, repr(tokenizer.sequences_to_texts([[target_idx.numpy()]])[0])))*
➔输出:
*Step 1
input: 51 ('📗')
expected output: 1 (' ')
Step 2
input: 1 (' ')
expected output: 33 ('S')
Step 3
input: 33 ('S')
expected output: 10 ('l')
Step 4
input: 10 ('l')
expected output: 5 ('o')
Step 5
input: 5 ('o')
expected output: 23 ('w')
Step 6
input: 23 ('w')
expected output: 1 (' ')
Step 7
input: 1 (' ')
expected output: 35 ('C')
Step 8
input: 35 ('C')
expected output: 5 ('o')
Step 9
input: 5 ('o')
expected output: 5 ('o')
Step 10
input: 5 ('o')
expected output: 25 ('k')*
将数据集分成几批
我们在数据集中有~100k
个食谱,每个食谱有两个由2000
字符组成的元组。
*print(dataset_targeted)*
➔输出:
*<MapDataset shapes: ((2000,), (2000,)), types: (tf.int32, tf.int32)>*
让我们打印常量值:
*print('TOTAL_RECIPES_NUM: ', TOTAL_RECIPES_NUM)
print('MAX_RECIPE_LENGTH: ', MAX_RECIPE_LENGTH)
print('VOCABULARY_SIZE: ', VOCABULARY_SIZE)*
➔输出:
*TOTAL_RECIPES_NUM: 100212
MAX_RECIPE_LENGTH: 2000
VOCABULARY_SIZE: 176*
如果我们在训练过程中将完整的数据集提供给模型,然后尝试一次对所有示例进行反向传播,我们可能会耗尽内存,并且每个训练时期可能需要太长时间来执行。为了避免这种情况,我们需要将数据集分成几批。
**# Batch size.*
BATCH_SIZE = 64*# Buffer size to shuffle the dataset (TF data is designed to work*
*# with possibly infinite sequences, so it doesn't attempt to shuffle*
*# the entire sequence in memory. Instead, it maintains a buffer in*
*# which it shuffles elements).*
SHUFFLE_BUFFER_SIZE = 1000dataset_train = dataset_targeted \
*# Shuffling examples first.*
.shuffle(SHUFFLE_BUFFER_SIZE) \
*# Splitting examples on batches.*
.batch(BATCH_SIZE, drop_remainder=**True**) \
*# Making a dataset to be repeatable (it will never ends).*
.repeat()print(dataset_train)*
➔输出:
*<RepeatDataset shapes: ((64, 2000), (64, 2000)), types: (tf.int32, tf.int32)>*
从上面的行中,您可能会注意到我们的数据集现在由相同的两个2000
字符元组组成,但是现在它们被64
分组到批处理中。
***for** input_text, target_text **in** dataset_train.take(1):
print('1st batch: input_text:', input_text)
print()
print('1st batch: target_text:', target_text)*
➔输出:
*1st batch: input_text: tf.Tensor(
[[ 51 1 54 ... 165 165 165]
[ 51 1 64 ... 165 165 165]
[ 51 1 44 ... 165 165 165]
...
[ 51 1 69 ... 165 165 165]
[ 51 1 55 ... 165 165 165]
[ 51 1 70 ... 165 165 165]], shape=(64, 2000), dtype=int32)1st batch: target_text: tf.Tensor(
[[ 1 54 4 ... 165 165 165]
[ 1 64 5 ... 165 165 165]
[ 1 44 6 ... 165 165 165]
...
[ 1 69 3 ... 165 165 165]
[ 1 55 3 ... 165 165 165]
[ 1 70 2 ... 165 165 165]], shape=(64, 2000), dtype=int32)*
建立模型
我们将使用 tf.keras.Sequential 来定义模型。在本实验中,我们将使用以下图层类型:
- TF . keras . layers . embedding—输入层(一个可训练的查找表,将每个字符的数字映射到一个具有
embedding_dim
维度的向量), - tf.keras.layers.LSTM —一种大小为
units=rnn_units
的 RNN(这里也可以使用 GRU 图层), - TF . keras . layers . dense—输出层,带有
VOCABULARY_SIZE
输出。
弄清楚嵌入层是如何工作的
让我们快速迂回一下,看看嵌入层是如何工作的。它接受几个字符索引序列(批处理)作为输入。它将每个序列的每个字符编码成一个长度为tmp_embedding_size
的向量。
*tmp_vocab_size = 10
tmp_embedding_size = 5
tmp_input_length = 8
tmp_batch_size = 2tmp_model = tf.keras.models.Sequential()
tmp_model.add(tf.keras.layers.Embedding(
input_dim=tmp_vocab_size,
output_dim=tmp_embedding_size,
input_length=tmp_input_length
))
*# The model will take as input an integer matrix of size (batch, input_length).*
*# The largest integer (i.e. word index) in the input should be no larger than 9 (tmp_vocab_size).*
*# Now model.output_shape == (None, 10, 64), where None is the batch dimension.*
tmp_input_array = np.random.randint(
low=0,
high=tmp_vocab_size,
size=(tmp_batch_size, tmp_input_length)
)
tmp_model.compile('rmsprop', 'mse')
tmp_output_array = tmp_model.predict(tmp_input_array)print('tmp_input_array shape:', tmp_input_array.shape)
print('tmp_input_array:')
print(tmp_input_array)
print()
print('tmp_output_array shape:', tmp_output_array.shape)
print('tmp_output_array:')
print(tmp_output_array)*
➔输出:
*tmp_input_array shape: (2, 8)
tmp_input_array:
[[2 4 7 5 1 6 9 7]
[3 6 8 1 4 0 1 2]]tmp_output_array shape: (2, 8, 5)
tmp_output_array:
[[[-0.02229502 -0.02800617 -0.0120693 -0.01681594 -0.00650246]
[-0.03046973 -0.03920818 0.04956308 0.04417323 -0.00446874]
[-0.0215276 0.01532575 -0.02229529 0.02834387 0.02725342]
[ 0.04567988 0.0141306 0.00877035 -0.02601192 0.00380837]
[ 0.02969306 0.02994296 -0.00233263 0.00716375 -0.00847433]
[ 0.04598364 -0.00704358 -0.01386416 0.01195388 -0.00309662]
[-0.00137572 0.01275543 -0.02348721 -0.04825885 0.00527108]
[-0.0215276 0.01532575 -0.02229529 0.02834387 0.02725342]] [[ 0.01082945 0.03824175 -0.00450991 -0.02865709 0.02502238]
[ 0.04598364 -0.00704358 -0.01386416 0.01195388 -0.00309662]
[ 0.02275398 0.03806095 -0.03491788 0.04705564 0.00167596]
[ 0.02969306 0.02994296 -0.00233263 0.00716375 -0.00847433]
[-0.03046973 -0.03920818 0.04956308 0.04417323 -0.00446874]
[-0.02909902 0.04426369 0.00150937 0.04579213 0.02559013]
[ 0.02969306 0.02994296 -0.00233263 0.00716375 -0.00847433]
[-0.02229502 -0.02800617 -0.0120693 -0.01681594 -0.00650246]]]*
LSTM 模型
让我们组装模型。
ℹ️您可以使用 TensorFlow 文档中的 RNN 笔记本来检查文本生成,以了解有关模型组件的更多详细信息。
***def** **build_model**(vocab_size, embedding_dim, rnn_units, batch_size):
model = tf.keras.models.Sequential() model.add(tf.keras.layers.Embedding(
input_dim=vocab_size,
output_dim=embedding_dim,
batch_input_shape=[batch_size, **None**]
)) model.add(tf.keras.layers.LSTM(
units=rnn_units,
return_sequences=**True**,
stateful=**True**,
recurrent_initializer=tf.keras.initializers.GlorotNormal()
)) model.add(tf.keras.layers.Dense(vocab_size))
**return** modelmodel = build_model(
vocab_size=VOCABULARY_SIZE,
embedding_dim=256,
rnn_units=1024,
batch_size=BATCH_SIZE
)model.summary()*
➔输出:
*Model: "sequential_13"
_________________________________________________________________
Layer (type) Output Shape Param #
=================================================================
embedding_13 (Embedding) (64, None, 256) 45056
_________________________________________________________________
lstm_9 (LSTM) (64, None, 1024) 5246976
_________________________________________________________________
dense_8 (Dense) (64, None, 176) 180400
=================================================================
Total params: 5,472,432
Trainable params: 5,472,432
Non-trainable params: 0
_________________________________________________________________*
让我们来看看这个模型:
*tf.keras.utils.plot_model(
model,
show_shapes=**True**,
show_layer_names=**True**,
to_file='model.png'
)*
➔输出:
网络架构(代码生成的映像)
对于每个字符,模型查找嵌入,以嵌入作为输入运行 LSTM 一个时间步长,并应用密集层来生成预测下一个字符的对数似然的逻辑:
图片来源:用 RNN 笔记本生成文本。
上图展示了 GRU 网络,但是你可以很容易地用 LSTM 代替 GRU。
在训练前尝试模型
让我们试验一下未经训练的模型,看看它的界面(我们需要什么样的输入,我们将有什么样的输出),并看看在训练之前模型预测了什么:
***for** input_example_batch, target_example_batch **in** dataset_train.take(1):
example_batch_predictions = model(input_example_batch)
print(example_batch_predictions.shape, "# (batch_size, sequence_length, vocab_size)")*
➔输出:
*(64, 2000, 176) # (batch_size, sequence_length, vocab_size)*
为了从模型中获得实际的预测,我们需要从输出分布中取样,以获得实际的字符索引。这种分布是由字符词汇表上的逻辑定义的。
*print('Prediction for the 1st letter of the batch 1st sequense:')
print(example_batch_predictions[0, 0])*
➔输出:
*Prediction for the 1st letter of the batch 1st sequense:
tf.Tensor(
[-9.0643829e-03 -1.9503604e-03 9.3381782e-04 3.7442446e-03
-2.0541784e-03 -7.4054599e-03 -7.1884273e-03 2.6014952e-03
4.8721582e-03 3.0045470e-04 2.6016519e-04 -4.1374690e-03
5.3856964e-03 2.6284808e-03 -5.6002503e-03 2.6019611e-03
-1.9491187e-03 -3.1097094e-04 6.3465843e-03 1.4640498e-03
2.4560774e-03 -3.1256995e-03 1.4104056e-03 2.5478401e-04
5.4266443e-03 -4.1188141e-03 3.6904984e-03 -5.8337618e-03
3.6372752e-03 -3.1899021e-05 3.2178329e-03 1.5033322e-04
5.2770867e-04 -8.1920059e-04 -2.2364906e-03 -2.3271297e-03
4.4109682e-03 4.2381673e-04 1.0532180e-03 -1.4208974e-03
-3.2446394e-03 -4.5869066e-03 4.3250201e-04 -4.3490473e-03
3.7889536e-03 -9.2122913e-04 7.8936084e-04 -9.7079907e-04
1.7070504e-03 -2.5260956e-03 6.7904620e-03 1.5470090e-03
-9.4337866e-04 -1.5072266e-03 6.8939931e-04 -1.0795534e-03
-3.1912089e-03 2.3665284e-03 1.7737487e-03 -2.3504677e-03
-6.8649277e-04 9.6421910e-04 -4.1204207e-03 -3.8750230e-03
1.9077851e-03 4.7145790e-05 -2.9846188e-03 5.8050319e-03
-5.6210475e-04 -2.5910907e-04 5.2890396e-03 -5.8653783e-03
-6.0040038e-06 2.3905798e-03 -2.9405006e-03 2.0132761e-03
-3.5594390e-03 4.0282350e-04 4.7719614e-03 -2.4438011e-03
-1.1028582e-03 2.0007135e-03 -1.6961874e-03 -4.2196750e-03
-3.5689408e-03 -4.1934610e-03 -8.5307617e-04 1.5773368e-04
-1.4612130e-03 9.5826073e-04 4.0543079e-04 -2.3562380e-04
-1.5394683e-03 3.6650903e-03 3.5997448e-03 2.2390878e-03
-6.8982318e-04 1.4068574e-03 -2.0531749e-03 -1.5443334e-03
-1.8235333e-03 -3.2099178e-03 1.6660831e-03 1.2230751e-03
3.8084832e-03 6.9559496e-03 5.7684043e-03 3.1751506e-03
7.4234616e-04 1.1971325e-04 -2.7798198e-03 2.1485630e-03
4.0362971e-03 6.4410735e-05 1.7432809e-03 3.2334479e-03
-6.1469898e-03 -2.2205685e-03 -1.0864032e-03 -2.0876178e-07
2.3065242e-03 -1.5816523e-03 -2.1492387e-03 -4.4033155e-03
1.1003019e-03 -9.7132073e-04 -6.3941808e-04 3.0277157e-03
2.9096641e-03 -2.4778468e-03 -2.9532036e-03 7.7463314e-04
2.7473709e-03 -7.6333171e-04 -8.1811845e-03 -1.3959130e-03
3.2840301e-03 6.0461317e-03 -1.3022404e-04 -9.4000692e-04
-2.0096730e-04 3.3895797e-03 2.9710699e-03 1.9046264e-03
2.5092331e-03 -2.0799250e-04 -2.2211851e-04 -3.4621451e-05
1.9962704e-03 -2.3159904e-03 2.9832027e-03 3.3852295e-03
3.4411502e-04 -1.9019389e-03 -3.6734296e-04 -1.4232489e-03
2.6938838e-03 -2.8015859e-03 -5.7366290e-03 8.0239226e-04
-6.2909431e-04 1.1508183e-03 -1.5899434e-04 -5.9326587e-04
-4.1618512e-04 5.2454891e-03 1.2823739e-03 -1.7550631e-03
-3.0120560e-03 -3.8433261e-03 -9.6873334e-04 1.9963509e-03
1.8154597e-03 4.7434499e-03 1.7146189e-03 1.1544267e-03], shape=(176,), dtype=float32)*
对于每个输入字符,example_batch_predictions
数组包含下一个字符可能是什么的概率向量。如果向量中位置15
处的概率是0.3
,位置25
处的概率是1.1
,这意味着我们最好选择索引为25
的字符作为下一个字符。
由于我们希望我们的网络生成不同的配方(即使对于相同的输入),我们不能只选择最大概率值。在这种情况下,我们将以网络一遍又一遍地预测相同的配方而告终。我们要做的是通过使用TF . random . categorial()函数从预测中提取样本(就像上面打印的一样)。会给网络带来一些模糊性。例如,假设我们有字符H
作为输入,然后,通过从分类分布中取样,我们的网络不仅可以预测单词He
,还可以预测单词Hello
和Hi
等。
了解tf.random.categorical
如何运作
**# logits is 2-D Tensor with shape [batch_size, num_classes].*
*# Each slice [i, :] represents the unnormalized log-probabilities for all classes.*
*# In the example below we say that the probability for class "0"*
*# (element with index 0) is low but the probability for class "2" is much higher.*
tmp_logits = [
[-0.95, 0, 0.95],
];*# Let's generate 5 samples. Each sample is a class index. Class probabilities*
*# are being taken into account (we expect to see more samples of class "2").*
tmp_samples = tf.random.categorical(
logits=tmp_logits,
num_samples=5
)print(tmp_samples)*
➔输出:
*tf.Tensor([[2 1 2 2 1]], shape=(1, 5), dtype=int64)*
从 LSTM 预测中取样
*sampled_indices = tf.random.categorical(
logits=example_batch_predictions[0],
num_samples=1
)sampled_indices = tf.squeeze(
input=sampled_indices,
axis=-1
).numpy()sampled_indices.shape*
➔输出:
*(2000,)*
让我们来看看菜谱前100
个字符的一些预测示例:
*sampled_indices[:100]*
➔输出:
*array([ 64, 21, 91, 126, 170, 42, 146, 54, 125, 164, 60, 171, 9,
87, 129, 28, 146, 103, 41, 101, 147, 3, 134, 171, 8, 170,
105, 5, 44, 173, 5, 105, 17, 138, 165, 32, 88, 96, 145,
83, 33, 65, 172, 162, 8, 29, 147, 58, 81, 153, 150, 56,
156, 38, 144, 134, 13, 40, 17, 50, 27, 35, 39, 112, 63,
139, 151, 133, 68, 29, 91, 2, 70, 112, 135, 31, 26, 156,
118, 71, 49, 104, 75, 27, 164, 41, 117, 124, 18, 137, 59,
160, 158, 119, 173, 50, 78, 45, 121, 118])*
我们现在可以看到我们未经训练的模型实际上预测了什么:
*print('Input:\n', repr(''.join(tokenizer.sequences_to_texts([input_example_batch[0].numpy()[:50]]))))
print()
print('Next char prediction:\n', repr(''.join(tokenizer.sequences_to_texts([sampled_indices[:50]]))))*
➔输出:
*Input:
'📗 R e s t a u r a n t - S t y l e C o l e s l a w I \n \n 🥕 \n \n • 1 ( 1 6 o u n c e ) p'Next char prediction:
'H . î ⁄ ă ( “ I º Â 8 ̀ s % ù y “ © 0 ’ ‧ a ì ̀ r ă + o A € o + m × ␣ ︎ ñ ç ‱ ! S : ⅞ ´ r 2 ‧ D Q Á'*
正如您可能看到的,该模型提出了一些无意义的预测,但这是因为它尚未经过训练。
训练模型
我们希望训练我们的模型来生成尽可能与真实食谱相似的食谱。我们将使用数据集中的所有数据进行训练。在这种情况下,不需要提取测试或验证子集。
附加一个优化器和一个损失函数
我们将使用TF . keras . optimizer . Adam优化器和TF . keras . loss . sparse _ categorial _ cross entropy()损失函数来训练模型:
**# An objective function.*
*# The function is any callable with the signature scalar_loss = fn(y_true, y_pred).*
**def** **loss**(labels, logits):
entropy = tf.keras.losses.sparse_categorical_crossentropy(
y_true=labels,
y_pred=logits,
from_logits=**True**
)
**return** entropyexample_batch_loss = loss(target_example_batch, example_batch_predictions)print("Prediction shape: ", example_batch_predictions.shape, " # (batch_size, sequence_length, vocab_size)")
print("scalar_loss.shape: ", example_batch_loss.shape)
print("scalar_loss: ", example_batch_loss.numpy().mean())*
➔输出:
*Prediction **shape: ** (64, 2000, 176) *# (batch_size, sequence_length, vocab_size)*
**scalar_loss.shape: ** (64, 2000)
**scalar_loss: ** 5.1618285*
让我们最后编译这个模型:
*adam_optimizer = tf.keras.optimizers.Adam(learning_rate=0.001)model.compile(
optimizer=adam_optimizer,
loss=loss
)*
配置回调
提前停止回调
对于模型训练过程,我们可以配置一个TF . keras . callbacks . early stopping回调。如果模型在几个时期内没有改善,它将自动停止训练:
*early_stopping_callback = tf.keras.callbacks.EarlyStopping(
patience=5,
monitor='loss',
restore_best_weights=**True**,
verbose=1
)*
模型检查点回调
让我们还配置一个TF . keras . callbacks . model check point检查点,它将允许我们定期将训练好的权重保存到文件中,以便我们可以在以后从权重中恢复模型。
**# Create a checkpoints directory.*
checkpoint_dir = 'tmp/checkpoints'
os.makedirs(checkpoint_dir, exist_ok=**True**)checkpoint_prefix = os.path.join(checkpoint_dir, 'ckpt_{epoch}')
checkpoint_callback=tf.keras.callbacks.ModelCheckpoint(
filepath=checkpoint_prefix,
save_weights_only=**True**
)*
执行培训
让我们为500
个时期训练我们的模型,每个时期有1500
个步骤。对于每个历元步,将取出一批64
配方,并对那些长度为2000
的64
配方逐步执行梯度下降。
如果您正在试验训练参数,可能有必要将历元数减少到,比如说20
以及每个历元的步数,然后看看模型在该条件下的表现。如果模型提高了性能,您可以向训练过程添加更多数据(步骤和时期)。当你调整参数时,它可能会节省你一些时间。
*EPOCHS = 500
INITIAL_EPOCH = 1
STEPS_PER_EPOCH = 1500print('EPOCHS: ', EPOCHS)
print('INITIAL_EPOCH: ', INITIAL_EPOCH)
print('STEPS_PER_EPOCH: ', STEPS_PER_EPOCH)*
➔输出:
*EPOCHS: 500
INITIAL_EPOCH: 1
STEPS_PER_EPOCH: 1500*
让我们开始培训:
*history = model.fit(
x=dataset_train,
epochs=EPOCHS,
steps_per_epoch=STEPS_PER_EPOCH,
initial_epoch=INITIAL_EPOCH,
callbacks=[
checkpoint_callback,
early_stopping_callback
]
)*# Saving the trained model to file (to be able to re-use it later).*
model_name = 'recipe_generation_rnn_raw.h5'
model.save(model_name, save_format='h5')*
可视化培训进度
***def** **render_training_history**(training_history):
loss = training_history.history['loss'] plt.title('Loss')
plt.xlabel('Epoch')
plt.ylabel('Loss')
plt.plot(loss, label='Training set')
plt.legend()
plt.grid(linestyle='--', linewidth=1, alpha=0.5)
plt.show()render_training_history(history)*
➔输出:
培训流程(代码生成的图像)
ℹ️ 在上面的图表中,只显示了前 10 个时期。
从图表中我们可以看出,在培训期间,模特的表现越来越好。这意味着该模型学习预测下一个字符,使得最终序列看起来类似于一些真实的食谱文本。
生成配方
从最新的检查点恢复模型
为了使这个预测步骤简单,我们将恢复保存的模型,并以批处理大小 1 重新构建它。由于 RNN 状态是从一个时间步长传递到另一个时间步长的,该模型一旦建立就只接受固定的批量大小。为了使用不同的batch_size
运行模型,我们需要重建模型并从检查点恢复权重。
*tf.train.latest_checkpoint(checkpoint_dir)*
➔输出:
*'tmp/checkpoints/ckpt_1'*
让我们重建批量为1
的模型,并向其加载训练好的权重:
*simplified_batch_size = 1model_simplified = build_model(vocab_size, embedding_dim, rnn_units, simplified_batch_size)
model_simplified.load_weights(tf.train.latest_checkpoint(checkpoint_dir))
model_simplified.build(tf.TensorShape([simplified_batch_size, **None**]))model_simplified.summary()*
➔输出:
*Model: "sequential_6"
_________________________________________________________________
Layer (type) Output Shape Param #
=================================================================
embedding_6 (Embedding) (1, None, 256) 45056
_________________________________________________________________
lstm_5 (LSTM) (1, None, 1024) 5246976
_________________________________________________________________
dense_5 (Dense) (1, None, 176) 180400
=================================================================
Total params: 5,472,432
Trainable params: 5,472,432
Non-trainable params: 0
_________________________________________________________________*
让我们仔细检查输入形状是否简化了:
*model_simplified.input_shape*
➔输出:
*(1, None)*
预测循环
为了使用我们训练过的模型来生成配方,我们需要实现一个所谓的预测循环。下面的代码块使用循环生成文本:
- 它首先选择一个起始字符串,初始化 RNN 状态,并设置要生成的字符数。
- 它使用起始字符串和 RNN 状态获得下一个字符的预测分布。
- 然后,它使用分类分布来计算预测字符的索引。它使用这个预测的字符作为模型的下一个输入。
- 模型返回的 RNN 状态被反馈到模型中,因此它现在有更多的上下文,而不是只有一个字符。预测下一个字符后,修改后的 RNN 状态再次反馈到模型中,这是它从先前预测的字符中获得更多上下文时的学习方式。
图片来源:用 RNN 笔记本生成文本。
这里的temperature
参数定义了生成的配方有多模糊或者有多出乎意料。温度越低,文本越容易预测。更高的温度导致更令人惊讶的文本。你需要尝试找到最佳设置。我们将在下面不同的温度下做一些实验。
***def** **generate_text**(model, start_string, num_generate = 1000, temperature=1.0):
*# Evaluation step (generating text using the learned model)*
padded_start_string = STOP_WORD_TITLE + start_string *# Converting our start string to numbers (vectorizing).*
input_indices = np.array(tokenizer.texts_to_sequences([padded_start_string])) *# Empty string to store our results.*
text_generated = [] *# Here batch size == 1.*
model.reset_states()
**for** char_index **in** range(num_generate):
predictions = model(input_indices)
*# remove the batch dimension*
predictions = tf.squeeze(predictions, 0) *# Using a categorical distribution to predict the character returned by the model.*
predictions = predictions / temperature
predicted_id = tf.random.categorical(
predictions,
num_samples=1
)[-1, 0].numpy() *# We pass the predicted character as the next input to the model*
*# along with the previous hidden state.*
input_indices = tf.expand_dims([predicted_id], 0)
next_character = tokenizer.sequences_to_texts(input_indices.numpy())[0] text_generated.append(next_character) **return** (padded_start_string + ''.join(text_generated))*
为预测回路计算出合适的温度
现在,让我们使用generate_text()
来实际生成一些新的食谱。generate_combinations()
功能会检查第一个配方字母和温度的所有可能组合。它生成56
不同的组合来帮助我们弄清楚模型的表现以及使用什么温度更好。
***def** **generate_combinations**(model):
recipe_length = 1000
try_letters = ['', '\n', 'A', 'B', 'C', 'O', 'L', 'Mushroom', 'Apple', 'Slow', 'Christmass', 'The', 'Banana', 'Homemade']
try_temperature = [1.0, 0.8, 0.4, 0.2] **for** letter **in** try_letters:
**for** temperature **in** try_temperature:
generated_text = generate_text(
model,
start_string=letter,
num_generate = recipe_length,
temperature=temperature
)
print(f'Attempt: "{letter}" + {temperature}')
print('-----------------------------------')
print(generated_text)
print('\n\n')*
为了避免这篇文章太长,下面将只列出一些56
的组合。
*generate_combinations(model_simplified)*
➔输出:
*Attempt: "A" + 1.0
-----------------------------------
📗 Azzeric Sweet Potato Puree🥕• 24 large baking potatoes, such as Carn or Marinara or 1 (14-ounce) can pot wine
• 1/4 pound unsalted butter, cut into small pieces
• 1/2 cup coarsely chopped scallions📝▪︎ Bring a large pot of water to a boil, place a large nonstick skillet over medium-high heat, add All Naucocal Volves. Reduce heat to medium and cook the potatoes until just cooked through, bubbles before adding the next layer, about 10 to 12 minutes. Remove ground beans and reserve. Reserve the crumb mixture for about 6 greased. Let cool 2 minutes. Strain soak into a glass pitcher. Let cool in ice. Add short-goodfish to the batter and stir to dissolve. Pour in the cheese mixture and whisk until smooth. Set aside for 20 seconds more. Remove dumplings and cheese curds. Spread 1/3 cup of the mixture on each circle for seal ballo. Transfer mixture into a greased 9-by-11-inch baking dish and chill for 20 minutes.
▪︎ Bake, covered, for 30 minutes. Serve warm.
␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣Attempt: "A" + 0.4
-----------------------------------
📗 Apricot "Cookie" Cakes🥕• 1 cup all-purpose flour
• 1 cup corn flour
• 1 cup sugar
• 1 tablespoon baking powder
• 1 teaspoon salt
• 1 teaspoon ground cinnamon
• 1 cup grated Parmesan
• 1 cup pecans, chopped
• 1/2 cup chopped pecans
• 1/2 cup raisins📝▪︎ Preheat oven to 350 degrees F.
▪︎ Butter and flour a 9 by 13-inch baking dish. In a medium bowl, whisk together the flour, sugar, baking powder, baking soda and salt. In a small bowl, whisk together the eggs, sugar, and eggs. Add the flour mixture to the butter mixture and mix until just combined. Stir in the raisins and pecans and transfer to the prepared pan. Spread the batter over the top of the crust. Bake for 15 minutes. Reduce the oven temperature to 350 degrees F, and bake until the cupcakes are set and the top is golden brown, about 20 minutes more. Transfer the cake to a wire rack to cool to room temperature. Refrigerate until ready to serve.
␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣Attempt: "A" + 0.2
-----------------------------------
📗 Alternative to the Fondant🥕• 1 cup sugar
• 1 cup water
• 1 cup heavy cream
• 1 teaspoon vanilla extract
• 1/2 cup heavy cream
• 1/2 cup heavy cream
• 1 teaspoon vanilla extract
• 1/2 cup chopped pecans📝▪︎ In a saucepan over medium heat, combine the sugar, sugar, and corn syrup. Cook over medium heat until the sugar is dissolved. Remove from the heat and stir in the vanilla. Refrigerate until cold. Stir in the chocolate chips and the chocolate chips. Serve immediately.
␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣Attempt: "B" + 0.4
-----------------------------------
📗 Battered French Toast with Bacon, Bacon, and Caramelized Onions and Pecorino🥕• 1/2 pound squid (shredded carrots)
• 1 small onion, diced
• 1 small green pepper, seeded and cut into strips
• 1 red bell pepper, stemmed, seeded and cut into 1/4-inch dice
• 1 small onion, chopped
• 1 green bell pepper, chopped
• 1 cup chicken stock
• 1 cup heavy cream
• 1/2 cup shredded sharp Cheddar
• 1 teaspoon ground cumin
• 1 teaspoon salt
• 1 teaspoon freshly ground black pepper📝▪︎ Preheat the oven to 350 degrees F.
▪︎ For the bacon mixture: In a large bowl, combine the cheese, sour cream, mustard, salt, pepper, and hot sauce. Stir together and mix well. Fold in the milk and set aside.
▪︎ For the filling: In a large bowl, mix the flour and salt and pepper, to taste. Add the beaten eggs and mix to combine. Set aside.
▪︎ For the topping: Mix the cream cheese with the mayonnaise, salt and pepper in a medium bowl. Add the chicken and toss to coat the other side. Transfer the mixture to the preparedAttempt: "C" + 1.0
-----------------------------------
📗 Crema battered Salmon🥕• 1 cup fresh cranberries (from 4 tablespoons left of 4 egg whites)
• 3 teaspoons sugar
• 1 tablespoon unsalted butter
• 2 tablespoons truffle oil
• Coarse salt
• Freshly ground black pepper📝▪︎ Place cornmeal in a small serving bowl, and combine it. Drizzle milk over the plums and season with salt and pepper. Let stand for about 5 minutes, until firm. Serve immediately.
␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣Attempt: "C" + 0.8
-----------------------------------
📗 Classic Iseasteroles🥕• 3 cups milk
• 3/4 cup coconut milk
• 1/2 cup malted maple syrup
• 1/2 teaspoon salt
• 3 cups sugar
• 4 1-inch strawberries, sliced into 1/4-inch pieces
• 1/2 teaspoon ground cinnamon📝▪︎ Place the cherries in a small saucepan; sprinkle with the sugar. Bring to a simmer over medium-low heat, then remove from the heat. Let stand until the coconut fluffy, about 15 to 20 minutes. Drain the coconut oil in a stream, whisking until combined. Add the cream, espresso and cocoa powder and stir to combine. Cover and refrigerate until ready to serve. Makes 10 to 12 small springs in the same fat from the surface of the bowl, which using paper colors, and freeze overnight.
▪︎ Meanwhile, combine the cream, sugar, vanilla and salt in a medium saucepan. Cook over medium heat until the sugar dissolves and the sugar melts and begins to boil, about 5 minutes. Remove from the heat and stir in the vanilla.
▪︎ To serve, carefully remove the pops from the casserole and put them inAttempt: "C" + 0.4
-----------------------------------
📗 Cinnamon Corn Cakes with Coconut Flour and Saffron Sauce🥕• 3 cups shredded sharp Cheddar
• 1 cup grated Parmesan
• 2 cups shredded sharp Cheddar
• 1 cup grated Parmesan
• 1 cup shredded part-skim mozzarella cheese
• 1 cup grated Parmesan
• 1 cup grated Parmesan
• 1 cup grated Parmesan
• 1 teaspoon kosher salt
• 1/2 teaspoon freshly ground black pepper📝▪︎ Preheat the oven to 400 degrees F. Line a baking sheet with a silpat and preheat the oven to 350 degrees F.
▪︎ In a large bowl, combine the masa harina, cumin, cayenne, and salt and pepper. Dredge the pasta in the flour and then dip in the egg mixture, then dip in the eggs, then dip in the egg mixture and then dredge in the breadcrumbs. Place the breaded cheese on a sheet tray. Bake until the crust is golden brown and the filling is bubbling, about 25 to 30 minutes. Remove from the oven and serve hot.
␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣ Attempt: "L" + 0.4
-----------------------------------
📗 Lighted Flan with Chocolate and Pecans🥕• 2 cups milk
• 1 cup sugar
• 1 teaspoon vanilla extract
• 1 cup heavy cream
• 1/2 cup heavy cream
• 1 tablespoon powdered sugar
• 1 teaspoon vanilla extract
• 1/2 cup heavy cream
• 1/2 teaspoon ground cinnamon
• 1/2 teaspoon ground nutmeg
• 1/2 cup chopped pecans📝▪︎ Watch how to make this recipe.
▪︎ In a small saucepan, combine the sugar, salt, and a pinch of salt. Cook over medium heat, stirring occasionally, until the sugar has dissolved. Remove from the heat and set aside to cool. Remove the cherries from the refrigerator and place in the freezer for 1 hour.
▪︎ In a blender, combine the milk, sugar, vanilla, salt and water. Blend until smooth. Pour the mixture into a 9-by-13-inch glass baking dish and set aside.
▪︎ In a small saucepan, combine the remaining 2 cups sugar, the vanilla, and 2 cups water. Bring the mixture to a boil, and then reduce the heat to low. Cook until the sugar is dissolved, about 5 minutes. Remove from the heat anAttempt: "L" + 0.2
-----------------------------------
📗 Lighted Fondanta with Chocolate and Cream Cheese Frosting🥕• 1 cup heavy cream
• 1 tablespoon sugar
• 1 tablespoon vanilla extract
• 1 teaspoon vanilla extract
• 1 cup heavy cream
• 1 cup heavy cream
• 1/2 cup sugar
• 1 teaspoon vanilla extract
• 1 teaspoon vanilla extract
• 1/2 cup chopped pistachios📝▪︎ Preheat the oven to 350 degrees F.
▪︎ In a large bowl, combine the cream cheese, sugar, eggs, vanilla, and salt. Stir until smooth. Pour the mixture into the prepared baking dish. Sprinkle with the remaining 1/2 cup sugar and bake for 15 minutes. Reduce the heat to 350 degrees F and bake until the crust is golden brown, about 15 minutes more. Remove from the oven and let cool completely. Spread the chocolate chips on the parchment paper and bake until the chocolate is melted and the top is golden brown, about 10 minutes. Set aside to cool.
▪︎ In a medium bowl, whisk together the egg yolks, sugar, and vanilla until smooth. Stir in the cream and continue to beat until the chocolateAttempt: "Mushroom" + 1.0
-----------------------------------
📗 Mushroom and Bacon Soup with Jumbo Sugar Coating🥕• 2 tablespoons vegetable oil
• 1 2/3 pounds red cabbage, shredded, about 4 cups of excess pasted dark ends of fat, and pocked or firm
• 2 red bell peppers, cored, seeded and diced
• 1 poblano pepper, chopped
• 3 medium carrots, finely chopped
• 1/2 medium pinch saffron
• 4 cups water
• 2 cups mushrooms or 1/2 cup frozen Sojo Bean red
• Salt and freshly ground black pepper
• 1 pound andouille sausage
• 1 gallon vegetable broth
• Chopped fresh parsley, cilantro leaves, for garnish📝▪︎ In a large Dutch oven for gas burner, heat oil over moderate heat. Add the leeks to the pot, scraping the bottom of the skillet. Add the beans and sausage and sprinkle the reserved potatoes with some orange juice cooked sausage (such as The Sauce.) Add roasted vegetables and pinto beans, mozzarella, basil and bamboo shoots. Simmer rice until soup is absorbed, 15 to 20 minutes.
▪︎ Bring another pan of water to a boil and cook shrimp for 5 minutes. While onionsAttempt: "Mushroom" + 0.8
-----------------------------------
📗 Mushrooms with Lentil Stewed Shallots and Tomatoes🥕• 1 tablespoon olive oil
• 3 cloves garlic, smashed
• Kosher salt
• 1 1/2 pounds lean ground turkey
• 1 cup coarsely peeled tart apples
• 2 tablespoons chopped garlic
• 1 teaspoon ground cumin
• 1/2 teaspoon cayenne pepper
• 1 teaspoon chopped fresh thyme
• 3/4 cup chopped fresh basil
• 1/2 small carrot, halved lengthwise and cut into 1/2-inch pieces
• 1 roasted red pepper, halved and sliced vertically diced and separated into rough chops
• 3 tablespoons unsalted butter
• 2 cups shredded mozzarella
• 1/4 cup grated parmesan cheese
• 1/4 cup prepared basil pesto📝▪︎ Stir the olive oil, garlic, thyme and 1 teaspoon salt in a saucepan; bring to a simmer over medium heat. Remove from the heat. Add the basil and toast the soup for 2 minutes.
▪︎ Meanwhile, heat 4 to 4 inches vegetable oil in the skillet over medium-high heat. Add the olive oil, garlic, 1/2 teaspoon salt and 1/2 teaspoon pepper and cook, stirring often, until cooked through, aAttempt: "Mushroom" + 0.4
-----------------------------------
📗 Mushroom Ravioli with Chickpeas and Shiitake Mushrooms and Sun-Dried Tomatoes🥕• 1 pound zucchini
• 1 cup chicken broth
• 1 cup fresh basil leaves
• 1/2 cup chopped fresh basil leaves
• 1/2 cup grated Parmesan
• 1 teaspoon salt
• 1/2 teaspoon freshly ground black pepper
• 1 teaspoon chopped fresh thyme
• 1 teaspoon fresh lemon juice
• 2 cups chicken broth
• 1/2 cup grated Parmesan
• 1/2 cup grated Parmigiano-Reggiano📝▪︎ Preheat oven to 450 degrees F.
▪︎ Place the bread cubes in a large bowl. Add the basil, parsley, olive oil, parsley, thyme, basil, salt and pepper and toss to coat. Spread the mixture out on a baking sheet and bake until the sausages are cooked through, about 20 minutes. Serve immediately.
▪︎ In a small saucepan, bring the chicken stock to a boil. Reduce the heat to low and cook the soup until the liquid is absorbed. Remove from the heat and stir in the parsley, shallots and season with salt and pepper. Serve immediately.
␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣Attempt: "Mushroom" + 0.2
-----------------------------------
📗 Mushroom and Spicy Sausage Stuffing🥕• 1 tablespoon olive oil
• 1 medium onion, chopped
• 2 cloves garlic, minced
• 1 cup frozen peas
• 1 cup frozen peas
• 1/2 cup chopped fresh parsley
• 1/2 cup grated Parmesan
• 1/2 cup grated Parmesan
• 1 teaspoon salt
• 1/2 teaspoon freshly ground black pepper
• 1 cup shredded mozzarella
• 1/2 cup grated Parmesan
• 1 cup shredded mozzarella
• 1 cup shredded mozzarella cheese📝▪︎ Preheat the oven to 350 degrees F.
▪︎ Bring a large pot of salted water to a boil. Add the pasta and cook until al dente, about 6 minutes. Drain and reserve.
▪︎ Meanwhile, heat the olive oil in a large skillet over medium-high heat. Add the shallots and saute until tender, about 3 minutes. Add the garlic and cook for 1 minute. Add the sausage and cook until the shallots are tender, about 3 minutes. Add the sausage and cook until tender, about 2 minutes. Add the garlic and cook, stirring, until the garlic is lightly browned, about 1 minute. Add the sausage and cook until the s*
交互式模型演示
你可以使用🎨 烹饪食谱生成器演示 使用这个模型,输入文本和温度参数,就可以在浏览器中生成一些随机的食谱。
来自机器学习实验的屏幕记录
需要改进的地方
这超出了本文的范围,但是模型仍然有以下问题需要解决:
- 我们需要删除配料部分的重复内容。
- 食谱部分(名称、配料和烹饪步骤)大部分时间是不连贯的,这意味着我们可能会在配料部分看到
mushrooms
,但它们不会在食谱名称或烹饪步骤中提及。
更多更新和新文章请在 Twitter 上关注我