앞서 설명했던 시소러스와 통계 기반 기법과 달리 이번에 설명할 추론 기반 기법은 신경망을 사용한다.

 

 

통계 기반 기법의 문제점

통계 기반 기법에 대해 간단히 설명하자면, 통계 기반 기법은 주변 단어의 빈도를 기초로 단어를 표현하는 방법이다. 구체적으로는 단어의 동시발생 행렬을 만들고 그 행렬에 특잇값 분해(SVD)를 적용하여 밀집벡터(단어의 분산 표현)를 얻었다. 하지만 이러한 방식은 실제 생활에서 사용하는 수많은 단어를 다루기에는 적합하지 않다. 예를 들어 어휘가 100만 개라면, 통계 기반 기법에서는 100만x100만이라는 거대 행렬을 만들기 때문이다. 이처럼 행렬이 매우 크기 때문에 특잇값 분해(SVD)를 사용해 차원을 축소하는 것은 사실상 불가능한 일이다.

 

 

 

 

추론 기반 기법

추론 기반 기법은 신경망을 사용하여 단어를 처리하는 기법으로 미니배치 학습을 하여 수많은 어휘를 효율적으로 학습한다. 추론 기반 기법과 통계 기반 기법의 차이점에 대해서는 아래의 표를 참고하길 바란다.

 

통계 기반 기법(배치 학습) 학습 데이터를 한꺼번에 처리한다.
/
어휘에 추가할 새 단어 발생시 단어 분산 표현 갱신을 위해 처음부터 다시 계산해야한다.
/
단어의 유사성 인코딩
추론 기반 기법(미니배치 학습) 학습 데이터의 일부를 사용하여 순차적으로 학습한다.
/
어휘에 추가할 새 단어 발생시 단어 분산 표현 갱신을 위해 매개변수만 다시 학습하면 되므로 기존에 학습한 내용을 해치지 않고 단어의 분산 표현을 효율적으로 갱신할 수 있다.
/
단어의 유사성 인코딩 및 단어 사이의 패턴 파악 가능
ex. king-man+woman=queen

 

 

통계 기반 기법은 단어의 맥락을 파악했다면, 추론 기반 기법은 맥락을 입력하면 사이에 나올 단어의 출현 확률을 출력하는 형태이다. 예를 들어 you ___ goodbye and I say hello. 라는 문장에서 ___ 에 들어갈 문장에 대한 확률을 추측하는 것이 바로 추론 기반 기법이다.

 

 

추론 기반 기법이란 위의 이미지에서의 모델이 맥락 정보를 입력받아 ___에 나올 수 있는 단어의 출현 확률을 출력하고, 우리는 말뭉치를 사용해 모델이 올바른 추측을 하도록 학습시키며 그 학습의 결과로 단어의 분산 표현을 얻는 기법을 뜻한다.

 

위의 모델은 신경망을 의미한다. 그럼 어떻게 해야 신경망이 you와 goodbye를 이해하며 받아들일 수 있을까? 이를 위해 우리는 input data인 단어들을 one-hot 벡터로 변환하여 입력한다.

 

 

one-hot 벡터는 위의 you, say, goodbye의 각각의 벡터들과 같이 본인들이 표현되는 위치만 1이고 나머지는 모두 0인 벡터를 의미한다. 이처럼 one-hot encoding을 하는 이유는 신경망의 입력층의 뉴런 수를 고정시키기 위해서이다. 이렇게 만들어진 각각의 벡터들은 기존에 우리가 배운 신경망의 학습 방법과 동일하게 가중치와의 연산을 통해 은닉층으로 넘어가게 되고 이 과정을 반복하며 유의미한 가중치(매개변수)를 얻어낼 수 있다.

 

 

 

 


 

 

Word2Vec

CBOW 모델

word2vec이라는 용어는 원래 프로그램이나 도구를 가리키는 데 사용됐다고 한다. 하지만 이 용어가 유명해지면서, 문맥에 따라 신경망 모델을 가리키는 경우도 볼 수 있다고 한다. 이 글에서의 word2vec은 신경망 모델을 가리키는 경우로 판단하길 바란다. CBOW 모델은 wrod2vec에서 제안하는 신경망으로 맥락으로부터 타깃을 추측하는 용도의 신경망이다. 여기서 타깃이란 중앙 단어이고 그 주변 단어들을 맥락이라고 표현한다. 즉 위의 예제에서 you goodbye는 맥락, ___에 들어갈 say가 타깃이라고 볼 수 있겠다.

 

 

CBOW 모델의 구조는 위의 이미지와 같은데 여기서 입력층이 2개인 이유는 맥락으로 고려할 단어를 2개로 정했기 때문이다. 만약 맥락에 포함시킬 단어를 N개로 지정하면 입력층도 N개가 된다.

 

아무튼 이 입력층 데이터는 은닉층을 거쳐 출력층으로 나오고, 두 입력층에서 은닉층으로의 변환은 똑같은 완전연결계층(가중치 : Win)이 처리한다. 또한 은닉층에서 출력층 뉴런으로의 변환은 다른 완전연결계층(가중치 : Wout)이 처리한다. 결론적으로 입력층과 은닉층 사이의 매개변수인 Win은 두개의 입력층이 같은 값을 공유한다는 이야기이며 입력층과 은닉층 사이의 가중치와 은닉층과 출력층 사이의 가중치는 서로 다른 값이라는 이야기이다.

 

은닉층의 뉴런은 입력층 전체의 평균이며, 출력층의 뉴런은 총 7개로, 뉴런 하나하나가 문장 각각의 단어에 대응한다. 또한 출력층 뉴런은 각 단어의 점수를 의미하며 이 점수가 Softmax 함수를 지나면 출현 확률로 나타내진다.

 

또한 입력층과 은닉층 사이의 매개변수인 Win은 단어의 분산 표현을 의미한다. Win의 크기는 위의 이미지를 참고하면 7x3의 행렬이며, 가중치의 각 행은 해당 단어의 분산 표현이다. 따라서 학습이 진행될수록 맥락에서 출현하는 단어를 잘 추측하는 방향으로 이 분산 표현들이 갱신될 것이다.

 

은닉층의 뉴런 수는 입력층의 뉴런 수보다 적어야하는데, 그 이유는 은닉층에는 단어 예측에 필요한 정보를 간결하게 담아 밀집벡터 표현을 얻기 위해서이다. 입력층과 매개변수(Win)의 연산 결과로 나온 은닉층(인코딩)은 인간이 이해할 수 없으나 이를 인간이 이해할 수 있는 표현으로 복원(디코딩)하게 되면 우리는 밀집벡터 표현을 얻을 수 있다.

 

설명한 내용 시각화

 

 

CBOW 모델이 학습하는 것은 매개변수이다. 이 매개변수들은 가중치에 단어의 출현 패턴을 파악한 벡터가 학습된다. CBOW 모델은 단어 출현 패턴을 사용하는 말뭉치에서 배우기 때문에 말뭉치가 다르면 학습 후 얻게 되는 단어의 분산 표현도 달라질 수 있다.

 

CBOW 모델은 결론적으로 다중 클래스를 분류하는 신경망으로, 학습에 사용되는 것들은 Softmax 함수와 Cross Entropy(교차 엔트로피) 오차이다. 여기서 Softmax 함수는 출력층의 결과인 점수를 확률로 변환하는 것이고, 그 확률과 정답 라벨의 오차(분류이기 때문에 Cross Entropy 사용)를 손실로 사용하여 매개변수를 학습한다.

 

 

 


 

 

 

CBOW모델 개선

Embedding(임베딩) 계층

CBOW 모델의 입력층인 맥락은 위에서 설명한것처럼 one-hot encoding된 형태일 수 있지만 이렇게 되면 단어의 개수가 많아질수록 연산량 또한 많아진다. 하지만 실질적으로 one-hot encoding된 입력층과 MatMul(Win) 계층의 행렬 곱은 단지 행렬의 특정 행을 추출하는 것 뿐이다.

 

 

따라서 one-hot encoding한 입력층과 MatMul 계층의 행렬 곱 연산은 사실상 필요하지 않다. 따라서 이와 같은 효과를 지닌, 즉 가중치 매개변수로부터 단어 ID에 해당하는 행(벡터)을 추출하는 계층인 Embedding(임베딩)계층을 사용한다. 결론적으로 Embedding 계층은 단어의 분산 표현을 저장하는 것이다. MatMul 계층을 Embedding 계층으로 전환하는 것은 메모리 사용량 감소와 계산량 감소의 효과를 불러일으킨다.

 

 

 

이진 분류

이진 분류는 은닉층 이후의 연산에 대한 병목 현상, 즉 다중 분류의 문제점을 효과적으로 해결하기 위한 방안이다. 은닉층 이후에서 계산이 오래 걸리는 곳은 은닉층의 뉴런과 가중치 행렬(Wout)의 곱과 Softmax 연산, 이 두곳이다. 이 두곳의 문제점을 해결하는 방법은 이진 분류이다. 우리는 은닉층 이후의 병목 현상이 나타나는 이유로, 다중 분류의 문제점을 파악할 수 있다. 다중 분류는 수많은 어휘 각각마다의 확률 및 계산 결과를 알아야하기 때문에 그만큼 연산량이 많아지지만, 이를 이진 분류 문제로 바꿔 Yes/No의 대답으로 나타낼 수 있게끔 하면 계산량이 효과적으로 줄어들 수 있다. 예를 들어 맥락이 you와 goodbye일 때, 타깃 단어가 say냐는 물음에 우리는 say가 나올 확률을 출력하면 된다.

 

 

