مقدمة
هناك العديد من البرامج التعليمية على الإنترنت التي تشرح كيفية عمل LDA (Latent Dirichlet Allocation) وكيفية وضعها موضع التنفيذ. غالبًا ما يتم عرض أمثلة على تدريب LDA على مجموعات البيانات "النموذجية" ، مثل "مجموعة بيانات مجموعات الأخبار 20" ، والتي تتوفر على sklearn.
ميزة التدريب على مثال مجموعات البيانات "النموذجية" هي أن البيانات الموجودة دائمًا في النظام ومكدسة بشكل ملائم في مكان واحد. عند تدريب نماذج الإنتاج ، تكون البيانات التي يتم الحصول عليها مباشرة من مصادر حقيقية عادة ما تكون عكس ذلك:
- الكثير من الانبعاثات.
- ترميز غير صحيح (إن وجد).
- اختلالات طبقية قوية للغاية والتوزيعات القبيحة لأي معلمات مجموعة بيانات.
- بالنسبة للنصوص ، هذه هي: الأخطاء النحوية ، وعدد كبير من الكلمات النادرة والفريدة ، والتعددية اللغوية.
- طريقة غير ملائمة لتخزين البيانات (تنسيقات مختلفة أو نادرة ، الحاجة إلى التحليل)
تاريخياً ، أحاول أن أتعلم من الأمثلة القريبة قدر الإمكان من واقع الإنتاج الواقعي لأنه بهذه الطريقة يمكن للمرء أن يشعر بشكل كامل بمجالات المشاكل الخاصة بنوع معين من المهام. لذلك كان الأمر مع LDA ، وفي هذه المقالة أريد أن أشارك تجربتي - كيفية تشغيل LDA من البداية ، على بيانات أولية تمامًا. سيتم تخصيص جزء من المقالة للحصول على هذه البيانات ، بحيث يصبح المثال "حالة هندسية" كاملة.
نمذجة الموضوع و LDA.
للبدء ، ضع في اعتبارك ما يفعله LDA بشكل عام والمهام التي يستخدمها.
في أغلب الأحيان ، يتم استخدام LDA في مهام نمذجة الموضوعات. مثل هذه المهام تعني مهام تجميع أو تصنيف النصوص - بحيث يحتوي كل فصل أو مجموعة على نصوص ذات مواضيع متشابهة.
من أجل تطبيق LDA على مجموعة بيانات النصوص (المشار إليها فيما يلي باسم نص النص) ، من الضروري تحويل النص إلى مصفوفة وثيقة - مصطلح.
مصفوفة وثيقة مصطلح هي مصفوفة لها حجم أين
N هو عدد المستندات في الحالة ، و W هو حجم قاموس الحالة ، أي عدد الكلمات (الفريدة) الموجودة في جسمنا. في الصف الأول ، يكون العمود j-th من المصفوفة رقمًا - عدد المرات التي تم فيها العثور على الكلمة j في النص الأول.
ينشئ LDA ، لمصفوفة وثيقة مصطلح معينة و T لعدد محدد مسبقًا من المواضيع ، توزيعين:
- توزيع المواضيع في النصوص (في الممارسة العملية ، مقدمة من مصفوفة الحجم )
- توزيع الكلمات حسب الموضوع (مصفوفة الحجم )
قيم خلايا هذه المصفوفات هي ، على التوالي ، احتمالات أن يكون هذا الموضوع واردًا في هذا المستند (أو نسبة الموضوع في المستند ، إذا اعتبرنا الوثيقة مزيجًا من الموضوعات المختلفة) لمصفوفة "توزيع المواضيع في النصوص".
بالنسبة لمصفوفة "توزيع الكلمات حسب المواضيع" ، فإن القيم هي احتمال تلبية الكلمة j في النص مع الموضوع i ، من الناحية النوعية ، يمكننا اعتبار هذه الأرقام بمثابة معاملات تصف كيف تكون هذه الكلمة نموذجية لهذا الموضوع.
يجب أن يقال أن موضوع الموضوع ليس تعريفًا "يوميًا" لهذه الكلمة. يخصص LDA T لأولئك ، ولكن أي نوع من الموضوعات هذه وما إذا كانت تتوافق مع أي مواضيع نصية معروفة ، مثل: "الرياضة" ، "العلوم" ، "السياسة" - غير معروفة. في هذه الحالة ، من الأنسب التحدث عن الموضوع كنوع من الكيان المجرد ، والذي يتم تعريفه بواسطة سطر في مصفوفة توزيع الكلمات حسب الموضوع ومع بعض الاحتمالات يتوافق مع هذا النص ، إذا كنت تستطيع تخيله كعائلة من مجموعات مميزة من الكلمات تتقابل مع الاحتمالات المقابلة (من الجدول) في مجموعة معينة من النصوص.
إذا كنت مهتمًا بالدراسة بمزيد من التفصيل و "في الصيغ" ، كيف يتم تدريب LDA وكيف يعمل ، فإليك بعض المواد (التي استخدمها المؤلف):
نحصل على بيانات جامحة
من أجل "العمل المخبري" ، نحتاج إلى مجموعة بيانات مخصصة مع عيوبها وميزاتها. يمكنك الحصول عليه في أماكن مختلفة: تنزيل المراجعات من Kinopoisk ، مقالات Wikipedia ، الأخبار من بعض بوابة الأخبار ، سنتخذ خيارًا أكثر تطرفًا قليلاً - المنشورات من مجتمعات VKontakte.
سنفعل هذا مثل هذا:
- نختار بعض مستخدم VK.
- نحصل على قائمة بجميع أصدقائه.
- لكل صديق ، نأخذ كل مجتمعه.
- لكل مجتمع من كل صديق ، نقوم بضخ أول عدد من مشاركات المجتمع n (n = 100) ودمجها في محتوى نص مجتمع واحد.
أدوات ومقالات
لتنزيل المشاركات ، سنستخدم وحدة vk للعمل مع VKontakte API لـ Python. واحدة من أكثر اللحظات تعقيدًا عند كتابة تطبيق باستخدام VKontakte API هي إذن ، لحسن الحظ ، الرمز الذي يؤدي هذا العمل مكتوب بالفعل وهو في المجال العام ، باستثناء vk ، استخدمت وحدة ترخيص صغيرة - vkauth.
روابط إلى الوحدات والمقالات المستخدمة لدراسة VKontakte API:
كتابة رمز
وهكذا ، باستخدام vkauth ، قم بتسجيل الدخول:
في هذه العملية ، تمت كتابة وحدة صغيرة تحتوي على جميع الوظائف اللازمة لتنزيل المحتوى بالتنسيق المناسب ، وهي مدرجة أدناه ، فلنستعرضها:
def get_friends_ids(api, user_id): ''' For a given API object and user_id returns a list of all his friends ids. ''' friends = api.friends.get(user_id=user_id, v = '5.68') friends_ids = friends['items'] return friends_ids def get_user_groups(api, user_id, moder=True, only_open=True): ''' For a given API user_id returns list of all groups he subscribed to. Flag model to get only those groups where user is a moderator or an admin) Flag only_open to get only public(open) groups. ''' kwargs = {'user_id' : user_id, 'v' : '5.68' } if moder == True: kwargs['filter'] = 'moder' if only_open == True: kwargs['extended'] = 1 kwargs['fields'] = ['is_closed'] groups = api.groups.get(**kwargs) groups_refined = [] for group in groups['items']: cond_check = (only_open and group['is_closed'] == 0) or not only_open if cond_check: refined = {} refined['id'] = group['id'] * (-1) refined['name'] = group['name'] groups_refined.append(refined) return groups_refined def get_n_posts_text(api, group_id, n_posts=50): ''' For a given api and group_id returns first n_posts concatenated as one text. ''' wall_contents = api.wall.get(owner_id = group_id, count=n_posts, v = '5.68') wall_contents = wall_contents['items'] text = '' for post in wall_contents: text += post['text'] + ' ' return text
خط الأنابيب النهائي هو كما يلي:
فشل
بشكل عام ، عملية تنزيل البيانات ليست صعبة في حد ذاتها ؛ يجب الانتباه إلى نقطتين فقط:
- في بعض الأحيان ، بسبب خصوصية بعض المجتمعات ، ستتلقى أخطاء الوصول ، وأحيانًا سيتم حل أخطاء أخرى عن طريق تثبيت المحاولة ، باستثناء المكان الصحيح.
- لدى VK حد لعدد الطلبات في الثانية.
عند إجراء عدد كبير من الطلبات ، على سبيل المثال في حلقة ، سنكتشف أيضًا الأخطاء. يمكن حل هذه المشكلة بعدة طرق:
- بغباء وبصراحة: التمسك بالنوم (بعض) كل 3 طلبات. يتم ذلك في سطر واحد ويبطئ بشكل كبير من التفريغ ، في المواقف التي لا تكون فيها أحجام البيانات كبيرة ، ولا يوجد وقت لطرق أكثر تعقيدًا - هذا مقبول تمامًا. (تم تنفيذه في هذه المقالة)
- فهم عمل طلبات الاستطلاع الطويل https://vk.com/dev/using_longpoll
في هذه الورقة ، تم اختيار طريقة بسيطة وبطيئة ، في المستقبل ، ربما سأكتب مقالة صغيرة حول طرق تجاوز أو تخفيف القيود على عدد الطلبات في الثانية.
الملخص
مع وجود مستخدم "البعض" الأساسي لديه 150 صديقًا تقريبًا ، تمكنوا من الحصول على 4679 نصًا - كل منهم يميز مجتمع VK معين. تختلف النصوص اختلافًا كبيرًا في حجمها ويتم كتابتها بالعديد من اللغات - بعضها غير مناسب لأغراضنا ، ولكننا سنتحدث عن هذا قليلاً.
الجسم الرئيسي

