Search
Close this search box.
Chatbot simples e inteligente com spaCy
aprendiz artificial

Posts Relacionados:

Recentemente, fizemos um aplicativo que retorna a previsão do tempo com dados do INPE. Neste post, vamos expandi-lo com a inclusão de um chatbot com spaCy.

Receba nossa newsletter

Recentemente, fizemos um tutorial de um pequeno aplicativo escrito em Python. Ele aceita uma cidade como argumento de entrada e acessa o site do INPE para retornar a previsão do tempo. Nesse post, iremos expandir o código do post anterior para inserir um pequeno chatbot para interações linguísticas com usuários.

Esse chatbot é baseado numa estratégia híbrida. Ele tem regras pré-definidas para suas interações, mas também usa a biblioteca spaCy para auxiliá-lo na compreensão linguística.

O que são chatbots baseados em regras?

Chatbots baseados em regras são adequados para contextos bem específicos, onde o repertório de interações é pequeno. Eles funcionam mediante regras pré-determinadas que definem as respostas do bot para interações previamente programadas em seus códigos.

Do ponto de vista técnico, as vantagens desses chatbots são a sua simplicidade e facilidade de implementação. Eles não precisam passar por treinos exaustivos e nem têm requerimentos de hardwares como GPUs. No entanto, eles são limitados e só funcionam bem se forem desenhados com cuidado.

Uma alternativa viável para contornar essas limitações é o uso de estratégias híbridas. Neste post, o chatbot será desenvolvido combinando regras com algumas funcionalidades da spaCy.

Procedimentos

Iremos modificar o código anterior. Ele foi desenvolvido para receber uma cidade como input e retornar a previsão do tempo. Nesse post, ele será modificado para receber uma pergunta sobre o clima como input e retornar uma resposta adequada.

Pré-requisitos

Além das bibliotecas que foram instaladas no post anterior, iremos usar a biblioteca NumPy, spaCy e o modelo de português médio da spaCy. Se você tem o modelo pequeno usado neste post, ele não servirá para o post atual.

Os comandos para as instalações são mostrados abaixo. Recomendamos fortemente que você utilize um ambiente virtual:

				
					pip install numpy spacy
python -m spacy download pt_core_news_md
				
			

Vale lembrar que a spaCy é atualizada com muita frequência e é necessário que ela esteja instalada em seu computador com a versão compatível com o modelo de português que será usado.

Alterações iniciais

Para começar, vamos fazer as importações necessárias e testar. Qualquer problema aqui, verifique sua versão da spaCy, sua compatibilidade com o modelo de português, e verifique se todas as bibliotecas estão instaladas de fato. Se estiver tudo certo, a execução do código não irá retornar nada.

				
					from clima_codigos import clima_dic
from lxml import etree
import numpy as np
import requests
from unidecode import unidecode
import spacy
nlp = spacy.load("pt_core_news_md")
				
			

Em seguida, iremos modificar o código do post anterior. Essa é a função busca_clima como definida previamente. Todas as linhas marcadas serão mudadas para esse post.

				
					def busca_clima(nome_id):
    """busca previsão do tempo para um id"""
    url = f"http://servicos.cptec.inpe.br/XML/cidade/7dias/{nome_id}/previsao.xml"
    response = requests.get(url, stream=True)
    if response.status_code == 200:
        xml_data = response.content 
        print(xml_data)
        root = etree.fromstring(xml_data)
        dados = {}
        contador = 0
        for previsao in root.findall('previsao'):
            contador += 1
            dia = (previsao.find('dia').text)
            tempo = (previsao.find('tempo').text)
            prev = clima_dic[tempo]
            Tmax = (previsao.find('maxima').text)
            Tmin = (previsao.find('minima').text)
            iuv = (previsao.find('iuv').text)
            dados['dia '+str(contador)] = (dia, prev, Tmax, Tmin, iuv)
        pprint.pprint(dados)
    else: 
        print('Houve um problema com a sua solicitação. Por favor, confira e digite novamente.')
     
				
			

