{ "cells": [ { "cell_type": "markdown", "metadata": {}, "source": [ "# Supervised sentiment: dense feature representations and neural networks" ] }, { "cell_type": "code", "execution_count": 1, "metadata": {}, "outputs": [], "source": [ "__author__ = \"Christopher Potts\"\n", "__version__ = \"CS224u, Stanford, Fall 2020\"" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Contents\n", "\n", "1. [Overview](#Overview)\n", "1. [Set-up](#Set-up)\n", "1. [Distributed representations as features](#Distributed-representations-as-features)\n", " 1. [GloVe inputs](#GloVe-inputs)\n", " 1. [IMDB representations](#IMDB-representations)\n", " 1. [Remarks on this approach](#Remarks-on-this-approach)\n", "1. [RNN classifiers](#RNN-classifiers)\n", " 1. [RNN dataset preparation](#RNN-dataset-preparation)\n", " 1. [Vocabulary for the embedding](#Vocabulary-for-the-embedding)\n", " 1. [PyTorch RNN classifier](#PyTorch-RNN-classifier)\n", " 1. [Pretrained embeddings](#Pretrained-embeddings)\n", " 1. [RNN hyperparameter tuning experiment](#RNN-hyperparameter-tuning-experiment)\n", "1. [The VecAvg baseline from Socher et al. 2013](#The-VecAvg-baseline-from-Socher-et-al.-2013)\n", " 1. [Defining the model](#Defining-the-model)\n", " 1. [VecAvg hyperparameter tuning experiment](#VecAvg-hyperparameter-tuning-experiment)\n", "1. [Tree-structured neural networks](#Tree-structured-neural-networks)\n", " 1. [TreeNN dataset preparation](#TreeNN-dataset-preparation)\n", " 1. [PyTorch TreeNN](#PyTorch-TreeNN)\n", " 1. [Subtree supervision](#Subtree-supervision)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Overview\n", "\n", "This notebook defines and explores __vector averaging__, __recurrent neural network (RNN) classifiers__ and __tree-structured neural network (TreeNN) classifiers__ for the Stanford Sentiment Treebank. \n", "\n", "These approaches make their predictions based on comprehensive representations of the examples: \n", "\n", "* For the vector averaging models, each word is modeled, but we assume that words combine via a simple function that is insensitive to their order or constituent structure.\n", "* For the RNN, each word is again modeled, and we also model the sequential relationships between words.\n", "* For the TreeNN, the entire parsed structure of the sentence is modeled.\n", "\n", "All these models contrast with the ones explored in [the previous notebook](sst_02_hand_built_features.ipynb), which make predictions based on more partial, potentially idiosyncratic information extracted from the examples." ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Set-up\n", "\n", "See [the first notebook in this unit](sst_01_overview.ipynb#Set-up) for set-up instructions." ] }, { "cell_type": "code", "execution_count": 2, "metadata": {}, "outputs": [], "source": [ "from collections import Counter\n", "import numpy as np\n", "import os\n", "import pandas as pd\n", "from np_rnn_classifier import RNNClassifier\n", "from np_tree_nn import TreeNN\n", "from sklearn.linear_model import LogisticRegression\n", "from sklearn.metrics import classification_report\n", "import torch\n", "import torch.nn as nn\n", "from torch_rnn_classifier import TorchRNNClassifier\n", "from torch_tree_nn import TorchTreeNN\n", "import sst\n", "import vsm\n", "import utils" ] }, { "cell_type": "code", "execution_count": 3, "metadata": {}, "outputs": [], "source": [ "utils.fix_random_seeds()" ] }, { "cell_type": "code", "execution_count": 4, "metadata": {}, "outputs": [], "source": [ "DATE_HOME = 'data'\n", "\n", "GLOVE_HOME = os.path.join(DATE_HOME, 'glove.6B')\n", "\n", "VSMDATA_HOME = os.path.join(DATE_HOME, 'vsmdata')\n", "\n", "SST_HOME = os.path.join(DATE_HOME, 'trees')" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Distributed representations as features\n", "\n", "As a first step in the direction of neural networks for sentiment, we can connect with our previous unit on distributed representations. Arguably, more than any specific model architecture, this is the major innovation of deep learning: __rather than designing feature functions by hand, we use dense, distributed representations, often derived from unsupervised models__.\n", "\n", "\"distreps-as-features.png\"\n", "\n", "Our model will just be `LogisticRegression`, and we'll continue with the experiment framework from the previous notebook. Here is `fit_maxent_classifier` again:" ] }, { "cell_type": "code", "execution_count": 5, "metadata": {}, "outputs": [], "source": [ "def fit_maxent_classifier(X, y):\n", " mod = LogisticRegression(\n", " fit_intercept=True,\n", " solver='liblinear',\n", " multi_class='auto')\n", " mod.fit(X, y)\n", " return mod" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### GloVe inputs\n", "\n", "To illustrate this process, we'll use the general purpose GloVe representations released by the GloVe team, at 300d:" ] }, { "cell_type": "code", "execution_count": 6, "metadata": {}, "outputs": [], "source": [ "glove_lookup = utils.glove2dict(\n", " os.path.join(GLOVE_HOME, 'glove.6B.300d.txt'))" ] }, { "cell_type": "code", "execution_count": 7, "metadata": {}, "outputs": [], "source": [ "def vsm_leaves_phi(tree, lookup, np_func=np.mean):\n", " \"\"\"Represent `tree` as a combination of the vector of its words.\n", "\n", " Parameters\n", " ----------\n", " tree : nltk.Tree\n", "\n", " lookup : dict\n", " From words to vectors.\n", "\n", " np_func : function (default: np.sum)\n", " A numpy matrix operation that can be applied columnwise,\n", " like `np.mean`, `np.sum`, or `np.prod`. The requirement is that\n", " the function take `axis=0` as one of its arguments (to ensure\n", " columnwise combination) and that it return a vector of a\n", " fixed length, no matter what the size of the tree is.\n", "\n", " Returns\n", " -------\n", " np.array, dimension `X.shape[1]`\n", "\n", " \"\"\"\n", " allvecs = np.array([lookup[w] for w in tree.leaves() if w in lookup])\n", " if len(allvecs) == 0:\n", " dim = len(next(iter(lookup.values())))\n", " feats = np.zeros(dim)\n", " else:\n", " feats = np_func(allvecs, axis=0)\n", " return feats" ] }, { "cell_type": "code", "execution_count": 8, "metadata": {}, "outputs": [], "source": [ "def glove_leaves_phi(tree, np_func=np.sum):\n", " return vsm_leaves_phi(tree, glove_lookup, np_func=np_func)" ] }, { "cell_type": "code", "execution_count": 9, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ " precision recall f1-score support\n", "\n", " negative 0.746 0.789 0.767 941\n", " positive 0.816 0.778 0.797 1135\n", "\n", " accuracy 0.783 2076\n", " macro avg 0.781 0.783 0.782 2076\n", "weighted avg 0.785 0.783 0.783 2076\n", "\n", "CPU times: user 2.21 s, sys: 276 ms, total: 2.49 s\n", "Wall time: 2.01 s\n" ] } ], "source": [ "%%time\n", "_ = sst.experiment(\n", " SST_HOME,\n", " glove_leaves_phi,\n", " fit_maxent_classifier,\n", " vectorize=False) # Tell `experiment` that we already have our feature vectors." ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### IMDB representations\n", "\n", "Our IMDB VSMs seems pretty well-attuned to the Stanford Sentiment Treebank, so we might think that they can do even better than the general-purpose GloVe inputs. Here are two quick assessments of that idea that seeks to build on ideas we developed in the unit on VSMs." ] }, { "cell_type": "code", "execution_count": 10, "metadata": {}, "outputs": [], "source": [ "imdb20 = pd.read_csv(\n", " os.path.join(VSMDATA_HOME, 'imdb_window20-flat.csv.gz'), index_col=0)" ] }, { "cell_type": "code", "execution_count": 11, "metadata": {}, "outputs": [], "source": [ "imdb20_ppmi = vsm.pmi(imdb20, positive=False)" ] }, { "cell_type": "code", "execution_count": 12, "metadata": {}, "outputs": [], "source": [ "imdb20_ppmi_svd = vsm.lsa(imdb20_ppmi, k=300)" ] }, { "cell_type": "code", "execution_count": 13, "metadata": {}, "outputs": [], "source": [ "imdb_lookup = dict(zip(imdb20_ppmi_svd.index, imdb20_ppmi_svd.values))" ] }, { "cell_type": "code", "execution_count": 14, "metadata": {}, "outputs": [], "source": [ "def imdb_phi(tree, np_func=np.sum):\n", " return vsm_leaves_phi(tree, imdb_lookup, np_func=np_func)" ] }, { "cell_type": "code", "execution_count": 15, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ " precision recall f1-score support\n", "\n", " negative 0.746 0.733 0.739 977\n", " positive 0.766 0.778 0.772 1099\n", "\n", " accuracy 0.757 2076\n", " macro avg 0.756 0.755 0.756 2076\n", "weighted avg 0.757 0.757 0.757 2076\n", "\n", "CPU times: user 2.88 s, sys: 1.06 s, total: 3.94 s\n", "Wall time: 2.04 s\n" ] } ], "source": [ "%%time\n", "_ = sst.experiment(\n", " SST_HOME,\n", " imdb_phi,\n", " fit_maxent_classifier,\n", " vectorize=False) # Tell `experiment` that we already have our feature vectors." ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### Remarks on this approach\n", "\n", "* Recall that our `unigrams_phi` created feature representations with over 16K dimensions and got about 0.77 with no hyperparameter tuning.\n", "\n", "* The above models' feature representations have only 300 dimensions, and they are about the same. In many ways, it's striking that we can get a model that is competitive with so few dimensions.\n", "\n", "* The promise of the Mittens model of [Dingwall and Potts 2018](https://arxiv.org/abs/1803.09901) is that we can use GloVe itself to update the general purpose information in the 'glove.6B' vectors with specialized information from one of these IMDB count matrices. That might be worth trying; the `mittens` package (`pip install mittens`) already implements this!\n", "\n", "* That said, just summing up all the word representations is pretty unappealing linguistically. There's no doubt that we're losing a lot of valuable information in doing this. The models we turn to now can be seen as addressing this shortcoming while retaining the insight that our distributed representations are valuable for this task.\n", "\n", "* We'll return to these ideas below, when we consider [the VecAvg baseline from Socher et al. 2013](#The-VecAvg-baseline-from-Socher-et-al.-2013). That model also posits a simple, fixed combination function (averaging). However, it begins with randomly initialized representations and updates them as part of training." ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## RNN classifiers\n", "\n", "A recurrent neural network (RNN) is any deep learning model that process its inputs sequentially. There are many variations on this theme. The one that we use here is an __RNN classifier__.\n", "\n", "\n", "\n", "The version of the model that is implemented in `np_rnn_classifier.py` corresponds exactly to the above diagram. We can express it mathematically as follows:\n", "\n", "$$\\begin{align*}\n", "h_{t} &= \\tanh(x_{t}W_{xh} + h_{t-1}W_{hh}) \\\\\n", "y &= \\textbf{softmax}(h_{n}W_{hy} + b_y)\n", "\\end{align*}$$\n", "\n", "where $1 \\leqslant t \\leqslant n$. The first line defines the recurrence: each hidden state $h_{t}$ is defined by the input $x_{t}$ and the previous hidden state $h_{t-1}$, together with weight matrices $W_{xh}$ and $W_{hh}$, which are used at all timesteps. As indicated in the above diagram, the sequence of hidden states is padded with an initial state $h_{0}$. In our implementations, this is always an all $0$ vector, but it can be initialized in more sophisticated ways (some of which we will explore in our units on natural language inference and grounded natural language generation).\n", "\n", "The model in `torch_rnn_classifier.py` expands on the above and allows for more flexibility:\n", "\n", "$$\\begin{align*}\n", "h_{t} &= \\text{RNN}(x_{t}, h_{t-1}) \\\\\n", "h &= f(h_{n}W_{hh} + b_{h}) \\\\\n", "y &= \\textbf{softmax}(hW_{hy} + b_y)\n", "\\end{align*}$$\n", "\n", "Here, $\\text{RNN}$ stands for all the parameters of the recurrent part of the model. This will depend on the choice one makes for `rnn_cell_class`; options include `nn.RNN`, `nn.LSTM`, and `nn.GRU`. In addition, the classifier part includes a hidden layer (middle row), and the user can decide on the activation funtion $f$ to use there (parameter: `classifier_activation`).\n", "\n", "This is a potential gain over our average-vectors baseline, in that it processes each word independently, and in the context of those that came before it. Thus, not only is this sensitive to word order, but the hidden representation create the potential to encode how the preceding context for a word affects its interpretation.\n", "\n", "The downside of this, of course, is that this model is much more difficult to set up and optimize. Let's dive into those details." ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### RNN dataset preparation\n", "\n", "SST contains trees, but the RNN processes just the sequence of leaf nodes. The function `sst.build_rnn_dataset` creates datasets in this format:" ] }, { "cell_type": "code", "execution_count": 16, "metadata": {}, "outputs": [], "source": [ "X_rnn_train, y_rnn_train = sst.build_rnn_dataset(\n", " SST_HOME, sst.train_reader, class_func=sst.binary_class_func)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Each member of `X_rnn_train` is a list of lists of words. Here's a look at the start of the first:" ] }, { "cell_type": "code", "execution_count": 17, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "['The', 'Rock', 'is', 'destined', 'to', 'be']" ] }, "execution_count": 17, "metadata": {}, "output_type": "execute_result" } ], "source": [ "X_rnn_train[0][: 6]" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Because this is a classifier, `y_rnn_train` is just a list of labels, one per example:" ] }, { "cell_type": "code", "execution_count": 18, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "'positive'" ] }, "execution_count": 18, "metadata": {}, "output_type": "execute_result" } ], "source": [ "y_rnn_train[0]" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "For experiments, let's build a `dev` dataset as well:" ] }, { "cell_type": "code", "execution_count": 19, "metadata": {}, "outputs": [], "source": [ "X_rnn_dev, y_rnn_dev = sst.build_rnn_dataset(\n", " SST_HOME, sst.dev_reader, class_func=sst.binary_class_func)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### Vocabulary for the embedding\n", "\n", "The first delicate issue we need to address is the vocabulary for our model:\n", "\n", "* As indicated in the figure above, the first thing we do when processing an example is look up the words in an embedding (a VSM), which has to have a fixed dimensionality. \n", "\n", "* We can use our training data to specify the vocabulary for this embedding; at prediction time, though, we will inevitably encounter words we haven't seen before. \n", "\n", "* The convention we adopt here is to map them to an `$UNK` token that is in our pre-specified vocabulary.\n", "\n", "* At the same time, we might want to collapse infrequent tokens into `$UNK` to make optimization easier and to try to create reasonable representations for words that we have to map to `$UNK` at test time.\n", "\n", "In `utils`, the function `get_vocab` will help you specify a vocabulary. It will let you choose a vocabulary by optionally specifying `mincount` or `n_words`, and it will ensure that `$UNK` is included." ] }, { "cell_type": "code", "execution_count": 20, "metadata": {}, "outputs": [], "source": [ "sst_full_train_vocab = utils.get_vocab(X_rnn_train)" ] }, { "cell_type": "code", "execution_count": 21, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "sst_full_train_vocab has 16,283 items\n" ] } ], "source": [ "print(\"sst_full_train_vocab has {:,} items\".format(len(sst_full_train_vocab)))" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "This frankly seems too big relative to our dataset size. Let's restrict to just words that occur at least twice:" ] }, { "cell_type": "code", "execution_count": 22, "metadata": {}, "outputs": [], "source": [ "sst_train_vocab = utils.get_vocab(X_rnn_train, mincount=2)" ] }, { "cell_type": "code", "execution_count": 23, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "sst_train_vocab has 7,564 items\n" ] } ], "source": [ "print(\"sst_train_vocab has {:,} items\".format(len(sst_train_vocab)))" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### PyTorch RNN classifier\n", "\n", "Here and throughout, we'll rely on `early_stopping=True` to try to find the optimal time to stop optimization. This behavior can be further refined by setting different values of `validation_fraction`, `n_iter_no_change`, and `tol`. For additional discussion, see [the section on model convergence in the evaluation methods notebook](#Assessing-models-without-convergence)." ] }, { "cell_type": "code", "execution_count": 24, "metadata": {}, "outputs": [], "source": [ "rnn = TorchRNNClassifier(\n", " sst_train_vocab,\n", " early_stopping=True)" ] }, { "cell_type": "code", "execution_count": 25, "metadata": {}, "outputs": [ { "name": "stderr", "output_type": "stream", "text": [ "Stopping after epoch 52. Validation score did not improve by tol=1e-05 for more than 10 epochs. Final error is 0.14730898616835475" ] }, { "name": "stdout", "output_type": "stream", "text": [ "CPU times: user 36.1 s, sys: 435 ms, total: 36.6 s\n", "Wall time: 8 s\n" ] } ], "source": [ "%time _ = rnn.fit(X_rnn_train, y_rnn_train)" ] }, { "cell_type": "code", "execution_count": 26, "metadata": {}, "outputs": [], "source": [ "rnn_dev_preds = rnn.predict(X_rnn_dev)" ] }, { "cell_type": "code", "execution_count": 27, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ " precision recall f1-score support\n", "\n", " negative 0.702 0.743 0.722 428\n", " positive 0.737 0.696 0.716 444\n", "\n", " accuracy 0.719 872\n", " macro avg 0.720 0.719 0.719 872\n", "weighted avg 0.720 0.719 0.719 872\n", "\n" ] } ], "source": [ "print(classification_report(y_rnn_dev, rnn_dev_preds, digits=3))" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "The above numbers are just a starting point. Let's try to improve on them by using pretrained embeddings and then by exploring a range of hyperparameter options." ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### Pretrained embeddings" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "With `embedding=None`, `TorchRNNClassifier` (and its counterpart in `np_rnn_classifier.py`) create random embeddings. You can also pass in an embedding, as long as you make sure it has the right vocabulary. The utility `utils.create_pretrained_embedding` will help with that:" ] }, { "cell_type": "code", "execution_count": 28, "metadata": {}, "outputs": [], "source": [ "glove_embedding, sst_glove_vocab = utils.create_pretrained_embedding(\n", " glove_lookup, sst_train_vocab)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Here's an illustration using `TorchRNNClassifier`:" ] }, { "cell_type": "code", "execution_count": 29, "metadata": {}, "outputs": [], "source": [ "rnn_glove = TorchRNNClassifier(\n", " sst_glove_vocab,\n", " embedding=glove_embedding,\n", " early_stopping=True)" ] }, { "cell_type": "code", "execution_count": 30, "metadata": {}, "outputs": [ { "name": "stderr", "output_type": "stream", "text": [ "Stopping after epoch 23. Validation score did not improve by tol=1e-05 for more than 10 epochs. Final error is 0.12253877334296703" ] }, { "name": "stdout", "output_type": "stream", "text": [ "CPU times: user 15 s, sys: 14.7 ms, total: 15 s\n", "Wall time: 2.74 s\n" ] } ], "source": [ "%time _ = rnn_glove.fit(X_rnn_train, y_rnn_train)" ] }, { "cell_type": "code", "execution_count": 31, "metadata": {}, "outputs": [], "source": [ "rnn_glove_dev_preds = rnn_glove.predict(X_rnn_dev)" ] }, { "cell_type": "code", "execution_count": 32, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ " precision recall f1-score support\n", "\n", " negative 0.824 0.724 0.771 428\n", " positive 0.762 0.851 0.804 444\n", "\n", " accuracy 0.789 872\n", " macro avg 0.793 0.788 0.788 872\n", "weighted avg 0.793 0.789 0.788 872\n", "\n" ] } ], "source": [ "print(classification_report(y_rnn_dev, rnn_glove_dev_preds, digits=3))" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "It looks like pretrained representations give us a notable boost, but we're still below most of the simpler models explored in [the previous notebook](sst_02_hand_built_features.ipynb)." ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### RNN hyperparameter tuning experiment\n", "\n", "As we saw in [the previous notebook](sst_02_hand_built_features.ipynb), we're not really done until we've done some hyperparameter search. So let's round out this section by cross-validating the RNN that uses GloVe embeddings, to see if we can improve on the default-parameters model we evaluated just above. For this, we'll use `sst.experiment`:" ] }, { "cell_type": "code", "execution_count": 33, "metadata": {}, "outputs": [], "source": [ "def simple_leaves_phi(tree):\n", " return tree.leaves()" ] }, { "cell_type": "code", "execution_count": 34, "metadata": {}, "outputs": [], "source": [ "def fit_rnn_with_hyperparameter_search(X, y):\n", " basemod = TorchRNNClassifier(\n", " sst_train_vocab,\n", " embedding=glove_embedding,\n", " batch_size=25, # Inspired by comments in the paper.\n", " bidirectional=True,\n", " early_stopping=True)\n", "\n", " # There are lots of other parameters and values we could\n", " # explore, but this is at least a solid start:\n", " param_grid = {\n", " 'embed_dim': [25, 50, 75, 100],\n", " 'hidden_dim': [25, 50, 75, 100],\n", " 'eta': [0.001, 0.01, 0.05]}\n", "\n", " bestmod = utils.fit_classifier_with_hyperparameter_search(\n", " X, y, basemod, cv=3, param_grid=param_grid)\n", "\n", " return bestmod" ] }, { "cell_type": "code", "execution_count": 35, "metadata": {}, "outputs": [ { "name": "stderr", "output_type": "stream", "text": [ "Stopping after epoch 13. Validation score did not improve by tol=1e-05 for more than 10 epochs. Final error is 3.154094541274389555" ] }, { "name": "stdout", "output_type": "stream", "text": [ "Best params: {'embed_dim': 75, 'eta': 0.001, 'hidden_dim': 75}\n", "Best score: 0.791\n", " precision recall f1-score support\n", "\n", " negative 0.769 0.834 0.800 1002\n", " positive 0.832 0.766 0.798 1074\n", "\n", " accuracy 0.799 2076\n", " macro avg 0.801 0.800 0.799 2076\n", "weighted avg 0.802 0.799 0.799 2076\n", "\n", "CPU times: user 34min 15s, sys: 15.6 s, total: 34min 30s\n", "Wall time: 34min 16s\n" ] } ], "source": [ "%%time\n", "rnn_experiment_xval = sst.experiment(\n", " SST_HOME,\n", " simple_leaves_phi,\n", " fit_rnn_with_hyperparameter_search,\n", " vectorize=False)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Here we carry forward the optimal model from our hyperparameter search, to run a final assessment on the test set:" ] }, { "cell_type": "code", "execution_count": 36, "metadata": {}, "outputs": [], "source": [ "def fit_optimized_rnn(X, y):\n", " mod = rnn_experiment_xval['model']\n", " mod.fit(X, y)\n", " return mod" ] }, { "cell_type": "code", "execution_count": 37, "metadata": {}, "outputs": [ { "name": "stderr", "output_type": "stream", "text": [ "Stopping after epoch 12. Validation score did not improve by tol=1e-05 for more than 10 epochs. Final error is 2.3395959859716413" ] }, { "name": "stdout", "output_type": "stream", "text": [ " precision recall f1-score support\n", "\n", " negative 0.815 0.826 0.820 912\n", " positive 0.823 0.812 0.817 909\n", "\n", " accuracy 0.819 1821\n", " macro avg 0.819 0.819 0.819 1821\n", "weighted avg 0.819 0.819 0.819 1821\n", "\n", "CPU times: user 21.4 s, sys: 102 ms, total: 21.5 s\n", "Wall time: 21.4 s\n" ] } ], "source": [ "%%time\n", "_ = sst.experiment(\n", " SST_HOME,\n", " simple_leaves_phi,\n", " fit_optimized_rnn,\n", " class_func=sst.binary_class_func,\n", " train_reader=(sst.train_reader, sst.dev_reader),\n", " assess_reader=sst.test_reader,\n", " vectorize=False)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "This model looks quite competitive with the simpler models we explored previously, and perhaps an even wider hyperparameter search would lead to additional improvements. In [contextualreps.ipynb](contextualreps.ipynb), we look at variants of the above that involve fine-tuning with ELMo and BERT, and those models achieve results around 0.90 on the test set, which further highlights the value of rich pretraining." ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## The VecAvg baseline from Socher et al. 2013\n", "\n", "One of the baseline models from [Socher et al., Table 1](http://www.aclweb.org/anthology/D/D13/D13-1170.pdf) is __VecAvg__. This is like the model we explored above under the heading of [Distributed representations as features](#Distributed-representations-as-features), but it uses a random initial embedding that is updated as part of optimization. Another perspective on it is that it is like the RNN we just evaluated, but with the RNN parameters replaced by averaging. \n", "\n", "In Socher et al. 2013, this model does reasonably well, scoring 80.1 on the root-only binary problem. In this section, we reimplement it, relying on `TorchRNNClassifier` to handle most of the heavy-lifting, and we evaluate it with a reasonably wide hyperparameter search." ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### Defining the model\n", "\n", "The core model is `TorchVecAvgModel`, which just looks up embeddings, averages them, and feeds the result to a classifier layer:" ] }, { "cell_type": "code", "execution_count": 38, "metadata": {}, "outputs": [], "source": [ "class TorchVecAvgModel(nn.Module):\n", " def __init__(self, vocab_size, output_dim, device, embed_dim=50):\n", " super().__init__()\n", " self.vocab_size = vocab_size\n", " self.embed_dim = embed_dim\n", " self.output_dim = output_dim\n", " self.device = device\n", " self.embedding = nn.Embedding(self.vocab_size, self.embed_dim)\n", " self.classifier_layer = nn.Linear(self.embed_dim, self.output_dim)\n", "\n", " def forward(self, X, seq_lengths):\n", " embs = self.embedding(X)\n", " # Mask based on the **true** lengths:\n", " mask = [torch.ones(l, self.embed_dim) for l in seq_lengths]\n", " mask = torch.nn.utils.rnn.pad_sequence(mask, batch_first=True)\n", " mask = mask.to(self.device)\n", " # True average:\n", " mu = (embs * mask).sum(axis=1) / seq_lengths.unsqueeze(1)\n", " # Classifier:\n", " logits = self.classifier_layer(mu)\n", " return logits" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "For the main interface, we can just subclass `TorchRNNClassifier` and change the `build_graph` method to use `TorchVecAvgModel`. (For more details on the code and logic here, see the notebook [tutorial_torch_models.ipynb](tutorial_torch_models.ipynb).)" ] }, { "cell_type": "code", "execution_count": 39, "metadata": {}, "outputs": [], "source": [ "class TorchVecAvgClassifier(TorchRNNClassifier):\n", "\n", " def build_graph(self):\n", " return TorchVecAvgModel(\n", " vocab_size=len(self.vocab),\n", " output_dim=self.n_classes_,\n", " device=self.device,\n", " embed_dim=self.embed_dim)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### VecAvg hyperparameter tuning experiment\n", "\n", "Now that we have the model implemented, let's see if we can reproduce Socher et al.'s 80.1 on the binary, root-only version of SST.\n", "\n", "First, we do the hyperparameter search:" ] }, { "cell_type": "code", "execution_count": 40, "metadata": {}, "outputs": [], "source": [ "def fit_vecavg_with_hyperparameter_search(X, y):\n", " basemod = TorchVecAvgClassifier(\n", " sst_train_vocab,\n", " early_stopping=True)\n", "\n", " param_grid = {\n", " 'embed_dim': [50, 100, 200, 300],\n", " 'eta': [0.001, 0.01, 0.05]}\n", "\n", " bestmod = utils.fit_classifier_with_hyperparameter_search(\n", " X, y, basemod, cv=3, param_grid=param_grid)\n", "\n", " return bestmod" ] }, { "cell_type": "code", "execution_count": 41, "metadata": {}, "outputs": [ { "name": "stderr", "output_type": "stream", "text": [ "Stopping after epoch 18. Validation score did not improve by tol=1e-05 for more than 10 epochs. Final error is 0.080092592164874088" ] }, { "name": "stdout", "output_type": "stream", "text": [ "Best params: {'embed_dim': 300, 'eta': 0.01}\n", "Best score: 0.766\n", " precision recall f1-score support\n", "\n", " negative 0.786 0.715 0.749 1011\n", " positive 0.751 0.815 0.782 1065\n", "\n", " accuracy 0.766 2076\n", " macro avg 0.768 0.765 0.765 2076\n", "weighted avg 0.768 0.766 0.766 2076\n", "\n", "CPU times: user 16min 49s, sys: 23.2 s, total: 17min 13s\n", "Wall time: 4min 47s\n" ] } ], "source": [ "%%time\n", "vecavg_experiment_xval = sst.experiment(\n", " SST_HOME,\n", " simple_leaves_phi,\n", " fit_vecavg_with_hyperparameter_search,\n", " vectorize=False)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "And then we use the best parameters found above to train a new model on the union of the train and dev sets:" ] }, { "cell_type": "code", "execution_count": 42, "metadata": {}, "outputs": [], "source": [ "def fit_optimized_vecavg(X, y):\n", " mod = vecavg_experiment_xval['model']\n", " mod.fit(X, y)\n", " return mod" ] }, { "cell_type": "code", "execution_count": 43, "metadata": {}, "outputs": [ { "name": "stderr", "output_type": "stream", "text": [ "Stopping after epoch 17. Validation score did not improve by tol=1e-05 for more than 10 epochs. Final error is 0.14763524942100048" ] }, { "name": "stdout", "output_type": "stream", "text": [ " precision recall f1-score support\n", "\n", " negative 0.798 0.823 0.811 912\n", " positive 0.817 0.791 0.804 909\n", "\n", " accuracy 0.807 1821\n", " macro avg 0.808 0.807 0.807 1821\n", "weighted avg 0.808 0.807 0.807 1821\n", "\n", "CPU times: user 29.5 s, sys: 2.14 s, total: 31.6 s\n", "Wall time: 9.93 s\n" ] } ], "source": [ "%%time\n", "_= sst.experiment(\n", " SST_HOME,\n", " simple_leaves_phi,\n", " fit_optimized_vecavg,\n", " class_func=sst.binary_class_func,\n", " train_reader=(sst.train_reader, sst.dev_reader),\n", " assess_reader=sst.test_reader,\n", " vectorize=False)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Excellent – it looks like we basically reproduced the number from the paper (80.1)." ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Tree-structured neural networks\n", "\n", "Tree-structured neural networks (TreeNNs) are close relatives of RNN classifiers. (If you tilt your head, you can see the above sequence model as a kind of tree.) The TreeNNs we explore here are the simplest possible and actually have many fewer parameters than RNNs. Here's a summary:\n", "\n", "\n", "\n", "The crucial property of these networks is the way they employ recursion: the representation of a parent node $p$ has the same dimensionality as the word representations, allowing seamless repeated application of the central combination function:\n", "\n", "$$p = \\tanh([x_{L};x_{R}]W_{wh} + b)$$\n", "\n", "Here, $[x_{L};x_{R}]$ is the concatenation of the left and right child representations, and $p$ is the resulting parent node, which can then be a child node in a higher subtree.\n", "\n", "When we reach the root node $h_{r}$ of the tree, we apply a softmax classifier using that top node's representation:\n", "\n", "$$y = \\textbf{softmax}(h_{r}W_{hy} + b)$$" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### TreeNN dataset preparation\n", "\n", "This is the only model under consideration here that makes use of the tree structures in the SST:" ] }, { "cell_type": "code", "execution_count": 44, "metadata": {}, "outputs": [], "source": [ "X_tree_train, y_tree_train = sst.build_tree_dataset(\n", " SST_HOME, sst.train_reader, class_func=sst.binary_class_func)" ] }, { "cell_type": "code", "execution_count": 45, "metadata": {}, "outputs": [ { "data": { "image/png": "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", "text/plain": [ "Tree('positive', [Tree(None, [Tree(None, ['The']), Tree(None, ['Rock'])]), Tree('positive', [Tree('positive', [Tree(None, ['is']), Tree('positive', [Tree(None, ['destined']), Tree(None, [Tree(None, [Tree(None, [Tree(None, [Tree(None, ['to']), Tree(None, [Tree(None, ['be']), Tree(None, [Tree(None, ['the']), Tree(None, [Tree(None, ['21st']), Tree(None, [Tree(None, [Tree(None, ['Century']), Tree(None, [\"'s\"])]), Tree(None, [Tree('positive', ['new']), Tree(None, [Tree(None, ['``']), Tree(None, ['Conan'])])])])])])])]), Tree(None, [\"''\"])]), Tree(None, ['and'])]), Tree('positive', [Tree(None, ['that']), Tree('positive', [Tree(None, ['he']), Tree('positive', [Tree(None, [\"'s\"]), Tree('positive', [Tree(None, ['going']), Tree('positive', [Tree(None, ['to']), Tree('positive', [Tree('positive', [Tree(None, ['make']), Tree('positive', [Tree('positive', [Tree(None, ['a']), Tree('positive', ['splash'])]), Tree(None, [Tree(None, ['even']), Tree('positive', ['greater'])])])]), Tree(None, [Tree(None, ['than']), Tree(None, [Tree(None, [Tree(None, [Tree(None, [Tree('negative', [Tree(None, ['Arnold']), Tree(None, ['Schwarzenegger'])]), Tree(None, [','])]), Tree(None, [Tree(None, ['Jean-Claud']), Tree(None, [Tree(None, ['Van']), Tree(None, ['Damme'])])])]), Tree(None, ['or'])]), Tree(None, [Tree(None, ['Steven']), Tree(None, ['Segal'])])])])])])])])])])])])]), Tree(None, ['.'])])])" ] }, "execution_count": 45, "metadata": {}, "output_type": "execute_result" } ], "source": [ "X_tree_train[0]" ] }, { "cell_type": "code", "execution_count": 46, "metadata": {}, "outputs": [], "source": [ "X_tree_dev, y_tree_dev = sst.build_tree_dataset(\n", " SST_HOME, sst.dev_reader, class_func=sst.binary_class_func)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### PyTorch TreeNN" ] }, { "cell_type": "code", "execution_count": 47, "metadata": {}, "outputs": [], "source": [ "torch_tree_nn_glove = TorchTreeNN(\n", " sst_glove_vocab,\n", " embedding=glove_embedding,\n", " max_grad_norm=10.0,\n", " early_stopping=True)" ] }, { "cell_type": "code", "execution_count": 48, "metadata": {}, "outputs": [ { "name": "stderr", "output_type": "stream", "text": [ "Stopping after epoch 33. Validation score did not improve by tol=1e-05 for more than 10 epochs. Final error is 4.148158252239227" ] }, { "name": "stdout", "output_type": "stream", "text": [ "CPU times: user 26min 57s, sys: 175 ms, total: 26min 57s\n", "Wall time: 26min 41s\n" ] } ], "source": [ "%time _ = torch_tree_nn_glove.fit(X_tree_train, y_tree_train)" ] }, { "cell_type": "code", "execution_count": 49, "metadata": {}, "outputs": [], "source": [ "tree_dev_preds = torch_tree_nn_glove.predict(X_tree_dev)" ] }, { "cell_type": "code", "execution_count": 50, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ " precision recall f1-score support\n", "\n", " negative 0.574 0.605 0.589 428\n", " positive 0.599 0.568 0.583 444\n", "\n", " accuracy 0.586 872\n", " macro avg 0.586 0.586 0.586 872\n", "weighted avg 0.587 0.586 0.586 872\n", "\n" ] } ], "source": [ "print(classification_report(y_tree_dev, tree_dev_preds, digits=3))" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### Subtree supervision\n", "\n", "We've so far ignored one of the most exciting aspects of the SST: it has sentiment labels on every constituent from the root down to the lexical nodes. \n", "\n", "It is fairly easy to extend `TorchTreeNN` to learn from these additional labels. The key change is that the recursive interpretation function has to gather all of the node representations and their true labels and pass these to the loss function:\n", "\n", "" ] } ], "metadata": { "kernelspec": { "display_name": "Python 3", "language": "python", "name": "python3" }, "language_info": { "codemirror_mode": { "name": "ipython", "version": 3 }, "file_extension": ".py", "mimetype": "text/x-python", "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", "version": "3.7.4" }, "widgets": { "state": {}, "version": "1.1.2" } }, "nbformat": 4, "nbformat_minor": 1 }