November 2017

Volume 32 Number 11

[Cognitive Services]

From Text to Targeted Sentiment Analysis with Cognitive Services

By Ashish Sahu

Human languages have evolved over many centuries and, as a result, tend to have complex rules to govern emotional expression. As practitioners of human languages, we typically rely on learning and various social conventions to master one or more languages and use them to express ourselves in different situations. The qualities and learnings are reflected in the responses we leave in various settings, including product reviews, survey responses in online and offline format, and the like. They follow the same language rules and provide a fascinating opportunity to test and advance the machine learning models that work on human language expression.

Today, social networks play a great role in shaping popular opinion. We participate in discussions on just about everything, express our thoughts and agree or disagree with each other for all of the world to see. These experiences also spill into our shopping habits and often we leave our views on things we buy and services we try in the form of reviews.

Because we can’t physically see and assess the things we want to buy online, we’re often left to others’ opinions to evaluate how durable or reliable they might be. These reviews may come with star ratings that offer a minimal convenience for sorting through them. However, the star rating doesn’t really provide an accurate picture of someone’s experience with a product or the quality of the product—sometimes they’re just unhappy that an item shipped later than expected.

That leaves us with just one option—to read through the existing reviews and make an educated guess about what’s good and bad about the product in question. While that helps, it’s time-consuming and inefficient. Now, however, the Text Analytics and the Linguistic Analysis APIs, part of Microsoft Cognitive Services, can help.

Just like the other Cognitive Services, the Text Analytics and Linguistic Analysis APIs are hosted services. There’s no software you need to download, configure or train before you start using these artificial intelligence (AI)-based APIs to do amazing things.

The Text Analytics API can infer insights, such as the language of the text, key phrases being discussed and the sentiment expressed in the text. The Linguistic Analysis API, in contrast, enables the understanding of linguistic concepts and actions in free-form text. It performs part-of-speech (POS) tagging, tokenization and sentence separation, among other things. You can use this API to mine information from customer feedback and to understand the intent of users and the structure of the text.

In this article, I’ll show how these APIs work and the scenarios where they can be used to generate insights from the various forms of text encountered every day.

What Does Text Analytics Do?

The Text Analytics API is a suite of services built with Azure Machine Learning. At the time of this writing, you can use the following API operations to perform the actions mentioned earlier:

  • Detect language determines the language of the text sent to the API from among the 120 supported languages.
  • Key phrases finds key phrases in the text input.
  • Sentiment detects the sentiment of the text, returning a numeric score between 0 and 1, where a higher score denotes the degree of positive sentiment.
  • Detect topics discovers topics or phrases from a corpus of text in a batch operation. This operation is marked for deprecation and won’t be covered in the article.

The Text Analytics API is pre-trained to perform these operations and doesn’t need any training data. It can handle most of the reviews on most shopping portals. To help you better understand the capabilities of the Text Analytics API, take a look at the following example review:

“This phone has a great battery. The display is sharp and bright. But the store does not have the apps I need.”

This excerpt reflects a shopper’s experience with a phone. Let’s run this example through the different API operations and see what turns up.

Along the way, you can use any utility you choose (such as Fiddler, Postman or Curl) for these tests to see the results for yourself. You can also use the API Testing Console, available in the API reference documentation. But there are a few things you need to know before you can test run these APIs:

  1. You need a subscription key. If you don’t have an Azure subscription, you can get a trial key from bit.ly/2eLG8OT.
  2. The API expects to receive the text record in the following format:
{
  "documents": [
    {
      "language": "string",
      "id": "string",
      "text": "string"
    }
  ]
}

This format applies to the key phrases and the sentiment operations. However, you’ll need to remove the “language” field for the calls to the detect language operation.

I’ll start with a simple test to determine the language of the example text, and the request body will be formatted like so:

{
  "documents": [
    {
      "id": "1",
      "text": "This phone has a great battery. The display is sharp and bright. 
        But the store does not have the apps I need."
    }
  ]
}

I’m using the Curl utility and here’s what my request looks like:

curl -v -X POST "https://westus.api.cognitive.microsoft.com/text/analytics/v2.0/languages"
-H "Content-Type: application/json"
-H "Ocp-Apim-Subscription-Key: my-trial-key"

--data-ascii "{
  "documents": [
    {
      "id": "1",
      "text": "This phone has a great battery. The display is sharp and bright. 
        But the store does not have the apps I need."
    }
  ]
}"

And the result I get back after I place this request is shown in Figure 1.

Figure 1 The Languages Result

