MaximeMuhlethaler commited on
Commit
bd0f882
·
verified ·
1 Parent(s): 90d2abf

Chess Challenge submission by MaximeMuhlethaler

Browse files
Files changed (8) hide show
  1. README.md +26 -0
  2. config.json +25 -0
  3. model.py +141 -0
  4. pytorch_model.bin +3 -0
  5. special_tokens_map.json +6 -0
  6. tokenizer.py +195 -0
  7. tokenizer_config.json +47 -0
  8. vocab.json +74 -0
README.md ADDED
@@ -0,0 +1,26 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ ---
2
+ library_name: transformers
3
+ tags:
4
+ - chess
5
+ - llm-course
6
+ - chess-challenge
7
+ license: mit
8
+ ---
9
+
10
+ # chess_MaximeMuh3
11
+
12
+ Chess model submitted to the LLM Course Chess Challenge.
13
+
14
+ ## Submission Info
15
+
16
+ - **Submitted by**: [MaximeMuhlethaler](https://huggingface.co/MaximeMuhlethaler)
17
+ - **Parameters**: 997,376
18
+ - **Organization**: LLM-course
19
+
20
+ ## Model Details
21
+
22
+ - **Architecture**: Chess Transformer (GPT-style)
23
+ - **Vocab size**: 72
24
+ - **Embedding dim**: 128
25
+ - **Layers**: 7
26
+ - **Heads**: 8
config.json ADDED
@@ -0,0 +1,25 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "architectures": [
3
+ "ChessForCausalLM"
4
+ ],
5
+ "auto_map": {
6
+ "AutoConfig": "model.ChessConfig",
7
+ "AutoModelForCausalLM": "model.ChessForCausalLM"
8
+ },
9
+ "bias": false,
10
+ "bos_token_id": 1,
11
+ "dropout": 0.05,
12
+ "dtype": "float32",
13
+ "eos_token_id": 2,
14
+ "model_type": "chess_transformer",
15
+ "n_ctx": 256,
16
+ "n_embd": 128,
17
+ "n_head": 8,
18
+ "n_inner": 275,
19
+ "n_layer": 7,
20
+ "pad_token_id": 0,
21
+ "tie_weights": true,
22
+ "transformers_version": "4.57.6",
23
+ "unk_token_id": 3,
24
+ "vocab_size": 72
25
+ }
model.py ADDED
@@ -0,0 +1,141 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """
2
+ Optimized Chess Transformer
3
+ Strategy: Deep & Narrow.
4
+ Max parameters dedicated to reasoning (Layers), minimal for Vocab.
5
+ """
6
+ from __future__ import annotations
7
+ import math
8
+ from typing import Optional, Tuple, Union
9
+ import torch
10
+ import torch.nn as nn
11
+ import torch.nn.functional as F
12
+ from transformers import PretrainedConfig, PreTrainedModel
13
+ from transformers.modeling_outputs import CausalLMOutputWithPast
14
+
15
+ class ChessConfig(PretrainedConfig):
16
+ model_type = "chess_transformer"
17
+
18
+ def __init__(
19
+ self,
20
+ vocab_size=80,
21
+ n_embd=128,
22
+ n_layer=10,
23
+ n_head=8,
24
+ n_ctx=256,
25
+ n_inner=None,
26
+ dropout=0.0,
27
+ bias=False,
28
+ tie_weights=True,
29
+ pad_token_id=0,
30
+ bos_token_id=1,
31
+ eos_token_id=2,
32
+ unk_token_id=3,
33
+ **kwargs,
34
+ ):
35
+ self.vocab_size = vocab_size
36
+ self.n_embd = n_embd
37
+ self.n_layer = n_layer
38
+ self.n_head = n_head
39
+ self.n_ctx = n_ctx
40
+ self.n_inner = n_inner if n_inner is not None else 4 * n_embd
41
+ self.dropout = dropout
42
+ self.bias = bias
43
+ self.tie_weights = tie_weights
44
+ super().__init__(pad_token_id=pad_token_id, bos_token_id=bos_token_id, eos_token_id=eos_token_id, unk_token_id=unk_token_id, **kwargs)
45
+
46
+ class MultiHeadAttention(nn.Module):
47
+ def __init__(self, config: ChessConfig):
48
+ super().__init__()
49
+ self.n_head = config.n_head
50
+ self.n_embd = config.n_embd
51
+ self.head_dim = config.n_embd // config.n_head
52
+ self.c_attn = nn.Linear(config.n_embd, 3 * config.n_embd, bias=config.bias)
53
+ self.c_proj = nn.Linear(config.n_embd, config.n_embd, bias=config.bias)
54
+ self.dropout = nn.Dropout(config.dropout)
55
+ self.register_buffer("bias_mask", torch.tril(torch.ones(config.n_ctx, config.n_ctx)).view(1, 1, config.n_ctx, config.n_ctx), persistent=False)
56
+
57
+ def forward(self, x, attention_mask=None):
58
+ B, T, C = x.size()
59
+ qkv = self.c_attn(x)
60
+ q, k, v = qkv.split(self.n_embd, dim=2)
61
+ q = q.view(B, T, self.n_head, self.head_dim).transpose(1, 2)
62
+ k = k.view(B, T, self.n_head, self.head_dim).transpose(1, 2)
63
+ v = v.view(B, T, self.n_head, self.head_dim).transpose(1, 2)
64
+ att = (q @ k.transpose(-2, -1)) * (1.0 / math.sqrt(self.head_dim))
65
+ att = att.masked_fill(self.bias_mask[:,:,:T,:T] == 0, float('-inf'))
66
+ if attention_mask is not None: att = att.masked_fill(attention_mask.view(B, 1, 1, T) == 0, float('-inf'))
67
+ att = F.softmax(att, dim=-1)
68
+ att = self.dropout(att)
69
+ y = att @ v
70
+ y = y.transpose(1, 2).contiguous().view(B, T, C)
71
+ return self.c_proj(y)
72
+
73
+ class FeedForward(nn.Module):
74
+ def __init__(self, config: ChessConfig):
75
+ super().__init__()
76
+ self.c_fc = nn.Linear(config.n_embd, config.n_inner, bias=config.bias)
77
+ self.c_proj = nn.Linear(config.n_inner, config.n_embd, bias=config.bias)
78
+ self.dropout = nn.Dropout(config.dropout)
79
+ def forward(self, x):
80
+ return self.dropout(self.c_proj(F.gelu(self.c_fc(x))))
81
+
82
+ class TransformerBlock(nn.Module):
83
+ def __init__(self, config: ChessConfig):
84
+ super().__init__()
85
+ self.ln_1 = nn.LayerNorm(config.n_embd)
86
+ self.attn = MultiHeadAttention(config)
87
+ self.ln_2 = nn.LayerNorm(config.n_embd)
88
+ self.mlp = FeedForward(config)
89
+ def forward(self, x, attention_mask=None):
90
+ x = x + self.attn(self.ln_1(x), attention_mask)
91
+ x = x + self.mlp(self.ln_2(x))
92
+ return x
93
+
94
+ class ChessForCausalLM(PreTrainedModel):
95
+ config_class = ChessConfig
96
+ base_model_prefix = "transformer"
97
+ def __init__(self, config: ChessConfig):
98
+ super().__init__(config)
99
+ self.wte = nn.Embedding(config.vocab_size, config.n_embd)
100
+ self.wpe = nn.Embedding(config.n_ctx, config.n_embd)
101
+ self.drop = nn.Dropout(config.dropout)
102
+ self.h = nn.ModuleList([TransformerBlock(config) for _ in range(config.n_layer)])
103
+ self.ln_f = nn.LayerNorm(config.n_embd)
104
+ self.lm_head = nn.Linear(config.n_embd, config.vocab_size, bias=False)
105
+ if config.tie_weights: self.lm_head.weight = self.wte.weight
106
+ self.post_init()
107
+
108
+ def get_input_embeddings(self): return self.wte
109
+ def set_input_embeddings(self, new): self.wte = new
110
+ def get_output_embeddings(self): return self.lm_head
111
+ def set_output_embeddings(self, new): self.lm_head = new
112
+
113
+ def forward(self, input_ids, attention_mask=None, position_ids=None, labels=None, return_dict=None, **kwargs):
114
+ return_dict = return_dict if return_dict is not None else self.config.use_return_dict
115
+ if return_dict is None: return_dict = True
116
+ device = input_ids.device
117
+ b, t = input_ids.size()
118
+ if position_ids is None: position_ids = torch.arange(t, device=device).unsqueeze(0)
119
+ x = self.wte(input_ids) + self.wpe(position_ids)
120
+ x = self.drop(x)
121
+ for block in self.h: x = block(x, attention_mask)
122
+ x = self.ln_f(x)
123
+ logits = self.lm_head(x)
124
+
125
+ # Protection génération
126
+ if labels is None:
127
+ logits[:, :, [self.config.pad_token_id, self.config.bos_token_id, self.config.unk_token_id]] = float("-inf")
128
+
129
+ loss = None
130
+ if labels is not None:
131
+ shift_logits = logits[..., :-1, :].contiguous()
132
+ shift_labels = labels[..., 1:].contiguous()
133
+ loss_fct = nn.CrossEntropyLoss(ignore_index=self.config.pad_token_id)
134
+ loss = loss_fct(shift_logits.view(-1, shift_logits.size(-1)), shift_labels.view(-1))
135
+
136
+ if not return_dict: return (loss, logits) if loss is not None else (logits,)
137
+ return CausalLMOutputWithPast(loss=loss, logits=logits)
138
+
139
+ from transformers import AutoConfig, AutoModelForCausalLM
140
+ AutoConfig.register("chess_transformer", ChessConfig)
141
+ AutoModelForCausalLM.register(ChessConfig, ChessForCausalLM)
pytorch_model.bin ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:19ac6a27df140ea95ed952ddce10f4a085b904e5e3d563cea00febeb8ef22fc2
3
+ size 4007995
special_tokens_map.json ADDED
@@ -0,0 +1,6 @@
 
 
 
 
 
 
 
