Skip to content

Commit c502e23

Browse files
authored
Merge pull request #14 from Yoctol/word2vec_LSTM
Word2vec lstm
2 parents 73dfdcb + aa6f6a6 commit c502e23

17 files changed

Lines changed: 668 additions & 85 deletions

requirements.txt

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
11
pylint
22
numpy
33
tensorflow
4-
keras
4+
keras == 1.2.2
55
yoctol_utils
6+
gensim
7+
h5py

seq2vec/__init__.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,3 @@
11
from .hash_text import HashSeq2Vec
22
from .seq2seq_auto_encoder import Seq2SeqAutoEncoderUseWordHash
3+
from .seq2seq_word2vec import Seq2SeqWord2Vec

seq2vec/base.py

Lines changed: 50 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -49,29 +49,61 @@ def transform_single_sequence(self, seq):
4949
class TrainableInterfaceMixin(object):
5050
"""Base Trainable sequence-to-vector class."""
5151

52-
@abstractmethod
53-
def fit(self, seqs):
54-
pass
52+
def fit(self, train_seqs, predict_seqs=None, verbose=1,
53+
nb_epoch=2, validation_split=0.2):
54+
train_x = self.input_transformer(train_seqs)
55+
if predict_seqs is None:
56+
train_y = self.output_transformer(train_seqs)
57+
else:
58+
train_y = self.output_transformer(predict_seqs)
59+
60+
self.model.fit(
61+
train_x, train_y,
62+
verbose=verbose,
63+
nb_epoch=nb_epoch,
64+
validation_split=validation_split,
65+
)
66+
67+
68+
def fit_generator(self, train_file_generator, test_file_generator,
69+
verbose=1, nb_epoch=2, batch_number=1024):
70+
training_sample_num = train_file_generator.batch_size * batch_number
71+
testing_sample_num = test_file_generator.batch_size * batch_number
72+
self.model.fit_generator(
73+
train_file_generator,
74+
samples_per_epoch=training_sample_num,
75+
validation_data=test_file_generator,
76+
nb_val_samples=testing_sample_num,
77+
verbose=verbose,
78+
nb_epoch=nb_epoch,
79+
)
80+
81+
def transform(self, seqs):
82+
test_x = self.input_transformer(seqs)
83+
return self.encoder.predict(test_x)
5584

56-
def fit_transform(self, seqs):
57-
self.fit(seqs)
85+
def transform_single_sequence(self, seq):
86+
return self.transform([seq])
87+
88+
def __call__(self, seqs):
5889
return self.transform(seqs)
5990

60-
@abstractmethod
61-
def save(self, path):
62-
r"""Serialize the transformer.
91+
def save_model(self, file_path):
92+
self.model.save(file_path)
6393

64-
Parameters
65-
----------
66-
path: str
67-
The path to store the Seq2Vec.
94+
def fit_transform(self, seqs):
95+
self.fit(seqs)
96+
return self.transform(seqs)
6897

69-
Returns
70-
-------
98+
class BaseTransformer(object):
99+
"""
100+
Base transformer to transform seq to input or output of seq2vec model
101+
"""
71102

72-
Raises
73-
------
74-
"""
103+
@abstractmethod
104+
def seq_transform(self, seq):
75105
pass
76106

77-
107+
@abstractmethod
108+
def __call__(self, seqs):
109+
pass

