딥러닝(AI학습 46)

이유진·2024년 7월 8일

--28.순환신경망 - 분류.ipynb--

RNN(순환신경망) 으로 분류하기

IMDB 데이터 셋 : 영화 리뷰 데이터 셋

두가지 방법으로 순환신경망에 입력

1. One-hot encoding

2. Word Embedding

IMDB 리뷰 데이터셋

  • imdb.com 에서 수집한 영화 리뷰를 감상평에 따라 '긍정' 과 '부정' 으로 분류해 놓은 데이터 셋
  • 총 50,000개의 샘플
  • 훈련:테스트 => 25,000개:25,000개

토큰 (token)

'''
He follows the cat. He loves the cat
↓ ↓ ↓ ↓ ↓ ↓ ↓ ↓
10 11 12 13 10 14 12 13

각 단어마다 고유한 정수값 부여. 동일 단어에는 동일한 정수 매핑
단어의 의미나 크기와는 관련 없다.

일반적으로, 영어문장의 경우 모두 소문자로 바꾸고 구둣점등을 삭제한 다음 공백을 기준으로 단어 분리
이렇게 분리된 단어를 토큰(token) 이라 부릅니다.

하나의 샘플은 여러개의 토큰으로 이루어져 있고, '1개의 토큰'이 '하나의 타임 스텝'에 해당된다.

※ 간단한 문제라면 영어 말뭉치에서는 '토큰' 과 '단어'는같다고 보아도 무방하나, 한글은 다릅니다
한글은 조사, 어미변형등이 발달되어 있어서 단순히 공백으로 나누는 것만으로는 부족하기에
반드시 형태소 분석을 통해 토큰을 만들어야 합니다 → KoNLPy 사용
'''
None

'''
토큰에 할당되는 정수중 특별한 용도로 예약되어 있는 경우도 있다
ex)
0 - 패딩
1 - 문장의 시작
2 - 어휘 사전에 없는 토큰

'어휘사전' : 훈련세트에서 '고유한 단어'를 뽑아 만든 목록
'''
None

import pandas as pd
import numpy as np
import matplotlib.pyplot as plt
import os

import tensorflow as tf
from tensorflow import keras

실행마다 동일한 결과를 얻기 위해 케라스에 랜덤 시드를 사용하고 텐서플로 연산을 결정적으로 만듭니다.

tf.keras.utils.set_random_seed(42) # 랜덤 시드 사용
tf.config.experimental.enable_op_determinism()

base_path = r'/content/drive/MyDrive/dataset'

IMDB 데이터

↓ 이미 정수로 인덱싱 된 형태의 데이터

from tensorflow.keras.datasets import imdb

(train_input, train_target), (test_input,test_target) = imdb.load_data(num_words=10000)

전체 데이터 셋에서 가장 자주 등장하는 단어 num_words= 만큼만 사용 지정

num_words= 를 지정하지 않으면 88584 만큼 단어가 로딩 되는데... 인덱스의 크기가 매우 커진다.

Data 확인

train_input.shape, test_input.shape

1차원 배열?

train_input.dtype # dtype('O') <- 데이터 원소가 Python Object 라는 뜻.

print(train_input[0]) # 첫번째 샘플 확인 => list!

리뷰의 텍스트의 list 길이는 제각각!

len(train_input[0]) # 첫번째 리뷰의 단어개수

len(train_input[1]) # 두번째 리뷰의 단어개수

하나의 리뷰가 하나의 샘플이 된다!

서로 다른 길이의 샘플을 신경망에 어떻게 전달하나?

IMDB 데이터 로딩시 num_words=1000 을 지정했다. 어휘사전에 1000개의 단어만 들어가있다.

단어사전에 없는 단어는 모드 2로 인덱싱되어있다.

Target 확인

train_target[:20]

↓ 해결할 문제는 리뷰가 '긍정(1)'인지 '부정(0)'인지 판단하는 것

이진(binary) 문제로 볼수 있다.

데이터 확인하기

다음과 같은 순서로 데이터 확인은 가능하다

1. 단어인덱스 가져오기

단어 인덱스를 (단어 -> 인덱스)에서 (인덱스 -> 단어)로 변환

인덱스가 3부터 시작하므로 인덱스 오프셋을 적용

2. 리뷰를 실제 단어로 변환해서 출력하기

