8. 텍스트를 위한 인공 신경망
8.1. 순차 데이터와 순환 신경망
8.1.1. 개요 - 용어 정리
- 순차 데이터(Sequential data)
- 순서에 의미가 있는 데이터
- 예시
- 텍스트 데이터 ⇒ 어순에 따라서 텍스트의 의미가 달라짐
- 시계열 데이터(Time series data) ⇒ 온도 기록 데이터에서 시간 순서를 섞으면 다음 온도를 예상하기 어려움
- 순환 신경망(Recurrent Neural Network, RNN)
- 순환되는 고리가 있는 신경망
- 앞의 샘플의 출력을 새로운 샘플을 계산할 때 재사용
- 즉, 계산된 데이터는 이전 샘플에 대한 기억을 가지고 있다고 볼 수 있음
- 입력에 가중치를 곱하고 활성화 함수를 통과시켜 다음 층으로 보내는 것은 다른 신경망과 동일하나, 층의 출력을 재사용하는 특징이 있는 신경망
- 이러한 특징으로 순차 데이터에 잘 맞는 신경망의 한 종류임
- 타임 스텝(Time step)
- 샘플을 처리하는 단계를 세는 단위
- 위 이미지에서는 A, B, C가 타임 스텝이라고 볼 수 있음
- 타임 스텝이 오래될수록 초기에 순환됐던 정보는 희미해짐
- 셀(Cell)
- 순환신경망에서 층을 부르는 용어
- 한 셀에는 여러 개의 뉴런이 포함되어 있지만, 뉴런을 모두 표시하지 않고 하나의 셀로 표현함
- 은닉 상태(hidden state) ⇒ 셀의 출력을 부르는 용어
- 하이퍼볼릭 탄젠트(hyperbolic tangent, tanh)
- 순환 신경망에서 자주 사용되는 활성화 함수
- -1~1 사이의 범위를 가짐
8.1.2. 순환 신경망 가중치
- 기본적으로 순환 신경망도 다른 신경망처럼 입력과 가중치를 곱하면 됨
- 하지만 추가로 이전 타임스텝의 은닉 상태에 가중치를 곱해야 함
- 가중치 w_x를 샘플마다 동일하게 사용
- 가중치 w_h를 샘플 타임 스텝마다 동일하게 공유
- 가중치 w_h는 타임스텝에 따라 변화되는 뉴런의 출력을 학습
- 타입스텝 1에서는 이전 은닉 상태(h_0)를 0으로 초기화하고 진행
- 순환 신경망의 각각의 뉴런들은 완전 연결되어 있음
- 순환층의 뉴런마다의 출력 h (은닉 상태) 또한 다른 모든 순환층의 뉴런과도 연결되어 있음
8.1.3. 순환 신경망 입출력
- 순환층은 일반적으로 샘플(=시퀀스(sequence))마다 2개의 차원을 가짐
- 시퀀스 안에는 여러 개의 아이템이 들어 있고, 시퀀스 길이 == 타임스텝 길이
- 위 이미지의 (1, 4, 3) 의미
- 1 ⇒ 1 개의 샘플 (I am a boy 문장)
- 4 ⇒ 4개의 단어 (I, am, a, boy)
- 3 ⇒ 각 단어를 3 표현하는 숫자의 크기 (본 예시에는 3개로 가정한 것)
- (1,4,3) 같은 입력이 순환층을 통과하면 두/세 번째 차원이 사라지고 순환층의 뉴런 개수만큼 출력됨
- 순환층은 기본적으로 마지막 타임스텝의 은닉 상태만 출력으로 내보내기에 2차원 배열이 기본 출력 형식임
- 순환 신경망의 입력은 3차원 배열을 기대하기 때문에, 다중 사용시에는 마지막이 아닌 앞선 셀들이 3차원 배열로 출력되게 해야 연결할 수 있음
- 순환 신경망 또한 마지막에는 밀집층을 두어 클래스 분류 진행
- 순환 신경망은 합성곱 신경망과 달리 마지막 셀의 출력이 1차원 ⇒ 셀의 출력을 그대로 밀집층에 사용할 수 있음
8.2. 순환 신경망으로 IMDB 리뷰 분류
8.2.1. 개요 - 용어 정리 및 목표 설정
- 용어 정의
- NLP(Natural Language Processing) ⇒ 컴퓨터를 사용해 인간의 언어를 처리하는 분야
- 말뭉치(corpus) ⇒ 자연어 처리 분야의 훈련 데이터
- 토큰 ⇒ 텍스트 분석을 위해 분리된 단어. 보통 텍스트에서 공백으로 구분되는 문자열을 의미
- 어휘사전 ⇒ 토큰의 집합, 훈련세트에서 고유한 단어를 뽑아 만든 목록
- 목표
- IMDB ⇒ 인터넷 무비 데이터베이스
- IMDB 리뷰 데이터를 이용해 영화를 좋게 판단하는지(양성), 나쁘게 판단하는지(음성) 감성 분석 진행
#케라스로 IMDB 데이터 불러오기
#본래는 텍스트를 기계가 처리할 수 있게 숫자로 바꿔주는 작업이 필요한데, 케라스에서 이를 제공해주고 있음
from tensorflow.keras.datasets import imdb
#num_words => 모든 단어를 사용하면 분석이 힘드니 몇 개의 단어를 분석할 지 제한
#제한 기준은 전체 데이터셋에서 가장 자주 등장하는 단어 순임
(train_input, train_target), (test_input, test_target) = imdb.load_data(
num_words=200)
print(train_input.shape, test_input.shape)
#1 => 문장의 시작부분을 나타내는 예약어
#2 => 위처럼 몇 개의 단어를 사용할건지 num_words로 제한했을 경우,
#테스트 문장에 현재 분석하지 않는 단어가 나올 수 있음. 이런 경우를 예약어인 2로 표현함
print(train_input[0]) #출력값 =>[1, 14, 22, 16, 43, 2, 2, (중략) 2, 19, 178, 32]
#1은 양성, 0은 음성 (본 예제에서는 양성 == 긍정, 음성 == 부정)
print(train_target[:20]) #출력값 => [1 0 0 1 0 (중략) 0 0 0 1 1 0 1]
8.2.2. 코드 - 훈련 세트 준비 및 시퀀스 패딩
import numpy as np
import matplotlib.pyplot as plt
from sklearn.model_selection import train_test_split
#20%를 validation set로 사용
train_input, val_input, train_target, val_target = train_test_split(
train_input, train_target, test_size=0.2, random_state=42)
#mean과 median으로 미루어보아 한 리뷰 내 단어의 개수 평균은 239개 정도 되지만 중간값은 178
#리뷰 길이 분포가 치우쳐져 있을 것으로 예상
lengths = np.array([len(x) for x in train_input])
print(np.mean(lengths), np.median(lengths)) #출력값 239.00925, 178.0
#리뷰 길이 분포를 히스토그램으로 확인
plt.hist(lengths)
plt.xlabel('length')
plt.ylabel('frequency')
plt.show()
- 위 히스토그램에서 대부분의 리뷰 길이가 300 미만임을 확인할 수 있음
- 이를 바탕으로 본 교재에서는 각 리뷰에서 100개의 단어만 추출해서 사용
- 100개 보다 적은 단어로 이루어진 문장은 패딩 처리를 해야함 ⇒ 시퀀스 패딩
from tensorflow.keras.preprocessing.sequence import pad_sequences
#maxlen=100 => 100개의 토큰 사용
#100개가 넘으면 100개로 자르고, 100개가 안되면 빈 공간을 0으로 채워넣음
#보통 문장의 앞부분이 잘리고, 뒷부분을 사용함 (문장의 뒷 부분이 더 의미있다 가정)
train_seq = pad_sequences(train_input, maxlen=100)
print(train_seq.shape) #출력값 => (20000, 100) / (샘플 개수, 토큰(타임스텝 개수))
#안 잘리면 맨 앞이 0이 아님
print(train_seq[0]) #출력값 => [ 10 4 20 9 2 중략 20 10 10 2 158]
#앞 부분이 패딩됨
print(train_seq[5]) #출력값 => [ 0 0 0 0 1 2 195 19 중략 48 64 18 4 2]
val_seq = pad_sequences(val_input, maxlen=100)
8.2.3. 코드 - 순환 신경망 모델 만들기
- 앞서 텍스트를 프로그램이 처리할 수 있게 하기 위해서 고유의 숫자로 변경해서 처리했음
- 프로그램은 숫자의 대소에 영향을 받기 때문에 영향 받지 않기 위해 데이터를 바꿔줘야함
- 원-핫 인코딩
- 데이터를 바꾸는 방법 중 하나
- 어떤 클래스에 해당하는 원소만 1이고 나머지는 모두 0인 벡터 (ex. [ 1, 0, 0, 0, 0, 0, 0 ] , [ 0, 0, 0, 1, 0, 0, 0 ])
from tensorflow import keras
model = keras.Sequential()
#SimpleRNN => 가장 간단한 순환신경망 클래스. 8의 자리는 뉴런의 개수를 의미
#SimpleRNN 사용 시 별도의 activation를 설정하지 않으면 tanh가 기본값
model.add(keras.layers.SimpleRNN(8, input_shape=(100, 200)))
model.add(keras.layers.Dense(1, activation='sigmoid'))
#keras.utils.to_categorical => 원핫 인코딩을 해주는 메소드
train_oh = keras.utils.to_categorical(train_seq)
val_oh = keras.utils.to_categorical(val_seq)
8.2.4. 코드 - 순환 신경망 훈련
#케라스 API에서는 다른 신경망과 순환 신경망 훈련 방법이 크게 다르지 않음
rmsprop = keras.optimizers.RMSprop(learning_rate=1e-4)
model.compile(optimizer=rmsprop, loss='binary_crossentropy',
metrics=['accuracy'])
checkpoint_cb = keras.callbacks.ModelCheckpoint('best-simplernn-model.keras',
save_best_only=True)
early_stopping_cb = keras.callbacks.EarlyStopping(patience=3,
restore_best_weights=True)
history = model.fit(train_oh, train_target, epochs=100, batch_size=64,
validation_data=(val_oh, val_target),
callbacks=[checkpoint_cb, early_stopping_cb])
#훈련 손실과 검증 손실을 그래프로 확인
plt.plot(history.history['loss'])
plt.plot(history.history['val_loss'])
plt.xlabel('epoch')
plt.ylabel('loss')
plt.legend(['train', 'val'])
plt.show()
8.2.5. 코드 - 임베딩
- 원-핫 인코딩 사용 시 입력 데이터가 매우 커진다는 단점이 있음
- 임베딩
- 각 단어를 고정된 크기의 실수 밀집 벡터로 만드는 방법으로 원-핫 인코딩 단점 보완
- 반복된 훈련을 통해 각 단어의 유사성 또한 반영할 수 있음
model2 = keras.Sequential()
#레이어로 Embedding 사용
model2.add(keras.layers.Embedding(200, 16, input_shape=(100,)))
model2.add(keras.layers.SimpleRNN(8))
model2.add(keras.layers.Dense(1, activation='sigmoid'))
rmsprop = keras.optimizers.RMSprop(learning_rate=1e-4)
model2.compile(optimizer=rmsprop, loss='binary_crossentropy',
metrics=['accuracy'])
checkpoint_cb = keras.callbacks.ModelCheckpoint('best-embedding-model.keras',
save_best_only=True)
early_stopping_cb = keras.callbacks.EarlyStopping(patience=3,
restore_best_weights=True)
history = model2.fit(train_seq, train_target, epochs=100, batch_size=64,
validation_data=(val_seq, val_target),
callbacks=[checkpoint_cb, early_stopping_cb])
#훈련 손실과 검증 손실을 그래프로 확인
plt.plot(history.history['loss'])
plt.plot(history.history['val_loss'])
plt.xlabel('epoch')
plt.ylabel('loss')
plt.legend(['train', 'val'])
plt.show()
8.3. LSTM 셀과 GRU 셀
8.3.1. 개요 - LSTM 셀, GRU셀
- LSTM(Long Short-Term Memory) 구조
- 단기 기억을 보다 길게 기억하기 위해 고안된 구조
- 은닉 상태 외에 또 다른 순환되는 상태인 셀 상태를 계산해서 같이 표현
- 셀 상태 ⇒ 다음 층으로 전달되지 않고 LSTM 셀에서 순환만 되는 값
- 셀 상태를 계산하기 위한 각 곱셈에 대한 용어
- 삭제 게이트 ⇒ 셀 상태에 있는 정보를 제거하는 역할
- 입력 게이트 ⇒ 새로운 정보를 셀 상태에 추가하는 역할
- 출력 게이트 ⇒ 다음 은닉 상태로 현재 셀 상태를 출력하는 역할
- GRU(Gated Recurrent Unit) 구조
- LSTM을 간소화한 버전
- 셀 상태를 계산하지 않음
- LSTM보다 가중치가 적어 계산량이 적지만 좋은 성능을 냄
8.3.2. 코드 - LSTM 셀, GRU셀
- LSTM 신경망 코드 구현
#데이터 준비 과정 생략 from tensorflow import keras model = keras.Sequential() model.add(keras.layers.Embedding(500, 16, input_shape=(100,))) #keras.layer.LSTM(뉴런개수)를 하면 LSTM 사용 가능 model.add(keras.layers.LSTM(8)) model.add(keras.layers.Dense(1, activation='sigmoid')) model.summary() rmsprop = keras.optimizers.RMSprop(learning_rate=1e-4) model.compile(optimizer=rmsprop, loss='binary_crossentropy', metrics=['accuracy']) checkpoint_cb = keras.callbacks.ModelCheckpoint('best-lstm-model.keras', save_best_only=True) early_stopping_cb = keras.callbacks.EarlyStopping(patience=3, restore_best_weights=True) history = model.fit(train_seq, train_target, epochs=100, batch_size=64, validation_data=(val_seq, val_target), callbacks=[checkpoint_cb, early_stopping_cb])
- GRU 신경망 코드 구현
#데이터 준비 과정 생략 model4 = keras.Sequential() model4.add(keras.layers.Embedding(500, 16, input_shape=(100,))) #keras.layer.GRU(뉴런개수)를 하면 GRU 사용 가능 model4.add(keras.layers.GRU(8)) model4.add(keras.layers.Dense(1, activation='sigmoid')) model4.summary() rmsprop = keras.optimizers.RMSprop(learning_rate=1e-4) model4.compile(optimizer=rmsprop, loss='binary_crossentropy', metrics=['accuracy']) checkpoint_cb = keras.callbacks.ModelCheckpoint('best-gru-model.keras', save_best_only=True) early_stopping_cb = keras.callbacks.EarlyStopping(patience=3, restore_best_weights=True) history = model4.fit(train_seq, train_target, epochs=100, batch_size=64, validation_data=(val_seq, val_target), callbacks=[checkpoint_cb, early_stopping_cb])
- 코드 세부 설정
- 순환층 드롭아웃 적용
- 드롭아웃 ⇒ 은닉층에 있는 뉴런의 출력을 랜덤하게 꺼서 과대적합을 방지하는 기법
- 순환층은 매개변수로 드롭아웃 기능 제공
- 2개의 순환층 연결
- 순환층은 기본적으로 마지막 타입스텝 값만 반환하지만, 다중 순환층으로 연결할 때는 전부 반환해야함
model3 = keras.Sequential() model3.add(keras.layers.Embedding(500, 16, input_shape=(100,))) #dropout=0.3 => 30%의 입력을 드롭아웃하겠다는 뜻 #return_sequences를 True로 하면 전체 타입스텝 값이 반환됨 #LSTM 외 GRU도 동일하게 적용할 수 있음 model3.add(keras.layers.LSTM(8, dropout=0.3, return_sequences=True)) model3.add(keras.layers.LSTM(8, dropout=0.3)) model3.add(keras.layers.Dense(1, activation='sigmoid')) model3.summary()
- 순환층 드롭아웃 적용
참고자료 :
1. 혼자 공부하는 머신러닝, 박해선, 한빛미디어
2. 혼자 공부하는 머신러닝 인프런 강의, 박해선, https://www.inflearn.com/course/%ED%98%BC%EC%9E%90%EA%B3%B5%EB%B6%80-%EB%A8%B8%EC%8B%A0%EB%9F%AC%EB%8B%9D-%EB%94%A5%EB%9F%AC%EB%8B%9D/dashboard
'Group Study (2024-2025) > Machine Learning 입문' 카테고리의 다른 글
[ML 입문] 7주차 스터디 (1) | 2024.11.20 |
---|---|
[ML 입문] 6주차 스터디 (3) | 2024.11.10 |
[ML 입문] 5주차 스터디 (1) | 2024.11.06 |
[ML 입문] 4주차 스터디 (0) | 2024.10.30 |
[ML 입문] 3주차 스터디 (1) | 2024.10.16 |