Introducción

  • Junto con las imágenes y los audios, los textos son una fuente de datos no estructurados que se multiplicó en los últimos años.

  • Para poder hacer uso de la información que contienen es necesario procesar los documentos originales a un formato lo suficientemente estandarizado como para que pueda alimentar algún tipo de modelo

  • Hoy veremos un repaso de algunas de las técnicas más usuales para normalizar la información de documentos.

Análisis de texto en Ciencias Sociales

  • La producción textual en Redes Sociales, medios de comunicación, Revistas, la producción literaria, los subtítulos de películas, etc. son expresiones de época.

  • El análisis de estos corpus permite extraer las expresiones culturales y opiniones de un determinado grupo, en algún tiempo y lugar.

  • Las técnicas que utilizamos actualmente nos son mejores que un ser humano en reconocer estos patrones, pero al hacerlo de forma automática pueden permitir análisis “simples” pero a gran escala.

Bag of Words

  • Los documentos se pueden caracterizar por las palabras que contienen. Esto esconde el supuesto fuerte de independencia. No estamos considerando el orden

  • Para este tipo de técnicas, una buena representación de la información es una bolsa de palabras. Un formato que indica la cantidad de veces que aparece una palabra en un documento.

  • También se conocen como Matrices Documento-Término o Término-Documento, según la orientación

Ejemplo: opiniones de trip advisor:

library(tidyverse)
library(tm)
doc1='Lugar espectacular e inolvidable'
doc2='Precioso lugar, la comida era espectacular, de 10, precioso!'

texto <- c(doc1,doc2)

myCorpus = VCorpus(VectorSource(texto))
myDTM = DocumentTermMatrix(myCorpus, control = list(minWordLength = 1))
m = as.matrix(myDTM)
m
    Terms
Docs 10, comida era espectacular espectacular, inolvidable lugar lugar, precioso precioso!
   1   0      0   0            1             0           1     1      0        0         0
   2   1      1   1            0             1           0     0      1        1         1

  • Nosotros sabemos que el significado de “lugar” y “lugar,” es el mismo

  • Al no estar normalizada la información, la BoW genera matrices muy grandes y esparsas, que son poco útiles para trabajar

Normalización

Para construir el Bag of Words se debe considerar los siguientes procesos:

  • Tokenization: Es el proceso de partir un string de texto en palabras y signos de puntuación.

  • Eliminar puntuación.

  • Stop Words: remover las palabras más comunes del idioma (“el”, “la”, “los”, “de”) ya que aparecen en todos los documentos y no aportan información valiosa para distinguirlos.

  • Lemmatization: Es la representación de todas las formas flexionadas (plural, femenino, conjugado, etc.). Para esto, es necesario contar con una base de datos léxica. Para esto podemos usar koRpus que incluye el lexicón TreeTagger.

  • Stemming: Es similar a la lematización, pero no se basa en las estructuras lexicales, sino que realiza una aproximación, quedándose con las primeras letras de la palabra.

  • N-gramas: A veces los conceptos que permiten distinguir entre documentos se componen de más de una palabra, por ejemplo:
    • “a duras penas” (trigrama),
    • “Buenos Aires” (bigrama)
    • Las expresiones idiomáticas o los nombres propios cambian radicalmente de sentido si se separan sus componentes.
    • Imaginense si quisiéramos clasificar la posición política de izquierda a derecha de los “Nacional Socialistas”!

Ejemplo: Limpiando el texto:


doc1='Lugar espectacular e inolvidable'
doc2='Precioso lugar, la comida era espectacular, de 10, precioso!'

texto <- c(doc1,doc2)

myCorpus = VCorpus(VectorSource(texto))
myCorpus = tm_map(myCorpus, content_transformer(tolower))
myCorpus = tm_map(myCorpus, removePunctuation)
myCorpus = tm_map(myCorpus, removeNumbers)
myCorpus = tm_map(myCorpus, removeWords, stopwords(kind = "es"))
myDTM = DocumentTermMatrix(myCorpus, control = list(minWordLength = 1))
m = as.matrix(myDTM)
m
    Terms
Docs comida espectacular inolvidable lugar precioso
   1      0            1           1     1        0
   2      1            1           0     1        2

Expresiones regulares.

Un elemento fundamental para la manipulación del texto son las expresiones regulares. Éstas sirven para captar patrones que aparecen en el texto y luego operar sobre ellos (extraerlos, reemplazarlos, detectarlos, etc.)

por ejemplo

un_texto <- 'una concatenación de caracteres'

str_detect(un_texto, 'una')
[1] TRUE

Para generar una expresión regular, utilizamos distintos elementos:

Caracteres especiales.

Son formas de referirnos a tipos de caracteres

por ejemplo

str_detect(un_texto, '[[:punct:]]')
[1] FALSE
str_detect(un_texto, '[[:alnum:]]')
[1] TRUE

Los cuantificadores nos permiten decír “Este caracter, X veces”

por ejemplo

