كيفية إنشاء تطبيق معالجة اللغة الطبيعية

نشرت: 2022-03-11

أصبحت معالجة اللغة الطبيعية - وهي تقنية تسمح للتطبيقات البرمجية بمعالجة لغة الإنسان - موجودة في كل مكان إلى حد ما خلال السنوات القليلة الماضية.

أصبح بحث Google قادرًا بشكل متزايد على الإجابة عن الأسئلة التي تبدو طبيعية ، ويستطيع Siri من Apple فهم مجموعة متنوعة من الأسئلة ، وتستخدم المزيد والمزيد من الشركات (بشكل معقول) الدردشة الذكية وروبوتات الهاتف للتواصل مع العملاء. ولكن كيف يعمل هذا البرنامج الذي يبدو "ذكيًا" حقًا؟

في هذه المقالة ، ستتعرف على التكنولوجيا التي تجعل هذه التطبيقات علامة ، وستتعلم كيفية تطوير برنامج معالجة اللغة الطبيعية الخاص بك.

ستوجهك المقالة خلال عملية المثال لبناء محلل ملاءمة الأخبار. تخيل أن لديك محفظة أسهم ، وتريد أن يقوم أحد التطبيقات بالزحف تلقائيًا عبر مواقع الأخبار الشائعة وتحديد المقالات ذات الصلة بمحفظة أعمالك. على سبيل المثال ، إذا كانت محفظة الأوراق المالية الخاصة بك تتضمن شركات مثل Microsoft و BlackStone و Luxottica ، فقد ترغب في الاطلاع على المقالات التي تذكر هذه الشركات الثلاث.

الشروع في العمل مع مكتبة ستانفورد البرمجة اللغوية العصبية

تطبيقات معالجة اللغة الطبيعية ، مثل أي تطبيقات أخرى للتعلم الآلي ، مبنية على عدد من الخوارزميات الصغيرة نسبيًا والبسيطة والبديهية التي تعمل جنبًا إلى جنب. غالبًا ما يكون من المنطقي استخدام مكتبة خارجية حيث تم بالفعل تنفيذ كل هذه الخوارزميات ودمجها.

على سبيل المثال ، سوف نستخدم مكتبة Stanford NLP ، وهي مكتبة معالجة قوية للغة الطبيعية قائمة على Java والتي تأتي مع دعم للعديد من اللغات.

إحدى الخوارزميات المعينة من هذه المكتبة التي نهتم بها هي أداة تمييز جزء الكلام (POS). يتم استخدام أداة تحديد نقاط البيع لتعيين أجزاء من الكلام تلقائيًا لكل كلمة في جزء من النص. تصنف أداة تمييز نقاط البيع هذه الكلمات في النص بناءً على الميزات المعجمية وتحللها فيما يتعلق بالكلمات الأخرى من حولها.

إن الآليات الدقيقة لخوارزمية علامات نقاط البيع خارج نطاق هذه المقالة ، ولكن يمكنك معرفة المزيد عنها هنا.

للبدء ، سننشئ مشروع Java جديدًا (يمكنك استخدام IDE المفضل لديك) ونضيف مكتبة Stanford NLP إلى قائمة التبعيات. إذا كنت تستخدم Maven ، فأضفه ببساطة إلى ملف pom.xml الخاص بك:

 <dependency> <groupId>edu.stanford.nlp</groupId> <artifactId>stanford-corenlp</artifactId> <version>3.6.0</version> </dependency> <dependency> <groupId>edu.stanford.nlp</groupId> <artifactId>stanford-corenlp</artifactId> <version>3.6.0</version> <classifier>models</classifier> </dependency>

نظرًا لأن التطبيق سيحتاج إلى استخراج محتوى المقالة تلقائيًا من صفحة ويب ، فستحتاج إلى تحديد التبعيتين التاليتين أيضًا:

 <dependency> <groupId>de.l3s.boilerpipe</groupId> <artifactId>boilerpipe</artifactId> <version>1.1.0</version> </dependency>
 <dependency> <groupId>net.sourceforge.nekohtml</groupId> <artifactId>nekohtml</artifactId> <version>1.9.22</version> </dependency>

