如何构建电子邮件情感分析机器人:NLP教程

本文概述

在过去的几年中, 自然语言处理技术已经变得非常复杂。从技术巨头到业余爱好者, 许多人都在急于建立可以分析, 理解和响应自然语言的丰富界面。亚马逊的Alexa, 微软的Cortana, 谷歌的Google Home和苹果的Siri都旨在改变我们与计算机交互的方式。

情感分析是自然语言处理的一个子领域, 由确定文本或语音语调的技术组成。如今, 借助机器学习以及从社交媒体和评论网站收集的大量数据, 我们可以训练模型以相当准确的方式识别自然语言段落的情感。

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

在本教程中, 你将学习如何构建一个机器人, 该机器人可以分析它收到的电子邮件的情绪, 并通知你有关可能需要立即引起注意的电子邮件。

分析电子邮件中的情绪

该机器人将结合使用Java和Python开发而成。这两个进程将使用Thrift相互通信。如果你不熟悉这两种语言中的一种或两种, 则仍然可以继续阅读, 因为本文的基本概念也适用于其他语言。

为了确定电子邮件是否需要引起你的注意, 该漫游器将对其进行分析并确定是否存在强烈的负面语气。然后, 如果需要, 它将发出文本警报。

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

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

有些词使我们与积极的情感相关联, 例如爱, 喜悦和愉悦。并且, 有些词与负面情绪相关, 例如仇恨, 悲伤和痛苦。为什么不训练模型来识别这些单词并计算每个正负单词的相对频率和强度?

好吧, 这有两个问题。

首先, 存在一个否定问题。例如, 像”桃子还不错”这样的句子使用我们最常与否定相关的词来暗示一种积极的情绪。一个简单的词袋模型将无法识别这句话中的否定词。

此外, 混合情绪被证明是幼稚情绪分析的另一个问题。例如, “桃子不错, 但苹果确实很糟糕”之类的句子包含了混合在一起的, 混合强度的情感。一种简单的方法将无法解决组合的情感, 不同的强度或情感之间的相互作用。

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

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

RNTN句子

RNTN算法首先将一个句子拆分为单个单词。然后构建一个神经网络, 其中节点是单个单词。最后, 添加张量层, 以便模型可以针对单词和短语之间的交互进行适当调整。

你可以在他们的官方网站上找到该算法的直观演示。

斯坦福大学自然语言处理小组使用手动标记的IMDB电影评论训练了递归神经张量网络, 发现他们的模型能够非常准确地预测情绪。

收到邮件的机器人

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

有很多方法可以完成此操作, 但是为了简单起见, 让我们设置一个简单的Web服务器, 并使用Sendgrid的入站解析挂钩将电子邮件发送到服务器。我们可以将电子邮件转发到Sendgrid的入站解析地址。然后, Sendgrid将POST请求发送到我们的Web服务器, 然后我们将能够通过我们的服务器处理数据。

要构建服务器, 我们将使用Flask, 这是一个适用于Python的简单网络框架。

除了构建Web服务器之外, 我们还将希望将Web服务连接到域。为简便起见, 我们将在本文中跳过有关此内容的文章。但是, 你可以在此处了解更多信息。

在Flask中构建Web服务器非常简单。

只需创建一个app.py并将其添加到文件中:

from flask import Flask, request
import datetime
 
app = Flask(__name__)
 
@app.route('/analyze', methods=['POST'])
def analyze():
    with open('logfile.txt', 'a') as fp_log:
        fp_log.write('endpoint hit %s \n' % datetime.datetime.now().strftime('%Y-%m-%d %H:%M:%S'))
    return "Got it"
 
app.run(host='0.0.0.0')

如果我们将此应用程序部署在域名后面并点击端点” / analyze”端点, 则应该看到类似以下内容:

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

接下来, 我们要向该端点发送电子邮件。

你可以在此处找到更多文档, 但本质上你希望将Sendgrid设置为你的电子邮件处理程序, 并让Sendgrid将电子邮件转发到我们的Web服务器。

这是我在Sendgrid上的设置。这将按照POST请求将电子邮件转发到@ sentibot.shanglunwang.com:” http://sentiments.shanglunwang.com/analyze”:

Sendgrid配置

你可以使用任何其他支持通过Webhooks发送入站电子邮件的服务。

设置完所有内容后, 尝试向你的Sendgrid地址发送电子邮件, 你应该在日志中看到以下内容:

endpoint hit 2017-05-25 14:35:46

