Commit 435d1076 authored by J. Fernando Sánchez's avatar J. Fernando Sánchez
Browse files

Add headers and minor fixes

parent c4321dc5
...@@ -4,8 +4,16 @@ All notable changes to this project will be documented in this file. ...@@ -4,8 +4,16 @@ All notable changes to this project will be documented in this file.
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
## [Unreleased] ## [1.0.1]
### Added
* License headers
* Description for PyPI (setup.py)
### Changed
* The evaluation tab shows datasets inline, and a tooltip shows the number of instances
* The docs should be clearer now
## [1.0.0]
### Fixed ### Fixed
* Restored hash changing function in `main.js` * Restored hash changing function in `main.js`
......
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
{
"cells": [
{
"cell_type": "markdown",
"metadata": {},
"source": [
"# Senpy in 1 minute\n",
"\n",
"This mini-tutorial only shows how to annotate with a service.\n",
"We will use the [demo server](http://senpy.gsi.upm.es), which runs some open source plugins for sentiment and emotion analysis."
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"Annotating with senpy is as simple as issuing an HTTP request to the API using your favourite tool.\n",
"This is just an example using curl:"
]
},
{
"cell_type": "code",
"execution_count": 16,
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"{\r\n",
" \"@context\": \"http://senpy.gsi.upm.es/api/contexts/YXBpL3NlbnRpbWVudDE0MD8j\",\r\n",
" \"@type\": \"Results\",\r\n",
" \"entries\": [\r\n",
" {\r\n",
" \"@id\": \"prefix:\",\r\n",
" \"@type\": \"Entry\",\r\n",
" \"marl:hasOpinion\": [\r\n",
" {\r\n",
" \"@type\": \"Sentiment\",\r\n",
" \"marl:hasPolarity\": \"marl:Positive\",\r\n",
" \"prov:wasGeneratedBy\": \"prefix:Analysis_1554389334.6431913\"\r\n",
" }\r\n",
" ],\r\n",
" \"nif:isString\": \"Senpy is awesome\",\r\n",
" \"onyx:hasEmotionSet\": []\r\n",
" }\r\n",
" ]\r\n",
"}"
]
}
],
"source": [
"!curl \"http://senpy.gsi.upm.es/api/sentiment140\" --data-urlencode \"input=Senpy is awesome\""
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"**Congratulations**, you've used your first senpy service!"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"Here is the equivalent using the `requests` library:"
]
},
{
"cell_type": "code",
"execution_count": 17,
"metadata": {
"scrolled": true
},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"{\n",
" \"@context\": \"http://senpy.gsi.upm.es/api/contexts/YXBpL3NlbnRpbWVudDE0MD9pbnB1dD1TZW5weStpcythd2Vzb21lIw%3D%3D\",\n",
" \"@type\": \"Results\",\n",
" \"entries\": [\n",
" {\n",
" \"@id\": \"prefix:\",\n",
" \"@type\": \"Entry\",\n",
" \"marl:hasOpinion\": [\n",
" {\n",
" \"@type\": \"Sentiment\",\n",
" \"marl:hasPolarity\": \"marl:Positive\",\n",
" \"prov:wasGeneratedBy\": \"prefix:Analysis_1554389335.9803226\"\n",
" }\n",
" ],\n",
" \"nif:isString\": \"Senpy is awesome\",\n",
" \"onyx:hasEmotionSet\": []\n",
" }\n",
" ]\n",
"}\n"
]
}
],
"source": [
"import requests\n",
"res = requests.get('http://senpy.gsi.upm.es/api/sentiment140',\n",
" params={\"input\": \"Senpy is awesome\",})\n",
"print(res.text)"
]
}
],
"metadata": {
"anaconda-cloud": {},
"kernelspec": {
"display_name": "Python 3",
"language": "python",
"name": "python3"
},
"language_info": {
"codemirror_mode": {
"name": "ipython",
"version": 3
},
"file_extension": ".py",
"mimetype": "text/x-python",
"name": "python",
"nbconvert_exporter": "python",
"pygments_lexer": "ipython3",
"version": "3.7.3"
},
"toc": {
"colors": {
"hover_highlight": "#DAA520",
"running_highlight": "#FF0000",
"selected_highlight": "#FFD700"
},
"moveMenuLeft": true,
"nav_menu": {
"height": "68px",
"width": "252px"
},
"navigate_menu": true,
"number_sections": true,
"sideBar": true,
"threshold": 4,
"toc_cell": false,
"toc_section_display": "block",
"toc_window_display": false
}
},
"nbformat": 4,
"nbformat_minor": 1
}
%% Cell type:markdown id: tags:
# Senpy in 1 minute
This mini-tutorial only shows how to annotate with a service.
We will use the [demo server](http://senpy.gsi.upm.es), which runs some open source plugins for sentiment and emotion analysis.
%% Cell type:markdown id: tags:
Annotating with senpy is as simple as issuing an HTTP request to the API using your favourite tool.
This is just an example using curl:
%% Cell type:code id: tags:
``` python
!curl "http://senpy.gsi.upm.es/api/sentiment140" --data-urlencode "input=Senpy is awesome"
```
%%%% Output: stream
{
"@context": "http://senpy.gsi.upm.es/api/contexts/YXBpL3NlbnRpbWVudDE0MD8j",
"@type": "Results",
"entries": [
{
"@id": "prefix:",
"@type": "Entry",
"marl:hasOpinion": [
{
"@type": "Sentiment",
"marl:hasPolarity": "marl:Positive",
"prov:wasGeneratedBy": "prefix:Analysis_1554389334.6431913"
}
],
"nif:isString": "Senpy is awesome",
"onyx:hasEmotionSet": []
}
]
}
%% Cell type:markdown id: tags:
**Congratulations**, you've used your first senpy service!
%% Cell type:markdown id: tags:
Here is the equivalent using the `requests` library:
%% Cell type:code id: tags:
``` python
import requests
res = requests.get('http://senpy.gsi.upm.es/api/sentiment140',
params={"input": "Senpy is awesome",})
print(res.text)
```
%%%% Output: stream
{
"@context": "http://senpy.gsi.upm.es/api/contexts/YXBpL3NlbnRpbWVudDE0MD9pbnB1dD1TZW5weStpcythd2Vzb21lIw%3D%3D",
"@type": "Results",
"entries": [
{
"@id": "prefix:",
"@type": "Entry",
"marl:hasOpinion": [
{
"@type": "Sentiment",
"marl:hasPolarity": "marl:Positive",
"prov:wasGeneratedBy": "prefix:Analysis_1554389335.9803226"
}
],
"nif:isString": "Senpy is awesome",
"onyx:hasEmotionSet": []
}
]
}
This diff is collapsed.
This diff is collapsed.
Advanced usage
--------------
.. toctree::
:maxdepth: 1
server-cli
conversion
commandline
development
Command line
============
Although the main use of senpy is to publish services, the tool can also be used locally to analyze text in the command line.
This is a short video demonstration:
.. image:: https://asciinema.org/a/9uwef1ghkjk062cw2t4mhzpyk.png
:width: 100%
:target: https://asciinema.org/a/9uwef1ghkjk062cw2t4mhzpyk
:alt: CLI demo
...@@ -130,6 +130,7 @@ html_theme_options = { ...@@ -130,6 +130,7 @@ html_theme_options = {
'github_user': 'gsi-upm', 'github_user': 'gsi-upm',
'github_repo': 'senpy', 'github_repo': 'senpy',
'github_banner': True, 'github_banner': True,
'sidebar_collapse': True,
} }
......
Conversion Automatic Model Conversion
---------- --------------------------
Senpy includes experimental support for emotion/sentiment conversion plugins. Senpy includes support for emotion and sentiment conversion.
When a user requests a specific model, senpy will choose a strategy to convert the model that the service usually outputs and the model requested by the user.
Out of the box, senpy can convert from the `emotionml:pad` (pleasure-arousal-dominance) dimensional model to `emoml:big6` (Ekman's big-6) categories, and vice versa.
This specific conversion uses a series of dimensional centroids (`emotionml:pad`) for each emotion category (`emotionml:big6`).
A dimensional value is converted to a category by looking for the nearest centroid.
The centroids are calculated according to this article:
.. code-block:: text
Kim, S. M., Valitutti, A., & Calvo, R. A. (2010, June).
Evaluation of unsupervised emotion models to textual affect recognition.
In Proceedings of the NAACL HLT 2010 Workshop on Computational Approaches to Analysis and Generation of Emotion in Text (pp. 62-70).
Association for Computational Linguistics.
It is possible to add new conversion strategies by `Developing a conversion plugin`_.
Use Use
=== ===
Consider the original query: http://127.0.0.1:5000/api/?i=hello&algo=emotion-random Consider the following query to an emotion service: http://senpy.gsi.upm.es/api/emotion-anew?i=good
The requested plugin (emotion-random) returns emotions using Ekman's model (or big6 in EmotionML): The requested plugin (emotion-random) returns emotions using the VAD space (FSRE dimensions in EmotionML):
.. code:: json .. code:: json
... rest of the document ... [
{ {
"@type": "emotionSet", "@type": "EmotionSet",
"onyx:hasEmotion": { "onyx:hasEmotion": [
"@type": "emotion", {
"onyx:hasEmotionCategory": "emoml:big6anger" "@type": "Emotion",
}, "emoml:pad-dimensions_arousal": 5.43,
"prov:wasGeneratedBy": "plugins/emotion-random_0.1" "emoml:pad-dimensions_dominance": 6.41,
} "emoml:pad-dimensions_pleasure": 7.47,
"prov:wasGeneratedBy": "prefix:Analysis_1562744784.8789825"
}
],
"prov:wasGeneratedBy": "prefix:Analysis_1562744784.8789825"
}
]
To get these emotions in VAD space (FSRE dimensions in EmotionML), we'd do this: To get the equivalent of these emotions in Ekman's categories (i.e., Ekman's Big 6 in EmotionML), we'd do this:
http://127.0.0.1:5000/api/?i=hello&algo=emotion-random&emotionModel=emoml:fsre-dimensions http://senpy.gsi.upm.es/api/emotion-anew?i=good&emotion-model=emoml:big6
This call, provided there is a valid conversion plugin from Ekman's to VAD, would return something like this: This call, provided there is a valid conversion plugin from Ekman's to VAD, would return something like this:
.. code:: json .. code:: json
[
... rest of the document ... {
"@type": "EmotionSet",
"onyx:hasEmotion": [
{ {
"@type": "emotionSet", "@type": "Emotion",
"onyx:hasEmotion": { "onyx:algorithmConfidence": 4.4979,
"@type": "emotion", "onyx:hasEmotionCategory": "emoml:big6happiness"
"onyx:hasEmotionCategory": "emoml:big6anger"
},
"prov:wasGeneratedBy": "plugins/emotion-random.1"
}, {
"@type": "emotionSet",
"onyx:hasEmotion": {
"@type": "emotion",
"A": 7.22,
"D": 6.28,
"V": 8.6
},
"prov:wasGeneratedBy": "plugins/Ekman2VAD_0.1"
} }
],
"prov:wasDerivedFrom": {
"@id": "Emotions0",
"@type": "EmotionSet",
"onyx:hasEmotion": [
{
"@id": "Emotion0",
"@type": "Emotion",
"emoml:pad-dimensions_arousal": 5.43,
"emoml:pad-dimensions_dominance": 6.41,
"emoml:pad-dimensions_pleasure": 7.47,
"prov:wasGeneratedBy": "prefix:Analysis_1562745220.1553965"
}
],
"prov:wasGeneratedBy": "prefix:Analysis_1562745220.1553965"
},
"prov:wasGeneratedBy": "prefix:Analysis_1562745220.1570725"
}
]
That is called a *full* response, as it simply adds the converted emotion alongside. That is called a *full* response, as it simply adds the converted emotion alongside.
It is also possible to get the original emotion nested within the new converted emotion, using the `conversion=nested` parameter: It is also possible to get the original emotion nested within the new converted emotion, using the `conversion=nested` parameter:
http://senpy.gsi.upm.es/api/emotion-anew?i=good&emotion-model=emoml:big6&conversion=nested
.. code:: json .. code:: json
[
{
"@type": "EmotionSet",
"onyx:hasEmotion": [
{
"@type": "Emotion",
"onyx:algorithmConfidence": 4.4979,
"onyx:hasEmotionCategory": "emoml:big6happiness"
}
],
"prov:wasDerivedFrom": {
"@id": "Emotions0",
"@type": "EmotionSet",
"onyx:hasEmotion": [
{
"@id": "Emotion0",
"@type": "Emotion",
"emoml:pad-dimensions_arousal": 5.43,
"emoml:pad-dimensions_dominance": 6.41,
"emoml:pad-dimensions_pleasure": 7.47,
"prov:wasGeneratedBy": "prefix:Analysis_1562744962.896306"
}
],
"prov:wasGeneratedBy": "prefix:Analysis_1562744962.896306"
},
"prov:wasGeneratedBy": "prefix:Analysis_1562744962.8978968"
}
]
... rest of the document ...
{
"@type": "emotionSet",
"onyx:hasEmotion": {
"@type": "emotion",
"onyx:hasEmotionCategory": "emoml:big6anger"
},
"prov:wasGeneratedBy": "plugins/emotion-random.1"
"onyx:wasDerivedFrom": {
"@type": "emotionSet",
"onyx:hasEmotion": {
"@type": "emotion",
"A": 7.22,
"D": 6.28,
"V": 8.6
},
"prov:wasGeneratedBy": "plugins/Ekman2VAD_0.1"
}
}
Lastly, `conversion=filtered` would only return the converted emotions. Lastly, `conversion=filtered` would only return the converted emotions.
.. code:: json
[
{
"@type": "EmotionSet",
"onyx:hasEmotion": [
{
"@type": "Emotion",
"onyx:algorithmConfidence": 4.4979,
"onyx:hasEmotionCategory": "emoml:big6happiness"
}
],
"prov:wasGeneratedBy": "prefix:Analysis_1562744925.7322266"
}
]
Developing a conversion plugin Developing a conversion plugin
================================ ==============================
Conversion plugins are discovered by the server just like any other plugin. Conversion plugins are discovered by the server just like any other plugin.
The difference is the slightly different API, and the need to specify the `source` and `target` of the conversion. The difference is the slightly different API, and the need to specify the `source` and `target` of the conversion.
...@@ -106,7 +165,6 @@ For instance, an emotion conversion plugin needs the following: ...@@ -106,7 +165,6 @@ For instance, an emotion conversion plugin needs the following:
.. code:: python .. code:: python
...@@ -114,3 +172,6 @@ For instance, an emotion conversion plugin needs the following: ...@@ -114,3 +172,6 @@ For instance, an emotion conversion plugin needs the following:
def convert(self, emotionSet, fromModel, toModel, params): def convert(self, emotionSet, fromModel, toModel, params):
pass pass
More implementation details are shown in the `centroids plugin <https://github.com/gsi-upm/senpy/blob/master/senpy/plugins/postprocessing/emotion/centroids.py>`_.
...@@ -2,7 +2,7 @@ Demo ...@@ -2,7 +2,7 @@ Demo
---- ----
There is a demo available on http://senpy.gsi.upm.es/, where you can test a live instance of Senpy, with several open source plugins. There is a demo available on http://senpy.gsi.upm.es/, where you can test a live instance of Senpy, with several open source plugins.
You can use the playground (a web interface) or make HTTP requests to the service API. You can use the playground (a web interface) or the HTTP API.
.. image:: playground-0.20.png .. image:: playground-0.20.png
:target: http://senpy.gsi.upm.es :target: http://senpy.gsi.upm.es
......
...@@ -19,6 +19,7 @@ Sharing your sentiment analysis with the world has never been easier! ...@@ -19,6 +19,7 @@ Sharing your sentiment analysis with the world has never been easier!
.. toctree:: .. toctree::
:maxdepth: 1 :maxdepth: 1
server-cli
plugins-quickstart plugins-quickstart
plugins-faq plugins-faq
plugins-definition plugins-definition
...@@ -12,24 +12,97 @@ Welcome to Senpy's documentation! ...@@ -12,24 +12,97 @@ Welcome to Senpy's documentation!
.. image:: https://img.shields.io/pypi/l/requests.svg .. image:: https://img.shields.io/pypi/l/requests.svg
:target: https://lab.gsi.upm.es/senpy/senpy/ :target: https://lab.gsi.upm.es/senpy/senpy/
Senpy is a framework to build sentiment and emotion analysis services.
It provides functionalities for:
Senpy is a framework for sentiment and emotion analysis services. - developing sentiment and emotion classifier and exposing them as an HTTP service
Senpy services are interchangeable and easy to use because they share a common semantic :doc:`apischema`. - requesting sentiment and emotion analysis from different providers (i.e. Vader, Sentimet140, ...) using the same interface (:doc:`apischema`). In this way, applications do not depend on the API offered for these services.
- combining services that use different sentiment model (e.g. polarity between [-1, 1] or [0,1] or emotion models (e.g. Ekkman or VAD)
- evaluating sentiment algorithms with well known datasets
If you interested in consuming Senpy services, read :doc:`Quickstart`.
Using senpy services is as simple as sending an HTTP request with your favourite tool or library.
Let's analyze the sentiment of the text "Senpy is awesome".
We can call the `Sentiment140 <http://www.sentiment140.com/>`_ service with an HTTP request using curl:
.. code:: shell
:emphasize-lines: 14,18
$ curl "http://senpy.gsi.upm.es/api/sentiment140" \
--data-urlencode "input=Senpy is awesome"
{
"@context": "http://senpy.gsi.upm.es/api/contexts/YXBpL3NlbnRpbWVudDE0MD8j",
"@type": "Results",
"entries": [
{
"@id": "prefix:",
"@type": "Entry",
"marl:hasOpinion": [
{
"@type": "Sentiment",
"marl:hasPolarity": "marl:Positive",
"prov:wasGeneratedBy": "prefix:Analysis_1554389334.6431913"
}
],
"nif:isString": "Senpy is awesome",
"onyx:hasEmotionSet": []
}
]
}
Congratulations, you’ve used your first senpy service!
You can observe the result: the polarity is positive (marl:Positive). The reason of this prefix is that Senpy follows a linked data approach.
You can analyze the same sentence using a different sentiment service (e.g. Vader) and requesting a different format (e.g. turtle):
.. code:: shell
$ curl "http://senpy.gsi.upm.es/api/sentiment-vader" \
--data-urlencode "input=Senpy is awesome" \
--data-urlencode "outformat=turtle"
@prefix : <http://www.gsi.upm.es/onto/senpy/ns#> .
@prefix endpoint: <http://senpy.gsi.upm.es/api/> .
@prefix marl: <http://www.gsi.dit.upm.es/ontologies/marl/ns#> .
@prefix nif: <http://persistence.uni-leipzig.org/nlp2rdf/ontologies/nif-core#> .
@prefix prefix: <http://senpy.invalid/> .
@prefix prov: <http://www.w3.org/ns/prov#> .
@prefix senpy: <http://www.gsi.upm.es/onto/senpy/ns#> .
prefix: a senpy:Entry ;
nif:isString "Senpy is awesome" ;
marl:hasOpinion [ a senpy:Sentiment ;
marl:hasPolarity "marl:Positive" ;
marl:polarityValue 6.72e-01 ;
prov:wasGeneratedBy prefix:Analysis_1562668175.9808676 ] .
[] a senpy:Results ;
prov:used pref