0. review gradient descent

저번 시간에 했던 gradient descent (경사하강법) 과 train 개념에 대해 복습한다.

  • loss를 최소로 가지는 모델, True line(선형모델에서) 을 얻기 위해 loss를 최소로 가지는 w(가중치)를 구하는 것이 학습 train / learning 이다.
  • train 과정 즉, 가중치 W를 loss가 적어지게 바꾸는 알고리즘 중 gradient descent (경사 하강법)을 배웟고 해당 알고리즘에서 기울기는 d_Loss / d_w로 정의한다.

$$
w = w - \alpha \cdot \frac{\partial \text{loss}}{\partial w}
$$

 

 

1. Back-propagation and Autograd 

저번 시간까지는 선형 모델 (1차함수 형태)의 모델을 기준으로 경사하강법을 적용하여 train하는 것을 해 보았고, 이 과정에서 gradient는 직접 loss에 대한 식을 w에 대해 미분하여 직접manually하게 구하였다.

당연하게도 모델이 선형 모델만 있는 것도 아니고, 매우 복잡한 network를 가지고 있을 수 있다.

이런 경우 직접 loss에 대한 식을 구하고, 이를 w에 대해 미분하는 것은 고난이 될 수 있다.

 

어케 미분함 ㅇㅅㅇ

 

Computation graph + chain rule 

이에 대한 해결책으로 Computation graph 와 chain rule을 사용하여 구한다. 

 

chain rule은 합성함수의 곱이라고 볼 수 있으며 이 의미는 input > output(input2) > final_output 과 같이 input과 output이 맞물려 있는 경우, 한개 함수의 output이 또 다른 함수의 input으로 들어가는 경우에 각각의 함수를 각각의 input변수에 대해 미분하고 곱하면 d_final_output / d_input 을 구할 수 있다는 것이다.

위의 fc_layer에서 여러 레이어가 맞물려 있는 경우 , 최종 Loss에 대해 처음 input layer의 가중치 w의 gradient를 구하려면 chain rule이 좋은 방법이 될 것을 상상해 볼 수 있다.

 

$$
\frac{dz}{dx} = \frac{dz}{dy} \cdot \frac{dy}{dx}
$$

 

Computation graph는 계산 과정을 노드와 엣지를 포함한 그래프로 표현한 것이다. 이렇게 말하면 와닫지 않을 수 있는데 디지털논리회로에서 논리 회로의 식을 AND ,NOR gate 등을 포함하여 그래프로 만드는 과정이 있는데 매우 유사하다는 생각을 했다.

 

단순 y= w*x라는 선형모델에 대해 loss를 계산하는 과정을 Computation graph로 그린 것을 아래에 첨부한다.

Computation graph

 

Computation graph + chain rule  > Back-propagation

앞서 매운 computation graph와 chain rule을 활용하여 , foward pass 와 Back propagation 과정을 따라가보자.

 

다시 모델이 train되는 과정을 생각해보자.

1 epoch 첫 시작에서 w가중치는 random value로 배정되고, 주어진 정답dataset의 input 에 대해 설정된 w로 예측값 y_pred를 계산한다. 이후 dataset의 y value(label 정답값)과 y_pred의 거리 loss를 계산하게 된다. (foward pass)

 

이제 계산된 loss를 가지고, 가중치 w를 업데이트 해야 한다. 전 강의에서는 d_loss / d_w에 대한 식을 직접 구했지만 이번에는 그렇게 하지 않고 chain rule을 사용한다.

 

1. 각각의 gate에 대해 local gradient를 계산한다.

2. chain rule을 사용해, 마지막 gate의 local gradient 부터 최종까지 연결해 d_loss / d_w를 계산한다. (Back-propagation)

 

쉬운 과정인데 텍스트로 치는게 어려운 것 같다. 해당 chain rule로 돌아가면서 뒤의 가중치를 계산하고, 강의에서는 업데이트 하는 w가 하나였지만 가중치가 여러개고 겹겹히 쌓여있는 것을 chain rule로 계산하는 것을 생각해 보니 이게 역전파Back-propagation라고 불리는 것이 납득이 간다.

 

