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:
- Longitud del sépalo en cm
- Anchura del sépalo en cm
- Longitud del pétalo en cm
- 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í:
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:
- Iris_main.m
- lrCostFunction.m
- oneVsAll.m
- predictOneVsAll.m
- sigmoid.m
- Iris_flower.txt
Iris_flower.txt
Comencemos por mirar el conjunto de datos que se nos ha proporcionado:
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”.