作者都是各自领域经过审查的专家,并撰写他们有经验的主题. 我们所有的内容都经过同行评审,并由同一领域的Toptal专家验证.
自然语言处理——一种允许软件应用程序处理人类语言的技术——在过去几年中变得相当普遍.
谷歌搜索越来越能够回答听起来很自然的问题, 苹果的Siri能够理解各种各样的问题, 越来越多的公司正在使用(合理的)智能聊天和电话机器人与客户沟通. 但是这个看似“智能”的软件究竟是如何工作的呢?
在本文中, 您将了解使这些应用程序运行的技术, 你将学习如何开发自己的自然语言处理软件.
本文将引导您完成构建新闻相关性分析器的示例过程. 假设你有一个股票投资组合, 你想要一个应用程序自动浏览流行的新闻网站,并识别与你的投资组合相关的文章. For example, 如果你的股票投资组合中有微软这样的公司, BlackStone, and Luxottica, 您可能希望看到提到这三家公司的文章.
自然语言处理应用, 和其他机器学习应用程序一样, 是建在几个相对较小的, simple, 直观的算法协同工作. 通常使用外部库是有意义的,其中所有这些算法都已经实现和集成.
对于我们的示例,我们将使用 斯坦福NLP图书馆, 一个强大的基于java的自然语言处理库,支持多种语言.
我们感兴趣的这个库中的一个特定算法是词性标注器(POS). 词性标注器用于自动为一段文本中的每个单词分配词性. 该词性标注器根据词汇特征对文本中的词进行分类,并将其与周围的词进行关联分析.
POS标记器算法的确切机制超出了本文的讨论范围, 但是你可以了解更多 here.
To begin, 我们将创建一个新的Java项目(您可以使用自己喜欢的IDE),并将斯坦福NLP库添加到依赖项列表中. 如果您正在使用Maven,只需将其添加到您的 pom.xml
file:
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
添加了这些依赖项之后,您就可以继续前进了.
我们的分析器的第一部分将涉及检索文章并从网页中提取其内容.
从新闻来源检索文章时, 这些页面通常充斥着无关的信息(如嵌入式视频), outbound links, videos, advertisements, etc.),与文章本身无关. This is where Boilerpipe 开始发挥作用.
Boilerpipe是一种非常健壮和高效的算法,用于消除“混乱”,通过使用平均句子长度等特征分析不同的内容块来识别新闻文章的主要内容, 在内容块中使用的标签类型, 链接的密度. 锅炉管道算法已被证明与其他计算成本更高的算法具有竞争力, 比如那些基于机器视觉的. 你可以在那里学到更多 project site.
Boilerpipe库内置了对抓取网页的支持. 它可以从web上获取HTML,从HTML中提取文本,并清理提取的文本. 你可以定义一个函数, extractFromURL
,它将接受一个URL,并使用Boilerpipe将最相关的文本作为字符串返回 ArticleExtractor
for this task:
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;
BoilerPipeExtractor {
extractFromUrl(String userUrl)
throws java.io.IOException,
org.xml.sax.SAXException,
de.l3s.boilerpipe.BoilerpipeProcessingException {
htmlDoc = HTMLFetcher.fetch(新URL (userUrl));
最后的TextDocument doc =新的BoilerpipeSAXInput(htmlDoc.toInputSource ()).getTextDocument ();
返回CommonExtractors.ARTICLE_EXTRACTOR.getText(doc);
}
}
Boilerpipe库提供了基于Boilerpipe算法的不同提取器,包括 ArticleExtractor
专门针对html格式的新闻文章进行了优化. ArticleExtractor
特别关注每个内容块中使用的HTML标记和出站链接密度. 这比快速但简单的方法更适合我们的任务 DefaultExtractor
.
内置函数为我们解决了所有问题:
HTMLFetcher.fetch
获取HTML文档getTextDocument
提取文本文档CommonExtractors.ARTICLE_EXTRACTOR.getText
使用boilerpipe算法从文章中提取相关文本现在,您可以通过一篇关于光学巨头依视路(Essilor)和陆逊梯卡(Luxottica)合并的示例文章来尝试一下, 你可以找到 here. 您可以将此URL提供给该函数,看看结果如何.
将以下代码添加到main函数中:
公共类App
{
public static void main(String[] args)
throws java.io.IOException,
org.xml.sax.SAXException,
de.l3s.boilerpipe.BoilerpipeProcessingException {
字符串urlString = "http://www.reuters.com/article/us-essilor-m-a-luxottica-group-idUSKBN14Z110”;
字符串文本= BoilerPipeExtractor.extractFromUrl (urlString);
System.out.println(text);
}
}
您应该在文章主体的输出中看到, 没有广告, HTML tags, 以及出站链接. 下面是我运行这个程序时得到的开始片段:
意大利的Luxottica (LUX).(MI)和法国的依视路(Essilor).日前,美国眼镜集团(PA)同意以460亿欧元(合490亿美元)的价格合并,打造一家年收入超过150亿欧元的全球眼镜巨头.
这笔全股票交易是欧洲最大的跨境合作之一,并将陆逊梯卡整合在一起, 拥有Ray-Ban和Oakley等品牌的世界顶级眼镜制造商, 领先的镜头制造商依视路.
"Finally ... 将设计两种天然互补的产品,即镜框和镜片, 在同一屋檐下生产和销售,陆逊梯卡81岁的创始人莱昂纳多·德尔·维奇奥周一在一份声明中说.
Luxottica的股价上涨了8%.6 percent at 53.到格林尼治时间14:05(美国东部时间9:05)为80欧元.m. 以视路上涨12点.2%至114.60 euros.
在950亿美元的眼镜市场上,这两家顶级公司的合并旨在帮助企业充分利用由于全球人口老龄化和人们对眼睛护理意识的提高而对处方眼镜和太阳镜的强劲需求.
杰富瑞(Jefferies)分析师估计,市场的增长速度介于这两者之间...
这就是文章的主体部分. 很难想象这比这更容易实现.
现在您已经成功地提取了文章主体, 您可以确定文章是否提到了用户感兴趣的公司.
您可能想要简单地执行字符串或正则表达式搜索, 但是这种方法也有一些缺点.
首先,字符串搜索可能容易出现误报. 例如,一篇提到微软Excel的文章可能会被标记为提到微软.
Secondly, 取决于正则表达式的构造, 正则表达式搜索可能导致假阴性. For example, 一篇包含“Luxottica的季度收益超出预期”的文章可能会被正则表达式搜索错过,因为搜索“Luxottica”的时候会被空格包围.
Finally, 如果您对大量的公司感兴趣并且正在处理大量的文章, 在整个文本中搜索用户投资组合中的每个公司可能会非常耗时, 产生不可接受的性能.
斯坦福大学的CoreNLP图书馆 具有许多强大的功能,并提供了解决这三个问题的方法.
对于我们的分析器,我们将使用词性标注器. In particular, 我们可以使用POS标记器找到文章中的所有专有名词,并将它们与我们感兴趣的股票组合进行比较.
By 结合自然语言处理技术, 我们不仅提高了标注器的准确性,还最大限度地减少了上面提到的误报和误报, 但我们也显著地减少了与股票投资组合相比所需的文本数量, 因为专有名词只占文章全文的一小部分.
通过将我们的投资组合预处理成一个数据结构 低会员查询成本,我们可以大大减少分析一篇文章所需的时间.
斯坦福CoreNLP提供了一个非常方便的标注器 MaxentTagger 它可以在几行代码中提供POS标记.
下面是一个简单的实现:
公共类PortfolioNewsAnalyzer {
private HashSet portfolio;
private static final String modelPath = "edu\\stanford\\nlp\\models\\ post -tagger\\english-left3words\\english-left3words-distsim.tagger";
private MaxentTagger;
public portfolio () {
tagger = new MaxentTagger(modelPath);
}
公共字符串tagPos(字符串输入){
return tagger.tagString(输入);
}
标注函数, tagPos
, 将一个字符串作为输入并输出一个字符串,该字符串包含原始字符串中的单词以及相应的词性. 在主函数中实例化 PortfolioNewsAnalyzer
然后将scraper的输出输入tagger函数,你应该会看到如下所示:
米兰/PARIS_NN Italy_NNP 's_POS Luxottica_NNP - lrb - _lrb - LUX.MI_NNP - rrb - _rrb -和_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 tiups_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 su_jj as_IN Ray-Ban_NNP和Oakley_NNP ,_, with_IN leading_VBG lens_NN manufacturer_NN Essilor_NNP ._. ' ' _ ' ' Finally_RB ..._:两个cd产品_ nns,其中_ wdt是_ vbp自然的_ rb互补_ jj -_:即_ rb框架_ nns和_ cc透镜_ nns -_:将_ md成为_ vb设计的_ vbn ,_, manufactured_VBN和cc distributed_VBN under_the_dt same_JJ roof_NN ,_, _“Luxottica_NNP”s_POS 81岁d_jj创始人_ nn Leonardo_NNP Del_NNP Vecchio_NNP said_VBD in_IN a_DT statement_NN on_IN Monday_NNP ._. 8 . Shares_NNS in_IN Luxottica_NNP wer_vbd up_RB by_IN.6 .[参考译文.gt_nnp - lrb - lrb - 9:05_CD.m._NN ET_NNP - rrb - _rrb -,_, with_IN Essilor_NNP up_IN.[au:] [au:] [au:.60 _cd euros_NNS ._. 95年The_DT merger_NN between_IN The_DT top_JJ players_NNS in_IN The_DT _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...
到目前为止,我们已经构建了下载、清理和标记新闻文章的函数. 但是我们仍然需要确定文章是否提到了用户感兴趣的公司.
To do this, 我们需要收集所有的专有名词,并检查我们投资组合中的股票是否包括在这些专有名词中.
找到所有的专有名词, 首先,我们要将带标记的字符串输出分割为多个标记(使用空格作为分隔符)。, 然后将下划线上的每个标记分开(_
),检查词性是否为专有名词.
一旦我们有了所有的专有名词, 我们希望将它们存储在一个更适合我们的目的的数据结构中. 在我们的例子中,我们将使用a HashSet
. 作为不允许重复条目和不记录条目顺序的交换, HashSet
允许非常快的成员查询. 因为我们只对查询成员感兴趣,所以 HashSet
完全符合我们的目的吗.
下面是实现专有名词拆分和存储的函数. 将这个函数放在 PortfolioNewsAnalyzer
class:
public static HashSet extractProperNouns(String taggedOutput) {
HashSet propNounSet = new HashSet();
String[] split = taggedOutput.split(" ");
for(字符串标记:split){
String[] splitTokens = token.split("_");
如果(splitTokesn [1].= (NNP)) {
propNounSet.add (splitTokens [0]);
}
}
返回propNounSet;
}
但是这个实现有一个问题. 如果公司名称由多个单词组成,(例如.g.(如蔡司在陆逊梯卡的例子)这个实现将无法捕捉到它. 以卡尔蔡司为例, “卡尔”和“蔡司”将分别插入到集合中, 因此永远不会包含“卡尔·蔡司”这个单一的字符串.”
为了解决这个问题,我们可以收集所有的 consecutive 专有名词,用空格连接. 下面是更新后的实现:
public static HashSet extractProperNouns(String taggedOutput) {
HashSet propNounSet = new HashSet();
String[] split = taggedOutput.split(" ");
List propNounList = new ArrayList();
for(字符串标记:split){
String[] splitTokens = token.split("_");
如果(splitTokens [1].= (NNP)) {
propNounList.add (splitTokens [0]);
} else {
if (!propNounList.isEmpty()) {
propNounSet.add (stringutil.join(pronounlist, " "));
propNounList.clear();
}
}
}
if (!propNounList.isEmpty()) {
propNounSet.add (stringutil.join(pronounlist, " "));
propNounList.clear();
}
返回propNounSet;
}
现在,函数应该返回一个包含单个专有名词的集合 and 连续专有名词(i.e.,以空格连接). 如果你打印 propNounSet
,您应该看到如下内容:
[... Monday, 蒋禄卡Semeraro, David Goodman, Delfin, North America, Luxottica, Latin America, 罗西/文件照片, Rome, Safilo Group, SFLG.MI, Friday, Valentina Za, Del Vecchio, CEO Hubert Sagnieres, Oakley, Sagnieres, Jefferies, Ray Ban, ...]
我们差不多完成了!
在前面的部分中, 我们构建了一个scraper,可以下载并提取文章的主体, 可以解析文章主体和识别专有名词的标注器, 还有一个处理器,它接受带标签的输出,并将专有名词收集到 HashSet
. 现在剩下要做的就是 HashSet
并将其与我们感兴趣的公司列表进行比较.
实现非常简单. 将以下代码添加到 PortfolioNewsAnalyzer
class:
private HashSet portfolio;
public portfolio () {
portfolio = new HashSet();
}
addPortfolioCompany(String company) {
portfolio.add(company);
}
public boolean arePortfolioCompaniesMentioned(HashSet articleProperNouns){
return !Collections.分离(articleProperNouns,投资组合);
}
现在我们可以运行整个应用程序了——抓取、清理、标记、收集和比较. 这是贯穿整个应用程序的函数. 将此函数添加到 PortfolioNewsAnalyzer
class:
公共布尔分析文章(字符串urlString)抛出
IOException,
SAXException,
BoilerpipeProcessingException
{
字符串articleText = extractFromUrl (urlString);
字符串标记= tagPos(articleText);
HashSet properNounsSet = extractProperNouns(tagged);
返回arePortfolioCompaniesMentioned (properNounsSet);
}
最后,我们可以使用应用程序!
以下是使用上述同一篇文章并将Luxottica作为投资组合公司的例子:
public static void main(String[] args)抛出
IOException,
SAXException,
BoilerpipeProcessingException
{
PortfolioNewsAnalyzer analyzer = new PortfolioNewsAnalyzer();
analyzer.addPortfolioCompany(“Luxottica”);
布尔提及=分析器.analyzeArticle (" http://www.reuters.com/article/us-essilor-m-a-luxottica-group-idUSKBN14Z110”);
如果(提到){
System.out.println(“文章提到投资组合公司”);
} else {
System.out.println(“文章未提及投资组合公司”);
}
}
运行这个程序,应用程序应该打印“文章提到投资组合公司”.”
将投资组合公司从Luxottica更改为文章中没有提到的公司(例如“Microsoft”)。, 应用程序应该打印“文章不提及投资组合公司.”
在本文中, 我们逐步完成了构建一个从URL下载文章的应用程序的过程, 使用Boilerpipe清洗, 使用斯坦福NLP进行处理, 并检查文章是否有特定的兴趣参考(在我们的例子中), 我们投资组合中的公司). 作为证明, 利用这一系列技术使原本令人生畏的任务变得相对简单.
我希望本文向您介绍了自然语言处理中有用的概念和技术,并启发您编写自己的自然语言应用程序.
[注:您可以找到本文中引用的代码副本 here.]
Sean是一个充满激情的通晓多种语言的人:一个全栈向导、系统管理员和数据科学家. 他还开发了市场情报软件.
世界级的文章,每周发一次.
世界级的文章,每周发一次.