2022-06-28 Log
02-01. Tokenization #
- Corpus에서 token이라 불리는 단위로 나누는 작업
- 단어 토큰화에서 단순히 구두점이나 특수문자를 제거하는 것은 의미의 손실을 발생시킬 수 있기 때문에,
사용자의 목적과 일치하는 토큰화 도구를 사용할 필요가 있음 - 구두점이나 특수 문자가 필요한 경우: Ph.D, AT&T, $45.55, 01/02/06 등
- 줄임말과 단어 내에 띄어쓰기가 있는 경우: what’re/what are, New York, rock ’n’ roll 등
- 문장 토큰화에서 단순히 마침표를 기준으로 문장을 잘라내는 것은 192.168.56.31, gmail.com과 같은 경우를 고려했을 때 올바르지 않음
한국어 토큰화 #
- 한국어의 경우 띄어쓰기가 가능한 단위가 어절인데,
‘그가’, ‘그에게’, ‘그를’과 같이 어절이 독립적인 단어로 구성되는 것이 아니라
조사 등의 무언가가 붙어있는 경우가 많기 때문에 이를 전부 형태소 단위로 분리해줘야 함 - 자립 형태소: 접사, 어미, 조사와 상관업싱 자립하여 사용할 수 있는 형태소, [체언, 수식언, 감탄사] 등
- 의존 형태소: 다른 형태소와 결합하여 사용되는 형태소, [접사, 어미, 조사, 어간]
- 한국어의 경우 띄어쓰기가 지켜지지 않아도 글을 쉽게 이해할 수 있어 띄어쓰기가 잘 지켜지지 않음
- 품사 태깅: 단어의 의미를 제대로 파악하기 위해서는 해당 단어가 어떤 품사로 쓰였는지 구분할 필요
NLTK, KoNLPy #
python
from nltk.tokenize import word_tokenize # 단어 토큰화
from nltk.tag import pos_tag # 품사 태깅
python
from konlpy.tag import Okt
okt = Okt()
okt.porphs(sentence) # 형태소 추출
okt.pos(sentence) # 품사 태깅02-02. Cleaning and Normalization #
- Cleaning(정제): 갖고 있는 corpus로부터 노이즈 데이터를 제거
- Normalization(정규화): 표현 방법이 다른 단어들을 통합시켜서 같은 단어로 만듦
- 영어권 언어에서 단어의 개수를 줄이는 정규화 방법으로 대,소문자 통합을 활용
- 노이즈 데이터: 아무 의미 없는 특수 문자 등, 분석하고자 하는 목적에 맞지 않는 불필요한 단어들
- 불필요한 단어를 제거하기 위해 불용어, 등장 빈도가 적은 단어, 길이가 짧은 단어 등을 제거
- 노이즈 데이터의 특징을 잡아낼 수 있다면, 정규표현식을 사용해서 제거
02-03. Stemming and Lemmatization #
Lemmatization #
- Lemma(표제어): 기본 사전형 단어, [am, are, is]의 뿌리 단어 be 등
- Stem(어간): 단어의 의미를 담고 있는 단어의 핵심 부분, ‘cats’에서 ‘cat’
- Affix(접사): 단어에 추가적인 의미를 주는 부분, ‘cats’에서 ’s’
python
from nltk.stem import WordNetLemmatizer
lemmatizer = WordNetLemmatizer()
lemmatizer.lemmatize(word)
# am -> be, having -> haveStemming #
python
from nltk.stem import PorterStemmer
stemmer = PorterStemmer()
stemmer.stem(word)
# am -> am, having -> hav한국어에서의 어간 추출 #
- 5언 9품사의 구조에서 용언에 해당되는 동사와 형용사는 어간과 어미의 결합으로 구성
- 활용: 용언의 어간이 어미를 가지는 일
- 규칙 활용: 어간이 어미를 취할 때 어간의 모습이 일정,
잡/어간 + 다/어미 - 불규칙 활용: 어간이 어미를 취할 때 어간의 모습이 바뀌거나 특수한 어미일 경우, ‘오르+아/어->올라’ 등
02-04. Stopword #
- Stopword(불용어): 문장에서는 자주 등장하지만 실제 의미 분석을 하는데는 기여하지 않는 단어, [조사, 접미사] 등
python
from nltk.corpus import stopwords
stop_words_list = stopwords.words('english')
python
okt = Okt()
okt.morphs(sentence) # 조사, 접속사 등 제거
# 또는 불용어 사전을 만들어서 제거02-05. Regular Expression #
- 정규 표현식 참고
02-06. Integer Encoding #
- 컴퓨터는 텍스트보다 숫자를 더 잘 처리할 수 있기 때문에 텍스트를 숫자로 변경
- 단어를 빈도수 순으로 정렬하고 순서대로 낮은 숫자부터 정수를 부여
- dictionary, Counter, nltk.FreqDist, keras.Tokenizer 등 활용
python
from nltk import FreqDist
FreqDist(np.hstack(preprocessed_sentences)) # np.hastack으로 문장 구분을 제거
python
from tensorflow.keras.preprocessing.text import Tokenizer
tokenizer = Tokenizer() # num_words 파라미터로 사용할 단어 개수 지정
tokenizer.fit_on_texts(preprocessed_sentences) # 빈도수 기분으로 단어 집합 생성
print(tokenizer.word_intex) # 정수 인덱스 확인
print(tokenizer.word_counts) # 단어 빈도수 확인
print(tokenizer.texts_to_sequences(preprocessed_sentences)) # corpus를 인덱스로 변환02-07. Padding #
- 병렬 연산을 위해 여러 문장의 길이를 임의로 동일하게 맞춰주는 작업이 필요
python
from tensorflow.keras.preprocessing.sequence import pad_sequences
encoded = tokenizer.texts_to_sequences(preprocessed_sentences)
padded = pad_seqences(encoded)
# padding='post'를 입력해야 뒤에서 부터 0을 채움
# maxlen으로 문장 길이 조절
# truncating='post'를 통해 문장 길이 초과 시 뒤의 단어가 삭제되도록 설정02-08. One-Hot Encoding #
- Vocabulary(단어 집합): 서로 다른 단어들의 집합, book과 books과 같은 변형 형태도 다른 단어로 간주
- One-Hot Encoding: 단어 집합의 크기를 벡터의 차원으로 하고,
표현하고 싶은 단어에 1, 다른 인텍스에 0을 부여하는 단어의 벡터 표현 방식 - 단어의 개수가 늘어날 수록 벡터를 저장하기 위해 필요한 공간이 계속 늘어나는 단점
- 단어의 유사도를 표현하지 못하는 단점 (강아지, 개, 냉장고 등)
python
from tensorflow.keras.utils import to_categorical
encoded = tokenizer.texts_to_sequences(preprocessed_sentences)[0]
one_hot = to_categorical(encoded)03-01. Language Model #
- 단어 시퀀스(문장)에 확률을 할당하는 모델, 이전 단어들이 주어졌을 때 다음 단어를 예측
- 단어 시퀀스 W의 확률 $P(W)=P(w_1,w_2,w_3,w_4,w_5,…,w_n)$
- 다음 단어 등장 확률 $P(w_n|w_1,…,w_{n-1})$
03-02. Statistical Language Model #
조건부 확률 #
| 남학생(A) | 여학생(B) | 계 | |
|---|---|---|---|
| 중학생(C) | 100 | 60 | 160 |
| 고등학생(D) | 80 | 120 | 200 |
| 계 | 180 | 180 | 360 |
- 학생을 뽑았을 때, 고등학생이면서 남학생일 확률 $P(A \bigcap B)=80/360$
- 고등학생 중 한명을 뽑았을 때, 남학생일 확률 $P(A|D)=P(A \bigcap D)/P(D)=(80/360)/(200/360)$
문장에 대한 확률 #
- ‘An adorable little boy is spreading smiles’의 확률
$P(\text{An adorable little boy is spreading smiles})=\ P(\text{An}) \times P(\text{adorable}|\text{An}) \times … \times P(\text{smiles}|\text{An adorable little boy is spreading})$
카운트 기반 접근 #
- An adorable little boy가 100번 등장했을 때 그 다음에 is가 등장한 경우가 30번이라면,
$P(\text{is}|\text{An adorable little boy})$는 30% - 카운트 기반으로 훈련할 경우 단어 시퀀스가 없어 확률이 0이 되는 경우를 방지하기 위해 방대한 양의 훈련 데이터가 필요
- 회소 문제: 충분한 데이터를 관측하지 못하여 언어를 정확히 모델링하지 못하는 문제
$$P(\text{is}|\text{An adorable little boy})= \frac{count(\text{An adorable little boy is})}{count(\text{An adorable little boy})}$$
03-03. N-gram Language Model #
- 통계적 언어 모델의 일종이지만, 모든 단어가 아닌 일부 단어만 고려하는 접근 방법 사용
- An adorable little boy에서 is가 나올 확률을 boy가 나왔을 때 is가 나올 확률로 대체
$P(\text{is}|\text{An adorable little boy}) \approx P(\text{is}|\text{boy})$ - 뒤의 단어 몇 개만 보다 보니 의도하고 싶은 대로 문장을 끝맺음하지 못하는 경우가 발생
- 전체 문장을 고려한 언어 모델보다는 정확도가 떨어짐
- 몇 개의 단어를 볼지 n을 정하는 것은 trade-off 문제를 발생시킴, n은 최대 5를 넘게 잡아서는 안된다고 권장
N-gram #
- n개의 연속적인 단어 나열
- An adorable little boy에 대해
unigrams: an, adorable, little, boy
bigrams: an adorable, adorable little, little boy
03-05. Perplexity #
- Perplexity(PPL): 헷갈리는 정도, 낮을수록 언어 모델의 성능이 좋음
$$PPL(W)=P(w_1,w_2,w_3,…,w_N)^{-\frac{1}{N}}=\sqrt[N]{\frac{1}{P(w_1,w_2,w_3,…,w_N)}}$$
Branching Factor #
- Branching factor(분기계수): PPL이 선택할 수 있는 가능한 경우의 수
- 대해 PPL이 10이 나왔을 때, 언어 모델은 테스트 데이터에 대해 다음 단어를 예측할 때 평균 10개의 단어를 고려
- PPL의 값이 낮다는 것은 테스트 데이터 상에서 높은 정확도를 보이는 것일뿐, 반드시 사람이 직접 느끼기에 좋은 모델인 것은 아님
$$PPL(W)=P(w_1,w_2,w_3,…,w_N)^{-\frac{1}{N}}=(\frac{1}{10}^N)^{-\frac{1}{N}}=\frac{1}{10}^{-1}=10$$
04-01. 단어의 표현 방법 #
- 국소 표현(이산 표현): 해당 단어 그 자체만 보고, 특정값을 맵핑하여 단어를 표현하는 방법
- 분산 표현(연속 표현): 그 단어를 표현하고자 주변을 참고하여 단어를 표현하는 방법
04-02. Bag of Words(BoW) #
- 단어들의 순서는 고려하지 않고, 단어들의 출현 빈도에만 집중하는 텍스트 데이터 수치화 표현 방법
python
doc1 = '정부가 발표하는 물가상승률과 소비자가 느끼는 물가상승률은 다르다.'
vocabulary : {'정부': 0, '가': 1, '발표': 2, '하는': 3, '물가상승률': 4, '과': 5, '소비자': 6, '느끼는': 7, '은': 8, '다르다': 9}
bag of words vector : [1, 2, 1, 1, 2, 1, 1, 1, 1, 1]
python
from sklearn.feature_extraction.text import CounteVectorizer
vector = CounterVectorizer() # stop_words 파라미터로 불용어 제거('english' 또는 리스트 등)
print('bag of words vector:', vector.fit_transform(corpus).toarray())
print('vocabulary:', vector.vocabulary_)04-03. Document-Term Matrix(DTM) #
- 다수의 문서에서 등장하는 각 단어들의 빈도를 행렬로 표현한 것
- One-hot vector와 마찬가지로 대부분의 값이 0인 희소 표현의 문제 발생
- 불용어와 중요한 단어에 대해서 가중치를 주기 위해 TF-IDF를 사용
04-04. TF-IDF #
- 단어의 빈도와 역 문서 빈도를 사용하여 DTM 내의 각 단어들마다 중요도를 가중치로 부여하는 방법
- $tf(d,t)$: 특정 문서 $d$에서의 특정 단어 $t$의 등장 횟수, DTM에서의 각 단어들의 가진 값
- $df(t)$: 특정 단어 $t$가 등장한 문서의 수, 특정 단어가 각 문서에서 등장한 횟수는 무시
- $idf(d,t)$: $df(t)$에 반비례하는 수,
총 문서의 수 n이 커질수록 기하급수적으로 증가하는 것을 방지하기 위해 $log$(일반적으로 자연 로그) 적용
$$idf(d,t)=log(\frac{n}{1+df(t)})$$
python
from sklearn.feature_extraction.text import TfidfVectorizer
print(vector.fit_transform(corpus).toarray())
print(ve)05. Vector Similarity #
Cosine Similarity #
- 두 벡터 간의 코사인 각도를 이용하여 구할 수 있는 두 벡터의 유사도
- 두 벡터의 방향이 동일하면 1의 값을 가지며, 값이 1에 가까울수록 유사도가 높음
- 문서의 길이가 다른 상황에서 비교적 공정한 비교를 할 수 있음
Euclidean Distance #
- 다차원 공간에서 두 개의 점 $p$와 $q$가 각각 $p=(p_1,p_2,p_3,…,p_n)$과 $q=(q_1,q_2,q_3,…,q_n)$의 좌표를 가질 때
두 점 사이의 거리를 계산하는 유클리드 거리 공식
$$\sqrt{(q_1-p_1)^2+(q_2-p_2)^2+…+(q_n-p_n)^2}=\sqrt{\Sigma^n_{i=1}(q_i-p_i)^2}$$
Jaccard Similarity #
- 합집합에서 교집합의 비율을 구한다면 두 집합 A와 B의 유사도를 구할 수 있음
- 자카드 유사도 J는 0과 1사이의 값을 가지며, 두 집합이 동일하면 1, 공통 원소가 없으면 0의 값을 가짐
$$J(A,B)=\frac{|A \bigcap B|}{|A \bigcup B|}=\frac{|A \bigcap B|}{|A|+|B|-|A \bigcap B|}$$
06. Machine Learning #
Classification and Regression #
- Bianry Classification: 두 개의 선택지 중 하나의 답을 선택
- Multi-class Classification: 세 개 이상의 선택지 중에서 답을 선택
- Regression: 연속적인 값의 범위 내에서 예측값을 도출
Learning #
- Supervised Learning: 정답 레이블과 함께 함습
- Unsupervised Learning: 데이터에 별도의 레이블이 없이 학습
- Self-Supervised Learning: 레이블이 없는 데이터가 주어지면, 모델이 학습을 위해 스스로 레이블을 생성
Confusion Matrix #
| 예측 참 | 예측 거짓 | |
|---|---|---|
| 실제 참 | TP(정답) | FN(오답) |
| 실제 거짓 | FP(오답) | TN(정답) |
- Precision(정밀도): True라고 분류한 것 중 실제 True의 비율, $Precision=\frac{TP}{TP+FP}$
- Recall(재현율): 실제 True인 것 중에서 모델이 True라고 예측한 것의 비율, $Recall=\frac{TP}{TP+FN}$
- Accuracy(정확도): 전체 예측한 데이터 중 정답을 맞춘 것에 대한 비율, $Accuracy=\frac{TP+TN}{TP+FN+FP+TN}$
Overfitting and Underfitting #
- Overfitting: 훈련 데이터를 과하게 학습, 훈련 데이터에 비해 테스트 데이터의 오차가 커짐
- Underfitting: 테스트 데이터의 성능이 올라갈 여지가 있음에도 훈련을 덜 한 상태, 훈련 데이터에서도 정확도가 낮음
07. Deep Learning #
Perceptron #
- 입력값 $x$, 가중치 $w$, 출력값 $y$
- 가중치의 값이 크면 클수록 해당 입력 값이 중요하다는 것을 의미
- 단층 퍼셉트론: 값을 보내는 input layer와 값을 받아서 출력하는 output layer로 구성
- 다층 퍼셉트론(MLP): 입력층과 출력층 사이에 hidden layer를 추가
FFNN #
- FFNN(피드 포워드 신경망): 오직 입력층에서 출력층 방향으로 연산이 전개되는 신경망
- RNN(순환 신경망): 은닉층의 출력값이 다시 은닉층으로 입력되는 신경망
Activision Function #
- 은닉층과 출력층의 뉴런에서 출력값을 결정하는 함수
- Step function, Sigmoid function, ReLU 등 비선형 함수의 특성
Loss Function #
- MSE: 연속형 변수 예측
- Binary Cross-Entropy: 시그모이드 함수 출력
- Categorical Cross-Entropy: 소프트맥스 함수 출력
Optimizer #
- Momentum: 경사 하강법에 모멘텀을 더해 Local Minimum에 빠지더라도 빠져나갈 수 있게 함
- Adagrad: 각 매개변수에 서로 다른 학습률을 적용
- RMSprop: Adagrad가 학습을 진행할수록 학습률이 지나치게 떨어지는 단점을 개선
- Adam: RMSprop과 Momentum을 합친 듯한 방법, 방향과 학습률 두 가지를 모두 잡기 위한 방법
Overfitting 방지 #
- 데이터의 양을 늘리기
데이터의 양이 적으면 데이터의 특정 패턴이나 노이즈까지 쉽게 암기해버림, Data Augmentation 활용 - 모델의 복잡도 줄이기
인공 신경망의 복잡도는 은닉층의 수나 매개변수의 수 등으로 결정 - 가충치 규제 적용하기
L1 규제(가중치의 절댓값 합계를 비용 함수에 추가), L2 규제(모든 가중치들의 제곱합을 비용 함수에 추가) - Dropout
학습 시에 인공 신경망이 특정 뉴런 또는 특정 조합에 너무 의존적이게 되는 것을 방지
기울기 소실 #
- 기울기 소실: 역전파 과정에서 입력층으로 갈 수록 기울기가 점차적으로 작아지는 현상
- 기울기 폭주: 기울기가 점차 커지다가 가중치들이 비정상적으로 큰 값이 되면서 발산되는 경우
- Gradient Clipping: 기울기 폭주를 막기 위해 임계값을 넘지 않도록 값의 크기를 감소
Weight Initialization #
- Xavier Initialization: 균등 분포 또는 정규 분포로 초기화 할 때 두 가지 경우로 나뉨
- He Initialization: Xavier 초기화와 다르게 다음 층의 뉴런의 수를 반영하지 않음
Batch Normalization #
- 내부 공변량 변화: 학습 과정에서 층 별로 입력 데이터 분포가 달라지는 현상
- 배치 정규화: 한 번에 들어오는 배치 단위로 정규화하는 것
- 배치 정규화는 추가 계산을 발생시켜 모델을 복잡하게 하기 때문에 예측 시 실행 시간이 느려지는 단점
- 너무 작은 배치 크기에서는 잘 동작하지 않을 수 있기 때문에 미니 배치 크기에 의존적임
- RNN은 각 시점마다 다른 통계치를 가지기 때문에 RNN에 적용하기 어려움
Keras API #
- Sequential API: 단순하게 층을 쌓는 방식, 다수의 입출력 및 층 간 연산을 구현하기 어려움
- Functional API: 입력의 크기(shape)를 명시한 입력층을 모델의 앞단에 정의
- Subclassing API: Functional API로도 구현할 수 없는 모델들도 구현 가능
texts_to_matrix()
#
tokenizer.texts_to_matrix(texts, mode='count'): DTM 생성tokenizer.texts_to_matrix(texts, mode='binary'): DTM과 유사하지만 단어의 개수는 무시tokenizer.texts_to_matrix(texts, mode='tfidf'): TF-IDF 행렬 생성tokenizer.texts_to_matrix(texts, mode='freq'):
각 문서에서의 단어 등장 횟수를 분자로, 문서의 크기를 분모로 하는 표현하는 방법
NNLM #
- 피드 포워드 신경망 언어 모델, 신경망 언어 모델의 시초로, RNNLM, BiLM 등으로 발전
- 기존 N-gram 언어 모델은 충분한 데이터를 관측하지 못하면 언어를 정확히 모델링하지 못하는 희소 문제를 가짐
- NNLM은 N-gram 언어 모델처럼 정해진 개수(window size)의 단어만을 참고
- NNLM은 N개의 input layer와 projection layer, hidden layer, output layer로 구성
- Projection layer의 크기가 M일 때, 각 입력 단어들은 V x M 크기의 가중치 행렬과 곱해짐
- 충분한 양의 훈련 코퍼스를 학습한다면 단어 간 유사도를 구할 수 있는 임베딩 벡터값을 얻을 수 있음
08-01. RNN #
- 입력과 출력을 시퀀스 단위로 처리하는 시퀀스 모델
- 은닉층의 노드에서 활성화 함수를 통해 나온 결과값을 다시 은닉층 노드의 다음 계산 입력으로 보내는 특징
- 셀(메모리 셀, RNN 셀): 은닉층에서 활성화 함수를 통해 결과를 내보내는 역할을 하는 노드, 이전의 값을 기억하는 역할
- Hidden state: 현재 시점을 t라 할 때, 메모리 셀이 다음 시점인 t+1의 자신에게 보내는 값
- 입력과 출력의 길이를 다르게 설계할 수 있어 다양한 용도로 사용 가능
- one-to-many: 하나의 이미지 입력에 대해서 사진의 제목인 시퀀스를 출력하는 이미지 캡셔닝 작업에 사용
- many-to-one: 단어 시퀀스에 대해서 하나의 출력을 하는 감성 분류, 스팸 메일 분류 등에 사용
- many-to-many: 사용자가 문장을 입력하면 대답 문장을 출력하는 챗봇이나 번역기에 사용
RNN Parameter #
- 현재 시점 $t$에서의 hidden state가 $h_t$라 할 때, 두 개의 가중치 $W_x$, $W_h$가 필요
- $W_x$는 입력층을 위한 가중치, $W_h$는 $t-1$의 hidden state인 $h_{t-1}$을 위한 가중치
- 은닉층 $h_t=tanh({W_x}{x_t}+{W_h}{h_{t-1}}+b)$, 출력층 $y_t=f({W_y}{h_t}+b)$
- 출력층의 활성화 함수 $f$는 이진 분류에서 시그모이드 함수, 다중 클래스 분류에서 소프트맥스 함수 등 사용
- RNN의 입력 $x_t$는 단어 벤터로 간주, 단어 벡터의 차원을 $d$, hidden state의 크기를 $D_h$라 할 때,
메모리 셀 $h_t$ = $tanh({W_h}\times{h_{t-1}}\times{W_x}\times{x_t}+{b})$
| $x_t$ | $W_x$ | $W_h$ | $h_{t-1}$ | $b$ |
|---|---|---|---|---|
| $({d}\times{1})$ | $({D_h}\times{d})$ | $({D_h}\times{D_h})$ | $({D_h}\times{1})$ | $({D_h}\times{1})$ |
Keras RNN #
- hidden_units: hidden state의 크기 (output_dim)
- timesteps: 입력 시퀀스(문장)의 길이 (input_length)
- input_dim: 입력의 크기, 단어 벡터의 차원
- RNN 층은 (batch_size, timesteps, input_dim) 크기의 3D 텐서를 입력으로 받음
- 메모리 셀의 최종 시점의 hidden state만 리턴할 경우 (batch_size, output_dim) 크기의 2D 텐서 반환
- 메모리 셀의 각 시점(time step)의 hidden state 값들을 모아 전체 시퀀스를 리턴할 경우 3D 텐서를 반환
return_sequences=True옵션으로 반환값 설정
python
from tensorflow.keras.layers import SimpleRNN
model = Sequential()
model.add(SimpleRNN(hidden_units, input_shape=(timesteps, input_dim)))메모리 셀에서 hidden state 계산은 다음과 같은 코드로 동작
python
hidden_state_t = 0 # 초기 은닉 상태를 0(벡터)로 초기화
for input_t in input_length: # 각 시점마다 입력을 받는다.
output_t = tanh(input_t, hidden_state_t) # 각 시점에 대해서 입력과 은닉 상태를 가지고 연산
hidden_state_t = output_t # 계산 결과는 현재 시점의 은닉 상태가 된다.Deep RNN #
- 순환 신경망에서 은닉층이 1개 더 추가되어 은닉층이 2개인 깊은 구조
- 첫번째 은닉층은 다음 은닉층이 존재하기 때문에
return_sequences=True를 설정하여 모든 시점을 전달
python
from tensorflow.keras.layers import SimpleRNN
model = Sequential()
model.add(SimpleRNN(hidden_units, input_shape=(timesteps, input_dim), return_sequences=True))
model.add(SimpleRNN(hidden_units, return_sequences=True))Bidirectional RNN #
- t에서의 출력값을 예측할 때 이전 시점의 입력 뿐 아니라, 이후 시점의 입력 또한 예측
- 빈칸 채우기 등의 문제에서 미래 시점의 입력에 힌트가 있기 때문에 이전과 이후의 시점을 모두 고려
- 하나의 출력값을 예측하기 위해 두 개의 메모리 셀을 사용
- 첫 번째 메모리 셀은 앞 시점의 hidden state를 전달받아 현재의 hidden state를 계산 (forward)
- 두 번째 메모리 셀은 뒤 시점의 hidden state를 전달받아 현재의 hidden state를 계산 (backward)
- 은닉층을 추가하면 학습할 수 있는 양이 많아지지만, 훈련 데이터 또한 많은 양이 필요
python
from tensorflow.keras.layers import Bidirectional
model = Sequential()
model.add(Bidirectional(SimpleRNN(hidden_units, return_sequences=True), input_shape=(timesteps, input_dim)))08-02. LSTM #
- Valina RNN(Simple RNN)은 출력 결과가 이전의 계산 결과에 의존하여 비교적 짧은 시퀀스에서면 효과를 봄
- 장기 의존성 문제: time step이 길어질수록 앞의 정보가 뒤로 충분히 전달되지 못하는 현상
- LSTM(장단기 메모리)은 은닉층의 메모리 셀에 입력 게이트, 망각 게이트, 출력 게이트를 추가하여
불필요한 기억을 지우고, 기억해야할 것들을 결정 - 전통적인 RNN에서 cell state $C_t$를 추가하여 긴 시퀀스의 입력을 처리하는데 탁월한 성능
- cell state 또한 이전 시점의 cell state가 다음 시점의 cell state를 구하기 위한 입력으로서 사용
- 입력 게이트: 현재 정보를 기억하기 위한 게이트
- 삭제 게이트: 기억을 삭제하기 위한 게이트, 시그모이드 함수의 반환값이 0에 가까울수록 많은 정보가 삭제
- 셀 상태: 삭제 게이트에서 일부 기억을 잃은 상태, 입력 게이트에서 선택된 기억을 삭제 게이트의 결과값과 더함
- 삭제 게이트는 이전 시점의 입력을 얼마나 반영할지 의미, 입력 게이트는 현재 시점의 입력을 얼마나 반영할지 결정
- 출력 게이트: 현재 시점 $t$의 hidden state를 결정하는 일에 사용
08-03. GRU #
- LSTM의 장기 의존성 문제에 대한 해결책을 유지하면서, hidden state를 업데이트하는 계산을 감소
- GRU는 업데이트 게이트와 리셋 게이트로 구성
- 데이터의 양이 적을 때는 매개 변수의 양이 적은 GRU가 유리, 반대의 경우엔 LSTM이 유리
08-04. Keras RNN and LSTM #
입력 생성 #
train_X가 2D 텐서의 형태일 경우 batch_size 1을 추가해 3D 텐서로 변경
python
train_X = [[[0.1, 4.2, 1.5, 1.1, 2.8], [1.0, 3.1, 2.5, 0.7, 1.1], [0.3, 2.1, 1.5, 2.1, 0.1], [2.2, 1.4, 0.5, 0.9, 1.1]]]
train_X = np.array(train_X, dtype=np.float32)
print(train_X.shape) # (1, 4, 5)SimpeRNN #
return_sequences=False일 경우 2D 텐서 반환return_sequences=True일 경우 timesteps를 포함하는 3D 텐서 반환return_state=True일 경우return_sequences에 관계없이 마지막 시점의 hidden state 출력
python
rnn = SimpleRNN(3)
hidden_state = rnn(train_X) # shape(1, 3)
python
rnn = SimpleRNN(3, return_sequences=True)
hidden_states = rnn(train_X) # shape(1, 4, 3)
python
rnn = SimpleRNN(3, return_sequences=True, return_state=True)
hidden_states, last_state = rnn(train_X) # shape(1, 4, 3), shape(1, 3)LSTM #
return_state=True일 경우 마지막 cell state를 포함한 세 개의 결과를 반환
python
lstm = LSTM(3, return_sequences=False, return_state=True)
hidden_state, last_state, last_cell_state = lstm(train_X) # each shape(1, 3)Bidirectional LSTM #
- 정방향과 역방향에 대한 hidden state와 cell state를 반환
return_sequences=False일 경우 정방향 LSTM의 마지막 시점 hidden state와
역방향 LSTM의 첫번째 시점 hidden state가 연결된 채 반환return_sequences=True일 경우 각각의 순서 맞게 연결된 hidden state 반환
python
bilstm = Bidirectional(LSTM(3, return_sequences=False, return_state=True, \
kernel_initializer=k_init, bias_initializer=b_init, recurrent_initializer=r_init))
hidden_states, forward_h, forward_c, backward_h, backward_c = bilstm(train_X)
# shape(1, 6), shape(1, 3), shape(1, 3)08-05. RNNLM #
- NNLM과 달리 time step을 도입하여 입력의 길이가 고정되지 않는 언어 모델
- Teaching Forcing: 테스트 과정에서 RNN 모델을 훈련시킬 때 사용하는 훈련 기법,
모델이 t 시점에서 예측한 값을 t+1 시점에 입력으로 사용하지 않고 실제 알고 있는 정답(t 시점의 레이블)을 사용 - 한 번 잘못 예측하면 뒤에서의 예측가지 영향을 미쳐 훈련 시간이 느려지기 때문에 교사 강요를 사용
- 훈련 과정 동안 활성화 함수로는 softmax 함수, 손실 함수로는 cross-entropy 함수를 사용
- one-hot vector $x_t$를 입력받으면 NNLM에서와 동일한 embedding layer를 거쳐
${V}\times{M}$ 크기의 embedding vector로 변환, $e_t=lookup(x_t)$ - 이후 RNN과 동일한 과정을 거쳐 $\hat{y_t}$를 반환, $h_t=\tanh({W_x}{e_t}+{W_h}{h_{t-1}}+b)$
- $\hat{y}_t$의 각 차원 안에서의 값은 $\hat{y}_t$의 j번째 인덱스가 가진 0과 1사이의 값이 j번째 단어가 다음 단어일 확률
08-06. Text Generation using RNN #
데이터 전처리 #
python
# 원본 한국어 문장
text = """경마장에 있는 말이 뛰고 있다\n
그의 말이 법이다\n
가는 말이 고와야 오는 말이 곱다\n"""
python
# 단어 집합 생성
tokenizer = Tokenizer()
tokenizer.fit_on_texts([text])
vocab_size = len(tokenizer.word_index) + 1
print('단어 집합의 크기 : %d' % vocab_size) # 12
python
# 각 라인마다 texts_to_sequences() 함수를 적용해서 훈련 데이터 생성
print(sequences)
[[2, 3], [2, 3, 1], [2, 3, 1, 4], [2, 3, 1, 4, 5], [6, 1], [6, 1, 7], [8, 1], [8, 1, 9], [8, 1, 9, 10], [8, 1, 9, 10, 1], [8, 1, 9, 10, 1, 11]]
python
# 패딩 후 라벨 분리 (가장 우측에 있는 단어, [경마장에, 있는]에서 '있는' 등을 라벨로 지정)
sequences = pad_sequences(sequences, maxlen=max_len, padding='pre')
sequences = np.array(sequences)
X = sequences[:,:-1]
y = sequences[:,-1]
python
# 라벨에 대해서 one-hot encoding 수행
y = to_categorical(y, num_classes=vocab_size)RNN 모델 설계 #
- many-to-one 구조의 RNN을 사용
- 모든 가능한 단어 중 마지막 시점에서 하나의 단어를 예측하는 다중 클래스 분류 문제 수행
- 활성화 함수로는 softmax 함수, 손실 함수로는 cross-entropy 함수 사용
- 첫 단어가 주어졌을 때, n번 동안 예측을 반복하면서 현재 단어와 문장에 예측 단어를 저장
python
embedding_dim = 10
hidden_units = 32
model = Sequential()
model.add(Embedding(vocab_size, embedding_dim))
model.add(SimpleRNN(hidden_units))
model.add(Dense(vocab_size, activation='softmax'))
model.compile(loss='categorical_crossentropy', optimizer='adam', metrics=['accuracy'])
model.fit(X, y, epochs=200, verbose=2)LSTM 모델 설계 #
- 뉴욕 타임즈 기사 제목 데이터 전처리 (단어 집합 크기 3494, 샘플 최대 길이 24)
- RNN과 동일한 작업을 수행할 LSTM 모델 설계, 예측 과정 또한 동일
python
embedding_dim = 10
hidden_units = 128
model = Sequential()
model.add(Embedding(vocab_size, embedding_dim))
model.add(LSTM(hidden_units))
model.add(Dense(vocab_size, activation='softmax'))
model.compile(loss='categorical_crossentropy', optimizer='adam', metrics=['accuracy'])
model.fit(X, y, epochs=200, verbose=2)08-07. Char RNN #
- 입출력의 단위를 word-level에서 character-level로 변경한 RNN
- 문자 단위를 입출력으로 사용하기 때문에 embedding layer를 사용하지 않음
- 이상한 나라의 앨리스 데이터 사용 (문자열 길이 159484, 문자 집합 크기 56)
- 훈련 데이터에 apple이라는 시퀀스가 있고 입력의 길이가 4일 때, ‘appl’을 입력하면 ‘pple’을 예측할 것으로 기대
- train_X.shape(2658, 60, 56), train_y.shape(2658, 60, 56)
Char RNN 모델 설계 #
python
hidden_units = 256
model = Sequential()
model.add(LSTM(hidden_units, input_shape=(None, train_X.shape[2]), return_sequences=True))
model.add(LSTM(hidden_units, return_sequences=True))
model.add(TimeDistributed(Dense(vocab_size, activation='softmax')))
model.compile(loss='categorical_crossentropy', optimizer='adam', metrics=['accuracy'])
model.fit(train_X, train_y, epochs=80, verbose=2)