A versão nova da função está abaixo. No lugar do dicionário anterior, ela tem um NumPy ndarray. Ele facilitará a busca das respostas por nosso bot. Além dessa alteração, também foi feita a inclusão de retorno na função.

				
					def busca_clima(nome_id):
    """busca previsão do tempo para um id"""
    url = f"http://servicos.cptec.inpe.br/XML/cidade/7dias/{nome_id}/previsao.xml"
    response = requests.get(url, stream=True)
    if response.status_code == 200:
        xml_data = response.content 
        root = etree.fromstring(xml_data)
        dados = np.ndarray([7, 5], dtype=object)
        dados[0,0:5] = "dia", "prev", "Tmax", "Tmin", "IUV"
        contador = 0
        for previsao in root.findall('previsao'):
            contador += 1
            dados[contador,0] = 'dia '+str(contador)
            tempo = (previsao.find('tempo').text)
            dados[contador,1] = clima_dic[tempo]
            dados[contador,2] = (previsao.find('maxima').text)
            dados[contador,3] = (previsao.find('minima').text)
            dados[contador,4] = (previsao.find('iuv').text)
    else: 
        dados = 'Houve um problema com a sua solicitação. Por favor, confira e digite novamente.'
    return dados
				
			

Também iremos passar o chamado das funções para uma nova função chamada chatbot. Ela recebe uma questão feita por um usuário do nosso aplicativo como argumento.

Função chatbot

Na função chatbot, a spaCy é usada inicialmente para identificar se existe uma localização geográfica na pergunta feita por um usuário. Se uma localização for encontrada, as funções anteriores são chamadas (linhas em destaque). Em caso de falha, ela avisa o erro.

O código abaixo já tem o comando para o chamado da função chatbot com uma pergunta para teste (linhas 17 e 18). Digite o código, salve e execute o arquivo. Por enquanto, ele não deve retornar nada.

				
					

def chatbot(texto):
    """ Recebe uma pergunta de um usuário, inicia o processamento de linguagem e chama outras funções"""

    frase = nlp(texto)
    for token in frase.ents:
        if token.label_ == "GPE" or token.label_=="LOC": # GeoPolitical Entity
            cidade = token.text
            # se achar cidade busca codigo
            nome_id = busca_codigo(cidade)
            if type(nome_id)==int:
                dados = busca_clima(nome_id)
            else:
                'Houve um problema com a sua solicitação. Por favor, confira e digite novamente.'
        else:
            return 'Houve um problema com a sua solicitação. Por favor, confira e digite novamente.'

texto = "Vai ficar nublado em Fortaleza?"
chatbot(texto)

				
			

Na próxima etapa do código, com a previsão do INPE retornada, será preciso analisar qual foi a pergunta feita pelo usuário para que o chatbot responda de acordo. Essa análise será feita com outras duas funções. A primeira se chama acha_similaridade. Ela é chamada no código da função chatbot como mostrado abaixo na linha em destaque.

				
					

def chatbot(texto):
    """ Recebe uma pergunta de um usuário, inicia o processamento de linguagem e chama outras funções"""

    frase = nlp(texto)
    for token in frase.ents:
        if token.label_ == "GPE" or token.label_=="LOC": # GeoPolitical Entity
            cidade = token.text
            # se achar cidade busca codigo
            nome_id = busca_codigo(cidade)
            if type(nome_id)==int:
                dados = busca_clima(nome_id)
                acha_similaridade(dados, texto, cidade)
            else:
                'Houve um problema com a sua solicitação. Por favor, confira e digite novamente.'
        else:
            return 'Houve um problema com a sua solicitação. Por favor, confira e digite novamente.'

texto = "Vai ficar nublado em Fortaleza?"
chatbot(texto)

				
			

O método similarity em ação

A spaCy é uma biblioteca muito poderosa para processamento de linguagem natural, com várias funcionalidades excelentes para essa tarefa. Entre elas está o método similarity. Ele compara dois inputs de texto e verifica o nível de semelhança entre eles numa escala de 0 a 1.

Para nosso chatbot, a comparação é feita com algumas sentenças definidas diretamente no nosso aplicativo. Definimos 4 sentenças, mas elas podem facilmente ser expandidas posteriormente:

  • Como está o clima em {cidade}?
  • Vai chover em {cidade}?
  • Vai fazer sol em {cidade}?
  • Vai ficar nublado em {cidade}?

No código abaixo, nossa função é definida com 3 argumentos: os dados da previsão, o input de texto do usuário e o nome da cidade. A cidade é usada para dinamicamente completar as frases mostradas acima (linhas 6-9). Essas frases são inseridas numa lista chamada frases. Uma outra lista se chama similaridades e será usada para armazenar os valores de similaridade obtidos (linha 11). Também criamos uma lista chamada targets com palavras-chave (linha12).

Em seguida, utilizamos um loop para verificar a similaridade de cada uma das sentenças mostradas acima com o input de texto do usuário (linhas 13-14).

