|  | |
|  | |
|  | |
| # Skip-Thought Vectors | |
| This is a TensorFlow implementation of the model described in: | |
| Jamie Ryan Kiros, Yukun Zhu, Ruslan Salakhutdinov, Richard S. Zemel, | |
| Antonio Torralba, Raquel Urtasun, Sanja Fidler. | |
| [Skip-Thought Vectors](https://papers.nips.cc/paper/5950-skip-thought-vectors.pdf). | |
| *In NIPS, 2015.* | |
| ## Contact | |
| ***Code author:*** Chris Shallue | |
| ***Pull requests and issues:*** @cshallue | |
| ## Contents | |
| * [Model Overview](#model-overview) | |
| * [Getting Started](#getting-started) | |
| * [Install Required Packages](#install-required-packages) | |
| * [Download Pretrained Models (Optional)](#download-pretrained-models-optional) | |
| * [Training a Model](#training-a-model) | |
| * [Prepare the Training Data](#prepare-the-training-data) | |
| * [Run the Training Script](#run-the-training-script) | |
| * [Track Training Progress](#track-training-progress) | |
| * [Expanding the Vocabulary](#expanding-the-vocabulary) | |
| * [Overview](#overview) | |
| * [Preparation](#preparation) | |
| * [Run the Vocabulary Expansion Script](#run-the-vocabulary-expansion-script) | |
| * [Evaluating a Model](#evaluating-a-model) | |
| * [Overview](#overview-1) | |
| * [Preparation](#preparation-1) | |
| * [Run the Evaluation Tasks](#run-the-evaluation-tasks) | |
| * [Encoding Sentences](#encoding-sentences) | |
| ## Model overview | |
| The *Skip-Thoughts* model is a sentence encoder. It learns to encode input | |
| sentences into a fixed-dimensional vector representation that is useful for many | |
| tasks, for example to detect paraphrases or to classify whether a product review | |
| is positive or negative. See the | |
| [Skip-Thought Vectors](https://papers.nips.cc/paper/5950-skip-thought-vectors.pdf) | |
| paper for details of the model architecture and more example applications. | |
| A trained *Skip-Thoughts* model will encode similar sentences nearby each other | |
| in the embedding vector space. The following examples show the nearest neighbor by | |
| cosine similarity of some sentences from the | |
| [movie review dataset](https://www.cs.cornell.edu/people/pabo/movie-review-data/). | |
| | Input sentence | Nearest Neighbor | | |
| |----------------|------------------| | |
| | Simplistic, silly and tedious. | Trite, banal, cliched, mostly inoffensive. | | |
| | Not so much farcical as sour. | Not only unfunny, but downright repellent. | | |
| | A sensitive and astute first feature by Anne-Sophie Birot. | Absorbing character study by André Turpin . | | |
| | An enthralling, entertaining feature. | A slick, engrossing melodrama. | | |
| ## Getting Started | |
| ### Install Required Packages | |
| First ensure that you have installed the following required packages: | |
| * **Bazel** ([instructions](http://bazel.build/docs/install.html)) | |
| * **TensorFlow** ([instructions](https://www.tensorflow.org/install/)) | |
| * **NumPy** ([instructions](http://www.scipy.org/install.html)) | |
| * **scikit-learn** ([instructions](http://scikit-learn.org/stable/install.html)) | |
| * **Natural Language Toolkit (NLTK)** | |
| * First install NLTK ([instructions](http://www.nltk.org/install.html)) | |
| * Then install the NLTK data ([instructions](http://www.nltk.org/data.html)) | |
| * **gensim** ([instructions](https://radimrehurek.com/gensim/install.html)) | |
| * Only required if you will be expanding your vocabulary with the [word2vec](https://code.google.com/archive/p/word2vec/) model. | |
| ### Download Pretrained Models (Optional) | |
| You can download model checkpoints pretrained on the | |
| [BookCorpus](http://yknzhu.wixsite.com/mbweb) dataset in the following | |
| configurations: | |
| * Unidirectional RNN encoder ("uni-skip" in the paper) | |
| * Bidirectional RNN encoder ("bi-skip" in the paper) | |
| ```shell | |
| # Directory to download the pretrained models to. | |
| PRETRAINED_MODELS_DIR="${HOME}/skip_thoughts/pretrained/" | |
| mkdir -p ${PRETRAINED_MODELS_DIR} | |
| cd ${PRETRAINED_MODELS_DIR} | |
| # Download and extract the unidirectional model. | |
| wget "http://download.tensorflow.org/models/skip_thoughts_uni_2017_02_02.tar.gz" | |
| tar -xvf skip_thoughts_uni_2017_02_02.tar.gz | |
| rm skip_thoughts_uni_2017_02_02.tar.gz | |
| # Download and extract the bidirectional model. | |
| wget "http://download.tensorflow.org/models/skip_thoughts_bi_2017_02_16.tar.gz" | |
| tar -xvf skip_thoughts_bi_2017_02_16.tar.gz | |
| rm skip_thoughts_bi_2017_02_16.tar.gz | |
| ``` | |
| You can now skip to the sections [Evaluating a Model](#evaluating-a-model) and | |
| [Encoding Sentences](#encoding-sentences). | |
| ## Training a Model | |
| ### Prepare the Training Data | |
| To train a model you will need to provide training data in TFRecord format. The | |
| TFRecord format consists of a set of sharded files containing serialized | |
| `tf.Example` protocol buffers. Each `tf.Example` proto contains three | |
| sentences: | |
| * `encode`: The sentence to encode. | |
| * `decode_pre`: The sentence preceding `encode` in the original text. | |
| * `decode_post`: The sentence following `encode` in the original text. | |
| Each sentence is a list of words. During preprocessing, a dictionary is created | |
| that assigns each word in the vocabulary to an integer-valued id. Each sentence | |
| is encoded as a list of integer word ids in the `tf.Example` protos. | |
| We have provided a script to preprocess any set of text-files into this format. | |
| You may wish to use the [BookCorpus](http://yknzhu.wixsite.com/mbweb) dataset. | |
| Note that the preprocessing script may take **12 hours** or more to complete | |
| on this large dataset. | |
| ```shell | |
| # Comma-separated list of globs matching the input input files. The format of | |
| # the input files is assumed to be a list of newline-separated sentences, where | |
| # each sentence is already tokenized. | |
| INPUT_FILES="${HOME}/skip_thoughts/bookcorpus/*.txt" | |
| # Location to save the preprocessed training and validation data. | |
| DATA_DIR="${HOME}/skip_thoughts/data" | |
| # Build the preprocessing script. | |
| cd tensorflow-models/skip_thoughts | |
| bazel build -c opt //skip_thoughts/data:preprocess_dataset | |
| # Run the preprocessing script. | |
| bazel-bin/skip_thoughts/data/preprocess_dataset \ | |
| --input_files=${INPUT_FILES} \ | |
| --output_dir=${DATA_DIR} | |
| ``` | |
| When the script finishes you will find 100 training files and 1 validation file | |
| in `DATA_DIR`. The files will match the patterns `train-?????-of-00100` and | |
| `validation-00000-of-00001` respectively. | |
| The script will also produce a file named `vocab.txt`. The format of this file | |
| is a list of newline-separated words where the word id is the corresponding 0- | |
| based line index. Words are sorted by descending order of frequency in the input | |
| data. Only the top 20,000 words are assigned unique ids; all other words are | |
| assigned the "unknown id" of 1 in the processed data. | |
| ### Run the Training Script | |
| Execute the following commands to start the training script. By default it will | |
| run for 500k steps (around 9 days on a GeForce GTX 1080 GPU). | |
| ```shell | |
| # Directory containing the preprocessed data. | |
| DATA_DIR="${HOME}/skip_thoughts/data" | |
| # Directory to save the model. | |
| MODEL_DIR="${HOME}/skip_thoughts/model" | |
| # Build the model. | |
| cd tensorflow-models/skip_thoughts | |
| bazel build -c opt //skip_thoughts/... | |
| # Run the training script. | |
| bazel-bin/skip_thoughts/train \ | |
| --input_file_pattern="${DATA_DIR}/train-?????-of-00100" \ | |
| --train_dir="${MODEL_DIR}/train" | |
| ``` | |
| ### Track Training Progress | |
| Optionally, you can run the `track_perplexity` script in a separate process. | |
| This will log per-word perplexity on the validation set which allows training | |
| progress to be monitored on | |
| [TensorBoard](https://www.tensorflow.org/get_started/summaries_and_tensorboard). | |
| Note that you may run out of memory if you run the this script on the same GPU | |
| as the training script. You can set the environment variable | |
| `CUDA_VISIBLE_DEVICES=""` to force the script to run on CPU. If it runs too | |
| slowly on CPU, you can decrease the value of `--num_eval_examples`. | |
| ```shell | |
| DATA_DIR="${HOME}/skip_thoughts/data" | |
| MODEL_DIR="${HOME}/skip_thoughts/model" | |
| # Ignore GPU devices (only necessary if your GPU is currently memory | |
| # constrained, for example, by running the training script). | |
| export CUDA_VISIBLE_DEVICES="" | |
| # Run the evaluation script. This will run in a loop, periodically loading the | |
| # latest model checkpoint file and computing evaluation metrics. | |
| bazel-bin/skip_thoughts/track_perplexity \ | |
| --input_file_pattern="${DATA_DIR}/validation-?????-of-00001" \ | |
| --checkpoint_dir="${MODEL_DIR}/train" \ | |
| --eval_dir="${MODEL_DIR}/val" \ | |
| --num_eval_examples=50000 | |
| ``` | |
| If you started the `track_perplexity` script, run a | |
| [TensorBoard](https://www.tensorflow.org/get_started/summaries_and_tensorboard) | |
| server in a separate process for real-time monitoring of training summaries and | |
| validation perplexity. | |
| ```shell | |
| MODEL_DIR="${HOME}/skip_thoughts/model" | |
| # Run a TensorBoard server. | |
| tensorboard --logdir="${MODEL_DIR}" | |
| ``` | |
| ## Expanding the Vocabulary | |
| ### Overview | |
| The vocabulary generated by the preprocessing script contains only 20,000 words | |
| which is insufficient for many tasks. For example, a sentence from Wikipedia | |
| might contain nouns that do not appear in this vocabulary. | |
| A solution to this problem described in the | |
| [Skip-Thought Vectors](https://papers.nips.cc/paper/5950-skip-thought-vectors.pdf) | |
| paper is to learn a mapping that transfers word representations from one model to | |
| another. This idea is based on the "Translation Matrix" method from the paper | |
| [Exploiting Similarities Among Languages for Machine Translation](https://arxiv.org/abs/1309.4168). | |
| Specifically, we will load the word embeddings from a trained *Skip-Thoughts* | |
| model and from a trained [word2vec model](https://arxiv.org/pdf/1301.3781.pdf) | |
| (which has a much larger vocabulary). We will train a linear regression model | |
| without regularization to learn a linear mapping from the word2vec embedding | |
| space to the *Skip-Thoughts* embedding space. We will then apply the linear | |
| model to all words in the word2vec vocabulary, yielding vectors in the *Skip- | |
| Thoughts* word embedding space for the union of the two vocabularies. | |
| The linear regression task is to learn a parameter matrix *W* to minimize | |
| *|| X - Y \* W ||<sup>2</sup>*, where *X* is a matrix of *Skip-Thoughts* | |
| embeddings of shape `[num_words, dim1]`, *Y* is a matrix of word2vec embeddings | |
| of shape `[num_words, dim2]`, and *W* is a matrix of shape `[dim2, dim1]`. | |
| ### Preparation | |
| First you will need to download and unpack a pretrained | |
| [word2vec model](https://arxiv.org/pdf/1301.3781.pdf) from | |
| [this website](https://code.google.com/archive/p/word2vec/) | |
| ([direct download link](https://drive.google.com/file/d/0B7XkCwpI5KDYNlNUTTlSS21pQmM/edit?usp=sharing)). | |
| This model was trained on the Google News dataset (about 100 billion words). | |
| Also ensure that you have already [installed gensim](https://radimrehurek.com/gensim/install.html). | |
| ### Run the Vocabulary Expansion Script | |
| ```shell | |
| # Path to checkpoint file or a directory containing checkpoint files (the script | |
| # will select the most recent). | |
| CHECKPOINT_PATH="${HOME}/skip_thoughts/model/train" | |
| # Vocabulary file generated by the preprocessing script. | |
| SKIP_THOUGHTS_VOCAB="${HOME}/skip_thoughts/data/vocab.txt" | |
| # Path to downloaded word2vec model. | |
| WORD2VEC_MODEL="${HOME}/skip_thoughts/googlenews/GoogleNews-vectors-negative300.bin" | |
| # Output directory. | |
| EXP_VOCAB_DIR="${HOME}/skip_thoughts/exp_vocab" | |
| # Build the vocabulary expansion script. | |
| cd tensorflow-models/skip_thoughts | |
| bazel build -c opt //skip_thoughts:vocabulary_expansion | |
| # Run the vocabulary expansion script. | |
| bazel-bin/skip_thoughts/vocabulary_expansion \ | |
| --skip_thoughts_model=${CHECKPOINT_PATH} \ | |
| --skip_thoughts_vocab=${SKIP_THOUGHTS_VOCAB} \ | |
| --word2vec_model=${WORD2VEC_MODEL} \ | |
| --output_dir=${EXP_VOCAB_DIR} | |
| ``` | |
| ## Evaluating a Model | |
| ### Overview | |
| The model can be evaluated using the benchmark tasks described in the | |
| [Skip-Thought Vectors](https://papers.nips.cc/paper/5950-skip-thought-vectors.pdf) | |
| paper. The following tasks are supported (refer to the paper for full details): | |
| * **SICK** semantic relatedness task. | |
| * **MSRP** (Microsoft Research Paraphrase Corpus) paraphrase detection task. | |
| * Binary classification tasks: | |
| * **MR** movie review sentiment task. | |
| * **CR** customer product review task. | |
| * **SUBJ** subjectivity/objectivity task. | |
| * **MPQA** opinion polarity task. | |
| * **TREC** question-type classification task. | |
| ### Preparation | |
| You will need to clone or download the | |
| [skip-thoughts GitHub repository](https://github.com/ryankiros/skip-thoughts) by | |
| [ryankiros](https://github.com/ryankiros) (the first author of the Skip-Thoughts | |
| paper): | |
| ```shell | |
| # Folder to clone the repository to. | |
| ST_KIROS_DIR="${HOME}/skip_thoughts/skipthoughts_kiros" | |
| # Clone the repository. | |
| git clone git@github.com:ryankiros/skip-thoughts.git "${ST_KIROS_DIR}/skipthoughts" | |
| # Make the package importable. | |
| export PYTHONPATH="${ST_KIROS_DIR}/:${PYTHONPATH}" | |
| ``` | |
| You will also need to download the data needed for each evaluation task. See the | |
| instructions [here](https://github.com/ryankiros/skip-thoughts). | |
| For example, the CR (customer review) dataset is found [here](http://nlp.stanford.edu/~sidaw/home/projects:nbsvm). For this task we want the | |
| files `custrev.pos` and `custrev.neg`. | |
| ### Run the Evaluation Tasks | |
| In the following example we will evaluate a unidirectional model ("uni-skip" in | |
| the paper) on the CR task. To use a bidirectional model ("bi-skip" in the | |
| paper), simply pass the flags `--bi_vocab_file`, `--bi_embeddings_file` and | |
| `--bi_checkpoint_path` instead. To use the "combine-skip" model described in the | |
| paper you will need to pass both the unidirectional and bidirectional flags. | |
| ```shell | |
| # Path to checkpoint file or a directory containing checkpoint files (the script | |
| # will select the most recent). | |
| CHECKPOINT_PATH="${HOME}/skip_thoughts/model/train" | |
| # Vocabulary file generated by the vocabulary expansion script. | |
| VOCAB_FILE="${HOME}/skip_thoughts/exp_vocab/vocab.txt" | |
| # Embeddings file generated by the vocabulary expansion script. | |
| EMBEDDINGS_FILE="${HOME}/skip_thoughts/exp_vocab/embeddings.npy" | |
| # Directory containing files custrev.pos and custrev.neg. | |
| EVAL_DATA_DIR="${HOME}/skip_thoughts/eval_data" | |
| # Build the evaluation script. | |
| cd tensorflow-models/skip_thoughts | |
| bazel build -c opt //skip_thoughts:evaluate | |
| # Run the evaluation script. | |
| bazel-bin/skip_thoughts/evaluate \ | |
| --eval_task=CR \ | |
| --data_dir=${EVAL_DATA_DIR} \ | |
| --uni_vocab_file=${VOCAB_FILE} \ | |
| --uni_embeddings_file=${EMBEDDINGS_FILE} \ | |
| --uni_checkpoint_path=${CHECKPOINT_PATH} | |
| ``` | |
| Output: | |
| ```python | |
| [0.82539682539682535, 0.84084880636604775, 0.83023872679045096, | |
| 0.86206896551724133, 0.83554376657824936, 0.85676392572944293, | |
| 0.84084880636604775, 0.83023872679045096, 0.85145888594164454, | |
| 0.82758620689655171] | |
| ``` | |
| The output is a list of accuracies of 10 cross-validation classification models. | |
| To get a single number, simply take the average: | |
| ```python | |
| ipython # Launch iPython. | |
| In [0]: | |
| import numpy as np | |
| np.mean([0.82539682539682535, 0.84084880636604775, 0.83023872679045096, | |
| 0.86206896551724133, 0.83554376657824936, 0.85676392572944293, | |
| 0.84084880636604775, 0.83023872679045096, 0.85145888594164454, | |
| 0.82758620689655171]) | |
| Out [0]: 0.84009936423729525 | |
| ``` | |
| ## Encoding Sentences | |
| In this example we will encode data from the | |
| [movie review dataset](https://www.cs.cornell.edu/people/pabo/movie-review-data/) | |
| (specifically the [sentence polarity dataset v1.0](https://www.cs.cornell.edu/people/pabo/movie-review-data/rt-polaritydata.tar.gz)). | |
| ```python | |
| ipython # Launch iPython. | |
| In [0]: | |
| # Imports. | |
| from __future__ import absolute_import | |
| from __future__ import division | |
| from __future__ import print_function | |
| import numpy as np | |
| import os.path | |
| import scipy.spatial.distance as sd | |
| from skip_thoughts import configuration | |
| from skip_thoughts import encoder_manager | |
| In [1]: | |
| # Set paths to the model. | |
| VOCAB_FILE = "/path/to/vocab.txt" | |
| EMBEDDING_MATRIX_FILE = "/path/to/embeddings.npy" | |
| CHECKPOINT_PATH = "/path/to/model.ckpt-9999" | |
| # The following directory should contain files rt-polarity.neg and | |
| # rt-polarity.pos. | |
| MR_DATA_DIR = "/dir/containing/mr/data" | |
| In [2]: | |
| # Set up the encoder. Here we are using a single unidirectional model. | |
| # To use a bidirectional model as well, call load_model() again with | |
| # configuration.model_config(bidirectional_encoder=True) and paths to the | |
| # bidirectional model's files. The encoder will use the concatenation of | |
| # all loaded models. | |
| encoder = encoder_manager.EncoderManager() | |
| encoder.load_model(configuration.model_config(), | |
| vocabulary_file=VOCAB_FILE, | |
| embedding_matrix_file=EMBEDDING_MATRIX_FILE, | |
| checkpoint_path=CHECKPOINT_PATH) | |
| In [3]: | |
| # Load the movie review dataset. | |
| data = [] | |
| with open(os.path.join(MR_DATA_DIR, 'rt-polarity.neg'), 'rb') as f: | |
| data.extend([line.decode('latin-1').strip() for line in f]) | |
| with open(os.path.join(MR_DATA_DIR, 'rt-polarity.pos'), 'rb') as f: | |
| data.extend([line.decode('latin-1').strip() for line in f]) | |
| In [4]: | |
| # Generate Skip-Thought Vectors for each sentence in the dataset. | |
| encodings = encoder.encode(data) | |
| In [5]: | |
| # Define a helper function to generate nearest neighbors. | |
| def get_nn(ind, num=10): | |
| encoding = encodings[ind] | |
| scores = sd.cdist([encoding], encodings, "cosine")[0] | |
| sorted_ids = np.argsort(scores) | |
| print("Sentence:") | |
| print("", data[ind]) | |
| print("\nNearest neighbors:") | |
| for i in range(1, num + 1): | |
| print(" %d. %s (%.3f)" % | |
| (i, data[sorted_ids[i]], scores[sorted_ids[i]])) | |
| In [6]: | |
| # Compute nearest neighbors of the first sentence in the dataset. | |
| get_nn(0) | |
| ``` | |
| Output: | |
| ``` | |
| Sentence: | |
| simplistic , silly and tedious . | |
| Nearest neighbors: | |
| 1. trite , banal , cliched , mostly inoffensive . (0.247) | |
| 2. banal and predictable . (0.253) | |
| 3. witless , pointless , tasteless and idiotic . (0.272) | |
| 4. loud , silly , stupid and pointless . (0.295) | |
| 5. grating and tedious . (0.299) | |
| 6. idiotic and ugly . (0.330) | |
| 7. black-and-white and unrealistic . (0.335) | |
| 8. hopelessly inane , humorless and under-inspired . (0.335) | |
| 9. shallow , noisy and pretentious . (0.340) | |
| 10. . . . unlikable , uninteresting , unfunny , and completely , utterly inept . (0.346) | |
| ``` | |