• Nenhum resultado encontrado

Construção do programa de geração de texto

4. Implementação

4.3 Construção do programa de geração de texto

Após estes esboços preliminares, que haviam provado que era possível a correlação em tempo real entre áudio e texto, componentes centrais deste trabalho, comecei a conceber a ferramenta no seu estado final, ou seja, a ferramenta de tal forma que funcione de forma estável e fiável mas, simultaneamente, que tenha incorporado todos os aspetos artísticos mencionados anteriormente.

O código completo está disponível no Anexo 2, devidamente formatado e comentado, no entanto passarei a analisar aqui as partes mais relevantes e que constituem a construção da componente de geração de texto usada na sua forma final. Este programa, no seguimento dos anteriores, foi construído em Processing. Para a sua construção recorri a um patch simples de Max/MSP que permitia controlar manualmente os inputs de OSC, unicamente para testes.

O programa inicia-se com a importação das librarias RiTA e oscP5 referentes respetivamente às ferramentas para recombinação de texto e à comunicação via OSC com o patch em Max/MSP. Posteriormente, faz-se a leitura e importação para duas strings dos dois ficheiros externos (em formato *.txt), os quais contêm os textos-fonte a serem combinados pelo programa. Pretende-se desta forma recorrer ao objeto RiMarkov para a geração do texto.

O objeto RiMarkov permite a geração de texto através do recurso a cadeias de Markov (também chamadas de n-grams). Ao ser aplicado às strings31 que possuem os textos, este objeto gera uma cadeia de Markov, ou seja, uma série de sequências probabilísticas dependentes da tokenização32 escolhida e também do grau (o valor de n) da cadeia de Markov, que representa a quantidade de tokens que o algoritmo analisa antes de saltar para a análise seguinte. Exemplificarei esquematicamente com uma frase simples e de uma maneira bastante generalista, sem grande profundidade matemática, mas de forma a que seja percetível a sua função nesta ferramenta. Considere-se a seguinte frase:

Gato preto, gato branco, gato vadio.

Aplicando a tokenização por palavras e considerando que o modelo também tokeniza o início e o final da frase, obtemos, para n=1 o seguinte esquema:

31 Tipo de variável usada em programação informática, nomeadamente para o armazenamento de dados. 32 Portuguesismo do termo em inglês tokenization é a divisão do texto em tokens, que podem ser cada

Fig. 4 – Esquema de probabilidades na aplicação de uma cadeia de Markov de grau 1

Começaríamos invariavelmente pela palavra “gato”, mas no estado seguinte teríamos três hipóteses, todas com a mesma probabilidade de ocorrência visto que ocorrem uma única vez neste exemplo. Iriamos então “saltar” entre estes três estados, voltar ao token “Gato” até que o estado gerado fosse o token “vadio”, onde teríamos invariavelmente o final da frase, visto que é o único estado que existe a seguir. No entanto, utilizando a mesma frase, com n=2 obteríamos o seguinte esquema:

Fig.5 – Esquema de probabilidades da aplicação de uma cadeia de Markov de grau 2

Ou seja, para n=2, como não temos repetições de grupos de duas palavras (tokens de duas palavras), neste exemplo tão pequeno, só conseguiríamos gerar sempre a mesma frase. Percebemos assim quais as implicações de graus mais altos nas cadeias de Markov aplicadas a textos muito grandes: começamos a obter maior similaridade com os textos originais. Temos também maior coerência gramatical, já que em textos maiores normalmente há sempre alguma probabilidade de, com um valor de n demasiado baixo, obtermos saltos erráticos entre dois estados que gramaticalmente não são coerentes mas que se verificam algures no corpo de texto, dado que a geração através da cadeia de Markov é, normalmente, dissociada do contexto gramatical.

No entanto a RiTa Library possui algum léxico pré-determinado na língua inglesa, ou seja, consegue distinguir entre uma vasta lista de verbos, substantivos e mesmo expressões completas em inglês, conferindo um nível de correção gramatical bastante alto mesmo em graus de Markov mais baixos (Howe, 2009).