{
  "documents": [
    {
      "id": "1",
      "detectedLanguages": [
        {
          "name": "English",
          "iso6391Name": "en",
          "score": 1
        }
      ]
    }
  ],
  "errors": []
}

As you can see, the result is a scored response and English was detected as the language of the text record submitted. You are welcome to try out a different language to see how it works.

Next, I’ll run the same record through the key phrase endpoint. I need to change my Curl command with the updated endpoint and the request body as follows:

curl -v --silent -X POST "https://westus.api.cognitive.microsoft.com/text/analytics/v2.0/keyPhrases"
-H "Content-Type: application/json" 
-H "Ocp-Apim-Subscription-Key: my-trial-key"
--data-ascii '{ 
  "documents": [
  { "language": "en",
    "id": "1",
    "text": "This phone has a great battery. The display is sharp and bright. 
      But the store does not have the apps I need."
  } 
  ] 
}'

The response is shown in Figure 2.

Figure 2 The Key Phrases Result

{
  "documents": [
    {
      "keyPhrases": [
        "phone",
        "great battery",
        "display",
        "store",
        "apps"
      ],
      "id": "1"
    }
  ],
  "errors": []
}

As you can see, the key phrases endpoint has detected appropriate phrases from the example review.

Now let’s see what this review denotes on the scale of positivity. I’ll run the same text through the sentiment endpoint and see what comes back. Here’s my Curl command:

curl -v --silent -X POST "https://westus.api.cognitive.microsoft.com/text/
  analytics/v2.0/sentiment"
-H "Content-Type: application/json" 
-H "Ocp-Apim-Subscription-Key: my-trial-key"
--data-ascii '{ 
  "documents": [
  { "language": "en",
    "id": "1",
    "text": "This phone has a great battery. The display is sharp and bright. 
      But the store does not have the apps I need."
  } 
  ] 
}'

And the result this time is:

{
  "documents": [
    {
      "score": 0.770478801630976,
      "id": "1"
    }
  ],
  "errors": []
}

The outcome is simpler this time, and you can see that the sentiment score for the review is .77, which, on a scale of 0 to 1 is 77 percent. This denotes a mostly positive sentiment for the text—which you can infer from text.

Now that I’ve run this text review with all the available operations, I’ll combine them to see the overall result:

  • Text: This phone has a great battery. The display is sharp and bright. But the store does not have the apps I need.
  • Language: English
  • Key Phrases: phone, great battery, display, store, apps
  • Sentiment: 77 percent

This experiment demonstrates what the Text Analytics Cognitive Service can do for text reviews, survey responses and customer input. However, combined with the Linguistic Analysis services, I can distil even deeper insights.

A Look at Linguistic Analysis: What’s the Difference?

The Text Analytics API uses a pre-trained model to analyze text to detect information such as language, key phrases and sentiments. In contrast, the Linguistic Analysis API uses advanced linguistic analysis tools to process the text inputs and allows you to understand the structure of the sentence. This understanding can then be used to mine information from text records, interpret user commands and process free-form text from any source.

I’m not a linguist, so I’ll leave the job of explaining the ins and outs of this service to someone who is. In this article, I’ll just cover the basics of this service and then come back to the original intent of this article, which is to generate deeper and meaningful insights from text inputs.

The Linguistic Analysis API uses the concept of analysers to understand the structure of text records. Currently, three kinds are supported:

  • Tokens
  • POS Tags
  • Constituency Tree

These analyzers come from the Penn Treebank, which is the annotated, parsed text corpus that allows the Linguistic Analysis API to understand whether a given word in a text input is a noun or a verb. For example, “I love Bing!” and “Let me bing this for you” use the word Bing in different capacity.

Let’s use this example to understand how Linguistic Analysis works its magic. Just as with Text Analytics, you’ll need a trial key if you don’t have an Azure subscription.

Once you have the key, just fire up your tool of choice to send a request to the Linguistic Analysis API. There are two operations available with this API:

  • List analyzers return the list of analysers to parse the text for Tokens, POS Tags and a Constituency Tree.
  • Analyze text parses the text inputs you provide using the analyzers you supply in the request.

Figure 3 shows what a simple GET request to the List Analyzer endpoint returns.

Figure 3 The Result of a Simple GET Request to the List Analyzer Endpoint

