Compare commits

..

10 Commits

Author SHA1 Message Date
cdc769fd33 Add summary 2021-01-12 23:00:19 +01:00
8fc55d5e2f Add results 2021-01-01 23:53:40 +01:00
3b08cb73f5 Complete missing values in columns with less data 2021-01-01 23:52:43 +01:00
9a8944cb5d Replace SMOTETomek with SMOTEENN 2021-01-01 23:37:41 +01:00
29fd2a8436 Remove ciudad from columns 2021-01-01 22:26:59 +01:00
0471cb0ab8 Add pandas to nix-shell instead of poetry 2021-01-01 21:54:28 +01:00
e05ccdabb9 Remove string trimming function 2021-01-01 21:54:05 +01:00
abfc877c7d Add processing module 2021-01-01 21:06:09 +01:00
793ba5fffb Balance the dataset 2021-01-01 21:06:00 +01:00
114e590238 Add summary with section names 2021-01-01 19:16:21 +01:00
10 changed files with 1386 additions and 37 deletions

1160
data/results.csv Normal file

File diff suppressed because it is too large Load Diff

View File

@@ -1,5 +0,0 @@
{ pkgs ? import <nixpkgs> { } }:
with pkgs;
poetry2nix.mkPoetryApplication { projectDir = ./.; }

120
docs/Summary.org Normal file
View File

@@ -0,0 +1,120 @@
#+TITLE: Práctica 3
#+SUBTITLE: Inteligencia de Negocio
#+AUTHOR: Amin Kasrou Aouam
#+DATE: 2021-01-12
#+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 3
** Introducción
En esta práctica, resolveremos un problema de clasificación multiclase, en concreto, trataremos de predecir la categoría de precio de una serie de coches
** Preprocesamiento de datos
*** Valores nulos
Nuestro /dataset/ contiene bastantes valores nulos, optamos por estrategias diferentes según las columnas:
- Eliminación: tipo marchas, descuento, ciudad
- Imputación: asientos, motor cc, potencia
El criterio que seleccionamos es el número de instancias nulas, en el caso de que sean muchas optamos por imputar, para mantener un número adecuado de datos.
La implementación se encuentra en la siguiente función:
#+begin_src python
def process_null_values(df_list):
drop_columns = ["tipo_marchas", "descuento", "ciudad"]
fill_columns = ["asientos", "motor_cc", "potencia"]
for df in df_list:
for column in fill_columns:
if column == "asientos":
df[column].fillna(value=df[column].median(), inplace=True)
else:
df[column].fillna(
value=df[column].str.extract("(\d+)").mean(), inplace=True
)
df.drop(columns=drop_columns, inplace=True)
df.dropna(inplace=True)
return df_list
#+end_src
*** Valores no numéricos
Ciertas columnas contienen valores alfanúmericos, aunque se nos proporcionan distintos archivos CSV para realizar un /mapping/. En este caso, utilizamos un *LabelEncoder*, y como entrada le damos el CSV correspondiente.
Es primordial usar el mismo /LabelEncoder/ para los datos de entrenamiento como de test. La implementación se encuentra en la siguiente función:
#+begin_src python
def encode_columns(df_list):
label_encoder = LabelEncoder()
files = [
"ao",
"asientos",
"combustible",
"consumo",
"kilometros",
"mano",
"motor_cc",
"nombre",
"potencia",
]
for data in files:
for df in df_list:
label = label_encoder.fit(read_csv("data/" + data + ".csv", squeeze=True))
if data == "ao":
df["año"] = label.transform(df["año"])
else:
df[data] = label.transform(df[data])
return df_list
#+end_src
*** Balanceo de clases
Observamos que la mayoría de coches son de la categoría de precio 3, lo cual no es idóneo para entrenar un modelo de inteligencia artificial.
Debemos realizar un balanceo de las clases, en este caso optamos por usar el modelo *SMOTEEEN*, que combina un /over-sampling/ mediante *SMOTE* y una limpieza gracias a /Edited Nearest Neighbours (ENN)/.
La implementación se encuentra en esta función:
#+begin_src python
def balance_training_data(df):
smote_enn = SMOTEENN(random_state=42)
data, target = split_data_target(df=df, dataset="data")
balanced_data, balanced_target = smote_enn.fit_resample(data, target)
balanced_data_df = DataFrame(
balanced_data, columns=df.columns.difference(["precio_cat"])
)
balanced_target_df = DataFrame(balanced_target, columns=["precio_cat"])
return balanced_data_df, balanced_target_df
#+end_src
** Elección de algoritmo
Elegimos el algoritmo *GradientBoostingClassifier*, que pertenece a los algoritmos de /ensemble/. Éstos combinan las predicciones de varios clasificadores, con el objetivo de mejorar la generalización y la robustez de las predicciones.
En particular, pertenece a la familia de /boosting methods/, cuya característica es que los clasificadores se crean de forma secuencial, y uno de ellos trata de reducir el sesgo de los demás.
** Resultados obtenidos
Al ejecutar el programa en local obtenemos los siguientes resultados:
#+CAPTION: Resultados de ejecución
[[./assets/F1.png]]
Desafortunadamente, en la plataforma Kaggle obtenemos unos resultados pésimos:
#+CAPTION: Resultados de Kaggle
[[./assets/F2.png]]
** Análisis de resultados
Debido a la discrepancia entre los resultados de la ejecución en local, y de la plataforma Kaggle, intuimos que debe de haber un problema en el preprocesamiento de datos.
También es posible que el modelo no sea óptimo para la tarea, aunque no justificaría un rendimiento tan bajo, puede contribuir a ello.

