• No se han encontrado resultados

Implementación de un algoritmo de aprendizaje automático en Apache Spark

N/A
N/A
Protected

Academic year: 2020

Share "Implementación de un algoritmo de aprendizaje automático en Apache Spark"

Copied!
65
0
0

Texto completo

(1)Universidad Central “Marta Abreu” de Las Villas Facultad de Matemática Física y Computación Centro de Estudios de Informática. Trabajo de Diploma. Implementación de un algoritmo de aprendizaje automático en Apache Spark. Autor: Ricardo Sánchez Alba Tutor: Dr. Carlos Morell Pérez. Santa Clara 2017 “Año 59 de la Revolución”.

(2) Hago constar que el presente Trabajo de Diploma fue realizado en la Universidad Central “Marta Abreu” de Las Villas como parte de la culminación de estudios de la especialidad de Licenciatura en Ciencia de la Computación, autorizando a que el mismo sea utilizado por la Institución, para los fines que estime conveniente, tanto de forma parcial como total y que además no podrá ser presentado en eventos, ni publicados sin autorización de la Universidad.. Ricardo Sánchez Alba Autor. Fecha. Los abajo firmantes certificamos que el presente trabajo ha sido realizado según acuerdo de la dirección de nuestro centro y el mismo cumple con los requisitos que debe tener un trabajo de esta envergadura referido a la temática señalada.. Ricardo Sánchez Alba Autor. Fecha. Dra.Gheisa Ferreira Lorenzo Jefa del Departmento. Fecha. Dr.Carlos Morell Pérez Jefe del Seminario. Fecha.

(3) PENSAMIENTO. “Si se coloca el centro de gravedad de la vida no en la vida, sino en el «más allá» -en la nada-, se le quita a la vida en general el centro de gravedad”.. Friedrich Wilhelm Nietzsche. i.

(4) DEDICATORIA. Al maestro.. ii.

(5) AGRADECIMIENTOS. A todos los que confiaron en mí, a mi familia, mi pareja, mis amigos. Al café.. iii.

(6) RESUMEN. El análisis de grandes cantidades de datos, así como la extracción de conocimiento útil de estos constituye en la actualidad un reto ya que cada día crecen velozmente los volúmenes de información generada y se necesitan programas capaces de realizar esta tarea en poco tiempo. Durante varios años frameworks de código abierto han sido utilizados para la aplicación de técnicas de aprendizaje automático en pequeños volúmenes de datos, pero la necesidad creciente de la industria ha dado como consecuencia una evolución en el área del cómputo distribuido, surgiendo así herramientas como Apache Hadoop y Apache Spark siendo éste último entre 10 y 100 veces más rápido que su antecesor. En este trabajo se propone un procedimiento general para la inclusión de nuevos algoritmos de aprendizaje automático en el framework Apache Spark y se implementa un algoritmo de regresión lineal con el fin de validar la metodología propuesta. Se realizaron una serie de experimentos al software implementado que permitieron valorar las ventajas del framework Apache Spark para reducir significativamente los tiempos de ejecución cuando este tipo de algoritmo se somete al procesamiento de cantidades masivas de datos.. iv.

(7) ABSTRACT. The analysis of large amounts of data, as well as the extraction of useful knowledge of these, is now a challenge as each day the volumes of information generated grow rapidly and programs are needed that can perform this task in a short time. For several years Open source frameworks have been used for the application of automated learning techniques in small volumes of data, but the growing need of the industry has resulted in an evolution in the area of distributed computing, resulting in tools such as Apache Hadoop and Apache Spark The latter being between 10 and 100 times faster than its predecessor. In this paper we propose a general procedure for the inclusion of new algorithms of automatic learning in the Apache Spark framework and a linear regression algorithm is implemented in order to validate the proposed methodology. A series of experiments were performed on the implemented software that allowed to evaluate the advantages of the Apache Spark framework to significantly reduce execution times when this type of algorithm is submitted to the processing of massive amounts of data.. v.

(8) TABLA DE CONTENIDO Página PENSAMIENTO . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .. i. DEDICATORIA . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .. ii. AGRADECIMIENTOS . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .. iii. RESUMEN . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .. iv. ABSTRACT . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .. v. INTRODUCCIÓN . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .. 1. CAPÍTULO 1 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .. 4. 1.. Marco teórico . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .. 5. 1.1.. Apache Spark . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .. 5. 1.2.. Componentes de Spark . . . . . . . . . . . . . . . . . . . . . . . . . . .. 7. 1.3.. Clústers de Spark . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .. 8. 1.4.. El modelo de programación de Spark . . . . . . . . . . . . . . . . . . .. 9. 1.4.1. Resilient Distributed Datasets. . . . . . . . . . . . . . . . . . . .. 9. 1.4.2. Spark SQL, DataFrames y Datasets . . . . . . . . . . . . . . . .. 10. 1.4.3. Operaciones en Spark . . . . . . . . . . . . . . . . . . . . . . . .. 10. 1.4.4. Ventajas de la evaluación perezosa . . . . . . . . . . . . . . . . .. 11. 1.4.5. Manejo de memoria y persistencia en memoria . . . . . . . . . .. 11. Aprendizaje Automático con Spark . . . . . . . . . . . . . . . . . . . .. 12. 1.5.1. MLlib vs ML . . . . . . . . . . . . . . . . . . . . . . . . . . . . .. 12. 1.5.2. DataFrame . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .. 13. 1.5.3. Transformers . . . . . . . . . . . . . . . . . . . . . . . . . . . . .. 14. 1.5.4. Estimators . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .. 14. 1.5.5. Pipeline. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .. 14. 1.5.6. Parámetros . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .. 17. 1.5.. vi.

(9) 1.6.. Métodos Lineales - API basada en RDD . . . . . . . . . . . . . . . . .. 18. 1.6.1. Formulación matemática . . . . . . . . . . . . . . . . . . . . . .. 18. 1.6.2. Funciones de pérdida . . . . . . . . . . . . . . . . . . . . . . . .. 19. 1.6.3. Regularizadores . . . . . . . . . . . . . . . . . . . . . . . . . . .. 19. Optimización . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .. 20. 1.7.1. Descenso de gradiente . . . . . . . . . . . . . . . . . . . . . . . .. 21. 1.7.2. Descenso de gradiente estocástico (SGD) . . . . . . . . . . . . .. 21. 1.7.3. Esquemas de actualización para SGD distribuido . . . . . . . . .. 22. 1.7.4. BFGS de memoria limitada (L-BFGS) . . . . . . . . . . . . . . .. 23. Implementación en MLlib. . . . . . . . . . . . . . . . . . . . . . . . . .. 23. 1.8.1. Descenso de gradiente y descenso de gradiente estocástico . . . .. 23. 1.8.2. L-BFGS . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .. 24. CAPÍTULO 2 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .. 26. 1.7.. 1.8.. 2.. Implementación de un modelo de Regresion Lineal personalizado utilizando Spark ML . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .. 27. 2.1.. Modelo de herencia de clases para la API de Spark ML . . . . . . . . .. 28. 2.2.. Pasos propuestos para el ajuste de un procedimiento de Aprendizaje Automático al modelo de Pipeline de Spark ML . . . . . . . . . . . .. 29. Descripción matemática del algoritmo implementado. . . . . . . . . . .. 30. 2.3.1. Ecuación normal . . . . . . . . . . . . . . . . . . . . . . . . . . .. 30. 2.3.2. Redefinición de la multiplicación de matrices para permitir la escalabilidad del cómputo paralelo . . . . . . . . . . . . . . . .. 31. Características y diseño general de la implementación . . . . . . . . . .. 33. 2.4.1. Principales clases y funciones implementadas . . . . . . . . . . .. 33. Diagramas de clases asociados a CustomLinearRegression y CustomLinearRegressionModel . . . . . . . . . . . . . . . . . . . . . . . . . . .. 37. CAPÍTULO 3 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .. 38. 3.. .. 39. Preparación del ambiente de desarrollo . . . . . . . . . . . . . . . . . .. 39. 3.1.1. Creación de un proyecto de Scala con Spark en el IDE IntelliJ IDEA . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .. 39. 3.1.2. Compilación y creación del archivo .jar para ser lanzado en un clúster de Spark mediante la herramienta spark-submit . . . .. 40. 2.3.. 2.4. 2.5.. HERRAMIENTAS, TÉCNICAS Y VALIDACIÓN DE LOS RESULTADOS 3.1.. vii.

