본 포스트팅은 인하대학교 컴퓨터공학과 오픈소스sw개론 수업자료에 기반하고 있습니다. 개발 포스팅과 달리 개인적인 학습을 위한 정리여서 설명이 다소 미흡할 수 있으니, 간단히 참고하실 분들만 포스팅을 참고해주세요 😎
이전에 했던것들과 방식이 완전히 동일하다.
tensorflow 와 전반적인 선형회귀 과정은 매우 유사하다.
이 과정이 사실 필요하긴 한데, 여기서는 pytorch의 MSE 가 간단해서 그냥 바로 계산했다. (따라서 이 과정은 따로 여기서 없음)
경사하강법을 사용. 경사하강법이란 도달하고 싶은 함수의 형태에 맞춰서 최소화 시키는 과정이라고 했었다. 값을 조금씩 변화시켜 가면서 원하느 함수에 대한 형태로 근사시키는 방식이었다. 조금씩 근사시킬때 방향과 보폭을 미분값을 통해서 알아낼 수 있었다.
tensor 와 거의 유사함. 우리가 원하는 가상의 직선은 y = 3x + 2 로 정의한다.
-2 ~ 2 까지 201개의 데이터를 생성한다.
torch.randn() : 201개의 데이터에 대해 noise 를 발생시킨다. 즉 -2~2 사이의 201개의 난수를 발생시킨다.
지난번과 거의 동일.
다른점은 torch.nn.Module 를 상속받아야한다.
또한 텐서플로우의 call 메소드와 동일한 기능을 하는것이 forward 메소드이다.
torch.nn.Linear(input 개수 n, output 개수 n ) 함수 : keras 의 dense 와 동일한 역할을 수행.
torch.optim.SGD(model.parameters()) : gradient descendent 와 관련해 최적화해주는 모듈.
update 과정은 위와 같은 5가지 코드 (step) 을 통해 가능하다.
유의할점은, 앞서 정의하길 x 는 201개의 난수를 리스트 형태로써 저장하고 있었다. 즉 "1차원" 리스트 형태로 저장하고 있었는데,
y.view(-1, 1) 과 같이 201x 1 형태의 2차원 행렬 형태로 굳이 변환해주었다.
=> 변환 이유 : 해당 객체가 이러한 차원의 형식밖에 못 받기 떄문이다.
- pytorch 에서는 2차원 형태의 linear 를 받는다.
- 당연히 이에대한 output 도 2차원 형태로 나온다.
대신 pytorch 에서는 model의 변수들을 update 하는 과정이 조금 까다롭다. update 과정을 계속 살펴보자.
update 을 위해선 먼저 optimizer 를 초기화해줘야한다. 매번 update 할때마다 가장 먼저 해줘야하는 과정이다.
=> zero_grad() 함수를 호출해서 초기화 하면 된다.
초기화를 안해주면 계속해서 이전에 경사하강법으로 계산된 미분값들이 현재 변수에 누적된다(더해진다).
=> 뭔 소리인지 모르겠다면, backward() 를 사용하면 w(weight)와 b(bias)의 미분값(gradient) 가 계산되는 것말 알고 넘어가자.
앞서 살펴본 nn.Linear 객체는 2차원 형태였다. 각 차원은 의미를 지닌다.
(각각 어떤 의미를 지니는지는 이따 뉴련 설명할때 나옴!)
output = model(x.view(-1,1)) : 과 같이 선언해주면 (201, 1) 2차원 행렬 형태를 지니는것으로 변환된다. 즉 각 데이터는 feature 가 1개이고, 총 데이터수는 201개인것이다.
cost.item() : pytorch의 스칼라 값을 파이썬 코드로 변환해줌 (중요한건 아님!)
학습 결과 log
나름대로 원래 값 3,2 에 근사하긴 했다.
ML 의 경우 강아지, 고양이 이미지등의 인풋 데이터를 직접 사람이 넣어줬었다.
이런 방식을 개선해, 기계가 스스로 feature 를 생성하고 학습하는 것이다. 이런 방식이 표현 학습이다.
이렇게 된다면 사람이 할일이 정말 줄어들긴하지만, 아직 성능이 부족했었다.
표현 학습이라는 것은 기계가 feature 에 대해 스스로 알아서 잘 학습해야하는데, 얼굴 형태(ex. 얼굴이 둥글다) 와 같은 어렵고 중요한 정보(high-level fearture) 들을 기계가 feature화 시키기 어려웠다.
대신 정말 단순한 정보인 low-level feature(ex. 이미지가 평평한가?) 들은 기계가 feature 이해하고 feature 로 만들기 쉬웠다.
=> 알고보니, high-level feature 들은 여러 low-level 들의 조합으로 만들 수 있었다! (ex. 엄청작은 픽셀들을 여러개 모아서 눈, 코, 눈썹과 같은 high-level feature 들을 생성 가능했다)
=> 픽셀들을 모아서 눈,코 등을 만들고, 이들을 또 모아서 하나의 얼굴을 만드는 등 계층 구조를 지녔다. 이렇게 층을 쌓아서 만드려면 여러 단계에 걸쳐서 해야지 high-level feature 결과가 나온다.
feature 를 학습하는 스탭이 너무 많아서, low-level 을 가지고 high-level feature 를 만드는 과정이 너무 오래걸렸다.
뉴런 네트워크 : 이런 계층을 계속 층을 쌓고 쌓다보니 low-level 정보들을 high-level 정보들로 자라게 되는 것이다.
=> 이러한 게층적 구조를 통한 학습방식을 딥러닝이라고 한다.
다른 뉴런들로부터 정보를 받는다고 해서 무조건 정보(신호)를 넘겨받는 것이 아니라, 넘겨진 취합된 정보들이 일정 세기 이상이여야 신호가 전달된다.
일정 기준을 넘기지 못하면 없던일이 되는것
일정 기준을 넘기면 뉴런은 그 뒤로 정보를 넘긴다.
=> 정리하면, 정보를 제공해주는 뉴런은 많지만 받은 정보를 다른 뉴런에게 무조건 넘겨주는 것이 아닌, "비선형적으로 전달"해준다.
인공 신경망의 기본 단위 : perceptron (=뉴런)
앞서 살핀 자연계에서의 뉴런 구조처럼, 다양한 뉴런들로부터 인풋을 입력받는다.
결국 아래와 같이 합을 도출해준다.
기본적으로는 디폴트로 바로 아웃풋을 보내주긴하는데, 대부분의 perceptron 에서는 비선형적으로 아웃풋을 내보내준다.
오른쪽과 같은 함수들을 적용해서 비선형적으로 내보내준다.
perceptron 에 정보들이 모여서 아웃풋으로 나갈텐데, linear 함수를 적용한 함수끼리 여러개를 엮으면 시너지가 발생하지 않는다.
linear 연산을 한 결과에다 linear 를 또 하면 그것은 그냥 linear 연산을 한 번 한것과 동일하다.
linear 로 층을 많이 쌓아봤자 의미가 없다. linear 중첩해봐야 linear 가 나와서 의미가 없는것이다.
=> 우리가 뭔가 복잡한 값에 대해 근사하고 싶은 경우, 충분한 횟수의 비선형 연산을 여러번 쌓아줘야한다. 비선형 연산을 여러번 적용해서 층을 쌓아놓으면, 그 안에서 최적화 하는것은 Weight 와 bias 가 바뀌면서 알아서 하는 것이다.
- 비선형 연산을 여러번하는 층을 쌓고나서, Weight 와 Bias 를 최적화시켜줘야 복잡한 연산을 근사 가능하다.
위 그림에서 파란색 동그라미 하나하나가 perceptron 이다.
layer : 들어온 인풋에 대해서 동시간대 (같은 스탭)에 처리하는 perceptrons 들의 집합을 layer (층) 이라고 한다.
한 layer 에서 뉴런(perceptrons) 을 더 늘리는 것이 가능하다.
또한 layer 를 더 늘리는 것도 가능하다.
위처럼 4개의 인풋이 들어오고 아웃이 1개가 나와야하는 상황의 경우,
이 사이에서 무한대로 뉴런 네트워크를 확장이 자유롭게 가능한것이다.
인풋과 아웃풋이 아닌 중간에 있는 perceptrons 들을 전부 숨겨있다고 해서, hidden layer 라고 부른다.
hidden layer 층이 2개 이상있는 뉴런 네트워크는 Deep 뉴런 네트워크라고 표현한다.
=> 출력할 아웃풋 : 0~9 사이의 각 숫자의 클래스에 대한 확률이다.
앞서 살핀 뉴런 네트워크는 1차원에 대한것을 봤었다. 아까봤던 torch linear 를 가지고 구성할 것이다.
개념적으로는 1차원이 대상이고, 실제로는 2차원이 대상이다.
각 숫자의 이미지 데이터는 2차원 또는 3차원이다. (가로 x 세로(2차원) or 가로 x 세로 x 색깔 픽셀값(3차원))
=> 이에따라 이미지는 2차원 또는 3차원으로, 1차원이 아니다. 그런데 우리가 만드는 뉴런 네트워크 model 은 1차원만 받을 수 있어서 문제가 발생한다. 이 문제는 reshape 해서 해결 가능하다. 즉 3차원 값을 일렬로 아래처럼 1차원 공간에 쫙 펴준다. 1차원으로 쫙 펴주면 784개의 픽셀 데이터로 구성된다.
맨처음에 인풋으로 784개가 들어온다. (직전에 이미지 데이터를 3차원에서 1차원으로 쫙 펴줬으므로)
이를 100개의 뉴런으로 연결하는 100개의 뉴런으로 구성된 layer 를 만든다.
이떄 두 layer 간에는 가능한 모든 경우에 대해 Weight로 연결이 되어있다.
이떄 총 연결개수는 784 x 100 = 78400개이다. 즉 78400개의 Weight 가 필요하다.
실습 환경은 아래와 같이 구축했다.
아까봤던 숫자 글씨체에 대한 dataset 에 대한 뉴런 네트워크를 코드로 아래처럼 구현해봤다.
dataset 을 불러오는데, 아래와 같은 방식으로 코드를 짜면 불러와진다는 것만 간단히 짚고 넘어가자.
=> dataset 에 대해 쪼갠 데이터 덩어리를 보고(들고와서) update 하고, 그 다음 덩어리보고 update 하는 것 과정을 반복하면서 학습을 진행한다!
for문 조지면서 학습시키면 된다.
아까와 다른점은, loss 를 define 해줬다. (아까는 바로 계산했었음)
mini betch 방식으로 데이터를 쪼개서 학습하기 때문에, for문이 가 2개가 돈다.(2중 for문) 쪼갠 데이터 안에서 각 데이터를 보는 for문이 추가된 것이다.
cost = critertion(output, y_train) : loss 계산
avg_cost : loss value 를 관리하기 위해 필요한 변수
=> argmax 를 사용하고나면 몇번쨰 index 에 들어있는 값이 가장 큰지 확인할 수 있다. 즉 각 숫자 클래스에 대해 나오는 예측값(= 각 숫자 클래스에 대에 들어있는 확률값) 중에 가장 큰 확률값을 확인할 수 있다.
Flatten : 어떤 차원의 tensor 가 들어오던지간에 모두 1차원으로 쭉 평평하게 펴주는것 (인풋이 28x28 2차원인데, 1차원 784 크기로 쭉 펴주는것)
Dense : nn.Linear 와 동일한 기능. 인풋 개수는 명시할 필요없이, 아웃풋 개수만 명시해주면 연결해준다.