دعنا نذهب من خلال جميع كتل خط الأنابيب - أولاً ، على (إلزامي) الإلزامي ، ثم على الباقي - فهي فقط ذات أهمية قصوى.
العد التنازلي
قبل تدريس LDA ، نحتاج إلى تقديم مستنداتنا في شكل مصفوفة مستندات Term. يتضمن هذا عادةً عمليات مثل:
- إزالة المواضع / الأرقام / الرموز غير الضرورية.
- الرمز (العرض كقائمة كلمات)
- عد الكلمات وتجميع مصفوفة وثيقة الحرارية.
يتم تنفيذ جميع هذه الإجراءات في sklearn بشكل ملائم في إطار كيان برنامجي واحد - sklearn.feature_extraction.text.CountVectorizer.
رابط التوثيق
كل ما عليك فعله هو:
count_vect = CountVectorizer(input='filename', stop_words=stopwords, vocabulary=voc) dataset = count_vect.fit_transform(train_names)
Lda
وبالمثل مع CountVectorizer ، يتم تنفيذ LDA بشكل مثالي في Sklearn وإطارات العمل الأخرى ، وبالتالي ، ليس هناك فائدة كبيرة في تكريس الكثير من المساحة مباشرة لعمليات تنفيذها ، في مقالتنا العملية البحتة.
رابط التوثيق
كل ما تحتاجه لبدء LDA هو:
المعالجة المسبقة
إذا أخذنا نصوصنا فورًا بعد تنزيلها وقمنا بتحويلها إلى مصفوفة وثيقة Term باستخدام CountVectorizer ، مع الرمز المميز الافتراضي المدمج ، فسوف نحصل على مصفوفة بحجم 4679x769801 (على البيانات التي أستخدمها).
سيكون حجم قاموسنا 769801. حتى إذا افترضنا أن معظم الكلمات مفيدة ، فلا يزال من غير المحتمل أن نحصل على LDA جيد ، شيء مثل "لعنات الأبعاد" في انتظارنا ، ناهيك عن ذلك بالنسبة لأي جهاز كمبيوتر تقريبًا ، سنقوم فقط بسد جميع ذاكرة الوصول العشوائي. في الواقع ، معظم هذه الكلمات غير مفهومة تمامًا. الغالبية العظمى منهم هم:
- الرموز والشخصيات والأرقام.
- كلمات فريدة أو نادرة جدًا (على سبيل المثال ، كلمات بولندية من مجموعة بها ميمات بولندية ، أو كلمات مكتوبة بشكل غير صحيح أو باللغة الألبانية).
- أجزاء كثيرة جدًا من الكلام (مثل حروف الجر والضمائر).
بالإضافة إلى ذلك ، تتخصص العديد من المجموعات في VK حصريًا في الصور - لا توجد مشاركات نصية تقريبًا هناك - النصوص المقابلة لها تتدهور ، في مصفوفة المستندات الحرارية سوف تعطينا تقريبًا أسطرًا تمامًا.
وهكذا ، دعونا فرز كل شيء!
نقوم بترميز جميع النصوص ، وإزالة علامات الترقيم والأرقام منها ، وإلقاء نظرة على الرسم البياني لتوزيع النصوص بعدد الكلمات:

