Ensinando português ao GPT-2
O GPT-2 leu a Wikipédia em português e aprendeu a inventar pessoas, cidades e eventos históricos
O GPT-2 é uma rede neural desenvolvida pela OpenAI que ganhou bastante atenção tanto nos meios técnicos quanto na mídia, por causa de sua incrível capacidade de gerar textos que à primeira vista parecem escritos por humanos. Tecnicamente, é um modelo de língua, e eu escrevi recentemente um post em inglês revisando estes tipos de modelos.
A versão maior e mais poderosa do GPT-2 não foi disponibilizada para o público, mas outras duas menores foram. É interessante que podemos fazer o fine-tuning destes modelos, isto é, treiná-los mais um pouco com dados específicos que queiramos que eles aprendam. Ou seja, em vez de começar o treinamento do zero, podemos já começar a partir de um modelo já pré-treinado, e alcançar resultados melhores e mais rápidos.
Já há alguns exemplos muito interessantes de versões do GPT-2 especializadas em diferentes estilos de texto em inglês: há um subreddit exclusivo para bots conversarem, gerador de poesia, simuladores de chat, entre outros.
Mas… e quanto a outras línguas? Especificamente, será que dá para fazer o GPT-2 aprender português? A resposta simples é que sim! Neste post, vou explicar como fiz o fine-tuning do GPT-2 na Wikipedia em português e mostrar os resultados.
GPT-2 original
Inglês vs.outras línguas
O GPT-2 original foi treinado com um corpus de cerca de 40 GB de textos coletados da Internet, a grande maioria em inglês. O GPT-2 é agnóstico quanto a línguas — ele não faz distinção se está tratando de inglês, português ou árabe, simplesmente aprende a predizer palavras nos textos que vê.
Na verdade, o GPT-2 nem lida bem com palavras, mas com Byte-Pair Encodings, ou BPE. Resumidamente, é como se fosse um vocabulário composto por caracteres e sequências de caracteres mais comuns vistas nos textos. Algumas palavras pequenas ou muito comuns fazem parte deste vocabulário, enquanto outras são decompostas em partes menores, e tratadas como mais de um token. Isso traz muita flexibilidade, pois não se fica amarrado ao vocabulário de uma língua específica.
A implementação de BPE usada no GPT-2 tem alguns truques para dar conta de caracteres Unicode, de modo que caracteres especiais e alfabetos diferentes podem ser representados sem problemas. A questão é apenas ter dados suficientes para aprender representações para todos eles.
Custo computacional
O custo computacional para se treinar um modelo de língua estado-da-arte é enorme, chegando a horas de uso de várias GPUs ou TPUs em paralelo. Isso implica em um gasto de dezenas de milhares de dólares com equipamento e eletricidade, além de toda a emissão de carbono gerada.
Por isso, é fundamental que possamos reaproveitar ao máximo os modelos pré-treinados. A boa notícia é que o transfer learning (aproveitar um modelo de machine learning para inicializar outro) tem dado ótimos resultados em NLP, e o fine-tuning gasta uma quantidade de processamento minúscula em comparação com o treinamento inicial. Mais uma motivação para este experimento!
WikiWriter
Usar a Wikipedia como um corpus é comum em NLP. Ela tem uma grande quantidade de textos facilmente acessíveis em várias línguas, e tem um vocabulário bastante amplo, ainda que com um estilo de escrita muito enciclopédico. Mas tudo bem, vai ser interessante ver o nosso modelo, que vou chamar de WikiWriter, aprender a escrever neste estilo.
Obtendo os dados
Primeiramente, precisamos baixar um dump da Wikipédia (com acento, a versão em português!) na página de dumps do MediaWiki. Acessando o primeiro link (Database backup dumps) chegamos a uma listagem de diversas línguas e bases. Para o nosso experimento, estamos interessados no ptwiki mais recente, que nos leva a uma página com vários links de dados e metadados da Wikipédia em português.
Desta lista, eu baixei o primeiro, que se chama ptwiki-[DATA]-pages-articles-multistream.xml.bz2
, com a data do dump. Trata-se de um arquivo compactado com 1,6 GB, e que descompactado tem cerca de 7,2 GB — estamos falando da Wikipédia lusófona inteira, mais de um milhão de artigos!
Pré-processamento
O dump é um XML cheio de metadados, e nós queremos apenas o texto corrido. Para extraí-lo, usei o WikiExtractor, um script que trata diversas particularidades destes arquivos.
O texto da Wikipedia contém vários códigos de template, que são usados para incluir nos artigos coisas como imagens, listas, tabelas, ou mesmo trechos de texto, como links ou a escrita fonética de uma palavra. O WikiExtractor, a princípio, consegue processar os templates para gerar um resultado bem próximo do que se vê na Wikipedia, mas fica extremamente lento. Tive então de desabilitar essa funcionalidade (com a flag --no_templates
), e em poucos minutos toda a Wikipedia estava convertida em 15 arquivos JSON.
Mesmo assim, os textos ficam bastante bons. Os templates mais comuns servem apenas para gerar links — algo do tipo [[Astronomia]]
escreve a palavra Astronomia no texto com um link para o artigo de mesmo nome — , e são tratado pelo WikiExtractor mesmo no modo no_templates
. Um dos poucos problemas que notei foi a ocorrência de vários parênteses vazios, onde havia algum tipo de informação gerada por templates mais complexos, mas dá para corrigir manualmente.
Escrevi um script simples em Python para ler o conteúdo dos arquivos JSON, extrair o atributo text
, remover pares de parênteses vazios e agregar tudo em um único arquivo de texto. O resultado foi um arquivo de "apenas" 1,5 GB.
Treino
O repositório oficial da OpenAI no GitHub contém apenas o código para rodar um modelo treinado, mas não para treinar um novo ou fazer fine-tuning de um existente. Não que seja tão difícil assim escrever um script para treino, especialmente para o segundo caso; tanto é que logo surgiu este fork com um código fácil de usar, e depois o gpt-2-simple, que simplifica ainda mais o processo.
A primeira coisa que o GPT-2 faz com o texto é tokenizá-lo (usando o BPE) e converter cada token em um código numérico. Esse processo é demorado para textos muito longos, e como podemos querer experimentar diferentes configurações com os mesmos dados, é bom tokenizar apenas uma vez e salvar o resultado intermediário. Para isso, usei o script encode.py
do fork do nshepperd. No caso do meu texto da Wikipedia, o resultado foi um arquivo numpy com cerca de 780 MB.
Para usar o codificar os tokens, é necessário já ter o modelo pré-treinado do GPT-2! Ele pode ser baixado usando o gpt-2-simple ou um script da OpenAI. O nome do modelo que usei é 345M.
Com isso, temos tudo pronto para começar o treino de fato! Fiz a seguinte chamada ao gpt_2_simple:
gpt_2_simple finetune wiki-text.npz --sample_every 500 --model_name 345M --steps 10000
Treinar um modelo neural deste tamanho leva muito tempo em CPU, e por isso é extremamente recomendado usar uma GPU. Em uma GeForce Titan Xp, o treino levou pouco mais de uma hora.
E assim surgiu o nosso WikiWriter, fine-tunado por 10 mil passos, com uma amostra gerada a cada 500. O --model_name
especifica que queremos treinar a partir do modelo 345M, a maior versão do GPT-2 atualmente disponível. A cada 10 passos, o gpt_2_simple mostra a média móvel da função de perda (ou loss) do modelo, uma indicação do quão bem ele consegue gerar textos. Não vou entrar em detalhes, mas a loss do GPT-2 é calculada como a entropia cruzada de todo o vocabulário a cada token gerado. Eis o gráfico da evolução da loss:
O pico no começo do treino é normal, já que pode haver uma certa variação no cálculo em pequenos batches (como é o caso aqui), mas os valores ficam mais consistentes ao longo de várias iterações. Podemos ver que a loss cai bastante até 2,5, a partir de onde fica mais difícil melhorar o WikiWriter.
Resultado
Enfim, vamos ver o que o WikiWriter aprendeu! Aqui estão alguns textos gerados:
“Armored Warfare” é o segundo álbum de estúdio da banda estadunidense The Band, lançado em 1965. Foi o primeiro álbum a ter lançamento em 1965 e foi lançado pela gravadora chamada Midway Records. Em 1969, o álbum foi gravado pela gravadora Midway Records, responsando a gravação do álbum. O disco foi gravado em diversas cenas, como “The Last Man (Remix)”, “The Last Person”, “The Last Man (Remix)”, “The Last Person”, “The Last Person”, “The Last Person”, “The Last Person”, “The Last Person” e “The Last Person”. O disco teve vendas de mais de 26 mil cópias nos Estados Unidos. Ame seus álbuns de estúdio foram certificados em diversos países brasileiros e o “single” “The Last Man” vendeu mais de 4 mil cópias nos Estados Unidos.Em 1979, o álbum “The Last Man” foi lançado. Em 1980, o disco “The Last Man” foi lançado. Em 1982, o quarto álbum da banda estadunidense, “Live at The End”, foi lançado. Em 1984, “Armored Warfare” foi lançado. Em 1985, o segundo álbum da banda estadunidense, “Live at The End” foi lançado. Em 1986, “Live At The End” foi lançado.
Temos aí uma mistura curiosa. Segundo a Wikipedia em inglês, Armored Warfare é um jogo de videogame e PC, mas este artigo nem existe na Wikipedia em português — aliás, estas duas palavras não aparecem em lugar nenhum lá! Já a The Band existe mesmo, mas só surgiu em 1965. Uma pena que o WikiWriter não tenha sido muito criativo com nomes dos outros álbuns. Fora isso, há alguns erros que tornam o texto estranho, mas à primeira vista é impressionante como o WikiWriter aprendeu a estrutura geral das frases em português.
Columbia é uma empresa que se localiza na cidade de Columbia, no estado norte-americano de Maryland.Em 2006, a empresa foi incorporada ao Grupo Malibu. Em 2007, a empresa passa a operar como a National of the Arts. Em 2010, a empresa foi fundada e distribui as empresas de música e de educação e escritores.Atualmente a empresa possui uma das mais importantes empresas do mundo: a "National Library of America", que conta com o volume "A History of the American Educational System", e a "National Endowment for the Arts", onde conta com o volume "A History of the American National Parks".
Existem algumas empresas chamadas Columbia-alguma-coisa (como Columbia Pictures), e existe apenas um pequeno povoado chamado Columbia em Maryland. Novamente, o WikiWriter não foi super criativo com os nomes, mas é interessante notar como ele alterna perfeitamente entre inglês e português para falar dos nomes das instituições e o texto em si. Temos uma inconsistência semântica (uma empresa foi fundada após já ter feito outras coisas!), mas o único deslize gramatical foi usar o verbo no presente no final do segundo parágrafo. Impressionante!
A confederação brasileira foi fundada em 1961 pelo Governo de São Paulo com lideranças do Partido Comunista Brasileiro (PCB). Ainda assim, após o fim da Segunda Guerra Mundial, o PCB foi transformado em união política sob o regime militar.A confederação foi fundada em 1961, após a morte de São Bento Maria Martins (SBN) e seus aliados São Domingos de Santos e Cacém e São Luís Freitas. Ainda assim, a confederação foi transformada na Comunidade Nacional de São Paulo (CNSP), após a morte de São Luís Freitas e seus aliados São Domingos de Santos e Cacém.A confederação se encerra em 1952.
A História numa realidade paralela? Dessa vez, nenhum erro gramatical, apesar dos absurdos semânticos — a suposta confederação foi encerrada antes de sua fundação — , da sigla SBN e de um pouco de repetições. Dá pra imaginar que alguém cansado numa leitura rápida sem muita atenção acredite que foi escrito por um humano.
São Luís Freitas (CCSA)São Luís Freitas é uma das três comunidades portuárias portuárias brasileiras.A cidade de São Luís Freitas conta com uma área total de 13,77 km², sendo, na sua área urbano, a mais alta área urbano do estado do Rio de Janeiro e a mais distante em relação à Terra, ao longo do lago Nossa Senhora.
Mais uma sigla que não corresponde às iniciais, uma área muito pequena para uma cidade, alguns erros de concordância, mas um texto bastante fluido com absurdos que me fizeram rir.
E tem mais São Luís Freitas!
São Luís FreitasSão Luís Freitas (Rio de Janeiro, 19 de abril de 1969) é um policial brasileiro. Ajuda a investigar crime na região, sendo este o responsável por se tornar o primeiro policial a ser investigado pelo ex-deputado federal Pedro Díaz de Oliveira, pelo Estado Novo e por uma indictação criminal.Recebeu o título de ocupante de police no interior do estado do Rio de Janeiro. Em 1994, atuou por ex-detetivo, escolheu o posto de policial, mas foi preso em 1996 em um acidente vehículo.Fundou o STF (Sociedade Federal de Direito e Trabalho). Em 1997, foi eleito deputado federal pelo PTB na habilidade de reeleger-se como senador e deputado federal, sendo sucedido pelo Partido Trabalhista.O STF foi o primeiro Partido Trabalhista a ter um representante no Brasil, sendo sucedido por o PTB. Demo-viveu na região do interior do estado até 2000, quando foi eleito para a confederação da PDC, run por Fernando Braga, na época como deputado federal.Foi um dos responsáveis pela política brasileira em 1992. Otou o cargo em 1992, quando foi mais uma vez ao mandato de Fernando Braga. Em 1994, foi selecionado para representar o estado do Rio de Janeiro na habilidade de se candidatar a deputado federal. Como senador, foi afastado após a sua eleição ao seu partido.Foi eleito deputado federal em 2000, sendo reeleito em 2002, sendo reeleito em 2004 e em 2010, obtendo-lhe o voto para a presidência do Brasil. Em 2012, repórteres do PCB selecionaram-no para candidatar-se a deputado federal. Em 2013, foi eleito deputado federal pelo PTB como um dos responsáveis do acidente que causou o acidente, mas foi repelido por uma revanche.Em 2015, foi eleito deputado federal pelo PTB por votação contra o Partido Trabalhista.Foi o primeiro deputado federal desde 1992, porém foi reeleito em 2016.Em 2016, foi desligado de seu mandato de deputado federal por ser falecido no acidente.
Na minha opinião, este foi o melhor gerado até agora, ao menos do ponto de vista cômico! Alguns erros ortográficos (police, vehículo e até run) parecem uma influência do pré-treino em inglês que não foi totalmente esquecido, mas que raramente vem à tona. Mas os absurdos lógicos praticamente não comprometem a gramaticalidade do texto, e é justamente essa fluência do WikiWriter junto com seu desconhecimento do mundo que tem a capacidade de tornar a biografia de um político imaginário inesperadamente hilária. A propósito: não existe nenhum São Luís Freitas nem Pedro Díaz de Oliveira na Wikipédia. Mais um ponto para a criatividade de nomes.
Conclusões
Eu criei o WikiWriter como uma forma de aprender na prática a usar o GPT-2 para transfer learning. E também, claro, para me divertir com o resultado. Estes objetivos foram alcançados sem muita dificuldade, mas vamos discutir um pouco mais.
Aplicações
Mas qual a utilidade de uma IA como o WikiWriter, além de escrever artigos surreais? Novamente, transfer learning. Redes neurais para diversas tarefas de NLP já se beneficiaram de aproveitar o GPT-2, BERT, ou outros modelos de língua pré-treinados. No entanto, estes modelos são normalmente treinados com textos majoritariamente em inglês, o que não é muito interessante quando trabalhamos com outras línguas.
Uma estratégia interessante é primeiro fazer o fine-tuning de um modelo de língua para um idioma novo (como o que fiz aqui), para depois aplicá-lo a outra tarefa específica neste idioma — como classificação de textos, análise de sentimentos ou detecção de entidades. Para ser sincero, ainda não conheço nenhum relato deste transfer learning em dois estágios, mas como esta é uma técnica um pouco recente em NLP e as aplicações para línguas sem ser o inglês costumam demorar para aparecer, me parece bastante razoável.
Qualidade
Como avaliar a qualidade dos textos gerados? Isso é bem subjetivo. Temos a função de loss, que mede basicamente o quão distante o modelo estava de predizer as palavras corretas durante seu treino. Mas ao gerar textos, nem sempre existe a palavra correta. Há muitas possibilidades que um gerador pode explorar, e avaliar isso automaticamente é um problema em aberto em NLP.
Como deu pra ver nos exemplos, o WikiWriter comete alguns erros de gramática e ortografia, além de produzir absurdos lógicos. Mas ao mesmo tempo, o estilo de escrita é bastante coerente com a Wikipédia. Eu diria que os textos acima são muito bons.
Mas as amostras que eu mostrei aqui foram escolhidas manualmente por serem as melhores que encontrei. Na verdade, o WikiWriter também produziu artigos bastante sem graça, cheios de repetição, às vezes com a mesma frase repetida mais de dez vezes. Então, na média, já não é nenhuma maravilha, e dificilmente se passaria por humano.
Em comparação com textos gerados em inglês após o fine-tuning do GPT-2 345M (como por exemplo o subreddit SubSimulatorGPT2), descontado o problema da repetição, me parece que a diferença de qualidade é bem pequena. Considerando que o WikiWriter deve ter visto muito pouco texto em português em seu pré-treino, isso parece excelente. Aliás, no subreddit, os posts dos bots costumam ser bem curtos, o que diminui bastante o problema de repetição. Se olharmos só para a primeira ou segunda frase gerada pelo WikiWriter, temos uma comparação mais justa.
Naturalmente, se tivéssemos acesso a um modelo de língua pré-treinado em textos em português, ou mesmo uma versão maior do GPT-2, já teríamos melhoras no texto gerado. Mas uma busca mais cuidadosa por hiperparâmetros na fase de fine-tuning, especialmente a taxa de aprendizado, também poderia dar melhores resultados. Eu não quis dedicar tempo demais a este experimento, mas seria uma coisa natural a se fazer se quiséssemos o melhor gerador possível.
Amostras
Aqui está uma amostra com 30 rodadas do modelo para gerar artigos. Cada rodada é separada por uma sequência de =====
, e o <|endoftext|>
indica o fim de um artigo. Veja e tire suas próprias conclusões!
Modelo Treinado
(atualizado 22/01/2020)
Agora o modelo treinado está disponível aqui! Para usá-lo, siga as instruções do gpt_2_simple.