647 lines
29 KiB
Plaintext
647 lines
29 KiB
Plaintext
{
|
|
"cells": [
|
|
{
|
|
"cell_type": "markdown",
|
|
"metadata": {},
|
|
"source": [
|
|
"# Nhiệm vụ phân loại văn bản\n",
|
|
"\n",
|
|
"Trong mô-đun này, chúng ta sẽ bắt đầu với một nhiệm vụ phân loại văn bản đơn giản dựa trên tập dữ liệu **[AG_NEWS](http://www.di.unipi.it/~gulli/AG_corpus_of_news_articles.html)**: chúng ta sẽ phân loại tiêu đề tin tức vào một trong 4 danh mục: Thế giới, Thể thao, Kinh doanh và Khoa học/Công nghệ.\n",
|
|
"\n",
|
|
"## Tập dữ liệu\n",
|
|
"\n",
|
|
"Để tải tập dữ liệu, chúng ta sẽ sử dụng 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": [
|
|
"Chúng ta hiện có thể truy cập vào phần huấn luyện và kiểm tra của tập dữ liệu bằng cách sử dụng `dataset['train']` và `dataset['test']` tương ứng:\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": [
|
|
"Hãy in ra 10 tiêu đề mới đầu tiên từ tập dữ liệu của chúng ta:\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": [
|
|
"## Biểu diễn văn bản dưới dạng vector\n",
|
|
"\n",
|
|
"Bây giờ chúng ta cần chuyển đổi văn bản thành **số** để có thể biểu diễn dưới dạng tensor. Nếu chúng ta muốn biểu diễn ở cấp độ từ, cần thực hiện hai bước:\n",
|
|
"\n",
|
|
"* Sử dụng một **tokenizer** để tách văn bản thành các **token**.\n",
|
|
"* Xây dựng một **vocabulary** từ các token đó.\n",
|
|
"\n",
|
|
"### Giới hạn kích thước từ vựng\n",
|
|
"\n",
|
|
"Trong ví dụ về tập dữ liệu AG News, kích thước từ vựng khá lớn, hơn 100 nghìn từ. Nói chung, chúng ta không cần những từ hiếm khi xuất hiện trong văn bản — chỉ một vài câu sẽ chứa chúng, và mô hình sẽ không học được gì từ chúng. Do đó, việc giới hạn kích thước từ vựng xuống một số lượng nhỏ hơn là hợp lý bằng cách truyền một tham số vào hàm khởi tạo của vectorizer:\n",
|
|
"\n",
|
|
"Cả hai bước này đều có thể được xử lý bằng lớp **TextVectorization**. Hãy khởi tạo đối tượng vectorizer, sau đó gọi phương thức `adapt` để duyệt qua toàn bộ văn bản và xây dựng từ vựng:\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": [
|
|
"> **Lưu ý** rằng chúng tôi chỉ sử dụng một phần nhỏ của toàn bộ tập dữ liệu để xây dựng từ vựng. Chúng tôi làm điều này để tăng tốc thời gian thực thi và không để bạn phải chờ đợi. Tuy nhiên, chúng tôi chấp nhận rủi ro rằng một số từ trong toàn bộ tập dữ liệu có thể không được đưa vào từ vựng và sẽ bị bỏ qua trong quá trình huấn luyện. Do đó, việc sử dụng toàn bộ kích thước từ vựng và chạy qua toàn bộ tập dữ liệu trong quá trình `adapt` có thể tăng độ chính xác cuối cùng, nhưng không đáng kể.\n",
|
|
"\n",
|
|
"Bây giờ chúng ta có thể truy cập vào từ vựng thực tế:\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": [
|
|
"Sử dụng vectorizer, chúng ta có thể dễ dàng mã hóa bất kỳ văn bản nào thành một tập hợp các con số:\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": [
|
|
"## Biểu diễn văn bản bằng phương pháp Bag-of-words\n",
|
|
"\n",
|
|
"Vì từ ngữ mang ý nghĩa, đôi khi chúng ta có thể hiểu được nội dung của một đoạn văn bản chỉ bằng cách nhìn vào các từ riêng lẻ, bất kể thứ tự của chúng trong câu. Ví dụ, khi phân loại tin tức, các từ như *thời tiết* và *tuyết* có khả năng chỉ ra *dự báo thời tiết*, trong khi các từ như *cổ phiếu* và *đô la* sẽ liên quan đến *tin tức tài chính*.\n",
|
|
"\n",
|
|
"Biểu diễn vector **Bag-of-words** (BoW) là cách biểu diễn vector truyền thống đơn giản nhất để hiểu. Mỗi từ được liên kết với một chỉ số vector, và một phần tử trong vector chứa số lần xuất hiện của mỗi từ trong một tài liệu cụ thể.\n",
|
|
"\n",
|
|
" \n",
|
|
"\n",
|
|
"> **Note**: Bạn cũng có thể nghĩ về BoW như tổng của tất cả các vector mã hóa một-hot cho từng từ trong văn bản.\n",
|
|
"\n",
|
|
"Dưới đây là một ví dụ về cách tạo biểu diễn bag-of-words bằng thư viện Scikit Learn trong 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": [
|
|
"Chúng ta cũng có thể sử dụng vectorizer Keras mà chúng ta đã định nghĩa ở trên, chuyển đổi mỗi số từ thành một mã hóa one-hot và cộng tất cả các vector đó lại.\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": [
|
|
"> **Lưu ý**: Bạn có thể ngạc nhiên khi thấy kết quả khác với ví dụ trước. Lý do là trong ví dụ Keras, độ dài của vector tương ứng với kích thước từ vựng, được xây dựng từ toàn bộ tập dữ liệu AG News, trong khi ở ví dụ Scikit Learn, chúng tôi xây dựng từ vựng từ văn bản mẫu ngay tại chỗ.\n"
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "markdown",
|
|
"metadata": {},
|
|
"source": [
|
|
"## Huấn luyện bộ phân loại BoW\n",
|
|
"\n",
|
|
"Bây giờ, sau khi đã học cách xây dựng biểu diễn bag-of-words cho văn bản của mình, hãy huấn luyện một bộ phân loại sử dụng nó. Đầu tiên, chúng ta cần chuyển đổi tập dữ liệu của mình sang biểu diễn bag-of-words. Điều này có thể thực hiện bằng cách sử dụng hàm `map` theo cách sau:\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": [
|
|
"Bây giờ hãy định nghĩa một mạng nơ-ron phân loại đơn giản chứa một lớp tuyến tính. Kích thước đầu vào là `vocab_size`, và kích thước đầu ra tương ứng với số lượng lớp (4). Vì chúng ta đang giải quyết một nhiệm vụ phân loại, hàm kích hoạt cuối cùng là **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": [
|
|
"Vì chúng ta có 4 lớp, một độ chính xác trên 80% là một kết quả tốt.\n",
|
|
"\n",
|
|
"## Huấn luyện một bộ phân loại như một mạng lưới\n",
|
|
"\n",
|
|
"Vì vectorizer cũng là một lớp của Keras, chúng ta có thể định nghĩa một mạng lưới bao gồm nó và huấn luyện từ đầu đến cuối. Bằng cách này, chúng ta không cần phải vector hóa tập dữ liệu bằng cách sử dụng `map`, mà chỉ cần truyền tập dữ liệu gốc vào đầu vào của mạng lưới.\n",
|
|
"\n",
|
|
"> **Lưu ý**: Chúng ta vẫn cần áp dụng các phép ánh xạ (maps) cho tập dữ liệu để chuyển đổi các trường từ các từ điển (như `title`, `description` và `label`) thành các bộ giá trị (tuples). Tuy nhiên, khi tải dữ liệu từ đĩa, chúng ta có thể xây dựng một tập dữ liệu với cấu trúc cần thiết ngay từ đầu.\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": [
|
|
"## Bigrams, trigrams và n-grams\n",
|
|
"\n",
|
|
"Một hạn chế của phương pháp bag-of-words là một số từ thuộc về các cụm từ nhiều từ, ví dụ, từ 'hot dog' có ý nghĩa hoàn toàn khác so với các từ 'hot' và 'dog' trong các ngữ cảnh khác. Nếu chúng ta luôn biểu diễn các từ 'hot' và 'dog' bằng cùng một vector, điều này có thể gây nhầm lẫn cho mô hình của chúng ta.\n",
|
|
"\n",
|
|
"Để giải quyết vấn đề này, **biểu diễn n-gram** thường được sử dụng trong các phương pháp phân loại tài liệu, nơi tần suất của mỗi từ, cụm hai từ hoặc cụm ba từ là một đặc điểm hữu ích để huấn luyện các bộ phân loại. Trong biểu diễn bigram, ví dụ, chúng ta sẽ thêm tất cả các cặp từ vào từ vựng, bên cạnh các từ gốc.\n",
|
|
"\n",
|
|
"Dưới đây là một ví dụ về cách tạo biểu diễn bag-of-words bigram bằng 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": [
|
|
"Nhược điểm chính của phương pháp n-gram là kích thước từ vựng bắt đầu tăng rất nhanh. Trong thực tế, chúng ta cần kết hợp biểu diễn n-gram với một kỹ thuật giảm chiều, chẳng hạn như *embeddings*, mà chúng ta sẽ thảo luận trong bài học tiếp theo.\n",
|
|
"\n",
|
|
"Để sử dụng biểu diễn n-gram trong tập dữ liệu **AG News**, chúng ta cần truyền tham số `ngrams` vào hàm khởi tạo `TextVectorization`. Độ dài của từ vựng bigram là **lớn hơn đáng kể**, trong trường hợp của chúng ta, nó vượt quá 1,3 triệu token! Vì vậy, việc giới hạn số lượng token bigram ở một mức hợp lý là điều hợp lý.\n",
|
|
"\n",
|
|
"Chúng ta có thể sử dụng cùng đoạn mã như trên để huấn luyện bộ phân loại, tuy nhiên, điều này sẽ rất không hiệu quả về mặt bộ nhớ. Trong bài học tiếp theo, chúng ta sẽ huấn luyện bộ phân loại bigram bằng cách sử dụng embeddings. Trong thời gian chờ đợi, bạn có thể thử nghiệm huấn luyện bộ phân loại bigram trong notebook này và xem liệu bạn có thể đạt được độ chính xác cao hơn không.\n"
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "markdown",
|
|
"metadata": {},
|
|
"source": [
|
|
"## Tự động tính toán các vector BoW\n",
|
|
"\n",
|
|
"Trong ví dụ trên, chúng ta đã tính toán các vector BoW bằng tay bằng cách cộng các mã hóa one-hot của từng từ riêng lẻ. Tuy nhiên, phiên bản mới nhất của TensorFlow cho phép chúng ta tính toán các vector BoW một cách tự động bằng cách truyền tham số `output_mode='count` vào trình khởi tạo vectorizer. Điều này làm cho việc định nghĩa và huấn luyện mô hình của chúng ta trở nên dễ dàng hơn đáng kể:\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": [
|
|
"## Tần suất thuật ngữ - tần suất nghịch tài liệu (TF-IDF)\n",
|
|
"\n",
|
|
"Trong biểu diễn BoW, số lần xuất hiện của từ được tính trọng số bằng cùng một kỹ thuật bất kể từ đó là gì. Tuy nhiên, rõ ràng rằng các từ phổ biến như *a* và *in* ít quan trọng hơn nhiều đối với việc phân loại so với các thuật ngữ chuyên ngành. Trong hầu hết các nhiệm vụ NLP, một số từ có mức độ liên quan cao hơn những từ khác.\n",
|
|
"\n",
|
|
"**TF-IDF** là viết tắt của **tần suất thuật ngữ - tần suất nghịch tài liệu**. Đây là một biến thể của bag-of-words, trong đó thay vì giá trị nhị phân 0/1 biểu thị sự xuất hiện của một từ trong tài liệu, một giá trị số thực được sử dụng, liên quan đến tần suất xuất hiện của từ trong tập dữ liệu.\n",
|
|
"\n",
|
|
"Một cách chính thức hơn, trọng số $w_{ij}$ của một từ $i$ trong tài liệu $j$ được định nghĩa như sau:\n",
|
|
"$$\n",
|
|
"w_{ij} = tf_{ij}\\times\\log({N\\over df_i})\n",
|
|
"$$\n",
|
|
"trong đó:\n",
|
|
"* $tf_{ij}$ là số lần xuất hiện của $i$ trong $j$, tức là giá trị BoW mà chúng ta đã thấy trước đó\n",
|
|
"* $N$ là số lượng tài liệu trong tập hợp\n",
|
|
"* $df_i$ là số lượng tài liệu chứa từ $i$ trong toàn bộ tập hợp\n",
|
|
"\n",
|
|
"Giá trị TF-IDF $w_{ij}$ tăng tỷ lệ thuận với số lần một từ xuất hiện trong tài liệu và được điều chỉnh bởi số lượng tài liệu trong tập dữ liệu chứa từ đó, giúp điều chỉnh thực tế rằng một số từ xuất hiện thường xuyên hơn những từ khác. Ví dụ, nếu từ xuất hiện trong *mọi* tài liệu trong tập hợp, $df_i=N$, và $w_{ij}=0$, và những thuật ngữ đó sẽ bị loại bỏ hoàn toàn.\n",
|
|
"\n",
|
|
"Bạn có thể dễ dàng tạo vector hóa TF-IDF của văn bản bằng cách sử dụng 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": [
|
|
"Trong Keras, lớp `TextVectorization` có thể tự động tính toán tần suất TF-IDF bằng cách truyền tham số `output_mode='tf-idf'`. Hãy lặp lại đoạn mã chúng ta đã sử dụng ở trên để xem liệu việc sử dụng TF-IDF có tăng độ chính xác hay không:\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": [
|
|
"## Kết luận\n",
|
|
"\n",
|
|
"Mặc dù các biểu diễn TF-IDF cung cấp trọng số tần suất cho các từ khác nhau, chúng không thể biểu diễn ý nghĩa hoặc thứ tự. Như nhà ngôn ngữ học nổi tiếng J. R. Firth đã nói vào năm 1935: \"Ý nghĩa đầy đủ của một từ luôn mang tính ngữ cảnh, và không thể nghiên cứu ý nghĩa mà không xét đến ngữ cảnh một cách nghiêm túc.\" Chúng ta sẽ học cách nắm bắt thông tin ngữ cảnh từ văn bản bằng cách sử dụng mô hình ngôn ngữ trong các phần sau của khóa học.\n"
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "markdown",
|
|
"metadata": {},
|
|
"source": [
|
|
"\n---\n\n**Tuyên bố miễn trừ trách nhiệm**: \nTài liệu này đã được dịch bằng dịch vụ dịch thuật AI [Co-op Translator](https://github.com/Azure/co-op-translator). Mặc dù chúng tôi cố gắng đảm bảo độ chính xác, xin lưu ý rằng các bản dịch tự động có thể chứa lỗi hoặc không chính xác. Tài liệu gốc bằng ngôn ngữ bản địa nên được coi là nguồn thông tin chính thức. Đối với các thông tin quan trọng, khuyến nghị sử dụng dịch vụ dịch thuật chuyên nghiệp bởi con người. Chúng tôi không chịu trách nhiệm cho bất kỳ sự hiểu lầm hoặc diễn giải sai nào phát sinh từ việc sử dụng bản dịch này.\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-29T16:42:02+00:00",
|
|
"source_file": "lessons/5-NLP/13-TextRep/TextRepresentationTF.ipynb",
|
|
"language_code": "vi"
|
|
}
|
|
},
|
|
"nbformat": 4,
|
|
"nbformat_minor": 4
|
|
} |