346 lines
12 KiB
Org Mode
346 lines
12 KiB
Org Mode
#+TITLE: Práctica 1
|
|
#+SUBTITLE: Inteligencia de Negocio
|
|
#+AUTHOR: Amin Kasrou Aouam
|
|
#+DATE: 2020-11-10
|
|
#+PANDOC_OPTIONS: template:~/.pandoc/templates/eisvogel.latex
|
|
#+PANDOC_OPTIONS: listings:t
|
|
#+PANDOC_OPTIONS: toc:t
|
|
#+PANDOC_METADATA: lang=es
|
|
#+PANDOC_METADATA: titlepage:t
|
|
#+PANDOC_METADATA: listings-no-page-break:t
|
|
#+PANDOC_METADATA: toc-own-page:t
|
|
#+PANDOC_METADATA: table-use-row-colors:t
|
|
#+PANDOC_METADATA: logo:/home/coolneng/Photos/Logos/UGR.png
|
|
* Práctica 1
|
|
|
|
** Introducción
|
|
|
|
En esta práctica, usaremos distintos algoritmos de aprendizaje automático para resolver un problema de clasificación.
|
|
|
|
** Procesado de datos
|
|
|
|
Antes de proceder con el entrenamiento de los distintos modelos, debemos realizar un preprocesado de los datos, para asegurarnos que nuestros modelos aprenden de un /dataset/ congruente.
|
|
|
|
La integridad de la lógica del preprocesado se encuentra en el archivo /preprocessing.py/, cuyo contenido mostramos aquí:
|
|
|
|
#+begin_src python
|
|
from pandas import read_csv
|
|
from sklearn.preprocessing import LabelEncoder
|
|
from sklearn.model_selection import KFold
|
|
|
|
|
|
def replace_values(df):
|
|
columns = ["BI-RADS", "Margin", "Density", "Age"]
|
|
for column in columns:
|
|
df[column].fillna(value=df[column].mean(), inplace=True)
|
|
return df
|
|
|
|
|
|
def process_na(df, action):
|
|
if action == "drop":
|
|
return df.dropna()
|
|
elif action == "fill":
|
|
return replace_values(df)
|
|
else:
|
|
print("Unknown action selected. The choices are: ")
|
|
print("fill: fills the na values with the mean")
|
|
print("drop: drops the na values")
|
|
exit()
|
|
|
|
|
|
def encode_columns(df):
|
|
label_encoder = LabelEncoder()
|
|
encoded_df = df.copy()
|
|
encoded_df["Shape"] = label_encoder.fit_transform(df["Shape"])
|
|
encoded_df["Severity"] = label_encoder.fit_transform(df["Severity"])
|
|
return encoded_df
|
|
|
|
|
|
def split_train_target(df):
|
|
train_data = df.drop(columns=["Severity"])
|
|
target_data = df["Severity"]
|
|
return train_data, target_data
|
|
|
|
|
|
def split_k_sets(df):
|
|
k_fold = KFold(shuffle=True, random_state=42)
|
|
return k_fold.split(df)
|
|
|
|
|
|
def parse_data(source, action):
|
|
df = read_csv(filepath_or_buffer=source, na_values="?")
|
|
processed_df = process_na(df=df, action=action)
|
|
encoded_df = encode_columns(df=processed_df)
|
|
test_data, target_data = split_train_target(df=encoded_df)
|
|
return test_data, target_data
|
|
#+end_src
|
|
|
|
#+RESULTS:
|
|
|
|
|
|
A continuación, mostraremos cada uno de los pasos que realizamos para obtener el /dataset/ final:
|
|
|
|
*** Valores nulos
|
|
|
|
Nuestro /dataset/ contiene valores nulos, representados mediante un signo de interrogación (?). Optamos por evaluar 2 estrategias:
|
|
|
|
**** Eliminar los valores nulos
|
|
|
|
#+BEGIN_SRC python
|
|
df = read_csv(filepath_or_buffer="../data/mamografia.csv", na_values="?")
|
|
processed_df = process_na(df=df, action="drop")
|
|
print("DataFrame sin preprocesamiento: ")
|
|
print(df.describe())
|
|
print("DataFrame sin preprocesamiento: ")
|
|
print(processed_df.describe())
|
|
#+END_SRC
|
|
|
|
#+begin_example
|
|
DataFrame sin preprocesamiento:
|
|
BI-RADS Age Margin Density
|
|
count 959.000000 956.000000 913.000000 885.000000
|
|
mean 4.296142 55.487448 2.796276 2.910734
|
|
std 0.706291 14.480131 1.566546 0.380444
|
|
min 0.000000 18.000000 1.000000 1.000000
|
|
25% 4.000000 45.000000 1.000000 3.000000
|
|
50% 4.000000 57.000000 3.000000 3.000000
|
|
75% 5.000000 66.000000 4.000000 3.000000
|
|
max 6.000000 96.000000 5.000000 4.000000
|
|
DataFrame sin preprocesamiento:
|
|
BI-RADS Age Margin Density
|
|
count 847.000000 847.000000 847.000000 847.000000
|
|
mean 4.322314 55.842975 2.833530 2.909091
|
|
std 0.703762 14.603754 1.564049 0.370292
|
|
min 0.000000 18.000000 1.000000 1.000000
|
|
25% 4.000000 46.000000 1.000000 3.000000
|
|
50% 4.000000 57.000000 3.000000 3.000000
|
|
75% 5.000000 66.000000 4.000000 3.000000
|
|
max 6.000000 96.000000 5.000000 4.000000
|
|
#+end_example
|
|
|
|
Observamos que el número de instancias disminuye considerablemente, hasta un máximo de 112, en el caso del /BI-RADS/. Aún así, los valores de la media y desviación estándar no se ven afectados de forma considerable.
|
|
|
|
**** Imputar su valor con la media
|
|
|
|
#+BEGIN_SRC python
|
|
df = read_csv(filepath_or_buffer="../data/mamografia.csv", na_values="?")
|
|
processed_df = process_na(df=df, action="fill")
|
|
print("DataFrame sin preprocesamiento: ")
|
|
print(df.describe())
|
|
print("DataFrame sin preprocesamiento: ")
|
|
print(processed_df.describe())
|
|
#+END_SRC
|
|
|
|
#+begin_example
|
|
DataFrame sin preprocesamiento:
|
|
BI-RADS Age Margin Density
|
|
count 961.000000 961.000000 961.000000 961.000000
|
|
mean 4.296142 55.487448 2.796276 2.910734
|
|
std 0.705555 14.442373 1.526880 0.365074
|
|
min 0.000000 18.000000 1.000000 1.000000
|
|
25% 4.000000 45.000000 1.000000 3.000000
|
|
50% 4.000000 57.000000 3.000000 3.000000
|
|
75% 5.000000 66.000000 4.000000 3.000000
|
|
max 6.000000 96.000000 5.000000 4.000000
|
|
DataFrame sin preprocesamiento:
|
|
BI-RADS Age Margin Density
|
|
count 961.000000 961.000000 961.000000 961.000000
|
|
mean 4.296142 55.487448 2.796276 2.910734
|
|
std 0.705555 14.442373 1.526880 0.365074
|
|
min 0.000000 18.000000 1.000000 1.000000
|
|
25% 4.000000 45.000000 1.000000 3.000000
|
|
50% 4.000000 57.000000 3.000000 3.000000
|
|
75% 5.000000 66.000000 4.000000 3.000000
|
|
max 6.000000 96.000000 5.000000 4.000000
|
|
#+end_example
|
|
|
|
Esta alternativa nos permite mantener el número de instancias en todas las columnas, sin alterar la media ni la desviación típica.
|
|
|
|
*** Valores no númericos
|
|
|
|
La mayoría de algoritmos de aprendizaje automática trabaja con datos numéricos, desafortunadamente nuestro /dataset/ contiene dos columnas con datos descriptivos.
|
|
|
|
Procedemos a convertirlos en valores numéricos mediante un /LabelEncoder/:
|
|
|
|
#+begin_src python
|
|
encoded_df = encode_columns(df=processed_df)
|
|
print(encoded_df.head())
|
|
#+end_src
|
|
|
|
#+begin_example
|
|
BI-RADS Age Shape Margin Density Severity
|
|
0 5.0 67.0 1 5.0 3.000000 1
|
|
1 4.0 43.0 4 1.0 2.910734 1
|
|
2 5.0 58.0 0 5.0 3.000000 1
|
|
3 4.0 28.0 4 1.0 3.000000 0
|
|
4 5.0 74.0 4 5.0 2.910734 1
|
|
#+end_example
|
|
|
|
Vemos como las columnas *Shape* y *Severity* se componen ahora únicamente de valores numéricos.
|
|
*** Separación de datos
|
|
|
|
Como último paso, separamos la columna objetivo de los demás datos.
|
|
|
|
#+begin_src python
|
|
test_data, target_data = split_train_target(df=encoded_df)
|
|
print("Datos de entrenamiento: ")
|
|
print(test_data.head())
|
|
print("Datos objetivo: ")
|
|
print(target_data.head())
|
|
#+end_src
|
|
|
|
#+begin_example
|
|
Datos de entrenamiento:
|
|
BI-RADS Age Shape Margin Density
|
|
0 5.0 67.0 1 5.0 3.000000
|
|
1 4.0 43.0 4 1.0 2.910734
|
|
2 5.0 58.0 0 5.0 3.000000
|
|
3 4.0 28.0 4 1.0 3.000000
|
|
4 5.0 74.0 4 5.0 2.910734
|
|
Datos objetivo:
|
|
0 1
|
|
1 1
|
|
2 1
|
|
3 0
|
|
4 1
|
|
Name: Severity, dtype: int64
|
|
#+end_example
|
|
** Configuración de algoritmos
|
|
|
|
Elegimos 5 algoritmos distintos:
|
|
|
|
1. Naive Bayes
|
|
2. Linear Support Vector Classification
|
|
3. K Nearest Neighbors
|
|
4. Árbol de decisión
|
|
5. Perceptrón multicapa (red neuronal)
|
|
|
|
Procedemos a evaluar el rendimiento de cada algoritmo, usando las siguientes métricas:
|
|
|
|
- Accuracy score
|
|
- Matriz de confusión
|
|
- Cross validation score
|
|
- Area under the curve (AUC)
|
|
|
|
Vamos a realizar 2 ejecuciones por algoritmo, para evaluar las diferencias que obtenemos según el preprocesado utilizado (eliminación de valores nulos o imputación).
|
|
|
|
La implementación se encuentra en el archivo /processing.py/, cuyo contenido mostramos a continuación:
|
|
|
|
#+begin_src python
|
|
from numpy import mean
|
|
from sklearn.metrics import confusion_matrix, accuracy_score, roc_auc_score
|
|
from sklearn.model_selection import cross_val_score
|
|
from sklearn.naive_bayes import GaussianNB
|
|
from sklearn.neural_network import MLPClassifier
|
|
from sklearn.neighbors import KNeighborsClassifier
|
|
from sklearn.preprocessing import scale
|
|
from sklearn.svm import LinearSVC
|
|
from sklearn.tree import DecisionTreeClassifier
|
|
|
|
from sys import argv
|
|
|
|
from preprocessing import parse_data, split_k_sets
|
|
|
|
|
|
def choose_model(model):
|
|
if model == "gnb":
|
|
return GaussianNB()
|
|
elif model == "svc":
|
|
return LinearSVC(random_state=42)
|
|
elif model == "knn":
|
|
return KNeighborsClassifier(n_neighbors=10)
|
|
elif model == "tree":
|
|
return DecisionTreeClassifier(random_state=42)
|
|
elif model == "neuralnet":
|
|
return MLPClassifier(hidden_layer_sizes=10)
|
|
else:
|
|
print("Unknown model selected. The choices are: ")
|
|
print("gnb: Gaussian Naive Bayes")
|
|
print("svc: Linear Support Vector Classification")
|
|
print("knn: K-neighbors")
|
|
print("tree: Decision tree")
|
|
print("neuralnet: MLP Classifier")
|
|
exit()
|
|
|
|
|
|
def predict_data(data, target, model):
|
|
model = choose_model(model)
|
|
if model == "knn":
|
|
data = scale(data)
|
|
accuracy_scores = []
|
|
confusion_matrices = []
|
|
auc = []
|
|
for train_index, test_index in split_k_sets(data):
|
|
model.fit(data.iloc[train_index], target.iloc[train_index])
|
|
prediction = model.predict(data.iloc[test_index])
|
|
accuracy_scores.append(accuracy_score(target.iloc[test_index], prediction))
|
|
confusion_matrices.append(confusion_matrix(target.iloc[test_index], prediction))
|
|
auc.append(roc_auc_score(target.iloc[test_index], prediction))
|
|
cv_score = cross_val_score(model, data, target, cv=10)
|
|
evaluate_performance(
|
|
confusion_matrix=mean(confusion_matrices, axis=0),
|
|
accuracy=mean(accuracy_scores),
|
|
cv_score=mean(cv_score),
|
|
auc=mean(auc),
|
|
)
|
|
|
|
|
|
def evaluate_performance(confusion_matrix, accuracy, cv_score, auc):
|
|
print("Accuracy Score: " + str(accuracy))
|
|
print("Confusion matrix: ")
|
|
print(str(confusion_matrix))
|
|
print("Cross validation score: " + str(cv_score))
|
|
print("AUC: " + str(auc))
|
|
#+end_src
|
|
|
|
** Resultados obtenidos
|
|
*** Naives Bayes
|
|
|
|
Los resultados que obtenemos son los siguientes:
|
|
|
|
#+CAPTION: Naive Bayes
|
|
[[./assets/gnb.png]]
|
|
*** Linear SVC
|
|
|
|
Los resultados que obtenemos son los siguientes:
|
|
|
|
#+CAPTION: Linear SVC con eliminación
|
|
[[./assets/svc_drop.png]]
|
|
|
|
#+CAPTION: Linear SVC con imputación
|
|
[[./assets/svc_fill.png]]
|
|
*** KNN
|
|
Antes de ejecutar este algoritmo, normalizamos los datos dado que el /KNN/ es un algoritmo basado en distancia.
|
|
|
|
Los resultados que obtenemos son los siguientes:
|
|
|
|
#+CAPTION: KNN
|
|
[[./assets/knn.png]]
|
|
*** Árbol de decisión
|
|
|
|
Los resultados que obtenemos son los siguientes:
|
|
|
|
#+CAPTION: Árbol de decisión
|
|
[[./assets/tree.png]]
|
|
*** Perceptrón multicapa
|
|
|
|
Los resultados que obtenemos son los siguientes:
|
|
|
|
#+CAPTION: Perceptrón multicapa con eliminación
|
|
[[./assets/neuralnet_drop.png]]
|
|
|
|
#+CAPTION: Perceptrón multicapa con imputación
|
|
[[./assets/neuralnet_fill.png]]
|
|
|
|
\clearpage
|
|
|
|
** Análisis de resultados
|
|
|
|
Tras ejecutar todos los algoritmos elegidos, podemos analizar los resultados obtenidos.
|
|
|
|
Observamos que la imputación de la media en los valores nulos no es una estrategia que aporte demasiados beneficios, y según ciertas métricas puede ser hasta dañino para nuestro modelo.
|
|
|
|
Vemos que nuestros modelos obtienen unos resultados bastante correctos, con un /accuracy score/, /cross validation score/ y /AUC/ en el intervalo $[0.7, 0.82]$.
|
|
|
|
Por lo tanto, concluimos que nuestro problema de clasificación es suficientemente genérico para ser resuelto por distintos modelos de aprendizaje, sin variaciones significantes en el rendimientos de éstos.
|