460 lines
25 KiB
Plaintext
460 lines
25 KiB
Plaintext
{
|
|
"cells": [
|
|
{
|
|
"cell_type": "markdown",
|
|
"metadata": {},
|
|
"source": [
|
|
"# 순환 신경망\n",
|
|
"\n",
|
|
"이전 모듈에서는 텍스트의 풍부한 의미 표현에 대해 다뤘습니다. 우리가 사용했던 아키텍처는 문장에서 단어들의 집합적인 의미를 포착하지만, 단어들의 **순서**를 고려하지 않습니다. 이는 임베딩 이후의 집계 작업이 원본 텍스트에서 이 정보를 제거하기 때문입니다. 이러한 모델은 단어 순서를 표현할 수 없기 때문에 텍스트 생성이나 질문 응답과 같은 더 복잡하거나 모호한 작업을 해결할 수 없습니다.\n",
|
|
"\n",
|
|
"텍스트 시퀀스의 의미를 포착하기 위해 **순환 신경망**(Recurrent Neural Network, RNN)이라는 신경망 아키텍처를 사용합니다. RNN을 사용할 때, 문장을 네트워크에 한 번에 하나의 토큰씩 전달하며, 네트워크는 **상태**를 생성합니다. 이 상태를 다음 토큰과 함께 네트워크에 다시 전달합니다.\n",
|
|
"\n",
|
|
"\n",
|
|
"\n",
|
|
"토큰 입력 시퀀스 $X_0,\\dots,X_n$가 주어지면, RNN은 신경망 블록의 시퀀스를 생성하고, 이 시퀀스를 역전파를 통해 끝까지 학습합니다. 각 네트워크 블록은 $(X_i,S_i)$ 쌍을 입력으로 받아들이고, 결과로 $S_{i+1}$을 생성합니다. 최종 상태 $S_n$ 또는 출력 $Y_n$은 선형 분류기로 전달되어 결과를 생성합니다. 모든 네트워크 블록은 동일한 가중치를 공유하며, 하나의 역전파 과정을 통해 끝까지 학습됩니다.\n",
|
|
"\n",
|
|
"> 위 그림은 순환 신경망을 펼친 형태(왼쪽)와 더 간결한 순환 표현(오른쪽)으로 보여줍니다. 모든 RNN 셀이 동일한 **공유 가능한 가중치**를 가진다는 점을 이해하는 것이 중요합니다.\n",
|
|
"\n",
|
|
"상태 벡터 $S_0,\\dots,S_n$가 네트워크를 통해 전달되기 때문에, RNN은 단어 간의 순차적 의존성을 학습할 수 있습니다. 예를 들어, 시퀀스 어딘가에 *not*이라는 단어가 나타날 때, 상태 벡터 내 특정 요소를 부정하는 방법을 학습할 수 있습니다.\n",
|
|
"\n",
|
|
"RNN 셀 내부에는 두 개의 가중치 행렬 $W_H$와 $W_I$, 그리고 편향 $b$가 포함되어 있습니다. 각 RNN 단계에서 입력 $X_i$와 입력 상태 $S_i$가 주어지면, 출력 상태는 $S_{i+1} = f(W_H\\times S_i + W_I\\times X_i+b)$로 계산됩니다. 여기서 $f$는 활성화 함수(종종 $\\tanh$)입니다.\n",
|
|
"\n",
|
|
"> 텍스트 생성(다음 단원에서 다룰 예정)이나 기계 번역과 같은 문제에서는 각 RNN 단계에서 출력 값을 얻고자 합니다. 이 경우, 또 다른 행렬 $W_O$가 있으며, 출력은 $Y_i=f(W_O\\times S_i+b_O)$로 계산됩니다.\n",
|
|
"\n",
|
|
"이제 순환 신경망이 뉴스 데이터셋을 분류하는 데 어떻게 도움을 줄 수 있는지 살펴보겠습니다.\n",
|
|
"\n",
|
|
"> 샌드박스 환경에서는 필요한 라이브러리가 설치되고 데이터가 미리 가져와졌는지 확인하기 위해 다음 셀을 실행해야 합니다. 로컬에서 실행 중이라면, 다음 셀을 건너뛸 수 있습니다.\n"
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "code",
|
|
"execution_count": 1,
|
|
"metadata": {},
|
|
"outputs": [],
|
|
"source": [
|
|
"import sys\n",
|
|
"!{sys.executable} -m pip install --quiet tensorflow_datasets==4.4.0\n",
|
|
"!cd ~ && wget -q -O - https://mslearntensorflowlp.blob.core.windows.net/data/tfds-ag-news.tgz | tar xz"
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "code",
|
|
"execution_count": 2,
|
|
"metadata": {},
|
|
"outputs": [],
|
|
"source": [
|
|
"import tensorflow as tf\n",
|
|
"from tensorflow import keras\n",
|
|
"import tensorflow_datasets as tfds\n",
|
|
"import numpy as np\n",
|
|
"\n",
|
|
"# We are going to be training pretty large models. In order not to face errors, we need\n",
|
|
"# to set tensorflow option to grow GPU memory allocation when required\n",
|
|
"physical_devices = tf.config.list_physical_devices('GPU') \n",
|
|
"if len(physical_devices)>0:\n",
|
|
" tf.config.experimental.set_memory_growth(physical_devices[0], True)\n",
|
|
"\n",
|
|
"ds_train, ds_test = tfds.load('ag_news_subset').values()"
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "markdown",
|
|
"metadata": {
|
|
"nteract": {
|
|
"transient": {
|
|
"deleting": false
|
|
}
|
|
}
|
|
},
|
|
"source": [
|
|
"대규모 모델을 훈련할 때 GPU 메모리 할당이 문제가 될 수 있습니다. 또한, 데이터를 GPU 메모리에 맞추면서도 훈련 속도를 충분히 빠르게 유지하기 위해 다양한 미니배치 크기를 실험해볼 필요가 있습니다. 만약 이 코드를 본인의 GPU 머신에서 실행 중이라면, 훈련 속도를 높이기 위해 미니배치 크기를 조정해보는 실험을 할 수 있습니다.\n",
|
|
"\n",
|
|
"> **Note**: 특정 버전의 NVidia 드라이버는 모델 훈련 후에도 메모리를 해제하지 않는 것으로 알려져 있습니다. 이 노트북에서는 여러 예제를 실행하고 있으며, 동일한 노트북에서 실험을 진행할 경우 특정 환경에서 메모리가 소진될 수 있습니다. 모델 훈련을 시작할 때 이상한 오류가 발생한다면, 노트북 커널을 재시작하는 것을 고려해보세요.\n"
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "code",
|
|
"execution_count": 3,
|
|
"metadata": {
|
|
"collapsed": true,
|
|
"jupyter": {
|
|
"outputs_hidden": false,
|
|
"source_hidden": false
|
|
},
|
|
"nteract": {
|
|
"transient": {
|
|
"deleting": false
|
|
}
|
|
}
|
|
},
|
|
"outputs": [],
|
|
"source": [
|
|
"batch_size = 16\n",
|
|
"embed_size = 64"
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "markdown",
|
|
"metadata": {},
|
|
"source": [
|
|
"## 간단한 RNN 분류기\n",
|
|
"\n",
|
|
"간단한 RNN의 경우, 각 순환 유닛은 입력 벡터와 상태 벡터를 받아 새로운 상태 벡터를 생성하는 단순한 선형 네트워크입니다. Keras에서는 이를 `SimpleRNN` 레이어로 표현할 수 있습니다.\n",
|
|
"\n",
|
|
"RNN 레이어에 원-핫 인코딩된 토큰을 직접 전달할 수도 있지만, 차원이 너무 높아 비효율적일 수 있습니다. 따라서 단어 벡터의 차원을 줄이기 위해 임베딩 레이어를 사용하고, 그 뒤에 RNN 레이어와 마지막으로 `Dense` 분류기를 추가할 것입니다.\n",
|
|
"\n",
|
|
"> **Note**: 차원이 그리 높지 않은 경우, 예를 들어 문자 수준 토큰화를 사용하는 경우에는 원-핫 인코딩된 토큰을 RNN 셀에 직접 전달하는 것이 적합할 수 있습니다.\n"
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "code",
|
|
"execution_count": 4,
|
|
"metadata": {},
|
|
"outputs": [
|
|
{
|
|
"name": "stdout",
|
|
"output_type": "stream",
|
|
"text": [
|
|
"Model: \"sequential\"\n",
|
|
"_________________________________________________________________\n",
|
|
"Layer (type) Output Shape Param # \n",
|
|
"=================================================================\n",
|
|
"text_vectorization (TextVect (None, None) 0 \n",
|
|
"_________________________________________________________________\n",
|
|
"embedding (Embedding) (None, None, 64) 1280000 \n",
|
|
"_________________________________________________________________\n",
|
|
"simple_rnn (SimpleRNN) (None, 16) 1296 \n",
|
|
"_________________________________________________________________\n",
|
|
"dense (Dense) (None, 4) 68 \n",
|
|
"=================================================================\n",
|
|
"Total params: 1,281,364\n",
|
|
"Trainable params: 1,281,364\n",
|
|
"Non-trainable params: 0\n",
|
|
"_________________________________________________________________\n"
|
|
]
|
|
}
|
|
],
|
|
"source": [
|
|
"vocab_size = 20000\n",
|
|
"\n",
|
|
"vectorizer = keras.layers.experimental.preprocessing.TextVectorization(\n",
|
|
" max_tokens=vocab_size,\n",
|
|
" input_shape=(1,))\n",
|
|
"\n",
|
|
"model = keras.models.Sequential([\n",
|
|
" vectorizer,\n",
|
|
" keras.layers.Embedding(vocab_size, embed_size),\n",
|
|
" keras.layers.SimpleRNN(16),\n",
|
|
" keras.layers.Dense(4,activation='softmax')\n",
|
|
"])\n",
|
|
"\n",
|
|
"model.summary()"
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "markdown",
|
|
"metadata": {},
|
|
"source": [
|
|
"> **참고:** 여기서는 간단함을 위해 학습되지 않은 임베딩 레이어를 사용하지만, 더 나은 결과를 위해 이전 단원에서 설명한 Word2Vec을 사용하여 사전 학습된 임베딩 레이어를 사용할 수 있습니다. 사전 학습된 임베딩을 활용하도록 코드를 수정하는 것은 좋은 연습이 될 것입니다.\n",
|
|
"\n",
|
|
"이제 RNN을 학습시켜 봅시다. 일반적으로 RNN은 학습시키기가 꽤 어렵습니다. RNN 셀이 시퀀스 길이에 따라 펼쳐지면, 역전파에 관여하는 레이어의 수가 매우 많아지기 때문입니다. 따라서 더 작은 학습률을 선택하고, 더 큰 데이터셋에서 네트워크를 학습시켜야 좋은 결과를 얻을 수 있습니다. 이 과정은 시간이 꽤 오래 걸릴 수 있으므로 GPU를 사용하는 것이 권장됩니다.\n",
|
|
"\n",
|
|
"속도를 높이기 위해, 우리는 뉴스 제목만을 사용하여 RNN 모델을 학습시킬 것이며, 설명 부분은 생략할 것입니다. 설명을 포함하여 학습을 시도해보고 모델을 학습시킬 수 있는지 확인해보는 것도 좋습니다.\n"
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "code",
|
|
"execution_count": 5,
|
|
"metadata": {
|
|
"scrolled": true
|
|
},
|
|
"outputs": [
|
|
{
|
|
"name": "stdout",
|
|
"output_type": "stream",
|
|
"text": [
|
|
"Training vectorizer\n"
|
|
]
|
|
}
|
|
],
|
|
"source": [
|
|
"def extract_title(x):\n",
|
|
" return x['title']\n",
|
|
"\n",
|
|
"def tupelize_title(x):\n",
|
|
" return (extract_title(x),x['label'])\n",
|
|
"\n",
|
|
"print('Training vectorizer')\n",
|
|
"vectorizer.adapt(ds_train.take(2000).map(extract_title))"
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "code",
|
|
"execution_count": 6,
|
|
"metadata": {},
|
|
"outputs": [
|
|
{
|
|
"name": "stdout",
|
|
"output_type": "stream",
|
|
"text": [
|
|
"7500/7500 [==============================] - 82s 11ms/step - loss: 0.6629 - acc: 0.7623 - val_loss: 0.5559 - val_acc: 0.7995\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\n"
|
|
]
|
|
},
|
|
{
|
|
"data": {
|
|
"text/plain": [
|
|
"<tensorflow.python.keras.callbacks.History at 0x7f3e0030d350>"
|
|
]
|
|
},
|
|
"execution_count": 6,
|
|
"metadata": {},
|
|
"output_type": "execute_result"
|
|
}
|
|
],
|
|
"source": [
|
|
"model.compile(loss='sparse_categorical_crossentropy',metrics=['acc'], optimizer='adam')\n",
|
|
"model.fit(ds_train.map(tupelize_title).batch(batch_size),validation_data=ds_test.map(tupelize_title).batch(batch_size))"
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "markdown",
|
|
"metadata": {
|
|
"nteract": {
|
|
"transient": {
|
|
"deleting": false
|
|
}
|
|
}
|
|
},
|
|
"source": [
|
|
"> **참고** 뉴스 제목만으로 학습하고 있기 때문에 정확도가 낮을 가능성이 높습니다.\n"
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "markdown",
|
|
"metadata": {},
|
|
"source": [
|
|
"## 변수 시퀀스 다시 살펴보기\n",
|
|
"\n",
|
|
"`TextVectorization` 레이어는 미니배치 내에서 가변 길이의 시퀀스를 자동으로 패드 토큰으로 채워줍니다. 그런데 이 패드 토큰도 훈련에 참여하게 되며, 이는 모델의 수렴을 복잡하게 만들 수 있습니다.\n",
|
|
"\n",
|
|
"패딩의 양을 최소화하기 위해 사용할 수 있는 몇 가지 접근법이 있습니다. 그 중 하나는 데이터셋을 시퀀스 길이에 따라 재정렬하고 모든 시퀀스를 크기별로 그룹화하는 것입니다. 이는 `tf.data.experimental.bucket_by_sequence_length` 함수를 사용하여 수행할 수 있습니다 (참고: [문서](https://www.tensorflow.org/api_docs/python/tf/data/experimental/bucket_by_sequence_length)).\n",
|
|
"\n",
|
|
"또 다른 접근법은 **마스킹**을 사용하는 것입니다. Keras에서는 일부 레이어가 훈련 시 어떤 토큰을 고려해야 하는지 보여주는 추가 입력을 지원합니다. 모델에 마스킹을 통합하려면 별도의 `Masking` 레이어를 포함시키거나 ([문서](https://keras.io/api/layers/core_layers/masking/)), `Embedding` 레이어의 `mask_zero=True` 매개변수를 지정할 수 있습니다.\n",
|
|
"\n",
|
|
"> **Note**: 이 훈련은 전체 데이터셋에서 한 에포크를 완료하는 데 약 5분 정도 소요됩니다. 인내심이 부족하다면 언제든지 훈련을 중단해도 괜찮습니다. 또한, 훈련에 사용되는 데이터 양을 제한하려면 `ds_train` 및 `ds_test` 데이터셋 뒤에 `.take(...)` 절을 추가할 수 있습니다.\n"
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "code",
|
|
"execution_count": 7,
|
|
"metadata": {},
|
|
"outputs": [
|
|
{
|
|
"name": "stdout",
|
|
"output_type": "stream",
|
|
"text": [
|
|
"7500/7500 [==============================] - 371s 49ms/step - loss: 0.5401 - acc: 0.8079 - val_loss: 0.3780 - val_acc: 0.8822\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\n"
|
|
]
|
|
},
|
|
{
|
|
"data": {
|
|
"text/plain": [
|
|
"<tensorflow.python.keras.callbacks.History at 0x7f3dec118850>"
|
|
]
|
|
},
|
|
"execution_count": 7,
|
|
"metadata": {},
|
|
"output_type": "execute_result"
|
|
}
|
|
],
|
|
"source": [
|
|
"def extract_text(x):\n",
|
|
" return x['title']+' '+x['description']\n",
|
|
"\n",
|
|
"def tupelize(x):\n",
|
|
" return (extract_text(x),x['label'])\n",
|
|
"\n",
|
|
"model = keras.models.Sequential([\n",
|
|
" vectorizer,\n",
|
|
" keras.layers.Embedding(vocab_size,embed_size,mask_zero=True),\n",
|
|
" keras.layers.SimpleRNN(16),\n",
|
|
" keras.layers.Dense(4,activation='softmax')\n",
|
|
"])\n",
|
|
"\n",
|
|
"model.compile(loss='sparse_categorical_crossentropy',metrics=['acc'], optimizer='adam')\n",
|
|
"model.fit(ds_train.map(tupelize).batch(batch_size),validation_data=ds_test.map(tupelize).batch(batch_size))"
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "markdown",
|
|
"metadata": {},
|
|
"source": [
|
|
"이제 마스킹을 사용하므로, 제목과 설명 전체 데이터셋으로 모델을 훈련할 수 있습니다.\n",
|
|
"\n",
|
|
"> **Note**: 지금까지 뉴스 제목에 대해 훈련된 벡터라이저를 사용하고, 기사 본문 전체에 대해 훈련하지 않았다는 점을 눈치채셨나요? 이로 인해 일부 토큰이 무시될 가능성이 있습니다. 따라서 벡터라이저를 다시 훈련하는 것이 더 나을 수 있습니다. 하지만 그 영향은 매우 미미할 가능성이 있으므로, 간단함을 위해 이전에 훈련된 벡터라이저를 계속 사용할 것입니다.\n"
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "markdown",
|
|
"metadata": {},
|
|
"source": [
|
|
"## LSTM: 장단기 메모리\n",
|
|
"\n",
|
|
"RNN의 주요 문제 중 하나는 **기울기 소실**입니다. RNN은 상당히 길어질 수 있으며, 역전파 과정에서 네트워크의 첫 번째 레이어까지 기울기를 제대로 전달하기 어려울 수 있습니다. 이런 일이 발생하면 네트워크는 먼 토큰 간의 관계를 학습할 수 없습니다. 이 문제를 피하는 한 가지 방법은 **게이트**를 사용하여 **명시적인 상태 관리**를 도입하는 것입니다. 게이트를 도입하는 가장 일반적인 두 가지 아키텍처는 **장단기 메모리**(LSTM)와 **게이트 순환 유닛**(GRU)입니다. 여기서는 LSTM에 대해 다룹니다.\n",
|
|
"\n",
|
|
"\n",
|
|
"\n",
|
|
"LSTM 네트워크는 RNN과 유사한 방식으로 구성되지만, 레이어 간에 전달되는 두 가지 상태가 있습니다: 실제 상태 $c$와 숨겨진 벡터 $h$입니다. 각 유닛에서 숨겨진 벡터 $h_{t-1}$은 입력 $x_t$와 결합되며, 이 둘이 함께 **게이트**를 통해 상태 $c_t$와 출력 $h_{t}$에 영향을 미칩니다. 각 게이트는 시그모이드 활성화 함수(출력 범위 $[0,1]$)를 가지며, 상태 벡터와 곱해질 때 비트 마스크처럼 작동한다고 생각할 수 있습니다. LSTM에는 다음과 같은 게이트가 있습니다 (위 그림에서 왼쪽에서 오른쪽 순서로):\n",
|
|
"* **망각 게이트**: 벡터 $c_{t-1}$의 어떤 구성 요소를 잊어야 하고, 어떤 것을 통과시켜야 할지를 결정합니다.\n",
|
|
"* **입력 게이트**: 입력 벡터와 이전 숨겨진 벡터에서 얼마나 많은 정보를 상태 벡터에 통합할지를 결정합니다.\n",
|
|
"* **출력 게이트**: 새로운 상태 벡터를 받아 그 구성 요소 중 어떤 것을 사용하여 새로운 숨겨진 벡터 $h_t$를 생성할지를 결정합니다.\n",
|
|
"\n",
|
|
"상태 $c$의 구성 요소는 켜고 끌 수 있는 플래그로 생각할 수 있습니다. 예를 들어, 시퀀스에서 *Alice*라는 이름을 만나면 여성을 지칭한다고 추측하고, 문장에 여성 명사가 있다는 플래그를 상태에서 올립니다. 이후 *and Tom*이라는 단어를 만나면 복수 명사가 있다는 플래그를 올립니다. 이렇게 상태를 조작함으로써 문법적 속성을 추적할 수 있습니다.\n",
|
|
"\n",
|
|
"> **Note**: LSTM의 내부 구조를 이해하는 데 유용한 훌륭한 자료가 있습니다: Christopher Olah의 [Understanding LSTM Networks](https://colah.github.io/posts/2015-08-Understanding-LSTMs/).\n",
|
|
"\n",
|
|
"LSTM 셀의 내부 구조는 복잡해 보일 수 있지만, Keras는 이를 `LSTM` 레이어 내부에 숨겨두었기 때문에 위의 예제에서 우리가 해야 할 유일한 일은 순환 레이어를 교체하는 것입니다:\n"
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "code",
|
|
"execution_count": 8,
|
|
"metadata": {},
|
|
"outputs": [
|
|
{
|
|
"name": "stdout",
|
|
"output_type": "stream",
|
|
"text": [
|
|
"15000/15000 [==============================] - 188s 13ms/step - loss: 0.5692 - acc: 0.7916 - val_loss: 0.3441 - val_acc: 0.8870\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\n"
|
|
]
|
|
},
|
|
{
|
|
"data": {
|
|
"text/plain": [
|
|
"<tensorflow.python.keras.callbacks.History at 0x7f3d6af5c350>"
|
|
]
|
|
},
|
|
"execution_count": 8,
|
|
"metadata": {},
|
|
"output_type": "execute_result"
|
|
}
|
|
],
|
|
"source": [
|
|
"model = keras.models.Sequential([\n",
|
|
" vectorizer,\n",
|
|
" keras.layers.Embedding(vocab_size, embed_size),\n",
|
|
" keras.layers.LSTM(8),\n",
|
|
" keras.layers.Dense(4,activation='softmax')\n",
|
|
"])\n",
|
|
"\n",
|
|
"model.compile(loss='sparse_categorical_crossentropy',metrics=['acc'], optimizer='adam')\n",
|
|
"model.fit(ds_train.map(tupelize).batch(8),validation_data=ds_test.map(tupelize).batch(8))"
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "markdown",
|
|
"metadata": {},
|
|
"source": []
|
|
},
|
|
{
|
|
"cell_type": "markdown",
|
|
"metadata": {},
|
|
"source": [
|
|
"## 양방향 및 다층 RNN\n",
|
|
"\n",
|
|
"지금까지의 예제에서는 순환 신경망이 시퀀스의 시작부터 끝까지 작동했습니다. 이는 우리가 읽거나 말을 들을 때의 방향과 같기 때문에 자연스럽게 느껴집니다. 하지만 입력 시퀀스를 임의로 접근해야 하는 상황에서는 순환 연산을 양방향으로 실행하는 것이 더 적합합니다. 양방향으로 연산을 허용하는 RNN을 **양방향 RNN**이라고 하며, 이는 순환 레이어를 특별한 `Bidirectional` 레이어로 감싸서 생성할 수 있습니다.\n",
|
|
"\n",
|
|
"> **Note**: `Bidirectional` 레이어는 내부 레이어의 두 복사본을 생성하며, 그 중 하나의 `go_backwards` 속성을 `True`로 설정하여 시퀀스를 반대 방향으로 처리하도록 만듭니다.\n",
|
|
"\n",
|
|
"순환 신경망은 단방향이든 양방향이든 시퀀스 내의 패턴을 포착하고 이를 상태 벡터에 저장하거나 출력으로 반환합니다. 합성곱 신경망과 마찬가지로, 첫 번째 레이어에서 추출한 저수준 패턴을 기반으로 더 높은 수준의 패턴을 포착하기 위해 첫 번째 레이어 다음에 또 다른 순환 레이어를 추가할 수 있습니다. 이를 통해 **다층 RNN**이라는 개념이 도출되며, 이는 두 개 이상의 순환 신경망으로 구성되고 이전 레이어의 출력이 다음 레이어의 입력으로 전달됩니다.\n",
|
|
"\n",
|
|
"\n",
|
|
"\n",
|
|
"*Fernando López의 [이 훌륭한 글](https://towardsdatascience.com/from-a-lstm-cell-to-a-multilayer-lstm-network-with-pytorch-2899eb5696f3)에서 가져온 그림입니다.*\n",
|
|
"\n",
|
|
"Keras는 이러한 네트워크를 쉽게 구성할 수 있도록 해줍니다. 모델에 더 많은 순환 레이어를 추가하기만 하면 됩니다. 마지막 레이어를 제외한 모든 레이어에는 `return_sequences=True` 매개변수를 지정해야 합니다. 이는 순환 연산의 최종 상태뿐만 아니라 모든 중간 상태를 반환하도록 레이어가 필요하기 때문입니다.\n",
|
|
"\n",
|
|
"이제 분류 문제를 위해 양방향 LSTM 두 개 층으로 구성된 모델을 만들어 보겠습니다.\n",
|
|
"\n",
|
|
"> **Note** 이 코드는 실행 시간이 꽤 오래 걸리지만, 지금까지 본 것 중 가장 높은 정확도를 제공합니다. 따라서 기다려서 결과를 확인할 가치가 있을 수 있습니다.\n"
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "code",
|
|
"execution_count": 9,
|
|
"metadata": {},
|
|
"outputs": [
|
|
{
|
|
"name": "stdout",
|
|
"output_type": "stream",
|
|
"text": [
|
|
"5044/7500 [===================>..........] - ETA: 2:33 - loss: 0.3709 - acc: 0.8706\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\r5045/7500 [===================>..........] - ETA: 2:33 - loss: 0.3709 - acc: 0.8706"
|
|
]
|
|
}
|
|
],
|
|
"source": [
|
|
"model = keras.models.Sequential([\n",
|
|
" vectorizer,\n",
|
|
" keras.layers.Embedding(vocab_size, 128, mask_zero=True),\n",
|
|
" keras.layers.Bidirectional(keras.layers.LSTM(64,return_sequences=True)),\n",
|
|
" keras.layers.Bidirectional(keras.layers.LSTM(64)), \n",
|
|
" keras.layers.Dense(4,activation='softmax')\n",
|
|
"])\n",
|
|
"\n",
|
|
"model.compile(loss='sparse_categorical_crossentropy',metrics=['acc'], optimizer='adam')\n",
|
|
"model.fit(ds_train.map(tupelize).batch(batch_size),\n",
|
|
" validation_data=ds_test.map(tupelize).batch(batch_size))"
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "markdown",
|
|
"metadata": {},
|
|
"source": [
|
|
"## RNN을 활용한 기타 작업\n",
|
|
"\n",
|
|
"지금까지 우리는 RNN을 사용하여 텍스트 시퀀스를 분류하는 데 초점을 맞췄습니다. 하지만 RNN은 텍스트 생성이나 기계 번역과 같은 훨씬 더 다양한 작업을 처리할 수 있습니다. 이러한 작업은 다음 단원에서 다룰 예정입니다.\n"
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "markdown",
|
|
"metadata": {},
|
|
"source": [
|
|
"\n---\n\n**면책 조항**: \n이 문서는 AI 번역 서비스 [Co-op Translator](https://github.com/Azure/co-op-translator)를 사용하여 번역되었습니다. 정확성을 위해 최선을 다하고 있지만, 자동 번역에는 오류나 부정확성이 포함될 수 있습니다. 원본 문서를 해당 언어로 작성된 상태에서 권위 있는 자료로 간주해야 합니다. 중요한 정보의 경우, 전문적인 인간 번역을 권장합니다. 이 번역 사용으로 인해 발생하는 오해나 잘못된 해석에 대해 책임을 지지 않습니다.\n"
|
|
]
|
|
}
|
|
],
|
|
"metadata": {
|
|
"kernel_info": {
|
|
"name": "conda-env-py37_tensorflow-py"
|
|
},
|
|
"kernelspec": {
|
|
"display_name": "py37_tensorflow",
|
|
"language": "python",
|
|
"name": "conda-env-py37_tensorflow-py"
|
|
},
|
|
"language_info": {
|
|
"codemirror_mode": {
|
|
"name": "ipython",
|
|
"version": 3
|
|
},
|
|
"file_extension": ".py",
|
|
"mimetype": "text/x-python",
|
|
"name": "python",
|
|
"nbconvert_exporter": "python",
|
|
"pygments_lexer": "ipython3",
|
|
"version": "3.7.9"
|
|
},
|
|
"nteract": {
|
|
"version": "nteract-front-end@1.0.0"
|
|
},
|
|
"coopTranslator": {
|
|
"original_hash": "81351e61f619b432ff51010a4f993194",
|
|
"translation_date": "2025-08-31T13:57:04+00:00",
|
|
"source_file": "lessons/5-NLP/16-RNN/RNNTF.ipynb",
|
|
"language_code": "ko"
|
|
}
|
|
},
|
|
"nbformat": 4,
|
|
"nbformat_minor": 4
|
|
} |