[PaddlePaddle] [Notas de estudio] Documentación oficial de PaddlePaddle: ① Use Python y NumPy para construir un modelo de red neuronal; ② Use PaddlePaddle para predecir los precios de la vivienda en Boston

1. Descripción general del aprendizaje automático y el aprendizaje profundo

1.1 La relación entre inteligencia artificial, aprendizaje automático y aprendizaje profundo

Los conceptos de inteligencia artificial, aprendizaje automático y aprendizaje profundo han sido muy populares en los últimos años, pero muchos profesionales tienen dificultades para explicar la relación entre ellos y los profanos están aún más confundidos. Antes de estudiar el aprendizaje profundo, comencemos con los orígenes de los tres conceptos. En resumen, las categorías técnicas cubiertas por inteligencia artificial, aprendizaje automático y aprendizaje profundo están disminuyendo gradualmente. La relación entre los tres se muestra en la figura siguiente, a saber: inteligencia artificial> aprendizaje automático> aprendizaje profundo.

Insertar descripción de la imagen aquí

La Inteligencia Artificial (IA) es el concepto más amplio: es una nueva ciencia técnica que desarrolla teorías, métodos, tecnologías y sistemas de aplicación para simular, ampliar y expandir la inteligencia humana. Dado que esta definición solo establece el objetivo y no limita el método, existen muchos métodos y ramas para lograr la inteligencia artificial, lo que hace que se convierta en una disciplina "mezcolanza". El aprendizaje automático (ML) es actualmente una forma relativamente eficaz de implementar la inteligencia artificial. El aprendizaje profundo (DL) es la rama más popular de los algoritmos de aprendizaje automático, ha logrado avances significativos en los últimos años y ha reemplazado a la mayoría de los algoritmos de aprendizaje automático tradicionales.

1.2 Aprendizaje automático

A diferencia de la inteligencia artificial, el aprendizaje automático, especialmente el aprendizaje supervisado, tiene una referencia más clara. El aprendizaje automático es un estudio especializado de cómo las computadoras pueden simular o implementar el comportamiento de aprendizaje humano para adquirir nuevos conocimientos o habilidades, reorganizar las estructuras de conocimiento existentes y mejorar continuamente su desempeño. Esta oración se siente un poco "nublada y confusa", lo que confunde a la gente. Analicémosla desde las dos dimensiones de la implementación y la metodología del aprendizaje automático para ayudar a los lectores a comprender los entresijos del aprendizaje automático con mayor claridad.

1.2.1 Implementación del aprendizaje automático

La implementación del aprendizaje automático se puede dividir en dos pasos: ① entrenamiento y ② predicción, similar a la inducción y deducción:

  • Inducción : abstrayendo las reglas generales de casos específicos, lo mismo ocurre con el "entrenamiento" en el aprendizaje automático. A partir de un cierto número de muestras (entrada del modelo conocido XXX y salida del modeloYYY ), aprende a generarYYY y entradaXXLa relación entre X (puede imaginarse como algún tipo de expresión).

  • Deducción : Deducir los resultados de casos específicos a partir de reglas generales, lo mismo ocurre con la "predicción" en el aprendizaje automático. Basado en YY obtenido del entrenamientoY yXXLa relación entre X , como una nueva entrada XX.X , calcula la salidaYYY. _ Por lo general, si la salida calculada por el modelo es consistente con la salida de la escena real, el modelo es efectivo.

1.2.2 Metodología de aprendizaje automático

La metodología del aprendizaje automático es similar al proceso de investigación científica humana. A continuación se toma el "conocimiento del aprendizaje automático del experimento de la segunda ley de Newton" como ejemplo para ayudar a los lectores a obtener una comprensión más profunda de la esencia de la metodología del aprendizaje automático (aprendizaje supervisado). ), es decir, en "aprendizaje automático" Se identificaron tres elementos clave del modelo durante el proceso de "pensamiento":

  1. hipótesis
  2. evaluar
  3. mejoramiento
1.2.2.1 Caso: La máquina aprende conocimientos a partir del experimento de la segunda ley de Newton

La segunda ley de Newton fue propuesta por Isaac Newton en su libro "Principios matemáticos de la filosofía natural" en 1687. Su expresión común es: la aceleración de un objeto es directamente proporcional a la fuerza, inversamente proporcional a la masa del objeto e inversamente proporcional. a la masa del objeto Proporcional al recíproco de la masa . La segunda ley del movimiento de Newton y la primera y tercera leyes juntas forman las leyes del movimiento de Newton, que describen las leyes básicas del movimiento en la mecánica clásica.

En los libros de texto de la escuela secundaria, existen dos métodos de diseño experimental para la segunda ley de Newton: ① Método de deslizamiento inclinado y ② Método de alambre horizontal, como se muestra en la figura.

Insertar descripción de la imagen aquí

Creo que muchos lectores tienen buenos recuerdos de sus días de juventud jugando con poleas y pequeños bloques de madera para realizar experimentos de física. A través de múltiples datos experimentales, la aceleración del bloque de madera bajo diferentes fuerzas se puede calcular como se muestra en la siguiente tabla.

frecuencia FuerzaXX_ _X AceleraciónYY _Y
1 4 2
2 4 2
norte 6 3

No es difícil adivinar, al observar los datos experimentales, que la aceleración del objeto aaa y forzarFFLa relación entre F debería ser lineal. Por lo tanto proponemos la hipótesisa = w ⋅ F a=w \cdot Fa=wF , dentro,aaa representa aceleración,FFF representa fuerza,www es el parámetro a determinar.

Mediante entrenamiento con una gran cantidad de datos experimentales, se determina el parámetro www es el recíproco de la masa del objeto1 m \frac{1}{m}metro1, es decir, se obtiene la fórmula del modelo completo a = F ⋅ 1 ma = F \cdot \frac{1}{m}a=Fmetro1. Cuando se conocen las fuerzas que actúan sobre un objeto, la aceleración del objeto se puede predecir rápidamente basándose en el modelo. Por ejemplo: el empuje del combustible sobre el cohete F = 10 F=10F=10 , la masa del cohetem = 2 m=2metro=2 , puedes obtener rápidamente la aceleración del cohetea = 5 a=5a=5 .

1.2.2.2 ¿Cómo determinar los parámetros del modelo?

Este interesante caso demuestra el proceso básico del aprendizaje automático, pero la implementación de un punto clave aún no está clara, a saber: cómo determinar los parámetros del modelo w = 1 mw=\frac{1}{m}w=metro1

El proceso de determinación de parámetros es similar a la forma en que los científicos proponen hipótesis: las hipótesis razonables pueden explicar mejor todos los datos de observación conocidos. Si en el futuro se observan nuevos datos que no se ajustan a las hipótesis teóricas, los científicos intentarán proponer nuevas hipótesis . Por ejemplo: en la historia de la astronomía, el movimiento de los cuerpos celestes se calculaba mediante una combinación de círculos grandes y círculos pequeños, en la Edad Media era posible ajustar los datos de observación. Sin embargo, con el avance de la Revolución Industrial europea, los equipos de observación astronómica se volvieron cada vez más potentes y las teorías existentes eran incapaces de explicar cada vez más datos de observación, lo que promovió el surgimiento de hipótesis teóricas que utilizan elipses para calcular el movimiento de los cuerpos celestes. Por tanto, la condición básica para que el modelo sea eficaz es poder ajustarse a muestras conocidas, lo que nos proporciona un plan de implementación para aprender modelos eficaces.

Insertar descripción de la imagen aquí

La imagen de arriba está en HH.H es la hipótesis del modelo, que trata sobre el parámetrowww e ingresaxxfunción de x , use H ( w , x ) H(w,x)H ( w ,x ) significa. El objetivo de optimización del modelo esH ( w , x ) H(w,x)H ( w ,La salida de x ) y la salida realYYY debe ser lo más consistente posible, y la diferencia entre los dos es la función de evaluación del efecto del modelo (cuanto menor sea la diferencia, mejor).

Entonces, el proceso de determinación de parámetros consiste en reducir continuamente la función de evaluación ( HHH Y Y Y brecha). Hasta que el modelo aprenda un parámetrowww , que minimiza el valor de la función de evaluación.La función de evaluación que mide la diferencia entre el valor predicho del modelo y el valor verdadero también se llama función de pérdida (Pérdida).

Supongamos que la máquina aprende conocimientos (parámetros del modelo ww ) intentando responder (minimizar la pérdida) una gran cantidad de ejercicios (muestras conocidas) correctamente.w ), y esperar el modelo H ( w , x ) H(w,x)representado por el conocimiento aprendidoH ( w ,x ) , responda preguntas del examen cuyas respuestas no sepa (muestra desconocida). Minimizar la pérdida es el objetivo de optimización del modelo.El método para lograr la minimización de pérdidas se denomina algoritmo de optimización, también conocido como algoritmo de búsqueda de soluciones (encontrar la solución paramétrica que minimiza la función de pérdida). Parámetrowww e ingresaxxLa estructura básica que constituye una fórmula se llama hipótesis. En el caso de la segunda ley de Newton, a partir de la observación de los datos, propusimos una hipótesis lineal, es decir, la fuerza y ​​la aceleración están relacionadas linealmente, expresadas mediante una ecuación lineal. Se puede ver que ①supuestos del modelo, ②función de evaluación(objetivo de pérdida/optimización) y ③algoritmo de optimizaciónson los tres elementos clave que constituyen el modelo.

1.2.2.3 Estructura del modelo

¿Cómo apoyan los supuestos del modelo, las funciones de evaluación y los algoritmos de optimización el proceso de aprendizaje automático? Como se muestra abajo.

Insertar descripción de la imagen aquí

  • Hipótesis del modelo : Hay miles de relaciones posibles en el mundo, exploración sin rumbo Y ← XY \leftarrow XYLa relación entre X es obviamente muy ineficiente. Por tanto, el espacio de hipótesis primero delimita las posibles relaciones que un modelo puede expresar, como se muestra en el círculo azul. La máquina buscará además el óptimo Y ← XY \leftarrow Xdentro del círculo hipotéticamente rodeadoYRelación X , es decir, determinar el parámetrowww .
  • Función de evaluación : Antes de buscar el óptimo, necesitamos definir qué es óptimo, es decir, evaluar a Y ← XY \leftarrow XYUn indicador de la calidad de la relación X. Por lo general, se mide si la relación puede ajustarse bien a las muestras de observación existentes y se toma el error de ajuste mínimo como objetivo de optimización.
  • Algoritmo de optimización : después de establecer el índice de evaluación, dentro del rango rodeado por la hipótesis, Y ← XY \leftarrowYEncuentre la relación X y este método para encontrar la solución óptima es el algoritmo de optimización. El algoritmo de optimización más estúpido es calcular la función de pérdida enumerando exhaustivamente todos los valores posibles de acuerdo con los parámetros posibles y conservar los parámetros que minimizan la función de pérdida como resultado final.

Del proceso anterior, se puede concluir que el proceso de aprendizaje automático es básicamente consistente con el proceso de aprendizaje de la segunda ley de Newton y se divide en tres etapas: hipótesis , evaluación y optimización :

  • Hipótesis : Observando la aceleración aaa y forzarFFDatos de observación de F , suponiendo aaa yffF es una relación lineal, es decir,a = w ⋅ F a=w \cdot Fa=wF. _
  • Evaluación : El efecto de ajuste sobre datos de observación conocidos es bueno, es decir, w ⋅ F w\cdot FwEl resultado del cálculo de F debe ser coherente con elaalo más cerca posible.
  • Optimización : en el parámetro wwEntre todos los valores posibles de w , se encuentra que w = 1 mw=\frac{1}{m}w=metro1Puede hacer que la evaluación sea la mejor (se ajuste mejor a la muestra observada).

El marco para que las máquinas realicen tareas de aprendizaje refleja que la esencia del aprendizaje es la "estimación de parámetros" (el aprendizaje es la estimación de parámetros)

La metodología anterior utiliza una representación más estandarizada como se muestra en la siguiente figura, la función objetivo desconocida fff , tomando las muestras de entrenamientoD = ( x 1 , y 1 ) , ( x 2 , y 2 ) , . . . , ( xn , yn ) D = (x_1, y_1), (x_2, y_2), ... , (x_n, y_n)D=( x1,y1) ,( x2,y2) ,... ,( xnorte,ynorte) se basa en. Del conjunto de hipótesisHHEn H , mediante el algoritmo de aprendizajeAA.A encuentra una funcióngggramo . siggg puede adaptarse mejor a la muestra de entrenamientoDDD , entonces se puede considerar la funciónggg está cerca de la función objetivoffF. _

Insertar descripción de la imagen aquí

Sobre esta base, se pueden aprender muchos problemas aparentemente completamente diferentes utilizando el mismo marco, como leyes científicas, reconocimiento de imágenes, traducción automática y respuesta automática a preguntas, etc.Sus objetivos de aprendizaje son adaptarse a una "gran fórmula " .f,Como se muestra abajo.

Insertar descripción de la imagen aquí

1.3 Aprendizaje profundo

La teoría de los algoritmos de aprendizaje automático maduró en la década de 1990 y logró el éxito en muchos campos, pero los días tranquilos sólo duraron hasta alrededor de 2010. Con la aparición de big data y la mejora de la potencia informática de las computadoras, han surgido modelos de aprendizaje profundo, que han cambiado en gran medida el panorama de las aplicaciones del aprendizaje automático. Hoy en día, la mayoría de las tareas de aprendizaje automático se pueden resolver utilizando modelos de aprendizaje profundo. Especialmente en campos como el habla, la visión por computadora y el procesamiento del lenguaje natural, los efectos de los modelos de aprendizaje profundo han mejorado significativamente en comparación con los algoritmos tradicionales de aprendizaje automático .

En comparación con los algoritmos tradicionales de aprendizaje automático, ¿qué mejoras ha realizado el aprendizaje profundo? De hecho, los dos son consistentes en su estructura teórica, a saber: supuestos del modelo, funciones de evaluación y algoritmos de optimización . La diferencia fundamental radica en la complejidad de los supuestos . Como se muestra en el segundo ejemplo (reconocimiento de imágenes) en la figura anterior, para fotografías de mujeres hermosas, el cerebro humano puede recibir señales ópticas coloridas y puede responder rápidamente que la imagen es de una mujer hermosa. Pero para una computadora, solo puede recibir una matriz digital. Para un concepto semántico de alto nivel como la belleza, la complejidad de la transformación de la información de píxeles a conceptos semánticos de alto nivel es inimaginable. Este proceso de conversión se muestra en la siguiente figura . .

Insertar descripción de la imagen aquí

Esta transformación ya no se puede expresar mediante fórmulas matemáticas., por lo que los investigadores se basaron en la estructura de las neuronas del cerebro humano y diseñaron un modelo de red neuronal, como se muestra en la siguiente figura.

Insertar descripción de la imagen aquí

La figura (a) muestra el diseño de la unidad básica de la red neuronal: el perceptrón. La forma en que procesa la información es muy similar a una sola neurona en el cerebro humano; la figura (b) muestra varias estructuras clásicas de la red neuronal (será explicado en detalle en capítulos posteriores) es similar a los diversos órganos con diferentes funciones formados en base a una gran cantidad de conexiones neuronales en el cerebro humano.

1.3.1 Conceptos básicos de redes neuronales

Las redes neuronales artificiales incluyen múltiples capas de redes neuronales, como: capa de convolución, capa completamente conectada, LSTM (memoria larga a corto plazo, red de memoria larga a corto plazo), etc. Cada capa incluye muchas neuronas, redes neuronales no lineales con más de tres Las capas se pueden llamar redes neuronales profundas (D-CNN). En términos simples, un modelo de aprendizaje profundo puede considerarse como una función de mapeo desde la entrada a la salida, como el mapeo de imágenes a la semántica de alto nivel (belleza). Teóricamente, una red neuronal lo suficientemente profunda puede adaptarse a cualquier función compleja . Por lo tanto, las redes neuronales son muy adecuadas para aprender las leyes inherentes y los niveles de representación de datos de muestra, y tienen buena aplicabilidad a tareas de texto, imágenes y voz. Las tareas en estos campos son los módulos básicos de la inteligencia artificial, por lo que no es sorprendente que el aprendizaje profundo se considere la base para la realización de la inteligencia artificial. La estructura básica de la red neuronal (NN) se muestra en la siguiente figura.

Insertar descripción de la imagen aquí

