
يعد تحليل النصوص ومعالجتها بلغة طبيعية مهمة موضعية باستمرار تم حلها وسيتم حلها وسيتم حلها بجميع الطرق المتاحة. أود اليوم أن أتحدث عن أدوات الحل لحل هذه المشكلة ، أي بلغة جوليا. بالطبع ، نظرًا لشباب اللغة ، لا توجد أدوات تحليل متطورة مثل ، على سبيل المثال ، Stanford CoreNLP ، و Apache OpenNLP ، GATE ، وما إلى ذلك ، على سبيل المثال ، للغة Java. ومع ذلك ، يمكن استخدام المكتبات التي تم تطويرها بالفعل في حل المشكلات النموذجية ويمكن التوصية بها كنقطة دخول للطلاب المهتمين بمجال معالجة النصوص. كما أن بساطة جوليا النحوية وأدواتها الرياضية المتقدمة تجعل من السهل الانغماس في مهام تجميع النصوص وتصنيفها.
الغرض من هذه المقالة هو مراجعة أدوات معالجة النصوص 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 ويتم تخزينه في ذاكرة الوصول العشوائي. توفر بنية 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 - مستند مقدم كمجموعة من n-grams في تمثيل UTF8 ، أي تسلسل من أحرف
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"))
يمكن أيضًا إنشاء مستند ببساطة باستخدام مُنشئ المستند العام ، وستجد المكتبة التطبيق المناسب للوثيقة.
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)
أصدر تحذيرًا بأن النص قد تمت استعادته تقريبًا ، لأن TokenDocument
لا يخزن محددات الكلمات. مكّن 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> 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-and-UTF-8 للحصول على التفاصيل. ولكن هنا مثال بسيط. لنقم بتعيين خط بأحرف UNICODE رياضية مفصولة عن x و y بمسافات:
julia> s = "\u2200 x \u2203 y" "∀ x ∃ y" julia> length(s)
الآن دعونا نلقي نظرة على المؤشرات:
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 شاملة ، أدت إلى خطأ. وفهرس 4 فقط أنتجت مسافة ، كالحرف التالي في السلسلة. ومع ذلك ، لتحديد حدود الأحرف بواسطة فهارس في سلسلة ، هناك وظائف مفيدة هي prevind
(الفهرس السابق) ، nextind
(الفهرس التالي) و thisind
(هذا الفهرس). على سبيل المثال ، لمعرفة الفجوة الموجودة أعلاه ، اسأل أين هي حدود الحدود السابقة:
julia> prevind(s, 4) 1
لقد حصلنا على الفهرس 1 كبداية للرمز ∀
.
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!
prepare!(sd, strip_articles| strip_numbers| strip_html_tags)
المقالات والأرقام وعلامات 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) " "
هيئة الوثيقة
يتم فهم corpus على أنه مجموعة من المستندات التي سيتم معالجتها وفقًا لنفس القواعد. تنفذ حزمة TextAnalysis تكوين مصفوفة وثيقة المصطلح . ولإنشاءه ، نحتاج إلى الحصول على مجموعة كاملة من المستندات على الفور. في مثال بسيط للمستندات:
D1 = "I like databases"
D2 = "I hate databases"
تبدو هذه المصفوفة:
يتم تمثيل الأعمدة بكلمات المستندات ، والصفوف هي معرفات (أو فهارس) المستندات. وفقًا لذلك ، ستكون الخلية 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 - تردد المدى - تردد الوثيقة العكسية) هو المهم.
خذ أبسط مثال مع ثلاثة مستندات تحتوي على مصطلحات Document
، 1
، 2
، 3
.
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
. في النتيجة المطبوعة ، نرى أن البعد هو 3 مستندات في 4 مصطلحات ، والتي تتضمن كلمة Document
والأرقام 1
، 2
، 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_idf()
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 و 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
في المستند نفسه ، حيث يوجد 1
في المستند الأول أيضًا .
المصفوفات الناتجة سهلة الاستخدام للغاية ، على سبيل المثال ، لحل مشكلة تجميع الوثائق. للقيام بذلك ، سوف تحتاج إلى حزمة التجميع . نحن نستخدم أبسط خوارزمية التجميع 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 على وثيقتين. علاوة على ذلك ، توضح المصفوفة التي تحتوي على مراكز مجموعات R.centers
بوضوح أن العمود الأول "ينجذب" بالمصطلح 2
. يتم تحديد العمود الثاني من خلال وجود المصطلحين 1
و 3
.
تحتوي الحزمة Clustering.jl
على مجموعة نموذجية من خوارزميات التجميع ، من بينها: K-mean و K-medoids و Affinity Propagation و Clustering-based Clustering للتطبيقات ذات الضوضاء (DBSCAN) و Markov Clustering Algorithm (MCL) و Fuzzy C-Means Clustering و تجميع هرمي (مفرد ، متوسط ، كامل ، ارتباط وارد). لكن تحليل مدى قابليتها للتطبيق يتجاوز نطاق هذه المقالة.
حزمة 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
- وظيفة لمعالجة الكلمات غير المعروفة. يتم تطبيق تحليل الدرجة اللونية باستخدام حزمة Flux.jl بناءً على حزمة IMDB. قيمة الإرجاع هي في النطاق من 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."
يعد المحلل اللغوي الذي يتم تطويره حاليًا مكونًا مهمًا جدًا لأي مكتبة تحليل نص ، وهو يميز أجزاء من الكلام - جزء من الكلام (جزء من الكلام). هناك العديد من الخيارات لاستخدامها. لمزيد من التفاصيل ، انظر أجزاء من تعليم الكلام.
. تسمى Tagging
لأن كل كلمة في النص المصدر ، يتم تشكيل علامة تعني جزءًا من الكلام.
هناك خياران للتنفيذ قيد التطوير. الأول هو متوسط الخوارزمية Perceptron. ويستند الثاني على استخدام بنية الشبكة العصبية LSTMs ، 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" "."
قائمة الاختصارات التي تعني جزءًا من الكلام مأخوذة من Penn Treebank . على وجه الخصوص ، DT - Determiner ، NN - Noun ، المفرد أو الكتلة ، VBZ - Verb ، الشخص الثالث المضارع ، الفعل ، النعت الماضي ، 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.0 … 0.0 0.0; 0.0 0.0 … 0.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
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 , .
استنتاج
, , 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, .
مراجع