rsu commited on
Commit
bc5f04e
·
verified ·
1 Parent(s): 636c8d8

Upload folder using huggingface_hub

Browse files
Files changed (6) hide show
  1. .DS_Store +0 -0
  2. M1.h5 +3 -0
  3. README.md +126 -0
  4. model.py +146 -0
  5. model.yaml +11 -0
  6. requirements.txt +2 -0
.DS_Store ADDED
Binary file (6.15 kB). View file
 
M1.h5 ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:e12389b383ddcd573405078048eaeea3fd8c370dea067283a28eea0e9aa206c1
3
+ size 20625576
README.md CHANGED
@@ -1,3 +1,129 @@
1
  ---
2
  license: mit
 
 
 
 
 
 
 
3
  ---
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
  ---
2
  license: mit
3
+ library_name: tensorflow
4
+ tags:
5
+ - othello
6
+ - reversi
7
+ - game-ai
8
+ - transformer
9
+ - mixture-of-experts
10
  ---
11
+
12
+ ## Transformer Reversi AI
13
+ This Reversi AI is a Transformer-based model that uses Mixture-of-Experts architecture for decision-making instead of CNNs.
14
+
15
+ ## Architecture
16
+
17
+ - Parameters: 1.68M
18
+ - Model size: 20.6MB
19
+
20
+ - Input: `(8, 8, 2)`
21
+ - Own stones
22
+ - Opponent stones
23
+ - Board representation: 64 tokens
24
+ - Embedding dimension: 128
25
+ - Transformer blocks: 4
26
+ - Attention heads: 4
27
+ - MHA experts: 2
28
+ - FFN experts: 2
29
+ - Routing: softmax-based dynamic routing
30
+ - Iterative routing steps: 2
31
+ - Normalization: Pre-LN LayerNorm
32
+ - FFN activation: GELU
33
+
34
+ ### Diagram
35
+ ```
36
+ Input (8×8×2)
37
+ ↓
38
+ 64 Tokens
39
+ ↓
40
+ Dense 128
41
+ ↓
42
+ Token + Row + Column + Move Embeddings
43
+ ↓
44
+ DynamicAssembly × 4
45
+ ├─ MHA Expert × 2
46
+ └─ FFN Expert × 2
47
+ ↓
48
+ ├─ Policy Head → 64 moves
49
+ └─ Value Head → Win-rate
50
+ ```
51
+
52
+ ### DynamicAssembly
53
+
54
+ Each DynamicAssembly block contains a shared pool of MHA and FFN
55
+ experts. At each iteration step, the mean-pooled board representation
56
+ is combined with a step embedding and passed to a router.
57
+
58
+ The router produces a probability distribution over the expert pool.
59
+ All experts are evaluated and their outputs are combined using the
60
+ router probabilities.
61
+
62
+ ### Policy Head
63
+
64
+ The policy head predicts a probability distribution over the 64
65
+ board positions.
66
+
67
+ ### Value Head
68
+
69
+ The value head predicts a scalar value in `[-1, 1]`, representing
70
+ the estimated game outcome.
71
+
72
+ ## Training
73
+
74
+ This model was trained using self-play game records generated by a previous CNN-based Othello AI.
75
+
76
+ ### Self-Play data generation
77
+ - MCTS simulations: 600
78
+ - MCTS batch size: 100
79
+ - PUCT constant: 2.3
80
+ - Total games: 10,000
81
+
82
+ ### Model Training
83
+ - Optimizer: AdamW
84
+ - Clipnorm: 1.0
85
+ - Weight decay: 0.05
86
+ - Learning rate: 6.0e-5
87
+ - Learning rate scheduler: ReduceLROnPlateau
88
+ - Training batch size: 256
89
+ - Epochs: 75
90
+
91
+ ## Evaluation
92
+
93
+ A 63M-parameter ResNet CNN model was used as the previous model.
94
+
95
+ ### Win rate
96
+ - vs Random : 100% (100 / 100 games)
97
+ - vs Prev model : 63% (63 / 100 games)
98
+
99
+ ### Average final stone count
100
+ - vs Random : 58
101
+ - vs Prev model : 47
102
+
103
+ ## Usage / Quick start
104
+ ```python
105
+ import numpy as np
106
+ import tensorflow as tf
107
+ from huggingface_hub import hf_hub_download
108
+ from model import TokenAndPositionEmbedding, MHA, FFN, DynamicAssembly
109
+
110
+ model_path = hf_hub_download(repo_id="rsu/Reversi-Transformer-1.6M_v2025-12-17", filename="M1.h5", repo_type="model")
111
+
112
+ custom_objects = {
113
+ "TokenAndPositionEmbedding": TokenAndPositionEmbedding,
114
+ "MHA": MHA,
115
+ "FFN": FFN,
116
+ "DynamicAssembly": DynamicAssembly,
117
+ }
118
+ model = tf.keras.models.load_model(model_path, custom_objects=custom_objects, compile=False)
119
+
120
+ board = np.zeros((1, 8, 8, 2), dtype=np.float32)
121
+ board[0, 3, 3, 1] = 1.0
122
+ board[0, 3, 4, 1] = 1.0
123
+ board[0, 3, 3, 1] = 1.0
124
+ board[0, 4, 4, 1] = 1.0
125
+
126
+ policy, value = model(board, training=False)
127
+ print("Policy (64 moves prob): ", policy.numpy())
128
+ print("Value (win rate est [-1, 1]): ", value.numpy().item())
129
+ ```
model.py ADDED
@@ -0,0 +1,146 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import tensorflow as tf
2
+ import keras
3
+ from keras import layers, models, ops
4
+
5
+ class TokenAndPositionEmbedding(layers.Layer):
6
+ def __init__(self, d_model, moves=64, **kwargs):
7
+ if 'position' in kwargs:
8
+ kwargs.pop('position')
9
+ super(TokenAndPositionEmbedding, self).__init__(**kwargs)
10
+ self.d_model = d_model
11
+ self.moves = moves
12
+ self.row_embedding = layers.Embedding(8, d_model, name="row_emb")
13
+ self.col_embedding = layers.Embedding(8, d_model, name="col_emb")
14
+ self.time_embedding = layers.Embedding(moves + 1, d_model, name="time_emb")
15
+
16
+ def call(self, inputs):
17
+ x, board = inputs
18
+
19
+ positions = tf.range(start=0, limit=64, delta=1, dtype=tf.int32)
20
+ r_emb = self.row_embedding(positions // 8)
21
+ c_emb = self.col_embedding(positions % 8)
22
+
23
+ stone_count = tf.reduce_sum(board, axis=[1, 2, 3])
24
+ current_moves = tf.cast(stone_count, tf.int32) - 4
25
+ current_moves = tf.maximum(current_moves, 0)
26
+
27
+ t_emb = self.time_embedding(current_moves)
28
+ t_emb = tf.expand_dims(t_emb, axis=1)
29
+
30
+ return x + tf.cast(r_emb, x.dtype) + tf.cast(c_emb, x.dtype) + tf.cast(t_emb, x.dtype)
31
+
32
+ class MHA(layers.Layer):
33
+ def __init__(self, d_model, num_heads, rate=0.2, **kwargs):
34
+ super().__init__(**kwargs)
35
+ self.att = layers.MultiHeadAttention(num_heads=num_heads, key_dim=d_model//num_heads)
36
+ self.layernorm = layers.LayerNormalization(epsilon=1e-6)
37
+ self.dropout = layers.Dropout(rate)
38
+
39
+ def call(self, x, training=False):
40
+ x_f32 = tf.cast(x, tf.float32)
41
+ normed_inputs = self.layernorm(x_f32)
42
+ attn_output = self.att(
43
+ query = normed_inputs,
44
+ value = normed_inputs,
45
+ key = normed_inputs,
46
+ training = training
47
+ )
48
+ attn_output = self.dropout(attn_output, training=training)
49
+ return x_f32 + tf.cast(attn_output, tf.float32)
50
+
51
+ class FFN(layers.Layer):
52
+ def __init__(self, d_model, rate=0.2, **kwargs):
53
+ super().__init__(**kwargs)
54
+ ff_dim = d_model * 4
55
+ self.ffn = models.Sequential([layers.Dense(ff_dim, activation='gelu'),layers.Dense(d_model)])
56
+ self.layernorm = layers.LayerNormalization(epsilon=1e-6)
57
+ self.dropout = layers.Dropout(rate)
58
+
59
+ def call(self, x, training=False):
60
+ x_f32 = tf.cast(x, tf.float32)
61
+ normed_inputs = self.layernorm(x_f32)
62
+ ffn_output = self.ffn(normed_inputs)
63
+ ffn_output = self.dropout(ffn_output, training=training)
64
+ return x_f32 + tf.cast(ffn_output, tf.float32)
65
+
66
+ class DynamicAssembly(layers.Layer):
67
+ def __init__(self, d_model, num_heads, num_mha=4, num_ffn=4, steps=8, rate=0.2, **kwargs):
68
+ super().__init__(**kwargs)
69
+ self.d_model = d_model
70
+ self.steps = steps
71
+ self.num_options = num_mha + num_ffn
72
+ self.layer_pool = []
73
+ for i in range(num_mha):
74
+ self.layer_pool.append(MHA(d_model, num_heads, rate, name=f"pool_mha_{i}"))
75
+ for i in range(num_ffn):
76
+ self.layer_pool.append(FFN(d_model, rate, name=f"pool_ffn_{i}"))
77
+ self.router_dense = layers.Dense(self.num_options, name="router")
78
+ self.step_embedding = layers.Embedding(steps, d_model)
79
+ self.last_probs = []
80
+
81
+ def call(self, x, training=False):
82
+ if not training:
83
+ self.last_probs = []
84
+
85
+ for i in range(self.steps):
86
+ step_vec = self.step_embedding(tf.convert_to_tensor([i]))
87
+ x_pooled = tf.reduce_mean(x, axis=1)
88
+
89
+ router_input = x_pooled + tf.cast(step_vec, x_pooled.dtype)
90
+ logits = self.router_dense(router_input)
91
+ probs = tf.nn.softmax(logits, axis=-1)
92
+
93
+ if not training:
94
+ self.last_probs.append(probs)
95
+
96
+ outputs = [layer(x, training=training) for layer in self.layer_pool]
97
+ stacked_outputs = tf.stack(outputs, axis=1)
98
+
99
+ probs_bc = tf.expand_dims(probs, axis=-1)
100
+ probs_bc = tf.expand_dims(probs_bc, axis=-1)
101
+ probs_bc = tf.cast(probs_bc, stacked_outputs.dtype)
102
+
103
+ weighted_sum = tf.reduce_sum(stacked_outputs * probs_bc, axis=1)
104
+
105
+ x = tf.cast(weighted_sum, x.dtype)
106
+ return x
107
+
108
+ def build_model(config):
109
+ d_model = config.get('embed_dim', 32)
110
+ num_blocks = config.get('block', 2)
111
+ num_heads = config.get('head', 4)
112
+ num_mha = config.get('num_mha', 2)
113
+ num_ffn = config.get('num_ffn', 2)
114
+ steps = config.get('steps', 3)
115
+ dropout_rate = config.get('dropout', 0.2)
116
+ input_shape = (8, 8, 2)
117
+
118
+ inputs = layers.Input(shape=input_shape, dtype=tf.float32)
119
+ x = layers.Reshape((64, 2))(inputs)
120
+ x = layers.Dense(d_model)(x)
121
+ x = TokenAndPositionEmbedding(d_model, 64)([x, inputs])
122
+
123
+ for _ in range(num_blocks):
124
+ x = DynamicAssembly(d_model, num_heads, num_mha=num_mha, num_ffn=num_ffn, steps=steps, rate=dropout_rate)(x)
125
+
126
+ # Policy Head
127
+ policy_x = layers.Dense(d_model, activation='relu', name="policy_hidden")(x)
128
+ policy_logits = layers.Dense(1, name="policy_logits")(policy_x)
129
+ policy_logits = layers.Reshape((64,))(policy_logits)
130
+ policy_head = layers.Activation('softmax', name='p', dtype='float32')(policy_logits)
131
+
132
+ # Value Head (M1.h5 actual structure: Conv1D -> Flatten -> Dense(128))
133
+ value_x = layers.Conv1D(8, 1, activation='relu')(x)
134
+ value_x = layers.Flatten()(value_x)
135
+ value_x = layers.Dense(128, activation='relu', name="value_hidden")(value_x)
136
+ value_head = layers.Dense(1, activation='tanh', name='v', dtype='float32')(value_x)
137
+
138
+ model = models.Model(inputs=inputs, outputs=[policy_head, value_head])
139
+
140
+ return model
141
+
142
+ if __name__ == '__main__':
143
+ conf = {'embed_dim': 128, 'block': 4, 'head': 4, 'num_mha': 2, 'num_ffn': 2, 'steps': 2}
144
+ model = build_model(conf)
145
+ model.summary()
146
+ print(f"Total Params: {model.count_params()}")
model.yaml ADDED
@@ -0,0 +1,11 @@
 
 
 
 
 
 
 
 
 
 
 
 
1
+ default_model: m1
2
+
3
+ m1:
4
+ arch: moe_1
5
+ embed_dim: 128
6
+ block: 4
7
+ head: 4
8
+ num_mha: 2
9
+ num_ffn: 2
10
+ steps: 2
11
+ model_save_path: "models/TF/M1.h5"
requirements.txt ADDED
@@ -0,0 +1,2 @@
 
 
 
1
+ tensorflow
2
+ numpy