Posteriormente, identificamos qual similaridade é maior. A frase que tiver o maior índice de similaridade será considerada a correta, desde que seu valor seja acima de 0.6 (linha 15). Abaixo desse valor, a similaridade é pequena e é melhor fazer o chatbot responder que não entendeu a questão.

Se a maior similaridade é acima de 0.6, o código usa o índice da lista similaridades (linha 16) para selecionar a palavra-chave correspondente na lista target (linha 17). Essa palavra será usada para inspecionar a previsão do tempo do INPE para responder à pergunta inicial. A inspeção é feita com nossa terceira função chamada interpreta_similaridade. Ela é chamada no código abaixo na linha 18.

				
					

def acha_similaridade(dados, texto, cidade):
    """Calcula as similaridades entre um input do usuário com frases pré-definidas. 
    Retorna uma palavra-chave que reflete qual frase teve a máxima similaridade."""

    frases = []
    frases.append(nlp(f"Qual é a previsão do tempo em {cidade}?"))
    frases.append(nlp(f"Vai chover em {cidade}?"))
    frases.append(nlp(f"Vai fazer sol em {cidade}?"))
    frases.append(nlp(f"Vai ficar nublado em {cidade}?"))
    frase_target = nlp(texto)
    similaridades = []
    targets = ["Clima", "Chuva", "Sol", "Nublado"]
    for i in range(len(frases)):
        similaridades.append(frase_target.similarity(frases[i]))
    if max(similaridades) > 0.6:
        idx = similaridades.index(max(similaridades))
        palavra_alvo = targets[idx]
        interpreta_similaridade(dados, palavra_alvo)
    else:
        return 'Houve um problema com a sua solicitação. Por favor, confira e digite novamente.'

				
			

Interpretando a similaridade obtida

Nossa terceira função recebe a palavra-alvo (target) e os dados da previsão como argumentos e define qual resposta será retornada. Se a palavra-chave for Clima, ela imprime a previsão completa como foi feito no post anterior (linhas 4-5 abaixo). Mas, se a palavra for Chuva, Sol ou Nublado, a função inspecionará a previsão para responder corretamente à pergunta feita pelo usuário.

A inspeção é feita com um loop que percorre uma coluna do array com os dados da previsão (linhas 7-11).

É aqui que a vantagem do uso de um array do NumPy entra em ação. Nós já sabemos que a previsão está na coluna 1 do array (o primeiro índice do NumPy é 0). Iremos utilizar o método find.char() para buscar a palavra-alvo no array. Cada vez que ela for encontrada (linhas 9-11), anotaremos seu índice (idx) numa lista chamada idxs. Note que é preciso garantir que o valor de idx é diferente de -1, pois esse é o valor que o Python retorna quando a string não é localizada (linha 10).

Por fim, utilizaremos o tamanho da lista para responder à pergunta do usuário do aplicativo (linhas 13-16). Se o tamanho for maior do que 0, a condição perguntada foi localizada na previsão. Nesse caso, o bot responderá de maneira afirmativa à pergunta que foi feita. Caso contrário, ele informara que a condição perguntada não está prevista para os próximos dias.

Veja o código abaixo da função completa.

				
					

def interpreta_similaridade(dados, target):
    """Usa uma palavra-chave para rastrear nos dados a informação adequada para definir resposta."""
    
    if target == "Clima":
        print(dados)
    else:
        idxs = []
        for i in range(dados.shape[0]):
            idx = np.char.find(dados[i,1], target)
            if idx != -1:
                idxs.append(i)
        
        if len(idxs) > 0:
            print(f"Sim, existe probabilidade de {target}.")
        else:
            print(f"Não, a previsão para os próximos dias nao inclui {target}.")
				
			

Agora é só testar com vários exemplos. Nosso código completo é mostrado abaixo com um exemplo de pergunta.

				
					
from clima_codigos import clima_dic
from lxml import etree
import numpy as np
import requests
from unidecode import unidecode
import spacy
nlp = spacy.load("pt_core_news_md")
 
def busca_codigo(cidade):
    """busca id"""
    cidade = cidade.lower()
    cidade = unidecode(cidade)
    url = f"http://servicos.cptec.inpe.br/XML/listaCidades?city={cidade}"
    response = requests.get(url)
    if response.status_code == 200: 
        xml_data = response.content 
        texto = etree.fromstring(xml_data) 
        if texto.find('cidade') == None: 
            return 'Houve um problema com sua solicitação. Por favor, confira e digite novamente.' 
        else: 
            nome_id = int(texto.find('cidade').find('id').text) 
            return nome_id
    else: 
        return 'Houve um problema com sua solicitação. Por favor, confira e digite novamente.'

