Modelling
Un modelo es una representación simplificada de la realidad creada para servir una propuesta. Las afirmaciones, la selección de características importantes, las restricciones y la trazabilidad definen un modelo. Un ejemplo de un modelo es un mapa que nos posibilita posicionar lugares de interes, calcular distancias entre ellos, o encontrar relaciones topológicas.
En ciencia de datos podemos tener modelos predictivos o modelos descriptivos. Los primeros forman un conjunto de formulas para estimar valores desconocidos. En el segundo caso, sirven para encontrar indicios subyacentes del proceso. Los datos del modelo representan hechos o evidencias de la realidad o de otro modelo. Siguiendo el ejemplo del mapa, represetan los bordes o contornos del territorio representado.
Fuente de ambas figuras [2]
El proceso de aprendizaje
El proceso de aprendizaje automático suele constar de una serie de pasos. Por lo general, estos son: - Preparación de datos. Carga y limpieza de datos. - Selección de atributos o métricas adecuadas. - Selección de la técnica a aplicar. - Ajuste de los hiperparámetros. - Evaluación del modelo.
Selección de Atributos
Dado un conjunto amplio de atributos que caracterizan los eventos a representar, el problema es elegir aquellos que puedan contribuir al modelo de aprendizaje.
En el ejemplo de las personas, los argumentos que podríamos tener son: - Forma de la cabeza: cuadrada o circular. - Forma del cuerpo: rectangular o óvalo. - Color del cuerpo: negro o blanco.
Si tuvieramos que segmentar dicha población deberíamos de plantear que atributos de estas personas nos permitiran identificar, por ejemplo, si cancelerán su compra (write-offs a yes/no). Este atributo es la variable objetivo: target variable. El diseño del modelo ha de contener una homogeneidad con respecto a esta variable. No puede existir un individuo que esté en ambos targets. Estaríamos tratando otro problema.
En este problema de segmentación, el objetivo sería encontrar una o varias variables que defininan puramente un grupo. Rara vez suele pasar. Por ejemplo, el atributo head-shape no lo es. Si tan solo eligieramos el atributo body-color a negro, no tendríamos una representatividad del resto de la población. E incluso, hay que tratar con valores númericos de atributos continuos o discretos.
En el campo de la Teoria de la Información, Shannon en 1948 definió dos conceptos importantes information gain (IG) y entropy.
La entropia es un indicador sobre el “desorden”: $ H = - :nbsphinx-math:`sum`^{W}_{i=1}p_i * log(p_i)$
\(p(write-off)= 7/12= 0.583\) \(p(non-write-off)= 5/12 = 0.416\)
$ H = - [ 7/12 * log(7/12) + 5/12 * log(5/12)] = 0.6791 $
La ganancia de información de un atributo con respecto a la variable objetivo representa cuanto aporta dicha variable para mejorar (reducir) la entropía debido al aumento de información.
$ IG(parent, children) = entropy(parent) - [ p(c_1)entropy(c_1)+ p(c_2)entropy(c_2)+…]$
Veamos un caso:
Fuente de la imagen [2]
La entropia de toda la población con respecto a \(\bullet\) y \(\star\) es:
Si seleccionamos el atributo de balance con un criterio de \(50k\) dispondremos de dos poblaciones nuevas con entropia diferentes.
$ entropy(balance < 50k) = - [ p(\bullet)log(:nbsphinx-math:`bullet`) + p(:nbsphinx-math:`star`)log(\star)] $ $ entropy(balance < 50k) = - [ 0.92 * -0.12 + 0.08*-3.7] = 0.39 $
$ entropy(balance \geq `50k) = - [ p(:nbsphinx-math:bullet`)log(:nbsphinx-math:`bullet`) + p(:nbsphinx-math:`star`)log(\star)] $ $ entropy(balance :nbsphinx-math:`geq `50k) = - [ 0.24 * -2.1 + 0.76*-0.39] = 0.79 $
\(IG = entropy(parent) - [ p(balance < 50k)*entropy(balance < 50k)+ p(balance \geq 50k)*entropy(balance \geq 50k) ]\) \(IG = entropy(parent) - [ 13/30 *entropy(balance < 50k)+ 17/30*entropy(balance \geq 50k) ]\)
$ IG = 0.99 - [0.43 * 0.39 + 0.57 * 0.79] = 0.37 $
Ganancia y Entropía de Información con Python
Vamos a utilizar el siguiente catálogo:
Mushroom Data Set http://archive.ics.uci.edu/ml/datasets/Mushroom
data/mushrooms.csv
¿Qué librerías de ML se usan habitualmente en Python ?
Scikit-learn: https://scikit-learn.org/stable/index.html
Instalamos dependencias:
pip install pandas
pip install numpy
pip install matplotlib
pip install seaborn
pip install sklearn
[16]:
import pandas as pd
df = pd.read_csv("data/mushrooms.csv")
print(df.shape)
print(df.columns)
es_col = ["clase", "forma de la caperuza", "superficie de la caperuza", "color de la caperuza", "magulladuras", "olor",
"tamaño de las branquias", "color de las branquias",
"forma del tallo", "raíz del tallo", "superficie del tallo por encima del anillo",
"superficie peduncular por debajo del anillo", "color peduncular por encima del anillo",
"color del tallo por debajo del anillo", "tipo de velo", "color del velo", "número de anillos",
"tipo de anillo", "color de impresión de las esporas", "población", "hábitat"]
(8124, 23)
Index(['class', 'cap-shape', 'cap-surface', 'cap-color', 'bruises', 'odor',
'gill-attachment', 'gill-spacing', 'gill-size', 'gill-color',
'stalk-shape', 'stalk-root', 'stalk-surface-above-ring',
'stalk-surface-below-ring', 'stalk-color-above-ring',
'stalk-color-below-ring', 'veil-type', 'veil-color', 'ring-number',
'ring-type', 'spore-print-color', 'population', 'habitat'],
dtype='object')
[17]:
from math import log, e
import numpy as np
def entropy(serie):
value,counts = np.unique(serie, return_counts=True)
norm_counts = counts / counts.sum()
return -np.sum(norm_counts * np.log(norm_counts))
[18]:
print("Attribute: class " , entropy(df["class"]))
print("Attribute: cap-surface " , entropy(df["cap-surface"]))
print("Attribute: gill-size " , entropy(df["gill-size"]))
Attribute: class 0.6925010959051001
Attribute: cap-surface 1.0920439563177977
Attribute: gill-size 0.6184649299084096
Donde más fácil resulta calcular la entropia es en árboles de decisión, por lo que se puede ver en la firma/signature de algunas funciones de scikit.
https://scikit-learn.org/stable/modules/generated/sklearn.tree.DecisionTreeClassifier.html
criterion{“gini”, “entropy”, “log_loss”}, default=”gini”
The function to measure the quality of a split. Supported criteria are “gini” for the Gini impurity and “log_loss” and “entropy” both for the Shannon information gain, see Mathematical formulation.
model = sklearn.tree.DecisionTreeClassifier(criterion='entropy')
Nota Profundizaremos este punto a medida que vayamos introduciendo algoritmos de ML con Python
Un ejemplo de ML algorithm
Veremos los principales métodos de Scikit-Learn library que nos proporciona la estructura basica de un algoritmo de ML.
[19]:
# Algoritmo: clasificación de hongos según su población
# Método supervisado
# 1º Datos
import numpy as np
import pandas as pd
df = pd.read_csv("data/mushrooms.csv")
print(df.shape)
# ¿Cuál es el objetivo que planteamos?
# 2º Target value.
# 21. population: abundant=a,clustered=c,numerous=n, scattered=s,several=v,solitary=y
# Entonces ¿Qué tipo de poblema tenemos?
# - Supervisado o No Supervisado?
# - Clasificación o Regresión ?
print(df.population.head())
value,counts = np.unique(df.population, return_counts=True)
print(value,counts)
df_y = df.population.copy()
df_x = df.drop(labels=["population"],axis=1)
# 3º Feature selection: Todos.
# 4º Data splitting. Crear modelo de testing.
# Datos de entrenamiento (train) y datos de comprobación (test)
# https://scikit-learn.org/stable/modules/classes.html#module-sklearn.model_selection
from sklearn.model_selection import train_test_split
x_train, x_test, y_train, y_test = train_test_split(df_x,df_y, test_size=0.2, random_state = 0)
print("x_train:", x_train.shape)
print("x_test: ",x_test.shape)
print("y_train: ",y_train.shape)
(8124, 23)
0 s
1 n
2 n
3 s
4 a
Name: population, dtype: object
['a' 'c' 'n' 's' 'v' 'y'] [ 384 340 400 1248 4040 1712]
x_train: (6499, 22)
x_test: (1625, 22)
y_train: (6499,)
[20]:
# 5º Tipo de algoritmo - elección del algoritmo:
#
# Quizás podriamos usar... un ejemplo Máquinas de Vectores de Soporte (SVM)
# https://scikit-learn.org/stable/modules/svm.html
# https://scikit-learn.org/stable/modules/generated/sklearn.svm.SVC.html
#
from sklearn.svm import SVC
clf = SVC(C=1.0, random_state=0)
clf.fit(x_train, y_train)
# Alerta: Genera un error!
---------------------------------------------------------------------------
ValueError Traceback (most recent call last)
/Users/isaac/Projects/TxADM_notebooks/notebooks/Part2/03_MachineLearningNotes/02_PredictiveModelling.ipynb Cell 10 line <cell line: 10>()
<a href='vscode-notebook-cell:/Users/isaac/Projects/TxADM_notebooks/notebooks/Part2/03_MachineLearningNotes/02_PredictiveModelling.ipynb#X12sZmlsZQ%3D%3D?line=6'>7</a> from sklearn.svm import SVC
<a href='vscode-notebook-cell:/Users/isaac/Projects/TxADM_notebooks/notebooks/Part2/03_MachineLearningNotes/02_PredictiveModelling.ipynb#X12sZmlsZQ%3D%3D?line=8'>9</a> clf = SVC(C=1.0, random_state=0)
---> <a href='vscode-notebook-cell:/Users/isaac/Projects/TxADM_notebooks/notebooks/Part2/03_MachineLearningNotes/02_PredictiveModelling.ipynb#X12sZmlsZQ%3D%3D?line=9'>10</a> clf.fit(x_train, y_train)
File ~/.pyenv/versions/3.9.7/envs/my397/lib/python3.9/site-packages/sklearn/svm/_base.py:173, in BaseLibSVM.fit(self, X, y, sample_weight)
171 check_consistent_length(X, y)
172 else:
--> 173 X, y = self._validate_data(
174 X,
175 y,
176 dtype=np.float64,
177 order="C",
178 accept_sparse="csr",
179 accept_large_sparse=False,
180 )
182 y = self._validate_targets(y)
184 sample_weight = np.asarray(
185 [] if sample_weight is None else sample_weight, dtype=np.float64
186 )
File ~/.pyenv/versions/3.9.7/envs/my397/lib/python3.9/site-packages/sklearn/base.py:596, in BaseEstimator._validate_data(self, X, y, reset, validate_separately, **check_params)
594 y = check_array(y, input_name="y", **check_y_params)
595 else:
--> 596 X, y = check_X_y(X, y, **check_params)
597 out = X, y
599 if not no_val_X and check_params.get("ensure_2d", True):
File ~/.pyenv/versions/3.9.7/envs/my397/lib/python3.9/site-packages/sklearn/utils/validation.py:1074, in check_X_y(X, y, accept_sparse, accept_large_sparse, dtype, order, copy, force_all_finite, ensure_2d, allow_nd, multi_output, ensure_min_samples, ensure_min_features, y_numeric, estimator)
1069 estimator_name = _check_estimator_name(estimator)
1070 raise ValueError(
1071 f"{estimator_name} requires y to be passed, but the target y is None"
1072 )
-> 1074 X = check_array(
1075 X,
1076 accept_sparse=accept_sparse,
1077 accept_large_sparse=accept_large_sparse,
1078 dtype=dtype,
1079 order=order,
1080 copy=copy,
1081 force_all_finite=force_all_finite,
1082 ensure_2d=ensure_2d,
1083 allow_nd=allow_nd,
1084 ensure_min_samples=ensure_min_samples,
1085 ensure_min_features=ensure_min_features,
1086 estimator=estimator,
1087 input_name="X",
1088 )
1090 y = _check_y(y, multi_output=multi_output, y_numeric=y_numeric, estimator=estimator)
1092 check_consistent_length(X, y)
File ~/.pyenv/versions/3.9.7/envs/my397/lib/python3.9/site-packages/sklearn/utils/validation.py:856, in check_array(array, accept_sparse, accept_large_sparse, dtype, order, copy, force_all_finite, ensure_2d, allow_nd, ensure_min_samples, ensure_min_features, estimator, input_name)
854 array = array.astype(dtype, casting="unsafe", copy=False)
855 else:
--> 856 array = np.asarray(array, order=order, dtype=dtype)
857 except ComplexWarning as complex_warning:
858 raise ValueError(
859 "Complex data not supported\n{}\n".format(array)
860 ) from complex_warning
File ~/.pyenv/versions/3.9.7/envs/my397/lib/python3.9/site-packages/pandas/core/generic.py:2064, in NDFrame.__array__(self, dtype)
2063 def __array__(self, dtype: npt.DTypeLike | None = None) -> np.ndarray:
-> 2064 return np.asarray(self._values, dtype=dtype)
ValueError: could not convert string to float: 'e'
[23]:
# El error proviene porque los SVM tan solo funcionan con variables continuas
# entonces, podemos transformar variables categoricas a discrete integer values !
from sklearn.preprocessing import LabelEncoder
le = LabelEncoder()
for i in df.columns:
df[i] = le.fit_transform(df[i])
print(df.head())
class cap-shape cap-surface cap-color bruises odor gill-attachment \
0 1 5 2 4 1 6 1
1 0 5 2 9 1 0 1
2 0 0 2 8 1 3 1
3 1 5 3 8 1 6 1
4 0 5 2 3 0 5 1
gill-spacing gill-size gill-color ... stalk-surface-below-ring \
0 0 1 4 ... 2
1 0 0 4 ... 2
2 0 0 5 ... 2
3 0 1 5 ... 2
4 1 0 4 ... 2
stalk-color-above-ring stalk-color-below-ring veil-type veil-color \
0 7 7 0 2
1 7 7 0 2
2 7 7 0 2
3 7 7 0 2
4 7 7 0 2
ring-number ring-type spore-print-color population habitat
0 1 4 2 3 5
1 1 4 3 2 1
2 1 4 3 2 3
3 1 4 2 3 5
4 1 0 3 0 1
[5 rows x 23 columns]
[24]:
x_train, x_test, y_train, y_test = train_test_split(df.drop('population', axis=1),df['population'], test_size=0.2,random_state=0)
[25]:
clf = SVC(C=1.0, kernel="" random_state=0)
clf.fit(x_train, y_train)
[25]:
SVC(random_state=0)In a Jupyter environment, please rerun this cell to show the HTML representation or trust the notebook.
On GitHub, the HTML representation is unable to render, please try loading this page with nbviewer.org.
SVC(random_state=0)
[11]:
y_pred = clf.predict(x_test)
[12]:
from sklearn.metrics import classification_report
print(classification_report(y_test, y_pred))
precision recall f1-score support
0 0.47 1.00 0.64 65
1 0.93 0.66 0.77 62
2 0.17 0.01 0.02 93
3 0.46 0.44 0.45 237
4 0.69 0.83 0.76 827
5 0.42 0.27 0.33 341
accuracy 0.61 1625
macro avg 0.52 0.54 0.49 1625
weighted avg 0.57 0.61 0.58 1625
La evaluación de un modelo de entrenamiento
La evaluación tiene como propósito dar validez y obtener confianza sobre el modelo propuesto. Dos perspectivas: Cualitativo y Cuantitativo. - Cualitativamente. ¿El modelo alcanza los objetivos de negocio, ayudando a la toma de decisiones? ¿Es posible integrarlo? ¿Se puede adaptar al stack tecnológico donde ha de aplicarse?, etc. - Cuantitativamente. Existen métricas sobre el rendimiento del consumo de recursos y el desempeño del modelo sobre el error de los resultados.
Métricas
Las principales métricas depende del modelo. Generalmente, en regresión y clasificación las métricas son.
En Clasificación:
Source: NillsF blog
Accuracy: número de predicciones correctas como ratio de todas las predicciones hechas $ \frac{TP+TN}{Total}.$
Precision: porcentaje de resultados positivos correctos sobre el total de resultados positivos \(\frac{TP}{Results} = \frac{TP}{TP+FP}\)
Recall: \(\frac{TP}{Predictive Results} = \frac{TP}{TP+FN}\)
f1-score: es la media harmónica de la precisión y el recall: \(\frac{2}{recall^{-1}+precision^{-1}}\)
Area under curve (AUC): para problemas de clasificación binaria. La Receiver Operating Characteristic (ROC) curva de probabilidad y AUC representa el grado o medida de separación. Expresa el grado del modelo para distingir ambas clases.
Confusion matrix: esta compuesta por la matriz de valores actuales x los valores de predicción .
\[\begin{split}\begin{equation} \begin{pmatrix} TP & FN \\ FP & TN \end{pmatrix} \end{equation}\end{split}\]
En Regresión: - Mean absolute error (MAE) es la suma de las diferencias absolutas entre las predicciones y el valor actual. - Mean squared error (MSE) representa la desviación estándard de las diferencias entre los valores de predicción y los valores observados. - R squared (:math:`R^2`) es un indicador de la bondad del entreno de la predicción al actual valor. - Adjusted R squared (Adj-:math:`R^2`) muestra como se ajusta la curva o linea de \(R^2\) para los valores del modelo.
Éstas y muchas más métricas están implementadas en la librería de Sci-kit - https://scikit-learn.org/stable/modules/classes.html#module-sklearn.metrics - https://scikit-learn.org/stable/modules/model_evaluation.html
Nota: Iremos viendo estas métricas a lo largo de las unidades siguientes
[13]:
from sklearn.metrics import accuracy_score
accuracy_score(y_test, y_pred)
[13]:
0.6092307692307692
[14]:
from sklearn import metrics
metrics.precision_score(y_test,y_pred,average="weighted")
[14]:
0.5725371139120128
[26]:
print("Recall:",metrics.recall_score(y_test,y_pred,average="weighted"))
print("F1_score:",metrics.f1_score(y_test,y_pred,average="weighted"))
Recall: 0.6092307692307692
F1_score: 0.5759066983339758
Rendimiento Computacional
Cada operación tiene un coste computacional. No sólo es tiempo sino memoria para almacenar las variables y valores intermedios. Las CPU/GPU tienen una capacidad computacional que suele medirse en Operaciones por Segundo. - MIPS - MFLOPS / GFLOPS
Ese ratio influye en el tiempo de ejecución de los modelos.
El lenguaje de programación y su compilador ayudan a obtener el mejor uso de la CPU/GPU. Son muchas las decisiones que se toman en la ejecución de un script. ¿Cuántos cores dispone una CPU/GPU para realizar la tarea? ¿Organización de las tareas y de las variables en memoría?, etc.
Librerías como Tensorflow[https://www.tensorflow.org/?hl=es-419] tienen funcionalidades que explotan los recursos que nos ofrecen las GPU en la computación paralela de operaciones.
gpus = tf.config.list_physical_devices('GPU')
if gpus:
# Restrict TensorFlow to only allocate 1GB of memory on the first GPU
try:
tf.config.set_logical_device_configuration(
gpus[0],
[tf.config.LogicalDeviceConfiguration(memory_limit=1024)])
logical_gpus = tf.config.list_logical_devices('GPU')
print(len(gpus), "Physical GPUs,", len(logical_gpus), "Logical GPUs")
except RuntimeError as e:
# Virtual devices must be set before GPUs have been initialized
print(e)
En general, hay dos modelos de computación para optimizar la ejecución de análisis: el uso de sistemas distribuidos (i.e. Cloud Computing) y la programación paralela.
En cualquier caso, la complejidad del modelo y la influencia de los datos (y su representación) tienen consecuencias sobre el rendimiento computacional y, por ende, en su aplicabilidad en un entorno real de explotación donde el tiempo de respuesta hacia el usuario sea razonable. Por ejemplo, un sistema de recomendación de productos que han de cargarse dinámicamente en una página web (i.e. Amazon)
https://scikit-learn.org/0.15/modules/computational_performance.html
Isaac Lera and Gabriel Moya Universitat de les Illes Balears isaac.lera@uib.edu, gabriel.moya@uib.edu