Inteligência Artificial com Lógica Fuzzy : Análise Prática de problema multivariado - Método do centroide
Prezado(a) Leitor(a), meu nome é Sheila atualmente me especializo na área de Ciência de Dados e tenho estudado com afinco Inteligência artificial para decisões no mercado de ações. Fiz vários algoritmos para melhorar minha técnica, pois pretendo me tornar projetistas desse tipo de sistema. Escrevo este artigo técnico na intenção de ajudar quem está começando e a também a fixar conceitos práticos trocando informações aqui no Linkedin com profissionais da área.
O código que eu analisei e reescrevi algumas partes eu retirei do próprio Scikit-Fuzzy. Considerei interessante trazer um código pronto apenas para mostrar a base e os conceitos que eu uso nos meus algoritmos de decisões para selecionar ações da bolsa de valores.
A Lógica, também conhecida como lógica difusa, é um ramo da lógica que lida com a incerteza e a imprecisão na tomada de decisões. Ao contrário da lógica tradicional, que opera com valores binários (verdadeiro ou falso, 0 ou 1), essa lógica permite a representação de valores intermediários entre verdadeiro e falso. É uma metodologia baseada na ideia de que a "veracidade" de algo pode ser expresso ao longo de um continuum. Isso significa que algo não é verdadeiro que algo não é falso, mas parcialmente verdadeiro ou parcialmente falso.
Um dos diferenciais da Lógica é poder fazer associações de resultado numérico, matemático com informação linguística. Isso significa que facilita associar um conhecimento objetivo com um conhecimento subjetivo. Um exemplo de conhecimento objetivo são as equações, modelos matemáticos onde são identificadas as variáveis e os parâmetros. Logo, conhecimento subjetivo é adquirido através da vivência, observação, técnica intuitiva o que se torna complexo quantificar através da matemática tradicional.
Uma variável difusa tem um valor que assume algum número sobre um domínio pré-definido que chamamos de universo. O valor é como quantificamos a variável usando a matemática tradicional. Um exemplo, a minha variável difusa era dar gorjeta para um garçom, o universo seria de 0% a 25% e poderia assumir uma valor médio de 15%. Uma variável fuzzy também tem vários termos que são usados para descreve-la . Esses termos geralmente são adjetivos como "baixo", "médio", "alto", "ruim", "bom", "excelente". Cada termo tem a função de associação como o valor para o termo em escala de 0 a 1.
Um sistema de controle difuso usa um conjunto de regras para mostrar como uma ou mais variáveis se relacionam. Estes são expressos na forma de declaração IF-THEN, a parte SE é chamada de antecedente e a parte ENTÃO de consequente.
O PROBLEMA DA GORJETA - CAMINHO DIFÍCIL - COMENTADO
O algoritmo para este artigo técnico eu retirei do próprio pacote do Scikit-Fuzzy.
O problema da gorjeta é o seguinte:
Quero montar um sistema de gorjeta no meu restaurante, cuja a porcentagem da gorjeta dependerá da nota que a qualidade da comida receber e a nota que o atendimento receber. A porcentagem a ser acrescentada na conta do cliente é de 0% a 25%. Vamos analisar passo a passo do algoritmo.
O comando !pip install scikit-fuzzy é utilizado para instalar a biblioteca scikit-fuzzy em seu ambiente Python. Scikit-fuzzy é uma biblioteca para lógica difusa (fuzzy logic) que fornece ferramentas para modelagem e análise de sistemas baseados em lógica fuzzy. Por isso que para começar digitamos o código a seguir:
!pip install scikit-fuzzy
Coloquei para rodar o código acima. O segundo passo é instalar mais três tipos de bibliotecas para conseguirmos gerar a solução em um gráfico com a porcentagem da gorjeta.
import numpy as np
import skfuzzy as fuzz
import matplotlib.pyplot as plt
Agora vamos gerar as variáveis de ENTRADA (ANTECEDENTE) :
Há muitas variáveis que influenciam na decisão da porcentagem da gorjeta, como:
Salientando que a nota do serviço é de 0 a 10. Pode ser números inteiros ou decimais.
Variável de SAÍDA (CONSEQUENTE) :
A variável de saída é a porcentagem que será adicionada na conta, porém em porcentagem.
O valor da gorjeta deve ser apresentado em porcentagem. No intervalo de 0% a 25%.
Dessa forma o código ficou da seguinte forma:
# Gerar variáveis universais
# * Qualidade e serviço em intervalos subjetivos de [0, 10]
# * A gorjeta tem um intervalo de [0, 25] em unidades de pontos percentuais
x_qual = np.arange(0, 11, 1)
x_serv = np.arange(0, 11, 1)
x_gorj = np.arange(0, 26, 1)
As linhas do código a seguir estão gerando funções de pertinência difusa para três variáveis: qualidade, serviço e gorjeta. As funções de pertinência difusa são uma parte fundamental da lógica difusa, que permite modelar a incerteza e a imprecisão nos sistemas.
# Gerar funções de pertinência difusa
qual_lo = fuzz.trimf(x_qual, [0, 0, 5])
qual_md = fuzz.trimf(x_qual, [0, 5, 10])
qual_hi = fuzz.trimf(x_qual, [5, 10, 10])
serv_lo = fuzz.trimf(x_serv, [0, 0, 5])
serv_md = fuzz.trimf(x_serv, [0, 5, 10])
serv_hi = fuzz.trimf(x_serv, [5, 10, 10])
gorj_lo = fuzz.trimf(x_gorj, [0, 0, 13])
gorj_md = fuzz.trimf(x_gorj, [0, 13, 25])
gorj_hi = fuzz.trimf(x_gorj, [13, 25, 25])
Vou explicar cada linha em detalhes:
O código a seguir cria uma visualização dos universos e funções de pertinência difusa associadas a eles para as variáveis 'Qualidade da comida', 'Qualidade do serviço' e 'Valor da gorjeta', organizados em três subplots. Ele também remove os eixos superior e direito de cada subplot para uma apresentação mais limpa.
fig, (ax0, ax1, ax2) = plt.subplots(nrows=3, figsize=(8, 9))
ax0.plot(x_qual, qual_lo, 'b', linewidth=1.5, label='Ruim')
ax0.plot(x_qual, qual_md, 'g', linewidth=1.5, label='Decente')
ax0.plot(x_qual, qual_hi, 'r', linewidth=1.5, label='Excelente')
ax0.set_title('Qualidade da comida')
ax0.legend()
ax1.plot(x_serv, serv_lo, 'b', linewidth=1.5, label='Pobre')
ax1.plot(x_serv, serv_md, 'g', linewidth=1.5, label='Aceitável')
ax1.plot(x_serv, serv_hi, 'r', linewidth=1.5, label='Incrível')
ax1.set_title('Qualidade do serviço')
ax1.legend()
ax2.plot(x_gorj, gorj_lo, 'b', linewidth=1.5, label='Baixa')
ax2.plot(x_gorj, gorj_md, 'g', linewidth=1.5, label='Média')
ax2.plot(x_gorj, gorj_hi, 'r', linewidth=1.5, label='Alta')
ax2.set_title('Valor da gorjeta')
ax2.legend()
for ax in (ax0, ax1, ax2):
ax.spines['top'].set_visible(False)
ax.spines['right'].set_visible(False)
ax.get_xaxis().tick_bottom()
ax.get_yaxis().tick_left()
plt.tight_layout()
Segue a explicação linha por linha:
Recomendados pelo LinkedIn
A saída desse código ficou da seguinte forma:
REGRAS NEBULOSAS
Nesta etapa definimos a relação difusa entre as variáveis de entrada e saída.
APLICAÇÃO DA REGRA
Vamos supor que "Qualidade da comida" recebeu nota 6,5 e a nota na "Qualidade do serviço" foi 9,8. A pergunta é qual o valor da gorjeta?
qual_level_lo = fuzz.interp_membership(x_qual, qual_lo, 6.5)
qual_level_md = fuzz.interp_membership(x_qual, qual_md, 6.5)
qual_level_hi = fuzz.interp_membership(x_qual, qual_hi, 6.5)
serv_level_lo = fuzz.interp_membership(x_serv, serv_lo, 9.8)
serv_level_md = fuzz.interp_membership(x_serv, serv_md, 9.8)
serv_level_hi = fuzz.interp_membership(x_serv, serv_hi, 9.8)
active_rule1 = np.fmax(qual_level_lo, serv_level_lo)
gorj_activation_lo = np.fmin(active_rule1, gorj_lo)
gorj_activation_md = np.fmin(serv_level_md, gorj_md)
active_rule3 = np.fmax(qual_level_hi, serv_level_hi)
gorj_activation_hi = np.fmin(active_rule3, gorj_hi)
gorj0 = np.zeros_like(x_gorj)
fig, ax0 = plt.subplots(figsize=(8, 3))
ax0.fill_between(x_gorj, gorj0, gorj_activation_lo, facecolor='b', alpha=0.7)
ax0.plot(x_gorj, gorj_lo, 'b', linewidth=0.5, linestyle='--', )
ax0.fill_between(x_gorj, gorj0, gorj_activation_md, facecolor='g', alpha=0.7)
ax0.plot(x_gorj, gorj_md, 'g', linewidth=0.5, linestyle='--')
ax0.fill_between(x_gorj, gorj0, gorj_activation_hi, facecolor='r', alpha=0.7)
ax0.plot(x_gorj, gorj_hi, 'r', linewidth=0.5, linestyle='--')
ax0.set_title( 'Atividade de pertinência de saída')
for ax in (ax0,):
ax.spines['top'].set_visible(False)
ax.spines['right'].set_visible(False)
ax.get_xaxis().tick_bottom()
ax.get_yaxis().tick_left()
plt.tight_layout()
Este trecho de código faz parte de um sistema de inferência difusa, onde estão sendo calculados os níveis de pertinência para os conjuntos difusos de entrada e aplicadas as regras de inferência para determinar a ativação dos conjuntos difusos de saída. Aqui está uma explicação linha por linha:
A saída do código acima ficou da seguinte forma:
Agregação de regras
Com a atividade de cada função de associação de saída conhecida as funções de associação devem ser combinadas. Isso normalmente é feito usando um operador máximo. Essa etapa também é conhecida como agregação.
Defuzzificação
A defuzzificação envolve a aplicação de um método para transformar a distribuição de pertinência dos conjuntos fuzzy em um valor ou conjunto de valores específicos que representam uma decisão ou ação a ser tomada. Existem vários métodos de defuzzificação, como o centro de gravidade, o método do máximo, o método médio dos máximos, entre outros. Cada método tem suas próprias características e é escolhido com base nas necessidades específicas do sistema fuzzy e da aplicação em questão.
Finalmente, para obter uma resposta do mundo real, voltamos à lógica nítida do mundo de funções de associação difusa. Para os fins deste exemplo: Será utilizado o método centroide.
aggregated = np.fmax(gorj_activation_lo,
np.fmax(gorj_activation_md, gorj_activation_hi))
gorj = fuzz.defuzz(x_gorj, aggregated, 'centroid')
gorj_activation = fuzz.interp_membership(x_gorj, aggregated, gorj) # for plot
fig, ax0 = plt.subplots(figsize=(8, 3))
ax0.plot(x_gorj, gorj_lo, 'b', linewidth=0.5, linestyle='--', )
ax0.plot(x_gorj, gorj_md, 'g', linewidth=0.5, linestyle='--')
ax0.plot(x_gorj, gorj_hi, 'r', linewidth=0.5, linestyle='--')
ax0.fill_between(x_gorj, gorj0, aggregated, facecolor='Orange', alpha=0.7)
ax0.plot([gorj, gorj], [0, gorj_activation], 'k', linewidth=1.5, alpha=0.9)
ax0.set_title('"Agregação de pertinência e resultado"')
for ax in (ax0,):
ax.spines['top'].set_visible(False)
ax.spines['right'].set_visible(False)
ax.get_xaxis().tick_bottom()
ax.get_yaxis().tick_left()
plt.tight_layout()
Segue a explição:
O método do centroide é comumente usado na defuzzificação em sistemas de lógica fuzzy devido à sua simplicidade. Ele calcula o valor nítido (ou valor defuzzificado) como o centroide da área sob a curva de pertinência agregada. Este método é intuitivo e fácil de entender, pois representa o "centro de gravidade" da distribuição de pertinência.
Quando aplicado ao contexto de determinar a gorjeta em um restaurante, o valor defuzzificado calculado pelo método do centroide seria o valor que representa o "centro de massa" da área de pertinência agregada. Isso significa que, em termos práticos, o valor da gorjeta escolhido seria uma média ponderada das diferentes opções de gorjeta, onde as opções com maior pertinência (representadas pelas alturas das curvas de pertinência) teriam mais peso na determinação do valor final.
Além da simplicidade da interpretação ser fácil, o método do centroide é computacionalmente eficiente e amplamente utilizado em aplicações de lógica fuzzy devido à sua capacidade de fornecer resultados satisfatórios na maioria dos casos. No entanto, é importante notar que em alguns contextos específicos ou para sistemas mais complexos, outros métodos de defuzzificação podem ser mais apropriados.
A minha intenção aqui não é dizer se o resultado foi satisfatório ou não, mas a técnica para a resolução do problema, a lógica para montar o problema.
Espero ter despertado em você caro leitor a vontade de saber mais sobre a Lógica Fuzzy.
Segue o link do código completo no Google Colab: