<gh_stars>0

from __future__ import print_function
from __future__ import absolute_import
import torch
import torch.nn as nn
import torch.nn.functional as F
from .wordsequence import WordSequence
from .crf import CRF

import numpy as np
# TODO - cleanup
'''experiment - set to False for original,
   True to get rid of extra cells for single-layer situation '''
DONT_ADD_2 = True

class SeqLabel(nn.Module):
    def __init__(self, data):
        super(SeqLabel, self).__init__()
        self.use_crf = data.use_crf
        print("build sequence labeling network...")

        '''Additions'''
        self.data = data
        '''End Additions'''

        print("use_char: ", data.use_char)
        if data.use_char:
            print("char feature extractor: ", data.char_feature_extractor)
        print("word feature extractor: ", data.word_feature_extractor)
        print("use crf: ", self.use_crf)

        self.gpu = data.HP_gpu
        self.average_batch = data.average_batch_loss
        ## add two more label for downlayer lstm, use original label size for CRF
        label_size = data.label_alphabet_size
        # TODO: remove flag?
        if not DONT_ADD_2:
            data.label_alphabet_size += 2
        self.word_hidden = WordSequence(data)
        if self.use_crf:
            self.crf = CRF(label_size, self.gpu)


    def calculate_loss(self, word_inputs, feature_inputs, word_seq_lengths, char_inputs, char_seq_lengths, char_seq_recover, batch_label, mask):
        outs = self.word_hidden(word_inputs,feature_inputs, word_seq_lengths, char_inputs, char_seq_lengths, char_seq_recover)
        batch_size = word_inputs.size(0)
        seq_len = word_inputs.size(1)
        if self.use_crf:
            total_loss = self.crf.neg_log_likelihood_loss(outs, mask, batch_label)
            scores, tag_seq = self.crf._viterbi_decode(outs, mask)
        else:
            loss_function = nn.NLLLoss(ignore_index=0, size_average=False)
            outs = outs.view(batch_size * seq_len, -1)
            score = F.log_softmax(outs, 1)
            total_loss = loss_function(score, batch_label.view(batch_size * seq_len))
            _, tag_seq  = torch.max(score, 1)
            tag_seq = tag_seq.view(batch_size, seq_len)
        if self.average_batch:
            total_loss = total_loss / batch_size
        return total_loss, tag_seq

    def update_tag_contribution(self, contribution_matrix, tag):
        if tag == 0:
            if np.max(contribution_matrix[0]) > 0:
                print("contribution matrix should have zeros for zero tag: {}".format(
                    contribution_matrix[0]
                ))

        if tag not in self.data.tag_contributions:
            if tag != 0:
                self.data.tag_contributions[tag] = contribution_matrix
            else: # 0
                self.data.tag_contributions[0] = np.zeros((10, 50))
            self.data.tag_counts[tag] = 1
        else:
            if tag != 0:
                  self.data.tag_contributions[tag] += contribution_matrix
            self.data.tag_counts[tag] += 1
        return

    def forward(self, word_inputs, feature_inputs, word_seq_lengths, char_inputs, char_seq_lengths, char_seq_recover, mask):
        outs = self.word_hidden(word_inputs,feature_inputs, word_seq_lengths, char_inputs, char_seq_lengths, char_seq_recover)
        np_out = outs.cpu().detach().numpy()
        batch_size = word_inputs.size(0)
        seq_len = word_inputs.size(1)
        if self.use_crf:
            scores, tag_seq = self.crf._viterbi_decode(outs, mask)
        else:
            outs = outs.view(batch_size * seq_len, -1)
            _, tag_seq  = torch.max(outs, 1)

            tag_seq = tag_seq.view(batch_size, seq_len)

            ## filter padded position with zero
            tag_seq = mask.long() * tag_seq
            np_tag_seq_view_masked = tag_seq.cpu().detach().numpy()
            # TODO: for each contribution in self.data.batch_contributions, file it in category by tag
            for i in range(len(tag_seq)):
                for j in range(len(tag_seq[i])):
                    self.update_tag_contribution(
                        self.data.batch_contributions[i][j],
                        np.asscalar(tag_seq[i][j].cpu().detach().numpy()))
        return tag_seq


    # def get_lstm_features(self, word_inputs, word_seq_lengths, char_inputs, char_seq_lengths, char_seq_recover):
    #     return self.word_hidden(word_inputs, word_seq_lengths, char_inputs, char_seq_lengths, char_seq_recover)


    def decode_nbest(self, word_inputs, feature_inputs, word_seq_lengths, char_inputs, char_seq_lengths, char_seq_recover, mask, nbest):
        if not self.use_crf:
            print("Nbest output is currently supported only for CRF! Exit...")
            exit(0)
        outs = self.word_hidden(word_inputs,feature_inputs, word_seq_lengths, char_inputs, char_seq_lengths, char_seq_recover)
        batch_size = word_inputs.size(0)
        seq_len = word_inputs.size(1)
        scores, tag_seq = self.crf._viterbi_decode_nbest(outs, mask, nbest)
        return scores, tag_seq


from __future__ import print_function
from __future__ import absolute_import
import torch
import torch.nn as nn
import torch.nn.functional as F
from .wordsequence import WordSequence
from .crf import CRF

import numpy as np
# TODO - cleanup
'''experiment - set to False for original,
   True to get rid of extra cells for single-layer situation '''
