作者都是各自领域经过审查的专家,并撰写他们有经验的主题. 我们所有的内容都经过同行评审,并由同一领域的Toptal专家验证.
Shanglun Wang
Verified Expert in Engineering

Sean是一个充满激情的通晓多种语言的人:一个全栈向导、系统管理员和数据科学家. 他还开发了市场情报软件.

PREVIOUSLY AT

CB Insights
Share

自然语言处理技术在过去几年中已经变得相当复杂. From tech giants to hobbyists, 许多人都急于构建可以进行分析的富界面, understand, 并对自然语言做出反应. Amazon’s Alexa, Microsoft’s Cortana, Google’s Google Home, 苹果的Siri都旨在改变我们与电脑互动的方式.

Sentiment analysis, 自然语言处理的一个子领域, 由决定文本或讲话语气的技巧组成. Today, 通过机器学习和从社交媒体和评论网站收集的大量数据, 我们可以训练模型来相当准确地识别自然语言段落的情感.

电子邮件情感分析机器人教程

In this tutorial, 您将学习如何构建一个机器人,它可以分析它收到的电子邮件的情绪,并通知您可能需要立即注意的电子邮件.

Analyzing Sentiment in Emails

该bot将使用Java和 Python development. 这两个进程将使用Thrift相互通信. 如果您不熟悉其中一种或两种语言, 您仍然可以继续阅读,因为本文的基本概念也适用于其他语言.

判断一封邮件是否需要你的关注, 机器人将解析它并确定是否有强烈的负面语气. 然后,如果需要,它将发送文本警报.

我们将使用Sendgrid连接到我们的邮箱,使用Twilio发送文本警报.

情感分析:一个看似简单的问题

有些词和积极的情绪联系在一起,比如爱、快乐和快乐. 还有一些词和负面情绪联系在一起,比如仇恨、悲伤和痛苦. 为什么不训练模型来识别这些单词,并计算每个积极和消极单词的相对频率和强度呢?

好吧,这有几个问题.

首先,有一个否定的问题. For example, 像“桃子还不错”这样的句子,用一个我们通常与消极联系在一起的词,暗示了一种积极的情绪. 一个简单的词袋模型将无法识别这句话中的否定.

此外,混合情绪被证明是幼稚情绪分析的另一个问题. For example, 像“桃子还不错。, 但是苹果真的很糟糕”这句话包含了相互影响的复杂情绪. 一个简单的方法将无法解决共同的情绪, the different intensity, 或者情感之间的相互作用.

基于递归神经张量网络的情感分析

The 斯坦福自然语言处理 情感分析库使用递归神经张量网络(RNTN)解决了这些问题。.

RNTN on a sentence

RNTN算法首先将一个句子分成单独的单词. 然后,它构建一个神经网络,其中节点是单个单词. Finally, 添加了一个张量层,以便模型可以适当地调整单词和短语之间的相互作用.

你可以在他们的网站上找到这个算法的可视化演示 official website.

斯坦福NLP小组使用手动标记的IMDB电影评论训练递归神经张量网络,并发现他们的模型能够非常准确地预测情绪.

Bot That Receives Emails

您要做的第一件事是设置电子邮件集成,以便数据可以通过管道传输到您的机器人.

有很多方法可以做到这一点, 但是为了简单起见, 让我们设置一个简单的web服务器,并使用Sendgrid的入站解析钩子将电子邮件传输到服务器. 我们可以将电子邮件转发到Sendgrid的入站解析地址. 然后,Sendgrid将向我们的web服务器发送一个POST请求, 然后我们就可以通过我们的服务器处理这些数据.

为了构建服务器,我们将使用Flask,一个简单的web框架 Python.

除了构建web服务器之外,我们还需要将web服务连接到域. 为简洁起见,我们将在本文中跳过这部分内容. 然而,你可以阅读更多关于它的内容 here.

Building a web server in Flask is incredibly simple.

Simply create an app.py and add this to the file:

从flask中导入flask
import datetime
 
app = Flask(__name__)
 
