朱莉娅NLP。 我们处理文本


用自然语言对文本进行分析和处理是一项始终不变的主题任务,已经解决,正在解决并且将以所有可用的方式解决。 今天,我想谈谈解决此问题的解决方案工具,即使用Julia语言。 当然,由于该语言的年轻性,因此没有像Java语言这样的发达的分析工具,例如Stanford CoreNLP,Apache OpenNLP,GATE等。 但是,即使是已经开发的库也可以用于解决典型问题,也可以推荐给对文字处理领域感兴趣的学生入学。 Julia的语法简单性及其先进的数学工具使您轻松沉浸于文本的聚类和分类任务中。


本文的目的是回顾Julia文字处理工具,并提供一些有关其用法的解释。 我们将在为NLP主题但希望完全了解Julia工具的人员提供的简短机会列表与为决定首次涉足NLP(自然语言处理)领域的人员的详细说明和应用示例之间取得平衡。


好了,现在,让我们继续看一下软件包概述。


TextAnalysis.jl


TextAnalysis.jl软件包是一个基本库,可实现最少的典型文本处理功能集。 我们从她那里开始。 示例部分取自文档


文件


基本实体是文档。


支持以下类型:


  • FileDocument-由磁盘上的简单文本文件表示的文档

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-由UTF-8字符串表示并存储在RAM中的文档。 StringDocument结构提供了整个文本的存储。

 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-一个由UTF-8令牌(突出显示的单词)组成的文档。 TokenDocument结构存储一组令牌,但是,完整的文本将无法恢复而不会丢失。

 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-以UTF8表示形式的一组n-gram表示的文档,即n UTF-8字符的序列,以及一个出现它们的计数器。 呈现文档的此选项是避免分析的文本中出现语言形态,拼写错误以及语言结构特征的某些最简单方法之一。 但是,与考虑语言信息的方法相比,这样做的代价是降低了文本分析的质量。

 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*** 

或一个简短的选择:


 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")) 

也可以使用通用的Document构造函数简单地创建文档,并且库将找到该文档的适当实现。


 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*** 

如您所见,文档的主体由文本/令牌和元数据组成。 可以使用text(...)方法获取文档的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" 

该示例演示了带有自动分析的令牌的文档。 我们看到对text(td)的调用text(td)发出警告,指出文本仅被近似还原,因为TokenDocument不存储单词定界符。 tokens(td)的调用tokens(td)使得准确获取突出显示的单词成为可能。


您可以从文档中请求元数据:


 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" 

所有这些都可以通过相应的功能进行更改。 在Julia中修改功能的符号与Ruby语言中的符号相同。 修改对象的函数具有后缀!


 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" 

具有UTF-8的字符串的功能


Julia在处理字符串时支持UTF-8编码,因此使用非拉丁字母没有问题。 任何字符处理选项都是自然可用的。 但是,请记住,Julia的行索引是字节,而不是字符。 每个字符可以用不同数量的字节表示。 并且有使用UNICODE字符的单独方法。 有关详细信息,请参见Unicode和UTF-8 。 但这是一个简单的例子。 让我们用数学UNICODE字符设置一行,并用空格将x和y隔开:


 julia> s = "\u2200 x \u2203 y" "∀ x ∃ y" julia> length(s) # ! 7 julia> ncodeunits(s) # ! 11 

现在让我们看一下索引:


 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) 

该示例清楚地表明,索引1允许我们获得符号 。 但是所有后续索引(包括3)(含3个索引)均导致错误。 并且只有第四个索引产生一个空格,作为字符串中的下一个字符。 但是,要通过字符串中的索引确定字符的边界,有一些有用的函数prevind (上一个索引), nextind (下一个索引)和thisind (此索引)。 例如,对于上面找到的差距,我们要求上一个的边界在哪里:


 julia> prevind(s, 4) 1 

我们将索引1作为符号beginning的开头。


 julia> thisind(s, 3) 1 

检查索引3并获得相同的有效1。


如果我们需要“遍历”所有字符,那么可以通过至少两种简单的方法来完成:
1)使用设计:


 julia> for c in s print(c) end ∀ x ∃ y 

2)使用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 

文件预处理


如果文档的文本是从某种外部表示形式获得的,则字节流中很可能存在编码错误。 要消除它们,请使用remove_corrupt_utf8!(sd)函数。 论据是上面讨论的文档。


处理TextAnalysis包中文档的主要功能是prepare!(...) 。 例如,从文本中删除标点符号:


 julia> str = StringDocument("here are some punctuations !!!...") julia> prepare!(str, strip_punctuation) julia> text(str) "here are some punctuations " 

