# Compression of Google Neural Machine Translation Model

## Overview

Google Neural Machine Translation (GNMT) is a Sequence to sequence (Seq2seq) model which learns a mapping from an input text to an output text.

The example below demonstrates how to train a highly sparse GNMT model with minimal loss in accuracy. The model is based on the GNMT model presented in the paper Google’s Neural Machine Translation System: Bridging the Gap between Human and Machine Translation [1] which consists of approximately 210M floating point parameters.

## GNMT Model

The GNMT architecture is an encoder-decoder architecture with attention as presented in the original paper [1].

The encoder consists of an embedding layer followed by 1 bi-directional and 3 uni-directional LSTM layers with residual connections between them. The decoder consists of an embedding layer followed by 4 uni-directional LSTM layers and a linear Softmax layer. The attention mechanism connects between the encoder’s bi-directional LSTM layer to all of the decoder’s LSTM layers.

The GNMT model was adapted from the model shown in Neural Machine Translation (seq2seq) Tutorial [2] and from its repository.

The Sparse model implementation can be found in `GNMTModel` and offers several options to build the GNMT model.

## Sparsity - Pruning GNMT

Sparse neural networks are networks where a portion of the network weights are zeros. A high sparsity ratio can help compress the model and accelerate inference, reduce power consumption used for memory transfer and computing.

In order to produce a sparse network the network weights are pruned while training by forcing weights to be zero. There are a number of methods to prune neural networks, for example the paper To prune, or not to prune: exploring the efficacy of pruning for model compression [3] presents a method for gradual pruning of weights with low amplitude.

The example below demonstrates how to prune the GNMT model up to 90% sparsity with minimal loss in BLEU score using the Tensorflow model_pruning package which implements the method presented in [3]

## Post Training Weight Quantization

The weights of pre-trained GNMT models are usually represented in 32bit Floating-point format. The highly sparse pre-trained model below can be further compressed by uniform quantization of the weights to 8bits Integer, gaining a further compression ratio of 4x with negligible accuracy loss. The implementation of the weight quantization is based on TensorFlow API. When using the model for inference, the int8 weights of the sparse and quantized model are de-quantized back to fp32.

## Dataset

The models below were trained using the following datasets:

- Europarlv7 [4]
- Common Crawl Corpus
- News Commentary 11
- Development and test sets

All datasets are provided by WMT Shared Task: Machine Translation of News

You can use this script wmt16_en_de.sh to download and prepare the data for training and evaluating your model.

## Results & Pre-Trained Models

The following table presents some of our experiments and results. We provide pre-trained checkpoints for a 90% sparse GNMT model and a similar 90% sparse but with 2x2 sparsity blocks pattern. See table below and our Model Zoo. You can use these models to Run Inference using our Pre-Trained Models and evaluate them.

Model | Sparsity | BLEU | Non-Zero Parameters | Data Type
Baseline | 0% | 29.9 | ~210M | Float32
Sparse | 90% | 28.4 | ~22M | Float32
2x2 Block Sparse | 90% | 27.8 | ~22M | Float32
Quantized Sparse | 90% | 28.4 | ~22M | Integer8
Quantized 2x2 Block Sparse | 90% | 27.6 | ~22M | Integer8

1. The pruning is applied to the embedding, decoder projection layer and all LSTM layers in both the encoder and decoder.
2. BLEU score is measured using newstest2015 test set provided by the Shared Task.
3. The accuracy of the quantized model was measure when we converted the 8 bits weights back to floating point during inference.

## Running Modalities

Below are simple examples for training 90% sparse `GNMTModel` model, running inference using a pre-trained/trained model, quantizing a model to 8bit Integer and running inference using a quantized model. Before inference, the int8 weights of the sparse and quantized model are de-quantize back to fp32.

### Training

