본 포스트팅은 인하대학교 컴퓨터공학과 오픈소스sw개론 수업자료에 기반하고 있습니다. 개인적인 학습을 위한 정리여서 설명이 다소 미흡할 수 있으니, 간단히 참고하실 분들만 포스팅을 참고해주세요 😎
텐서플로우에 들어가있는 배열 형태의 가장 기본적인 단위. 이때 tensor 안의 각 배열의 구성성분으로 여러가지 타입(int, float, ...)의 데이터들이 저장될 수 없다. 한 가지 타입으로만 구성됨
tensor 객체는 tf . Tensor 와 같이 생성가능
한번 생성한 tensor 의 값은 변경불가능.
tf.constant(인풋 데이터) : tesntor 객체 생성하는 함수
tensor 객체 : tf.Tensor(value, shape = (), dtype = )
shape=(3, ) : 현재 tensor 의 배열을 보면 축이 1개밖에 없다. shape 에 출력되는것은 각 축에 대한 크기 정보인데 축이 하나밖에 없어서 3을 출력하고 끝난다.
아래처럼 3x2x5 짜리 3차원 tensor 배열을 생성할 수 있다.
그림으로 표현해보자면 아래와 같다.
아래와 같은 형태로, tensor 객체를 Numpy 배열로 변환 가능하다.
numpy 에서 했던 사칙연산 함수처럼 tensor 에서도 사용 가능하다.
tf . add( tensor1, tensor2 ) 또는 tensor1 + tensor2
tf . multiply( tensor1, tensor2 ) 또는 tensor1 * tensor2
tf . matmul( tensor1, tensor2 ) 또는 tensor1 @ tensor2
예를들어 위와같이 생성한경우 아래 그림처럼 3x2x4x5 짜리 4차원짜리 tensor 배열이 생성되는 것이다.
차원을 말할떄 axis 라는 축의 단위가 등장한다.
위의 경우 첫번쨰 축은 axis = 0, 2번쨰 축은 axis = 1, 그 다음으로 2, 3이 되는 것이다.
이렇게 n번쨰 axis 를 말할때 마이너스 인덱싱도 가능하다. 즉 axis 3은 axis -1이 되기도 한다.
앞서 살펴본 value, shape, dtype 외에도 다양한 속성들이 존재한다. 이들을 조회하는 함수들이 있다
위와같은 1차원 tensor 에 대해 인덱싱을 해보자. 각 print 문에서 인덱싱한 결과를 numpy() 함수를 이용해 numpy 로 변환해서 출력하고 있다.
출력 결과는 아래와 같다.
출력결과
shape의 크기를 명시할때 -1은 딱 한번만 사용 가능하다.
-1을 딱 한번만 사용하면 자동으로 알아서 적절한 shape 가 할당되도록 숫자가 할당된다.
tf.reshape(rank_2_tensor, [-1, 3])
=> -1이 등장했을때 TensorFlow는 자동으로 shape=(2,3) 을 보고 배열의 총 사이즈가 6임을 인지하고, 동일한 사이즈로 reshape 해주기 위해 TensorFlow가 알아서 자동으로 2로 변환해준다.
tf.reshape(rank_2_tensor, [-1, -1, 2]) => -1 이 2개 이상할당되면 -1에 어떤 값이 할당되어야할지 자동으로 절대 계산해줄 수가 없다. 따라서 에러발생
tf.Tensor 와 유사하지만, tf.Tensor 와 달리 값들이 수정될 수 있다.
tf.Variable() 안의 값들은 assign 함수를 통해 수정 가능하다.
형태 : variable객체.assign(새롭게 할당할 배열)
주의사항 : 새롭게 할당할 배열의 shape(형태) 와 기존 배열의 shape 가 똑같아야 한다.
만일 아래처럼 3x2 짜리 배열인데 2x2 의 shape 를 지닌 배열로 변경하려 했다면 에러가 발생할것이다.
=> 쉽게말해, 미분할 함수를 준비를 해주는 것이다.
우선 아래처럼 미분할 함수를 정의했다고 하자.
이떄 파라미터 변수 x 는 Tensorflow 의 tf.Variable 이다.
g_x 에는 "x^2 + 2*x - 5" 를 미분한 결과인 "2*x + 2" 에 대해 x값인 1을 할당한 결과가 저장된다.
=> 앞서 정의한 tf.Variable에 저장된 값인 1.0의 값을 2*x + 2 식에 할당한 결과가 g_x에 저장되는 것이다.
어떤 함수에 대해서 우리가 경사하강법( gradient descendent ) 을 사용해서 파라미터를 추척해 갈 것인지 loss function 을 정의해야한다.
loss function 이 바로 학습목표(training objecttive) 에 해당한다. (지난번에 지도.비지도 학습때 말했던 것)
=> 왜냐하면 이 값을 줄이려고 파라미터가 바뀌기 떄문이다.
=> loss function 을 다르게하면 곧 학습목표가 달라지는 것이므로, 학습방향이 달라져서 결과가 다르게 나온다.
objective function, cost function 등으로도 불린다.
선형 모델(linear model) : f(x) = W*x + b
=> W : weight(가중치), b:bias(편향)
low-level API 를 활용해서 학습을 시키고 모델을 만들어내는 과정을 알아보자.
우선 아래와 같은 코드를 통해 Dataset 을 구성해보자.
전체코드
데이터를 구성하는 과정을 살펴보자.
TRUE_W, TRUE_B : 실제로 우리가 데이터를 생성할 떄 참조할 weight(가중치) 과 bias(편향) 값
=> 즉 y = 3x+2 와 같은 선형방정식을 가정하고 데이터를 준비해 놓는 것이다.
NUM_EXAMPLES = 201 : dataset 의 크기는 201로 가정. 즉, 201개의 데이터를 생성할 것이다.
=> x 는 -2 에서 2 사이의 사이의 데이터 값을 201 가지는 tensor 이다.
noise 란 직선에서 벗어난 정도를 의미한다.
201개의 데이터들이 3x+2 형태로 나타나게 되면 학습(Training) 과정이 정말 쉽다, 그러나 실제로 관측되는 값들은 점 형태로 표현해봤을 떄, 점들이 많이 흩어져서 분포되어 있는 모습이 나온다. 예를들면 아래와 같은 형태이다.
=> 해당 직선를 따라서 가는 각 점들은 noise (직선에서 벗어나는 정도. 수치) 가 있다. noise 를 발생시켜보려고 위와 같은 함수를 사용한 것이다.
그리고 matpolib 모듈의 plt.plot 으로 201개의 데이터들을 출력을 해보면 아래와 같다.
앞서 Dataset 을 구성했다면, 이제는 Model 을 정의해야한다.
클래스 형태로써 Model 을 정의하자.
코드 해설
생성자(init) 를 보면 super() 를 통해 부모 클래스(tf.Module 의 생성자를 호출한다.
=> 파라미터를 선언한 부분. weight(가중치) 와 bias(편향) 값을 가지고있다. weight 은 5.0으로 주고, bias 는 0.0 으로 주었다.
=> 아래처럼 tf.Module 을 상속받은 클래스 Mymodel의 객체인 model 에 대해, model(3.0) 과 같이 호출했는데, 이는 곧 "model._ _call__(3.0)" 과 같이 호출한 것과 동일하다.
아무튼 print(model(3.0).numpy()) 의 출력결과는 5x3 + 0 = 15 가 된다.
loss function 의 종류는 정말 다양한데, 이번 강의에서는 MSE(mean squared error) 라는 loss function 을 사용한다.
예를들어 output 에서 label 을 뺀 값을 제곱하고 평균을 계산하는 함수이다. 즉 (output - label)^2 에 대해 평균을 구하는 것이다.
=> 지금 이 경우는 201개의 데이터에 대해 모두 합한후 그 결과를 201로 나누는것이다.
텐서플로우로 MSE 함숨를 구현해보면 아래와 같다. output(target_y) 에서 label(=predicted_y) 를 빼주고, sqaure() 로 제곱을 해준다음 reduce_mean() 으로 평균을 구해준다.
다음 과정으로는 아까 데이터들의 point 를 표시했던것에 이어서, 데이터와 실제 Model 와 우리의 직선이 나타내고 잇는 직선, 그리고 loss 값까지 나타내보는 것이다.
그래프를 보면 현재 우리가 나타내는 Model의 직선(초록색 직선)이 주황색 직선(실제 Model 이 표현하는 직선) 이 많이 빗나가고 있음을 볼 수 있다. 주황색 직선에 맞춰주기 위해 계속 변화를 시켜나갈 것이다.
전형적인 graident descent(경사 하강법) 에서의 Training(학습) 반복문 구조는 아래와 같은 4가지 과젇들을 포함한다.
- 1) Model 에 입력을 넣어주어서 예측값을 계산한다.
- Model 에 입력을 넣어줘서 현재 Model 입장에서 변화값은 무엇이라 생각하니? 라고 묻는것
- 2) loss value 값을 계산한다
- 3) gradient tape 를 활용해서 gradient 값을 계산한다
- 4) gradient 값, 즉 미분값이 잘 나왔다면 그 값을 기반으로 변수들의 값을 바꿔준다.
=> 이 1~4 과정을 반복문을 도는 것이다.
우리는 지금부터 살펴볼 예제에서 위 과정들을 한번에 수행해주는 for문이 포함된 train 함수를 정의해보고, 이 함수안에서 traning function 을 호출하는 방식으로 학습을 시킬것이다.
=> 우선 gradient 를 하고자하는 함수를 등록해주었다.
이떄 model(x) 에서 output, 즉 예측값을 계산해낸다. 그러고 아까 정의한 loss 함수를 통해 loss value 를 계산해주는 것이다. (1,2번 과정 수행)
앞서 gradient 를 구하고 싶은 loss value 값에 대해 with tf.GradientTape() 구문에서 선언이 끝났다면, 이제 현재 loss value 에 대한 미분값(gradient) 를 계산해주면 하면된다. 이를 t.gradient 에서 해주고있다.
t.gradient(current_loss, [model.x, model.b]) : current_loss (현 loss value) 에 대해 미분을 하는데, model 의 w (weight)와 b (bias)에 대한 미분을 둘다 하는 것이다.
=> 이렇게 우리가 원하는 목표값을 계산하기 위해 여러개의 값에 대해서 미분을 하고싶다면, 이처럼 리스트 형태로 여러 값들을 파라미터로 넘겨주면 된다.
리스트로 넘겨준 값 순서에 따라서 차례대로 dw, db 에 미분된 값이 저장된다.
마지막으로 model 의 w(weight) 와 b(bias) 를 update 해주면 된다.
이떄 Variable 의 내장함수 assign_sub 란, 기존에 Variable 이 저장하고 있던 값에서 함수의 인자값으로 넘져준 변수의 값만큼을 빼주는 것이다.
=> model.w.assign_sub(learning_rate * dw) : 즉, model 의 Variable 인 w 에서 learning x dw 값을 뺴주고 그 결과값을 다시 model.w 에 저장해주는 것이다. ( w = w - learning_rate x dw 가 수행된다.)
이로써 경사하강법을 수행하는 코드가 다 완성이 된것이다. 이제 이를 원하는 만큼 계속 반복해서 훈련시키면 될것이다.
앞서 만든 train 함수를 활용해서 이제 실제로 training 을 시키면된다.
=> for 문을 10번 돌면서 매 반복마다 앞서 정의한 train 함수를 호출한다.
마지막으로 logging 하는 과정이다. for문의 매 반복이 한번 끝날때마다 해당 model 의 값들이 어떻게 되어있는 상태이다라고 간단히 print 하는것
사실상 for문 돌때 이렇게만 구성되어 있어도 당연히 학습이 잘 되긴하는데, 학습이 진행되면서 model 이 어떻게 변화되고 있는지 상태를 확인해보려고 앞선 print 문 같은 것들을 넣어놓은 것이다.
학습이 진행됨에 따라 loss value 값이 계속 감소하는 모습을 볼 수 있다. 즉, 우리가 원하는 loss 를 추려나가고 있다.
또한 맨 마지막에 학습된 값 w, b 를 확인해보면 w 와 b가 각각 3, 2 라는 값에 거의 근사하도록 값이 변한것을 확인할 수 있다.
matplotlib 의 plot 함수로 다시 시각적으로 학습 결과를 확인해보자. 위 코드는 학습에 계속 진행됨에따른 w 와 b 의 값을 보여주고 있다.
학습에 진행됨에따라 w 와 b 가 실제값(3, 2) 로 거의 근사하는 모습을 볼 수 있다.
결국 우리가 원하는대로 loss 를 최소화하는 모습이다.
=> 만일 for문을 엄청 더 조져서 w와 b 값이 정확하게 3, 2가 되었다면 오차가 0이기 떄문에, loss value 가 0이 될것이다.
또한 학습된 model을 data 그래프와 같이 그래프로써 출력해보면 두 직선이 거의 차이가 나지 않는 모습을 볼 수 있다. 게다가 loss value 초기에는 11정도나 되었는데, 1로 줄어든 모습을 볼 수있다.
모델을 정의하는 부분이다. 이떄 keras.Sequential() 은 다양한 element 를 순서대로 쌓은 모델이다. Sequential() 안에 들어오는 파라미터의 각 요소들이 들어온 순서대로 모델을 구성하는 것이다.
Dense() :y =ax+b 에 대한 아웃풋을 units 옵션에 지정된 개수만큼 생성해내는것