00 자연어 처리 (natural language processing)

자연어란 우리가 일상생활에서 말하는 말을 의미한다.

자연어처리는 우리의 말을 컴퓨터에게 전달하여 처리시키는 작업이라고 할 수 있다.

 

이를 위해서 자연어를 컴퓨터가 알아들을 수 있는 말로 바꿔 주어야 한다,

텍스트 전처리 작업에는 크게 토큰화 / 정제 / 정규화가 있다고한다.

 

01-01 토큰화(Tokenization)

자연어처리 모델은 "코퍼스"라는 데이터셋을 바탕으로 만들어지는데, 이 코퍼스에서 "토큰"이라고 불리는 단위로 

나누는 작업을 "토큰화"라고 한다.

일반적으로 토큰은 '의미있는 단위"로 정의한다고 생각하면 된다.

 

영어로 예를 들어 Don't be shy. Mr.San is  expecting a lot from you.라는 문장을 토큰화 한다면,

XXXXX, be , shy , Mr , San , is , expecting , a , lot , from , you 이렇게 될 수 있다.

영어권 언어는 한국과 달리 띄어쓰기만으로 쉽게 토큰화 작업을 수행할 수 있다.

 

하지만 소유격을 나타내는 ' (apostrophe)는 어떻게 토큰화 해야할까?

위에 Don't을 예시로 든다면 다양한 방식이 있다.
Dont

Do, n't 

Don, 't 

word_tokenize 모듈을 통해서 어퍼스트로피를 어떻게 처리하는지 보자.

 

Don't라는 코퍼스를 "Do" 와 "n't"로 구분한 것을 볼 수 있다. 구두점과 공백과 달리 단순 제거하게 되면 올바른 토큰화 작업이 힘들어 질 수 있다.

 

그렇다면 점과 공백은 어떤가?

마찬가지로 고려할 사항들이 있다.

Ph.D 처럼 점 자체가 토큰이 될 수도 있고, 문장에서의 마침표로써의 점은 문장의 경계를 파악하는데 도움이 되기 때문에 

점을 제거하지 않을 수 있다.

 

공백의 경우도 마찬가지이다.

공백이 포함된 단어가 존재할 수 있기 때문이다.New York, rock n roll..)

 

- 문장 토큰화

-> 한 문장씩 구분해야함.

-> 어떠한 기준으로 구분할 것인가?

명확한 기준은 ?나 ! 같은 비교적 명확한 구분자(boundary)가 존재한다.

구두점의 경우는 명확하지 않을 수 있다.

위에서 언급한 Ph.D나 Mr.San.. 등 문장의 끝에서만 등장하지 않을 수 있기 때문이다.

 

- 한국어 토큰화의 어려움

영어는 보편적으로 New York과 같은 합성어나 he's와 같은 예외처리만 한다면 띄어쓰기만 해도 토큰화가 쉽다.

이에 반해 한국어는 띄어쓰기만으로는 토큰화가 힘들다.

 

한국어에서 띄어쓰기 단위가 되는 단위를 "어절"이라고 하는데, 어절 토큰화는 NLP에서 지양하고 있다고 한다.

(어절 토큰화 != 단어 토큰화  때문)

 

이 이유는 근본적으로 한국어가 영어와 다른 형태를 가지는 교착어이기 때문이다. (조사, 어미 등을 붙여서 말을 만드는 것)

교착어는 첨가어라고도 하며, "첨가"라는 말 그대로 어근에 접사가 붙어서 의미가 변화하는 형태의 언어를 말한다.

 

예시로 "그"라는 어근에 다양한 조사가 붙어서 그는, 그가, 그에게, 그또한 등등 다양한 의미로 바뀌기 때문에 토큰화가 어렵다. 영어의 경우는 "그" "에게" 처럼 분리가 되기 때문에 한국어가 어렵다고 하는 것이다.

 

한국어 토큰화의 핵심은 "형태소" ( Morpheme) 이다.

형태소는 "뜻을 가진 가장 작은 말의 단위"이며, 두 가지 형태소로 이루어져 있다. "자립 형태소"와 "의존 형태소"이다.

 

Ex) 에디가 책을 읽었다.

자립 형태소: 에디, 책  (어떠한 접사, 어미, 조사와 관계없이 자립하여 쓸 수 있는 형태소)

의존 향태소: -가, -을, 읽, -었, -다.  (다른 형태소와 결합하여 사용되는 형태소)

 

따라서 한국어의 올바른 토큰화를 하기 위해서는 어절 토큰화가 아닌 위의 분리와 같은 형태소 토큰화를 진행해야 한다.!!

 

- 품사 태깅

단어는 품사에 따라 의미가 달리지기도 한다.

fly -> 동사 : 날다, 명사: 파리

못 -> 명사: 얇고 긴거, 부사 : 부정의 의미

따라서 단어를 올바르게 파악하기 위해서는 단어가 어떠한 품사로 쓰였는지에 따라 구분해놓아야 하는데, 

이를 품사 태깅이라고 한다.

 

NLTK에서는 pos_tag라는 기준을 사용하여 품사를 태깅한다.

VBP - 동사

RB - 부사

.......

 

한국어처리는 대표적으로 KoNLPy(코엔엘파이) 패키지에서, 형태소 분석기로 Okt(Open Korea text)를 이용한다.

 

Yolo( You Only Look Once )

-CNN을 기반으로 하는 실시간 객체 detection 모델.

 

Yolo는 Object Detection에 쓰이는 모델로써, 그 밖에도 segmentation, pose estimation? 등이 있다고 한다.

 

Object Detection은 이미지 내에서 object가 있다고 판단되면 각 객체의 위치와 클래스 정보를 알려주는 역할을 한다.

 

 

Yolo 모델을 공부하기에 앞서,

객체 Detector는 크게 2가지로 나뉘는데, 바로 1-stage Detector와 2-stage Detector이다.

 

1-stage Detector의 경우 Regional proposal과 Classification이 동시에 이루어진다!

2-stage Detector의 경우 Regional proposal과 Classification이 순차적으로 이루어진다!

 

먼저 Regional proposal과 Classification에 대해서 알아야하는데,

Regional proposal은 직역하면 "지역 제안"이며 얼핏 드는 생각은 이미지 중에서 검출할 영역을 탐색하는 것?이다.

정확하게는 이미지에서 유사한 색,텍스쳐를 가지고 있는 부분들을 보고 어떠한 객체가 있을 것이라 추정하는 영역을 찾는 것이다. 또한 이방식은 class는 고려하지 않고, object가 있을것 같다 생각하면 모두 잡아낸다는 특징이 있어, 매우 빠르지만 정확도는 비교적 낮다고 평가되는 방식이다.

Regional proposal 방식에는 대표적으로 두 가지가 있으며, 이름만 알고 가자.

1. Sliding Window

2. Selective Search

 

따라서 2-stage Detector는 순차적으로 진행된다고 하였으며, 아래 그림과 같다.

2-stage Detector을 사용하는 모델로는 R-CNN등 이 있다.

 

 