en:

  • Neurona : Cada nodo de la red neuronal se llama neurona y consta de dos partes:
    1. Suma ponderada : suma ponderada de todas las entradas.
    2. Transformación no lineal (función de activación) : el resultado de la suma ponderada se transforma mediante una función no lineal, lo que permite que los cálculos neuronales tengan capacidades no lineales.
  • Conexión multicapa : una gran cantidad de estos nodos están dispuestos en diferentes niveles y conectados para formar una estructura multicapa, que se denomina red neuronal.
  • Cálculo directo : el proceso de calcular la salida a partir de la entrada, en orden de adelante hacia atrás de la red.
  • Gráfico computacional : la visualización gráfica de la lógica computacional de una red neuronal también se denomina gráfico computacional. El gráfico computacional de una red neuronal también se puede expresar en forma de fórmula: Y = f 3 ( f 2 ( f 1 ( w 1 ⋅ x 1 + w 2 ⋅ x 2 + w 3 ⋅ x 3 + b ) + . . . ) + . . . ) Y = f_3(f_2(f_1(w_1 \cdot x_1 + w_2 \cdot x_2 + w_3 \cdot x_3 +b) +...)+...)Y=F3( f2( f1( w1X1+w2X2+w3X3+segundo )+... )+... )

Se puede ver que la red neuronal (NN) no es tan misteriosa, su esencia es una "gran fórmula" que contiene muchos parámetros .

1.3.2 La historia del desarrollo del aprendizaje profundo

La idea de las redes neuronales se propuso hace más de 70 años, y las teorías de diseño actuales de redes neuronales y aprendizaje profundo se están volviendo cada vez más perfectas paso a paso. En estos largos años de desarrollo, los entusiastas del aprendizaje profundo merecen recordar algunos momentos brillantes de avances clave, como se muestra en la siguiente figura.

Insertar descripción de la imagen aquí

  • Década de 1940 : Se propone por primera vez la estructura de una neurona, pero no se pueden aprender los pesos.
  • Décadas de 1950 a 1960 : se propuso la teoría del aprendizaje por peso y la estructura de las neuronas tendió a ser perfecta, lo que abrió la primera era dorada de las redes neuronales (NN).
  • 1969 : Se planteó el problema XOR (la gente se sorprendió al descubrir que el modelo de red neuronal no podía resolver ni siquiera problemas XOR simples y sus expectativas cayeron de las nubes al fondo), y el modelo de red neuronal entró en la era oscura de ser archivado. .
  • 1986 : La red neuronal multicapa recientemente propuesta resolvió el problema XOR, pero con el surgimiento de modelos de aprendizaje automático como SVM con teorías más completas y mejores resultados prácticos después de la década de 1990, las redes neuronales no recibieron atención.
  • Hacia 2010 : El aprendizaje profundo entra en su verdadero auge. A medida que la tecnología de modelos de redes neuronales mejorados brilla en las tareas de habla y visión por computadora, gradualmente se ha demostrado que es más efectiva en más tareas, como el procesamiento del lenguaje natural (PNL) y las tareas de datos masivos. En este punto, el modelo de red neuronal ha renacido y tiene un nombre más famoso: Deep Learning.

¿Por qué las redes neuronales no cobraron vida hasta 2010? Esto está relacionado con los requisitos previos de los que depende el éxito del aprendizaje profundo: ① aparición de big data, ② desarrollo de hardware y ③ optimización de algoritmos.

  1. La aparición del big data : El big data es un requisito previo eficaz para el desarrollo de las redes neuronales. Las redes neuronales (NN) y el aprendizaje profundo (DL) son modelos muy potentes que requieren cantidades suficientes de datos de entrenamiento. Hoy en día, la razón por la que muchos algoritmos tradicionales de aprendizaje automático y funciones artificiales siguen siendo lo suficientemente eficaces es que en muchos escenarios no hay suficientes datos etiquetados para respaldar el aprendizaje profundo. La capacidad del aprendizaje profundo se parece particularmente a las heroicas palabras del científico Arquímedes: "¡Dadme una palanca lo suficientemente larga y podré mover la Tierra!". El aprendizaje profundo también puede hacer afirmaciones similares: "Dame suficientes datos y podré aprender cualquier relación compleja". Pero en realidad, un apalancamiento lo suficientemente prolongado, como suficientes datos, a menudo puede no ser más que una visión optimista. No fue hasta los últimos años que el nivel de TI de varias industrias aumentó y la cantidad de datos acumulados aumentó explosivamente, lo que hizo posible aplicar modelos de aprendizaje profundo.
  2. Desarrollo de hardware y optimización de algoritmos : confíe en el desarrollo de hardware y la optimización de algoritmos. En esta etapa, al depender de computadoras más potentes, GPU, preentrenamiento de codificadores automáticos y tecnologías de computación paralela, las dificultades del aprendizaje profundo en el entrenamiento de modelos se han superado gradualmente. Entre ellos, el volumen de datos y el hardware son los motivos más importantes. Sin los dos primeros, los científicos no podrían optimizar el algoritmo.

1.3.3 La investigación y la aplicación del aprendizaje profundo están en auge

Ya en 1998, algunos científicos habían utilizado modelos de redes neuronales para reconocer imágenes de dígitos escritos a mano (MNIST). Sin embargo, el auge del aprendizaje profundo en las aplicaciones de visión por computadora comenzó con el uso de AlexNet para la clasificación de imágenes en la competencia ImageNet de 2012. Si compara los modelos de 1998 y 2012, encontrará que los dos son muy similares en la estructura de red, con solo algunas optimizaciones en los detalles. En los últimos 14 años, la mejora sustancial en el rendimiento informático y el crecimiento explosivo del volumen de datos han llevado al modelo a completar el salto del "reconocimiento de números simple" a la "clasificación de imágenes complejas".

Aunque tiene una larga historia, el aprendizaje profundo todavía está en auge hoy en día: por un lado, la investigación básica se está desarrollando rápidamente y, por otro lado, las prácticas industriales están surgiendo sin cesar. Según las estadísticas de ICLR (Conferencia Internacional sobre Representaciones de Aprendizaje), la principal conferencia sobre aprendizaje profundo, la cantidad de artículos relacionados con el aprendizaje profundo aumenta año tras año, como se muestra en la siguiente figura. Al mismo tiempo, no solo las conferencias de aprendizaje profundo, sino también una gran cantidad de artículos de conferencias internacionales como ICML y KDD relacionados con tecnología de datos y modelos, CVPR centrado en la visión y EMNLP centrado en el procesamiento del lenguaje natural, todos involucran aprendizaje profundo. tecnología. La investigación en este campo y campos relacionados está en auge, y la tecnología aún está experimentando innovaciones y avances.

Insertar descripción de la imagen aquí

Por otro lado, la tecnología de inteligencia artificial basada en el aprendizaje profundo tiene escenarios de aplicación extremadamente amplios para actualizar y transformar muchos campos industriales tradicionales. La siguiente imagen está tomada de un informe de investigación de iResearch. La tecnología de inteligencia artificial no solo se puede aplicar en muchas industrias (amplitud), sino que también ha logrado su realización en el mercado en algunas industrias (como seguridad, detección remota, Internet, finanzas, industria, etc.) y un rápido crecimiento (profundidad), aportando un enorme valor económico a la sociedad.

Insertar descripción de la imagen aquí

Como se muestra en la figura siguiente, tomando como ejemplo la distribución de aplicaciones industriales de visión por computadora (CV), según las estadísticas y pronósticos de IDC, con la penetración de la inteligencia artificial en varias industrias, la proporción del valor de producción de la industria de Internet que El uso actual de la inteligencia artificial ha aumentado y gradualmente se hará más pequeño.

Insertar descripción de la imagen aquí

1.3.4 El aprendizaje profundo ha cambiado el modelo de investigación y desarrollo de aplicaciones de IA

1.3.4.1 Aprendizaje implementado de un extremo a otro (End2End)

El aprendizaje profundo ha cambiado el modelo de implementación de algoritmos en muchos campos. Antes del surgimiento del aprendizaje profundo, la idea del modelado en muchos campos era invertir mucha energía en ingeniería de características, precipitar la "comprensión artificial" de un determinado campo por parte de los expertos en expresiones de características y luego usar modelos simples para completar tareas. (como clasificación o regresión). Cuando hay suficientes datos, el modelo de aprendizaje profundo puede lograr un aprendizaje de extremo a extremo (End2End), es decir, no se requiere ingeniería de características especiales.Las características originales se ingresan en el modelo y el modelo puede completar la extracción y clasificación de características. tareas al mismo tiempo, como se muestra a continuación.

Insertar descripción de la imagen aquí

Tomando como ejemplo las tareas de visión por computadora, la ingeniería de características es una serie de pasos computacionales para extraer características diseñadas por muchos científicos de imágenes basadas en la comprensión humana de la teoría visual, generalmente como las características SIFT. En el campo de la visión por computadora antes de 2010, la gente generalmente usaba características de tipo SIFT + modelos superficiales simples de tipo SVM para completar tareas de modelado.

Descripción :

  1. Las funciones SIFT fueron propuestas por David Lowe en 1999 y refinadas en 2004. Las funciones SIFT se basan en algunos puntos de interés de apariencia local del objeto y son independientes del tamaño y la rotación de la imagen. La tolerancia a la luz, el ruido y los cambios de ángulo de visión también es bastante alta. Según estas características, son muy destacados y relativamente fáciles de recuperar. En una base de datos de características grande, los objetos se identifican fácilmente y la identificación errónea es rara. La tasa de detección de oclusión parcial de objetos utilizando la descripción de características SIFT también es bastante alta, e incluso más de 3 características de objetos SIFT son suficientes para calcular la posición y orientación. En las condiciones actuales de velocidad del hardware de la computadora y base de datos de funciones pequeñas, la velocidad de reconocimiento puede acercarse a la operación en tiempo real. Las funciones SIFT tienen una gran cantidad de información y son adecuadas para realizar coincidencias rápidas y precisas en bases de datos masivas.

  2. La diferencia más esencial entre el aprendizaje profundo y el aprendizaje automático tradicional es la necesidad de ingeniería de funciones.:

    • En el aprendizaje automático tradicional, la ingeniería de características es un paso importante que requiere que los expertos en el dominio diseñen y seleccionen manualmente características relevantes a partir de datos sin procesar como entrada para su uso en algoritmos de aprendizaje automático. Este proceso a menudo requiere un importante conocimiento del dominio, preprocesamiento de datos y esfuerzo manual para crear representaciones de datos informativas y significativas.
    • El aprendizaje profundo puede aprender automáticamente características relevantes de los datos originales durante el proceso de capacitación. Los modelos de aprendizaje profundo, especialmente las redes neuronales, pueden aprender gradualmente representaciones jerárquicas de datos a través de múltiples niveles de computación. Esta capacidad de aprender funciones automáticamente hace que el aprendizaje profundo sea más adecuado para procesar datos complejos y de alta dimensión.
1.3.4.2 Estandarización lograda del marco de aprendizaje profundo

Además de su amplia gama de aplicaciones, el aprendizaje profundo también promueve la inteligencia artificial en la etapa de producción industrial en masa. La versatilidad del algoritmo conduce a la creación de marcos estandarizados, automatizados y modulares, como se muestra en la siguiente figura.

Insertar descripción de la imagen aquí

Antes de esto, diferentes escuelas de algoritmos de aprendizaje automático tenían diferentes teorías e implementaciones, lo que hacía que cada algoritmo tuviera que implementarse de forma independiente, como Random Forest (RF) y Support Vector Machine (SVM). Sin embargo, en el marco del aprendizaje profundo, las estructuras algorítmicas de diferentes modelos tienen una gran versatilidad, como el modelo de red neuronal convolucional (CNN) comúnmente utilizado en visión por computadora y el modelo de memoria a corto plazo (LSTM) comúnmente utilizado en el procesamiento del lenguaje natural. Se divide en módulo de red, módulo de optimización de descenso de gradiente y módulo de predicción. Esto hace posible abstraer un marco unificado y reduce en gran medida el costo de escribir código de modelado. El marco puede implementar algunos módulos relativamente comunes, como la implementación de operadores de red básicos y varios algoritmos de optimización. Los modeladores sólo necesitan centrarse en el procesamiento de datos, configurar redes y unir los procesos de entrenamiento y predicción con una pequeña cantidad de código.

Antes de la aparición de los marcos de aprendizaje profundo, los ingenieros de aprendizaje automático se encontraban en la era de la producción en "taller manual". Para completar el modelado, los ingenieros deben reservar una gran cantidad de conocimientos matemáticos y acumular muchos conocimientos de la industria para el trabajo de ingeniería de características. Cada modelo es extremadamente personalizado y el modelador, como un artesano, forma su propia acumulación en una "firma personalizada" del modelo. Hoy en día, los "ingenieros de aprendizaje profundo" han entrado en la era de la producción en masa industrializada. Siempre que dominen los conocimientos teóricos necesarios pero pequeños del aprendizaje profundo y dominen la programación Python, pueden implementar modelos muy efectivos en el marco de aprendizaje profundo, incluso con el La mayoría de los modelos líderes en el campo. Las barreras técnicas en el campo del modelado se enfrentan a la subversión, lo que también es una oportunidad para nuevos participantes.

Insertar descripción de la imagen aquí

1.4 Existe un amplio espacio para el desarrollo profesional en inteligencia artificial

Analicemos si la inteligencia artificial es una carrera prometedora desde la perspectiva de los retornos económicos. Hablando francamente, como dijo Buffett, elegir una carrera que te guste es una carrera realmente buena. Pero para la mayoría de la gente corriente, los beneficios financieros también son una consideración importante a la hora de elegir una carrera. Una ocupación con altos rendimientos económicos debe ser aquella en la que la demanda del mercado es mucho mayor que la oferta del mercado, y la demanda del mercado debe mantener el crecimiento a largo plazo, mientras que la oferta del mercado es difícil de reponer en el corto y mediano plazo.

1.4.1 La demanda del mercado de puestos de inteligencia artificial es fuerte

Según informes de investigación de la industria de importantes empresas consultoras, se espera que las industrias relacionadas con la inteligencia artificial tengan una tasa de crecimiento anual del 30% al 40% en los próximos diez años. Por un lado, la aplicación de la inteligencia artificial se expandirá gradualmente desde la industria de Internet a una gama más amplia de industrias como finanzas, industria, agricultura, energía, ciudades, transporte, atención médica, educación, etc., con un enorme espacio de aplicación y potencial, por otro lado, limitado por la inteligencia artificial La madurez de la tecnología en sí y la implementación de la inteligencia artificial deben combinarse con las limitaciones del procesamiento de datos de escenarios, la transformación del sistema y la optimización de los procesos comerciales. El proceso de liberación de valor de las aplicaciones de inteligencia artificial será relativamente lento. Esto ha provocado que la demanda del mercado de trabajos de inteligencia artificial forme una curva de crecimiento constante y a largo plazo. En comparación con la industria de Internet, es más amigable para la mayoría de los solicitantes de empleo, como se muestra en la siguiente figura.

Insertar descripción de la imagen aquí

Debido al corto ciclo de madurez de la tecnología y al rápido avance de la implementación de aplicaciones, la industria de Internet ha formado una curva con una tasa de crecimiento más alta (tasa de crecimiento anual de más del 100%), pero un ciclo de crecimiento más corto (10 años en la era de Internet informática). y 10 años en la era de Internet móvil). Cuando el crecimiento de la industria alcance su punto máximo, la demanda de empleo caerá en consecuencia, al igual que la situación actual de la industria de Internet a finales de 2021.

1.4.2 El talento integral se ha convertido en una necesidad en el mercado

En el proceso de aplicación de la inteligencia artificial a miles de industrias, lo que las empresas más y más urgentemente necesitan son "talentos compuestos" que comprendan no solo el conocimiento y los escenarios de la industria, sino también la teoría de la inteligencia artificial, así como las habilidades prácticas y la experiencia . Convertirse en un "talento complejo" requiere no sólo aprender conocimientos de libros, sino también una gran cantidad de práctica industrial, de modo que dichos talentos tengan un crecimiento profundo y el crecimiento de la oferta sea lento. Del análisis anterior se puede ver que cuando la industria de la inteligencia artificial mantenga un crecimiento constante en las próximas décadas y los "talentos complejos" que necesita la industria sean difíciles de suministrar en grandes cantidades, los puestos de I + D en aplicaciones de inteligencia artificial mantendrán un buen retorno económico.

