'Doing following operations:
1 For fprop: A(K, C), B(X,C,N), C(X,K,N) --> call batched_dot(A, B, C)
2 For bprop: A(K, C), B(X,K,N), C(X,C,N) --> call batched_dot(A.T, B, C)
3 For update: A(X,K,N), B(X,C,N), C(K,C) --> call batched_dot(A, B.T, C).
Arguments:
A, B (CPUTensor): input operands
C (CPUTensor): output
alpha, beta, relu: see usage in dot()'

def batched_dot(self, A, B, C, alpha=1.0, beta=0.0, relu=False):

    assert (A.dtype == B.dtype == C.dtype)
    (dima, dimb, dimc) = (0, 0, 0)
    (batch_grid, batch_loops) = (1, 1)
    if (len(A.shape) == 3):
        dima = 1
    if (len(B.shape) == 3):
        dimb = 1
    assert (dima or dimb), 'Tensor    A    or    B    must    have    3    dims    to    use    batched_dot'
    if (len(C.shape) == 3):
        dimc = 1
        batch_grid = C.shape[0]
        assert ((not dima) or (A.shape[0] == batch_grid))
        assert ((not dimb) or (B.shape[0] == batch_grid))
    if dima:
        batch_loops = A.shape[0]
        assert ((not dimb) or (B.shape[0] == batch_loops))
    elif dimb:
        batch_loops = B.shape[0]
        assert ((not dima) or (A.shape[0] == batch_loops))
    assert (A.shape[(0 + dima)] == C.shape[(0 + dimc)])
    assert (B.shape[(1 + dimb)] == C.shape[(1 + dimc)])
    assert (A.shape[(1 + dima)] == B.shape[(0 + dimb)])
    tmp = np.zeros(C.shape)
    for i in range(batch_loops):
        if dima:
            tmp += np.dot(A._tensor[i], B._tensor[i])
        else:
            tmp[i] = np.dot(A._tensor, B._tensor[i])
    np.multiply(tmp, alpha, tmp)
    if relu:
        self.Relu(tmp, tmp)
    np.add((C._tensor * beta), tmp, C._tensor)
    return C