1-stage Detector는  Regional proposal과 Classification이 동시 이루어진다고 하였다.

이것이 Yolo 모델이며 아래 그림과 같다.

YOlo에서는 Anchor Box라는 것을 통하여 객체를 detection하는데, 

Anchor Box란 특정 값이 이미 정의된 경계 상자(bounding box)를 의미한다. (v3모델의 경우 3개의 앵커를 가진다고 한다.)

또한 앵커 박스는 K-means에 의한 데이터로부터 생성되며, 학습을통해 처음 앵커의 위치와 크기를 조금씩 조정해가며 객체를 탐지한다고 한다.

 

이 개념을 통해서 2-stage-Detector에서 쓰이는 Sliding window를 사용할 필요가 없게 된다.

Sliding Window는 모든 잠재적 위치에서 별도의 예측을 해야하는 계산을 해야 하는데,

Anchor box를 통해 한 번에 처리할 수 있기 때문이다.

 

Anchor box가 어떻게 사용되는지는 아래 순서와 같다.

  1. 이미지 주위에 수천 개의 후보 앵커 박스 형성
  2. 앵커 상자에 대해 해당 상자에서 후보 상자로 약간의 오프셋을 예측
  3. 측 예를 기반으로 손실 함수 계산
  4. 어진 오프셋 상자가 실제 개체와 겹칠 확률 계산
  5. 당 확률이 0.5보다 크면 예측을 손실 함수로 인수
  6. 측된 상자에 보상 및 페널티를 부여하여 모델을 실제 객체만 현지화하도록 천천히 당긴다.

이것이 모델을 가볍게 훈련했을 때 예측된 상자가 곳곳에 나타나는 이유라고 한다.

 

 

출처: https://wikidocs.net/173914

 

C_3.01 Anchor Boxes

2. **Anchor Boxes** 3. **Making Predictions** -. 3.1 Center Coordinates -. 3.2 Dimensions of …

wikidocs.net

https://velog.io/@qtly_u/Object-Detection-Architecture-1-or-2-stage-detector-%EC%B0%A8%EC%9D%B4

 

velog

 

velog.io

https://dotiromoook.tistory.com/24

베이지안 최적화 

- 용도 : 하이퍼파라미터 최적해 찾기 ( 엑셀 해찾기 기능)

베이지안 최적화를 보기 전, 파라미터와 하이퍼 파라미터를 다시 생각해보자.

 

 

파라미터는 데이터를 통해 구해지며, 모델 내부적으로 결정되는 값이다.

예를 들면 , 한 클래스에 속해 있는 학생들의 키에 대한 정규분포를 그린다고 해보자. 정규분포를 그리면 평균(μ)과 표준편차(σ) 값이 구해지는데, 여기서 평균과 표준편차를 파라미터(parameter)라고 한다.

 

하이퍼 파라미터는 모델링할 때 사용자가 직접 세팅해주는 값을 뜻한다.

하이퍼 파라미터는 정해진 최적의 값이 없다. 휴리스틱한 방법이나 경험 법칙(rules of thumb)에 의해 결정하는 경우가 많다.

변수(입력 데이터)가 5개 있다고 가정했을 때, 변수1의 값을 설정해놓고, 변수 2, 3,4...이렇게 찾는것이 아니다.

변수 1의 최적값은 변수 2의 값이 바뀌면 또 바뀐다. 따라서 변수들을 동시에 바꿔가며 최적의 값을 찾는 것이다.

 

이렇게 최적값을 찾는것은 매우 복잡하고 시간이 오래 걸리는 작업이므로 해결하기 위한 방법들이 있다.

 

1. 그리드 서치

그리드 서치는 하이퍼 파라미터의 최적값을 교차 검증을 통해 찾아준다. (그리드 서치를 사용하면 cross_validate를 따로 호출 할 필요가 없다.)

그리드 서치에 대한 자세한 설명은 참조 : https://woghkszhf.tistory.com/10

 

5 - 2 교차 검증과 그리드 서치

여태까지 배웠던 최근접 이웃, 로지스틱 회귀, SGD, 트리 등 다양한 머신러닝 모델들이 있었지만, 모든 모델들은 결국 실전에 투입시키기 위해 사용한다. 따라서 성능을 높이기 위해 반복적으로

woghkszhf.tistory.com

  • 즉, 모든 parameter의 경우의 수에 대해 cross-validation 결과가 가장 좋은 parameter를 고르는 방법이다.
  • 전체 탐색 대상 구간을 어떻게 설정할지, 간격은 어떻게 설정할지 등을 결정하는 데 있어 여전히 사람의 손이 필요하나
  • 앞선 Manual Search와 비교하면 좀 더 균등하고 전역적인 탐색이 가능하다는 장점이 있다.
  • 하지만 탐색하고자하는 hyperparameter의 개수를 한 번에 여러 종류로 가져갈수록, 전체 탐색 시간이 기하급수적으로 증가한다는 단점이 있다.

2. 랜덤 서치

  • Random Search는 Grid Search와 큰 맥락은 유사하나
  • 탐색 대상 구간 내의 후보 hyperparameter 값들을 랜덤 샘플링을 통해 선정한다는 점이 다르다.
  • Random Search는 Grid Search에 비해 불필요한 반복 수행 횟수를 대폭 줄이면서, 동시에 정해진 간격(grid) 사이에 위치한 값들에 대해서도 확률적으로 탐색이 가능하므로, 최적 값을 더 빨리 찾을 수 있는 것으로 알려져 있다.
  • 즉, 랜덤서치는 모든 grid를 전부 찾는 대신, 랜덤하게 일부의 파라미터 들만 관측한 후, 그 중에서 가장 좋은 파라미터를 고른다.
  • 그리드 서치는 중요한/ 안중요한 파라미터들을 동일하게 관측해야하기 때문에, 정작 중요한 파라미터를 다양하게 시도해볼 수 있는 기회가 적지만
  • 랜덤서치는 grid로 제한되지 않기 때문에 확률적으로 중요 변수들을 더 살펴볼 수 있는 기회를 받게 됩니다.
  • 그럼에도 불구하고, 랜덤 서치에서도 '여전히 약간의 불필요한 탐색을 반복하는 것 같다’는 느낌을 지우기 어려우실 것이라고 생각합니다.
  • 왜냐하면 그리드 서치와 랜덤 서치 모두, 바로 다음 번 시도할 후보 hyperparameter 값을 선정하는 과정에서, 이전까지의 조사 과정에서 얻어진 하이퍼파라미터 값들의 성능 결과에 대한 ‘사전 지식’이 전혀 반영되어 있지 않기 때문입니다.
  • 매 회 새로운 하이퍼 파라미터 값에 대한 조사를 수행할 시 ‘사전 지식’을 충분히 반영하면서, 동시에 전체적인 탐색 과정을 체계적으로 수행할 수 있는 방법론으로, 베이지안 옵티마이저를 들 수 있습니다.