(10) 3.1.3. Carga de los datasets hacia el clúster de Spark con HDFS y ejecución del programa en el clúster . . . . . . . . . . . . . . . .. 40. Pruebas preliminares . . . . . . . . . . . . . . . . . . . . . . . . . . . .. 41. 3.2.1. Pruebas realizadas al dataset E2006.train . . . . . . . . . . . . .. 41. 3.2.2. Pruebas realizadas al dataset YearPredictionMSD . . . . . . . .. 45. Pruebas de escalabilidad . . . . . . . . . . . . . . . . . . . . . . . . . .. 46. 3.3.1. Características del clúster utilizado: . . . . . . . . . . . . . . . .. 46. 3.3.2. Pruebas sobre el dataset YearPredictionMSD: . . . . . . . . . . .. 47. CONCLUSIONES Y RECOMENDACIONES . . . . . . . . . . . . . . . . . . . . .. 52. CONCLUSIONES . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .. 53. RECOMENDACIONES . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .. 54. 3.2.. 3.3.. viii.

(11) INTRODUCCIÓN. Cada día la cantidad de información de cualquier tipo aumenta aceleradamente y así mismo se hace necesario no solo almacenar esta información sino poder procesarla eficiente y eficazmente para extraer conocimiento útil de esta. Hastie et al. (2003) Construir modelos para detectar fraudes de tarjetas de crédito utilizando miles de características y billones de transacciones; recomendar inteligentemente millones de productos a millones de usuarios; estimar riesgos financieros utilizando simulaciones de portafolios incluyendo millones de instrumentos y manipular datos del genoma humano para detectar asociaciones genéticas con enfermedades eran tareas muy difíciles o imposibles de realizar hace 5 o 10 años atrás debido a la alta complejidad computacional y las cantidades de datos asociadas. El modelo de programación funcional MapReduce ha sido pionero en el tratamiento de estas grandes cantidades de datos dada su facilidad para ser implementado mediante sistemas de cómputo paralelos. Sistemas distribuidos como Apache Hadoop han encontrado así su camino y han tenido una amplia aplicación en múltiples empresas.Ryza et al. (2015). Antecedentes y planteamiento del problema. Durante un largo periodo de tiempo, frameworks de código abierto como R, PyData y Octave han sido utilizados para realizar rápidos análisis y construcciones de modelos viables sobre pequeños datasets. Pero un correcto aprovechamiento de estos frameworks sería extenderlos para ejecutarlos en múltiples computadoras, mantener sus modelos de programación y reescribir sus interioridades para lidiar eficientemente con características distribuidas. Sin embargo, muchos elementos que se asumían por estar en sistemas con un solo 1.

(12) INTRODUCCIÓN. 2. nodo requieren ser repensados para la computación distribuida. Por ejemplo, porque los datos deben estar particionados a lo largo de múltiples nodos en un clúster de computadoras, algoritmos que tenían amplias dependencias de datos sufrirían el hecho de que las tasas de transferencia de la red son más lentas que el acceso a memoria. Así como que a medida que aumentan la cantidad de computadoras trabajando en un problema, aumenta la probabilidad de fallo.Ryza et al. (2015). Como parte de las investigaciones que se desarrollan en el laboratorio de Inteligencia Artificial del Centro de Investigaciones de Informática (CEI) se desea poder utilizar el ambiente de computación distribuida Apache Spark para disminuir significativamente los tiempos de ejecución de algoritmos de Aprendizaje Automatizado cuando estos se someten a procesamientos de cantidades masivas de datos. Apache Spark cuenta con un módulo en desarrollo concebido para el trabajo con algoritmos de esta naturaleza, dentro de este, se tiene un conjunto de algoritmos de aprendizaje automático y utilidades afines que se brindan como un paquete pre-elaborado. Es de gran utilidad conocer el procedimiento a seguir cuando un investigador desea incluir un algoritmo propio y que este aproveche las ventajas de la arquitectura subyacente definida por los desarrolladores de Apache Spark.. A partir de la problemática descrita se plantea la siguiente pregunta de investigación: ¿Cuál es el procedimiento adecuado para incluir nuevos algoritmos de aprendizaje automático al ambiente de cómputo distribuido Apache Spark, de forma sencilla y utilizando las facilidades adecuadas? De esta forma se desea desarrollar un algoritmo de aprendizaje automático en Apache Spark mediante la definición de un conjunto de pasos generales para ajustar estos procedimientos al modelo de programación de este framework, lo cual constituye el objetivo general de este trabajo. Para dar cumplimiento al anterior objetivo general se proponen los objetivos específicos.

(13) INTRODUCCIÓN. 3. siguientes:. Determinar las características de la arquitectura de Apache Spark que permiten la implementación de algoritmos de aprendizaje automático. Proponer un conjunto de pasos para la implementación de algoritmos de aprendizaje automático en Apache Spark. Implementar un algoritmo de aprendizaje automático en Apache Spark utilizando la arquitectura del modelo del framework y los pasos propuestos en este trabajo. Evaluar el desempeño del algoritmo bajo el ambiente distribuido de Spark mediante la realización de experimentos..

(14) CAPÍTULO 1.

(15) Capítulo 1 MARCO TEÓRICO. 1.1.. Apache Spark. Apache Spark es un framework para la computación distribuida; este framework tiene como propósito simplificar la escritura de programas que se ejecutan en paralelo utilizando varios núcleos en un cluster de computadoras. El mismo intenta abstraer la tarea de la planificación de recursos, envíos de trabajo, ejecución, rastreo y comunicación entre nodos, así como las operaciones de bajo nivel que son inherentes al procesamiento de datos en paralelo. De esta forma, es similar a otros frameworks de procesamiento distribuido como Apache Hadoop; no obstante, la arquitectura subyacente es de cierta forma diferente. Pentreath. (2015). Spark comenzó como un proyecto de investigación en la Universidad de California, Berkeley. La universidad estaba concentrada en el caso de uso de algoritmos de aprendizaje automatizado en arquitecturas distribuidas. Consecuentemente, se diseñó desde su inicio para el alto desempeño en aplicaciones de naturaleza iterativa, en las cuales el mismo dataset se accede múltiples veces. Este desempeño se logra fundamentalmente mediante la carga de estos dataset en memoria, combinado con una baja latencia y overhead para emprender tareas de cómputo paralelo. Junto a otras características como la tolerancia a fallos, estructuras de datos flexibles de memoria distribuida y una poderosa y funcional API, Spark ha probado ser significativamente útil para un amplio rango de tareas de. 5.

(16) MARCO TEÓRICO. 6. procesamiento de datos a gran escala, así como para el aprendizaje automático y analísis iterativos. Pentreath. (2015). Spark se puede ejecutar de cuatro modos diferentes: El modo local auto-sostenido, donde todos los procesos de Spark corren en la misma Java Virtual Machine (JVM). El modo clúster auto-sostenido, usando el framework de planificación de trabajo embebido de Spark. Usando Mesos, un popular framework de código abierto para cluster-computing. Usando YARN (comunmente referido como NextGen MapReduce). Pentreath. (2015). Figura 1–1: Diagrama del ecosistema de procesamiento de datos incluyendo Spark..

(17) MARCO TEÓRICO. 7 1.2.. Componentes de Spark. Spark provee un lenguaje de consultas de alto nivel para el procesamiento de datos. En el ecosistema de Spark, Spark Core es el componente principal de procesamiento de datos, el mismo tiene APIs en Scala, Java, Python y R. Además incluye otros componentes de primer orden como Spark SQL, Spark MLlib, Spark ML, Spark Streaming y GraphX los cuales proveen funcionalidades de procesamiento más específicas.. Figura 1–2: Principales componentes de Spark. Algunos de estos componentes tienen las mismas convenciones de implementación de Spark Core, otras tienen consideraciones particulares. Un ejemplo de esto es que Spark SQL utiliza un motor de optimización de consultas diferente.. Spark SQL introduce dos tipos de datos semi-estructurados en forma de interfaces, DataFrames y Datasets . Este componente constituye el futuro de Spark, con opciones de almacenamiento más eficientes, optimizadores avanzados y operaciones directas en datos serializados. Este componente es fundamental para extraer el mejor desempeño de Spark.Karau (2017). ML y MLlib son los componentes de aprendizaje automático de Spark. MLlib fue escrito junto con Spark. Spark ML está todavía en las primeras etapas de desarrollo y existe únicamente luego de la versión 1.2 de Spark. Spark ML provee una API de más alto nivel que MLlib y tiene el objetivo de facilitar la construcción práctica de cadenas de procesos de.

