ナード戦隊データマン

機械学習と自然言語処理についてのブログ

気分のダイナミクスに関する数理モデル

前回の記事では、とりあえずそれっぽい仮定の基でオレオレモデリングを行い、その結果をプロットしました。今回はちゃんとした感情・気分の数理モデルを論文1から使います。

論文概要

Modeling the Dynamics of Mood and Depression[^1] という論文では、発生イベントに対する気分のダイナミクスをモデル化しています。

Screenshot_2019-02-01_18-49-50.png

平均感情、主観感情、気分レベル、思考、感度、長期的に予期された気分、短期的に予期された気分という時系列変数は、時間を追うごとに各変数に影響を与え、f(t+△t)をf(t)を用いて更新していきます。

コード

class Emotion:
    def __init__(
        self, oevs, 
        d=0.5, 
        c=0.5, 
        w_sevs_mood=0.5,
        w_thoughts_mood=0.5,
        w_sevs_thoughts=0.5,
        w_mood_thoughts=0.5,
        w_mood_sens=0.5,
        w_thoughts_sens=0.5,
        LT=0.5
    ):
        tmp = [LT for _ in range(len(oevs))]
        self.first_stack = [LT]+oevs[:]
        self.LT = LT
        self.oevsl = [LT] + oevs
        self.sevsl = [LT] + tmp
        self.moodl = [LT] + tmp
        self.thoughtsl = [LT] + tmp
        self.sensl = [LT] + tmp
        self.betal = [LT] + tmp
        self.STl = [LT] + tmp
        self.d = d
        self.c = c
        self.w_sevs_mood = w_sevs_mood
        self.w_thoughts_mood = w_thoughts_mood
        self.w_sevs_thoughts = w_sevs_thoughts
        self.w_mood_thoughts = w_mood_thoughts
        self.w_mood_sens = w_mood_sens
        self.w_thoughts_sens = w_thoughts_sens
        self.current_t = 0
    
    def __next__(self):
        self.current_t += 1
        t = self.current_t
        self.oevsl[t] = self.oevs(t)
        self.sevsl[t] = self.sevs(t)
        self.moodl[t] = self.mood(t)
        self.thoughtsl[t] = self.thoughts(t)
        self.sensl[t] = self.sens(t)
        self.betal[t] = self.beta(t)
        self.STl[t] = self.ST(t)
        if self.current_t == len(self.oevsl)-1:
            self.current_t = 0
        return {
            "oevs":self.oevsl[t], 
            "sevs":self.sevsl[t],
            "mood":self.moodl[t],
            "thoughts":self.thoughtsl[t],
            "sens":self.sensl[t],
            "beta":self.betal[t],
            "ST":self.STl[t]
        }
    
    def oevs(self, t):
        if self.first_stack[t] != None:
            return self.first_stack[t]
        delta = self.moodl[t-1]-self.betal[t-1]*self.LT
        if delta >= 0:
            p = self.oevsl[t-1]*delta
        else:
            p = (1-self.oevsl[t-1])*delta
        return self.oevsl[t-1]-self.sensl[t-1]*p
    
    def sevs(self, t):
        gamma = self.d*self.oevsl[t-1]*self.thoughtsl[t-1] + self.c*(1-(1-self.oevsl[t-1])*(1-self.thoughtsl[t-1]))
        return self.sevsl[t-1]+(gamma-self.sevsl[t-1])
    
    
    def mood(self, t):
        p = self.sevsl[t-1]*self.w_sevs_mood + self.thoughtsl[t-1]*self.w_thoughts_mood
        if p >= self.moodl[t-1]:
            val = self.moodl[t-1]+self.c*(p-self.moodl[t-1])
        else:
            val = self.moodl[t-1]+self.d*(p-self.moodl[t-1])
        return val
    
    def thoughts(self, t):
        p = self.sevsl[t-1]*self.w_sevs_thoughts + self.moodl[t-1]*self.w_mood_thoughts
        if p >= self.thoughtsl[t-1]:
            return self.thoughtsl[t-1]+self.c*(p-self.thoughtsl[t-1])
        else:
            return self.thoughtsl[t-1]+self.d*(p-self.thoughtsl[t-1])
        
    def sens(self, t):
        p = self.moodl[t-1]*self.w_mood_sens + self.thoughtsl[t-1]*self.w_thoughts_sens
        if p >= self.sensl[t-1]:
            return self.sensl[t-1]+self.c*(p-self.sensl[t-1])
        else:
            return self.sensl[t-1]+self.d*(p-self.sensl[t-1])
    
    def ST(self, t):
        return self.betal[t]*self.LT
        
    def beta(self, t):
        return self.betal[t-1] + (self.d*(self.moodl[t-1]/self.LT-self.betal[t-1])+self.c*(1-self.betal[t-1]))