[
  {
    "id": "4fa79af1-f22c-408d-98bb-b7d7aeef7f04",
    "languages": [
      "en"
    ],
    "kind": "POS_Tags",
    "specification": "PennTreebank3",
    "implementation": "cmm"
  },
  {
    "id": "22a6b758-420f-4745-8a3c-46835a67c0d2",
    "languages": [
      "en"
    ],
    "kind": "Constituency_Tree",
    "specification": "PennTreebank3",
    "implementation": "SplitMerge"
  },
  {
    "id": "08ea174b-bfdb-4e64-987e-602f85da7f72",
    "languages": [
      "en"
    ],
    "kind": "Tokens",
    "specification": "PennTreebank3",
    "implementation": "regexes"
  }
]

I’ll use the previously mentioned analyzers to parse this text: “I Love Bing! Let me Bing this for you,” formatting the request body as follows:

{
  "language" : "en",
  "analyzerIds" : ["4fa79af1-f22c-408d-98bb-b7d7aeef7f04", 
    "22a6b758-420f-4745-8a3c-46835a67c0d2", 
    "08ea174b-bfdb-4e64-987e-602f85da7f72"],
  "text" : "I love Bing! Let me bing this for you" 
}

Here’s my Curl command:

curl -v --silent -X POST https://westus.api.cognitive.microsoft.com/ 
  linguistics/v1.0/analyze
-H "Content-Type: application/json" 
-H "Ocp-Apim-Subscription-Key: my-trial-key" 
--data-ascii ' {
  "language" : "en",
  "analyzerIds" : ["4fa79af1-f22c-408d-98bb-b7d7aeef7f04", 
    "22a6b758-420f-4745-8a3c-46835a67c0d2", 
    "08ea174b-bfdb-4e64-987e-602f85da7f72"],
  "text" : "I love Bing! Let me bing this for you" 
}'

The response to this request is shown in Figure 4.

Figure 4 Parsing the Example Text

[
  {
    "analyzerId":"4fa79af1-f22c-408d-98bb-b7d7aeef7f04",
    "result":[
      [
        "PRP",
        "VBP",
        "VBG",
        "."
      ],
      [
        "VB",
        "PRP",
        "JJ",
        "DT",
        "IN",
        "PRP"
      ]
    ]
  },
  {
    "analyzerId":"22a6b758-420f-4745-8a3c-46835a67c0d2",
    "result":[
      "(TOP (S (NP (PRP I)) (VP (VBP love) (NNP Bing)) (. !)))",
      "(VP (VBD Let) (S (NP (PRP me)) (VP (VBG bing) (NP (DT this)) 
      (PP (IN for) (NP (PRP you))))))"
    ]
  },
  {
    "analyzerId":"08ea174b-bfdb-4e64-987e-602f85da7f72",
    "result":[
        {
          "Len":12,
          "Offset":0,
          "Tokens":[
            {
              "Len":1,
              "NormalizedToken":"I",
              "Offset":0,
              "RawToken":"I"
            },
            {
              "Len":4,
              "NormalizedToken":"love",
              "Offset":2,
              "RawToken":"love"
            },
            {
              "Len":4,
              "NormalizedToken":"Bing",
              "Offset":7,
              "RawToken":"Bing"
            },
            {
              "Len":1,
              "NormalizedToken":"!",
              "Offset":11,
              "RawToken":"!"
            }
          ]
        },
      {
        "Len":24,
        "Offset":13,
        "Tokens":[
          {
            "Len":3,
            "NormalizedToken":"Let",
            "Offset":13,
            "RawToken":"Let"
          },
          {
            "Len":2,
            "NormalizedToken":"me",
            "Offset":17,
            "RawToken":"me"
          },
          {
            "Len":4,
            "NormalizedToken":"bing",
            "Offset":20,
            "RawToken":"bing"
          },
          {
            "Len":4,
            "NormalizedToken":"this",
            "Offset":25,
            "RawToken":"this"
          },
          {
            "Len":3,
            "NormalizedToken":"for",
            "Offset":30,
            "RawToken":"for"
          },
          {
            "Len":3,
            "NormalizedToken":"you",
            "Offset":34,
            "RawToken":"you"
          }
        ]
      }
    ]
  }
]

The result is segregated based on each of the analyzers sent in the request - POS Tags, Constituency Tree and Tokens in this example.

As you can see, the POS Tags are just tags for each of the words in the text input, while the Constituency Tree analyzer returns the tree structure of the text input marked with tags and the words. The Tokens analyzer returns the most readable result where it includes the information about each of the words in the text input along with their position in the record.

For this article, I'll be using the Constituency Tree and Tokens analyzers to break text reviews into separate records based on Sentence Separation information and the Conjunction words.

