【Python】global lability synchronization法による神経信号の同期の不安定性

神経ネットワークの自己組織化臨界現象を調べる上で、global lability of synchronization(同期のグローバルな不安定性)法という手法を使うことになったので、その計算方法をメモしておく。

大まかにいうと、ある神経ネットワークの活動において時刻$t$で同期しているニューロンのペアの個数$M(t)$と時刻$t+1$で同期しているペアの個数$M(t+1)$がどれだけ違うか、ということを評価する。値が大きければ大きいほど全体の同期/非同期プロセスを表し、小さいほど局所的なものを表す。

## 位相同期の評価

時系列データとして、二つの信号$s_i(t)$と$s_j(t)$の二種類が与えられたとする。

この二つの信号がある時刻$t$において

* 位相差$\theta_{i, j}(t)$の絶対値が$\pi/4$未満である
* 時間窓$v$の範囲での位相差$\theta_{i, j}(t)$に対する同期パラメータ$\gamma_{i,j}(t)$が$\sqrt{1/2}$より大きいこと

この二つの条件を満たす時、二つの信号は同期しているとする。

ここで位相差$\theta_{i, j}(t)$は以下のように計算を行う。

$$ \Delta \theta_{i,j}(t)=\arctan[{\tilde s_i(t)s_j(t)- \tilde s_j(t)s_i(t)\over  s_i(t)s_j(t)- \tilde s_j(t)\tilde  s_i(t)}] $$

ここで$\tilde s(t)$は信号$s(t)$のヒルベルト変換を表す。

$$ \tilde s(t) = {1\over \pi}\int^\infty_{-\infty} {s(\tau)\over t-\tau} d\tau$$
ヒルベルト変換って何？という方は以下↓
参考: https://www.onosokki.co.jp/HP-WK/eMM_back/emm180.pdf
ざっと言うと実部のみの信号$s(t)$が本来含む虚部の信号$\tilde s(t)$を返す操作。さらに大雑把に言えば位相を90°ずらす操作に当たるらしい。

同期パラメータ$\gamma_{i,j}(t)$は以下のように求める。

$$ \gamma_{i,j}(t) = \sqrt{[{1\over v}\sum^{t+v}_t \cos \Delta \theta _{i,j}(t)]^2+[{1\over v}\sum^{t+v}_t \sin\Delta \theta _{i,j}(t)]} $$

ここで$v$は時間窓を表す。

ネットワークの中の全てのニューロン信号について、これらの条件を満たす一致するペアの個数を$M(t)$とする。以下のような式で表すことができる。

$$ M(t)=\sum[|\Delta \theta_{i,j}|<\pi/4 ~and ~ \gamma_{i,j}(t)>\sqrt{1/2}] $$

## 不安定性の評価
得られたペアの個数$M(t)$と$M(t+1)$から、以下のようにして不安定性$l(t)$を求めることができる。

$$ l(t) = |M(t+1)-M(t)|^2 $$
## 実装

前提として、ヒルベルト変換は以下のように計算することができる。

```py
import matplotlib.pyplot as plt
import numpy as np
from scipy.signal import hilbert


x = np.arange(-10,10, 0.1)
y = np.cos(x)
z = hilbert(y)
plt.plot(x,z.real, label="hil_real")
plt.plot(x,z.imag, label="hil_imag")
plt.legend()
plt.show()
```
![rapture_20220120135741.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/356208/1282969c-6cd5-e9e2-45ab-0097c821ddc8.jpeg)

元の入力であるcos波に対して虚部であるsin波が取得できていることがわかる。

これを用いて実装を行ったのが以下のクラス。

```py
import itertools
import matplotlib.pyplot as plt
import numpy as np
from scipy.signal import hilbert

class DLS():
    def __init__(self, signals, v=50):
        self.signals = [hilbert(s) for s in signals]
        self.v = v #時間窓を50ステップとする
    
    def theta(self, s1, s2, t_i):
        y = s1[t_i].imag*s2[t_i].real - s2[t_i].imag*s1[t_i].real
        x = s1[t_i].real*s2[t_i].real - s1[t_i].imag*s2[t_i].imag
        return np.arctan(y/x)

    def gamma(self, i, j, t_i):
        if t_i + self.v >= len(self.signals[i]):
            return 0
        T_i = np.arange(t_i, t_i+self.v)
        cos_ave = np.cos(self.theta(self.signals[i], self.signals[j], T_i)).sum()/len(T_i)
        sin_ave = np.sin(self.theta(self.signals[i], self.signals[j], T_i)).sum()/len(T_i)


        return np.sqrt(cos_ave**2 + sin_ave**2)

    def isPair(self, i, j, t_i):
        theta_abs = abs(self.theta(self.signals[i], self.signals[j], t_i))
        gamma = self.gamma(i,j,t_i)

        if theta_abs < np.pi/4 and gamma > np.sqrt(1/2):
            return 1
        else:
            return 0

    def M(self, t_i):
        count = 0
        for i, j in itertools.combinations(range(len(self.signals)), 2):
            count += self.isPair(i, j, t_i)
        return count
    def l(self, t_i):
        return (self.M(t_i+1)-self.M(t_i))**2

```

試しに全く同じsin波を3つ入れて計算してみる。

```py
v = 50
x = np.sin(np.arange(0,10, 0.1))
signals = [x, x, x]
dls = DLS(signals, v)
for i, x in enumerate(x[:v]):
    print(dls.M(i))
    # 一致する組み合わせ数3が出力される
    print(dls.l(i))
    # 完全に安定なので0が出力される
```
こうして計算を行うことができた。


## 参考文献

https://www.onosokki.co.jp/HP-WK/eMM_back/emm180.pdf

Kitzbichler, M. G., Smith, M. L., Christensen, S. R., & Bullmore, E. (2009). Broadband criticality of human brain network synchronization. PLoS computational biology, 5(3), e1000314.

Aguilar-Velázquez, D., & Guzmán-Vargas, L. (2019). Critical synchronization and 1/f noise in inhibitory/excitatory rich-club neural networks. Scientific reports, 9(1), 1-13.