3. 베이지안 옵티마이저

  • Bayesian Optimization 은 어느 입력값(x)를 받는 미지의 목적 함수(f(x))를 상정하여,
  • 해당 함숫값(f(x))을 최대로 만드는 최적해를 찾는 것을 목적으로 합니다.
    • 즉, 목적 함수(탐색대상함수)와 하이퍼파라미터 쌍(pair)을 대상으로 Surrogate Model(대체 모델) 을 만들고,
    • 순차적으로 하이퍼 파라미터를 업데이트해 가면서 평가를 통해 최적의 하이퍼파라미터 조합을 탐색합니다.
    • 이 때의 목점 함수 black-box function 이라고 합니다.
  • Bayesian Optimization 에는 두 가지 필수 요소가 존재합니다.
    • 먼저 Surrogate Model 은, 현재까지 조사된 입력값-함숫결과값 점들 (x1, f(x1)),...,(xt, f(xt)) 을 바탕으로, 미지의 목적 함수의 형태에 대한 확률적인 추정을 수행하는 모델을 지칭합니다.
    • 그리고 Acquisition Function 은, 목적 함수에 대한 현재까지의 확률적 추정 결과를 바탕으로, ‘최적 입력값을 찾는 데 있어 가장 유용할 만한’ 다음 입력값 후보를 추천해 주는 함수를 지칭합니다.

Bayesian Optimization 수행 과정

  • 자세한 수행 과정
    1. 입력값, 목적 함수 및 그 외 설정값들을 정의한다.
      • 입력값 x : 여러가지 hyperparameter
      • 목적 함수 f(x) : 설정한 입력값을 적용해 학습한, 딥러닝 모델의 성능 결과 수치(e.g. 정확도)
      • 입력값 x 의 탐색 대상 구간 : (a,b)
      • 입력값-함숫결과값 점들의 갯수 : n
      • 조사할 입력값-함숫결과값 점들의 갯수 : N
    2. 설정한 탐색 대상 구간 (a,b) 내에서 처음 n 개의 입력값들을 랜덤하게 샘플링하여 선택한다.
    3. 선택한 n 개의 입력값 x1, x2, ..., xn 을 각각 모델의 하이퍼 파라미터로 설정하여 딥러닝 모델을 학습한 뒤, 학습이 완료된 모델의 성능 결과 수치를 계산한다.
      • 이들을 각각 함숫결과값 f(x1), f(x2), ..., f(xn) 으로 간주한다.
    4. 입력값-함숫결과값 점들의 모음 (x1, f(x1)), (x2, f(x2)), ..., (xn, f(xn)) 에 대하여 Surrogate Model(대체 모델) 로 확률적 추정을 수행합니다.
    5. 조사된 입력값-함숫결과값 점들이 총 N 개에 도달할 때까지, 아래의 과정을 반복적으로 수행한다.
      • 기존 입력값-함숫결과값 점들의 모음 (x1, f(x1)),(x2, f(x2)), ..., (xt, f(xt)) 에 대한 Surrogate Model (대체 모델)의 확률적 추정 결과를 바탕으로, 입력값 구간 (a,b) 내에서의 EI 의 값을 계산하고, 그 값이 가장 큰 점을 다음 입력값 후보 x1 로 선정한다.
      • 다음 입력값 후보 x1 를 hyperparameter 로 설정하여 딥러닝 모델을 학습한 뒤, 학습이 완료된 모델의 성능 결과 수치를 계산하고, 이를 f(x1) 값으로 간주한다.
      • 새로운 점 (x2, f(x2)) 을 기존 입력값-함숫결과값 점들의 모음에 추가하고, 갱신된 점들의 모음에 대하여 Surrogate Model 로 확률적 추정을 다시 수행한다.
    6. 총 N 개의 입력값-함숫결과값 점들에 대하여 확률적으로 추정된 목적 함수 결과물을 바탕으로, 평균 함수 μ(x) 을 최대로 만드는 최적해를 최종 선택합니다. 추후 해당값을 하이퍼파라미터로 사용하여 딥러닝 모델을 학습하면, 일반화 성능이 극대화된 모델을 얻을 수 있다.

LSTM = Long Short-Term Memory

 

LSTM을 이해하기 앞서, RNN을 복습해보자.

 

RNN이란 Recurrence Neural Network라는 인공 신경망으로써, 자연어처리에 용이한 딥러닝 기법 중 하나이다.

recursive한 말 그대로 과거의 데이터를 누적하여 학습에 사용함으로써, 언어처리, 문장,맥락 등을 이해하기 위해서다.

RNN은 따라서 음성인식, 언어 모델링, 번역 등 다양한 분야에서 쓰이고 있다.

이 RNN의 핵심이 바로 LSTM이다.

 

LSTM은 RNN의 특별한 한 종류로, 특정 상황에서는 RNN보다 훨씬 효율적으로 해결할 수 있다.

 

RNN / LSTM 의 특징은 아래의 예시를 들어 설명할 수 있다.

 

1) 구름은 xx에 있다.  에서 xx를 예측하는것은 쉽다. 

하지만

2) 나는 프랑스에서 자랐다. 따라서 나는 자연스럽게 축구를 좋아한다. 그리고 나는 xx어를 유창하게 할 수 있다.

위 2번의 답을 예측하려면 정답에 필요한 정보를 얻기 위해 훨씬 앞 혹은 뒤에서 정보를 찾아야 한다.

따라서  정보를 얻기 위한 시간격차가 커지게 된다.

RNN은 안타깝게도 격차가 늘어날 수록 효율성이 매우 떨어진다고 한다.

 

GPT에 물어보니 기울기 소실, 기울기 문제 폭발 두 가지 때문이라고 한다.

LSTM은 RNN의 긴 시간격차로 발생하는 문제를 보완하기 위해 생겨난 아키텍쳐라고 한다.

 

이제 본격적으로 LSTM을 알아보자.

앞서 말했듯이, LSTM은 RNN에 비해 장기적인 문제를 해결하는데 탁월하다고 했다.

 

먼저 RNN의 구조와 LSTM의 구조를 그림을 통해 살펴보자.

< RNN > 

< LSTM > 

 

 

LSTM의 핵심

LSTM의 핵심 아이디어는 cell state이다. 아래에서 수평으로 그어진 윗 선을 말한다.cell state= 흐르는 물이라고 생각

LSTM은 cell state에 뭔가를 더하거나 없앨 수 있는 능력이 있는데,

이 능력은 gate라고 불리는 구조에 의해서 조심스럽게 제어된다.

 

Gate는 정보가 전달될 수 있는 추가적인 방법으로, sigmoid layer와 pointwise 곱셈으로 이루어져 있다.

 

LSTM의 첫 단계로는 cell state로부터 어떤 정보를 버릴 것인지를 정하는 것으로, sigmoid layer에 의해 결정된다.

 

그래서 이 단계의 gate를 "forget gate layer"라고 부른다. ( = 망각층 )

 

 

