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