[Tensorflow] Matrix Broadcasting - 행렬의 브로드캐스팅
[텐서플로 첫걸음]이란 책을 가지고 텐서플로(Tensorflow)를 공부하면서 3장 KMeans에서 이해가 되지 않는 부분이 생겼다. 바로 Broadcasting이라는 개념인데, 한참동안 이해가 되지 않다가, 페이스북 텐서플로우 코리아 그룹에 글을 올렸다. 거기서 댓글을 달아주신 분의 도움으로 이것저것 테스트 해보니 이해가 되었다.
일단 브로드캐스팅(Broadcasting)의 개념을 살펴본 뒤 테스트 한 부분을 포스팅하도록 하겠다.
1. Broadcasting이란?
Broadcast의 사전적인 의미는 '퍼뜨리다'라는 뜻이 있는데, 이와 마찬가지로 두 행렬 A, B 중 크기가 작은 행렬을 크기가 큰 행렬과 모양(shape)이 맞게끔 늘려주는 것을 의미한다. 예를 들어, 아래의 행렬처럼 (3, 3)행렬에 (1, 3)행렬을 더하려고 할 때, (1, 3)행렬을 (3, 3)처럼 확장시켜 주는 것이 바로 브로드캐스팅(Broadcasting)이다. 따라서, 브로드캐스팅(Broadcasting)은 행렬의 덧셈 혹은 뺄셈에서 크기가 작은 행렬을 확장시켜 계산이 가능한 형태로 맞춰주는 것이다. 하지만, 모양(shape)을 확장하는 것은 가능하지만 축소하는 것은 데이터 손실 때문에 불가능하다.
2. Broadcasting의 예제
위의 행렬을 실제 코드로 작성해 보았다. 아래와 같이 자동으로 broadcasting되어 행렬y를 (3, 3)으로 만들어 행렬의 뺄셈을 수행했다.
# 예제 1 import tensorflow as tf x = tf.constant([[1., 2., 3.], [4., 5., 6.], [7., 8., 9.]]) y = tf.constant([[1., 1., 1.]]) print(x.get_shape()) print(y.get_shape()) #결과 (3, 3) (1, 3) subXY = tf.sub(x,y) sess = tf.Session() result = sess.run(subXY) print(result) #결과 [[ 0. 1. 2.] [ 3. 4. 5.] [ 6. 7. 8.]]
또 다른 예를 보도록 하자. 이번에는 행렬 y에 행을 추가하여 (2, 3)으로 만든다음 뺄셈을 해주면 어떻게 될까?
아래에서 볼 수 있듯이, 차원은 동일해야한다고 에러가 난다.
# 예제 2 import tensorflow as tf x = tf.constant([[1., 2., 3.], [4., 5., 6.], [7., 8., 9.]]) y = tf.constant([[1., 1., 1.], [2., 2., 2.]]) subXY = tf.sub(x,y) sess = tf.Session() result = sess.run(subXY) print(result) # 결과 ValueError: Dimensions must be equal, but are 3 and 2 for 'Sub' (op: 'Sub') with input shapes: [3,3], [2,3].
이를 해결하려면 어떻게 해야할까?
3. Tensorflow의 expand_dims( ) 함수
위의 [예제 2] 처럼의 상황은 어떤게 있을까? 아래의 그림(출처: https://tensorflow.blog/) K-means clustering 처럼 4개의 centorid(k=4)에 대해, (2000, 2)행렬 vectors의 거리를 각각 계산한다고 할 때 broadcasting이 필요하다. 이때, 텐서플로우에서는 expand_dims( )라는 함수를 제공한다.
tf.expand_dims( ): 텐서에 차원을 추가한다.
그런데 이해가 안가는 부분이 있었다. 왜 차원을 추가할 때, vectors에는 첫번째 차원(D0)을 추가하고, centroids에는 두번째 차원(D1)을 추가를 하는것일까? 이 궁금증 때문에 이것저것 테스트를 해본 결과 이유를 알아냈다. 예제를 통해 알아보도록 하자.
4. expand_dims( ) 테스트
아래의 [예제1]은 행렬 X 에는 첫번째 차원(D0)을 추가하고, 행렬 Y 에는 두번째 차원(D1)를 추가한 것이고, [예제 2] 행렬 X 에는 두번째 차원(D1)을 추가하고, 행렬 Y 에는 첫번째 차원(D0)를 추가한 것이다.
'''예제 1''' import tensorflow as tf X = tf.constant([[1., 2., 3.], [4., 5., 6.], [7., 8., 9.]]) Y = tf.constant([[1., 1., 1.], [2., 2., 2.]]) print(X.get_shape()) print(Y.get_shape()) #결과 (3, 3) (2, 3) expanded_X = tf.expand_dims(X, 0) expanded_Y = tf.expand_dims(Y, 1) print(expanded_X.get_shape()) print(expanded_Y.get_shape()) #결과 (1, 3, 3) (2, 1, 3) subXY = tf.sub(expanded_X, expanded_Y) sess = tf.Session() result = sess.run(subXY) print(result, "\n shape: \n", subXY.get_shape()) #결과 [[[ 0. 1. 2.] [ 3. 4. 5.] [ 6. 7. 8.]] [[-1. 0. 1.] [ 2. 3. 4.] [ 5. 6. 7.]]] shape: (2, 3, 3) #================================ '''예제 2''' import tensorflow as tf X = tf.constant([[1., 2., 3.], [4., 5., 6.], [7., 8., 9.]]) Y = tf.constant([[1., 1., 1.], [2., 2., 2.]]) print(X.get_shape()) print(Y.get_shape()) #결과 (3, 3) (2, 3) expanded_X = tf.expand_dims(X, 1) expanded_Y = tf.expand_dims(Y, 0) print(expanded_X.get_shape()) print(expanded_Y.get_shape()) #결과 (3, 1, 3) (1, 2, 3) subXY = tf.sub(expanded_X, expanded_Y) sess = tf.Session() result = sess.run(subXY) print(result, "\n shape: \n", subXY.get_shape()) #결과 [[[ 0. 1. 2.] [-1. 0. 1.]] [[ 3. 4. 5.] [ 2. 3. 4.]] [[ 6. 7. 8.] [ 5. 6. 7.]]] shape: (3, 2, 3)
[예제1]과 [예제2]의 차이는 무엇일까?
바로 기준과 차원의 차이이다. [예제1]은 Y를 기준으로 잡고 X를 빼준것이고, [예제2]는 X를 기준으로 잡고 빼준것이다. 아래의 그림을 보면 더욱 쉽게 이해가 된다.
따라서, K-Means에서는 4개의 centroids를 기준으로 (2000, 2)의 행렬 vectors 의 거리를 구해야 하므로, vectors에는 첫번째 차원(D0)을 추가하고, centroids에는 두번째 차원(D1)을 추가한 것이다.