결론적으로 앞에서 설명했던 방식의 출력층은 모든 단어를 대상으로 계산을 수행했으나, 타깃으로 예상되는 단어에 주목하여 그 점수, 확률만을 계산하는 것이 차이이다.

 

우리는 CNN 신경망에 대해 자세히 공부하면서 이진 분류일 경우 출력층에서 Sigmoid 함수를, 다중 분류일 경우에는 Softmax 함수를 사용한다는 것을 알 수 있었다.(손실 함수는 둘 다 교차 엔트로피 사용) 따라서 우리는 CBOW 모델을 이진 분류를 수행하는 모델로 바꿔 계산량을 효과적으로 줄일 수 있다.

 

 

위의 이미지에서 은닉층 뉴런 h와 출력 측의 가중치 Wout에서 단어 'say'에 해당하는 단어 벡터와의 내적을 계산하고, 계산 결과를 Sigmoid with Loss 계층에 입력해 최종 손실을 얻는다.

 

 

 

Negative Sampling

위의 이진 분류는 정답에 대해서만 학습하고 오답인 경우에 대해서는 학습하지 않았다. 이 말을 달리 표현하자면 오답인 단어도 충분히 정답과 같은 확률로 나타내질 수 있다는 이야기이다. 우리는 이 신경망 모델이 정답에 대해서는 Sigmoid의 출력이 1에 가깝도록, 오답에 대해서는 0에 가깝도록 만드는 것이 최종적인 목표이다. 따라서 오답인 경우에 대해서도 학습을 시켜야 한다. 어떻게 하면 오답인 경우에 대해서도 효과적으로 학습할 수 있을까?

 

학습에 사용되는 말뭉치에서 오답인 경우를 모두 학습시킨다는 것은 현실적으로 말이 되지 않는다. 따라서 우리는 이런 경우에 효과적인 학습을 위해 Negative Sampling을 사용한다. Negative Sampling은 부정적인 예(오답) 몇 개를 선택(샘플링)하여 사용하는 것이다. 네거티브 샘플링은 긍정적인 예(정답)를 타깃으로 한 경우의 손실과 부정적인 예 몇 개를 샘플링(선별)하여, 그 부정적인 예에 대해서도 손실을 구하고 각각의 데이터의 손실을 더한 값을 최종 손실로 하는 것을 의미한다.

 

그렇다면 어떻게 부정적인 예를 샘플링 할 수 있을까? 단순히 무작위로 샘플링하는 것보다 좋은 방법이 존재하는데, 그것은 바로 말뭉치에서 자주 등장하는 단어를 많이 추출하고 드물게 등장하는 단어를 적게 추출하는 방법이다. 이를 위해 말뭉치에서 각 단어의 출현 횟수를 구해 확률분포로 나타낸 후, 그 확률분포대로 단어를 샘플링한다.

 

Negative Sampling 식

 

위의 Negative Sampling 식에서 확률분포의 각 요소를 0.75 제곱하는 이유는 출현 확률이 낮은 단어의 확률을 살짝 올려 샘플링에서 배제되는 것을 방지하기 위함이다. 꼭 0.75가 아닌 다른 숫자가 와도 된다.

 

 

 

 

 

 

 

Skip-gram 모델

skip-gram 모델은 CBOW에서 다루는 맥락과 타깃을 역전시킨 모델이다. 즉, 입력층에 중앙 단어(CBOW에서 타깃이었던 단어)로부터 주변의 여러 단어(맥락)를 추측한다. skip-gram 모델은 입력층이 하나고 출력층이 맥락의 수만큼 존재한다.

 

 

word2vec에서는 단어의 분산 표현으로 입력과 은닉층 사이의 가중치 Win을 최종 단어의 분산 표현으로 이용한다.

 

그런데 왜 word2vec은 CBOW와 skip-gram 모델 두가지를 추천하는 걸까?

이에 대해서는 두 모델의 장단점을 보면 알 수 있는데, 단어 분산 표현의 정밀도 면에서는 skip-gram 모델의 결과가 더 좋은 경우가 많지만(말뭉치가 커질수록 저빈도 단어나 유추 문제의 성능이 skip-gram이 더 뛰어남) 학습 속도 측면에서는 CBOW모델이 더 빠르다.

 

word2vec으로 얻은 단어의 분산 표현을 사용하면, 벡터의 덧셈과 뺄셈으로 유추 문제를 풀 수 있다. 예를 들어 king : man = queen : ? 에서 ?에 woman을 대답할 수 있고, take : took = go : ? 에서 ?에 went를 대답할 수 있다. 이 말은 즉, 단어의 단순한 의미 뿐만 아니라 문법적 패턴도 파악할 수 있다는 뜻이다.

 

 

 

 


 

 

 

현재까지 설명한 내용 중, 모든 내용이 중요하지만 가장 근본적으로 중요한 것은 역시 단어나 문장을 고정 길이 벡터로 변환할 수 있다는 것이다. 자연어를 벡터로 변환할 수 있게 됨으로써 우리는 일반적인 머신러닝 기법을 적용할 수 있게 되었기 때문이다. 이러한 word2vec의 근본적인 해결책과 생각들은 자연어 뿐만 아니라 음성, 이미지, 동영상 등에서도 응용이 되어 다양한 딥러닝 분야 발전에 도움을 주었다.

 

 

 

NLP(Natural Language Processing)

NLP란 자연어 처리를 의미하며, 이는 인간의 언어를 컴퓨터에게 이해시키기 위한 기술이라고 생각하면 된다. 어떻게 인간의 언어를 컴퓨터에게 이해시킬 수가 있을까? 우선 언어를 이해시키기 위해서는 언어를 구성하면서 의미가 있는 가장 작은 단위인 '단어'에 대해 이해해야 한다. 어떻게 하면 컴퓨터가 '단어의 의미'를 이해할 수 있을까? 이 방법을 알기 위해서 우선 우리는 자연어 처리에 대한 역사에 대해 알 필요가 있다.

 

 

 

시소러스

'단어의 의미'를 나타내는 방법으로 가장 쉽지만 가장 비효율적인 것은 국어사전과 같이 '사람이 직접 단어의 의미를 정의하는 방식'이다. 시소러스는 정확히는 유의어 사전으로, '뜻이 같은 단어(동의어)'나 '뜻이 비슷한 단어(유의어)'가 한 그룹으로 분류되어 있다. 또한 자연어 처리에 이용되는 시소러스는 단어 사이의 '상위와 하위' 혹은 '전체와 부분' 등, 세세한 관계까지 정의해둔 경우도 있다. 이처럼 모든 단어에 대한 유의어 집합을 만든 후, 단어간의 연결을 정의하면 이러한 네트워크를 통해 컴퓨터는 단어 사이의 관계를 이해할 수 있다.

 

 

WordNet은 자연어 처리 분야에서 가장 유명한 시소러스로, 프린스턴 대학교에서 1985년부터 구축하기 시작하여 현재까지 많은 연구와 다양한 자연어 처리 애플리케이션에서 활용되고 있다. WordNet은 앞서 설명했던 단어 네트워크를 이용할 수 있으며 단어 간의 유사도를 구할 수 있다.

 

 

시소러스의 단점

하지만 시소러스의 문제점은 치명적인 단점이 존재한다.

우선적으로 시대 변화에 대응하기 어렵다는 것이다. 이에 대해 간단히 설명하자면 최근에 새롭게 생긴 단어인 내로남불, 창렬, 혜자와 같은 단어에 대해서 사람이 직접 입력하기 전까지 이해하지 못한다는 것이다.

 

두번째로는 사람을 쓰는 비용이 크다는 것이다. 위의 '시대 변화에 대응하기 어렵다'는 단점에 대한 해결책으로 사람이 일일이 새롭게 생겨난 단어에 대해 입력해줘야하는데, 그러기엔 너무나 단순 노동 뿐만 아니라 인건비가 많이 든다.

 

세번째로는 단어의 미묘한 차이를 표현할 수 없다. 이에 대한 예시로는 우리나라의 대표적인 만능 문자 '헐'이 있겠다. 사람들은 단순히 문자로 되어있는 글에서도 '헐'의 의미를 유추하고 파악할 수 있으나 시소러스로는 '헐'의 미묘한 차이를 이해할 수 없다.

 

 

 

 


 

 

 

 

통계 기반 기법

위의 시소러스의 단점들을 해결하고자 새롭게 만들어 낸 방법이 통계 기반 기법이다. 통계 기반 기법은 특정 단어를 주목했을 때, 그 주변에 어떤 단어가 몇 번이나 등장하는지를 세어 집계하는 방법이다. 이에 대한 이해를 하기 위해서는 앞으로 내가 기술할 내용들의 흐름을 따라가야 한다. 우리는 통계 기반 기법에서 '말뭉치(corpus)'라는 것을 사용하게 된다. 말뭉치란 자연어 처리 연구나 애플리케이션을 염두에 두고 수집된 대량의 텍스트 데이터를 의미한다. 말뭉치에 담긴 문장들을 보면 문장을 쓰는 방법, 단어 선택 방법, 단어의 의미 등 자연어에 대한 사람의 '지식'이 담겨져 있다. 통계 기반 기법은 결론적으로 사람의 지식으로 가득한 말뭉치에서 자동으로, 그리고 효율적으로 핵심을 추출하고자 하는 기법이다.

 

