Calculate multi-perspective cosine matching between time-steps of vectors
    of the same length.

    Parameters
    ----------
    vector1 : ``torch.Tensor``
        A tensor of shape ``(batch, seq_len, hidden_size)``
    vector2 : ``torch.Tensor``
        A tensor of shape ``(batch, seq_len or 1, hidden_size)``
    weight : ``torch.Tensor``
        A tensor of shape ``(num_perspectives, hidden_size)``

    Returns
    -------
    A tuple of two tensors consisting multi-perspective matching results.
    The first one is of the shape (batch, seq_len, 1), the second one is of shape
    (batch, seq_len, num_perspectives)

def multi_perspective_match(vector1: torch.Tensor,
                            vector2: torch.Tensor,
                            weight: torch.Tensor) -> Tuple[torch.Tensor, torch.Tensor]:
    """
    Calculate multi-perspective cosine matching between time-steps of vectors
    of the same length.

    Parameters
    ----------
    vector1 : ``torch.Tensor``
        A tensor of shape ``(batch, seq_len, hidden_size)``
    vector2 : ``torch.Tensor``
        A tensor of shape ``(batch, seq_len or 1, hidden_size)``
    weight : ``torch.Tensor``
        A tensor of shape ``(num_perspectives, hidden_size)``

    Returns
    -------
    A tuple of two tensors consisting multi-perspective matching results.
    The first one is of the shape (batch, seq_len, 1), the second one is of shape
    (batch, seq_len, num_perspectives)
    """
    assert vector1.size(0) == vector2.size(0)
    assert weight.size(1) == vector1.size(2) == vector1.size(2)

    # (batch, seq_len, 1)
    similarity_single = F.cosine_similarity(vector1, vector2, 2).unsqueeze(2)

    # (1, 1, num_perspectives, hidden_size)
    weight = weight.unsqueeze(0).unsqueeze(0)

    # (batch, seq_len, num_perspectives, hidden_size)
    vector1 = weight * vector1.unsqueeze(2)
    vector2 = weight * vector2.unsqueeze(2)

    similarity_multi = F.cosine_similarity(vector1, vector2, dim=3)

    return similarity_single, similarity_multi