행렬의 연산 및 조작


대부분의 딥러닝 계산 작업은 곱셈, 덧셈, 뺄셈, 전치 등과 같은 기본 행렬 연산을 통해 수행됩니다. 따라서 기본 행렬 연산을 숙지하고 있어야 합니다. m행 및 n열을 가진 행렬 A는 n개의 열 벡터가 m차원으로 나란히 쌓여 있는 행렬로 간주될 수 있습니다. 행렬을 다음 수식 (1)과 같이 나타낼 수 있습니다.

행렬 덧셈

두 행렬 A와 B는 덧셈 연산이 가능합니다. 두 행렬의 합 C는 아래 수식 (2)와 같이 표현할 수 있습니다.

행렬 뺄셈

덧셈과 마찬가지로, 뺄셈 연산도 가능합니다. 두 행렬의 차 C는 아래 수식 (3)과 같이 표현할 수 있습니다.

행렬의 곱셈

두 개의 행렬 A와 B의 곱셈을 진행하기 위해서는 A의 열과 B의 행이 같아야 합니다.
결과 행렬의 형태는 A의 열 * B의 행으로 나타납니다. 행렬의 곱셈은 수식 (4)와 같이 표현될 수 있습니다.

행렬의 전치

행렬의 전치는 행렬의 행과 열을 뒤바꾸는 것으로, 아래 수식 (5)와 같이 표현될 수 있습니다.

 

선형대수학(Linear Algebra)


선형대수학은 벡터와 한 벡터 공간에서 다른 벡터 공간으로의 변환을 다루는 수학의 한 부분입니다. 선형 학습은 머신러닝 및 딥러닝에서 다차원의 데이터와 그 연산 방법을 다루기 때문에 거의 모든 머신러닝 및 딥러닝 알고리즘에서 중요한 역할을 수행합니다. 아래 그림 Fig 1은 3차원 벡터 공간을 표현한 것이며, 여기서 v1, v2, v3는 벡터고 P는 3차원 공간 내의 2차원 평면을 의미합니다.

 

벡터(Vector)

연속 또는 이산 숫자의 배열을 벡터라 하고, 벡터로 구성된 공간을 벡터 공간이라고 합니다. 벡터 공간 차원은 유한하거나 무한하지만, 대부분의 머신 러닝 또는 데이터 과학에서는 고정 길이 벡터를 처리합니다. 머신러닝에서는 다찬원 데이터를 처리하기 때문에 벡터가 아주 중요합니다. 예를 들어, 집의 면적, 침식의 수, 욕실의 수, 지역의 인구 밀도에 기초해 주택 가격을 예측하려 한다고 가정하면, 이러한 모든 특정을 이용해 주택 가격 예측 문제에 대한 입력 특성 벡터를 만들 수 있습니다.

 

스칼라(Scalar)

1차원 벡터를 스칼라라고 합니다. 스칼라는 크기만 있고 방향성이 존재하지 않습니다. 스칼라는 움직일 수 있는 방향이 하나뿐이므로 그 방향은 중요하지 않으며, 크기만 고려하면 됩니다. 그 예로 키 혹은 무게 등이 있습니다.

 

행렬(Matrix)

행과 열로 배열된 숫자의 2차원 배열입니다. 행렬의 크기는 행 길이와 열 길이에 의해 결정됩니다. 행렬 A가 m행과 n행을 가진다면, 이것은 m * n 원소를 가지는 직사각형 객체로 표현될 수 있습니다. 일반적으로 An*m으로 표현합니다. 동일한 벡터 공간에 속하는 몇 개의 벡터는 하나의 행렬을 형성합니다. 예를 들어, 이미지는 행렬 형태로 저장됩니다. 이미지의 크기는 이미지 행렬 크기에 따라 결정되고 각 행렬의 셀은 픽셀 강도를 나타내는 0 ~ 255 사이의 값을 가집니다.

 

텐서(Tensor)