那很棒!现在, 你有了一个可以接收电子邮件的机器人。那是我们正在尝试做的一半。

现在, 你想让该机器人能够分析电子邮件中的情绪。

使用Stanford NLP的电子邮件情绪分析

由于Stanford NLP库是用Java编写的, 因此我们将要用Java构建分析引擎。

首先, 从Maven下载Stanford NLP库和模型。创建一个新的Java项目, 将以下内容添加到你的Maven依赖项中, 然后导入:

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

通过在管道初始化代码中指定情感注释器, 可以访问Stanford 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 )
    {
        Properties pipelineProps = new Properties();
        Properties tokenizerProps = new Properties();
        pipelineProps.setProperty("annotators", "parse, sentiment");
        pipelineProps.setProperty("parse.binaryTrees", "true");
        pipelineProps.setProperty("enforceRequirements", "false");
        tokenizerProps.setProperty("annotators", "tokenize ssplit");
        StanfordCoreNLP tokenizer = new StanfordCoreNLP(tokenizerProps);
        StanfordCoreNLP pipeline = new StanfordCoreNLP(pipelineProps);
        String line = "Amazingly grateful beautiful friends are fulfilling an incredibly joyful accomplishment. What an truly terrible idea.";
        Annotation annotation = tokenizer.process(line);
        pipeline.annotate(annotation);
        // normal output
        for (CoreMap sentence : annotation.get(CoreAnnotations.SentencesAnnotation.class)) {
            String output = sentence.get(SentimentCoreAnnotations.SentimentClass.class);
            System.out.println(output);
        }
    }
}

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

Very Positive
Negative

整合Bot和分析引擎

因此, 我们有一个用Java编写的情感分析器程序和一个用Python编写的电子邮件bot。我们如何让他们互相交谈?

有许多可能的解决方案, 但是在这里我们将使用Thrift。我们将把Sentiment Analyzer用作Thrift服务器, 将电子邮件bot用作Thrift客户端。

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

要使用Thrift, 我们需要做两件事:.thrift文件以定义服务端点, 以及生成的代码以使用.proto文件中定义的协议。对于分析器服务, sentiment.thrift如下所示:

namespace java sentiment
namespace py sentiment
 
service SentimentAnalysisService
{
        string sentimentAnalyze(1:string sentence), }

我们可以使用此.thrift文件生成客户端和服务器代码。跑:

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;
 
public class SentimentHandler implements SentimentAnalysisService.Iface {
    SentimentAnalyzer analyzer;
    SentimentHandler() {
        analyzer = new SentimentAnalyzer();
    }
 
    public String sentimentAnalyze(String sentence) {
        System.out.println("got: " + sentence);
        return analyzer.analyze(sentence);
    }
 
}

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

SentimentAnalyzer.java

package seanwang;
 
// ...
 
public class SentimentAnalyzer {
    StanfordCoreNLP tokenizer;
    StanfordCoreNLP pipeline;
 
    public SentimentAnalyzer() {
        Properties pipelineProps = new Properties();
        Properties tokenizerProps = new Properties();
        pipelineProps.setProperty("annotators", "parse, sentiment");
        pipelineProps.setProperty("parse.binaryTrees", "true");
        pipelineProps.setProperty("enforceRequirements", "false");
        tokenizerProps.setProperty("annotators", "tokenize ssplit");
        tokenizer = new StanfordCoreNLP(tokenizerProps);
        pipeline = new StanfordCoreNLP(pipelineProps);
    }
 
    public String analyze(String line) {
        Annotation annotation = tokenizer.process(line);
        pipeline.annotate(annotation);
        String output = "";
        for (CoreMap sentence : annotation.get(CoreAnnotations.SentencesAnnotation.class)) {
            output += sentence.get(SentimentCoreAnnotations.SentimentClass.class);
            output += "\n";
        }
        return output;
    }
}

分析器使用Stanford NLP库确定文本的情感, 并生成一个字符串, 其中包含文本中每个句子的情感注释。

SentimentServer.java

package seanwang;
 
// ...
 
public class SentimentServer {
    public static SentimentHandler handler;
 
    public static 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();
        }
    }
 
    public static void simple(SentimentAnalysisService.Processor processor) {
        try {
            TServerTransport serverTransport = new TServerSocket(9090);
            TServer server = new TSimpleServer(new Args(serverTransport).processor(processor));
 
            System.out.println("Starting the simple server...");
            server.serve();
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}

请注意, 由于它是生成的文件, 因此在此未包括SentimentAnalysisService.java文件。你将需要将生成的代码放在其余代码可以访问的地方。

现在我们已经启动了服务器, 让我们编写一个Python客户端来使用服务器。

client.py

from sentiment import SentimentAnalysisService
from thrift.transport import TSocket
from thrift.transport import TTransport
from thrift.protocol import 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.analyze('An amazingly wonderful sentence'))