2. Utilice Python y NumPy para crear modelos de redes neuronales.

En el último capítulo, tuvimos una comprensión preliminar de los conceptos básicos de las redes neuronales (como neuronas, conexiones multicapa, cálculos directos, gráficos de cálculo) y los tres elementos de la estructura del modelo (supuestos del modelo, funciones de evaluación y algoritmos de optimización). ). Este capítulo tomará la tarea "Predicción del precio de la vivienda en Boston" como ejemplo para presentar el proceso de pensamiento y el método operativo de utilizar Python y NumPy para construir un modelo de red neuronal.

La predicción del precio de la vivienda en Boston es una tarea clásica de aprendizaje automático, similar al "Hola mundo" del mundo de los programadores. Como todo el mundo sabe sobre los precios de la vivienda, los precios de la vivienda en el área de Boston se ven afectados por muchos factores. Este conjunto de datos cuenta 13 factores que pueden afectar los precios de la vivienda y el precio promedio de las casas de este tipo. Se espera construir un modelo para predecir los precios de la vivienda basado en 13 factores, como se muestra en la siguiente figura.

Insertar descripción de la imagen aquí

Para problemas de predicción, se puede dividir en ① tareas de regresión y ② tareas de clasificación según si el tipo de salida de predicción son valores reales continuos o etiquetas discretas. Dado que los precios de la vivienda son un valor continuo, la predicción del precio de la vivienda es obviamente una tarea de regresión. A continuación intentamos resolver este problema con el modelo de regresión lineal más simple y utilizamos una red neuronal para implementar este modelo.

2.1 Modelo de regresión lineal

Supongamos que la relación entre los precios de la vivienda y varios factores que influyen se puede describir mediante una relación lineal:

y = ∑ j = 1 M xjwj + b (1) y = \sum_{j=1}^M x_jw_j + b \tag{1}y=j = 1mXjwj+b( 1 )

La solución del modelo es ajustar cada wj w_j a través de los datos.wjybb_ _B. _ Entre ellos,wj w_jwjybb_ _b representan respectivamente el peso (Peso) y el sesgo (Sesgo) del modelo lineal. En el caso unidimensional,wj w_jwjybb_ _b es la pendiente y la intersección de la recta.

El modelo de regresión lineal utiliza el error cuadrático medio (MSE) como función de pérdida (Pérdida) para medir la diferencia entre el precio previsto de la vivienda y el precio real de la vivienda. La fórmula es la siguiente:

MSE = 1 n ∑ i = 1 n ( Y ^ i − Y i ) 2 (2) \mathrm{MSE} = \frac{1}{n}\sum_{i=1}^n (\hat{Y} _i - Y_i)^2 \etiqueta{2}MSE=norte1yo = 1norte(Y^yoYyo)2( 2 )

Piensa :

¿Por qué utilizar el error cuadrático medio (MSE) como función de pérdida? La precisión de la muestra general se mide sumando los errores de predicción del modelo en cada muestra de entrenamiento. Esto se debe a que el diseño de la función de pérdida no solo debe considerar la "racionalidad", sino también la "facilidad de solución", tema que se detallará en el siguiente contenido.


En la estructura estándar de una red neuronal, cada neurona (neurona) se compone de una suma ponderada y una transformación no lineal, y luego se colocan y conectan múltiples neuronas en capas para formar una red neuronal (NN). El modelo de regresión lineal puede considerarse un caso especial minimalista del modelo de red neuronal: es una neurona con solo una suma ponderada y sin transformación no lineal (no es necesario formar una red), como se muestra en la siguiente figura.

Insertar descripción de la imagen aquí

2.2 Utilice Python y NumPy para implementar la tarea de predicción del precio de la vivienda en Boston

El aprendizaje profundo no solo logra el aprendizaje de modelos de un extremo a otro, sino que también promueve la inteligencia artificial en la etapa de producción industrial en masa, produciendo un marco universal para la estandarización, la automatización y la modularización. Los modelos de aprendizaje profundo en diferentes escenarios tienen cierta versatilidad y la construcción y capacitación del modelo se pueden completar en cinco pasos, como se muestra en la figura siguiente.

Insertar descripción de la imagen aquí

Es precisamente debido a la universalidad del proceso de modelado y capacitación del aprendizaje profundo que al construir diferentes modelos, solo los tres elementos del modelo son diferentes y los otros pasos son básicamente los mismos, por lo que el marco de aprendizaje profundo puede ser útil.

2.2.1 Procesamiento de datos

El procesamiento de datos incluye cinco partes: ① importación de datos, ② transformación de forma de datos, ③ división del conjunto de datos, ④ procesamiento de normalización de datos y ⑤ load_datafunción de empaquetado. Generalmente, el modelo solo puede llamar a los datos después de haberlos preprocesado.

2.2.1.1 Lectura de datos

Lea los datos a través del siguiente código para comprender la estructura del conjunto de datos de los precios de la vivienda en Boston. Los datos se almacenan en housing.dataarchivos en el directorio local.

Dirección de descarga del conjunto de datos: http://paddlemodels.bj.bcebos.com/uci_housing/housing.data

import numpy as np
import json


# 读取数据
datafile = "/data/data_01/lijiandong/Datasets/boston_house_price/housing.data"
data = np.fromfile(datafile, sep=" ")
print(data)  # /data/data_01/lijiandong/Datasets/boston_house_price/housing.data
print(data.shape)  # (7084,)

2.2.1.2 Transformación de forma de datos

Dado que los datos originales leídos son unidimensionales, todos los datos están conectados entre sí. Por lo tanto, necesitamos transformar la forma de los datos para formar una matriz bidimensional, cada fila tiene una muestra de datos (14 columnas) y cada muestra de datos contiene 13 XX.X (características que afectan los precios de la vivienda) yYYY (precio medio de viviendas de este tipo).

