---
# System prepended metadata

title: Ising Model（伊辛模型）介紹

---

# Ising Model（伊辛模型）介紹

伊辛模型（Ising Model）是統計物理學中的經典模型，用於描述磁性物質中的自旋系統。由物理學家 **Wilhelm Lenz** 提出，其學生 **Ernst Ising** 在 1924 年進一步發展。該模型在研究相變、臨界現象以及分布式系統中具有廣泛應用。

---

## 1. 模型基礎

### 1.1 自旋系統
- **系統組成**：由一個 $d$ 維格子上的 **自旋變量** 組成。
- **自旋取值**：
  - 每個格點 $i$ 的自旋 $s_i$ 取值為 $+1$（向上自旋）或 $-1$（向下自旋）。
  - 自旋表示微觀粒子的磁矩方向。

---

### 1.2 哈密頓量（Hamiltonian）
- **總能量公式**：
  $$
  H = -J \sum_{\langle i, j \rangle} s_i s_j - h \sum_i s_i
  $$
  其中：
  - $J$：**相互作用強度**，描述鄰居自旋之間的耦合程度。
    - 當 $J > 0$：鄰居自旋傾向一致（鐵磁性）。
    - 當 $J < 0$：鄰居自旋傾向相反（反鐵磁性）。
  - $\langle i, j \rangle$：表示格子中所有相鄰自旋對。
  - $h$：**外部磁場強度**。
  - $s_i$：格點 $i$ 的自旋狀態（取值 $+1$ 或 $-1$）。

---

### 1.3 相互作用機制
- **局部交互**：
  - 每個格點的自旋狀態受鄰居自旋和外部磁場的共同影響。
- **能量最小化**：
  - 系統傾向於選擇自旋分佈，使總能量 $H$ 最小。

---
# 基於伊辛模型的自旋格子強化學習

## 程式碼

