03E6A377-C3F6-462B-AAEC-EC669E55FE73.jpeg

Full Transcript

## TAREA 1 ### Introducción El objetivo de esta tarea es implementar clasificadores lineales (Perceptron, Winnow) y no lineales (Adaline, Regresión Logística) desde cero, utilizando Python. Los algoritmos implementados se entrenan con el conjunto de datos Iris para compararlos en un problema senci...

## TAREA 1 ### Introducción El objetivo de esta tarea es implementar clasificadores lineales (Perceptron, Winnow) y no lineales (Adaline, Regresión Logística) desde cero, utilizando Python. Los algoritmos implementados se entrenan con el conjunto de datos Iris para compararlos en un problema sencillo. #### El conjunto de datos Iris El conjunto de datos Iris es un conjunto de datos multivariante introducido por Ronald Fisher en 1936. El conjunto de datos contiene 50 muestras de cada una de tres especies de Iris (Iris setosa, Iris virginica e Iris versicolor). Se midieron cuatro características de cada muestra: el largo y el ancho de los sépalos y pétalos, en centímetros. Basado en la combinación de estas cuatro características, Fisher desarrolló un modelo discriminante lineal para distinguir las especies entre sí. #### Métricas de evaluación Para evaluar el rendimiento de los clasificadores, se utilizarán las siguientes métricas: * Exactitud (Accuracy): Es la proporción de predicciones correctas entre el número total de casos evaluados. * Precisión (Precision): Es la proporción de verdaderos positivos entre los casos predichos como positivos. * Exhaustividad (Recall): Es la proporción de verdaderos positivos entre los casos que realmente son positivos. * Puntuación F1 (F1 score): Es la media armónica ponderada de la precisión y la exhaustividad. ### Perceptrón El Perceptrón es un algoritmo de aprendizaje supervisado para clasificación binaria. Funciona aprendiendo los coeficientes de ponderación para las características que mejor predicen la clase de la instancia. El Perceptrón es un algoritmo lineal, lo que significa que solo puede aprender patrones que son linealmente separables. #### Algoritmo 1. Inicializar los pesos a cero o a valores aleatorios pequeños. 2. Para cada instancia de entrenamiento: * Calcular la salida del perceptrón: $$ \text{output} = \text{step\_function}(\mathbf{w} \cdot \mathbf{x} + b) $$ donde $\mathbf{w}$ son los pesos, $\mathbf{x}$ son las características de entrada, $b$ es el sesgo (bias) y $\text{step\_function}$ es una función escalón que devuelve 1 si la entrada es mayor que 0, y 0 en caso contrario. * Actualizar los pesos si la predicción es incorrecta: $$ \mathbf{w} = \mathbf{w} + \text{learning\_rate} \cdot (y - \text{output}) \cdot \mathbf{x} $$ $$ b = b + \text{learning\_rate} \cdot (y - \text{output}) $$ donde $\text{learning\_rate}$ es la tasa de aprendizaje, e $y$ es la etiqueta verdadera. 3. Repetir el paso 2 hasta que el algoritmo converja o se alcance un número máximo de iteraciones. #### Implementación ```python class Perceptron: def __init__(self, learning_rate=0.01, n_iters=1000): self.lr = learning_rate self.n_iters = n_iters self.weights = None self.bias = None def fit(self, X, y): n_samples, n_features = X.shape # init weights self.weights = np.zeros(n_features) self.bias = 0 # adjust weights for _ in range(self.n_iters): for idx, x_i in enumerate(X): linear_output = np.dot(x_i, self.weights) + self.bias y_predicted = self.unit_step_func(linear_output) update = self.lr * (y[idx] - y_predicted) self.weights += update * x_i self.bias += update def predict(self, X): linear_output = np.dot(X, self.weights) + self.bias y_predicted = self.unit_step_func(linear_output) return y_predicted def unit_step_func(self, x): return np.where(x >= 0, 1, 0) ``` #### Experimentación Para experimentar con el Perceptrón, se utiliza el conjunto de datos Iris. Primero, se cargan los datos y se dividen en conjuntos de entrenamiento y prueba. Luego, se crea una instancia del Perceptrón y se entrena con los datos de entrenamiento. Finalmente, se evalúa el rendimiento del Perceptrón en los datos de prueba utilizando las métricas de evaluación mencionadas anteriormente. ### Winnow El algoritmo Winnow es un algoritmo de aprendizaje supervisado para clasificación binaria. Es similar al Perceptrón, pero utiliza una regla de actualización multiplicativa para los pesos. Esto lo hace más robusto al ruido y a las características irrelevantes. #### Algoritmo 1. Inicializar los pesos a 1. 2. Para cada instancia de entrenamiento: * Calcular la salida del Winnow: $$ \text{output} = \text{step\_function}(\mathbf{w} \cdot \mathbf{x}) $$ donde $\mathbf{w}$ son los pesos, $\mathbf{x}$ son las características de entrada y $\text{step\_function}$ es una función escalón que devuelve 1 si la entrada es mayor que 0, y 0 en caso contrario. * Actualizar los pesos si la predicción es incorrecta: * Si la predicción es 0 y la etiqueta verdadera es 1: $$ w_i = w_i \cdot \alpha $$ * Si la predicción es 1 y la etiqueta verdadera es 0: $$ w_i = w_i / \alpha $$ donde $\alpha > 1$ es el factor de promoción/democión. 3. Repetir el paso 2 hasta que el algoritmo converja o se alcance un número máximo de iteraciones. #### Implementación ```python class Winnow: def __init__(self, alpha=1.2, n_iters=1000): self.alpha = alpha self.n_iters = n_iters self.weights = None def fit(self, X, y): n_samples, n_features = X.shape # init weights self.weights = np.ones(n_features) # adjust weights for _ in range(self.n_iters): for idx, x_i in enumerate(X): linear_output = np.dot(x_i, self.weights) y_predicted = self.unit_step_func(linear_output) if y[idx] == 1 and y_predicted == 0: self.weights *= self.alpha elif y[idx] == 0 and y_predicted == 1: self.weights /= self.alpha def predict(self, X): linear_output = np.dot(X, self.weights) y_predicted = self.unit_step_func(linear_output) return y_predicted def unit_step_func(self, x): return np.where(x >= 0, 1, 0) ``` #### Experimentación De manera similar al Perceptrón, se experimenta con el algoritmo Winnow utilizando el conjunto de datos Iris y las mismas métricas de evaluación. ### Adaline (Adaptive Linear Neuron) Adaline es una mejora del Perceptrón. En lugar de utilizar una función escalón para predecir la clase, utiliza una función de activación lineal. Esto permite que el algoritmo aprenda una función continua que puede ser utilizada para predecir la clase. #### Algoritmo 1. Inicializar los pesos a cero o a valores aleatorios pequeños. 2. Para cada instancia de entrenamiento: * Calcular la salida del Adaline: $$ \text{output} = \mathbf{w} \cdot \mathbf{x} + b $$ donde $\mathbf{w}$ son los pesos, $\mathbf{x}$ son las características de entrada y $b$ es el sesgo. * Calcular la predicción utilizando una función escalón: $$ \text{prediction} = \text{step\_function}(\text{output}) $$ * Actualizar los pesos utilizando la regla de actualización de Widrow-Hoff: $$ \mathbf{w} = \mathbf{w} + \text{learning\_rate} \cdot (y - \text{output}) \cdot \mathbf{x} $$ $$ b = b + \text{learning\_rate} \cdot (y - \text{output}) $$ donde $\text{learning\_rate}$ es la tasa de aprendizaje e $y$ es la etiqueta verdadera. 3. Repetir el paso 2 hasta que el algoritmo converja o se alcance un número máximo de iteraciones. #### Implementación ```python class AdalineGD: """ADAptive LInear NEuron classifier.""" def __init__(self, eta=0.01, n_iter=50, random_state=1): self.eta = eta self.n_iter = n_iter self.random_state = random_state def fit(self, X, y): """ Fit training data. Parameters ---------- X : {array-like}, shape = [n_samples, n_features] Training vectors, where n_samples is the number of samples and n_features is the number of features. y : array-like, shape = [n_samples] Target values. Returns ------- self : object """ rgen = np.random.RandomState(self.random_state) self.w_ = rgen.normal(loc=0.0, scale=0.01, size=X.shape) self.b_ = np.float_(0.) self.losses_ = [] for i in range(self.n_iter): net_input = self.net_input(X) output = self.activation(net_input) errors = (y - output) self.w_ += self.eta * 2.0 * X.T.dot(errors) / X.shape self.b_ += self.eta * 2.0 * errors.sum() / X.shape loss = (errors**2).sum() / (2.0 * X.shape) self.losses_.append(loss) return self def net_input(self, X): """Calculate net input""" return np.dot(X, self.w_) + self.b_ def activation(self, X): """Compute linear activation""" return X def predict(self, X): """Return class label after unit step""" return np.where(self.activation(self.net_input(X)) >= 0.5, 1, 0) ``` #### Experimentación Al igual que con los clasificadores anteriores, se evalúa Adaline en el conjunto de datos Iris, utilizando las métricas de evaluación estándar. ### Regresión Logística La Regresión Logística es un algoritmo de clasificación lineal que utiliza una función logística para predecir la probabilidad de que una instancia pertenezca a una clase. #### Algoritmo 1. Inicializar los pesos a cero o a valores aleatorios pequeños. 2. Para cada instancia de entrenamiento: * Calcular la salida de la Regresión Logística: $$ \text{output} = \sigma(\mathbf{w} \cdot \mathbf{x} + b) $$ donde $\sigma$ es la función sigmoide, $\mathbf{w}$ son los pesos, $\mathbf{x}$ son las características de entrada y $b$ es el sesgo. * Actualizar los pesos utilizando el gradiente descendente: $$ \mathbf{w} = \mathbf{w} - \text{learning\_rate} \cdot (\text{output} - y) \cdot \mathbf{x} $$ $$ b = b - \text{learning\_rate} \cdot (\text{output} - y) $$ donde $\text{learning\_rate}$ es la tasa de aprendizaje e $y$ es la etiqueta verdadera. 3. Repetir el paso 2 hasta que el algoritmo converja o se alcance un número máximo de iteraciones. #### Implementación ```python class LogisticRegressionGD: """Logistic Regression classifier using gradient descent.""" def __init__(self, eta=0.01, n_iter=50, random_state=1): self.eta = eta self.n_iter = n_iter self.random_state = random_state def fit(self, X, y): """ Fit training data. Parameters ---------- X : {array-like}, shape = [n_samples, n_features] Training vectors, where n_samples is the number of samples and n_features is the number of features. y : array-like, shape = [n_samples] Target values. Returns ------- self : object """ rgen = np.random.RandomState(self.random_state) self.w_ = rgen.normal(loc=0.0, scale=0.01, size=X.shape) self.b_ = np.float_(0.) self.losses_ = [] for i in range(self.n_iter): net_input = self.net_input(X) output = self.activation(net_input) errors = (y - output) self.w_ += self.eta * 2.0 * X.T.dot(errors) / X.shape self.b_ += self.eta * 2.0 * errors.sum() / X.shape loss = (-y.dot(np.log(output)) - ((1 - y).dot(np.log(1 - output))) / X.shape) self.losses_.append(loss) return self def net_input(self, X): """Calculate net input""" return np.dot(X, self.w_) + self.b_ def activation(self, z): """Compute logistic sigmoid activation""" return 1. / (1. + np.exp(-np.clip(z, -250, 250))) def predict(self, X): """Return class label after unit step""" return np.where(self.activation(self.net_input(X)) >= 0.5, 1, 0) ``` #### Experimentación La Regresión Logística se evalúa en el conjunto de datos Iris utilizando las métricas de evaluación estándar. ### Comparación de los clasificadores Finalmente, se comparan los clasificadores Perceptrón, Winnow, Adaline y Regresión Logística en términos de su rendimiento en el conjunto de datos Iris. Se analizan las métricas de evaluación para determinar cuál de los clasificadores es el más adecuado para este problema en particular. También se discuten las ventajas y desventajas de cada clasificador en términos de su complejidad computacional, interpretabilidad y robustez al ruido.