If you'd like to read more about the Linguistic Analysis API and related concepts, I encourage you to read the complete API documentation available at bit.ly/2eTc2Nj.

Now let's go back the original example used for the Text Analytics API: "This phone has a great battery. The display is sharp and bright but the store does not have the apps I need."

There's a subtle change in the example this time as I've removed the period at the end of the second sentence of the original example and used the conjunction word "but" to combine it with the third sentence. You'll see that I have a purpose in doing so.

I'll now call the Analyze Text operation of the Linguistic Analysis API with the following request body:

{
  "language" : "en",
  "analyzerIds" : ["22a6b758-420f-4745-8a3c-46835a67c0d2", "08ea174b-bfdb-4e64-987e-602f85da7f72"],
  "text" : "This phone has a great battery. 
    The display is sharp and bright but the store does not have the apps I need." 
}

I'm sending this request with the Constituency Tree and Tokens analyzers only. Figure 5 shows the result.

Figure 5 Using the Analyze Text Operation of the Linguistic Analysis API

[
  {
    "analyzerId": "22a6b758-420f-4745-8a3c-46835a67c0d2",
    "result": [
      "(TOP (S (NP (DT This) (NN phone)) (VP (VBZ has) 
        (NP (DT a) (JJ great) (NN battery))) (. .)))",
      "(TOP (S (S (NP (DT The) (NN display)) (VP (VBZ is) (ADJP 
        (JJ sharp) (CC and) (JJ bright)))) (CC but) (S 
        (NP (DT the) (NN store)) (VP (VBZ does) (RB not) 
        (VP (VB have) (NP (NP (DT the) (NNS apps)) (SBAR (S 
        (NP (PRP I)) (VP (VBP need)))))))) (. .)))"
    ]
  },
  {
    "analyzerId": "08ea174b-bfdb-4e64-987e-602f85da7f72",
    "result": [
      {
        "Len": 31,
        "Offset": 0,
        "Tokens": [
          {
            "Len": 4,
            "NormalizedToken": "This",
            "Offset": 0,
            "RawToken": "This"
          },
          {
            "Len": 5,
            "NormalizedToken": "phone",
            "Offset": 5,
            "RawToken": "phone"
          },
          {
            "Len": 3,
            "NormalizedToken": "has",
            "Offset": 11,
            "RawToken": "has"
          },
          {
            "Len": 1,
            "NormalizedToken": "a",
            "Offset": 15,
            "RawToken": "a"
          },
          {
            "Len": 5,
            "NormalizedToken": "great",
            "Offset": 17,
            "RawToken": "great"
          },
          {
            "Len": 7,
            "NormalizedToken": "battery",
            "Offset": 23,
            "RawToken": "battery"
          },
          {
            "Len": 1,
            "NormalizedToken": ".",
            "Offset": 30,
            "RawToken": "."
          }
        ]
      },
      {
        "Len": 76,
        "Offset": 32,
        "Tokens": [
          {
            "Len": 3,
            "NormalizedToken": "The",
            "Offset": 32,
            "RawToken": "The"
          },
          {
            "Len": 7,
            "NormalizedToken": "display",
            "Offset": 36,
            "RawToken": "display"
          },
          {
            "Len": 2,
            "NormalizedToken": "is",
            "Offset": 44,
            "RawToken": "is"
          },
          {
            "Len": 5,
            "NormalizedToken": "sharp",
            "Offset": 47,
            "RawToken": "sharp"
          },
          {
            "Len": 3,
            "NormalizedToken": "and",
            "Offset": 53,
            "RawToken": "and"
          },
          {
            "Len": 6,
            "NormalizedToken": "bright",
            "Offset": 57,
            "RawToken": "bright"
          },
          {
            "Len": 3,
            "NormalizedToken": "but",
            "Offset": 64,
            "RawToken": "but"
          },
          {
            "Len": 3,
            "NormalizedToken": "the",
            "Offset": 68,
            "RawToken": "the"
          },
          {
            "Len": 5,
            "NormalizedToken": "store",
            "Offset": 72,
            "RawToken": "store"
          },
          {
            "Len": 4,
            "NormalizedToken": "does",
            "Offset": 78,
            "RawToken": "does"
          },
          {
            "Len": 3,
            "NormalizedToken": "not",
            "Offset": 83,
            "RawToken": "not"
          },
          {
            "Len": 4,
            "NormalizedToken": "have",
            "Offset": 87,
            "RawToken": "have"
          },
          {
            "Len": 3,
            "NormalizedToken": "the",
            "Offset": 92,
            "RawToken": "the"
          },
          {
            "Len": 4,
            "NormalizedToken": "apps",
            "Offset": 96,
            "RawToken": "apps"
          },
          {
            "Len": 1,
            "NormalizedToken": "I",
            "Offset": 101,
            "RawToken": "I"
          },
          {
            "Len": 4,
            "NormalizedToken": "need",
            "Offset": 103,
            "RawToken": "need"
          },
          {
            "Len": 1,
            "NormalizedToken": ".",
            "Offset": 107,
            "RawToken": "."
          }
        ]
      }
    ]
  }