텐서는 숫자의 다차원 배열입니다. 사실, 벡터와 행렬은 1D 및 2D 텐서로 취급될 수 있습니다. 딥러닝에서 텐서는 주로 데이터 저장 및 처리에 사용됩니다. 예를 들어 RGB의 이미지는 3차원 텐서에 저장되며, 여기서 한 차원은 가로축, 다른 차원은 세로축, 그리고 마지막 차원은 세 가지 색 채널, 즉 Red, Green, Blue를 저장합니다.

또 다른 예로,
컨볼루션 신경망에서 미니 배치를 통해 이미지를 공급하는데 사용되는 4차원 텐서입니다. 첫 번째 차원에는 일괄 처리의 이미지 번호가 있고, 두 번째 차원에는 색상 채널이 있으며, 세 번째 및 네 번째 차원에는 가로 및 세로 방향의 픽셀 위치가 있습니다.

Python의 언더스코어(Underscore, _)

타 언어에서 언더스코어()는 단지 스네이크 표기법의 변수나 함수명을 위해서만 사용되어지는 반면 (물론 그렇지 않은 언어도 있다), 파이썬에서는 이 문자의 의미가 다양하다. 아마 파이썬 프로그래머라면 for _ in range(10)나 init(self)등의 문법들이 굉장히 익숙할 것이다. 이번 포스트에서는 이 언더스코어()가 언제 어떤 의미로 쓰이는지에 대해 다루어보려고 한다. 크게 기술적인 내용은 아니지만 파이썬 프로그래머로서 알아두면 좋을 것 같아 정리해보려고 한다.

파이썬에서 언더스코어(_)는 다음과 같은 상황에서 사용되는데 크게 5가지의 경우가 있다.

  1. 인터프리터(Interpreter)에서 마지막 값을 저장할 때
  2. 값을 무시하고 싶을 때 (흔히 “I don’t care”라고 부른다.)
  3. 변수나 함수명에 특별한 의미 또는 기능을 부여하고자 할 때
  4. 국제화(Internationalization, i18n)/지역화(Localization, l10n) 함수로써 사용할 때
  5. 숫자 리터럴값의 자릿수 구분을 위한 구분자로써 사용할 때

인터프리터에서 사용되는 경우

print(10) # 10
print(_) # 10
print(_ * 3) # 30
print(_ * 20) # 600

값을 무시하고 싶은 경우

_는 또한 어떤 특정 값을 무시하기 위한 용도로 사용되기도 하는데, 값이 필요하지 않거나 사용되지 않는 값을 _에 할당하기만 하면 된다.

#
x, _, y = (1, 2, 3) # x = 1, y = 3
#
x, *_, y = (1, 2, 3, 4, 5) # x = 1, y = 5
#
for _ in range(5): # test test test test test
print('test')
#
for _, val in list_of_tuple:
do_something()

특별한 의미의 네이밍을 하는 경우

파이썬에서 _가 가장 많이 사용되는 곳은 아마 네이밍일 것이다. 파이썬 컨벤션 가이드라인인 PEP8에는 다음과 같은 4가지의 언더스코어를 활용한 네이밍 컨벤션을 소개하고 있다.

  • _single_leading_underscore : 주로 한 모듈 내부에서만 사용하는 private 클래스/함수/변수/메서드를 선언할 때 사용하는 컨벤션이다. 이 컨벤션으로 선언하게 되면 from module import *시 _로 시작하는 것들은 모두 임포트에서 무시된다. 그러나, 파이썬은 진정한 의미의 private을 지원하고 있지는 않기 때문에 private을 완전히 강제할 수는 없다. 즉, 위와 같은 임포트문에서는 무시되지만 직접 가져다 쓰거나 호출을 할 경우엔 사용이 가능하다. 그래서 “weak internal use indicator”라고 부르기도 한다.
