最近在调研文本相似度计算方法的时候,突然看到有关MatchZoo有关的内容,MatchZoo 是一个通用的文本匹配工具包,它旨在方便大家快速的实现、比较、以及分享最新的深度文本匹配模型,貌似比较好玩,于是乎,看了一下MatchZoo的使用方法。在此简单记录一下我的使用过程,运行totorials里面的示例代码。如果之前没接触过MatchZoo,直接看github中README.md中的示例代码可能有点云里雾里,不过等看过totorial中的代码之后,才发现,那是MatchZoo的精髓。我在使用的时候是2019.11,随着版本的跟新,我这篇博客可能也就过时了,因为在我查找资料的时候,很多博客中讲解的已经与我实际测试的内容大相径庭。
在github上也提到了,对于MatchZoo中包含了两种安装方式:
pip install matchzoo
git clone https://github.com/NTMC-Community/MatchZoo.git
cd MatchZoo
python setup.py install
在模型中,目前给出了很多模型。但是对于不同的模型,由于做了封装,使用起来比较简单,主要分两步:第一,创建模型;第二,创建模型参数。这个在接下来的步骤中可以比较清楚的看到。
jupyter notebook
如果,机器上没有安装jupyter或者之前没有接触过,可参考jupyter notebook简介import matchzoo as mz
print(mz.__version__)
### 定义任务,包含两种,一个是Ranking,一个是classification
task = mz.tasks.Ranking()
print(task)
### 准备数据,数据在源码中有,不确定在pip安装的是否存在
### train_raw是matchzoo中自定的数据格式 matchzoo.data_pack.data_pack.DataPack
train_raw = mz.datasets.toy.load_data(stage='train', task=task)
test_raw = mz.datasets.toy.load_data(stage='test', task=task)
### 数据预处理,BasicPreprocessor为指定预处理的方式,在预处理中包含了两步:fit,transform
### fit将收集一些有用的信息到preprocessor.context中,不会对输入DataPack进行处理
### transformer 不会改变context、DataPack,他将重新生成转变后的DataPack.
### 在transformer过程中,包含了Tokenize => Lowercase => PuncRemoval等过程,这个过程在方法中应该是可以自定义的
preprocessor = mz.preprocessors.BasicPreprocessor()
preprocessor.fit(train_raw) ## init preprocessor inner state.
train_processed = preprocessor.transform(train_raw)
test_processed = preprocessor.transform(test_raw)
### 创建模型以及修改参数(可以使用mz.models.list_available()查看可用的模型列表)
model = mz.models.DenseBaseline()
model.params['task'] = task
model.params['mlp_num_units'] = 3
model.params.update(preprocessor.context)
model.params.completed()
model.build()
model.compile()
model.backend.summary()
### 训练, 评估, 预测
x, y = train_processed.unpack()
test_x, test_y = test_processed.unpack()
model.fit(x , y,batch_size=32, epochs=5)
model.evaluate(test_x,test_y)
model.predict(test_x)
### 保存模型
model.save('my-model')
loaded_model = mz.load_model('my-model')
import matchzoo as mz
print(mz.__version__)
d:\software\python\python37\lib\site-packages\tqdm\std.py:651: FutureWarning: The Panel class is removed from pandas. Accessing it from the top-level namespace will also be removed in the next version
from pandas import Panel
Using TensorFlow backend.
d:\software\python\python37\lib\site-packages\tqdm\std.py:651: FutureWarning: The Panel class is removed from pandas. Accessing it from the top-level namespace will also be removed in the next version
from pandas import Panel
2.2.0
There are two types of tasks available in MatchZoo. mz.tasks.Ranking
and mz.tasks.Classification
. We will use a ranking task for this demo.
task = mz.tasks.Ranking()
print(task)
Ranking Task
train_raw = mz.datasets.toy.load_data(stage='train', task=task)
test_raw = mz.datasets.toy.load_data(stage='test', task=task)
type(train_raw)
matchzoo.data_pack.data_pack.DataPack
DataPack
is a MatchZoo native data structure that most MatchZoo data handling processes build upon. A DataPack
is consists of three pandas.DataFrame
:
train_raw.left.head()
text_left | |
---|---|
id_left | |
Q1 | how are glacier caves formed? |
Q2 | How are the directions of the velocity and for... |
Q5 | how did apollo creed die |
Q6 | how long is the term for federal judges |
Q7 | how a beretta model 21 pistols magazines works |
train_raw.right.head()
text_right | |
---|---|
id_right | |
D1-0 | A partly submerged glacier cave on Perito More... |
D1-1 | The ice facade is approximately 60 m high |
D1-2 | Ice formations in the Titlis glacier cave |
D1-3 | A glacier cave is a cave formed within the ice... |
D1-4 | Glacier caves are often called ice caves , but... |
train_raw.relation.head()
id_left | id_right | label | |
---|---|---|---|
0 | Q1 | D1-0 | 0.0 |
1 | Q1 | D1-1 | 0.0 |
2 | Q1 | D1-2 | 0.0 |
3 | Q1 | D1-3 | 1.0 |
4 | Q1 | D1-4 | 0.0 |
It is also possible to convert a DataPack
into a single pandas.DataFrame
that holds all information.
train_raw.frame().head()
id_left | text_left | id_right | text_right | label | |
---|---|---|---|---|---|
0 | Q1 | how are glacier caves formed? | D1-0 | A partly submerged glacier cave on Perito More... | 0.0 |
1 | Q1 | how are glacier caves formed? | D1-1 | The ice facade is approximately 60 m high | 0.0 |
2 | Q1 | how are glacier caves formed? | D1-2 | Ice formations in the Titlis glacier cave | 0.0 |
3 | Q1 | how are glacier caves formed? | D1-3 | A glacier cave is a cave formed within the ice... | 1.0 |
4 | Q1 | how are glacier caves formed? | D1-4 | Glacier caves are often called ice caves , but... | 0.0 |
However, using such pandas.DataFrame
consumes much more memory if there are many duplicates in the texts, and that is the exact reason why we use DataPack
. For more details about data handling, consult matchzoo/tutorials/data_handling.ipynb
.
MatchZoo preprocessors are used to convert a raw DataPack
into a DataPack
that ready to be fed into a model.
preprocessor = mz.preprocessors.BasicPreprocessor()
There are two steps to use a preprocessor. First, fit
. Then, transform
. fit
will only changes the preprocessor’s inner state but not the input DataPack
.
preprocessor.fit(train_raw)
Processing text_left with chain_transform of Tokenize => Lowercase => PuncRemoval: 100%|█| 13/13 [00:00<00:00, 861.97it/s]
Processing text_right with chain_transform of Tokenize => Lowercase => PuncRemoval: 100%|█| 100/100 [00:00<00:00, 1558.32it/s]
Processing text_right with append: 100%|██████████████████████████████████████████| 100/100 [00:00<00:00, 99674.52it/s]
Building FrequencyFilter from a datapack.: 100%|██████████████████████████████████| 100/100 [00:00<00:00, 99864.38it/s]
Processing text_right with transform: 100%|███████████████████████████████████████| 100/100 [00:00<00:00, 99769.36it/s]
Processing text_left with extend: 100%|█████████████████████████████████████████████| 13/13 [00:00<00:00, 12908.61it/s]
Processing text_right with extend: 100%|██████████████████████████████████████████| 100/100 [00:00<00:00, 98782.48it/s]
Building Vocabulary from a datapack.: 100%|████████████████████████████████████| 1974/1974 [00:00<00:00, 983904.47it/s]
<matchzoo.preprocessors.basic_preprocessor.BasicPreprocessor at 0x24aad5d17b8>
fit
will gather useful information into its context
, which will be used later in a transform
or used to set hyper-parameters of a model.
preprocessor.context
{'filter_unit': <matchzoo.preprocessors.units.frequency_filter.FrequencyFilter at 0x24aad5d1e80>,
'vocab_unit': <matchzoo.preprocessors.units.vocabulary.Vocabulary at 0x24aad771b38>,
'vocab_size': 289,
'embedding_input_dim': 289,
'input_shapes': [(30,), (30,)]}
Once fit
, the preprocessor has enough information to transform
. transform
will not change the preprocessor’s inner state and the input DataPack
, but return a transformed DataPack
.
train_processed = preprocessor.transform(train_raw)
test_processed = preprocessor.transform(test_raw)
Processing text_left with chain_transform of Tokenize => Lowercase => PuncRemoval: 100%|█| 13/13 [00:00<00:00, 4323.00it/s]
Processing text_right with chain_transform of Tokenize => Lowercase => PuncRemoval: 100%|█| 100/100 [00:00<00:00, 2695.45it/s]
Processing text_right with transform: 100%|███████████████████████████████████████| 100/100 [00:00<00:00, 19946.28it/s]
Processing text_left with transform: 100%|███████████████████████████████████████████| 13/13 [00:00<00:00, 4339.17it/s]
Processing text_right with transform: 100%|███████████████████████████████████████| 100/100 [00:00<00:00, 66313.11it/s]
Processing length_left with len: 100%|██████████████████████████████████████████████| 13/13 [00:00<00:00, 12960.77it/s]
Processing length_right with len: 100%|███████████████████████████████████████████| 100/100 [00:00<00:00, 99698.22it/s]
Processing text_left with transform: 100%|███████████████████████████████████████████| 13/13 [00:00<00:00, 2592.52it/s]
Processing text_right with transform: 100%|███████████████████████████████████████| 100/100 [00:00<00:00, 33238.01it/s]
Processing text_left with chain_transform of Tokenize => Lowercase => PuncRemoval: 100%|█| 3/3 [00:00<00:00, 1495.83it/s]
Processing text_right with chain_transform of Tokenize => Lowercase => PuncRemoval: 100%|█| 20/20 [00:00<00:00, 1813.91it/s]
Processing text_right with transform: 100%|█████████████████████████████████████████| 20/20 [00:00<00:00, 19934.90it/s]
Processing text_left with transform: 100%|█████████████████████████████████████████████| 3/3 [00:00<00:00, 2991.66it/s]
Processing text_right with transform: 100%|█████████████████████████████████████████| 20/20 [00:00<00:00, 19934.90it/s]
Processing length_left with len: 100%|█████████████████████████████████████████████████| 3/3 [00:00<00:00, 2988.82it/s]
Processing length_right with len: 100%|████████████████████████████████████████████████████████| 20/20 [00:00<?, ?it/s]
Processing text_left with transform: 100%|█████████████████████████████████████████████| 3/3 [00:00<00:00, 2992.37it/s]
Processing text_right with transform: 100%|█████████████████████████████████████████| 20/20 [00:00<00:00, 19930.17it/s]
train_processed.left.head()
text_left | length_left | |
---|---|---|
id_left | ||
Q1 | [111, 265, 176, 216, 217, 2, 0, 0, 0, 0, 0, 0,... | 6 |
Q2 | [111, 265, 98, 223, 248, 98, 37, 60, 203, 143,... | 15 |
Q5 | [111, 145, 283, 251, 69, 0, 0, 0, 0, 0, 0, 0, ... | 5 |
Q6 | [111, 13, 270, 98, 272, 227, 47, 87, 0, 0, 0, ... | 8 |
Q7 | [111, 219, 56, 108, 122, 14, 230, 63, 0, 0, 0,... | 8 |
As we can see, text_left
is already in sequence form that nerual networks love.
Just to make sure we have the correct sequence:
vocab_unit = preprocessor.context['vocab_unit']
print('Orig Text:', train_processed.left.loc['Q1']['text_left'])
sequence = train_processed.left.loc['Q1']['text_left']
print('Transformed Indices:', sequence)
print('Transformed Indices Meaning:',
'_'.join([vocab_unit.state['index_term'][i] for i in sequence]))
Orig Text: [111, 265, 176, 216, 217, 2, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]
Transformed Indices: [111, 265, 176, 216, 217, 2, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]
Transformed Indices Meaning: how_are_glacier_caves_formed__<PAD>_<PAD>_<PAD>_<PAD>_<PAD>_<PAD>_<PAD>_<PAD>_<PAD>_<PAD>_<PAD>_<PAD>_<PAD>_<PAD>_<PAD>_<PAD>_<PAD>_<PAD>_<PAD>_<PAD>_<PAD>_<PAD>_<PAD>_<PAD>
For more details about preprocessing, consult matchzoo/tutorials/data_handling.ipynb
.
MatchZoo provides many built-in text matching models.
mz.models.list_available()
[matchzoo.models.naive.Naive,
matchzoo.models.dssm.DSSM,
matchzoo.models.cdssm.CDSSM,
matchzoo.models.dense_baseline.DenseBaseline,
matchzoo.models.arci.ArcI,
matchzoo.models.arcii.ArcII,
matchzoo.models.match_pyramid.MatchPyramid,
matchzoo.models.knrm.KNRM,
matchzoo.models.duet.DUET,
matchzoo.models.drmmtks.DRMMTKS,
matchzoo.models.drmm.DRMM,
matchzoo.models.anmm.ANMM,
matchzoo.models.mvlstm.MVLSTM,
matchzoo.contrib.models.match_lstm.MatchLSTM,
matchzoo.contrib.models.match_srnn.MatchSRNN,
matchzoo.contrib.models.hbmp.HBMP,
matchzoo.contrib.models.esim.ESIM,
matchzoo.contrib.models.bimpm.BiMPM,
matchzoo.contrib.models.diin.DIIN,
matchzoo.models.conv_knrm.ConvKNRM]
Let’s use mz.models.DenseBaseline
for our demo.
model = mz.models.DenseBaseline()
The model is initialized with a hyper parameter table, in which values are partially filled. To view parameters and their values, use print
.
print(model.params)
model_class <class 'matchzoo.models.dense_baseline.DenseBaseline'>
input_shapes None
task None
optimizer adam
with_multi_layer_perceptron True
mlp_num_units 256
mlp_num_layers 3
mlp_num_fan_out 64
mlp_activation_func relu
to_frame
gives you more informartion in addition to just names and values.
model.params.to_frame()[['Name', 'Description', 'Value']]
Name | Description | Value | |
---|---|---|---|
0 | model_class | Model class. Used internally for save/load. Ch... | <class 'matchzoo.models.dense_baseline.DenseBa... |
1 | input_shapes | Dependent on the model and data. Should be set... | None |
2 | task | Decides model output shape, loss, and metrics. | None |
3 | optimizer | None | adam |
4 | with_multi_layer_perceptron | A flag of whether a multiple layer perceptron ... | True |
5 | mlp_num_units | Number of units in first `mlp_num_layers` layers. | 256 |
6 | mlp_num_layers | Number of layers of the multiple layer percetron. | 3 |
7 | mlp_num_fan_out | Number of units of the layer that connects the... | 64 |
8 | mlp_activation_func | Activation function used in the multiple layer... | relu |
To set a hyper-parameter:
model.params['task'] = task
model.params['mlp_num_units'] = 3
print(model.params)
model_class <class 'matchzoo.models.dense_baseline.DenseBaseline'>
input_shapes None
task Ranking Task
optimizer adam
with_multi_layer_perceptron True
mlp_num_units 3
mlp_num_layers 3
mlp_num_fan_out 64
mlp_activation_func relu
Notice that we are still missing input_shapes
, and that information is store in the preprocessor.
print(preprocessor.context['input_shapes'])
[(30,), (30,)]
We may use update
to load a preprocessor’s context into a model’s hyper-parameter table.
model.params.update(preprocessor.context)
Now we have a completed hyper-parameter table.
model.params.completed()
True
With all parameters filled in, we can now build and compile the model.
model.build()
model.compile()
MatchZoo models are wrapped over keras models, and the backend
property of a model gives you the actual keras model built.
model.backend.summary()
Model: "model_1"
__________________________________________________________________________________________________
Layer (type) Output Shape Param # Connected to
==================================================================================================
text_left (InputLayer) (None, 30) 0
__________________________________________________________________________________________________
text_right (InputLayer) (None, 30) 0
__________________________________________________________________________________________________
concatenate_1 (Concatenate) (None, 60) 0 text_left[0][0]
text_right[0][0]
__________________________________________________________________________________________________
dense_1 (Dense) (None, 3) 183 concatenate_1[0][0]
__________________________________________________________________________________________________
dense_2 (Dense) (None, 3) 12 dense_1[0][0]
__________________________________________________________________________________________________
dense_3 (Dense) (None, 3) 12 dense_2[0][0]
__________________________________________________________________________________________________
dense_4 (Dense) (None, 64) 256 dense_3[0][0]
__________________________________________________________________________________________________
dense_5 (Dense) (None, 1) 65 dense_4[0][0]
==================================================================================================
Total params: 528
Trainable params: 528
Non-trainable params: 0
__________________________________________________________________________________________________
For more details about models, consult matchzoo/tutorials/models.ipynb
.
A DataPack
can unpack
itself into data that can be directly used to train a MatchZoo model.
x, y = train_processed.unpack()
test_x, test_y = test_processed.unpack()
model.fit(x, y, batch_size=32, epochs=5)
WARNING: Logging before flag parsing goes to stderr.
W1108 15:57:07.798936 9460 deprecation_wrapper.py:119] From d:\software\python\python37\lib\site-packages\keras\backend\tensorflow_backend.py:422: The name tf.global_variables is deprecated. Please use tf.compat.v1.global_variables instead.
Epoch 1/5
100/100 [==============================] - ETA: 0s - loss: 0.033 - 0s 3ms/step - loss: 0.0963
Epoch 2/5
100/100 [==============================] - ETA: 0s - loss: 0.064 - 0s 160us/step - loss: 0.0606
Epoch 3/5
100/100 [==============================] - ETA: 0s - loss: 0.068 - 0s 170us/step - loss: 0.0522
Epoch 4/5
100/100 [==============================] - ETA: 0s - loss: 0.001 - 0s 170us/step - loss: 0.0499
Epoch 5/5
100/100 [==============================] - ETA: 0s - loss: 0.032 - 0s 221us/step - loss: 0.0495
<keras.callbacks.callbacks.History at 0x24aad760b38>
An alternative to train a model is to use a DataGenerator
. This is useful for delaying expensive preprocessing steps or doing real-time data augmentation. For some models that needs dynamic batch-wise information, using a DataGenerator
is required. For more details about DataGenerator
, consult matchzoo/tutorials/data_handling.ipynb
.
data_generator = mz.DataGenerator(train_processed, batch_size=32)
model.fit_generator(data_generator, epochs=5, use_multiprocessing=True, workers=4)
Epoch 1/5
4/4 [==============================] - ETA: 55s - loss: 0.09 - 19s 5s/step - loss: 0.0406
Epoch 2/5
4/4 [==============================] - ETA: 57s - loss: 2.3547e- - 19s 5s/step - loss: 0.0403
Epoch 3/5
4/4 [==============================] - ETA: 58s - loss: 0.12 - 19s 5s/step - loss: 0.0402
Epoch 4/5
4/4 [==============================] - ETA: 1:06 - loss: 0.060 - 22s 6s/step - loss: 0.0401
Epoch 5/5
4/4 [==============================] - ETA: 54s - loss: 0.03 - 18s 5s/step - loss: 0.0818
<keras.callbacks.callbacks.History at 0x24aade40940>
model.evaluate(test_x, test_y)
{mean_average_precision(0.0): 0.1111111111111111}
model.predict(test_x)
array([[0.02392608],
[0.02392608],
[0.02392608],
[0.02392608],
[0.32358152],
[0.02392608],
[0.02392608],
[0.02392608],
[0.02392608],
[0.02392608],
[0.02392608],
[0.02392608],
[0.02392608],
[0.02392608],
[0.02392608],
[0.02392608],
[0.02392608],
[0.02392608],
[0.02392608],
[0.02392608]], dtype=float32)
Since data preprocessing and model building are laborious and special setups of some models makes this even worse, MatchZoo provides prepare
, a unified interface that handles interaction among data, model, and preprocessor automatically.
More specifically, prepare
does these following things:
DataGeneratorBuilder
that will build a correctly formed DataGenerator
given a DataPack
It also does many special handling for specific models, but we will not go into the details of that here.
for model_class in mz.models.list_available():
print(model_class)
model, preprocessor, data_generator_builder, embedding_matrix = mz.auto.prepare(
task=task,
model_class=model_class,
data_pack=train_raw,
)
train_processed = preprocessor.transform(train_raw, verbose=0)
test_processed = preprocessor.transform(test_raw, verbose=0)
train_gen = data_generator_builder.build(train_processed)
test_gen = data_generator_builder.build(test_processed)
model.fit_generator(train_gen, epochs=1)
model.evaluate_generator(test_gen)
print()
<class 'matchzoo.models.naive.Naive'>
Epoch 1/1
1/1 [==============================] - 0s 148ms/step - loss: 8960.0771
<class 'matchzoo.models.dssm.DSSM'>
W1108 16:01:02.558478 9460 deprecation.py:323] From d:\software\python\python37\lib\site-packages\tensorflow\python\ops\math_grad.py:1250: add_dispatch_support.<locals>.wrapper (from tensorflow.python.ops.array_ops) is deprecated and will be removed in a future version.
Instructions for updating:
Use tf.where in 2.0, which has the same broadcast rule as np.where
Epoch 1/1
1/1 [==============================] - 0s 482ms/step - loss: 0.1887
<class 'matchzoo.models.cdssm.CDSSM'>
Epoch 1/1
1/1 [==============================] - 2s 2s/step - loss: 0.0801
<class 'matchzoo.models.dense_baseline.DenseBaseline'>
Epoch 1/1
1/1 [==============================] - 0s 303ms/step - loss: 1091.7343
<class 'matchzoo.models.arci.ArcI'>
W1108 16:01:11.513350 9460 deprecation_wrapper.py:119] From d:\software\python\python37\lib\site-packages\keras\backend\tensorflow_backend.py:4070: The name tf.nn.max_pool is deprecated. Please use tf.nn.max_pool2d instead.
Epoch 1/1
1/1 [==============================] - 1s 919ms/step - loss: 0.0496
<class 'matchzoo.models.arcii.ArcII'>
Epoch 1/1
1/1 [==============================] - 1s 673ms/step - loss: 0.1104
<class 'matchzoo.models.match_pyramid.MatchPyramid'>
Epoch 1/1
1/1 [==============================] - 1s 660ms/step - loss: 0.0744
<class 'matchzoo.models.knrm.KNRM'>
Epoch 1/1
1/1 [==============================] - 1s 918ms/step - loss: 1183.9641
<class 'matchzoo.models.duet.DUET'>
Epoch 1/1
1/1 [==============================] - 1s 893ms/step - loss: 0.1633
<class 'matchzoo.models.drmmtks.DRMMTKS'>
Epoch 1/1
1/1 [==============================] - 1s 849ms/step - loss: 0.0546
<class 'matchzoo.models.drmm.DRMM'>
Epoch 1/1
1/1 [==============================] - 1s 1s/step - loss: 0.0681
<class 'matchzoo.models.anmm.ANMM'>
Epoch 1/1
1/1 [==============================] - 1s 653ms/step - loss: 0.0640
<class 'matchzoo.models.mvlstm.MVLSTM'>
Epoch 1/1
1/1 [==============================] - 3s 3s/step - loss: 0.0495
<class 'matchzoo.contrib.models.match_lstm.MatchLSTM'>
Epoch 1/1
1/1 [==============================] - 5s 5s/step - loss: 0.0681
<class 'matchzoo.contrib.models.match_srnn.MatchSRNN'>
Epoch 1/1
1/1 [==============================] - 3s 3s/step - loss: 0.0620
<class 'matchzoo.contrib.models.hbmp.HBMP'>
Epoch 1/1
1/1 [==============================] - 7s 7s/step - loss: 0.0519
<class 'matchzoo.contrib.models.esim.ESIM'>
Epoch 1/1
1/1 [==============================] - 5s 5s/step - loss: nan
<class 'matchzoo.contrib.models.bimpm.BiMPM'>
Epoch 1/1
1/1 [==============================] - 5s 5s/step - loss: 0.0494
<class 'matchzoo.contrib.models.diin.DIIN'>
W1108 16:03:36.396669 9460 deprecation.py:506] From d:\software\python\python37\lib\site-packages\matchzoo-2.2.0-py3.7.egg\matchzoo\contrib\layers\decaying_dropout_layer.py:87: calling dropout (from tensorflow.python.ops.nn_ops) with keep_prob is deprecated and will be removed in a future version.
Instructions for updating:
Please use `rate` instead of `keep_prob`. Rate should be set to `rate = 1 - keep_prob`.
Epoch 1/1
1/1 [==============================] - 4s 4s/step - loss: 0.0500
<class 'matchzoo.models.conv_knrm.ConvKNRM'>
Epoch 1/1
1/1 [==============================] - 10s 10s/step - loss: 1611.8352
model.save('my-model')
loaded_model = mz.load_model('my-model')