Gerando a continuação de Game of Thrones com Deep Learning

Como podemos usar Inteligência Artificial para dar uma mãozinha pro George R. R. Martin

“Uma mente necessita de livros da mesma forma que uma espada necessita de uma pedra de amolar, se quisermos que se mantenha afiada.” — Tyrion Lannister

Game of Thrones é um fenômeno mundial que desde a sua estreia, em 2011, tem sido aclamada pelo público e pela crítica. A última temporada veio com tudo e vai focar na luta final contra o Rei da Noite e a disputa pelo trono de ferro. Quem é fã da série sabe como é triste perceber que essa é a última temporada— Lembrando que os livros possuem uma história diferente da série e a HBO já está planejando a produção de derivados. Apesar do encerramento da série, com o uso inteligência artificial (IA) podemos criar uma continuação dos livros do autor George R.R. Martin e escrever uma história totalmente nova e inesperada.

O código completo você encontra nesse repositório.

1 . Deep Learning criativa

O potencial da inteligência artificial para reproduzir processos de pensamento humano, como reconhecimento de objetos e tarefas como dirigir um carro ou gerar imagens, já está sendo implementado e está no centro das atenções das últimas pesquisas.

Estamos nos aproximando de um futuro em que grande parte dos conteúdos que consumimos durante nosso dia a dia será criada com IA. Alguns produtores artísticos utilizaram IA para auxilio ao processo de criação de músicas, vídeos, roteiros de filmes e etc.

IA não foi criada para rivalizar com pintores, roteiristas e compositores. Algumas tarefas de IA não conseguem substituir humanos (por enquanto), pois certas atividades exigem uma capacidade de abstração que apenas nosso cérebro é capaz de desenvolver. IA não é sobre a substituição de nossa inteligência, e sim sobre trazer novos recursos e melhores condições de vida .

Machine Learning (ML) é apenas matemática. O algoritmo não tem conhecimento da vida humana, emoções ou experiência do nosso mundo. 
ML pode aprender o padrão estatístico na análise de imagens, músicas e textos, e a partir disso, o modelo pode criar novas obras de arte com características semelhantes a criação humana com base nos exemplos que viu em seus dados de treinamento.

2. Gerando texto com redes neurais recorrentes

Redes Neurais Recorrentes (RNNs) são um tipo poderoso e robusto de redes neurais e pertencem aos algoritmos mais promissores que existem no momento, porque são os únicos que podem armazenar dados.

Por conta da sua memória interna, as RNNs conseguem armazenar dados importantes sobre conteúdos que recebem, o que permite que sejam precisas em prever o futuro.

Essa é a razão pela qual RNNs são recomendadas para dados sequenciais, como séries temporais, textos, dados financeiros, áudio, clima e outros. RNNs conseguem formar uma compreensão muito mais profunda de uma sequência e todo seu contexto comparado com outros algoritmos.

Nesse projeto, a continuação do livro será gerada usando LSTM —Long Short-Term Memory, uma extensão das RNNs altamente poderosa, pois possuem células que são capazes de armazenar maiores quantidades de memória.

Essas extensão permitem que as RNNs se lembrem de suas entradas por um longo período de tempo. Isso porque contêm suas informações em uma memória, que é muito parecida com a memória de um computador, por conta que podem gravar e excluir informações.

2.1. Como a sequência é gerada?

Existem diversas formas de criar dados sequências e cada método depende do tipo de dado que você vai gerar. Como estamos tratando de texto, existem duas maneiras — Caractere por caractere ou palavra por palavra.

O método mais comum é gerar caractere por caractere. Por exemplo, a frase: “Jon Snow é o verdadeiro re”, a rede neural é treinada para predizer o próximo caractere “i”.

Toda rede neural capaz de modelar a probabilidade do próximo caractere em determinada língua, é chamada de language model, pois captura o padrão e a estrutura estatística da linguagem.

Com a rede neural treinada em um grande conjunto de texto, você inicialmente pode gerar uma frase inicial e alimentar algoritmo. A partir disso, o algoritmo ficará gerando novos caracteres e esses novos caracteres serão adicionados aos dados de entrada(a frase que você criou) e assim o processo continua.

