01-NLP-04-01用RNN做文本生成

---恢复内容开始---

用RNN做文本生成

举个小小的例子,来看看LSTM是怎么玩的

我们这里用温斯顿丘吉尔的人物传记作为我们的学习语料。

(各种中文语料可以自行网上查找, 英文的小说语料可以从古登堡计划网站下载txt平文本:https://www.gutenberg.org/wiki/Category:Bookshelf)

第一步,一样,先导入各种库

In [66]:
import numpy
from keras.models import Sequential
from keras.layers import Dense
from keras.layers import Dropout
from keras.layers import LSTM
from keras.callbacks import ModelCheckpoint
from keras.utils import np_utils
 

接下来,我们把文本读入

In [67]:
raw_text = open('../input/Winston_Churchil.txt').read()
raw_text = raw_text.lower()
 

既然我们是以每个字母为层级,字母总共才26个,所以我们可以很方便的用One-Hot来编码出所有的字母(当然,可能还有些标点符号和其他noise)

In [68]:
chars = sorted(list(set(raw_text)))    #set将raw——text中所有出现的元素组成一个集合(由于集合中元素没有重复),然后再将无顺序的集合转化为列表(有序)并排序
char_to_int = dict((c, i) for i, c in enumerate(chars))   #生成字符和数字编号的对照表
int_to_char = dict((i, c) for i, c in enumerate(chars))
 

我们看到,全部的chars:

In [69]:
chars
Out[69]:
['
',
 ' ',
 '!',
 '#',
 '$',
 '%',
 '(',
 ')',
 '*',
 ',',
 '-',
 '.',
 '/',
 '0',
 '1',
 '2',
 '3',
 '4',
 '5',
 '6',
 '7',
 '8',
 '9',
 ':',
 ';',
 '?',
 '@',
 '[',
 ']',
 '_',
 'a',
 'b',
 'c',
 'd',
 'e',
 'f',
 'g',
 'h',
 'i',
 'j',
 'k',
 'l',
 'm',
 'n',
 'o',
 'p',
 'q',
 'r',
 's',
 't',
 'u',
 'v',
 'w',
 'x',
 'y',
 'z',
 '‘',
 '’',
 '“',
 '”',
 'ufeff']
 

一共有:

In [70]:
len(chars)
Out[70]:
61
 

同时,我们的原文本一共有:

In [71]:
len(raw_text)
Out[71]:
276830

我们这里简单的文本预测就是,给了前置的字母以后,下一个字母是谁?

比如,Winsto, 给出 n Britai 给出 n;如果给Winston会给出空格。

构造训练测试集

我们需要把我们的raw text变成可以用来训练的x,y:

x 是前置字母们 y 是后一个字母

In [72]:
seq_length = 100  #规定好前后语境的长度(100步)
x = []
y = []
for i in range(0, len(raw_text) - seq_length):
    given = raw_text[i:i + seq_length]    # 待训练的已知量
    predict = raw_text[i + seq_length]  # 预测量
    x.append([char_to_int[char] for char in given])
    y.append(char_to_int[predict])
 

我们可以看看我们做好的数据集的长相:

In [73]:
print(x[:3])   #前三个数组,每个小数组都为100维的
print(y[:3])
 
[[60, 45, 47, 44, 39, 34, 32, 49, 1, 36, 50, 49, 34, 43, 31, 34, 47, 36, 57, 48, 1, 47, 34, 30, 41, 1, 48, 44, 41, 33, 38, 34, 47, 48, 1, 44, 35, 1, 35, 44, 47, 49, 50, 43, 34, 9, 1, 31, 54, 1, 47, 38, 32, 37, 30, 47, 33, 1, 37, 30, 47, 33, 38, 43, 36, 1, 33, 30, 51, 38, 48, 0, 0, 49, 37, 38, 48, 1, 34, 31, 44, 44, 40, 1, 38, 48, 1, 35, 44, 47, 1, 49, 37, 34, 1, 50, 48, 34, 1, 44], [45, 47, 44, 39, 34, 32, 49, 1, 36, 50, 49, 34, 43, 31, 34, 47, 36, 57, 48, 1, 47, 34, 30, 41, 1, 48, 44, 41, 33, 38, 34, 47, 48, 1, 44, 35, 1, 35, 44, 47, 49, 50, 43, 34, 9, 1, 31, 54, 1, 47, 38, 32, 37, 30, 47, 33, 1, 37, 30, 47, 33, 38, 43, 36, 1, 33, 30, 51, 38, 48, 0, 0, 49, 37, 38, 48, 1, 34, 31, 44, 44, 40, 1, 38, 48, 1, 35, 44, 47, 1, 49, 37, 34, 1, 50, 48, 34, 1, 44, 35], [47, 44, 39, 34, 32, 49, 1, 36, 50, 49, 34, 43, 31, 34, 47, 36, 57, 48, 1, 47, 34, 30, 41, 1, 48, 44, 41, 33, 38, 34, 47, 48, 1, 44, 35, 1, 35, 44, 47, 49, 50, 43, 34, 9, 1, 31, 54, 1, 47, 38, 32, 37, 30, 47, 33, 1, 37, 30, 47, 33, 38, 43, 36, 1, 33, 30, 51, 38, 48, 0, 0, 49, 37, 38, 48, 1, 34, 31, 44, 44, 40, 1, 38, 48, 1, 35, 44, 47, 1, 49, 37, 34, 1, 50, 48, 34, 1, 44, 35, 1]]
[35, 1, 30]
 

此刻,楼上这些表达方式,类似就是一个词袋,或者说 index。

接下来我们做两件事:

  1. 我们已经有了一个input的数字表达(index),我们要把它变成LSTM需要的数组格式: [样本数,时间步伐,特征]

  2. 第二,对于output,我们在Word2Vec里学过,用one-hot做output的预测可以给我们更好的效果,相对于直接预测一个准确的y数值的话。

In [74]:
n_patterns = len(x)
n_vocab = len(chars)

# 把x变成LSTM需要的样子
x = numpy.reshape(x, (n_patterns, seq_length, 1))   #1表示将每个元素变成一维的特征向量。不能省去,否则x原来是100维的,没有办法放到LSTM中
# 简单normal到0-1之间
x = x / float(n_vocab)
# output变成one-hot
y = np_utils.to_categorical(y)   #否则,y就是一个0-61之间的实数,如果要输出31,此时要求ML得到的预测值应该锁定,30.5-31.5之间,而这对于61长条形的线性预测而言,很难预测而且还不一定能学会
# one-hot表示的时候可以分别给出61个位置分别为1的概率,此时只需要将概率最大的one-hot向量对应的数值取出即可得到预测值,大大减少运算量,输出也更加简便 #注意:x没用one-hot的原因是我们会采用word2vec来实现同样的效果
print(x[11]) #每个元素都是100个的一维数组 print(y[11]) #61维的one-hot表达式
 
[[ 0.80327869]
 [ 0.55737705]
 [ 0.70491803]
 [ 0.50819672]
 [ 0.55737705]
 [ 0.7704918 ]
 [ 0.59016393]
 [ 0.93442623]
 [ 0.78688525]
 [ 0.01639344]
 [ 0.7704918 ]
 [ 0.55737705]
 [ 0.49180328]
 [ 0.67213115]
 [ 0.01639344]
 [ 0.78688525]
 [ 0.72131148]
 [ 0.67213115]
 [ 0.54098361]
 [ 0.62295082]
 [ 0.55737705]
 [ 0.7704918 ]
 [ 0.78688525]
 [ 0.01639344]
 [ 0.72131148]
 [ 0.57377049]
 [ 0.01639344]
 [ 0.57377049]
 [ 0.72131148]
 [ 0.7704918 ]
 [ 0.80327869]
 [ 0.81967213]
 [ 0.70491803]
 [ 0.55737705]
 [ 0.14754098]
 [ 0.01639344]
 [ 0.50819672]
 [ 0.8852459 ]
 [ 0.01639344]
 [ 0.7704918 ]
 [ 0.62295082]
 [ 0.52459016]
 [ 0.60655738]
 [ 0.49180328]
 [ 0.7704918 ]
 [ 0.54098361]
 [ 0.01639344]
 [ 0.60655738]
 [ 0.49180328]
 [ 0.7704918 ]
 [ 0.54098361]
 [ 0.62295082]
 [ 0.70491803]
 [ 0.59016393]
 [ 0.01639344]
 [ 0.54098361]
 [ 0.49180328]
 [ 0.83606557]
 [ 0.62295082]
 [ 0.78688525]
 [ 0.        ]
 [ 0.        ]
 [ 0.80327869]
 [ 0.60655738]
 [ 0.62295082]
 [ 0.78688525]
 [ 0.01639344]
 [ 0.55737705]
 [ 0.50819672]
 [ 0.72131148]
 [ 0.72131148]
 [ 0.6557377 ]
 [ 0.01639344]
 [ 0.62295082]
 [ 0.78688525]
 [ 0.01639344]
 [ 0.57377049]
 [ 0.72131148]
 [ 0.7704918 ]
 [ 0.01639344]
 [ 0.80327869]
 [ 0.60655738]
 [ 0.55737705]
 [ 0.01639344]
 [ 0.81967213]
 [ 0.78688525]
 [ 0.55737705]
 [ 0.01639344]
 [ 0.72131148]
 [ 0.57377049]
 [ 0.01639344]
 [ 0.49180328]
 [ 0.70491803]
 [ 0.8852459 ]
 [ 0.72131148]
 [ 0.70491803]
 [ 0.55737705]
 [ 0.01639344]
 [ 0.49180328]
 [ 0.70491803]]
[ 0.  0.  0.  0.  0.  0.  0.  0.  0.  0.  0.  0.  0.  0.  0.  0.  0.  0.
  0.  0.  0.  0.  0.  0.  0.  0.  0.  0.  0.  0.  0.  0.  0.  0.  0.  0.
  0.  0.  0.  0.  0.  0.  0.  0.  0.  0.  0.  0.  0.  0.  0.  0.  0.  0.
  1.  0.  0.  0.  0.  0.]
 

模型建造

LSTM模型构建

In [ ]:
model = Sequential()  #前后关系用sequential(),还有一种是图片性质的graphic()
model.add(LSTM(128, input_shape=(x.shape[1], x.shape[2])))    #第一层为LTSM,放128个神经元,x.shape[1]表示时间步伐100,x.shape[2]表每个中的小特征向量为1
model.add(Dropout(0.2)) #防止数据集过拟合,每次随机取输出的80%的神经元来表示输出,这样让变化速率减缓,不会很快直接下降到极点
model.add(Dense(y.shape[1], activation='softmax')) #dense为普通神经网络,在LSTM之后加一个普通神经网络做输出结果的计算。softmax来得出概率
model.compile(loss='categorical_crossentropy', optimizer='adam') #多元制的交叉熵,adam的学习率可以按照差距来调整步伐,减少过拟合概率

跑模型

In [ ]:
model.fit(x, y, nb_epoch=10, batch_size=32)   #nb_epoch=10表示跑多少次,每个batch中放多少集 
Epoch 1/10
 21120/276730 [=>............................] - ETA: 1078s - loss: 3.0638
 

我们来写个程序,看看我们训练出来的LSTM的效果:

In [ ]:
def predict_next(input_array):   #将处理后变成数字的数组放入
# 预处理x x = numpy.reshape(input_array, (1, seq_length, 1)) x = x / float(n_vocab) y = model.predict(x) return y def string_to_index(raw_input): res = [] for c in raw_input[(len(raw_input)-seq_length):]: #利用查找表将输入的字符转化为数字 res.append(char_to_int[c]) return res def y_to_char(y): largest_index = y.argmax() #取y中最大数值的序列号(就是对照表中的数字顺序),来检索出预测的字符 c = int_to_char[largest_index] return c
 

好,写成一个大程序:

In [ ]:
def generate_article(init, rounds=500):
    in_string = init.lower()
    for i in range(rounds):  #rounds需要往后生成多少个字符
        n = y_to_char(predict_next(string_to_index(in_string)))
        in_string += n    #将生成的附在之前的后面,当成下一次的输入,继续下一次的预测生成
    return in_string
In [ ]:
init = 'Professor Michael S. Hart is the originator of the Project'
article = generate_article(init)
print(article)
原文地址:https://www.cnblogs.com/Josie-chen/p/9098604.html