(18) MARCO TEÓRICO. 8. aprendizaje automático. La comunidad de Spark planea concentrarse solamente en Spark ML y no en MLlib, quedando este último caducado en un estado de corrección de errores solamente. Estos tienen consideraciones de desempeño individuales ya que Spark MLlib está implementado sobre Spark Core y ML está desarrollado sobre Spark SQL.. Spark Streaming está centrado en la manipulación de datos en flujo. Esta contiene dos APIs, DStreams y otra basada en streaming estructurado que se encuentra en un estado de desarrollo alpha la cual utiliza DataFrames de Spark SQL.Karau (2017). GraphX está concentrado en el procesamiento de grafos. Es uno de los componentes menos desarrollados de Spark.Karau (2017). 1.3.. Clústers de Spark. Un clúster de Spark está constituido por dos tipos de procesos: un programa conductor y múltiples ejecutores. En el modo local, todos estos procesos están corriendo en la misma JVM. En un clúster de computadoras estos procesos corren usualmente en nodos por separado. Pentreath. (2015). Figura 1–3: Clúster de Spark.

(19) MARCO TEÓRICO 1.4.. 9 El modelo de programación de Spark. Spark puede ser utilizado desde varios lenguajes de programación, entre ellos Python, Java y Scala. En este documento se utilizará Scala para las secciones de código y las anotaciones generales de programación dada la claridad sintáctica que este presenta en este tipo de programa.. Generalmente un programa en Spark se inicia con un dataset, que usualmente esta distribuido, utilizando por ejemplo Hadoop Distributed File System (HDFS) y usualmente esta relacionado con los pasos: Definir un conjunto de transformaciones en datasets de entrada. Invocar acciones que devuelven el dataset transformado en almacenamiento persistente o retornan resultados hacia la memoria local. Ejecutar procesamientos locales que operan en los resultados computados de manera distribuida, lo cual puede ayudar a decidir que transformaciones y acciones realizar posteriormente. Ryza et al. (2015) 1.4.1.. Resilient Distributed Datasets. El núcleo de Spark es un concepto denominado Resilient Distributed Datasets (RDD). Un RDD es una colección de registros que están distribuidos o particionados a través múltiples nodos en un clúster (para los propósitos del modo local de Spark esto puede ser pensado de la misma manera). Un RDD en Spark es tolerante a fallos; esto significa que si un nodo o una tarea falla (por alguna razón aparte de código erróneo del usuario, como fallas de hardware, perdida de comunicación, entre otras), el RDD puede ser reconstruido automáticamente desde los restantes nodos y el trabajo se mantendrá intacto, esto es posible ya que los RDDs autocontienen toda la información necesaria para su reconstrucción.

(20) MARCO TEÓRICO. 10. mediante sus dependencias. Pentreath. (2015). 1.4.2.. Spark SQL, DataFrames y Datasets. A diferencia de la API basada en RDD, las interfaces brindadas por Spark SQL proveen a Spark mas información acerca de las estructuras tanto de los datos como del cómputo que se está realizando. Internamente, Spark SQL utiliza esta información extra para realizar optimizaciones en la manipulación de los datos. Un Dataset es una colección de datos distribuida. Se introdujo en Spark 1.6 para proveer los beneficios de los RDD(tipado fuerte y posibilidad de utilizar poderosas funciones lambda) junto a los beneficios de el motor de ejecución optimizado de Spark SQL. Un DataFrame es un Dataset organizado en columnas nombradas. Esto es conceptualmente equivalente a una tabla en una base de datos relacional. Los DataFrames pueden ser construidos desde una amplia colección de orígenes, como campos de datos estructurados, tablas en Hive, bases de datos externas o RDDs existentes. 1.4.3.. Operaciones en Spark. Las operaciones que se realizan sobre las colecciones distribuidas de Spark se pueden clasificar en dos grupos: las transformaciones y las acciones. Una transformación aplica una función sobre todos los registros de un RDD modificándolos de alguna manera, al estilo de la operación map de los paradigmas de programación funcional. Una acción generalmente realiza alguna acción de agregación sobre estos registros y devuelve un resultado concreto al programa principal, lo cuál es coherente con la operación reduce.. Una característica medular de las transformaciones es que estas presentan lazy-evaluation o evaluación perezosa. Luego de llamar a una transformación Spark no ejecuta el cómputo en las particiones hasta que alguna acción se ejecuta, en este momento comienza la.

(21) MARCO TEÓRICO. 11. construcción de un grafo acíclico dirigido basado en las dependencias entre las transformaciones de las particiones. Spark evalúa las acciones mediante el cómputo previo de las transformaciones, en un cómputo retrospectivo.. 1.4.4.. Ventajas de la evaluación perezosa. La evaluación perezosa permite a Spark combinar operaciones que no necesitan comunicación con el programa principal, por ejemplo operaciones map y filter solo necesitarían recorrer los datos una vez ejecutando ambas operaciones combinadas en vez de ejecutar estas operaciones por separado lo cual requeriría recorrer los datos varias veces. El paradigma de evaluación perezosa de Spark también permite facilidades de implementación ya que permite encadenar procesamientos y dejar la tarea de su consolidación al motor de evaluación de Spark. La inmutabilidad de estas estructuras, al estar implementadas respetando las convenciones de la programación funcional permiten que este framework brinde la facilidad de escribir programas utilizando paralelización implícita, esta es un ventaja fundamental ya que reduce significativamente la escritura y el entendimiento de programas paralelos al manifiestar un nivel de abstracción más alto que frameworks distribuidos anteriores.. 1.4.5.. Manejo de memoria y persistencia en memoria. La ventaja de desempeño de Spark respecto MapReduce es substancial en casos donde es necesario cálculos repetidos, esto es posible ya que Spark utiliza persistencia de datos en memoria. Spark permite la opción de que los ejecutores conserven los datos cargados en memoria en vez de acceder repetidas veces en el disco, lo cual reduce el tiempo de acceso significativamente cuando estos datos son accedidos repetidas veces. Spark provee tres opciones para el manejo de memoria, de manera serializada en memoria, sin serializar en memoria y una tercera en disco. Cada una con sus particularidades de rendimiento..

(22) MARCO TEÓRICO. 12. La función persist() de la clase RDD permite al usuario controlar la forma de almacenamiento del RDD. Por defecto, persist() almacena un RDD como objeto deserializado en memoria, pero esta función puede recibir varios parámetros para controlar la forma de almacenamiento. Si el espacio ocupado por un RDD es requerido para el cómputo o persistencia de una nueva partición, la implementación por defecto de los RDD desecha la partición de uso menos reciente, según la política LRU. No obstante este comportamiento puede ser configurado mediante la función persistancePriority() de la class RDD.Karau (2017). 1.5.. Aprendizaje Automático con Spark. Spark contiene dos bibliotecas de aprendizaje automático, Spark MLlib y Spark ML con APIs marcadamente diferentes, en conjunto incluyen utilidades para clasificación, regresión, agrupamiento, filtrado colaborativo, reducción de dimensionalidad, así como las primitivas de optimización subyacentes. Estas bibliotecas heredan consideraciones de rendimiento de la API basada en RDD y la basada en Datasets. MLlib es la primera de las dos pero se encuentra en un estado de mantenimiento y corrección de errores solamente. Spark ML es la API nueva, donde el desarrollo activo esta tomando lugar.Karau (2017). 1.5.1.. MLlib vs ML. Estructuralmente los algoritmos de aprendizaje automático que se quieran implementar deben estar dentro de estas bibliotecas o bien utilizar interfaces públicas brindadas por estas APIs. Esta sección esta enfocada a mostrar los diferentes puntos de vista necesarios para la elección de la API a utilizar, y enfatiza principalmente en la implementación usando Spark ML..

(23) MARCO TEÓRICO. 13. Los diferentes algoritmos de aprendizaje automático están agrupados en la API de Spark ML según su tipo en los paquetes: classification , regression , recommendation y clustering . Adicionalmente a los paquetes proveídos por las distribuciones estándar de Spark existen paquetes de algoritmos construidos por la comunidad de desarrollo de Spark y están concentrados en el sitio web spark-packages.org.. Una de las diferencias básicas de las APIs está en los tipos de datos con los que estas trabajan, MLlib usa RDD y ML utiliza DataFrames y Datasets de Spark SQL, esta diferencia no tiene una importancia marcada ya que estas APIs trabajan con RDD y Datasets de Vectores respectivamente, los cuales son fácilmente transformables entre ellos.. Desde el punto de vista del diseño, Spark MLlib está enfocado en proveer un conjunto de algoritmos para los usuarios, dejando a un lado la secuencia de procesamiento previo de limpieza, preparación y selección de características, lo cual queda en manos del usuario de la biblioteca. Spark ML está enfocada en dar a conocer una API basada en el concepto de Pipelines o tuberías las cuales no son más que secuencias de procesamientos que van desde la preparación inicial de los datos hasta las etapas finales de selección y evaluación de modelos.. 1.5.2.. DataFrame. El aprendizaje automático se puede aplicar a una amplia variedad de tipos de datos, como vectores, texto, imágenes y datos estructurados. Esta API adopta el DataFrame de Spark SQL con el fin de soportar una variedad de tipos de datos. Además de los tipos enumerados en la guía Spark SQL, un DataFrame también puede utilizar el tipo Vector. Un DataFrame puede crearse implícita o explícitamente desde un RDD..