seq2vec/data_generator.py

Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,51 @@
1+
2+
3+
class DataGenterator(object):
4+
5+
def __init__(
6+
self, train_file_path, generate_x, generate_y,
7+
predict_file_path=None, batch_size=128
8+
):
9+
10+
self.train_file_path = train_file_path
11+
self.predict_file_path = train_file_path
12+
if predict_file_path is not None:
13+
self.predict_file_path = predict_file_path
14+
15+
self.generate_x = generate_x
16+
self.generate_y = generate_y
17+
self.batch_size = batch_size
18+
19+
def array_generator(self, file_path, generating_function, batch_size):
20+
with open(file_path, 'r', encoding='utf-8') as array_file:
21+
seqs = []
22+
seqs_len = 0
23+
for line in array_file:
24+
if seqs_len < batch_size:
25+
seqs.append(line.strip().split(' '))
26+
seqs_len += 1
27+
else:
28+
array = generating_function(seqs)
29+
seqs = [line.strip().split(' ')]
30+
seqs_len = 1
31+
yield array
32+
array = generating_function(seqs)
33+
yield array
34+
35+
def __next__(self):
36+
while True:
37+
for x_array, y_array in zip(
38+
self.array_generator(
39+
self.train_file_path,
40+
self.generate_x,
41+
self.batch_size
42+
),
43+
self.array_generator(
44+
self.predict_file_path,
45+
self.generate_y,
46+
self.batch_size
47+
)
48+
):
49+
assert (len(x_array) == len(y_array)), \
50+
'training data has different length with testing data'
51+
return (x_array, y_array)

seq2vec/seq2seq_auto_encoder.py

Lines changed: 96 additions & 52 deletions
Original file line numberDiff line numberDiff line change
@@ -1,33 +1,63 @@
11
"""Sequence-to-Sequence Auto Encoder."""
22
import numpy as np
33

4-
from keras.preprocessing.sequence import pad_sequences
4+
import keras.models
5+
from keras.models import Sequential
56
from keras.optimizers import RMSprop
6-
from keras.layers import Input, LSTM, RepeatVector
7-
from keras.layers import Dropout
7+
from keras.layers import LSTM, RepeatVector
8+
from keras.layers.embeddings import Embedding
9+
from keras.layers.wrappers import TimeDistributed
10+
from keras.layers import Dense, Dropout, Activation
811
from keras.models import Model
912

1013
from yoctol_utils.hash import consistent_hash
1114

1215
from .base import BaseSeq2Vec
1316
from .base import TrainableInterfaceMixin
14-
17+
from .base import BaseTransformer
18+
from .util import generate_padding_array
1519

1620
def _create_single_layer_seq2seq_model(
1721
max_length,
1822
max_index,
23+
embedding_size,
1924
latent_size,
2025
learning_rate,
2126
rho=0.9,
22-
decay=0.0,
27+
decay=0.01,
2328
):
24-
inputs = Input(shape=(max_length, max_index))
25-
encoded = LSTM(latent_size)(inputs)
26-
decoded = Dropout(0.3)(encoded)
27-
decoded = RepeatVector(max_length)(decoded)
28-
decoded = LSTM(max_index, return_sequences=True)(decoded)
29-
model = Model(inputs, decoded)
30-
encoder = Model(inputs, encoded)
29+
30+
model = Sequential()
31+
model.add(
32+
Embedding(
33+
max_index, embedding_size, input_length=max_length,
34+
name='embedding', mask_zero=True, dropout=0.2
35+
)
36+
)
37+
model.add(
38+
LSTM(
39+
output_dim=latent_size, return_sequences=False,
40+
name='en_LSTM_1', dropout_W=0.2, dropout_U=0.3
41+
)
42+
)
43+
model.add(
44+
RepeatVector(max_length)
45+
)
46+
model.add(
47+
LSTM(
48+
embedding_size, return_sequences=True,
49+
name='de_LSTM_1', dropout_W=0.2, dropout_U=0.3
50+
)
51+
)
52+
model.add(
53+
TimeDistributed(Dense(max_index))
54+
)
55+
model.add(Dropout(0.2))
56+
model.add(Activation('softmax'))
57+
58+
encoder = Model(
59+
model.input, model.get_layer('en_LSTM_1').output
60+
)
3161