مع إضافة هذه التبعيات ، فأنت جاهز للمضي قدمًا.

كشط وتنظيف

سيتضمن الجزء الأول من المحلل الخاص بنا استرداد المقالات واستخراج محتواها من صفحات الويب.

عند استرداد المقالات من مصادر الأخبار ، عادةً ما تكون الصفحات مليئة بمعلومات غريبة (مقاطع الفيديو المضمنة ، والروابط الصادرة ، ومقاطع الفيديو ، والإعلانات ، وما إلى ذلك) والتي لا صلة لها بالمقالة نفسها. هذا هو المكان الذي تلعب فيه Boilerpipe.

Boilerpipe هي خوارزمية قوية وفعالة للغاية لإزالة "الفوضى" التي تحدد المحتوى الرئيسي لمقالة إخبارية من خلال تحليل كتل المحتوى المختلفة باستخدام ميزات مثل طول الجملة المتوسطة وأنواع العلامات المستخدمة في كتل المحتوى وكثافة الروابط. أثبتت خوارزمية boilerpipe أنها قادرة على المنافسة مع خوارزميات أخرى أكثر تكلفة من الناحية الحسابية ، مثل تلك التي تعتمد على رؤية الآلة. يمكنك معرفة المزيد في موقع المشروع الخاص بها.

تأتي مكتبة Boilerpipe مع دعم مدمج لكشط صفحات الويب. يمكنه جلب HTML من الويب واستخراج النص من HTML وتنظيف النص المستخرج. يمكنك تحديد دالة ، extractFromURL ، تأخذ عنوان URL وتستخدم Boilerpipe لإرجاع النص الأكثر صلة كسلسلة باستخدام ArticleExtractor لهذه المهمة:

 import java.net.URL; import de.l3s.boilerpipe.document.TextDocument; import de.l3s.boilerpipe.extractors.CommonExtractors; import de.l3s.boilerpipe.sax.BoilerpipeSAXInput; import de.l3s.boilerpipe.sax.HTMLDocument; import de.l3s.boilerpipe.sax.HTMLFetcher; public class BoilerPipeExtractor { public static String extractFromUrl(String userUrl) throws java.io.IOException, org.xml.sax.SAXException, de.l3s.boilerpipe.BoilerpipeProcessingException { final HTMLDocument htmlDoc = HTMLFetcher.fetch(new URL(userUrl)); final TextDocument doc = new BoilerpipeSAXInput(htmlDoc.toInputSource()).getTextDocument(); return CommonExtractors.ARTICLE_EXTRACTOR.getText(doc); } }

توفر مكتبة Boilerpipe أدوات استخلاص مختلفة بناءً على خوارزمية boilerpipe ، حيث تم تحسين ArticleExtractor خصيصًا للمقالات الإخبارية بتنسيق HTML. يركز ArticleExtractor بشكل خاص على علامات HTML المستخدمة في كل كتلة محتوى وكثافة الارتباط الصادر. هذا هو الأنسب لمهمتنا من برنامج DefaultExtractor الأسرع ولكن الأبسط.

الوظائف المدمجة تعتني بكل شيء بالنسبة لنا:

  • يحصل HTMLFetcher.fetch على مستند HTML
  • getTextDocument باستخراج المستند النصي
  • CommonExtractors.ARTICLE_EXTRACTOR.getText باستخراج النص ذي الصلة من المقالة باستخدام خوارزمية boilerpipe

يمكنك الآن تجربته بمثال على مقالة تتعلق بدمج عمالقة البصريات Essilor و Luxottica ، والتي يمكنك العثور عليها هنا. يمكنك تغذية عنوان URL هذا للوظيفة ومعرفة ما سيخرج.