نزيل جميع النصوص التي يقل حجمها عن 100 كلمة (يوجد 525 منها)
الآن القاموس:
إزالة جميع الرموز (الكلمات) التي ليست حروفًا ، في إطار مهمتنا - هذا أمر مقبول تمامًا. يقوم CountVectorizer بذلك من تلقاء نفسه ، حتى لو لم يكن كذلك ، أعتقد أنه لا توجد حاجة لإعطاء أمثلة هنا (هم في النسخة الكاملة من كود المقال).
أحد أكثر الإجراءات شيوعًا لتقليل حجم القاموس هو إزالة ما يسمى بكلمات التوقف (كلمات الإيقاف) - وهي الكلمات التي لا تحمل حملًا دلاليًا و / أو لا تحتوي على ألوان موضوعية (في حالتنا ، نمذجة الموضوع). مثل هذه الكلمات في حالتنا ، على سبيل المثال:
- الضمائر وحروف الجر.
- مقالات - أ.
- الكلمات الشائعة: "كن" ، "جيد" ، "ربما" ، إلخ.
شكلت وحدة nltk قوائم كلمات الإيقاف باللغتين الروسية والإنجليزية ، لكنها ضعيفة إلى حد ما. على الإنترنت ، يمكنك أيضًا العثور على قوائم كلمات الإيقاف لأي لغة وإضافتها إلى تلك الموجودة في nltk. لذا سنفعل. خذ كلمات إيقاف إضافية من هنا:
من الناحية العملية ، عند حل مشكلات معينة ، يتم تعديل قوائم كلمات الإيقاف وتكميلها تدريجيًا أثناء تدريب النماذج ، حيث توجد كلمات "غير متناسقة" محددة لكل مجموعة بيانات ومشكلة. سنختار أيضًا كلمات إيقاف مخصصة بعد تدريب الجيل الأول من LDA.
في حد ذاته ، يتم تضمين إجراء إزالة كلمات الإيقاف في CountVectorizer - نحن بحاجة فقط إلى قائمة بها.
هل ما فعلناه يكفي؟