if __name__ == "__main__":
    import matplotlib.pyplot as plt
    from random import random
    
    size = 50
    x_set = [i for i in range(size) if random() > 0.2]
    x = [i for i in range(size)]

    def gen_value(size, x_set):
        for i in range(size):
            if i in x_set:
                yield None
            else:
                yield random()
            
    y = list(gen_value(size, x_set))

    y_fix = []
    for a in y:
        if a is None:
            y_fix.append(0)
        else:
            y_fix.append(a)
            
    emo = Emotion(
        y, 
        c=0.3, d=0.7, 
        w_sevs_mood=0.7, 
        w_thoughts_mood=0.3, 
        w_sevs_thoughts=0.6, 
        w_mood_thoughts=0.4, 
        w_mood_sens=0.5, 
        w_thoughts_sens=0.5, 
        LT=0.5)

    for _ in range(len(y)):
        next(emo)
        
    plt.figure(1)
    plt.subplot(211)
    plt.plot([0]+x, emo.oevsl, "r")
    plt.bar([0]+x, [0]+y_fix)
    plt.subplot(212)
    plt.plot([0]+x, emo.moodl, "g")
    plt.subplot(211)
    plt.plot([0]+x, emo.sevsl, "m")
    plt.subplot(212)
    plt.plot([0]+x, emo.sensl, "r--")
    plt.subplot(212)
    plt.plot([0]+x, emo.thoughtsl, "b--")
    plt.bar([0]+x, [0]+y_fix)
    plt.savefig('figure.png')

出力

figure.png

出力の説明

青いバープロットは、イベントとその強度です。平均感情は赤色でプロットされていますが、これは平均的な人の感情です。

紫の折れ線グラフは、個人の性向を加味した感情です。この場合、c=0.3, d=0.7なので、ネガティブ思考の人の感情を表します。

紫の折れ線グラフは、気分レベルや思考状態を平均感情に演算することで求められますが、気分や思考状態は、下のグラフに表示されています。

考察

関連論文2を見てみると、このようなモデリングは感情・気分を表すためのモデルとしてよく使われるようです。ここでコード化したものは、その中でも最も単純な部類に含まれます。

しかし、相変わらず「この数理モデルのシミュレーションに対応した実際の人間のデータはあるのか」という問題は健在で、測定可能性の問題も含まれます。

ただ、入力に「イベントに対する感情の度合い」を受け付ければ、ある程度は測定可能なものとなります。ここでは乱数でそれをシミュレーションしていますが、そのような入力をユーザーから受け付けるアプリなどを用いれば、データと比較することが可能かもしれません。

ここで用いられている定数パラメータについては、個人の性向を表す変数であると考えることができますが、アプリから収集したデータを用いれば、もしかしたらこの重みを更新することも可能かもしれません。

要するに、論文のモデルを実際の人間の感情と一致していることを確かめる確かな実証データがほしいところではあります。

ただし、心理学のコンセプトを数理モデルとして表すことによって、心理学が仮定する変数が浮き彫りになるという意味では、意義のあるものと言えるかもしれません。