Skip to content

Commit

Permalink
[textanalytics] adds AnalyzeSentimentAction to analyze batch method (A…
Browse files Browse the repository at this point in the history
…zure#18413)

* add AnalyzeSentimentAction code

* update readme and changelog

* update samples

* update tests and recordings for sentiment action

* just look for sentiment in error target
  • Loading branch information
kristapratico authored Apr 29, 2021
1 parent 9ff5225 commit 7d79990
Show file tree
Hide file tree
Showing 29 changed files with 1,831 additions and 628 deletions.
1 change: 1 addition & 0 deletions sdk/textanalytics/azure-ai-textanalytics/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@

**New Features**
- Added enums `EntityConditionality`, `EntityCertainty`, and `EntityAssociation`.
- Added `AnalyzeSentimentAction` as a supported action type for `begin_analyze_batch_actions`.

## 5.1.0b6 (2021-03-09)

Expand Down
20 changes: 18 additions & 2 deletions sdk/textanalytics/azure-ai-textanalytics/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -499,6 +499,7 @@ Note: The Healthcare Entities Analysis service is currently available only in th
- PII Entities Recognition
- Linked Entity Recognition
- Key Phrase Extraction
- Sentiment Analysis

```python
from azure.core.credentials import AzureKeyCredential
Expand All @@ -507,7 +508,8 @@ from azure.ai.textanalytics import (
RecognizeEntitiesAction,
RecognizePiiEntitiesAction,
ExtractKeyPhrasesAction,
RecognizeLinkedEntitiesAction
RecognizeLinkedEntitiesAction,
AnalyzeSentimentAction
)

credential = AzureKeyCredential("<api_key>")
Expand All @@ -524,7 +526,8 @@ poller = text_analytics_client.begin_analyze_batch_actions(
RecognizeEntitiesAction(),
RecognizePiiEntitiesAction(),
ExtractKeyPhrasesAction(),
RecognizeLinkedEntitiesAction()
RecognizeLinkedEntitiesAction(),
AnalyzeSentimentAction()
]
)

Expand Down Expand Up @@ -584,6 +587,19 @@ for idx, doc in enumerate(docs):
print(".........Offset: {}".format(match.offset))
print(".........Length: {}".format(match.length))
print("------------------------------------------")

fifth_action_result = next(result)
print("Results of Sentiment Analysis action:")
docs = [doc for doc in fifth_action_result.document_results if not doc.is_error]

for doc in docs:
print("Overall sentiment: {}".format(doc.sentiment))
print("Scores: positive={}; neutral={}; negative={} \n".format(
doc.confidence_scores.positive,
doc.confidence_scores.neutral,
doc.confidence_scores.negative,
))
print("------------------------------------------")
```

The returned response is an object encapsulating multiple iterables, each representing results of individual analyses.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,7 @@
HealthcareRelation,
HealthcareRelationRole,
HealthcareEntityAssertion,
AnalyzeSentimentAction
)
from ._paging import AnalyzeHealthcareEntitiesResult
from ._generated.v3_1_preview_5.models import (
Expand Down Expand Up @@ -104,7 +105,8 @@
"HealthcareEntityAssertion",
"EntityConditionality",
"EntityCertainty",
"EntityAssociation"
"EntityAssociation",
"AnalyzeSentimentAction"
]

__version__ = VERSION
Original file line number Diff line number Diff line change
Expand Up @@ -1362,7 +1362,8 @@ class AnalyzeBatchActionsType(str, Enum):
RECOGNIZE_ENTITIES = "recognize_entities" #: Entities Recognition action.
RECOGNIZE_PII_ENTITIES = "recognize_pii_entities" #: PII Entities Recognition action.
EXTRACT_KEY_PHRASES = "extract_key_phrases" #: Key Phrase Extraction action.
RECOGNIZE_LINKED_ENTITIES = "recognize_linked_entities" #: Linked Entities Recognition action.
RECOGNIZE_LINKED_ENTITIES = "recognize_linked_entities" #: Linked Entities Recognition action.
ANALYZE_SENTIMENT = "analyze_sentiment" #: Sentiment Analysis action.


class AnalyzeBatchActionsResult(DictMixin):
Expand Down Expand Up @@ -1460,6 +1461,58 @@ def to_generated(self):
)