BIN
docs/Summary.pdf Normal file

Binary file not shown.

BIN
docs/assets/F1.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 10 KiB

BIN
docs/assets/F2.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 10 KiB

View File

@@ -7,14 +7,11 @@ authors = ["coolneng <akasroua@gmail.com>"]
[tool.poetry.dependencies]
python = "^3.8"
scikit-learn = "^0.24.0"
pandas = "^1.2.0"
imbalanced-learn = "^0.7.0"
numpy = "^1.19.4"
[tool.poetry.dev-dependencies]
[build-system]
requires = ["poetry-core>=1.0.0"]
build-backend = "poetry.core.masonry.api"
[tool.poetry.scripts]
competition = "processing:main"

View File

@@ -2,4 +2,4 @@
with pkgs;
mkShell { buildInputs = [ python38 poetry ]; }
mkShell { buildInputs = [ python38 python38Packages.pandas poetry ]; }

View File

@@ -1,6 +1,7 @@
from pandas import DataFrame, read_csv
from sklearn.preprocessing import LabelEncoder
from sklearn.model_selection import KFold
from imblearn.combine import SMOTEENN
def construct_dataframes(train, test):
@@ -12,49 +13,73 @@ def construct_dataframes(train, test):
return df_list
def drop_null_values(df_list):
for df in df_list:
df.dropna(inplace=True)
df.drop(columns="Tipo_marchas", inplace=True)
return df_list
def rename_columns(df_list) -> DataFrame:
for df in df_list:
df.columns = df.columns.str.lower()
return df_list
def trim_column_names(df_list) -> DataFrame:
columns = ["consumo", "motor_CC", "potencia"]
def process_null_values(df_list):
drop_columns = ["tipo_marchas", "descuento", "ciudad"]
fill_columns = ["asientos", "motor_cc", "potencia"]
for df in df_list:
for col in columns:
df[col] = df[col].str.replace(pat="[^.0-9]", repl="").astype(float)
for column in fill_columns:
if column == "asientos":
df[column].fillna(value=df[column].median(), inplace=True)
else:
df[column].fillna(
value=df[column].str.extract("(\d+)").mean(), inplace=True
)
df.drop(columns=drop_columns, inplace=True)
df.dropna(inplace=True)
return df_list
def encode_columns(df_list):
label_encoder = LabelEncoder()
files = [
"ao"
"asientos"
"ciudad"
"combustible"
"consumo"
"descuento"
"kilometros"
"mano"
"motor_cc"
"nombre"
"potencia"
"ao",
"asientos",
"combustible",
"consumo",
"kilometros",
"mano",
"motor_cc",
"nombre",
"potencia",
]
for data in files:
for df in df_list:
label = label_encoder.fit(read_csv("data/" + data + ".csv", squeeze=True))
if data == "ao":
df["año"] = label.transform(df["año"])
else:
df[data] = label.transform(df[data])
return df_list
def split_data_target(df, dataset):
if dataset == "data":
df.drop(columns="id", inplace=True)
data = df.drop(columns=["precio_cat"])
target = df["precio_cat"]
else:
data = df.drop(columns=["id"])
target = df["id"]
return data, target
def balance_training_data(df):
smote_enn = SMOTEENN(random_state=42)
data, target = split_data_target(df=df, dataset="data")
balanced_data, balanced_target = smote_enn.fit_resample(data, target)
balanced_data_df = DataFrame(
balanced_data, columns=df.columns.difference(["precio_cat"])
)
balanced_target_df = DataFrame(balanced_target, columns=["precio_cat"])
return balanced_data_df, balanced_target_df
def split_k_sets(df):
k_fold = KFold(shuffle=True, random_state=42)
return k_fold.split(df)
@@ -63,7 +88,8 @@ def split_k_sets(df):
def parse_data(train, test):
df_list = construct_dataframes(train=train, test=test)
renamed_df_list = rename_columns(df_list)
processed_df_list = drop_null_values(renamed_df_list)
numeric_df_list = trim_column_names(processed_df_list)
encoded_df_list = encode_columns(numeric_df_list)
return encoded_df_list
processed_df_list = process_null_values(renamed_df_list)
encoded_df_list = encode_columns(processed_df_list)
train_data, train_target = balance_training_data(encoded_df_list[0])
test_data, test_ids = split_data_target(encoded_df_list[1], dataset="test")
return train_data, train_target, test_data, test_ids