أضف الكود التالي إلى وظيفتك الرئيسية:

 public class App { public static void main( String[] args ) throws java.io.IOException, org.xml.sax.SAXException, de.l3s.boilerpipe.BoilerpipeProcessingException { String urlString = "http://www.reuters.com/article/us-essilor-ma-luxottica-group-idUSKBN14Z110"; String text = BoilerPipeExtractor.extractFromUrl(urlString); System.out.println(text); } }

يجب أن ترى في مخرجاتك في النص الأساسي للمقالة ، بدون الإعلانات وعلامات HTML والروابط الصادرة. هذا هو مقتطف البداية مما حصلت عليه عندما قمت بتشغيل هذا:

 MILAN/PARIS Italy's Luxottica (LUX.MI) and France's Essilor (ESSI.PA) have agreed a 46 billion euro ($49 billion) merger to create a global eyewear powerhouse with annual revenue of more than 15 billion euros. The all-share deal is one of Europe's largest cross-border tie-ups and brings together Luxottica, the world's top spectacles maker with brands such as Ray-Ban and Oakley, with leading lens manufacturer Essilor. "Finally ... two products which are naturally complementary -- namely frames and lenses -- will be designed, manufactured and distributed under the same roof," Luxottica's 81-year-old founder Leonardo Del Vecchio said in a statement on Monday. Shares in Luxottica were up by 8.6 percent at 53.80 euros by 1405 GMT (9:05 am ET), with Essilor up 12.2 percent at 114.60 euros. The merger between the top players in the 95 billion eyewear market is aimed at helping the businesses to take full advantage of expected strong demand for prescription spectacles and sunglasses due to an aging global population and increasing awareness about eye care. Jefferies analysts estimate that the market is growing at between...

وهذا هو بالفعل نص المقال الرئيسي. من الصعب تخيل أن هذا أسهل بكثير في التنفيذ.

وضع علامات على أجزاء من الكلام

الآن بعد أن نجحت في استخراج نص المقالة الرئيسي ، يمكنك العمل على تحديد ما إذا كانت المقالة تشير إلى الشركات التي تهم المستخدم.

قد تميل إلى القيام ببساطة ببحث سلسلة أو تعبير عادي ، ولكن هناك العديد من العيوب لهذا الأسلوب.

بادئ ذي بدء ، قد يكون البحث عن سلسلة عرضة لإيجابيات خاطئة. قد يتم وضع علامة على المقالة التي تشير إلى Microsoft Excel على أنها تشير إلى Microsoft ، على سبيل المثال.

ثانيًا ، اعتمادًا على تكوين التعبير النمطي ، يمكن أن يؤدي البحث عن التعبير العادي إلى نتائج سلبية خاطئة. على سبيل المثال ، قد يتم تفويت المقالة التي تحتوي على العبارة "تجاوزت أرباح Luxottica ربع السنوية التوقعات" من خلال البحث بالتعبير العادي الذي يبحث عن "Luxottica" محاطًا بمسافات بيضاء.

أخيرًا ، إذا كنت مهتمًا بعدد كبير من الشركات وتعالج عددًا كبيرًا من المقالات ، فإن البحث في النص الكامل لكل شركة في محفظة المستخدم قد يكون مضيعة للوقت للغاية ، مما يؤدي إلى أداء غير مقبول.

تحتوي مكتبة CoreNLP في جامعة ستانفورد على العديد من الميزات القوية وتوفر طريقة لحل هذه المشكلات الثلاثة.

بالنسبة للمحلل الخاص بنا ، سنستخدم أداة تمييز أجزاء الكلام (POS). على وجه الخصوص ، يمكننا استخدام أداة تمييز نقاط البيع للعثور على جميع الأسماء المناسبة في المقالة ومقارنتها بمحفظة الأسهم المثيرة للاهتمام الخاصة بنا.

من خلال دمج تقنية البرمجة اللغوية العصبية (NLP) ، لا نقوم فقط بتحسين دقة أداة العلامات الخاصة بنا وتقليل الإيجابيات والسلبيات الخاطئة المذكورة أعلاه ، ولكننا أيضًا نقلل بشكل كبير مقدار النص الذي نحتاج إلى مقارنته بمحفظة الأسهم لدينا ، نظرًا لأن الأسماء المناسبة تتكون فقط من مجموعة فرعية صغيرة من النص الكامل للمقالة.

