Construir proyecto simple con Machine Learning

Aprendizaje automático es una aplicación de inteligencia artificial que proporciona a los sistemas la capacidad de aprender y mejorar automáticamente de la experiencia sin ser programado explícitamente. La imagen de arriba muestra cómo una máquina puede aprender a tocar un instrumento musical con la capacitación correcta. Este artículo cubre algunos de los conceptos básicos, funciones y algoritmos para ayudar a los principiantes a comenzar con el aprendizaje automático. Entonces, comencemos nuestro viaje construyendo un proyecto de clasificación simple.

¿Qué estamos construyendo?

Estamos haciendo un proyecto de clasificación para clasificar tres conjuntos de flores de Iris: Iris_setosa (1), Iris_versicolor (2) e Iris_verginica ( 3).

Puede consultarlos para comprender estos proyectos de Iris:

Se nos ha proporcionado un conjunto de datos de 150 ejemplos de capacitación (es decir, 50 para cada flor) con ‘4’ características para cada flor. Las características de las flores son las siguientes:

  1. Longitud del sépalo en cm
  2. Anchura del sépalo en cm
  3. Longitud del pétalo en cm
  4. Anchura del pétalo en cm

Tenemos que entrenar nuestro algoritmo de tal manera que Podría clasificar cualquier ejemplo en una de las 3 especies. Puede echar un vistazo al conjunto de datos proporcionado en el archivo Iris_flower.txt. Espero que tengas la idea de lo que vamos a construir.

Algunos requisitos previos antes de comenzar

Debes tener Octave o Matlab para implementar este proyecto. Recomiendo usar Octave porque es gratis y compatible para este proyecto. Puede descargar Octave aquí:

Descargar

En segundo lugar, debe tener un conocimiento básico de las matrices, los comandos de Octave y algunos algoritmos. Con esto dicho, vamos a sumergirnos en las cosas divertidas … ¡Sí! el código.

Archivos incluidos en el proyecto

Vamos a hacer un montón de archivos (no se preocupe, es muy fácil) para el proyecto. Vamos a enumerarlos para mantener todo esto estructurado:

  1. Iris_main.m
  2. lrCostFunction.m
  3. oneVsAll.m
  4. predictOneVsAll.m
  5. sigmoid.m
  6. Iris_flower.txt

Iris_flower.txt

Comencemos por mirar el conjunto de datos que se nos ha proporcionado:

https://archive.ics.uci.edu/ml/machine-learning-databases/iris/iris.data [19659017] Puedes ver que hay 4 características (ya mencionadas anteriormente) para cada ejemplo de entrenamiento separados por comas y el nombre de las respectivas especies de la flor. Aquí, hemos usado los nombres de las flores, pero en la práctica lo vamos a reemplazar con los números 1, 2 y 3 respectivamente.

Iris_main.m

Este archivo es como el esqueleto de nuestro proyecto porque contiene Todas las funciones de las declaraciones de llamada. Veamos el archivo e intentemos comprender el código completo desde el principio.

% Este es el proyecto de detección de flores del iris 
% que se nos da para identificar 3 tipos de flores 
% según un conjunto de entrenamiento 
% Las funciones incluidas son 
% lrCostFunction.m (función de costo de regresión logística) 
% oneVsAll.m 
% predictOneVsAll.m 
% sigmoid.m 
% fmincg.m [1965902533] borrar; cerrar todo; clc; 
    