1
+ {
2
+ "bos_token": "[BOS]",
3
+ "eos_token": "[EOS]",
4
+ "pad_token": "[PAD]",
5
+ "unk_token": "[UNK]"
6
+ }
tokenizer.py ADDED
@@ -0,0 +1,195 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from __future__ import annotations
2
+
3
+ import json
4
+ import os
5
+ import re
6
+ import shutil
7
+ from typing import Dict, List, Optional
8
+
9
+ from transformers import PreTrainedTokenizer
10
+
11
+
12
+ REGEX_CASE = re.compile(r"([a-h][1-8])")
13
+
14
+ REGEX_PROMO = re.compile(r"[=\(]?([qrbnQRBN])[\)]?$")
15
+
16
+ class ChessTokenizer(PreTrainedTokenizer):
17
+ """
18
+ Tokenizer qui traite le jeu d'échecs case par case.
19
+ Vocabulaire déterministe : Spéciaux + Cases (a1..h8) + Promotions.
20
+ """
21
+
22
+
23
+ vocab_files_names = {"vocab_file": "vocab.json"}
24
+ model_input_names = ["input_ids", "attention_mask"]
25
+
26
+ # Tokens
27
+ PAD_TOKEN = "[PAD]"
28
+ BOS_TOKEN = "[BOS]"
29
+ EOS_TOKEN = "[EOS]"
30
+ UNK_TOKEN = "[UNK]"
31
+
32
+ def __init__(
33
+ self,
34
+ vocab_file: Optional[str] = None,
35
+ vocab: Optional[Dict[str, int]] = None,
36
+ **kwargs,
37
+ ):
38
+ self._pad_token = self.PAD_TOKEN
39
+ self._bos_token = self.BOS_TOKEN
40
+ self._eos_token = self.EOS_TOKEN
41
+ self._unk_token = self.UNK_TOKEN
42
+
43
+ for cle in ["pad_token", "bos_token", "eos_token", "unk_token"]:
44
+ kwargs.pop(cle, None)
45
+
46
+ if vocab:
47
+ self.map_token_id = vocab
48
+ elif vocab_file and os.path.exists(vocab_file):
49
+ with open(vocab_file, "r", encoding="utf-8") as f:
50
+ self.map_token_id = json.load(f)
51
+ else:
52
+ self.map_token_id = self._generer_vocabulaire()
53
+
54
+
55
+ self.map_id_token = {i: t for t, i in self.map_token_id.items()}
56
+
57
+ super().__init__(
58
+ pad_token=self._pad_token,
59
+ bos_token=self._bos_token,
60
+ eos_token=self._eos_token,
61
+ unk_token=self._unk_token,
62
+ **kwargs,
63
+ )
64
+
65
+ def _generer_vocabulaire(self) -> Dict[str, int]:
66
+ """Génère la liste fixe des tokens nécessaires."""
67
+ liste_tokens = [self.PAD_TOKEN, self.BOS_TOKEN, self.EOS_TOKEN, self.UNK_TOKEN]
68
+
69
+ colonnes = "abcdefgh"
70
+ lignes = "12345678"
71
+ cases = [f"{c}{l}" for c in colonnes for l in lignes]
72
+ liste_tokens.extend(cases)
73
+
74
+ pieces_promo = ["q", "r", "b", "n"]
75
+ liste_tokens.extend(pieces_promo)
76
+
77
+
78
+ return {t: i for i, t in enumerate(liste_tokens)}
79
+
80
+ @property
81
+ def vocab_size(self) -> int:
82
+ return len(self.map_token_id)
83
+
84
+ def get_vocab(self) -> Dict[str, int]:
85
+ return dict(self.map_token_id)
86
+
87
+ def _tokenize(self, text: str) -> List[str]:
88
+ """
89
+ Transforme une phrase de coups en liste de tokens.
90
+ """
91
+ resultat = []
92
+
93
+
94
+ mouvements = text.strip().split()
95
+
96
+ for mv in mouvements:
97
+
98
+ cases_trouvees = REGEX_CASE.findall(mv)
99
+
100
+
101
+ if len(cases_trouvees) >= 2:
102
+
103
+ resultat.extend(cases_trouvees[:2])
104
+
105
+
106
+ match_promo = REGEX_PROMO.search(mv)
107
+ if match_promo:
108
+
109
+ resultat.append(match_promo.group(1).lower())
110
+
111
+
112
+ elif mv in self.map_token_id:
113
+ resultat.append(mv)
114
+ else:
115
+
116
+ resultat.append(self.UNK_TOKEN)
117
+
118
+ return resultat
119
+
120
+ def _convert_token_to_id(self, token: str) -> int:
121
+ return self.map_token_id.get(token, self.map_token_id[self.UNK_TOKEN])
122
+
123
+ def _convert_id_to_token(self, index: int) -> str:
124
+ return self.map_id_token.get(index, self.UNK_TOKEN)
125
+
126
+ def convert_tokens_to_string(self, tokens: List[str]) -> str:
127
+ """
128
+ Reconstruit la chaine de caractères depuis les tokens.
129
+ Logique : on assemble les paires de cases.
130
+ """
131
+ sortie = []
132
+ tampon_cases = []
133
+
134
+ exclus = {self.PAD_TOKEN, self.BOS_TOKEN, self.EOS_TOKEN, self.UNK_TOKEN}
135
+ promotions = {"q", "r", "b", "n"}
136
+
137
+ for t in tokens:
138
+ if t in exclus:
139
+ continue
140
+
141
+ if t in promotions:
142
+ if sortie:
143
+ sortie[-1] += t
144
+ else:
145
+ tampon_cases.append(t)
146
+
147
+ if len(tampon_cases) == 2:
148
+ coup_complet = "".join(tampon_cases)
149
+ sortie.append(coup_complet)
150
+ tampon_cases = []
151
+
152
+ return " ".join(sortie)
153
+
154
+ def save_vocabulary(self, save_directory: str, filename_prefix: Optional[str] = None) -> tuple:
155
+ """Sauvegarde le vocabulaire sur le disque."""
156
+ if not os.path.exists(save_directory):
157
+ os.makedirs(save_directory)
158
+
159
+ nom_fichier = "vocab.json"
160
+ if filename_prefix:
161
+ nom_fichier = f"{filename_prefix}-{nom_fichier}"
162
+
163
+ chemin_complet = os.path.join(save_directory, nom_fichier)
164
+
165
+ with open(chemin_complet, "w", encoding="utf-8") as f:
166
+ json.dump(self.map_token_id, f, ensure_ascii=False, indent=2)
167
+
168
+ return (chemin_complet,)
169
+
170
+
171
+ def save_pretrained(self, save_directory: str, **kwargs):
172
+ """
173
+ Sauvegarde standard + Copie du script tokenizer.py pour Hugging Face.
174
+ """
175
+ super().save_pretrained(save_directory, **kwargs)
176
+
177
+ source = os.path.abspath(__file__)
178
+ dest = os.path.join(save_directory, "tokenizer.py")
179
+ if source != dest:
180
+ shutil.copy(source, dest)
181
+
182
+ chem_config = os.path.join(save_directory, "tokenizer_config.json")
183
+ if os.path.exists(chem_config):
184
+ with open(chem_config, "r") as f:
185
+ cfg = json.load(f)
186
+ cfg["auto_map"] = {"AutoTokenizer": "tokenizer.ChessTokenizer"}
187
+ with open(chem_config, "w") as f:
188
+ json.dump(cfg, f, indent=2)
189
+
190
+
191
+ from transformers import AutoTokenizer
192
+ try:
193
+ ChessTokenizer.register_for_auto_class("AutoTokenizer")
194
+ except:
195
+ pass
tokenizer_config.json ADDED
@@ -0,0 +1,47 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "added_tokens_decoder": {
3
+ "0": {
4
+ "content": "[PAD]",
5
+ "lstrip": false,
6
+ "normalized": false,
7
+ "rstrip": false,
8
+ "single_word": false,
9
+ "special": true
10
+ },
11
+ "1": {
12
+ "content": "[BOS]",
13
+ "lstrip": false,
14
+ "normalized": false,
15
+ "rstrip": false,
16
+ "single_word": false,
17
+ "special": true
18
+ },
19
+ "2": {
20
+ "content": "[EOS]",
21
+ "lstrip": false,
22
+ "normalized": false,
23
+ "rstrip": false,
24
+ "single_word": false,
25
+ "special": true
26
+ },
27
+ "3": {
28
+ "content": "[UNK]",
29
+ "lstrip": false,
30
+ "normalized": false,
31
+ "rstrip": false,
32
+ "single_word": false,
33
+ "special": true
34
+ }
35
+ },
36
+ "auto_map": {
37
+ "AutoTokenizer": "tokenizer.ChessTokenizer"
38
+ },
39
+ "bos_token": "[BOS]",
40
+ "clean_up_tokenization_spaces": false,
41
+ "eos_token": "[EOS]",
42
+ "extra_special_tokens": {},
43
+ "model_max_length": 1000000000000000019884624838656,
44
+ "pad_token": "[PAD]",
45
+ "tokenizer_class": "ChessTokenizer",
46
+ "unk_token": "[UNK]"
47
+ }
vocab.json ADDED
@@ -0,0 +1,74 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "[PAD]": 0,
3
+ "[BOS]": 1,
4
+ "[EOS]": 2,
5
+ "[UNK]": 3,
6
+ "a1": 4,
7
+ "a2": 5,
8
+ "a3": 6,
9
+ "a4": 7,
10
+ "a5": 8,
11
+ "a6": 9,
12
+ "a7": 10,
13
+ "a8": 11,
14
+ "b1": 12,
15
+ "b2": 13,
16
+ "b3": 14,
17
+ "b4": 15,
18
+ "b5": 16,
19
+ "b6": 17,
20
+ "b7": 18,
21
+ "b8": 19,
22
+ "c1": 20,
23
+ "c2": 21,
24
+ "c3": 22,
25
+ "c4": 23,
26
+ "c5": 24,
27
+ "c6": 25,
28
+ "c7": 26,
29
+ "c8": 27,
30
+ "d1": 28,
31
+ "d2": 29,
32
+ "d3": 30,
33
+ "d4": 31,
34
+ "d5": 32,
35
+ "d6": 33,
36
+ "d7": 34,
37
+ "d8": 35,
38
+ "e1": 36,
39
+ "e2": 37,
40
+ "e3": 38,
41
+ "e4": 39,
42
+ "e5": 40,
43
+ "e6": 41,
44
+ "e7": 42,
45
+ "e8": 43,
46
+ "f1": 44,
47
+ "f2": 45,
48
+ "f3": 46,
49
+ "f4": 47,
50
+ "f5": 48,
51
+ "f6": 49,
52
+ "f7": 50,
53
+ "f8": 51,
54
+ "g1": 52,
55
+ "g2": 53,
56
+ "g3": 54,
57
+ "g4": 55,
58
+ "g5": 56,
59
+ "g6": 57,
60
+ "g7": 58,
61
+ "g8": 59,
62
+ "h1": 60,
63
+ "h2": 61,
64
+ "h3": 62,
65
+ "h4": 63,
66
+ "h5": 64,
67
+ "h6": 65,
68
+ "h7": 66,
69
+ "h8": 67,
70
+ "q": 68,
71
+ "r": 69,
72
+ "b": 70,
73
+ "n": 71
74
+ }