Julia NLP. Kami memproses teks


Analisis dan pemrosesan teks dalam bahasa alami adalah tugas topikal yang terus-menerus diselesaikan, sedang dipecahkan, dan akan dipecahkan dengan semua cara yang tersedia. Hari ini saya ingin berbicara tentang alat solusi untuk menyelesaikan masalah ini, yaitu, dalam bahasa Julia. Tentu saja, karena usia bahasanya, tidak ada alat analisis yang dikembangkan, seperti, misalnya, Stanford CoreNLP, Apache OpenNLP, GATE, dll., Seperti, misalnya, untuk bahasa Jawa. Namun, bahkan perpustakaan yang telah dikembangkan dapat digunakan baik untuk memecahkan masalah tipikal dan dapat direkomendasikan sebagai titik masuk bagi siswa yang tertarik dalam bidang pengolah kata. Dan kesederhanaan sintaksis Julia dan alat matematika canggihnya membuatnya mudah untuk membenamkan diri dalam tugas pengelompokan dan klasifikasi teks.


Tujuan artikel ini adalah untuk meninjau alat pengolah kata Julia dengan beberapa penjelasan tentang penggunaannya. Kami akan menyeimbangkan antara daftar singkat peluang bagi mereka yang berada dalam topik NLP, tetapi ingin melihat secara tepat alat Julia, dan penjelasan yang lebih rinci dan contoh aplikasi bagi mereka yang memutuskan untuk terjun ke area NLP (Pemrosesan Bahasa Alami) seperti untuk pertama kalinya.


Nah, sekarang, mari kita beralih ke tinjauan umum paket.


TextAnalysis.jl


Paket TextAnalysis.jl adalah pustaka dasar yang mengimplementasikan sekumpulan fungsi pemrosesan teks minimal. Dengan dia kita mulai. Contoh diambil sebagian dari dokumentasi .


Dokumen


Entitas dasar adalah sebuah dokumen.


Jenis-jenis berikut ini didukung:


  • FileDocument - dokumen yang diwakili oleh file teks sederhana pada disk

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 - dokumen yang diwakili oleh string UTF-8 dan disimpan dalam RAM. Struktur StringDocument menyediakan penyimpanan teks secara keseluruhan.

 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 - dokumen yang merupakan urutan token UTF-8 (kata-kata yang disorot). Struktur TokenDocument menyimpan satu set token, namun teks lengkapnya tidak dapat dipulihkan tanpa kehilangan.

 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 - dokumen yang disajikan sebagai set n-gram dalam representasi UTF8, mis. Urutan n karakter UTF-8, dan penghitung untuk kemunculannya. Opsi penyajian dokumen ini adalah salah satu cara paling sederhana untuk menghindari beberapa masalah morfologi bahasa, kesalahan ketik, dan fitur struktur bahasa dalam teks yang dianalisis. Namun, biaya untuk ini adalah penurunan kualitas analisis teks dibandingkan dengan metode di mana informasi bahasa diperhitungkan.

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

Atau opsi singkat:


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

Dokumen juga dapat dibuat hanya dengan menggunakan konstruktor Dokumen generik, dan perpustakaan akan menemukan implementasi dokumen yang sesuai.


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

Seperti yang Anda lihat, isi dokumen terdiri dari teks / token dan metadata. Teks dokumen dapat diperoleh dengan menggunakan metode 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" 

Contoh menunjukkan dokumen dengan token yang diuraikan secara otomatis. Kita melihat bahwa panggilan ke text(td) mengeluarkan peringatan bahwa teks itu hanya sekitar dipulihkan, karena TokenDocument tidak menyimpan pembatas kata. Panggilan tokens(td) memungkinkan untuk mendapatkan persis kata-kata yang disorot.


Anda dapat meminta metadata dari dokumen:


 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" 

Dan semuanya dapat diubah dengan fungsi yang sesuai. Notasi untuk memodifikasi fungsi dalam Julia sama dengan dalam bahasa Ruby. Fungsi yang memodifikasi objek memiliki akhiran ! :


 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" 