51
src/processing.py Normal file
View File

@@ -0,0 +1,51 @@
from numpy import mean
from pandas import DataFrame
from sklearn.metrics import accuracy_score
from sklearn.model_selection import cross_val_score
from sklearn.ensemble import GradientBoostingClassifier
from preprocessing import parse_data, split_k_sets
def predict_data(train_data, train_target, test_data, test_ids):
model = GradientBoostingClassifier(random_state=42)
accuracy_scores = []
for train_index, test_index in split_k_sets(train_data):
model.fit(train_data.iloc[train_index], train_target.iloc[train_index])
prediction = model.predict(train_data.iloc[test_index])
accuracy_scores.append(
accuracy_score(train_target.iloc[test_index], prediction)
)
cv_score = cross_val_score(model, train_data, train_target)
evaluate_performance(
accuracy=mean(accuracy_scores),
cv_score=mean(cv_score),
)
predictions = model.predict(test_data)
export_results(ids=test_ids, prediction=predictions)
def evaluate_performance(accuracy, cv_score):
print("Accuracy Score: " + str(accuracy))
print("Cross validation score: " + str(cv_score))
def export_results(ids, prediction):
result_df = DataFrame({"id": ids, "Precio_cat": prediction})
result_df.to_csv(path_or_buf="data/results.csv", index=False)
def main():
train_data, train_target, test_data, test_ids = parse_data(
train="data/train.csv", test="data/test.csv"
)
predict_data(
train_data=train_data,
train_target=train_target,
test_data=test_data,
test_ids=test_ids,
)
if __name__ == "__main__":
main()