```python
import torch
import numpy as np
import matplotlib.pyplot as plt
from collections import deque
from random import shuffle

device = torch.device("cuda" if torch.cuda.is_available() else "cpu")

# Softmax 策略
def softmax_policy(qvals, temp=0.9):
    soft = torch.exp(qvals / temp) / torch.sum(torch.exp(qvals / temp))
    action = torch.multinomial(soft, 1)
    return action

# 獲取格點在二維格子中的座標
def get_coords(grid, j):
    x = int(np.floor(j / grid.shape[0]))
    y = int(j - x * grid.shape[0])
    return x, y

# 計算二維格子的獎勵
def get_reward_2d(action, action_mean):
    r = (action * (action_mean - action / 2)).sum() / action.sum()
    return torch.tanh(5 * r)

# 獲取自旋格點的子狀態
def get_substate(b):
    s = torch.zeros(2).to(device)
    if b > 0:
        s[1] = 1
    else:
        s[0] = 1
    return s

# 計算格點的平均行動
def mean_action(grid, j):
    x, y = get_coords(grid, j)
    action_mean = torch.zeros(2).to(device)
    for i in [-1, 0, 1]:
        for k in [-1, 0, 1]:
            if i == k == 0:
                continue
            x_, y_ = x + i, y + k
            x_ = x_ if x_ >= 0 else grid.shape[0] - 1
            y_ = y_ if y_ >= 0 else grid.shape[1] - 1
            x_ = x_ if x_ < grid.shape[0] else 0
            y_ = y_ if y_ < grid.shape[1] else 0
            cur_n = grid[x_, y_]
            s = get_substate(cur_n)
            action_mean += s
    action_mean /= action_mean.sum()
    return action_mean

# 初始化自旋格子
def init_grid(size=(10, 10)):
    grid = torch.randint(0, 2, size).to(device)
    return grid

# 生成參數的函數
def gen_params(N, size):
    ret = []
    for i in range(N):
        vec = torch.randn(size).to(device) / 10.
        vec.requires_grad = True
        ret.append(vec)
    return ret

# 假設的 q 函數，用來估算 Q 值
def qfunc(state, params, layers=[(2, 10), (10, 2)], afn=torch.tanh):
    l1n = layers[0]
    l1s = np.prod(l1n)
    theta_1 = params[0:l1s].reshape(l1n)
    l2n = layers[1]
    l2s = np.prod(l2n)
    theta_2 = params[l1s:l2s + l1s].reshape(l2n)
    bias = torch.ones((1, theta_1.shape[1])).to(device)
    l1 = state @ theta_1 + bias
    l1 = torch.nn.functional.elu(l1)
    l2 = afn(l1 @ theta_2)
    return l2.flatten()

# 主程式開始
size = (10, 10)
J = np.prod(size)  # 計算格子的總數量
hid_layer = 10
layers = [(2, hid_layer), (hid_layer, 2)]
params = gen_params(1, 2 * hid_layer + hid_layer * 2)  # 生成隨機參數
grid = init_grid(size=size)
grid_ = grid.clone()
grid__ = grid.clone()

# 顯示初始狀態並保存為圖片
plt.imshow(grid.cpu())
plt.title("Initial Spin Lattice")
plt.colorbar()
plt.savefig("initial_grid.png")
plt.show()
print("Initial grid sum:", grid.sum())

# 開始訓練過程
epochs = 75
lr = 0.0001
num_iter = 3  # A
losses = [[] for i in range(J)]  # B
replay_size = 50  # C
replay = deque(maxlen=replay_size)  # D
batch_size = 10  # E
gamma = 0.9  # F

for i in range(epochs): 
    act_means = torch.zeros((J, 2)).to(device)  # G
    q_next = torch.zeros(J).to(device)  # H
    for m in range(num_iter):  # I
        for j in range(J):  # J
            action_mean = mean_action(grid_, j).detach()
            act_means[j] = action_mean.clone()
            qvals = qfunc(action_mean.detach(), params[0], layers=layers)
            action = softmax_policy(qvals.detach(), temp=0.5)
            grid__[get_coords(grid_, j)] = action
            q_next[j] = torch.max(qvals).detach()
        grid_.data = grid__.data
    grid.data = grid_.data

    # 計算當前動作和獎勵
    actions = torch.stack([get_substate(a.item()) for a in grid.flatten()])
    rewards = torch.stack([get_reward_2d(actions[j], act_means[j]) for j in range(J)])
    exp = (actions, rewards, act_means, q_next)  # K
    replay.append(exp)
    shuffle(replay)

    # 使用批量回放進行更新
    if len(replay) > batch_size:  # L
        ids = np.random.randint(low=0, high=len(replay), size=batch_size)  # M
        exps = [replay[idx] for idx in ids]
        for j in range(J):
            jacts = torch.stack([ex[0][j] for ex in exps]).detach()
            jrewards = torch.stack([ex[1][j] for ex in exps]).detach()
            jmeans = torch.stack([ex[2][j] for ex in exps]).detach()
            vs = torch.stack([ex[3][j] for ex in exps]).detach()
            qvals = torch.stack([qfunc(jmeans[h].detach(), params[0], layers=layers) for h in range(batch_size)])
            target = qvals.clone().detach()
            target[:, torch.argmax(jacts, dim=1)] = jrewards + gamma * vs
            loss = torch.sum(torch.pow(qvals - target.detach(), 2))
            losses[j].append(loss.item())
            loss.backward()
            with torch.no_grad():
                params[0] = params[0] - lr * params[0].grad
            params[0].requires_grad = True

# 可視化損失值和最終格子狀態
fig, ax = plt.subplots(2, 1)
fig.set_size_inches(10, 10)

# 損失值的平均變化
mean_losses = np.mean([np.array(l) for l in losses if len(l) > 0], axis=0)
ax[0].plot(mean_losses)
ax[0].set_title("Average Loss over Epochs")

# 顯示最終自旋格子狀態
ax[1].imshow(grid.cpu())
ax[1].set_title("Final Spin Lattice")
plt.savefig('training_results.png')  # 保存圖片
plt.show()
```
# Softmax 策略

```python
def softmax_policy(qvals, temp=0.9):
    soft = torch.exp(qvals / temp) / torch.sum(torch.exp(qvals / temp))
    action = torch.multinomial(soft, 1)
    return action
```
# Softmax 函數公式

$$
P(a_i) = \frac{\exp(Q(a_i) / \text{temp})}{\sum_j \exp(Q(a_j) / \text{temp})}
$$

- **$Q(a_i)$**：行為 $a_i$ 的 Q 值。
- **$\text{temp}$**：溫度參數，用於調節 Softmax 函數對 Q 值的響應程度。

---

## 溫度參數對指數函數的影響

### 1. 高溫（大 $\text{temp}$）

當 $\text{temp} \to \infty$ 時：

$$
\frac{Q(a_i)}{\text{temp}} \to 0
$$