@app.路线(/分析,方法=['文章'])
def analyze():
    with open('logfile.txt', 'a') as fp_log:
        fp_log.写入('端点命中%s \n' %日期时间.datetime.now().strftime(“% Y - % m - H % d %: % m: % S '))
    return "Got it"
 
app.run(host='0.0.0.0')

如果我们将这个应用程序部署在域名后面,并点击“/analyze”端点, 你应该看到这样的内容:

> >> requests.post('http://sentiments.shanglunwang.com:5000/analyze').text
'Got it'

接下来,我们要向这个端点发送电子邮件.

您可以找到更多文档 here 但你基本上想设置Sendgrid作为你的电子邮件处理器,并让Sendgrid将电子邮件转发到我们的web服务器.

Here is my setup on Sendgrid. This will forward emails to @sentibot.shanglunwang.com POST请求到“http://sentiments”.shanglunwang.com/analyze”:

Sendgrid configuration

您可以使用任何其他支持通过webhook发送入站电子邮件的服务.

After setting everything up, 尝试发送电子邮件到您的Sendgrid地址, 你应该在日志中看到这样的内容:

终端命中2017-05-25 14:35:46

That’s great! 现在您有了一个能够接收电子邮件的机器人. 这是我们正在努力做的一半.

现在,你想给这个机器人分析电子邮件情绪的能力.

斯坦福NLP电子邮件情感分析

由于斯坦福NLP库是用Java编写的,所以我们希望用Java构建分析引擎.

让我们从下载斯坦福NLP库和Maven中的模型开始. Create a new Java 项目,将以下内容添加到Maven依赖项中,并导入:


   edu.stanford.nlp
   stanford-corenlp
   3.6.0

斯坦福NLP的情感分析引擎可以通过在管道初始化代码中指定情感注释器来访问. 然后可以将注释作为树结构检索.

为本教程的目的, 我们只想知道一个句子的大意, 所以我们不需要解析整个树. 我们只需要看看基本节点.

这使得主代码相对简单:

package seanwang;
import edu.stanford.nlp.pipeline.*;
import edu.stanford.nlp.util.CoreMap;
import edu.stanford.nlp.ling.CoreAnnotations;
import edu.stanford.nlp.sentiment.SentimentCoreAnnotations;
 
 
import java.util.*;
 
public class App
{
    public static void main(String[] args)
    {
        属性pipelineProps =新属性();
        Properties tokenizerProps = new Properties();
        pipelineProps.setProperty("annotators", "parse, sentiment");
        pipelineProps.setProperty("parse.binaryTrees", "true");
        pipelineProps.setProperty(“enforceRequirements”,“假”);
        tokenizerProps.setProperty("annotators", "tokenize ssplit");
        standfordcorenlp tokenizer = new standfordcorenlp (tokenizerProps);
        StanfordCoreNLP管道= new StanfordCoreNLP(pipelineProps);
        非常感激的漂亮朋友们正在完成一件非常快乐的事. What an truly terrible idea.";
        Annotation =标记器.process(line);
        pipeline.annotate(annotation);
        // normal output
        对于CoreMap句子:注释.get(CoreAnnotations.SentencesAnnotation.class)) {
            String output = sentence.get(SentimentCoreAnnotations.SentimentClass.class);
            System.out.println(output);
        }
    }
}

尝试一些句子,您应该会看到适当的注释. 运行示例代码输出:

Very Positive
Negative

集成Bot和分析引擎

所以我们有一个用Java编写的情感分析程序和一个用Python编写的电子邮件机器人. 我们怎么让他们互相交谈?

这个问题有许多可能的解决方案,但在这里我们将使用 Thrift. 我们将启动情绪分析器作为一个Thrift服务器和电子邮件机器人作为一个Thrift客户端.

Thrift是一个代码生成器和一个协议,用于启用两个应用程序, 通常用不同的语言写的, 能够使用已定义的协议相互通信. 多语言团队使用Thrift构建微服务网络,以充分利用他们使用的每种语言的优点.

要使用Thrift,我们需要两样东西:a .thrift 文件中定义的服务端点,并生成代码以使用在 .proto file. For the analyzer service, the sentiment.thrift looks like this:

namespace java sentiment
namespace py sentiment
 
服务SentimentAnalysisService
{
        字符串情感分析(1:字符串句子),
}

我们可以使用它生成客户端和服务器代码 .thrift file. Run:

thrift-0.10.0.exe --gen py sentiment.thrift
thrift-0.10.0.exe --gen java sentiment.thrift

注意:我是在Windows机器上生成代码的. 您将希望在您的环境中使用到Thrift可执行文件的适当路径.

现在,让我们对分析引擎进行适当的更改以创建服务器. 你的Java程序应该是这样的:

SentimentHandler.java

package seanwang;
 
公共类sentimentandler实现SentimentAnalysisService.Iface {
    SentimentAnalyzer analyzer;
    SentimentHandler() {
        analyzer = new SentimentAnalyzer();
    }
 
    公共字符串sentimentAnalyze(字符串句子){
        System.out.println("got: " + sentence);
        return analyzer.analyze(sentence);
    }
 
}

这个处理程序是我们通过Thrift协议接收分析请求的地方.

SentimentAnalyzer.java

package seanwang;
 
// ...
 
公共类SentimentAnalyzer {
    StanfordCoreNLP tokenizer;
    StanfordCoreNLP pipeline;
 
    public SentimentAnalyzer() {
        属性pipelineProps =新属性();
        Properties tokenizerProps = new Properties();
        pipelineProps.setProperty("annotators", "parse, sentiment");
        pipelineProps.setProperty("parse.binaryTrees", "true");
        pipelineProps.setProperty(“enforceRequirements”,“假”);
        tokenizerProps.setProperty("annotators", "tokenize ssplit");
        tokenizer = new StanfordCoreNLP(tokenizerProps);
        pipeline = new StanfordCoreNLP(pipelineProps);
    }
 
    公共字符串分析(字符串行){
        Annotation =标记器.process(line);
        pipeline.annotate(annotation);
        String output = "";
        对于CoreMap句子:注释.get(CoreAnnotations.SentencesAnnotation.class)) {
            output += sentence.get(SentimentCoreAnnotations.SentimentClass.class);
            output += "\n";
        }
        return output;
    }
}