(24) MARCO TEÓRICO 1.5.3.. 14. Transformers. Un Transformers es una abstracción que incluye transformadores de características y modelos aprendidos. Técnicamente, un Transformer implementa un método transform(), el cual convierte un DataFrame en otro, generalmente agregándole una o más columnas. Por ejemplo: Un transformador de características tomaría un DataFrame , leería una columna (ej. texto), la mapearía a una nueva columna (ej. vector de características), y devolvería un nuevo DataFrame con la columna mapeada adjunta. Un modelo de aprendizaje puede tomar un DataFrame , leer la columna que contiene los vectores de características, predecir la clase para cada vector de características y devolver un nuevo DataFrame con la columna de las clases adjunta.. 1.5.4.. Estimators. Un estimador abstrae el concepto de algoritmo de aprendizaje o de cualquier algoritmo que se aplique o se entrene con datos. Técnicamente, un Estimator implementa un método fit() que acepta un DataFrame y produce un Model, el cual es un Transformer . Por ejemplo, un algoritmo de aprendizaje como LogisticRegression es un Estimator , y llamando al método fit() entrena a un LogisticRegressionModel, el cual es un Model y por lo tanto un Transformer .. 1.5.5.. Pipeline. En aprendizaje automático es común ejecutar una secuencia de algoritmos para procesar y aprender de datos. Por ejemplo: un workflow para un procesamiento simple de texto pudiera incluir varias etapas: • Separar el texto de cada documento en palabras. • Convertir cada palabra en un vector numérico de características. • Aprender un modelo de predicción usando los vectores de características y las clases..

(25) MARCO TEÓRICO. 15. Spark ML representa este flujo de trabajo como un Pipeline , el cual consiste en una secuencia de PipelineStages ( Transformer s y Estimator s) que se ejecutan en un orden específico.. Un Pipeline se especifica como una secuencia de etapas, cada una es un Transformer o un Estimator . Estas etapas se ejecutan en orden y el DataFrame de entrada se transforma a medida que transita cada etapa. En las etapas de tipo Transformer se llama al método transform() en los DataFrames . En las etapas de tipo Estimator , el método fit() es el llamado para producir un Transformer (el cual forma parte del PipelineModel), y el método transform() de ese Transformer es llamado en el DataFrame . Ej, para el flujo de procesamiento de texto:. Figura 1–4: Pipeline. En la figura, la fila superior representa una Pipeline con tres etapas. Las dos primeras (. Tokenizer. y. HashingTF ) son. Transformers. (azul), y el tercero. ( LogisticRegression ) es un Estimator (rojo). La fila inferior representa los datos que fluyen a través de la tubería, donde los cilindros indican DataFrames . El método Pipeline.fit() se llama en el DataFrame original, que contiene documentos de texto plano y clases. El método Tokenizer.transform() divide los documentos de texto plano en palabras, añadiendo una nueva columna con palabras al DataFrame . El método HashingTF.transform() convierte la columna de palabras en vectores de características, añadiendo una nueva columna con los vectores al DataFrame . Ahora, puesto que LogisticRegression es un Estimator , la tubería primero llama al método LogisticRegression.fit() para producir un.

(26) MARCO TEÓRICO. 16. LogisticRegressionModel . Si el Pipeline hubiese tenido más etapas, habría llamado al método LogisticRegressionModel.transform() en el DataFrame antes antes de pasarlo a la siguiente etapa.ASF (2017) Una Pipeline es un Estimator . Por lo tanto, luego de ejecutarse su método fit() se produce un PipelineModel , el cual es un Transformer . Este modelo se usa en el momento de prueba. Por ejemplo:. Figura 1–5: PipelineModel. En la figura de arriba, el PipelineModel tiene el mismo número de etapas que el Pipeline original, pero todos los Estimators en el Pipeline original en este caso son Transformers . Cuando el método transform() del PipelineModel es llamado en un dataset de prueba, los datos son enviados en orden a través del Pipeline ajustado. El método transform() de cada etapa actualiza el dataset y lo envía a la próxima etapa. Las Pipelines y los PipelineModels ayudan a asegurarse de que los datos de entrenamiento y prueba pasan a través de procesamientos de características idénticos.. Detalles Las Pipelines se pueden constituir también en una topología no lineal, donde las etapas no formen una línea directa, siempre y cuando formen un grafo acíclico dirigido y en este caso las etapas deben ser especificadas siguiendo el ordenamiento topológico..

(27) MARCO TEÓRICO. 17. Como las Pipelines pueden operar en DataFrames con tipos variables, estas no pueden usar chequeo de tipos en tiempo de compilación por lo que se utiliza chequeo en tiempo de ejecución previo a que se ejecute realmente el Pipeline . Este chequeo de tipos se realiza utilizando el esquema DataFrame , una descripción de los tipos de datos de las columnas en el DataFrame . Las etapas de las Pipelines deben ser instancias únicas. Ej, la misma instancia myHashingTF no debería ser insertada dos veces en una Pipeline ya que las etapas de las Pipelines tienen un ID único. Sin embargo, diferentes instancias myHashingTF1 y myHashingTF2 (ambas de tipo HashingTF ) pueden ser insertadas en la misma Pipeline ya que diferentes instancias se crean con diferentes IDs.. 1.5.6.. Parámetros. Los estimadores y transformadores utilizan una API homogénea para la especificación de parámetros. Un Param es un parámetro nombrado con documentación autocontenida. Un ParamMap es un conjunto de pares (parametro, valor).. Existen dos formas fundamentales de pasar parámetros a un algoritmo: 1. Poner los parámetros en una instancia. Por ejemplo, si lr es una instancia de LogisticRegression , se podría llamar a lr.setMaxIter(10) para hacer que lr.fit() utilice un máximo de 10 iteraciones. 2. Enviarle un ParamMap a fit() o a transform . Cualquier parámetro en el ParamMap sobrescribirá parámetros previamente introducidos mediante métodos setters. Los parámetros pertenecen a instancias específicas de Estimators y Transformers . Por ejemplo, si se tienen dos instancias de LogisticRegression lr1 y lr2 , entonces se puede construir un ParamMap con el parámetro maxIter de ambos especificados de la forma:.

(28) MARCO TEÓRICO. 18. ParamMap(lr1.maxIter ->10, lr2.maxIter ->20) . Esto es útil si se tienen dos algoritmos con el parámetro maxIter en una Pipeline .. 1.6. 1.6.1.. Métodos Lineales - API basada en RDD. Formulación matemática. En la siguiente sección se exponen algunas de las funcionalidades matemáticas esenciales brindadas por MLlib mediante paquetes de algoritmos configurables.. Muchos métodos estándar de aprendizaje automático pueden ser formulados como un problema de optimización convexo, esto es, la tarea de hallar un mínimo de una función convexa f que depende de un vector variable w, el cual tiene d entradas. Formalmente, se puede escribir esto como el problema minw∈Rd f (w), donde la función objetivo tiene la forma: n 1∑ f (w) = λR(w) + L(w; xi , yi ). n i=1. Los vectores xi ∈ Rd son los datos de entrenamiento, para 1 ≤ i ≤ n, y yi ∈ R son sus clases correspondientes, las que se desea predecir. Se dice que el método es lineal si L(w; x, y) puede ser expresado como una función de wT x y y. Varios de los algoritmos de clasificación y regresión de spark.mllib caen en esta categoría.. La función objetivo f tiene dos partes: el regularizador, que controla la complejidad del modelo y la pérdida, que da una medida del error del modelo en los datos de entrenamiento. La función de pérdida L(w; .) es típicamente una función convexa en w. El parámetro de regularización ajustado λ ≥ 0 define la relación entre las dos finalidades de minimizar la pérdida (esto es, el error de entrenamiento) y minimizar la complejidad del modelo (esto es, evitar el sobreajuste)..

