UNIVERSIDADE ESTADUAL DE MARINGÁ
DEPARTAMENTO DE INFORMÁTICA
Bacharelado em Ciência da Computação
Redes Bayesianas - Loan Approval Prediction
Alunos: Rafael Ruiz Schlichting, Gabriel Oliveira Gomes
Ra: 124810, 125154
Maringá - PR
2024
Universidade Estadual de Maringá – Departamento de Informática
Av. Colombo, 5790 – Bloco C56, Zona 7 – CEP: 87020-900
Fone: (44) 3011-4324/3011-4219
Maringá – PR
UNIVERSIDADE ESTADUAL DE MARINGÁ
DEPARTAMENTO DE INFORMÁTICA
Bacharelado em Ciência da Computação
1. Descrição do Problema
O objetivo deste modelo é prever a probabilidade de aprovação de um
empréstimo com base em variáveis financeiras e pessoais dos solicitantes. No
contexto de instituições financeiras, é comum avaliar uma série de fatores que
podem influenciar na decisão de aprovação ou não de um pedido de empréstimo.
Utilizando uma rede bayesiana, é possível modelar as relações entre
variáveis-chave, considerando a influência probabilística de cada uma delas na
decisão final.
A rede bayesiana proposta (baseada nos dados .csv ‘Loan-Approval-
Prediction-Dataset’ do usuário architsharma01¹) permitirá combinar todas essas
variáveis para calcular a probabilidade de aprovação de um empréstimo, dado um
conjunto específico de características do solicitante.
2. Descrição do Dataset
A seguir, a descrição de cada variável do dataset juntamente com suas
relações de dependência:
● education: Define se o solicitante é Graduado ou não.
● self_employed: Define se o solicitante tem é Empregado ou Autônomo.
● no_of_dependents: Define o número de dependentes que o solicitante tem.
● loan_amount: Define o valor do empréstimo e se ele é considerado:
baixo, médio ou alto.
● luxury_assets_value: Define o valor dos bens de luxo(imóveis,
veículos, joias, etc) que o solicitante possui e se ele é considerado:
baixo, médio ou alto.
● bank_asset_value: Define o valor dos ativos bancários do solicitante e
se ele é considerado: baixo, médio ou alto.
● loan_term: Define a taxa de juros do empréstimo e se ela é
considerada: baixa, média ou alta.
● commercial_assets_value: Define o valor dos ativos comerciais que o
solicitante ou sua empresa possuem e se ele é considerado:
insolvente, baixo, médio ou alto.
● residential_assets_value: Define o valor dos ativos residenciais do
solicitante e se ele é considerado: insolvente, baixo, médio ou alto.
● cibil_score²: Define a pontuação de crédito do solicitante e se ela é
considerada: baixa, média ou alta.
● income_annum: Define a renda anual do solicitante e se ela é
considerada: baixa, média ou alta.
Universidade Estadual de Maringá – Departamento de Informática
Av. Colombo, 5790 – Bloco C56, Zona 7 – CEP: 87020-900
Fone: (44) 3011-4324/3011-4219
Maringá – PR
UNIVERSIDADE ESTADUAL DE MARINGÁ
DEPARTAMENTO DE INFORMÁTICA
Bacharelado em Ciência da Computação
● cibil_score → loan_term, loan_amout: O quanto de confiança que um
banco possui com seu cliente afeta diretamente a quantidade de dinheiro que
se pode pegar emprestado. E também, o quanto os juros podem ser
reduzidos/aumentados para esse cliente mais/menos confiável.
● income_annum → loan_amout: Pessoas com maior renda anual tendem a
poder pegar mais dinheiro de empréstimo com o banco.
● self_employed → bank_asset_value: Pessoas autônomas têm a tendência
de possuírem maior valor bancário.
● education → cibil_score: Pessoas graduadas têm a tendência de possuir
maior score de confiança bancária.
● no_of_dependents → income_annum: Devido a necessidade de sustentar
mais de uma pessoa, pessoas com dependentes tendem a precisar de uma
maior renda anual.
● [...] -> loan_status: Todas são variáveis que afetam (em maior ou menor
grau) se um cliente irá pagar ou não o empréstimo (com testes de precisão,
foi descoberto que cibil_score e no_of_dependents são duas variáveis
importantes para determinar loan_status).
Universidade Estadual de Maringá – Departamento de Informática
Av. Colombo, 5790 – Bloco C56, Zona 7 – CEP: 87020-900
Fone: (44) 3011-4324/3011-4219
Maringá – PR
UNIVERSIDADE ESTADUAL DE MARINGÁ
DEPARTAMENTO DE INFORMÁTICA
Bacharelado em Ciência da Computação
3. Estrutura da Rede
Antes de explicar a estrutura da rede, é importante conhecer as funções basilares:
def makeTheContinuosVariable(name, bins, labels, hasEvidence=[]):
brute = [Link](data[name], bins=bins, labels=labels, right=False)
return {
'name': name,
'labels': labels,
'card': len(labels),
'brute': brute,
'values': [[x] for x in brute.value_counts(normalize=True).sort_index().values],
'checker': lambda x: [Link]([x], bins=bins, labels=labels, right=False)[0],
'hasEvidence': hasEvidence
}
def makeTheDiscreteVariable(name, labels, hasEvidence=[]):
return {
'name': name,
'labels': labels,
'card': len(labels),
'brute': data[name],
'values': [[x] for x in data[name].value_counts(normalize=True).values],
'checker': lambda x: x,
'hasEvidence': hasEvidence
}
def substituteZerosByMean(values, mean_pair):
zeroArray = [0 for _ in range(len(values[0]))]
return [Link]([mean_pair if (x == zeroArray).all() else x for x in values]).[Link]()
def myCrosstab(evidence, brutes, mean_pair, toUseChecker):
col = data[evidence] if not toUseChecker else [my_model_data[evidence]['checker'](x) for x in data[evidence]]
table = [Link](
index=brutes,
columns=col,
dropna=False,
)
return substituteZerosByMean(
([Link]([Link](axis=1), axis=0).fillna(0)).values, mean_pair
)
DATA_INDEX = [
'loan_id','no_of_dependents','education','self_employed','income_annum',
'loan_amount','loan_term','cibil_score','residential_assets_value',
'commercial_assets_value','luxury_assets_value','bank_asset_value','loan_status'
]
Universidade Estadual de Maringá – Departamento de Informática
Av. Colombo, 5790 – Bloco C56, Zona 7 – CEP: 87020-900
Fone: (44) 3011-4324/3011-4219
Maringá – PR
UNIVERSIDADE ESTADUAL DE MARINGÁ
DEPARTAMENTO DE INFORMÁTICA
Bacharelado em Ciência da Computação
● makeTheDiscreteVariable: Essa função estrutura uma variável
discreta, organizando informações como rótulos, frequência relativa de
cada valor e uma função para identificar valores. Isso ajuda a preparar
os dados de uma variável categórica para análise ou modelagem.
● makeTheContinuosVariable: Essa função transforma uma variável
contínua em uma variável categórica, dividindo-a em intervalos
definidos. Além disso, estrutura da mesma forma que em
makeTheDiscreteVariable.
● substituteZerosByMean: Essa função substitui vetores zerados por
um vetor médio fornecido.
● myCrosstab³: Essa função cria uma tabela cruzada entre duas
variáveis, normaliza as contagens para obter proporções e substitui
quaisquer linhas ou colunas de zeros por um vetor de média fornecido.
Com isso bem entendido, explicaremos o modelo:
import pickle
import numpy as np
from [Link] import BayesianNetwork
from [Link] import TabularCPD
data = pd.read_csv('loan_approval_dataset.csv')
mean_pair = [x for x in data['loan_status'].value_counts(normalize=True).values]
# ------------------------------ Definindo o Modelo
my_model_data = {
'education': makeTheDiscreteVariable( # Graduado ou Não Graduado
name='education', labels=['Graduate', 'Not Graduate']
),
'self_employed': makeTheDiscreteVariable( # Autônomo ou Contratado
name='self_employed', labels=['No', 'Yes']
),
'no_of_dependents': makeTheDiscreteVariable( # Número de Dependentes
name='no_of_dependents', labels=[0,1,2,3,4,5]
),
'luxury_assets_value': makeTheContinuosVariable( # Valor em Ativos de Luxo
name='luxury_assets_value', bins=[[Link], milion(15), milion(30), [Link]],
labels=['Low', 'Mid', 'High']
),
'commercial_assets_value': makeTheContinuosVariable( # Valor em Ativos Comerciais
name='commercial_assets_value', bins=[[Link], 0, milion(7.5), milion(15), [Link]],
labels=['Insolvent', 'Low', 'Mid', 'High']
),
'residential_assets_value': makeTheContinuosVariable( # Valor em Ativos Residenciais
Universidade Estadual de Maringá – Departamento de Informática
Av. Colombo, 5790 – Bloco C56, Zona 7 – CEP: 87020-900
Fone: (44) 3011-4324/3011-4219
Maringá – PR
UNIVERSIDADE ESTADUAL DE MARINGÁ
DEPARTAMENTO DE INFORMÁTICA
Bacharelado em Ciência da Computação
name='residential_assets_value', bins=[[Link], 0, milion(10), milion(20), [Link]],
labels=['Insolvent', 'Low', 'Mid', 'High']
),
'bank_asset_value': makeTheContinuosVariable( # Valor em Ativos no Banco
name='bank_asset_value', bins=[[Link], milion(5), milion(10), [Link]],
labels=['Low', 'Mid', 'High'], hasEvidence=['self_employed']
),
'cibil_score': makeTheContinuosVariable( # 'Score de Confiança Bancária'
name='cibil_score', bins=[[Link], 500, 700, [Link]], labels=['Low', 'Mid',
'High'], hasEvidence=['education']
),
'income_annum': makeTheContinuosVariable( # Renda anual
name='income_annum', bins=[[Link], milion(3.5), milion(7), [Link]], labels=['Low',
'Mid', 'High'], hasEvidence=['education', 'no_of_dependents']
),
'loan_term': makeTheContinuosVariable( # Juros
name='loan_term', bins=[[Link], 7.5, 15, [Link]], labels=['Low', 'Mid', 'High'],
hasEvidence=['cibil_score']
),
'loan_amount': makeTheContinuosVariable( # Quantidade de Empréstimo
name='loan_amount', bins=[[Link], milion(15), milion(30), [Link]], labels=['Low',
'Mid', 'High'], hasEvidence=['cibil_score', 'income_annum']
),
}
# Essas são variáveis que descobri serem importantes estarem ligadas diretamente à loan_status
IMPORTANT_VARIABLES = ['cibil_score', 'no_of_dependents']
all_evidence = [my_model_data[key]['name'] for key in my_model_data]
all_cards = [my_model_data[key]['card'] for key in my_model_data]
all_brute = [my_model_data[key]['brute'] for key in my_model_data]
print('1) Modelo Definido')
O modelo acima pode ser dividido em três etapas:
Primeiro, são lidos os dados de um arquivo (loan_approval_dataset.csv),
que contém informações sobre clientes. Após isso, é calculado as condições de
aprovação e recusa de empréstimo para ter uma média de referência que pode ser
usada mais adiante (mean_pair).
Em seguida, são organizadas as variáveis que influenciam a decisão de
aprovação. Algumas delas são tratadas como discretas, ou seja, variáveis
categóricas, como o nível de educação e o status de emprego, que são
representadas por rótulos fixos, como “Graduado” e “Não Graduado”. Outras
variáveis são contínuas, ou seja, valores numéricos que representam, por exemplo,
o valor dos ativos de luxo ou o total anual de renda. Esses valores contínuos são
agrupados em faixas, como “baixo”, “médio” e “alto”, tornando mais simples a
análise, pois cada faixa representa um intervalo em vez de um valor exato.
Universidade Estadual de Maringá – Departamento de Informática
Av. Colombo, 5790 – Bloco C56, Zona 7 – CEP: 87020-900
Fone: (44) 3011-4324/3011-4219
Maringá – PR
UNIVERSIDADE ESTADUAL DE MARINGÁ
DEPARTAMENTO DE INFORMÁTICA
Bacharelado em Ciência da Computação
Por último, são definidas as relações entre as variáveis. Por exemplo, o valor
de certos ativos pode depender do status de emprego do cliente, e a renda anual
pode estar relacionada ao número de dependentes. Além disso, são identificadas
variáveis especialmente importantes para o resultado final (como a pontuação de
confiança bancária e o número de dependentes), que influenciam diretamente na
decisão final de aprovação do empréstimo (IMPORTANT_VARIABLES).
Após isso, as evidências são adicionadas:
# ------------------------------ Adicionando as Evidências
cpd_list = []
nodes_list = []
added_evidences = []
for evidence in all_evidence:
state_names = {evidence: my_model_data[evidence]['labels']}
hasEvidence = len(my_model_data[evidence]['hasEvidence']) > 0
evidenceValues = []
if(hasEvidence):
evidenceValues = myCrosstab(evidence,
[my_model_data[x]['brute'] for x in my_model_data[evidence]['hasEvidence']],
mean_pair, True
)
for key in my_model_data[evidence]['hasEvidence']:
state_names[key] = my_model_data[key]['labels']
nodes_list.append((key, evidence))
if key not in added_evidences:
added_evidences.append(key)
else:
evidenceValues = my_model_data[evidence]['values']
cpd_list.append(TabularCPD(
variable=evidence,
variable_card=my_model_data[evidence]['card'],
values=evidenceValues,
state_names=state_names,
evidence=my_model_data[evidence]['hasEvidence'],
evidence_card=[my_model_data[x]['card'] for x in my_model_data[evidence]['hasEvidence']]
))
not_added_evidences = [x for x in all_evidence if x not in added_evidences]
not_added_evidences.extend(IMPORTANT_VARIABLES)
for evidence in not_added_evidences:
nodes_list.append((evidence, 'loan_status'))
model = BayesianNetwork(nodes_list)
print('2) Evidências Adicionadas')
Universidade Estadual de Maringá – Departamento de Informática
Av. Colombo, 5790 – Bloco C56, Zona 7 – CEP: 87020-900
Fone: (44) 3011-4324/3011-4219
Maringá – PR
UNIVERSIDADE ESTADUAL DE MARINGÁ
DEPARTAMENTO DE INFORMÁTICA
Bacharelado em Ciência da Computação
Aqui é configurado como as variáveis do modelo se relacionam na rede
bayesiana, identificando dependências entre elas. Para variáveis que dependem de
outras, ele calcula uma tabela de probabilidades condicionais, mostrando como elas
se influenciam. Já para as variáveis independentes, ele insere suas probabilidades
diretamente. Por fim, as variáveis principais estão ligadas ao resultado de aprovação
do empréstimo (loan_status), disponibilizando o modelo para calcular a
probabilidade de aprovação com base nas características do cliente.
E tão logo, é adicionado a variável destino loan_status no modelo:
# ------------------------------ Adicionando a Variável Destino
new_brute = [my_model_data[x]['brute'] for x in not_added_evidences]
values = myCrosstab('loan_status', new_brute, mean_pair, False)
cpd_loan_status = TabularCPD(
variable='loan_status',
variable_card = 2,
evidence=not_added_evidences,
evidence_card=[my_model_data[key]['card'] for key in not_added_evidences],
values=values,
state_names={
'loan_status': ['Rejected','Approved'],
**{key: my_model_data[key]['labels'] for key in not_added_evidences}
},
)
cpd_list.append(cpd_loan_status)
print('3) Variável Destino Adicionada')
Configurando a variável loan_status (destino) no modelo, é definido as
probabilidades de um empréstimo ser aprovado ou rejeitado com base nas outras
variáveis. O código calcula essas probabilidades condicionais (usando do método
crosstab já descrito) e as inclui no modelo, completando a estrutura para prever o
status do empréstimo.
E por fim, o modelo é concluído e salvo:
# ------------------------------ Fechando, Checando, e Salvando o Modelo
model.add_cpds(*cpd_list)
assert model.check_model()
with open('[Link]', 'wb') as f:
[Link](model, f)
print('4) Modelo Completo e Salvo')
print(f"[{len(cpd_list)} Variáveis | {len(values[0])} Combinações]")
O modelo é finalizado ao ser adicionado todas as probabilidades calculadas,
é verificado se o modelo está correto e ele é salvo no arquivo ([Link]) para uso
futuro.
Universidade Estadual de Maringá – Departamento de Informática
Av. Colombo, 5790 – Bloco C56, Zona 7 – CEP: 87020-900
Fone: (44) 3011-4324/3011-4219
Maringá – PR
UNIVERSIDADE ESTADUAL DE MARINGÁ
DEPARTAMENTO DE INFORMÁTICA
Bacharelado em Ciência da Computação
4. Resultados e Análise
A seguir, todas as probabilidade condicionais obtidas:
Universidade Estadual de Maringá – Departamento de Informática
Av. Colombo, 5790 – Bloco C56, Zona 7 – CEP: 87020-900
Fone: (44) 3011-4324/3011-4219
Maringá – PR
UNIVERSIDADE ESTADUAL DE MARINGÁ
DEPARTAMENTO DE INFORMÁTICA
Bacharelado em Ciência da Computação
Usando do modelo, é possível realizar inferências próprias. Alguns exemplos
bem e mal sucedidos a seguir:
Universidade Estadual de Maringá – Departamento de Informática
Av. Colombo, 5790 – Bloco C56, Zona 7 – CEP: 87020-900
Fone: (44) 3011-4324/3011-4219
Maringá – PR
UNIVERSIDADE ESTADUAL DE MARINGÁ
DEPARTAMENTO DE INFORMÁTICA
Bacharelado em Ciência da Computação
E por fim, rodando todo os dados (4269 linhas) no modelo criado
anteriormente, temos uma precisão de aproximadamente ~90%:
Esse valor é calculado através do seguinte procedimento:
def get_inf(line, variable):
variable_value_in_line = line[DATA_INDEX.index(variable)]
return my_model_data[variable]['checker'](variable_value_in_line)
def get_status(line):
return 1 if line[DATA_INDEX.index('loan_status')] == 'Rejected' else 0
acc = 0
i = 0
prevMessage = ''
for line in [Link]:
query_result = [Link](variables=['loan_status'], evidence={
'education': get_inf(line, 'education'),
'self_employed': get_inf(line, 'self_employed'),
'income_annum': get_inf(line, 'income_annum'),
'loan_amount': get_inf(line, 'loan_amount'),
'loan_term': get_inf(line, 'loan_term'),
'cibil_score': get_inf(line, 'cibil_score'),
'no_of_dependents': get_inf(line, 'no_of_dependents'),
'luxury_assets_value': get_inf(line, 'luxury_assets_value'),
'bank_asset_value': get_inf(line, 'bank_asset_value'),
'commercial_assets_value': get_inf(line, 'commercial_assets_value'),
'residential_assets_value': get_inf(line, 'residential_assets_value'),
})
acc += query_result.values[get_status(line)]
i+=1
acc /= len(data)
print(f"\nPrecision: {round(acc*100,2)}%")
Para cada linha no .csv, pegamos seus valores e criamos uma query de
inferência. Após isso, soma-se o valor em um acumulador pegando o primeiro ou
segundo index da query (se a linha é declarada no .csv como Reprovada, pega-se valor
de index 1. Do contrário, o valor de index 0). Por fim, se divide o acumulador pela
quantidade de linhas (em um modelo perfeito, o resultado seria 100%, dado que cada
linha somaria 1 no acumulador, para por fim ser dividido pela quantidade de linhas).
Universidade Estadual de Maringá – Departamento de Informática
Av. Colombo, 5790 – Bloco C56, Zona 7 – CEP: 87020-900
Fone: (44) 3011-4324/3011-4219
Maringá – PR
UNIVERSIDADE ESTADUAL DE MARINGÁ
DEPARTAMENTO DE INFORMÁTICA
Bacharelado em Ciência da Computação
5. Conclusão
Os resultados foram satisfatórios, mas é inegável que a aplicação certamente
teria um melhor resultado prático ao ser alimentado com mais dados (além de meras
4269 linhas). Temos plena convicção de que esse modelo não pode ser usado para
classificar créditos no mundo real, sendo necessário um amplo estudo de caso e
uma base de dados de pelo menos duas ou três ordens de grandeza acima.
6. Referências Bibliográficas
¹ [Link]
² [Link]
³ [Link]
Universidade Estadual de Maringá – Departamento de Informática
Av. Colombo, 5790 – Bloco C56, Zona 7 – CEP: 87020-900
Fone: (44) 3011-4324/3011-4219
Maringá – PR