아래에 exercise 4-1 x = 2 , y = 4 , w =1 에 대한 foward pass 와 back - propagation 과정 풀이를 첨부한다.

 

2. Autograd with pytorch exercise

파이토치에서는 Computation graph를 구현하고 , gradient를 계산하는 과정이 자동화되어 있다. 강의에서는 torch 의 Variable을 사용해 Computation graph를 구현하는데 현 시점 tensor안에 해당 기능이 통합되었다. 따라서 강의와 코드가 다르고 , 의미도 어드정도 다르다.

 

https://tutorials.pytorch.kr/beginner/basics/intro.html 파이토치 한국어 듀토리얼

 

한줄한줄 기능을 살펴보자

 

1 - tensor, requires_grad = True

w = torch.tensor([1.0], requires_grad=True)

텐서를 선언

requires_grad=True

해당 텐서가 미분 대상인지 아닌지 보여주는 flag

[1.0],

텐서의 값 여기서는 random value인 1.0으로 시작 (1 * 1 크기 tensor인 것)

 

2 - .backward()

l.backward() # 3) Back propagation to update weights

l 변수 (여기서는 loss 식)를 미분한다. 모든 변수에 대해 편미분 (역전파 과정), 여기서 Computation graph + chain rule  > Back-propagation 과정을 한번에 해준다고 보면 된다.

 

3 - .grad

강의에서는 d_loss / d_w 의 값이 w. data에 저장된다고 알려줬지만 , gpt 피셜 최신 방법으로 grad를 더 추천한다고 한다.

loss.backward()
print(w.grad)
print(b.grad)

각각 d_loss / dw 와 d_loss / db를 출력한다.

 

4. .torc.no_grad():

with torch.no_grad():
w -= rl * w.grad # l.grad = d_loss / d_w

 

여기서 부터는 헷갈릴 수 잇다. 

먼저 leaf tensor 에 대해 알아봐야 한다.

 

4.1  w-= rl * w.grad

 

leaf tensor란? 

  • 사람이 직접 만든 텐서고
  • 계산의 결과물이 아니며
  • 미분 가능한 대상

 

우리에게는 w가 leaf tensor라고 볼 수 있다.

a. torch는 leaf tensor에 대해 autograd를 저장한다. 

 

즉 backward()를 호출하면,
Autograd는 계산 그래프를 따라 자동 미분을 수행하고,
그 결과는 "leaf tensor"의 .grad 속성에만 저장된다. 

 

아래와 같은 방식(out of place)으로 식을 작성하면 torch는 w를 새로운 텐서로 형성하고

해당 w는 더 이상 leaf tensor가 아니게 된다.

w = w - rl * w.grad

 

따라서 아래와 같은 (in - place 식을 써야 한다. )

 

w -= rl * w.grad # l.grad = d_loss / d_w

 

4.2  with torch.no_grad()

 

torch 는 우리가 일반적인 수식을 코드로 작성하더라도 이를 모두 자동적으로 기억하고 그래프화 시킨다 Autograd. 여기서 그래프화 시킨다는 말은 모든 코드 수식에 대해 Computation graph을 그린다고 이해하면 된다.

나중에 .backward를 했을 때 자동 미분이 가능하게 이런 구현을 해놓은 것이다.

하지만 위 코드에서 경사하강법으로 w를 업데이트 하는 부분은 미분의 대상이 아니고, 계산 그래프에 포함되면 안 되는 연산 즉, autograd의 적용대상이 아니므로 그 부분을 예외처리하는 코드가

with torch.no_grad():

인 것이다. 

 

 

4 - .grad.zero_()

torch에서는 grad값을 초기화 하지 않고, 다시 backward하면 기존 .grad에 단순합+= 누적되어 계산된다 이를 방지하기 위해 

w.grad.zero_()

를 써준다.

 

 

전체 코드는 아래와 같다.

 

import torch

x_data = [1.0, 2.0, 3.0]
y_data = [2.0, 4.0, 6.0]

w = torch.tensor([1.0], requires_grad = True ) #ramdom value인 1로 시작하는 텐서, requires_grad = True로 미분가능 텐서로 선언

rl = 0.01 # learnning rate

def forward(x) :
   return w*x

def loss(x,y) :
   y_pred = forward(x)
   return (y_pred - y) ** 2


print("before trainning predict y, x is 4", forward(4.0))

for epoch in range(100) :
   for x_val, y_val in zip(x_data, y_data) :
   l = loss(x_val, y_val)
   l.backward() # back propagation
   with torch.no_grad():
      w -= rl * w.grad # l.grad = d_loss / d_w
   w.grad.zero_()
   print ("\t loss grad w ", l, w.grad, w)

print("before trainning predict y, x is 4", forward(4.0))



 

 

 

<+>

강의에서 사용한 

.data

w.grad.data

를 사용하면 

with torch.no_grad()와 in-place식을 사용하지 않고도, 동일하게 코드를 작성할 수 있다.

 

여기서 .data와 grad.data는 autograd 기능을 잠시 끄고, tensor의 raw data만을 가져오는 방법이다.

   with torch.no_grad():

와 동일한 역할을 한다고 볼 수 잇다.

 

그러나 해당 방식은 .data로 직접 수정하는 경우 그래프가 손상될 위험이 커, 권장하지 않는다고 한다. .data를 사용한 코드는 아래와 같다. + gpt의 설명

 

🔹 .data 요약

  • .data는 텐서의 실제 값만 추출한 버전으로, autograd(자동 미분) 추적에서 제외됩니다.
  • 과거에는 gradient를 수동으로 수정할 때 .data를 사용했지만, 지금은 with torch.no_grad():가 더 안전하고 권장됩니다.
  • .data를 잘못 사용하면 계산 그래프가 손상되어 학습이 제대로 되지 않을 수 있으므로, 특별한 경우가 아니면 사용을 피하는 것이 좋습니다.

🔧 .data가 왜 위험한가?

1. .data는 autograd 추적을 우회함

  • .data로 접근한 텐서는 requires_grad=False 상태이며 autograd가 변화를 추적하지 않습니다.
  • 이 상태에서 값을 변경하면, PyTorch는 변화가 있었다는 걸 감지하지 못합니다.

2. 계산 그래프는 여전히 연결되어 있다

  • .data를 통해 수정한 텐서는 계산 그래프 상에서 부모 노드와 연결은 유지되지만, 수정된 값은 추적되지 않기 때문에 일관성이 깨집니다.
  • 이로 인해 backward로 계산된 gradient가 잘못되거나, 오류는 없지만 학습이 엉망이 되는 경우가 발생합니다.

 


x_data = [1.0, 2.0, 3.0]
y_data = [2.0, 4.0, 6.0]

w = torch.tensor([1.0], requires_grad = True ) #ramdom value인 1로 시작하는 텐서, requires_grad = True로 미분가능 텐서로 선언

rl = 0.01 # learnning rate

def forward(x) :
return w*x

def loss(x,y) :
y_pred = forward(x)
return (y_pred - y) ** 2


print("before trainning predict y, x is 4", forward(4.0))

for epoch in range(100) :
for x_val, y_val in zip(x_data, y_data) :
l = loss(x_val, y_val)
l.backward() # back propagation
w.data = w.data - rl * w.grad.data # l.grad = d_loss / d_w
w.grad.data.zero_()
print ("\t loss grad w ", l, w.grad, w)

print("before trainning predict y, x is 4", forward(4.0))



 

가중치가 2개이고 이차함수 형태인 비선형 모델에 대해 Gradient Descent 알고리즘으로 머신러닝 구현

  • loss를 w1, w2에 대해 편미분하였다.
  • 기존 마지막 데이터 포인트값에 대해 스칼라값으로 loss를 출력하던걸, 한 개 epoch에서 loss를 평균하여 출력하도록 개선하였다.
#exercise 3-1 compute gradint, 모델의 가중치가 2개인 경우 -> implement machine learning

import numpy as np
import matplotlib.pyplot as plt

w1 = 1.0 #가중치 랜덤값으로 시작
w2 = 1.0 #가중치 랜덤값으로 시작
b = 0.0 #biased

x_data = [1.0,2.0,3.0]
y_data = [2.0,4.0,6.0]
running_rate = 0.01

