AI-For-Beginners/translations/vi/lessons/5-NLP/13-TextRep/TextRepresentationPyTorch.i...

577 lines
23 KiB
Plaintext
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

{
"cells": [
{
"cell_type": "markdown",
"metadata": {},
"source": [
"# Nhiệm vụ phân loại văn bản\n",
"\n",
"Như đã đề cập, chúng ta sẽ tập trung vào 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**, đó là 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ập dữ liệu này được tích hợp trong mô-đun [`torchtext`](https://github.com/pytorch/text), vì vậy chúng ta có thể dễ dàng truy cập nó.\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": [
"Ở đây, `train_dataset` và `test_dataset` chứa các tập hợp trả về các cặp gồm nhãn (số của lớp) và văn bản tương ứng, ví dụ:\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": [
"Vậy, 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": 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": [
"Bởi vì các tập dữ liệu là các trình lặp, nếu chúng ta muốn sử dụng dữ liệu nhiều lần, chúng ta cần chuyển nó thành danh sách:\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": [
"## Phân tách từ\n",
"\n",
"Bây giờ chúng ta cần chuyển đổi văn bản thành **số** có thể được 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 việc:\n",
"* sử dụng **tokenizer** để chia văn bản thành **token**\n",
"* xây dựng một **từ vựng** cho các token đó.\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": [
"Sử dụng từ vựng, chúng ta có thể dễ dàng mã hóa chuỗi đã được phân tách thành một tập hợp các số:\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": [
"## Biểu diễn văn bản bằng 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 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*, *tuyết* có khả năng chỉ ra *dự báo thời tiết*, trong khi các từ như *cổ phiếu*, *đô la* sẽ liên quan đến *tin tức tài chính*.\n",
"\n",
"**Bag of Words** (BoW) là cách biểu diễn vector truyền thống được sử dụng phổ biến nhất. Mỗi từ được liên kết với một chỉ số vector, phần tử trong vector chứa số lần xuất hiện của từ đó trong một tài liệu cụ thể.\n",
"\n",
"![Hình ảnh minh họa cách biểu diễn vector Bag of Words trong bộ nhớ.](../../../../../translated_images/vi/bag-of-words-example.606fc1738f1d7ba9.webp) \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ừ riêng lẻ 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": 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": [
"Để tính toán vector bag-of-words từ biểu diễn vector của tập dữ liệu AG_NEWS, chúng ta có thể sử dụng hàm sau:\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 i<bow_vocab_size:\n",
" res[i] += 1\n",
" return res\n",
"\n",
"print(to_bow(train_dataset[0][1]))"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"> **Lưu ý:** Ở đây chúng ta sử dụng biến toàn cục `vocab_size` để chỉ định kích thước mặc định của từ vựng. Vì kích thước từ vựng thường khá lớn, chúng ta có thể giới hạn kích thước từ vựng bằng cách chỉ sử dụng các từ phổ biến nhất. Hãy thử giảm giá trị của `vocab_size` và chạy đoạn mã dưới đây, và xem nó ảnh hưởng như thế nào đến độ chính xác. Bạn nên mong đợi một số giảm độ chính xác, nhưng không đáng kể, để đổi lấy hiệu suất cao hơn.\n"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"## Huấn luyện bộ phân loại BoW\n",
"\n",
"Bây giờ khi chúng ta đã học cách xây dựng biểu diễn Bag-of-Words cho văn bản, hãy huấn luyện một bộ phân loại dựa trên đó. Đầu tiên, chúng ta cần chuyển đổi tập dữ liệu để huấn luyện sao cho tất cả các biểu diễn vector vị trí được chuyển đổi thành biểu diễn bag-of-words. Điều này có thể thực hiện bằng cách truyền hàm `bowify` như tham số `collate_fn` vào `DataLoader` tiêu chuẩn của torch:\n"
]
},
{
"cell_type": "code",
"execution_count": 21,
"metadata": {},
"outputs": [],
"source": [
"from torch.utils.data import DataLoader\n",
"import numpy as np \n",
"\n",
"# this collate function gets list of batch_size tuples, and needs to \n",
"# return a pair of label-feature tensors for the whole minibatch\n",
"def bowify(b):\n",
" return (\n",
" torch.LongTensor([t[0]-1 for t in b]),\n",
" torch.stack([to_bow(t[1]) for t in b])\n",
" )\n",
"\n",
"train_loader = DataLoader(train_dataset, batch_size=16, collate_fn=bowify, shuffle=True)\n",
"test_loader = DataLoader(test_dataset, batch_size=16, collate_fn=bowify, shuffle=True)"
]
},
{
"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 của vector đầu vào bằng `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 bài toán phân loại, hàm kích hoạt cuối cùng là `LogSoftmax()`.\n"
]
},
{
"cell_type": "code",
"execution_count": 22,
"metadata": {},
"outputs": [],
"source": [
"net = torch.nn.Sequential(torch.nn.Linear(vocab_size,4),torch.nn.LogSoftmax(dim=1))"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"Bây giờ chúng ta sẽ định nghĩa vòng lặp huấn luyện tiêu chuẩn của PyTorch. Vì tập dữ liệu của chúng ta khá lớn, cho mục đích giảng dạy, chúng ta sẽ chỉ huấn luyện trong một epoch, và đôi khi thậm chí ít hơn một epoch (việc chỉ định tham số `epoch_size` cho phép chúng ta giới hạn huấn luyện). Chúng ta cũng sẽ báo cáo độ chính xác huấn luyện tích lũy trong quá trình huấn luyện; tần suất báo cáo được chỉ định bằng tham số `report_freq`.\n"
]
},
{
"cell_type": "code",
"execution_count": 24,
"metadata": {},
"outputs": [],
"source": [
"def train_epoch(net,dataloader,lr=0.01,optimizer=None,loss_fn = torch.nn.NLLLoss(),epoch_size=None, report_freq=200):\n",
" optimizer = optimizer or torch.optim.Adam(net.parameters(),lr=lr)\n",
" net.train()\n",
" total_loss,acc,count,i = 0,0,0,0\n",
" for labels,features in dataloader:\n",
" optimizer.zero_grad()\n",
" out = net(features)\n",
" loss = loss_fn(out,labels) #cross_entropy(out,labels)\n",
" loss.backward()\n",
" optimizer.step()\n",
" total_loss+=loss\n",
" _,predicted = torch.max(out,1)\n",
" acc+=(predicted==labels).sum()\n",
" count+=len(labels)\n",
" i+=1\n",
" if i%report_freq==0:\n",
" print(f\"{count}: acc={acc.item()/count}\")\n",
" if epoch_size and count>epoch_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 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": 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": [
"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 số 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 xây dựng từ vựng ngram đặc biệt:\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": [
"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, cách này sẽ rất tốn bộ nhớ. Trong phần 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.\n",
"\n",
"> **Lưu ý:** Bạn chỉ nên giữ lại những ngram xuất hiện trong văn bản nhiều hơn số lần được chỉ định. Điều này sẽ đảm bảo rằng các bigram hiếm gặp sẽ bị loại bỏ, và giảm đáng kể kích thước không gian chiều. Để làm điều này, hãy đặt tham số `min_freq` ở giá trị cao hơn, và quan sát sự thay đổi độ dài của từ vựng.\n"
]
},
{
"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 đánh giá ngang nhau, bất kể bản thân từ đó là gì. Tuy nhiên, rõ ràng rằng các từ xuất hiện thường xuyên, như *a*, *in*, v.v., ít quan trọng hơn đối với việc phân loại so với các thuật ngữ chuyên biệt. Thực tế, trong hầu hết các nhiệm vụ NLP, một số từ có mức độ liên quan cao hơn các 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 mô hình 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 xá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 hợp chứa từ đó, giúp cân bằng thực tế rằng một số từ xuất hiện thường xuyên hơn các 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$, những thuật ngữ đó sẽ hoàn toàn bị bỏ qua.\n",
"\n",
"Bạn có thể dễ dàng tạo vector hóa TF-IDF cho văn bản bằng cách sử dụng 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": [
"## Kết luận\n",
"\n",
"Tuy nhiê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 có ngữ cảnh một cách nghiêm túc.”. Sau này trong khóa họ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ữ.\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, nên sử dụng dịch vụ dịch thuật chuyên nghiệp từ 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": "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-29T16:36:40+00:00",
"source_file": "lessons/5-NLP/13-TextRep/TextRepresentationPyTorch.ipynb",
"language_code": "vi"
}
},
"nbformat": 4,
"nbformat_minor": 2
}