-
이 강의에서는
두 번째 RNN에 대한 이야기를 하겠습니다
-
분류 작업을 중심적으로 보도록 하죠
-
이전 강의에서
일반적 RNN 모델에 대해 배웠죠
-
먼저 x를 입력으로 넣으면
-
간단한 룩업 테이블인
임베딩층을 거쳐서 나오는
-
임베딩 출력값을
RNN의 입력으로 사용합니다
-
그리고 맨 위쪽에는
완전연결층을 추가해서
-
출력값들을 생성합니다
-
작업의 종류에 따라서
다른 손실 함수를 사용할 수 있는데요
-
여기에서 우리가 사용한 예시에서는
크로스-엔트로피를 사용했습니다
-
좀 더 정확하게 말하자면
우리는 시계열 입력을 사용했습니다
-
각 셀은 출력값을 생성하고
-
주어진 레이블과 이 출력값을
비교해볼 수 있죠
-
따라서 손실을 계산할 수 있습니다
-
그리고 이 손실을 가지고
모델을 훈련시키는데
-
이것이 일반적으로 RNN에서
손실을 구하고 훈련시키는 방법입니다
-
다양한 방법으로
RNN을 연결할 수 있는데요
-
굉장히 다양한 모델과
응용 방법을 고려할 수 있죠
-
이 강의에서는 한 가지 모델에
대해서만 보도록 할 것인데요
-
RNN 분류라고 불리는 것에
대한 내용입니다
-
이러한 형태로 모든
RNN을 연결할 수 있죠
-
시계열 입력을 사용할 수 있습니다
-
하지만 여기서는 마지막에 나오는
출력값만 사용할 것입니다
-
이것을 출력으로 사용하는 것이죠
-
이러한 형태는 종종
시계열 기반 분류나
-
감성 분석 등에 사용됩니다
-
이번 강의에서는 이 RNN 모델로
이름 분류 작업을 해보도록 하겠습니다
-
우리가 사용한 데이터는
이러한 형태이고요
-
주어진 이름들이 있고
-
각각에 해당하는 나라들이 있습니다
-
예를 들어서 Malouf라는 사람은
아랍에서 온 사람이죠
-
Taerajima라는 사람은
일본인이고요
-
Huie는 중국인입니다
-
따라서 주어진 이름에 대해
나라를 예측해보려 합니다
-
이 데이터셋에는
18개의 나라가 있기 때문에
-
이 18개 나라에 대한
소프트맥스 출력값을 만들게 됩니다
-
입력값으로는
전체 이름을 넣을 것인데요
-
이것을 각 글자로 나누어서
-
각 셀에 한 글자씩
넣어주게 될 것입니다
-
이게 우리가 사용할
기본적 모델입니다
-
이 모델을 디자인할 때
-
확실하게 해야 할 한 가지는
-
어떻게 입력 공간을
디자인하는지에 대한 것입니다
-
이 경우에 각 셀에 대한
입력값은 각 글자가 됩니다
-
그러면 RNN에 들어가게 될
각 입력은 어떻게 될까요?
-
물론 나중에
임베딩도 사용하게 될 겁니다
-
이건 룩업 테이블의 일종이죠
-
주어진 글자에 대해서
각 인덱스를 가지게 됩니다
-
따라서 각각의 고유한 글자를
구분할 수 있게 되죠
-
그리고 어떤 단어를 만들면
-
이 단어의 식별자를
룩업 테이블에서 찾을 수 있습니다
-
이 경우에는 각 입력이
영어 알파벳이라는 것을 알기 때문에
-
여기서는
아스키 코드를 사용할 수 있습니다
-
따라서 이것을
인덱스로 바로 사용할 수 있어요
-
예를 들어서
여기에 있는 'a'에 대한
-
아스키 코드는 97이 되고
-
97은 임베딩에서의
값을 찾는데 사용됩니다
-
따라서 임베딩의 출력값은
이런 형태가 되죠
-
그리고 이 값들이
RNN의 입력으로 사용됩니다
-
예를 들어 보겠습니다
-
adylov라는 이름이
있다고 해봅시다
-
특정한 이름이 있고
-
이것을 각 글자로 나눕니다
-
그리고 각 글자에 대한
아스키 코드를 식별자로 사용하는 거죠
-
이제 이 값을 인덱스로 사용합니다
-
룩업 테이블인
임베딩층을 통과시키면
-
97과 100이라는 인덱스는
-
룩업 테이블로부터
이러한 값을 얻게 됩니다
-
물론 이 값은 임베딩에서
훈련될 수 있는 값입니다
-
이제 이 값을 입력값으로 사용합니다
-
이 입력값은 역전파를 이용해
훈련될 수 있다는 것이죠
-
소스 코드에서는
어떻게 이 글자들을
-
아스키 코드로 변환할까요?
-
코드 한 줄이면 할 수 있는데요
-
주어진 이름에 대해서
-
각 글자를 얻어낸 다음에
-
ord 함수를 호출합니다
-
이 함수는 아스키 코드를 반환해주죠
-
그리고 이것에
리스트 변환을 사용하면
-
배열을 생성할 수 있고
이것을 반환하면 됩니다
-
이제 임베딩은 상당히 간단해지죠
-
간단하게 여기서
임베딩 레이어를 하나 생성하는데
-
input_voc_size를 넣어주죠
-
단어의 개수가 몇 개인지를
나타내는 값입니다
-
아스키 코드의 개수가 되겠죠
-
그리고 여기는
출력의 크기인데요
-
이 부분은 결국
RNN의 입력값이 될 겁니다
-
따라서 RNN 입력 크기이면서
임베딩의 출력이 되는 것이죠
-
이제 이 레이어를 생성한 후에
-
입력을 넣어줍니다
-
따라서 임베딩의
출력값이 만들어지죠
-
그리고 이 값이
RNN의 입력값으로 들어갑니다
-
전체 소스코드를 보도록 하죠
-
이 부분이 메인 모듈이 되는데요
-
우리가 RNN 분류기를
생성한다고 해 봅시다
-
먼저 주어진 하이퍼파라미터에 대해서
RNN을 생성합니다
-
주어진 이름에 대해서
해야하는 것은
-
아스키 배열을 만드는 것인데요
-
이 배열을
tensor와 variable에 넘기고
-
PyTorch Variable이죠
-
이것을 입력값으로 사용합니다
-
그리고 이 값을 앞으로 넘겨줍니다
-
분류기에 넣어주는 것이죠
-
이렇게 출력값을 얻을 수 있습니다
-
여기서 계수를 주의 깊게
볼 필요가 있는데요
-
입력과 출력의 형태이죠
-
입력값은
6개의 글자로 이루어지고요
-
입력의 개수는 1개입니다
-
따라서 (1, 6)이 되죠
-
그리고 출력에 대해서는
18개의 다른 출력값을 만들 겁니다
-
18개의 나라를
예측하고자 하기 때문이죠
-
출력은 이렇게 되죠
-
이제 RNNClass를 봅시다
-
초기화하는 부분에서는
여러 개의 하이퍼파라미터가 있습니다
-
그리고 하이퍼파라미터를 설정한 후
세 개의 레이어를 만드는데요
-
가장 먼저 임베딩이 나옵니다
-
임베딩은 input_size와
hidden_size를 넣어줍니다
-
hidden_size는
RNN 입력의 크기가 되죠
-
여기에서는 RNN으로
GRU를 사용할 건데요
-
여기서도 입력의 크기와
출력의 크기를 정해 줍니다
-
이 RNN의 경우에는
서로 동일한 크기를 가집니다
-
hidden_size를
입력의 크기로 사용함과 동시에
-
hidden_size를
출력의 크기로도 사용합니다
-
그리고 가장 윗부분에
완전연결층을 연결합니다
-
여기서는 선형 레이어을 사용하는데요
-
입력의 크기로 hidden_size를 사용하고
-
출력의 크기로 18을 사용하죠
-
출력의 크기로 18을 사용하죠
-
이렇게 여러 레이어를 정의하고
-
forward 모듈에서는
이것들을 모두 연결합니다
-
적절한 순서로
적절한 입력값을 넣어줍니다
-
여기서 입력을 받는데요
-
입력은 여기서 생성됩니다
-
이 값을 입력값으로 사용합니다
-
입력값에 대해 더 자세히 보자면
-
입력의 형태는
배치 크기 × 시퀀스 크기가 됩니다
-
여기서 배치의 크기는 1이고
-
6개의 글자를 사용하기 때문에
-
이 값이 입력의 크기가 되죠
-
하지만 우리가
사용하는 GRU에서는
-
여기에서 batch_first를
사용하지 않았기 때문에
-
GRU의 입력값은
이런 형태가 됩니다
-
따라서 이렇게 시퀀스
배치, 입력값을 얻게 됩니다
-
이것이 GRU를 통해
얻을 수 있는 값이죠
-
이제 우리가 먼저 해야할 것은
-
이 input을 전치시키는 것인데요
-
따라서 B × S를 S × B로 바꿀 수 있죠
-
이것을 전치시킨 후에
-
이 입력값을
임베딩에 바로 넣어줍니다
-
출력값은
이렇게 될 것입니다
-
임베딩된 출력값은
S × B × I가 될 것입니다
-
이제 이것을
GRU의 입력값으로 사용하게 되죠
-
이 값이 입력값이 됩니다
-
그리고 은닉 값도 만들어야 하는데요
-
초기 은닉 값으로 넣어줘야 하죠
-
마지막으로 볼 부분은
출력값을 넣어주는 부분입니다
-
마지막 출력값을
완전연결층에 넣어주는 거죠
-
여기서 약간의
트릭을 사용할 것인데요
-
마지막 레이어에서의
출력값은
-
또 다른 은닉값이 됩니다
-
기본적으로는
동일한데요
-
따라서 이 은닉값을
입력값으로 사용할 것입니다
-
여기서 이렇게 은닉값이
각 셀의 입력값으로 사용되었죠
-
그리고 다시 은닉값을 반환합니다
-
이 은닉값은 이제
완전연결층의 입력으로 사용됩니다
-
그리고 이것의 출력값을
마지막에 반환합니다
-
물론 이 출력값도 사용할 수 있습니다
-
모든 내용을 포함하는
것이기 때문인데요
-
이 마지막 출력값만을
은닉값과 같은 것으로 사용할 수 있습니다
-
간단하게 만들기 위해서는
-
이 은닉값을 완전연결층의
입력값으로 사용하면 됩니다
-
다시 한 번 주의깊게
보아야 할 부분은 이 부분입니다
-
전치시키는 과정과
-
입력값을 올바른 계수로 만드는 부분이죠
-
여기에 초기 입력값이 있고요
-
주어진 입력값은
이렇게 생겼죠
-
이 값을 전치시킬 건데요
-
전치시킨 후에
얻게되는 입력값은
-
이런 형태가 되겠죠
-
이제 룩업 테이블을 봅시다
-
여기서 얻어진 embedded가
이렇게 될 겁니다
-
이 임베딩은
RNN의 입력값이 되겠죠
-
하지만 우리는 한 번에
여러 개의 이름을 다룰 수 있어야 합니다
-
여러 개의 주어진 이름에 대해
-
반복문을 이용해서
한 개씩 처리할 수 있겠죠
-
하지만 그리 효율적이진 않습니다
-
배치를 이용하면
이 모든 입력을 다룰 수 있습니다
-
이것들을 모두 합칠 수 있는 겁니다
-
여기서 한 가지 문제는
-
이 부분에서 볼 수 있듯이
-
이 값이 입력값의 크기인데요
-
각 이름
즉 각 문자열의 길이를 의미하죠
-
여기에 보이는 것처럼
-
다른 이름은
길이가 달라질 수 있죠
-
따라서 이 부분에
어떠한 것을 넣어줘야 하는 거죠
-
배치를 사용하기 위해서 말입니다
-
한 개씩 처리해도
문제는 없지만
-
배치로 만들어 사용한다면
-
넣어주어야 하는 값에서
약간 문제가 생기겠죠
-
가장 일반적인 해결 방법은
제로 패딩이라는 것인데요
-
이 공간에 0을 넣는 방법입니다
-
이 부분이 문자열, 즉 이름의
끝부분을 표시해주는 것이죠
-
이 코드 세 줄로
이것을 구현할 수 있습니다
-
주어진 시퀀스와
주어진 문자열 길이에 대해서
-
제로 패딩된 출력값을 생성할 수 있죠
-
먼저 모두 0인 상태에서 시작해서
-
실제 값을 넣어줍니다
-
따라서 제로 패딩 처리된
배치 행렬을 얻을 수 있습니다
-
이런 형태를 가지고 있을 때
-
동일한 방법으로 여기에
-
embedding을 호출할 수 있는데요
-
따라서 임베딩을 생성해주죠
-
예를 들어 각 이름에 대해서
임베딩을 생성하게 되면
-
이 임베딩을 RNN의 입력으로
바로 사용할 수 있게 됩니다
-
이 부분은 이제 헬퍼 함수들을
포함해 구현된 부분입니다
-
예를 들어 이 부분은
문자열을 아스키 배열로 바꿔주고요
-
이 부부은 제로 패딩에 대한 것이죠
-
이 부분은 이름을
variable로 만들어주는 추가적인 부분입니다
-
따라서 이 두 가지 함수를
사용하고 있죠
-
이제 메인 함수에서는
훨씬 간단합니다
-
이렇게 이름들이 있고
-
이 make_variable을 이용해
배치 입력값을 만듭니다
-
그리고 이것을
분류기에 넣어주면
-
출력값을 얻을 수 있습니다
-
입력값은 이런 형태가 되는데
-
최대 크기는 제로 패딩을 포함한
최대 크기를 사용합니다
-
이 부분은 이름의 개수를 나타내죠
-
이 경우에는 배치의 크기가
4임을 나타냅니다
-
따라서 출력의 크기는 18이면서
-
4개의 다른
출력값들을 가지게 됩니다
-
각 이름에 대한 예측값이 되겠죠
-
훈련시키기 위해서는
Adam 최적화와
-
크로스-엔트로피 손실 함수를
사용할 수 있습니다
-
손실값의 criterion으로
-
예측값인 출력값과
실제 레이블 타겟을 사용하여
-
손실값을 계산합니다
-
그리고 역전파인 backward를 수행하면서
-
이 최적화로
훈련시킨 변수를 갱신해 줍니다
-
크기가 큰 행렬을 다룰 때에는
-
PackedSequnce를 사용할 수 있는데요
-
RNN 연산을
더 효율적으로 만들어 줍니다
-
PyTorch에서 제공하는
유틸리티 함수를 사용할 수 있는데요
-
꽤나 간단합니다
-
먼저 입력 배치를
길이에 따라 정렬해야 하는데요
-
가장 긴 것부터
나열하면 됩니다
-
물론 배치가 가지는
전체 길이는 최대 길이가 되겠죠
-
이제 이것을 나열하고
-
임베딩과 같은
전처리를 수행합니다
-
그리고 나서
packing이라는 메소드를 호출합니다
-
그러면 이렇게 모두
하나로 합쳐진 것을 얻을 수 있습니다
-
이것을 RNN의
입력으로 사용할 수 있는데요
-
이렇게 합쳐진 입력값은
-
이 RNN을 훨씬
효율적으로 만들어줍니다
-
그렇게 출력값을 생성하는데요
-
이 출력값을 다시 언팩킹해서
-
바로 이 출력값을
사용할 수 있게 만듭니다
-
깃헙 레포지토리에서
더 많은 예시를 보실 수 있습니다
-
이것들을 모두
더 효율적으로 만들어 볼 수 있겠죠
-
이 슬라이드에는
팩킹과 언팩킹의 개념과
-
RNN에서 일반적인 형태의
배치 입력과의 차이를 보여줍니다
-
동작을 효율적으로 만드는
또 다른 방법은
-
GPU를 사용하는 것입니다
-
1개 이상의 GPU를 가지고 있다면
데이터 병렬성을 활용할 수 있습니다
-
PyTorch에서 GPU를 사용하는 것은
-
굉장히 간단합니다
-
이 두 단계를
따르기만 하면 되는데요
-
먼저 모든 변수를 GPU로 복사합니다
-
tensor.cuda()를 사용해서
변수를 생성하면 됩니다
-
두 번째 단계는
모델을 GPU에 복사해 넣습니다
-
그러면 각 모델은 GPU로부터
데이터를 바로 읽어들일 수 있죠
-
이 모델을 사용한다면
-
여기에
model.cuda()를 호출하면 됩니다
-
그러면 모델을
GPU에 넣을 수 있습니다
-
이제 이 분류기를 그대로 사용해도
아무런 문제가 없습니다
-
1개보다 많은 GPU를 가지고 있다면
-
병렬성을 활용할 수 있는데요
-
이것을 사용하는 방법 또한
매우 간단합니다
-
가장 먼저 이런 식으로
분류기를 생성하고
-
이 부분은
실제 분류기입니다
-
그리고 이 분류기를
데이터 병렬로 감싸줍니다
-
그러면 또 다른
분류기를 생성하는데
-
이제 이 분류기를 사용하면 되죠
-
일단 입력 데이터를
넣어주기만 하면
-
자동적으로 GPU 개수로
나누어 줄 겁니다
-
그리고 PyTorch는
자동으로 출력값을 병합해주죠
-
매우 편리합니다
-
이 데이터 병렬성에 대해
더 알고 싶으시다면
-
PyTorch 문서에 있는
저희가 만든 튜토리얼을 읽어보면 됩니다
-
연습 문제에서 이 강의에서
다른 모든 부분을 해볼 수 있는데요
-
GPU를 사용하고
데이터 병렬성을 사용하고
-
pad-pack을 사용해서
-
이름 분류를 구현할 수 있습니다
-
동일한 형태의
모델을 사용해
-
더 많은 일을 할 수 있습니다
-
예를 들어 영화 리뷰에 대한
감성 분석에 적용할 수 있죠
-
여기에 kaggle의 실제 데이터가 있습니다
-
이 데이터를 다운로드 받아서
-
주어진 리뷰에 대한
감정을 예측할 수 있을 겁니다
-
이 5개의 레이블 중 하나가 되겠죠
-
다음 강의에서는 RNN으로
언어 모델링을 해보도록 하겠습니다