def forward(x) : 
    return x*x*w2 + x*w1 + b

def loss(x,y):
    y_pred = forward(x)
    return  (y_pred - y)*(y_pred-y)

def gradient_1(x,y):  # d_loss / d_w1
    return 2 * x * (x*x*w2 + x*w1 + b - y)

def gradient_2(x,y) : #d_loss / d_w2
    return 2 * x * x * (x*x*w2 + x*w1 + b - y)

print("학습하기 전 예측값 공부시간 4시간 시험점수는 ? ", forward(4))

for epoch in range(100) :
    loss_total = 0 # 변수 초기화
    for i , x_val in enumerate(x_data):
        y_val = y_data[i]
        w1 = w1 - running_rate * gradient_1(x_val,y_val)
        w2 = w2 - running_rate * gradient_2(x_val,y_val)
        l = loss(x_val,y_val)
        loss_total += l
        print("\t x_val y_val ", x_val, y_val, " grad " ,gradient_1(x_val,y_val), gradient_2(x_val,y_val) )
    print("epoch", epoch , "w1 " , w1 , "w2", w2 , "mean of loss", loss_total / len(x_data))

print("학습 후 예측값 공부시간 4시간 시험점수는 ? ", forward(4))

1. review train, linear model

저번 시간에는 선형 모델을 정의하고,

MSE가 최소가 되는 w가 우리가 원하는 이상적인 모델, true line이라는 것을 배웠고,

이를 찾기 위해 정해진 범위 내에서 일일이 w를 움직여 가면서 MSE를 계산하고 최소인 점을 찾았다.

하지만, 해당 방법은 계산해야 되는 w가 점점 더 많아질수록 계산량이 많아지고 복잡하다는 점이 있다.

처음에는 ramdom한 값으로 임의의 직선이 형성되지만 true line과의 loss 계산을 통해서, 점점 true line과 가까워지도록 w를 업데이트 한다.

 

정리하자, 저번 시간에 배운 train 또는 learning의 목표는 loss를 최소화하는 w를 찾는 것이다.

--

2. Gradient Descent (경사하강법)

임의의 기울기가 양인 이차함수를 생각해보자. 함수의 정의역은 w이고 치역은 loss이다.

우리는 loss가 최소인 w를 찾기 위해서, 해당 이차함수의 극점을 찾아야 한다.

이를 위해서 Gradient Descent algorithm, 경사하강법은 점 w'에서 기울기가 양이면 좌측으로 w를 업데이트, 음이면 우측으로 w를 업데이트하는 방식으로 극점, 즉 loss가 최소가 되는 w를 찾는다.

$$
\text{loss} = (\hat{y} - y)^2 = (x \cdot w - y)^2
$$

$$
w = w - \alpha \cdot \frac{\partial \text{loss}}{\partial w}
$$

--

3. (extra) 경사하강법의 한계

강의를 듣고 드는 의문점들이 많아서, 추가로 찾아본 내용들이다.

--

q1. 우리는 MSE가 최소가 되는 점을 찾아야 하기에, MSE를 w에 대해 미분한 기울기를 써야만 할 것 같은데, 왜 위의 수식에서는 각 datapoint의 loss를 미분하는가?

a1.

각 데이터 포인트의 loss만 미분하는 이유는 계산을 빠르게 하기 위해서입니다.
전체 MSE의 gradient는 개별 loss gradient의 평균이기 때문에,
결국 여러 번 반복하면 같은 방향으로 수렴합니다.

그렇다고 한다.. 내가 말한 전체 MSE에 대해 미분하는 건
1. Batch Gradient Descent (배치 경사하강법)

위에서 한 방법은
2. Stochastic Gradient Descent (확률적 경사하강법, SGD) 라고 한다.

막 납득이 엄청 되지는 않지만 ~ 일단 넘어가기로 한다.

--

q2. 단순 점의 기울기만 가지고 w를 업데이트하면 global한 최솟값을 찾지 못할 것 같아 보이는데?

a2.

단일 점만 보고는 최솟값을 못 찾는 것이 맞지만,
무작위 점들을 반복해서 보는 방식(SGD)은 전체 평균 방향을 점점 따라가게 되어
결국 global minimum에 수렴할 수 있습니다
(단, 손실함수가 convex거나, 좋은 조건에서).

