Como lidar com dados não-estruturados usando Python
- #Python
Keywords
#python, nltk, huggingface, pln
Sumário
Entenda a diferença entre tipos de dados, como analisar dados não-estruturados, tudo isto usando Python. Por fim, você será capaz de construir o seu primeiro modelo capaz de identificar se uma mensagem é positiva, negativa ou neutra!
Tópicos
- Dados
- Informações
- Padrões
- Tipos de Dados
- Texto
- Um exemplo simples, mas complexo
- A monalisa da inteligência artificial
- Frameworks
- Python
- Na Prática
- Código
- Dados
- Entendendo o código
- HuggingFace Transformers
- Conclusão
Imagine que você está ensinando seu filho como andar de bicicleta, há todo um protocolo, é preciso escolher terrenos amigáveis, adicionar equipamentos adequados. É quase uma simulação. Precisa ser, ainda bem. Pensando sobre a computação, em especial eu quero falar sobre o campo da Ciência de Dados o iniciado é apresentado a uma série de trabalhos estruturados, limpos e prontos para serem analisados. Mas, assim como a criança terá que remover as rodinhas adicionais da sua bicicleta, no mundo real os dados estão em um formato um pouco mais caótico do que você imagina.
Este artigo almeja esclarecer a diferença entre dados estruturados e não-estruturados. Feito isso, entenderemos como trabalhar com um tipo específico deles, texto. Todo processo será feito utilizando a linguagem de programação Python e esclarecendo a importância de frameworks consistentes e bem documentos como um ponto da linguagem.
Dados
Às vezes em minha profissão, quando preciso simplificar conceitos, observo que a falta de compreensão desses conceitos começa na base. Em se tratando da ciência de dados, os dados. Em Lil’s Log, a autora comenta algo preciso, considerando a história moderna da inteligência artificial, “tudo que é possível hoje, é possível apenas pela quantidade de dados disponíveis”. Certamente, ferramentas para processar este também cumprem um papel vital. Mas, pensando profundamente, sem dados todas elas seriam inúteis e não teriam aplicabilidade alguma. Portanto, vamos entender primeiro o que são dados.
Informações
Vamos pensar em peças de Lego, você pode criar diversas coisas com elas, digamos, um castelo ou quem sabe apenas juntar cada uma delas aleatoriamente. Pense em dados como essas peças, todos os dias, comportamentos de usuários, preenchimento de formulários ou prescrições médicas produzem uma infinidade inigualável deles. Mas qual a diferença entre uma informação e um dados. Lembra das peças? Quando criamos algo com forma conhecida, digamos um castelo isso significa que podemos chamar isto de dados porque há alguma relação lógica entre essas minúsculas peças por outro lado quando elas estão organizadas de uma maneira confusa, não podemos fazer tanto.
Padrões
Mas, se houver um padrão e eu não for capaz de entender? Aha, é por isso que existem ferramentas que nos possibilitam “enxergar” melhor este padrão. Nem toda bagunça é ruim, às vezes precisamos apenas colocar cada uma dessas peças no lugar, como alguém que organiza o seu quarto, cuidadosamente colocando cada objeto no seu devido lugar. Isto é de suma importância, de tal modo que existe toda uma área do conhecimento voltada a isto, chama-se Engenharia de Dados. Em seu trabalho Wickham (2014), trata sobre como exatamente e em que formato estes dados precisam estar, de modo que pudéssemos ter uma estrutura universal para dados tabulares. Há ainda inúmeras outras evidências da importância da exploração de dados “bagunçados”. Porém, ao invés de nos estender nessas evidências, vamos entender as diferenças entre diferentes tipos de dados.
Tipos de Dados
Assim como existem diferentes tipos de peças de lego, para que possamos construir diferentes coisas, há diferentes tipos de dados. Pense um pouco, se alguém tirar fotos de centenas de flores essa informação é equivalente a uma planilha do excel onde você registrou a quantidade de horas de sono e as notas das sua avaliação? Não, definitivamente não, isto implica algo importante–diferentes tipos de dados precisam de diferentes tipos de tratamento.
Além das imagens, podemos ter as informações em forma de texto–por exemplo, milhares de comentários que foram extraídos do X/Twiiter. Em oposição aos chamados dados tabulares, aqueles que seguem uma estrutura semelhante ao que vemos nos escritórios. No geral, dados tabulares são simples de serem trabalhados, ou mesmo analisados. Mas, se você pensar um pouco notará que, “toda revolução, no campo da Inteligência Artificial Generativa, que estamos vivenciando agora usa como base dados em formatos não-estruturados”.
Portanto, aprender como lidar com este tipo de dados, é uma tarefa urgente, necessária e que é semelhante a dar um passo além nos seus estudos em oposição a dados em formatos ideias.
Texto
Sou um defensor do estudo da matemática que suporta inteligência artificial, para que quem pratica Machine Learning possa ter claramente em sua mente, o que significa Inteligência. Por exemplo–máquinas não podem entender textos.
Na autora de um campo chamado Processamento Natural de Linguagem (PNL), os pesquisadores perceberam que fazer avanços poderiam levar anos e de fato. Há inúmeras razões para tanto: como representar palavras diferentes mas no mesmo sentido? Como seria possível possibilitar que a máquina fosse capaz de entender contexto?
Esses anos passaram desde a década de 40 e graças àqueles esforços vivemos um momento magnífico em termos de análise de texto, em especial pela criação da arquitetura transformers (Ashish Vaswani et al 2017).
Mas ainda assim, é preciso cautela quando lidamos com este tipo de informação, ou pelo conteúdo que pode estar incorreto–uma informação precisa ou pela forma como se apresenta, uma vez que nenhum modelo nos dias de hoje entende de fato palavras, eles entendem representações delas. Mas nem tudo está perdido, porque sabemos, porque sabemos como fazer isto!
Um exemplo simples, mas complicado…
Em seu paper Mikolov et al. (2013) apresenta um método para representar palavras em um espaço com múltiplas dimensões. A ideia é simples, primeiramente cada palavra é transformada em um vetor. Esses vetores estão inseridos dentro de um espaço com muitas dimensões. Algo interessante de se notar, é que palavras semelhantes representam vetores mais próximos. Ainda mais surpreendente, os autores descreveram que o modelo, composto apenas por uma camada de neurônios, aprendeu a formular novas palavras. Por exemplo, subtraindo o vetor Rei com o vetor Homem resulta no vetor Rainha.
Fig.1 W2Vec representation. Observe como as palavras Lobo e Cachorro estão mais próximas, assim como Gato e Tigre (fonte: google images)
Mas como seria possível fazer tal cálculo? Calculando o produto vetorial entre dois vetores. Vetores que apontam na mesma direção tem uma correção entre si mais fortes, logo as palavras que foram codificadas nestes vetores em formatos numéricos, são mais próximas!
Fig.2 Produto Vetorial (fonte: google images)
Acontece que, para duas dimensões como na imagem acima, é possível computar isto, com relativa simplicidade, como mostra o código em Python, abaixo:
import numpy as np
from numpy.linalg import norm
# define two lists or array
A = np.array([2,1,2,3,2,9])
B = np.array([3,4,2,4,5,5])
print("A:", A)
print("B:", B)
cosine = np.dot(A,B)/(norm(A)*norm(B))
print("Cosine Similarity:", cosine)
Mas imagine isto para um dataset com milhares de linhas, pior, imagine sempre que que implementar isso, toda vez que precisar comparar a proximidade entre duas palavras. Por um lado, não será necessário uma vez que o que viria depois em 2017, superaria todo e qualquer modelo feito na compreensão de textos, porém não viável implementar o mecanismo em Python puro.
A monalisa da inteligência artificial
Fig.4 A Gioconda (fonte: WikiArt)
Em 2017 eu estava iniciando minha jornada no mundo da inteligência artificial, por ironia do destino, as coisas iriam mudar radicalmente naquele ano, a gigante Google anunciava a publicação de um paper intitulado “Attention Is All You Need”. Como sugere o nome, era uma nova forma de ensinar modelos a reconhecerem palavras usando um mecanismo chamado Atenção.
Pense sobre isto da seguinte maneira, você tem uma dúvida de história. Para tirar a sua dúvida você vai para um evento com milhares de professores, de todas as área do conhecimento. Você perguntaria a todos eles? Não, não faz sentido. Você prestaria atenção apenas nos mais relevantes, os professores de História. É isto que o modelo proposto por Ashish Vaswani et al (2017) faz, ele prioriza palavras que são mais importantes para o contexto.
Fig.5 O Mecanismo Atenção (fonte: autor)
Não é tão simples quanto parece, em especial quando avaliamos a estrutura e a matemática do problema, você certamente não teria tempo ou conhecimento para implementar este modelo todas as vezes.
Fig.6 O mecanismo transfoermers (fonte: Attention is All You Need)
Frameworks
Já pensou como seria se todas as vezes que tivesse que cozinhar um bolo, tivesse que criar a receita. Bem, é a mesma coisa. Reproduzir a arquitetura de Ashish Vaswani et al (2017) se tornaria uma missão impossível, quando na verdade você quer apenas usar os poderes que ela oferece. Bem, para nossa alegria alguém fez isso antes. De modo que você não precisa sempre ter que implementar a arquitetura, o nome técnico para isto é framework. Um framework, dita como o código deve se comportar, por exemplo, exige importações específicas, formatos específicos, digamos do texto, se estiver lidando com eles. Mas quando aprende ele te recompensa possibilitando construir coisas complexas com poucas linhas de código. Hoje, muitos destes frameworks são usados inclusive por grandes empresas como Google, Meta e Openai. Alguns deles são Trax, Tensorflow e Pytorch. Bom, o mais popular é certamente o Pytorch, sim , significa que precisamos falar do Py em PyTORCH.
P(ython)essoas
Eu poderia tratar sobre aspectos técnicos da linguagem, falar sobre como o Guido Van Rossum é certamente a pessoa que mais consegue sair de uma design platônico para uma linguagem de implementar ela de forma sublime, mas isto é cansativo e você lerá isto em toda introdução a linguagem. Vamos falar sobre o que realmente “move a agulha”, a comunidade.
Pense por um seguindo, umas das bibliotecas mais populares para Machine Learning é sem dúvidas o Scikit-Learn (Sklearn). O framework foi criado em 2007 e ainda continua sendo o mais usado, constância e aprimoramento. É um perfeito loop de feedback em que novos testes são feitos, os dados são analisados e o framework corrigido. Em qual linguagem foi escrito? Por “coincidência” Python.
No dia 02 de Fevereiro a Openai anuncia um dos modelos mais poderosos com ênfase na pesquisa científica, o Deep Research. Com uma pequena ressalva, para ter acesso ao modelo o usuário teria que desembolsar o valor de $200,00 dólares, mensais. Em resposta, no dia seguinte, a Startup HuggingFace, anuncia o Open Deep Research, uma versão alternativa e gratuita. Em apenas 24h! Então, da próxima vez que pensar em Python, também lembre das pessoas. Toda parte técnica você já sabe o bastante.
Na prática
Agora que compreende todo aporte teórico, preciso lhe mostrar em código como cada uma dessas coisas acontece. Se preferir, criei um notebook onde você pode acompanhar todo o código, clique neste link.
Após todo este contexto, sobre a importância das ferramentas, digamos que, a você foi atribuída a seguinte tarefa. Construir um modelo supervisionado que seja capaz de classificar tweets como ofensivos ou não ofensivos.
No primeiro momento, pode parecer um grande desafio, mas note. Isto é maior desafio que o trabalho de Ashish Vaswani et al (2017) e Mikolov et al (2013) ? Não, na verdade, em comparação com este esforço, tal coisa se assemelha a uma brincadeira e ainda bem! Podemos construir mais coisas e ir ainda mais longe do que sequer eles pensaram quando produziram seus trabalhos. Vamos passo a passo trabalhar com dados não-estruturados e usando as ferramentas mencionadas neste texto.
Código
import pandas as pd
df = pd.read_csv('/content/drive/MyDrive/Colab Notebooks/season_02/ep01/twitter_validation.csv')
df = df.rename(columns={'Irrelevant':'Target', 'I mentioned on Facebook that I was struggling for motivation to go for a run the other day, which has been translated by Tom's great auntie as 'Hayley can't get out of bed' and told to his grandma, who now thinks I'm a lazy, terrible person 🤣':'Text'})
column = ['Target', 'Text']
df = df[column]
from sklearn.preprocessing import LabelEncoder
le = LabelEncoder()
df.Target = le.fit_transform(df.Target)
import numpy as np
from sklearn.feature_extraction.text import CountVectorizer
count = CountVectorizer()
docs = np.array([
'The sun is shining',
'The weather is sweet',
'The sun is shining, the weather is sweet, and one and one is two'])
bag = count.fit_transform(docs)
print(count.vocabulary_)
from sklearn.feature_extraction.text import TfidfTransformer
tfidf = TfidfTransformer(use_idf=True,
norm='l2',
smooth_idf=True)
print(tfidf.fit_transform(count.fit_transform(docs))
.toarray())
df.loc[0, 'Text'][-50:]
import re
def preprocessor(text):
text = re.sub('<[^>]*>', '', text)
emoticons = re.findall('(?::|;|=)(?:-)?(?:\)|\(|D|P)',
text)
text = (re.sub('[\W]+', ' ', text.lower()) +
' '.join(emoticons).replace('-', ''))
return text
preprocessor(df.loc[0, 'Text'][-50:])
df['Text'] = df['Text'].apply(preprocessor)
from nltk.stem.porter import PorterStemmer
porter = PorterStemmer()
def tokenizer(text):
return text.split()
def tokenizer_porter(text):
return [porter.stem(word) for word in text.split()]
tokenizer_porter('runners like running and thus they run')
import nltk
nltk.download('stopwords')
from nltk.corpus import stopwords
stop = stopwords.words('english')
[w for w in tokenizer_porter('a runner likes running and runs a lot')
if w not in stop]
from sklearn.model_selection import train_test_split
X = df.iloc[:, 1].values
y = df.iloc[:, 0].values
X_train, X_test, y_train, y_test = train_test_split(
X, y, test_size=0.20, random_state=42)
from sklearn.pipeline import Pipeline
from sklearn.linear_model import LogisticRegression
from sklearn.feature_extraction.text import TfidfVectorizer
from sklearn.model_selection import GridSearchCV
tfidf = TfidfVectorizer(strip_accents=None,
lowercase=False,
preprocessor=None)
"""
param_grid = [{'vect__ngram_range': [(1, 1)],
'vect__stop_words': [stop, None],
'vect__tokenizer': [tokenizer, tokenizer_porter],
'clf__penalty': ['l1', 'l2'],
'clf__C': [1.0, 10.0, 100.0]},
{'vect__ngram_range': [(1, 1)],
'vect__stop_words': [stop, None],
'vect__tokenizer': [tokenizer, tokenizer_porter],
'vect__use_idf':[False],
'vect__norm':[None],
'clf__penalty': ['l1', 'l2'],
'clf__C': [1.0, 10.0, 100.0]},
]
"""
small_param_grid = [{'vect__ngram_range': [(1, 1)],
'vect__stop_words': [None],
'vect__tokenizer': [tokenizer, tokenizer_porter],
'clf__penalty': ['l2'],
'clf__C': [1.0, 10.0]},
{'vect__ngram_range': [(1, 1)],
'vect__stop_words': [stop, None],
'vect__tokenizer': [tokenizer],
'vect__use_idf':[False],
'vect__norm':[None],
'clf__penalty': ['l2'],
'clf__C': [1.0, 10.0]},
]
lr_tfidf = Pipeline([('vect', tfidf),
('clf', LogisticRegression(solver='liblinear'))])
gs_lr_tfidf = GridSearchCV(lr_tfidf, small_param_grid,
scoring='accuracy',
cv=5,
verbose=1,
n_jobs=-1)
gs_lr_tfidf.fit(X_train, y_train)
print(f'Best parameter set: {gs_lr_tfidf.best_params_}')
print(f'CV Accuracy: {gs_lr_tfidf.best_score_:.3f}')
clf = gs_lr_tfidf.best_estimator_
print(f'Test Accuracy: {clf.score(X_test, y_test):.3f}')
clf.predict(['love it'])
Entendendo o código
Vamos entender o que está acontecendo em cada etapa deste código e como ele te ajudará a completar a sua tarefa e impressionar seu chefe!
Primeiro, precisamos carregar os nossos dados, e fazer pequenos ajustes (se necessário), note que abaixo eu apenas mudei o nome das colunas, para simplificar nosso trabalho, no futuro.
import pandas as pd # pandas te ajuda a ler dados como planilhas
df = pd.read_csv('dados-twitter.csv')
df = df.rename(columns={'Irrelevant':'Target', 'I mentioned on Facebook that I was struggling for motivation to go for a run the other day, which has been translated by Tom's great auntie as 'Hayley can't get out of bed' and told to his grandma, who now thinks I'm a lazy, terrible person 🤣':'Text'})
column = ['Target', 'Text']
df = df[column]
Lembra sobre o que conversamos antes? Computadores não entendem letras, apenas número. Então, precisamos transformar a nossa coluna que contém as palavras que o modelo precisa prever: negativo, positivo e neutro em um formato especial. Para isto, vamos usar o sklearn.
from sklearn.preprocessing import LabelEncoder
le = LabelEncoder()
df.Target = le.fit_transform(df.Target)
Excelente, agora o nosso modelo poderá entender melhor o que significa cada uma daquelas palavras!
Agora, precisamos ficar um pouco mais técnicos, afinal. Ainda temos um monte de textos para analisar. Em machine learning, podemos aplicar diversas estratégias. Uma delas chama-se BOW. Vejamos como fazer isto e depois vamos entender porque usamos ele.
import numpy as np
from sklearn.feature_extraction.text import CountVectorizer
count = CountVectorizer()
docs = np.array([
'Rosas são violentas',
'Rosas são azuis',
'Eu não sei escrever um poema'])
bag = count.fit_transform(docs)
print(count.vocabulary_)
Imagine que você é um detetive e recebe uma mensagem, você não entende muito bem ela, mas quer analisar. Mas ao invés de olhar para gramática ou para o sentido. Você conta quais palavras aparecem mais vezes. Então você marca isso e descobre que se a palavra ‘Rosas’ aparece mais vezes, talvez ela seja mais importante. É isto, você acabou de aplicar BOW em seu texto.
Mas… será que todas as palavras que aparecem, são realmente importantes, não. Precisamos de mais uma etapa TD-IDF. Não se assuste com o nome, pense no TD-IDF como um fiscal que irá conferir se a palavra é realmente importante. Vamos ver como ficaria em código.
from sklearn.feature_extraction.text import TfidfTransformer
tfidf = TfidfTransformer(use_idf=True,
norm='l2',
smooth_idf=True)
Você por acaso entrou no Twitter algumas vezes, os jovens de hoje em dia usam palavras engraçadas e símbolos estranhos. Nosso modelo é esperto, mas nem tanto. Podemos facilitar a vida dele, limpando esse texto e fazendo ele parecer com algo mais parecido com o livro. A função abaixo, faz exatamente isto. Ela remove símbolos estranhos que mais parecem outro idioma!
import re
def preprocessor(text):
text = re.sub('<[^>]*>', '', text)
emoticons = re.findall('(?::|;|=)(?:-)?(?:\)|\(|D|P)',
text)
text = (re.sub('[\W]+', ' ', text.lower()) +
' '.join(emoticons).replace('-', ''))
return text
Continuando a nossa jornada de tratar os dados, agora chegamos ao momento crucial de todo código. A tokenização, outra palavra difícil para algo simples. Tokenizar é como criar uma caixinha especial para cada palavra da frase.
Fig.7 Tokenização (fonte: Google imagens)
Para fazer isto, vamos utilizar um framework (que você já aprendeu o que é) chamado nltk, ele facilita aplicar tokenização, sem que tenhamos que escrever nenhum código extra.
from nltk.stem.porter import PorterStemmer
porter = PorterStemmer()
def tokenizer(text):
return text.split()
def tokenizer_porter(text):
return [porter.stem(word) for word in text.split()]
Além disso, o nltk oferece uma função chamada “stop words”, que basicamente elimina palavras irrelevantes para a nossa análise, afinal, algumas palavras como: que, né, tá… não agregam em nada a nossa análise.
import nltk
nltk.download('stopwords')
In [ ]:
from nltk.corpus import stopwords
stop = stopwords.words('english')
[w for w in tokenizer_porter('a runner likes running and runs a lot')
if w not in stop]
Depois disso, as demais etapas são apenas Machine Learning, como o ênfase deste artigo não é tratar sobre, não vou me aprofundar nas etapas, mas até aqui, com sucesso lidamos com dados não-estruturados. Parabéns!
from sklearn.model_selection import train_test_split
X = df.iloc[:, 1].values
y = df.iloc[:, 0].values
X_train, X_test, y_train, y_test = train_test_split(
X, y, test_size=0.20, random_state=42)
In [ ]:
from sklearn.pipeline import Pipeline
from sklearn.linear_model import LogisticRegression
from sklearn.feature_extraction.text import TfidfVectorizer
from sklearn.model_selection import GridSearchCV
tfidf = TfidfVectorizer(strip_accents=None,
lowercase=False,
preprocessor=None)
param_grid = [{'vect__ngram_range': [(1, 1)],
'vect__stop_words': [stop, None],
'vect__tokenizer': [tokenizer, tokenizer_porter],
'clf__penalty': ['l1', 'l2'],
'clf__C': [1.0, 10.0, 100.0]},
{'vect__ngram_range': [(1, 1)],
'vect__stop_words': [stop, None],
'vect__tokenizer': [tokenizer, tokenizer_porter],
'vect__use_idf':[False],
'vect__norm':[None],
'clf__penalty': ['l1', 'l2'],
'clf__C': [1.0, 10.0, 100.0]},
]
small_param_grid = [{'vect__ngram_range': [(1, 1)],
'vect__stop_words': [None],
'vect__tokenizer': [tokenizer, tokenizer_porter],
'clf__penalty': ['l2'],
'clf__C': [1.0, 10.0]},
{'vect__ngram_range': [(1, 1)],
'vect__stop_words': [stop, None],
'vect__tokenizer': [tokenizer],
'vect__use_idf':[False],
'vect__norm':[None],
'clf__penalty': ['l2'],
'clf__C': [1.0, 10.0]},
]
lr_tfidf = Pipeline([('vect', tfidf),
('clf', LogisticRegression(solver='liblinear'))])
gs_lr_tfidf = GridSearchCV(lr_tfidf, small_param_grid,
scoring='accuracy',
cv=5,
verbose=1,
n_jobs=-1)
gs_lr_tfidf.fit(X_train, y_train)
print(f'Best parameter set: {gs_lr_tfidf.best_params_}')
print(f'CV Accuracy: {gs_lr_tfidf.best_score_:.3f}')
clf = gs_lr_tfidf.best_estimator_
print(f'Test Accuracy: {clf.score(X_test, y_test):.3f}')
clf.predict(['love it'])
HuggingFace Transformers
Até o presente momento usamos Machine Learning, mas podemos ir além e de forma ainda mais simples. Usando frameworks da startup HuggingFace conseguimos carregar diferentes tipos de modelos, que usam a arquitetura transformers! Sabe o que eles têm de especial? Eles já foram treinados por alguém muito esperto ou por empresas grandes. Para acessar esses modelos, digamos, para classificar texto. Usamos apenas três linhas.
from transformers import pipeline
pipe = pipeline(model="FacebookAI/roberta-large-mnli")
pipe("Meu texto incrível")
Agora, você pode usar o modelo Roberta, do Facebook! Ou quem sabe você queira treinar seu próprio modelo e enviar ao HuggingFace. Para que outras pessoas possam ver e usar, assim como nós acabamos de utilizar o modelo do Facebook. Recentemente eu treinei um modelo capaz de classificar textos em pt-br como sendo ofensivos ou não. O modelo pode ser acessado exatamente da mesma forma. Tudo é possível graças ao empenho de pessoas que todos os dias simplificam o processo de treinar e disponibilizar modelos de inteligência artificial.
Conclusão
Wow, conseguimos. Com sucesso fomos capazes de atravessar a história dos dados e como eles são divididos. Você também analisou, tratou e construiu seu primeiro modelo para detecção de impressões com base em um dataset com textos do Twitter. Tudo isto com Python e usando alguns frameworks. Finalmente, vimos como carregar modelos poderosos treinados por outras pessoas usando os frameworks transformers da startup HuggingFace. Agora que você sabe o básico, você pode construir modelos ainda mais interessantes e eficientes e assim como crianças que precisam se desafiar para aprender a andar de bicicleta, você será capaz de explorar novos campos e desafios em Machine Learning e no campo da Inteligência Artificial como um tudo.
Referências
Vaswani, A. (2017). Attention is all you need. Advances in Neural Information Processing Systems.
Mikolov, T., Grave, E., Bojanowski, P., Puhrsch, C., & Joulin, A. (2017). Advances in pre-training distributed word representations. arXiv preprint arXiv:1712.09405.
Raschka, S., Liu, Y. H., & Mirjalili, V. (2022). Machine Learning with PyTorch and Scikit-Learn: Develop machine learning and deep learning models with Python. Packt Publishing Ltd.
Wickham, H. (2014). Tidy data. Journal of statistical software, 59, 1-23.
Rothman, D. (2021). Transformers for Natural Language Processing: Build innovative deep neural network architectures for NLP with Python, PyTorch, TensorFlow, BERT, RoBERTa, and more. Packt Publishing Ltd.