말뭉치를 처리하고자 하는 코드적인 부분인 여기를 참고하길 바란다. 간략하게 말하자면 자연어는 순서와 흐름이 존재하므로 ('나는 너를 싫어한다.' 와 '너는 나를 싫어한다.' 의 의미가 다른 이유는 나와 너의 위치의 차이이며, 이는 자연어에서 중요한 특징이다.) 이 순서를 기억하기 위해서 문장을 구성하는 단어들을 순서(단어ID)와 단어에 대한 정보를 각각 저장하는(단어ID->단어, 단어->단어ID) 딕셔너리로 만들어 저장한다.

 

여기서 잠깐 생각해보면 아무리 말뭉치고 뭐고 생각을 해도 컴퓨터가 자연어를 이해한다는게 이해되지 않지 않는가? (뭔 말장난 같은 문장이여) 컴퓨터는 숫자, 근본적으로는 0과 1을 이해하는 숫자충(충실할 충)이고 자연어는 지극히 언어적이다. 이런 완전히 반대되는 녀석을 어떻게 숫자충인 컴퓨터가 이해할까? 그러기 위해서는 당연하게도 컴퓨터가 이해할 수 있도록 자연어를 처리해야한다. 그 말은 즉, 단어를 숫자로 바꿔야한다는 의미이다. (정말 충격적이지 않은가?! 말이 쉽지 이걸 그래서 어떻게 하는데) 따라서 우리는 단어의 의미를 정확하게 파악할 수 있도록 단어를 벡터로 표현해야 하고 이를 '단어의 분산 표현'이라고 한다.

 

 

분포 가설

단어를 벡터로 표현하기 위해서는 어떻게 해야할까? 이를 위해 우리는 이 사실을 알아야 한다. 단어의 의미는 주변 단어에 의해 형성이 된다는 것이다. 이를 분포 가설이라고 하며, 이 분포 가설이 의미하는 바는 매우 간단하다. 단어란, 그 자체에는 의미가 없고, 그 단어가 사용된 맥락(context)이 의미를 형성한다는 것이다. 이를 쉽게 이해하려면 간단히 '헐'을 떠올리면 된다. (이하 설명은 생략하겠다. 한국인이라면 충분히 이해할 수 있다.)

 

윈도우 크기(window size) 설명

 

위의 이미지에서 설명하는 내용은 window size에 대한 내용이다. window size란 '맥락의 크기'를 의미하며 '맥락'이란 특정 단어를 중심에 둔 주변 단어들을 의미한다. 따라서 위의 경우에서 windwo size가 1인 경우는 say와 and가 goodbye의 맥락이다. window size가 2인 경우는 you,say,and,i가 goodbye의 맥락이 된다. 이러한 맥락을 벡터로 나타내는 것을 우리는 '동시발생 행렬'이라고 한다.

 

 

동시발생 행렬

이에 대한 설명은 아래의 이미지를 통해 직관적으로 이해하고 넘어가겠다.

 

 

 

그림 2-7은 모든 단어에 대해 동시발생하는 단어를 표로 정리한 것이다. 이 표의 각 행은 해당 단어를 표현한 벡터가 되며, 이 표가 행렬의 형태를 띤다는 뜻에서 '동시발생 행렬'이라고 한다.

 

 

벡터 간 유사도

단어 벡터의 유사도를 나타낼 때는 코사인 유사도를 자주 이용한다. 코사인 유사도의 식은 아래와 같고, 코사인 유사도를는직관적으로 '두 벡터가 가리키는 방향이 얼마나 비슷한가'를 의미한다. 두 벡터의 방향이 완전히 일치한다면 1, 완전히 반대라면 -1이 된다.

 

 

놀랍게도 이 코사인 유사도를 사용하면 유의어 및 반의어에 대해 컴퓨터가 이해한다. 이렇게 단어에 대한 네트워크를 이해하기 위해서는 큰 말뭉치가 필요하다. (역시 현대사회에선 다다익선)

 

 

PMI(상호정보량)

동시발생 행렬의 원소는 두 단어가 동시에 발생한 횟수를 나타낸다. 하지만 이것의 치명적인 단점이 있는데, 한국말에서는 조사, 영어에서는 관사가 이 단점의 원흉이다. 영어 문법을 보면 수량을 알기 위해서나 단어를 특정하기 위해 사용되는 a,an,the가 실제 명사(car) 다음에 오는 동사(drive)보다 동시발생 행렬의 개념('등장 횟수가 높을수록 관련성이 강함')에 의해 훨씬 관련성이 높게 나온다는 것이다. 결국 우리는 앞서 설명한 동시발생 행렬로 유의미한 단어의 관계성을 파악할 수 없다. 이를 해결하기 위한것이 PMI이다.

 

PMI란 점별 상호정보량이라는 척도로, 확률의 개념이 포함되어 단어의 연관성을 나타내는 행렬이다.

 

 

P(x)는 x가 일어날 확률을, P(y)는 y가 일어날 확률을, P(x,y)는 x와 y가 동시에 일어날 확률을 의미한다. 위의 식을 보면 PMI는 결론적으로 값이 높을수록 관련성이 높음을 의미한다. 이 식을 동시발생 행렬을 사용하는 식으로 바꾸면 다음과 같다.

 

 

 

C는 동시발생 행렬이고 N은 말뭉치의 단어 수이며, C(x,y)는 단어 x,y가 동시에 발생하는 횟수, C(x)와 C(y)는 각각 단어 x와 y의 등장 횟수이다. PMI("the","car")의 수치와 PMI("car","drive")의 수치는 다음과 같이 연산되어 나타난다.

 

 

앞서 설명했던 동시발생 행렬의 단점을 완벽하게 보완한 모습을 볼 수 있다. 이렇게 계산이 될 수 있었던 것은 PMI는 단어가 단독으로 출현하는 횟수에 대해서도 고려되었기 때문이다. 관사인 a,an,the는 car 뿐만 아니라 다양한 단어와 함께 나오고, 많이 나온다. 그 모든 횟수를 고려하면 사실 the는 car와 그렇게 크게 관련성이 없다는 것을 알 수 있다.

 

PMI도 단점이 존재하는데, 바로 두 단어의 동시발생 횟수가 0이면 -inf가 된다는 점이다. 이 문제를 피하기 위해 실제 구현시에는 양의 상호정보량(PPMI)를 사용하며 식은 다음과 같다. 별 내용은 없다. PMI가 음수일 때 0으로 취급한다는 의미이다.

 

 

이 PPMI에 대해서도 또 문제점이 존재한다.(완벽이란 없어...^^) 말뭉치의 어휘 수가 증가함에 따라 각 단어 벡터의 차원 수도 증가한다는 문제이다. 즉, 말뭉치의 어휘 수가 10만개라면 벡터의 차원 수도 10만이 된다는 것이다. 또한 심지어 이 벡터의 대부분의 원소는 0이다. 결론적으로 벡터가 차원이 큰것 치고 실속이 없다는 얘기다. 이렇게 실속없고 등치만 큰 벡터는 노이즈에 약하며 견고하지 못하다. 이에 대한 문제를 해결하기 위해 우리는 차원 감소를 수행한다.

 

차원 감소는 말 그대로 벡터의 차원을 '중요한 정보'는 최대한 유지하며 줄이는 방법이다. 이 방법에 대해서는 특이값분해(SVD) 등 다양한 방법이 있다. 이에 대한 자세한 내용은 여기를 참고하길 바란다. 또한 내가 앞에서 설명했던 차원 감소 방법 중 하나인 주성분 분석(PCA)와 특이값분해(SVD)에 대한 차이점 및 설명에 대해서는 여기를 참고하길 바란다.

 

 

 


 

 

 

 

결론적으로 통계 기반 기법은 말뭉치로부터 단어의 동시발생 행렬을 만들고, PPMI 행렬로 변환한 후, 안전성을 높이기 위해 SVD를 이용해 차원을 감소시켜 각 단어의 분산 표현을 만들어 낸다.

 

놀랍게도(ㅠㅠ) 아직 단어의 분산 표현을 만드는 방법을 다 설명하지 않았다.

단어의 분산 표현을 만드는 방법인 또 하나의 방법인 '추론 기반 기법'에 대해서는 다음에 설명하도록 하겠다.

 

 

 

 

pycharm에서 tensorflow-cpu로 사용하기 위해서는 다음의 코드를 추가해야함(아마 그럴것.. 나는 주피터에서 돌려서)

import os
os.environ['TF_CPP_MIN_LOG_LEVEL'] = '2'

 

 

categorical_crossentropy를 손실함수로 사용했을 때의 (대략적인) CNN 신경망 코드

import tensorflow as tf
from tensorflow.keras.utils import to_categorical
from tensorflow.keras.datasets.fashion_mnist import load_data
from tensorflow.keras.models import Sequential, save_model, load_model
from tensorflow.keras.layers import Dense, Dropout, Activation, Flatten
from tensorflow.keras.layers import Conv2D, MaxPooling2D

# Fashion-MNIST 데이터 불러오기
(x_train, y_train), (x_test, y_test) = load_data()
x_train, x_test = x_train / 255.0, x_test / 255.0
x_train = x_train.reshape(-1,28,28,1)
x_test = x_test.reshape(-1,28,28,1)

# 원핫인코딩 (one-hot encoding) 처리 (categorical_crossentropy를 사용하기 때문)
y_train = to_categorical(y_train)
y_test = to_categorical(y_test)

# train/valid 분류
from sklearn.model_selection import train_test_split
x_train , x_val , y_train , y_val = train_test_split(x_train, y_train, test_size=0.3,random_state=777)