“데이터가 충분하고, 반복을 무한히 하면 global minimum에 수렴한다”

하지만 아래와 같은 한계가 존재

  • 비볼록 함수 문제 딥러닝 등 복잡한 모델은 local minima, saddle point가 많아 global minimum 보장 안 됨
  • 학습률(learning rate) 민감성 너무 크면 발산, 너무 작으면 느림 또는 최솟값 근처에서 멈춤
  • 노이즈와 진동 문제 (SGD) 샘플 하나의 gradient만 쓰면 방향에 노이즈 → 진동 발생
  • 평탄한 영역(flat region) 기울기 거의 0인 구간에서는 학습 정체
  • 전역 정보 부족 현재 위치의 기울기만 보고 움직이기 때문에 전체 구조 고려 불가

그렇다고 한다. 일단 넘어가자.

--

4. 실습 코드

epoch, learning rate와 같은 용어가 새로 등장했다. 익숙해지자!

import numpy as np  
import matplotlib.pyplot as plt

#1. 주어진 dataset과 초기 랜덤값 가중치 구현

x\_data = \[1.0,2.0,3.0\]  
y\_data = \[2.0,3.0,6.0\]  
w = 1.0 # 랜덤값으로 시작

learning\_rate = 0.01 # gradient descent 의 alpha값, learning rate

#2. forward, gradient, loss 함수를 구현

def forward(x):  
return w\*x

def loss(x,y):  
y\_pred = forward(x)  
return (y\_pred-y)\*(y\_pred-y)

def gradient(x,y): # d\_loss/d\_w  
return 2 \* x \*( w \* x - y )

#train 진행 전, 예측값을 print  
print("before training input 4, predict is ",forward(4.0))

for epoch in range(100):  
for i,x\_val in enumerate(x\_data):  
y\_val = y\_data\[i\]  
grad = gradient(x\_val,y\_val)  
w = w - learning\_rate \* grad  
l = loss(x\_val,y\_val)  
print("\\t grad ", x\_val, y\_val, grad)  
print("epoch : ",epoch, "w = " , w , "loss : " ,l)

#train 후, 예측값을 print  
print( "after training input 4, predict is ",forward(4.0))

02 Linear Model


1. review Machine Learning

저번 강의 overview에서 간단하게 배웠던 개념을 복습하고 시작한다.
AI라는 큰 범주 안에서 다양한 알고리즘이 존재하고, 그 안에 Machine Learning이 존재한다.

Machine Learning 안에서도 다양한 알고리즘들이 있는데,
그 중에서도 Deep learningRepresentation Learning을 기반으로 인공신경망을 깊게 쌓아놓은 구조라 하여 Deep Learning이라고 불린다.


비AI라고도 말할 수 있는 rule based system과 전통적인 ML, Deep learning (Representation Learning)을 비교하며 ML, DL의 정의에 대해서 알아보자.

  • rule based system을 생각해보면, input이 있고 ouput이 있을 때, input을 output으로 바꾸어 주는 system 자체를 모두 사람이 일일이 정의하고, 설계해야 한다.
  • 그와 비교하여 전통적인 ML은 system 자체를 사람이 전부 설계할 필요가 없다. input에서 좋은 feature를 추출하여 ML에게 가져다 주면 학습하고, 스스로 시스템을 만든다.
  • Deep learning (+ Representation Learning) 은 뭐가 다를까? 기존 전통적 ML이 good feature을 사람이 직접 추출하는 부분이 한계점이라 한다면 해당 알고리즘은 그 한계를 벗어났다. 그저 input을 가져다 주면, 시스템이 자동적으로 good feature을 추출하고, 합리적인 ouput을 뽑아낸다.

정리해보자.
AI안에 Machine Learning이 있고, Machine Learning안에 Representation Learning, Deep learning이 있다.

Machine Learning은 사람이 명시적으로 시스템을 전부 정의하지 않아도 데이터를 기반으로 스스로 학습하고, INPUT에 대해 OUTPUT을 뽑아내는 알고리즘을 말한다.

