'Produce a prediction from our model. Update the model using the
targets if available.'

def predict(self, xs, ys=None, cands=None):

    batchsize = len(xs)
    text_cand_inds = None
    xes = self.lt(xs).t()
    h0 = self.init_zeros(batchsize)
    (_output, hn) = self.encoder(xes, h0)
    x = Variable(self.END_TENSOR)
    xe = self.lt(x).unsqueeze(1)
    xes = xe.expand(xe.size(0), batchsize, xe.size(2))
    output_lines = [[] for _ in range(batchsize)]
    if (ys is not None):
        self.zero_grad()
        loss = 0
        self.longest_label = max(self.longest_label, ys.size(1))
        for i in range(ys.size(1)):
            (output, hn) = self.decoder(xes, hn)
            (preds, scores) = self.hidden_to_idx(output, dropout=True)
            y = ys.select(1, i)
            loss += self.criterion(scores, y)
            xes = self.lt(y).unsqueeze(0)
            for b in range(batchsize):
                token = self.v2t([preds.data[b][0]])
                output_lines[b].append(token)
        loss.backward()
        self.update_params()
        if (random.random() < 0.1):
            print ('prediction:', '    '.join(output_lines[0]), '\nlabel:', self.dict.vec2txt(ys.data[0]))
    else:
        done = [False for _ in range(batchsize)]
        total_done = 0
        max_len = 0
        if cands:
            cview = cands.view((-1), cands.size(2))
            cands_xes = xe.expand(xe.size(0), cview.size(0), xe.size(2))
            sz = hn.size()
            cands_hn = hn.view(sz[0], sz[1], 1, sz[2]).expand(sz[0], sz[1], cands.size(1), sz[2]).contiguous().view(sz[0], (-1), sz[2])
            cand_scores = torch.zeros(cview.size(0))
            cand_lengths = torch.LongTensor(cview.size(0)).fill_(0)
            if self.use_cuda:
                cand_scores = cand_scores.cuda(async=True)
                cand_lengths = cand_lengths.cuda(async=True)
            cand_scores = Variable(cand_scores)
            cand_lengths = Variable(cand_lengths)
            for i in range(cview.size(1)):
                (output, cands_hn) = self.decoder(cands_xes, cands_hn)
                (preds, scores) = self.hidden_to_idx(output, dropout=False)
                cs = cview.select(1, i)
                non_nulls = cs.ne(self.NULL_IDX)
                cand_lengths += non_nulls.long()
                score_per_cand = torch.gather(scores, 1, cs.unsqueeze(1))
                cand_scores += (score_per_cand.squeeze() * non_nulls.float())
                cands_xes = self.lt(cs).unsqueeze(0)
            cand_scores -= cand_lengths.eq(0).float()
            cand_scores /= cand_lengths.float()
            cand_scores = cand_scores.view(cands.size(0), cands.size(1))
            (srtd_scores, text_cand_inds) = cand_scores.sort(1, True)
            text_cand_inds = text_cand_inds.data
        while ((total_done < batchsize) and (max_len < self.longest_label)):
            (output, hn) = self.decoder(xes, hn)
            (preds, scores) = self.hidden_to_idx(output, dropout=False)
            xes = self.lt(preds.t())
            max_len += 1
            for b in range(batchsize):
                if (not done[b]):
                    token = self.v2t(preds.data[b])
                    if (token == self.END):
                        done[b] = True
                        total_done += 1
                    else:
                        output_lines[b].append(token)
        if (random.random() < 0.1):
            print ('prediction:', '    '.join(output_lines[0]))
    return (output_lines, text_cand_inds)