同样,单词处理中的一个有用步骤是将所有字母都转换为小写,因为这简化了单词之间的进一步比较。 在这种情况下,通常情况下,必须理解,我们可能会丢失有关文本的重要信息,例如,单词是专有名称,或者单词是句子的边界。 但这一切都取决于进一步处理的模型。 小写由remove_case!()函数完成。


 julia> sd = StringDocument("Lear is mad") A StringDocument{String} julia> remove_case!(sd) julia> text(sd) "lear is mad" 

在此过程中,我们可以删除无用字,即在信息检索和匹配分析中没有用的无用字。 可以使用remove_words!(…)函数和这些停用词的数组来明确地完成此操作。


 julia> remove_words!(sd, ["lear"]) julia> text(sd) " is mad" 

在要删除的单词中,还有冠词,介词,代词,数字和只是停用词,它们的出现频率是寄生的。 对于每种特定语言,这些词典都是单独的。 这些数字设置在Languages.jl包中,数字使我们感到困扰,因为在将来为热文档建模时,它们可以极大地增加矩阵的尺寸,而不会改善例如文本的聚类。 但是,例如在搜索问题中,不再总是可以丢弃数字。


可用的清洁方法包括以下选项:


  • 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)

选项可以组合。 例如,一个电话prepare! 同时删除文章,数字和html标签- prepare!(sd, strip_articles| strip_numbers| strip_html_tags)


另一种处理方式是突出显示单词的基础,删除结尾和后缀。 这使您可以组合不同的单词形式,并大大降低文档表示模型的维数。 为此需要使用字典,因此必须明确指出文档的语言。 俄语处理示例:


 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) "   " 

文件主体


语料库被理解为将根据相同规则处理的一组文档。 TextAnalysis包实现了术语文档矩阵的形成。 对于其构造,我们需要立即拥有一整套文档。 在一个简单的文档示例中:
D1 = "I like databases"
D2 = "I hate databases"


这个矩阵看起来像:


喜欢讨厌资料库
D11个1个01个
D21个01个1个

列由文档的词表示,行是文档的标识符(或索引)。 因此,如果单词(项)未出现在文档中,则该单元格将为0。 如果发生多次则为1。 更复杂的模型会同时考虑出现的频率(TF模型)和相对于整个身体的重要性(TF-IDF)。


我们可以使用Corpus()构造函数构建主体:


 crps = Corpus([StringDocument("Document 1"), StringDocument("Document 2")]) 

如果我们立即请求条款列表,则会得到:


 julia> lexicon(crps) Dict{String,Int64} with 0 entries 

并且,在这里,使用update_lexicon!(crps)强制库重新计算属于案例的所有术语,我们得到了不同的结果:


 julia> update_lexicon!(crps) julia> lexicon(crps) Dict{String,Int64} with 3 entries: "1" => 1 "2" => 1 "Document" => 2 

也就是说,我们可以在文档正文中看到选定的术语(单词和数字)及其条目数。


同时,我们可以澄清术语的频率,例如“文档”:


 julia> lexical_frequency(crps, "Document") 0.5 

同样,我们可以建立一个反向索引,即针对每个主题,获取案例编号。 当您需要从术语列表中查找文档列表时,该索引用于信息检索:


 julia> update_inverse_index!(crps) julia> inverse_index(crps) Dict{String,Array{Int64,1}} with 3 entries: "1" => [1] "2" => [2] "Document" => [1, 2] 

对于整个案例,您可以应用预处理功能,就像处理每个单独的文档一样。 使用prepare!功能的另一种方法prepare! 考虑较早。 在这里,第一个论点就是这种情况。


 julia> crps = Corpus([StringDocument("Document ..!!"), StringDocument("Document ..!!")]) julia> prepare!(crps, strip_punctuation) julia> text(crps[1]) "Document " julia> text(crps[2]) "Document " 

除了单个文档,您还可以请求整个正文的元数据。


 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" 

您可以一次为整个主体设置相同的值,也可以为特定文档单独设置一个值,方法是传递一个带有逐元素值的数组。


 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"]) 

功能突出


特征提取是机器学习的基本阶段之一。 这与本文的主题没有直接关系,但是在TextAnalysis程序包的文档中,有相当大的一部分专门用于识别这种表示形式中的功能。 该部分实际上包括术语文档矩阵的构造以及许多其他方法。 https://juliatext.imtqy.com/TextAnalysis.jl/dev/features/


我们简要地考虑建议的方案。