이 단계에서는 \(h_{t-1}\)과 \(x_t\)를 받아서 0과 1 사이의 값을 \(C_{t-1}\)에 보내준다. 그 값이 1이면 "모든 정보를 보존해라"가 되고, 0이면 "죄다 갖다버려라"가 된다.

 

아까 얘기했던 이전 단어들을 바탕으로 다음 단어를 예측하는 언어 모델 문제로 돌아가보겠다. 여기서 cell state는 현재 주어의 성별 정보를 가지고 있을 수도 있어서 그 성별에 맞는 대명사가 사용되도록 준비하고 있을 수도 있을 것이다. 그런데 새로운 주어가 왔을 때, 우리는 기존 주어의 성별 정보를 생각하고 싶지 않을 것이다.

 

< 망각층 >

 

다음 단계는 앞으로 들어오는 새로운 정보 중 어떤 것을 cell state에 저장할 것인지를 정한다.

먼저, "input gate layer"라고 불리는 sigmoid layer가 어떤 값을 업데이트할 지 정한다.

그 다음에 tanh layer가 새로운 후보 값들인 \(\tilde{C}_t\) 라는 vector를 만들고, cell state에 더할 준비를 한다.

이렇게 두 단계에서 나온 정보를 합쳐서 state를 업데이트할 재료를 만들게 된다.

 

다시 언어 모델의 예제에서, 기존 주어의 성별을 잊어버리기로 했고, 그 대신 새로운 주어의 성별 정보를 cell state에 더하고 싶을 것이다.

 

새 정보 유입 -> < input gate >

이제 과거 state인 \(C_{t-1}\)를 업데이트해서 새로운 cell state인 \(C_t\)를 만들 것이다. 이미 이전 단계에서 어떤 값을 얼마나 업데이트해야 할 지 다 정해놨으므로 여기서는 그 일을 실천만 하면 된다.

LSTM의 핵심

cell state가 존재하고 RNN과 달리 한 cell마다 네 개의 layer가 존재.

망각층과 입력층을 통해 cell을 계속 업데이트해가며 학습을 진행하는 메카니즘이다.

 

 

 

RNN과 비슷하다는 느낌이기 때문에 당연히 자연어처리에 특화되어 있고,

장기 데이터 처리, 학습, 예측에도 용이하기 때문에 시계열 데이터에도 유용하게 쓰인다고 한다.

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

https://dgkim5360.tistory.com/entry/understanding-long-short-term-memory-lstm-kr

'부록' 카테고리의 다른 글

Resnet 논문 리뷰  (0) 2024.04.22
Style GAN & Style GAN2  (1) 2024.04.07
[Modeling] XGB Classifier + 그리드서치 / 베이지안 옵티마이저  (0) 2023.03.18
[딥러닝] 옵티마이저 [퍼온 글]  (0) 2023.03.06
Bayesian Optimization  (2) 2023.02.21

7.3 풀링 계층

풀링은 세로·가로 방향의 공간을 줄이는 연산이다. (패딩 : 주변 채우기 스트라이드: 도장 찍는 간격)

아래 그림을 통해 보자.

위 그림과 같이 2x2 영역을 원소 하나로 집약하여 공간 크기를 줄이는 것이 풀링이다.

위 그림은 최대 풀링 (max pooling)이고, 평균 풀링 (average pooling)도 존재한다. +(최소, 스토캐스틱, 가중치 등)

7.3.1 풀링 계층의 특징

- 학습해야 할 매개변수가 없음

풀링 계층은 합성곱 계층과 달리 학습해야 할 매개변수가 존재하지 않는다. 풀링은 대상 영역에서 최대값이나 평균을 취하는 명확한 처리이므로 특별히 학습할 것이 없다는게 특징이다.

 

- 채널 수가 변하지 않음

풀링 연산은 입력 데이터의 채널 수 그대로 출력 데이터로 내보낸다. 

위 그림처럼 채널마다 독립적으로 계산하기 때문

 

- 입력의 변화의 영향을 적게 받음 (강건함)

입력 데이터가 변해도 풀링의 결과는 잘 변하지 않는다.\

7.4 합성곱 / 풀링 계층 구현

7.4.1 4차원 배열

 앞에서 설명한대로 CNN에서 계층 사이를 흐르는 데이터는 4차원 이다.

 

이제 파이썬으로 구현해보자.

먼저 랜덤으로 데이터를 만들어보자.

x = np.random.rand(10,1,28,28)
x.shape

(10, 1, 28, 28)

여기에서 첫 번째 데이터에 접근하려면 x[0]이라고 하면된다. (두 번째 x[1]...)

x[0].shape # (1, 28, 28)
x[1].shape # (1, 28, 28)

7.4.2 im2col로 데이터 전개하기

합성곱 연산을 곧이곧대로 for을 쓰려면 너무 번거롭기 때문에, im2col을 이용하자.

 

im2col은 입력 데이터를 필터링 (가중치 계산)하기 좋게 전개해주는 함수이다. (펼쳐주는 함수)

위 그림과 같이, 3차원 입력 데이터에 im2col을 적용하면 2차원 행렬로 변하는 것을 볼 수 있다.

 

im2col은 필터링 하기 좋게 입력 데이터를 전개한다.

구체적으로는 아래 그림과 같이, 입력 데이터에서 필터를 적용하는 영역(3차원 블록)을 한 줄로 늘어놓는다.

 

이 전개를 필터를 적용하는 모든 영역에서 수행하는 것이 im2col이다.

 

이를 토대로 합성곱 연산의 필터 처리 상세과정을 그림으로 그려보면 아래와 같다.

7.4.3 합성곱 계층 구현하기

 

CNN ( convolutional neural network)은 이미지 인식과 음성 인식 등 다양한 곳에서 사용된다.

convolutional = 나선형이라는 뜻이 먼저 나오지만, 딥러닝에서 CNN은 합성곱이라는 의미로 쓰인다.

 

CNN의 핵심 키워드는 다음과 같다.

  • Convolution(합성곱)
  • 채널(Channel)
  • 필터(Filter)
  • 커널(Kernel)
  • 스트라이드(Strid)
  • 패딩(Padding)
  • 피처 맵(Feature Map)
  • 액티베이션 맵(Activation Map)
  • 풀링(Pooling) 레이어

7.1 전체 구조

CNN의 네트워크 구조를 살펴보면 크게 합성곱 계층과 풀링 계층으로 되어 있다. 지금까지 본 신경망은 인접하는 계층의 모든 뉴런과 결합되어 있다. 이를 완전연결이라고 하며, 

 

완전 연결된 계층을 Affine 계층이라 했었다.

Affine계층을 사용한 5층 완전연결 신경망은 아래 그림과 같다.

Affine-ReLu 조합의 층이 4개, 마지막 출력층에서는 Softmax 활성화함수를 이용되는 것을 볼 수 있다.

 

다음은 CNN의 예시이다.   (일반적인 CNN구조라고한다.)

Affine-ReLu연결이    Conv-ReLu-Pooling 연결로 바뀌었다고 생각하면 편하다.