Train a German to English GNMT model with 90% sparsity using the WMT16 dataset:

    # Download the dataset
    wmt16_en_de.sh /tmp/wmt16_en_de

    # Go to examples directory
    cd <nlp_architect root>/examples

    # Train the sparse GNMT
    python -m sparse_gnmt.nmt \
        --src=de --tgt=en \
        --hparams_path=sparse_gnmt/standard_hparams/sparse_wmt16_gnmt_4_layer.json \
        --out_dir=<output directory> \
        --vocab_prefix=/tmp/wmt16_en_de/vocab.bpe.32000 \
        --train_prefix=/tmp/wmt16_en_de/train.tok.clean.bpe.32000 \
        --dev_prefix=/tmp/wmt16_en_de/newstest2013.tok.bpe.32000 \
        --test_prefix=/tmp/wmt16_en_de/newstest2015.tok.bpe.32000

- Train using GPUs by adding `--num_gpus=<n>`
- Model configuration JSON files are found in `examples/sparse_gnmt/standard_hparams` directory.
- Sparsity policy can be re-configured by changing the parameters given in `--pruning_hparams`. E.g. change `target_policy=0.7` in order to train 70% sparse GNMT.
- All pruning hyper parameters are listed in model_pruning.

While training Tensorflow checkpoints, Tensorboard events, Hyper-Parameters used and log files will be saved in the output directory given.

### Inference

Run inference using a trained model:

    # Go to examples directory
    cd <nlp_architect root>/examples

    # Run Inference
    python -m sparse_gnmt.nmt \
    --src=de --tgt=en \
    --hparams_path=sparse_gnmt/standard_hparams/sparse_wmt16_gnmt_4_layer.json \
    --ckpt=<path to a trained checkpoint> \
    --vocab_prefix=/tmp/wmt16_en_de/vocab.bpe.32000 \
    --out_dir=<output directory> \
    --inference_input_file=<file with lines in the source language> \
    --inference_output_file=<target file to place translations>

- Measure performance and BLEU score against a reference file by adding `--inference_ref_file=<reference file in the target language>`
- Inference using GPUs by adding `--num_gpus=<n>`

#### Run Inference using our Pre-Trained Models

Run inference using our pre-trained models:

    # Download pre-trained model zip file, e.g. gnmt_sparse.zip
    wget https://d2zs9tzlek599f.cloudfront.net/models/sparse_gnmt/gnmt_sparse.zip

    # Unzip checkpoint + vocabulary files
    unzip gnmt_sparse.zip -d /tmp/gnmt_sparse_checkpoint

    # Go to examples directory
    cd <nlp_architect root>/examples

    # Run Inference
    python -m sparse_gnmt.nmt \
        --src=de --tgt=en \
        --hparams_path=sparse_gnmt/standard_hparams/sparse_wmt16_gnmt_4_layer.json \
        --ckpt=/tmp/gnmt_sparse_checkpoint/gnmt_sparse.ckpt\
        --vocab_prefix=/tmp/gnmt_sparse_checkpoint/vocab.bpe.32000 \
        --out_dir=<output directory> \
        --inference_input_file=<file with lines in the source language> \
        --inference_output_file=<target file to place translations>

Important Note: use the vocabulary files provided with the checkpoint when using our pre-trained models

#### Quantized Inference

Add the following flags to the Inference command line in order to quantize the pre-trained models and run inference with the quantized models:

- `--quantize_ckpt=true`: Produce a quantized checkpoint. Checkpoint will be saved in the output directory. Inference will run using the produced checkpoint.
- `--from_quantized_ckpt=true`: Inference using an already quantized checkpoint

### Custom Training/Inference Parameters

All customizable parameters can be obtained by running: `python -m nlp-architect.examples.sparse_gnmt.nmt -h`