指數函數趨近於 1：

$$
\exp\left(\frac{Q(a_i)}{\text{temp}}\right) \approx 1
$$

因此所有 Q 值的影響力變得相似，機率分布接近均勻分布：

$$
P(a_i) \approx \frac{1}{\text{總行為數}}
$$

- **原因**：當 $\text{temp}$ 很大時，Q 值被過度平滑化，無法有效區分高 Q 值和低 Q 值行為，最終所有行為的機率幾乎相等。

---

### 2. 低溫（小 $\text{temp}$）

當 $\text{temp} \to 0^+$ 時：

$$
\frac{Q(a_i)}{\text{temp}} \to \infty \quad (\text{若 } Q(a_i) \text{ 最大})
$$

此時，Q 值較大的行為對指數函數的影響變得極其顯著，而其他較小 Q 值的行為影響趨近於零。因此，Softmax 分布會強烈偏向 Q 值最大的行為。

- **數學解釋**：

若 $Q(a_k) > Q(a_j)$ 且 $\text{temp} \to 0^+$，則：

$$
\frac{\exp(Q(a_k) / \text{temp})}{\exp(Q(a_j) / \text{temp})} \to \infty
$$

導致機率分布中：

$$
P(a_k) \to 1 \quad \text{且} \quad P(a_j) \to 0 \quad (\text{若 } j \neq k)
$$

# Softmax 計算範例：高溫與低溫

## 1. 高溫：$$\text{temp} = 2$$

### 指數值計算
$$
\exp\left(\frac{Q}{\text{temp}}\right) = \exp\left(\frac{[2, 1, 0]}{2}\right) = [e^1, e^{0.5}, e^0] \approx [2.718, 1.649, 1.0]
$$

### 分母計算（正規化因子）
$$
Z = \sum \exp\left(\frac{Q}{\text{temp}}\right) = 2.718 + 1.649 + 1.0 = 5.367
$$

### Softmax 分布
$$
\text{soft} = \left[\frac{2.718}{5.367}, \frac{1.649}{5.367}, \frac{1.0}{5.367}\right] \approx [0.506, 0.307, 0.186]
$$

### 解釋
- 在高溫下，所有行為的機率相對均勻。
- 行為 $0$ 的主導性被削弱，低 Q 值行為（行為 $2$）仍有一定機率被選中。

---

## 2. 低溫：$$\text{temp} = 0.1$$

### 指數值計算
$$
\exp\left(\frac{Q}{\text{temp}}\right) = \exp\left(\frac{[2, 1, 0]}{0.1}\right) = [e^{20}, e^{10}, e^0]
$$

由於指數函數的快速增長特性：
$$
\exp(20) \gg \exp(10) \gg \exp(0)
$$

### 分母計算（正規化因子）
$$
Z \approx \exp(20) + \exp(10) + 1
$$

### Softmax 分布
$$
\frac{\exp(20)}{Z} \approx 1.0, \quad \frac{\exp(10)}{Z} \approx 0, \quad \frac{1}{Z} \approx 0
$$

### 解釋
- 在低溫下，Softmax 分布強烈偏向於 Q 值最大的行為。
- 行為 $0$ 的機率接近 $1.0$，而其他行為的機率接近 $0$。
# `get_reward_2d` 函數

此函數用於計算基於行動和行動均值的獎勵，並應用雙曲正切函數（`tanh`）對結果進行縮放。

## 函數實現

```python
def get_reward_2d(action, action_mean):
    r = (action * (action_mean - action / 2)).sum() / action.sum()
    return torch.tanh(5 * r)
```
# 格點行為獎勵計算範例

## 1. 假設數據

### 1.1 格點的行為分布
格點 $j$ 的行為概率（action）：
$$
\text{action} = [0.7, 0.3]
$$
- $0.7$：格點 $j$ 以 70% 的概率選擇行為 $0$。
- $0.3$：格點 $j$ 以 30% 的概率選擇行為 $1$。

---

### 1.2 鄰居的平均行為偏好
鄰居行為的平均分布（action_mean）：
$$
\text{action_mean} = [0.6, 0.4]
$$
- $0.6$：鄰居偏好行為 $0$。
- $0.4$：鄰居偏好行為 $1$。

---

## 2. 運算過程

