핸즈 온 머신러닝 (Hands-On Machine Learning with Scikit-Learn & TensorFlow) / 오렐리앙 제론 지음 , 박해선 옮김
을 읽고, 추후 기억을 되살릴 수 있게끔 나만의 방법으로 내용을 리뷰한다. 따라서 리뷰의 내용 별 비중이 주관적일 수 있다.
챕터 9. 텐서플로 시작하기
Tensorflow는 수치 계산을 위한 강력한 라이브러리이다. 계산 그래프를 정의한 다음, 실행하는 형태로 이루어져있다. 텐서플로는 구글 브레인에서 개발하였고 C++로 개발된 python library다. 텐서플로의 장점으로는 자동 미분 기능을 지원한다는 점과 TensorBoard라는 훌륭한 시각화 도구를 활용할 수 있다는 점, 모든 종류의 신경망 구조 및 계산 그래프를 만들 수 있다는 점 등 다양하다. 많은 사람들이 텐서플로를 사용하며 점점 더 많은 자료가 구축되고 있다는 점이 중요하다.
Tensorflow의 기본을 담고 있는 챕터이니 만큼 예제 코드가 많이 등장하지만, 코드는 핵심적이라고 생각하는 부분만 가져오고 추후 검색할 수 있는 키워드 및 개념을 언급하는 느낌으로 정리하겠다.
또한 본 책에서는 Tensorflow 2.0이 아닌, Tensorflow v1을 사용중이다.
9.1 설치
python 가상환경을 생성 한 뒤, pip3 install --upgrade tensorflow로 설치한다.
9.2 첫 번째 계산 그래프를 만들어 세션에서 실행하기
import tensorflow as tf
x = tf.Variable(3, name="x")
y = tf.Variable(4, name="y")
f = x*x*y + y + 2
간단한 계산그래프를 생성하는 코드이다. 이 때 f의 값이 계산되는 것이 아니라 그래프를 만든 것이므로 세션에서 실행하여야 결과를 알 수 있다.
sess = tf.Session()
sess.run(x.initializer)
sess.run(y.initializer)
result = sess.run(f)
print(result)
sess.close()
기본적으로 x,y를 초기화하여 f의 값을 구하는 코드이다. sess가 계속 반복되는 것이 번거로우므로
with tf.Session() as sess:
x.initializer.run()
y.initializer.run()
result = f.eval()
과 같이 표현할 수 있다. python에서 with 구문을 사용하면 클래스에서 어떤 일이 일어날지 __enter__와 __exit__ 구문에 적혀있는데, tf의 session의 경우 세션이 열리고 닫힌다고 생각하면 된다. f.eval()은 tf.get_default_session().run(f)와 동일하며 모든 변수를 초기화하는 과정을 tf.global_variables_initializer() 함수를 사용하여 해결할 수 있다.
주피터 노트북 등에서 사용할 때에는 셀마다 sess를 선언하기 불편하므로 Interactive Session 을 활용할 수 있다.
9.3 계산 그래프 관리
노드를 만들면 자동으로 기본 계산 그래프에 추가되는데, 여러 계산 그래프를 만들고 싶다면 Graph 객체를 선언하여 사용할 수 있다.
9.4 노드 값의 생애 주기
특정 노드를 eval 할 때에 그 노드가 의존하고 있는 다른 노드들을 찾아 먼저 eval 해야한다. 여러 번의 eval을 실행하면 각각 처음부터 그래프를 실행하므로, sess.run([y, z]) 와 같이 한 번에 여러 노드에 대해 실행해야 효율적이다.
9.5 텐서플로를 이용한 선형 회귀
상수와 변수는 입력이 없으므로 source ops라고 하고, 입력과 출력은 tensor라는 다차원 배열로 이루어져있다. 텐서는 스칼라 값이 될 수도 있고 배열이 될 수도 있다. 책에서는 선형 회귀를 정규방정식을 이용하여 계산하는 그래프를 소개한다.
9.6 경사 하강법 구현
9.6.1 직접 그래디언트 계산
직접 그래디언트를 계산하여 경사 하강법을 구현한다. 경사하강법을 계산하는 노드는 theta = theta - learning_rate * gradients 와 같이 작성하면 theta는 subtract 연산의 출력을 가리키는 텐서가 되어버린다. 따라서 training_op = tf.assign (theta, theta - learning_rate * gradients) 와 같이 노드를 선언해야 한다.
9.6.2 자동 미분 사용
그래디언트를 수학적으로 유도하기 보다는 tf의 자동 미분 기능을 사용하는 것이 효율적이고 편하다. gradients = tf.gradients(mes, [theta])[0] 과 같이 사용할 수 있다.
그래디언트를 계산하는 네 가지 방법 (수치 미분, 기호 미분, 전진 모드 자동 미분, 후진 모드 자동 미분) 중 tf는 후진 모드 자동 미분법을 사용한다. 그래프를 총 n_output + 1 회 순회하면 미분이 가능하다.
9.6.3 옵티마이저 사용
하지만 자동 미분을 사용하여 그래디언트를 계산하는 것 보다 빠른 방법이 있는데, GradientDescentOptimizer를 사용하는 것이다.
https://www.tensorflow.org/api_docs/python/tf/compat/v1/train/GradientDescentOptimizer
9.7 훈련 알고리즘에 데이터 주입
미니배치 경사 하강법을 구현하려면 매 반복마다 X, y를 변경해야 하는데 가장 간단한 방법은 placeholder 노드를 사용하는 것이다. 특정 노드를 eval 할 때에 feed_dict를 통해 placeholder의 값을 전달해 주면 된다.
9.8 모델 저장과 복원
모델을 훈련시키고 나면 다시 쓸 수 있도록 파라미터를 디스크에 저장해야하는데, Saver를 사용하면 된다.
https://www.tensorflow.org/api_docs/python/tf/compat/v1/train/Saver
그래프의 구조는 .meta 확장자를 가진 동일 이름의 파일에 저장되므로, import_meta_graph 를 이용해 그래프의 구조도 불러올 수 있다.
https://www.tensorflow.org/api_docs/python/tf/compat/v1/train/import_meta_graph
9.9 텐서보드로 그래프와 학습 곡선 시각화하기
텐서플로를 통해 그래프를 실행하는 과정에서 훈련 통곗값 등을 텐서보드에 전달하여 interactive graph를 볼 수 있다. 또한 그래프의 정의를 사용하여 그래프 구조를 살펴볼 수 있다.
9.10 이름 범위
계산 그래프가 수천 개의 노드로 인해 어질러지기 쉬우므로, name scope를 만들어서 관련 있는 노드들을 그룹으로 묶을 수 있다.
with tf.name_scope("loss") as scope:
error = y_pred - y
mse = tf.reduce_mean(tf.square(error), name="mse")
같은 이름의 노드가 반복되면 a_1, a_2, a_3 ... 노드로 바뀌듯이, name_scope 도 언급될 때 마다 _1, _2, ... 이 붙는다. 기존에 존재하는 name_scope를 부르고 싶다면 "a" 대신 "a/" 를 부르면 된다.
9.11 모듈화
반복적인 부분이 많은 코드는 유지 보수하기 어렵고 에러가 발생하기 쉽다. 예를 들어 ReLU 함수를 실행하는 노드를 반복적으로 만든다면 ReLU 노드를 생성하는 함수를 만들어 여러번 실행하는 것이 좋다. 이 과정에서 name_scope를 사용한다면 깔끔하게 정리된 그래프가 생성될 것이다.
9.12 변수 공유
그래프의 여러 구송 요소 간에 변수를 공유하고 싶다면, 변수를 먼저 만들어서 함수에 매개변수로 전달하는 방법이 있다. 하지만 이 방법보다는 tensorflow에서 지원하는 get_variable() 함수를 사용하는 것이 좋다. 이 함수는 공유 변수가 아직 존재하지 않을 때는 새로 만들고, 이미 있을 때는 재사용 하는 것을 도와준다.
variable_scope는 name_scope와 비슷하지만, get_variable() 에서 언급하는 node의 이름은 variable_scope의 영향만을 받는다. 또한, 이미 get_variable을 이용하여 이미 존재하는 변수를 가져올 지 여부 등 variable에 관한 변수를 설정할 수 있다.
https://www.tensorflow.org/api_docs/python/tf/compat/v1/variable_scope
https://www.tensorflow.org/api_docs/python/tf/compat/v1/get_variable
예를 들어, relu 노드를 만드는 함수를 정의할 때에는 resue를 True로 설정한 후, get_variable 을 이용하여 기존 변수를 가져오면 된다. 이 때 함수를 반복하여 실행하기 전, get_variable을 이용하여 변수를 생성 및 초기화 시켜주는 것이 중요하다.
혹은, 공유 변수가 처음 호출될 때에만 새로 생성하게끔 함수를 작성하여도 된다. 이 경우 그래프의 모양은 조금 바뀔 것이다.
'개발 인생 > ML' 카테고리의 다른 글
핸즈 온 머신러닝 :: 11. 심층 신경망 훈련 (0) | 2020.01.22 |
---|---|
핸즈 온 머신러닝 :: 10. 인공 신경망 소개 (0) | 2020.01.19 |
핸즈 온 머신러닝 :: 8. 차원 축소 (0) | 2020.01.18 |
핸즈 온 머신러닝 :: 7. 앙상블 학습과 랜덤 포레스트 (0) | 2020.01.17 |
핸즈 온 머신러닝 :: 6. 결정 트리 (0) | 2020.01.17 |