Analyzer使用Stanford NLP库来确定文本的情感,并为文本中的每个句子生成一个包含情感注释的字符串.

SentimentServer.java

package seanwang;
 
// ...
 
公共类SentimentServer {
    公共静态SentimentHandler处理程序;
 
    公共静态SentimentAnalysisService.Processor processor;
 
    public static void main(String [] args) {
        try {
            handler = new SentimentHandler();
            processor = new SentimentAnalysisService.Processor(handler);
 
            Runnable simple = new Runnable() {
                public void run() {
                    simple(processor);
                }
            };
 
            new Thread(simple).start();
        } catch (Exception x) {
            x.printStackTrace();
        }
    }
 
    (SentimentAnalysisService ..Processor processor) {
        try {
            TServerTransport = new TServerSocket(9090);
            TServer server = new TSimpleServer(new Args(serverTransport)).processor(processor));
 
            System.out.启动简单服务器...");
            server.serve();
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}

注意,我没有包括 SentimentAnalysisService.java 文件,因为它是生成的文件. 您将希望将生成的代码放在其他代码可以访问的地方.

现在我们已经建立了服务器,让我们编写一个Python客户机来使用服务器.

client.py

从情感导入SentimentAnalysisService
from thrift.transport import TSocket
from thrift.transport import TTransport
from thrift.导入TBinaryProtocol
 
class SentimentClient:
    Def __init__(self, server='localhost', socket=9090):
        transport = TSocket.TSocket(server, socket)
        transport = TTransport.TBufferedTransport(transport)
        protocol = TBinaryProtocol.TBinaryProtocol(transport)
        self.transport = transport
        self.client = SentimentAnalysisService.Client(protocol)
        self.transport.open()
 
    def __del__(self):
        self.transport.close()
 
    def analyze(self, sentence):
        return self.client.sentimentAnalyze(sentence)
 
if __name__ == '__main__':
    client = SentimentClient()
    print(client.分析(“一个奇妙的句子”))

Run this and you should see:

Very Positive

Great! 现在我们已经运行了服务器并与客户机进行了通信, 让我们通过实例化一个客户端并将电子邮件导入其中来将其与电子邮件机器人集成.

import client
 
# ...
 
@app.路线(/分析,方法=['文章'])
def analyze():
    sentiment_client = client.SentimentClient()
    with open('logfile.txt', 'a') as fp_log:
        fp_log.write(str(request.form.get('text')))
        fp_log.write(request.form.get('text'))
        fp_log.write(sentiment_client.analyze(request.form.get('text')))
    return "Got it"

现在将Java服务部署到运行web服务器的同一台机器上, start the service, and restart the app. 向机器人发送一封带有测试句子的电子邮件,您应该在日志文件中看到如下内容:

非常积极和美丽的句子.
Very Positive

Analyzing the Email

All right! 现在我们有了一个能够进行情感分析的电子邮件机器人! 我们可以发送电子邮件,并收到我们发送的每个句子的情感标签. 现在,让我们来探讨一下如何使情报具有可操作性.

To keep things simple, 让我们把重点放在那些高度集中了否定句和非常否定句的电子邮件上. 让我们使用一个简单的评分系统,假设一封电子邮件包含超过75%的负面情绪句子, 我们会将其标记为可能需要立即回复的潜在警报邮件. 让我们在analyze路由中实现评分逻辑:

@app.路线(/分析,方法=['文章'])
def analyze():
    text = str(request.form.get('text'))
    sentiment_client = client.SentimentClient()
    text.Replace ('\n', ") #删除所有新行
    sentences = text.rstrip('.').split('.’)#删除分裂前的最后一段
    negative_sentences = [
        句接句,句接句
        if sentiment_client.analyze(sentence).rstrip() in ['Negative', 'Very Negative'] # remove换行字符
    ]
    urgent = len(negative_sentences) / len(sentences) > 0.75
    with open('logfile.txt', 'a') as fp_log:
        fp_log.写("已收到:%s") %(请求.form.get('text')))
        fp_log.Write ("urgent = %s" % (str(urgent)))
 
    return "Got it"

上面的代码做了一些假设,但用于演示目的. 发送几封电子邮件到你的机器人,你应该在日志中看到电子邮件分析:

收到:这是对系统的测试. 这应该是一个非紧急请求.
It's very good! 在大多数情况下,这是积极的或中性的. Great things
are happening!
urgent = False
 
收到:这是一个紧急请求. Everything is truly awful. This is a disaster.
人们讨厌这种无味的邮件.
urgent = True

Sending Out an Alert

We’re almost done!

我们已经建立了一个电子邮件机器人,能够接收电子邮件, perform sentiment analysis, 确定一封邮件是否需要立即处理. 现在,当收到一封特别负面的邮件时,我们只需要发送短信提醒.

我们将使用Twilio发送文本警报. 他们的Python API,有文档记录 here, is pretty straightforward. 让我们修改分析路由,以便在接收到紧急请求时发出请求.

def send_message(body):
    twilio_client.messages.create(
        to=on_call,
        from_=os.采用“TWILIO_PHONE_NUMBER”),
        body=body
    )
 
app = Flask(__name__)
 
 
@app.路线(/分析,方法=['文章'])
def analyze():
    text = str(request.form.get('text'))
    sentiment_client = client.SentimentClient()
    text.Replace ('\n', ") #删除所有新行
    sentences = text.rstrip('.').split('.’)#删除分裂前的最后一段
    negative_sentences = [
        句接句,句接句
        if sentiment_client.analyze(sentence).rstrip() in ['Negative', 'Very Negative'] # remove换行字符
    ]
    urgent = len(negative_sentences) / len(sentences) > 0.75
    if urgent:
        收到了非常负面的邮件. Please take action')
    with open('logfile.txt', 'a') as fp_log:
        fp_log.write("Received: " % request.form.get('text'))
        fp_log.Write ("urgent = %s" % (str(urgent)))
        fp_log.write("\n")
 
    return "Got it"

您需要将您的环境变量设置为您的Twilio帐户凭据,并将呼叫号码设置为您可以检查的电话. Once you have done that, 向分析端点发送一封电子邮件,您应该会看到一个文本被发送到有问题的电话号码.

And we’re done!

斯坦福NLP使自然语言处理变得简单

In this article, 您学习了如何使用斯坦福NLP库构建电子邮件情感分析机器人. 该库有助于抽象出自然语言处理的所有细节,并允许您将其用作NLP应用程序的构建块.

我希望这篇文章展示了情感分析的许多惊人的潜在应用之一, 这会激励你建立一个自己的NLP应用程序.

你可以在NLP教程中找到邮件情感分析机器人的代码 GitHub.

Understanding the basics

  • 什么是自然语言处理?

    自然语言处理是使用算法来分析和理解普通的人类语言,以确定诸如情绪之类的指标.

  • 斯坦福NLP图书馆是什么?

    Stanford NLP库是一组用Java编写的自然语言处理软件,由Stanford NLP Group构建并开放源代码.

  • 什么是递归神经张量网络(RNTN)?

    递归神经张量网络是一组以树状结构组织的神经网络,其中每个节点都是一个神经网络. 这在自然语言处理中特别有用,在自然语言处理中,算法处理单词及其在句子中的相互作用.

聘请Toptal这方面的专家.
Hire Now
Shanglun Wang

Shanglun Wang

Verified Expert in Engineering

New York, NY, United States

2016年12月16日成为会员

About the author

Sean是一个充满激情的通晓多种语言的人:一个全栈向导、系统管理员和数据科学家. 他还开发了市场情报软件.

作者都是各自领域经过审查的专家,并撰写他们有经验的主题. 我们所有的内容都经过同行评审,并由同一领域的Toptal专家验证.

PREVIOUSLY AT

CB Insights

世界级的文章,每周发一次.

输入您的电子邮件,即表示您同意我们的 privacy policy.

世界级的文章,每周发一次.

输入您的电子邮件,即表示您同意我们的 privacy policy.

Toptal Developers

Join the Toptal® community.