### 2.1 相容性項計算
點乘的第一部分計算當前行為與鄰居行為的匹配度：
$$
\text{compatibility} = \text{action} \cdot \text{action_mean}
$$
對應計算：
$$
\text{compatibility} = (0.7 \times 0.6) + (0.3 \times 0.4) = 0.42 + 0.12 = 0.54
$$
- **解釋**：
  - $0.42$：當前行為 $0$ 與鄰居偏好 $0$ 的匹配程度。
  - $0.12$：當前行為 $1$ 與鄰居偏好 $1$ 的匹配程度。
  - **總和** $0.54$：行為的總相容性，越高表示與鄰居越一致。

---

### 2.2 自抑制項計算
點乘的第二部分計算行為的極端性懲罰：
$$
\text{self-suppression} = \text{action} \cdot \frac{\text{action}}{2}
$$
對應計算：
$$
\text{self-suppression} = (0.7 \times 0.35) + (0.3 \times 0.15) = 0.245 + 0.045 = 0.29
$$
- **解釋**：
  - $0.245$：行為 $0$ 的極端性懲罰。
  - $0.045$：行為 $1$ 的極端性懲罰。
  - **總和** $0.29$：自抑制懲罰，越高表示行為分布越極端。

---

### 2.3 最終點乘結果
將兩部分結合：
$$
r = \text{compatibility} - \text{self-suppression}
$$
計算：
$$
r = 0.54 - 0.29 = 0.25
$$

---

### 2.4 正規化與非線性壓縮
最終獎勵值進行非線性處理：
$$
\text{reward} = \tanh(5 \cdot r)
$$
對應計算：
$$
\text{reward} = \tanh(5 \cdot 0.25) = \tanh(1.25) \approx 0.848
$$

---

## 3. 結果解釋

- **相容性高**：當前行為 $\text{action} = [0.7, 0.3]$ 與鄰居行為 $\text{action_mean} = [0.6, 0.4]$ 有較高的匹配度（$0.54$）。
- **行為懲罰**：$0.7$ 和 $0.3$ 的分布較平衡，因此自抑制項 $0.29$ 不算太高。
- **最終獎勵**：經過壓縮，獎勵值為 $\approx 0.848$，說明該行為與鄰居偏好相對一致，獲得了較高的激勵。

---

## 4. 不同情況比較

### 4.1 完全一致的行為
若當前行為與鄰居偏好完全一致：
$$
\text{action} = [0.6, 0.4], \quad \text{action_mean} = [0.6, 0.4]
$$
計算：
$$
\text{compatibility} = (0.6 \times 0.6) + (0.4 \times 0.4) = 0.36 + 0.16 = 0.52
$$
$$
\text{self-suppression} = (0.6 \times 0.3) + (0.4 \times 0.2) = 0.18 + 0.08 = 0.26
$$
$$
r = 0.52 - 0.26 = 0.26
$$
$$
\text{reward} = \tanh(5 \cdot 0.26) \approx 0.856
$$
- **結果**：行為完全一致時，獎勵值達到更高，為 $\approx 0.856$。

---

### 4.2 完全不一致的行為
若當前行為與鄰居偏好完全不一致：
$$
\text{action} = [0.4, 0.6], \quad \text{action_mean} = [0.6, 0.4]
$$
計算：
$$
\text{compatibility} = (0.4 \times 0.6) + (0.6 \times 0.4) = 0.24 + 0.24 = 0.48
$$
$$
\text{self-suppression} = (0.4 \times 0.2) + (0.6 \times 0.3) = 0.08 + 0.18 = 0.26
$$
$$
r = 0.48 - 0.26 = 0.22
$$
$$
\text{reward} = \tanh(5 \cdot 0.22) \approx 0.804
$$
- **結果**：行為不一致時，獎勵值降低，但仍有一定的正值。

---

### 4.3 極端行為
若行為極端化，例如完全偏向一個行為：
$$
\text{action} = [1.0, 0.0], \quad \text{action_mean} = [0.6, 0.4]
$$
計算：
$$
\text{compatibility} = (1.0 \times 0.6) + (0.0 \times 0.4) = 0.6 + 0 = 0.6
$$
$$
\text{self-suppression} = (1.0 \times 0.5) + (0.0 \times 0) = 0.5 + 0 = 0.5
$$
$$
r = 0.6 - 0.5 = 0.1
$$
$$
\text{reward} = \tanh(5 \cdot 0.1) \approx 0.462
$$
- **結果**：行為極端化時，自抑制項懲罰變高，獎勵值顯著降低。