Fitur string dengan UTF-8


Julia mendukung pengkodean UTF-8 saat memproses string, sehingga tidak ada masalah dengan menggunakan huruf non-Latin. Semua opsi pemrosesan karakter tersedia secara alami. Namun, perlu diingat bahwa indeks baris untuk Julia adalah byte, bukan karakter. Dan setiap karakter dapat diwakili oleh jumlah byte yang berbeda. Dan ada metode terpisah untuk bekerja dengan karakter UNICODE. Lihat Unicode-and-UTF-8 untuk detailnya. Tapi ini adalah contoh sederhana. Mari kita menetapkan garis dengan UNICODE-karakter matematika yang dipisahkan dari x dan y oleh spasi:


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

Sekarang mari kita lihat indeksnya:


 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) 

Contohnya dengan jelas menunjukkan bahwa indeks 1 memungkinkan kita untuk mendapatkan simbol . Tetapi semua indeks berikutnya hingga 3 inklusif, menyebabkan kesalahan. Dan hanya indeks ke-4 yang menghasilkan spasi, sebagai karakter berikutnya dalam string. Namun, untuk menentukan batas karakter berdasarkan indeks dalam string, ada fungsi yang berguna untuk prevind (indeks sebelumnya), berikutnya (indeks berikutnya) dan ini (indeks ini). Misalnya, untuk celah yang ditemukan di atas, kami bertanya di mana batas yang sebelumnya:


 julia> prevind(s, 4) 1 

Kami mendapat indeks 1 sebagai awal simbol .


 julia> thisind(s, 3) 1 

Memeriksa indeks 3 dan mendapatkan valid yang sama 1.


Jika kita perlu "memeriksa" semua karakter, maka ini dapat dilakukan setidaknya dalam dua cara sederhana:
1) menggunakan desain:


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

2) menggunakan enumeratorindeksindeks:


 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 

Pra-pemrosesan dokumen


Jika teks dokumen diperoleh dari beberapa representasi eksternal, maka sangat mungkin bahwa ada kesalahan pengkodean dalam aliran byte. Untuk menghilangkannya, gunakan fungsi remove_corrupt_utf8!(sd) . Argumennya adalah dokumen yang dibahas di atas.


Fungsi utama untuk memproses dokumen dalam paket TextAnalysis adalah prepare!(...) . Misalnya, hapus tanda baca dari teks:


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

Juga, langkah yang berguna dalam pemrosesan kata adalah konversi semua huruf menjadi huruf kecil, karena ini menyederhanakan perbandingan kata lebih lanjut satu sama lain. Dalam kasus ini, dalam kasus umum, harus dipahami bahwa kita dapat kehilangan informasi penting tentang teks, misalnya, fakta bahwa kata itu adalah nama yang tepat atau kata itu adalah batas dari sebuah kalimat. Tetapi itu semua tergantung pada model pemrosesan selanjutnya. Huruf kecil dilakukan oleh fungsi remove_case!() .


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

Sepanjang jalan, kita dapat menghapus kata-kata sampah, yaitu kata-kata yang tidak digunakan dalam pencarian informasi dan analisis untuk kecocokan. Ini dapat dilakukan secara eksplisit menggunakan fungsi remove_words!(…) dan larik kata-kata berhenti ini.


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

Di antara kata-kata yang akan dihapus, ada juga artikel, preposisi, kata ganti, angka dan hanya kata-kata berhenti, yang parasit dalam frekuensi kejadian. Untuk setiap bahasa tertentu, kamus-kamus ini bersifat individual. Dan mereka diatur dalam paket Languages.jl. Angka mengganggu kita karena di masa depan model dokumen termal, mereka dapat sangat meningkatkan dimensi matriks tanpa meningkatkan, misalnya, pengelompokan teks. Namun, dalam masalah pencarian, misalnya, tidak lagi selalu mungkin untuk menjatuhkan angka.