(29) MARCO TEÓRICO 1.6.2.. 19. Funciones de pérdida. La siguiente tabla resume las funciones de pérdida y sus gradientes o subgradientes para los métodos que spark.mllib soporta:. funciones de pérdida L(w; x, y). hinge loss. pérdida logística. max{0, 1 −. ywT x}, y. ∈ −1, +1. gradiente o subgradiente    −yx   0. log(1 + exp(−ywT x)), y ∈ −1, +1. pérdida cuadrática. 1 T 2 (w x. − y)2 , y ∈ R. (. si. ywT x < 1,. en otro caso.. −y 1 −. 1 1+exp(−ywT x). ). x. (wT x − y)x. Nota: Para la formulación matemática se han utilizado a conveniencia los valores -1 y +1 para la variable de clase binaria y, el valor de la clase negativa es tratada en spark.mllib como 0 para ser consecuentes con la clasificación multiclase.. 1.6.3.. Regularizadores. La finalidad del regularizador es potenciar modelos simples y evitar el sobreajuste. Los regularizadores suministrados por spark.mllib son:.

(30) MARCO TEÓRICO. 20 regularizador R(w). gradiente o subgradiente. 0. 0. L2. 1 ∥w∥22 2. w. L1. ∥w∥1. sign(w). α∥w∥1 + (1 − α) 12 ∥w∥22. αsign(w) + (1 − α)w. cero (sin regularizar). red elástica. Donde sign(w) es el vector consistente de los signos (±1) de todas las entradas de w. Los problemas regularizados con L2 son generalmente más fáciles de resolver que los regularizados con L1 dado el nivel de refinamiento. No obstante la regularización con L1 puede ayudar a promover esparcimiento en los pesos conduciendo a modelos más pequeños y más interpretables lo cual puede ser útil para la selección de rasgos. Red elástica es una combinación de las regularizaciones L1 y L2. No es recomendable entrenar modelos sin ninguna regularización, especialmente cuando el número de ejemplos de entrenamiento es pequeño. ASF (2017) 1.7.. Optimización. Los métodos lineales utilizan optimizaciones convexas para optimizar las funciones objetivo. spark.mllib utiliza dos métodos, SGD y L-BFGS. Actualmente, la mayoría de las API de algoritmos soportan Descenso de Gradiente Etocástico (SGD por sus siglas en inglés), y algunos otros soportan L-BFGS..

(31) MARCO TEÓRICO 1.7.1.. 21. Descenso de gradiente. El método más simple para resolver problemas de optimización de la forma minw∈Rd f (w) es el del descenso de gradiente. Estos métodos de optimización de primer orden (incluyendo descenso de gradiente y sus variantes estocásticas) están bien condicionados para cómputo distribuido y a gran escala.. El método de descenso de gradiente tiene como principal objetivo encontrar un mínimo local de una función, tomando iterativamente pasos en la dirección del descenso más pronunciado, lo cual es el opuesto de la derivada de la función en el punto actual. Si la función objetivo f no es diferenciable en todos los argumentos pero se mantiene convexa, entonces un sub-gradiente es la generalización natural para el gradiente y asume el rol de definir la dirección del paso. En cualquier caso, calcular el gradiente o el sub-gradiente de f computacionalmente es costoso ya que requiere una pasada total al dataset completo con el fin de calcular la contribución de todos los términos de pérdida.. 1.7.2.. Descenso de gradiente estocástico (SGD). Los problemas de optimización cuya función objetivo f está escrita como una suma están particularmente condicionados para ser resueltos utilizando SGD. En aprendizaje automático supervisado para la formulación se utiliza comúnmente: f (w) = λR(w) +. n 1∑ L(w; xi , yi ). n i=1. (1.1). esto es especialmente natural, ya que la pérdida total esta escrita como un promedio de las pérdidas individuales provenientes de cada punto. Un subgradiente estocástico es una elección aleatoria de un vector, que puede ser probablemente el subgradiente real de la función objetivo. Tomando un punto i ∈ [1..n] uniformemente aleatorio, obtenemos un subgradiente estocástico de (1) respecto a w.

(32) MARCO TEÓRICO. 22. de la siguiente forma: ′ fw,i = L′w,i + λRw′ ,. donde L′w,i ∈ Rd es un subgradiente de la parte de la función de pérdida determinado por el i-ésimo punto, esto es L′w,i ∈ regularizador R(w), esto es, Rw′ ∈. ∂ L(w; xi , yi ). ∂w. ∂ R(w). ∂w. Rw′ es un subgradiente del. El término Rw′ no depende de cuál pun-. to aleatorio se halla escogido. Teniendo en cuenta la opción aleatoria escogida de ′ i ∈ [1..n], tenemos que fw,i es un subgradiente del objetivo original f , lo cual significa ′ que E[fw,i ]∈. ∂ f (w). ∂w. Ejecutar SGD entonces se convierte en avanzar en la dirección del subgradiente esto′ cástico negativo fw,i , que es: ′ w(t+1) := w(t) − γfw,i. (1.2). Longitud de paso. El parámetro γ es la longitud de paso, la cual en la implementación por defecto se escoge decreciente con la raíz cuadrada del contador de iteraciones, esto es γ :=. √s t. en la t-ésima iteración, con el parámetro de entrada s (longitud de. paso). La selección de la mejor longitud de paso para el método SGD es delicado en la práctica y es un tema de investigación activo.ASF (2017). 1.7.3.. Esquemas de actualización para SGD distribuido. La implementación de SGD utilizada en GradientDescent utiliza un muestreo simple (distribuido) de los ejemplos de datos. Recuérdese que la pérdida como parte del problema de optimización (1) es. 1 n. ∑n i=1. L(w; xi , yi ), y por tanto. 1 n. ∑n i=1. L′w,i podría. ser el verdadero subgradiente. Dado que esto requeriría acceso total al dataset, el parámetro miniBatchFraction especifica cual fracción del total de datos usar en cambio. El promedio de los gradientes sobre este subconjunto definido como 1 ∑ ′ L , |S| i∈S w,i.

(33) MARCO TEÓRICO. 23. es un gradiente estocástico. S es el subconjunto de muestra, con tamaño |S| = miniBatchFraction ·n. En cada iteración el muestreo sobre el dataset distribuido (RDD), así como el cómputo de la suma de los resultados parciales de cada worker es realizado por las rutinas estándar de Spark.. Si a la fracción de puntos miniBatchFraction se le hace corresponder el valor 1 (original), entonces el paso resultante en cada iteración es el descenso de gradiente original. En ese caso no hay aleatoriedad ni varianza en las direcciones de paso utilizadas. En el otro extremo, si miniBatchFraction se escoge muy pequeño, por ejemplo, si se toma tal que |S| = miniBatchFraction ·n = 1, entonces el algoritmo es equivalente al SGD estándar. En este caso, la dirección del paso depende de la uniformidad del muestreo aleatorio del punto.. 1.7.4.. BFGS de memoria limitada (L-BFGS). L-BFGS es un algoritmo de optimización de la familia de los métodos quasi-Newton para resolver problemas de optimización de la forma minw∈Rd f (w). El método L-BFGS aproxima localmente la función objetivo como cuadrática, sin evaluar la segunda derivada parcial para construir la matriz Hessiana. La matriz Hessiana se aproxima por evaluaciones previas del gradiente, por lo que es permisible la escalabilidad vertical (por número de características de entrenamiento). Como resultado, L-BFGS logra a menudo convergencia rápida comparado con otras optimizaciones de primer orden.. 1.8. 1.8.1.. Implementación en MLlib. Descenso de gradiente y descenso de gradiente estocástico. Los métodos descenso de gradiente incluyendo descenso de subgradiente estocástico están incluidos como primitivas de bajo nivel en MLlib sobre las cuales se desarrollan varios algoritmos de aprendizaje..