_internal_name = 'one_module' # Private Variable
_internal_version = '1.0' # Private Variable
def __init__(self, price):
self._price = price
def _double_price(self): # private
return self._price * self._hidden_factor
def get_double_price(self):
return self._double_price()
* `single_trailing_underscore_`: . .
```python
Tkinter.Toplevel(master, class_='ClassName') # class
list_ = List.objects.get(1) # list
  • __double_leading_underscores : 이는 컨벤션이라기보단 하나의 문법적인 요소이다. 더블 언더스코어는 클래스 속성명을 맹글링하여 클래스간 속성명의 충돌을 방지하기 위한 용도로 사용된다. (맹글링이란, 컴파일러나 인터프리터가 변수/함수명을 그대로 사용하지 않고 일정한 규칙에 의해 변형시키는 것을 말한다.) 파이썬의 맹글링 규칙은 더블 언더스코어로 지정된 속성명 앞에 _ClassName을 결합하는 방식이다. 즉, ClassName이라는 클래스에서 method라는 메서드를 선언했다면 이는 _ClassNamemethod로 맹글링 된다.
class A:
def _single_method(self):
pass
def __double_method(self): #
pass
class B(A):
def __double_method(self): #
pass
print(dir(A())) # ['_A_double_method', ..., '_single_method']
print(dir(B())) # ['_A_double_method', '_B_double_method', ..., '_single_method']
# .

더블 언더스코어로 지정된 속성명은 위와 같이 맹글링이 되기 때문에 일반적인 속성 접근인 ClassName.__method으로 접근이 불가능하다. 간혹, 이러한 특징으로 더블 언더스코어를 사용해 진짜 private처럼 보이게 하는 경우가 있는데 이는 private을 위한 것이 아니며 private으로의 사용도 권장되지 않는다. 이에 대한 자세한 내용은 Python Naming을 참고하면 좋을 것 같다.

  • __double_leading_and_trailing_underscores__ : 스페셜 변수나 메서드(매직 메서드라고도 부른다.)에 사용되는 컨벤션이며, __init__, __len__과 같은 메서드들이 있다. 이런 형태의 메서드들은 어떤 특정한 문법적 기능을 제공하거나 특정한 일을 수행한다. 가령, __file__은 현재 파이썬 파일의 위치를 나타내는 스페셜 변수이며, __eq__은 a == b라는 식이 수행될 때 실행되는 스페셜 메서드이다. 물론 사용자가 직접 만들 수도 있지만 그런 경우는 정말 거의 없으며, 일부 스페셜 메서드의 경우 직접 수정하거나 하는 일은 빈번히 있을 수 있다. __init__의 경우 클래스의 인스턴스가 생성될 때 처음으로 실행되는 메서드인데 인스턴스의 초기화 작업을 이 메서드의 내용으로 작성할 수 있다.
class A:
def __init__(self, a): # __init__ .
self.a = a
def __custom__(self): # . .
pass

국제화(i18n) / 지역화(l10n) 함수로 사용되는 경우

이는 어떤 특정한 문법적 규칙이라기보단 말 그대로 컨벤션이다. 즉, _가 국제화/지역화 함수라는 의미는 아니며, i18n/l10n 함수를 _로 바인딩하는 C 컨벤션에서 유래된 컨벤션이다. i18n/l10n 라이브러리인 gettext라는 파이썬 내장 라이브러리 API 문서에서도 이 컨벤션을 사용하고 있으며, i18n과 l10n을 지원하는 파이썬 웹 프레임워크인 Django의 공식 문서에서도 이 컨벤션을 소개하면서 사용하고 있다.

# gettext : https://docs.python.org/3/library/gettext.html
import gettext
gettext.bindtextdomain('myapplication', '/path/to/my/language/directory')
gettext.textdomain('myapplication')
_ = gettext.gettext
# ...
print(_('This is a translatable string.'))
from django.utils.translation import ugettext as _
from django.http import HttpResponse
def translate_view(request):
translated = _('This string will be translated.')
return HttpResponse(translated)

숫자 리터럴값의 자릿수 구분을 위한 구분자로 사용되는 경우

Python 3.6에 추가된 문법으로 언더스코어로 숫자값을 좀 더 읽기 쉽도록 자릿수를 구분할 수 있게 되었다.

dec_base = 1_000_000
bin_base = 0b_1111_0000
hex_base = 0x_1234_abcd
print(dec_base) # 1000000
print(bin_base) # 240
print(hex_base) # 305441741


1. Tensorflow에서 Tensor를 생성하는 방법

텐서플로에서 사용하는 주 자료구조가 텐서(Tensor)입니다. 텐서는 변수로 선언할 수도 있고, 데이터 투입 대상인 Placeholder로 선언할 수도 있습니다. 텐서를 생성하는 방법은 여러가지가 있습니다. 텐서를 생성하기 위한 여러가지 내장 함수를 살펴보도록 하겠습니다.


1. 고정 텐서

텐서플로에서 고정된 값으로 텐서를 생성하기 위한 방법은 아래와 같습니다.

tf.zeros()

0 값으로 채워진 텐서를 생성하기 위한 함수입니다.

1zeros_tensor = tf.zeros([row_dim, col_dim])

tf.ones()

1값으로 채워진 텐서를 생성하기 위한 함수입니다.

1ones_tensor = tf.ones([row_dim, col_dim])

tf.fill()

동일한 상수 값으로 채워진 텐서를 생성하기 위한 함수입니다.

1filled_tensor = tf.fill([row_dim, col_dim])

tf.constant()

기존 상수를 이용해 텐서를 생성하기 위한 함수입니다.

1constant_tensor = tf.constant([1, 2, 3])

2. 비슷한 값을 가지는 텐서

기존 텐서의 형태를 바탕으로 텐서 변수를 초기화하는 것도 가능합니다.

tf.zeros_like / tf.ones_like

1zeros_similar = tf.zeros_like(constant_tensor)
2ones_similar = tf.ones_like(constant_tensor)

3. 시퀀스 텐서

구간을 지정하는 방식으로 텐선을 선언할 수 있습니다.

tf.range()

정수 시퀀스 텐서를 생성합니다.

1# range(start, limit=None, delta=1, name='range')
2integer_sequence_tensor = tf.range(start=6, limit=15, delta=3)
  • start : 시퀀스의 시작값이며, 기본값은 0
  • limit : 시퀀스의 상한값이며, 시퀀스에 포함되지 않음
  • delta : start를 증가시키는 수
  • name : 연산의 명칭(선택)

tf.linspace()

일정 구간 사이의 값을 생성합니다.

1# linspace(start, stop, num, name=None)
2linear_tensor = tf.linspace(start=0, stop=1, num=3)
  • start : 시퀀스의 시작값
  • stop : 시퀀스의 상한값
  • num : 생성할 값들의 개수

4. 랜덤 텐서

난수 기반의 텐서를 생성하는 방법은 아래와 같습니다.

tf.random_uniform()

균등 분포를 따르는 난수를 생성합니다.

1# random_uniform(shape, minval=0, maxval=None, dtype=tf.float32, seed=None, name=None)
2randunif_tensor = tf.random_uniform([row_dim, col_dim], minval=0, maxval=1)
  • shape : 정수값의 D-1 텐서 또는 파이썬 배열 입력
  • minval : 난수값 생성 구간의 하한
  • maxval : 난수값 생성 구간의 상한
  • dtype : 반환값의 타입 설정 (float32, float64, int32, int64)
  • seed : 난수 시드값 설정에 사용
  • name : 연산의 명칭(선택)

tf.random_normal()

정규 분포를 따르는 난수를 생성합니다.

1# random_normal(shape, mean=0.0, stddev=1.0, dtype=tf.float32, seed=None, name=None)
2randnorm_tensor = tf.random_normal([row_dim, col_dim], mean=0.0, stddev=1.0)
  • shape : 정수값의 D-1 텐서 또는 파이썬 배열 입력
  • mean : 정규분포의 평균값
  • stddev : 정규분포의 표준 편차
  • dtype : 반환값의 타입 설정 (float32, float64, int32, int64)
  • seed : 난수 시드값 설정에 사용
  • name : 연산의 명칭(선택)

tf.truncated_normal()

지정한 평균에서 표준편차 2배 이내의 값을 생성합니다. 특정 범위에 속하는 정규 분포 임의의 값을 생성하고 싶은 경우 사용합니다.

1# truncated_normal(shape, mean, stddev=1.0, dtype=tf.float32, seed=None, name=None)
2runcnorm_tensor = tf.truncated_normal([row_dim, col_dim], mean=0.0, stddev=1.0])
  • shape : 정수값의 D-1 텐서 또는 파이썬 배열 입력
  • mean : 정규분포의 평균값
  • stddev : 정규분포의 표준 편차
  • dtype : 반환값의 타입 설정 (float32, float64, int32, int64)
  • seed : 난수 시드값 설정에 사용
  • name : 연산의 명칭(선택)

tf.random_shuffle()

배열 항목을 뒤섞을 때 사용합니다. random_shuffle() 함수는 값의 첫번째 차원을 기준으로 랜덤하게 섞어줍니다.

1# random_shuffle(value, seed=None, name=None)
2shuffled_output = tf.random_shuffle(input_tensor)
  • value : 입력 텐서

tf.random_crop(value, size, seed=None, name=None)

텐서를 주어진 사이즈만큼 랜덤하게 잘라내고 싶을 때 사용합니다.

1# random_crop(value, size, seed=None, name=None)
2cropped_output = tf.random_crop(input_tensor, crop_size)
  • value : 입력 텐서
  • size : 잘라내고 싶은 텐서의 사이즈


Tensorflow와 Algorithm Flow

구글 텐서플로를 사용하면 머신 러닝 문제를 아주 효과적으로 해결할 수 있습니다. 머신 러닝은 음성 인식, 자연어 처리, 영상 처리 등의 다양한 분야에서 사용되고 있습니다. 텐서플로가 동작하는 방식이 처음에는 낮설게 느껴질 수 있지만, 이러한 복잡한 계산 방식 덕분에 복잡한 알고리즘을 쉽게 개발할 수 있습니다.

텐서플로는 Linux, MacOS, Window를 지원합니다. 텐서플로의 핵심 코드들은 C++로 작성되어 있지만, Python으로 구현된 라이브러리만으로도 충분히 머신 러닝을 진행할 수 있습니다.


텐서플로 참고 사이트

  1. https://www.tensorflow.org/api_docs/python/
  2. https://www.tensorflow.org/tutorials

텐서플로 알고리즘의 흐름

  1. 데이터셋 생성
  2. 데이터 전처리
  3. 데이터셋 분할
  4. 머신 러닝 알고리즘 파라미터 설정
  5. 변수 및 플레이스홀더 초기화
  6. 모델 구조 정의
  7. 비용 함수(Loss Function) 선언
  8. 모델 초기화 및 학습
  9. 모델 평가
  10. 파라미터 재정의 (선택)
  11. 적용 및 결과 예측

데이터셋 생성

머신 러닝 뿐만 아니라 데이터 분석을 위해서는 데이터셋이 필요합니다. 데이터셋은 직접 생성하거나 외부에서 가져오는 방식으로 얻을 수 있습니다.

데이터 전처리

데이터 전처리는 머신 러닝을 진행하는 데 있어 아주 중요한 역할을 수행합니다. 대부분의 데이터셋은 머신 러닝을 진행하는데 적합한 형태로 제공되지 않기 때문에 적절한 형태로 변환할 필요성이 있습니다. 텐서플로는 데이터 전처리를 위한 다양한 내장 함수를 제공합니다.

데이터셋 분할

머신 러닝 알고리즘 테스트를 위해 일반적으로 Test Set과 Train Set으로 데이터를 구분합니다. 필요에 따라서는 알고리즘 매개변수의 조절이 필요하기 떄문에 매개변수의 최적 값을 정하기 위해 검증셋까지 구분하는 경우도 있습니다.

머신 러닝 알고리즘 파라미터 설정

일반적인 알고리즘에는 학습을 위해 다양한 파라미터가 존재하고, 파라미터에 따라 예측 결과가 변화합니다. 따라서, 학습을 진행하기 이전에 파라미터를 적절히 설정하는 것이 중요합니다.

변수 및 플레이스홀더 초기화

텐서플로는 수정할 수 있는 값과 수정할 수 없는 값이 있습니다. 텐서플로는 비용 함수를 최소화하는 최적화 과정에서 변수 값, 가중치(Weight)와 편향(Bias) 값을 조정합니다. 데이터를 플레이스 홀더 자리에 투입해 최적화를 진행합니다. 변수 및 플레이스 홀더의 크기와 타입을 모두 초기화 시점에 지정하여 처리 대상에 대한 정보를 텐서플로에 알려주어야 합니다. 또한, 텐서플로가 처리할 데이터의 타입도 알려주어야 합니다.
(*플레이스홀더(Placeholder)에 대한 설명은 추후에 진행하도록 하겠습니다.)

1a_var = tf.constant(42)
2x_input = tf.placeholder(tf.float32, [None, input_size])
3y_input = tf.placeholder(tf.float32, [None, num_classes])

모델 구조 정의

학습을 위한 모델을 정의해야 합니다. 모델 정의는 계산 그래프 생성을 통해 이루어 집니다. 텐서플로는 모델의 결과를 얻기 위해 특정 연산과 값을 변수 및 플레이스홀더에 지정해야 합니다.
(*계산 그래프에 대한 설명 또한 추후에 진행하도록 하겠습니다)

비용 함수 선언

모델을 정의한 이후 모델 평가를 진행해야 합니다. 이를 위해 비용 함수를 정의합니다. 비용 함수는 해당 모델이 예측한 값이 실제 값과 얼마나 차이가 있는지를 알려주는 중요한 요소입니다.

모델 초기화 및 학습

그래프 인스턴스를 생성하고 플레이스홀더에 데이터를 투입해 학습을 진행합니다.

모델 평가

구축한 모델이 얼마나 잘 예측하는지를 알아보기 위해 모델 평가를 진행합니다. Train set을 통해 학습한 모델이 Test set에서는 어떻게 예측하는지 파악합니다. 결과로서 모델의 과적합, 과소적합 등의 여부를 판단할 수 있습니다.

파라미터 재정의

필요에 따라서는 구축한 모델의 성능이 만족스럽지 않을 때 알고리즘의 매개변수를 변경하고 싶을 수 있습니다. 알고리즘의 매개변수를 조정해가며 모델의 성능을 향상시킵니다.

적용 및 결과 예측

새로운 데이터를 생성해보고 구축한 모델에 적용해보며 얼마나 잘 예측하는지 확인합니다.




파이썬 2와 파이썬 3 중 무엇을 써야할까?

파이썬의 주요 버전은 크게 버전 2와 버전 3 두 가지로 나뉩니다. 버전 3이 최신이지만 버전 2가 여전히 과학 분야에서 가장 흔하게 사용되고, 여러 운영체제에서 기본적으로 채택하고 있는 버전입니다. 버전 3이 릴리즈됐을 때 대부분의 과학용 패키지는 이를 지원하지 않았기 때문에 버전 2를 고수해왔지만, 이후 파이썬 3과 호환이 불가능한 일부를 제외하고는 거의 모든 패키지들이 업데이트 되었습니다.

최근에는 파이썬 3에 대한 인기가 늘어나고 있지만, 여전이 데이터 사이언티스트들과 데이터 분석가 사이에서 널리 사용되는 버전은 파이썬 2입니다. 하지만, 파이썬 3을 사용한다고 해서 문제될 것은 없습니다. 파이썬 3 사용자라면 파이썬 2에서 구현된 코드를 파이썬 3로 변환하는 것이 어렵지 않기 때문입니다.


+ Recent posts