핵심 키워드
- #말뭉치 #토큰 #원-핫 인코딩 #단어 임베딩
- 텐서플로를 사용해 순환 신경망을 만들어 영화 리뷰 데이터셋에 적용해서 리뷰를 긍정과 부정으로 분류하자
IMDB 리뷰 데이터셋
- IMDB 리뷰 데이터셋
유명한 인터넷 영화 데이터베이스인 imdb.com에서 수집한 리뷰를 감상평에 따라 긍정과 부정으로 분류해 놓은 데이터셋
이러한 텍스트 자체는 신경망에 전달하지 않고 데이터에 등장하는 단어마다 고유한 정수를 부여하는 방법을 사용
일반적으로 영어 문장은 모두 소문자로 바꾸고 구둣점을 삭제한 다음 공백을 기준으로 분리하며, 이렇게 분리 된 단어는 토큰
토큰에 할당하는 정수 중에 몇 개는 특정 용도로 예약되어 있는 경우가 많음
0은 패딩, 1은 문장의 시작, 2는 어휘 단어에 없는 토큰을 나타냄
+) 자연어 처리 (Natural Language Processing, NLP)
자연어 처리는 컴퓨터를 사용해 인간의 언어를 처리하는 분야이며, 대표적인 세부 분야로는 음성 인식, 기계 번역, 감성 분석
IMDB 리뷰를 감상평에 따라 분류하는 작업은 감성 분석에 해당하며, 자연어 처리 분야에서의 훈련 데이터는 말뭉치라고 불리며
여기에서 IMDB 리뷰 데이터셋이 하나의 말뭉치가 되는 것이며, 훈련 세트에서 고유한 단어를 뽑아 만든 목록은 어휘 사전이라 함
- 케라스로 IMDB 데이터 불러오기
IMDB 리뷰 데이터셋은 영어로 된 문장이지만 이미 텐서플로에서 정수로 바꾼 데이터가 포함되어 있으므로
imdb 모듈을 임포트하여 이 데이터를 적재한 후, 가장 자주 등장하는 단어 500개만 사용하도록 num_words 매개변수를 지정
훈련 세트와 테스트 세트의 크기를 확인해보면 각각 25000개의 1차원 배열 샘플로 이루어져 있음
IMDB 리뷰 텍스트는 길이가 제각각이므로 따라서 고정 크기의 2차원 배열에 담기 보다는
리뷰마다 별도의 파이썬 리스트로 담아야 메모리를 효율적으로 사용할 수 있으므로 1차원 배열 샘플로 이루어지며
이 데이터는 개별 리뷰를 담은 파이썬 리스트 객체로 이루어진 넘파이 배열이 됨
첫 번째 리뷰에 담긴 내용을 출력해보면 IDMB 리뷰 데이터가 이미 정수로 변환되어 있는 것을 볼 수 있으며
num_words=500으로 지정했기 때문에 어휘 사전에는 500개의 단어만 들어가 있으므로
어휘 사전에 없는 단어는 모두 2로 표시되어 나타나는 것을 볼 수 있음
해결할 문제는 리뷰가 긍정인지 부정인지 판단하는것이므로 이는 이진 분류 문제로 볼 수 있으므로
타깃 데이터를 출력하면 타깃값이 0(부정)과 1(긍정)으로 나누어진 것을 볼 수 있음
- 훈련 세트 준비하기
훈련 세트에서 20%를 검증 세트로 떼어 놓으면 훈련 세트의 크기는 20000개로 줄어들게 됨
훈련 세트에 대해 각 리뷰의 길이를 계산해 넘파이 배열에 담은 후,
평균적인 리뷰의 길이와 가장 짧은 리뷰의 길이 그리고 가장 긴 리뷰의 길이를 확인하도록 함
리뷰의 평균 단어 개수는 239개이고 중간값은 178인 것을 보아 이 리뷰 길이 데이터는 한쪽에 치우친 분포를 보일 것임
히스토그램으로 표현해보면 한쪽으로 치우친 것을 볼 수 있으며,
평균이 중간값보다 높은 이유는 오른쪽 끝에 아주 큰 데이터가 있기 때문임을 알 수 있음
하지만 대부분의 리뷰는 짧으므로 중간값보다 훨씬 짧은 100개의 단어만 사용하도록 함
- 시퀀스 패딩
100개의 단어만 사용할 때, 100개의 단어보다 작은 리뷰가 있을 때는
이런 리뷰들의 길이를 100에 맞추기 위해 패딩이 필요하므로 패딩을 나타내는 토큰으로 0을 사용
케라스는 시퀀스 데이터의 길이를 맞추는 pad_sequences() 함수를 제공하므로 maxlen에 원하는 길이인 100을 지정
이후 train_seq의 크기를 확인해보면 (20000, 100) 크기의 2차원 배열이 되게 됨
train_seq에 있는 첫 번째 샘플을 출력해보면 앞뒤에 패딩값 0이 없으므로 100보다는 길었을 것을 알 수 있음
원래 샘플의 앞부분이 잘렸는지, 뒷부분이 잘렸는지 알아보기 위해 train_input에 있는 원본 샘플의 끝을 확인해보면
train_seq[0]의 출력값과 정확히 일치하므로 샘플의 앞부분이 잘렸다는 것을 짐작할 수 있음
pad_sequences() 함수는 기본으로 maxlen보다 긴 시퀀스의 앞부분을 자름
이는 일반적으로 시퀀스의 뒷부분의 정보가 더 유용하리라 기대하기 때문이며
만약 시퀀스의 뒷부분을 잘라내고 싶다면 truncating 매개변수의 값을 기본값 'pre'가 아닌 'post'로 변경하면 됨
train_seq에 있는 여섯 번째 샘플을 출력해보면 앞부분에 0이 있는 것을 보아 이 샘플의 길이는 100이 안되는 것을 알 수 있음
역시 위와 같은 이유로 패딩 토큰은 시퀀스의 뒷부분이 아닌 앞부분에 추가된 것
테스트 세트와 마찬가지로 검증 세트의 길이도 100으로 맞추면 훈련 세트와 검증 세트 준비 완료
순환 신경망 만들기
- 순환 신경망 모델 만들기
케라스는 여러 종류의 순환층 클래스를 제공하며, 그 중에서 가장 간단한 것은 SimpleRNN 클래스
SimpleRNN 클래스를 사용하기 위한 첫 번째 매개변수는 사용할 뉴런의 개수이며 input_shape에 입력 차원 (100, 500) 지정
첫 번째 차원이 100인 것은 앞에서 샘플의 길이를 100으로 지정했기 때문이며
두 번째 차원이 500인 이유는 500개의 단어만 사용하도록 지정했기 때문에 고유한 단어는 모두 500개이므로
원-핫 인코딩으로 표현하려면 배열의 길이가 500이어야 하며, 이로 인해 정수 하나마다 모두 500차원의 배열로 변경되었기 때문
활성화 함수로는 SimpleRNN 클래스의 activation 매개변수의 기본값인 'tanh'로 하이퍼볼릭 탄젠트 함수 사용
- 원-핫 인코딩
train_seq와 val_seq처럼 토큰을 정수로 변환한 데이터를 신경망에 주입하면 큰 정수가 큰 활성화 출력을 만들어내는 문제 발생
예) 10 x w(가중치) = 큰 활성화 출력
하지만 이 정수 사이에는 20번 토큰을 10번 토큰보다 더 중요시해야 할 어떤 관련이 없으므로
단순한 정숫값을 신경망에 입력하기 위해서 정숫값에 있는 크기 속성을 없애고 각 정수를 고유하게 표현하는 원-핫 인코딩 사용
원-핫 인코딩은 정숫값을 배열에서 해당 정수 위치의 원소만 1이고 나머지는 모두 0으로 변환하게 됨
케라스에서는 원-핫 인코딩을 위한 유틸리티인 to_categorial() 함수를 제공하며
정수 배열을 입력하면 자동으로 원-핫 인코딩된 배열을 반환해줌
정수 하나마다 모두 500차원의 배열로 변경되었기 때문에 (20000, 100) 크기의 train_seq가 (20000, 100, 500) 크기로 변환됨
이렇게 샘플 데이터의 크기가 1차원 정수 배열 (100, )에서 2차원 배열 (100, 500)으로 바꿔야 하므로
위의 SimpleRNN 클래스의 input_shape 매개변수의 값을 (100, 500)으로 지정한 것
원-핫 인코딩으로 변환된 train_oh의 첫 번째 샘플의 첫 번째 토큰 10이 잘 인코딩되었는지 확인해보면
처음 12개 원소 중 열 한 번째 원소가 1인 것을 확인할 수 있으며, 나머지 원소를 모두 더하면 1이므로
열한 번째 원소만 1이고 나머지는 모두 0이어서 원-핫 인코딩이 잘된 것을 볼 수 있음
같은 방식으로 val_seq도 원-핫 인코딩으로 변환
- 모델 구조 확인
SimpleRNN에 전달할 샘플의 크기는 (100, 500)이지만 이 순환층은 마지막 타임스텝의 은닉 상태만 출력하므로
출력의 크기가 순환층의 뉴런 개수와 동일한 8임을 확인할 수 있음
순환층에 사용된 모델 파라미터의 개수는
원-핫 인코딩 배열이 순환층의 뉴런 8개와 완전히 연결되기 때문에 총 500 x 8 = 4000개의 가중치가 있게 되고
순환층의 은닉 상태는 다시 다음 타임스텝에 사용되기 때문에 또 다른 가중치인 8 (은닉 상태 크기) x 8 (뉴런 개수) = 64개
마지막으로 뉴런마다 하나의 절편이 있으므로 모두 4000 + 64 + 8 = 4072개의 모델 파라미터가 필요함
밀집층은 뉴런이 1개이므로 8 x 1 + 1 = 9개의 모델 파라미터가 필요함
순환 신경망 훈련하기
- 모델 훈련
RMSprop 객체를 만들어 학습률을 0.0001로 지정하고 에포크 횟수를 100으로 늘리고 배치 크기는 64개로 설정
그 밖에 체크포인트와 조기 종료를 구성하는 코드를 작성하여 훈련
이 훈련은 마흔 번째 에포크에서 조기 종료되며 검증 세트에 대한 정확도는 약 80% 정도
매우 뛰어난 성능은 아니지만 감상평을 분류하는데 어느 정도 성과를 내고 있다고 판단할 수 있음
그리고 훈련 손실와 검증 손실을 그래프로 그려서 훈련 과정을 살펴봄
단어 임베딩을 사용하기
- 임베딩
위의 훈련에서는 입력 데이터를 원-핫 인코딩으로 변환하였으나
원-핫 인코딩의 단점으로 인해 토큰 1개를 500차원으로 늘리기 때문에 입력 데이터가 엄청 커지게 됨 (20000 x 100 x 500)
그러므로 토큰의 개수와 어휘 사전의 개수를 늘리기 힘들어짐
그러므로 순환 신경망에서 텍스트를 처리할 때 즐겨 사용하는 방법인 단어 임베딩을 사용
단어 임베딩은 각 단어를 고정된 크기의 실수 벡터로 바꾸어주며
이런 단어 임베딩으로 만들어진 벡터는 원-핫 인코딩된 벡터보다 훨씬 의미 있는 값으로 채워지므로
자연어 처리에서 더 좋은 성능을 내는 경우가 많음
케라스에서는 Embedding 클래스로 임베딩 기능을 제공
Embedding 클래스의 첫 번째 매개변수는 어휘 사전의 크기이므로 500으로 지정
두 번째 매개변수는 임베딩 벡터의 크기이므로 16개의 벡터로 토큰 하나를 출력하도록 16으로 지정
세 번째 매개변수는 시퀀스의 길이를 나타내므로 앞서 샘플의 길이를 100으로 맞추어 train_seq를 만들었으므로 100으로 지정
그다음 SimpleRNN 층과 Dense 층은 이전과 동일
이 모델은 (100, ) 크기의 입력을 받아 (100, 16) 크기의 출력을 만들며
Embedding 클래스는 500개의 각 토큰을 크기가 16인 벡터로 변경하기 때문에 총 500 x 16 = 8000개의 모델 파라미터를 가짐
그다음 SimpleRNN 층은 임베딩 벡터의 크기가 16이므로 8개의 뉴런과 곱하기 위해 필요한 가중치 16 x 8 = 128개를 가지며
또한 은닉 상태에 곱해지는 가중치인 8 x 8 = 64개와 8개의 절편이 있으므로 순환층에 있는 전체 모델 파라미터의 개수는 200개
마지막 Dense 층의 가중치는 이전과 동일하게 8 x 1 + 1 = 9개
훈련을 해보면 원-핫 인코딩보다 SimpleRNN에 주입되는 입력의 크기가 크게 줄었지만
임베딩 벡터는 단어를 잘 표현하는 능력이 있기 때문에 훈련 결과는 이전과 못지 않은 비슷한 성능을 내는 것을 볼 수 있음
반면에 순환층의 가중치 개수는 훨씬 작고 훈련 세트의 크기도 훨씬 줄어들게 됨
'ML > 혼자 공부하는 머신러닝 + 딥러닝' 카테고리의 다른 글
혼자 공부하는 머신러닝 + 딥러닝 - 목차 (0) | 2023.07.03 |
---|---|
[혼공머신] 09. 텍스트를 위한 인공 신경망 - LSTM과 GRU 셀 (0) | 2022.07.21 |
[혼공머신] 09. 텍스트를 위한 인공 신경망 - 순차 데이터와 순환 신경망 (0) | 2022.07.19 |
[혼공머신] 08. 이미지를 위한 인공 신경망 - 합성곱 신경망의 시각화 (0) | 2022.07.14 |
[혼공머신] 08. 이미지를 위한 인공 신경망 - 합성곱 신경망을 사용한 이미지 분류 (0) | 2022.07.13 |