Voltando ao modelo implementado, importadas ambas as fontes de texto cada uma para a sua string, é necessário combiná-las numa só string final, de forma a que possamos aplicar o cálculo

acima apresentado, recorrendo ao objecto RiMarkov já mencionado. No entanto, como pretendo ter controlo sobre o peso de cada texto original na mistura final, foi necessário aqui resolver ainda mais um passo. Sendo que estou a combinar os dois textos originais num só string, a forma mais simples e directa de conseguir controlar o peso de cada texto no resultado é multiplicar as vezes que cada texto é introduzido na string final. Ou seja, introduzindo na string final duas vezes o texto A e uma vez o texto B, significa que o conteúdo do texto A estará duas vezes mais presente no resultado, comparativamente ao texto B.

Usar o texto A na sua totalidade e somente metade do texto B iria resultar na mesma relação de 2:1, no entanto não estaria a usar o texto B na sua totalidade, o que seria extremamente prejudicial para o resultado final. Mais ainda, desta forma poderia obter tanta resolução de mistura quanto fosse necessária através da repetição incremental de ambos os textos na string final, ou seja, se introduzir 10 vezes qualquer combinação de textos consigo uma resolução de 10 níveis, se introduzir 100 vezes consigo 100 níveis de resolução e assim sucessivamente.

Posteriormente, foi necessário resolver ainda outro problema relativo ao controlo do grau da cadeia de Markov. Sendo que uma das características que queria explorar era a inteligibilidade do texto gerado, a forma mais interessante que encontrei de o fazer foi recorrendo à própria aplicação da cadeia de Markov. Quanto maior fosse o grau desta (o valor de n), maior seria a inteligibilidade e correção gramatical, bem como a similaridade com os textos originais. Pelo contrário, quanto mais baixo fosse o valor de n, mais inteligível e glitchy seria o texto resultante.

No entanto, quando recorremos ao RiMarkov, o grau da cadeia de Markov é aplicado aquando da geração das tabelas de probabilidades. Este é também o único momento em que o processamento é mais pesado, principalmente com quantidades grandes de texto visto que se estava agora a multiplicar a quantidade de texto inicial. O resultado fazia com que a geração de texto deixava de ser instantânea, pois ao mudar o grau, teria de ser calculada toda uma nova tabela de probabilidades. Pelo contrário, após criada a tabela, podemos gerar tanto texto quanto quisermos de forma instantânea, visto que só estamos a ler valores já previamente calculados. Assim, a solução encontrada foi gerar todos estes cálculos durante o arranque do sistema visto que assim podem ser feitos uma única vez, mais demorada, mas que acontece antes da performance em si e nunca durante.

Deste modo optei pelo seguinte esquema: obter sete níveis de mistura entre os dois textos, mais os dois estados referentes a somente o texto A e somente o texto B, ou seja, nove strings de texto diferentes de acordo com o seguinte diagrama:

Fig. 6 – Esquema da distribuição de textos nas strings

Estas nove strings seriam calculadas 3 vezes cada uma, uma vez para n=2, outra vez para n=3 e ainda outra para n=4, resultando assim em 27 strings de texto previamente calculados dos quais poderia gerar texto instantaneamente consoante a mistura e o grau da cadeia de Markov pretendidos.

Fig. 7 – Esquema das strings de texto para cada grau da cadeia de Markov

Estava assim implementado o sistema final de geração de texto. Este sistema teria agora de ser emparelhado com a componente de análise sonora de modo a criar a ferramenta final. Esta componente havia sido desenvolvida juntamente com a componente de texto, ainda que em modo de operação manual. Era então agora necessário automatizar a ferramenta e mapear os parâmetros de controlo conforme já descritos em 3.2.

4.4 Construção da componente de análise áudio e ferramenta final

Documentos relacionados