
El análisis y procesamiento de textos en un lenguaje natural es una tarea constantemente actual que se ha resuelto, se está resolviendo y se resolverá de todas las formas disponibles. Hoy me gustaría hablar sobre herramientas de solución para resolver este problema, es decir, en el lenguaje Julia. Por supuesto, debido a la juventud del lenguaje, no existen herramientas de análisis desarrolladas, como, por ejemplo, Stanford CoreNLP, Apache OpenNLP, GATE, etc., como, por ejemplo, el lenguaje Java. Sin embargo, incluso las bibliotecas que ya se han desarrollado se pueden usar para resolver problemas típicos y se pueden recomendar como un punto de entrada para los estudiantes interesados en el campo del procesamiento de textos. Y la simplicidad sintáctica de Julia y sus herramientas matemáticas avanzadas hacen que sea fácil sumergirse en las tareas de agrupación y clasificación de textos.
El propósito de este artículo es revisar las herramientas de procesamiento de texto de Julia con algunas explicaciones sobre su uso. Balancearemos entre una breve lista de oportunidades para aquellos que están en el tema de PNL, pero quisieran ver exactamente las herramientas de Julia, y explicaciones más detalladas y ejemplos de aplicación para aquellos que decidieron sumergirse en el área de PNL (Procesamiento del lenguaje natural) como tal por primera vez.
Bueno, ahora, pasemos a la descripción general del paquete.
TextAnalysis.jl
El paquete TextAnalysis.jl es una biblioteca básica que implementa un conjunto mínimo de funciones típicas de procesamiento de texto. Es con ella que comenzamos. Los ejemplos se toman parcialmente de la documentación .
El documento
La entidad básica es un documento.
Se admiten los siguientes tipos:
- FileDocument: un documento representado por un simple archivo de texto en el disco
julia> pathname = "/usr/share/dict/words" "/usr/share/dict/words" julia> fd = FileDocument(pathname) A FileDocument * Language: Languages.English() * Title: /usr/share/dict/words * Author: Unknown Author * Timestamp: Unknown Time * Snippet: AA's AMD AMD's AOL AOL's Aachen Aachen's Aaliyah
- StringDocument: un documento representado por una cadena UTF-8 y almacenado en la RAM. La estructura StringDocument permite almacenar texto en su conjunto.
julia> str = "To be or not to be..." "To be or not to be..." julia> sd = StringDocument(str) A StringDocument{String} * Language: Languages.English() * Title: Untitled Document * Author: Unknown Author * Timestamp: Unknown Time * Snippet: To be or not to be...
- TokenDocument: un documento que es una secuencia de tokens UTF-8 (palabras resaltadas). La estructura
TokenDocument
almacena un conjunto de tokens, sin embargo, el texto completo no se puede restaurar sin pérdida.
julia> my_tokens = String["To", "be", "or", "not", "to", "be..."] 6-element Array{String,1}: "To" "be" "or" "not" "to" "be..." julia> td = TokenDocument(my_tokens) A TokenDocument{String} * Language: Languages.English() * Title: Untitled Document * Author: Unknown Author * Timestamp: Unknown Time * Snippet: ***SAMPLE TEXT NOT AVAILABLE***
- NGramDocument: un documento presentado como un conjunto de n-gramas en una representación UTF8, es decir, una secuencia de
n
UTF-8 y un contador para su aparición. Esta opción de presentar un documento es una de las formas más simples de evitar algunos problemas de la morfología de los idiomas, errores tipográficos y características de las estructuras del lenguaje en los textos analizados. Sin embargo, la tarifa por esto es una disminución en la calidad del análisis de texto en comparación con los métodos donde se tiene en cuenta la información del idioma.
julia> my_ngrams = Dict{String, Int}("To" => 1, "be" => 2, "or" => 1, "not" => 1, "to" => 1, "be..." => 1) Dict{String,Int64} with 6 entries: "or" => 1 "be..." => 1 "not" => 1 "to" => 1 "To" => 1 "be" => 2 julia> ngd = NGramDocument(my_ngrams) A NGramDocument{AbstractString} * Language: Languages.English() * Title: Untitled Document * Author: Unknown Author * Timestamp: Unknown Time * Snippet: ***SAMPLE TEXT NOT AVAILABLE***
O una opción corta:
julia> str = "To be or not to be..." "To be or not to be..." julia> ngd = NGramDocument(str, 2) NGramDocument{AbstractString}(Dict{AbstractString,Int64}("To be" => 1,"or not" => 1,"be or" => 1,"or" => 1,"not to" => 1,"not" => 1,"to be" => 1,"to" => 1,"To" => 1,"be" => 2…), 2, TextAnalysis.DocumentMetadata( Languages.English(), "Untitled Document", "Unknown Author", "Unknown Time"))
También se puede crear un documento simplemente usando el constructor genérico del documento, y la biblioteca encontrará la implementación adecuada del documento.
julia> Document("To be or not to be...") A StringDocument{String} * Language: Languages.English() * Title: Untitled Document * Author: Unknown Author * Timestamp: Unknown Time * Snippet: To be or not to be... julia> Document("/usr/share/dict/words") A FileDocument * Language: Languages.English() * Title: /usr/share/dict/words * Author: Unknown Author * Timestamp: Unknown Time * Snippet: AA's AMD AMD's AOL AOL's Aachen Aachen's Aaliyah julia> Document(String["To", "be", "or", "not", "to", "be..."]) A TokenDocument{String} * Language: Languages.English() * Title: Untitled Document * Author: Unknown Author * Timestamp: Unknown Time * Snippet: ***SAMPLE TEXT NOT AVAILABLE*** julia> Document(Dict{String, Int}("a" => 1, "b" => 3)) A NGramDocument{AbstractString} * Language: Languages.English() * Title: Untitled Document * Author: Unknown Author * Timestamp: Unknown Time * Snippet: ***SAMPLE TEXT NOT AVAILABLE***
Como puede ver, el cuerpo del documento consta de texto / tokens y metadatos. El texto del documento se puede obtener utilizando el método de text(...)
:
julia> td = TokenDocument("To be or not to be...") TokenDocument{String}(["To", "be", "or", "not", "to", "be"], TextAnalysis.DocumentMetadata( Languages.English(), "Untitled Document", "Unknown Author", "Unknown Time")) julia> text(td) ┌ Warning: TokenDocument's can only approximate the original text └ @ TextAnalysis ~/.julia/packages/TextAnalysis/pcFQf/src/document.jl:111 "To be or not to be" julia> tokens(td) 6-element Array{String,1}: "To" "be" "or" "not" "to" "be"
El ejemplo muestra un documento con tokens analizados automáticamente. Vemos que la llamada al text(td)
emitió una advertencia de que el texto solo se restauró aproximadamente, ya que TokenDocument
no almacena delimitadores de palabras. La llamada de tokens(td)
hizo posible obtener exactamente las palabras resaltadas.
Puede solicitar metadatos de un documento:
julia> StringDocument("This document has too foo words") A StringDocument{String} * Language: Languages.English() * Title: Untitled Document * Author: Unknown Author * Timestamp: Unknown Time * Snippet: This document has too foo words julia> language(sd) Languages.English() julia> title(sd) "Untitled Document" julia> author(sd) "Unknown Author" julia> timestamp(sd) "Unknown Time"
Y todos ellos pueden ser cambiados por las funciones correspondientes. La notación de las funciones de modificación en Julia es la misma que en el lenguaje Ruby. ¡Una función que modifica un objeto tiene un sufijo !
:
julia> using TextAnalysis.Languages julia> language!(sd, Languages.Russian()) Languages.Russian () julia> title!(sd, "") "" julia> author!(sd, " ..") " .." julia> import Dates:now julia> timestamp!(sd, string(now())) "2019-11-09T22:53:38.383"
Características de cuerdas con UTF-8
Julia admite la codificación UTF-8 cuando procesa cadenas, por lo que no tiene problemas con el uso de alfabetos no latinos. Cualquier opción de procesamiento de caracteres está naturalmente disponible. Sin embargo, tenga en cuenta que los índices de fila para Julia son bytes, no caracteres. Y cada carácter puede ser representado por un número diferente de bytes. Y hay métodos separados para trabajar con caracteres UNICODE. Vea Unicode-and-UTF-8 para más detalles. Pero aquí hay un ejemplo simple. Vamos a establecer una línea con caracteres UNICODE matemáticos separados de x e y por espacios:
julia> s = "\u2200 x \u2203 y" "∀ x ∃ y" julia> length(s)
Ahora echemos un vistazo a los índices:
julia> s[1] '∀': Unicode U+2200 (category Sm: Symbol, math) julia> s[2] ERROR: StringIndexError("∀ x ∃ y", 2) [...] julia> s[3] ERROR: StringIndexError("∀ x ∃ y", 3) Stacktrace: [...] julia> s[4] ' ': ASCII/Unicode U+0020 (category Zs: Separator, space)
El ejemplo muestra claramente que el índice 1
nos permitió obtener el símbolo ∀
. Pero todos los índices posteriores hasta 3 inclusive, condujeron a un error. Y solo el cuarto índice produjo un espacio, como el siguiente carácter en la cadena. Sin embargo, para determinar los límites de los caracteres por índices en una cadena, existen funciones útiles prevind
(índice anterior), nextind
(índice siguiente) y thisind
(este índice). Por ejemplo, para el espacio que se encuentra arriba, preguntamos dónde está el borde del anterior:
julia> prevind(s, 4) 1
Tenemos el índice 1 como el comienzo del símbolo ∀
.
julia> thisind(s, 3) 1
Verificó el índice 3 y obtuvo el mismo 1 válido.
Si necesitamos "repasar" todos los personajes, esto se puede hacer al menos de dos maneras simples:
1) usando el diseño:
julia> for c in s print(c) end ∀ x ∃ y
2) usando eachindex
enumerador de eachindex
:
julia> collect(eachindex(s)) 7-element Array{Int64,1}: 1 4 5 6 7 10 11 julia> for i in eachindex(s) print(s[i]) end ∀ x ∃ y
Preprocesamiento de documentos
Si el texto del documento se obtuvo de alguna representación externa, entonces es muy posible que pueda haber errores de codificación en el flujo de bytes. Para eliminarlos, use la función remove_corrupt_utf8!(sd)
. El argumento es el documento discutido anteriormente.
La función principal para procesar documentos en el paquete TextAnalysis es prepare!(...)
. Por ejemplo, elimine los signos de puntuación del texto:
julia> str = StringDocument("here are some punctuations !!!...") julia> prepare!(str, strip_punctuation) julia> text(str) "here are some punctuations "
Además, un paso útil en el procesamiento de textos es la conversión de todas las letras a minúsculas, ya que esto simplifica la comparación adicional de las palabras entre sí. En este caso, en el caso general, debe entenderse que podemos perder información importante sobre el texto, por ejemplo, el hecho de que la palabra es un nombre propio o la palabra es el límite de una oración. Pero todo depende del modelo de procesamiento posterior. Las minúsculas se realizan mediante la función remove_case!()
.
julia> sd = StringDocument("Lear is mad") A StringDocument{String} julia> remove_case!(sd) julia> text(sd) "lear is mad"
En el camino, podemos eliminar palabras basura, es decir, aquellas que no sirven para recuperar información y analizar coincidencias. Esto se puede hacer explícitamente usando la función remove_words!(…)
y una matriz de estas palabras de detención.
julia> remove_words!(sd, ["lear"]) julia> text(sd) " is mad"
Entre las palabras que se eliminarán, también hay artículos, preposiciones, pronombres, números y solo palabras de detención, que son parasitarias en frecuencia. Para cada idioma específico, estos diccionarios son individuales. Y están configurados en el paquete Languages.jl. Los números nos molestan porque en el futuro modelo de un documento térmico, pueden aumentar considerablemente la dimensión de la matriz sin mejorar, por ejemplo, la agrupación de textos. Sin embargo, en problemas de búsqueda, por ejemplo, ya no siempre es posible soltar números.
Entre los métodos de limpieza disponibles se encuentran las siguientes opciones:
prepare!(sd, strip_articles)
prepare!(sd, strip_indefinite_articles)
prepare!(sd, strip_definite_articles)
prepare!(sd, strip_preposition)
prepare!(sd, strip_pronouns)
prepare!(sd, strip_stopwords)
prepare!(sd, strip_numbers)
prepare!(sd, strip_non_letters)
prepare!(sd, strip_spares_terms)
prepare!(sd, strip_frequent_terms)
prepare!(sd, strip_html_tags)
Las opciones se pueden combinar. Por ejemplo, en una llamada para prepare!
eliminar simultáneamente artículos, números y etiquetas html: prepare!(sd, strip_articles| strip_numbers| strip_html_tags)
Otro tipo de procesamiento es resaltar la base de las palabras, eliminar terminaciones y sufijos. Esto le permite combinar diferentes formas de palabras y reducir drásticamente la dimensionalidad del modelo de presentación de documentos. Para ello se requieren diccionarios, por lo que el idioma de los documentos debe estar claramente indicado. Ejemplo de procesamiento en ruso:
julia> sd = StringDocument(" ") StringDocument{String}(" ", TextAnalysis.DocumentMetadata(Languages.English(), "Untitled Document", "Unknown Author", "Unknown Time")) julia> language!(sd, Languages.Russian()) Languages.Russian() julia> stem!(sd) julia> text(sd) " "
Cuerpo del documento
El corpus se entiende como un conjunto de documentos que se procesarán de acuerdo con las mismas reglas. El paquete TextAnalysis implementa la formación de un término matriz de documentos . Y para su construcción, necesitamos tener inmediatamente un conjunto completo de documentos. En un ejemplo simple para documentos:
D1 = "I like databases"
D2 = "I hate databases"
esta matriz se ve así:
Las columnas están representadas por las palabras de los documentos, y las filas son identificadores (o índices) de los documentos. En consecuencia, la celda será 0 si la palabra (término) no aparece en el documento. Y 1 si ocurre varias veces. Los modelos más complejos tienen en cuenta tanto la frecuencia de ocurrencia (modelo TF) como la importancia en relación con todo el cuerpo (TF-IDF).
Podemos construir el cuerpo usando el constructor Corpus()
:
crps = Corpus([StringDocument("Document 1"), StringDocument("Document 2")])
Si solicitamos una lista de términos de inmediato, obtenemos:
julia> lexicon(crps) Dict{String,Int64} with 0 entries
Y, aquí, obligando a la biblioteca a contar todos los términos que forman parte del caso usando update_lexicon!(crps)
, obtenemos un resultado diferente:
julia> update_lexicon!(crps) julia> lexicon(crps) Dict{String,Int64} with 3 entries: "1" => 1 "2" => 1 "Document" => 2
Es decir, podemos ver los términos seleccionados (palabras y números) y su número de entradas en el cuerpo del documento.
Al mismo tiempo, podemos aclarar la frecuencia del término, por ejemplo, "Documento":
julia> lexical_frequency(crps, "Document") 0.5
Además, podemos construir un índice inverso, es decir, para cada tema, obtener los números de documento en el caso. Este índice se utiliza en la recuperación de información, cuando necesita encontrar una lista de documentos donde aparecen de la lista de términos:
julia> update_inverse_index!(crps) julia> inverse_index(crps) Dict{String,Array{Int64,1}} with 3 entries: "1" => [1] "2" => [2] "Document" => [1, 2]
Para el caso en su conjunto, puede aplicar las funciones de preprocesamiento, lo mismo que para cada documento individual. ¡Se utiliza otro método de la función de prepare!
considerado anteriormente. Aquí, el primer argumento es el caso.
julia> crps = Corpus([StringDocument("Document ..!!"), StringDocument("Document ..!!")]) julia> prepare!(crps, strip_punctuation) julia> text(crps[1]) "Document " julia> text(crps[2]) "Document "
Además de los documentos individuales, puede solicitar metadatos para todo el cuerpo.
julia> crps = Corpus([StringDocument("Name Foo"), StringDocument("Name Bar")]) julia> languages(crps) 2-element Array{Languages.English,1}: Languages.English() Languages.English() julia> titles(crps) 2-element Array{String,1}: "Untitled Document" "Untitled Document" julia> authors(crps) 2-element Array{String,1}: "Unknown Author" "Unknown Author" julia> timestamps(crps) 2-element Array{String,1}: "Unknown Time" "Unknown Time"
Puede establecer los mismos valores para todo el cuerpo a la vez o individualmente para documentos específicos pasando una matriz con valores elemento por elemento para ellos.
julia> languages!(crps, Languages.German()) julia> titles!(crps, "") julia> authors!(crps, "Me") julia> timestamps!(crps, "Now") julia> languages!(crps, [Languages.German(), Languages.English julia> titles!(crps, ["", "Untitled"]) julia> authors!(crps, ["Ich", "You"]) julia> timestamps!(crps, ["Unbekannt", "2018"])
Destacado de funciones
La extracción de características es una de las etapas básicas del aprendizaje automático. Esto no se relaciona directamente con el tema de este artículo, pero en la documentación del paquete TextAnalysis se dedica una sección bastante grande a la identificación de características en esta misma formulación. Esta sección incluye, de hecho, la construcción de una matriz de documentos a término y muchos otros métodos. https://juliatext.imtqy.com/TextAnalysis.jl/dev/features/
Consideramos brevemente las opciones propuestas.
El modelo básico para presentar documentos es un modelo donde se almacena un conjunto de palabras para cada documento. Además, su posición no es importante. Por lo tanto, en un escritor en inglés, esta opción se llama Bolsa de palabras. Para cada palabra, solo el hecho de su presencia en el documento, la frecuencia de ocurrencia (TF - Frecuencia de término) o un modelo que tenga en cuenta la frecuencia de ocurrencia del término en el cuerpo como un todo (TF-IDF - Frecuencia de término - Frecuencia de documento inversa) es importante.
Tome el ejemplo más simple con tres documentos que contienen los términos Document
, 1
, 2
, 3
.
julia> using TextAnalysis julia> crps = Corpus([StringDocument("Document 1"), StringDocument("Document 2"), StringDocument("Document 1 3")])
No utilizaremos el preprocesamiento. Pero construiremos el léxico completo y la matriz del término documento:
julia> update_lexicon!(crps) julia> m = DocumentTermMatrix(crps) DocumentTermMatrix( [1, 1] = 1 [3, 1] = 1 [2, 2] = 1 [3, 3] = 1 [1, 4] = 1 [2, 4] = 1 [3, 4] = 1, ["1", "2", "3", "Document"], Dict("1" => 1,"2" => 2,"Document" => 4,"3" => 3))
La variable m
tiene un valor de tipo DocumentTermMatrix
. En el resultado impreso, vemos que la dimensión es de 3 documentos en 4 términos, que incluyen la palabra Document
y los números 1
, 2
, 3
. Para un mayor uso del modelo, necesitamos una matriz en la representación tradicional. Podemos obtenerlo usando el método dtm()
:
julia> dtm(m) 3×4 SparseArrays.SparseMatrixCSC{Int64,Int64} with 7 stored entries: [1, 1] = 1 [3, 1] = 1 [2, 2] = 1 [3, 3] = 1 [1, 4] = 1 [2, 4] = 1 [3, 4] = 1
Esta opción está representada por el tipo SparseMatrixCSC
, que es económico al representar una matriz muy escasa, pero solo hay un número limitado de bibliotecas que lo admiten. El problema del tamaño del término matriz de documentos se debe al hecho de que el número de términos aumenta muy rápidamente con el número de documentos procesados. Si no procesa previamente los documentos, entonces absolutamente todas las palabras con todas sus formas de palabras, números y fechas se incluirán en esta matriz. Incluso si la cantidad de formas de palabras se reduce debido a la reducción a la forma principal, la cantidad de tallos restantes será del orden de miles, decenas de miles. Es decir, la dimensión completa del término matriz de documentos está determinada por el producto total de esta cantidad por el número de documentos procesados. Una matriz completa requiere almacenar no solo unidades sino también ceros, sin embargo, es más fácil de usar que SparseMatrixCSC
. Puede obtenerlo mediante otro método dtm(..., :dense)
o convirtiendo una matriz dispersa en una completa utilizando el método collect()
:
julia> dtm(m, :dense) 3×4 Array{Int64,2}: 1 0 0 1 0 1 0 1 1 0 1 1
Si imprime una serie de términos, en cada línea es fácil ver la composición original de los documentos (el orden original de los términos no se tiene en cuenta).
julia> m.terms 4-element Array{String,1}: "1" "2" "3" "Document"
El término matriz de documentos para modelos de frecuencia se puede obtener utilizando los métodos tf()
y tf_idf()
:
julia> tf(m) |> collect 3×4 Array{Float64,2}: 0.5 0.0 0.0 0.5 0.0 0.5 0.0 0.5 0.333333 0.0 0.333333 0.333333
Es fácil ver la importancia de los términos para cada uno de los documentos. Los primeros dos documentos contienen dos términos. El último es tres. Entonces su peso se reduce.
Y para el método TF-IDF y tf_idf()
:
julia> tdm = tf_idf(m) |> collect 3×4 Array{Float64,2}: 0.202733 0.0 0.0 0.0 0.0 0.549306 0.0 0.0 0.135155 0.0 0.366204 0.0
Y en este modelo es fácil ver que el término Document
, que se encuentra en todos los documentos, tiene un valor de 0. Pero el término 3
en el tercer documento ha ganado más peso que 1
en el mismo documento, ya que 1
también se encuentra en el primer documento .
Las matrices resultantes son muy fáciles de usar, por ejemplo, para resolver el problema de la agrupación de documentos. Para hacer esto, necesitará el paquete de Clustering . Utilizamos el algoritmo de agrupamiento de k-medias más simple, que necesita especificar el número de grupos deseados. Dividimos nuestros tres documentos en dos grupos. La matriz de entrada para kmeans
es una matriz de kmeans
, donde las filas representan entidades y las columnas representan patrones. Por lo tanto, las matrices obtenidas anteriormente deben transponerse.
julia> using Clustering julia> R = kmeans(tdm', 2; maxiter=200, display=:iter) Iters objv objv-change | affected ------------------------------------------------------------- 0 1.386722e-01 1 6.933608e-02 -6.933608e-02 | 0 2 6.933608e-02 0.000000e+00 | 0 K-means converged with 2 iterations (objv = 0.06933608051588186) KmeansResult{Array{Float64,2},Float64,Int64}( [0.0 0.16894379504506848; 0.5493061443340549 0.0; 0.0 0.1831020481113516; 0.0 0.0], [2, 1, 2], [0.03466804025794093, 0.0, 0.03466804025794093], [1, 2], [1, 2], 0.06933608051588186, 2, true) julia> c = counts(R)
Como resultado, vemos que el primer grupo contiene un documento, el grupo número 2 contiene dos documentos. Además, la matriz que contiene los centros de los grupos de centros R.centers
muestra claramente que la primera columna está "atraída" por el término 2
. La segunda columna está determinada por la presencia de los términos 1
y 3
.
El paquete Clustering.jl
contiene un conjunto típico de algoritmos de agrupación, entre ellos: K-means, K-medoids, Affinity Propagation, Clustering espacial basado en densidad de aplicaciones con ruido (DBSCAN), Algoritmo de agrupación de Markov (MCL), Fuzzy C-Means Clustering, Agrupación jerárquica (individual, promedio, completa, vinculación de pupilo). Pero un análisis de su aplicabilidad está más allá del alcance de este artículo.
El paquete TextAnalysis.jl
encuentra actualmente en desarrollo activo, por lo que algunas funciones estarán disponibles solo cuando instale el paquete directamente desde el repositorio de git. No es difícil hacer esto, pero solo se puede recomendar a aquellos que no planean poner la solución en funcionamiento en un futuro cercano:
julia> ] (v1.2) pkg> add https://github.com/JuliaText/TextAnalysis.jl
Sin embargo, no debe ignorar estas funciones en la revisión. Por lo tanto, los consideramos también.
Una de las mejoras es el uso de la función de clasificación Okapi BM25 . Similar a los modelos tf
anteriores. tf_idf
, usamos el bm_25(m)
. El uso de la matriz resultante es similar a los casos anteriores.
Se puede hacer un análisis de la tonalidad de los textos utilizando métodos:
model = SentimentAnalyzer(doc) model = SentimentAnalyzer(doc, handle_unknown)
Además, doc
es uno de los tipos de documentos anteriores. handle_unknown
: función para procesar palabras desconocidas. El análisis de tonalidad se implementa utilizando el paquete Flux.jl basado en el paquete IMDB. El valor de retorno está en el rango de 0 a 1.
La generalización de documentos se puede implementar utilizando el método de summarize(d, ns)
. El primer argumento es el documento. El segundo es ns=
número de oraciones al final.
julia> s = StringDocument("Assume this Short Document as an example. Assume this as an example summarizer. This has too foo sentences.") julia> summarize(s, ns=2) 2-element Array{SubString{String},1}: "Assume this Short Document as an example." "This has too foo sentences."
Un componente muy importante de cualquier biblioteca de análisis de texto es el analizador sintáctico que se encuentra actualmente en desarrollo, que distingue las partes del discurso - POS (parte del discurso). Hay varias opciones para usarlo. Para obtener más información, consulte Partes del etiquetado de voz.
. Se llama al Tagging
porque para cada palabra en el texto fuente, se forma una etiqueta que significa parte del discurso.
Dos opciones para la implementación están en desarrollo. El primero es el algoritmo de perceptrón promedio. El segundo se basa en el uso de la arquitectura de red neuronal LSTM, CNN y el método CRF. Aquí hay un ejemplo de un marcado simple de oraciones.
julia> pos = PoSTagger() julia> sentence = "This package is maintained by John Doe." "This package is maintained by John Doe." julia> tags = pos(sentence) 8-element Array{String,1}: "DT" "NN" "VBZ" "VBN" "IN" "NNP" "NNP" "."
La lista de abreviaturas que significan parte del discurso está tomada de Penn Treebank . En particular, DT - Determinador, NN - Sustantivo, singular o en masa, VBZ - Verbo, tercera persona del singular presente, Verbo, participio pasado, IN - Preposición o conjunción subordinada, NNP - Sustantivo propio, singular.
Los resultados de este marcado también se pueden usar como características adicionales para la clasificación de documentos.
Métodos de reducción de dimensiones
TextAnalysis proporciona dos opciones para reducir la dimensionalidad definiendo términos dependientes. Este análisis semántico latente - LSA y colocación de Dirichlet latente - LDA.
La tarea principal de la LSA es obtener la descomposición de la matriz de término-documento (usando TF-IDF) en 3 matrices, cuyo producto corresponde aproximadamente al original.
julia> crps = Corpus([StringDocument("this is a string document"), TokenDocument("this is a token document")]) julia> update_lexicon!(crps) julia> m = DocumentTermMatrix(crps) julia> tf_idf(m) |> collect 2×6 Array{Float64,2}: 0.0 0.0 0.0 0.138629 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.138629 julia> F2 = lsa(m) SVD{Float64,Float64,Array{Float64,2}}([1.0 0.0; 0.0 1.0], [0.138629, 0.138629], [0.0 0.0 … 0.0 0.0; 0.0 0.0 … 0.0 1.0])
, -, TF-IDF . SVD, , .
LDA . Un ejemplo:
julia> crps = Corpus([StringDocument("This is the Foo Bar Document"), StringDocument("This document has too Foo words")]) julia> update_lexicon!(crps) julia> m = DocumentTermMatrix(crps) julia> k = 2
k
lda
, . ϕ
θ
, ntopics × nwords
, — ntopics × ndocs
.
— . . , . NaiveBayesClassifier()
. — fit!()
:
using TextAnalysis: NaiveBayesClassifier, fit!, predict m = NaiveBayesClassifier([:legal, :financial]) fit!(m, "this is financial doc", :financial) fit!(m, "this is legal doc", :legal)
predict
:
julia> predict(m, "this should be predicted as a legal document") Dict{Symbol,Float64} with 2 entries: :legal => 0.666667 :financial => 0.333333
, , :legal
.
TextAnalysis.jl . , . MLJ.jl . AdaBoostClassifier, BaggingClassifier, BernoulliNBClassifier, ComplementNBClassifier, ConstantClassifier, XGBoostClassifier, DecisionTreeClassifier. - LSA, . .
TextAnalysis.jl CRF — Conditional Random Fields , Flux.jl, . .
TextAnalysis.jl — NER . NERTagger()
:
:
julia> sentence = "This package is maintained by John Doe." "This package is maintained by John Doe." julia> tags = ner(sentence) 8-element Array{String,1}: "O" "O" "O" "O" "O" "PER" "PER" "O"
NERTagger
TextAnalysis. .
StringDistances.jl
. , , . . , StringDistances.jl . :
using StringDistances compare("martha", "martha", Hamming())
compare
— . , 1 — . 0 — .
, Jaro-Winkler. , . RatcliffObershelp, , . , . .
compare("mariners vs angels", "angels vs mariners", RatcliffObershelp())
, , . , TokenSort , . Julia — Julia, .
WordTokenizers.jl
WordTokenizers.jl . , , TextAnalysis.jl.
— . , tokenize(text)
.
julia> using WordTokenizers julia> text = "I cannot stand when they say \"Enough is enough.\""; julia> tokenize(text) |> print
WordTokenizers .
julia> text = "The leatherback sea turtle is the largest, measuring six or seven feet (2 m) in length at maturity, and three to five feet (1 to 1.5 m) in width, weighing up to 2000 pounds (about 900 kg). Most other species are smaller, being two to four feet in length (0.5 to 1 m) and proportionally less wide. The Flatback turtle is found solely on the northerncoast of Australia."; julia> split_sentences(text) 3-element Array{SubString{String},1}: "The leatherback sea turtle is the largest, measuring six or seven feet (2 m) in length at maturity, and three to five feet (1 to 1.5 m) in width, weighing up to 2000 pounds (about900 kg). " "Most other species are smaller, being two to four feet in length (0.5 to 1 m) and proportionally less wide. " "The Flatback turtle is found solely on the northern coast of Australia." julia> tokenize.(split_sentences(text)) 3-element Array{Array{SubString{String},1},1}: SubString{String}["The", "leatherback", "sea", "turtle", "is", "the", "largest", ",", "measuring", "six" … "up", "to", "2000", "pounds", "(", "about", "900", "kg", ")", "."] SubString{String}["Most", "other", "species", "are", "smaller", ",", "being", "two", "to", "four" … "0.5", "to", "1", "m", ")", "and", "proportionally", "less", "wide", "."] SubString{String}["The", "Flatback", "turtle", "is", "found", "solely", "on", "the", "northern", "coast", "of", "Australia", "."]
:
- Poorman's tokenizer — . ,
split
. - Punctuation space tokenize — . , .
- Penn Tokenizer — , Penn Treebank.
- Improved Penn Tokenizer — , NLTK.
- NLTK Word tokenizer — , NLTK, , UNICODE- .
- Reversible Tokenizer — , .
- TokTok Tokenizer — , .
- Tweet Tokenizer — , , , HTML- .
set_tokenizer(nltk_word_tokenize)
Embeddings.jl
Embeddings.jl . , , , , , , . Word2Vec. , : king - man + woman = queen
. , , . , , , Wikipedia, . , . «semantic space», , «semantic distance». , , , «» «» . , , , .
, «embedding» , , , . , , , , , , . , -, . , . , . .
Embeddings.jl : Word2Vec, GloVe (English only), FastText. . , , . — , . , , word2vec, 8-16 . , .
, , DataDeps.jl . , (" "). , Embedding.jl , , . , .
ENV["DATADEPS_ALWAYS_ACCEPT"] = true
— . ~/.julia/datadeps
.
. — :
using Embeddings const embtable = load_embeddings(Word2Vec)
— :
julia> get_embedding("blue") 300-element Array{Float32,1}: 0.01540828 0.03409082 0.0882124 0.04680265 -0.03409082 ...
WordTokenizers TextAnalysis, . , Julia:
julia> a = rand(5) 5-element Array{Float64,1}: 0.012300397820243392 0.13543646950484067 0.9780602985106086 0.24647179461578816 0.18672770774122105 julia> b = ones(5) 5-element Array{Float64,1}: 1.0 1.0 1.0 1.0 1.0 julia> a+b 5-element Array{Float64,1}: 1.0123003978202434 1.1354364695048407 1.9780602985106086 1.2464717946157882 1.186727707741221
Clustering.jl. , — . MLJ.jl. , https://github.com/JuliaStats/Distances.jl , :
- Euclidean distance
- Squared Euclidean distance
- Periodic Euclidean distance
- Cityblock distance
- Total variation distance
- Jaccard distance
- Rogers-Tanimoto distance
- Chebyshev distance
- Minkowski distance
- Hamming distance
- Cosine distance
- Correlation distance
- Chi-square distance
- Kullback-Leibler divergence
- Generalized Kullback-Leibler divergence
- Rényi divergence
- Jensen-Shannon divergence
- Mahalanobis distance
- Squared Mahalanobis distance
- Bhattacharyya distance
- Hellinger distance
- Haversine distance
- Mean absolute deviation
- Mean squared deviation
- Root mean squared deviation
- Normalized root mean squared deviation
- Bray-Curtis dissimilarity
- Bregman divergence
.
Transformers.jl — Julia «Transformers», BERT Google. , NER — , .
Transformers.jl Flux.jl , , , Julia- , . Flux.jl CPU GPU, , , , .
BERT , . :
using Transformers using Transformers.Basic using Transformers.Pretrain using Transformers.Datasets using Transformers.BidirectionalEncoder using Flux using Flux: onehotbatch, gradient import Flux.Optimise: update! using WordTokenizers ENV["DATADEPS_ALWAYS_ACCEPT"] = true const FromScratch = false
vectorize
. :
using Distances x1 = vectorize("Some test about computers") x2 = vectorize("Some test about printers") cosine_dist(x1, x2)
, wordpiece
, tokenizer
— . 12 — . 768 — . . https://chengchingwen.imtqy.com/Transformers.jl/dev/pretrain/ . , Transformers.Pretrain.@pretrain_str, pretrain"model-description:item"
.
, , Transformers.jl , .
Conclusión
, , Julia . . , , . , Julia , . , Julia.
, , , - «», . , , «open source» , , , . , , Julia . , Jupyter Notebook , , — Atom/Juno, VS Code, . , , Julia — 2-3 , ( , , ), C++ .
, , Julia, -, . , , , , . , 2-3 , , . Julia . - , for
. — « ». , C, . Julia, , - , , — Julia-. , Julia — , .
, , Julia . , , .
, - Julia — @JuliaLanguage, .
Referencias