项目创建:第一部分
在本节课中,我们将基于字符级RNN开展另一个项目。在这个项目中,我们将构建一个模型,用于破解加密的文本。
项目简介
现在你已经了解了循环神经网络(RNN)的工作原理,并且在上一章中也完成了一个项目。在本章中,我们将构建一个模型,用于破解使用特定密码算法加密的字符串。
什么是密码(cipher)?
密码是一种用于加密和解密数据的加密算法。在本项目中,我们将使用凯撒密码(Caesar cipher)。它是一种单字母替换密码,即明文中的每个字母都被另一个字母替代,从而形成密文。这是最简单的替换密码形式,通常也被称为移位密码。其基本思想是将每一个字母替换成在字母表中向后(或向前)移动固定位数(0 到 25 之间)的另一个字母。
例如,如果我们对字符串 hello 使用右移 1 位的方式进行加密,得到的密文(即加密后的数据)将是 ifmmp。
请完成下面的小测验来检验你的理解。
现在你已经清楚我们要做什么了,接下来就开始吧。
下载数据集
该数据集包含 10,000 条加密短语及其对应的明文版本。请下载以下两个数据文件并查看内容。
plaintext.txt
cipher.txt
加载数据集
下载完数据集并查看文本文件后,我们接下来加载数据并开始处理。
1import os 2 3def load_data(path): 4 input_file = os.path.join(path) 5 with open(input_file, "r") as f: 6 data = f.read() 7 return data.split('\n') 8 9codes = load_data('cipher.txt') 10plaintext = load_data('plaintext.txt') 11print('数据加载成功!')
运行上述代码后,我们就完成了数据的加载。我们将加密文本存储在变量 codes 中,将原始明文存储在变量 plaintext 中。
你可以使用以下代码查看数据内容:
1print(codes[:3]) 2print(plaintext[:3])
上述语句将打印出数据集中的前三个句子。第一行输出的是密文,第二行输出的是对应的明文。
现在,在继续之前,我们需要明确将要构建的模型类型。
模型概述——字符级 RNN
我们将使用的模型是一个字符级的循环神经网络(RNN),因为凯撒密码是在字符级别上操作的。
字符级 RNN 接收一个整数作为输入,代表某个特定字符,并输出另一个整数。换句话说,我们的模型将以编码(转换为整数形式)作为输入,并输出对应的明文(整数再转换回文本)。为了让模型正常工作,我们需要对数据集进行如下预处理步骤:
- 将每个字符单独分离为数组元素(而不是以整个短语或单词作为数组元素)。
- 对字符进行标记化(tokenization),以便将字母转换为整数,反之亦然。
- 对字符串进行填充(padding),使所有输入和输出都能以矩阵形式表示。
为了更直观地理解这一过程,假设我们的源序列(此处为 codes)或目标序列(此处为 plaintext)如下所示(一个字符串列表):
(图示:原始字符串列表)
由于模型将在字符级别上运行,我们需要将每个字符串拆分为字符列表(这一步由分词器隐式完成):
(图示:拆分为字符)
然后,通过标记化过程将每个字符转换为整数。注意,在之前的课程中我们使用的是词级 RNN,它将每个单词转换为整数;而这里的字符级 RNN 中,分词器会为每个字母分配一个整数,每个单元格代表一个字符而非一个单词。
(图示:字符转为整数)
在之前的课程中我们讨论过填充(padding)操作,这里我们也需要进行填充以构成矩阵。为了将输入转换为矩阵形式,我们需要找到列表中最长的序列,并用 0 填充所有较短的序列。假设本例中“and two”是最长序列,则最终的矩阵如下所示:
(图示:填充后的矩阵)
数据预处理
为了让神经网络能够预测文本数据,首先必须将其转换为模型可理解的形式。像 "dog" 这样的文本数据本质上是一系列 ASCII 字符编码。由于神经网络是由一系列乘法和加法运算组成的,因此输入数据必须是数值形式。
我们将沿用上一章中的方法,将文本数据转换为数字。
1from tensorflow.python.keras.preprocessing.text import Tokenizer 2 3def tokenize(x): 4 x_tk = Tokenizer(char_level=True) 5 x_tk.fit_on_texts(x) 6 return x_tk.texts_to_sequences(x), x_tk 7 8text_sentences = [ 9 'The quick brown fox jumps over the lazy dog .', 10 'By Jove , my quick study of lexicography won a prize .', 11 'This is a short sentence .'] 12 13text_tokenized, text_tokenizer = tokenize(text_sentences) 14 15print(text_tokenizer.word_index) 16 17for sample_i, (sent, token_sent) in enumerate(zip(text_sentences, text_tokenized)): 18 print('x 中的第 {} 个序列'.format(sample_i + 1)) 19 print(' 输入: {}'.format(sent)) 20 print(' 输出: {}'.format(token_sent))
解释:
- 第 1 行:从
Keras库中导入Tokenizer。 - 第 3 到第 7 行:定义了一个函数用于对输入文本进行标记化(即将文本转换为数字)。该函数返回两个结果:一是给定文本的整数表示,二是
Tokenizer对象,后者可用于后续将文本转换为整数。之所以需要返回这个对象,是因为我们希望在其他句子上使用相同的分词器,以确保相同字符始终对应相同的整数表示。 - 第 8 行:定义了一些测试句子,用于验证函数是否正常工作。
- 第 14 行输出如下:
可以看到,每个字符都被映射到了一个唯一的整数。1{' ': 1, 'e': 2, 'o': 3, 't': 4, 'i': 5, 's': 6, 'h': 7, 'r': 8, 'y': 9, 'u': 10, 'c': 11, 'n': 12, 'a': 13, 'p': 14, '.': 15, 'q': 16, 'k': 17, 'b': 18, 'w': 19, 'f': 20, 'x': 21, 'j': 22, 'm': 23, 'v': 24, 'l': 25, 'z': 26, 'd': 27, 'g': 28, ',': 29} - 第 16 行:使用循环打印出标记化后的数据,也就是转换为整数形式的结果。注意,一组句子中的每个整数代表一个字符,而不是一个单词。
Quiz
如果右移10位,输入文本 "Go kbo vokbxsxq klyed mszrobc" 的解密文本是什么?