def busca_clima(nome_id):
    """busca previsão do tempo para um id"""
    url = f"http://servicos.cptec.inpe.br/XML/cidade/7dias/{nome_id}/previsao.xml"
    response = requests.get(url, stream=True)
    if response.status_code == 200:
        xml_data = response.content 
        root = etree.fromstring(xml_data)
        dados = np.ndarray([7, 5], dtype=object)
        dados[0,0:5] = "dia", "prev", "Tmax", "Tmin", "IUV"
        contador = 0
        for previsao in root.findall('previsao'):
            contador += 1
            dados[contador,0] = 'dia '+str(contador)
            tempo = (previsao.find('tempo').text)
            dados[contador,1] = clima_dic[tempo]
            dados[contador,2] = (previsao.find('maxima').text)
            dados[contador,3] = (previsao.find('minima').text)
            dados[contador,4] = (previsao.find('iuv').text)
    else: 
        dados = 'Houve um problema com a sua solicitação. Por favor, confira e digite novamente.'
    return dados
     
def chatbot(texto):
    """ Recebe uma pergunta de um usuário, inicia o processamento de linguagem e chama outras funções"""
    frase = nlp(texto)
    for token in frase.ents:
        if token.label_ == "GPE" or token.label_=="LOC": 
            cidade = token.text
            nome_id = busca_codigo(cidade)
            if type(nome_id)==int:
                dados = busca_clima(nome_id)
                acha_similaridade(dados, texto, cidade)
            else:
                'Houve um problema com a sua solicitação. Por favor, confira e digite novamente.'
        else:
            return 'Houve um problema com a sua solicitação. Por favor, confira e digite novamente.'

def acha_similaridade(dados, texto, cidade):
    """Calcula as similaridades entre um input do usuário com frases pré-definidas. 
    Retorna uma palavra-chave que reflete qual frase teve a máxima similaridade."""
    frases = []
    frases.append(nlp(f"Qual é a previsão do tempo em {cidade}?"))
    frases.append(nlp(f"Vai chover em {cidade}?"))
    frases.append(nlp(f"Vai fazer sol em {cidade}?"))
    frases.append(nlp(f"Vai ficar nublado em {cidade}?"))
    frase_target = nlp(texto)
    similaridades = []
    targets = ["Clima", "Chuva", "Sol", "Nublado"]
    for i in range(len(frases)):
        similaridades.append(frase_target.similarity(frases[i]))
    if max(similaridades) > 0.6:
        idx = similaridades.index(max(similaridades))
        palavra_alvo = targets[idx]
        interpreta_similaridade(dados, palavra_alvo)
    else:
        return 'Houve um problema com a sua solicitação. Por favor, confira e digite novamente.'

def interpreta_similaridade(dados, target):
    """Usa uma palavra-chave para rastrear nos dados a informação adequada para definir resposta."""
    if target == "Clima":
        print(dados)
    else:
        idxs = []
        for i in range(dados.shape[0]):
            idx = np.char.find(dados[i,1], target)
            if idx != -1:
                idxs.append(i)
        if len(idxs) > 0:
            print(f"Sim, existe probabilidade de {target}.")
        else:
            print(f"Não, a previsão para os próximos dias não inclui {target}.")

texto = "Vai fazer sol em Fortaleza?"
chatbot(texto)
				
			

E esse foi o resultado que obtivemos.

				
					
Não, a previsão para os próximos dias não inclui Sol.
				
			

Conclusões

Nesse post, apresentamos um chatbot baseado em regras e turbinado com spaCy. Ele é simples e de escopo limitado, mas nem por isso deve ser descartado para aplicações específicas. Bibliotecas como spaCy facilitam muito a implementação deste tipo de bot e o que mostramos pode ser facilmente expandido. Por exemplo, o método lemmatizer seria uma adição bem interessante para um chatbot como esse. Mas isso é uma expansão que deixaremos para quem se interessar em explorar mais as aplicações da spaCy.

A inspiração para esse post veio daqui.

Imagem com IA Generativa – Dia 274

IA generativa img 274

Arte com IA generativa: imagem do dia

Todos os dias, postamos um exemplo de imagem artística gerada com inteligência artificial.

Tutoriais

Postagens Mais Recentes

Outras Postagens Que Podem Interessar

Veja
Mais

Fique em contato

Se inscreva para receber nossa newsletter com novidades.

aprendiz artificial