Recently I came across the so called circumplex model of affect. When seeing the plot, I immediately associated it with the kNN algorithm. I have re-drawn and simplified the model by creating an Microsoft Excel 2010 scatter chart:
More formally: let the sample space be a 2-dimensional Euclidean space
, the centre coordinates (0,0) and the radius
, then our classes are located at
Next we define
and setting its boundaries
Maybe a few words about Pleasure and Activation before we continue. Activation could be seen as a kind of entropy of the robot in a physical sense, depending for instance on the ambient light level and temperature. Pleasure could be seen as a success rate of a given task the robot has to fulfill or to learn. Just to give you some ideas.
The distance between two points in a 2-dimensional Euclidean space are given by
and follows directly from the Pythagorean theorem.
As we have set the boundaries
and
, so what is the maximum distance
between a class point and a new sample point that can occur?
Let me illustrate this with the following little sketch:
Obviously
Before we write down the function, we declare the radius as a constant and assign a value to it:
const float r = 5.0;
The function might then look as follows:
void Emotional_agent(float x_P, float y_A) {
if(x_P < - r) x_P = - r; // limit the range of x_P and y_A
else if(x_P > r) x_P = r;
if(y_A < - r) y_A = - r;
else if(y_A > r) y_A = r;
float emotion_coordinates[2][9] = {
{0.0, 0.0, r, - r, 0.0, r/sqrt(2.0), - r/sqrt(2.0), r/sqrt(2.0), - r/sqrt(2.0)}, // x-coordinate of training samples
{0.0, r, 0.0, 0.0, -r, r/sqrt(2.0), r/sqrt(2.0), - r/sqrt(2.0), -r/sqrt(2.0)} // y- coordinate of training samples
};
char *emotions[] = {“neutral”, “vigilant”, “happy”, “sad”, “bored”, “excited”, “angry”, “relaxed”, “depressed”};
byte i_emotion;
byte closestEmotion;
float MaxDiff;
float MinDiff = sqrt(2.0) * r + r; //set MinDiff initially to maximum distance that can occure
for (i_emotion = 0; i_emotion < 9; i_emotion ++) {
// compute Euclidean distances
float Euclidian_distance = sqrt(pow((emotion_coordinates[0][i_emotion] - x_P),2.0) + pow((emotion_coordinates[1][i_emotion] - y_A),2.0));
MaxDiff = Euclidian_distance;
// find minimum distance
if (MaxDiff < MinDiff) {
MinDiff = MaxDiff;
closestEmotion = i_emotion;
}
}
Serial.println(emotions[closestEmotion]);
Serial.println("");
}
The above model is very simple. We haven’t yet considered that emotions aren’t static. If someone isn’t suffering from mental illness, emotions aren’t static. They are dynamic. They depend on time. Usually nobody is always angry or happy or sad. These states are just moments in time. Complex biochemical reactions are responsible to stimulate an emotion. The human body synthesizes chemicals, distributes the chemicals and then decomposes them. Epinephrine might be an example. The distribution takes a split of a second, but the decomposition takes a few minutes or longer. Hence we can conclude that the emotional agent will always aim to reach equilibrium; means it is always aiming to reach the center point.
A simple biochemical reaction or distribution-decomposition model is the following:
where
a: Decomposition rate of the chemicals in the human body, which are responsible for the emotion
t: Time
b: Base
Extrema:
Plot of f(t) with a > 0 (black) and a < 0 (red):
Now we equal the maximum function value to r:
Substituting a in
yields to
The limits for t→∞ are given by
How fast the function value approaches 0 depends only on b. We can set the function value to 0 by an according if-statement if the function value has reached a defined constant close to 0, as it will never reach 0 for t<∞.
Example code for one axis (Activation). The distribution-decomposition is triggered by submitting 'a+' or 'a-' in the serial monitor.
const unsigned int interval = 100; // interval in ms const float base = 1.2; const float r = 5.0; long previousMillis = 0; float Activation = 0.0; float time_Activation = 0.0; boolean Positive_Activation = false; boolean Negative_Activation = false;void setup() {
Serial.begin(9600);
}void loop() {
String InputBuffer = “”;
while (Serial.available() > 0) {
char ch = Serial.read();
InputBuffer += ch;
if(InputBuffer == “a+”) {
time_Activation = 0.0;
Positive_Activation = true;
Negative_Activation = false;
}
if(InputBuffer == “a-”) {
time_Activation = 0.0;
Positive_Activation = false;
Negative_Activation = true;
}
delay (10);
}
unsigned long currentMillis = millis();
if(currentMillis - previousMillis > interval) {
previousMillis = currentMillis;
time_Activation += float(interval) / 1000.0;
}
if(Positive_Activation == true) {
Activation = log(base) * exp(1.0) * r * time_Activation / pow(base, time_Activation);
if(time_Activation > (1/log(base)) && Activation <= 0.02) {
Activation = 0.0;
Positive_Activation = false;
}
}
if(Negative_Activation == true) {
Activation = (log(base) * exp(1.0) * r * time_Activation / pow(base, time_Activation)) * - 1.0;
if(time_Activation > (1/log(base)) && Activation >= - 0.02) {
Activation = 0.0;
Negative_Activation = false;
}
}
Serial.println(Activation);
}
We don’t need to set the maximum/minimum function value necessarily to r. We can make the maximum/minimum function value depending on other parameters. Same for the base b. Therefore we can model the distribution of different chemical substances in the human body.
Combining the distribution-decomposition function with the circumplex model of affect and the nearest neighbour algorithm:
const float interval = 100.0; const float base = 1.2; const float r = 5.0; const float cutoff = 0.02; byte buffer_emotion = 10; long previousMillis = 0; float Activation = 0.0; float Pleasure = 0.0; float time_Activation = 0.0; float time_Pleasure = 0.0; boolean Positive_Activation = false; boolean Negative_Activation = false; boolean Positive_Pleasure = false; boolean Negative_Pleasure = false;void setup() {
Serial.begin(9600);
}void loop() {
String InputBuffer = “”;
while (Serial.available() > 0) {
char ch = Serial.read();
InputBuffer += ch;
if(InputBuffer == “a+”) {
time_Activation = 0.0;
Positive_Activation = true;
Negative_Activation = false;
}
if(InputBuffer == “a-”) {
time_Activation = 0.0;
Positive_Activation = false;
Negative_Activation = true;
}
if(InputBuffer == “p+”) {
time_Pleasure = 0.0;
Positive_Pleasure = true;
Negative_Pleasure = false;
}
if(InputBuffer == “p-”) {
time_Pleasure = 0.0;
Positive_Pleasure = false;
Negative_Pleasure = true;
}
delay (10);
}
unsigned long currentMillis = millis();
if(currentMillis - previousMillis > interval) {
previousMillis = currentMillis;
time_Activation += interval / 1000.0;
time_Pleasure += interval / 1000.0;
}
if(Positive_Activation == true) {
Activation = log(base) * exp(1.0) * r * time_Activation / pow(base, time_Activation);
if(time_Activation > (1/log(base)) && Activation <= cutoff) {
Activation = 0.0;
Positive_Activation = false;
}
}
if(Negative_Activation == true) {
Activation = (log(base) * exp(1.0) * r * time_Activation / pow(base, time_Activation)) * - 1.0;
if(time_Activation > (1/log(base)) && Activation >= - cutoff) {
Activation = 0.0;
Negative_Activation = false;
}
}
if(Positive_Pleasure == true) {
Pleasure = log(base) * exp(1.0) * r * time_Pleasure / pow(base, time_Pleasure);
if(time_Pleasure > (1/log(base)) && Pleasure <= cutoff) {
Pleasure = 0.0;
Positive_Pleasure = false;
}
}
if(Negative_Pleasure == true) {
Pleasure = (log(base) * exp(1.0) * r * time_Pleasure / pow(base, time_Pleasure)) * - 1.0;
if(time_Pleasure > (1/log(base)) && Pleasure >= - cutoff) {
Pleasure = 0.0;
Negative_Pleasure = false;
}
}
Emotional_agent(Pleasure, Activation);
}void Emotional_agent(float x_P, float y_A) {
if(x_P < - r) x_P = - r; // limit the range of x_P and y_A
else if(x_P > r) x_P = r;
if(y_A < - r) y_A = - r;
else if(y_A > r) y_A = r;
float emotion_coordinates[2][9] = {
{0.0, 0.0, r, - r, 0.0, r/sqrt(2.0), - r/sqrt(2.0), r/sqrt(2.0), - r/sqrt(2.0)}, // x-coordinate of training samples
{0.0, r, 0.0, 0.0, -r, r/sqrt(2.0), r/sqrt(2.0), - r/sqrt(2.0), -r/sqrt(2.0)} // y- coordinate of training samples
};
char *emotions[] = {“neutral”, “vigilant”, “happy”, “sad”, “bored”, “exited”, “angry”, “relaxed”, “depressed”};
byte i_emotion;
byte closestEmotion;
float MaxDiff;
float MinDiff = sqrt(2.0) * r + r; //set MinDiff initially to maximum distance that can occure
for (i_emotion = 0; i_emotion < 9; i_emotion ++) {
// compute Euclidean distances
float Euclidian_distance = sqrt(pow((emotion_coordinates[0][i_emotion] - x_P),2.0) + pow((emotion_coordinates[1][i_emotion] - y_A),2.0));
MaxDiff = Euclidian_distance;
// find minimum distance
if (MaxDiff < MinDiff) {
MinDiff = MaxDiff;
closestEmotion = i_emotion;
}
}
if(buffer_emotion != closestEmotion) {
Serial.println(emotions[closestEmotion]);
Serial.println("");
}
buffer_emotion = closestEmotion;
}