DONT_ADD_2 = True

class SeqLabel(nn.Module):
    def __init__(self, data):
        super(SeqLabel, self).__init__()
        self.use_crf = data.use_crf
        print("build sequence labeling network...")

        '''Additions'''
        self.data = data
        '''End Additions'''

        print("use_char: ", data.use_char)
        if data.use_char:
            print("char feature extractor: ", data.char_feature_extractor)
        print("word feature extractor: ", data.word_feature_extractor)
        print("use crf: ", self.use_crf)

        self.gpu = data.HP_gpu
        self.average_batch = data.average_batch_loss
        ## add two more label for downlayer lstm, use original label size for CRF
        label_size = data.label_alphabet_size
        # TODO: remove flag?
        if not DONT_ADD_2:
            data.label_alphabet_size += 2
        self.word_hidden = WordSequence(data)
        if self.use_crf:
            self.crf = CRF(label_size, self.gpu)


    def calculate_loss(self, word_inputs, feature_inputs, word_seq_lengths, char_inputs, char_seq_lengths, char_seq_recover, batch_label, mask):
        outs = self.word_hidden(word_inputs,feature_inputs, word_seq_lengths, char_inputs, char_seq_lengths, char_seq_recover)
        batch_size = word_inputs.size(0)
        seq_len = word_inputs.size(1)
        if self.use_crf:
            total_loss = self.crf.neg_log_likelihood_loss(outs, mask, batch_label)
            scores, tag_seq = self.crf._viterbi_decode(outs, mask)
        else:
            loss_function = nn.NLLLoss(ignore_index=0, size_average=False)
            outs = outs.view(batch_size * seq_len, -1)
            score = F.log_softmax(outs, 1)
            total_loss = loss_function(score, batch_label.view(batch_size * seq_len))
            _, tag_seq  = torch.max(score, 1)
            tag_seq = tag_seq.view(batch_size, seq_len)
        if self.average_batch:
            total_loss = total_loss / batch_size
        return total_loss, tag_seq

    def update_tag_contribution(self, contribution_matrix, tag):
        if tag == 0:
            if np.max(contribution_matrix[0]) > 0:
                print("contribution matrix should have zeros for zero tag: {}".format(
                    contribution_matrix[0]
                ))

        if tag not in self.data.tag_contributions:
            if tag != 0:
                self.data.tag_contributions[tag] = contribution_matrix
            else: # 0
                self.data.tag_contributions[0] = np.zeros((10, 50))
            self.data.tag_counts[tag] = 1
        else:
            if tag != 0:
                  self.data.tag_contributions[tag] += contribution_matrix
            self.data.tag_counts[tag] += 1
        return

    def forward(self, word_inputs, feature_inputs, word_seq_lengths, char_inputs, char_seq_lengths, char_seq_recover, mask):
        outs = self.word_hidden(word_inputs,feature_inputs, word_seq_lengths, char_inputs, char_seq_lengths, char_seq_recover)
        np_out = outs.cpu().detach().numpy()
        batch_size = word_inputs.size(0)
        seq_len = word_inputs.size(1)
        if self.use_crf:
            scores, tag_seq = self.crf._viterbi_decode(outs, mask)
        else:
            outs = outs.view(batch_size * seq_len, -1)
            _, tag_seq  = torch.max(outs, 1)

            tag_seq = tag_seq.view(batch_size, seq_len)

            ## filter padded position with zero
            tag_seq = mask.long() * tag_seq
            np_tag_seq_view_masked = tag_seq.cpu().detach().numpy()
            # TODO: for each contribution in self.data.batch_contributions, file it in category by tag
            for i in range(len(tag_seq)):
                for j in range(len(tag_seq[i])):
                    self.update_tag_contribution(
                        self.data.batch_contributions[i][j],
                        np.asscalar(tag_seq[i][j].cpu().detach().numpy()))
        return tag_seq


    # def get_lstm_features(self, word_inputs, word_seq_lengths, char_inputs, char_seq_lengths, char_seq_recover):
    #     return self.word_hidden(word_inputs, word_seq_lengths, char_inputs, char_seq_lengths, char_seq_recover)


    def decode_nbest(self, word_inputs, feature_inputs, word_seq_lengths, char_inputs, char_seq_lengths, char_seq_recover, mask, nbest):
        if not self.use_crf:
            print("Nbest output is currently supported only for CRF! Exit...")
            exit(0)
        outs = self.word_hidden(word_inputs,feature_inputs, word_seq_lengths, char_inputs, char_seq_lengths, char_seq_recover)
        batch_size = word_inputs.size(0)
        seq_len = word_inputs.size(1)
        scores, tag_seq = self.crf._viterbi_decode_nbest(outs, mask, nbest)
        return scores, tag_seq
# TODO - cleanup experiment - set to False for original,    True to get rid of extra cells for single-layer situation Additions End Additions ## add two more label for downlayer lstm, use original label size for CRF # TODO: remove flag? # 0 ## filter padded position with zero # TODO: for each contribution in self.data.batch_contributions, file it in category by tag # def get_lstm_features(self, word_inputs, word_seq_lengths, char_inputs, char_seq_lengths, char_seq_recover): #     return self.word_hidden(word_inputs, word_seq_lengths, char_inputs, char_seq_lengths, char_seq_recover)