페이지

2022년 7월 22일 금요일

2.3.3 텐서 점곱

 텐서 곱셈(tensor product)이라고도 부르는 (원소별 곱셈과 혼동하지 마세요) 점곱 연산(dot operation)은 가장 널리 사용되고 유용한 텐서 연산입니다. 원소별 연산과 반대로 입련 텐서의 원소들을 결합시킵니다.

넘파이, 케라스, 씨아노, 텐서플로에서 원소별 곱셈은 * 연산자를 사용합니다. 텐서플로에서는 dot 연산자가 다르지만 넘파이와 케라스는 점곱 연산에 보편적인 dot연산자를 사용합니다.

    import numpy as np

    z = np.dot(x, y)

    z = x . y

점곱 연산은 수학에서 어떤일을 할까요? 2개의 벡터 x 와 y의 점곱은 다음과 같이 계산을 합니다.

def naive_vector_dot(x, y):

   assert len(x.shape) == 1........... x는 넘파이 벡터입니다.

   assert len(y.shape) == 1........... y는 넘파이 벡터입니다.

    z = 0.

    for i in range(x.shape[0]):

        z += x[i] * y[i]

    return z

여기서 볼 수 있듯이 두 벡터의 점곱은 스칼라가 되므로 원소 개수가 같은 벡터끼리 점곱이 가능합니다.

행렬 x와 벡터 y사이에서도 점곱이 가능합니다. y와  x의 행 사이에서 점곱이 일어나므로 벡터가 반환됩니다. 다음과 같이 구현할 수 있습니다.

    import numpy as np

    def naive_matrix_vector_dot(x, y)

        assert len(x.shape) == 2 .......... x는 넘파이 행렬입니다.

        assert len(y.shape) == 1............ y는 넘파이 벡터입니다.

        assert x.shape[1] == y.shape[0] ......... x의 두 번째 차원이  y의 첫번째 차원과 같아야 합니다.

        z = np.zeros(x.shape[0]) .....이 연산은 x의 행과 같은 크기의  0이 채워진 벡터를 만듭니다.

        for i in range(x.shape[0]):

            for j in range(x.shape[1]):

                z[i] += x[i,j] * y[j]

        return z

행렬-벡터 점곱과 벡터-벡터 점곱 사이의 관계를 부각하기 위해 앞에ㅐ서 만든 함수르 재사용하겠습니다.

    def naive_matrix_vector_dot(x,y):

        z = np.zeros(x.shape[0])

        for i in range(x.shape[0])

            z[i] = naive_vector_dot(x[i, :],  y)

        return z

두 텐서 중 하나라도 ndim이 1보다 크면 dot 연산에 교환 법칙이 성립되지 않습니다. 다시 말하면 dot(x, y)와 dot(y, x)가 같지 않습니다.

물론 점곱은 임의의 축 개수를 가진 텐서에 일반화됩니다. 가장 일반적인 용도는 두 행렬 간의 점곱일 것입니다. x.shape[1] == y.shape[0]일 때 두 행렬 x와 y의 점곱(dot(x, y))이 성립됩니다. x의 행과 y의 열 사이 백터 점곱으로 인해(x.shape[0], y.shape[1]) 크기의 행렬이 됩니다. 다음은 단순한 구현 예 입니다.

def naive_matrix_dot(x, y):

    assert len(x.shape) == 2 .......... x는 넘파이 행렬입니다.

    assert len(y.shape) == 2 .......... y는 넘파이 행렬입니다.

    assert x.shape[1] == y.shape[0]   ........ x 의 두 번째 차원이 y의 첫번째 차원과 같아야 합니다!

    z = np.zeros((x.shape[0], y.shape[1])) ........ 이 연산은 0이 채워진 특정 크기의 벡터를 만듭니다.

    for i in range (x.shape[0]):   ...... x의 행을 반복합니다.

        for j in range(y.shape[1]): ..... y의 열을 반복합니다.

             row_x = x[i, :]

            column_y = y[:, j]

            z[i, j] = naive_vector_dot(row_x, column_y)

    return z

그림 2-5 와 같이 입력과 출력을 배치해 보면 어떤 크기의 점곱이 가능하지 이해하는데 도움이 됩니다.

x, y, z는 직사각형 모양으로 그려져 있습니다(원소들이 채워진 박스라고 생각하면 됩니다). x의 행 벡터와 y의 열 벡터가 같은 크기여야 하므로 자동으로 x의 너비는 y의 놉이와 동일해야 합니다. 새로운 머신 러닝 알고리즘을 개발할 때 이런 그림을 자주 그리게 될 것 입니다.

더 일반적으로 앞서 설명한 2D의 경우 처럼 크기를 맞추는 동일한 규칙을 따르면 다음과 같이 고차원 텐서 간의 점곱을 할 수 있습니다.

(a, b, c, d) . (d,) -> (a, b, c)

(a, b, c, d) . (d, e) -> (a , b, c, e)




댓글 없음: