はじめに
「意見は対立していても相手を理解する事は可能なのか:理解と意見を分離した簡単なシミュレーション」という記事を書きまして、SNSでの議論を模した簡単なシミュレーションを組んだんですが、感情による議論のこじれを加味していない事を、Xで突っ込まれましてその方に意見を聞いてモデルを改良してみました。
個人的にも、意固地になっていく人を再現できない事を気にしていたので、意固地になる人がいるとどうなるか考察してみました。
結論としては、
- 意固地な人が増えると理解度が落ちる
- 全体的なストレスは意外と上がらない
- 意固地で意見が変わらない人は、他人の1/10も意見を聞いていない
という考察が得られました。
モデル化
基本的なモデルは、以前の記事と同じで、それにストレスという概念と意見 の聞きやすさを追加した形になります。
ストレスモデルと意見の聞きやすさ
jがiに話すときのストレスを$z_{j,i}$として表現する事にしました。
このストレスは発言や理解度に影響を及ぼします。
また、意見の聞きやすさを個体別に、$stubborn_j$と表現する事にしました。
この意見の聞きやすさは、理解度や意見の更新に影響をもたらします。
さらに、時間によるストレスの減衰係数$recovery$の導入しました。
モデルを更新する際に、ストレスを減らす処理をします。
-
z_{j,i}
- $z \in [0,\inf)$
-
$stubborn_j$
- $stubborn_j \in [0,1]$
-
$recovery$
- $recovery \in [0,1]$
-
発言への影響
- ストレスは発言の偏りをもたらす
- 意見が偏れば偏るほどその方向に強い意見を出す
- $s_i = x_i + noise + \eta z_{i,j} (x_{i} - 0.5)$
-
理解度への影響
- ストレスは、理解度$\alpha_{eff}$を下げる
- 意見の聞きやすさは、理解度$\alpha_{eff}$を下げる
- $\alpha_{eff} = \exp(-k z_{j,i}) stubborn_j$
- $ \hat{x_{i,j}} \leftarrow \hat{x_{i,j}} + \alpha_{eff}( s_i - \hat{x_{i,j}} )$
-
意見の更新への影響
- 意見の聞きやすさは、意見の更新へ影響をもたらす
- $x_j \leftarrow x_j + \beta stubborn_j u_{i,j} r_j (s_i - x_j)$
- $r_j \in \{ +1,0,-1 \}$
- 意見の聞きやすさは、意見の更新へ影響をもたらす
-
ストレスの減衰
- 1議論あれば全体のストレスを減衰させます
- $z \leftarrow z (1 - recovery)$
- 1議論あれば全体のストレスを減衰させます
意固地な人について
意固地な人は、最初の意見が0か1どちらかに偏っていて、$stubborn$の値が低い人とすることにしました。今回は、$stubborn=0.1$としました。
シミュレーション結果
パラメータは、意固地な人がいない場合に、前回の記事を基本に、ストレスが極端に減
ったり増えたりしないパラメータを選定しました。
# -----------------------------
# パラメータ
# -----------------------------
N = 10 # エージェント数
T = 200 # 議論ステップ数
alpha = 0.5 # 理解学習率
beta = 0.3 # 意見更新率
gamma = 5.0 # 理解度の進み方(意見が予想とズレた時、小さいと理解度が下がりずらく、大きいと理解度が下がりやすい)
sigma = 0.1 # 表現ノイズ
agree_ratio = 0.4 # 賛成率
disagree_ratio = 0.6 # 反対率
understand_ratio = 1 - agree_ratio - disagree_ratio # 理解はするが意見は変えない率
k = 3 # ストレスモデルの係数
recovery = 0.008 # ストレスの回復係数
c = 1 # 意見距離で増えるストレスの係数
d = 1 # 理解度で減るストレスの係数
eta = 1 # ストレスでの発言の誇張度
np.random.seed(0)
# -----------------------------
# 初期化
# -----------------------------
x = np.random.rand(N) # 意見
u = np.zeros((N, N)) # 理解度
np.fill_diagonal(u, 1.0) # 自己理解=1
x_hat = np.random.rand(N,N) # i->jの相手意見の予測
z = np.random.gamma(shape=2.0, scale=0.5, size=(N, N)) # j が i と話すときのストレス
np.fill_diagonal(z, 0) # 自己ストレス=0
stubborn = np.ones(N) # 意見の聞きやすさ
# 意固地な人の設定
stubborn_num = None
#stubborn_num = [N-1]
#stubborn_num = [N-1,N-2]
#stubborn_num = [N-1,N-2,N-3]
#stubborn_num = [N-1,N-2,N-3,N-4]
if stubborn_num:
x[stubborn_num] = np.random.choice([0.0,1.0],size=len(stubborn_num))# 意固地な人
stubborn[stubborn_num] = 0.1 # 意固地な人
一番左の図が理解度の変化なのですが、意固地な人が増えると、全体の理解度が下がっていく傾向が見られます。
真ん中の図が、ストレスの変化の図になります。意外なのですが、ストレスが乱数のバラつきの範囲内になります。意外とストレス増えないもんなんですよね。
今回のモデルでは、ストレスは関係ごとの局所量であり、時間減衰も入れているため、意固地な人が増えても全体に蓄積しにくい設計になっています。
左の図が意見の変化を見た図になります。
点線で意固地な人の意見の表示をしているのですが、ゆっくりだけど意見が変わるという結果になりました。
考察
全体の理解度が高くても、意固地な人が増えると理解度が下がるが、ストレスはあまり変化しない。意固地な人は、ストレスよりも理解度に影響を与えやすい。
また、通常の人と比較して1/10程度の話の聞き具合でも意見を変える事がわかった。
逆に言えば、意見が全く変わらない人を再現するには、ほとんど相互作用を遮断する必要があった。
つまり、SNSで時折見かける一切意見を変えず、かつ相手の話を理解しようとしない人はかなり話を聞いておらず、少しでも意見を聞く気があれば、全体の理解度が上がり、意見が、変わる事があり得るのではないか。
まとめ
ストレスによる影響を加味した議論のモデル構築を行った。
それにより、意固地な人がいるとストレスよりも理解度が敏感に反応し落ちる事がわかった。
また、他人の1/10程度しか意見を聞かない場合でも、意見の変化が、起こりうることが分かった。
さいごに
他人の話をちょっとでも(10%でも)聞く事って重要ですね、と言う話でした。
パラメータかなり多くなってしまったので、正確なモデルとは言いづらく、結果も変わってしまう可能性ありますが、悪い結論ではない気がします。
意見あれば取り入れて、時間のある時に試そうと思うので、気軽にコメントなどください。
コード
import numpy as np
import matplotlib.pyplot as plt
# -----------------------------
# パラメータ
# -----------------------------
N = 10 # エージェント数
T = 200 # 議論ステップ数
alpha = 0.5 # 理解学習率
beta = 0.3 # 意見更新率
gamma = 5.0 # 理解度の進み方(意見が予想とズレた時、小さいと理解度が下がりずらく、大きいと理解度が下がりやすい)
sigma = 0.1 # 表現ノイズ
agree_ratio = 0.4 # 賛成率
disagree_ratio = 0.6 # 反対率
understand_ratio = 1 - agree_ratio - disagree_ratio # 理解はするが意見は変えない率
k = 3 # ストレスモデルの係数
recovery = 0.008 # ストレスの回復係数
c = 1 # 意見距離で増えるストレスの係数
d = 1 # 理解度で減るストレスの係数
eta = 1 # ストレスでの発言の誇張度
np.random.seed(0)
# -----------------------------
# 初期化
# -----------------------------
x = np.random.rand(N) # 意見
u = np.zeros((N, N)) # 理解度
np.fill_diagonal(u, 1.0) # 自己理解=1
x_hat = np.random.rand(N,N) # i->jの相手意見の予測
z = np.random.gamma(shape=2.0, scale=0.5, size=(N, N)) # j が i と話すときのストレス
np.fill_diagonal(z, 0) # 自己ストレス=0
stubborn = np.ones(N) # 意見の聞きやすさ
# 意固地な人の設定
stubborn_num = None
#stubborn_num = [N-1]
#stubborn_num = [N-1,N-2]
#stubborn_num = [N-1,N-2,N-3]
#stubborn_num = [N-1,N-2,N-3,N-4]
if stubborn_num:
x[stubborn_num] = np.random.choice([0.0,1.0],size=len(stubborn_num))# 意固地な人
stubborn[stubborn_num] = 0.1 # 意固地な人
x_history = np.zeros((T, N))
U_history = []
z_history = []
z_stubborn_history = []
reactivity = np.random.choice([1.0, 0.0, -1.0], size=N, p=[agree_ratio, understand_ratio,disagree_ratio])
# -----------------------------
# シミュレーション
# -----------------------------
for t in range(T):
for _ in range(N): # ランダムなペアで議論
i, j = np.random.choice(N, 2, replace=False)
# i が発言
# ストレスで発言が誇張される
s = x[i] + np.random.normal(0, sigma) + eta * z[i, j] * (x[i] - 0.5)
# 相手モデル更新(理解)(ストレスモデル込み)
alpha_eff = alpha * np.exp(-k * z[j, i]) * stubborn[j]
x_hat[j, i] += alpha_eff * (s - x_hat[j, i])
# j の予測(単純化:現在の意見)
pred = x_hat[j,i]
# 予測誤差
error = abs(s - pred)
# --- 理解更新 ---
# 理解度
u[j, i] = np.exp(-gamma * (s - pred)**2)
# ストレス
# 意見距離でストレスが増える
z[j, i] += c * abs(s - x[j])
# 理解度でストレスが減る
z[j, i] -= d * u[j, i]
z[j, i] = max(z[j, i], 0)
# ストレスの時間減衰
z *= (1 - recovery)
# 意見更新(反応タイプ)
x[j] += beta * stubborn[j] * u[j, i] * reactivity[j] * (s - x[j])
x = np.clip(x,0,1)
# 指標記録
x_history[t] = x
U_history.append(np.mean(u[np.eye(N) == 0]))
z_history.append(np.mean(z[np.eye(N) == 0]))
if stubborn_num:
z_stubborn = np.mean(z[stubborn_num, :]) + np.mean(z[:, stubborn_num])
else:
z_stubborn = np.mean(z[0, :]) + np.mean(z[:, 0])
z_stubborn_history.append(z_stubborn)
# -----------------------------
# 可視化
# -----------------------------
fig, ax = plt.subplots(1, 3, figsize=(10, 4))
ax[0].plot(U_history)
ax[0].set_title("Average Mutual Understanding")
ax[0].set_xlabel("Time")
ax[0].set_ylabel("U")
ax[0].set_ylim(0, 1.1)
ax[1].plot(z_history,label='mean')
if stubborn_num:
ax[1].plot(z_stubborn_history,label='related stubborn')
else:
ax[1].plot(z_stubborn_history,label='related num 0')
ax[1].legend()
ax[1].set_title("Average Mutual Stress")
ax[1].set_xlabel("Time")
ax[1].set_ylabel("z")
#ax[1].set_ylim(0, 1.1)
for i in range(N):
if stubborn_num:
if i in stubborn_num:
ax[2].plot(x_history[:,i],'--',lw=2)
else:
ax[2].plot(x_history[:, i], lw=2)
else:
ax[2].plot(x_history[:, i], lw=2)
ax[2].set_xlabel("Time")
ax[2].set_ylabel("Opinion x")
ax[2].set_title("Opinion Trajectories")
ax[2].set_ylim(-0.1, 1.1)
plt.tight_layout()
plt.show()
コードは GitHub に置いてあります:



