Ao longo dos últimos meses, tenho trabalhado num novo projeto nas horas vagas: Alda, uma assistente virtual em Python, com síntese de voz e reconhecimento de fala. Inclusive, foi durante o desenvolvimento da Alda que eu escrevi os posts sobre síntese de voz com Python, e reconhecimento de fala com Python e Vosk.
Na verdade, pelo menos até o momento, dizer que a Alda é um assistente virtual é um pouco de exagero, já que ela não ajuda você em muita coisa. Recentemente, eu acrescentei uma funcionalidade pra que ela realize operações aritméticas básicas, o que permite que ela responda perguntas como “Quanto é cinco mais dois?” ou “Quanto é quarenta vezes cem?”. E eu ainda pretendo adicionar mais algumas funcionalidades pra torná-la mais “útil”.
ELIZA, chatbots e a bendita língua portuguesa
Do jeito que o projeto se encontra no momento, a Alda seria melhor classificada com um chatbot. De fato, a lógica que eu utilizei para processar a entrada do usuário e elaborar uma resposta da Alda, através de palavras chave, correspondência de padrões e etc. foram diretamente inspirados na ELIZA, do Weizenbaum, o primeiro chatbot da história da computação.
O mecanismo básico de funcionamento da ELIZA é o seguinte: o programa procura por palavras-chave no texto de entrada, e caso encontre uma, ele vê se essa palavra bate com algum padrão especificado em um script. Em caso positivo, ele escolhe uma regra de “reescritura”, para elaborar uma resposta.
Colocando mais concretamente: suponha que no script do chatbot exista a palavra-chave GOSTO. Essa palavra-chave está associada a uma coleção de padrões, que são como formas, nas quais o programa tenta encaixar a frase dita pelo usuário. Um exemplo de padrão, seria 1 GOSTO DE 2 (os números 1 e 2 servem para fazer referência às palavras na string de entrada). Associado a esse padrão existe uma coleção de “regras de reescritura”, que são outros tipos de “formas”, utilizadas para produzir a resposta do programa. Um exemplo desse tipo de regra seria EU NÃO GOSTO NEM UM POUCO DE 2..
Então, se o usuário disser EU GOSTO DE BOLO DE CHOCOLATE, o programa detecta a palavra-chave BOLO, e percebe que a frase se encaixa no padrão 1 GOSTO DE 2, com 1 = EU e 2 = BOLO DE CHOCOLATE. Então ele utiliza a regra EU NÃO GOSTO NEM UM POUCO DE 2, para produzir a sentença EU NÃO GOSTO NEM UM POUCO DE BOLO DE CHOCOLATE.
Como o programa é capaz de fazer referência ao que acabou de ser dito em frases que (se tudo der certo) fazem sentido, o usuário fica com a impressão de que o programa realmente é dotado de algum tipo de inteligência (quando na verdade ele não tem inteligência nenhuma).
Um dos problemas de utilizar o mecanismo de padrões e regras da ELIZA em português é que como a língua portuguesa tem uma estrutura morfológica mais complexa que a da língua inglesa, isso limita bastante as possibilidades de regras de reescritura.
Por exemplo, em inglês, poderíamos criar o padrão I 1 YOU (Eu X você), e a regra ARE YOU SHURE YOU 1 ME? (Você tem certeza que você me *?). De modo que se usuário dissesse I LOVE YOU (Eu amo você), o programa responderia ARE YOU SHURE YOU LOVE ME? (Você tem certeza que me ama?). Já em português, isso não daria certo, porque se o usuário dissesse EU AMO VOCÊ, o programa responderia VOCÊ TEM CERTEZA QUE ME AMO? (perceba a falta de flexão do verbo).
Esse problema é muito menos trivial do que pode parecer num primeiro momento. Pra tentar resolver o problema da flexão verbal, por exemplo, seria necessário usar técnicas de processamento de linguagem natural para detectar a presença de verbos e conjugá-los adequadamente. Mas existem vários outros problemas relacionados à língua. Por exemplo, ao invés de EU AMO VOCÊ, o usuário poderia dizer EU TE AMO, TE AMO, AMO-TE, EU AMO A TI, etc. Mas, por enquanto, eu estou me limitando a estudar uma solução apenas para a questão dos verbos.
Estrutura do programa
Eu me esforcei bastante pra garantir uma boa modularidade do código. O programa tem três módulos principais: síntese de fala, reconhecimento de fala e “raciocínio”. Cada módulo pode ser substituído ou modificado, contanto que mantenha a mesma interface para comunicação com os outros módulos.
O programa principal faz o meio de campo entre esses três módulos. Primeiro, ele coleta a entrada de voz do usuário e envia o áudio para o módulo de reconhecimento de fala. Então ele recebe o texto correspondente ao áudio, envia o texto para o módulo “pensador”, e recebe de volta o texto de resposta, que ele envia para o módulo de síntese de voz. E esse ciclo se repete ad infinitum.
O reconhecimento de fala é feito com Vosk, uma biblioteca que tem dois pontos positivos: usa modelos bem leves e não faz uso de nenhuma API online. Por outro lado, ele precisa de uma certa “calibragem” para obter resultados satisfatórios.
Em relação à síntese de voz, o primeiro protótipo da Alda (que se chamava Geraldo) usava pyttsx3, que no Linux utiliza a engine espeak. Mas essa engine usa a técnica de síntese de formantes (eu falo um pouco sobre isso no texto que linquei no início desse post), e eu queria utilizar vozes humanas no projeto. Por isso optei pela biblioteca voxpopuli, que usa o espeak apenas para converter texto em fonemas e depois utiliza o MBROLA para fazer a síntese de voz. O resultado é bem legal.
Pra a captação de som, usei o sounddevice, do mesmo modo que expliquei no post sobre reconhecimento de voz, e no programa principal, usei o colorama pra acrescentar cores no terminal, só pra deixar o programa mais bonitinho.
A Alda, de um certo modo, é como uma framework, para a qual você pode desenvolver seus próprios chatbots, bastando para isso criar seus scripts seguindo o protocolo esperado pelo módulo de raciocínio (eu ainda não escrevi nenhuma documentação sobre isso, simplesmente porque acho que ninguém vai ler). Atualmente, o projeto conta com duas “engines de raciocínio” (o módulo responsável por pegar uma fala do usuário e elaborar uma resposta). O mais simples é basicamente uma reimplementação da ELIZA em Python.
Já o segundo módulo, que foi desenvolvido a partir do primeiro e no qual tenho trabalhado nos últimos tempos, permite que determinados padrões reconhecidos no texto disparem funções que utilizam elementos do texto como argumentos.
Por exemplo, o padrão 1 QUANTO É 2 3(MAIS/MENOS/VEZES/DIVIDO POR) 4 “bate” com qualquer frase do tipo “Quanto é vinte vezes cem”, “Quanto é quarenta e nove dividido por sete”, etc. E quando o padrão é detectado, ele dispara uma função para realizar operações aritméticas que recebe os argumentos 2 (o primeiro operando),3 (o operador) e 4 (o segundo operando). (Nota: se isso pareceu confuso, releia o quinto parágrafo do post pra entender melhor!).
Tudo isso é definido no próprio script, sem a necessidade de modificar nada no módulo propriamente dito. Num futuro próximo, eu pretendo desenvolver mais algumas coisas simples, como dizer as horas, a data, etc.
É claro que tudo isso é muito bonito em tese, mas na prática, um programa estruturado de forma tão isolada também tem suas desvantagens. A pior delas, na minha opinião, é que todo o resto do programa depende do funcionamento adequado do módulo de reconhecimento de voz, que por sua vez, mesmo “calibrado” (retreinado com um corpus textual adequado ao domínio da aplicação) ainda tem uma taxa de erro significativa.
Num sistema integrado, o módulo de reconhecimento de voz poderia enviar uma lista de hipóteses sobre o que foi dito pelo usuário, para ser analisada por um outro módulo que testasse a plausibilidade de cada hipótese, e que, no pior dos casos tentasse converter uma entrada truncada em algo pelo menos gramaticalmente correto.
No vídeo abaixo, por exemplo, existe um momento em que eu pergunto “Você foi… irônica?” e a Alda entende “Você foi e onde fica” e por conta disso, responde “Talvez eu seja mesmo foi e onde fica”. Então por mais que todo o resto do programa funcione adequadamente, em alguns momentos, parece que estamos conversando com uma pessoa sob efeito de drogas e com problemas auditivos.
Pra resolver esse tipo de questão, eu precisaria ou de uma biblioteca com maior precisão, ou então desenvolver meu próprio módulo de reconhecimento de fala (o que ainda não sou capaz de fazer!). Por enquanto, tenho tentado resolver outros aspectos do programa, e, de todo modo, a Alda já é capaz de render alguns minutos de entretenimento.
O código-fonte da Alda se encontra no meu github, com instruções para botar tudo pra funcionar (em inglês!). E o código está razoavelmente bem comentado (em inglês), caso você queira dar uma olhada!
One thought on “Alda: uma assistente virtual em Python”