من خلال المعالجة المسبقة لمحفظتنا في بنية بيانات ذات تكلفة منخفضة لاستعلام العضوية ، يمكننا تقليل الوقت اللازم لتحليل مقالة بشكل كبير.

يوفر Stanford CoreNLP أداة تمييز مريحة للغاية تسمى MaxentTagger يمكنها توفير علامات نقاط البيع في بضعة أسطر من التعليمات البرمجية.

هنا تنفيذ بسيط:

 public class PortfolioNewsAnalyzer { private HashSet<String> portfolio; private static final String modelPath = "edu\\stanford\\nlp\\models\\pos-tagger\\english-left3words\\english-left3words-distsim.tagger"; private MaxentTagger tagger; public PortfolioNewsAnalyzer() { tagger = new MaxentTagger(modelPath); } public String tagPos(String input) { return tagger.tagString(input); }

تأخذ وظيفة tagger ، tagPos ، سلسلة كمدخلات وتخرج سلسلة تحتوي على الكلمات الموجودة في السلسلة الأصلية إلى جانب الجزء المقابل من الكلام. في وظيفتك الرئيسية ، قم بإنشاء مثيل PortfolioNewsAnalyzer وأدخل إخراج الكاشطة في وظيفة tagger وسترى شيئًا مثل هذا:

 MILAN/PARIS_NN Italy_NNP 's_POS Luxottica_NNP -LRB-_-LRB- LUX.MI_NNP -RRB-_-RRB- and_CC France_NNP 's_POS Essilor_NNP -LRB-_-LRB- ESSI.PA_NNP -RRB-_-RRB- have_VBP agreed_VBN a_DT 46_CD billion_CD euro_NN -LRB-_-LRB- $_$ 49_CD billion_CD -RRB-_-RRB- merger_NN to_TO create_VB a_DT global_JJ eyewear_NN powerhouse_NN with_IN annual_JJ revenue_NN of_IN more_JJR than_IN 15_CD billion_CD euros_NNS ._. The_DT all-share_JJ deal_NN is_VBZ one_CD of_IN Europe_NNP 's_POS largest_JJS cross-border_JJ tie-ups_NNS and_CC brings_VBZ together_RB Luxottica_NNP ,_, the_DT world_NN 's_POS top_JJ spectacles_NNS maker_NN with_IN brands_NNS such_JJ as_IN Ray-Ban_NNP and_CC Oakley_NNP ,_, with_IN leading_VBG lens_NN manufacturer_NN Essilor_NNP ._. ``_`` Finally_RB ..._: two_CD products_NNS which_WDT are_VBP naturally_RB complementary_JJ --_: namely_RB frames_NNS and_CC lenses_NNS --_: will_MD be_VB designed_VBN ,_, manufactured_VBN and_CC distributed_VBN under_IN the_DT same_JJ roof_NN ,_, ''_'' Luxottica_NNP 's_POS 81-year-old_JJ founder_NN Leonardo_NNP Del_NNP Vecchio_NNP said_VBD in_IN a_DT statement_NN on_IN Monday_NNP ._. Shares_NNS in_IN Luxottica_NNP were_VBD up_RB by_IN 8.6_CD percent_NN at_IN 53.80_CD euros_NNS by_IN 1405_CD GMT_NNP -LRB-_-LRB- 9:05_CD am_NN ET_NNP -RRB-_-RRB- ,_, with_IN Essilor_NNP up_IN 12.2_CD percent_NN at_IN 114.60_CD euros_NNS ._. The_DT merger_NN between_IN the_DT top_JJ players_NNS in_IN the_DT 95_CD billion_CD eyewear_NN market_NN is_VBZ aimed_VBN at_IN helping_VBG the_DT businesses_NNS to_TO take_VB full_JJ advantage_NN of_IN expected_VBN strong_JJ demand_NN for_IN prescription_NN spectacles_NNS and_CC sunglasses_NNS due_JJ to_TO an_DT aging_NN global_JJ population_NN and_CC increasing_VBG awareness_NN about_IN...

معالجة المخرجات ذات العلامات في مجموعة

حتى الآن ، قمنا ببناء وظائف لتنزيل مقالة إخبارية وتنظيفها ووضع علامات عليها. لكن ما زلنا بحاجة إلى تحديد ما إذا كان المقال يذكر أيًا من الشركات التي تهم المستخدم.

للقيام بذلك ، نحتاج إلى جمع جميع الأسماء الصحيحة ومعرفة ما إذا كانت الأسهم من محفظتنا مدرجة في تلك الأسماء الصحيحة.

للعثور على جميع الأسماء الصحيحة ، سنريد أولاً تقسيم إخراج السلسلة الموسومة إلى رموز مميزة (باستخدام المسافات كمحددات) ، ثم تقسيم كل من الرموز المميزة على الشرطة السفلية ( _ ) وتحقق مما إذا كان جزء الكلام هو اسم صحيح .

بمجرد أن نحصل على جميع الأسماء المناسبة ، سنرغب في تخزينها في بنية بيانات مُحسَّنة بشكل أفضل لغرضنا. على سبيل المثال لدينا ، سنستخدم HashSet . في مقابل عدم السماح بإدخالات مكررة وعدم تتبع ترتيب الإدخالات ، يسمح HashSet باستعلامات عضوية سريعة جدًا. نظرًا لأننا مهتمون فقط بالاستعلام عن العضوية ، فإن HashSet مثالية لأغراضنا.

يوجد أدناه الوظيفة التي تنفذ تقسيم وتخزين أسماء العلم. ضع هذه الوظيفة في فئة PortfolioNewsAnalyzer الخاصة بك:

 public static HashSet<String> extractProperNouns(String taggedOutput) { HashSet<String> propNounSet = new HashSet<String>(); String[] split = taggedOutput.split(" "); for (String token: split ){ String[] splitTokens = token.split("_"); if(splitTokesn[1].equals("NNP")){ propNounSet.add(splitTokens[0]); } } return propNounSet; }

هناك مشكلة في هذا التنفيذ بالرغم من ذلك. إذا كان اسم الشركة يتكون من كلمات متعددة ، (على سبيل المثال ، Carl Zeiss في مثال Luxottica) فلن يتمكن هذا التطبيق من اللحاق به. في مثال Carl Zeiss ، سيتم إدراج "Carl" و "Zeiss" في المجموعة بشكل منفصل ، وبالتالي لن يحتوي على السلسلة المفردة "Carl Zeiss".

لحل هذه المشكلة ، يمكننا جمع كل أسماء العلم المتتالية وربطها بمسافات. إليك التنفيذ المحدث الذي يحقق ذلك:

 public static HashSet<String> extractProperNouns(String taggedOutput) { HashSet<String> propNounSet = new HashSet<String>(); String[] split = taggedOutput.split(" "); List<String> propNounList = new ArrayList<String>(); for (String token: split ){ String[] splitTokens = token.split("_"); if(splitTokens[1].equals("NNP")){ propNounList.add(splitTokens[0]); } else { if (!propNounList.isEmpty()) { propNounSet.add(StringUtils.join(propNounList, " ")); propNounList.clear(); } } } if (!propNounList.isEmpty()) { propNounSet.add(StringUtils.join(propNounList, " ")); propNounList.clear(); } return propNounSet; }

الآن يجب أن تقوم الوظيفة بإرجاع مجموعة مع أسماء العلم الفردية وأسماء العلم المتتالية (على سبيل المثال ، مرتبطة بمسافات). إذا قمت بطباعة propNounSet ، فسترى شيئًا مشابهًا لما يلي:

 [... Monday, Gianluca Semeraro, David Goodman, Delfin, North America, Luxottica, Latin America, Rossi/File Photo, Rome, Safilo Group, SFLG.MI, Friday, Valentina Za, Del Vecchio, CEO Hubert Sagnieres, Oakley, Sagnieres, Jefferies, Ray Ban, ...]

مقارنة المحفظة مع مجموعة PropNouns

نحن على وشك الإنتهاء!

في الأقسام السابقة ، قمنا ببناء مكشطة يمكنها تنزيل واستخراج نص المقالة ، و tagger يمكنه تحليل نص المقالة وتحديد الأسماء المناسبة ، ومعالجًا يأخذ الإخراج الموسوم ويجمع الأسماء الصحيحة في HashSet . كل ما تبقى الآن هو أخذ HashSet ومقارنتها بقائمة الشركات التي نهتم بها.

التنفيذ بسيط للغاية. أضف الكود التالي في فئة PortfolioNewsAnalyzer الخاصة بك:

 private HashSet<String> portfolio; public PortfolioNewsAnalyzer() { portfolio = new HashSet<String>(); } public void addPortfolioCompany(String company) { portfolio.add(company); } public boolean arePortfolioCompaniesMentioned(HashSet<String> articleProperNouns){ return !Collections.disjoint(articleProperNouns, portfolio); }

ضع كل شيء معا

يمكننا الآن تشغيل التطبيق بالكامل — الكشط والتنظيف ووضع العلامات والتجميع والمقارنة. هذه هي الوظيفة التي يتم تشغيلها من خلال التطبيق بأكمله. أضف هذه الوظيفة إلى فئة PortfolioNewsAnalyzer الخاصة بك:

 public boolean analyzeArticle(String urlString) throws IOException, SAXException, BoilerpipeProcessingException { String articleText = extractFromUrl(urlString); String tagged = tagPos(articleText); HashSet<String> properNounsSet = extractProperNouns(tagged); return arePortfolioCompaniesMentioned(properNounsSet); }

أخيرًا ، يمكننا استخدام التطبيق!

فيما يلي مثال باستخدام نفس المقالة على النحو الوارد أعلاه و Luxottica كشركة محفظة:

 public static void main( String[] args ) throws IOException, SAXException, BoilerpipeProcessingException { PortfolioNewsAnalyzer analyzer = new PortfolioNewsAnalyzer(); analyzer.addPortfolioCompany("Luxottica"); boolean mentioned = analyzer.analyzeArticle("http://www.reuters.com/article/us-essilor-ma-luxottica-group-idUSKBN14Z110"); if (mentioned) { System.out.println("Article mentions portfolio companies"); } else { System.out.println("Article does not mention portfolio companies"); } }

شغّل هذا ، ويجب أن يطبع التطبيق "المقالات تشير إلى شركات المحفظة".

قم بتغيير شركة المحفظة من Luxottica إلى شركة غير مذكورة في المقالة (مثل "Microsoft") ، ويجب أن يطبع التطبيق "المقالة لا تذكر شركات المحفظة".

لا يحتاج بناء تطبيق البرمجة اللغوية العصبية إلى أن يكون صعبًا

في هذه المقالة ، انتقلنا خلال عملية إنشاء تطبيق يقوم بتنزيل مقال من عنوان URL وتنظيفه باستخدام Boilerpipe ومعالجته باستخدام Stanford NLP والتحقق مما إذا كانت المقالة تشير إلى مراجع محددة ذات أهمية (في حالتنا ، الشركات في منطقتنا ملف). كما هو موضح ، فإن الاستفادة من هذه المجموعة من التقنيات تجعل ما كان من الممكن أن يكون مهمة شاقة إلى مهمة مباشرة نسبيًا.

آمل أن يكون هذا المقال قد قدم لك مفاهيم وتقنيات مفيدة في معالجة اللغة الطبيعية وأن تكون قد ألهمتك لكتابة تطبيقات اللغة الطبيعية الخاصة بك.

[ملاحظة: يمكنك العثور هنا على نسخة من الكود المشار إليه في هذه المقالة.]