nchar(un_texto)
[1] 31
str_detect(un_texto, '(\\w|\\W){29,33}')
[1] TRUE
str_detect(un_texto, '(\\w|\\W){32,35}')
[1] FALSE

Esto se lee como “caracteres de palabras (a,b,c…z ; A,B,C… Z ; 0,1,2..9) ó otros caracteres distintos, entre 29 y 33 veces”.

A veces para extraer pedazos de texto nos conviene chequear que hay antes y después. Eso lo hacemos con los lookarounds

Por ejemplo, si queremos recuperar el DNI de la persona entre muchas otras palabras:

texto_con_dni <- 'Lorem ipsum dolor sit amet, DNI 38765239,faucibus et dui tellus, eros mi elit...'

str_extract(texto_con_dni, pattern = '(?<=DNI )\\d{3,}')
[1] "38765239"

el patrón se lee “Luego de DNI tres o más dígitos”

Distancia de palabras

La distancia de palabras se puede entender desde distintos lugares:

  • Distancia de caracteres: Refiere a la similitud de escritura “Mueve” vs “Nueve”
  • Distancia conceptual: Refiere a la similitud del concepto: “Perro” vs “Labrador”

Distancia de caracteres

  • Distancia de Levenshtein o distancia de edición es el número mínimo de operaciones requeridas para transformar una cadena de caracteres en otra. Una operación puede ser una inserción, eliminación o sustitución de un carácter.

  • Jaro Winkler: Esta medida de similitud da mejores puntajes a los strings que son similares en el principio de la oración. \(0 < sim_{jw}<1\), donde 1 significa que las palabras son idénticas (excepto que p=0.25 y compartan los primeros 4 caracteres). y 0 significa que no se parecen en nada

Distancia Conceptual

  • Word Embeddings: Son una representación vectorial de las palabras que se construye a partir de observar una gran cantidad de documentos.

  • Word2Vec fue la primera implementación de esta idea. Se entrena una red neuronal para predecir el contexto de una palabra, y luego se utiliza una matriz que se construye dentro de la red como representación de las palabras.

ejemplo: Proyección en tres dimensiones

Distancia de Documentos

Similitud Coseno

  • En el modelo de BoW representamos a todos los documentos como vectores n-dimensionales que toman valores en el espacio de los números enteros.

  • La dimensión n del espacio está determinada por lo largo del vocabulario utilizado en el corpus.

  • Para comparar la similitud entre dos documentos, podemos utilizar la similitud coseno entre sus representaciones vectoriales. Intuitivamente, la similitud coseno es una medida de correlación de vectores que representan atributos en lugar de variables que se mueven en un espacio continuo.

recordemos primero algunas definiciones.

El producto interno entre dos vectores x,y se define como:

\[ \langle x,y \rangle=\sum_i x_i y_i = \|x\|\ \|y\|\cos(\theta) \]

Por su parte, la norma 2 de un vector x se define como:

\[ ||x||=\sqrt{\sum_i x^2_i} \]

\[ CosSim(x,y) = \frac{\langle x,y \rangle}{\|x\|\ \|y\|} = \frac{\sum_i x_iy_i}{\sum_i x^2_i\sum_i y^2_i} \]

  • Cuantas más palabras compartan los documentos, mayor es el producto punto. Este a su vez se normaliza por el tamaño de cada documento.

  • Este valor va de 1 para los documentos son identicos a 0 cuando son totalmente distintos.

Topic Modelling

  • Las técincas de Modelado de Tópicos tratan de captar los temas de los que habla un corpus de texto.

  • Una de las técnicas más difundidas en la actualidad es Latent Dirichlet Allocation Models
  • Éste es un modelo inferencial bayesiano. No vamos a poder estudiar el detalle del modelo en este curso, pero a grandes rasgos propone un proceso generativo donde cada palabra es el resultado de un encadenamiento de distribuciones, y luego se realiza inferencia hacia atrás para calcular la distribución más probable dada las palabras y los documentos

El resultado del modelo es:

  • Una distribución de palabras por tópico: Podemos caracterizar cada tópico por sus palabras más importantes.
  • Una distribución de los tópicos por documento: Podemos caracterizar un documento por sus temas más importantes

