scieee AI-readable full text Open interactive document viewer

Análisis de datos y aprendizaje automático en Haskell

Terán Parrado, Rafael

Abstract

“Data is the new oil”, this famous quote by Clive Humby, a renowned mathematician and pioneer in the analysis of large volumes of data to extract valuable consumer insights, said in 2006, may be more accurate today than it has ever been. Like oil, its true value comes within a proper refining and analysis. In this project, we will provide a description on two of the most relevant methods in data analysis and machine learning: the principal component analysis and the Naive Bayes classifier are. We will then implement these methods in Haskell and demostrate their application through two examples.

Full text

FACULTAD DE MATEM´ ATICAS Trabajo fin de Grado Grado en Matem´aticas An´alisis de datos y aprendizaje autom´atico en Haskell Realizado por Rafael Ter´an Parrado Dirigido por Francisco Jes´us Mart´ın Mateos Departamento Ciencias de la Computaci´on e Inteligencia Artificial Sevilla, Octubre de 2024 Abstract “Data is the new oil”, this famous quote by Clive Humby, a renowned mathematician and pioneer in the analysis of large volumes of data to extract valuable consumer insights, said in 2006, may be more accurate today than it has ever been. Like oil, its true value comes within a proper refining and analysis. In this project, we will provide a description on two of the most relevant methods in data analysis and machine learning: the principal component analysis and the Naive Bayes classifier are. We will then implement these methods in Haskell and demostrate their application through two examples. I Resumen “Los datos son el nuevo petr´oleo”, esta famosa cita de Clive Humby, un renombrado matem´atico y pionero en el an´alisis de grandes vol´umenes de datos para extraer valiosos conocimientos sobre los consumidores, dicha en 2006, puede ser hoy m´as precisa que nunca. Al igual que en el petr´oleo, su verdadero valor se encuentra tras un adecuado refinamiento y an´alisis. En este proyecto, proporcionaremos una descripci´on de dos de los m´etodos m´as relevantes en el an´alisis de datos y el aprendizaje autom´atico: el An´alisis de Componentes Principales y el Clasificador Naive Bayes. Posteriormente, implementaremos estos m´etodos en Haskell y demostraremos su aplicaci´on a trav´es de dos ejemplos. III Agradecimientos A mi madre, a mi padre y a mi hermana. V ´ Indice general ´ Indice general VII ´ Indice de tablas IX ´ Indice de c´odigo XI 1 Introducci´on 1 2 Clasificador Naive Bayes 3 2.1 Conocimientos probabil´ısticos previos . . . . . . . . . . . . . . . . . . . . . . . 3 2.1.1 Experimento, resultado, espacio muestral y suceso . . . . . . . . . . . . . 3 2.1.2 σ-´algebradeconjuntos............................. 4 2.1.3 Probabilidad .................................. 5 2.1.4 Probabilidad condicionada . . . . . . . . . . . . . . . . . . . . . . . . . . . 5 2.1.5 Independencia ................................. 6 2.2 TeoremadeBayes .................................. 6 2.3 Clasificaci´on Naive Bayes . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 8 2.3.1 SuavizadodeLaplace ............................. 9 3 An´alisis de componentes principales 11 3.1 Introducci´on estad´ıstica . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 11 3.1.1 Esperanza, varianza y covarianza . . . . . . . . . . . . . . . . . . . . . . . 12 3.1.2 Vectoraleatorio................................. 13 3.1.3 Inferenciamuestral............................... 14 3.2 Introducci´on a la descomposici´on espectral de matrices . . . . . . . . . . . . . 16 3.2.1 Teorema espectral para matrices normales . . . . . . . . . . . . . . . . . . 16 3.3 An´alisis de las relaciones entre variables: Matriz de covarianza . . . . . . . . . 17 3.3.1 Estandarizaci´on de los datos . . . . . . . . . . . . . . . . . . . . . . . . . 18 3.3.2 Matrizdecovarianza.............................. 18 3.4 Componentes principales . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 19 3.4.1 Determinaci´on de las componentes principales . . . . . . . . . . . . . . . . 19 3.4.2 Criterios para la selecci´on de componentes principales . . . . . . . . . . . 20 3.4.3 Proyecci´on de los datos en el espacio de componentes principales . . . . . 21 3.5 Interpretaci´on de los resultados . . . . . . . . . . . . . . . . . . . . . . . . . . . 21 VII 21. Introducci´on Haskell, un lenguaje funcional con amplia relaci´on con el mundo matem´atico, terminando con unas aplicaciones m´as especificas que servir´an de ejemplos de usos de las librer´ıas implementadas. CAP´ ITULO 2 Clasificador Naive Bayes El clasificador Naive Bayes es el primero de los algoritmos de machine learning que veremos. Fundamentado en el teorema de Bayes, es uno de los algoritmos de referencia en el campo de la clasificaci´on autom´atica, utilizando un m´etodo probabil´ıstico sorprendentemente simple como fundamento te´orico. Es conocido por ser una herramienta de clasificaci´on usada principalmente en textos, sus utilidades suelen estar relacionadas con la detecci´on de spam en correos electr´onicos, o como en la aplicaci´on pr´actica que veremos en el cap´ıtulo “Aplicaciones”, con la clasificaci´on de textos por idioma. Adem´as de ser un muy buen punto de entrada al mundo del aprendizaje autom´atico omachine learning. 2.1– Conocimientos probabil´ısticos previos El objetivo de esta secci´on es introducir de manera leve varios conceptos probabil´ısticos que ser´an esenciales para el correcto entendimiento de la teor´ıa detr´as del Clasificador Naive Bayes. El objetivo de este ensayo no es explicar estos conceptos a fondo, y por lo tanto se recomienda a aquellos lectores menos familiarizados con el mundo de la estad´ıstica o aquellos que quieran informarse, que busquen informaci´on adicional sobre el tema. Esta peque˜na introducci´on est´a basada en los apuntes de Francisco Montes Suay titulados “Introducci´on a la Probabilidad” (2007, Universitat de Val`encia), los cuales se encuentran referenciados en la bibliograf´ıa. 2.1.1. Experimento, resultado, espacio muestral y suceso Introducimos algunos de los conceptos principales dentro de la teor´ıa de la probabilidad. Definici´on 2.1. Un experimento aleatorio es un proceso o fen´omeno que, bajo condiciones controladas, puede reproducirse indefinidamente, pero dando un resultado espec´ıfico que no se puede predecir con certeza antes de su realizaci´on. Ejemplo 2.2. Un experimento aleatorio podr´ıa consistir en lanzar un dado perfecto o en sacar, sin mirar, una bola de una caja que contiene tres bolas cuya ´unica diferencia es el color. Definici´on 2.3. Un resultado,ω, de un experimento aleatorio es el valor o conjunto de valores obtenidos al realizar el experimento. 3 42. Clasificador Naive Bayes Ejemplo 2.4. Los resultados de los ejemplos anteriores ser´ıan un n´umero del 1 al 6 en el caso del dado, o uno de los tres colores en el caso de las bolas. Definici´on 2.5. Un espacio muestral, Ω, de un experimento aleatorio es el conjunto de posibles resultados que puede tener el experimento. Ejemplo 2.6. Los espacios muestrales de los ejemplos anteriores ser´ıan Ω = {1,2,3,4,5,6} para el dado y Ω = {rojo, azul, amarillo}para las bolas. Definici´on 2.7. Un suceso aleatorio,A,B,..., es un subconjunto de un espacio muestral, normalmente caracterizado por una cualidad com´un. Cuando el resultado del experimento forma parte de un suceso aleatorio A, se dice que ha ocurrido o se ha realizado A. Ejemplo 2.8. Unos sucesos aleatorios del ejemplo del dado podr´ıan ser: A={Ha salido par}={ωes par}={2,4,6}(2.1) B={Ha salido menor que 4}={ω < 4}={1,2,3}(2.2) 2.1.2. σ-´algebra de conjuntos Para definir la probabilidad de un suceso aleatorio, tenemos que determinar algunas propiedades que deben poseer estos conjuntos. Por ejemplo, es l´ogico pensar que todo suceso y su complementario deben sumar una probabilidad de 1, expres´andola en tanto por uno. O que la probabilidad de unir dos sucesos deber´a ser la probabilidad de ambos sumadas. A partir de ahora, para asegurarnos de que trabajamos sobre conjuntos de sucesos que sean coherentes con estas propiedades, cuando se hable de un suceso aleatorio, se estar´a hablando formalmente de un elemento de una σ-´algebra de conjuntos, una σ-´algebra de sucesos m´as concretamente. Definici´on 2.9. Una σ-´algebra de conjuntos se define como una familia de conjuntos A sobre Ω tal que: 1. Ω ∈ A. 2. A∈A⇒Ac∈ A. 3. {An}n≥1⊂ A ⇒ Sn≥1An∈ A. En la definici´on de suceso que hemos dado, es evidente que tanto el complementario de un suceso como la uni´on y la intersecci´on de sucesos ser´ıan sucesos. Por lo que existe una compatibilidad entre ambas definiciones. El concepto de σ-´algebra ser´a especialmente importante para asegurar la estabilidad de esas propiedades en sucesos no numerables. Podemos observar que el conjunto de todos los sucesos, es decir, la familia de las partes de Ω, P(Ω), tiene estructura de σ-´algebra. 2.1. Conocimientos probabil´ısticos previos 5 2.1.3. Probabilidad La idea de la probabilidad no es m´as que dar un n´umero que asocie un suceso de un experimento aleatorio a la posibilidad de que se realice. Para ello se define una funci´on sobre la σ-´algebra de sucesos, a esta funci´on se le llama medida de probabilidad o probabilidad directamente. Definici´on 2.10. Una probabilidad,P, definida sobre la una σ-´algebra Aes una funci´on tal que: 1. P(A)≥0 para todo A∈ A. 2. P(Ω) = 1. 3. Pes numerablemente aditiva, es decir, si {An}n≥1es una sucesi´on de sucesos de A disjuntos dos a dos, entonces P [ n≥1 An =X n≥1 P(An). A la terna (Ω,A, P) la denominaremos espacio de probabilidad. Ejemplo 2.11. Volviendo al ejemplo del dado, la funci´on probabilidad ser´ıa: P(A) = P({Ha salido el 5}) = P({ω= 5}) = P({5}) = 1 6= 0,166 (2.3) P(B) = P({Ha salido par}) = P({ωes par}) = P({2,4,6}) = 3 6= 0,5 (2.4) P(Ω) = P({Ha salido un n´umero del 1 al 6}) = P({1,2,3,4,5,6}) = 6 6= 1 (2.5) 2.1.4. Probabilidad condicionada A la hora de calcular una probabilidad, a veces hay que tener en cuenta otros sucesos que ya ocurrieron, o considerar la posibilidad de que ocurran. Al existir la posibilidad de que esto cambie la probabilidad de un suceso, se debe definir la probabilidad condicionada. Definici´on 2.12. Sea (Ω,A, P) un espacio de probabilidad y sean AyBdos sucesos, cumpliendo P(B)= 0, definimos la probabilidad de Acondicionada aBcomo: P(A|B) = P(A∩B) P(B)(2.6) Ejemplo 2.13. Para un experimento consistente de sacar varias bolas al azar, sin devolverlas, de una caja donde hay dos bolas rojas y otras tres azules, siendo R={Sacar una bola roja}yA={Sacar una bola azul}, sus probabilidades ser´ıan: P(R) = 2 5= 0,4P(A) = 3 5= 0,6 (2.7) Por lo que las probabilidades condicionadas tomar´ıan la siguiente forma: P(R|A) = 2 4= 0,5P(A|A) = 2 4= 0,5 (2.8) P(R|R) = 1 4= 0,25 P(A|R) = 3 4= 0,75 (2.9) 62. Clasificador Naive Bayes Tomando la definici´on de probabilidad condicionada, se puede deducir una expresi´on de la probabilidad de la intersecci´on de dos sucesos en funci´on de la probabilidad condicionada: P(A∩B) = P(A|B)·P(B) (2.10) 2.1.5. Independencia En la cara opuesta de la idea que nos hizo pensar en la probabilidad condicionada, est´a la independencia de sucesos. Muchas veces dos o m´as sucesos no guardan ninguna relaci´on entre s´ı, y que ocurra o deje de ocurrir uno o varios de ellos no afecta en la probabilidad del resto. Definici´on 2.14. Sean AyBdos sucesos, decimos que AyBson independientes si y solo si: P(A∩B) = P(A)·P(B) (2.11) Se puede observar que en el caso de que dos sucesos AyBsean independientes, sus probabilidades condicionadas coinciden con sus probabilidades simples: P(A|B) = P(A∩B) P(B)=P(A)·P(B) P(B)=P(A) (2.12) Tambi´en ser´a importante extender el concepto a no solo una relaci´on dual sino que un grupo de sucesos finito sean independientes entre s´ı. Llamaremos a esto independencia mutua. Definici´on 2.15. Sea {A1, ..., An}una familia de sucesos, decimos que son mutuamente independientes si y solo si P( m \ i=1 Aki) = m Y i=1 P(Aki) (2.13) siendo {k1, ..., km}⊆{1, ..., n} 2.2– Teorema de Bayes Ya con todos esos conceptos planteados y las notaciones claras, damos el paso a introducir el Teorema de Bayes, de donde saldr´a toda la base te´orica para sacar adelante el clasificador Naive Bayes. El teorema de Bayes es uno de los conceptos principales en la estad´ıstica. Planteado por el matem´atico y reverendo ingl´es Thomas Bayes (1702-1761), se trata de la base de toda la estad´ıstica bayesiana, as´ı como un basti´on fundamental en todo el desarrollo de las t´ecnicas de an´alisis de datos. La idea en la que se fundamenta el teorema es bastante intuitiva y sigue un razonamiento que podemos tener en el d´ıa a d´ıa cuando se intenta hacer una predicci´on o tomar una decisi´on sobre un tema sin tener todos los datos. 2.2. Teorema de Bayes 7 Probabilidad a priori El primer concepto que tomaremos es la probabilidad a priori,P(A). Previo a todo juicio, todos tenemos una idea preconcebida o unas creencias iniciales previas a cualquier evidencia o informaci´on nueva. Normalmente est´a basada en experiencias pasadas o conocimiento general. Por ejemplo, si nuestro suceso a estudiar (A) es si va a llover hoy. Si llovi´o ayer y estamos en oto˜no, hay una probabilidad a priori (P(A)) alta de que hoy llueva. Esta es una idea inicial previa a evidencias como lo son mirar por la ventana o mirar el pron´ostico del tiempo. Verosimilitud Habiendo hecho una primera aproximaci´on al problema, se pasa al concepto de verosimilitud,P(B|A), en ´el, habiendo recogido alguna evidencia (B), se cuestiona la probabilidad de esta, tomando como cierto el primer acercamiento al problema (A). La verosimilitud es la probabilidad de haber observado la evidencia tomada si el primer juicio fue el acertado, es decir, dice c´omo de coherente era la creencia inicial. Volviendo al ejemplo donde se piensa que va a llover, si despu´es de hacer la predicci´on se decide abrir la ventana y no se ve ninguna nube en el cielo, la verosimilitud dir´ıa: ”Si es cierto que hoy va a llover, ¿c´omo de probable es que no est´e nublado?”. Estamos dando como cierto que hoy llueve (A) y vemos la probabilidad de que est´e nublado sabiendo esto (P(B|A)). Evidencia La probabilidad de que lo observado sea cierto ser´a la evidencia o verosimilitud marginal,P(B). La importancia de analizar lo com´un que sea la nueva informaci´on recogida reside en ayudar a contextualizar la informaci´on y restarle o sumarle peso. La relaci´on entre este dato y el resto ser´a de proporcionalidad inversa, cu´anto m´as probable sea la evidencia, menos importar´a en nuestra conclusi´on final. En el ejemplo de la lluvia, si la observaci´on fue ver nubes en el cielo (B), la verosimilitud marginal (P(B)) depender´a de c´omo de com´un sea ver nubes en el cielo. Si se vive en Andaluc´ıa tendr´a m´as importancia pues no siempre hay nubes y pueden significar que llover´a, sin embargo si se vive en Galicia ver nubes en el cielo es mucho m´as com´un y no es tan significativo porque siempre hay nubes, llueva o no. Probabilidad a posteriori La ´ultima, y fundamental variable en el teorema de Bayes, es la probabilidad a posteriori,P(A|B). Es el resultado a estudiar y la creencia o conclusi´on final del problema. Sin m´as pre´ambulos, es la probabilidad de que se d´e el suceso a estudiar dada una cierta informaci´on observada. En nuestro ejemplo, la probabilidad a posteriori (P(A|B)) es la respuesta a c´omo de probable es que la hip´otesis tomada (A) sea cierta, en este caso, que hoy llov´ıa, sabiendo que est´a nublado (B). 82. Clasificador Naive Bayes La misi´on principal del teorema de Bayes es ajustar la probabilidad a posteriori seg´un la informaci´on recibida o preconcebida. B´asicamente el teorema dice que la probabilidad a posteriori es el resultado de combinar la verosimilitud y la probabilidad a priori, siendo esto normalizado por la verosimilitud marginal o evidencia, para que el resultado sea una probabilidad. De manera formal, esta es la formulaci´on del teorema de Bayes: Teorema 2.1. Sean AyBdos sucesos, tal que P(B)= 0, se tiene: P(A|B) = P(B|A)·P(A) P(B)(2.14) La demostraci´on es tan sencilla como aplicar la definici´on de probabilidad condicionada: P(A|B) = P(A∩B) P(B)=P(B|A)·P(A) P(B)(2.15) 2.3– Clasificaci´on Naive Bayes Contando con todos los conceptos matem´aticos previos, nos adentraremos en el funcionamiento del Clasificador Naive Bayes. Podemos definir un clasificador como un m´etodo supervisado que permite entrenar un modelo para que determine la clase m´as probable que debemos asignar a un registro, en este caso, el m´etodo que seguir´a estar´a basado en el Teorema de Bayes. El entrenamiento que se dar´a al modelo ser´a un conjunto de una gran cantidad de ejemplos ya clasificados en sus respectivas clases, al que llamaremos conjun- to de entrenamiento. Gracias a estos, podremos buscar patrones o similitudes entre los registros a clasificar y los elementos del conjunto de entrenamiento. Si un registro guarda muchas similitudes con elementos de entrenamiento clasificados en una clase concreta, este tendr´a muchas probabilidades de pertenecer a esta misma clase. Partiendo del teorema de Bayes, tendremos: P(A|B) = P(B|A)·P(A) P(B)(2.16) Donde: •Aes el suceso de que un registro pertenezca a una clase concreta. •Bes el suceso de observar una caracter´ıstica espec´ıfica en el registro. Extendemos la f´ormula a una versi´on donde tengamos una cantidad finita de sucesos dados, B1, B2, ..., Bn: P(A|B1, B2, ..., Bn) = P(B1, B2, ..., Bn|A)·P(A) P(B1, B2, ..., Bn)(2.17) 2.3. Clasificaci´on Naive Bayes 9 Suposici´on Naive El nombre de la clasificaci´on, “Naive Bayes”, proviene de la fusi´on entre el teorema de Bayes y el t´ermino Naive, palabra inglesa que significa ingenuo. Se le llama ingenuo al modelo por asumir que los datos que condicionan la probabilidad no tienen dependencia entre ellos, es decir, son mutuamente independientes. Esto nos deja con la siguiente ecuaci´on: P(A|B1, B2, ..., Bn) = P(B1|A)·P(B2|A)·... ·P(B2|A)·P(A) P(B1)·P(B2)·... ·P(Bn)(2.18) Para nuestros intereses, no nos preocupa el valor real de la probabilidad que tenga un elemento de pertenecer a una clase u otra, simplemente queremos tener un valor orientativo que nos permita comparar probabilidades y quedarnos con la opci´on m´as probable, la probabilidad en s´ı no es significativa para el clasificador. Por lo tanto, observando que en nuestra ecuaci´on el denominador no nos da una diferencia comparativa, ya que no depende de la clase, A, podemos simplificar la f´ormula eliminando el denominador de la ecuaci´on: P(A|B1, B2, . . . , Bn)∝P(A)· n Y i=1 P(Bi|A) (2.19) La clase m´as probable y que asignaremos a cada registro ser´a aquella que maximice la ecuaci´on anterior. Formalmente, si fuera A=A1, A2, ..., Amel conjunto de clases a las que puede pertenecer un registro, la clase que nuestro modelo seleccionar´ıa ser´ıa la siguiente: A∗= arg m´ax A∈A P(A)· n Y i=1 P(Bi|A) (2.20) 2.3.1. Suavizado de Laplace En la teor´ıa el resultado anteriormente presentado funciona perfectamente, pero en la pr´actica hay que tener en mente algunas consideraciones. Hay que ser consciente de que la base de datos que sirva como conjunto de entrenamiento no es infinita, est´a limitada, en muchos casos no contiene todos los posibles fen´omenos que se relacionan con la clase a la que pertenecen. Al estar multiplic´andose todas las probabilidades, con que una de las caracter´ısticas del registro no se encuentre en el conjunto de entrenamiento, este tendr´a probabilidad nula y har´a que el producto sea nulo, imposibilitando la correcta clasificaci´on. Esto puede entenderse mejor con un ejemplo. Uno de los posibles usos que puede tener un clasificador Naive Bayes es el de filtrar rese˜nas o comentarios de un establecimiento en funci´on de si son positivos, negativos o neutros. Para ello en nuestra base de datos tendremos palabras positivas como pueden ser “rico”, “barato”, “estupendo”, etc., provenientes de mensajes positivos que recogimos en la base de datos, pero no podemos tener todas las palabras existentes en castellano. Si intentamos clasificar un comentario que dice “Fui con mi prima y estuvo todo estupendo”, existe la posibilidad de que la palabra “prima” no se encuentre en el conjunto de palabras que se relacionan con los mensajes positivos, es decir, que en ninguna rese˜na positiva del conjunto de entrenamiento se mencione la palabra “prima”. P(“prima” |Rese˜na positiva) = 0 (2.21) 10 2. Clasificador Naive Bayes El comentario es evidentemente positivo pero por una sola palabra se arruina todo el proceso. A este problema se le llama Problema de Cero Frecuencia. Una manera de resolver este error es mediante una t´ecnica llamada suavizado de Laplace (Laplace Smoothing). El suavizado de Laplace se suele usar en estad´ıstica y aprendizaje autom´atico para solucionar problemas de estimaci´on de probabilidad como en el caso en el que nos encontramos. La soluci´on que ofrece el suavizado de Laplace es a˜nadir una constante en el c´alculo de cada probabilidad, haciendo que no existan probabilidades nulas pero sin perder la proporci´on. Partiendo de que el c´alculo de la probabilidad sin suavizado es: P(Bi|A) = nABi nA (2.22) Donde: •nABies el n´umero de veces que se repite el suceso Bien el conjunto de entrenamiento y que est´a asociado a la clase A. •nA es el n´umero de sucesos sin repetici´on que hay en el conjunto de entrenamiento asociados a la clase A. La modificaci´on a la probabilidad para el suavizado ser´ıa la siguiente: P′(Bi|A) = nABi+α nA +α·N(2.23) Donde: •α > 0 es una constante (normalmente se coge α= 1 por simplicidad). •Nes el n´umero total de sucesos sin repetici´on que hay en el conjunto de entrenamiento. Llamaremos probabilidad suavizada aP′, tras implementarla en el nuevo y definitivo modelo del clasificador, nos quedar´ıa la siguiente ecuaci´on: A∗= arg m´ax A∈A P(A)· n Y i=1 P′(Bi|A) (2.24) CAP´ ITULO 3 An´alisis de componentes principales La segunda t´ecnica de an´alisis de datos que abordaremos es el an´alisis de componentes principales (PCA). Para enfrentar el desaf´ıo de analizar desbordantes cantidades de datos con abundantes variables diferentes que hacen la comprensi´on de los datos en crudo pr´acticamente imposible, una herramienta ampliamente utilizada y probada es el an´alisis de componentes principales, consistente en reducir la “dimensionalidad” del conjunto de datos, tratando de minimizar la perdida de informaci´on en el proceso. El objetivo ser´a determinar un nuevo n´umero, mucho menor, de variables nuevas que condensen la informaci´on que ten´ıamos en las antiguas variables, de las cuales muchas es normal que sean redundantes o poco importantes. Esto se llevar´a a cabo utilizando instrumentos del ´algebra lineal, como lo es la descomposici´on en valores y vectores propios de una matriz. 3.1– Introducci´on estad´ıstica Empezaremos esta incursi´on en el an´alisis de componentes principales partiendo de las bases te´oricas que lo sustentan. En esta primera secci´on daremos las claves para la comprensi´on de conceptos principales en estad´ıstica como lo son la varianza y la covarianza, para terminar con una ampliaci´on de este ´ultimo, la matriz de covarianzas. Toda la introducci´on se encuentra respaldada por el libro “An Introduction to Probability Theory and Its Application” de William Feller, debidamente citado en la bibliograf´ıa, se recomienda su lectura para aquellos que quieran ampliar sus conocimientos en este campo, concretamente trataremos el noveno cap´ıtulo “Random Variables; Expectation”. Variables aleatorias Durante la presentaci´on del clasificador Naive Bayes, se defini´o el concepto de experimento aleatorio, as´ı como el de resultado de un suceso aleatorio, recordemos que se trata del valor o conjunto de valores obtenidos al realizar un experimento aleatorio. Volviendo a esas ideas definimos un primer resultado ´ıntimamente relacionado con ellas: Definici´on 3.1. Una variable aleatoria,X,Y,..., es una funci´on que devuelve o asigna un valor al resultado de un experimento aleatorio. 11 18 3. An´alisis de componentes principales 3.3.1. Estandarizaci´on de los datos El primer paso para realizar un an´alisis entre variables es asegurarnos que se encuentran en magnitudes que las hagan comparables. En muchos casos se dispondr´an de variables en escalas muy distintas y que har´an imposible una justa comparaci´on entre sus variabilidades. Las variables que posean una mayor magnitud o mayor varianza natural tendr´an una influencia injustificada que vendr´ıa de la naturaleza de las variables, no de su importancia en el modelo. Un ejemplo ser´ıa tener como variables los ingresos anuales en miles de euros y el n´umero de hijos por familia, los ingresos tendr´an un mayor valor que el n´umero de hijos y una mayor variabilidad, el n´umero de hijos se suele mover en los mismos valores y los ingresos entre familias pueden variar mucho, pero no por ello una variable es m´as importante que la otra. En caso de necesitarse, seg´un la naturaleza de los datos, distinguiremos dos normalizaciones que realizar a una variable aleatoria: Normalizaci´on con media y desviaci´on est´andar Es la m´as recomendable, elimina los problemas de dispersi´on y de escalas. x∗ ij =xij −µj σj (3.17) Siendo µjla media muestral y σjla desviaci´on est´andar de xj. Centrado de datos Es otro tipo de normalizaci´on donde no se estandariza la variabilidad entre variables, simplemente centra los datos alrededor del cero. Es ´util en casos en los que las variables est´an en la misma escala. x∗ ij =xij −µj(3.18) Siendo µjla media muestral de xj. Denotaremos por X∗a la matriz Xnormalizada con valores x∗ ij. 3.3.2. Matriz de covarianza Con los datos ya normalizados ser´a sencillo obtener la matriz de covarianza. Recordando la f´ormula de esta: S=1 n−1 n X i=1 (xi−¯x)(xi−¯x)T=1 n−1(X−1n·¯x)T(X−1n·¯x) para n > 1 (3.19) Siendo 1nel vector de unos de dimensi´on 1 ×n. Podemos observar que (xi−¯x) no es m´as que el proceso de centrar los datos, algo que ya hemos realizado en la normalizaci´on, por lo tanto podemos expresar la matriz de covarianza en la siguiente f´ormula matricial: S=1 n−1(X∗)TX∗(3.20) 3.4. Componentes principales 19 3.4– Componentes principales Despu´es del an´alisis de las variables iniciales, el siguiente paso es erigir unas nuevas m´as importantes o significativas. Para ello vamos a buscar las combinaciones de vectores columna de X, representando a las variables originales, con mayor variabilidad, es decir, aquellas que maximicen la varianza. A estos patrones o combinaciones de variables los llamaremos componentes principales y ser´an las variables que asignaremos al final del proceso. 3.4.1. Determinaci´on de las componentes principales Como ya se mencion´o, buscaremos aquellas combinaciones lineales de columnas de Xcon mayor variabilidad. Siendo a= (a1, . . . , ap) un vector de constantes cualquiera, sabemos que las combinaciones lineales tendr´an la forma: p X j=1 aj·xj=Xa (3.21) Por medio de la matriz de covarianza Sde X, podemos expresar la an´aloga de Xa: V ar(Xa) = aTV ar(X)a=aTSa (3.22) Para tener una soluci´on bien definida para este problema de maximizar la varianza, nos restringiremos a los vectores aunitarios, es decir, los que cumplan aTa= 1. El problema a maximizar quedar´ıa con la restricci´on de la siguiente manera: m´ax a∈RpaTSa −λ(aTa−1) (3.23) Encontramos el m´aximo a partir de la funci´on Lagrangiana: L(a, λ) = aTSa −λ(aTa−1) (3.24) La cual derivamos con respecto a ae igualamos a cero: ∂L ∂a = 2Sa −2λa = 0 ⇒Sa =λa (3.25) En este punto, reconocemos la ecuaci´on Sa =λa como propia de la descomposici´on en autovalores y autovectores, haciendo de aun autovector unitario de Sy de λsu autovalor. Sustituyendo en la f´ormula de la varianza, tenemos que la varianza m´axima posible es: V ar(X·a) = aTSa =λaTa=λ(3.26) Obtenemos como conclusi´on, la combinaci´on lineal de columnas de Xcon mayor varianza es aquella de la forma Xa donde aes el autovector unitario con el autovalor m´as alto. Por lo tanto, Xa ser´a la primera componente principal. 20 3. An´alisis de componentes principales Propiedad de incorrelaci´on entre componentes principales Toda matriz real sim´etrica cuadrada es normal, por lo que podemos aplicar el teorema espectral para matrices normales sobre S. Tendremos que Stiene exactamente p autovalores reales y que los autovectores pueden expresarse de forma ortonormal. Denotando por λ1, . . . , λplos autovalores de Sordenados de mayor a menor, es decir, λ1≥λ2≥ · · · ≥ λp, y a1, . . . , apsus respectivos autovectores unitarios ortonormales entre s´ı. Al ser ortonormales, tendremos que los vectores no tendr´an una direcci´on com´un en la que coincidan, es decir, ak·ak′= 0, ∀k=k′. Esto har´a que la covarianza de cada par de autovectores distintos sea nula, los autovectores ser´an incorrelados. Debido a la incorrelaci´on de los autovectores, ak,∀k= 1, . . . , p, tenemos que las combinaciones lineales de Xdeterminadas por ellos mismos, Xak,∀k= 1, . . . , p, tambi´en ser´an incorreladas. Esta incorrelaci´on implica que cada una de las combinaciones lineales descritas expliquen una porci´on de la varianza que no es explicada por las dem´as. Teniendo as´ı que para k= 1, . . . , p la combinaci´on lineal de Xcon mayor varianza no explicada por Xa1, . . . , Xak−1sea Xak. Definiendo as´ı la k-´esima componente principal como Xak, ∀k= 1, . . . , p. 3.4.2. Criterios para la selecci´on de componentes principales Dado que las componentes principales son incorreladas, cada una explica una parte de la varianza en exclusiva, cuantificada por su autovalor asociado, las pcomponentes explicar´an la totalidad de la varianza de la muestra, y esta podr´a ser calculada mediante la suma de las varianzas individuales, no existir´a redundancia. Al porcentaje de la varianza total que explique cada componente principal lo llamaremos varianza explicada. Al depender exclusivamente del tama˜no del autovalor asociado, las calcularemos en base a estos y denot´andolas con πj,∀j= 1, . . . , p. πj=λj Pp j=1 λj =λj traza(S)(3.27) Tambi´en es importante el concepto de varianza explicada acumulada, nos dar´a una idea de cu´antas componentes principales debemos de seleccionar en cada caso. Ser´a la suma de las kprimeras varianzas explicadas: Pk i=jπj,∀k= 1, . . . , p. El n´umero de componentes principales que deber´ıan ser elegidas para formar parte del an´alisis es indefinido, en la mayor´ıa de los casos se recomendar´a el n´umero necesario para que se disponga de una varianza explicada acumulada en el modelo al menos del 80 %. El umbral aconsejable es entre el 80 % y 90 %, por debajo nos quedar´ıamos sin una parte sustancial de las componentes de la muestra y por arriba empezar´ıan a entrar variables menos importantes no significativas, se las conoce como ruido. Al tener poca importancia suelen estar dominadas por la aleatoriedad y a˜nadir m´as variables har´an el modelo nuevo menos interpretable. Sin embargo, en algunos casos se preferir´a disponer de las dos o tres primeras componentes principales que permiten una f´acil visualizaci´on gr´afica, aunque eso reduzca la varianza explicada acumulada a un valor inferior al deseado, siempre que no caiga por debajo del 70 %, lo que significar´ıa una perdida excesiva de la informaci´on. 3.5. Interpretaci´on de los resultados 21 3.4.3. Proyecci´on de los datos en el espacio de componentes principales El paso final en el an´alisis de componentes principales, es el de conseguir los datos de la muestra en funci´on de las nuevas componentes principales. El proceso a seguir ser´a la proyecci´on de las nobservaciones que se encontrar´an en el espacio de pvariables inicial sobre el nuevo espacio con un n´umero q < p elegido de componentes iniciales. La proyecci´on se ejecutar´a con la matriz Aqde dimensi´on p×qcuyas columnas son los autovectores correspondientes a las qprimeras componentes iniciales, ak,∀k= 1, . . . , q. Aq=a1|a2|···|aq(3.28) Siendo X∗la matriz de dimensi´on n×pde los datos normalizados, la proyecci´on se realiza por medio del producto de ambas matrices. Multiplicando obtenemos una proyecci´on de cada observaci´on, ubicadas en las filas de X∗, sobre las nuevas componentes principales. El resultado tendr´a forma de matriz n×qy ser´a una representaci´on de los datos en el nuevo subespacio de las componentes principales. 3.5– Interpretaci´on de los resultados Finalizado el an´alisis de componentes principales, habremos conseguido trasladar gran parte de la informaci´on que exist´ıa de nobservaciones en un espacio de ppar´ametros a un espacio ortogonal de q < p, donde esos qpar´ametros definen las qmayores diferencias entre los datos, siendo totalmente independientes unas de otras. Los usos pr´acticos de esta t´ecnica son abundantes. Para facilitar la comprensi´on del mecanismo de uso, daremos un breve ejemplo, adem´as del que viene desarrollado en el apartado de “Aplicaciones” de este trabajo. Si obtenemos una serie de datos de una gran cantidad de deportistas de ´elite de diversos deportes, entre los que se encuentran m´as de 200 datos entre biom´etricos como pueden ser altura, peso, di´ametro del muslo, porcentaje graso, envergadura, capacidad aer´obica, etc.; y resultados de pruebas f´ısicas como pueden ser tiempos en carrera, frecuencia cardiaca en reposo o pesos levantados en ejercicios de fuerza; no podremos sacar grandes conclusiones de primeras, los datos ser´an dif´ıciles de entender a simple vista por su inmensidad. Adem´as muchos de los datos estar´an altamente relacionados, el atleta m´as alto tambi´en ser´a el que tenga mayor envergadura, y el m´as pesado el que tenga mayor di´ametro a la altura del pecho. Sin embargo, realizando las t´ecnicas descritas, obtendremos unas nuevas variables que dar´an un valor a cada deportista. Estas variables ser´an las componentes principales y vendr´an determinadas por las mayores variaciones. En este caso, la primera componente probablemente dar´ıa un alto valor a los jugadores de baloncesto o rugby, y bajo a los gimnastas o maratonianos, ´esta organizar´ıa los deportistas seg´un su tama˜no, tanto a lo ancho como a lo largo, acumulando la informaci´on de todas las variables que guardan relaci´on de este tipo, en general el peso y las medidas. Una segunda componente podr´ıa diferenciar a los deportistas seg´un lo aer´obico que sea su deporte, diferenciar´a los ciclistas y atletas, de halter´ofilos o lanzadores, esta relacionar´ıa otras variables como tiempos en carrera de larga distancia o la frecuencia cardiaca. Con estas dos primeras ya podr´ıamos exponer los resultados en una gr´afica en dos dimensiones con cada eje siendo la puntuaci´on en cada componente principal. Si se quiere mayor riqueza en los datos obtenidos, debido a la gran cantidad de datos recogidos, ser´ıa m´as inteligente profundizar m´as en el an´alisis 22 3. An´alisis de componentes principales sacando m´as componentes. Algunas otras ir´an acorde a la potencia explosiva, la agilidad o la flexibilidad. Teniendo as´ı un an´alisis mucho m´as claro e intuitivo que el inicial. Los beneficios de realizar el an´alisis son m´as que evidentes en todo lo relacionado con la interpretabilidad de los datos. CAP´ ITULO 4 Implementaciones En este capitulo desarrollaremos las implementaciones de las t´ecnicas de machine learning presentadas anteriormente. Armaremos en Haskell, un lenguaje de programaci´on funcional, un clasificador capaz de asignar clases predefinidas a cadenas de texto bas´andonos en el clasificador de Naive Bayes, y un recomendador que a partir de un fragmento de texto concreto utilizar´a el an´alisis de componentes principales para darnos los textos de una base de datos que mayor similitud guarden con el primero. 4.1– Lenguaje Haskell Haskell es un lenguaje de programaci´on puramente funcional, creado en 1990, y cuyo nombre hace referencia al matem´atico estadounidense Haskell Brooks Curry, pieza fundamental en el desarrollo de los lenguajes de programaci´on funcionales. La programaci´on funcional est´a fundamentada en el uso de funciones puras, siendo estas aquellas cuyo valor de retorno est´a solamente determinado por los valores de entrada, las funciones toman un valor principal en el lenguaje. As´ı como tambi´en es un lenguaje de operaci´on perezosa, s´olo realiza un c´alculo cuando este es estrictamente necesario y hace falta para continuar, esto puede ser interesante a la hora de optimizar el rendimiento del programa, siempre que se tenga cuidado con las fugas de memoria que pueda generar. El uso de lenguajes de programaci´on funcional y evaluaci´on perezosa, siendo Haskell el m´as conocido y usado entre ellos, es de especial inter´es en el mundo de las matem´aticas por su similitud con el lenguaje matem´atico. 4.2– Implementaci´on: Clasificador Naive Bayes Para hacer funcionar el clasificador, partiremos de una base de datos con texto organizado en distintas categor´ıas. La misi´on del clasificador ser´a asignar una categor´ıa concreta de la base de datos a una cadena de texto que aportemos. Ejemplos de uso incluyen valorar si un comentario es negativo o positivo, si un mensaje es spam o no, o, como haremos en el pr´oximo cap´ıtulo, determinar el idioma de un fragmento de texto. La elaboraci´on de la base de datos es una parte crucial para crear un clasificador Nai- ve Bayes. En el pr´oximo cap´ıtulo veremos un ejemplo de ello. Para esta implementaci´on, 23 24 4. Implementaciones consideramos que tenemos una base de datos, a la que nos referiremos como datos, con estructura [(String,String)]. Los pares tendr´an como primera componente un fragmento de texto a estudiar y como segunda la categor´ıa a la que pertenecen. Una vez terminada la aplicaci´on, el texto que aportaremos como entrada a clasificar, se le asignar´a una de estas categor´ıas. Con los datos organizados, podemos comenzar con el desarrollo de la librer´ıa. Nosotros crearemos un nuevo archivo Clasificadors.hs donde ubicaremos el clasificador. 4.2.1. Declaraci´on del m´odulo e importaci´on de librer´ıas Creamos y declaramos un nuevo m´odulo llamado Clasificador, sobre ´el implementaremos el clasificador: C´odigo 4.1: Declaraci´on del m´odulo Clasificador 1module Clasificador where C´odigo 4.1: Declaraci´on del m´odulo Clasificador En el proceso, necesitaremos la importaci´on de las siguientes librer´ıas: •Data.List, librer´ıa disponible por defecto en el entorno de haskell que usaremos para operar con listas, la importamos expl´ıcitamente para poder usar sus funciones con un alias, L, y confrontar posibles errores de ambig¨uedad. •Data.Hashable nos ayuda a crear datos con estructura HashMap, implementados desde la librer´ıa Data.HashMap.Strict. Los datos de estructura HashMap nos ayudar´an con el manejo de los datos ya que nos aportan facilidades a la hora de realizar busquedas entre los mismos. •Data.Ord nos proporciona funcionalidades relacionadas con el orden de los elementos que nos ser´an de utilidad. C´odigo 4.2: Librer´ıas usadas en Clasificador 1import Data.List as L 2import Data.Hashable 3import Data.HashMap.Strict as HM 4import Data.Ord C´odigo 4.2: Librer´ıas usadas en Clasificador 4.2.2. Funci´on frecuencia Definimos una funci´on auxiliar que utilizaremos en diversas ocasiones. Su utilidad ser´a la de contar los elementos de una lista integr´andolos en un HashMap. Esta estructura est´a formada por pares de elementos donde el primero, la clave, ser´a un elemento de la lista a contar y el segundo, el valor, el n´umero de veces que se repite el elemento en la lista, en otras palabras la frecuencia de veces que aparece, de ah´ı el nombre de la funci´on, frecuencia. 4.2. Implementaci´on: Clasificador Naive Bayes 25 C´odigo 4.3: Funci´on frecuencia 1frecuencia :: (Eq k,Data.Hashable.Hashable k,Integral v)=> [k]->HashMap k v 2frecuencia []=HM.empty 3frecuencia (x:xs)=HM.insertWith (+) x1 (frecuencia xs) C´odigo 4.3: Funci´on frecuencia 4.2.3. Funciones para la probabilidad por clase La primera probabilidad a calcular para aplicar el algoritmo de Naive Bayes es la probabilidad a priori, en este caso, estamos hablando de la probabilidad de que el texto pertenezca a una clase u otra sin tener informaci´on previa al respecto. La funci´on frecClase producir´a un HashMap donde la clave representa cada clase y el valor es el n´umero de veces que se repite en la base de datos, es decir, el n´umero de entradas que existen que pertenecen a esa categor´ıa. C´odigo 4.4: HashMap frecClase 1frecClase :: [(String,String)] -> HashMap String Double 2frecClase datos = 3HM.map fromIntegral (frecuencia (L.map fst datos)) C´odigo 4.4: HashMap frecClase La funci´on de probabilidad queda implementada en nuestro m´odulo con probClase. El n´umero de extractos, nExtractos, no es m´as que la suma de todas las entradas o extractos de la base de datos, la cantidad de elementos sin repetici´on. El valor de ese n´umero lo introduciremos directamente en la funci´on, ya que no variar´a por idioma y nos ahorraremos c´alculos no teniendo que calcular la suma para cada idioma, haciendo la aplicaci´on m´as eficiente. C´odigo 4.5: Funci´on probClase 1probClase :: String -> Double -> HashMap String Double -> Double 2probClase clase nExtractos frecuencias = 3lookupDefault 0clase (HM.map (/nExtractos)frecuencias) C´odigo 4.5: Funci´on probClase 4.2.4. Funciones para la probabilidad por palabra dada la clase Tomando como referencia las anteriores funciones de la probabilidad de la clase, el c´alculo de la probabilidad de cada palabra dada la clase toma el mismo patr´on. Tenemos una funci´on frecPalabraPorClase que nos devolver´a las frecuencias de las palabras de una clase concreta. Lo primero ser´a mirar hacia la secuencia “L.filter (\x ->clase == (snd x)) datos”, que consistir´ıa en una lista que filtra los elementos de datos qued´andose con aquellos de la clase elegida. Se realiza un L.map sobre esta lista para operar sobre cada elemento de la misma, se filtran los extractos de texto de ese tipo, se extraen y se separan por palabras, creando una lista con todas las palabras de esa clase. A esa lista de palabras se le aplica frecuencia para obtener el n´umero de veces que se repite cada palabra en la clase, todo ello organizado en un HashMap. Los elementos del 26 4. Implementaciones HashMap estar´ıan conformados por una palabra en la clave y el n´umero de veces que se repite el valor en la clase. C´odigo 4.6: Funci´on frecPalabraPorClase 1frecPalabrasPorClase :: [(String,String)] -> String -> HashMap String Integer 2frecPalabrasPorClase datos clase = 3frecuencia (concatMap words (L.map snd 4(L.filter (\x-> clase == (fst x)) datos))) C´odigo 4.6: Funci´on frecPalabraPorClase La siguiente funci´on calcula la probabilidad de que dadas una palabra y una clase, entre todas las palabras de esa clase, se escoja al azar una palabra y sea la dada. Esta probabilidad estar´a suavizada por el suavizado de Laplace (Laplace Smoothing), como se explic´o en la parte te´orica, de esta manera, sortearemos el Problema de Cero Frecuencia. probSuaviPalabraDadaClase almacena en frecPalabrasEnClase el HashMap de palabras y sus frecuencias de la clase especificada. En nPalabrasEnClase se guarda el n´umero de elementos de frecPalabrasEnClase, es decir, el n´umero de palabras de esa clase, y en nPalabraEnClase la frecuencia de la palabra elegida. Posteriormente, se calcula la probabilidad suavizada por medio de la f´ormula presentada en la parte te´orica: P′(Bi|A) = nABi+α nA +α·N(4.1) Tomando α= 1, por simplicidad. C´odigo 4.7: Funci´on probSuaviPalabraDadaClase 1probSuaviPalabraDadaClase :: String -> String 2-> [(String,String)] -> Double -> Double 3probSuaviPalabraDadaClase palabra clase datos nPalabras = 4let frecPalabrasEnClase =frecPalabrasPorClase datos clase 5nPalabrasEnClase =fromInteger $sum $HM.elems frecPalabrasEnClase 6nPalabraEnClase =fromInteger $ 7lookupDefault 0palabra frecPalabrasEnClase 8in (nPalabraEnClase + 1)/(nPalabrasEnClase +nPalabras) C´odigo 4.7: Funci´on probSuaviPalabraDadaClase 4.2.5. Funci´on para la verosimilitud de la clase dado un texto Llegamos a la parte final de la construcci´on del clasificador, nos queda completar la f´ormula donde se respaldar´a el clasificador para calcular la verosimilitud de que, dado un fragmento de texto, este pertenezca a una clase u otra. Con ayuda de las funciones probSuaviPalabraDadaClase yprobClase, implementamos el resultado de: P(A)· n Y i=1 P′(Bi|A) (4.2) Llamaremos a la nueva funci´on verosiClaseDadoTexto. Destacar que el producto se hace creando una lista con L.map iterando sobre las palabras del texto de la lista (words 4.2. Implementaci´on: Clasificador Naive Bayes 27 texto), y que el resto de las variables se usar´an ya calculadas para mejorar la eficiencia, no dependen de la clase por lo que ser´ıa un gasto innecesario calcularlas para cada clase. C´odigo 4.8: Funci´on verosiClaseDadoTexto 1verosiClaseDadoTexto :: String -> String -> Double -> 2HashMap String Double -> [(String,String)]-> Double -> Double 3verosiClaseDadoTexto clase texto nPalabras frecuencias datos nExtractos = 4let pTexto =L.map (\palabras -> probSuaviPalabraDadaClase palabras clase 5datos nPalabras) (words texto) 6pClase =probClase clase nExtractos frecuencias 7in pClase * (product pTexto) C´odigo 4.8: Funci´on verosiClaseDadoTexto 4.2.6. Clasificadores Presentaremos dos versiones del clasificador, la primera ser´a clasificadorClases que b´asicamente consiste en evaluar la funci´on anterior, verosiClaseDadoTexto, para todas las clases. Nos devolver´a una lista formada por pares donde el primer elemento representar´a una clase y el segundo elemento la verosimilitud de que el texto dado sea de cada clase. La clase que tenga un valor de verosimilitud mayor ser´a la que se le asignar´a. Esta versi´on necesitar´a de los siguientes par´ametros: •texto: el fragmento de texto a clasificar en formato String. •datos: los datos en formato [(String,String)], donde la primera componente del par es un fragmento de texto del conjunto de entrenamiento y la segunda componente representa la clase a la que se asocia ese fragmento. •frecuencias: la frecuencia con la que aparece cada clase en datos en formato HashMap String Double donde la clave representa cada clase valor el n´umero de elementos en datos de esa clase. •clases: una lista cuyos elementos representan las clases en formato [String]. •nPalabras: el n´umero de palabras sin repetici´on que aparecen en los fragmentos de texto de datos en formato Double. •nExtractos: el n´umero de elementos de datos en formato Double. C´odigo 4.9: Funci´on clasificadorClases 1clasificadorClases :: String -> [(String,String)] -> HashMap String Double 2-> [String]->Double -> Double -> [(String,Double)] 3clasificadorClases texto datos frecuencias clases nPalabras nExtractos = 4L.map (\clase ->(clase,verosiClaseDadoTexto clase texto nPalabras 5frecuencias datos nExtractos)) clases C´odigo 4.9: Funci´on clasificadorClases 34 4. Implementaciones •nombres: una lista de String con los nombres/t´ıtulos de los fragmentos de texto de datos sin repetici´on. •matriz: simplemente el resultado de la funci´on palabrasMatriz, la matriz de los datos de las frecuencias referentes a las palabras clave que aparecen por cada elemento de nombres. •(autovalores, autovectores): el resultado de la funci´on eigDescomposicion. •nAutoV: el n´umero de autovalores requeridos para que la varianza explicada por el an´alisis tenga al menos un porcentaje de valor procentaje. •pcaMatriz: el resultado de analisisComponentesPrincipales,matriz proyectada sobre las nuevas componentes principales. •indNombre: la posici´on de nombre en nombres en un Int en caso de que nombre se encuentre en la lista, si esto no es as´ı, devolver´a un mensaje de error. •sumaDeCuadrados: una lista de valores Double, siendo estos la distancia eucl´ıdea cuadrada de las distintas observaciones con respecto a la observaci´on dada nombre. •masCercanos: es la respuesta de indicesOrdenados con respecto a sumaDeCuadrados. Recordemos que esta ser´ıa una lista de los ´ındices de sumaDeCuadrados ordenados en funci´on de lo peque˜nos que sean sus valores. Tendremos por lo tanto, los ´ındices de nombres en funci´on de lo cerca que est´en de nombre. Por ´ultimo definimos el “output” como los primeros nnombres/t´ıtulos de la base de datos m´as cercanos, sin contar nombre. Usaremos genericIndex para que nos devuelva el elemento seg´un su ´ındice. El resultado que proporcionar´a recomendador es una lista [String] con los nombres o t´ıtulos de las nrecomendaciones pedidas. C´odigo 4.22: Funci´on recomendador 1recomendador :: [(String,String)] -> [String]->String -> Int -> Int -> 2Double -> [String] 3recomendador datos palabrasVacias nombre n nClaves porcentaje = 4let claves =L.map fst (palabrasClave datos palabrasVacias nClaves) 5nombres =keys $frecuencia $L.map fst datos 6matriz =palabrasMatriz datos claves nombres 7(autovalores,autovectores)=eigDescomposicion matriz 8nAutoV =nAutovectores autovalores porcentaje 9pcaMatriz =analisisComponentesPrincipales autovectores matriz nAutoV 10 indNombre =fromMaybe (error $"Error: No se encuentra el elemento " 11 ++ nombre ++ " en la base de datos.") (elemIndex nombre nombres) 12 sumaDeCuadrados =LA.toList $distEuclidCuadrPorFila pcaMatriz indNombre 13 masCercanos =indicesOrdenados sumaDeCuadrados 14 in L.map (\ind -> genericIndex nombres ind) 15 (take n(L.filter (/= indNombre)masCercanos)) C´odigo 4.22: Funci´on recomendador CAP´ ITULO 5 Aplicaciones Explicada la teor´ıa del clasificador Naive Bayes y del an´alisis de componentes principales, y habiendo visto sus respectivas implementaciones en librer´ıas en Haskell. Dedicaremos este cap´ıtulo a crear dos aplicaciones que servir´an como ejemplos pr´acticos de sus utilidades. Partiendo de una base de datos que dispondr´a de fragmentos de textos, junto con el idioma en el que est´an escritos y un t´ıtulo que los identifique. La primera de las aplicaciones usar´a la clasificaci´on Naive Bayes para determinar el idioma de una cadena de texto cualquiera dada. Mientras que la segunda, por medio del an´alisis de componentes principales, ser´a capaz de dar una lista de t´ıtulos de la base de datos cuyo texto guarde mayor similitud con un fragmento de texto introducido. Para ello ser´a esencial generar una base de datos. Explicaremos el proceso de crear una base de datos que podamos usar en sendas aplicaciones. La base de datos deber´a constar de texto en distintos idiomas y la capacidad de ser identificados cada uno con un nombre. El contenido del texto debe ser variado en cuanto a las palabras que lo forman y con una extensi´on relativamente considerable, para as´ı asegurar el buen funcionamiento de nuestras aplicaciones. En este caso se trabajar´a con texto extra´ıdo de Wikipedia. 5.1– Wikipedia Wikipedia se define en su entrada hom´onima en su web en espa˜nol como “una enciclopedia libre, pol´ıglota y editada de manera colaborativa”. Conocida por casi todos los internautas, esta enciclopedia creada en 2001 se ha convertido en una de las mayores obras de internet, sustentada de manera independiente, siendo administrada por la Fundaci´on Wikimedia, una organizaci´on sin ´animo de lucro financiada por donaciones, y con m´as de tres mil administradores y casi trescientos mil usuarios que ayudan y escriben para la actualizaci´on y la riqueza de una enciclopedia libre y global. El origen del texto para alimentar la base de datos no ha sido una decisi´on fortuita. Deb´ıa de cumplir unos requisitos concretos: •Variedad de idiomas. •Posibilidad de descargar el texto, el idioma y un t´ıtulo representativo. 35 36 5. Aplicaciones •Diversidad de palabras y temas. Nos deb´ıamos asegurar de que la fuente tuviera una variedad de idiomas considerable. En la mayor´ıa del contenido en la red, el idioma predominante es el ingl´es; sin embargo Wikipedia cumple esto con creces, teniendo art´ıculos en 334 idiomas y, lo que es m´as importante, no de manera anecd´otica: tiene 30 idiomas con m´as de medio mill´on de art´ıculos. Tambi´en era de vital importancia que la p´agina web elegida, contara con una API (interfaz de programaci´on de aplicaciones) que nos permitiera descargar el texto y un t´ıtulo para el mismo. Cosa que Wikipedia nos otorga. No hac´ıa falta necesariamente un t´ıtulo, tambi´en se manejo la posibilidad de obtener un nombre de usuario por cada texto para relacionar la similitud entre usuarios, en definitiva, necesit´abamos un peque˜no grupo de palabras que nos hicieran identificar el texto. Mucho menos usual en las p´aginas webs, es la posibilidad de saber el idioma en el que est´a el texto descargado, muchas no distinguen entre un idioma u otro, pero este no es el caso de Wikipedia que cuenta con un subdominio para cada idioma, de donde se puede extraer f´acilmente el idioma. La diversidad de palabras tambi´en es algo en el que la Wikipedia sobresale, la enciclopedia libre cuenta con una gran cantidad y diversidad de temas en sus art´ıculos. El contenido del conjunto de entrenamiento del modelo vendr´a de art´ıculos de Wikipedia. Concretamente, utilizaremos introducciones de art´ıculos elegidos de manera aleatoria, gracias a una herramienta que nos da Wikipedia. La decisi´on de usar solo la primera parte de los art´ıculos viene motivada por las caracter´ısticas de los art´ıculos en s´ı. Muchos de ellos son art´ıculos cortos y todo su contenido se encuentra en la introducci´on, adem´as elegir s´olo la introducci´on tambi´en nos simplifica el trabajo, s´olo tiene texto simple, nos libramos de las posibles alteraciones en nuestra porci´on de palabras que pueden causar tablas, esquemas u otros formatos m´as all´a del texto simple. Al fin y al cabo, s´olo buscamos informaci´on de texto “real”. Otro beneficio podr´ıa ser la mayor diversidad de origen de palabras, cogiendo solo introducciones nos libramos de posibles art´ıculos m´as largos que puedan acumular una gran parte de las palabras de la base de datos, si los dem´as art´ıculos son de menor tama˜no. Otra decisi´on que se tuvo que tomar es la elecci´on de idiomas que nuestro clasificador pueda tratar. Aunque la idea m´as sencilla y l´ogica pudiera parecer a priori seleccionar los idiomas con m´as art´ıculos, este n´umero est´a distorsionado. Un ejemplo sorprendente de ello es que el segundo idioma con mayor n´umero de art´ıculos, muy cerca del ingl´es y con tres veces m´as art´ıculos que el espa˜nol, sea el cebuano, el sexagesimosegundo idioma m´as hablado del mundo con a penas 21 millones de hablantes y aut´octono de la isla de Ceb´u en Filipinas. Esto parece ser que se debe a la existencia de bots que a˜naden art´ıculos muy cortos de ciudades y pueblos de manera autom´atica de este idioma y otros idiomas. Para ahorrarnos estos idiomas menos habituales, hemos decidido quedarnos con los idiomas con mayor n´umero de usuarios activos como regla a la hora de seleccionar los m´as comunes en la web. Seg´un Wikipedia, el idioma espa˜nol a pesar de contar tres veces menos art´ıculos que el cebuano, tiene 13.284 usuarios activos frente a los 172 del cebuano. Todos estos datos relacionados con el n´umero de usuarios o art´ıculos de Wikipedia est´an a nuestra disposici´on en una entrada de la versi´on inglesa de la propia Wikipedia, llamada “List of Wikipedias”. Fuente debidamente citada en la bibliograf´ıa. 5.2. Preparaci´on de los datos 37 5.2– Preparaci´on de los datos El paso previo a la creaci´on de las aplicaciones es la constituci´on de una base de datos, en este caso, una base de datos con forma de tabla donde se guarden las introducciones de distintos art´ıculos de Wikipedia junto con su t´ıtulo y el idioma correspondiente en el que est´an escritos. 5.2.1. Generaci´on aleatoria de art´ıculos de Wikipedia La selecci´on de los art´ıculos que guardaremos en nuestra base de datos se har´a de manera totalmente aleatoria. En esta subsecci´on dise˜namos este proceso aleatorio. Toda esta primera implementaci´on estar´a ubicada en un archivo que hemos llamado GeneradorEnlaces.hs. Lo primero que debemos hacer en este archivo ser´a declarar el m´odulo que estamos creando, GeneradorEnlaces, marcando en ´el las funciones enlacesAleatoriosPorIdioma yenlacesAleatorios que querremos exportar para usar en otros programas: C´odigo 5.1: Declaraci´on del m´odulo GeneradorEnlaces 1module GeneradorEnlaces ( 2enlacesAleatoriosPorIdioma, 3enlacesAleatorios 4)where C´odigo 5.1: Declaraci´on del m´odulo GeneradorEnlaces Ser´a fundamental importar las siguientes librer´ıas: •Para evitar ambig¨uedades, ocultaremos la funci´on est´andar id del m´odulo Prelude, importado por defecto. •Network.HTTP.Client yNetwork.HTTP.Client.TLS para hacer las peticiones correspondientes a la API. •Data.Aeson donde encontraremos herramientas para tratar los datos en formato JSON y convertirlos a tipos de datos m´as manejables. •GHC.Generics nos aportar´a la derivaci´on autom´atica de instancias. C´odigo 5.2: Librer´ıas usadas en GeneradorEnlaces 1import Prelude hiding (id) 2import Network.HTTP.Client 3import Network.HTTP.Client.TLS 4import Data.Aeson 5import GHC.Generics C´odigo 5.2: Librer´ıas usadas en GeneradorEnlaces Los tipos de datos est´an determinados por el formato que nos proporciona la API. Tendremos 3 tipos, cada uno dentro del siguiente, RespuestaApi es el nos devolver´a la API con un s´olo campo llamado query de tipo BusquedaAleatoria que nos indicar´a con su ´unico campo el tipo de busqueda que hemos hecho en la API, “random”. Este campo 38 5. Aplicaciones random es una lista del ´ultimo tipo de dato, ArticuloAleatorio, cuyo ´unico campo id nos aportar´a un n´umero de tipo Int que usaremos para identificar el art´ıculo elegido de manera aleatoria. C´odigo 5.3: Tipos de datos usados 1data ArticuloAleatorio =ArticuloAleatorio 2{id :: Int 3}deriving (Show,Generic) 4 5data BusquedaAleatoria =BusquedaAleatoria 6{random :: [ArticuloAleatorio] 7}deriving (Show,Generic) 8 9data RespuestaApi =RespuestaApi 10 {query :: BusquedaAleatoria 11 }deriving (Show,Generic) C´odigo 5.3: Tipos de datos usados Las siguientes 3 l´ıneas de c´odigo van destinadas a la deserializaci´on de datos en formato JSON, para convertirlos en nuestros datos ya definidos. C´odigo 5.4: Instancias usadas 1instance FromJSON ArticuloAleatorio 2instance FromJSON BusquedaAleatoria 3instance FromJSON RespuestaApi C´odigo 5.4: Instancias usadas La API que proporciona Wikipedia da la posibilidad de obtener de manera aleatoria unos identificadores (id) que podremos usar para encontrar estos art´ıculos en el pr´oximo m´odulo. Sin embargo, la elecci´on del idioma no es aleatoria y debe hacerse desde el subdominio de la url de la llamada de la API. Esto nos ofrece algunas ventajas como que podremos asegurarnos de que tenemos en nuestra base de datos texto proveniente del mismo n´umero de art´ıculos. Si la elecci´on del idioma fuera aleatoria, tendr´ıamos una gran cantidad de texto de los idiomas que m´as art´ıculos tengan en Wikipedia y nuestro clasificador podr´ıa fallar en algunos idiomas. La parte negativa es que no podremos estimar la frecuencia con la que los art´ıculos tienen un idioma u otro a partir de los datos obtenidos, ya que todos los idiomas tendr´an el mismo n´umero de art´ıculos. Esto podr´ıa llegar a ser un problema en otros contextos pero en este caso, consultando la entrada ya mencionada List of Wikipedias, tenemos a nuestro alcance datos sobre el n´umero de usuarios activos por idioma. Posteriormente, en el desarrollo de la aplicaci´on usaremos estos datos para determinar los idiomas que usaremos y la frecuencia de cada uno de ellos. Llegamos a nuestra primera funci´on, enlacesAleatoriosPorIdioma har´a un llamamiento a la API de Wikipedia para elegir de manera aleatoria un n´umero concreto de art´ıculos, nArticulos, en un idioma concreto que vendr´a indicado por idioma.idioma ser´a un String formado por un subdominio de Wikipedia identificado con un idioma ("en", "es","fr", etc). Los c´odigos de los idiomas usados forman parte de la lista ISO 639-1, la lista de abreviaturas de dos letras de idiomas m´as usada globalmente y la que utiliza Wikipedia en sus subdominios. 5.2. Preparaci´on de los datos 39 La funci´on devolver´a una lista con una dupla con dos String, con el c´odigo del idioma y el identificador (id) del art´ıculo seleccionado de manera aleatoria. Cada c´odigo har´a referencia a un art´ıculo concreto, del que posteriormente descargaremos el texto haciendo otra llamada a la API. C´odigo 5.5: Funci´on enlacesAleatoriosPorIdioma 1enlacesAleatoriosPorIdioma :: Int -> String -> IO [(String,String)] 2enlacesAleatoriosPorIdioma nArticulos idioma =do 3manager <- newManager tlsManagerSettings 4let url ="https://" ++ idioma ++ ".wikipedia.org/w/api.php?action=query"++ 5"&format=json&list=random&rnnamespace=0&rnlimit="++(show nArticulos) 6req <- parseRequest url 7res <- httpLbs req manager 8let maybeResponse =decode (responseBody res)::Maybe RespuestaApi 9return $case maybeResponse of 10 Just respuestaApi -> 11 fmap (\page -> (idioma,show (id page))) (random (query respuestaApi)) 12 Nothing -> [] C´odigo 5.5: Funci´on enlacesAleatoriosPorIdioma Terminamos este m´odulo con una simple funci´on enlacesAleatorios que nos devuelva el resultado de enlacesAleatoriosPorIdioma aplicada a todos los idiomas de la lista idiomas. Elegiremos aleatoriamente un n´umero nArticulos de art´ıculos por cada idioma, el n´umero de art´ıculos por llamada ser´a limitado por quinientos, si nArticulos supera este l´ımite se realizar´a una recursi´on por la que haremos llamamientos a la funci´on enlacesAleatoriosPorIdioma, y por ende a la API, con un m´aximo de quinientos art´ıculos hasta llegar al n´umero nArticulos requerido. Gracias a ser Haskell un lenguaje funcional, podremos usar la funci´on enlacesAleatorios como si fuera una lista. C´odigo 5.6: Funci´on enlacesAleatorios 1enlacesAleatorios :: Int -> [String]->IO [(String,String)] 2enlacesAleatorios nArticulos idiomas = 3if nArticulos > 500 4then do resto <- enlacesAleatorios (nArticulos-500) idiomas 5resultadoAux <- mapM (\x-> enlacesAleatoriosPorIdioma 500 x)idiomas 6return ((concat resultadoAux)++resto) 7else do resul <- mapM (\x-> enlacesAleatoriosPorIdioma nArticulos x)idiomas 8return (concat resul) C´odigo 5.6: Funci´on enlacesAleatorios 5.2.2. Extracci´on del texto de los art´ıculos El m´odulo GeneradorTextos tendr´a como objetivo convertir la lista de duplas de c´odigos de idioma e identificadores de art´ıculos en otra lista de tuplas de tres elementos con el mismo c´odigo de idioma, el t´ıtulo del art´ıculo y la introducci´on del mismo. El nuevo archivo GeneradorTextos.hs comenzar´a con la declaraci´on del m´odulo, indicando futuras exportaciones. C´odigo 5.7: Declaraci´on del m´odulo GeneradorTextos 40 5. Aplicaciones 1module GeneradorTextos ( 2Wiki, 3idioma, 4titulo, 5texto, 6textosAleatorios, 7extractorIntroTitulo 8)where C´odigo 5.7: Declaraci´on del m´odulo GeneradorTextos Necesitaremos alguna librer´ıa m´as en esta ocasi´on, concretamente: •Importaremos el m´odulo que hicimos para disponer de enlacesAleatorios. •Tambi´en necesitaremos todas las librer´ıas del anterior “script” para volver a hacer llamamientos a la API y tratar los resultados. Estas ser´ıan Network.HTTP.Client, Network.HTTP.Client.TLS,Data.Aeson yGHC.Generics. •Data.HashMap.Strict ser´a ´util para manejar los datos con estructura de HashMap, que ser´a como se descargar´an algunos datos de la API. La importaci´on la haremos con import qualified Data.HashMap.Strict as HM para ahorrarnos problemas de ambig¨uedades y poder acceder a los elementos de la librer´ıa de manera m´as simple con el prefijo HM. •La estructura de datos Text la manipularemos con Data.Text, librer´ıa que marcaremos con T. •Ajustaremos el texto descargado con herramientas de Data.Char para quedarnos s´olo con palabras y eliminar s´ımbolos u otros caracteres del texto plano que no sean palabras. C´odigo 5.8: Librer´ıas usadas en GeneradorTextos 1import GeneradorEnlaces 2import Network.HTTP.Client 3import Network.HTTP.Client.TLS 4import Data.Aeson 5import GHC.Generics 6import qualified Data.HashMap.Strict as HM 7import qualified Data.Text as T 8import Data.Char C´odigo 5.8: Librer´ıas usadas en GeneradorTextos Esta vez tendremos que la API nos proporcionar´a los datos con estructura RespuestaApi, un tipo de dato con un ´unico campo llamado query de tipo Busqueda que a su vez tendr´a otro campo de pages con estructura de HashMap. Los elementos de tipo HashMap almacenan datos en pares de clave-valor, en nuestro caso una clave de tipo T.Text y un valor de tipo Articulo que definiremos con un campo extract de tipo T.Text donde se descargar´a el texto plano de la introducci´on del art´ıculo, y un campo title donde har´a lo propio el 5.2. Preparaci´on de los datos 41 t´ıtulo. El operador !se usa para marcar que ese campo debe ser evaluado estrictamente, no perezosamente, para ahorrarnos posibles problemas de memoria. Por ´ultimo tenemos un tipo que no pertenece al de los datos descargados de la API sino que lo hemos creado nosotros para almacenar los datos que saquemos y conseguir as´ı una sem´antica m´as clara. Hemos definido una estructura de datos Wiki con tres campos idioma, donde ir´a el c´odigo del idioma, titulo con el t´ıtulo del art´ıculo y texto en referencia al texto que sacaremos de cada art´ıculo. C´odigo 5.9: Tipos de datos usados 1data RespuestaApi =RespuestaApi 2{query :: Busqueda 3}deriving (Show,Generic) 4 5data Busqueda =Busqueda 6{pages :: HM.HashMap T.Text Articulo 7}deriving (Show,Generic) 8 9data Articulo =Articulo 10 {title :: !T.Text, 11 extract :: !T.Text 12 }deriving (Show,Generic) 13 14 data Wiki =Wiki 15 {idioma :: !T.Text, 16 titulo :: !T.Text, 17 texto :: !T.Text 18 }deriving (Show,Generic) C´odigo 5.9: Tipos de datos usados As´ı quedar´ıan las instancias: C´odigo 5.10: Instancias usadas 1instance FromJSON RespuestaApi 2instance FromJSON Busqueda 3instance FromJSON Articulo C´odigo 5.10: Instancias usadas La funci´on principal del m´odulo es extractorIntroIdiomas, a partir de un (String, String) donde el primer String representa un c´odigo de idioma y el segundo un identificador de un art´ıculo de Wikipedia, y tras hacer un llamamiento a la API, recoger los datos en res, y decodificarlos en maybeRespuestaApi en un formato Maybe RespuestaApi, conseguimos un resultado en formato Either String Wiki donde la salida ser´a un String indicando de un error si el c´odigo era err´oneo, o simplemente maybeRespuestaApi est´a vac´ıo por otro tipo de error, o un elemento Wiki que guarda el idioma, el t´ıtulo y el texto ya manipulado con limpiadorIntroduccion, una funci´on que definiremos m´as adelante, que nos dejar´a el texto de la introducci´on del art´ıculo “limpio”. C´odigo 5.11: Funci´on extractorIntroIdiomas 1extractorIntro :: (String,String)->IO (Either String Wiki) 2extractorIntro (idioma,articuloID)=do 42 5. Aplicaciones 3manager <- newManager tlsManagerSettings 4let url ="https://" ++ idioma ++ 5".wikipedia.org/w/api.php?action=query&prop=info|extracts" 6++ "&explaintext&exintro&format=json&pageids=" ++ articuloID 7req <- parseRequest url 8res <- httpLbs req manager 9let maybeRespuestaApi =decode (responseBody res)::Maybe RespuestaApi 10 return $case maybeRespuestaApi of 11 Just respuestaApi -> 12 let articulo =Prelude.head $HM.elems $pages $query respuestaApi 13 texto =extract articulo 14 textoLimpio =limpiadorIntroduccion texto 15 titulo =title articulo 16 in Right (Wiki (T.pack idioma)titulo textoLimpio) 17 Nothing -> Left $("Texto no encontrado") C´odigo 5.11: Funci´on extractorIntroIdiomas Adem´as de extractorIntroIdiomas que conseguir´a descargar toda la informaci´on necesaria y guardarla en un dato con estructura Wiki por medio del idioma y el c´odigo del art´ıculo, definimos una versi´on alternativa que har´a lo mismo pero a trav´es de un [(String,String)] con el dato del idioma y el t´ıtulo directamente. La ´unica diferencia ser´a la llamada a la API que utilizar´a el t´ıtulo en vez del c´odigo para identificar los art´ıculos. Esta funci´on ser´a utilizada en la aplicaci´on del an´alisis de componentes principales, donde buscaremos similitudes entre los art´ıculos recogidos de manera aleatoria y uno del que el usuario dar´a el t´ıtulo. La funci´on se usar´a para obtener la introducci´on de este art´ıculo no aleatorio. C´odigo 5.12: Funci´on extractorIntroTitulo 1extractorIntroTitulo :: (String,String)->IO (Either String Wiki) 2extractorIntroTitulo (idioma,titulo)=do 3manager <- newManager tlsManagerSettings 4let url ="https://" ++ idioma ++ 5".wikipedia.org/w/api.php?action=query&prop=info|extracts" 6++ "&explaintext&exintro&format=json&titles=" ++ titulo 7req <- parseRequest url 8res <- httpLbs req manager 9let maybeRespuestaApi =decode (responseBody res)::Maybe RespuestaApi 10 return $case maybeRespuestaApi of 11 Just respuestaApi -> 12 let articulo =Prelude.head $HM.elems $pages $query respuestaApi 13 texto =extract articulo 14 textoLimpio =limpiadorIntroduccion texto 15 tituloAux =title articulo 16 in Right (Wiki (T.pack idioma)tituloAux textoLimpio) 17 Nothing -> Left $("Texto no encontrado") C´odigo 5.12: Funci´on extractorIntroTitulo Definiremos a continuaci´on la funci´on limpiadorIntroduccion que “limpiar´a” el texto de las introducciones. Sabiendo que la introducci´on adquirida en formato de texto plano 5.2. Preparaci´on de los datos 43 tendr´a "\n" indicando el final de cada p´arrafo, cambiaremos esto por un espacio en blanco para poder separar la ´ultima palabra de cada p´arrafo con la primera del siguiente. Adem´as, estrenamos la librer´ıa Data.Char para filtrar el texto, quitar todo lo que no sean letras o espacios en blanco y pasar todas las letras a min´usculas. C´odigo 5.13: Funci´on limpiadorIntroduccion 1limpiadorIntroduccion :: T.Text -> T.Text 2limpiadorIntroduccion text = 3let introLimpia =T.replace (T.pack "\n") (T.pack " ")text 4in T.pack (Prelude.map toLower (Prelude.filter (\c-> isAlpha c|| c== ’ ’) 5(T.unpack (introLimpia)))) C´odigo 5.13: Funci´on limpiadorIntroduccion Terminamos con una funci´on similar a la ´ultima del anterior m´odulo, extiende la funci´on principal extractorIntroIdiomas a todos los elementos de enlacesAleatorios, importada desde el otro m´odulo. En textosAleatorios tendremos una lista de datos con estructura Wiki que almacenar´an idioma, t´ıtulo e introducci´on de unos art´ıculos de Wikipedia elegidos de manera aleatoria. C´odigo 5.14: Funci´on textosAleatorios 1textosAleatorios :: Int -> [String]->IO [Either String Wiki] 2textosAleatorios nArticulos idiomas =do 3aux <- enlacesAleatorios nArticulos idiomas 4mapM extractorIntro aux C´odigo 5.14: Funci´on textosAleatorios 5.2.3. Creaci´on de nuestra base de datos GeneradorBaseDatos, ubicado en un nuevo archivo hom´onimo, ser´a nuestro ´ultimo m´odulo dedicado a la construcci´on de la base de datos. Desarrollaremos en ´el una serie de funciones que usaremos para crear dos bases de datos para cada una de las dos aplicaciones. Usaremos una base de datos SQLite. SQlite es un tipo de base de datos que no requiere de una conexi´on con un servidor para almacenar los datos, lo hace en un solo archivo del sistema de archivos local. Comenzamos el m´odulo con la declaraci´on como siempre. C´odigo 5.15: Declaraci´on del m´odulo GeneradorBaseDatos 1module GeneradorBaseDatos where C´odigo 5.15: Declaraci´on del m´odulo GeneradorBaseDatos Importamos el anterior m´odulo GeneradorTextos. Adem´as de 2 nuevas librer´ıas sobre las base de datos SQLite, Database.HDBC yDatabase.HDBC.Sqlite3, y otras 2 para obtener herramientas que necesitaremos en el desarollo del m´odulo, la primera sobre las listas, Data.List, y otra sobre los tipos de datos Either,Data.Either. C´odigo 5.16: Librer´ıas usadas en GeneradorBaseDatos 50 5. Aplicaciones 5.3.4. Clasificadores de idiomas Con todos los preparativos terminados, se pueden definir las funciones definitivas que ejercer´an de clasificadores de idiomas. Tendremos 2 versiones. Primero presentaremos clasificadorIdioma, que a partir de un fragmento de texto en formato String, nos devolver´a otro String con el c´odigo ISO 639-1 que el programa le asign´o. Para ello, la funci´on guardar´a intros en datos. Usaremos esa informaci´on para definir el resto de par´ametros que necesitamos para clasificadorClases, importado desde la librer´ıa que dise˜namos previamente. El ´unico dato que no calcularemos ser´a la frecuencia que la ten´ıamos almacenada de manera expl´ıcita en frecUsuariosPorIdioma. Recibiremos una lista con todos los idiomas y su verosimilitud, elegimos la m´as alta y terminamos mostrando el c´odigo que representa el idioma que nuestro clasificador considera que debe ser asignado. C´odigo 5.28: Funci´on clasificadorIdioma 1clasificadorIdioma :: String -> IO String 2clasificadorIdioma texto =do 3datos <- intros 4let clases =HM.keys $(frecuencia .L.map fst)datos 5nPalabras =fromInteger $sum $HM.elems 6((frecuencia .concatMap words) (L.map snd datos)) 7nExtractos =sum $HM.elems frecUsuariosPorIdioma 8probs =clasificadorClases texto datos frecUsuariosPorIdioma 9clases nPalabras nExtractos 10 return $fst (L.maximumBy (comparing snd)probs) C´odigo 5.28: Funci´on clasificadorIdioma La segunda versi´on es m´as completa, s´olo variar´a en la ´ultima l´ınea, donde en vez de quedarse con el idioma con mayor verosimilitud, devolver´a una lista en formato [(String, Double)], donde el primer valor del par es el c´odigo del idioma y el segundo la respectiva verosimilitud. El orden de la lista no ser´a arbitrario, ir´a ordenada de mayor verosimilitud a menor. C´odigo 5.29: Funci´on clasificadorIdiomaTodos 1clasificadorIdiomaTodos texto =do 2datos <- intros 3let clases =HM.keys $(frecuencia .L.map fst)datos 4nPalabras =fromInteger $sum $HM.elems 5((frecuencia .concatMap words) (L.map snd datos)) 6nExtractos =sum $HM.elems frecUsuariosPorIdioma 7probs =clasificadorClases (L.map toLower texto)datos 8frecUsuariosPorIdioma clases nPalabras nExtractos 9return (L.sortBy (comparing (Down .snd)) probs) C´odigo 5.29: Funci´on clasificadorIdiomaTodos 5.4. Aplicaci´on: Recomendador art´ıculos 51 5.4– Aplicaci´on: Recomendador art´ıculos El desarrollo del recomendador de art´ıculos que montaremos guarda similitudes con la anterior aplicaci´on. En esta ocasi´on, crearemos una base de datos con un mayor n´umero de introducciones pero de un s´olo idioma, nos interesar´a comparar los art´ıculos entre ellos y para ello deben estar en el mismo idioma. Por lo tanto, s´olo nos interesaran las columnas de la tabla de la base de datos relativas al t´ıtulo y el texto. La misi´on de la aplicaci´on que escribiremos en el fichero RecomendadorTitulos.hs, ser´a dar un uso pr´actico a las funciones generales que definimos en Recomendador.hs. Utilizaremos el clasificador que dise˜namos con las bases te´oricas del an´alisis de componentes principales para crear un recomendador que, a partir de cualquier t´ıtulo de un art´ıculo web de Wikipedia que proporcione el usuario, devuelva una lista de los art´ıculos de la base de datos que tienen mayor similitud con el dado. Teniendo la base de datos sus art´ıculos recogidos de manera aleatoria. 5.4.1. Declaraci´on del m´odulo e importaci´on de librer´ıas Crearemos un m´odulo con el mismo nombre que dimos al archivo. C´odigo 5.30: Declaraci´on del m´odulo RecomendadorArticulos 1module RecomendadorArticulos where C´odigo 5.30: Declaraci´on del m´odulo RecomendadorArticulos Entre las librer´ıas importadas, destacaremos las dos creadas para este trabajo, GeneradorBaseDatos yRecomendador. Adem´as tendremos Data.List yData.HashMap.Strict para operar sobre listas y datos HashMap respectivamente, y Database.HDBC.Sqlite3 y Database.HDBC que usaremos para hacer consultas a la base de datos. C´odigo 5.31: Librer´ıas usadas en RecomendadorArticulos 1import GeneradorBaseDatos 2import Recomendador 3import Data.List as L 4import Data.HashMap.Strict as HM 5import Database.HDBC.Sqlite3 6import Database.HDBC C´odigo 5.31: Librer´ıas usadas en RecomendadorArticulos 5.4.2. Generaci´on base de datos Las dos siguientes funciones estar´an dedicadas a crear la base de datos. La funci´on introsArticulos crear´a una base de datos de tantos art´ıculos como se especifique en nIntros y el idioma introducido por su c´odigo ISO 639-1 en idioma. Usar´a la funci´on baseDatosAleatoria de GeneradorBaseDatos. El nombre que le hemos asignado a la base de datos SQL es introsArticulos.sql donde se crear´a una tabla tablaArticulos. C´odigo 5.32: Funci´on introsArticulos 1introsArticulos :: Int -> String -> IO () 2introsArticulos nIntros idioma =do 52 5. Aplicaciones 3baseDatosAleatoria "introsArticulos.sql" "tablaArticulos" nIntros [idioma] C´odigo 5.32: Funci´on introsArticulos Se usar´a insertarArticulo para introducir en la tabla de la base de datos creada, introsArticulos un art´ıculo cualquiera. La informaci´on que daremos de este art´ıculo a introducir ser´a un par (String,String) del c´odigo ISO 639-1 del idioma y su t´ıtulo. La utilidad que se le dar´a a esta funci´on ser´a la de conseguir los datos del art´ıculo del que se buscar´an recomendaciones e introducirlos en la base de datos. C´odigo 5.33: Funci´on insertarArticulo 1insertarArticulo :: (String,String)->IO () 2insertarArticulo articulo =do 3insertarTextoEnBaseDatosArticulo articulo "introsArticulos.sql" 4"tablaArticulos" C´odigo 5.33: Funci´on insertarArticulo Lista introsTitulos Accederemos a la tabla tablaArticulos de la base de datos creada para conseguir los t´ıtulos de los art´ıculos y sus introducciones. Crearemos as´ı una lista de pares de estructura [(String,String)] donde el primer elemento ser´a el t´ıtulo y el segundo el texto de la introducci´on. Llamaremos introsTitulos a la funci´on que nos aportar´a esta lista de los datos definitivos que usaremos en el recomendador. C´odigo 5.34: Funci´on introsTitulos 1introsTitulos :: IO [(String,String)] 2introsTitulos =do 3baseDatos <- buscadorBaseDatos 4"introsArticulos.sql" "SELECT titulo, texto FROM tablaArticulos" 5let intros =zip (extraerColumna baseDatos 0) (extraerColumna baseDatos 1) 6return $HM.keys $frecuencia intros C´odigo 5.34: Funci´on introsTitulos 5.4.3. Palabras vac´ıas Una de las cosas que necesit´abamos implementar para crear nuestro recomendador era una lista de palabras que el an´alisis ignorar´ıa por su excesiva repetici´on y su poca aportaci´on al an´alisis que se quiere hacer. Ya mencionamos que a este tipo de palabras se le daba el nombre de palabras vac´ıas. Hemos a˜nadido algo menos de 500 de palabras de este tipo al m´odulo en una lista de nombre palabrasVacias. Esta lista contiene todo tipo de palabras comunes del espa˜nol que no nos dar´ıan informaci´on importante como “el”, “un”, “arriba”, “mucho”, etc. , adem´as de algunas palabras que son muy repetidas en el contexto de los ´articulos de Wikipedia como “mundial”, “conocido”, “ubicado” o “nombre”. C´odigo 5.35: Principio lista palabrasVacias 5.4. Aplicaci´on: Recomendador art´ıculos 53 1palabrasVacias :: [String] 2palabrasVacias = ["alg´un","alguna","algunas","alguno","algunos","ambos", 3"empleamos","ante","antes","aquel","aquellas","aquellos", 4"aqu´ı","arriba","atr´as","bajo","bastante","bien","cada", 5"cierta","ciertas","cierto","ciertos","como","con", C´odigo 5.35: Principio lista palabrasVacias 5.4.4. Recomendador de t´ıtulos Terminamos la aplicaci´on con la funci´on que nos dar´a la lista final de recomendaciones. La funci´on recomendadorArticulos recibir´a un String que ser´a el t´ıtulo del que se quiere buscar recomendaciones, titulo y un n´umero nen formato Int que ser´a el n´umero de recomendaciones que se dar´an. Por medio de insertarArticulo a˜nadir´a los datos del nuevo art´ıculo a la base de datos creada introsArticulos.sql por introsArticulos. Posteriormente se realizar´a un llamamiento a la funci´on recomendador del m´odulo hom´onimo. En el c´alculo se han a˜nadido dos valores de manera arbitraria, 50 que ser´a el n´umero de variables a las que se aplicar´a el an´alisis de variables principales, y 0.9 en referencia a que se buscar´a un an´alisis que explique como m´ınimo un 90 % de la varianza de estas 50 variables. Estos valores se pueden modificar para buscar un mayor ajuste en la soluci´on final. C´odigo 5.36: Funci´on recomendadorArticulos 1recomendadorArticulos :: String -> Int -> IO [String] 2recomendadorArticulos titulo n =do 3insertarArticulo ("es",titulo) 4datos <- introsTitulos 5return $recomendador datos palabrasVacias titulo n 50 0.9 C´odigo 5.36: Funci´on recomendadorArticulos CAP´ ITULO 6 Conclusiones Los m´etodos de aprendizaje autom´atico nos rodean en nuestro d´ıa a d´ıa. Vemos ejemplos en todos los lados, ya sea en los v´ıdeos recomendados de YouTube, en los algoritmos de distintas redes sociales, aplicaciones de reconocimiento de voz, detecci´on de fraudes o incluso en c´amaras que realizan an´alisis de sentimientos. El objetivo del trabajo no es otro que introducir al lector en el razonamiento matem´atico que subyace a estos problemas. Durante la extensi´on del mismo, hemos analizado dos de estos procesos que, aunque su modo de afrontar los problemas es totalmente distinto —y uno de ellos carece de un conjunto de entrenamiento como tal—, hemos visto que comparten varias similitudes. De hecho en nuestro ejemplo de aplicaci´on de un sistema no supervisado, el recomendador de art´ıculos, s´ı que se utilizaba una especie de base de datos preconcebida a la que se le iban uniendo los nuevos objetivos a analizar. La principal diferencia era la no existencia de clases preconcebidas, en el aprendizaje supervisado part´ıamos de una clases ya establecidas; en nuestro ejemplo, los idiomas. Ambos problemas ser´ıan adaptables a un proceso de la naturaleza opuesta, se podr´ıa hacer un clasificador de idiomas con aprendizaje no supervisado donde las componentes principales terminar´ıan siendo los distintos idiomas, no se introducir´ıan expl´ıcitamente, tendr´ıamos tantos idiomas para clasificar como hubiera en la base de datos, y la respuesta final del idioma asignado vendr´ıa dada por la mayor componente principal de cada caso. De manera similar, el otro caso tambi´en es transmutable, podr´ıamos definir ciertas categor´ıas para cada art´ıculo y la aplicaci´on recomendar´ıa una categor´ıa de art´ıculo. La implementaci´on de las librer´ıas en el cuarto cap´ıtulo nos dej´o tambi´en cosas interesantes. Pudimos ver una combinaci´on entre el aprendizaje autom´atico y un lenguaje de programaci´on funcional, donde las funciones actuaban como valores y datos, y tomaban un papel principal. Adem´as de ser una perfecta puerta de entrada al mundo de la programaci´on desde una perspectiva matem´atica. Se considerar´ıa interesante ampliar el campo de estudio del trabajo a algoritmos o aplicaciones que pudieran tener como respuesta una soluci´on gr´afica para mostrar las mejoras en la visualizaci´on y s´ıntesis de la informaci´on de los datos. Sobre todo en el caso del an´alisis de componentes principales donde se pudiera reducir el n´umero de componentes a dos o tres para poder expresar cada componente en un eje de manera sencilla, y poder darnos cuenta de la verdadera potencia de an´alisis que tiene este proceso. Concluimos el trabajo habiendo explicado las claves principales de algunas de las t´ecni- 55 56 6. Conclusiones cas m´as conocidas del an´alisis de datos referentes al aprendizaje autom´atico, y dando algunos ejemplos de las mismas. Esperamos que los conocimientos adquiridos en este trabajo sirvan como cimientos para futuras investigaciones y proyectos. Bibliograf´ıa [1] James Church Learning Haskell Data Analysis Packt Publishing, 2015. [2] Francisco Montes Suay Introducci´on a la probabilidad Apuntes, Universitat de Val`encia, 2007. [3] William Feller An Introduction to Probability Theory and Its Applications Wiley, 1957. [4] Roger A. Horn & Charles R. Johnson Matrix Analysis Cambridge University Press, 2013. [5] Alexander McFarlane Mood, Franklin A. Graybill, Duane C. Boes Introduction to the Theory of Statistics McGraw-Hill, tercera edici´on, 1974. [6] Ian T. Jolliffe & Jorge Cadima Principal component analysis: a review and recent developments The Royal Society Publishing, 2016. Disponible en: https: //royalsocietypublishing.org/doi/epdf/10.1098/rsta.2015.0202 [7] Ionos ¿Qu´e es Haskell? P´agina web, consultada el 11 de septiembre de 2024. Disponible en: https://www.ionos.es/digitalguide/paginas-web/desarrollo-web/ que-es-haskell/ [8] Analytics Vidhya Introduction to the Powerful Bayes Theorem for Data Science P´agina web, consultada el 11 de septiembre de 2024. Disponible en: https://www.analyticsvidhya.com/blog/2019/06/ introduction-powerful-bayes-theorem-data-science/?utm_source= Backlink&utm_medium=SEO [9] Analytics Vidhya Na¨ıve Bayes Algorithm P´agina web, consultada el 11 de septiembre de 2024. Disponible en: https://medium.com/analytics-vidhya/na%C3% AFve-bayes-algorithm-5bf31e9032a2 [10] Carlos D. Carpio M´etodos de Clasificaci´on: Naive Bayes P´agina web, consultada el 12 de septiembre de 2024. Disponible en: https://dcain.etsin.upm.es/~carlos/ bookAA/02.1_MetodosdeClasificacion-Naive-Bayes.html [11] Wikipedia contributors List of Wikipedias Wikipedia, consultada el 13 de septiembre de 2024. Disponible en: https://en.wikipedia.org/wiki/List_of_Wikipedias [12] Wikipedia contributors Wikipedia Wikipedia en espa˜nol, consultada el 13 de septiembre de 2024. Disponible en: https://es.wikipedia.org/wiki/Wikipedia 57 58 Bibliograf´ıa [13] Ivan Lokhov Wikipedia articles written by a bot Datawrapper Blog, consultada el 13 de septiembre de 2024. Disponible en: https://blog.datawrapper.de/ wikipedia-articles-written-by-a-bot/ [14] Joshua New Why Do People Still Think Data Is the New Oil? Center for Data Innovation, consultada el 27 de septiembre de 2024. Disponible en: https://datainnovation.org/2018/01/ why-do-people-still-think-data-is-the-new-oil/ Anexo I: Salidas del clasificador Naive Bayes En este anexo se muestran las salidas obtenidas del ejemplo de implementaci´on del clasificador Naive Bayes en Haskell presentado con anterioridad, utilizando el entorno interactivo GHCi (Glasgow Haskell Compiler interactive). 1ghci> :l ClasificadorIdioma 2[1 of 5] Compiling Clasificador (Clasificador.hs,interpreted ) 3[2 of 5] Compiling GeneradorEnlaces (GeneradorEnlaces.hs,interpreted ) 4[3 of 5] Compiling GeneradorTextos (GeneradorTextos.hs,interpreted ) 5[4 of 5] Compiling GeneradorBaseDatos (GeneradorBaseDatos.hs,interpreted ) 6[5 of 5] Compiling ClasificadorIdioma (ClasificadorIdioma.hs,interpreted ) 7Ok,five modules loaded. 8(0.79 secs,) 9ghci>introsIdiomas 50 10 Base de datos creada con ´exito. 11 Se han insertado 1500 elementos en la base de datos con ´exito. 12 (1109.29 secs, 16,529,818,528 bytes) La base de datos estar´a nutrida de 1500 introducciones de art´ıculos aleatorios de Wikipedia, 50 de cada uno de los 30 idiomas. En nuestro caso, tardar´a 1109,29 segundos en su construcci´on, unos 18 minutos y medio. Incluiremos como ejemplos la evaluaci´on de la frase “Me alegro mucho de verte” en distintos idiomas: espa˜nol, ingl´es, portugu´es, alem´an, polaco, italiano y noruego. 1ghci>clasificadorIdioma "Me alegro mucho de verte" 2"es" 3(1.85 secs, 1,328,026,208 bytes) 4ghci>clasificadorIdiomaTodos "Me alegro mucho de verte" 5[("es",1.4385796966713727e-23),("fr",2.0575862825810786e-24), 6("pt",1.5006988488188862e-24),("en",1.3350031668054959e-24), 7("nl",4.312475639514897e-25),("ca",1.868594793261105e-25), 8("sv",7.40659682333777e-26),("ro",7.234569525937924e-26), 9("it",2.457068448809503e-26),("tr",2.0575288642321292e-26), 10 ("hu",1.6668801211580946e-26),("no",1.1000231787011853e-26), 11 ("de",1.0814625409986618e-26),("ar",9.750177632771201e-27), 12 ("ja",9.45650958601682e-27),("id",6.475369149049737e-27), 13 ("fa",6.467582829508386e-27),("uk",5.923137433434641e-27), 14 ("ru",5.703296873969729e-27),("pl",5.6597861219381146e-27), 15 ("zh",5.320793439211851e-27),("vi",4.238956125487497e-27), 59