647 lines
27 KiB
Plaintext
647 lines
27 KiB
Plaintext
{
|
|
"cells": [
|
|
{
|
|
"cell_type": "markdown",
|
|
"metadata": {},
|
|
"source": [
|
|
"# 텍스트 분류 작업\n",
|
|
"\n",
|
|
"이 모듈에서는 **[AG_NEWS](http://www.di.unipi.it/~gulli/AG_corpus_of_news_articles.html)** 데이터셋을 기반으로 간단한 텍스트 분류 작업을 시작합니다. 우리는 뉴스 헤드라인을 세계, 스포츠, 비즈니스, 과학/기술 중 하나의 카테고리로 분류할 것입니다.\n",
|
|
"\n",
|
|
"## 데이터셋\n",
|
|
"\n",
|
|
"데이터셋을 로드하기 위해 **[TensorFlow Datasets](https://www.tensorflow.org/datasets)** API를 사용할 것입니다.\n"
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "code",
|
|
"execution_count": 1,
|
|
"metadata": {},
|
|
"outputs": [],
|
|
"source": [
|
|
"import tensorflow as tf\n",
|
|
"from tensorflow import keras\n",
|
|
"import tensorflow_datasets as tfds\n",
|
|
"\n",
|
|
"# In this tutorial, we will be training a lot of models. In order to use GPU memory cautiously,\n",
|
|
"# we will 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",
|
|
"dataset = tfds.load('ag_news_subset')"
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "markdown",
|
|
"metadata": {},
|
|
"source": [
|
|
"이제 `dataset['train']` 및 `dataset['test']`를 사용하여 데이터셋의 학습 및 테스트 부분에 각각 접근할 수 있습니다:\n"
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "code",
|
|
"execution_count": 3,
|
|
"metadata": {},
|
|
"outputs": [
|
|
{
|
|
"name": "stdout",
|
|
"output_type": "stream",
|
|
"text": [
|
|
"Length of train dataset = 120000\n",
|
|
"Length of test dataset = 7600\n"
|
|
]
|
|
}
|
|
],
|
|
"source": [
|
|
"ds_train = dataset['train']\n",
|
|
"ds_test = dataset['test']\n",
|
|
"\n",
|
|
"print(f\"Length of train dataset = {len(ds_train)}\")\n",
|
|
"print(f\"Length of test dataset = {len(ds_test)}\")"
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "markdown",
|
|
"metadata": {},
|
|
"source": [
|
|
"우리의 데이터셋에서 새로운 헤드라인 10개를 출력해 봅시다:\n"
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "code",
|
|
"execution_count": 4,
|
|
"metadata": {},
|
|
"outputs": [
|
|
{
|
|
"name": "stdout",
|
|
"output_type": "stream",
|
|
"text": [
|
|
"3 (Sci/Tech) -> b'AMD Debuts Dual-Core Opteron Processor' b'AMD #39;s new dual-core Opteron chip is designed mainly for corporate computing applications, including databases, Web services, and financial transactions.'\n",
|
|
"1 (Sports) -> b\"Wood's Suspension Upheld (Reuters)\" b'Reuters - Major League Baseball\\\\Monday announced a decision on the appeal filed by Chicago Cubs\\\\pitcher Kerry Wood regarding a suspension stemming from an\\\\incident earlier this season.'\n",
|
|
"2 (Business) -> b'Bush reform may have blue states seeing red' b'President Bush #39;s quot;revenue-neutral quot; tax reform needs losers to balance its winners, and people claiming the federal deduction for state and local taxes may be in administration planners #39; sights, news reports say.'\n",
|
|
"3 (Sci/Tech) -> b\"'Halt science decline in schools'\" b'Britain will run out of leading scientists unless science education is improved, says Professor Colin Pillinger.'\n",
|
|
"1 (Sports) -> b'Gerrard leaves practice' b'London, England (Sports Network) - England midfielder Steven Gerrard injured his groin late in Thursday #39;s training session, but is hopeful he will be ready for Saturday #39;s World Cup qualifier against Austria.'\n"
|
|
]
|
|
}
|
|
],
|
|
"source": [
|
|
"classes = ['World', 'Sports', 'Business', 'Sci/Tech']\n",
|
|
"\n",
|
|
"for i,x in zip(range(5),ds_train):\n",
|
|
" print(f\"{x['label']} ({classes[x['label']]}) -> {x['title']} {x['description']}\")"
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "markdown",
|
|
"metadata": {},
|
|
"source": [
|
|
"## 텍스트 벡터화\n",
|
|
"\n",
|
|
"이제 텍스트를 **숫자**로 변환하여 텐서로 표현해야 합니다. 단어 수준의 표현을 원한다면, 두 가지 작업이 필요합니다:\n",
|
|
"\n",
|
|
"* **토크나이저**를 사용하여 텍스트를 **토큰**으로 분리합니다.\n",
|
|
"* 이러한 토큰의 **어휘집**을 만듭니다.\n",
|
|
"\n",
|
|
"### 어휘 크기 제한하기\n",
|
|
"\n",
|
|
"AG News 데이터셋 예제를 보면, 어휘 크기가 상당히 큽니다. 10만 개 이상의 단어가 포함되어 있습니다. 일반적으로 텍스트에 거의 등장하지 않는 단어는 필요하지 않습니다. 이런 단어들은 몇 문장에만 나타나며, 모델이 학습하는 데 큰 도움이 되지 않습니다. 따라서 벡터화 생성자에 인자를 전달하여 어휘 크기를 더 작은 숫자로 제한하는 것이 합리적입니다.\n",
|
|
"\n",
|
|
"이 두 단계는 모두 **TextVectorization** 레이어를 사용하여 처리할 수 있습니다. 이제 벡터화 객체를 생성한 다음, `adapt` 메서드를 호출하여 모든 텍스트를 처리하고 어휘집을 만들어 보겠습니다.\n"
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "code",
|
|
"execution_count": 5,
|
|
"metadata": {},
|
|
"outputs": [],
|
|
"source": [
|
|
"vocab_size = 50000\n",
|
|
"vectorizer = keras.layers.experimental.preprocessing.TextVectorization(max_tokens=vocab_size)\n",
|
|
"vectorizer.adapt(ds_train.take(500).map(lambda x: x['title']+' '+x['description']))"
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "markdown",
|
|
"metadata": {},
|
|
"source": [
|
|
"> **참고** 우리는 전체 데이터셋의 일부만 사용하여 어휘를 구축하고 있습니다. 이렇게 하면 실행 시간을 단축시켜 기다리는 시간을 줄일 수 있습니다. 하지만 전체 데이터셋의 일부 단어가 어휘에 포함되지 않아 학습 중 무시될 위험이 있습니다. 따라서 `adapt` 과정에서 전체 어휘 크기를 사용하고 모든 데이터셋을 처리하면 최종 정확도가 약간 향상될 수 있지만, 그 차이는 크지 않을 것입니다.\n",
|
|
"\n",
|
|
"이제 실제 어휘에 접근할 수 있습니다:\n"
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "code",
|
|
"execution_count": 6,
|
|
"metadata": {},
|
|
"outputs": [
|
|
{
|
|
"name": "stdout",
|
|
"output_type": "stream",
|
|
"text": [
|
|
"['', '[UNK]', 'the', 'to', 'a', 'in', 'of', 'and', 'on', 'for']\n",
|
|
"Length of vocabulary: 5335\n"
|
|
]
|
|
}
|
|
],
|
|
"source": [
|
|
"vocab = vectorizer.get_vocabulary()\n",
|
|
"vocab_size = len(vocab)\n",
|
|
"print(vocab[:10])\n",
|
|
"print(f\"Length of vocabulary: {vocab_size}\")"
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "markdown",
|
|
"metadata": {},
|
|
"source": [
|
|
"벡터라이저를 사용하여 우리는 텍스트를 숫자 집합으로 쉽게 인코딩할 수 있습니다.\n"
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "code",
|
|
"execution_count": 7,
|
|
"metadata": {},
|
|
"outputs": [
|
|
{
|
|
"data": {
|
|
"text/plain": [
|
|
"<tf.Tensor: shape=(7,), dtype=int64, numpy=array([ 112, 3695, 3, 304, 11, 1041, 1], dtype=int64)>"
|
|
]
|
|
},
|
|
"execution_count": 7,
|
|
"metadata": {},
|
|
"output_type": "execute_result"
|
|
}
|
|
],
|
|
"source": [
|
|
"vectorizer('I love to play with my words')"
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "markdown",
|
|
"metadata": {},
|
|
"source": [
|
|
"## Bag-of-words 텍스트 표현\n",
|
|
"\n",
|
|
"단어는 의미를 나타내기 때문에, 때로는 문장에서 단어의 순서를 고려하지 않고 개별 단어만을 살펴보는 것으로 텍스트의 의미를 파악할 수 있습니다. 예를 들어, 뉴스 분류를 할 때 *weather*와 *snow* 같은 단어는 *날씨 예보*를 나타낼 가능성이 높고, *stocks*와 *dollar* 같은 단어는 *금융 뉴스*에 해당할 것입니다.\n",
|
|
"\n",
|
|
"**Bag-of-words** (BoW) 벡터 표현은 가장 이해하기 쉬운 전통적인 벡터 표현 방식입니다. 각 단어는 벡터 인덱스에 연결되며, 벡터 요소는 주어진 문서에서 각 단어가 나타난 횟수를 포함합니다.\n",
|
|
"\n",
|
|
" \n",
|
|
"\n",
|
|
"> **Note**: BoW를 텍스트 내 개별 단어에 대한 모든 원-핫 인코딩 벡터의 합으로 생각할 수도 있습니다.\n",
|
|
"\n",
|
|
"아래는 Scikit Learn 파이썬 라이브러리를 사용하여 bag-of-words 표현을 생성하는 예제입니다:\n"
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "code",
|
|
"execution_count": 8,
|
|
"metadata": {},
|
|
"outputs": [
|
|
{
|
|
"data": {
|
|
"text/plain": [
|
|
"array([[1, 1, 0, 2, 0, 0, 0, 0, 0]], dtype=int64)"
|
|
]
|
|
},
|
|
"execution_count": 8,
|
|
"metadata": {},
|
|
"output_type": "execute_result"
|
|
}
|
|
],
|
|
"source": [
|
|
"from sklearn.feature_extraction.text import CountVectorizer\n",
|
|
"sc_vectorizer = CountVectorizer()\n",
|
|
"corpus = [\n",
|
|
" 'I like hot dogs.',\n",
|
|
" 'The dog ran fast.',\n",
|
|
" 'Its hot outside.',\n",
|
|
" ]\n",
|
|
"sc_vectorizer.fit_transform(corpus)\n",
|
|
"sc_vectorizer.transform(['My dog likes hot dogs on a hot day.']).toarray()"
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "markdown",
|
|
"metadata": {},
|
|
"source": [
|
|
"우리는 위에서 정의한 Keras 벡터라이저를 사용하여 각 단어 번호를 원-핫 인코딩으로 변환하고, 그 벡터들을 모두 더할 수도 있습니다.\n"
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "code",
|
|
"execution_count": 9,
|
|
"metadata": {},
|
|
"outputs": [
|
|
{
|
|
"data": {
|
|
"text/plain": [
|
|
"array([0., 5., 0., ..., 0., 0., 0.], dtype=float32)"
|
|
]
|
|
},
|
|
"execution_count": 9,
|
|
"metadata": {},
|
|
"output_type": "execute_result"
|
|
}
|
|
],
|
|
"source": [
|
|
"def to_bow(text):\n",
|
|
" return tf.reduce_sum(tf.one_hot(vectorizer(text),vocab_size),axis=0)\n",
|
|
"\n",
|
|
"to_bow('My dog likes hot dogs on a hot day.').numpy()"
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "markdown",
|
|
"metadata": {},
|
|
"source": [
|
|
"> **참고**: 결과가 이전 예제와 다르다는 점이 놀라울 수 있습니다. 그 이유는 Keras 예제에서는 벡터의 길이가 전체 AG News 데이터셋에서 생성된 어휘 크기에 해당하지만, Scikit Learn 예제에서는 샘플 텍스트에서 즉석으로 어휘를 생성했기 때문입니다.\n"
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "markdown",
|
|
"metadata": {},
|
|
"source": [
|
|
"## BoW 분류기 학습하기\n",
|
|
"\n",
|
|
"이제 텍스트의 bag-of-words 표현을 만드는 방법을 배웠으니, 이를 사용하는 분류기를 학습시켜 봅시다. 먼저, 데이터셋을 bag-of-words 표현으로 변환해야 합니다. 이는 다음과 같이 `map` 함수를 사용하여 수행할 수 있습니다:\n"
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "code",
|
|
"execution_count": 11,
|
|
"metadata": {},
|
|
"outputs": [],
|
|
"source": [
|
|
"batch_size = 128\n",
|
|
"\n",
|
|
"ds_train_bow = ds_train.map(lambda x: (to_bow(x['title']+x['description']),x['label'])).batch(batch_size)\n",
|
|
"ds_test_bow = ds_test.map(lambda x: (to_bow(x['title']+x['description']),x['label'])).batch(batch_size)"
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "markdown",
|
|
"metadata": {},
|
|
"source": [
|
|
"이제 하나의 선형 계층을 포함하는 간단한 분류기 신경망을 정의해 봅시다. 입력 크기는 `vocab_size`이고, 출력 크기는 클래스 수(4)에 해당합니다. 분류 작업을 해결하고 있으므로 최종 활성화 함수는 **softmax**입니다:\n"
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "code",
|
|
"execution_count": 12,
|
|
"metadata": {},
|
|
"outputs": [
|
|
{
|
|
"name": "stdout",
|
|
"output_type": "stream",
|
|
"text": [
|
|
"938/938 [==============================] - 66s 70ms/step - loss: 0.6144 - acc: 0.8427 - val_loss: 0.4416 - val_acc: 0.8697\n"
|
|
]
|
|
},
|
|
{
|
|
"data": {
|
|
"text/plain": [
|
|
"<keras.callbacks.History at 0x20c70a947f0>"
|
|
]
|
|
},
|
|
"execution_count": 12,
|
|
"metadata": {},
|
|
"output_type": "execute_result"
|
|
}
|
|
],
|
|
"source": [
|
|
"model = keras.models.Sequential([\n",
|
|
" keras.layers.Dense(4,activation='softmax',input_shape=(vocab_size,))\n",
|
|
"])\n",
|
|
"model.compile(loss='sparse_categorical_crossentropy',optimizer='adam',metrics=['acc'])\n",
|
|
"model.fit(ds_train_bow,validation_data=ds_test_bow)"
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "markdown",
|
|
"metadata": {},
|
|
"source": [
|
|
"우리가 4개의 클래스를 가지고 있기 때문에, 80% 이상의 정확도는 좋은 결과입니다.\n",
|
|
"\n",
|
|
"## 하나의 네트워크로 분류기 학습시키기\n",
|
|
"\n",
|
|
"벡터라이저가 Keras 레이어이기 때문에, 이를 포함한 네트워크를 정의하고 처음부터 끝까지 학습시킬 수 있습니다. 이렇게 하면 `map`을 사용해 데이터셋을 벡터화할 필요가 없으며, 원본 데이터셋을 네트워크의 입력으로 바로 전달할 수 있습니다.\n",
|
|
"\n",
|
|
"> **Note**: 여전히 데이터셋의 필드(예: `title`, `description`, `label`)를 딕셔너리에서 튜플로 변환하기 위해 `map`을 적용해야 합니다. 하지만 디스크에서 데이터를 로드할 때 처음부터 필요한 구조를 가진 데이터셋을 생성할 수 있습니다.\n"
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "code",
|
|
"execution_count": 13,
|
|
"metadata": {},
|
|
"outputs": [
|
|
{
|
|
"name": "stdout",
|
|
"output_type": "stream",
|
|
"text": [
|
|
"Model: \"model\"\n",
|
|
"_________________________________________________________________\n",
|
|
" Layer (type) Output Shape Param # \n",
|
|
"=================================================================\n",
|
|
" input_1 (InputLayer) [(None, 1)] 0 \n",
|
|
" \n",
|
|
" text_vectorization (TextVec (None, None) 0 \n",
|
|
" torization) \n",
|
|
" \n",
|
|
" tf.one_hot (TFOpLambda) (None, None, 5335) 0 \n",
|
|
" \n",
|
|
" tf.math.reduce_sum (TFOpLam (None, 5335) 0 \n",
|
|
" bda) \n",
|
|
" \n",
|
|
" dense_2 (Dense) (None, 4) 21344 \n",
|
|
" \n",
|
|
"=================================================================\n",
|
|
"Total params: 21,344\n",
|
|
"Trainable params: 21,344\n",
|
|
"Non-trainable params: 0\n",
|
|
"_________________________________________________________________\n",
|
|
"938/938 [==============================] - 73s 77ms/step - loss: 0.6057 - acc: 0.8414 - val_loss: 0.4202 - val_acc: 0.8736\n"
|
|
]
|
|
},
|
|
{
|
|
"data": {
|
|
"text/plain": [
|
|
"<keras.callbacks.History at 0x20c721521f0>"
|
|
]
|
|
},
|
|
"execution_count": 13,
|
|
"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",
|
|
"inp = keras.Input(shape=(1,),dtype=tf.string)\n",
|
|
"x = vectorizer(inp)\n",
|
|
"x = tf.reduce_sum(tf.one_hot(x,vocab_size),axis=1)\n",
|
|
"out = keras.layers.Dense(4,activation='softmax')(x)\n",
|
|
"model = keras.models.Model(inp,out)\n",
|
|
"model.summary()\n",
|
|
"\n",
|
|
"model.compile(loss='sparse_categorical_crossentropy',optimizer='adam',metrics=['acc'])\n",
|
|
"model.fit(ds_train.map(tupelize).batch(batch_size),validation_data=ds_test.map(tupelize).batch(batch_size))\n"
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "markdown",
|
|
"metadata": {},
|
|
"source": [
|
|
"## 바이그램, 트라이그램 및 n-그램\n",
|
|
"\n",
|
|
"Bag-of-words 접근법의 한계 중 하나는 일부 단어가 다단어 표현의 일부라는 점입니다. 예를 들어, 'hot dog'이라는 단어는 다른 문맥에서 'hot'과 'dog'이라는 단어와 완전히 다른 의미를 가집니다. 'hot'과 'dog'을 항상 동일한 벡터로 표현하면 모델이 혼란스러워질 수 있습니다.\n",
|
|
"\n",
|
|
"이를 해결하기 위해, **n-그램 표현**이 문서 분류 방법에서 자주 사용됩니다. 여기서 각 단어, 두 단어 또는 세 단어의 빈도는 분류기를 학습시키는 데 유용한 특징이 됩니다. 예를 들어, 바이그램 표현에서는 원래 단어 외에도 모든 단어 쌍을 어휘에 추가합니다.\n",
|
|
"\n",
|
|
"아래는 Scikit Learn을 사용하여 바이그램 bag-of-words 표현을 생성하는 방법의 예입니다:\n"
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "code",
|
|
"execution_count": 14,
|
|
"metadata": {},
|
|
"outputs": [
|
|
{
|
|
"name": "stdout",
|
|
"output_type": "stream",
|
|
"text": [
|
|
"Vocabulary:\n",
|
|
" {'i': 7, 'like': 11, 'hot': 4, 'dogs': 2, 'i like': 8, 'like hot': 12, 'hot dogs': 5, 'the': 16, 'dog': 0, 'ran': 14, 'fast': 3, 'the dog': 17, 'dog ran': 1, 'ran fast': 15, 'its': 9, 'outside': 13, 'its hot': 10, 'hot outside': 6}\n"
|
|
]
|
|
},
|
|
{
|
|
"data": {
|
|
"text/plain": [
|
|
"array([[1, 0, 1, 0, 2, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]],\n",
|
|
" dtype=int64)"
|
|
]
|
|
},
|
|
"execution_count": 14,
|
|
"metadata": {},
|
|
"output_type": "execute_result"
|
|
}
|
|
],
|
|
"source": [
|
|
"bigram_vectorizer = CountVectorizer(ngram_range=(1, 2), token_pattern=r'\\b\\w+\\b', min_df=1)\n",
|
|
"corpus = [\n",
|
|
" 'I like hot dogs.',\n",
|
|
" 'The dog ran fast.',\n",
|
|
" 'Its hot outside.',\n",
|
|
" ]\n",
|
|
"bigram_vectorizer.fit_transform(corpus)\n",
|
|
"print(\"Vocabulary:\\n\",bigram_vectorizer.vocabulary_)\n",
|
|
"bigram_vectorizer.transform(['My dog likes hot dogs on a hot day.']).toarray()\n"
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "markdown",
|
|
"metadata": {},
|
|
"source": [
|
|
"n-gram 접근법의 주요 단점은 어휘 크기가 매우 빠르게 증가한다는 점입니다. 실제로는 *임베딩(embeddings)*과 같은 차원 축소 기법과 n-gram 표현을 결합해야 합니다. 이에 대해서는 다음 단원에서 다룰 예정입니다.\n",
|
|
"\n",
|
|
"**AG News** 데이터셋에서 n-gram 표현을 사용하려면, `TextVectorization` 생성자에 `ngrams` 매개변수를 전달해야 합니다. 바이그램 어휘의 길이는 **상당히 더 커지며**, 우리의 경우 130만 개 이상의 토큰에 달합니다! 따라서 합리적인 숫자로 바이그램 토큰의 수를 제한하는 것이 타당합니다.\n",
|
|
"\n",
|
|
"위와 동일한 코드를 사용하여 분류기를 훈련할 수도 있지만, 이는 메모리 효율성이 매우 낮을 것입니다. 다음 단원에서는 임베딩을 사용하여 바이그램 분류기를 훈련할 것입니다. 그동안 이 노트북에서 바이그램 분류기 훈련을 실험해 보고 더 높은 정확도를 얻을 수 있는지 확인해 보세요.\n"
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "markdown",
|
|
"metadata": {},
|
|
"source": [
|
|
"## BoW 벡터 자동 계산\n",
|
|
"\n",
|
|
"위 예제에서는 개별 단어의 원-핫 인코딩을 합산하여 BoW 벡터를 수작업으로 계산했습니다. 하지만 TensorFlow의 최신 버전을 사용하면 벡터라이저 생성자에 `output_mode='count` 매개변수를 전달하여 BoW 벡터를 자동으로 계산할 수 있습니다. 이를 통해 모델을 정의하고 학습시키는 과정이 훨씬 간단해집니다:\n"
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "code",
|
|
"execution_count": 15,
|
|
"metadata": {},
|
|
"outputs": [
|
|
{
|
|
"name": "stdout",
|
|
"output_type": "stream",
|
|
"text": [
|
|
"Training vectorizer\n",
|
|
"938/938 [==============================] - 7s 7ms/step - loss: 0.5929 - acc: 0.8486 - val_loss: 0.4168 - val_acc: 0.8772\n"
|
|
]
|
|
},
|
|
{
|
|
"data": {
|
|
"text/plain": [
|
|
"<keras.callbacks.History at 0x20c725217c0>"
|
|
]
|
|
},
|
|
"execution_count": 15,
|
|
"metadata": {},
|
|
"output_type": "execute_result"
|
|
}
|
|
],
|
|
"source": [
|
|
"model = keras.models.Sequential([\n",
|
|
" keras.layers.experimental.preprocessing.TextVectorization(max_tokens=vocab_size,output_mode='count'),\n",
|
|
" keras.layers.Dense(4,input_shape=(vocab_size,), activation='softmax')\n",
|
|
"])\n",
|
|
"print(\"Training vectorizer\")\n",
|
|
"model.layers[0].adapt(ds_train.take(500).map(extract_text))\n",
|
|
"model.compile(loss='sparse_categorical_crossentropy',optimizer='adam',metrics=['acc'])\n",
|
|
"model.fit(ds_train.map(tupelize).batch(batch_size),validation_data=ds_test.map(tupelize).batch(batch_size))"
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "markdown",
|
|
"metadata": {},
|
|
"source": [
|
|
"## 용어 빈도 - 역문서 빈도 (TF-IDF)\n",
|
|
"\n",
|
|
"BoW 표현에서는 단어 자체와 상관없이 동일한 방식으로 단어 발생 빈도가 가중치로 사용됩니다. 하지만 *a*나 *in* 같은 자주 등장하는 단어들은 전문 용어에 비해 분류에 훨씬 덜 중요하다는 것은 명백합니다. 대부분의 NLP 작업에서는 특정 단어들이 다른 단어들보다 더 중요합니다.\n",
|
|
"\n",
|
|
"**TF-IDF**는 **용어 빈도 - 역문서 빈도**를 의미합니다. 이는 단순히 문서 내 단어의 존재 여부를 0/1로 나타내는 BoW 방식과 달리, 단어 발생 빈도와 관련된 부동 소수점 값을 사용하는 BoW의 변형입니다.\n",
|
|
"\n",
|
|
"좀 더 공식적으로, 문서 $j$에서 단어 $i$의 가중치 $w_{ij}$는 다음과 같이 정의됩니다:\n",
|
|
"$$\n",
|
|
"w_{ij} = tf_{ij}\\times\\log({N\\over df_i})\n",
|
|
"$$\n",
|
|
"여기서\n",
|
|
"* $tf_{ij}$는 $j$에서 $i$가 등장한 횟수, 즉 이전에 본 BoW 값입니다.\n",
|
|
"* $N$은 컬렉션 내 문서의 총 개수입니다.\n",
|
|
"* $df_i$는 컬렉션 전체에서 단어 $i$를 포함하는 문서의 개수입니다.\n",
|
|
"\n",
|
|
"TF-IDF 값 $w_{ij}$는 단어가 문서에 등장한 횟수에 비례하여 증가하며, 해당 단어를 포함하는 코퍼스 내 문서 수에 따라 조정됩니다. 이는 일부 단어가 다른 단어보다 더 자주 등장하는 사실을 보정하는 데 도움을 줍니다. 예를 들어, 특정 단어가 컬렉션 내 *모든* 문서에 등장한다면, $df_i=N$이 되고 $w_{ij}=0$이 되어 해당 단어는 완전히 무시됩니다.\n",
|
|
"\n",
|
|
"Scikit Learn을 사용하면 텍스트의 TF-IDF 벡터화를 쉽게 생성할 수 있습니다:\n"
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "code",
|
|
"execution_count": 16,
|
|
"metadata": {},
|
|
"outputs": [
|
|
{
|
|
"data": {
|
|
"text/plain": [
|
|
"array([[0.43381609, 0. , 0.43381609, 0. , 0.65985664,\n",
|
|
" 0.43381609, 0. , 0. , 0. , 0. ,\n",
|
|
" 0. , 0. , 0. , 0. , 0. ,\n",
|
|
" 0. ]])"
|
|
]
|
|
},
|
|
"execution_count": 16,
|
|
"metadata": {},
|
|
"output_type": "execute_result"
|
|
}
|
|
],
|
|
"source": [
|
|
"from sklearn.feature_extraction.text import TfidfVectorizer\n",
|
|
"vectorizer = TfidfVectorizer(ngram_range=(1,2))\n",
|
|
"vectorizer.fit_transform(corpus)\n",
|
|
"vectorizer.transform(['My dog likes hot dogs on a hot day.']).toarray()"
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "markdown",
|
|
"metadata": {},
|
|
"source": [
|
|
"Keras에서 `TextVectorization` 레이어는 `output_mode='tf-idf'` 매개변수를 전달하여 TF-IDF 빈도를 자동으로 계산할 수 있습니다. TF-IDF를 사용하면 정확도가 향상되는지 확인하기 위해 위에서 사용한 코드를 반복해 봅시다:\n"
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "code",
|
|
"execution_count": 17,
|
|
"metadata": {},
|
|
"outputs": [
|
|
{
|
|
"name": "stdout",
|
|
"output_type": "stream",
|
|
"text": [
|
|
"Training vectorizer\n",
|
|
"938/938 [==============================] - 12s 12ms/step - loss: 0.4197 - acc: 0.8662 - val_loss: 0.3432 - val_acc: 0.8849\n"
|
|
]
|
|
},
|
|
{
|
|
"data": {
|
|
"text/plain": [
|
|
"<keras.callbacks.History at 0x20c729dfd30>"
|
|
]
|
|
},
|
|
"execution_count": 17,
|
|
"metadata": {},
|
|
"output_type": "execute_result"
|
|
}
|
|
],
|
|
"source": [
|
|
"model = keras.models.Sequential([\n",
|
|
" keras.layers.experimental.preprocessing.TextVectorization(max_tokens=vocab_size,output_mode='tf-idf'),\n",
|
|
" keras.layers.Dense(4,input_shape=(vocab_size,), activation='softmax')\n",
|
|
"])\n",
|
|
"print(\"Training vectorizer\")\n",
|
|
"model.layers[0].adapt(ds_train.take(500).map(extract_text))\n",
|
|
"model.compile(loss='sparse_categorical_crossentropy',optimizer='adam',metrics=['acc'])\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",
|
|
"TF-IDF 표현이 각 단어에 빈도 가중치를 부여하더라도, 의미나 순서를 표현할 수는 없습니다. 유명한 언어학자 J. R. 퍼스가 1935년에 말했듯이, \"단어의 완전한 의미는 항상 맥락적이며, 맥락을 벗어난 의미 연구는 진지하게 받아들일 수 없다.\" 이후 강의에서 언어 모델링을 사용하여 텍스트에서 맥락 정보를 포착하는 방법을 배우게 될 것입니다.\n"
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "markdown",
|
|
"metadata": {},
|
|
"source": [
|
|
"\n---\n\n**면책 조항**: \n이 문서는 AI 번역 서비스 [Co-op Translator](https://github.com/Azure/co-op-translator)를 사용하여 번역되었습니다. 정확성을 위해 최선을 다하고 있지만, 자동 번역에는 오류나 부정확성이 포함될 수 있습니다. 원본 문서를 해당 언어로 작성된 상태에서 권위 있는 자료로 간주해야 합니다. 중요한 정보의 경우, 전문적인 인간 번역을 권장합니다. 이 번역 사용으로 인해 발생하는 오해나 잘못된 해석에 대해 당사는 책임을 지지 않습니다.\n"
|
|
]
|
|
}
|
|
],
|
|
"metadata": {
|
|
"interpreter": {
|
|
"hash": "0cb620c6d4b9f7a635928804c26cf22403d89d98d79684e4529119355ee6d5a5"
|
|
},
|
|
"kernel_info": {
|
|
"name": "conda-env-py37_tensorflow-py"
|
|
},
|
|
"kernelspec": {
|
|
"display_name": "py37_tensorflow",
|
|
"language": "python",
|
|
"name": "python3"
|
|
},
|
|
"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.8.12"
|
|
},
|
|
"nteract": {
|
|
"version": "nteract-front-end@1.0.0"
|
|
},
|
|
"coopTranslator": {
|
|
"original_hash": "19b43951d55b377a76209c24c1f017e4",
|
|
"translation_date": "2025-08-31T14:11:40+00:00",
|
|
"source_file": "lessons/5-NLP/13-TextRep/TextRepresentationTF.ipynb",
|
|
"language_code": "ko"
|
|
}
|
|
},
|
|
"nbformat": 4,
|
|
"nbformat_minor": 4
|
|
} |