"""
读入之后的数据被转化成1维array,其中array的第0-13项是第一条数据,第14-27项是第二条数据,以此类推.... 
这里对原始数据做reshape,变成N x 14的形式
"""
feature_names = [ 'CRIM', 'ZN', 'INDUS', 'CHAS', 'NOX', 'RM', 'AGE','DIS', 'RAD', 'TAX', 'PTRATIO', 'B', 'LSTAT', 'MEDV' ]
feature_num = len(feature_names)
data = data.reshape([data.shape[0] // feature_num, feature_num])

# 查看数据
x = data[0]  # 取第一行
print(x.shape)  # (14,)
print(x)
"""
[6.320e-03 1.800e+01 2.310e+00 0.000e+00 5.380e-01 6.575e+00 6.520e+01
 4.090e+00 1.000e+00 2.960e+02 1.530e+01 3.969e+02 4.980e+00 2.400e+01]
"""
2.2.1.3 División del conjunto de datos

El conjunto de datos se divide en un conjunto de entrenamiento y un conjunto de prueba: el conjunto de entrenamiento se utiliza para determinar los parámetros del modelo y el conjunto de prueba se utiliza para evaluar el efecto del modelo. ¿Por qué deberíamos dividir el conjunto de datos y no aplicarlo directamente al entrenamiento de modelos? Esto es similar a la relación entre enseñanza y exámenes durante los días de estudiante, como se muestra en la siguiente figura.

Insertar descripción de la imagen aquí

Cuando estaba en la escuela, siempre había algunos estudiantes inteligentes que no estudiaban en serio, estudiaban antes del examen y memorizaban los ejercicios, pero sus calificaciones a menudo no eran buenas. Porque las escuelas esperan que los estudiantes dominen los conocimientos, no sólo los ejercicios en sí. Introducir nuevas preguntas de prueba puede alentar a los estudiantes a trabajar duro para comprender los principios detrás de los ejercicios. De manera similar, esperamos que el modelo aprenda las reglas esenciales de la tarea, en lugar de los datos de entrenamiento en sí. Solo los datos que no se utilizan en el entrenamiento del modelo pueden evaluar más verdaderamente el efecto del modelo.

En este caso utilizamos el 80% de los datos como conjunto de entrenamiento y el 20% como conjunto de prueba, el código de implementación es el siguiente.

ratio = 0.8
offset = int(data.shape[0] * ratio)
training_data = data[:offset]
test_data = data[offset:]

print(data.shape)  # (506, 14)
print(training_data.shape)  # (404, 14)
print(test_data.shape)  # (102, 14)

Al imprimir la forma del conjunto de entrenamiento, podemos encontrar que hay 404 muestras en total, cada una de las cuales contiene 13 características y 1 valor predicho.

2.2.1.4 Procesamiento de normalización de datos

La normalización de datos es una técnica de preprocesamiento de datos común que se utiliza para escalar datos a un cierto rango, generalmente [0, 1] [0, 1][ 0 ,1 ] o[ − 1 , 1 ] [-1, 1][ -1 , _1 ] (el primero se usa más ampliamente, por eso usamos el primero). La fórmula para la normalización de datos es la siguiente:

Para cada característica (o cada columna), suponga que el rango de los datos originales es [ min ⁡ , max ⁡ ] [\min, \max][ mínimo ,max ] , los datos normalizados se pueden calcular mediante la siguiente fórmula:

Valor normalizado = valor original − min ⁡ max ⁡ − min ⁡ Valor normalizado = \frac{Valor original - \min}{\max - \min}Valor normalizado=máximomín.Valor originalmin

Entre ellos, "valor original" se refiere al valor de datos original en una característica específica, "mínimo" es el valor mínimo de la característica en el conjunto de datos y "máximo" es el valor máximo de la característica en el conjunto de datos.

Utilice la normalización para escalar el valor de cada característica a [0, 1] [0, 1][ 0 ,1 ] entre. Esto tiene dos beneficios:

  1. La capacitación modelo es más eficiente, lo que se explicará en detalle en la segunda mitad de esta sección;
  2. El peso antes de una característica puede representar la contribución de la variable al resultado de la predicción (porque cada valor de característica tiene el mismo rango).
# 计算train数据集的最大值和最小值
maxinums, minimus = training_data.max(axis=0), training_data.min(axis=0)

# 对数据进行归一化处理
for col_name in range(feature_num):
    # 训练集归一化
    training_data[:, col_name] = (training_data[:, col_name] - minimus[col_name]) / (maxinums[col_name] - minimus[col_name])
    # 测试集归一化(确保了测试集上的数据也使用了与训练集相同的归一化转换,避免了引入测试集信息污染)
    test_data[:, col_name] = (test_data[:, col_name] - minimus[col_name]) / (maxinums[col_name] - minimus[col_name])

# 验证是否有大于1的值
print(np.any(training_data[:, :-1] > 1.0))  # False
print(np.any(test_data[:, :-1] > 1.0))  # True(这是正常的,因为我们使用了训练集的归一化参数)
2.2.1.5 Encapsular en load_datafunciones

Encapsule las operaciones de procesamiento de datos anteriores en load_datafunciones para el siguiente paso de llamar al modelo. El método de implementación es el siguiente.

import numpy as np
import json


def data_load():
    # 2.2.1.1 读入数据
    datafile = "/data/data_01/lijiandong/Datasets/boston_house_price/housing.data"
    data = np.fromfile(datafile, sep=" ")


    # 2.2.1.2 数据形状变换
    feature_names = [ 'CRIM', 'ZN', 'INDUS', 'CHAS', 'NOX', 'RM', 'AGE','DIS', 'RAD', 'TAX', 'PTRATIO', 'B', 'LSTAT', 'MEDV' ]
    feature_num = len(feature_names)
    data = data.reshape([data.shape[0] // feature_num, feature_num])  # [N, 14]


    # 2.2.1.3 数据集划分
    ratio = 0.8
    offset = int(data.shape[0] * ratio)
    training_data = data[:offset]
    test_data = data[offset:]


    # 2.2.1.4 数据归一化处理
    # 计算train数据集的最大值和最小值
    maxinums, minimus = training_data.max(axis=0), training_data.min(axis=0)

    # 对数据进行归一化处理
    for col_name in range(feature_num):
        # 训练集归一化
        training_data[:, col_name] = (training_data[:, col_name] - minimus[col_name]) / (maxinums[col_name] - minimus[col_name])
        # 测试集归一化(确保了测试集上的数据也使用了与训练集相同的归一化转换,避免了引入测试集信息污染)
        test_data[:, col_name] = (test_data[:, col_name] - minimus[col_name]) / (maxinums[col_name] - minimus[col_name])
        
    return training_data, test_data


# 获取数据
training_data, test_data = data_load()
x = training_data[:, :-1]  # 训练数据
target = training_data[:, -1:]  # 目标值

# 查看数据
print(f"x.shape: {
      
      x.shape}")  # (404, 13)
print(f"target.shape: {
      
      target.shape}")  # (404,)

2.2.2 Diseño del modelo

El diseño del modelo es uno de los elementos clave de un modelo de aprendizaje profundo, también llamado diseño de estructura de red, y es equivalente al espacio de hipótesis del modelo, es decir, el proceso de realizar el "cálculo directo" (desde la entrada hasta la salida). ) del modelo.

Si tanto la característica de entrada como el valor predicho de salida se expresan como vectores, la característica de entrada xxx tiene 13 componentes,yyy tiene 1 componente, entonces la forma del peso del parámetro es13 × 1 13 \times 113×1 . Supongamos que inicializamos los parámetros con cualquier número de la siguiente manera:

w = [0.1, 0.2, 0.3, 0.4, 0.5, 0.6, 0.7, 0.8, - 0.1, - 0.2, - 0.3, - 0.4, 0.0] w = [0.1, 0.2, 0.3, 0.4, 0.5, 0.6, 0.7, 0,8, -0,1, -0,2, -0,3, -0,4, 0,0]w=[ 0.1 ,0,2 ,0,3 ,0,4 ,0,5 ,0,6 ,0,7 ,0,8 ,0,1 ,0,2 ,0,3 ,0,4 ,0.0 ]

Expresado en código:

w = [0.1, 0.2, 0.3, 0.4, 0.5, 0.6, 0.7, 0.8, -0.1, -0.2, -0.3, -0.4, 0.0]
w = np.array(w).reshape([13, 1])

Saque los datos de la primera muestra y observe el resultado de multiplicar el vector de características y el vector de parámetros de la muestra.

x1 = x[0]
t = np.dot(x1, w)
print(t)  # [0.69474855]

La fórmula de regresión lineal completa también necesita inicializar el desplazamiento bbb , también asigne un valor inicial de -0,2 de forma arbitraria. Entonces, el resultado completo del modelo de regresión lineal esz = t + bz=t+bz=t+b . Este proceso de calcular los valores de salida a partir de características y parámetros se denomina "cálculo directo".

b = -0.2  # bias
z = t + b
print(z)  # [0.49474855]

El proceso anterior para calcular la salida de predicción se describe en forma de "clase y objeto" y la variable miembro de la clase tiene el parámetro ww.w ybbB. _ Complete el proceso de cálculo anterior a partir de características y parámetros para generar valores predichos escribiendo unaforwardfunción (que representa "cálculo directo"), el código es el siguiente.

class Network:
    def __init__(self, num_of_weights):
        np.random.seed(0)
        self.w = np.random.randn(num_of_weights, 1)  # 随机产生w的初始值
        self.b = 0.0  # 不使用偏置
        
    def forward(self, x):
        z = np.dot(x, self.w) + self.b
        return z

Basado en la definición de la clase Red, el proceso de cálculo del modelo es el siguiente.

net = Network(13)
x1 = x[0]
y1 = target[0]
z = net.forward(x1)
print(z)  # [2.39362982]

Del proceso de cálculo directo anterior, podemos ver que la regresión lineal también se puede expresar como una red neuronal simple (solo una neurona y la función de activación es la identidad y = yy = yy=y ). Esta es también la razón por la que los modelos de aprendizaje automático generalmente se reemplazan por modelos de aprendizaje profundo:debido a las poderosas capacidades de representación de las redes de aprendizaje profundo, las capacidades de aprendizaje de muchos modelos tradicionales de aprendizaje automático son equivalentes a las de modelos de aprendizaje profundo relativamente simples.

2.2.3 Configuración de entrenamiento

Una vez completado el diseño del modelo, es necesario encontrar el valor óptimo del modelo mediante la configuración de entrenamiento, es decir, medir la calidad del modelo mediante la función de pérdida. La configuración de la formación también es uno de los elementos clave de los modelos de aprendizaje profundo.

Calcula x 1 x_1 a través del modelo.X1El precio de la vivienda correspondiente a los factores influyentes representados debería ser zzz , pero los datos reales nos dicen que el precio de la vivienda esyyy . En este momento necesitamos algún tipo de indicador para medir el valor previstozzz y el valor verdaderoyyla diferencia entre y . Para problemas de regresión, el método de medición más utilizado es utilizar el error cuadrático medio (MSE) como indicador para evaluar la calidad del modelo. La definición específica es la siguiente:

L oss = ( y − z ) 2 (3) \mathrm{Pérdida} = (y - z)^2 \tag{3}Pérdida=( yz )2( 3 )

Pérdida en la fórmula anterior (abreviada como: LLL ) también suele denominarse función de pérdida, que es una medida de la calidad del modelo. Aquí debemos pensar en una pregunta: si queremos medir la brecha entre los precios previstos de la vivienda y los precios reales de la vivienda, ¿podemos simplemente sumar el valor absoluto de la brecha para cada muestra? La suma de los valores absolutos de las diferencias es una idea más intuitiva y sencilla ¿Por qué la suma de los cuadrados?

Esto se debe a que el diseño de la función de pérdida no sólo debe considerar la "razonabilidad" de medir con precisión el problema, sino que también suele considerar la "facilidad de optimización y solución". En cuanto a la respuesta a esta pregunta, se revelará después de que se introduzca el algoritmo de optimización.

En los problemas de regresión, el error cuadrático medio (MSE) es una forma relativamente común. En los problemas de clasificación, la entropía cruzada generalmente se usa como función de pérdida, que se presentará con más detalle en capítulos posteriores. La implementación para calcular el valor de la función de pérdida para una muestra es la siguiente.

loss = (y1 - z) ** 2
print(loss)  # [3.88644793]

Debido a que es necesario tener en cuenta el valor de la función de pérdida de cada muestra al calcular la función de pérdida, debemos sumar la función de pérdida de una sola muestra y dividirla por el número total de muestras NN .norte .

L = 1 N ∑ i = 1 N ( yi − zi ) 2 (4) L = \frac{1}{N} \sum_{i=1}^N (y_i - z_i)^2 \tag{4}l=norte1yo = 1norte( yyozyo)2( 4 )

El proceso de cálculo para agregar la función de pérdida en la clase Red es el siguiente.

class Network:
    def __init__(self, num_of_weights):
        np.random.seed(0)
        self.w = np.random.randn(num_of_weights, 1)  # 随机产生w的初始值
        self.b = 0.0  # 不使用偏置
        
    def forward(self, x):
        z = np.dot(x, self.w) + self.b
        return z
    
    def loss(self, pred, gt):
        loss_value_sum = (pred - gt) ** 2
        return np.mean(loss_value_sum)

Utilizando la clase de red definida, el valor previsto y la función de pérdida se pueden calcular fácilmente. Cabe señalar que las variables de la clase x, w, b, pred, gt, loss_value_sumson todas vectores. Tomando la variable xcomo ejemplo, hay dos dimensiones, una representa el número de características (el valor es 13) y la otra representa el número de muestras, el código es el siguiente.

# 组成向量一次性计算多个值
x1 = x[:3]  # 前三行
y1 = target[:3]  # 前三行

pred = net.forward(x1)
print(f"pred: {
      
      pred}")

loss = net.loss(pred, target)
print(f"loss: {
      
      loss}")

resultado:

pred: [[2.39362982]
 [2.46752393]
 [2.02483479]]
loss: 3.573658599044957

2.2.4 Proceso de formación

El proceso de cálculo anterior describe cómo construir una red neuronal y completar el cálculo de los valores predichos y las funciones de pérdida a través de la red neuronal. A continuación, presentaremos cómo resolver el parámetro ww.w ybbEl valor de b ,este proceso también se denomina proceso de formación del modelo. El proceso de capacitación es uno de los elementos clave del modelo de aprendizaje profundo y su objetivo es hacer que la función de pérdida definidaL oss \mathrm{Loss}La pérdida debe ser lo más pequeña posible, es decir, encontrar una solución paramétricawww ybbb , de modo que la función de pérdida obtenga un valor mínimo.

Primero hagamos una pequeña prueba: como se muestra en la figura siguiente, basándose en conocimientos de cálculo, encuentre que la pendiente de una curva en un punto determinado es igual al valor de la derivada de la función en ese punto. Entonces piénselo, cuando está en el punto extremo de la curva, ¿cuál es la pendiente de ese punto?

Insertar descripción de la imagen aquí

Esta pregunta no es difícil de responder: la pendiente en el punto extremo de la curva es 0, es decir, la derivada de la función en el punto extremo es 0. Luego, dejemos que la función de pérdida tome el valor mínimo de www ybbb debería ser la solución del siguiente sistema de ecuaciones:

∂ L ∂ w = 0 (5) \frac{\partial L}{\partial w} = 0 \tag{5}w∂L _=0( 5 )

∂ L ∂ b = 0 (6) \frac{\partial L}{\partial b} = 0 \tag{6}segundo∂L _=0( 6 )

donde LLL representa el valor de la función de pérdida,www es el peso del modelo,bbb es el término de sesgo. www ybbb son todos los parámetros del modelo que se deben aprender.

Exprese la función de pérdida en forma de matriz, de la siguiente manera:

L = 1 N ∣ ∣ y − ( X w + b ) ∣ ∣ 2 (7) L = \frac{1}{N} ||y - (Xw + b)||^2 \tag{7}l=norte1∣∣y _( Xw+b ) 2( 7 )

entre ellos yyy esNNUn vector compuesto por valores de etiqueta de N muestras, con una forma deN × 1 N\times 1norte×1XXX esNNUna matriz compuesta por N vectores de características de muestra, con una forma de N × DN × Dnorte×D ,DDD es la longitud de la característica de datos;www es un vector de peso con una forma deD × 1 D × 1D×1bbb significa que todos los elementos sonbbvector de b con formaN × 1 N × 1norte×1 .

Fórmula de cálculo 7 para el parámetro bbDerivada parcial de b :

∂ L ∂ b = 1 T [ y − ( X w + b ) ] (8) \frac{\partial L}{\partial b} = 1^T[y - (Xw + b)] \tag{8}segundo∂L _=1T [y( Xw+segundo )]( 8 )

Tenga en cuenta que la fórmula anterior ignora el coeficiente 2 N \frac{2}{N}norte2, no afecta el resultado final. de los cuales 1 11 esNNUn vector todo-1 de N dimensiones.

Sea la fórmula 8 igual a 0, obtenemos

b ∗ = x ‾ T w − y ‾ (9) b^* = \overline{x}^Tw - \overline{y} \tag{9}b=Xdos _y( 9 )

其中 y ‾ = 1 N 1 T y \overline{y} = \frac{1}{N} 1^T y y=norte11T yes el promedio de todas las etiquetas,x ‾ = 1 N ( 1 TX ) T \overline{x} = \frac{1}{N}(1^TX)^TX=norte1( 1TX )_T es el promedio de todos los vectores de características. Seráb ∗ b^*b se introduce en la fórmula 7 y el parámetrowwTomando la derivada parcial de w , obtenemos

∂ L ∂ w = ( X − x ‾ T ) T [ ( y − y ‾ ) − ( X − x ‾ T ) w ] (10) \frac{\partial L}{\partial w} = (X - \ overline x^T)^T [(y - \overline y) - (X - \overline x^T)w] \tag{10}w∂L _=( XXT )T [(yy)( XXT )w]( 10 )

Haga que la fórmula 10 sea igual a 0 para obtener los parámetros óptimos

w ∗ = [ ( X − x ‾ T ) T ( X − x ‾ T ) ] − 1 ( X − x ‾ T ) T ( y − y ‾ ) (11) w^* = [(X - \overline x ^T)^T(X - \overline x^T)]^{-1}(X - \overline x^T)^T(y - \overline y) \tag{11}w=[( XXT )T (XXT )]1 (XXT )T (yy)( 11 )

b ∗ = x ‾ T w ∗ − y ‾ (12) b^* = \overline x^T w^* - \overline y \tag{12}b=Xdos _y( 12 )

Ponga los datos de muestra (x, y) (x,y)( x ,y ) en la fórmula 11 y la fórmula 12 anteriores para resolverwww ybbvalor de b , pero este método sólo es efectivo para tareas simples como la regresión lineal. Si el modelo contiene una transformación no lineal, o la función de pérdida no está en la forma simple de error cuadrático medio, será difícil resolverlo mediante la fórmula anterior. Para resolver este problema, a continuación presentaremos un método de solución numérica más universal: el método de descenso de gradiente.

2.2.4.1 Método de descenso de gradiente

En realidad, existe una gran cantidad de funciones que son fáciles de resolver en dirección directa pero difíciles de resolver en dirección inversa, se denominan funciones unidireccionales. Este tipo de funciones tiene una gran cantidad de aplicaciones en criptografía. La característica del bloqueo de contraseña es que puede determinar rápidamente si una clave es correcta (conocida xxx,求 y y y es muy fácil), pero incluso si obtienes el sistema de bloqueo de contraseña, no podrás descifrar la clave correcta (se sabe queyyy , encuentraxxx es difícil).

Esta situación es particularmente similar a la de un ciego que quiere caminar desde una montaña hasta un valle, pero no puede ver dónde está el valle (no puede resolver el problema a la inversa para encontrar Pérdida \mathrm{Pérdida}El valor del parámetro cuando la derivada de pérdida es 0), pero puede estirar los pies para explorar la pendiente a su alrededor (el valor de la derivada del punto actual, también llamado gradiente). Luego, resolver el valor mínimo de la función de Pérdida se puede lograr de la siguiente manera: tomando el valor del parámetro actual, descendiendo paso a paso en dirección descendente hasta llegar al punto más bajo. El autor llama a este método el "método ciego cuesta abajo". Oh no, existe un término más formal "método de descenso en gradiente".

La clave del entrenamiento es encontrar un conjunto de (w, b) (w,b)( w ,b ) , haciendo que la función de pérdidaLLL toma un valor mínimo. Veamos primero la función de pérdidaLL.L solo toma dos parámetrosw 5 w_5w5suma w 9 w_9w9Las situaciones simples durante los cambios inspiran ideas para encontrar soluciones.

L = L ( w 5 , w 9 ) (13) L = L(w_5, w_9) \tag{13}l=L ( w5,w9)( 13 )

Generales w 0 , w 1 , . . . , w 12 w_0, w_1, ..., w_{12}w0,w1,... ,w12Eliminación media w 5 , w 9 w_5, w_9w5,w9Parámetros distintos de y bbCon b fijo, puedes dibujarL ( w 5 , w 9 ) L(w_5, w_9)L ( w5,w9) y dibuje una gráfica de superficie de la función de pérdida que cambia con los parámetros en un espacio tridimensional.

import matplotlib.pyplot as plt
from mpl_toolkits.mplot3d import Axes3D


net = Network(num_of_weights=13)

# 只画出参数w5和w9在区间[-160, 160]的曲线部分,以及包含损失函数的极值
w5 = np.arange(-160.0, 160.0, 1.0)
w9 = np.arange(-160.0, 160.0, 1.0)
losses = np.zeros(shape=[len(w5), len(w9)])

# 计算设定区域内每个参数取值所对应的Loss
for i in range(len(w5)):
    for j in range(len(w9)):
        # 更改模型参数的数值
        net.w[5] = w5[i]
        net.w[9] = w9[i]
        
        # 模型infer并计算、记录loss
        pred = net.forward(x)
        loss = net.loss(pred=pred, gt=target)
        losses[i, j] = loss
        
# 使用matplotlib将两个变量和对应的Loss作3D图
fig = plt.figure(dpi=300)
ax = fig.add_axes(Axes3D(fig))

# 设置坐标轴标签
ax.set_xlabel('w5')
ax.set_ylabel('w9')
ax.set_zlabel('Loss')

w5, w9 = np.meshgrid(w5, w9)

ax.plot_surface(w5, w9, losses, rstride=1,cstride=1, cmap="rainbow")
plt.savefig("gd_sample_demo.png")

Insertar descripción de la imagen aquí

Se puede observar claramente en la figura que el valor de la función de algunas áreas es menor que el de los puntos circundantes. Lo que hay que explicar es: ¿por qué elegir w 5 w_5 ?w5suma w 9 w_9w9¿Qué tal hacer un dibujo? Esto se debe a que al seleccionar estos dos parámetros, la existencia de puntos extremos se puede encontrar de manera relativamente intuitiva en el gráfico de superficie de la función de pérdida. Para otras combinaciones de parámetros, no es lo suficientemente intuitivo observar gráficamente los puntos extremos de la función de pérdida.

Observe que la curva anterior muestra una pendiente "suave", que es una de las razones por las que elegimos el error cuadrático medio como función de pérdida. La siguiente figura presenta la curva de función de pérdida del error cuadrático medio y el error de valor absoluto (solo se acumula el error de cada muestra, sin procesamiento cuadrático) cuando solo hay una dimensión de parámetro.

Insertar descripción de la imagen aquí

Se puede ver que la pendiente "suave" representada por el error cuadrático medio tiene dos beneficios:

  1. El punto más bajo de la curva es diferenciable.
  2. 越接近最低点,曲线的坡度逐渐放缓,有助于通过当前的梯度来判断接近最低点的程度(是否逐渐减少步长,以免错过最低点)。

绝对值误差是不具备这两个特性的,这也是损失函数的设计不仅仅要考虑“合理性”,还要追求“易解性”的原因。

现在我们要找出一组 [ w 5 , w 9 ] [w_5, w_9] [w5,w9] 的值,使得损失函数最小,实现梯度下降法的方案如下:

  • 步骤 1:随机的选一组初始值,例如: [ w 5 , w 9 ] = [ − 100.0 , − 100.0 ] [w_5, w_9] = [-100.0, -100.0] [w5,w9]=[100.0,100.0]
  • 步骤 2:选取下一个点 [ w 5 ′ , w 9 ′ ] [w'_5, w'_9] [w5,w9] ,使得 L ( w 5 ′ , w 9 ′ ) < L ( w 5 , w 9 ) L(w'_5, w'_9) < L(w_5, w_9) L(w5,w9)<L(w5,w9)
  • 步骤 3:重复步骤 2,直到损失函数几乎不再下降。

Cómo elegir [ w 5 ′ , w 9 ′ ] [w'_5, w'_9][ w5,w9] es crucial. Primero, debemos asegurarnosde que LLL está cayendo, y lo segundo es hacer que la tendencia a la baja sea lo más rápida posible. El conocimiento básico de cálculo nos dice que la dirección opuesta del gradiente es la dirección en la que el valor de la función disminuye más rápido, como se muestra en la siguiente figura. Para entenderlo simplemente, la dirección del gradiente de una función en un determinado punto es la dirección con la pendiente más grande de la curva, pero la dirección del gradiente es hacia arriba, por lo que la disminución más rápida es en la dirección opuesta al gradiente.

Insertar descripción de la imagen aquí

2.2.4.2 Cálculo del gradiente

El método de cálculo de la función de pérdida se presentó anteriormente y aquí se reescribe ligeramente. Para que el cálculo del gradiente sea más conciso, se introduce el factor 1 2 \frac{1}{2}21, defina la función de pérdida de la siguiente manera:

L = 1 2 N ∑ i = 1 N ( yi − zi ) 2 (14) L = \frac{1}{2N} \sum_{i=1}^N(y_i - z_i)^2 \tag{14}l=2 norte1yo = 1norte( yyozyo)2( 14 )

Itinaka zi z_izyoes el par de redes iiValor previsto de i muestras:

zi = ∑ j = 0 12 xij ⋅ wj + b (15) z_i = \sum_{j=0}^{12} x_i^j \cdot w_j + b\tag{15}zyo=j = 012Xijwj+b( 15 )

Definición de gradiente:

gradiente = ( ∂ L ∂ w 0 , ∂ L ∂ w 1 , . . . , ∂ L ∂ w 12 , ∂ L ∂ b ) (16) \mathrm{gradiente} = (\frac{\partial L}{\partial w_0}, \frac{\partial L}{\partial w_1}, ..., \frac{\partial L}{\partial w_{12}}, \frac{\partial L}{\partial b}) \ etiqueta{16}degradado=(w0∂L _,w1∂L _,... ,w12∂L _,segundo∂L _)( 16 )

LL se puede calcularL awww ybbDerivada parcial de b :

∂ L ∂ wj = 1 N ∑ i = 1 N ( zi − yi ) ∂ zi ∂ wj = 1 N ∑ i = 1 N ( zi − yi ) xij (17) \begin{aligned} \frac{\partial L} {\partial w_j} & = \frac{1}{N} \sum_{i=1}^N(z_i - y_i) \frac{\partial z_i}{\partial w_j}\\ & = \frac{1} {N} \sum_{i=1}^N(z_i - y_i) x_i^j \tag{17} \end{alineado}wj∂L _=norte1yo = 1norte( zyoyyo)wjzyo=norte1yo = 1norte( zyoyyo) xij( 17 )

∂ L ∂ b = 1 N ∑ i = 1 N ( zi − yi ) ∂ zi ∂ b = 1 N ∑ i = 1 N ( zi − yi ) (18) \begin{aligned} \frac{\partial L}{ \partial b} & = \frac{1}{N} \sum_{i=1}^N(z_i - y_i) \frac{\partial z_i}{\partial b}\\ & = \frac{1}{ N} \sum_{i=1}^N(z_i - y_i) \tag{18} \end{alineado}segundo∂L _=norte1yo = 1norte( zyoyyo)segundozyo=norte1yo = 1norte( zyoyyo)( 18 )

Del proceso de cálculo de la derivada se puede ver que el factor 1 2 \frac{1}{2}21se elimina porque la derivación de la función cuadrática producirá un factor de
2, razón por la cual reescribimos la función de pérdida.

A continuación consideramos el caso en el que solo hay una muestra y calculamos el gradiente:

L = 1 2 ( yi − zi ) 2 (19) L = \frac{1}{2}(y_i - z_i)^2 \tag{19}l=21( yyozyo)2( 19 )

z 1 = x 1 0 ⋅ w 0 + x 1 1 ⋅ w 1 + . . . + x 1 12 ⋅ w 12 + b (20) z_1 = x_1^0 \cdot w_0 + x_1^1 \cdot w_1 + ... + x_1^{12} \cdot w_{12} + b \tag{20}z1=X10w0+X11w1+...+X112w12+b( 20 )

Se puede calcular:

L = 1 2 ( x 1 0 ⋅ w 0 + x 1 1 ⋅ w 1 + . . . + x 1 12 ⋅ w 12 + b − y 1 ) 2 (21) L = \frac{1}{2}( x_1^0 \cdot w_0 + x_1^1 \cdot w_1 + ... + x_1^{12} \cdot w_{12} + b - y_1)^2 \tag{21}l=21( x10w0+X11w1+...+X112w12+by1)2( 21 )

LL se puede calcularL awww ybbDerivada parcial de b :

∂ L ∂ w 0 = ( x 1 0 ⋅ w 0 + x 1 1 ⋅ w 1 + . . . + x 1 12 ⋅ w 12 + b − y 1 ) ⋅ x 1 0 = ( z 1 − y 1 ) ⋅ x 1 0 (22) \begin{aligned} \frac{\partial L}{\partial w_0} & = (x_1^0 \cdot w_0 + x_1^1 \cdot w_1 + ... + x_1^{12} \ cdot w_{12} + b - y_1) \cdot x_1^0 \\ & = (z_1 - y_1) \cdot x_1^0 \tag{22} \end{aligned}w0∂L _=( x10w0+X11w1+...+X112w12+by1)X10=( z1y1)X10( 22 )

∂ L ∂ b = ( x 1 0 ⋅ w 0 + x 1 1 ⋅ w 1 + . . . + x 1 12 ⋅ w 12 + b − y 1 ) ⋅ 1 = ( z 1 − y 1 ) (23) \ comenzar{alineado} \frac{\partial L}{\partial b} & = (x_1^0 \cdot w_0 + x_1^1 \cdot w_1 + ... + x_1^{12} \cdot w_{12} + b - y_1) \cdot 1 \\ & = (z_1 - y_1) \tag{23} \end{aligned}segundo∂L _=( x10w0+X11w1+...+X112w12+by1)1=( z1y1)( 23 )

Los datos y dimensiones de cada variable se pueden visualizar mediante procedimientos específicos.

x1 = x[0]
y1 = target[0]
z1 = net.forward(x1)
print(f"x1.shape: {
      
      x1.shape}, The value is: {
      
      x1}")
print(f"y1.shape: {
      
      y1.shape}, The value is: {
      
      y1}")
print(f"z1.shape: {
      
      z1.shape}, The value is: {
      
      z1}")

resultado:

x1.shape: (13,), The value is: 
[0.         0.18       0.07344184 0.         0.31481481 0.57750527
 0.64160659 0.26920314 0.         0.22755741 0.28723404 1.
 0.08967991]
y1.shape: (1,), The value is: [0.42222222]
z1.shape: (1,), The value is: [130.86954441]

De acuerdo con la fórmula anterior, cuando solo hay una muestra, se puede calcular un cierto wj w_jwj, como w 0 w_0w0degradado.

gradient_w0 = (z1 - y1) * x1[0]
print(f"The gradient of w0 is: {
      
      gradient_w0}")  # [0.]

De manera similar podemos calcular w 1 w_1w1degradado.

gradient_w1 = (z1 - y1) * x1[1]
print(f"The gradient of w1 is: {
      
      gradient_w1}")  # [0.35485337]

Calcular wj w_j a su vezwjdegradado.

gradients_of_weights = []
for i in range(net.w.shape[0]):
    gradient = (z1 - y1) * x1[i]
    gradients_of_weights.append(gradient)
    
print(gradients_of_weights)

resultado:

[array([0.]), array([0.35485337]), array([0.14478381]), array([0.]), array([0.62062832]), array([1.13849828]), array([1.26486811]), array([0.53070911]), array([0.]), array([0.44860841]), array([0.56625537]), array([1.9714076]), array([0.17679566])]
2.2.4.3 Usar NumPy para calcular el gradiente

Basado en el mecanismo de transmisión de NumPy (los cálculos vectoriales y matriciales son los mismos que los cálculos sobre una sola variable), los cálculos de gradiente se pueden implementar más rápidamente. En el código para calcular el gradiente , ( z 1 − y 1 ) ⋅ x 1 (z_1 - y_1) \cdot x_1 se usa directamente.( z1y1)X1, el resultado es un vector de 13 dimensiones, cada componente representa el gradiente de esa dimensión.

gradient_w = (z1 - y1) * x1
print(f"[The gradient of w by sampled] gradient_w.shape: {
      
      gradient_w.shape}, gradient_w: \r\n{
      
      gradient_w}")

resultado:

[The gradient of w by sampled] gradient_w.shape: (13,), gradient_w: 
[0.         0.35485337 0.14478381 0.         0.62062832 1.13849828
 1.26486811 0.53070911 0.         0.44860841 0.56625537 1.9714076
 0.17679566]

Hay varias muestras en los datos de entrada, cada una de las cuales contribuye al gradiente. El código anterior calcula el valor del gradiente cuando solo hay la muestra 1. El mismo método de cálculo también puede calcular la contribución de la muestra 2 y la muestra 3 al gradiente.

for i in range(x.shape[0]):
    input = x[i]
    gt = target[i]
    pred = net.forward(input)
    gradient = (pred - gt) * input
    if 1 <= i <= 3:
        print(f"[The gradient of w by sampled_{
      
      i}] gradient.shape: {
      
      gradient.shape}, gradient: \r\n{
      
      gradient}")

resultado:

[The gradient of w by sampled_1] gradient.shape: (13,), gradient: 
[4.95115308e-04 0.00000000e+00 5.50693832e-01 0.00000000e+00
 3.62727044e-01 1.15004718e+00 1.64259797e+00 7.32343840e-01
 9.12450018e-02 2.40970621e-01 1.16094704e+00 2.09863504e+00
 4.29108324e-01]
 
[The gradient of w by sampled_2] gradient.shape: (13,), gradient: 
[3.21688482e-04 0.00000000e+00 3.58140452e-01 0.00000000e+00
 2.35897372e-01 9.47722033e-01 8.18057517e-01 4.76275452e-01
 5.93406432e-02 1.56713807e-01 7.55014992e-01 1.34780052e+00
 8.66203097e-02]
 
[The gradient of w by sampled_3] gradient.shape: (13,), gradient: 
[2.95458209e-04 0.00000000e+00 6.89019665e-02 0.00000000e+00
 1.51571633e-01 6.64543743e-01 4.45830114e-01 4.52623356e-01
 8.77472466e-02 7.37333335e-02 6.54837165e-01 1.00206898e+00
 3.36921340e-02]

Algunos lectores pueden pensar una vez más que se puede utilizar un bucle for para calcular la contribución de cada muestra al gradiente y luego promediarla. Pero no necesitamos hacer esto, aún podemos usar las operaciones matriciales de NumPy para simplificar la operación, como en el caso de 3 muestras.

# 注意这里是一次取出3个样本的数据,不是取出第3个样本
x_3_samples = x[:3]
y_3_samples = target[:3]
z_3_samples = net.forward(x_3_samples)

print('x {}, shape {}'.format(x_3_samples, x_3_samples.shape))
print('y {}, shape {}'.format(y_3_samples, y_3_samples.shape))
print('z {}, shape {}'.format(z_3_samples, z_3_samples.shape))
x [[0.00000000e+00 1.80000000e-01 7.34418420e-02 0.00000000e+00
  3.14814815e-01 5.77505269e-01 6.41606591e-01 2.69203139e-01
  0.00000000e+00 2.27557411e-01 2.87234043e-01 1.00000000e+00
  8.96799117e-02]
 [2.35922539e-04 0.00000000e+00 2.62405717e-01 0.00000000e+00
  1.72839506e-01 5.47997701e-01 7.82698249e-01 3.48961980e-01
  4.34782609e-02 1.14822547e-01 5.53191489e-01 1.00000000e+00
  2.04470199e-01]
 [2.35697744e-04 0.00000000e+00 2.62405717e-01 0.00000000e+00
  1.72839506e-01 6.94385898e-01 5.99382080e-01 3.48961980e-01
  4.34782609e-02 1.14822547e-01 5.53191489e-01 9.87519166e-01
  6.34657837e-02]], shape (3, 13)
  
y [[0.42222222]
 [0.36888889]
 [0.66      ]], shape (3, 1)
 
z [[2.39362982]
 [2.46752393]
 [2.02483479]], shape (3, 1)

x_3_samples, y_3_samples , z_3_samples El tamaño de la primera dimensión es 3, lo que significa que hay 3 muestras. La contribución de estas 3 muestras al gradiente se calcula a continuación.

gradient_w = (z_3_samples - y_3_samples) * x_3_samples
print('gradient_w {}, gradient.shape {}'.format(gradient_w, gradient_w.shape))
gradient_w [[0.00000000e+00 3.54853368e-01 1.44783806e-01 0.00000000e+00
  6.20628319e-01 1.13849828e+00 1.26486811e+00 5.30709115e-01
  0.00000000e+00 4.48608410e-01 5.66255375e-01 1.97140760e+00
  1.76795660e-01]
 [4.95115308e-04 0.00000000e+00 5.50693832e-01 0.00000000e+00
  3.62727044e-01 1.15004718e+00 1.64259797e+00 7.32343840e-01
  9.12450018e-02 2.40970621e-01 1.16094704e+00 2.09863504e+00
  4.29108324e-01]
 [3.21688482e-04 0.00000000e+00 3.58140452e-01 0.00000000e+00
  2.35897372e-01 9.47722033e-01 8.18057517e-01 4.76275452e-01
  5.93406432e-02 1.56713807e-01 7.55014992e-01 1.34780052e+00
  8.66203097e-02]], gradient.shape (3, 13)

Se puede ver aquí que gradient_wla dimensión para calcular el gradiente es 3 × 13 3 × 133×13 , y la primera fila es consistente con el gradiente calculado por la primera muestra anteriorgradient_w_by_sample1, la segunda fila es consistente con el gradiente calculado por la segunda muestra anteriores consistentegradient_w_by_sample2con el gradiente calculado por la tercera muestra anteriorgradient_w_by_sample3Usando operaciones matriciales aquí, es más conveniente calcular la contribución de cada una de las tres muestras al gradiente.

Entonces para NNEn el caso de N muestras, podemos calcular directamente la contribución de todas las muestras al gradiente utilizando el siguiente método: esta es la conveniencia que brinda el uso de la función de transmisión de la biblioteca NumPy. Resumamos aquí la función de transmisión usando la biblioteca NumPy:

  • Por un lado, la dimensión de los parámetros se puede ampliar, reemplazando el bucle for para calcular 1 par de muestras a partir de w 0 w_0w0Remolque 12 w_ {12}w12gradientes de todos los parámetros.
  • Por otro lado, la dimensión de la muestra se puede expandir y el bucle for se puede usar para calcular el gradiente de los parámetros desde la muestra 0 hasta la muestra 403.
z = net.forward(x)
gradient_w = (z - target) * x
print('gradient_w shape {}'.format(gradient_w.shape))
print(gradient_w)
gradient_w shape (404, 13)
[[0.00000000e+00 3.54853368e-01 1.44783806e-01 ... 5.66255375e-01
  1.97140760e+00 1.76795660e-01]
 [4.95115308e-04 0.00000000e+00 5.50693832e-01 ... 1.16094704e+00
  2.09863504e+00 4.29108324e-01]
 [3.21688482e-04 0.00000000e+00 3.58140452e-01 ... 7.55014992e-01
  1.34780052e+00 8.66203097e-02]
 ...
 [7.66711387e-01 0.00000000e+00 3.35694398e+00 ... 3.87578270e+00
  4.79373123e+00 2.45903597e+00]
 [4.83683601e-01 0.00000000e+00 3.14256160e+00 ... 3.62826605e+00
  4.20149273e+00 2.30075782e+00]
 [1.42480820e+00 0.00000000e+00 3.58013213e+00 ... 4.13346610e+00
  5.11244491e+00 2.54493671e+00]]

Cada fila de arriba gradient_wrepresenta la contribución de una muestra al gradiente. Según la fórmula de cálculo del gradiente, el gradiente total es el valor promedio de la contribución de cada muestra al gradiente.

∂ L ∂ w j = 1 N ∑ i = 1 N ( z i − y i ) ∂ z i ∂ w j = 1 N ∑ i = 1 N ( z i − y i ) x i j (17) \begin{aligned} \frac{\partial L}{\partial w_j} & = \frac{1}{N} \sum_{i=1}^N(z_i - y_i) \frac{\partial z_i}{\partial w_j}\\ & = \frac{1}{N} \sum_{i=1}^N(z_i - y_i) x_i^j \tag{17} \end{aligned} wjL=N1i=1N(ziyi)wjzi=N1i=1N(ziyi)xij(17)

可以使用 NumPy 的均值函数来完成此过程,代码实现如下。

# axis = 0 表示把每一行做相加然后再除以总的行数
gradient_w = np.mean(gradient_w, axis=0)
print('gradient_w ', gradient_w.shape)
print('w ', net.w.shape)
print(gradient_w)
print(net.w)
gradient_w  (13,)
w  (13, 1)
[0.10197566 0.20327718 1.21762392 0.43059902 1.05326594 1.29064465
 1.95461901 0.5342187  0.88702053 1.15069786 1.5790441  2.43714929
 0.87116361]
[[ 1.76405235]
 [ 0.40015721]
 [ 0.97873798]
 [ 2.2408932 ]
 [ 1.86755799]
 [-0.97727788]
 [ 0.95008842]
 [-0.15135721]
 [-0.10321885]
 [ 0.4105985 ]
 [ 0.14404357]
 [ 1.45427351]
 [ 0.76103773]]

使用 NumPy 的矩阵操作方便地完成了 gradient 的计算,但引入了一个问题,gradient_w 的形状是 ( 13 , ) (13,) (13,),而 w w Las dimensiones de w son(13, 1) (13, 1)( 13 ,1 ) . El problema se debe anp.meanla eliminación de la dimensión 0 al utilizar la función. Para facilitar cálculos como suma, resta, multiplicación y división,gradient_wywwDebemos mantener una forma consistente. Por lo tantogradient_wtambién establecemos las dimensiones de a(13, 1) (13,1)( 13 ,1 ) , el código es el siguiente:

gradient_w = gradient_w[:, np.newaxis]
print('gradient_w shape', gradient_w.shape)  # gradient_w shape (13, 1)

Según el análisis anterior, el código para calcular el gradiente es el siguiente.

pred = net.forward(x)
gradient_w = (pred - target) * x
gradient_w = np.mean(gradient_w, axis=0)
gradient_w = gradient_w[:, np.newaxis]
print(gradient_w)
[[0.10197566]
 [0.20327718]
 [1.21762392]
 [0.43059902]
 [1.05326594]
 [1.29064465]
 [1.95461901]
 [0.5342187 ]
 [0.88702053]
 [1.15069786]
 [1.5790441 ]
 [2.43714929]
 [0.87116361]]

El código anterior completa el ww de manera muy concisa.Cálculo del gradiente de w . De manera similar, calculebbEl código para el gradiente de b tiene un principio similar.

gradient_b = (pred - target)
gradient_b = np.mean(gradient_b)
# 此处b是一个数值,所以可以直接用np.mean得到一个标量
print(gradient_b)  # 2.599327274554706

Calcular ww desde arribaw ybbEl proceso de gradiente de bNetwork está escrito como una función de clasegradienty el método de implementación es el siguiente.

class Network_with_gradient:
    def __init__(self, num_of_weights):
        np.random.seed(0)
        self.w = np.random.randn(num_of_weights, 1)  # 随机产生w的初始值
        self.b = 0.0  # 不使用偏置
        
    def forward(self, x):
        z = np.dot(x, self.w) + self.b
        return z
    
    def loss(self, pred, gt):
        loss_value_sum = (pred - gt) ** 2
        return np.mean(loss_value_sum)
    
    def gradient(self, x, gt):
        pred = self.forward(x)
        gradient_w = (pred - gt) * x
        gradient_w = np.mean(gradient_w, axis=0)
        gradient_w = gradient_w[:, np.newaxis]
        gradient_b = (pred - gt)
        gradient_b = np.mean(gradient_b)
        
        return gradient_w, gradient_b
    
    
# 初始化网络
net = Network_with_gradient(13)
# 设置[w5, w9] = [-100., -100.]
net.w[5] = -100.0
net.w[9] = -100.0

pred = net.forward(x)
loss = net.loss(pred, target)
gradient_w, gradient_b = net.gradient(x, target)
gradient_w5 = gradient_w[5][0]
gradient_w9 = gradient_w[9][0]
print('point {}, loss {}'.format([net.w[5][0], net.w[9][0]], loss))
print('gradient {}'.format([gradient_w5, gradient_w9]))
point [-100.0, -100.0], loss 7873.345739941161
gradient [-45.87968288123223, -35.50236884482904]
2.2.4.4 Actualización de gradiente

A continuación, estudiaremos el método de actualización del gradiente para determinar el punto donde la función de pérdida es menor. Primero muévase un pequeño paso en la dirección opuesta al gradiente para encontrar el siguiente punto P 1 P_1PAG1, observe los cambios en la función de pérdida.

# 在[w5, w9]平面上,沿着梯度的反方向移动到下一个点P1
# 定义移动步长 eta
eta = 0.1
# 更新参数w5和w9
net.w[5] = net.w[5] - eta * gradient_w5
net.w[9] = net.w[9] - eta * gradient_w9
# 重新计算z和loss
pred = net.forward(x)
loss = net.loss(pred, target)
gradient_w, gradient_b = net.gradient(x, target)
gradient_w5 = gradient_w[5][0]
gradient_w9 = gradient_w[9][0]
print('point {}, loss {}'.format([net.w[5][0], net.w[9][0]], loss))
print('gradient {}'.format([gradient_w5, gradient_w9]))
point [-95.41203171187678, -96.4497631155171], loss 7214.694816482369
gradient [-43.883932999069096, -34.019273908495926]

Al ejecutar el código anterior, puede encontrar que al dar un pequeño paso en la dirección opuesta del gradiente, la función de pérdida en el siguiente punto se reduce. Si está interesado, puede intentar hacer clic en el bloque de código de arriba para ver si la función de pérdida sigue haciéndose más pequeña.

在上述代码中,每次更新参数使用的语句: net.w[5] = net.w[5] - eta * gradient_w5

  • 相减:参数需要向梯度的反方向移动。
  • eta:控制每次参数值沿着梯度反方向变动的大小,即每次移动的步长,又称为学习率。

大家可以思考下,为什么之前我们要做输入特征的归一化,保持尺度一致?这是为了让统一的步长更加合适,使训练更加高效。

如下图所示,特征输入归一化后,不同参数输出的 Loss 是一个比较规整的曲线,学习率可以设置成统一的值 ;特征输入未归一化时,不同特征对应的参数所需的步长不一致,尺度较大的参数需要大步长,尺寸较小的参数需要小步长,导致无法设置统一的学习率。

Insertar descripción de la imagen aquí

2.2.4.5 封装 Train 函数

将上面的循环计算过程封装在 trainupdate 函数中,实现方法如下所示。

import numpy as np
import json
import matplotlib.pyplot as plt


class Network_with_gradient:
    def __init__(self, num_of_weights):
        np.random.seed(0)
        self.w = np.random.randn(num_of_weights, 1)  # 随机产生w的初始值
        self.b = 0.0  # 不使用偏置
        
    def forward(self, x):
        z = np.dot(x, self.w) + self.b
        return z
    
    def loss(self, pred, gt):
        loss_value_sum = (pred - gt) ** 2
        return np.mean(loss_value_sum)
    
    def gradient(self, x, gt):
        pred = self.forward(x)
        gradient_w = (pred - gt) * x
        gradient_w = np.mean(gradient_w, axis=0)
        gradient_w = gradient_w[:, np.newaxis]
        gradient_b = (pred - gt)
        gradient_b = np.mean(gradient_b)
        
        return gradient_w, gradient_b
    
    def update(self, gradient_w5, gradient_w9, lr=0.01):
        self.w[5] = self.w[5] - lr * gradient_w5
        self.w[9] = self.w[9] - lr * gradient_w9
        
    def train(self, inp, gt, interations=100, lr=0.01):
        pts = []
        losses = []
        
        for i in range(interations):
            pts.append([self.w[5][0], self.w[9][0]])
            pred = self.forward(inp)
            loss = self.loss(pred, gt)
            gradient_w, gradient_b = self.gradient(x=inp, gt=gt)
            gradient_w5 = gradient_w[5][0]
            gradient_w9 = gradient_w[9][0]
            self.update(gradient_w5, gradient_w9, lr=lr)
            losses.append(loss)
            
            if i % 50 == 0:
                print(f"iter: {
      
      i}, point: {
      
      [self.w[5][0], self.w[9][0]]}, Loss: {
      
      loss}")
        return pts, losses


def data_load():
    # 2.2.1.1 读入数据
    datafile = "/data/data_01/lijiandong/Datasets/boston_house_price/housing.data"
    data = np.fromfile(datafile, sep=" ")


    # 2.2.1.2 数据形状变换
    feature_names = [ 'CRIM', 'ZN', 'INDUS', 'CHAS', 'NOX', 'RM', 'AGE','DIS', 'RAD', 'TAX', 'PTRATIO', 'B', 'LSTAT', 'MEDV' ]
    feature_num = len(feature_names)
    data = data.reshape([data.shape[0] // feature_num, feature_num])  # [N, 14]


    # 2.2.1.3 数据集划分
    ratio = 0.8
    offset = int(data.shape[0] * ratio)
    training_data = data[:offset]
    test_data = data[offset:]


    # 2.2.1.4 数据归一化处理
    # 计算train数据集的最大值和最小值
    maxinums, minimus = training_data.max(axis=0), training_data.min(axis=0)

    # 对数据进行归一化处理
    for col_name in range(feature_num):
        # 训练集归一化
        training_data[:, col_name] = (training_data[:, col_name] - minimus[col_name]) / (maxinums[col_name] - minimus[col_name])
        # 测试集归一化(确保了测试集上的数据也使用了与训练集相同的归一化转换,避免了引入测试集信息污染)
        test_data[:, col_name] = (test_data[:, col_name] - minimus[col_name]) / (maxinums[col_name] - minimus[col_name])
        
    return training_data, test_data
    
    
if __name__ == "__main__":
    # 获取数据
    train_data, test_data = data_load()
    inputs = train_data[:, :-1]
    targets = train_data[:, -1:]
    
    # 创建网络
    model = Network_with_gradient(num_of_weights=13)
    num_iteration = 2000
    
    # 启动训练
    points, losses = model.train(inputs, targets, num_iteration, lr=0.01)
    
    # 画出损失函数的变化趋势
    plot_x = np.arange(num_iteration)
    plot_y = np.array(losses)
    plt.plot(plot_x, plot_y)
    plt.xlabel("Iteration")
    plt.ylabel("Loss")
    plt.savefig("test_model_loss.png")
iter: 0, point: [-0.9901843263352099, 0.39909152329488246], Loss: 8.74595446663459
iter: 50, point: [-1.565620876463878, -0.12301073215571104], Loss: 6.306355217447029
iter: 100, point: [-2.022354418155828, -0.5542991021920926], Loss: 4.711795652571785
iter: 150, point: [-2.3835826672990916, -0.9119900464266062], Loss: 3.6675975768722684
...
iter: 1850, point: [-3.2091283870302365, -3.3267726714708044], Loss: 1.4862553256192466
iter: 1900, point: [-3.1961376490600024, -3.344849195951625], Loss: 1.4842710198735334
iter: 1950, point: [-3.183547669731126, -3.3622933329823237], Loss: 1.4824177199043154

Insertar descripción de la imagen aquí

2.2.4.6 训练过程扩展到全部参数

为了能给读者直观的感受,上文演示的梯度下降的过程仅包含 w 5 w_5 w5 w 9 w_9 w9 两个参数。但房价预测的模型必须要对所有参数 w w w b b b 进行求解,这需要将 Network 中的 updatetrain 函数进行修改。由于不再限定参与计算的参数(所有参数均参与计算),修改之后的代码反而更加简洁。

实现逻辑:

  1. 前向计算输出
  2. 根据输出和真实值计算 Loss
  3. 基于 Loss 和输入计算梯度
  4. 根据梯度更新参数值

四个部分反复执行,直到到损失函数最小。

import numpy as np
import json
import matplotlib.pyplot as plt


class Network_full_weights:
    def __init__(self, num_of_weights):
        np.random.seed(0)
        self.w = np.random.randn(num_of_weights, 1)  # 随机产生w的初始值
        self.b = 0.0  # 不使用偏置
        
    def forward(self, x):
        z = np.dot(x, self.w) + self.b
        return z
    
    def loss(self, pred, gt):
        loss_value_sum = (pred - gt) ** 2
        return np.mean(loss_value_sum)
    
    def gradient(self, x, gt):
        pred = self.forward(x)
        gradient_w = (pred - gt) * x
        gradient_w = np.mean(gradient_w, axis=0)
        gradient_w = gradient_w[:, np.newaxis]
        gradient_b = (pred - gt)
        gradient_b = np.mean(gradient_b)
        
        return gradient_w, gradient_b
    
    def update(self, gradient_w, gradient_b, lr=0.01):
        self.w = self.w - lr * gradient_w  # 负梯度,所以是减
        self.b = self.b - lr * gradient_b  # 负梯度,所以是减
        
    def train(self, inp, gt, interations=100, lr=0.01):
        pts = []
        losses = []
        
        for i in range(interations):
            pts.append([self.w[5][0], self.w[9][0]])
            pred = self.forward(inp)
            loss = self.loss(pred, gt)
            gradient_w, gradient_b = self.gradient(x=inp, gt=gt)
            self.update(gradient_w, gradient_b, lr=lr)
            losses.append(loss)
            
            if i % 50 == 0:
                print(f"iter: {
      
      i}, point: {
      
      [self.w[5][0], self.w[9][0]]}, Loss: {
      
      loss}")
        return pts, losses


def data_load():
    # 2.2.1.1 读入数据
    datafile = "/data/data_01/lijiandong/Datasets/boston_house_price/housing.data"
    data = np.fromfile(datafile, sep=" ")


    # 2.2.1.2 数据形状变换
    feature_names = [ 'CRIM', 'ZN', 'INDUS', 'CHAS', 'NOX', 'RM', 'AGE','DIS', 'RAD', 'TAX', 'PTRATIO', 'B', 'LSTAT', 'MEDV' ]
    feature_num = len(feature_names)
    data = data.reshape([data.shape[0] // feature_num, feature_num])  # [N, 14]


    # 2.2.1.3 数据集划分
    ratio = 0.8
    offset = int(data.shape[0] * ratio)
    training_data = data[:offset]
    test_data = data[offset:]


    # 2.2.1.4 数据归一化处理
    # 计算train数据集的最大值和最小值
    maxinums, minimus = training_data.max(axis=0), training_data.min(axis=0)

    # 对数据进行归一化处理
    for col_name in range(feature_num):
        # 训练集归一化
        training_data[:, col_name] = (training_data[:, col_name] - minimus[col_name]) / (maxinums[col_name] - minimus[col_name])
        # 测试集归一化(确保了测试集上的数据也使用了与训练集相同的归一化转换,避免了引入测试集信息污染)
        test_data[:, col_name] = (test_data[:, col_name] - minimus[col_name]) / (maxinums[col_name] - minimus[col_name])
        
    return training_data, test_data
    
    
if __name__ == "__main__":
    # 获取数据
    train_data, test_data = data_load()
    inputs = train_data[:, :-1]
    targets = train_data[:, -1:]
    
    # 创建网络
    model = Network_full_weights(num_of_weights=13)
    num_iteration = 2000
    
    # 启动训练
    points, losses = model.train(inputs, targets, num_iteration, lr=0.01)
    
    # 画出损失函数的变化趋势
    plot_x = np.arange(num_iteration)
    plot_y = np.array(losses)
    plt.plot(plot_x, plot_y)
    plt.xlabel("Iteration")
    plt.ylabel("Loss")
    plt.savefig("test_model_loss.png")
iter: 0, point: [-0.9901843263352099, 0.39909152329488246], Loss: 8.74595446663459
iter: 50, point: [-1.2510816196755312, 0.10537114344977061], Loss: 1.2774697388163774
iter: 100, point: [-1.248700167154482, 0.013128221346003856], Loss: 0.8996702309578077
iter: 150, point: [-1.207833898147619, -0.03959546161885696], Loss: 0.7517595081577438
iter: 200, point: [-1.1647360167738359, -0.08022928159585666], Loss: 0.639972629138626
...
iter: 1850, point: [-0.593635514684025, -0.23371944218812102], Loss: 0.10208939582379549
iter: 1900, point: [-0.5835935123829064, -0.23148564712851874], Loss: 0.09960847979302702
iter: 1950, point: [-0.5736614290879241, -0.2292815676008641], Loss: 0.09724348540057882

Insertar descripción de la imagen aquí

2.2.4.7 Descenso del gradiente estocástico

En el programa anterior, cada función de pérdida y cálculo de gradiente se basa en la cantidad total de datos del conjunto de datos. Para el conjunto de datos de la tarea de predicción del precio de la vivienda en Boston, el número de muestras es relativamente pequeño, solo 404. Sin embargo, en problemas prácticos, el conjunto de datos suele ser muy grande. Si se utiliza la cantidad total de datos para el cálculo cada vez, la eficiencia es muy baja. En términos sencillos, es "matar un pollo con un cuchillo de toro". Dado que los parámetros solo se actualizan un poco en la dirección opuesta al gradiente cada vez, no es necesario que la dirección sea tan precisa. Una solución razonable es extraer aleatoriamente una pequeña parte de los datos del conjunto de datos total cada vez para representar el conjunto y calcular el gradiente y la pérdida en función de esta parte de los datos para actualizar los parámetros. Este método se llama descenso de gradiente estocástico. . SGD), los conceptos centrales son los siguientes:

  • minilote : un lote de datos extraídos durante cada iteración se denomina minilote.
  • tamaño_lote : el número de muestras contenidas en un mini lote se denomina tamaño_lote.
  • Época : Cuando el programa itera, las muestras se extraen gradualmente según un mini lote. Cuando se recorre todo el conjunto de datos, se completa una ronda de entrenamiento, también llamada época. Al comenzar el entrenamiento, puede pasar el número de rondas de entrenamiento num_epochs y batch_size como parámetros.

El proceso de implementación específico se presenta a continuación junto con el programa, que implica la modificación del código en los procesos de procesamiento de datos y capacitación.

2.2.4.7.1 Modificación del código de procesamiento de datos

El procesamiento de datos requiere dos funciones: dividir lotes de datos y reordenar muestras (para lograr el efecto de muestreo aleatorio).

# 获取数据
train_data, test_data = data_load()
print(train_data.shape)  # (404, 14)

train_datacontiene un total de 404 datos. Si batch_size=10, las primeras 0 a 9 muestras se toman como el primer mini lote y se denominan train_data1.

train_data1 = train_data[:10]
print(train_data1.shape)  # (10, 14)

Utilice train_data1los datos (muestras 0 ~ 9) para calcular el gradiente y actualizar los parámetros de la red.

# 获取数据
train_data, test_data = data_load()
print(train_data.shape)  # (404, 14)

train_data1 = train_data[:10]
print(train_data1.shape)  # (10, 14)

model = Network_full_weights(13)
inputs = train_data1[:, :-1]
targets = train_data1[:, -1:]

points, losses = model.train(inputs, targets, 1, lr=0.01)
print(losses[0])  # 4.497480200683046

Luego tome las muestras No. 10 ~ 19 como segundo mini lote, calcule el gradiente y actualice los parámetros de la red. Según este método, continuamente se extraen nuevos minilotes y los parámetros de la red se actualizan gradualmente.

Luego, train_datadivida en múltiples mini_batch de tamaño batch_size, como se muestra en el siguiente código: se dividirá train_dataen 404 10 + 1 = 41 \frac{404}{10} + 1 = 4110404+1=Hay 41 mini_batch, los primeros 40 mini_batch contienen cada uno 10 muestras y el último mini_batch solo contiene 4 muestras.

# 获取数据
train_data, test_data = data_load()
batch_size = 10
n = len(train_data)
mini_batches = [train_data[k: k+batch_size] for k in range(0, n, batch_size)]  # 如果不够k+batch_size了,那么就取完

print(f"第一个mini_batch的shape为: {
      
      mini_batches[0].shape}")  # (10, 14)
print(f"最后一个mini_batch的shape为: {
      
      mini_batches[-1].shape}")  # (4, 14)

Además, aquí mini_batch se lee secuencialmente, mientras que en SGD una parte de las muestras se selecciona aleatoriamente para representar la población. Para lograr el efecto del muestreo aleatorio, primero train_dataalteramos aleatoriamente el orden de las muestras internas y luego extraemos mini_batch. Para alterar aleatoriamente el orden de las muestras, es necesario utilizar np.random.shufflela función. Primero introduzcamos su uso.

Explicación : A través de una gran cantidad de experimentos, se ha descubierto que el modelo se ve más afectado por las últimas etapas del entrenamiento, de manera similar a cómo el cerebro humano siempre recuerda con mayor claridad los eventos recientes. Para evitar que el orden del conjunto de muestras de datos interfiera con el efecto de entrenamiento del modelo, es necesario realizar operaciones de reordenamiento de muestras. Por supuesto, si el orden de las muestras de entrenamiento es el orden en que se generaron las muestras, y esperamos que el modelo preste más atención a las muestras generadas recientemente (las muestras predichas estarán más cerca de la distribución de las muestras de entrenamiento recientes), entonces No es necesario barajar este paso.

a = np.array(list(range(1, 13)))
print(f"Shuffle前的数组为: {
      
      a}")  # [ 1  2  3  4  5  6  7  8  9 10 11 12]
np.random.shuffle(a)  # inplace操作
print(f"Shuffle后的数组为: {
      
      a}")  # [ 6  9  1 11  4 10  7  3  2 12  5  8]

Ejecute el código anterior varias veces y podrá encontrar que shuffleel orden de los números después de ejecutar la función es diferente cada vez. El ejemplo anterior es un caso en el que una matriz unidimensional está fuera de orden. Veamos el efecto de una matriz bidimensional fuera de orden.

a = np.array(list(range(1, 13))).reshape([6, 2])
print(f"Shuffle前的数组为: \r\n{
      
      a}")
np.random.shuffle(a)  # inplace操作
print(f"Shuffle后的数组为: \r\n{
      
      a}")
Shuffle前的数组为: 
[[ 1  2]
 [ 3  4]
 [ 5  6]
 [ 7  8]
 [ 9 10]
 [11 12]]
 
Shuffle后的数组为: 
[[ 5  6]
 [ 1  2]
 [ 3  4]
 [11 12]
 [ 9 10]
 [ 7  8]]

Al observar los resultados de la ejecución, podemos encontrar que los elementos de la matriz se mezclan aleatoriamente en la dimensión 0, pero el orden de la dimensión 1 permanece sin cambios. Por ejemplo, el número 2 todavía está inmediatamente detrás del número 1, el número 8 todavía está inmediatamente detrás del número 7 y la segunda dimensión [3, 4] no viene después de [1, 2]. Integre esta parte del código que implementa el algoritmo SGD en trainla función de la clase Network. El código completo final es el siguiente.

# 获取数据
train_data, test_data = data_load()
    
# Shuffle
# 在训练集需要进行shuffle(随机打乱样本顺序)的情况下,测试
np.random.shuffle(train_data)
    
# 将 train_data 分成多个 mini_batch
batch_size = 10
n = len(train_data)
mini_batches = [train_data[k: k+batch_size] for k in range(0
    
# 创建网络
model = Network_full_weights(13)
# 依次使用每个mini_batch的数据
for idx, mini_batch in enumerate(mini_batches):
    inputs = mini_batch[:, :-1]
    targets = mini_batch[:, -1:]
    loss = model.train(inputs, targets, interations=1, lr=0.
    print(f"mini_batch[{
      
      idx}]'s loss: {
      
      loss}")
iter: 0, point: [-0.988214319272273, 0.4040241417140549], Loss: 5.310067748287684
mini_batch[0]'s loss: ([[-0.977277879876411, 0.41059850193837233]], [5.310067748287684])
iter: 0, point: [-1.0000665119658712, 0.3901139032526141], Loss: 9.948974311703784
mini_batch[1]'s loss: ([[-0.988214319272273, 0.4040241417140549]], [9.948974311703784])
iter: 0, point: [-1.0099096964900296, 0.3859828997852013], Loss: 4.582071696627923
...
mini_batch[39]'s loss: ([[-1.2343588100526453, 0.14439735680045832]], [1.0128812556524254])
iter: 0, point: [-1.237167617412837, 0.1414588534395813], Loss: 0.09470445497034455
mini_batch[40]'s loss: ([[-1.236799748470745, 0.14160559639242365]], [0.09470445497034455])
2.2.4.7.2 Modificación del código del proceso de formación

Cada dato de minilote seleccionado aleatoriamente se ingresa en el modelo para el entrenamiento de parámetros. El núcleo del proceso de formación es un bucle de dos capas:

  1. El primer nivel del bucle representa cuántas veces se entrenará y atravesará el conjunto de muestras, lo que se denomina "época" y el código es el siguiente:
for epoch in range(epochs)
  1. El segundo nivel del bucle representa los múltiples lotes en los que se divide el conjunto de muestras durante cada recorrido, y todos deben entrenarse, lo que se denomina "iter (iteración)". El código es el siguiente:
for iter_idx, mini_batch in enumerate(mini_batches):

Dentro del bucle de dos capas se encuentra el proceso de entrenamiento clásico de cuatro pasos: cálculo directo->calcular pérdida->calcular gradiente->actualizar parámetros. Esto es consistente con lo que ha aprendido antes. El código es el siguiente:

# 前向推理
pred = model.forward(inputs)

# 计算损失
loss = model.train(inputs, targets, interations=1, lr=0.01)

# 计算梯度
gradient_w, gradient_b = model.gradient(inputs, targets)

# 更新参数
model.update(gradient_w, gradient_b, lr=0.01)

Integre las dos partes del código reescrito en trainla función de la clase Red y la implementación final es la siguiente.

import numpy as np
import json
import matplotlib.pyplot as plt


class Network_full_weights:
    def __init__(self, num_of_weights):
        np.random.seed(0)
        self.w = np.random.randn(num_of_weights, 1)  # 随机产生w的初始值
        self.b = 0.0  # 不使用偏置
        
    def forward(self, x):
        z = np.dot(x, self.w) + self.b
        return z
    
    def loss(self, pred, gt):
        loss_value_sum = (pred - gt) ** 2
        return np.mean(loss_value_sum)
    
    def gradient(self, x, gt):
        pred = self.forward(x)
        gradient_w = (pred - gt) * x
        gradient_w = np.mean(gradient_w, axis=0)
        gradient_w = gradient_w[:, np.newaxis]
        gradient_b = (pred - gt)
        gradient_b = np.mean(gradient_b)
        
        return gradient_w, gradient_b
    
    def update(self, gradient_w, gradient_b, lr=0.01):
        self.w = self.w - lr * gradient_w  # 负梯度,所以是减
        self.b = self.b - lr * gradient_b  # 负梯度,所以是减
        
    def train(self, inputs, epochs=100, batch_size=1, lr=0.01):
        n = len(inputs)
        losses = []
        
        for epoch in range(epochs):
            # 在每轮迭代开始之前,将训练数据的顺序随机打乱
            # 然后再按每次取batch_size条数据的方式取出
            np.random.shuffle(inputs)
            
            # 将训练数据进行拆分,每个mini_batch包含batch_size条的数据
            mini_batches = [inputs[k: k+batch_size] for k in range(0, n, batch_size)]
            
            # 对每个mini_batch进行训练
            for iter_idx, mini_batch in enumerate(mini_batches):
                x = mini_batch[:, :-1]
                gt = mini_batch[:, -1:]
                
                # infer
                pred = self.forward(x)

                # calc loss
                loss = self.loss(pred, gt)

                # record loss
                losses.append(loss)

                # calc gradients
                gradient_w, gradient_b = self.gradient(x, gt)
                
                # update weights and bias
                self.update(gradient_w, gradient_b, lr)
                
                # print
                print(f"Epoch: {
      
      epoch}\titer: {
      
      iter_idx}\tloss: {
      
      loss:.4f}")
        
        return losses


def data_load():
    # 2.2.1.1 读入数据
    datafile = "/data/data_01/lijiandong/Datasets/boston_house_price/housing.data"
    data = np.fromfile(datafile, sep=" ")


    # 2.2.1.2 数据形状变换
    feature_names = [ 'CRIM', 'ZN', 'INDUS', 'CHAS', 'NOX', 'RM', 'AGE','DIS', 'RAD', 'TAX', 'PTRATIO', 'B', 'LSTAT', 'MEDV' ]
    feature_num = len(feature_names)
    data = data.reshape([data.shape[0] // feature_num, feature_num])  # [N, 14]


    # 2.2.1.3 数据集划分
    ratio = 0.8
    offset = int(data.shape[0] * ratio)
    training_data = data[:offset]
    test_data = data[offset:]


    # 2.2.1.4 数据归一化处理
    # 计算train数据集的最大值和最小值
    maxinums, minimus = training_data.max(axis=0), training_data.min(axis=0)

    # 对数据进行归一化处理
    for col_name in range(feature_num):
        # 训练集归一化
        training_data[:, col_name] = (training_data[:, col_name] - minimus[col_name]) / (maxinums[col_name] - minimus[col_name])
        # 测试集归一化(确保了测试集上的数据也使用了与训练集相同的归一化转换,避免了引入测试集信息污染)
        test_data[:, col_name] = (test_data[:, col_name] - minimus[col_name]) / (maxinums[col_name] - minimus[col_name])
        
    return training_data, test_data
    
    
if __name__ == "__main__":
    # get data
    train_data, test_data = data_load()
    
    # create model
    model = Network_full_weights(13)

    # start training
    losses = model.train(train_data, epochs=100, batch_size=100, lr=0.1)
    
    # 画出损失函数的变化趋势
    plot_x = np.arange(len(losses))
    plot_y = np.array(losses)
    plt.plot(plot_x, plot_y)
    plt.xlabel("Iteration = samples num / batch size * epochs")
    plt.ylabel("Loss")
    plt.savefig("test_model_loss.png")

resultado:

Epoch: 0        iter: 0 loss: 10.1354
Epoch: 0        iter: 1 loss: 3.8290
Epoch: 0        iter: 2 loss: 1.9208
Epoch: 0        iter: 3 loss: 1.7740
Epoch: 0        iter: 4 loss: 0.3366
Epoch: 1        iter: 0 loss: 1.6275
Epoch: 1        iter: 1 loss: 0.9842
Epoch: 1        iter: 2 loss: 0.9121
Epoch: 1        iter: 3 loss: 0.9971
Epoch: 1        iter: 4 loss: 0.6071
...
Epoch: 98       iter: 0 loss: 0.0662
Epoch: 98       iter: 1 loss: 0.0468
Epoch: 98       iter: 2 loss: 0.0267
Epoch: 98       iter: 3 loss: 0.0340
Epoch: 98       iter: 4 loss: 0.0203
Epoch: 99       iter: 0 loss: 0.0342
Epoch: 99       iter: 1 loss: 0.0688
Epoch: 99       iter: 2 loss: 0.0377
Epoch: 99       iter: 3 loss: 0.0317
Epoch: 99       iter: 4 loss: 0.0061

Insertar descripción de la imagen aquí

Al observar los cambios en la pérdida anterior, el descenso del gradiente estocástico acelera el proceso de entrenamiento, pero dado que los parámetros se actualizan y la pérdida se calcula basándose solo en una pequeña cantidad de muestras cada vez, la curva de disminución de la pérdida oscilará.

Descripción :

  1. Dado que la cantidad de datos para la predicción del precio de la vivienda es demasiado pequeña, es difícil sentir la mejora del rendimiento provocada por el descenso del gradiente estocástico.
  2. En el aprendizaje automático, el número de iteraciones (Iteración) se refiere al número total de veces que se actualizan los parámetros del modelo durante el proceso de entrenamiento. Se calcula dividiendo la cantidad de elementos (la cantidad de muestras en el conjunto de datos) por el tamaño del lote, multiplicado por la cantidad de épocas de entrenamiento. Es decir: Número de iteraciones (Iteración) = Número de muestras Tamaño de lote × E poch \rm Número de iteraciones (Iteración) =\frac{Número de muestras}{Tamaño de lote} \times ÉpocaNúmero de iteraciones ( Iteración )=Tamaño del loteTamaño de la muestra×La fórmula Epoch nos dice cuántas veces el modelo actualizará los parámetros durante todo el proceso de entrenamiento. El proceso de entrenamiento pasará por varias épocas. Cada época divide el conjunto de datos en varios lotes para el entrenamiento. El tamaño del lote está determinado por el tamaño del lote.

2.2.5 Guardar modelo

Numpy proporciona saveuna interfaz que puede guardar directamente la matriz de pesos del modelo en .npyun archivo en el formato.

np.save("w.npy", model.w)  # 保存模型参数
np.save("b.npy", model.b)  # 保存模型偏置

2.2.6 Resumen

En este capítulo, presentamos en detalle cómo usar NumPy para implementar el algoritmo de descenso de gradiente y construir y entrenar un modelo lineal simple para predecir los precios de la vivienda en Boston. Se puede concluir que hay tres puntos clave en el uso de redes neuronales para modelar viviendas. predicciones de precios:

  1. Construya la red e inicialice los parámetros www ybbb , defina el método de cálculo de la predicción y la función de pérdida.
  2. Seleccione aleatoriamente el punto inicial y establezca el método de cálculo del gradiente y el método de actualización de parámetros.
  3. Extraiga parte de los datos del conjunto de datos total como un mini_batch, calcule el gradiente y actualice los parámetros, y continúe iterando hasta que la función de pérdida casi ya no disminuya.

3. Utilice PaddlePaddle para reescribir la tarea de predicción del precio de la vivienda en Boston

Los casos de este tutorial cubren escenarios de aplicaciones convencionales, como visión por computadora, procesamiento de lenguaje natural y sistemas de recomendación. El proceso de utilizar Flying Paddle para implementar estos casos es básicamente el mismo, como se muestra en la figura siguiente.

Insertar descripción de la imagen aquí

En el Capítulo 2, aprendimos cómo usar Python y NumPy para implementar la tarea de predicción del precio de la vivienda en Boston. En este capítulo, intentaremos usar Flying Paddle para reescribir la tarea de predicción del precio de la vivienda y experimentaremos las similitudes y diferencias entre las dos. Antes del procesamiento de datos, primero se deben cargar las bibliotecas de clases relevantes del marco de la paleta voladora.

import paddle
from paddle.nn import Linear
import paddle.nn as nn
import paddle.nn.functional as F
import paddle.optimizer as optimizer
import numpy as np
import os
import random

El significado de los parámetros en el código es el siguiente:

  • paddle: La biblioteca principal de Paddle. Los alias de las API de uso común están reservados en el directorio raíz de Paddle, y actualmente incluyen:
    • paleta.tensor
    • padel.marco
    • dispositivo.de.padel
    • Todas las API en el directorio;
  • Linear:神经网络的全连接层函数,包含所有输入权重相加的基本神经元结构。在房价预测任务中,使用只有一层的神经网络(全连接层)实现线性回归模型。
  • paddle.nn:组网相关的 API,包括 Linear、卷积 Conv2D、循环神经网络 LSTM、损失函数 CrossEntropyLoss、激活函数 ReLU 等;
  • paddle.nn.functional:与 paddle.nn 一样,包含组网相关的 API,如:Linear、激活函数 ReLU 等,二者包含的同名模块功能相同,运行性能也基本一致。 差别在于 paddle.nn 目录下的模块均是类,每个类自带模块参数;paddle.nn.functional 目录下的模块均是函数,需要手动传入函数计算所需要的参数。在实际使用时,卷积、全连接层等本身具有可学习的参数,建议使用 paddle.nn;而激活函数、池化等操作没有可学习参数,可以考虑使用 paddle.nn.functional

说明:飞桨支持两种深度学习建模编写方式,更方便调试的动态图模式和性能更好并便于部署的静态图模式。

  • 动态图模式(命令式编程范式,类比 Python):解析式的执行方式。用户无需预先定义完整的网络结构,每写一行网络代码,即可同时获得计算结果;
  • 静态图模式(声明式编程范式,类比 C++):先编译后执行的方式。用户需预先定义完整的网络结构,再对网络结构进行编译优化后,才能执行获得计算结果。

飞桨框架 2.0 及之后的版本,默认使用动态图模式进行编码,同时提供了完备的动转静支持,开发者仅需添加一个装饰器(to_static),飞桨会自动将动态图的程序转换为静态图的 program,并使用该 program 训练并可保存静态模型以实现推理部署。

3.1 数据处理

数据处理的代码不依赖框架实现,与使用 Python 构建房价预测任务的代码相同,详细解读请参考第二章,这里不再赘述。

def load_data():
    # 从文件导入数据
    datafile = './work/housing.data'
    data = np.fromfile(datafile, sep=' ', dtype=np.float32)

    # 每条数据包括14项,其中前面13项是影响因素,第14项是相应的房屋价格中位数
    feature_names = [ 'CRIM', 'ZN', 'INDUS', 'CHAS', 'NOX', 'RM', 'AGE', \
                      'DIS', 'RAD', 'TAX', 'PTRATIO', 'B', 'LSTAT', 'MEDV' ]
    feature_num = len(feature_names)

    # 将原始数据进行Reshape,变成[N, 14]这样的形状
    data = data.reshape([data.shape[0] // feature_num, feature_num])

    # 将原数据集拆分成训练集和测试集
    # 这里使用80%的数据做训练,20%的数据做测试
    # 测试集和训练集必须是没有交集的
    ratio = 0.8
    offset = int(data.shape[0] * ratio)
    training_data = data[:offset]

    # 计算train数据集的最大值,最小值
    maximums, minimums = training_data.max(axis=0), training_data.min(axis=0)
    
    # 记录数据的归一化参数,在预测时对数据做归一化
    global max_values
    global min_values
   
    max_values = maximums
    min_values = minimums
    
    # 对数据进行归一化处理
    for i in range(feature_num):
        data[:, i] = (data[:, i] - min_values[i]) / (maximums[i] - minimums[i])

    # 训练集和测试集的划分比例
    training_data = data[:offset]
    test_data = data[offset:]
    return training_data, test_data
# 验证数据集读取程序的正确性
training_data, test_data = load_data()
print(training_data.shape)
print(training_data[1,:])
(404, 14)

[2.35922547e-04 0.00000000e+00 2.62405723e-01 0.00000000e+00
 1.72839552e-01 5.47997713e-01 7.82698274e-01 3.48961979e-01
 4.34782617e-02 1.14822544e-01 5.53191364e-01 1.00000000e+00
 2.04470202e-01 3.68888885e-01]

3.2 模型设计

模型定义的实质是定义线性回归的网络结构,飞桨建议通过创建 Python 类的方式完成模型网络的定义,该类需要继承 paddle.nn.Layer 父类,并且在类中定义 __init__ 函数和 forward 函数。forward 函数是框架指定实现前向计算逻辑的函数,程序在调用模型实例时会自动执行,forward 函数中使用的网络层需要在 __init__ 函数中声明。

  • 定义 __init__ 函数:在类的初始化函数中声明每一层网络的实现函数。在房价预测任务中,只需要定义一层全连接层,模型结构和第二章保持一致;
  • 定义 forward 函数:构建神经网络结构,实现前向计算过程,并返回预测结果,在本任务中返回的是房价预测结果。
class Regression(nn.Layer):
    def __init__(self):
        super(Regression, self).__init__()
        
        # 定义一层全连接层,输入维度是13,输出维度是1
        self.fc = nn.Linear(in_features=13, out_features=1)
        
    
    def forward(self, inputs):
        x = self.fc(inputs)
        return x

3.3 训练配置

训练配置过程如下图所示:

Insertar descripción de la imagen aquí

if __name__ == "__main__":
    # 实例化模型
    model = Regression()
    
    # 开启模型训练模式
    model.train()
    
    # 加载数据
    training_data, test_data = load_data()
    
    # 定义优化算法,使用SGD
    optimizer = optimizer.SGD(learning_rate=0.01, parameters=model.parameters())

说明:模型实例有两种状态:训练状态 .train() 和预测状态 .eval()。训练时要执行正向计算和反向传播梯度两个过程,而预测时只需要执行正向计算,为模型指定运行状态,有两点原因:

  1. 部分高级的算子在两个状态执行的逻辑不同,如:DropoutBatchNorm(在后续的“计算机视觉”章节会详细介绍);
  2. 从性能和存储空间的考虑,预测状态时更节省内存(无需记录反向梯度),性能更好。

使用飞桨框架只需要设置 SGD 函数的参数并调用,即可实现优化器设置,大大简化了这个过程。

3.4 训练过程

训练过程采用二层循环嵌套方式:

  • 内层循环: 负责整个数据集的一次遍历,采用分批次方式(batch)。假设数据集样本数量为 1000,一个批次有 10 个样本,则遍历一次数据集的批次数量是 1000/10=100,即内层循环需要执行 100 次。
  • Bucle externo : define el número de veces que se recorrerá el conjunto de datos, epochsestablecido mediante parámetros.
# 外层循环: 定义遍历数据集的次数,通过参数 epochs 设置
for epoch in range(epochs):
    ...
    # 内层循环: 负责整个数据集的一次遍历,采用分批次方式(batch)。
    for iter_idx, mini_batch in enumerate(mini_batch):
        ...

Nota : batchEl valor de afectará el efecto de entrenamiento del modelo:

  • batchSi es demasiado grande, aumentará el consumo de memoria y el tiempo de cálculo, y el efecto de entrenamiento no mejorará significativamente (cada parámetro solo se mueve un pequeño paso en la dirección opuesta del gradiente, por lo que no es necesario que la dirección sea particularmente precisa). );
  • batchSi es demasiado pequeño, batchlos datos de muestra de cada uno no tendrán significación estadística y la dirección del gradiente calculada puede tener una gran desviación.

Dado que el conjunto de datos de entrenamiento del modelo de predicción del precio de la vivienda es pequeño, se batchestablece en 10. Cada bucle interno debe realizar los pasos que se muestran en la siguiente figura. El proceso de cálculo es exactamente el mismo que escribir el modelo en Python.

Insertar descripción de la imagen aquí

  • Preparación de datos : primero convierta un lote de datos al nparrayformato y luego al Tensorformato;
  • Cálculo directo : vierta un lote de datos de muestra en la red y calcule el resultado de salida;
  • Calcule la función de pérdida : tome el resultado del cálculo adelantado y el precio real de la vivienda como entrada y square_error_costcalcule el valor de la función de pérdida (Pérdida) a través de la API de la función de pérdida.
  • Propagación hacia atrás : realice la función de propagación hacia atrás del gradiente , es decir, calcule el gradiente de cada capa capa por capa de atrás hacia adelante y actualice los parámetros ( función) backwardde acuerdo con el algoritmo de optimización establecido .opt.step
epochs = 100  # 总样本需要训练的轮次
batch_size = 10  # 每个 mini_batch 的样本数量

# 外层循环: 定义遍历数据集的次数,通过参数 epochs 设置
for epoch in range(epochs + 1):
    # 在每个 epoch 开始之前,将训练数据进行 Shuffle
    np.random.shuffle(training_data)
    
    # 将训练数据按照batch_size进行拆分
    mini_batches = [training_data[k: k + batch_size] for k in range(0, len(training_data), batch_size)]
    
    epoch_loss = []  # 记录损失
    
    # 内层循环: 负责整个数据集的一次遍历,采用分批次方式(mini-batch)。
    for iter_idx, mini_batch in enumerate(mini_batches):
        x = np.array(mini_batch[:, :-1])  # 训练样本
        y = np.array(mini_batch[:, -1:])  # 目标值

        # ndarray -> tensor
        inputs = paddle.to_tensor(x)
        targets = paddle.to_tensor(y)
        
        # infer
        preds = model(inputs)
        
        # calc losses
        losses = F.square_error_cost(input=preds, label=targets)
        
        # mean loss
        avg_loss = paddle.mean(losses, axis=0)
        epoch_loss.append(avg_loss.numpy())
            
        # 反向传播,计算每层参数的梯度值
        avg_loss.backward()

        # 让优化器更新参数
        optimizer.step()
        
        # 为下一个 epoch 清空当前梯度
        optimizer.clear_grad()
        
    # 打印损失
    print(f"Epoch: {
      
      epoch}\tLoss: {
      
      np.average(epoch_loss):.4f}")
    epoch_loss.clear()  # 清空历史损失

resultado:

W0805 03:36:18.712543 116394 gpu_resources.cc:119] Please NOTE: device: 0, GPU Compute Capability: 7.5, Driver API Version: 10.2, Runtime API Version: 10.2
W0805 03:36:18.715507 116394 gpu_resources.cc:149] device: 0, cuDNN Version: 8.2.
Epoch: 0        Loss: 0.2431
Epoch: 1        Loss: 0.0846
Epoch: 2        Loss: 0.0774
...
Epoch: 99       Loss: 0.0133
Epoch: 100      Loss: 0.0134

Es lo mismo que PyTorch: cálculo directo, pérdida de cálculo y gradiente de propagación hacia atrás, cada operación solo requiere de 1 a 2 líneas de código para implementarse. Flying Paddle nos ha ayudado a implementar automáticamente el proceso de cálculo de gradiente inverso y actualización de parámetros, y ya no necesitamos escribir código uno por uno.

3.5 Guardar y probar el modelo

3.5.1 Guardar modelo

Utilice la API paddle.save para guardar los datos de los parámetros actuales del modelo model.state_dict()en un archivo para llamadas de programas para predicción o verificación del modelo.

# 保存模型
paddle.save(obj=model.state_dict(), path="LR_model.pdparams")
print(f"模型保存成功, 路径为: LR_model.pdparams")

说明:为什么要执行保存模型操作,而不是直接使用训练好的模型进行预测?

理论而言,直接使用模型实例即可完成预测,但是在实际应用中,训练模型和使用模型往往是不同的场景。模型训练通常使用大量的线下服务器(不对外向企业的客户/用户提供在线服务);模型预测则通常使用线上提供预测服务的服务器实现或者将已经完成的预测模型嵌入手机或其他终端设备中使用。因此本教程中“先保存模型,再加载模型”的讲解方式更贴合真实场景的使用方法。

3.5.2 测试模型

下面选择一条数据样本,测试下模型的预测效果。测试过程和在应用场景中使用模型的过程一致,主要可分成如下三个步骤:

  1. 配置模型预测的机器资源。
  2. 将训练好的模型参数 LR_model.pdparams 加载到模型实例中。由两个语句完成:
    1. 第一句是从文件中读取模型参数;
    2. 第二句是将参数内容加载到模型。
    • 加载完毕后,需要将模型的状态调整为 eval()(校验)。
    • 上文中提到,训练状态的模型需要同时支持前向计算和反向传导梯度,模型的实现较为臃肿,而校验和预测状态的模型只需要支持前向计算,模型的实现更加简单,性能更好。
  3. 将待预测的样本特征输入到模型中,打印输出的预测结果。

我们通过 load_one_example 函数实现从数据集中抽一条样本作为测试样本,具体实现代码如下所示。

def load_one_example(test_data):
    # 从已经加载的测试集中随机选择一条作为测试数据
    idx = np.random.randint(low=0, high=test_data.shape[0])
    one_data, label = test_data[idx, :-1], test_data[idx, -1:]
    
    # 修改该条数据的shape为 [1, 13]
    one_data = one_data.reshape([1, -1])
    
    return one_data, label


# 读取模型
model_dict = paddle.load("LR_model.pdparams")
model = Regression()
model.load_dict(state_dict=model_dict)
# 转换模型状态
model.eval()
    
# 读取单条测试数据
one_data, label = load_one_example(test_data=test_data)
one_data = paddle.to_tensor(one_data)
    
# 模型推理
pred = model(one_data)
    
# 对推理结果做反归一化处理
pred = pred * (max_values[-1] - min_values[-1]) + min_values[-1]
# 对真实标签做反归一化处理
label = label * (max_values[-1] - min_values[-1]) + min_values[-1]
    
print(F"推理结果为: {
      
      pred.numpy()}\t对应的真实值为: {
      
      label}")

结果:

推理结果为: [[16.670935]]       对应的真实值为: [13.6]

通过比较“模型预测值”和“真实房价”可见,模型的预测效果与真实房价接近。房价预测仅是一个最简单的模型,使用飞桨编写均可事半功倍。那么对于工业实践中更复杂的模型,使用飞桨节约的成本是不可估量的。同时飞桨针对很多应用场景和机器资源做了性能优化,在功能和性能上远强于自行编写的模型。

4. PaddlePaddle 与 PyTorch 语法区别

4.1 前向传播:一样

"""=============== PyTorch ==============="""
# 前向传播
loss = model(input_data, target)


"""=============== Paddle ==============="""
# 前向传播
loss = model(input_data, target)

4.2 反向传播:一样

"""=============== PyTorch ==============="""
loss.backward()


"""=============== Paddle ==============="""
# 反向传播
loss.backward()

4.3 更新参数:一样

"""=============== PyTorch ==============="""
# 定义优化器
optimizer = torch.optim.SGD(model.parameters(), lr=learning_rate)

# 更新参数
optimizer.step()


"""=============== Paddle ==============="""
# 定义优化器
optimizer = paddle.optimizer.SGD(learning_rate=learning_rate, parameters=model.parameters())

# 更新参数
optimizer.step()

4.4 清空梯度:不一样

"""=============== PyTorch ==============="""
# 清空梯度
optimizer.zero_grad()


"""=============== Paddle ==============="""
# 清空梯度
optimizer.clear_gradients()

4.5 Archivo de parámetros del modelo y sufijo del modelo: lo mismo

"""=============== PyTorch ==============="""
# 保存模型参数
torch.save(model.state_dict(), "model.pt")  # 后缀一般是 .pt 或 .pth


"""=============== Paddle ==============="""
# 保存模型参数
paddle.save(model.state_dict(), "model.pdparams")  # 后缀一般是 .pdparams

4.6 Modelo de lectura: diferente

"""=============== PyTorch ==============="""
# 加载模型参数
model_state_dict = torch.load("model.pt")
model.load_state_dict(model_state_dict)


"""=============== Paddle ==============="""
# 加载模型参数
model_state_dict = paddle.load("model.pdparams")
model.set_state_dict(model_state_dict)

fuente de conocimiento

  1. https://www.paddlepaddle.org.cn/tutorials/projectdetail/3520300
  2. https://www.paddlepaddle.org.cn/tutorials/projectdetail/5836960
  3. https://www.paddlepaddle.org.cn/tutorials/projectdetail/4309126

Supongo que te gusta

Origin blog.csdn.net/weixin_44878336/article/details/132096948
Recomendado
Clasificación