运行此命令, 你应该看到:

Very Positive

大!现在, 我们已经在运行服务器并与客户端通信, 让我们通过实例化客户端并将电子邮件发送到其中将其与电子邮件机器人进行集成。

import client
 
# ...
 
@app.route('/analyze', methods=['POST'])
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服务器的同一台计算机上, 启动该服务, 然后重新启动该应用。向机器人发送带有测试语句的电子邮件, 你应该在日志文件中看到以下内容:

Amazingly wonderfully positive and beautiful sentence.
Very Positive

分析电子邮件

行!现在, 我们有一个电子邮件机器人, 能够执行情绪分析!我们可以发送电子邮件, 并为每个发送的句子接收一个情感标签。现在, 让我们探讨如何使情报具有可行性。

为简单起见, 让我们集中讨论否定和非常否定句子高度集中的电子邮件。让我们使用一个简单的评分系统, 并说, 如果一封电子邮件中包含超过75%的负面情绪句子, 我们会将其标记为可能需要立即回复的潜在警报电子邮件。让我们在分析路径中实施评分逻辑:

@app.route('/analyze', methods=['POST'])
def analyze():
    text = str(request.form.get('text'))
    sentiment_client = client.SentimentClient()
    text.replace('\n', '')  # remove all new lines
    sentences = text.rstrip('.').split('.')  # remove the last period before splitting
    negative_sentences = [
        sentence for sentence in sentences
        if sentiment_client.analyze(sentence).rstrip() in ['Negative', 'Very negative']  # remove newline char
    ]
    urgent = len(negative_sentences) / len(sentences) > 0.75
    with open('logfile.txt', 'a') as fp_log:
        fp_log.write("Received: %s" % (request.form.get('text')))
        fp_log.write("urgent = %s" % (str(urgent)))
 
    return "Got it"

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

Received: Here is a test for the system. This is supposed to be a non-urgent request.
It's very good! For the most part this is positive or neutral. Great things
are happening!
urgent = False
 
Received: This is an urgent request. Everything is truly awful. This is a disaster.
People hate this tasteless mail.
urgent = True

发出警报

我们快完成了!

我们构建了一个电子邮件机器人, 该机器人可以接收电子邮件, 执行情感分析并确定电子邮件是否需要立即关注。现在, 当电子邮件特别负面时, 我们只需要发送文本警报即可。

我们将使用Twilio发送文本警报。他们的Python API(此处记录)非常简单。让我们修改分析路线, 以在收到紧急请求时发出请求。

def send_message(body):
    twilio_client.messages.create(
        to=on_call, from_=os.getenv('TWILIO_PHONE_NUMBER'), body=body
    )
 
app = Flask(__name__)
 
 
@app.route('/analyze', methods=['POST'])
def analyze():
    text = str(request.form.get('text'))
    sentiment_client = client.SentimentClient()
    text.replace('\n', '')  # remove all new lines
    sentences = text.rstrip('.').split('.')  # remove the last period before splitting
    negative_sentences = [
        sentence for sentence in sentences
        if sentiment_client.analyze(sentence).rstrip() in ['Negative', 'Very negative']  # remove newline char
    ]
    urgent = len(negative_sentences) / len(sentences) > 0.75
    if urgent:
        send_message('Highly negative email received. 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帐户凭据, 并将通话中的号码设置为可以检查的电话。完成此操作后, 向分析端点发送电子邮件, 你应该会看到一条文本已发送至相关电话号码。

我们完成了!

斯坦福大学自然语言处理使自然语言处理变得轻松

在本文中, 你学习了如何使用Stanford NLP库构建电子邮件情绪分析机器人。该库有助于抽象出自然语言处理的所有细节, 并允许你将其用作NLP应用程序的构建块。

我希望这篇文章已经证明了情感分析的许多惊人的潜在应用之一, 并且可以激发你构建自己的NLP应用程序。

你可以从GitHub上的此NLP教程中找到电子邮件情感分析机器人的代码。

相关:情感分析准确性的四个陷阱

微信公众号
手机浏览(小程序)
0
分享到:
没有账号? 忘记密码?