Обработката на естествен език - технология, която позволява на софтуерните приложения да обработват човешки език - стана доста повсеместна през последните няколко години.
Търсенето с Google е все по-способно да отговаря на естествено звучащи въпроси, Siri на Apple е в състояние да разбере голямо разнообразие от въпроси и все повече компании използват (разумно) интелигентен чат и телефонни ботове за комуникация с клиентите. Но как наистина работи този привидно „умен“ софтуер?
В тази статия ще научите за технологията, която кара тези приложения да отмятат, и ще научите как да разработите собствен софтуер за обработка на естествен език.
Статията ще ви преведе през примерния процес на изграждане на анализатор за уместност на новините. Представете си, че имате портфейл от акции и бихте искали приложение автоматично да обхожда популярни уебсайтове с новини и да идентифицира статии, които са подходящи за вашето портфолио. Например, ако вашият портфейл от акции включва компании като Microsoft, BlackStone и Luxottica, бихте искали да видите статии, в които се споменават тези три компании.
Приложенията за обработка на естествен език, както и всички други приложения за машинно обучение, са изградени върху редица относително малки, прости, интуитивни алгоритми, работещи в тандем. Често има смисъл да се използва външна библиотека, където всички тези алгоритми вече са внедрени и интегрирани.
За нашия пример ще използваме НЛП библиотека в Станфорд , мощна Java-базирана библиотека за обработка на естествен език, която се предлага с поддръжка за много езици.
Един конкретен алгоритъм от тази библиотека, който ни интересува, е тагът за част от речта (POS). POS тагър се използва за автоматично присвояване на части от речта към всяка дума в парче текст. Този POS маркер класифицира думите в текста въз основа на лексикални характеристики и ги анализира във връзка с други думи около тях.
Точната механика на POS таг алгоритъма е извън обхвата на тази статия, но можете да научите повече за нея тук .
За начало ще създадем нов Java проект (можете да използвате любимата си IDE) и ще добавим библиотеката на Stanford NLP към списъка на зависимостите. Ако използвате Maven, просто го добавете към вашия pom.xml
файл:
edu.stanford.nlp stanford-corenlp 3.6.0 edu.stanford.nlp stanford-corenlp 3.6.0 models
Тъй като приложението ще трябва автоматично да извлече съдържанието на статия от уеб страница, ще трябва да посочите и следните две зависимости:
de.l3s.boilerpipe boilerpipe 1.1.0
net.sourceforge.nekohtml nekohtml 1.9.22
С добавените тези зависимости сте готови да продължите напред.
Първата част на нашия анализатор ще включва извличане на статии и извличане на тяхното съдържание от уеб страници.
Когато извличате статии от новинарски източници, страниците обикновено са осеяни с чужда информация (вградени видеоклипове, изходящи връзки, видеоклипове, реклами и др.), Които не са от значение за самата статия. Това е където Казан за тръби влиза в игра.
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 осигурява различни екстрактори, базирани на алгоритъма на бойлерпипе, с ArticleExtractor
е специално оптимизиран за HTML-форматирани новинарски статии. ArticleExtractor
се фокусира специално върху HTML таговете, използвани във всеки блок на съдържание и плътност на изходящите връзки. Това е по-подходящо за нашата задача, отколкото по-бързото, но по-просто DefaultExtractor
.
Вградените функции се грижат за всичко за нас:
HTMLFetcher.fetch
получава HTML документаgetTextDocument
извлича текстовия документCommonExtractors.ARTICLE_EXTRACTOR.getText
извлича съответния текст от статията с помощта на котелния алгоритъмСега можете да го изпробвате с примерна статия относно сливанията на оптични гиганти 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-m-a-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 ( 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 a.m. 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). По-специално, можем да използваме POS маркера, за да намерим всички правилни съществителни в статията и да ги сравним с нашето портфолио от интересни запаси.
Чрез включването на NLP технологията ние не само подобряваме точността на нашия маркер и свеждаме до минимум фалшивите положителни и отрицателни точки, споменати по-горе, но също така драстично свеждаме до минимум количеството текст, което трябва да сравним с нашето портфолио от запаси, тъй като собствените съществителни съдържат само малка част от пълния текст на статията.
Чрез предварителна обработка на нашето портфолио в структура от данни, която има ниска цена на заявката за членство , можем драстично да намалим времето, необходимо за анализ на дадена статия.
Stanford CoreNLP предоставя много удобен тагър, наречен MaxentTagger които могат да осигурят POS маркиране само в няколко реда код.
Ето едно просто изпълнение:
public class PortfolioNewsAnalyzer { private HashSet 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); }
Функцията за маркиране, tagPos
, приема низ като вход и извежда низ, който съдържа думите в оригиналния низ заедно със съответната част от речта. В основната си функция инстанцирайте a PortfolioNewsAnalyzer
и подайте изхода на скрепера във функцията тагър и трябва да видите нещо подобно:
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 a.m._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 extractProperNouns(String taggedOutput) { HashSet propNounSet = new HashSet(); 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 extractProperNouns(String taggedOutput) { HashSet propNounSet = new HashSet(); String[] split = taggedOutput.split(' '); List propNounList = new ArrayList(); 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, ...]
Почти сме готови!
В предишните раздели създадохме скрепер, който може да изтегли и извлече тялото на статия, маркер, който може да анализира тялото на статията и да идентифицира правилни съществителни, и процесор, който взема маркирания изход и събира правилните съществителни в HashSet
. Сега остава само да вземем HashSet
и го сравнете със списъка на компаниите, от които се интересуваме.
Изпълнението е много просто. Добавете следния код във вашия PortfolioNewsAnalyzer
клас:
private HashSet portfolio; public PortfolioNewsAnalyzer() { portfolio = new HashSet(); } public void addPortfolioCompany(String company) { portfolio.add(company); } public boolean arePortfolioCompaniesMentioned(HashSet 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 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-m-a-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 и проверяваме дали статията прави конкретни препратки от интерес (в нашия случай компаниите в нашия портфолио). Както беше демонстрирано, използването на този набор от технологии превръща онова, което иначе би било плашеща задача, в сравнително ясна.
Надявам се, че тази статия ви е запознала с полезни концепции и техники за обработка на естествен език и че ви е вдъхновила да пишете свои собствени приложения на естествен език.
[Забележка: Можете да намерите копие на кода, посочен в тази статия тук .]