Di antara metode pembersihan yang tersedia adalah opsi berikut:


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

Opsi dapat digabungkan. Misalnya, dalam satu panggilan untuk prepare! menghapus artikel, angka, dan tag html secara bersamaan - prepare!(sd, strip_articles| strip_numbers| strip_html_tags)


Jenis pemrosesan lainnya adalah menyoroti basis kata-kata, menghilangkan akhiran dan akhiran. Ini memungkinkan Anda untuk menggabungkan berbagai bentuk kata dan secara dramatis mengurangi dimensi model presentasi dokumen. Kamus diperlukan untuk ini, sehingga bahasa dokumen harus ditunjukkan dengan jelas. Contoh pemrosesan dalam bahasa Rusia:


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

Badan dokumen


Korpus dipahami sebagai seperangkat dokumen yang akan diproses sesuai dengan aturan yang sama. Paket TextAnalysis mengimplementasikan pembentukan matriks dokumen istilah . Dan untuk konstruksinya, kita perlu segera memiliki satu set dokumen yang lengkap. Dalam contoh sederhana untuk dokumen:
D1 = "I like databases"
D2 = "I hate databases"


matriks ini terlihat seperti:


Sayasepertibencibasis data
D11101
D21011

Kolom diwakili oleh kata-kata dokumen, dan baris adalah pengidentifikasi (atau indeks) dokumen. Dengan demikian, sel akan menjadi 0 jika kata (istilah) tidak muncul dalam dokumen. Dan 1 jika itu terjadi beberapa kali. Model yang lebih kompleks mempertimbangkan frekuensi kemunculan (model TF) dan signifikansi dalam kaitannya dengan seluruh tubuh (TF-IDF).


Kita dapat membangun tubuh menggunakan konstruktor Corpus() :


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

Jika kami segera meminta daftar persyaratan, kami mendapatkan:


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

Dan, di sini, memaksa perpustakaan untuk menghitung ulang semua istilah yang merupakan bagian dari case menggunakan update_lexicon!(crps) , kami mendapatkan hasil yang berbeda:


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

Artinya, kita dapat melihat istilah yang dipilih (kata-kata dan angka) dan jumlah entri mereka di badan dokumen.


Pada saat yang sama, kami dapat mengklarifikasi frekuensi istilah, misalnya, "Dokumen":


 julia> lexical_frequency(crps, "Document") 0.5 

Juga, kita dapat membuat indeks terbalik, yaitu, untuk setiap topik, dapatkan nomor dokumen dalam case. Indeks ini digunakan dalam pencarian informasi, ketika Anda perlu menemukan daftar dokumen di mana mereka muncul dari daftar istilah:


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

Untuk case secara keseluruhan, Anda dapat menerapkan fungsi preprocessing, sama seperti untuk setiap dokumen individu. Metode lain dari fungsi prepare! digunakan prepare! dipertimbangkan sebelumnya. Di sini, argumen pertama adalah masalahnya.


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

Seperti halnya untuk dokumen individual, Anda dapat meminta metadata untuk seluruh badan.


 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" 

Anda dapat mengatur nilai yang sama untuk seluruh badan sekaligus atau individu untuk dokumen tertentu dengan melewatkan array dengan nilai elemen demi elemen untuknya.


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

Sorotan Fitur


Ekstraksi fitur adalah salah satu tahapan dasar pembelajaran mesin. Ini tidak secara langsung berhubungan dengan topik artikel ini, tetapi dalam dokumentasi untuk paket TextAnalysis, bagian yang agak besar dikhususkan untuk identifikasi fitur dalam formulasi ini. Bagian ini mencakup keduanya, pada kenyataannya, pembangunan sebuah term-document matrix, dan banyak metode lainnya. https://juliatext.imtqy.com/TextAnalysis.jl/dev/features/


Kami mempertimbangkan secara singkat opsi yang diusulkan.