(34) MARCO TEÓRICO. 24. La clase para SGD GradientDescent establece los siguientes parámetros:. • Gradient es la clase que calcula el gradiente estocástico de la función a optimizar. MLlib incluye clases de gradiente para funciones de pérdida comunes. La clase gradiente toma como entrada un ejemplo de entrenamiento, su clase y el valor del parámetro actual. • Updater es la clase que realiza el paso del descenso, esto es, actualizando los pesos en cada iteración, dado un gradiente de la componente de pérdida. Este es el encargado de realizar la actualización de la componente de regularización. MLlib incluye actualizadores para casos sin regularización, así como regularizadores L1 y L2. • stepSize es un valor escalar que denota el tamaño inicial del paso para el descenso del gradiente. Todos los actualizadores en MLlib utilizan un tamaño de √ paso en la t-ésima iteración igual a stepSize / t. • numIterations es el número de iteraciones a realizar. • regParam es el parámetro de regularización cuando se utiliza regularizaciones L1 o L2. • miniBatchFraction es la fracción del total de datos que se muestrea en cada iteración para el cálculo de la dirección del gradiente. 1.8.2.. L-BFGS. L-BFGS constituye actualmente solo una primitiva de optimización de bajo nivel y si se quiere utilizar en un algoritmo de aprendizaje automático como LogisticRegression o LinearRegression es necesario pasarle el gradiente de la función objetivo y el actualizador manualmente en vez de poder usar la API de entrenamiento de la manera que se hace con LogisticRegressionWithSGD . El método LBFGS.runLBFGS toma los siguientes parámetros:. • Gradient (ídem a SGD).

(35) MARCO TEÓRICO. 25. • Updater es la clase que efectúa el cálculo del gradiente y la pérdida de la función objetivo de la componente de regularización para L-BFGS. • numCorrections es el número de correcciones en la actualización de L-BFGS. Se recomienda 10. • maxNumIterations es el máximo número de iteraciones que L-BFGS ejecutará. • regParam es el parámetro usado para la regularización. • convergenceTol controla que tanto cambio relativo es permitido cuando se dice que L-BFGS converge. Este debe ser no negativo. Valores pequeños son menos tolerantes y causan mas iteraciones en la ejecución. El resultado es una tupla que contiene: como primer elemento, una matriz columna que contiene los pesos de cada característica y el segundo elemento es un arreglo que contiene la pérdida calculada para cada iteración..

(36) CAPÍTULO 2.

(37) Capítulo 2 IMPLEMENTACIÓN DE UN MODELO DE REGRESION LINEAL PERSONALIZADO UTILIZANDO SPARK ML. En orden de agregar nuevas funcionalidades a Spark ML se tienen dos opciones, la primera es implementar los algoritmos utilizando transformaciones a los RDD siguiendo las convenciones de Spark MLlib y seguir a partir de ahí, para Spark ML este acercamiento es válido también pero se pierden características integradas muy útiles, incluyendo la posibilidad de ejecutar meta-algoritmos como búsqueda de parámetros con cross-validación, la segunda vía es extender el modelo de Spark ML Pipeline.. Para agregar un nuevo algoritmo a una pipeline se necesita implementar ya sea Estimator o Transformer , las cuales implementan a su vez la interfaz PipelineStage . Para los algoritmos que no necesitan entrenamiento, se puede implementar la interfaz Transformer , y para algoritmos con entrenamiento la interfaz Estimator , ambas en org.apache.spark.ml . Holden Karau (2017). Además de las funciones transform o fit , todas las etapas de una pipeline necesitan proveer transformSchema y un constructor copy o implementar una clase que realice esta tarea. copy es utilizado para clonar la etapa actual, con cualquier parámetro adicional que se desee agregar.. 27.

(38) Implementación de un modelo de Regresion Lineal personalizado utilizando Spark ML 28 La función transformSchema es la encargada de producir la estructura bajo la cual se realiza la salida para cualquier conjunto de parámetros y un esquema de entrada. Además esta función es la encargada de validar si el esquema de entrada es adecuado para la etapa (por ejemplo, que la columna de entrada sea del tipo esperado).. 2.1.. Modelo de herencia de clases para la API de Spark ML. La API de Spark puede ser de cierta forma vista desde dos perspectivas la API pública y la API privada. Estas están delimitadas por el paquete declarado en los archivos de código donde están implementadas las clases. Las clases recomendadas para la extensión de Spark ML ( Estimator y Transfomer ) pueden ser heredadas de manera natural desde un proyecto de usuario siempre que se tengan las respectivas dependencias a la biblioteca estándar de Spark. Por otra parte, los desarrolladores de Spark implementan las nuevas funcionalidades dentro de los paquetes internos de Spark, en este caso org.apache.spark.ml que al mismo tiempo se encuentran en un nivel inferior en la jerarquía de clases respecto a las clases Estimator y Transformer , en lo que se refiere a la extensión del modelo de Pipelines.. Figura 2–1: Herencia asociada a Estimator.

(39) Implementación de un modelo de Regresion Lineal personalizado utilizando Spark ML 29. Figura 2–2: Herencia asociada a Transformer 2.2.. Pasos propuestos para el ajuste de un procedimiento de Aprendizaje Automático al modelo de Pipeline de Spark ML. Dada la homogeneidad estructural presentada por la API de Spark ML, las potencialidades de desempeño de las estructuras que este utiliza y la creciente comunidad de desarrolladores que se encuentran en una intensa labor de extensión de esta API se decidió utilizarla para el caso de estudio de la inclusión de un algoritmo de aprendizaje automático que funcionara como vehículo para ejemplificar el proceso de implementación. Para ello se confeccionaron una serie de pasos generales a seguir para ajustar un algoritmo que se desee implementar utilizando los beneficios de esta API. Básicamente estos pasos son:. Definir matemáticamente el algoritmo a implementar. Redefinir las estructuras y subrutinas asociadas para condicionarlas al modelado distribuido necesario para la paralelización. Esto es, condicionar la futura implementación utilizando DataFrame y Dataset de Spark SQL y el paradigma de programación funcional. Determinar si el algoritmo necesita entrenamiento o si es una transformación directa de los datos de entrada..

(40) Implementación de un modelo de Regresion Lineal personalizado utilizando Spark ML 30 En caso de necesitarse entrenamiento, implementar la interfaz Estimator de Spark ML en la cual el código del entrenamiento debe estar implementado en el método fit() Implementar la interfaz Transformer en cuyo método transform() se debe situar el código de la transformación de los datos. En caso de requerirse la integración del algoritmo como parte de una Pipeline se debe tener en cuentra la impementación de los métodos transformSchema() de ambas interfaces donde se explicita la transformación en la estructura de los datos en la entrada y salida de esa etapa de la Pipeline 2.3. 2.3.1.. Descripción matemática del algoritmo implementado. Ecuación normal. Se desea predecir el valor de una variable y mediante su aproximación a la función lineal: h(x) =. n ∑. θi xi = θT x,. i=0. siendo n el número de variables de entrada. Un método utilizado para escoger los parámetros θ es definir una función que estime qué tan cerca están los valores de h(x(i) ) respecto a los valores correspondientes de y (i) . Sea la función de costo: J(θ) =. m 1∑ (hθ (x(i) ) − y (i) )2 . 2 i=1. Esta función es comúnmente denominada función de costo de mínimos cuadrados. Sea X la matriz que contiene las instancias de entrenamiento y y el vector de valores de la función objetivo asociados a estas instancias. Una posible solución al problema de regresión lineal es utilizar la minimización explicita mediante el uso de la ecuación normal: θ = (X T X)−1 X T y, lo que es equivalente a la resolución del sistema lineal: X T Xθ = X T y.

(41) Implementación de un modelo de Regresion Lineal personalizado utilizando Spark ML 31 2.3.2.. Redefinición de la multiplicación de matrices para permitir la escalabilidad del cómputo paralelo. La resolución de la ecuación normal como método para calcular los valores de los parámetros θ incluye el cómputo de la multiplicación de matrices. Este procedimiento es implementado por varias bibliotecas locales de álgebra lineal dada la frecuencia de su utilización. Para un correcto aprovechamiento de las capacidades del cómputo distribuido en este frecuente procedimiento algebraico se hace necesario plantear la multiplicación de matrices de manera tal que las matrices puedan ser fraccionadas a lo largo de un clúster de computadoras así como poder computar porciones de la matriz resultado de manera distribuida.. Sea: . An,m.  a1,1   a  2,1 =  .  ..   . . a1,2 · · · a2,2 · · · .. .. . .. an,1 an,2 · · ·. . . a1,m .  b1,1      b a2,m   2,1 , Bm,n =    . ..   .. .      . an,m. b1,2. ···. b2,2 .. .. ··· .. .. bm,1 bm,2 · · ·. b1,n    b2,n    ..  .    . bm,n. La formulación de la multiplicación con el operador · del elemento (i, j) de la matriz (A·B) se define como:. (A · B)i,j =. ∑ k. ai,k bk,j.