As you can see, the Constituency Tree analyzer broke the text record in to two sentences and indicated the conjunction word "but" with the tag CC in the result. As noted, I purposely removed the period character at the end of the second sentence in the original text in order to highlight this capability of the Linguistic Analysis API.

Better Together

This little experiment demonstrates an extremely reliable way to break the original example into three separate sentences, which can now be processed using the Text Analytics API to gain deeper insight about the customer input. You might think that simple string parsing and splitting would provide a similar result offline, but that's easier said than done - take my word for that.

Now, in the same manner, I can take the output of the Linguistic Analysis API and, with a simple string-manipulation routine, in any programming language, separate the review into individual parts. The original example can be broken down into the following three parts:

  • This phone has a great battery
  • The display is sharp and bright
  • The store does not have the apps I need

Let's run these three parts individually using the Text Analytics API and see what comes back and how this lets you make better sense of the review. I'll now use the key phrase and the sentiment operations of the Text Analytics API and show the output of this API transformed into a powerful targeted analysis of the sentiment reflected in the text input.

I also need to remove the punctuation marks and any conjunctions in order to get the best analysis. I changed my Curl script to pass the tokens one by one first to key phrase endpoint and then to the sentiment endpoint. Figure 6 shows my result.

Figure 6 Using the Text Analytics API on the Output of the Linguistic Analysis API

{
  "documents": [
    {
      "keyPhrases": [
        "great battery"
      ],
      "id": "1"
    }
  ],
  "errors": []
}
{
  "documents": [
    {
      "score": 0.814330127882407,
      "id": "1"
    }
  ],
  "errors": []
}
{
  "documents": [
    {
      "keyPhrases": [
        "display"
      ],
      "id": "1"
    }
  ],
  "errors": []
}
{
  "documents": [
    {
      "score": 0.94089591967051,
      "id": "1"
    }
  ],
  "errors": []
}
{
  "documents": [
    {
      "keyPhrases": [
        "store",
        "apps"
      ],
      "id": "1"
    }
  ],
  "errors": []
}
{
  "documents": [
    {
      "score": 0.255424793209646,
      "id": "1"
    }
  ],
  "errors": []
}

If I compile these results to a human-friendly format, here's what it looks like:

  • This phone has a great batteryKey phrase: great batterySentiment: 81 percent
  • The display is sharp and brightKey phrase: displaySentiment: 94 percent
  • The store does not have the apps I needKey phrase: store, appsSentiment: 25 percent

When discussing the Text Analytics API earlier in this article, I ran the complete review through the API and saw that the review had a 77 percent positive sentiment. However, running the review with the Linguistic API, breaking it down to its individual tokens and then processing them with the Text Analytics API gives a truer assessment of the individual aspects of the phone in question and the targeted sentiment analysis for each of them.

It also made clear that a better apps selection for this phone would have brought the overall positivity of this review much higher than the initial 77 percent.

Wrapping Up

I recently came across the tagline, "Data is the new electricity." Just as electricity became the lifeblood of the second industrial revolution, data is the lifeblood of the AI revolution that's just ahead. As a developer, you gather data all the time, in various forms. One increasingly significant form of data is customer input - as reviews on shopping and traveling portals, in survey responses, feedback and suggestions both off- and online, and more. It's also becoming increasingly common for customers to openly provide such feedback, making it even more important to take this information seriously. Organizations will want to make sure to listen to their customers and take appropriate action to remediate any issues they encounter with products and services. The Text Analytics and the Linguistic Analysis Cognitive Services are just the tools you need to do justice to your customers.


Ashish Sahu is a senior technical evangelist working with Developer Experience at Microsoft India, helping ISVs and startups overcome technical challenges, adopt the latest technologies, and evolve their solutions to the next level. He can be contacted at ashish.sahu@microsoft.com.

Thanks to the following Microsoft technical expert for reviewing this article: Sandeep Alur


Discuss this article in the MSDN Magazine forum