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

577 lines
22 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": [
"# Tarefa de classificação de texto\n",
"\n",
"Como mencionamos, vamos focar em uma tarefa simples de classificação de texto baseada no dataset **AG_NEWS**, que consiste em classificar manchetes de notícias em uma das 4 categorias: Mundo, Esportes, Negócios e Ciência/Tecnologia.\n",
"\n",
"## O Dataset\n",
"\n",
"Este dataset está integrado no módulo [`torchtext`](https://github.com/pytorch/text), então podemos acessá-lo facilmente.\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": [
"Aqui, `train_dataset` e `test_dataset` contêm coleções que retornam pares de rótulo (número da classe) e texto, respectivamente, por exemplo:\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": [
"Então, vamos imprimir as primeiras 10 novas manchetes do nosso conjunto de dados:\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": [
"Porque conjuntos de dados são iteradores, se quisermos usar os dados várias vezes, precisamos convertê-los para lista:\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": [
"## Tokenização\n",
"\n",
"Agora precisamos converter o texto em **números** que podem ser representados como tensores. Se quisermos uma representação no nível de palavras, precisamos fazer duas coisas:\n",
"* usar um **tokenizador** para dividir o texto em **tokens**\n",
"* construir um **vocabulário** desses tokens.\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": [
"Usando vocabulário, podemos facilmente codificar nossa string tokenizada em um conjunto de números:\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": [
"## Representação de texto com Bag of Words\n",
"\n",
"Como as palavras representam significado, às vezes podemos entender o significado de um texto apenas observando as palavras individuais, independentemente da ordem em que aparecem na frase. Por exemplo, ao classificar notícias, palavras como *clima*, *neve* provavelmente indicam *previsão do tempo*, enquanto palavras como *ações*, *dólar* seriam associadas a *notícias financeiras*.\n",
"\n",
"A representação vetorial **Bag of Words** (BoW) é a representação vetorial tradicional mais comumente usada. Cada palavra está vinculada a um índice do vetor, e o elemento do vetor contém o número de ocorrências de uma palavra em um determinado documento.\n",
"\n",
"![Imagem mostrando como uma representação vetorial Bag of Words é representada na memória.](../../../../../translated_images/bag-of-words-example.606fc1738f1d7ba98a9d693e3bcd706c6e83fa7bf8221e6e90d1a206d82f2ea4.br.png) \n",
"\n",
"> **Note**: Você também pode pensar no BoW como a soma de todos os vetores one-hot-encoded para palavras individuais no texto.\n",
"\n",
"Abaixo está um exemplo de como gerar uma representação Bag of Words usando a biblioteca python Scikit Learn:\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": [
"Para calcular o vetor bag-of-words a partir da representação vetorial do nosso conjunto de dados AG_NEWS, podemos usar a seguinte função:\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": [
"> **Nota:** Aqui estamos usando a variável global `vocab_size` para especificar o tamanho padrão do vocabulário. Como frequentemente o tamanho do vocabulário é bastante grande, podemos limitar o tamanho do vocabulário às palavras mais frequentes. Tente reduzir o valor de `vocab_size` e executar o código abaixo, e veja como isso afeta a precisão. Você deve esperar alguma queda na precisão, mas nada dramático, em troca de um desempenho maior.\n"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"## Treinando o classificador BoW\n",
"\n",
"Agora que aprendemos como construir a representação Bag-of-Words do nosso texto, vamos treinar um classificador com base nela. Primeiro, precisamos converter nosso conjunto de dados para treinamento de forma que todas as representações vetoriais posicionais sejam transformadas em representações Bag-of-Words. Isso pode ser feito passando a função `bowify` como o parâmetro `collate_fn` para o `DataLoader` padrão do 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": [
"Agora vamos definir uma rede neural classificadora simples que contém uma camada linear. O tamanho do vetor de entrada é igual a `vocab_size`, e o tamanho da saída corresponde ao número de classes (4). Como estamos resolvendo uma tarefa de classificação, a função de ativação final é `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": [
"Agora vamos definir o loop de treinamento padrão do PyTorch. Como nosso conjunto de dados é bastante grande, para fins de ensino treinaremos apenas por uma época, e às vezes até por menos de uma época (especificar o parâmetro `epoch_size` nos permite limitar o treinamento). Também iremos relatar a precisão acumulada do treinamento durante o processo; a frequência de relatórios é especificada usando o parâmetro `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 e N-Grams\n",
"\n",
"Uma limitação da abordagem de bag of words é que algumas palavras fazem parte de expressões compostas, por exemplo, a palavra 'hot dog' tem um significado completamente diferente das palavras 'hot' e 'dog' em outros contextos. Se representarmos as palavras 'hot' e 'dog' sempre pelos mesmos vetores, isso pode confundir nosso modelo.\n",
"\n",
"Para lidar com isso, as **representações N-gram** são frequentemente usadas em métodos de classificação de documentos, onde a frequência de cada palavra, bi-palavra ou tri-palavra é uma característica útil para treinar classificadores. Na representação bigram, por exemplo, adicionaremos todos os pares de palavras ao vocabulário, além das palavras originais.\n",
"\n",
"Abaixo está um exemplo de como gerar uma representação de bag of words com bigram usando o 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": [
"A principal desvantagem da abordagem N-gram é que o tamanho do vocabulário começa a crescer muito rapidamente. Na prática, precisamos combinar a representação N-gram com algumas técnicas de redução de dimensionalidade, como *embeddings*, que discutiremos na próxima unidade.\n",
"\n",
"Para usar a representação N-gram em nosso conjunto de dados **AG News**, precisamos construir um vocabulário especial de 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": [
"Poderíamos então usar o mesmo código acima para treinar o classificador, no entanto, isso seria muito ineficiente em termos de memória. Na próxima unidade, treinaremos um classificador de bigramas usando embeddings.\n",
"\n",
"> **Nota:** Você pode deixar apenas os ngrams que aparecem no texto mais do que o número especificado de vezes. Isso garantirá que bigramas pouco frequentes sejam omitidos e reduzirá significativamente a dimensionalidade. Para fazer isso, defina o parâmetro `min_freq` para um valor mais alto e observe a mudança no tamanho do vocabulário.\n"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"## Frequência de Termos e Frequência Inversa de Documentos TF-IDF\n",
"\n",
"Na representação BoW, as ocorrências de palavras são ponderadas igualmente, independentemente da palavra em si. No entanto, é evidente que palavras frequentes, como *a*, *em*, etc., são muito menos importantes para a classificação do que termos especializados. De fato, na maioria das tarefas de PLN, algumas palavras são mais relevantes do que outras.\n",
"\n",
"**TF-IDF** significa **frequência de termosfrequência inversa de documentos**. É uma variação do modelo bag of words, onde, em vez de um valor binário 0/1 indicando a presença de uma palavra em um documento, utiliza-se um valor de ponto flutuante, que está relacionado à frequência de ocorrência da palavra no corpus.\n",
"\n",
"Mais formalmente, o peso $w_{ij}$ de uma palavra $i$ no documento $j$ é definido como:\n",
"$$\n",
"w_{ij} = tf_{ij}\\times\\log({N\\over df_i})\n",
"$$\n",
"onde\n",
"* $tf_{ij}$ é o número de ocorrências de $i$ em $j$, ou seja, o valor BoW que vimos anteriormente\n",
"* $N$ é o número de documentos na coleção\n",
"* $df_i$ é o número de documentos que contêm a palavra $i$ em toda a coleção\n",
"\n",
"O valor TF-IDF $w_{ij}$ aumenta proporcionalmente ao número de vezes que uma palavra aparece em um documento e é ajustado pelo número de documentos no corpus que contêm a palavra, o que ajuda a compensar o fato de que algumas palavras aparecem com mais frequência do que outras. Por exemplo, se a palavra aparece em *todos* os documentos da coleção, $df_i=N$, e $w_{ij}=0$, e esses termos seriam completamente desconsiderados.\n",
"\n",
"Você pode criar facilmente a vetorização TF-IDF de texto usando 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": [
"## Conclusão\n",
"\n",
"No entanto, embora as representações TF-IDF atribuam pesos de frequência a diferentes palavras, elas não conseguem representar significado ou ordem. Como disse o famoso linguista J. R. Firth em 1935: “O significado completo de uma palavra é sempre contextual, e nenhum estudo de significado fora do contexto pode ser levado a sério.”. Mais adiante no curso, aprenderemos como capturar informações contextuais de textos utilizando modelagem de linguagem.\n"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"\n---\n\n**Aviso Legal**: \nEste documento foi traduzido utilizando o serviço de tradução por IA [Co-op Translator](https://github.com/Azure/co-op-translator). Embora nos esforcemos para garantir a precisão, esteja ciente de que traduções automatizadas podem conter erros ou imprecisões. O documento original em seu idioma nativo deve ser considerado a fonte autoritativa. Para informações críticas, recomenda-se a tradução profissional realizada por humanos. Não nos responsabilizamos por quaisquer mal-entendidos ou interpretações equivocadas decorrentes do uso desta tradução.\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-28T14:32:39+00:00",
"source_file": "lessons/5-NLP/13-TextRep/TextRepresentationPyTorch.ipynb",
"language_code": "br"
}
},
"nbformat": 4,
"nbformat_minor": 2
}