class AnalyzeSentimentAction(DictMixin):
"""AnalyzeSentimentAction encapsulates the parameters for starting a long-running
Sentiment Analysis operation.
If you just want to analyze sentiment in a list of documents, and not perform a batch
of long running actions on the input of documents, call method `analyze_sentiment` instead
of interfacing with this model.
:keyword str model_version: The model version to use for the analysis.
:keyword bool show_opinion_mining: Whether to mine the opinions of a sentence and conduct more
granular analysis around the aspects of a product or service (also known as
aspect-based sentiment analysis). If set to true, the returned
:class:`~azure.ai.textanalytics.SentenceSentiment` objects
will have property `mined_opinions` containing the result of this analysis.
:keyword str string_index_type: Specifies the method used to interpret string offsets.
`UnicodeCodePoint`, the Python encoding, is the default. To override the Python default,
you can also pass in `Utf16CodePoint` or TextElement_v8`. For additional information
see https://aka.ms/text-analytics-offsets
:ivar str model_version: The model version to use for the analysis.
:ivar bool show_opinion_mining: Whether to mine the opinions of a sentence and conduct more
granular analysis around the aspects of a product or service (also known as
aspect-based sentiment analysis). If set to true, the returned
:class:`~azure.ai.textanalytics.SentenceSentiment` objects
will have property `mined_opinions` containing the result of this analysis.
:ivar str string_index_type: Specifies the method used to interpret string offsets.
`UnicodeCodePoint`, the Python encoding, is the default. To override the Python default,
you can also pass in `Utf16CodePoint` or TextElement_v8`. For additional information
see https://aka.ms/text-analytics-offsets
"""

def __init__(self, **kwargs):
self.model_version = kwargs.get('model_version', "latest")
self.show_opinion_mining = kwargs.get('show_opinion_mining', False)
self.string_index_type = kwargs.get('string_index_type', None)

def __repr__(self, **kwargs):
return "AnalyzeSentimentAction(model_version={}, show_opinion_mining={}, string_index_type={}".format(
self.model_version,
self.show_opinion_mining,
self.string_index_type
)[:1024]

def to_generated(self):
return _latest_preview_models.SentimentAnalysisTask(
parameters=_latest_preview_models.SentimentAnalysisTaskParameters(
model_version=self.model_version,
opinion_mining=self.show_opinion_mining,
string_index_type=self.string_index_type
)
)


