Embeddings
Em uma postagem anterior sobre tokens, já vimos a representação mínima de cada palavra. O que corresponde a atribuir um número à divisão mínima de cada palavra.
Entretanto, os transformadores e, portanto, os LLMs, não representam as informações das palavras dessa forma, mas o fazem por meio de "embeddings".
Este caderno foi traduzido automaticamente para torná-lo acessível a mais pessoas, por favor me avise se você vir algum erro de digitação..
Primeiramente, examinaremos duas formas de representar palavras, a "codificação ordinal" e a "codificação de um hot". E, analisando os problemas desses dois tipos de representações, poderemos chegar a word embeddings
e sentence embeddings
.
Também veremos um exemplo de como treinar um modelo de word embeddings
com a biblioteca gensim
.
E, por fim, veremos como usar modelos pré-treinados de embeddings
com a biblioteca transformers
do HuggingFace.
Codificação ordinal
Essa é a maneira mais básica de representar as palavras dentro dos transformadores. Ela consiste em atribuir um número a cada palavra ou manter os números já atribuídos aos tokens.
No entanto, esse tipo de representação tem dois problemas
Imaginemos que table corresponda ao token 3, cat ao token 1 e dog ao token 2. Poderíamos supor que
table = cat + dog
, mas esse não é o caso. Não existe essa relação entre essas palavras. Poderíamos até pensar que, atribuindo os tokens corretos, essa relação poderia ocorrer. No entanto, essa ideia não funciona com palavras que têm mais de um significado, como a palavra "banco".O segundo problema é que as redes neurais fazem internamente muitos cálculos numéricos, portanto, pode ser que mesa tenha o token 3 e seja internamente mais importante do que a palavra cat que tem o token 1.
Portanto, esse tipo de representação de palavras pode ser descartado muito rapidamente.
Uma codificação quente
O que você faz aqui é usar vetores de dimensão N
. Por exemplo, vimos que o OpenAI tem um vocabulário de 100277
tokens distintos. Portanto, se usarmos "one hot encoding", cada palavra será representada por um vetor de "100277" dimensões.
No entanto, uma codificação quente tem dois outros problemas importantes.
- Ele não leva em conta a relação entre as palavras. Portanto, se tivermos duas palavras que sejam sinônimas, por exemplo,
cat
efeline
, teremos dois vetores diferentes para representá-las.
No idioma, a relação entre as palavras é muito importante, e não levar essa relação em conta é um grande problema.
- O segundo problema é que os vetores são muito grandes. Se tivermos um vocabulário de
100277
tokens, cada palavra será representada por um vetor de100277
dimensões. Isso torna os vetores muito grandes e os cálculos muito caros. Além disso, esses vetores serão todos zeros, exceto na posição correspondente ao token da palavra. Portanto, a maioria dos cálculos serão multiplicações por zero, que são cálculos que não somam nada. Portanto, teremos muita memória alocada para vetores em que só há um1
em uma determinada posição.
Embeddings de palavras
A incorporação de palavras é uma tentativa de resolver os problemas dos dois tipos anteriores de representação. Para isso, são usados vetores de N
dimensões, mas, nesse caso, não são usados vetores de 100277 dimensões, e sim vetores de dimensões muito menores. Por exemplo, veremos que o OpenAI usa 1536
dimensões.
Cada uma das dimensões desses vetores representa uma característica da palavra. Por exemplo, uma dimensão pode representar se a palavra é um verbo ou um substantivo. Outra dimensão pode representar se a palavra é um animal ou não. Outra dimensão pode representar se a palavra é um substantivo próprio ou não. E assim por diante.
No entanto, esses recursos não são definidos manualmente, mas aprendidos automaticamente. Durante o treinamento dos transformadores, os valores de cada uma das dimensões dos vetores são ajustados, de modo que as características de cada uma das palavras sejam aprendidas.
Ao fazer com que cada uma das dimensões da palavra represente uma característica da palavra, as palavras que têm características semelhantes terão vetores semelhantes. Por exemplo, as palavras cat
e feline
terão vetores muito semelhantes, pois ambas são animais. E as palavras table
e chair
terão vetores semelhantes, pois ambas são móveis.
Na imagem a seguir, podemos ver uma representação tridimensional das palavras e podemos ver que todas as palavras relacionadas a school
estão próximas, todas as palavras relacionadas a food
estão próximas e todas as palavras relacionadas a ball
estão próximas.
Como cada uma das dimensões dos vetores representa uma característica da palavra, podemos realizar operações com palavras. Por exemplo, se subtrairmos a palavra "king" (rei) da palavra "man" (homem) e adicionarmos a palavra "woman" (mulher), obteremos uma palavra muito semelhante à palavra "queen" (rainha). Verificaremos isso mais tarde com um exemplo
Similaridade entre palavras
Como cada palavra é representada por um vetor de N
dimensões, podemos calcular a similaridade entre duas palavras. A função de similaridade de cosseno é usada para essa finalidade.
Se duas palavras estiverem próximas no espaço vetorial, isso significa que o ângulo entre seus vetores é pequeno, portanto, seu cosseno é próximo de 1. Se houver um ângulo de 90 graus entre os vetores, o cosseno será 0, o que significa que não há semelhança entre as palavras. E se houver um ângulo de 180 graus entre os vetores, o cosseno será -1, ou seja, as palavras são opostas.
Exemplo com embeddings da OpenAI
Agora que já sabemos o que são embeddings
, vamos dar uma olhada em alguns exemplos com os embeddings
fornecidos pela API
OpenAI
.
Para fazer isso, primeiro precisamos ter o pacote OpenAI
instalado.
pip install openai
```
Importamos as bibliotecas necessárias
from openai import OpenAIimport torchfrom torch.nn.functional import cosine_similarity
Usamos uma "chave API" da OpenAI. Para fazer isso, vá para a página [OpenAI] (https://openai.com/) e registre-se. Depois de registrado, vá para a seção API Keys e crie uma nova API Key
.
from openai import OpenAIimport torchfrom torch.nn.functional import cosine_similarityapi_key = "Pon aquí tu API key"
Selecionamos o modelo de embeddings que queremos usar. Nesse caso, usaremos o text-embedding-ada-002
, que é recomendado pela OpenAI
em sua documentação [embeddings] (https://platform.openai.com/docs/guides/embeddings/).
from openai import OpenAIimport torchfrom torch.nn.functional import cosine_similarityapi_key = "Pon aquí tu API key"model_openai = "text-embedding-ada-002"
Criar um cliente `API
from openai import OpenAIimport torchfrom torch.nn.functional import cosine_similarityapi_key = "Pon aquí tu API key"model_openai = "text-embedding-ada-002"client_openai = OpenAI(api_key=api_key, organization=None)
Vamos ver como são os embeddings
da palavra King
.
from openai import OpenAIimport torchfrom torch.nn.functional import cosine_similarityapi_key = "Pon aquí tu API key"model_openai = "text-embedding-ada-002"client_openai = OpenAI(api_key=api_key, organization=None)word = "Rey"embedding_openai = torch.Tensor(client_openai.embeddings.create(input=word, model=model_openai).data[0].embedding)embedding_openai.shape, embedding_openai
(torch.Size([1536]),tensor([-0.0103, -0.0005, -0.0189, ..., -0.0009, -0.0226, 0.0045]))
Como podemos ver, obtemos um vetor de 1536
dimensões.
Operações com palavras
Vamos obter os embeddings das palavras king
, man
, woman
e queen
.
embedding_openai_rey = torch.Tensor(client_openai.embeddings.create(input="rey", model=model_openai).data[0].embedding)embedding_openai_hombre = torch.Tensor(client_openai.embeddings.create(input="hombre", model=model_openai).data[0].embedding)embedding_openai_mujer = torch.Tensor(client_openai.embeddings.create(input="mujer", model=model_openai).data[0].embedding)embedding_openai_reina = torch.Tensor(client_openai.embeddings.create(input="reina", model=model_openai).data[0].embedding)
embedding_openai_rey = torch.Tensor(client_openai.embeddings.create(input="rey", model=model_openai).data[0].embedding)embedding_openai_hombre = torch.Tensor(client_openai.embeddings.create(input="hombre", model=model_openai).data[0].embedding)embedding_openai_mujer = torch.Tensor(client_openai.embeddings.create(input="mujer", model=model_openai).data[0].embedding)embedding_openai_reina = torch.Tensor(client_openai.embeddings.create(input="reina", model=model_openai).data[0].embedding)embedding_openai_reina.shape, embedding_openai_reina
(torch.Size([1536]),tensor([-0.0110, -0.0084, -0.0115, ..., 0.0082, -0.0096, -0.0024]))
Vamos obter a incorporação resultante da subtração da incorporação de "homem" de "rei" e da adição da incorporação de "mulher" a "rei".
embedding_openai = embedding_openai_rey - embedding_openai_hombre + embedding_openai_mujer
embedding_openai = embedding_openai_rey - embedding_openai_hombre + embedding_openai_mujerembedding_openai.shape, embedding_openai
(torch.Size([1536]),tensor([-0.0226, -0.0323, 0.0017, ..., 0.0014, -0.0290, -0.0188]))
Por fim, comparamos o resultado obtido com a incorporação da reina
. Para isso, usamos a função cosine_similarity
fornecida pela biblioteca pytorch
.
similarity_openai = cosine_similarity(embedding_openai.unsqueeze(0), embedding_openai_reina.unsqueeze(0)).item()print(f"similarity_openai: {similarity_openai}")
similarity_openai: 0.7564167976379395
Como podemos ver, é um valor muito próximo de 1, portanto, podemos dizer que o resultado obtido é muito semelhante à incorporação da reina
.
Se usarmos palavras em inglês, obteremos um resultado mais próximo de 1.
embedding_openai_rey = torch.Tensor(client_openai.embeddings.create(input="king", model=model_openai).data[0].embedding)embedding_openai_hombre = torch.Tensor(client_openai.embeddings.create(input="man", model=model_openai).data[0].embedding)embedding_openai_mujer = torch.Tensor(client_openai.embeddings.create(input="woman", model=model_openai).data[0].embedding)embedding_openai_reina = torch.Tensor(client_openai.embeddings.create(input="queen", model=model_openai).data[0].embedding)
embedding_openai_rey = torch.Tensor(client_openai.embeddings.create(input="king", model=model_openai).data[0].embedding)embedding_openai_hombre = torch.Tensor(client_openai.embeddings.create(input="man", model=model_openai).data[0].embedding)embedding_openai_mujer = torch.Tensor(client_openai.embeddings.create(input="woman", model=model_openai).data[0].embedding)embedding_openai_reina = torch.Tensor(client_openai.embeddings.create(input="queen", model=model_openai).data[0].embedding)embedding_openai = embedding_openai_rey - embedding_openai_hombre + embedding_openai_mujer
embedding_openai_rey = torch.Tensor(client_openai.embeddings.create(input="king", model=model_openai).data[0].embedding)embedding_openai_hombre = torch.Tensor(client_openai.embeddings.create(input="man", model=model_openai).data[0].embedding)embedding_openai_mujer = torch.Tensor(client_openai.embeddings.create(input="woman", model=model_openai).data[0].embedding)embedding_openai_reina = torch.Tensor(client_openai.embeddings.create(input="queen", model=model_openai).data[0].embedding)embedding_openai = embedding_openai_rey - embedding_openai_hombre + embedding_openai_mujersimilarity_openai = cosine_similarity(embedding_openai.unsqueeze(0), embedding_openai_reina.unsqueeze(0))print(f"similarity_openai: {similarity_openai}")
similarity_openai: tensor([0.8849])
Isso é normal, pois o modelo OpenAi foi treinado com mais textos em inglês do que em espanhol.
Tipos de incorporação de palavras
Há vários tipos de word embeddings, e cada um deles tem suas vantagens e desvantagens. Vamos dar uma olhada nos mais importantes
- Word2Vec
- GloVe
- FastText
- BERT
- GPT-2
Word2Vec
O Word2Vec é um algoritmo usado para criar embeddings de palavras. Esse algoritmo foi criado pelo Google em 2013 e é um dos algoritmos mais usados para criar word embeddings.
Ele tem duas variantes, CBOW
e Skip-gram
. O CBOW
é mais rápido de treinar, enquanto o Skip-gram
é mais preciso. Vamos dar uma olhada em como cada um funciona
CBOW
CBOW ou Continuous Bag of Words
é um algoritmo usado para prever uma palavra a partir das palavras ao redor. Por exemplo, se tivermos a frase O gato é um animal
, o algoritmo tentará prever a palavra gato
a partir das palavras ao redor, nesse caso O
, é
, um
e animal
.
Nessa arquitetura, o modelo prevê qual é a palavra mais provável em um determinado contexto. Portanto, as palavras que têm a mesma probabilidade de ocorrência são consideradas semelhantes e, portanto, estão mais próximas no espaço dimensional.
Suponha que, em uma frase, substituamos boat
por boat
, então o modelo prevê a probabilidade de ambos e, se for semelhante, podemos considerar que as palavras são semelhantes.
Skip-gram
O Skip-gram
ou Skip-gram with Negative Sampling
é um algoritmo usado para prever as palavras ao redor de uma palavra. Por exemplo, se tivermos a frase O gato é um animal
, o algoritmo tentará prever as palavras O
, é
, um
e animal
a partir da palavra gato
.
Skip-gram](https://pub-fb664c455eca46a2ba762a065ac900f7.r2.dev/Skip-gram.webp)
Essa arquitetura é semelhante à do CBOW, mas o modelo funciona de forma inversa. O modelo prevê o contexto usando a palavra dada. Portanto, as palavras que têm o mesmo contexto são consideradas semelhantes e, portanto, estão mais próximas no espaço dimensional.
GloVe
GloVeou
Global Vectors for Word Representation` é um algoritmo usado para criar embeddings de palavras. Esse algoritmo foi criado pela Universidade de Stanford em 2014.
O Word2Vec ignora o fato de que algumas palavras de contexto ocorrem com mais frequência do que outras e também leva em conta apenas o contexto local e, portanto, não captura o contexto global.
Esse algoritmo usa uma matriz de co-ocorrência para criar os embeddings de palavras. Essa matriz de co-ocorrência é uma matriz que contém o número de vezes que cada palavra aparece ao lado de cada uma das outras palavras do vocabulário.
FastText
O FastText é um algoritmo usado para criar incorporação de palavras. Esse algoritmo foi criado pelo Facebook em 2016.
Uma das principais desvantagens do Word2Vec
e do GloVe
é que eles não podem codificar palavras desconhecidas ou fora do vocabulário.
Portanto, para lidar com esse problema, o Facebook propôs um modelo FastText
. Ele é uma extensão do Word2Vec
e segue o mesmo modelo Skip-gram
e CBOW
, mas, ao contrário do Word2Vec
, que alimenta a rede neural com palavras inteiras, o FastText
primeiro divide as palavras em várias subpalavras (ou n-gramas
) e depois as alimenta na rede neural.
Por exemplo, se o valor de n
for 3 e a palavra for apple
, então seu tri-grama será [<ma
, man
, anz
, nza
, zan
, ana
, na>
] e sua incorporação de palavras será a soma da representação vetorial desses tri-gramas. Aqui, os hiperparâmetros min_n
e max_n
são considerados como 3 e os caracteres <
e >
representam o início e o fim da palavra.
Portanto, usando essa metodologia, as palavras desconhecidas podem ser representadas em forma de vetor, pois há uma alta probabilidade de que seus n-gramas
também estejam presentes em outras palavras.
Esse algoritmo é um aprimoramento do Word2Vec
, pois, além de levar em conta as palavras ao redor de uma palavra, ele também leva em conta os n-gramas
da palavra. Por exemplo, se tivermos a palavra cat
, ele também leva em conta os n-gramas
da palavra, nesse caso ga
, at
e to
, para n = 2
.
Limitações da incorporação de palavras
As técnicas de incorporação de palavras produziram um resultado decente, mas o problema é que a abordagem não é suficientemente precisa. Elas não levam em conta a ordem em que as palavras aparecem, o que leva à perda da compreensão sintática e semântica da frase.
Por exemplo, Você vai lá para ensinar, não para brincar
E Você vai lá para brincar, não para ensinar
Ambas as frases terão a mesma representação no espaço vetorial, mas não significam a mesma coisa.
Além disso, o modelo de incorporação de palavras não pode fornecer resultados satisfatórios em uma grande quantidade de dados de texto, pois a mesma palavra pode ter um significado diferente em uma frase diferente, dependendo do contexto da frase.
Por exemplo, I am going to sit in the bank
E I am going to do business in the bank
Em ambas as frases, a palavra bank
tem significados diferentes.
Portanto, precisamos de um tipo de representação que possa reter o significado contextual da palavra presente em uma frase.
Embeddings de frases
A incorporação de frases é semelhante à incorporação de palavras, mas, em vez de palavras, ela codifica a frase inteira na representação vetorial.
Uma maneira simples de obter a incorporação de frases é calcular a média das incorporações de palavras de todas as palavras presentes na frase. Mas isso não é suficientemente preciso.
Alguns dos modelos mais avançados de incorporação de frases são o ELMo
, o InferSent
e o Sentence-BERT
.
ELMo
ELMoou
Embeddings from Language Modelsé um modelo de incorporação de frases criado pela Allen University em 2018. Ele usa uma rede LSTM profunda bidirecional para produzir representação vetorial. O
ELMo` pode representar palavras desconhecidas ou fora do vocabulário em forma de vetor, pois é baseado em caracteres.
InferSent
O InferSent é um modelo de incorporação de frases criado pelo Facebook em 2017. Ele usa uma rede LSTM profunda bidirecional para produzir representação vetorial. O InferSent
pode representar palavras desconhecidas ou fora do vocabulário em forma de vetor, pois é baseado em caracteres. As frases são codificadas em uma representação vetorial de 4096 dimensões.
O treinamento do modelo é feito com o conjunto de dados Stanford Natural Language Inference (SNLI
). Esse conjunto de dados é rotulado e escrito por humanos para cerca de 500 mil pares de frases.
Sentença-BERT
O Sentence-BERT é um modelo de incorporação de frases criado pela Universidade de Londres em 2019. Ele usa uma rede LSTM profunda bidirecional para produzir representação vetorial. O Sentence-BERT
pode representar palavras desconhecidas ou fora do vocabulário na forma vetorial, pois é baseado em caracteres. As frases são codificadas em uma representação vetorial de 768 dimensões.
O modelo de PNL de última geração BERT
é excelente em tarefas de Semantic Textual Similarity, mas o problema é que ele levaria muito tempo para um corpus enorme (65 horas para 10.000 frases), pois exige que ambas as frases sejam inseridas na rede, o que aumenta o cálculo em um fator enorme.
Portanto, o Sentence-BERT
é uma modificação do modelo BERT
.
Treinamento de um modelo word2vec com gensim
Para fazer o download do conjunto de dados que vamos usar, precisamos instalar a biblioteca dataset
da huggingface:
pip install datasets
Para treinar o modelo de embeddings, usaremos a biblioteca gensim
. Para instalá-la com o conda, usamos
conda install -c conda-forge gensim
E para instalá-lo com o pip, usamos
pip install gensim
Para limpar o conjunto de dados que baixamos, usaremos expressões regulares, que normalmente já estão instaladas no python, e nltk
, que é uma biblioteca de processamento de linguagem natural. Para instalá-la com o conda, usamos
conda install -c anaconda nltk
E para instalá-lo com o pip, usamos
pip install nltk
Agora que temos tudo instalado, podemos importar as bibliotecas que usaremos:
from gensim.models import Word2Vecfrom gensim.parsing.preprocessing import strip_punctuation, strip_numeric, strip_shortimport refrom nltk.corpus import stopwordsfrom nltk.tokenize import word_tokenize
Download do conjunto de dados
Vamos fazer o download de um conjunto de dados de textos da Wikipédia em espanhol, para isso executamos o seguinte:
from gensim.models import Word2Vecfrom gensim.parsing.preprocessing import strip_punctuation, strip_numeric, strip_shortimport refrom nltk.corpus import stopwordsfrom nltk.tokenize import word_tokenizefrom datasets import load_datasetdataset_corpus = load_dataset('large_spanish_corpus', name='all_wikis')
Vamos ver como é
from gensim.models import Word2Vecfrom gensim.parsing.preprocessing import strip_punctuation, strip_numeric, strip_shortimport refrom nltk.corpus import stopwordsfrom nltk.tokenize import word_tokenizefrom datasets import load_datasetdataset_corpus = load_dataset('large_spanish_corpus', name='all_wikis')dataset_corpus
DatasetDict({train: Dataset({features: ['text'],num_rows: 28109484})})
Como podemos ver, o conjunto de dados tem mais de 28 milhões de textos. Vamos dar uma olhada em alguns deles:
dataset_corpus['train']['text'][0:10]
['¡Bienvenidos!','Ir a los contenidos»','= Contenidos =','','Portada','Tercera Lengua más hablada en el mundo.','La segunda en número de habitantes en el mundo occidental.','La de mayor proyección y crecimiento día a día.','El español es, hoy en día, nombrado en cada vez más contextos, tomando realce internacional como lengua de cultura y civilización siempre de mayor envergadura.','Ejemplo de ello es que la comunidad minoritaria más hablada en los Estados Unidos es precisamente la que habla idioma español.']
Como há muitos exemplos, criaremos um subconjunto de 10 milhões de exemplos para trabalhar mais rapidamente:
subset = dataset_corpus['train'].select(range(10000000))
Limpeza do conjunto de dados
Agora, baixamos as stopwords
do nltk
, que são palavras que não fornecem informações e que serão removidas dos textos.
import nltk
nltk.download('stopwords')
Agora vamos fazer o download do punkt
do nltk
, que é um tokenizer
que nos permitirá separar os textos em sentenças.
nltk.download('punkt')
Criamos uma função para limpar os dados, essa função vai:
- Alterar o texto para letras minúsculas
- Remover urls
Remover menções de redes sociais, como @twitter
ou #hashtag
* Remover menções de redes sociais, como @twitter
ou #hashtag
.
- Remover sinais de pontuação
- Eliminar os números
- Eliminar palavras curtas
- Eliminar palavras de parada
Como estamos usando um conjunto de dados huggeface, os textos estão no formato dict
, portanto, retornamos um dicionário.
subset = dataset_corpus['train'].select(range(10000000))import nltknltk.download('stopwords')nltk.download('punkt')def clean_text(sentence_batch):# extrae el texto de la entradatext_list = sentence_batch['text']cleaned_text_list = []for text in text_list:# Convierte el texto a minúsculastext = text.lower()# Elimina URLstext = re.sub(r'httpS+|wwwS+|httpsS+', '', text, flags=re.MULTILINE)# Elimina las menciones @ y '#' de las redes socialestext = re.sub(r'@w+|#w+', '', text)# Elimina los caracteres de puntuacióntext = strip_punctuation(text)# Elimina los númerostext = strip_numeric(text)# Elimina las palabras cortastext = strip_short(text,minsize=2)# Elimina las palabras comunes (stop words)stop_words = set(stopwords.words('spanish'))word_tokens = word_tokenize(text)filtered_text = [word for word in word_tokens if word not in stop_words]cleaned_text_list.append(filtered_text)# Devuelve el texto limpioreturn {'text': cleaned_text_list}
Aplicamos a função aos dados
subset = dataset_corpus['train'].select(range(10000000))import nltknltk.download('stopwords')nltk.download('punkt')def clean_text(sentence_batch):# extrae el texto de la entradatext_list = sentence_batch['text']cleaned_text_list = []for text in text_list:# Convierte el texto a minúsculastext = text.lower()# Elimina URLstext = re.sub(r'httpS+|wwwS+|httpsS+', '', text, flags=re.MULTILINE)# Elimina las menciones @ y '#' de las redes socialestext = re.sub(r'@w+|#w+', '', text)# Elimina los caracteres de puntuacióntext = strip_punctuation(text)# Elimina los númerostext = strip_numeric(text)# Elimina las palabras cortastext = strip_short(text,minsize=2)# Elimina las palabras comunes (stop words)stop_words = set(stopwords.words('spanish'))word_tokens = word_tokenize(text)filtered_text = [word for word in word_tokens if word not in stop_words]cleaned_text_list.append(filtered_text)# Devuelve el texto limpioreturn {'text': cleaned_text_list}sentences_corpus = subset.map(clean_text, batched=True)
[nltk_data] Downloading package stopwords to[nltk_data] /home/wallabot/nltk_data...[nltk_data] Package stopwords is already up-to-date![nltk_data] Downloading package punkt to /home/wallabot/nltk_data...[nltk_data] Package punkt is already up-to-date!Map: 0%| | 0/10000000 [00:00<?, ? examples/s]
Salvaremos o conjunto de dados filtrado em um arquivo para que não seja necessário executar o processo de limpeza novamente.
sentences_corpus.save_to_disk("sentences_corpus")
Saving the dataset (0/4 shards): 0%| | 0/15000000 [00:00<?, ? examples/s]
Para carregá-lo, podemos fazer o seguinte
from datasets import load_from_disksentences_corpus = load_from_disk('sentences_corpus')
Agora, o que teremos é uma lista de listas, em que cada lista é uma frase tokenizada sem stopwords. Ou seja, temos uma lista de frases, e cada frase é uma lista de palavras. Vamos ver como isso se parece:
from datasets import load_from_disksentences_corpus = load_from_disk('sentences_corpus')for i in range(10):print(f'La frase "{subset["text"][i]}" se convierte en la lista de palabras "{sentences_corpus["text"][i]}"')
La frase "¡Bienvenidos!" se convierte en la lista de palabras "['¡bienvenidos']"La frase "Ir a los contenidos»" se convierte en la lista de palabras "['ir', 'contenidos', '»']"La frase "= Contenidos =" se convierte en la lista de palabras "['contenidos']"La frase "" se convierte en la lista de palabras "[]"La frase "Portada" se convierte en la lista de palabras "['portada']"La frase "Tercera Lengua más hablada en el mundo." se convierte en la lista de palabras "['tercera', 'lengua', 'hablada', 'mundo']"La frase "La segunda en número de habitantes en el mundo occidental." se convierte en la lista de palabras "['segunda', 'número', 'habitantes', 'mundo', 'occidental']"La frase "La de mayor proyección y crecimiento día a día." se convierte en la lista de palabras "['mayor', 'proyección', 'crecimiento', 'día', 'día']"La frase "El español es, hoy en día, nombrado en cada vez más contextos, tomando realce internacional como lengua de cultura y civilización siempre de mayor envergadura." se convierte en la lista de palabras "['español', 'hoy', 'día', 'nombrado', 'cada', 'vez', 'contextos', 'tomando', 'realce', 'internacional', 'lengua', 'cultura', 'civilización', 'siempre', 'mayor', 'envergadura']"La frase "Ejemplo de ello es que la comunidad minoritaria más hablada en los Estados Unidos es precisamente la que habla idioma español." se convierte en la lista de palabras "['ejemplo', 'ello', 'comunidad', 'minoritaria', 'hablada', 'unidos', 'precisamente', 'habla', 'idioma', 'español']"
Treinamento do modelo word2vec
Vamos treinar um modelo de embeddings que converterá palavras em vetores. Para isso, usaremos a biblioteca gensim
e seu modelo Word2Vec
.
dataset = sentences_corpus['text']dim_embedding = 100window_size = 5 # 5 palabras a la izquierda y 5 palabras a la derechamin_count = 5 # Ignora las palabras con frecuencia menor a 5workers = 4 # Número de hilos de ejecuciónsg = 1 # 0 para CBOW, 1 para Skip-grammodel = Word2Vec(dataset, vector_size=dim_embedding, window=window_size, min_count=min_count, workers=workers, sg=sg)
Esse modelo foi treinado na CPU, pois o gensim
não tem a opção de treinar na GPU e, mesmo assim, em meu computador, foram necessários X minutos para treinar o modelo. Embora o tamanho da incorporação que escolhemos seja de apenas 100 (ao contrário do tamanho da incorporação do openai, que é de 1536), não é um tempo muito longo, pois o conjunto de dados tem 10 milhões de frases.
Os modelos de linguagem grandes são treinados com conjuntos de dados de bilhões de frases, portanto, é normal que o treinamento de um modelo de embeddings com um conjunto de dados de 10 milhões de frases leve alguns minutos.
Depois que o modelo foi treinado, nós o salvamos em um arquivo para uso futuro.
dataset = sentences_corpus['text']dim_embedding = 100window_size = 5 # 5 palabras a la izquierda y 5 palabras a la derechamin_count = 5 # Ignora las palabras con frecuencia menor a 5workers = 4 # Número de hilos de ejecuciónsg = 1 # 0 para CBOW, 1 para Skip-grammodel = Word2Vec(dataset, vector_size=dim_embedding, window=window_size, min_count=min_count, workers=workers, sg=sg)model.save('word2vec.model')
Se quisermos fazer o upload no futuro, podemos fazer isso com
dataset = sentences_corpus['text']dim_embedding = 100window_size = 5 # 5 palabras a la izquierda y 5 palabras a la derechamin_count = 5 # Ignora las palabras con frecuencia menor a 5workers = 4 # Número de hilos de ejecuciónsg = 1 # 0 para CBOW, 1 para Skip-grammodel = Word2Vec(dataset, vector_size=dim_embedding, window=window_size, min_count=min_count, workers=workers, sg=sg)model.save('word2vec.model')model = Word2Vec.load('word2vec.model')
Avaliação do modelo word2vec
Vamos dar uma olhada nas palavras mais semelhantes de algumas palavras
dataset = sentences_corpus['text']dim_embedding = 100window_size = 5 # 5 palabras a la izquierda y 5 palabras a la derechamin_count = 5 # Ignora las palabras con frecuencia menor a 5workers = 4 # Número de hilos de ejecuciónsg = 1 # 0 para CBOW, 1 para Skip-grammodel = Word2Vec(dataset, vector_size=dim_embedding, window=window_size, min_count=min_count, workers=workers, sg=sg)model.save('word2vec.model')model = Word2Vec.load('word2vec.model')model.wv.most_similar('perro', topn=10)
[('gato', 0.7948548197746277),('perros', 0.77247554063797),('cachorro', 0.7638891339302063),('hámster', 0.7540281414985657),('caniche', 0.7514827251434326),('bobtail', 0.7492328882217407),('mastín', 0.7491254210472107),('lobo', 0.7312178611755371),('semental', 0.7292628288269043),('sabueso', 0.7290207147598267)]
model.wv.most_similar('gato', topn=10)
[('conejo', 0.8148329854011536),('zorro', 0.8109457492828369),('perro', 0.7948548793792725),('lobo', 0.7878773808479309),('ardilla', 0.7860757112503052),('mapache', 0.7817519307136536),('huiña', 0.766639232635498),('oso', 0.7656188011169434),('mono', 0.7633568644523621),('camaleón', 0.7623056769371033)]
Vejamos agora o exemplo em que verificamos a semelhança da palavra "queen" com o resultado da subtração da palavra "man" (homem) da palavra "king" (rei) e da adição da palavra "woman" (mulher).
embedding_hombre = model.wv['hombre']embedding_mujer = model.wv['mujer']embedding_rey = model.wv['rey']embedding_reina = model.wv['reina']
embedding_hombre = model.wv['hombre']embedding_mujer = model.wv['mujer']embedding_rey = model.wv['rey']embedding_reina = model.wv['reina']embedding = embedding_rey - embedding_hombre + embedding_mujer
embedding_hombre = model.wv['hombre']embedding_mujer = model.wv['mujer']embedding_rey = model.wv['rey']embedding_reina = model.wv['reina']embedding = embedding_rey - embedding_hombre + embedding_mujerfrom torch.nn.functional import cosine_similarityembedding = torch.tensor(embedding).unsqueeze(0)embedding_reina = torch.tensor(embedding_reina).unsqueeze(0)similarity = cosine_similarity(embedding, embedding_reina, dim=1)similarity
tensor([0.8156])
Como podemos ver, há muitas semelhanças
Exibição de incorporações
Vamos visualizar os embeddings. Para isso, primeiro obtemos os vetores e as palavras do modelo.
embeddings = model.wv.vectorswords = list(model.wv.index_to_key)
Como a dimensão dos embeddings é 100, para exibi-los em 2 ou 3 dimensões, temos que reduzir a dimensão. Para isso, usaremos o PCA
(mais rápido) ou o TSNE
(mais preciso) do sklearn
.
embeddings = model.wv.vectorswords = list(model.wv.index_to_key)from sklearn.decomposition import PCAdimmesions = 2pca = PCA(n_components=dimmesions)reduced_embeddings_PCA = pca.fit_transform(embeddings)
embeddings = model.wv.vectorswords = list(model.wv.index_to_key)from sklearn.decomposition import PCAdimmesions = 2pca = PCA(n_components=dimmesions)reduced_embeddings_PCA = pca.fit_transform(embeddings)from sklearn.manifold import TSNEdimmesions = 2tsne = TSNE(n_components=dimmesions, verbose=1, perplexity=40, n_iter=300)reduced_embeddings_tsne = tsne.fit_transform(embeddings)
[t-SNE] Computing 121 nearest neighbors...[t-SNE] Indexed 493923 samples in 0.013s...[t-SNE] Computed neighbors for 493923 samples in 377.143s...[t-SNE] Computed conditional probabilities for sample 1000 / 493923[t-SNE] Computed conditional probabilities for sample 2000 / 493923[t-SNE] Computed conditional probabilities for sample 3000 / 493923[t-SNE] Computed conditional probabilities for sample 4000 / 493923[t-SNE] Computed conditional probabilities for sample 5000 / 493923[t-SNE] Computed conditional probabilities for sample 6000 / 493923[t-SNE] Computed conditional probabilities for sample 7000 / 493923[t-SNE] Computed conditional probabilities for sample 8000 / 493923[t-SNE] Computed conditional probabilities for sample 9000 / 493923[t-SNE] Computed conditional probabilities for sample 10000 / 493923[t-SNE] Computed conditional probabilities for sample 11000 / 493923[t-SNE] Computed conditional probabilities for sample 12000 / 493923[t-SNE] Computed conditional probabilities for sample 13000 / 493923[t-SNE] Computed conditional probabilities for sample 14000 / 493923[t-SNE] Computed conditional probabilities for sample 15000 / 493923[t-SNE] Computed conditional probabilities for sample 16000 / 493923[t-SNE] Computed conditional probabilities for sample 17000 / 493923[t-SNE] Computed conditional probabilities for sample 18000 / 493923[t-SNE] Computed conditional probabilities for sample 19000 / 493923[t-SNE] Computed conditional probabilities for sample 20000 / 493923[t-SNE] Computed conditional probabilities for sample 21000 / 493923[t-SNE] Computed conditional probabilities for sample 22000 / 493923...[t-SNE] Computed conditional probabilities for sample 493923 / 493923[t-SNE] Mean sigma: 0.275311[t-SNE] KL divergence after 250 iterations with early exaggeration: 117.413788[t-SNE] KL divergence after 300 iterations: 5.774648
Agora vamos visualizá-los em duas dimensões com o matplotlib
. Vamos visualizar a redução de dimensionalidade que fizemos com PCA
e com TSNE
.
import matplotlib.pyplot as plt
plt.figure(figsize=(10, 10))
for i, word in enumerate(words[:200]): # Limitar a las primeras 200 palabras
plt.scatter(reduced_embeddings_PCA[i, 0], reduced_embeddings_PCA[i, 1])
plt.annotate(word, xy=(reduced_embeddings_PCA[i, 0], reduced_embeddings_PCA[i, 1]), xytext=(5, 2), textcoords='offset points', ha='right', va='bottom')
plt.title('Embeddings (PCA)')
plt.show()
plt.figure(figsize=(10, 10))
for i, word in enumerate(words[:200]): # Limitar a las primeras 200 palabras
plt.scatter(reduced_embeddings_tsne[i, 0], reduced_embeddings_tsne[i, 1])
plt.annotate(word, xy=(reduced_embeddings_tsne[i, 0], reduced_embeddings_tsne[i, 1]), xytext=(5, 2),
textcoords='offset points', ha='right', va='bottom')
plt.show()
Uso de modelos pré-treinados com huggingface
Para usar modelos embeddings
pré-treinados, usaremos a biblioteca transformers
da huggingface
. Para instalá-la com o conda, usamos
conda install -c conda-forge transformers
E para instalá-lo com o pip, usamos
pip install transformers
Com a tarefa feature-extraction
do huggingface
, podemos usar modelos pré-treinados para obter os embeddings das palavras. Para fazer isso, primeiro importamos a biblioteca necessária
import matplotlib.pyplot as pltplt.figure(figsize=(10, 10))for i, word in enumerate(words[:200]): # Limitar a las primeras 200 palabrasplt.scatter(reduced_embeddings_PCA[i, 0], reduced_embeddings_PCA[i, 1])plt.annotate(word, xy=(reduced_embeddings_PCA[i, 0], reduced_embeddings_PCA[i, 1]), xytext=(5, 2), textcoords='offset points', ha='right', va='bottom')plt.title('Embeddings (PCA)')plt.show()plt.figure(figsize=(10, 10))for i, word in enumerate(words[:200]): # Limitar a las primeras 200 palabrasplt.scatter(reduced_embeddings_tsne[i, 0], reduced_embeddings_tsne[i, 1])plt.annotate(word, xy=(reduced_embeddings_tsne[i, 0], reduced_embeddings_tsne[i, 1]), xytext=(5, 2),textcoords='offset points', ha='right', va='bottom')plt.show()from transformers import pipeline
Vamos obter os embeddings
do BERT
.
import matplotlib.pyplot as pltplt.figure(figsize=(10, 10))for i, word in enumerate(words[:200]): # Limitar a las primeras 200 palabrasplt.scatter(reduced_embeddings_PCA[i, 0], reduced_embeddings_PCA[i, 1])plt.annotate(word, xy=(reduced_embeddings_PCA[i, 0], reduced_embeddings_PCA[i, 1]), xytext=(5, 2), textcoords='offset points', ha='right', va='bottom')plt.title('Embeddings (PCA)')plt.show()plt.figure(figsize=(10, 10))for i, word in enumerate(words[:200]): # Limitar a las primeras 200 palabrasplt.scatter(reduced_embeddings_tsne[i, 0], reduced_embeddings_tsne[i, 1])plt.annotate(word, xy=(reduced_embeddings_tsne[i, 0], reduced_embeddings_tsne[i, 1]), xytext=(5, 2),textcoords='offset points', ha='right', va='bottom')plt.show()from transformers import pipelinecheckpoint = "bert-base-uncased"feature_extractor = pipeline("feature-extraction",framework="pt",model=checkpoint)
Vamos dar uma olhada nos embeddings
da palavra king
.
import matplotlib.pyplot as pltplt.figure(figsize=(10, 10))for i, word in enumerate(words[:200]): # Limitar a las primeras 200 palabrasplt.scatter(reduced_embeddings_PCA[i, 0], reduced_embeddings_PCA[i, 1])plt.annotate(word, xy=(reduced_embeddings_PCA[i, 0], reduced_embeddings_PCA[i, 1]), xytext=(5, 2), textcoords='offset points', ha='right', va='bottom')plt.title('Embeddings (PCA)')plt.show()plt.figure(figsize=(10, 10))for i, word in enumerate(words[:200]): # Limitar a las primeras 200 palabrasplt.scatter(reduced_embeddings_tsne[i, 0], reduced_embeddings_tsne[i, 1])plt.annotate(word, xy=(reduced_embeddings_tsne[i, 0], reduced_embeddings_tsne[i, 1]), xytext=(5, 2),textcoords='offset points', ha='right', va='bottom')plt.show()from transformers import pipelinecheckpoint = "bert-base-uncased"feature_extractor = pipeline("feature-extraction",framework="pt",model=checkpoint)embedding = feature_extractor("rey", return_tensors="pt").squeeze(0)embedding.shape
torch.Size([3, 768])
Como podemos ver, obtemos um vetor de 768
dimensões, ou seja, os embeddings
do BERT
têm 768
dimensões. Por outro lado, vemos que ele tem 3 vetores de embeddings
, isso porque o BERT
adiciona um token no início e outro no final da frase, portanto, estamos interessados apenas no vetor do meio.
Vamos refazer o exemplo em que verificamos a semelhança da palavra "rainha" com o resultado da subtração da palavra "homem" da palavra "rei" e da adição da palavra "mulher" à palavra "rei".
embedding_hombre = feature_extractor("man", return_tensors="pt").squeeze(0)[1]embedding_mujer = feature_extractor("woman", return_tensors="pt").squeeze(0)[1]embedding_rey = feature_extractor("king", return_tensors="pt").squeeze(0)[1]embedding_reina = feature_extractor("queen", return_tensors="pt").squeeze(0)[1]
embedding_hombre = feature_extractor("man", return_tensors="pt").squeeze(0)[1]embedding_mujer = feature_extractor("woman", return_tensors="pt").squeeze(0)[1]embedding_rey = feature_extractor("king", return_tensors="pt").squeeze(0)[1]embedding_reina = feature_extractor("queen", return_tensors="pt").squeeze(0)[1]embedding = embedding_rey - embedding_hombre + embedding_mujer
Vamos dar uma olhada na semelhança
import torch
from torch.nn.functional import cosine_similarity
embedding = torch.tensor(embedding).unsqueeze(0)
embedding_reina = torch.tensor(embedding_reina).unsqueeze(0)
similarity = cosine_similarity(embedding, embedding_reina, dim=1)
similarity.item()
Usando os embeddings
do BERT
, também obtemos um resultado muito próximo de 1.