> `-h, --help` | show this help message and exit
> `--num_units NUM_UNITS`
> | Network size.
> `--num_layers NUM_LAYERS`
> | Network depth.
> `--num_encoder_layers NUM_ENCODER_LAYERS`
> | Encoder depth, equal to num_layers if None.
> `--num_decoder_layers NUM_DECODER_LAYERS`
> | Decoder depth, equal to num_layers if None.
> `--encoder_type` | uni | bi | gnmt. For bi, we build num_encoder_layers/2 bi-directional layers. For gnmt, we build 1 bi- directional layer, and (num_encoder_layers - 1) uni- directional layers.
> `--residual` | Whether to add residual connections.
> `--time_major` | Whether to use time-major mode for dynamic RNN.
> `--num_embeddings_partitions NUM_EMBEDDINGS_PARTITIONS`
> | Number of partitions for embedding vars.
> `--attention` | luong | scaled_luong | bahdanau | normed_bahdanau or set to “” for no attention
> `--attention_architecture`
> | standard | gnmt | gnmt_v2. standard: use top layer to compute attention. gnmt: GNMT style of computing attention, use previous bottom layer to compute attention. gnmt_v2: similar to gnmt, but use current bottom layer to compute attention.
> `--output_attention`
> | Only used in standard attention_architecture. Whether use attention as the cell output at each timestep.
> `--pass_hidden_state`
> | Whether to pass encoder’s hidden state to decoder when using an attention based model.
> `--optimizer` | sgd | adam
> `--learning_rate LEARNING_RATE`
> | Learning rate. Adam: 0.001 | 0.0001
> `--warmup_steps WARMUP_STEPS`
> | How many steps we inverse-decay learning.
> `--warmup_scheme`
> | How to warmup learning rates. Options include: t2t: Tensor2Tensor’s way, start with lr 100 times smaller, then exponentiate until the specified lr.
> `--decay_scheme` | How we decay learning rate. Options include: luong234: after 2/3 num train steps, we start halving the learning rate for 4 times before finishing. luong5: after 1/2 num train steps, we start halving the learning rate for 5 times before finishing. luong10: after 1/2 num train steps, we start halving the learning rate for 10 times before finishing.
> `--num_train_steps NUM_TRAIN_STEPS`
> | Num steps to train.
> `--colocate_gradients_with_ops`
> | Whether try colocating gradients with corresponding op
> `--init_op` | uniform | glorot_normal | glorot_uniform
> `--init_weight INIT_WEIGHT`
> | for uniform init_op, initialize weights between .
> `--src SRC` | Source suffix, e.g., en.
> `--tgt TGT` | Target suffix, e.g., de.
> `--train_prefix TRAIN_PREFIX`
> | Train prefix, expect files with src/tgt suffixes.
> `--dev_prefix DEV_PREFIX`
> | Dev prefix, expect files with src/tgt suffixes.
> `--test_prefix TEST_PREFIX`
> | Test prefix, expect files with src/tgt suffixes.
> `--out_dir OUT_DIR`
> | Store log/model files.
> `--vocab_prefix VOCAB_PREFIX`
> | Vocab prefix, expect files with src/tgt suffixes.
> `--embed_prefix EMBED_PREFIX`
> | Pretrained embedding prefix, expect files with src/tgt suffixes. The embedding files should be Glove formatted txt files.
> `--sos SOS` | Start-of-sentence symbol.
> `--eos EOS` | End-of-sentence symbol.
> `--share_vocab` | Whether to use the source vocab and embeddings for both source and target.
> `--check_special_token CHECK_SPECIAL_TOKEN`
> | Whether check special sos, eos, unk tokens exist in the vocab files.
> `--src_max_len SRC_MAX_LEN`
> | Max length of src sequences during training.
> `--tgt_max_len TGT_MAX_LEN`
> | Max length of tgt sequences during training.
> `--src_max_len_infer SRC_MAX_LEN_INFER`
> | Max length of src sequences during inference.
> `--tgt_max_len_infer TGT_MAX_LEN_INFER`
> | Max length of tgt sequences during inference. Also used to restrict the maximum decoding length.
> `--unit_type` | lstm | gru | layer_norm_lstm | nas | mlstm
> `--projection_type`
> | dense | sparse
> `--embedding_type`
> | dense | sparse
> `--forget_bias FORGET_BIAS`
> | Forget bias for BasicLSTMCell.
> `--dropout DROPOUT`
> | Dropout rate (not keep_prob)
> `--max_gradient_norm MAX_GRADIENT_NORM`
> | Clip gradients to this norm.
> `--batch_size BATCH_SIZE`
> | Batch size.
> `--steps_per_stats STEPS_PER_STATS`
> | How many training steps to do per stats logging.Save checkpoint every 10x steps_per_stats
> `--max_train MAX_TRAIN`
> | Limit on the size of training data (0: no limit).
> `--num_buckets NUM_BUCKETS`
> | Put data into similar-length buckets.
> `--num_sampled_softmax NUM_SAMPLED_SOFTMAX`
> | Use sampled_softmax_loss if > 0.Otherwise, use full softmax loss.
> `--subword_option`
> | Set to bpe or spm to activate subword desegmentation.
> `--use_char_encode USE_CHAR_ENCODE`
> | Whether to split each word or bpe into character, and then generate the word-level representation from the character representation.
> `--num_gpus NUM_GPUS`
> | Number of gpus in each worker.
> `--log_device_placement`
> | Debug GPU allocation.
> `--metrics METRICS`
> | Comma-separated list of evaluations metrics (bleu,rouge,accuracy)
> `--steps_per_external_eval STEPS_PER_EXTERNAL_EVAL`
> | How many training steps to do per external evaluation. Automatically set based on data if None.
> `--scope SCOPE` | scope to put variables under
> `--hparams_path HPARAMS_PATH`
> | Path to standard hparams json file that overrides hparams values from FLAGS.
> `--random_seed RANDOM_SEED`
> | Random seed (>0, set a specific seed).
> `--override_loaded_hparams`
> | Override loaded hparams with values specified
> `--num_keep_ckpts NUM_KEEP_CKPTS`
> | Max number of checkpoints to keep.
> `--avg_ckpts` | Average the last N checkpoints for external evaluation. N can be controlled by setting –num_keep_ckpts.
> `--language_model`
> | True to train a language model, ignoring encoder
> `--ckpt CKPT` | Checkpoint file to load a model for inference.
> `--quantize_ckpt QUANTIZE_CKPT`
> | Set to True to produce a quantized checkpoint from existing checkpoint
> `--from_quantized_ckpt FROM_QUANTIZED_CKPT`
> | Set to True when the given checkpoint is quantized
> `--inference_input_file INFERENCE_INPUT_FILE`
> | Set to the text to decode.
> `--inference_list INFERENCE_LIST`
> | A comma-separated list of sentence indices (0-based) to decode.
> `--infer_batch_size INFER_BATCH_SIZE`
> | Batch size for inference mode.
> `--inference_output_file INFERENCE_OUTPUT_FILE`
> | Output file to store decoding results.
> `--inference_ref_file INFERENCE_REF_FILE`
> | Reference file to compute evaluation scores (if provided).
> `--infer_mode` | Which type of decoder to use during inference.
> `--beam_width BEAM_WIDTH`
> | beam width when using beam search decoder. If 0 (default), use standard decoder with greedy helper.
> `--length_penalty_weight LENGTH_PENALTY_WEIGHT`
> | Length penalty for beam search.
> `--sampling_temperature SAMPLING_TEMPERATURE`
> | Softmax sampling temperature for inference decoding, 0.0 means greedy decoding. This option is ignored when using beam search.
> `--num_translations_per_input NUM_TRANSLATIONS_PER_INPUT`
> | Number of translations generated for each sentence. This is only used for inference.
> `--jobid JOBID` | Task id of the worker.
> `--num_workers NUM_WORKERS`
> | Number of workers (inference only).
> `--num_inter_threads NUM_INTER_THREADS`
> | number of inter_op_parallelism_threads
> `--num_intra_threads NUM_INTRA_THREADS`
> | number of intra_op_parallelism_threads
> `--pruning_hparams PRUNING_HPARAMS`
> | model pruning parameters

## References

[1] | (1, 2) Wu, Yonghui and Schuster, Mike and Chen, Zhifeng and Le, Quoc V and Norouzi, Mohammad and Macherey, Wolfgang and Krikun, Maxim and Cao, Yuan and Gao, Qin and Macherey, Klaus and others. Google’s neural machine translation system: Bridging the gap between human and machine translation. https://arxiv.org/pdf/1609.08144.pdf

[2] | Minh-Thang Luong and Eugene Brevdo and Rui Zhao. Neural Machine Translation (seq2seq) Tutorial. https://github.com/tensorflow/nmt

[3] | (1, 2) Zhu, Michael and Gupta, Suyog. To prune, or not to prune: exploring the efficacy of pruning for model compression. https://arxiv.org/pdf/1710.01878.pdf

[4] | A Parallel Corpus for Statistical Machine Translation, Philipp Koehn, MT Summit 2005