Bootstrap

Keras 文字生成系统

  同是深度学习“槛内人”,我怎么不知道这样高大上的文字生成对话系统

  文字信息是存在最广泛的信息形式之一,而深度学习的序列模型(Sequential Model)在对文字生成建模(Generative Model)方面具备独特的优势。文字自动生成可应用于自然语言对话建模和自动文稿生成,极大地提高了零售客服、网络导购以及新闻业的生产效率。目前比较成熟的是单轮对话系统以及基于单轮对话系统的简单多轮对话系统,这类系统应用范围广泛,技术相对成熟,在零售客服、网络导购等领域都有很高的边际收益。例如苹果手机中的Siri,以及微软早期的小冰机器人,都属于这种系统的尝试。

  从应用范围来看,自然对话系统分为所谓的闲聊(Chit-Chat)对话系统和专业(DomainSpecific)对话系统两种。

  从技术上看,短对话聊天系统分为两种:基于检索的系统(Retrieval Based System)和基于文字生成的系统(Generative System)。构造对话机器人的方法可以分为三种,其中有两种属于索引式模型,一种属于最新的生成式模型。

  第一种是基于深度学习流行之前的技术,使用AIML 这种标识语言构造大量的应答库,通过现有的对文字结构的理解来构造的简单对话系统。这种系统的构造费时费力,灵活性差,扩展性差,智能度低,很难构造多轮对话系统,但是因为应答都是真人生成的,不会有语法错误,语言标准,所以适用于简单集中的业务环境。

  第二种是使用深度学习方法来寻找对应于当前对话背景的最佳应答,相对于第一种方法降低了很多人工构造应答库的工作,灵活性高,扩展性强,有一定智能度,可以用来构造多轮对话系统。

  第三种是目前最新研究的领域,使用深度学习技术实时生成应答,灵活度和智能度都极高,属于自动扩展,但是需要极大量的数据积累和比较复杂的模型才能得到较好的结果。通常第三种系统需要与第二种系统相结合,在第二种系统已有的应答库中无法找到足够满意的选项时,可以启用第三种系统来实时生成应答。

  愿做一次槛外人,学习这基于文字生成的对话系统。这里我们着重介绍第三种。

基于文字生成的对话系统

  这里使用作家老舍的小说《四世同堂》作为训练数据进行演示。读者可以根据自己的应用和业务环境,选择合适的数据。

  对于训练文本可以一次性读入:

alltext = open("e:\\data\\Text\\四世同堂.txt", encoding='utf-8').read()

  得到的结果是一个巨大的字符串列表。因为我们将以每个单字作为建模对象,因此这样读入数据是最方便以后操作的。如果要以词组和单句进行建模,则分段读入最佳。《四世同堂》这本书一共有 3545 个不重复的单字和符号。

  按照对文字序列建模的顺序,我们依次进行下面的操作。

  (1) 首先对所有待建模的单字和字符进行索引。

  (2) 其次构造句子序列。

  (3) 然后建立神经网络模型,对索引标号序列进行向量嵌入后的向量构造长短记忆神经网络。

  (4) 最后我们来检验建模效果。

  对单字和字符建立索引非常简单,用下面三句命令即可:

1 sortedcharset = sorted(set(alltext))
2 char_indices = dict((c, i) for i, c in enumerate(sortedcharset))
3 indices_char = dict((i, c) for i, c in enumerate(sortedcharset))

  第一句的对用 set 函数抽取的每个单字的集合按照编码从小到大排序。第二句对一个单字进行编号索引,第三句进行反向操作,对每个索引建立单字的词典,主要是为了方便预测出来的索引标号向量转换为人能够阅读的文字。

  构造句子序列也非常简单:

1 maxlen = 40
2 step = 3
3 sentences = []
4 next_chars = []
5 for i in range(0, len(alltext) - maxlen, step):
6 sentences.append(alltext[i: i + maxlen])
7 next_chars.append(alltext[i + maxlen])
8 print('nb sequences:', len(sentences))

  构造句子序列的原因是原始数据是单字列表,因此需要人为构造句子的序列来模仿句子序列。在上面的代码中,maxlen=40 标识人工构造的句子长度为 40 个单字,step=3表示在构造句子时每次跳过 3 个单字,比如用这一串单字列表“这首小令是李清照的奠定才女地位之作,轰动朝野。传闻就是这首词,使得赵明诚日夜作相思之梦,充分说明了这首小令在当时引起的轰动。又说此词是化用韩偓《懒起》诗意。”来构造句子的时候,假设句子长度为 10,那么第一句是“这首小令是李清照的奠”,而第二句则是移动 3 个单字以后的“令是李清照的奠定才女”。跳字的目的是为了增加句子与句子之间的变化,否则每两个相邻句子之间只有一个单字的差异,但是这两个相邻句子是用来构造前后对话序列的,缺乏变化使得建模效果不好。当然,如果跳字太多,那么会大大降低数据量。比如《四世同堂》一共有 711501 个单字和符号,每隔三个字或者符号进行跳字操作构造的句子只有 237154 个,是原数据量的 1/3。如何选择跳字的个数是读者在建模的时候要根据情况调整的一个参数。

  需要注意的是,因为句子是人工构造的,都有固定的长度,因此这里不需要进行句子补齐操作。同时,这些句子的向量其实都是一个稀疏矩阵,因为它们只将包含数据的索引编号计入。人工构造句子完毕后就可以对其矩阵化,即对于每一句话,将其中的索引标号映射到所有出现的单字和符号,每一句话所对应的 40 个字符的向量被投影到一个 3545 个元素的向量中,在这个向量中,如果某个元素出现在这句话中,则其值为 1,否则为 0。

  下面的代码执行这个操作:

