{ "cells": [ { "cell_type": "markdown", "metadata": {}, "source": [ "# งานการจัดประเภทข้อความ\n", "\n", "ในโมดูลนี้ เราจะเริ่มต้นด้วยงานการจัดประเภทข้อความง่าย ๆ โดยใช้ชุดข้อมูล **[AG_NEWS](http://www.di.unipi.it/~gulli/AG_corpus_of_news_articles.html)**: เราจะจัดประเภทหัวข้อข่าวให้อยู่ในหนึ่งใน 4 หมวดหมู่ ได้แก่ โลก กีฬา ธุรกิจ และวิทยาศาสตร์/เทคโนโลยี\n", "\n", "## ชุดข้อมูล\n", "\n", "เพื่อโหลดชุดข้อมูล เราจะใช้ API ของ **[TensorFlow Datasets](https://www.tensorflow.org/datasets)**\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", "* ใช้ **tokenizer** เพื่อแบ่งข้อความออกเป็น **tokens** \n", "* สร้าง **vocabulary** ของ tokens เหล่านั้น \n", "\n", "### การจำกัดขนาดของคำศัพท์\n", "\n", "ในตัวอย่างชุดข้อมูล AG News ขนาดของคำศัพท์ค่อนข้างใหญ่ มีมากกว่า 100,000 คำ โดยทั่วไปแล้ว เราไม่จำเป็นต้องใช้คำที่ปรากฏในข้อความน้อยมาก — มีเพียงไม่กี่ประโยคเท่านั้นที่จะมีคำเหล่านั้น และโมเดลจะไม่สามารถเรียนรู้จากคำเหล่านั้นได้ ดังนั้นจึงสมเหตุสมผลที่จะจำกัดขนาดของคำศัพท์ให้เล็กลงโดยการส่งอาร์กิวเมนต์ไปยังตัวสร้าง vectorizer:\n", "\n", "ทั้งสองขั้นตอนนี้สามารถจัดการได้โดยใช้เลเยอร์ **TextVectorization** มาสร้างออบเจ็กต์ vectorizer และเรียกใช้เมธอด `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": [ "" ] }, "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", "![ภาพแสดงการแสดงข้อความแบบ bag-of-words ในหน่วยความจำ](../../../../../translated_images/th/bag-of-words-example.606fc1738f1d7ba9.webp)\n", "\n", "> **Note**: คุณสามารถคิดถึง BoW ว่าเป็นผลรวมของเวกเตอร์แบบ one-hot-encoded ของคำแต่ละคำในข้อความก็ได้เช่นกัน\n", "\n", "ด้านล่างนี้คือตัวอย่างการสร้างการแสดงข้อความแบบ bag-of-words โดยใช้ไลบรารี Scikit Learn ในภาษา Python:\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 classifier\n", "\n", "ตอนนี้เราได้เรียนรู้วิธีสร้างตัวแทนแบบ bag-of-words สำหรับข้อความของเราแล้ว มาฝึกโมเดล classifier ที่ใช้ตัวแทนนี้กัน ก่อนอื่น เราจำเป็นต้องแปลงชุดข้อมูลของเราให้อยู่ในรูปแบบ 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": [ "" ] }, "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 เช่นกัน เราสามารถกำหนดเครือข่ายที่รวมตัวแปลงเวกเตอร์ไว้ และฝึกมันแบบ end-to-end ด้วยวิธีนี้เราไม่จำเป็นต้องแปลงข้อมูลชุดด้วย `map` เราสามารถส่งข้อมูลชุดต้นฉบับไปยังอินพุตของเครือข่ายได้เลย\n", "\n", "> **Note**: เรายังคงต้องใช้ `map` กับข้อมูลชุดของเราเพื่อแปลงฟิลด์จากดิกชันนารี (เช่น `title`, `description` และ `label`) เป็นทูเพิล อย่างไรก็ตาม เมื่อโหลดข้อมูลจากดิสก์ เราสามารถสร้างข้อมูลชุดที่มีโครงสร้างที่ต้องการตั้งแต่แรกได้\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": [ "" ] }, "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", "ข้อจำกัดอย่างหนึ่งของวิธีการแบบถุงคำ (bag-of-words) คือบางคำเป็นส่วนหนึ่งของวลีที่มีหลายคำ ตัวอย่างเช่น คำว่า 'hot dog' มีความหมายที่แตกต่างอย่างสิ้นเชิงจากคำว่า 'hot' และ 'dog' ในบริบทอื่นๆ หากเราแทนคำว่า 'hot' และ 'dog' ด้วยเวกเตอร์เดียวกันเสมอ อาจทำให้โมเดลของเราสับสนได้\n", "\n", "เพื่อแก้ปัญหานี้ **การแทนค่าด้วยเอ็นแกรม** มักถูกนำมาใช้ในวิธีการจัดประเภทเอกสาร โดยที่ความถี่ของแต่ละคำ, คำคู่ หรือคำสามคำ เป็นคุณลักษณะที่มีประโยชน์สำหรับการฝึกตัวจำแนก ในการแทนค่าด้วยไบแกรม ตัวอย่างเช่น เราจะเพิ่มคู่คำทั้งหมดลงในคลังคำ นอกเหนือจากคำเดิมที่มีอยู่แล้ว\n", "\n", "ด้านล่างนี้คือตัวอย่างวิธีการสร้างการแทนค่าถุงคำแบบไบแกรมโดยใช้ Scikit Learn:\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 คือขนาดของคำศัพท์ที่เพิ่มขึ้นอย่างรวดเร็วมาก ในทางปฏิบัติ เราจำเป็นต้องรวมการแสดงผลแบบ n-gram เข้ากับเทคนิคการลดมิติ เช่น *embeddings* ซึ่งเราจะพูดถึงในบทถัดไป\n", "\n", "ในการใช้การแสดงผลแบบ n-gram กับชุดข้อมูล **AG News** เราจำเป็นต้องส่งพารามิเตอร์ `ngrams` ไปยังตัวสร้าง `TextVectorization` ขนาดของคำศัพท์แบบ bigram นั้น **ใหญ่ขึ้นอย่างมาก** ในกรณีของเรา มีมากกว่า 1.3 ล้านคำ! ดังนั้นจึงสมเหตุสมผลที่จะจำกัดคำแบบ bigram ด้วยจำนวนที่เหมาะสม\n", "\n", "เราสามารถใช้โค้ดเดียวกับด้านบนเพื่อฝึกตัวจำแนกประเภทได้ อย่างไรก็ตาม วิธีนี้จะใช้หน่วยความจำอย่างไม่มีประสิทธิภาพ ในบทถัดไป เราจะฝึกตัวจำแนกประเภทแบบ bigram โดยใช้ embeddings ในระหว่างนี้ คุณสามารถทดลองฝึกตัวจำแนกประเภทแบบ bigram ในโน้ตบุ๊กนี้และดูว่าคุณสามารถเพิ่มความแม่นยำได้หรือไม่\n" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## การคำนวณเวกเตอร์ BoW โดยอัตโนมัติ\n", "\n", "ในตัวอย่างข้างต้น เราได้คำนวณเวกเตอร์ BoW ด้วยมือโดยการรวมผลรวมของการเข้ารหัสแบบ one-hot ของแต่ละคำ อย่างไรก็ตาม เวอร์ชันล่าสุดของ TensorFlow ช่วยให้เราสามารถคำนวณเวกเตอร์ BoW ได้โดยอัตโนมัติ เพียงแค่เพิ่มพารามิเตอร์ `output_mode='count` ลงในตัวสร้าง vectorizer วิธีนี้ทำให้การกำหนดและฝึกโมเดลของเราง่ายขึ้นอย่างมาก:\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": [ "" ] }, "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** ย่อมาจาก **term frequency - inverse document frequency** ซึ่งเป็นรูปแบบหนึ่งของ bag-of-words โดยแทนที่จะใช้ค่าไบนารี 0/1 เพื่อบ่งบอกการปรากฏของคำในเอกสาร จะใช้ค่าทศนิยมที่สัมพันธ์กับความถี่ของการปรากฏของคำในคอร์ปัสแทน\n", "\n", "ในเชิงคณิตศาสตร์ น้ำหนัก $w_{ij}$ ของคำ $i$ ในเอกสาร $j$ ถูกกำหนดเป็น:\n", "$$\n", "w_{ij} = tf_{ij}\\times\\log({N\\over df_i})\n", "$$\n", "โดยที่\n", "* $tf_{ij}$ คือจำนวนครั้งที่คำ $i$ ปรากฏในเอกสาร $j$ หรือค่าของ BoW ที่เราเคยเห็นมาก่อน\n", "* $N$ คือจำนวนเอกสารทั้งหมดในชุดข้อมูล\n", "* $df_i$ คือจำนวนเอกสารที่มีคำ $i$ ในชุดข้อมูลทั้งหมด\n", "\n", "ค่าของ TF-IDF $w_{ij}$ จะเพิ่มขึ้นตามสัดส่วนของจำนวนครั้งที่คำปรากฏในเอกสาร และจะถูกปรับลดลงตามจำนวนเอกสารในคอร์ปัสที่มีคำดังกล่าว ซึ่งช่วยปรับให้เหมาะสมกับความจริงที่ว่าบางคำปรากฏบ่อยกว่าคำอื่นๆ ตัวอย่างเช่น หากคำปรากฏใน *ทุก* เอกสารในชุดข้อมูล $df_i=N$ และ $w_{ij}=0$ คำเหล่านั้นจะถูกละเลยไปโดยสิ้นเชิง\n", "\n", "คุณสามารถสร้างการเวกเตอร์ TF-IDF ของข้อความได้อย่างง่ายดายโดยใช้ Scikit Learn:\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` สามารถคำนวณความถี่ TF-IDF ได้โดยอัตโนมัติโดยการส่งพารามิเตอร์ `output_mode='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": [ "" ] }, "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. Firth กล่าวไว้ในปี 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-29T11:03:50+00:00", "source_file": "lessons/5-NLP/13-TextRep/TextRepresentationTF.ipynb", "language_code": "th" } }, "nbformat": 4, "nbformat_minor": 4 }