% cargando los datos de iris_flower.txt 
 fprintf (& # 039; cargando los datos ...  n & # 039;); 
    
% todo el conjunto de datos está almacenado en una matriz & # 039; datos & # 039; 
    
 datos = carga (& # 039; iris_flower.txt & # 039;); % de datos es una matriz de 150x5 
    
% almacena todas las características de cada entrenamiento 
% establecido en la matriz X, y el correspondiente 
% resulta en la matriz y
 X = datos (:, 1: 4); % Matriz de 150x4
 y = datos (:, 5); % 150x1 matrix
 fprintf (& # 039; Programa pausado. Presione enter para continuar.  N & # 039;); 
 pause; 
% Nota: Agregamos una columna de unos a X para equilibrar 
% el término de sesgo en theta.
 X = [ones(size(X,1),1) X];
 fprintf (& # 039; Running oneVsall Algorithm ...  n & # 039;); 
    
% número de clases significa diferentes tipos de flores
 num_of_classes = 3; 
 lambda = 0.1; 
    [all_theta]  = oneVsAll (X, y, num_of_classes, lambda);
 fprintf (& # 039; Programa pausado. Pulse Intro para continuar.  n & # 039;); 
 pause; 
 fprintf (& # 039; ==== Running prediction ===  n & # 039;); 
   
    p = predictOneVsAll (X, all_theta); 
 % Calculamos la precisión de nuestra hipótesis
 precisión = media ((p == datos (:, 5)) * 100); 
 fprintf (& # 039; Precisión del algoritmo =% f  n & # 039 ;, precisión); 
   
   % comprueba las predicciones Vs Resultado original 
 fprintf (& # 039; P OVal  n  n & # 039;); 
   
    para i = 1: tamaño (X, 1) 
 fprintf (& # 039;% d% d  n & # 039; p (i), y (i)); 
 final 
     
    fprintf (& # 039; Programa en pausa. Presione enter para continuar.  N & # 039;); 
 pause; 
 fprintf (& # 039; END  n & # 039;);

Comenzamos cargando nuestro conjunto de datos en nuestra matriz de ‘datos’ y luego divida los datos en la matriz ‘X’ y ‘y’. Agregamos una columna adicional de unos en nuestra matriz X para equilibrar el término de sesgo en la matriz “theta”. Una vez que hayamos terminado con las partes de inicialización, veamos nuestra función de costo

Función de costo (lrCostFunction.m)

La función de costo usa el Modelo de Regresión Logística. La siguiente figura explica la función de hipótesis (h) para el modelo

La gráfica para la función sigmoidea (‘g’) o la función logística se muestra arriba. No importa cuál sea la entrada, la función genera un valor entre 0 y 1.

Hemos implementado esta función de costo en nuestro lrCostFunction.m. Veamos qué hace.
 función [J, grad] = lrCostFunction (theta, X, y, lambda)
 m = tamaño (X, 1); 
 n = tamaño (X, 2); 
 J = 0; 
 grad = ceros (tamaño (theta));
 h = sigmoide (X * theta); 
% aquí h es nuestra función de hipótesis 
 J = (1 / m ) * (- y & # 039; * log (h) - (1 - y) & # 039; * log (1 - h)); 
 J_reg = (lambda / (2 * m)) * (suma (theta. ^ 2) - theta (1) ^ 2); 
 J = J + J_reg; 
 grad = (1 / m ). * (X & # 039; * (h - y)) + (lambda / m). * (Theta); 
 grad (1) = (1 / m) * (suma (h - y));
 final

Tomamos theta, X, y y lambda como entrada a la función. Tenemos que devolver “J” la función de costo y “grad” el gradiente. La lambda es el término de regularización que usamos para evitar el ajuste excesivo del conjunto de datos. No vamos a discutir la regularización aquí, si desea obtener más información sobre la regularización, siga el enlace que se proporciona a continuación:

Regresión logística regularizada – Regularización | Coursera

Hemos utilizado la regularización tanto en la función de costo como en el gradiente. Si recuerda el concepto de regularización, no regularizamos el término de sesgo (theta0) tanto en la función de costo como en el gradiente. Ahora, echemos un vistazo más profundo al código. Primero calculamos la hipótesis (“h”) y la usamos para calcular J, que es nuestra función de costo. Para eliminar el término de sesgo de la regularización, restamos el cuadrado del término de sesgo de la suma del cuadrado de todos los valores theta. Luego calculamos el gradiente (todos los valores theta se calculan simultáneamente) y para eliminar el término de sesgo de la regularización, actualizamos el valor de grad (1) como se muestra arriba.

Función sigmoide

Calcula el sigmoide del argumento ( matriz o vector) que resulta en un valor entre 0 y 1.

 función [ret_z] = sigmoide (z) 
% toma z como argumento y devuelve su sigmoide 
 ret_z = 1. / (1 + exp (-z));
% Nota: Aquí, hemos pasado z = X * theta
 end

oneVsAll algorithm

Este algoritmo divide el conjunto de datos dado a nosotros ( aquí 3 conjuntos) en 2 conjuntos de datos e intenta encontrar el ajuste perfecto o nuestra función de hipótesis o, más precisamente, los parámetros theta necesarios para adaptarse a los 2 conjuntos de datos creados. Lo hace para todos los diferentes conjuntos (todas las diferentes especies de flores). Entonces, para el conjunto de entrenamiento dado tendríamos 3 funciones de hipótesis diferentes que almacenamos en nuestra matriz “all_theta”. Dicho esto, veamos el código de oneVsAll para ver cómo funciona.

 El algoritmo% oneVsAll divide el conjunto de datos en 
% 2 clases y encuentra los valores theta 
% óptimos para las respectivas clases
 ] = oneVsAll (X, y, num_of_classes, lambda) 
 m = tamaño (X, 1); 
 n = tamaño (X, 2); 
 all_theta = ceros (num_of_classes, n); [19659073]% para este ejemplo tenemos 3 clases 
  
   para i = 1: num_of_classes 
% Establezca los parámetros para nuestra optimización avanzada 
% ================= ================= 
 initial_theta = ceros (n, 1); 
 opciones = optimset (& # 039; GradObj & # 039;, & # 039; en & # 039;, & # 039; MaxIter & # 039;, 50); 
% ========== ========================= 
 costfunc = @ (t) (lrCostFunction (t, X, y == i, lambda)); 
    [theta]  = fmincg (costfunc, initial_theta, opciones); 
% (y == i) divide el conjunto de entrenamiento en 2 clases distintas 
% y luego trata de encontrar los valores óptimos de theta 
 para ajustarse al conjunto de datos 
    
 all_theta (i, :) = theta; % 3x5 matrix 
    
 end
 end

Ahora, veamos algunos de los detalles de esta función. Los argumentos para la función oneVsAll son X (150×5), y (150×1), num_of_classes (3) y lambda. Hemos creado la matriz “all_theta” para almacenar los valores theta que mejor se ajustan a los que la función “fmincg” va a devolver. Dentro del bucle, primero tenemos la initial_theta que actúa como nuestro punto de partida para minimizar J (tomado como un vector cero). Puede pensarlo como coordenadas que se establecen en cero con el objetivo final para alcanzar las coordenadas de ajuste perfecto. Luego tenemos el objeto de “opciones” que pasamos a nuestra optimización avanzada como argumento. El objeto de opciones establece ‘GradObj’ como ‘on’, lo que significa que queremos considerar gradiente para minimizar nuestra función de costo, seguido de ‘MaxIter’ que nos dice la cantidad de iteraciones que queremos realizar para minimizar nuestra función de costo. A continuación, pasamos nuestra función de costo con los argumentos initial_theta (como ‘t’), X, (y == i) y lambda en fmincg, lo que minimiza nuestra función de costo. Minimizar la función de costo devuelve un theta óptimo (los mejores valores de ajuste para theta) que almacenamos en nuestra matriz all_theta para cada iteración.

predictOneVsAll.m

En esta función pasamos nuestra matriz all_theta y matriz X para predecir el tipo De especies para todos los ejemplos de entrenamiento. Veamos el código para la función y analicemos algunos conceptos importantes.

% aquí usaremos todos los ejemplos de entrenamiento 
% y encontraremos sus respectivas clases 
% usando la función de hipótesis sigmoide 
 % El valor en el que la hipótesis devuelve el valor máximo es nuestro valor predicho que almacenamos en p 
% Nota: p es un vector de columna
 función [p] = predictOneVsAll (X, all_theta) 
 m = tamaño (X, 1); 
 p = ceros (m, 1); 
 h = sigmoide (X * all_theta & # 039;); 
  [max_val,max_ind]  = max (h, []2); 
 p = max_ind;
 end

Definimos un vector de predicción ‘p’ del tamaño de nuestro vector X para almacenar nuestro valor predicho para todos los ejemplos de entrenamiento. Para realizar esta tarea, calculamos el vector de hipótesis que devuelve un valor de hipótesis para cada una de las clases (cada una de las 3 especies). El valor máximo predicho entre los tres valores se tomaría como la predicción. La función max devuelve el valor máximo (max_val) así como el índice para max_value. Necesitamos el valor de índice porque necesitamos predecir cualquiera de los tres valores 1,2 o 3. Nuestro vector ‘p’ contendría 150 predicciones y las usaremos para calcular la precisión.

Precisión

 p = predictOneVsAll (X, all_theta); 
 precisión = media ((p == datos (:, 5)) * 100); 
 fprintf (& # 039; Precisión del algoritmo =% f  n & # 039 ;, precisión);

El vector de precisión contiene la fracción de las respuestas correctas predichas que llega a ser del 97%. Esto significa que nuestro algoritmo realiza el 97% con precisión.

fmincg

Esta es una optimización avanzada que se utiliza para minimizar una función de acuerdo con los argumentos dados. No necesitas conocer este código en este momento y es bastante avanzado para que lo entiendas como un principiante, así que solo concéntrate en su funcionamiento.

 función [X, fX, i] = fmincg (f, X, opciones, P1, P2, P3, P4, P5)
 si existe (& # 039; opciones & # 039 ;, & # 039; var & # 039;) && ~ isempty (opciones) && isfield (opciones, & # 039; MaxIter & # 039;) 
 longitud = opciones.MaxIter; 
 else 
 longitud = 100; 
 final
 RHO = 0.01; % un grupo de constantes para búsquedas de líneas 
 SIG = 0.5; % RHO y SIG son las constantes en las condiciones de Wolfe-Powell 
 INT = 0.1; % no vuelva a evaluar dentro de 0.1 del límite del corchete actual 
 EXT = 3.0; % de extrapolación máximo 3 veces el corchete actual 
 MAX = 20; % máx. 20 evaluaciones de función por búsqueda de línea 
 RATIO = 100; % de relación de pendiente máxima permitida
 argstr = ['feval(f, X']; % componer cadena utilizada para llamar a la función 
 para i = 1: (nargin - 3) 
 argstr = [argstr, ',P', int2str(i)]; 
 end 
 argstr = [argstr, ')'];
 if max (size ( longitud)) == 2, rojo = longitud (2); longitud = longitud (1); si no rojo = 1; final 
 S = ['Iteration '];
 i = 0; % cero el contador de longitud de ejecución 
 ls_failed = 0; % no ha fallado la búsqueda de línea anterior 
 fX = []; 
[f1 df1]  = eval (argstr); % obtener el valor de la función y el gradiente 
 i = i + (longitud <0); % cuenta las épocas?! 
 s = -df1; % dirección de búsqueda es más pronunciada 
 d1 = -s & # 039; * s; % esta es la pendiente 
 z1 = rojo / (1-d1); % paso inicial es rojo / (| s | +1)
 mientras que i <abs (longitud)% mientras no está terminado 
 i = i + (longitud> 0); % cuenta iteraciones?!
 X0 = X; f0 = f1; df0 = df1; % hace una copia de los valores actuales 
 X = X + z1 * s; % comenzar búsqueda de línea 
  [f2 df2]  = eval (argstr); 
 i = i + (longitud <0); % recuento de épocas?! 
 d2 = df2 & # 039; * s; 
 f3 = f1; d3 = d1; z3 = -z1; % inicialice el punto 3 igual al punto 1 
 si longitud> 0, M = MAX; si no M = min (MAX, -length-i); fin 
 éxito = 0; límite = -1; % inicializar cantidades 
 mientras que 1 
 mientras que ((f2> f1 + z1 * RHO * d1) || (d2> -SIG * d1)) && (M> 0) 
 limit = z1; % apriete el soporte 
 si f2> f1 
 z2 = z3 - (0.5 * d3 * z3 * z3) / (d3 * z3 + f2-f3); % de ajuste cuadrático 
 else 
 A = 6 * (f2-f3) / z3 + 3 * (d2 + d3); % de ajuste cúbico 
 B = 3 * (f3-f2) -z3 * (d3 + 2 * d2); 
 z2 = (sqrt (B * B-A * d2 * z3 * z3) -B) / A; % de error numérico posible - ¡ok! 
 fin 
 si isnan (z2) || isinf (z2) 
 z2 = z3 / 2; % si tuviéramos un problema numérico, entonces bisect 
 end 
 z2 = max (min (z2, INT * z3), (1-INT) * z3); % no acepta demasiado cerca de los límites 
 z1 = z1 + z2; % actualizar el paso 
 X = X + z2 * s; 
      [f2 df2]  = eval (argstr); 
 M = M - 1; i = i + (longitud <0); % recuento de épocas?! 
 d2 = df2 & # 039; * s; 
 z3 = z3-z2; % z3 ahora es relativo a la ubicación de z2 
 final 
 si f2> f1 + z1 * RHO * d1 || d2> -SIG * d1 
 break; % esto es un error 
 elseif d2> SIG * d1 
 success = 1; descanso; % de éxito 
 elseif M == 0 
 break; % de falla 
 final 
 A = 6 * (f2-f3) / z3 + 3 * (d2 + d3); % de extrapolación cúbica 
 B = 3 * (f3-f2) -z3 * (d3 + 2 * d2); 
 z2 = -d2 * z3 * z3 / (B + sqrt (B * BA * d2 * z3 * z3)); % num. error posible - ¡ok! 
 if ~ isreal (z2) || isnan (z2) || isinf (z2) || z2 <0% num prob o signo equivocado? 
 si límite <-0.5% si no tenemos un límite superior 
 z2 = z1 * (EXT-1); % extrapola la cantidad máxima 
 else 
 z2 = (limit-z1) / 2; % de lo contrario bisecta 
 final 
 elseif (límite> -0.5) && (z2 + z1> límite)% extraplación más allá del máx? 
 z2 = (límite-z1) / 2; % bisect 
 elseif (límite  z1 * EXT)% de extrapolación más allá del límite 
 z2 = z1 * (EXT-1.0); % establecido en el límite de extrapolación 
 elseif z2 <-z3 * INT 
 z2 = -z3 * INT; 
 elseif (límite> -0.5) && (z2 <(límite-z1) * (1.0-INT) )% demasiado cerca del límite? 
 z2 = (límite-z1) * (1.0-INT); 
 final 
 f3 = f2; d3 = d2; z3 = -z2; % del punto de ajuste 3 igual al punto 2 
 z1 = z1 + z2; X = X + z2 * s; % actualización de estimaciones actuales 
    [f2 df2]  = eval (argstr); 
 M = M - 1; i = i + (longitud <0); % count epochs?! 
 d2 = df2 & # 039; * s; 
 final% búsqueda de final de línea
 si% de éxito si la búsqueda de línea tuvo éxito 
 f1 = f2; fX = [fX' f1] & # 039 ;; 
 fprintf (& # 039;% s% 4i | Coste:% 4.6e  r & # 039 ;, S, i, f1); 
 s = (df2 & # 039; * df2-df1 & # 039; * df2) / (df1 & # 039; * df1) * s - df2; % Dirección Polack-Ribiere 
 tmp = df1; df1 = df2; df2 = tmp; % swap derivados 
 d2 = df1 & # 039; * s; 
 si d2> 0% pendiente nueva debe ser negativa 
 s = -df1; % de lo contrario, utilice la dirección más inclinada 
 d2 = -s & # 039; * s; 
 final 
 z1 = z1 * min (RATIO, d1 / (d2-realmin)); % de relación de pendiente pero RATIO máx. 
 d1 = d2; 
 ls_failed = 0; % esta búsqueda de línea no falló 
 de lo contrario 
 X = X0; f1 = f0; df1 = df0; % de punto de restauración desde la búsqueda de línea anterior 
 si ls_failed || i> abs (longitud)% de búsqueda de línea falló dos veces seguidas 
 break; % o nos quedamos sin tiempo, por lo que abandonamos 
 end 
 tmp = df1; df1 = df2; df2 = tmp; % swap derivados 
 s = -df1; % try steepest 
 d1 = -s & # 039; * s; 
 z1 = 1 / (1-d1); 
 ls_failed = 1; % esta búsqueda de línea falló 
 fin 
 si existe (& # 039; OCTAVE_VERSION & # 039;) 
 fflush (stdout); 
 end 
 end 
 fprintf (& # 039;  n & # 039;);
 si% de éxito si la búsqueda de línea tuvo éxito 
 f1 = f2; fX = [fX' f1] & # 039 ;; 
 fprintf (& # 039;% s% 4i | Coste:% 4.6e  r & # 039 ;, S, i, f1); 
 s = (df2 & # 039; * df2-df1 & # 039; * df2) / (df1 & # 039; * df1) * s - df2; % Dirección Polack-Ribiere 
 tmp = df1; df1 = df2; df2 = tmp; % swap derivados 
 d2 = df1 & # 039; * s; 
 si d2> 0% pendiente nueva debe ser negativa 
 s = -df1; % de lo contrario, utilice la dirección más inclinada 
 d2 = -s & # 039; * s; 
 final 
 z1 = z1 * min (RATIO, d1 / (d2-realmin)); % de relación de pendiente pero RATIO máx. 
 d1 = d2; 
 ls_failed = 0; % esta búsqueda de línea no falló 
 de lo contrario 
 X = X0; f1 = f0; df1 = df0; % de punto de restauración antes de la búsqueda de línea fallida 
 si ls_failed || i> abs (longitud)% de búsqueda de línea falló dos veces seguidas 
 break; % o nos quedamos sin tiempo, por lo que abandonamos 
 end 
 tmp = df1; df1 = df2; df2 = tmp; % swap derivados 
 s = -df1; % try steepest 
 d1 = -s & # 039; * s; 
 z1 = 1 / (1-d1); 
 ls_failed = 1; % esta búsqueda de línea falló 
 fin 
 si existe (& # 039; OCTAVE_VERSION & # 039;) 
 fflush (stdout); 
 end 
 end 
 fprintf (& # 039;  n & # 039;);

Esta función devuelve los parámetros theta de mejor ajuste en nuestra matriz [theta] en la función ‘oneVsAll.m’ utilizada anteriormente. Alternativamente, puede usar la función ‘fminunc’ que hace el mismo trabajo que ‘fmincg’ pero se usa cuando hay menos características en los ejemplos de capacitación.

Conclusión

Este código apenas lleva tiempo para completarse una vez que conoce los conceptos Detrás de los algoritmos. Espero que hayas disfrutado haciendo este proyecto; si lo hiciste, siéntete libre de hacer clic en el botón “Aplaudir”.

 

Dejá un comentario