mirror of
https://github.com/wassname/Castor.git
synced 2026-09-09 11:13:20 +08:00
Used torchtext to refactor relation prediction model - much cleaner! (#33)
Refactored the code for Ferhan's relation prediction model using torchtext
This commit is contained in:
committed by
Jimmy Lin
parent
a0755e6aa3
commit
061cc8dd09
@@ -1,4 +1,6 @@
|
||||
data/
|
||||
data/*
|
||||
data/glove/
|
||||
resources/
|
||||
saved_checkpoints/
|
||||
__pycache__/
|
||||
relation_prediction/*cache*/*
|
||||
+16
-9
@@ -1,14 +1,21 @@
|
||||
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.
|
||||
## Relation Prediction Model
|
||||
|
||||
- Download and extract SimpleQuestions dataset by running the script:
|
||||
```
|
||||
bash fetch_and_preprocess.sh
|
||||
bash fetch_dataset.sh
|
||||
```
|
||||
3. Run this command to train the model. Make sure you have PyTorch and other Python dependencies installed.
|
||||
|
||||
- You will also require the package - [torchtext](https://github.com/pytorch/text).
|
||||
```
|
||||
python train_relation_model.py
|
||||
git clone https://github.com/pytorch/text.git
|
||||
cd path/to/torchtext
|
||||
python setup.py install
|
||||
```
|
||||
For GPU, use:
|
||||
|
||||
- Run the training script with the following commands. Please check out args.py file to see the different commands available:
|
||||
```
|
||||
cd relation_prediction
|
||||
python train.py
|
||||
python train.py --no_cuda
|
||||
python train.py --rnn_type gru
|
||||
```
|
||||
python train_relation_model.py --cuda
|
||||
```
|
||||
@@ -1,25 +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=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
|
||||
@@ -1,14 +0,0 @@
|
||||
#!/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
|
||||
Executable
+8
@@ -0,0 +1,8 @@
|
||||
#!/bin/bash
|
||||
|
||||
# download dataset and put it in data directory
|
||||
mkdir data
|
||||
pushd data
|
||||
wget https://www.dropbox.com/s/tohrsllcfy7rch4/SimpleQuestions_v2.tgz
|
||||
tar -xvzf SimpleQuestions_v2.tgz
|
||||
popd
|
||||
@@ -1,65 +0,0 @@
|
||||
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
|
||||
@@ -0,0 +1,32 @@
|
||||
import os
|
||||
|
||||
from argparse import ArgumentParser
|
||||
|
||||
def get_args():
|
||||
parser = ArgumentParser(description='Simple QA model - Ferhan Ture')
|
||||
parser.add_argument('--epochs', type=int, default=40)
|
||||
parser.add_argument('--batch_size', type=int, default=32)
|
||||
parser.add_argument('--rnn_type', type=str, default='lstm') # or use 'gru'
|
||||
parser.add_argument('--d_embed', 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-5)
|
||||
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_gradient', type=float, default=0.5, help='gradient clipping')
|
||||
parser.add_argument('--log_every', type=int, default=300)
|
||||
parser.add_argument('--dev_every', type=int, default=1200)
|
||||
parser.add_argument('--save_every', type=int, default=1200)
|
||||
parser.add_argument('--dropout_prob', type=float, default=0.3)
|
||||
parser.add_argument('--patience', type=int, default=10, help="number of epochs to wait before early stopping")
|
||||
parser.add_argument('--no_cuda', action='store_false', help='do not use CUDA', dest='cuda')
|
||||
parser.add_argument('--gpu', type=int, default=0, help='GPU device to use') # use -1 for CPU
|
||||
parser.add_argument('--seed', type=int, default=1111, help='random seed for reproducing results')
|
||||
parser.add_argument('--save_path', type=str, default='saved_checkpoints')
|
||||
parser.add_argument('--data_cache', type=str, default=os.path.join(os.getcwd(), 'data_cache'))
|
||||
parser.add_argument('--vector_cache', type=str, default=os.path.join(os.getcwd(), 'vector_cache/input_vectors.pt'))
|
||||
parser.add_argument('--word_vectors', type=str, default='glove.42B')
|
||||
parser.add_argument('--train_embed', action='store_false', dest='fix_emb') # fine-tune the word embeddings
|
||||
parser.add_argument('--resume_snapshot', type=str, default='')
|
||||
args = parser.parse_args()
|
||||
return args
|
||||
+2
-2
@@ -10,8 +10,8 @@ for id in range(count):
|
||||
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 {} >> " \
|
||||
command = "python train.py --dev_every 500 --log_every 250 --batch_size 32 " \
|
||||
"--epochs {} --lr {} --d_hidden {} --n_layers {} --dropout_prob {} --clip_gradient {} >> " \
|
||||
"results.txt".format(epochs, learning_rate, d_hidden, n_layers, dropout, clip)
|
||||
|
||||
print("Running: " + command)
|
||||
@@ -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
|
||||
if config.rnn_type.lower() == "gru":
|
||||
self.rnn = nn.GRU(input_size=config.d_embed, hidden_size=config.d_hidden,
|
||||
num_layers=config.n_layers, dropout=config.dropout_prob,
|
||||
bidirectional=config.birnn)
|
||||
else:
|
||||
self.rnn = nn.LSTM(input_size=config.d_embed, hidden_size=config.d_hidden,
|
||||
num_layers=config.n_layers, dropout=config.dropout_prob,
|
||||
bidirectional=config.birnn)
|
||||
|
||||
|
||||
def forward(self, inputs):
|
||||
# shape of `inputs` - (sequence length, batch size, dimension of embedding)
|
||||
batch_size = inputs.size()[1]
|
||||
state_shape = self.config.n_cells, batch_size, self.config.d_hidden
|
||||
if self.config.rnn_type.lower() == "gru":
|
||||
h0 = Variable(inputs.data.new(*state_shape).zero_())
|
||||
outputs, ht = self.rnn(inputs, h0)
|
||||
else:
|
||||
h0 = c0 = Variable(inputs.data.new(*state_shape).zero_())
|
||||
outputs, (ht, ct) = self.rnn(inputs, (h0, c0))
|
||||
return ht[-1] if not self.config.birnn else ht[-2:].transpose(0, 1).contiguous().view(batch_size, -1)
|
||||
|
||||
|
||||
class RelationClassifier(nn.Module):
|
||||
|
||||
def __init__(self, config):
|
||||
super(RelationClassifier, self).__init__()
|
||||
self.config = config
|
||||
self.embed = nn.Embedding(config.n_embed, config.d_embed)
|
||||
self.encoder = Encoder(config)
|
||||
self.dropout = nn.Dropout(p=config.dropout_prob)
|
||||
self.relu = nn.ReLU()
|
||||
seq_in_size = config.d_hidden
|
||||
if self.config.birnn:
|
||||
seq_in_size *= 2
|
||||
|
||||
self.out = nn.Sequential(
|
||||
nn.Linear(seq_in_size, seq_in_size), # can apply batch norm after this - add later
|
||||
nn.BatchNorm1d(seq_in_size),
|
||||
self.relu,
|
||||
self.dropout,
|
||||
nn.Linear(seq_in_size, config.d_out)
|
||||
)
|
||||
|
||||
def forward(self, batch):
|
||||
# shape of `batch` - (sequence length, batch size)
|
||||
question_embed = self.embed(batch.question)
|
||||
if self.config.fix_emb:
|
||||
question_embed = Variable(question_embed.data)
|
||||
# shape of `question_embed` - (sequence length, batch size, dimension of embedding)
|
||||
question_encoded = self.encoder(question_embed)
|
||||
# shape of `question_encoded` - (batch size, number of cells X size of hidden)
|
||||
output = self.out(question_encoded)
|
||||
scores = F.log_softmax(output)
|
||||
return scores
|
||||
@@ -0,0 +1,72 @@
|
||||
import os
|
||||
|
||||
from torchtext import data
|
||||
|
||||
# most basic tokenizer - split on whitespace
|
||||
def my_tokenizer():
|
||||
return lambda text: [tok for tok in text.split()]
|
||||
|
||||
class SimpleQaRelationDataset(data.ZipDataset, data.TabularDataset):
|
||||
|
||||
url = 'https://www.dropbox.com/s/tohrsllcfy7rch4/SimpleQuestions_v2.tgz'
|
||||
filename = 'SimpleQuestions_v2.tgz'
|
||||
dirname = 'SimpleQuestions_v2'
|
||||
|
||||
@staticmethod
|
||||
def sort_key(ex):
|
||||
return len(ex.question)
|
||||
|
||||
@classmethod
|
||||
def splits(cls, text_field, label_field, root='../data',
|
||||
train='train.txt', validation='valid.txt', test='test.txt'):
|
||||
"""Create dataset objects for splits of the Simple QA dataset.
|
||||
This is the most flexible way to use the dataset.
|
||||
Arguments:
|
||||
text_field: The field that will be used for premise and hypothesis
|
||||
data.
|
||||
label_field: The field that will be used for label data.
|
||||
root: The root directory that the dataset's zip archive will be
|
||||
expanded into; therefore the directory in which the
|
||||
train/valid/test data files will be stored.
|
||||
train: The filename of the train data. Default: 'annotated_fb_data_train.txt'.
|
||||
validation: The filename of the validation data, or None to not
|
||||
load the validation set. Default: 'annotated_fb_data_valid.txt'.
|
||||
test: The filename of the test data, or None to not load the test
|
||||
set. Default: 'annotated_fb_data_test.txt'.
|
||||
"""
|
||||
print("root path for relation dataset: {}".format(root))
|
||||
path = cls.download_or_unzip(root)
|
||||
prefix_fname = 'annotated_fb_data_'
|
||||
return super(SimpleQaRelationDataset, cls).splits(
|
||||
os.path.join(path, prefix_fname), train, validation, test,
|
||||
format='TSV', fields=[('subject', None), ('relation', label_field), (object, None), ('question', text_field)]
|
||||
)
|
||||
|
||||
@classmethod
|
||||
def iters(cls, batch_size=32, device=0, root='.', wv_dir='.',
|
||||
wv_type=None, wv_dim='300d', **kwargs):
|
||||
"""Create iterator objects for splits of the Simple QA dataset.
|
||||
This is the simplest way to use the dataset, and assumes common
|
||||
defaults for field, vocabulary, and iterator parameters.
|
||||
Arguments:
|
||||
batch_size: Batch size.
|
||||
device: Device to create batches on. Use -1 for CPU and None for
|
||||
the currently active GPU device.
|
||||
root: The root directory that the dataset's zip archive will be
|
||||
expanded into; therefore the directory in whose wikitext-2
|
||||
subdirectory the data files will be stored.
|
||||
wv_dir, wv_type, wv_dim: Passed to the Vocab constructor for the
|
||||
text field. The word vectors are accessible as
|
||||
train.dataset.fields['text'].vocab.vectors.
|
||||
Remaining keyword arguments: Passed to the splits method.
|
||||
"""
|
||||
TEXT = data.Field(tokenize=my_tokenizer())
|
||||
LABEL = data.Field(sequential=False)
|
||||
|
||||
train, val, test = cls.splits(TEXT, LABEL, root=root, **kwargs)
|
||||
|
||||
TEXT.build_vocab(train, wv_dir=wv_dir, wv_type=wv_type, wv_dim=wv_dim)
|
||||
LABEL.build_vocab(train)
|
||||
|
||||
return data.BucketIterator.splits(
|
||||
(train, val, test), batch_size=batch_size, device=device)
|
||||
@@ -0,0 +1,161 @@
|
||||
import os
|
||||
import sys
|
||||
import time
|
||||
import glob
|
||||
import numpy as np
|
||||
import torch
|
||||
import torch.nn as nn
|
||||
import torch.optim as optim
|
||||
|
||||
from torch.autograd import Variable
|
||||
from torchtext import data
|
||||
|
||||
from model import RelationClassifier
|
||||
from args import get_args
|
||||
from simple_qa_relation import SimpleQaRelationDataset
|
||||
|
||||
# get the configuration arguments and set machine - GPU/CPU
|
||||
args = get_args()
|
||||
# set random seeds for reproducibility
|
||||
torch.manual_seed(args.seed)
|
||||
if not args.cuda:
|
||||
args.gpu = -1
|
||||
if torch.cuda.is_available() and not args.cuda:
|
||||
print("WARNING: You have CUDA but not using it.")
|
||||
if torch.cuda.is_available() and args.cuda:
|
||||
torch.cuda.set_device(args.gpu)
|
||||
torch.cuda.manual_seed(args.seed)
|
||||
|
||||
# ---- prepare the dataset with Torchtext -----
|
||||
questions = data.Field(lower=True)
|
||||
relations = data.Field(sequential=False)
|
||||
|
||||
train, dev, test = SimpleQaRelationDataset.splits(questions, relations)
|
||||
|
||||
# build vocab for questions
|
||||
questions.build_vocab(train, dev, test)
|
||||
|
||||
# load word vectors if already saved or else load it from start and save it
|
||||
if os.path.isfile(args.vector_cache):
|
||||
questions.vocab.vectors = torch.load(args.vector_cache)
|
||||
else:
|
||||
questions.vocab.load_vectors(wv_dir=args.data_cache, wv_type=args.word_vectors, wv_dim=args.d_embed)
|
||||
os.makedirs(os.path.dirname(args.vector_cache), exist_ok=True)
|
||||
torch.save(questions.vocab.vectors, args.vector_cache)
|
||||
|
||||
# build vocab for relations
|
||||
relations.build_vocab(train, dev, test)
|
||||
|
||||
# BucketIterator buckets the examples according to length so less padding is needed
|
||||
train_iter, dev_iter, test_iter = data.BucketIterator.splits(
|
||||
(train, dev, test), batch_size=args.batch_size, device=args.gpu)
|
||||
train_iter.repeat = False # do not repeat examples after finishing an epoch
|
||||
|
||||
|
||||
# ---- define the model, loss, optim ------
|
||||
config = args
|
||||
config.n_embed = len(questions.vocab) # vocab. size / number of embeddings
|
||||
config.d_out = len(relations.vocab)
|
||||
config.n_cells = config.n_layers
|
||||
# double the number of cells for bidirectional networks
|
||||
if config.birnn:
|
||||
config.n_cells *= 2
|
||||
print(config)
|
||||
|
||||
if args.resume_snapshot:
|
||||
model = torch.load(args.resume_snapshot, map_location=lambda storage,location: storage.cuda(args.gpu))
|
||||
else:
|
||||
model = RelationClassifier(config)
|
||||
if args.word_vectors:
|
||||
model.embed.weight.data = questions.vocab.vectors
|
||||
if args.cuda:
|
||||
model.cuda()
|
||||
|
||||
criterion = nn.NLLLoss()
|
||||
optimizer = optim.Adam(model.parameters(), lr=args.lr)
|
||||
|
||||
|
||||
# ---- train the model ------
|
||||
iterations = 0
|
||||
start = time.time()
|
||||
best_dev_acc = -1
|
||||
train_iter.repeat = False
|
||||
header = ' Time Epoch Iteration Progress (%Epoch) Loss Dev/Loss Accuracy Dev/Accuracy'
|
||||
dev_log_template = ' '.join('{:>6.0f},{:>5.0f},{:>9.0f},{:>5.0f}/{:<5.0f} {:>7.0f}%,{:>8.6f},{:8.6f},{:12.4f},{:12.4f}'.split(','))
|
||||
log_template = ' '.join('{:>6.0f},{:>5.0f},{:>9.0f},{:>5.0f}/{:<5.0f} {:>7.0f}%,{:>8.6f},{},{:12.4f},{}'.split(','))
|
||||
os.makedirs(args.save_path, exist_ok=True)
|
||||
print(header)
|
||||
|
||||
for epoch in range(args.epochs):
|
||||
train_iter.init_epoch()
|
||||
n_correct, n_total = 0, 0
|
||||
|
||||
for batch_idx, batch in enumerate(train_iter):
|
||||
iterations += 1
|
||||
|
||||
# switch model to training mode, clear gradient accumulators
|
||||
model.train(); optimizer.zero_grad()
|
||||
|
||||
# forward pass
|
||||
answer = model(batch)
|
||||
|
||||
# calculate accuracy of predictions in the current batch
|
||||
n_correct += (torch.max(answer, 1)[1].view(batch.relation.size()).data == batch.relation.data).sum()
|
||||
n_total += batch.batch_size
|
||||
train_acc = 100. * n_correct/n_total
|
||||
|
||||
# calculate loss of the network output with respect to training labels & backpropagate to compute gradients
|
||||
loss = criterion(answer, batch.relation)
|
||||
loss.backward()
|
||||
|
||||
# clip the gradients (prevent exploding gradients) and update the weights
|
||||
torch.nn.utils.clip_grad_norm(model.parameters(), args.clip_gradient)
|
||||
optimizer.step()
|
||||
|
||||
# checkpoint model periodically
|
||||
if iterations % args.save_every == 0:
|
||||
snapshot_prefix = os.path.join(args.save_path, 'snapshot')
|
||||
snapshot_path = snapshot_prefix + '_acc_{:.4f}_loss_{:.6f}_iter_{}_model.pt'.format(train_acc, loss.data[0], iterations)
|
||||
torch.save(model, snapshot_path)
|
||||
for f in glob.glob(snapshot_prefix + '*'):
|
||||
if f != snapshot_path:
|
||||
os.remove(f)
|
||||
|
||||
# evaluate performance on validation set periodically
|
||||
if iterations % args.dev_every == 0:
|
||||
|
||||
# switch model to evaluation mode
|
||||
model.eval(); dev_iter.init_epoch()
|
||||
|
||||
# calculate accuracy on validation set
|
||||
n_dev_correct, dev_loss = 0, 0
|
||||
for dev_batch_idx, dev_batch in enumerate(dev_iter):
|
||||
answer = model(dev_batch)
|
||||
n_dev_correct += (torch.max(answer, 1)[1].view(dev_batch.relation.size()).data == dev_batch.relation.data).sum()
|
||||
dev_loss = criterion(answer, dev_batch.relation)
|
||||
dev_acc = 100. * n_dev_correct / len(dev)
|
||||
|
||||
print(dev_log_template.format(time.time()-start,
|
||||
epoch, iterations, 1+batch_idx, len(train_iter),
|
||||
100. * (1+batch_idx) / len(train_iter), loss.data[0], dev_loss.data[0], train_acc, dev_acc))
|
||||
|
||||
# update best valiation set accuracy
|
||||
if dev_acc > best_dev_acc:
|
||||
# found a model with better validation set accuracy
|
||||
best_dev_acc = dev_acc
|
||||
snapshot_prefix = os.path.join(args.save_path, 'best_snapshot')
|
||||
snapshot_path = snapshot_prefix + '_devacc_{}_devloss_{}__iter_{}_model.pt'.format(dev_acc, dev_loss.data[0], iterations)
|
||||
|
||||
# save model, delete previous 'best_snapshot' files
|
||||
torch.save(model, snapshot_path)
|
||||
for f in glob.glob(snapshot_prefix + '*'):
|
||||
if f != snapshot_path:
|
||||
os.remove(f)
|
||||
|
||||
elif iterations % args.log_every == 0:
|
||||
|
||||
# print progress message
|
||||
print(log_template.format(time.time()-start,
|
||||
epoch, iterations, 1+batch_idx, len(train_iter),
|
||||
100. * (1+batch_idx) / len(train_iter), loss.data[0], ' '*8, n_correct/n_total*100, ' '*12))
|
||||
|
||||
@@ -1,207 +0,0 @@
|
||||
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
|
||||
@@ -1,43 +0,0 @@
|
||||
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!")
|
||||
@@ -1,57 +0,0 @@
|
||||
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)
|
||||
|
||||
@@ -1,84 +0,0 @@
|
||||
"""
|
||||
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!")
|
||||
@@ -1,72 +0,0 @@
|
||||
import gensim
|
||||
import glob
|
||||
import pickle
|
||||
import nltk
|
||||
import string
|
||||
import numpy as np
|
||||
|
||||
fnames = glob.glob('../datasets/SimpleQuestions_v2/annotated*.txt')
|
||||
all_lines = []
|
||||
for fname in fnames:
|
||||
with open(fname) as fin:
|
||||
for line in fin:
|
||||
all_lines.append(line.rstrip())
|
||||
|
||||
print("num of examples in train/val/test: {}".format(len(all_lines)))
|
||||
|
||||
max_sent_length = -1
|
||||
for line in all_lines:
|
||||
qText = line.split("\t")[3]
|
||||
punc_remover = str.maketrans('', '', string.punctuation)
|
||||
processed_text = qText.lower().translate(punc_remover)
|
||||
tokens = nltk.word_tokenize(processed_text)
|
||||
if len(tokens) > max_sent_length:
|
||||
max_sent_length = len(tokens)
|
||||
|
||||
print("max_sent_length: {}".format(max_sent_length))
|
||||
|
||||
|
||||
|
||||
# # get all the words in the train, dev, val, test set
|
||||
# all_words = set()
|
||||
# for line in all_lines:
|
||||
# qText = line.split("\t")[3]
|
||||
# # process text: remove punctuations, lowercase
|
||||
# punc_remover = str.maketrans('', '', string.punctuation)
|
||||
# processed_text = qText.lower().translate(punc_remover)
|
||||
# tokens = nltk.word_tokenize(processed_text)
|
||||
# for tok in tokens:
|
||||
# all_words.add(tok)
|
||||
#
|
||||
# print("vocab. size: {}".format(len(all_words)))
|
||||
#
|
||||
# # word_to_ix = { word:i for i, word in enumerate(all_words) }
|
||||
# # # dump the pickle
|
||||
# # with open("../resources/word_to_ix_SQ.pkl", 'wb') as fh:
|
||||
# # pickle.dump(word_to_ix, fh)
|
||||
#
|
||||
# word_to_vector_map = {}
|
||||
# w2v_path = "../resources/GoogleNews-vectors-negative300.bin.gz"
|
||||
# # get their word vectors
|
||||
# print("loading word vectors...")
|
||||
# word_vectors = gensim.models.KeyedVectors.load_word2vec_format(w2v_path, binary=True)
|
||||
#
|
||||
# # store in a dict
|
||||
# found = 0
|
||||
# random = 0
|
||||
# for w in all_words:
|
||||
# # print(w)
|
||||
# try:
|
||||
# word_to_vector_map[w] = word_vectors[w]
|
||||
# found += 1
|
||||
# except:
|
||||
# word_to_vector_map[w] = np.random.uniform(low=0.0, high=1.0, size=300)
|
||||
# random += 1
|
||||
#
|
||||
# print("found: {}".format(found))
|
||||
# print("random: {}".format(random))
|
||||
#
|
||||
# # dump the pickle
|
||||
# print("dumping the w2v map pickle...")
|
||||
# with open("../resources/w2v_map_SQ.pkl", 'wb') as fh:
|
||||
# pickle.dump(word_to_vector_map, fh)
|
||||
@@ -1,56 +0,0 @@
|
||||
import gensim
|
||||
import glob
|
||||
import json
|
||||
import pickle
|
||||
import nltk
|
||||
import string
|
||||
import numpy as np
|
||||
|
||||
fnames = glob.glob('../datasets/dataset-factoid-webquestions/main/*.json')
|
||||
all_entries = []
|
||||
for fname in fnames:
|
||||
with open(fname) as fin:
|
||||
data = json.load(fin)
|
||||
all_entries.extend(data)
|
||||
|
||||
print("num of examples in train/val/test: {}".format(len(all_entries)))
|
||||
|
||||
# get all the words in the train, dev, val, test set
|
||||
all_words = set()
|
||||
for entry in all_entries:
|
||||
qText = entry.get('qText')
|
||||
# process text: remove punctuations, lowercase
|
||||
punc_remover = str.maketrans('', '', string.punctuation)
|
||||
processed_text = qText.lower().translate(punc_remover)
|
||||
tokens = nltk.word_tokenize(processed_text)
|
||||
for tok in tokens:
|
||||
all_words.add(tok)
|
||||
|
||||
print("vocab. size: {}".format(len(all_words)))
|
||||
|
||||
word_to_vector_map = {}
|
||||
|
||||
w2v_path = "../resources/GoogleNews-vectors-negative300.bin.gz"
|
||||
# get their word vectors
|
||||
print("loading word vectors...")
|
||||
word_vectors = gensim.models.KeyedVectors.load_word2vec_format(w2v_path, binary=True)
|
||||
|
||||
# store in a dict
|
||||
found = 0
|
||||
random = 0
|
||||
for w in all_words:
|
||||
# print(w)
|
||||
try:
|
||||
word_to_vector_map[w] = word_vectors[w]
|
||||
found += 1
|
||||
except:
|
||||
word_to_vector_map[w] = np.random.uniform(low=0.0, high=1.0, size=300)
|
||||
random += 1
|
||||
|
||||
print("found: {}".format(found))
|
||||
print("random: {}".format(random))
|
||||
|
||||
# dump the pickle
|
||||
print("dumping the w2v map pickle...")
|
||||
with open("../resources/w2v_map_WQ.pkl", 'wb') as fh:
|
||||
pickle.dump(word_to_vector_map, fh)
|
||||
@@ -1,11 +0,0 @@
|
||||
import pickle
|
||||
|
||||
# load the pickled word vectors
|
||||
w2v_pkl_path = "w2v_map_SQ.pkl"
|
||||
print("loading word vectors from the pickle...")
|
||||
w2v_map = None
|
||||
with open(w2v_pkl_path, 'rb') as fh:
|
||||
w2v_map = pickle.load(fh)
|
||||
|
||||
print(list(w2v_map.keys()))
|
||||
print(len(w2v_map.keys()))
|
||||
@@ -1,27 +0,0 @@
|
||||
import glob
|
||||
import pickle
|
||||
|
||||
fnames = glob.glob('../datasets/SimpleQuestions_v2/annotated*.txt')
|
||||
all_lines = []
|
||||
for fname in fnames:
|
||||
with open(fname) as fin:
|
||||
for line in fin:
|
||||
all_lines.append(line.rstrip())
|
||||
|
||||
print("num of examples in train/val/test: {}".format(len(all_lines)))
|
||||
|
||||
all_relations = set()
|
||||
for line in all_lines:
|
||||
rel = line.split("\t")[1]
|
||||
all_relations.add(rel)
|
||||
|
||||
print("num of relation types: {}".format(len(all_relations)))
|
||||
print(all_relations)
|
||||
print( ("www.freebase.com/music/release/region") in all_relations )
|
||||
|
||||
rel_to_ix = { rel:i for i, rel in enumerate(all_relations) }
|
||||
# print(rel_to_ix)
|
||||
# # dump the pickle
|
||||
# print("dumping the w2v map pickle...")
|
||||
# with open("../resources/rel_to_ix_SQ.pkl", 'wb') as fh:
|
||||
# pickle.dump(rel_to_ix, fh)
|
||||
@@ -1,24 +0,0 @@
|
||||
import glob
|
||||
import json
|
||||
|
||||
fnames = glob.glob('dataset-factoid-webquestions/d-freebase-rp/*.json')
|
||||
all_entries = []
|
||||
for fname in fnames:
|
||||
with open(fname) as fin:
|
||||
data = json.load(fin)
|
||||
all_entries.extend(data)
|
||||
|
||||
print("num of examples in train/val/test: {}".format(len(all_entries)))
|
||||
|
||||
all_relations = set()
|
||||
for entry in all_entries:
|
||||
qID = entry.get('qId')
|
||||
relPaths = entry.get('relPaths')
|
||||
for relPath in relPaths:
|
||||
relations = relPath[0]
|
||||
for rel in relations:
|
||||
all_relations.add(rel)
|
||||
|
||||
print("num of relation types: {}".format(len(all_relations)))
|
||||
print(all_relations)
|
||||
print( ("/people/marriage/spouse") in all_relations )
|
||||
@@ -1,40 +0,0 @@
|
||||
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")
|
||||
@@ -1,40 +0,0 @@
|
||||
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()
|
||||
@@ -1,184 +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 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("<PAD>")
|
||||
word_vocab.add_unk_token("<UNK>")
|
||||
|
||||
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]))
|
||||
|
||||
@@ -1,71 +0,0 @@
|
||||
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
|
||||
|
||||
@@ -1,68 +0,0 @@
|
||||
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("<UNK>")
|
||||
|
||||
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("<UNK>")
|
||||
|
||||
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
|
||||
|
||||
Reference in New Issue
Block a user