또 주의할 점은 출력에 가까운 층에는 Affine-ReLu 구성의 연결을 사용할 수 있다는 점이다!! 

 

7.2 합성곱 계층

CNN에서는 또한 합성곱 계층, 풀링 계층 의외  패딩 / 스트라이드의 개념이 새로 등장한다.

 

7.2.1 완전연결 (Affine) 계층의 문제점

7장 이전에 배웠던 Affine 계층에서는 인접하는 계층의 뉴런이 모두 연결되고 출력 수를 임의로 정할 수 있었다.

 

위 문장을 토대로 완전연결계층의 문제점은 무엇일까? 

바로 데이터의 형상 (차원)이 무시된다는 점이다.

 

입력 데이터가 이미지라고 한다면, 3차원 데이터일 것이다. (세로, 가로 ,채널(색))

하지만 이 데이터를 완전 계층에 입력하려면 1차원으로 평탄하게 해주어야 한다.

 

하지만 3차원 데이터는 그 자체로 중요한 정보를 갖고 있다.

예를 들면 공간적으로 가까운 픽셀은 값이 비슷하거나, RGB 각 채널은 서로 밀접하게 관련되어 있는 등 패턴이 숨겨져 있을 수 있다. 하지만 이를 1차원 데이터로 변환 시키는 순간 이 정보들을 살릴 수 없게 된다.

 

CNN에서는 합성곱 계층의 입출력 데이터를 특징맵 ( feature map ) 이라고도 한다.

따라서 합성곱 계층의 입력 데이터를 입력 특징맵 , 출력 데이터를 출력 특징맵 이라고 한다.

이 책에서는 입출력 데이터특징맵 을 같은 의미로 사용할 것이기 때문에 주의해서 해석을 하도록 한다.!!!

 

7.2.2 합성곱 연산

 합성곱 계층에서는 합성곱 연산을 처리하는데, 이는 이미지 처리에서 말하는 필터 연산에 해당한다. 

필터 연산에 대해 구체적인 예시를 통해 알아보자.

위 그림을 보면 입력 데이터에 필터(=커널, 도장) 를 적용하는 것을 볼 수 있다.

입력 데이터는 4x4, 필터는 3x3이므로 도장을 총 네번 찍어서 2x2(네 번의 도장 값)의 출력 데이터를 출력한다. 

 

자세한 곱 연산은 다음과 같다.

도장안의 각 값들은 가중치라고 볼 수 있고, 편향을 추가한다면 다음과 같은 수식을 얻을 수 있다.

7.2.3 패딩

합성곱 연산을 수행하기 전에 입력 데이터 주변 값을 특정 값 (or 0)으로 채우는 것을 패딩이라고 한다.

7.2.4 스트라이드

스트라이드 (stride)란 필터(=커널, 도장)을 적용하는 위치의 간격을 말한다.

 

간격이 2이면 도장이 2칸씩 이동하는 것을 말한다.

 

7.2.5 3차원 데이터의 합성곱 연산

위에서 알아본 합성곱 연산은 데이터가 2차원일 때의 경우이다.

3차원 데이터의 합성곱 연산에 대해 알아보자. 데이터는 이미지라고 가정한 후 (세로,가로,색상)을 다룬다고 하자.

주의사항: 입력 데이터의 채널 수와 필터의 채널 수가 같아야 한다.

                 필터 고정

7.2.6 블록으로 생각하기

3차원의 합성곱은 데이터와 필터를 직육면체 블록이라고 생각하면 쉽다.

블록은 아래 그림과 같이 3차원이다.

위 그림은 출력 데이터가 한 장의 특징 맵이다.

한 장의 특징 맵은 곧 채널이 1개인 특징 맵이라는 말과 같다.

만약 합성곱 연산의 출력으로 다수의 채널을 출력하려면 어떻게 해야 할까?

 

 

->필터를 1개보다 더 사용하는 것이다.

FN은 필터의 개수를 의미하고 이에 따라서 출력 맵 또한 FN개가 생성된다.

 


여기에 편향까지 고려한다면 최종 흐름은 아래 그림과 같다.

7.2.3 배치 처리

7장 이전의 신경망 처리에서는 입력 데이터를 한 덩어리로 묶어서 배치로 처리했었다.\

CNN에서도 배치 처리를 적용시켜보자.

 

배치 처리를 하기 위해 n개의 데이터를 묶고자 한다면 데이터 차원 수가 하나 더 늘어나서 총 4차원으로 바뀐다.

( 데이터 수, 채널 수, 높이, 너비)

 

26.2.3 ReLu를 사용할 때의 가중치 초기값

지난 포스팅에서 본 Xavier 초기값은 활성화 함수가 선형인 것을 전제로 이끈 결과이다.

시그모이드 함수와 tanh(하이퍼볼릭 탄젠트) 함수는 좌우 대칭이기 때문에 중앙 부근이 선형인 함수로 볼 수 있다.

 

따라서 Xavier초기값을 사용하기에 적절하다.

 

하지만 ReLu함수를 사용할 때는 ReLu에 특화된 초기값을 권장한다. (not  xavier)

He 초기값

He 초기값은 앞 계층의 노드가 n개일 때, 표준편차가  √(2 /n) 인 정규분포를 사용한다.

(Xavier 초기값: 표준편차  1 / √n ) 

키워드 : 최적화 옵티마이저 (4가지), 가중치 매개변수 초기값, 드롭아웃 등

 

6.1 매개변수 갱신

신경망 학습의 목적은 손실 함수의 값을 최소화 할 수 있는 매개변수를 찾는 것.  == 매개변수의 최적값을 찾는 것

 

정리하면 신경망 학습의 목적은 곧 최적화이다. ( optimization )

지금까지 최적화의 방법으로 매개변수의 기울기를 이용했었다. (미분)

 

매개변수의 기울기를 구해서 기울어진 방향으로 매개변수의 값을 갱신하는 일을 계속 반복해서 최적의 값을 찾아나갔다.

이를 SGD (확률적 경사 하강법)이라고 한다. 이 챕터에서는 SGD의외의 다양한 최적화 방법들을 소개한다.

 

우선 SGD를 포함한 다양한 옵티마이저를 그림으로 보고 넘어가자.

6.1.2 확률적 경사 하강법 (SGD)

SGD를 다시 복습해보자.

위 수식에서 W는 가중치, ∂L/∂WW에 대한 손실 함수의 기울기이다. n(에타)은 학습률이다. (보통 0.01이나 0.001 사용)

SGD클래스를 구현해보자.

class SGD:
	def __init__(self,lr=0.01)
    	self.lr = lr
    def update(self, params, grads):
    	for key in params.keys():
        	params[key] -= self.lr * grads[key]

SGD클래스를 이용하면 신경망 매개변수의 진행을 다음과 같이 수행할 수 있다.

network = TwoLayerNet(...)
optimizer = SGD()

for i in range(1000):
	...
    x_batch, t_batch = get_mini_batch(...) #미니배치
    grads = network.gradient(x_batch, t_batch)
    params = network.params
    optimizer.update(params, grads)
    ...