# 모델 생성
model = Sequential([
  Conv2D(64, kernel_size=(5,5),input_shape=x_train.shape[1:], activation='relu'),
  MaxPooling2D(pool_size=(2, 2), padding='same'),
  Dropout(0.2),
  Flatten(),
  Dense(128, activation='relu'),
  Dense(128, activation='relu'),
  Dense(32, activation='relu'),
  Dropout(0.2),
  Dense(10, activation='softmax')
])

model.compile(optimizer='adam',
              loss='categorical_crossentropy',
              metrics=['accuracy'])

# 모델 학습
hist = model.fit(x_train, y_train, batch_size=100 ,epochs=20, validation_data=(x_val ,y_val))
print(model.evaluate(x_test,  y_test, verbose=1))

# 정확도 시각화
import matplotlib.pyplot as plt
# print(hist.history)

plt.plot(hist.history['accuracy'],'b-', label='training')
plt.plot(hist.history['val_accuracy'],'r:' , label='validation')
plt.title('Model accuracy')
plt.ylabel('Accuracy')
plt.xlabel('Epoch')
plt.legend(['Train', 'Test'], loc='upper left')
plt.show()

# 손실값 시각화
plt.plot(hist.history['loss'])
plt.plot(hist.history['val_loss'])
plt.title('Model loss')
plt.ylabel('Loss')
plt.xlabel('Epoch')
plt.legend(['Train', 'Test'], loc='upper left')
plt.show()

# 모델 저장
save_model(model,"c:\\data\\fashion_mnist.h5")

# 모델 불러오기
model2 = load_model("c:\\data\\fashion_mnist.h5")

# test 데이터를 사용해 불러온 모델 성능 평가
result = model2.evaluate(x_test,y_test)
print(dict(zip(model.metrics_names, result)))

# sklearn accuracy_score를 사용하여 모델 정확도 평가
from sklearn.metrics import accuracy_score
print(accuracy_score(model2.predict_classes(x_test), np.argmax(y_test,axis=1)))

 

categorical_crossentropy는 다중 분류 손실함수로 one-hot encoding 클래스를 사용한다. 따라서 출력값이 one-hot encoding된 결과로 나오고 정답(label)과 비교하며 학습을 진행하거나 정확도를 알아내기 위해서는 정답(label)도 one-hot encoding된 형태여야 한다. 따라서 위의 코드 구현에서 정답(y_train, y_test)에 대해 one-hot encoding을 하였다.

 

 

 

sparse_categorical_crossentropy를 손실함수로 사용했을 때의 완전연결신경망 코드

import tensorflow as tf
from tensorflow.keras.datasets.fashion_mnist import load_data
from tensorflow.keras.models import Sequential, save_model, load_model
from tensorflow.keras.layers import Dense, Dropout, Activation, Flatten
from tensorflow.keras.layers import Conv2D, MaxPooling2D

# Fashion-MNIST 데이터 불러오기
(x_train, y_train), (x_test, y_test) = load_data()

# 데이터 정규화
x_train, x_test = x_train / 255.0, x_test / 255.0

# sparse_categorical_crossentropy를 손실함수로 사용하기 때문에 one-hot encoding 사용 안함



# train 데이터 분리 (train/valid)
from sklearn.model_selection import train_test_split
x_train , x_val , y_train , y_val = train_test_split(x_train, y_train, test_size=0.3,random_state=777)

# 신경망 모델 생성
model = Sequential([
  Flatten(input_shape=(28, 28)),
  Dense(128, activation='relu'),
  Dense(128, activation='relu'),
  Dense(32, activation='relu'),
  Dropout(0.2),
  Dense(10, activation='softmax')
])

# 학습할 때 사용할 옵티마이저, 손실함수, 기준에 대한 정보
model.compile(optimizer='adam',
              loss='sparse_categorical_crossentropy',
              metrics=['sparse_categorical_accuracy'])

# 모델 학습
hist = model.fit(x_train, y_train, batch_size=64, epochs=20, validation_data=(x_val ,y_val))

# 학습 시각화
import matplotlib.pyplot as plt
# print(hist.history) # hist에 어떤 데이터가 있는지 확인할 수 있음
plt.plot(hist.history['sparse_categorical_accuracy'],'b-', label='training')
plt.plot(hist.history['val_sparse_categorical_accuracy'],'r:' , label='validation')
plt.title('Model accuracy')
plt.ylabel('Accuracy')
plt.xlabel('Epoch')
plt.legend(['Train', 'Valid'], loc='upper left')
plt.show()

# 학습 손실 값과 검증 손실 값 시각화
plt.plot(hist.history['loss'])
plt.plot(hist.history['val_loss'])
plt.title('Model loss')
plt.ylabel('Loss')
plt.xlabel('Epoch')
plt.legend(['Train', 'Valid'], loc='upper left')
plt.show()

# 구현한 model의 파라미터들 h5 파일로 저장
save_model(model, "c:\\data\\fashion_mnist2.h5")

# 모델 성능 평가
model2 = load_model("c:\\data\\fashion_mnist2.h5")
print(model2.evaluate(x_test,y_test,verbose=0))

# sklearn의 accuracy_score를 사용하여 정확도 확인
from sklearn.metrics import accuracy_score
print(accuracy_score(model2.predict_classes(x_test), y_test))

 

sparse_categorical_crossentropy 또한 다중 분류 손실함수로, categorical_crossentropy와 동일하지만 integer type 클래스라는 것이 다르다. 결론적으로 sparse_categorical_crossentropy는 정답(label)이 one-hot encoding 형태가 아니어도 된다.

 

sparse_categorical_crossentropy를 사용해서 모델을 학습시키고 이 학습된 모델을 불러와서 정확도를 측정하기 위해서는 metrics가 sparse_categorical_accuracy여야 한다. 안그러면 불러온 모델로 evaluate을 하면 정확도가 엉망으로 나온다...^^ (이걸로 개고생함) 만약 실수로 metrics를 sparse_categorical_accuracy로 안하고 그냥 accuracy로 했다면 evaluate으로 정확도를 평가하지말고 sklearn의 accuracy_score로 확인해야 제대로 값이 나온다는 것을 추가적으로 꼭꼭 알길 바란다.

 

 

 

h5 파일이 어떻게 구성되어 있는지 확인하는 파이썬 코드

import h5py
filename = "c:\\data\\fashion_mnist.h5"

def read_hdf5(path):

    weights = {}

    keys = []
    with h5py.File(path, 'r') as f: # open file
        f.visit(keys.append) # append all keys to list
        for key in keys:
            if ':' in key: # contains data if ':' in key
                print(f[key].name)
                weights[f[key].name] = f[key].value
    return weights

read_hdf5(filename)

 

 

 

 

 

+

 

 

 

 

 

Keras Callbacks

 

Callbacks - Keras Documentation