LS0tCnRpdGxlOiBNaW5lcsOtYSBkZSBUZXh0b3MKc3VidGl0bGU6IEV4cGxpY2FjacOzbgpkYXRlOiAiIgpvdXRwdXQ6CiAgaHRtbF9ub3RlYm9vazogCiAgICB0b2M6IHRydWUKICAgIHRvY19mbG9hdDogdHJ1ZSAKLS0tCgoKIVtdKGltZy93b3JkY2xvdWQucG5nKQoKIyMgSW50cm9kdWNjacOzbgoKLSBKdW50byBjb24gbGFzIGltw6FnZW5lcyB5IGxvcyBhdWRpb3MsIGxvcyB0ZXh0b3Mgc29uIHVuYSBmdWVudGUgZGUgZGF0b3Mgbm8gZXN0cnVjdHVyYWRvcyBxdWUgc2UgbXVsdGlwbGljw7MgZW4gbG9zIMO6bHRpbW9zIGHDsW9zLgoKLSBQYXJhIHBvZGVyIGhhY2VyIHVzbyBkZSBsYSBpbmZvcm1hY2nDs24gcXVlIGNvbnRpZW5lbiBlcyBuZWNlc2FyaW8gcHJvY2VzYXIgbG9zIGRvY3VtZW50b3Mgb3JpZ2luYWxlcyBhIHVuIGZvcm1hdG8gbG8gc3VmaWNpZW50ZW1lbnRlIGVzdGFuZGFyaXphZG8gY29tbyBwYXJhIHF1ZSBwdWVkYSBhbGltZW50YXIgYWxnw7puIHRpcG8gZGUgbW9kZWxvCgotIEhveSB2ZXJlbW9zIHVuIHJlcGFzbyBkZSBhbGd1bmFzIGRlIGxhcyB0w6ljbmljYXMgbcOhcyB1c3VhbGVzIHBhcmEgbm9ybWFsaXphciBsYSBpbmZvcm1hY2nDs24gZGUgZG9jdW1lbnRvcy4KCgojIyBBbsOhbGlzaXMgZGUgdGV4dG8gZW4gQ2llbmNpYXMgU29jaWFsZXMKCi0gTGEgcHJvZHVjY2nDs24gdGV4dHVhbCBlbiBSZWRlcyBTb2NpYWxlcywgbWVkaW9zIGRlIGNvbXVuaWNhY2nDs24sIFJldmlzdGFzLCBsYSBwcm9kdWNjacOzbiBsaXRlcmFyaWEsIGxvcyBzdWJ0w610dWxvcyBkZSBwZWzDrWN1bGFzLCBldGMuIHNvbiBleHByZXNpb25lcyBkZSDDqXBvY2EuIAoKLSBFbCBhbsOhbGlzaXMgZGUgZXN0b3MgY29ycHVzIHBlcm1pdGUgZXh0cmFlciBsYXMgZXhwcmVzaW9uZXMgY3VsdHVyYWxlcyB5IG9waW5pb25lcyBkZSB1biBkZXRlcm1pbmFkbyBncnVwbywgZW4gYWxnw7puIHRpZW1wbyB5IGx1Z2FyLgoKLSBMYXMgdMOpY25pY2FzIHF1ZSB1dGlsaXphbW9zIGFjdHVhbG1lbnRlIG5vcyBzb24gbWVqb3JlcyBxdWUgdW4gc2VyIGh1bWFubyBlbiByZWNvbm9jZXIgZXN0b3MgcGF0cm9uZXMsIHBlcm8gYWwgaGFjZXJsbyBkZSBmb3JtYSBhdXRvbcOhdGljYSBwdWVkZW4gcGVybWl0aXIgYW7DoWxpc2lzIOKAnHNpbXBsZXPigJ0gcGVybyBhIGdyYW4gZXNjYWxhLgoKCiMjIEFsZ3Vub3MgZWplbXBsb3MKCiFbaHR0cHM6Ly93d3cucG5hcy5vcmcvY29udGVudC9wbmFzLzExNS8xNi9FMzYzNS5mdWxsLnBkZl0oaW1nL2VtYmVkZGluZ3Nfc3RlcmVvdHlwZXMucG5nKQoKIVtodHRwczovL2RsLmFjbS5vcmcvY2l0YXRpb24uY2ZtP2lkPTIzOTA0OTBdKGltZy9lbGVjdGlvbnMucG5nKQoKIVtodHRwczovL2xpbmsuc3ByaW5nZXIuY29tL2FydGljbGUvMTAuMTAwNy9zMTExOTktMDE5LTAxMDE5LXhdKGltZy9maWxtcy5wbmcpCgoKIyMgQmFnIG9mIFdvcmRzCgotIExvcyBkb2N1bWVudG9zIHNlIHB1ZWRlbiBjYXJhY3Rlcml6YXIgcG9yIGxhcyBwYWxhYnJhcyBxdWUgY29udGllbmVuLiBFc3RvIGVzY29uZGUgZWwgc3VwdWVzdG8gZnVlcnRlIGRlIGluZGVwZW5kZW5jaWEuIE5vIGVzdGFtb3MgY29uc2lkZXJhbmRvIGVsIG9yZGVuCgotIFBhcmEgZXN0ZSB0aXBvIGRlIHTDqWNuaWNhcywgdW5hIGJ1ZW5hIHJlcHJlc2VudGFjacOzbiBkZSBsYSBpbmZvcm1hY2nDs24gZXMgdW5hIGJvbHNhIGRlIHBhbGFicmFzLiBVbiBmb3JtYXRvIHF1ZSBpbmRpY2EgbGEgY2FudGlkYWQgZGUgdmVjZXMgcXVlIGFwYXJlY2UgdW5hIHBhbGFicmEgZW4gdW4gZG9jdW1lbnRvLgoKLSBUYW1iacOpbiBzZSBjb25vY2VuIGNvbW8gTWF0cmljZXMgRG9jdW1lbnRvLVTDqXJtaW5vIG8gVMOpcm1pbm8tRG9jdW1lbnRvLCBzZWfDum4gbGEgb3JpZW50YWNpw7NuCgpfX0VqZW1wbG9fXzogb3BpbmlvbmVzIGRlIHRyaXAgYWR2aXNvcjoKCgpgYGB7ciBtZXNzYWdlPUZBTFNFLCB3YXJuaW5nPUZBTFNFfQpsaWJyYXJ5KHRpZHl2ZXJzZSkKbGlicmFyeSh0bSkKZG9jMT0nTHVnYXIgZXNwZWN0YWN1bGFyIGUgaW5vbHZpZGFibGUnCmRvYzI9J1ByZWNpb3NvIGx1Z2FyLCBsYSBjb21pZGEgZXJhIGVzcGVjdGFjdWxhciwgZGUgMTAsIHByZWNpb3NvIScKCnRleHRvIDwtIGMoZG9jMSxkb2MyKQoKbXlDb3JwdXMgPSBWQ29ycHVzKFZlY3RvclNvdXJjZSh0ZXh0bykpCm15RFRNID0gRG9jdW1lbnRUZXJtTWF0cml4KG15Q29ycHVzLCBjb250cm9sID0gbGlzdChtaW5Xb3JkTGVuZ3RoID0gMSkpCm0gPSBhcy5tYXRyaXgobXlEVE0pCm0KYGBgCgoKIVtdKGltZy9EVE1fYmFkLnBuZykKCi0gTm9zb3Ryb3Mgc2FiZW1vcyBxdWUgZWwgc2lnbmlmaWNhZG8gZGUg4oCcbHVnYXLigJ0geSDigJxsdWdhcizigJ0gZXMgZWwgbWlzbW8KCi0gQWwgbm8gZXN0YXIgbm9ybWFsaXphZGEgbGEgaW5mb3JtYWNpw7NuLCBsYSBCb1cgZ2VuZXJhIG1hdHJpY2VzIG11eSBncmFuZGVzIHkgZXNwYXJzYXMsIHF1ZSBzb24gcG9jbyDDunRpbGVzIHBhcmEgdHJhYmFqYXIKCgojIyBOb3JtYWxpemFjacOzbgoKClBhcmEgY29uc3RydWlyIGVsIEJhZyBvZiBXb3JkcyBzZSBkZWJlIGNvbnNpZGVyYXIgbG9zIHNpZ3VpZW50ZXMgcHJvY2Vzb3M6CgotIF9fVG9rZW5pemF0aW9uX186IEVzIGVsIHByb2Nlc28gZGUgcGFydGlyIHVuIHN0cmluZyBkZSB0ZXh0byBlbiBwYWxhYnJhcyB5IHNpZ25vcyBkZSBwdW50dWFjacOzbi4KIAotIF9fRWxpbWluYXIgcHVudHVhY2nDs25fXy4KCi0gX19TdG9wIFdvcmRzX186IHJlbW92ZXIgbGFzIHBhbGFicmFzIG3DoXMgY29tdW5lcyBkZWwgaWRpb21hICjigJxlbOKAnSwg4oCcbGHigJ0sIOKAnGxvc+KAnSwg4oCcZGXigJ0pIHlhIHF1ZSBhcGFyZWNlbiBlbiB0b2RvcyBsb3MgZG9jdW1lbnRvcyB5IG5vIGFwb3J0YW4gaW5mb3JtYWNpw7NuIHZhbGlvc2EgcGFyYSBkaXN0aW5ndWlybG9zLgoKLSBfX0xlbW1hdGl6YXRpb25fXzogRXMgbGEgcmVwcmVzZW50YWNpw7NuIGRlIHRvZGFzIGxhcyBmb3JtYXMgZmxleGlvbmFkYXMgKHBsdXJhbCwgZmVtZW5pbm8sIGNvbmp1Z2FkbywgZXRjLikuIFBhcmEgZXN0bywgZXMgbmVjZXNhcmlvIGNvbnRhciBjb24gdW5hIGJhc2UgZGUgZGF0b3MgbMOpeGljYS4gUGFyYSBlc3RvIHBvZGVtb3MgdXNhciBba29ScHVzXShodHRwczovL2dpdGh1Yi5jb20vdW5Eb2NVTWVhbnRJdC9rb1JwdXMpIHF1ZSBpbmNsdXllIGVsIGxleGljw7NuIFRyZWVUYWdnZXIuCgotIF9fU3RlbW1pbmdfXzogRXMgc2ltaWxhciBhIGxhIGxlbWF0aXphY2nDs24sIHBlcm8gbm8gc2UgYmFzYSBlbiBsYXMgZXN0cnVjdHVyYXMgbGV4aWNhbGVzLCBzaW5vIHF1ZSByZWFsaXphIHVuYSBhcHJveGltYWNpw7NuLCBxdWVkw6FuZG9zZSBjb24gbGFzIHByaW1lcmFzIGxldHJhcyBkZSBsYSBwYWxhYnJhLiAKCi0gX19OLWdyYW1hc19fOiBBIHZlY2VzIGxvcyBjb25jZXB0b3MgcXVlIHBlcm1pdGVuIGRpc3Rpbmd1aXIgZW50cmUgZG9jdW1lbnRvcyBzZSBjb21wb25lbiBkZSBtw6FzIGRlIHVuYSBwYWxhYnJhLCBwb3IgZWplbXBsbzoKICAtIOKAnGEgZHVyYXMgcGVuYXPigJ0gKHRyaWdyYW1hKSwKICAtIOKAnEJ1ZW5vcyBBaXJlc+KAnSAoYmlncmFtYSkgCiAgLSBMYXMgZXhwcmVzaW9uZXMgaWRpb23DoXRpY2FzIG8gbG9zIG5vbWJyZXMgcHJvcGlvcyBjYW1iaWFuIHJhZGljYWxtZW50ZSBkZSBzZW50aWRvIHNpIHNlIHNlcGFyYW4gc3VzIGNvbXBvbmVudGVzLiAKICAtIEltYWdpbmVuc2Ugc2kgcXVpc2nDqXJhbW9zIGNsYXNpZmljYXIgbGEgcG9zaWNpw7NuIHBvbMOtdGljYSBkZSBpenF1aWVyZGEgYSBkZXJlY2hhIGRlIGxvcyDigJxOYWNpb25hbCBTb2NpYWxpc3Rhc+KAnSEKCl9fRWplbXBsb19fOiBMaW1waWFuZG8gZWwgdGV4dG86CgoKYGBge3J9Cgpkb2MxPSdMdWdhciBlc3BlY3RhY3VsYXIgZSBpbm9sdmlkYWJsZScKZG9jMj0nUHJlY2lvc28gbHVnYXIsIGxhIGNvbWlkYSBlcmEgZXNwZWN0YWN1bGFyLCBkZSAxMCwgcHJlY2lvc28hJwoKdGV4dG8gPC0gYyhkb2MxLGRvYzIpCgpteUNvcnB1cyA9IFZDb3JwdXMoVmVjdG9yU291cmNlKHRleHRvKSkKbXlDb3JwdXMgPSB0bV9tYXAobXlDb3JwdXMsIGNvbnRlbnRfdHJhbnNmb3JtZXIodG9sb3dlcikpCm15Q29ycHVzID0gdG1fbWFwKG15Q29ycHVzLCByZW1vdmVQdW5jdHVhdGlvbikKbXlDb3JwdXMgPSB0bV9tYXAobXlDb3JwdXMsIHJlbW92ZU51bWJlcnMpCm15Q29ycHVzID0gdG1fbWFwKG15Q29ycHVzLCByZW1vdmVXb3Jkcywgc3RvcHdvcmRzKGtpbmQgPSAiZXMiKSkKbXlEVE0gPSBEb2N1bWVudFRlcm1NYXRyaXgobXlDb3JwdXMsIGNvbnRyb2wgPSBsaXN0KG1pbldvcmRMZW5ndGggPSAxKSkKbSA9IGFzLm1hdHJpeChteURUTSkKbQpgYGAKCiMjIFtFeHByZXNpb25lcyByZWd1bGFyZXMuXShodHRwczovL3N0cmluZ3IudGlkeXZlcnNlLm9yZy9hcnRpY2xlcy9yZWd1bGFyLWV4cHJlc3Npb25zLmh0bWwpCgpVbiBlbGVtZW50byBmdW5kYW1lbnRhbCBwYXJhIGxhIG1hbmlwdWxhY2nDs24gZGVsIHRleHRvIHNvbiBsYXMgX2V4cHJlc2lvbmVzIHJlZ3VsYXJlc18uIMOJc3RhcyBzaXJ2ZW4gcGFyYSBjYXB0YXIgX3BhdHJvbmVzXyBxdWUgYXBhcmVjZW4gZW4gZWwgdGV4dG8geSBsdWVnbyBvcGVyYXIgc29icmUgZWxsb3MgKGV4dHJhZXJsb3MsIHJlZW1wbGF6YXJsb3MsIGRldGVjdGFybG9zLCBldGMuKQoKIVtdKGltZy9yZWdleC5wbmcpe3dpZHRoPTEwMDB9CgoKcG9yIGVqZW1wbG8KCmBgYHtyfQp1bl90ZXh0byA8LSAndW5hIGNvbmNhdGVuYWNpw7NuIGRlIGNhcmFjdGVyZXMnCgpzdHJfZGV0ZWN0KHVuX3RleHRvLCAndW5hJykKYGBgCgoKUGFyYSBnZW5lcmFyIHVuYSBleHByZXNpw7NuIHJlZ3VsYXIsIHV0aWxpemFtb3MgZGlzdGludG9zIGVsZW1lbnRvczoKCiMjIyBDYXJhY3RlcmVzIGVzcGVjaWFsZXMuCgoKU29uIGZvcm1hcyBkZSByZWZlcmlybm9zIGEgdGlwb3MgZGUgY2FyYWN0ZXJlcwoKIVtdKGltZy9jaGFyYWN0ZXJzLnBuZyl7d2lkdGg9MzAwfQoKcG9yIGVqZW1wbG8KCmBgYHtyfQpzdHJfZGV0ZWN0KHVuX3RleHRvLCAnW1s6cHVuY3Q6XV0nKQpzdHJfZGV0ZWN0KHVuX3RleHRvLCAnW1s6YWxudW06XV0nKQoKYGBgCgohW10oaW1nL2N1YW50aWZpY2Fkb3Jlcy5wbmcpe3dpZHRoPTMwMH0KCkxvcyBjdWFudGlmaWNhZG9yZXMgbm9zIHBlcm1pdGVuIGRlY8OtciAiRXN0ZSBjYXJhY3RlciwgWCB2ZWNlcyIKCnBvciBlamVtcGxvCmBgYHtyfQpuY2hhcih1bl90ZXh0bykKc3RyX2RldGVjdCh1bl90ZXh0bywgJyhcXHd8XFxXKXsyOSwzM30nKQpzdHJfZGV0ZWN0KHVuX3RleHRvLCAnKFxcd3xcXFcpezMyLDM1fScpCgpgYGAKRXN0byBzZSBsZWUgY29tbyAiY2FyYWN0ZXJlcyBkZSBwYWxhYnJhcyAoYSxiLGMuLi56IDsgQSxCLEMuLi4gWiA7IDAsMSwyLi45KSBfX8OzX18gb3Ryb3MgY2FyYWN0ZXJlcyBkaXN0aW50b3MsIGVudHJlIDI5IHkgMzMgdmVjZXMiLiAKCgpBIHZlY2VzIHBhcmEgZXh0cmFlciBwZWRhem9zIGRlIHRleHRvIG5vcyBjb252aWVuZSBjaGVxdWVhciBxdWUgaGF5IGFudGVzIHkgZGVzcHXDqXMuIEVzbyBsbyBoYWNlbW9zIGNvbiBsb3MgX2xvb2thcm91bmRzXwoKIVtdKGltZy9sb29rYWhlYWRzLnBuZyl7d2lkdGg9MzAwfQoKUG9yIGVqZW1wbG8sIHNpIHF1ZXJlbW9zIHJlY3VwZXJhciBlbCBETkkgZGUgbGEgcGVyc29uYSBlbnRyZSBtdWNoYXMgb3RyYXMgcGFsYWJyYXM6CgpgYGB7cn0KdGV4dG9fY29uX2RuaSA8LSAnTG9yZW0gaXBzdW0gZG9sb3Igc2l0IGFtZXQsIEROSSAzODc2NTIzOSxmYXVjaWJ1cyBldCBkdWkgdGVsbHVzLCBlcm9zIG1pIGVsaXQuLi4nCgpzdHJfZXh0cmFjdCh0ZXh0b19jb25fZG5pLCBwYXR0ZXJuID0gJyg/PD1ETkkgKVxcZHszLH0nKQpgYGAKZWwgcGF0csOzbiBzZSBsZWUgIkx1ZWdvIGRlIF9ETkkgXyB0cmVzIG8gbcOhcyBkw61naXRvcyIKCiMjIERpc3RhbmNpYSBkZSBwYWxhYnJhcwoKTGEgZGlzdGFuY2lhIGRlIHBhbGFicmFzIHNlIHB1ZWRlIGVudGVuZGVyIGRlc2RlIGRpc3RpbnRvcyBsdWdhcmVzOgoKCi0gX19EaXN0YW5jaWEgZGUgY2FyYWN0ZXJlc19fOiBSZWZpZXJlIGEgbGEgc2ltaWxpdHVkIGRlIGVzY3JpdHVyYSDigJxNdWV2ZeKAnSB2cyDigJxOdWV2ZeKAnQotIF9fRGlzdGFuY2lhIGNvbmNlcHR1YWxfXzogIFJlZmllcmUgYSBsYSBzaW1pbGl0dWQgZGVsIGNvbmNlcHRvOiDigJxQZXJyb+KAnSB2cyDigJxMYWJyYWRvcuKAnQoKCgojIyBEaXN0YW5jaWEgZGUgY2FyYWN0ZXJlcwoKLSBfX0Rpc3RhbmNpYSBkZSBMZXZlbnNodGVpbl9fIG8gZGlzdGFuY2lhIGRlIGVkaWNpw7NuIGVzIGVsIG7Dum1lcm8gbcOtbmltbyBkZSBvcGVyYWNpb25lcyByZXF1ZXJpZGFzIHBhcmEgdHJhbnNmb3JtYXIgdW5hIGNhZGVuYSBkZSBjYXJhY3RlcmVzIGVuIG90cmEuIFVuYSBvcGVyYWNpw7NuIHB1ZWRlIHNlciB1bmEgaW5zZXJjacOzbiwgZWxpbWluYWNpw7NuIG8gc3VzdGl0dWNpw7NuIGRlIHVuIGNhcsOhY3Rlci4KCi0gX19KYXJvIFdpbmtsZXJfXzogRXN0YSBtZWRpZGEgZGUgc2ltaWxpdHVkIGRhIG1lam9yZXMgcHVudGFqZXMgYSBsb3Mgc3RyaW5ncyBxdWUgc29uIHNpbWlsYXJlcyBlbiBlbCBwcmluY2lwaW8gZGUgbGEgb3JhY2nDs24uICQwIDwgc2ltX3tqd308MSQsIGRvbmRlIDEgc2lnbmlmaWNhIHF1ZSBsYXMgcGFsYWJyYXMgc29uIGlkw6ludGljYXMgKGV4Y2VwdG8gcXVlIHA9MC4yNSB5IGNvbXBhcnRhbiBsb3MgcHJpbWVyb3MgNCBjYXJhY3RlcmVzKS4geSAwIHNpZ25pZmljYSBxdWUgbm8gc2UgcGFyZWNlbiBlbiBuYWRhCgoKIyMgRGlzdGFuY2lhIENvbmNlcHR1YWwKCgotICBfX1dvcmQgRW1iZWRkaW5nc19fOiBTb24gdW5hIHJlcHJlc2VudGFjacOzbiB2ZWN0b3JpYWwgZGUgbGFzIHBhbGFicmFzIHF1ZSBzZSBjb25zdHJ1eWUgYSBwYXJ0aXIgZGUgb2JzZXJ2YXIgdW5hIGdyYW4gY2FudGlkYWQgZGUgZG9jdW1lbnRvcy4KCi0gX19Xb3JkMlZlY19fIGZ1ZSBsYSBwcmltZXJhIGltcGxlbWVudGFjacOzbiBkZSBlc3RhIGlkZWEuIFNlIGVudHJlbmEgdW5hIHJlZCBuZXVyb25hbCBwYXJhIHByZWRlY2lyIGVsIGNvbnRleHRvIGRlIHVuYSBwYWxhYnJhLCB5IGx1ZWdvIHNlIHV0aWxpemEgdW5hIG1hdHJpeiBxdWUgc2UgY29uc3RydXllIGRlbnRybyBkZSBsYSByZWQgY29tbyByZXByZXNlbnRhY2nDs24gZGUgbGFzIHBhbGFicmFzLgoKIVtdKGltZy9za2lwZ3JhbS5wbmcpCgoKX19lamVtcGxvX186IFtQcm95ZWNjacOzbiBlbiB0cmVzIGRpbWVuc2lvbmVzXShodHRwczovL3Byb2plY3Rvci50ZW5zb3JmbG93Lm9yZy8pCgojIyBEaXN0YW5jaWEgZGUgRG9jdW1lbnRvcwoKIyMjIFNpbWlsaXR1ZCBDb3Nlbm8KCgotIEVuIGVsIG1vZGVsbyBkZSBfX0JvV19fIHJlcHJlc2VudGFtb3MgYSB0b2RvcyBsb3MgZG9jdW1lbnRvcyBjb21vIHZlY3RvcmVzIG4tZGltZW5zaW9uYWxlcyBxdWUgdG9tYW4gdmFsb3JlcyBlbiBlbCBlc3BhY2lvIGRlIGxvcyBuw7ptZXJvcyBlbnRlcm9zLiAKCi0gTGEgZGltZW5zacOzbiBuIGRlbCBlc3BhY2lvIGVzdMOhIGRldGVybWluYWRhIHBvciBsbyBsYXJnbyBkZWwgdm9jYWJ1bGFyaW8gdXRpbGl6YWRvIGVuIGVsIGNvcnB1cy4gCgotIFBhcmEgY29tcGFyYXIgbGEgc2ltaWxpdHVkIGVudHJlIGRvcyBkb2N1bWVudG9zLCBwb2RlbW9zIHV0aWxpemFyIGxhIF9fc2ltaWxpdHVkIGNvc2Vub19fIGVudHJlIHN1cyByZXByZXNlbnRhY2lvbmVzIHZlY3RvcmlhbGVzLiAgSW50dWl0aXZhbWVudGUsIGxhIHNpbWlsaXR1ZCBjb3Nlbm8gZXMgdW5hIG1lZGlkYSBkZSBjb3JyZWxhY2nDs24gZGUgdmVjdG9yZXMgcXVlIHJlcHJlc2VudGFuIGF0cmlidXRvcyBlbiBsdWdhciBkZSB2YXJpYWJsZXMgcXVlIHNlIG11ZXZlbiBlbiB1biBlc3BhY2lvIGNvbnRpbnVvLiAKCgoKcmVjb3JkZW1vcyBwcmltZXJvIGFsZ3VuYXMgZGVmaW5pY2lvbmVzLiAKCgpFbCBwcm9kdWN0byBpbnRlcm5vIGVudHJlIGRvcyB2ZWN0b3JlcyB4LHkgc2UgZGVmaW5lIGNvbW86CgoKJCQKXGxhbmdsZSB4LHkgXHJhbmdsZT1cc3VtX2kgeF9pIHlfaSA9IFx8eFx8XCBcfHlcfFxjb3MoXHRoZXRhKQokJAoKClBvciBzdSBwYXJ0ZSwgbGEgbm9ybWEgMiBkZSB1biB2ZWN0b3IgeCBzZSBkZWZpbmUgY29tbzoKCiQkCnx8eHx8PVxzcXJ0e1xzdW1faSB4XjJfaX0KJCQKCiFbXShpbWcvY29zaW5lX3NpbWlsYXJpdHkucG5nKQoKJCQKQ29zU2ltKHgseSkgPSBcZnJhY3tcbGFuZ2xlIHgseSBccmFuZ2xlfXtcfHhcfFwgXHx5XHx9ID0gXGZyYWN7XHN1bV9pIHhfaXlfaX17XHN1bV9pIHheMl9pXHN1bV9pIHleMl9pfSAKJCQKCgotIEN1YW50YXMgbcOhcyBwYWxhYnJhcyBjb21wYXJ0YW4gbG9zIGRvY3VtZW50b3MsIG1heW9yIGVzIGVsIHByb2R1Y3RvIHB1bnRvLiBFc3RlIGEgc3UgdmV6IHNlIG5vcm1hbGl6YSBwb3IgZWwgdGFtYcOxbyBkZSBjYWRhIGRvY3VtZW50by4gCgotIEVzdGUgdmFsb3IgdmEgZGUgMSBwYXJhIGxvcyBkb2N1bWVudG9zIHNvbiBpZGVudGljb3MgYSAwIGN1YW5kbyBzb24gdG90YWxtZW50ZSBkaXN0aW50b3MuCgoKIyMgVG9waWMgTW9kZWxsaW5nCgotIExhcyB0w6ljaW5jYXMgZGUgTW9kZWxhZG8gZGUgVMOzcGljb3MgdHJhdGFuIGRlIGNhcHRhciBsb3MgdGVtYXMgZGUgbG9zIHF1ZSBoYWJsYSB1biBjb3JwdXMgZGUgdGV4dG8uCgotIFVuYSBkZSBsYXMgdMOpY25pY2FzIG3DoXMgZGlmdW5kaWRhcyBlbiBsYSBhY3R1YWxpZGFkIGVzIFtfX0xhdGVudCBEaXJpY2hsZXQgQWxsb2NhdGlvbiBNb2RlbHNfX10oaHR0cDovL3d3dy5qbWxyLm9yZy9wYXBlcnMvdm9sdW1lMy9ibGVpMDNhL2JsZWkwM2EucGRmKQotIMOJc3RlIGVzIHVuIG1vZGVsbyBpbmZlcmVuY2lhbCBiYXllc2lhbm8uIE5vIHZhbW9zIGEgcG9kZXIgZXN0dWRpYXIgZWwgZGV0YWxsZSBkZWwgbW9kZWxvIGVuIGVzdGUgY3Vyc28sIHBlcm8gYSBncmFuZGVzIHJhc2dvcyBwcm9wb25lIHVuIF9wcm9jZXNvIGdlbmVyYXRpdm9fIGRvbmRlIGNhZGEgcGFsYWJyYSBlcyBlbCByZXN1bHRhZG8gZGUgdW4gZW5jYWRlbmFtaWVudG8gZGUgZGlzdHJpYnVjaW9uZXMsIHkgbHVlZ28gc2UgcmVhbGl6YSBfaW5mZXJlbmNpYSBoYWNpYSBhdHLDoXNfIHBhcmEgY2FsY3VsYXIgX2xhIGRpc3RyaWJ1Y2nDs24gbcOhcyBwcm9iYWJsZSBkYWRhIGxhcyBwYWxhYnJhcyB5IGxvcyBkb2N1bWVudG9zXwoKIVtdKGltZy9ncmFmb19sZGEucG5nKXt3aWR0aD0xMDAwfQoKRWwgcmVzdWx0YWRvIGRlbCBtb2RlbG8gZXM6CgotIF9VbmEgZGlzdHJpYnVjacOzbiBkZSBwYWxhYnJhcyBwb3IgdMOzcGljb186IFBvZGVtb3MgY2FyYWN0ZXJpemFyIGNhZGEgdMOzcGljbyBwb3Igc3VzIHBhbGFicmFzIG3DoXMgaW1wb3J0YW50ZXMuCi0gX1VuYSBkaXN0cmlidWNpw7NuIGRlIGxvcyB0w7NwaWNvcyBwb3IgZG9jdW1lbnRvXzogUG9kZW1vcyBjYXJhY3Rlcml6YXIgdW4gZG9jdW1lbnRvIHBvciBzdXMgdGVtYXMgbcOhcyBpbXBvcnRhbnRlcwoKCgohW10oaW1nL0xEQS5wbmcpe3dpZHRoPTEwMDB9Cgo=