Subido por Dante Larroy

Hands-On Machine Learning with Scikit-Learn, Keras, and TensorFlow

Anuncio
Machine Translated by Google
Machine Translated by Google
Aprendizaje automático práctico con
Scikit­Learn, Keras y
Flujo tensorial
SEGUNDA EDICIÓN
Conceptos, herramientas y técnicas para construir inteligencia
Sistemas
Aurelien Géron
Machine Translated by Google
Aprendizaje automático práctico con Scikit­Learn, Keras y
Flujo tensorial
por Aurélien Géron
Copyright © 2019 Aurélien Géron. Todos los derechos reservados.
Impreso en Canadá.
Publicado por O'Reilly Media, Inc., 1005 Gravenstein Highway North, Sebastopol, CA
95472.
Los libros de O'Reilly se pueden comprar para fines educativos, comerciales o
promocionales. También hay ediciones en línea disponibles para la mayoría de los
títulos (http://oreilly.com). Para obtener más información, comuníquese
con nuestro departamento de ventas corporativo/institucional: 800­998­9938
o corporate@oreilly.com.
Editores: Rachel Roumeliotis y Nicole Tache
Editor de producción: Kristen Brown
Correctora de estilo: Amanda Kersey
Correctora de pruebas: Rachel Head
Indizador: Judith McConville
Diseñador de interiores: David Futato
Diseñador de la portada: Karen Montgomery
Ilustradora: Rebecca Demarest
Septiembre 2019: Segunda edición
Historial de revisiones de la segunda edición
05­09­2019: Primer lanzamiento
Machine Translated by Google
Consulte http://oreilly.com/catalog/errata.csp?isbn=9781492032649 para detalles del
lanzamiento.
El logotipo de O'Reilly es una marca registrada de O'Reilly Media, Inc.
Aprendizaje automático práctico con Scikit­Learn, Keras y TensorFlow, la imagen de portada
y la imagen comercial relacionada son marcas comerciales de O'Reilly Media, Inc.
Las opiniones expresadas en este trabajo son las del autor y no representan las
opiniones del editor. Si bien el editor y el autor han hecho todos los esfuerzos posibles para
garantizar que la información y las instrucciones contenidas en este trabajo sean
precisas, el editor y el autor declinan toda responsabilidad por errores u omisiones, incluida,
sin limitación, la responsabilidad por daños resultantes del uso o la confianza depositada
en este trabajo. El uso de la información y las instrucciones contenidas en este trabajo se
realiza bajo su propio riesgo. Si algún ejemplo de código u otra tecnología que este trabajo
contiene o describe está sujeto a licencias de código abierto o a los derechos de propiedad
intelectual de terceros, es su responsabilidad asegurarse de que su uso de los mismos
cumpla con dichas licencias y/o derechos.
978­1­492­03264­9
[ES]
Machine Translated by Google
Prefacio
El tsunami del aprendizaje automático En 2006, Geoffrey Hinton
1
et al. publicaron un artículo En este artículo se muestra cómo entrenar
una red neuronal
profunda capaz de reconocer dígitos escritos a mano con una precisión de última generación
(>98%). A esta técnica la han denominado “aprendizaje profundo”. Una red
neuronal profunda es un modelo (muy) simplificado de nuestra corteza cerebral,
compuesto por una pila de capas de neuronas artificiales.
En aquel momento, entrenar una red neuronal profunda se consideraba imposible y la
2
mayoría de los investigadores habían abandonado la idea a finales de los años 1990. Este
artículo reavivó el interés de la comunidad científica y, en poco tiempo, muchos artículos
nuevos demostraron que el aprendizaje profundo no solo era posible, sino que era capaz
de alcanzar logros alucinantes que ninguna otra técnica de aprendizaje automático (ML)
podía aspirar a igualar (con la ayuda de una enorme potencia informática y grandes
cantidades de datos). Este entusiasmo pronto se extendió a muchas otras áreas del
aprendizaje automático.
Aproximadamente una década después, el aprendizaje automático ha conquistado la industria:
está en el corazón de gran parte de la magia de los productos de alta tecnología actuales,
clasificando los resultados de búsqueda web, potenciando el reconocimiento de voz
de su teléfono inteligente, recomendando videos y derrotando al campeón mundial en el juego Go.
Antes de que te des cuenta, estará conduciendo tu coche.
Aprendizaje automático en sus proyectos
Entonces, ¡naturalmente estás entusiasmado con el aprendizaje automático y te encantaría
unirte a la fiesta!
¿Quizás te gustaría darle a tu robot casero un cerebro propio?
¿Hacer que reconozca caras? ¿O aprender a caminar?
Machine Translated by Google
O tal vez su empresa tenga toneladas de datos (registros de usuarios, datos financieros, datos
de producción, datos de sensores de máquinas, estadísticas de líneas directas, informes de RR. HH.,
etc.) y lo más probable es que pueda descubrir algunas joyas ocultas si supiera dónde buscar. Con
el aprendizaje automático, podría lograr lo siguiente y más:
Segmentar clientes y encontrar la mejor estrategia de marketing para cada uno
grupo.
Recomendar productos a cada cliente basándose en lo que compraron clientes
similares.
Detectar qué transacciones son susceptibles de ser fraudulentas.
Pronosticar los ingresos del próximo año.
Sea cual sea el motivo, has decidido aprender Machine Learning e implementarlo en tus proyectos.
¡Gran idea!
Objetivo y enfoque Este libro asume que no sabe
prácticamente nada sobre aprendizaje automático. Su objetivo es brindarle los conceptos,
las herramientas y la intuición que necesita para implementar programas capaces de aprender de los
datos.
Cubriremos una gran cantidad de técnicas, desde las más simples y comúnmente utilizadas (como la
regresión lineal) hasta algunas de las técnicas de Deep Learning que regularmente ganan competencias.
En lugar de implementar nuestras propias versiones de juguete de cada algoritmo, utilizaremos marcos
de Python listos para producción:
Scikit­Aprende es muy fácil de usar, pero implementa muchos algoritmos de aprendizaje
automático de manera eficiente, por lo que constituye un excelente punto de entrada para
aprender aprendizaje automático.
Flujo tensorial es una biblioteca más compleja para el cálculo numérico distribuido. Permite
entrenar y ejecutar cálculos muy grandes.
Machine Translated by Google
redes neuronales de manera eficiente al distribuir los cálculos entre cientos
de servidores con múltiples GPU (unidades de procesamiento gráfico).
TensorFlow (TF) fue creado en Google y es compatible con muchas de
sus aplicaciones de aprendizaje automático a gran escala.
Se hizo público en noviembre de 2015.
Keras es una API de aprendizaje profundo de alto nivel que simplifica
enormemente el entrenamiento y la ejecución de redes neuronales. Puede
ejecutarse sobre TensorFlow, Theano o Microsoft Cognitive Toolkit (antes
conocido como CNTK). TensorFlow viene con su propia
implementación de esta API, llamada tf.keras, que brinda soporte para
algunas funciones avanzadas de TensorFlow (por ejemplo, la capacidad de
cargar datos de manera eficiente).
El libro favorece un enfoque práctico, que permite desarrollar una comprensión intuitiva del
aprendizaje automático a través de ejemplos prácticos concretos y un poco de teoría. Si
bien puedes leer este libro sin tener que usar tu computadora portátil, te recomiendo que
experimentes con los ejemplos de código disponibles en línea como cuadernos Jupyter
en https://github.com/ageron/handson­ml2.
Prerrequisitos Este libro
asume que tienes alguna experiencia en programación en Python y que estás familiarizado
con las principales bibliotecas científicas de Python, en particular, NumPy, pandas, y
Matplotlib.
Además, si te importa lo que hay bajo el capó, también deberías tener un
conocimiento razonable de matemáticas de nivel universitario (cálculo, álgebra lineal,
probabilidades y estadística).
Si aún no conoces Python, http://learnpython.org/ es un excelente lugar para comenzar. El
tutorial oficial en Python.org También es bastante bueno.
Si nunca ha utilizado Jupyter, el Capítulo 2 lo guiará a través de la instalación y
los conceptos básicos: es una herramienta poderosa para tener en su caja de herramientas.
Machine Translated by Google
Si no está familiarizado con las bibliotecas científicas de Python, los cuadernos Jupyter que se
proporcionan incluyen algunos tutoriales. También hay un tutorial rápido de matemáticas para
álgebra lineal.
Hoja de ruta
Este libro está organizado en dos partes. La Parte I, Fundamentos del aprendizaje automático, cubre los
siguientes temas:
Qué es el Machine Learning, qué problemas intenta resolver y las principales categorías y
conceptos fundamentales de sus sistemas
Los pasos de un proyecto típico de aprendizaje automático
Aprendizaje mediante el ajuste de un modelo a los datos
Optimización de una función de costes
Manejo, limpieza y preparación de datos
Selección y diseño de características
Selección de un modelo y ajuste de hiperparámetros mediante
validación cruzada
Los desafíos del aprendizaje automático, en particular el subajuste y el sobreajuste (la
compensación entre sesgo y varianza)
Los algoritmos de aprendizaje más comunes: Lineal y Polinomial
Regresión, regresión logística, k vecinos más cercanos, soporte
Máquinas vectoriales, árboles de decisión, bosques aleatorios y métodos de conjunto
Reducir la dimensionalidad de los datos de entrenamiento para combatir la “maldición
de la dimensionalidad”
Otras técnicas de aprendizaje no supervisado, incluida la agrupación, la estimación de
densidad y la detección de anomalías.
Machine Translated by Google
La Parte II, Redes neuronales y aprendizaje profundo, cubre los siguientes temas:
Qué son las redes neuronales y para qué sirven
Construcción y entrenamiento de redes neuronales con TensorFlow y Keras
Las arquitecturas de redes neuronales más importantes: redes neuronales de
propagación hacia adelante para datos tabulares, redes convolucionales para
visión artificial, redes recurrentes y redes de memoria a corto y largo plazo
(LSTM) para procesamiento de secuencias, codificadores/decodificadores y
transformadores para procesamiento de lenguaje natural, autocodificadores
y redes generativas antagónicas (GAN) para aprendizaje generativo.
Técnicas para entrenar redes neuronales profundas
Cómo construir un agente (por ejemplo, un bot en un juego) que pueda aprender
buenas estrategias a través de prueba y error, utilizando aprendizaje de refuerzo
Cargar y preprocesar grandes cantidades de datos de manera eficiente
Entrenamiento e implementación de modelos TensorFlow a escala
La primera parte se basa principalmente en Scikit­Learn, mientras que la segunda parte
utiliza TensorFlow y Keras.
PRECAUCIÓN
No te apresures a lanzarte a aguas profundas: si bien el aprendizaje profundo es sin duda una de las áreas más
apasionantes del aprendizaje automático, primero debes dominar los fundamentos.
Además, la mayoría de los problemas se pueden resolver bastante bien utilizando técnicas más simples, como
los bosques aleatorios y los métodos de conjunto (que se analizan en la Parte I). El aprendizaje profundo es
más adecuado para problemas complejos, como el reconocimiento de imágenes, el reconocimiento de
voz o el procesamiento del lenguaje natural, siempre que se cuente con suficientes datos, capacidad de
procesamiento y paciencia.
Cambios en la segunda edición
Esta segunda edición tiene seis objetivos principales:
Machine Translated by Google
1. Cubrir temas adicionales de ML: más aprendizaje no supervisado
técnicas (incluyendo agrupamiento, detección de anomalías, estimación
de densidad y modelos de mezcla); más técnicas para entrenar redes
profundas (incluyendo redes autonormalizadas); técnicas adicionales de
visión por computadora (incluyendo Xception, SENet, detección de objetos
con YOLO y segmentación semántica usando R­CNN); manejo de secuencias
usando redes neuronales covolucionales (CNN, incluyendo WaveNet);
procesamiento de lenguaje natural usando redes neuronales recurrentes (RNN),
CNN y Transformers; y GAN.
2. Cubrir bibliotecas y API adicionales (Keras, la API de datos, TF­
Agentes para el aprendizaje por refuerzo) y entrenamiento e implementación
de modelos TF a escala mediante la API de estrategias de distribución, TF­
Serving y Google Cloud AI Platform. También se presenta brevemente TF
Transform, TFLite, TF Addons/Seq2Seq y TensorFlow.js.
3. Analice algunos de los últimos resultados importantes del aprendizaje profundo.
investigación.
4. Migre todos los capítulos de TensorFlow a TensorFlow 2 y utilice la
implementación de TensorFlow de la API de Keras (tf.keras) siempre
que sea posible.
5. Actualice los ejemplos de código para utilizar las últimas versiones de Scikit­
Learn, NumPy, pandas, Matplotlib y otras bibliotecas.
6. Aclarar algunas secciones y corregir algunos errores, gracias a los excelentes
comentarios de los lectores.
Se agregaron algunos capítulos, se reescribieron otros y se reordenaron algunos.
Ver https://homl.info/changes2 para más detalles sobre lo que cambió en la
segunda edición.
Otros recursos
Hay muchos recursos excelentes disponibles para aprender sobre aprendizaje automático.
Por ejemplo, el curso de ML de Andrew Ng en Coursera Es increíble, aunque
Machine Translated by Google
requiere una inversión de tiempo significativa (piense en meses).
También hay muchos sitios web interesantes sobre aprendizaje automático,
incluida, por supuesto, la excepcional Guía del usuario de Scikit­Learn. También te puede
interesar Dataquest, que ofrece tutoriales interactivos muy interesantes y blogs de ML
como los que aparecen en Quora. Por último, el sitio web de Deep Learning Tiene una
buena lista de recursos que puedes consultar para obtener más información.
Existen muchos otros libros introductorios sobre aprendizaje automático. En particular:
La ciencia de datos desde cero, de Joel Grus (O'Reilly) presenta los
fundamentos del Machine Learning e implementa algunos de los principales
algoritmos en Python puro (desde cero, como sugiere el nombre).
Machine Learning: An Algorithmic Perspective (Chapman & Hall) de
Stephen Marsland es una excelente introducción al aprendizaje automático, que
cubre una amplia gama de temas en profundidad con ejemplos de código
en Python (también desde cero, pero usando NumPy).
Python Machine Learning de Sebastian Raschka (Packt Publishing) también es
una excelente introducción al aprendizaje automático y aprovecha las
bibliotecas de código abierto de Python (Pylearn 2 y Theano).
El libro Deep Learning with Python (Manning) de François Chollet es un libro
muy práctico que cubre una amplia gama de temas de forma clara y concisa,
como cabría esperar del autor de la excelente biblioteca Keras. Favorece
los ejemplos de código por sobre la teoría matemática.
El libro de cien páginas sobre aprendizaje automático de Andriy Burkov es
muy breve y cubre una variedad impresionante de temas, presentándolos en
términos accesibles sin eludir las ecuaciones matemáticas.
Learning from Data (AMLBook) de Yaser S. Abu­Mostafa, Malik Magdon­
Ismail y Hsuan­Tien Lin es un libro bastante teórico.
Machine Translated by Google
enfoque del aprendizaje automático que proporciona conocimientos profundos, en particular
sobre el equilibrio entre sesgo y varianza (véase el Capítulo 4).
Artificial Intelligence: A Modern Approach, 3.ª edición (Pearson), de Stuart
Russell y Peter Norvig , es un libro excelente (y enorme) que abarca una increíble
cantidad de temas, incluido el aprendizaje automático. Ayuda a poner el aprendizaje
automático en perspectiva.
Por último, unirse a sitios web de competencia de ML como Kaggle.com Le permitirá practicar
sus habilidades en problemas del mundo real, con la ayuda y los conocimientos de algunos de
los mejores profesionales de ML que existen.
Convenciones utilizadas en este libro
En este libro se utilizan las siguientes convenciones tipográficas:
Itálico
Indica nuevos términos, URL, direcciones de correo electrónico, nombres de archivos y
extensiones de archivos.
Ancho constante
Se utiliza para listados de programas, así como dentro de párrafos para hacer
referencia a elementos del programa, como nombres de variables o funciones, bases de
datos, tipos de datos, variables de entorno, declaraciones y palabras clave.
Negrita de ancho constante
Muestra comandos u otro texto que debe escribirse literalmente.
usuario.
Cursiva de ancho constante
Muestra texto que debe reemplazarse con valores proporcionados por el usuario o
por valores determinados por el contexto.
Machine Translated by Google
CONSEJO
Este elemento significa un consejo o sugerencia.
NOTA
Este elemento significa una nota general.
ADVERTENCIA
Este elemento indica una advertencia o precaución.
Ejemplos de código Hay una
serie de cuadernos Jupyter llenos de material complementario, como ejemplos de código y
ejercicios, disponibles para descargar en https://github.com/ageron/handson­
ml2.
Algunos de los ejemplos de código del libro omiten secciones repetitivas o detalles
que son obvios o no están relacionados con el aprendizaje automático. Esto permite
centrarse en las partes importantes del código y ahorrar espacio para cubrir más
temas. Si desea los ejemplos de código completos, todos están disponibles en los
cuadernos de Jupyter.
Tenga en cuenta que cuando los ejemplos de código muestran algunos
resultados, estos se muestran con indicaciones de Python (>>> y ...), como en un
shell de Python, para distinguir claramente el código de los resultados. Por ejemplo,
este código define la función square(), luego calcula y muestra el cuadrado de 3:
>>> def cuadrado(x): ** 2
...
...
devolver x
>>> resultado = cuadrado(3)
Machine Translated by Google
>>> resultado
9
Cuando el código no muestra nada, no se utilizan indicaciones. Sin embargo, a veces
el resultado puede mostrarse como un comentario, como este:
def cuadrado(x): devuelve
x ** 2
resultado = cuadrado(3)
#
El resultado es
9
Usando ejemplos de código
Este libro está aquí para ayudarle a hacer su trabajo. En general, si se ofrece
código de ejemplo con este libro, puede usarlo en sus programas y
documentación. No necesita ponerse en contacto con nosotros para solicitar permiso a
menos que esté reproduciendo una parte importante del código. Por ejemplo,
escribir un programa que utilice varios fragmentos de código de este libro no requiere
permiso. Vender o distribuir un CD­ROM de ejemplos de los libros de O'Reilly sí
requiere permiso. Responder a una pregunta citando este libro y citando código de
ejemplo no requiere permiso. Incorporar una cantidad significativa de código de
ejemplo de este libro en la documentación de su producto sí requiere permiso.
Apreciamos, pero no exigimos, la atribución. Una atribución generalmente
incluye el título, el autor, el editor y el ISBN. Por ejemplo: “Aprendizaje automático
práctico con Scikit­Learn, Keras y TensorFlow, 2.ª edición, de Aurélien Géron (O'Reilly).
Copyright 2019 Aurélien Géron, 978­1­492­ 03264­9”. Si considera que el uso que hace
de los ejemplos de código no se considera legítimo o no se encuentra dentro del
permiso otorgado anteriormente, no dude en comunicarse
con nosotros a permissions@oreilly.com.
Aprendizaje en línea de O'Reilly
Machine Translated by Google
NOTA
Durante casi 40 años, O'Reilly Media Ha proporcionado capacitación, conocimiento y visión
tecnológica y empresarial para ayudar a las empresas a tener éxito.
Nuestra red única de expertos e innovadores comparte sus conocimientos y experiencia a través de libros,
artículos, conferencias y nuestra plataforma de aprendizaje en línea. La plataforma de aprendizaje en línea
de O'Reilly le brinda acceso a pedido a cursos de capacitación en vivo, rutas de aprendizaje en profundidad,
entornos de codificación interactivos y una vasta colección de textos y videos de O'Reilly y más de
200 editoriales. Para obtener más información, visite http://oreilly.com.
Cómo contactarnos
Por favor, dirija sus comentarios y preguntas sobre este libro al
editor:
O'Reilly Media, Inc.
1005 Gravenstein Highway Norte
Sebastopol, CA 95472
800­998­9938 (en Estados Unidos o Canadá)
707­829­0515 (internacional o local)
707­829­0104 (fax)
Contamos con una página web para este libro, donde enumeramos erratas, ejemplos y cualquier información
adicional. Puede acceder a esta página en https://homl.info/oreilly2.
Machine Translated by Google
Para comentar o hacer preguntas técnicas sobre este libro, envíe un correo electrónico
a bookquestions@oreilly.com.
Para obtener más información sobre nuestros libros, cursos, conferencias y noticias, visite
nuestro sitio web en http://www.oreilly.com.
Encuéntrenos en Facebook: http://facebook.com/oreilly
Síguenos en Twitter: http://twitter.com/oreillymedia
Míranos en YouTube: http://www.youtube.com/oreillymedia
Agradecimientos Nunca en mis sueños
más locos imaginé que la primera edición de este libro tendría una audiencia tan grande.
Recibí muchísimos mensajes de lectores, muchos de ellos haciendo preguntas, algunos
amablemente señalándome erratas y la mayoría enviándome palabras de aliento. No puedo
expresar lo agradecido que estoy con todos estos lectores por su tremendo apoyo. ¡Muchas
gracias a todos!
No dude en informar problemas en GitHub. Si encuentra errores en los ejemplos de
código (o simplemente para hacer preguntas) o para enviar erratas. Si encuentras errores
en el texto, algunos lectores también comentaron cómo este libro les ayudó a conseguir su
primer trabajo o cómo les ayudó a resolver un problema concreto en el que estaban
trabajando. Considero que este tipo de comentarios son increíblemente motivadores. Si
este libro te resulta útil, me encantaría que pudieras compartir tu historia conmigo, ya sea de
forma privada (por ejemplo, a través de LinkedIn). o públicamente (por ejemplo, en un
tweet o mediante una reseña de Amazon).
También estoy increíblemente agradecido a todas las personas increíbles que se tomaron el
tiempo de sus ocupadas vidas para revisar mi libro con tanto cuidado. En particular, me
gustaría agradecer a François Chollet por revisar todos los capítulos basados en Keras
y TensorFlow y brindarme comentarios excelentes y detallados. Dado que Keras es una de
las principales incorporaciones a esta segunda edición, que su autor haya revisado el libro fue
invaluable. Recomiendo encarecidamente el libro de François Deep Learning with
Python (Manning): Tiene la concisión, claridad y profundidad de la propia biblioteca de Keras.
Un agradecimiento especial también a Ankur Patel, quien revisó cada capítulo de esta
segunda edición y me brindó una excelente
Machine Translated by Google
Comentarios, en particular sobre el capítulo 9, que trata de técnicas de aprendizaje no
supervisado. Podría escribir un libro entero sobre el tema... ¡Ah, espera, lo hizo!
No deje de consultar Hands­On Unsupervised Learning Using Python: How to Build
Applied Machine Learning Solutions from Unlabeled Data (O'Reilly). También
quiero agradecer enormemente a Olzhas Akpambetov, que revisó todos los capítulos de
la segunda parte del libro, probó gran parte del código y ofreció muchas sugerencias
excelentes. Agradezco a Mark Daoust, Jon Krohn, Dominic Monn y Josh Patterson por
revisar la segunda parte de este libro tan exhaustivamente y ofrecer su experiencia. No
dejaron ninguna piedra sin remover y brindaron comentarios increíblemente útiles.
Mientras escribía esta segunda edición, tuve la suerte de recibir mucha ayuda de los
miembros del equipo de TensorFlow, en particular de Martin Wicke, quien respondió
incansablemente a docenas de mis preguntas y envió el resto a las personas adecuadas,
incluidos Karmel Allison, Paige Bailey, Eugene Brevdo, William Chargin, Daniel "Wolff"
Dobson, Nick Felt, Bruce Fontaine, Goldie Gadde, Sandeep Gupta, Priya Gupta,
Kevin Haas, Konstantinos Katsiapis, Viacheslav Kovalevskyi, Allen Lavoie, Clemens
Mewald, Dan Moldovan, Sean Morgan, Tom O'Malley, Alexandre Passos, André Susano
Pinto, Anthony Platanios, Oscar Ramirez, Anna Revinskaya, Saurabh Saxena, Ryan
Sepassi, Jiri Simsa, Xiaodan Song, Christina Sorokin, Dustin Tran, Todd Wang, Pete
Warden (quien también revisó la primera edición), Edd Wilder­James y Yuefeng Zhou.
Todos ellos fueron de gran ayuda.
¡Muchísimas gracias a todos ustedes y a todos los demás miembros del equipo de
TensorFlow, no sólo por su ayuda, sino también por crear una biblioteca tan fantástica!
Un agradecimiento especial a Irene Giannoumis y Robert Crowe del equipo TFX por revisar
los capítulos 13 y 19 en profundidad.
Muchas gracias también al fantástico personal de O'Reilly, en particular a Nicole
Taché, que me dio comentarios muy perspicaces y siempre estuvo alegre,
alentadora y servicial: no podría soñar con una mejor editora. Muchas gracias también a
Michele Cronin, que fue muy servicial (y paciente) al comienzo de esta segunda edición,
y a Kristen Brown, la editora de producción de la segunda edición, que la supervisó en
todos los pasos (también coordinó correcciones y actualizaciones para cada reimpresión
de la primera edición). Gracias también a Rachel
Machine Translated by Google
A Monaghan y Amanda Kersey por su minuciosa revisión (para la primera y la
segunda edición, respectivamente), y a Johnny O'Toole, que gestionó la relación con Amazon y
respondió a muchas de mis preguntas. Gracias a Marie Beaugureau, Ben Lorica, Mike
Loukides y Laurel Ruma por creer en este proyecto y ayudarme a definir su alcance.
Gracias a Matt Hacker y a todo el equipo de Atlas por responder todas mis preguntas
técnicas sobre formato, AsciiDoc y LaTeX, y gracias a Nick Adams, Rebecca Demarest,
Rachel Head, Judith McConville, Helen Monroe, Karen Montgomery, Rachel
Roumeliotis y todos los demás en O'Reilly que contribuyeron a este libro.
También me gustaría agradecer a mis antiguos colegas de Google, en particular al equipo de
clasificación de videos de YouTube, por enseñarme tanto sobre aprendizaje automático.
Sin ellos, nunca podría haber comenzado la primera edición. Un agradecimiento especial
a mis gurús personales de aprendizaje automático: Clément Courbet, Julien Dubois, Mathias
Kende, Daniel Kitachewsky, James Pack, Alexander Pak, Anosh Raj, Vitor Sessak, Wiktor
Tomczak, Ingrid von Glehn y Rich Washington. Y gracias a todos los demás con los que
trabajé en YouTube y en los increíbles equipos de investigación de Google en Mountain View.
Muchas gracias también a Martin Andrews, Sam Witteveen y Jason Zaman por darme la
bienvenida a su grupo de expertos en desarrollo de Google en Singapur, con el amable apoyo
de Soonson Kwon, y por todas las excelentes discusiones que tuvimos sobre aprendizaje
profundo y TensorFlow. Cualquiera que esté interesado en el aprendizaje profundo en
Singapur definitivamente debería unirse a su reunión de aprendizaje profundo en Singapur.
¡Jason merece un agradecimiento especial por compartir parte de su experiencia en TFLite para
el Capítulo 19!
Nunca olvidaré a las amables personas que revisaron la primera edición de este libro, entre
ellas David Andrzejewski, Lukas Biewald, Justin Francis, Vincent Guilbeau, Eddy Hung,
Karim Matrah, Grégoire Mesnil, Salim Sémaoune, Iain Smears, Michel Tessier, Ingrid von
Glehn, Pete Warden y, por supuesto, a mi querido hermano Sylvain. Un agradecimiento
especial a Haesun Park, quien me brindó muchos comentarios excelentes y detectó varios
errores mientras escribía la traducción al coreano de la primera edición de este libro. También
tradujo los cuadernos Jupyter al coreano, por no mencionar
Machine Translated by Google
Documentación de TensorFlow. No hablo coreano, pero a juzgar por la calidad de sus
comentarios, ¡todas sus traducciones deben ser realmente excelentes! Haesun también
contribuyó amablemente con algunas de las soluciones a los ejercicios de esta
segunda edición.
Por último, pero no por ello menos importante, estoy infinitamente
agradecido a mi adorada esposa, Emmanuelle, y a nuestros tres maravillosos hijos,
Alexandre, Rémi y Gabrielle, por animarme a trabajar duro en este libro. También les estoy
agradecido por su insaciable curiosidad: explicarles a mi esposa y a mis hijos algunos de
los conceptos más difíciles de este libro me ayudó a aclarar mis ideas y mejoró
directamente muchas partes del mismo. ¡Y siguen trayéndome galletas y café! ¿Qué
más se puede soñar?
1 Geoffrey E. Hinton et al., “Un algoritmo de aprendizaje rápido para redes de creencias profundas”, Neural
Computación 18 (2006): 1527–1554.
2 A pesar de que las redes neuronales convolucionales profundas de Yann LeCun habían funcionado bien para
el reconocimiento de imágenes desde la década de 1990, no eran de propósito tan general.
Machine Translated by Google
Parte I. Fundamentos del aprendizaje
automático
Machine Translated by Google
Capítulo 1. El panorama
del aprendizaje automático
Cuando la mayoría de la gente oye “Machine Learning”, se imagina un robot: un
mayordomo confiable o un Terminator mortal, según a quién le preguntes. Pero el Machine
Learning no es solo una fantasía futurista; ya está aquí. De hecho, ha existido durante décadas
en algunas aplicaciones especializadas, como el reconocimiento óptico de caracteres (OCR).
Pero la primera aplicación de ML que realmente se volvió popular, mejorando las vidas de
cientos de millones de personas, se apoderó del mundo en la década de 1990: el filtro de spam.
No es exactamente un Skynet consciente de sí mismo, pero técnicamente califica como
Machine Learning (de hecho, ha aprendido tan bien que rara vez es necesario marcar un
correo electrónico como spam). Fue seguido por cientos de aplicaciones de ML que ahora
impulsan silenciosamente cientos de productos y funciones que usas regularmente, desde
mejores recomendaciones hasta búsqueda por voz.
¿Dónde empieza y dónde termina el aprendizaje automático? ¿Qué significa exactamente que
una máquina aprenda algo? Si descargo una copia de Wikipedia, ¿mi computadora realmente
ha aprendido algo? ¿De repente es más inteligente? En este capítulo comenzaremos
aclarando qué es el aprendizaje automático y por qué es posible que desees usarlo.
Luego, antes de comenzar a explorar el continente del aprendizaje automático, echaremos
un vistazo al mapa y aprenderemos sobre las principales regiones y los puntos de referencia más
destacados: aprendizaje supervisado versus no supervisado, aprendizaje en línea versus
aprendizaje por lotes, aprendizaje basado en instancias versus aprendizaje basado en modelos.
Luego, veremos el flujo de trabajo de un proyecto de aprendizaje automático típico, analizaremos
los principales desafíos que puede enfrentar y cubriremos cómo evaluar y ajustar un
sistema de aprendizaje automático.
Este capítulo presenta muchos conceptos fundamentales (y jerga) que todo científico de datos
debería saber de memoria. Será una descripción general de alto nivel (es el único capítulo sin
mucho código), bastante simple, pero debería
Machine Translated by Google
Asegúrate de que todo te quede claro antes de continuar con el resto del libro. ¡Así que
tómate un café y comencemos!
CONSEJO
Si ya conoce todos los conceptos básicos del aprendizaje automático, puede pasar
directamente al Capítulo 2. Si no está seguro, intente responder todas las preguntas que
aparecen al final del capítulo antes de continuar.
¿Qué es el aprendizaje automático?
El aprendizaje automático es la ciencia (y el arte) de programar computadoras para que puedan
aprender de los datos.
He aquí una definición un poco más general:
[El aprendizaje automático es el] campo de estudio que brinda a las computadoras la capacidad
de aprender sin ser programadas explícitamente.
—Arthur Samuel, 1959
Y uno más orientado a la ingeniería:
Se dice que un programa de computadora aprende de la experiencia E con respecto a
alguna tarea T y alguna medida de rendimiento P, si su rendimiento en T, medido por P,
mejora con la experiencia E.
—Tom Mitchell, 1997
Su filtro de spam es un programa de aprendizaje automático que, dados ejemplos de correos
electrónicos spam (por ejemplo, marcados por los usuarios) y ejemplos de correos electrónicos
normales (no spam, también llamados "ham"), puede aprender a marcar el spam. Los ejemplos
que el sistema utiliza para aprender se denominan conjunto de entrenamiento. Cada ejemplo
de entrenamiento se denomina instancia de entrenamiento (o muestra). En este caso, la
tarea T es marcar el spam para los correos electrónicos nuevos, la experiencia E son
los datos de entrenamiento y se debe definir la medida de rendimiento P ; por ejemplo, puede
utilizar la proporción de correos electrónicos clasificados correctamente. Esta medida de
rendimiento en particular se denomina precisión y se utiliza a menudo en tareas de clasificación.
Machine Translated by Google
Si simplemente descargas una copia de Wikipedia, tu computadora tendrá muchos más
datos, pero no será mejor de repente en ninguna tarea. Por lo tanto, descargar una copia
de Wikipedia no es aprendizaje automático.
¿Por qué utilizar el aprendizaje automático?
Considere cómo escribiría un filtro de spam utilizando técnicas de
programación tradicionales (Figura 1­1):
1. Primero, debes considerar cómo se ve el spam en general.
Es posible que notes que algunas palabras o frases (como “4U”, “tarjeta de
crédito”, “gratis” y “increíble”) suelen aparecer con frecuencia en la línea de
asunto. Tal vez también notes algunos otros patrones en el nombre del
remitente, el cuerpo del correo electrónico y otras partes del mismo.
2. Escribirías un algoritmo de detección para cada uno de los patrones.
que usted notó y su programa marcaría los correos electrónicos como spam si
se detectara una cantidad de estos patrones.
3. Probaría su programa y repetiría los pasos 1 y 2 hasta que estuviera
Suficientemente bueno para lanzarlo.
Machine Translated by Google
Figura 1­1. El enfoque tradicional
Dado que el problema es difícil, su programa probablemente se convertirá en una larga lista de reglas
complejas, bastante difíciles de mantener.
Por el contrario, un filtro de spam basado en técnicas de aprendizaje automático aprende
automáticamente qué palabras y frases son buenos predictores de spam al detectar patrones de
palabras inusualmente frecuentes en los ejemplos de spam en comparación con los ejemplos de
radioaficionados (Figura 1­2). El programa es mucho más corto, más fácil de mantener y, muy
probablemente, más preciso.
¿Qué pasa si los spammers se dan cuenta de que todos sus mensajes de correo electrónico
que contienen “4U” están bloqueados? Es posible que empiecen a escribir “Para ti” en su lugar.
Un filtro de spam que utilice técnicas de programación tradicionales debería actualizarse para marcar
los mensajes de correo electrónico “Para ti”. Si los spammers siguen eludiendo su filtro de spam,
tendrá que seguir escribiendo nuevas reglas para siempre.
Por el contrario, un filtro de spam basado en técnicas de aprendizaje automático detecta
automáticamente que “Para ti” se ha vuelto inusualmente frecuente en el spam marcado por los usuarios
y comienza a marcarlos sin su intervención (Figura 1­3).
Machine Translated by Google
Figura 1­2. El enfoque del aprendizaje automático
Figura 1­3. Adaptación automática al cambio
Otra área en la que el aprendizaje automático destaca es en los problemas que son
demasiado complejos para los enfoques tradicionales o que no tienen un algoritmo conocido.
Por ejemplo, considere el reconocimiento de voz. Digamos que desea comenzar de
manera sencilla y escribir un programa capaz de distinguir las palabras "uno" y "dos".
Es posible que notes que la palabra “dos” comienza con un sonido agudo (“T”), por lo que
podrías codificar un algoritmo que mida el sonido agudo.
Machine Translated by Google
intensidad y utilizarla para distinguir los unos de los dos, pero obviamente esta técnica no
se puede aplicar a miles de palabras habladas por millones de personas muy diferentes en
entornos ruidosos y en docenas de idiomas. La mejor solución (al menos hoy) es escribir un
algoritmo que aprenda por sí solo, dadas muchas grabaciones de ejemplo para cada palabra.
Por último, el aprendizaje automático puede ayudar a los humanos a aprender (Figura
1­4). Los algoritmos de aprendizaje automático pueden inspeccionarse para ver qué han
aprendido (aunque para algunos algoritmos esto puede ser complicado). Por ejemplo, una
vez que un filtro de spam se ha entrenado con suficiente spam, se puede inspeccionar fácilmente
para revelar la lista de palabras y combinaciones de palabras que cree que son los mejores
predictores de spam. A veces, esto revelará correlaciones insospechadas o nuevas tendencias
y, por lo tanto, conducirá a una mejor comprensión del problema. La aplicación de técnicas
de aprendizaje automático para profundizar en grandes cantidades de datos puede ayudar a
descubrir patrones que no eran evidentes de inmediato. Esto se llama minería de datos.
Figura 1­4. El aprendizaje automático puede ayudar a los humanos a aprender
En resumen, el aprendizaje automático es excelente para:
Problemas para los cuales las soluciones existentes requieren muchos ajustes o
largas listas de reglas: un algoritmo de aprendizaje automático a menudo puede
Machine Translated by Google
Simplificar el código y funcionar mejor que el enfoque tradicional.
Problemas complejos para los que el uso de un enfoque tradicional no produce una buena
solución: las mejores técnicas de aprendizaje automático tal vez puedan encontrar una
solución.
Entornos fluctuantes: un sistema de aprendizaje automático puede adaptarse
a nuevos datos.
Obtener información sobre problemas complejos y grandes cantidades de
datos.
Ejemplos de aplicaciones
Veamos algunos ejemplos concretos de tareas de aprendizaje automático, junto con las técnicas que
pueden abordarlas:
Analizar imágenes de productos en una línea de producción para clasificarlos automáticamente. Esto
es una clasificación
de imágenes, que normalmente se realiza mediante redes neuronales convolucionales (CNN; consulte
el Capítulo 14).
Detección de tumores en escáneres cerebrales
Se trata de una segmentación semántica, en la que se clasifica cada píxel de la imagen (ya
que queremos determinar la ubicación exacta y la forma de los tumores), normalmente también
utilizando CNN.
Clasificación automática de artículos de noticias
Se trata del procesamiento del lenguaje natural (PLN), y más específicamente de la clasificación de
texto, que puede abordarse mediante redes neuronales recurrentes (RNN), CNN o Transformers
(véase el Capítulo 16).
Marcar automáticamente los comentarios ofensivos en los foros de discusión
Esta también es una clasificación de texto, utilizando las mismas herramientas de PNL.
Resumiendo documentos largos automáticamente
Machine Translated by Google
Esta es una rama de la PNL llamada resumen de texto, que nuevamente utiliza las mismas
herramientas.
Creando un chatbot o un asistente personal
Esto involucra muchos componentes de PNL, incluidos módulos de comprensión del lenguaje
natural (NLU) y de respuesta a preguntas.
Pronosticar los ingresos de su empresa el próximo año, basándose en muchas métricas de
rendimiento
Esta es una tarea de regresión (es decir, predicción de valores) que puede abordarse utilizando
cualquier modelo de regresión, como un modelo de regresión lineal o de regresión polinómica (consulte el
Capítulo 4), una regresión SVM (consulte el Capítulo 5), un bosque aleatorio de regresión (consulte el
Capítulo 7) o una red neuronal artificial (consulte el Capítulo 10). Si desea tener en cuenta
secuencias de métricas de rendimiento anteriores, puede utilizar RNN, CNN o Transformers (consulte
los Capítulos 15 y 16).
Cómo hacer que tu aplicación reaccione a los comandos de voz
Se trata de reconocimiento de voz, que requiere el procesamiento de muestras de audio: dado que son
secuencias largas y complejas, normalmente se procesan utilizando RNN, CNN o
Transformers (consulte los Capítulos 15 y 16).
Detección de fraudes con tarjetas de crédito
Esta es la detección de anomalías (ver Capítulo 9).
Segmentar a los clientes en función de sus compras para poder diseñar una estrategia de marketing
diferente para cada segmento
Esto es agrupamiento (ver Capítulo 9).
Representar un conjunto de datos complejo y de alta dimensión en un diagrama claro y esclarecedor
Se trata de una visualización de datos que a menudo implica técnicas de reducción de dimensionalidad
(véase el Capítulo 8).
Machine Translated by Google
Recomendar un producto que pueda interesar a un cliente, basándose en compras anteriores
Se trata de un sistema de recomendación. Una de las estrategias consiste en introducir las
compras anteriores (y otra información sobre el cliente) en una red neuronal artificial (véase el
capítulo 10) y hacer que ésta muestre la próxima compra más probable.
Esta red neuronal normalmente se entrenaría con secuencias de compras pasadas de
todos los clientes.
Construyendo un bot inteligente para un juego
Esto se suele abordar mediante el aprendizaje por refuerzo (RL, ver Capítulo 18),
que es una rama del aprendizaje automático que entrena a los agentes (como los bots) para que
elijan las acciones que maximizarán sus recompensas a lo largo del tiempo (por ejemplo, un bot
puede obtener una recompensa cada vez que el jugador pierde algunos puntos de vida), dentro de
un entorno determinado (como el juego). El famoso programa AlphaGo que venció al campeón
mundial en el juego de Go se creó utilizando RL.
Esta lista podría continuar indefinidamente, pero esperamos que le brinde una idea de la
increíble amplitud y complejidad de las tareas que el aprendizaje automático puede abordar y los tipos
de técnicas que usaría para cada tarea.
Tipos de sistemas de aprendizaje automático
Hay tantos tipos diferentes de sistemas de aprendizaje automático que resulta útil clasificarlos en
categorías amplias, según los siguientes criterios:
Ya sea que estén o no entrenados con supervisión humana (supervisados, no
supervisados, semisupervisados y de refuerzo).
Aprendiendo)
Si pueden o no aprender de forma incremental sobre la marcha (aprendizaje en línea
versus aprendizaje por lotes)
Ya sea que funcionen simplemente comparando nuevos puntos de datos con
puntos de datos conocidos o, en cambio, detectando patrones en el entrenamiento.
Machine Translated by Google
datos y construcción de un modelo predictivo, de forma muy similar a como lo hacen los científicos
(aprendizaje basado en instancias versus aprendizaje basado en modelos)
Estos criterios no son excluyentes; puedes combinarlos de la forma que desees. Por ejemplo,
un filtro antispam de última generación puede aprender sobre la marcha utilizando un modelo de
red neuronal profunda entrenado con ejemplos de spam y correo basura; esto lo convierte
en un sistema de aprendizaje supervisado, basado en modelos y en línea.
Veamos cada uno de estos criterios un poco más de cerca.
Aprendizaje supervisado/no supervisado
Los sistemas de aprendizaje automático se pueden clasificar según la cantidad y el tipo de
supervisión que reciben durante el entrenamiento. Existen cuatro categorías
principales: aprendizaje supervisado, aprendizaje no supervisado, aprendizaje semisupervisado
y aprendizaje por refuerzo.
Aprendizaje supervisado En el
aprendizaje supervisado, el conjunto de entrenamiento que usted alimenta al algoritmo incluye
las soluciones deseadas, llamadas etiquetas (Figura 1­5).
Figura 1­5. Un conjunto de entrenamiento etiquetado para la clasificación de spam (un ejemplo de aprendizaje supervisado)
Una tarea típica de aprendizaje supervisado es la clasificación. El filtro de spam es un buen
ejemplo de ello: se lo entrena con muchos mensajes de correo electrónico de ejemplo junto
con su clase (spam o ham) y debe aprender a clasificar los mensajes de correo electrónico nuevos.
Otra tarea típica es predecir un valor numérico objetivo , como el precio de un automóvil, dado
un conjunto de características (kilometraje, antigüedad, marca, etc.) llamadas
Machine Translated by Google
1
Predictores. Este tipo de tarea se denomina regresión (Figura 1­6). Para entrenar el sistema, es
necesario darle muchos ejemplos de automóviles, incluidos sus predictores y sus etiquetas (es
decir, sus precios).
NOTA
En Machine Learning, un atributo es un tipo de datos (por ejemplo, “kilometraje”), mientras que una
característica tiene varios significados, según el contexto, pero generalmente significa un atributo más
su valor (por ejemplo, “kilometraje = 15 000”). Muchas personas usan las palabras atributo y
característica indistintamente.
Tenga en cuenta que algunos algoritmos de regresión también se pueden utilizar para la
clasificación y viceversa. Por ejemplo, la regresión logística se utiliza habitualmente para la
clasificación, ya que puede generar un valor que corresponde a la probabilidad de
pertenecer a una clase determinada (por ejemplo, 20 % de probabilidad de ser correo no deseado).
Figura 1­6. Problema de regresión: predecir un valor, dada una característica de entrada (normalmente hay
múltiples características de entrada y, a veces, múltiples valores de salida)
A continuación se presentan algunos de los algoritmos de aprendizaje supervisado más
importantes (tratados en este libro):
k­Vecinos más cercanos
Machine Translated by Google
Regresión lineal
Regresión logística
Máquinas de vectores de soporte (SVM)
Árboles de decisión y bosques aleatorios
Redes neuronales
2
Aprendizaje no supervisado
En el aprendizaje no supervisado, como se puede suponer, los datos de entrenamiento
no están etiquetados (Figura 1­7). El sistema intenta aprender sin un profesor.
Figura 1­7. Un conjunto de entrenamiento sin etiquetar para el aprendizaje no supervisado
A continuación se presentan algunos de los algoritmos de aprendizaje no supervisado más
importantes (la mayoría de ellos se tratan en los capítulos 8 y 9):
Agrupamiento
K­medias
Escaneo de base de datos
Análisis de conglomerados jerárquico (HCA)
Detección de anomalías y detección de novedades
Machine Translated by Google
SVM de una sola clase
Bosque de aislamiento
Visualización y reducción de dimensionalidad
Análisis de componentes principales (PCA)
PCA del núcleo
Incrustación lineal local (LLE)
Integración de vecinos estocásticos distribuidos en t (t­SNE)
Aprendizaje de reglas de asociación
A priori
Brillo
Por ejemplo, supongamos que tiene una gran cantidad de datos sobre los visitantes de
su blog. Puede que desee ejecutar un algoritmo de agrupamiento para intentar detectar
grupos de visitantes similares (Figura 1­8). En ningún momento le dice al algoritmo a qué
grupo pertenece un visitante: encuentra esas conexiones sin su ayuda. Por ejemplo,
puede notar que el 40% de sus visitantes son hombres que aman los cómics y
generalmente leen su blog por la noche, mientras que el 20% son jóvenes amantes de la
ciencia ficción que lo visitan durante los fines de semana. Si utiliza un algoritmo
de agrupamiento jerárquico , también puede subdividir cada grupo en grupos más pequeños.
Esto puede ayudarlo a orientar sus publicaciones para cada grupo.
Machine Translated by Google
Figura 1­8. Agrupamiento
Los algoritmos de visualización también son buenos ejemplos de algoritmos de aprendizaje
no supervisado: se les proporciona una gran cantidad de datos complejos y sin etiquetas,
y ellos generan una representación 2D o 3D de los datos que se puede representar
gráficamente fácilmente (Figura 1­9). Estos algoritmos intentan preservar la mayor
cantidad posible de estructura (por ejemplo, intentan evitar que los grupos
separados en el espacio de entrada se superpongan en la visualización) para que se pueda
comprender cómo se organizan los datos y tal vez identificar patrones inesperados.
Machine Translated by Google
Figura 1­9. Ejemplo de una visualización t­SNE que resalta los grupos semánticos
3
Una tarea relacionada es la reducción de dimensionalidad, cuyo objetivo es simplificar los
datos sin perder demasiada información. Una forma de hacerlo es fusionar varias
características correlacionadas en una sola. Por ejemplo, el kilometraje de un automóvil
puede estar fuertemente correlacionado con su antigüedad, por lo que el algoritmo de
reducción de dimensionalidad los fusionará en una sola característica que represente el
desgaste del automóvil. Esto se denomina extracción de características.
CONSEJO
A menudo, es una buena idea intentar reducir la dimensión de los datos de entrenamiento mediante
un algoritmo de reducción de dimensionalidad antes de introducirlos en otro algoritmo de aprendizaje
automático (como un algoritmo de aprendizaje supervisado). Se ejecutará mucho más rápido, los datos
ocuparán menos espacio en el disco y la memoria y, en algunos casos, también pueden funcionar
mejor.
Otra tarea importante no supervisada es la detección de anomalías; por ejemplo,
detectar transacciones inusuales con tarjetas de crédito para prevenir fraudes.
Machine Translated by Google
La detección de defectos de fabricación o la eliminación automática de valores atípicos de un
conjunto de datos antes de alimentarlo a otro algoritmo de aprendizaje. Al sistema se
le muestran principalmente instancias normales durante el entrenamiento, por lo que
aprende a reconocerlas; luego, cuando ve una nueva instancia, puede decir si parece una
normal o si es probable que sea una anomalía (consulte la Figura 1­10). Una tarea muy
similar es la detección de novedades: tiene como objetivo detectar nuevas instancias que
se vean diferentes de todas las instancias en el conjunto de entrenamiento. Esto requiere
tener un conjunto de entrenamiento muy "limpio", desprovisto de cualquier instancia
que le gustaría que el algoritmo detecte. Por ejemplo, si tiene miles de imágenes de
perros y el 1% de estas imágenes representan chihuahuas, entonces un algoritmo
de detección de novedades no debería tratar las nuevas imágenes de chihuahuas
como novedades. Por otro lado, los algoritmos de detección de anomalías pueden
considerar a estos perros como tan raros y tan diferentes de otros perros que
probablemente los clasificarían como anomalías (sin ofender a los chihuahuas).
Figura 1­10. Detección de anomalías
Por último, otra tarea común no supervisada es el aprendizaje de reglas de asociación, en el
que el objetivo es analizar grandes cantidades de datos y descubrir relaciones interesantes
entre atributos. Por ejemplo, supongamos que usted es dueño de un
supermercado. La ejecución de una regla de asociación en sus registros de ventas puede
revelar que las personas que compran salsa de barbacoa y patatas fritas también tienden a
comprar bistec. Por lo tanto, es posible que desee colocar estos artículos cerca unos de otros.
Machine Translated by Google
Aprendizaje semisupervisado Dado
que etiquetar datos suele ser una tarea costosa y que requiere mucho tiempo, a menudo
tendrá muchas instancias sin etiquetar y pocas instancias etiquetadas. Algunos
algoritmos pueden trabajar con datos que están parcialmente etiquetados. Esto se
denomina aprendizaje semisupervisado (Figura 1­11).
Figura 1­11. Aprendizaje semisupervisado con dos clases (triángulos y cuadrados): los ejemplos sin
etiquetar (círculos) ayudan a clasificar una nueva instancia (la cruz) en la clase de triángulos en lugar de la
clase de cuadrados, aunque está más cerca de los cuadrados etiquetados
Algunos servicios de alojamiento de fotografías, como Google Photos, son buenos
ejemplos de esto. Una vez que subes todas las fotografías de tu familia al
servicio, este reconoce automáticamente que la misma persona A aparece en las
fotografías 1, 5 y 11, mientras que otra persona B aparece en las fotografías 2, 5 y 7. Esta
es la parte no supervisada del algoritmo (agrupamiento). Ahora todo lo que necesita el
4
sistema es que le digas quiénes son estas personas. Simplemente agrega una etiqueta por
persona y podrá nombrar a todos los que aparecen en cada fotografía, lo que resulta útil
para buscar fotografías.
La mayoría de los algoritmos de aprendizaje semisupervisado son
combinaciones de algoritmos supervisados y no supervisados. Por ejemplo, las
redes de creencias profundas (DBN) se basan en componentes no supervisados llamados
máquinas de Boltzmann restringidas (RBM) apiladas una sobre otra. Las RBM son
Machine Translated by Google
se entrena secuencialmente de manera no supervisada y luego se ajusta todo el sistema
utilizando técnicas de aprendizaje supervisado.
Aprendizaje por refuerzo El
aprendizaje por refuerzo es un fenómeno muy diferente. El sistema de aprendizaje, llamado
agente en este contexto, puede observar el entorno, seleccionar y realizar acciones y
obtener recompensas a cambio (o penalizaciones en forma de recompensas negativas,
como se muestra en la Figura 1­12). Luego debe aprender por sí mismo cuál es la mejor
estrategia, llamada política, para obtener la mayor recompensa a lo largo del tiempo.
Una política define qué acción debe elegir el agente cuando se encuentra en una situación
determinada.
Figura 1­12. Aprendizaje por refuerzo
Machine Translated by Google
Por ejemplo, muchos robots implementan algoritmos de aprendizaje por refuerzo para aprender a
caminar. El programa AlphaGo de DeepMind también es un buen ejemplo de aprendizaje
por refuerzo: fue noticia en mayo de 2017 cuando venció al campeón mundial Ke Jie en el juego
de Go. Aprendió su política ganadora analizando millones de juegos y luego jugando muchos
juegos contra sí mismo. Nótese que el aprendizaje se desactivó durante los juegos contra el
campeón; AlphaGo simplemente estaba aplicando la política que había aprendido.
Aprendizaje por lotes y en línea
Otro criterio utilizado para clasificar los sistemas de aprendizaje automático es si el sistema puede o
no aprender de forma incremental a partir de un flujo de datos entrantes.
Aprendizaje por lotes
En el aprendizaje por lotes, el sistema no es capaz de aprender de forma incremental: debe
entrenarse utilizando todos los datos disponibles. Esto generalmente requiere mucho tiempo y
recursos informáticos, por lo que normalmente se realiza sin conexión. Primero se entrena el
sistema y luego se pone en producción y se ejecuta sin aprender más; solo aplica lo que ha
aprendido. Esto se llama aprendizaje sin conexión.
Si desea que un sistema de aprendizaje por lotes conozca datos nuevos (como un nuevo tipo de
spam), debe entrenar una nueva versión del sistema desde cero con el conjunto de datos completo
(no solo los datos nuevos, sino también los antiguos), luego detener el sistema antiguo y
reemplazarlo por el nuevo.
Afortunadamente, todo el proceso de entrenamiento, evaluación y lanzamiento de un sistema
de aprendizaje automático se puede automatizar con bastante facilidad (como se muestra en la
Figura 1­3), por lo que incluso un sistema de aprendizaje por lotes puede adaptarse al cambio.
Simplemente actualice los datos y entrene una nueva versión del sistema desde cero con la frecuencia
que sea necesaria.
Esta solución es sencilla y suele funcionar bien, pero el entrenamiento con el conjunto completo de
datos puede llevar muchas horas, por lo que normalmente entrenaría un nuevo sistema solo cada
24 horas o incluso solo una vez por semana. Si su sistema necesita adaptarse a
Machine Translated by Google
Si necesita datos que cambian rápidamente (por ejemplo, para predecir los precios de las acciones), necesitará
una solución más reactiva.
Además, el entrenamiento con el conjunto completo de datos requiere una gran cantidad de recursos informáticos
(CPU, espacio de memoria, espacio de disco, E/S de disco, E/S de red, etc.). Si tienes una gran cantidad de
datos y automatizas tu sistema para entrenar desde cero todos los días, terminará costándote mucho dinero. Si la
cantidad de datos es enorme, puede incluso resultar imposible utilizar un algoritmo de aprendizaje por lotes.
Por último, si su sistema necesita poder aprender de manera autónoma y tiene recursos limitados (por ejemplo,
una aplicación para teléfonos inteligentes o un explorador en Marte), entonces llevar consigo grandes cantidades
de datos de entrenamiento y consumir muchos recursos para entrenar durante horas todos los días es
un obstáculo.
Afortunadamente, una mejor opción en todos estos casos es utilizar algoritmos que sean capaces de aprender
incrementalmente.
Aprendizaje en línea En el
aprendizaje en línea, se entrena al sistema de manera incremental al alimentarlo con instancias de datos de
manera secuencial, ya sea de manera individual o en grupos pequeños llamados minilotes. Cada paso de
aprendizaje es rápido y económico, por lo que el sistema puede aprender sobre los nuevos datos sobre
la marcha, a medida que llegan (consulte la Figura 1­13).
Figura 1­13. En el aprendizaje en línea, se entrena un modelo y se lo lanza a producción, y luego continúa
aprendiendo a medida que llegan nuevos datos.
Machine Translated by Google
El aprendizaje en línea es ideal para sistemas que reciben datos como un flujo continuo (por ejemplo,
precios de acciones) y necesitan adaptarse a los cambios de manera rápida o autónoma. También
es una buena opción si tienes recursos informáticos limitados: una vez que un sistema de
aprendizaje en línea ha aprendido sobre nuevas instancias de datos, ya no las necesita, por lo
que puedes descartarlas (a menos que quieras poder volver a un estado anterior y "reproducir" los
datos). Esto puede ahorrar una gran cantidad de espacio.
Los algoritmos de aprendizaje en línea también se pueden utilizar para entrenar sistemas con
conjuntos de datos enormes que no caben en la memoria principal de una máquina (esto se
denomina aprendizaje fuera del núcleo ). El algoritmo carga parte de los datos, ejecuta un paso de
entrenamiento con esos datos y repite el proceso hasta que se haya ejecutado con todos los datos
(consulte la Figura 1­14).
ADVERTENCIA
El aprendizaje fuera del núcleo suele realizarse sin conexión (es decir, no en el sistema en vivo), por lo que el
aprendizaje en línea puede ser un nombre confuso. Piense en ello como un aprendizaje incremental.
Un parámetro importante de los sistemas de aprendizaje en línea es la velocidad con la que
deben adaptarse a los cambios en los datos: esto se denomina tasa de aprendizaje. Si se establece
una tasa de aprendizaje alta, el sistema se adaptará rápidamente a los nuevos datos, pero también
tenderá a olvidar rápidamente los datos antiguos (no es conveniente que un filtro de spam marque
solo los tipos de spam más recientes que se le mostraron). Por el contrario, si se establece una tasa
de aprendizaje baja, el sistema tendrá más inercia; es decir, aprenderá más lentamente, pero
también será menos sensible al ruido en los nuevos datos o a las secuencias de puntos de datos no
representativos (valores atípicos).
Machine Translated by Google
Figura 1­14. Uso del aprendizaje en línea para gestionar grandes conjuntos de datos
Un gran desafío con el aprendizaje en línea es que si se introducen datos incorrectos
en el sistema, el rendimiento del mismo disminuirá gradualmente. Si se trata de un
sistema en vivo, sus clientes lo notarán. Por ejemplo, los datos incorrectos pueden provenir
de un sensor defectuoso en un robot o de alguien que envía spam a un motor de búsqueda
para intentar obtener una posición alta en los resultados de búsqueda. Para reducir este
riesgo, debe monitorear su sistema de cerca y desactivar rápidamente el aprendizaje
(y posiblemente volver a un estado que funcionaba previamente) si detecta una caída
en el rendimiento. También puede querer monitorear los datos de entrada y reaccionar
ante datos anormales (por ejemplo, utilizando un algoritmo de detección de anomalías).
Aprendizaje basado en instancias versus aprendizaje basado en modelos
Otra forma de clasificar los sistemas de aprendizaje automático es según su generalización.
La mayoría de las tareas de aprendizaje automático consisten en hacer predicciones.
Esto significa que, dada una cantidad de ejemplos de entrenamiento, el sistema debe poder
hacer buenas predicciones para (generalizar a) ejemplos que nunca ha visto antes. Tener una
buena medida de rendimiento en los datos de entrenamiento es bueno, pero no suficiente;
el verdadero objetivo es tener un buen rendimiento en nuevas instancias.
Machine Translated by Google
Hay dos enfoques principales para la generalización: el aprendizaje basado en instancias y el
aprendizaje basado en modelos.
Aprendizaje basado en instancias
Posiblemente la forma más trivial de aprendizaje sea simplemente aprender de memoria. Si creara
un filtro de spam de esta manera, solo marcaría todos los correos electrónicos que sean idénticos a los
correos electrónicos que ya han sido marcados por los usuarios; no es la peor solución, pero
ciertamente no es la mejor.
En lugar de marcar únicamente los correos electrónicos que son idénticos a los correos electrónicos
no deseados, el filtro de correo no deseado podría programarse para marcar también los correos
electrónicos que son muy similares a los correos electrónicos no deseados. Esto requiere una
medida de similitud entre dos correos electrónicos. Una medida de similitud (muy básica) entre
dos correos electrónicos podría ser contar la cantidad de palabras que tienen en común. El sistema
marcaría un correo electrónico como correo no deseado si tiene muchas palabras en común con un
correo electrónico no deseado conocido.
Esto se denomina aprendizaje basado en instancias: el sistema aprende los ejemplos de memoria y
luego los generaliza a nuevos casos utilizando una medida de similitud para compararlos con
los ejemplos aprendidos (o un subconjunto de ellos). Por ejemplo, en la Figura 1­15, la nueva instancia
se clasificaría como un triángulo porque la mayoría de las instancias más similares pertenecen a esa
clase.
Figura 1­15. Aprendizaje basado en instancias
Machine Translated by Google
Aprendizaje basado en modelos
Otra forma de generalizar a partir de un conjunto de ejemplos es construir un modelo de
Estos ejemplos y luego usar ese modelo para hacer predicciones. Esto se llama
aprendizaje basado en modelos (Figura 1­16).
Figura 1­16. Aprendizaje basado en modelos
Por ejemplo, supongamos que quieres saber si el dinero hace feliz a la gente, entonces
Puede descargar los datos del Índice de Vida Mejor del sitio web de la OCDE y
Estadísticas sobre el producto interno bruto (PIB) per cápita del FMI
Sitio web. Luego, se unen las tablas y se ordenan por PIB per cápita. Tabla 1­1
muestra un extracto de lo que obtendrás.
Tabla 1­1. ¿El dinero hace más felices a las personas?
PIB per cápita del país (USD) Satisfacción con la vida
Hungría
12.240
4.9
Corea
27.195
5.8
Francia
37.675
6.5
Australia
50,962
7.3
Estados Unidos 55,805
7.2
Machine Translated by Google
Grafiquemos los datos de estos países (Figura 1­17).
Figura 1­17. ¿Ves una tendencia aquí?
¡Aquí parece haber una tendencia! Aunque los datos son ruidosos (es decir, en parte
aleatorios), parece que la satisfacción con la vida aumenta más o menos linealmente a
medida que aumenta el PIB per cápita del país. Por lo tanto, usted decide
modelar la satisfacción con la vida como una función lineal del PIB per cápita. Este
paso se llama selección del modelo: usted seleccionó un modelo lineal de satisfacción
con la vida con un solo atributo, el PIB per cápita (Ecuación 1­1).
Ecuación 1­1. Un modelo lineal simple
satisfacción_con_la_vida = θ0 + θ1 × PIB_per_cápita
Este modelo tiene dos parámetros de modelo, los0
5
1Al modificar estos
parámetros θ y θ , puede hacer que su modelo represente cualquier función lineal,
como se muestra en la Figura 1­18.
Machine Translated by Google
Figura 1­18. Algunos modelos lineales posibles
Antes de poder utilizar su modelo, debe definir los valores de los parámetros θ y θ
0
1 ¿Cómo puede saber qué valores harán que su modelo tenga el mejor
rendimiento? Para responder a esta pregunta, debe especificar una medida de rendimiento.
Puede definir una función de utilidad (o función de aptitud) que mida la calidad de su
modelo, o puede definir una función de costo que mida su calidad . Para los problemas
de regresión lineal, las personas suelen utilizar una función de costo que mide la distancia
entre las predicciones del modelo lineal y los ejemplos de entrenamiento; el objetivo es
minimizar esta distancia.
Aquí es donde entra en juego el algoritmo de regresión lineal: le suministras tus ejemplos de entrenamiento y encuentra los
parámetros que hacen que el modelo lineal se ajuste mejor a tus datos. Esto se llama entrenar el modelo. En nuestro caso,
el algoritmo encuentra que los valores óptimos de los parámetros son θ = 4,85 y θ = –5 4,91 × 10
0
.
1
Machine Translated by Google
ADVERTENCIA
De manera confusa, la misma palabra “modelo” puede referirse a un tipo de modelo (por ejemplo,
regresión lineal), a una arquitectura de modelo completamente especificada (por ejemplo, regresión lineal
con una entrada y una salida) o al modelo entrenado final listo para usarse para predicciones (por ejemplo,
regresión lineal con una entrada y una salida, utilizando θ = 4,85 y θ = 4,91 × 10 ).0La selección del modelo
1
–5
consiste en elegir el tipo de modelo y especificar completamente su arquitectura. Entrenar un modelo
significa ejecutar un algoritmo para encontrar los parámetros del modelo que lo harán ajustarse mejor
a los datos de entrenamiento (y, con suerte, hacer buenas predicciones sobre nuevos datos).
Ahora el modelo se ajusta a los datos de entrenamiento lo más fielmente posible (para un
modelo lineal), como se puede ver en la Figura 1­19.
Figura 1­19. El modelo lineal que mejor se ajusta a los datos de entrenamiento
Finalmente, está listo para ejecutar el modelo para hacer predicciones. Por ejemplo, digamos
que quiere saber qué tan felices son los chipriotas y los datos de la OCDE no tienen la
respuesta. Afortunadamente, puede usar su modelo para hacer una buena predicción: busque
el PIB per cápita de Chipre, encuentre $22,587 y luego aplique su modelo y descubra que la
satisfacción con la vida probablemente esté en algún lugar alrededor de 4.85 + 22,587 × 4.91 ×
10 = 5.96.
­5
Machine Translated by Google
Para abrir el apetito, el Ejemplo 1­1 muestra el código Python que carga el
6
datos, los prepara, crea
un diagrama de dispersión para la visualización y luego entrena un
7
modelo lineal y hace una predicción.
Ejemplo 1­1. Entrenamiento y ejecución de un modelo lineal con Scikit­Learn
importar matplotlib.pyplot como plt
importar numpy como np
importar pandas como pd
importar sklearn.linear_model
# Cargar los datos
oecd_bli = pd.read_csv("oecd_bli_2015.csv", miles=',')
PIB per cápita =
pd.read_csv("pib_per_cápita.csv",miles=',',delimitador='\t',
codificación='latin1', na_values="n/a")
# Preparar
Los datos
estadísticas_país = preparar_estadísticas_país(oecd_bli, pib_per_cápita)
X = np.c_[country_stats["PIB per cápita"]]
y = np.c_[country_stats[" Satisfacción con la vida"]]
# Visualizar los datos
country_stats.plot(kind='scatter', x="PIB per cápita", y='Vida
satisfacción')
plt.mostrar()
#
Seleccionar a
modelo lineal
modelo = sklearn.linear_model.LinearRegression()
# Tren
El modelo
modelo.fit(X, y)
# Hacer a predicción para
X_nuevo = [[22587]]
Chipre
# De Chipre
print(modelo.predict(X_nuevo))
PIB por
cápita
número de productos[[5.96242338]]
Machine Translated by Google
NOTA
Si en cambio hubiera utilizado un algoritmo de aprendizaje basado en instancias, habría
descubierto que Eslovenia tiene el PIB per cápita más cercano al de Chipre (20.732 dólares) y,
dado que los datos de la OCDE nos indican que la satisfacción con la vida de los eslovenos
es de 5,7, habría predicho una satisfacción con la vida de 5,7 para Chipre. Si se aleja un poco
y observa los dos países más cercanos, encontrará que Portugal y España tienen satisfacciones
con la vida de 5,1 y 6,5, respectivamente. Si se promedian estos tres valores, se obtiene 5,77,
que es bastante cercano a la predicción basada en el modelo. Este algoritmo simple se
llama regresión de k vecinos más cercanos (en este ejemplo, k = 3).
Reemplazar el modelo de regresión lineal con la regresión de k vecinos más cercanos en el
código anterior es tan simple como reemplazar estas dos líneas:
importar sklearn.linear_model modelo
= sklearn.linear_model.LinearRegression()
con estos dos:
importar sklearn.neighbors modelo
= sklearn.neighbors.KNeighborsRegressor( n_neighbors=3)
Si todo salió bien, su modelo hará buenas predicciones. Si no, es posible que deba utilizar más
atributos (tasa de empleo, salud, contaminación del aire, etc.), obtener más datos de entrenamiento
o de mejor calidad, o tal vez seleccionar un modelo más potente (por ejemplo, un modelo de
regresión polinómica).
En resumen:
Estudiaste los datos.
Has seleccionado un modelo.
Lo entrenó con los datos de entrenamiento (es decir, el algoritmo de aprendizaje
buscó los valores de los parámetros del modelo que minimizan una función de
costo).
Finalmente, aplicó el modelo para hacer predicciones sobre nuevos casos (esto se
llama inferencia), con la esperanza de que este modelo se generalizara.
Machine Translated by Google
Bueno.
Así es como se ve un proyecto típico de aprendizaje automático. En el Capítulo 2, experimentará
esto de primera mano al recorrer un proyecto de principio a fin.
Hemos cubierto mucho terreno hasta ahora: ahora sabe de qué se trata realmente el
aprendizaje automático, por qué es útil, cuáles son algunas de las categorías más comunes de
sistemas de aprendizaje automático y cómo es un flujo de trabajo de proyecto típico. Ahora
veamos qué puede salir mal en el aprendizaje y evitar que haga predicciones precisas.
Principales desafíos del aprendizaje automático En resumen, dado que su tarea
principal es seleccionar un algoritmo de aprendizaje y entrenarlo con algunos datos, las dos
cosas que pueden salir mal son un "mal algoritmo" y "mal datos". Comencemos con ejemplos
de mal datos.
Cantidad insuficiente de datos de entrenamiento Para que un
niño pequeño aprenda qué es una manzana, todo lo que se necesita es señalar una manzana y
decir "manzana" (posiblemente repitiendo este procedimiento unas cuantas veces).
Ahora el niño es capaz de reconocer manzanas de todo tipo de colores y formas.
Genio.
El aprendizaje automático aún no ha llegado a su fin; se necesitan muchos datos para que
la mayoría de los algoritmos de aprendizaje automático funcionen correctamente. Incluso
para problemas muy simples, normalmente se necesitan miles de ejemplos, y para
problemas complejos, como el reconocimiento de imágenes o de voz, es posible que se
necesiten millones de ejemplos (a menos que se puedan reutilizar partes de un modelo existente).
Machine Translated by Google
LA EFICACIA IRRAZONABLE DE LOS DATOS
En un artículo famoso Publicado en 2001, los investigadores de Microsoft Michele
Banko y Eric Brill demostraron que algoritmos de aprendizaje automático muy
diferentes, incluidos algunos bastante simples, funcionaban casi idénticamente bien
8
en un problema complejo de desambiguación del lenguaje natural una vez que
se
les proporcionaban suficientes datos (como se puede ver en la Figura 1­20).
9
Figura 1­20. La importancia de los datos frente a los algoritmos
Como dicen los autores, “estos resultados sugieren que quizá debamos
reconsiderar la compensación entre gastar tiempo y dinero en el desarrollo
de algoritmos y gastarlo en el desarrollo de corpus”.
Machine Translated by Google
La idea de que los datos son más importantes que los algoritmos para problemas
complejos fue popularizada aún más por Peter Norvig et al. en un artículo titulado
10
“The Unreasonable Effectiveness of Data”, publicado en 2009. Sin embargo, debe
tenerse en cuenta que los conjuntos de datos pequeños y medianos siguen siendo
muy comunes y no siempre es fácil o barato obtener datos de entrenamiento
adicionales, así que no abandone los algoritmos todavía.
Datos de entrenamiento no representativos Para
generalizar bien, es fundamental que los datos de entrenamiento sean
representativos de los nuevos casos a los que desea generalizar. Esto es así tanto
si utiliza aprendizaje basado en instancias como si utiliza aprendizaje basado en modelos.
Por ejemplo, el conjunto de países que utilizamos anteriormente para entrenar el
modelo lineal no era perfectamente representativo; faltaban algunos países.
La figura 1­21 muestra cómo se ven los datos cuando se agregan los países
faltantes.
Figura 1­21. Una muestra de entrenamiento más representativa
Si se entrena un modelo lineal con estos datos, se obtiene la línea continua, mientras que
el modelo antiguo está representado por la línea de puntos. Como se puede ver, no
solo la adición de algunos países faltantes altera significativamente el modelo, sino que
también deja en claro que un modelo lineal tan simple probablemente nunca va a
funcionar bien. Parece que los países muy ricos no son más felices que los países
moderadamente ricos (de hecho, parecen más infelices) y, a la inversa, algunos
países pobres parecen más felices que muchos países ricos.
Machine Translated by Google
Al utilizar un conjunto de entrenamiento no representativo, entrenamos un modelo que
probablemente no realice predicciones precisas, especialmente para países muy pobres y
muy ricos.
Es fundamental utilizar un conjunto de entrenamiento que sea representativo de los
casos a los que se desea generalizar. Esto suele ser más difícil de lo que parece: si la
muestra es demasiado pequeña, se obtendrá ruido de muestreo (es decir, datos no
representativos como resultado del azar), pero incluso muestras muy grandes pueden no ser
representativas si el método de muestreo es defectuoso. Esto se denomina sesgo de muestreo.
Machine Translated by Google
EJEMPLOS DE SESGO DE MUESTREO
Tal vez el ejemplo más famoso de sesgo de muestreo ocurrió durante las elecciones
presidenciales de Estados Unidos de 1936, en las que se enfrentaron Landon y
Roosevelt: el Literary Digest realizó una encuesta muy amplia, enviando correos a unos
10 millones de personas. Obtuvo 2,4 millones de respuestas y predijo con gran
certeza que Landon obtendría el 57% de los votos. En cambio, Roosevelt ganó con el
62% de los votos. El fallo estaba en el método de muestreo del Literary Digest :
En primer lugar, para obtener las direcciones a las que enviar las encuestas, el
Literary Digest utilizó directorios telefónicos, listas de suscriptores
de revistas, listas de miembros de clubes y similares. Todas estas listas tendían
a favorecer a las personas más ricas, que tenían más probabilidades de votar
por los republicanos (de ahí el nombre de Landon).
En segundo lugar, menos del 25% de las personas encuestadas
respondieron. Esto introdujo un sesgo de muestreo, al descartar
potencialmente a personas a las que no les interesaba mucho la política, a
las que no les gustaba el Literary Digest y a otros grupos clave. Se trata de un
tipo especial de sesgo de muestreo llamado sesgo de falta de respuesta.
He aquí otro ejemplo: supongamos que quieres crear un sistema para reconocer vídeos de
música funk. Una forma de crear tu conjunto de entrenamiento es buscar "música funk" en
YouTube y utilizar los vídeos resultantes. Pero esto supone que el motor de
búsqueda de YouTube devuelve un conjunto de vídeos que son representativos de todos
los vídeos de música funk de YouTube. En realidad, es probable que los resultados de la
búsqueda estén sesgados hacia los artistas populares (y si vives en Brasil, obtendrás muchos
vídeos de "funk carioca", que no suenan como James Brown). Por otro lado, ¿de qué otra
manera puedes obtener un gran conjunto de entrenamiento?
Datos de mala calidad
Machine Translated by Google
Obviamente, si los datos de entrenamiento están llenos de errores, valores atípicos y ruido (por
ejemplo, debido a mediciones de mala calidad), será más difícil para el sistema detectar los patrones
subyacentes, por lo que es menos probable que el sistema funcione bien. A menudo, vale la
pena dedicar tiempo a limpiar los datos de entrenamiento. La verdad es que la mayoría de
los científicos de datos dedican una parte importante de su tiempo a hacer precisamente eso. A
continuación, se muestran algunos ejemplos de situaciones en las que conviene limpiar los datos de
entrenamiento:
Si algunos casos son claramente atípicos, puede ser útil simplemente descartarlos
o intentar corregir los errores manualmente.
Si a algunas instancias les faltan algunas características (por ejemplo, el 5
% de sus clientes no especificaron su edad), debe decidir si desea ignorar este
atributo por completo, ignorar estas instancias, completar los valores faltantes
(por ejemplo, con la edad media) o entrenar un modelo con la característica
y un modelo sin ella.
Características irrelevantes
Como dice el refrán: basura entra, basura sale. Su sistema solo podrá aprender si los datos de
entrenamiento contienen suficientes características relevantes y no demasiadas irrelevantes. Una
parte fundamental del éxito de un proyecto de aprendizaje automático es idear un buen
conjunto de características para entrenar. Este proceso, llamado ingeniería de características, implica
los siguientes pasos:
Selección de características (seleccionar las características más útiles para entrenar
entre las características existentes)
Extracción de características (combinación de características existentes para
producir una más útil; como vimos anteriormente, los algoritmos de reducción de
dimensionalidad pueden ayudar)
Creación de nuevas funciones mediante la recopilación de nuevos datos
Ahora que hemos visto muchos ejemplos de datos incorrectos, veamos un par de ejemplos de
algoritmos incorrectos.
Machine Translated by Google
Sobreajuste de los datos de entrenamiento
Supongamos que estás visitando un país extranjero y el taxista te estafa. Podrías sentirte
tentado a decir que todos los taxistas de ese país son ladrones.
La generalización excesiva es algo que los humanos hacemos con demasiada
frecuencia y, por desgracia, las máquinas pueden caer en la misma trampa si no tenemos
cuidado. En el aprendizaje automático, esto se denomina sobreajuste: significa
que el modelo funciona bien con los datos de entrenamiento, pero no generaliza bien.
La figura 1­22 muestra un ejemplo de un modelo de satisfacción vital
polinomial de alto grado que se ajusta en gran medida a los datos de entrenamiento.
Aunque funciona mucho mejor en los datos de entrenamiento que el modelo lineal
simple, ¿realmente confiaría en sus predicciones?
Figura 1­22. Sobreajuste de los datos de entrenamiento
Los modelos complejos, como las redes neuronales profundas, pueden detectar patrones
sutiles en los datos, pero si el conjunto de entrenamiento es ruidoso o demasiado
pequeño (lo que genera ruido de muestreo), es probable que el modelo detecte patrones
en el propio ruido. Obviamente, estos patrones no se generalizarán a nuevas
instancias. Por ejemplo, supongamos que introduce en su modelo de satisfacción vital
muchos más atributos, incluidos algunos que no son informativos, como el nombre del país.
En ese caso, un modelo complejo puede detectar patrones como el hecho de que
todos los países en los datos de entrenamiento con una w en su nombre tienen una
satisfacción con la vida mayor que 7: Nueva Zelanda (7,3), Noruega (7,4), Suecia
(7,2) y Suiza (7,5). ¿Qué tan seguro está de que la regla de satisfacción con la w
se generaliza a Ruanda o Zimbabue? Obviamente, este patrón ocurrió en
Machine Translated by Google
los datos de entrenamiento por pura casualidad, pero el modelo no tiene forma de saber
si un patrón es real o simplemente el resultado del ruido en los datos.
ADVERTENCIA
El sobreajuste se produce cuando el modelo es demasiado complejo en relación con la cantidad y el
ruido de los datos de entrenamiento. Estas son algunas posibles soluciones:
Simplifique el modelo seleccionando uno con menos parámetros (por ejemplo, un modelo
lineal en lugar de un modelo polinomial de alto grado), reduciendo la cantidad de atributos
en los datos de entrenamiento o restringiendo el modelo.
Recopilar más datos de entrenamiento.
Reducir el ruido en los datos de entrenamiento (por ejemplo, corregir errores de datos y eliminar
valores atípicos).
La restricción de un modelo para simplificarlo y reducir el riesgo de sobreajuste se
denomina regularización. Por ejemplo, el modelo lineal que definimos anteriormente tiene
dos parámetros, θ y θ grados
0 de
1 Esto le da al algoritmo de aprendizaje dos
libertad para adaptar el modelo a los datos de entrenamiento: puede ajustar tanto la
altura (θ ) como la pendiente
(θ ) de la línea.1Si forzamos θ = 0, la
0
1
El algoritmo tendría solo un grado de libertad y le resultaría mucho más difícil ajustar los
datos correctamente: todo lo que podría hacer es mover la línea hacia arriba o hacia abajo
para acercarse lo más posible a las instancias de entrenamiento, por lo que terminaría
alrededor de la media. ¡Un modelo muy simple de hecho! Si permitimos que el
algoritmo modifique θ pero 1lo obligamos a mantenerlo pequeño, entonces el algoritmo
de aprendizaje tendrá efectivamente algo entre uno y dos grados de libertad.
Producirá un modelo que es más simple que uno con dos grados de libertad, pero más
complejo que uno con solo uno. Quiere encontrar el equilibrio adecuado entre ajustar
los datos de entrenamiento perfectamente y mantener el modelo lo suficientemente
simple para garantizar que se generalizará bien.
La figura 1­23 muestra tres modelos. La línea de puntos representa el modelo original
que se entrenó con los países representados como círculos (sin los países representados
como cuadrados), la línea discontinua es nuestro segundo modelo.
Machine Translated by Google
Entrenado con todos los países (círculos y cuadrados), y la línea sólida es un modelo
entrenado con los mismos datos que el primer modelo pero con una restricción
de regularización. Puede ver que la regularización obligó al modelo a tener una pendiente
menor: este modelo no se ajusta a los datos de entrenamiento (círculos) tan bien como el
primer modelo, pero en realidad se generaliza mejor a nuevos ejemplos que no vio
durante el entrenamiento (cuadrados).
Figura 1­23. La regularización reduce el riesgo de sobreajuste
La cantidad de regularización que se aplicará durante el aprendizaje se puede controlar
mediante un hiperparámetro. Un hiperparámetro es un parámetro de un algoritmo
de aprendizaje (no del modelo). Como tal, no se ve afectado por el algoritmo de aprendizaje
en sí; debe establecerse antes del entrenamiento y permanece constante durante el
mismo. Si establece el hiperparámetro de regularización en un valor muy grande,
obtendrá un modelo casi plano (una pendiente cercana a cero); es casi seguro que el
algoritmo de aprendizaje no se sobreajustará a los datos de entrenamiento, pero será menos
probable que encuentre una buena solución. Ajustar los hiperparámetros es una parte
importante de la construcción de un sistema de aprendizaje automático (verá un ejemplo
detallado en el próximo capítulo).
Ajuste insuficiente de los datos de entrenamiento
Como se puede suponer, el subajuste es lo opuesto al sobreajuste: se produce cuando el
modelo es demasiado simple para aprender la estructura subyacente de los datos. Por
ejemplo, un modelo lineal de satisfacción con la vida tiende a no ajustarse lo suficiente; la
realidad es simplemente más compleja que el modelo, por lo que sus predicciones están
destinadas a ser inexactas, incluso en los ejemplos de entrenamiento.
Machine Translated by Google
Estas son las principales opciones para solucionar este problema:
Seleccione un modelo más potente, con más parámetros.
Alimente mejores características al algoritmo de aprendizaje (ingeniería de
características).
Reducir las restricciones del modelo (por ejemplo, reducir el hiperparámetro
de regularización).
Retrocediendo un poco A
esta altura, ya sabe mucho sobre el aprendizaje automático. Sin embargo, hemos repasado
tantos conceptos que puede sentirse un poco perdido, así que retrocedamos un poco y observemos el
panorama general:
El aprendizaje automático consiste en hacer que las máquinas mejoren en alguna tarea
aprendiendo de los datos, en lugar de tener que codificar reglas explícitamente.
Hay muchos tipos diferentes de sistemas de ML: supervisados o no, por lotes o en línea,
basados en instancias o basados en modelos.
En un proyecto de ML, se recopilan datos en un conjunto de entrenamiento y se alimenta
el conjunto de entrenamiento a un algoritmo de aprendizaje. Si el algoritmo está basado
en modelos, ajusta algunos parámetros para adaptar el modelo al conjunto de entrenamiento
(es decir, para hacer buenas predicciones sobre el propio conjunto de entrenamiento) y,
con suerte, también podrá hacer buenas predicciones sobre nuevos casos. Si el algoritmo
está basado en instancias, simplemente aprende los ejemplos de memoria y
los generaliza a nuevas instancias utilizando una medida de similitud para compararlas
con las instancias aprendidas.
El sistema no funcionará bien si el conjunto de entrenamiento es demasiado pequeño, o si
los datos no son representativos, son ruidosos o están contaminados con características
irrelevantes (basura que entra, basura que sale). Por último, el modelo no debe ser ni
demasiado simple (en cuyo caso se ajustará por debajo de lo necesario) ni demasiado
complejo (en cuyo caso se ajustará por encima de lo necesario).
Machine Translated by Google
Solo queda un último tema importante por cubrir: una vez que hayas entrenado un modelo,
no debes simplemente "esperar" que se generalice a nuevos casos. Debes evaluarlo y ajustarlo
si es necesario. Veamos cómo hacerlo.
Prueba y validación La única forma de saber
qué tan bien se generalizará un modelo a nuevos casos es probarlo en casos nuevos. Una
forma de hacerlo es poner el modelo en producción y monitorear su rendimiento. Esto funciona
bien, pero si el modelo es terriblemente malo, los usuarios se quejarán, lo que no es la mejor
idea.
Una mejor opción es dividir los datos en dos conjuntos: el conjunto de entrenamiento y el
conjunto de prueba. Como lo indican estos nombres, se entrena el modelo utilizando el conjunto
de entrenamiento y se prueba utilizando el conjunto de prueba. La tasa de error en los casos
nuevos se denomina error de generalización (o error fuera de la muestra) y, al evaluar el
modelo en el conjunto de prueba, se obtiene una estimación de este error. Este valor indica el
rendimiento del modelo en casos que nunca antes ha visto.
Si el error de entrenamiento es bajo (es decir, su modelo comete pocos errores en el
conjunto de entrenamiento) pero el error de generalización es alto, significa que su modelo
está sobreajustando los datos de entrenamiento.
CONSEJO
Es común utilizar el 80% de los datos para entrenamiento y reservar el 20% para pruebas.
Sin embargo, esto depende del tamaño del conjunto de datos: si contiene 10 millones de instancias,
entonces mantener el 1% significa que su conjunto de prueba contendrá 100.000 instancias,
probablemente más que suficiente para obtener una buena estimación del error de generalización.
Ajuste de hiperparámetros y selección de modelos Evaluar un modelo es
bastante simple: solo use un conjunto de prueba. Pero suponga que está dudando entre dos
tipos de modelos (por ejemplo, un modelo lineal y un modelo polinomial): ¿cómo puede
decidir entre ellos? Una opción es entrenar ambos y comparar qué tan bien se generalizan
usando el conjunto de prueba.
Machine Translated by Google
Ahora supongamos que el modelo lineal se generaliza mejor, pero queremos aplicar
alguna regularización para evitar el sobreajuste. La pregunta es, ¿cómo elegimos el valor
del hiperparámetro de regularización? Una opción es entrenar 100 modelos diferentes utilizando
100 valores diferentes para este hiperparámetro. Supongamos que encontramos
el mejor valor de hiperparámetro que produce un modelo con el menor error de
generalización (digamos, solo un 5 %).
Lanzas este modelo a producción, pero lamentablemente no funciona tan bien como
esperabas y genera un 15 % de errores. ¿Qué sucedió?
El problema es que usted midió el error de generalización varias veces en el conjunto de
prueba y adaptó el modelo y los hiperparámetros para producir el mejor modelo para ese
conjunto en particular. Esto significa que es poco probable que el modelo funcione tan
bien con datos nuevos.
Una solución común a este problema se denomina validación de reserva: simplemente
se reserva una parte del conjunto de entrenamiento para evaluar varios modelos
candidatos y seleccionar el mejor. El nuevo conjunto reservado se denomina
conjunto de validación (o, a veces, conjunto de desarrollo o conjunto dev). Más
específicamente, se entrenan varios modelos con varios hiperparámetros en el conjunto de
entrenamiento reducido (es decir, el conjunto de entrenamiento completo menos el conjunto de
validación) y se selecciona el modelo que funciona mejor en el conjunto de validación.
Después de este proceso de validación de reserva, se entrena el mejor modelo en el
conjunto de entrenamiento completo (incluido el conjunto de validación) y esto da como
resultado el modelo final. Por último, se evalúa este modelo final en el conjunto de
prueba para obtener una estimación del error de generalización.
Esta solución suele funcionar bastante bien. Sin embargo, si el conjunto de validación es
demasiado pequeño, las evaluaciones del modelo serán imprecisas: puede terminar
seleccionando un modelo subóptimo por error. Por el contrario, si el conjunto de validación es
demasiado grande, el conjunto de entrenamiento restante será mucho más pequeño que el
conjunto de entrenamiento completo. ¿Por qué esto es malo? Bueno, dado que el
modelo final se entrenará en el conjunto de entrenamiento completo, no es ideal comparar
modelos candidatos entrenados en un conjunto de entrenamiento mucho más pequeño.
Sería como seleccionar al velocista más rápido para participar en una maratón. Una forma de
resolver este problema es realizar una validación cruzada repetida, utilizando muchos conjuntos de validación peq
Machine Translated by Google
Cada modelo se evalúa una vez por conjunto de validación después de entrenarlo con el
resto de los datos. Al promediar todas las evaluaciones de un modelo, se obtiene una
medida mucho más precisa de su rendimiento. Sin embargo, existe un inconveniente:
el tiempo de entrenamiento se multiplica por la cantidad de conjuntos de validación.
Desajuste de datos
En algunos casos, es fácil obtener una gran cantidad de datos para el entrenamiento, pero
estos datos probablemente no serán perfectamente representativos de los datos que
se utilizarán en la producción. Por ejemplo, supongamos que desea crear una aplicación
móvil para tomar fotografías de flores y determinar automáticamente su especie. Puede
descargar fácilmente millones de fotografías de flores en la web, pero no serán
perfectamente representativas de las fotografías que realmente se tomarán utilizando la
aplicación en un dispositivo móvil. Tal vez solo tenga 10 000 fotografías
representativas (es decir, tomadas realmente con la aplicación). En este caso, la regla más
importante que debe recordar es que el conjunto de validación y el conjunto de prueba
deben ser lo más representativos posible de los datos que espera utilizar en la
producción, por lo que deben estar compuestos exclusivamente de imágenes
representativas: puede mezclarlas y colocar la mitad en el conjunto de validación y la otra
mitad en el conjunto de prueba (asegurándose de que no terminen duplicados o casi
duplicados en ambos conjuntos). Pero después de entrenar el modelo con las
imágenes web, si observa que el rendimiento del modelo en el conjunto de
validación es decepcionante, no sabrá si esto se debe a que el modelo se ha ajustado en
exceso al conjunto de entrenamiento o si esto se debe simplemente a la falta de coincidencia
entre las imágenes web y las imágenes de la aplicación móvil. Una solución es dejar
algunas de las imágenes de entrenamiento (de la web) en otro conjunto que
Andrew Ng llama el conjunto de entrenamiento­desarrollo. Una vez que el modelo se
ha entrenado (en el conjunto de entrenamiento, no en el conjunto de entrenamiento­
desarrollo), puede evaluarlo en el conjunto de entrenamiento­desarrollo. Si funciona bien,
entonces el modelo no se está ajustando en exceso al conjunto de entrenamiento. Si
funciona mal en el conjunto de validación, el problema debe provenir de la falta de
coincidencia de los datos. Puede intentar abordar este problema preprocesando las imágenes
web para que se parezcan más a las imágenes que tomará la aplicación móvil y luego
volviendo a entrenar el modelo. Por el contrario, si el modelo funciona mal en el conjunto de entrenamiento­de
Machine Translated by Google
conjunto, por lo que debe intentar simplificar o regularizar el modelo, obtener más datos
de entrenamiento y limpiar los datos de entrenamiento.
TEOREMA DEL ALMUERZO NO ES GRATIS
Un modelo es una versión simplificada de las observaciones. Las
simplificaciones tienen como objetivo descartar los detalles superfluos que es poco
probable que se generalicen a nuevas instancias. Para decidir qué datos descartar y qué
datos conservar, se deben hacer suposiciones. Por ejemplo, un modelo lineal supone
que los datos son fundamentalmente lineales y que la distancia entre las instancias y
la línea recta es solo ruido, que se puede ignorar sin problemas.
11
En un famoso artículo de 1996, David Wolpert demostró que si no se hace ninguna
suposición sobre los datos, no hay razón para preferir un modelo sobre otro. Esto se llama
el teorema de que no hay almuerzo gratis (NFL, por sus siglas en inglés). Para algunos
conjuntos de datos, el mejor modelo es un modelo lineal, mientras que para otros es
una red neuronal. No existe ningún modelo que esté garantizado a priori que funcione
mejor (de ahí el nombre del teorema).
La única forma de saber con certeza cuál es el mejor modelo es evaluarlos todos. Como
esto no es posible, en la práctica se hacen algunas suposiciones razonables sobre los
datos y se evalúan solo unos pocos modelos razonables.
Por ejemplo, para tareas simples puedes evaluar modelos lineales con varios niveles de
regularización, y para un problema complejo puedes evaluar varias redes neuronales.
Ceremonias
En este capítulo hemos cubierto algunos de los conceptos más importantes del aprendizaje
automático. En los próximos capítulos profundizaremos más y escribiremos más código,
pero antes de hacerlo, asegúrese de saber cómo responder las siguientes preguntas:
1. ¿Cómo definirías el aprendizaje automático?
Machine Translated by Google
2. ¿Puedes nombrar cuatro tipos de problemas en los que destaca?
3. ¿Qué es un conjunto de entrenamiento etiquetado?
4. ¿Cuáles son las dos tareas supervisadas más comunes?
5. ¿Puedes nombrar cuatro tareas comunes no supervisadas?
6. ¿Qué tipo de algoritmo de aprendizaje automático utilizarías para permitir
¿Un robot para caminar en diversos terrenos desconocidos?
7. ¿Qué tipo de algoritmo utilizarías para segmentar a tus clientes en múltiples grupos?
8. ¿Enmarcaría el problema de la detección de spam como un problema de aprendizaje
supervisado o un problema de aprendizaje no supervisado?
9. ¿Qué es un sistema de aprendizaje en línea?
10. ¿Qué es el aprendizaje fuera del núcleo?
11. ¿Qué tipo de algoritmo de aprendizaje se basa en una medida de similitud para hacer
predicciones?
12. ¿Cuál es la diferencia entre un parámetro de modelo y un hiperparámetro de un algoritmo
de aprendizaje?
13. ¿Qué buscan los algoritmos de aprendizaje basados en modelos? ¿Cuál es la estrategia
más común que utilizan para tener éxito? ¿Cómo hacen predicciones?
14. ¿Puedes nombrar cuatro de los principales desafíos en el aprendizaje automático?
15. Si su modelo funciona muy bien con los datos de entrenamiento pero no se generaliza
bien a nuevas instancias, ¿qué sucede? ¿Puede nombrar tres posibles soluciones?
16. ¿Qué es un conjunto de pruebas y por qué querría usarlo?
17. ¿Cuál es el propósito de un conjunto de validación?
Machine Translated by Google
18. ¿Qué es el conjunto train­dev, cuándo lo necesitas y cómo lo haces?
¿lo usas?
19. ¿Qué puede salir mal si ajustas los hiperparámetros mediante la prueba?
¿colocar?
Las soluciones a estos ejercicios están disponibles en el Apéndice A.
1 Dato curioso: este nombre que suena extraño es un término estadístico introducido por Francis Galton mientras
estudiaba el hecho de que los hijos de personas altas tienden a ser más bajos que sus padres.
Como los niños eran más bajos, a esto lo llamó regresión a la media. Este nombre se aplicó luego a los métodos
que utilizó para analizar las correlaciones entre variables.
2 Algunas arquitecturas de redes neuronales pueden no estar supervisadas, como los autocodificadores y
Máquinas de Boltzmann restringidas. También pueden ser semisupervisadas, como en las redes de creencias
profundas y en el preentrenamiento no supervisado.
3 Observe cómo los animales están bastante bien separados de los vehículos y cómo los caballos están cerca de
los ciervos pero lejos de las aves. Figura reproducida con autorización de Richard Socher et al., “Zero­Shot
Learning Through Cross­Modal Transfer”, Proceedings of the 26th International Conference on
Neural Information Processing Systems 1 (2013): 935–943.
4 Entonces el sistema funciona perfectamente. En la práctica, suele crear unos cuantos clústeres por
persona, y a veces confunde a dos personas que parecen iguales, por lo que es posible que tengas que proporcionar
algunas etiquetas por persona y limpiar manualmente algunos grupos.
Por convención , la letra griega θ (theta) se utiliza con frecuencia para representar los parámetros del modelo.
6 La definición de la función prepare_country_stats() no se muestra aquí (ver esta
(Si quieres conocer todos los detalles sangrientos, consulta el cuaderno Jupyter del capítulo). Es solo un aburrido
código de Pandas que une los datos de satisfacción de vida de la OCDE con los datos del PIB per cápita del FMI.
No hay problema si aún no entiendes todo el código; presentaremos Scikit­Learn en los siguientes capítulos.
8 Por ejemplo, saber si escribir “to”, “two” o “too”, dependiendo del contexto.
9 Figura reproducida con autorización de Michele Banko y Eric Brill, “Escalamiento a corpora muy muy grandes
para la desambiguación del lenguaje natural”, Actas de la 39.ª Reunión Anual de la Asociación de Lingüística
Computacional (2001): 26–33.
10 Peter Norvig et al., “La eficacia irrazonable de los datos”, IEEE Intelligent Systems
24, no. 2 (2009): 8–12.
11 David Wolpert, “La falta de distinciones a priori entre algoritmos de aprendizaje”, Neural Computation 8, no. 7 (1996):
1341–1390.
Machine Translated by Google
Capítulo 2. Proyecto de aprendizaje automático de extremo a extremo
En este capítulo, trabajarás en un proyecto de ejemplo de principio a fin, y te harás pasar
1
por un científico de datos recientemente contratado en una empresa inmobiliaria. Estos son los pasos
principales que seguirás:
1. Mira el panorama general.
2. Obtenga los datos.
3. Descubra y visualice los datos para obtener información.
4. Preparar los datos para los algoritmos de aprendizaje automático.
5. Seleccione un modelo y entrénelo.
6. Perfecciona tu modelo.
7. Presenta tu solución.
8. Inicie, supervise y mantenga su sistema.
Trabajando con datos reales
Cuando se aprende sobre aprendizaje automático, lo mejor es experimentar con datos del mundo real,
no con conjuntos de datos artificiales. Afortunadamente, hay miles de conjuntos de datos abiertos
entre los que elegir, que abarcan todo tipo de dominios. Estos son algunos lugares en los que puede
buscar datos:
Repositorios populares de datos abiertos
Repositorio de aprendizaje automático de la Universidad de California en Irvine
Conjuntos de datos de Kaggle
Conjuntos de datos de AWS de Amazon
Machine Translated by Google
Portales meta (enumeran repositorios de datos abiertos)
Portales de datos
Monitor de datos abiertos
Cuándl
Otras páginas que enumeran muchos repositorios de datos abiertos populares
Lista de conjuntos de datos de aprendizaje automático de Wikipedia
Quora.com
El subreddit de conjuntos de datos
En este capítulo utilizaremos el conjunto de datos de precios de vivienda de California de
2
Repositorio de StatLib (ver Figura 2­1). Este conjunto de datos se basa en datos del censo de California
de 1990. No es exactamente reciente (en ese momento, todavía era asequible tener una casa bonita en el
Área de la Bahía), pero tiene muchas cualidades para el aprendizaje, por lo que supondremos que son datos
recientes. Para fines didácticos, he añadido un atributo categórico y he eliminado algunas características.
Machine Translated by Google
Figura 2­1. Precios de la vivienda en California
Mira el panorama general
¡Bienvenido a Machine Learning Housing Corporation! Su primera tarea es utilizar los datos
del censo de California para crear un modelo de los precios de la vivienda en el estado. Estos
datos incluyen métricas como la población, el ingreso medio y el precio medio de la
vivienda para cada grupo de bloques de California. Los grupos de bloques son la
unidad geográfica más pequeña para la que la Oficina del Censo de los EE. UU. publica datos
de muestra (un grupo de bloques suele tener una población de entre 600 y 3000
personas). Los llamaremos "distritos" para abreviar.
Su modelo debería aprender de estos datos y ser capaz de predecir el precio medio de
la vivienda en cualquier distrito, dadas todas las demás métricas.
Machine Translated by Google
CONSEJO
Como eres un científico de datos bien organizado, lo primero que debes hacer es sacar la lista de verificación
de tu proyecto de aprendizaje automático. Puedes empezar con la que se encuentra en el Apéndice B;
debería funcionar razonablemente bien para la mayoría de los proyectos de aprendizaje automático, pero
asegúrate de adaptarla a tus necesidades. En este capítulo repasaremos muchos elementos de la lista de
verificación, pero también omitiremos algunos, ya sea porque se explican por sí solos o porque se tratarán en
capítulos posteriores.
Enmarcar el problema
La primera pregunta que debes hacerle a tu jefe es cuál es exactamente el objetivo del negocio.
Probablemente, la creación de un modelo no sea el objetivo final. ¿Cómo espera la empresa
utilizar este modelo y beneficiarse de él? Conocer el objetivo es importante porque
determinará cómo enmarcará el problema, qué algoritmos seleccionará, qué medida de
rendimiento utilizará para evaluar su modelo y cuánto esfuerzo dedicará a modificarlo.
Su jefe le responde que el resultado de su modelo (una predicción del precio medio de la
vivienda en un distrito) se enviará a otro sistema de aprendizaje automático (consulte la Figura
3
2­2), junto con muchas otras señales. Este sistema posterior determinará si vale la pena
invertir en una zona determinada o no. Hacerlo bien es fundamental, ya que afecta
directamente a los ingresos.
Figura 2­2. Un flujo de trabajo de aprendizaje automático para inversiones inmobiliarias
Machine Translated by Google
OLEODUCTOS
Una secuencia de componentes de procesamiento de datos se denomina canalización de datos.
Los pipelines son muy comunes en los sistemas de Machine Learning, ya que hay muchos datos para
manipular y muchas transformaciones de datos para aplicar.
Los componentes suelen ejecutarse de forma asincrónica. Cada componente obtiene una gran
cantidad de datos, los procesa y genera el resultado en otro almacén de datos. Luego, algún tiempo
después, el siguiente componente en la secuencia obtiene estos datos y genera su propio resultado.
Cada componente es bastante autónomo: la interfaz entre los componentes es simplemente el almacén
de datos. Esto hace que el sistema sea fácil de entender (con la ayuda de un gráfico de flujo de datos) y
que los distintos equipos puedan centrarse en distintos componentes. Además, si un componente se
avería, los componentes posteriores a menudo pueden seguir funcionando con normalidad (al
menos durante un tiempo) utilizando simplemente el último resultado del componente averiado.
Esto hace que la arquitectura sea bastante robusta.
Por otro lado, un componente averiado puede pasar desapercibido durante algún tiempo si no se
implementa un control adecuado. Los datos se vuelven obsoletos y el rendimiento general del sistema
disminuye.
La siguiente pregunta que debe hacerle a su jefe es cómo es la solución actual (si es que existe alguna). La
situación actual a menudo le dará una referencia sobre el desempeño, así como ideas sobre cómo resolver el
problema. Su jefe le responde que actualmente los precios de las viviendas por distrito son estimados
manualmente por expertos: un equipo recopila información actualizada sobre un distrito y, cuando no
pueden obtener el precio medio de la vivienda, lo estiman utilizando reglas complejas.
Esto es costoso y lleva mucho tiempo, y sus estimaciones no son muy buenas; en los casos en que logran
averiguar el precio medio real de la vivienda, a menudo se dan cuenta de que sus estimaciones estaban
equivocadas en más de un 20 %. Por eso, la empresa cree que sería útil entrenar un modelo para
predecir el precio medio de la vivienda de un distrito, a partir de otros datos sobre ese distrito. Los datos del
censo parecen un gran conjunto de datos para explotar con este fin, ya que incluyen los precios medios
de la vivienda de miles de distritos, así como otros datos.
Con toda esta información ya estás listo para comenzar a diseñar tu sistema.
Primero, es necesario enmarcar el problema: ¿está supervisado, no supervisado o?
Machine Translated by Google
¿Aprendizaje por refuerzo? ¿Se trata de una tarea de clasificación, de regresión o de otra
cosa? ¿Debería utilizar técnicas de aprendizaje por lotes o de aprendizaje en línea?
Antes de continuar leyendo, haga una pausa e intente responder estas preguntas usted mismo.
¿Ha encontrado las respuestas? Veamos: es claramente una tarea típica de aprendizaje
supervisado, ya que se le proporcionan ejemplos de entrenamiento etiquetados (cada instancia
viene con el resultado esperado, es decir, el precio medio de la vivienda del distrito). También es una
tarea típica de regresión, ya que se le pide que prediga un valor. Más específicamente, se trata
de un problema de regresión múltiple , ya que el sistema utilizará múltiples características para
hacer una predicción (utilizará la población del distrito, el ingreso medio, etc.). También es un problema
de regresión univariante , ya que solo estamos tratando de predecir un único valor para cada distrito.
Si estuviéramos tratando de predecir múltiples valores por distrito, sería un problema de
regresión multivariante . Finalmente, no hay un flujo continuo de datos que ingresan al sistema,
no hay una necesidad particular de ajustarse a datos cambiantes rápidamente y los datos son lo
suficientemente pequeños como para caber en la memoria, por lo que el aprendizaje por lotes
simple debería funcionar bien.
CONSEJO
Si los datos fueran enormes, podría dividir el trabajo de aprendizaje por lotes entre varios
servidores (usando la técnica MapReduce) o utilizar una técnica de aprendizaje en línea.
Seleccione una medida de desempeño
El siguiente paso es seleccionar una medida de rendimiento. Una medida de rendimiento típica
para los problemas de regresión es el error cuadrático medio (RMSE). Da una idea de cuánto error
comete normalmente el sistema en sus predicciones, con un peso mayor para los errores grandes. La
ecuación 2­1 muestra la fórmula matemática para calcular el RMSE.
Ecuación 2­1. Error cuadrático medio (RMSE)
1
metro
Ecuación fundamental media (X, h) =
metro
yo=1
2
(h(x (i)) − y (i)) ∑
Machine Translated by Google
NOTACIONES
Esta ecuación presenta varias notaciones de aprendizaje automático muy
comunes que usaremos a lo largo de este libro:
m es el número de instancias en el conjunto de datos en el que está midiendo el
RMSE.
Por ejemplo, si está evaluando el RMSE en un conjunto de
validación de 2000 distritos, entonces m = 2000.
(i) x es un vector de todos los valores de características (excluyendo la etiqueta)
El
de la (i) instancia i en el conjunto de datos, e y es su etiqueta (el valor de salida
deseado para esa instancia).
Por ejemplo, si el primer distrito del conjunto de datos está ubicado
en la longitud ­118,29°, latitud 33,91°, y tiene 1.416 habitantes con
un ingreso medio de $38.372 y el valor medio de la vivienda es
$156.400 (ignorando las otras características por ahora),
entonces:
­118,29
incógnita
(1) =
33,91
1.416
38.372
y:
(1)
= 156.400 años
X es una matriz que contiene todos los valores de las características (excluidas
las etiquetas) de todas las instancias del conjunto de datos. Hay una fila por
El
(i)
instancia y (i) la fila i es igual a la transpuesta de x , 4 anotado (x ).
Por ejemplo, si el primer distrito es como el que se acaba de describir,
entonces la matriz X se verá así:
Machine Translated by Google
(x (1))
(x (2))
X=
=( −118,29 33,91 1.416 38.372
)
(x (1999))
(x (2000))
h es la función de predicción de su sistema, también llamada hipótesis. (i)
, él
Cuando a su sistema se le proporciona el vector de características de
(i)
una instancia x (i), se genera un valor predicho ŷ = h(x ) para esa instancia
(ŷ se pronuncia “y­hat”).
Por ejemplo, si su sistema predice que el precio medio (1) de la
vivienda
en el primer distrito es $158,400, entonces ŷ = h(x ) = 158,400. El error de
(1)
predicción para este distrito es (1) (1) ŷ – y = 2,000.
RMSE(X,h) es la función de costo medida en el conjunto de ejemplos utilizando
su hipótesis h.
(i)
Utilizamos fuente cursiva minúscula para valores escalares (como m o y ) y nombres
(i)
de funciones (como h), fuente negrita minúscula para vectores (como x ) y fuente negrita
mayúscula para matrices (como X).
Aunque el RMSE es generalmente la medida de rendimiento preferida para las tareas de
regresión, en algunos contextos puede que prefiera utilizar otra función. Por ejemplo, supongamos
que hay muchos distritos atípicos. En ese caso, puede considerar utilizar el error absoluto medio
(MAE, también llamado desviación absoluta media; consulte la ecuación 2­2):
Ecuación 2­2. Error absoluto medio (EMA)
MAE(X,h) =
1
metro
h(x (i)) − y ∑
metro
yo=1
(yo)
Machine Translated by Google
Tanto el RMSE como el MAE son formas de medir la distancia entre dos vectores: el vector de predicciones
y el vector de valores objetivo. Son posibles varias medidas de distancia, o normas :
Calcular la raíz de una suma de cuadrados (RMSE) corresponde a la norma euclidiana: esta
es la noción de distancia con la que estás familiarizado.
También se llama norma ℓ , descrita
como
2
∙
(o simplemente
2
∙
).
El cálculo de la suma de los absolutos (MAE) corresponde a la norma ℓ, que se indica1como
∙
. A veces se la denomina norma de Manhattan porque mide la distancia entre dos puntos
1
de una ciudad si solo se puede viajar a lo largo de manzanas ortogonales.
De manera más general, la norma
ℓ de un vector v que contiene n elementos es
a
definida como
v
k = (|v0| k
+ |v1| a +
1k
0 el
+ |vn| k) . ℓ da
número de elementos distintos de cero en el vector, y ℓ da el valor absoluto
máximo
∞
en el vector.
Cuanto más alto es el índice de norma, más se centra en los valores grandes y deja de lado
los pequeños. Por eso el RMSE es más sensible a los valores atípicos que el MAE. Pero
cuando los valores atípicos son exponencialmente raros (como en una curva con forma de
campana), el RMSE funciona muy bien y, en general, se prefiere.
Verifique las suposiciones Por último, es una
buena práctica enumerar y verificar las suposiciones que se han hecho hasta el momento (por usted o
por otros); esto puede ayudarle a detectar problemas graves en una etapa temprana.
Por ejemplo, los precios de distrito que genera su sistema se van a introducir en un sistema de aprendizaje
automático posterior y usted supone que estos precios se van a utilizar como tales. Pero ¿qué sucede
si el sistema posterior convierte los precios en categorías (por ejemplo, “barato”, “medio” o “caro”)
y luego utiliza esas categorías en lugar de los precios en sí? En este caso, obtener el precio perfecto no es
importante en absoluto; su sistema solo necesita obtener la categoría correcta. Si es así, entonces el
problema debería haberse enmarcado como una tarea de clasificación, no como una tarea de regresión. No
querrá descubrir esto después de trabajar en un sistema de regresión durante meses.
Machine Translated by Google
Afortunadamente, después de hablar con el equipo a cargo del sistema downstream, estás seguro
de que realmente necesitan los precios reales, no solo las categorías. ¡Genial! ¡Ya está
todo listo, las luces están en verde y puedes comenzar a codificar ahora!
Obtener los datos
Es hora de ponerse manos a la obra. No dude en tomar su computadora portátil y revisar los
siguientes ejemplos de código en un cuaderno Jupyter. El cuaderno Jupyter completo está
disponible en https://github.com/ageron/handson­ml2.
Crear el espacio de trabajo
Primero deberás tener instalado Python. Probablemente ya esté instalado en tu sistema. Si no es así,
5
puedes obtenerlo en https://www.python.org/.
A continuación, debe crear un directorio de espacio de trabajo para su código y conjuntos de
datos de aprendizaje automático. Abra una terminal y escriba los siguientes comandos (después
de las indicaciones $):
$ export ML_PATH="$HOME/ml" $
mkdir ­p $ML_PATH
# Puedes cambiar la ruta si lo prefieres
Necesitará varios módulos de Python: Jupyter, NumPy, pandas, Matplotlib y Scikit­Learn.
Si ya tiene Jupyter en ejecución con todos estos módulos instalados, puede pasar sin problemas
a “Descargar los datos”. Si aún no los tiene, hay muchas formas de instalarlos (y sus
dependencias). Puede usar el sistema de empaquetado de su sistema (por ejemplo,
apt­get en Ubuntu o MacPorts o Homebrew en macOS), instalar una distribución Scientific Python
como Anaconda y usar su sistema de empaquetado, o simplemente usar el propio sistema de
empaquetado de Python, pip, que se incluye de forma predeterminada con los instaladores
binarios de Python (desde Python 2.7.9). Puede verificar si pip está instalado escribiendo el
siguiente comando:
$ python3 ­m pip ­­version pip 19.0.2
desde [...]/lib/python3.6/site­packages (python 3.6)
6
Machine Translated by Google
Debe asegurarse de tener instalada una versión reciente de pip. Para actualizar
7
el módulo pip, escriba lo siguiente (la versión exacta puede variar):
$ python3 ­m pip install ­­user ­U pip Recopilando pip
[...]
Se instaló correctamente pip­19.0.2
Machine Translated by Google
CREANDO UN ENTORNO AISLADO
Si desea trabajar en un entorno aislado (lo cual es muy recomendable para poder
trabajar en diferentes proyectos sin tener versiones de biblioteca en conflicto), instale
8
virtualenv ejecutando el siguiente comando pip (nuevamente, si desea que virtualenv se
instale para todos los usuarios en su máquina, elimine ­­user y ejecute este comando con
derechos de administrador):
$ python3 ­m pip install ­­user ­U virtualenv Recopilando
virtualenv [...]
Virtualenv instalado correctamente
Ahora puedes crear un entorno Python aislado escribiendo esto:
$ cd $ML_PATH
$ virtualenv my_env
Usando el prefijo base '[...]'
Nuevo ejecutable de Python en [...]/ml/my_env/bin/python3.6 También se
está creando un ejecutable en [...]/ml/my_env/bin/python Instalando
setuptools, pip, wheel...listo.
Ahora, cada vez que quieras activar este entorno, simplemente abre una terminal
y escribe lo siguiente:
$ cd $ML_PATH
$ source my_env/bin/activate # en Linux o macOS $ .
\my_env\Scripts\activate # en Windows
Para desactivar este entorno, escriba deactivate. Mientras el entorno esté activo,
cualquier paquete que instale utilizando pip se instalará en este entorno aislado
y Python solo tendrá acceso a estos paquetes (si también desea tener acceso a los
paquetes del sistema, debe crear el entorno utilizando la opción ­­system­site­packages
de virtualenv). Consulte la documentación de virtualenv para obtener más información.
Ahora puedes instalar todos los módulos necesarios y sus dependencias usando este
simple comando pip (si no estás usando un entorno virtual, necesitarás el
Machine Translated by Google
­­opción de usuario o derechos de administrador):
$ python3 ­m pip install ­U jupyter matplotlib numpy pandas scipy scikit­learn Recopilando jupyter Descargando jupyter­1.0.0­
py2.py3­
none­any.whl
Recopilación de matplotlib [...]
Para comprobar su instalación, intente importar cada módulo de esta manera:
$ python3 ­c "importar jupyter, matplotlib, numpy, pandas, scipy, sklearn"
No debería haber ningún resultado ni error. Ahora puedes iniciar Jupyter escribiendo lo siguiente:
$ jupyter notebook [I 15:24
NotebookApp] Sirviendo notebooks desde el directorio local: [...]/ml [I 15:24 NotebookApp] 0 kernels activos [I 15:24
NotebookApp] El Jupyter Notebook se está ejecutando en: http://
localhost:8888/ [I 15:24 NotebookApp] Use Control­C para detener este servidor y apagar todos
los kernels (dos veces para omitir la
confirmación).
Ahora hay un servidor Jupyter ejecutándose en tu terminal, que escucha en el puerto 8888. Puedes
visitar este servidor abriendo tu navegador web en http://localhost:8888/ (esto suele suceder
automáticamente cuando se inicia el servidor). Deberías ver tu directorio de espacio de trabajo
vacío (que contiene solo el directorio env si seguiste las instrucciones de virtualenv anteriores).
Ahora crea un nuevo cuaderno de Python haciendo clic en el botón Nuevo y seleccionando la
versión de Python adecuada (ver Figura9 2­3). Al hacer eso, se creará un nuevo archivo de cuaderno
llamado Untitled.ipynb en tu espacio de trabajo, iniciarás un kernel de Python de Jupyter para
ejecutar el cuaderno y lo abrirás en una nueva pestaña. Debes comenzar por cambiar el nombre
de este cuaderno a "Housing" (esto cambiará automáticamente el nombre del archivo a
Housing.ipynb) haciendo clic en Untitled y escribiendo el nuevo nombre.
Machine Translated by Google
Figura 2­3. Su espacio de trabajo en Jupyter
Un cuaderno contiene una lista de celdas. Cada celda puede contener código ejecutable o
texto formateado. En este momento, el cuaderno contiene solo una celda de código vacía,
etiquetada como "En [1]:". Intente escribir print("¡Hola mundo!") en la celda y haga clic en el
botón de reproducción (consulte la Figura 2­4) o presione Shift­Enter. Esto envía la celda
actual al núcleo Python de este cuaderno, que lo ejecuta y devuelve el resultado. El resultado
se muestra debajo de la celda y, como ha llegado al final del cuaderno, se crea
automáticamente una nueva celda. Realice el recorrido por la interfaz de usuario desde el
menú Ayuda de Jupyter para aprender los conceptos básicos.
Figura 2­4. Cuaderno de notas de Python Hola mundo
Descargar los datos
Machine Translated by Google
En entornos típicos, sus datos estarían disponibles en una base de datos relacional (o algún
otro almacén de datos común) y distribuidos en varias tablas, documentos
y archivos. Para acceder a ellos, primero necesitaría obtener sus credenciales y
10
autorizaciones de acceso y familiarizarse con el esquema de datos. Sin embargo, en este
proyecto, las cosas son mucho más simples: simplemente descargará un único archivo
comprimido, housing.tgz, que contiene un archivo de valores separados por comas
(CSV) llamado housing.csv con todos los datos.
Puede utilizar su navegador web para descargar el archivo y ejecutar tar xzf
housing.tgz para descomprimirlo y extraer el archivo CSV, pero es preferible crear una
pequeña función para hacerlo. Tener una función que descargue los datos es útil en
particular si los datos cambian regularmente: puede escribir un pequeño script que use
la función para obtener los datos más recientes (o puede configurar un trabajo
programado para que lo haga automáticamente a intervalos regulares). Automatizar el
proceso de obtención de los datos también es útil si necesita instalar el conjunto de datos en
varias máquinas.
Aquí está la función para obtener los datos:
11
importar
sistema operativo
importar archivo tar importar urllib
DESCARGAR_RAÍZ = "https://raw.githubusercontent.com/ageron/handson­ml2/master/"
RUTA_DE_VIVIENDA = os.path.join("conjuntos de datos", "vivienda")
URL_DE_VIVIENDA = RAÍZ_DE_DESCARGA + "conjuntos_de_datos/vivienda/vivienda.tgz"
def fetch_housing_data(url_de_vivienda=URL_DE_VIVIENDA, ruta_de_vivienda=RUTA_DE_VIVIENDA):
os.makedirs(ruta_vivienda, exist_ok=True) ruta_tgz =
os.path.join(ruta_vivienda, "vivienda.tgz") urllib.request.urlretrieve(url_vivienda,
ruta_tgz) vivienda_tgz = tarfile.open(ruta_tgz)
vivienda_tgz.extractall(ruta=ruta_vivienda)
vivienda_tgz.close()
Ahora, cuando llama a fetch_housing_data(), crea un directorio datasets/housing en su
espacio de trabajo, descarga el archivo housing.tgz y extrae el archivo housing.csv en este
directorio.
Ahora carguemos los datos con pandas. Nuevamente, debes escribir una pequeña función
para cargar los datos:
Machine Translated by Google
importar pandas como pd
def carga_datos_de_vivienda(ruta_de_vivienda=RUTA_DE_VIVIENDA):
csv_path = os.path.join(ruta_vivienda, "vivienda.csv") return
pd.read_csv(ruta_csv)
Esta función devuelve un objeto pandas DataFrame que contiene todos los datos.
Eche un vistazo rápido a la estructura de datos
Echemos un vistazo a las cinco filas superiores utilizando el método head() de
DataFrame (ver Figura 2­5).
Figura 2­5. Las cinco primeras filas del conjunto de datos
Cada fila representa un distrito. Hay 10 atributos (puedes ver los primeros 6 en la captura
de pantalla): longitud, latitud, edad media de la vivienda, total de habitaciones,
total de dormitorios, población, hogares, ingresos medios, valor medio de la vivienda y
proximidad al océano.
El método info() es útil para obtener una descripción rápida de los datos, en
particular el número total de filas, el tipo de cada atributo y el número de valores no
nulos (ver Figura 2­6).
Machine Translated by Google
Figura 2­6. Información de la vivienda
Hay 20 640 instancias en el conjunto de datos, lo que significa que es bastante pequeño
según los estándares de aprendizaje automático, pero es perfecto para comenzar. Observe
que el atributo total_bedrooms tiene solo 20 433 valores no nulos, lo que significa que 207
distritos carecen de esta característica. Tendremos que ocuparnos de esto más adelante.
Todos los atributos son numéricos, excepto el campo ocean_proximity. Su tipo es objeto,
por lo que podría contener cualquier tipo de objeto Python. Pero como has cargado estos
datos desde un archivo CSV, sabes que debe ser un atributo de texto. Cuando miraste
las cinco primeras filas, probablemente hayas notado que los valores de la columna
ocean_proximity eran repetitivos, lo que significa que probablemente se trata de un atributo
categórico. Puedes averiguar qué categorías existen y cuántos distritos pertenecen a
cada categoría utilizando el método value_counts():
>>> vivienda["proximidad_al_océano"].value_counts()
<1H OCÉANO
9136
INTERIOR
6551
CERCA DEL OCÉANO
2658
CERCA DE LA BAHÍA
2290
ISLA 5
Nombre: ocean_proximity, tipo de dato: int64
Veamos los demás campos. El método describe() muestra un resumen de los atributos
numéricos (Figura 2­7).
Machine Translated by Google
Figura 2­7. Resumen de cada atributo numérico
Las filas de recuento, media, mínimo y máximo se explican por sí solas. Tenga en cuenta
que los valores nulos se ignoran (por lo tanto, por ejemplo, el recuento de total_bedrooms
es 20 433, no 20 640). La fila std muestra la desviación estándar, que mide qué tan
12
dispersos están los valores. Las filas 25 %, 50 % y 75 % muestran los
percentiles correspondientes: un percentil indica el valor por debajo del cual se
encuentra un porcentaje determinado de observaciones en un grupo de observaciones. Por
ejemplo, el 25 % de los distritos tienen una edad media de vivienda inferior a 18 años,
mientras que el 50 % son inferiores a 29 años y el 75 % son inferiores a 37 años. Estos
suelen denominarse percentil 25 (o primer cuartil), mediana y percentil 75 (o tercer
cuartil).
Otra forma rápida de hacerse una idea del tipo de datos con los que se está trabajando es
trazar un histograma para cada atributo numérico. Un histograma muestra la cantidad de
instancias (en el eje vertical) que tienen un rango de valores determinado (en el eje
horizontal). Puede trazar este atributo por separado o puede llamar al método hist() en
todo el conjunto de datos (como se muestra en el siguiente ejemplo de código) y trazará un
histograma para cada atributo numérico (consulte la Figura 2­8):
%matplotlib inline import
solo # en a Cuaderno Jupyter
matplotlib.pyplot como plt vivienda.hist(bins=50,
figsize=(20,15)) plt.show()
Machine Translated by Google
NOTA
El método hist() depende de Matplotlib, que a su vez depende de un backend gráfico
especificado por el usuario para dibujar en la pantalla. Por lo tanto, antes de poder trazar algo, debe
especificar qué backend debe utilizar Matplotlib. La opción más sencilla es utilizar el comando
mágico %matplotlib inline de Jupyter. Esto le indica a Jupyter que configure Matplotlib para que utilice
el backend de Jupyter. Los gráficos se representan luego dentro del propio notebook. Tenga en
cuenta que llamar a show() es opcional en un notebook de Jupyter, ya que Jupyter mostrará
automáticamente los gráficos cuando se ejecute una celda.
Figura 2­8. Un histograma para cada atributo numérico
Hay algunas cosas que podrías notar en estos histogramas:
1. En primer lugar, el atributo de ingresos medios no parece estar expresado en
dólares estadounidenses (USD). Después de consultar con el equipo que recopiló
los datos, le dicen que los datos se han escalado y limitado a 15 (en
realidad, 15,0001) para los ingresos medios más altos, y a 0,5 (en realidad,
0,4999) para los ingresos medios más bajos. Los números representan aproximadamente
Machine Translated by Google
decenas de miles de dólares (por ejemplo, 3 en realidad significa alrededor de $30.000).
Trabajar con atributos preprocesados es común en el aprendizaje automático y
no es necesariamente un problema, pero debes intentar comprender cómo se calcularon
los datos.
2. La edad media de las viviendas y el valor medio de las mismas también fueron
capped. Esto último puede ser un problema grave, ya que es su atributo de destino (sus
etiquetas). Sus algoritmos de aprendizaje automático pueden aprender que los precios
nunca superan ese límite. Debe consultar con su equipo de clientes (el equipo que
utilizará la salida de su sistema) para ver si esto es un problema o no. Si le dicen que
necesitan predicciones precisas incluso más allá de los $500,000, entonces tiene dos
opciones:
a. Reúna las etiquetas adecuadas para los distritos cuyas etiquetas fueron
tapado.
b. Eliminar esos distritos del conjunto de entrenamiento (y también de
el conjunto de prueba, ya que su sistema no debe evaluarse negativamente si
predice valores superiores a $500 000).
3. Estos atributos tienen escalas muy diferentes. Hablaremos de esto más adelante.
En este capítulo, exploramos el escalamiento de características.
4. Por último, muchos histogramas tienen una cola muy pesada: se extienden mucho más
hacia la derecha de la mediana que hacia la izquierda. Esto puede dificultar un poco la
detección de patrones por parte de algunos algoritmos de aprendizaje automático.
Intentaremos transformar estos atributos más adelante para obtener distribuciones
con forma de campana.
Esperamos que ahora comprenda mejor el tipo de datos con los que está tratando.
ADVERTENCIA
¡Espera! Antes de seguir mirando los datos, debes crear un conjunto de prueba, dejarlo a un lado y no
mirarlo nunca más.
Crear un conjunto de pruebas
Machine Translated by Google
Puede resultar extraño dejar de lado voluntariamente parte de los datos en esta etapa.
Después de todo, solo ha echado un vistazo rápido a los datos y seguramente debería aprender
mucho más sobre ellos antes de decidir qué algoritmos utilizar, ¿verdad? Esto es cierto, pero su cerebro es
un sistema de detección de patrones asombroso, lo que significa que es muy propenso al sobreajuste: si
observa el conjunto de prueba, puede tropezar con algún patrón aparentemente interesante en los datos
de prueba que lo lleve a seleccionar un tipo particular de modelo de aprendizaje automático. Cuando calcule el
error de generalización utilizando el conjunto de prueba, su estimación será demasiado optimista y lanzará
un sistema que no funcionará tan bien como se esperaba. Esto se llama sesgo de espionaje de datos .
Crear un conjunto de pruebas es teóricamente simple: elija algunas instancias al azar, generalmente el
20 % del conjunto de datos (o menos si su conjunto de datos es muy grande) y déjelas a un lado:
importar numpy como np
def split_train_test(datos, relación_de_prueba):
índices_barajados = np.random.permutation(len(datos))
tamaño_del_conjunto_de_prueba = int(len(datos) *
relación_de_prueba) índices_de_prueba =
índices_barajados[:tamaño_del_conjunto_de_prueba]
índices_de_entrenamiento = índices_barajados[tamaño_del_conjunto_de_prueba:] return datos.iloc[índices_de_entrenamiento
Luego puedes usar esta función de la siguiente manera: 13
>>> conjunto_de_entrenamiento, conjunto_de_prueba = split_train_test(vivienda, 0.2) >>>
len(conjunto_de_entrenamiento)
16512
>>> len(conjunto_de_prueba)
4128
Bueno, esto funciona, pero no es perfecto: si vuelves a ejecutar el programa, generará un conjunto de
pruebas diferente. Con el tiempo, tú (o tus algoritmos de aprendizaje automático) podrán ver el
conjunto de datos completo, que es lo que quieres evitar.
Una solución es guardar el conjunto de pruebas en la primera ejecución y luego cargarlo en
ejecuciones posteriores. Otra opción es configurar la semilla del generador de números aleatorios (por
14
ejemplo, con np.random.seed(42)) antes de llamar
Machine Translated by Google
np.random.permutation() para que siempre genere los mismos índices mezclados.
Pero ambas soluciones dejarán de funcionar la próxima vez que obtengas un conjunto de datos actualizado.
Para tener una división estable de entrenamiento/prueba incluso después de actualizar el conjunto de
datos, una solución común es usar el identificador de cada instancia para decidir si debe ir o no en el
conjunto de prueba (suponiendo que las instancias tienen un identificador único e inmutable).
Por ejemplo, podría calcular un hash del identificador de cada instancia y colocar esa instancia
en el conjunto de prueba si el hash es menor o igual al 20 % del valor hash máximo. Esto garantiza que el
conjunto de prueba se mantendrá consistente en múltiples ejecuciones, incluso si actualiza el conjunto de
datos. El nuevo conjunto de prueba contendrá el 20 % de las nuevas instancias, pero no contendrá
ninguna instancia que estuviera previamente en el conjunto de entrenamiento.
He aquí una posible implementación:
Desde zlib importa crc32
def test_set_check(identificador, relación_de_prueba): return
crc32(np.int64(identificador)) & 0xffffffff < relación_de_prueba * 2**32
def split_train_test_by_id(datos, relación_prueba, columna_id): ids =
datos[columna_id]
en_conjunto_prueba = ids.apply(lambda id_: conjunto_prueba_check(id_, relación_prueba)) return
datos.loc[~en_conjunto_prueba], datos.loc[en_conjunto_prueba]
Lamentablemente, el conjunto de datos de vivienda no tiene una columna de identificador. La solución
más sencilla es utilizar el índice de fila como identificador:
# añade un columna `índice`
vivienda_con_id = vivienda.reset_index()
conjunto_de_entrenamiento, conjunto_de_prueba = dividir_prueba_de_entrenamiento_por_id(vivienda_con_id, 0.2, "índice")
Si utiliza el índice de fila como un identificador único, debe asegurarse de que los datos nuevos se
agreguen al final del conjunto de datos y que nunca se elimine ninguna fila. Si esto no es posible,
puede intentar utilizar las características más estables para crear un identificador único. Por ejemplo, se
garantiza que la latitud y la longitud de un distrito serán estables durante unos pocos millones de años, por
lo que podría combinarlas en un identificador como el siguiente:15
Machine Translated by Google
vivienda_con_id["id"] = vivienda["longitud"] * 1000 + vivienda["latitud"] conjunto_de_entrenamiento,
conjunto_de_prueba = dividir_prueba_de_entrenamiento_por_id(vivienda_con_id, 0.2, "id")
Scikit­Learn ofrece algunas funciones para dividir conjuntos de datos en múltiples subconjuntos
de varias maneras. La función más simple es train_test_split(), que hace prácticamente lo
mismo que la función split_train_test(), con un par de características adicionales. En
primer lugar, hay un parámetro random_state que le permite establecer la semilla del
generador aleatorio. En segundo lugar, puede pasarle múltiples conjuntos de datos con una
cantidad idéntica de filas y los dividirá en los mismos índices (esto es muy útil, por ejemplo, si
tiene un DataFrame separado para las etiquetas):
desde sklearn.model_selection importar train_test_split
conjunto_de_entrenamiento, conjunto_de_prueba = división_de_prueba_de_entrenamiento(alojamiento,
tamaño_de_prueba=0.2, estado_aleatorio=42)
Hasta ahora hemos considerado métodos de muestreo puramente aleatorios. Esto generalmente
está bien si su conjunto de datos es lo suficientemente grande (especialmente en relación
con el número de atributos), pero si no lo es, corre el riesgo de introducir un sesgo de
muestreo significativo. Cuando una empresa de encuestas decide llamar a 1000 personas
para hacerles algunas preguntas, no elige simplemente a 1000 personas al azar en una guía
telefónica. Intentan asegurarse de que estas 1000 personas sean representativas de toda
la población. Por ejemplo, la población de EE. UU. está compuesta por un 51,3 % de
mujeres y un 48,7 % de hombres, por lo que una encuesta bien realizada en EE. UU. intentaría
mantener esta proporción en la muestra: 513 mujeres y 487 hombres. Esto se llama
muestreo estratificado: la población se divide en subgrupos homogéneos llamados
estratos, y se muestrea el número correcto de instancias de cada estrato para garantizar
que el conjunto de prueba sea representativo de la población general. Si quienes llevaron a
cabo la encuesta usaron un muestreo puramente aleatorio, habría una probabilidad de alrededor
del 12% de muestrear un grupo de prueba sesgado que tuviera menos del 49% de mujeres
o más del 54% de mujeres. De cualquier manera, los resultados de la encuesta estarían
significativamente sesgados.
Supongamos que ha hablado con expertos que le han dicho que el ingreso medio es un
atributo muy importante para predecir los precios medios de la vivienda. Es posible que
desee asegurarse de que el conjunto de prueba sea representativo de las distintas categorías
de ingresos en todo el conjunto de datos. Dado que el ingreso medio es un valor numérico continuo
Machine Translated by Google
Para crear un atributo de categoría de ingresos, primero debe crear un atributo de categoría
de ingresos. Veamos el histograma de ingresos medios más de cerca (en la Figura 2­8): la
mayoría de los valores de ingresos medios se agrupan en torno a 1,5 a 6 (es decir, $15 000–
$60 000), pero algunos ingresos medios van mucho más allá de 6. Es importante tener una
cantidad suficiente de instancias en su conjunto de datos para cada estrato, o de lo
contrario la estimación de la importancia de un estrato puede estar sesgada. Esto significa que
no debe tener demasiados estratos y que cada estrato debe ser lo suficientemente grande.
El siguiente código utiliza la función pd.cut() para crear un atributo de categoría de ingresos
con cinco categorías (etiquetadas del 1 al 5): la categoría 1 va de 0 a 1,5 (es decir, menos
de $15 000), la categoría 2 de 1,5 a 3, y así sucesivamente:
vivienda["ingreso_cat"] = pd.cut(vivienda["ingreso_medio"],
contenedores=[0, 1,5, 3,0, 4,5, 6, np.inf], etiquetas=[1,
2, 3, 4, 5])
Estas categorías de ingresos están representadas en la Figura 2­9:
vivienda["categoría_de_ingresos"].hist()
Figura 2­9. Histograma de categorías de ingresos
Machine Translated by Google
Ahora está listo para realizar un muestreo estratificado según la categoría de ingresos.
Para esto puedes utilizar la clase StratifiedShuffleSplit de Scikit­Learn:
desde sklearn.model_selection importar StratifiedShuffleSplit
split = StratifiedShuffleSplit(n_splits=1, tamaño_de_prueba=0.2, estado_aleatorio=42) para índice_de_entrenamiento,
índice_de_prueba en split.split(vivienda, vivienda["categoría_de_ingreso"]):
strat_train_set = vivienda.loc[índice_entrenamiento] strat_test_set
= vivienda.loc[índice_prueba]
Veamos si esto funcionó como se esperaba. Puedes empezar por observar las proporciones de las
categorías de ingresos en el conjunto de prueba:
>>> conjunto_de_pruebas_estrat["categoría_de_ingresos"].cuenta_valores() / len(conjunto_de_pruebas_estrat) 3
0,350533
2
0,318798
4
0,176357
5
0,114583
1
0,039729
Nombre: income_cat, tipo de dato: float64
Con un código similar, se pueden medir las proporciones de las categorías de ingresos en el conjunto
de datos completo. La Figura 2­10 compara las proporciones de las categorías de ingresos en el
conjunto de datos general, en el conjunto de prueba generado con muestreo estratificado y en un
conjunto de prueba generado con muestreo puramente aleatorio. Como se puede ver, el conjunto
de prueba generado con muestreo estratificado tiene proporciones de categorías de ingresos casi
idénticas a las del conjunto de datos completo, mientras que el conjunto de prueba generado con
muestreo puramente aleatorio está sesgado.
Machine Translated by Google
Figura 2­10. Comparación del sesgo de muestreo entre el muestreo estratificado y el muestreo puramente aleatorio
Ahora debes eliminar el atributo income_cat para que los datos vuelvan a su estado original:
para set_ en (conjunto_de_entrenamiento_estratificado, conjunto_de_prueba_estratificado):
set_.drop("ingreso_cat", eje=1, inplace=True)
Dedicamos bastante tiempo a la generación de conjuntos de pruebas por una buena razón: se trata de una
parte a menudo descuidada, pero fundamental, de un proyecto de aprendizaje automático. Además,
muchas de estas ideas serán útiles más adelante cuando analicemos la validación cruzada.
Ahora es el momento de pasar a la siguiente etapa: explorar los datos.
Descubra y visualice los datos para obtener ganancias
Perspectivas
Hasta ahora, solo ha echado un vistazo rápido a los datos para obtener una idea general del tipo
de datos que está manipulando. Ahora, el objetivo es profundizar un poco más.
En primer lugar, asegúrese de haber dejado de lado el conjunto de prueba y de que solo está explorando el
conjunto de entrenamiento. Además, si el conjunto de entrenamiento es muy grande, es posible que desee
tomar una muestra de un conjunto de exploración para que las manipulaciones sean fáciles y rápidas. En
nuestro caso, el conjunto es bastante pequeño, por lo que puede trabajar directamente en el conjunto completo.
Creemos una copia para que pueda jugar con ella sin dañar el conjunto de entrenamiento:
vivienda = strat_train_set.copy()
Machine Translated by Google
Visualización de datos geográficos
Dado que existe información geográfica (latitud y longitud), es una buena idea crear
un diagrama de dispersión de todos los distritos para visualizar los datos (Figura 2­11):
vivienda.plot(kind="dispersión", x="longitud", y="latitud")
Figura 2­11. Diagrama de dispersión geográfica de los datos
Parece que esto es California, pero más allá de eso es difícil ver un patrón
particular. Si se establece la opción alfa en 0,1, resulta mucho más fácil visualizar
los lugares donde hay una alta densidad de puntos de datos (Figura 2­12):
vivienda.plot(kind="dispersión", x="longitud", y="latitud", alfa=0,1)
Machine Translated by Google
Figura 2­12. Una mejor visualización que resalta las áreas de alta densidad
Ahora está mucho mejor: se pueden ver claramente las áreas de alta densidad, es decir, el Área de la Bahía y
alrededor de Los Ángeles y San Diego, además de una larga línea de densidad bastante alta en el Valle Central, en
particular alrededor de Sacramento y Fresno.
Nuestros cerebros son muy buenos para detectar patrones en imágenes, pero es posible que
tengas que jugar con los parámetros de visualización para que los patrones se destaquen.
Ahora veamos los precios de las viviendas (Figura 2­13). El radio de cada círculo representa
la población del distrito (opción s) y el color representa el precio (opción c). Usaremos un
mapa de colores predefinido (opción cmap) llamado jet,
que va del azul (valores bajos) al rojo (precios altos):
16
vivienda.plot(kind="dispersión", x="longitud", y="latitud", alpha=0.4, s=vivienda["población"]/
100, label="población", figsize=(10,7), c="valor_medio_de_la_casa",
cmap=plt.get_cmap("jet"), colorbar=True,
) plt.leyenda()
Machine Translated by Google
Figura 2­13. Precios de la vivienda en California: el rojo es caro, el azul es barato, los círculos más grandes indican
áreas con mayor población
Esta imagen te indica que los precios de las viviendas están muy relacionados con la
ubicación (por ejemplo, cerca del océano) y con la densidad de población, como
probablemente ya sabías. Un algoritmo de agrupamiento debería ser útil para detectar el
grupo principal y para agregar nuevas características que midan la proximidad a los centros
de los grupos. El atributo de proximidad al océano también puede ser útil, aunque en el norte
de California los precios de las viviendas en los distritos costeros no son demasiado altos,
por lo que no es una regla simple.
Buscando correlaciones
Dado que el conjunto de datos no es demasiado grande, puedes calcular fácilmente
el coeficiente de correlación estándar (también llamado r de Pearson ) entre cada
par de atributos utilizando el método corr():
corr_matrix = vivienda.corr()
Machine Translated by Google
Ahora veamos en qué medida se correlaciona cada atributo con el precio medio de la casa.
valor:
>>> corr_matrix["valor_medio_de_la_casa"].sort_values(ascending=False)
1.000000
valor_medio_de_la_casa
ingreso_medio
0,687170
0,135231
total_de_habitaciones
0,114220
0,064702
0,047865
­0,026699
­0,047279
edad_mediana_de_la_vivienda hogares
total_de_dormitorios población longitud latitud
­0,142826
Nombre: valor_de_la_casa_mediana, tipo_de_datos: float64
El coeficiente de correlación varía de –1 a 1. Cuando está cerca de 1, significa
que existe una fuerte correlación positiva; por ejemplo, la casa mediana
El valor tiende a subir cuando el ingreso medio sube. Cuando el coeficiente es
cerca de ­1, significa que hay una fuerte correlación negativa; se puede ver una
Pequeña correlación negativa entre la latitud y el valor medio de la vivienda.
(es decir, los precios tienen una ligera tendencia a bajar cuando se viaja al norte). Finalmente,
Los coeficientes cercanos a 0 significan que no existe correlación lineal. Figura 2­14
muestra varios gráficos junto con el coeficiente de correlación entre ellos
ejes horizontal y vertical.
Figura 2­14. Coeficiente de correlación estándar de varios conjuntos de datos (fuente: Wikipedia; dominio público)
imagen)
Machine Translated by Google
ADVERTENCIA
El coeficiente de correlación solo mide correlaciones lineales (“si x sube, entonces y
generalmente sube/baja”). Puede pasar por alto por completo las relaciones no lineales (por
ejemplo, “si x está cerca de 0, entonces y generalmente sube”). Observe cómo todos los gráficos
de la fila inferior tienen un coeficiente de correlación igual a 0, a pesar del hecho de que sus ejes
claramente no son independientes: estos son ejemplos de relaciones no lineales. Además, la
segunda fila muestra ejemplos donde el coeficiente de correlación es igual a 1 o ­1; observe que esto
no tiene nada que ver con la pendiente. Por ejemplo, su altura en pulgadas tiene un coeficiente de
correlación de 1 con su altura en pies o en nanómetros.
Otra forma de comprobar la correlación entre atributos es utilizar la función
scatter_matrix() de pandas, que grafica cada atributo numérico en relación con
todos los demás atributos numéricos. Como ahora hay 11 atributos numéricos,
2
obtendríamos 11 = 121 gráficos, que no cabrían en una página, así que
centrémonos en unos pocos atributos prometedores que parecen estar más
correlacionados con el valor medio de la vivienda (Figura 2­15):
desde pandas.plotting importa scatter_matrix
atributos = ["valor_medio_de_la_casa", "ingreso_medio", "habitaciones_totales",
"edad_mediana_de_la_vivienda"]
matriz_de_dispersión(vivienda[atributos], tamaño_de_la_figura=(12, 8))
Machine Translated by Google
Figura 2­15. Esta matriz de dispersión representa gráficamente cada atributo numérico en relación con cada uno de los demás
atributos numéricos, además de un histograma de cada atributo numérico.
La diagonal principal (de arriba a la izquierda a abajo a la derecha) estaría llena de líneas rectas
si Pandas representara gráficamente cada variable en relación a sí misma, lo que no sería muy
útil. Por lo tanto, Pandas muestra un histograma de cada atributo (hay otras opciones
disponibles; consulte la documentación de Pandas para obtener más detalles).
El atributo más prometedor para predecir el valor medio de la vivienda es el ingreso medio, así
que acerquémonos a su diagrama de correlación (Figura 2­16):
vivienda.plot(kind="scatter", x="ingreso_medio", y="valor_medio_de_la_casa", alfa=0,1)
Machine Translated by Google
Figura 2­16. Ingresos medios versus valor medio de la vivienda
Este gráfico revela algunas cosas. En primer lugar, la correlación es realmente muy fuerte; se
puede ver claramente la tendencia al alza y los puntos no están demasiado dispersos. En
segundo lugar, el límite de precio que notamos antes es claramente visible como una línea
horizontal en $500,000. Pero este gráfico revela otras líneas rectas menos obvias: una línea
horizontal alrededor de $450,000, otra alrededor de $350,000, tal vez una alrededor de
$280,000 y algunas más por debajo de esa. Es posible que desee intentar eliminar los
distritos correspondientes para evitar que sus algoritmos aprendan a reproducir estas
peculiaridades de los datos.
Experimentación con combinaciones de atributos Esperamos que las
secciones anteriores le hayan dado una idea de algunas formas en las que puede
explorar los datos y obtener información. Identificó algunas peculiaridades de los datos que
quizás desee limpiar antes de alimentar los datos a un algoritmo de aprendizaje
automático y encontró correlaciones interesantes entre los atributos, en particular con
el atributo de destino. También notó que algunos atributos tienen una distribución con mucha
cola, por lo que es posible que desee transformarlos (por ejemplo, calculando su
logaritmo). Por supuesto, su rendimiento variará considerablemente con cada proyecto,
pero las ideas generales son similares.
Machine Translated by Google
Una última cosa que quizás quieras hacer antes de preparar los datos para Machine
Los algoritmos de aprendizaje consisten en probar distintas combinaciones de atributos. Por ejemplo,
El número total de habitaciones en un distrito no es muy útil si no lo sabes.
¿Cuántos hogares hay? Lo que realmente quieres es el número de habitaciones.
por hogar. De manera similar, el número total de dormitorios por sí solo no es muy
Útil: probablemente quieras compararlo con el número de habitaciones. Y el
La población por hogar también parece una combinación de atributos interesante
para mirar. Vamos a crear estos nuevos atributos:
vivienda["habitaciones_por_hogar"] = vivienda["habitaciones_totales"]/vivienda["hogares"]
vivienda["dormitorios_por_habitación"] =
vivienda["total_dormitorios"]/vivienda["total_habitaciones"]
vivienda["población_por_hogar"]=vivienda["población"]/vivienda["hogares
"]
Y ahora veamos nuevamente la matriz de correlación:
>>> corr_matrix = vivienda.corr()
>>> corr_matrix["valor_medio_de_la_casa"].sort_values(ascending=False)
1.000000
valor_medio_de_la_casa
ingreso_medio
0,687160
habitaciones_por_hogar
0,146285
total_de_habitaciones
0,135097
edad_mediana_de_la_vivienda
hogares
0,114110
total_de_dormitorios
0,047689
0,064506
población_por_hogar ­0.021985
población ­0,026920
longitud latitud
dormitorios
­0,047432
por habitación Nombre:
­0,259984
­0,142724
valor_mediano_de_la_casa, tipo_de_datos: float64
¡No está mal! El nuevo atributo beds_per_room está mucho más correlacionado
siendo el valor medio de la vivienda mayor que el número total de habitaciones o dormitorios.
Aparentemente, las casas con una proporción menor de habitaciones por dormitorio tienden a ser más
caro. El número de habitaciones por hogar también es más informativo que
el número total de habitaciones en un distrito; obviamente, cuanto más grandes sean las casas,
Son más caros.
Esta ronda de exploración no tiene por qué ser absolutamente exhaustiva; el punto es
para empezar con el pie derecho y obtener rápidamente conocimientos que le ayudarán a obtener un
Machine Translated by Google
El primer prototipo es razonablemente bueno, pero se trata de un proceso iterativo: una vez que se tiene
un prototipo en funcionamiento, se puede analizar su resultado para obtener más información y volver a
este paso de exploración.
Preparar los datos para los algoritmos de aprendizaje automático Es hora de preparar
los datos para los
algoritmos de aprendizaje automático. En lugar de hacerlo manualmente, conviene escribir funciones para
este fin por varias buenas razones:
Esto le permitirá reproducir estas transformaciones fácilmente en cualquier conjunto de datos
(por ejemplo, la próxima vez que obtenga un nuevo conjunto de datos).
Gradualmente construirás una biblioteca de funciones de transformación que podrás
reutilizar en proyectos futuros.
Puede utilizar estas funciones en su sistema en vivo para transformar los nuevos datos antes
de introducirlos en sus algoritmos.
Esto le permitirá probar fácilmente varias transformaciones y ver qué
combinación de transformaciones funciona mejor.
Pero primero, volvamos a un conjunto de entrenamiento limpio (copiando strat_train_set una vez
más). También separemos los predictores y las etiquetas, ya que no necesariamente queremos aplicar
las mismas transformaciones a los predictores y los valores objetivo (tenga en cuenta que drop() crea
una copia de los datos y no afecta a strat_train_set):
vivienda = strat_train_set.drop("valor_medio_de_la_casa", axis=1)
etiquetas_de_vivienda = strat_train_set["valor_medio_de_la_casa"].copy()
Limpieza de datos La
mayoría de los algoritmos de aprendizaje automático no pueden funcionar con características faltantes,
por lo que crearemos algunas funciones para encargarnos de ellas. Vimos anteriormente que la
Machine Translated by Google
El atributo total_bedrooms tiene algunos valores faltantes, así que vamos a solucionarlo. Tienes
tres opciones:
1. Deshacerse de los distritos correspondientes.
2. Deshágase de todo el atributo.
3. Establezca los valores en algún valor (cero, la media, la mediana, etc.).
Puede lograr esto fácilmente usando dropna(), drop() y dropFrame de DataFrame.
métodos fillna():
vivienda.dropna(subset=["total_dormitorios"])
vivienda.drop("total_dormitorios", axis=1) mediana =
vivienda["total_dormitorios"].median()
vivienda["total_dormitorios"].fillna(median, inplace=True)
#
Opción #
Opción #
Opción
1
2
3
Si elige la opción 3, debe calcular el valor mediano en el entrenamiento.
Establezca y utilícelo para completar los valores faltantes en el conjunto de entrenamiento. No olvide guardar
el valor medio que has calculado. Lo necesitarás más adelante para reemplazar
valores faltantes en el conjunto de prueba cuando desea evaluar su sistema, y también
una vez que el sistema se activa para reemplazar los valores faltantes en los nuevos datos.
Scikit­Learn proporciona una clase útil para solucionar los valores faltantes:
SimpleImputer. Aquí te explicamos cómo usarlo. Primero, debes crear un
Instancia de SimpleImputer, especificando que desea reemplazar cada atributo
valores faltantes con la mediana de ese atributo:
desde sklearn.impute importar SimpleImputer
imputador = SimpleImputer(estrategia="mediana")
Dado que la mediana solo se puede calcular a partir de atributos numéricos, es necesario
crea una copia de los datos sin el atributo de texto ocean_proximity:
número_de_vivienda = vivienda.drop("proximidad_al_océano", eje=1)
Ahora puedes ajustar la instancia del imputador a los datos de entrenamiento usando fit()
método:
Machine Translated by Google
imputer.fit(número_de_vivienda)
El imputador simplemente ha calculado la mediana de cada atributo y la ha almacenado.
Resultado en su variable de instancia statistics_. Solo el total_bedrooms
El atributo tenía valores faltantes, pero no podemos estar seguros de que no habrá ninguno.
valores faltantes en los nuevos datos después de que el sistema se pone en marcha, por lo que es más seguro aplicar
el imputador a todos los atributos numéricos:
>>> imputer.estadísticas_
array([ ­118.51 34.26 >>> ,
, 29.
, 2119.5
, 433.
, 1164.
, 408.
, 3.5409])
, 29.
, 2119.5
, 433.
, 1164.
, 408.
, 3.5409])
número_de_vivienda.mediana().valores
matriz([­118,51 34,26
,
Ahora puedes usar este imputador “entrenado” para transformar el conjunto de entrenamiento mediante
Reemplazando los valores faltantes con las medianas aprendidas:
X = imputador.transform(número_de_vivienda)
El resultado es una matriz NumPy simple que contiene las características transformadas. Si
Si quieres volver a colocarlo en un DataFrame de pandas, es simple:
vivienda_tr = pd.DataFrame(X, columnas=vivienda_num.columnas,
índice=num_vivienda.índice)
Machine Translated by Google
DISEÑO DE SCIKIT­LEARN
La API de Scikit­Learn está muy bien diseñada. Estos son los principios de diseño
17
principales :
Consistencia
Todos los objetos comparten una interfaz consistente y sencilla:
Estimadores
Cualquier objeto que pueda estimar algunos parámetros en función de un conjunto
de datos se denomina estimador (por ejemplo, un imputador es un
estimador). La estimación en sí se realiza mediante el método fit() y solo toma un
conjunto de datos como parámetro (o dos para algoritmos de aprendizaje
supervisado; el segundo conjunto de datos contiene las etiquetas). Cualquier
otro parámetro necesario para guiar el proceso de estimación se considera un
hiperparámetro (como la estrategia de un imputador) y debe configurarse como
una variable de instancia (generalmente a través de un parámetro constructor).
Transformadores
Algunos estimadores (como un imputador) también pueden transformar un
conjunto de datos; estos se denominan transformadores. Una vez más, la
API es simple: la transformación se realiza mediante el método transform()
con el conjunto de datos a transformar como parámetro. Devuelve el conjunto de
datos transformado. Esta transformación generalmente se basa en los parámetros
aprendidos, como es el caso de un imputador. Todos los transformadores también
tienen un método conveniente llamado fit_transform() que es equivalente a llamar
a fit() y luego a transform() (pero a veces fit_transform() está optimizado y se ejecuta
mucho más rápido).
Predictores
Por último, algunos estimadores, dado un conjunto de datos, son capaces de
hacer predicciones; se denominan predictores. Por ejemplo, el modelo
de regresión lineal del capítulo anterior era un predictor: dado el PIB per cápita de
un país, predecía la satisfacción con la vida. Un predictor tiene un método predict()
que toma un conjunto de datos de nuevos
Machine Translated by Google
instancias y devuelve un conjunto de datos de predicciones correspondientes.
También tiene un método score() que mide la calidad de las predicciones,
dado un conjunto de prueba (y las etiquetas correspondientes, en el caso de
algoritmos de aprendizaje supervisado).
18
Inspección
Todos los hiperparámetros del estimador son accesibles directamente a través de variables
de instancia públicas (por ejemplo, imputer.strategy), y todos los parámetros aprendidos
del estimador son accesibles a través de variables de instancia públicas con un sufijo de
guión bajo (por ejemplo, imputer.statistics_).
No proliferación de clases
Los conjuntos de datos se representan como matrices de NumPy o matrices dispersas
de SciPy, en lugar de clases caseras. Los hiperparámetros son simplemente cadenas o
números normales de Python.
Composición
Los bloques de construcción existentes se reutilizan tanto como sea posible. Por ejemplo,
es fácil crear un estimador de tuberías a partir de una secuencia arbitraria de transformadores
seguida de un estimador final, como veremos.
Valores predeterminados sensatos
Scikit­Learn proporciona valores predeterminados razonables para la mayoría de los
parámetros, lo que facilita la creación rápida de un sistema de trabajo de referencia.
Manejo de texto y atributos categóricos Hasta ahora solo hemos tratado
con atributos numéricos, pero ahora veamos los atributos de texto. En este conjunto de datos, solo
hay uno: el atributo ocean_proximity.
Veamos su valor para las primeras 10 instancias:
>>> vivienda_cat = vivienda[["proximidad_al_océano"]] >>> vivienda_cat.head(10)
proximidad_al_océano <1H OCÉANO
17606
18632
<1H OCÉANO
Machine Translated by Google
14650
CERCA DEL OCÉANO
3230
INTERIOR
3555
<1H OCÉANO
19480
INTERIOR
8879
<1H OCÉANO
13685
INTERIOR
4937
<1H OCÉANO
4861
<1H OCÉANO
No se trata de un texto arbitrario: hay una cantidad limitada de valores posibles, cada uno de los cuales representa una
categoría. Por lo tanto, este atributo es un atributo categórico. La mayoría de los algoritmos de aprendizaje automático
prefieren trabajar con números, así que vamos a convertir estas categorías de texto a números. Para ello, podemos
utilizar la clase OrdinalEncoder de Scikit­Learn 19 :
>>> de sklearn.preprocessing importar OrdinalEncoder >>> codificador_ordinal =
OrdinalEncoder() >>> vivienda_gato_codificado =
codificador_ordinal.fit_transform(vivienda_gato) >>> vivienda_gato_codificado[:10] array([[0.], [0.], [4.], [1.], [0.], [1.],
[0.], [1.], [0.], [0.]])
Puede obtener la lista de categorías mediante la variable de instancia categorías_. Es una
lista que contiene una matriz unidimensional de categorías para cada atributo categórico
(en este caso, una lista que contiene una única matriz, ya que solo hay un atributo
categórico):
>>> ordinal_encoder.categories_ [array(['<1H
OCÉANO', 'INTERIOR', 'ISLA', 'CERCA DE LA BAHÍA', 'CERCA DEL OCÉANO'], dtype=object)]
Un problema con esta representación es que los algoritmos de ML asumirán que dos valores
cercanos son más similares que dos valores distantes. Esto puede estar bien en algunos
casos (por ejemplo, para categorías ordenadas como "malo", "promedio", "bueno" y
Machine Translated by Google
“excelente”), pero obviamente no es el caso de la columna ocean_proximity (por ejemplo, las categorías 0 y 4 son
claramente más similares que las categorías 0 y 1). Para solucionar este problema, una solución común es crear un
atributo binario por categoría: un atributo igual a 1 cuando la categoría es “<1H OCEAN” (y 0 en caso contrario), otro
atributo igual a 1 cuando la categoría es “INLAND” (y 0 en caso contrario), y así sucesivamente. Esto se llama
codificación one­hot, porque solo un atributo será igual a 1 (hot), mientras que los demás serán 0 (cold).
Los nuevos atributos a veces se denominan atributos ficticios . Scikit­Learn proporciona una clase OneHotEncoder
para convertir valores categóricos en vectores one­ 20 hot:
>>> de sklearn.preprocessing importar OneHotEncoder >>>
cat_encoder = OneHotEncoder() >>>
vivienda_cat_1hot = cat_encoder.fit_transform(vivienda_cat) >>>
vivienda_cat_1hot
<16512x5 matriz dispersa del tipo '<clase 'numpy.float64'>'
con 16512 elementos almacenados en formato de fila dispersa comprimida>
Tenga en cuenta que la salida es una matriz dispersa de SciPy, en lugar de una matriz NumPy.
Esto es muy útil cuando tienes atributos categóricos con miles de categorías. Después de la
codificación one­hot, obtenemos una matriz con miles de columnas, y la matriz está llena
de ceros, excepto por un solo 1 por fila. Usar toneladas de memoria principalmente para
almacenar ceros sería un gran desperdicio, por lo que, en cambio, una matriz dispersa solo
almacena la ubicación de los elementos distintos de cero. Puedes usarla principalmente como una
21
matriz 2D normal, pero si realmente quieres convertirla en una matriz NumPy (densa),
simplemente llama al método toarray():
>>> vivienda_cat_1hot.toarray()
matriz([[1., 0., 0., 0., 0.], [1., 0., 0.,
0., 0.], [0., 0., 0., 0., 1.],
...,
[0., 1., 0., 0., 0.], [1., 0., 0.,
0., 0.], [0., 0., 0., 1., 0.] ])
Una vez más, puedes obtener la lista de categorías utilizando la variable de instancia
categorías_ del codificador:
Machine Translated by Google
>>> cat_encoder.categories_ [array(['<1H
OCÉANO', 'INTERIOR', 'ISLA', 'CERCA DE LA BAHÍA', 'CERCA DEL OCÉANO'], dtype=object)]
CONSEJO
Si un atributo categórico tiene una gran cantidad de categorías posibles (por ejemplo, código de país,
profesión, especie), la codificación one­hot generará una gran cantidad de características de
entrada. Esto puede ralentizar el entrenamiento y degradar el rendimiento. Si esto sucede, es posible
que desee reemplazar la entrada categórica con características numéricas útiles relacionadas con las
categorías: por ejemplo, puede reemplazar la característica ocean_proximity con la distancia al
océano (de manera similar, un código de país podría reemplazarse con la población del país y el PIB
per cápita). Alternativamente, puede reemplazar cada categoría con un vector de baja dimensión que se
pueda aprender llamado incrustación. La representación de cada categoría se aprendería durante el
entrenamiento. Este es un ejemplo de aprendizaje de representación (consulte los Capítulos 13 y 17
para obtener más detalles).
Transformadores personalizados
Aunque Scikit­Learn proporciona muchos transformadores útiles, necesitarás escribir los tuyos
propios para tareas como operaciones de limpieza personalizadas o la combinación de
atributos específicos. Querrás que tu transformador funcione sin problemas con las
funcionalidades de Scikit­Learn (como las canalizaciones) y, dado que Scikit­Learn se basa en
tipado de pato (no herencia), todo lo que necesitas hacer es crear una clase e implementar
tres métodos: fit() (que devuelve self), transform() y fit_transform().
Puedes obtener el último de forma gratuita simplemente agregando TransformerMixin como
clase base. Si agregas BaseEstimator como clase base (y evitas *args y **kargs en tu
constructor), también obtendrás dos métodos adicionales (get_params() y
set_params()) que serán útiles para el ajuste automático de hiperparámetros.
Por ejemplo, aquí hay una pequeña clase de transformador que agrega los atributos
combinados que discutimos anteriormente:
desde sklearn.base importar BaseEstimator, TransformerMixin
habitaciones_ix, dormitorios_ix, población_ix, hogares_ix = 3, 4, 5, 6
Machine Translated by Google
clase CombinedAttributesAdder(BaseEstimator, TransformerMixin):
def __init__(self, agregar_dormitorios_por_habitación = True):
# No
*argumentos
o
**cargos
self.add_dormitorios_por_habitación = agregar_dormitorios_por_habitación
def fit(self, X, y=Ninguno):
devuelve autodef
nada más #
a
hacer
transform(self, X, y = Ninguno):
habitaciones_por_hogar = X[:, habitaciones_ix] / X[:, hogares_ix]
población_por_hogar = X[:, población_ix] / X[:, hogares_ix]
Si se agregan habitaciones por habitación:
dormitorios_por_habitación = X[:, dormitorios_ix] / X[:, habitaciones_ix]
devuelve np.c_[X, habitaciones_por_hogar, población_por_hogar,
[dormitorios_por_habitación]
demás:
devuelve np.c_[X, habitaciones_por_hogar, población_por_hogar]
attr_adder = CombinedAttributesAdder(agregar_dormitorios_por_habitación=Falso)
vivienda_extra_attribs = attr_adder.transform(vivienda.valores)
En este ejemplo, el transformador tiene un hiperparámetro,
add_bedrooms_per_room, establecido en Verdadero de manera predeterminada (a menudo es útil proporcionar
valores predeterminados razonables). Este hiperparámetro le permitirá averiguarlo fácilmente.
si agregar este atributo ayuda a los algoritmos de aprendizaje automático o no.
De manera más general, puede agregar un hiperparámetro para controlar cualquier preparación de datos.
paso del que no estás 100% seguro. Cuanto más automatices estos datos
pasos de preparación, cuantas más combinaciones puedas probar automáticamente,
haciendo que sea mucho más probable que encuentres una gran combinación (y
ahorrándote mucho tiempo).
Escalado de características
Una de las transformaciones más importantes que debes aplicar a tus datos es
escalado de características. Con pocas excepciones, los algoritmos de aprendizaje automático no
funcionan bien cuando los atributos numéricos de entrada tienen escalas muy diferentes.
Este es el caso de los datos de vivienda: el número total de habitaciones varía entre
alrededor de 6 a 39.320, mientras que los ingresos medios sólo varían de 0 a 15. Nota
que generalmente no es necesario escalar los valores objetivo.
Hay dos formas comunes de lograr que todos los atributos tengan la misma escala: escala mínima­máxima y
estandarización.
Machine Translated by Google
El escalado de mínimo a máximo (muchas personas lo llaman normalización) es el más simple:
los valores se desplazan y se reescalan de modo que terminen en un rango de 0 a 1. Para ello,
restamos el valor mínimo y lo dividimos por el máximo menos el mínimo. Scikit­Learn
proporciona un transformador llamado MinMaxScaler para esto. Tiene un
hiperparámetro feature_range que le permite cambiar el rango si, por alguna razón, no
desea 0–1.
La estandarización es diferente: primero se resta el valor medio (por lo que los valores
estandarizados siempre tienen una media cero) y luego se divide por la desviación estándar
para que la distribución resultante tenga una varianza unitaria. A diferencia del escalamiento
mínimo­máximo, la estandarización no limita los valores a un rango específico, lo que
puede ser un problema para algunos algoritmos (por ejemplo, las redes neuronales a
menudo esperan un valor de entrada que va de 0 a 1). Sin embargo, la estandarización se ve
mucho menos afectada por los valores atípicos. Por ejemplo, supongamos que un distrito
tuviera un ingreso medio igual a 100 (por error). El escalamiento mínimo­máximo reduciría
entonces todos los demás valores de 0 a 15 a 0 a 0,15, mientras que la estandarización no se vería muy afectad
Scikit­Learn proporciona un transformador llamado StandardScaler para la
estandarización.
ADVERTENCIA
Al igual que con todas las transformaciones, es importante ajustar los escaladores solo a los datos de entrenamiento, no
al conjunto de datos completo (incluido el conjunto de prueba). Solo entonces podrá usarlos para transformar el conjunto de
entrenamiento y el conjunto de prueba (y los datos nuevos).
Canalizaciones de transformación Como
puede ver, hay muchos pasos de transformación de datos que deben ejecutarse en el
orden correcto. Afortunadamente, Scikit­Learn proporciona la clase Pipeline para ayudar con
dichas secuencias de transformaciones. Aquí hay una pequeña canalización para los atributos
numéricos:
desde sklearn.pipeline importar Pipeline desde
sklearn.preprocessing importar StandardScaler
num_pipeline = Tubería([
('imputer', SimpleImputer(strategy="median")), ('attribs_adder',
CombinedAttributesAdder()),
Machine Translated by Google
('std_scaler', Escalador estándar()),
])
número_de_vivienda_tr = número_de_tubería.fit_transform(número_de_vivienda)
El constructor Pipeline toma una lista de pares de nombre/estimador que definen una
secuencia de pasos. Todos los estimadores, excepto el último, deben ser transformadores
(es decir, deben tener un método fit_transform()). Los nombres pueden ser los que desee
(siempre que sean únicos y no contengan guiones bajos dobles, __); serán útiles más
adelante para ajustar los hiperparámetros.
Cuando se llama al método fit() del pipeline, este llama a fit_transform()
secuencialmente en todos los transformadores, pasando la salida de cada llamada
como parámetro a la siguiente llamada hasta que llega al estimador final, para el cual llama al
método fit().
La canalización expone los mismos métodos que el estimador final. En este ejemplo, el último
estimador es un StandardScaler, que es un transformador, por lo que la canalización
tiene un método transform() que aplica todas las transformaciones a los datos en secuencia
(y, por supuesto, también un método fit_transform(), que es el que usamos).
Hasta ahora hemos manejado las columnas categóricas y numéricas por separado. Sería
más conveniente tener un único transformador capaz de manejar todas las columnas,
aplicando las transformaciones adecuadas a cada columna.
En la versión 0.20, Scikit­Learn introdujo ColumnTransformer para este propósito, y la
buena noticia es que funciona muy bien con pandas DataFrames.
Usémoslo para aplicar todas las transformaciones a los datos de la vivienda:
desde sklearn.compose importar ColumnTransformer
num_attribs = lista(num_vivienda)
cat_attribs = ["proximidad_oceánica"]
full_pipeline = ColumnTransformer([ ("num",
num_pipeline, num_attribs), ("cat",
OneHotEncoder(), cat_attribs),
])
vivienda_preparada = tubería_completa.fit_transform(vivienda)
Machine Translated by Google
Primero importamos la clase ColumnTransformer, luego obtenemos la lista de
nombres de columnas numéricas y la lista de nombres de columnas categóricas, y luego
construimos un ColumnTransformer. El constructor requiere una lista de tuplas, donde
22
cada tupla contiene un nombre, un transformador
y una lista de nombres (o índices)
de columnas a las que se debe aplicar el transformador. En este ejemplo, especificamos
que las columnas numéricas se deben transformar utilizando el num_pipeline que
definimos anteriormente, y las columnas categóricas se deben transformar utilizando un
OneHotEncoder. Finalmente, aplicamos este ColumnTransformer a
los datos de la vivienda: aplica cada transformador a las columnas apropiadas y
concatena las salidas a lo largo del segundo eje (los transformadores deben devolver el
mismo número de filas).
Tenga en cuenta que OneHotEncoder devuelve una matriz dispersa,
mientras que num_pipeline devuelve una matriz densa. Cuando existe una combinación
de matrices dispersas y densas, ColumnTransformer calcula la densidad de la matriz
final (es decir, la proporción de celdas distintas de cero) y devuelve una matriz dispersa
si la densidad es inferior a un umbral determinado (por defecto, sparse_threshold=0,3).
En este ejemplo, se devuelve una matriz densa. ¡Y eso es todo! Tenemos
una secuencia de preprocesamiento que toma todos los datos de la vivienda y
aplica las transformaciones adecuadas a cada columna.
CONSEJO
En lugar de utilizar un transformador, puede especificar la cadena "drop" si desea que se
eliminen las columnas, o puede especificar "passthrough" si desea que las columnas
permanezcan intactas. De forma predeterminada, las columnas restantes (es decir, las que no
se enumeraron) se eliminarán, pero puede establecer el hiperparámetro resto en cualquier
transformador (o en "passthrough") si desea que estas columnas se gestionen de forma diferente.
Si está utilizando Scikit­Learn 0.19 o una versión anterior, puede utilizar una biblioteca de
terceros como sklearn­pandas, o puede implementar su propio transformador personalizado
para obtener la misma funcionalidad que ColumnTransformer. Alternativamente, puede
utilizar la clase FeatureUnion, que puede aplicar diferentes transformadores y
concatenar sus salidas. Pero no puede especificar diferentes columnas para cada
transformador; todas se aplican a todos los datos. Es posible solucionar este problema
Machine Translated by Google
Esta limitación utiliza un transformador personalizado para la selección de columnas
(consulte el cuaderno Jupyter para ver un ejemplo).
Seleccionar y entrenar un modelo
¡Por fin! Planteaste el problema, obtuviste los datos y los exploraste, tomaste muestras de
un conjunto de entrenamiento y un conjunto de prueba, y escribiste secuencias de transformación
para limpiar y preparar tus datos para algoritmos de aprendizaje automático de manera
automática. Ahora estás listo para seleccionar y entrenar un modelo de aprendizaje
automático.
Entrenamiento y evaluación en el conjunto de entrenamiento
La buena noticia es que gracias a todos estos pasos previos, las cosas ahora van a ser mucho más
sencillas de lo que crees. Primero, entrenemos un modelo de regresión lineal, como hicimos en el
capítulo anterior:
desde sklearn.linear_model importar LinearRegression
lin_reg = LinearRegression()
lin_reg.fit(vivienda_preparada, etiquetas_vivienda)
¡Listo! Ahora tienes un modelo de regresión lineal en funcionamiento. Probémoslo en algunas
instancias del conjunto de entrenamiento:
>>> algunos_datos = vivienda.iloc[:5] >>>
algunas_etiquetas = etiquetas_vivienda.iloc[:5] >>>
algunos_datos_preparados = canalización_completa.transform(algunos_datos)
>>> print("Predicciones:", lin_reg.predict(algunos_datos_preparados))
Predicciones: [ 210644.6045 317768.8069 210956.4333 59218.9888 189747.5584]
>>>
print("Etiquetas:", list(some_labels))
Etiquetas: [286600.0, 340600.0, 196900.0, 46300.0, 254500.0]
Funciona, aunque las predicciones no son del todo precisas (por ejemplo, ¡la primera
predicción tiene un error de cerca del 40 %!). Midamos el RMSE de este modelo de regresión
en todo el conjunto de entrenamiento utilizando la función mean_squared_error() de Scikit­Learn:
Machine Translated by Google
>>> de sklearn.metrics importar error_cuadrático_medio >>> predicciones_de_vivienda
= lin_reg.predict(vivienda_preparada) >>> lin_mse = error_cuadrático_medio(etiquetas_de_vivienda,
predicciones_de_vivienda) >>> lin_rmse = np.sqrt(lin_mse) >>> lin_rmse 68628.19819848922
Esto es mejor que nada, pero claramente no es una gran puntuación: los valores de vivienda
media de la mayoría de los distritos oscilan entre $120,000 y $265,000, por lo que un error de
predicción típico de $68,628 no es muy satisfactorio. Este es un ejemplo de un modelo que no
se ajusta a los datos de entrenamiento. Cuando esto sucede, puede significar que las características
no brindan suficiente información para hacer buenas predicciones o que el modelo no es lo
suficientemente potente. Como vimos en el capítulo anterior, las principales formas de corregir
el ajuste insuficiente son seleccionar un modelo más potente, alimentar el algoritmo de entrenamiento
con mejores características o reducir las restricciones del modelo. Este modelo no está
regularizado, lo que descarta la última opción. Puede intentar agregar más características (por
ejemplo, el logaritmo de la población), pero primero probemos un modelo más complejo para ver
cómo funciona.
Entrenemos un DecisionTreeRegressor. Se trata de un modelo potente, capaz de encontrar
relaciones no lineales complejas en los datos (los árboles de decisión se presentan con más
detalle en el Capítulo 6). El código ya debería resultar familiar:
desde sklearn.tree importar DecisionTreeRegressor
tree_reg = DecisionTreeRegressor()
tree_reg.fit(vivienda_preparada, etiquetas_vivienda)
Ahora que el modelo está entrenado, evaluémoslo en el conjunto de entrenamiento:
>>> predicciones_de_vivienda = árbol_reg.predict(vivienda_preparada) >>> árbol_mse =
error_cuadrático_medio(etiquetas_de_vivienda, predicciones_de_vivienda) >>> árbol_rmse = np.sqrt(árbol_mse) >>>
árbol_rmse 0.0
¡Espera, qué! ¿No hay ningún error? ¿Es posible que este modelo sea absolutamente perfecto?
Por supuesto, es mucho más probable que el modelo haya sobreajustado gravemente los datos.
¿Cómo puedes estar seguro? Como vimos antes, no debes tocar el equipo de prueba.
Machine Translated by Google
hasta que esté listo para lanzar un modelo en el que esté seguro, por lo que necesita utilizar
parte del conjunto de entrenamiento para el entrenamiento y parte para la validación del modelo.
Mejor evaluación mediante validación cruzada Una forma de evaluar
el modelo de árbol de decisiones sería utilizar la función train_test_split() para
dividir el conjunto de entrenamiento en un conjunto de entrenamiento más pequeño y un conjunto
de validación, y luego entrenar los modelos con el conjunto de entrenamiento más pequeño y
evaluarlos con el conjunto de validación. Es un poco de trabajo, pero nada demasiado difícil, y
funcionaría bastante bien.
Una gran alternativa es utilizar la función de validación cruzada K­fold de Scikit­Learn .
El código siguiente divide aleatoriamente el conjunto de entrenamiento en 10 subconjuntos
distintos denominados pliegues, luego entrena y evalúa el modelo de árbol de decisiones 10
veces, eligiendo un pliegue diferente para la evaluación cada vez y entrenando en los otros 9
pliegues. El resultado es una matriz que contiene las 10 puntuaciones de evaluación:
desde sklearn.model_selection importar cross_val_score scores =
cross_val_score(tree_reg, vivienda_preparada, vivienda_etiquetas,
puntuación="error_cuadrático_medio_negativo", cv=10)
puntuaciones_rmse_árbol = np.sqrt(­puntuaciones)
ADVERTENCIA
Las funciones de validación cruzada de Scikit­Learn esperan una función de utilidad (cuanto mayor sea,
mejor) en lugar de una función de costo (cuanto menor sea, mejor), por lo que la función de puntuación
es en realidad lo opuesto del MSE (es decir, un valor negativo), razón por la cual el código anterior
calcula ­scores antes de calcular la raíz cuadrada.
Veamos los resultados:
>>> def display_scores(scores):
...
print("Puntuaciones:",
...
puntuaciones) print("Media:",
...
puntuaciones.media()) print("Desviación estándar:", puntuaciones.std())
...
>>> display_scores(puntuaciones_rmse_árbol)
Puntuaciones: [70194.33680785 66855.16363941 72432.58244769 70758.73896782
71115.88230639 75585.14172901 70262.86139133 70273.6325285 75366.87952553
71231.65726027]
Machine Translated by Google
Media: 71407.68766037929
Desviación estándar: 2439,4345041191004
Ahora el árbol de decisiones no se ve tan bien como antes. De hecho, ¡parece tener un peor
rendimiento que el modelo de regresión lineal! Observe que la validación cruzada le
permite obtener no solo una estimación del rendimiento de su modelo, sino también una medida
de cuán precisa es esta estimación (es decir, su desviación estándar). El árbol de decisiones
tiene una puntuación de aproximadamente 71 407, generalmente ±2439. No tendría esta información
si solo usara un conjunto de validación. Pero la validación cruzada implica entrenar el modelo
varias veces, por lo que no siempre es posible.
Calculemos las mismas puntuaciones para el modelo de regresión lineal solo para ser
seguro:
>>> lin_scores = cross_val_score(lin_reg, vivienda_preparada, vivienda_etiquetas,
...
puntuación="error cuadrático medio negativo", cv=10)
...
>>> puntuaciones_rmse_lin = np.sqrt(­puntuaciones_lin)
>>> mostrar_puntuaciones(puntuaciones_rmse_lin)
Puntuaciones: [66782.73843989 66960.118071 70347.95244419 74739.57052552
68031.13388938 71193.84183426 64969.63056405 68281.61137997 71552.91566558
67665.10082067]
Media: 69052,46136345083
Desviación estándar: 2731,674001798348
Así es: el modelo de árbol de decisión está tan sobreajustado que su rendimiento es peor que el
del modelo de regresión lineal.
Probemos ahora un último modelo: el RandomForestRegressor. Como veremos en el Capítulo 7, los
bosques aleatorios funcionan entrenando muchos árboles de decisión en subconjuntos aleatorios de
las características y luego promediando sus predicciones. La creación de un modelo sobre la base
de muchos otros modelos se denomina aprendizaje conjunto y, a menudo, es una excelente
manera de llevar los algoritmos de aprendizaje automático aún más lejos. Omitiremos la mayor
parte del código, ya que es esencialmente el mismo que para los otros modelos:
>>> de sklearn.ensemble importar RandomForestRegressor >>> forest_reg =
RandomForestRegressor() >>> forest_reg.fit(vivienda_preparada,
etiquetas_vivienda) >>> [...] >>> forest_rmse 18603.515021376355
Machine Translated by Google
>>> mostrar_puntuaciones(puntuaciones_rmse_forestales)
Puntuaciones: [49519.80364233 47461.9115823 50029.02762854 52325.28068953
49308.39426421 53446.37892622 48634.8036574 47585.73832311 53490.10699751 50021.5852922 ]
Media: 50182.303100336096
Desviación estándar: 2097.0810550985693
Vaya, esto es mucho mejor: los bosques aleatorios parecen muy prometedores. Sin embargo, tenga en
cuenta que la puntuación en el conjunto de entrenamiento sigue siendo mucho menor que en los
conjuntos de validación, lo que significa que el modelo todavía está sobreajustando el conjunto de entrenamiento.
Las posibles soluciones para el sobreajuste son simplificar el modelo, restringirlo (es decir, regularizarlo) u
obtener muchos más datos de entrenamiento. Sin embargo, antes de profundizar mucho más en los bosques
aleatorios, debe probar muchos otros modelos de varias categorías de algoritmos de aprendizaje automático
(por ejemplo, varias máquinas de vectores de soporte con diferentes núcleos y posiblemente una red
neuronal), sin dedicar demasiado tiempo a ajustar los hiperparámetros. El objetivo es seleccionar unos
pocos modelos prometedores (de dos a cinco).
CONSEJO
Debes guardar todos los modelos con los que experimentes para poder volver fácilmente a cualquier
modelo que quieras. Asegúrate de guardar tanto los hiperparámetros como los parámetros
entrenados, así como las puntuaciones de validación cruzada y quizás también las predicciones
reales. Esto te permitirá comparar fácilmente las puntuaciones entre los tipos de modelos y comparar
los tipos de errores que cometen. Puedes guardar fácilmente los modelos de Scikit­Learn utilizando
el módulo pickle de Python o utilizando la biblioteca joblib, que es más eficiente para serializar
matrices NumPy grandes (puedes instalar esta biblioteca utilizando pip):
importar biblioteca de trabajos
joblib.dump(mi_modelo, "mi_modelo.pkl")
# y
más tarde...
mi_modelo_cargado = joblib.load("mi_modelo.pkl")
Perfeccione su modelo
Supongamos que ya tiene una lista de modelos prometedores y que ahora necesita perfeccionarlos.
Veamos algunas formas de hacerlo.
Machine Translated by Google
Búsqueda en cuadrícula
Una opción sería modificar los hiperparámetros manualmente hasta encontrar una
buena combinación de valores de hiperparámetros. Esto sería un trabajo muy
tedioso y es posible que no tenga tiempo para explorar muchas combinaciones.
En lugar de eso, deberías hacer que GridSearchCV de Scikit­Learn busque por ti.
Todo lo que necesitas hacer es decirle con qué hiperparámetros quieres que
experimente y qué valores probar, y utilizará la validación cruzada para evaluar
todas las combinaciones posibles de valores de hiperparámetros. Por ejemplo, el
siguiente código busca la mejor combinación de valores de hiperparámetros
para RandomForestRegressor:
desde sklearn.model_selection importar GridSearchCV
param_grid =
[ {'n_estimadores': [3, 10, 30], 'máximo_características': [2, 4, 6, 8]}, {'bootstrap':
[Falso], 'n_estimadores': [3, 10], 'máximo_características': [2, 3, 4]}, ]
forest_reg = Regresor forestal aleatorio ()
búsqueda_en_cuadrícula = GridSearchCV(reg_bosque, cuadrícula_parámetro, cv=5,
puntuación='error_cuadrático_medio_negativo',
puntuación_de_entrenamiento_de_retorno=Verdadero)
grid_search.fit(vivienda_preparada, etiquetas_de_vivienda)
CONSEJO
Cuando no tienes idea de qué valor debe tener un hiperparámetro, un enfoque simple es
probar potencias consecutivas de 10 (o un número más pequeño si quieres una búsqueda
más detallada, como se muestra en este ejemplo con el hiperparámetro n_estimators).
Este param_grid le dice a Scikit­Learn que primero evalúe las 3 × 4 =
12 combinaciones de valores de hiperparámetros n_estimators y max_features
especificados en el primer dict (no se preocupe por lo que significan estos
hiperparámetros por ahora; se explicarán en el Capítulo 7), luego pruebe las 2
× 3 = 6 combinaciones de valores de hiperparámetros en el segundo dict, pero esta vez con
Machine Translated by Google
el hiperparámetro de arranque se establece en Falso en lugar de Verdadero (que es el valor
predeterminado para este hiperparámetro).
La búsqueda en cuadrícula explorará 12 + 6 = 18 combinaciones de valores
de hiperparámetros de RandomForestRegressor y entrenará cada modelo 5 veces (ya que estamos
usando una validación cruzada quíntuple). En otras palabras, en total, habrá 18 × 5 = 90 rondas de
entrenamiento. Puede llevar bastante tiempo, pero cuando esté listo, podrá obtener la mejor combinación
de parámetros de esta manera:
>>> grid_search.best_params_
{'máximo_funciones': 8, 'n_estimadores': 30}
CONSEJO
Dado que 8 y 30 son los valores máximos que se evaluaron, probablemente debería intentar
buscar nuevamente con valores más altos; la puntuación puede seguir mejorando.
También puedes obtener el mejor estimador directamente:
>>>
grid_search.best_estimator_RandomForestRegressor (bootstrap=Verdadero, criterio='mse', profundidad_máxima=Ningun
max_features=8, max_leaf_nodes=Ninguno, min_impureza_decrecimiento=0.0,
min_impureza_división=Ninguno, min_muestras_hoja=1,
min_muestras_división=2, min_weight_fraction_leaf=0.0,
n_estimators=30, n_jobs=Ninguno, oob_score=False, random_state=Ninguno,
verbose=0, warm_start=False)
NOTA
Si GridSearchCV se inicializa con refit=True (que es el valor predeterminado), una vez que encuentra el mejor
estimador mediante validación cruzada, lo vuelve a entrenar con todo el conjunto de entrenamiento. Esto
suele ser una buena idea, ya que si se le suministran más datos, es probable que mejore su rendimiento.
Y por supuesto también están disponibles las puntuaciones de evaluación:
>>> cvres = grid_search.cv_results_ >>> para
puntuación_media, parámetros en zip(cvres["puntuación_media_de_la_prueba"], cvres["parámetros"]):
...
print(np.sqrt(­media_puntuación), parámetros)
...
Machine Translated by Google
63669.05791727153 {'máxima_características': 2, 'n_estimadores': 3} 55627.16171305252
{'máxima_características': 2, 'n_estimadores': 10} 53384.57867637289
{'máxima_características': 2, 'n_estimadores': 30} 60965.99185930139
{'máxima_características': 4, 'n_estimadores': 3} 52740.98248528835
{'máxima_características': 4, 'n_estimadores': 10} 50377.344409590376
{'máxima_características': 4, 'n_estimadores': 30} 58663.84733372485 {'máx. características':
6, 'n_estimadores': 3} 52006.15355973719 {'máx. características': 6, 'n_estimadores':
10} 50146.465964159885 {'máx. características': 6, 'n_estimadores': 30}
57869.25504027614 {'máx. características': 8, 'n_estimadores': 3} 51711.09443660957
{'máx. características': 8, 'n_estimadores': 10} 49682.25345942335 {'max_features': 8,
'n_estimators': 30} 62895.088889905004 {'bootstrap': Falso, 'max_features': 2,
'n_estimators': 3} 54658.14484390074 {'bootstrap': Falso, 'max_features': 2, 'n_estimators':
10} 59470.399594730654 {'bootstrap': Falso, 'max_features': 3, 'n_estimators': 3} 52725.01091081235 {'bootstrap': Falso,
'max_features': 3, 'n_estimators': 10} 57490.612956065226 {'bootstrap': Falso, 'máximas_características': 4,
'n_estimadores': 3} 51009.51445842374 {'bootstrap': Falso, 'máximas_características': 4, 'n_estimadores': 10}
En este ejemplo, obtenemos la mejor solución al configurar el hiperparámetro
max_features en 8 y el hiperparámetro n_estimators en 30. El puntaje RMSE para esta
combinación es 49,682, que es ligeramente mejor que el puntaje que obtuvo
anteriormente usando los valores de hiperparámetro predeterminados (que era 50,182).
¡Felicitaciones, has logrado afinar con éxito tu mejor modelo!
CONSEJO
No olvide que puede tratar algunos de los pasos de preparación de datos como hiperparámetros. Por ejemplo,
la búsqueda en cuadrícula determinará automáticamente si debe agregar o no una característica de la que
no estaba seguro (por ejemplo, utilizando el hiperparámetro add_bedrooms_per_room de su transformador
CombinedAttributesAdder). De manera similar, se puede utilizar para encontrar automáticamente la mejor
manera de manejar valores atípicos, características faltantes, selección de características y más.
Búsqueda aleatoria
El enfoque de búsqueda en cuadrícula es adecuado cuando se exploran
relativamente pocas combinaciones, como en el ejemplo anterior, pero cuando el
espacio de búsqueda de hiperparámetros es grande, a menudo es preferible utilizar
RandomizedSearchCV en su lugar. Esta clase se puede utilizar de la misma manera que
la clase GridSearchCV, pero en lugar de probar todas las combinaciones posibles, evalúa una determinada
Machine Translated by Google
Número de combinaciones aleatorias seleccionando un valor aleatorio para cada
hiperparámetro en cada iteración. Este enfoque tiene dos ventajas principales:
Si permite que la búsqueda aleatoria se ejecute durante, digamos, 1000
iteraciones, este enfoque explorará 1000 valores diferentes para cada hiperparámetro
(en lugar de solo unos pocos valores por hiperparámetro con el enfoque de
búsqueda en cuadrícula).
Simplemente estableciendo el número de iteraciones, usted tiene más control
sobre el presupuesto computacional que desea asignar a la búsqueda de
hiperparámetros.
Métodos de conjunto
Otra forma de ajustar el sistema es tratar de combinar los modelos que funcionan mejor.
El grupo (o “conjunto”) a menudo tendrá un mejor rendimiento que el mejor modelo
individual (al igual que los bosques aleatorios tienen un mejor rendimiento que los
árboles de decisión individuales en los que se basan), especialmente si los modelos
individuales cometen tipos de errores muy diferentes. Trataremos este tema con más detalle
en el Capítulo 7.
Analizar los mejores modelos y sus errores A menudo, se obtendrán
buenos conocimientos sobre el problema si se inspeccionan los mejores modelos. Por
ejemplo, el RandomForestRegressor puede indicar la importancia relativa de cada atributo
para realizar predicciones precisas:
>>> importancia_de_las_características = búsqueda_en_cuadrícula.mejor_estimador_.importancia_de_las_características_
>>> importancia_de_las_características
matriz([7.33442355e­02, 6.29090705e­02, 4.11437985e­02, 1.46726854e­02, 1.41064835e­02,
1.48742809e­02, 1.42575993e­02, 3.66158981e­01, 5.64191792e­02, 1.08792957e­01,
5.33510773e­02, 1.03114883e­02, 1.64780994e­01, 6.02803867e­05, 1.96041560e­03,
2.85647464e­03])
Mostremos estos puntajes de importancia junto a su atributo correspondiente.
nombres:
>>> atributos_adicionales = ["habitaciones_por_habitación", "habitaciones_por_habitación",
"dormitorios_por_habitación"] >>> codificador_cat =
canalización_completa.transformadores_con_nombre_["cat"] >>> atributos_cat_one_hot = lista(codificador_cat.categorías_[0])
Machine Translated by Google
___
( _ >>>
gramo
_[ ])
atributos = num_attribs + extra_attribs + cat_one_hot_attribs >>> ordenado(código
postal(importancias_de_las_características, atributos), reverso=Verdadero) [(0,3661589806181342,
'ingreso_mediano'), (0,1647809935615905, 'INTERIOR'),
(0,10879295677551573,
'habitación_por_habitación'), (0,07334423551601242,
'longitud'), (0,0629090704826203, 'latitud'),
(0,05641917918195401, 'habitaciones_por_habitación'),
(0,05335107734767581, 'dormitorios_por_habitación'),
(0,041143798478729635, 'edad_media_de_la_vivienda'),
(0,014874280890402767, 'población'), (0,014672685420543237,
'habitaciones_totales'), (0,014257599323407807,
'hogares'), (0,014106483453584102, 'dormitorios_totales'),
(0,010311488326303787, '<1H OCÉANO'),
(0,002856474637320158, 'CERCA DEL OCÉANO'),
(0,00196041559947807, 'CERCA BAHÍA'),
(6.028038672736599e­05, 'ISLA')]
Con esta información, es posible que quieras intentar eliminar algunas de las características
menos útiles (por ejemplo, aparentemente solo una categoría ocean_proximity es realmente útil,
por lo que podrías intentar eliminar las demás).
También debe analizar los errores específicos que comete su sistema, luego tratar de
comprender por qué los comete y qué podría solucionar el problema (agregar características
adicionales o deshacerse de las que no brindan información, limpiar valores atípicos, etc.).
Evalúe su sistema en el conjunto de pruebas Después de
ajustar sus modelos durante un tiempo, finalmente tendrá un sistema que funciona lo
suficientemente bien. Ahora es el momento de evaluar el modelo final en el conjunto de
pruebas. Este proceso no tiene nada de especial; solo obtenga los predictores y las etiquetas
de su conjunto de pruebas, ejecute su full_pipeline para transformar los datos (llame a
transform(), no a fit_transform(); ¡no desea ajustar el conjunto de pruebas!) y evalúe el
modelo final en el conjunto de pruebas:
modelo_final = búsqueda_cuadrícula.mejor_estimador_
X_test = strat_test_set.drop("valor_mediano_de_la_casa", axis=1) y_test =
strat_test_set["valor_mediano_de_la_casa"].copy()
X_test_prepared = canalización_completa.transform(X_test)
Machine Translated by Google
predicciones_finales = modelo_final.predict(X_prueba_preparada)
final_mse = error_cuadrático_medio(prueba_y, predicciones_finales)
# => evalúa
a 47.730,2
final_rmse = np.sqrt(final_mse)
En algunos casos, una estimación puntual del error de generalización no será
suficiente para convencerlo de que lo lance: ¿qué pasa si es solo un 0,1 % mejor que el
modelo que se encuentra actualmente en producción? Tal vez le interese tener una idea
de cuán precisa es esta estimación. Para ello, puede calcular un intervalo de confianza
del 95 % para el error de generalización utilizando scipy.stats.t.interval():
>>> de scipy importar estadísticas >>>
confianza = 0,95 >>>
errores_cuadrados = (predicciones_finales ­ prueba_y) ** 2 >>>
np.sqrt(stats.t.interval(confianza, len(errores_cuadrados) ­ 1, loc=errores_cuadrados.media(),
...
...
...
escala=stats.sem(errores_cuadrados)))
matriz([45685.10470776, 49691.25001878])
Si realizó muchos ajustes de hiperparámetros, el rendimiento generalmente será
levemente peor que lo que midió utilizando la validación cruzada (porque su sistema
termina ajustado para funcionar bien con los datos de validación y probablemente
no funcionará tan bien con conjuntos de datos desconocidos). No es el caso en
este ejemplo, pero cuando esto sucede, debe resistir la tentación de ajustar los
hiperparámetros para que los números se vean bien en el conjunto de prueba;
es poco probable que las mejoras se generalicen a nuevos datos.
Ahora viene la fase de prelanzamiento del proyecto: debe presentar su solución
(destacando lo que ha aprendido, lo que funcionó y lo que no, qué suposiciones se
hicieron y cuáles son las limitaciones de su sistema), documentar todo y crear
presentaciones agradables con visualizaciones claras y afirmaciones fáciles de
recordar (por ejemplo, "el ingreso medio es el predictor número uno de los
precios de la vivienda"). En este ejemplo de vivienda de California, el rendimiento
final del sistema no es mejor que las estimaciones de precios de los expertos, que
a menudo tenían un error de alrededor del 20%, pero aún así puede ser una buena
idea lanzarlo, especialmente si esto libera algo de tiempo para que los expertos
puedan trabajar en tareas más interesantes y productivas.
Machine Translated by Google
Inicie, supervise y mantenga su sistema
Perfecto, ¡obtuviste la aprobación para lanzar! Ahora necesitas preparar tu solución
para producción (por ejemplo, pulir el código, escribir documentación y pruebas,
etc.). Luego puedes implementar tu modelo en tu entorno de producción. Una forma
de hacer esto es guardar el modelo entrenado de Scikit­Learn (por ejemplo, usando
joblib), incluyendo el preprocesamiento completo y la secuencia de predicción, luego
cargar este modelo entrenado dentro de tu entorno de producción y usarlo para hacer
predicciones llamando a su método predict(). Por ejemplo, tal vez el modelo se usará
dentro de un sitio web: el usuario escribirá algunos datos sobre un nuevo distrito y hará
clic en el botón Estimar precio. Esto enviará una consulta que contiene los datos al
servidor web, que los reenviará a tu aplicación web y, finalmente, tu código
simplemente llamará al método predict() del modelo (quieres cargar el modelo al
iniciar el servidor, en lugar de cada vez que se usa el modelo).
Como alternativa, puede envolver el modelo dentro de un servicio web dedicado
que su aplicación web pueda consultar a través de una API23
REST (consulte la Figura
2­17). Esto facilita la actualización de su modelo a nuevas versiones sin interrumpir
la aplicación principal. También simplifica el escalamiento, ya que puede iniciar
tantos servicios web como necesite y equilibrar la carga de las solicitudes provenientes
de su aplicación web entre estos servicios web. Además, permite que su
aplicación web use cualquier lenguaje, no solo Python.
Figura 2­17. Un modelo implementado como un servicio web y utilizado por una aplicación web
Otra estrategia popular es implementar su modelo en la nube, por ejemplo, en Google
Cloud AI Platform (anteriormente conocida como Google Cloud ML Engine): simplemente
guarde su modelo usando joblib y cárguelo en Google Cloud Storage (GCS), luego
diríjase a Google Cloud AI Platform y cree una nueva versión del modelo, apuntándola
al archivo GCS. ¡Eso es todo! Esto le brinda un servicio web simple que se encarga
del equilibrio de carga y el escalamiento por usted. Toma solicitudes JSON que
contienen los datos de entrada (por ejemplo, de un distrito) y devuelve
respuestas JSON que contienen las predicciones. Luego puede usar este servicio web en
Machine Translated by Google
Su sitio web (o cualquier entorno de producción que esté utilizando). Como veremos en el Capítulo
19, implementar modelos de TensorFlow en AI Platform no es muy diferente de implementar
modelos de Scikit­Learn.
Pero la implementación no es el final de la historia. También es necesario escribir un código de
monitoreo para verificar el rendimiento en vivo de su sistema a intervalos regulares y activar alertas
cuando disminuya. Puede tratarse de una caída abrupta, probablemente debido a un
componente dañado en su infraestructura, pero tenga en cuenta que también puede ser un
deterioro leve que fácilmente podría pasar desapercibido durante mucho tiempo. Esto es bastante
común porque los modelos tienden a "descomponerse" con el tiempo: de hecho, el mundo cambia,
por lo que si el modelo se entrenó con los datos del año pasado, es posible que no esté adaptado a los datos de hoy.
ADVERTENCIA
Incluso un modelo entrenado para clasificar imágenes de gatos y perros puede necesitar ser reentrenado
periódicamente, no porque los gatos y los perros muten de la noche a la mañana, sino porque las cámaras
siguen cambiando, junto con los formatos de imagen, la nitidez, el brillo y las proporciones de tamaño. Además,
la gente puede que el año que viene adore razas diferentes, o puede que decida vestir a sus mascotas con
sombreros diminutos... ¿quién sabe?
Entonces, necesitas monitorear el desempeño en vivo de tu modelo. ¿Pero cómo lo haces?
Bueno, depende. En algunos casos, el rendimiento del modelo se puede inferir a partir de métricas
posteriores. Por ejemplo, si su modelo es parte de un sistema de recomendación y sugiere
productos que pueden interesar a los usuarios, entonces es fácil monitorear la cantidad de
productos recomendados que se venden cada día. Si esta cantidad disminuye (en comparación
con los productos no recomendados), entonces el principal sospechoso es el modelo. Esto
puede deberse a que el flujo de datos está dañado o tal vez el modelo necesita volver a
entrenarse con datos nuevos (como analizaremos en breve).
Sin embargo, no siempre es posible determinar el rendimiento del modelo sin ningún análisis
humano. Por ejemplo, supongamos que entrenó un modelo de clasificación de imágenes
(consulte el Capítulo 3) para detectar varios defectos de productos en una línea de producción.
¿Cómo puede recibir una alerta si el rendimiento del modelo disminuye, antes de que se envíen
miles de productos defectuosos a sus clientes? Una solución es enviar a los evaluadores
humanos una muestra de todas las imágenes que clasificó el modelo (especialmente las imágenes
de las que el modelo no estaba tan seguro).
Machine Translated by Google
Dependiendo de la tarea, los evaluadores pueden tener que ser expertos o no especialistas, como los
trabajadores de una plataforma de crowdsourcing (por ejemplo, Amazon Mechanical Turk). En algunas aplicaciones,
incluso podrían ser los propios usuarios, que responderían, por ejemplo, a través de encuestas o
captchas reutilizados.
24
De cualquier manera, es necesario implementar un sistema de monitoreo (con o sin evaluadores humanos para
evaluar el modelo en vivo), así como todos los procesos relevantes para definir qué hacer en caso de fallas y cómo
prepararse para ellas.
Lamentablemente, esto puede ser mucho trabajo. De hecho, a menudo es mucho más trabajo que construir y
entrenar un modelo.
Si los datos siguen evolucionando, necesitarás actualizar tus conjuntos de datos y volver a entrenar tu modelo
periódicamente. Probablemente deberías automatizar todo el proceso tanto como sea posible. A continuación,
se indican algunas cosas que puedes automatizar:
Recopile datos nuevos periódicamente y etiquételos (por ejemplo, utilizando evaluadores humanos).
Escriba un script para entrenar el modelo y ajustar los hiperparámetros automáticamente. Este script
podría ejecutarse automáticamente, por ejemplo, todos los días o todas las semanas, según sus
necesidades.
Escriba otro script que evalúe tanto el nuevo modelo como el modelo anterior en el conjunto de
pruebas actualizado, e implemente el modelo en producción si el rendimiento no ha disminuido
(si lo hizo, asegúrese de investigar por qué).
También debe asegurarse de evaluar la calidad de los datos de entrada del modelo.
A veces, el rendimiento se degradará levemente debido a una señal de mala calidad (por ejemplo, un sensor que no
funciona correctamente y envía valores aleatorios o la salida de otro equipo se vuelve obsoleta), pero puede
pasar un tiempo antes de que el rendimiento de su sistema se degrade lo suficiente como para activar
una alerta. Si supervisa las entradas de su modelo, puede detectar esto antes. Por ejemplo, podría activar una alerta
si faltan cada vez más entradas de una característica, si su media o desviación estándar se aleja demasiado del
conjunto de entrenamiento o si una característica categórica comienza a contener nuevas categorías.
Por último, asegúrese de mantener copias de seguridad de cada modelo que cree y de tener el proceso y las
herramientas a mano para volver a un modelo anterior rápidamente, en caso de que el nuevo modelo comience a
fallar gravemente por algún motivo. Tener copias de seguridad también facilita
Machine Translated by Google
Es posible comparar fácilmente los nuevos modelos con los anteriores. De manera similar, debe
mantener copias de seguridad de cada versión de sus conjuntos de datos para poder volver a un
conjunto de datos anterior si el nuevo alguna vez se corrompe (por ejemplo, si los datos nuevos que se
le agregan resultan estar llenos de valores atípicos). Tener copias de seguridad de sus conjuntos de
datos también le permite evaluar cualquier modelo en comparación con cualquier conjunto de
datos anterior.
CONSEJO
Es posible que desee crear varios subconjuntos del conjunto de prueba para evaluar el rendimiento de
su modelo en partes específicas de los datos. Por ejemplo, es posible que desee tener un
subconjunto que contenga solo los datos más recientes, o un conjunto de prueba para tipos específicos
de entradas (por ejemplo, distritos ubicados en el interior frente a distritos ubicados cerca del
océano). Esto le permitirá comprender mejor las fortalezas y debilidades de su modelo.
Como puede ver, el aprendizaje automático implica una gran cantidad de infraestructura, por lo que
no se sorprenda si su primer proyecto de aprendizaje automático requiere mucho esfuerzo y
tiempo para desarrollarse e implementarse en producción. Afortunadamente, una vez que toda la
infraestructura esté lista, pasar de la idea a la producción será mucho más rápido.
¡Pruébalo!
Esperamos que este capítulo le haya dado una buena idea de cómo es un proyecto de
aprendizaje automático y le haya mostrado algunas de las herramientas que puede usar para
entrenar un gran sistema. Como puede ver, gran parte del trabajo se encuentra en el paso
de preparación de datos: crear herramientas de monitoreo, configurar canales de evaluación
humana y automatizar el entrenamiento regular del modelo. Los algoritmos de aprendizaje
automático son importantes, por supuesto, pero probablemente sea preferible sentirse
cómodo con el proceso general y conocer bien tres o cuatro algoritmos en lugar de pasar todo el
tiempo explorando algoritmos avanzados.
Por lo tanto, si aún no lo ha hecho, ahora es un buen momento para tomar una computadora
portátil, seleccionar un conjunto de datos que le interese e intentar realizar todo el proceso de A
a Z. Un buen lugar para comenzar es en un sitio web de competencia como http://kaggle.com/: Tendrás
un conjunto de datos con los que jugar, un objetivo claro y personas con las que compartir la
experiencia. ¡Diviértete!
Machine Translated by Google
Ceremonias
Los siguientes ejercicios se basan en el conjunto de datos de vivienda de este capítulo:
1. Pruebe un regresor de máquina de vectores de soporte (sklearn.svm.SVR) con
varios hiperparámetros, como kernel="linear" (con varios valores para el
hiperparámetro C) o kernel="rbf" (con varios valores para los hiperparámetros
C y gamma). No se preocupe por el significado de estos hiperparámetros por ahora.
¿Cómo funciona el mejor predictor de SVR?
2. Intente reemplazar GridSearchCV con RandomizedSearchCV.
3. Intente agregar un transformador en la tubería de preparación para seleccionar solo el
atributos más importantes.
4. Intente crear una única canalización que realice la preparación completa de los
datos más la predicción final.
5. Explore automáticamente algunas opciones de preparación
utilizando GridSearchCV.
Las soluciones a estos ejercicios se pueden encontrar en los cuadernos Jupyter
disponibles en https://github.com/ageron/handson­ml2.
1 El proyecto de ejemplo es ficticio; el objetivo es ilustrar los pasos principales de un Machine Learning
proyecto, no para aprender nada sobre el negocio inmobiliario.
2 El conjunto de datos original apareció en R. Kelley Pace y Ronald Barry, “Sparse Spatial
Autoregressions”, Statistics & Probability Letters 33, no. 3 (1997): 291–297.
3 A la información que se introduce en un sistema de aprendizaje automático se la suele llamar señal, en referencia
a la teoría de la información de Claude Shannon, que desarrolló en Bell Labs para mejorar las
telecomunicaciones. Su teoría: se busca una alta relación señal­ruido.
4 Recuerde que el operador de transposición transforma un vector de columna en un vector de fila (y viceversa).
Se recomienda la última versión de Python 3. Python 2.7+ también puede funcionar, pero ahora que está obsoleto,
todas las bibliotecas científicas principales están dejando de brindarle soporte, por lo que debe migrar a Python
3 lo antes posible .
6 Mostraré los pasos de instalación usando pip en un shell bash en un sistema Linux o macOS. Es posible que deba
adaptar estos comandos a su propio sistema. En Windows, recomiendo instalar
Machine Translated by Google
Anaconda en cambio.
7 Si desea actualizar pip para todos los usuarios de su máquina en lugar de solo para su propio usuario,
debe eliminar la opción ­­user y asegurarse de tener derechos de administrador (por ejemplo, agregando sudo antes de todo el
comando en Linux o macOS).
Las herramientas alternativas incluyen venv (muy similar a virtualenv e incluido en la biblioteca estándar), virtualenvwrapper (ofrece
funcionalidades adicionales sobre virtualenv), pyenv (permite cambiar fácilmente entre versiones de Python) y pipenv (una
excelente herramienta de empaquetado del mismo autor que la popular biblioteca de solicitudes, construida sobre pip y virtualenv).
Tenga en cuenta que Jupyter puede manejar múltiples versiones de Python e incluso muchos otros lenguajes como R u Octave.
También es posible que tengas que comprobar las restricciones legales, como los campos privados que nunca deben copiarse en
almacenes de datos no seguros.
En un proyecto real , guardarías este código en un archivo Python, pero por ahora puedes escribirlo en tu cuaderno Jupyter.
12 La desviación estándar se denota generalmente σ (la letra griega sigma) y es la raíz cuadrada de la varianza, que es el promedio de
la desviación al cuadrado de la media. Cuando una característica tiene una distribución normal en forma de campana (también
llamada distribución gaussiana), que es muy común, se aplica la regla “68­95­99,7”: alrededor del 68 % de los valores se
encuentran dentro de 1σ de la media, el 95 % dentro de 2σ y el 99,7 % dentro de 3σ.
En este libro, cuando un ejemplo de código contiene una mezcla de código y resultados, como es el caso aquí, se formatea como en el
intérprete de Python, para una mejor legibilidad: las líneas de código tienen como prefijo >>> (o ... para bloques sangrados), y
los resultados no tienen prefijo .
A menudo verás personas que establecen la semilla aleatoria en 42. Este número no tiene ninguna propiedad especial, salvo la
de ser la Respuesta a la Pregunta Fundamental de la Vida, el Universo y Todo.
15 La información de ubicación es en realidad bastante burda y, como resultado, muchos distritos tendrán exactamente el mismo ID,
por lo que terminarán en el mismo conjunto (prueba o tren). Esto introduce un sesgo de muestreo desafortunado.
16 Si estás leyendo esto en escala de grises, toma un bolígrafo rojo y dibuja sobre la mayor parte de la costa desde el Área de la Bahía hasta
San Diego (como es de esperar). También puedes agregar una zona amarilla alrededor de Sacramento.
17 Para obtener más detalles sobre los principios de diseño, consulte Lars Buitinck et al., “API Design for Machine Learning Software:
Experiences from the Scikit­Learn Project”, preimpresión de arXiv arXiv:1309.0238 (2013).
18 Algunos predictores también proporcionan métodos para medir la confianza de sus predicciones.
19 Esta clase está disponible en Scikit­Learn 0.20 y versiones posteriores. Si utiliza una versión anterior,
considere actualizar o utilizar el método pandas Series.factorize().
Antes de Scikit­Learn 0.20, el método solo podía codificar valores categóricos enteros, pero desde 0.20 también puede manejar otros
tipos de entradas, incluidas entradas categóricas de texto.
21 Consulte la documentación de SciPy para obtener más detalles.
Machine Translated by Google
22 Al igual que en el caso de las tuberías, el nombre puede ser cualquier cosa siempre que no contenga doble
subraya.
En pocas palabras, una API REST (o RESTful) es una API basada en HTTP que sigue algunas convenciones, como el
uso de verbos HTTP estándar para leer, actualizar, crear o eliminar recursos (GET, POST, PUT y DELETE) y
el uso de JSON para las entradas y salidas.
24 Un captcha es una prueba para garantizar que un usuario no sea un robot. Estas pruebas se han utilizado a menudo como una forma barata
forma de etiquetar los datos de entrenamiento.
Machine Translated by Google
Capítulo 3. Clasificación
En el Capítulo 1 mencioné que las tareas de aprendizaje supervisado más comunes son la regresión (predecir
valores) y la clasificación (predecir clases).
En el Capítulo 2, exploramos una tarea de regresión para predecir valores de viviendas, utilizando varios
algoritmos como la regresión lineal, los árboles de decisión y los bosques aleatorios (que se explicarán con
más detalle en capítulos posteriores). Ahora nos centraremos en los sistemas de clasificación.
MNIST
En este capítulo, utilizaremos el conjunto de datos MNIST, que es un conjunto de 70.000 pequeñas
imágenes de dígitos escritos a mano por estudiantes de secundaria y empleados de la Oficina del Censo
de los EE. UU. Cada imagen está etiquetada con el dígito que representa. Este conjunto se ha estudiado tanto
que a menudo se lo llama el "hola mundo" del aprendizaje automático: siempre que las personas inventan
un nuevo algoritmo de clasificación, sienten curiosidad por ver cómo funcionará en MNIST, y cualquiera que
aprenda aprendizaje automático se enfrenta a este conjunto de datos tarde o temprano.
Scikit­Learn ofrece muchas funciones auxiliares para descargar conjuntos de datos populares. MNIST
es una de ellas. El siguiente código recupera el conjunto de datos MNIST 1 :
>>> de sklearn.datasets import fetch_openml >>> mnist =
fetch_openml('mnist_784', version=1) >>> mnist.keys()
dict_keys(['datos',
'objetivo', 'nombres_de_funciones', 'DESCR', 'detalles', 'categorías', 'url'])
Los conjuntos de datos cargados por Scikit­Learn generalmente tienen una estructura de diccionario
similar, que incluye lo siguiente:
Una clave DESCR que describe el conjunto de datos
Machine Translated by Google
Una clave de datos que contiene una matriz con una fila por instancia y una columna
por característica
Una clave de destino que contiene una matriz con las etiquetas
Veamos estas matrices:
>>> X, y = mnist["datos"], mnist["objetivo"]
>>> Forma X
(70000, 784)
>>> Forma Y
(70000,)
Hay 70.000 imágenes y cada una tiene 784 características. Esto se debe a que cada imagen
tiene 28 × 28 píxeles y cada característica simplemente representa la intensidad de un
píxel, desde 0 (blanco) hasta 255 (negro). Echemos un vistazo a un dígito del conjunto de
datos. Todo lo que necesita hacer es tomar el vector de características de una instancia,
transformarlo en una matriz de 28 × 28 y mostrarlo utilizando la función imshow() de
Matplotlib:
importar matplotlib como mpl
importar matplotlib.pyplot como plt
algún_dígito = X[0]
alguna_imagen_de_dígito = algún_dígito.reshape(28, 28)
plt.imshow(alguna_imagen_de_dígito, cmap="binario")
plt.axis("desactivado")
plt.show()
Machine Translated by Google
Esto parece un 5, y de hecho eso es lo que nos dice la etiqueta:
>>> y[0] '5'
Tenga en cuenta que la etiqueta es una cadena. La mayoría de los algoritmos de aprendizaje automático esperan números, así
que vamos a convertir y en un entero:
>>> y = y.astype(np.uint8)
Machine Translated by Google
Para darle una idea de la complejidad de la tarea de clasificación, la Figura 3­1 muestra algunas
imágenes más del conjunto de datos MNIST.
Figura 3­1. Dígitos del conjunto de datos MNIST
Pero, ¡espera! Siempre debes crear un conjunto de prueba y dejarlo a un lado antes de
inspeccionar los datos de cerca. El conjunto de datos MNIST en realidad ya está dividido en un
conjunto de entrenamiento (las primeras 60 000 imágenes) y un conjunto de prueba (las últimas
10 000 imágenes):
X_train, X_test, y_train, y_test = X[:60000], X[60000:], y[:60000], y[60000:]
Machine Translated by Google
El conjunto de entrenamiento ya está mezclado para nosotros, lo cual es bueno porque esto
garantiza que todos los pliegues de validación cruzada serán similares (no desea
un pliegue puede faltar algunos dígitos). Además, algunos algoritmos de aprendizaje
Son sensibles al orden de las instancias de entrenamiento y tienen un rendimiento deficiente.
Si se obtienen muchas instancias similares seguidas, mezclar el conjunto de datos garantiza
que esto no sucederá.
2
Entrenamiento de un clasificador binario
Simplifiquemos el problema por ahora e intentemos identificar solo un dígito:
Por ejemplo, el número 5. Este “detector 5” será un ejemplo de un sistema binario.
clasificador, capaz de distinguir entre sólo dos clases, 5 y no 5.
Creemos los vectores de destino para esta tarea de clasificación:
y_tren_5 = (y_tren == 5) y_prueba_5 =
#
Verdadero
para todos los 5,
Falso para todos los demás dígitos
(y_prueba == 5)
Ahora, elijamos un clasificador y entrenémoslo. Un buen lugar para comenzar es con un
Clasificador de descenso de gradiente estocástico (SGD), utilizando Scikit­Learn
Clase SGDClassifier. Este clasificador tiene la ventaja de poder
de manejar conjuntos de datos muy grandes de manera eficiente. Esto se debe en parte a que SGD
se ocupa de las instancias de entrenamiento de forma independiente, una a la vez (lo que también
hace que SGD sea muy adecuado para el aprendizaje en línea), como veremos más adelante.
Crea un SGDClassifier y entrénalo en todo el conjunto de entrenamiento:
desde sklearn.linear_model importar SGDClassifier
sgd_clf = SGDClassifier(estado_aleatorio=42)
sgd_clf.fit(tren_X, tren_Y_5)
CONSEJO
El SGDClassifier se basa en la aleatoriedad durante el entrenamiento (de ahí el nombre)
“estocástico”). Si desea resultados reproducibles, debe configurar el estado aleatorio
parámetro.
Machine Translated by Google
Ahora podemos usarlo para detectar imágenes del número 5:
>>> sgd_clf.predict([algún_dígito])
matriz([Verdadero])
El clasificador supone que esta imagen representa un 5 (Verdadero). ¡Parece que acertó
en este caso particular! Ahora, evaluemos el rendimiento de este modelo.
Medidas de rendimiento
Evaluar un clasificador suele ser mucho más complicado que evaluar un regresor, por
lo que dedicaremos gran parte de este capítulo a este tema. Hay muchas medidas de
rendimiento disponibles, así que ¡prepárese para aprender muchos conceptos y acrónimos
nuevos!
Medición de la precisión mediante validación cruzada
Una buena manera de evaluar un modelo es utilizar la validación cruzada, tal como lo hizo
en el Capítulo 2.
Machine Translated by Google
IMPLEMENTACIÓN DE LA VALIDACIÓN CRUZADA
En ocasiones, necesitarás más control sobre el proceso de validación cruzada
que el que ofrece Scikit­Learn. En estos casos, puedes implementar la validación
cruzada tú mismo. El código siguiente hace más o menos lo mismo que la función
cross_val_score() de Scikit­Learn e imprime el mismo resultado:
desde sklearn.model_selection importar StratifiedKFold desde sklearn.base
importar clonar
skfolds = StratifiedKFold(n_divisiones=3, estado_aleatorio=42)
para train_index, test_index en skfolds.split(X_train, y_train_5): clone_clf = clone(sgd_clf)
X_tren_pliegues = X_tren[índice_tren] y_tren_pliegues =
y_tren_5[índice_tren]
X_prueba_fold = X_train[índice_prueba]
y_prueba_fold = y_train_5[índice_prueba]
clone_clf.fit(X_entrenamiento_pliegues,
y_entrenamiento_pliegues) y_pred =
clone_clf.predict(X_prueba_pliegue) n_correcto
# imprime 0,9502,/0,96565,
= suma(y_pred == y_prueba_pliegue) print(n_correcto
len(y_pred))
y
0,96495
La clase StratifiedKFold realiza un muestreo estratificado (como se explica
en el Capítulo 2) para producir pliegues que contienen una proporción representativa
de cada clase. En cada iteración, el código crea un clon del clasificador, entrena
ese clon en los pliegues de entrenamiento y realiza predicciones en el pliegue de
prueba. Luego, cuenta la cantidad de predicciones correctas y genera la proporción
de predicciones correctas.
Utilicemos la función cross_val_score() para evaluar nuestro modelo
SGDClassifier, utilizando la validación cruzada de K­fold con tres pliegues.
Recuerde que la validación cruzada de K­fold significa dividir el conjunto de
entrenamiento en K­folds (en este caso, tres), luego hacer predicciones y evaluar
Machine Translated by Google
ellos en cada pliegue usando un modelo entrenado en los pliegues restantes (ver
Capítulo 2):
>>> de sklearn.model_selection importar cross_val_score >>>
cross_val_score(sgd_clf, X_train, y_train_5, cv=3, scoring="precisión")
array([0.96355, 0.93795,
0.95615])
¡Guau! ¿Más del 93 % de precisión (proporción de predicciones correctas) en todos los
pliegues de validación cruzada? Parece increíble, ¿no? Bueno, antes de que te emociones
demasiado, veamos un clasificador muy tonto que simplemente clasifica cada imagen en la clase
"no 5":
desde sklearn.base importar BaseEstimator
clase Never5Classifier(BaseEstimator): def fit(self,
X, y=Ninguno):
pasar
def predict(self, X): devolver
np.zeros((len(X), 1), dtype=bool)
¿Puedes adivinar la precisión de este modelo? Vamos a averiguarlo:
>>> never_5_clf = Never5Classifier() >>>
cross_val_score(never_5_clf, X_train, y_train_5, cv=3, scoring="precisión")
array([0.91125, 0.90855,
0.90915])
Así es, ¡tiene una precisión de más del 90%! Esto se debe simplemente a que solo alrededor
del 10% de las imágenes son 5, por lo que si siempre adivinas que una imagen no es un 5,
acertarás aproximadamente el 90% de las veces. Supera a Nostradamus.
Esto demuestra por qué la precisión generalmente no es la medida de rendimiento
preferida para los clasificadores, especialmente cuando se trabaja con conjuntos de datos
sesgados (es decir, cuando algunas clases son mucho más frecuentes que otras).
Matriz de confusión
Machine Translated by Google
Una forma mucho mejor de evaluar el rendimiento de un clasificador es observar la matriz
de confusión. La idea general es contar la cantidad de veces que las instancias de la
clase A se clasifican como clase B. Por ejemplo, para saber la cantidad de veces que
el clasificador confundió imágenes de 5 con 3, se debe observar la quinta fila y la tercera
columna de la matriz de confusión.
Para calcular la matriz de confusión, primero debe tener un conjunto de
predicciones para poder compararlas con los objetivos reales. Puede hacer predicciones
en el conjunto de prueba, pero no lo tocaremos por ahora (recuerde que desea
utilizar el conjunto de prueba solo al final de su proyecto, una vez que tenga un
clasificador que esté listo para ejecutar). En cambio, puede utilizar la función
cross_val_predict():
desde sklearn.model_selection importar cross_val_predict
y_train_pred = cross_val_predict(sgd_clf, X_train, y_train_5, cv=3)
Al igual que la función cross_val_score(), cross_val_predict() realiza una
validación cruzada de K­fold, pero en lugar de devolver las puntuaciones de evaluación,
devuelve las predicciones realizadas en cada fold de prueba. Esto significa que se
obtiene una predicción limpia para cada instancia en el conjunto de entrenamiento
(“limpia” significa que la predicción la realiza un modelo que nunca vio los datos
durante el entrenamiento).
Ahora está listo para obtener la matriz de confusión mediante la
función confusion_matrix(). Simplemente, pásele las clases de destino (y_train_5) y las
clases predichas (y_train_pred):
>>> de sklearn.metrics importar matriz_confusión >>>
matriz_confusión(y_train_5, y_train_pred) matriz([[53057,
1522], [ 1325, 4096]])
Cada fila de una matriz de confusión representa una clase real, mientras que cada
columna representa una clase prevista. La primera fila de esta matriz considera imágenes
distintas de 5 (la clase negativa): 53.057 de ellas se clasificaron correctamente
como distintas de 5 (se denominan verdaderos negativos), mientras que el resto
Machine Translated by Google
1.522 fueron clasificados erróneamente como 5 (falsos positivos). La segunda fila
considera las imágenes de 5 (la clase positiva): 1.325 fueron clasificadas
erróneamente como no 5 (falsos negativos), mientras que las 4.096 restantes fueron
clasificadas correctamente como 5 (verdaderos positivos). Un clasificador perfecto tendría
solo verdaderos positivos y verdaderos negativos, por lo que su matriz de confusión tendría
valores distintos de cero solo en su diagonal principal (de arriba a la izquierda a abajo a la derecha):
alcanzó
pretender #
>>> y_train_predicciones_perfectas = y_train_5
perfección
>>> matriz_confusión(y_train_5, y_train_predicciones_perfectas) matriz([[54579,
0],
[
0, 5421]])
nosotros
La matriz de confusión proporciona mucha información, pero a veces es preferible una
métrica más concisa. Una métrica interesante es la precisión de las predicciones
positivas; esto se denomina precisión del clasificador (ecuación 3­1).
Ecuación 3­1. Precisión
precisión =
TP
TP + FP
TP es el número de verdaderos positivos y FP es el número de falsos positivos.
Una forma trivial de lograr una precisión perfecta es hacer una única predicción
positiva y asegurarse de que sea correcta (precisión = 1/1 = 100 %). Pero esto no
sería muy útil, ya que el clasificador ignoraría todos los casos positivos menos uno.
Por lo tanto, la precisión se suele utilizar junto con otra métrica denominada recuperación,
también llamada sensibilidad o tasa de verdaderos positivos (TPR): esta es la proporción de
casos positivos que el clasificador detecta correctamente (ecuación 3­2).
Ecuación 3­2. Recordatorio
recordar =
TP
TP + FN
Machine Translated by Google
FN es, por supuesto, el número de falsos negativos.
Si está confundido acerca de la matriz de confusión, la Figura 3­2 puede ayudarlo.
Figura 3­2. Una matriz de confusión ilustrada muestra ejemplos de verdaderos negativos (arriba a la izquierda), falsos negativos (arriba a la izquierda) y negativos (arriba a la derecha).
positivos (arriba a la derecha), falsos negativos (abajo a la izquierda) y verdaderos positivos (abajo a la derecha)
Precisión y recuperación
Scikit­Learn proporciona varias funciones para calcular métricas del clasificador,
Incluyendo precisión y recuperación:
>>> de sklearn.metrics importa precision_score, recall_score
#
>>> puntuación_precisión(y_train_5, y_train_pred) 0,7290850836596654
>>> puntuación de recuperación (y_train_5, y_train_pred)
#
+
== 4096 / (4096
== 4096 / (4096
+
1522)
1325)
0,7555801512636044
Ahora su detector de 5 no luce tan brillante como cuando lo miró
Su exactitud. Cuando afirma que una imagen representa un 5, sólo es correcto.
El 72,9% de las veces. Además, solo detecta el 75,6% de los 5.
A menudo resulta conveniente combinar precisión y recuperación en una única métrica.
llamado puntaje
1 F, en particular si necesita una forma sencilla de comparar dos
Clasificadores. La puntuación
F es la media armónica de precisión y recuperación.
1
(Ecuación 3­3). Mientras que la media regular trata todos los valores por igual, la
Machine Translated by Google
La media armónica otorga mucho más peso a los valores bajos. Como resultado, el clasificador
solo obtendrá una puntuación F alta si tanto
1 la recuperación como la precisión son altas.
Ecuación 3­3.F 1
F1 =
2
1 + precisión
1
recuerdo
=2×
precisión × recuperación
TP
=
precisión + recuperación
TP+
FN+FP 2
Para calcular la puntuación
1 F, simplemente llame a la función f1_score():
>>> de sklearn.metrics importar f1_score >>> f1_score(y_train_5,
y_train_pred) 0.7420962043663375
La puntuación
F favorece a los clasificadores que tienen una precisión y una recuperación
1
similares. Esto no siempre es lo que se desea: en algunos contextos, lo que más nos importa
es la precisión, y en otros contextos, lo que más nos importa es la recuperación. Por ejemplo, si
entrenamos un clasificador para detectar vídeos que son seguros para los niños, probablemente
preferiríamos un clasificador que rechazara muchos vídeos buenos (baja recuperación) pero que
conservara solo los seguros (alta precisión), en lugar de un clasificador que tuviera una
recuperación mucho mayor pero que permitiera que aparecieran unos pocos vídeos
realmente malos en nuestro producto (en tales casos, es posible que incluso queramos añadir
un proceso humano para comprobar la selección de vídeos del clasificador). Por otro lado,
supongamos que entrenamos un clasificador para detectar ladrones de tiendas en imágenes de
vigilancia: probablemente esté bien si nuestro clasificador tiene solo un 30 % de precisión siempre
que tenga un 99 % de recuperación (claro, los guardias de seguridad recibirán algunas
alertas falsas, pero atraparán a casi todos los ladrones de tiendas).
Lamentablemente, no se puede tener ambas cosas: aumentar la precisión reduce la recuperación,
y viceversa. Esto se denomina el equilibrio entre precisión y recuperación.
Relación entre precisión y recuperación
Para entender esta disyuntiva, veamos cómo el SGDClassifier toma sus decisiones de
clasificación. Para cada instancia, calcula una puntuación basada en una función de decisión. Si esa
puntuación es mayor que un umbral, asigna la
Machine Translated by Google
instancia a la clase positiva; de lo contrario, la asigna a la clase negativa.
La figura 3­3 muestra algunos dígitos ubicados desde la puntuación más baja a la
izquierda hasta la puntuación más alta a la derecha. Suponga que el umbral
de decisión está ubicado en la flecha central (entre los dos 5): encontrará 4
positivos verdaderos (5 reales) a la derecha de ese umbral y 1 positivo falso (en
realidad un 6). Por lo tanto, con ese umbral, la precisión es del 80 % (4 de 5). Pero
de los 6 5 reales, el clasificador solo detecta 4, por lo que la recuperación es del
67 % (4 de 6). Si aumenta el umbral (muévalo a la flecha de la derecha), el
positivo falso (el 6) se convierte en un negativo verdadero, lo que aumenta
la precisión (hasta el 100 % en este caso), pero un positivo verdadero se convierte
en un negativo falso, lo que reduce la recuperación al 50 %. Por el contrario,
reducir el umbral aumenta la recuperación y reduce la precisión.
Figura 3­3. En este equilibrio entre precisión y recuperación, las imágenes se clasifican según su puntuación
de clasificador y las que superan el umbral de decisión elegido se consideran positivas; cuanto más alto es el
umbral, menor es la recuperación, pero (en general) mayor es la precisión.
Scikit­Learn no le permite establecer el umbral directamente, pero le da acceso
a los puntajes de decisión que utiliza para hacer predicciones. En lugar de llamar al
método predict() del clasificador, puede llamar a su método
decision_function(), que devuelve un puntaje para cada instancia, y luego usar
cualquier umbral que desee para hacer predicciones basadas en esos
montones:
>>> y_scores = sgd_clf.función_decisión([algún_dígito])
>>> y_scores
array([2412.53175101]) >>> umbral =
0 >>> y_algún_dígito_pred =
(y_scores > umbral) array([ Verdadero])
Machine Translated by Google
El SGDClassifier utiliza un umbral igual a 0, por lo que el código anterior
devuelve el mismo resultado que el método predict() (es decir, True). Vamos a plantear
El umbral:
>>> umbral = 8000
>>> y_algún_dígito_pred = (y_puntuaciones > umbral)
>>> y_algunos_dígitos_pred
matriz([Falso])
Esto confirma que al elevar el umbral se reduce el recuerdo. La imagen
En realidad representa un 5, y el clasificador lo detecta cuando el umbral es 0,
Pero lo pierde cuando el umbral se incrementa a 8.000.
¿Cómo se decide qué umbral utilizar? En primer lugar, utilice el
Función cross_val_predict() para obtener las puntuaciones de todas las instancias en el
conjunto de entrenamiento, pero esta vez especifique que desea devolver puntajes de decisión
En lugar de predicciones:
puntuaciones_y = cross_val_predict(sgd_clf, X_train, y_train_5, cv=3,
método="función_de_decisión")
Con estas puntuaciones, utilice la función precision_recall_curve() para
Calcular precisión y recuperación para todos los umbrales posibles:
desde sklearn.metrics importar precision_recall_curve
precisiones, recuperaciones, umbrales = precision_recall_curve(y_train_5,
y_puntuaciones)
Por último, utilice Matplotlib para trazar la precisión y la recuperación como funciones de la
valor umbral (Figura 3­4):
def plot_precision_recall_vs_threshold(precisiones, recuperaciones, umbrales):
plt.plot(umbrales, precisiones[:­1], "b­­", etiqueta="Precisión")
plt.plot(umbrales, recuperaciones[:­1], "g­", etiqueta="Recuperación")
[...]
red
Resalte el umbral y agregue la leyenda, #
eje
etiqueta, y
Machine Translated by Google
plot_precision_recall_vs_threshold(precisiones, recuperaciones, umbrales) plt.show()
Figura 3­4. Precisión y recuperación frente al umbral de decisión
NOTA
Quizás se pregunte por qué la curva de precisión es más irregular que la curva de
recuperación en la Figura 3­4. La razón es que la precisión a veces puede disminuir
cuando se aumenta el umbral (aunque en general aumentará). Para entender por
qué, mire nuevamente la Figura 3­3 y observe lo que sucede cuando comienza desde el
umbral central y lo mueve solo un dígito a la derecha: la precisión pasa de 4/5 (80 %) a 3/4 (75 %).
Por otro lado, el recuerdo sólo puede disminuir cuando se aumenta el umbral, lo que
explica por qué su curva parece suave.
Otra forma de seleccionar un buen equilibrio entre precisión y recuperación es representar gráficamente
la precisión directamente en función de la recuperación, como se muestra en la Figura 3­5 (se
resalta el mismo umbral que antes).
Machine Translated by Google
Figura 3­5. Precisión versus recuperación
Se puede ver que la precisión realmente comienza a caer drásticamente alrededor del 80% de recuperación.
Probablemente querrá seleccionar un equilibrio entre precisión y recuperación justo antes de esa caída,
por ejemplo, alrededor del 60 % de recuperación. Pero, por supuesto, la elección depende de su proyecto.
Supongamos que decide apuntar a una precisión del 90 %. Busca el primer gráfico y descubre que necesita
utilizar un umbral de aproximadamente 8000. Para ser más preciso, puede buscar el umbral más bajo
que le proporcione al menos un 90 % de precisión (np.argmax() le proporcionará el primer índice del valor
máximo, que en este caso significa el primer valor verdadero):
umbral_precisión_90 = umbrales[np.argmax(precisiones >= 0,90)]
~7816
#
Para hacer predicciones (en el conjunto de entrenamiento por ahora), en lugar de llamar al método predict()
del clasificador, puede ejecutar este código:
Machine Translated by Google
y_train_pred_90 = (y_scores >= umbral_90_precision)
Verifiquemos la precisión y la recordación de estas predicciones:
>>> puntuación_precisión(y_train_5, y_train_pred_90) 0,9000380083618396 >>>
puntuación_recuperación(y_train_5,
y_train_pred_90) 0,4368197749492714
¡Genial, tienes un clasificador con una precisión del 90 %! Como puedes ver, es bastante fácil crear
un clasificador con prácticamente cualquier precisión que quieras: solo tienes que establecer un
umbral lo suficientemente alto y listo. Pero espera, no te apresures. ¡Un clasificador de alta
precisión no es muy útil si su recuperación es demasiado baja!
CONSEJO
Si alguien dice: "Alcancemos una precisión del 99%", deberías preguntar: "¿Con qué recall?"
La curva ROC
La curva ROC ( característica operativa del receptor ) es otra herramienta común que se utiliza
con los clasificadores binarios. Es muy similar a la curva de precisión/recuperación, pero en lugar
de representar gráficamente la precisión frente a la recuperación, la curva ROC representa
gráficamente la tasa de verdaderos positivos (otro nombre para la recuperación) frente a la tasa
de falsos positivos (FPR). La FPR es la proporción de instancias negativas que se clasifican
incorrectamente como positivas. Es igual a 1: la tasa de verdaderos negativos (TNR), que es la
proporción de instancias negativas que se clasifican correctamente como negativas.
La TNR también se denomina especificidad. Por lo tanto, la curva ROC representa la sensibilidad
(recuperación) frente a la especificidad .
Para trazar la curva ROC, primero se utiliza la función roc_curve() para calcular el TPR y el FPR
para varios valores de umbral:
desde sklearn.metrics importar roc_curve
fpr, tpr, umbrales = curva_roc(y_train_5, y_scores)
Machine Translated by Google
Luego puedes graficar el FPR contra el TPR usando Matplotlib. Este código
produce la gráfica de la Figura 3­6:
def plot_roc_curve(fpr, tpr, etiqueta=Ninguna):
plt.plot(fpr, tpr, ancho de línea=2, etiqueta=etiqueta)
plt.plot([0, 1], [0, 1], 'k­­') [...]
# Agregar eje
etiquetas y cuadrícula
#
Diagonal discontinua
trazar_curva_roc(fpr, tpr)
plt.mostrar()
Una vez más, existe una disyuntiva: cuanto mayor sea el recall (TPR), más falsos serán los resultados.
positivos (FPR) que produce el clasificador. La línea de puntos representa la
Curva ROC de un clasificador puramente aleatorio; un buen clasificador se mantiene tan lejos
lejos de esa línea lo más posible (hacia la esquina superior izquierda).
Figura 3­6. Esta curva ROC representa gráficamente la tasa de falsos positivos frente a la tasa de verdaderos positivos para todos los
Umbrales posibles; el círculo rojo resalta la proporción elegida (con un 43,68 % de recuperación)
Machine Translated by Google
Una forma de comparar clasificadores es medir el área bajo la curva (AUC). Un
clasificador perfecto tendrá un AUC ROC igual a 1, mientras que un clasificador
puramente aleatorio tendrá un AUC ROC igual a 0,5. Scikit­Learn proporciona una
función para calcular el AUC ROC:
>>> de sklearn.metrics importar roc_auc_score >>> roc_auc_score(y_train_5,
y_scores) 0.9611778893101814
CONSEJO
Dado que la curva ROC es tan similar a la curva de precisión/recuperación (PR), es posible
que se pregunte cómo decidir cuál utilizar. Como regla general, debe preferir la curva PR
siempre que la clase positiva sea poco común o cuando le importen más los falsos positivos
que los falsos negativos. De lo contrario, utilice la curva ROC. Por ejemplo, al observar la
curva ROC anterior (y la puntuación del AUC de ROC), puede pensar que el clasificador es
realmente bueno. Pero esto se debe principalmente a que hay pocos positivos (5) en
comparación con los negativos (no 5). En cambio, la curva PR deja claro que el
clasificador tiene margen de mejora (la curva podría estar más cerca de la esquina superior izquierda).
Ahora, entrenemos un RandomForestClassifier y comparemos su curva ROC y su
puntuación AUC ROC con las del SGDClassifier. Primero, debe obtener puntuaciones
para cada instancia en el conjunto de entrenamiento. Pero debido a la forma en
que funciona (consulte el Capítulo 7), la clase RandomForestClassifier no tiene
un método decision_function(). En cambio, tiene un método predict_proba().
Los clasificadores de Scikit­Learn generalmente tienen uno u otro, o ambos.
El método predict_proba() devuelve una matriz que contiene una fila por
instancia y una columna por clase, cada una conteniendo la probabilidad de que la
instancia dada pertenezca a la clase dada (por ejemplo, 70% de probabilidad de que
la imagen represente un 5):
desde sklearn.ensemble importar RandomForestClassifier
bosque_clf = ClasificadorForestalAleatorio(estado_aleatorio=42)
y_probas_bosque = cross_val_predict(bosque_clf, tren_X, tren_y_5, cv=3, método="predict_proba")
Machine Translated by Google
La función roc_curve() espera etiquetas y puntuaciones, pero en lugar de puntuaciones
Puedes darle probabilidades de clase. Usemos la probabilidad de clase positiva.
según la puntuación:
y_scores_bosque = y_probas_bosque[:, 1]
clase
#
puntaje
= Proba de positivo
fpr_forest, tpr_forest, umbrales_forest =
curva_roc(y_tren_5,y_puntajes_bosque)
Ahora está listo para trazar la curva ROC. Es útil trazar la primera curva ROC
curva también para ver cómo se comparan (Figura 3­7):
plt.plot(fpr, tpr, "b:", etiqueta="SGD")
plot_roc_curve(bosque_fpr, bosque_tpr, "Bosque aleatorio")
plt.legend(loc="abajo a la derecha")
plt.mostrar()
Figura 3­7. Comparación de curvas ROC: el clasificador Random Forest es superior al SGD
clasificador porque su curva ROC está mucho más cerca de la esquina superior izquierda y tiene un AUC mayor
Machine Translated by Google
Como puede ver en la Figura 3­7, la curva ROC de RandomForestClassifier se ve mucho
mejor que la de SGDClassifier: se acerca mucho más a la esquina superior izquierda. Como
resultado, su puntaje ROC AUC también es significativamente mejor:
>>> roc_auc_score(y_train_5, y_scores_forest) 0.9983436731328145
Intente medir los índices de precisión y recuperación: debería obtener un 99,0 % de
precisión y un 86,6 % de recuperación. ¡No está mal!
Ahora ya sabe cómo entrenar clasificadores binarios, elegir la métrica adecuada para
su tarea, evaluar sus clasificadores mediante validación cruzada, seleccionar el
equilibrio entre precisión y recuperación que se ajuste a sus necesidades y utilizar
curvas ROC y puntuaciones ROC AUC para comparar varios modelos. Ahora, intentemos
detectar más que solo los 5.
Clasificación multiclase
Mientras que los clasificadores binarios distinguen entre dos clases, los clasificadores
multiclase (también llamados clasificadores multinomiales) pueden distinguir entre más
de dos clases.
Algunos algoritmos (como los clasificadores SGD, los clasificadores Random Forest y los
clasificadores Bayes ingenuos) son capaces de manejar múltiples clases de forma nativa.
Otros (como los clasificadores de regresión logística o de máquinas de vectores
de soporte) son clasificadores estrictamente binarios. Sin embargo, existen varias
estrategias que se pueden utilizar para realizar una clasificación multiclase con
varios clasificadores binarios.
Una forma de crear un sistema que pueda clasificar las imágenes de dígitos en 10
clases (de 0 a 9) es entrenar 10 clasificadores binarios, uno para cada dígito (un detector
0, un detector 1, un detector 2, etc.). Luego, cuando se desea clasificar una imagen, se
obtiene el puntaje de decisión de cada clasificador para esa imagen y se selecciona la
clase cuyo clasificador genera el puntaje más alto.
Esta estrategia se denomina “ uno contra el resto ” (OvR), también llamada “uno
contra todos”.
Machine Translated by Google
Otra estrategia es entrenar un clasificador binario para cada par de dígitos: uno para distinguir 0 y
1, otro para distinguir 0 y 2, otro para 1 y 2, y así sucesivamente. Esto se llama la estrategia uno
contra uno (OvO). Si hay N clases, necesitas entrenar N × (N – 1) / 2 clasificadores. Para el
problema MNIST, esto significa entrenar 45 clasificadores binarios. Cuando quieres clasificar
una imagen, tienes que ejecutar la imagen a través de los 45 clasificadores y ver qué clase gana la
mayoría de los duelos. La principal ventaja de OvO es que cada clasificador solo necesita ser
entrenado en la parte del conjunto de entrenamiento para las dos clases que debe distinguir.
Algunos algoritmos (como los clasificadores de máquinas de vectores de soporte) no se
adaptan bien al tamaño del conjunto de entrenamiento. Para estos algoritmos, se prefiere
OvO porque es más rápido entrenar muchos clasificadores en conjuntos de entrenamiento
pequeños que entrenar pocos clasificadores en conjuntos de entrenamiento grandes. Sin
embargo, para la mayoría de los algoritmos de clasificación binaria, se prefiere OvR.
Scikit­Learn detecta cuando intentas usar un algoritmo de clasificación binaria para una tarea de
clasificación multiclase y ejecuta automáticamente OvR u OvO, según el algoritmo. Probemos esto
con un clasificador de máquina de vectores de soporte (consulte el Capítulo 5), utilizando la clase
sklearn.svm.SVC:
>>> de sklearn.svm importar SVC >>> svm_clf = SVC()
>>> svm_clf.fit(X_train, y_train)
>>> svm_clf.predict([algún_dígito]) array([5], dtype=uint8)
y_tren, #
y_train_5 no
¡Eso fue fácil! Este código entrena al SVC en el conjunto de entrenamiento utilizando las
clases objetivo originales de 0 a 9 (y_train), en lugar de las clases objetivo de 5 contra el resto
(y_train_5). Luego hace una predicción (una correcta en este caso). En realidad, Scikit­Learn utilizó
la estrategia OvO: entrenó 45 clasificadores binarios, obtuvo sus puntajes de decisión para la
imagen y seleccionó la clase que ganó la mayor cantidad de duelos.
Si llama al método decision_function(), verá que devuelve 10 puntuaciones por instancia (en lugar
de solo 1). Es decir, una puntuación por clase:
Machine Translated by Google
>>> puntuaciones_de_algunos_dígitos = svm_clf.función_de_decisión([algunos_dígitos]) >>>
puntuaciones_de_algunos_dígitos
matriz([[ 2.92492871, 7.02307409, 3.93648529, 0.90117363, 5.96945908,
9.5
, 1.90718593, 8.02755089, ­0.13202708,
4.94216947]])
La puntuación más alta es precisamente la correspondiente a la clase 5:
>>> np.argmax(puntajes_de_algunos_dígitos) 5
>>> svm_clf.classes_ array([0, 1,
2, 3, 4, 5, 6, 7, 8, 9], dtype=uint8) >>> svm_clf.classes_[5] 5
ADVERTENCIA
Cuando se entrena un clasificador, almacena la lista de clases de destino en su atributo
classes_, ordenadas por valor. En este caso, el índice de cada clase en la matriz classes_
coincide convenientemente con la clase misma (por ejemplo, la clase en el índice 5 resulta
ser la clase 5), pero en general no tendrás tanta suerte.
Si desea forzar a Scikit­Learn a utilizar uno contra uno o uno contra el resto, puede
utilizar las clases OneVsOneClassifier o OneVsRestClassifier. Simplemente cree
una instancia y pase un clasificador a su constructor (ni siquiera tiene que ser un
clasificador binario). Por ejemplo, este código crea un clasificador multiclase
utilizando la estrategia OvR, basada en un SVC:
>>> de sklearn.multiclass importar OneVsRestClassifier >>> ovr_clf = OneVsRestClassifier(SVC())
>>> ovr_clf.fit(X_train, y_train) >>> ovr_clf.predict([algún_dígito])
array([5], dtype=uint8) >>> len(ovr_clf.estimators_) 10
Entrenar un SGDClassifier (o un RandomForestClassifier) es igual de...
fácil:
Machine Translated by Google
>>> sgd_clf.fit(tren_X, tren_Y) >>>
sgd_clf.predict([algún_dígito]) array([5],
dtype=uint8)
Esta vez, Scikit­Learn no tuvo que ejecutar OvR ni OvO porque los clasificadores SGD pueden
clasificar directamente las instancias en múltiples clases. El método decision_function() ahora
devuelve un valor por clase. Veamos la puntuación que el clasificador SGD asignó a cada clase:
>>> sgd_clf.decision_function([algún_dígito])
matriz([[­15955.22628, ­38080.96296, ­13326.66695, 573.52692, ­17680.68466,
2412.53175, ­25526.86498, ­12290.15705, ­7946.05205,
­10631.35889]])
Se puede ver que el clasificador está bastante seguro de su predicción: casi todos los puntajes
son en gran medida negativos, mientras que la clase 5 tiene un puntaje de 2412,5.
El modelo tiene una pequeña duda con respecto a la clase 3, que obtiene una puntuación de
573,5. Ahora, por supuesto, desea evaluar este clasificador. Como es habitual, puede utilizar la
validación cruzada. Utilice la función cross_val_score() para evaluar la precisión del SGDClassifier:
>>> cross_val_score(sgd_clf, X_train, y_train, cv=3, scoring="precisión") matriz([0.8489802, 0.87129356,
0.86988048])
Obtiene más del 84 % en todos los pliegues de prueba. Si utilizara un clasificador aleatorio,
obtendría una precisión del 10 %, por lo que no es una puntuación tan mala, pero aún puede obtener
resultados mucho mejores. Simplemente escalando las entradas (como se explica en el Capítulo
2) aumenta la precisión por encima del 89 %:
>>> desde sklearn.preprocessing importar StandardScaler >>> escalador =
StandardScaler()
>>> X_train_scaled = escalador.fit_transform(X_train.astype(np.float64)) >>> cross_val_score(sgd_clf,
X_train_scaled, y_train, cv=3, scoring="precisión") array([0.89707059, 0.8960948,
0.90693604])
Machine Translated by Google
Análisis de errores
Si este fuera un proyecto real, ahora seguirías los pasos en tu
Lista de verificación del proyecto de aprendizaje automático (consulte el Apéndice B). Exploraría los datos
Opciones de preparación, probar varios modelos (seleccionar los mejores y
afinando sus hiperparámetros usando GridSearchCV) y automatizarlos como
tanto como sea posible. Aquí, asumiremos que ha encontrado una solución prometedora.
modelo y desea encontrar formas de mejorarlo. Una forma de hacerlo es
analizar los tipos de errores que comete.
Primero, mira la matriz de confusión. Debes hacer predicciones usando la
Función cross_val_predict(), luego llama a confusion_matrix()
función, tal como lo hiciste antes:
>>> y_train_pred = cross_val_predict(sgd_clf, X_train_scaled, y_train,
cv=3)
>>> conf_mx = matriz_confusión(tren_y, tren_y_pred)
>>> conf_mx
0, 22, 7, 8, 45, 35, 5, 222, [0, 6410, 35, 26, 8, 198, 13],
matriz([[5578,
4, 44, 4, [ 28, 37,
27, 5232, 100, 74, 27, 68, [23, 18, 115, 5254, 2,
354, 11],
209, 26, 38, 373, 73],
45, 12, 5219, 11, 33, [26, 31, 173, 54, 4484,
[ 11, 26, 299, 14,
172],
16,
76, 14, 482, 65],
17, 45, 2, 42, 98, 5556, 3, 123, [20, 10, 53, 27, 50, 13, 3, 5696,
[ 31, 1],
173, 220],
64,
47, 91, 3, 125, 24, [ 24, 18, 29, 67, 116, 39,
[ 17, 11, 5421,
48],
1, 174, 329, 5152]])
Son muchos números. A menudo resulta más cómodo mirar una imagen.
Representación de la matriz de confusión, utilizando matshow() de Matplotlib
función:
plt.matshow(conf_mx, cmap=plt.cm.gray)
plt.mostrar()
1],
Machine Translated by Google
Esta matriz de confusión se ve bastante bien, ya que la mayoría de las imágenes
están en la diagonal principal, lo que significa que se clasificaron correctamente. Los
5 se ven ligeramente más oscuros que los otros dígitos, lo que podría significar que hay
menos imágenes de 5 en el conjunto de datos o que el clasificador no funciona tan bien
con los 5 como con los otros dígitos. De hecho, puedes verificar que ambos casos son así.
Centremos el gráfico en los errores. Primero, hay que dividir cada valor de la matriz
de confusión por la cantidad de imágenes de la clase correspondiente para poder
comparar las tasas de error en lugar de las cantidades absolutas de errores (lo que
haría que las clases abundantes se vieran injustamente mal):
Machine Translated by Google
sumas_filas = conf_mx.suma(eje=1, keepdims=Verdadero)
norma_conf_mx = conf_mx / sumas_filas
Rellene la diagonal con ceros para conservar sólo los errores y grafique el resultado:
np.fill_diagonal(norm_conf_mx, 0)
plt.matshow(norm_conf_mx, cmap=plt.cm.gray) plt.show()
Puedes ver claramente los tipos de errores que comete el clasificador. Recuerda que las
filas representan clases reales, mientras que las columnas representan clases previstas.
Machine Translated by Google
Clases. La columna de la clase 8 es bastante brillante, lo que indica que muchas
imágenes se clasifican erróneamente como 8. Sin embargo, la fila de la clase 8 no es
tan mala, lo que indica que los 8 reales en general se clasifican correctamente como 8.
Como puede ver, la matriz de confusión no es necesariamente simétrica. También puede
ver que los 3 y los 5 a menudo se confunden (en ambas direcciones).
El análisis de la matriz de confusión a menudo le brinda información sobre formas
de mejorar su clasificador. Al observar este gráfico, parece que sus esfuerzos
deberían concentrarse en reducir los falsos 8. Por ejemplo, podría intentar recopilar
más datos de entrenamiento para dígitos que parecen 8 (pero no lo son) para que el
clasificador pueda aprender a distinguirlos de los 8 reales. O podría diseñar nuevas
características que ayudarían al clasificador, por ejemplo, escribir un algoritmo para contar
la cantidad de bucles cerrados (por ejemplo, 8 tiene dos, 6 tiene uno, 5 no tiene
ninguno). O podría preprocesar las imágenes (por ejemplo, utilizando Scikit­Image,
Pillow u OpenCV) para hacer que algunos patrones, como los bucles cerrados, se
destaquen más.
Analizar errores individuales también puede ser una buena forma de obtener
información sobre lo que hace el clasificador y por qué falla, pero es más difícil y
requiere más tiempo. Por ejemplo, grafiquemos ejemplos de 3 y 5 (la función
plot_digits() solo usa la función imshow() de Matplotlib; consulte el cuaderno Jupyter
de este capítulo para obtener más detalles):
cl_a, cl_b = 3, 5
X_aa = X_tren[(y_tren == cl_a) & (y_tren_pred == cl_a)]
X_ab = X_tren[(y_tren == cl_a) & (y_tren_pred == cl_b)]
X_ba = X_tren[(y_tren == cl_b) & (y_tren_pred == cl_a)]
X_bb = X_tren[(y_tren == cl_b) & (y_tren_pred == cl_b)]
plt.figure(figsize=(8,8))
plt.subplot(221); dígitos_del_gráfico( X_aa[:25], imágenes_por_fila=5 )
plt.subplot(222); dígitos_del_gráfico (X_ab[:25], imágenes_por_fila=5)
plt.subplot(223); dígitos_del_gráfico(X_ba[:25], imágenes_por_fila=5)
plt.subplot(224); dígitos_del_gráfico(X_bb[:25], imágenes_por_fila=5)
plt.show()
Machine Translated by Google
Los dos bloques de 5 × 5 de la izquierda muestran dígitos clasificados como 3, y los dos
bloques de 5 × 5 de la derecha muestran imágenes clasificadas como 5. Algunos de los
dígitos que el clasificador obtiene mal (es decir, en los bloques de la parte inferior izquierda
y superior derecha) están tan mal escritos que incluso un humano tendría problemas
para clasificarlos (por ejemplo, el 5 en la primera fila y la segunda columna realmente
parece un 3 mal escrito). Sin embargo, la mayoría de las imágenes mal clasificadas nos
parecen errores obvios, y es difícil entender por qué el clasificador cometió el error.
3
Los errores que cometió
fueron los siguientes: utilizamos un SGDClassifier simple,
que es un modelo lineal. Todo lo que hace es asignar un peso por clase a cada píxel
y, cuando ve una nueva imagen, simplemente suma los valores ponderados de los píxeles.
Machine Translated by Google
intensidades para obtener una puntuación para cada clase. Por lo tanto, dado que los 3 y los 5 difieren
solo en unos pocos píxeles, este modelo los confundirá fácilmente.
La principal diferencia entre los números 3 y 5 es la posición de la pequeña línea que une la línea
superior con el arco inferior. Si dibuja un 3 con la unión ligeramente desplazada hacia la izquierda, el
clasificador podría clasificarlo como un 5, y viceversa. En otras palabras, este clasificador es bastante
sensible al desplazamiento y la rotación de la imagen. Por lo tanto, una forma de reducir la
confusión entre los números 3 y 5 sería preprocesar las imágenes para asegurarse de que
estén bien centradas y no demasiado rotadas. Esto probablemente también ayudará a reducir
otros errores.
Clasificación multietiqueta
Hasta ahora, cada instancia siempre ha sido asignada a una sola clase. En algunos casos, es
posible que desee que su clasificador genere múltiples clases para cada instancia. Considere un
clasificador de reconocimiento facial: ¿qué debería hacer si reconoce a varias personas en la misma
imagen? Debería adjuntar una etiqueta por cada persona que reconoce. Digamos que el clasificador
ha sido entrenado para reconocer tres caras, Alice, Bob y Charlie. Entonces, cuando se le muestra
una imagen de Alice y Charlie, debería generar [1, 0, 1] (que significa "Alice sí, Bob no, Charlie sí").
Un sistema de clasificación de este tipo que genera múltiples etiquetas binarias se denomina
sistema de clasificación de etiquetas múltiples .
No profundizaremos todavía en el reconocimiento facial, pero veamos un ejemplo más simple,
solo con fines ilustrativos:
desde sklearn.neighbors importa KNeighborsClassifier
y_tren_grande = (y_tren >= 7)
y_tren_impar = (y_tren % 2 == 1)
y_multilabel = np.c_[y_tren_grande, y_tren_impar]
knn_clf = KNeighborsClassifier()
knn_clf.fit(X_tren, y_multietiqueta)
Este código crea una matriz y_multilabel que contiene dos etiquetas de destino para cada imagen
de dígito: la primera indica si el dígito es grande o no (7, 8,
Machine Translated by Google
o 9), y la segunda indica si es impar o no. Las siguientes líneas crean una instancia
de KNeighborsClassifier (que admite la clasificación de múltiples etiquetas, aunque
no todos los clasificadores lo hacen) y la entrenamos utilizando la matriz de múltiples
objetivos. Ahora puede hacer una predicción y observar que genera dos etiquetas:
>>> knn_clf.predict([algún_dígito])
matriz([[Falso, Verdadero]])
¡Y acierta! El dígito 5 no es grande (Falso) ni impar (Verdadero).
Existen muchas formas de evaluar un clasificador de múltiples etiquetas, y la selección de
la métrica correcta depende realmente de su proyecto. Un enfoque consiste en medir la
puntuación
F de cada etiqueta individual (o cualquier otra métrica de clasificador binario
1
analizada anteriormente) y luego simplemente calcular la puntuación promedio. Este
código calcula la puntuación F1 promedio en todas las etiquetas:
>>> y_train_knn_pred = cross_val_predict(knn_clf, X_train, y_multilabel, cv=3) >>> f1_score(y_multilabel, y_train_knn_pred,
promedio="macro") 0.976410265560605
Esto supone que todas las etiquetas son igualmente importantes, pero puede que no
sea así. En particular, si tiene muchas más imágenes de Alice que de Bob o Charlie, es
posible que desee dar más peso a la puntuación del clasificador en las imágenes de
Alice. Una opción sencilla es dar a cada etiqueta un peso igual a su soporte (es decir, la
cantidad de instancias con esa etiqueta de destino). Para ello, simplemente configure
Average="weighted" en el código anterior.
4
Clasificación de múltiples salidas
El último tipo de tarea de clasificación que vamos a analizar aquí se denomina clasificación
multiclase­multisalida (o simplemente clasificación multisalida). Es
simplemente una generalización de la clasificación multietiqueta, en la que cada etiqueta
puede ser multiclase (es decir, puede tener más de dos valores posibles).
Machine Translated by Google
Para ilustrar esto, construyamos un sistema que elimine el ruido de las imágenes. Tomará como
entrada una imagen digital ruidosa y (con suerte) generará una imagen digital limpia, representada
como una matriz de intensidades de píxeles, al igual que las imágenes MNIST. Observe que
la salida del clasificador es multietiqueta (una etiqueta por píxel) y cada etiqueta puede tener múltiples
valores (la intensidad de los píxeles varía de 0 a 255). Por lo tanto, es un ejemplo de un sistema de
clasificación de múltiples salidas.
NOTA
La línea entre clasificación y regresión a veces es borrosa, como en este ejemplo. Podría decirse
que predecir la intensidad de los píxeles es más parecido a una regresión que a una clasificación.
Además, los sistemas de múltiples salidas no se limitan a las tareas de clasificación; incluso se podría
tener un sistema que genere múltiples etiquetas por instancia, incluidas tanto etiquetas de clase
como etiquetas de valor.
Comencemos por crear los conjuntos de entrenamiento y prueba tomando las imágenes
MNIST y agregando ruido a sus intensidades de píxeles con la función randint() de
NumPy. Las imágenes de destino serán las imágenes originales:
ruido = np.random.randint(0, 100, (len(X_train), 784))
X_train_mod = X_train + ruido ruido =
np.random.randint(0, 100, (len(X_test), 784))
X_test_mod = X_test + ruido
y_train_mod = X_train
y_test_mod = X_test
Echemos un vistazo a una imagen del conjunto de prueba (sí, estamos fisgoneando en los datos de
prueba, por lo que debería estar frunciendo el ceño ahora mismo):
Machine Translated by Google
A la izquierda se encuentra la imagen de entrada con ruido y a la derecha la imagen de
destino limpia. Ahora, entrenemos al clasificador y hagamos que limpie esta imagen:
knn_clf.fit(mod_entrenamiento_X,
mod_entrenamiento_y) dígito_limpio =
knn_clf.predict([mod_prueba_X[algún_índice]]) dígito_limpio(dígito_limpio)
Machine Translated by Google
¡Parece bastante cerca del objetivo! Con esto concluye nuestro recorrido de clasificación.
Ahora debería saber cómo seleccionar buenas métricas para tareas de clasificación, escoger
el equilibrio adecuado entre precisión y recuperación, comparar clasificadores y, de
manera más general, construir buenos sistemas de clasificación para una variedad de tareas.
Ceremonias
Machine Translated by Google
1. Intente crear un clasificador para el conjunto de datos MNIST que alcance una precisión
superior al 97 % en el conjunto de prueba. Sugerencia: el KNeighborsClassifier
funciona bastante bien para esta tarea; solo necesita encontrar buenos
valores de hiperparámetros (intente una búsqueda en cuadrícula en los
hiperparámetros weights y n_neighbors).
2. Escriba una función que pueda desplazar una imagen MNIST en cualquier dirección
5
(izquierda, derecha, arriba o abajo) en un píxel. Luego, para cada imagen del conjunto
de entrenamiento, cree cuatro copias desplazadas (una por dirección) y agréguelas al
conjunto de entrenamiento. Por último, entrene su mejor modelo en este conjunto de
entrenamiento ampliado y mida su precisión en el conjunto de prueba.
¡Deberías observar que tu modelo ahora funciona aún mejor!
Esta técnica de hacer crecer artificialmente el conjunto de entrenamiento se llama
aumento de datos o expansión del conjunto de entrenamiento.
3. Aborda el conjunto de datos del Titanic. Un buen lugar para comenzar es Kaggle.
4. Construya un clasificador de spam (un ejercicio más desafiante):
Descargue ejemplos de spam y jamón de los conjuntos de datos
públicos de Apache SpamAssassin.
Descomprima los conjuntos de datos y familiarícese con el formato de datos.
Divida los conjuntos de datos en un conjunto de entrenamiento y un conjunto de prueba.
Escriba una secuencia de preparación de datos para convertir cada correo
electrónico en un vector de características. Su secuencia de preparación
debe transformar un correo electrónico en un vector (disperso) que indique
la presencia o ausencia de cada palabra posible. Por ejemplo, si
todos los correos electrónicos solo contienen cuatro palabras, "Hola",
"cómo", "eres", "tú", entonces el correo electrónico "Hola, tú, Hola, Hola, tú"
se convertiría en un vector [1, 0, 0, 1] (lo que significa ["Hola" está presente,
"cómo" está ausente, "eres" está ausente, "tú" está presente]), o [3, 0, 0, 2] si
prefiere contar la cantidad de ocurrencias de cada palabra.
Machine Translated by Google
Es posible que desee agregar hiperparámetros a su canal
de preparación para controlar si desea o no eliminar los encabezados de
correo electrónico, convertir cada correo electrónico a minúsculas,
eliminar la puntuación, reemplazar todas las URL con "URL",
reemplazar todos los números con "NÚMERO" o incluso realizar la
derivación (es decir, recortar las terminaciones de las palabras; hay
bibliotecas de Python disponibles para hacer esto).
Por último, pruebe varios clasificadores y vea si puede construir un
excelente clasificador de spam, con alta recuperación y alta precisión.
Las soluciones a estos ejercicios se pueden encontrar en los cuadernos Jupyter
disponibles en https://github.com/ageron/handson­ml2.
1 De forma predeterminada, Scikit­Learn almacena en caché los conjuntos de datos descargados en un directorio llamado
$INICIO/scikit_learn_data.
2 La redistribución de datos puede ser una mala idea en algunos contextos; por ejemplo, si se trabaja con datos
de series temporales (como precios de la bolsa o condiciones climáticas). Analizaremos este tema en los
próximos capítulos.
3 Pero recuerda que nuestro cerebro es un fantástico sistema de reconocimiento de patrones, y nuestro
sistema visual realiza mucho preprocesamiento complejo antes de que cualquier información llegue
a nuestra conciencia, por lo que el hecho de que parezca simple no significa que lo sea.
4 Scikit­Learn ofrece algunas otras opciones de promedio y métricas de clasificador de etiquetas múltiples; consulte
documentación para más detalles.
5 Puede utilizar la función shift() del módulo scipy.ndimage.interpolation.
Por ejemplo, shift(image, [2, 1], cval=0) desplaza la imagen dos píxeles hacia abajo y un píxel hacia la derecha.
Machine Translated by Google
Capítulo 4. Modelos de entrenamiento
Hasta ahora hemos tratado los modelos de aprendizaje automático y sus algoritmos de
entrenamiento como cajas negras. Si has realizado algunos de los ejercicios de los
capítulos anteriores, es posible que te hayas sorprendido de lo mucho que puedes hacer sin
saber nada de lo que hay debajo del capó: has optimizado un sistema de regresión, has
mejorado un clasificador de imágenes de dígitos e incluso has creado un clasificador de
spam desde cero, todo esto sin saber cómo funcionan realmente. De hecho, en muchas situaciones
no necesitas conocer los detalles de implementación.
Sin embargo, comprender bien cómo funcionan las cosas puede ayudarlo a encontrar
rápidamente el modelo adecuado, el algoritmo de entrenamiento correcto que debe usar y un
buen conjunto de hiperparámetros para su tarea. Comprender lo que hay debajo del capó también
lo ayudará a depurar problemas y realizar análisis de errores de manera más eficiente. Por
último, la mayoría de los temas tratados en este capítulo serán esenciales para comprender,
construir y entrenar redes neuronales (que se analizan en la Parte II de este libro).
En este capítulo comenzaremos analizando el modelo de regresión lineal, uno de los modelos más
simples que existen. Analizaremos dos formas muy diferentes de entrenarlo:
Utilizando una ecuación directa de “forma cerrada” que calcula directamente los
parámetros del modelo que mejor ajustan el modelo al conjunto de entrenamiento (es
decir, los parámetros del modelo que minimizan la función de costo en el conjunto de
entrenamiento).
Utilizando un enfoque de optimización iterativo llamado Descenso de gradiente (GD)
que modifica gradualmente los parámetros del modelo para minimizar la función de
costo en el conjunto de entrenamiento, convergiendo finalmente al mismo conjunto
de parámetros que el primer método. Veremos algunas variantes del Descenso de
gradiente que utilizaremos una y otra vez cuando estudiemos redes neuronales en la
Parte II: GD por lotes, GD por minilotes y GD estocástico.
Machine Translated by Google
A continuación, analizaremos la regresión polinómica, un modelo más complejo que puede adaptarse
a conjuntos de datos no lineales. Dado que este modelo tiene más parámetros que la regresión
lineal, es más propenso a sobreajustar los datos de entrenamiento, por lo que veremos cómo detectar
si este es el caso o no mediante curvas de aprendizaje y, luego, analizaremos varias técnicas de
regularización que pueden reducir el riesgo de sobreajustar el conjunto de entrenamiento.
Por último, veremos dos modelos más que se utilizan comúnmente para tareas de
clasificación: Regresión logística y Regresión Softmax.
ADVERTENCIA
En este capítulo se tratarán bastantes ecuaciones matemáticas, en las que se utilizarán
nociones básicas de álgebra lineal y cálculo. Para comprender estas ecuaciones,
necesitará saber qué son los vectores y las matrices; cómo transponerlos, multiplicarlos e
invertirlos; y qué son las derivadas parciales. Si no está familiarizado con estos
conceptos, consulte los tutoriales introductorios de álgebra lineal y cálculo disponibles
como cuadernos Jupyter en el material complementario en línea. Para aquellos que son
realmente alérgicos a las matemáticas, aún así deberían leer este capítulo y simplemente
omitir las ecuaciones; con suerte, el texto será suficiente para ayudarlos a comprender la mayoría de los conceptos.
Regresión lineal
En el Capítulo 1 analizamos un modelo de regresión simple de la satisfacción con la vida:
satisfacción_con_la_vida =0θ + θ ×
1 PIB_per_cápita.
Este modelo es simplemente una función lineal de la característica de entrada PIB per cápita. θ y θ 0
son los 1parámetros del modelo.
De manera más general, un modelo lineal realiza una predicción simplemente calculando una
suma ponderada de las características de entrada, más una constante llamada término de sesgo
(también llamado término de intersección), como se muestra en la ecuación 4­1.
Ecuación 4­1. Predicción del modelo de regresión lineal
yˆ = θ0 + θ1x1 + θ2x2 +
En esta ecuación:
+ θnxn
Machine Translated by Google
ŷ es el valor previsto.
n es el número de características.
El
x ies el valor de la característica i .
El
0
θyoes el parámetro del modelo j (incluido el término de sesgo θ y los pesos
de
,
,
las características θ θ12
, θ ).
norte
Esto se puede escribir de forma mucho más concisa utilizando una forma vectorizada, como se
muestra en la Ecuación 4­2.
Ecuación 4­2. Predicción del modelo de regresión lineal (forma vectorizada)
yˆ = hθ(x) = θ
x
En esta ecuación:
θ es el vector de parámetros del modelo , que contiene el término de sesgo θ0y los
1θaθ .
pesos de las características
norte
0 x
x es el vector de características de la instancia, que contiene
uno ,
0
con x siempre
a x igual a 1.
θ ∙ x es el producto escalar de los vectores θ y x, que por supuesto es igual a
θ0x0 + θ1x1 + θ2x2 +
+ θnxn.
h θes la función de hipótesis, utilizando los parámetros del modelo θ.
NOTA
En el aprendizaje automático, los vectores suelen representarse como vectores columna, que
son matrices 2D con una sola columna. Si θ y x son vectores columna, entonces la
predicción es yˆ = θ
x, donde θ
es la transpuesta de θ (un vector fila en lugar de un vector
columna) y θ x es la multiplicación matricial de θ
y x. Por supuesto, es la misma predicción,
excepto que ahora se representa como una matriz de una sola celda en lugar de un valor escalar.
En este libro, utilizaré esta notación para evitar cambiar entre productos escalares
y multiplicaciones matriciales.
Bien, ese es el modelo de regresión lineal, pero ¿cómo lo entrenamos? Bueno, recuerde que
entrenar un modelo significa configurar sus parámetros para que el modelo se ajuste mejor
Machine Translated by Google
el conjunto de entrenamiento. Para ello, primero necesitamos una medida de qué tan
bien (o mal) el modelo se ajusta a los datos de entrenamiento. En el Capítulo 2 vimos
que la medida de rendimiento más común de un modelo de regresión es el error
cuadrático medio (RMSE) (Ecuación 2­1). Por lo tanto, para entrenar un modelo de regresión
lineal, necesitamos encontrar el valor de θ que minimice el RMSE. En la práctica, es más
sencillo minimizar el error cuadrático medio (MSE) que el RMSE, y conduce al mismo
resultado (porque el valor que minimiza una función también minimiza su raíz cuadrada).
1
El MSE de una hipótesis de regresión lineal h en un conjunto
de entrenamiento X
θ
se calcula utilizando la ecuación 4­3.
Ecuación 4­3. Función de costo MSE para un modelo de regresión lineal
MSE (X, hθ) =
1
metro
2
metro
(i))
∑(θ
yo=1
x (i) − y
La mayoría de estas notaciones se presentaron en el Capítulo 2 (ver “Notaciones”). La
única diferencia es que escribimos h en θlugar de solo h para dejar en claro que el modelo
está parametrizado por el vector θ. Para simplificar las notaciones, simplemente
escribiremos MSE(θ) en lugar de MSE(X, hθ ).
La ecuación normal
Para hallar el valor de θ que minimice la función de costo, existe una solución en forma
cerrada, es decir, una ecuación matemática que da el resultado directamente. Esta
se denomina ecuación normal (ecuación 4­4).
Ecuación 4­4. Ecuación normal
­1
ˆθ = (X
X)
X
y
En esta ecuación:
ˆθ es el valor de θ que minimiza la función de costo.
(m) y es el vector de valores objetivo que contienen y a(1)
y.
Machine Translated by Google
Generemos algunos datos de aspecto lineal para probar esta ecuación (Figura 4­1):
importar numpy como np
X = 2 * np.random.rand(100, 1)
y = 4 + 3 * X + np.random.randn(100, 1)
Figura 4­1. Conjunto de datos lineales generados aleatoriamente
Ahora calculemos ˆθ usando la ecuación normal. Usaremos la función inv()
función del módulo de álgebra lineal de NumPy (np.linalg) para calcular la
inversa de una matriz y el método dot() para la multiplicación de matrices:
añadir #x0
X_b = np.c_[np.ones((100, 1)), X] theta_mejor
= np.linalg.inv(X_b.T.punto(X_b)).punto(X_b.T).punto(y)
= 1 a
cada instancia
La función que utilizamos para generar los datos es y = 4 + 3x + 1Gaussian
Ruido. Veamos qué encontró la ecuación:
>>> theta_mejor
matriz([[4.21509616],
[2.77011339]])
Machine Translated by Google
Habríamos esperado que θ = 4 y θ0 = 3 en lugar1de θ = 4,215 y θ = 0
2.770. Bastante cerca, pero el ruido hizo imposible recuperar la precisión.
1
parámetros de la función original.
Ahora podemos hacer predicciones usando ˆθ:
>>> X_nuevo = np.array([[0], [2]])
>>> X_nuevo_b = np.c_[np.ones((2, 1)), X_nuevo] >>>
y_predicción = X_nuevo_b.punto(theta_mejor)
>>> y_predecir
matriz([[4.21509616],
[9.75532293]])
#
agregar
x0 = 1 a
Cada instancia
Grafiquemos las predicciones de este modelo (Figura 4­2):
plt.plot(X_nuevo, y_predicción, "r­")
plt.plot(X, y, "b.")
plt.eje([0, 2, 0, 15])
plt.mostrar()
Figura 4­2. Predicciones del modelo de regresión lineal
Realizar una regresión lineal con Scikit­Learn es sencillo:
2
Machine Translated by Google
>>> de sklearn.linear_model importar Regresión lineal >>> lin_reg =
Regresión lineal() >>> lin_reg.fit(X, y) >>>
lin_reg.intercept_,
lin_reg.coef_ (matriz([4.21509616]),
matriz([[2.77011339]])) >>> lin_reg.predict(X_new)
matriz([[4.21509616], [9.75532293]])
La clase LinearRegression se basa en la función scipy.linalg.lstsq() (el nombre
significa “mínimos cuadrados”), a la que puedes llamar directamente:
>>> theta_best_svd, residuos, rango, s = np.linalg.lstsq(X_b, y, rcond=1e­6) >>> theta_best_svd
array([[4.21509616],
[2.77011339]])
Esta función calcula ˆθ = X+y, donde X+ es la pseudoinversa de X (específicamente, la
inversa de Moore­Penrose). Puedes usar np.linalg.pinv() para calcular la pseudoinversa
directamente:
>>> np.linalg.pinv(X_b).dot(y)
matriz([[4.21509616],
[2.77011339]])
La pseudoinversa en sí se calcula utilizando una técnica de factorización matricial estándar
llamada Descomposición en Valores Singulares (SVD) que puede descomponer la matriz
del conjunto de entrenamiento X en la multiplicación matricial de tres matrices U Σ V (ver
numpy.linalg.svd()). La pseudoinversa se calcula como X+ = VΣ+U
. Para
calcular la matriz Σ+, el algoritmo toma Σ y establece en cero todos los valores menores
que un valor umbral minúsculo, luego reemplaza todos los valores distintos de cero con su
inversa y, finalmente, transpone la matriz resultante. Este enfoque es más eficiente
que calcular la ecuación normal, además de que maneja bien los casos extremos:
de hecho, la ecuación normal puede no funcionar si la matriz XX no es invertible (es
decir, singular), como si m < n o si algunas características son redundantes, pero la
pseudoinversa siempre está definida.
Machine Translated by Google
Complejidad computacional
La ecuación normal calcula la inversa de X X, que es una matriz (n + 1) × (n + 1) (donde n es
el número de características). La complejidad computacional de invertir una matriz
2.4
de este tipo suele ser de aproximadamente O(n ) a O(n ), según la implementación. En
3
otras palabras, si duplica el número de características, multiplica el tiempo de cálculo por
2.4
aproximadamente 2 = 5,3 a 2 = 8.
3
El método SVD que utiliza la clase LinearRegression de Scikit­Learn es de aproximadamente O(n ).
2
Si duplica el número de características, multiplica el tiempo de cálculo por aproximadamente 4.
ADVERTENCIA
Tanto el método de ecuación normal como el de SVD se vuelven muy lentos cuando la cantidad de
características aumenta (por ejemplo, 100 000). En el lado positivo, ambos son lineales con respecto a la
cantidad de instancias en el conjunto de entrenamiento (son O(m)), por lo que manejan conjuntos de
entrenamiento grandes de manera eficiente, siempre que quepan en la memoria.
Además, una vez que haya entrenado su modelo de regresión lineal (usando la ecuación
normal o cualquier otro algoritmo), las predicciones son muy rápidas: la complejidad
computacional es lineal con respecto tanto a la cantidad de instancias sobre las que desea
hacer predicciones como a la cantidad de características. En otras palabras, hacer predicciones
sobre el doble de instancias (o el doble de características) llevará aproximadamente el doble
de tiempo.
Ahora veremos una forma muy diferente de entrenar un modelo de regresión lineal, que es más
adecuada para casos en los que hay una gran cantidad de características o demasiadas instancias
de entrenamiento para caber en la memoria.
Descenso de gradiente
El descenso de gradiente es un algoritmo de optimización genérico capaz de encontrar
soluciones óptimas para una amplia gama de problemas. La idea general del descenso de gradiente es que
Machine Translated by Google
El descenso consiste en ajustar los parámetros de forma iterativa para minimizar una
función de coste.
Supongamos que estás perdido en las montañas en medio de una densa niebla y que solo puedes
sentir la pendiente del terreno bajo tus pies. Una buena estrategia para llegar al fondo del valle
rápidamente es descender en dirección a la pendiente más pronunciada.
Esto es exactamente lo que hace el Descenso de gradiente: mide el gradiente local de la función de
error con respecto al vector de parámetros θ y va en la dirección del gradiente descendente.
Una vez que el gradiente es cero, ¡ha alcanzado un mínimo!
En concreto, se empieza por llenar θ con valores aleatorios (esto se llama inicialización aleatoria).
Luego se mejora gradualmente, dando un pequeño paso a la vez, en cada paso intentando disminuir
la función de costo (por ejemplo, el MSE), hasta que el algoritmo converge a un mínimo (ver
Figura 4­3).
Figura 4­3. En esta representación del descenso de gradiente, los parámetros del modelo se inicializan aleatoriamente y se
modifican repetidamente para minimizar la función de costo; el tamaño del paso de aprendizaje es proporcional a la pendiente de
la función de costo, por lo que los pasos se vuelven gradualmente más pequeños a medida que los parámetros se acercan a la pendiente.
mínimo
Un parámetro importante en el Descenso de gradiente es el tamaño de los pasos,
determinado por el hiperparámetro de tasa de aprendizaje . Si la tasa de aprendizaje es demasiado
Machine Translated by Google
pequeño, entonces el algoritmo tendrá que pasar por muchas iteraciones
para converger, lo que tomará mucho tiempo (ver Figura 4­4).
Figura 4­4. La tasa de aprendizaje es demasiado pequeña
Por otro lado, si la tasa de aprendizaje es demasiado alta, es posible que saltes del
valle y acabes en el otro lado, posiblemente incluso más arriba de donde estabas
antes. Esto puede hacer que el algoritmo diverja, con valores cada vez
mayores, y no encuentre una buena solución (ver Figura 4­5).
Machine Translated by Google
Figura 4­5. La tasa de aprendizaje es demasiado grande
Por último, no todas las funciones de coste tienen el aspecto de cuencos
bonitos y regulares. Puede haber hoyos, crestas, mesetas y todo
tipo de terrenos irregulares, lo que dificulta la convergencia al mínimo. La
figura 4­6 muestra los dos principales desafíos del descenso de gradiente.
Si la inicialización aleatoria inicia el algoritmo a la izquierda, convergerá a un
mínimo local, que no es tan bueno como el mínimo global. Si comienza a la
derecha, tardará mucho tiempo en cruzar la meseta. Y si se detiene demasiado
pronto, nunca alcanzará el mínimo global.
Machine Translated by Google
Figura 4­6. Problemas del descenso de gradientes
Afortunadamente, la función de costo MSE para un modelo de regresión lineal es una función convexa,
lo que significa que si se eligen dos puntos cualesquiera en la curva, el segmento de línea que los
une nunca cruza la curva. Esto implica que no hay mínimos locales, solo un mínimo global. También
es una función continua con una pendiente que nunca cambia abruptamente. Estos dos
3
hechos tienen una gran consecuencia: se garantiza que el descenso de gradiente se acercará
arbitrariamente al mínimo global (si se espera lo suficiente y si la tasa de aprendizaje no es demasiado
alta).
De hecho, la función de costo tiene la forma de un cuenco, pero puede ser un cuenco alargado si las
características tienen escalas muy diferentes. La Figura 4­7 muestra el Descenso de gradiente en
un conjunto de entrenamiento donde las características 1 y 2 tienen la misma escala (a la izquierda) y
en un conjunto de entrenamiento donde la característica 1 tiene valores mucho más pequeños
4
que la característica 2 (a la derecha).
Machine Translated by Google
Figura 4­7. Descenso de gradiente con (izquierda) y sin (derecha) escala de características
Como se puede ver, a la izquierda el algoritmo de descenso de gradiente va directo al
mínimo, alcanzándolo rápidamente, mientras que a la derecha va primero en una
dirección casi ortogonal a la dirección del mínimo global, y termina con una larga
marcha por un valle casi plano. Finalmente alcanzará el mínimo, pero tardará mucho
tiempo.
ADVERTENCIA
Al utilizar Gradient Descent, debe asegurarse de que todas las funciones tengan una escala similar (por
ejemplo, utilizando la clase StandardScaler de Scikit­Learn), o de lo contrario, tomará mucho más tiempo.
converger.
Este diagrama también ilustra el hecho de que entrenar un modelo significa buscar una
combinación de parámetros del modelo que minimice una función de costo (sobre el
conjunto de entrenamiento). Es una búsqueda en el espacio de parámetros del modelo:
cuantos más parámetros tenga un modelo, más dimensiones tendrá este espacio y más
difícil será la búsqueda: buscar una aguja en un pajar de 300 dimensiones es mucho más
complicado que en 3 dimensiones. Afortunadamente, dado que la función de costo es
convexa en el caso de la regresión lineal, la aguja simplemente está en el fondo del
recipiente.
Descenso de gradiente por lotes
Machine Translated by Google
Para implementar el Descenso de gradiente, es necesario calcular el gradiente de la función de
costo con respecto a cada parámetro del modelo θ j y calcular cuánto
. En otras palabras, tú
cambiará la función de costo si cambia θ j solo un poco. Esto se llama derivada parcial. Es como
preguntar "¿Cuál es la pendiente de la montaña bajo mis pies si miro hacia el este?" y luego hacer la
misma pregunta mirando hacia el norte (y así sucesivamente para todas las demás dimensiones, si
puede imaginar un universo con más de tres dimensiones). La ecuación 4­5 calcula la derivada
parcial de la función de costo con respecto al parámetro θ j
∂
, MSE(θ) anotado.
∂θj
Ecuación 4­5. Derivadas parciales de la función de costo
∂
∂θj
2
MSE (θ) =
metro
metro
(i)
xyo=1
∑ (θ
x (i) − y (i))
yo
En lugar de calcular estas derivadas parciales de forma individual, puede utilizar la
ecuación 4­6 para calcularlas todas de una sola vez. El vector de gradiente,
denominado
θ
MSE(θ), contiene todas las derivadas parciales de la función de costo
(una para cada parámetro del modelo).
Ecuación 4­6. Vector gradiente de la función de costo
∂
MSE (θ) ∂θ0
∂
∂θ1 MSE (θ)
θ MSE (θ) =
=X
2
metro
∂
∂θn MSE (θ)
(Xθ − y)
Machine Translated by Google
ADVERTENCIA
Tenga en cuenta que esta fórmula implica cálculos sobre el conjunto de entrenamiento completo X,
en cada paso del Descenso de gradiente. Es por eso que el algoritmo se llama Descenso de gradiente por
lotes: utiliza todo el lote de datos de entrenamiento en cada paso (de hecho, Descenso de gradiente
completo probablemente sería un nombre mejor). Como resultado, es terriblemente lento en conjuntos de
entrenamiento muy grandes (pero veremos algoritmos de Descenso de gradiente mucho más rápidos
en breve). Sin embargo, el Descenso de gradiente escala bien con la cantidad de características; entrenar
un modelo de regresión lineal cuando hay cientos de miles de características es mucho más rápido utilizando
el Descenso de gradiente que utilizando la ecuación normal o la descomposición SVD.
Una vez que se tiene el vector de gradiente, que apunta hacia arriba,
simplemente se va en la dirección opuesta para ir hacia abajo. Esto significa
restar
θ
5
Aquí es donde entra en juego la tasa de aprendizaje η : multiplique el vector de
gradiente por η para determinar el tamaño del paso descendente (Ecuación 4­7).
Ecuación 4­7. Paso de descenso de gradiente
θ (siguiente paso) = θ − η
θ MSE(θ)
Veamos una implementación rápida de este algoritmo:
eta = 0,1
# tasa de aprendizaje
n_iteraciones = 1000 m = 100
theta = np.random.randn(2,1)
# inicialización aleatoria
para iteración en rango(n_iteraciones): gradientes = 2/m *
X_b.T.dot(X_b.dot(theta) ­ y) theta = theta ­ eta * gradientes
¡No fue tan difícil! Veamos el resultado theta:
>>> matriz
theta ([[4.21509616],
[2.77011339]])
¡Oye, eso es exactamente lo que encontró la ecuación normal! El descenso de gradiente funcionó
perfectamente. Pero ¿qué hubiera pasado si hubieras usado una tasa de aprendizaje eta diferente?
MSE(θ) de θ.
Machine Translated by Google
La Figura 4­8 muestra los primeros 10 pasos del Descenso de Gradiente utilizando tres tasas de
aprendizaje diferentes (la línea discontinua representa el punto de partida).
Figura 4­8. Descenso de gradiente con distintas tasas de aprendizaje
A la izquierda, la tasa de aprendizaje es demasiado baja: el algoritmo acabará alcanzando la
solución, pero tardará mucho tiempo. En el medio, la tasa de aprendizaje parece bastante
buena: en tan solo unas pocas iteraciones, ya ha convergido hacia la solución. A la derecha, la
tasa de aprendizaje es demasiado alta: el algoritmo diverge, salta por todos lados y, en realidad,
se aleja cada vez más de la solución en cada paso.
Para encontrar una buena tasa de aprendizaje, puede utilizar la búsqueda en cuadrícula (consulte el Capítulo 2).
Sin embargo, es posible que desee limitar el número de iteraciones para que la búsqueda en
cuadrícula pueda eliminar los modelos que tardan demasiado en converger.
Quizás te preguntes cómo establecer el número de iteraciones. Si es demasiado bajo, seguirás
estando lejos de la solución óptima cuando el algoritmo se detenga; pero si es demasiado alto,
perderás tiempo mientras los parámetros del modelo ya no cambian. Una solución sencilla es
establecer un número muy grande de iteraciones pero interrumpir el algoritmo cuando el vector de
gradiente se vuelve minúsculo, es decir, cuando su norma se vuelve más pequeña que un número
o Descenso de gradiente ha
minúsculo (llamado tolerancia), porque esto sucede cuando el
alcanzado (casi) el mínimo.
Machine Translated by Google
TASA DE CONVERGENCIA
Cuando la función de costo es convexa y su pendiente no cambia abruptamente (como es
el caso de la función de costo MSE), el Descenso de gradiente por lotes con una tasa de
aprendizaje fija eventualmente convergerá a la solución óptima, pero es posible que deba
o
esperar un tiempo: puede tomar O(1/) iteraciones para alcanzar
el óptimo dentro de un
o de la función de costo. Si divide la tolerancia
rango de , dependiendo de la forma
por 10 para tener una solución más precisa, entonces el algoritmo puede tener
que ejecutarse aproximadamente 10 veces más.
Descenso de gradiente estocástico
El principal problema con el descenso de gradiente por lotes es el hecho de que utiliza todo
el conjunto de entrenamiento para calcular los gradientes en cada paso, lo que lo hace muy
lento cuando el conjunto de entrenamiento es grande. En el extremo opuesto, el descenso de
gradiente estocástico elige una instancia aleatoria en el conjunto de entrenamiento en cada
paso y calcula los gradientes basándose únicamente en esa única instancia. Obviamente,
trabajar en una sola instancia a la vez hace que el algoritmo sea mucho más rápido
porque tiene muy pocos datos para manipular en cada iteración. También permite entrenar en
conjuntos de entrenamiento enormes, ya que solo es necesario que haya una instancia en la
memoria en cada iteración (el descenso de gradiente estocástico se puede implementar
como un algoritmo fuera del núcleo; consulte el Capítulo 1).
Por otro lado, debido a su naturaleza estocástica (es decir, aleatoria), este algoritmo es mucho
menos regular que el de descenso por gradiente por lotes: en lugar de disminuir
suavemente hasta alcanzar el mínimo, la función de costo rebotará hacia arriba y hacia
abajo, disminuyendo solo en promedio. Con el tiempo, terminará muy cerca del mínimo, pero
una vez que llegue allí, continuará rebotando, sin estabilizarse nunca (consulte la Figura 4­9).
Por lo tanto, una vez que el algoritmo se detiene, los valores finales de los parámetros
son buenos, pero no óptimos.
Machine Translated by Google
Figura 4­9. Con el descenso de gradiente estocástico, cada paso de entrenamiento es mucho más rápido, pero también mucho más
estocástico que cuando se utiliza el descenso de gradiente por lotes.
Cuando la función de costo es muy irregular (como en la Figura 4­6), esto puede ayudar al
algoritmo a salir de los mínimos locales, por lo que el Descenso de Gradiente Estocástico
tiene más posibilidades de encontrar el mínimo global que el Descenso de Gradiente
por Lotes.
Por lo tanto, la aleatoriedad es buena para escapar de los óptimos locales, pero mala porque
significa que el algoritmo nunca puede estabilizarse en el mínimo. Una solución a este
dilema es reducir gradualmente la tasa de aprendizaje. Los pasos comienzan siendo
grandes (lo que ayuda a avanzar rápidamente y escapar de los mínimos locales), luego se
hacen cada vez más pequeños, lo que permite que el algoritmo se estabilice en el mínimo global.
Este proceso es similar al recocido simulado, un algoritmo inspirado en el proceso de
recocido en metalurgia, en el que el metal fundido se enfría lentamente. La función que
determina la tasa de aprendizaje en cada iteración se denomina programa de
aprendizaje. Si la tasa de aprendizaje se reduce demasiado rápido, puede quedarse
atascado en un mínimo local o incluso terminar congelado a mitad de camino hacia el
Machine Translated by Google
mínimo. Si la tasa de aprendizaje se reduce demasiado lentamente, puede saltar alrededor del
mínimo durante mucho tiempo y terminar con una solución subóptima si detiene el entrenamiento
demasiado pronto.
Este código implementa el descenso de gradiente estocástico utilizando un programa de
aprendizaje simple:
n_épocas = 50 t0,
t1 = 5, 50
hiperparámetros del programa de aprendizaje #
def learning_schedule(t): devuelve
t0 / (t + t1)
theta = np.random.randn(2,1)
# inicialización aleatoria
para época en rango(n_épocas): para i
en rango(m):
índice_aleatorio = np.random.randint(m) xi =
X_b[índice_aleatorio:índice_aleatorio+1] yi =
y[índice_aleatorio:índice_aleatorio+1] gradientes
= 2 * xi.T.dot(xi.dot(theta) ­ yi) eta = programa_aprendizaje(época
* m + i) theta = theta ­ eta * gradientes
Por convención, iteramos por rondas de m iteraciones; cada ronda se denomina época. Mientras
que el código Batch Gradient Descent iteró 1000 veces a través de todo el conjunto de
entrenamiento, este código recorre el conjunto de entrenamiento solo 50 veces y llega a una
solución bastante buena:
>>> theta
matriz([[4.21076011],
[2.74856079]])
La figura 4­10 muestra los primeros 20 pasos del entrenamiento (observe cuán irregulares
son los pasos).
Machine Translated by Google
Figura 4­10. Los primeros 20 pasos del descenso de gradiente estocástico
Tenga en cuenta que, dado que las instancias se seleccionan de forma aleatoria, algunas instancias
pueden seleccionarse varias veces por época, mientras que otras pueden no seleccionarse en
absoluto. Si desea asegurarse de que el algoritmo recorra todas las instancias en cada época, otro
enfoque es mezclar el conjunto de entrenamiento (asegurándose de mezclar las
características de entrada y las etiquetas de forma conjunta), luego recorrerlo instancia por
instancia, luego mezclarlo nuevamente, y así sucesivamente. Sin embargo, este enfoque
generalmente converge más lentamente.
ADVERTENCIA
Al utilizar el Descenso de gradiente estocástico, las instancias de entrenamiento deben ser
independientes y estar distribuidas de manera idéntica (IID) para garantizar que los parámetros se
orienten hacia el óptimo global, en promedio. Una forma sencilla de garantizar esto es mezclar las
instancias durante el entrenamiento (por ejemplo, elegir cada instancia al azar o mezclar el
conjunto de entrenamiento al comienzo de cada época). Si no mezcla las instancias (por ejemplo,
si las instancias están ordenadas por etiqueta), entonces SGD comenzará optimizando para una
etiqueta, luego para la siguiente, y así sucesivamente, y no se asentará cerca del mínimo global.
Machine Translated by Google
Para realizar una regresión lineal mediante GD estocástico con Scikit­Learn, puede utilizar la clase
SGDRegressor, que por defecto optimiza la función de costo de error cuadrático. El siguiente código
se ejecuta durante un máximo de 1000 épocas o hasta que la pérdida se reduzca a menos de 0,001
durante una época (max_iter=1000, tol=1e­3). Comienza con una tasa de aprendizaje de 0,1 (eta0=0,1),
utilizando el programa de aprendizaje predeterminado (distinto del anterior). Por último, no utiliza ninguna
regularización (penalty=None; más detalles sobre esto en breve):
desde sklearn.linear_model importar SGDRegressor
sgd_reg = SGDRegressor(max_iter=1000, tol=1e­3, penalización=Ninguna, eta0=0.1)
sgd_reg.fit(X, y.ravel())
Una vez más, encuentras una solución bastante cercana a la devuelta por la Normal.
Ecuación:
>>> sgd_reg.intercept_, sgd_reg.coef_
(matriz([4.24365286]), matriz([2.8250878]))
Descenso de gradiente en minilotes
El último algoritmo de descenso de gradiente que veremos se llama descenso de gradiente de
minilotes. Es fácil de entender una vez que conoces el descenso de gradiente de lotes y
estocástico: en cada paso, en lugar de calcular los gradientes en función del conjunto de entrenamiento
completo (como en el descenso de gradiente de lotes) o en función de una sola instancia (como en el
descenso de gradiente estocástico), el descenso de gradiente de minilotes calcula los gradientes
en pequeños conjuntos aleatorios de instancias llamados minilotes. La principal ventaja del descenso
de gradiente de minilotes sobre el descenso de gradiente estocástico es que puedes obtener un aumento
de rendimiento a partir de la optimización de hardware de las operaciones matriciales, especialmente cuando se utilizan GPU.
El progreso del algoritmo en el espacio de parámetros es menos errático que con GD
estocástico, especialmente con minilotes bastante grandes. Como resultado, GD en minilotes terminará
caminando un poco más cerca del mínimo que GD estocástico, pero puede ser más difícil para él
escapar de los mínimos locales (en el caso de problemas que sufren de mínimos locales, a diferencia de
la regresión lineal). La Figura 4­11 muestra los caminos tomados por los tres algoritmos de
descenso de gradiente en el espacio de parámetros durante el entrenamiento. Todos terminan
cerca del mínimo, pero el camino de GD por lotes en realidad se detiene en el mínimo, mientras que
Machine Translated by Google
Tanto el GD estocástico como el GD por lotes pequeños siguen funcionando. Sin embargo,
no olvide que el GD por lotes requiere mucho tiempo para dar cada paso, y el GD
estocástico y el GD por lotes pequeños también alcanzarían el mínimo si utilizara un
buen programa de aprendizaje.
Figura 4­11. Trayectorias de descenso de gradiente en el espacio de parámetros
Comparemos los algoritmos que hemos analizado hasta ahora para la regresión lineal
(recuerde que m es el número de instancias de entrenamiento y n es el número de
características); consulte la Tabla 4­1.
6
Machine Translated by Google
Tabla 4­1. Comparación de algoritmos para la regresión lineal
Algoritmo
Normal
Ecuación
Verbo Verbita
Grande
Fuera del núcleo
apoyo
Hiperparámetros
Escalada
Rápido no
Lento 0
No
N/A
Rápido no
Lento 0
No
Regresión lineal
Rápido 2
Sí
Regresor SGD
Rápido sí
Rápido ≥2
Sí
Regresor SGD
Rápido sí
Rápido ≥2
Sí
Regresor SGD
Dios bendiga
Mini­lote
Scikit­Aprende
requerido
Lote GD Lento No
Estocástico
Grande
norte
metro
Dios bendiga
NOTA
Casi no hay diferencia después del entrenamiento: todos estos algoritmos terminan con resultados muy buenos.
modelos similares y hacer predicciones exactamente de la misma manera.
Regresión polinómica
¿Qué sucede si sus datos son más complejos que una línea recta? Sorprendentemente, puede
Utilice un modelo lineal para ajustar datos no lineales. Una forma sencilla de hacerlo es agregar
potencias de cada característica como nuevas características, luego entrenar un modelo lineal en esto
Conjunto ampliado de características. Esta técnica se denomina regresión polinómica.
Veamos un ejemplo. Primero, generemos algunos datos no lineales, basados en
7
una ecuación cuadrática simple (más algo
de ruido; consulte la Figura 4­12):
m = 100
X = 6 * np.random.rand(m, 1) ­ 3
y = 0,5 * X**2 + X + 2 + np.random.randn(m, 1)
Machine Translated by Google
Figura 4­12. Conjunto de datos no lineales y ruidosos generados
Es evidente que una línea recta nunca se ajustará correctamente a estos datos. Por lo tanto, usemos
la clase PolynomialFeatures de Scikit­Learn para transformar nuestros datos de entrenamiento,
agregando el cuadrado (polinomio de segundo grado) de cada característica en el conjunto de entrenamiento
como una nueva característica (en este caso, solo hay una característica):
>>> desde sklearn.preprocessing importar PolynomialFeatures >>>
poly_features = PolynomialFeatures(grado=2, incluir_sesgo=Falso)
>>> X_poly = poly_features.fit_transform(X)
>>> X[0]
matriz([­0,75275929])
>>> Matriz
X_poly[0] ([­0,75275929, 0,56664654])
X_poly ahora contiene la característica original de X más el cuadrado de esta característica.
Ahora puede ajustar un modelo de regresión lineal a estos datos de entrenamiento extendidos (Figura
4­13):
>>> lin_reg = Regresión lineal() >>>
lin_reg.fit(X_poly, y) >>>
lin_reg.intercept_, lin_reg.coef_
(matriz([1.78134581]), matriz([[0.93366893, 0.56456263]]))
Machine Translated by Google
Figura 4­13. Predicciones del modelo de regresión polinómica
2
+ 0,93x1 + 1,78 cuando en realidad el
No está mal: el modelo estima yˆ = 0,56x1
2
+ 1.0x1 + 2.0 + ruido gaussiano.
La función original era y = 0,5x1
Tenga en cuenta que cuando hay múltiples características, la regresión polinomial es capaz
de encontrar relaciones entre características (que es algo que un algoritmo lineal simple)
El modelo de regresión no puede hacerlo). Esto es posible gracias al hecho de que
PolynomialFeatures también suma todas las combinaciones de características hasta el valor dado.
grado. Por ejemplo, si hubiera dos características a y b,
PolynomialFeatures con grado=3 no solo agregaría las características a 2 b
, 3yb
2
23
2
, pero también las combinaciones ab, a b y ab .
ADVERTENCIA
PolynomialFeatures(degree=d) transforma una matriz que contiene n características en una
(n+d)!
matriz que contiene características, donde n! es el factorial de n, igual a 1 × 2 × 3 ×
¡Maldita sea!
× n. ¡Cuidado con la explosión combinatoria del número de características!
,a ,
Machine Translated by Google
Curvas de aprendizaje
Si realiza una regresión polinómica de alto grado, probablemente ajustará los datos de
entrenamiento mucho mejor que con una regresión lineal simple. Por ejemplo, la Figura 4­14
aplica un modelo polinómico de 300 grados a los datos de entrenamiento anteriores y compara el
resultado con un modelo lineal puro y un modelo cuadrático (polinomio de segundo grado).
Observe cómo el modelo polinómico de 300 grados se mueve para acercarse lo más posible a
las instancias de entrenamiento.
Figura 4­14. Regresión polinómica de alto grado
Este modelo de regresión polinómica de alto grado está sobreajustando severamente los
datos de entrenamiento, mientras que el modelo lineal los está subajustando. El modelo que
se generalizará mejor en este caso es el modelo cuadrático, lo que tiene sentido porque
los datos se generaron utilizando un modelo cuadrático. Pero en general, no sabrá qué función
generó los datos, así que ¿cómo puede decidir qué tan complejo debe ser su modelo? ¿Cómo
puede saber si su modelo está sobreajustando o subajustando los datos?
En el Capítulo 2, utilizó la validación cruzada para obtener una estimación del rendimiento
de generalización de un modelo. Si un modelo funciona bien con los datos de entrenamiento
Machine Translated by Google
pero generaliza mal según las métricas de validación cruzada, entonces su modelo está sobreajustado.
Si tiene un desempeño deficiente en ambos, entonces está subajustado.
Esta es una forma de saber cuándo un modelo es demasiado simple o demasiado complejo.
Otra forma de saberlo es observar las curvas de aprendizaje: son gráficos del rendimiento del modelo
en el conjunto de entrenamiento y el conjunto de validación en función del tamaño del conjunto de
entrenamiento (o la iteración de entrenamiento). Para generar los gráficos, entrene el modelo varias
veces en subconjuntos de diferentes tamaños del conjunto de entrenamiento. El siguiente código
define una función que, dados algunos datos de entrenamiento, traza las curvas de aprendizaje de un
modelo:
desde sklearn.metrics importa error_cuadrático_medio
desde sklearn.model_selection importa train_test_split
def plot_learning_curves(modelo, X, y):
X_train, X_val, y_train, y_val = train_test_split(X, y, test_size=0.2) train_errors, val_errors =
[], [] para m en rango(1, len(X_train)):
modelo.fit(X_train[:m], y_train[:m])
y_train_predict = modelo.predict (X_train[:m]) y_val_predict
= modelo.predict(X_val)
errores_de_entrenamiento.append(error_cuadrado_medio(y_train[:m],
y_train_predict))
errores_de_val.append(error_cuadrado_medio(y_val, y_val_predict))
plt.plot(np.sqrt(errores_de_entrenamiento), "r­+", ancho_de_línea=2,
etiqueta="entrenamiento") plt.plot(np.sqrt(errores_de_val), "b­", ancho_de_línea=3, etiqueta="val")
Veamos las curvas de aprendizaje del modelo de regresión lineal simple (una línea recta; consulte
la Figura 4­15):
lin_reg = Regresión lineal()
trazar_curvas_de_aprendizaje(lin_reg, X, y)
Machine Translated by Google
Figura 4­15. Curvas de aprendizaje
Este modelo que no se ajusta lo suficiente merece una pequeña explicación. Primero, veamos el
rendimiento en los datos de entrenamiento: cuando solo hay una o dos instancias en el
conjunto de entrenamiento, el modelo puede ajustarlas perfectamente, por lo que la curva
comienza en cero. Pero a medida que se agregan nuevas instancias al conjunto de entrenamiento,
se vuelve imposible para el modelo ajustarse perfectamente a los datos de entrenamiento,
tanto porque los datos son ruidosos como porque no son lineales en absoluto. Por lo tanto, el
error en los datos de entrenamiento aumenta hasta que alcanza una meseta, momento en el que
agregar nuevas instancias al conjunto de entrenamiento no hace que el error promedio sea
mucho mejor o peor. Ahora veamos el rendimiento del modelo en los datos de validación.
Cuando el modelo se entrena en muy pocas instancias de entrenamiento, es incapaz de
generalizar adecuadamente, por lo que el error de validación inicialmente es bastante grande.
Luego, a medida que se le muestran más ejemplos de entrenamiento al modelo, este aprende y,
por lo tanto, el error de validación disminuye lentamente. Sin embargo, una vez más, una
línea recta no puede hacer un buen trabajo al modelar los datos, por lo que el error termina en
una meseta, muy cerca de la otra curva.
Estas curvas de aprendizaje son típicas de un modelo que no se ajusta bien. Ambas curvas han
llegado a una meseta; están cerca y son bastante altas.
Machine Translated by Google
CONSEJO
Si su modelo no se ajusta a los datos de entrenamiento, no será de ayuda agregar más ejemplos
de entrenamiento. Debe utilizar un modelo más complejo o crear mejores características.
Ahora veamos las curvas de aprendizaje de un modelo polinomial de décimo grado en
los mismos datos (Figura 4­16):
desde sklearn.pipeline importar Pipeline
regresión_polinomial =
Pipeline([ ("características_poli",
Características_polinomiales(grado=10, sesgo_de_inclusión=Falso)), ("reg_lin", Regresión_lineal()),
])
trazar_curvas_de_aprendizaje(regresión_polinómica, X, y)
Figura 4­16. Curvas de aprendizaje para el modelo polinomial de décimo grado
Estas curvas de aprendizaje se parecen un poco a las anteriores, pero hay dos
diferencias muy importantes:
Machine Translated by Google
El error en los datos de entrenamiento es mucho menor que con el modelo de
regresión lineal.
Existe una brecha entre las curvas. Esto significa que el modelo funciona
significativamente mejor con los datos de entrenamiento que con los
datos de validación, lo que es el sello distintivo de un modelo sobreajustado.
Sin embargo, si se utilizara un conjunto de entrenamiento mucho más grande, las
dos curvas seguirían acercándose.
CONSEJO
Una forma de mejorar un modelo de sobreajuste es alimentarlo con más datos de entrenamiento hasta que
el error de validación alcance el error de entrenamiento.
Machine Translated by Google
EL COMPROMISO ENTRE SESGO Y VARIANZA
Un resultado teórico importante de la estadística y el aprendizaje automático es el hecho de que el error
de generalización de un modelo puede expresarse como la suma de tres errores muy diferentes:
Inclinación
Esta parte del error de generalización se debe a suposiciones erróneas, como suponer que los
datos son lineales cuando en realidad son cuadráticos.
Es muy probable que un modelo con alto sesgo no se ajuste a los datos de entrenamiento.8
Diferencia
Esta parte se debe a la excesiva sensibilidad del modelo a pequeñas variaciones en los
datos de entrenamiento. Es probable que un modelo con muchos grados de libertad (como
un modelo polinomial de alto grado) tenga una varianza alta y, por lo tanto, sobreajuste los datos
de entrenamiento.
Error irreducible
Esta parte se debe al ruido de los datos en sí. La única forma de reducir esta parte del error es
limpiar los datos (por ejemplo, reparar las fuentes de datos, como sensores dañados, o detectar y
eliminar valores atípicos).
Aumentar la complejidad de un modelo generalmente aumenta su varianza y reduce su sesgo. Por el
contrario, reducir la complejidad de un modelo aumenta su sesgo y reduce su varianza. Por eso se lo
llama compensación.
Modelos lineales regularizados Como vimos en los capítulos 1
y 2, una buena manera de reducir el sobreajuste es regularizar el modelo (es decir, restringirlo):
cuantos menos grados de libertad tenga, más difícil será que se sobreajuste a los datos. Una forma sencilla
de regularizar un modelo polinómico es reducir el número de grados polinómicos.
En el caso de un modelo lineal, la regularización se logra normalmente limitando los pesos del modelo. Ahora
veremos la regresión de cresta, Lasso
Machine Translated by Google
Regresión y Elastic Net, que implementan tres formas diferentes de restringir los pesos.
Regresión de cresta
La regresión de cresta (también llamada regularización de Tikhonov) es una regresión regularizada
norte
Versión de regresión lineal: se agrega un término de regularización igual a α∑ a la función yo=1 θ yo
2
es
de costo. Esto obliga al algoritmo de aprendizaje no solo a ajustar los datos, sino también a mantener los
pesos del modelo lo más pequeños posible. Tenga en cuenta que el término de regularización solo debe
agregarse a la función de costo durante el entrenamiento.
Una vez entrenado el modelo, debes utilizar la medida de rendimiento no regularizada para evaluar el
rendimiento del modelo.
NOTA
Es bastante común que la función de costo utilizada durante el entrenamiento sea diferente de la medida de
rendimiento utilizada para las pruebas. Además de la regularización, otra razón por la que pueden ser diferentes es que
una buena función de costo de entrenamiento debe tener derivadas compatibles con la optimización, mientras
que la medida de rendimiento utilizada para las pruebas debe ser lo más cercana posible al objetivo final. Por ejemplo,
los clasificadores a menudo se entrenan utilizando una función de costo como la pérdida de registro (que se analizará en
un momento), pero se evalúan utilizando precisión/recuperación.
El hiperparámetro α controla cuánto desea regularizar el modelo.
Si α = 0, la regresión de cresta es simplemente una regresión lineal. Si α es muy grande, todos los pesos terminan
muy cerca de cero y el resultado es una línea plana que atraviesa la media de los datos. La ecuación 4­8 presenta
la función de costo 9 de la regresión de cresta .
Ecuación 4­8. Función de costo de la regresión de cresta
2
J(θ) = MSE(θ) + α ∑n
12
yo=1 θ yo
Tenga en cuenta que el término de0 sesgo θ no está regularizado (la suma comienza en i = 1, no en 0). Si
definimos w como el vector de pesos de características (θ a θ ), entonces
el término de
1
norte
regularización es igual a ½(
w
) , donde
w
2
2representa el ℓ
2
2
Machine Translated by Google
10
Norma del vector de peso. Para el descenso de gradiente, simplemente agregue αw al
vector de gradiente MSE (Ecuación 4­6).
ADVERTENCIA
Es importante escalar los datos (por ejemplo, utilizando un StandardScaler) antes de realizar la
regresión de cresta, ya que es sensible a la escala de las características de entrada. Esto es así en la mayoría
de los modelos regularizados.
La Figura 4­17 muestra varios modelos Ridge entrenados con algunos datos lineales
utilizando diferentes valores α . A la izquierda, se utilizan modelos Ridge simples, lo que
genera predicciones lineales. A la derecha, los datos se expanden
primero utilizando PolynomialFeatures(degree=10), luego se escalan
utilizando un StandardScaler y, finalmente, se aplican los modelos Ridge a las
características resultantes: se trata de una regresión polinómica con regularización Ridge.
Observe cómo el aumento de α genera predicciones más planas (es decir, menos extremas,
más razonables), lo que reduce la varianza del modelo pero aumenta su sesgo.
Figura 4­17. Un modelo lineal (izquierda) y un modelo polinomial (derecha), ambos con varios niveles de regularización de
Ridge
Al igual que con la regresión lineal, podemos realizar la regresión de cresta calculando
una ecuación de forma cerrada o realizando un descenso de gradiente. Las ventajas y
desventajas son las mismas. La ecuación 4­9 muestra la solución de forma cerrada.
Machine Translated by Google
11
donde A es la matriz identidad (n + 1) × (n + 1) , excepto con
un 0 en la celda superior
izquierda, correspondiente al término de sesgo.
Ecuación 4­9. Solución de forma cerrada de regresión de cresta
ˆθ = (X
X + αA)
­1
X
y
A continuación se explica cómo realizar una regresión de cresta con Scikit­Learn utilizando
una solución de forma cerrada (una variante de la ecuación 4­9 que utiliza una técnica de
factorización matricial de André­Louis Cholesky):
>>> de sklearn.linear_model importar Ridge >>> ridge_reg =
Ridge(alpha=1, solver="cholesky") >>> ridge_reg.fit(X, y) >>>
ridge_reg.predict([[1.5]])
array([[1.55071465]])
Y usando el Descenso de Gradiente Estocástico: 12
>>> sgd_reg = SGDRegressor(penalty="l2") >>> sgd_reg.fit(X,
y.ravel()) >>> sgd_reg.predict([[1.5]])
matriz([1.47012588])
El hiperparámetro de penalización establece el tipo de término de regularización a utilizar.
Especificar "l2" indica que desea que SGD agregue un término de regularización a la función
de costo igual a la mitad del cuadrado de la norma ℓ del vector2 de peso: esto es simplemente
regresión de cresta.
Regresión de lazo
La regresión con operador de selección y contracción mínima absoluta (generalmente
llamada simplemente regresión Lasso) es otra versión regularizada de la regresión lineal:
al igual que la regresión Ridge, agrega un término de regularización a la función de costo,
pero utiliza la norma ℓ del vector de peso
1 en lugar de la mitad del cuadrado de la norma ℓ (ver
ecuación 4­10).
2
Ecuación 4­10. Función de costo de la regresión Lasso
Machine Translated by Google
J(θ) = MSE(θ) + α∑ yo=1 |θi|
norte
La Figura 4­18 muestra lo mismo que la Figura 4­17, pero reemplaza los modelos Ridge
con modelos Lasso y utiliza valores α más pequeños .
Figura 4­18. Un modelo lineal (izquierda) y un modelo polinomial (derecha), ambos con distintos niveles de
regularización Lasso
Una característica importante de la regresión Lasso es que tiende a eliminar los pesos de
las características menos importantes (es decir, los establece en cero). Por ejemplo,
la línea discontinua en el gráfico de la derecha de la Figura 4­18 (con α = 10) parece
­7
cuadrática, casi lineal: todos los pesos de las características polinómicas de
alto grado son iguales a cero. En otras palabras, la regresión Lasso realiza
automáticamente la selección de características y genera un modelo disperso (es decir,
con pocos pesos de características distintos de cero).
Puede tener una idea de por qué es así observando la Figura 4­19: los ejes representan
dos parámetros del modelo y los contornos de fondo representan diferentes funciones de
pérdida. En el gráfico superior izquierdo, los contornos representan la pérdida ℓ (|θ | + | 1
θ |), que 1disminuye
linealmente a medida que se acerca a cualquier eje. Por ejemplo,
2
si inicializa los parámetros del modelo a θ = 2 y θ = 0,5,
1
2
Al ejecutar Gradient Descent, ambos parámetros se reducirán por igual (como se
representa con la línea amarilla discontinua); por lo tanto, θ llegará
primero a 0 (ya que
2
estaba más cerca de 0 para empezar). Después de eso, Gradient Descent se desplazará
por el canal hasta que alcance θ 1= 0 (con un poco de rebote, ya que los gradientes de
ℓ nunca se acercan
1 a 0: son ­1 o 1 para cada uno).
Machine Translated by Google
parámetro). En el gráfico superior derecho, los contornos representan la función de costo de
Lasso (es decir, una función de costo de MSE1más una pérdida ℓ). Los pequeños círculos
blancos muestran el camino que toma Gradient Descent para optimizar algunos parámetros
del modelo que se inicializaron 1alrededor de θ = 2
0,25 y θ = –1: observe una vez más cómo el
camino alcanza rápidamente
θ = 0, luego rueda por la cuneta y termina rebotando
2
alrededor del óptimo global (representado por el cuadrado rojo). Si aumentamos α, el óptimo
global se movería a la izquierda a lo largo de la línea amarilla discontinua, mientras que si
disminuimos α, el óptimo global se movería a la derecha (en este ejemplo, los parámetros
óptimos para el MSE no regularizado son θ = 2 y θ = 0,5).
1
2
Figura 4­19. Regularización Lasso versus Ridge
Los dos gráficos inferiores muestran lo mismo pero con una penalización
2 de ℓ.
En el gráfico inferior izquierdo, se puede ver que la pérdida
de ℓ disminuye con la distancia al
2
origen, por lo que el Descenso de Gradiente simplemente toma un camino recto hacia ese punto.
En el gráfico inferior derecho, los contornos representan la función de costo de la
regresión de cresta (es decir, una función de costo de MSE
2 más una pérdida ℓ). Hay dos
Machine Translated by Google
Diferencias con Lasso. En primer lugar, los gradientes se hacen más pequeños a medida que los parámetros
se acercan al óptimo global, por lo que el descenso del gradiente se ralentiza naturalmente, lo que ayuda a
la convergencia (ya que no hay rebotes). En segundo lugar, los parámetros óptimos (representados por
el cuadrado rojo) se acercan cada vez más al origen cuando se aumenta α, pero nunca se eliminan por completo.
CONSEJO
Para evitar que el Descenso de Gradiente rebote alrededor del óptimo al final al usar Lasso,
debe reducir gradualmente la tasa de aprendizaje durante el entrenamiento (seguirá rebotando
alrededor del óptimo, pero los pasos serán cada vez más pequeños, por lo que convergerá).
La función de costo Lasso no es diferenciable en θ = 0 (para i = 1, 2,
, n),
i pero el Descenso de Gradiente aún
13
funciona bien si usa un vector de subgradiente g en su lugar cuando cualquier θ = 0. La ecuación 4­11 muestra
una ecuación de ivector de subgradiente que puede usar para el Descenso de Gradiente con la función de costo
Lasso.
Ecuación 4­11. Vector de subgradiente de regresión Lasso
signo(θ1)
−1 si θi < 0
signo(θ2)
g(θ, J) =
θ MSE(θ) + α
donde signo(θi) =
0 si θi = 0
+1 si θi > 0
signo(θn)
A continuación se muestra un pequeño ejemplo de Scikit­Learn que utiliza la clase Lasso:
>>> de sklearn.linear_model importar Lasso >>>
lasso_reg = Lasso(alfa=0.1) >>>
lasso_reg.fit(X, y) >>>
lasso_reg.predict([[1.5]])
array([1.53788174])
Tenga en cuenta que en su lugar podría utilizar SGDRegressor(penalty="l1").
Red elástica
Machine Translated by Google
Elastic Net es un término intermedio entre la regresión de Ridge y la regresión de
Lasso. El término de regularización es una simple combinación de los términos de
regularización de Ridge y Lasso, y se puede controlar la relación de combinación r. Cuando r
= 0, Elastic Net es equivalente a la regresión de Ridge, y cuando r = 1, es equivalente
a la regresión de Lasso (consulte la ecuación 4­12).
Ecuación 4­12. Función de costo neto elástico
J(θ) = MSE(θ) + rα∑
norte
yo=1 |θi|+ α∑
1−r
2
norte
2
yo=1 θ yo
Entonces, ¿cuándo debería utilizar la regresión lineal simple (es decir, sin ninguna
regularización), Ridge, Lasso o Elastic Net? Casi siempre es preferible tener al menos un poco
de regularización, por lo que, en general, debería evitar la regresión lineal simple. Ridge es
una buena opción predeterminada, pero si sospecha que solo algunas características son útiles,
debería preferir Lasso o Elastic Net porque tienden a reducir los pesos de las características
inútiles a cero, como hemos comentado. En general, se prefiere Elastic Net sobre Lasso
porque Lasso puede comportarse de forma errática cuando la cantidad de características es
mayor que la cantidad de instancias de entrenamiento o cuando varias características están
fuertemente correlacionadas.
A continuación se muestra un breve ejemplo que utiliza ElasticNet de Scikit­Learn (l1_ratio
corresponde a la relación de mezcla r):
>>> desde sklearn.linear_model importar ElasticNet >>> elastic_net
= ElasticNet(alfa=0,1, relación l1=0,5) >>> elastic_net.fit(X, y) >>>
elastic_net.predict([[1,5]])
matriz([1,54333232])
Parada temprana
Una forma muy diferente de regularizar algoritmos de aprendizaje iterativo como el
Descenso de gradiente es detener el entrenamiento tan pronto como el error de validación
alcanza un mínimo. Esto se llama detención temprana. La Figura 4­20 muestra un modelo
complejo (en este caso, un modelo de regresión polinómica de alto grado) que se está
entrenando con el Descenso de gradiente por lotes. A medida que pasan las épocas, el
algoritmo aprende y su error de predicción (RMSE) en el conjunto de entrenamiento
disminuye, junto con su error de predicción en el conjunto de validación. Sin embargo,
después de un tiempo, el error de validación deja de disminuir y comienza a aumentar nuevamente. Esto indica qu
Machine Translated by Google
ha comenzado a sobreajustar los datos de entrenamiento. Con la detención temprana, simplemente se detiene el
entrenamiento tan pronto como el error de validación alcanza el mínimo. Es una técnica de regularización tan simple
y eficiente que Geoffrey Hinton la llamó un "hermoso almuerzo gratis".
Figura 4­20. Regularización por detención temprana
CONSEJO
Con el descenso de gradiente estocástico y de minilotes, las curvas no son tan suaves y puede
resultar difícil saber si se ha alcanzado el mínimo o no. Una solución es detenerse solo después de
que el error de validación haya estado por encima del mínimo durante algún tiempo (cuando se
esté seguro de que el modelo no mejorará) y luego revertir los parámetros del modelo al punto
en el que el error de validación era mínimo.
A continuación se muestra una implementación básica de detención anticipada:
desde sklearn.base importar clon
# preparar
Los datos
poly_scaler = Tubería([
("características_poligonales", Características_poligonales(grado=90,
Machine Translated by Google
incluir_sesgo=Falso)),
("std_scaler", StandardScaler())
])
X_train_poly_scaled = poly_scaler.fit_transform(X_train)
X_val_poly_scaled = poly_scaler.transform(X_val)
sgd_reg = SGDRegressor(iterador máximo=1, tol=­np.infty, inicio cálido=Verdadero,
penalización=Ninguna, tasa de aprendizaje="constante", eta0=0,0005)
error_val_mínimo = float("inf") mejor_época
= Ninguno mejor_modelo
= Ninguno para época
en rango(1000):
sgd_reg.fit(X_train_poly_scaled, y_train) y_val_predict
= sgd_reg.predict(X_val_poly_scaled) error_val =
error_cuadrático_medio(y_val, y_val_predict) si error_val <
error_val_mínimo: error_val_mínimo =
error_val mejor_época = época
mejor_modelo =
clone(sgd_reg)
continúa donde lo dejó #
Tenga en cuenta que con warm_start=True, cuando se llama al método fit(), continúa el
entrenamiento donde lo dejó, en lugar de reiniciar desde cero.
Regresión logística
Como comentamos en el Capítulo 1, algunos algoritmos de regresión se pueden utilizar para
la clasificación (y viceversa). La regresión logística (también llamada regresión Logit)
se utiliza habitualmente para estimar la probabilidad de que una instancia pertenezca a una
clase en particular (por ejemplo, ¿cuál es la probabilidad de que este correo electrónico sea
spam?). Si la probabilidad estimada es mayor del 50 %, el modelo predice que la instancia
pertenece a esa clase (llamada clase positiva, etiquetada como “1”) y, en caso contrario,
predice que no pertenece (es decir, pertenece a la clase negativa, etiquetada como “0”). Esto
lo convierte en un clasificador binario.
Estimación de probabilidades
Entonces, ¿cómo funciona la regresión logística? Al igual que un modelo de regresión lineal, un
modelo de regresión logística calcula una suma ponderada de las características de entrada
(más un término de sesgo), pero en lugar de generar el resultado directamente como el modelo lineal,
Machine Translated by Google
El modelo de regresión genera la logística de este resultado (ver ecuación 4­13 ).
Ecuación 4­13. Probabilidad estimada del modelo de regresión logística (forma vectorizada)
pˆ = hθ (x) = σ(x
θ)
La función logística, denominada σ(∙), es una función sigmoidea (es decir, con forma de S)
que genera un número entre 0 y 1. Se define como se muestra en la ecuación 4­14
y la figura 4­21.
Ecuación 4­14. Función logística
σ(t) =
1
1 + exp(−t)
Figura 4­21. Función logística
Una vez que el modelo de regresión logística ha estimado la probabilidad pˆ = h (x) de θque
una instancia x pertenezca a la clase positiva, puede hacer su predicción ŷ fácilmente (ver
Ecuación 4­15).
Ecuación 4­15. Predicción del modelo de regresión logística
0,5
yˆ ={ 01sisipˆpˆ<≥0,5
Tenga en cuenta que σ(t) < 0,5 cuando t < 0, y σ(t) ≥ 0,5 cuando t ≥ 0, por lo que un
modelo de regresión logística predice 1 si x θ es positivo y 0 si es negativo.
Machine Translated by Google
NOTA
La puntuación t suele denominarse logit. El nombre proviene del hecho de que la función logit, definida
como logit(p) = log(p / (1 – p)), es la inversa de la función logística.
De hecho, si calculas el logit de la probabilidad estimada p, encontrarás que el resultado es t. El logit
también se denomina log­odds, ya que es el logaritmo de la relación entre la probabilidad estimada para la
clase positiva y la probabilidad estimada para la clase negativa.
Entrenamiento y función de costo Ahora ya sabe
cómo un modelo de regresión logística estima probabilidades y realiza predicciones. Pero, ¿cómo se
entrena? El objetivo del entrenamiento es establecer el vector de parámetros θ de modo que el modelo
estime probabilidades altas para instancias positivas (y = 1) y probabilidades bajas para instancias
negativas (y = 0).
Esta idea se captura mediante la función de costo que se muestra en la ecuación 4­16 para una
única instancia de entrenamiento x.
Ecuación 4­16. Función de costo de una única instancia de entrenamiento
si y = 1
c(θ) ={ − −
log(pˆ)
log(1 − pˆ) si y = 0
Esta función de costo tiene sentido porque –log(t) crece mucho cuando t se acerca a 0, por lo que
el costo será grande si el modelo estima una probabilidad cercana a 0 para un caso positivo, y
también será muy grande si el modelo estima una probabilidad cercana a 1 para un caso negativo.
Por otro lado, –log(t) es cercano a 0 cuando t es cercano a 1, por lo que el costo será cercano a 0 si la
probabilidad estimada es cercana a 0 para un caso negativo o cercana a 1 para un caso positivo, que
es precisamente lo que queremos.
La función de costo sobre todo el conjunto de entrenamiento es el costo promedio sobre todas las
instancias de entrenamiento. Puede escribirse en una sola expresión llamada pérdida logarítmica, que
se muestra en la ecuación 4­17.
Ecuación 4­17. Función de costo de regresión logística (pérdida logarítmica)
J(θ) = − ∑1
metro
mi=1[y (i) log(pˆ (i))+(1 − y (i))log(1 − pˆ (i))]
Machine Translated by Google
La mala noticia es que no se conoce ninguna ecuación de forma cerrada para calcular el
valor de θ que minimice esta función de costo (no existe un equivalente de la ecuación
normal). La buena noticia es que esta función de costo es convexa, por lo que se
garantiza que el descenso de gradiente (o cualquier otro algoritmo de optimización) encontrará
el mínimo global (si la tasa de aprendizaje no es demasiado grande y se espera lo
suficiente). Las derivadas parciales de la función de costo con respecto al parámetro
El
del modelo j θ se dan en
la ecuación 4­18.
yo
Ecuación 4­18. Derivadas parciales de la función de costo logístico
∂
∂θj
J(θ) =
1
metro
metro
(i)
∑
(σ (θ
yo=1
x (i)) − y (i)) x
yo
Esta ecuación se parece mucho a la ecuación 4­5: para cada instancia, calcula el
error de predicción y lo multiplica por el valor de la característica j ,Ely luego calcula el
promedio de todas las instancias de entrenamiento. Una vez que tenga el vector de
gradiente que contiene todas las derivadas parciales, puede usarlo en el algoritmo de
descenso de gradiente por lotes. Eso es todo: ahora sabe cómo entrenar un modelo de
regresión logística. Para la regresión logística estocástica, tomaría una instancia a la vez, y
para la regresión logística de minilotes, usaría un minilote a la vez.
Límites de decisión
Utilicemos el conjunto de datos de iris para ilustrar la regresión logística. Se trata de un
famoso conjunto de datos que contiene la longitud y el ancho de los sépalos y los pétalos
de 150 flores de iris de tres especies diferentes: Iris setosa, Iris versicolor e Iris virginica
(consulte la Figura 4­22).
Machine Translated by Google
Figura 4­22. Flores de tres especies de plantas de iris
14
Intentemos construir un clasificador para detectar el tipo de Iris virginica basándonos únicamente en
La función de ancho de pétalo. Primero carguemos los datos:
>>> desde sklearn importar conjuntos de datos
>>> iris = conjuntos de datos.load_iris()
>>> lista(iris.keys())
['datos', 'objetivo', 'nombres_de_objetivo', 'DESCR', 'nombres_de_funciones', 'nombre_de_archivo']
>>> X = iris["datos"][:, 3:] >>> y =
(iris["objetivo"] == 2).astype(np.int)
# Ancho del pétalo
# 1 si
Iris
Ahora vamos a entrenar un modelo de regresión logística:
desde sklearn.linear_model importar LogisticRegression
log_reg = Regresión logística()
log_reg.fit(X, y)
Veamos las probabilidades estimadas del modelo para flores con pétalos.
anchos que varían de 0 cm a 3 cm (Figura 4­23):
X_nuevo = np.linspace(0, 3, 1000).reshape(­1, 1)
y_proba = log_reg.predict_proba(X_nuevo)
plt.plot(X_new, y_proba[:, 1], "g­", etiqueta="Iris virginica")
15
virginica,
demás 0
Machine Translated by Google
plt.plot(X_new, y_proba[:, 0], "b­­", etiqueta="No es Iris virginica")
# + Más código de Matplotlib
a hacer el
La imagen se ve bonita
Figura 4­23. Probabilidades estimadas y límite de decisión
El ancho de los pétalos de las flores de Iris virginica (representadas por triángulos) varía
de 1,4 cm a 2,5 cm, mientras que las demás flores de iris (representadas por cuadrados)
Generalmente tienen un ancho de pétalo más pequeño, que varía entre 0,1 cm y 1,8 cm.
que hay un poco de superposición. Por encima de unos 2 cm, el clasificador es muy
seguro de que la flor es una Iris virginica (genera una alta probabilidad de
esa clase), mientras que por debajo de 1 cm hay una alta seguridad de que no es un Iris
virginica (alta probabilidad para la clase “No Iris virginica”). Entre
En estos extremos, el clasificador no está seguro. Sin embargo, si le pides que prediga el
clase (usando el método predict() en lugar de predict_proba()
método), devolverá la clase que sea más probable. Por lo tanto, hay
un límite de decisión en torno a 1,6 cm donde ambas probabilidades son iguales
50%: si el ancho del pétalo es mayor a 1,6 cm, el clasificador predecirá que
La flor es un Iris virginica, y de lo contrario predecirá que no lo es.
(aunque no sea muy seguro):
>>> log_reg.predict([[1.7], [1.5]])
matriz([1, 0])
La figura 4­24 muestra el mismo conjunto de datos, pero esta vez muestra dos características:
Ancho y largo de los pétalos. Una vez entrenado, el clasificador de regresión logística puede:
Con base en estas dos características, estima la probabilidad de que una nueva flor sea una
Iris virginica. La línea discontinua representa los puntos donde se encuentra el modelo.
estima una probabilidad del 50 %: este es el límite de decisión del modelo. Nótese que
Machine Translated by Google
16 línea paralela representa los puntos en los que el modelo genera
Se trata de un límite lineal. Cada
una probabilidad específica, desde el 15 % (abajo a la izquierda) hasta el 90 % (arriba a la
derecha). Todas las flores que se encuentran más allá de la línea superior derecha tienen una
probabilidad superior al 90 % de ser Iris virginica, según el modelo.
Figura 4­24. Límite de decisión lineal
Al igual que los demás modelos lineales, los modelos de regresión logística se pueden
regularizar utilizando penalizaciones
1
2 ℓ o ℓ. De hecho, Scikit­Learn agrega una penalización 2ℓ de manera
predeterminada.
NOTA
El hiperparámetro que controla la fuerza de regularización de un modelo de
regresión logística de Scikit­Learn no es alfa (como en otros modelos lineales), sino su inverso:
C. Cuanto mayor sea el valor de C, menos regularizado estará el modelo.
Regresión Softmax El modelo de
regresión logística se puede generalizar para admitir múltiples clases directamente, sin tener
que entrenar y combinar múltiples clasificadores binarios (como se explica en el Capítulo
3). Esto se denomina regresión Softmax o regresión logística multinomial.
La idea es simple: cuando se da una instancia x, el modelo de regresión Softmax primero calcula una
puntuación s (x) para cada clase
a k, luego estima la probabilidad de cada clase aplicando la función
softmax (también llamada función normalizada).
Machine Translated by Google
exponencial) a las puntuaciones. La ecuación para calcular s (x) debería
resultar familiar,
a
ya que es igual que la ecuación para la predicción de regresión lineal (consulte la ecuación 4­19).
Ecuación 4­19. Puntuación Softmax para la clase k
sk (x) = x
θ (k)
(k)
Tenga en cuenta que cada clase tiene su propio vector de parámetros dedicado θ . Todos estos
vectores normalmente se almacenan como filas en una matriz de parámetros Θ.
Una vez que haya calculado la puntuación de cada clase para la instancia x, puede estimar la
probabilidad pˆ de que la instanciaa pertenezca a la clase k ejecutando las puntuaciones a través de
la función softmax (Ecuación 4­20). La función calcula el exponencial de cada puntuación y luego
las normaliza (dividándolas por la suma de todas las exponenciales). Las puntuaciones generalmente
se denominan logits o log­odds (aunque en realidad son log­odds no normalizados).
Ecuación 4­20. Función Softmax
pˆk = σ(s(x)) a =
exp(sk (x))
K
∑ j=1 exp(sj (x))
En esta ecuación:
K es el número de clases.
s(x) es un vector que contiene las puntuaciones de cada clase para la instancia x.
σ(s(x)) esa la probabilidad estimada de que la instancia x pertenezca a la clase k,
dadas las puntuaciones de cada clase para esa instancia.
Al igual que el clasificador de regresión logística, el clasificador de regresión Softmax
predice la clase con la probabilidad estimada más alta (que es simplemente la clase con la
puntuación más alta), como se muestra en la ecuación 4­21.
Ecuación 4­21. Predicción del clasificador de regresión Softmax
Machine Translated by Google
yˆ = argmáx σ(s(x)) a argmax
a
a
sk (x) = argmax =
a
((θ (k))
incógnita)
El operador argmax devuelve el valor de una variable que maximiza una función. En esta
ecuación, devuelve el valor de k que maximiza la probabilidad estimada σ(s(x)) .
a
CONSEJO
El clasificador de regresión Softmax predice solo una clase a la vez (es decir, es multiclase, no multisalida),
por lo que debe usarse solo con clases mutuamente excluyentes, como diferentes tipos de plantas. No
puede usarlo para reconocer a varias personas en una imagen.
Ahora que ya sabe cómo el modelo estima probabilidades y hace predicciones, veamos el
entrenamiento. El objetivo es tener un modelo que estime una probabilidad alta para la clase objetivo
(y, en consecuencia, una probabilidad baja para las otras clases). Minimizar la función de
costo que se muestra en la ecuación 4­22, llamada entropía cruzada, debería conducir a este
objetivo porque penaliza al modelo cuando estima una probabilidad baja para una clase objetivo.
La entropía cruzada se utiliza con frecuencia para medir qué tan bien un conjunto de
probabilidades de clase estimadas coincide con las clases objetivo.
Ecuación 4­22. Función de costo de entropía cruzada
J(Θ) = − ∑
1
metro
metro
yo=1 ∑K k=1 y
(i)
k log(pˆ (i) k )
En esta ecuación:
(i)
El
es la probabilidad objetivo de que la instancia i pertenezca a la clase k. y k En general, es igual a 1 o 0,
dependiendo de si la instancia pertenece a la clase o no.
Tenga en cuenta que cuando solo hay dos clases (K = 2), esta función de costo es
equivalente a la función de costo de la regresión logística (pérdida logarítmica; consulte la ecuación
4­17).
Machine Translated by Google
ENTROPÍA CRUZADA
La entropía cruzada se originó a partir de la teoría de la información. Supongamos que
desea transmitir de manera eficiente información sobre el clima todos los días. Si hay
ocho opciones (soleado, lluvioso, etc.), podría codificar cada opción utilizando tres
3
bits porque 2 = 8. Sin embargo, si cree que estará soleado casi todos los días, sería
mucho más eficiente codificar "soleado" en solo un bit (0) y las otras siete opciones
en cuatro bits (comenzando con un 1). La entropía cruzada mide la cantidad promedio
de bits que realmente envía por opción. Si su suposición sobre el clima es
perfecta, la entropía cruzada será igual a la entropía del clima en sí (es decir, su
imprevisibilidad intrínseca). Pero si sus suposiciones son incorrectas (por
ejemplo, si llueve a menudo), la entropía cruzada será mayor en una cantidad
llamada divergencia de Kullback­Leibler (KL).
La entropía cruzada entre dos distribuciones de probabilidad p y q se define
como H (p, q) = − ∑x p (x)log q (x) (al menos cuando las distribuciones
son discretas). Para obtener más detalles, consulte mi video sobre el tema.
(k)
El vector de gradiente de esta función de costo con respecto a θ está dado por la
ecuación 4­23.
Ecuación 4­23. Vector de gradiente de entropía cruzada para la clase k
θ(k) J (Θ) =
1
metro
(i)
a
metro
(i)
∑(pˆ
yo=1
­ y k )x (yo)
Ahora puede calcular el vector de gradiente para cada clase y luego usar el Descenso de
Gradiente (o cualquier otro algoritmo de optimización) para encontrar la matriz de
parámetros Θ que minimiza la función de costo.
Utilicemos la regresión Softmax para clasificar las flores de iris en las tres clases. La
regresión logística de Scikit­Learn utiliza una contra el resto de forma predeterminada
cuando la entrena en más de dos clases, pero puede configurar el hiperparámetro
multi_class en "multinomial" para cambiarlo a Softmax.
Machine Translated by Google
Regresión. También debe especificar un solucionador que admita la regresión
Softmax, como el solucionador "lbfgs" (consulte la documentación de Scikit­Learn para
obtener más detalles). También aplica la2regularización ℓ de forma predeterminada, que
puede controlar mediante el hiperparámetro C:
X = iris["datos"][:, (2, 3)] y =
iris["objetivo"]
largo del pétalo, ancho del pétalo #
softmax_reg = Regresión logística (multi_class="multinomial", solver="lbfgs", C=10) softmax_reg.fit(X,
y)
Entonces, la próxima vez que encuentres un iris con pétalos de 5 cm de largo y 2 cm
de ancho, puedes pedirle a tu modelo que te diga qué tipo de iris es, y responderá Iris
virginica (clase 2) con un 94,2% de probabilidad (o Iris versicolor con un 5,8% de
probabilidad):
>>> softmax_reg.predict([[5, 2]]) matriz([2])
>>>
softmax_reg.predict_proba([[ 5, 2]])
matriz([[6.38014896e­07, 5.74929995e­02, 9.42506362e­01]])
La figura 4­25 muestra los límites de decisión resultantes, representados por los
colores de fondo. Observe que los límites de decisión entre dos clases cualesquiera
son lineales. La figura también muestra las probabilidades para la clase Iris
versicolor , representadas por las líneas curvas (por ejemplo, la línea etiquetada con
0,450 representa el límite de probabilidad del 45%). Observe que el modelo puede
predecir una clase que tiene una probabilidad estimada por debajo del 50%. Por ejemplo,
en el punto donde se encuentran todos los límites de decisión, todas las clases
tienen una probabilidad estimada igual del 33%.
Machine Translated by Google
Figura 4­25. Límites de decisión de regresión Softmax
Ceremonias
1. ¿Qué algoritmo de entrenamiento de regresión lineal puedes usar si tienes
¿Un conjunto de entrenamiento con millones de funciones?
2. Suponga que las características de su conjunto de entrenamiento tienen escalas muy diferentes.
¿Qué algoritmos pueden verse afectados por esto y cómo? ¿Qué se puede
hacer al respecto?
3. ¿Puede el descenso de gradiente quedarse atascado en un mínimo local al entrenar un modelo de
regresión logística?
4. ¿Todos los algoritmos de descenso de gradiente conducen al mismo modelo, siempre que
se los deje funcionar durante el tiempo suficiente?
5. Supongamos que utiliza Batch Gradient Descent y traza el error de validación en cada época. Si observa
que el error de validación aumenta constantemente, ¿qué es lo que probablemente esté
sucediendo? ¿Cómo puede solucionarlo?
6. ¿Es una buena idea detener el descenso de gradiente por lotes en miniatura inmediatamente?
¿Cuando sube el error de validación?
7. ¿Qué algoritmo de descenso de gradiente (entre los que analizamos) alcanzará más rápido la
proximidad de la solución óptima? ¿Cuál convergerá realmente? ¿Cómo se puede hacer que los
demás también converjan?
Machine Translated by Google
8. Supongamos que utiliza la regresión polinómica. Traza las curvas de aprendizaje y
observa que hay una gran diferencia entre el error de entrenamiento y el error de
validación. ¿Qué sucede? ¿Cuáles son las tres formas de resolverlo?
9. Supongamos que está utilizando la regresión de cresta y observa que el error de
entrenamiento y el error de validación son casi iguales y bastante altos. ¿Diría
que el modelo sufre un sesgo alto o una varianza alta? ¿Debería aumentar el
hiperparámetro de regularización α o reducirlo?
10. ¿Por qué querrías utilizar:
a. ¿Regresión de cresta en lugar de regresión lineal simple (es decir, sin
ninguna regularización)?
b. ¿Lasso en lugar de regresión de cresta?
c. ¿Red elástica en lugar de lazo?
11. Supongamos que desea clasificar imágenes como exteriores/interiores y
Día/noche. ¿Debería implementar dos clasificadores de regresión logística o un
clasificador de regresión Softmax?
12. Implementar el descenso de gradiente por lotes con detención temprana para Softmax
Regresión (sin utilizar Scikit­Learn).
Las soluciones a estos ejercicios están disponibles en el Apéndice A.
1 A menudo, un algoritmo de aprendizaje intenta optimizar una función diferente a la medida de rendimiento utilizada
para evaluar el modelo final. Esto se debe generalmente a que esa función es más fácil de calcular, a que tiene
propiedades de diferenciación útiles de las que carece la medida de rendimiento o a que queremos restringir el
modelo durante el entrenamiento, como verá cuando analicemos la regularización.
2 Tenga en cuenta que Scikit­Learn separa el término de sesgo (intercept_) de los pesos de las características.
(coeficiente de correlación).
Técnicamente hablando, su derivada es Lipschitz continua.
4 Dado que la característica 1 es más pequeña, se necesita un cambio mayor
en θ para afectar la función de costo, por
1
lo que el recipiente se alarga a lo largo del eje θ .1
Machine Translated by Google
5 Eta (η) es la séptima letra del alfabeto griego.
Si bien la ecuación normal solo puede realizar regresión lineal, los algoritmos de descenso de gradiente se pueden
usar para entrenar muchos otros modelos, como veremos.
7 Una ecuación cuadrática tiene la forma y = ax 2 + bx + c.
Esta noción de sesgo no debe confundirse con el término de sesgo de los modelos lineales.
Es común utilizar la notación J(θ) para funciones de costo que no tienen un nombre corto; utilizaremos esta notación a
menudo en el resto de este libro. El contexto dejará en claro de qué función de costo se trata .
En el capítulo 2 se analizan 10 normas .
11 Una matriz cuadrada llena de 0 excepto 1 en la diagonal principal (de arriba a la izquierda a abajo a la derecha).
12 Alternativamente, puede utilizar la clase Ridge con el solucionador "sag". El algoritmo Stochastic Average Gradient es una variante del
algoritmo Stochastic GD. Para obtener más detalles, consulte la presentación “Minimizing Finite Sums with the Stochastic Average
Gradient Algorithm” (Minimizar sumas finitas con el algoritmo Stochastic Average Gradient) de Mark Schmidt et al. de la
Universidad de Columbia Británica.
13 Puedes pensar en un vector de subgradiente en un punto no diferenciable como un vector intermedio
entre los vectores de gradiente alrededor de ese punto.
14 fotografías reproducidas de las páginas correspondientes de Wikipedia. Foto de Iris virginica de Frank Mayfield
(Creative Commons BY­SA 2.0), Fotografía de iris versicolor de D. Gordon E. Robertson (Creative Commons BY­SA
3.0), Foto de Iris setosa de dominio público.
La función reshape() de NumPy permite que una dimensión sea ­1, lo que significa “no especificado”: el valor se infiere
de la longitud de la matriz y las dimensiones restantes.
16 Es el conjunto de puntos x tales que θ + θ x + θ x 0= 0, que
1 1define2 una
2 línea recta.
Machine Translated by Google
Capítulo 5. Máquinas de vectores de soporte
Una máquina de vectores de soporte (SVM) es un modelo de aprendizaje automático potente y versátil,
capaz de realizar clasificaciones lineales o no lineales, regresiones e incluso detección de valores
atípicos. Es uno de los modelos más populares en aprendizaje automático y cualquier persona interesada en el
aprendizaje automático debería tenerlo en su caja de herramientas. Las SVM son especialmente adecuadas
para la clasificación de conjuntos de datos complejos de tamaño pequeño o mediano.
Este capítulo explicará los conceptos básicos de las SVM, cómo utilizarlas y cómo funcionan.
Clasificación SVM lineal
La idea fundamental detrás de los SVM se explica mejor con algunas imágenes. La Figura 5­1 muestra parte del
conjunto de datos de iris que se presentó al final del Capítulo 4. Las dos clases se pueden separar
fácilmente con una línea recta (son linealmente separables). El gráfico de la izquierda muestra los límites de
decisión de tres posibles clasificadores lineales. El modelo cuyo límite de decisión está representado
por la línea discontinua es tan malo que ni siquiera separa las clases correctamente. Los otros dos modelos
funcionan perfectamente en este conjunto de entrenamiento, pero sus límites de decisión se acercan tanto
a las instancias que estos modelos probablemente no funcionarán tan bien en nuevas instancias. En
contraste, la línea continua en el gráfico de la derecha representa el límite de decisión de un
clasificador SVM; esta línea no solo separa las dos clases, sino que también se mantiene lo más lejos posible
de las instancias de entrenamiento más cercanas. Puede pensar en un clasificador SVM como si ajustara la
calle más ancha posible (representada por las líneas discontinuas paralelas) entre las clases. Esto se llama
clasificación de margen grande.
Figura 5­1. Clasificación de márgenes amplios
Tenga en cuenta que agregar más instancias de entrenamiento “fuera de la calle” no afectará en absoluto
el límite de decisión: está completamente determinado (o “apoyado”) por las instancias ubicadas en el borde de
la calle. Estas instancias se denominan vectores de soporte (están encerrados en un círculo en la Figura 5­1).
Machine Translated by Google
Figura 5­2. Sensibilidad a las escalas de características
ADVERTENCIA
Las SVM son sensibles a las escalas de las características, como se puede ver en la Figura 5­2: en el gráfico de la izquierda, la escala vertical es
mucho mayor que la escala horizontal, por lo que la calle más ancha posible está cerca de la horizontal. Después de aplicar la escala de las
características (por ejemplo, utilizando StandardScaler de Scikit­Learn), el límite de decisión en el gráfico de la derecha se ve mucho mejor.
Clasificación de margen blando
Si imponemos estrictamente que todas las instancias deben estar fuera de la calle y en el lado derecho,
esto se llama clasificación de margen duro. Hay dos problemas principales con la clasificación
de margen duro. Primero, solo funciona si los datos son linealmente separables. Segundo, es sensible a los
valores atípicos. La Figura 5­3 muestra el conjunto de datos de iris con solo un valor atípico adicional: a la
izquierda, es imposible encontrar un margen duro; a la derecha, el límite de decisión termina siendo muy
diferente del que vimos en la Figura 5­1 sin el valor atípico, y probablemente no se generalizará tan bien.
Figura 5­3. Sensibilidad del margen duro a los valores atípicos
Para evitar estos problemas, utilice un modelo más flexible. El objetivo es encontrar un buen equilibrio
entre mantener la calle lo más grande posible y limitar las violaciones de los márgenes (es decir, los
casos que terminan en el medio de la calle o incluso en el lado equivocado). Esto se denomina clasificación
de márgenes blandos.
Al crear un modelo SVM con Scikit­Learn, podemos especificar una cantidad de hiperparámetros.
C es uno de esos hiperparámetros. Si lo configuramos con un valor bajo, terminaremos
Machine Translated by Google
Con el modelo de la izquierda de la Figura 5­4, obtenemos un valor alto. Las violaciones de margen son malas.
Por lo general, es mejor tener pocas. Sin embargo, en este caso, el modelo de la izquierda tiene muchas violaciones
de margen, pero probablemente se generalizará mejor.
Figura 5­4. Margen grande (izquierda) versus menos violaciones de margen (derecha)
CONSEJO
Si su modelo SVM está sobreajustado, puede intentar regularizarlo reduciendo C.
El siguiente código Scikit­Learn carga el conjunto de datos de iris, escala las características y luego entrena un modelo
SVM lineal (utilizando la clase LinearSVC con C=1 y la función de pérdida de bisagra , descrita en breve)
para detectar flores de Iris virginica :
importar numpy como np
desde sklearn importar conjuntos de
datos desde sklearn.pipeline importar Pipeline desde
sklearn.preprocessing importar StandardScaler desde sklearn.svm
importar LinearSVC
iris = conjuntos de datos.load_iris()
X = iris["datos"][:, (2, 3)] y =
(iris["objetivo"] == 2).astype(np.float64)
largo del pétalo, ancho del pétalo #
# Iris
Virginia
svm_clf = Pipeline([ ("scaler",
StandardScaler()), ("linear_svc",
LinearSVC(C=1, loss="bisagra")),
])
svm_clf.fit(X, y)
El modelo resultante se representa a la izquierda en la Figura 5­4.
Luego, como de costumbre, puedes usar el modelo para hacer predicciones:
>>> svm_clf.predict([[5.5, 1.7]]) matriz([1.])
Machine Translated by Google
NOTA
A diferencia de los clasificadores de regresión logística, los clasificadores SVM no generan probabilidades para cada clase.
En lugar de utilizar la clase LinearSVC, podríamos utilizar la clase SVC con un núcleo lineal.
Al crear el modelo SVC, escribiríamos SVC(kernel="linear", C=1). O podríamos usar la clase SGDClassifier, con
SGDClassifier(loss="hinge", alpha=1/(m*C)). Esto aplica el Descenso de Gradiente Estocástico
regular (ver Capítulo 4) para entrenar un clasificador SVM lineal. No converge tan rápido como la clase LinearSVC, pero
puede ser útil para manejar tareas de clasificación en línea o conjuntos de datos enormes que no caben en la
memoria (entrenamiento fuera del núcleo).
CONSEJO
La clase LinearSVC regulariza el término de sesgo, por lo que primero debe centrar el conjunto de entrenamiento restando su media.
Esto es automático si escala los datos utilizando StandardScaler. Asegúrese también de configurar el hiperparámetro de pérdida en
"bisagra", ya que no es el valor predeterminado. Por último, para un mejor rendimiento, debe configurar el hiperparámetro dual en
Falso, a menos que haya más características que instancias de entrenamiento (hablaremos de la dualidad más adelante en el capítulo).
Clasificación SVM no lineal
Aunque los clasificadores SVM lineales son eficientes y funcionan sorprendentemente bien en muchos casos,
muchos conjuntos de datos ni siquiera se acercan a ser linealmente separables. Un enfoque para manejar
conjuntos de datos no lineales es agregar más características, como características polinómicas (como hizo en
el Capítulo 4); en algunos casos, esto puede dar como resultado un conjunto de datos linealmente separable.
Considere el gráfico de la izquierda en la Figura 5­5: representa un conjunto de datos simple
1 Este conjunto de datos es
2
con solo una característica, x no linealmente separable, como puede ver. Pero si agrega una segunda
2
1
característica x = (x ) , el conjunto de datos 2D resultante es perfectamente linealmente separable.
Machine Translated by Google
Figura 5­5. Cómo agregar características para que un conjunto de datos sea linealmente separable
Para implementar esta idea con Scikit­Learn, cree un pipeline que contenga un
transformador PolynomialFeatures (que se analiza en “Regresión polinómica”), seguido de un
StandardScaler y un LinearSVC. Probemos esto en el conjunto de datos moons: este es un conjunto de
datos de juguete para la clasificación binaria en el que los puntos de datos tienen la forma de dos
semicírculos entrelazados (consulte la Figura 5­6). Puede generar este conjunto de datos utilizando la función make_moons()
desde sklearn.datasets importar make_moons
desde sklearn.pipeline importar Pipeline desde
sklearn.preprocessing importar PolynomialFeatures
X, y = make_moons(n_muestras=100, ruido=0,15)
polynomial_svm_clf =
Pipeline([ ("poly_features", PolynomialFeatures(grado=3)),
("scaler", StandardScaler()),
("svm_clf", LinearSVC(C=10, pérdida="bisagra"))
])
polinomio_svm_clf.fit(X, y)
Machine Translated by Google
Figura 5­6. Clasificador SVM lineal que utiliza características polinómicas
Núcleo polinomial
Agregar características polinómicas es fácil de implementar y puede funcionar muy bien con todo tipo de
algoritmos de aprendizaje automático (no solo con SVM). Dicho esto, con un grado polinómico bajo, este
método no puede manejar conjuntos de datos muy complejos y, con un grado polinómico alto, crea una
gran cantidad de características, lo que hace que el modelo sea demasiado lento.
Afortunadamente, al utilizar SVM, se puede aplicar una técnica matemática casi milagrosa llamada
el truco del núcleo (que se explica en un momento). El truco del núcleo permite obtener el mismo
resultado que si se hubieran añadido muchas características polinómicas, incluso con polinomios de grado
muy alto, sin tener que añadirlas realmente. Por lo tanto, no se produce una explosión combinatoria
del número de características porque en realidad no se añade ninguna característica. Este truco lo
implementa la clase SVC. Probémoslo en el conjunto de datos de las lunas:
desde sklearn.svm importar SVC
poly_kernel_svm_clf = Pipeline([ ("scaler",
StandardScaler()), ("svm_clf",
SVC(kernel="poly", grado=3, coef0=1, C=5))
])
poly_kernel_svm_clf.fit(X, y)
Este código entrena un clasificador SVM que utiliza un núcleo polinomial de tercer grado. Se representa a la
izquierda en la Figura 5­7. A la derecha hay otro clasificador SVM que utiliza un núcleo polinomial de
décimo grado. Obviamente, si su modelo está sobreajustado, es posible que desee reducir el
Machine Translated by Google
Grado polinómico. Por el contrario, si el ajuste es insuficiente, puede intentar aumentarlo. El
hiperparámetro coef0 controla en qué medida el modelo se ve influenciado por polinomios de alto
grado en comparación con los de bajo grado.
Figura 5­7. Clasificadores SVM con un núcleo polinomial
CONSEJO
Un método común para encontrar los valores de hiperparámetros correctos es utilizar la búsqueda en cuadrícula (consulte el Capítulo 2). Suele ser más rápido
realizar primero una búsqueda en cuadrícula muy general y luego una búsqueda en cuadrícula más precisa en torno a los mejores valores encontrados.
Tener una buena idea de lo que realmente hace cada hiperparámetro también puede ayudarle a buscar en la parte correcta
del espacio de hiperparámetros.
Características de similitud
Otra técnica para abordar problemas no lineales es agregar características calculadas utilizando una
función de similitud, que mide cuánto se parece cada instancia a un punto de referencia en particular.
Por ejemplo, tomemos el conjunto de datos 1D analizado anteriormente y agreguemos dos puntos de
referencia 1en x = –2 y x =1 1 (vea el gráfico de la izquierda en la Figura 5­8). A continuación, definamos la
función de similitud como la Función de Base Radial Gaussiana (RBF) con γ = 0,3 (vea la Ecuación 5­1).
Ecuación 5­1. Factor de fricción gaussiano
γ (x, ℓ) = exp(−γ
x−ℓ
2)
Esta es una función con forma de campana que varía de 0 (muy lejos del punto de referencia) a 1 (en el
punto de referencia). Ahora estamos listos para calcular las nuevas características. Por ejemplo, observemos
la instancia x = –1:
está ubicada a una distancia de 1 del primer punto de referencia y 2 del primero.
1
2
Segundo punto de referencia. Por lo tanto, sus nuevas características
son x = exp(–0,3 × 1) ≈ 0,74 3y x = exp(–
2
2
0,3 × 2) ≈ 0,30. El gráfico de la derecha de la Figura 5­8 muestra el conjunto de datos transformado
(eliminando las características originales). Como puede ver, ahora es linealmente separable.
Machine Translated by Google
Figura 5­8. Características de similitud utilizando el factor de similitud gaussiano
Quizás se pregunte cómo seleccionar los puntos de referencia. El enfoque más simple es crear un punto
de referencia en la ubicación de cada una de las instancias del conjunto de datos. Al hacer eso se crean muchas
dimensiones y, por lo tanto, aumentan las posibilidades de que el conjunto de entrenamiento transformado sea
linealmente separable. La desventaja es que un conjunto de entrenamiento con m instancias y n
características se transforma en un conjunto de entrenamiento con m instancias y m características (suponiendo
que se eliminan las características originales). Si el conjunto de entrenamiento es muy grande, termina con
una cantidad igualmente grande de características.
Núcleo RBF gaussiano
Al igual que el método de características polinómicas, el método de características de similitud puede ser útil con
cualquier algoritmo de aprendizaje automático, pero puede resultar costoso computacionalmente calcular todas las
características adicionales, especialmente en conjuntos de entrenamiento grandes. Una vez más, el truco del kernel
hace su magia SVM, lo que permite obtener un resultado similar al que se obtendría si se hubieran agregado
muchas características de similitud. Probemos la clase SVC con el kernel RBF gaussiano:
rbf_kernel_svm_clf =
Pipeline([ ("escalador",
StandardScaler()), ("svm_clf", SVC(kernel="rbf", gamma=5, C=0.001))
])
rbf_kernel_svm_clf.fit(X, y)
Este modelo se representa en la parte inferior izquierda de la Figura 5­9. Los otros gráficos muestran modelos
entrenados con diferentes valores de los hiperparámetros gamma (γ) y C. El aumento de gamma hace que la curva
en forma de campana sea más estrecha (consulte los gráficos de la derecha en la Figura 5­8). Como resultado,
el rango de influencia de cada instancia es más pequeño: el límite de decisión termina siendo más irregular
y se mueve alrededor de las instancias individuales. Por el contrario, un valor de gamma pequeño hace que la curva
en forma de campana sea más ancha: las instancias tienen un rango de influencia más grande y el límite de
decisión termina siendo más suave. Por lo tanto, γ actúa como un hiperparámetro de regularización: si su modelo
está sobreajustado, debe reducirlo; si está subajustado, debe aumentarlo (similar al hiperparámetro C).
Machine Translated by Google
Figura 5­9. Clasificadores SVM que utilizan un núcleo RBF
Existen otros núcleos, pero se utilizan con mucha menos frecuencia. Algunos núcleos están especializados
en estructuras de datos específicas. Los núcleos de cadena se utilizan a veces para clasificar
documentos de texto o secuencias de ADN (por ejemplo, utilizando el núcleo de subsecuencia de cadena o
núcleos basados en la distancia de Levenshtein).
CONSEJO
Con tantos kernels para elegir, ¿cómo puedes decidir cuál usar? Como regla general, siempre debes probar primero el kernel
lineal (recuerda que LinearSVC es mucho más rápido que SVC(kernel="linear")), especialmente si el conjunto
de entrenamiento es muy grande o si tiene muchas características. Si el conjunto de entrenamiento no es demasiado grande,
también debes probar el kernel RBF gaussiano; funciona bien en la mayoría de los casos.
Luego, si tiene tiempo libre y capacidad de procesamiento, puede experimentar con algunos otros núcleos, utilizando validación
cruzada y búsqueda en cuadrícula. Le convendría experimentar de esa manera, especialmente si hay núcleos especializados
para la estructura de datos de su conjunto de entrenamiento.
Complejidad computacional
La clase LinearSVC se basa en la biblioteca liblinear, que implementa un algoritmo optimizado para SVM lineales.
1
No admite el truco del núcleo, pero se escala casi linealmente con la cantidad de instancias de
entrenamiento y la cantidad de características. Su complejidad de tiempo de entrenamiento es aproximadamente
O(m × n).
Machine Translated by Google
El algoritmo tarda más si se requiere una precisión muy alta. Esto lo controla el
o
Hiperparámetro de tolerancia (llamado
tol en Scikit­Learn). En la mayoría de las tareas de clasificación, el
La tolerancia predeterminada está bien.
La clase SVC se basa en la biblioteca libsvm, que implementa un algoritmo que apoya
El truco del núcleo. 2La complejidad del tiempo de entrenamiento suele estar entre O(m 2× n) y O(m ×
3
n). Desafortunadamente, esto significa que se vuelve terriblemente lento cuando aumenta la cantidad de entrenamiento.
Las instancias se vuelven grandes (por ejemplo, cientos de miles de instancias). Este algoritmo es perfecto para
Conjuntos de entrenamiento complejos, pequeños o medianos. Se adapta bien a la cantidad de funciones.
especialmente con características dispersas (es decir, cuando cada instancia tiene pocas características distintas de cero). En este
En este caso, el algoritmo se escala aproximadamente con el número promedio de características distintas de cero por
Por ejemplo, la Tabla 5­1 compara las clases de clasificación SVM de Scikit­Learn.
Tabla 5­1. Comparación de clases de Scikit­Learn para la clasificación de SVM
Clase
Complejidad temporal
Soporte fuera del núcleo Escala requerida Truco del kernel
LinealSVC
O(m × n)
No
Sí
No
Clasificador SGD O(m × n)
Sí
Sí
No
Sí
Sí
CVS
O(m² × n) a O(m³ × n) No
Regresión SVM
Como se mencionó anteriormente, el algoritmo SVM es versátil: no solo admite algoritmos lineales y
clasificación no lineal, pero también admite regresión lineal y no lineal. Para utilizar SVM
Para la regresión en lugar de la clasificación, el truco es invertir el objetivo: en lugar de
Intentando encajar la calle más grande posible entre dos clases mientras se limita el margen
violaciones, la regresión SVM intenta encajar tantas instancias como sea posible en la calle mientras
Limitar las violaciones de margen (es decir, instancias fuera de la calle). El ancho de la calle es
o
controlado por un hiperparámetro, . La Figura
5­10 muestra dos modelos de regresión lineal SVM
entrenado con algunos datos lineales aleatorios, uno con un margen grandeo(= 1,5) y el otro con un
o 0,5).
margen pequeño (=
Machine Translated by Google
Figura 5­10. Regresión SVM
Agregar más instancias de entrenamiento dentro del margen no afecta las predicciones del modelo; por lo tanto,
o ­insensible.
se dice que el modelo es
Puede utilizar la clase LinearSVR de Scikit­Learn para realizar una regresión lineal SVM. El código
siguiente produce el modelo representado a la izquierda en la Figura 5­10 (los datos de entrenamiento deben
escalarse y centrarse primero):
desde sklearn.svm importar LinearSVR
svm_reg = LinearSVR(épsilon=1.5)
svm_reg.fit(X, y)
Para abordar tareas de regresión no lineal, puede utilizar un modelo SVM kernelizado. La Figura 5­11
muestra la regresión SVM en un conjunto de entrenamiento cuadrático aleatorio, utilizando un kernel
polinomial de segundo grado. Hay poca regularización en el gráfico de la izquierda (es decir, un valor C grande)
y mucha más regularización en el gráfico de la derecha (es decir, un valor C pequeño).
Figura 5­11. Regresión SVM utilizando un núcleo polinomial de segundo grado
Machine Translated by Google
El siguiente código utiliza la clase SVR de Scikit­Learn (que admite el truco del núcleo) para producir el
modelo representado a la izquierda en la Figura 5­11:
desde sklearn.svm importar SVR
svm_poly_reg = SVR(núcleo="poli", grado=2, C=100, épsilon=0,1) svm_poly_reg.fit(X, y)
La clase SVR es el equivalente de regresión de la clase SVC, y la clase LinearSVR es el equivalente de regresión
de la clase LinearSVC. La clase LinearSVR escala linealmente con el tamaño del conjunto de entrenamiento (al
igual que la clase LinearSVC), mientras que la clase SVR se vuelve demasiado lenta cuando el conjunto de
entrenamiento crece (al igual que la clase SVC).
NOTA
Las SVM también se pueden utilizar para la detección de valores atípicos; consulte la documentación de Scikit­Learn para obtener más detalles.
Bajo el capó
En esta sección se explica cómo las máquinas de modelado de secuencias (SVM) realizan predicciones y cómo funcionan
sus algoritmos de entrenamiento, comenzando con los clasificadores lineales de SVM. Si recién está comenzando con el
aprendizaje automático, puede omitir esta sección sin problemas y pasar directamente a los ejercicios que se encuentran
al final de este capítulo, y volver más tarde cuando desee comprender mejor las SVM.
Primero, una palabra sobre notaciones. En el Capítulo 4 usamos la convención de poner todos los parámetros
del modelo en un vector θ, incluyendo el término de sesgo θ y los0pesos de las características de entrada1θ
a θ, y agregando una entrada de sesgo x = 1 a todas las instancias. En este capítulo, utilizaremos una convención
norte
0
que es más conveniente (y más común) cuando se trabaja con SVM: el término de sesgo se llamará b y el vector
de ponderaciones de características se llamará w. No se agregará ninguna característica de sesgo a los vectores
de características de entrada.
Función de decisión y predicciones
El modelo de clasificación lineal SVM predice la clase de una nueva instancia x simplemente
calculando la función de decisión wx + b = wx +
+ wx +1b.1 Si el resultado
nnes positivo, la clase predicha ŷ es la
clase positiva (1) y, de lo contrario, es la clase negativa (0); consulte la ecuación 5­2.
Ecuación 5­2. Predicción del clasificador SVM lineal
si w
w xx ++ bb <≥ 0,
0
yˆ ={ 01 si
Machine Translated by Google
La Figura 5­12 muestra la función de decisión que corresponde al modelo de la izquierda
en la Figura 5­4: es un plano 2D porque este conjunto de datos tiene dos características (ancho
y largo del pétalo). El límite de decisión es el conjunto de puntos donde la función de decisión es
igual a 0: es la intersección de dos planos, que es una línea recta (representada por la línea sólida
3
gruesa).
Figura 5­12. Función de decisión para el conjunto de datos del iris
Las líneas discontinuas representan los puntos en los que la función de decisión es igual a 1 o ­1:
son paralelas y están a la misma distancia del límite de decisión, y forman un margen a su
alrededor. Entrenar un clasificador SVM lineal significa encontrar los valores de w y b que
hagan que este margen sea lo más amplio posible, evitando violaciones del margen (margen
duro) o limitándolas (margen suave).
Objetivo del entrenamiento
Considere la pendiente de la función de decisión: es igual a la norma del vector de peso,
w
. Si
dividimos esta pendiente por 2, los puntos donde la función de decisión es igual a ±1 estarán
el doble de lejos del límite de decisión. En otras palabras, dividir la pendiente por 2 multiplicará
el margen por 2. Esto puede ser más fácil de visualizar en 2D, como se muestra en la Figura 5­13.
Cuanto menor sea el vector de peso w, mayor será el margen.
Machine Translated by Google
Figura 5­13. Un vector de peso más pequeño da como resultado un margen más grande.
Por lo tanto, queremos minimizar
w
para obtener un margen grande. Si también queremos evitar cualquier
violación del margen (margen duro), entonces necesitamos que la función de decisión sea mayor que 1 para
todas las instancias de entrenamiento positivas y menor que ­1 para las instancias de entrenamiento negativas.
(i)
(i)
Si definimos (i) (i) t = ­1 para instancias negativas
(si y = 0)
y t = 1 para instancias positivas (si y = 1), entonces (i) podemos expresar esta restricción como t (wx + b) ≥ 1 para
(yo)
todas las instancias.
Por lo tanto, podemos expresar el objetivo del clasificador SVM lineal de margen duro como el
problema de optimización restringida en la Ecuación 5­3.
Ecuación 5­3. Objetivo del clasificador SVM lineal de margen duro
minimizar
w,b
1s
2
sujeto a t (i) (w
s
x (i) + b) ≥ 1 para i = 1, 2,
,m
NOTA
1
Estamos minimizando w w, que es igual a
2
w
1
, una derivada 2 , en lugar de minimizar
agradable y simple (es simplemente w), mientras que
mucho mejor en funciones diferenciables.
2
w
w
. De hecho,
1
w
2
2
tiene
una
no es diferenciable en w = 0. Los algoritmos de optimización funcionan
(i)
Para obtener el objetivo de margen blando, necesitamos introducir una variable de holgura ζ ≥ 0 para cada
4 (i)
El
instancia: ζ mide cuánto se le permite a la instancia i violar el margen. Ahora tenemos dos objetivos en conflicto:
hacer que las variables de holgura sean lo más pequeñas posible para reducir las violaciones del margen y hacer
que ww sea lo más pequeño posible para
aumentar el margen. Aquí es donde entra en juego el hiperparámetro
12
C: nos permite definir el equilibrio entre estos dos objetivos. Esto nos da el problema de optimización restringida
en la ecuación 5­4.
Ecuación 5­4. Objetivo del clasificador SVM lineal de margen blando
Machine Translated by Google
1
minimizar
w,b,ζ
2w
sujeto a t (i) (w
w + C m∑ (yo )
yo=1
x (i) + b) ≥ 1 − ζ (i) y ζ (i) ≥ 0 para i = 1, 2,
,m
Programación cuadrática
Los problemas de margen duro y margen blando son problemas de optimización cuadrática convexa
con restricciones lineales. Dichos problemas se conocen como problemas de programación cuadrática (QP).
Hay muchos solucionadores disponibles para resolver problemas de QP mediante el uso de una variedad de
5
técnicas que están fuera del alcance de este libro.
La formulación general del problema viene dada por la ecuación 5­5.
Ecuación 5­5. Problema de programación cuadrática
1p
Minimizar p
Hp + f
p2
sujeto a Ap ≤ bp es un
vector np­dimensional (np = número de parámetros),
H es una matriz np × np , f es un
dónde
vector np­dimensional,
A es una matriz nc × np (nc = número de restricciones),
b es un vector nc­dimensional.
(yo)
Nótese que la expresión A p ≤ b define n restricciones: pa ≤ b para i = 1, 2,
do
El
(i)
que contiene los elementos de la i fila de A y b es el i elemento de b.
(i)
, n donde a es el vector
(i)
do
,
El
Puede verificar fácilmente que si establece los parámetros QP de la siguiente manera, obtendrá el objetivo
del clasificador SVM lineal de margen duro:
n = n + 1, donde n es el número de características (el +1 es para el término de sesgo).
pag
n = m, donde m es el número de instancias de entrenamiento.
do
H es la matriz
identidad n × n , excepto con un cero en la celda superior izquierda (para ignorar el
páginas
término de sesgo).
f = 0, un vector n ­dimensional lleno de 0.
pag
b = –1, un vector n ­dimensional lleno de –1.
do
(i) (i) (i) a = –t ,
(i)
(i)
x˙
donde x˙ es igual a x con una característica de sesgo adicional x˙
0 = 1.
Machine Translated by Google
Una forma de entrenar un clasificador SVM lineal de margen duro es utilizar un solucionador QP estándar
y pasarle los parámetros anteriores. El vector resultante p contendrá el término de sesgo b = p y los pesos
de0 las características w = p para i i= 1, 2,
i
, n. De manera similar, puede utilizar un solucionador QP
para resolver el problema de margen blando (consulte los ejercicios al final del capítulo).
Para utilizar el truco del núcleo, vamos a analizar un problema de optimización restringida diferente.
El doble problema
Dado un problema de optimización restringido, conocido como el problema primal, es posible expresar
un problema diferente pero estrechamente relacionado, llamado su problema dual. La solución al problema
dual normalmente da un límite inferior a la solución del problema primal, pero bajo ciertas condiciones
puede tener la misma solución que el problema primal. Afortunadamente, el problema SVM cumple con
6
estas condiciones, por lo que puede elegir resolver el problema
primal o el problema dual; ambos
tendrán la misma solución. La ecuación 5­6 muestra la forma dual del objetivo SVM lineal (si está
interesado en saber cómo derivar el problema dual a partir del problema primal, consulte el Apéndice C).
Ecuación 5­6. Forma dual del objetivo SVM lineal
1 minimizar
alfa
2 m∑m∑ α (i)α (j) t (i) t (j)x (i)
yo=1
incógnita
(j)
− m∑
alfa
(i)
yo=1
j=1
(i) ≥ 0 para i = 1, 2,
, m sujeto a α
Una vez que encuentre el vector αˆ que minimiza esta ecuación (usando un solucionador QP), use la
Ecuación 5­7 para calcular ˆw y ˆb que minimizan el problema primal.
Ecuación 5­7. De la solución dual a la solución primal
ˆw = m∑
αˆ (i) t (i) x (i)
yo=1
1
ˆb = m∑(t (i) − ˆw
ns
x (i))
yo=1
αˆ (i)>0
El problema dual es más rápido de resolver que el problema primario cuando la cantidad de
instancias de entrenamiento es menor que la cantidad de características. Más importante aún, el
problema dual hace posible el truco del núcleo, mientras que el primario no. Entonces, ¿qué es este
truco del núcleo?
SVM kernelizados
Machine Translated by Google
Supongamos que desea aplicar una transformación polinómica de segundo grado a un conjunto
de entrenamiento bidimensional (como el conjunto de entrenamiento de lunas) y luego entrenar un clasificador
SVM lineal en el conjunto de entrenamiento transformado. La ecuación 5­8 muestra la función de
mapeo polinómico de segundo grado que desea aplicar.
Ecuación 5­8. Aplicación de polinomios de segundo grado
2x1
(x)=
√2x1x2
(( x1x2 ))=
2x2
Observe que el vector transformado es tridimensional en lugar de bidimensional. Ahora veamos qué sucede
con un par de vectores bidimensionales, a y b, si aplicamos esta función polinómica de segundo grado y luego
7
calculamos el producto escalar de los vectores transformados (consulte la ecuación 5­9).
Ecuación 5­9. Truco de kernel para una aplicación polinómica de segundo grado
2
2 a1
(a)
(b) =
2
b1 √2 b1b2 = a12b1 + 2a1b1a2b2 + a2 2b2
√2a1a2
2a2
b2
2
2
2
= (a1b1 + a2b2)
2
= (( a1 a2 ) ( b1b2 ) )
= (a
b)
2
¿Qué tal? El producto escalar de los vectores transformados es igual al cuadrado del
2
(a) (b) = (a b).
producto escalar de los vectores originales:
Aquí está la idea clave: si aplica la transformación a todas las instancias de entrenamiento, entonces el problema
dual (i)
(j) (ver Ecuación 5­6) contendrá el producto escalar (x ) (x ). Pero si es la transformación polinómica de segundo
grado definida en la Ecuación 5­8, entonces puede reemplazar
Este producto escalar de vectores transformados simplemente por (x (i)
2
x (j)) . Así que no es necesario
No transforme las instancias de entrenamiento en absoluto; simplemente reemplace el producto escalar por su
cuadrado en la ecuación 5­6. El resultado será estrictamente el mismo que si se hubiera tomado la
molestia de transformar el conjunto de entrenamiento y luego ajustar un algoritmo SVM lineal, pero este truco
hace que todo el proceso sea mucho más eficiente computacionalmente.
2
La función K(a, b) = (a b) es un núcleo polinomial de segundo grado. En aprendizaje automático, un núcleo es
una función capaz de calcular el producto escalar (a) (b), basándose únicamente en los vectores originales
a y b, sin tener que calcular (o incluso conocer) la transformación . La ecuación 5­10 enumera
algunos de los núcleos más utilizados.
Ecuación 5­10. Núcleos comunes
Machine Translated by Google
Lineal: K (a, b) = a
b
Polinomio: K (a, b) = (γa
b + r)
Factor de error común gaussiano: K (a, b) = exp (−γ
Sigmoide: K (a, b) = tanh(γa
d
a−b
2)
b + r)
TEOREMA DE MERCER
Según el teorema de Mercer, si una función K(a, b) respeta algunas condiciones matemáticas llamadas
condiciones de Mercer (por ejemplo, K debe ser continua y simétrica en sus argumentos de modo que K(a, b)
= K(b, a), etc.), entonces existe una función que mapea a y b en otro espacio (posiblemente con
dimensiones mucho mayores) de modo que K(a, b) = (a) (b). Puedes usar K como un kernel porque sabes
que existe, incluso si no sabes qué es. En el caso del kernel RBF gaussiano, se puede demostrar que
mapea cada instancia de entrenamiento a un espacio de dimensión infinita, ¡así que es bueno que no
necesites realizar realmente el mapeo!
Tenga en cuenta que algunos núcleos utilizados con frecuencia (como el núcleo sigmoide) no respetan todas
las condiciones de Mercer, aunque generalmente funcionan bien en la práctica.
Todavía hay un cabo suelto que debemos atar. La ecuación 5­7 muestra cómo pasar de la solución dual
a la solución primaria en el caso de un clasificador SVM lineal. Pero si aplicas el truco del núcleo (x ). De
(i)
hecho,
ˆw debe tener el mismo truco del
núcleo, terminas con ecuaciones que incluyen el número de
(i) enorme o incluso infinito, por lo que no puedes calcularlo. Pero ¿cómo
dimensiones como (x ), que puede ser
puedes hacer predicciones sin conocer ˆw? Bueno, la buena noticia es que puedes introducir la fórmula para
ˆw de la ecuación 5­7 en la función de decisión para una nueva instancia y obtienes una ecuación con solo
incógnita
(norte)
, productos escalares entre los vectores de entrada. Esto hace posible usar el truco del núcleo (ecuación
5­11).
Ecuación 5­11. Realización de predicciones con una máquina virtual de software kernelizada
αˆ (i)
hˆw,ˆb(
(x (n))) = ˆw
(x (n)) + ˆb = ( m∑
yo=1
= m∑ αˆ (i) t (i)(
(x (i))
t (i)
(x (i)))
(x (n)) + ˆb
(x (n)))+ˆb
yo=1
= m∑ αˆ (i) t (i) K (x (i) , x (n)) + ˆb
yo=1
αˆ (i)>0
Tenga en cuenta que,(i)dado que α ≠ 0 solo para vectores de soporte, hacer predicciones implica calcular (n) el
producto escalar del nuevo vector de entrada x solo con los vectores de soporte, no con todos los
Machine Translated by Google
instancias de entrenamiento. Por supuesto, es necesario utilizar el mismo truco para calcular el término de sesgo
ˆb (Ecuación 5­12).
Ecuación 5­12. Uso del truco del núcleo para calcular el término de sesgo
1
ns
m∑ (t (i)
1
= m∑
ns
yo=1
αˆ (i)>0
yo=1
αˆ (i)>0
− ˆw
αˆ (j)
yo=1
(x (i)))= m∑ (t
(i) − ( m∑ ˆbj=1
=
yo − m∑ αˆ (j)
j=1
1
ns
αˆ (i)>0
t (j)
(x (j)))
(x (i)))
t (j)K (x (i) , x (j))
αˆ (j)>0
Si empiezas a tener dolor de cabeza, es perfectamente normal: es un desafortunado efecto secundario del truco del
kernel.
SVM en línea
Antes de concluir este capítulo, echemos un vistazo rápido a los clasificadores SVM en línea (recuerde que el aprendizaje
en línea significa aprender de forma incremental, generalmente a medida que llegan nuevas instancias).
En el caso de los clasificadores SVM lineales, un método para implementar un clasificador SVM en línea es utilizar
el método de descenso de gradiente (por ejemplo, utilizando SGDClassifier) para minimizar la función de
costo en la ecuación 5­13, que se deriva del problema primal. Desafortunadamente, el método de descenso de gradiente
converge mucho más lentamente que los métodos basados en QP.
Ecuación 5­13. Función de costo del clasificador SVM lineal
1
J (w, b) = w2 w + C m∑ máx(0, 1 − t (i) (w
yo=1
x (i) + b))
La primera suma de la función de costo hará que el modelo tenga un vector de peso w pequeño, lo que genera un
margen mayor. La segunda suma calcula el total de todas las violaciones del margen. La violación del margen de una
instancia es igual a 0 si está ubicada fuera de la calle y en el lado correcto, o bien es proporcional a la distancia al
lado correcto de la calle. Minimizar este término garantiza que el modelo haga que las violaciones del margen sean
lo más pequeñas y escasas posibles.
Machine Translated by Google
PÉRDIDA DE BISAGRA
La función max(0, 1 – t) se denomina función de pérdida de bisagra (ver la siguiente imagen). Es igual a 0
cuando t ≥ 1. Su derivada (pendiente) es igual a –1 si t < 1 y 0 si t > 1. No es diferenciable en t = 1, pero
al igual que para la regresión Lasso (ver “Regresión Lasso”), aún puede usar el descenso de gradiente
utilizando cualquier subderivada en t = 1 (es decir, cualquier valor entre –1 y 0).
También es posible implementar máquinas virtuales de soporte kernelizadas en línea, como se
8
describe en los artículos “Incremental and Decremental Support Vector Machine Learning”
y “Fast
9 máquinas virtuales de soporte kernelizadas se
Kernel Classifiers with Online and Active Learning”. Estas
implementan en Matlab y C++. Para problemas no lineales a gran escala, es posible que desee
considerar el uso de redes neuronales en su lugar (consulte la Parte II).
Ceremonias
1. ¿Cuál es la idea fundamental detrás de las máquinas de vectores de soporte?
2. ¿Qué es un vector de soporte?
3. ¿Por qué es importante escalar las entradas cuando se utilizan SVM?
4. ¿Puede un clasificador SVM generar un puntaje de confianza cuando clasifica una instancia?
¿Qué pasa con la probabilidad?
5. ¿Debería utilizar la forma primaria o dual del problema SVM para entrenar un modelo en un
conjunto de entrenamiento con millones de instancias y cientos de características?
Machine Translated by Google
6. Supongamos que ha entrenado un clasificador SVM con un núcleo RBF, pero parece que no se ajusta al
conjunto de entrenamiento. ¿Debería aumentar o disminuir γ (gamma)? ¿Qué sucede con C?
7. ¿Cómo se deben configurar los parámetros QP (H, f, A y b) para resolver el margen blando?
¿Problema de clasificador SVM lineal utilizando un solucionador QP estándar?
8. Entrene un LinearSVC en un conjunto de datos linealmente separables. Luego, entrene un SVC y un
SGDClassifier en el mismo conjunto de datos. Vea si puede lograr que produzcan aproximadamente el
mismo modelo.
9. Entrene un clasificador SVM en el conjunto de datos MNIST. Dado que los clasificadores SVM son binarios,
deberá utilizar un algoritmo de uno contra el resto para clasificar los 10 dígitos. Es posible que desee ajustar
los hiperparámetros utilizando pequeños conjuntos de validación para acelerar el proceso. ¿Qué precisión
puede alcanzar?
10. Entrene un regresor SVM en el conjunto de datos de vivienda de California.
Las soluciones a estos ejercicios están disponibles en el Apéndice A.
1 Chih­Jen Lin et al., “Un método de descenso de coordenadas dual para SVM lineal a gran escala”, Actas de la 25.ª
Conferencia internacional sobre aprendizaje automático (2008): 408–415.
2 John Platt, “Optimización mínima secuencial: un algoritmo rápido para entrenar máquinas de vectores de soporte”
(Informe técnico de Microsoft Research, 21 de abril de 1998), https://www.microsoft.com/en­us/research/wp­content/uploads/
2016/02/tr­98­14.pdf.
3 De manera más general, cuando hay n características, la función de decisión es un hiperplano n­dimensional y la
El límite de decisión es un hiperplano de dimensión (n – 1).
4 Zeta (ζ) es la sexta letra del alfabeto griego.
5 Para aprender más sobre programación cuadrática, puede comenzar leyendo a Stephen Boyd y Lieven
El libro de Vandenberghe Convex Optimization (Cambridge University Press, 2004) o vea la serie de conferencias en video de Richard
Brown .
6 La función objetivo es convexa y las restricciones de desigualdad son continuamente diferenciables y convexas.
funciones.
7 Como se explicó en el Capítulo 4, el producto escalar de dos vectores a y b normalmente se representa como a ∙ b. Sin embargo, en el
aprendizaje automático, los vectores se representan frecuentemente como vectores columna (es decir, matrices de una sola columna), por
lo que el producto escalar se logra calculando a
b. Para mantener la coherencia con el resto del libro, utilizaremos esta notación aquí,
ignorando el hecho de que técnicamente esto da como resultado una matriz de una sola celda en lugar de un valor escalar.
8 Gert Cauwenberghs y Tomaso Poggio, “Aprendizaje automático de vectores de soporte incremental y decremental”,
Actas de la 13ª Conferencia internacional sobre sistemas de procesamiento de información neuronal (2000): 388–394.
9 Antoine Bordes et al., “Clasificadores de kernel rápidos con aprendizaje en línea y activo”, Journal of Machine Learning
Investigación 6 (2005): 1579–1619.
Machine Translated by Google
Capítulo 6. Árboles de decisión
Al igual que los SVM, los árboles de decisión son algoritmos de aprendizaje automático versátiles que pueden realizar
tareas de clasificación y regresión, e incluso tareas de múltiples resultados. Son algoritmos potentes,
capaces de ajustar conjuntos de datos complejos. Por ejemplo, en el Capítulo 2 entrenó un modelo
DecisionTreeRegressor en el conjunto de datos de vivienda de California, ajustándolo perfectamente (en
realidad, sobreajustándolo).
Los árboles de decisión también son los componentes fundamentales de los bosques aleatorios (ver Capítulo 7),
que se encuentran entre los algoritmos de aprendizaje automático más potentes disponibles en la actualidad.
En este capítulo comenzaremos por analizar cómo entrenar, visualizar y hacer predicciones con árboles de decisión.
Luego repasaremos el algoritmo de entrenamiento CART que utiliza Scikit­Learn y analizaremos cómo regularizar
árboles y utilizarlos para tareas de regresión. Por último, analizaremos algunas de las limitaciones de los árboles
de decisión.
Entrenamiento y visualización de un árbol de decisiones
Para entender los árboles de decisión, construyamos uno y veamos cómo hace predicciones.
El siguiente código entrena un DecisionTreeClassifier en el conjunto de datos iris (ver Capítulo 4):
desde sklearn.datasets importar load_iris desde sklearn.tree
importar DecisionTreeClassifier
iris = cargar_iris()
X = iris.datos[:, 2:] y = iris.objetivo# largo y ancho del pétalo
árbol_clf = ClasificadorDeÁrbolDeDecisiones(profundidad_máxima=2)
árbol_clf.fit(X, y)
Puede visualizar el árbol de decisiones entrenado utilizando primero el método export_graphviz() para generar un
archivo de definición de gráfico llamado iris_tree.dot:
desde sklearn.tree importar export_graphviz
export_graphviz( árbol_clf,
archivo_de_salida=ruta_de_imagen("iris_tree.dot"),
nombres_de_características=iris.nombres_de_características[2:],
nombres_de_clase=iris.nombres_de_destino,
redondeado=Verdadero,
relleno=Verdadero
)
Machine Translated by Google
Luego, puede utilizar la herramienta de línea de comandos dot del paquete Graphviz para convertir este archivo .dot
1
a una variedad de formatos, como PDF o PNG. Esta línea de comandos convierte el archivo .dot en un archivo
de imagen .png :
$ punto ­Tpng iris_tree.punto ­o iris_tree.png
Su primer árbol de decisiones se parece a la Figura 6­1.
Figura 6­1. Árbol de decisión de Iris
Haciendo predicciones
Veamos cómo el árbol representado en la Figura 6­1 hace predicciones. Supongamos que encuentra una flor de
iris y desea clasificarla. Comienza en el nodo raíz (profundidad 0, en la parte superior): este nodo pregunta si la
longitud del pétalo de la flor es menor a 2,45 cm. Si es así, entonces se mueve hacia abajo hasta el nodo hijo
izquierdo de la raíz (profundidad 1, izquierda). En este caso, es un nodo hoja (es decir, no tiene ningún nodo hijo),
por lo que no hace ninguna pregunta: simplemente observe la clase predicha para ese nodo, y el árbol de
decisiones predice que su flor es una Iris setosa (clase=setosa).
Ahora supongamos que encuentras otra flor, y esta vez la longitud del pétalo es mayor a 2,45 cm.
Debes moverte hacia abajo hasta el nodo hijo derecho de la raíz (profundidad 1, derecha), que no es un nodo de
hoja, por lo que el nodo plantea otra pregunta: ¿el ancho del pétalo es menor a 1,75 cm? Si es así, lo más probable
es que tu flor sea un Iris versicolor (profundidad 2, izquierda). Si no, es probable que sea un Iris virginica
(profundidad 2, derecha). Es realmente así de simple.
Machine Translated by Google
NOTA
Una de las muchas cualidades de los árboles de decisión es que requieren muy poca preparación de datos. De hecho, no
requieren escalar ni centrar las características en absoluto.
El atributo samples de un nodo cuenta a cuántas instancias de entrenamiento se aplica. Por ejemplo, 100
instancias de entrenamiento tienen una longitud de pétalo mayor a 2,45 cm (profundidad 1, derecha) y de
esas 100, 54 tienen un ancho de pétalo menor a 1,75 cm (profundidad 2, izquierda). El atributo value de un
nodo indica a cuántas instancias de entrenamiento de cada clase se aplica este nodo: por ejemplo, el nodo
inferior derecho se aplica a 0 Iris setosa, 1 Iris versicolor y 45 Iris virginica. Por último, el atributo gini de un
nodo mide su impureza: un nodo es “puro” (gini=0) si todas las instancias de entrenamiento a las que se
aplica pertenecen a la misma clase. Por ejemplo, dado que el nodo izquierdo de profundidad 1 se aplica solo
a las instancias de entrenamiento de Iris setosa , es puro y su puntuación de gini es 0. La ecuación 6­1
El
muestra cómo el algoritmo de entrenamiento calcula la ipuntuación de gini G del i nodo. El nodo izquierdo
2
2
2
de profundidad 2 tiene una puntuación de Gini igual a 1 – (0/54) – (49/54) – (5/54) ≈ 0,168.
Ecuación 6­1. Impureza de Gini
Gi = 1 − n∑ 2 pi,k
k=1
En esta ecuación:
El
p es la relación de instancias de clase k entre las instancias de entrenamiento en el nodo i .
Yo sé
NOTA
Scikit­Learn utiliza el algoritmo CART, que produce únicamente árboles binarios: los nodos que no son hojas siempre tienen dos
hijos (es decir, las preguntas solo tienen respuestas de sí o no). Sin embargo, otros algoritmos como ID3 pueden producir árboles
de decisión con nodos que tienen más de dos hijos.
La Figura 6­2 muestra los límites de decisión de este árbol de decisiones. La línea vertical gruesa representa
el límite de decisión del nodo raíz (profundidad 0): longitud del pétalo = 2,45 cm. Dado que el área de la
izquierda es pura (solo Iris setosa), no se puede dividir más. Sin embargo, el área de la derecha es impura,
por lo que el nodo derecho de profundidad 1 la divide en el ancho del pétalo = 1,75 cm (representado
por la línea discontinua). Dado que max_depth se estableció en 2, el árbol de decisiones se detiene allí. Si
establece max_depth en 3, los dos nodos de profundidad 2 agregarían cada uno otro límite de decisión
(representado por las líneas de puntos).
Machine Translated by Google
Figura 6­2. Límites de decisión del árbol de decisión
INTERPRETACIÓN DEL MODELO: CAJA BLANCA VERSUS CAJA NEGRA
Los árboles de decisión son intuitivos y sus decisiones son fáciles de interpretar. A estos modelos
se les suele llamar modelos de caja blanca. Por el contrario, como veremos, los bosques
aleatorios o las redes neuronales se consideran generalmente modelos de caja negra. Hacen
grandes predicciones y se pueden comprobar fácilmente los cálculos que realizaron para
hacerlas; sin embargo, suele ser difícil explicar en términos simples por qué se hicieron las
predicciones. Por ejemplo, si una red neuronal dice que una persona en particular aparece
en una imagen, es difícil saber qué contribuyó a esta predicción: ¿el modelo reconoció los ojos de
esa persona? ¿Su boca? ¿Su nariz? ¿Sus zapatos? ¿O incluso el sofá en el que estaba sentada?
Por el contrario, los árboles de decisión proporcionan reglas de clasificación agradables y simples que
incluso se pueden aplicar manualmente si es necesario (por ejemplo, para la clasificación de flores).
Estimación de probabilidades de clase
Un árbol de decisiones también puede estimar la probabilidad de que una instancia pertenezca a una
clase particular k. Primero recorre el árbol para encontrar el nodo de hoja para esta instancia y luego
devuelve la proporción de instancias de entrenamiento de la clase k en este nodo. Por ejemplo,
supongamos que ha encontrado una flor cuyos pétalos miden 5 cm de largo y 1,5 cm de ancho. El
nodo de hoja correspondiente es el nodo izquierdo de profundidad 2, por lo que el árbol de decisiones
debería generar las siguientes probabilidades: 0 % para Iris setosa (0/54), 90,7 % para Iris versicolor
(49/54) y 9,3 % para Iris virginica (5/54). Y si le pide que prediga la clase, debería generar Iris
versicolor (clase 1) porque tiene la probabilidad más alta. Veamos esto:
>>> árbol_clf.predict_proba([[5, 1.5]]) matriz([[0,
0.90740741, 0.09259259]])
Machine Translated by Google
>>> árbol_clf.predict([[5, 1.5]]) matriz([1])
¡Perfecto! Observe que las probabilidades estimadas serían idénticas en cualquier otro lugar del rectángulo
inferior derecho de la Figura 6­2, por ejemplo, si los pétalos tuvieran 6 cm de largo y 1,5 cm de ancho (aunque
parece obvio que lo más probable es que se trate de un Iris virginica en este caso).
El algoritmo de entrenamiento CART
Scikit­Learn utiliza el algoritmo de árbol de clasificación y regresión (CART) para entrenar árboles de decisión
(también llamados árboles “en crecimiento”). El algoritmo funciona dividiendo primero el conjunto de
entrenamiento en dos subconjuntos utilizando una única característica kay un umbral t (por ejemplo, “longitud del pétalo ≤ 2,45 cm”).
¿Cómo elige k y t ? Busca el par (k,
a t ) que produce los subconjuntos más
a puros (ponderados por su tamaño). La
ecuación 6­2 proporciona la función de costo que el algoritmo intenta minimizar.
Ecuación 6­2. Función de costo CART para clasificación
izquierda
correcto
metro
metro
J (k, tk) = Gizquierda +
Miedo
mleft/right
el número
instancias en
el subconjunto izquierdo/derecho.
donde Gleft/right
mide laesimpureza
deldesubconjunto
izquierdo/derecho,
Una vez que el algoritmo CART ha dividido con éxito el conjunto de entrenamiento en dos, divide los subconjuntos
utilizando la misma lógica, luego los subconjuntos, y así sucesivamente, de forma recursiva. Deja de realizar la
recursión una vez que alcanza la profundidad máxima (definida por el hiperparámetro max_depth), o si no puede
encontrar una división que reduzca la impureza. Algunos otros hiperparámetros (descritos en un momento)
controlan condiciones de detención adicionales (min_samples_split, min_samples_leaf,
min_weight_fraction_leaf y max_leaf_nodes).
ADVERTENCIA
Como puede ver, el algoritmo CART es un algoritmo voraz: busca vorazmente una división óptima en el nivel superior
y luego repite el proceso en cada nivel subsiguiente. No verifica si la división conducirá o no a la menor impureza
posible varios niveles más abajo. Un algoritmo voraz a menudo produce una solución que es razonablemente buena,
pero no se garantiza que sea óptima.
2
Lamentablemente, se sabe que encontrar el árbol óptimo es un problema NP­completo : requiere un tiempo O(exp(m)) , lo que hace
que el problema sea insoluble incluso para conjuntos de entrenamiento pequeños. Por eso debemos conformarnos con una
solución “razonablemente buena”.
Complejidad computacional
Machine Translated by Google
Para realizar predicciones es necesario recorrer el árbol de decisiones desde la raíz hasta una hoja. Los árboles de decisiones
generalmente están aproximadamente equilibrados, por lo que para recorrerlos es necesario pasar por aproximadamente O(log (m))
3
nodos. Dado que cada nodo solo2requiere verificar el valor de una característica, la complejidad general de la predicción es O(log (m)),
independientemente de la cantidad de características. Por lo tanto, las predicciones
son muy rápidas, incluso cuando se
2
trabaja con grandes conjuntos de entrenamiento.
El algoritmo de entrenamiento compara todas las características (o menos si se establece max_features) en todas las muestras de
cada nodo. La comparación de todas las características en todas las muestras de cada nodo da como resultado una
complejidad de entrenamiento de O(n ×2 m log (m)). Para conjuntos de entrenamiento pequeños (menos de unos pocos miles de
instancias), Scikit­Learn puede acelerar el entrenamiento al clasificar previamente los datos (establecer presort=True), pero al hacer
eso se ralentiza considerablemente el entrenamiento para conjuntos de entrenamiento más grandes.
¿Impureza de Gini o entropía?
De forma predeterminada, se utiliza la medida de impureza de Gini, pero puede seleccionar la medida de impureza de
entropía en su lugar estableciendo el hiperparámetro de criterio en "entropía". El concepto de entropía se originó en la
termodinámica como una medida del desorden molecular: la entropía se acerca a cero cuando las moléculas están
quietas y bien ordenadas. La entropía se extendió más tarde a una amplia variedad de dominios, incluida la teoría de la información
de Shannon, donde mide el contenido de información promedio de un mensaje: la entropía es cero cuando todos los mensajes son
4
idénticos. En el aprendizaje automático, la entropía se utiliza con frecuencia como una medida de impureza: la entropía de un
conjunto es cero cuando contiene instancias de una sola clase. La ecuación 6­3 muestra la definición de la entropía del nodo i . Por
ejemplo, el nodo izquierdo de profundidad 2 en la Figura 6­1 tiene una entropía igual
El
49
5
a − log2
( ) − 49
log2 ( 54)5 ≈ 0,445.
54
54
54
Ecuación 6­3. Entropía
Hi = − n∑ pi,k log2 (pi,k)
k=1
pi,k≠0
Entonces, ¿debería utilizar la impureza de Gini o la entropía? La verdad es que, la mayoría de las veces, no hay una gran diferencia: conducen a
árboles similares. La impureza de Gini es ligeramente más rápida de calcular, por lo que es una buena opción predeterminada. Sin embargo,
cuando difieren, la impureza de Gini tiende a aislar la clase más frecuente en su propia rama del árbol, mientras que la entropía tiende
a producir árboles ligeramente más equilibrados .
Hiperparámetros de regularización
Los árboles de decisión hacen muy pocas suposiciones sobre los datos de entrenamiento (a diferencia de los modelos lineales,
que suponen que los datos son lineales, por ejemplo). Si no se limitan, la estructura del árbol se adaptará a los datos de
entrenamiento, ajustándose a ellos con mucha precisión; de hecho, lo más probable es que
Machine Translated by Google
sobreajuste. Este tipo de modelo se suele denominar modelo no paramétrico, no porque no tenga parámetros (a
menudo tiene muchos), sino porque el número de parámetros no se determina antes del entrenamiento, por
lo que la estructura del modelo es libre de ceñirse estrictamente a los datos. Por el contrario, un modelo paramétrico,
como un modelo lineal, tiene un número predeterminado de parámetros, por lo que su grado de libertad es
limitado, lo que reduce el riesgo de sobreajuste (pero aumenta el riesgo de subajuste).
Para evitar el sobreajuste de los datos de entrenamiento, es necesario restringir la libertad del árbol de decisiones
durante el entrenamiento. Como ya sabe, esto se denomina regularización. Los hiperparámetros de
regularización dependen del algoritmo utilizado, pero, en general, puede restringir al menos la profundidad máxima
del árbol de decisiones. En Scikit­Learn, esto se controla mediante el hiperparámetro max_depth (el valor
predeterminado es None, lo que significa ilimitado). Reducir max_depth regularizará el modelo y, por lo tanto, reducirá
el riesgo de sobreajuste.
La clase DecisionTreeClassifier tiene algunos otros parámetros que restringen de manera similar la forma del árbol
de decisiones: min_samples_split (la cantidad mínima de muestras que debe tener un nodo antes de que pueda
dividirse), min_samples_leaf (la cantidad mínima de muestras que debe tener un nodo hoja), min_weight_fraction_leaf
(igual que min_samples_leaf pero expresado como una fracción de la cantidad total de instancias ponderadas),
max_leaf_nodes (la cantidad máxima de nodos hoja) y max_features (la cantidad máxima de características que se
evalúan para dividir en cada nodo). Aumentar los hiperparámetros min_* o reducir los hiperparámetros max_* regularizará
el modelo.
NOTA
Otros algoritmos funcionan entrenando primero el árbol de decisiones sin restricciones y luego podando
(eliminando) los nodos innecesarios. Un nodo cuyos hijos son todos nodos hoja se considera innecesario si la
mejora de pureza que proporciona no es estadísticamente significativa. Se utilizan pruebas estadísticas2 estándar,
como la prueba χ (prueba de chi­cuadrado), para estimar la probabilidad de que la mejora sea puramente el resultado
del azar (lo que se denomina hipótesis nula). Si esta probabilidad, denominada valor p, es superior a un
umbral determinado (normalmente el 5 %, controlado por un hiperparámetro), entonces el nodo se considera innecesario
y se eliminan sus hijos. La poda continúa hasta que se hayan podado todos los nodos innecesarios.
La Figura 6­3 muestra dos árboles de decisión entrenados en el conjunto de datos de lunas (introducido en el Capítulo 5).
A la izquierda, el árbol de decisiones se entrena con los hiperparámetros predeterminados (es decir, sin
restricciones) y, a la derecha, se entrena con min_samples_leaf=4. Es bastante obvio que el modelo de la izquierda
presenta un sobreajuste y que el modelo de la derecha probablemente se generalizará mejor.
Machine Translated by Google
Figura 6­3. Regularización mediante
min_muestras_hoja
Regresión
Los árboles de decisión también pueden realizar tareas de regresión. Construyamos un árbol de regresión
utilizando la clase DecisionTreeRegressor de Scikit­Learn, entrenándolo en un conjunto de datos cuadráticos
ruidosos con max_depth=2:
desde sklearn.tree importar DecisionTreeRegressor
tree_reg = DecisionTreeRegressor(profundidad máxima=2)
tree_reg.fit(X, y)
El árbol resultante se representa en la Figura 6­4.
Figura 6­4. Un árbol de decisión para la regresión
Machine Translated by Google
Este árbol se parece mucho al árbol de clasificación que creó anteriormente. La principal diferencia es que, en lugar
de predecir una clase en cada nodo, predice un valor. Por ejemplo, suponga que desea realizar una predicción
1 por la raíz y, finalmente, llega al nodo de hoja
para una nueva instancia con x = 0,6. Recorre el árbol comenzando
que predice un valor = 0,111. Esta predicción es el valor objetivo promedio de las 110 instancias de entrenamiento
asociadas con este nodo de hoja y da como resultado un error cuadrático medio igual a 0,015 en estas 110
instancias.
Las predicciones de este modelo se representan a la izquierda en la Figura 6­5. Si establece max_depth=3,
obtendrá las predicciones representadas a la derecha. Observe cómo el valor predicho para cada región
siempre es el valor objetivo promedio de las instancias en esa región. El algoritmo divide cada región de una manera
que hace que la mayoría de las instancias de entrenamiento estén lo más cerca posible de ese valor predicho.
Figura 6­5. Predicciones de dos modelos de regresión de árboles de decisión
El algoritmo CART funciona básicamente de la misma manera que antes, excepto que en lugar de intentar dividir
el conjunto de entrenamiento de una manera que minimice la impureza, ahora intenta dividir el conjunto de
entrenamiento de una manera que minimice el MSE. La ecuación 6­4 muestra la función de costo que el algoritmo
intenta minimizar.
Ecuación 6­4. Función de costo CART para regresión
2
izquierda
J(k, tk) =
derecha
MSEizquierda +
metro
MSEnodo = ∑i
nodo (yˆnodo − y (i))
1
MSEjusto donde
metro
yˆnodo =
mnodo
∑i
nodo y
(i)
Al igual que en el caso de las tareas de clasificación, los árboles de decisión son propensos a sobreajustarse
cuando se trata de tareas de regresión. Sin ninguna regularización (es decir, utilizando los hiperparámetros
predeterminados), se obtienen las predicciones de la izquierda en la Figura 6­6. Estas predicciones obviamente
sobreajustan muy mal el conjunto de entrenamiento. Con solo establecer min_samples_leaf=10 se
obtiene un modelo mucho más razonable, representado a la derecha en la Figura 6­6.
Machine Translated by Google
Figura 6­6. Regularización de un regresor de árbol de decisión
Inestabilidad
Con suerte, a esta altura ya estará convencido de que los árboles de decisión tienen muchas ventajas: son fáciles de
entender e interpretar, fáciles de usar, versátiles y potentes. Sin embargo, tienen algunas limitaciones. En primer
lugar, como habrá notado, a los árboles de decisión les encantan los límites de decisión ortogonales (todas las
divisiones son perpendiculares a un eje), lo que los hace sensibles a la rotación del conjunto de entrenamiento. Por
ejemplo, la Figura 6­7 muestra un conjunto de datos linealmente separable simple: a la izquierda, un árbol de decisión
puede dividirlo fácilmente, mientras que a la derecha, después de que el conjunto de datos se rota 45°, el límite de
decisión parece innecesariamente complicado. Aunque ambos árboles de decisión se ajustan perfectamente al conjunto
de entrenamiento, es muy probable que el modelo de la derecha no se generalice bien.
Una forma de limitar este problema es utilizar el análisis de componentes principales (véase el Capítulo 8), que a
menudo da como resultado una mejor orientación de los datos de entrenamiento.
Figura 6­7. Sensibilidad a la rotación del conjunto de entrenamiento
En términos más generales, el principal problema con los árboles de decisión es que son muy sensibles a pequeñas
variaciones en los datos de entrenamiento. Por ejemplo, si simplemente elimina el iris versicolor más ancho del
conjunto de entrenamiento de iris (el que tiene pétalos de 4,8 cm de largo y 1,8 cm de ancho) y entrena un nuevo árbol
de decisión, puede obtener el modelo representado en la Figura 6­8. Como puede ver, se ve
Machine Translated by Google
Muy diferente del árbol de decisión anterior (Figura 6­2). En realidad, dado que el algoritmo de entrenamiento utilizado por Scikit­Learn es
6
estocástico, puede obtener modelos muy diferentes incluso con los mismos datos de entrenamiento (a menos que configure el hiperparámetro random_state).
Figura 6­8. Sensibilidad a los detalles del conjunto de entrenamiento
Los bosques aleatorios pueden limitar esta inestabilidad promediando las predicciones sobre muchos árboles, como veremos en el próximo capítulo.
Ceremonias
1. ¿Cuál es la profundidad aproximada de un árbol de decisión entrenado (sin restricciones) en un conjunto de entrenamiento con un millón de
instancias?
2. ¿La impureza de Gini de un nodo es generalmente menor o mayor que la de su padre? ¿Es generalmente menor/mayor o siempre menor/
mayor?
3. Si un árbol de decisiones está sobreajustando el conjunto de entrenamiento, ¿es una buena idea intentar disminuirlo?
¿profundidad máxima?
4. Si un árbol de decisiones no se ajusta al conjunto de entrenamiento, ¿es una buena idea intentar escalarlo?
¿Características de entrada?
5. Si se necesita una hora para entrenar un árbol de decisiones en un conjunto de entrenamiento que contiene 1 millón de instancias,
¿aproximadamente cuánto tiempo llevará entrenar otro árbol de decisiones en un conjunto de entrenamiento que contiene 10 millones
de instancias?
6. Si su conjunto de entrenamiento contiene 100 000 instancias, ¿la configuración de presort=True acelerará la ejecución?
¿capacitación?
7. Entrene y ajuste un árbol de decisiones para el conjunto de datos de lunas siguiendo estos pasos:
Machine Translated by Google
a. Utilice make_moons(n_samples=10000, noise=0.4) para generar una luna
conjunto de datos.
b. Utilice train_test_split() para dividir el conjunto de datos en un conjunto de entrenamiento y un conjunto de prueba.
colocar.
c. Utilice la búsqueda en cuadrícula con validación cruzada (con la ayuda de GridSearchCV)
clase) para encontrar buenos valores de hiperparámetros para un DecisionTreeClassifier.
Sugerencia: pruebe varios valores para max_leaf_nodes.
d. Entrénelo con el conjunto de entrenamiento completo utilizando estos hiperparámetros y mida el rendimiento de
su modelo en el conjunto de prueba. Debería obtener una precisión de entre el 85 % y el 87 %.
8. Cultiva un bosque siguiendo estos pasos:
a. Continuando con el ejercicio anterior, genere 1000 subconjuntos del conjunto de entrenamiento, cada uno de los
cuales contenga 100 instancias seleccionadas aleatoriamente. Sugerencia: puede utilizar la clase ShuffleSplit de
Scikit­Learn para esto.
b. Entrene un árbol de decisión en cada subconjunto, utilizando el mejor hiperparámetro
Valores encontrados en el ejercicio anterior. Evalúe estos 1000 árboles de decisión en el conjunto de prueba.
Dado que se entrenaron en conjuntos más pequeños, es probable que estos árboles de decisión tengan
un peor desempeño que el primer árbol de decisión y alcancen solo un 80 % de precisión.
c. Ahora viene la magia. Para cada instancia del conjunto de prueba, genere las predicciones de los 1000 árboles
de decisión y conserve solo la predicción más frecuente (puede usar la función mode() de SciPy para esto).
Este enfoque le brinda predicciones de mayoría de votos sobre el conjunto de prueba.
d. Evalúe estas predicciones en el conjunto de prueba: debería obtener una precisión ligeramente mayor que
la de su primer modelo (aproximadamente entre un 0,5 y un 1,5 % mayor).
¡Felicitaciones, has entrenado un clasificador de Random Forest!
Las soluciones a estos ejercicios están disponibles en el Apéndice A.
1 Graphviz es un paquete de software de visualización de gráficos de código abierto, disponible en http://www.graphviz.org/.
2 P es el conjunto de problemas que se pueden resolver en tiempo polinómico. NP es el conjunto de problemas cuyas soluciones se pueden verificar en
tiempo polinómico. Un problema NP­difícil es un problema al que cualquier problema NP se puede reducir en tiempo polinómico. Un problema
NP­completo es tanto NP como NP­difícil. Una pregunta matemática importante abierta es si P = NP o no. Si P ≠ NP (lo que parece probable),
entonces nunca se encontrará un algoritmo polinómico para ningún problema NP­completo (excepto quizás en una computadora cuántica).
3 log es 2
el logaritmo binario. Es igual a log (m) = log(m) / log(2). 2
A una reducción de la entropía a menudo se le denomina ganancia de información.
Machine Translated by Google
5 Véase el interesante análisis de Sebastian Raschka para más detalles.
Selecciona aleatoriamente el conjunto de características a evaluar en cada nodo.
Machine Translated by Google
Capítulo 7. Aprendizaje en conjunto y bosques
aleatorios
Supongamos que plantea una pregunta compleja a miles de personas al azar y luego agrega sus respuestas. En muchos
casos, descubrirá que esta respuesta agregada es mejor que la respuesta de un experto. Esto se denomina la sabiduría de
la multitud. De manera similar, si agrega las predicciones de un grupo de predictores (como clasificadores o regresores), a
menudo obtendrá mejores predicciones que con el mejor predictor individual. Un grupo de predictores se denomina conjunto;
por lo tanto, esta técnica se denomina aprendizaje conjunto, y un algoritmo de aprendizaje conjunto se denomina método
conjunto.
Como ejemplo de un método Ensemble, se puede entrenar un grupo de clasificadores de árboles de decisión,
cada uno en un subconjunto aleatorio diferente del conjunto de entrenamiento. Para hacer predicciones, se obtienen las
predicciones de todos los árboles individuales y luego se predice la clase que obtiene la mayor cantidad de votos (consulte
el último ejercicio del Capítulo 6). Este conjunto de árboles de decisión se denomina Bosque aleatorio y, a pesar de su
simplicidad, es uno de los algoritmos de aprendizaje automático más potentes disponibles en la actualidad.
Como se explicó en el Capítulo 2, a menudo se utilizan métodos Ensemble cerca del final de un proyecto, una vez
que ya se han creado algunos buenos predictores, para combinarlos en un predictor aún mejor. De hecho, las
soluciones ganadoras en las competiciones de aprendizaje automático suelen implicar varios métodos Ensemble (el caso
más famoso fue el de la competición Netflix Prize ).
En este capítulo, analizaremos los métodos de Ensemble más populares, como el bagging, el boosting y el stacking.
También exploraremos los bosques aleatorios.
Clasificadores de votación Supongamos
que ha entrenado unos cuantos clasificadores, cada uno de los cuales logra una precisión de aproximadamente el 80%.
Es posible que tenga un clasificador de regresión logística, un clasificador SVM, un clasificador de bosque aleatorio, un
clasificador de K vecinos más cercanos y quizás algunos más (consulte la Figura 7­1).
Machine Translated by Google
Figura 7­1. Entrenamiento de diversos clasificadores
Una forma muy sencilla de crear un clasificador aún mejor es agregar las predicciones de cada
clasificador y predecir la clase que obtiene más votos. Este clasificador de mayoría de votos
se denomina clasificador de votación dura (consulte la Figura 7­2).
Figura 7­2. Predicciones del clasificador de votación dura
Resulta un tanto sorprendente que este clasificador de votación con frecuencia logre una precisión
mayor que el mejor clasificador del conjunto. De hecho, incluso si cada clasificador es un
aprendiz débil (es decir, que solo lo hace un poco mejor que la suposición aleatoria), el conjunto
puede ser un aprendiz fuerte (logrando una alta precisión), siempre que haya una cantidad
suficiente de aprendices débiles y sean lo suficientemente diversos.
Machine Translated by Google
¿Cómo es posible? La siguiente analogía puede ayudar a arrojar algo de luz sobre este misterio.
Supongamos que tenemos una moneda ligeramente sesgada que tiene un 51% de posibilidades de salir
cara y un 49% de posibilidades de salir cruz. Si la lanzamos 1.000 veces, generalmente obtendremos más
o menos 510 caras y 490 cruces, y por lo tanto una mayoría de caras. Si hacemos los cálculos, veremos
que la probabilidad de obtener una mayoría de caras después de 1.000 lanzamientos es cercana al 75%.
Cuanto más lancemos la moneda, mayor será la probabilidad (por ejemplo, con 10.000 lanzamientos, la
probabilidad aumenta por encima del 97%). Esto se debe a la ley de los grandes números: a medida que
lanzamos la moneda, la proporción de caras se acerca cada vez más a la probabilidad de cara (51%).
La figura 7­3 muestra 10 series de lanzamientos de moneda sesgados. Podemos ver que a medida que
aumenta el número de lanzamientos, la proporción de caras se acerca al 51%. Al final, las 10 series
terminan tan cerca del 51% que se sitúan constantemente por encima del 50%.
Figura 7­3. La ley de los grandes números
De manera similar, supongamos que creamos un conjunto que contiene 1000 clasificadores
que son correctos individualmente solo el 51 % de las veces (apenas mejor que una suposición aleatoria).
Si predecimos la clase votada por la mayoría, ¡podemos esperar una precisión de hasta el 75 %! Sin
embargo, esto solo es cierto si todos los clasificadores son perfectamente independientes y cometen
errores no correlacionados, lo que claramente no es el caso porque se entrenan con los mismos
datos. Es probable que cometan los mismos tipos de errores, por lo que habrá muchos votos mayoritarios
para la clase incorrecta, lo que reducirá la precisión del conjunto.
CONSEJO
Los métodos de conjunto funcionan mejor cuando los predictores son lo más independientes posible entre sí.
Una forma de obtener clasificadores diversos es entrenarlos utilizando algoritmos muy diferentes. Esto aumenta
la probabilidad de que cometan tipos de errores muy diferentes, mejorando así la precisión del conjunto.
Machine Translated by Google
El siguiente código crea y entrena un clasificador de votación en Scikit­Learn, compuesto por tres
clasificadores diversos (el conjunto de entrenamiento es el conjunto de datos moons, presentado en el Capítulo 5):
desde sklearn.ensemble importe RandomForestClassifier desde
sklearn.ensemble importe VotingClassifier desde
sklearn.linear_model importe LogisticRegression desde sklearn.svm
importe SVC
log_clf = Regresión logística() rnd_clf =
Clasificador forestal aleatorio() svm_clf =
SVC()
votación_clf = ClasificadorDeVotación(
estimadores=[('lr', log_clf), ('rf', rnd_clf), ('svc', svm_clf)], votación='hard')
votación_clf.fit(X_train, y_train)
Veamos la precisión de cada clasificador en el conjunto de prueba:
>>> de sklearn.metrics importar puntuación_precisión >>>
para clf en (log_clf, rnd_clf, svm_clf, votación_clf): clf.fit(X_train, y_train)
...
y_pred = clf.predict(X_test)
...
imprimir(clf.__class__.__name__,
...
puntuación_precisión(y_test, y_pred))
...
Regresión logística 0,864 Clasificador
aleatorio de bosques 0,896
CVS 0,888
Clasificador de votación 0.904
¡Y ahí lo tienes! El clasificador de votación supera ligeramente a todos los clasificadores
individuales.
Si todos los clasificadores pueden estimar las probabilidades de clase (es decir, todos tienen
un método predict_proba()), entonces puede indicarle a Scikit­Learn que prediga la clase con la probabilidad
de clase más alta, promediada sobre todos los clasificadores individuales. Esto se llama votación suave. A
menudo logra un mayor rendimiento que la votación dura porque da más peso a los votos altamente
confiables. Todo lo que necesita hacer es reemplazar voting="hard" con voting="soft" y asegurarse de
que todos los clasificadores puedan estimar las probabilidades de clase. Este no es el caso de la clase
SVC de forma predeterminada, por lo que debe establecer su hiperparámetro de probabilidad
en True (esto hará que la clase SVC use la validación cruzada para estimar las probabilidades de clase, lo
que ralentizará el entrenamiento, y agregará un método predict_proba()).
Si modifica el código anterior para utilizar votación suave, encontrará que el clasificador de votación
logra una precisión de más del 91,2%.
Machine Translated by Google
Bagging y Pasting Una forma de obtener
un conjunto diverso de clasificadores es utilizar algoritmos de entrenamiento muy diferentes, como se acaba de
comentar. Otro enfoque es utilizar el mismo algoritmo de entrenamiento para cada predictor y
entrenarlos en diferentes subconjuntos aleatorios del conjunto de entrenamiento. Cuando el muestreo se realiza
con reemplazo, este método se denomina bagging (abreviatura de agregación 1
bootstrap ). Cuando el
2
muestreo se realiza
sin reemplazo, se denomina pegado.
3
En otras palabras, tanto el bagging como el pegado permiten que las instancias de entrenamiento se muestreen
varias veces a través de múltiples predictores, pero solo el bagging permite que las instancias de
entrenamiento se muestreen varias veces para el mismo predictor. Este proceso de muestreo y
entrenamiento se representa en la Figura 7­4.
Figura 7­4. El método bagging and paste implica entrenar varios predictores en diferentes muestras aleatorias del conjunto de entrenamiento.
Una vez que se han entrenado todos los predictores, el conjunto puede hacer una predicción para una nueva
instancia simplemente agregando las predicciones de todos los predictores. La función de agregación es
típicamente el modo estadístico (es decir, la predicción más frecuente, como un clasificador de votación
estricta) para la clasificación, o el promedio para la regresión. Cada predictor individual tiene un sesgo mayor
que si se hubiera entrenado en el conjunto de entrenamiento original, pero la agregación reduce tanto el sesgo
4 el resultado neto es que el conjunto tiene un sesgo similar pero una varianza
como la varianza. Generalmente,
menor que un solo predictor entrenado en el conjunto de entrenamiento original.
Como puede ver en la Figura 7­4, los predictores pueden entrenarse en paralelo, a través de diferentes núcleos
de CPU o incluso de diferentes servidores. De manera similar, las predicciones pueden realizarse en paralelo.
Esta es una de las razones por las que el bagging y el pasting son métodos tan populares: escalan muy bien.
Machine Translated by Google
Empaquetado y pegado en Scikit­Learn
Scikit­Learn ofrece una API sencilla tanto para el empaquetado como para el pegado con
la clase BaggingClassifier (o BaggingRegressor para la regresión). El código siguiente entrena un conjunto
5
de 500 clasificadores de árboles de decisión: cada uno se entrena en 100 instancias de entrenamiento
seleccionadas aleatoriamente del conjunto de entrenamiento con reemplazo (este es un ejemplo de
empaquetado, pero si desea utilizar el pegado en su lugar, simplemente configure bootstrap=False). El
parámetro n_jobs le indica a Scikit­Learn la cantidad de núcleos de CPU que se deben usar para el
entrenamiento y las predicciones (–1 le indica a Scikit­Learn que use todos los núcleos disponibles):
desde sklearn.ensemble importa BaggingClassifier desde sklearn.tree
importa DecisionTreeClassifier
bolsa_clf =
BaggingClassifier( DecisionTreeClassifier(), n_estimadores=500,
muestras_máximas=100, bootstrap=Verdadero, n_trabajos=­1)
bolsa_clf.fit(X_train, y_train) y_pred =
bolsa_clf.predict(X_test)
NOTA
El BaggingClassifier realiza automáticamente una votación suave en lugar de una votación dura si el clasificador base
puede estimar probabilidades de clase (es decir, si tiene un método predict_proba()), lo cual es el caso de los clasificadores
de árbol de decisión.
La Figura 7­5 compara el límite de decisión de un solo árbol de decisión con el límite de decisión de un
conjunto de bagging de 500 árboles (del código anterior), ambos entrenados en el conjunto de datos de lunas.
Como puede ver, las predicciones del conjunto probablemente se generalizarán mucho mejor que las
predicciones del árbol de decisión único: el conjunto tiene un sesgo comparable pero una varianza menor
(comete aproximadamente la misma cantidad de errores en el conjunto de entrenamiento, pero el límite de
decisión es menos irregular).
Machine Translated by Google
Figura 7­5. Un solo árbol de decisión (izquierda) versus un conjunto de 500 árboles (derecha)
El bootstrapping introduce un poco más de diversidad en los subconjuntos en los que se entrena cada
predictor, por lo que el bagging termina con un sesgo ligeramente mayor que el pegado; pero la diversidad
adicional también significa que los predictores terminan estando menos correlacionados, por lo que se reduce
la varianza del conjunto. En general, el bagging a menudo da como resultado mejores modelos, lo que explica
por qué generalmente se prefiere. Sin embargo, si tiene tiempo libre y potencia de CPU, puede usar la
validación cruzada para evaluar tanto el bagging como el pegado y seleccionar el que funcione mejor.
Evaluación fuera de la bolsa
Con bagging, algunas instancias pueden ser muestreadas varias veces para cualquier predictor dado,
mientras que otras pueden no ser muestreadas en absoluto. De forma predeterminada, un BaggingClassifier
muestrea m instancias de entrenamiento con reemplazo (bootstrap=True), donde m es el tamaño del
conjunto de entrenamiento. Esto significa que solo alrededor del 63 % de las instancias de entrenamiento
6
se muestrean en promedio para cada predictor. El 37 % restante de las instancias de entrenamiento que
no se muestrean se denominan instancias fuera de la bolsa (oob). Tenga en cuenta que no son el mismo 37
% para todos los predictores.
Dado que un predictor nunca ve las instancias fuera de línea durante el entrenamiento, se lo puede evaluar
en estas instancias, sin necesidad de un conjunto de validación independiente. Puede evaluar el conjunto
en sí mismo promediando las evaluaciones fuera de línea de cada predictor.
En Scikit­Learn, puede configurar oob_score=True al crear un BaggingClassifier para solicitar una evaluación
oob automática después del entrenamiento. El siguiente código demuestra esto. La puntuación de
evaluación resultante está disponible a través de la variable oob_score_:
>>> bolsa_clf = ClasificadorEmbalaje(
...
DecisionTreeClassifier(), n_estimators=500,
...
bootstrap=True, n_jobs=­1, oob_score=True)
...
>>> bolsa_clf.fit(tren_X, tren_Y)
Machine Translated by Google
>>> puntuación de bolsa clf.oob
0,90133333333333332
Según esta evaluación OOB, es probable que este BaggingClassifier alcance una precisión de aproximadamente el 90,1 % en
el conjunto de prueba. Verifiquemos esto:
>>> de sklearn.metrics importar puntuación_precisión >>> y_pred =
bag_clf.predict(X_test) >>> puntuación_precisión(y_test,
y_pred) 0.91200000000000003
Obtenemos una precisión del 91,2 % en el conjunto de pruebas: ¡bastante cerca!
La función de decisión oob para cada instancia de entrenamiento también está disponible a través de la variable
oob_decision_function_. En este caso (ya que el estimador base tiene un método predict_proba()), la función de
decisión devuelve las probabilidades de clase para cada instancia de entrenamiento. Por ejemplo, la evaluación oob estima
que la primera instancia de entrenamiento tiene una probabilidad del 68,25 % de pertenecer a la clase positiva (y del 31,75 % de
pertenecer a la clase negativa):
>>> función_de_decisión_bolsa_clf.oob_array
([[0,31746032, 0,68253968], [0,34117647,
0,65882353], [1. ],
, 0.
...
, 0.0,96891192],
[1. ], [0,03108808,
[0,57291667, 0,42708333]])
Parches aleatorios y subespacios aleatorios
La clase BaggingClassifier también admite el muestreo de características. El muestreo se controla mediante dos
hiperparámetros: max_features y bootstrap_features. Funcionan de la misma manera que max_samples y bootstrap, pero
para el muestreo de características en lugar del muestreo de instancias. Por lo tanto, cada predictor se entrenará en un subconjunto
aleatorio de las características de entrada.
Esta técnica es particularmente útil cuando se trabaja con entradas de alta dimensión (como imágenes). El muestreo de las instancias de
entrenamiento y las características se denomina método de parches aleatorios . El método de subespacios aleatorios consiste en
7
conservar todas las instancias de entrenamiento (estableciendo bootstrap=False y max_samples=1.0) pero el muestreo de las
características (estableciendo bootstrap_features en True y/o max_features en un valor menor que 1.0). Método 8 .
Machine Translated by Google
Las características de muestreo dan como resultado una diversidad de predictores aún mayor, intercambiando un poco más de sesgo
por una varianza menor.
Bosques aleatorios
9
Como hemos comentado, un Bosque Aleatorio es
un conjunto de árboles de decisión, generalmente
entrenados a través del método bagging (o a veces pasteing), típicamente con max_samples establecido
en el tamaño del conjunto de entrenamiento. En lugar de construir un BaggingClassifier y pasarle un
DecisionTreeClassifier, puede usar la clase RandomForestClassifier, que es más conveniente y está
optimizada para árboles de decisión (de manera similar, existe una clase 10
RandomForestRegressor
para tareas de regresión). El siguiente código usa todos los núcleos de CPU disponibles para
entrenar un clasificador de bosque aleatorio con 500 árboles (cada uno limitado a un máximo de 16
nodos):
desde sklearn.ensemble importar RandomForestClassifier
rnd_clf = RandomForestClassifier(n_estimadores=500, máximo_nodos_hoja=16, n_trabajos=­1) rnd_clf.fit(X_train,
y_train)
y_pred_rf = rnd_clf.predict(X_prueba)
Con algunas excepciones, un RandomForestClassifier tiene todos los hiperparámetros de un
DecisionTreeClassifier (para controlar cómo se cultivan los árboles), más todos los
hiperparámetros de un BaggingClassifier para controlar el conjunto en sí.
11
El algoritmo Random Forest introduce aleatoriedad adicional al hacer crecer los árboles; en lugar de buscar
la mejor característica al dividir un nodo (ver Capítulo 6), busca la mejor característica entre un
subconjunto aleatorio de características. El algoritmo da como resultado una mayor diversidad de árboles,
lo que (de nuevo) cambia un mayor sesgo por una menor varianza, lo que generalmente produce un mejor
modelo en general. El siguiente BaggingClassifier es aproximadamente equivalente al RandomForestClassifier
anterior:
bolsa_clf = ClasificadorEmbalaje(
DecisionTreeClassifier(splitter="aleatorio", máx. nodos_hoja=16), n_estimadores=500,
máx. muestras=1.0, bootstrap=Verdadero, n_trabajos=­1)
Árboles extra
Cuando se crea un árbol en un bosque aleatorio, en cada nodo solo se considera un subconjunto aleatorio
de las características para la división (como se explicó anteriormente). Es posible hacer que los árboles
sean aún más aleatorios utilizando también umbrales aleatorios para cada característica en lugar
de buscar los mejores umbrales posibles (como lo hacen los árboles de decisión normales).
Machine Translated by Google
Un bosque de árboles extremadamente aleatorios se llama árboles extremadamente aleatorios.
12
Conjunto (o Extra­Trees para abreviar). Una vez más, esta técnica intercambia más sesgo por una varianza
menor. También hace que los Extra­Trees sean mucho más rápidos de entrenar que los Bosques
Aleatorios regulares, porque encontrar el mejor umbral posible para cada característica en cada nodo es una
de las tareas que más tiempo consumen al hacer crecer un árbol.
Puede crear un clasificador Extra­Trees utilizando la clase ExtraTreesClassifier de Scikit­Learn. Su API
es idéntica a la de la clase RandomForestClassifier. De manera similar, la clase
ExtraTreesRegressor tiene la misma API que la clase RandomForestRegressor.
CONSEJO
Es difícil saber de antemano si un RandomForestClassifier tendrá un mejor o peor rendimiento que un
ExtraTreesClassifier. En general, la única forma de saberlo es probar ambos y compararlos mediante una
validación cruzada (ajustando los hiperparámetros mediante una búsqueda en cuadrícula).
Importancia de las características
Otra gran cualidad de los bosques aleatorios es que facilitan la medición de la importancia relativa de
cada característica. Scikit­Learn mide la importancia de una característica observando cuánto reducen
la impureza en promedio los nodos del árbol que utilizan esa característica (en todos los árboles del
bosque). Más precisamente, es un promedio ponderado, donde el peso de cada nodo es igual a la
cantidad de muestras de entrenamiento que están asociadas con él (consulte el Capítulo 6).
Scikit­Learn calcula esta puntuación automáticamente para cada característica después del entrenamiento
y luego escala los resultados de modo que la suma de todas las importancias sea igual a 1. Puede
acceder al resultado utilizando la variable feature_importances_. Por ejemplo, el siguiente código
entrena un RandomForestClassifier en el conjunto de datos del iris (introducido en el Capítulo 4) y
genera la importancia de cada característica. Parece que las características más importantes son la
longitud (44%) y el ancho (42%) de los pétalos, mientras que la longitud y el ancho de los
sépalos son bastante poco importantes en comparación (11% y 2%, respectivamente):
>>> de sklearn.datasets importa load_iris >>> iris =
load_iris() >>> rnd_clf =
RandomForestClassifier(n_estimators=500, n_jobs=­1) >>> rnd_clf.fit(iris["data"],
iris["target"]) >>> para nombre, puntaje en código
postal(iris["feature_names"], rnd_clf.feature_importances_):
...
imprimir(nombre, puntuación)
...
longitud del sépalo (cm) 0,112492250999
ancho del sépalo (cm) 0,0231192882825
longitud del pétalo (cm) 0,441030464364
ancho del pétalo (cm) 0,423357996355
Machine Translated by Google
De manera similar, si entrena un clasificador Random Forest en el conjunto de datos MNIST (presentado en
el Capítulo 3) y grafica la importancia de cada píxel, obtendrá la imagen representada en la Figura 7­6 .
Figura 7­6. Importancia de los píxeles del MNIST (según un clasificador de Random Forest)
Los bosques aleatorios son muy útiles para obtener una comprensión rápida de qué características realmente
importan, en particular si necesita realizar una selección de características.
Boosting Boosting
( originalmente llamado boosting de hipótesis) se refiere a cualquier método Ensemble que pueda combinar
varios aprendices débiles en un aprendiz fuerte. La idea general de la mayoría de los métodos de boosting
es entrenar predictores secuencialmente, cada uno tratando de corregir a su predecesor. Hay muchos
13
métodos de boosting disponibles, pero los más populares son AdaBoost (abreviatura de Adaptive Boosting) y
Gradient Boosting. Empecemos con AdaBoost.
AdaBoost
Una forma de que un nuevo predictor corrija a su predecesor es prestar un poco más de atención a las
instancias de entrenamiento que el predecesor no ajustó correctamente. Esto hace que los nuevos
predictores se centren cada vez más en los casos difíciles. Esta es la técnica que utiliza AdaBoost.
Machine Translated by Google
Por ejemplo, al entrenar un clasificador AdaBoost, el algoritmo primero entrena un clasificador base (como
un árbol de decisión) y lo usa para hacer predicciones en el conjunto de entrenamiento.
Luego, el algoritmo aumenta el peso relativo de las instancias de entrenamiento mal clasificadas.
Luego entrena un segundo clasificador, utilizando los pesos actualizados, y nuevamente hace
predicciones en el conjunto de entrenamiento, actualiza los pesos de las instancias, y así sucesivamente (ver Figura 7­7).
Figura 7­7. Entrenamiento secuencial de AdaBoost con actualizaciones de peso de instancias
La figura 7­8 muestra los límites de decisión de cinco predictores consecutivos en el conjunto de datos de lunas
(en este ejemplo, cada predictor es un clasificador SVM altamente regularizado con un núcleo RBF). El primer
14
clasificador se equivoca en muchas instancias, por lo que sus ponderaciones aumentan. Por lo tanto, el
segundo clasificador hace un mejor trabajo en estas instancias, y así sucesivamente.
El gráfico de la derecha representa la misma secuencia de predictores, excepto que la tasa de
aprendizaje se reduce a la mitad (es decir, los pesos de las instancias mal clasificadas aumentan a la mitad en
cada iteración). Como puede ver, esta técnica de aprendizaje secuencial tiene algunas similitudes con
el Descenso de gradiente, excepto que en lugar de ajustar los parámetros de un solo predictor para minimizar
una función de costo, AdaBoost agrega predictores al conjunto, lo que lo mejora gradualmente.
Machine Translated by Google
Figura 7­8. Límites de decisión de predictores consecutivos
Una vez que todos los predictores están entrenados, el conjunto hace predicciones muy similares a las de bagging
o paste, excepto que los predictores tienen diferentes pesos dependiendo de su precisión general en el
conjunto de entrenamiento ponderado.
ADVERTENCIA
Esta técnica de aprendizaje secuencial tiene un inconveniente importante: no se puede paralelizar (o solo
parcialmente), ya que cada predictor solo se puede entrenar después de que se haya entrenado y evaluado el
predictor anterior. Como resultado, no se escala tan bien como el bagging o el paste.
Analicemos más de cerca el algoritmo AdaBoost. El peso de cada instancia w es inicialmente
1 juego a .
metro
(i)
Se entrena un primer predictor y se calcula su tasa de error ponderada r en
1 el
conjunto de entrenamiento; ver ecuación 7­1.
El
Ecuación 7­1. Tasa de error ponderada del predictor j
m∑w
(i)
i=1
(i)
yˆyo ≠y (i)
donde yˆ
rj =
metro∑
(i)
yo
es la j
El
El
predicción del predictor para el i
instancia.
el (i)
yo=1
Luego, se calcula el peso α del predictor
utilizando la ecuación 7­2, donde η es el hiperparámetro de tasa de
yo
15
aprendizaje (el valor predeterminado es 1). Cuanto más preciso sea el predictor, mayor será su peso. Si solo está
adivinando al azar, entonces su peso será cercano a cero.
Sin embargo, si la mayoría de las veces es erróneo (es decir, es menos preciso que una suposición aleatoria),
entonces su peso será negativo.
Machine Translated by Google
Ecuación 7­2. Peso del predictor
αj = η log
1 − rj
rj
A continuación, el algoritmo AdaBoost actualiza los pesos de las instancias, utilizando la ecuación 7­3,
que aumenta los pesos de las instancias mal clasificadas.
Ecuación 7­3. Regla de actualización de peso
para i = 1, 2,
,m
(i) (i) si yˆj
= y (i) (i) ≠ y
el
w(i) exp (αj) si yˆj
(i) ←{ w(i)
metro
Luego, todos los pesos de instancia se normalizan (es decir, se dividen por ∑=1
yo
(i) ).
Por último, se entrena un nuevo predictor utilizando los pesos actualizados y se repite todo el proceso
(se calcula el peso del nuevo predictor, se actualizan los pesos de la instancia, luego se entrena
otro predictor, y así sucesivamente). El algoritmo se detiene cuando se alcanza la cantidad
deseada de predictores o cuando se encuentra un predictor perfecto.
Para realizar predicciones, AdaBoost simplemente calcula las predicciones de todos los predictores y las pondera
utilizando los pesos de los predictores α . La clase predicha es la que j recibe la mayoría de los votos
ponderados (ver Ecuación 7­4).
Ecuación 7­4. Predicciones de AdaBoost
norte
yˆ (x) = argmax
a
∑ αj donde N es el número de predictores.
j=1
yˆj(x)=k
16
Scikit­Learn utiliza una versión multiclase de AdaBoost llamada SAMME (que significa Modelado
aditivo por etapas usando una función de pérdida exponencial multiclase). Cuando solo hay dos clases,
SAMME es equivalente a AdaBoost. Si los predictores pueden estimar las probabilidades de clase (es
decir, si tienen un método predict_proba()), Scikit­Learn puede usar una variante de SAMME llamada
SAMME.R (la R significa “Real”), que se basa en las probabilidades de clase en lugar de las
predicciones y, en general, funciona mejor.
El código siguiente entrena un clasificador AdaBoost basado en 200 Decision Stumps utilizando la
clase AdaBoostClassifier de Scikit­Learn (como se podría esperar, también hay una clase
AdaBoostRegressor). Un Decision Stump es un árbol de decisión con max_depth=1—
Machine Translated by Google
En otras palabras, un árbol compuesto por un único nodo de decisión más dos nodos de hoja. Este es el
estimador base predeterminado para la clase AdaBoostClassifier:
Desde sklearn.ensemble, importe AdaBoostClassifier
ada_clf = Clasificador AdaBoost(
Clasificador de árboles de decisión (profundidad máxima = 1), n_estimadores =
200, algoritmo="SAMME.R", tasa de aprendizaje = 0,5)
ada_clf.fit(entrenamiento X, entrenamiento y)
CONSEJO
Si su conjunto AdaBoost sobreajusta el conjunto de entrenamiento, puede intentar reducir la cantidad de
estimadores o regularizar más fuertemente el estimador base.
Aumento de gradiente Otro
17
algoritmo de aumento muy popular es el aumento de gradiente. Al igual que AdaBoost, el aumento de
gradiente funciona añadiendo predictores de forma secuencial a un conjunto, cada uno de los cuales
corrige a su predecesor. Sin embargo, en lugar de ajustar los pesos de las instancias en cada iteración como
lo hace AdaBoost, este método intenta ajustar el nuevo predictor a los errores residuales cometidos por el
predictor anterior.
Veamos un ejemplo de regresión simple, utilizando árboles de decisión como predictores de base
(por supuesto, el aumento de gradiente también funciona muy bien con tareas de regresión). Esto se denomina
aumento de árbol de gradiente o árboles de regresión potenciados por gradiente (GBRT). Primero,
ajustemos un DecisionTreeRegressor al conjunto de entrenamiento (por ejemplo, un conjunto de
entrenamiento cuadrático ruidoso):
desde sklearn.tree importar DecisionTreeRegressor
árbol_reg1 = DecisionTreeRegressor(profundidad_máxima=2)
árbol_reg1.fit(X, y)
A continuación, entrenaremos un segundo DecisionTreeRegressor con los errores residuales cometidos por
el primer predictor:
y2 = y ­ árbol_reg1.predict(X) árbol_reg2 =
DecisionTreeRegressor(profundidad_máxima=2) árbol_reg2.fit(X,
y2)
Luego entrenamos un tercer regresor sobre los errores residuales cometidos por el segundo predictor:
Machine Translated by Google
y3 = y2 ­ árbol_reg2.predict(X)
árbol_reg3 = DecisionTreeRegressor(profundidad_máxima=2)
árbol_reg3.fit(X, y3)
Ahora tenemos un conjunto que contiene tres árboles. Puede hacer predicciones sobre una nueva
instancia simplemente sumando las predicciones de todos los árboles:
y_pred = suma(árbol.predict(X_new) para árbol en (árbol_reg1, árbol_reg2, árbol_reg3))
La Figura 7­9 representa las predicciones de estos tres árboles en la columna de la izquierda y las
predicciones del conjunto en la columna de la derecha. En la primera fila, el conjunto tiene solo un
árbol, por lo que sus predicciones son exactamente las mismas que las del primer árbol. En la segunda
fila, se entrena un nuevo árbol con los errores residuales del primer árbol. A la derecha, puede ver que las
predicciones del conjunto son iguales a la suma de las predicciones de los dos primeros árboles. De
manera similar, en la tercera fila, se entrena otro árbol con los errores residuales del segundo árbol.
Puede ver que las predicciones del conjunto mejoran gradualmente a medida que se agregan árboles al
conjunto.
Una forma más sencilla de entrenar conjuntos GBRT es usar la clase
GradientBoostingRegressor de Scikit­Learn. Al igual que la clase RandomForestRegressor, tiene
hiperparámetros para controlar el crecimiento de los árboles de decisión (por ejemplo,
max_depth, min_samples_leaf), así como hiperparámetros para controlar el entrenamiento del conjunto,
como la cantidad de árboles (n_estimators). El siguiente código crea el mismo conjunto que el anterior:
desde sklearn.ensemble importar GradientBoostingRegressor
gbrt = GradientBoostingRegressor(profundidad_máxima=2, n_estimadores=3, tasa_de_aprendizaje=1.0)
gbrt.fit(X, y)
Machine Translated by Google
Figura 7­9. En esta representación de la potenciación de gradiente, el primer predictor (arriba a la izquierda) se entrena de manera normal,
luego cada predictor consecutivo (centro a la izquierda y abajo a la izquierda) se entrena con los residuos del predictor anterior; la columna de la
derecha muestra las predicciones del conjunto resultante
El hiperparámetro learning_rate escala la contribución de cada árbol. Si lo establece en un valor bajo, como
0,1, necesitará más árboles en el conjunto para ajustarse al conjunto de entrenamiento, pero las predicciones
generalmente se generalizarán mejor. Esta es una técnica de regularización llamada contracción. La
Figura 7­10 muestra dos conjuntos GBRT entrenados con una tasa de aprendizaje baja: el de la izquierda
no tiene suficientes árboles para ajustarse al conjunto de entrenamiento, mientras que el de la derecha
tiene demasiados árboles y se ajusta en exceso al conjunto de entrenamiento.
Machine Translated by Google
Figura 7­10. Conjuntos GBRT con predictores insuficientes (izquierda) y demasiados (derecha)
Para encontrar el número óptimo de árboles, puedes utilizar la parada temprana (ver Capítulo 4).
Una forma sencilla de implementar esto es usar el método staged_predict(): devuelve un iterador sobre
las predicciones realizadas por el conjunto en cada etapa del entrenamiento (con un árbol, dos árboles,
etc.). El siguiente código entrena un conjunto GBRT con 120 árboles, luego mide el error de validación
en cada etapa del entrenamiento para encontrar la cantidad óptima de árboles y, finalmente, entrena
otro conjunto GBRT utilizando la cantidad óptima de árboles:
importar numpy como np
desde sklearn.model_selection importar train_test_split desde
sklearn.metrics importar error_cuadrático_medio
X_tren, X_val, y_tren, y_val = tren_prueba_split(X, y)
gbrt = GradientBoostingRegressor(profundidad_máxima=2, n_estimadores=120)
gbrt.fit(entrenamiento_X, entrenamiento_y)
errores = [error_cuadrado_medio(y_val, y_pred) para y_pred
en gbrt.staged_predict(X_val)] bst_n_estimators =
np.argmin(errores) + 1
gbrt_best = GradientBoostingRegressor(profundidad_máxima=2,n_estimadores=bst_n_estimadores)
gbrt_best.fit(entrenamiento_X, entrenamiento_y)
Los errores de validación están representados a la izquierda de la Figura 7­11, y las mejores
predicciones del modelo están representadas a la derecha.
Machine Translated by Google
Figura 7­11. Ajuste del número de árboles mediante detención temprana
También es posible implementar la detención temprana deteniendo el entrenamiento antes de
tiempo (en lugar de entrenar primero una gran cantidad de árboles y luego mirar hacia atrás para
encontrar la cantidad óptima). Puede hacerlo configurando warm_start=True, lo que hace que Scikit­
Learn mantenga los árboles existentes cuando se llama al método fit(), lo que permite un
entrenamiento incremental. El siguiente código detiene el entrenamiento cuando el error de validación
no mejora durante cinco iteraciones seguidas:
gbrt = GradientBoostingRegressor(profundidad máxima=2, inicio cálido=Verdadero)
min_val_error = float("inf") error_going_up =
0 para n_estimadores en el
rango (1, 120):
gbrt.n_estimators = n_estimators gbrt.fit(X_train,
y_train) y_pred = gbrt.predict(X_val)
val_error = error_cuadrático_medio(y_val,
y_pred) si val_error < min_val_error: min_val_error = val_error
error_subiendo = 0 de lo contrario:
error_subiendo += 1 si
error_subiendo == 5: romper
# parada temprana
La clase GradientBoostingRegressor también admite un hiperparámetro de submuestra, que especifica
la fracción de instancias de entrenamiento que se utilizará para entrenar cada árbol. Por ejemplo, si
submuestra=0,25, cada árbol se entrena con el 25 % de las instancias de entrenamiento, seleccionadas
aleatoriamente. Como probablemente ya pueda adivinar, esta técnica intercambia un sesgo mayor
por una varianza menor. También acelera considerablemente el entrenamiento. Esto se
denomina Stochastic Gradient Boosting.
Machine Translated by Google
NOTA
Es posible utilizar el aumento de gradiente con otras funciones de costo. Esto se controla mediante el hiperparámetro
de pérdida (consulte la documentación de Scikit­Learn para obtener más detalles).
Vale la pena señalar que una implementación optimizada de Gradient Boosting está disponible en la popular
biblioteca de Python XGBoost. que significa Extreme Gradient Boosting. Este paquete fue desarrollado inicialmente
por Tianqi Chen como parte del proyecto Distributed (Deep)
Comunidad de aprendizaje automático (DMLC) y su objetivo es ser extremadamente rápida, escalable y portátil. De
hecho, XGBoost suele ser un componente importante de las propuestas ganadoras en las competiciones de
aprendizaje automático. La API de XGBoost es bastante similar a la de Scikit­Learn:
importar xgboost
xgb_reg = xgboost.XGBRegressor()
xgb_reg.fit(X_train, y_train) y_pred =
xgb_reg.predict(X_val)
XGBoost también ofrece varias funciones interesantes, como la de encargarse automáticamente de la detención
anticipada:
xgb_reg.fit(tren_X, tren_Y,
conjunto_eval=[(X_val, y_val)], rondas_de_detención_temprana=2)
y_pred = xgb_reg.predict(X_val)
¡Definitivamente deberías probarlo!
Apilamiento El
último método Ensemble que discutiremos en este capítulo se llama apilamiento (abreviatura de generalización
18
apilada). Se basa en una idea sencilla:
en lugar de utilizar funciones triviales (como la votación directa) para agregar
las predicciones de todos los predictores de un conjunto, ¿por qué no entrenamos un modelo para que realice esta
agregación? La figura 7­12 muestra un conjunto de este tipo que realiza una tarea de regresión en una
nueva instancia. Cada uno de los tres predictores inferiores predice un valor diferente (3.1, 2.7 y 2.9) y, luego,
el predictor final (llamado mezclador o metaaprendiz) toma estas predicciones como entradas y realiza la
predicción final (3.0).
Machine Translated by Google
Figura 7­12. Agregación de predicciones mediante un predictor de combinación
19
Para entrenar a Blender, un enfoque común es utilizar un conjunto de reserva. Veamos cómo funciona.
Primero, el conjunto de entrenamiento se divide en dos subconjuntos. El primer subconjunto se utiliza para entrenar
a los predictores en la primera capa (consulte la Figura 7­13).
Machine Translated by Google
Figura 7­13. Entrenamiento de la primera capa
A continuación, los predictores de la primera capa se utilizan para realizar predicciones en el segundo conjunto
(retenido) (consulte la Figura 7­14). Esto garantiza que las predicciones sean "limpias", ya que los predictores
nunca vieron estas instancias durante el entrenamiento. Para cada instancia en el conjunto retenido, hay tres
valores predichos. Podemos crear un nuevo conjunto de entrenamiento utilizando estos valores predichos como
características de entrada (lo que hace que este nuevo conjunto de entrenamiento sea 3D) y manteniendo los valores objetivo.
La licuadora se entrena con este nuevo conjunto de entrenamiento, por lo que aprende a predecir el valor
objetivo, dadas las predicciones de la primera capa.
Machine Translated by Google
Figura 7­14. Entrenamiento de la licuadora
En realidad, es posible entrenar varios mezcladores diferentes de esta manera (por ejemplo, uno usando
regresión lineal y otro usando regresión de bosque aleatorio) para obtener una capa completa de mezcladores.
El truco consiste en dividir el conjunto de entrenamiento en tres subconjuntos: el primero se utiliza para
entrenar la primera capa, el segundo se utiliza para crear el conjunto de entrenamiento utilizado para entrenar la
segunda capa (utilizando las predicciones realizadas por los predictores de la primera capa) y el tercero se
utiliza para crear el conjunto de entrenamiento para entrenar la tercera capa (utilizando las predicciones
realizadas por los predictores de la segunda capa). Una vez hecho esto, podemos hacer una predicción para
una nueva instancia recorriendo cada capa secuencialmente, como se muestra en la Figura 7­15.
Machine Translated by Google
Figura 7­15. Predicciones en un conjunto de apilamiento multicapa
Lamentablemente, Scikit­Learn no admite el apilamiento directamente, pero no es demasiado difícil implementarlo usted
mismo (consulte los siguientes ejercicios). Como alternativa, puede utilizar una implementación de código abierto como brew.
Ceremonias
1. Si ha entrenado cinco modelos diferentes con exactamente los mismos datos de entrenamiento y todos
ellos alcanzan una precisión del 95 %, ¿existe alguna posibilidad de que pueda combinar estos modelos
para obtener mejores resultados? Si es así, ¿cómo? Si no, ¿por qué?
Machine Translated by Google
2. ¿Cuál es la diferencia entre los clasificadores de votación dura y blanda?
3. ¿Es posible acelerar el entrenamiento de un conjunto de embolsado distribuyéndolo?
¿En varios servidores? ¿Qué tal pegar conjuntos, potenciar conjuntos, usar bosques aleatorios o apilar
conjuntos?
4. ¿Cuál es el beneficio de la evaluación fuera de la bolsa?
5. ¿Qué hace que los Extra­Trees sean más aleatorios que los Random Forests normales? ¿Cómo puede
ayudar esta aleatoriedad adicional? ¿Los Extra­Trees son más lentos o más rápidos que los Random
Forests normales?
6. Si su conjunto AdaBoost no se ajusta a los datos de entrenamiento, ¿qué hiperparámetros?
¿Deberías hacer ajustes y cómo?
7. Si su conjunto de refuerzo de gradiente se ajusta en exceso al conjunto de entrenamiento, ¿debería
¿Aumentar o disminuir la tasa de aprendizaje?
8. Cargue los datos MNIST (introducidos en el Capítulo 3) y divídalos en un conjunto de entrenamiento, un
conjunto de validación y un conjunto de prueba (por ejemplo, utilice 50 000 instancias para el
entrenamiento, 10 000 para la validación y 10 000 para la prueba). Luego, entrene varios clasificadores,
como un clasificador de bosque aleatorio, un clasificador de árboles adicionales y un clasificador SVM.
A continuación, intente combinarlos en un conjunto que supere a cada clasificador individual en el conjunto
de validación, mediante votación suave o dura. Una vez que haya encontrado uno, pruébelo en el conjunto
de prueba. ¿Cuánto mejor funciona en comparación con los clasificadores individuales?
9. Ejecute los clasificadores individuales del ejercicio anterior para realizar predicciones en el conjunto de
validación y cree un nuevo conjunto de entrenamiento con las predicciones resultantes: cada
instancia de entrenamiento es un vector que contiene el conjunto de predicciones de todos sus clasificadores
para una imagen y el objetivo es la clase de la imagen. Entrene un clasificador en este nuevo conjunto de
entrenamiento. ¡Felicitaciones, acaba de entrenar un mezclador y, junto con los clasificadores, forma un
conjunto de apilamiento! Ahora evalúe el conjunto en el conjunto de prueba. Para cada imagen en el
conjunto de prueba, realice predicciones con todos sus clasificadores y luego envíe las predicciones
al mezclador para obtener las predicciones del conjunto. ¿Cómo se compara con el clasificador de votación
que entrenó anteriormente?
Las soluciones a estos ejercicios están disponibles en el Apéndice A.
1 Leo Breiman, “Bagging Predictors”, Machine Learning 24, núm. 2 (1996): 123–140.
2 En estadística, el remuestreo con reemplazo se denomina bootstrap.
Machine Translated by Google
3 Leo Breiman, “Pegar pequeños votos para su clasificación en bases de datos grandes y en línea”, Machine Learning
36, no. 1–2 (1999): 85–103.
4 El sesgo y la varianza se introdujeron en el Capítulo 4.
Alternativamente , max_samples se puede establecer en un valor flotante entre 0,0 y 1,0, en cuyo caso el número máximo de instancias
a muestrear es igual al tamaño del conjunto de entrenamiento multiplicado por max_samples.
A medida que m crece , esta relación se aproxima a 1 – exp(–1) ≈ 63,212%.
7 Gilles Louppe y Pierre Geurts, “Conjuntos en parches aleatorios”, Notas de clase en Ciencias de la Computación
7523 (2012): 346–361.
8 Tin Kam Ho, “El método del subespacio aleatorio para construir bosques de decisiones”, IEEE Transactions on Pattern Analysis and
Machine Intelligence 20, no. 8 (1998): 832–844.
9 Tin Kam Ho, “Bosques de decisiones aleatorias”, Actas de la Tercera Conferencia Internacional sobre el Documento
Análisis y Reconocimiento 1 (1995): 278.
La clase BaggingClassifier sigue siendo útil si desea una bolsa de algo más que árboles de decisión.
11 Hay algunas excepciones notables: el divisor está ausente (obligado a "aleatorio"), el presort está ausente (obligado a
Falso), max_samples está ausente (forzado a 1.0) y base_estimator está ausente (forzado a DecisionTreeClassifier
con los hiperparámetros proporcionados).
12 Pierre Geurts et al., “Árboles extremadamente aleatorios”, Machine Learning 63, no. 1 (2006): 3–42.
13 Yoav Freund y Robert E. Schapire, “Una generalización teórica de decisiones del aprendizaje en línea y una aplicación al boosting”,
Journal of Computer and System Sciences 55, no. 1 (1997): 119–139.
14 Esto es solo para fines ilustrativos. Los SVM generalmente no son buenos predictores de base para AdaBoost; son
lento y tiende a ser inestable con él.
15 El algoritmo original AdaBoost no utiliza un hiperparámetro de tasa de aprendizaje.
16 Para más detalles, véase Ji Zhu et al., “Multi­Class AdaBoost”, Statistics and Its Interface 2, no. 3 (2009): 349–
360.
17 El aumento de gradiente se introdujo por primera vez en el artículo de Leo Breiman de 1997 “Arcing the Edge” y se desarrolló más
en el documento de 1999 “Aproximación de funciones codiciosas: una máquina potenciadora de gradientes” por Jerome H. Friedman.
18 David H. Wolpert, “Generalización apilada”, Neural Networks 5, no. 2 (1992): 241–259.
19 Alternativamente, es posible utilizar predicciones fuera de contexto. En algunos contextos, esto se denomina apilamiento, mientras que
El uso de un conjunto de retención se denomina combinación. Para muchas personas, estos términos son sinónimos.
Machine Translated by Google
Capítulo 8. Reducción de la
dimensionalidad
Muchos problemas de aprendizaje automático implican miles o incluso millones de
características para cada instancia de entrenamiento. Todas estas características no solo
hacen que el entrenamiento sea extremadamente lento, sino que también pueden dificultar
mucho la búsqueda de una buena solución, como veremos. Este problema suele
denominarse la maldición de la dimensionalidad.
Afortunadamente, en los problemas del mundo real, a menudo es posible reducir
considerablemente la cantidad de características, convirtiendo un problema insoluble en
uno tratable. Por ejemplo, considere las imágenes MNIST (introducidas en el Capítulo 3):
los píxeles en los bordes de la imagen son casi siempre blancos, por lo que podría eliminar
completamente estos píxeles del conjunto de entrenamiento sin perder mucha
información. La Figura 7­6 confirma que estos píxeles son absolutamente irrelevantes para la
tarea de clasificación. Además, dos píxeles vecinos a menudo están altamente
correlacionados: si los fusiona en un solo píxel (por ejemplo, tomando la media de las
intensidades de los dos píxeles), no perderá mucha información.
ADVERTENCIA
Reducir la dimensionalidad provoca cierta pérdida de información (al igual que comprimir una imagen a
JPEG puede degradar su calidad), por lo que, aunque acelerará el entrenamiento, puede hacer que el
rendimiento de su sistema sea ligeramente peor. También hace que sus procesos sean un poco más
complejos y, por lo tanto, más difíciles de mantener. Por lo tanto, si el entrenamiento es demasiado
lento, primero debe intentar entrenar su sistema con los datos originales antes de considerar
el uso de la reducción de dimensionalidad. En algunos casos, reducir la dimensionalidad de los datos de
entrenamiento puede filtrar algo de ruido y detalles innecesarios y, por lo tanto, dar como resultado
un mayor rendimiento, pero en general no será así; solo acelerará el entrenamiento.
Machine Translated by Google
Además de acelerar el entrenamiento, la reducción de la dimensionalidad también es
extremadamente útil para la visualización de datos (o DataViz). Reducir la cantidad de dimensiones
a dos (o tres) permite trazar una vista condensada de un conjunto de entrenamiento de alta dimensión
en un gráfico y, a menudo, obtener información importante al detectar patrones visualmente,
como clústeres.
Además, DataViz es esencial para comunicar sus conclusiones a personas que no son científicos de
datos, en particular a los tomadores de decisiones que utilizarán sus resultados.
En este capítulo, analizaremos la maldición de la dimensionalidad y nos haremos una idea de lo que
ocurre en el espacio de alta dimensión. Luego, consideraremos los dos enfoques principales para la
reducción de la dimensionalidad (proyección y aprendizaje de variedades) y repasaremos tres de
las técnicas de reducción de la dimensionalidad más populares: PCA, Kernel PCA
y LLE.
La maldición de la dimensionalidad Estamos tan acostumbrados
1
a vivir en tres dimensiones que nuestra intuición nos falla cuando tratamos de imaginar un
espacio de alta dimensión. Incluso un hipercubo básico de 4D es increíblemente difícil de
imaginar en nuestras mentes (ver Figura 8­1), y más aún un elipsoide de 200 dimensiones doblado
en un espacio de 1000 dimensiones.
Figura 8­1. Punto, segmento, cuadrado, cubo y teseracto (hipercubos de 0D a 4D)2
Resulta que muchas cosas se comportan de manera muy diferente en el espacio de alta dimensión.
Por ejemplo, si eliges un punto al azar en un cuadrado unitario (un cuadrado de 1 × 1), tendrá
solo una probabilidad de alrededor del 0,4 % de estar ubicado a menos de 1000 pies de la superficie.
Machine Translated by Google
0,001 desde un borde (en otras palabras, es muy poco probable que un punto aleatorio sea
“extremo” a lo largo de cualquier dimensión). Pero en un hipercubo unitario de 10 000
dimensiones, esta probabilidad es mayor que el 99,999999 %. La mayoría de los puntos
3
en un hipercubo de alta dimensión están muy cerca del borde.
Aquí hay una diferencia más problemática: si eliges dos puntos al azar en un cuadrado
unitario, la distancia entre estos dos puntos será, en promedio, aproximadamente 0,52.
Si eliges dos puntos al azar en un cubo unitario 3D, la distancia promedio será
aproximadamente 0,66. Pero ¿qué pasa con dos puntos elegidos al azar en un hipercubo
de 1.000.000 de dimensiones? La distancia promedio, lo creas o no, será de aproximadamente
408,25 (aproximadamente √1.000.000/6). Esto es contraintuitivo: ¿cómo
pueden estar tan separados dos puntos cuando ambos se encuentran dentro del mismo
hipercubo unitario? Bueno, hay mucho espacio en las dimensiones altas. Como resultado,
los conjuntos de datos de alta dimensión corren el riesgo de ser muy dispersos: es
probable que la mayoría de las instancias de entrenamiento estén muy alejadas entre sí.
Esto también significa que es probable que una nueva instancia esté muy alejada de cualquier
instancia de entrenamiento, lo que hace que las predicciones sean mucho menos
confiables que en dimensiones inferiores, ya que se basarán en extrapolaciones mucho
más grandes. En resumen, cuantas más dimensiones tenga el conjunto de
entrenamiento, mayor será el riesgo de sobreajustarlo.
En teoría, una solución a la maldición de la dimensionalidad podría ser aumentar el tamaño
del conjunto de entrenamiento para alcanzar una densidad suficiente de instancias
de entrenamiento. Desafortunadamente, en la práctica, la cantidad de instancias de
entrenamiento necesarias para alcanzar una densidad dada crece exponencialmente con
la cantidad de dimensiones. Con solo 100 características (significativamente menos que en
el problema MNIST), se necesitarían más instancias de entrenamiento que átomos
en el universo observable para que las instancias de entrenamiento estuvieran dentro de 0,1
entre sí en promedio, suponiendo que estuvieran distribuidas uniformemente en todas
las dimensiones.
Principales enfoques para la dimensionalidad
Reducción
Machine Translated by Google
Antes de profundizar en algoritmos específicos de reducción de dimensionalidad, echemos
un vistazo a los dos enfoques principales para reducir la dimensionalidad: proyección y
aprendizaje múltiple.
Proyección En la
mayoría de los problemas del mundo real, las instancias de entrenamiento no se
distribuyen uniformemente en todas las dimensiones. Muchas características son casi constantes,
mientras que otras están altamente correlacionadas (como se discutió anteriormente para
MNIST). Como resultado, todas las instancias de entrenamiento se encuentran dentro (o
cerca de) un subespacio de dimensión mucho menor del espacio de dimensión alta. Esto suena
muy abstracto, así que veamos un ejemplo. En la Figura 8­2 puede ver un conjunto de datos 3D
representado por círculos.
Figura 8­2. Un conjunto de datos 3D que se encuentra cerca de un subespacio 2D
Tenga en cuenta que todas las instancias de entrenamiento se encuentran cerca de un
plano: este es un subespacio de menor dimensión (2D) del espacio de alta dimensión (3D).
Si proyectamos cada instancia de entrenamiento perpendicularmente sobre este subespacio
(como se representa mediante las líneas cortas que conectan las instancias con el plano),
Machine Translated by Google
Obtenga el nuevo conjunto de datos 2D que se muestra en la Figura 8­3. ¡Listo! Acabamos
de reducir la dimensionalidad del conjunto de datos de 3D a 2D. Observe que los ejes
corresponden a las nuevas
características
z y z (las coordenadas de las proyecciones en el plano).
1
2
Figura 8­3. El nuevo conjunto de datos 2D después de la proyección
Sin embargo, la proyección no siempre es la mejor estrategia para reducir la
dimensionalidad. En muchos casos, el subespacio puede torcerse y girar, como en el
famoso conjunto de datos del juguete de rollo suizo representado en la Figura 8­4.
Machine Translated by Google
Figura 8­4. Conjunto de datos del rollo suizo
Con solo proyectar sobre un plano (por ejemplo, al colocar x3 ), se aplastarían
las diferentes capas del rollo suizo, como se muestra en el lado izquierdo de la
Figura 8­5. Lo que realmente se desea es desenrollar el rollo suizo para obtener el
conjunto de datos 2D del lado derecho de la Figura 8­5.
Figura 8­5. Aplastamiento mediante proyección sobre un plano (izquierda) versus desenrollado del rollo suizo (derecha)
Machine Translated by Google
Aprendizaje de variedades El
rollo suizo es un ejemplo de variedad 2D. En pocas palabras, una variedad 2D es una forma
2D que se puede doblar y torcer en un espacio de dimensiones superiores.
En términos más generales, una variedad de dimensión d es una parte de un espacio de
dimensión n (donde d < n) que se asemeja localmente a un hiperplano de dimensión d. En el
caso del rollo suizo, d = 2 y n = 3: se asemeja localmente a un plano 2D, pero gira en la tercera
dimensión.
Muchos algoritmos de reducción de dimensionalidad funcionan modelando la
variedad en la que se encuentran las instancias de entrenamiento; esto se denomina
aprendizaje de variedades. Se basa en el supuesto de variedad, también llamado hipótesis
de variedad, que sostiene que la mayoría de los conjuntos de datos de alta dimensión del mundo
real se encuentran cerca de una variedad de dimensión mucho menor. Este supuesto se
observa empíricamente con mucha frecuencia.
Una vez más, piense en el conjunto de datos MNIST: todas las imágenes de dígitos escritos
a mano tienen algunas similitudes. Están hechas de líneas conectadas, los bordes son blancos
y están más o menos centradas. Si generara imágenes aleatoriamente, solo una fracción
ridículamente pequeña de ellas se vería como dígitos escritos a mano. En otras
palabras, los grados de libertad disponibles para usted si intenta crear una imagen de dígito son
drásticamente menores que los grados de libertad que tendría si se le permitiera generar
cualquier imagen que quisiera. Estas restricciones tienden a comprimir el conjunto de datos en
una variedad de dimensiones inferiores.
El supuesto de variedad suele ir acompañado de otro supuesto implícito: que la tarea
en cuestión (por ejemplo, clasificación o regresión) será más sencilla si se expresa en el espacio
de menor dimensión de la variedad. Por ejemplo, en la fila superior de la Figura 8­6, el rollo
suizo se divide en dos clases: en el espacio 3D (a la izquierda), el límite de decisión
sería bastante complejo, pero en el espacio de variedad desenrollado 2D (a la derecha),
el límite de decisión es una línea recta.
Sin embargo, esta suposición implícita no siempre se cumple. Por ejemplo, en la fila inferior
de la Figura 8­6, el límite de decisión se encuentra en x = 5.
Este límite de decisión parece muy simple en el espacio 3D original (a
1
Machine Translated by Google
plano vertical), pero parece más complejo en la variedad desenrollada (una colección de
cuatro segmentos de línea independientes).
En resumen, reducir la dimensionalidad de su conjunto de entrenamiento antes de entrenar un
modelo generalmente acelerará el entrenamiento, pero puede que no siempre conduzca a una
solución mejor o más simple; todo depende del conjunto de datos.
Con suerte, ahora tendrá una buena idea de qué es la maldición de la dimensionalidad y cómo
los algoritmos de reducción de la dimensionalidad pueden combatirla, especialmente
cuando se cumple el supuesto de variedad. El resto de este capítulo analizará algunos
de los algoritmos más populares.
Figura 8­6. El límite de decisión puede no ser siempre más simple con dimensiones más bajas
PCA
El análisis de componentes principales (PCA) es, con diferencia, el algoritmo de
reducción de dimensionalidad más popular. En primer lugar, identifica el hiperplano que
Machine Translated by Google
se encuentra más cerca de los datos y luego proyecta los datos sobre él, tal como en
la Figura 8­2.
Conservación de la varianza Antes de
poder proyectar el conjunto de entrenamiento en un hiperplano de menor
dimensión, primero debe elegir el hiperplano correcto. Por ejemplo, un conjunto de datos
2D simple se representa a la izquierda en la Figura 8­7, junto con tres ejes diferentes (es
decir, hiperplanos 1D). A la derecha está el resultado de la proyección del conjunto
de datos en cada uno de estos ejes. Como puede ver, la proyección en la línea
continua conserva la varianza máxima, mientras que la proyección en la línea de puntos
conserva muy poca varianza y la proyección en la línea discontinua conserva una
cantidad intermedia de varianza.
Figura 8­7. Selección del subespacio sobre el que se proyectará
Parece razonable seleccionar el eje que preserva la máxima cantidad de varianza, ya que
es muy probable que pierda menos información que las otras proyecciones. Otra
forma de justificar esta elección es que es el eje que minimiza la distancia cuadrática
media entre el conjunto de datos original y su proyección sobre ese eje. Esta es la idea
bastante simple detrás del PCA.
Componentes principales
4
Machine Translated by Google
El análisis de componentes principales (PCA) identifica el eje que representa la mayor cantidad
de varianza en el conjunto de entrenamiento. En la Figura 8­7, es la línea continua.
También encuentra un segundo eje, ortogonal al primero, que representa la mayor cantidad
de varianza restante. En este ejemplo 2D no hay elección: es la línea de puntos. Si se tratara
de un conjunto de datos de más dimensiones, el análisis de componentes principales también
encontraría un tercer eje, ortogonal a ambos ejes anteriores, y un cuarto, un quinto, y así
sucesivamente: tantos ejes como el número de dimensiones del conjunto de datos.
El
El
El eje i se denomina componente principal i (PC) de los datos. En la Figura
8­7, el primer PC es el eje en el que se encuentra el vector1 c , y el segundo PC
2
es el eje en el que se encuentra el vector
c . En la Figura 8­2, los dos primeros
PC son los ejes ortogonales en los que se encuentran las dos flechas, en el
plano, y el tercer PC es el eje ortogonal a ese plano.
NOTA
Para cada componente principal, el PCA encuentra un vector unitario centrado en cero que apunta en la
dirección del componente principal. Dado que dos vectores unitarios opuestos se encuentran en el
mismo eje, la dirección de los vectores unitarios devueltos por el PCA no es estable: si perturba ligeramente
el conjunto de entrenamiento y ejecuta el PCA nuevamente, los vectores unitarios pueden apuntar en la
dirección opuesta a los vectores originales. Sin embargo, generalmente seguirán estando en los mismos
ejes. En algunos casos, un par de vectores unitarios puede incluso rotar o intercambiarse (si las
varianzas a lo largo de estos dos ejes son cercanas), pero el plano que definen generalmente seguirá siendo el mismo.
Entonces, ¿cómo se pueden encontrar los componentes principales de un conjunto de
entrenamiento? Afortunadamente, existe una técnica de factorización matricial estándar
llamada Descomposición en Valores Singulares (SVD) que puede descomponer la matriz X
del conjunto de entrenamiento en la multiplicación matricial de
, donde V contiene el
tres matrices U Σ V vectores unitarios que definen todos los componentes principales que
estamos buscando, como se muestra en la Ecuación 8­1.
Ecuación 8­1. Matriz de componentes principales
V=
c1c2
cn
Machine Translated by Google
El siguiente código Python utiliza la función svd() de NumPy para obtener todos los
componentes principales del conjunto de entrenamiento y luego extrae los dos vectores
unitarios que definen los dos primeros PC:
X_centrado = X ­ X.media(eje=0)
U, s, Vt = np.linalg.svd(X_centrado) c1 = Vt.T[:, 0] c2 =
Vt.T[:, 1]
ADVERTENCIA
El PCA supone que el conjunto de datos está centrado en torno al origen. Como veremos, las clases
PCA de Scikit­Learn se encargan de centrar los datos por usted. Si implementa el PCA usted mismo
(como en el ejemplo anterior) o si utiliza otras bibliotecas, no olvide centrar los datos primero.
Proyección hacia abajo hasta las dimensiones d Una
vez que haya identificado todos los componentes principales, puede reducir la dimensionalidad
del conjunto de datos hasta las dimensiones d al proyectarlo sobre el hiperplano definido
por los primeros d componentes principales. Al seleccionar este hiperplano, se garantiza que
la proyección conservará la mayor cantidad de varianza posible. Por ejemplo, en la Figura
8­2, el conjunto de datos 3D se proyecta hacia abajo hasta el plano 2D definido por los dos
primeros componentes principales, lo que conserva una gran parte de la varianza del
conjunto de datos. Como resultado, la proyección 2D se parece mucho al conjunto de
datos 3D original.
Para proyectar el conjunto de entrenamiento sobre el hiperplano y obtener un conjunto de datos
reducido
d­proyecto X de dimensionalidad d, calcule la multiplicación matricial de la matriz X del
conjunto de entrenamiento por la matriz W de
d , definida como la matriz que contiene
las primeras d columnas de V, como se muestra en la Ecuación 8­2.
Ecuación 8­2. Proyección del conjunto de entrenamiento hasta las dimensiones d
Xd­proyección = XWd
Machine Translated by Google
El siguiente código Python proyecta el conjunto de entrenamiento en el plano definido
por los dos primeros componentes principales:
W2 = Vt.T[:, :2]
X2D = X_centrado.punto(W2)
¡Y ya lo tienes! Ahora sabes cómo reducir la dimensionalidad de cualquier conjunto de
datos a cualquier número de dimensiones, preservando al mismo tiempo la mayor
varianza posible.
Uso de Scikit­Learn
La clase PCA de Scikit­Learn utiliza la descomposición SVD para implementar PCA, tal
como lo hicimos anteriormente en este capítulo. El siguiente código aplica PCA
para reducir la dimensionalidad del conjunto de datos a dos dimensiones (tenga en
cuenta que se encarga automáticamente de centrar los datos):
Desde sklearn.decomposition importar PCA
pca = PCA(n_componentes = 2)
X2D = pca.fit_transform(X)
Después de ajustar el transformador PCA al conjunto de datos, su atributo Components_
contiene la transposición de W
d (por ejemplo, el vector unitario que define el
primer componente principal es igual a pca.components_.T[:, 0]).
Razón de varianza explicada Otro dato
útil es la razón de varianza explicada de cada componente principal, disponible a
través de la variable explained_variance_ratio_. La razón indica la proporción de la
varianza del conjunto de datos que se encuentra a lo largo de cada componente
principal. Por ejemplo, observemos las razones de varianza explicadas de los
dos primeros componentes del conjunto de datos 3D representado en la Figura 8­2:
Machine Translated by Google
>>> pca.explained_variance_ratio_array
([0,84248607, 0,14631839])
Este resultado indica que el 84,2 % de la varianza del conjunto de datos se encuentra
en el primer PC y el 14,6 % en el segundo PC. Esto deja menos del 1,2 % para el tercer
PC, por lo que es razonable suponer que el tercer PC probablemente contenga poca
información.
Elección del número correcto de dimensiones En lugar de elegir
arbitrariamente el número de dimensiones a las que se reducirá, es más sencillo elegir
el número de dimensiones que sumen una porción suficientemente grande de la
varianza (por ejemplo, 95 %). A menos, por supuesto, que esté reduciendo la
dimensionalidad para la visualización de datos; en ese caso, querrá reducir la
dimensionalidad a 2 o 3.
El siguiente código realiza PCA sin reducir la dimensionalidad y luego calcula la cantidad
mínima de dimensiones necesarias para preservar el 95 % de la varianza del conjunto
de entrenamiento:
pca = PCA()
pca.fit(X_train)
cumsum = np.cumsum(pca.cociente_de_varianza_explicado) d
= np.argmax(cumsum >= 0,95) + 1
Luego, puede configurar n_components=d y ejecutar PCA nuevamente. Pero hay
una opción mucho mejor: en lugar de especificar la cantidad de componentes
principales que desea conservar, puede configurar n_components como un valor flotante
entre 0,0 y 1,0, que indique la proporción de varianza que desea conservar:
pca = PCA(n_componentes=0,95)
X_reducido = pca.fit_transform(X_train)
Otra opción es representar gráficamente la varianza explicada como una función del
número de dimensiones (simplemente graficar la suma acumulada; ver Figura 8­8).
Generalmente habrá un codo en la curva, donde termina la varianza explicada.
Machine Translated by Google
Creciendo rápidamente. En este caso, se puede ver que reducir la dimensionalidad a
aproximadamente 100 dimensiones no perdería demasiada varianza explicada.
Figura 8­8. Varianza explicada en función del número de dimensiones
PCA para compresión Después de la
reducción de dimensionalidad, el conjunto de entrenamiento ocupa mucho menos espacio.
Como ejemplo, intente aplicar el análisis de componentes principales (PCA) al conjunto de datos
MNIST mientras conserva el 95 % de su varianza. Debería descubrir que cada instancia tendrá un poco
más de 150 características, en lugar de las 784 características originales. Por lo tanto, si bien se
conserva la mayor parte de la varianza, el conjunto de datos ahora tiene menos del 20 % de su tamaño original.
Esta es una relación de compresión razonable, y se puede ver cómo esta reducción de tamaño
puede acelerar enormemente un algoritmo de clasificación (como un clasificador SVM).
También es posible descomprimir el conjunto de datos reducido a 784 dimensiones
aplicando la transformación inversa de la proyección PCA.
Esto no le devolverá los datos originales, ya que la proyección perdió un poco de información (dentro
de la variación del 5 % que se eliminó), pero sí
Machine Translated by Google
Es probable que se acerque a los datos originales. La distancia cuadrática media entre los datos
originales y los datos reconstruidos (comprimidos y luego descomprimidos) se
denomina error de reconstrucción.
El siguiente código comprime el conjunto de datos MNIST a 154 dimensiones
y luego utiliza el método inverse_transform() para descomprimirlo nuevamente a 784
dimensiones:
pca = PCA(n_componentes = 154)
X_reducido = pca.fit_transform(X_train)
X_recuperado = pca.transformación_inversa(X_reducido)
La figura 8­9 muestra algunos dígitos del conjunto de entrenamiento original (a la izquierda)
y los dígitos correspondientes después de la compresión y la descompresión. Se puede ver
que hay una ligera pérdida de calidad de la imagen, pero los dígitos siguen estando
prácticamente intactos.
Figura 8­9. Compresión MNIST que conserva el 95% de la varianza
La ecuación de la transformación inversa se muestra en la Ecuación 8­3.
Ecuación 8­3. Transformación inversa de PCA, de regreso al número original de dimensiones
Xrecuperado = Xd­projWd
Machine Translated by Google
PCA aleatorizado
Si establece el hiperparámetro svd_solver en "aleatorio", Scikit­Learn utiliza un algoritmo
estocástico llamado PCA aleatorio que encuentra rápidamente una aproximación de los
primeros d componentes principales. Su complejidad computacional es O(m × d )
2
3
2
3
+ O(d ), en lugar de O(m × n ) + O(n ) para el enfoque SVD completo, por lo que es
drásticamente más rápido que el SVD completo cuando d es mucho menor que n:
rnd_pca = PCA(n_componentes=154, svd_solver="aleatorio")
X_reducido = rnd_pca.fit_transform(X_train)
De forma predeterminada, svd_solver está configurado en "auto": Scikit­
Learn utiliza automáticamente el algoritmo PCA aleatorio si m o n es mayor que 500 y d
es menor que el 80% de m o n, o bien utiliza el enfoque SVD completo.
Si desea forzar a Scikit­Learn a utilizar SVD completo, puede establecer el
hiperparámetro svd_solver en "completo".
PCA incremental
Un problema con las implementaciones anteriores de PCA es que requieren que
todo el conjunto de entrenamiento quepa en la memoria para que el algoritmo se ejecute.
Afortunadamente, se han desarrollado algoritmos de PCA incremental (IPCA).
Permiten dividir el conjunto de entrenamiento en minilotes y alimentar un algoritmo de
IPCA con un minilote a la vez. Esto es útil para conjuntos de entrenamiento grandes y
para aplicar PCA en línea (es decir, sobre la marcha, a medida que llegan nuevas
instancias).
El siguiente código divide el conjunto de datos MNIST en 100 minilotes (utilizando la
función array_split() de NumPy) y los envía a la clase IncrementalPCA de Scikit­
5
Learn. para reducir la dimensionalidad del conjunto de datos MNIST a 154
dimensiones (como antes). Tenga en cuenta que debe llamar al método partial_fit() con
cada minilote, en lugar del método fit() con todo el conjunto de entrenamiento:
Machine Translated by Google
Desde sklearn.decomposition importa IncrementalPCA
n_lotes = 100
inc_pca = IncrementalPCA(n_componentes=154) para
X_lote en np.array_split(X_train, n_lotes): inc_pca.partial_fit(X_lote)
X_reducido = inc_pca.transform(X_train)
Como alternativa, puede utilizar la clase memmap de NumPy, que le permite manipular
una matriz grande almacenada en un archivo binario en el disco como si estuviera
completamente en la memoria; la clase carga solo los datos que necesita en la memoria,
cuando los necesita. Dado que la clase IncrementalPCA usa solo una pequeña parte de la
matriz en un momento dado, el uso de la memoria permanece bajo control. Esto hace
posible llamar al método fit() habitual, como puede ver en el siguiente código:
X_mm = np.memmap(nombrearchivo, dtype="float32", mode="readonly", shape=(m, n))
tamaño_lote = m // n_lotes inc_pca
= IncrementalPCA(n_componentes=154, tamaño_lote=tamaño_lote) inc_pca.fit(X_mm)
PCA del núcleo
En el Capítulo 5 analizamos el truco del núcleo, una técnica matemática que asigna de
manera implícita instancias a un espacio de dimensiones muy altas (llamado espacio de
características), lo que permite la clasificación y regresión no lineal con máquinas de
vectores de soporte. Recuerde que un límite de decisión lineal en el espacio de
características de dimensiones altas corresponde a un límite de decisión no lineal
complejo en el espacio original.
Resulta que el mismo truco se puede aplicar al PCA, lo que permite realizar proyecciones
no lineales complejas para reducir la dimensionalidad.
6
Esto se llama Kernel PCA (kPCA). A menudo es útil para preservar grupos de instancias
después de la proyección o, a veces, incluso para desenrollar conjuntos de datos que se
encuentran cerca de una variedad retorcida.
Machine Translated by Google
El siguiente código utiliza la clase KernelPCA de Scikit­Learn para realizar kPCA con
un kernel RBF (consulte el Capítulo 5 para obtener más detalles sobre el kernel RBF
y otros kernels):
Desde sklearn.decomposition importa KernelPCA
rbf_pca = KernelPCA(n_componentes = 2, kernel="rbf", gamma=0.04)
X_reducido = rbf_pca.fit_transform(X)
La figura 8­10 muestra el rollo suizo, reducido a dos dimensiones utilizando un kernel
lineal (equivalente a simplemente utilizar la clase PCA), un kernel RBF y un kernel
sigmoide.
Figura 8­10. Rollo suizo reducido a 2D utilizando kPCA con varios núcleos
Selección de un kernel y ajuste de hiperparámetros Como kPCA es un
algoritmo de aprendizaje no supervisado, no hay una medida de rendimiento
obvia que lo ayude a seleccionar los mejores valores de kernel e
hiperparámetros. Dicho esto, la reducción de la dimensionalidad suele ser un
paso de preparación para una tarea de aprendizaje supervisado (por ejemplo,
clasificación), por lo que puede usar la búsqueda en cuadrícula para seleccionar el
kernel y los hiperparámetros que conducen al mejor rendimiento en esa tarea. El
siguiente código crea una secuencia de dos pasos, primero reduciendo la
dimensionalidad a dos dimensiones usando kPCA, luego aplicando regresión
logística para la clasificación. Luego usa GridSearchCV para encontrar el mejor valor
de kernel y gamma para kPCA con el fin de obtener la mejor precisión de clasificación al final de la secu
Machine Translated by Google
desde sklearn.model_selection importa GridSearchCV desde
sklearn.linear_model importa LogisticRegression desde
sklearn.pipeline importa Pipeline
clf = Pipeline([ ("kpca",
KernelPCA(n_componentes=2)), ("log_reg",
Regresión logística())
])
param_grid =
[{ "kpca__gamma": np.linspace(0.03, 0.05, 10),
"kpca__kernel": ["rbf", "sigmoide"]
}]
búsqueda_cuadrícula = Búsqueda_cuadrículaCV(clf, parámetro_cuadrícula,
cv=3) búsqueda_cuadrícula.fit(X, y)
El mejor kernel y los hiperparámetros están entonces disponibles a través de la
variable best_params_:
>>> print(grid_search.best_params_)
{'kpca__gamma': 0.043333333333333335, 'kpca__kernel': 'rbf'}
Otro enfoque, esta vez completamente sin supervisión, es seleccionar el kernel y los
hiperparámetros que produzcan el menor error de reconstrucción. Tenga en cuenta que la
reconstrucción no es tan fácil como con el PCA lineal. He aquí por qué. La Figura 8­11
muestra el conjunto de datos 3D de rollo suizo original (arriba a la izquierda) y el conjunto
de datos 2D resultante después de aplicar kPCA utilizando un kernel RBF (arriba a la
derecha). Gracias al truco del kernel, esta transformación es matemáticamente equivalente a
utilizar el mapa de características φ para mapear el conjunto de entrenamiento a un
espacio de características de dimensión infinita (abajo a la derecha) y luego proyectar el
conjunto de entrenamiento transformado a 2D utilizando PCA lineal.
Tenga en cuenta que si pudiéramos invertir el paso de PCA lineal para una instancia dada
en el espacio reducido, el punto reconstruido se encontraría en el espacio de características,
no en el espacio original (por ejemplo, como el representado por una X en el
diagrama). Dado que el espacio de características es de dimensión infinita, no podemos
calcular el punto reconstruido y, por lo tanto, no podemos calcular el verdadero error de
reconstrucción. Afortunadamente, es posible encontrar un punto en el espacio original
que se corresponda con el punto reconstruido. Este punto
Machine Translated by Google
se denomina preimagen de reconstrucción. Una vez que tenga esta preimagen, puede
medir su distancia al cuadrado con respecto a la instancia original. Luego, puede
seleccionar el núcleo y los hiperparámetros que minimicen este error de preimagen
de reconstrucción.
Figura 8­11. PCA del núcleo y error de reconstrucción de la imagen previa
Quizás se esté preguntando cómo realizar esta reconstrucción. Una solución es
entrenar un modelo de regresión supervisada, con las instancias proyectadas como
conjunto de entrenamiento y las instancias originales como objetivos. Scikit­Learn lo
hará automáticamente si establece fit_inverse_transform=True, como se muestra en
el siguiente código:
7
rbf_pca = KernelPCA(n_componentes = 2, kernel="rbf", gamma=0.0433,
transformación_inversa_ajuste=Verdadero)
X_reducido = rbf_pca.fit_transform(X)
X_preimagen = rbf_pca.transformación_inversa(X_reducida)
Machine Translated by Google
NOTA
De forma predeterminada, fit_inverse_transform=False y KernelPCA no
tiene el método inverse_transform(). Este método solo se crea cuando se
establece fit_inverse_transform=True.
A continuación, puede calcular el error de reconstrucción de la imagen previa:
>>> de sklearn.metrics importar error_cuadrado_medio >>> error_cuadrado_medio(X,
X_preimage) 32.786308795766132
Ahora puede utilizar la búsqueda de cuadrícula con validación cruzada para encontrar el kernel y
los hiperparámetros que minimizan este error.
LLE
8
Incrustación lineal local (LLE) es otra técnica poderosa de reducción de dimensionalidad
no lineal (NLDR). Es una técnica de aprendizaje de variedades que no se basa en proyecciones,
como lo hacen los algoritmos anteriores. En pocas palabras, LLE funciona midiendo primero
cómo cada instancia de entrenamiento se relaciona linealmente con sus vecinos más cercanos (cn),
y luego buscando una representación de baja dimensión del conjunto de entrenamiento donde
estas relaciones locales se conservan mejor (más detalles en breve). Este enfoque
lo hace particularmente bueno para desenrollar variedades retorcidas, especialmente cuando
no hay demasiado ruido.
El siguiente código utiliza la clase LocallyLinearEmbedding de Scikit­Learn para desenrollar el rollo
suizo:
desde sklearn.manifold importar LocallyLinearEmbedding
lle = Incrustación lineal local (n_componentes=2, n_vecinos=10)
X_reducido = lle.fit_transform(X)
Machine Translated by Google
El conjunto de datos 2D resultante se muestra en la Figura 8­12. Como puede ver,
el rollo suizo está completamente desenrollado y las distancias entre las instancias se
conservan bien a nivel local. Sin embargo, las distancias no se conservan a mayor
escala: la parte izquierda del rollo suizo desenrollado se estira, mientras que la parte
derecha se comprime. No obstante, LLE hizo un buen trabajo al modelar la variedad.
Figura 8­12. Rollo suizo desenrollado con LLE
Así es como funciona LLE: para cada instancia de entrenamiento x
(i)
, El algoritmo
identifica sus k vecinos más cercanos (en el código anterior k = 10), luego intenta (i) reconstruir x
como una función lineal de estos vecinos. Más específicamente, encuentra los pesos
w tales que la distancia al cuadrado entre (i) (j)yo,jsea lo más pequeña posible, asumiendo w = 0 si x
(j)
metro
yo,j el primer paso de
no es el
x yproblema
∑ j=1 wi,jx
LLE
de(i) uno de los k vecinos más cercanos de x . Por lo tanto,
optimización restringida
descrito en la Ecuación 8­4, donde W es la matriz de pesos que contiene todos los pesos w .
La segunda restricción i,j (i) simplemente normaliza los pesos para cada instancia de entrenamiento
x
.
Ecuación 8­4. Paso uno de LLE: modelado lineal de relaciones locales
Machine Translated by Google
metro
Wˆ = argmin∑
Yo
2
metro
∑
i=1 (x (i) −
j=1 con,jx (j))
(j) no es uno de los k cn de x (i) si x
metro
sujeto a { wi,j = 0
∑ j=1 wi,j = 1 para i = 1, 2, , m
Después de este paso, la matriz de pesos Wˆ (que contiene los pesos wˆi,j) codifica
las relaciones lineales locales entre las instancias de entrenamiento. El segundo
paso es mapear las instancias de entrenamiento en un espacio de dimensión d (donde
d < n) mientras se preservan estas relaciones locales tanto como sea posible. Si (i)z
es la imagen de x(i)en este espacio de dimensión d, entonces queremos que el
metro
(i) la distancia entre z y ∑ conduce j=1 cuadrado de wˆi,jz (j) sea lo más pequeño posible. Esta idea
al problema de optimización sin restricciones descrito en la ecuación 8­5 . Parece muy similar
al primer paso, pero en lugar de mantener las instancias fijas y encontrar los pesos
óptimos, estamos haciendo lo contrario: manteniendo los pesos fijos y encontrando la
posición óptima de las imágenes de las instancias en el espacio de baja dimensión.
Nótese que Z es la matriz (i) que contiene todos los valores z.
.
Ecuación 8­5. Paso dos de LLE: reducir la dimensionalidad mientras se preservan las relaciones
metro
Zˆ = argmin∑
O
i=1 (z (i) −
metro
2
∑
j=1 wˆi,jz (j))
La implementación de LLE de Scikit­Learn tiene la siguiente complejidad
computacional: O(m log(m)n log(k)) para encontrar los k vecinos más cercanos,
2
O(mnk )3 para optimizar los pesos y O(dm ) para construir
las representaciones de
2
baja dimensión. Desafortunadamente, la m en el último término
hace que este
algoritmo sea poco escalable para conjuntos de datos muy grandes.
Otras técnicas de reducción de dimensionalidad
Machine Translated by Google
Existen muchas otras técnicas de reducción de dimensionalidad, varias de las cuales
están disponibles en Scikit­Learn. A continuación se muestran algunas de las más populares.
unos:
Proyecciones aleatorias
Como sugiere su nombre, proyecta los datos a un espacio de menor dimensión
utilizando una proyección lineal aleatoria. Puede parecer una locura, pero resulta que
es muy probable que una proyección tan aleatoria conserve bien las distancias, como
demostró matemáticamente William B.
Johnson y Joram Lindenstrauss en un famoso lema. La calidad de la reducción de
dimensionalidad depende de la cantidad de instancias y de la dimensionalidad de
destino, pero sorprendentemente no de la dimensionalidad inicial. Consulte
la documentación del paquete sklearn.random_projection para
obtener más detalles.
Escalamiento multidimensional (MDS)
Reduce la dimensionalidad mientras intenta preservar las distancias entre las instancias.
Isomap
Crea un gráfico conectando cada instancia a sus vecinos más cercanos, luego reduce la dimensionalidad
mientras intenta preservar las 9 distancias geodésicas entre las instancias.
Integración de vecinos estocásticos distribuidos en t (t­SNE)
Reduce la dimensionalidad mientras intenta mantener las instancias similares cerca y
las instancias diferentes separadas. Se utiliza principalmente para visualización, en
particular para visualizar grupos de instancias en un espacio de alta dimensión (por
ejemplo, para visualizar las imágenes MNIST en 2D).
Análisis discriminante lineal (LDA)
Es un algoritmo de clasificación, pero durante el entrenamiento aprende los ejes
más discriminantes entre las clases, y estos ejes pueden luego usarse para definir
un hiperplano sobre el cual proyectar los datos. El beneficio
Machine Translated by Google
La ventaja de este enfoque es que la proyección mantendrá las clases lo más separadas posible, por lo
que LDA es una buena técnica para reducir la dimensionalidad antes de ejecutar otro algoritmo de
clasificación, como un clasificador SVM.
La figura 8­13 muestra los resultados de algunas de estas técnicas.
Figura 8­13. Uso de diversas técnicas para reducir el rollo de Swill a 2D
Ceremonias
1. ¿Cuáles son las principales motivaciones para reducir la dimensionalidad de un
conjunto de datos? ¿Cuáles son los principales inconvenientes?
2. ¿Qué es la maldición de la dimensionalidad?
3. Una vez que se ha reducido la dimensionalidad de un conjunto de datos, ¿es posible?
¿Revertir la operación? Si es así, ¿cómo? Si no, ¿por qué?
4. ¿Se puede utilizar PCA para reducir la dimensionalidad de un conjunto de datos
altamente no lineal?
5. Supongamos que realiza un análisis de componentes principales (PCA) en un conjunto de datos de
1000 dimensiones y establece la relación de varianza explicada en 95 %. ¿Cuántas dimensiones
tendrá el conjunto de datos resultante?
6. ¿En qué casos utilizarías PCA vainilla, PCA incremental,
¿PCA aleatorio o PCA de núcleo?
Machine Translated by Google
7. ¿Cómo puedes evaluar el rendimiento de un algoritmo de reducción de
dimensionalidad en tu conjunto de datos?
8. ¿Tiene sentido encadenar dos dimensiones diferentes?
¿Algoritmos de reducción?
9. Cargue el conjunto de datos MNIST (presentado en el Capítulo 3) y divídalo en un
conjunto de entrenamiento y un conjunto de prueba (tome las primeras 60 000
instancias para el entrenamiento y las 10 000 restantes para las pruebas). Entrene
un clasificador de Random Forest en el conjunto de datos y mida el tiempo que
tarda, luego evalúe el modelo resultante en el conjunto de prueba. A continuación,
utilice PCA para reducir la dimensionalidad del conjunto de datos, con una relación
de varianza explicada del 95 %. Entrene un nuevo clasificador de Random
Forest en el conjunto de datos reducido y vea cuánto tiempo tarda. ¿El
entrenamiento fue mucho más rápido? A continuación, evalúe el clasificador en el
conjunto de prueba. ¿Cómo se compara con el clasificador anterior?
10. Utilice t­SNE para reducir el conjunto de datos MNIST a dos dimensiones y grafique el
resultado con Matplotlib. Puede utilizar un diagrama de dispersión con 10 colores
diferentes para representar la clase de destino de cada imagen.
Como alternativa, puede reemplazar cada punto en el diagrama de dispersión con la
clase de instancia correspondiente (un dígito del 0 al 9), o incluso trazar versiones
reducidas de las imágenes de los dígitos (si traza todos los dígitos, la visualización
será demasiado desordenada, por lo que debe extraer una muestra aleatoria o
trazar una instancia solo si no se ha trazado ninguna otra instancia a una distancia
cercana). Debería obtener una buena visualización con grupos de dígitos bien
separados. Intente utilizar otros algoritmos de reducción de dimensionalidad como
PCA, LLE o MDS y compare las visualizaciones resultantes.
Las soluciones a estos ejercicios están disponibles en el Apéndice A.
1 Bueno, cuatro dimensiones si cuentas el tiempo, y unas cuantas más si eres un teórico de cuerdas.
2 Vea un teseracto giratorio proyectado en el espacio 3D en https://homl.info/30. Imagen del usuario
de Wikipedia NerdBoy1392 (Creative Commons BY­SA 3.0). Reproducido de https://
en.wikipedia.org/wiki/Tesseract.
Machine Translated by Google
3 Dato curioso: cualquiera que conozcas probablemente sea un extremista en al menos una dimensión (por ejemplo, ¿cómo?
tanta azúcar le ponen al café), si se consideran suficientes dimensiones.
4 Karl Pearson, “Sobre líneas y planos de ajuste más cercano a sistemas de puntos en el espacio”, The
Londres, Edimburgo y Dublín Philosophical Magazine and Journal of Science 2, no. 11 (1901): 559­572,
https://homl.info/pca.
5 Scikit­Learn utiliza el algoritmo descrito en David A. Ross et al., “Incremental Learning
para seguimiento visual robusto”, International Journal of Computer Vision 77, no. 1–3 (2008): 125–141.
6 Bernhard Schölkopf et al., “Análisis de componentes principales del núcleo”, en Lecture Notes in
Computer Science 1327 (Berlín: Springer, 1997): 583–588.
7 Si establece fit_inverse_transform=True, Scikit­Learn utilizará el algoritmo (basado en Kernel Ridge
Regression) descrito en Gokhan H. Bakır et al., “Learning to Find Pre­Images”, Proceedings of the
16th International Conference on Neural Information Processing Systems (2004): 449–456.
8 Sam T. Roweis y Lawrence K. Saul, “Reducción de dimensionalidad no lineal mediante
“Incrustación lineal”, Science 290, no. 5500 (2000): 2323–2326.
9 La distancia geodésica entre dos nodos en un gráfico es el número de nodos en el
camino más corto entre estos nodos.
Machine Translated by Google
Capítulo 9. Técnicas de
aprendizaje no supervisado
Aunque la mayoría de las aplicaciones actuales del aprendizaje automático se basan
en el aprendizaje supervisado (y, como resultado, es ahí donde se destinan la mayor
parte de las inversiones), la gran mayoría de los datos disponibles no están
etiquetados: tenemos las características de entrada X, pero no tenemos las
etiquetas y. El científico informático Yann LeCun dijo la famosa frase de que “si la
inteligencia fuera un pastel, el aprendizaje no supervisado sería el pastel, el
aprendizaje supervisado sería la guinda del pastel y el aprendizaje de refuerzo
sería la guinda del pastel”. En otras palabras, existe un enorme potencial en el
aprendizaje no supervisado al que apenas hemos empezado a hincarle el diente.
Digamos que desea crear un sistema que tome algunas fotografías de cada artículo
en una línea de producción de fabricación y detecte qué artículos están defectuosos.
Es bastante fácil crear un sistema que tome fotografías automáticamente, lo que
podría darte miles de fotografías cada día. Luego puedes construir un conjunto
de datos razonablemente grande en solo unas pocas semanas. Pero espera, ¡no hay
etiquetas! Si quieres entrenar un clasificador binario regular que prediga si un
artículo es defectuoso o no, necesitarás etiquetar cada una de las imágenes
como "defectuoso" o "normal". Esto generalmente requerirá que expertos
humanos se sienten y revisen manualmente todas las imágenes. Esta es una
tarea larga, costosa y tediosa, por lo que generalmente solo se realizará en un
pequeño subconjunto de las imágenes disponibles. Como resultado, el conjunto
de datos etiquetados será bastante pequeño y el rendimiento del clasificador será decepcionante.
Además, cada vez que la empresa realiza algún cambio en sus productos, todo el
proceso debe comenzar desde cero. ¿No sería fantástico si el algoritmo pudiera
aprovechar los datos sin etiquetar sin necesidad de que los humanos etiqueten cada
imagen? Es el momento de introducir el aprendizaje no supervisado.
En el Capítulo 8 analizamos la tarea de aprendizaje no supervisado más común: la
reducción de dimensionalidad. En este capítulo veremos algunas tareas más.
Machine Translated by Google
Tareas y algoritmos de aprendizaje no supervisado:
Agrupamiento
El objetivo es agrupar instancias similares en clústeres. La agrupación en clústeres es una
excelente herramienta para el análisis de datos, la segmentación de clientes, los sistemas de
recomendación, los motores de búsqueda, la segmentación de imágenes, el
aprendizaje semisupervisado, la reducción de dimensionalidad y más.
Detección de anomalías
El objetivo es aprender cómo se ven los datos “normales” y luego utilizar eso para detectar
instancias anormales, como artículos defectuosos en una línea de producción o una
nueva tendencia en una serie temporal.
Estimación de densidad
Esta es la tarea de estimar la función de densidad de probabilidad (PDF) del proceso aleatorio
que generó el conjunto de datos. La estimación de densidad se utiliza comúnmente para la
detección de anomalías: las instancias ubicadas en regiones de muy baja densidad tienen
más probabilidades de ser anomalías. También es útil para el análisis y la visualización
de datos.
¿Listo para comer un poco de pastel? Comenzaremos con la agrupación, utilizando K­Means y
DBSCAN, y luego analizaremos los modelos de mezcla gaussiana y veremos cómo se pueden
utilizar para la estimación de densidad, la agrupación y la detección de anomalías.
Agrupamiento
Mientras disfrutas de una caminata por las montañas, te encuentras con una planta que nunca
habías visto antes. Miras a tu alrededor y notas unas cuantas más. No son idénticas, pero son lo
suficientemente similares como para que sepas que, muy probablemente, pertenecen a la
misma especie (o al menos al mismo género). Es posible que necesites un botánico que te diga
de qué especie se trata, pero ciertamente no necesitas un experto para identificar grupos de objetos
de aspecto similar. Esto se llama agrupamiento: es la tarea de identificar instancias similares y
asignarlas a grupos o grupos de instancias similares.
Machine Translated by Google
Al igual que en la clasificación, cada instancia se asigna a un grupo. Sin embargo, a diferencia de la clasificación,
la agrupación es una tarea no supervisada. Considere la Figura 9­1: a la izquierda está el conjunto
de datos iris (presentado en el Capítulo 4), donde la especie de cada instancia (es decir, su clase) se
representa con un marcador diferente. Es un conjunto de datos etiquetado, para el cual los algoritmos
de clasificación como la regresión logística, las SVM o los clasificadores de bosque aleatorio son muy
adecuados.
A la derecha se encuentra el mismo conjunto de datos, pero sin las etiquetas, por lo que ya no se puede utilizar
un algoritmo de clasificación. Aquí es donde entran en juego los algoritmos de agrupamiento: muchos de ellos
pueden detectar fácilmente el grupo inferior izquierdo. También es bastante fácil de ver con nuestros
propios ojos, pero no es tan obvio que el grupo superior derecho esté compuesto por dos subgrupos distintos.
Dicho esto, el conjunto de datos tiene dos características adicionales (longitud y anchura de los sépalos), que
no se representan aquí, y los algoritmos de agrupamiento pueden hacer un buen uso de todas las
características, por lo que, de hecho, identifican los tres grupos bastante bien (por ejemplo, utilizando un
modelo de mezcla gaussiana, solo 5 instancias de 150 se asignan al grupo incorrecto).
Figura 9­1. Clasificación (izquierda) versus agrupamiento (derecha)
La agrupación en clústeres se utiliza en una amplia variedad de aplicaciones, incluidas las siguientes:
Para la segmentación de clientes Puedes
agrupar a tus clientes en función de sus compras y su actividad en tu sitio web. Esto es útil para
entender quiénes son tus clientes y qué necesitan, de modo que puedas adaptar tus productos
y campañas de marketing a cada segmento. Por ejemplo, la segmentación de clientes puede ser útil en
sistemas de recomendación para sugerir contenido que otros usuarios del mismo grupo
disfrutaron.
Machine Translated by Google
Para el análisis de datos
Al analizar un nuevo conjunto de datos, puede ser útil ejecutar un algoritmo de agrupamiento y luego
analizar cada grupo por separado.
Como técnica de reducción de dimensionalidad
Una vez que se ha agrupado un conjunto de datos, normalmente es posible medir la afinidad de cada
instancia con cada grupo (la afinidad es cualquier medida de lo bien que encaja una instancia en un
grupo). El vector de características x de cada instancia se puede reemplazar entonces con el vector de
afinidades de sus grupos. Si hay k grupos, entonces este vector es k­dimensional. Este vector suele tener
una dimensión mucho menor que el vector de características original, pero puede conservar
suficiente información para su posterior procesamiento.
Para la detección de anomalías (también llamada detección de valores atípicos)
Es probable que cualquier instancia que tenga una afinidad baja con todos los clústeres sea una anomalía.
Por ejemplo, si ha agrupado a los usuarios de su sitio web en función de su comportamiento, puede
detectar usuarios con un comportamiento inusual, como una cantidad inusual de solicitudes por
segundo. La detección de anomalías es particularmente útil para detectar defectos de fabricación o para
la detección de fraudes.
Para aprendizaje semisupervisado
Si solo tiene unas pocas etiquetas, puede realizar una agrupación y propagar las etiquetas a
todas las instancias del mismo clúster. Esta técnica puede aumentar en gran medida la cantidad
de etiquetas disponibles para un algoritmo de aprendizaje supervisado posterior y, por lo tanto,
mejorar su rendimiento.
Para motores de búsqueda
Algunos motores de búsqueda permiten buscar imágenes similares a una imagen de referencia. Para
crear un sistema de este tipo, primero se debe aplicar un algoritmo de agrupamiento a todas las
imágenes de la base de datos; las imágenes similares terminarían en el mismo grupo. Luego, cuando un
usuario proporciona una
Machine Translated by Google
imagen de referencia, todo lo que necesita hacer es usar el modelo de agrupamiento
entrenado para encontrar el grupo de esta imagen y luego puede simplemente devolver
todas las imágenes de este grupo.
Para segmentar una imagen
Al agrupar los píxeles según su color y luego reemplazar el color de cada píxel con el
color medio de su grupo, es posible reducir considerablemente la cantidad de
colores diferentes en la imagen. La segmentación de imágenes se utiliza en muchos sistemas
de detección y seguimiento de objetos, ya que facilita la detección del contorno de cada objeto.
No existe una definición universal de lo que es un clúster: depende del contexto y distintos
algoritmos capturarán distintos tipos de clústeres. Algunos algoritmos buscan instancias
centradas en un punto en particular, llamado centroide. Otros buscan regiones continuas de
instancias densamente compactadas: estos clústeres pueden adoptar cualquier forma.
Algunos algoritmos son jerárquicos y buscan clústeres de clústeres. Y la lista continúa.
En esta sección, analizaremos dos algoritmos de agrupamiento populares, K­Means y
DBSCAN, y exploraremos algunas de sus aplicaciones, como la reducción de dimensionalidad
no lineal, el aprendizaje semisupervisado y la detección de anomalías.
K­medias
Considere el conjunto de datos sin etiquetar representado en la Figura 9­2: puede ver claramente
cinco blobs de instancias. El algoritmo K­Means es un algoritmo simple capaz de agrupar este tipo
de conjunto de datos de manera muy rápida y eficiente, a menudo en solo unas pocas
iteraciones. Fue propuesto por Stuart Lloyd en Bell Labs en 1957 como una técnica para la
modulación por código de pulsos, pero recién se publicó fuera de la empresa en 1982.
1 el mismo algoritmo, por lo que a veces
En 1965, Edward W. Forgy había publicado prácticamente
se hace referencia a K­Means como Lloyd­Forgy.
Machine Translated by Google
Figura 9­2. Un conjunto de datos sin etiquetar compuesto por cinco blobs de instancias
Entrenemos un clusterizador K­Means en este conjunto de datos. Intentará encontrar
el centro de cada blob y asignará cada instancia al blob más cercano:
desde sklearn.cluster importar KMeans k = 5 kmeans =
KMeans(n_clusters=k) y_pred = kmeans.fit_predict(X)
Tenga en cuenta que debe especificar la cantidad de clústeres k que debe encontrar el
algoritmo. En este ejemplo, al observar los datos resulta bastante obvio que k debe
establecerse en 5, pero en general no es tan fácil. Hablaremos de esto en breve.
Cada instancia se asignó a uno de los cinco clústeres. En el contexto de la agrupación,
la etiqueta de una instancia es el índice del clúster al que el algoritmo asigna esta
instancia: esto no debe confundirse con las etiquetas de clase en la clasificación (recuerde
que la agrupación es una tarea de aprendizaje no supervisada). La instancia de
KMeans conserva una copia de las etiquetas de las instancias en las que se entrenó,
disponible a través de la variable de instancia labels_:
>>> y_pred
matriz([4, 0, 1, ..., 2, 1, 0], dtype=int32) >>> y_pred es kmeans.labels_
Verdadero
Machine Translated by Google
También podemos echar un vistazo a los cinco centroides que encontró el algoritmo:
>>> kmeans.cluster_centers_ matriz
([[­2,80389616, 1,80117999], [ 0,20876306,
2,25551336], [­2,79290307,
2,79641063], [­1,46679593,
2,28585348], [­2,80037642,
1,30082566]])
Puede asignar fácilmente nuevas instancias al clúster cuyo centroide esté más
cercano:
>>> X_nuevo = np.array([[0, 2], [3, 2], [­3, 3], [­3, 2.5]]) >>>
kmeans.predict(X_nuevo)
array([1, 1, 2, 2], dtype=int32)
Si trazas los límites de decisión del clúster, obtienes una teselación de Voronoi (ver Figura
9­3, donde cada centroide está representado con una X).
Figura 9­3. Límites de decisión de K­Means (teselación de Voronoi)
La gran mayoría de las instancias se asignaron claramente al grupo apropiado, pero es
probable que algunas instancias estuvieran mal etiquetadas (especialmente cerca del
límite entre el grupo superior izquierdo y el grupo central). De hecho, el algoritmo K­
Means no se comporta muy bien cuando los blobs tienen valores muy
Machine Translated by Google
diferentes diámetros porque lo único que le importa al asignar una instancia a un clúster
es la distancia al centroide.
En lugar de asignar cada instancia a un único clúster, lo que se denomina agrupamiento
rígido, puede resultar útil otorgar a cada instancia una puntuación por clúster, lo que se
denomina agrupamiento blando. La puntuación puede ser la distancia entre la
instancia y el centroide; a la inversa, puede ser una puntuación de similitud (o
afinidad), como la función de base radial gaussiana (introducida en el capítulo 5).
En la clase KMeans, el método transform() mide la distancia desde cada instancia hasta
cada centroide:
>>> kmeans.transform(X_new)
matriz([[2.81093633, 0.32995317, 2.9042344, 1.49439034, 2.88633901], [5.80730058,
2.80290755, 5.84739223, 4.4759332, 5.84236351], [1.21475352, 3.29399768,
0.29040966, 1.69136631, 1.71086031], [0.72581411, 3.21806371, 0.36159148,
1.54808703, 1.21567622]])
En este ejemplo, la primera instancia en X_new se encuentra a una distancia de 2,81 del
primer centroide, 0,33 del segundo centroide, 2,90 del tercer centroide, 1,49 del cuarto
centroide y 2,89 del quinto centroide. Si tiene un conjunto de datos de alta dimensión y lo
transforma de esta manera, obtendrá un conjunto de datos de k dimensiones: esta
transformación puede ser una técnica de reducción de dimensionalidad no lineal muy
eficiente.
El algoritmo K­Means Entonces,
¿cómo funciona el algoritmo? Bueno, supongamos que le proporcionaron los
centroides. Podría etiquetar fácilmente todas las instancias en el conjunto de
datos asignando cada una de ellas al grupo cuyo centroide esté más cercano.
Por el contrario, si se le proporcionaran todas las etiquetas de las instancias, podría
localizar fácilmente todos los centroides calculando la media de las instancias para cada
grupo. Pero no se le proporcionan ni las etiquetas ni los centroides, así que ¿cómo puede
proceder? Bueno, simplemente comience colocando los centroides aleatoriamente (por
ejemplo, eligiendo k instancias al azar y usando sus ubicaciones como centroides). Luego
etiquete las instancias, actualice los centroides, etiquete las instancias, actualice los
centroides, y así sucesivamente hasta que los centroides dejen de moverse. El algoritmo es
Machine Translated by Google
se garantiza que convergerá en un número finito de pasos (normalmente bastante pequeños); no oscilará para
siempre.
Puede ver el algoritmo en acción en la Figura 9­4: los centroides se inicializan
aleatoriamente (arriba a la izquierda), luego se etiquetan las instancias (arriba a la
derecha), luego se actualizan los centroides (centro a la izquierda), se vuelven a
etiquetar las instancias (centro a la derecha), y así sucesivamente. Como puede ver,
en solo tres iteraciones, el algoritmo ha alcanzado una agrupación que parece cercana a la óptima.
NOTA
La complejidad computacional del algoritmo es generalmente lineal con respecto al número de
instancias m, el número de clústeres k y el número de dimensiones n.
Sin embargo, esto solo es cierto cuando los datos tienen una estructura de agrupamiento. Si
no es así, en el peor de los casos, la complejidad puede aumentar exponencialmente con el
número de instancias. En la práctica, esto rara vez sucede y K­Means es, en general, uno de los
algoritmos de agrupamiento más rápidos.
Machine Translated by Google
Figura 9­4. Algoritmo K­Means
Aunque se garantiza que el algoritmo convergerá, puede que no converja a la
solución correcta (es decir, puede que converja a un óptimo local): que lo
haga o no depende de la inicialización del centroide. La figura 9­5 muestra
dos soluciones subóptimas a las que el algoritmo puede converger si no
tiene suerte con el paso de inicialización aleatoria.
Figura 9­5. Soluciones subóptimas debido a inicializaciones de centroides desafortunadas
Machine Translated by Google
Veamos algunas formas de mitigar este riesgo mejorando la inicialización del
centroide.
Métodos de inicialización del centroide
Si sabe aproximadamente dónde deberían estar los centroides (por ejemplo, si ejecutó
otro algoritmo de agrupamiento anteriormente), puede establecer el hiperparámetro init
en una matriz NumPy que contenga la lista de centroides y establecer n_init en 1:
buena_iniciación = np.array([[­3, 3], [­3, 2], [­3, 1], [­1, 2], [0, 2]]) kmeans = KMeans(n_clústeres=5,
init=buena_iniciación, n_iniciación=1)
Otra solución es ejecutar el algoritmo varias veces con distintas inicializaciones
aleatorias y quedarse con la mejor solución. La cantidad de inicializaciones aleatorias
está controlada por el hiperparámetro n_init: por defecto, es igual a 10, lo que significa
que todo el algoritmo descrito anteriormente se ejecuta 10 veces cuando se llama a fit()
y Scikit­Learn se queda con la mejor solución.
Pero, ¿cómo sabe exactamente cuál es la mejor solución? ¡Utiliza una métrica
de rendimiento! Esa métrica se llama inercia del modelo, que es la distancia cuadrática
media entre cada instancia y su centroide más cercano. Es aproximadamente igual a
223,3 para el modelo de la izquierda en la Figura 9­5, 237,5 para el modelo de la
derecha en la Figura 9­5 y 211,6 para el modelo de la Figura 9­3 . La clase KMeans
ejecuta el algoritmo n_init veces y conserva el modelo con la inercia más baja. En este
ejemplo, se seleccionará el modelo de la Figura 9­3 (a menos que tengamos muy mala
suerte con n_init inicializaciones aleatorias consecutivas). Si tiene curiosidad, se
puede acceder a la inercia de un modelo a través de la variable de instancia inertia_:
>>> kmeans.inercia_
211.59853725816856
El método score() devuelve la inercia negativa. ¿Por qué negativa? Porque el método
score() de un predictor siempre debe respetar la “inercia mayor” de Scikit­Learn.
Machine Translated by Google
es mejor” regla: si un predictor es mejor que otro, su método score() debe devolver una
puntuación mayor.
>>> kmeans.puntaje(X)
­211.59853725816856
En un artículo de 2006 se propuso una mejora importante del algoritmo K­Means, K­Means+
3
+. por David Arthur y Sergei Vassilvitskii. Introdujeron un paso de inicialización más inteligente
que tiende a seleccionar centroides que están distantes entre sí, y esta mejora hace que el
algoritmo K­Means tenga muchas menos probabilidades de converger a una solución
subóptima. Demostraron que el cálculo adicional requerido para el paso de inicialización
más inteligente vale la pena porque permite reducir drásticamente la cantidad de
veces que se debe ejecutar el algoritmo para encontrar la solución óptima. Aquí
está el algoritmo de inicialización K­Means++:
1. Tome un conjunto de datos (1) , elegidos uniformemente al azar de entre
centroide c .
(i)
2. Tome un nuevo centroide c ,(i)eligiendo una instancia x con probabilidad
D(x (j))
2
D(x (i)) / ∑ j=1
metro
2
, (i) donde D(x ) es la distancia entre
(i)
la instancia x y el centroide más cercano que ya fue elegido.
Esta distribución de probabilidad garantiza que las instancias más alejadas de los
centroides ya elegidos tengan muchas más probabilidades de ser seleccionadas
como centroides.
3. Repita el paso anterior hasta que se hayan elegido todos los k centroides.
La clase KMeans utiliza este método de inicialización de forma predeterminada. Si desea
forzarla a utilizar el método original (es decir, elegir k instancias aleatoriamente para definir
los centroides iniciales), puede configurar el hiperparámetro init como "random". Rara vez
necesitará hacer esto.
K­Means acelerados y K­Means de minilotes
Machine Translated by Google
Otra mejora importante del algoritmo K­Means fue propuesta en un artículo de 2003 por
4
Charles Elkan. Acelera considerablemente el algoritmo al evitar muchos cálculos
de distancia innecesarios. Elkan logró esto explotando la desigualdad triangular (es
decir, que una línea recta es siempre la distancia más corta entre dos puntos) y haciendo
5
un seguimiento de los límites superior e inferior para las distancias entre instancias y
centroides.
Este es el algoritmo que la clase KMeans usa de manera predeterminada (puede forzarlo
a usar el algoritmo original configurando el hiperparámetro del algoritmo en "completo",
aunque probablemente nunca lo necesitará).
Otra variante importante del algoritmo K­Means fue propuesta en un artículo de 2010 por
6
David Sculley. En lugar de utilizar el conjunto de datos completo en cada iteración,
el algoritmo es capaz de utilizar minilotes, moviendo los centroides apenas un poco
en cada iteración. Esto acelera el algoritmo típicamente por un factor de tres o
cuatro y hace posible agrupar conjuntos de datos enormes que no caben en la memoria.
Scikit­Learn implementa este algoritmo en la clase MiniBatchKMeans. Puedes usar esta
clase como la clase KMeans:
desde sklearn.cluster importar MiniBatchKMeans
minibatch_kmeans = MiniBatchKMeans(n_clústeres=5)
minibatch_kmeans.fit(X)
Si el conjunto de datos no cabe en la memoria, la opción más sencilla es utilizar la
clase memmap, como hicimos para el PCA incremental en el Capítulo 8. Como
alternativa, puede pasar un minilote a la vez al método partial_fit(), pero esto requerirá
mucho más trabajo, ya que deberá realizar múltiples inicializaciones y seleccionar la mejor
usted mismo (consulte la sección K­Means del minilote del cuaderno para ver un
ejemplo).
Aunque el algoritmo Mini­batch K­Means es mucho más rápido que el algoritmo K­
Means regular, su inercia es generalmente ligeramente peor, especialmente a
medida que aumenta el número de clústeres. Puede ver esto en la Figura 9­6:
el gráfico de la izquierda compara las inercias de los modelos Mini­batch K­Means
y K­Means regular entrenados en el conjunto de datos anterior utilizando
Machine Translated by Google
Varias cantidades de clústeres k. La diferencia entre las dos curvas permanece
bastante constante, pero esta diferencia se vuelve cada vez más significativa a
medida que k aumenta, ya que la inercia se vuelve cada vez más pequeña. En el gráfico
de la derecha, puede ver que el método Mini­batch K­Means es mucho más rápido que
el método K­Means normal, y esta diferencia aumenta con k.
Figura 9­6. El minibatch K­Means tiene una inercia mayor que K­Means (izquierda), pero es mucho más rápido (derecha),
especialmente a medida que aumenta k.
Encontrar el número óptimo de clústeres Hasta ahora,
hemos establecido el número de clústeres k en 5 porque era obvio al observar los datos
que ese era el número correcto de clústeres. Pero en general, no será tan fácil saber
cómo establecer k, y el resultado puede ser bastante malo si lo establece en el valor
incorrecto. Como puede ver en la Figura 9­7, establecer k en 3 u 8 da como resultado
modelos bastante malos.
Figura 9­7. Malas decisiones en cuanto al número de clústeres: cuando k es demasiado pequeño, los clústeres
separados se fusionan (izquierda) y cuando k es demasiado grande, algunos clústeres se dividen en múltiples partes (derecha)
Machine Translated by Google
Quizás estés pensando que podríamos simplemente elegir el modelo con la inercia
más baja, ¿verdad? Desafortunadamente, no es tan simple. La inercia para k=3 es
653,2, que es mucho más alta que para k=5 (que era 211,6). Pero con k=8, la inercia es
solo 119,1. La inercia no es una buena métrica de rendimiento cuando intentamos elegir k
porque sigue disminuyendo a medida que aumentamos k. De hecho, cuantos más
clústeres haya, más cerca estará cada instancia de su centroide más cercano y, por lo
tanto, menor será la inercia. Grafiquemos la inercia como una función de k (consulte la
Figura 9­8).
Figura 9­8. Al representar gráficamente la inercia en función del número de grupos k, la curva a
menudo contiene un punto de inflexión llamado “codo”.
Como puede ver, la inercia cae muy rápidamente a medida que aumentamos k hasta 4,
pero luego disminuye mucho más lentamente a medida que seguimos aumentando k.
Esta curva tiene aproximadamente la forma de un brazo, y hay un "codo" en k = 4.
Entonces, si no lo supiéramos, 4 sería una buena opción: cualquier valor más bajo sería
dramático, mientras que cualquier valor más alto no ayudaría mucho, y podríamos estar
dividiendo clústeres perfectamente buenos por la mitad sin ninguna buena razón.
Esta técnica para elegir el mejor valor para el número de clústeres es bastante burda.
Un enfoque más preciso (pero también más costoso computacionalmente) es utilizar
la puntuación de silueta, que es el coeficiente de silueta medio de todas las
instancias. El coeficiente de silueta de una instancia es igual a (b – a) / máx(a, b),
donde a es la distancia media a las otras instancias en el mismo clúster (es decir,
la distancia media dentro del clúster) y b es la distancia media al clúster más cercano (es
decir, la distancia media a las instancias
Machine Translated by Google
del siguiente clúster más cercano, definido como el que minimiza b, excluyendo el propio
clúster de la instancia). El coeficiente de silueta puede variar entre –1 y +1. Un coeficiente
cercano a +1 significa que la instancia está bien dentro de su propio clúster y lejos de
otros clústeres, mientras que un coeficiente cercano a 0 significa que está cerca de
un límite de clúster y, finalmente, un coeficiente cercano a –1 significa que la instancia
puede haber sido asignada al clúster incorrecto.
Para calcular la puntuación de silueta, puede utilizar la función
Silhouette_score() de Scikit­Learn, que le proporciona todas las instancias en el
conjunto de datos y las etiquetas que se les asignaron:
>>> de sklearn.metrics importar puntaje_silueta >>> puntaje_silueta(X,
kmeans.labels_) 0.655517642572828
Comparemos las puntuaciones de silueta para diferentes cantidades de grupos (ver
Figura 9­9).
Figura 9­9. Selección del número de clústeres k utilizando la puntuación de silueta
Como puede ver, esta visualización es mucho más rica que la anterior: aunque
confirma que k = 4 es una muy buena elección, también subraya el hecho de que k = 5
también es bastante bueno, y mucho mejor que k = 6 o 7. Esto no era visible al comparar
inercias.
Se obtiene una visualización aún más informativa cuando se traza el coeficiente de
silueta de cada instancia, ordenado por el grupo al que están asignados y por el valor del
coeficiente. Esto se denomina diagrama de silueta (consulte la Figura 9­10). Cada
diagrama contiene una forma de cuchillo por grupo.
Machine Translated by Google
La altura de la forma indica la cantidad de instancias que contiene el clúster y su ancho
representa los coeficientes de silueta ordenados de las instancias del clúster (cuanto más
ancho, mejor). La línea discontinua indica el coeficiente de silueta medio.
Figura 9­10. Análisis de los diagramas de silueta para distintos valores de k
Las líneas discontinuas verticales representan la puntuación de silueta para cada número de
clústeres. Cuando la mayoría de las instancias en un clúster tienen un coeficiente inferior a
esta puntuación (es decir, si muchas de las instancias terminan antes de la línea discontinua,
a la izquierda de la misma), entonces el clúster es bastante malo, ya que esto significa que
sus instancias están demasiado cerca de otros clústeres. Podemos ver que cuando k = 3 y
cuando k = 6, obtenemos clústeres malos. Pero cuando k = 4 o k = 5, los clústeres se ven
bastante bien: la mayoría de las instancias se extienden más allá de la línea discontinua,
hacia la derecha y más cerca de 1.0. Cuando k = 4, el clúster en el índice 1 (el tercero desde
arriba) es bastante grande. Cuando k = 5, todos los clústeres tienen tamaños similares. Entonces, incluso
Machine Translated by Google
Aunque la puntuación general de la silueta de k = 4 es ligeramente mayor que para k
= 5, parece una buena idea utilizar k = 5 para obtener grupos de tamaños similares.
Límites de K­Means
A pesar de sus muchas ventajas, la más notable es que es rápido y escalable, K­
Means no es perfecto. Como vimos, es necesario ejecutar el algoritmo varias veces
para evitar soluciones subóptimas, además de que es necesario especificar el
número de clústeres, lo que puede ser bastante complicado. Además, K­Means no se
comporta muy bien cuando los clústeres tienen distintos tamaños, densidades
diferentes o formas no esféricas. Por ejemplo, la Figura 9­11 muestra cómo K­
Means agrupa un conjunto de datos que contiene tres clústeres elipsoidales
de diferentes dimensiones, densidades y orientaciones.
Figura 9­11. K­Means no logra agrupar estas manchas elipsoidales correctamente
Como puede ver, ninguna de estas soluciones es buena. La solución de la izquierda
es mejor, pero aún así elimina el 25 % del grupo del medio y lo asigna al grupo de la
derecha. La solución de la derecha es simplemente terrible, aunque su inercia es
menor. Por lo tanto, según los datos, diferentes algoritmos de agrupamiento pueden
funcionar mejor. En este tipo de grupos elípticos, los modelos de mezcla
gaussiana funcionan muy bien.
Machine Translated by Google
CONSEJO
Es importante escalar las características de entrada antes de ejecutar K­Means, ya que los
clústeres pueden verse muy estirados y K­Means tendrá un rendimiento deficiente. Escalar las
características no garantiza que todos los clústeres sean agradables y esféricos, pero generalmente
mejora las cosas.
Ahora veamos algunas formas en las que podemos beneficiarnos de la agrupación en clústeres.
Usaremos K­Means, pero siéntete libre de experimentar con otros algoritmos de agrupación en clústeres.
Uso de agrupamiento para segmentación de imágenes La segmentación de
imágenes es la tarea de dividir una imagen en múltiples segmentos. En la segmentación
semántica, todos los píxeles que forman parte del mismo tipo de objeto se asignan al mismo
segmento. Por ejemplo, en el sistema de visión de un automóvil autónomo, todos los píxeles
que forman parte de la imagen de un peatón podrían asignarse al segmento "peatón" (habría un
segmento que contuviera a todos los peatones). En la segmentación de instancias, todos los
píxeles que forman parte del mismo objeto individual se asignan al mismo segmento. En este
caso, habría un segmento diferente para cada peatón. El estado del arte en la segmentación semántica
o de instancias hoy en día se logra utilizando arquitecturas complejas basadas en redes
neuronales convolucionales (consulte el Capítulo 14). Aquí, vamos a hacer algo mucho más simple:
segmentación de color. Simplemente asignaremos píxeles al mismo segmento si tienen un color
similar. En algunas aplicaciones, esto puede ser suficiente. Por ejemplo, si desea analizar
imágenes satelitales para medir cuánta área forestal total hay en una región, la segmentación de
color puede ser suficiente.
Primero, use la función imread() de Matplotlib para cargar la imagen (vea la imagen superior izquierda
en la Figura 9­12):
>>> desde matplotlib.image importar imread
# o
`de importación de imageio
estoy leído`
>>> imagen =
imread(os.path.join("imágenes", "aprendizaje_no_supervisado", "ladybug.png"))
Machine Translated by Google
>>> imagen.forma
(533, 800, 3)
La imagen se representa como una matriz 3D. El tamaño de la primera dimensión
es la altura; la segunda es el ancho; y la tercera es el número de canales de
color, en este caso rojo, verde y azul (RGB). En otras palabras, para cada píxel hay un
vector 3D que contiene las intensidades de rojo, verde y azul, cada una entre 0,0 y 1,0
(o entre 0 y 255, si utiliza imageio.imread()). Algunas imágenes
pueden tener menos canales, como las imágenes en escala de grises (un canal).
Y algunas imágenes pueden tener más canales, como las imágenes con un
canal alfa adicional para transparencia o las imágenes satelitales, que a
menudo contienen canales para muchas frecuencias de luz (por ejemplo, infrarrojos).
El siguiente código cambia la forma de la matriz para obtener una lista larga de colores
RGB, luego agrupa estos colores utilizando K­Means:
X = imagen.reshape(­1, 3)
kmeans = KMeans(n_clusters=8).fit(X)
img_segmentada = kmeans.cluster_centers_[kmeans.labels_]
img_segmentada = img_segmentada.reshape(imagen.shape)
Por ejemplo, puede identificar un grupo de colores para todos los tonos de verde. A
continuación, para cada color (por ejemplo, verde oscuro), busca el color medio del
grupo de colores del píxel. Por ejemplo, todos los tonos de verde pueden
reemplazarse por el mismo color verde claro (suponiendo que el color medio del
grupo de verdes sea verde claro). Por último, cambia la forma de esta larga lista
de colores para obtener la misma forma que la imagen original. ¡Y ya está!
Esto genera la imagen que se muestra en la parte superior derecha de la Figura 9­12.
Puede experimentar con distintas cantidades de grupos, como se muestra en la figura.
Cuando utilice menos de ocho grupos, observe que el llamativo color rojo de la mariquita
no logra formar un grupo propio: se fusiona con los colores del entorno. Esto
se debe a que K­Means prefiere grupos de tamaños similares.
La mariquita es pequeña (mucho más pequeña que el resto de la imagen), por lo que,
aunque su color es llamativo, K­Means no logra dedicarle un grupo.
Machine Translated by Google
Figura 9­12. Segmentación de imágenes mediante K­Means con varios números de grupos de colores
No fue muy difícil, ¿verdad? Ahora veamos otra aplicación de la agrupación: el preprocesamiento.
El uso de agrupamiento para preprocesamiento puede ser un enfoque
eficiente para la reducción de la dimensionalidad, en particular como paso de preprocesamiento antes
de un algoritmo de aprendizaje supervisado.
Como ejemplo del uso de agrupamiento para la reducción de dimensionalidad, abordemos el
conjunto de datos de dígitos, que es un conjunto de datos simple similar a MNIST que contiene 1797
imágenes en escala de grises de 8 × 8 que representan los dígitos del 0 al 9. Primero, cargue el conjunto
de datos:
desde sklearn.datasets importar load_digits
dígitos_x, dígitos_y = cargar_dígitos(devolver_X_y=Verdadero)
Ahora, divídalo en un conjunto de entrenamiento y un conjunto de prueba:
desde sklearn.model_selection importar train_test_split
X_tren, X_prueba, y_tren, y_prueba = tren_prueba_split(X_dígitos, y_dígitos)
A continuación, ajuste un modelo de regresión logística:
Machine Translated by Google
desde sklearn.linear_model importar LogisticRegression
log_reg = Regresión logística()
log_reg.fit(X_train, y_train)
Evaluemos su precisión en el conjunto de prueba:
>>> log_reg.score(prueba_X, prueba_y)
0,9688888888888889
Bien, esa es nuestra línea base: 96,9 % de precisión. Veamos si podemos mejorar
usando K­Means como paso de preprocesamiento. Crearemos una secuencia que
primero agrupará el conjunto de entrenamiento en 50 grupos y reemplazará las
imágenes con sus distancias a estos 50 grupos, luego aplicaremos un modelo de
regresión logística:
desde sklearn.pipeline importar Pipeline
tubería = Tubería([ ("kmeans",
KMeans(n_clusters=50)), ("log_reg", Regresión
logística()),
])
pipeline.fit(tren_X, tren_Y)
ADVERTENCIA
Dado que hay 10 dígitos diferentes, resulta tentador establecer el número de grupos en 10.
Sin embargo, cada dígito se puede escribir de varias maneras diferentes, por lo que es preferible utilizar un
mayor número de grupos, como por ejemplo 50.
Ahora evaluemos esta secuencia de clasificación:
>>> pipeline.score(prueba_x, prueba_y) 0.97777777777777777
¿Qué te parece? ¡Hemos reducido la tasa de error en casi un 30% (de aproximadamente el 3,1%
a aproximadamente el 2,2%)!
Machine Translated by Google
Pero elegimos el número de clústeres k arbitrariamente; seguramente podemos hacerlo mejor.
Dado que K­Means es solo un paso de preprocesamiento en una secuencia de
clasificación, encontrar un buen valor para k es mucho más simple que antes. No es necesario
realizar un análisis de silueta ni minimizar la inercia; el mejor valor de k es simplemente el
que da como resultado el mejor rendimiento de clasificación durante la validación cruzada.
Podemos usar GridSearchCV para encontrar la cantidad óptima de clústeres:
desde sklearn.model_selection importar GridSearchCV
param_grid = dict(kmeans__n_clusters=range(2, 100)) grid_clf =
GridSearchCV(pipeline, param_grid, cv=3, verbose=2) grid_clf.fit(X_train, y_train)
Veamos el mejor valor para k y el rendimiento del pipeline resultante:
>>> grid_clf.best_params_
{'kmeans__n_clusters': 99} >>>
grid_clf.score(prueba_X, prueba_Y) 0.9822222222222222
Con k = 99 clústeres, obtenemos un aumento significativo en la precisión, alcanzando un 98,22
% de precisión en el conjunto de prueba. ¡Genial! Es posible que desees seguir explorando
valores más altos para k, ya que 99 fue el valor más alto en el rango que exploramos.
Uso de la agrupación en clústeres para el aprendizaje semisupervisado Otro
caso de uso de la agrupación en clústeres es el aprendizaje semisupervisado, cuando
tenemos muchas instancias sin etiquetar y muy pocas instancias etiquetadas. Entrenemos
un modelo de regresión logística en una muestra de 50 instancias etiquetadas del
conjunto de datos de dígitos:
n_etiquetado = 50
log_reg = LogisticRegression()
log_reg.fit(X_train[:n_etiquetado], y_train[:n_etiquetado])
¿Cuál es el rendimiento de este modelo en el conjunto de pruebas?
Machine Translated by Google
>>> log_reg.score(prueba_X, prueba_y)
0.8333333333333334
La precisión es de tan solo el 83,3 %. No debería sorprender que sea mucho menor que
antes, cuando entrenamos el modelo con el conjunto de entrenamiento completo. Veamos
cómo podemos mejorar. Primero, agrupemos el conjunto de entrenamiento en 50
grupos. Luego, para cada grupo, busquemos la imagen más cercana al centroide.
A estas imágenes las llamaremos imágenes representativas:
k = 50
kmeans = KMeans(n_clusters=k)
distancia_dígito_X = kmeans.fit_transform(X_train) id_dígito_representativo_x
= np.argmin(distancia_dígito_X, eje=0)
X_dígitos_representativos = X_tren[dígito_representativo_idx]
La figura 9­13 muestra estas 50 imágenes representativas.
Figura 9­13. Cincuenta imágenes de dígitos representativos (uno por grupo)
Veamos cada imagen y etiquetémosla manualmente:
y_dígitos_representativos = np.array([4, 8, 0, 6, 8, 3, ..., 7, 6, 2, 3, 1, 1])
Ahora tenemos un conjunto de datos con solo 50 instancias etiquetadas, pero en lugar de
ser instancias aleatorias, cada una de ellas es una imagen representativa de su grupo.
Veamos si el rendimiento es mejor:
>>> log_reg = LogisticRegression() >>>
log_reg.fit(dígitos_representativos_X, dígitos_representativos_Y) >>> log_reg.score(prueba_X, prueba_y)
0.9222222222222223
Machine Translated by Google
¡Guau! Pasamos de una precisión del 83,3 % al 92,2 %, aunque todavía solo estamos
entrenando el modelo en 50 instancias. Dado que etiquetar instancias suele ser costoso y
complicado, especialmente cuando debe hacerlo manualmente un experto, es una buena idea
etiquetar instancias representativas en lugar de solo instancias aleatorias.
Pero quizás podamos ir un paso más allá: ¿qué sucedería si propagáramos las etiquetas a
todas las demás instancias del mismo clúster? Esto se llama propagación de etiquetas .
propagación:
y_train_propagated = np.empty(len(X_train), dtype=np.int32) para i en rango(k):
y_train_propagated[kmeans.labels_==i] = y_representative_digits[i]
Ahora entrenemos nuevamente el modelo y observemos su rendimiento:
>>> log_reg = LogisticRegression() >>> log_reg.fit(X_train,
y_train_propagated) >>> log_reg.score(X_test, y_test) 0.9333333333333333
Obtuvimos un aumento de precisión razonable, pero nada absolutamente sorprendente.
El problema es que propagamos la etiqueta de cada instancia representativa a todas las
instancias del mismo clúster, incluidas las instancias ubicadas cerca de los límites del clúster,
que tienen más probabilidades de estar mal etiquetadas. Veamos qué sucede si solo propagamos
las etiquetas al 20 % de las instancias que están más cerca de los centroides:
percentil_más_cercano = 20
distancia_de_clúster_X = distancia_de_dígitos_X[np.arange(len(X_train)), kmeans.labels_] para
i en rango(k): en_clúster
= (kmeans.labels_ == i) distancia_de_clúster
= distancia_de_clúster_X [en_clúster] distancia_de_corte
= np.percentile(distancia_de_clúster, percentil_más_cercano) distancia_de_corte_por_encima
= (distancia_de_clúster_X > distancia_de_corte)
X_cluster_dist[dentro del clúster y por encima del límite] = ­1
parcialmente_propagado = (X_cluster_dist != ­1)
Machine Translated by Google
Tren_X_parcialmente_propagado = Tren_X[parcialmente_propagado] Tren_Y_parcialmente_propagado
= Tren_Y_propagado[parcialmente_propagado]
Ahora entrenemos nuevamente el modelo en este conjunto de datos parcialmente propagado:
>>> log_reg = LogisticRegression() >>>
log_reg.fit(X_entrenamiento_parcialmente_propagado,
y_entrenamiento_parcialmente_propagado) >>>
log_reg.score(X_prueba, y_prueba) 0.94
¡Excelente! Con solo 50 instancias etiquetadas (¡solo 5 ejemplos por clase en promedio!),
obtuvimos una precisión del 94,0 %, que está bastante cerca del rendimiento de la regresión logística
en el conjunto de datos de dígitos completamente etiquetados (que fue del 96,9 %). Este buen
rendimiento se debe al hecho de que las etiquetas propagadas son realmente bastante buenas:
su precisión es muy cercana al 99 %, como lo muestra el siguiente código:
>>> np.mean(y_train_parcialmente_propagado ==
y_train[parcialmente_propagado]) 0.9896907216494846
Machine Translated by Google
APRENDIZAJE ACTIVO
Para seguir mejorando el modelo y el conjunto de entrenamiento, el siguiente paso
podría ser realizar algunas rondas de aprendizaje activo, que es cuando un experto
humano interactúa con el algoritmo de aprendizaje y proporciona etiquetas
para instancias específicas cuando el algoritmo las solicita. Existen muchas
estrategias diferentes para el aprendizaje activo, pero una de las más comunes
se denomina muestreo de incertidumbre. Así es como funciona:
1. El modelo se entrena en las instancias etiquetadas recopiladas hasta el
momento y se utiliza para realizar predicciones en todas las
instancias no etiquetadas.
2. Los casos en los que el modelo es más incierto (es decir, cuando su
probabilidad estimada es más baja) se entregan al experto para que
los etiquete.
3. Repita este proceso hasta que la mejora del rendimiento deje de justificar
el esfuerzo de etiquetado.
Otras estrategias incluyen etiquetar las instancias que producirían el mayor cambio
de modelo, o la mayor caída en el error de validación del modelo, o las instancias
en las que diferentes modelos no están de acuerdo (por ejemplo, un SVM o un
bosque aleatorio).
Antes de pasar a los modelos de mezcla gaussiana, echemos un vistazo a
DBSCAN, otro algoritmo de agrupamiento popular que ilustra un enfoque muy
diferente basado en la estimación de la densidad local. Este enfoque permite que el
algoritmo identifique grupos de formas arbitrarias.
Escaneo de base de datos
Este algoritmo define los clústeres como regiones continuas de alta densidad. Así es como
funciona:
Machine Translated by Google
Para cada instancia, el algoritmo cuenta cuántas instancias se encuentran
dentro de una pequeña distancia ε (épsilon) de ella. Esta región se denomina
vecindad ε de la instancia.
Si una instancia tiene al menos min_samples instancias en su
vecindario ε (incluida ella misma), entonces se considera una instancia
central. En otras palabras, las instancias centrales son aquellas que se
encuentran en regiones densas.
Todas las instancias en el vecindario de una instancia central pertenecen al
mismo clúster. Este vecindario puede incluir otras instancias
centrales; por lo tanto, una secuencia larga de instancias centrales
vecinas forma un solo clúster.
Cualquier instancia que no sea una instancia central y no tenga una en su
vecindad se considera una anomalía.
Este algoritmo funciona bien si todos los cúmulos son lo suficientemente densos y están
bien separados por regiones de baja densidad. La clase DBSCAN de Scikit­Learn
es tan sencilla de usar como cabría esperar. Probémosla en el conjunto de datos de
las lunas, presentado en el Capítulo 5:
desde sklearn.cluster importe DBSCAN
desde sklearn.datasets importe make_moons
X, y = make_moons(n_muestras=1000, ruido=0,05) dbscan
= DBSCAN(eps=0,05, min_muestras=5) dbscan.fit(X)
Las etiquetas de todas las instancias ahora están disponibles en la variable de instancia
labels_:
>>> matriz
dbscan.labels_ ([0, 2, ­1, ­1, 1, 0, 0, 0, ..., 3, 2, 3, 3, 4, 2, 6, 3])
Tenga en cuenta que algunas instancias tienen un índice de agrupamiento igual a ­1, lo
que significa que el algoritmo las considera anomalías. Los índices de la
Machine Translated by Google
Las instancias principales están disponibles en la variable de instancia
core_sample_indices_, y las instancias principales en sí mismas están
disponibles en la variable de instancia component_:
>>> len(dbscan.core_sample_indices_) 808 >>>
dbscan.core_sample_indices_ matriz([ 0, 4, 5, 6, 7, 8,
10, 11, ..., 992, 993, 995, 997, 998, 999]) >>> dbscan.components_ matriz([[­0,02137124, 0,40618608], [­0,84192557,
0,53058695],
...
[­0,94355873, 0,3278936],
[0,79419406, 0,60777171]])
Esta agrupación se representa en el gráfico de la izquierda de la Figura 9­14. Como
puede ver, identificó una gran cantidad de anomalías, además de siete grupos diferentes.
¡Qué decepción! Afortunadamente, si ampliamos el vecindario de cada instancia
aumentando eps a 0,2, obtenemos la agrupación de la derecha, que parece perfecta.
Continuemos con este modelo.
Figura 9­14. Agrupamiento DBSCAN utilizando dos radios de vecindad diferentes
Sorprendentemente, la clase DBSCAN no tiene un método predict(), aunque sí
tiene un método fit_predict(). En otras palabras, no puede predecir a qué clúster
pertenece una nueva instancia. Esta decisión de implementación se
tomó porque diferentes algoritmos de clasificación pueden ser mejores para
diferentes tareas, por lo que los autores decidieron dejar que
Machine Translated by Google
El usuario elige cuál utilizar. Además, no es difícil de implementar.
Por ejemplo, entrenemos un KNeighborsClassifier:
desde sklearn.neighbors importa KNeighborsClassifier
knn = KNeighborsClassifier(n_vecinos=50)
knn.fit(dbscan.componentes_, dbscan.etiquetas_[dbscan.índices_de_muestra_principales_])
Ahora, dadas unas cuantas instancias nuevas, podemos predecir a qué grupo es más
probable que pertenezcan e incluso estimar una probabilidad para cada grupo:
>>> X_nuevo = np.array([[­0.5, 0], [0, 0.5], [1, ­0.1], [2, 1]]) >>> knn.predict(X_nuevo)
array([1, 0, 1, 0]) >>>
knn.predict_proba(X_nuevo)
array([[0.18, 0.82], [1. , 0. ], [0.12,
0.88], [1. , 0. ]])
Tenga en cuenta que solo entrenamos el clasificador en las instancias principales, pero
también podríamos haber elegido entrenarlo en todas las instancias, o en todas excepto
las anomalías: esta elección depende de la tarea final.
El límite de decisión se representa en la Figura 9­15 (las cruces representan las cuatro
instancias en X_new). Observe que, dado que no hay ninguna anomalía en el conjunto de
entrenamiento, el clasificador siempre elige un clúster, incluso cuando ese clúster está muy
alejado. Es bastante sencillo introducir una distancia máxima, en cuyo caso las dos
instancias que están lejos de ambos clústeres se clasifican como anomalías. Para ello,
utilice el método kneighbors() del KNeighborsClassifier. Dado un conjunto de instancias,
devuelve las distancias y los índices de los k vecinos más cercanos en el conjunto de
entrenamiento (dos matrices, cada una con k columnas):
>>> y_dist, y_pred_idx = knn.kvecinos(X_nuevo, n_vecinos=1) >>> y_pred =
dbscan.etiquetas_[dbscan.índices_de_muestra_core_][y_pred_idx] >>> y_pred[y_dist >
0.2] = ­1 >>> y_pred.ravel() matriz([­1,
0, 1, ­1])
Machine Translated by Google
Figura 9­15. Límite de decisión entre dos clústeres
En resumen, DBSCAN es un algoritmo muy simple pero poderoso capaz de identificar
cualquier cantidad de clústeres de cualquier forma. Es robusto a los valores atípicos y
tiene solo dos hiperparámetros (eps y min_samples). Sin embargo, si la densidad varía
significativamente entre los clústeres, puede resultar imposible capturar todos los clústeres
correctamente. Su complejidad computacional es aproximadamente O(m log m), lo
que lo hace bastante cercano a lineal con respecto a la cantidad de instancias, pero la
implementación de Scikit­Learn puede requerir hasta O(m ) de memoria si eps es grande.
2
CONSEJO
También puede probar Hierarchical DBSCAN (HDBSCAN), que está implementado en
el proyecto scikit­learn­contrib.
Otros algoritmos de agrupamiento
Scikit­Learn implementa varios algoritmos de agrupamiento más que deberías
consultar. No podemos cubrirlos todos en detalle aquí, pero a continuación te ofrecemos una
breve descripción general:
Aglomeración aglomerativa
Machine Translated by Google
Una jerarquía de clústeres se construye de abajo hacia arriba. Piense en muchas burbujas
diminutas flotando en el agua y uniéndose gradualmente entre sí hasta que haya un gran
grupo de burbujas. De manera similar, en cada iteración, la agrupación
aglomerativa conecta el par de clústeres más cercano (comenzando con instancias
individuales). Si dibujara un árbol con una rama para cada par de clústeres que se
fusionaran, obtendría un árbol binario de clústeres, donde las hojas son las instancias
individuales. Este enfoque se escala muy bien a grandes cantidades de instancias o
clústeres. Puede capturar clústeres de varias formas, produce un árbol de clústeres
flexible e informativo en lugar de obligarlo a elegir una escala de clúster en particular
y se puede usar con cualquier distancia por pares. Puede escalar muy bien a grandes
cantidades de instancias si proporciona una matriz de conectividad, que es una matriz
dispersa m × m que indica qué pares de instancias son vecinos (por ejemplo, devuelto por
sklearn.neighbors.kneighbors_graph()). Sin una matriz de conectividad, el algoritmo no
se escala bien a grandes conjuntos
de datos.
ABEDUL
El algoritmo BIRCH (Balanced Iterative Reducing and Clustering using Hierarchies)
fue diseñado específicamente para conjuntos de datos muy grandes y puede ser
más rápido que el algoritmo K­Means por lotes, con resultados similares, siempre que la
cantidad de características no sea demasiado grande (<20). Durante el
entrenamiento, construye una estructura de árbol que contiene la información suficiente
para asignar rápidamente cada nueva instancia a un clúster, sin tener que almacenar
todas las instancias en el árbol: este enfoque le permite usar una memoria limitada, mientras
maneja conjuntos de datos enormes.
Desplazamiento medio
Este algoritmo comienza colocando un círculo centrado en cada instancia; luego, para
cada círculo, calcula la media de todas las instancias ubicadas dentro de él y desplaza el
círculo para que quede centrado en la media.
A continuación, itera este paso de cambio de media hasta que todos los círculos
dejan de moverse (es decir, hasta que cada uno de ellos está centrado en la
media de las instancias que contiene). El cambio de media desplaza los círculos en la dirección de
Machine Translated by Google
mayor densidad, hasta que cada uno de ellos haya encontrado un máximo de densidad local.
Finalmente, todas las instancias cuyos círculos se han asentado en el mismo lugar (o lo
suficientemente cerca) se asignan al mismo clúster. Mean­Shift tiene algunas de las mismas
características que DBSCAN, como la capacidad de encontrar cualquier cantidad de
clústeres de cualquier forma, tiene muy pocos hiperparámetros (solo uno: el radio de los círculos,
llamado ancho de banda) y se basa en la estimación de densidad local. Pero a diferencia de
DBSCAN, Mean­Shift tiende a cortar los clústeres en pedazos cuando tienen variaciones de
densidad interna.
2 no es adecuado
Desafortunadamente, su complejidad computacional es O(m ), por lo que
para conjuntos de datos grandes.
Propagación por afinidad
Este algoritmo utiliza un sistema de votación, en el que las instancias votan a instancias
similares para que sean sus representantes y, una vez que el algoritmo converge, cada
representante y sus votantes forman un grupo. La propagación por afinidad puede detectar
cualquier cantidad de grupos de diferentes tamaños. Desafortunadamente, este algoritmo tiene
2
una complejidad computacional de O(m ), por lo que tampoco es adecuado
para grandes
conjuntos de datos.
Agrupamiento espectral
Este algoritmo toma una matriz de similitud entre las instancias y crea una incrustación de
baja dimensión a partir de ella (es decir, reduce su dimensionalidad), luego utiliza otro
algoritmo de agrupamiento en este espacio de baja dimensión (la implementación de Scikit­
Learn utiliza K­Means).
La agrupación espectral permite capturar estructuras de agrupaciones complejas y también se
puede utilizar para cortar gráficos (por ejemplo, para identificar agrupaciones de amigos en
una red social). No se adapta bien a grandes cantidades de instancias y no funciona bien
cuando las agrupaciones tienen tamaños muy diferentes.
Ahora profundicemos en los modelos de mezcla gaussiana, que pueden usarse para la
estimación de densidad, la agrupación y la detección de anomalías.
Mezclas gaussianas
Machine Translated by Google
Un modelo de mezcla gaussiana (GMM) es un modelo probabilístico que supone que
las instancias se generaron a partir de una mezcla de varias distribuciones
gaussianas cuyos parámetros son desconocidos. Todas las instancias generadas a
partir de una única distribución gaussiana forman un grupo que, por lo general, parece un
elipsoide. Cada grupo puede tener una forma, un tamaño, una densidad y una
orientación elipsoidales diferentes, tal como se muestra en la Figura 9­11. Cuando
observa una instancia, sabe que se generó a partir de una de las
distribuciones gaussianas, pero no se le dice cuál y no sabe cuáles son los parámetros
de estas distribuciones.
Existen varias variantes del GMM. En la variante más simple, implementada en la clase
GaussianMixture, se debe conocer de antemano el número k de distribuciones
gaussianas. Se supone que el conjunto de datos X se generó mediante el siguiente
proceso probabilístico:
Para cada instancia, se elige aleatoriamente un grupo entre k grupos. La probabilidad de elegir el
El
grupo j se define por (j) 7 el peso del grupo, . El índice del grupo elegido para la ( i) i instancia se anota z
.
El
El
(i)
Si z = j, lo que significa que la instancia i ha sido asignada al grupo j , la
(i)
ubicación x de esta instancia se muestrea aleatoriamente de la distribución
(j)
gaussiana con media μ y matriz de covarianza (i)
N (μ(j) , Σ(j)). (j)
Esto se anota x Σ
Este proceso generativo puede representarse como un modelo gráfico. La figura 9­16
representa la estructura de las dependencias condicionales entre variables
aleatorias.
Machine Translated by Google
Figura 9­16. Representación gráfica de un modelo de mezcla gaussiana, incluidos sus parámetros.
(cuadrados), variables aleatorias (círculos) y sus dependencias condicionales (flechas sólidas)
8
A continuación se explica cómo interpretar la figura:
Los círculos representan variables aleatorias.
Los cuadrados representan valores fijos (es decir, parámetros del modelo).
Los rectángulos grandes se llaman placas. Indican que su
El contenido se repite varias veces.
El número en la parte inferior derecha de cada plato indica cuántas
veces que se repite su contenido. Por lo tanto, hay m variables aleatorias z
(1)
(i)
(m) (i)
(de z a z ) y m variables aleatorias x . También hay k
(j) (j)
significa μ y k matrices de covarianza Σ . Por último, sólo hay una
(1) (k)
a
vector de peso
(que contiene todos los pesos).
(i)
Cada variable z se extrae de la distribución categórica con
pesos
(i)
. Cada variable x se extrae de la normal
distribución, con la matriz de media y covarianza definida por su
(i)
grupo z
.
Las flechas sólidas representan dependencias condicionales.
Por ejemplo, la distribución de probabilidad para cada variable aleatoria z
(i)
Machine Translated by Google
depende del vector de peso
. Nótese que cuando una flecha cruza
un límite de placa, significa que se aplica a todas las repeticiones de
esa placa. Por ejemplo, el vector de peso
condiciona la
distribuciones de probabilidad de todas las variables aleatorias x(1)
ax
(metro)
.
(i)
(i)un interruptor: dependiendo
La flecha ondulada de z a x representa
sobre el valor de z (yo) (yo)
, La instancia x será muestreada de a
(i)
distribución gaussiana diferente. Por ejemplo, si z = j, entonces
(i)
incógnita
N (μ(j) , Σ(j)).
Los nodos sombreados indican que el valor es conocido. Por lo tanto, en este caso,
(i) valores conocidos: se llaman
Sólo las variables aleatorias x tienen
Variables observadas. Las variables aleatorias desconocidas z (i)
se denominan
variables latentes.
Entonces, ¿qué se puede hacer con un modelo de este tipo? Bueno, dado el conjunto de datos X,
Por lo general, queremos comenzar estimando los pesos
(1)y Σ a Σ(k). Mezcla (1)
(k) de Scikit­Learn
parámetros μ a μ
gaussiana
La clase hace que esto sea súper fácil:
de sklearn.mixture importar GaussianMixture
gm = Mezcla gaussiana (n_componentes=3, n_init=10)
gm.ajuste(X)
Veamos los parámetros que estimó el algoritmo:
>>> pesos gm_
matriz([0,20965228, 0,4000662 >>> gm.means_
, 0,39028152])
matriz([[ 3.39909717, 1.05933727],
[­1,40763984, 1,42710194],
[ 0,05135313, 0,07524095]])
>>> covarianzas gm_
matriz([[[ 1.14807234, ­0.03270354],
[­0,03270354, 0,95496237]],
[[0,63478101, 0,72969804],
[ 0,72969804, 1,1609872 ]],
y toda la distribución
Machine Translated by Google
[[0,68809572, 0,79608475],
[ 0,79608475, 1,21234145]]])
Genial, ¡funcionó bien! De hecho, los pesos que se usaron para generar los datos fueron
0,2, 0,4 y 0,4; y de manera similar, las matrices de medias y covarianzas fueron muy
cercanas a las encontradas por el algoritmo. Pero, ¿cómo? Esta clase se basa en el
algoritmo Expectation­Maximization (EM), que tiene muchas similitudes con el algoritmo K­
Means: también inicializa los parámetros del clúster de forma aleatoria, luego repite
dos pasos hasta la convergencia, primero asignando instancias a los clústeres (esto se
llama el paso de expectativa) y luego actualizando los clústeres (esto se llama el paso de
maximización). Suena familiar, ¿verdad? En el contexto de la agrupación, puede pensar
en EM como una (1) generalización de K­Means que no solo encuentra los centros de
los clústeres (μ a (1) μ ), sino también su tamaño, forma y orientación (Σ a Σ ), así como (k)
(k) pesos relativos (). Sin embargo, a diferencia de K­Means, EM utiliza(k)
sus
asignaciones
de
(1)
a duras. Para cada instancia, durante el paso de expectativa,
clústeres flexibles, no asignaciones
el algoritmo estima la probabilidad de que pertenezca a cada clúster (según los parámetros
del clúster actual). Luego, durante el paso de maximización, cada clúster se actualiza
utilizando todas las instancias del conjunto de datos, y cada instancia se pondera según
la probabilidad estimada de que pertenezca a ese clúster. Estas probabilidades se
denominan responsabilidades de los clústeres para las instancias. Durante el paso de
maximización, la actualización de cada clúster se verá afectada principalmente por las
instancias de las que es más responsable.
ADVERTENCIA
Lamentablemente, al igual que K­Means, EM puede terminar convergiendo hacia soluciones deficientes, por lo
que debe ejecutarse varias veces, conservando solo la mejor solución. Por eso, configuramos n_init en
10. Tenga cuidado: de manera predeterminada, n_init está configurado en 1.
Puedes comprobar si el algoritmo convergió o no y cuántas iteraciones fueron
necesarias:
Machine Translated by Google
>>> gm.converged_ Verdadero
>>> gm.n_iter_ 3
Ahora que tiene una estimación de la ubicación, el tamaño, la forma, la orientación y el peso relativo
de cada grupo, el modelo puede asignar fácilmente cada instancia al grupo más probable
(agrupamiento estricto) o estimar la probabilidad de que pertenezca a un grupo en particular
(agrupamiento flexible). Simplemente use el método predict() para el agrupamiento estricto o el
método predict_proba() para el agrupamiento flexible:
>>> gm.predict(X) matriz([2,
2, 1, ..., 0, 0, 0]) >>> gm.predict_proba(X)
matriz([[2,32389467e­02, 6,77397850e­07,
9,76760376e­01], [1,64685609e­02, 6,75361303e­04, 9,82856078e­01], [2,01535333e­06,
9,99923053e­01, 7,49319577e­05],
...,
[9.99999571e­01, 2.13946075e­26, 4.28788333e­07], [1.00000000e+00,
1.46454409e­41, 5.12459171e­16], [1.00000000e+00, 8.02006365e­41,
2.27626238e­15]])
Un modelo de mezcla gaussiana es un modelo generativo, lo que significa que puedes tomar
muestras de nuevas instancias de él (ten en cuenta que están ordenadas por índice de grupo):
>>> X_nuevo, y_nuevo = gm.muestra(6)
>>> X_nueva
matriz([[ 2,95400315, 2,63680992], [­1,16654575,
1,62792705], [­1,39477712, ­1,48511338],
[ 0,27221525, 0,690366 ], [ 0,54095936,
0,48591934], [ 0,38064009, ­0,56240465]])
>>> y_nueva
matriz([0, 1, 2, 2, 2, 2])
También es posible estimar la densidad del modelo en cualquier ubicación dada. Esto se
logra utilizando el método score_samples(): para cada instancia dada, este método estima el
logaritmo de la probabilidad.
Machine Translated by Google
Función de densidad (PDF) en esa ubicación. Cuanto mayor sea la puntuación, mayor
será la densidad:
>>> gm.score_samples(X)
matriz([­2,60782346, ­3,57106041, ­3,33003479, ..., ­3,51352783,
­4,39802535, ­3,80743859])
Si se calcula el exponencial de estas puntuaciones, se obtiene el valor de la PDF en la
ubicación de las instancias dadas. Estas no son probabilidades, sino densidades de
probabilidad: pueden tomar cualquier valor positivo, no solo un valor entre 0 y 1. Para
estimar la probabilidad de que una instancia caiga dentro de una región particular, se
tendría que integrar la PDF sobre esa región (si se hace así sobre todo el espacio de
posibles ubicaciones de instancias, el resultado será 1).
La figura 9­17 muestra las medias de los clústeres, los límites de decisión (líneas
discontinuas) y los contornos de densidad de este modelo.
Figura 9­17. Medias de conglomerados, límites de decisión y contornos de densidad de un modelo de mezcla
gaussiana entrenado
¡Excelente! El algoritmo claramente encontró una solución excelente. Por supuesto,
facilitamos su tarea generando los datos utilizando un conjunto de distribuciones
gaussianas 2D (desafortunadamente, los datos de la vida real no siempre son tan
gaussianos y de baja dimensión). También le dimos al algoritmo el número correcto de
Machine Translated by Google
Clústeres. Cuando hay muchas dimensiones, muchos clústeres o pocas instancias, EM
puede tener dificultades para converger a la solución óptima. Es posible que deba reducir la
dificultad de la tarea limitando la cantidad de parámetros que el algoritmo debe
aprender. Una forma de hacerlo es limitar el rango de formas y orientaciones que pueden
tener los clústeres. Esto se puede lograr imponiendo restricciones a las matrices de covarianza.
Para ello, establezca el hiperparámetro covariance_type en uno de los siguientes valores:
"esférico"
Todos los cúmulos deben ser esféricos, pero pueden tener diferentes diámetros (es
decir, diferentes variaciones).
"diagnóstico"
Los clústeres pueden adoptar cualquier forma elipsoidal de cualquier tamaño,
pero los ejes del elipsoide deben ser paralelos a los ejes de coordenadas (es
decir, las matrices de covarianza deben ser diagonales).
"atado"
Todos los grupos deben tener la misma forma elipsoidal, tamaño y orientación (es decir,
todos los grupos comparten la misma matriz de covarianza).
De manera predeterminada, covariance_type es igual a "full", lo que significa que cada grupo
puede adoptar cualquier forma, tamaño y orientación (tiene su propia matriz de
covarianza sin restricciones). La Figura 9­18 muestra las soluciones encontradas por el algoritmo
EM cuando covariance_type se establece en "tied" o "spherical".
Machine Translated by Google
Figura 9­18. Mezclas gaussianas para cúmulos ligados (izquierda) y cúmulos esféricos (derecha)
NOTA
La complejidad computacional del entrenamiento de un modelo GaussianMixture depende de
la cantidad de instancias m, la cantidad de dimensiones n, la cantidad de clústeres k y las
restricciones de las matrices de covarianza. Si covariance_type es "spherical" o "diag", es O(kmn),
suponiendo que los datos tienen una estructura de agrupamiento. Si covariance_type
2
3
es "tied" o "full", es O(kmn + kn ), por lo que no se escalará a una gran cantidad de características.
Los modelos de mezcla gaussiana también se pueden utilizar para la detección de anomalías. Veamos
cómo.
Detección de anomalías mediante mezclas gaussianas
La detección de anomalías (también llamada detección de valores atípicos) es la tarea de detectar
instancias que se desvían considerablemente de la norma. Estas instancias se denominan anomalías
o valores atípicos, mientras que las instancias normales se denominan valores atípicos.
La detección de anomalías es útil en una amplia variedad de aplicaciones, como la detección
de fraudes, la detección de productos defectuosos en la fabricación o la eliminación de
valores atípicos de un conjunto de datos antes de entrenar otro modelo (lo que puede mejorar
significativamente el rendimiento del modelo resultante).
Machine Translated by Google
Utilizar un modelo de mezcla gaussiana para la detección de anomalías es bastante simple:
cualquier instancia ubicada en una región de baja densidad puede considerarse una anomalía.
Debe definir qué umbral de densidad desea utilizar. Por ejemplo, en una empresa de fabricación que
intenta detectar productos defectuosos, la proporción de productos defectuosos suele ser bien
conocida. Digamos que es igual al 4 %. A continuación, establece el umbral de densidad como el
valor que da como resultado que el 4 % de las instancias se encuentren en áreas por debajo de
esa densidad de umbral. Si nota que obtiene demasiados falsos positivos (es decir, productos
perfectamente buenos que se marcan como defectuosos), puede reducir el umbral. Por el
contrario, si tiene demasiados falsos negativos (es decir, productos defectuosos que el sistema no
marca como defectuosos), puede aumentar el umbral. Este es el equilibrio habitual entre precisión
y recuperación (consulte el Capítulo 3). A continuación, se muestra cómo identificaría los
valores atípicos utilizando la densidad más baja del cuarto percentil como umbral (es decir,
aproximadamente el 4 % de las instancias se marcarán como anomalías):
densidades = gm.score_samples(X)
umbral_densidad = np.percentile(densidades, 4) anomalías =
X[densidades < umbral_densidad]
La figura 9­19 representa estas anomalías como estrellas.
Figura 9­19. Detección de anomalías mediante un modelo de mezcla gaussiana
Una tarea estrechamente relacionada es la detección de novedades: se diferencia de la
detección de anomalías en que se supone que el algoritmo se entrena en un entorno “limpio”.
Machine Translated by Google
conjunto de datos no contaminado por valores atípicos, mientras que la detección de anomalías no
hace esta suposición. De hecho, la detección de valores atípicos se utiliza a menudo para limpiar un
conjunto de datos.
CONSEJO
Los modelos de mezcla gaussiana intentan ajustar todos los datos, incluidos los valores atípicos, por
lo que si tiene demasiados, esto sesgará la visión del modelo de "normalidad" y algunos valores
atípicos pueden considerarse erróneamente como normales. Si esto sucede, puede intentar ajustar
el modelo una vez, usarlo para detectar y eliminar los valores atípicos más extremos y luego ajustar el
modelo nuevamente en el conjunto de datos depurado. Otro enfoque es utilizar métodos de estimación
de covarianza robustos (consulte la clase EllipticEnvelope).
Al igual que K­Means, el algoritmo GaussianMixture requiere que especifiques
la cantidad de clústeres. Entonces, ¿cómo puedes averiguarlo?
Selección del número de clústeres
Con K­Means, se puede utilizar la inercia o la puntuación de silueta para seleccionar
la cantidad adecuada de conglomerados. Pero con las mezclas gaussianas, no es
posible utilizar estas métricas porque no son fiables cuando los conglomerados
no son esféricos o tienen diferentes tamaños. En su lugar, se puede intentar
encontrar el modelo que minimice un criterio de información teórico, como el criterio de
información bayesiano (BIC) o el criterio de información de Akaike (AIC),
definidos en la ecuación 9­1.
Ecuación 9­1. Criterio de información bayesiano (BIC) y criterio de información de Akaike (AIC)
BIC = logaritmo (m) p − 2 logaritmo (Lˆ)
AIC = 2p − 2 log(Lˆ)
En estas ecuaciones:
m es el número de instancias, como siempre.
Machine Translated by Google
p es el número de parámetros aprendidos por el modelo.
Lˆ es el valor maximizado de la función de verosimilitud del modelo.
Tanto el BIC como el AIC penalizan los modelos que tienen más parámetros que aprender
(por ejemplo, más grupos) y recompensan a los modelos que se ajustan bien a los datos.
A menudo terminan seleccionando el mismo modelo. Cuando difieren, el modelo
seleccionado por el BIC tiende a ser más simple (menos parámetros) que el seleccionado
por el AIC, pero tiende a no ajustarse tan bien a los datos (esto es especialmente
cierto para conjuntos de datos más grandes).
Machine Translated by Google
FUNCIÓN DE VEROSIMILITUD
Los términos “probabilidad” y “verosimilitud” se utilizan a menudo
indistintamente en el idioma inglés, pero tienen significados muy diferentes en
estadística. Dado un modelo estadístico con algunos parámetros θ, la palabra
“probabilidad” se utiliza para describir cuán plausible es un resultado futuro x
(conociendo los valores de los parámetros θ), mientras que la palabra
“verosimilitud” se utiliza para describir cuán plausible es un conjunto particular
de valores de parámetros θ , una vez que se conoce el resultado x .
Considere un modelo de mezcla 1D de dos distribuciones gaussianas centradas
en ­4 y +1. Para simplificar, este modelo de juguete tiene un único parámetro θ
que controla las desviaciones estándar de ambas distribuciones. El gráfico de
contorno superior izquierdo de la Figura 9­20 muestra todo el modelo f(x; θ) como una
función tanto de x como de θ. Para estimar la distribución de probabilidad de un
resultado futuro x, debe establecer el parámetro del modelo θ. Por ejemplo, si
establece θ en 1,3 (la línea horizontal), obtendrá la función de densidad de
probabilidad f(x; θ=1,3) que se muestra en el gráfico inferior izquierdo. Digamos
que desea estimar la probabilidad de que x se encuentre entre ­2 y +2. Debe
calcular la integral de la PDF en este rango (es decir, la superficie de la región
sombreada). Pero ¿qué sucede si no conoce θ y, en cambio, ha observado una
única instancia x=2,5 (la línea vertical en el gráfico superior izquierdo)? En este
caso, se obtiene la función de probabilidad L (θ|x=2,5)=f(x=2,5;
θ), representada en el gráfico superior derecho.
Machine Translated by Google
Figura 9­20. Función paramétrica de un modelo (arriba a la izquierda) y algunas funciones derivadas: una función de probabilidad de fórmula
(abajo a la izquierda), una función de verosimilitud (arriba a la derecha) y una función de logaritmo de verosimilitud (abajo a la derecha)
En resumen, la función de densidad de probabilidad es una función de x (con θ fijo),
mientras que la función de verosimilitud es una función de θ (con x fijo). Es importante
entender que la función de verosimilitud no es una distribución de probabilidad:
si integras una distribución de probabilidad sobre todos los valores posibles de x,
siempre obtendrás 1; pero si integras la función de verosimilitud sobre todos
los valores posibles de θ, el resultado puede ser cualquier valor positivo.
Dado un conjunto de datos X, una tarea habitual consiste en intentar estimar los
valores más probables para los parámetros del modelo. Para ello, se deben
encontrar los valores que maximizan la función de verosimilitud, dado X. En este
ejemplo, si se ha observado una única instancia x=2,5, la estimación de
máxima verosimilitud (MLE) de θ es ˆθ=1,5. Si existe una distribución de probabilidad
previa g sobre θ , es posible tenerla en cuenta maximizando L (θ|x)g(θ) en
lugar de simplemente maximizar L (θ|x). Esto se denomina estimación máxima a
posteriori (MAP). Dado que la MAP restringe los valores de los parámetros, se
puede considerar como una versión regularizada de la MLE.
Observe que maximizar la función de verosimilitud es equivalente a maximizar
su logaritmo (representado en el gráfico inferior derecho de la Figura 9­20). De
hecho, el logaritmo es una función estrictamente creciente, por lo que si θ maximiza
el logaritmo de la verosimilitud, también maximiza la verosimilitud.
Machine Translated by Google
Resulta que, en general , es más fácil maximizar la probabilidad logarítmica. Por ejemplo,
si ,
(1)
observas varias instancias independientes x a x, deberás encontrar el valor
de θ
que maximice el producto de las funciones de probabilidad individuales. Pero es
equivalente, y mucho más simple, maximizar la suma (no el producto) de las
funciones de probabilidad logarítmica, gracias a la magia del logaritmo que convierte
los productos en sumas: log(ab)=log(a)+log(b).
Una vez que haya estimado ˆθ, el valor de θ que maximiza la función de
verosimilitud, entonces estará listo para calcular Lˆ = L ( ˆθ, X),
que es el valor utilizado para calcular el AIC y el BIC; puede considerarse como una
medida de qué tan bien el modelo se ajusta a los datos.
Para calcular el BIC y el AIC, llame a los métodos bic() y aic():
>>> gm.bic(X)
8189.74345832983
>>> gm.aic(X)
8102.518178214792
La Figura 9­21 muestra el BIC para diferentes números de clústeres k. Como puede ver,
tanto el BIC como el AIC son más bajos cuando k=3, por lo que es muy probable que sea
la mejor opción. Tenga en cuenta que también podríamos buscar el mejor valor para el
Hiperparámetro covariance_type. Por ejemplo, si es "esférico" en lugar de "completo",
entonces el modelo tiene significativamente menos parámetros para aprender, pero no
se ajusta tan bien a los datos.
Machine Translated by Google
Figura 9­21. AIC y BIC para diferentes números de clústeres k
Modelos de mezcla gaussiana bayesiana
En lugar de buscar manualmente la cantidad óptima de clústeres,
puede utilizar la clase BayesianGaussianMixture, que es capaz de dar
pesos iguales (o cercanos) a cero para grupos innecesarios. Establezca el número de
agrupa n_componentes en un valor que tenga buenas razones para creer que es
mayor que el número óptimo de clústeres (esto supone un mínimo
conocimiento sobre el problema en cuestión), y el algoritmo eliminará
los clústeres innecesarios automáticamente. Por ejemplo, establezcamos el número
de clusters a 10 y ver qué pasa:
>>> de sklearn.mixture importar BayesianGaussianMixture
>>> bgm = BayesianGaussianMixture(n_componentes=10, n_init=10)
>>> bgm.fit(X)
>>> np.round(bgm.pesos_, 2)
, 0.
, 0.
, 0.
, 0.
, 0.
, 0.
matriz([0.4, 0.21, 0.4
, 0. ])
Perfecto: el algoritmo detectó automáticamente que solo hay tres clústeres
necesario, y los grupos resultantes son casi idénticos a los de
Figura 9­17.
En este modelo, los parámetros del clúster (incluidos los pesos, las medias y
Las matrices de covarianza ya no se tratan como parámetros fijos del modelo,
pero como variables aleatorias latentes, como las asignaciones de conglomerados (ver Figura 9­
22) Por lo tanto, z ahora incluye tanto los parámetros del clúster como el clúster.
asignaciones
Machine Translated by Google
La distribución Beta se utiliza comúnmente para modelar variables aleatorias cuyos valores
se encuentran dentro de un rango fijo. En este caso, el rango es de 0 a 1. El Proceso de
Rotura de Bastones (SBP, por sus siglas en inglés) se explica mejor a través de un
ejemplo: supongamos que Φ=[0.3, 0.6, 0.5,…], entonces el 30% de las instancias se
asignarán al grupo 0, luego el 60% de las instancias restantes se asignarán al grupo 1,
luego el 50% de las instancias restantes se asignarán al grupo 2, y así sucesivamente.
Este proceso es un buen modelo para conjuntos de datos donde es más probable que
nuevas instancias se unan a grupos grandes que a grupos pequeños (por ejemplo, es más
probable que las personas se muden a ciudades más grandes). Si la concentración
α es alta, entonces los valores de Φ probablemente estarán cerca de 0 y el SBP generará muchos grupos.
Por el contrario, si la concentración es baja, los valores de Φ probablemente serán
cercanos a 1 y habrá pocos conglomerados. Por último, la distribución de Wishart se
utiliza para muestrear matrices de covarianza: los parámetros d y V controlan la
distribución de las formas de los conglomerados.
Figura 9­22. Modelo de mezcla gaussiana bayesiana
El conocimiento previo sobre las variables latentes z se puede codificar en una
distribución de probabilidad p(z) llamada previa. Por ejemplo, podemos tener una creencia
previa de que es probable que los conglomerados sean pocos (baja concentración) o,
por el contrario, que es probable que sean abundantes (alta concentración). Esta creencia
previa sobre el número de conglomerados se puede ajustar utilizando la
Machine Translated by Google
Hiperparámetro weight_concentration_prior. Si se establece en 0,01 o 10 000, se
obtienen agrupaciones muy diferentes (consulte la Figura 9­23). Sin embargo, cuantos
más datos tengamos, menos importarán los valores a priori. De hecho, para trazar
diagramas con diferencias tan grandes, se deben utilizar valores a priori muy sólidos y pocos datos.
Figura 9­23. El uso de diferentes valores previos de concentración en los mismos datos da como resultado diferentes números de
conglomerados.
El teorema de Bayes (ecuación 9­2) nos dice cómo actualizar la distribución de
probabilidad sobre las variables latentes después de observar algunos datos X.
Calcula la distribución posterior p(z|X), que es la probabilidad condicional de z dado
X.
Ecuación 9­2. Teorema de Bayes
anterior (z|X) = posterior =
probabilidad × p
evidencia
=
p(X|z) p(z) p(X)
Desafortunadamente, en un modelo de mezcla gaussiana (y muchos otros problemas),
el denominador p(x) es intratable, ya que requiere integrar todos los valores posibles de
z (Ecuación 9­3), lo que requeriría considerar todas las combinaciones posibles de
parámetros de clúster y asignaciones de clúster.
Ecuación 9­3. La evidencia p(X) es a menudo intratable
Machine Translated by Google
p(X) = ∫ p(X|z)p(z)dz
Esta intratabilidad es uno de los problemas centrales de la estadística bayesiana, y
existen varios enfoques para resolverla. Uno de ellos es la inferencia variacional,
que elige una familia de distribuciones q(z; λ) con sus propios parámetros
variacionales λ (lambda), y luego optimiza estos parámetros para hacer que q(z)
sea una buena aproximación de p(z|X). Esto se logra encontrando el valor de λ que
minimiza la divergencia KL de q(z) a p(z|X), denotada como D (q
p). La ecuación
de divergencia KL se muestra en la Ecuación 9­4, y puede reescribirse como el
Kuala Lumpur
logaritmo de la evidencia (log p(X)) menos el límite inferior de la evidencia (ELBO).
Dado que el logaritmo de la evidencia no depende de q, es un término constante, por
lo que minimizar la divergencia KL solo requiere maximizar el ELBO.
Ecuación 9­4. Divergencia KL de q(z) a p(z|X)
q(z)
DKL (q
p) = Eq[log p(z | X) ]
= Ecuación[log q(z) − log p(z |
X)] p(z,
= Eq[log q (z) − log X) p(X) ]
= Eq[log q(z) − log p(z, X) + log p(X)]
= Eq[log q(z)]−Eq[log p(z, X)]+Eq[log p(X)]
= Eq[log p(X)]−(Eq[log p(z, X)]−Eq[log q(z)])
= log p(X) − ELBO
donde ELBO = Eq[log p(z, X)]−Eq[log q(z)]
En la práctica, existen diferentes técnicas para maximizar el ELBO. En la inferencia
variacional de campo medio, es necesario elegir la familia de distribuciones
q(z; λ) y la p(z) previa con mucho cuidado para garantizar que la ecuación para
el ELBO se simplifique a una forma que se pueda calcular.
Lamentablemente, no existe una forma general de hacerlo. Elegir la familia adecuada
Machine Translated by Google
La determinación de la distribución y la distribución previa correcta depende de la tarea y requiere
ciertas habilidades matemáticas. Por ejemplo, las distribuciones y las ecuaciones de límite
inferior utilizadas en la clase BayesianGaussianMixture de Scikit­Learn se presentan en la
documentación. A partir de estas ecuaciones es posible derivar ecuaciones de actualización
para los parámetros del cluster y las variables de asignación: estas se utilizan de forma
muy similar al algoritmo Expectation­Maximization. De hecho, la complejidad
computacional de la clase BayesianGaussianMixture es similar a la de la clase
GaussianMixture (pero generalmente significativamente más lenta). Un
enfoque más simple para maximizar el ELBO se llama inferencia variacional estocástica de
caja negra (BBSVI): en cada iteración, se extraen algunas muestras de q, y se utilizan
para estimar los gradientes del ELBO con respecto a los parámetros variacionales λ, que luego
se utilizan en un paso de ascenso del gradiente. Este enfoque permite utilizar la inferencia
bayesiana con cualquier tipo de modelo (siempre que sea diferenciable), incluso redes
neuronales profundas; el uso de la inferencia bayesiana con redes neuronales profundas se
llama aprendizaje profundo bayesiano.
CONSEJO
Si desea profundizar en las estadísticas bayesianas, consulte el libro Bayesian
Data Analysis de Andrew Gelman et al. (Chapman & Hall).
Los modelos de mezcla gaussiana funcionan muy bien en agrupaciones con formas elipsoidales,
pero si intenta ajustar un conjunto de datos con formas diferentes, puede llevarse una
sorpresa desagradable. Por ejemplo, veamos qué sucede si utilizamos un modelo de
mezcla gaussiana bayesiana para agrupar el conjunto de datos de las lunas (consulte la Figura 9­24).
Machine Translated by Google
Figura 9­24. Ajuste de una mezcla gaussiana a cúmulos no elipsoidales
¡Ups! El algoritmo buscó desesperadamente elipsoides, por lo que encontró ocho cúmulos
diferentes en lugar de dos. La estimación de la densidad no es demasiado mala, por lo
que este modelo podría quizás usarse para la detección de anomalías, pero no logró
identificar las dos lunas. Veamos ahora algunos algoritmos de agrupamiento capaces
de lidiar con cúmulos de formas arbitrarias.
Otros algoritmos para la detección de anomalías y novedades
Scikit­Learn implementa otros algoritmos dedicados a la detección de anomalías o detección
de novedades:
PCA (y otras técnicas de reducción de dimensionalidad con un método
inverse_transform())
Si comparamos el error de reconstrucción de una instancia normal con el error de
reconstrucción de una anomalía, este último suele ser mucho mayor. Se trata de
un método de detección de anomalías sencillo y, a menudo, bastante eficaz
(consulte los ejercicios de este capítulo para ver una aplicación de este
método).
MCD rápido (determinante de covarianza mínima)
Implementado por la clase EllipticEnvelope, este algoritmo es útil para la detección de
valores atípicos, en particular para limpiar un conjunto de datos. Supone que las
instancias normales (valores atípicos) se generan a partir de una única distribución
gaussiana (no una mezcla). También supone que el conjunto de datos está
contaminado con valores atípicos que no se generaron a partir de esta distribución gaussiana.
Machine Translated by Google
Distribución. Cuando el algoritmo estima los parámetros de la distribución gaussiana (es decir,
la forma de la envolvente elíptica alrededor de los valores atípicos), tiene cuidado de ignorar los
casos que probablemente sean valores atípicos. Esta técnica brinda una mejor estimación de la
envolvente elíptica y, por lo tanto, hace que el algoritmo sea mejor para identificar los valores
atípicos.
Bosque de aislamiento
Este es un algoritmo eficiente para la detección de valores atípicos, especialmente en conjuntos de
datos de alta dimensión. El algoritmo crea un bosque aleatorio en el que cada árbol de decisión crece
aleatoriamente: en cada nodo, elige una característica aleatoriamente, luego elige un valor umbral
aleatorio (entre los valores mínimo y máximo) para dividir el conjunto de datos en dos. El conjunto de
datos se corta gradualmente en pedazos de esta manera, hasta que todas las instancias
terminan aisladas de las otras instancias. Las anomalías generalmente están lejos de otras instancias,
por lo que en promedio (en todos los árboles de decisión) tienden a aislarse en menos pasos que las
instancias normales.
Factor de valor atípico local (LOF)
Este algoritmo también es útil para la detección de valores atípicos. Compara la densidad de
instancias alrededor de una instancia dada con la densidad alrededor de sus vecinos. Una anomalía
suele estar más aislada que sus k vecinos más cercanos.
SVM de una sola clase
Este algoritmo es más adecuado para la detección de novedades. Recordemos que un
clasificador SVM kernelizado separa dos clases asignando primero (implícitamente) todas las
instancias a un espacio de alta dimensión y luego separando las dos clases utilizando un clasificador
SVM lineal dentro de este espacio de alta dimensión (véase el Capítulo 5). Dado que solo
tenemos una clase de instancias, el algoritmo SVM de una clase intenta separar las instancias en
el espacio de alta dimensión desde el origen. En el espacio original, esto corresponderá a encontrar
una pequeña región que abarque todas las instancias. Si una nueva instancia no cae dentro de
esta región, se
Machine Translated by Google
una anomalía. Hay algunos hiperparámetros que ajustar: los habituales para un SVM
kernelizado, más un hiperparámetro de margen que corresponde a la probabilidad
de que una nueva instancia sea considerada por error como novedosa cuando
en realidad es normal. Funciona muy bien, especialmente con conjuntos de datos
de alta dimensión, pero como todos los SVM, no se escala a conjuntos de
datos grandes.
Ceremonias
1. ¿Cómo definirías la agrupación? ¿Puedes nombrar algunas agrupaciones?
¿algoritmos?
2. ¿Cuáles son algunas de las principales aplicaciones de los algoritmos de clusterización?
3. Describe dos técnicas para seleccionar el número correcto de clústeres.
al utilizar K­Means.
4. ¿Qué es la propagación de etiquetas? ¿Por qué debería implementarse?
¿cómo?
5. ¿Puede nombrar dos algoritmos de agrupamiento que puedan escalarse a grandes
conjuntos de datos? ¿Y dos que busquen regiones de alta densidad?
6. ¿Se te ocurre algún caso práctico en el que el aprendizaje activo resultaría útil?
¿Cómo lo implementarías?
7. ¿Cuál es la diferencia entre detección de anomalías y novedad?
¿detección?
8. ¿Qué es una mezcla gaussiana? ¿Para qué tareas se puede utilizar?
9. ¿Puedes nombrar dos técnicas para encontrar el número correcto de clústeres?
¿Cuándo se utiliza un modelo de mezcla gaussiana?
10. El conjunto de datos de rostros clásicos de Olivetti contiene 400 imágenes de rostros en
escala de grises de 64 × 64 píxeles. Cada imagen se aplana a un vector unidimensional
de tamaño 4096. Se fotografiaron 40 personas diferentes (10 veces cada una) y
la tarea habitual es entrenar un modelo que pueda predecir qué
Machine Translated by Google
En cada imagen se representa a una persona. Cargue el conjunto de datos con la
función sklearn.datasets.fetch_olivetti_faces() y luego divídalo en un conjunto de
entrenamiento, un conjunto de validación y un conjunto de prueba (tenga en cuenta que el
conjunto de datos ya está escalado entre 0 y 1). Dado que el conjunto de datos es bastante
pequeño, probablemente desee utilizar un muestreo estratificado para asegurarse
de que haya la misma cantidad de imágenes por persona en cada conjunto. A
continuación, agrupe las imágenes con K­Means y asegúrese de tener una buena cantidad
de grupos (utilizando una de las técnicas analizadas en este capítulo). Visualice los
grupos: ¿ve caras similares en cada grupo?
11. Continuando con el conjunto de datos de caras de Olivetti, entrene un clasificador para
Predecir qué persona está representada en cada imagen y evaluarla en el conjunto de
validación. A continuación, utilizar K­Means como herramienta de reducción de
dimensionalidad y entrenar un clasificador en el conjunto reducido. Buscar la cantidad de
clústeres que le permita al clasificador obtener el mejor rendimiento: ¿qué rendimiento
se puede alcanzar? ¿Qué sucede si se agregan las características del conjunto
reducido a las características originales (nuevamente, buscando la mejor cantidad de
clústeres)?
12. Entrene un modelo de mezcla gaussiana en el conjunto de datos de caras de Olivetti.
Para acelerar el algoritmo, probablemente debería reducir la dimensionalidad del conjunto
de datos (por ejemplo, usar PCA, preservando el 99% de la varianza).
Utilice el modelo para generar algunas caras nuevas (utilizando el método sample()) y
visualícelas (si utilizó PCA, deberá utilizar su método inverse_transform()). Intente
modificar algunas imágenes (por ejemplo, rotarlas, voltearlas, oscurecerlas) y vea
si el modelo puede detectar las anomalías (es decir, compare la salida del método
score_samples() para imágenes normales y para anomalías).
13. También se pueden utilizar algunas técnicas de reducción de dimensionalidad para la
detección de anomalías. Por ejemplo, tome el conjunto de datos de rostros de Olivetti
y redúzcalo con PCA, conservando el 99 % de la varianza. Luego, calcule el error de
reconstrucción para cada imagen. A continuación, tome algunas de las imágenes
modificadas que creó en el ejercicio anterior y
Machine Translated by Google
Observa el error de reconstrucción: observa cuánto mayor es el error de
reconstrucción. Si trazas una imagen reconstruida, verás por qué: intenta reconstruir
un rostro normal.
Las soluciones a estos ejercicios están disponibles en el Apéndice A.
1 Stuart P. Lloyd, “Cuantización de mínimos cuadrados en PCM”, IEEE Transactions on Information
Teoría 28, no. 2 (1982): 129–137.
2 Esto se debe a que la distancia cuadrática media entre las instancias y su centroide más cercano
Sólo se puede bajar en cada paso.
3 David Arthur y Sergei Vassilvitskii, “k­Means++: Las ventajas de una siembra cuidadosa”,
Actas del 18º Simposio Anual ACM­SIAM sobre Algoritmos Discretos (2007): 1027–1035.
4 Charles Elkan, “Uso de la desigualdad triangular para acelerar k­medias”, Actas de la
20ª Conferencia Internacional sobre Aprendizaje Automático (2003): 147–153.
5 La desigualdad triangular es AC ≤ AB + BC donde A, B y C son tres puntos y AB, AC,
y BC son las distancias entre estos puntos.
6 David Sculley, “Web­Scale K­Means Clustering”, Actas de la 19.ª Conferencia Internacional
Conferencia sobre la World Wide Web (2010): 1177–1178.
7 phi (
o φ) es la letra número 21 del alfabeto griego.
8 La mayoría de estas notaciones son estándar, pero se tomaron algunas notaciones adicionales de
Artículo de Wikipedia sobre notación de placas.
Machine Translated by Google
Parte II. Redes neuronales y
aprendizaje profundo
Machine Translated by Google
Capítulo 10. Introducción a las
redes neuronales artificiales con Keras
Los pájaros nos inspiraron a volar, las plantas de bardana inspiraron el velcro y la
naturaleza ha inspirado innumerables inventos más. Parece lógico, entonces, observar
la arquitectura del cerebro en busca de inspiración para construir una máquina
inteligente. Esta es la lógica que dio origen a las redes neuronales artificiales (RNA): una
RNA es un modelo de aprendizaje automático inspirado en las redes de
neuronas biológicas que se encuentran en nuestros cerebros. Sin embargo, aunque
los aviones se inspiraron en los pájaros, no tienen por qué batir sus alas. De manera
similar, las RNA se han ido diferenciando gradualmente de sus primas biológicas. Algunos
investigadores incluso sostienen que deberíamos abandonar por completo la
analogía biológica (por ejemplo, diciendo "unidades" en lugar de "neuronas"), para no
1
restringir nuestra creatividad a sistemas biológicamente plausibles.
Las ANN son el núcleo del aprendizaje profundo. Son versátiles, potentes y escalables,
lo que las hace ideales para abordar tareas de aprendizaje automático grandes
y muy complejas, como clasificar miles de millones de imágenes (por ejemplo,
Google Images), impulsar servicios de reconocimiento de voz (por ejemplo, Siri de
Apple), recomendar los mejores videos para ver a cientos de millones de usuarios
todos los días (por ejemplo, YouTube) o aprender a vencer al campeón mundial en el
juego Go (AlphaGo de DeepMind).
La primera parte de este capítulo presenta las redes neuronales artificiales, comenzando
con un recorrido rápido por las primeras arquitecturas de ANN y llegando hasta los
perceptrones multicapa (MLP), que se utilizan ampliamente en la actualidad (se
explorarán otras arquitecturas en los próximos capítulos). En la segunda parte, veremos
cómo implementar redes neuronales utilizando la popular API de Keras. Se trata de
una API de alto nivel, hermosamente diseñada y sencilla para construir, entrenar, evaluar
y ejecutar redes neuronales. Pero no se deje engañar por
Machine Translated by Google
Su simplicidad: es lo suficientemente expresiva y flexible como para permitirle construir una
amplia variedad de arquitecturas de redes neuronales. De hecho, probablemente
será suficiente para la mayoría de sus casos de uso. Y si alguna vez necesita flexibilidad
adicional, siempre puede escribir componentes Keras personalizados utilizando su
API de nivel inferior, como veremos en el Capítulo 12.
¡Pero primero, retrocedamos en el tiempo para ver cómo surgieron las redes neuronales
artificiales!
De las neuronas biológicas a las neuronas artificiales Sorprendentemente,
las ANN existen desde hace bastante tiempo: fueron introducidas por primera vez en
1943 por el neurofisiólogo Warren McCulloch y el matemático Walter Pitts. En su artículo de
2
referencia En “Un cálculo lógico de ideas inmanentes en la actividad nerviosa”,
McCulloch y Pitts presentaron un modelo computacional simplificado de cómo las neuronas
biológicas podrían trabajar juntas en los cerebros animales para realizar cálculos
complejos utilizando la lógica proposicional. Esta fue la primera arquitectura de red neuronal
artificial. Desde entonces se han inventado muchas otras arquitecturas, como veremos.
Los primeros éxitos de las ANN llevaron a la creencia generalizada de que pronto
estaríamos conversando con máquinas verdaderamente inteligentes. Cuando en la década
de 1960 se hizo evidente que esta promesa no se cumpliría (al menos por un tiempo), la
financiación se fue a otro lado y las ANN entraron en un largo invierno. A principios de la
década de 1980, se inventaron nuevas arquitecturas y se desarrollaron mejores
técnicas de entrenamiento, lo que provocó un resurgimiento del interés por el conexionismo
(el estudio de las redes neuronales). Pero el progreso fue lento y en la década de 1990 se
inventaron otras técnicas poderosas de aprendizaje automático, como las máquinas
de vectores de soporte (véase el capítulo 5). Estas técnicas parecían ofrecer mejores
resultados y fundamentos teóricos más sólidos que las ANN, por lo que una vez más el
estudio de las redes neuronales quedó en suspenso.
Ahora estamos presenciando otra ola de interés en las ANN. ¿Se extinguirá esta ola
como lo hicieron las anteriores? Bueno, aquí hay algunas buenas razones
Machine Translated by Google
creer que esta vez es diferente y que el renovado interés en las ANN tendrá un impacto mucho
más profundo en nuestras vidas:
Actualmente hay una enorme cantidad de datos disponibles para entrenar redes
neuronales, y las ANN con frecuencia superan otras técnicas de ML en problemas
muy grandes y complejos.
El enorme aumento de la potencia de procesamiento desde los años 90 permite entrenar
redes neuronales de gran tamaño en un tiempo razonable. Esto se debe en parte a
la ley de Moore (el número de componentes de los circuitos integrados se ha duplicado
aproximadamente cada dos años durante los últimos 50 años), pero también a la
industria de los videojuegos, que ha estimulado la producción de millones de
potentes tarjetas GPU. Además, las plataformas en la nube han hecho que esta
potencia sea accesible para todos.
Se han mejorado los algoritmos de entrenamiento. Para ser justos, solo son
ligeramente diferentes de los que se usaban en los años 90, pero estos cambios
relativamente pequeños han tenido un gran impacto positivo.
Algunas limitaciones teóricas de las ANN han resultado ser benignas en la
práctica. Por ejemplo, muchas personas pensaban que los algoritmos de
entrenamiento de las ANN estaban condenados al fracaso porque era probable que
se quedaran estancados en óptimos locales, pero resulta que esto es bastante
raro en la práctica (y cuando es el caso, suelen estar bastante cerca del óptimo
global).
Las ANN parecen haber entrado en un círculo virtuoso de financiación y
progreso. Los productos sorprendentes basados en ANN aparecen con regularidad
en los titulares de las noticias, lo que atrae cada vez más atención y financiación
hacia ellas, lo que da lugar a más y más progreso y a productos aún más sorprendentes.
Neuronas biológicas Antes de
analizar las neuronas artificiales, echemos un vistazo rápido a una neurona biológica (representada
en la Figura 10­1). Es una célula de aspecto inusual que en su mayoría está formada por células
Machine Translated by Google
Se encuentra en los cerebros de los animales. Está compuesta por un cuerpo celular
que contiene el núcleo y la mayoría de los componentes complejos de la célula,
muchas extensiones ramificadas llamadas dendritas, más una extensión muy larga
llamada axón. La longitud del axón puede ser solo unas pocas veces más larga que
el cuerpo celular, o hasta decenas de miles de veces más larga. Cerca de su extremo,
el axón se divide en muchas ramas llamadas telodendrias, y en la punta de estas ramas
hay estructuras minúsculas llamadas terminales sinápticas (o simplemente
3
sinapsis), que están conectadas a las dendritas o cuerpos celulares de otras
neuronas. Las neuronas biológicas producen impulsos eléctricos cortos llamados
potenciales de acción (PA, o simplemente señales) que viajan a lo largo de los
axones y hacen que las sinapsis liberen señales químicas llamadas
neurotransmisores. Cuando una neurona recibe una cantidad suficiente de estos
neurotransmisores en unos pocos milisegundos, dispara sus propios
impulsos eléctricos (en realidad, depende de los neurotransmisores, ya que algunos de ellos inhiben el
Figura 10­1. Neurona biológica4
Así, las neuronas biológicas individuales parecen comportarse de una manera bastante
simple, pero están organizadas en una vasta red de miles de millones de
neuronas, en la que cada neurona suele estar conectada a miles de otras neuronas.
Machine Translated by Google
Los cálculos pueden ser realizados por una red de neuronas bastante simples, de
manera muy similar a cómo un hormiguero complejo puede surgir de los esfuerzos
5
combinados de hormigas simples. La arquitectura de las redes neuronales biológicas
(BNN, por sus siglas en inglés) aún es objeto de investigación activa, pero se han
mapeado algunas partes del cerebro y parece que las neuronas a menudo se
organizan en capas consecutivas, especialmente en la corteza cerebral (es decir, la capa
externa del cerebro), como se muestra en la Figura 10­2.
Figura 10­2. Múltiples capas en una red neuronal biológica (corteza humana)
6
Cálculos lógicos con neuronas
McCulloch y Pitts propusieron un modelo muy simple de la neurona biológica, que
más tarde se conocería como neurona artificial: tiene una o más entradas binarias
(encendido/apagado) y una salida binaria. La neurona artificial activa su salida cuando
más de un cierto número de sus entradas están activas. En su artículo, demostraron
que incluso con un modelo tan simplificado es posible construir una red de neuronas
artificiales que calcule cualquier proposición lógica que se desee. Para ver cómo
funciona una red de este tipo, construyamos algunas ANN que realicen varios cálculos
lógicos (consulte la Figura 10­3), suponiendo que una neurona se activa cuando al menos
dos de sus entradas están activas.
Machine Translated by Google
Figura 10­3. ANN realizando cálculos lógicos simples
Veamos qué hacen estas redes:
La primera red a la izquierda es la función identidad: si la neurona A está
activada, entonces la neurona C también se activa (ya que recibe dos señales
de entrada de la neurona A); pero si la neurona A está apagada, entonces
la neurona C también está apagada.
La segunda red realiza un AND lógico: la neurona C se activa solo
cuando ambas neuronas A y B se activan (una sola señal de entrada no es
suficiente para activar la neurona C).
La tercera red realiza un OR lógico: la neurona C se activa si se activa la
neurona A o la neurona B (o ambas).
Finalmente, si suponemos que una conexión de entrada puede inhibir la
actividad de la neurona (que es el caso de las neuronas biológicas), entonces la
cuarta red calcula una proposición lógica ligeramente más compleja: la
neurona C se activa solo si la neurona A está activa y la neurona B está
apagada. Si la neurona A está activa todo el tiempo, entonces se obtiene un
NO lógico: la neurona C está activa cuando la neurona B está apagada, y viceversa.
viceversa.
Puedes imaginar cómo se pueden combinar estas redes para calcular expresiones
lógicas complejas (consulta los ejercicios al final del capítulo para ver un ejemplo).
Machine Translated by Google
El perceptrón El
perceptrón es una de las arquitecturas de ANN más simples, inventada en 1957 por Frank
Rosenblatt. Se basa en una neurona artificial ligeramente diferente (ver Figura 10­4)
llamada unidad lógica de umbral (TLU), o a veces unidad de umbral lineal (LTU). Las
entradas y salidas son números (en lugar de valores binarios de encendido/apagado), y cada
conexión de entrada está asociada con un peso. La TLU calcula una suma ponderada
de sus entradas (z = wx + wx +
1 a1 esa 2 2
+ wx = x w), luego aplica una función de paso
nn el resultado: h (x) = paso(z), donde z
suma y genera
= x a.
el
Figura 10­4. Unidad lógica de umbral: una neurona artificial que calcula una suma ponderada de sus entradas
y luego aplica una función escalonada.
La función escalonada más común que se utiliza en los perceptrones es la función
escalonada de Heaviside (véase la ecuación 10­1). A veces se utiliza en su lugar la función de signo.
Ecuación 10­1. Funciones de paso comunes utilizadas en perceptrones (suponiendo que el umbral es 0)
−1 si z < 0 0
lado pesado (z) = { 01sisizz<≥00
signo (z) =
si z = 0
+1 si z > 0
Machine Translated by Google
Se puede utilizar una única TLU para una clasificación binaria lineal simple. Calcula una combinación lineal de las entradas y,
si el resultado supera un umbral, genera la clase positiva. De lo contrario, genera la clase negativa (como una regresión logística o un
clasificador SVM lineal). Por ejemplo, se podría utilizar una única TLU para clasificar las flores de iris en función de la longitud y el ancho
de los pétalos (añadiendo también una característica de sesgo adicional x = 1, como hicimos en los capítulos anteriores). Entrenar una TLU
en este caso significa encontrar los valores correctos para ww 0 1
0
,
, y w (el2algoritmo de entrenamiento se analiza en breve).
7
Un perceptrón se compone simplemente de una sola capa de TLU, cada una de las
cuales está conectada a todas las entradas. Cuando todas las neuronas de una
capa están conectadas a cada neurona de la capa anterior (es decir, sus neuronas
de entrada), la capa se denomina capa completamente conectada o capa densa. Las
entradas del perceptrón se envían a neuronas de paso especiales llamadas
neuronas de entrada: emiten cualquier entrada que se les envíe. Todas las neuronas
de entrada forman la capa de entrada. Además, generalmente se agrega una
característica
de sesgo adicional (x = 1): generalmente se representa utilizando un tipo
0
especial de neurona llamada neurona de sesgo, que emite 1 todo el tiempo. Un
perceptrón con dos entradas y tres salidas se representa en la Figura 10­5. Este
perceptrón puede clasificar instancias simultáneamente en tres clases binarias diferentes,
lo que lo convierte en un clasificador de múltiples salidas.
Machine Translated by Google
Figura 10­5. Arquitectura de un perceptrón con dos neuronas de entrada, una neurona de polarización y tres
neuronas de salida
Gracias a la magia del álgebra lineal, la ecuación 10­2 permite calcular eficientemente las
salidas de una capa de neuronas artificiales para varias instancias a la vez.
Ecuación 10­2. Cálculo de las salidas de una capa completamente conectada
hW,b (X) =
(XW + b)
En esta ecuación:
Como siempre, X representa la matriz de características de entrada. Tiene
una fila por instancia y una columna por característica.
La matriz de ponderaciones W contiene todas las ponderaciones de conexión,
excepto las de la neurona de polarización. Tiene una fila por neurona de entrada
y una columna por neurona artificial en la capa.
El vector de polarización b contiene todos los pesos de conexión entre la neurona
de polarización y las neuronas artificiales. Tiene un término de polarización
por neurona artificial.
Machine Translated by Google
La función
se llama función de activación: cuando las neuronas
artificiales son TLU, es una función escalonada (pero discutiremos otras
funciones de activación en breve).
Entonces, ¿cómo se entrena un perceptrón? El algoritmo de entrenamiento del
perceptrón propuesto por Rosenblatt se inspiró en gran medida en la regla de Hebb.
En su libro de 1949 The Organization of Behavior (La organización del comportamiento )
(Wiley), Donald Hebb sugirió que cuando una neurona biológica activa a otra neurona
con frecuencia, la conexión entre estas dos neuronas se hace más fuerte.
Siegrid Löwel resumió más tarde la idea de Hebb en la pegadiza frase: "Las células
que se activan juntas, se conectan entre sí"; es decir, el peso de la conexión entre dos
neuronas tiende a aumentar cuando se activan simultáneamente. Esta regla más tarde se
conoció como la regla de Hebb (o aprendizaje hebbiano). Los perceptrones se entrenan
utilizando una variante de esta regla que tiene en cuenta el error cometido por la red
cuando hace una predicción; la regla de aprendizaje del perceptrón refuerza las
conexiones que ayudan a reducir el error. Más específicamente, el perceptrón se
alimenta con una instancia de entrenamiento a la vez, y para cada instancia hace sus predicciones.
Por cada neurona de salida que produjo una predicción errónea, se refuerzan los pesos
de conexión de las entradas que habrían contribuido a la predicción correcta. La
regla se muestra en la ecuación 10­3.
Ecuación 10­3. Regla de aprendizaje del perceptrón (actualización de peso)
(siguiente
paso) wi,j
= wi,j + η(yj − yˆj) xi
En esta ecuación:
El
w es el peso de la conexión entre la neurona de entrada
i y la
i, j
th
j neurona de salida.
x ies el valorEl de entrada i de la instancia de entrenamiento actual.
El
yˆj es la salida de la neurona de salida j para el entrenamiento actual
instancia.
Machine Translated by Google
El
y yoes la salida de destino de la neurona de salida j para la instancia de
entrenamiento actual.
η es la tasa de aprendizaje.
El límite de decisión de cada neurona de salida es lineal, por lo que los perceptrones son
incapaces de aprender patrones complejos (al igual que los clasificadores de regresión
logística). Sin embargo, si las instancias de entrenamiento son linealmente separables,
Rosenblatt demostró que este algoritmo convergería hacia una solución. Esto se
8
denomina teorema de convergencia del perceptrón.
Scikit­Learn ofrece una clase Perceptron que implementa una red de una sola TLU. Se puede
utilizar prácticamente como cabría esperar, por ejemplo, en el conjunto de datos del iris
(presentado en el Capítulo 4):
importar numpy como
np desde sklearn.datasets importar load_iris desde
sklearn.linear_model importar Perceptron
iris = cargar_iris()
X = iris.datos[:, (2, 3)] y =
(iris.objetivo == 0).astype(np.int)
largo del pétalo, ancho del pétalo # # Iris
setosa?
per_clf = Perceptrón()
per_clf.fit(X, y)
y_pred = per_clf.predict([[2, 0.5]])
Es posible que haya notado que el algoritmo de aprendizaje del perceptrón se parece
mucho al algoritmo de descenso de gradiente estocástico. De hecho, la clase Perceptrón de
Scikit­Learn es equivalente a usar un SGDClassifier con los siguientes hiperparámetros:
loss="perceptron", learning_rate="constant", eta0=1 (la tasa de aprendizaje) y penalty=None
(sin regularización).
Tenga en cuenta que, a diferencia de los clasificadores de regresión logística, los perceptrones
no generan una probabilidad de clase, sino que hacen predicciones basadas en un umbral
estricto. Esta es una de las razones para preferir la regresión logística a los
perceptrones.
Machine Translated by Google
En su monografía Perceptrones de 1969, Marvin Minsky y Seymour Papert
destacaron una serie de debilidades graves de los perceptrones, en particular, el
hecho de que son incapaces de resolver algunos problemas triviales (por ejemplo, el
problema de clasificación OR exclusivo (XOR); vea el lado izquierdo de la Figura
10­6). Esto es cierto para cualquier otro modelo de clasificación lineal (como los
clasificadores de regresión logística), pero los investigadores esperaban mucho más
de los perceptrones, y algunos estaban tan decepcionados que abandonaron las
redes neuronales por completo a favor de problemas de nivel superior como
la lógica, la resolución de problemas y la búsqueda.
Resulta que algunas de las limitaciones de los perceptrones se pueden eliminar
apilando varios perceptrones. La red neuronal artificial resultante se
denomina perceptrón multicapa (MLP). Un MLP puede resolver el problema XOR,
como se puede verificar calculando la salida del MLP representado en el lado
derecho de la Figura 10­6: con entradas (0, 0) o (1, 1), la red genera 0, y con
entradas (0, 1) o (1, 0) genera 1. Todas las conexiones tienen un peso igual a
1, excepto las cuatro conexiones en las que se muestra el peso.
¡Intenta verificar que esta red realmente resuelve el problema XOR!
Figura 10­6. Problema de clasificación XOR y un MLP que lo resuelve
Machine Translated by Google
El perceptrón multicapa y la retropropagación Un perceptrón multicapa
se compone de una capa de entrada (de paso), una o más capas de TLU, llamadas
capas ocultas, y una capa final de TLU llamada capa de salida (consulte la
Figura 10­7). Las capas cercanas a la capa de entrada se denominan
generalmente capas inferiores, y las cercanas a las salidas se denominan
generalmente capas superiores. Cada capa, excepto la capa de salida,
incluye una neurona de polarización y está completamente conectada a la siguiente capa.
Figura 10­7. Arquitectura de un perceptrón multicapa con dos entradas, una capa oculta de cuatro neuronas y
tres neuronas de salida (las neuronas de polarización se muestran aquí, pero por lo general están
implícitas)
NOTA
La señal fluye solo en una dirección (de las entradas a las salidas), por lo que esta
arquitectura es un ejemplo de una red neuronal de propagación hacia adelante (FNN).
Machine Translated by Google
9
Cuando una ANN contiene una pila profunda de capas ocultas, se denomina red
neuronal profunda (DNN). El campo del aprendizaje profundo estudia las DNN y,
en términos más generales, los modelos que contienen pilas profundas de cálculos.
Aun así, muchas personas hablan de aprendizaje profundo cuando se trata de
redes neuronales (incluso las superficiales).
Durante muchos años, los investigadores lucharon sin éxito por encontrar una forma
de entrenar a los MLP. Pero en 1986, David Rumelhart, Geoffrey Hinton y Ronald
10
Williams publicaron un artículo innovador que introdujo el algoritmo de
entrenamiento de retropropagación , que todavía se utiliza hoy en día. En resumen,
es el Descenso de Gradiente (introducido en el Capítulo 4) que utiliza una técnica
11
eficiente para calcular los gradientes automáticamente: en solo dos pasadas a través
de la red (una hacia adelante, una hacia atrás), el algoritmo de retropropagación es
capaz de calcular el gradiente del error de la red con respecto a cada parámetro del
modelo. En otras palabras, puede averiguar cómo se debe ajustar cada
peso de conexión y cada término de sesgo para reducir el error. Una vez que tiene
estos gradientes, simplemente realiza un paso de Descenso de Gradiente normal, y
todo el proceso se repite hasta que la red converge a la solución.
NOTA
El cálculo automático de gradientes se denomina diferenciación automática o autodif .
Existen varias técnicas de autodif, con diferentes ventajas y desventajas. La que se utiliza con
retropropagación se denomina autodif en modo inverso .
Es rápido y preciso, y es adecuado
cuando la función a diferenciar tiene muchas variables (por ejemplo, pesos de conexión) y pocas
salidas (por ejemplo, una pérdida). Si desea obtener más información sobre la función de diferenciación
automática, consulte el Apéndice D.
Repasemos este algoritmo con un poco más de detalle:
Maneja un minilote a la vez (por ejemplo, cada uno con 32 instancias) y
recorre todo el conjunto de entrenamiento varias veces. Cada pasada se
denomina época.
Machine Translated by Google
Cada minilote se pasa a la capa de entrada de la red, que lo envía a la
primera capa oculta. A continuación, el algoritmo calcula la salida de todas
las neuronas de esta capa (para cada instancia del minilote). El resultado
se pasa a la siguiente capa, se calcula su salida y se pasa a la siguiente
capa, y así sucesivamente hasta que obtenemos la salida de la última capa,
la capa de salida. Este es el paso hacia delante: es exactamente como hacer
predicciones, excepto que se conservan todos los resultados
intermedios, ya que son necesarios para el paso hacia atrás.
A continuación, el algoritmo mide el error de salida de la red (es decir,
utiliza una función de pérdida que compara la salida deseada y la salida
real de la red y devuelve alguna medida del error).
Luego, calcula cuánto contribuyó cada conexión de salida al error. Esto se
hace analíticamente aplicando la regla de la cadena (quizás la regla más
fundamental del cálculo), lo que hace que este paso sea rápido y preciso.
El algoritmo mide entonces qué proporción de estas
contribuciones de error provienen de cada conexión en la capa inferior,
nuevamente utilizando la regla de la cadena, trabajando hacia atrás
hasta que el algoritmo llega a la capa de entrada. Como se explicó
anteriormente, este paso inverso mide de manera eficiente el gradiente de
error en todos los pesos de conexión en la red al propagarlo hacia
atrás a través de la red (de ahí el nombre del algoritmo).
Finalmente, el algoritmo realiza un paso de descenso de gradiente para
ajustar todos los pesos de conexión en la red, utilizando los
gradientes de error que acaba de calcular.
Este algoritmo es tan importante que vale la pena resumirlo nuevamente: para cada
instancia de entrenamiento, el algoritmo de retropropagación primero hace una
predicción (paso hacia adelante) y mide el error, luego recorre cada capa en sentido
inverso para medir la contribución del error de cada conexión.
Machine Translated by Google
(paso inverso) y, finalmente, ajusta los pesos de la conexión para reducir el error
(paso de descenso de gradiente).
ADVERTENCIA
Es importante inicializar los pesos de conexión de todas las capas ocultas de forma aleatoria, o de
lo contrario el entrenamiento fallará. Por ejemplo, si inicializa todos los pesos y sesgos a cero,
entonces todas las neuronas en una capa dada serán perfectamente idénticas y,
por lo tanto, la retropropagación las afectará exactamente de la misma manera, por lo que
permanecerán idénticas. En otras palabras, a pesar de tener cientos de neuronas por capa, su modelo
actuará como si tuviera solo una neurona por capa: no será demasiado inteligente. Si, en cambio,
inicializa los pesos de forma aleatoria, rompe la simetría y permite que la retropropagación entrene a un
equipo diverso de neuronas.
Para que este algoritmo funcione correctamente, sus autores realizaron un cambio clave
en la arquitectura del MLP: reemplazaron la función de paso por la función logística
(sigmoidea), σ(z) = 1 / (1 + exp(–z)). Esto fue esencial porque la función de paso contiene
solo segmentos planos, por lo que no hay gradiente con el que trabajar (Gradient
Descent no puede moverse sobre una superficie plana), mientras que la función logística
tiene una derivada distinta de cero bien definida en todas partes, lo que permite
que Gradient Descent avance en cada paso. De hecho, el algoritmo de
retropropagación funciona bien con muchas otras funciones de activación, no
solo con la función logística. A continuación se presentan otras dos opciones
populares:
La función tangente hiperbólica : tanh(z) = 2σ(2z) – 1
Al igual que la función logística, esta función de activación tiene forma de S, es
continua y diferenciable, pero su valor de salida varía de –1 a 1 (en lugar de 0 a 1 en
el caso de la función logística). Ese rango tiende a hacer que la salida de cada capa
esté más o menos centrada en 0 al comienzo del entrenamiento, lo que a
menudo ayuda a acelerar la convergencia.
Función unitaria lineal rectificada: ReLU(z) = max(0, z)
La función ReLU es continua pero lamentablemente no diferenciable en z
= 0 (la pendiente cambia abruptamente, lo que puede provocar un descenso del gradiente)
Machine Translated by Google
rebotan), y su derivada es 0 para z < 0. En la práctica, sin embargo, funciona muy
bien y tiene la ventaja de ser rápido de calcular, por lo que se ha convertido en el valor
12
predeterminado. Lo más importante
es que el hecho de que no tenga un valor de
salida máximo ayuda a reducir algunos problemas durante el descenso de
gradiente (volveremos a esto en el Capítulo 11).
Estas funciones de activación populares y sus derivadas se representan en la Figura 10­8.
¡Pero espere! ¿Por qué necesitamos funciones de activación en primer lugar? Bueno,
si encadena varias transformaciones lineales, todo lo que obtiene es una transformación
lineal. Por ejemplo, si f(x) = 2x + 3 y g(x) = 5x – 1, entonces encadenar estas dos funciones
lineales le da otra función lineal: f(g(x)) = 2(5x – 1) + 3 = 10x + 1. Entonces, si no
tiene algo de no linealidad entre capas, entonces incluso una pila profunda de capas es
equivalente a una sola capa, y no puede resolver problemas muy complejos con eso.
Por el contrario, una DNN lo suficientemente grande con activaciones no lineales puede
aproximarse teóricamente a cualquier función continua.
Figura 10­8. Funciones de activación y sus derivadas
¡Bien! Ya sabes de dónde provienen las redes neuronales, cuál es su arquitectura y
cómo calcular sus resultados. También has aprendido sobre el algoritmo de
retropropagación. Pero, ¿qué puedes hacer exactamente con ellas?
MLP de regresión
En primer lugar, las MLP se pueden utilizar para tareas de regresión. Si desea
predecir un único valor (por ejemplo, el precio de una casa, dadas muchas de sus
características), entonces solo necesita una única neurona de salida: su salida es el valor predicho.
Machine Translated by Google
En la regresión multivariante (es decir, para predecir varios valores a la vez), se necesita
una neurona de salida por dimensión de salida. Por ejemplo, para ubicar el centro de un
objeto en una imagen, se necesitan coordenadas 2D, por lo que se necesitan dos
neuronas de salida. Si también se desea colocar un cuadro delimitador alrededor del
objeto, se necesitan dos números más: el ancho y la altura del objeto. Por lo tanto, se
obtienen cuatro neuronas de salida.
En general, al construir un MLP para regresión, no desea utilizar ninguna función de
activación para las neuronas de salida, por lo que son libres de generar cualquier rango
de valores. Si desea garantizar que la salida siempre será positiva, puede utilizar la
función de activación ReLU en la capa de salida. Alternativamente, puede utilizar la
función de activación softplus , que es una variante suave de ReLU: softplus(z) = log(1 +
exp(z)). Está cerca de 0 cuando z es negativo y cerca de z cuando z es positivo.
Finalmente, si desea garantizar que las predicciones caerán dentro de un rango dado de
valores, puede utilizar la función logística o la tangente hiperbólica y luego escalar las
etiquetas al rango apropiado: 0 a 1 para la función logística y ­1 a 1 para la tangente
hiperbólica.
La función de pérdida que se utiliza durante el entrenamiento suele ser el error
cuadrático medio, pero si hay muchos valores atípicos en el conjunto de entrenamiento, es
posible que prefiera utilizar el error absoluto medio. Como alternativa, puede utilizar la
pérdida de Huber, que es una combinación de ambos.
CONSEJO
La pérdida de Huber es cuadrática cuando el error es menor que un umbral δ (normalmente
1), pero lineal cuando el error es mayor que δ. La parte lineal la hace menos sensible a
los valores atípicos que el error cuadrático medio, y la parte cuadrática le permite
converger más rápido y ser más precisa que el error absoluto medio.
La Tabla 10­1 resume la arquitectura típica de una regresión MLP.
Machine Translated by Google
Tabla 10­1. Arquitectura típica de regresión MLP
Hiperparámetro
Valor típico
# neuronas de entrada
Una por característica de entrada (por ejemplo, 28 x 28 = 784 para MNIST)
# capas ocultas
Depende del problema, pero normalmente de 1 a 5.
# neuronas por capa oculta
Depende del problema, pero normalmente entre 10 y 100.
# neuronas de salida
1 por dimensión de predicción
Activación oculta
ReLU (o SELU, ver Capítulo 11)
Activación de salida
Función de pérdida
Ninguno, o ReLU/softplus (si hay salidas positivas) o logístico/tanh (si hay salidas
acotadas)
MSE o MAE/Huber (si hay valores atípicos)
Clasificación de las MLP
Las MLP también se pueden utilizar para tareas de clasificación. Para un problema de
clasificación binaria, solo se necesita una única neurona de salida que utilice la función de
activación logística: la salida será un número entre 0 y 1, que se puede interpretar
como la probabilidad estimada de la clase positiva. La probabilidad estimada de la clase
negativa es igual a uno menos ese número.
Los MLP también pueden manejar fácilmente tareas de clasificación binaria de múltiples
etiquetas (ver Capítulo 3). Por ejemplo, podría tener un sistema de clasificación de
correo electrónico que prediga si cada correo electrónico entrante es spam o
correo no deseado, y simultáneamente prediga si es un correo electrónico urgente o no
urgente. En este caso, necesitaría dos neuronas de salida, ambas utilizando la
función de activación logística: la primera generaría la probabilidad de que el correo
electrónico sea spam, y la segunda generaría la probabilidad de que sea urgente. De
manera más general, dedicaría una neurona de salida para cada clase positiva.
Tenga en cuenta que las probabilidades de salida no necesariamente suman 1. Esto
permite que el modelo genere cualquier combinación de etiquetas: puede tener jamón no urgente,
Machine Translated by Google
jamón urgente, spam no urgente y quizás incluso spam urgente (aunque eso probablemente sería
un error).
Si cada instancia solo puede pertenecer a una única clase, de tres o más clases posibles (por
ejemplo, clases 0 a 9 para la clasificación de imágenes de dígitos), entonces necesita tener una
neurona de salida por clase y debe usar la función de activación softmax para toda la capa de
salida (consulte la Figura 10­9).
La función softmax (presentada en el Capítulo 4) garantizará que todas las probabilidades
estimadas estén entre 0 y 1 y que sumen 1 (lo que es necesario si las clases son exclusivas).
Esto se denomina clasificación multiclase.
Figura 10­9. Un MLP moderno (que incluye ReLU y softmax) para la clasificación
Respecto a la función de pérdida, dado que estamos prediciendo distribuciones de
probabilidad, la pérdida de entropía cruzada (también llamada pérdida logarítmica,
véase el Capítulo 4) es generalmente una buena opción.
La Tabla 10­2 resume la arquitectura típica de una MLP de clasificación.
Machine Translated by Google
Tabla 10­2. Arquitectura típica de clasificación MLP
Hiperparámetro
Clasificación binaria de
Clasificación
múltiples etiquetas
multiclase
Lo mismo que la regresión
Lo mismo que la regresión
1 por etiqueta
1 por clase
Logístico
Logístico
Máximo suave
Entropía cruzada
Entropía cruzada
Entropía cruzada
Clasificación binaria
Capas de entrada y
Lo mismo que
ocultas
regresión
# neuronas de salida 1
Activación de la
capa de salida
Función de pérdida
CONSEJO
Antes de continuar, te recomiendo que hagas el ejercicio 1 al final de este capítulo.
Jugarás con varias arquitecturas de redes neuronales y visualizarás sus resultados utilizando TensorFlow
Playground. Esto será muy útil para comprender mejor las MLP, incluidos los efectos de todos los
hiperparámetros (número de capas y neuronas, funciones de activación y más).
¡Ahora tienes todos los conceptos que necesitas para comenzar a implementar MLP con
Keras!
Implementación de MLP con Keras Keras es una API de
aprendizaje profundo de alto nivel que le permite construir, entrenar, evaluar y ejecutar
fácilmente todo tipo de redes neuronales. Su documentación (o especificación) está
disponible en https://keras.io/. La implementación de referencia , también
llamada Keras, fue desarrollada por François Chollet como parte de un proyecto de
13
investigación y se lanzó como un proyecto de código abierto en marzo de 2015.
Rápidamente ganó popularidad, debido a su facilidad de uso, flexibilidad y
hermoso diseño. Para realizar los cálculos pesados que requieren las redes
neuronales, esta implementación de referencia se basa en un backend de cálculo.
En la actualidad, puede elegir entre tres populares
Machine Translated by Google
Bibliotecas de aprendizaje profundo de origen: TensorFlow, Microsoft Cognitive
Toolkit (CNTK) y Theano. Por lo tanto, para evitar confusiones, nos referiremos a
esta implementación de referencia como Keras multibackend.
Desde finales de 2016, se han publicado otras implementaciones. Ahora puedes
ejecutar Keras en Apache MXNet, Core ML de Apple, JavaScript o TypeScript (para
ejecutar código Keras en un navegador web) y PlaidML (que puede ejecutarse en
todo tipo de dispositivos GPU, no solo Nvidia). Además, TensorFlow ahora viene
con su propia implementación de Keras, tf.keras. Solo es compatible con
TensorFlow como backend, pero tiene la ventaja de ofrecer algunas características
adicionales muy útiles (consulte la Figura 10­10): por ejemplo, es compatible
con la API de datos de TensorFlow, que facilita la carga y el preprocesamiento
de datos de manera eficiente. Por esta razón, usaremos tf.keras en este libro.
Sin embargo, en este capítulo no usaremos ninguna de las características
específicas de TensorFlow, por lo que el código también debería
ejecutarse bien en otras implementaciones de Keras (al menos en
Python), con solo modificaciones menores, como cambiar las importaciones.
Figura 10­10. Dos implementaciones de la API de Keras: Keras multibackend (izquierda) y tf.keras
(derecha)
La biblioteca de aprendizaje profundo más popular, después de Keras y
TensorFlow, es PyTorch de Facebook biblioteca. La buena noticia es que su API es
bastante similar a la de Keras (en parte porque ambas API se inspiraron en Scikit­
Learn y Chainer), Entonces, una vez que conoces Keras, no es difícil cambiar a
PyTorch, si alguna vez lo deseas. La popularidad de PyTorch creció
exponencialmente en 2018, en gran parte gracias a su simplicidad y excelente documentación, que fu
Machine Translated by Google
No son las principales fortalezas de TensorFlow 1.x. Sin embargo, se podría decir que
TensorFlow 2 es tan simple como PyTorch, ya que adoptó Keras como su API oficial de
alto nivel y sus desarrolladores simplificaron y limpiaron en gran medida el resto de la
API. La documentación también se reorganizó por completo y ahora es mucho más fácil
encontrar lo que necesita. De manera similar, las principales debilidades de
PyTorch (por ejemplo, portabilidad limitada y falta de análisis de gráficos de computación)
se abordaron en gran medida en PyTorch 1.0. La competencia sana es beneficiosa
para todos.
Muy bien, ¡es hora de codificar! Como tf.keras está incluido en TensorFlow,
comencemos por instalar TensorFlow.
Instalación de TensorFlow 2
Suponiendo que haya instalado Jupyter y Scikit­Learn siguiendo las instrucciones
de instalación del Capítulo 2, utilice pip para instalar TensorFlow. Si creó un entorno
aislado con virtualenv, primero debe activarlo:
$ cd $ML_PATH
# Su directorio de trabajo de ML (por ejemplo,
$HOME/ml)
$ source my_env/bin/activate # en Linux o macOS $ .
\my_env\Scripts\activate # en Windows
A continuación, instale TensorFlow 2 (si no está utilizando un entorno virtual, necesitará
derechos de administrador o agregar la opción ­­user):
$ python3 ­m pip install ­­upgrade tensorflow
NOTA
Para la compatibilidad con GPU, al momento de escribir este artículo, debe instalar tensorflow­
gpu en lugar de tensorflow, pero el equipo de TensorFlow está trabajando para tener una
única biblioteca que admita tanto sistemas equipados con CPU como con GPU. Aún deberá
instalar bibliotecas adicionales para la compatibilidad con GPU (consulte https://tensorflow.org/
install Para más detalles). Analizaremos las GPU con más profundidad en el Capítulo 19.
Machine Translated by Google
Para probar su instalación, abra un shell de Python o un cuaderno Jupyter, luego importe
TensorFlow y tf.keras e imprima sus versiones:
>>> importar tensorflow como tf >>> desde
tensorflow importar keras >>> tf.__version__ '2.0.0'
>>> keras.__version__ '2.2.4­tf'
La segunda versión es la versión de la API de Keras implementada por tf.keras.
Tenga en cuenta que termina con ­tf, lo que resalta el hecho de que tf.keras implementa
la API de Keras, además de algunas funciones adicionales específicas de TensorFlow.
¡Ahora usemos tf.keras! Comenzaremos por crear un clasificador de imágenes simple.
Construcción de un clasificador de imágenes mediante la API secuencial Primero,
necesitamos cargar un conjunto de datos. En este capítulo abordaremos Fashion
MNIST, que es un reemplazo directo de MNIST (introducido en el Capítulo 3).
Tiene exactamente el mismo formato que MNIST (70,000 imágenes en escala de
grises de 28 × 28 píxeles cada una, con 10 clases), pero las imágenes representan artículos
de moda en lugar de dígitos escritos a mano, por lo que cada clase es más diversa y el
problema resulta ser significativamente más desafiante que MNIST. Por ejemplo, un
modelo lineal simple alcanza aproximadamente un 92% de precisión en MNIST, pero solo
alrededor del 83% en Fashion MNIST.
Uso de Keras para cargar el conjunto de datos
Keras proporciona algunas funciones de utilidad para obtener y cargar conjuntos de datos
comunes, incluidos MNIST, Fashion MNIST y el conjunto de datos de vivienda de California
que usamos en el Capítulo 2. Carguemos Fashion MNIST:
fashion_mnist = keras.datasets.fashion_mnist (X_entrenamiento_completo,
y_entrenamiento_completo), (X_prueba, y_prueba) = fashion_mnist.load_data()
Machine Translated by Google
Al cargar MNIST o Fashion MNIST con Keras en lugar de Scikit­Learn, una diferencia importante
es que cada imagen se representa como una matriz de 28 × 28 en lugar de una matriz
unidimensional de tamaño 784. Además, las intensidades de los píxeles se representan
como números enteros (de 0 a 255) en lugar de como números flotantes (de 0,0 a 255,0).
Echemos un vistazo a la forma y el tipo de datos del conjunto de entrenamiento:
>>> Forma completa del tren X
(60000, 28, 28)
>>> X_train_full.dtype dtype('uint8')
Tenga en cuenta que el conjunto de datos ya está dividido en un conjunto de entrenamiento y
un conjunto de prueba, pero no hay un conjunto de validación, por lo que crearemos uno ahora.
Además, dado que vamos a entrenar la red neuronal mediante el descenso de gradiente,
debemos escalar las características de entrada. Para simplificar, reduciremos la escala de las
intensidades de los píxeles al rango de 0 a 1 dividiéndolas por 255,0 (esto también las convierte
en números flotantes):
X_válido, X_tren = X_tren_completo[:5000] / 255.0, X_tren_completo[5000:] / 255.0
y_válido, y_tren = y_tren_completo[:5000], y_tren_completo[5000:]
Con MNIST, cuando la etiqueta es igual a 5, significa que la imagen representa el dígito
5 escrito a mano. Fácil. Sin embargo, para Fashion MNIST, necesitamos la lista de nombres de
clases para saber con qué estamos tratando:
class_names = ["Camiseta/top", "Pantalón", "Jersey", "Vestido", "Abrigo",
"Sandalia", "Camisa", "Zapatilla", "Bolso", "Botín"]
Por ejemplo, la primera imagen del conjunto de entrenamiento representa un abrigo:
>>> nombres_de_clase[y_train[0]]
'Abrigo'
La figura 10­11 muestra algunas muestras del conjunto de datos de moda MNIST.
Machine Translated by Google
Figura 10­11. Muestras de Fashion MNIST
Creación del modelo mediante la API secuencial
Ahora, construyamos la red neuronal. Aquí tenemos una clasificación MLP con dos capas
ocultas:
modelo = keras.models.Sequential()
modelo.add(keras.layers.Flatten(forma_entrada=[28, 28]))
modelo.add(keras.layers.Dense(300, activación="relu"))
modelo.add(keras.layers.Dense(100, activación="relu"))
modelo.add(keras.layers.Dense(10, activación="softmax"))
Repasemos este código línea por línea:
La primera línea crea un modelo secuencial. Este es el tipo más simple de
modelo Keras para redes neuronales que se componen simplemente de una sola
pila de capas conectadas secuencialmente. Esto se denomina API secuencial.
A continuación, construimos la primera capa y la añadimos al modelo.
Se trata de una capa Flatten cuya función es convertir cada imagen de entrada
en una matriz unidimensional: si recibe datos de entrada X, calcula X.reshape(­1, 1).
Esta capa no tiene parámetros, solo está ahí para realizar un
preprocesamiento simple. Dado que es la primera capa del modelo, debe
especificar input_shape, que no incluye el tamaño del lote, solo la forma de
las instancias. Alternativamente, puede
Machine Translated by Google
Podría agregar un keras.layers.InputLayer como la primera capa, estableciendo
input_shape=[28,28].
A continuación, añadimos una capa oculta densa con 300 neuronas.
Utilizará la función de activación ReLU. Cada capa densa gestiona su propia
matriz de pesos, que contiene todos los pesos de conexión entre las neuronas
y sus entradas. También gestiona un vector de términos de sesgo (uno por
neurona). Cuando recibe algunos datos de entrada, calcula la ecuación 10­2.
Luego agregamos una segunda capa oculta densa con 100 neuronas,
también utilizando la función de activación ReLU.
Por último, agregamos una capa de salida densa con 10 neuronas (una
por clase), utilizando la función de activación softmax (porque las clases son
exclusivas).
CONSEJO
Especificar activation="relu" es equivalente a especificar
activation=keras.activations.relu. Hay otras funciones de activación disponibles
En el paquete keras.activations, utilizaremos muchas de ellas en este libro. Consulte
https://keras.io/activations/ para la lista completa.
En lugar de agregar las capas una por una como acabamos de hacer, puedes pasar
una lista de capas al crear el modelo secuencial:
modelo =
keras.models.Sequential([ keras.layers.Flatten(forma_de_entrada=[28,
28]), keras.layers.Dense(300, activación="relu"),
keras.layers.Dense(100, activación="relu"), keras.layers.Dense(10,
activación="softmax")
])
Machine Translated by Google
USANDO EJEMPLOS DE CÓDIGO DE KERAS.IO
Los ejemplos de código documentados en keras.io funcionarán bien con tf.keras, pero deberá
cambiar las importaciones. Por ejemplo, considere este código de keras.io:
desde keras.layers importar Dense
output_layer = Dense(10)
Debes cambiar las importaciones de la siguiente manera:
desde tensorflow.keras.layers importar Dense
output_layer = Dense(10)
O simplemente utiliza rutas completas, si lo prefieres:
desde tensorflow importar keras
output_layer = keras.layers.Dense(10)
Este enfoque es más detallado, pero lo utilizo en este libro para que puedas ver fácilmente
qué paquetes usar y para evitar confusiones entre clases estándar y clases personalizadas.
En el código de producción, prefiero el enfoque anterior. Muchas personas también usan from
tensorflow.keras import layers seguido de layers.Dense(10).
El método summary() del modelo muestra todas las capas del modelo, incluido el
14
nombre de cada capa (que se genera automáticamente a menos que lo configure al crear la capa), su
forma de salida (Ninguno significa que el tamaño del lote puede ser cualquiera) y su número de
parámetros. El resumen finaliza con el número total de parámetros, incluidos los parámetros
entrenables y no entrenables. Aquí solo tenemos parámetros entrenables (veremos ejemplos
de parámetros no entrenables en el Capítulo 11):
>>> modelo.summary()
Modelo: "secuencial"
_________________________________________________________________
Machine Translated by Google
Capa (tipo)
Forma de salida
Parámetro #
==================================================== ================
0
aplanar (Aplanar)
(Ninguno, 784)
_________________________________________________________________
235500
denso (denso)
(Ninguno, 300)
_________________________________________________________________
30100
denso_1 (denso)
(Ninguno, 100)
_________________________________________________________________
1010
denso_2 (denso)
(Ninguno, 10)
==================================================== ================
Parámetros totales: 266.610
Parámetros entrenables: 266.610
Parámetros no entrenables: 0
_________________________________________________________________
Tenga en cuenta que las capas densas suelen tener muchos parámetros. Por ejemplo,
La primera capa oculta tiene 784 × 300 pesos de conexión, más 300 términos de sesgo,
¡Lo que suma 235.500 parámetros! Esto le da al modelo una gran cantidad de
flexibilidad para ajustar los datos de entrenamiento, pero también significa que el modelo ejecuta el
Riesgo de sobreajuste, especialmente cuando no tienes muchos datos de entrenamiento.
Volveremos a esto más adelante.
Puede obtener fácilmente la lista de capas de un modelo, buscar una capa por su índice o
Puedes obtenerlo por nombre:
>>> modelo.capas
[<tensorflow.python.keras.layers.core.Flatten en 0x132414e48>,
<tensorflow.python.keras.layers.core.Dense en 0x1324149b0>,
<tensorflow.python.keras.layers.core.Dense en 0x1356ba8d0>,
[tensorflow.python.keras.layers.core.Dense en 0x13240d240>]
>>> hidden1 = modelo.capas[1]
>>> nombre1 oculto
'denso'
>>> model.get_layer('dense') está oculto1
Verdadero
Se puede acceder a todos los parámetros de una capa utilizando su get_weights() y
Métodos set_weights(). Para una capa densa, esto incluye tanto el
Pesos de conexión y términos de sesgo:
>>> pesos, sesgos = hidden1.get_weights()
>>> pesos
Machine Translated by Google
matriz([[ 0,02448617, ­0,00877795, ­0,02189048, ..., ­0,02766046, 0,03859074,
­0,06889391],
...,
[­0,06022581, 0,01577859, ­0,02585464, ..., ­0,00527829, 0,00272203, ­0,06793761]],
dtype=float32) >>> pesos.forma (784, 300) >>> sesgos matriz ([0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, ...,
0, 0, 0, 0.],
dtype=float32)
>>> sesgos.forma (300,)
Tenga en cuenta que la capa Dense inicializó los pesos de conexión de forma aleatoria
(lo que es necesario para romper la simetría, como comentamos anteriormente) y los
sesgos se inicializaron a cero, lo cual está bien. Si alguna vez desea utilizar un método
de inicialización diferente, puede configurar kernel_initializer (kernel es otro nombre para la
matriz de pesos de conexión) o bias_initializer al crear la capa.
Hablaremos más sobre los inicializadores en el Capítulo 11, pero si desea la lista
completa, consulte https://keras.io/initializers/.
NOTA
La forma de la matriz de ponderación depende de la cantidad de entradas. Por eso se
recomienda especificar la forma de entrada al crear la primera capa en un
modelo secuencial. Sin embargo, si no especifica la forma de entrada, no hay problema:
Keras simplemente esperará hasta conocer la forma de entrada antes de construir el modelo.
Esto sucederá cuando le proporciones datos reales (por ejemplo, durante el entrenamiento)
o cuando llames a su método build(). Hasta que el modelo esté realmente construido, las
capas no tendrán ningún peso y no podrás hacer ciertas cosas (como imprimir el
resumen del modelo o guardarlo). Por lo tanto, si conoces la forma de entrada al crear
el modelo, es mejor especificarla.
Compilando el modelo
Después de crear un modelo, debe llamar a su método compile() para especificar la
función de pérdida y el optimizador que se utilizará. Opcionalmente, puede especificar
una lista de métricas adicionales para calcular durante el entrenamiento y la evaluación:
Machine Translated by Google
modelo.compile(pérdida="entropía_cruzada_categórica_dispersa",
optimizador="sgd",
métricas=["precisión"])
NOTA
El uso de loss="sparse_categorical_crossentropy" es equivalente a usar
loss=keras.losses.sparse_categorical_crossentropy. De manera similar, especificar
optimized="sgd" es equivalente a especificar
optimized=keras.optimizers.SGD(), y metrics=["accuracy"] es equivalente a
metrics=[keras.metrics.sparse_categorical_accuracy] (cuando se usa esta
pérdida). En este libro, usaremos muchas otras pérdidas, optimizadores y métricas;
para ver las listas completas, consulte https://keras.io/losses. https://keras.io/
optimizers, y https://keras.io/metrics.
Este código requiere una explicación. Primero, usamos la
pérdida "sparse_categorical_crossentropy" porque tenemos etiquetas
dispersas (es decir, para cada instancia, solo hay un índice de clase objetivo,
de 0 a 9 en este caso), y las clases son exclusivas. Si en cambio tuviéramos
una probabilidad objetivo por clase para cada instancia (como vectores one­
hot, por ejemplo [0., 0., 0., 1., 0., 0., 0., 0., 0., 0.] para representar la clase 3),
entonces necesitaríamos usar la pérdida "categorical_crossentropy" en su
lugar. Si estuviéramos haciendo una clasificación binaria (con una o más
etiquetas binarias), entonces usaríamos la función de activación "sigmoidea"
(es decir, logística) en la capa de salida en lugar de la función de activación
"softmax", y usaríamos la pérdida "binary_crossentropy".
CONSEJO
Si desea convertir etiquetas dispersas (es decir, índices de clase) en etiquetas
vectoriales one­hot, utilice la función keras.utils.to_categorical(). Para hacer lo contrario,
utilice la función np.argmax() con axis=1.
Machine Translated by Google
En cuanto al optimizador, "sgd" significa que entrenaremos el modelo utilizando el
simple Descenso de gradiente estocástico. En otras palabras, Keras realizará el
algoritmo de retropropagación descrito anteriormente (es decir, autodiff en modo
inverso más Descenso de gradiente). Hablaremos de optimizadores más eficientes en el
Capítulo 11 (mejoran la parte del Descenso de gradiente, no la autodiff).
NOTA
Al utilizar el optimizador SGD, es importante ajustar la tasa de aprendizaje. Por lo tanto,
generalmente querrá utilizar optimized=keras.optimizers.SGD(lr=???) para establecer la tasa
de aprendizaje, en lugar de optimized="sgd", que tiene un valor predeterminado de lr=0,01.
Por último, dado que se trata de un clasificador, es útil medir su "precisión" durante
el entrenamiento y la evaluación.
Entrenamiento y evaluación del modelo
Ahora el modelo está listo para ser entrenado. Para ello, simplemente debemos llamar
a su método fit():
>>> historial = modelo.fit(X_train, y_train, épocas=30,
...
datos_validacion=(X_valido, y_valido))
...
Entrenar en 55000 muestras, validar en 5000 muestras Época 1/30 55000/55000 [======] ­ 3s
49us/muestra ­
pérdida: 0,7218 0,7660
­ exactitud:
­ pérdida de valor: 0,4973 ­ precisión de valor:
0,8366
Época 2/30
55000/55000 [======] ­ 2s 45us/muestra ­ pérdida: 0,4840 0,8327
­ exactitud:
­ pérdida de valor: 0,4456 ­ precisión de valor:
0,8480 [...]
Época 30/30
55000/55000 [======] ­ 3s 53us/muestra ­ pérdida: 0,2252 0,9192
­ exactitud:
Machine Translated by Google
­ val_pérdida: 0,2999 ­ val_precisión:
0,8926
Le pasamos las características de entrada (X_train) y las clases de destino (y_train), así
como el número de épocas a entrenar (o de lo contrario, el valor predeterminado sería solo
1, lo que definitivamente no sería suficiente para converger a una buena solución). También
pasamos un conjunto de validación (esto es opcional). Keras medirá la pérdida y las métricas
adicionales en este conjunto al final de cada época, lo que es muy útil para ver qué tan bien
funciona realmente el modelo. Si el rendimiento en el conjunto de entrenamiento es
mucho mejor que en el conjunto de validación, es probable que su modelo esté
sobreajustando el conjunto de entrenamiento (o haya un error, como una discrepancia
de datos entre el conjunto de entrenamiento y el conjunto de validación).
15
¡Y eso es todo! La red neuronal está entrenada. En cada época durante el
entrenamiento, Keras muestra la cantidad de instancias procesadas hasta el momento
(junto con una barra de progreso), el tiempo de entrenamiento promedio por muestra y la
pérdida y precisión (o cualquier otra métrica adicional que haya solicitado) tanto en el
conjunto de entrenamiento como en el conjunto de validación. Puede ver que la pérdida de
entrenamiento disminuyó, lo cual es una buena señal, y la precisión de validación
alcanzó el 89,26 % después de 30 épocas. Eso no está muy lejos de la precisión de
entrenamiento, por lo que no parece haber mucho sobreajuste.
CONSEJO
En lugar de pasar un conjunto de validación mediante el argumento validation_data, puede establecer
validation_split en la proporción del conjunto de entrenamiento que desea que Keras utilice para la
validación. Por ejemplo, validation_split=0.1 le indica a Keras que utilice el último 10 % de los datos (antes
de mezclarlos) para la validación.
Si el conjunto de entrenamiento estuviera muy sesgado, con algunas
clases sobrerrepresentadas y otras subrepresentadas, sería útil establecer el argumento
class_weight al llamar al método fit(), que daría un peso mayor a las clases
subrepresentadas y un peso menor a las clases sobrerrepresentadas. Keras utilizaría
estos pesos al calcular la pérdida. Si necesita pesos por instancia, establezca el
argumento
Machine Translated by Google
argumento sample_weight (reemplaza a class_weight). Por instancia
Los pesos podrían ser útiles si algunos casos fueran etiquetados por expertos mientras
otros fueron etiquetados mediante una plataforma de crowdsourcing: es posible que desees
Dar más peso a los primeros. También puedes proporcionar ponderaciones de muestra (pero
no pesos de clase) para el conjunto de validación agregándolos como un tercer elemento en
la tupla validation_data.
El método fit() devuelve un objeto History que contiene el entrenamiento
parámetros (history.params), la lista de épocas por las que pasó
(history.epoch), y lo más importante un diccionario (history.history)
que contiene la pérdida y las métricas adicionales que midió al final de cada época
en el conjunto de entrenamiento y en el conjunto de validación (si lo hay). Si utiliza este
diccionario para crear un DataFrame de pandas y llamar a su método plot(),
Obtenga las curvas de aprendizaje que se muestran en la Figura 10­12:
importar pandas como pd
importar matplotlib.pyplot como plt
pd.DataFrame(historial.historial).plot(tamaño de figura=(8, 5))
plt.grid(Verdadero)
plt.gca().set_ylim(0, 1) plt.show()
#
colocar
El rango vertical
a
[0­1]
Machine Translated by Google
Figura 10­12. Curvas de aprendizaje: pérdida de entrenamiento y precisión medias medidas en cada época,
y pérdida de validación y precisión medias medidas al final de cada época
Se puede ver que tanto la precisión del entrenamiento como la precisión de la validación
aumentan de forma constante durante el entrenamiento, mientras que la pérdida del
entrenamiento y la pérdida de la validación disminuyen. ¡Bien! Además, las curvas de
validación están cerca de las curvas de entrenamiento, lo que significa que no hay demasiado
sobreajuste. En este caso particular, parece que el modelo funcionó mejor en el conjunto
de validación que en el conjunto de entrenamiento al comienzo del entrenamiento. Pero
ese no es el caso: de hecho, el error de validación se calcula al final de cada época,
mientras que el error de entrenamiento se calcula utilizando una media móvil durante
cada época. Por lo tanto, la curva de entrenamiento debería desplazarse media época hacia la izquierda.
Si haces eso, verás que las curvas de entrenamiento y validación se superponen casi
perfectamente al comienzo del entrenamiento.
CONSEJO
Al trazar la curva de entrenamiento, ésta debe desplazarse media época hacia la izquierda.
Machine Translated by Google
El rendimiento del conjunto de entrenamiento termina superando al rendimiento de
validación, como suele suceder cuando se entrena durante el tiempo suficiente. Se puede
decir que el modelo aún no ha convergido del todo, ya que la pérdida de validación sigue
disminuyendo, por lo que probablemente debería continuar con el entrenamiento. Es tan
simple como volver a llamar al método fit(), ya que Keras simplemente continúa con el
entrenamiento donde lo dejó (debería poder alcanzar una precisión de validación cercana al 89%).
Si no está satisfecho con el rendimiento de su modelo, debe volver atrás y ajustar los
hiperparámetros. El primero que debe verificar es la tasa de aprendizaje. Si eso no ayuda,
pruebe con otro optimizador (y siempre vuelva a ajustar la tasa de aprendizaje después
de cambiar cualquier hiperparámetro). Si el rendimiento sigue sin ser excelente, intente
ajustar los hiperparámetros del modelo, como la cantidad de capas, la cantidad de neuronas
por capa y los tipos de funciones de activación que se utilizarán para cada capa oculta.
También puede intentar ajustar otros hiperparámetros, como el tamaño del lote (se
puede configurar en el método fit() utilizando el argumento batch_size, que tiene un valor
predeterminado de 32). Volveremos al ajuste de hiperparámetros al final de este capítulo.
Una vez que esté satisfecho con la precisión de validación de su modelo, debe evaluarlo
en el conjunto de prueba para estimar el error de generalización antes de implementar el
modelo en producción. Puede hacerlo fácilmente utilizando el método evaluation()
(también admite varios otros argumentos, como batch_size y sample_weight; consulte la
documentación para obtener más detalles):
>>> model.evaluate(X_test, y_test) 10000/10000
[==========] ­ 0 s 29 us/muestra ­ pérdida: 0,3340 ­ precisión: 0,8851
[0,3339798209667206, 0,8851]
Como vimos en el Capítulo 2, es común obtener un rendimiento ligeramente inferior en el
conjunto de prueba que en el conjunto de validación, porque los hiperparámetros se
ajustan en el conjunto de validación, no en el conjunto de prueba (sin embargo, en este
ejemplo, no hicimos ningún ajuste de hiperparámetros, por lo que la menor precisión es
simplemente mala suerte). Recuerde resistir la tentación de ajustar los hiperparámetros en
el conjunto de prueba, o de lo contrario su estimación del error de generalización será
demasiado optimista.
Machine Translated by Google
Utilizando el modelo para hacer predicciones
A continuación, podemos utilizar el método predict() del modelo para realizar predicciones sobre
nuevas instancias. Como no tenemos nuevas instancias reales, simplemente usaremos
las tres primeras instancias del conjunto de prueba:
>>> X_nuevo = X_prueba[:3]
>>> y_proba = modelo.predict(X_new)
>>> y_proba.round(2)
, , 0.
, 0.
, 0.
, 0,03, 0. 0.
matriz([[0. , 0. 0. [0. , , 0.98, 0. [0.
0.
, 0,02, 0. 0. 0.
,
0. dtype=float32)
1.
,
,
,
,
, 0.
, 0,01, 0. 0. 0.
,
, 0.
,
, 0.
, 0,96],
, 0. ],
, 0. ]],
Como puede ver, para cada instancia el modelo estima una probabilidad por
clase, de la clase 0 a la clase 9. Por ejemplo, para la primera imagen se estima
que la probabilidad de clase 9 (bota hasta el tobillo) es del 96%, la probabilidad de clase
5 (sandalia) es 3%, la probabilidad de la clase 7 (zapatilla deportiva) es 1% y la
Las probabilidades de las otras clases son despreciables. En otras palabras,
“Cree” que la primera imagen es calzado, probablemente botines, pero posiblemente
sandalias o zapatillas deportivas. Si solo te importa la clase con la más alta
probabilidad estimada (incluso si esa probabilidad es bastante baja), entonces puedes
Utilice el método predict_classes() en su lugar:
>>> y_pred = modelo.predict_classes(X_new)
>>> y_pred
matriz([9, 2, 1])
>>> np.array(nombres_de_clase)[y_pred]
array(['Botín', 'Jersey', 'Pantalón'], dtype='<U11')
Aquí, el clasificador en realidad clasificó las tres imágenes correctamente (estas
Las imágenes se muestran en la Figura 10­13):
>>> y_nuevo = y_prueba[:3]
>>> y_nuevo
matriz([9, 2, 1])
Machine Translated by Google
Figura 10­13. Imágenes de Fashion MNIST correctamente clasificadas
Ahora ya sabe cómo usar la API secuencial para crear, entrenar, evaluar y usar un MLP
de clasificación. ¿Pero qué sucede con la regresión?
Construcción de una regresión MLP utilizando la API secuencial Pasemos al
problema de la vivienda en California y abordémoslo utilizando una red neuronal
de regresión. Para simplificar, utilizaremos la función fetch_california_housing()
de Scikit­Learn para cargar los datos. Este conjunto de datos es más simple que el
que utilizamos en el Capítulo 2, ya que contiene solo características
numéricas (no hay ninguna característica ocean_proximity) y no hay ningún valor
faltante. Después de cargar los datos, los dividimos en un conjunto de
entrenamiento, un conjunto de validación y un conjunto de prueba, y escalamos todas las características:
desde sklearn.datasets importar fetch_california_housing desde
sklearn.model_selection importar train_test_split desde sklearn.preprocessing
importar StandardScaler
vivienda = fetch_california_housing()
X_entrenamiento_completo, X_prueba, y_entrenamiento_completo, y_prueba = entrenamiento_prueba_dividido(
vivienda.datos, vivienda.objetivo)
X_entrenamiento, X_válido, y_entrenamiento, y_válido = tren_prueba_división(
Tren X lleno, tren Y lleno)
escalador = StandardScaler()
X_train = escalador.fit_transform(X_train)
X_valid = escalador.transform(X_valid)
X_test = escalador.transform(X_test)
Machine Translated by Google
El uso de la API secuencial para crear, entrenar, evaluar y utilizar una MLP de
regresión para realizar predicciones es bastante similar a lo que hicimos para la clasificación.
Las principales diferencias son el hecho de que la capa de salida tiene una sola neurona
(ya que solo queremos predecir un único valor) y no utiliza ninguna función de
activación, y la función de pérdida es el error cuadrático medio. Como el conjunto de datos
es bastante ruidoso, simplemente utilizamos una única capa oculta con menos neuronas
que antes, para evitar el sobreajuste:
modelo =
keras.modelos.Secuencial([ keras.capas.Dense(30, activación="relu",
forma_de_entrada=X_train.forma[1:]),
keras.capas.Dense(1)
])
model.compile(pérdida="error_cuadrado_medio", optimized="sgd") history
= model.fit(X_train, y_train, épocas=20,
datos_validacion=(X_valido, y_valido))
mse_test = modelo.evaluar(X_test, y_test)
X_nuevo = X_prueba[:3]
Imagina que son nuevas instancias #
y_pred = modelo.predict(X_nuevo)
Como puede ver, la API secuencial es bastante fácil de usar. Sin embargo, aunque los
modelos secuenciales son extremadamente comunes, a veces resulta útil para
construir redes neuronales con topologías más complejas o con múltiples entradas o
salidas. Para este propósito, Keras ofrece la API funcional.
Creación de modelos complejos mediante la API funcional Un ejemplo de una red
neuronal no secuencial es una red neuronal amplia y profunda . Esta arquitectura de red
neuronal se presentó en un artículo de 2016 por Heng­Tze Cheng et al. Conecta todas o
16
parte de las entradas directamente a la capa de salida, como se muestra en la Figura
10­14. Esta arquitectura permite que la red neuronal aprenda tanto patrones
profundos (utilizando la ruta profunda) como reglas simples (a través de la ruta corta). En
17
cambio, un MLP regular obliga a que todos los datos fluyan a través de la pila completa
de capas; por lo tanto, los patrones simples en los datos pueden terminar
distorsionados por esta secuencia de transformaciones.
Machine Translated by Google
Figura 10­14. Red neuronal amplia y profunda
Construyamos una red neuronal de este tipo para abordar el problema de la vivienda
en California:
entrada_ = keras.capas.Entrada(forma=X_train.forma[1:]) oculta1 =
keras.capas.Dense(30, activación="relu")(entrada_) oculta2 =
keras.capas.Dense(30, activación="relu")(oculta1) concatenar =
keras.capas.Concatenar()([entrada_, oculta2]) salida =
keras.capas.Dense(1)(concat) modelo =
keras.Modelo(entradas=[entrada_], salidas=[salida])
Repasemos cada línea de este código:
Machine Translated by Google
18
Primero, necesitamos crear un objeto de entrada. Se trata de una especificación
del tipo de entrada que recibirá el modelo, incluida su forma y tipo de datos. Un
modelo puede tener varias entradas, como veremos en breve.
A continuación, creamos una capa densa con 30 neuronas, utilizando la función
de activación ReLU. Una vez creada, observe que la llamamos como una función
y le pasamos la entrada. Por eso se llama API funcional. Observe que solo le
estamos diciendo a Keras cómo debe conectar las capas entre sí; todavía no se
están procesando datos reales.
Luego creamos una segunda capa oculta y la usamos nuevamente como
función. Observe que le pasamos la salida de la primera capa oculta.
A continuación, creamos una capa Concatenar y, una vez más, la
usamos inmediatamente como una función para concatenar la entrada y la
salida de la segunda capa oculta. Es posible que prefieras la función
keras.layers.concatenate(), que crea una capa Concatenar y la llama
inmediatamente con las entradas dadas.
Luego creamos la capa de salida, con una sola neurona y sin función de
activación, y la llamamos como función, pasándole el resultado de la
concatenación.
Por último, creamos un modelo Keras, especificando qué entradas y salidas
utilizar.
Una vez que haya construido el modelo Keras, todo es exactamente como antes, por lo que
no es necesario repetirlo aquí: debe compilar el modelo, entrenarlo, evaluarlo y usarlo
para hacer predicciones.
Pero ¿qué sucede si desea enviar un subconjunto de las características a través de la ruta
ancha y un subconjunto diferente (posiblemente superpuesto) a través de la ruta profunda
(consulte la Figura 10­15)? En este caso, una solución es utilizar múltiples entradas. Por
ejemplo, supongamos que deseamos enviar cinco características a través de la ruta
ancha (características 0 a 4) y seis características a través de la ruta profunda (características 2 a 7):
Machine Translated by Google
entrada_A = keras.capas.Entrada(forma=[5], nombre="entrada_ancha") entrada_B
= keras.capas.Entrada(forma=[6], nombre="entrada_profunda") oculta1 =
keras.capas.Dense(30, activación="relu")(entrada_B) oculta2 = keras.capas.Dense(30,
activación="relu")(oculta1) concatenar = keras.capas.concatenar([entrada_A,
oculta2]) salida = keras.capas.Dense(1, nombre="salida")(concat) modelo
= keras.Modelo(entradas=[entrada_A, entrada_B], salidas=[salida])
Figura 10­15. Manejo de múltiples entradas
El código se explica por sí solo. Debes nombrar al menos las capas más importantes,
especialmente cuando el modelo se vuelve un poco complejo como este. Ten en cuenta que
Machine Translated by Google
Al crear el modelo, especificamos inputs=[input_A, input_B]. Ahora podemos compilar el
modelo como siempre, pero cuando llamamos al método fit(), en lugar de pasar una
única matriz de entrada X_train, debemos pasar un par de matrices (X_train_A,
19
X_train_B): una por entrada. Lo mismo es válido para X_valid, y también para X_test y
X_new cuando llamas a evaluation() o predict():
modelo.compilar(pérdida="mse", optimizador=keras.optimizers.SGD(lr=1e­3))
X_tren_A, X_tren_B = X_tren[:, :5], X_tren[:, 2:]
X_válido_A, X_válido_B = X_válido[:, :5], X_válido[:, 2:]
X_prueba_A, X_prueba_B = X_prueba[:, :5], X_prueba[:, 2:]
X_nueva_A, X_nueva_B = X_prueba_A[:3], X_prueba_B[:3]
historial = modelo.ajuste((X_tren_A, X_tren_B), y_tren, épocas=20,
datos_validación=((X_válido_A, X_válido_B), y_válido))
mse_test = modelo.evaluar((X_prueba_A, X_prueba_B), y_prueba)
y_pred = modelo.predecir((X_nueva_A, X_nueva_B))
Hay muchos casos de uso en los que es posible que desee tener múltiples salidas:
La tarea puede exigirlo. Por ejemplo, puede que quieras localizar y clasificar el
objeto principal de una imagen. Se trata tanto de una tarea de regresión
(encontrar las coordenadas del centro del objeto, así como su ancho y altura)
como de una tarea de clasificación.
De manera similar, puede tener múltiples tareas independientes basadas en
los mismos datos. Por supuesto, puede entrenar una red neuronal por tarea,
pero en muchos casos obtendrá mejores resultados en todas las tareas si
entrena una sola red neuronal con una salida por tarea. Esto se debe a que
la red neuronal puede aprender características en los datos que son útiles
en todas las tareas. Por ejemplo, puede realizar una clasificación
multitarea en imágenes de rostros, utilizando una salida para clasificar la
expresión facial de la persona (sonriente, sorprendida, etc.) y otra salida
para identificar si usa anteojos o no.
Otro caso de uso es como técnica de regularización (es decir, una restricción
de entrenamiento cuyo objetivo es reducir el sobreajuste y, por lo tanto,
Machine Translated by Google
mejorar la capacidad del modelo para generalizar). Por ejemplo, puede
Quiero agregar algunas salidas auxiliares en una red neuronal.
arquitectura (ver Figura 10­16) para garantizar que la parte subyacente
de la red aprende algo útil por sí sola, sin
confiando en el resto de la red.
Figura 10­16. Manejo de múltiples salidas, en este ejemplo para agregar una salida auxiliar para
regularización
Agregar salidas adicionales es bastante fácil: simplemente conéctelas a las salidas adecuadas.
capas y agréguelas a la lista de resultados de su modelo. Por ejemplo, la
El siguiente código construye la red representada en la Figura 10­16:
[...]
# Mismo como arriba,
arriba
a
La capa principal
producción
salida = keras.layers.Dense(1, nombre="main_output")(concat)
salida_auxiliar = keras.capas.Dense(1, nombre="salida_auxiliar")(oculto2)
modelo = keras.Modelo(entradas=[entrada_A, entrada_B], salidas=[salida,
salida auxiliar])
Cada salida necesitará su propia función de pérdida. Por lo tanto, cuando compilamos
el modelo, debemos pasar una lista de pérdidas 20
(si pasamos una sola pérdida, Keras
Machine Translated by Google
asumirá que se debe utilizar la misma pérdida para todas las salidas). De forma
predeterminada, Keras calculará todas estas pérdidas y simplemente las sumará para obtener
la pérdida final utilizada para el entrenamiento. Nos preocupamos mucho más por la
salida principal que por la salida auxiliar (ya que solo se utiliza para la regularización), por lo
que queremos darle a la pérdida de la salida principal un peso mucho mayor.
Afortunadamente, es posible establecer todos los pesos de pérdida al compilar el modelo:
modelo.compile(pérdida=["mse", "mse"], pesos_pérdida=[0.9, 0.1],
optimizador="sgd")
Ahora, cuando entrenamos el modelo, necesitamos proporcionar etiquetas para cada salida.
En este ejemplo, la salida principal y la salida auxiliar deben intentar predecir lo mismo,
por lo que deben usar las mismas etiquetas. Por lo tanto, en lugar de pasar y_train,
debemos pasar (y_train, y_train) (y lo mismo ocurre con y_valid y y_test):
historial =
modelo.fit( [X_tren_A, X_tren_B], [y_tren, y_tren], épocas=20,
datos_validación=([X_válido_A, X_válido_B], [y_válido, y_válido]))
Cuando evaluamos el modelo, Keras devolverá la pérdida total, así como todas las pérdidas
individuales:
pérdida_total, pérdida_principal, pérdida_auxiliar = modelo.evaluar(
[X_test_A, X_test_B], [y_test, y_test])
De manera similar, el método predict() devolverá predicciones para cada salida:
y_pred_main, y_pred_aux = modelo.predict([X_nuevo_A, X_nuevo_B])
Como puede ver, puede crear cualquier tipo de arquitectura que desee con bastante
facilidad con la API funcional. Veamos una última forma de crear modelos Keras.
Uso de la API de subclases para crear modelos dinámicos
Machine Translated by Google
Tanto la API secuencial como la API funcional son declarativas: comienzas
declarando qué capas quieres usar y cómo deben estar conectadas, y solo
entonces puedes comenzar a alimentar al modelo con algunos datos para
entrenamiento o inferencia. Esto tiene muchas ventajas: el modelo se puede
guardar, clonar y compartir fácilmente; su estructura se puede mostrar y analizar; el
marco puede inferir formas y verificar tipos, por lo que los errores se pueden detectar
de manera temprana (es decir, antes de que los datos pasen por el modelo). También
es bastante fácil de depurar, ya que todo el modelo es un gráfico estático de capas.
Pero la otra cara es justamente eso: es estático. Algunos modelos
involucran bucles, formas variables, ramificaciones condicionales y otros
comportamientos dinámicos. Para tales casos, o simplemente si prefieres
un estilo de programación más imperativo, la API de subclases es para ti.
Simplemente crea una subclase de la clase Model, crea las capas que
necesitas en el constructor y úsalas para realizar los cálculos que deseas en el
método call(). Por ejemplo, crear una instancia de la siguiente clase
WideAndDeepModel nos da un modelo equivalente al que acabamos de crear con
la API funcional. Luego puedes compilarlo, evaluarlo y usarlo para hacer predicciones,
exactamente como lo acabamos de hacer:
clase WideAndDeepModel(keras.Model): def
__init__(self, unidades=30, activación="relu", **kwargs):
super().__init__(**kwargs)
maneja argumentos estándar (por ejemplo, nombre) #
self.hidden1 = keras.layers.Dense(unidades, activación=activación) self.hidden2 =
keras.layers.Dense(unidades, activación=activación) self.main_output =
keras.layers.Dense(1) self.aux_output =
keras.layers.Dense(1)
def call(self, entradas):
entrada_A, entrada_B = entradas
oculta1 = self.oculta1(entrada_B) oculta2
= self.oculta2(oculta1) concat =
keras.capas.concatenar([entrada_A, oculta2]) salida_principal =
self.salida_principal(concat) salida_auxiliar =
self.salida_auxiliar(oculta2) return salida_principal,
salida_auxiliar
modelo = WideAndDeepModel()
Machine Translated by Google
Este ejemplo se parece mucho a la API funcional, excepto que no necesitamos crear las entradas;
solo usamos el argumento de entrada para el método call() y separamos la creación de las capas
21
en el constructor de su uso en el método call(). La gran diferencia es que puedes hacer
prácticamente todo lo que quieras en el método call(): bucles for, declaraciones if, operaciones de
bajo nivel de TensorFlow... ¡tu imaginación es el límite (ver Capítulo 12)! Esto la convierte en una
excelente API para investigadores que experimentan con nuevas ideas.
Esta flexibilidad adicional tiene un costo: la arquitectura de su modelo está oculta dentro del
método call(), por lo que Keras no puede inspeccionarla fácilmente; no puede guardarla ni
clonarla; y cuando llama al método summary(), solo obtiene una lista de capas, sin ninguna
información sobre cómo están conectadas entre sí. Además, Keras no puede verificar
tipos y formas con anticipación, y es más fácil cometer errores. Entonces, a menos que realmente
necesite esa flexibilidad adicional, probablemente debería quedarse con la API secuencial o la API
funcional.
CONSEJO
Los modelos de Keras se pueden utilizar como capas normales, por lo que puedes combinarlos fácilmente
para construir arquitecturas complejas.
Ahora que sabes cómo construir y entrenar redes neuronales usando Keras, ¡querrás guardarlas!
Guardar y restaurar un modelo
Al utilizar la API secuencial o la API funcional, guardar un archivo entrenado
El modelo de Keras es lo más simple que existe:
modelo = keras.layers.Sequential([...]) model.compile([...])# o
model.fit([...])
model.save("my_keras_model.h5")
keras.Modelo([...])
Machine Translated by Google
Keras utilizará el formato HDF5 para guardar tanto la arquitectura del modelo (incluidos
los hiperparámetros de cada capa) como los valores de todos los parámetros del modelo para
cada capa (por ejemplo, pesos de conexión y sesgos). También guarda el optimizador
(incluidos sus hiperparámetros y cualquier estado que pueda tener).
Normalmente, tendrás un script que entrena un modelo y lo guarda, y uno o más scripts (o
servicios web) que cargan el modelo y lo utilizan para hacer predicciones. Cargar el
modelo es igual de fácil:
modelo = keras.models.load_model("my_keras_model.h5")
ADVERTENCIA
Esto funcionará cuando se use la API secuencial o la API funcional, pero
lamentablemente no cuando se use la subclasificación de modelos. Puede usar
save_weights() y load_weights() para al menos guardar y restaurar los parámetros del
modelo, pero deberá guardar y restaurar todo lo demás usted mismo.
Pero, ¿qué pasa si el entrenamiento dura varias horas? Esto es bastante común, especialmente
cuando se entrena con grandes conjuntos de datos. En este caso, no solo debe guardar su
modelo al final del entrenamiento, sino también guardar puntos de control a intervalos regulares
durante el entrenamiento, para evitar perder todo si su computadora falla. Pero, ¿cómo puede
indicarle al método fit() que guarde los puntos de control? Utilice devoluciones de llamadas.
Uso de devoluciones de llamadas
El método fit() acepta un argumento de devolución de llamada que le permite especificar una
lista de objetos que Keras llamará al inicio y al final del entrenamiento, al inicio y al final de
cada época, e incluso antes y después de procesar cada lote. Por ejemplo, la devolución
de llamada ModelCheckpoint guarda los puntos de control de su modelo a intervalos regulares
durante el entrenamiento, de manera predeterminada al final de cada época:
Machine Translated by Google
[...]
# Construir y compilar el modelo
checkpoint_cb = keras.callbacks.ModelCheckpoint("mi_modelo_keras.h5")
historial = modelo.fit(X_train, y_train, épocas=10, devoluciones de llamada=
[punto de control_cb])
Además, si utiliza un conjunto de validación durante el entrenamiento, puede configurar
save_best_only=True al crear el ModelCheckpoint. En este caso,
Solo guardará su modelo cuando su rendimiento en el conjunto de validación sea
El mejor hasta ahora. De esta manera, no tendrás que preocuparte por entrenar durante demasiado tiempo.
largo y sobreajustar el conjunto de entrenamiento: simplemente restaure el último modelo guardado
después del entrenamiento, y este será el mejor modelo en el conjunto de validación.
El siguiente código es una forma sencilla de implementar la detención anticipada (introducida en
Capítulo 4):
punto de control_cb = keras.callbacks.ModelCheckpoint("mi_modelo_keras.h5",
save_best_only=Verdadero)
historia = modelo.fit(X_train, y_train, épocas=10,
datos_validacion=(X_válido, y_válido),
devoluciones de llamada=[punto de control_cb])
modelo = keras.models.load_model("my_keras_model.h5")
modelo
# retroceder a
mejor
Otra forma de implementar la parada temprana es simplemente utilizar el
Devolución de llamada EarlyStopping. Interrumpirá el entrenamiento cuando no mida nada.
progreso en el conjunto de validación para un número de épocas (definido por el
argumento de paciencia) y, opcionalmente, volverá al mejor modelo.
Puede combinar ambas devoluciones de llamadas para guardar los puntos de control de su modelo (en
caso de que su computadora se bloquee) e interrumpir el entrenamiento temprano cuando no hay
más progreso (para no perder tiempo y recursos):
parada_temprana_cb = keras.callbacks.EarlyStopping(paciencia=10,
restaurar_mejores_pesos=Verdadero)
historia = modelo.fit(X_train, y_train, épocas=100,
datos_validacion=(X_válido, y_válido),
devoluciones de llamada=[punto de control_cb, detención_temprana_cb])
Machine Translated by Google
La cantidad de épocas se puede configurar en un valor grande, ya que el entrenamiento se detendrá
automáticamente cuando no haya más progreso. En este caso, no es necesario restaurar el mejor
modelo guardado porque la devolución de llamada EarlyStopping hará un seguimiento de los mejores pesos
y los restaurará por usted al final del entrenamiento.
CONSEJO
Hay muchas otras devoluciones de llamadas disponibles en el paquete keras.callbacks.
Si necesita control adicional, puede escribir fácilmente sus propias devoluciones de llamadas personalizadas.
Como ejemplo de cómo hacerlo, la siguiente devolución de llamada personalizada mostrará la
relación entre la pérdida de validación y la pérdida de entrenamiento durante el entrenamiento (por ejemplo,
para detectar sobreajuste):
clase PrintValTrainRatioCallback(keras.callbacks.Callback):
def on_epoch_end(self, época, registros):
print("\nval/train: {:.2f}".format(registros["val_loss"] / registros["loss"]))
Como es de esperar, puede implementar on_train_begin(), on_train_end(), on_epoch_begin(),
on_epoch_end(), on_batch_begin() y on_batch_end(). Las devoluciones de llamadas
también se pueden utilizar durante la evaluación y las predicciones, en caso de que las necesite (por
ejemplo, para la depuración). Para la evaluación, debe implementar on_test_begin(), on_test_end(),
on_test_batch_begin() o on_test_batch_end() (llamados por evaluation()), y para la predicción debe
implementar on_predict_begin(), on_predict_end(), on_predict_batch_begin() o on_predict_batch_end()
(llamados por predict()).
Ahora echemos un vistazo a una herramienta más que definitivamente deberías tener en tu caja de
herramientas al usar tf.keras: TensorBoard.
Machine Translated by Google
Uso de TensorBoard para visualización TensorBoard
es una excelente herramienta de visualización interactiva que puede usar para
ver las curvas de aprendizaje durante el entrenamiento, comparar curvas de
aprendizaje entre múltiples ejecuciones, visualizar el gráfico de cálculo, analizar
estadísticas de entrenamiento, ver imágenes generadas por su
modelo, visualizar datos multidimensionales complejos proyectados en 3D y
agrupados automáticamente para usted, ¡y más! Esta herramienta se instala
automáticamente cuando instala TensorFlow, por lo que ya la tiene.
Para usarlo, debe modificar su programa para que muestre los datos que desea visualizar
en archivos de registro binarios especiales llamados archivos de eventos. Cada registro de
datos binarios se denomina resumen. El servidor TensorBoard supervisará el directorio de
registro y detectará automáticamente los cambios y actualizará las visualizaciones: esto le
permite visualizar datos en vivo (con un breve retraso), como las curvas de aprendizaje
durante el entrenamiento. En general, desea apuntar el servidor TensorBoard a un
directorio de registro raíz y configurar su programa para que escriba en un subdirectorio
diferente cada vez que se ejecute. De esta manera, la misma instancia del servidor
TensorBoard le permitirá visualizar y comparar datos de múltiples ejecuciones de su
programa, sin mezclar todo.
Comencemos por definir el directorio de registro raíz que usaremos para
nuestros registros de TensorBoard, además de una pequeña función que generará una
ruta de subdirectorio basada en la fecha y hora actuales para que sea diferente en cada ejecución.
Es posible que desees incluir información adicional en el nombre del directorio de registro,
como los valores de hiperparámetros que estás probando, para que sea más fácil saber
qué estás viendo en TensorBoard:
importar os
root_logdir = os.path.join(os.curdir, "mis_registros")
def get_run_logdir(): import
time id_ejecución
= time.strftime("ejecución_%Y_%m_%d­%H_%M_%S") return
os.path.join(root_logdir, id_ejecución)
ejecutar_logdir = obtener_ejecutar_logdir()
por ejemplo, './mis_registros/ejecución_2019_06_07­15_15_22' #
Machine Translated by Google
La buena noticia es que Keras proporciona una buena devolución de llamada TensorBoard():
[...]
#
Construye y compila tu modelo
tensorboard_cb = keras.callbacks.TensorBoard(run_logdir)
historia = modelo.fit(X_train, y_train, épocas=30,
datos_validacion=(X_válido, y_válido),
devoluciones de llamada=[tensorboard_cb])
¡Y eso es todo! No podría ser más fácil de usar. Si ejecutas este
código, la devolución de llamada TensorBoard() se encargará de crear el registro
directorio para usted (junto con sus directorios principales si es necesario) y durante
entrenamiento creará archivos de eventos y escribirá resúmenes en ellos. Después
ejecutando el programa una segunda vez (quizás cambiando algunos
valor del hiperparámetro), terminará con una estructura de directorio similar
A este:
mis_registros/
├── carrera_2019_06_07­15_15_22
│ ├── tren
│ │ ├──
eventos.out.tfevents.1559891732.mycomputer.local.38511.694049.v2
├── eventos.out.tfevents.1559891732.micomputadora.local.perfil­vacío
└──
plugins/perfil/2019­06­07_15­15­32
└── rastreo local
│ │ │ └── validación
└──
│ │ │ │ │ eventos.out.tfevents.1559891733.mycomputer.local.38511.696430.v2
└──
carrera_2019_06_07­15_15_49
└──
[...]
Hay un directorio por ejecución, cada uno contiene un subdirectorio para
registros de entrenamiento y uno para registros de validación. Ambos contienen archivos de eventos, pero el
Los registros de entrenamiento también incluyen rastros de creación de perfiles: esto permite a TensorBoard
Te mostramos exactamente cuánto tiempo pasó el modelo en cada parte de tu
modelo, en todos sus dispositivos, lo cual es ideal para localizar el rendimiento
cuellos de botella.
A continuación, debe iniciar el servidor TensorBoard. Una forma de hacerlo es mediante
ejecutar un comando en una terminal. Si instaló TensorFlow dentro de una
Machine Translated by Google
virtualenv, debes activarlo. A continuación, ejecuta el siguiente comando en la raíz del proyecto
(o desde cualquier otro lugar, siempre que apuntes al directorio de registro adecuado):
$ tensorboard ­­logdir=./my_logs ­­port=6006 TensorBoard
2.0.0 en http://mycomputer.local:6006/ (Presione CTRL+C para salir)
Si su shell no puede encontrar el script de tensorboard , entonces debe actualizar su variable de
entorno PATH para que contenga el directorio en el que se instaló el script (alternativamente,
puede simplemente reemplazar tensorboard en la línea de comandos con python3 ­m
tensorboard.main). Una vez que el servidor esté en funcionamiento, puede abrir un navegador
web e ir a http://localhost:6006.
Como alternativa, puede utilizar TensorBoard directamente en Jupyter ejecutando los siguientes
comandos. La primera línea carga la extensión TensorBoard y la segunda línea inicia un servidor
TensorBoard en el puerto 6006 (a menos que ya esté iniciado) y se conecta a él:
%load_ext tensorboard
%tensorboard ­­logdir=./mis_registros ­­port=6006
De cualquier manera, debería ver la interfaz web de TensorBoard. Haga clic en la
pestaña ESCALARES para ver las curvas de aprendizaje (consulte la Figura 10­17). En la
parte inferior izquierda, seleccione los registros que desea visualizar (por ejemplo, los registros
de entrenamiento de la primera y la segunda ejecución) y haga clic en el escalar epoch_loss.
Observe que la pérdida de entrenamiento disminuyó de manera adecuada durante ambas
ejecuciones, pero la segunda ejecución disminuyó mucho más rápido. De hecho,
usamos una tasa de aprendizaje de 0,05 (optimizer=keras.optimizers.SGD(lr=0.05)) en lugar de 0,001.
Machine Translated by Google
Figura 10­17. Visualización de curvas de aprendizaje con TensorBoard
También puedes visualizar el gráfico completo, los pesos aprendidos (proyectados a
3D) o los rastros de perfilado. La devolución de llamada TensorBoard() tiene opciones para
También registre datos adicionales, como incrustaciones (consulte el Capítulo 13).
Además, TensorFlow ofrece una API de nivel inferior en tf.summary
paquete. El siguiente código crea un SummaryWriter utilizando el
función create_file_writer() y utiliza este escritor como contexto para
escalares logarítmicos, histogramas, imágenes, audio y texto, todo lo cual puede luego ser
Visualizado usando TensorBoard (¡pruébalo!):
test_logdir = obtener_ejecutar_logdir()
escritor = tf.summary.create_file_writer(test_logdir)
con writer.as_default():
para paso en rango(1, 1000 + 1):
tf.summary.scalar("mi_escalar", np.sin(paso / 10), paso=paso)
# Algunos datos aleatorios
datos = (np.random.randn(100) + 2) * paso / 100
tf.summary.histogram("my_hist", datos, buckets=50, paso=paso)
# aleatorio 32×32 RGB imágenes
imágenes = np.random.rand(2, 32, 32, 3)
tf.summary.image("mis_imágenes", imágenes * paso / 1000, paso=paso)
"
" +
textos = ["El paso es + str(paso), "Su cuadrado es
Machine Translated by Google
str(paso**2)]
tf.summary.text("mi_texto", textos, paso=paso)
onda_sinusoidal = tf.math.sin(tf.range(12000) / 48000 * 2 * np.pi *
paso)
audio = tf.reshape(tf.cast(onda_sine, tf.float32), [1, ­1, 1])
tf.summary.audio("mi_audio", audio, frecuencia_de_muestreo=48000, paso=paso)
En realidad, esta es una herramienta de visualización útil, incluso más allá
de TensorFlow o Deep Learning.
Resumamos lo que has aprendido hasta ahora en este capítulo: vimos de dónde vienen
las redes neuronales, qué es un MLP y cómo puedes usarlo para clasificación y
regresión, cómo usar la API secuencial de tf.keras para crear MLP y cómo usar la API
funcional o la API de subclases para crear arquitecturas de modelos más complejas.
Aprendiste a guardar y restaurar un modelo y a usar devoluciones de llamadas para
puntos de control, detención temprana y más. Finalmente, aprendiste a usar
TensorBoard para visualización. ¡Ya puedes seguir adelante y usar redes neuronales
para abordar muchos problemas!
Sin embargo, es posible que te preguntes cómo elegir la cantidad de capas ocultas, la
cantidad de neuronas en la red y todos los demás hiperparámetros. Veámoslo ahora.
Ajuste fino de los hiperparámetros de las redes
neuronales La flexibilidad de las
redes neuronales es también uno de sus principales inconvenientes: hay muchos
hiperparámetros que ajustar. No solo se puede utilizar cualquier arquitectura de
red imaginable, sino que incluso en un MLP simple se puede cambiar el número
de capas, el número de neuronas por capa, el tipo de función de activación a utilizar en
cada capa, la lógica de inicialización de pesos y mucho más. ¿Cómo saber qué
combinación de hiperparámetros es la mejor para su tarea?
Una opción es simplemente probar muchas combinaciones de hiperparámetros y ver
cuál funciona mejor en el conjunto de validación (o utilizar la validación cruzada
K­fold). Por ejemplo, podemos utilizar GridSearchCV o
Machine Translated by Google
RandomizedSearchCV para explorar el espacio de hiperparámetros, como hicimos en
el Capítulo 2. Para ello, necesitamos envolver nuestros modelos Keras en objetos que
imiten los regresores Scikit­Learn habituales. El primer paso es crear una función que
construya y compile un modelo Keras, dado un conjunto de hiperparámetros:
def build_model(n_oculto=1, n_neuronas=30, tasa_de_aprendizaje=3e­3, forma_de_entrada=[8]):
modelo =
keras.models.Sequential()
modelo.add(keras.layers.InputLayer(forma_de_entrada=forma_de_entrada)) para capa en
rango(n_oculto):
modelo.add(keras.layers.Dense(n_neurons, activación="relu"))
model.add(keras.layers.Dense(1)) optimizador =
keras.optimizers.SGD(lr=tasa_de_aprendizaje) model.compile(pérdida="mse",
optimizador=optimizador) devolver modelo
Esta función crea un modelo secuencial simple para regresión univariante
(solo una neurona de salida), con la forma de entrada dada y la cantidad dada de
capas ocultas y neuronas, y lo compila utilizando un optimizador SGD configurado
con la tasa de aprendizaje especificada. Es una buena práctica proporcionar
valores predeterminados razonables para tantos hiperparámetros como sea posible, como
lo hace Scikit­Learn.
A continuación, crearemos un KerasRegressor basado en esta función
build_model():
keras_reg = keras.wrappers.scikit_learn.KerasRegressor(build_model)
El objeto KerasRegressor es una envoltura delgada alrededor del modelo Keras
creado con build_model(). Como no especificamos ningún hiperparámetro al crearlo,
utilizará los hiperparámetros predeterminados que definimos en build_model(). Ahora
podemos usar este objeto como un regresor Scikit­Learn normal: podemos entrenarlo
con su método fit(), luego evaluarlo con su método score() y usarlo para hacer predicciones
con su método predict(), como puede ver en el siguiente código:
Machine Translated by Google
keras_reg.fit(X_train, y_train, épocas=100,
datos_validación=(X_valid, y_valid),
devoluciones_de_llamadas=[keras.devoluciones_de_llamadas.EarlyStopping(paciencia=10)]) mse_test = keras_reg
Tenga en cuenta que cualquier parámetro adicional que pase al método fit() se
pasará al modelo Keras subyacente. Tenga en cuenta también que la puntuación será la
opuesta al MSE porque Scikit­Learn quiere puntuaciones, no pérdidas (es decir, cuanto
más alto, mejor).
No queremos entrenar y evaluar un único modelo como este, aunque queremos
entrenar cientos de variantes y ver cuál funciona mejor en el conjunto de validación.
Dado que hay muchos hiperparámetros, es preferible utilizar una búsqueda aleatoria
en lugar de una búsqueda en cuadrícula (como analizamos en el Capítulo 2).
Intentemos explorar la cantidad de capas ocultas, la cantidad de neuronas y la tasa de
aprendizaje:
desde scipy.stats importar reciprocal desde
sklearn.model_selection importar RandomizedSearchCV
param_distribs =
{ "n_oculto": [0, 1, 2, 3],
"n_neuronas": np.arange(1, 100),
"tasa_de_aprendizaje": recíproco(3e­4, 3e­2),
}
rnd_search_cv = RandomizedSearchCV(keras_reg, param_distribs, n_iter=10, cv=3)
rnd_search_cv.fit(X_train, y_train, épocas=100,
datos_validación=(X_valid, y_valid),
devoluciones de llamada=[keras.devolucionesdellamada.Detencióntemprana(paciencia=10)
Esto es idéntico a lo que hicimos en el Capítulo 2, excepto que aquí pasamos
parámetros adicionales al método fit() y se transmiten a los modelos Keras
subyacentes. Tenga en cuenta que RandomizedSearchCV utiliza K­fold cross­
validación, por lo que no utiliza X_valid e y_valid, que solo se utilizan para la detención
anticipada.
Machine Translated by Google
La exploración puede durar muchas horas, dependiendo del hardware, el tamaño del conjunto
de datos, la complejidad del modelo y los valores de n_iter y cv. Cuando termine, podrá
acceder a los mejores parámetros encontrados, la mejor puntuación y el modelo Keras
entrenado de la siguiente manera:
>>> rnd_search_cv.best_params_
{'tasa_de_aprendizaje': 0,0033625641252688094, 'n_oculto': 2, 'n_neuronas': 42} >>> rnd_search_cv.best_score_
­0,3189529188278931
>>> modelo = rnd_search_cv.mejor_estimador_.modelo
Ahora puede guardar este modelo, evaluarlo en el conjunto de pruebas y, si está satisfecho
con su rendimiento, implementarlo en producción. El uso de la búsqueda aleatoria no es
demasiado difícil y funciona bien para muchos problemas bastante simples.
Sin embargo, cuando el entrenamiento es lento (por ejemplo, para problemas más
complejos con conjuntos de datos más grandes), este enfoque solo explorará una
pequeña porción del espacio de hiperparámetros. Puede aliviar parcialmente este
problema al ayudar al proceso de búsqueda manualmente: primero ejecute una búsqueda
aleatoria rápida utilizando rangos amplios de valores de hiperparámetros, luego ejecute otra
búsqueda utilizando rangos de valores más pequeños centrados en los mejores encontrados
durante la primera ejecución, y así sucesivamente. Con suerte, este enfoque se centrará
en un buen conjunto de hiperparámetros. Sin embargo, consume mucho tiempo y probablemente
no sea la mejor manera de utilizar su tiempo.
Afortunadamente, existen muchas técnicas para explorar un espacio de búsqueda de
forma mucho más eficiente que de forma aleatoria. La idea central es simple: cuando una
región del espacio resulta ser buena, se debe explorar más. Estas técnicas se
encargan del proceso de “zoom” por usted y conducen a soluciones mucho mejores en
mucho menos tiempo. A continuación, se muestran algunas bibliotecas de Python que puede
utilizar para optimizar los hiperparámetros:
Hiperóptico
Una biblioteca popular para optimizar todo tipo de espacios de búsqueda complejos
(incluidos valores reales, como la tasa de aprendizaje, y valores discretos, como el
número de capas).
Hiperas, Coger, o Talos
Machine Translated by Google
Bibliotecas útiles para optimizar hiperparámetros para modelos Keras (las dos primeras se
basan en Hyperopt).
Sintonizador Keras
Una biblioteca de optimización de hiperparámetros fácil de usar de Google para
modelos Keras, con un servicio alojado para visualización y análisis.
Optimización de Scikit (skopt)
Una biblioteca de optimización de propósito general. La clase BayesSearchCV
realiza la optimización bayesiana mediante una interfaz similar a
GridSearchCV.
Menta verde
Una biblioteca de optimización bayesiana.
Hiperbanda
Una biblioteca de ajuste rápido de hiperparámetros basada en el reciente artículo
22 de Lisha Li et al.
Hyperband
Sklearn­Deap
Una biblioteca de optimización de hiperparámetros basada en algoritmos
evolutivos, con una interfaz similar a GridSearchCV.
Además, muchas empresas ofrecen servicios de optimización de hiperparámetros.
Analizaremos el servicio de ajuste de hiperparámetros de Google Cloud AI Platform en el
Capítulo 19. Otras opciones incluyen los servicios de Arimo y SigOpt, y el Oscar de CallDesk .
El ajuste de hiperparámetros sigue siendo un área activa de investigación y los algoritmos
evolutivos están volviendo a ponerse de moda. Por ejemplo, consulte el excelente artículo de
23 autores optimizan conjuntamente una población de modelos y
DeepMind de 2017, donde los
sus hiperparámetros. Google también ha utilizado un enfoque evolutivo, no solo para buscar
hiperparámetros sino también para buscar la mejor arquitectura de red neuronal para el
problema; su suite AutoML ya está disponible como servicio en la nube. Tal vez hayan
pasado los días en que se construían redes neuronales.
Machine Translated by Google
¿Pronto se acabará la configuración manual de redes? Consulta la publicación de Google
sobre este tema. De hecho, se han utilizado algoritmos evolutivos con éxito para entrenar redes
neuronales individuales, reemplazando al omnipresente Gradient Descent.
Para ver un ejemplo, consulte la publicación de 2017 por Uber donde los autores presentan
su técnica de Neuroevolución Profunda .
Pero a pesar de todo este emocionante progreso y de todas estas herramientas y servicios,
aún es útil tener una idea de qué valores son razonables para cada hiperparámetro,
de modo que se pueda construir un prototipo rápido y restringir el espacio de búsqueda. Las
siguientes secciones proporcionan pautas para elegir la cantidad de capas ocultas y neuronas
en un MLP y para seleccionar buenos valores para algunos de los hiperparámetros principales.
Número de capas ocultas En muchos
problemas, se puede empezar con una sola capa oculta y obtener resultados razonables.
Un MLP con una sola capa oculta puede modelar teóricamente incluso las funciones más
complejas, siempre que tenga suficientes neuronas.
Pero para problemas complejos, las redes profundas tienen una eficiencia de parámetros
mucho mayor que las redes superficiales: pueden modelar funciones complejas utilizando
exponencialmente menos neuronas que las redes superficiales, lo que les permite alcanzar
un rendimiento mucho mejor con la misma cantidad de datos de entrenamiento.
Para entender por qué, supongamos que se le pide que dibuje un bosque utilizando algún
software de dibujo, pero tiene prohibido copiar y pegar nada. Se necesitaría una enorme
cantidad de tiempo: tendría que dibujar cada árbol individualmente, rama por rama, hoja por
hoja. Si en cambio pudiera dibujar una hoja, copiarla y pegarla para dibujar una rama, luego
copiar y pegar esa rama para crear un árbol, y finalmente copiar y pegar este árbol para
hacer un bosque, terminaría en poco tiempo. Los datos del mundo real a menudo se
estructuran de esa manera jerárquica, y las redes neuronales profundas aprovechan
automáticamente este hecho: las capas ocultas inferiores modelan estructuras de
bajo nivel (por ejemplo, segmentos de línea de varias formas y orientaciones), las capas ocultas
intermedias combinan estas estructuras de bajo nivel para modelar estructuras de nivel
intermedio (por ejemplo, cuadrados, círculos) y las capas ocultas más altas combinan estas
estructuras de bajo nivel para modelar estructuras de nivel intermedio (por ejemplo, cuadrados, círculos).
Machine Translated by Google
Las capas ocultas y la capa de salida combinan estas estructuras intermedias para
modelar estructuras de alto nivel (por ejemplo, caras).
Esta arquitectura jerárquica no solo ayuda a las DNN a converger más rápido hacia una
buena solución, sino que también mejora su capacidad de generalizarse a nuevos
conjuntos de datos. Por ejemplo, si ya ha entrenado un modelo para reconocer rostros
en imágenes y ahora desea entrenar una nueva red neuronal para reconocer
peinados, puede iniciar el entrenamiento reutilizando las capas inferiores de la primera
red. En lugar de inicializar aleatoriamente los pesos y sesgos de las primeras capas de
la nueva red neuronal, puede inicializarlas con los valores de los pesos y sesgos de
las capas inferiores de la primera red. De esta manera, la red no tendrá que aprender
desde cero todas las estructuras de bajo nivel que aparecen en la mayoría de las
imágenes; solo tendrá que aprender las estructuras de nivel superior (por ejemplo, los
peinados). Esto se denomina aprendizaje por transferencia.
En resumen, para muchos problemas se puede empezar con solo una o dos capas ocultas
y la red neuronal funcionará bien. Por ejemplo, se puede alcanzar fácilmente una
precisión superior al 97 % en el conjunto de datos MNIST utilizando solo una capa
oculta con unos pocos cientos de neuronas, y una precisión superior al 98 % utilizando
dos capas ocultas con la misma cantidad total de neuronas, en aproximadamente la
misma cantidad de tiempo de entrenamiento. Para problemas más complejos, se puede
aumentar la cantidad de capas ocultas hasta que se comience a sobreajustar el conjunto de entrenamiento.
Las tareas muy complejas, como la clasificación de imágenes de gran tamaño
o el reconocimiento de voz, suelen requerir redes con docenas de capas (o incluso
cientos, pero no totalmente conectadas, como veremos en el Capítulo 14) y necesitan una
enorme cantidad de datos de entrenamiento. Rara vez tendrá que entrenar estas redes
desde cero: es mucho más común reutilizar partes de una red de última generación
previamente entrenada que realiza una tarea similar. El entrenamiento será entonces
mucho más rápido y requerirá muchos menos datos (lo analizaremos en el Capítulo 11).
Número de neuronas por capa oculta El número de neuronas
en las capas de entrada y salida está determinado por el tipo de entrada y salida que
requiere la tarea. Por ejemplo, la tarea MNIST
Machine Translated by Google
requiere 28 × 28 = 784 neuronas de entrada y 10 neuronas de salida.
En cuanto a las capas ocultas, solía ser común dimensionarlas para formar una
pirámide, con cada vez menos neuronas en cada capa, con el argumento de que muchas
características de bajo nivel pueden fusionarse en muchas menos características
de alto nivel. Una red neuronal típica para MNIST podría tener 3 capas ocultas: la primera
con 300 neuronas, la segunda con 200 y la tercera con 100.
Sin embargo, esta práctica se ha abandonado en gran medida porque parece que usar
la misma cantidad de neuronas en todas las capas ocultas funciona igual de bien en
la mayoría de los casos, o incluso mejor; además, solo hay un hiperparámetro para
ajustar, en lugar de uno por capa. Dicho esto, dependiendo del conjunto de datos, a veces
puede resultar útil hacer que la primera capa oculta sea más grande que las demás.
Al igual que con el número de capas, puedes intentar aumentar el número de
neuronas gradualmente hasta que la red comience a sobreajustarse. Pero en la práctica,
suele ser más simple y eficiente elegir un modelo con más capas y neuronas de las
que realmente necesitas y luego usar técnicas de detención temprana y otras
técnicas de regularización para evitar el sobreajuste. Vincent Vanhoucke, un
científico de Google, ha denominado a esto el enfoque de los "pantalones
elásticos": en lugar de perder el tiempo buscando pantalones que se adapten
perfectamente a tu talla, simplemente usa pantalones elásticos grandes que se
encojan hasta la talla correcta. Con este enfoque, evitas las capas de cuello de botella
que podrían arruinar tu modelo. Por otro lado, si una capa tiene muy pocas neuronas,
no tendrá suficiente poder de representación para preservar toda la información útil de las
entradas (por ejemplo, una capa con dos neuronas solo puede generar datos 2D, por lo
que si procesa datos 3D, se perderá parte de la información). No importa cuán grande y
poderoso sea el resto de la red, esa información nunca se recuperará.
CONSEJO
En general, obtendrás más beneficios por tu inversión si aumentas el número de
capas en lugar del número de neuronas por capa.
Tasa de aprendizaje, tamaño de lote y otros hiperparámetros
Machine Translated by Google
La cantidad de capas ocultas y neuronas no son los únicos hiperparámetros que se pueden
modificar en un MLP. A continuación, se indican algunos de los más importantes, así como consejos sobre
cómo configurarlos:
Tasa de aprendizaje
La tasa de aprendizaje es posiblemente el hiperparámetro más importante. En general, la tasa de
aprendizaje óptima es aproximadamente la mitad de la tasa de aprendizaje máxima (es decir, la
tasa de aprendizaje por encima de la cual el algoritmo de entrenamiento diverge, como vimos en el Capítulo
4). Una forma de encontrar una buena tasa de aprendizaje es entrenar el modelo durante unos cientos de
iteraciones, comenzando con una tasa de aprendizaje muy baja (por ejemplo, 10) y aumentándola
gradualmente hasta un valor muy grande­5(por ejemplo, 10). Esto se hace multiplicando la tasa de
aprendizaje por un
factor constante en cada iteración (p. ej., por exp(log(10 )/500) para pasar de 10 a610 en 500 iteraciones). Si
­5 la pérdida como una función de la tasa de aprendizaje (usando una escala logarítmica para la
graficas
tasa de aprendizaje), deberías ver que disminuye al principio. Pero después de un tiempo, la tasa de
aprendizaje será demasiado grande, por lo que la pérdida se disparará de nuevo: la tasa de aprendizaje
óptima será un poco más baja que el punto en el que la pérdida comienza a subir (normalmente unas 10
veces más baja que el punto de inflexión). Luego puedes reiniciar tu modelo y entrenarlo normalmente
usando esta buena tasa de aprendizaje. Veremos más técnicas de tasa de aprendizaje en el Capítulo 11.
Optimizador
También es muy importante elegir un optimizador mejor que el clásico Mini­batch Gradient Descent
(y ajustar sus hiperparámetros). Veremos varios optimizadores avanzados en el Capítulo 11.
Tamaño del lote
El tamaño del lote puede tener un impacto significativo en el rendimiento y el tiempo de
entrenamiento del modelo. El principal beneficio de usar lotes de gran tamaño es que los aceleradores de
hardware, como las GPU, pueden procesarlos de manera eficiente (consulte el Capítulo 19), por
lo que el algoritmo de entrenamiento verá más instancias por segundo. Por lo tanto, muchos investigadores
y profesionales recomiendan usar el tamaño de lote más grande que quepa en la RAM de la GPU.
Machine Translated by Google
Sin embargo, hay un problema: en la práctica, los lotes de gran tamaño suelen
provocar inestabilidades en el entrenamiento, especialmente al principio del mismo, y
el modelo resultante puede no generalizarse tan bien como un modelo entrenado con
un tamaño de lote pequeño. En abril de 2018, Yann LeCun incluso tuiteó: “Los amigos
no dejan que sus amigos utilicen minilotes de más de 32”, citando un artículo de
24
2018 de Dominic Masters y Carlo Luschi que concluía que era preferible utilizar lotes
pequeños (de 2 a 32) porque los lotes pequeños conducían a mejores modelos en
menos tiempo de entrenamiento. Otros artículos señalan
Sin embargo, la dirección opuesta: en 2017, los artículos de Elad Hoffer et al. y Priya
25
26
Goyal et al. Se demostró que era posible utilizar tamaños de lote muy grandes (hasta
8192) utilizando varias técnicas, como el calentamiento de la tasa de aprendizaje (es
decir, iniciar el entrenamiento con una tasa de aprendizaje pequeña y luego aumentarla,
como veremos en el Capítulo 11). Esto condujo a un tiempo de entrenamiento muy
corto, sin ninguna brecha de generalización. Por lo tanto, una estrategia es intentar utilizar
un tamaño de lote grande, utilizando el calentamiento de la tasa de aprendizaje y, si el
entrenamiento es inestable o el rendimiento final es decepcionante, entonces intentar
utilizar un tamaño de lote pequeño en su lugar.
Función de activación
Ya hemos hablado de cómo elegir la función de activación en este capítulo: en
general, la función de activación ReLU será una buena opción predeterminada
para todas las capas ocultas. Para la capa de salida, realmente depende de la tarea.
Número de iteraciones
En la mayoría de los casos, no es necesario modificar el número de iteraciones de
entrenamiento: basta con utilizar la detención anticipada.
CONSEJO
La tasa de aprendizaje óptima depende de otros hiperparámetros, especialmente el tamaño del
lote, por lo que si modifica algún hiperparámetro, asegúrese de actualizar también la tasa de aprendizaje.
Machine Translated by Google
Para conocer más prácticas recomendadas sobre el ajuste de hiperparámetros de redes
27
neuronales, consulte el excelente artículo de 2018 por Leslie Smith.
Con esto concluye nuestra introducción a las redes neuronales artificiales y su
implementación con Keras. En los próximos capítulos, analizaremos técnicas para
entrenar redes muy profundas. También exploraremos cómo personalizar modelos utilizando
la API de nivel inferior de TensorFlow y cómo cargar y preprocesar datos de
manera eficiente utilizando la API de datos. Y profundizaremos en otras arquitecturas de
redes neuronales populares: redes neuronales convolucionales para procesamiento de
imágenes, redes neuronales recurrentes para datos secuenciales,
autocodificadores para aprendizaje de representación y redes generativas antagónicas
28
para modelar y generar datos.
Ceremonias
1. El patio de juegos de TensorFlow es un simulador de redes neuronales muy útil
creado por el equipo de TensorFlow. En este ejercicio, entrenará varios
clasificadores binarios con solo unos clics y ajustará la arquitectura del
modelo y sus hiperparámetros para obtener una idea intuitiva de cómo
funcionan las redes neuronales y qué hacen sus hiperparámetros.
Tómese un tiempo para explorar lo siguiente:
a. Los patrones aprendidos por una red neuronal. Intente entrenarlos
Red neuronal predeterminada haciendo clic en el botón Ejecutar
(arriba a la izquierda). Observe cómo encuentra rápidamente una
buena solución para la tarea de clasificación. Las neuronas de la
primera capa oculta han aprendido patrones simples, mientras que
las neuronas de la segunda capa oculta han aprendido a combinar los
patrones simples de la primera capa oculta en patrones más
complejos. En general, cuantas más capas haya, más complejos
pueden ser los patrones.
b. Funciones de activación. Intente reemplazar la función de activación
tanh con una función de activación ReLU y vuelva a entrenar la red.
Observe que encuentra una solución incluso más rápido.
Machine Translated by Google
Pero esta vez los límites son lineales. Esto se debe a la forma de la función
ReLU.
c. El riesgo de mínimos locales. Modificar la red
Arquitectura para tener solo una capa oculta con tres neuronas.
Entrénela varias veces (para restablecer los pesos de la red, haga clic
en el botón Restablecer junto al botón Reproducir).
Tenga en cuenta que el tiempo de entrenamiento varía mucho y, a veces,
incluso se queda estancado en un mínimo local.
d. ¿Qué sucede cuando las redes neuronales son demasiado pequeñas?
Elimina una neurona para conservar solo dos. Observa que la red
neuronal ahora es incapaz de encontrar una buena solución, incluso si
lo intentas varias veces. El modelo tiene muy pocos parámetros y
sistemáticamente se ajusta por debajo del conjunto de entrenamiento.
e. ¿Qué sucede cuando las redes neuronales son lo suficientemente grandes?
Establezca el número de neuronas en ocho y entrene la red varias veces.
Observe que ahora es constantemente rápida y nunca se bloquea. Esto
resalta un hallazgo importante en la teoría de redes neuronales: las redes
neuronales grandes casi nunca se bloquean en mínimos locales, e incluso
cuando lo hacen, estos óptimos locales son casi tan buenos como el óptimo
global.
Sin embargo, todavía pueden quedarse estancados en mesetas prolongadas durante
mucho tiempo.
f. El riesgo de que los gradientes se desvanezcan en las redes profundas.
Seleccione el conjunto de datos en espiral (el conjunto de datos
de la parte inferior derecha bajo “DATOS”) y cambie la arquitectura de la
red para que tenga cuatro capas ocultas con ocho neuronas cada una.
Observe que el entrenamiento lleva mucho más tiempo y, a menudo,
se queda estancado en mesetas durante largos períodos de tiempo.
Observe también que las neuronas en las capas más altas (a la derecha)
tienden a evolucionar más rápido que las neuronas en las capas más bajas (a la izquierda).
Este problema, llamado el problema de los “gradientes que desaparecen”,
se puede aliviar con una mejor inicialización del peso y
Machine Translated by Google
otras técnicas, mejores optimizadores (como AdaGrad o Adam) o
normalización por lotes (que se analiza en el Capítulo
11).
g. Vaya más allá. Tómese una hora aproximadamente para jugar con
otros parámetros y familiarizarse con su función, para así poder
entender intuitivamente las redes neuronales.
2. Dibuje una ANN usando las neuronas artificiales originales (como las de la
Figura 10­3) que calcule A
Pista: A
B = (A
¬B
B (donde
(¬ A
representa la operación XOR).
B).
3. ¿Por qué generalmente es preferible utilizar una regresión logística?
¿Cómo se puede modificar un perceptrón para que sea equivalente a un
clasificador de regresión logística?
4. ¿Por qué la función de activación logística fue un ingrediente clave en el
entrenamiento de los primeros MLP?
5. Nombra tres funciones de activación conocidas. ¿Puedes dibujarlas?
6. Supongamos que tenemos un MLP compuesto por una capa de entrada con
10 neuronas de paso, seguida de una capa oculta con 50 neuronas
artificiales y, por último, una capa de salida con 3 neuronas artificiales.
Todas las neuronas artificiales utilizan la función de activación ReLU.
¿Cuál es la forma de la matriz de entrada X?
¿Cuáles son las formas del vector de peso W de la capa oculta y
su yovector de sesgo b ?
yo
¿Cuáles son las formas del vector de peso W de la capa de salida
y suo vector de polarización b ?o
¿Cuál es la forma de la matriz de salida Y de la red ?
Machine Translated by Google
Escribe la ecuación que calcula la salida de la red.
matriz Y en función de X, W
,
, Yo o , y b .
bhhh
o
7. ¿Cuántas neuronas necesitas en la capa de salida si quieres?
¿Clasificar el correo electrónico como spam o correo basura? ¿Qué función de activación debería utilizar?
¿Qué utilizas en la capa de salida? Si, en cambio, quieres abordar MNIST,
¿Cuántas neuronas necesitas en la capa de salida y cuáles?
¿Qué función de activación deberías utilizar? ¿Qué tal para conseguir tu
¿Red para predecir los precios de la vivienda, como en el Capítulo 2?
8. ¿Qué es la retropropagación y cómo funciona? ¿Qué es la
¿Diferencia entre retropropagación y autodiff en modo inverso?
9. ¿Puedes enumerar todos los hiperparámetros que puedes modificar en un lenguaje básico?
¿MLP? Si el MLP sobreajusta los datos de entrenamiento, ¿cómo podría modificarlo?
¿Estos hiperparámetros para intentar solucionar el problema?
10. Entrena un MLP profundo en el conjunto de datos MNIST (puedes cargarlo usando
keras.datasets.mnist.load_data(). Vea si puede superarlo
98% de precisión. Intente buscar la tasa de aprendizaje óptima por
utilizando el enfoque presentado en este capítulo (es decir, haciendo crecer el
tasa de aprendizaje exponencialmente, graficando el error y encontrando el
punto donde se dispara el error). Intente agregar todas las campanas y
Silbatos: guarde los puntos de control, utilice paradas tempranas y trace el aprendizaje
curvas utilizando TensorBoard.
Las soluciones a estos ejercicios están disponibles en el Apéndice A.
1 Puedes obtener lo mejor de ambos mundos estando abierto a las inspiraciones biológicas sin ser...
tienen miedo de crear modelos biológicamente irreales, siempre y cuando funcionen bien.
2 Warren S. McCulloch y Walter Pitts, “Un cálculo lógico de las ideas inmanentes en
Actividad nerviosa”, The Bulletin of Mathematical Biology 5, no. 4 (1943): 115–113.
3 En realidad no están unidos, sólo tan cerca que pueden intercambiar sustancias químicas muy rápidamente.
señales.
4 Imagen de Bruce Blaus (Creative Commons 3.0). Reproducido de
https://en.wikipedia.org/wiki/Neuron.
Machine Translated by Google
5 En el contexto del aprendizaje automático, la frase “redes neuronales” generalmente se refiere a
ANN, no BNN.
6 Dibujo de una laminación cortical de S. Ramón y Cajal (dominio público). Reproducido de
https://en.wikipedia.org/wiki/Cerebral_cortex.
7 El nombre Perceptrón se utiliza a veces para referirse a una pequeña red con una única TLU.
8 Nótese que esta solución no es única: cuando los puntos de datos son linealmente separables, hay una
infinidad de hiperplanos que pueden separarlos.
En la década de 1990, una ANN con más de dos capas ocultas se consideraba profunda. Hoy en día, es común ver
ANN con docenas de capas, o incluso cientos, por lo que la definición de “profunda” es bastante confusa.
10 David Rumelhart et al. “Aprendizaje de representaciones internas mediante propagación de errores”,
(Informe técnico del Centro de Información Técnica de Defensa, septiembre de 1985).
11 Esta técnica en realidad fue inventada de forma independiente varias veces por varios investigadores.
en diferentes campos, comenzando con Paul Werbos en 1974.
12 Las neuronas biológicas parecen implementar una función de activación aproximadamente sigmoidea (en forma
de S), por lo que los investigadores se aferraron a las funciones sigmoideas durante mucho tiempo. Pero
resulta que ReLU generalmente funciona mejor en las ANN. Este es uno de los casos en los que la analogía
biológica era engañosa.
13 Proyecto ONEIROS (Sistema Operativo de Robot Inteligente Neuroelectrónico Abierto).
14 Puede utilizar keras.utils.plot_model() para generar una imagen de su modelo.
15 Si sus datos de entrenamiento o validación no coinciden con la forma esperada, recibirá un
Excepción. Este es quizás el error más común, por lo que debe familiarizarse con el mensaje de error. El mensaje
es bastante claro: por ejemplo, si intenta entrenar este modelo con una matriz que contiene imágenes
aplanadas (X_train.reshape(­1, 784)), obtendrá la siguiente excepción: “ValueError: Error al verificar la entrada:
se esperaba que flatten_input tuviera 3 dimensiones, pero se obtuvo una matriz con forma (60000,
784)”.
16 Heng­Tze Cheng et al., “Aprendizaje amplio y profundo para sistemas de recomendación”,
Actas del primer taller sobre aprendizaje profundo para sistemas de recomendación (2016): 7– 10.
La ruta corta también se puede utilizar para proporcionar características diseñadas manualmente al sistema neuronal.
red.
18 El nombre input_ se utiliza para evitar eclipsar la función input() incorporada de Python.
19 Como alternativa, puede pasar un diccionario que asigne los nombres de entrada a los valores de entrada, como
{"wide_input": X_train_A, "deep_input": X_train_B}. Esto es especialmente útil cuando hay muchas entradas,
para evitar que el orden sea incorrecto.
20 Alternativamente, puede pasar un diccionario que asigne cada nombre de salida a la pérdida correspondiente. Al
igual que para las entradas, esto es útil cuando hay múltiples salidas, para evitar que se produzcan errores.
Machine Translated by Google
El orden es incorrecto. Los pesos y las métricas de pérdida (que se analizarán en breve) también se pueden configurar
mediante diccionarios.
21 Los modelos Keras tienen un atributo de salida, por lo que no podemos usar ese nombre para la salida principal.
capa, por eso la renombramos a main_output.
22 Lisha Li et al., “Hyperband: un nuevo enfoque basado en Bandit para la optimización de hiperparámetros”, Journal
of Machine Learning Research 18 (abril de 2018): 1–52.
23 Max Jaderberg et al., “Entrenamiento de redes neuronales basado en la población”, preimpresión de arXiv arXiv:1711.09846
(2017).
24 Dominic Masters y Carlo Luschi, “Revisitando el entrenamiento en lotes pequeños para el aprendizaje neuronal profundo”
Redes”, preimpresión arXiv arXiv:1804.07612 (2018).
25 Elad Hoffer et al., “Entrenar durante más tiempo, generalizar mejor: cerrar la brecha de generalización en el entrenamiento
de grandes lotes de redes neuronales”, Actas de la 31.ª Conferencia internacional sobre sistemas de
procesamiento de información neuronal (2017): 1729–1739.
26 Priya Goyal et al., “SGD de minibatch grande y preciso: entrenamiento de ImageNet en 1 hora”, arXiv
preimpresión arXiv:1706.02677 (2017).
27 Leslie N. Smith, “Un enfoque disciplinado para los hiperparámetros de redes neuronales: Parte 1: tasa de aprendizaje, tamaño
de lote, momento y disminución del peso”, preimpresión de arXiv arXiv:1803.09820 (2018).
En el Apéndice E se presentan algunas arquitecturas ANN adicionales .
Machine Translated by Google
Capítulo 11. Entrenamiento de
redes neuronales profundas
En el capítulo 10, presentamos las redes neuronales artificiales y entrenamos nuestras primeras redes neuronales
profundas. Pero eran redes superficiales, con solo unas pocas capas ocultas. ¿Qué sucede si necesita abordar un
problema complejo, como detectar cientos de tipos de objetos en imágenes de alta resolución? Es posible que
deba entrenar una red neuronal profunda, tal vez con 10 capas o muchas más, cada una con cientos de neuronas,
vinculadas por cientos de miles de conexiones. Entrenar una red neuronal profunda no es un paseo por el
parque. Estos son algunos de los problemas con los que podría encontrarse:
Es posible que te enfrentes al complicado problema de los gradientes que desaparecen o al problema
relacionado de los gradientes explosivos . Esto sucede cuando los gradientes se vuelven
cada vez más pequeños, o cada vez más grandes, al fluir en sentido inverso a través de la red
neuronal de doble cadena durante el entrenamiento. Ambos problemas hacen que las capas inferiores
sean muy difíciles de entrenar.
Es posible que no tengas suficientes datos de entrenamiento para una red tan grande o que sea
demasiado costoso etiquetarla.
El entrenamiento puede ser extremadamente lento.
Un modelo con millones de parámetros correría un grave riesgo de sobreajustar el
conjunto de entrenamiento, especialmente si no hay suficientes instancias de entrenamiento o
si son demasiado ruidosas.
En este capítulo, analizaremos cada uno de estos problemas y presentaremos técnicas para resolverlos.
Comenzaremos explorando los problemas de gradientes evanescentes y explosivos y algunas de sus
soluciones más populares.
A continuación, analizaremos el aprendizaje por transferencia y el preentrenamiento no supervisado, que pueden
ayudarle a abordar tareas complejas incluso cuando dispone de pocos datos etiquetados.
A continuación, analizaremos varios optimizadores que pueden acelerar el entrenamiento de grandes cantidades.
Machine Translated by Google
modelos tremendamente. Por último, repasaremos algunas técnicas de
regularización populares para redes neuronales grandes.
Con estas herramientas podrás entrenar redes muy profundas. ¡Bienvenido al Deep
Learning!
Los gradientes que desaparecen/explotan
Problemas
Como analizamos en el Capítulo 10, el algoritmo de retropropagación funciona pasando de
la capa de salida a la capa de entrada, propagando el gradiente de error a lo largo del
camino. Una vez que el algoritmo ha calculado el gradiente de la función de costo con respecto
a cada parámetro de la red, utiliza estos gradientes para actualizar cada parámetro con
un paso de descenso de gradiente.
Desafortunadamente, los gradientes suelen hacerse cada vez más pequeños a medida que
el algoritmo avanza hacia las capas inferiores. Como resultado, la actualización de
Gradient Descent deja los pesos de conexión de las capas inferiores prácticamente sin
cambios y el entrenamiento nunca converge hacia una buena solución. A esto lo llamamos el
problema de los gradientes que desaparecen . En algunos casos, puede ocurrir lo contrario:
los gradientes pueden hacerse cada vez más grandes hasta que las capas reciben
actualizaciones de peso increíblemente grandes y el algoritmo diverge. Este es el
problema de los gradientes explosivos , que surge en las redes neuronales recurrentes
(consulte el Capítulo 15). De manera más general, las redes neuronales profundas sufren de
gradientes inestables; las diferentes capas pueden aprender a velocidades muy diferentes.
Este desafortunado comportamiento se observó empíricamente hace mucho tiempo, y fue
una de las razones por las que las redes neuronales profundas fueron abandonadas en su
mayoría a principios de la década de 2000. No estaba claro qué causaba que los gradientes
fueran tan inestables al entrenar una DNN, pero un artículo de 2010 arrojó algo de luz al
1 Bengio. Los autores encontraron algunos sospechosos,
respecto. por Xavier Glorot y Yoshua
incluida la combinación de la popular función de activación sigmoidea logística y la técnica
de inicialización de peso que era más popular en ese momento (es decir, una distribución
normal con una media de 0 y una desviación estándar de 1). En resumen, demostraron que
con esta función de activación y esta inicialización
Machine Translated by Google
En este esquema, la varianza de las salidas de cada capa es mucho mayor que la
varianza de sus entradas. A medida que avanzamos en la red, la varianza sigue
aumentando después de cada capa hasta que la función de activación se satura en las
capas superiores. Esta saturación se agrava por el hecho de que la función logística tiene
una media de 0,5, no 0 (la función tangente hiperbólica tiene una media de 0 y se
comporta ligeramente mejor que la función logística en redes profundas).
Si observamos la función de activación logística (ver Figura 11­1), podemos ver que
cuando las entradas se vuelven grandes (negativas o positivas), la función se
satura en 0 o 1, con una derivada extremadamente cercana a 0. Por lo tanto, cuando
se activa la retropropagación, prácticamente no tiene gradiente para propagarse de
regreso a través de la red; y el pequeño gradiente que existe se sigue diluyendo a medida
que la retropropagación avanza hacia abajo a través de las capas superiores, por lo
que realmente no queda nada para las capas inferiores.
Figura 11­1. Saturación de la función de activación logística
Inicialización de Glorot y He
Machine Translated by Google
En su artículo, Glorot y Bengio proponen una forma de aliviar significativamente el problema
de los gradientes inestables. Señalan que necesitamos que la señal fluya correctamente en
ambas direcciones: en la dirección hacia adelante cuando se hacen predicciones, y en la
dirección inversa cuando se retropropagan los gradientes.
No queremos que la señal se apague, ni que explote y se sature. Para que la señal
fluya correctamente, los autores sostienen que necesitamos que la varianza de las salidas de
cada capa sea igual a la varianza de sus entradas, y necesitamos que los gradientes
2
tengan la misma varianza antes y después de fluir a través de una capa en la dirección inversa
(consulte el artículo si está interesado en los detalles matemáticos). En realidad, no es posible
garantizar ambas cosas a menos que la capa tenga un número igual de entradas y
neuronas (estos números se denominan fan­in y fan­out de la capa), pero Glorot y Bengio
propusieron un buen compromiso que ha demostrado funcionar muy bien en la práctica: los
pesos de conexión de cada capa deben inicializarse aleatoriamente como se describe en
la ecuación 11­1, donde fanavg = (fanin + fanout) /2. Esta estrategia de inicialización se
llama inicialización de Xavier o inicialización de Glorot, en honor al primer
autor del artículo.
Ecuación 11­1. Inicialización de Glorot (cuando se utiliza la función de activación logística)
Distribución normal con media 0 y varianza σ
2 =
1
promedio de fanáticos
O una distribución uniforme entre − r y + r, con r = √
3
promedio de fanáticos
en
Si reemplazamos fan por fan en la ecuación
11­1, obtenemos una estrategia de inicialización
promedio
que Yann LeCun propuso en la década de 1990. La llamó inicialización de LeCun.
Genevieve Orr y Klaus­Robert Müller incluso la recomendaron en su libro de 1998 Neural
Networks: Tricks of the Trade (Redes neuronales: trucos del oficio) (Springer).
en .
La inicialización de LeCun es equivalente a la inicialización de Glorot cuando fan = fan
afuera
Los investigadores tardaron más de una década en darse cuenta de lo importante
que es este truco. El uso de la inicialización de Glorot puede acelerar considerablemente el
entrenamiento y es uno de los trucos que llevaron al éxito del aprendizaje profundo.
3
Algunos artículos han proporcionado estrategias similares para diferentes funciones de
activación. Estas estrategias difieren solo en la escala de la varianza y en si utilizan fan o
fan , como se muestra en la Tabla 11­1en(para la función de activación uniforme).
promedio
Machine Translated by Google
distribución, solo calcule r = √3σ2). La estrategia de inicialización La función de activación ReLU
(y sus variantes, incluida la activación ELU que se describe en breve) a veces se denomina
inicialización He, en honor al primer autor del artículo. La función de activación SELU se explicará
más adelante en este capítulo. Debe utilizarse con la inicialización LeCun (preferiblemente con
una distribución normal, como veremos).
Tabla 11­1. Parámetros de inicialización para cada tipo de
función de activación
Funciones de activación de inicialización
Glorioso
σ² (Normal)
Ninguno, tanh, logístico, softmax 1/ fan
promedio
Él
ReLU y variantes
2 / ventilador en
Le Cun
SELU
1/ ventilador en
De forma predeterminada, Keras utiliza la inicialización de Glorot con una distribución uniforme.
Al crear una capa, puede cambiar esto a la inicialización He configurando
kernel_initializer="he_uniform" o
kernel_initializer="he_normal" de esta manera:
keras.layers.Dense(10, activación="relu", inicializador_del_núcleo="he_normal")
Si desea una inicialización con una distribución uniforme pero basada en fan en lugar de fan , puede utilizar el
inicializador VarianceScaling como avg
en de esta manera:
he_avg_init = keras.inicializadores.VarianceScaling(escala=2., modo='fan_avg',
distribución='uniforme')
keras.layers.Dense(10, activación="sigmoide",
inicializador_del_núcleo=he_avg_init)
Funciones de activación no saturantes
Machine Translated by Google
Una de las conclusiones del artículo de 2010 de Glorot y Bengio fue que los
problemas con los gradientes inestables se debían en parte a una mala elección
de la función de activación. Hasta entonces, la mayoría de la gente había asumido
que si la Madre Naturaleza había elegido utilizar funciones de activación
aproximadamente sigmoideas en neuronas biológicas, debían ser una excelente
elección. Pero resulta que otras funciones de activación se comportan mucho mejor en
redes neuronales profundas, en particular, la función de activación ReLU,
principalmente porque no se satura con valores positivos (y porque es rápida de calcular).
Lamentablemente, la función de activación de ReLU no es perfecta. Padece un problema
conocido como ReLU moribundas: durante el entrenamiento, algunas neuronas
efectivamente "mueren", lo que significa que dejan de generar cualquier valor que no
sea 0. En algunos casos, puede descubrir que la mitad de las neuronas de su red
están muertas, especialmente si utilizó una tasa de aprendizaje alta. Una neurona muere
cuando sus pesos se modifican de tal manera que la suma ponderada de sus entradas
es negativa para todas las instancias en el conjunto de entrenamiento. Cuando
esto sucede, simplemente sigue generando ceros y el Descenso de gradiente ya no la
4
afecta porque el gradiente de la función ReLU es cero cuando su entrada es negativa.
Para resolver este problema, es posible que desee utilizar una variante de la
función ReLU, como la ReLU con fugas. Esta función se define como
LeakyReLU (z) = max(αz, z) (consulte la Figura 11­2). El hiperparámetro α define
alfa
cuánto “fuga” la función: es la pendiente de la función para z < 0 y normalmente se
establece en 0,01. Esta pequeña pendiente garantiza que las ReLU con fugas nunca
mueran; pueden entrar en un coma prolongado, pero tienen la posibilidad de
despertar eventualmente. Un artículo de 52015 compararon varias variantes de la función
de activación ReLU, y una de sus conclusiones fue que las variantes con fugas siempre
superaron a la función de activación ReLU estricta. De hecho, establecer α = 0,2 (una
fuga enorme) pareció dar como resultado un mejor rendimiento que α = 0,01 (una fuga
pequeña). El artículo también evaluó la ReLU con fugas aleatoria (RReLU),
donde α se elige aleatoriamente en un rango dado durante el entrenamiento y se fija en
un valor promedio durante la prueba. RReLU también funcionó bastante bien y pareció
actuar como un regularizador (reduciendo el riesgo de sobreajuste del conjunto de
entrenamiento). Finalmente, el artículo evaluó la ReLU con fugas paramétrica
(PReLU), donde se autoriza que α se aprenda durante el entrenamiento (en lugar de
Machine Translated by Google
Al ser un hiperparámetro, se convierte en un parámetro que se puede modificar mediante
retropropagación como cualquier otro parámetro). Se informó que PReLU supera
ampliamente a ReLU en conjuntos de datos de imágenes grandes, pero en conjuntos de datos más
pequeños corre el riesgo de sobreajustar el conjunto de entrenamiento.
Figura 11­2. ReLU con fugas: como ReLU, pero con una pendiente pequeña para valores negativos
6
Por último, pero no menos importante, un artículo de 2015 Djork­Arné Clevert et al. propusieron
una nueva función de activación llamada unidad lineal exponencial (ELU) que superó a
todas las variantes de ReLU en los experimentos de los autores: se redujo el tiempo de
entrenamiento y la red neuronal tuvo un mejor desempeño en el conjunto de prueba.
La figura 11­3 grafica la función y la ecuación 11­2 muestra su definición.
Ecuación 11­2. Función de activación ELU
el
ELUα (z) ={ α(exp(z)
− 1) si z <si0 z ≥ 0
Machine Translated by Google
Figura 11­3. Función de activación de ELU
La función de activación ELU se parece mucho a la función ReLU, con algunas diferencias
importantes:
Toma valores negativos cuando z < 0, lo que permite que la unidad tenga una
salida promedio más cercana a 0 y ayuda a aliviar el problema de los
gradientes que desaparecen. El hiperparámetro α define el valor al que se
aproxima la función ELU cuando z es un número negativo grande. Generalmente se
establece en 1, pero se puede modificar como cualquier otro hiperparámetro.
Tiene un gradiente distinto de cero para z < 0, lo que evita el problema de las
neuronas muertas.
Si α es igual a 1, entonces la función es suave en todas partes, incluso
alrededor de z = 0, lo que ayuda a acelerar el descenso de gradiente ya que no
rebota tanto hacia la izquierda y la derecha de z = 0.
El principal inconveniente de la función de activación ELU es que es más lenta de calcular
que la función ReLU y sus variantes (debido al uso de la
Machine Translated by Google
función exponencial). Su tasa de convergencia más rápida durante el
entrenamiento compensa ese cálculo lento, pero aún así, en el momento de la prueba,
una red ELU será más lenta que una red ReLU.
7
Luego, un artículo de 2017 Günter Klambauer et al. introdujeron la función de activación
Scaled ELU (SELU): como sugiere su nombre, es una variante escalada de la función
de activación ELU. Los autores demostraron que si se construye una red neuronal
compuesta exclusivamente por una pila de capas densas y si todas las capas ocultas
utilizan la función de activación SELU, la red se autonormalizará : la salida de cada capa
tenderá a conservar una media de 0 y una desviación estándar de 1 durante el
entrenamiento, lo que resuelve el problema de los gradientes que desaparecen/
explotan. Como resultado, la función de activación SELU a menudo supera significativamente
a otras funciones de activación para dichas redes neuronales (especialmente las
profundas). Sin embargo, existen algunas condiciones para que se produzca la
autonormalización (consulte el artículo para obtener la justificación matemática):
Las características de entrada deben estar estandarizadas (media 0 y
desviación estándar 1).
Los pesos de cada capa oculta deben inicializarse con la inicialización
normal de LeCun. En Keras, esto significa configurar
kernel_initializer="lecun_normal".
La arquitectura de la red debe ser secuencial. Lamentablemente, si intenta
utilizar SELU en arquitecturas no secuenciales, como redes recurrentes
(consulte el Capítulo 15) o redes con conexiones de salto (es decir,
conexiones que saltan capas, como en redes anchas y profundas), no se
garantizará la autonormalización, por lo que SELU no necesariamente superará
a otras funciones de activación.
El artículo solo garantiza la autonormalización si todas las capas son densas,
pero algunos investigadores han observado que la función de activación SELU
también puede mejorar el rendimiento en redes neuronales convolucionales (ver
Capítulo 14).
Machine Translated by Google
CONSEJO
Entonces, ¿qué función de activación debería usar para las capas ocultas de sus redes neuronales
profundas? Aunque su rendimiento variará, en general SELU > ELU > ReLU con fugas (y sus variantes)
> ReLU > tanh > logístico. Si la arquitectura de la red le impide autonormalizarse, entonces ELU
puede funcionar mejor que SELU (ya que SELU no es suave en z = 0). Si le importa mucho la
latencia en tiempo de ejecución, entonces puede preferir ReLU con fugas. Si no desea ajustar otro
hiperparámetro, puede usar los valores α predeterminados que usa Keras (por ejemplo, 0,3 para ReLU
con fugas). Si tiene tiempo libre y potencia de cálculo, puede usar la validación cruzada para evaluar
otras funciones de activación, como RReLU si su red está sobreajustada o PReLU si tiene un conjunto
de entrenamiento enorme. Dicho esto, debido a que ReLU es la función de activación más utilizada
(por lejos), muchas bibliotecas y aceleradores de hardware proporcionan optimizaciones específicas
de ReLU; Por lo tanto, si la velocidad es su prioridad, ReLU podría ser la mejor opción.
Para utilizar la función de activación de LeakyReLU, cree una capa LeakyReLU y
agréguela a su modelo justo después de la capa a la que desea aplicarla:
modelo = keras.modelos.Secuencial([ [...]
keras.capas.Dense(10, kernel_initializer="he_normal"),
keras.capas.LeakyReLU(alpha=0.2), [...]
])
Para PReLU, reemplace LeakyRelu(alpha=0.2) por PReLU(). Actualmente no
existe una implementación oficial de RReLU en Keras, pero puede implementar
la suya con bastante facilidad (para aprender a hacerlo, consulte los
ejercicios al final del Capítulo 12).
Para activar SELU, configure activation="selu" y
kernel_initializer="lecun_normal" al crear una capa:
capa = keras.capas.Dense(10, activación="selu",
kernel_initializer="lecun_normal")
Normalización por lotes
Machine Translated by Google
Aunque el uso de la inicialización de He junto con ELU (o cualquier variante de ReLU)
puede reducir significativamente el peligro de problemas de gradientes que
desaparecen o explotan al comienzo del entrenamiento, no garantiza que no regresen
durante el entrenamiento.
8
En un artículo de 2015, Sergey Ioffe y Christian Szegedy propusieron una técnica
llamada Normalización por Lotes (BN, por sus siglas en inglés) que aborda
estos problemas. La técnica consiste en agregar una operación en el modelo justo
antes o después de la función de activación de cada capa oculta. Esta operación
simplemente centra en cero y normaliza cada entrada, luego escala y desplaza el
resultado utilizando dos nuevos vectores de parámetros por capa: uno para
escalar, el otro para desplazar. En otras palabras, la operación permite que el
modelo aprenda la escala y la media óptimas de cada una de las entradas de la
capa. En muchos casos, si agrega una capa BN como la primera capa de su red
neuronal, no necesita estandarizar su conjunto de entrenamiento (por ejemplo,
utilizando un StandardScaler); la capa BN lo hará por usted (bueno, aproximadamente,
ya que solo analiza un lote a la vez, y también puede reescalar y desplazar cada característica de entra
Para centrar en cero y normalizar las entradas, el algoritmo necesita estimar la
media y la desviación estándar de cada entrada. Para ello, evalúa la media
y la desviación estándar de la entrada en el minilote actual (de ahí el nombre de
“normalización por lotes”). Toda la operación se resume paso a paso en la ecuación
11­3.
Ecuación 11­3. Algoritmo de normalización por lotes
megabyte
1
1.μB =
∑
megabyte
(i)
incógnita
yo=1
2
2. σB
=
1
megabyte
3.
xˆ (i)
=
incógnita
el
(i)
yo=1
(x (i) − μB) ∑
(i) − μB √σB
2=γ
4.
2
megabyte
xˆ (i) + β
+ε
Machine Translated by Google
En este algoritmo:
μB
es el vector de medias de entrada, evaluado sobre todo el minilote B (contiene una
media por entrada).
σB
es el vector de desviaciones estándar de entrada, también evaluado sobre todo el mini­
lote (contiene una desviación estándar por entrada).
mB
es el número de instancias en el mini­lote.
(i)
xˆ es el vector de entradas normalizadas y centradas en cero para la instancia i.
γ es el vector de parámetros de escala de salida de la capa (contiene un parámetro de
escala por entrada).
representa la multiplicación elemento por elemento (cada entrada se
multiplica por su parámetro de escala de salida correspondiente).
β es el vector de parámetros de desplazamiento (offset) de salida de la capa
(contiene un parámetro de desplazamiento por entrada). Cada entrada se desplaza
según su parámetro de desplazamiento correspondiente.
ε es un número pequeño que evita la división por cero (normalmente 10).
–5
Esto se llama término de suavizado.
(i) z es la salida de la operación BN. Es una versión reescalada y desplazada de
las entradas.
Entonces, durante el entrenamiento, BN estandariza sus entradas, luego las reescala y las
compensa. ¡Bien! ¿Qué pasa en el momento de la prueba? Bueno, no es tan simple. De hecho, es
posible que necesitemos hacer predicciones para instancias individuales en lugar de para lotes
de instancias: en este caso, no tendremos forma de calcular la media y la desviación estándar de
cada entrada. Además, incluso si tenemos un lote de instancias, puede ser demasiado pequeño, o las
instancias pueden no ser independientes y estar distribuidas de manera idéntica, por lo que calcular
las estadísticas sobre las instancias del lote no sería confiable. Una solución podría ser
esperar hasta el final del entrenamiento, luego ejecutar todo el conjunto de entrenamiento a través
de la red neuronal y
Machine Translated by Google
Calcular la media y la desviación estándar de cada entrada de la capa BN.
Estas medias y desviaciones estándar de entrada “finales” podrían entonces usarse
en lugar de las medias y desviaciones estándar de entrada por lotes al hacer
predicciones. Sin embargo, la mayoría de las implementaciones de Normalización por
lotes estiman estas estadísticas finales durante el entrenamiento usando un promedio móvil
de las medias y desviaciones estándar de entrada de la capa. Esto es lo que Keras hace
automáticamente cuando se usa la capa BatchNormalization. En resumen, se aprenden
cuatro vectores de parámetros en cada capa normalizada por lotes: γ (el vector de escala
de salida) y β (el vector de desplazamiento de salida) se aprenden a través de
retropropagación regular, y μ (el vector de media de entrada final) y σ (el vector de
desviación estándar de entrada final) se estiman usando un promedio móvil exponencial.
Tenga en cuenta que μ y σ se estiman durante el entrenamiento, pero se usan solo después
del entrenamiento (para reemplazar las medias y desviaciones estándar de entrada por
lotes en la Ecuación 11­3).
Ioffe y Szegedy demostraron que la Normalización por Lotes mejoró considerablemente
todas las redes neuronales profundas con las que experimentaron, lo que llevó a una
enorme mejora en la tarea de clasificación de ImageNet (ImageNet es una gran base de
datos de imágenes clasificadas en muchas clases, comúnmente utilizada para evaluar
sistemas de visión artificial). El problema de los gradientes que se desvanecen se redujo
considerablemente, hasta el punto de que pudieron usar funciones de activación de
saturación como la tanh e incluso la función de activación logística. Las redes también
fueron mucho menos sensibles a la inicialización de pesos. Los autores pudieron usar
tasas de aprendizaje mucho mayores, acelerando significativamente el proceso de
aprendizaje. En concreto, señalan que:
Aplicada a un modelo de clasificación de imágenes de última generación, la
normalización por lotes logra la misma precisión con 14 veces menos pasos de
entrenamiento y supera al modelo original por un margen significativo. […] Al utilizar un
conjunto de redes normalizadas por lotes, mejoramos el mejor resultado publicado
sobre la clasificación de ImageNet: alcanzamos un error de validación de top­5
del 4,9 % (y un error de prueba del 4,8 %), superando la precisión de la clasificación humana.
evaluadores.
Finalmente, como un regalo que sigue dando, la Normalización por Lotes actúa como un
regularizador, reduciendo la necesidad de otras técnicas de regularización (como
Machine Translated by Google
abandono escolar, que se describe más adelante en este capítulo).
Sin embargo, la normalización por lotes agrega cierta complejidad al modelo (aunque puede
eliminar la necesidad de normalizar los datos de entrada, como analizamos anteriormente).
Además, hay una penalización en el tiempo de ejecución: la red neuronal realiza
predicciones más lentas debido a los cálculos adicionales necesarios en cada capa.
Afortunadamente, a menudo es posible fusionar la capa BN con la capa anterior, después del
entrenamiento, evitando así la penalización en el tiempo de ejecución. Esto se hace actualizando
los pesos y sesgos de la capa anterior para que produzca directamente resultados de la escala y el
desplazamiento adecuados. Por ejemplo, si la capa anterior calcula XW + b, entonces la capa
BN calculará γ
definimos W′ = γ
(XW + b – μ)/σ + β (ignorando el término de suavizado ε en el denominador). Si
W/σ y b′ = γ
(b – μ)/σ + β, la ecuación se simplifica a XW′ + b′. Entonces, si
reemplazamos los pesos y sesgos de la capa anterior (W y b) con los pesos y sesgos actualizados
(W′ y b′), podemos deshacernos de la capa BN (el optimizador de TFLite lo hace automáticamente;
consulte el Capítulo 19).
NOTA
Es posible que el entrenamiento sea bastante lento, porque cada época lleva mucho más tiempo
cuando se utiliza la normalización por lotes. Esto suele compensarse con el hecho de que la
convergencia es mucho más rápida con la normalización por lotes, por lo que se necesitarán menos
épocas para alcanzar el mismo rendimiento. En general, el tiempo de pared suele ser más corto (este
es el tiempo que mide el reloj de la pared).
Implementación de la normalización por lotes con Keras
Como ocurre con la mayoría de las cosas con Keras, la implementación de la normalización
por lotes es sencilla e intuitiva. Solo hay que añadir una capa de normalización por lotes antes o
después de la función de activación de cada capa oculta y, opcionalmente, añadir una capa de
normalización por lotes además de la primera capa del modelo. Por ejemplo, este modelo aplica
normalización por lotes después de cada capa oculta y como primera capa del modelo (después de
aplanar las imágenes de entrada):
modelo =
keras.modelos.Secuencial([ keras.capas.Aplanar(forma_entrada=[28, 28]),
Machine Translated by Google
keras.capas.BatchNormalization(),
keras.layers.Dense(300, activación="elu",
kernel_initializer="él_normal"),
keras.capas.BatchNormalization(),
keras.layers.Dense(100, activación="elu",
kernel_initializer="él_normal"),
keras.capas.BatchNormalization(),
keras.layers.Dense(10, activación="softmax")
])
¡Eso es todo! En este pequeño ejemplo con solo dos capas ocultas, es poco probable
que la Normalización por Lotes tendrá un impacto muy positivo; pero para mayor profundidad
Las redes pueden hacer una enorme diferencia.
Vamos a mostrar el resumen del modelo:
>>> modelo.summary()
Modelo: "sequential_3"
_________________________________________________________________
Parámetro #
Forma de salida
Capa (tipo)
==================================================== ================
0
flatten_3 (Aplanar)
(Ninguno, 784)
_________________________________________________________________
3136
normalización_por_lotes_v2 (Batc (Ninguno, 784)
_________________________________________________________________
235500
denso_50 (denso)
(Ninguno, 300)
_________________________________________________________________
1200
normalización_por_lotes_v2_1 (Ba (Ninguno, 300)
_________________________________________________________________
30100
denso_51 (denso)
(Ninguno, 100)
_________________________________________________________________
400
normalización_por_lotes_v2_2 (Ba (Ninguno, 100)
_________________________________________________________________
1010
denso_52 (denso)
(Ninguno, 10)
==================================================== ================
Parámetros totales: 271.346
Parámetros entrenables: 268.978
Parámetros no entrenables: 2368
Como puede ver, cada capa BN agrega cuatro parámetros por entrada: γ, β, μ y
σ (por ejemplo, la primera capa BN agrega 3136 parámetros, que es 4 ×
784) Los dos últimos parámetros, μ y σ, son los promedios móviles; son
no se ven afectados por la retropropagación, por lo que Keras los llama "no entrenables"9 (si
Machine Translated by Google
Si cuentas el número total de parámetros BN, 3136 + 1200 + 400, y lo divides por
2, obtienes 2368, que es el número total de parámetros no entrenables en este
modelo).
Veamos los parámetros de la primera capa de BN. Dos son entrenables (mediante
retropropagación) y dos no:
>>> [(var.name, var.trainable) para var en model.layers[1].variables]
[('batch_normalization_v2/gamma:0', Verdadero),
('batch_normalization_v2/beta:0', Verdadero),
('batch_normalization_v2/moving_mean:0', Falso),
('batch_normalization_v2/moving_variance:0', Falso)]
Ahora, cuando crea una capa BN en Keras, también crea dos operaciones que
Keras llamará en cada iteración durante el entrenamiento. Estas operaciones
actualizarán los promedios móviles. Dado que estamos usando el backend
de TensorFlow, estas operaciones son operaciones de TensorFlow (hablaremos de
las operaciones de TF en el Capítulo 12):
>>> model.layers[1].updates
[<tf.Operación 'cond_2/Identidad' tipo=Identidad>, <tf.Operación
'cond_3/Identidad' tipo=Identidad>]
Los autores del artículo sobre BN argumentaron a favor de agregar las capas
de BN antes de las funciones de activación, en lugar de después (como acabamos
de hacer). Existe cierto debate sobre esto, ya que lo que es preferible parece depender
de la tarea; también puede experimentar con esto para ver qué opción funciona mejor
en su conjunto de datos. Para agregar las capas de BN antes de las funciones de
activación, debe eliminar la función de activación de las capas ocultas y agregarlas
como capas separadas después de las capas de BN. Además, dado
que una capa de Normalización por lotes incluye un parámetro de compensación
por entrada, puede eliminar el término de sesgo de la capa anterior (solo pase
use_bias=False al crearla):
modelo =
keras.modelos.Sequential([ keras.capas.Flatten(forma_de_entrada=[28, 28]), keras.capas.BatchNormalizatio
Machine Translated by Google
use_bias=False),
keras.layers.BatchNormalization (),
keras.layers.Activation ("elu") ,
keras.layers.Dense(100, kernel_initializer="he_normal", use_bias=False),
keras.layers.BatchNormalization(),
keras.layers.Activation("elu"),
keras.layers.Dense(10, activación="softmax")
])
La clase BatchNormalization tiene bastantes hiperparámetros que se pueden
modificar. Los valores predeterminados suelen ser correctos, pero es posible que en
ocasiones sea necesario modificar el impulso. La capa
BatchNormalization utiliza este hiperparámetro cuando actualiza los promedios
móviles exponenciales; dado un nuevo valor v (es decir, un nuevo vector de
medias de entrada o desviaciones estándar calculadas sobre el lote actual), la
capa actualiza el promedio móvil vˆ utilizando la siguiente ecuación:
vˆ ← vˆ × momento + v × (1 − momento)
Un buen valor de momento suele estar cerca de 1; por ejemplo, 0,9, 0,99 o 0,999 (se
necesitan más 9 para conjuntos de datos más grandes y minilotes más pequeños).
Otro hiperparámetro importante es el eje: determina qué eje debe normalizarse.
El valor predeterminado es ­1, lo que significa que, de manera predeterminada,
normalizará el último eje (utilizando las medias y las desviaciones estándar calculadas
en los otros ejes). Cuando el lote de entrada es 2D (es decir, la forma del lote es
[tamaño del lote, características]), esto significa que cada característica de
entrada se normalizará en función de la media y la desviación estándar calculadas en
todas las instancias del lote. Por ejemplo, la primera capa BN en el ejemplo de código
anterior normalizará de forma independiente (y reescalará y desplazará) cada una de
las 784 características de entrada. Si movemos la primera capa BN antes de la
capa Flatten, los lotes de entrada serán 3D, con forma [tamaño del lote, altura,
ancho]; por lo tanto, la capa BN calculará 28 medias y 28 desviaciones estándar
(1 por columna de píxeles, calculada en todas las instancias del lote y en todas las
filas de la columna), y normalizará todos los píxeles en una columna dada utilizando la
misma media y desviación estándar. Habrá
Machine Translated by Google
También puede tener solo 28 parámetros de escala y 28 parámetros de desplazamiento.
Si, en cambio, aún desea tratar cada uno de los 784 píxeles de forma independiente, debe
configurar axis=[1, 2].
Tenga en cuenta que la capa BN no realiza el mismo cálculo durante el entrenamiento y
después del mismo: utiliza estadísticas por lotes durante el entrenamiento y las estadísticas
“finales” después del entrenamiento (es decir, los valores finales de los promedios
móviles). Echemos un vistazo al código fuente de esta clase para ver cómo se maneja esto:
clase BatchNormalization(keras.layers.Layer):
[...]
def call(self, entradas, entrenamiento=Ninguno): [...]
El método call() es el que realiza los cálculos; como puede ver, tiene un argumento de
entrenamiento adicional, que se establece en None de manera predeterminada, pero el
método fit() lo establece en 1 durante el entrenamiento. Si alguna vez necesita escribir
una capa personalizada y debe comportarse de manera diferente durante el entrenamiento
y la prueba, agregue un argumento de entrenamiento al método call() y use esto
10
argumento en el método para decidir qué calcular (analizaremos las capas personalizadas
en el Capítulo 12).
BatchNormalization se ha convertido en una de las capas más utilizadas en redes
neuronales profundas, hasta el punto de que a menudo se omite en los diagramas, ya que
11
se supone que BN se agrega después de cada capa. Pero un artículo reciente por
Hongyi Zhang et al. puede cambiar esta suposición: al utilizar una novedosa técnica de
inicialización de peso de actualización fija (fixup), los autores lograron entrenar una red
neuronal muy profunda (¡10 000 capas!) sin BN, logrando un rendimiento de vanguardia en
tareas complejas de clasificación de imágenes. Sin embargo, como se trata de una
investigación de vanguardia, es posible que desee esperar a que se realicen
investigaciones adicionales para confirmar este hallazgo antes de abandonar la
normalización por lotes.
Recorte de degradado
Machine Translated by Google
Otra técnica popular para mitigar el problema de los gradientes explosivos es recortar los
gradientes durante la retropropagación de modo que nunca superen un umbral
12
determinado. Esto se denomina recorte de gradiente. Esta técnica se utiliza con mayor
frecuencia en redes neuronales recurrentes, ya que la normalización por lotes es difícil
de usar en RNN, como veremos en el Capítulo 15. Para otros tipos de redes, la BN suele
ser suficiente.
En Keras, implementar el recorte de degradado es solo una cuestión de configurar el
argumento clipvalue o clipnorm al crear un optimizador, de la siguiente manera:
optimizador = keras.optimizers.SGD(clipvalue=1.0)
modelo.compilar(pérdida="mse", optimizador=optimizador)
Este optimizador recortará cada componente del vector de gradiente a un valor entre ­1,0
y 1,0. Esto significa que todas las derivadas parciales de la pérdida (con respecto a todos
y cada uno de los parámetros entrenables) se recortarán entre ­1,0 y 1,0. El
umbral es un hiperparámetro que se puede ajustar.
Tenga en cuenta que puede cambiar la orientación del vector de gradiente. Por
ejemplo, si el vector de gradiente original es [0.9, 100.0], apunta principalmente en la
dirección del segundo eje; pero una vez que lo recorta por valor, obtiene [0.9, 1.0], que
apunta aproximadamente en la diagonal entre los dos ejes. En la práctica, este
enfoque funciona bien. Si desea asegurarse de que el recorte de gradiente no cambie
la dirección del vector de gradiente, debe recortar por norma estableciendo clipnorm en
lugar de clipvalue. Esto recortará todo el gradiente si su norma ℓ es mayor que el umbral
que seleccionó. Por ejemplo,
2 si establece clipnorm=1.0, entonces el vector [0.9, 100.0]
se recortará a [0.00899964, 0.9999595], conservando su orientación pero eliminando
casi por completo el primer componente. Si observa que los gradientes explotan durante
el entrenamiento (puede rastrear el tamaño de los gradientes usando TensorBoard),
puede probar tanto el recorte por valor como el recorte por norma, con
diferentes umbrales, y ver qué opción funciona mejor en el conjunto de validación.
Reutilización de capas preentrenadas
Machine Translated by Google
En general, no es una buena idea entrenar una red neuronal de gran tamaño desde
cero: en lugar de eso, siempre debe intentar encontrar una red neuronal existente que
realice una tarea similar a la que está tratando de abordar (hablaremos sobre cómo
encontrarlas en el Capítulo 14) y luego reutilizar las capas inferiores de esta red. Esta técnica
se llama aprendizaje por transferencia. No solo acelerará considerablemente el entrenamiento,
sino que también requerirá significativamente menos datos de entrenamiento.
Supongamos que tiene acceso a una red neuronal profunda que se entrenó para clasificar
imágenes en 100 categorías diferentes, incluidos animales, plantas, vehículos y objetos
cotidianos. Ahora desea entrenar una red neuronal profunda para clasificar tipos específicos
de vehículos. Estas tareas son muy similares, incluso se superponen parcialmente, por lo
que debe intentar reutilizar partes de la primera red (consulte la Figura 11­4).
Figura 11­4. Reutilización de capas preentrenadas
Machine Translated by Google
NOTA
Si las imágenes de entrada de la nueva tarea no tienen el mismo tamaño que las utilizadas en la tarea
original, normalmente tendrá que agregar un paso de preprocesamiento para cambiar su tamaño al tamaño
esperado por el modelo original. En términos más generales, el aprendizaje por transferencia funcionará
mejor cuando las entradas tengan características de bajo nivel similares.
Generalmente, la capa de salida del modelo original debe reemplazarse porque
probablemente no sea útil en absoluto para la nueva tarea y es posible que ni siquiera tenga
la cantidad correcta de salidas para la nueva tarea.
De manera similar, es menos probable que las capas ocultas superiores del modelo
original sean tan útiles como las capas inferiores, ya que las características de alto nivel
que son más útiles para la nueva tarea pueden diferir significativamente de las que eran
más útiles para la tarea original. Debe encontrar la cantidad correcta de capas para
reutilizar.
CONSEJO
Cuanto más similares sean las tareas, más capas querrá reutilizar (empezando por las capas inferiores).
Para tareas muy similares, intente conservar todas las capas ocultas y simplemente reemplazar la
capa de salida.
Intente congelar primero todas las capas reutilizadas (es decir, haga que sus
pesos no se puedan entrenar para que el Descenso de gradiente no los modifique),
luego entrene su modelo y vea cómo funciona. Luego intente descongelar una o dos de
las capas ocultas superiores para permitir que la retropropagación las modifique y vea si
el rendimiento mejora. Cuantos más datos de entrenamiento tenga, más capas
podrá descongelar. También es útil reducir la tasa de aprendizaje cuando descongele
capas reutilizadas: esto evitará arruinar sus pesos ajustados.
Si aún no puede obtener un buen rendimiento y tiene pocos datos de entrenamiento,
intente eliminar las capas ocultas superiores y congelar todas las capas ocultas restantes
nuevamente. Puede iterar hasta que encuentre la cantidad correcta de capas para
reutilizar. Si tiene muchos datos de entrenamiento, puede intentar reemplazar las capas superiores.
Machine Translated by Google
capas ocultas en lugar de eliminarlas, e incluso agregar más capas ocultas.
Transferencia de aprendizaje con Keras
Veamos un ejemplo. Supongamos que el conjunto de datos de moda de
MNIST solo contenía ocho clases (por ejemplo, todas las clases excepto sandalia y
camisa). Alguien construyó y entrenó un modelo de Keras en ese conjunto y
obtuvo un rendimiento razonablemente bueno (>90 % de precisión). Llamemos a este modelo A.
Ahora quieres abordar una tarea diferente: tienes imágenes de sandalias y camisas,
y quieres entrenar un clasificador binario (positivo=camisa,
negativo=sandalia). Tu conjunto de datos es bastante pequeño; solo tienes 200
imágenes etiquetadas. Cuando entrenas un nuevo modelo para esta tarea
(llamémoslo modelo B) con la misma arquitectura que el modelo A, funciona
razonablemente bien (97,2 % de precisión). Pero como es una tarea mucho más
fácil (solo hay dos clases), esperabas más. Mientras tomas tu café de la
mañana, te das cuenta de que tu tarea es bastante similar a la tarea A, así
que tal vez el aprendizaje por transferencia pueda ayudar. ¡Vamos a averiguarlo!
Primero, debes cargar el modelo A y crear un nuevo modelo basado en las capas
de ese modelo. Reutilicemos todas las capas excepto la capa de salida:
modelo_A = keras.models.load_model("mi_modelo_A.h5")
modelo_B_en_A = keras.models.Sequential(modelo_A.layers[:­1])
modelo_B_en_A.add(keras.layers.Dense(1, activación="sigmoide"))
Tenga en cuenta que model_A y model_B_on_A ahora comparten algunas capas.
Cuando entrena model_B_on_A, también afectará a model_A. Si desea evitarlo, debe
clonar model_A antes de reutilizar sus capas. Para ello, clona la arquitectura de
model A con clone.model() y luego copia sus pesos (ya que clone_model() no clona
los pesos):
modelo_A_clone = keras.models.clone_model(modelo_A)
modelo_A_clone.set_weights(modelo_A.get_weights())
Machine Translated by Google
Ahora puede entrenar model_B_on_A para la tarea B, pero como la nueva capa de
salida se inicializó aleatoriamente, cometerá grandes errores (al menos durante las primeras
épocas), por lo que habrá grandes gradientes de error que pueden arruinar los pesos
reutilizados. Para evitar esto, un enfoque es congelar las capas reutilizadas durante las
primeras épocas, lo que le da a la nueva capa algo de tiempo para aprender pesos
razonables. Para hacer esto, configure el atributo entrenable de cada capa en Falso y
compile el modelo:
para la capa en el modelo_B_on_A.layers[:­1]:
capa.entrenable = False
model_B_on_A.compile(pérdida="entropía_cruzada_binaria", optimizador="sgd",
métricas=["precisión"])
NOTA
Siempre debes compilar tu modelo después de congelar o descongelar capas.
Ahora puede entrenar el modelo durante algunas épocas, luego descongelar las capas
reutilizadas (lo que requiere compilar el modelo nuevamente) y continuar con el
entrenamiento para ajustar las capas reutilizadas para la tarea B. Después de descongelar
las capas reutilizadas, suele ser una buena idea reducir la tasa de aprendizaje, una
vez más para evitar dañar los pesos reutilizados:
historia = modelo_B_en_A.fit(X_tren_B, y_tren_B, épocas=4,
datos_validacion=(X_valido_B, y_valido_B))
para la capa en el modelo_B_on_A.layers[:­1]:
capa.entrenable = True
# El lr predeterminado es 1e­2
optimizador = keras.optimizers.SGD(lr=1e­4)
modelo_B_en_A.compile(pérdida="entropía_cruzada_binaria", optimizador=optimizador,
métricas=["precisión"])
historia = modelo_B_en_A.fit(X_tren_B, y_tren_B, épocas=16,
datos_validacion=(X_valido_B, y_valido_B))
Machine Translated by Google
Entonces, ¿cuál es el veredicto final? Bueno, la precisión de la prueba de este modelo
es del 99,25 %, lo que significa que el aprendizaje por transferencia redujo la tasa de error
del 2,8 % a casi el 0,7 %. ¡Eso es un factor de cuatro!
>>> modelo_B_en_A.evaluar(prueba_X_B, prueba_y_B)
[0.06887910133600235, 0.9925]
¿Estás convencido? No deberías estarlo: ¡hice trampa! Probé muchas
configuraciones hasta que encontré una que demostró una mejora notable.
Si intentas cambiar las clases o la semilla aleatoria, verás que la mejora generalmente
disminuye, o incluso desaparece o se revierte. Lo que hice se llama “torturar los datos hasta
que confiesen”. Cuando un artículo parece demasiado positivo, debes sospechar: tal vez la
nueva técnica llamativa en realidad no ayude mucho (de hecho, puede incluso degradar el
rendimiento), pero los autores probaron muchas variantes e informaron solo los mejores
resultados (que pueden deberse a pura suerte), sin mencionar cuántos fracasos encontraron
en el camino. La mayoría de las veces, esto no es malicioso en absoluto, pero es
parte de la razón por la que tantos resultados en ciencia nunca pueden reproducirse.
¿Por qué hice trampa? Resulta que el aprendizaje por transferencia no funciona muy bien con
redes pequeñas y densas, probablemente porque las redes pequeñas aprenden pocos
patrones y las redes densas aprenden patrones muy específicos, que es poco
probable que sean útiles en otras tareas. El aprendizaje por transferencia funciona mejor con
redes neuronales convolucionales profundas, que tienden a aprender detectores de
características que son mucho más generales (especialmente en las capas inferiores).
Volveremos a tratar el aprendizaje por transferencia en el Capítulo 14, utilizando las
técnicas que acabamos de analizar (y esta vez no haremos trampa, ¡lo prometo!).
Preentrenamiento no supervisado
Supongamos que desea abordar una tarea compleja para la que no tiene muchos datos de
entrenamiento etiquetados, pero desafortunadamente no puede encontrar un modelo
entrenado en una tarea similar. ¡No pierda la esperanza! Primero, debe intentar
recopilar más datos de entrenamiento etiquetados, pero si no puede, aún puede realizar
un preentrenamiento no supervisado (consulte la Figura 11­5). De hecho, a menudo es
barato recopilar ejemplos de entrenamiento sin etiquetar, pero caro etiquetarlos. Si
Machine Translated by Google
Si puede reunir una gran cantidad de datos de entrenamiento sin etiquetar, puede intentar usarlos
para entrenar un modelo no supervisado, como un autocodificador o una red generativa antagónica
(consulte el Capítulo 17). Luego, puede reutilizar las capas inferiores del autocodificador o
las capas inferiores del discriminador de la GAN, agregar la capa de salida para su tarea en la parte
superior y ajustar la red final mediante el aprendizaje supervisado (es decir, con los ejemplos de
entrenamiento etiquetados).
Esta es la técnica que Geoffrey Hinton y su equipo utilizaron en 2006 y que condujo al
resurgimiento de las redes neuronales y al éxito del aprendizaje profundo. Hasta 2010, el
preentrenamiento no supervisado (normalmente con máquinas de Boltzmann restringidas [RBM];
véase el Apéndice E) era la norma para las redes profundas, y solo después de que se alivió el
problema de los gradientes evanescentes se volvió mucho más común entrenar las DNN utilizando
únicamente el aprendizaje supervisado. El preentrenamiento no supervisado (que hoy en
día suele utilizar autocodificadores o GAN en lugar de RBM) sigue siendo una buena opción cuando
se tiene una tarea compleja que resolver, no hay un modelo similar que se pueda reutilizar y hay
pocos datos de entrenamiento etiquetados, pero muchos datos de entrenamiento sin etiquetar.
Tenga en cuenta que en los primeros días del aprendizaje profundo era difícil entrenar modelos
profundos, por lo que las personas usaban una técnica llamada preentrenamiento por
capas voraz (que se muestra en la Figura 11­5). Primero, entrenaban un modelo no
supervisado con una sola capa, generalmente un RBM, luego congelaban esa capa y
agregaban otra encima de ella, luego entrenaban el modelo nuevamente (en realidad, solo
entrenaban la nueva capa), luego congelaban la nueva capa y agregaban otra capa encima de
ella, entrenaban el modelo nuevamente, y así sucesivamente.
Hoy en día, las cosas son mucho más simples: las personas generalmente entrenan el
modelo completo no supervisado de una sola vez (es decir, en la Figura 11­5, simplemente
comienzan directamente en el paso tres) y usan autocodificadores o GAN en lugar de RBM.
Machine Translated by Google
Figura 11­5. En el entrenamiento no supervisado, se entrena un modelo con los datos no etiquetados (o con todos los
datos) utilizando una técnica de aprendizaje no supervisado, luego se lo perfecciona para la tarea final con los datos
etiquetados utilizando una técnica de aprendizaje supervisado; la parte no supervisada puede entrenar una capa a la vez
como se muestra aquí, o puede entrenar el modelo completo directamente
Entrenamiento previo en una tarea auxiliar Si no
tiene muchos datos de entrenamiento etiquetados, una última opción es entrenar una
primera red neuronal en una tarea auxiliar para la que pueda obtener o generar fácilmente
datos de entrenamiento etiquetados y luego reutilizar las capas inferiores de esa red
para su tarea real. Las capas inferiores de la primera red neuronal aprenderán
detectores de características que probablemente serán reutilizables por la segunda red neuronal.
Por ejemplo, si quieres construir un sistema para reconocer rostros, es posible que
solo tengas unas pocas fotografías de cada individuo, lo que claramente no es suficiente
para entrenar un buen clasificador. Recopilar cientos de fotografías de cada persona no
sería práctico. Sin embargo, podrías reunir muchas fotografías de personas al azar en la
web y entrenar una primera red neuronal para detectar si dos fotografías diferentes
presentan o no a la misma persona. Una red de este tipo aprendería
Machine Translated by Google
buenos detectores de características para rostros, por lo que reutilizar sus capas inferiores le
permitiría entrenar un buen clasificador de rostros que utiliza pocos datos de entrenamiento.
Para las aplicaciones de procesamiento de lenguaje natural (PLN), puede descargar un corpus de
millones de documentos de texto y generar automáticamente datos etiquetados a partir de él. Por
ejemplo, podría enmascarar aleatoriamente algunas palabras y entrenar un modelo para predecir
cuáles son las palabras que faltan (por ejemplo, debería predecir que la palabra que falta en la oración
"¿Qué ___ estás diciendo?" es probablemente "son" o "were"). Si puede entrenar un modelo para que
alcance un buen desempeño en esta tarea, entonces ya sabrá bastante sobre el lenguaje y, sin
duda, puede reutilizarlo para su tarea real y ajustarlo con sus datos etiquetados (hablaremos más
sobre tareas de preentrenamiento en el Capítulo 15).
NOTA
El aprendizaje autosupervisado se produce cuando se generan automáticamente las etiquetas a partir de
los propios datos y luego se entrena un modelo con el conjunto de datos “etiquetados” resultante
mediante técnicas de aprendizaje supervisado. Dado que este enfoque no requiere etiquetado humano
alguno, se clasifica mejor como una forma de aprendizaje no supervisado.
Optimizadores más rápidos
Entrenar una red neuronal profunda muy grande puede ser dolorosamente lento. Hasta ahora hemos
visto cuatro formas de acelerar el entrenamiento (y llegar a una mejor solución): aplicar una
buena estrategia de inicialización para los pesos de conexión, usar una buena función de activación,
usar la normalización por lotes y reutilizar partes de una red preentrenada (posiblemente construida
sobre una tarea auxiliar o usando aprendizaje no supervisado). Otro gran aumento de
velocidad proviene del uso de un optimizador más rápido que el optimizador de descenso de
gradiente habitual. En esta sección presentaremos los algoritmos más populares: optimización
de momento, gradiente acelerado de Nesterov, AdaGrad, RMSProp y, finalmente,
optimización de Adam y Nadam.
Optimización del impulso
Machine Translated by Google
Imaginemos una bola de bolos que rueda por una pendiente suave sobre una superficie
lisa: comenzará lentamente, pero rápidamente adquirirá impulso hasta que
finalmente alcance la velocidad terminal (si hay alguna fricción o resistencia del
aire). Esta es la idea muy simple detrás de la optimización del impulso.
13
propuesto por Boris Polyak en 1964. Por el contrario, el descenso de gradiente regular
simplemente realizará pasos pequeños y regulares por la pendiente, por lo que el
algoritmo tardará mucho más tiempo en llegar al final.
Recordemos que el Descenso de gradiente actualiza los pesos θ restando directamente
el gradiente de la función de costo J(θ) con respecto a los pesos (
por la tasa de aprendizaje η. La ecuación es: θ ← θ – η
J(θ)) multiplicado
θ
J(θ). No le importa
θ cuáles fueron
los gradientes anteriores. Si el gradiente local es minúsculo, avanza muy lentamente.
La optimización del momento se preocupa mucho por los gradientes anteriores: en cada
iteración, resta el gradiente local del vector de momento m (multiplicado por la tasa de
aprendizaje η) y actualiza los pesos añadiendo este vector de momento (véase la
ecuación 11­4). En otras palabras, el gradiente se utiliza para la aceleración, no para
la velocidad. Para simular algún tipo de mecanismo de fricción y evitar que el momento
crezca demasiado, el algoritmo introduce un nuevo hiperparámetro β, llamado momento,
que debe establecerse entre 0 (alta fricción) y 1 (sin fricción). Un valor de momento
típico es 0,9.
Ecuación 11­4. Algoritmo de momento
1. m ← βm − η
θJ (θ) 2. θ ←
θ+m
Puede verificar fácilmente que si el gradiente permanece constante, la velocidad
terminal (es decir, el tamaño máximo de las actualizaciones de peso) es igual a ese
gradiente multiplicado por la tasa de aprendizaje η multiplicada por (ignorando el signo).
1 1−β
Por ejemplo, si β = 0,9, entonces la velocidad terminal es igual a 10 veces el
gradiente multiplicado por la tasa de aprendizaje, por lo que la optimización del
momento termina siendo 10 veces más rápida que el descenso del gradiente.
Esto permite que la optimización del momento escape de las mesetas mucho más rápido que
Machine Translated by Google
Descenso de gradiente. Vimos en el Capítulo 4 que cuando las entradas tienen
escalas muy diferentes, la función de costo se verá como un cuenco alargado (ver
Figura 4­7). El Descenso de gradiente desciende por la pendiente pronunciada
bastante rápido, pero luego tarda mucho tiempo en bajar por el valle. Por el
contrario, la optimización del momento se desplazará por el valle cada vez más rápido
hasta llegar al fondo (el óptimo). En las redes neuronales profundas que no utilizan la
Normalización por lotes, las capas superiores a menudo terminarán teniendo entradas con
escalas muy diferentes, por lo que el uso de la optimización del momento ayuda mucho.
También puede ayudar a superar los óptimos locales.
NOTA
Debido al impulso, el optimizador puede sobrepasar un poco el límite, luego volver a
sobrepasar el límite, volver a sobrepasar el límite y oscilar así muchas veces antes de
estabilizarse en el mínimo. Esta es una de las razones por las que es bueno tener un poco de
fricción en el sistema: elimina estas oscilaciones y, por lo tanto, acelera la convergencia.
Implementar la optimización del impulso en Keras es muy sencillo: solo hay que usar el
optimizador SGD y configurar su hiperparámetro de impulso, luego relajarse y obtener
ganancias.
optimizador = keras.optimizers.SGD(lr=0,001, momentum=0,9)
El único inconveniente de la optimización del momento es que agrega otro hiperparámetro
para ajustar. Sin embargo, el valor de momento de 0,9 suele funcionar bien en la práctica
y casi siempre va más rápido que el descenso de gradiente normal.
Gradiente acelerado de Nesterov
Una pequeña variante de la optimización del momento, propuesta por Yurii Nesterov en
14
1983, es casi siempre más rápida que la optimización del momento tradicional.
El método de gradiente acelerado de Nesterov (NAG), también conocido como
optimización del momento de Nesterov, mide el gradiente del costo
Machine Translated by Google
función no en la posición local θ sino ligeramente por delante en la dirección del
momento, en θ + βm (véase la ecuación 11­5).
Ecuación 11­5. Algoritmo de gradiente acelerado de Nesterov
1. m ← βm − η
θJ (θ + βm) 2. θ ← θ
+m
Este pequeño ajuste funciona porque, en general, el vector de momento apuntará en la
dirección correcta (es decir, hacia el óptimo), por lo que será ligeramente más
preciso utilizar el gradiente medido un poco más lejos en esa dirección en lugar del
gradiente en la posición original, como se puede ver en la Figura 11­6 (donde
representa el gradiente de1la función de costo medida en el punto de inicio θ, y
representa el gradiente en el punto ubicado 2en θ + βm).
Como puede ver, la actualización de Nesterov termina un poco más cerca del
punto óptimo. Después de un tiempo, estas pequeñas mejoras se acumulan y NAG
termina siendo significativamente más rápido que la optimización de impulso normal.
Además, tenga en cuenta que cuando el impulso empuja los pesos a través de un
valle,
continúa
empujando más a través del valle, mientras que
1
empuja
2 hacia
el fondo del valle. Esto ayuda a reducir las oscilaciones y, por lo tanto, NAG converge
más rápido.
NAG es generalmente más rápido que la optimización de momento normal. Para
utilizarlo, simplemente configure nesterov=True al crear el optimizador SGD:
optimizador = keras.optimizers.SGD(lr=0.001, impulso=0.9, nesterov=True)
Machine Translated by Google
Figura 11­6. Optimización de momento regular versus Nesterov: la primera aplica los gradientes
calculados antes del paso de momento, mientras que la segunda aplica los gradientes calculados después.
AdaGrad
Consideremos nuevamente el problema del cuenco alargado: el descenso de
gradiente comienza descendiendo rápidamente por la pendiente más
pronunciada, que no apunta directamente hacia el óptimo global, y luego desciende
muy lentamente hasta el fondo del valle. Sería bueno que el algoritmo pudiera
corregir su dirección antes para apuntar un poco más hacia el óptimo global.
El algoritmo15
AdaGrad El algoritmo logra esta corrección reduciendo el vector de
gradiente a lo largo de las dimensiones más pronunciadas (ver ecuación 11­6).
Ecuación 11­6. Algoritmo AdaGrad
1.
s←s+
2. θ ← θ − η
θJ (θ)
θJ (θ)
θJ (θ)
√s + ε
El primer paso acumula el cuadrado de los gradientes en el vector s (recuerde
que el símbolo
representa la multiplicación elemento por elemento).
Esta forma vectorizada es equivalente a calcular s ← s + i(∂ J(θ)i / ∂ θ ) para cadai 2
elemento s del vectori s; en otras palabras, cada s acumula el
i
Machine Translated by Google
los cuadrados de la derivada parcial de la función de costo con respecto al parámetro
El
θ serán cada i . Si la función de costo es empinada a lo largo de la dimensión i , entonces si
vez más grandes en cada iteración.
El segundo paso es casi idéntico al Descenso de gradiente, pero con una gran diferencia: el
vector de gradiente se reduce por un factor de √s + ε (el símbolo
representa la división
elemento por elemento, y ε es un término de suavizado para evitar la división por cero,
–10
normalmente establecido en 10). Esta forma vectorizada es equivalente a calcular
simultáneamente θi ← θi − η ∂J(θ)/∂θi/√si + ε para todos los parámetros θ
i.
En resumen, este algoritmo reduce la tasa de aprendizaje, pero lo hace más rápido en
dimensiones con pendientes pronunciadas que en dimensiones con pendientes más suaves.
Esto se denomina tasa de aprendizaje adaptativa. Ayuda a orientar las actualizaciones
resultantes más directamente hacia el óptimo global (consulte la Figura 11­7). Un beneficio
adicional es que requiere mucho menos ajuste del hiperparámetro de tasa de aprendizaje η.
Figura 11­7. AdaGrad versus descenso de gradiente: el primero puede corregir su dirección antes
para apuntar al óptimo.
AdaGrad suele funcionar bien para problemas cuadráticos simples, pero a menudo se detiene
demasiado pronto al entrenar redes neuronales. La tasa de aprendizaje se reduce tanto que el
algoritmo termina deteniéndose por completo antes de alcanzar el óptimo global. Por lo tanto,
aunque Keras tiene un Adagrad
Machine Translated by Google
Optimizador, no deberías usarlo para entrenar redes neuronales profundas (aunque puede ser
eficiente para tareas más simples como la regresión lineal). De todos modos, comprender
AdaGrad es útil para comprender otros optimizadores de tasa de aprendizaje adaptativo.
Propiedad RMS
Como hemos visto, AdaGrad corre el riesgo de desacelerarse demasiado rápido y nunca converger
16
al óptimo global. El algoritmo RMSProp soluciona este problema acumulando solo los gradientes de
las iteraciones más recientes (en lugar de todos los gradientes desde el comienzo del entrenamiento).
Para ello, utiliza una desintegración exponencial en el primer paso (consulte la ecuación 11­7).
Ecuación 11­7. Algoritmo RMSProp
1.
s ← βs + (1 − β)
2. θ ← θ − η
θJ (θ)
θJ (θ)
θJ (θ)
√s + ε
La tasa de decaimiento β se establece normalmente en 0,9. Sí, se trata de un nuevo
hiperparámetro, pero este valor predeterminado suele funcionar bien, por lo que es posible que no
sea necesario ajustarlo en absoluto.
Como era de esperar, Keras tiene un optimizador RMSprop:
optimizador = keras.optimizers.RMSprop(lr=0.001, rho=0.9)
Excepto en problemas muy simples, este optimizador casi siempre funciona mucho mejor que
AdaGrad. De hecho, era el algoritmo de optimización preferido de muchos investigadores hasta
que apareció Adam Optimization.
Optimización de Adam y Nadam
17
Adán, que significa estimación de momento adaptativo, combina las ideas de optimización de momento
y RMSProp: al igual que la optimización de momento, realiza un seguimiento de un
promedio decreciente exponencialmente del pasado.
Machine Translated by Google
gradientes; y al igual que RMSProp, realiza un seguimiento de un promedio decreciente
18
exponencialmente de gradientes cuadrados pasados (ver Ecuación 11­8).
Ecuación 11­8. Algoritmo de Adam
1. m ← β1m − (1 − β1)
2.
← β2s + (1 − β2)
ˆs ←
θJ (θ)
metro
3. ˆm ←
4.
θJ (θ) s
θJ (θ)
1 − β1
s
1 − β2
5. θ ← θ + η ˆm
√ˆs + ε
En esta ecuación, t representa el número de iteración (comenzando en 1).
Si solo observa los pasos 1, 2 y 5, notará la gran similitud de Adam con la optimización del
momento y RMSProp. La única diferencia es que el paso 1 calcula un promedio que decae
exponencialmente en lugar de una suma que decae exponencialmente, pero en realidad son
equivalentes excepto por un factor constante (el promedio que decae es solo 1 – β multiplicado
por la suma que decae). Los pasos 3 y 4 son un detalle técnico: dado que m y s se inicializan en 0,
1
estarán sesgados hacia 0 al comienzo del entrenamiento, por lo que estos dos pasos ayudarán a impulsar
m y s al comienzo del entrenamiento.
El hiperparámetro de decaimiento de momento β se inicializa
1 normalmente en 0,9, mientras que el
hiperparámetro de decaimiento de escala β suele inicializarse
2 en 0,999. Como antes, el término de
suavizado ε suele inicializarse en un número minúsculo como
–
7 10 Estos son los valores predeterminados para la clase Adam (para ser precisos, epsilon
tiene como valor predeterminado Ninguno, lo que le indica a Keras que use
–7
keras.backend.epsilon(), cuyo valor predeterminado es 10; puede cambiarlo utilizando
keras.backend.set_epsilon(). A continuación, se muestra cómo crear un optimizador de Adam utilizando
Keras:
optimizador = keras.optimizadores.Adam(lr=0.001, beta_1=0.9, beta_2=0.999)
Machine Translated by Google
Dado que Adam es un algoritmo de tasa de aprendizaje adaptativo (como AdaGrad y
RMSProp), requiere menos ajuste del hiperparámetro de tasa de aprendizaje η.
A menudo puedes usar el valor predeterminado η = 0,001, lo que hace que Adam sea aún más
fácil de usar que Gradient Descent.
CONSEJO
Si empieza a sentirse abrumado por todas estas técnicas diferentes y se pregunta
cómo elegir las adecuadas para su tarea, no se preocupe: al final de este capítulo
encontrará algunas pautas prácticas.
Finalmente, cabe mencionar dos variantes de Adán:
Adámax
Observe que en el paso 2 de la ecuación 11­8, Adam acumula los cuadrados de los
gradientes en s (con un peso mayor para los pesos más recientes). En el paso 5, si
ignoramos ε y los pasos 3 y 4 (que son detalles técnicos de todos modos), Adam
reduce las actualizaciones de parámetros por la raíz cuadrada de s. En resumen, Adam
reduce las actualizaciones de parámetros por la norma ℓ de los gradientes decaídos
2 en el
tiempo (recuerde que la norma ℓ es la raíz cuadrada de la suma
2 de los cuadrados).
AdaMax, introducido en el mismo artículo que Adam, reemplaza la norma ℓ con la norma
ℓ (una forma elegante de decir
reemplaza el paso 2 de la
∞
2 el máximo). En concreto,
ecuación 11­8 por s ← max(β2s,
θJ (θ)), omite el paso 4 y, en el paso 5,
reduce la escala de las actualizaciones de gradiente por un factor de s, que es
simplemente el máximo de los gradientes decaídos en el tiempo. En la práctica, esto
puede hacer que AdaMax sea más estable que Adam, pero realmente depende del conjunto
de datos y, en general, Adam funciona mejor. Por lo tanto, este es solo un
optimizador más que puede probar si experimenta problemas con Adam en alguna tarea.
Nada
La optimización de Nadam es la optimización de Adam más el truco de Nesterov, por lo
que a menudo convergerá un poco más rápido que Adam. En su informe de
19
presentación de esta técnica, el investigador Timothy Dozat compara
Machine Translated by Google
muchos optimizadores diferentes en varias tareas y descubre que Nadam
generalmente supera a Adam, pero a veces es superado por RMSProp.
ADVERTENCIA
Los métodos de optimización adaptativa (incluidas las optimizaciones RMSProp, Adam y
Nadam) suelen ser excelentes y convergen rápidamente hacia una buena solución. Sin embargo, un
artículo20
de 2017 de Ashia C. Wilson et al. demostró que pueden conducir a soluciones que se
generalizan de manera deficiente en algunos conjuntos de datos. Por lo tanto, cuando esté decepcionado
con el rendimiento de su modelo, intente utilizar en su lugar el gradiente acelerado de Nesterov: es
posible que su conjunto de datos sea alérgico a los gradientes adaptativos. Consulte también las últimas
investigaciones, porque avanzan rápidamente.
Todas las técnicas de optimización analizadas hasta ahora se basan únicamente en
las derivadas parciales de primer orden (las jacobianas). La literatura sobre
optimización también contiene algoritmos sorprendentes basados en las derivadas parciales
de segundo orden (las hessianas, que son las derivadas parciales de las jacobianas).
Lamentablemente, estos algoritmos son muy difíciles de aplicar a las redes
2
neuronales profundas porque hay n hessianos por salida (donde n es la cantidad de
parámetros), en lugar de solo n jacobianos por salida. Como las DNN suelen tener
decenas de miles de parámetros, los algoritmos de optimización de segundo
orden a menudo ni siquiera caben en la memoria, e incluso cuando lo hacen, el cálculo
de los hessianos es demasiado lento.
Machine Translated by Google
ENTRENAMIENTO DE MODELOS DISPERSOS
Todos los algoritmos de optimización que acabamos de presentar producen modelos densos,
lo que significa que la mayoría de los parámetros serán distintos de cero. Si necesita un modelo
increíblemente rápido en tiempo de ejecución o si necesita que ocupe menos memoria, es
posible que prefiera terminar con un modelo disperso.
Una forma sencilla de lograrlo es entrenar el modelo como de costumbre y luego eliminar los
pesos diminutos (establecerlos en cero). Tenga en cuenta que esto normalmente no dará
como resultado un modelo muy disperso y puede degradar el rendimiento del
modelo.
Una mejor opción es aplicar una regularización ℓ1fuerte durante el entrenamiento (veremos
cómo más adelante en este capítulo), ya que empuja al optimizador a poner en cero tantos
pesos como pueda (como se analiza en “Regresión Lasso” en el Capítulo 4).
Si estas técnicas siguen siendo insuficientes, consulte el kit de herramientas de
optimización de modelos TensorFlow (TF­MOT), que proporciona una API de poda capaz de
eliminar conexiones de forma iterativa durante el entrenamiento en función de su magnitud.
La Tabla 11­2 compara todos los optimizadores que hemos analizado hasta ahora (* es malo, ** es
promedio y *** es bueno).
Machine Translated by Google
Tabla 11­2. Comparación de optimizadores
Clase
Velocidad de convergencia Calidad de convergencia
*
***
**
***
**
***
Dólares singapurenses
SGD(impulso=...)
SGD(impulso=..., nesterov=Verdadero)
Adagrado
Prop. RMS
Adán
Nada
Adámax
***
* (se detiene demasiado pronto)
***
** o ***
***
** o ***
***
** o ***
***
** o ***
Programación de la tasa de aprendizaje
Encontrar una buena tasa de aprendizaje es muy importante. Si la estableces demasiado alta,
El entrenamiento puede divergir (como comentamos en “Descenso de gradiente”). Si lo configuras
demasiado bajo, el entrenamiento eventualmente convergerá al óptimo, pero tomará
durante mucho tiempo. Si lo configuras un poco demasiado alto, el progreso será muy lento.
Al principio rápidamente, pero terminará bailando alrededor del óptimo, nunca
realmente estableciéndose. Si tiene un presupuesto informático limitado, puede
tener que interrumpir el entrenamiento antes de que haya convergido correctamente, produciendo un
solución subóptima (ver Figura 11­8).
Machine Translated by Google
Figura 11­8. Curvas de aprendizaje para distintas tasas de aprendizaje η
Como comentamos en el Capítulo 10, puedes encontrar una buena tasa de aprendizaje
entrenando el modelo durante unos cientos de iteraciones, aumentando exponencialmente la tasa de
aprendizaje desde un valor muy pequeño a un valor muy grande y luego observando la curva de
aprendizaje y eligiendo una tasa de aprendizaje ligeramente inferior a aquella en la que la curva
de aprendizaje comienza a aumentar. Luego puedes reinicializar tu modelo y entrenarlo con esa tasa
de aprendizaje.
Pero se puede hacer algo mejor que una tasa de aprendizaje constante: si se empieza con una tasa de
aprendizaje alta y luego se reduce una vez que el entrenamiento deja de hacer un progreso rápido,
se puede llegar a una buena solución más rápido que con la tasa de aprendizaje constante
óptima. Hay muchas estrategias diferentes para reducir la tasa de aprendizaje durante el
entrenamiento. También puede ser beneficioso empezar con una tasa de aprendizaje baja,
aumentarla y luego volver a reducirla. Estas estrategias se denominan programas de aprendizaje
(introdujimos brevemente este concepto en el Capítulo 4). Estos son los programas de aprendizaje más
utilizados:
Programación de energía
Establezca la tasa de aprendizaje en función del número de iteraciones t: η(t) = η / (1 + t/s) . La0
do
tasa de aprendizaje
inicial η , la potencia c (normalmente
establecida en 1) y los pasos s son
0
hiperparámetros. La tasa de aprendizaje disminuye en cada paso. Después de s pasos, baja a
η / 2. Después de s pasos más, baja a η / 3, luego
baja a η / 4, luego a η / 5, y así sucesivamente.
0
Como
puede
0
0
0
Machine Translated by Google
Mira, este programa primero cae rápidamente, luego cada vez más lentamente. Por
supuesto, la programación de potencia requiere ajustar η y0 s (y posiblemente c).
Programación exponencial
t/s
Establezca la tasa de aprendizaje en η(t) =
0 η 0,1. La tasa de aprendizaje disminuirá
gradualmente en un factor de 10 cada s pasos. Mientras que la programación de potencia
reduce la tasa de aprendizaje cada vez más lentamente, la programación exponencial la sigue
reduciendo en un factor de 10 cada s pasos.
Programación constante por partes
Utilice una tasa de aprendizaje constante para una cantidad de épocas (por ejemplo,
η = 0,1
0
para 5 épocas), luego una tasa de aprendizaje menor para otra cantidad de épocas (por
ejemplo, η1 = 0,001 para 50 épocas), y así sucesivamente. Aunque esta solución puede
funcionar muy bien, requiere hacer algunas pruebas para determinar la secuencia
correcta de tasas de aprendizaje y durante cuánto tiempo utilizar cada una de ellas.
Programación de rendimiento
Mida el error de validación cada N pasos (como para la detención anticipada) y
reduzca la tasa de aprendizaje por un factor de λ cuando el error deje de disminuir.
Programación de 1 ciclo
A diferencia de otros enfoques, 1cycle (introducido en un artículo de 2018) por Leslie Smith) 21
comienza aumentando la tasa de aprendizaje inicial η , creciendo linealmente hasta 0η
a la mitad del entrenamiento. Luego
1 disminuye la tasa de aprendizaje linealmente hasta η
nuevamente durante la segunda mitad del entrenamiento,
terminando las últimas épocas
0
reduciendo la tasa en varios órdenes de magnitud (todavía linealmente). La tasa de
aprendizaje máxima η se elige utilizando el mismo enfoque que usamos para encontrar la
tasa de 1aprendizaje óptima, y la tasa de aprendizaje inicial η se elige para que sea
aproximadamente 10 veces menor. Cuando usamos un momento,
comenzamos primero con
0
un momento alto (por ejemplo, 0,95), luego lo reducimos a un momento más
bajo durante la primera mitad del entrenamiento (por ejemplo, hasta 0,85, linealmente) y
luego lo volvemos a subir al valor máximo (por ejemplo, 0,95) durante la segunda mitad.
Machine Translated by Google
la mitad del entrenamiento, terminando las últimas épocas con ese valor máximo.
Smith realizó muchos experimentos que demostraron que este enfoque a menudo
podía acelerar considerablemente el entrenamiento y alcanzar un mejor
rendimiento. Por ejemplo, en el popular conjunto de datos de imágenes CIFAR10, este
enfoque alcanzó una precisión de validación del 91,9 % en solo 100 épocas, en
lugar del 90,3 % de precisión en 800 épocas a través de un enfoque estándar (con la
misma arquitectura de red neuronal).
22 Andrew Senior et al. compararon el rendimiento de algunas de las
Un artículo de 2013
programaciones de aprendizaje más populares al utilizar la optimización del momento
para entrenar redes neuronales profundas para el reconocimiento de voz. Los autores
concluyeron que, en este contexto, tanto la programación de rendimiento como la
programación exponencial funcionaron bien. Se inclinaron por la programación
exponencial porque era fácil de ajustar y convergía ligeramente más rápido a la solución
óptima (también mencionaron que era más fácil de implementar que la programación de
rendimiento, pero en Keras ambas opciones son fáciles). Dicho esto, el enfoque de 1 ciclo
parece funcionar incluso mejor.
Implementar la programación de energía en Keras es la opción más sencilla: simplemente
configure el hiperparámetro de decaimiento al crear un optimizador:
optimizador = keras.optimizers.SGD(lr=0.01, decaimiento=1e­4)
La desintegración es la inversa de s (el número de pasos que se necesitan para dividir la
tasa de aprendizaje por una unidad más), y Keras supone que c es igual a 1.
La programación exponencial y la programación por partes también son bastante simples.
Primero, debe definir una función que tome la época actual y devuelva la tasa de aprendizaje.
Por ejemplo, implementemos la programación exponencial:
def exponential_decay_fn(época):
devuelve 0,01 * 0,1**(época / 20)
Si no desea codificar η y s, puede crear una
0 función que devuelva una función configurada:
Machine Translated by Google
def decaimiento exponencial(lr0, s):
def exponential_decay_fn(época):
devuelve lr0 * 0.1**(época / s)
devuelve exponencial_decaimiento_función
función_de_decaimiento_exponencial = decaimiento_exponencial(lr0=0.01, s=20)
A continuación, cree una devolución de llamada LearningRateScheduler, asígnele la función
de programación y pase esta devolución de llamada al método fit():
lr_scheduler =
keras.callbacks.LearningRateScheduler(función_de_decadencia_exponencial) history
= model.fit(X_train_scaled, y_train, [...], callbacks= [lr_scheduler])
LearningRateScheduler actualizará el atributo learning_rate del optimizador al comienzo de
cada época. Actualizar la tasa de aprendizaje una vez por época suele ser suficiente, pero
si desea que se actualice con más frecuencia, por ejemplo en cada paso, siempre puede
escribir su propia devolución de llamada (consulte la sección "Programación exponencial" del
cuaderno para ver un ejemplo).
Actualizar la tasa de aprendizaje en cada paso tiene sentido si hay muchos pasos por
época. Como alternativa, puede utilizar el enfoque
keras.optimizers.schedules, que se describe a continuación.
La función de programación puede tomar opcionalmente la tasa de aprendizaje actual
como segundo argumento. Por ejemplo, la siguiente función de programación multiplica la tasa
de aprendizaje anterior por una
1/20
, lo que da como resultado el mismo exponencial
disminución de 0,1 (excepto que la disminución ahora comienza al comienzo de la época 0
en lugar de 1):
def exponential_decay_fn(época, lr):
devuelve lr * 0,1**(1 / 20)
Esta implementación se basa en la tasa de aprendizaje inicial del optimizador (a
diferencia de la implementación anterior), así que asegúrese de configurarla
adecuadamente.
Machine Translated by Google
Cuando guardas un modelo, el optimizador y su tasa de aprendizaje se guardan junto con
él. Esto significa que con esta nueva función de programación, puedes simplemente cargar un
modelo entrenado y continuar con el entrenamiento donde lo dejaste, sin problemas. Sin
embargo, las cosas no son tan simples si tu función de programación usa el argumento
epoch: la época no se guarda y se restablece a 0 cada vez que llamas al método fit(). Si
continuaras con el entrenamiento de un modelo donde lo dejaste, esto podría generar
una tasa de aprendizaje muy alta, lo que probablemente dañaría los pesos de tu modelo. Una
solución es configurar manualmente el argumento initial_epoch del método fit() para que la época
comience en el valor correcto.
Para la programación constante por partes, puede utilizar una función de programación como
la siguiente (como antes, puede definir una función más general si lo desea; consulte la
sección “Programación constante por partes” del cuaderno para ver un ejemplo), luego
cree una devolución de llamada LearningRateScheduler con esta función y pásela al método fit(),
tal como lo hicimos para la programación exponencial:
def piecewise_constant_fn(época): si época < 5: devuelve
0,01
época elif < 15: devuelve
0,005
demás:
devuelve 0,001
Para programar el rendimiento, utilice la devolución de llamada ReduceLROnPlateau. Por
ejemplo, si pasa la siguiente devolución de llamada al método fit(), multiplicará la tasa de
aprendizaje por 0,5 siempre que la mejor pérdida de validación no mejore durante cinco épocas
consecutivas (hay otras opciones disponibles; consulte la documentación para obtener más
detalles):
lr_scheduler = keras.callbacks.ReduceLROnPlateau(factor=0.5, paciencia=5)
Por último, tf.keras ofrece una forma alternativa de implementar la programación de la
tasa de aprendizaje: definir la tasa de aprendizaje utilizando una de las programaciones disponibles en
Machine Translated by Google
keras.optimizers.schedules, luego pase esta tasa de aprendizaje a cualquier
optimizador. Este enfoque actualiza la tasa de aprendizaje en cada paso en lugar de
en cada época. Por ejemplo, aquí se muestra cómo implementar el mismo
programación exponencial como la función exponential_decay_fn() que
definido anteriormente:
# Número de en 20 épocas (pasos de tamaño de lote)
s = 20 * len(X_tren) // 32
32)
tasa_de_aprendizaje = keras.optimizers.schedules.ExponentialDecay(0.01, s, 0.1)
optimizador = keras.optimizers.SGD(tasa_de_aprendizaje)
=
Esto es agradable y simple, además, cuando guardas el modelo, la tasa de aprendizaje
y su programación (incluido su estado) también se guarda. Este enfoque,
Sin embargo, no es parte de la API de Keras; es específico de tf.keras.
En cuanto al enfoque de 1 ciclo, su implementación no plantea ningún desafío particular.
Dificultad: simplemente crea una devolución de llamada personalizada que modifique la tasa de aprendizaje en
cada iteración (puede actualizar la tasa de aprendizaje del optimizador cambiando
self.model.optimizer.lr). Consulte la sección “Programación de 1 ciclos” del
Cuaderno como ejemplo.
En resumen, la disminución exponencial, la programación del rendimiento y 1 ciclo pueden
aceleran considerablemente la convergencia, ¡así que pruébalos!
Cómo evitar el sobreajuste mediante la regularización
Con cuatro parámetros puedo ajustar un elefante y con cinco puedo hacerlo
menear su trompa.
—John von Neumann, citado por Enrico Fermi en Nature
427
Con miles de parámetros, puedes adaptar todo el zoológico. Inteligencia artificial profunda
Las redes suelen tener decenas de miles de parámetros, a veces incluso
millones. Esto les da una increíble cantidad de libertad y medios.
Pueden adaptarse a una gran variedad de conjuntos de datos complejos. Pero esta gran flexibilidad
Machine Translated by Google
También hace que la red sea propensa a sobreajustar el conjunto de entrenamiento.
Necesitamos regularización.
Ya hemos implementado una de las mejores técnicas de regularización en el Capítulo 10: la
detención temprana. Además, aunque la Normalización por Lotes fue diseñada para resolver los
problemas de gradientes inestables, también actúa como un regularizador bastante bueno. En
esta sección, examinaremos otras técnicas de regularización populares para redes neuronales:
regularización ℓ y ℓ, abandono y regularización de norma máxima.1
2
Regularización
1
2 ℓ y ℓ Tal como lo hizo en el
Capítulo 4 para modelos lineales simples, puede usar la regularización ℓ para restringir los
2
pesos de conexión de una red neuronal y/o la regularización ℓ si desea un modelo disperso (con
muchos
pesos iguales a 0). Aquí se explica cómo aplicar la regularización ℓ a los pesos de conexión
1
de una capa de Keras, utilizando un2 factor de regularización de 0,01:
capa = keras.capas.Dense(100, activación="elu",
kernel_initializer="él_normal",
kernel_regularizer=keras.regularizadores.l2(0.01))
La función l2() devuelve un regularizador que se llamará en cada paso durante el entrenamiento
para calcular la pérdida de regularización. Esta se suma luego a la pérdida final. Como es de
esperar, puede utilizar simplemente keras.regularizadores.l1() si desea
regularización ℓ; si desea regularización ℓ y ℓ, utilice keras.regularizadores.l1_l2()
(especificando
1
ambos factores
de2regularización).
1
Dado que normalmente querrá aplicar el mismo regularizador a todas las capas de su red, así como
utilizar la misma función de activación y la misma estrategia de inicialización en todas las capas
ocultas, es posible que se encuentre repitiendo los mismos argumentos. Esto hace que el código sea
feo y propenso a errores. Para evitarlo, puede intentar refactorizar su código para utilizar bucles.
Otra opción es
Machine Translated by Google
Utilice la función functools.partial() de Python, que le permite crear un contenedor fino para
cualquier objeto invocable, con algunos valores de argumento predeterminados:
desde functools importar parcial
RegularizedDense = parcial(keras.capas.Dense,
activación="elu",
inicializador_del_kernel="he_normal",
kernel_regularizer=keras.regularizadores.l2(0.01))
modelo =
keras.modelos.Sequential([ keras.capas.Flatten(forma_de_entrada=[28, 28]), RegularizedDense(300), Regulari
inicializador_del_kernel="glorot_uniforme")
])
Abandonar
La deserción es una de las técnicas de regularización más populares para redes
neuronales profundas. Fue propuesta en un artículo Por23Geoffrey Hinton en 2012
24 Nitish Srivastava et al., y ha demostrado ser
y se detalla más en un documento de 2014 por
muy exitoso: incluso las redes neuronales de última generación obtienen un aumento de
precisión del 1 al 2 % simplemente agregando la pérdida de datos. Esto puede no parecer
mucho, pero cuando un modelo ya tiene una precisión del 95 %, obtener un aumento
de precisión del 2 % significa reducir la tasa de error en casi un 40 % (pasando del 5 % de
error a aproximadamente el 3 %).
Es un algoritmo bastante simple: en cada paso de entrenamiento, cada neurona
(incluidas las neuronas de entrada, pero siempre excluyendo las neuronas de salida) tiene
una probabilidad p de ser "abandonada" temporalmente, lo que significa que será
completamente ignorada durante este paso de entrenamiento, pero puede estar activa
durante el siguiente paso (ver Figura 11­9). El hiperparámetro p se llama tasa de abandono
y generalmente se establece entre el 10% y el 50%: más cerca del 20­30% en redes
neuronales recurrentes (ver Capítulo 15), y más cerca del 40­50% en redes
neuronales convolucionales (ver Capítulo 14). Después del entrenamiento, las neuronas
Machine Translated by Google
No te dejes caer más. Y eso es todo (salvo un detalle técnico que comentaremos en
breve).
Figura 11­9. Con la regularización de abandono, en cada iteración de entrenamiento se “elimina” un
subconjunto aleatorio de todas las neuronas en una o más capas (excepto la capa de salida); estas
neuronas generan 0 en esta iteración (representado por las flechas discontinuas).
Al principio, resulta sorprendente que esta técnica destructiva funcione. ¿Una empresa
obtendría mejores resultados si se les pidiera a sus empleados que lanzaran una
moneda cada mañana para decidir si ir o no a trabajar? Bueno, quién sabe,
¡quizás sí! La empresa se vería obligada a adaptar su organización; no podría depender
de una sola persona para manejar la máquina de café o realizar cualquier otra
tarea crítica, por lo que esta experiencia tendría que distribuirse entre varias personas.
Los empleados tendrían que aprender a cooperar con muchos de sus compañeros,
no solo con un puñado de ellos. La empresa se volvería mucho más resistente. Si una
persona se marchara, no habría mucha diferencia. No está claro si esta idea realmente
funcionaría para las empresas, pero ciertamente lo hace para las redes
neuronales. Las neuronas entrenadas con la técnica de abandono no pueden
coadaptarse con sus neuronas vecinas; tienen que ser tan flexibles como las que se encuentran en la re
Machine Translated by Google
Por sí solos, no pueden depender excesivamente de unas pocas neuronas de entrada, sino que
deben prestar atención a cada una de ellas.
Terminan siendo menos sensibles a pequeños cambios en las entradas. Al final, se obtiene una
red más robusta que generaliza mejor.
Otra forma de entender el poder de la deserción es darse cuenta de que se genera una red
neuronal única en cada paso de entrenamiento. Dado que cada neurona puede estar presente o
norte
ausente, hay un total de 2 redes posibles (donde N es el número total de neuronas que se pueden
descartar). Este es un número tan grande que es prácticamente imposible que se muestree la
misma red neuronal dos veces.
Una vez que hayas ejecutado 10 000 pasos de entrenamiento, esencialmente habrás
entrenado 10 000 redes neuronales diferentes (cada una con una sola instancia de entrenamiento).
Estas redes neuronales obviamente no son independientes porque comparten muchos de sus
pesos, pero aun así son todas diferentes. La red neuronal resultante puede verse como un conjunto
promediado de todas estas redes neuronales más pequeñas.
CONSEJO
En la práctica, normalmente se puede aplicar la deserción solo a las neuronas de las primeras tres capas (excluyendo la capa de
salida).
Hay un pequeño pero importante detalle técnico. Supongamos que p = 50%, en cuyo caso
durante la prueba una neurona estaría conectada al doble de neuronas de entrada de las que
estaría (en promedio) durante el entrenamiento. Para compensar este hecho, necesitamos
multiplicar los pesos de conexión de entrada de cada neurona por 0,5 después del entrenamiento.
Si no lo hacemos, cada neurona recibirá una señal de entrada total aproximadamente el doble
de grande que la que se utilizó para entrenar la red y es poco probable que tenga un buen
rendimiento. De manera más general, necesitamos multiplicar cada peso de conexión de entrada
por la probabilidad de conservación (1 – p) después del entrenamiento.
Alternativamente, podemos dividir la salida de cada neurona por la probabilidad de conservación
durante el entrenamiento (estas alternativas no son perfectamente equivalentes, pero
funcionan igualmente bien).
Machine Translated by Google
Para implementar la deserción con Keras, puede utilizar la capa
keras.layers.Dropout. Durante el entrenamiento, descarta aleatoriamente algunas entradas
(estableciéndolas en 0) y divide las entradas restantes por la probabilidad de conservación.
Después del entrenamiento, no hace nada en absoluto; solo pasa las entradas a la siguiente capa. El
siguiente código aplica la regularización de la deserción antes de cada capa Dense, utilizando
una tasa de deserción de 0,2:
modelo =
keras.modelos.Secuencial([ keras.capas.Aplanar(forma_de_entrada=[28, 28]), keras.capas.Dropout(tasa=0.2), ker
keras.capas.Dropout(tasa=0.2),
keras.capas.Dense(100, activación="elu",
inicializador_del_kernel="he_normal"),
keras.capas.abandono(tasa=0,2),
keras.capas.denso(10, activación="softmax")
])
ADVERTENCIA
Dado que la pérdida de entrenamiento solo está activa durante el entrenamiento, comparar la pérdida de entrenamiento y
la pérdida de validación puede ser engañoso. En particular, un modelo puede estar sobreajustando el conjunto de
entrenamiento y, sin embargo, tener pérdidas de entrenamiento y validación similares. Por lo tanto, asegúrese de evaluar
la pérdida de entrenamiento sin pérdida de entrenamiento (por ejemplo, después del entrenamiento).
Si observa que el modelo se ajusta en exceso, puede aumentar la tasa de abandono. Por el
contrario, debe intentar reducir la tasa de abandono si el modelo no se ajusta lo suficiente al
conjunto de entrenamiento. También puede resultar útil aumentar la tasa de abandono para capas
grandes y reducirla para capas pequeñas. Además, muchas arquitecturas de última generación solo
utilizan el abandono después de la última capa oculta, por lo que puede que desee probar esto si el
abandono total es demasiado fuerte.
La deserción tiende a ralentizar significativamente la convergencia, pero normalmente da como
resultado un modelo mucho mejor cuando se ajusta correctamente. Por lo tanto, generalmente vale la
pena dedicarle tiempo y esfuerzo adicionales.
Machine Translated by Google
CONSEJO
Si desea regularizar una red autonormalizadora basada en la función de activación
SELU (como se discutió anteriormente), debe usar la caída alfa: esta es una variante
de la caída que preserva la media y la desviación estándar de sus entradas
(se introdujo en el mismo artículo que SELU, ya que la caída regular rompería
la autonormalización).
Abandono de Montecarlo (MC)
25
En 2016, un artículo Por Yarin Gal y Zoubin Ghahramani agregaron algunas buenas razones
más para usar la deserción:
En primer lugar, el artículo estableció una conexión profunda entre las
redes de abandono (es decir, redes neuronales que contienen una capa de
abandono antes de cada capa de peso) y la inferencia bayesiana
26
aproximada, lo que le da al abandono una sólida justificación matemática.
En segundo lugar, los autores introdujeron una poderosa técnica llamada MC
Dropout, que puede aumentar el rendimiento de cualquier modelo de abandono
entrenado sin tener que volver a entrenarlo o incluso modificarlo en
absoluto, proporciona una medida mucho mejor de la incertidumbre del modelo y
también es sorprendentemente sencilla de implementar.
Si todo esto suena como un anuncio de “un truco raro”, entonces eche un vistazo al código
siguiente. Es la implementación completa de MC Dropout, que potencia el modelo
de abandono que entrenamos antes sin volver a entrenarlo:
y_probas = np.stack([modelo(X_test_scaled, entrenamiento=True) para la
muestra en el rango(100)])
y_proba = y_probas.media(eje=0)
Simplemente hacemos 100 predicciones sobre el conjunto de prueba, configuramos
training=True para asegurarnos de que la capa Dropout esté activa y apilamos las
predicciones. Como Dropout está activo, todas las predicciones serán diferentes.
Recuerde que predict() devuelve una matriz con una fila por instancia y una columna por
clase. Debido a que hay 10 000 instancias en el conjunto de prueba y 10 clases, esto
Machine Translated by Google
es una matriz de forma [10000, 10]. Apilamos 100 de estas matrices, por lo que y_probas
es una matriz de forma [100, 10000, 10]. Una vez que promediamos sobre el primer
dimensión (eje=0), obtenemos y_proba, una matriz de forma [10000, 10], como
obtendríamos con una sola predicción. ¡Eso es todo! Promediando sobre múltiples
Las predicciones con abandono activado nos dan una estimación de Monte Carlo que es
Generalmente más confiable que el resultado de una sola predicción con abandono
Desactivado. Por ejemplo, veamos la predicción del modelo para la primera instancia.
En el conjunto de prueba, con deserción:
>>> np.round(modelo.predict(X_test_scaled[:1]), 2)
, 0.
, 0.
, 0.
, 0.
matriz([[0. , , 0.01, 0.0.
dtype=float32)
, 0.
, 0,99]],
El modelo parece casi seguro que esta imagen pertenece a la clase 9 (tobillo)
¿Debemos confiar en él? ¿Realmente hay tan poco margen para la duda?
Compare esto con las predicciones realizadas cuando se activa la deserción:
>>> np.round(y_probas[:, :1], 2)
, 0.
matriz([[[0. , 0. [[0. , [[0. , [...]
0.
, 0.
0.
, 0.
, 0.
, 0.
, 0.
, 0.
, 0.
, 0.
, 0,14, 0. ,
0,16, 0. ,
0,02, 0.
, 0,17, 0,0,2 0.
,
,
, 0,01, 0.
, 0,68]],
, 0,64]],
, 0,97]],
Esto cuenta una historia muy diferente: aparentemente, cuando activamos la deserción, el
El modelo ya no está seguro. Todavía parece preferir la clase 9, pero a veces
Vacila con las clases 5 (sandalia) y 7 (zapatilla), lo cual tiene sentido.
Dado que todos son calzados, una vez que hacemos el promedio de la primera dimensión,
Obtenga las siguientes predicciones de MC Dropout:
>>> np.round(y_proba[:1], 2)
,
matriz([[0. , 0. 0. 0. dtype=float32)
,
, 0.
, 0,22, 0.
, 0,16, 0.
, 0,62]],
El modelo todavía piensa que esta imagen pertenece a la clase 9, pero solo con un 62%.
confianza, que parece mucho más razonable que el 99%. Además, es útil
para saber exactamente qué otras clases cree que son probables. Y también puede
Eche un vistazo a la desviación estándar de las estimaciones de probabilidad:
Machine Translated by Google
>>> y_std = y_probas.std(eje=0)
>>> np.round(y_std[:1], 2)
0.
,
,
matriz([[0. , 0. 0. dtype=float32)
, 0.
, 0,28, 0.
, 0,21, 0,02, 0,32]],
Aparentemente hay mucha variación en las estimaciones de probabilidad: si
Estaba construyendo un sistema sensible al riesgo (por ejemplo, un sistema médico o financiero).
sistema), probablemente debería tratar una predicción tan incierta con
Mucho cuidado. Definitivamente no lo tratarías como si fuera un caso con un 99% de confianza.
predicción. Además, la precisión del modelo recibió un pequeño impulso de 86,8 a
86.9:
>>> precisión = np.sum(y_pred == y_test) / len(y_test)
>>> precisión
0,8694
NOTA
La cantidad de muestras de Monte Carlo que utilice (100 en este ejemplo) es una
Hiperparámetro que se puede modificar. Cuanto más alto sea, más precisas serán las predicciones.
y sus estimaciones de incertidumbre serán. Sin embargo, si lo duplicas, el tiempo de inferencia será
También se puede duplicar. Además, por encima de un cierto número de muestras, notarás poco
mejora. Por lo tanto, su trabajo consiste en encontrar el equilibrio adecuado entre latencia y
precisión, dependiendo de su aplicación.
Si su modelo contiene otras capas que se comportan de una manera especial durante
entrenamiento (como capas BatchNormalization), entonces no deberías forzar
modo de entrenamiento como el que acabamos de hacer. En su lugar, deberías reemplazar el modo Dropout
capas con la siguiente clase MCDropout:
27
clase MCDropout(keras.capas.Dropout):
def llamada(self, entradas):
devuelve super().call(entradas, entrenamiento=Verdadero)
Aquí, simplemente subclasificamos la capa Dropout y anulamos el método call()
para forzar su argumento de entrenamiento a Verdadero (ver Capítulo 12). De manera similar,
Podría definir una clase MCAlphaDropout subclasificando AlphaDropout
Machine Translated by Google
En cambio, si está creando un modelo desde cero, solo es cuestión de usar MCDropout en lugar de
Dropout. Pero si tiene un modelo que ya se entrenó con Dropout, debe crear un nuevo modelo que
sea idéntico al modelo existente, excepto que reemplaza las capas de Dropout con MCDropout y
luego copia los pesos del modelo existente a su nuevo modelo.
En resumen, MC Dropout es una técnica fantástica que potencia los modelos de abandono y
proporciona mejores estimaciones de incertidumbre. Y, por supuesto, dado que se trata
simplemente de un abandono regular durante el entrenamiento, también actúa como un regularizador.
Regularización de norma máxima Otra técnica
de regularización que es popular para las redes neuronales se llama regularización de norma
máxima: para cada neurona, restringe los pesos w de las conexiones entrantes de modo que
≤ r, donde r es el hiperparámetro de norma máxima y
2
∙
w
es2la norma ℓ.
2
La regularización de norma máxima no agrega un término de pérdida de regularización a la
función de pérdida general. En cambio, normalmente se implementa calculando
w
después de
cada2paso de entrenamiento y reescalando w si es necesario (w ← w ).
a
en 2
Reducir r aumenta la cantidad de regularización y ayuda a reducir el sobreajuste. La
regularización de norma máxima también puede ayudar a aliviar los problemas de gradientes
inestables (si no está utilizando la normalización por lotes).
Para implementar la regularización de norma máxima en Keras, configure el
argumento kernel_constraint de cada capa oculta en una restricción max_norm() con el valor
máximo apropiado, de esta manera:
keras.layers.Dense(100, activación="elu", inicializador_del_kernel="he_normal",
restricción_del_núcleo=keras.restricciones.norma_máxima(1.))
Después de cada iteración de entrenamiento, el método fit() del modelo llamará al objeto devuelto por
max_norm(), le pasará los pesos de la capa y obtendrá pesos reescalados a cambio, que
luego reemplazarán los pesos de la capa. Como verá en el Capítulo 12, puede definir su propia
restricción personalizada
Machine Translated by Google
función si es necesario y úsela como kernel_constraint. También puede restringir los términos
de sesgo estableciendo el argumento bias_constraint.
La función max_norm() tiene un argumento axis cuyo valor predeterminado es 0. Una
capa densa normalmente tiene pesos de forma [número de entradas, número de neuronas],
por lo que usar axis=0 significa que la restricción max­norm se aplicará de forma independiente
al vector de pesos de cada neurona. Si desea usar max­norm con capas convolucionales
(consulte el Capítulo 14), asegúrese de configurar el argumento axis de la restricción
max_norm() de forma adecuada (normalmente axis=[0, 1, 2]).
Resumen y pautas prácticas
En este capítulo hemos cubierto una amplia gama de técnicas y es posible que se pregunte
cuáles debería utilizar. Esto depende de la tarea y aún no hay un consenso claro, pero he
descubierto que la configuración de la Tabla 11­3 funciona bien en la mayoría de los
casos, sin necesidad de realizar muchos ajustes de hiperparámetros. Dicho esto,
no considere estos valores predeterminados como reglas estrictas.
Tabla 11­3. Configuración de DNN predeterminada
Hiperparámetro
Valor predeterminado
Inicializador del núcleo
El inicializacion
Función de activación ELU
Normalización
Ninguno si es poco profundo; Norma de lote si es profundo
Regularización
Parada temprana (+ℓ reg.2si es necesario)
Optimizador
Optimización del momento (o RMSProp o Nadam)
Programa de ritmo de aprendizaje de 1 ciclo
Machine Translated by Google
Si la red es una pila simple de capas densas, entonces puede autonormalizarse y debería utilizar la
configuración de la Tabla 11­4 .
Tabla 11­4. Configuración de DNN para una red autonormalizante
Hiperparámetro
Valor predeterminado
Inicializador del núcleo
Inicialización de LeCun
Función de activación SELU
Normalización
Ninguna (autonormalización)
Regularización
Abandono alfa si es necesario
Optimizador
Optimización del momento (o RMSProp o Nadam)
Programa de ritmo de aprendizaje de 1 ciclo
¡No olvides normalizar las características de entrada! También deberías intentar reutilizar partes de
una red neuronal previamente entrenada si puedes encontrar una que resuelva un problema
similar, o usar un preentrenamiento no supervisado si tienes muchos datos sin etiquetar, o usar
un preentrenamiento en una tarea auxiliar si tienes muchos datos etiquetados para una tarea similar.
Si bien las pautas anteriores deberían cubrir la mayoría de los casos, aquí hay algunas
excepciones:
1
Si necesita un modelo disperso, puede utilizar la regularización
ℓ (y, opcionalmente,
poner a cero los pesos diminutos después del entrenamiento). Si necesita un modelo aún
más disperso, puede utilizar el kit de herramientas de optimización de
modelos de TensorFlow. Esto romperá la autonormalización, por lo que debe utilizar la
configuración predeterminada en este caso.
Si necesita un modelo de baja latencia (uno que realice predicciones ultrarrápidas), es
posible que deba usar menos capas, integrar las capas de normalización por lotes en
las capas anteriores y posiblemente usar una función de activación más rápida, como
ReLU con fugas o simplemente ReLU.
También será de ayuda contar con un modelo escaso. Por último, es posible que desee
Machine Translated by Google
Reducir la precisión de punto flotante de 32 bits a 16 o incluso 8 bits (ver
“Implementación de un modelo en un dispositivo móvil o integrado”). Nuevamente,
consulte TF­MOT.
Si está creando una aplicación sensible al riesgo o la latencia de inferencia
no es muy importante en su aplicación, puede usar MC Dropout para mejorar el
rendimiento y obtener estimaciones de probabilidad más confiables, junto con
estimaciones de incertidumbre.
Con estas pautas, ¡ya estás listo para entrenar redes muy profundas! Espero que ahora
estés convencido de que puedes llegar muy lejos usando solo Keras.
Sin embargo, puede llegar un momento en el que necesites tener incluso más control;
por ejemplo, para escribir una función de pérdida personalizada o para ajustar el
algoritmo de entrenamiento. Para esos casos, necesitarás usar la API de nivel inferior
de TensorFlow, como verás en el próximo capítulo.
Ceremonias
1. ¿Está bien inicializar todos los pesos con el mismo valor siempre que ese valor se
seleccione aleatoriamente mediante la inicialización He?
2. ¿Está bien inicializar los términos de sesgo a 0?
3. Nombra tres ventajas de la función de activación SELU sobre
ReLU.
4. ¿En qué casos querría utilizar cada uno de los siguientes?
funciones de activación: SELU, ReLU con fugas (y sus variantes), ReLU, tanh,
logística y softmax.
5. ¿Qué puede suceder si establece el hiperparámetro de momento demasiado
cerca de 1 (por ejemplo, 0,99999) al utilizar un optimizador SGD?
6. Nombra tres formas en que puedes producir un modelo disperso.
7. ¿El abandono ralentiza el entrenamiento? ¿Ralentiza la inferencia (es decir, la
realización de predicciones sobre nuevas instancias)? ¿Qué sucede con MC?
Machine Translated by Google
¿Abandonar?
8. Practique el entrenamiento de una red neuronal profunda en la imagen CIFAR10
conjunto de datos:
a. Construya una red neuronal con 20 capas ocultas de 100 neuronas cada una (son
demasiadas, pero es el objetivo de este ejercicio). Utilice la inicialización He y la
función de activación ELU.
b. Mediante la optimización de Nadam y la detención temprana, entrene la red en el
conjunto de datos CIFAR10. Puede cargarlo con
keras.datasets.cifar10.load_ data(). El conjunto de datos está compuesto por 60
000 imágenes en color de 32 × 32 píxeles (50 000 para entrenamiento, 10 000
para pruebas) con 10 clases, por lo que necesitará una capa de salida softmax
con 10 neuronas. Recuerde buscar la tasa de aprendizaje correcta cada vez que
cambie la arquitectura o los hiperparámetros del modelo.
c. Ahora intente agregar Normalización por lotes y compare
Curvas de aprendizaje: ¿Está convergiendo más rápido que antes? ¿Produce un
modelo mejor? ¿Cómo afecta a la velocidad de entrenamiento?
d. Intente reemplazar la Normalización por Lotes con SELU y realice los ajustes
necesarios para garantizar que la red se autonormalice (es decir, estandarice
las características de entrada, use la inicialización normal LeCun,
asegúrese de que la DNN contenga solo una secuencia de capas densas, etc.).
e. Intente regularizar el modelo con la eliminación alfa. Luego, sin volver a
entrenar el modelo, vea si puede lograr una mayor precisión con la eliminación
de MC.
f. Vuelva a entrenar su modelo utilizando la programación de 1 ciclo y vea si mejora
la velocidad de entrenamiento y la precisión del modelo.
Las soluciones a estos ejercicios están disponibles en el Apéndice A.
Machine Translated by Google
1 Xavier Glorot y Yoshua Bengio, “Comprendiendo la dificultad de entrenar profundamente
Redes neuronales de propagación hacia adelante”, Actas de la 13ª Conferencia internacional sobre inteligencia
artificial y estadística (2010): 249–256.
2 He aquí una analogía: si ajustas la perilla del amplificador de un micrófono demasiado cerca de cero, la gente...
No escucharán tu voz, pero si la configuras demasiado cerca del máximo, tu voz se saturará y la gente no
entenderá lo que estás diciendo. Ahora imagina una cadena de amplificadores de este tipo: todos deben estar
configurados correctamente para que tu voz suene alta y clara al final de la cadena. Tu voz tiene que salir
de cada amplificador con la misma amplitud con la que entró.
3 Por ejemplo, Kaiming He et al., “Profundizando en los rectificadores: superando el nivel humano
Rendimiento en la clasificación ImageNet”, Actas de la Conferencia Internacional IEEE de 2015 sobre Visión
por Computador (2015): 1026–1034.
A menos que sea parte de la primera capa oculta, una neurona muerta a veces puede volver a la vida: el Descenso de
Gradiente puede, de hecho, modificar las neuronas en las capas inferiores de tal manera que la suma
ponderada de las entradas de la neurona muerta sea positiva nuevamente.
5 Bing Xu et al., “Evaluación empírica de activaciones rectificadas en redes convolucionales”, preimpresión de arXiv
arXiv:1505.00853 (2015).
6 Djork­Arné Clevert et al., “Aprendizaje rápido y preciso de redes profundas mediante unidades lineales
exponenciales (ELU)”, Actas de la Conferencia internacional sobre representaciones de aprendizaje
(2016).
7 Günter Klambauer et al., “Redes neuronales autonormalizantes”, Actas de la 31.ª Conferencia internacional sobre
sistemas de procesamiento de información neuronal (2017): 972–981.
8 Sergey Ioffe y Christian Szegedy, “Normalización por lotes: aceleración del entrenamiento de redes profundas
mediante la reducción del cambio de covariables internas”, Actas de la 32.ª Conferencia internacional sobre
aprendizaje automático (2015): 448–456.
9 Sin embargo, se calculan durante el entrenamiento, en función de los datos de entrenamiento, por lo que se podría
decir que son entrenables . En Keras, "no entrenable" en realidad significa "no afectado por la retropropagación".
La API de Keras también especifica una función keras.backend.learning_phase() que debe devolver 1 durante el
entrenamiento y 0 en caso contrario .
11 Hongyi Zhang et al., “Inicialización de corrección: aprendizaje residual sin normalización”, preimpresión de arXiv
arXiv:1901.09321 (2019).
12 Razvan Pascanu et al., “Sobre la dificultad de entrenar redes neuronales recurrentes”,
Actas de la 30ª Conferencia Internacional sobre Aprendizaje Automático (2013): 1310– 1318.
13 Boris T. Polyak, “Algunos métodos para acelerar la convergencia de los métodos de iteración”,
Matemáticas computacionales y física matemática de la URSS 4, no. 5 (1964): 1–17.
14 Yurii Nesterov, “Un método para el problema de minimización convexa sin restricciones con la tasa de
convergencia O(1/k 2 )”, Doklady AN URSS 269 (1983): 543–547.
Machine Translated by Google
15 John Duchi et al., “Métodos de subgradiente adaptativos para aprendizaje en línea y optimización estocástica”,
Journal of Machine Learning Research 12 (2011): 2121–2159.
16 Este algoritmo fue creado por Geoffrey Hinton y Tijmen Tieleman en 2012 y presentado por Geoffrey Hinton
en su clase de Coursera sobre redes neuronales (diapositivas: https://homl.info/57; vídeo: https://
homl.info/58). Curiosamente, dado que los autores no escribieron un artículo para describir el algoritmo, los
investigadores a menudo citan la “diapositiva 29 de la clase 6” en sus artículos.
17 Diederik P. Kingma y Jimmy Ba, “Adam: un método para la optimización estocástica”, arXiv
preimpresión arXiv:1412.6980 (2014).
18 Se trata de estimaciones de la media y de la varianza (no centrada) de los gradientes. La media suele denominarse
primer momento , mientras que la varianza suele denominarse segundo momento, de ahí el nombre del
algoritmo.
19 Timothy Dozat, “Incorporando el impulso de Nesterov en Adán” (2016).
20 Ashia C. Wilson et al., “El valor marginal de los métodos de gradiente adaptativo en el aprendizaje automático”,
Advances in Neural Information Processing Systems 30 (2017): 4148–4158.
21 Leslie N. Smith, “Un enfoque disciplinado para los hiperparámetros de redes neuronales: Parte 1—
“Tasa de aprendizaje, tamaño de lote, momento y disminución del peso”, preimpresión de
arXiv arXiv:1803.09820 (2018).
22 Andrew Senior et al., “Un estudio empírico de las tasas de aprendizaje en redes neuronales profundas para el
reconocimiento de voz”, Actas de la Conferencia internacional IEEE sobre acústica, habla y procesamiento
de señales (2013): 6724–6728.
23 Geoffrey E. Hinton et al., “Mejora de las redes neuronales mediante la prevención de la coadaptación de los
detectores de características”, preimpresión de arXiv arXiv:1207.0580 (2012).
24 Nitish Srivastava et al., “Abandono: una forma sencilla de evitar el sobreajuste de las redes neuronales”,
Journal of Machine Learning Research 15 (2014): 1929–1958.
25 Yarin Gal y Zoubin Ghahramani, “La deserción como aproximación bayesiana: representación de la incertidumbre
del modelo en el aprendizaje profundo”, Actas de la 33.ª Conferencia internacional sobre aprendizaje automático
(2016): 1050–1059.
En concreto, muestran que entrenar una red de abandono es matemáticamente equivalente a aproximar la
inferencia bayesiana en un tipo específico de modelo probabilístico llamado Proceso Gaussiano Profundo.
27 Esta clase MCDropout funcionará con todas las API de Keras, incluida la API secuencial. Si solo le interesa la
API funcional o la API de subclasificación, no tiene que crear una clase MCDropout; puede crear una capa
Dropout normal y llamarla con training=True.
Machine Translated by Google
Capítulo 12. Modelos personalizados y
entrenamiento con TensorFlow
Hasta ahora, hemos utilizado únicamente la API de alto nivel de TensorFlow, tf.keras,
pero ya nos ha llevado bastante lejos: hemos creado varias arquitecturas de redes
neuronales, incluidas redes de regresión y clasificación, redes Wide & Deep y redes
autonormalizadoras, utilizando todo tipo de técnicas, como la normalización
por lotes, la deserción y las programaciones de tasa de aprendizaje. De hecho, el 95
% de los casos de uso que encontrará no requerirán nada más que tf.keras (y tf.data;
consulte el Capítulo 13). Pero ahora es el momento de profundizar en
TensorFlow y echar un vistazo a su API de Python de nivel inferior. Esto será útil
cuando necesites un control adicional para escribir funciones de pérdida personalizadas,
métricas personalizadas, capas, modelos, inicializadores, regularizadores, restricciones
de peso y más. Es posible que incluso necesites controlar por completo el bucle
de entrenamiento en sí, por ejemplo, para aplicar transformaciones o restricciones
especiales a los gradientes (más allá de simplemente recortarlos) o para usar
múltiples optimizadores para diferentes partes de la red. Cubriremos todos estos casos
en este capítulo, y también veremos cómo puedes potenciar tus modelos
personalizados y algoritmos de entrenamiento usando la función de generación
automática de gráficos de TensorFlow. Pero primero, hagamos un recorrido rápido por TensorFlow.
NOTA
TensorFlow 2.0 (beta) se lanzó en junio de 2019, lo que hace que TensorFlow sea mucho más fácil de
usar. La primera edición de este libro utilizó TF 1, mientras que esta edición utiliza TF 2.
Un recorrido rápido por TensorFlow
Como sabes, TensorFlow es una potente biblioteca para el cálculo numérico, especialmente
adecuada y optimizada para el aprendizaje automático a gran escala.
Machine Translated by Google
(pero puedes usarlo para cualquier otra cosa que requiera cálculos pesados).
Fue desarrollado por el equipo de Google Brain y es la base de muchos de los
servicios a gran escala de Google, como Google Cloud Speech, Google Photos y
Google Search. Se convirtió en código abierto en noviembre de 2015 y ahora es la biblioteca
de aprendizaje profundo más popular (en términos de citas en artículos, adopción en
empresas, estrellas en GitHub, etc.). Innumerables proyectos utilizan TensorFlow para
todo tipo de tareas de aprendizaje automático, como clasificación de imágenes,
procesamiento de lenguaje natural, sistemas de recomendación y pronóstico de series
temporales.
¿Qué ofrece TensorFlow? A continuación, un resumen:
Su núcleo es muy similar a NumPy, pero con soporte de GPU.
Admite computación distribuida (en múltiples dispositivos y servidores).
Incluye un tipo de compilador Just­in­Time (JIT) que le permite optimizar los
cálculos para la velocidad y el uso de memoria. Funciona extrayendo el gráfico
de cálculo de una función de Python, luego optimizándolo (por ejemplo,
eliminando los nodos no utilizados) y, finalmente, ejecutándolo de manera
eficiente (por ejemplo, ejecutando automáticamente operaciones
independientes en paralelo).
Los gráficos de cálculo se pueden exportar a un formato portátil, de modo que
puede entrenar un modelo TensorFlow en un entorno (por ejemplo, usando
Python en Linux) y ejecutarlo en otro (por ejemplo, usando Java en un
dispositivo Android).
Implementa autodiff (ver Capítulo 10 y Apéndice D) y proporciona algunos
optimizadores excelentes, como RMSProp y Nadam (ver Capítulo 11), para que
pueda minimizar fácilmente todo tipo de funciones de pérdida.
TensorFlow ofrece muchas más funciones basadas en estas funciones principales: la más
1
importante es, por supuesto, tf.keras, pero también tiene operaciones de carga y
preprocesamiento de datos (tf.data, tf.io, etc.), operaciones de procesamiento de imágenes
Machine Translated by Google
(tf.image), operaciones de procesamiento de señales (tf.signal) y más (consulte la Figura 12­1 para
obtener una descripción general de la API de Python de TensorFlow).
CONSEJO
Cubriremos muchos de los paquetes y funciones de la API de TensorFlow, pero es
imposible cubrirlos todos, por lo que debería tomarse un tiempo para explorar la API;
encontrará que es bastante rica y está bien documentada.
Figura 12­1. API de Python de TensorFlow
En el nivel más bajo, cada operación de TensorFlow (op para abreviar) se implementa
2
utilizando código C++ altamente eficiente. Muchas operaciones tienen
múltiples implementaciones
llamadas núcleos: cada núcleo está dedicado a un tipo de dispositivo específico, como CPU, GPU
o incluso TPU (unidades de procesamiento tensorial). Como sabrá, las GPU pueden
acelerar drásticamente los cálculos al dividirlos en muchos fragmentos más pequeños y ejecutarlos
en paralelo en muchos subprocesos de GPU. Las TPU son aún más rápidas: son
Machine Translated by Google
3
chips ASIC personalizados creados específicamente para operaciones de aprendizaje
profundo (discutiremos cómo usar TensorFlow con GPU o TPU en el Capítulo 19).
La arquitectura de TensorFlow se muestra en la Figura 12­2. La mayor parte del tiempo,
su código utilizará las API de alto nivel (especialmente tf.keras y tf.data); pero
cuando necesite más flexibilidad, utilizará la API de Python de nivel inferior, que maneja
los tensores directamente. Tenga en cuenta que también hay API para otros
lenguajes disponibles. En cualquier caso, el motor de ejecución de TensorFlow se
encargará de ejecutar las operaciones de manera eficiente, incluso en varios
dispositivos y máquinas si así se lo indica.
Figura 12­2. Arquitectura de TensorFlow
TensorFlow no solo funciona en Windows, Linux y macOS, sino también en
dispositivos móviles (usando TensorFlow Lite), incluidos iOS y Android (consulte el
Capítulo 19). Si no desea utilizar la API de Python, existen API de C++, Java, Go y Swift.
Incluso existe una implementación de JavaScript llamada TensorFlow.js que
permite ejecutar sus modelos directamente en su navegador.
TensorFlow es mucho más que una biblioteca. TensorFlow es el centro de un amplio
ecosistema de bibliotecas. En primer lugar, está TensorBoard para visualización
(consulte el Capítulo 10). A continuación, está TensorFlow Extended (TFX),
Machine Translated by Google
que es un conjunto de bibliotecas creadas por Google para poner en producción proyectos de
TensorFlow: incluye herramientas para la validación de datos, el preprocesamiento, el
análisis de modelos y la entrega (con TF Serving; consulte el Capítulo 19). El
TensorFlow Hub de Google ofrece una forma de descargar y reutilizar fácilmente redes
neuronales preentrenadas. También puede obtener muchas arquitecturas de redes
neuronales, algunas de ellas preentrenadas, en el jardín de modelos de TensorFlow. Consulta
los recursos de TensorFlow y https://github.com/jtoy/awesome­tensorflow Para más proyectos
basados en TensorFlow. Encontrará cientos de proyectos de TensorFlow en GitHub, por lo que
suele ser fácil encontrar código existente para lo que esté intentando hacer.
CONSEJO
Cada vez se publican más artículos sobre aprendizaje automático junto con sus implementaciones
y, a veces, incluso con modelos previamente entrenados. Consulta https://paperswithcode.com/ para
encontrarlos fácilmente.
Por último, pero no por ello menos importante, TensorFlow cuenta con un equipo dedicado
de desarrolladores apasionados y serviciales, así como con una gran comunidad que
contribuye a mejorarlo. Para hacer preguntas técnicas, debe utilizar http://
stackoverflow.com/ y etiqueta tu pregunta con tensorflow y python. Puedes informar
errores y solicitar funciones a través de GitHub. Para discusiones generales, únase al grupo de
Google.
¡Bien, es hora de empezar a codificar!
Uso de TensorFlow como NumPy La API de TensorFlow gira en
torno a los tensores, que fluyen de una operación a otra (de ahí el nombre TensorFlow). Un
tensor suele ser una matriz multidimensional (exactamente como un ndarray de
NumPy), pero también puede contener un escalar (un valor simple, como 42). Estos tensores
serán importantes cuando creemos funciones de costo personalizadas, métricas personalizadas,
capas personalizadas y más, así que veamos cómo crearlos y manipularlos.
Machine Translated by Google
Tensores y operaciones
Puedes crear un tensor con tf.constant(). Por ejemplo, aquí hay un tensor que representa una matriz con dos filas
y tres columnas de números flotantes:
>>> tf.constant([[1., 2., 3.], [4., 5., 6.]]) <tf.Tensor: id=0, forma=(2, 3),
matriz #
tipo de datos=float32, numpy= array([[1., 2., 3.], [4., 5., 6.]], tipo de datos=float32)>
>>> tf.constant(42) <tf.Tensor:
id=1, forma=(), tipo de datos=int32, numpy=42>
escalar #
Al igual que un ndarray, un tf.Tensor tiene una forma y un tipo de datos (dtype):
>>> t = tf.constant([[1., 2., 3.], [4., 5., 6.]]) >>> t.shape TensorShape([2, 3])
>>> t.dtype
tf.float32
La indexación funciona de manera muy similar a NumPy:
>>> t[:, 1:]
<tf.Tensor: id=5, forma=(2, 2), tipo_d=float32, numpy= matriz([[2., 3.], [5., 6.]],
tipo_d=float32)>
>>> t[..., 1, tf.newaxis] <tf.Tensor: id=15,
forma=(2, 1), tipo=float32, numpy= matriz([[2.],
[5.]], tipo de datos=float32)>
Lo más importante es que están disponibles todo tipo de operaciones tensoriales:
>>> t + 10
<tf.Tensor: id=18, forma=(2, 3), tipod=float32, numpy= matriz([[11., 12., 13.], [14., 15.,
16.]], tipod=float32)> >>>
tf.cuadrado(t) <tf.Tensor: id=20, forma=(2, 3),
tipod=float32, numpy=
matriz([[1., 4., 9.], [16., 25., 36.]], tipod=float32)> >>> t @ tf.transposición(t)
Machine Translated by Google
<tf.Tensor: id=24, forma=(2, 2), tipo=float32, numpy= matriz([[14., 32.],
[32., 77.]], tipo de datos=float32)>
Tenga en cuenta que escribir t + 10 es equivalente a llamar a tf.add(t, 10) (de
hecho, Python llama al método mágico t.__add__(10), que
simplemente llama a tf.add(t, 10)). También se admiten otros operadores
como ­ y *. El operador @ se agregó en Python 3.5 para la
multiplicación de matrices: es equivalente a llamar a la función tf.matmul().
Encontrará todas las operaciones matemáticas básicas que necesita
(tf.add(), tf.multiply(), tf.square(), tf.exp(), tf.sqrt(), etc.) y la mayoría de las
operaciones que puede encontrar en NumPy (por ejemplo,
tf.reshape(), tf.squeeze(), tf.tile()). Algunas funciones tienen un nombre diferente
al de NumPy; por ejemplo, tf.reduce_mean(), tf.reduce_sum(),
tf.reduce_max() y tf.math.log() son equivalentes a np.mean(), np.sum(),
np.max() y np.log(). Cuando el nombre difiere, a menudo hay una buena razón
para ello. Por ejemplo, en TensorFlow debe escribir tf.transpose(t);
no puede escribir simplemente tT como en NumPy. La razón es que la función
tf.transpose() no hace exactamente lo mismo que el atributo T de NumPy: en
TensorFlow, se crea un nuevo tensor con su propia copia de los datos
transpuestos, mientras que en NumPy, tT es solo una vista transpuesta de los
mismos datos. De manera similar, la operación tf.reduce_sum() se llama así
porque su núcleo de GPU (es decir, implementación de GPU) usa un
algoritmo de reducción que no garantiza el orden en el que se agregan los
elementos: debido a que los flotantes de 32 bits tienen una precisión limitada,
el resultado puede cambiar ligeramente cada vez que llama a esta
operación. Lo mismo es cierto para tf.reduce_mean() (pero, por supuesto, tf.reduce_max() es d
Machine Translated by Google
NOTA
Muchas funciones y clases tienen alias. Por ejemplo, tf.add() y tf.math.add()
son la misma función. Esto permite que TensorFlow tenga nombres concisos para las
4
operaciones más comunes y, al mismo tiempo, mantenga los paquetes bien organizados.
API DE BAJO NIVEL DE KERAS
La API de Keras tiene su propia API de bajo nivel, ubicada en keras.backend.
Incluye funciones como square(), exp() y sqrt(). En tf.keras, estas funciones
generalmente solo invocan las operaciones de TensorFlow correspondientes. Si
desea escribir código que sea portable a otras implementaciones de Keras, debe
usar estas funciones de Keras.
Sin embargo, solo cubren un subconjunto de todas las funciones disponibles en
TensorFlow, por lo que en este libro utilizaremos las operaciones de TensorFlow
directamente. A continuación, se muestra un ejemplo simple con keras.backend, que
comúnmente se denomina K para abreviar:
>>> de tensorflow importar keras >>> K =
keras.backend >>>
K.square(K.transpose(t)) + 10 <tf.Tensor: id=39,
forma=(3, 2), dtype=float32, numpy= array([[11., 26.], [14., 35.], [19., 46.]], dtype=float32)>
Los tensores y NumPy funcionan
bien con NumPy: puedes crear un tensor a partir de una matriz NumPy y viceversa. Incluso
puedes aplicar operaciones de TensorFlow a matrices NumPy y operaciones de
NumPy a tensores:
>>> a = np.array([2., 4., 5.]) >>> tf.constant(a)
<tf.Tensor: id=111,
forma=(3,), dtype=float64, numpy=array([2., 4., 5.])>
Machine Translated by Google
# o np.matriz(t)
>>> t.numpy()
matriz([[1., 2., 3.], [4., 5., 6.]],
dtype=float32) >>> tf.square(a) <tf.Tensor:
id=116, forma=(3,),
dtype=float64, numpy=matriz([4., 16., 25.])> >>> np.square(t) matriz([[ 1., 4., 9.], [16., 25., 36.]],
dtype=float32)
ADVERTENCIA
Tenga en cuenta que NumPy utiliza una precisión de 64 bits de forma predeterminada, mientras que TensorFlow utiliza 32 bits.
Esto se debe a que la precisión de 32 bits suele ser más que suficiente para las redes neuronales, además
de que se ejecuta más rápido y utiliza menos RAM. Por lo tanto, cuando cree un tensor a partir de una
matriz NumPy, asegúrese de configurar dtype=tf.float32.
Conversiones de tipos
Las conversiones de tipos pueden afectar significativamente el rendimiento y pueden pasar
desapercibidas fácilmente cuando se realizan de forma automática. Para evitarlo, TensorFlow
no realiza ninguna conversión de tipos de forma automática: solo genera una excepción
si intenta ejecutar una operación en tensores con tipos incompatibles. Por ejemplo, no puede
agregar un tensor de tipo flotante y un tensor de tipo entero, y ni siquiera puede agregar
un tensor de tipo flotante de 32 bits y un tensor de tipo flotante de 64 bits:
>>> tf.constante(2.) + tf.constante(40)
Traceback[...]InvalidArgumentError[...]se esperaba que fuera un flotante[...] >>> tf.constant(2.)
+ tf.constant(40., dtype=tf.float64)
Traceback[...]InvalidArgumentError[...]se esperaba que fuera un doble[...]
Esto puede resultar un poco molesto al principio, pero recuerda que es por una buena
causa. Y, por supuesto, puedes usar tf.cast() cuando realmente necesites convertir tipos:
>>> t2 = tf.constant(40., dtype=tf.float64) >>> tf.constant(2.0)
+ tf.cast(t2, tf.float32) <tf.Tensor: id=136, forma=(), dtype=float32,
numpy=42.0>
Machine Translated by Google
Variables
Los valores de tf.Tensor que hemos visto hasta ahora son inmutables: no puedes
modificarlos. Esto significa que no podemos usar tensores regulares para implementar
pesos en una red neuronal, ya que necesitan ser ajustados por
retropropagación. Además, es posible que también sea necesario cambiar otros parámetros.
tiempo (por ejemplo, un optimizador de momento realiza un seguimiento de los gradientes pasados). Lo que
La necesidad es una variable tf:
>>> v = tf.Variable([[1., 2., 3.], [4., 5., 6.]])
>>> v
<tf.Variable 'Variable:0' forma=(2, 3) tipo=float32, numpy=
matriz([[1., 2., 3.],
[4., 5., 6.]], tipo de datos=float32)>
Una tf.Variable actúa de forma muy similar a un tf.Tensor: puedes realizar lo mismo
operaciones con él, también funciona bien con NumPy, y es igual de
Es exigente con los tipos, pero también se puede modificar en el lugar mediante la función asignar().
método (o asignar_add() o asignar_sub(), que incrementan o
decrementar la variable por el valor dado). También puedes modificar
celdas individuales (o porciones), mediante el uso de la función asignar() de la celda (o porción)
método (la asignación directa de elementos no funcionará) o utilizando el
métodos scatter_update() o scatter_nd_update():
v.assign(2 * v) v[0,
1].assign(42) v[:,
2].assign([0., 1.])
# => [[2.,
# => [[2.,
# => [[2.,
4., 10.,6.],
6.],[8.,
[8.,12.]]
12.]]
42., 10., 0.], [8., 1.]]
42., 10.,
v.scatter_nd_update(índices=[[0, 0], [1, 2]], actualizaciones=[100., 200.])
# => [[100.,
42.,
0.],
[8.,
10.,
200.]]
NOTA
En la práctica, rara vez tendrás que crear variables manualmente, ya que Keras proporciona una
método add_weight() que se encargará de ello por ti, como veremos. Además,
Los parámetros del modelo generalmente serán actualizados directamente por los optimizadores, por lo que
rara vez es necesario actualizar las variables manualmente.
Machine Translated by Google
Otras estructuras de datos
TensorFlow admite varias otras estructuras de datos, incluidas las siguientes (consulte la
sección “Tensores y operaciones” en el cuaderno o
Apéndice F para más detalles):
Tensores dispersos (tf.SparseTensor)
Representación eficiente de tensores que contienen principalmente ceros. El paquete
tf.sparse contiene operaciones para tensores dispersos.
Matrices tensoriales (tf.TensorArray)
Son listas de tensores. Tienen un tamaño fijo por defecto pero pueden
hacerse dinámicas de manera opcional. Todos los tensores que contienen deben
tener la misma forma y tipo de datos.
Tensores irregulares (tf.RaggedTensor)
Representan listas estáticas de listas de tensores, donde cada tensor tiene la
misma forma y tipo de datos. El paquete tf.ragged contiene operaciones para tensores
irregulares.
Tensores de cuerda
Son tensores regulares de tipo tf.string. Representan cadenas de bytes, no cadenas
Unicode, por lo que si crea un tensor de cadena utilizando una cadena Unicode
(por ejemplo, una cadena regular de Python 3 como "café"), se codificará
automáticamente en UTF­8 (por ejemplo, b"caf\xc3\xa9").
Como alternativa, puede representar cadenas Unicode utilizando tensores de tipo
tf.int32, donde cada elemento representa un punto de código Unicode (por ejemplo,
[99, 97, 102, 233]). El paquete tf.strings (con una s) contiene operaciones para
cadenas de bytes y cadenas Unicode (y para convertir una en la otra).
Es importante tener en cuenta que un tf.string es atómico, lo que significa que su
longitud no aparece en la forma del tensor. Una vez que lo convierte en un tensor
Unicode (es decir, un tensor de tipo tf.int32 que contiene puntos de código Unicode),
la longitud aparece en la forma.
Machine Translated by Google
Conjuntos
Se representan como tensores regulares (o tensores dispersos). Por ejemplo,
tf.constant([[1, 2], [3, 4]]) representa los dos conjuntos {1, 2} y {3, 4}. De manera más
general, cada conjunto se representa mediante un vector en el último eje del
tensor. Puede manipular conjuntos utilizando operaciones del paquete tf.sets.
Colas
Almacena tensores en varios pasos. TensorFlow ofrece varios tipos de colas: colas
simples FIFO (First In, First Out) (FIFOQueue), colas que pueden priorizar algunos
elementos (PriorityQueue), mezclar sus elementos (RandomShuffleQueue) y
agrupar elementos de distintas formas mediante relleno (PaddingFIFOQueue).
Todas estas clases se encuentran en el paquete tf.queue.
¡Con tensores, operaciones, variables y varias estructuras de datos a su disposición,
ahora está listo para personalizar sus modelos y algoritmos de entrenamiento!
Personalización de modelos y algoritmos de entrenamiento
Comencemos creando una función de pérdida personalizada, que es simple y
caso de uso común.
Funciones de pérdida personalizadas
Supongamos que desea entrenar un modelo de regresión, pero su conjunto de
entrenamiento es un poco ruidoso. Por supuesto, comienza por intentar limpiar su
conjunto de datos eliminando o corrigiendo los valores atípicos, pero eso resulta
insuficiente; el conjunto de datos sigue siendo ruidoso. ¿Qué función de pérdida debería
utilizar? El error cuadrático medio podría penalizar demasiado los errores
grandes y hacer que su modelo sea impreciso. El error absoluto medio no penalizaría
tanto los valores atípicos, pero el entrenamiento podría tardar un tiempo en converger y el modelo entrenad
Machine Translated by Google
No es muy preciso. Probablemente sea un buen momento para utilizar la pérdida de Huber
(introducida en el Capítulo 10) en lugar del viejo y bueno MSE. La pérdida de Huber no forma
parte actualmente de la API oficial de Keras, pero está disponible en tf.keras (solo use una
instancia de la clase keras.losses.Huber). Pero supongamos que no está allí: ¡implementarla es
muy fácil! Simplemente cree una función que tome las etiquetas y predicciones como
argumentos y use operaciones de TensorFlow para calcular la pérdida de cada
instancia:
def huber_fn(y_verdadero, y_pred): error
= y_verdadero ­ y_pred
es_un_error_pequeño = tf.abs(error) < 1
pérdida_cuadrada = tf.cuadrado(error) / 2
pérdida_lineal = tf.abs(error) ­ 0.5 return
tf.where(es_un_error_pequeño, pérdida_cuadrada, pérdida_lineal)
ADVERTENCIA
Para obtener un mejor rendimiento, debe utilizar una implementación vectorizada, como en
este ejemplo. Además, si desea aprovechar las características gráficas de TensorFlow, debe
utilizar únicamente operaciones de TensorFlow.
También es preferible devolver un tensor que contenga una pérdida por instancia, en lugar
de devolver la pérdida media. De esta manera, Keras puede aplicar ponderaciones de
clase o ponderaciones de muestra cuando se lo solicite (consulte el Capítulo 10).
Ahora puedes usar esta pérdida cuando compiles el modelo Keras y luego entrenes tu modelo:
modelo.compilar(pérdida=función_huber, optimizador="nadam")
modelo.ajustar(entre_X, entre_Y, [...])
¡Y eso es todo! Para cada lote durante el entrenamiento, Keras llamará a la función
huber_fn() para calcular la pérdida y la usará para realizar un paso de descenso de gradiente.
Además, hará un seguimiento de la pérdida total desde el comienzo de la época y
mostrará la pérdida media.
¿Pero qué pasa con esta pérdida personalizada cuando guardas el modelo?
Machine Translated by Google
Guardar y cargar modelos que contienen componentes personalizados
Guardar un
modelo que contiene una función de pérdida personalizada funciona bien,
ya que Keras guarda el nombre de la función. Siempre que lo cargue,
deberá proporcionar un diccionario que asigne el nombre de la función a la función real.
De manera más general, cuando carga un modelo que contiene objetos
personalizados, debe asignar los nombres a los objetos:
modelo = keras.models.load_model("mi_modelo_con_una_pérdida_personalizada.h5",
objetos_personalizados={"huber_fn": huber_fn})
Con la implementación actual, cualquier error entre ­1 y 1 se considera “pequeño”. Pero
¿qué pasa si desea un umbral diferente? Una solución es crear una función que
cree una función de pérdida configurada:
def create_huber(umbral=1.0): def
huber_fn(y_verdadero, y_pred): error =
y_verdadero ­ y_pred error_pequeño
= tf.abs(error) < umbral pérdida_cuadrada = tf.cuadrado(error) / 2
pérdida_lineal = umbral * tf.abs(error) ­ umbral**2 / 2
return tf.where(error_pequeño, pérdida_cuadrada, pérdida_lineal) return huber_fn
modelo.compilar(pérdida=create_huber(2.0), optimizador="nadam")
Desafortunadamente, cuando guarde el modelo, el umbral no se guardará.
Esto significa que tendrás que especificar el valor umbral al cargar el modelo
(ten en cuenta que el nombre a utilizar es "huber_fn", que es el nombre de la función
que le diste a Keras, no el nombre de la función que lo creó):
modelo =
keras.models.load_model("mi_modelo_con_un_umbral_de_pérdida_personalizado_2.h5",
objetos_personalizados={"huber_fn":
crear_huber(2.0)})
Machine Translated by Google
Puedes resolver esto creando una subclase de la clase keras.losses.Loss y luego
implementando su método get_config():
clase HuberLoss(keras.pérdidas.Pérdida): def
__init__(self, umbral=1.0, **kwargs): self.umbral = umbral
super().__init__(**kwargs) def llamada(self,
y_verdadero, y_pred): error = y_verdadero
­ y_pred error_pequeño = tf.abs(error) <
self.umbral pérdida_cuadrada =
tf.cuadrado(error) / 2 pérdida_lineal = self.umbral * tf.abs(error) ­
self.umbral**2
/2
devolver tf.where(es_un_error_pequeño, pérdida_cuadrada, pérdida_lineal)
def obtener_config(self):
base_config = super().get_config() devolver
{**base_config, "umbral": self.umbral}
ADVERTENCIA
Actualmente, la API de Keras solo especifica cómo usar la subclasificación para definir capas,
modelos, devoluciones de llamadas y regularizadores. Si crea otros componentes (como pérdidas,
métricas, inicializadores o restricciones) mediante la subclasificación, es posible que no se puedan
transferir a otras implementaciones de Keras. Es probable que la API de Keras se actualice para
especificar también la subclasificación para todos estos componentes.
Repasemos este código:
El constructor acepta **kwargs y los pasa al constructor padre, que maneja
los hiperparámetros estándar: el nombre de la pérdida y el algoritmo de
reducción que se utilizará para agregar las pérdidas de instancias
individuales. De forma predeterminada, es
"sum_over_batch_size", lo que significa que la pérdida será la suma de las
pérdidas de instancia, ponderada por los pesos de muestra, si los hay, y
dividida por el tamaño del lote (no por la suma de pesos, por lo que
5
Esta no es la media ponderada). Otros valores posibles son "suma" y Ninguno.
Machine Translated by Google
El método call() toma las etiquetas y predicciones, calcula todas las pérdidas de
instancias y las devuelve.
El método get_config() devuelve un diccionario que asigna el nombre de cada
hiperparámetro a su valor. Primero llama al método get_config() de la clase
padre y luego agrega los nuevos hiperparámetros a este diccionario (tenga en
cuenta que la sintaxis {**x} conveniente se agregó en Python 3.5).
Luego puedes usar cualquier instancia de esta clase al compilar el modelo:
modelo.compilar(pérdida=HuberLoss(2.), optimizador="nadam")
Cuando guardes el modelo, el umbral se guardará junto con él; y cuando cargues el
modelo, solo tendrás que asignar el nombre de la clase a la clase misma:
modelo = keras.models.load_model("mi_modelo_con_una_clase_de_pérdida_personalizada.h5",
objetos_personalizados={"HuberLoss": HuberLoss})
Cuando guarda un modelo, Keras llama al método get_config() de la instancia de
pérdida y guarda la configuración como JSON en el archivo HDF5. Cuando carga el modelo,
llama al método de clase from_config() en la clase HuberLoss: este método es
implementado por la clase base (Loss) y crea una instancia de la clase, pasando
**config al constructor.
¡Eso es todo por las pérdidas! No fue muy difícil, ¿verdad? Igual de simples son las funciones
de activación personalizadas, los inicializadores, los regularizadores y las restricciones.
Veámoslos ahora.
Funciones de activación personalizadas, inicializadores, regularizadores y
restricciones
La mayoría de las funcionalidades de Keras, como pérdidas, regularizadores,
restricciones, inicializadores, métricas, funciones de activación, capas e incluso modelos
completos, se pueden personalizar de forma muy similar. La mayoría de las veces, solo necesitará
Machine Translated by Google
Necesita escribir una función simple con las entradas y salidas apropiadas.
A continuación se muestran ejemplos de una función de activación personalizada (equivalente a
keras.activations.softplus() o tf.nn.softplus()), una función personalizada
Inicializador Glorot (equivalente a
keras.initializers.glorot_normal()), un regularizador ℓ personalizado
1
(equivalente a keras.regularizers.l1(0.01)) y una restricción personalizada
que garantiza que todos los pesos sean positivos (equivalentes a
keras.constraints.nonneg() o tf.nn.relu()):
# valor de retorno es justo
def my_softplus(z):
devuelve tf.math.log(tf.exp(z) + 1.0)
tf.nn.softplus(z)
def my_glorot_initializer(forma, tipo_d=tf.float32):
desvst = tf.sqrt(2. / (forma[0] + forma[1]))
devuelve tf.random.normal(forma, desvst=desvst, tipod=tipod)
def my_l1_regularizer(pesos):
devuelve tf.reduce_sum(tf.abs(0.01 * pesos))
# valor de retorno es justo
def mis_pesos_positivos(pesos):
tf.nn.relu(pesos)
devuelve tf.where(pesos < 0., tf.zeros_like(pesos), pesos)
Como puede ver, los argumentos dependen del tipo de función personalizada.
Estas funciones personalizadas se pueden utilizar normalmente; por ejemplo:
capa = keras.capas.Dense(30, activación=my_softplus,
kernel_initializer=mi_inicializador_glorot,
kernel_regularizer=mi_regularizador_l1,
kernel_constraint=mis_pesos_positivos)
La función de activación se aplicará a la salida de esta capa densa,
y su resultado se pasará a la siguiente capa. Los pesos de la capa serán
inicializarse utilizando el valor devuelto por el inicializador. En cada entrenamiento
paso los pesos se pasarán a la función de regularización para calcular
la pérdida de regularización, que se añadirá a la pérdida principal para obtener la
Pérdida final utilizada para el entrenamiento. Finalmente, se llamará a la función de restricción.
Machine Translated by Google
después de cada paso de entrenamiento, los pesos de la capa serán reemplazados por los
pesos restringidos.
Si una función tiene hiperparámetros que se deben guardar junto con el modelo, entonces
querrá crear una subclase de la clase adecuada, como keras.regularizers.Regularizer,
keras.constraints.Constraint, keras.initializers.Initializer o keras.layers.Layer (para cualquier capa,
incluidas las funciones de activación). De manera muy similar a lo que hicimos para la pérdida
personalizada, aquí hay una clase simple para la regularización ℓ que guarda su hiperparámetro
de factor (esta vez no necesitamos llamar al1 constructor principal ni al método get_config(),
ya que no están definidos por la clase principal):
clase MyL1Regularizer(keras.regularizadores.Regularizer):
def __init__(self, factor): self.factor = factor
def __call__(self, pesos):
devuelve tf.reduce_sum(tf.abs(self.factor * pesos)) def get_config(self): devuelve
{"factor": self.factor}
Tenga en cuenta que debe implementar el método call() para pérdidas, capas (incluidas las
funciones de activación) y modelos, o el método __call__() para regularizadores, inicializadores y
restricciones. Para las métricas, las cosas son un poco diferentes, como veremos ahora.
Métricas personalizadas
Las pérdidas y las métricas no son conceptualmente lo mismo: las pérdidas (por ejemplo, la entropía
cruzada) son utilizadas por Gradient Descent para entrenar un modelo, por lo que deben ser
diferenciables (al menos donde se evalúan) y sus gradientes no deben ser 0 en todas partes.
Además, no hay problema si no son fácilmente interpretables por los humanos. Por
el contrario, las métricas (por ejemplo, la precisión) se utilizan para evaluar un modelo: deben ser
más fácilmente interpretables y pueden ser no diferenciables o tener gradientes 0 en todas partes.
Dicho esto, en la mayoría de los casos, definir una función métrica personalizada es exactamente
lo mismo que definir una función de pérdida personalizada. De hecho, incluso podríamos usar la
Machine Translated by Google
6
Función de pérdida de Huber que creamos anteriormente como métrica; funcionaría bien (y la
persistencia también funcionaría de la misma manera, en este caso solo guardando el
nombre de la función, "huber_fn"):
modelo.compile(pérdida="mse", optimizador="nadam", métricas=[create_huber(2.0)])
Para cada lote durante el entrenamiento, Keras calculará esta métrica y hará un seguimiento
de su media desde el comienzo de la época. La mayoría de las veces, esto es exactamente lo
que quieres. ¡Pero no siempre! Considere la precisión de un clasificador binario, por
ejemplo. Como vimos en el Capítulo 3, la precisión es la cantidad de verdaderos positivos dividida
por la cantidad de predicciones positivas (incluyendo tanto verdaderos positivos como
falsos positivos). Supongamos que el modelo hizo cinco predicciones positivas en el
primer lote, cuatro de las cuales fueron correctas: eso es una precisión del 80%. Luego
supongamos que el modelo hizo tres predicciones positivas en el segundo lote, pero todas
fueron incorrectas: eso es una precisión del 0% para el segundo lote. Si solo calcula la media
de estas dos precisiones, obtendrá el 40%. Pero espere un segundo: ¡esa no es la precisión
del modelo en estos dos lotes! De hecho, hubo un total de cuatro verdaderos positivos (4 +
0) de ocho predicciones positivas (5 + 3), por lo que la precisión general es del 50%, no del
40%. Lo que necesitamos es un objeto que pueda llevar un registro de la cantidad de
verdaderos positivos y la cantidad de falsos positivos y que pueda calcular su proporción
cuando se lo solicitemos. Esto es precisamente lo que hace la clase keras.metrics.Precision:
>>> precisión = keras.metrics.Precision() >>>
precisión([0, 1, 1, 1 , 0, 1 , 0, 1], [1, 1, 0, 1, 0, 1, 0, 1]) <tf.Tensor: id=581729,
forma=(), dtype=float32, numpy=0.8> >>> precisión([0, 1, 0, 0, 1, 0, 1, 1],
[1, 0, 1, 1, 0, 0, 0, 0]) <tf.Tensor: id=581780, forma=(), dtype=float32, numpy=0.5>
En este ejemplo, creamos un objeto Precision, luego lo usamos como una función,
pasándole las etiquetas y predicciones para el primer lote, luego para el segundo lote (tenga
en cuenta que también podríamos haber pasado pesos de muestra). Usamos la misma cantidad
de verdaderos y falsos positivos que en el ejemplo que acabamos de analizar. Después del
primer lote, devuelve una precisión del 80 %; luego, después del primer lote, devuelve una precisión del 80 %.
Machine Translated by Google
En el segundo lote, devuelve el 50% (que es la precisión general hasta ahora, no la
La precisión del segundo lote). Esto se denomina métrica de transmisión (o con estado).
métrica), a medida que se actualiza gradualmente, lote tras lote.
En cualquier momento, podemos llamar al método result() para obtener el valor actual de
la métrica. También podemos observar sus variables (seguimiento del número de valores verdaderos)
y falsos positivos) mediante el uso del atributo variables, y podemos restablecer
Estas variables utilizan el método reset_states():
>>> p.resultado()
<tf.Tensor: id=581794, forma=(), tipo de datos=float32, numpy=0.5>
>>> p.variables
[<tf.Variable 'verdaderos_positivos:0' [...] numpy=array([4.], dtype=float32)>,
<tf.Variable 'falsos_positivos:0' [...] numpy=array([4.],
tipod=float32)>]
>>> p.reset_states()
ambas variables #
obtener
reiniciar a
0.0
Si necesita crear una métrica de transmisión de este tipo, cree una subclase de la
Clase keras.metrics.Metric. Aquí hay un ejemplo simple que realiza un seguimiento
de la pérdida total de Huber y el número de casos vistos hasta ahora. Cuando
Pregunté por el resultado y me devolvió la proporción, que es simplemente la media de Huber.
pérdida:
clase HuberMetric(keras.metrics.Metric):
def __init__(self, umbral=1.0, **kwargs):
super().__init__(**kwargs) self.threshold
= umbral
# maneja argumentos base (por ejemplo, dtype)
self.huber_fn = create_huber(umbral)
self.total = self.add_weight("total", inicializador="ceros")
self.count = self.add_weight("count", initializer="ceros")
def update_state(self, y_true, y_pred, sample_weight=Ninguno):
métrica = self.huber_fn(y_true, y_pred)
self.total.assign_add(tf.reduce_sum(métrica))
auto.contar.asignar_agregar(tf.cast(tf.tamaño(y_verdadero), tf.float32))
def resultado(auto):
devuelve self.total / self.count
definición get_config(auto):
base_config = super().get_config()
devuelve {**base_config, "umbral": self.umbral}
Repasemos este código:
7
Machine Translated by Google
El constructor utiliza el método add_weight() para crear las variables
necesarias para realizar un seguimiento del estado de la métrica en varios
lotes; en este caso, la suma de todas las pérdidas de Huber (total) y la cantidad
de instancias observadas hasta el momento (conteo). Puede crear las
variables manualmente si lo prefiere. Keras realiza un seguimiento
de cualquier variable tf.Variable que se configure como atributo (y, de manera
más general, de cualquier objeto "rastreable", como capas o modelos).
El método update_state() se llama cuando se utiliza una instancia de esta clase
como función (como hicimos con el objeto Precision).
Actualiza las variables, dadas las etiquetas y predicciones para un lote (y los
pesos de muestra, pero en este caso los ignoramos).
El método result() calcula y devuelve el resultado final, en este caso la
métrica de Huber media de todas las instancias. Cuando se utiliza la métrica
como función, primero se llama al método update_state(), luego se llama al
método result() y se devuelve su salida.
También implementamos el método get_config() para garantizar que el
umbral se guarde junto con el modelo.
La implementación predeterminada del método reset_states() restablece
todas las variables a 0.0 (pero puede anularlo si es necesario).
NOTA
Keras se encargará de la persistencia de las variables sin problemas; no se requiere ninguna acción.
Cuando se define una métrica mediante una función simple, Keras la invoca
automáticamente para cada lote y realiza un seguimiento de la media durante cada
época, tal como lo hicimos manualmente. Por lo tanto, el único beneficio de nuestra clase
HuberMetric es que se guardará el umbral. Pero, por supuesto, algunas métricas, como
Machine Translated by Google
La precisión no se puede promediar simplemente en lotes: en esos casos, no hay otra
opción que implementar una métrica de transmisión.
Ahora que hemos creado una métrica de transmisión, ¡crear una capa personalizada
parecerá un paseo por el parque!
Capas personalizadas
Es posible que, en ocasiones, desee crear una arquitectura que contenga una capa
exótica para la que TensorFlow no proporciona una implementación predeterminada. En
este caso, deberá crear una capa personalizada. O puede que simplemente desee crear
una arquitectura muy repetitiva, que contenga bloques idénticos de capas repetidas
muchas veces, y sería conveniente tratar cada bloque de capas como una sola capa.
Por ejemplo, si el modelo es una secuencia de capas A, B, C, A, B, C, A, B, C, entonces
es posible que desee definir una capa personalizada D que contenga las capas A, B, C,
de modo que su modelo simplemente sea D, D, D.
Veamos cómo crear capas personalizadas.
En primer lugar, algunas capas no tienen pesos, como keras.layers.Flatten o
keras.layers.ReLU. Si desea crear una capa personalizada sin pesos, la opción más
sencilla es escribir una función y envolverla en una capa keras.layers.Lambda.
Por ejemplo, la siguiente capa aplicará la función exponencial a sus entradas:
capa_exponencial = keras.capas.Lambda(lambda x: tf.exp(x))
Esta capa personalizada se puede utilizar como cualquier otra capa, utilizando
la API secuencial, la API funcional o la API de subclasificación. También se puede
utilizar como una función de activación (o se puede utilizar activation=tf.exp,
activation=keras.activations.exponential o simplemente
activation="exponential"). La capa exponencial se utiliza a veces en la capa de salida de
un modelo de regresión cuando los valores a predecir tienen escalas muy diferentes
(por ejemplo, 0,001, 10, 1000).
Como probablemente ya hayas adivinado, para crear una capa con estado personalizada
(es decir, una capa con pesos), necesitas crear una subclase de la
Machine Translated by Google
Clase keras.layers.Layer. Por ejemplo, la siguiente clase implementa una
versión simplificada de la capa Dense:
clase MyDense(keras.layers.Layer): def __init__(self,
unidades, activación=Ninguna, **kwargs):
super().__init__(**kwargs) self.unidades
= unidades self.activacion =
keras.activaciones.get(activacion)
def build(self, forma_de_entrada_por_lote): self.kernel
= self.add_weight(
nombre="kernel", forma=[batch_input_shape[­1], self.units], inicializador="glorot_normal")
auto.sesgo = auto.añadir_peso(
nombre="sesgo", forma=[self.units], inicializador="ceros")
# debe
ser en
super().build(forma_de_entrada_por_lote)
el fin
def call(self, X): devuelve
self.activation(X @ self.kernel + self.bias)
def computar_forma_salida(self, forma_entrada_lote):
devuelve tf.TensorShape(forma_de_entrada_por_lote.como_lista()[:­1] +
[unidades propias])
def get_config(self): base_config
= super().get_config() return {**base_config, "unidades":
self.unidades, "activación":
keras.activations.serialize(auto.activacion)}
Repasemos este código:
El constructor toma todos los hiperparámetros como argumentos
(en este ejemplo, unidades y activación) y, lo que es más
importante, también toma un argumento **kwargs. Llama al
constructor padre y le pasa el kwargs: esto se encarga de los
argumentos estándar, como input_shape, trainable y name.
Luego, guarda los hiperparámetros como atributos y convierte
el argumento de activación en la función de activación
adecuada mediante la función keras.activations.get() (acepta funciones,
cadenas estándar como "relu" o "selu", o simplemente None).8
Machine Translated by Google
El método build() tiene como función crear las variables de la capa llamando
al método add_weight() para cada peso. El método build() se llama la primera
vez que se utiliza la capa. En ese momento, Keras sabrá la forma de las
entradas de esta capa y la pasará al método build(), que suele ser necesario para
9
crear algunos de los pesos. Por ejemplo, necesitamos saber la cantidad de
neuronas de la capa anterior para crear la matriz de pesos de conexión (es
decir, el "kernel"): esto corresponde al tamaño de la última dimensión de las
entradas. Al final del método build() (y solo al final), debes llamar al método build()
del padre: esto le dice a Keras que la capa está construida (solo
establece self.built=True).
El método call() realiza las operaciones deseadas. En este caso, calculamos la
multiplicación matricial de las entradas X y el núcleo de la capa, sumamos
el vector de polarización y aplicamos la función de activación al resultado, lo que
nos da la salida de la capa.
El método calculate_output_shape() simplemente devuelve la forma de las
salidas de esta capa. En este caso, es la misma forma que las entradas,
excepto que la última dimensión se reemplaza con la cantidad de neuronas en
la capa. Tenga en cuenta que en tf.keras, las formas son instancias de la clase
tf.TensorShape, que puede convertir en listas de Python usando as_list().
El método get_config() es igual que en las clases personalizadas anteriores.
Tenga en cuenta que guardamos la función de activación completa.
configuración llamando a keras.activations.serialize().
¡Ahora puedes usar una capa MyDense como cualquier otra capa!
Machine Translated by Google
NOTA
Generalmente, puedes omitir el método calculate_output_shape(), ya que tf.keras
infiere automáticamente la forma de salida, excepto cuando la capa es dinámica (como
veremos en breve). En otras implementaciones de Keras, este método es obligatorio o su
implementación predeterminada supone que la forma de salida es la misma que la forma de entrada.
Para crear una capa con múltiples entradas (por ejemplo, Concatenar), el argumento
del método call() debe ser una tupla que contenga todas las entradas y, de
manera similar, el argumento del método calculate_output_shape() debe ser una
tupla que contenga la forma del lote de cada entrada. Para crear una capa
con múltiples salidas, el método call() debe devolver la lista de salidas y
calculate_output_shape() debe devolver la lista de formas de salida del lote (una
por salida). Por ejemplo, la siguiente capa de juguete toma dos entradas y devuelve
tres salidas:
clase MyMultiLayer(keras.layers.Layer):
def llamada(self, X):
X1, X2 = X
devuelve [X1 + X2, X1 * X2, X1 / X2]
def computar_forma_salida(self, forma_entrada_lote):
b1, b2 = forma_de_entrada_por_lote
# Probablemente debería ocuparse de las normas de transmisión
devolver [b1, b1, b1]
Esta capa ahora se puede usar como cualquier otra capa, pero por supuesto solo
utilizando las API funcionales y de subclases, no la API secuencial (que solo
acepta capas con una entrada y una salida).
Si su capa necesita tener un comportamiento diferente durante el entrenamiento y
durante la prueba (por ejemplo, si utiliza capas Dropout o BatchNormalization),
entonces debe agregar un argumento de entrenamiento al método call() y
usar este argumento para decidir qué hacer. Por ejemplo, creemos una capa que
agregue ruido gaussiano durante el entrenamiento (para la regularización) pero no
haga nada durante la prueba (Keras tiene una capa que
hace lo mismo, keras.layers.GaussianNoise):
Machine Translated by Google
clase MyGaussianNoise(keras.layers.Layer): def __init__(self, stddev,
**kwargs):
super().__init__(**kwargs) propio.stddev =
stddev
def call(self, X, entrenamiento=Ninguno): si
entrenamiento:
ruido = tf.random.normal(tf.shape(X), stddev=self.stddev) devuelve X + ruido de lo contrario:
devolver X
def computar_forma_salida(self, forma_entrada_lote):
devolver lote_entrada_forma
Con esto, ¡ahora puedes crear cualquier capa personalizada que necesites! Ahora, vamos a crear
modelos personalizados.
Modelos personalizados
Ya vimos cómo crear clases de modelos personalizadas en el Capítulo 10, cuando
10
Analizamos la API de subclasificación. Es sencilla: crea una subclase de la clase keras.Model,
crea capas y variables en el constructor e implementa el método call() para hacer lo que quieras
que haga el modelo.
Supongamos que desea construir el modelo representado en la Figura 12­3.
Machine Translated by Google
Figura 12­3. Ejemplo de modelo personalizado: un modelo arbitrario con una capa ResidualBlock personalizada
que contiene una conexión de omisión
Las entradas pasan por una primera capa densa, luego por un bloque residual
compuesto por dos capas densas y una operación de adición (como veremos en
el Capítulo 14, un bloque residual suma sus entradas a sus salidas), luego por
este mismo bloque residual tres veces más, luego por un segundo bloque
residual, y el resultado final pasa por una capa densa de salida. Tenga en cuenta
que este modelo no tiene mucho sentido; es solo un ejemplo para ilustrar el hecho
de que puede construir fácilmente cualquier tipo de modelo que desee,
incluso uno que contenga bucles y conexiones de salto. Para implementar este
modelo, es mejor crear primero una capa ResidualBlock, ya que vamos a crear
un par de bloques idénticos (y es posible que queramos reutilizarlo en otro modelo):
clase ResidualBlock(keras.capas.Capa): def __init__(self,
n_capas, n_neuronas, **kwargs):
super().__init__(**kwargs) self.hidden
= [keras.layers.Dense(n_neuronas, activación="elu",
kernel_initializer="he_normal") en
para _ rango(n_capas)]
Machine Translated by Google
def call(self, entradas): Z = entradas
para la capa en
self.hidden: Z = capa(Z) devuelve
entradas + Z
Esta capa es un poco especial, ya que contiene otras capas. Esto lo gestiona Keras
de forma transparente: detecta automáticamente que el atributo oculto contiene objetos
rastreables (capas en este caso), por lo que sus variables se añaden
automáticamente a la lista de variables de esta capa. El resto de esta clase se explica por
sí sola. A continuación, utilicemos la API de subclases para definir el modelo en sí:
clase ResidualRegressor(keras.Model):
def __init__(self, salida_dim, **kwargs): super().__init__(**kwargs)
self.hidden1 = keras.layers.Dense(30,
activación="elu", inicializador_kernel="he_normal") self.block1 = ResidualBlock(2, 30)
self.block2 = ResidualBlock(2, 30) self.out =
keras.layers.Dense(salida_dim)
def llamada(self, entradas): Z =
self.hidden1(entradas) para en rango(1
+ 3): Z_= self.block1(Z)
Z = self.block2(Z) devuelve
self.out(Z)
Creamos las capas en el constructor y las usamos en el método call(). Este
modelo puede usarse como cualquier otro modelo (compilarlo, ajustarlo, evaluarlo y
usarlo para hacer predicciones). Si también quieres poder guardar el modelo usando el
método save() y cargarlo usando la función keras.models.load_model(), debes
implementar el método get_config() (como hicimos antes) tanto en la clase
ResidualBlock como en la clase ResidualRegressor. Alternativamente, puedes
guardar y cargar los pesos usando los métodos save_weights() y load_weights().
Machine Translated by Google
La clase Model es una subclase de la clase Layer, por lo que los modelos se pueden definir y utilizar
exactamente como capas. Pero un modelo tiene algunas funcionalidades adicionales, incluidos, por
supuesto, los métodos compile(), fit(), evaluation() y predict() (y algunas variantes), además del
método get_layers() (que puede devolver cualquiera de las capas del modelo por nombre o por
índice) y el método save() (y compatibilidad con keras.models.load_model() y
keras.models.clone_model()).
CONSEJO
Si los modelos proporcionan más funcionalidad que las capas, ¿por qué no definir cada capa
como un modelo? Bueno, técnicamente podría, pero suele ser más claro distinguir los
componentes internos de su modelo (es decir, capas o bloques reutilizables de capas) del
modelo en sí (es decir, el objeto que entrenará). El primero debería subclasificar la clase
Capa, mientras que el segundo debería subclasificar la clase Modelo.
Con eso, puedes construir de manera natural y concisa casi cualquier modelo que encuentres en
un artículo, usando la API secuencial, la API funcional, la API de subclasificación o incluso una
combinación de estas. ¿"Casi" cualquier modelo? Sí, todavía hay algunas cosas que debemos
analizar: primero, cómo definir pérdidas o métricas basadas en los elementos internos del modelo y,
segundo, cómo construir un ciclo de entrenamiento personalizado.
Pérdidas y métricas basadas en los datos internos del modelo
Las pérdidas y métricas personalizadas que definimos anteriormente se basaban en las etiquetas
y las predicciones (y, opcionalmente, en los pesos de muestra). Habrá ocasiones en las que desee
definir pérdidas en función de otras partes de su modelo, como los pesos o las activaciones de sus
capas ocultas. Esto puede resultar útil para fines de regularización o para monitorear algún aspecto
interno de su modelo.
Para definir una pérdida personalizada basada en los elementos internos del modelo, calcule la pérdida
en función de cualquier parte del modelo que desee y luego pase el resultado a add_loss().
Machine Translated by Google
Método. Por ejemplo, construyamos un modelo de regresión MLP personalizado
compuesto por una pila de cinco capas ocultas más una capa de salida. Este
modelo personalizado también tendrá una salida auxiliar sobre la capa oculta
superior. La pérdida asociada a esta salida auxiliar se denominará pérdida de
reconstrucción (consulte el Capítulo 17): es la diferencia cuadrática media entre la
reconstrucción y las entradas. Al agregar esta pérdida de reconstrucción a la pérdida
principal, alentaremos al modelo a preservar la mayor cantidad de información posible
a través de las capas ocultas, incluso información que no es directamente útil para la
tarea de regresión en sí. En la práctica, esta pérdida a veces mejora la generalización
(es una pérdida de regularización). Aquí está el código para este modelo personalizado
con una pérdida de reconstrucción personalizada:
clase ReconstructingRegressor(keras.Model):
def __init__(self, salida_dim, **kwargs): super().__init__(**kwargs)
self.hidden = [keras.layers.Dense(30,
activación="selu",
kernel_initializer="lecun_normal") para
_ en rango(5)]
self.out = keras.layers.Dense(output_dim)
def build(self, lote_entrada_forma):
n_entradas = forma_entrada_lote[­1] self.reconstruct
= keras.capas.Dense(n_entradas) super().build(forma_entrada_lote)
def call(self, entradas): Z = entradas
para la capa en
self.hidden: Z = capa(Z) reconstrucción
= self.reconstruct(Z)
recon_loss = tf.reduce_mean(tf.square(reconstruction ­
entradas)) self.add_loss(0.05 * recon_loss) return self.out(Z)
Repasemos este código:
El constructor crea la DNN con cinco capas ocultas densas y una capa de
salida densa.
Machine Translated by Google
El método build() crea una capa densa adicional que se utilizará para
reconstruir las entradas del modelo. Debe crearse aquí porque su número
de unidades debe ser igual al número de entradas, y este número es
desconocido antes de llamar al método build().
El método call() procesa las entradas a través de las cinco capas ocultas y
luego pasa el resultado a través de la capa de reconstrucción, que produce
la reconstrucción.
Luego, el método call() calcula la pérdida de reconstrucción (la diferencia
cuadrática media entre la reconstrucción y las entradas) y la agrega a la
lista de pérdidas del modelo mediante el método add_loss(). Observe
11
que reducimos la pérdida de reconstrucción multiplicándola por
0,05 (este es un hiperparámetro que puede ajustar). Esto
garantiza que la pérdida de reconstrucción no domine la
pérdida principal.
Finalmente, el método call() pasa la salida de las capas ocultas a la capa de
salida y devuelve su salida.
De manera similar, puede agregar una métrica personalizada basada en los
elementos internos del modelo calculándola de la forma que desee, siempre que el
resultado sea la salida de un objeto de métrica. Por ejemplo, puede crear un objeto
keras.metrics.Mean en el constructor, luego llamarlo en el método call(), pasarle
recon_loss y, finalmente, agregarlo al modelo llamando al método add_metric()
del modelo. De esta manera, cuando entrene el modelo, Keras mostrará tanto la
pérdida media en cada época (la pérdida es la suma de la pérdida principal más
0,05 veces la pérdida de reconstrucción) como el error de reconstrucción
medio en cada época. Ambos se reducirán durante el entrenamiento:
Época 1/5
11610/11610 [=============] [...] pérdida: 4.3092 ­ error_de_reconstrucción: 1.7360 Época 2/5 11610/11610 [=============] [...] pérdida:
1.1232 ­
error_de_reconstrucción:
Machine Translated by Google
0,8964 [...]
En más del 99 % de los casos, todo lo que hemos analizado hasta ahora
será suficiente para implementar cualquier modelo que desee crear, incluso con
arquitecturas, pérdidas y métricas complejas. Sin embargo, en algunos casos
excepcionales, es posible que deba personalizar el ciclo de entrenamiento en sí.
Antes de llegar a ese punto, debemos ver cómo calcular gradientes automáticamente en TensorFlow.
Cálculo de gradientes mediante Autodiff
Para entender cómo utilizar autodiff (ver Capítulo 10 y Apéndice D) para calcular
gradientes automáticamente, consideremos una función de juguete simple:
definición f(w1, w2):
devuelve 3 * w1 ** 2 + 2 * w1 * w2
Si sabes cálculo, puedes encontrar analíticamente que la derivada parcial de esta
función con respecto a w1 es 6 * w1 + 2 * w2. También puedes encontrar que su
derivada parcial con respecto a w2 es 2 * w1. Por ejemplo, en el punto (w1, w2) = (5,
3), estas derivadas parciales son iguales a 36 y 10, respectivamente, por lo que el
vector de gradiente en este punto es (36, 10). Pero si se tratara de una red neuronal, la
función sería mucho más compleja, normalmente con decenas de miles de
parámetros, y encontrar las derivadas parciales analíticamente a mano sería una
tarea casi imposible. Una solución podría ser calcular una aproximación de cada
derivada parcial midiendo cuánto cambia la salida de la función cuando
modificas el parámetro correspondiente:
>>> a1, a2 = 5, 3 >>> eps =
1e­6 >>> (f(a1 + eps,
a2) ­ f(a1, a2)) / eps 36.000003007075065 >>> (f(a1, a2 + eps) ­
f(a1, a2)) / eps
10.000000003174137
Machine Translated by Google
Parece correcto. Esto funciona bastante bien y es fácil de implementar, pero es solo una
aproximación y, lo que es más importante, es necesario llamar a f() al menos una vez por
parámetro (no dos, ya que podríamos calcular f(w1, w2) solo una vez). La necesidad de
llamar a f() al menos una vez por parámetro hace que este enfoque sea intratable para redes
neuronales grandes. Por lo tanto, en su lugar, deberíamos usar autodiff.
TensorFlow hace que esto sea bastante simple:
w1, w2 = tf.Variable(5.), tf.Variable(3.) con
tf.GradientTape() como cinta: z = f(w1,
w2)
gradientes = cinta.gradiente(z, [w1, w2])
Primero definimos dos variables w1 y w2, luego creamos un contexto
tf.GradientTape que registrará automáticamente cada operación que involucre una variable
y, finalmente, le pedimos a esta cinta que calcule los gradientes del resultado z con
respecto a ambas variables [w1, w2]. Echemos un vistazo a los gradientes que calculó
TensorFlow:
>>> gradientes
[<tf.Tensor: id=828234, forma=(), dtype=float32, numpy=36.0>, <tf.Tensor:
id=828229, forma=(), dtype=float32, numpy=10.0>]
¡Perfecto! No solo el resultado es preciso (la precisión solo está limitada por los errores de
punto flotante), sino que el método gradient() solo recorre los cálculos registrados una vez
(en orden inverso), sin importar cuántas variables haya, por lo que es increíblemente
eficiente. ¡Es como magia!
CONSEJO
Para ahorrar memoria, coloque únicamente el mínimo estricto dentro del bloque tf.GradientTape().
Como alternativa, puede pausar la grabación creando un bloque with
tape.stop_recording() dentro del bloque tf.GradientTape().
Machine Translated by Google
La cinta se borra automáticamente inmediatamente después de llamarla.
método gradient(), por lo que obtendrá una excepción si intenta llamar
gradient() dos veces:
con tf.GradientTape() como cinta:
z = f(w1, w2)
dz_dw1 = cinta.gradiente(z, w1) dz_dw2 =
cinta.gradiente(z, w2)
# => tensor 36.0
# ¡Error de tiempo de ejecución!
Si necesita llamar a gradient() más de una vez, debe hacer la cinta
persistente y elimínelo cada vez que haya terminado de usarlo para liberar recursos:
12
con tf.GradientTape(persistent=True) como cinta:
z = f(w1, w2)
dz_dw1 = tape.gradient(z, w1) dz_dw2 =
tape.gradient(z, w2) del tape
# => tensor 36.0
# => Tensor 10.0, funciona bien
¡ahora!
De forma predeterminada, la cinta solo rastreará operaciones que involucren variables, por lo que si
Intenta calcular el gradiente de z con respecto a cualquier cosa que no sea a
variable, el resultado será Ninguno:
c1, c2 = tf.constante(5.), tf.constante(3.)
con tf.GradientTape() como cinta:
z = f(c1, c2)
gradientes = cinta.gradiente(z, [c1, c2])
# devuelve [Ninguno, Ninguno]
Sin embargo, puedes forzar la cinta para que observe cualquier tensor que desees, para grabar
cada operación que los involucra. Luego puedes calcular gradientes con
respecto a estos tensores, como si fueran variables:
con tf.GradientTape() como cinta:
cinta.watch(c1)
cinta.watch(c2)
z = f(c1, c2)
gradientes = cinta.gradiente(z, [c1, c2])
# devuelve [tensor 36.,
tensor
10.]
Machine Translated by Google
Esto puede ser útil en algunos casos, como si desea implementar un
pérdida de regularización que penaliza las activaciones que varían mucho cuando la
Las entradas varían poco: la pérdida se basará en el gradiente de las activaciones.
con respecto a las entradas. Dado que las entradas no son variables, se
Necesito decirle a la cinta que los mire.
La mayoría de las veces se utiliza una cinta de gradiente para calcular los gradientes de una
valor único (normalmente la pérdida) con respecto a un conjunto de valores (normalmente el
parámetros del modelo). Aquí es donde brilla la autodiferencia en modo inverso, ya que simplemente
Es necesario realizar una pasada hacia adelante y una pasada hacia atrás para obtener todos los gradientes.
a la vez. Si intenta calcular los gradientes de un vector, por ejemplo, un
vector que contiene múltiples pérdidas, entonces TensorFlow calculará el
gradientes de la suma del vector. Por lo tanto, si alguna vez necesitas obtener los valores individuales
gradientes (por ejemplo, los gradientes de cada pérdida con respecto al modelo
parámetros), debe llamar al método jabobian() de la cinta: realizará
autodiff en modo inverso una vez por cada pérdida en el vector (todo en paralelo por
predeterminado). Incluso es posible calcular derivadas parciales de segundo orden
(las hessianas, es decir, las derivadas parciales de las derivadas parciales), pero
Esto rara vez es necesario en la práctica (consulte “Cálculo de gradientes con
(Sección “Autodiff” del cuaderno para ver un ejemplo).
En algunos casos, es posible que desees evitar que los gradientes se propaguen hacia atrás.
a través de alguna parte de tu red neuronal. Para ello, debes utilizar el
Función tf.stop_gradient(). La función devuelve sus entradas durante el
Pase hacia adelante (como tf.identity()), pero no deja pasar los gradientes
Durante la retropropagación (actúa como una constante):
definición f(w1, w2):
devuelve 3 * w1 ** 2 + tf.stop_gradient(2 * w1 * w2)
con tf.GradientTape() como cinta:
# mismo resultado como sin stop_gradient()
z = f(w1, w2)
gradientes = cinta.gradiente(z, [w1, w2])
# => devuelve [tensor 30.,
Ninguno]
Finalmente, es posible que ocasionalmente te encuentres con algunos problemas numéricos cuando
cálculo de gradientes. Por ejemplo, si calcula los gradientes de la
Machine Translated by Google
Función my_softplus() para entradas grandes, el resultado será NaN:
>>> x = tf.Variable([100.]) >>> con
tf.GradientTape() como cinta: z = my_softplus(x)
...
...
>>> cinta.gradiente(z, [x])
<tf.Tensor: [...] numpy=array([nan], dtype=float32)>
Esto se debe a que calcular los gradientes de esta función mediante autodiff
genera algunas dificultades numéricas: debido a errores de precisión de punto
flotante, autodiff termina calculando infinito dividido por infinito (que devuelve
NaN). Afortunadamente, podemos determinar analíticamente que la derivada
de la función softplus es simplemente 1 / (1 + 1 / exp(x)), que es numéricamente estable.
A continuación, podemos indicarle a TensorFlow que use esta función estable al
calcular los gradientes de la función my_softplus() decorándola con
@tf.custom_gradient y haciendo que devuelva tanto su salida normal como la
función que calcula las derivadas (tenga en cuenta que recibirá como entrada
los gradientes que se propagaron hacia atrás hasta ahora, hasta la función
softplus; y de acuerdo con la regla de la cadena, deberíamos multiplicarlos por
los gradientes de esta función):
@tf.custom_gradient def
my_better_softplus(z): exp =
tf.exp(z) def
my_softplus_gradients(grad): devuelve
grad / (1 + 1 / exp) devuelve
tf.math.log(exp + 1), my_softplus_gradients
Ahora, cuando calculamos los gradientes de la función my_better_softplus(),
obtenemos el resultado adecuado, incluso para valores de entrada grandes (sin
embargo, la salida principal aún explota debido al exponencial; una solución
alternativa es usar tf.where() para devolver las entradas cuando son grandes).
¡Felicitaciones! Ahora puedes calcular los gradientes de cualquier función
(siempre que sea diferenciable en el punto en el que la calculas), incluso
bloqueando la retropropagación cuando sea necesario, y escribir tu propio gradiente.
Machine Translated by Google
¡Funciones! Probablemente, esta sea una flexibilidad mayor de la que necesitarás, incluso si
creas tus propios bucles de entrenamiento personalizados, como veremos ahora.
Circuitos de entrenamiento personalizados
En algunos casos excepcionales, el método fit() puede no ser lo suficientemente flexible para
lo que necesita hacer. Por ejemplo, el documento Wide & Deep El método fit() que
analizamos en el capítulo 10 utiliza dos optimizadores diferentes: uno para la ruta ancha y
otro para la ruta profunda. Dado que el método fit() solo utiliza un optimizador (el que
especificamos al compilar el modelo), para implementar este documento es necesario
escribir un bucle personalizado.
También puede que desees escribir bucles de entrenamiento personalizados simplemente
para sentirte más seguro de que hacen exactamente lo que pretendes que hagan (quizás no
estés seguro de algunos detalles del método fit()). A veces puede parecer más seguro hacer
que todo sea explícito. Sin embargo, recuerda que escribir un bucle de entrenamiento
personalizado hará que tu código sea más largo, más propenso a errores y más difícil de
mantener.
CONSEJO
A menos que realmente necesites flexibilidad adicional, es mejor que utilices el método fit() en lugar de
implementar tu propio ciclo de entrenamiento, especialmente si trabajas en equipo.
Primero, construyamos un modelo simple. No es necesario compilarlo, ya que
manejaremos el ciclo de entrenamiento manualmente:
l2_reg = keras.regularizadores.l2(0.05) modelo =
keras.modelos.Secuencial([ keras.capas.Dense(30,
activación="elu", inicializador_kernel="he_normal",
regularizador_kernel=l2_reg),
keras.capas.Dense(1, kernel_regularizer=l2_reg)
])
Machine Translated by Google
A continuación, vamos a crear una pequeña función que tomará muestras aleatorias de un
lote de instancias del conjunto de entrenamiento (en el Capítulo 13 analizaremos los datos).
API, que ofrece una alternativa mucho mejor):
def random_batch(X, y, tamaño_de_lote=32): idx =
np.random.randint(len(X), tamaño=tamaño_de_lote) return X[idx], y[idx]
Definamos también una función que muestre el estado del entrenamiento, incluyendo el número
de pasos, el número total de pasos, la pérdida media desde el inicio de la época (es decir,
utilizaremos la métrica Media para calcularla) y otras métricas:
def print_status_bar(iteración, total, pérdida, métricas=Ninguna): ­ ".join(["{}: {:.4f}".format(m.name,
"
métricas =
m.result())
para m en [pérdida] + (métricas o [])]) fin = "" si iteración
< total de lo contrario "\n" print("\r{}/{} ­ ".format(iteración, total)
+ métricas, fin=fin)
Este código se explica por sí solo, a menos que no esté familiarizado con el formato de cadenas
de Python: {:.4f} formateará un flotante con cuatro dígitos después del punto decimal, y el uso
de \r (retorno de carro) junto con end="" garantiza que la barra de estado siempre se imprima
en la misma línea. En el cuaderno, la función print_status_bar() incluye una barra de
progreso, pero puede utilizar la práctica biblioteca tqdm en su lugar.
Dicho esto, ¡vamos a ponernos manos a la obra! Primero, debemos definir algunos
hiperparámetros y elegir el optimizador, la función de pérdida y las métricas (solo el MAE
en este ejemplo):
n_épocas = 5
tamaño_lote = 32
n_pasos = len(X_train) // tamaño_lote optimizador =
keras.optimizers.Nadam(lr=0.01) función_pérdida =
keras.pérdidas.error_cuadrado_medio pérdida_media =
keras.métricas.Mean() métricas =
[keras.métricas.MeanAbsoluteError()]
Machine Translated by Google
¡Y ahora estamos listos para construir el bucle personalizado!
para época en rango(1, n_épocas + 1): print("Época {}/
{}".format(época, n_épocas)) para paso en rango(1, n_pasos + 1):
X_batch, y_batch = random_batch(X_train_scaled, y_train) con tf.GradientTape() como
cinta: y_pred = model(X_batch, training=True)
main_loss = tf.reduce_mean(loss_fn(y_batch, y_pred)) loss
= tf.add_n([main_loss] + model.losses) gradients = tape.gradient(loss,
model.trainable_variables) optimized.apply_gradients(zip(gradients,
modelo.variables_entrenables))
mean_loss(pérdida)
para métrica en métricas:
metric(y_batch, y_pred)
print_status_bar(paso * tamaño_lote, len(y_train), pérdida_media, métricas)
print_status_bar(len(y_train), len(y_train), pérdida_media, métricas) para métrica en [pérdida_media] +
métricas: metric.reset_states()
Hay muchas cosas sucediendo en este código, así que veámoslas:
Creamos dos bucles anidados: uno para las épocas y otro para los lotes dentro de
una época.
Luego tomamos una muestra aleatoria del conjunto de entrenamiento.
Dentro del bloque tf.GradientTape(), hacemos una predicción para un lote (usando
el modelo como una función), y calculamos la pérdida: es igual a la pérdida
principal más las otras pérdidas (en este modelo, hay una pérdida de
regularización por capa). Dado que la función mean_squared_error()
devuelve una pérdida por instancia, calculamos la media sobre el lote usando
tf.reduce_mean() (si quisiera aplicar diferentes pesos a cada instancia, aquí es donde
lo haría). Las pérdidas de regularización ya están reducidas a un solo escalar
cada una, por lo que solo necesitamos sumarlas (usando tf.add_n(), que suma
múltiples tensores de la misma forma y tipo de datos).
Machine Translated by Google
A continuación, le pedimos a la cinta que calcule el gradiente de la pérdida
con respecto a cada variable entrenable (¡no todas las variables!) y las aplicamos
al optimizador para realizar un paso de Descenso de Gradiente.
Luego actualizamos la pérdida media y las métricas (sobre la época actual) y
mostramos la barra de estado.
Al final de cada época, mostramos nuevamente la barra de estado para hacer
13
se ve completo y para imprimir un salto de línea, y reiniciamos los estados de la
pérdida media y las métricas.
Si configura el hiperparámetro clipnorm o clipvalue del optimizador, este se encargará de
esto por usted. Si desea aplicar cualquier otra transformación a los gradientes, simplemente
hágalo antes de llamar al método apply_gradients().
Si agrega restricciones de peso a su modelo (por ejemplo, al configurar
kernel_constraint o bias_constraint al crear una capa), debe actualizar el ciclo de
entrenamiento para aplicar estas restricciones justo después de apply_gradients():
Para la variable en model.variables:
Si variable.constraint no es None:
variable.asignar(variable.restricción(variable))
Lo más importante es que este bucle de entrenamiento no maneja capas que se
comportan de manera diferente durante el entrenamiento y la prueba (por ejemplo,
BatchNormalization o Dropout). Para manejarlas, debes llamar al modelo
con training=True y asegurarte de que lo propague a cada capa que lo necesite.
Como puedes ver, hay muchas cosas que debes hacer bien y es fácil cometer errores. Pero
el lado positivo es que tienes el control total, así que tú decides.
14
Ahora que sabe cómo personalizar cualquier parte de sus modelos y algoritmos de
entrenamiento, veamos cómo puede utilizar la personalización automática de TensorFlow.
Machine Translated by Google
Función de generación de gráficos: puede acelerar considerablemente su código
personalizado y también lo hará portátil a cualquier plataforma compatible con TensorFlow
(consulte el Capítulo 19).
Funciones y gráficos de TensorFlow
En TensorFlow 1, los gráficos eran inevitables (al igual que las complejidades que los
acompañaban) porque eran una parte central de la API de TensorFlow. En TensorFlow
2, siguen estando ahí, pero no tan centrales, y son mucho (¡mucho!) más simples de
usar. Para mostrar cuán simple es, comencemos con una función trivial que calcula el
cubo de su entrada:
def cubo(x): devuelve
x
** 3
Obviamente podemos llamar a esta función con un valor de Python, como un int o un float,
o podemos llamarla con un tensor:
>>> cubo(2) 8
>>> cubo(tf.constant(2.0)) <tf.Tensor:
id=18634148, forma=(), dtype=float32, numpy=8.0>
Ahora, usemos tf.function() para convertir esta función de Python en una función
TensorFlow:
>>> tf_cube = tf.function(cubo) >>> tf_cube
<tensorflow.python.eager.def_function.Función en 0x1546fc080>
Esta función TF se puede utilizar exactamente como la función Python original y
devolverá el mismo resultado (pero como tensores):
>>> tf_cube(2)
<tf.Tensor: id=18634201, forma=(), dtype=int32, numpy=8> >>> tf_cube(tf.constant(2.0)) <tf.Tensor:
id=18634211, forma=(), dtype=float32, numpy=8.0>
Machine Translated by Google
En segundo plano, tf.function() analizó los cálculos realizados por la función cube() y
generó un gráfico de cálculo equivalente. Como puedes ver, fue bastante sencillo
(veremos cómo funciona esto en breve).
Alternativamente, podríamos haber utilizado tf.function como decorador; esto es en
realidad más común:
@tf.función def
tf_cube(x): ** 3
devolver x
La función Python original todavía está disponible a través del atributo
python_function de la función TF, en caso de que alguna vez lo necesite:
>>> tf_cube.python_function(2) 8
TensorFlow optimiza el gráfico de cálculo, podando nodos no utilizados, simplificando
expresiones (por ejemplo, 1 + 2 se reemplazaría con 3) y más.
Una vez que el gráfico optimizado está listo, la función TF ejecuta eficientemente las
operaciones en el gráfico, en el orden apropiado (y en paralelo cuando puede). Como
resultado, una función TF generalmente se ejecutará mucho más rápido que la función
Python original, especialmente si realiza cálculos complejos. La mayoría
15
de las veces, no necesitará saber más que eso: cuando desee potenciar una función
Python, simplemente transfórmela en una función TF. ¡Eso es todo!
Además, cuando escribes una función de pérdida personalizada, una métrica
personalizada, una capa personalizada o cualquier otra función personalizada y la
usas en un modelo Keras (como hicimos a lo largo de este capítulo), Keras convierte
automáticamente tu función en una función TF, sin necesidad de usar tf.function().
Por lo tanto, la mayor parte del tiempo, toda esta magia es 100 % transparente.
Machine Translated by Google
CONSEJO
Puedes indicarle a Keras que no convierta tus funciones de Python en funciones TF
configurando dynamic=True al crear una capa o un modelo personalizado.
Alternativamente, puedes configurar run_eagerly=True al llamar al método compile() del modelo.
De forma predeterminada, una función TF genera un nuevo gráfico para cada
conjunto único de formas de entrada y tipos de datos y lo almacena en
caché para llamadas posteriores. Por ejemplo, si llama a
tf_cube(tf.constant(10)), se generará un gráfico para
tensores int32 de forma []. Luego, si llama a tf_cube(tf.constant(20)), se
reutilizará el mismo gráfico. Pero si luego llama a tf_cube(tf.constant([10,
20])), se generará un nuevo gráfico para tensores int32 de forma [2]. Así es
como las funciones TF manejan el polimorfismo (es decir, varían los tipos de
argumentos y formas). Sin embargo, esto solo es cierto para los argumentos
de tensor: si pasa valores numéricos de Python a una función TF, se generará
un nuevo gráfico para cada valor distinto: por ejemplo, llamar a tf_cube(10) y tf_cube(20) gener
ADVERTENCIA
Si llamas a una función TF muchas veces con diferentes valores numéricos de Python,
se generarán muchos gráficos, lo que ralentizará tu programa y consumirá mucha RAM
(debes eliminar la función TF para liberarla). Los valores de Python deben
reservarse para argumentos que tendrán pocos valores únicos, como hiperparámetros
como la cantidad de neuronas por capa. Esto permite que TensorFlow optimice mejor
cada variante de tu modelo.
AutoGraph y seguimiento
Entonces, ¿cómo genera TensorFlow gráficos? Comienza analizando el
código fuente de la función Python para capturar todas las instrucciones de
flujo de control, como bucles for, bucles while y declaraciones if, así como
las instrucciones break, continue y return. Este primer paso se llama AutoGraph.
La razón por la que TensorFlow tiene que analizar el código fuente es que Python no
Machine Translated by Google
proporciona cualquier otra forma de capturar declaraciones de flujo de
control: ofrece métodos mágicos como __add__() y __mul__() para capturar
operadores como + y *, pero no hay métodos mágicos __while__()
o __if__(). Después de analizar el código de la función, AutoGraph genera
una versión mejorada de esa función en la que todas las declaraciones de
flujo de control se reemplazan por las operaciones de TensorFlow
adecuadas, como tf.while_loop() para bucles for y tf.cond() para declaraciones
if. Por ejemplo, en la Figura 12­4, AutoGraph analiza el código fuente de
la función sum_squares() de Python y genera la función tf__sum_squares().
En esta función, el bucle for se reemplaza por la definición de la función
loop_body() (que contiene el cuerpo del bucle for original), seguido de
una llamada a la función for_stmt(). Esta llamada creará la operación
tf.while_loop() adecuada en el gráfico de cálculo.
Figura 12­4. Cómo TensorFlow genera gráficos utilizando AutoGraph y el seguimiento
A continuación, TensorFlow llama a esta función "actualizada", pero en lugar
de pasar el argumento, pasa un tensor simbólico (un tensor sin ningún valor
real, solo un nombre, un tipo de datos y una forma). Por ejemplo,
si llama a sum_squares(tf.constant(10)), se llamará a la función
tf__sum_squares() con un tensor simbólico de tipo int32 y forma [].
Machine Translated by Google
La función se ejecutará en modo gráfico, lo que significa que cada operación de
TensorFlow agregará un nodo en el gráfico para representarse a sí misma y a su(s)
tensor(es) de salida (a diferencia del modo regular, llamado ejecución ansiosa o
modo ansioso). En el modo gráfico, las operaciones de TF no realizan ningún cálculo.
Esto debería resultarle familiar si conoce TensorFlow 1, ya que el modo gráfico
era el modo predeterminado. En la Figura 12­4, puede ver la función tf__sum_squares()
que se llama con un tensor simbólico como argumento (en este caso, un tensor int32
de forma []) y el gráfico final que se genera durante el seguimiento. Los nodos
representan operaciones y las flechas representan tensores (tanto la función generada
como el gráfico están simplificados).
CONSEJO
Para ver el código fuente de la función generada, puedes
llamar a tf.autograph.to_code(sum_squares.python_function). El código no tiene por
qué ser atractivo, pero a veces puede resultar útil para la depuración.
Reglas de la función TF
La mayoría de las veces, convertir una función Python que realiza operaciones de
TensorFlow en una función TF es trivial: decórela con @tf.function o deje que Keras
se encargue de ello por usted. Sin embargo, hay algunas reglas que respetar:
Si llama a cualquier biblioteca externa, incluida NumPy o incluso la
biblioteca estándar, esta llamada se ejecutará solo durante el seguimiento;
no será parte del gráfico. De hecho, un gráfico de TensorFlow solo puede
incluir construcciones de TensorFlow (tensores, operaciones, variables,
conjuntos de datos, etc.). Por lo tanto, asegúrese de usar tf.reduce_sum()
en lugar de np.sum(), tf.sort() en lugar de la función integrada sorted(), etc.
(a menos que realmente desee que el código se ejecute solo durante
el seguimiento). Esto tiene algunas implicaciones adicionales:
Si define una función TF f(x) que solo devuelve
np.random.rand(), solo se obtendrá un número aleatorio.
Machine Translated by Google
se genera cuando se traza la función, por lo que
f(tf.constant(2.)) y f(tf.constant(3.)) devolverán el mismo número
aleatorio, pero f(tf.constant([2., 3.]))
devolverá uno diferente.
Si reemplaza np.random.rand() con
tf.random.uniform([]), se generará un nuevo número aleatorio en
cada llamada, ya que la operación será parte del gráfico.
Si su código que no es de TensorFlow tiene efectos secundarios
(como registrar algo o actualizar un contador de Python), entonces
no debe esperar que esos efectos secundarios ocurran cada vez
que llame a la función TF, ya que solo ocurrirán cuando se
rastree la función.
Puedes encapsular código Python arbitrario en
una operación tf.py_function(), pero al hacerlo afectarás el
rendimiento, ya que TensorFlow no podrá realizar ninguna
optimización gráfica en este código. También reducirá la
portabilidad, ya que el gráfico solo se ejecutará en plataformas donde
Python esté disponible (y donde estén instaladas las bibliotecas
adecuadas).
Puedes llamar a otras funciones de Python o funciones TF, pero deben
seguir las mismas reglas, ya que TensorFlow capturará sus operaciones
en el gráfico de cálculo. Ten en cuenta que no es necesario decorar
estas otras funciones con @tf.function.
Si la función crea una variable de TensorFlow (o cualquier otro objeto de
TensorFlow con estado, como un conjunto de datos o una cola), debe
hacerlo en la primera llamada y solo entonces, o de lo contrario obtendrá
una excepción. Por lo general, es preferible crear variables fuera de la
función TF (por ejemplo, en el método build() de una capa personalizada).
Si desea asignar un nuevo valor a la variable, asegúrese de llamar a su
método allow(), en lugar de utilizar el operador =.
Machine Translated by Google
El código fuente de su función Python debe estar disponible para TensorFlow.
Si el código fuente no está disponible (por ejemplo, si define su función en el
shell de Python, que no da acceso al código fuente, o si implementa solo
los archivos *.pyc de Python compilados en producción), entonces el
proceso de generación de gráficos fallará o tendrá una funcionalidad
limitada.
TensorFlow solo capturará bucles for que iteren sobre un tensor o un conjunto
de datos. Por lo tanto, asegúrese de utilizar for i en tf.range(x) en lugar de for i
en range(x), o de lo contrario el bucle no se capturará en el gráfico. En cambio,
se ejecutará durante el seguimiento. (Esto puede ser lo que desea si el bucle
for está destinado a construir el gráfico, por ejemplo, para crear cada capa en
una red neuronal).
Como siempre, por razones de rendimiento, es mejor preferir una
implementación vectorizada siempre que sea posible, en lugar de utilizar
bucles.
¡Es hora de resumir! En este capítulo comenzamos con una breve descripción general
de TensorFlow, luego analizamos la API de bajo nivel de TensorFlow, incluidos
tensores, operaciones, variables y estructuras de datos especiales. Luego usamos
estas herramientas para personalizar casi todos los componentes en tf.keras.
Finalmente, analizamos cómo las funciones TF pueden mejorar el rendimiento,
cómo se generan los gráficos utilizando AutoGraph y el rastreo, y qué reglas seguir cuando
escribe funciones TF (si desea abrir la caja negra un poco más, por ejemplo para
explorar los gráficos generados, encontrará detalles técnicos en el Apéndice G).
En el próximo capítulo, veremos cómo cargar y preprocesar datos de manera eficiente
con TensorFlow.
Ceremonias
1. ¿Cómo describirías TensorFlow en una frase breve?
¿Cuáles son sus principales características? ¿Puedes nombrar otras herramientas populares de Deep Learning?
Machine Translated by Google
¿bibliotecas?
2. ¿TensorFlow es un reemplazo directo de NumPy? ¿Cuáles son las
¿Cuáles son las principales diferencias entre ambos?
3. ¿Obtienes el mismo resultado con tf.range(10) y tf.constant(np.arange(10))?
4. ¿Puedes nombrar otras seis estructuras de datos disponibles en TensorFlow?
¿más allá de los tensores regulares?
5. Se puede definir una función de pérdida personalizada escribiendo una función o creando
una subclase de la clase keras.losses.Loss. ¿Cuándo utilizarías cada opción?
6. De manera similar, una métrica personalizada se puede definir en una función o una
Subclase de keras.metrics.Metric. ¿Cuándo utilizarías cada opción?
7. ¿Cuándo debería crear una capa personalizada en lugar de un modelo personalizado?
8. ¿Cuáles son algunos casos de uso que requieren escribir su propio ciclo de entrenamiento
personalizado?
9. ¿Pueden los componentes Keras personalizados contener código Python arbitrario o deben
ser convertibles a funciones TF?
10. ¿Cuáles son las principales reglas a respetar si queremos que una función sea
convertible a una Función TF?
11. ¿Cuándo necesitarías crear un modelo Keras dinámico? ¿Cómo lo haces? ¿Por qué no
hacer que todos tus modelos sean dinámicos?
12. Implemente una capa personalizada que realice la Normalización de Capa (usaremos este
tipo de capa en el Capítulo 15):
a. El método build() debe definir dos pesos entrenables α y β, ambos de forma
input_shape[­1:] y tipo de datos
Machine Translated by Google
tf.float32. α debe inicializarse con 1 y β con 0.
b. El método call() debe calcular la media μ y
desviación estándar σ de las características de cada instancia. Para
esto, puede utilizar tf.nn.moments(inputs, axes=­1,
keepdims=True), que devuelve la media μ y la varianza σ de
2
todas las instancias (calcule la raíz cuadrada de la varianza para
obtener la desviación estándar). Luego, la función debe calcular
y devolver α
(X ­ μ)/(σ + ε) + β, donde
representa la multiplicación
de ítems (*) y ε es un término de suavizado (una constante pequeña
para evitar la división por cero, p. ej., 0,001).
c. Asegúrese de que su capa personalizada produzca el mismo (o muy
casi la misma) salida que la capa
keras.layers.LayerNormalization.
13. Entrena un modelo usando un ciclo de entrenamiento personalizado para abordar la moda.
Conjunto de datos MNIST (véase el Capítulo 10).
a. Muestra la época, la iteración, la pérdida de entrenamiento media
y la precisión media en cada época (actualizada en cada
iteración), así como la pérdida de validación y la precisión al final de
cada época.
b. Intente utilizar un optimizador diferente con una tasa de aprendizaje
diferente para las capas superiores y las capas inferiores.
Las soluciones a estos ejercicios están disponibles en el Apéndice A.
1 TensorFlow incluye otra API de aprendizaje profundo llamada API de estimadores, pero el equipo de
TensorFlow recomienda utilizar tf.keras en su lugar.
2 Si alguna vez lo necesitas (pero probablemente no lo harás), puedes escribir tus propias operaciones usando
la API de C++.
Machine Translated by Google
3 Para obtener más información sobre las TPU y cómo funcionan, consulte https://homl.info/tpus.
4 Una excepción notable es tf.math.log(), que se usa comúnmente pero no tiene un alias tf.log() (ya que podría
confundirse con el registro).
No sería una buena idea utilizar una media ponderada: si lo hiciera, dos instancias con el mismo peso pero en lotes
diferentes tendrían un impacto diferente en el entrenamiento, dependiendo del peso total de cada lote .
Sin embargo, la pérdida de Huber rara vez se utiliza como métrica (se prefiere la MAE o la MSE).
7 Esta clase es solo para fines ilustrativos. Una implementación más simple y mejor sería simplemente subclasificar la clase
keras.metrics.Mean; consulte la sección “Métricas de transmisión” del cuaderno para ver un ejemplo.
8 Esta función es específica de tf.keras. Puede utilizar keras.activations.Activation
en cambio.
La API de Keras llama a este argumento input_shape, pero como también incluye el lote
dimensión, prefiero llamarla batch_input_shape. Lo mismo ocurre con calculate_output_shape().
10 El nombre “API de subclasificación” generalmente se refiere solo a la creación de modelos personalizados
subclasificación, aunque se pueden crear muchas otras cosas mediante subclasificación, como vimos en este capítulo.
11 También puedes llamar a add_loss() en cualquier capa dentro del modelo, ya que el modelo recursivamente
recoge pérdidas de todas sus capas.
12 Si la cinta queda fuera del alcance, por ejemplo cuando la función que la utilizó retorna, el recolector de basura de Python
la eliminará por usted.
13 La verdad es que no procesamos cada instancia individual en el conjunto de entrenamiento, porque
Se tomaron muestras de instancias de forma aleatoria: algunas se procesaron más de una vez, mientras que otras no
se procesaron en absoluto. Asimismo, si el tamaño del conjunto de entrenamiento no es un múltiplo del tamaño del lote,
se perderán algunas instancias. En la práctica, eso está bien.
14 Con la excepción de los optimizadores, ya que muy pocas personas los personalizan; consulte la sección
“Optimizadores personalizados” en el cuaderno para ver un ejemplo.
15 Sin embargo, en este ejemplo trivial, el gráfico de cálculo es tan pequeño que no hay nada en
Todo para optimizar, por lo que tf_cube() en realidad se ejecuta mucho más lento que cube().
Machine Translated by Google
Capítulo 13. Carga y
preprocesamiento de datos con
TensorFlow
Hasta ahora solo hemos utilizado conjuntos de datos que caben en la memoria, pero los sistemas de
aprendizaje profundo a menudo se entrenan con conjuntos de datos muy grandes que no caben en la RAM.
La ingesta de un gran conjunto de datos y su preprocesamiento eficiente puede ser difícil de implementar
con otras bibliotecas de aprendizaje profundo, pero TensorFlow lo hace fácil gracias a la API de datos:
solo crea un objeto de conjunto de datos y le indica dónde obtener los datos y cómo transformarlos.
TensorFlow se encarga de todos los detalles de implementación, como el procesamiento multihilo, la puesta
en cola, el procesamiento por lotes y la precarga. Además, la API de datos funciona a la perfección con
tf.keras.
La API de datos puede leer archivos de texto (como archivos CSV), archivos binarios con registros de
tamaño fijo y archivos binarios que utilizan el formato TFRecord de TensorFlow, que admite registros de
distintos tamaños. TFRecord es un formato binario flexible y eficiente basado en Protocol Buffers (un
formato binario de código abierto). La API de datos también admite la lectura de bases de datos SQL.
Además, hay muchas extensiones de código abierto disponibles para leer todo tipo de fuentes de
datos, como el servicio BigQuery de Google.
La lectura eficiente de grandes conjuntos de datos no es la única dificultad: los datos también necesitan
ser preprocesados, normalmente normalizados. Además, no siempre están compuestos estrictamente de
campos numéricos convenientes: puede haber características de texto, características categóricas,
etc. Estas deben codificarse, por ejemplo, utilizando codificación one­hot, codificación bag­of­words o
incrustaciones (como veremos, una incrustación es un vector denso entrenable que representa una
categoría o un token). Una opción para manejar todo este preprocesamiento es escribir sus propias capas
de preprocesamiento personalizadas. Otra es utilizar las capas de preprocesamiento estándar
proporcionadas por Keras.
Machine Translated by Google
En este capítulo, abordaremos la API de datos, el formato TFRecord y cómo crear capas de
preprocesamiento personalizadas y utilizar las capas estándar de Keras. También
analizaremos rápidamente algunos proyectos relacionados del ecosistema de
TensorFlow:
Transformación TF (tf.Transform)
Permite escribir una única función de preprocesamiento que se puede ejecutar en
modo por lotes en su conjunto de entrenamiento completo, antes del entrenamiento
(para acelerarlo), y luego exportar a una función TF e incorporar a su modelo
entrenado para que una vez que se implemente en producción pueda encargarse de
preprocesar nuevas instancias sobre la marcha.
Conjuntos de datos de TF (TFDS)
Proporciona una función conveniente para descargar muchos conjuntos de datos
comunes de todo tipo, incluidos los grandes como ImageNet, así como objetos de
conjuntos de datos convenientes para manipularlos utilizando la API de datos.
¡Así que comencemos!
La API de datos
Toda la API de datos gira en torno al concepto de un conjunto de datos: como puede
sospechar, esto representa una secuencia de elementos de datos. Por lo general, se
utilizan conjuntos de datos que leen datos gradualmente desde el disco, pero para
simplificar, crearemos un conjunto de datos
completamente en RAM utilizando tf.data.Dataset.from_tensor_slices():
>>> X = tf.range(10) >>>
#
cualquier
datos
tensor
conjunto de datos = tf.data.Dataset.from_tensor_slices(X) >>> conjunto de datos
<TensorSliceDataset
formas: (), tipos: tf.int32>
La función from_tensor_slices() toma un tensor y crea un tf.data.Dataset cuyos
elementos son todas las porciones de X (a lo largo de la primera dimensión), por lo que
este conjunto de datos contiene 10 elementos: tensores 0, 1, 2, …, 9. En este
Machine Translated by Google
caso habríamos obtenido el mismo conjunto de datos si hubiéramos utilizado
tf.data.Conjunto de datos.rango(10).
Puedes simplemente iterar sobre los elementos de un conjunto de datos de esta manera:
>>> para el elemento del conjunto de datos:
...
imprimir(artículo)
...
tf.Tensor(0, forma=(), tipod=int32) tf.Tensor(1,
forma=(), tipod=int32) tf.Tensor(2, forma=(),
tipod=int32) [...] tf.Tensor(9, forma=(),
tipod=int32)
Encadenamiento de transformaciones Una
vez que se tiene un conjunto de datos, se pueden aplicar todo tipo de transformaciones llamando
a sus métodos de transformación. Cada método devuelve un nuevo conjunto de datos, por lo
que se pueden encadenar transformaciones de esta manera (esta cadena se ilustra
en la Figura 13­1):
>>> dataset = dataset.repeat(3).batch(7) >>> para el elemento
en el conjunto de datos:
...
imprimir(artículo)
...
tf.Tensor([0 1 2 3 4 5 6], forma=(7,), tipod=int32) tf.Tensor([7 8 9 0
1 2 3], forma=(7,), tipod=int32) tf.Tensor([4 5 6 7 8 9 0], forma=(7,),
tipod=int32) tf.Tensor([1 2 3 4 5 6 7], forma=(7,), tipod=int32)
tf.Tensor([8 9], forma=(2,), tipod=int32)
Machine Translated by Google
Figura 13­1. Encadenamiento de transformaciones de conjuntos de datos
En este ejemplo, primero llamamos al método repeat() en el conjunto de datos original, y
devuelve un nuevo conjunto de datos que repetirá los elementos del conjunto de
datos original tres veces. Por supuesto, esto no copiará todos los datos en la memoria tres
veces. (Si llama a este método sin argumentos, el nuevo conjunto de datos repetirá el
conjunto de datos de origen para siempre, por lo que el código que itera sobre el conjunto
de datos tendrá que decidir cuándo detenerse). Luego, llamamos al método batch()
en este nuevo conjunto de datos, y nuevamente esto crea un nuevo conjunto de datos. Este
agrupará los elementos del conjunto de datos anterior en lotes de siete elementos.
Por último, iteramos sobre los elementos de este conjunto de datos final. Como puede ver,
el método batch() tenía que generar un lote final de tamaño dos en lugar de siete, pero
puede llamarlo con drop_remainder=True si desea que descarte este lote final para que
todos los lotes tengan exactamente el mismo tamaño.
ADVERTENCIA
Los métodos de conjunto de datos no modifican los conjuntos de datos, crean conjuntos nuevos, así que asegúrese
de mantener una referencia a estos nuevos conjuntos de datos (por ejemplo, con dataset = ...), de lo contrario no
sucederá nada.
También puedes transformar los elementos llamando al método map(). Por ejemplo,
esto crea un nuevo conjunto de datos con todos los elementos duplicados:
>>> conjunto de datos = conjunto de datos.map(lambda x: x * 2)
Elementos: [0,2,4,6,8,10,12] #
Machine Translated by Google
Esta función es la que llamarás para aplicar cualquier preprocesamiento que desees.
a sus datos. A veces, esto incluirá cálculos que pueden ser bastante
intensivo, como remodelar o rotar una imagen, por lo que normalmente querrás
para generar múltiples subprocesos para acelerar las cosas: es tan simple como configurar el
argumento num_parallel_calls. Tenga en cuenta que la función que pasa a la
El método map() debe ser convertible a una función TF (ver Capítulo 12).
Mientras que el método map() aplica una transformación a cada elemento, el
El método apply() aplica una transformación al conjunto de datos como un todo.
Por ejemplo, el siguiente código aplica la función unbatch() al conjunto de datos
(Esta función es actualmente experimental, pero lo más probable es que se traslade a la
API principal en una versión futura). Cada elemento del nuevo conjunto de datos será un tensor de un
solo entero en lugar de un lote de siete enteros:
>>> conjunto de datos = conjunto de datos.apply(tf.data.experimental.unbatch()) #
Elementos:
0,2,4,...
También es posible simplemente filtrar el conjunto de datos utilizando el método filter():
>>> conjunto de datos = conjunto de datos.filtro(lambda x: x < 10) #
6...
Elementos:
02
4 6 802
4
A menudo, querrás ver solo algunos elementos de un conjunto de datos. Puedes usar
El método take() para eso:
>>> para el elemento en dataset.take(3):
...
imprimir(artículo)
...
tf.Tensor(0, forma=(), tipo=int64)
tf.Tensor(2, forma=(), tipo=int64)
tf.Tensor(4, forma=(), tipo=int64)
Mezclando los datos
Como sabes, el Descenso de gradiente funciona mejor cuando las instancias en el
El conjunto de entrenamiento es independiente y se distribuye de forma idéntica (véase el Capítulo 4).
Una forma sencilla de garantizar esto es mezclar las instancias, utilizando shuffle()
Machine Translated by Google
Método. Creará un nuevo conjunto de datos que comenzará llenando un búfer con los
primeros elementos del conjunto de datos de origen. Luego, cada vez que se le solicite un
elemento, extraerá uno aleatoriamente del búfer y lo reemplazará con uno nuevo del
conjunto de datos de origen, hasta que haya iterado por completo a través del conjunto de
datos de origen. En este punto, continúa extrayendo elementos aleatoriamente del búfer hasta
que esté vacío. Debe especificar el tamaño del búfer y es importante que sea lo
suficientemente grande, o de lo contrario, la mezcla no será muy efectiva. Simplemente
1
no exceda la cantidad de RAM que tiene, e incluso si tiene suficiente, no hay necesidad de
ir más allá del tamaño del conjunto de datos. Puede proporcionar una semilla aleatoria si
desea el mismo orden aleatorio cada vez que ejecute su programa. Por ejemplo, el siguiente
código crea y muestra un conjunto de datos que contiene los números enteros del 0 al
9, repetidos 3 veces, mezclados utilizando un búfer de tamaño 5 y una semilla aleatoria de 42,
y procesados por lotes con un tamaño de lote de 7:
>>> conjunto de datos = tf.data.Dataset.range(10).repeat(3) >>> conjunto de
#0
a
9, tres veces
datos = dataset.shuffle(buffer_size=5, seed=42).batch(7) >>> para el elemento en el conjunto de
datos:
...
...
imprimir(artículo)
tf.Tensor([0 2 3 6 7 9 4], forma=(7,), tipod=int64) tf.Tensor([5 0 1 1 8
6 5], forma=(7,), tipod=int64) tf.Tensor([4 8 7 1 2 3 0], forma=(7,),
tipod=int64) tf.Tensor([5 4 2 7 8 9 9], forma=(7,), tipod=int64)
tf.Tensor([3 6], forma=(2,), tipod=int64)
CONSEJO
Si llama a repeat() en un conjunto de datos mezclados, de manera predeterminada generará un
nuevo orden en cada iteración. Esto suele ser una buena idea, pero si prefiere reutilizar el
mismo orden en cada iteración (por ejemplo, para pruebas o depuración),
puede configurar reshuffle_each_iteration=False.
En el caso de un conjunto de datos de gran tamaño que no cabe en la memoria, este
sencillo método de mezclar el búfer puede no ser suficiente, ya que el búfer será
pequeño en comparación con el conjunto de datos. Una solución es mezclar los datos de origen en sí.
Machine Translated by Google
(por ejemplo, en Linux puedes mezclar archivos de texto usando el comando
shuf). ¡Esto definitivamente mejorará mucho la mezcla! Incluso si los datos de origen están
mezclados, normalmente querrás mezclarlos un poco más, o de lo contrario se repetirá el
mismo orden en cada época, y el modelo puede terminar siendo sesgado (por ejemplo,
debido a algunos patrones espurios presentes por casualidad en el orden de los datos de
origen). Para mezclar las instancias un poco más, un enfoque común es dividir los
datos de origen en varios archivos, luego leerlos en un orden aleatorio durante el
entrenamiento. Sin embargo, las instancias ubicadas en el mismo archivo aún terminarán
cerca unas de otras. Para evitar esto, puedes elegir varios archivos al azar y leerlos
simultáneamente, intercalando sus registros.
Además de eso, puedes agregar un buffer de mezcla usando el método shuffle(). Si todo
esto te parece mucho trabajo, no te preocupes: la API de datos hace que todo esto sea
posible en tan solo unas pocas líneas de código. Veamos cómo hacerlo.
Intercalar líneas de varios archivos En primer lugar,
supongamos que ha cargado el conjunto de datos de viviendas de California, lo ha
mezclado (a menos que ya estuviera mezclado) y lo ha dividido en un conjunto de
entrenamiento, un conjunto de validación y un conjunto de prueba. Luego, divide cada
conjunto en muchos archivos CSV que se ven así (cada fila contiene ocho características
de entrada más el valor medio de la vivienda de destino):
MedInc, Edad de la casa, Promedio de habitaciones, Promedio de dormitorios, Población, Promedio de ocupación, Latitud, Longitud, Valor medio de la casa
uno
3.5214,15.0,3.0499,1.1065,1447.0,1.6059,37.63,­122.43,1.442
5.3275,5.0,6.4900,0.9910,3464.0,3.4433,33.69,­117.39,1.687
,29.0,7.5423,1.5915,1328.0,2.2508,38.44, ­122,98,1,621 [...]
Supongamos también que train_filepaths contiene la lista de rutas de archivos de entrenamiento
(y también tiene valid_filepaths y test_filepaths):
>>> train_filepaths
['conjuntos de datos/vivienda/mi_tren_00.csv',
'conjuntos de datos/vivienda/mi_tren_01.csv',...]
Machine Translated by Google
Como alternativa, puede utilizar patrones de archivos; por ejemplo, train_filepaths =
"datasets/housing/my_train_*.csv". Ahora, creemos un conjunto de datos que
contenga solo estas rutas de archivos:
filepath_dataset = tf.data.Dataset.list_files(train_filepaths, semilla=42)
De forma predeterminada, la función list_files() devuelve un conjunto de datos que
mezcla las rutas de los archivos. En general, esto es bueno, pero puedes configurar
shuffle=False si no lo deseas por algún motivo.
A continuación, puede llamar al método interleave() para leer cinco archivos a la vez e
intercalar sus líneas (omitiendo la primera línea de cada archivo, que es la fila del
encabezado, usando el método skip()):
n_lectores = 5
conjunto de datos =
ruta_de_archivo_conjunto_de_datos.interleave( lambda ruta_de_archivo:
tf.data.TextLineDataset(ruta_de_archivo).skip(1), longitud_de_ciclo=n_lectores)
El método interleave() creará un conjunto de datos que extraerá cinco rutas de archivo
del filepath_dataset y, para cada una de ellas, llamará a la función que le haya
proporcionado (una lambda en este ejemplo) para crear un nuevo conjunto de datos (en
este caso, un TextLineDataset). Para que quede claro, en esta etapa habrá siete conjuntos
de datos en total: el conjunto de datos filepath, el conjunto de datos interleave y los
cinco TextLineDatasets creados internamente por el conjunto de datos interleave. Cuando
iteramos sobre el conjunto de datos interleave, recorrerá estos cinco
TextLineDatasets, leyendo una línea a la vez de cada uno hasta que todos los conjuntos
de datos se queden sin elementos. Luego, obtendrá las siguientes cinco rutas
de archivo del filepath_dataset y las intercalará de la misma manera, y así sucesivamente
hasta que se quede sin rutas de archivo.
Machine Translated by Google
CONSEJO
Para que el intercalado funcione mejor, es preferible tener archivos de longitud idéntica;
De lo contrario, los extremos de los archivos más largos no se intercalarán.
De forma predeterminada, interleave() no utiliza paralelismo; solo lee una línea
a la vez desde cada archivo, secuencialmente. Si quieres que realmente lea archivos
En paralelo, puede establecer el argumento num_parallel_calls en el número
de hilos que desea (tenga en cuenta que el método map() también tiene este argumento).
Incluso puedes configurarlo en tf.data.experimental.AUTOTUNE para que
TensorFlow elige la cantidad correcta de subprocesos de forma dinámica en función de
CPU disponible (sin embargo, esta es una función experimental por ahora).
Mira lo que contiene el conjunto de datos ahora:
>>> para la línea en dataset.take(5):
...
imprimir(linea.numpy())
...
b'4.2083,44.0,5.3232,0.9171,846.0,2.3370,37.47,­122.2,2.782'
b'4.1812,52.0,5.7013,0.9965,692.0,2.4027,33.73,­118.31,3.215'
b'3.6875,44.0,4.5244,0.9930,457.0,3.1958,34.04,­118.15,1.625'
b'3.3456,37.0,4.5140,0.9084,458.0,3.2253,36.67,­121.7,2.526'
b'3.5214,15.0,3.0499,1.1065,1447.0,1.6059,37.63,­122.43,1.442'
Estas son las primeras filas (ignorando la fila del encabezado) de cinco archivos CSV,
Elegidos al azar. ¡Se ven bien! Pero como puedes ver, son solo bytes.
cadenas; necesitamos analizarlas y escalar los datos.
Preprocesamiento de los datos
Implementemos una pequeña función que realizará este preprocesamiento:
X_media, X_estándar = [...]
#
significar
y la escala de cada característica
colocar
n_entradas = 8
def preprocess(linea):
defs = [0.] * n_entradas + [tf.constant([], dtype=tf.float32)]
campos = tf.io.decode_csv(línea, valores predeterminados de registro=defs)
en El entrenamiento
Machine Translated by Google
x = tf.stack(campos[:­1]) y =
tf.stack(campos[­1:]) devuelve
(x ­ X_media) / X_estándar, y
Repasemos este código:
En primer lugar, el código supone que hemos calculado previamente la media y la desviación
estándar de cada característica del conjunto de entrenamiento. X_mean y X_std son
simplemente tensores unidimensionales (o matrices NumPy) que contienen ocho puntos
flotantes, uno por cada característica de entrada.
La función preprocess() toma una línea CSV y comienza por analizarla. Para ello, utiliza
la función tf.io.decode_csv(), que toma dos argumentos: el primero es la línea que se va
a analizar y el segundo es una matriz que contiene el valor predeterminado para cada
columna del archivo CSV. Esta matriz le indica a TensorFlow no solo el valor predeterminado
para cada columna, sino también la cantidad de columnas y sus tipos. En este ejemplo,
le indicamos que todas las columnas de características son flotantes y que los valores
faltantes deben tener el valor predeterminado 0, pero proporcionamos una matriz vacía de tipo
tf.float32 como valor predeterminado para la última columna (el objetivo): la matriz le
indica a TensorFlow que esta columna contiene flotantes, pero que no hay un valor
predeterminado, por lo que generará una excepción si encuentra un valor faltante.
La función decode_csv() devuelve una lista de tensores escalares (uno por columna), pero
necesitamos devolver matrices de tensores 1D. Por lo tanto, llamamos a tf.stack() en todos
los tensores excepto en el último (el objetivo): esto apilará estos tensores en una matriz 1D.
Luego hacemos lo mismo con el valor objetivo (esto lo convierte en una matriz de tensores
1D con un solo valor, en lugar de un tensor escalar).
Finalmente, escalamos las características de entrada restando las medias de las
características y luego dividiendo por las desviaciones estándar de las características, y
devolvemos una tupla que contiene las características escaladas y el objetivo.
Probemos esta función de preprocesamiento:
Machine Translated by Google
>>>
preprocesar(b'4.2083,44.0,5.3232,0.9171,846.0,2.3370,37.47,­122.2,2.782') (<tf.Tensor: id=6227, forma=(8,), tipo_d=float32,
numpy= matriz([ 0.16579159, 1.216324 , ­0.05204564, ­0.39215982, ­0.5277444 ­0.2633488
0.8543046 ­1.3072058 ], tipo_d=float32)>, <tf.Tensor: [...], numpy=matriz([2.782], tipo_d=float32)>)
,
,
,
¡Se ve bien! Ahora podemos aplicar la función al conjunto de datos.
Poniendo todo junto
Para que el código sea reutilizable, juntemos todo lo que hemos discutido hasta ahora en
una pequeña función auxiliar: creará y devolverá un conjunto de datos que cargará de
manera eficiente los datos de vivienda de California desde múltiples archivos CSV, los
preprocesará, los mezclará, los repetirá opcionalmente y los procesará por lotes (ver Figura 13­2):
def csv_reader_dataset(rutas de archivo, repetición=1, n_lectores=5,
n_read_threads=Ninguno, tamaño de búfer aleatorio=10000,
n_parse_threads=5, tamaño de lote=32):
conjunto de datos = tf.data.Dataset.list_files(rutas de archivo)
conjunto de datos = conjunto de datos.interleave(
ruta de archivo lambda : tf.data.TextLineDataset(ruta_de_archivo).skip(1),
longitud_de_ciclo=n_lectores, número_de_llamadas_paralelas=n_subprocesos_de_lectura)
conjunto de datos = conjunto de datos.map(preprocesar, núm_llamadas_paralelas=n_subprocesos_de_análisis)
conjunto de datos = conjunto de datos.shuffle(tamaño_de_buffer_shuffle).repeat(repetir)
devolver conjunto de datos.batch(tamaño_de_lote).prefetch(1)
Todo debería tener sentido en este código, excepto la última línea (prefetch(1)),
que es importante para el rendimiento.
Machine Translated by Google
Figura 13­2. Carga y preprocesamiento de datos desde varios archivos CSV
Precarga
Al llamar a prefetch(1) al final, estamos creando un conjunto de datos que hará todo lo
2
posible para estar siempre un lote por delante. En otras palabras, mientras nuestro
algoritmo de entrenamiento está trabajando en un lote, el conjunto de datos ya estará
trabajando en paralelo para preparar el siguiente lote (por ejemplo, leyendo los datos
del disco y preprocesándolos). Esto puede mejorar el rendimiento drásticamente, como
se ilustra en la Figura 13­3. Si también nos aseguramos de que la carga y el
preprocesamiento sean multiproceso (al configurar num_parallel_calls al llamar
a interleave() y map()), podemos aprovechar múltiples núcleos en la CPU y, con suerte,
hacer que la preparación de un lote de datos sea más corta que ejecutar un paso de
entrenamiento en la GPU: de esta manera, la GPU se utilizará casi al 100% (excepto
3
por el tiempo de transferencia de datos de la CPU a la GPU), y el entrenamiento se
ejecutará mucho más rápido.
Machine Translated by Google
Figura 13­3. Con la precarga, la CPU y la GPU trabajan en paralelo: mientras la GPU trabaja en un lote, la CPU
trabaja en el siguiente.
CONSEJO
Si piensas comprar una tarjeta GPU, su potencia de procesamiento y el tamaño de su memoria son,
por supuesto, muy importantes (en particular, una gran cantidad de RAM es crucial para la visión
artificial). Igual de importante para obtener un buen rendimiento es su ancho de banda de memoria;
es decir, la cantidad de gigabytes de datos que puede introducir o sacar de su RAM por segundo.
Si el conjunto de datos es lo suficientemente pequeño como para caber en la memoria, puede
acelerar significativamente el entrenamiento utilizando el método cache() del conjunto de
datos para almacenar en caché su contenido en la RAM. Por lo general, debe hacer esto
después de cargar y preprocesar los datos, pero antes de mezclar, repetir, agrupar y
precargar. De esta manera, cada instancia solo se leerá y preprocesará una vez (en lugar de una vez por
Machine Translated by Google
época), pero los datos se seguirán mezclando de forma diferente en cada época y el siguiente
lote se preparará con antelación.
Ahora sabe cómo crear canalizaciones de entrada eficientes para cargar y
preprocesar datos de varios archivos de texto. Hemos analizado los métodos de conjuntos
de datos más comunes, pero hay algunos más que quizás desee consultar: concatenate(), zip(),
window(), reduce(), shard(), flat_map() y padded_batch(). También hay un par de métodos de
clase más: from_generator() y from_tensors(), que crean un nuevo conjunto de datos a
partir de un generador de Python o una lista de tensores, respectivamente. Consulte la
documentación de la API para obtener más detalles. Tenga en cuenta también que hay funciones
experimentales disponibles en tf.data.experimental, muchas de las cuales probablemente llegarán
a la API principal en futuras versiones (por ejemplo, consulte la clase CsvDataset, así como el
método make_csv_dataset(), que se encarga de inferir el tipo de cada columna).
Uso del conjunto de datos con tf.keras
Ahora podemos usar la función csv_reader_dataset() para crear un conjunto de datos para el
conjunto de entrenamiento. Tenga en cuenta que no es necesario repetirlo, ya que tf.keras
se encargará de ello. También creamos conjuntos de datos para el conjunto de validación y el
conjunto de prueba:
conjunto_de_entrenamiento = csv_reader_dataset(rutas_de_archivos_de_entrenamiento)
conjunto_válido = csv_reader_dataset(rutas_de_archivos_válidas)
conjunto_de_prueba = csv_reader_dataset(rutas_de_archivos_de_prueba)
Y ahora podemos simplemente construir y entrenar un modelo Keras usando estos conjuntos
4
de datos. Todo lo que tenemos que hacer es pasar los conjuntos de datos de entrenamiento y validación a
5 el método fit(), en lugar de X_train, y_train, X_valid y y_valid:
modelo = keras.models.Sequential([...]) modelo.compile([...])
modelo.fit(conjunto_entrenamiento,
épocas=10, datos_validación=conjunto_válido)
Machine Translated by Google
De manera similar, podemos pasar un conjunto de datos a las funciones evaluar() y predecir().
métodos:
modelo.evaluar(conjunto_de_prueba)
tengo 3
nuevo_conjunto = conjunto_de_prueba.take(3).map(lambda X, y: X) # pretender
instancias
modelo.predict(nuevo_conjunto) # a conjunto de datos que contiene nuevas instancias
nosotros
nuevo
A diferencia de los otros conjuntos, el new_set normalmente no contendrá etiquetas (si
lo hace, Keras los ignorará). Tenga en cuenta que en todos estos casos, aún puede usar
Matrices NumPy en lugar de conjuntos de datos si lo desea (pero, por supuesto, deben
se han cargado y preprocesado primero).
Si desea crear su propio circuito de entrenamiento personalizado (como en el Capítulo 12),
puede simplemente iterar sobre el conjunto de entrenamiento, de forma muy natural:
Para X_batch, y_batch en train_set:
[...]
Realizar un paso de descenso de gradiente #
De hecho, incluso es posible crear una función TF (ver Capítulo 12) que
realiza todo el ciclo de entrenamiento:
@tf.función
def train(modelo, optimizador, función_pérdida, n_épocas, [...]):
conjunto_de_tren = conjunto_de_datos_del_lector_csv(rutas_de_archivos_de_tren, repetición=n_épocas,
[...])
Para X_batch, y_batch en train_set:
con tf.GradientTape() como cinta:
y_pred = modelo(X_lote)
pérdida_principal = tf.reduce_mean(función_pérdida(y_lote, y_pred))
pérdida = tf.add_n([pérdida_principal] + modelo.pérdidas)
grads = cinta.gradiente(pérdida, modelo.variables_entrenables)
optimizador.apply_gradients(zip(grads, modelo.variables_entrenables))
Felicitaciones, ahora sabes cómo crear canalizaciones de entrada potentes.
¡Usando la API de datos! Sin embargo, hasta ahora hemos utilizado archivos CSV, que son
común, simple y conveniente, pero no realmente eficiente, y no
Admite estructuras de datos grandes o complejas (como imágenes o audio).
Bueno, veamos cómo utilizar TFRecords.
Machine Translated by Google
CONSEJO
Si está satisfecho con los archivos CSV (o cualquier otro formato que esté utilizando), no tiene por
qué utilizar TFRecords. Como dice el dicho: si no está roto, no lo arregles. Los TFRecords son
útiles cuando el cuello de botella durante el entrenamiento es la carga y el análisis de los datos.
El formato TFRecord
El formato TFRecord es el formato preferido de TensorFlow para almacenar grandes
cantidades de datos y leerlos de manera eficiente. Es un formato binario muy simple que
solo contiene una secuencia de registros binarios de distintos tamaños (cada registro
se compone de una longitud, una suma de comprobación CRC para verificar que la
longitud no esté dañada, luego los datos reales y, finalmente, una suma de comprobación
CRC para los datos). Puede crear fácilmente un archivo TFRecord
utilizando la clase tf.io.TFRecordWriter:
con tf.io.TFRecordWriter("my_data.tfrecord") como f: f.write(b"Este es el primer
registro") f.write(b"Y este es el segundo registro")
Y luego puedes usar un tf.data.TFRecordDataset para leer uno o más archivos TFRecord:
filepaths = ["my_data.tfrecord"] dataset =
tf.data.TFRecordDataset(filepaths) para el elemento en el conjunto de
datos: print(item)
Esto dará como resultado:
tf.Tensor(b'Este es el primer registro', forma=(), dtype=string) tf.Tensor(b'Y este es el segundo
registro', forma=(), dtype=string)
Machine Translated by Google
CONSEJO
De forma predeterminada, un TFRecordDataset leerá los archivos uno por uno, pero puedes
hacer que lea varios archivos en paralelo e intercalar sus registros
configurando num_parallel_reads. Como alternativa, puedes obtener el mismo resultado
utilizando list_files() e interleave() como hicimos antes para leer varios archivos CSV.
Archivos TFRecord comprimidos A veces
puede resultar útil comprimir los archivos TFRecord, especialmente si es
necesario cargarlos a través de una conexión de red. Puede crear un
archivo TFRecord comprimido configurando el argumento de opciones:
opciones = tf.io.TFRecordOptions(compression_type="GZIP") con
tf.io.TFRecordWriter("my_compressed.tfrecord", opciones) como f:
[...]
Al leer un archivo TFRecord comprimido, debe especificar el tipo de compresión:
conjunto de datos = tf.data.TFRecordDataset(["my_compressed.tfrecord"],
tipo_de_compresión="GZIP")
Una breve introducción a los buffers de protocolo
Aunque cada registro puede utilizar cualquier formato binario que desee, los archivos
TFRecord generalmente contienen buffers de protocolo serializados (también llamados protobufs).
Este es un formato binario portátil, extensible y eficiente desarrollado en Google en 2001
y convertido en código abierto en 2008; los protobufs ahora se usan ampliamente, en
particular en gRPC, Sistema de llamada a procedimiento remoto de Google. Se
definen mediante un lenguaje sencillo que se parece al siguiente:
sintaxis = "proto3";
mensaje Persona {
cadena nombre = 1;
int32 id = 2;
cadena repetida correo electrónico = 3;
}
Machine Translated by Google
Esta definición dice que estamos usando la versión 3 del formato protobuf, y
6
especifica que cada objeto Persona puede
tener (opcionalmente) un nombre de tipo
cadena, un id de tipo int32 y cero o más campos de correo electrónico, cada uno de tipo
cadena. Los números 1, 2 y 3 son los identificadores de campo: se utilizarán
en la representación binaria de cada registro. Una vez que tenga una definición en un
Archivo .proto , puedes compilarlo. Para ello se necesita protoc, el protobuf
compilador, para generar clases de acceso en Python (o algún otro lenguaje).
Tenga en cuenta que las definiciones de protobuf que usaremos ya han sido compiladas.
para usted, y sus clases de Python son parte de TensorFlow, por lo que no necesitará
Necesitas usar protoc. Todo lo que necesitas saber es cómo usar el acceso protobuf.
Clases en Python. Para ilustrar los conceptos básicos, veamos un ejemplo simple.
que utiliza las clases de acceso generadas para el protobuf Persona (el código es
explicado en los comentarios):
#
>>> de person_pb2 importar Person >>> person =
Importar la clase de acceso generada
# crear
Person(nombre="Al", id=123, email=["a@b.com"])
a
Persona
>>> imprimir nombre de
la persona : "Al"
Persona
mostrar el #
identificación: 123
Correo electrónico: "a@b.com"
>>> nombre.persona "Al"
#
a campo
leer
>>> persona.nombre = "Alice" >>>
persona.correo electrónico[0]
#
#
a campo
modificar
se puede acceder como
campos repetidos pueden
matrices
"a@b.com"
#
>>> persona.email.append("c@d.com") >>> s =
Agregar dirección
un
de correo electrónico
serializar el objeto #
persona.SerializeToString()
a
a
byte
cadena
>>> a
b'\n\x05Alice\x10{\x1a\x07a@b.com\x1a\x07c@d.com'
# crear
a
>>> persona2 = Persona() >>>
#
persona2.ParseFromString(s) 27
>>> persona == persona2
#
Nueva persona
analizar gramaticalmente
el
cadena de bytes (27 bytes de longitud)
ahora Son iguales
Verdadero
En resumen, importamos la clase Persona generada por protoc, creamos una
instancia y jugar con ella, visualizándola y leyendo y escribiendo algo
Machine Translated by Google
campos, luego los serializamos usando el método SerializeToString(). Estos son los datos binarios
que están listos para ser guardados o transmitidos a través de la red.
Al leer o recibir estos datos binarios, podemos analizarlos utilizando el método ParseFromString() y obtenemos
una copia del objeto que fue serializado .
Podríamos guardar el objeto Person serializado en un archivo TFRecord, luego podríamos
cargarlo y analizarlo: todo funcionaría bien. Sin embargo, SerializeToString() y
ParseFromString() no son operaciones de TensorFlow (y tampoco lo son las otras operaciones
en este código), por lo que no se pueden incluir en una función de TensorFlow (excepto
envolviéndolas en una operación tf.py_function(), lo que haría que el código fuera más lento y menos
portátil, como vimos en el Capítulo 12). Afortunadamente, TensorFlow incluye definiciones
especiales de protobuf para las cuales proporciona operaciones de análisis.
Buffers de prototipos de TensorFlow
El protobuf principal que se suele utilizar en un archivo TFRecord es el protobuf Example,
que representa una instancia en un conjunto de datos. Contiene una lista de características con
nombre, donde cada característica puede ser una lista de cadenas de bytes, una lista de números
flotantes o una lista de números enteros. A continuación se muestra la definición del protobuf:
sintaxis = "proto3";
mensaje BytesList { valor de bytes repetidos = 1; } mensaje
FloatList { valor de punto flotante repetido = 1 [empaquetado = verdadero]; } mensaje
Int64List { valor int64 repetido = 1 [empaquetado = verdadero]; } mensaje Feature
{ único {
Lista de bytes lista_de_bytes = 1;
Lista flotante lista_flotante = 2;
Lista Int64 lista int64 = 3;
}
};
mensaje Características { map<string, Característica> característica =
1; }; mensaje Ejemplo { Características características = 1; };
Machine Translated by Google
Las definiciones de BytesList, FloatList e Int64List son bastante sencillas.
Tenga en cuenta que [packed = true] se utiliza para campos numéricos repetidos, para
una codificación más eficiente. Una característica contiene una BytesList, una FloatList o
una Int64List. Una característica (con una s) contiene un diccionario que asigna un
nombre de característica al valor de característica correspondiente. Y, por último,
un ejemplo contiene solo un objeto de características.
8
A continuación se muestra cómo puede crear un tf.train.Example que represente a la
misma persona que antes y escribirlo en un archivo TFRecord:
de tensorflow.train import BytesList, FloatList, Int64List de tensorflow.train
import Característica, Características, Ejemplo
persona_ejemplo =
Ejemplo( características=Características( característica={ "nombre":
Característica(bytes_list=BytesList(valor=[b"Alice"])), "id":
Característica(int64_list=Int64List(valor=[123])), "correos electrónicos":
Característica(bytes_list=BytesList(val
}))
El código es un poco verboso y repetitivo, pero es bastante sencillo (y se puede incluir
fácilmente dentro de una pequeña función auxiliar). Ahora que tenemos un protobuf de
ejemplo, podemos serializarlo llamando a su método
SerializeToString() y luego escribir los datos resultantes en un archivo TFRecord:
con tf.io.TFRecordWriter("mis_contactos.tfrecord") como f:
f.write(persona_ejemplo.SerializeToString())
Normalmente, escribirías mucho más de un ejemplo. Normalmente, crearías un script
de conversión que lee desde tu formato actual (por ejemplo, archivos CSV), crea un
protobuf de ejemplo para cada instancia, los serializa y los guarda en varios archivos
TFRecord, idealmente mezclándolos en el proceso. Esto requiere un poco de trabajo,
así que, una vez más, asegúrate de que sea realmente necesario (quizás tu canalización
funcione bien con archivos CSV).
Machine Translated by Google
Ahora que tenemos un buen archivo TFRecord que contiene un ejemplo serializado,
intentemos cargarlo.
Ejemplos de carga y análisis
Para cargar los protobufs serializados de Example, utilizaremos un
tf.data.TFRecordDataset una vez más y analizaremos cada Example utilizando
tf.io.parse_single_example(). Esta es una operación de TensorFlow, por lo que se puede
incluir en una función TF. Requiere al menos dos argumentos: un tensor escalar de cadena
que contenga los datos serializados y una descripción de cada característica. La
descripción es un diccionario que asigna cada nombre de característica a un descriptor
tf.io.FixedLenFeature que indica la forma, el tipo y el valor predeterminado de la
característica, o a un descriptor tf.io.VarLenFeature que indica solo el tipo (si la longitud
de la lista de características puede variar, como en el caso de la característica "emails").
El siguiente código define un diccionario de descripción, luego itera sobre TFRecordDataset
y analiza el protobuf de ejemplo serializado que contiene este conjunto de datos:
descripción_de_características = {
"nombre": tf.io.FixedLenFeature([], tf.string, valor_predeterminado=""), "id":
tf.io.FixedLenFeature([], tf.int64, valor_predeterminado=0), "correos
electrónicos": tf.io.VarLenFeature(tf.string),
}
para serialized_example en
tf.data.TFRecordDataset(["my_contacts.tfrecord"]):
ejemplo_analizado = tf.io.parse_single_example(ejemplo_serializado,
descripción de la característica)
Las características de longitud fija se analizan como tensores regulares, pero las
características de longitud variable se analizan como tensores dispersos. Puede
convertir un tensor disperso en un tensor denso utilizando tf.sparse.to_dense(), pero en este
caso es más sencillo simplemente acceder a sus valores:
Machine Translated by Google
>>> tf.sparse.to_dense(parsed_example["correos electrónicos"], valor_predeterminado=b"")
<tf.Tensor: [...] dtype=string, numpy=array([b'a@b.com', b'c@d.com'], [...])> >>>
parsed_example["correos electrónicos"].values
<tf.Tensor: [...] dtype=string, numpy=array([b'a@b.com', b'c@d.com'], [...])>
Una BytesList puede contener cualquier dato binario que desees, incluido
cualquier objeto serializado. Por ejemplo, puedes usar tf.io.encode_jpeg() para
codificar una imagen usando el formato JPEG y poner estos datos binarios en
una BytesList. Más tarde, cuando tu código lea el TFRecord, comenzará por
analizar el Example, luego necesitará llamar a tf.io.decode_jpeg() para analizar los
datos y obtener la imagen original (o puedes usar
tf.io.decode_image(), que puede decodificar cualquier imagen BMP, GIF, JPEG o
PNG). También puedes almacenar cualquier tensor que quieras en una
BytesList serializando el tensor usando tf.io.serialize_tensor() y luego poniendo la
cadena de bytes resultante en una función BytesList. Más tarde, cuando analices
el TFRecord, puedes analizar estos datos usando tf.io.parse_tensor().
En lugar de analizar los ejemplos uno por uno usando
tf.io.parse_single_example(), es posible que desees analizarlos lote por lote usando
tf.io.parse_example():
conjunto de datos = tf.data.TFRecordDataset(["my_contacts.tfrecord"]).batch(10) para
ejemplos serializados en el conjunto de datos:
ejemplos_analizados = tf.io.parse_example(ejemplos_serializados,
descripción_de_funciones)
Como puede ver, el ejemplo protobuf probablemente será suficiente para la mayoría
de los casos de uso. Sin embargo, puede ser un poco complicado de usar
cuando se trabaja con listas de listas. Por ejemplo, suponga que desea clasificar
documentos de texto. Cada documento puede representarse como una lista de
oraciones, donde cada oración se representa como una lista de palabras. Y quizás
cada documento también tenga una lista de comentarios, donde cada comentario se
representa como una lista de palabras. También puede haber algunos datos contextuales, como la
Machine Translated by Google
autor, título y fecha de publicación del documento. El protobuf
SequenceExample de TensorFlow está diseñado para tales casos de uso.
Manejo de listas de listas mediante SequenceExample
Buf prototípico
Aquí está la definición del protobuf SequenceExample:
mensaje FeatureList { Característica repetida feature = 1; }; mensaje
FeatureLists { mapa<string, FeatureList> feature_list = 1; }; mensaje SequenceExample {
Características contexto = 1;
Listas de funciones feature_lists = 2;
};
Un SequenceExample contiene un objeto Features para los datos contextuales y
un objeto FeatureLists que contiene uno o más objetos FeatureList con nombre (por
ejemplo, una FeatureList llamada "contenido" y otra llamada "comentarios").
Cada FeatureList contiene una lista de objetos Feature, cada uno de los cuales
puede ser una lista de cadenas de bytes, una lista de enteros de 64 bits o una lista
de flotantes (en este ejemplo, cada Feature representaría una oración o un
comentario, quizás en forma de una lista de identificadores de palabras). Crear
un SequenceExample, serializarlo y analizarlo es similar a crear, serializar y
analizar un Example, pero debe usar
tf.io.parse_single_sequence_example() para analizar un solo
SequenceExample o tf.io.parse_sequence_example() para analizar un lote.
Ambas funciones devuelven una tupla que contiene las características del contexto
(como un diccionario) y las listas de características (también como un diccionario).
Si las listas de características contienen secuencias de distintos tamaños (como en el
ejemplo anterior), es posible que desee convertirlas
en tensores irregulares, utilizando tf.RaggedTensor.from_sparse() (consulte el cuaderno para ver el có
contexto_analizado, listas_de_características_analizadas
=
tf.io.parse_single_sequence_example( ejemplo_de_secuencia_serializada,
descripciones_de_características_de_contexto, descripciones_de_características_de_secuencia)
Machine Translated by Google
contenido analizado
= tf.RaggedTensor.from_sparse(listas_de_características_analizadas["contenido"])
Ahora que sabe cómo almacenar, cargar y analizar datos de manera eficiente, el siguiente
paso es prepararlos para poder alimentarlos a una red neuronal.
Preprocesamiento de las características de entrada La preparación
de los datos para una red neuronal requiere convertir todas las características en
características numéricas, por lo general normalizarlas, y más. En particular, si
los datos contienen características categóricas o características de texto, deben convertirse
en números. Esto se puede hacer con anticipación al preparar los archivos de datos,
utilizando cualquier herramienta que desee (por ejemplo, NumPy, pandas o Scikit­Learn).
Alternativamente, puede preprocesar sus datos sobre la marcha al cargarlos con la API de
datos (por ejemplo, utilizando el método map() del conjunto de datos, como vimos
anteriormente), o puede incluir una capa de preprocesamiento directamente en su
modelo. Veamos ahora esta última opción.
Por ejemplo, aquí se muestra cómo se puede implementar una capa de estandarización
utilizando una capa Lambda. Para cada característica, se resta la media y se divide por
su desviación estándar (más un pequeño término de suavizado para evitar la división por cero):
media = np.media(X_train, eje=0, keepdims=True) stds =
np.std(X_train, eje=0, keepdims=True) eps =
keras.backend.epsilon() modelo =
keras.models.Sequential([
keras.layers.Lambda( entradas lambda : (entradas ­ medias) / (stds + eps)), [...]
otras capas #
])
¡No es demasiado difícil! Sin embargo, es posible que prefieras usar una capa personalizada
autónoma y agradable (como StandardScaler de Scikit­Learn), en lugar de tener
variables globales como medias y valores estándar dando vueltas:
clase Estandarización(keras.layers.Layer): def adapt(self,
data_sample):
self.means_ = np.mean(muestra_de_datos, eje=0, keepdims=True) self.stds_
= np.std(muestra_de_datos, eje=0, keepdims=True)
Machine Translated by Google
_
_ pag ,
( def llamada (self, entradas):
devuelve (entradas ­ self.means_) / (self.stds_ +
keras.backend.epsilon())
pag
,
pag
)
Antes de poder utilizar esta capa de estandarización, deberá adaptarla a
su conjunto de datos llamando al método adapt() y pasándole una muestra de datos.
Esto le permitirá utilizar la media y la desviación estándar adecuadas para
Cada característica:
std_layer = Estandarización()
std_layer.adapt(muestra_de_datos)
Esta muestra debe ser lo suficientemente grande para ser representativa de su conjunto de datos, pero
No tiene por qué ser el conjunto de entrenamiento completo: en general, unos pocos cientos
Bastarán instancias seleccionadas al azar (sin embargo, esto depende de su
tarea). A continuación, puede utilizar esta capa de preprocesamiento como una capa normal:
modelo = keras.Sequential()
modelo.add(capa_estándar)
# crear
el
descansar del modelo
[...]
modelo.compilar([...])
modelo.fit([...])
Si estás pensando que Keras debería contener una capa de estandarización como
Este, aquí hay una buena noticia para ti: cuando leas esto, el
La capa keras.layers.Normalization probablemente estará disponible.
funcionan de manera muy similar a nuestra capa de estandarización personalizada: primero, cree el
capa, luego adáptela a su conjunto de datos pasando una muestra de datos a adapt()
método y finalmente use la capa normalmente.
Ahora veamos las características categóricas. Comenzaremos codificándolas como
vectores one­hot.
Codificación de características categóricas mediante vectores one­hot
Considere la función ocean_proximity en el conjunto de datos de vivienda de California
Exploramos en el Capítulo 2: es una característica categórica con cinco posibles
Machine Translated by Google
valores: "<1H OCÉANO", "INTERIOR", "CERCA DEL OCÉANO", "CERCA DE LA BAHÍA" y
"ISLA". Necesitamos codificar esta característica antes de introducirla en un sistema neuronal.
red. Dado que hay muy pocas categorías, podemos utilizar codificación one­hot.
Para ello, primero debemos asignar cada categoría a su índice (0 a 4), lo que se puede hacer mediante
una tabla de búsqueda:
vocabulario = ["<1H OCÉANO", "INTERIOR", "CERCA DEL OCÉANO", "CERCA DE LA BAHÍA", "ISLA"]
índices = tf.range(len(vocab), dtype=tf.int64) inicio_tabla =
tf.lookup.KeyValueTensorInitializer(vocab, índices) num_oov_buckets = 2 tabla =
tf.lookup.StaticVocabularyTable(inicio_tabla, num_oov_buckets)
Repasemos este código:
Primero definimos el vocabulario: esta es la lista de todas las categorías posibles.
Luego creamos un tensor con los índices correspondientes (0 a 4).
A continuación, creamos un inicializador para la tabla de búsqueda, pasándole la lista
de categorías y sus índices correspondientes. En este ejemplo, ya tenemos estos datos,
por lo que utilizamos un KeyValueTensorInitializer;
pero si las categorías estuvieran listadas en un archivo de texto (con una categoría por
línea), utilizaríamos un TextFileInitializer en su lugar.
En las dos últimas líneas creamos la tabla de búsqueda, le damos el inicializador
y especificamos el número de categorías fuera del vocabulario (oov). Si buscamos una
categoría que no existe en el vocabulario, la tabla de búsqueda calculará un
hash de esta categoría y lo utilizará para asignar la categoría desconocida a una de las
categorías oov. Sus índices comienzan después de las categorías conocidas,
por lo que en este ejemplo los índices de las dos categorías oov son 5 y 6.
¿Por qué utilizar contenedores oov? Bueno, si la cantidad de categorías es grande (por ejemplo,
códigos postales, ciudades, palabras, productos o usuarios) y el conjunto de datos también es
grande, o cambia constantemente, entonces obtener la lista completa de categorías puede no ser
Machine Translated by Google
Conveniente. Una solución es definir el vocabulario en función de una muestra de
datos (en lugar de todo el conjunto de entrenamiento) y agregar algunos contenedores
oov para las otras categorías que no estaban en la muestra de datos. Cuantas más
categorías desconocidas espere encontrar durante el entrenamiento, más contenedores
oov debería usar. De hecho, si no hay suficientes contenedores oov, habrá colisiones:
diferentes categorías terminarán en el mismo contenedor, por lo que la red neuronal
no podrá distinguirlas (al menos no en función de esta característica).
Ahora usemos la tabla de búsqueda para codificar un pequeño lote de características
categóricas en vectores one­hot:
>>> categorías = tf.constant(["CERCA DE LA BAHÍA", "DESIERTO", "TIERRA INTERIOR", "TIERRA INTERIOR"])
>>> índices_gato = tabla.lookup(categorías) >>> índices_gato
<tf.Tensor: id=514,
forma=(4,), tipo_d=int64, numpy=array([3, 5, 1, 1])> >>> gato_one_hot = tf.one_hot(índices_gato,
profundidad=len(vocab) + num_oov_buckets) >>> gato_one_hot <tf.Tensor: id=524, forma=(4,
7), tipo_d=float32,
numpy= array([[0., 0.,
0., 1., 0., 0., 0.], [0., 0., 0., 0., 0., 1., 0.], [0., 1., 0., 0., 0., 0., 0.], [0., 1., 0., 0., 0., 0., 0.]],
dtype=float32)>
Como puede ver, "NEAR BAY" se asignó al índice 3, la categoría desconocida
"DESERT" se asignó a uno de los dos contenedores oov (en el índice 5) y "INLAND" se
asignó al índice 1, dos veces. Luego, usamos tf.one_hot() para codificar one­hot estos
índices. Observe que tenemos que indicarle a esta función el número total de índices,
que es igual al tamaño del vocabulario más el número de contenedores oov. ¡Ahora sabe
cómo codificar características categóricas en vectores one­hot usando TensorFlow!
Al igual que antes, no sería demasiado difícil agrupar toda esta lógica en una bonita clase
autónoma. Su método adapt() tomaría una muestra de datos y extraería todas las
categorías distintas que contiene. Crearía una tabla de búsqueda para asignar cada
categoría a su índice (incluidas las categorías desconocidas que utilizan contenedores
oov). Luego, su método call() usaría la tabla de búsqueda para
Machine Translated by Google
mapear las categorías de entrada a sus índices. Bueno, aquí hay más buenas noticias:
cuando leas esto, Keras probablemente incluirá una capa llamada
keras.layers.TextVectorization, que será capaz de hacer exactamente eso: su
método adapt() extraerá el vocabulario de una muestra de datos, y su método call()
convertirá cada categoría a su índice en el vocabulario. Podrías agregar esta capa
al comienzo de tu modelo, seguida de una capa Lambda que aplicaría la función
tf.one_hot(), si quieres convertir estos índices en vectores one­hot.
Sin embargo, puede que esta no sea la mejor solución. El tamaño de cada vector one­
hot es la longitud del vocabulario más la cantidad de contenedores oov. Esto está bien
cuando solo hay unas pocas categorías posibles, pero si el vocabulario es grande, es
mucho más eficiente codificarlas usando incrustaciones .
CONSEJO
Como regla general, si la cantidad de categorías es inferior a 10, la codificación one­
hot suele ser la mejor opción (¡pero los resultados pueden variar!). Si la cantidad de
categorías es superior a 50 (que suele ser el caso cuando se utilizan contenedores hash),
las incrustaciones suelen ser preferibles. Entre 10 y 50 categorías, es posible que desee
experimentar con ambas opciones y ver cuál funciona mejor para su uso.
caso.
Codificación de características categóricas mediante incrustaciones Una
incrustación es un vector denso entrenable que representa una categoría. De forma
predeterminada, las incrustaciones se inicializan de forma aleatoria, por lo que, por
ejemplo, la categoría "CERCA DE LA BAHÍA" podría representarse inicialmente
mediante un vector aleatorio como [0,131, 0,890], mientras que la categoría "CERCA
DEL OCÉANO" podría representarse mediante otro vector aleatorio como [0,631,
0,791]. En este ejemplo, utilizamos incrustaciones 2D, pero la cantidad de
dimensiones es un hiperparámetro que puede ajustar. Dado que estas incrustaciones
son entrenables, mejorarán gradualmente durante el entrenamiento; y como
representan categorías bastante similares, el Descenso de gradiente seguramente
terminará acercándolas, mientras que tenderá a alejarlas de la incrustación de la categoría "TIERRA IN
Machine Translated by Google
(ver Figura 13­4). De hecho, cuanto mejor sea la representación, más fácil será para la
red neuronal hacer predicciones precisas, por lo que el entrenamiento tiende a hacer
que las incrustaciones sean representaciones útiles de las categorías. Esto se llama
aprendizaje de representación (veremos otros tipos de aprendizaje de representación
en el Capítulo 17).
Figura 13­4. Las incrustaciones mejorarán gradualmente durante el entrenamiento.
Machine Translated by Google
INCRUSTACIONES DE PALABRAS
Las incrustaciones no solo serán representaciones útiles para la tarea en cuestión, sino que,
con mucha frecuencia, se pueden reutilizar con éxito para otras tareas. El ejemplo más
común de esto son las incrustaciones de palabras (es decir, incrustaciones de palabras
individuales): cuando se trabaja en una tarea de procesamiento del lenguaje natural, a menudo
es mejor reutilizar incrustaciones de palabras previamente entrenadas que entrenar las propias.
La idea de utilizar vectores para representar palabras se remonta a la década de 1960, y se
han utilizado muchas técnicas sofisticadas para generar vectores útiles, incluido el uso de
redes neuronales. Pero las cosas realmente despegaron en 2013, cuando Tomáš Mikolov y
otros investigadores de Google publicaron un artículo describiendo una técnica eficiente para
9 incrustaciones de palabras utilizando redes neuronales, superando
aprender
significativamente los intentos anteriores.
Esto les permitió aprender incrustaciones en un corpus de texto muy grande: entrenaron una
red neuronal para predecir las palabras cercanas a cualquier palabra dada y obtuvieron
incrustaciones de palabras asombrosas. Por ejemplo, los sinónimos tenían
incrustaciones muy cercanas y palabras semánticamente relacionadas como Francia, España
e Italia terminaron agrupadas.
Sin embargo, no se trata solo de proximidad: las incrustaciones de palabras también se
organizaron a lo largo de ejes significativos en el espacio de incrustación. He aquí un
ejemplo famoso: si se calcula King – Man + Woman (sumando y restando los vectores de
incrustación de estas palabras), el resultado será muy cercano a la incrustación de la palabra
Queen (ver Figura 13­5).
En otras palabras, ¡la palabra incrustaciones codifica el concepto de género!
De manera similar, se puede calcular Madrid – España + Francia, y el resultado es cercano a
París, lo que parece mostrar que la noción de ciudad capital también estaba codificada en las
incrustaciones.
Machine Translated by Google
Figura 13­5. Las incrustaciones de palabras similares tienden a estar cerca y algunos ejes parecen
codificar conceptos significativos.
Desafortunadamente, las incrustaciones de palabras a veces capturan nuestros peores sesgos.
Por ejemplo, aunque aprenden correctamente que el hombre es al rey lo que la mujer es
a la reina, también parecen aprender que el hombre es al médico lo que la mujer es a la
enfermera: ¡un sesgo bastante sexista! Para ser justos, este ejemplo en particular
probablemente sea exagerado, como se señaló en un artículo de 2019. por Malvina Nissim et
al. Sin embargo, garantizar la equidad en los algoritmos de aprendizaje profundo es un
tema de investigación importante y activo.
Veamos cómo podríamos implementar incrustaciones manualmente, para entender
cómo funcionan (luego usaremos una capa Keras simple en su lugar).
Primero, necesitamos crear una matriz de incrustación que contenga la incrustación de cada
categoría, inicializada aleatoriamente; tendrá una fila por categoría y por contenedor oov, y una
columna por dimensión de incrustación:
incrustación_dim = 2
incrustación_init = tf.random.uniform([len(vocab) + num_oov_buckets, incrustación_dim])
incrustación_matriz
= tf.Variable(incrustación_init)
10
Machine Translated by Google
En este ejemplo, utilizamos incrustaciones 2D, pero como regla general, las incrustaciones
suelen tener entre 10 y 300 dimensiones, según la tarea y el tamaño del vocabulario (tendrá
que ajustar este hiperparámetro).
Esta matriz de inserción es una matriz aleatoria de 6 × 2, almacenada en una variable (por lo que
puede modificarse mediante Gradient Descent durante el entrenamiento):
>>> matriz_incrustada
<tf.Variable 'Variable:0' forma=(6, 2) dtype=float32, numpy= matriz([[0.6645621 , 0.44100678],
[0.3528825 , 0.46448255], [0.03366041,
0.68467236], [0.74011743, 0.8724445 ],
[0.22632635, 0.22319686], [0.3103881 ,
0.7223358 ]], dtype=float32)>
Ahora vamos a codificar el mismo lote de características categóricas que antes, pero esta vez
utilizando estas incrustaciones:
>>> categorías = tf.constant(["CERCA DE LA BAHÍA", "DESIERTO", "TIERRA INTERIOR", "TIERRA INTERIOR"])
>>> índices_cat = tabla.lookup(categorías) >>> índices_cat
<tf.Tensor: id=741,
forma=(4,), tipo_d=int64, numpy=array([3, 5, 1, 1])> >>> tf.nn.incrustación_lookup(matriz_incrustación,
índices_cat) <tf.Tensor: id=864, forma=(4, 2), tipo_d=float32, numpy= array([[0.74011743,
0.8724445 ], [0.3103881 , 0.7223358 ], [0.3528825 , 0,46448255], [0,3528825,
0,46448255]], tipo de datos=float32)>
La función tf.nn.embedding_lookup() busca las filas en la matriz de incrustación, en los
índices dados; eso es todo lo que hace. Por ejemplo, la tabla de búsqueda indica que la
categoría "INLAND" está en el índice 1, por lo que la función tf.nn.embedding_lookup()
devuelve la incrustación en la fila 1 de la matriz de incrustación (dos veces): [0,3528825,
0,46448255].
Keras proporciona una capa keras.layers.Embedding que maneja la matriz de incrustación
(entrenable, por defecto); cuando se crea la capa, inicializa la matriz de incrustación
aleatoriamente y luego, cuando se la llama con
Machine Translated by Google
algunos índices de categoría devuelve las filas en esos índices en la matriz
de inserción:
>>> incrustación = keras.capas.Embedding(input_dim=len(vocab) +
num_oov_buckets,
...
salida_dim=dim_incrustación)
...
>>> incrustación(cat_indices)
<tf.Tensor: id=814, forma=(4, 2), dtype=float32, numpy=
array([[ 0.02401174, 0.03724445],
[­0.01896119, 0.02223358],
[­0.01471175, ­0.00355174],
[­0.01471175, ­0.00355174]], dtype=float32)>
Juntando todo, ahora podemos crear un modelo Keras que puede procesar
características categóricas (junto con características numéricas regulares) y aprender
una incrustación para cada categoría (así como para cada categoría oov):
entradas_regulares = keras.capas.Entrada(forma=[8])
categorías = keras.capas.Entrada(forma=[], dtype=tf.string) índices_gato =
keras.capas.Lambda(lambda gatos: tabla.lookup(gatos)) (categorías) inserción_gato
=
keras.capas.Incrustación(dim_entrada=6, dim_salida=2) (índices_gato)
entradas_codificadas = keras.capas.concatenar([entradas_regulares, inserción_gato])
salidas = keras.capas.Dense(1)(entradas_codificadas)
modelo = keras.modelos.Modelo(entradas=[entradas_regulares, categorías],
salidas=[salidas])
Este modelo toma dos entradas: una entrada regular que contiene ocho
características numéricas por instancia, más una entrada categórica (que contiene
una característica categórica por instancia). Utiliza una capa Lambda para buscar
el índice de cada categoría, luego busca las incrustaciones para estos
índices. A continuación, concatena las incrustaciones y las entradas regulares para
proporcionar las entradas codificadas, que están listas para ser alimentadas a una
red neuronal. Podríamos agregar cualquier tipo de red neuronal en este punto, pero solo
agregamos una capa de salida densa y creamos el modelo Keras.
Cuando la capa keras.layers.TextVectorization está disponible, puedes llamar a su
método adapt() para que extraiga el vocabulario de un dato.
Machine Translated by Google
ejemplo (se encargará de crear la tabla de búsqueda por usted). Luego, puede
agregarlo a su modelo y realizará la búsqueda de índice (reemplazando la capa Lambda
en el ejemplo de código anterior).
NOTA
La codificación one­hot seguida de una capa densa (sin función de activación ni sesgos)
es equivalente a una capa de incrustación. Sin embargo, la capa de incrustación utiliza
muchos menos cálculos (la diferencia de rendimiento se hace evidente cuando aumenta el
tamaño de la matriz de incrustación). La matriz de peso de la capa densa desempeña el
papel de la matriz de incrustación. Por ejemplo, utilizar vectores one­hot de tamaño 20 y una
capa densa con 10 unidades es equivalente a utilizar una capa de incrustación con
input_dim=20 y output_dim=10. Como resultado, sería un desperdicio utilizar más
dimensiones de incrustación que la cantidad de unidades en la capa que sigue a la capa de incrustación.
Ahora veamos un poco más de cerca las capas de preprocesamiento de Keras.
Capas de preprocesamiento de Keras El
equipo de TensorFlow está trabajando para proporcionar un conjunto de capas de
preprocesamiento de Keras estándar. Probablemente estén disponibles cuando lea esto;
sin embargo, la API puede cambiar ligeramente para entonces, así que consulte el
cuaderno de notas de este capítulo si algo se comporta de manera inesperada. Esta
nueva API probablemente reemplazará a la API de columnas de funciones existente, que
es más difícil de usar y menos intuitiva (si desea obtener más información
sobre la API de columnas de funciones de todos modos, consulte el cuaderno de notas de este capítulo).
Ya hemos hablado de dos de estas capas: la capa
keras.layers.Normalization, que se encargará de la estandarización de las
características (será equivalente a la capa de estandarización que definimos antes),
y la capa TextVectorization, que será capaz de codificar cada palabra de las entradas en
su índice en el vocabulario. En ambos casos, se crea la capa, se llama a su método
adapt() con una muestra de datos y luego se utiliza la capa normalmente en el
modelo. Las otras capas de preprocesamiento seguirán el mismo patrón.
Machine Translated by Google
La API también incluirá una capa keras.layers.Discretization que dividirá los datos
continuos en diferentes categorías y codificará cada una de ellas como un vector one­hot.
Por ejemplo, podría utilizarla para discretizar los precios en tres categorías (bajo,
medio, alto), que se codificarían como [1, 0, 0], [0, 1, 0] y [0, 0, 1], respectivamente. Por
supuesto, esto pierde mucha información, pero en algunos casos puede ayudar al
modelo a detectar patrones que de otro modo no serían obvios al observar solo
los valores continuos.
ADVERTENCIA
La capa de discretización no será diferenciable y solo se debe utilizar al comienzo del modelo.
De hecho, las capas de preprocesamiento del modelo se congelarán durante el
entrenamiento, por lo que sus parámetros no se verán afectados por el descenso de gradiente
y, por lo tanto, no es necesario que sean diferenciables. Esto también significa que no debe
utilizar una capa de incrustación directamente en una capa de preprocesamiento personalizada
si desea que sea entrenable: en su lugar, debe agregarse por separado a su modelo, como
en el ejemplo de código anterior.
También será posible encadenar varias capas de preprocesamiento utilizando la clase
PreprocessingStage. Por ejemplo, el código siguiente creará una secuencia de
preprocesamiento que primero normalizará las entradas y luego las discretizará (esto
puede recordarle a las secuencias de Scikit­Learn). Después de adaptar esta secuencia a
una muestra de datos, puede usarla como una capa normal en sus modelos (pero
nuevamente, solo al comienzo del modelo, ya que contiene una capa de
preprocesamiento no diferenciable):
normalización = keras.layers.Normalization() discretización =
keras.layers.Discretization([...]) pipeline =
keras.layers.PreprocessingStage([normalización, discretización])
pipeline.adapt(muestra_de_datos)
La capa TextVectorization también tendrá una opción para generar vectores de
recuento de palabras en lugar de índices de palabras. Por ejemplo, si el vocabulario
contiene tres palabras, digamos ["y", "baloncesto", "más"], entonces la capa TextVectorization
Machine Translated by Google
El texto "more and more" se mapeará al vector [1, 0, 2]: la palabra "and" aparece una vez, la
palabra "basketball" no aparece en absoluto y la palabra "more" aparece dos veces. Esta
representación de texto se llama bolsa de palabras, ya que pierde completamente el orden de
las palabras. Las palabras comunes como "and" tendrán un gran valor en la mayoría de los
textos, aunque normalmente sean las menos interesantes (por ejemplo, en el texto "more
and more basketball" la palabra "basketball" es claramente la más importante,
precisamente porque no es una palabra muy frecuente). Por lo tanto, los recuentos de
palabras deben normalizarse de una manera que reduzca la importancia de las palabras
frecuentes. Una forma común de hacer esto es dividir cada recuento de palabras por el
logaritmo del número total de instancias de entrenamiento en las que aparece la palabra. Esta
técnica se llama frecuencia de término × frecuencia de documento inverso (TF­IDF). Por
ejemplo, imaginemos que las palabras "y", "baloncesto" y "más" aparecen respectivamente
en 200, 10 y 100 instancias de texto en el conjunto de entrenamiento: en este caso, el vector
final será [1/log(200), 0/log(10), 2/log(100)], que es aproximadamente igual a [0,19, 0,
0,43]. La capa TextVectorization tendrá (probablemente) una opción para realizar
TF­IDF.
NOTA
Si las capas de preprocesamiento estándar no son suficientes para su tarea, aún tendrá la opción de
crear su propia capa de preprocesamiento personalizada, de manera similar a lo que hicimos
anteriormente con la clase Standardization. Cree una subclase de la
Clase keras.layers.PreprocessingLayer con un método adapt(), que debe tomar un argumento
data_sample y opcionalmente un argumento reset_state adicional: si es Verdadero, entonces
el método adapt() debe restablecer cualquier estado existente antes de calcular el nuevo estado; si
es Falso, debe intentar actualizar el estado existente.
Como puede ver, estas capas de preprocesamiento de Keras harán que el preprocesamiento
sea mucho más fácil. Ahora bien, ya sea que elija escribir sus propias capas de
preprocesamiento o usar las de Keras (o incluso usar la API de columnas de funciones),
todo el preprocesamiento se realizará sobre la marcha. Sin embargo, durante el entrenamiento, puede ser
Machine Translated by Google
Es preferible realizar el preprocesamiento con anticipación. Veamos por qué querríamos
hacerlo y cómo lo haríamos.
Transformación TF
Si el preprocesamiento es costoso en términos computacionales, entonces manejarlo antes
del entrenamiento en lugar de sobre la marcha puede brindarle una aceleración significativa: los
datos se preprocesarán solo una vez por instancia antes del entrenamiento, en lugar de una
vez por instancia y por época durante el entrenamiento. Como se mencionó anteriormente, si el
conjunto de datos es lo suficientemente pequeño como para caber en la RAM, puede
usar su método cache(). Pero si es demasiado grande, entonces herramientas como Apache
Beam o Spark le ayudarán. Le permiten ejecutar canalizaciones de procesamiento de datos
eficientes sobre grandes cantidades de datos, incluso distribuidos en varios servidores, para que
pueda usarlas para preprocesar todos los datos de entrenamiento antes del entrenamiento.
Esto funciona muy bien y, de hecho, puede acelerar el entrenamiento, pero hay un
problema: una vez que se entrena el modelo, supongamos que desea implementarlo en una
aplicación móvil. En ese caso, deberá escribir algún código en su aplicación para encargarse del
preprocesamiento de los datos antes de que se introduzcan en el modelo. ¿Y supongamos
que también desea implementar el modelo en TensorFlow.js para que se ejecute en un navegador
web? Una vez más, deberá escribir algún código de preprocesamiento. Esto puede convertirse en
una pesadilla de mantenimiento: cada vez que desee cambiar la lógica de preprocesamiento,
deberá actualizar su código Apache Beam, el código de su aplicación móvil y su código
JavaScript. Esto no solo consume mucho tiempo, sino que también es propenso a errores: puede
terminar con diferencias sutiles entre las operaciones de preprocesamiento realizadas antes del
entrenamiento y las realizadas en su aplicación o en el navegador. Esta desviación entre
el entrenamiento y el servicio generará errores o un rendimiento degradado.
Una mejora sería tomar el modelo entrenado (entrenado con datos que fueron preprocesados por
su código Apache Beam o Spark) y, antes de implementarlo en su aplicación o navegador,
agregar capas de preprocesamiento adicionales para encargarse del preprocesamiento sobre la
marcha. Eso es definitivamente mejor, ya que ahora solo tiene dos versiones de su código de
preprocesamiento: el código Apache Beam o Spark y el código de las capas de preprocesamiento.
Machine Translated by Google
Pero ¿qué sucedería si pudiera definir sus operaciones de preprocesamiento solo una
vez? Para eso se diseñó TF Transform. Es parte de TensorFlow Extended (TFX),
una plataforma integral para la producción de modelos de TensorFlow.
En primer lugar, para utilizar un componente TFX como TF Transform, debe
instalarlo; no viene incluido con TensorFlow. Luego, defina su función de
preprocesamiento solo una vez (en Python), utilizando funciones de TF
Transform para escalar, dividir en grupos y más. También puede utilizar
cualquier operación de TensorFlow que necesite. Así es como se vería esta función
de preprocesamiento si solo tuviéramos dos funciones:
importar tensorflow_transform como tft
# entradas = a lote de características de entrada
def preprocesar(entradas):
edad_mediana = entradas["edad_mediana_de_la_vivienda"]
proximidad_océano = entradas["proximidad_océano"]
edad_estandarizada = tft.scale_to_z_score(edad_mediana)
id_de_proximidad_océano =
tft.compute_and_apply_vocabulary(proximidad_océano) return
{ "edad_mediana_estandarizada": edad_estandarizada,
"id_de_proximidad_océano": id_de_proximidad_océano
}
A continuación, TF Transform le permite aplicar esta función preprocess() a todo
el conjunto de entrenamiento mediante Apache Beam
(proporciona una clase AnalyzeAndTransformDataset que puede utilizar para este
propósito en su canalización de Apache Beam). En el proceso, también calculará
todas las estadísticas necesarias sobre todo el conjunto de entrenamiento: en este
ejemplo, la media y la desviación estándar de la característica
housing_median_age y el vocabulario para la característica ocean_proximity.
Los componentes que calculan estas estadísticas se denominan analizadores.
Es importante destacar que TF Transform también generará una función
TensorFlow equivalente que puede conectar al modelo que implemente. Esta
función TF incluye algunas constantes que corresponden a todas las
estadísticas necesarias calculadas por Apache Beam (la media, la desviación
estándar y el vocabulario).
Machine Translated by Google
Con la API de datos, TFRecords, las capas de preprocesamiento de Keras y TF Transform,
puede crear canales de entrada altamente escalables para capacitación y beneficiarse de un
preprocesamiento de datos rápido y portátil en producción.
Pero ¿qué pasa si solo quieres usar un conjunto de datos estándar? En ese caso, las cosas
son mucho más sencillas: ¡solo tienes que usar TFDS!
El proyecto de conjuntos de datos de TensorFlow (TFDS) El proyecto de conjuntos de
datos de TensorFlow El proyecto facilita la descarga de conjuntos de datos comunes, desde los
más pequeños como MNIST o Fashion MNIST hasta conjuntos de datos enormes como ImageNet
(¡necesitará bastante espacio en disco!). La lista incluye conjuntos de datos de imágenes,
conjuntos de datos de texto (incluidos los conjuntos de datos de traducción) y conjuntos de
datos de audio y video. Puede visitar https://homl.info/tfds para ver la lista completa, junto con una
descripción de cada conjunto de datos.
TFDS no está incluido en TensorFlow, por lo que debe instalar la biblioteca
tensorflow­datasets (por ejemplo, usando pip). Luego, llame a la función
tfds.load() y descargará los datos que desea (a menos que ya se hayan descargado antes) y
devolverá los datos como un diccionario de conjuntos de datos (normalmente uno para
entrenamiento y otro para pruebas, pero esto depende del conjunto de datos que elija). Por
ejemplo, descarguemos MNIST:
Importar tensorflow_datasets como tfds
conjunto de datos = tfds.load(nombre="mnist")
mnist_train, mnist_test = conjunto de datos["train"], conjunto de datos["test"]
Luego, puedes aplicar cualquier transformación que desees (normalmente, mezcla,
procesamiento por lotes y precarga) y estarás listo para entrenar tu modelo. Aquí tienes un
ejemplo sencillo:
mnist_train = mnist_train.shuffle(10000).batch(32).prefetch(1) para el elemento en
mnist_train:
imágenes = artículo["imagen"]
etiquetas = artículo["etiqueta"]
[...]
Machine Translated by Google
CONSEJO
La función load() mezcla cada fragmento de datos que descarga (solo para el conjunto de
entrenamiento). Esto puede no ser suficiente, por lo que es mejor mezclar un poco más los datos de entrenamiento.
Tenga en cuenta que cada elemento del conjunto de datos es un diccionario que contiene
tanto las características como las etiquetas. Pero Keras espera que cada elemento sea
una tupla que contenga dos elementos (de nuevo, las características y las etiquetas). Puede
transformar el conjunto de datos utilizando el método map(), de la siguiente manera:
mnist_train = mnist_train.shuffle(10000).batch(32) mnist_train =
mnist_train.map( elementos lambda : (elementos["imagen"], elementos["etiqueta"])) mnist_train =
mnist_train.prefetch(1)
Pero es más sencillo pedirle a la función load() que haga esto por usted configurando
as_supervised=True (obviamente, esto solo funciona para conjuntos de datos etiquetados).
También puede especificar el tamaño del lote si lo desea. Luego, puede pasar el conjunto de
datos directamente a su modelo tf.keras:
conjunto de datos = tfds.load(nombre="mnist", tamaño_de_lote=32, como_supervisado=Verdadero) mnist_train
= conjunto de datos["train"].prefetch(1) modelo =
keras.models.Sequential([...])
modelo.compile(pérdida="entropía_cruzada_categórica_dispersa", optimizador="sgd") modelo.fit(mnist_train,
épocas=5)
Este fue un capítulo bastante técnico y puede que sientas que está un poco alejado de la
belleza abstracta de las redes neuronales, pero el hecho es que el aprendizaje profundo a menudo
involucra grandes cantidades de datos y saber cómo cargarlos, analizarlos y preprocesarlos de
manera eficiente es una habilidad crucial que se debe tener. En el próximo capítulo, veremos las
redes neuronales convolucionales, que se encuentran entre las arquitecturas de redes
neuronales más exitosas para el procesamiento de imágenes y muchas otras aplicaciones.
Ceremonias
Machine Translated by Google
1. ¿Por qué querrías utilizar la API de datos?
2. ¿Cuáles son los beneficios de dividir un conjunto de datos grande en múltiples
¿archivos?
3. Durante el entrenamiento, ¿cómo puedes saber que tu canal de entrada es el correcto?
¿Cuello de botella? ¿Qué puedes hacer para solucionarlo?
4. ¿Puedes guardar cualquier dato binario en un archivo TFRecord o solo buffers de
protocolo serializados?
5. ¿Por qué debería tomarse la molestia de convertir todos sus datos al formato de ejemplo de protobuf?
¿Por qué no utilizar su propia definición de protobuf?
6. Al utilizar TFRecords, ¿cuándo querría activar la compresión? ¿Por qué no hacerlo
sistemáticamente?
7. Los datos se pueden preprocesar directamente al escribir los archivos de datos, o dentro de la
secuencia de comandos tf.data, o en capas de preprocesamiento dentro de su modelo, o usando
TF Transform. ¿Puede enumerar algunas ventajas y desventajas de cada opción?
8. Nombra algunas técnicas comunes que puedes usar para codificar
Características categóricas. ¿Qué pasa con el texto?
9. Cargue el conjunto de datos Fashion MNIST (presentado en el Capítulo 10); divídalo en un conjunto
de entrenamiento, un conjunto de validación y un conjunto de prueba; mezcle el conjunto de
entrenamiento y guarde cada conjunto de datos en múltiples archivos TFRecord.
Cada registro debe ser un protobuf de ejemplo serializado con dos características: la imagen
serializada (use tf.io.serialize_tensor() para serializar cada imagen) y la etiqueta. Luego, use tf.data
11
para crear un conjunto de datos eficiente para cada conjunto. Finalmente, use un modelo Keras para
entrenar estos conjuntos de datos, incluida una capa de preprocesamiento para estandarizar
cada característica de entrada. Intente hacer que la secuencia de entrada sea lo más eficiente posible,
usando TensorBoard para visualizar los datos de creación de perfiles.
Machine Translated by Google
10. En este ejercicio descargarás un conjunto de datos, lo dividirás y crearás un
tf.data.Dataset para cargarlo y preprocesarlo de manera eficiente, luego construir y entrenar
un modelo de clasificación binaria que contenga una capa de incrustación:
a. Descargue el gran conjunto de datos de reseñas de películas, cual
Contiene 50.000 reseñas de películas de la base de datos de películas de Internet .
Los datos están organizados en dos directorios, train y test, cada uno de los
cuales contiene un subdirectorio pos con 12.500 reseñas positivas y un
subdirectorio neg con 12.500 reseñas negativas. Cada reseña se almacena
en un archivo de texto independiente. Hay otros archivos y carpetas (incluidos los
paquetes de palabras preprocesados), pero los ignoraremos en este
ejercicio.
b. Divida el conjunto de prueba en un conjunto de validación (15 000) y un conjunto
de prueba (10 000).
c. Utilice tf.data para crear un conjunto de datos eficiente para cada conjunto.
d. Crear un modelo de clasificación binaria, utilizando un
Capa TextVectorization para preprocesar cada reseña. Si la capa TextVectorization
aún no está disponible (o si te gustan los desafíos), intenta crear tu propia capa
de preprocesamiento personalizada: puedes usar las funciones del paquete
tf.strings, por ejemplo lower() para convertir todo en minúsculas,
regex_replace() para reemplazar la puntuación con espacios y split() para
dividir palabras en espacios. Debes usar una tabla de búsqueda para
generar índices de palabras, que deben prepararse en el método adapt().
e. Agregue una capa de incrustación y calcule la media
incrustación para cada revisión, multiplicada por la raíz cuadrada del número de
palabras (ver Capítulo 16). Esta reescalada
Machine Translated by Google
La incrustación media luego se puede pasar al resto del modelo.
f. Entrena el modelo y observa qué precisión obtienes. Intenta
optimizar tus procesos para que el entrenamiento sea lo más
rápido posible.
g. Utilice TFDS para cargar el mismo conjunto de datos más
fácilmente: tfds.load("imdb_reviews").
Las soluciones a estos ejercicios están disponibles en el Apéndice A.
1 Imagina una baraja de cartas ordenada a tu izquierda: supón que tomas las tres cartas de arriba y las barajas,
luego escoges una al azar y la pones a tu derecha, manteniendo las otras dos en tus manos. Toma otra carta a
tu izquierda, baraja las tres cartas en tus manos y escoges una de ellas al azar, y la pones a tu derecha. Cuando
hayas terminado de revisar todas las cartas de esta manera, tendrás una baraja de cartas a tu derecha:
¿crees que estará perfectamente barajada?
2 En general, basta con precargar un lote, pero en algunos casos puede que sea necesario precargar algunos
más. Como alternativa, puede dejar que TensorFlow decida automáticamente al pasar
tf.data.experimental.AUTOTUNE (esta es una función experimental por ahora).
3 Pero consulte la función tf.data.experimental.prefetch_to_device(), que
Puede obtener datos previamente directamente en la GPU.
4 El soporte para conjuntos de datos es específico de tf.keras; esto no funcionará en otras implementaciones de
la API de Keras.
5 El método fit() se encargará de repetir el conjunto de datos de entrenamiento. Como alternativa, puede
llamar a repeat() en el conjunto de datos de entrenamiento para que se repita indefinidamente y
especificar el argumento steps_per_epoch al llamar al método fit(). Esto puede resultar útil en algunos casos
excepcionales, por ejemplo, si desea utilizar un búfer de reproducción aleatoria que cruce épocas.
6 Dado que los objetos protobuf están destinados a ser serializados y transmitidos, se denominan
mensajes.
Este capítulo contiene lo mínimo que necesita saber sobre protobufs para usar TFRecords. Para obtener
más información sobre protobufs, visite https://homl.info/protobuf .
8 ¿Por qué se definió Example, si no contiene más que un objeto Features? Bueno, los desarrolladores de
TensorFlow pueden algún día decidir agregarle más campos. Mientras la nueva definición de Example aún
contenga el campo features, con el mismo ID, será compatible con versiones anteriores. Esta extensibilidad es
una de las grandes características de los protobufs.
Machine Translated by Google
9 Tomas Mikolov et al., “Representaciones distribuidas de palabras y frases y su composicionalidad”, Actas de
la 26.ª Conferencia internacional sobre sistemas de procesamiento de información neuronal 2 (2013):
3111–3119.
10 Malvina Nissim et al., “Lo justo es mejor que lo sensacionalista: el hombre es al médico lo que la mujer es al médico”.
Doctor”, preimpresión arXiv arXiv:1905.09866 (2019).
11 Para imágenes grandes, puede utilizar tf.io.encode_jpeg() en su lugar. Esto le ahorrará mucho tiempo.
espacio, pero perdería un poco de calidad de imagen.
Machine Translated by Google
Capítulo 14. Visión artificial profunda
mediante redes neuronales
convolucionales
Aunque la supercomputadora Deep Blue de IBM derrotó al campeón mundial de ajedrez Garry
Kasparov en 1996, no fue hasta hace relativamente poco que las computadoras pudieron
realizar de manera confiable tareas aparentemente triviales, como detectar un cachorro en
una imagen o reconocer palabras habladas. ¿Por qué estas tareas son tan fáciles para
nosotros los humanos? La respuesta está en el hecho de que la percepción tiene lugar en gran
medida fuera del ámbito de nuestra conciencia, dentro de módulos visuales, auditivos y
otros sensores especializados en nuestro cerebro. Para cuando la información sensorial
llega a nuestra conciencia, ya está adornada con características de alto nivel; por ejemplo,
cuando miras una foto de un lindo cachorro, no puedes elegir no ver al cachorro, no notar
su ternura.
Tampoco podemos explicar cómo reconocemos a un lindo cachorro; simplemente, nos resulta
obvio. Por lo tanto, no podemos confiar en nuestra experiencia subjetiva: la percepción no
es en absoluto trivial y para comprenderla debemos observar cómo funcionan los
módulos sensoriales.
Las redes neuronales convolucionales (CNN) surgieron del estudio de la corteza visual
del cerebro y se han utilizado en el reconocimiento de imágenes desde la década de 1980.
En los últimos años, gracias al aumento de la potencia computacional, la cantidad de datos
de entrenamiento disponibles y los trucos presentados en el Capítulo 11 para entrenar redes
profundas, las CNN han logrado un rendimiento sobrehumano en algunas tareas
visuales complejas. Impulsan servicios de búsqueda de imágenes, automóviles
autónomos, sistemas de clasificación automática de videos y más. Además, las CNN no se
limitan a la percepción visual: también son exitosas en muchas otras tareas, como
el reconocimiento de voz y el procesamiento del lenguaje natural. Sin embargo, por ahora
nos centraremos en las aplicaciones visuales.
Machine Translated by Google
En este capítulo, exploraremos de dónde provienen las CNN, cómo son sus
componentes básicos y cómo implementarlas utilizando TensorFlow y Keras. Luego,
analizaremos algunas de las mejores arquitecturas de CNN, así como otras tareas
visuales, incluida la detección de objetos (clasificar varios objetos en una imagen y
colocar cuadros delimitadores a su alrededor) y la segmentación semántica
(clasificar cada píxel según la clase del objeto al que pertenece).
La arquitectura de la corteza visual
David H. Hubel y Torsten Wiesel realizaron una serie de experimentos con gatos en
2 tarde sobre los monos ), lo que proporcionó
3
1958 y 1959 (y1unos años más
información crucial sobre la estructura de la corteza visual (los autores recibieron el Premio
Nobel de Fisiología o Medicina en 1981 por su trabajo). En particular, demostraron
que muchas neuronas de la corteza visual tienen un campo receptivo local pequeño,
lo que significa que reaccionan solo a estímulos visuales ubicados en una región
limitada del campo visual (véase la Figura 14­1, en la que los campos receptivos
locales de cinco neuronas están representados por círculos discontinuos). Los
campos receptivos de diferentes neuronas pueden superponerse y juntos cubren
todo el campo visual.
Además, los autores demostraron que algunas neuronas reaccionan únicamente a
imágenes de líneas horizontales, mientras que otras reaccionan únicamente a líneas con
orientaciones diferentes (dos neuronas pueden tener el mismo campo receptivo pero
reaccionar a orientaciones de líneas diferentes). También observaron que algunas
neuronas tienen campos receptivos más grandes y reaccionan a patrones más
complejos que son combinaciones de los patrones de nivel inferior. Estas observaciones
llevaron a la idea de que las neuronas de nivel superior se basan en las salidas de
neuronas vecinas de nivel inferior (en la Figura 14­1, observe que cada neurona está
conectada únicamente a unas pocas neuronas de la capa anterior). Esta poderosa
arquitectura es capaz de detectar todo tipo de patrones complejos en cualquier área del campo visual.
Machine Translated by Google
Figura 14­1. Las neuronas biológicas de la corteza visual responden a patrones específicos en pequeñas regiones del
campo visual llamadas campos receptivos; a medida que la señal visual recorre módulos cerebrales consecutivos,
las neuronas responden a patrones más complejos en campos receptivos más grandes.
4
Estos estudios de la corteza visual inspiraron el neocognitrón, Se introdujo en 1980 y
evolucionó gradualmente hasta convertirse en lo que hoy llamamos redes neuronales
5
convolucionales. Un hito importante fue un artículo de 1998 de Yann LeCun et al.,
que introdujo la famosa arquitectura LeNet­5 , ampliamente utilizada por los bancos para
reconocer números de cheques escritos a mano. Esta arquitectura tiene algunos
elementos básicos que ya conoce, como las capas completamente conectadas y
las funciones de activación sigmoidea, pero también introduce dos nuevos elementos
básicos: las capas convolucionales y las capas de agrupamiento. Veámoslas ahora.
NOTA
¿Por qué no utilizar simplemente una red neuronal profunda con capas completamente conectadas
para tareas de reconocimiento de imágenes? Desafortunadamente, aunque esto funciona bien
para imágenes pequeñas (por ejemplo, MNIST), no funciona para imágenes más grandes debido a la
enorme cantidad de parámetros que requiere. Por ejemplo, una imagen de 100 × 100 píxeles tiene 10
000 píxeles, y si la primera capa tiene solo 1000 neuronas (lo que ya restringe severamente
la cantidad de información transmitida a la siguiente capa), esto significa un total de 10
millones de conexiones. Y eso es solo la primera capa. Las CNN resuelven este problema utilizando
capas parcialmente conectadas y reparto de peso.
Capas convolucionales
Machine Translated by Google
El componente más importante de una CNN es la capa convolucional: las neuronas de
6
la primera capa convolucional no están conectadas a cada píxel de la imagen de
entrada (como lo estaban en las capas analizadas en los capítulos anteriores), sino solo
a los píxeles de sus campos receptivos (consulte la Figura 14­2). A su vez, cada neurona
de la segunda capa convolucional está conectada solo a las neuronas ubicadas dentro
de un pequeño rectángulo en la primera capa. Esta arquitectura permite
que la red se concentre en pequeñas características de bajo nivel en la primera capa
oculta, luego las ensamble en características más grandes de nivel superior en la
siguiente capa oculta, y así sucesivamente. Esta estructura jerárquica es común en las
imágenes del mundo real, que es una de las razones por las que las CNN funcionan
tan bien para el reconocimiento de imágenes.
Figura 14­2. Capas CNN con campos receptivos locales rectangulares
NOTA
Todas las redes neuronales multicapa que hemos analizado hasta ahora tenían capas compuestas
por una larga línea de neuronas, y tuvimos que aplanar las imágenes de entrada a 1D antes de
introducirlas en la red neuronal. En una CNN, cada capa está representada en 2D, lo que
facilita la asociación de las neuronas con sus entradas correspondientes.
Machine Translated by Google
Una neurona ubicada en la fila i, columna j de una capa dada está conectada a
las salidas de las neuronas de la capa anterior ubicadas en las filas i a i + f – 1,yo
columnas j a j + f – 1,
la altura
y el ancho del campo receptivo
el donde f y f son
yo
el
(ver Figura 14­3). Para que una capa tenga la misma altura y ancho que la capa
anterior, es común agregar ceros alrededor de las entradas, como se muestra en
el diagrama. Esto se llama relleno de ceros.
Figura 14­3. Conexiones entre capas y relleno de ceros
También es posible conectar una capa de entrada grande a una capa mucho más
pequeña espaciando los campos receptivos, como se muestra en la
Figura 14­4. Esto reduce drásticamente la complejidad computacional del
modelo. El cambio de un campo receptivo al siguiente se llama paso. En el diagrama,
una capa de entrada de 5 × 7 (más relleno de ceros) está conectada a una capa de
3 × 4, utilizando campos receptivos de 3 × 3 y un paso de 2 (en este ejemplo, el
paso es el mismo en ambas direcciones, pero no tiene por qué ser así). Una neurona
ubicada en la fila i, columna j en la capa superior está conectada a las salidas de las neuronas en
Machine Translated by Google
la capa anterior ubicada en las filas i × s a i × syo+ f – 1, columnas
j×sa
yo
yo
el
j × s +enf – 1, donde s y s sonyolos pasos
el verticales y horizontales.
Figura 14­4. Reducción de la dimensionalidad mediante un paso de 2
Filtros
Los pesos de una neurona se pueden representar como una imagen pequeña del tamaño de la
campo receptivo. Por ejemplo, la Figura 14­5 muestra dos posibles conjuntos de
pesos, llamados filtros (o núcleos de convolución). El primero está representado
como un cuadrado negro con una línea blanca vertical en el medio (es un 7 × 7
matriz llena de 0 excepto la columna central, que está llena de 1);
Las neuronas que utilizan estos pesos ignorarán todo lo que haya en su campo receptivo.
excepto la línea vertical central (ya que todas las entradas se multiplicarán por
0, excepto los ubicados en la línea vertical central). El segundo filtro
es un cuadrado negro con una línea blanca horizontal en el medio. Una vez más,
Las neuronas que utilizan estos pesos ignorarán todo lo que haya en su campo receptivo.
excepto la línea horizontal central.
Machine Translated by Google
Ahora bien, si todas las neuronas de una capa utilizan el mismo filtro de línea vertical (y
el mismo término de sesgo) y se alimenta a la red con la imagen de entrada que se
muestra en la Figura 14­5 (la imagen inferior), la capa generará la imagen superior
izquierda. Observe que las líneas blancas verticales se realzan mientras que el resto se
difumina. De manera similar, la imagen superior derecha es lo que se obtiene
si todas las neuronas utilizan el mismo filtro de línea horizontal; observe que las líneas
blancas horizontales se realzan mientras que el resto se difumina. Por lo tanto, una
capa llena de neuronas que utilizan el mismo filtro genera un mapa de características,
que resalta las áreas de una imagen que activan más el filtro. Por supuesto, no es
necesario definir los filtros manualmente: en cambio, durante el
entrenamiento, la capa convolucional aprenderá automáticamente los filtros más útiles
para su tarea, y las capas superiores aprenderán a combinarlos en patrones más complejos.
Figura 14­5. Aplicación de dos filtros diferentes para obtener dos mapas de características
Apilamiento de múltiples mapas de características
Hasta ahora, para simplificar, he representado la salida de cada capa
convolucional como una capa 2D, pero en realidad una capa convolucional tiene
múltiples filtros (usted decide cuántos) y genera un mapa de características por capa.
Machine Translated by Google
filtro, por lo que se representa con mayor precisión en 3D (consulte la Figura 14­6). Tiene una
neurona por píxel en cada mapa de características, y todas las neuronas dentro de un mapa de
características determinado comparten los mismos parámetros (es decir, los mismos pesos y
término de sesgo). Las neuronas en diferentes mapas de características usan diferentes
parámetros. El campo receptivo de una neurona es el mismo que se describió anteriormente,
pero se extiende a través de todos los mapas de características de las capas anteriores. En resumen,
una capa convolucional aplica simultáneamente múltiples filtros entrenables a sus entradas, lo
que la hace capaz de detectar múltiples características en cualquier parte de sus entradas.
NOTA
El hecho de que todas las neuronas de un mapa de características compartan los mismos
parámetros reduce drásticamente la cantidad de parámetros del modelo. Una vez que la CNN
ha aprendido a reconocer un patrón en una ubicación, puede reconocerlo en cualquier otra
ubicación. Por el contrario, una vez que una DNN normal ha aprendido a reconocer un patrón en
una ubicación, puede reconocerlo solo en esa ubicación en particular.
Las imágenes de entrada también se componen de varias subcapas: una por canal de color.
Normalmente hay tres: rojo, verde y azul (RGB). Las imágenes en escala de grises tienen solo un
canal, pero algunas imágenes pueden tener muchos más; por ejemplo, las imágenes satelitales que
capturan frecuencias de luz adicionales (como la infrarroja).
Machine Translated by Google
Figura 14­6. Capas convolucionales con múltiples mapas de características e imágenes con tres canales de color
Específicamente, una neurona ubicada en la fila i, columna j del mapa de características k en
una capa convolucional dada l está conectada a las salidas de las neuronas en la capa
anterior l – 1, ubicadas en las filas i × s a i × s + f – 1 y lasyocolumnas jyo× s ayoj × s + f –
1, en todos los mapas
de características
(en la capa l – 1).
el
en
Tenga en cuenta que todas las neuronas ubicadas en la misma fila i y columna j pero
en diferentes mapas de características están conectadas a las salidas de exactamente
las mismas neuronas en la capa anterior.
La ecuación 14­1 resume las explicaciones anteriores en una gran ecuación
matemática: muestra cómo calcular la salida de una neurona dada en una capa
convolucional. Es un poco fea debido a todas las diferentes
Machine Translated by Google
índices, pero lo único que hace es calcular la suma ponderada de todas las entradas, más el
término de sesgo.
Ecuación 14­1. Cálculo de la salida de una neurona en una capa convolucional
fh−1 fw−1 fn′−1
∑zi ,j,k = bk +
∑
∑
u=0
v=0
k'=0
xi ′ ,j
j ×+ usw + v
′ ,k ′. wu,v,k ′ ,k con { i' = ij'×=sh
En esta ecuación:
el
es la salida de la neurona ubicada en la fila i, columna j en el mapa de
características i, j, k de la capa convolucional (capa l).
yo
el
Como se explicó anteriormente,
s y s son
los pasos verticales y horizontales, f
yo
y f son la altura
y el elancho del campo receptivo, y f es el número de mapas de
características en la capa anterior (capa l – 1).
norte'
incógnita
j′,
es la salida de la neurona ubicada en la capa l – 1, fila i′, i′, j′, k′ columna
mapa de características k′ (o canal k′ si la capa anterior es la capa de entrada).
b aes el término de sesgo para el mapa de características k (en la capa l). Se puede
pensar en él como una perilla que ajusta el brillo general del mapa de características
k.
es el peso de la conexión entre cualquier neurona en la característica w u, v, k′ ,k
mapa k de la capa l y su entrada ubicada en la fila u, columna v (relativa al
campo receptivo de la neurona), y mapa de características k′.
Implementación de TensorFlow En TensorFlow,
cada imagen de entrada se representa típicamente como un tensor 3D de forma [altura, ancho,
canales]. Un minilote se representa como un tensor 4D de forma [tamaño del minilote, altura,
ancho, canales]. Los pesos de una capa convolucional se representan como un tensor
4D de forma [f , f , f , f ].
¿cómo es ?
Machine Translated by Google
Los términos de sesgo de una capa convolucional se representan simplemente como una dimensión.
tensor de forma [f ].
norte
Veamos un ejemplo sencillo. El código siguiente carga dos muestras
imágenes, utilizando load_sample_image() de Scikit­Learn (que carga dos
imágenes en color, una de un templo chino y la otra de una flor), luego
crea dos filtros y los aplica a ambas imágenes, y finalmente muestra
Uno de los mapas de características resultantes:
desde sklearn.datasets importar load_sample_image
# Cargar imágenes de muestra
China = cargar imagen de muestra("china.jpg") / 255
flor = cargar_imagen_de_muestra("flor.jpg") / 255
imágenes = np.array([china, flor])
tamaño_lote, altura, ancho, canales = imágenes.forma
# Crear
2 filtros
filtros = np.zeros(forma=(7, 7, canales, 2), dtype=np.float32)
# línea vertical
filtros[:, 3, :, 0] = 1 filtros[3, :, :, 1] = 1
# línea horizontal
salidas = tf.nn.conv2d(imágenes, filtros, pasos=1, relleno="igual")
plt.imshow(salidas[0, :, :, 1], cmap="gris")
# trama 1º
imagen 2
característica mapa
plt.mostrar()
Repasemos este código:
La intensidad del píxel para cada canal de color se representa como un byte.
de 0 a 255, por lo que escalamos estas características simplemente dividiéndolas por
255, para obtener flotantes que van de 0 a 1.
Luego creamos dos filtros de 7 × 7 (uno con una línea blanca vertical en
el medio, y el otro con una línea blanca horizontal en el
medio).
Los aplicamos a ambas imágenes usando tf.nn.conv2d()
función, que es parte del aprendizaje profundo de bajo nivel de TensorFlow
Machine Translated by Google
API. En este ejemplo, utilizamos relleno de ceros (padding="same") y un paso de 2.
Finalmente, graficamos uno de los mapas de características resultantes (similar a la
imagen superior derecha de la Figura 14­5).
La línea tf.nn.conv2d() merece un poco más de explicación:
Las imágenes son el minilote de entrada (un tensor 4D, como se explicó anteriormente).
filtros es el conjunto de filtros a aplicar (también un tensor 4D, como se
explicó anteriormente).
strides es igual a 1, pero también podría ser una matriz 1D con cuatro elementos,
donde los dos elementos centrales son los strides verticales y horizontales (s y
s ). El primer y el último elemento
deben
ser iguales a 1. Algún día podrán usarse
yo
el
para especificar un strides de lote (para omitir algunas instancias) y un strides de
canal (para omitir algunos de los mapas de características o canales de la capa anterior).
El relleno debe ser "igual" o "válido":
Si se establece en "igual", la capa convolucional utiliza relleno de
ceros si es necesario. El tamaño de salida se establece en la cantidad de
neuronas de entrada dividida por la zancada, redondeada hacia arriba.
Por ejemplo, si el tamaño de entrada es 13 y la zancada es 5 (consulte
la Figura 14­7), entonces el tamaño de salida es 3 (es decir, 13 / 5 = 2,6,
redondeado a 3). Luego, se agregan ceros de la manera más
uniforme posible alrededor de las entradas, según sea necesario. Cuando
las zancadas = 1, las salidas de la capa tendrán las mismas dimensiones
espaciales (ancho y alto) que sus entradas, de ahí el nombre " igual".
Si se establece en "válido", la capa convolucional no utiliza relleno de
ceros y puede ignorar algunas filas y columnas en la parte inferior y
derecha de la imagen de entrada, dependiendo del paso, como se muestra
en la Figura 14­7 (para simplificar, solo la
Machine Translated by Google
Aquí se muestra la dimensión horizontal, pero por supuesto se
aplica la misma lógica a la dimensión vertical). Esto significa que el
campo receptivo de cada neurona se encuentra estrictamente
dentro de posiciones válidas dentro de la entrada (no se sale
de los límites), de ahí el nombre válido.
Figura 14­7. Padding="same" o "valid" (con ancho de entrada 13, ancho de filtro 6, paso 5)
En este ejemplo, definimos manualmente los filtros, pero en una CNN real,
normalmente se definirían los filtros como variables entrenables para que la red
neuronal pueda aprender qué filtros funcionan mejor, como se explicó anteriormente.
En lugar de crear manualmente las variables, use la capa keras.layers.Conv2D:
conv = keras.layers.Conv2D(filtros=32, tamaño_kernel=3, pasos=1,
relleno="mismo", activación="relu")
Este código crea una capa Conv2D con 32 filtros, cada uno de 3 × 3, utilizando un paso
de 1 (tanto horizontal como verticalmente) y el "mismo" relleno, y aplicando la función
de activación ReLU a sus salidas. Como puede ver, las capas convolucionales tienen
bastantes hiperparámetros: debe elegir el número de filtros, su altura y ancho, los
pasos y el tipo de relleno. Como siempre,
Machine Translated by Google
Puede utilizar la validación cruzada para encontrar los valores de hiperparámetros correctos, pero esto
requiere mucho tiempo. Más adelante analizaremos las arquitecturas CNN más comunes para darle una idea
de qué valores de hiperparámetros funcionan mejor en la práctica.
Requisitos de memoria
Otro problema con las CNN es que las capas convolucionales requieren una enorme cantidad de RAM.
Esto es especialmente cierto durante el entrenamiento, porque el paso inverso de la retropropagación requiere
todos los valores intermedios calculados durante el paso directo.
Por ejemplo, considere una capa convolucional con filtros de 5 × 5, que genera 200 mapas de características
de tamaño 150 × 100, con paso 1 y relleno "mismo". Si la entrada es una imagen RGB de 150 × 100 (tres
canales), entonces el número de parámetros es (5 × 5 × 3 + 1) × 200 = 15 200 (el + 1 corresponde al
términos de sesgo), que es bastante pequeño en comparación con una capa completamente conectada. 7
Sin embargo, cada uno de los 200 mapas de características contiene 150 × 100 neuronas, y cada una de
estas neuronas necesita calcular una suma ponderada de sus 5 × 5 × 3 = 75 entradas: eso es un total de
225 millones de multiplicaciones de coma flotante. No es tan malo como una capa completamente conectada,
pero aún así es bastante intensivo en términos computacionales.
Además, si los mapas de características se representan mediante números flotantes de 32
bits, la salida de la capa convolucional ocupará 200 × 150 × 100 × 32 = 96 millones
de bits (12 MB) de RAM. Y eso es solo8 para una instancia: si un lote de entrenamiento
contiene 100 instancias, ¡esta capa utilizará 1,2 GB de RAM!
Durante la inferencia (es decir, cuando se hace una predicción para una nueva instancia), la RAM ocupada
por una capa se puede liberar tan pronto como se haya calculado la siguiente capa, por lo que solo se
necesita tanta RAM como lo requieran dos capas consecutivas. Pero durante el entrenamiento, todo
lo calculado durante el paso hacia adelante debe conservarse para el paso inverso, por lo que la cantidad
de RAM necesaria es (al menos) la cantidad total de RAM requerida por todas las capas.
Machine Translated by Google
CONSEJO
Si el entrenamiento falla debido a un error de falta de memoria, puedes intentar reducir el
tamaño del minilote. Como alternativa, puedes intentar reducir la dimensionalidad mediante un
paso o eliminar algunas capas. O puedes intentar usar números de punto flotante de 16 bits en
lugar de 32 bits. O puedes distribuir la CNN en varios dispositivos.
Ahora veamos el segundo componente común de las CNN: la capa de agrupación.
Capas de agrupación Una
vez que comprende cómo funcionan las capas convolucionales, las capas de
agrupación son bastante fáciles de entender. Su objetivo es submuestrear (es decir,
reducir) la imagen de entrada para reducir la carga computacional, el uso de memoria
y la cantidad de parámetros (lo que limita el riesgo de sobreajuste).
Al igual que en las capas convolucionales, cada neurona de una capa de
agrupamiento está conectada a las salidas de un número limitado de neuronas de la
capa anterior, ubicadas dentro de un pequeño campo receptivo rectangular. Debe
definir su tamaño, el paso y el tipo de relleno, al igual que antes. Sin embargo,
una neurona de agrupamiento no tiene pesos; todo lo que hace es agregar las entradas
utilizando una función de agregación como el máximo o la media. La Figura 14­8
muestra una capa de agrupamiento máximo, que es el tipo más común de capa de
9
agrupamiento. En este ejemplo, utilizamos un núcleo de agrupamiento 2 × 2, con un paso de 2 y sin rellen
Solo el valor de entrada máximo en cada campo receptivo pasa a la siguiente capa,
mientras que las otras entradas se descartan. Por ejemplo, en el campo
receptivo inferior izquierdo de la Figura 14­8, los valores de entrada son 1, 5, 3, 2, por
lo que solo el valor máximo, 5, se propaga a la siguiente capa. Debido al paso de 2, la
imagen de salida tiene la mitad de la altura y la mitad del ancho de la imagen de
entrada (redondeada hacia abajo ya que no usamos relleno).
Machine Translated by Google
Figura 14­8. Capa de agrupación máxima (núcleo de agrupación 2 × 2, paso 2, sin relleno)
NOTA
Una capa de agrupación normalmente funciona en cada canal de entrada de forma independiente, por lo que la profundidad
de salida es la misma que la profundidad de entrada.
Además de reducir los cálculos, el uso de memoria y la cantidad de parámetros,
una capa de agrupamiento máximo también introduce cierto nivel de invariancia en las
traslaciones pequeñas, como se muestra en la Figura 14­9. Aquí asumimos que los
píxeles brillantes tienen un valor menor que los píxeles oscuros y consideramos tres
imágenes (A, B, C) que pasan por una capa de agrupamiento máximo con un núcleo
de 2 × 2 y un paso de 2. Las imágenes B y C son iguales a la imagen A, pero
desplazadas uno y dos píxeles hacia la derecha. Como puede ver, las salidas de la capa
de agrupamiento máximo para las imágenes A y B son idénticas. Esto es lo que significa
invariancia de traslación. Para la imagen C, la salida es diferente: se desplaza un
píxel hacia la derecha (pero sigue habiendo un 75 % de invariancia). Al insertar una
capa de agrupamiento máximo cada pocas capas en una CNN, es posible obtener
cierto nivel de invariancia de traslación a una escala mayor. Además, el agrupamiento
máximo ofrece una pequeña cantidad de invariancia rotacional y una ligera
invariancia de escala. Esta invariancia (aunque sea limitada) puede ser útil en casos
en los que la predicción no debería depender de estos detalles, como en las tareas de clasificación.
Machine Translated by Google
Figura 14­9. Invariancia ante traslaciones pequeñas
Sin embargo, el agrupamiento máximo también tiene algunas desventajas. En primer lugar,
es obviamente muy destructivo: incluso con un núcleo diminuto de 2 × 2 y un paso de 2, la salida
será dos veces más pequeña en ambas direcciones (por lo que su área será cuatro veces más
pequeña), simplemente eliminando el 75% de los valores de entrada. Y en algunas
aplicaciones, la invariancia no es deseable. Tomemos la segmentación semántica (la tarea de
clasificar cada píxel de una imagen según el objeto al que pertenece ese píxel, que exploraremos
más adelante en este capítulo): obviamente, si la imagen de entrada se traslada un píxel a
la derecha, la salida también debería trasladarse un píxel a la derecha. El objetivo en este
caso es la equivariancia, no la invariancia: un pequeño cambio en las entradas
debería conducir a un pequeño cambio correspondiente en la salida.
Implementación de TensorFlow
Implementar una capa de agrupación máxima en TensorFlow es bastante fácil. El
código siguiente crea una capa de agrupación máxima utilizando un kernel de 2 × 2. Los
pasos predeterminados son el tamaño del kernel, por lo que esta capa utilizará un paso de 2 (ambos
Machine Translated by Google
horizontal y verticalmente). De manera predeterminada, utiliza un relleno "válido" (es decir, no
utiliza ningún relleno):
max_pool = keras.capas.MaxPool2D(tamaño_pool=2)
Para crear una capa de agrupación promedio, simplemente use AvgPool2D en lugar
de MaxPool2D. Como podría esperar, funciona exactamente como una capa de agrupación
máxima, excepto que calcula la media en lugar del máximo. Las capas de agrupación promedio
solían ser muy populares, pero ahora la gente usa principalmente capas de agrupación máxima,
ya que generalmente funcionan mejor. Esto puede parecer sorprendente, ya que calcular la
media generalmente pierde menos información que calcular el máximo. Pero, por otro lado, la
agrupación máxima conserva solo las características más fuertes, eliminando todas las que
no tienen sentido, por lo que las siguientes capas obtienen una señal más limpia con la que
trabajar. Además, la agrupación máxima ofrece una invariancia de traducción más fuerte
que la agrupación promedio y requiere un poco menos de cómputo.
Tenga en cuenta que la agrupación máxima y la agrupación promedio se pueden realizar a lo
largo de la dimensión de profundidad en lugar de las dimensiones espaciales, aunque esto no es
tan común. Esto puede permitir que la CNN aprenda a ser invariante a varias
características. Por ejemplo, podría aprender múltiples filtros, cada uno de los cuales
detectaría una rotación diferente del mismo patrón (como dígitos escritos a mano; consulte
la Figura 14­10), y la capa de agrupación máxima en profundidad garantizaría que la salida sea
la misma independientemente de la rotación. La CNN también podría aprender a ser
invariante a cualquier otra cosa: grosor, brillo, sesgo, color, etc.
Machine Translated by Google
Figura 14­10. La agrupación máxima en profundidad puede ayudar a la CNN a aprender cualquier invariancia
Keras no incluye una capa de agrupación máxima en profundidad, pero la API de
aprendizaje profundo de bajo nivel de TensorFlow sí lo hace: solo use la función
tf.nn.max_pool() y especifique el tamaño del kernel y los pasos como 4­tuplas (es decir,
tuplas de tamaño 4). Los primeros tres valores de cada uno deben ser 1: esto indica
que el tamaño del kernel y el paso a lo largo de las dimensiones de lote, altura y ancho
deben ser 1. El último valor debe ser el tamaño del kernel y el paso que desee a lo
largo de la dimensión de profundidad; por ejemplo, 3 (este debe ser un divisor de la
profundidad de entrada; no funcionará si la capa anterior genera 20 mapas de
características, ya que 20 no es un múltiplo de 3):
salida = tf.nn.max_pool(imágenes,
tamañok=(1, 1, 1, 3),
Machine Translated by Google
pasos=(1, 1, 1, 3),
relleno="válido")
Si desea incluir esto como una capa en sus modelos Keras, envuélvalo en un
Capa Lambda (o cree una capa Keras personalizada):
profundidad_pool = keras.capas.Lambda(
lambda X: tf.nn.max_pool(X, ksize=(1, 1, 1, 3), pasos=(1, 1, 1, 3), relleno="válido"))
Un último tipo de capa de agrupación que se ve a menudo en las arquitecturas
modernas es la capa de agrupación promedio global. Funciona de forma muy
diferente: todo lo que hace es calcular la media de cada mapa de características completo (es
como una capa de agrupación promedio que utiliza un núcleo de agrupación con las mismas
dimensiones espaciales que las entradas). Esto significa que solo genera un único número
por mapa de características y por instancia. Aunque esto es, por supuesto, extremadamente
destructivo (se pierde la mayor parte de la información del mapa de características), puede
ser útil como capa de salida, como veremos más adelante en este capítulo. Para crear
una capa de este tipo, simplemente use la clase keras.layers.GlobalAvgPool2D:
global_avg_pool = keras.capas.GlobalAvgPool2D()
Es equivalente a esta simple capa Lambda, que calcula la media sobre las dimensiones
espaciales (alto y ancho):
global_avg_pool = keras.layers.Lambda(lambda X: tf.reduce_mean(X, eje= [1, 2]))
Ahora ya conoces todos los elementos básicos para crear redes neuronales
convolucionales. Veamos cómo ensamblarlos.
Arquitecturas CNN
Las arquitecturas CNN típicas apilan unas cuantas capas convolucionales (cada una
generalmente seguida de una capa ReLU), luego una capa de agrupamiento, luego otras
cuantas capas convolucionales (+ReLU), luego otra capa de agrupamiento, y así sucesivamente.
Machine Translated by Google
La imagen se hace cada vez más pequeña a medida que avanza a través de la red, pero también
suele hacerse cada vez más profunda (es decir, con más mapas de características), gracias a las
capas convolucionales (consulte la Figura 14­11). En la parte superior de la pila, se agrega
una red neuronal de propagación hacia adelante regular, compuesta por algunas capas
completamente conectadas (+ReLU), y la capa final genera la predicción (por ejemplo, una capa
softmax que genera probabilidades de clase estimadas).
Figura 14­11. Arquitectura típica de CNN
CONSEJO
Un error común es utilizar núcleos de convolución que son demasiado grandes. Por
ejemplo, en lugar de utilizar una capa convolucional con un núcleo de 5 × 5, apile dos capas
con núcleos de 3 × 3: utilizará menos parámetros y requerirá menos cálculos, y
generalmente tendrá un mejor rendimiento. Una excepción es la primera capa
convolucional: normalmente puede tener un núcleo grande (por ejemplo, 5 × 5), generalmente
con un paso de 2 o más: esto reducirá la dimensión espacial de la imagen sin perder
demasiada información y, dado que la imagen de entrada solo tiene tres canales en general, no será demasiado costoso
Aquí te mostramos cómo puedes implementar una CNN sencilla para abordar la moda.
Conjunto de datos MNIST (introducido en el Capítulo 10):
modelo = keras.models.Sequential([
keras.layers.Conv2D(64, 7, activación="relu", relleno="igual", forma_de_entrada=[28,
28, 1]),
keras.capas.MaxPooling2D(2),
keras.capas.Conv2D( 128, 3, activación="relu", relleno="igual") , keras.capas.Conv2D( 128,
3, activación="relu", relleno="igual"), keras.capas.MaxPooling2D(2),
keras.capas.Conv2D(256, 3,
activación="relu", relleno="igual"), keras.capas.Conv2D(256, 3, activación="relu",
relleno="igual"), keras.capas.MaxPooling2D(2), keras.capas.Flatten(),
Machine Translated by Google
keras.capas.Dense(128, activación="relu"),
keras.capas.Dropout(0.5),
keras.capas.Dense(64, activación="relu"),
keras.capas.Dropout(0.5),
keras.capas.Dense(10, activación="softmax")
])
Repasemos este modelo:
La primera capa utiliza 64 filtros bastante grandes (7 × 7), pero no hay paso porque las
imágenes de entrada no son muy grandes. También establece input_shape=[28,
28, 1], porque las imágenes tienen 28 × 28 píxeles, con un solo canal de color (es
decir, escala de grises).
A continuación, tenemos una capa de agrupación máxima que utiliza un tamaño de
agrupación de 2, por lo que divide cada dimensión espacial por un factor de 2.
Luego repetimos la misma estructura dos veces: dos capas convolucionales seguidas de una
capa de agrupamiento máximo. Para imágenes más grandes, podríamos repetir esta
estructura varias veces más (la cantidad de repeticiones es un hiperparámetro que se puede
ajustar).
Tenga en cuenta que la cantidad de filtros aumenta a medida que ascendemos por la
CNN hacia la capa de salida (inicialmente es 64, luego 128, luego 256): tiene sentido que
crezca, ya que la cantidad de características de bajo nivel suele ser bastante baja (por
ejemplo, círculos pequeños, líneas horizontales), pero hay muchas formas diferentes de
combinarlas en características de nivel superior. Es una práctica común duplicar la
cantidad de filtros después de cada capa de agrupación: dado que una capa de agrupación
divide cada dimensión espacial por un factor de 2, podemos permitirnos duplicar la cantidad
de mapas de características en la siguiente capa sin temor a que explote la cantidad de
parámetros, el uso de memoria o la carga computacional.
A continuación, se encuentra la red completamente conectada, compuesta por dos
capas densas ocultas y una capa de salida densa. Nótese que debemos aplanar sus
entradas, ya que una red densa espera una matriz unidimensional de características para
cada instancia. También agregamos dos capas de abandono, con una tasa de abandono del
50 % cada una, para reducir el sobreajuste.
Machine Translated by Google
Esta CNN alcanza una precisión de más del 92 % en el conjunto de prueba. No es lo
último en tecnología, pero es bastante buena y claramente mucho mejor que lo que
logramos con redes densas en el Capítulo 10.
A lo largo de los años, se han desarrollado variantes de esta arquitectura
fundamental que han dado lugar a avances sorprendentes en este campo. Una buena
medida de este progreso es la tasa de error en competiciones como el desafío
ILSVRC ImageNet. En esta competición, la tasa de error de clasificación de imágenes de
los cinco primeros puestos se redujo de más del 26% a menos del 2,3% en sólo seis
años. La tasa de error de los cinco primeros puestos es el número de imágenes de prueba
para las que las cinco predicciones principales del sistema no incluyeron la respuesta
correcta. Las imágenes son grandes (256 píxeles de alto) y hay 1.000 clases, algunas
de las cuales son realmente sutiles (intente distinguir 120 razas de perros). Observar la
evolución de las imágenes ganadoras es una buena forma de entender cómo funcionan las CNN.
Primero analizaremos la arquitectura clásica LeNet­5 (1998), luego tres de los ganadores
del desafío ILSVRC: AlexNet (2012), GoogLeNet (2014) y ResNet (2015).
LeNet­5
10 la arquitectura CNN más conocida. Como se
La arquitectura LeNet­5 es quizás
mencionó anteriormente, fue creada por Yann LeCun en 1998 y ha sido ampliamente
utilizada para el reconocimiento de dígitos escritos a mano (MNIST). Está compuesta por
las capas que se muestran en la Tabla 14­1.
Machine Translated by Google
Tabla 14­1. Arquitectura de LeNet­5
Tipo de capa
Tamaño de los mapas Tamaño del kernel Activación de la zancada
Fuera Totalmente conectado –
10
–
–
F6
84
–
–
Tan
5×5
1
Tan
Completamente conectado –
Federación Rusa
Convolución C5
120 1 × 1
S4
16
5×52×2
2
Tan
Convolución C3
16
10 × 10 5 × 5
1
Tan
S2
6
14 × 14 2 × 2
2
Tan
Convolución C1
6
28 × 28 5 × 5
1
Tan
En
1
32 × 32 –
–
–
Agrupamiento promedio
Agrupamiento promedio
Aporte
Hay algunos detalles adicionales que conviene tener en cuenta:
Las imágenes MNIST tienen un tamaño de 28 × 28 píxeles, pero se rellenan con ceros hasta 32
× 32 píxeles y normalizados antes de ser introducidos a la red.
El resto de la red no utiliza ningún relleno, por lo que
El tamaño continúa disminuyendo a medida que la imagen avanza a través de la red.
Las capas de agrupación promedio son ligeramente más complejas de lo habitual:
Cada neurona calcula la media de sus entradas y luego la multiplica.
resultado por un coeficiente que se puede aprender (uno por mapa) y agrega un
término de sesgo aprendible (de nuevo, uno por mapa), luego finalmente aplica el
Función de activación.
La mayoría de las neuronas en los mapas C3 están conectadas a neuronas en solo tres
o cuatro mapas S2 (en lugar de los seis mapas S2). Véase la tabla 1 (página 8)
10 detalles.
en el artículo original para más
La capa de salida es un poco especial: en lugar de calcular la matriz
multiplicación de las entradas y el vector de peso, cada neurona
genera el cuadrado de la distancia euclidiana entre su entrada
Machine Translated by Google
vector y su vector de peso. Cada salida mide en qué medida la imagen pertenece a una
clase de dígito en particular. Ahora se prefiere la función de costo de entropía cruzada,
ya que penaliza mucho más las predicciones incorrectas, produce gradientes más
grandes y converge más rápido.
El sitio web de Yann LeCun Incluye excelentes demostraciones de clasificación de dígitos LeNet­5.
AlexNet
11
La arquitectura CNN de AlexNet ganó el desafío ILSVRC de ImageNet 2012 por un
amplio margen: logró una tasa de error del 17%, ¡entre las cinco mejores, mientras que la segunda
mejor logró solo el 26%! Fue desarrollada por Alex Krizhevsky (de ahí el nombre), Ilya Sutskever y
Geoffrey Hinton. Es similar a LeNet­5, solo que mucho más grande y profunda, y fue la primera
en apilar capas convolucionales directamente una sobre otra, en lugar de apilar una capa
de agrupamiento sobre cada capa convolucional. La Tabla 14­2 presenta esta arquitectura.
Machine Translated by Google
Tabla 14­2. Arquitectura de AlexNet
Tipo de capa
Tamaño de los mapas
Activación del relleno de zancada del tamaño del núcleo
Fuera Totalmente conectado –
1.000
–
–
–
Máximo suave
F9
Completamente conectado –
4.096
–
–
–
ReLU
F8
Completamente conectado –
4.096
–
–
–
ReLU
Convolución C7
256
13 × 13 3 × 3
1
mismo
ReLU
Convolución C6
384
13 × 13 3 × 3
1
mismo
ReLU
Convolución C5
384
13 × 13 3 × 3
1
mismo
ReLU
Agrupación máxima de S4
256
13 × 13 3 × 3
2
válido ­
Convolución C3
256
27 × 27 5 × 5
1
mismo
Agrupación máxima de S2
96
27 × 27 3 × 3
2
válido ­
Convolución C1
96
55 × 55
4
ReLU válido
En
3 (RGB) 227 × 227 –
–
–
Aporte
11 × 11
ReLU
–
Para reducir el sobreajuste, los autores utilizaron dos técnicas de regularización.
En primer lugar, aplicaron la deserción escolar (introducida en el Capítulo 11) con una tasa de deserción del 50%.
tasa durante el entrenamiento a las salidas de las capas F8 y F9. En segundo lugar,
Se realizó una ampliación de datos desplazando aleatoriamente las imágenes de entrenamiento mediante
varios desplazamientos, volteándolos horizontalmente y cambiando la iluminación
condiciones.
Machine Translated by Google
AUMENTO DE DATOS
La ampliación de datos aumenta artificialmente el tamaño del conjunto de
entrenamiento al generar muchas variantes realistas de cada instancia de
entrenamiento. Esto reduce el sobreajuste, lo que la convierte en una técnica
de regularización. Las instancias generadas deben ser lo más realistas posible:
idealmente, dada una imagen del conjunto de entrenamiento ampliado, un humano
no debería poder decir si fue aumentada o no. Simplemente agregar ruido blanco
no ayudará; las modificaciones deben poder aprenderse (el ruido blanco no lo es).
Por ejemplo, puede desplazar, rotar y redimensionar ligeramente cada imagen del
conjunto de entrenamiento en distintas cantidades y agregar las imágenes
resultantes al conjunto de entrenamiento (consulte la Figura 14­12). Esto obliga al
modelo a ser más tolerante a las variaciones en la posición, la orientación y
el tamaño de los objetos en las imágenes. Para un modelo que sea más tolerante
a diferentes condiciones de iluminación, puede generar de manera similar
muchas imágenes con distintos contrastes. En general, también puede
voltear las imágenes horizontalmente (excepto el texto y otros objetos
asimétricos). Al combinar estas transformaciones, puede aumentar en gran medida
el tamaño de su conjunto de entrenamiento.
Machine Translated by Google
Figura 14­12. Generación de nuevas instancias de entrenamiento a partir de las existentes
AlexNet también utiliza un paso de normalización competitiva inmediatamente
después del paso ReLU de las capas C1 y C3, llamado normalización de
respuesta local (LRN): las neuronas más fuertemente activadas inhiben a otras
neuronas ubicadas en la misma posición en mapas de características vecinos
(tal activación competitiva se ha observado en neuronas biológicas). Esto
alienta a diferentes mapas de características a especializarse, separándolos y
obligándolos a explorar una gama más amplia de características, mejorando en última instancia la gen
La ecuación 14­2 muestra cómo aplicar LRN.
Ecuación 14­2. Normalización de la respuesta local (LRN)
­β
alto
∑
bi = ai(k + α j=jbajo aj2 )
En esta ecuación:
con
jhigh = mín(i +
a
2 , función − 1)
a
jbajo = máx(0, i − ) 2
Machine Translated by Google
b ies la salida normalizada de la neurona ubicada en el mapa de características i, en
alguna fila u y columna v (tenga en cuenta que en esta ecuación solo
consideramos neuronas ubicadas en esta fila y columna, por lo que u y v no se
muestran).
a ies la activación de esa neurona después del paso ReLU, pero antes de la
normalización.
k, α, β y r son hiperparámetros. k se denomina sesgo y r se denomina radio de
profundidad.
f es el número de mapas de características.
norte
Por ejemplo, si r = 2 y una neurona tiene una fuerte activación, inhibirá la activación de las
neuronas ubicadas en los mapas de características inmediatamente encima y debajo de la
suya.
En AlexNet, los hiperparámetros se establecen de la siguiente manera: r = 2, α = 0,00002, β =
0,75 y k = 1. Este paso se puede implementar utilizando la función
tf.nn.local_response_normalization() (que puede envolver en una capa Lambda si desea usarla
en un modelo Keras).
12
Una variante de AlexNet llamada ZF Net Fue desarrollado por Matthew Zeiler y Rob Fergus y
ganó el desafío ILSVRC 2013. Es esencialmente AlexNet con algunos hiperparámetros
modificados (número de mapas de características, tamaño del núcleo, paso, etc.).
GoogleNet
La arquitectura de GoogleNet Fue desarrollado por Christian Szegedy et al. de Google
13
Research y ganó el desafío ILSVRC 2014 al llevar la tasa de error de los cinco primeros
por debajo del 7 %. Este gran desempeño se debió en gran parte al hecho de que la red era
mucho más profunda que las CNN anteriores (como verá en la Figura 14­14). Esto fue posible
gracias a subredes llamadas módulos de inicio, que permiten que GoogLeNet use
14
parámetros de manera mucho más eficiente que las arquitecturas anteriores: GoogLeNet
Machine Translated by Google
En realidad tiene 10 veces menos parámetros que AlexNet (aproximadamente 6
millones en lugar de 60 millones).
La figura 14­13 muestra la arquitectura de un módulo de inicio. La notación “3 × 3 + 1(S)”
significa que la capa utiliza un núcleo de 3 × 3, paso 1 y el mismo relleno. La señal
de entrada se copia primero y se envía a cuatro capas diferentes. Todas las capas
convolucionales utilizan la función de activación ReLU. Observe que el segundo
conjunto de capas convolucionales utiliza diferentes tamaños de núcleo (1 × 1, 3 × 3 y 5
× 5), lo que les permite capturar patrones en diferentes escalas.
Tenga en cuenta también que cada capa utiliza un paso de 1 y el "mismo" relleno
(incluso la capa de agrupación máxima), por lo que todas sus salidas tienen la misma
altura y ancho que sus entradas. Esto hace posible concatenar todas las salidas a lo
largo de la dimensión de profundidad en la capa de concatenación de profundidad
final (es decir, apilar los mapas de características de las cuatro capas
convolucionales superiores). Esta capa de concatenación se puede implementar
en TensorFlow utilizando la operación tf.concat(), con axis=3 (el eje es la profundidad).
Figura 14­13. Módulo de inicio
Quizás te preguntes por qué los módulos de inicio tienen capas convolucionales con
núcleos 1 × 1. Seguramente estas capas no pueden capturar ninguna característica porque
Machine Translated by Google
¿Mirar sólo un píxel a la vez? De hecho, las capas tienen tres propósitos:
Aunque no pueden capturar patrones espaciales, pueden capturar patrones a
lo largo de la dimensión de profundidad.
Están configurados para generar menos mapas de características que
sus entradas, por lo que funcionan como capas de cuello de botella, lo que
significa que reducen la dimensionalidad. Esto reduce el costo computacional y
la cantidad de parámetros, acelerando el entrenamiento y mejorando la generalización.
Cada par de capas convolucionales ([1 × 1, 3 × 3] y [1 × 1, 5 × 5]) actúa como una
única capa convolucional potente, capaz de capturar patrones más
complejos. De hecho, en lugar de barrer la imagen con un clasificador lineal
simple (como lo hace una única capa convolucional), este par
de capas convolucionales barre la imagen con una red neuronal de dos capas.
En resumen, puedes pensar en todo el módulo de inicio como una capa convolucional
con esteroides, capaz de generar mapas de características que capturan patrones
complejos en varias escalas.
ADVERTENCIA
La cantidad de núcleos convolucionales para cada capa convolucional es
un hiperparámetro. Lamentablemente, esto significa que hay que ajustar seis
hiperparámetros más para cada capa inicial que se agregue.
Ahora veamos la arquitectura de la CNN de GoogLeNet (ver Figura 14­14 ). La cantidad
de mapas de características generados por cada capa convolucional y cada capa de
agrupamiento se muestra antes del tamaño del núcleo. La arquitectura es tan profunda
que debe representarse en tres columnas, pero GoogLeNet es en realidad una pila
alta, que incluye nueve módulos de inicio (las cajas con las peonzas). Los seis números
en los módulos de inicio representan la cantidad de mapas de características generados por
cada capa convolucional en el módulo (en el mismo orden que en la Figura 14­13). Observe
que todas las capas convolucionales utilizan la función de activación ReLU.
Machine Translated by Google
Figura 14­14. Arquitectura de GoogleNet
Recorramos esta red:
Las dos primeras capas dividen la altura y el ancho de la imagen por 4 (por lo que
su área se divide por 16) para reducir la carga computacional. La primera capa
utiliza un tamaño de núcleo grande para que se conserve gran parte de la información.
Luego, la capa de normalización de respuesta local garantiza que las capas
anteriores aprendan una amplia variedad de características (como se explicó
anteriormente).
A continuación se presentan dos capas convolucionales, donde la primera actúa como
una capa de cuello de botella. Como se explicó anteriormente, se puede pensar en este par como
Machine Translated by Google
una única capa convolucional más inteligente.
Nuevamente, una capa de normalización de respuesta local garantiza que las
capas anteriores capturen una amplia variedad de patrones.
A continuación, una capa de agrupación máxima reduce la altura y el ancho de la
imagen en 2, nuevamente para acelerar los cálculos.
Luego viene la pila alta de nueve módulos de inicio, intercalados con un par de capas
de agrupación máxima para reducir la dimensionalidad y acelerar la red.
A continuación, la capa de agrupación de promedios globales genera la media de cada
mapa de características: esto descarta cualquier información espacial restante, lo cual
está bien porque no quedaba mucha información espacial en ese punto. De hecho, se
espera que las imágenes de entrada de GoogLeNet tengan un tamaño de 224 × 224
píxeles, por lo que después de 5 capas de agrupación máximas, cada una de las cuales
divide la altura y el ancho por 2, los mapas de características se reducen a 7 × 7.
Además, se trata de una tarea de clasificación, no de localización, por lo que no
importa dónde se encuentre el objeto. Gracias a la reducción de dimensionalidad
que aporta esta capa, no es necesario tener varias capas completamente conectadas
en la parte superior de la CNN (como en AlexNet), lo que reduce considerablemente
el número de parámetros en la red y limita el riesgo de sobreajuste.
Las últimas capas se explican por sí solas: abandono para regularización, luego una
capa completamente conectada con 1000 unidades (ya que hay 1000 clases) y
una función de activación softmax para generar probabilidades de clase
estimadas.
Este diagrama está ligeramente simplificado: la arquitectura original de GoogLeNet también incluía
dos clasificadores auxiliares conectados sobre los módulos de inicio tercero y sexto. Ambos estaban
compuestos por una capa de agrupación promedio, una capa convolucional, dos capas
completamente conectadas y una capa de activación softmax. Durante el entrenamiento, su
pérdida (reducida en un 70 %) se agregó a la pérdida general. El objetivo era luchar contra los
gradientes que se desvanecen.
Machine Translated by Google
problema y regularizar la red. Sin embargo, más tarde se demostró que su efecto era
relativamente menor.
Posteriormente, los investigadores de Google propusieron varias variantes de la
arquitectura GoogLeNet, incluidas Inception­v3 e Inception­v4, que utilizaban
módulos de inicio ligeramente diferentes y alcanzaban un rendimiento aún mejor.
VGGNet
15 por Karen
El subcampeón en el desafío ILSVRC 2014 fue VGGNet, Desarrollada
Simonyan y Andrew Zisserman del laboratorio de investigación Visual Geometry
Group (VGG) de la Universidad de Oxford, tenía una arquitectura muy simple y
clásica, con 2 o 3 capas convolucionales y una capa de agrupamiento, luego 2 o 3
capas convolucionales y una capa de agrupamiento, y así sucesivamente
(alcanzando un total de solo 16 o 19 capas convolucionales, dependiendo de la
variante VGG), más una red densa final con 2 capas ocultas y la capa de salida.
Utilizaba solo 3 × 3 filtros, pero muchos filtros.
Red de res
Kaiming He et al. ganaron el desafío ILSVRC 2015 utilizando una red residual
16 asombrosa tasa de error entre las cinco primeras,
(o ResNet), que arrojó una
inferior al 3,6 %. La variante ganadora utilizó una CNN extremadamente profunda
compuesta por 152 capas (otras variantes tenían 34, 50 y 101 capas). Confirmó la
tendencia general: los modelos son cada vez más profundos, con cada vez menos
parámetros. La clave para poder entrenar una red tan profunda es utilizar
conexiones de salto (también llamadas conexiones de acceso directo): la señal que
se introduce en una capa también se suma a la salida de una capa ubicada un
poco más arriba en la pila. Veamos por qué esto es útil.
Al entrenar una red neuronal, el objetivo es lograr que modele una función
objetivo h(x). Si se suma la entrada x a la salida de la red (es decir, se agrega una
conexión de salto), la red se verá obligada a modelar f(x) = h(x) – x en lugar de
h(x). Esto se denomina aprendizaje residual (consulte la Figura 14­15).
Machine Translated by Google
Figura 14­15. Aprendizaje residual
Cuando se inicializa una red neuronal normal, sus pesos son cercanos a cero, por
lo que la red solo genera valores cercanos a cero. Si se agrega una
conexión de salto, la red resultante solo genera una copia de sus entradas; en otras
palabras, inicialmente modela la función de identidad. Si la función de destino
es bastante cercana a la función de identidad (que suele ser el caso), esto
acelerará considerablemente el entrenamiento.
Además, si se agregan muchas conexiones de salto, la red puede comenzar a
avanzar incluso si varias capas aún no han comenzado a aprender
(consulte la Figura 14­16). Gracias a las conexiones de salto, la señal puede
atravesar fácilmente toda la red. La red residual profunda se puede ver como una
pila de unidades residuales (RU), donde cada unidad residual es una pequeña
red neuronal con una conexión de salto.
Machine Translated by Google
Figura 14­16. Red neuronal profunda regular (izquierda) y red residual profunda (derecha)
Ahora veamos la arquitectura de ResNet (ver Figura 14­17). Es
sorprendentemente simple. Comienza y termina exactamente como GoogLeNet
(excepto que no tiene una capa de abandono), y en el medio hay solo una pila muy
profunda de unidades residuales simples. Cada unidad residual está compuesta por dos
capas convolucionales (¡y ninguna capa de agrupamiento!), con Normalización
por Lotes (BN) y activación ReLU, utilizando núcleos 3 × 3 y preservando las dimensiones
espaciales (paso 1, relleno "mismo").
Machine Translated by Google
Figura 14­17. Arquitectura de ResNet
Tenga en cuenta que la cantidad de mapas de características se duplica cada pocas
unidades residuales, al mismo tiempo que su altura y ancho se reducen
a la mitad (utilizando una capa convolucional con paso 2). Cuando esto sucede, las
entradas no se pueden agregar directamente a las salidas de la unidad residual
porque no tienen la misma forma (por ejemplo, este problema afecta la conexión
de salto representada por la flecha discontinua en la Figura 14­17). Para resolver este
problema, las entradas se pasan a través de una capa convolucional 1 × 1 con paso 2
y la cantidad correcta de mapas de características de salida (consulte la Figura 14­18).
Machine Translated by Google
Figura 14­18. Omitir conexión al cambiar el tamaño y la profundidad del mapa de características
ResNet­34 es la ResNet con 34 capas (solo se cuentan las capas convolucionales y la
17
capa completamente conectada) que contiene 3 unidades residuales que generan 64
mapas de características, 4 RU con 128 mapas, 6 RU con 256 mapas y 3 RU con 512
mapas. Implementaremos esta arquitectura más adelante en este capítulo.
Las ResNets más profundas que eso, como ResNet­152, utilizan unidades
residuales ligeramente diferentes. En lugar de dos capas convolucionales de 3 × 3
con, digamos, 256 mapas de características, utilizan tres capas
convolucionales: primero una capa convolucional de 1 × 1 con solo 64 mapas de
características (4 veces menos), que actúa como una capa de cuello de botella (como ya
se discutió), luego una capa de 3 × 3 con 64 mapas de características y, finalmente, otra
capa convolucional de 1 × 1 con 256 mapas de características (4 veces 64) que restaura
la profundidad original. ResNet­152 contiene 3 RU de este tipo que generan 256 mapas,
luego 8 RU con 512 mapas, la friolera de 36 RU con 1024 mapas y, finalmente, 3 RU con 2048 mapas.
NOTA
18
El origen de Google v4 La arquitectura
fusionó las ideas de GoogLeNet y ResNet y logró una tasa de
error dentro de los cinco primeros cercana al 3% en la clasificación de ImageNet.
Machine Translated by Google
Xception Otra
variante de la arquitectura de GoogLeNet que vale la pena destacar es Xception
19
(que significa Extreme Inception) fue propuesto en 2016 por François Chollet (el
autor de Keras), y superó significativamente a Inception­v3 en una enorme tarea de
visión (350 millones de imágenes y 17.000 clases). Al igual que Inception­v4, fusiona
las ideas de GoogLeNet y ResNet, pero reemplaza los módulos de inicio
con un tipo especial de capa llamada capa de convolución separable en
profundidad (o capa de convolución separable para abreviar). Estas capas se habían
20 antes en algunas arquitecturas de CNN, pero no eran tan centrales
utilizado
como en la arquitectura Xception. Mientras que una capa convolucional
regular utiliza filtros que intentan capturar simultáneamente patrones espaciales
(por ejemplo, un óvalo) y patrones de canales cruzados (por ejemplo, boca +
nariz + ojos = cara), una capa convolucional separable hace la fuerte suposición
de que los patrones espaciales y los patrones de canales cruzados se
pueden modelar por separado (ver Figura 14­19). Por lo tanto, se compone de dos
partes: la primera parte aplica un único filtro espacial para cada mapa de
características de entrada, luego la segunda parte busca exclusivamente patrones
de canales cruzados: es simplemente una capa convolucional regular con filtros 1 × 1.
Machine Translated by Google
Figura 14­19. Capa convolucional separable en profundidad
Dado que las capas convolucionales separables solo tienen un filtro espacial por
canal de entrada, debe evitar usarlas después de capas que tienen muy
pocos canales, como la capa de entrada (eso es lo que representa la Figura
14­19 , pero es solo para fines ilustrativos). Por este motivo, la arquitectura
Xception comienza con 2 capas convolucionales regulares, pero luego el resto de
la arquitectura usa solo convoluciones separables (34 en total), más algunas
capas de agrupamiento máximo y las capas finales habituales (una capa de
agrupamiento promedio global y una capa de salida densa).
Quizás te preguntes por qué se considera a Xception una variante de GoogLeNet,
ya que no contiene ningún módulo de inicio. Bueno, como comentamos antes, un
módulo de inicio contiene capas convolucionales con filtros 1 × 1: estos buscan
exclusivamente patrones de canales cruzados. Sin embargo, las capas
convolucionales que se encuentran encima de ellos son capas convolucionales
regulares que buscan patrones espaciales y de canales cruzados. Por lo
tanto, puedes pensar en un módulo de inicio como un intermedio entre una capa
convolucional regular (que considera patrones espaciales y patrones de canales
cruzados de manera conjunta) y una capa convolucional separable (que los considera por separado
Machine Translated by Google
En la práctica, parece que las capas convolucionales separables generalmente funcionan
mejor.
CONSEJO
Las capas convolucionales separables utilizan menos parámetros, menos memoria y
menos cálculos que las capas convolucionales regulares y, en general, incluso funcionan mejor,
por lo que debería considerar usarlas de forma predeterminada (excepto después de capas con
pocos canales).
El equipo CUImage de la Universidad China de Hong Kong ganó el desafío ILSVRC
2016. Utilizaron un conjunto de muchas técnicas diferentes, incluido un sofisticado
sistema de detección de objetos.
21
llamado GBD­Net, para conseguir una tasa de error entre las cinco primeras por debajo
del 3%. Aunque este resultado es indudablemente impresionante, la complejidad de la
solución contrasta con la simplicidad de ResNets. Es más, un año después, otra
arquitectura bastante sencilla rindió incluso mejor, como veremos
ahora.
SENet
La arquitectura ganadora en el desafío ILSVRC 2017 fue la red Squeeze­and­Excitation
22
(SENet). Esta arquitectura extiende arquitecturas existentes como las redes de inicio
y ResNets, y mejora su rendimiento. Esto le permitió a SENet ganar la competencia
con una sorprendente tasa de error de 2,25 % entre los cinco primeros. Las
versiones extendidas de las redes de inicio y ResNets se denominan SE­Inception y SE­
ResNet, respectivamente. El impulso proviene del hecho de que una SENet
agrega una pequeña red neuronal, llamada bloque SE, a cada unidad en la arquitectura
original (es decir, cada módulo de inicio o cada unidad residual), como se
muestra en la Figura 14­20.
Machine Translated by Google
Figura 14­20. Módulo SE­Inception (izquierda) y unidad SE­ResNet (derecha)
Un bloque SE analiza la salida de la unidad a la que está conectado, centrándose
exclusivamente en la dimensión de profundidad (no busca ningún patrón
espacial), y aprende qué características suelen ser más activas juntas. Luego utiliza
esta información para recalibrar los mapas de características, como se muestra
en la Figura 14­21. Por ejemplo, un bloque SE puede aprender que las bocas, narices
y ojos suelen aparecer juntos en las imágenes: si ve una boca y una nariz, debe
esperar ver ojos t
Descargar