---



- **高相容性**：行為與鄰居偏好一致時，獲得更高的獎勵。
- **平衡行為**：自抑制項防止極端行為，保證行為分布更平衡。
- **極端行為懲罰**：行為極端化會導致懲罰增加，獎勵值顯著下降。
- **獎勵設計特點**：
  - 鼓勵行為與鄰居一致（高相容性）。
  - 懲罰極端行為，促進穩定性與協同效應。

 # `Get_substate` 
 ```python
def get_substate(b):
    s = torch.zeros(2).to(device)
    if b > 0:
        s[1] = 1
    else:
        s[0] = 1
    return s
 ```
### 自旋狀態轉換函數：`get_substate`

該函數用於將輸入值 $b$ 轉換為二維向量，用以表示二元自旋系統的狀態。

---

### **數學描述**

若 $b > 0$，則輸出：
$$
[0, 1]
$$
表示「向上自旋」。

若 $b \leq 0$，則輸出：
$$
[1, 0]
$$
表示「向下自旋」。

---

### **程式碼實現**

```python
def get_substate(b):
    s = torch.zeros(2).to(device)
    if b > 0:
        s[1] = 1
    else:
        s[0] = 1
    return s
```


### 範例

```python
# 假設 b 為正數
b = 1
state = get_substate(b)
print(state)  # 輸出: tensor([0., 1.], device='cuda:0')

# 假設 b 為負數
b = -1
state = get_substate(b)
print(state)  # 輸出: tensor([1., 0.], device='cuda:0')
```
# `Mean action` 
```python
def mean_action(grid, j):
    x, y = get_coords(grid, j)
    action_mean = torch.zeros(2).to(device)
    for i in [-1, 0, 1]:
        for k in [-1, 0, 1]:
            if i == k == 0:
                continue
            x_, y_ = x + i, y + k
            # 處理格點的環繞邊界
            x_ = x_ if x_ >= 0 else grid.shape[0] - 1
            y_ = y_ if y_ >= 0 else grid.shape[1] - 1
            x_ = x_ if x_ < grid.shape[0] else 0
            y_ = y_ if y_ < grid.shape[1] else 0
            cur_n = grid[x_, y_]
            s = get_substate(cur_n)
            action_mean += s
    action_mean /= action_mean.sum()
    return action_mean
```
# 範例：計算目標格點的平均行動

## 自旋格子設定
假設一個 $5 \times 5$ 的自旋格子，定義如下：

```python
grid = torch.tensor([
    [0, 1, 0, 1, 0],
    [1, 0, 1, 0, 1],
    [0, 1, 0, 1, 0],
    [1, 0, 1, 0, 1],
    [0, 1, 0, 1, 0],
]).to(device)
```
## 目標格點平均行動計算範例

目標格點索引 $j=12$，對應的座標為 $(2, 2)$。

### 計算步驟

#### 1. 鄰居格點的自旋狀態
目標格點 $(2, 2)$ 的鄰居狀態為：

$$
\{0, 1, 0, 1, 1, 0, 1, 0\}
$$

---

#### 2. 轉換為向量形式
將鄰居的自旋狀態轉換為向量形式：

$$
\{
[1, 0], [0, 1], [1, 0], [0, 1], [0, 1], [1, 0], [0, 1], [1, 0]
\}
$$

---

#### 3. 累加結果
累加所有向量：

$$
\text{action_mean} = [5, 3]
$$

---

#### 4. 歸一化
將累加結果歸一化：

$$
\text{action_mean} = \left[\frac{5}{8}, \frac{3}{8}\right] = [0.625, 0.375]
$$

---

### 返回結果
目標格點的平均行動為：

$$
[0.625, 0.375]
$$

## 網路結構特點

### 1. 兩層全連接結構
- **第一層**：
  - 輸入：$(2,)$
  - 隱藏層輸出：$(10,)$
- **第二層**：
  - 隱藏層輸入：$(10,)$
  - 最終輸出：$(2,)$

---

### 2. 激活函數
- **ELU**：應用於第一層，增強非線性特性。
- **Tanh**：應用於第二層，進一步提升網路的非線性能力。

---

### 3. 參數共享
- **特點**：所有格點共享網路參數。
- **優勢**：大幅簡化模型設計，提升計算效率。
## 實驗結果
![training_results (1)](https://hackmd.io/_uploads/Syv1WBjzJg.png)