用于呈现文档的基本模型是其中为每个文档存储一组单词的模型。 而且,它们的位置并不重要。 因此,在英语作家中,此选项称为单词袋。 对于每个单词,只有其存在于文档中的事实,出现频率(TF-术语频率)或考虑整体中该术语出现频率的模型(TF-IDF-术语频率-逆文档频率)才是重要的。


以最简单的示例为例,其中三个文档包含术语Document3


 julia> using TextAnalysis julia> crps = Corpus([StringDocument("Document 1"), StringDocument("Document 2"), StringDocument("Document 1 3")]) 

我们将不使用预处理。 但是,我们将建立术语文档的完整词典和矩阵:


 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)) 

变量m具有类型DocumentTermMatrix的值。 在打印结果中,我们看到维是4个术语的3个文档,其中包括单词Document和数字3 。 为了进一步使用该模型,我们需要传统表示形式的矩阵。 我们可以使用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 

此选项由SparseMatrixCSC类型表示,该类型在表示非常稀疏的矩阵时很经济,但是支持它的库数量有限。 术语文档矩阵大小的问题是由于以下事实造成的:术语数量随着处理的文档数量而迅速增长。 如果您不对文档进行预处理,那么绝对所有带有其单词形式,数字,日期的单词都将落入此矩阵。 即使由于减少了主形式而减少了词的形式,剩余词干的数量也将是数千至数万。 即,术语文档矩阵的整个维度由该数量的总乘积与已处理文档的数量确定。 完整矩阵不仅需要存储单位,还需要存储零,但是比SparseMatrixCSC易于使用。 您可以通过另一种方法dtm(..., :dense)或通过使用collect()方法将稀疏矩阵转换为完整的矩阵来获取它:


 julia> dtm(m, :dense) 3×4 Array{Int64,2}: 1 0 0 1 0 1 0 1 1 0 1 1 

如果您打印一组术语,则在每一行中都可以很容易地看到文档的原始组成(不考虑术语的原始顺序)。


 julia> m.terms 4-element Array{String,1}: "1" "2" "3" "Document" 

可以使用tf()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 

很容易看出每个文档中术语的重要性。 前两个文档包含两个术语。 最后是三个。 因此,它们的重量得以减轻。


而对于TF-IDF和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 

在此模型中,很容易看到在所有文档中都找到的术语Document的值为0。但是在第三个文档中,术语3的权重大于在同一文档中的权重,因为在第一个文档中也发现1 。


生成的矩阵非常易于使用,例如,用于解决文档聚类的问题。 为此,您需要Clustering包 。 我们使用最简单的k均值聚类算法,该算法需要指定所需聚类的数量。 我们将三个文档分为两个类。 kmeans的输入矩阵是特征矩阵,其中行表示要素,列表示模式。 因此,以上获得的矩阵必须转置。


 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) #    2-element Array{Int64,1}: 1 2 julia> a = assignments(R) #     3-element Array{Int64,1}: 2 1 2 julia> M = R.centers #     4×2 Array{Float64,2}: 0.0 0.168944 0.549306 0.0 0.0 0.183102 0.0 0.0 

结果,我们看到第一个群集包含一个文档,群集编号2包含两个文档。 此外,包含R.centers簇中心的矩阵清楚地表明,第一列被术语2 “吸引”。 第二列由术语13的存在确定。


Clustering.jl软件包包含一组典型的聚类算法,其中包括:K-均值,K-medoids,亲和传播,基于噪声的基于应用程序的空间密度聚类(DBSCAN),Markov聚类算法(MCL),模糊C均值聚类,层次聚类(单个,平均,完整,沃德联动)。 但是,对其适用性的分析超出了本文的范围。


TextAnalysis.jl软件包目前正在积极开发中,因此,仅当直接从git存储库直接安装该软件包时,某些功能才可用。 做到这一点并不难,但是只能建议那些不打算在不久的将来将该解决方案投入运行的用户:


 julia> ] (v1.2) pkg> add https://github.com/JuliaText/TextAnalysis.jl 

但是,您不应在审阅中忽略这些功能。 因此,我们也考虑它们。


改进之一是使用Okapi BM25排名功能。 与以前的tf模型相似。 tf_idf ,我们使用bm_25(m)方法。 所得矩阵的使用与之前的情况类似。


可以使用以下方法对文本的音调进行分析:


 model = SentimentAnalyzer(doc) model = SentimentAnalyzer(doc, handle_unknown) 

而且, doc是上述文件类型之一。 handle_unknown用于处理未知单词的函数。 使用基于IMDB包的Flux.jl包可以实现音调分析。 返回值在0到1的范围内。


