Tensorflow 2.0 책 요약
1. 텐서플로우 기초
Tensorflow는 텐서(tensor)가 흐른다는 의미.
텐서는 값을 가진 벡터나 행렬을 나타낸다.
Tensorflow는 이 텐서의 값을 계산해서 원하는 결과를 얻어낸다.
Tensorflow를 사용한 작업은 먼저 그래프를 정의한 다음 그래프를 생성, 그 안에서 텐서를 주고받으며 계산하는 구조
1-1 난수
: 난수 (Random number), 랜덤은 신경망에서 꼭 필요한 기능이다.
신경망은 많은 숫자로 구성된 행렬이라고 볼 수 있다.
이 행렬은 어떠한 입력을 넣으면 출력을 얻게 되고, 잘 작동할 경우 원하는 출력에 점점 가까워지게 된다.
여기서 행렬을 구성하는 숫자는 처음에는 숫자들을 랜덤한 값으로 지정해 줄 수 밖에 없다고 한다.
맨 처음 신경망의 초깃값을 지정해주는 것을 '초기화(Initialization)' 라고 한다.
현재 가장 많이 쓰이는 방법은 Xavier 초기화(Xavier Initialization) 와 He 초기화(He Initialization) 인데,
이 방법들은 랜덤하지만 어느 정도 규칙성이 있는 범위 내에서 난수를 지정한다.
rand = tf.random.uniform([1], 0, 1)
print(rand)
tf.random.uniform 함수를 불러오면 균일 분포(uniform distribution) 의 난수를 얻을 수 있다.
* 균일 분포 : 최솟값과 최댓값 사이의 모든 수가 나올 확률이 동일한 분포에서 수를 뽑는다는 뜻
위의 코드에서 괄호 안에 첫번째 나오는 [1]은 결괏값의 shape을 의미한다.
여기서 Shape이란 행렬을 구성하는 행, 열 등 차원의 수를 나타내는 것이다. (1차원)
f.random.uniform 의 두 번째와 세 번째 인수는 0, 1 은 각각 최솟값과 최댓값을 의미한다.
즉, 여기서는 최솟값 0과 최댓값 1 사이에서 모든 수가 나올 확률이 동일한 분포에서 난수 하나를 뽑는 것.
여기서 tf.Tensor 바로 다음에 나오는 대괄호 안의 숫자라는 값을 얻는다.
output : tf.Tensor([0.54247725], shape=(1,), dtype=float32)
rand = tf.random.uniform([4], 0, 1)
print(rand)
output : tf.Tensor([0.89720833 0.23328388 0.9443103 0.29189503], shape=(4,), dtype=float32)
균일 분포 외에 난수를 얻는 방법 중 중요한 분포로는 정규(normal) 분포가 있다.
* 정규 분포 : 가운데가 높고 양극단으로 갈수록 낮아져서 종 모양을 그리는 분포
정규 분포의 난수를 구하기 위해서는 .uniform 대신 .normal 로 바꾸면 된다.
rand = tf.random.normal([4], 0, 1)
print(rand)
여기서 두 번째의 0은 정규 분포의 평균(mean), 세 번째의 1은 정규 분포의 표준편차(standard deviation)를 의미한다.
평균이 0이고 표준 편차가 1일 때의 정규 분포를 표준 정규 분포(standard normal distribution) 라고 한다.
Xavier 초기화나 He 초기화는 '균일 분포'와 '정규 분포' 중 하나를 택해서 신경망의 초깃값을 초기화한다.
뉴런 만들기
이전에는 뉴런을 퍼셉트론이라고도 불렀으며, 입력을 받아서 계산 후 출력을 반환하는 단순한 구조이다.
신경망은 뉴런이 여러 개가 모여 레이어(layer)를 구성한 후, 이 레이어가 다시 모여 구성된 형태이다.
뉴런의 구성
입력 / 가중치 / 활성화함수 / 출력
입력, 가중치, 출력은 보통 정수(integer) 나 실수(float)가 많이 쓰인다.
활성화함수는 뉴런의 출력값을 정하는 함수
- 가장 간단한 형태의 뉴런은 입력에 가중치를 곱한 뒤(행렬의 곱셈) 활성화 함수를 취하면 출력을 얻을 수 있다.
뉴런에서 학습할 때 변하는 것은 가중치이다. 가중치는 처음에는 초기화를 통해 랜덤한 값을 넣고,
학습 과정에서 점차 일정한 값으로 수렴한다. 학습이 잘 된다는 것은 좋은 가중치를 엇어서 원하는 출력에 점점
가까운 값을 얻는 것이라고 할 수 있다.
활성화함수는 시그모이드(sigmoid), ReLU 등을 주로 쓴다.
* 시그모이드는 S자 형태의 곡선이라는 뜻
* ReLU 는 Rectified Linear Unitd 의 약자로, 정류된(rectified) 선형함수(linear unit) 라는 뜻
딥러닝에서 선형 함수는 y = x 라는 식으로 정의할 수 있는 입력과 출력이 동일한 함수를 의미한다.
이 함수를 정류해서 음수 값을 0으로 만드는 것이 ReLU 이다.
* 정류되다 : 물이나 공기, 유체의 흐름이 고르게 되다. / 교류를 직류로 바꾸다. 등의 의미
신경망 초창기에는 시그모이드가 주로 쓰였지만 은닉층을 다수 사용하는 딥러닝의 시대가 되면서 ReLu가 더 많이
쓰이고 있다.
* 은닉층 : 입력층(Input layer, 입력 벡터가 자리 잡는 층)과 출력층(output layer, 최종 출력값이 자리 잡는 층) 사이에
위치하는 모든 층을 은닉층(hidden layer)라고 한다. 실제 인공신경망의 층 개수를 셀 때 입력층은 생략하는 것을 주의
딥러닝에서는 오류를 역전파(backpropagate)할 때 시그모이드 함수가 값을 점점 작아지게 하는 문제를 토론토 대학교의
비노드 네어와 제프리 힌든 교수가 지적하며, ReLU를 대안으로 제시한 2010년 논문에 이에 대한 자세한 내용이 있음.
시그모이드는 출력값이 0 ~ 1 사이로만 제한하게 되지만 ReLu는 양수를 그대로 반환하기 때문에 값의 왜곡이 적어진다.
* sigmoid 함수는 음수 값을 0에 가깝게 표현하기 때문에 입력 값이 최종 레이어에서 미치는 영향이 적어지는
Vanishing Gradient Problem(기울기 값이 사라져버리는 문제)이 발생한다.
x의 절대값이 커질수록 Gradient Backpropagation 시 미분 값이 소실될 가능성이 큰 단점이 있다.
* 역전파 : 출력층에서 입력층으로 가중치를 업데이트 하며 결괏값을 얻는 것
import math
def sigmoid(x):
return 1 / (1 + math.exp(-x))
# math.exp()는 상수 e의 제곱을 계산하기 위해 사용
# 뉴런의 입력과 출력을 정의
# 입력이 1일 때 기대 출력이 0이 되는 뉴런을 만든다면?
x = 1
y = 0
w = tf.random.normal([1], 0, 1)
output = sigmoid(x * w)
print(output)
output : 0.6295179565259805
여기서 뉴런이란 결국 w 값. w 값으 변화시키기 위해서 어떤 알고리즘을 사용할까?
경사하강법(Gradient Descent) 이라는 방법을 사용하면, w 에 입력과 학습률(a)과 에러를 곱한 값을 더해주는 것.
학습률은 w를 업데이트하는 정도로, 큰 값으로 설정하면 학습이 빨리되지만 과도한 학습으로 적정한 수치를 벗아날
우려가 있고, 너무 작은 값으로 설정하면 학습 속도가 너무 느려질 수 있다.
for i in range(1000):
output = sigmoid(x * w)
error = y - output
w = w + x * 0.1 * error
if i % 100 == 99:
print(i, error, output)
output :
99 -0.1114868713202125 0.1114868713202125
199 -0.054842696773689435 0.054842696773689435
299 -0.03595426310274868 0.03595426310274868
399 -0.026653063261482566 0.026653063261482566
499 -0.021144541886180267 0.021144541886180267
599 -0.017509860863774023 0.017509860863774023
699 -0.01493495860728854 0.01493495860728854
799 -0.013016709794379673 0.013016709794379673
899 -0.01153300829241085 0.01153300829241085
999 -0.010351554334539586 0.010351554334539586
출력을 구하는 부분은 위와 동일하고 error를 구하는 식은 기대출력인 y에서 실제출력인 output을 뺀다.
이렇게 구한 error를 경사하강법의 업데이트 식에 넣어 w값을 다시 계산
입력값이 0인 경우 원하는 출력을 얻을 수 없기 때문에 이를 방지하기 위해 편향(bias) 라는 것을 뉴런에 넣어준다.
편향이란 말처럼 입력으로는 늘 한쪽으로 치우친 고정된 값(예 : 1) 을 입력받아서 입력으로 0을 받았을 때 뉴런이 아무것도
배우지 못하는 상황을 방지한다. 편향의 입력으로는 보편적으로 쓰이는 1을 넣는다. 편향은 w처럼 난수로 초기화되며
뉴런에 더해져서 출력을 계산하게 된다. 수식에서는 관용적으로 bias의 앞 글자인 b 를 쓴다.
(편향을 쓰지 않은 경우)
x = 0
y = 1
w = tf.random.normal([1], 0, 1)
for i in range(1000):
output = sigmoid(x * w)
error = y - output
w = w + x * 0.1 * error
if i % 100 == 99:
print(i, error, output)
output :
99 0.5 0.5
199 0.5 0.5
299 0.5 0.5
399 0.5 0.5
499 0.5 0.5
599 0.5 0.5
699 0.5 0.5
799 0.5 0.5
899 0.5 0.5
999 0.5 0.5
(편향을 추가했을 때)
x = 0
y = 1
w = tf.random.normal([1], 0, 1)
b = tf.random.normal([1], 0, 1)
for i in range(1000):
output = sigmoid(x * w + 1 * b)
error = y - output
w = w + x * 0.1 * error
b = b + 1 * 0.1 * error
if i % 100 == 99:
print(i, error, output)
output :
99 0.06981800921207859 0.9301819907879214
199 0.041997974615891165 0.9580020253841088
299 0.029873458318409263 0.9701265416815907
399 0.02313491701141579 0.9768650829885842
499 0.01885860676298068 0.9811413932370193
599 0.015907996575644567 0.9840920034243554
699 0.01375116440806401 0.986248835591936
799 0.012106760178035247 0.9878932398219648
899 0.010812026076422732 0.9891879739235773
999 0.009766410636732115 0.9902335893632679
편향을 나타내는 b를 추가하고 w 처럼 tf.random.normal 로 초기화 한다.
실제 출력인 output 값을 계산할 때, sigmoid(x * w + 1 * b) 라는 수식을 계산해서 각 입력에 가중치와 편향을
곱해서 더해준 뒤 시그모이드 함수를 취한다. 기대출력과 실제 출력의 차이인 error로 w와 b를 각각 업데이트해서
뉴런을 학습시킨다.
이상.
피곤해서 다음에 마저 함
'Deep Learning' 카테고리의 다른 글
[Django 기초] 아키텍처 패턴(Architecture Pattern) (0) | 2021.12.03 |
---|