Seq2Seq练手之单词镜像
2019-10-23 12:05:57

起源

最近在做Web安全技术的大作业,大作业是做密码分析的东西,一个密码可以理解为一个序列,可以使用NLP的方法。

在大作业中,有个任务要做中文分析,比如出现了哪个中文字。比如,密码中可能存在“woainixxxx”这样的形式,如何里面的中文拼音分开,这里想到了Seq2Seq模型,训练数据可以是“woaini”,然后输出为“wo ai ni”,让神经网络帮我们加上空格。

任务

这里先想到了之前学Udacity的ud730的LSTM时候留的作业,之前一直没有做。感觉这是一个很好例子,不过有点儿难,暂时没去用TensorFlow来原生实现,这里将使用Keras来做。

task

这里的任务是单词镜像,其实就是把单词反转一下。

代码

代码以jupyter notebook形式放在了https://github.com/nladuo/ml-study-stuff/blob/master/udacity-deep-learning/mirror_the_words.ipynb

构建数据集

取数据

接下来,先构建数据集,这里使用了之前的text8。text8里面一共有1亿个字符,这里只是做个demo,数据取少一点儿,我取了1万个字符作为测试数据,200万字符作为训练数据。

1
2
3
4
5
valid_size = 10000
train_size = 2000000
valid_text = text[:valid_size]
train_text = text[valid_size:valid_size+train_size]
train_size = len(train_text)

code1.png

字符数字转换

在神经网络中,取值都是数值型的,只有小写字符和空格两种,也是就是26+1中字符。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
# 将字符转为id
import string

vocabulary_size = len(string.ascii_lowercase) + 1 # [a-z] + ' '
first_letter = ord(string.ascii_lowercase[0])

def char2id(char):
if char in string.ascii_lowercase:
return ord(char) - first_letter + 1
elif char == ' ':
return 0
else:
print('Unexpected character: %s' % char)
return 0

def id2char(dictid):
if dictid > 0:
return chr(dictid + first_letter - 1)
else:
return ' '

code2.png

构建Label

我们要做单词镜像,这在代码中很好实现。镜像后的text也就是训练的标签。

1
2
3
4
5
6
def mirror(text):
words = text.split(" ")
mirror = []
for word in words:
mirror.append(word[::-1])
return " ".join(mirror)

code3.png

构建dataset和labels

下面,我们把text拆开,20个为一组,作为dataset;把20个字符取一个mirror,就成了labels

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
seq_len = 20

def build_dataset(text):
dataset = []
labels = []
length = len(text) / seq_len
for i in range(length):
line = text[i:i+seq_len]
mirror_line = mirror(line)
dataset.append([char2id(ch) for ch in line])
labels.append([char2id(ch) for ch in mirror_line])
return dataset, labels

train_set, train_labels = build_dataset(train_text)
valid_set, valid_labels = build_dataset(valid_text)

然后,进行One-Hot编码,比如:如果数据有三类,0编码成[0 0 1], 1编码成[0 1 0],2编码成[1 0 0]。这里一共有27种字符,所以是一个长度为27的向量。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
import numpy as np

def vectorize(word, seq_len, vec_size):
vec = np.zeros((seq_len, vec_size), dtype=int)
for i, ch in enumerate(word):
vec[i, ch] = 1
return vec

def vectorize_dataset(dataset, labels):
x = np.zeros((len(dataset), seq_len, vocabulary_size), dtype=np.int8)
y = np.zeros((len(dataset), seq_len, vocabulary_size), dtype=np.int8)

for i in range(len(dataset)):
x[i] = vectorize(dataset[i], seq_len, vocabulary_size)
y[i] = vectorize(labels[i], seq_len, vocabulary_size)
return x, y

train_x, train_y = vectorize_dataset(train_set, train_labels)
valid_x, valid_y = vectorize_dataset(valid_set, valid_labels)

code4.png

到这里,数据集就构建好了。

训练与测试

构建模型

这里要建立seq2seq网络模型了,这里参考了这篇文章:使用 Keras 实现简单的 Sequence to Sequence 模型

模型里有个RNN,第一个RNN作为Encoder,然后把第一层的输出复制20份(数据的长度),然后作为第二个RNN(Decoder),最后得到一个序列的输出。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
from keras.layers.recurrent import GRU
from keras.layers.wrappers import TimeDistributed
from keras.models import Sequential, model_from_json
from keras.layers.core import Dense, RepeatVector

def build_model(input_size, seq_len, hidden_size):
model = Sequential()
model.add(GRU(input_shape=(None, input_size), units=hidden_size, return_sequences=False))
model.add(Dense(hidden_size, activation="relu"))
model.add(RepeatVector(seq_len))
model.add(GRU(units=hidden_size, return_sequences=True))
model.add(TimeDistributed(Dense(units=input_size, activation="linear")))
model.compile(loss="mse", optimizer='adam')

return model

model = build_model(vocabulary_size, seq_len, 128)

训练

1
2
3
4
5
model.fit(train_x, train_y,
batch_size=128,
epochs=128,
verbose=1,
validation_data=(valid_x, valid_y))

这里使用fit就好了,然后可以看到迭代次数增加,loss降低。
model1.png

效果

最后看一下效果,虽然效果不是完全正确,但是还有那么点儿意思的。
result.png

神经网络完全从0、1学习出来能做到这种效果也算是不错了。

Prev
2019-10-23 12:05:57
Next