"""
CS224N 2021-2022: Homework 3
parser_model.py: Feed-Forward Neural Network for Dependency Parsing
Sahil Chopra <[email protected]>
Haoshen Hong <[email protected]>
"""
import argparse
import numpy as np
import torch
import torch.nn as nn
import torch.nn.functional as F
class ParserModel(nn.Module):
""" Feedforward neural network with an embedding layer and two hidden layers.
The ParserModel will predict which transition should be applied to a
given partial parse configuration.
PyTorch Notes:
- Note that "ParserModel" is a subclass of the "nn.Module" class. In PyTorch all neural networks
are a subclass of this "nn.Module".
- The "__init__" method is where you define all the layers and parameters
(embedding layers, linear layers, dropout layers, etc.).
- "__init__" gets automatically called when you create a new instance of your class, e.g.
when you write "m = ParserModel()".
- Other methods of ParserModel can access variables that have "self." prefix. Thus,
you should add the "self." prefix layers, values, etc. that you want to utilize
in other ParserModel methods.
- For further documentation on "nn.Module" please see https://pytorch.org/docs/stable/nn.html.
"""
def __init__(self, embeddings, n_features=36,
hidden_size=200, n_classes=3, dropout_prob=0.5):
""" Initialize the parser model.
@param embeddings (ndarray): word embeddings (num_words, embedding_size)
@param n_features (int): number of input features
@param hidden_size (int): number of hidden units
@param n_classes (int): number of output classes
@param dropout_prob (float): dropout probability
"""
super(ParserModel, self).__init__()
self.n_features = n_features
self.n_classes = n_classes
self.dropout_prob = dropout_prob
self.embed_size = embeddings.shape[1]
self.hidden_size = hidden_size
self.embeddings = nn.Parameter(torch.tensor(embeddings))
self.embed_to_hidden_weight = nn.Parameter(torch.empty(self.embed_size * self.n_features, self.hidden_size))
self.embed_to_hidden_bias = nn.Parameter(torch.empty(self.hidden_size))
nn.init.xavier_uniform_(self.embed_to_hidden_weight)
nn.init.uniform_(self.embed_to_hidden_bias)
self.dropout = nn.Dropout(p=self.dropout_prob)
self.hidden_to_logits_weight = nn.Parameter(torch.empty(self.hidden_size, self.n_classes))
self.hidden_to_logits_bias = nn.Parameter(torch.empty(self.n_classes))
nn.init.xavier_uniform_(self.hidden_to_logits_weight)
nn.init.uniform_(self.hidden_to_logits_bias)
def embedding_lookup(self, w):
""" Utilize `w` to select embeddings from embedding matrix `self.embeddings`
@param w (Tensor): input tensor of word indices (batch_size, n_features)
@return x (Tensor): tensor of embeddings for words represented in w
(batch_size, n_features * embed_size)
"""
N = w.shape[0]
x = torch.index_select(self.embeddings, 0, torch.flatten(w)).view(N, -1)
return x
def forward(self, w):
""" Run the model forward.
Note that we will not apply the softmax function here because it is included in the loss function nn.CrossEntropyLoss
PyTorch Notes:
- Every nn.Module object (PyTorch model) has a `forward` function.
- When you apply your nn.Module to an input tensor `w` this function is applied to the tensor.
For example, if you created an instance of your ParserModel and applied it to some `w` as follows,
the `forward` function would called on `w` and the result would be stored in the `output` variable:
model = ParserModel()
output = model(w) # this calls the forward function
- For more details checkout: https://pytorch.org/docs/stable/nn.html#torch.nn.Module.forward
@param w (Tensor): input tensor of tokens (batch_size, n_features)
@return logits (Tensor): tensor of predictions (output after applying the layers of the network)
without applying softmax (batch_size, n_classes)
"""
x = self.embedding_lookup(w)
h = F.relu(x.mm(self.embed_to_hidden_weight) + self.embed_to_hidden_bias)
logits = h.mm(self.hidden_to_logits_weight) + self.hidden_to_logits_bias
return logits
if __name__ == "__main__":
parser = argparse.ArgumentParser(description='Simple sanity check for parser_model.py')
parser.add_argument('-e', '--embedding', action='store_true', help='sanity check for embeding_lookup function')
parser.add_argument('-f', '--forward', action='store_true', help='sanity check for forward function')
args = parser.parse_args()
embeddings = np.zeros((100, 30), dtype=np.float32)
model = ParserModel(embeddings)
def check_embedding():
inds = torch.randint(0, 100, (4, 36), dtype=torch.long)
selected = model.embedding_lookup(inds)
assert np.all(selected.data.numpy() == 0), "The result of embedding lookup: " \
+ repr(selected) + " contains non-zero elements."
def check_forward():
inputs =torch.randint(0, 100, (4, 36), dtype=torch.long)
out = model(inputs)
expected_out_shape = (4, 3)
assert out.shape == expected_out_shape, "The result shape of forward is: " + repr(out.shape) + \
" which doesn't match expected " + repr(expected_out_shape)
if args.embedding:
check_embedding()
print("Embedding_lookup sanity check passes!")
if args.forward:
check_forward()
print("Forward sanity check passes!")