단어 인덱스 가져오기

word_index = imdb.get_word_index()

type(word_index)

len(word_index)

print(word_index)

word_index['woods'] # 특정 단어 -> 인덱스

인덱스 -> 단어

reverse_word_index = {value : key for key, value in word_index.items()}

print(reverse_word_index)

reverse_word_index[1408]

reverse_word_index[1] # 가장 많이 등장한 단어

인덱스가 3부터 시작하므로 인덱스 오프셋을 적용

IMDB 데이터셋에서 인덱스 오프셋이 3인 이유는 다음과 같은 특별한 토큰들을 예약해 두기 위해서입니다:

0: 패딩 토큰 (padding token)

1: 시작 토큰 (start token)

2: 알 수 없는 단어 (unknown token)

def decode_review(encoded_review):
return ' '.join([reverse_word_index.get(i - 3, '?') for i in encoded_review])

i번째 리뷰를 실제 단어로 변환해서 출력하기

i = 1
decoded_review = decode_review(train_input[1])

print(train_input[i]) # 토큰 인덱스 값
print(decoded_review) # decode 된 문자열
print(train_target[i])

가장 자주 등장하는 단어 500개로 구성된 단어사전 사용

(train_input, train_target), (test_input,test_target) = imdb.load_data(num_words=500)

Train, Validation 데이터 세트 분리

from sklearn.model_selection import train_test_split

train_input, val_input, train_target, val_target = train_test_split(train_input, train_target, test_size=0.2, random_state=42)

train_input.shape, val_input.shape

Train 세트 들여다 보기

각 리뷰(샘플)의 길이를 계산해 numpy 배열에 담기

평균길이, 최대, 최소 리뷰 길이 확인

lengths = np.array([len(x) for x in train_input])

np.min(lengths), np.max(lengths), np.mean(lengths), np.median(lengths)

↑ 평균 단어개수가 239개, 중간값 178개 ... 이 리뷰 데이터는 치우친 분포를 가지는 듯 하다.

plt.hist(lengths)
plt.xlabel('lengths')
plt.ylabel('frquency')
plt.show()

패딩 (pad_sequences())

리뷰는 대부분 짧아서, 이번 예제에서는 중간값보다 훨씬 짧은 100개의 단어만 사용해보겠습니다.

여전히 100개의 단어보다 작은 리뷰가 있기 때문에 이런 리뷰들의 길이를 100에 맞추기 위해

'패딩'을 사용합니다. 보통 패딩을 나타내는 토큰으로는 '0'을 사용합니다

from tensorflow.keras.preprocessing.sequence import pad_sequences

padding 전

len(train_input[0]), len(train_input[5])

max_len = 100

train_seq = pad_sequences(
sequences=train_input,
maxlen=max_len # 100개의 단어로 패딩

               # 100개의 단어보다 많으면 잘려지고
               # 100개의 단어보다 작으면 padding으로 채워진

)

train_seq.shape

(20000, 100)

train_input 은 list 의 1차원 배열

train_seq 는 2차원 배열이 되었다

padding 후

len(train_seq[0]), len(train_seq[5])

print(train_seq[0])

[0]번째는 원래 100보다 컸었다.

앞뒤로 패딩('0')이 안붙었다.

원래 샘플의 앞부분이 잘렸는지, 뒷부분이 잘렸는지 확인

print(train_input[0][:100]) # 원복 앞부분
print(train_input[0][-100:]) # 원본 뒷부분

pad_sequences() 는 기본적으로 샘플의 앞부분을 자름

truncating='pre'

pad_sequences() 함수는 기본적으로 maxlen 보다 긴 시퀀스는 앞부분을 자릅니다

이렇게 하는 이유는 일반적으로 시퀀스의 '뒷부분의 정보가 더 유용'하리라 기대하기 때문

영화 리뷰 데이터를 생각해보면 리뷰의 끝에 뭔가 결정적인 소감(혹은 결론)을 말할 가능성이 높다

만약 시퀀스의 뒷부분을 잘라내고 싶다면 pad_sequences() 함수의 truncating= 매개변수의 값을

기본값 'pre' 가 아닌 'post' 로 바꾸면 된다.

print(train_seq[5]) # 원래 train_input[5] 는 96개 였다. 패딩('0')이 4개가 들어갔다.

profile
독해지자

0개의 댓글