"A computer program is said to learn from experience E with respect to some class of tasks T and performance measure P, if its performance at tasks in T, as measured by P, improves with experience E."

Representation Learning은 인간이 좋은 feature를 일일이 추출하지 않아도, 스스로 모델이 feature을 추출하는 것을 말한다.

Deep learning은 인공 신경망을 깊게 쌓아 놓은 구조로 모델이 학습하는 경우를 말한다.

헷갈림 주의 : Representation Learning과 Supervised Learning(지도학습)을 지금까지 헷갈려 하고 있었다;;
Representation Learning은 모델이 사람의 개입 없이 의미 있는 feature를 자동으로 추출하는 학습 방법이며, 이는 지도학습 또는 비지도학습 방식으로 구현된다.


2. Linear Model

Supervised Learning(지도학습)으로 모델에게 시험 점수를 output을 뽑는 경우를 가정하자.
주어지는 dataset은 공부 시간 : 시험 점수 이다.

공부 시간 input (X) 시험 점수 (Y)
2 20
4 40
6 60
8 80

이런 경우 모델을 어떻게 정의해야 할까?
생각해보면 제일 간단한 일차함수를 생각해 볼 수 있다.

$$
\hat{y} = W \cdot x + b
$$

우리의 목표, 가지고 있는 dataset을 가지고, 해당 일차함수(시스템)이 임의의 input에 대해 reasonable한 output을 뽑도록 하는 것

해당 부분을 위해 loss 그리고 MSE를 정의한다

  • loss 또는 error 는 예측값과 실제 값의 거리 즉, 차의 제곱의 형태

$$
\text{loss} = (\hat{y} - y)^2 = (x \cdot w - y)^2
$$

(해당 수식에서 biased b는 실습과정 편의를 위해 생략)

해당 식 loss 또는 error >>> 한 data point에서 의미.

  • MSE (mean square error) 은 한 개 가중치 W에 대해 dataset의 모든 datapoint의 loss 의 평균

$$
\text{MSE} = \frac{1}{N} \sum_{i=1}^{N} (x_i \cdot W - y_i)^2
$$


해당 개념들을 가지고 Supervised Learning(지도학습) 하는 절차

  1. 처음에는 임의의 W로 시작 (random한 값) → 해당 W의 MSE를 계산
  2. 해당 W를 움직여 가면서 각 W에 대해서 MSE를 계산
  3. 결과적으로 dataset에 대해 MSE가 제일 적은 W를 선택

실습 코드

import numpy as np
import matplotlib.pyplot as plt


w = 0 #random value로 스타트
x_data = [1.0,2.0,3.0]
y_data = [2.0,4.0,6.0]
#train data set

# 1. 
def forward(x) :
    return w*x

# 2. 
def loss(x, y) : 
    y_ = forward(x)
    return (y-y_)*(y-y_)

# 3. 
for w in np.arange(0.0 , 4.1 , 0.1) :
    print("w = ", w)
    l_mean = 0
    for i,x_ in enumerate(x_data):
        l_ = loss(x_,y_data[i])
        l_mean += l_
    l_mean = l_mean / len(x_data)
    print("MSE = ", l_mean)

# 4. 
l_list = []
w_list = []

for w in np.arange(0.0 , 4.1 , 0.1) :
    print("w = ", w)
    l_mean = 0
    for i,x_ in enumerate(x_data):
        l_ = loss(x_,y_data[i])
        l_mean += l_
    l_mean = l_mean/ len(x_data)
    print("NSE = ", l_mean)
    w_list.append(w)
    l_list.append(l_mean)


plt.plot(w_list, l_list)
plt.ylabel('loss')
plt.xlabel('W')
plt.show()

홍콩 과기대 김성근 교수님의 PyTorch Zero To All Lecture Review 를 공부하고, 정리할 예정이다.

https://youtu.be/b4Vyma9wPHo?si=BpPDZNl1wfF5Ome8

총 13강으로 구성되어 있고, 대략 15분 정도의 분량으로 학기 공부와 병행하기 좋은 것 같다.
하루에 2강씩 수강하여 2주안에 끝내고 정리하는 것을 목표로 잡음

+ Recent posts