(42) Implementación de un modelo de Regresion Lineal personalizado utilizando Spark ML 32. La multiplicación completa en una notación exhaustiva luciría así: . .  a1,1 b1,1 + a1,2 b2,1 + . . . + a1,m bm,1 . . . a1,1 b1,m + a1,2 b2,m + . . . + a1,m bm,m    a b + a b + ... + a b  2,1 1,1 2,2 2,1 2,m m,1 . . . a2,1 b1,m + a2,2 b2,m + . . . + a2,m bm,m (A · B) =   ..  .   . am,1 b1,1 + am,2 b2,1 + . . . + am,m bm,1 . . . am,1 b1,m + am,2 b2,m + . . . + am,m bm,m. Esta. formulación. puede.   a1,1 b1,1   (A·B) =   a2,1 b1,1  . ser. reescrita. de.  . la . siguiente.      ,     . manera:. . . . . . a1,1 b1,m   a1,2 b2,1. . . . a1,2 b2,m . . . . a1,m bm,m . .... .... ....      a2,1 b1,m  + a2,2 b2,1    . am,1 b1,1 . . . am,1 b1,m.  a1,m bm,1      a2,2 b2,m +. . .+  a2,m bm,1    . am,2 b2,1 . . . am,2 b2,m.   a2,m bm,m    . am,m bm,1 . . . am,m bm,m. Si utilizamos la definición de producto tensorial ⊗ la suma anterior puede ser escrita como:. (A · B) =. ∑. (⃗ ai ⊗ b⃗i ). i. Golub and van Loan (1996) Esta formulación nos permite realizar un fraccionamiento de las matrices operando de manera que estas pueden ser distribuidas en un clúster de computadoras pero acarrea la inconveniente de generar los pares (⃗ ai , b⃗i ) para aplicar el operador ⊗ mediante la operación map y reducir los accesos a los datos por lo que el almacenamiento necesario para una correcta paralelización es mucho mayor. En el caso de la multiplicación X T X presente en la ecuación normal se pueden aprovechar varias bondades. Dado que la multiplicación es de la matriz por ella misma, no es necesario generar los pares (⃗ ai , a⃗i ) por lo que es menos costoso en cuestiones de espacio de almacenamiento. La otra particularidad de esta multiplicación es que dado que (⃗ ai ⊗ a⃗i ) = a⃗i T · a⃗i es una matriz simétrica, no es necesario calcular todos los elementos, sino solo los elementos de la triangular inferior y se disminuyen significativamente los cálculos necesarios..

(43) Implementación de un modelo de Regresion Lineal personalizado utilizando Spark ML 33 2.4.. Características y diseño general de la implementación. La implementación del algoritmo se realizó siguiendo el modelo de programación de la API de Spark ML basada en Pipelines. El lenguaje de programación escogido fue Scala versión 2.11.8 incluida dentro de la biblioteca de Spark versión 2.0.1. Para las tareas locales de álgebra lineal se utilizó la biblioteca LAPACK incluida en esa versión de Spark. El IDE utilizado para la implementación fue IntelliJ IDEA version 2016.3. La versión de Java utilizada por Scala en esta implementación fue la 8 actualización 91.. 2.4.1.. Principales clases y funciones implementadas. Clase Instance Representa una instancia de entrenamiento que contiene un atributo label que representa la clase de esa instancia y un atributo features que almacena el vector de características de la instancia.. Clase CustomLinearRegression Esta case hereda de la clase Estimator de Spark ML. Es la encargada de construir un modelo a partir de las instancias de entrenamiento. En este caso el modelo construido es un modelo de regresión lineal utilizando la ecuación normal. El principal método implementado en esta clase es fit el cual es el encargado de construir el modelo aprendido a partir de las instancias, recibe como parámetro un Dataset de Spark SQL con los datos de entrenamiento..

(44) Implementación de un modelo de Regresion Lineal personalizado utilizando Spark ML 34 Pseudocódigo de la función fit. Entrada: dataset . Objeto que contiene las instancias de entrenamiento. Salida: Objeto de CustomLinearRegressionModel que contiene el vector θ de coeficientes para evaluar la función lineal. 1. trainInstances ← dataset.select("features", "label") 2. l2RegularizationParam ← 0.1 3. Calcular ambos miembros de la ecuación normal X T Xθ = X T y. 4. Aplicar regularización L2 sobre el miembro X T Xθ. 5. Resolver el sistema lineal X T Xθ = X T y sobre la variable θ. 6. Devolver una nueva instancia de CustomLinearRegressionModel con el valor del vector θ.. Clase CustomLinearRegressionModel. Esta clase hereda de la clase Model de Spark ML. Es la encargada de representar el modelo aprendido, así como de contener las funcionalidades para transformar los datos de entrada y predecir características para nuevas instancias. Concretamente esta clase contiene un método transform que recibe un Dataset de Spark SQL como datos de entrada, en este caso, instancias para las cuales se desea predecir el valor de la clase mediante la evaluación de una función lineal que utiliza los valores de las características y devuelve un DataFrame de Spark SQL que contiene los valores arrojados por la predicción..

(45) Implementación de un modelo de Regresion Lineal personalizado utilizando Spark ML 35 Pseudocódigo de la función transform. Entrada: Dataset que contiene las instancias para las cuales se desea predecir la variable continua y de la clase a partir del vector de características de cada una y el vector de coeficientes θ aprendido. Salida: DataFrame que contiene las instancias de entrada con la columna adicional de valores arrojados por la evaluación de la función lineal para cada una de esas instancias. 1. instances ← dataset.select("features", "label") 2. Evaluar la función lineal mediante el cálculo de θT ⃗x para cada instancia. 3. Agregar la columna de valores arrojados por la predicción al dataset de entrada. 4. Devolver el nuevo dataset en forma de DataFrame. También se implementaron varias funciones necesarias para las tareas de cálculo algebraico y estadísticas sobre los resultados.. Función rmse Esta función es la encargada de calcular el error cuadrático medio de la estimación realizada por la función lineal respecto a los valores reales de la variable y para instancias de las cuales se conoce este dato. De manera que esta medida de error puede ser utilizada para valorar qué tan acertada está la estimación.. Función outerVecProduct(v: Vector) Esta función es la encargada de calcular el producto externo de un vector por si mismo, concretamente la operación ⃗a ⊗ ⃗a. La implementación de este método solo calcula los elemetos de la matriz triangular asociada al resultado, dado que es una matriz simétrica y devuelve esta matriz en representación vectorial por filas..

(46) Implementación de un modelo de Regresion Lineal personalizado utilizando Spark ML 36 Función vecAdd(v1: Vector, v2: Vector) Esta función realiza la suma de dos vectores. Función vecScale(v1: Vector, s: Double) Realiza la multiplicación de un vector por un valor escalar.. Es importante resaltar que al realizarse la implementación con Scala, que es un lenguaje de programación funcional las estructuras para la representación de colecciones como vectores y matrices, así como de las estructuras distribuidas de Spark son inmutables e implementan eficientemente el paradigma map-reduce. En el caso de la implementación de las estructuras de Spark este modelo de programación permite la paralelización implícita por lo que las operaciones de la naturaleza map están implementadas para ser escalables por definición.. Como consecuencia de haberse implementado este algoritmo siguiendo el modelo de programación de Spark ML este está condicionado para ser integrado como parte de una Pipeline y ser utilizado como parte de una cadena de procesamientos sucesivos de datos..

(47) Implementación de un modelo de Regresion Lineal personalizado utilizando Spark ML 37 2.5.. Diagramas de clases asociados a CustomLinearRegression y CustomLinearRegressionModel. Figura 2–3: Diagrama asociado a CustomLinearRegression. Figura 2–4: Diagrama asociado a CustomLinearRegressionModel.

(48) CAPÍTULO 3.

(49) Capítulo 3 HERRAMIENTAS, TÉCNICAS Y VALIDACIÓN DE LOS RESULTADOS. 3.1.. Preparación del ambiente de desarrollo. En esta sección se describen los pasos a seguir para la creación de la aplicación final utilizando Spark mediante su API en Scala, así como su preparación para la ejecución en un clúster. 3.1.1.. Creación de un proyecto de Scala con Spark en el IDE IntelliJ IDEA. Pasos a seguir para la creación del proyecto en IntelliJ IDEA: 1. Descargar el IDE IntelliJ IDEA de su sitio web oficial. 2. Descargar el plugin de Scala asociado a la versión del IDE desde la misma web. 3. Descargar Spark desde su sitio web oficial. 4. Extraer el contenido de Spark en el directorio de trabajo. 5. Instalar el IDE. 6. Agregar el plugin de Scala mediante la opción: File >Settings >Plugins >Install plugin from disk 7. Crear un proyecto nuevo de Scala. 8. Agregar las dependencias externas de Spark mediante la opción: File >Project Structure >Libraries >+ >Seleccionar los archivos .jar del directorio jars de Spark excluyendo los relacionados con Scala 9. Agregar el SDK de Scala en otra biblioteca mediante los pasos anteriores utilizando los archivos relacionados con Scala. 10. Asociar la biblioteca de Scala como el SDK de Scala del proyecto actual. 39.