위 코드처럼 optimizer = '...'로 지정하여 SGD의외 기법도 호출하여 편하게 사용할 수 있다.

 

6.1.3 SGD의 단점

6.1.3 SGD의 단점

6.1.4 모멘텀 ( Momentum )

모멘텀이란 운동량을 뜻하는 말로, 물리와 관계가 있다. 또한 수식으로는 다음과 같다.

n(에타) = 학습, v = 속도(기울기를 따라 이동하는 속도라고 생각)

∂L/∂W = 손실함수의 기울기,   W = 가중치

av 항은 물체가 아무런 힘을 받지 않을 때 서서히 하강시키는 역할을 한다고 한다.

 

class Momentum:
	def __init__(self, lr=0.01, momentum = 0.9):
    self.lr = lr
    self.momentum = momentum
    self.v = None
   	
    def update(self, params, grads):
    	if self.v is None:
        	self.v = {}
            for key, val in params.items():
            self.v[key] = np.zeros_like(val)
        
        for key in params.keys():
        	self.v[key] = self.momentum * self.v[key] - self.lr*grads[key]
            params[key] += self.v[key]    # 인스턴스 변수 v는 물체의 속도

모멘텀을 생각할 때는 공이 경사면을 따라 굴러간다고 생각하면 이해하기 쉽다.

또한 SGD와 비교했을 때 위아래 흔들림이 적다.

6.1.5 Adagrad

신경망 학습에서는 학습률 값이 중요하다. 너무 크거나 작으면 학습이 효과적으로 이루어지지 않는다.

이 학습률을 올바르게 설정하기 위한 몇 가지 기술 中, 학습률 감소가 있다.

학습률 감소란 처음에는 크게 학습하다가 진행하면서 조금씩 학습률을 점차 줄여나가는 방법이다.

 

학습률 감소에 대한 구체적인 설명은 다음 블로그를 참고하자.

https://velog.io/@good159897/Learning-rate-Decay%EC%9D%98-%EC%A2%85%EB%A5%98

 

Learning rate Decay의 종류

Michigan 대학의 CS231n 강의를 보고 Learning rate Decay에 대해 정리를 했습니다.

velog.io

학습률을 서서히 낮추는 가장 간단한 방법은 "전체"의 학습률을 값을 일괄적으로 낮추는 것이다.

이를 더욱 발전시킨 것이 AdaGrad이다.

 

Adagrad는 전체에서 발전해 "각각"의 매개변수에 맞게 갱신해준다.

'적응적 학슙를'을 기반으로 한 옵티마이저이다.

 

Adagrad, RMSprop = 적응적 학습률 기반

momentum이라는 개념을 이용하여 gradient를 조절

 

어떻게 개별 매개변수마다 갱신을 해줄까? 자세히 알아보자.

위 수식에서 h는 기존 기울기값을 제곱하여 계속 더해준다. 

그리고 매개변수를 갱신할 때 1/ √h를 곱해서 학습률을 조정한다.!!

이 뜻은 매개변수 원소 중에서 많이 움직이는 원소는 학습률이 낮아진다는 뜻이라고 볼 수 있다.

 

(다시 말해 학습률 감소가 매개변수 원소마다 다르게 작용됨.)

 

손코딩

class AdaGrad:
    def __init__(self, lr=0.01):
        self.lr = lr
        self.h = None
        
    def update(self, params, grads):
        if self.h in None:
            for key, val in params.items():
                self.h[key] = np.zeros_like(val)
        
        for key in params.keys():
            self.h[key] += grads[key] * grads[key]
            parmas[key] -= self.lr * grads[key] / (np.sqrt(self.h[key]) + 1e-7)

주의) 마지막 줄에서 1e-7를 더해주는 부분. 1e-7은 self.h[key]에 0이 담겨 있어도 0으로 나누는 것을 막아줌.

Adagrad의 갱신경로를 그림으로 보자.

최소값을 향해 효율적인 움직임을 보여준다.!!

 

6.1.6 아담 ( Adam )

모멘텀은 공을 구르는 듯한 움직임을 보여준다. Adagrad는 원소 개별적으로 갱신해주었다.

이 둘을 합치면 Adam이라고 할 수 있다.

그림을 먼저 보자.

모멘텀과 비슷한 패턴이지만, 좌우 흔들림이 적은 것을 볼 수 있다.

이는 학습의 갱신 강도를 적응적으로 조정해서 얻는 benefit이라고 한다.

 

마지막으로 정리해서 봐보자.

무엇이 제일 좋다!! 라는 것은 데이터, 목적, 그 밖의 하이퍼 파라미터에 따라 다르기 때문에 정해진 것이 없다.

하지만 통상적으로 SGD보다 momentum,adam,adagrad 3개가 더 좋고, 일반적으로 Adam을 많이 쓴다고 한다.

 

6.2 가중치의 초기값

신경망 학습에서 가중치 초기값이 매우 중요하다고 한다.

2절에서는 권장 초기값을 배우고 실제로 학습이 잘 이루어지는지 확인해보자.

 

6.2.1 초기값을 0으로 하면?

이제부터 오버피팅을 억제해 범용 성능을 높이는 테크닉인 가중치 감소 기법을 소개해주겠다.

가중치 감소가중치 매개변수의 값이 작아지도록 학습하는 방법이다.

 

가중치 값을 작게 해서 오버피팅을 일어나지 않도록 하게 하는 것이다.

가중치 값을 작게 만들고 싶으면 초기값도 최대한 작은 값에서 시작하는 것이 정공법이기 때문에,,!

사실 지금까지 가중치 초기값은 0.01 * np.random.randn(10,100)처럼 정규분포에 생성된 값에 0.01배를 한 작은값을 사용

했었다.

 

그렇다면 가중치 초기값을 모두 0으로 설정하면 어떨까??

결론부터 말하자면, 아주 안좋은 생각이다.

실제로 가중치 초기값을 0으로 하면 학습이 올바로 이루어지지 않는다.

오차역전파법에서 모든 가중치의 값이 똑같이 갱신되기 때문이다.

 

예를 들어 2층 신경망에서 첫 번째, 두 번째 층의 가중치가 0이라고 가정해보자.

그럼 순전파 때는 입력층의 가중치가 0이기 때문에 두 번째 층의 뉴런에 모두 같은 값이 전달된다.

두 번째 층의 모든 뉴런에 같은 값이 입력된다는 것은 역전파  두 번 층의 가중치가 모두 똑같이 갱신된다는 말과 같다.

(곱셈 노드의 역전파를 기억해보자.)

그래서 가중치들을 같은 초기값에서 시작하고 갱신을 거쳐도 여전히 같은 값을 유지하는 것이다.

이는 가중치를 여러 개 갖는 의미를 사라지게 한다.

 

이 '가중치가 고르게 되어버리는 상황'을 막으려면 초기값을 무작위로 설정해야 한다.

 

6.2.2 은닉층의 활성화값 분포

