Обработката на естествен език - технология, която позволява на софтуерните приложения да обработват човешки език - е станала донякъде повсеместна през последните години.
Търсене в Google е в състояние да отговори на естествено звучащи въпроси. Siri на Apple може да разбере голямо разнообразие от въпроси, поради което много компании използват (разумни) интелигентни телефони за чат и ботове, за да комуникират с клиентите. Но как всъщност работи този привидно „умен“ софтуер?
В тази статия ще научите за технологията, която задвижва тези приложения, както и ще научите как сами да разработите софтуер за обработка на естествен език.
Статията ще ви преведе през пример за процес на изграждане на анализатор, достоен за новини. Представете си, че имате портфолио от ценни книжа и бихте искали приложение, което автоматично преминава през популярни новинарски уебсайтове и идентифицира статии, които са от значение за вашето портфолио. Например, ако портфолиото от акции включва компании като Microsoft, BlackStone и Luxottica, трябва да сте наясно със статии, в които се споменават тези три компании.
Приложенията за обработка на естествен език, както всяко друго приложение за машинно обучение, са изградени на основата на относително малки, прости и интуитивни алгоритми, които работят заедно. Често има по-смисъл да се използва външна библиотека, където всички тези алгоритми вече са внедрени и интегрирани.
За нашия пример ще използваме НЛП библиотека в Станфорд , мощна Java-базирана библиотека за обработка на естествен език, тя идва с поддръжка за много езици.
Един конкретен алгоритъм, който ни интересува в тази библиотека, е този за маркиране на граматиката. Граматичното маркиране се използва за автоматично присвояване на части от речта към всяка дума в парче текст. Това граматично маркиране класифицира думите в текста въз основа на лексикални характеристики, като същевременно ги анализира във връзка с други думи около тях.
Точната механика на граматичния алгоритъм за маркиране е извън обхвата на тази статия, но можете да научите за нея. тук .
За начало ще създадем нов Java проект (можете да използвате вашата среда за интерактивно развитие ТУК ) и добавете библиотеката на 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
Като добавите тези зависимости, сте готови да продължите напред:
Първата част на нашия анализатор ще бъде за вземане на статии и извличане на тяхното съдържание от уеб страници.
Когато вземаме статии от новинарски източници, страниците обикновено са пълни с неподходяща информация (вградени видеоклипове, изходящи връзки, видеоклипове, реклама и т.н.), която е без значение за статията като такава. Това е където Казан за тръби се отбелязва.
Казан за тръби е изключително важен и ефективен алгоритъм за премахване на 'бъркотията', който идентифицира основното съдържание на дадена статия чрез анализ на различни съдържателни блокове, като се използват характеристики като средна дължина на изречението, типове маркери, използвани в съдържателните блокове и плътност на съдържанието. Алгоритъмът на бойлер той е доказал, че може да се конкурира с други алгоритми, които са много по-скъпи в изчисления, като тези, базирани на машинно зрение. Можете да научите повече в уебсайта на вашия проект .
Библиотеката на бойлер идва с инсталирана поддръжка за изстъргване на уеб страници. Той може да извлече HTML от мрежата, да извлече текст от HTML и да почисти извлечения текст. Можете да дефинирате функция, extractFromURL
, която ще вземе url и ще използва Казан за тръби за да върнете най-подходящия текст като символен низ, използвайки 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); } }
Библиотека бойлер предоставя различни екстрактори, базирани на алгоритъма на бойлерната тръба, с 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 Luxottica de Italia (LUX.MI) y Essilor de Francia (ESSI.PA) han llegado a un acuerdo de una fusión de 46 billones de euros (49 billones de dólares) para crear *global eyewear powerhouse* con ingresos anuales de más de 15 billones de euros. El acuerdo de todo tipo de acciones es uno de los mayores vínculos transfronterizos de Europa y une a Luxottica, el mayor creador de lentes del mundo con marcas como Ray-Ban y Oakley, con el fabricante de lentes líder Essilor. 'Finalmente... dos productos que naturalmente se complementan – es decir monturas y lentes – serán diseñados, manufacturados y distribuidos bajo el mismo techo,' Leonardo Del Vecchio, fundador de Luxottica de 81 años, dijo en un comunicado el lunes. Las acciones en Luxottica subieron un 8.6 por ciento a 53.80 euros a las 1405 GMT (9:05 a.m. ET), con Essilor arriba un 12.2 por ciento a 114.60 euros. La fusión entre estos jugadores tan importantes en el mercado de lentes de 95 billones, se enfoca en ayudar a los negocios a aprovechar la demanda tan fuerte que se espera para lentes de prescripción y lentes de sol, debido a una población global que envejece y una toma de conciencia mayor con respecto al cuidado de los ojos. Los analistas Jefferies estiman que el mercado está creciendo entre…
И това е основната част на основната статия. Трудно е да си представим, че това е по-лесно за изпълнение.
След като успешно сте извлекли тялото на статията, можете да се съсредоточите върху определянето дали в статията се споменават компании, които представляват интерес за потребителя.
Може да се изкушите просто да направите низ или да направите регулярно изражение, но има няколко недостатъка на този подход.
На първо място, търсенето в низ може да бъде изложено на фалшиви положителни резултати. Статия, в която се споменава Microsoft Excel, може да бъде етикетирана така, сякаш споменава например Microsoft.
Второ, в зависимост от конструкцията на регулярния израз, търсенето на регулярен израз може да доведе до фалшиви отрицания. Например, статия, която съдържа фразата „тримесечните приходи на Luxottica надминаха очакванията“, може да бъде загубена при търсене с регулярен израз, която намира думата „Luxottica“, заобиколена от бели пространства.
И накрая, ако се интересувате от голям брой компании и обработвате голям брой статии, търсенето на всяка компания в портфолиото на потребителя в цялата основна част на текста може да доведе до лоша производителност и в същото време да отнеме много време време.
Библиотеката на Stanford CoreNLP той има много силни характеристики и предоставя начин за решаване на тези три проблема.
За нашия парсер ще използваме граматично маркиране. По-специално, можем да го използваме, за да намерим всички правилни имена в статията и да ги сравним в нашето портфолио от интересни акции.
Чрез включването на технологията NLP ние не само подобряваме точността на нашия етикет и минимизираме гореспоменатите фалшиви положителни и отрицателни страни, но също така драстично намаляваме количеството текст, което трябва да сравним с нашето портфолио от акции, тъй като правилните имена компрометират само малка част пълният текст на статията.
Чрез предварителна обработка на нашата статия в структура от данни, която има ниска цена на консултация за членство , можем драстично да намалим времето, необходимо за анализ на елемент.
CoreNLP на Станфорд предоставя много удобен тагър, наречен MaxentTagger което може да осигури граматична маркировка в няколко реда код.
Ето едно просто изпълнение:
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; }
Сега функцията трябва да върне набор с индивидуалните собствени имена Y. последователни собствени имена (например, обединени с интервали). Ако отпечатате 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, ...]
Почти сме готови!
В предишните раздели изградихме a стъргало който може да изтегли и извлече тялото на статията, маркер, който може да анализира тялото на статията и да идентифицира собствените имена, както и визуализатор, който взема маркирания изход и събира правилните имена в 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 адрес, изчиства я с помощта Казан за тръби , обработва го с помощта на НЛП на Станфорд и проверява дали статията прави конкретни препратки, представляващи интерес (в нашия случай компании от нашето портфолио). Както вече беше показано, управлението на тази матрица от технологии прави това, което иначе би било плашеща задача, задача, която е относително ясна.
Надявам се, че тази статия ви е научила на полезни концепции и техники за обработка на естествен език и ви е вдъхновила да пишете приложения на естествен език сами.
[Забележка: Можете да намерите копие на кода, посочен в тази статия тук .