لا تزال معظم الكلمات الموجودة في قاموسنا غير مفيدة للغاية لتعلم LDA عليها وليست في قائمة كلمات الإيقاف. لذلك ، نطبق طريقة تصفية أخرى على بياناتنا.
أين
t كلمة من القاموس.
د - حالة (نصوص كثيرة)
د هو أحد نصوص الجسم.
نحن نحسب جيش الدفاع الإسرائيلي لكل كلماتنا ، ونقطع الكلمات بأكبر معرف (نادر جدًا) وبأصغر (كلمات منتشرة).
تم الحصول عليها بعد الإجراءات المذكورة أعلاه وهي مناسبة تمامًا لتدريب LDA ، ولكننا سنبذل المزيد من الجهد - نفس الكلمات غالبًا ما توجد في مجموعة البيانات الخاصة بنا ، ولكن في حالات مختلفة. للجذع ، تم استخدام pymystem3 .
بعد تطبيق التصفية أعلاه ، انخفض حجم القاموس من 769801 إلى
13611 وبالفعل مع هذه البيانات ، يمكنك الحصول على نموذج LDA بجودة مقبولة.
اختبار وتطبيق وضبط LDA
الآن بعد أن أصبح لدينا مجموعة البيانات والمعالجة المسبقة والنماذج التي قمنا بتدريبها على مجموعة البيانات المجهزة ، سيكون من الجيد التحقق من كفاية نماذجنا ، بالإضافة إلى إنشاء بعض التطبيقات لها.
كتطبيق ، بالنسبة للمبتدئين ، ضع في اعتبارك مهمة إنشاء كلمات رئيسية لنص معين. يمكنك القيام بذلك بطريقة بسيطة إلى حد ما كما يلي:
- نحصل من LDA على توزيع المواضيع لهذا النص.
- اختر n (على سبيل المثال ، n = 2) من أكثر المواضيع وضوحًا.
- لكل موضوع ، اختر m (على سبيل المثال m = 3) الكلمات الأكثر تميزًا.
- لدينا مجموعة من الكلمات n * m تميز نصًا معينًا.
سنكتب فئة واجهة بسيطة ستنفذ هذه الطريقة لتوليد الكلمات الرئيسية:
نطبق طريقتنا على العديد من النصوص ونرى ما يحدث:
المجتمع : وكالة سفريات "ألوان العالم"
الكلمات الرئيسية: ["صورة" ، "اجتماعي" ، "سفر" ، "مجتمع" ، "سفر" ، "يورو" ، "سكن" ، "سعر" ، "بولندا" ، "مغادرة"]
المجتمع: الغذاء متحركة
الكلمات الرئيسية: ["زبدة" ، "ش" ، "ملح" ، "كمبيوتر" ، "عجين" ، "طبخ" ، "بصل" ، "فلفل" ، "سكر" ، "غرام"]
النتائج أعلاه ليست "اختيار الكرز" وتبدو مناسبة تمامًا. في الواقع ، هذه هي النتائج من نموذج تم تكوينه بالفعل. أسفرت LDAs الأولى التي تم تدريبها كجزء من هذه المقالة عن نتائج أسوأ بكثير ، من بين الكلمات الرئيسية التي يمكنك رؤيتها غالبًا ، على سبيل المثال:
- المكونات المركبة لعناوين الويب: www ، http ، ru ، com ...
- الكلمات الشائعة.
- الوحدات: سم ، متر ، كم ...
تم ضبط (ضبط) النموذج على النحو التالي:
- لكل موضوع ، حدد n (n = 5) أكثر الكلمات المميزة.
- نعتبرها IDF ، وفقا لحالة التدريب.
- نحضر الكلمات الرئيسية 5-10٪ من الأكثر انتشارًا.
يجب إجراء هذا "التنظيف" بعناية ، مع معاينة تلك الـ 10٪ من الكلمات مسبقًا. بدلاً من ذلك ، يجب اختيار المرشحين للحذف بهذه الطريقة ، ثم يجب تحديد الكلمات التي يجب حذفها يدويًا منهم.
في مكان ما من الجيل 2-3 من النماذج ، وبطريقة مماثلة لاختيار كلمات الإيقاف ، لأعلى 5٪ من توزيعات الكلمة الأعلى المنتشرة ، نحصل على:
["أي" ، "تمامًا" ، "صحيح" ، "سهل" ، "التالي" ، "إنترنت" ، "صغير" ، "طريق" ، "صعب" ، "مزاج" ، "كثيرًا" ، "ضبط" ، " الخيار "،" الاسم "،" الكلام "،" البرنامج "،" المنافسة "،" الموسيقى "،" الهدف "،" الفيلم "،" السعر "،" اللعبة "،" النظام "،" اللعب "،" الشركة " ، "لطيف"]
المزيد من التطبيقات
أول ما يتبادر إلى ذهني على وجه التحديد هو استخدام توزيع الموضوعات في النص كـ "تضمين" للنصوص ، في هذا التفسير يمكنك تطبيق التصور أو تجميع الخوارزميات عليها ، والبحث عن المجموعات المواضيعية "الفعالة" النهائية بهذه الطريقة.
لنفعل هذا:
term_doc_matrix = count_vect.transform(names) embeddings = lda.transform(term_doc_matrix) kmeans = KMeans(n_clusters=30) clust_labels = kmeans.fit_predict(embeddings) clust_centers = kmeans.cluster_centers_ embeddings_to_tsne = np.concatenate((embeddings,clust_centers), axis=0) tSNE = TSNE(n_components=2, perplexity=15) tsne_embeddings = tSNE.fit_transform(embeddings_to_tsne) tsne_embeddings, centroids_embeddings = np.split(tsne_embeddings, [len(clust_labels)], axis=0)
عند الإخراج نحصل على الصورة التالية:

الصلبان هي مراكز جاذبية العناقيد (cenroids).
في صورة tSNE للتضمين ، يمكن ملاحظة أن المجموعات المختارة باستخدام KMeans تكون مترابطة تمامًا وغالبًا مجموعات منفصلة مكانيًا.
كل شيء آخر متروك لكم.
رابط لجميع التعليمات البرمجية: https://gitlab.com/Mozes/VK_LDA