은닉층의 활성화값(활성화 함수의 출력 데이터)의 분포를 관찰하면 중요한 정보를 얻을 수 있었다.

 

이번 절에서는 가중치의 초기값에 따라 은닉층 활성화 값들이 어떤 분포를 갖는지 시각화 해보려한다.

예시로 5층의 신경망을 가정, 시그모이드 함수 사용, 입력데이터 무작위로 선정

import numpy as np
import matplotlib.pyplot as plt

def sigmoid(x):
    return 1 / (1 + np.exp(-1))
    
x = np.random.randn(1000, 100) # 1000개의 데이터
node_num = 100                 # 각 은닉층의 노드(뉴런) 수
hidden_layer_size = 5          # 은닉층이 5개
activations = {}               # 이 곳에 활성화 결과(활성화값)를 저장

fro i in range(hidden_layer_size):
    if i != 0:
        x = activations[i-1]
        
    w = np.random.randn(node_num, node_num) * 1
    
    a = np.dot(x, w)
    z = sigmoid(a)
    activations[i] = z

층은 5개, 각 층의 뉴런은 100개씩이다.

활성화 값들의 분포를 그려보자.

# 히스토그램 그리기
for i, a in activations.items():
    plt.subplot(1, len(activations), i+1)
    plt.title(str(i+1) + '-layer')
    plt.hist(a.flatten(), 30, range=(0,1))
plt.show()

각 층의 활성화 값들이 0과 1에 치우쳐져 있다.

시그모이드 함수는 출력이 0또는 1에 가까워지면 미분값은 0에 수렴한다.

따라서 데이터가 0과 1에 치우쳐 분포하게 되면 역전파 기울기 값이 점점 작아지다가 사라진다.

이를 기울기 소실 현상 ( gradient vanishing )이라고 한다.

 

이제 표준편차를 0.01로 바꿔서 다시 그려보면 다음과 같은 그래프를 얻을 수 있다.

아까와 같은 기울기 소실 문제는 보이지 않지만, 모두 중간값에 몰빵되있는 것을 볼 수 있다.

이는 표현력 관점에서 문제라고 볼 수 있다.

 

Xavier 초기값

사비에르 초기값이란 사비에르가 작성한 논문에서 발췌한 것으로, 현재까지 딥러닝 프레임워크에서 많이 사용한다.

초기값 = 표준편차를 1 /√n 로 설정!   ( n = 노드 개수)

 

사비에르 초기값을 사용해서 분포를 보면 다음과 같다.

 

5.6 Affine / Softmax 계층 구현하기

5.6.1 Affine 계층 

Affine 변환 = 순전파에서 행렬,   

Affine 계층 = Affine 변환을 수행하는 처리

.신경망의 순전파에서는 가중치 총합의 신호를 계산하기 위해 행렬 곱을 사용했다 ( np.dot ) 

 

그리고 행렬 곱의 핵심은 원소 개수 (차원 개수)를 일치시키는 것이다.

 

예시를 통해 그림으로 이해해보자. (계산 그래프)

X,W,B가 있다고 하자. 각각 입력, 가중치, 편향이고  shape은 각각(2,) , (2,3), (3,)이다.

X와 W가 들어와 np.dot을 통해 행렬 곱 수행 -> x * W   -> ( x * W ) +B  = Y

 

이제 역전파를 알아보자.

아래 식이 도출되는것을 확인하자.

  WT는 T의 전치행렬을 말함. 

(전치행렬 = Wij 를 Wji로 바꾼것. 아래 그림 참조)

위 식을 통해서 계산 그래프의 역전파를 구해보자.

위 그림의 네모 박스 1,2를 유심히 살펴보자. (덧셈노드는 그대로 흐르고, 곱셈 노드는 교차하므로 !!)

X,W,B의 shape은 각각(2,) , (2,3), (3,)이다.

∂L/∂Y는 (3,) 인데 W인 (2,3)과 차원이 달라 곱셈이 되지 않는다. 

따라서 WT를 통해 (3,2)로 바꿔 곱셈을 수행할 수 있도록 변환 (+X도 마찬가지)

 

5.6.2 배치용 Affine 계층

지금까지 예시로 든것은 X (입력 데이터)가 하나일 때만 고려한 상황이였다.

하지만 현실에서는 하나일 확률이 매우 적으므로 실제 Affine 계층을 생각해보자.

 

입력 데이터가 N개일 경우이다.

5.6.3 Softmax-with-Loss 계층

마지막으로 배울 것은 출력층에서 사용하는 소프트맥스 함수이다.

  • 딥러닝에서는 학습과 추론 두 가지가 있다.
  • 일반적으로 추론일 때는 Softmax 계층(layer)을 사용하지 않는다. Softmax 계층 앞의 Affine 계층의 출력을 점수(score)라고 하는데, 딥러닝의 추론에서는 답을 하나만 예측하는 경우에는 가장 높은 점수만 알면 되므로 Softmax 계층이 필요없다. 반면, 딥러닝을 학습할 때는 Softmax 계층이 필요하다.

이제 소프트맥스 계층을 구현할텐데, 손실 함수인 엔트로피 오차도 포함하여 계산 그래프를 살펴보자.

너무 복잡하다. 간소화 해보자.

여기서는 3클래스 분류를 가정하고 이전 계층에서 3개의 입력(점수)를 받는다.

그림과 같이 Softmax 계층은 입력 a1,a2,a3를 정규화하여 y1,y2,y3를 출력함

Cross-entropy계층은 y1,y2,y3와 t1,t2,t3(정답)을 넘겨받고 손실 L를 출력함.

 

위 그림에서 역전파의 결과인 (y1-t1),(y2-t2),(y3-t3)를 보면 softmax계층의 출력 정답레이블의 차분임.

따라서 신경망에서의 역전파는 '출력 - 정답', 즉 오차를 출력한다고 확인할 수 있다.

파이썬으로Softmax-with-Loss를 구현해보자.

# Softmax-with-Loss 계층 구현
# common / layer.py
 
class SoftmaxWithLoss:
    def __init__(self):
        self.loss = None # 손실
        self.y = None # softmax의 출력
        self.t = None # 정답레이블 (원-핫 벡터)
 
    def forward(self, x, t):
        self.t = t
        self.y = softmax(x)
        self.loss = cross_entropy_error(self.y, self.t)
        
        return self.loss
 
    def backward(self, dout=1):
        batch_size = self.t.shape[0]
        if self.t.size == self.y.size: # 정답레이블이 원-핫 벡터일 경우
            dx = (self.y - self.t) / batch_size
        else:
            dx = self.y.copy()
            dx[np.arange(batch_size), self.t] -= 1
            dx = dx / batch_size
        
        return dx

5.7 오차역전파법 구현하기

5.7.1 신경망 학습의 전체 그림

 

이제 구현해보자!!

5.7.2 오차역전파를 사용한 신경망 구현

