centroids.py 5.69 KB
Newer Older
1
2
3
4
5
6
7
8
from senpy.plugins import EmotionConversionPlugin
from senpy.models import EmotionSet, Emotion, Error

import logging
logger = logging.getLogger(__name__)


class CentroidConversion(EmotionConversionPlugin):
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
    def __init__(self, info):
        if 'centroids' not in info:
            raise Error('Centroid conversion plugins should provide '
                        'the centroids in their senpy file')
        if 'onyx:doesConversion' not in info:
            if 'centroids_direction' not in info:
                raise Error('Please, provide centroids direction')

            cf, ct = info['centroids_direction']
            info['onyx:doesConversion'] = [{
                'onyx:conversionFrom': cf,
                'onyx:conversionTo': ct
            }, {
                'onyx:conversionFrom': ct,
                'onyx:conversionTo': cf
            }]

        if 'aliases' in info:
            aliases = info['aliases']
            ncentroids = {}
            for k1, v1 in info['centroids'].items():
                nv1 = {}
                for k2, v2 in v1.items():
                    nv1[aliases.get(k2, k2)] = v2
                ncentroids[aliases.get(k1, k1)] = nv1
            info['centroids'] = ncentroids
35

36
        super(CentroidConversion, self).__init__(info)
37

38
39
40
41
42
43
44
45
        self.dimensions = set()
        for c in self.centroids.values():
            self.dimensions.update(c.keys())
        self.neutralPoints = self.get("neutralPoints", dict())
        if not self.neutralPoints:
            for i in self.dimensions:
                self.neutralPoints[i] = self.get("neutralValue", 0)

46
    def _forward_conversion(self, original):
47
48
49
50
51
        """Sum the VAD value of all categories found weighted by intensity.
        Intensities are scaled by onyx:maxIntensityValue if it is present, else maxIntensityValue
        is assumed to be one. Emotion entries that do not have onxy:hasEmotionIntensity specified
        are assumed to have maxIntensityValue. Emotion entries that do not have
        onyx:hasEmotionCategory specified are ignored."""
52
        res = Emotion()
53
        maxIntensity = float(original.get("onyx:maxIntensityValue", 1))
54
        for e in original.onyx__hasEmotion:
55
56
            category = e.get("onyx:hasEmotionCategory", None)
            if not category:
57
                continue
58
59
            intensity = e.get("onyx:hasEmotionIntensity", maxIntensity) / maxIntensity
            if not intensity:
60
                continue
61
            centroid = self.centroids.get(category, None)
62
63
            if centroid:
                for dim, value in centroid.items():
64
65
66
67
                    neutral = self.neutralPoints[dim]
                    if dim not in res:
                        res[dim] = 0
                    res[dim] += (value - neutral) * intensity + neutral
68
69
70
71
        return res

    def _backwards_conversion(self, original):
        """Find the closest category"""
72
73
74
75
76
77
78
79
        centroids = self.centroids
        neutralPoints = self.neutralPoints
        dimensions = self.dimensions

        def distance_k(centroid, original, k):
            # k component of the distance between the value and a given centroid
            return (centroid.get(k, neutralPoints[k]) - original.get(k, neutralPoints[k]))**2

80
        def distance(centroid):
81
82
83
            return sum(distance_k(centroid, original, k) for k in dimensions)

        emotion = min(centroids, key=lambda x: distance(centroids[x]))
84
85

        result = Emotion(onyx__hasEmotionCategory=emotion)
86
        result.onyx__algorithmConfidence = distance(centroids[emotion])
87
88
89
90
91
        return result

    def convert(self, emotionSet, fromModel, toModel, params):

        cf, ct = self.centroids_direction
92
93
        logger.debug(
            '{}\n{}\n{}\n{}'.format(emotionSet, fromModel, toModel, params))
94
        e = EmotionSet()
95
        if fromModel == cf and toModel == ct:
96
            e.onyx__hasEmotion.append(self._forward_conversion(emotionSet))
97
        elif fromModel == ct and toModel == cf:
98
99
100
101
102
            for i in emotionSet.onyx__hasEmotion:
                e.onyx__hasEmotion.append(self._backwards_conversion(i))
        else:
            raise Error('EMOTION MODEL NOT KNOWN')
        yield e
J. Fernando Sánchez's avatar
J. Fernando Sánchez committed
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153

    def test(self, info=None):
        if not info:
            info = {
                "name": "CentroidTest",
                "description": "Centroid test",
                "version": 0,
                "centroids": {
                    "c1": {"V1": 0.5,
                           "V2": 0.5},
                    "c2": {"V1": -0.5,
                           "V2": 0.5},
                    "c3": {"V1": -0.5,
                           "V2": -0.5},
                    "c4": {"V1": 0.5,
                           "V2": -0.5}},
                "aliases": {
                    "V1": "X-dimension",
                    "V2": "Y-dimension"
                },
                "centroids_direction": ["emoml:big6", "emoml:fsre-dimensions"]
            }

        c = CentroidConversion(info)

        es1 = EmotionSet()
        e1 = Emotion()
        e1.onyx__hasEmotionCategory = "c1"
        es1.onyx__hasEmotion.append(e1)
        res = c._forward_conversion(es1)
        assert res["X-dimension"] == 0.5
        assert res["Y-dimension"] == 0.5

        e2 = Emotion()
        e2.onyx__hasEmotionCategory = "c2"
        es1.onyx__hasEmotion.append(e2)
        res = c._forward_conversion(es1)
        assert res["X-dimension"] == 0
        assert res["Y-dimension"] == 1

        e = Emotion()
        e["X-dimension"] = -0.2
        e["Y-dimension"] = -0.3
        res = c._backwards_conversion(e)
        assert res["onyx:hasEmotionCategory"] == "c3"

        e = Emotion()
        e["X-dimension"] = -0.2
        e["Y-dimension"] = 0.3
        res = c._backwards_conversion(e)
        assert res["onyx:hasEmotionCategory"] == "c2"