2.2. Randômico ou não?

O método que a rede escolhe o próximo caractere é extremamente importante, pois dependendo da estratégia, talvez o texto fique incoerente ou sem sentido. Uma das possibilidades de abordagem seria por greedy sampling, escolhendo o próximo caractere de acordo com a formação das palavras que estão nos dados de treino. Entretanto, essa abordagem gera palavras repetidas e fora de contexto.

Outra abordagem seria aplicar um pouco de aleatoriedade no algoritmo, processo chamado de stochastic sampling. Permite que caracteres que possam ser improváveis ​​para serem selecionados, sejam escolhidos, gerando frases mais interessantes com maior criatividade, apresentando novas palavras que não ocorreram nos dados de treinamento.

Para controlar a taxa de dados aleatórios, vamos introduzir o conceito chamado de softmax temperature, que indica o quanto determinístico será a predição.

Estabelecido a softmax temperature, valores mais altos resultam em distribuições amostrais maiores e gerarão mais dados aleatórios e menos estruturados, ao mesmo tempo que, valores menores, resultarão em menos aleatoriedade e os dados gerados serão mais previsíveis.

3. Obtendo os dados

Os livros estão disponíveis no Kaggle: Game Of Thrones books. Esse dataset contém os cinco livros em formato txt, justamente o que nós precisamos.

4. Pré-Processamento dos dados

Vamos juntar o conteúdo dos cinco livros, extrair sequências de tamanhos determinados, aplicar one-hot encoded e transformar os dados em um Numpy array 3D. Precisamos passar os dados para o formato numérico, pois a rede neural só compreende números. Será necessário realizar esse processo para ambos dados de treino e teste.

# Comprimento de sequências de caracteres extraídos
maxlen = 160

# Geramos uma nova sequência a cada caractere de "step"
step = 9

# Armazena as sequências extraídas
sentences = []

# Armazena os próximos caracteres
next_chars = []

for i in range(0, len(corpus_raw) - maxlen, step):
sentences.append(corpus_raw[i: i + maxlen])
next_chars.append(corpus_raw[i + maxlen])
print('Number of sequences:', len(sentences))

# Lista de caracteres únicos no texto
chars = sorted(list(set(corpus_raw)))
print('Unique characters:', len(chars))
# Dictionary mapping unique characters to their index in `chars`
char_indices = dict((char, chars.index(char)) for char in chars)

# Aplica o método one-hot encode nos caracteres
print('Vectorization...')
x = np.zeros((len(sentences), maxlen, len(chars)), dtype=np.bool)
y = np.zeros((len(sentences), len(chars)), dtype=np.bool)
for i, sentence in enumerate(sentences):
for t, char in enumerate(sentence):
x[i, t, char_indices[char]] = 1
y[i, char_indices[next_chars[i]]] = 1

5. Construindo a rede

A estrutura da rede será apenas uma camada de LSTM seguida por uma rede totalmente conectada, contendo o número de neurônios de acordo com o total de caracteres que foram encontrados no texto.

A última camada da rede terá softmax como função de ativação, pois como vamos utilizar um processo aleatório para seleção dos caracteres, precisamos de uma função capaz de escolher entre as probabilidades finais de cada caractere.

Como as saídas são one-hot encoded, usaremos categorical_crossentropy para treinar o modelo.

model = keras.models.Sequential()
model.add(layers.LSTM(128, input_shape=(maxlen, len(chars))))
model.add(layers.Dense(len(chars), activation='softmax'))
optimizer = keras.optimizers.adam(lr=0.01)
model.compile(loss='categorical_crossentropy', optimizer=optimizer)

Uma das características mais impressionantes é a simplicidade da rede. Com apenas uma camada contendo 128 neurônios, podemos gerar textos realistas!

6. Treinamento

O treinamento é simples e segue o mesmo conceito das outras estruturas de redes. Entenda que, como estamos trabalhando com texto, o algoritmo precisa de um pequeno incentivo para começar a criar novos conteúdos.

