Compare commits
10 Commits
3848a25c32
...
master
| Author | SHA1 | Date | |
|---|---|---|---|
|
cdc769fd33
|
|||
|
8fc55d5e2f
|
|||
|
3b08cb73f5
|
|||
|
9a8944cb5d
|
|||
|
29fd2a8436
|
|||
|
0471cb0ab8
|
|||
|
e05ccdabb9
|
|||
|
abfc877c7d
|
|||
|
793ba5fffb
|
|||
|
114e590238
|
1160
data/results.csv
Normal file
1160
data/results.csv
Normal file
File diff suppressed because it is too large
Load Diff
@@ -1,5 +0,0 @@
|
||||
{ pkgs ? import <nixpkgs> { } }:
|
||||
|
||||
with pkgs;
|
||||
|
||||
poetry2nix.mkPoetryApplication { projectDir = ./.; }
|
||||
120
docs/Summary.org
Normal file
120
docs/Summary.org
Normal 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
BIN
docs/Summary.pdf
Normal file
Binary file not shown.
BIN
docs/assets/F1.png
Normal file
BIN
docs/assets/F1.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 10 KiB |
BIN
docs/assets/F2.png
Normal file
BIN
docs/assets/F2.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 10 KiB |
@@ -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"
|
||||
|
||||
@@ -2,4 +2,4 @@
|
||||
|
||||
with pkgs;
|
||||
|
||||
mkShell { buildInputs = [ python38 poetry ]; }
|
||||
mkShell { buildInputs = [ python38 python38Packages.pandas poetry ]; }
|
||||
|
||||
@@ -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))
|
||||
df[data] = label.transform(df[data])
|
||||
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
51
src/processing.py
Normal 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()
|
||||
Reference in New Issue
Block a user