diff --git a/simple_qa_rnn/.gitignore b/simple_qa_rnn/.gitignore index f8a81ed..48a0166 100644 --- a/simple_qa_rnn/.gitignore +++ b/simple_qa_rnn/.gitignore @@ -1,4 +1,4 @@ -datasets/ +data/ resources/ saved_checkpoints/ -__pycache__/ \ No newline at end of file +__pycache__/ diff --git a/simple_qa_rnn/README.md b/simple_qa_rnn/README.md index e67ce5d..467460d 100644 --- a/simple_qa_rnn/README.md +++ b/simple_qa_rnn/README.md @@ -1,34 +1,14 @@ -Setup: -1. Create 3 directories under "simple_qa_rnn" - "resources", "datasets", "saved_checkpoints" -2. Download the SimpleQA dataset from [here](https://github.com/castorini/data) and put it under the "datasets" directory -3. Download these files from this [Dropbox link](https://www.dropbox.com/sh/e5g12v7zu7sgzf7/AACW272AqPZJIUC7-A40LAsNa?dl=0) and paste them in the "resources" directory -4. The directory structure should look like this now: +Directions: +1. Download SimpleQuestions data from this [link](https://www.dropbox.com/s/tohrsllcfy7rch4/SimpleQuestions_v2.tgz) and put it in directory "data/SimpleQuestions_v2/" +2. Run this script to download GloVe word embeddings and do some preprocessing. ``` -simple_qa_rnn - ├── datasets - │   └── SimpleQuestions_v2 - │   ├── ... - ├── model.py - ├── README.md - ├── resources - │   ├── rel_to_ix_SQ.pkl - │   ├── w2v_map_SQ.pkl - │   └── word_to_ix_SQ.pkl - ├── saved_checkpoints - │   └── [...models will be saved here later...] - ├── scripts - │   ├── ... - ├── train.py - └── util.py +bash fetch_and_preprocess.sh ``` -5. Please take a look at the arguments in utils.py and set them accordingly to train the model. -6. Run this command to train the model. Make sure you have PyTorch and other Python dependencies installed. +3. Run this command to train the model. Make sure you have PyTorch and other Python dependencies installed. ``` -python train.py +python train_relation_model.py ``` - -NOTE: There are pre-trained models saved in the 'finished_checkpoints' directory. They can be loaded up using PyTorch. -You can run a pre-trained model on the test dataset: -``` -python train.py --not_bidirectional --resume_snapshot finished_checkpoints/lstm1/[model_filename] --test +For GPU, use: ``` +python train_relation_model.py --cuda +``` \ No newline at end of file diff --git a/simple_qa_rnn/args.py b/simple_qa_rnn/args.py new file mode 100644 index 0000000..71a4be2 --- /dev/null +++ b/simple_qa_rnn/args.py @@ -0,0 +1,25 @@ +from argparse import ArgumentParser + +def get_args(): + parser = ArgumentParser(description='Simple QA BiGRU model - Ferhan') + parser.add_argument('--epochs', type=int, default=30) + parser.add_argument('--batch_size', type=int, default=64) + parser.add_argument('--d_embedding', type=int, default=300) + parser.add_argument('--d_hidden', type=int, default=400) + parser.add_argument('--n_layers', type=int, default=2) + parser.add_argument('--lr', type=float, default=1e-4) + parser.add_argument('--test', action='store_true', dest='test', help='turn on test mode; no training.') + parser.add_argument('--not_bidirectional', action='store_false', dest='birnn') + parser.add_argument('--clip', type=float, default=0.4, help='gradient clipping') + parser.add_argument('--log_every', type=int, default=400) + parser.add_argument('--dev_every', type=int, default=1000) + parser.add_argument('--save_every', type=int, default=1000) + parser.add_argument('--dropout_prob', type=float, default=0.3) + parser.add_argument('--gpu', type=int, default=0) + parser.add_argument('--seed', type=int, default=1111, help='random seed for reproducing results') + parser.add_argument('--cuda', action='store_true', help='use CUDA') + parser.add_argument('--device', type=int, default=0, help='GPU device to use') + parser.add_argument('--save_path', type=str, default='saved_checkpoints') + parser.add_argument('--resume_snapshot', type=str, default='') + args = parser.parse_args() + return args diff --git a/simple_qa_rnn/data.py b/simple_qa_rnn/data.py deleted file mode 100644 index 982bf75..0000000 --- a/simple_qa_rnn/data.py +++ /dev/null @@ -1,80 +0,0 @@ -import nltk -import string -import pickle -import numpy as np -import torch -from torch.autograd import Variable - -def get_all_lines(data_filename): - all_lines = [] - with open(data_filename) as fin: - for line in fin: - all_lines.append(line.rstrip()) - return all_lines - -def create_rp_dataset(data_file): - dataset = [] - all_lines = get_all_lines(data_file) - for line in all_lines: - line_split = line.split("\t") - text = line_split[3] - relation = line_split[1] - dataset.append( (text, relation) ) - return np.array(dataset) - -def tokenize_text(text): - punc_remover = str.maketrans('', '', string.punctuation) - processed_text = text.lower().translate(punc_remover) - tokens = nltk.word_tokenize(processed_text) - return tokens - -def add_padding_tokens(text_tokens, max_length, pad_type='both', pad_token=''): - num_pads = max_length - len(text_tokens) - right_pad = int(num_pads / 2) - left_pad = num_pads - right_pad - if pad_type == "both": - padded_tokens = [pad_token]*left_pad + text_tokens + [pad_token]*right_pad - elif pad_type == "right": - padded_tokens = text_tokens + [pad_token]*num_pads - else: - padded_tokens = [pad_token]*num_pads + text_tokens - return padded_tokens - -def load_map(pname): - ret_map = None - with open(pname, 'rb') as fh: - ret_map = pickle.load(fh) - return ret_map - -def text_to_vector(text, w2v_map, pad=False, max_length=None): - vec = [] - tokens = tokenize_text(text) - if pad and (max_length != None): - tokens = add_padding_tokens(tokens, max_length) - for token in tokens: - vec.append( w2v_map[token] ) - return np.array(vec) - -def label_to_vector(label_ix, num_labels): - # create one-hot vector label representation - y_vec = np.zeros(num_labels, dtype=np.int32) - y_vec[label_ix] = 1 - return y_vec - -def create_tensorized_data(sentence, label, w2v_map, label_to_ix): - # x.shape: |S| X |D| - sentence length can vary between examples, dimension is fixed - x = text_to_vector(sentence, w2v_map) - y = label_to_ix[label] - inputs = Variable(torch.Tensor(x)) - targets = Variable(torch.LongTensor([y])) - return inputs, targets - -def create_tensorized_batch(batch, max_sent_length, w2v_map, label_to_ix): - X = [] - y = [] - for sent, label in batch: - X.append( text_to_vector(sent, w2v_map, pad=True, max_length=max_sent_length) ) - y.append( label_to_ix[label] ) - inputs = Variable(torch.Tensor(X)) - targets = Variable(torch.LongTensor(y)) - return inputs, targets \ No newline at end of file diff --git a/simple_qa_rnn/fetch_and_preprocess.sh b/simple_qa_rnn/fetch_and_preprocess.sh new file mode 100755 index 0000000..bc82133 --- /dev/null +++ b/simple_qa_rnn/fetch_and_preprocess.sh @@ -0,0 +1,14 @@ +#!/bin/bash + +# download GloVe word embeddings +python2 scripts/download.py + +glove_dir="data/glove" +glove_pre="glove.840B" +glove_dim="300d" +if [ ! -f $glove_dir/$glove_pre.$glove_dim.pt ]; then + python scripts/convert_wordvecs.py $glove_dir/$glove_pre.$glove_dim.txt \ + $glove_dir/$glove_pre.$glove_dim.pt +else + echo "The processed word embeddings file - $glove_dir/$glove_pre.$glove_dim.pt - already exists!" +fi \ No newline at end of file diff --git a/simple_qa_rnn/hyperparameter_tuning.py b/simple_qa_rnn/hyperparameter_tuning.py new file mode 100644 index 0000000..c377bb7 --- /dev/null +++ b/simple_qa_rnn/hyperparameter_tuning.py @@ -0,0 +1,18 @@ +from random import randint, uniform +from subprocess import call + +epochs = 50 +count = 20 +for id in range(count): + learning_rate = 10 ** uniform(-5, -4) + d_hidden = randint(550, 600) + n_layers = randint(4, 5) + dropout = uniform(0.5, 0.6) + clip = uniform(0.6, 0.7) + + command = "python train.py --cuda --device 1 --dev_every 500 --log_every 250 --batch_size 128 " \ + "--epochs {} --lr {} --d_hidden {} --n_layers {} --dropout_prob {} --clip {} >> " \ + "results.txt".format(epochs, learning_rate, d_hidden, n_layers, dropout, clip) + + print("Running: " + command) + call(command, shell=True) diff --git a/simple_qa_rnn/model.py b/simple_qa_rnn/model.py deleted file mode 100644 index 3556fe6..0000000 --- a/simple_qa_rnn/model.py +++ /dev/null @@ -1,37 +0,0 @@ -import torch -import torch.nn as nn -from torch.autograd import Variable -import torch.nn.functional as F - -class BiLSTM(nn.Module): - - def __init__(self, config): - super(BiLSTM, self).__init__() - self.config = config - - self.lstm = nn.LSTM(input_size=config.d_embedding, hidden_size=config.d_hidden, - num_layers=config.n_layers, dropout=config.dropout_prob, - bidirectional=config.birnn, batch_first=True) - - # linear layer maps from hidden state space to label space - self.hidden2label = nn.Linear(config.n_layers*config.n_directions*config.d_hidden, config.d_out) - self.hidden = self.init_hidden() - # self.dropout = nn.Dropout(p=config.dropout_prob) - # self.log_softmax = nn.LogSoftmax() - - - def init_hidden(self): - # axes semantics are (num_layers, batch_size, hidden_dim) - n_layers = self.config.n_layers * self.config.n_directions - return (Variable(torch.zeros(n_layers, self.config.batch_size, self.config.d_hidden)), - Variable(torch.zeros(n_layers, self.config.batch_size, self.config.d_hidden))) - - # embeds is Variable of size - (|B|, |S|, |D|) - def forward(self, embeds): - batch_size = embeds.data.size()[0] - sequence_length = embeds.data.size()[1] - lstm_out, self.hidden = self.lstm(embeds, self.hidden) - # print("ht size: {}".format(ht.size())) - rel_space = self.hidden2label(self.hidden[0].transpose(0, 1).contiguous().view(batch_size, -1)) # size - (|B|, |K|) - scores = F.log_softmax(rel_space) - return scores diff --git a/simple_qa_rnn/relation_model.py b/simple_qa_rnn/relation_model.py new file mode 100644 index 0000000..2eadc50 --- /dev/null +++ b/simple_qa_rnn/relation_model.py @@ -0,0 +1,65 @@ +import torch +import torch.nn as nn +from torch.autograd import Variable +import torch.nn.functional as F + +class Encoder(nn.Module): + def __init__(self, config): + super(Encoder, self).__init__() + self.config = config + self.lstm = nn.LSTM(input_size=config.d_embedding, hidden_size=config.d_hidden, + num_layers=config.n_layers, dropout=config.dropout_prob, + bidirectional=config.birnn, batch_first=True) + + self.hidden = self.init_hidden() + + def init_hidden(self): + # axes semantics are (num_layers, batch_size, hidden_dim) + n_layers = self.config.n_layers * self.config.n_directions + out = (Variable(torch.zeros(n_layers, self.config.batch_size, self.config.d_hidden)), + Variable(torch.zeros(n_layers, self.config.batch_size, self.config.d_hidden))) + if self.config.cuda: + out = ( Variable(torch.zeros(n_layers, self.config.batch_size, self.config.d_hidden).cuda()), + Variable(torch.zeros(n_layers, self.config.batch_size, self.config.d_hidden).cuda() )) + return out + + def forward(self, embeds): + batch_size = embeds.data.size()[0] + # sequence_length = embeds.data.size()[1] + lstm_out, self.hidden = self.lstm(embeds, self.hidden) + # print("ht size: {}".format(ht.size())) + return self.hidden[0].transpose(0, 1).contiguous().view(batch_size, -1) # size - (|B|, |K|) + + +class RelationPredictor(nn.Module): + def __init__(self, config): + super(RelationPredictor, self).__init__() + self.config = config + self.embed = nn.Embedding(config.vocab_size, config.d_embedding) + self.encoder = Encoder(config) + + self.dropout = nn.Dropout(p=config.dropout_prob) + self.relu = nn.ReLU() + + # linear layers map from hidden state space to label space + num_in_features = config.n_layers * config.n_directions * config.d_hidden + self.hidden2label = nn.Sequential ( + nn.Linear(num_in_features, num_in_features), + nn.BatchNorm1d(num_in_features), + self.relu, + self.dropout, + nn.Linear(num_in_features, num_in_features), + nn.BatchNorm1d(num_in_features), + self.relu, + self.dropout, + nn.Linear(num_in_features, config.d_out) + ) + + # batch_input is Variable of size - (|B|, |S|) + def forward(self, batch_input): + batch_input_embed = self.embed(batch_input) # size - (|B|, |S|, |D|) + # size - (|B|, |X|) where |X| = n_layers * n_directions * d_hidden + encoded = self.encoder(batch_input_embed) + rel_space = self.hidden2label(encoded) # size - (|B|, |K|) + scores = F.log_softmax(rel_space) + return scores diff --git a/simple_qa_rnn/relation_prediction_results.txt b/simple_qa_rnn/relation_prediction_results.txt new file mode 100644 index 0000000..e7e6856 --- /dev/null +++ b/simple_qa_rnn/relation_prediction_results.txt @@ -0,0 +1,207 @@ +vocab size = 62622 +num classes = 1837 +unk vocab count = 22761 +loading train/val/test datasets... +train_file: data/SimpleQuestions_v2/annotated_fb_data_train.txt, num train = 75910 +val_file: data/SimpleQuestions_v2/annotated_fb_data_valid.txt, num dev = 10845 +test_file: data/SimpleQuestions_v2/annotated_fb_data_test.txt, num test = 21687 +Namespace(batch_size=128, birnn=True, clip=0.6, cuda=True, d_embedding=300, d_hidden=500, d_out=1837, dev_every=500, device=1, dropout_prob=0.5487647674511446, epochs=25, gpu=0, log_every=250, lr=2.3368165563254845e-05, n_directions=2, n_layers=4, resume_snapshot='', save_every=1000, save_path='saved_checkpoints', seed=1111, test=False, vocab_size=62622) + Time Epoch Iteration Loss Train/Acc. Val/Acc. + 1 0 1 7.519370 + 28 0 250 4.217004 + 70 0 500 3.454021 0.464165 0.460844 + 97 1 750 2.695011 + 139 1 1000 2.023616 0.582841 0.579520 + 167 2 1250 1.947864 + 209 2 1500 2.173526 0.654498 0.643415 + 236 2 1750 2.050010 + 278 3 2000 1.698692 0.691334 0.676525 + 305 3 2250 1.334085 + 347 4 2500 1.258004 0.721398 0.703776 + 375 4 2750 1.318608 + 417 5 3000 1.237201 0.745152 0.725632 + 445 5 3250 1.053011 + 488 5 3500 1.393011 0.762713 0.740141 + 516 6 3750 1.064027 + 558 6 4000 1.188779 0.780143 0.753441 + 586 7 4250 1.180950 + 629 7 4500 1.158713 0.792488 0.765811 + 657 8 4750 1.092308 + 699 8 5000 0.772544 0.804582 0.771205 + 728 8 5250 0.935450 + 770 9 5500 1.031198 0.814555 0.776786 + 798 9 5750 0.795584 + 840 10 6000 0.759640 0.824318 0.785156 + 868 10 6250 0.861564 + 910 10 6500 0.605637 0.833289 0.787388 + 938 11 6750 0.720791 + 980 11 7000 0.615107 0.841089 0.791295 + 1007 12 7250 0.765555 + 1050 12 7500 0.601661 0.847874 0.800037 + 1078 13 7750 0.523285 + 1120 13 8000 0.550137 0.855265 0.800130 + 1148 13 8250 0.722784 + 1190 14 8500 0.632320 0.860231 0.802269 + 1218 14 8750 0.631018 + 1261 15 9000 0.584924 0.867174 0.804781 + 1289 15 9250 0.341058 + 1331 16 9500 0.483922 0.872853 0.809431 + 1359 16 9750 0.433858 + 1402 16 10000 0.514899 0.878004 0.809152 + 1430 17 10250 0.388845 + 1472 17 10500 0.379268 0.884301 0.809338 + 1500 18 10750 0.504304 + 1542 18 11000 0.477527 0.887898 0.807385 + 1570 18 11250 0.496538 + 1612 19 11500 0.457043 0.894630 0.808780 + 1639 19 11750 0.351258 + 1682 20 12000 0.567525 0.900348 0.809896 + 1710 20 12250 0.422562 + 1752 21 12500 0.327270 0.904247 0.810919 + 1779 21 12750 0.443917 + 1821 21 13000 0.429144 0.907726 0.811663 + 1849 22 13250 0.339602 + 1891 22 13500 0.393765 0.913101 0.812035 + 1919 23 13750 0.493038 + 1961 23 14000 0.322024 0.916473 0.810361 + 1989 24 14250 0.394269 + 2031 24 14500 0.315911 0.920623 0.814081 + 2059 24 14750 0.302249 +vocab size = 62622 +num classes = 1837 +unk vocab count = 22761 +loading train/val/test datasets... +train_file: data/SimpleQuestions_v2/annotated_fb_data_train.txt, num train = 75910 +val_file: data/SimpleQuestions_v2/annotated_fb_data_valid.txt, num dev = 10845 +test_file: data/SimpleQuestions_v2/annotated_fb_data_test.txt, num test = 21687 +Namespace(batch_size=128, birnn=True, clip=0.6, cuda=True, d_embedding=300, d_hidden=500, d_out=1837, dev_every=500, device=1, dropout_prob=0.5235083503167401, epochs=25, gpu=0, log_every=250, lr=9.543880052712213e-05, n_directions=2, n_layers=4, resume_snapshot='', save_every=1000, save_path='saved_checkpoints', seed=1111, test=False, vocab_size=62622) + Time Epoch Iteration Loss Train/Acc. Val/Acc. + 1 0 1 7.562489 + 28 0 250 2.717191 + 70 0 500 1.675420 0.683917 0.673270 + 98 1 750 1.317849 + 140 1 1000 1.132524 0.757101 0.731678 + 168 2 1250 1.179604 + 210 2 1500 0.727408 0.802843 0.771484 + 238 2 1750 1.093024 + 280 3 2000 0.711856 0.829522 0.785156 + 308 3 2250 0.868195 + 351 4 2500 0.789406 0.848229 0.793062 + 379 4 2750 0.748050 + 421 5 3000 0.530056 0.864856 0.803478 + 449 5 3250 0.489331 + 492 5 3500 0.791100 0.880520 0.802362 + 520 6 3750 0.684948 + 562 6 4000 0.574934 0.893853 0.801804 + 590 7 4250 0.449654 + 632 7 4500 0.407685 0.905407 0.804781 + 660 8 4750 0.298767 + 702 8 5000 0.382648 0.917765 0.803850 + 730 8 5250 0.311796 + 772 9 5500 0.240214 0.928199 0.802083 + 799 9 5750 0.403507 + 842 10 6000 0.195193 0.942216 0.804222 + 869 10 6250 0.360780 + 911 10 6500 0.179228 0.949542 0.801060 + 939 11 6750 0.244037 + 981 11 7000 0.239074 0.957828 0.798642 + 1009 12 7250 0.187415 + 1051 12 7500 0.242319 0.964113 0.796038 + 1079 13 7750 0.205365 + 1121 13 8000 0.188374 0.970542 0.795294 + 1149 13 8250 0.218278 + 1191 14 8500 0.165913 0.973585 0.797433 + 1218 14 8750 0.181852 + 1261 15 9000 0.171079 0.977406 0.795666 + 1288 15 9250 0.153494 + 1331 16 9500 0.063608 0.982017 0.789528 + 1358 16 9750 0.129606 + 1401 16 10000 0.067068 0.983940 0.789249 + 1428 17 10250 0.056862 + 1470 17 10500 0.113310 0.985640 0.790365 + 1498 18 10750 0.082778 + 1540 18 11000 0.108898 0.986483 0.792225 + 1568 18 11250 0.156388 + 1610 19 11500 0.074055 0.988406 0.784598 + 1638 19 11750 0.110450 + 1680 20 12000 0.103423 0.988709 0.787481 + 1707 20 12250 0.072456 + 1749 21 12500 0.057874 0.991015 0.786086 + 1777 21 12750 0.036475 + 1819 21 13000 0.082274 0.991792 0.788876 + 1847 22 13250 0.047618 + 1889 22 13500 0.054384 0.992108 0.785714 + 1917 23 13750 0.035868 + 1959 23 14000 0.062694 0.991502 0.790365 + 1987 24 14250 0.084560 + 2029 24 14500 0.061790 0.994809 0.785714 + 2056 24 14750 0.044786 +vocab size = 62622 +num classes = 1837 +unk vocab count = 22761 +loading train/val/test datasets... +train_file: data/SimpleQuestions_v2/annotated_fb_data_train.txt, num train = 75910 +val_file: data/SimpleQuestions_v2/annotated_fb_data_valid.txt, num dev = 10845 +test_file: data/SimpleQuestions_v2/annotated_fb_data_test.txt, num test = 21687 +Namespace(batch_size=128, birnn=True, clip=0.6, cuda=True, d_embedding=300, d_hidden=500, d_out=1837, dev_every=500, device=1, dropout_prob=0.5973369506283627, epochs=25, gpu=0, log_every=250, lr=6.789914913570565e-05, n_directions=2, n_layers=4, resume_snapshot='', save_every=1000, save_path='saved_checkpoints', seed=1111, test=False, vocab_size=62622) + Time Epoch Iteration Loss Train/Acc. Val/Acc. + 1 0 1 7.538278 + 28 0 250 2.793929 + 70 0 500 2.281076 0.605212 0.600074 + 98 1 750 1.900491 + 140 1 1000 1.823353 0.709607 0.691778 + 167 2 1250 1.095232 + 209 2 1500 1.101957 0.756521 0.735026 + 237 2 1750 1.370564 + 279 3 2000 0.972925 0.788891 0.762184 + 307 3 2250 0.815842 + 349 4 2500 1.124413 0.810511 0.773996 + 376 4 2750 0.545115 + 419 5 3000 0.806257 0.829363 0.786086 + 446 5 3250 0.610705 + 489 5 3500 0.797700 0.845858 0.794829 + 516 6 3750 0.497445 + 558 6 4000 0.694232 0.855396 0.797805 + 586 7 4250 0.741005 + 629 7 4500 0.607206 0.867873 0.801897 + 657 8 4750 0.565121 + 699 8 5000 0.472905 0.879690 0.804315 + 727 8 5250 0.708608 + 769 9 5500 0.614066 0.889242 0.803757 + 797 9 5750 0.456040 + 839 10 6000 0.432193 0.899241 0.806920 + 866 10 6250 0.382535 + 908 10 6500 0.394834 0.909333 0.807292 + 935 11 6750 0.367661 + 978 11 7000 0.637153 0.918950 0.808594 + 1005 12 7250 0.394814 + 1047 12 7500 0.319768 0.927514 0.810919 + 1075 13 7750 0.262185 + 1117 13 8000 0.243790 0.932639 0.804688 + 1144 13 8250 0.240168 + 1187 14 8500 0.224746 0.939858 0.806734 + 1215 14 8750 0.280511 + 1257 15 9000 0.223731 0.948698 0.801339 + 1284 15 9250 0.342026 + 1327 16 9500 0.219288 0.954956 0.801525 + 1354 16 9750 0.144273 + 1396 16 10000 0.268068 0.960437 0.799014 + 1424 17 10250 0.142029 + 1466 17 10500 0.204999 0.964903 0.800688 + 1494 18 10750 0.188032 + 1536 18 11000 0.197725 0.968842 0.801153 + 1563 18 11250 0.163313 + 1606 19 11500 0.213809 0.970818 0.796875 + 1633 19 11750 0.167466 + 1675 20 12000 0.110523 0.974349 0.793992 + 1702 20 12250 0.145699 + 1744 21 12500 0.074068 0.977392 0.794085 + 1772 21 12750 0.098240 + 1814 21 13000 0.112673 0.983084 0.794550 + 1842 22 13250 0.106451 + 1884 22 13500 0.222595 0.982254 0.793992 + 1912 23 13750 0.079745 + 1954 23 14000 0.101426 0.985060 0.792318 + 1982 24 14250 0.118253 + 2024 24 14500 0.109737 0.986101 0.791853 + 2052 24 14750 0.081499 diff --git a/simple_qa_rnn/scripts/build_vocab.py b/simple_qa_rnn/scripts/build_vocab.py new file mode 100755 index 0000000..3718b54 --- /dev/null +++ b/simple_qa_rnn/scripts/build_vocab.py @@ -0,0 +1,43 @@ +import os +import glob +import torch +import nltk +import string + +# this method was copied from utils/read_data.py +def process_tokenize_text(text): + punc_remover = str.maketrans('', '', string.punctuation) + processed_text = text.lower().translate(punc_remover) + tokens = nltk.word_tokenize(processed_text) + return tokens + +def build_vocab_SQ(data_dir): + filepaths = glob.glob(os.path.join(data_dir, 'annotated*.txt')) + print("reading filepaths: {}".format(filepaths)) + word_vocab = set() + relation_vocab = set() + for filepath in filepaths: + with open(filepath) as f: + for line in f: + line_items = line.split("\t") + # add relation + relation = line_items[1] + relation_vocab.add(relation) + # add text + qText = line_items[3] + tokens = process_tokenize_text(qText) + word_vocab |= set(tokens) + + word2index_dict = {word: i for i, word in enumerate(sorted(word_vocab))} # word to index dictionary + rel2index_dict = {relation: i for i, relation in enumerate(sorted(relation_vocab))} # relation to index dictionary + return (word2index_dict, rel2index_dict) + + +print("WARNING: This script is dataset specific. Please change it to fit your own dataset.") +data_dir = 'data/SimpleQuestions_v2/' +dst_path = os.path.join(data_dir, 'vocab.pt') +print("Building vocab for data in: {}".format(data_dir)) +ret = build_vocab_SQ(data_dir) # ret = (word2index dict, relation2index dict) +print("saving word2index and answer2index dicts to {}".format(dst_path)) +torch.save(ret, dst_path) +print("Done!") \ No newline at end of file diff --git a/simple_qa_rnn/scripts/convert_wordvecs.py b/simple_qa_rnn/scripts/convert_wordvecs.py new file mode 100644 index 0000000..5daa838 --- /dev/null +++ b/simple_qa_rnn/scripts/convert_wordvecs.py @@ -0,0 +1,57 @@ +import sys +from tqdm import tqdm +import torch +import array +import six + +try: + path = sys.argv[1] + outpath = sys.argv[2] +except: + print("ERROR: the command line arguments passed in were not valid.\n"); + print("USAGE: python scripts/convert_wordvecs.py [input_file] [output_file]"); + print("EXAMPLE: python scripts/convert_wordvecs.py glove_300d.txt glove_300d.pt"); + sys.exit(1); + +prefix_toks = path.split(".") +print('Converting ' + path + ' to PyTorch serialized format...') + +lines = [line.rstrip('\n') for line in open(path)] +print("number of lines: {}".format(len(lines))) + +wv_tokens = [] +wv_arr = array.array('d') +wv_size = None # dimension of the word vectors +vocab_size = 0 # counts the number of words saved +if lines is not None: + for i in tqdm(range(len(lines)), desc="loading word vectors from {}".format(path)): + entries = lines[i].strip().split() + word, entries = entries[0], entries[1:] + if wv_size is None: + wv_size = len(entries) + else: + # safety check that the dimension is the same + if len(entries) != wv_size: + print(len(entries)) + print(lines[i]) + continue + try: + if isinstance(word, six.binary_type): + word = word.decode('utf-8') + except: + print('non-UTF8 token', repr(word), 'ignored') + continue + wv_arr.extend(float(x) for x in entries) + wv_tokens.append(word) + vocab_size += 1 + +print("vocab size: {}".format(vocab_size)) +print("dim: {}".format(wv_size)) + +wv_dict = {word: i for i, word in enumerate(wv_tokens)} # word to index dictionary +wv_arr = torch.Tensor(wv_arr).view(vocab_size, wv_size) # word embeddings in Tensor of shape (|V|, |D|) +ret = (wv_dict, wv_arr, wv_size) # save all three info in a tuple + +print("saving word vectors to {}".format(outpath)) +torch.save(ret, outpath) + diff --git a/simple_qa_rnn/scripts/download.py b/simple_qa_rnn/scripts/download.py new file mode 100644 index 0000000..e0ece09 --- /dev/null +++ b/simple_qa_rnn/scripts/download.py @@ -0,0 +1,84 @@ +""" +Downloads the following: +- Glove vectors + +We Thank Kai Sheng Tai for providing the preprocessing/basis codes. +Taken from: https://github.com/castorini/NCE-CNN-Torch +""" + +from __future__ import print_function +import urllib2 +import sys +import os +import shutil +import zipfile +import gzip + +def download(url, dirpath): + filename = url.split('/')[-1] + filepath = os.path.join(dirpath, filename) + try: + u = urllib2.urlopen(url) + except: + print("URL %s failed to open" %url) + raise Exception + try: + f = open(filepath, 'wb') + except: + print("Cannot write %s" %filepath) + raise Exception + try: + filesize = int(u.info().getheaders("Content-Length")[0]) + except: + print("URL %s failed to report length" %url) + raise Exception + print("Downloading: %s Bytes: %s" % (filename, filesize)) + + downloaded = 0 + block_sz = 8192 + status_width = 70 + while True: + buf = u.read(block_sz) + if not buf: + print('') + break + else: + print('', end='\r') + downloaded += len(buf) + f.write(buf) + status = (("[%-" + str(status_width + 1) + "s] %3.2f%%") % + ('=' * int(float(downloaded) / filesize * status_width) + '>', downloaded * 100. / filesize)) + print(status, end='') + sys.stdout.flush() + f.close() + return filepath + +def unzip(filepath): + print("Extracting: " + filepath) + dirpath = os.path.dirname(filepath) + with zipfile.ZipFile(filepath) as zf: + zf.extractall(dirpath) + os.remove(filepath) + +def download_wordvecs(dirpath): + if os.path.exists(dirpath): + print('Found Glove vectors - skip') + return + else: + os.makedirs(dirpath) + url = 'https://nlp.stanford.edu/data/glove.840B.300d.zip' + unzip(download(url, dirpath)) + +if __name__ == '__main__': + base_dir = os.path.dirname(os.path.dirname(os.path.realpath(__file__))) + + # data + data_dir = os.path.join(base_dir, 'data') + wordvec_dir = os.path.join(data_dir, 'glove') + + # libraries + lib_dir = os.path.join(base_dir, 'lib') + + # download GloVe word embeddings + download_wordvecs(wordvec_dir) + print("Finished downloading word embeddings!") \ No newline at end of file diff --git a/simple_qa_rnn/scripts/sq_entity_data.py b/simple_qa_rnn/scripts/sq_entity_data.py new file mode 100644 index 0000000..230b9e7 --- /dev/null +++ b/simple_qa_rnn/scripts/sq_entity_data.py @@ -0,0 +1,40 @@ +import glob +import os + +fnames = glob.glob('../data/SimpleQuestions_v2/annotated*.txt') +print(fnames) +all_entities = set() +for fname in fnames: + with open(fname) as fin: + for line in fin: + entity = line.split("\t")[0] + # entity: www.freebase.com/m/0f3xg_ --> m.0f3xg_ + if entity.startswith("www.freebase.com/"): + entity = entity[17:].replace("/", ".") + all_entities.add(entity) + +print("num of entities in train/val/test: {}".format(len(all_entities))) + +names_map = {} +names_file = "../data/freebase/names-map-0.ttl" +with open(names_file) as fin: + for line in fin: + id = line.split("\t")[0][3:] + name = line.split("\t")[2].rstrip() + if name.endswith("\"@en."): + name = name[1:-5] + if id in all_entities: + names_map[id] = name + +print("num of FB entities in the map: {}".format(len(names_map))) + +found = len(names_map) +print("found: {}".format(found)) +print("notfound: {}".format(len(all_entities) - found)) + +outfile = open("names_map.tsv", 'w') +for id, name in names_map.items(): + outfile.write("{}\t{}\n".format(id, name)) + +outfile.close() +print("done") \ No newline at end of file diff --git a/simple_qa_rnn/scripts/wq_relation_data.py b/simple_qa_rnn/scripts/wq_relation_data.py new file mode 100644 index 0000000..1b3d0b3 --- /dev/null +++ b/simple_qa_rnn/scripts/wq_relation_data.py @@ -0,0 +1,40 @@ +import json +import os + +rel_dir = 'data/dataset-factoid-webquestions/d-freebase-rp/' +text_dir = 'data/dataset-factoid-webquestions/main/' +out_dir = 'data/webquestions-custom/relation-prediction' +fname = 'val.json' +rel_filename = os.path.join(rel_dir, fname) +text_filename = os.path.join(text_dir, fname) +out_filename = os.path.join(out_dir, fname) + +text_entries = [] +with open(text_filename) as fin: + data = json.load(fin) + text_entries.extend(data) + +rel_entries = [] +with open(rel_filename) as fin: + data = json.load(fin) + rel_entries.extend(data) + + +print("num of examples: {}".format(len(text_entries))) +assert len(text_entries) == len(rel_entries) + +outfile = open(out_filename, 'w') +count = 0 +for text_entry, rel_entry in zip(text_entries, rel_entries): + assert text_entry.get('qId') == rel_entry.get('qId') + qID = text_entry.get('qId') + qText = text_entry.get('qText') + relPaths = rel_entry.get('relPaths') + for relPath in relPaths: + relations = relPath[0] + for rel in relations: + count += 1 + outfile.write("{}\t{}\t{}\n".format(qID, rel, qText)) + +print("count: {}".format(count)) +outfile.close() diff --git a/simple_qa_rnn/train.py b/simple_qa_rnn/train.py deleted file mode 100644 index 5209dee..0000000 --- a/simple_qa_rnn/train.py +++ /dev/null @@ -1,137 +0,0 @@ -import os -import sys -import time -import glob -import numpy as np -import torch -from torch.autograd import Variable -import torch.nn as nn -import torch.optim as optim -from model import BiLSTM -from util import get_args -import data - -args = get_args() - - -# torch.cuda.set_device(args.gpu) - - -# ---- Helper Methods ------ -def evaluate_dataset_batch(data_set, max_sent_length, model, w2v_map, label_to_ix): - n_total = len(data_set) - n_correct = 0 - num_batches = len(data_set) // args.batch_size - batch_indices = np.split(range(n_total), - range(args.batch_size, n_total, args.batch_size)) - model.eval() - for batch_ix in range(num_batches): - batch = data_set[batch_indices[batch_ix]] - inputs, targets = data.create_tensorized_batch(batch, max_sent_length, w2v_map, label_to_ix) - scores = model(inputs) - pred_label_ix = np.argmax(scores.data.numpy(), axis=1) # check this properly - correct_label_ix = targets.data.numpy() - n_correct += (pred_label_ix == correct_label_ix).sum() - acc = n_correct / n_total - return acc - -def repackage_hidden(h): - """Wraps hidden states in new Variables, to detach them from their history.""" - if type(h) == Variable: - return Variable(h.data) - else: - return tuple(repackage_hidden(v) for v in h) - -#Load Datasets ------ -train_file = "datasets/SimpleQuestions_v2/annotated_fb_data_train.txt" -val_file = "datasets/SimpleQuestions_v2/annotated_fb_data_valid.txt" -test_file = "datasets/SimpleQuestions_v2/annotated_fb_data_test.txt" - -train_set = data.create_rp_dataset(train_file) -val_set = data.create_rp_dataset(val_file) -test_set = data.create_rp_dataset(test_file) -# train_set = train_set[:4] # work with few examples first - -# ---- Build Vocabulary ------ -w2v_map = data.load_map("resources/w2v_map_SQ.pkl") -w2v_map[''] = np.zeros(300) -word_to_ix = data.load_map("resources/word_to_ix_SQ.pkl") -label_to_ix = data.load_map("resources/rel_to_ix_SQ.pkl") -vocab_size = len(word_to_ix) -num_classes = len(label_to_ix) -max_sent_length = 36 # set from the paper - -# ---- Define Model, Loss, Optim ------ -config = args -config.d_out = num_classes -config.n_directions = 2 if config.birnn else 1 -print(config) -model = BiLSTM(config) -loss_function = nn.NLLLoss() -optimizer = optim.Adam(model.parameters(), lr=args.lr) - -# ---- Test Model ------ -if args.test: - print("Test Mode: loading pre-trained model and testing on test set...") - # model = torch.load(args.resume_snapshot, map_location=lambda storage, location: storage.cuda(args.gpu)) - model.load_state_dict(torch.load(args.resume_snapshot)) - test_acc = evaluate_dataset_batch(test_set, max_sent_length, model, w2v_map, label_to_ix) - print("Accuracy: {}".format(test_acc)) - sys.exit(0) - - -# ---- Train Model ------ -start = time.time() -best_val_acc = -1 -iter = 0 -header = ' Time Epoch Iteration Loss Train/Acc. Val/Acc.' -print(header) -log_template = ' '.join('{:>6.0f},{:>5.0f},{:>9.0f},{:>9.6f}'.split(',')) -dev_log_template = ' '.join('{:>6.0f},{:>5.0f},{:>9.0f},{:>9.6f},{:9.6f},{:11.6f}'.split(',')) - -model.train() -for epoch in range(args.epochs): - # shuffle the dataset and create batches (truncate the last batch if not of equal size) - shuffled_indices = np.random.permutation(len(train_set)) - num_batches = len(shuffled_indices) // args.batch_size - batch_indices = np.split(shuffled_indices, - range(args.batch_size, len(shuffled_indices), args.batch_size)) - model.hidden = model.init_hidden() - for batch_ix in range(num_batches): - iter += 1 - batch = train_set[batch_indices[batch_ix]] - inputs, targets = data.create_tensorized_batch(batch, max_sent_length, w2v_map, label_to_ix) - # print("inputs size: {}".format(inputs.size())) - # print("targets size: {}".format(targets.size())) - - # clear out gradients and hidden states of the model - model.zero_grad() - model.hidden = repackage_hidden(model.hidden) - - # prepare inputs for LSTM model and run forward pass - scores = model(inputs) - - # compute the loss, gradients, and update the parameters - loss = loss_function(scores, targets) - loss.backward() - # `clip_grad_norm` helps prevent the exploding gradient problem in RNNs / LSTMs. - torch.nn.utils.clip_grad_norm(model.parameters(), args.clip) - optimizer.step() - - # log at intervals - if iter % args.dev_every == 0: - train_acc = evaluate_dataset_batch(train_set[:8000], max_sent_length, model, w2v_map, label_to_ix) - val_acc = evaluate_dataset_batch(val_set, max_sent_length, model, w2v_map, label_to_ix) - print(dev_log_template.format(time.time()-start, epoch, iter, loss.data[0], train_acc, val_acc)) - if val_acc > best_val_acc: - best_val_acc = val_acc - snapshot_prefix = os.path.join(args.save_path, 'best_snapshot') - snapshot_path = snapshot_prefix + '_valacc_{:6.4f}__iter_{}_model.pt'.format(val_acc, iter) - torch.save(model.state_dict(), snapshot_path) - for f in glob.glob(snapshot_prefix + '*'): - if f != snapshot_path: - os.remove(f) - - elif iter == 1 or iter % args.log_every == 0: - print(log_template.format(time.time() - start, epoch, iter, loss.data[0])) - diff --git a/simple_qa_rnn/train_relation_model.py b/simple_qa_rnn/train_relation_model.py new file mode 100644 index 0000000..f810ddb --- /dev/null +++ b/simple_qa_rnn/train_relation_model.py @@ -0,0 +1,184 @@ +import os +import sys +import time +import glob +import numpy as np +import torch +from torch.autograd import Variable +import torch.nn as nn +import torch.optim as optim +from relation_model import RelationPredictor +from args import get_args +import data +from utils.vocab import Vocab +from utils.read_data import * + +args = get_args() +# Set the random seed manually for reproducibility. +torch.manual_seed(args.seed) +if torch.cuda.is_available(): + if not args.cuda: + print("WARNING: You have a CUDA device, so you should probably run with --cuda") + else: + torch.cuda.manual_seed(args.seed) + torch.cuda.set_device(args.device) + +# ---- helper methods ------ +def evaluate_dataset_batch(data_set, model): + n_total = data_set["size"] + n_correct = 0 + num_batches = n_total // args.batch_size + batch_indices = np.split(range(n_total), + range(args.batch_size, n_total, args.batch_size)) + model.eval() + for batch_ix in range(num_batches): + batch_questions = data_set["questions"][batch_indices[batch_ix]] + batch_relations = data_set["rel_labels"][batch_indices[batch_ix]] + inputs = Variable(read_text_tensor(batch_questions, word_vocab), volatile=True) + targets = Variable(read_labels_tensor(batch_relations, rel_vocab), volatile=True) + if args.cuda: + inputs.data = inputs.data.cuda() + targets.data = targets.data.cuda() + scores = model(inputs) + pred_score, pred_label_ix = torch.max(scores, dim=1) # check this properly + pred_label_ix = pred_label_ix.view(args.batch_size) + sum_correct = torch.sum(torch.eq(pred_label_ix, targets)) + n_correct += sum_correct.data[0] + acc = n_correct / (num_batches * args.batch_size) + return acc + +def repackage_hidden(h): + """Wraps hidden states in new Variables, to detach them from their history.""" + if type(h) == Variable: + return Variable(h.data) + else: + return tuple(repackage_hidden(v) for v in h) + + +# ---- dataset paths ------ +data_dir = "data/SimpleQuestions_v2/" +train_file = os.path.join(data_dir, "annotated_fb_data_train.txt") +val_file = os.path.join(data_dir, "annotated_fb_data_valid.txt") +test_file = os.path.join(data_dir, "annotated_fb_data_test.txt") + +# ---- load GloVe embeddings ------ +embed_pt_filepath = 'data/glove/glove.840B.300d.pt' +emb_w2i, emb_vecs = read_embedding(embed_pt_filepath) +emb_vocab = Vocab(emb_w2i) +emb_dim = emb_vecs.size()[1] + +# ---- create dataset vocabulary and embeddings ------ +vocab_pt_filepath = os.path.join(data_dir, "vocab.pt") +word2index_dict, rel2index_dict = torch.load(vocab_pt_filepath) + +word_vocab = Vocab(word2index_dict) +word_vocab.add_pad_token("") +word_vocab.add_unk_token("") + +rel_vocab = Vocab(rel2index_dict) + +vocab_size = word_vocab.size +num_classes = len(rel2index_dict) +print('vocab size = {}'.format(vocab_size)) +print('num classes = {}'.format(num_classes)) + +num_unk = 0 +vecs = torch.FloatTensor(vocab_size, emb_dim) +for i in range(vocab_size): + word = word_vocab.get_token(i) + if emb_vocab.contains(word): + vecs[i] = emb_vecs[emb_vocab.get_index(word)] + elif word == word_vocab.pad_token: + vecs[i].zero_() + else: + num_unk += 1 + vecs[i].uniform_(-0.05, 0.05) + +print('unk vocab count = {}'.format(num_unk)) +emb_vocab = None +emb_vecs = None + +# ---- load datasets ------ +print("loading train/val/test datasets...") +train_dataset = read_dataset(train_file, word_vocab, rel_vocab) +val_dataset = read_dataset(val_file, word_vocab, rel_vocab) +test_dataset = read_dataset(test_file, word_vocab, rel_vocab) +print('train_file: {}, num train = {}'.format(train_file, train_dataset["size"])) +print('val_file: {}, num dev = {}'.format(val_file, val_dataset["size"])) +print('test_file: {}, num test = {}'.format(test_file, test_dataset["size"])) + + +# ---- Define Model, Loss, Optim ------ +config = args +config.vocab_size = vocab_size +config.d_out = num_classes +config.n_directions = 2 if config.birnn else 1 +print(config) +model = RelationPredictor(config) +# initialize the embedding layer with the word vectors +model.embed.weight.data = vecs +if args.cuda: + model.cuda() +loss_function = nn.NLLLoss() +optimizer = optim.Adam(model.parameters(), lr=args.lr) + +# ---- Train Model ------ +start = time.time() +best_val_acc = -1 +iter = 0 +header = ' Time Epoch Iteration Loss Train/Acc. Val/Acc.' +print(header) +log_template = ' '.join('{:>6.0f},{:>5.0f},{:>9.0f},{:>9.6f}'.split(',')) +dev_log_template = ' '.join('{:>6.0f},{:>5.0f},{:>9.0f},{:>9.6f},{:9.6f},{:11.6f}'.split(',')) + +model.train() +for epoch in range(args.epochs): + # shuffle the dataset and create batches (truncate the last batch if not of equal size) + shuffled_indices = np.random.permutation(train_dataset["size"]) + num_batches = len(shuffled_indices) // args.batch_size + batch_indices = np.split(shuffled_indices, + range(args.batch_size, len(shuffled_indices), args.batch_size)) + model.encoder.hidden = model.encoder.init_hidden() + for batch_ix in range(num_batches): + iter += 1 + batch_questions = train_dataset["questions"][batch_indices[batch_ix]] + batch_relations = train_dataset["rel_labels"][batch_indices[batch_ix]] + inputs = Variable( read_text_tensor(batch_questions, word_vocab) ) + targets = Variable( read_labels_tensor(batch_relations, rel_vocab) ) + if args.cuda: + inputs.data = inputs.data.cuda() + targets.data = targets.data.cuda() + + # clear out gradients and hidden states of the model + model.zero_grad() + model.encoder.hidden = repackage_hidden(model.encoder.hidden) + + # prepare inputs for LSTM model and run forward pass + scores = model(inputs) + + # compute the loss, gradients, and update the parameters + loss = loss_function(scores, targets) + loss.backward() + + # `clip_grad_norm` helps prevent the exploding gradient problem in RNNs / LSTMs. + torch.nn.utils.clip_grad_norm(model.parameters(), args.clip) + optimizer.step() + + # log at intervals + if iter % args.dev_every == 0: + train_acc = evaluate_dataset_batch(train_dataset, model) + val_acc = evaluate_dataset_batch(val_dataset, model) + model.train() + print(dev_log_template.format(time.time()-start, epoch, iter, loss.data[0], train_acc, val_acc)) + if val_acc > best_val_acc: + best_val_acc = val_acc + snapshot_prefix = os.path.join(args.save_path, 'best_snapshot') + snapshot_path = snapshot_prefix + '_valacc_{:6.4f}_trainacc{:6.4f}_iter_{}_model.pt'.format(val_acc, train_acc, iter) + torch.save(model.state_dict(), snapshot_path) + for f in glob.glob(snapshot_prefix + '*'): + if f != snapshot_path: + os.remove(f) + + elif iter == 1 or iter % args.log_every == 0: + print(log_template.format(time.time() - start, epoch, iter, loss.cpu().data[0])) + diff --git a/simple_qa_rnn/util.py b/simple_qa_rnn/util.py deleted file mode 100644 index 370e8a2..0000000 --- a/simple_qa_rnn/util.py +++ /dev/null @@ -1,22 +0,0 @@ -from argparse import ArgumentParser - -def get_args(): - parser = ArgumentParser(description='Simple QA BiGRU model - Ferhan') - parser.add_argument('--epochs', type=int, default=30) - parser.add_argument('--batch_size', type=int, default=256) - parser.add_argument('--d_embedding', type=int, default=300) - parser.add_argument('--d_hidden', type=int, default=300) - parser.add_argument('--n_layers', type=int, default=1) - parser.add_argument('--test', action='store_true', dest='test', help='turn on test mode; no training.') - parser.add_argument('--not_bidirectional', action='store_false', dest='birnn') - parser.add_argument('--clip', type=float, default=0.25, help='gradient clipping') - parser.add_argument('--log_every', type=int, default=50) - parser.add_argument('--lr', type=float, default=1e-3) - parser.add_argument('--dev_every', type=int, default=300) - parser.add_argument('--save_every', type=int, default=1000) - parser.add_argument('--dropout_prob', type=int, default=0.2) - parser.add_argument('--gpu', type=int, default=0) - parser.add_argument('--save_path', type=str, default='saved_checkpoints') - parser.add_argument('--resume_snapshot', type=str, default='') - args = parser.parse_args() - return args diff --git a/simple_qa_rnn/utils/__init__.py b/simple_qa_rnn/utils/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/simple_qa_rnn/utils/read_data.py b/simple_qa_rnn/utils/read_data.py new file mode 100644 index 0000000..d9719fb --- /dev/null +++ b/simple_qa_rnn/utils/read_data.py @@ -0,0 +1,71 @@ +import torch +import nltk +import string +from torch.autograd import Variable +import numpy as np + +## functions for loading data from disk + +def process_tokenize_text(text): + punc_remover = str.maketrans('', '', string.punctuation) + processed_text = text.lower().translate(punc_remover) + tokens = nltk.word_tokenize(processed_text) + return tokens + +def read_embedding(embed_pt_filepath): + embed_tuple = torch.load(embed_pt_filepath) + word2index, w2v_tensor, dim = embed_tuple + return word2index, w2v_tensor + + +def find_max_seq_length(text): + max_len = -1 + for tokens in text: + curr_len = len(tokens) + if curr_len > max_len: + max_len = curr_len + return max_len + + +def read_text_tensor(batch_text, word_vocab): + out_text = [] + max_len = find_max_seq_length(batch_text) + for sent_tokens in batch_text: + S = len(sent_tokens) + sent = [] + for i in range(S): + token = sent_tokens[i] + sent.append( word_vocab.get_index(token) ) + # pad the right end till the max length of the mini batch + for i in range(S, max_len): + sent.append( word_vocab.pad_index ) + out_text.append(sent) + return torch.LongTensor(out_text) + +def read_labels_tensor(rel_labels, rel_vocab, cuda=False): + N = len(rel_labels) + labels_list = [] + for i in range(N): + token = rel_labels[i] + labels_list.append( rel_vocab.get_index(token) ) + return torch.LongTensor(labels_list) + +def read_dataset(datapath, word_vocab, rel_vocab): + questions = [] + rel_labels = [] + # read questions and label from the datapath - could be train, dev, testls + with open(datapath) as f: + for line in f: + line_items = line.split("\t") + # add relation + relation = line_items[1] + rel_labels.append(relation) + # add text + qText = line_items[3] + tokens = process_tokenize_text(qText) + questions.append(tokens) + + dataset = {"word_vocab": word_vocab, "rel_vocab": rel_vocab, "size": len(rel_labels), + "questions": np.array(questions), "rel_labels": np.array(rel_labels)} + return dataset + diff --git a/simple_qa_rnn/utils/vocab.py b/simple_qa_rnn/utils/vocab.py new file mode 100644 index 0000000..834f13d --- /dev/null +++ b/simple_qa_rnn/utils/vocab.py @@ -0,0 +1,68 @@ +import torch + +class Vocab(object): + """ + A vocabulary object. Initialized from a file with one vocabulary token per line. + Maps between vocabulary tokens and indices. If an UNK token is defined in the + vocabulary, returns the index to this token if queried for an out-of-vocabulary + token. + """ + + # def __init__(self, vocabpath): + # self.size = 0 + # self.index = {} + # self.tokens = {} + # + # with open(vocabpath, 'r') as f: + # for line in f: + # word = line.rstrip() + # self.tokens[self.size] = word + # self.index[word] = self.size + # self.size += 1 + # # automatically add unknown token + # # self.add_unk_token("") + + def __init__(self, word2index): + self.index = word2index + self.size = len(word2index) + self.tokens = {index: word for word, index in word2index.items()} + # self.add_unk_token("") + + def contains(self, word): + return word in self.index.keys() + + def add(self, word): + if not self.contains(word): + self.tokens[self.size] = word + self.index[word] = self.size + self.size += 1 + + def add_unk_token(self, token): + self.unk_token = token + self.add(token) + self.unk_index = self.index[token] + + def add_pad_token(self, token): + self.pad_token = token + self.add(token) + self.pad_index = self.index[token] + + def get_index(self, word): + if self.contains(word): + return self.index[word] + else: + print("{} - word not found in vocab. returning unk_index".format(word)) + return self.unk_index + + def get_token(self, index): + if index < 0 or index >= self.size: + raise IndexError('index {} out of bounds'.format(index)) + return self.tokens[index] + + def map(self, tokens): + N = len(tokens) + out = torch.IntTensor(N) + for i in range(N): + out[i] = self.index(tokens[i]) + return out + diff --git a/sm_cnn/.gitignore b/sm_cnn/.gitignore index 8206f0d..3c6f031 100644 --- a/sm_cnn/.gitignore +++ b/sm_cnn/.gitignore @@ -1,2 +1,4 @@ *pyc -trec_eval-8.0/trec_eval \ No newline at end of file +trec_eval-8.0/trec_eval +data/ +trained_models/