O algoritmo seguirá esses passos repetidamente:

  1. Extrair do modelo uma distribuição de probabilidade para o próximo caractere, dado o texto que foi gerado até agora.
  2. Mudar o valor da temperatura para uma nova distribuição.
  3. Gerar o próximo caractere de acordo com a nova distribuição.
  4. Adicionar o novo caractere no final do texto.

O algoritmo irá gerar textos em diferentes temperaturas à cada época. 
Isso permite que você observe como o texto gerado evolui à medida que o modelo começa a convergir, bem como o valor da temperatura pode impactar na amostragem de caracteres.

7. Gerando texto

Chegou a parte mais esperada, vamos gerar a continuação de Game of Thrones!

Como eu disse acima, precisamos conceder um texto de entrada para a rede, portanto, será realizado diversos testes com diferentes textos.

Nosso algoritmo se encarrega de gerar a entrada de texto randômica.

import random
import sys
for epoch in range(1, 60):
print('epoch', epoch)
model.fit(x, y,
batch_size=512,
epochs=1)

# Seleciona um texto aleatório
start_index = random.randint(0, len(corpus_raw) - maxlen - 1)
generated_text = corpus_raw[start_index: start_index + maxlen]
print('--- Generating with seed: "' + generated_text + '"')

for temperature in [0.2, 0.5, 1.0, 1.2]:
print('------ temperature:', temperature)
sys.stdout.write(generated_text)

# Vamos gerar 400 caracteres
for i in range(400):
sampled = np.zeros((1, maxlen, len(chars)))
for t, char in enumerate(generated_text):
sampled[0, t, char_indices[char]] = 1.

preds = model.predict(sampled, verbose=0)[0]
next_index = sample(preds, temperature)
next_char = chars[next_index]

generated_text += next_char
generated_text = generated_text[1:]

sys.stdout.write(next_char)
sys.stdout.flush()

Para a primeira época, foi gerado o texto: with those golden eyes, sad and knowing. She had been dreaming, she realized. Lady was with her, and they were running together, and… and… trying”.

Obtemos os seguintes resultados de acordo com o valor da temperatura.

Temperatura = 0.2:

Temperatura = 0.5:

Temperatura = 1:

Temperatura = 1.2:

Durante o treino, o texto começa a ficar mais coerente e significativo, podemos perceber isso já em épocas maiores. O seguinte texto foi gerado na época 10: Ted from the tigers mouth. The mans eyes burst with soft popping sounds, and the brass around them began to run. The Dragon tore off a hunk of flesh, most of”.

Temperatura = 0.2:

Temperatura = 0.5:

Temperatura = 1:

Temperatura = 1.2:

O treinamento pode demorar, estou utilizando o Google Colab e cada época demora em média 50 minutos. Foram definidas 60 épocas, portanto, pode levar 2 dias para o treinamento completo.

8. Conclusão

Como você pode ter percebido, um valor de baixa temperatura resulta em um texto repetitivo e previsível, porém, com uma estrutura mais realista. Com temperaturas mais altas, o texto gerado se torna mais interessante, surpreendente e criativo. Com o valor da temperatura alta, a estrutura começa a quebrar e algumas palavras são inventadas pela rede. De acordo com os testes, 0.5 e 1.0 são os valores de temperatura mais interessantes para geração de texto . É importante experimentar diversas estratégias. Um texto perfeito fica entre a estrutura aprendida e a aleatoriedade, já que isso torna a geração relevante.

Algo interessante que pode ser implementado é gerar uma lista com os principais nomes dos personagens da série e alimentar a rede, quem sabe ela não tenha algo interessante para nos dizer sobre cada personagem…

A estrutura do algoritmo permite você realizar testes com outros livros, portanto, sinta-se a vontade.


Gostou do texto? Agora você já tem uma ideia básica de como Deep Learning pode ser usada para geração de conteúdo. Caso tenha alguma dúvida ou sugestão, deixe aqui nos comentários.

Até a próxima!

9. Referências

Deep Learning — Ian Goodfellow and Yoshua Bengio and Aaron Courville

Deep Learning with Python — François Chollet

Hands-On Machine Learning with Scikit-Learn & TensorFlow — Aurélien Géron