(50) HERRAMIENTAS, TÉCNICAS Y VALIDACIÓN DE LOS RESULTADOS. 40. Luego de la implementación de la aplicación y realización de pruebas bajo el ambiente local esta puede ser preparada para su ejecución en un clúster de computadoras.. 3.1.2.. Compilación y creación del archivo .jar para ser lanzado en un clúster de Spark mediante la herramienta spark-submit. Una manera práctica de ejecutar la aplicación en un clúster de Spark es mediante la herramienta spark-submit contenida en las distribuciones estándar de Spark, para esto se hace necesario crear el archivo .jar ejecutable y copiarlo hacia el directorio de trabajo del clúster de computadoras.. Para la creación del archivo .jar en el IDE IntelliJ IDEA es necesario crear un artefacto de compilación mediante el menú: File >Project Structure >Artifacts >+ >Crear un artefacto de compilación de tipo jar, excluir las dependencias. Es importante resaltar que no se deben incluir las dependencias locales de Spark pues la aplicación debe utilizar la distribución de Spark instalada previamente en el clúster y que esta se desempeñe correctamente bajo este ambiente. Luego de ejecutar el artefacto de compilación se obtiene el archivo .jar que debe ser copiado hacia el clúster, en el caso de esta investigación se utilizó la herramienta WinSCP para esta tarea.. 3.1.3.. Carga de los datasets hacia el clúster de Spark con HDFS y ejecución del programa en el clúster. Para almacenar los datasets en el sistema de archivos distribuido de Hadoop se debe primeramente copiar el dataset a un directorio local en el clúster, esto puede hacerse mediante la herramienta WinSCP, luego que el dataset esté completamente copiado hacia ese directorio se debe utilizar el comando hadoop fs -copyFromLocal <localFile><HDFS-dir> donde localFile es la ruta absoluta del dataset almacenado localmente el el clúster y HDFS-dir es la ruta al directorio donde está montado el sistema de archivos distribuido.

(51) HERRAMIENTAS, TÉCNICAS Y VALIDACIÓN DE LOS RESULTADOS. 41. de Hadoop.. Para el lanzamiento de la aplicación en el clúster se utilizó la herramienta spark-sumbit . Los parámetros utilizados fueron: –-master yarn Para especificar que se desea utilizar YARN como manejador de clúster. –-driver-memory 4096m para especificar el tamaño de memoria de la JVM del programa driver en este caso 4 GiB. –-executor-memory 4096m análogamente para la memoria en los ejecutores. –-num-executors 3 para especificar la cantidad de ejecutores a utilizar. –-executor-cores 3 para especificar la cantidad de núcleos en cada ejecutor, en este caso 3 núcleos virtuales. A manera de resumen el comando sería, por ejemplo: spark-sumbit –-master yarn –-num-executors 7 –-driver-memory 1024m –-executor-memory 1024m –-executor-cores 2 ejemplo.jar Donde ejemplo.jar es el ejecutable del programa que se sea lanzar.. 3.2. 3.2.1.. Pruebas preliminares. Pruebas realizadas al dataset E2006.train. Csie.ntu.edu.tw (2017) Características del dataset: Tamaño en disco: 485 MB. Número de instancias: 16087 / 3308 (test).

(52) HERRAMIENTAS, TÉCNICAS Y VALIDACIÓN DE LOS RESULTADOS. 42. Número de características: 150360 Localización: https://www.csie.ntu.edu.tw/~cjlin/libsvmtools/datasets/regression.html Prueba 1.1: Historial de eventos en el clúster de Spark:. Figura 3–1: Algoritmo de regresión lineal personalizado usando ecuación normal. Figura 3–2: Algoritmo de regresión lineal contenido en Spark Observaciones: El algoritmo personalizado implementado utiliza la solución por ecuación normal sin distinguir el tamaño de los datos de entrada. En el cálculo de los términos.

(53) HERRAMIENTAS, TÉCNICAS Y VALIDACIÓN DE LOS RESULTADOS. 43. de la ecuación normal de esta implementación se generan grandes cantidades de datos ya que se calcula X T X y X T y lo cual tiene un alto costo en almacenamiento causando que la memoria de trabajo se use excesivamente y el recolector de basura de las JVM tome demasiado tiempo en reusar la memoria causando una falla en el proceso, específicamente el error es del tipo java.lang.OutOfMemoryError: GC overhead limit exceeded . La 2. complejidad computacional del almacenamiento utilizado es del orden O( n2 +n) para cada instancia, siendo n la dimension de los vectores de características. Una posible solución sería aumentar el tamaño del montículo de las JVM de acuerdo al tamaño de la instancia del problema, lo cual está limitado por la capacidad del clúster utilizado. El valor utilizado por los desarrolladores de Spark para definir el tamaño máximo de los vectores de características permisibles es 4096, garantizándose así la viabilidad de este método. En la clase WeightedLeastSquares de Spark ML se puede observar el siguiente comentario de los desarrolladores:. Figura 3–3: Detalle del algoritmo de regresión lineal contenido en Spark El algoritmo de regresión lineal contenido en Spark tiene en cuenta el tamaño de los datos de entrada y toma decisiones de solución diferentes (Normal equation, SGD, L-BFGS), en este algoritmo se utiliza una optimización en las operaciones reduce más costosas, esta operación denominada treeAggregate es una generalización de la operación reduce y su propósito es disminuir la carga en memoria y procesamiento causada por la agregación masiva hacia el programa driver, en este caso los datos se van agregando parcialmente en forma de árbol a lo largo de todas sus particiones, de manera que el proceso de agregación es colaborativo entre los nodos..

(54) HERRAMIENTAS, TÉCNICAS Y VALIDACIÓN DE LOS RESULTADOS. 44. Figura 3–4: Diferencias entre Aggregate y Tree Aggregate. Prueba 1.2: Nota: Se modificó la operación reduce donde se encontraba el mayor costo computacional, esta vez se implementó utilizando treeAgreggate como intento de optimización para no sobrecargar el programa driver. Esta implementacion se ejecutó utilizando el mismo dataset que la Prueba 1.1 con el objetivo de reafirmar que el causante del fallo es la complejidad espacial de la operación y no la carga de procesamiento por agregación masiva. Historial de eventos en el clúster de Spark:. Figura 3–5: Algoritmo de regresión lineal personalizado usando ecuación normal Observaciones: El algoritmo sufre el mismo fallo que en la prueba 1..

(55) HERRAMIENTAS, TÉCNICAS Y VALIDACIÓN DE LOS RESULTADOS 3.2.2.. 45. Pruebas realizadas al dataset YearPredictionMSD. Csie.ntu.edu.tw (2017) Características del dataset: Tamaño en disco: 553 MB. Número de instancias: 463715 / 51630 (test) Número de características: 90 Localización: https://www.csie.ntu.edu.tw/~cjlin/libsvmtools/datasets/regression.html Prueba 1.3: Historial de eventos en el clúster de Spark:. Figura 3–6: Algoritmo de regresión lineal personalizado usando ecuación normal. Figura 3–7: Algoritmo de regresión lineal contenido en Spark.

(56) HERRAMIENTAS, TÉCNICAS Y VALIDACIÓN DE LOS RESULTADOS. 46. Observaciones: En esta prueba se utilizó un dataset de mayor tamaño en disco. Presenta vectores de características de menor tamaño que las pruebas anteriores pero muchas más instancias de entrenamiento. El algoritmo resuelve eficientemente el cómputo solicitado. Los tiempos de ejecución tanto de la implementación personalizada como la contenida en Spark son similares.. 3.3. 3.3.1.. Pruebas de escalabilidad. Características del clúster utilizado:. El clúster utilizado para las pruebas presenta las siguientes prestaciones:. Cantidad de Nodos: 10 Versión de Spark: 2.1.0 Manejador de clúster: YARN Configuración de YARN: Nodo: Memoria reservada para todos los contenedores de YARN en un nodo: 12GB CPU: Porciento de CPU físico para todos los contenedores en un nodo: 80 % Número de núcleos virtuales: 3 Contenedor: Tamaño de memoria los contenedores: Mínimo 1024 MB; Máximo 12 GB Tamaño de los contenedores (CPU): Mínimo 1; Máximo 3 (Núcleos virtuales) Tamaño de montículo de las JVM: 3064 MB.

Referencias

Documento similar