class RecognizePiiEntitiesAction(DictMixin):
"""RecognizePiiEntitiesAction encapsulates the parameters for starting a long-running PII
Entities Recognition operation.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@
RecognizeEntitiesAction,
RecognizePiiEntitiesAction,
RecognizeLinkedEntitiesAction,
AnalyzeSentimentAction,
AnalyzeBatchActionsType,
)

Expand Down Expand Up @@ -75,6 +76,8 @@ def _determine_action_type(action):
return AnalyzeBatchActionsType.RECOGNIZE_PII_ENTITIES
if isinstance(action, RecognizeLinkedEntitiesAction):
return AnalyzeBatchActionsType.RECOGNIZE_LINKED_ENTITIES
if isinstance(action, AnalyzeSentimentAction):
return AnalyzeBatchActionsType.ANALYZE_SENTIMENT
return AnalyzeBatchActionsType.EXTRACT_KEY_PHRASES

def _check_string_index_type_arg(string_index_type_arg, api_version, string_index_type_default="UnicodeCodePoint"):
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -205,6 +205,8 @@ def _get_deserialization_callback_from_task_type(task_type):
return pii_entities_result
if task_type == AnalyzeBatchActionsType.RECOGNIZE_LINKED_ENTITIES:
return linked_entities_result
if task_type == AnalyzeBatchActionsType.ANALYZE_SENTIMENT:
return sentiment_result
return key_phrases_result

def _get_property_name_from_task_type(task_type):
Expand All @@ -214,14 +216,17 @@ def _get_property_name_from_task_type(task_type):
return "entity_recognition_pii_tasks"
if task_type == AnalyzeBatchActionsType.RECOGNIZE_LINKED_ENTITIES:
return "entity_linking_tasks"
if task_type == AnalyzeBatchActionsType.ANALYZE_SENTIMENT:
return "sentiment_analysis_tasks"
return "key_phrase_extraction_tasks"

def _num_tasks_in_current_page(returned_tasks_object):
return (
len(returned_tasks_object.entity_recognition_tasks or []) +
len(returned_tasks_object.entity_recognition_pii_tasks or []) +
len(returned_tasks_object.key_phrase_extraction_tasks or []) +
len(returned_tasks_object.entity_linking_tasks or [])
len(returned_tasks_object.entity_linking_tasks or []) +
len(returned_tasks_object.sentiment_analysis_tasks or [])
)

def _get_task_type_from_error(error):
Expand All @@ -231,6 +236,8 @@ def _get_task_type_from_error(error):
return AnalyzeBatchActionsType.RECOGNIZE_ENTITIES
if "entitylinking" in error.target.lower():
return AnalyzeBatchActionsType.RECOGNIZE_LINKED_ENTITIES
if "sentiment" in error.target.lower():
return AnalyzeBatchActionsType.ANALYZE_SENTIMENT
return AnalyzeBatchActionsType.EXTRACT_KEY_PHRASES

def _get_mapped_errors(analyze_job_state):
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,7 @@
RecognizePiiEntitiesAction,
RecognizeLinkedEntitiesAction,
ExtractKeyPhrasesAction,
AnalyzeSentimentAction,
AnalyzeHealthcareEntitiesResultItem,
AnalyzeBatchActionsResult,
)
Expand Down Expand Up @@ -744,7 +745,7 @@ def _analyze_result_callback(self, doc_id_order, task_order, raw_response, _, he
def begin_analyze_batch_actions( # type: ignore
self,
documents, # type: Union[List[str], List[TextDocumentInput], List[Dict[str, str]]]
actions, # type: List[Union[RecognizeEntitiesAction, RecognizeLinkedEntitiesAction, RecognizePiiEntitiesAction, ExtractKeyPhrasesAction]] # pylint: disable=line-too-long
actions, # type: List[Union[RecognizeEntitiesAction, RecognizeLinkedEntitiesAction, RecognizePiiEntitiesAction, ExtractKeyPhrasesAction, AnalyzeSentimentAction]] # pylint: disable=line-too-long
**kwargs # type: Any
): # type: (...) -> LROPoller[ItemPaged[AnalyzeBatchActionsResult]]
"""Start a long-running operation to perform a variety of text analysis actions over a batch of documents.
Expand All @@ -763,7 +764,7 @@ def begin_analyze_batch_actions( # type: ignore
Duplicate actions in list not supported.
:type actions:
list[RecognizeEntitiesAction or RecognizePiiEntitiesAction or ExtractKeyPhrasesAction or
RecognizeLinkedEntitiesAction]
RecognizeLinkedEntitiesAction or AnalyzeSentimentAction]
:keyword str display_name: An optional display name to set for the requested analysis.
:keyword str language: The 2 letter ISO 639-1 representation of language for the
entire batch. For example, use "en" for English; "es" for Spanish etc.
Expand Down Expand Up @@ -825,6 +826,10 @@ def begin_analyze_batch_actions( # type: ignore
a for a in actions
if _determine_action_type(a) == AnalyzeBatchActionsType.RECOGNIZE_LINKED_ENTITIES
]
],
sentiment_analysis_tasks=[
t.to_generated() for t in
[a for a in actions if _determine_action_type(a) == AnalyzeBatchActionsType.ANALYZE_SENTIMENT]
]
)
analyze_body = self._client.models(api_version='v3.1-preview.5').AnalyzeBatchInput(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,8 @@
ExtractKeyPhrasesAction,
AnalyzeBatchActionsResult,
AnalyzeBatchActionsType,
RecognizeLinkedEntitiesAction,
AnalyzeSentimentAction
)
from .._lro import TextAnalyticsOperationResourcePolling
from .._async_lro import (
Expand Down Expand Up @@ -727,7 +729,7 @@ def _analyze_result_callback(self, doc_id_order, task_order, raw_response, _, he
async def begin_analyze_batch_actions( # type: ignore
self,
documents, # type: Union[List[str], List[TextDocumentInput], List[Dict[str, str]]]
actions, # type: List[Union[RecognizeEntitiesAction, RecognizePiiEntitiesAction, ExtractKeyPhrasesAction]]
actions, # type: List[Union[RecognizeEntitiesAction, RecognizeLinkedEntitiesAction, RecognizePiiEntitiesAction, ExtractKeyPhrasesAction, AnalyzeSentimentAction]] # pylint: disable=line-too-long
**kwargs # type: Any
): # type: (...) -> AsyncLROPoller[AsyncItemPaged[AnalyzeBatchActionsResult]]
"""Start a long-running operation to perform a variety of text analysis actions over a batch of documents.
Expand All @@ -746,7 +748,7 @@ async def begin_analyze_batch_actions( # type: ignore
Duplicate actions in list not supported.
:type actions:
list[RecognizeEntitiesAction or RecognizePiiEntitiesAction or ExtractKeyPhrasesAction or
RecognizeLinkedEntitiesAction]
RecognizeLinkedEntitiesAction or AnalyzeSentimentAction]
:keyword str display_name: An optional display name to set for the requested analysis.
:keyword str language: The 2 letter ISO 639-1 representation of language for the
entire batch. For example, use "en" for English; "es" for Spanish etc.
Expand Down Expand Up @@ -808,6 +810,10 @@ async def begin_analyze_batch_actions( # type: ignore
a for a in actions if \
_determine_action_type(a) == AnalyzeBatchActionsType.RECOGNIZE_LINKED_ENTITIES
]
],
sentiment_analysis_tasks=[
t.to_generated() for t in
[a for a in actions if _determine_action_type(a) == AnalyzeBatchActionsType.ANALYZE_SENTIMENT]
]
)
analyze_body = self._client.models(api_version='v3.1-preview.5').AnalyzeBatchInput(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,8 +11,9 @@
DESCRIPTION:
This sample demonstrates how to submit a collection of text documents for analysis, which consists of a variety
of text analysis actions, such as Entity Recognition, PII Entity Recognition,
or Key Phrase Extraction. The response will contain results from each of the individual actions specified in the request.
of text analysis actions, such as Entity Recognition, PII Entity Recognition, Linked Entity Recognition,
Sentiment Analysis, or Key Phrase Extraction. The response will contain results from each of the individual
actions specified in the request.
USAGE:
python sample_analyze_batch_actions_async.py
Expand All @@ -37,6 +38,7 @@ async def analyze_async(self):
RecognizeLinkedEntitiesAction,
RecognizePiiEntitiesAction,
ExtractKeyPhrasesAction,
AnalyzeSentimentAction,
AnalyzeBatchActionsType
)

Expand Down Expand Up @@ -65,7 +67,8 @@ async def analyze_async(self):
RecognizeEntitiesAction(),
RecognizePiiEntitiesAction(),
ExtractKeyPhrasesAction(),
RecognizeLinkedEntitiesAction()
RecognizeLinkedEntitiesAction(),
AnalyzeSentimentAction()
]
)

Expand Down Expand Up @@ -124,6 +127,17 @@ async def analyze_async(self):
print(".........Length: {}".format(match.length))
print("------------------------------------------")

if action_result.action_type == AnalyzeBatchActionsType.ANALYZE_SENTIMENT:
print("Results of Sentiment Analysis action:")
for doc in action_result.document_results:
print("Overall sentiment: {}".format(doc.sentiment))
print("Scores: positive={}; neutral={}; negative={} \n".format(
doc.confidence_scores.positive,
doc.confidence_scores.neutral,
doc.confidence_scores.negative,
))
print("------------------------------------------")

# [END analyze_async]


Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,8 +11,9 @@
DESCRIPTION:
This sample demonstrates how to submit a collection of text documents for analysis, which consists of a variety
of text analysis actions, such as Entity Recognition, PII Entity Recognition,
or Key Phrase Extraction. The response will contain results from each of the individual actions specified in the request.
of text analysis actions, such as Entity Recognition, PII Entity Recognition, Linked Entity Recognition,
Sentiment Analysis, or Key Phrase Extraction. The response will contain results from each of the individual
actions specified in the request.
USAGE:
python sample_analyze_batch_actions.py
Expand All @@ -37,6 +38,7 @@ def analyze(self):
RecognizeLinkedEntitiesAction,
RecognizePiiEntitiesAction,
ExtractKeyPhrasesAction,
AnalyzeSentimentAction,
PiiEntityDomainType,
)

Expand Down Expand Up @@ -64,7 +66,8 @@ def analyze(self):
RecognizeEntitiesAction(),
RecognizePiiEntitiesAction(domain_filter=PiiEntityDomainType.PROTECTED_HEALTH_INFORMATION),
ExtractKeyPhrasesAction(),
RecognizeLinkedEntitiesAction()
RecognizeLinkedEntitiesAction(),
AnalyzeSentimentAction()
],
)

Expand Down Expand Up @@ -129,6 +132,19 @@ def analyze(self):
print(".........Length: {}".format(match.length))
print("------------------------------------------")

fifth_action_result = action_results[4]
print("Results of Sentiment Analysis action:")
docs = [doc for doc in fifth_action_result.document_results if not doc.is_error]

for doc in docs:
print("Overall sentiment: {}".format(doc.sentiment))
print("Scores: positive={}; neutral={}; negative={} \n".format(
doc.confidence_scores.positive,
doc.confidence_scores.neutral,
doc.confidence_scores.negative,
))
print("------------------------------------------")

# [END analyze]


Expand Down
Loading

0 comments on commit 7d79990

Please sign in to comment.