{ "cells": [ { "cell_type": "markdown", "metadata": {}, "source": [ "# งานการจัดประเภทข้อความ\n", "\n", "ตามที่เราได้กล่าวไว้ เราจะมุ่งเน้นไปที่งานการจัดประเภทข้อความแบบง่าย โดยใช้ชุดข้อมูล **AG_NEWS** ซึ่งเป็นการจัดประเภทหัวข้อข่าวให้อยู่ในหนึ่งใน 4 หมวดหมู่ ได้แก่ World, Sports, Business และ Sci/Tech\n", "\n", "## ชุดข้อมูล\n", "\n", "ชุดข้อมูลนี้ถูกรวมอยู่ในโมดูล [`torchtext`](https://github.com/pytorch/text) ดังนั้นเราสามารถเข้าถึงได้อย่างง่ายดาย\n" ] }, { "cell_type": "code", "execution_count": 1, "metadata": {}, "outputs": [], "source": [ "import torch\n", "import torchtext\n", "import os\n", "import collections\n", "os.makedirs('./data',exist_ok=True)\n", "train_dataset, test_dataset = torchtext.datasets.AG_NEWS(root='./data')\n", "classes = ['World', 'Sports', 'Business', 'Sci/Tech']" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "ที่นี่ `train_dataset` และ `test_dataset` ประกอบด้วยคอลเลกชันที่ส่งคืนคู่ของป้ายกำกับ (หมายเลขของคลาส) และข้อความตามลำดับ ตัวอย่างเช่น:\n" ] }, { "cell_type": "code", "execution_count": 2, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "(3,\n", " \"Wall St. Bears Claw Back Into the Black (Reuters) Reuters - Short-sellers, Wall Street's dwindling\\\\band of ultra-cynics, are seeing green again.\")" ] }, "execution_count": 2, "metadata": {}, "output_type": "execute_result" } ], "source": [ "list(train_dataset)[0]" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "ดังนั้น มาพิมพ์พาดหัวข่าวใหม่ 10 หัวข้อแรกจากชุดข้อมูลของเรากัน:\n" ] }, { "cell_type": "code", "execution_count": 5, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "**Sci/Tech** -> Wall St. Bears Claw Back Into the Black (Reuters) Reuters - Short-sellers, Wall Street's dwindling\\band of ultra-cynics, are seeing green again.\n", "**Sci/Tech** -> Carlyle Looks Toward Commercial Aerospace (Reuters) Reuters - Private investment firm Carlyle Group,\\which has a reputation for making well-timed and occasionally\\controversial plays in the defense industry, has quietly placed\\its bets on another part of the market.\n", "**Sci/Tech** -> Oil and Economy Cloud Stocks' Outlook (Reuters) Reuters - Soaring crude prices plus worries\\about the economy and the outlook for earnings are expected to\\hang over the stock market next week during the depth of the\\summer doldrums.\n", "**Sci/Tech** -> Iraq Halts Oil Exports from Main Southern Pipeline (Reuters) Reuters - Authorities have halted oil export\\flows from the main pipeline in southern Iraq after\\intelligence showed a rebel militia could strike\\infrastructure, an oil official said on Saturday.\n", "**Sci/Tech** -> Oil prices soar to all-time record, posing new menace to US economy (AFP) AFP - Tearaway world oil prices, toppling records and straining wallets, present a new economic menace barely three months before the US presidential elections.\n" ] } ], "source": [ "for i,x in zip(range(5),train_dataset):\n", " print(f\"**{classes[x[0]]}** -> {x[1]}\")\n" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "เนื่องจากชุดข้อมูลเป็นตัววนซ้ำ หากเราต้องการใช้ข้อมูลหลายครั้ง เราจำเป็นต้องแปลงมันเป็นรายการ:\n" ] }, { "cell_type": "code", "execution_count": 3, "metadata": {}, "outputs": [], "source": [ "train_dataset, test_dataset = torchtext.datasets.AG_NEWS(root='./data')\n", "train_dataset = list(train_dataset)\n", "test_dataset = list(test_dataset)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## การแบ่งข้อความออกเป็นหน่วยย่อย\n", "\n", "ตอนนี้เราต้องแปลงข้อความให้เป็น **ตัวเลข** ที่สามารถแสดงผลในรูปแบบเทนเซอร์ได้ หากเราต้องการการแสดงผลในระดับคำ เราต้องทำสองสิ่งนี้:\n", "* ใช้ **tokenizer** เพื่อแบ่งข้อความออกเป็น **โทเค็น**\n", "* สร้าง **vocabulary** ของโทเค็นเหล่านั้น\n" ] }, { "cell_type": "code", "execution_count": 4, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "['he', 'said', 'hello']" ] }, "execution_count": 4, "metadata": {}, "output_type": "execute_result" } ], "source": [ "tokenizer = torchtext.data.utils.get_tokenizer('basic_english')\n", "tokenizer('He said: hello')" ] }, { "cell_type": "code", "execution_count": 5, "metadata": {}, "outputs": [], "source": [ "counter = collections.Counter()\n", "for (label, line) in train_dataset:\n", " counter.update(tokenizer(line))\n", "vocab = torchtext.vocab.vocab(counter, min_freq=1)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "โดยใช้คำศัพท์ เราสามารถเข้ารหัสสตริงที่ผ่านการแยกเป็นโทเค็นของเราให้เป็นชุดของตัวเลขได้อย่างง่ายดาย:\n" ] }, { "cell_type": "code", "execution_count": 19, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "Vocab size if 95810\n" ] }, { "data": { "text/plain": [ "[599, 3279, 97, 1220, 329, 225, 7368]" ] }, "execution_count": 19, "metadata": {}, "output_type": "execute_result" } ], "source": [ "vocab_size = len(vocab)\n", "print(f\"Vocab size if {vocab_size}\")\n", "\n", "stoi = vocab.get_stoi() # dict to convert tokens to indices\n", "\n", "def encode(x):\n", " return [stoi[s] for s in tokenizer(x)]\n", "\n", "encode('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": 7, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "array([[1, 1, 0, 2, 0, 0, 0, 0, 0]], dtype=int64)" ] }, "execution_count": 7, "metadata": {}, "output_type": "execute_result" } ], "source": [ "from sklearn.feature_extraction.text import CountVectorizer\n", "vectorizer = CountVectorizer()\n", "corpus = [\n", " 'I like hot dogs.',\n", " 'The dog ran fast.',\n", " 'Its hot outside.',\n", " ]\n", "vectorizer.fit_transform(corpus)\n", "vectorizer.transform(['My dog likes hot dogs on a hot day.']).toarray()" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "ในการคำนวณเวกเตอร์แบบถุงคำจากการแสดงเวกเตอร์ของชุดข้อมูล AG_NEWS ของเรา เราสามารถใช้ฟังก์ชันต่อไปนี้:\n" ] }, { "cell_type": "code", "execution_count": 20, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "tensor([2., 1., 2., ..., 0., 0., 0.])\n" ] } ], "source": [ "vocab_size = len(vocab)\n", "\n", "def to_bow(text,bow_vocab_size=vocab_size):\n", " res = torch.zeros(bow_vocab_size,dtype=torch.float32)\n", " for i in encode(text):\n", " if iepoch_size:\n", " break\n", " return total_loss.item()/count, acc.item()/count" ] }, { "cell_type": "code", "execution_count": 25, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "3200: acc=0.8028125\n", "6400: acc=0.8371875\n", "9600: acc=0.8534375\n", "12800: acc=0.85765625\n" ] }, { "data": { "text/plain": [ "(0.026090790722161722, 0.8620069296375267)" ] }, "execution_count": 25, "metadata": {}, "output_type": "execute_result" } ], "source": [ "train_epoch(net,train_loader,epoch_size=15000)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## BiGrams, TriGrams และ N-Grams\n", "\n", "ข้อจำกัดอย่างหนึ่งของวิธีการแบบ bag of words คือบางคำเป็นส่วนหนึ่งของวลีที่มีหลายคำ ตัวอย่างเช่น คำว่า 'hot dog' มีความหมายที่แตกต่างอย่างสิ้นเชิงจากคำว่า 'hot' และ 'dog' ในบริบทอื่นๆ หากเราแทนคำว่า 'hot' และ 'dog' ด้วยเวกเตอร์เดียวกันเสมอ อาจทำให้โมเดลของเราสับสนได้\n", "\n", "เพื่อแก้ปัญหานี้ **การแทนด้วย N-gram** มักถูกนำมาใช้ในวิธีการจัดประเภทเอกสาร โดยที่ความถี่ของแต่ละคำ, คู่คำ หรือสามคำ เป็นคุณลักษณะที่มีประโยชน์สำหรับการฝึกตัวจำแนก ในการแทนแบบ bigram ตัวอย่างเช่น เราจะเพิ่มคู่คำทั้งหมดลงในคำศัพท์ นอกเหนือจากคำเดิมที่มีอยู่แล้ว\n", "\n", "ด้านล่างนี้คือตัวอย่างวิธีการสร้างการแทนแบบ bigram bag of words โดยใช้ Scikit Learn:\n" ] }, { "cell_type": "code", "execution_count": 26, "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": 26, "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** ของเรา เราจำเป็นต้องสร้างคำศัพท์ ngram เฉพาะ:\n" ] }, { "cell_type": "code", "execution_count": 27, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "Bigram vocabulary length = 1308842\n" ] } ], "source": [ "counter = collections.Counter()\n", "for (label, line) in train_dataset:\n", " l = tokenizer(line)\n", " counter.update(torchtext.data.utils.ngrams_iterator(l,ngrams=2))\n", " \n", "bi_vocab = torchtext.vocab.vocab(counter, min_freq=1)\n", "\n", "print(\"Bigram vocabulary length = \",len(bi_vocab))" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "เราสามารถใช้โค้ดเดียวกันกับด้านบนเพื่อฝึกตัวจำแนกประเภทได้ อย่างไรก็ตาม วิธีนี้จะใช้หน่วยความจำอย่างไม่มีประสิทธิภาพมาก ในหน่วยถัดไป เราจะฝึกตัวจำแนกประเภท bigram โดยใช้ embeddings\n", "\n", "> **หมายเหตุ:** คุณสามารถเก็บเฉพาะ ngrams ที่ปรากฏในข้อความมากกว่าจำนวนครั้งที่กำหนดไว้เท่านั้น วิธีนี้จะช่วยให้ bigrams ที่พบไม่บ่อยถูกละเว้น และลดมิติข้อมูลลงอย่างมาก ในการทำเช่นนี้ ให้ตั้งค่าพารามิเตอร์ `min_freq` เป็นค่าที่สูงขึ้น และสังเกตการเปลี่ยนแปลงของความยาวคำศัพท์\n" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## ความถี่คำและความถี่คำผกผันเอกสาร (TF-IDF)\n", "\n", "ในรูปแบบการแทนคำแบบ BoW (Bag of Words) การปรากฏของคำจะถูกให้น้ำหนักเท่ากัน โดยไม่คำนึงถึงตัวคำเอง อย่างไรก็ตาม เป็นที่ชัดเจนว่าคำที่ปรากฏบ่อย เช่น *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": 28, "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": 28, "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": [ "## บทสรุป\n", "\n", "แม้ว่า TF-IDF จะช่วยให้คำต่าง ๆ มีน้ำหนักตามความถี่ที่ปรากฏ แต่ก็ยังไม่สามารถแสดงถึงความหมายหรือลำดับของคำได้ ดังที่นักภาษาศาสตร์ชื่อดัง J. R. Firth กล่าวไว้ในปี 1935 ว่า “ความหมายที่สมบูรณ์ของคำจะขึ้นอยู่กับบริบทเสมอ และการศึกษาความหมายที่แยกออกจากบริบทนั้นไม่สามารถถือว่าเป็นเรื่องจริงจังได้” ในบทเรียนต่อไป เราจะได้เรียนรู้วิธีการดึงข้อมูลเชิงบริบทจากข้อความโดยใช้การสร้างแบบจำลองภาษา\n" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "\n---\n\n**ข้อจำกัดความรับผิดชอบ**: \nเอกสารนี้ได้รับการแปลโดยใช้บริการแปลภาษาอัตโนมัติ [Co-op Translator](https://github.com/Azure/co-op-translator) แม้ว่าเราจะพยายามให้การแปลมีความถูกต้อง แต่โปรดทราบว่าการแปลอัตโนมัติอาจมีข้อผิดพลาดหรือความไม่แม่นยำ เอกสารต้นฉบับในภาษาต้นทางควรถือเป็นแหล่งข้อมูลที่เชื่อถือได้ สำหรับข้อมูลที่สำคัญ ขอแนะนำให้ใช้บริการแปลภาษาจากผู้เชี่ยวชาญ เราไม่รับผิดชอบต่อความเข้าใจผิดหรือการตีความที่ผิดพลาดซึ่งเกิดจากการใช้การแปลนี้\n" ] } ], "metadata": { "interpreter": { "hash": "16af2a8bbb083ea23e5e41c7f5787656b2ce26968575d8763f2c4b17f9cd711f" }, "kernelspec": { "display_name": "Python 3.8.12 ('py38')", "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" }, "coopTranslator": { "original_hash": "7b9040985e748e4e2d4c689892456ad7", "translation_date": "2025-08-29T11:01:27+00:00", "source_file": "lessons/5-NLP/13-TextRep/TextRepresentationPyTorch.ipynb", "language_code": "th" } }, "nbformat": 4, "nbformat_minor": 2 }