Model dasar untuk mempresentasikan dokumen adalah model di mana satu set kata disimpan untuk setiap dokumen. Apalagi posisi mereka tidak penting. Karena itu, dalam penulis berbahasa Inggris, opsi ini disebut Kantung kata-kata. Untuk setiap kata, hanya fakta keberadaannya dalam dokumen, frekuensi kemunculan (TF - Term Frequency) atau model yang memperhitungkan frekuensi kemunculan istilah dalam tubuh secara keseluruhan (TF-IDF - Term Frequency - Inverse Document Frequency) penting.


Ambil contoh paling sederhana dengan tiga dokumen yang berisi istilah Document , 1 , 2 , 3 .


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

Kami tidak akan menggunakan preprocessing. Tetapi kami akan membangun leksikon dan matriks penuh dari istilah dokumen:


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

Variabel m memiliki nilai tipe DocumentTermMatrix . Dalam hasil cetak, kita melihat bahwa dimensi adalah 3 dokumen dalam 4 istilah, yang meliputi kata Document dan angka 1 , 2 , 3 . Untuk penggunaan lebih lanjut dari model, kita membutuhkan matriks dalam representasi tradisional. Kita bisa mendapatkannya menggunakan metode 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 

Opsi ini diwakili oleh tipe SparseMatrixCSC , yang ekonomis dalam merepresentasikan matriks yang sangat jarang, tetapi hanya ada sejumlah perpustakaan yang mendukungnya. Masalah ukuran matriks dokumen istilah adalah karena fakta bahwa jumlah istilah tumbuh sangat cepat dengan jumlah dokumen yang diproses. Jika Anda tidak melakukan pra-proses dokumen, maka benar-benar semua kata dengan semua bentuk kata, angka, tanggal akan jatuh ke dalam matriks ini. Sekalipun jumlah bentuk kata dikurangi karena reduksi ke bentuk utama, jumlah batang yang tersisa akan menjadi urutan ribuan - puluhan ribu. Artinya, dimensi penuh dari matriks dokumen istilah ditentukan oleh total produk dari jumlah ini dengan jumlah dokumen yang diproses. Matriks lengkap membutuhkan penyimpanan tidak hanya unit tetapi juga nol, namun lebih mudah digunakan daripada SparseMatrixCSC . Anda bisa mendapatkannya dengan metode lain dtm(..., :dense) atau dengan mengubah matriks jarang menjadi yang penuh menggunakan metode collect() :


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

Jika Anda mencetak array istilah, maka di setiap baris mudah untuk melihat komposisi asli dokumen (urutan asli ketentuan tidak diperhitungkan).


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

Matriks dokumen istilah untuk model frekuensi dapat diperoleh dengan menggunakan metode tf() dan 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 

Sangat mudah untuk melihat signifikansi persyaratan untuk masing-masing dokumen. Dua dokumen pertama berisi dua istilah. Yang terakhir adalah tiga. Jadi berat badan mereka berkurang.


Dan untuk metode TF-IDF dan 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 

Dan dalam model ini mudah untuk melihat bahwa istilah Document , yang ditemukan di semua dokumen, memiliki nilai 0. Tetapi istilah 3 dalam dokumen ketiga memiliki bobot lebih dari 1 dalam dokumen yang sama, karena 1 juga ditemukan dalam dokumen pertama .


Matriks yang dihasilkan sangat mudah digunakan, misalnya, untuk menyelesaikan masalah pengelompokan dokumen. Untuk melakukan ini, Anda akan memerlukan paket Clustering . Kami menggunakan algoritma pengelompokan k-means paling sederhana, yang perlu menentukan jumlah kluster yang diinginkan. Kami membagi tiga dokumen kami menjadi dua kelompok. Matriks input untuk kmeans adalah matriks fitur, di mana baris mewakili fitur dan kolom mewakili pola. Oleh karena itu, matriks yang diperoleh di atas harus diubah.


 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 