Usage of callbacks 콜백은 학습 과정의 특정 단계에서 적용할 함수의 세트입니다. 학습 과정 중 콜백을 사용해서 모델의 내적 상태와 통계자료를 확인 할 수 있습니다. 콜백의 리스트는 (키워드 인수

keras.io

 

추가적으로 모델 학습시, keras의 callback 기능으로 최적의 모델을 만들 수 있다. 이에 대한 부분은 위의 kears 링크를 참고하는 것이 빠르다.

 

from tensorflow.keras.datasets import fashion_mnist

# Fashion_MNIST 데이터 불러오기
(x_train, y_train), (x_test, y_test) = fashion_mnist.load_data()

import matplotlib.pyplot as plt
import numpy as np

# 정규화, 4차원으로 데이터를 만들어줌
x_train = np.reshape(x_train / 255, (-1, 28, 28, 1))
x_test = np.reshape(x_test / 255, (-1, 28, 28, 1))

from tensorflow.keras.utils import to_categorical
# one-hot encoding
y_train = to_categorical(y_train)
y_test = to_categorical(y_test)

from sklearn.model_selection import train_test_split
# train / valid 분리
x_train, x_val, y_train, y_val = train_test_split(x_train, y_train, test_size = 0.3, random_state = 777)

print('Fashion-MNIST ready~')

from tensorflow.keras.models import Sequential
from tensorflow.keras.layers import Conv2D, MaxPool2D, Dense, Flatten

model = Sequential([
    # 항상 모델의 첫 번째 층은 입력의 형태를 명시해주어야함
    Conv2D(filters = 16, kernel_size = 3, strides = (1, 1), padding = 'same', activation = 'relu', input_shape = (28, 28, 1)),
    MaxPool2D(pool_size = (2, 2), strides = 2, padding = 'same'),  
    Conv2D(filters = 32, kernel_size = 3, strides = (1, 1), padding = 'same', activation = 'relu'),
    MaxPool2D(pool_size = (2, 2), strides = 2, padding = 'same'),
    Conv2D(filters = 64, kernel_size = 3, strides = (1, 1), padding = 'same', activation = 'relu'),
    MaxPool2D(pool_size = (2, 2), strides = 2, padding = 'same'),
    Flatten(), # 완전연결층에 들어가기 전 데이터 펼치기
    Dense(64, activation = 'relu'),
    Dense(10, activation = 'softmax') # 10개의 출력을 가지는 신경망
])

model.compile(optimizer = 'adam', loss = 'categorical_crossentropy', metrics = ['acc'])

# kears callbacks 사용
from tensorflow.keras.callbacks import ModelCheckpoint, EarlyStopping

filepath = 'c:\\data\\fashion.hdf5'

# ModelCheckpoint : 학습 과정을 모니터링하다가 val_loss가 가장 작을 때 모델의 가중치를 저장
# EarlyStopping : 모니터링하는 평가지표에서 성능 향상이 일어나지 않는 경우
# (patience=5 면 향상이 일어나지 않는 경우 5번까지는 참아줌) 학습 중단
callbacks = [ ModelCheckpoint( filepath = filepath , monitor = 'val_loss' , verbose=1, save_best_only=True ),
            EarlyStopping(monitor='val_loss', patience=0, verbose=0, mode='auto')]

model.fit(x_train, y_train, batch_size = 32 , validation_data=(x_val , y_val) , epochs=10, callbacks=callbacks )
model.summary() # 모델의 구조 확인

 

 

 

 

 

Pooling 계층에서의 im2col

 

입력 데이터에 풀링 적용 영역을 전개 (2x2 풀링)

 

풀링 계층도 앞서 설명한 합성곱 계층과 마찬가지로 im2col을 사용해 입력 데이터를 전개한다.

이번에는 코드와 함께 설명하도록 하겠다.

 

우선, pooling 계층을 구현하기 위해서는 im2col 함수가 필요하다.

import numpy as np

def im2col(input_data, filter_h, filter_w, stride=1, pad=0):
    """
    다수의 이미지를 입력받아 2차원 배열로 변환한다(평탄화).

    Parameters
    ----------
    input_data : 4차원 배열 형태의 입력 데이터(이미지 수, 채널 수, 높이, 너비)
    filter_h : 필터의 높이
    filter_w : 필터의 너비
    stride : 스트라이드
    pad : 패딩

    Returns
    -------
    col : 2차원 배열
    """
    N, C, H, W = input_data.shape
    out_h = (H + 2 * pad - filter_h) // stride + 1
    out_w = (W + 2 * pad - filter_w) // stride + 1

    img = np.pad(input_data, [(0, 0), (0, 0), (pad, pad), (pad, pad)], 'constant')
    col = np.zeros((N, C, filter_h, filter_w, out_h, out_w))

    for y in range(filter_h):
        y_max = y + stride * out_h
        for x in range(filter_w):
            x_max = x + stride * out_w
            col[:, :, y, x, :, :] = img[:, :, y:y_max:stride, x:x_max:stride]

    col = col.transpose(0, 4, 5, 1, 2, 3).reshape(N * out_h * out_w, -1)
    return col

 

 

이후 풀링 계층의 진행 순서는 다음과 같다.

(선택사항). 입력 데이터가 만약 4차원이 아니라면 im2col의 입력값으로 바꾸기 위해서 4차원으로 변경한다.

1. im2col로 4차원 데이터를 행렬로 (2차원) 변경한다.

2. np.max를 사용하여 각 행에서의 최대값을 취한다.

3. 최대값만 취한 데이터를 다시 입력 데이터의 shape으로 변경한다.

 

 


 

 

 

그럼 다음의 그림과 같은 예제를 코드로 진행하도록 하겠다.

 

2x2 크기의 max-pooling 적용 예제

 

1. im2col로 4차원 데이터를 행렬로 (2차원) 변경한다.

# 예제 데이터 생성
x = np.array([[[[1,2,3,0],[0,1,2,4],[1,0,4,2],[3,2,0,1]],
              [[3,0,6,5],[4,2,4,3],[3,0,1,0],[2,3,3,1]],
              [[4,2,1,2],[0,1,0,4],[3,0,6,2],[4,2,4,5]]]])

x.ndim # 4차원
x.shape # (1,3,4,4)

# 1번 : im2col로 4차원 데이터를 행렬로 (2차원) 변경한다.
step_1 = im2col(x,2,2,2,0) # 입력 데이터(x)를 필터크기(2x2)와 stride(2), padding(0)으로 평탄화함
step_1

'''
array([[1., 2., 0., 1., 3., 0., 4., 2., 4., 2., 0., 1.],
       [3., 0., 2., 4., 6., 5., 4., 3., 1., 2., 0., 4.],
       [1., 0., 3., 2., 3., 0., 2., 3., 3., 0., 4., 2.],
       [4., 2., 0., 1., 1., 0., 3., 1., 6., 2., 4., 5.]])
'''

step_1.shape # (4,12)

# 1번_2 : 평탄화 된(im2col 사용) 행렬을 그림과 같은 2차원으로 변경한다.
step1 = step_1.reshape(-1,4)

'''
array([[1., 2., 0., 1.],
       [3., 0., 4., 2.],
       [4., 2., 0., 1.],
       [3., 0., 2., 4.],
       [6., 5., 4., 3.],
       [1., 2., 0., 4.],
       [1., 0., 3., 2.],
       [3., 0., 2., 3.],
       [3., 0., 4., 2.],
       [4., 2., 0., 1.],
       [1., 0., 3., 1.],
       [6., 2., 4., 5.]])
'''

step1.shape # (12,4)

 

그림의 전개에 해당하는 부분으로 보고 싶으면 이렇게 묶어 봐야함

im2col 함수를 사용한 변수의 결과는 위의 이미지로, 그림의 전개에 해당하는 부분처럼 이해하고 싶으면 사각형으로 봐야 한다. 실제 코드로 구현할 때는 채널1, 채널2, 채널3에 해당하는 수들을 전개하는 것이 아니라 채널1과 채널2, 채널3에 같은 위치(행)에 존재하는 것들을 일렬로 전개한다. 이때문에 2번째로 reshape을 해 준 array는 그림에서의 전개 후 이미지와는 약간 다르다.

 

 

2. np.max를 사용하여 각 행에서의 최대값을 취한다.

# 2. np.max를 사용하여 각 행에서의 최대값을 취한다.
step2 = np.max(step1,axis=1)
step2 # array([2., 4., 4., 4., 6., 4., 3., 3., 4., 4., 3., 6.])

 

여기서 np.max(,axis=1)을 사용하는 이유는 여기를 참고하길 바란다.

step2의 결과물도 그림의 최댓값 이후의 전개물과 동일하게 이해하려면 다음의 그림을 참고하면 된다.

 

주황색 : 채널1 / 초록색 : 채널2 / 파란색 : 채널3

 

 

3. 최대값만 취한 데이터를 다시 입력 데이터의 shape으로 변경한다.

# 3. 최대값만 취한 데이터를 다시 입력 데이터의 shape으로 변경한다.
step3 = step2.reshape(1,2,2,-1) # step2의 결과를 배치사이즈(1), 출력데이터크기(2x2), 채널(-1)로 reshape한다.
step3.transpose(0,3,1,2) #(1,2,2,3) : step3의 결과(N,H,W,C)를 (N,C,H,W)로 순서를 바꿔준다.

'''
array([[[[2., 4.],
         [3., 4.]],

        [[4., 6.],
         [3., 3.]],

        [[4., 4.],
         [4., 6.]]]])
'''

 

그럼 그림과 일치하게, 우리가 원하는 풀링 계층의 출력 데이터를 만들 수 있다.

위의 흐름을 따라서 Pooling class를 만들면 다음과 같다.

 

class Pooling :
    def __init__(self,pool_h,pool_w,stride=1,pad=0) :
        self.pool_h = pool_h
        self.pool_w = pool_w
        self.stride = stride
        self.pad = pad
        
    def forward(self,x) :
        N, C, H, W = x.shape
        out_h = int(1+(H - self.pool_h)/self.stride) # 공식 사용, padding은 Pooling에서 사용하지 않으므로 애초에 수식에서 뺌
        out_w = int(1+(W - self.pool_w)/self.stride) # 공식 사용, padding은 Pooling에서 사용하지 않으므로 애초에 수식에서 뺌
        
        col = im2col(x,self.pool_h,self.pool_w,self.stride,self.pad) # im2col로 평탄화해줌
        col = col.reshape(-1,self.pool_h*self.pool_w) # np.max를 수행하기 위해 reshape 해줌
        out = np.max(col,axis=1) # np.max 수행
        out = out.reshape(N,out_h,out_w,C).transpose(0,3,1,2) # (N,H,W,C)의 모양을 (N,C,H,W)로 바꿔줌
        return out
    
pool = Pooling(2,2,2) # pooling size(2x2), stride(2)의 풀링 계층 구현

pool.forward(x)

 

 

 

 

 

 


 

 

 

 

 

 

 

im2col을 사용하는 이유는 행렬 연산에 최적화 된 컴퓨터가 연산을 더욱 빠르게 하도록,

또 딥러닝에 많이 사용되는 GPU의 병렬 연산을 효율적으로 사용하기 위해서였다.

 

 

그런데 im2col과 완전연결신경망인 MLNN(Multi-Layer Neural Network)에서의 flatten은 정확히 무슨 차이가 있는걸까?

MLNN은 가중치를 갱신하면서 최적의 가중치를 찾아가며 신경망을 학습하고 이 때 입력 데이터의 모든 데이터가 사용된다. 따라서 초반에 2차원, 혹은 3차원의 입력 데이터를 flatten함으로써 모든 데이터가 신경망 전체를 아우르는 가중치의 학습 및 갱신에 사용된다고 볼 수 있다.

하지만 im2col은 결국 Conv층과 Pooling층의 연산 속도를 빠르게 하기 위해 사용하는 함수이고, 실질적으로 im2col을 사용한 후의 데이터는 Conv층의 합성곱을 수행하고 Pooling층의 기능을 한다. 또한 Conv층과 Pooling층은 각각의 filter를 갖고 있기 때문에 입력 데이터는 각각의 filter에 대해서만 영향을 받는다. 따라서 im2col과 flatten은 데이터를 압축시킨다는 역할은 같으나, im2col은 각 계층의 연산을 빠르게 해준다는 장점 이외의 데이터 변형, 또는 연산 변형은 전혀 없다. (정확하다고 볼 수는 없겠지만 느낌적으로 이 둘의 차이를 이해하길 바란다. 개인적인 해석으로 틀렸다면 꼭 알려주시길 바랍니다 크흡,,)

 

 

결론적으로 CNN은 MLNN과 달리 데이터를 flatten시키지 않아 데이터 형상을 유지하면서 Conv계층, Pooling계층의 연산(내부적으로는 im2col을 사용하여 연산속도를 빠르게 함)을 사용하며 이미지 자체를 학습할 수 있다.

 

 

 

 

 

im2col (Image to Column)

CNN은 3차원의 데이터 (주로 이미지)를 학습시키는데 특화되어있는 신경망이다.

차원이 큰 데이터에 대해서도 정보를 잃지 않기 위해 합성곱을 사용하기 때문에 for문을 여러개를 사용해야 한다.

 

import numpy as np

x=np.array([[[1,2,3,0],
                [0,1,2,3],  # --> red 행렬
                [3,0,1,2],
                [2,3,0,1]],
              [[2,3,4,1],
               [1,2,3,4],  # --> green 행렬
               [4,1,2,3],
              [3,4,1,2]],
             [[3,4,5,2],  # --> blue 행렬
              [2,3,4,5],
              [5,2,3,4],
              [4,5,2,3]]])

print (x.ndim)  # 3
print (x.shape)  # (3,4,4)  (차원,행,열)

f=np.array([[[2,0,1],
               [0,1,2],
               [1,0,2]],
               [[3,1,2],
                [1,2,3],
                [2,1,3]],
                [[4,2,3],
                [2,3,4],
                [3,2,4]]])

print ( f.ndim )  #  3
print ( f.shape ) #  ( 3, 3, 3 )

output = np.zeros([x.shape[1]-f.shape[1]+1,x.shape[2]-f.shape[2]+1])

fh,fw = f.shape[1], f.shape[2]

for i in range(output.shape[0]):
    for j in range(output.shape[1]):
        for k in range(x.shape[0]) :
            output[i,j]+=np.sum(x[k][i:fw+i,j:fh+j]*f[k])
            
print(output)

 

위의 함수를 보다시피, for문을 여러번 사용해야 합성곱 연산을 할 수 있다. 하지만 이런 식이라면 연산 속도가 매우 떨어질 것이다.

수만건의 데이터를 처리해야하는 CNN은 이러한 연산속도 저하 문제를 어떻게 해결할까?

 

그에 대한 해결이 im2col 함수이다.

 

3차원 데이터를 im2col 함수에 넣은 후의 출력 데이터

위의 이미지는 im2col의 동작을 단적으로 보여주고 있다. im2col은 쉽게 말해서 다차원의 데이터를 행렬로 변환하여 행렬 연산을 하도록 해주는 함수를 말한다.

다차원 데이터의 합성곱(convolution)은 im2col을 통해 행렬로 변환된 데이터의 내적과 같다.

 

 

 

합성곱 계층(Convolution Layer)에서의 im2col

여기 (7,7,3) 인 입력데이터와 (5,5,3)인 필터가 있다고 하자. C는 RGB로 3이다.

 

 

우리는 원래대로라면 이 3차원 입력데이터와 필터에 대해 합성곱을 진행해야 한다. 하지만 im2col을 사용하면 행렬의 내적으로 합성곱과 같은 결과를 낼 수 있다.

어떻게 3차원 데이터를 2차원 행렬로 변환시킬 수 있을까?

 

7x7xC 입력 데이터는 필터에 의해 9개로 나눠진다.

 

위의 그림이 우리가 기본적으로 알고 있던 합성곱에 대한 개념이다. 입력 데이터(7,7,3)는 필터 사이즈(5,5,3)만큼 가로로 3번, 세로로 3번 총 9개(3x3)로 나뉘어 필터와 합성곱 연산을 한다. 아래는 9개로 나뉜 데이터의 사이즈(filter 사이즈와 같음)를 함께 보여주는 그림이다.

 

9개로 나뉜 데이터의 size는 (5,5,3)으로 filter size와 같다.

 

filter size만큼 나뉘어진 원본 데이터를 행렬로 변환하면 다음과 같다.

 

 

 

입력 데이터는 필터에 의해 총 9개의 데이터로 쪼개졌고, 그 데이터 하나 하나를 행렬로 만들어 준다. 쪼개진 데이터는 (5,5,3)의 크기를 갖고 이것을 행렬로 만들면 세로는 1(데이터 1개라는 의미), 가로는 5x5xC의 크기로, 행렬 1개(1,5x5xC)로 3차원의 데이터를 나타낼 수 있다.

 

입력데이터를 im2col로 변환한 최종 결과 행렬

 

이러한 과정을 쪼개진 데이터 개수인 9만큼을 반복해서 나타낸 행렬은 입력 데이터에 대한 정보를 모두 갖게 된다.

이 과정을 filter에 대해서도 진행한다.

 

filter에 대해서 im2col을 진행한 결과 행렬

 

우리는 (5,5,C)의 모양을 가진 필터 하나를 사용하기 때문에 im2col을 진행하면 행렬 1개가 나온다.

 

 

3차원의 데이터를 합성곱하는 결과는 각각의 데이터를 im2col 함수를 사용해 모든 데이터를 잃지 않으면서 2차원 행렬로 변환시켜 행렬연산을 진행하는 것과 같은 결과를 도출한다. 컴퓨터는 행렬 연산에 매우 특화되어 있기 때문에 im2col을 사용하여 행렬 연산을 진행하면 비록 연산해야하는 데이터 수는 늘어났지만(원래는 입력데이터가 1개 뿐이었지만 입력데이터 1개를 9개로 나눴기 때문에 연산해야하는 데이터 수는 결과적으로 늘었다.) 연산 속도는 빠르다. 하지만 연산해야하는 데이터 수가 늘어났기 때문에 연산 메모리가 증가한다는 단점도 존재한다.

 

im2col로 변환한 데이터와 필터의 연산 과정

 

이렇게 행렬로 변환한 입력 데이터와 필터의 행렬 연산 이후, 우리는 출력된 데이터(행렬)를 다시 원래의 데이터(3차원)로 변환해주는 작업을 한다.

이러한 흐름이 합성곱 계층의 구현 흐름이다.

 

그리고 더 정확하게 말하자면 데이터들은 정확히는 3차원이 아니라 4차원이다. 여기를 보면 알다시피 입력 데이터는 (N,C,H,W)로 구성되어있으며 N은 데이터의 갯수를 의미하는 배치 사이즈를 뜻한다. 따라서 정확히는 입력 데이터와 filter 데이터, 출력 데이터 모두 4차원이지만 이해를 돕기위해 입력 데이터 1개와 필터 1개를 예시로 하여 3차원으로 설명하였으니 오해가 없길 바란다.

 

 

 

 

 

출처 : https://ettrends.etri.re.kr/ettrends/159/0905002137/0905002137.html

 

딥러닝이란 층을 깊게 한 심층 신경망을 의미한다. 신경망의 층이 깊어지면 깊어질수록 너비가 늘어난다. 이에 대한 자세한 설명은 여기를 참고하길 바란다. 층이 깊어질수록 너비가 늘어나는 이유는 합성곱 이후의 너비는 필터 개수와 연관이 있기 때문이다. 내가 지정한 필터 수가 늘어나면 늘어날수록 합성곱 계층의 너비 또한 늘어난다.

 

이러한 딥러닝 신경망의 정확도를 높이기 위해서는 앙상블 학습, 학습률 감소(Learning Rate 감소), 데이터 확장(Data Augmentation) 등을 하는데 특히 이 중에 데이터 확장은 훈련 이미지를 인위적으로 확장하는 방법이다. (필자도 예전에 케글 당뇨성 망막증 대회를 참여했을 때 이 방법을 사용하여 데이터 개수를 늘렸었다.) 이 방법은 데이터가 몇 개 없을 때 효과적인 수단으로 훈련 데이터를 회전, 이동, 확대, 축소, 밝기 변형, 좌우 반전(flip)을 하는 등의 미세한 변화로 데이터의 개수를 늘리는 방법이다.

 

 

 

 

신경망을 깊게 하는 이유

신경망이 깊을수록 처음에는 단순한 특징에만 반응하다 점점 더 복잡하고 추상화된 정보의 특징을 파악하며 결론적으로는 사물의 의미를 이해하게 된다는 것을 이전에 말했었다. 직관적으로 이해가 가면서도 이해가 되지 않는다. 지금부터는 이 부분에 대해서 조금 더 자세하게 설명하도록 하겠다.

 

왼쪽 : 5x5 합성곱 연산 / 오른쪽 : 3x3 합성곱 계층 2회 반복

 

오른쪽의 3x3 연산을 2회 수행한 것은 왼쪽의 5x5 합성곱 연산을 한 것과 동일한 결과를 낼 수 있다. 그런데 왜 3x3을 굳이 2번이나 반복할까? 그냥 5x5 합성곱 연산을 한번만 하는게 훨씬 이득이 아닐까?

 

5x5 합성곱 연산은 매개변수(Filter)가 25(5x5)개이지만, 3x3 합성곱 연산 2회 반복은 매개변수가 18(2x3x3)개이다. 이러한 매개변수 개수 차이는 층이 깊어지면 깊어질수록 더 커지게 된다. 결국 신경망이 깊어질수록 매개변수가 줄어들어, 층이 깊지 않은 경우보다 적은 매개변수로 같은 수준의 표현력을 달성할 수 있다는 것이다.

 

매개변수가 줄어들 때의 장점은 무엇일까? 첫번째로 넓은 수용 영역(Receptive Field)를 소화할 수 있다는 장점이 생기는데, 쉽게 말하면 필터가 작을수록 국소부위에 대한 특징을 잡아낼 수 있어 더 많은 디테일을 찾아낼 수 있다는 것이다. 두번째로는 층마다 활성화 함수를 사용하면서 활성화 함수가 신경망의 비선형을 유지시키며 신경망의 표현력을 개선할 수 있다는 것이다. 또 층이 깊어질수록 사물을 이해하기 때문에 비교적 적은 학습 데이터로도 좋은 성능의 신경망을 만들 수 있어 학습의 효율성 또한 좋아진다.

 

 

 

 

전이 학습 (Transfer Learning)

전이학습은 이미지넷(100만장이 넘는 이미지를 담고 있는 데이터셋으로 각 이미지에 레이블이 붙어 있음)이 제공하는 거대한 데이터셋으로 학습한 가중치의 일부를 능력이 유사하거나 새로운 분야의 신경망에 복사한 후, 그 상태로 재학습을 수행하는 것을 의미한다. 전이 학습은 학습 데이터가 부족한 프로젝트를 진행하는 경우 매우 큰 도움이 된다.

 

예를 들자면 내가 동양인과 서양인을 구분하는 신경망을 구현하고자 할 때, 여자와 남자를 구분하는 신경망에서 이미지의 특징을 추출하는 부분에 대한 파라미터들을 가져와 나머지를 내 데이터로 학습시켜 완성도를 높이는 것이 전이 학습이다.

 

전이 학습은 데이터의 수가 적을 때도(물론 많을 때도) 매우 효과적이며 학습 속도 또한 빠르고 전이 학습 없이 학습하는 것보다 훨씬 높은 정확도를 제공한다는 장점이 있다. 전이 학습에 대한 코드적인 자세한 예제는 여기여기를 참고하길 바란다.

 

 

 

 

강화 학습 (Reinforcement Learning)

 

 

위의 그림처럼 에이전트가 더 좋은 보상을 받기 위해 스스로 학습하는 것을 강화 학습이라고 한다. 강화 학습은 기존의 지도 학습과는 다르다. 지도 학습은 정답과 예측 값에 대한 오차를 통해(가르침) 모델을 완성하는 반면, 강화 학습은 에이전트라는 것이 환경에 맞게 행동을 선택하고, 그 행동에 의해서 환경이 변하면서 그에 맞는 보상을 얻으며 모델을 완성해간다. 강화 학습의 보상은 명확한 것이 아니라 상황에 따라 달라지기 때문에 게임 점수나 게임 종료와 같은 명확한 지표로부터 역산하여 예상 보상을 정해야 한다. 강화학습에 대한 코드적인 예제는 여기여기를 참고하길 바란다.

 

 

 

 

 

MLNN(Multi-Layer Neural Network) : Affine(완전 연결) 계층으로 이뤄진 네트워크

여태 설명했던 신경망은 인접하는 모든 계층의 뉴런과 결합되어 있었다.

이를 완전연결(Fully-Connected)계층(Dense Layer라고도 함)이라고 하며,

완전히 연결된 계층을 Affine 계층이라는 이름으로 구현했다.

 

Affine 계층으로 이뤄진 신경망

 

 

 

CNN (Convolutional Neural Network)

CNN은 여태 배웠던 신경망이 Affine-ReLU 연결을 한 것과는 달리 Conv-ReLU-Pooling의 흐름으로 신경망이 구성된다.

 

CNN으로 이뤄진 신경망 (합성곱 계층, 풀링 계층 추가)

 

그런데 왜 CNN을 사용하는걸까?

 

 

 

Affine 계층의 문제점

Affine 계층은 학습을 진행할 때 데이터의 형상을 무시한다. 이미지 데이터는 가로, 세로, 색(RGB) 총 3차원의 데이터인데, Affine 계층으로 학습 시 이 3차원의 데이터를 1차원으로 평평하게 평탄화(Flatten)해야 학습을 할 수 있다. 이렇게 차원을 줄이다보면 유의미한 데이터를 정확하게 살릴 수 없다.

 

MLNN의 문제점

 

또한 위의 MLNN의 문제점 이미지를 살펴보면 글자의 형상을 고려하지 않고 학습을하기 때문에 데이터가 살짝만 모양이 바뀌어도 새롭게 학습을 시켜야 한다는 문제점이 있다. 따라서 데이터의 모든 정보를 최대한 손실 없이 학습 시키면서 데이터의 모양 변화에 큰 영향을 받지 않는 새로운 방법이 필요했고, 그에 대한 해답이 CNN이었다.

 

 

 

 

 


 

 

 

 

Conv 계층

Conv(합성곱) 계층의 입출력 데이터를 CNN에서는 특징 맵(Feature Map)이라고도 해서 Conv 계층의 입력 데이터를 입력 특징 맵(Input Feature Map), 출력 데이터를 출력 특징 맵(Output Feature Map)이라고 한다.

 

Conv 계층은 이름과 일치하게 합성곱 연산을 처리하며, 이는 이미지 처리에서 말하는 필터(커널(Kernel)이라고 주로 표현) 연산에 해당한다. 합성곱 연산은 Kernel의 Window를 일정 간격으로 이동하며 입력 데이터에 적용한다. 이러한 과정은 기존의 Affine 계층으로만 이루어진 신경망과 달리 데이터의 형상을 잃지 않으면서 학습을 진행할 수 있도록(이미지의 특징을 파악할 수 있도록) 도와준다. CNN도 기존 신경망과 동일하게 가중치와 편향이 존재하는데 필터를 구성하는 매개변수들이 가중치에 해당하고 편향은 항상 1x1로 하나만 존재한다.

 

합성곱 연산

 

 

 

 

필터 (Filter)

필터란 필터에 해당하는 특징이 데이터에 있는지를 검출해주는 함수로, 아래의 그림에서는 곡선이 필터이다.

이 필터를 기존의 데이터와 합성곱 연산을 진행하면 필터에 해당하는 특성을 갖고 있으면 결과 값으로 큰 값이 나오고, 특성을 가지지 않으면 결과값이 0에 가깝게 나오면서 입력데이터가 필터에 해당하는 특징을 갖고 있는지, 아닌지에 대한 여부를 알 수 있다.

 

필터(Filter)에 대한 이해를 돕기 위한 이미지

 

 

 

 

패딩 (Padding)

합성곱 연산을 하기 이전, 입력데이터 주변을 특정값으로 채워 늘리는 것을 패딩(Padding)이라고 한다. 패딩(Padding)은 주로 출력 데이터의 크기를 조정할 목적으로 사용한다. 합성곱 연산을 반복하다보면 어느 시점부터는 출력 데이터의 크기가 1이 되어 더이상 합성곱 연산을 진행할 수 없게 된다. (위의 이미지로만 봐도 4x4 크기의 입력 데이터가 합성곱 연산을 진행하면서 2x2의 데이터로 출력되는 것을 확인할 수 있다.) 따라서 패딩(Padding)을 사용하여 입력데이터의 외각의 크기를 늘려 출력 데이터의 크기가 줄어드는 것을 방지한다.

 

Padding 사용 시 합성곱 연산 : 출력 데이터의 크기가 입력 데이터의 크기와 동일

 

 

 

 

스트라이드 (Stride)

Stride는 입력데이터에 필터를 적용할 때 이동할 간격을 조절하는, 즉 필터가 이동할 간격을 뜻한다. Stride의 값을 키우면 키울수록 출력 데이터의 크기는 작아지며 Padding은 크게하면 할수록 출력 크기가 커진다.

 

Padding, Stride를 적용한 합성곱 연산

 

이렇게 Padding, Stride를 사용하면 출력 크기가 어떻게 되는지에 대한 수식은 다음과 같으며, 출력 크기에 해당하는 OH와 OW는 정수로 나타내져야 한다.

 

더보기

 

위의 수식을 이용한 예제로 한번 생각해보길 바란다.

 

 

 

 

 

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

이미지 데이터는 가로, 세로, 색(RGB) 총 3차원 데이터라고 언급했다. 하지만 위에서 봤던 Conv 계층의 연산은 2차원 데이터로 진행했다. 그렇다면 이미지 데이터와 같은, 혹은 그 이상의 차원의 데이터같은 경우는 어떻게 모든 차원을 살리며 합성곱 연산을 진행할 수 있을까?

 

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

 

이에 대한 해답은 위의 그림을 참고하면 된다. 입력 데이터의 3개의 데이터는 색(RGB) 채널 방향으로 특징 맵이 늘어났다. 즉, 각각의 색에 대한 정보를 담은 데이터로 늘어났다고 보면 된다. 그리고 이것을 각의 필터로 합성곱 연산을 진행하여 출력 데이터로 반환하면 된다. 그러면 출력데이터는 2차원의 형태지만 기존 3차원의 데이터를 모두 갖고 있는 데이터로 출력된다. 이러한 3차원 합성곱 연산에서 주의할 점은 입력 데이터의 채널 수와 필터의 채널 수가 같아야 한다는 사실이다. 예를 들어 RGB 3개의 채널을 갖고 있다면 필터의 채널 수도 3개여야하고, RB 2개의 채널만 갖고 있다면 필터의 채널 수도 2개여야 한다는 뜻이다.

 

 

 

 

 

합성곱 연산의 배치 처리

합성곱 연산 시 배치 처리된 데이터를 학습시키는 방법은 기존의 배치와 크게 다르지 않다. 우리가 CNN을 사용하는 이유는 주로 이미지 데이터와 같이 다차원의 데이터를 최대한 기존 데이터의 특징을 모두 학습시키기 위함인데, 배치를 사용하면 3차원이었던 데이터가 4차원으로 늘어나면 된다. 즉, 맨 앞에 배치 사이즈(N)를 추가하여 데이터를 학습시키면 된다.

 

합성곱 연산 처리

 

위의 이미지는 CNN의 연산 처리 과정으로 지금부터 하나하나 뜯어 설명해보도록 하겠다. 입력 데이터는 배치된 데이터로 N개의 데이터로 구성되어져있어 채널, 가로, 세로로 구성되어져 있는 N개의 데이터 묶음(배치)이다. 그렇다면 필터의 FN은 정확히 무엇을 의미하는걸까? 필터의 FN은 입력 데이터의 배치 사이즈와는 정확히 다르다. 이해를 돕기위해 다시 한번 맨 위의 필터(Filter) 설명에 사용했던 쥐의 이미지를 가져 오겠다.

 

1개의 필터에 대한 합성곱 연산

 

이 그림에 대한 필터는 곡선으로, 오직 1개 뿐이다. 그러면 출력 데이터는 특징 1개에 대한 정보만을 갖고 있는 데이터로 출력된다. 그렇다면 필터의 개수를 FN개로 늘려보면 어떨까?

 

FN개의 필터들

 

FN개의 필터들로 입력 데이터를 학습시키면 각각의 특징에 대한 출력 데이터를 FN개 얻을 수 있다. 이것들의 묶음을 블록 단위로 하여 다음 계층에 넘기는 것이 필터 (FN, C, FH, FW)의 의미이다. 입력 데이터와 FN개의 필터들과의 합성곱 연산으로 출력된 데이터 (FN, OH, OW)는 필터 개수만큼의 편향 (FN, 1, 1) 과의 연산을 통해 (FN, OH, OW)의 데이터로 출력될 수 있다. 여기서 초기 입력 데이터의 C(채널)가 출력 데이터들에 없는 이유는 합성곱을 진행하면서 C에 대한 데이터가 OH, OW에 모두 스며들었기 때문이다.

 

 

 

 

 

 


 

 

 

 

 

풀링(Pooling) 계층

풀링은 세로, 가로 방향의 공간을 줄이는 연산이다.

풀링의 종류로는 크게 max pooling과 average pooling이 있는데,

max pooling은 이미지를 선명하게 만드는 역할을, average pooling은 이미지를 부드럽게 만드는 역할을 한다.

 

Max-pooling을 사용하는 경우

 

Pooling은 주로 Conv 계층에서 출력데이터의 크기는 입력 데이터의 크기 그대로 유지하고, 풀링 계층에서 크기를 조절한다.

그렇다면 풀링 계층의 특징은 무엇일까?

 

  • 학습해야 할 매개변수가 없다.
  • 채널 수가 변하지 않는다.
  • 입력의 변화에 영향을 적게 받는다.

 

여기서 맨 마지막 입력의 변화에 영향을 적게 받는 이유는 아래의 이미지를 참고하면 이해하기 쉽다.

 

 

출력 데이터가 오른쪽으로 하나씩 밀려도 풀링 결과는 달라지지 않는다.

 

또한 Pooling은 parameter를 줄이기 때문에 신경망의 표현력이 줄어들어 Overfitting을 억제하고 연산을 비교적 간단하게 만들어준다.

 

 

 


 

 

 

CNN 구현 흐름 시각화 / 원하는 만큼 Conv, Pooling 계층 진행 후 완전연결계층 진행

 

 

 

 

 

 

 

 


 

 

 

 

 

 

 

층 깊이에 따른 특징 추출 변화

 

딥러닝의 흥미로운 점은 합성곱 계층이 여러 개 쌓이면서 층이 깊어질수록 더 복잡하고 추상화된 정보가 추출된다는 것이다. 위의 이미지를 보면 Conv1층은 단순하게 Edge에만 반응하지만 Conv3층은 텍스쳐에 반응하고 Conv5는 사물의 일부에 반응하도록 변화된다. 즉, 층이 깊어지면서 뉴런이 반응하는 대상이 단순한 모양에서 고급 정보로 변화해간다. 따라서 합성곱 계층이 깊어지면 깊어질수록 신경망은 사물의 의미를 이해하게 된다.

 

 

 

 

 

앞서 설명했던 가중치 초깃값의 중요성은 결론적으로 활성화된 값들이 고르게 분포되기 위함이었다.

그렇다면 초기에 설정하는 가중치값 말고 또 활성화 값들을 고르게 만들 수는 없을까?

 

 

배치 정규화 (Batch Normalization)

이러한 생각에서 시작된 것이 배치 정규화(Batch Normalization)이다. 배치 정규화는 활성화 함수의 출력값(활성화값)을 정규화하는 작업을 뜻한다. 배치 정규화의 장점은 다음과 같다.

 

  • 학습을 빨리 진행할 수 있다. (학습 속도 개선)
  • 가중치 초깃값에 크게 의존하지 않는다.
  • 오버피팅을 억제한다.

장점이 너무 어마어마해서 다들 많이 쓰는것 같다. 앞에서 말했던 가중치 초깃값도 별로 중요치 않고, 오버피팅을 억제하기 위해 후에 기술할 드롭 아웃, 가중치 감소 등도 굳이 사용하지 않아도 되니 말이다. 이게 바로 3 in 1

 

배치 정규화를 사용한 신경망

 

위의 이미지가 배치 정규화를 사용한 신경망이다. 여기서 Batch Norm이 배치 정규화 계층으로, 학습 시 미니배치를 단위로 정규화를 한다. 구체적으로 데이터 분포가 평균이 0, 분산이 1이 되도록 정규화한다.

 

이 Batch Norm이 활성화 함수의 전에 있어야 하는지, 후에 있어야 하는지에 대한 것은 논의 중이라고 하는데 자세하게 알게 되면 추가하겠다. 우선 위의 신경망은 Batch Norm의 활성화 함수 이전에 넣었다. 이렇게 Batch Norm을 신경망 구현에 사용하게 되면 데이터가 덜 치우치게 분포하게 되고, 또한 배치 정규화 계층마다 이렇게 정규화된 데이터를 확대, 이동하기도 한다.

 

Batch Norm도 확대와 이동의 하이퍼파라미터를 설정해야하는데, 현재 대다수의 완성된 신경망은 적절한 Batch Norm 하이퍼파라미터를 내장하고 있기 때문에 크게 신경쓰지 않아도 된다. 하지만 직접 만들겠다면 다른 얘기

 

 

 

 

 

 


 

 

 

 

 

 

과적합, 오버피팅 (Overfitting)

오버피팅이란 신경망이 훈련 데이터에만 지나치게 적응되어 그 외의 데이터에는 제대로 대응하지 못하는 상태를 말한다. 오버피팅은 매객변수가 많고 표현력이 높은 모델일수록, 훈련 데이터가 적을수록 많이 일어난다.

 

train data에 대해서는 정확도가 높지만 test 데이터에서는 낮은 이러한 상황을 overfitting, 과적합이라고 한다.

 

 

 

가중치 감소 (Weight Decay)

오버피팅을 억제하는 방법으로 가중치 감소가 있다. 이는 학습 과정에서, 큰 가중치에 대해서는 그에 상응하는 큰 페널티를 부과하여 오버피팅을 억제하는 방법이다. 오버피팅은 가중치 매개변수의 값이 커서 발생하는 경우가 크기 때문에 가중치 감소는 예로부터 오래 써온 오버피팅 억제 방법이며 모든 가중치 각각의 손실함수에 L2노름(각 원소의 제곱들을 더해서 루트를 씌운 값)을 더하여 구한다.

 

 

 

드롭아웃 (Dropout)

가중치 감소는 간단하게 구현할 수 있고 어느정도 과적합을 방지할 수 있지만 신경망이 깊어지고 복잡해질수록 과적합을 막기 어려워진다. 이러한 경우, 드롭아웃을 사용한다.

 

드롭아웃이란 런을 임의로 삭제하면서 학습하는 방법으로 훈련 시 은닉층의 뉴런을 무작위로 골라 삭제한다. 삭제된 뉴런은 신호를 전달하지 않기 때문에 학습에 사용되지 않는다. 이렇게 학습을 반복할 때마다 무작위로 뉴런을 삭제하며 학습을 하다가 실제 시험 데이터에서는 모든 뉴런에 신호를 전달한다. 시험 때는 각 뉴런의 출력에 훈련 때 삭제 안 한 비율을 곱하여 출력한다.

 

 

드롭아웃은 기계학습의 앙상블 학습(Ensemble Learning)과 비슷하다. 앙상블 학습이란 개별적으로 학습시킨 여러 모델의 출력을 평균 내 추론하는 방식이다. 드롭아웃 또한 학습 때 뉴런을 무작위로 삭제하는 행위가 매번 다른 모델을 학습시키는 것과 같은 맥락으로 이해할 수 있다. 또한 추론 시, 뉴런의 출력에 삭제한 비율을 곱함으로써 앙상블 학습에서 여러 모델의 평균을 내는 것과 같은 효과를 얻을 수 있다. 따라서 드롭아웃은 앙상블과 같은 효과를 신경망에 적용했다고 볼 수 있다.

 

 

 

+ Recent posts