import sys, os
sys.path.append('/deep-learning-from-scratch')
import numpy as np
from common.layers import *
from common.gradient import numerical_gradient
from collections import OrderedDict
 
 
class TwoLayerNet:
 
    def __init__(self, input_size, hidden_size, output_size, weight_init_std = 0.01):
        # 가중치 초기화
        self.params = {}
        self.params['W1'] = weight_init_std * np.random.randn(input_size, hidden_size)
        self.params['b1'] = np.zeros(hidden_size)
        self.params['W2'] = weight_init_std * np.random.randn(hidden_size, output_size) 
        self.params['b2'] = np.zeros(output_size)
 
        # 계층 생성
        # OrderedDict = 순서가 있는 딕셔너리, 순서 기억
        # 순전파 때는 계층을 추가한 순서대로 / 역전파 때는 계층 반대 순서로 호출
        self.layers = OrderedDict()
        self.layers['Affine1'] = Affine(self.params['W1'], self.params['b1'])
        self.layers['Relu1'] = Relu()
        self.layers['Affine2'] = Affine(self.params['W2'], self.params['b2'])
 
        self.lastLayer = SoftmaxWithLoss()
        
    def predict(self, x):
        for layer in self.layers.values():
            x = layer.forward(x)
        
        return x
        
    # x: 입력데이터, t : 정답레이블
    def loss(self, x, t):
        y = self.predict(x)
        return self.lastLayer.forward(y, t)
    
    def accuracy(self, x, t):
        y = self.predict(x)
        y = np.argmax(y, axis=1)
        if t.ndim != 1 : t = np.argmax(t, axis=1)
        
        accuracy = np.sum(y == t) / float(x.shape[0])
        return accuracy
        
    # x: 입력데이터, t : 정답레이블
    def numerical_gradient(self, x, t):
        loss_W = lambda W: self.loss(x, t)
        
        grads = {}
        grads['W1'] = numerical_gradient(loss_W, self.params['W1'])
        grads['b1'] = numerical_gradient(loss_W, self.params['b1'])
        grads['W2'] = numerical_gradient(loss_W, self.params['W2'])
        grads['b2'] = numerical_gradient(loss_W, self.params['b2'])
        
        return grads
        
    def gradient(self, x, t):
        # forward, 순전파
        self.loss(x, t)
 
        # backward, 역전파
        dout = 1
        dout = self.lastLayer.backward(dout)
        
        layers = list(self.layers.values())
        layers.reverse()
        for layer in layers:
            dout = layer.backward(dout)
 
        # 결과 저장
        grads = {}
        grads['W1'], grads['b1'] = self.layers['Affine1'].dW, self.layers['Affine1'].db
        grads['W2'], grads['b2'] = self.layers['Affine2'].dW, self.layers['Affine2'].db
 
        return grads

아래 표를 참고해서 위 코드를 이해합시다.

또한 위 코드에서 OrderDict주의 ( 순서가 있는 딕셔너리 ) 이를 통해 순전파, 역전파를 쉽게 사

5.7.3 오차역전파법으로 구한 기울기 검증하기

  • 느린 수치 미분보다 오차역전파법을 사용한 해석적 방법이 효율적 계산 가능
  • 수치 미분은 오차역전파법을 정확히 구현했는지 확인위해 필요
  • 수치 미분은 구현하기 쉬우나, 오차역전파법은 구현 복잡해 실수가 있을 수 있음
  • 기울기 확인(gradient check) : 두 방식으로 구한 기울기가 일치(거의 같음)함을 확인 하는 작업
# 기울기 확인 (gradient check)
import sys, os
sys.path.append('/deep-learning-from-scratch')
import numpy as np
from dataset.mnist import load_mnist
from ch05.two_layer_net import TwoLayerNet
 
# 데이터 읽기
(x_train, t_train), (x_test, t_test) = load_mnist(normalize=True, one_hot_label=True)
 
network = TwoLayerNet(input_size=784, hidden_size=50, output_size=10)
 
x_batch = x_train[:3]
t_batch = t_train[:3]
 
grad_numerical = network.numerical_gradient(x_batch, t_batch) # 수치미분법
grad_backprop = network.gradient(x_batch, t_batch) # 오차역전파법
 
# 각 가중치 차이의 절댓값을 구한 후, 절댓값들의 평균 구함
for key in grad_numerical.keys():
    diff = np.average( np.abs(grad_backprop[key] - grad_numerical[key]) )
    print(key + ":" + str(diff))
    
>>>
W1:4.1821647831167817e-10
b1:2.534937764494963e-09
W2:5.183343681548899e-09

5.7.4 오차역전파법을 사용한 학습 구현하기

import sys, os
sys.path.append('/content/drive/MyDrive/deep-learning-from-scratch')
 
import numpy as np
from dataset.mnist import load_mnist
from ch05.two_layer_net import TwoLayerNet
 
# 데이터 읽기
(x_train, t_train), (x_test, t_test) = load_mnist(normalize=True, one_hot_label=True)
 
network = TwoLayerNet(input_size=784, hidden_size=50, output_size=10)
 
iters_num = 10000
train_size = x_train.shape[0]
batch_size = 100
learning_rate = 0.1
 
train_loss_list = []
train_acc_list = []
test_acc_list = []
 
iter_per_epoch = max(train_size / batch_size, 1)
 
for i in range(iters_num):
    batch_mask = np.random.choice(train_size, batch_size)
    x_batch = x_train[batch_mask]
    t_batch = t_train[batch_mask]
    
    # 오차역전파법으로 기울기 구함
    #grad = network.numerical_gradient(x_batch, t_batch) # 수치미분법
    grad = network.gradient(x_batch, t_batch)
    
    # 갱신
    for key in ('W1', 'b1', 'W2', 'b2'):
        network.params[key] -= learning_rate * grad[key]
    
    loss = network.loss(x_batch, t_batch)
    train_loss_list.append(loss)
    
    if i % iter_per_epoch == 0:
        train_acc = network.accuracy(x_train, t_train)
        test_acc = network.accuracy(x_test, t_test)
        train_acc_list.append(train_acc)
        test_acc_list.append(test_acc)
        print(train_acc, test_acc)
        
>>>
0.16111666666666666 0.1669
0.9040833333333333 0.9045
0.9236666666666666 0.9269
0.93625 0.9373
0.94525 0.944
0.9503833333333334 0.948
0.9547166666666667 0.951
0.9602166666666667 0.9569
0.9626333333333333 0.9588
0.9652166666666666 0.9598
0.9688 0.9619
0.9709833333333333 0.9641
0.9729 0.9653
0.9746166666666667 0.9667
0.97505 0.9663
0.97645 0.967
0.9784833333333334 0.9692

'밑바닥 딥러닝' 카테고리의 다른 글

Ch 6-2 학습 관련 기술들  (0) 2023.05.31
Ch 6-1 학습 관련 기술들  (0) 2023.05.29
Ch 5-1 오차 역전파 ( Error Backpropagation )  (0) 2023.05.27
Ch4-2 신경망 학습  (0) 2023.05.26
Ch4-1 신경망 학습  (0) 2023.05.17

+ Recent posts