Sebagai hasilnya, kita melihat bahwa cluster pertama berisi satu dokumen, cluster nomor 2 berisi dua dokumen. Selain itu, matriks yang berisi pusat-pusat cluster R.centers jelas menunjukkan bahwa kolom pertama "tertarik" dengan istilah 2 . Kolom kedua ditentukan oleh keberadaan istilah 1 dan 3 .


Paket Clustering.jl berisi seperangkat algoritma pengelompokan yang khas, di antaranya: K-means, K-medoid, Propagasi Afinitas, pengelompokan spasial berbasis kepadatan aplikasi dengan kebisingan (DBSCAN), Markov Clustering Algorithm (MCL), Fuzzy C-Means Clustering, Clustering Hirarkis (Tunggal, Rata-Rata, Lengkap, Linkage Ward). Tetapi analisis penerapannya berada di luar cakupan artikel ini.


Paket TextAnalysis.jl saat ini sedang dalam pengembangan aktif, sehingga beberapa fungsi hanya akan tersedia ketika menginstal paket langsung dari repositori git. Ini tidak sulit untuk dilakukan, tetapi hanya dapat disarankan bagi mereka yang tidak berencana untuk menempatkan solusi dalam waktu dekat:


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

Namun, Anda tidak boleh mengabaikan fungsi-fungsi ini dalam ulasan. Karena itu, kami mempertimbangkannya juga.


Salah satu perbaikannya adalah penggunaan fungsi peringkat Okapi BM25 . Mirip dengan model tf sebelumnya. tf_idf , kami menggunakan metode bm_25(m) . Penggunaan matriks yang dihasilkan mirip dengan kasus sebelumnya.


Analisis nada suara teks dapat dilakukan dengan menggunakan metode:


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

Selain itu, doc adalah salah satu dari jenis dokumen di atas. handle_unknown - berfungsi untuk memproses kata-kata yang tidak dikenal. Analisis nada suara diimplementasikan menggunakan paket Flux.jl berdasarkan paket IMDB. Nilai kembali dalam kisaran 0 hingga 1.


Generalisasi dokumen dapat diimplementasikan menggunakan metode summarize(d, ns) . Argumen pertama adalah dokumen. Yang kedua adalah ns= jumlah kalimat pada akhirnya.


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

Komponen yang sangat penting dari setiap pustaka analisis teks adalah parser sintaksis yang saat ini sedang dikembangkan, yang membedakan bagian-bagian ucapan - POS (bagian ucapan). Ada beberapa opsi untuk menggunakannya. Untuk detailnya, lihat Bagian Penandaan Pidato.
. Tagging disebut karena untuk setiap kata dalam teks sumber, tag dibentuk yang berarti bagian dari pidato.


Dua opsi untuk implementasi sedang dikembangkan. Yang pertama adalah Algoritma Perceptron Rata-Rata. Yang kedua didasarkan pada penggunaan arsitektur jaringan saraf LSTMs, CNN dan metode CRF. Berikut adalah contoh markup kalimat sederhana.


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

Daftar singkatan yang berarti bagian dari pidato diambil dari Penn Treebank . Khususnya, DT - Determiner, NN - Noun, singular atau massa, VBZ - Verb, hadir tunggal ke 3 orang, Verb, past participle, IN - Preposition atau konjungsi bawahan, NNP - Noper Proper, singular.


Hasil markup ini juga dapat digunakan sebagai fitur tambahan untuk klasifikasi dokumen.


Metode Pengurangan Dimensi


TextAnalysis menyediakan dua opsi untuk mengurangi dimensionalitas dengan mendefinisikan istilah dependen. Analisis semantik laten ini - LSA dan penempatan Dirichlet laten - LDA.


Tugas utama LSA adalah untuk mendapatkan dekomposisi dari term-document matrix (menggunakan TF-IDF) menjadi 3 matriks, produk yang kira-kira sesuai dengan aslinya.


 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 . Contoh:


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


Kesimpulan


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


Referensi


Source: https://habr.com/ru/post/id475922/


All Articles