mirror of
https://github.com/wassname/Castor.git
synced 2026-08-20 12:00:37 +08:00
* runnable * update mask * minor update * minor update * Update README.md * fix multi GPU issue * add visualize argument * fix more comments, retab * remove util
347 lines
14 KiB
Python
347 lines
14 KiB
Python
import sys
|
|
import math
|
|
import numpy as np
|
|
from datetime import datetime
|
|
|
|
import torch
|
|
import torch.nn as nn
|
|
import torch.nn.functional as F
|
|
from torch.autograd import Variable
|
|
from torch.nn.utils.rnn import pack_padded_sequence, pad_packed_sequence
|
|
|
|
def ortho_weight(ndim):
|
|
"""
|
|
Random orthogonal weights
|
|
Used by norm_weights(below), in which case, we
|
|
are ensuring that the rows are orthogonal
|
|
(i.e W = U \Sigma V, U has the same
|
|
# of rows, V has the same # of cols)
|
|
"""
|
|
W = np.random.randn(ndim, ndim)
|
|
u, s, v = np.linalg.svd(W)
|
|
return u.astype('float32')
|
|
|
|
def norm_weight(nin, nout=None, scale=0.01, ortho=True):
|
|
"""
|
|
Random weights drawn from a Gaussian
|
|
"""
|
|
if nout is None:
|
|
nout = nin
|
|
if nout == nin and ortho:
|
|
W = ortho_weight(nin)
|
|
else:
|
|
W = scale * np.random.randn(nin, nout)
|
|
return W.astype('float32')
|
|
|
|
class LSTM_Cell(nn.Module):
|
|
|
|
def __init__(self, device, in_dim, mem_dim):
|
|
super(LSTM_Cell, self).__init__()
|
|
self.device = device
|
|
self.in_dim = in_dim
|
|
self.mem_dim = mem_dim
|
|
|
|
def new_gate():
|
|
h = nn.Linear(self.mem_dim, self.mem_dim, bias=False)
|
|
h.weight.data.copy_(torch.from_numpy(ortho_weight(self.mem_dim)))
|
|
return h
|
|
|
|
def new_W():
|
|
w = nn.Linear(self.in_dim, self.mem_dim)
|
|
w.weight.data.copy_(torch.from_numpy(ortho_weight(self.mem_dim)))
|
|
return w
|
|
|
|
self.ih = new_gate()
|
|
self.fh = new_gate()
|
|
self.oh = new_gate()
|
|
self.ch = new_gate()
|
|
|
|
self.cx = new_W()
|
|
self.ox = new_W()
|
|
self.fx = new_W()
|
|
self.ix = new_W()
|
|
|
|
|
|
def forward(self, input, h, c):
|
|
u = F.tanh(self.cx(input) + self.ch(h))
|
|
i = F.sigmoid(self.ix(input) + self.ih(h))
|
|
f = F.sigmoid(self.fx(input) + self.fh(h))
|
|
c = i*u + f*c
|
|
o = F.sigmoid(self.ox(input) + self.oh(h))
|
|
h = o * F.tanh(c)
|
|
return c, h
|
|
|
|
class LSTM(nn.Module):
|
|
def __init__(self, device, in_dim, mem_dim):
|
|
super(LSTM, self).__init__()
|
|
self.device = device
|
|
self.in_dim = in_dim
|
|
self.mem_dim = mem_dim
|
|
|
|
self.TreeCell = LSTM_Cell(device, in_dim, mem_dim)
|
|
self.output_module = None
|
|
|
|
def forward(self, x, x_mask):
|
|
"""
|
|
:param x: #step x #sample x dim_emb
|
|
:param x_mask: #step x #sample
|
|
:param x_left_mask: #step x #sample x #step
|
|
:param x_right_mask: #step x #sample x #step
|
|
:return:
|
|
"""
|
|
h = Variable(torch.zeros(x.size(1), x.size(2)))
|
|
c = Variable(torch.zeros(x.size(1), x.size(2)))
|
|
if torch.cuda.is_available():
|
|
h=h.to(self.device)
|
|
c=c.to(self.device)
|
|
all_hidden=[]
|
|
for step in range(x.size(0)):
|
|
input=x[step] # #sample x dim_emb
|
|
step_c, step_h=self.TreeCell(input, h, c)
|
|
h=x_mask[step][:,None] * step_h + (1. - x_mask[step])[:,None] * h
|
|
c = x_mask[step][:, None] * step_c + (1. - x_mask[step])[:, None] * c
|
|
all_hidden.append(torch.unsqueeze(h,0))
|
|
return torch.cat(all_hidden,0)
|
|
|
|
class ESIM(nn.Module):
|
|
"""
|
|
Implementation of the multi feed forward network model described in
|
|
the paper "A Decomposable Attention Model for Natural Language
|
|
Inference" by Parikh et al., 2016.
|
|
It applies feedforward MLPs to combinations of parts of the two sentences,
|
|
without any recurrent structure.
|
|
"""
|
|
def __init__(self, num_units, num_classes, embedding_size, dropout, device=0,
|
|
training=True, project_input=True,
|
|
use_intra_attention=False, distance_biases=10, max_sentence_length=30):
|
|
"""
|
|
Create the model based on MLP networks.
|
|
:param num_units: size of the networks
|
|
:param num_classes: number of classes in the problem
|
|
:param embedding_size: size of each word embedding
|
|
:param use_intra_attention: whether to use intra-attention model
|
|
:param training: whether to create training tensors (optimizer)
|
|
:param project_input: whether to project input embeddings to a
|
|
different dimensionality
|
|
:param distance_biases: number of different distances with biases used
|
|
in the intra-attention model
|
|
"""
|
|
super(ESIM, self).__init__()
|
|
self.arch = "ESIM"
|
|
self.num_units = num_units
|
|
self.num_classes = num_classes
|
|
self.project_input = project_input
|
|
self.embedding_size=embedding_size
|
|
self.distance_biases=distance_biases
|
|
self.max_sentence_length=max_sentence_length
|
|
self.device = device
|
|
self.dropout = nn.Dropout(p=dropout)
|
|
|
|
self.lstm_intra=LSTM(device, embedding_size, num_units)
|
|
|
|
self.linear_layer_compare = nn.Sequential(nn.Linear(4*num_units*2, num_units), nn.ReLU(), nn.Dropout(p=dropout))
|
|
# nn.Dropout(p=0.2), nn.Linear(num_units, num_units), nn.ReLU())
|
|
|
|
self.lstm_compare=LSTM(device, embedding_size, num_units)
|
|
|
|
self.linear_layer_aggregate = nn.Sequential(nn.Dropout(p=dropout), nn.Linear(4*num_units*2, num_units), nn.ReLU(),
|
|
nn.Dropout(p=dropout), nn.Linear(num_units, num_classes))
|
|
|
|
self.init_weight()
|
|
|
|
def ortho_weight(self):
|
|
"""
|
|
Random orthogonal weights
|
|
Used by norm_weights(below), in which case, we
|
|
are ensuring that the rows are orthogonal
|
|
(i.e W = U \Sigma V, U has the same
|
|
# of rows, V has the same # of cols)
|
|
"""
|
|
ndim=self.num_units
|
|
W = np.random.randn(ndim, ndim)
|
|
u, s, v = np.linalg.svd(W)
|
|
return u.astype('float32')
|
|
|
|
def initialize_lstm(self):
|
|
if torch.cuda.is_available():
|
|
init=torch.Tensor(np.concatenate([self.ortho_weight(),self.ortho_weight(),self.ortho_weight(),self.ortho_weight()], 0)).to(self.device)
|
|
else:
|
|
init = torch.Tensor(
|
|
np.concatenate([self.ortho_weight(), self.ortho_weight(), self.ortho_weight(), self.ortho_weight()], 0))
|
|
return init
|
|
|
|
def init_weight(self):
|
|
#nn.init.normal(self.linear_layer_project,mean=0,std=0.1)
|
|
#print(self.linear_layer_attend[3])
|
|
#self.linear_layer_attend[1].weight.data.normal_(0, 0.01)
|
|
#self.linear_layer_attend[1].bias.data.fill_(0)
|
|
#self.linear_layer_attend[4].weight.data.normal_(0, 0.01)
|
|
#self.linear_layer_attend[4].bias.data.fill_(0)
|
|
self.linear_layer_compare[0].weight.data.normal_(0, 0.01)
|
|
self.linear_layer_compare[0].bias.data.fill_(0)
|
|
#self.linear_layer_compare[4].weight.data.normal_(0, 0.01)
|
|
#self.linear_layer_compare[4].bias.data.fill_(0)
|
|
self.linear_layer_aggregate[1].weight.data.normal_(0, 0.01)
|
|
self.linear_layer_aggregate[1].bias.data.fill_(0)
|
|
self.linear_layer_aggregate[4].weight.data.normal_(0, 0.01)
|
|
self.linear_layer_aggregate[4].bias.data.fill_(0)
|
|
|
|
def attention_softmax3d(self,raw_attentions):
|
|
reshaped_attentions = raw_attentions.view(-1, raw_attentions.size(2))
|
|
out=nn.functional.softmax(reshaped_attentions, dim=1)
|
|
return out.view(raw_attentions.size(0),raw_attentions.size(1),raw_attentions.size(2))
|
|
|
|
def _transformation_input(self,embed_sent, x1_mask):
|
|
embed_sent = self.word_embedding(embed_sent)
|
|
embed_sent = self.dropout(embed_sent)
|
|
hidden=self.lstm_intra(embed_sent, x1_mask)
|
|
return hidden
|
|
|
|
|
|
def aggregate(self,v1, v2):
|
|
"""
|
|
Aggregate the representations induced from both sentences and their
|
|
representations
|
|
:param v1: tensor with shape (batch, time_steps, num_units)
|
|
:param v2: tensor with shape (batch, time_steps, num_units)
|
|
:return: logits over classes, shape (batch, num_classes)
|
|
"""
|
|
v1_mean = torch.mean(v1, 0)
|
|
v2_mean = torch.mean(v2, 0)
|
|
v1_max, _ = torch.max(v1, 0)
|
|
v2_max, _ = torch.max(v2, 0)
|
|
out = self.linear_layer_aggregate(torch.cat((v1_mean, v1_max, v2_mean, v2_max), 1))
|
|
|
|
#v1_sum=torch.sum(v1,1)
|
|
#v2_sum=torch.sum(v2,1)
|
|
#out=self.linear_layer_aggregate(torch.cat([v1_sum,v2_sum],1))
|
|
|
|
return out
|
|
|
|
def cosine_interaction(self, tensor1, tensor2):
|
|
"""
|
|
:param tensor1: #step1 * dim
|
|
:param tensor2: #step2 * dim
|
|
:return: #step1 * #step2
|
|
"""
|
|
simCube_0=tensor1[0].view(1,-1)
|
|
simCube_1=tensor2[0].view(1,-1)
|
|
for i in range(tensor1.size(0)):
|
|
for j in range(tensor2.size(0)):
|
|
if not(i==0 and j==0):
|
|
simCube_0=torch.cat((simCube_0, tensor1[i].view(1,-1)))
|
|
simCube_1=torch.cat((simCube_1, tensor2[j].view(1,-1)))
|
|
simCube=F.cosine_similarity(simCube_0, simCube_1)
|
|
return simCube.view(tensor1.size(0),tensor2.size(0))
|
|
|
|
def create_mask(self, sent):
|
|
masks = []
|
|
sent_lengths = [len(s.split(" ")) for s in sent]
|
|
max_len = max(sent_lengths)
|
|
|
|
for s_length in sent_lengths:
|
|
pad_mask = np.zeros(max_len)
|
|
pad_mask[:s_length] = 1
|
|
masks.append(pad_mask)
|
|
|
|
masks = np.array(masks)
|
|
return torch.from_numpy(masks).float().to(self.device)
|
|
|
|
#def forward(self, x1, x1_mask, x2, x2_mask):
|
|
def forward(self, sent1, sent2, ext_feats=None, word_to_doc_count=None, raw_sent1=None, raw_sent2=None, visualize=False):
|
|
# idx = [i for i in range(embed_sent.size(1) - 1, -1, -1)]
|
|
# if torch.cuda.is_available():
|
|
# idx = torch.cuda.LongTensor(idx)
|
|
# else:
|
|
# idx = torch.LongTensor(idx)
|
|
sent1 = sent1.permute(2, 0, 1) # from [B * D * T] to [T * B * D]
|
|
sent2 = sent2.permute(2, 0, 1)
|
|
x1_mask = self.create_mask(raw_sent1)
|
|
x2_mask = self.create_mask(raw_sent2)
|
|
x1_mask = x1_mask.permute(1, 0)
|
|
x2_mask = x2_mask.permute(1, 0)
|
|
#x1 = self.word_embedding(x1)
|
|
x1 = self.dropout(sent1)
|
|
#x2 = self.word_embedding(x2)
|
|
x2 = self.dropout(sent2)
|
|
idx_1 = [i for i in range(x1.size(0) - 1, -1, -1)]
|
|
idx_1 = Variable(torch.LongTensor(idx_1))
|
|
if torch.cuda.is_available():
|
|
idx_1 = idx_1.to(self.device)
|
|
x1_r=torch.index_select(x1,0,idx_1)
|
|
x1_mask_r=torch.index_select(x1_mask,0,idx_1)
|
|
idx_2=[i for i in range(x2.size(0) -1, -1, -1)]
|
|
idx_2 = Variable(torch.LongTensor(idx_2))
|
|
if torch.cuda.is_available():
|
|
idx_2 = Variable(torch.LongTensor(idx_2)).to(self.device)
|
|
x2_r=torch.index_select(x2,0,idx_2)
|
|
x2_mask_r=torch.index_select(x2_mask, 0, idx_2)
|
|
|
|
proj1=self.lstm_intra(x1, x1_mask)
|
|
proj1_r=self.lstm_intra(x1_r, x1_mask_r)
|
|
proj2=self.lstm_intra(x2, x2_mask)
|
|
proj2_r=self.lstm_intra(x2_r, x2_mask_r)
|
|
|
|
ctx1=torch.cat((proj1, torch.index_select(proj1_r,0,idx_1)),2)
|
|
ctx2=torch.cat((proj2, torch.index_select(proj2_r, 0, idx_2)),2)
|
|
# ctx1: #step1 x #sample x #dimctx
|
|
# ctx2: #step2 x #sample x #dimctx
|
|
ctx1 = ctx1 * x1_mask[:, :, None]
|
|
ctx2 = ctx2 * x2_mask[:, :, None]
|
|
|
|
# weight_matrix: #sample x #step1 x #step2
|
|
weight_matrix = torch.matmul(ctx1.permute(1, 0, 2), ctx2.permute(1, 2, 0))
|
|
if visualize:
|
|
return weight_matrix
|
|
weight_matrix_1 = torch.exp(weight_matrix - weight_matrix.max(1, keepdim=True)[0]).permute(1, 2, 0)
|
|
weight_matrix_2 = torch.exp(weight_matrix - weight_matrix.max(2, keepdim=True)[0]).permute(1, 2, 0)
|
|
|
|
# weight_matrix_1: #step1 x #step2 x #sample
|
|
weight_matrix_1 = weight_matrix_1 * x1_mask[:, None, :]
|
|
weight_matrix_2 = weight_matrix_2 * x2_mask[None, :, :]
|
|
|
|
alpha = weight_matrix_1 / weight_matrix_1.sum(0, keepdim=True)
|
|
beta = weight_matrix_2 / weight_matrix_2.sum(1, keepdim=True)
|
|
|
|
self.alpha=alpha
|
|
self.beta=beta
|
|
|
|
ctx2_ = (torch.unsqueeze(ctx1,1) * torch.unsqueeze(alpha,3)).sum(0)
|
|
ctx1_ = (torch.unsqueeze(ctx2, 0) * torch.unsqueeze(beta,3)).sum(1)
|
|
|
|
# cosine distance and Euclidean distance
|
|
'''
|
|
tmp_result=[]
|
|
for batch_i in range(ctx1.size(1)):
|
|
tmp_result.append(torch.unsqueeze(self.cosine_interaction(ctx1[:,batch_i,:], ctx2[:,batch_i,:]), 0))
|
|
weight_matrix=torch.cat(tmp_result)
|
|
weight_matrix_1 = torch.exp(weight_matrix - weight_matrix.max(1, keepdim=True)[0]).permute(1, 2, 0)
|
|
weight_matrix_2 = torch.exp(weight_matrix - weight_matrix.max(2, keepdim=True)[0]).permute(1, 2, 0)
|
|
# weight_matrix_1: #step1 x #step2 x #sample
|
|
weight_matrix_1 = weight_matrix_1 * x1_mask[:, None, :]
|
|
weight_matrix_2 = weight_matrix_2 * x2_mask[None, :, :]
|
|
alpha = weight_matrix_1 / weight_matrix_1.sum(0, keepdim=True)
|
|
beta = weight_matrix_2 / weight_matrix_2.sum(1, keepdim=True)
|
|
ctx2_cos_ = (torch.unsqueeze(ctx1, 1) * torch.unsqueeze(alpha, 3)).sum(0)
|
|
ctx1_cos_ = (torch.unsqueeze(ctx2, 0) * torch.unsqueeze(beta, 3)).sum(1)
|
|
'''
|
|
|
|
inp1 = torch.cat([ctx1, ctx1_, ctx1 * ctx1_, ctx1 - ctx1_], 2)
|
|
inp2 = torch.cat([ctx2, ctx2_, ctx2 * ctx2_, ctx2 - ctx2_], 2)
|
|
#inp1 = torch.cat([ctx1, ctx1_, ctx1_cos_, ctx1 * ctx1_, ctx1 * ctx1_cos_, ctx1 - ctx1_, ctx1 - ctx1_cos_], 2)
|
|
#inp2 = torch.cat([ctx2, ctx2_, ctx2_cos_, ctx2 * ctx2_, ctx2 * ctx2_cos_, ctx2 - ctx2_, ctx2 - ctx2_cos_], 2)
|
|
inp1=self.dropout(self.linear_layer_compare(inp1))
|
|
inp2=self.dropout(self.linear_layer_compare(inp2))
|
|
inp1_r=torch.index_select(inp1, 0, idx_1)
|
|
inp2_r=torch.index_select(inp2, 0, idx_2)
|
|
|
|
v1=self.lstm_compare(inp1, x1_mask)
|
|
v2=self.lstm_compare(inp2, x2_mask)
|
|
v1_r = self.lstm_compare(inp1_r, x1_mask)
|
|
v2_r = self.lstm_compare(inp2_r, x2_mask)
|
|
v1=torch.cat((v1, torch.index_select(v1_r, 0, idx_1)),2)
|
|
v2=torch.cat((v2, torch.index_select(v2_r, 0, idx_2)),2)
|
|
out = self.aggregate(v1, v2)
|
|
out = F.log_softmax(out, dim=1)
|
|
return out
|