1 print('Vectorization...')
2 X = np.zeros((len(sentences), maxlen, len(sortedcharset)), dtype=np.bool)
3 y = np.zeros((len(sentences), len(sortedcharset)), dtype=np.bool)
4 for i, sentence in enumerate(sentences):
5 if (i % 30000 == 0):
6 print(i)
7 for t in range(maxlen):
8 char=sentence[t]
9 X[i, t, char_indices[char]] = 1
10 y[i, char_indices[next_chars[i]]] = 1

  当然这个新生成的数据会非常大,比如 X 会是一个 237154 × 40 × 3545 的实数矩阵,实际计算的时候占用的内存会超过 20GB。因此这里需要使用前面提到的数据生成器(data generator)方法,对一个具有较小批量数的样本进行投影操作。可以通过下面这个很简单的函数实现:

1 def data_generator(X, y, batch_size):
2 if batch_size<1:
3 batch_size=256
4 number_of_batches = X.shape[0]//batch_size
5 counter=0
6 shuffle_index = np.arange(np.shape(y)[0])
7 np.random.shuffle(shuffle_index)
8 #reset generator
9 while 1:
10 index_batch = shuffle_index[batch_size*counter:batch_size*(counter+1)]
11 X_batch = (X[index_batch,:,:]).astype('float32')
12 y_batch = (y[index_batch,:]).astype('float32')
13 counter += 1
14 yield(np.array(X_batch),y_batch)
15 if (counter < number_of_batches):
16 np.random.shuffle(shuffle_index)
17 counter=0

  这个函数与前面的 batch_generator 函数非常相似,主要区别是这个函数同时处理X 和 Y 矩阵的小批量生成,另外要求输入和输出数据都是 NumPy 多维矩阵而不是列表的列表。另外 Python 里的数值数据是 float64 类型的,因此专门使用 astype(‘float32’)将矩阵的数据类型强制定为 32 位浮点数以符合 CNTK 对数据类型的要求,这样不需要在后台再进行数据类型转换,从而提高效率。现在可以构造我们的长短记忆神经网络模型了。这时候再次体现了 Keras 的高效建模能力。下面短短几个命令就可以让我们构造一个深度学习模型:

1 # build the model: a single LSTM
2 batch_size=256
3 print('Build model...')
4 model = Sequential()
5 model.add(LSTM(256, batch_size=batch_size, input_shape=(maxlen, len(sortedcharset)), recurrent_dropout=0.1, dropout=0.1))
6 model.add(Dense(len(sortedcharset)))
7 model.add(Activation('softmax'))
8
9 optimizer = RMSprop(lr=0.01)
10 model.compile(loss='categorical_crossentropy', optimizer=optimizer)

  其中第一句命令指定要生成一个序列模型,第二到第四句命令要求依次添加三层网络,分别是一个长短记忆网络和一个全连接网络,最后使用一个softmax 的激活层输出预测。在长短记忆网络里,规定输入数据的维度为(时间步数,所有出现的不重复字符的个数),即输入的数据是对应每一句话处理以后的形式,并且对输入神经元权重和隐藏状态权重分别设定了10% 的放弃率。全连接层的输出维度为所有字符的个数,方便最后的激活函数计算。最后两条命令指定网络优化算法的参数,比如里面指定损失函数为典型的categorical_crossentropy,优化算法是指定的学习速率为0.01 的RMSprop算法。对于循环神经网络,这个优化算法通常表现较好。

  深度学习和人工智能无疑是现在最热门的技术之一,很多人希望能掌握这方面的技能,但是担心门槛太高。这本书可谓是及时雨,给大家提供了非常好的入门学习资料,也是目前国内唯一一本 Keras简单易用的深度学习框架书。其内容不仅涵盖了当前深度学习的几个主要应用领域,而且实用性强,同时也延伸到相关的系搭建、数据获取以及可预见的未来物联网方面的应用,非常值得一读。愿你轻松跨过这槛,化身时代弄潮的深度学习“槛内人”。

  以上内容节选自《Keras快速上手:基于Python的深度学习实战》,点此链接可在博文视点官网查看此书。
                 图片描述
  想及时获得更多精彩文章,可在微信中搜索“博文视点”或者扫描下方二维码并关注。
                    图片描述

;