3262
optimizer = RMSprop(
3363
lr=learning_rate,
@@ -46,6 +76,42 @@ def _one_hot_encode_seq(seq, max_index):
4676
np_seq.append(arr)
4777
return np_seq
4878

79+
def _hash_seq(sequence, max_index):
80+
return [consistent_hash(word) % max_index + 1 for word in sequence]
81+
82+
class Seq2vecAutoEncoderInputTransformer(BaseTransformer):
83+
84+
def __init__(self, max_index, max_length):
85+
self.max_index = max_index
86+
self.max_length = max_length
87+
88+
def seq_transform(self, seq):
89+
return _hash_seq(seq, self.max_index)
90+
91+
def __call__(self, seqs):
92+
array = generate_padding_array(
93+
seqs, self.seq_transform, 0, self.max_length, inverse=True
94+
)
95+
return array
96+
97+
class Seq2vecAutoEncoderOutputTransformer(BaseTransformer):
98+
99+
def __init__(self, max_index, max_length):
100+
self.max_index = max_index
101+
self.max_length = max_length
102+
103+
def seq_transform(self, seq):
104+
transformed_seq = _one_hot_encode_seq(
105+
_hash_seq(seq, self.max_index), self.max_index
106+
)
107+
return transformed_seq
108+
109+
def __call__(self, seqs):
110+
array = generate_padding_array(
111+
seqs, self.seq_transform, np.zeros(self.max_index + 1),
112+
self.max_length, inverse=False
113+
)
114+
return array
49115

50116
class Seq2SeqAutoEncoderUseWordHash(TrainableInterfaceMixin, BaseSeq2Vec):
51117
"""Hash words and feed to seq2seq auto-encoder.
@@ -68,60 +134,38 @@ def __init__(
68134
max_index,
69135
max_length,
70136
learning_rate=0.0001,
137+
embedding_size=64,
71138
latent_size=20,
72139
):
73140
self.max_index = max_index
74141
self.max_length = max_length
75142
self.learning_rate = learning_rate
143+
self.embedding_size = embedding_size
76144
self.latent_size = latent_size
77145

146+
self.input_transformer = Seq2vecAutoEncoderInputTransformer(
147+
max_index, max_length
148+
)
149+
self.output_transformer = Seq2vecAutoEncoderOutputTransformer(
150+
max_index, max_length
151+
)
152+
78153
model, encoder = _create_single_layer_seq2seq_model(
79154
max_length=self.max_length,
80155
max_index=self.max_index + 1,
156+
embedding_size=self.embedding_size,
81157
latent_size=self.latent_size,
82158
learning_rate=self.learning_rate,
83159
)
84160
self.model = model
85161
self.encoder = encoder
86162

87-
def _hash_seq(self, sequence):
88-
return [consistent_hash(word) % self.max_index + 1 for word in sequence]
89-
90-
def _generate_padding_array(self, seqs):
91-
hashed_seq = []
92-
for seq in seqs:
93-
hashed_seq.append(self._hash_seq(seq))
94-
data_pad = pad_sequences(
95-
hashed_seq,
96-
maxlen=self.max_length,
97-
value=0,
98-
)
99-
100-
array = []
101-
for seq in data_pad:
102-
np_seq = _one_hot_encode_seq(seq, self.max_index)
103-
array.append(np_seq)
104-
return np.array(array)
105-
106-
def fit(self, train_seqs, predict_seqs=None, verbose=2, nb_epoch=10, validation_split=0.0):
107-
train_x = self._generate_padding_array(train_seqs)
108-
if predict_seqs is None:
109-
train_y = train_x
110-
else:
111-
train_y = self._generate_padding_array(predict_seqs)
112-
self.model.fit(
113-
train_x, train_y,
114-
verbose=verbose,
115-
nb_epoch=nb_epoch,
116-
validation_split=validation_split,
163+
def load_model(self, file_path):
164+
self.model = keras.models.load_model(file_path)
165+
self.encoder = Model(
166+
self.model.input, self.model.get_layer('en_LSTM_1').output
117167
)
118-
119-
def transform(self, seqs):
120-
test_x = self._generate_padding_array(seqs)
121-
return self.encoder.predict(test_x)
122-
123-
def transform_single_sequence(self, seq):
124-
return self.transform([seq])
125-
126-
def __call__(self, seqs):
127-
return self.transform(seqs)
168+
self.max_index = self.model.get_layer('embedding').input_dim - 1
169+
self.max_length = self.model.input_shape[1]
170+
self.embedding_size = self.model.get_layer('embedding').output_dim
171+
self.latent_size = self.model.get_layer('en_LSTM_1').output_dim

0 commit comments

Comments
 (0)