可以使用summarize(d, ns)方法实现文档概括。 第一个参数是文档。 第二个是ns=的句子数。


 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." 

任何文本分析库的一个非常重要的组成部分是当前正在开发的语法解析器,它可以区分语音部分-POS(语音部分)。 有几种使用它的选项。 有关详细信息,请参见词性标记。
。 之所以称为Tagging是因为对于源文本中的每个单词,都会形成一个标记,这意味着语音的一部分。


正在制定两种实施方案。 第一个是平均感知器算法。 第二种基于神经网络架构LSTM,CNN和CRF方法的使用。 这是一个简单句子标记的示例。


 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" "." 

意思词性的缩写清单取自宾州树银行 。 特别是DT-限定词,NN-名词,单数或质量,VBZ-动词,第三人称单数现在,动词,过去分词,IN-介词或从属连词,NNP-专有名词,单数。


该标记的结果还可以用作文档分类的附加功能。


降维方法


TextAnalysis提供了两个选项,可通过定义从属术语来减少维数。 这种潜在的语义分析-LSA和潜在的Dirichlet放置-LDA。


LSA的主要任务是将术语文档矩阵(使用TF-IDF)分解为3个矩阵,其乘积与原始矩阵大致对应。


 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.00.0 0.0; 0.0 0.00.0 1.0]) 

, -, TF-IDF . SVD, , .


LDA . 一个例子:


 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 # number of topics julia> iterations = 1000 # number of gibbs sampling iterations julia> α = 0.1 # hyper parameter julia> β = 0.1 # hyper parameter julia> ϕ, θ = lda(m, k, iterations, α, β) ( [2 , 1] = 0.333333 [2 , 2] = 0.333333 [1 , 3] = 0.222222 [1 , 4] = 0.222222 [1 , 5] = 0.111111 [1 , 6] = 0.111111 [1 , 7] = 0.111111 [2 , 8] = 0.333333 [1 , 9] = 0.111111 [1 , 10] = 0.111111, [0.5 1.0; 0.5 0.0]) 

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.jlNER . NERTagger() :


  • PER:
  • LOC:
  • ORG:
  • MISC:
  • O:

:


 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()) #> 1.0 compare("martha", "marhta", Jaro()) #> 0.9444444444444445 compare("martha", "marhta", Winkler(Jaro())) #> 0.9611111111111111 compare("william", "williams", QGram(2)) #> 0.9230769230769231 compare("william", "williams", Winkler(QGram(2))) #> 0.9538461538461539 

compare — . , 1 — . 0 — .


, Jaro-Winkler. , . RatcliffObershelp, , . , . .


  compare("mariners vs angels", "angels vs mariners", RatcliffObershelp()) #> 0.44444 compare("mariners vs angels", "angels vs mariners", TokenSort(RatcliffObershelp()) #> 1.0 compare("mariners vs angels", "los angeles angels at seattle mariners", Jaro()) #> 0.559904 compare("mariners vs angels", "los angeles angels at seattle mariners", TokenSet(Jaro())) #> 0.944444 compare("mariners vs angels", "los angeles angels at seattle mariners", TokenMax(RatcliffObershelp())) #> 0.855 

, , . , 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 # Default tokenizer SubString{String}["I", "can", "not", "stand", "when", "they", "say", "``", "Enough", "is", "enough", ".", "''"] 

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) # or load_embeddings(FastText_Text) or ... const get_word_index = Dict(word=>ii for (ii,word) in enumerate(embtable.vocab)) function get_embedding(word) ind = get_word_index[word] emb = embtable.embeddings[:,ind] return emb end 

— :


 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


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 #use wordpiece and tokenizer from pretrain const wordpiece = pretrain"bert-uncased_L-12_H-768_A-12:wordpiece" const tokenizer = pretrain"bert-uncased_L-12_H-768_A-12:tokenizer" const vocab = Vocabulary(wordpiece) const bert_model = gpu( FromScratch ? create_bert() : pretrain"bert-uncased_L-12_H-768_A-12:bert_model" ) Flux.testmode!(bert_model) function vectorize(str::String) tokens = str |> tokenizer |> wordpiece text = ["[CLS]"; tokens; "[SEP]"] token_indices = vocab(text) segment_indices = [fill(1, length(tokens) + 2);] sample = (tok = token_indices, segment = segment_indices) bert_embedding = sample |> bert_model.embed collect(sum(bert_embedding, dims=2)[:]) end 

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 , .


结论


, , 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, .


参考文献


Source: https://habr.com/ru/post/zh-CN475922/


All Articles