maia2 0.1__py2.py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- maia2/__init__.py +3 -0
- maia2/fetch_data.sh +28 -0
- maia2/inference.py +154 -0
- maia2/main.py +742 -0
- maia2/utils.py +347 -0
- maia2-0.1.dist-info/LICENSE +24 -0
- maia2-0.1.dist-info/METADATA +10 -0
- maia2-0.1.dist-info/RECORD +9 -0
- maia2-0.1.dist-info/WHEEL +5 -0
maia2/__init__.py
ADDED
maia2/fetch_data.sh
ADDED
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
#!/bin/bash
|
|
2
|
+
|
|
3
|
+
# Loop over each year
|
|
4
|
+
for year in {2018..2023}; do
|
|
5
|
+
# Loop over each month
|
|
6
|
+
for month in {05..12}; do
|
|
7
|
+
# Form the URL
|
|
8
|
+
url="https://database.lichess.org/standard/lichess_db_standard_rated_${year}-${month}.pgn.zst"
|
|
9
|
+
|
|
10
|
+
# Check if the URL exists
|
|
11
|
+
if wget --spider $url 2>/dev/null; then
|
|
12
|
+
# Download the file
|
|
13
|
+
wget "$url"
|
|
14
|
+
|
|
15
|
+
# The downloaded file name
|
|
16
|
+
filename="lichess_db_standard_rated_${year}-${month}.pgn.zst"
|
|
17
|
+
|
|
18
|
+
else
|
|
19
|
+
echo "File for ${year}-${month} does not exist."
|
|
20
|
+
fi
|
|
21
|
+
|
|
22
|
+
# Stop the loop if we've reached October 2023
|
|
23
|
+
if [ "$year" -eq 2023 ] && [ "$month" = "10" ]; then
|
|
24
|
+
break
|
|
25
|
+
fi
|
|
26
|
+
done
|
|
27
|
+
|
|
28
|
+
done
|
maia2/inference.py
ADDED
|
@@ -0,0 +1,154 @@
|
|
|
1
|
+
from utils import *
|
|
2
|
+
from main import *
|
|
3
|
+
|
|
4
|
+
|
|
5
|
+
class TestDataset(torch.utils.data.Dataset):
|
|
6
|
+
|
|
7
|
+
def __init__(self, data, all_moves_dict, elo_dict):
|
|
8
|
+
|
|
9
|
+
self.all_moves_dict = all_moves_dict
|
|
10
|
+
self.data = data.values.tolist()
|
|
11
|
+
self.elo_dict = elo_dict
|
|
12
|
+
|
|
13
|
+
def __len__(self):
|
|
14
|
+
|
|
15
|
+
return len(self.data)
|
|
16
|
+
|
|
17
|
+
def __getitem__(self, idx):
|
|
18
|
+
|
|
19
|
+
fen, move, elo_self, elo_oppo = self.data[idx]
|
|
20
|
+
|
|
21
|
+
if fen.split(' ')[1] == 'w':
|
|
22
|
+
board = chess.Board(fen)
|
|
23
|
+
elif fen.split(' ')[1] == 'b':
|
|
24
|
+
board = chess.Board(fen).mirror()
|
|
25
|
+
move = mirror_move(move)
|
|
26
|
+
else:
|
|
27
|
+
raise ValueError(f"Invalid fen: {fen}")
|
|
28
|
+
|
|
29
|
+
board_input = board_to_tensor(board)
|
|
30
|
+
|
|
31
|
+
elo_self = map_to_category(elo_self, self.elo_dict)
|
|
32
|
+
elo_oppo = map_to_category(elo_oppo, self.elo_dict)
|
|
33
|
+
|
|
34
|
+
legal_moves, _ = get_side_info(board, move, self.all_moves_dict)
|
|
35
|
+
|
|
36
|
+
return fen, board_input, elo_self, elo_oppo, legal_moves
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
def get_preds(model, dataloader, all_moves_dict_reversed, cfg_inference):
|
|
40
|
+
|
|
41
|
+
all_probs = []
|
|
42
|
+
predicted_move_probs = []
|
|
43
|
+
predicted_moves = []
|
|
44
|
+
predicted_win_probs = []
|
|
45
|
+
|
|
46
|
+
model.eval()
|
|
47
|
+
with torch.no_grad():
|
|
48
|
+
|
|
49
|
+
for fens, boards, elos_self, elos_oppo, legal_moves in dataloader:
|
|
50
|
+
|
|
51
|
+
if cfg_inference.gpu:
|
|
52
|
+
boards = boards.cuda()
|
|
53
|
+
elos_self = elos_self.cuda()
|
|
54
|
+
elos_oppo = elos_oppo.cuda()
|
|
55
|
+
legal_moves = legal_moves.cuda()
|
|
56
|
+
|
|
57
|
+
logits_maia, _, logits_value = model(boards, elos_self, elos_oppo)
|
|
58
|
+
logits_maia_legal = logits_maia * legal_moves
|
|
59
|
+
probs = logits_maia_legal.softmax(dim=-1)
|
|
60
|
+
|
|
61
|
+
all_probs.append(probs.cpu())
|
|
62
|
+
predicted_move_probs.append(probs.max(dim=-1).values.cpu())
|
|
63
|
+
predicted_move_indices = probs.argmax(dim=-1)
|
|
64
|
+
for i in range(len(fens)):
|
|
65
|
+
fen = fens[i]
|
|
66
|
+
predicted_move = all_moves_dict_reversed[predicted_move_indices[i].item()]
|
|
67
|
+
if fen.split(' ')[1] == 'b':
|
|
68
|
+
predicted_move = mirror_move(predicted_move)
|
|
69
|
+
predicted_moves.append(predicted_move)
|
|
70
|
+
|
|
71
|
+
predicted_win_probs.append((logits_value / 2 + 0.5).cpu())
|
|
72
|
+
|
|
73
|
+
all_probs = torch.cat(all_probs).cpu().numpy()
|
|
74
|
+
predicted_move_probs = torch.cat(predicted_move_probs).numpy()
|
|
75
|
+
predicted_win_probs = torch.cat(predicted_win_probs).numpy()
|
|
76
|
+
|
|
77
|
+
return all_probs, predicted_move_probs, predicted_moves, predicted_win_probs
|
|
78
|
+
|
|
79
|
+
|
|
80
|
+
def inference_batch(data):
|
|
81
|
+
|
|
82
|
+
cfg = parse_args()
|
|
83
|
+
cfg_inference = parse_inference_args()
|
|
84
|
+
if cfg_inference.verbose:
|
|
85
|
+
show_cfg(cfg_inference)
|
|
86
|
+
|
|
87
|
+
all_moves = get_all_possible_moves()
|
|
88
|
+
all_moves_dict = {move: i for i, move in enumerate(all_moves)}
|
|
89
|
+
elo_dict = create_elo_dict()
|
|
90
|
+
|
|
91
|
+
model = MAIA2Model(len(all_moves), elo_dict, cfg)
|
|
92
|
+
model = nn.DataParallel(model)
|
|
93
|
+
|
|
94
|
+
checkpoint = torch.load(cfg_inference.model_path, map_location='cpu')
|
|
95
|
+
model.load_state_dict(checkpoint['model_state_dict'])
|
|
96
|
+
model = model.module
|
|
97
|
+
|
|
98
|
+
if cfg_inference.gpu:
|
|
99
|
+
model = model.cuda()
|
|
100
|
+
|
|
101
|
+
all_moves_dict_reversed = {v: k for k, v in all_moves_dict.items()}
|
|
102
|
+
dataset = TestDataset(data, all_moves_dict, elo_dict)
|
|
103
|
+
dataloader = torch.utils.data.DataLoader(dataset,
|
|
104
|
+
batch_size=cfg_inference.batch_size,
|
|
105
|
+
shuffle=False,
|
|
106
|
+
drop_last=False,
|
|
107
|
+
num_workers=cfg_inference.num_workers)
|
|
108
|
+
if cfg_inference.verbose:
|
|
109
|
+
dataloader = tqdm.tqdm(dataloader)
|
|
110
|
+
all_probs, predicted_move_probs, predicted_moves, predicted_win_probs = get_preds(model, dataloader, all_moves_dict_reversed, cfg_inference)
|
|
111
|
+
|
|
112
|
+
data['predicted_move'] = predicted_moves
|
|
113
|
+
data['predicted_move_prob'] = predicted_move_probs
|
|
114
|
+
data['predicted_win_prob'] = predicted_win_probs
|
|
115
|
+
data['all_probs'] = all_probs.tolist()
|
|
116
|
+
|
|
117
|
+
return data
|
|
118
|
+
|
|
119
|
+
def parse_inference_args(args=None):
|
|
120
|
+
|
|
121
|
+
parser = argparse.ArgumentParser()
|
|
122
|
+
|
|
123
|
+
parser.add_argument('--verbose', default=1, type=int)
|
|
124
|
+
parser.add_argument('--batch_size', default=2048, type=int)
|
|
125
|
+
parser.add_argument('--num_workers', default=16, type=int)
|
|
126
|
+
parser.add_argument('--model_path', default='your_model_path', type=str)
|
|
127
|
+
parser.add_argument('--gpu', default=True, type=bool)
|
|
128
|
+
|
|
129
|
+
return parser.parse_args(args)
|
|
130
|
+
|
|
131
|
+
def show_cfg(cfg):
|
|
132
|
+
print('Configurations:', flush=True)
|
|
133
|
+
for arg in vars(cfg):
|
|
134
|
+
print(f'\t{arg}: {getattr(cfg, arg)}', flush=True)
|
|
135
|
+
|
|
136
|
+
|
|
137
|
+
if __name__ == '__main__':
|
|
138
|
+
|
|
139
|
+
data = pd.read_csv('../data/all_reduced_rapid.csv')
|
|
140
|
+
data = data[data.move_ply > 10]
|
|
141
|
+
|
|
142
|
+
data_novice = data[data['rounded_elo'] <= 1500][['board', 'move', 'active_elo', 'opponent_elo']]
|
|
143
|
+
data_intermediate = data[(data['rounded_elo'] > 1500) & (data['rounded_elo'] < 2000)][['board', 'move', 'active_elo', 'opponent_elo']]
|
|
144
|
+
data_advanced = data[data['rounded_elo'] >= 2000][['board', 'move', 'active_elo', 'opponent_elo']]
|
|
145
|
+
print(f'lens: {len(data_novice)}, {len(data_intermediate)}, {len(data_advanced)}', flush=True)
|
|
146
|
+
|
|
147
|
+
results = []
|
|
148
|
+
for split in [data_novice, data_intermediate, data_advanced]:
|
|
149
|
+
inference_batch(split)
|
|
150
|
+
print(round(len(split[split['predicted_move'] == split['move']]) / len(split), 4))
|
|
151
|
+
|
|
152
|
+
|
|
153
|
+
|
|
154
|
+
|
maia2/main.py
ADDED
|
@@ -0,0 +1,742 @@
|
|
|
1
|
+
import chess.pgn
|
|
2
|
+
import chess
|
|
3
|
+
import pdb
|
|
4
|
+
from multiprocessing import Pool, cpu_count, Queue, Process
|
|
5
|
+
import torch
|
|
6
|
+
import tqdm
|
|
7
|
+
import argparse
|
|
8
|
+
from utils import *
|
|
9
|
+
import torch.nn as nn
|
|
10
|
+
import torch.nn.functional as F
|
|
11
|
+
from tqdm.contrib.concurrent import process_map
|
|
12
|
+
import os
|
|
13
|
+
import pandas as pd
|
|
14
|
+
import time
|
|
15
|
+
from einops import rearrange
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
def process_chunks(cfg, pgn_path, pgn_chunks, elo_dict):
|
|
19
|
+
|
|
20
|
+
# process_per_chunk((pgn_chunks[0][0], pgn_chunks[0][1], pgn_path, elo_dict, cfg))
|
|
21
|
+
|
|
22
|
+
if cfg.verbose:
|
|
23
|
+
results = process_map(process_per_chunk, [(start, end, pgn_path, elo_dict, cfg) for start, end in pgn_chunks], max_workers=len(pgn_chunks), chunksize=1)
|
|
24
|
+
else:
|
|
25
|
+
with Pool(processes=len(pgn_chunks)) as pool:
|
|
26
|
+
results = pool.map(process_per_chunk, [(start, end, pgn_path, elo_dict, cfg) for start, end in pgn_chunks])
|
|
27
|
+
|
|
28
|
+
ret = []
|
|
29
|
+
count = 0
|
|
30
|
+
list_of_dicts = []
|
|
31
|
+
for result, game_count, frequency in results:
|
|
32
|
+
ret.extend(result)
|
|
33
|
+
count += game_count
|
|
34
|
+
list_of_dicts.append(frequency)
|
|
35
|
+
|
|
36
|
+
total_counts = {}
|
|
37
|
+
|
|
38
|
+
for d in list_of_dicts:
|
|
39
|
+
for key, value in d.items():
|
|
40
|
+
total_counts[key] = total_counts.get(key, 0) + value
|
|
41
|
+
|
|
42
|
+
print(total_counts, flush=True)
|
|
43
|
+
|
|
44
|
+
return ret, count, len(pgn_chunks)
|
|
45
|
+
|
|
46
|
+
|
|
47
|
+
def process_per_game(game, white_elo, black_elo, white_win, cfg):
|
|
48
|
+
|
|
49
|
+
ret = []
|
|
50
|
+
|
|
51
|
+
board = game.board()
|
|
52
|
+
moves = list(game.mainline_moves())
|
|
53
|
+
|
|
54
|
+
for i, node in enumerate(game.mainline()):
|
|
55
|
+
|
|
56
|
+
move = moves[i]
|
|
57
|
+
|
|
58
|
+
if i >= cfg.first_n_moves:
|
|
59
|
+
|
|
60
|
+
comment = node.comment
|
|
61
|
+
clock_info = extract_clock_time(comment)
|
|
62
|
+
|
|
63
|
+
if i % 2 == 0:
|
|
64
|
+
board_input = board.fen()
|
|
65
|
+
move_input = move.uci()
|
|
66
|
+
elo_self = white_elo
|
|
67
|
+
elo_oppo = black_elo
|
|
68
|
+
active_win = white_win
|
|
69
|
+
|
|
70
|
+
else:
|
|
71
|
+
board_input = board.mirror().fen()
|
|
72
|
+
move_input = mirror_move(move.uci())
|
|
73
|
+
elo_self = black_elo
|
|
74
|
+
elo_oppo = white_elo
|
|
75
|
+
active_win = - white_win
|
|
76
|
+
|
|
77
|
+
if clock_info > cfg.clock_threshold:
|
|
78
|
+
ret.append((board_input, move_input, elo_self, elo_oppo, active_win))
|
|
79
|
+
|
|
80
|
+
board.push(move)
|
|
81
|
+
if i == cfg.max_ply:
|
|
82
|
+
break
|
|
83
|
+
|
|
84
|
+
return ret
|
|
85
|
+
|
|
86
|
+
|
|
87
|
+
def game_filter(game):
|
|
88
|
+
|
|
89
|
+
white_elo = game.headers.get("WhiteElo", "?")
|
|
90
|
+
black_elo = game.headers.get("BlackElo", "?")
|
|
91
|
+
time_control = game.headers.get("TimeControl", "?")
|
|
92
|
+
result = game.headers.get("Result", "?")
|
|
93
|
+
event = game.headers.get("Event", "?")
|
|
94
|
+
|
|
95
|
+
if white_elo == "?" or black_elo == "?" or time_control == "?" or result == "?" or event == "?":
|
|
96
|
+
return
|
|
97
|
+
|
|
98
|
+
if 'Rated' not in event:
|
|
99
|
+
return
|
|
100
|
+
|
|
101
|
+
if 'Rapid' not in event:
|
|
102
|
+
return
|
|
103
|
+
|
|
104
|
+
for _, node in enumerate(game.mainline()):
|
|
105
|
+
if 'clk' not in node.comment:
|
|
106
|
+
return
|
|
107
|
+
|
|
108
|
+
white_elo = int(white_elo)
|
|
109
|
+
black_elo = int(black_elo)
|
|
110
|
+
|
|
111
|
+
if result == '1-0':
|
|
112
|
+
white_win = 1
|
|
113
|
+
elif result == '0-1':
|
|
114
|
+
white_win = -1
|
|
115
|
+
elif result == '1/2-1/2':
|
|
116
|
+
white_win = 0
|
|
117
|
+
else:
|
|
118
|
+
return
|
|
119
|
+
|
|
120
|
+
return game, white_elo, black_elo, white_win
|
|
121
|
+
|
|
122
|
+
|
|
123
|
+
def process_per_chunk(args):
|
|
124
|
+
|
|
125
|
+
start_pos, end_pos, pgn_path, elo_dict, cfg = args
|
|
126
|
+
|
|
127
|
+
ret = []
|
|
128
|
+
game_count = 0
|
|
129
|
+
|
|
130
|
+
frequency = {}
|
|
131
|
+
|
|
132
|
+
with open(pgn_path, 'r', encoding='utf-8') as pgn_file:
|
|
133
|
+
|
|
134
|
+
pgn_file.seek(start_pos)
|
|
135
|
+
|
|
136
|
+
while pgn_file.tell() < end_pos:
|
|
137
|
+
|
|
138
|
+
game = chess.pgn.read_game(pgn_file)
|
|
139
|
+
|
|
140
|
+
if game is None:
|
|
141
|
+
break
|
|
142
|
+
|
|
143
|
+
filtered_game = game_filter(game)
|
|
144
|
+
if filtered_game:
|
|
145
|
+
game, white_elo, black_elo, white_win = filtered_game
|
|
146
|
+
white_elo = map_to_category(white_elo, elo_dict)
|
|
147
|
+
black_elo = map_to_category(black_elo, elo_dict)
|
|
148
|
+
|
|
149
|
+
if white_elo < black_elo:
|
|
150
|
+
range_1, range_2 = black_elo, white_elo
|
|
151
|
+
else:
|
|
152
|
+
range_1, range_2 = white_elo, black_elo
|
|
153
|
+
|
|
154
|
+
freq = frequency.get((range_1, range_2), 0)
|
|
155
|
+
if freq >= cfg.max_games_per_elo_range:
|
|
156
|
+
continue
|
|
157
|
+
|
|
158
|
+
ret_per_game = process_per_game(game, white_elo, black_elo, white_win, cfg)
|
|
159
|
+
ret.extend(ret_per_game)
|
|
160
|
+
if len(ret_per_game):
|
|
161
|
+
|
|
162
|
+
if (range_1, range_2) in frequency:
|
|
163
|
+
frequency[(range_1, range_2)] += 1
|
|
164
|
+
else:
|
|
165
|
+
frequency[(range_1, range_2)] = 1
|
|
166
|
+
|
|
167
|
+
game_count += 1
|
|
168
|
+
|
|
169
|
+
return ret, game_count, frequency
|
|
170
|
+
|
|
171
|
+
|
|
172
|
+
class MAIA1Dataset(torch.utils.data.Dataset):
|
|
173
|
+
|
|
174
|
+
def __init__(self, data, all_moves_dict, elo_dict, cfg):
|
|
175
|
+
|
|
176
|
+
self.all_moves_dict = all_moves_dict
|
|
177
|
+
self.cfg = cfg
|
|
178
|
+
self.data = data.values.tolist()
|
|
179
|
+
self.elo_dict = elo_dict
|
|
180
|
+
|
|
181
|
+
def __len__(self):
|
|
182
|
+
|
|
183
|
+
return len(self.data)
|
|
184
|
+
|
|
185
|
+
def __getitem__(self, idx):
|
|
186
|
+
|
|
187
|
+
fen, move, elo_self, elo_oppo, white_active = self.data[idx]
|
|
188
|
+
|
|
189
|
+
if white_active:
|
|
190
|
+
board = chess.Board(fen)
|
|
191
|
+
else:
|
|
192
|
+
board = chess.Board(fen).mirror()
|
|
193
|
+
move = mirror_move(move)
|
|
194
|
+
|
|
195
|
+
board_input = board_to_tensor(board)
|
|
196
|
+
move_input = self.all_moves_dict[move]
|
|
197
|
+
|
|
198
|
+
elo_self = map_to_category(elo_self, self.elo_dict)
|
|
199
|
+
elo_oppo = map_to_category(elo_oppo, self.elo_dict)
|
|
200
|
+
|
|
201
|
+
legal_moves, side_info = get_side_info(board, move, self.all_moves_dict)
|
|
202
|
+
|
|
203
|
+
return board_input, move_input, elo_self, elo_oppo, legal_moves, side_info
|
|
204
|
+
|
|
205
|
+
|
|
206
|
+
class MAIA2Dataset(torch.utils.data.Dataset):
|
|
207
|
+
|
|
208
|
+
|
|
209
|
+
def __init__(self, data, all_moves_dict, cfg):
|
|
210
|
+
|
|
211
|
+
self.all_moves_dict = all_moves_dict
|
|
212
|
+
self.data = data
|
|
213
|
+
self.cfg = cfg
|
|
214
|
+
|
|
215
|
+
def __len__(self):
|
|
216
|
+
|
|
217
|
+
return len(self.data)
|
|
218
|
+
|
|
219
|
+
def __getitem__(self, idx):
|
|
220
|
+
|
|
221
|
+
board_input, move_uci, elo_self, elo_oppo, active_win = self.data[idx]
|
|
222
|
+
|
|
223
|
+
board = chess.Board(board_input)
|
|
224
|
+
board_input = board_to_tensor(board)
|
|
225
|
+
|
|
226
|
+
legal_moves, side_info = get_side_info(board, move_uci, self.all_moves_dict)
|
|
227
|
+
|
|
228
|
+
move_input = self.all_moves_dict[move_uci]
|
|
229
|
+
|
|
230
|
+
return board_input, move_input, elo_self, elo_oppo, legal_moves, side_info, active_win
|
|
231
|
+
|
|
232
|
+
|
|
233
|
+
class BasicBlock(torch.nn.Module):
|
|
234
|
+
|
|
235
|
+
def __init__(self, in_planes, planes, stride=1):
|
|
236
|
+
super(BasicBlock, self).__init__()
|
|
237
|
+
|
|
238
|
+
mid_planes = planes
|
|
239
|
+
|
|
240
|
+
self.conv1 = torch.nn.Conv2d(in_planes, mid_planes, kernel_size=3, stride=stride, padding=1, bias=False)
|
|
241
|
+
self.bn1 = torch.nn.BatchNorm2d(mid_planes)
|
|
242
|
+
self.conv2 = torch.nn.Conv2d(mid_planes, planes, kernel_size=3, stride=1, padding=1, bias=False)
|
|
243
|
+
self.bn2 = torch.nn.BatchNorm2d(planes)
|
|
244
|
+
self.dropout = nn.Dropout(p=0.5)
|
|
245
|
+
|
|
246
|
+
def forward(self, x):
|
|
247
|
+
|
|
248
|
+
out = self.conv1(x)
|
|
249
|
+
out = self.bn1(out)
|
|
250
|
+
out = F.relu(out)
|
|
251
|
+
out = self.dropout(out)
|
|
252
|
+
|
|
253
|
+
out = self.conv2(out)
|
|
254
|
+
out = self.bn2(out)
|
|
255
|
+
out += x
|
|
256
|
+
out = F.relu(out)
|
|
257
|
+
|
|
258
|
+
return out
|
|
259
|
+
|
|
260
|
+
|
|
261
|
+
class ChessResNet(torch.nn.Module):
|
|
262
|
+
|
|
263
|
+
def __init__(self, block, cfg):
|
|
264
|
+
super(ChessResNet, self).__init__()
|
|
265
|
+
|
|
266
|
+
self.conv1 = torch.nn.Conv2d(cfg.input_channels, cfg.dim_cnn, kernel_size=3, stride=1, padding=1, bias=False)
|
|
267
|
+
self.bn1 = torch.nn.BatchNorm2d(cfg.dim_cnn)
|
|
268
|
+
self.layers = self._make_layer(block, cfg.dim_cnn, cfg.num_blocks_cnn)
|
|
269
|
+
self.conv_last = torch.nn.Conv2d(cfg.dim_cnn, cfg.vit_length, kernel_size=3, stride=1, padding=1, bias=False)
|
|
270
|
+
self.bn_last = torch.nn.BatchNorm2d(cfg.vit_length)
|
|
271
|
+
|
|
272
|
+
def _make_layer(self, block, planes, num_blocks, stride=1):
|
|
273
|
+
|
|
274
|
+
layers = []
|
|
275
|
+
for _ in range(num_blocks):
|
|
276
|
+
layers.append(block(planes, planes, stride))
|
|
277
|
+
|
|
278
|
+
return torch.nn.Sequential(*layers)
|
|
279
|
+
|
|
280
|
+
def forward(self, x):
|
|
281
|
+
|
|
282
|
+
out = F.relu(self.bn1(self.conv1(x)))
|
|
283
|
+
out = self.layers(out)
|
|
284
|
+
out = self.conv_last(out)
|
|
285
|
+
out = self.bn_last(out)
|
|
286
|
+
|
|
287
|
+
return out
|
|
288
|
+
|
|
289
|
+
|
|
290
|
+
class FeedForward(nn.Module):
|
|
291
|
+
def __init__(self, dim, hidden_dim, dropout = 0.):
|
|
292
|
+
super().__init__()
|
|
293
|
+
self.net = nn.Sequential(
|
|
294
|
+
nn.LayerNorm(dim),
|
|
295
|
+
nn.Linear(dim, hidden_dim),
|
|
296
|
+
nn.GELU(),
|
|
297
|
+
nn.Dropout(dropout),
|
|
298
|
+
nn.Linear(hidden_dim, dim),
|
|
299
|
+
nn.Dropout(dropout)
|
|
300
|
+
)
|
|
301
|
+
|
|
302
|
+
def forward(self, x):
|
|
303
|
+
return self.net(x)
|
|
304
|
+
|
|
305
|
+
|
|
306
|
+
class Attention(nn.Module):
|
|
307
|
+
def __init__(self, dim, heads = 8, dim_head = 64, dropout = 0.):
|
|
308
|
+
super().__init__()
|
|
309
|
+
inner_dim = dim_head * heads
|
|
310
|
+
project_out = not (heads == 1 and dim_head == dim)
|
|
311
|
+
|
|
312
|
+
self.heads = heads
|
|
313
|
+
self.scale = dim_head ** -0.5
|
|
314
|
+
|
|
315
|
+
self.norm = nn.LayerNorm(dim)
|
|
316
|
+
|
|
317
|
+
self.attend = nn.Softmax(dim = -1)
|
|
318
|
+
self.dropout = nn.Dropout(dropout)
|
|
319
|
+
|
|
320
|
+
self.to_qkv = nn.Linear(dim, inner_dim * 3, bias = False)
|
|
321
|
+
|
|
322
|
+
self.to_out = nn.Sequential(
|
|
323
|
+
nn.Linear(inner_dim, dim),
|
|
324
|
+
nn.Dropout(dropout)
|
|
325
|
+
) if project_out else nn.Identity()
|
|
326
|
+
|
|
327
|
+
def forward(self, x):
|
|
328
|
+
x = self.norm(x)
|
|
329
|
+
|
|
330
|
+
qkv = self.to_qkv(x).chunk(3, dim = -1)
|
|
331
|
+
q, k, v = map(lambda t: rearrange(t, 'b n (h d) -> b h n d', h = self.heads), qkv)
|
|
332
|
+
|
|
333
|
+
dots = torch.matmul(q, k.transpose(-1, -2)) * self.scale
|
|
334
|
+
|
|
335
|
+
attn = self.attend(dots)
|
|
336
|
+
attn = self.dropout(attn)
|
|
337
|
+
|
|
338
|
+
out = torch.matmul(attn, v)
|
|
339
|
+
out = rearrange(out, 'b h n d -> b n (h d)')
|
|
340
|
+
return self.to_out(out)
|
|
341
|
+
|
|
342
|
+
|
|
343
|
+
class EloAwareAttention(nn.Module):
|
|
344
|
+
def __init__(self, dim, heads=8, dim_head=64, dropout=0., elo_dim=64):
|
|
345
|
+
super().__init__()
|
|
346
|
+
inner_dim = dim_head * heads
|
|
347
|
+
project_out = not (heads == 1 and dim_head == dim)
|
|
348
|
+
|
|
349
|
+
self.heads = heads
|
|
350
|
+
self.scale = dim_head ** -0.5
|
|
351
|
+
|
|
352
|
+
self.norm = nn.LayerNorm(dim)
|
|
353
|
+
|
|
354
|
+
self.attend = nn.Softmax(dim=-1)
|
|
355
|
+
self.dropout = nn.Dropout(dropout)
|
|
356
|
+
|
|
357
|
+
self.to_qkv = nn.Linear(dim, inner_dim * 3, bias=False)
|
|
358
|
+
|
|
359
|
+
self.elo_query = nn.Linear(elo_dim, inner_dim, bias=False)
|
|
360
|
+
|
|
361
|
+
self.to_out = nn.Sequential(
|
|
362
|
+
nn.Linear(inner_dim, dim),
|
|
363
|
+
nn.Dropout(dropout)
|
|
364
|
+
) if project_out else nn.Identity()
|
|
365
|
+
|
|
366
|
+
def forward(self, x, elo_emb):
|
|
367
|
+
x = self.norm(x)
|
|
368
|
+
|
|
369
|
+
qkv = self.to_qkv(x).chunk(3, dim=-1)
|
|
370
|
+
q, k, v = map(lambda t: rearrange(t, 'b n (h d) -> b h n d', h=self.heads), qkv)
|
|
371
|
+
|
|
372
|
+
elo_effect = self.elo_query(elo_emb).view(x.size(0), self.heads, 1, -1)
|
|
373
|
+
q = q + elo_effect
|
|
374
|
+
|
|
375
|
+
dots = torch.matmul(q, k.transpose(-1, -2)) * self.scale
|
|
376
|
+
|
|
377
|
+
attn = self.attend(dots)
|
|
378
|
+
attn = self.dropout(attn)
|
|
379
|
+
|
|
380
|
+
out = torch.matmul(attn, v)
|
|
381
|
+
out = rearrange(out, 'b h n d -> b n (h d)')
|
|
382
|
+
return self.to_out(out)
|
|
383
|
+
|
|
384
|
+
|
|
385
|
+
class Transformer(nn.Module):
|
|
386
|
+
def __init__(self, dim, depth, heads, dim_head, mlp_dim, dropout = 0., elo_dim=64):
|
|
387
|
+
super().__init__()
|
|
388
|
+
self.norm = nn.LayerNorm(dim)
|
|
389
|
+
self.layers = nn.ModuleList([])
|
|
390
|
+
self.elo_layers = nn.ModuleList([])
|
|
391
|
+
for _ in range(depth):
|
|
392
|
+
self.elo_layers.append(nn.ModuleList([
|
|
393
|
+
EloAwareAttention(dim, heads = heads, dim_head = dim_head, dropout = dropout, elo_dim = elo_dim),
|
|
394
|
+
FeedForward(dim, mlp_dim, dropout = dropout)
|
|
395
|
+
]))
|
|
396
|
+
|
|
397
|
+
def forward(self, x, elo_emb):
|
|
398
|
+
for attn, ff in self.elo_layers:
|
|
399
|
+
x = attn(x, elo_emb) + x
|
|
400
|
+
x = ff(x) + x
|
|
401
|
+
|
|
402
|
+
return self.norm(x)
|
|
403
|
+
|
|
404
|
+
|
|
405
|
+
class MAIA2Model(torch.nn.Module):
|
|
406
|
+
|
|
407
|
+
def __init__(self, output_dim, elo_dict, cfg):
|
|
408
|
+
super(MAIA2Model, self).__init__()
|
|
409
|
+
|
|
410
|
+
self.cfg = cfg
|
|
411
|
+
self.chess_cnn = ChessResNet(BasicBlock, cfg)
|
|
412
|
+
|
|
413
|
+
heads = 16
|
|
414
|
+
dim_head = 64
|
|
415
|
+
self.to_patch_embedding = nn.Sequential(
|
|
416
|
+
nn.Linear(8 * 8, cfg.dim_vit),
|
|
417
|
+
nn.LayerNorm(cfg.dim_vit),
|
|
418
|
+
)
|
|
419
|
+
self.transformer = Transformer(cfg.dim_vit, cfg.num_blocks_vit, heads, dim_head, mlp_dim=cfg.dim_vit, dropout = 0.1, elo_dim = cfg.elo_dim * 2)
|
|
420
|
+
self.pos_embedding = nn.Parameter(torch.randn(1, cfg.vit_length, cfg.dim_vit))
|
|
421
|
+
|
|
422
|
+
self.fc_1 = nn.Linear(cfg.dim_vit, output_dim)
|
|
423
|
+
# self.fc_1_1 = nn.Linear(cfg.dim_vit, cfg.dim_vit)
|
|
424
|
+
self.fc_2 = nn.Linear(cfg.dim_vit, output_dim + 6 + 6 + 1 + 64 + 64)
|
|
425
|
+
# self.fc_2_1 = nn.Linear(cfg.dim_vit, cfg.dim_vit)
|
|
426
|
+
self.fc_3 = nn.Linear(128, 1)
|
|
427
|
+
self.fc_3_1 = nn.Linear(cfg.dim_vit, 128)
|
|
428
|
+
|
|
429
|
+
self.elo_embedding = torch.nn.Embedding(len(elo_dict), cfg.elo_dim)
|
|
430
|
+
|
|
431
|
+
self.dropout = nn.Dropout(p=0.1)
|
|
432
|
+
self.last_ln = nn.LayerNorm(cfg.dim_vit)
|
|
433
|
+
|
|
434
|
+
|
|
435
|
+
def forward(self, boards, elos_self, elos_oppo):
|
|
436
|
+
|
|
437
|
+
batch_size = boards.size(0)
|
|
438
|
+
boards = boards.view(batch_size, self.cfg.input_channels, 8, 8)
|
|
439
|
+
embs = self.chess_cnn(boards)
|
|
440
|
+
embs = embs.view(batch_size, embs.size(1), 8 * 8)
|
|
441
|
+
x = self.to_patch_embedding(embs)
|
|
442
|
+
x += self.pos_embedding
|
|
443
|
+
x = self.dropout(x)
|
|
444
|
+
|
|
445
|
+
elos_emb_self = self.elo_embedding(elos_self)
|
|
446
|
+
elos_emb_oppo = self.elo_embedding(elos_oppo)
|
|
447
|
+
elos_emb = torch.cat((elos_emb_self, elos_emb_oppo), dim=1)
|
|
448
|
+
x = self.transformer(x, elos_emb).mean(dim=1)
|
|
449
|
+
|
|
450
|
+
x = self.last_ln(x)
|
|
451
|
+
|
|
452
|
+
logits_maia = self.fc_1(x)
|
|
453
|
+
logits_side_info = self.fc_2(x)
|
|
454
|
+
logits_value = self.fc_3(self.dropout(torch.relu(self.fc_3_1(x)))).squeeze(dim=-1)
|
|
455
|
+
|
|
456
|
+
return logits_maia, logits_side_info, logits_value
|
|
457
|
+
|
|
458
|
+
|
|
459
|
+
def read_monthly_data_path(cfg):
|
|
460
|
+
|
|
461
|
+
print('Training Data:', flush=True)
|
|
462
|
+
pgn_paths = []
|
|
463
|
+
|
|
464
|
+
for year in range(cfg.start_year, cfg.end_year + 1):
|
|
465
|
+
start_month = cfg.start_month if year == cfg.start_year else 1
|
|
466
|
+
end_month = cfg.end_month if year == cfg.end_year else 12
|
|
467
|
+
|
|
468
|
+
for month in range(start_month, end_month + 1):
|
|
469
|
+
formatted_month = f"{month:02d}"
|
|
470
|
+
pgn_path = cfg.data_root + f"/lichess_db_standard_rated_{year}-{formatted_month}.pgn"
|
|
471
|
+
# skip 2019-12
|
|
472
|
+
if year == 2019 and month == 12:
|
|
473
|
+
continue
|
|
474
|
+
print(pgn_path, flush=True)
|
|
475
|
+
pgn_paths.append(pgn_path)
|
|
476
|
+
|
|
477
|
+
return pgn_paths
|
|
478
|
+
|
|
479
|
+
|
|
480
|
+
def evaluate(model, dataloader):
|
|
481
|
+
|
|
482
|
+
counter = 0
|
|
483
|
+
correct_move = 0
|
|
484
|
+
|
|
485
|
+
model.eval()
|
|
486
|
+
with torch.no_grad():
|
|
487
|
+
|
|
488
|
+
for boards, labels, elos_self, elos_oppo, legal_moves, side_info in dataloader:
|
|
489
|
+
|
|
490
|
+
boards = boards.cuda()
|
|
491
|
+
labels = labels.cuda()
|
|
492
|
+
elos_self = elos_self.cuda()
|
|
493
|
+
elos_oppo = elos_oppo.cuda()
|
|
494
|
+
legal_moves = legal_moves.cuda()
|
|
495
|
+
|
|
496
|
+
logits_maia, logits_side_info, logits_value = model(boards, elos_self, elos_oppo)
|
|
497
|
+
logits_maia_legal = logits_maia * legal_moves
|
|
498
|
+
preds = logits_maia_legal.argmax(dim=-1)
|
|
499
|
+
correct_move += (preds == labels).sum().item()
|
|
500
|
+
|
|
501
|
+
counter += len(labels)
|
|
502
|
+
|
|
503
|
+
return correct_move, counter
|
|
504
|
+
|
|
505
|
+
|
|
506
|
+
def evaluate_MAIA1_data(model, all_moves_dict, elo_dict, cfg, tiny=False):
|
|
507
|
+
|
|
508
|
+
elo_list = range(1000, 2600, 100)
|
|
509
|
+
|
|
510
|
+
for i in elo_list:
|
|
511
|
+
start = i
|
|
512
|
+
end = i + 100
|
|
513
|
+
file_path = f"../data/test/KDDTest_{start}-{end}.csv"
|
|
514
|
+
data = pd.read_csv(file_path)
|
|
515
|
+
data = data[data.type == 'Rapid'][['board', 'move', 'active_elo', 'opponent_elo', 'white_active']]
|
|
516
|
+
dataset = MAIA1Dataset(data, all_moves_dict, elo_dict, cfg)
|
|
517
|
+
dataloader = torch.utils.data.DataLoader(dataset,
|
|
518
|
+
batch_size=cfg.batch_size,
|
|
519
|
+
shuffle=False,
|
|
520
|
+
drop_last=False,
|
|
521
|
+
num_workers=cfg.num_workers)
|
|
522
|
+
if cfg.verbose:
|
|
523
|
+
dataloader = tqdm.tqdm(dataloader)
|
|
524
|
+
print(f'Testing Elo Range {start}-{end} with MAIA 1 data:', flush=True)
|
|
525
|
+
correct_move, counter = evaluate(model, dataloader)
|
|
526
|
+
print(f'Accuracy Move Prediction: {round(correct_move / counter, 4)}', flush=True)
|
|
527
|
+
if tiny:
|
|
528
|
+
break
|
|
529
|
+
|
|
530
|
+
|
|
531
|
+
def train_chunks(cfg, data, model, optimizer, all_moves_dict, criterion_maia, criterion_side_info, criterion_value):
|
|
532
|
+
|
|
533
|
+
dataset_train = MAIA2Dataset(data, all_moves_dict, cfg)
|
|
534
|
+
dataloader_train = torch.utils.data.DataLoader(dataset_train,
|
|
535
|
+
batch_size=cfg.batch_size,
|
|
536
|
+
shuffle=True,
|
|
537
|
+
drop_last=False,
|
|
538
|
+
num_workers=cfg.num_workers)
|
|
539
|
+
if cfg.verbose:
|
|
540
|
+
dataloader_train = tqdm.tqdm(dataloader_train)
|
|
541
|
+
|
|
542
|
+
avg_loss = 0
|
|
543
|
+
avg_loss_maia = 0
|
|
544
|
+
avg_loss_side_info = 0
|
|
545
|
+
avg_loss_value = 0
|
|
546
|
+
step = 0
|
|
547
|
+
for boards, labels, elos_self, elos_oppo, legal_moves, side_info, wdl in dataloader_train:
|
|
548
|
+
|
|
549
|
+
model.train()
|
|
550
|
+
boards = boards.cuda()
|
|
551
|
+
labels = labels.cuda()
|
|
552
|
+
elos_self = elos_self.cuda()
|
|
553
|
+
elos_oppo = elos_oppo.cuda()
|
|
554
|
+
side_info = side_info.cuda()
|
|
555
|
+
wdl = wdl.float().cuda()
|
|
556
|
+
|
|
557
|
+
logits_maia, logits_side_info, logits_value = model(boards, elos_self, elos_oppo)
|
|
558
|
+
|
|
559
|
+
loss = 0
|
|
560
|
+
loss_maia = criterion_maia(logits_maia, labels)
|
|
561
|
+
loss += loss_maia
|
|
562
|
+
|
|
563
|
+
if cfg.side_info:
|
|
564
|
+
|
|
565
|
+
loss_side_info = criterion_side_info(logits_side_info, side_info) * cfg.side_info_coefficient
|
|
566
|
+
loss += loss_side_info
|
|
567
|
+
|
|
568
|
+
if cfg.value:
|
|
569
|
+
loss_value = criterion_value(logits_value, wdl) * cfg.value_coefficient
|
|
570
|
+
loss += loss_value
|
|
571
|
+
|
|
572
|
+
optimizer.zero_grad()
|
|
573
|
+
loss.backward()
|
|
574
|
+
optimizer.step()
|
|
575
|
+
|
|
576
|
+
avg_loss += loss.item()
|
|
577
|
+
avg_loss_maia += loss_maia.item()
|
|
578
|
+
if cfg.side_info:
|
|
579
|
+
avg_loss_side_info += loss_side_info.item()
|
|
580
|
+
if cfg.value:
|
|
581
|
+
avg_loss_value += loss_value.item()
|
|
582
|
+
step += 1
|
|
583
|
+
|
|
584
|
+
return round(avg_loss / step, 3), round(avg_loss_maia / step, 3), round(avg_loss_side_info / step, 3), round(avg_loss_value / step, 3)
|
|
585
|
+
|
|
586
|
+
|
|
587
|
+
def preprocess_thread(queue, cfg, pgn_path, pgn_chunks_sublist, elo_dict):
|
|
588
|
+
|
|
589
|
+
data, game_count, chunk_count = process_chunks(cfg, pgn_path, pgn_chunks_sublist, elo_dict)
|
|
590
|
+
queue.put([data, game_count, chunk_count])
|
|
591
|
+
del data
|
|
592
|
+
|
|
593
|
+
|
|
594
|
+
def worker_wrapper(semaphore, *args, **kwargs):
|
|
595
|
+
with semaphore:
|
|
596
|
+
preprocess_thread(*args, **kwargs)
|
|
597
|
+
|
|
598
|
+
|
|
599
|
+
def parse_args(args=None):
|
|
600
|
+
|
|
601
|
+
parser = argparse.ArgumentParser()
|
|
602
|
+
|
|
603
|
+
# Supporting Arguments
|
|
604
|
+
parser.add_argument('--data_root', default='your_data_root', type=str)
|
|
605
|
+
parser.add_argument('--seed', default=42, type=int)
|
|
606
|
+
parser.add_argument('--num_workers', default=16, type=int)
|
|
607
|
+
parser.add_argument('--verbose', default=1, type=int)
|
|
608
|
+
parser.add_argument('--max_epochs', default=3, type=int)
|
|
609
|
+
parser.add_argument('--max_ply', default=300, type=int)
|
|
610
|
+
parser.add_argument('--clock_threshold', default=30, type=int)
|
|
611
|
+
parser.add_argument('--chunk_size', default=20000, type=str)
|
|
612
|
+
parser.add_argument('--start_year', default=2018, type=int)
|
|
613
|
+
parser.add_argument('--start_month', default=5, type=int)
|
|
614
|
+
parser.add_argument('--end_year', default=2023, type=int)
|
|
615
|
+
parser.add_argument('--end_month', default=11, type=int)
|
|
616
|
+
parser.add_argument('--from_checkpoint', default=False, type=bool)
|
|
617
|
+
parser.add_argument('--checkpoint_epoch', default=2, type=int)
|
|
618
|
+
parser.add_argument('--checkpoint_year', default=2018, type=int)
|
|
619
|
+
parser.add_argument('--checkpoint_month', default=6, type=int)
|
|
620
|
+
parser.add_argument('--num_cpu_left', default=16, type=int)
|
|
621
|
+
parser.add_argument('--queue_length', default=2, type=int)
|
|
622
|
+
parser.add_argument('--max_games_per_elo_range', default=20, type=int)
|
|
623
|
+
|
|
624
|
+
# Tunable Arguments
|
|
625
|
+
parser.add_argument('--lr', default=1e-4, type=float)
|
|
626
|
+
parser.add_argument('--wd', default=1e-5, type=float)
|
|
627
|
+
parser.add_argument('--batch_size', default=8192, type=int)
|
|
628
|
+
parser.add_argument('--first_n_moves', default=10, type=int)
|
|
629
|
+
parser.add_argument('--last_n_moves', default=10, type=int)
|
|
630
|
+
parser.add_argument('--dim_cnn', default=256, type=int)
|
|
631
|
+
parser.add_argument('--dim_vit', default=1024, type=int)
|
|
632
|
+
parser.add_argument('--num_blocks_cnn', default=5, type=int)
|
|
633
|
+
parser.add_argument('--num_blocks_vit', default=2, type=int)
|
|
634
|
+
parser.add_argument('--input_channels', default=18, type=int)
|
|
635
|
+
parser.add_argument('--vit_length', default=8, type=int)
|
|
636
|
+
parser.add_argument('--elo_dim', default=128, type=int)
|
|
637
|
+
parser.add_argument('--side_info', default=True, type=bool)
|
|
638
|
+
parser.add_argument('--side_info_coefficient', default=1, type=float)
|
|
639
|
+
parser.add_argument('--value', default=True, type=bool)
|
|
640
|
+
parser.add_argument('--value_coefficient', default=1, type=float)
|
|
641
|
+
|
|
642
|
+
return parser.parse_args(args)
|
|
643
|
+
|
|
644
|
+
|
|
645
|
+
if __name__ == '__main__':
|
|
646
|
+
|
|
647
|
+
cfg = parse_args()
|
|
648
|
+
print('Configurations:', flush=True)
|
|
649
|
+
for arg in vars(cfg):
|
|
650
|
+
print(f'\t{arg}: {getattr(cfg, arg)}', flush=True)
|
|
651
|
+
seed_everything(cfg.seed)
|
|
652
|
+
num_processes = cpu_count() - cfg.num_cpu_left
|
|
653
|
+
|
|
654
|
+
save_root = f'../tmp/{cfg.lr}_{cfg.batch_size}_{cfg.wd}_MAIA2/'
|
|
655
|
+
if not os.path.exists(save_root):
|
|
656
|
+
os.makedirs(save_root)
|
|
657
|
+
|
|
658
|
+
all_moves = get_all_possible_moves()
|
|
659
|
+
all_moves_dict = {move: i for i, move in enumerate(all_moves)}
|
|
660
|
+
elo_dict = create_elo_dict()
|
|
661
|
+
|
|
662
|
+
model = MAIA2Model(len(all_moves), elo_dict, cfg)
|
|
663
|
+
|
|
664
|
+
print(model, flush=True)
|
|
665
|
+
model = model.cuda()
|
|
666
|
+
model = nn.DataParallel(model)
|
|
667
|
+
criterion_maia = nn.CrossEntropyLoss()
|
|
668
|
+
criterion_side_info = nn.BCEWithLogitsLoss()
|
|
669
|
+
criterion_value = nn.MSELoss()
|
|
670
|
+
|
|
671
|
+
optimizer = torch.optim.AdamW(model.parameters(), lr=cfg.lr, weight_decay=cfg.wd)
|
|
672
|
+
N_params = count_parameters(model)
|
|
673
|
+
print(f'Trainable Parameters: {N_params}', flush=True)
|
|
674
|
+
|
|
675
|
+
accumulated_samples = 0
|
|
676
|
+
accumulated_games = 0
|
|
677
|
+
|
|
678
|
+
if cfg.from_checkpoint:
|
|
679
|
+
formatted_month = f"{cfg.checkpoint_month:02d}"
|
|
680
|
+
checkpoint = torch.load(save_root + f'epoch_{cfg.checkpoint_epoch}_{cfg.checkpoint_year}-{formatted_month}.pgn.pt')
|
|
681
|
+
model.load_state_dict(checkpoint['model_state_dict'])
|
|
682
|
+
optimizer.load_state_dict(checkpoint['optimizer_state_dict'])
|
|
683
|
+
accumulated_samples = checkpoint['accumulated_samples']
|
|
684
|
+
accumulated_games = checkpoint['accumulated_games']
|
|
685
|
+
|
|
686
|
+
for epoch in range(cfg.max_epochs):
|
|
687
|
+
|
|
688
|
+
print(f'Epoch {epoch + 1}', flush=True)
|
|
689
|
+
pgn_paths = read_monthly_data_path(cfg)
|
|
690
|
+
|
|
691
|
+
num_file = 0
|
|
692
|
+
for pgn_path in pgn_paths:
|
|
693
|
+
|
|
694
|
+
start_time = time.time()
|
|
695
|
+
decompress_zst(pgn_path + '.zst', pgn_path)
|
|
696
|
+
print(f'Decompressing {pgn_path} took {readable_time(time.time() - start_time)}', flush=True)
|
|
697
|
+
|
|
698
|
+
pgn_chunks = read_or_create_chunks(pgn_path, cfg)
|
|
699
|
+
print(f'Training {pgn_path} with {len(pgn_chunks)} chunks.', flush=True)
|
|
700
|
+
|
|
701
|
+
queue = Queue(maxsize=cfg.queue_length)
|
|
702
|
+
|
|
703
|
+
pgn_chunks_sublists = []
|
|
704
|
+
for i in range(0, len(pgn_chunks), num_processes):
|
|
705
|
+
pgn_chunks_sublists.append(pgn_chunks[i:i + num_processes])
|
|
706
|
+
|
|
707
|
+
pgn_chunks_sublist = pgn_chunks_sublists[0]
|
|
708
|
+
# For debugging only
|
|
709
|
+
# process_chunks(cfg, pgn_path, pgn_chunks_sublist, elo_dict)
|
|
710
|
+
worker = Process(target=preprocess_thread, args=(queue, cfg, pgn_path, pgn_chunks_sublist, elo_dict))
|
|
711
|
+
worker.start()
|
|
712
|
+
|
|
713
|
+
num_chunk = 0
|
|
714
|
+
offset = 0
|
|
715
|
+
while True:
|
|
716
|
+
if not queue.empty():
|
|
717
|
+
if offset + 1 < len(pgn_chunks_sublists):
|
|
718
|
+
pgn_chunks_sublist = pgn_chunks_sublists[offset + 1]
|
|
719
|
+
worker = Process(target=preprocess_thread, args=(queue, cfg, pgn_path, pgn_chunks_sublist, elo_dict))
|
|
720
|
+
worker.start()
|
|
721
|
+
offset += 1
|
|
722
|
+
data, game_count, chunk_count = queue.get()
|
|
723
|
+
loss, loss_maia, loss_side_info, loss_value = train_chunks(cfg, data, model, optimizer, all_moves_dict, criterion_maia, criterion_side_info, criterion_value)
|
|
724
|
+
num_chunk += chunk_count
|
|
725
|
+
accumulated_samples += len(data)
|
|
726
|
+
accumulated_games += game_count
|
|
727
|
+
print(f'[{num_chunk}/{len(pgn_chunks)}]', flush=True)
|
|
728
|
+
print(f'[# Positions]: {readable_num(accumulated_samples)}', flush=True)
|
|
729
|
+
print(f'[# Games]: {readable_num(accumulated_games)}', flush=True)
|
|
730
|
+
print(f'[# Loss]: {loss} | [# Loss MAIA]: {loss_maia} | [# Loss Side Info]: {loss_side_info} | [# Loss Value]: {loss_value}', flush=True)
|
|
731
|
+
if num_chunk == len(pgn_chunks):
|
|
732
|
+
break
|
|
733
|
+
|
|
734
|
+
num_file += 1
|
|
735
|
+
print(f'### ({num_file} / {len(pgn_paths)}) Took {readable_time(time.time() - start_time)} to train {pgn_path} with {len(pgn_chunks)} chunks.', flush=True)
|
|
736
|
+
os.remove(pgn_path)
|
|
737
|
+
|
|
738
|
+
# evaluate_MAIA1_data(model, all_moves_dict, elo_dict, cfg, tiny=False)
|
|
739
|
+
torch.save({'model_state_dict': model.state_dict(),
|
|
740
|
+
'optimizer_state_dict': optimizer.state_dict(),
|
|
741
|
+
'accumulated_samples': accumulated_samples,
|
|
742
|
+
'accumulated_games': accumulated_games}, f'{save_root}epoch_{epoch + 1}_{pgn_path[-11:]}.pt')
|
maia2/utils.py
ADDED
|
@@ -0,0 +1,347 @@
|
|
|
1
|
+
import pdb
|
|
2
|
+
import chess
|
|
3
|
+
import pickle
|
|
4
|
+
import os
|
|
5
|
+
import random
|
|
6
|
+
import numpy as np
|
|
7
|
+
import torch
|
|
8
|
+
import time
|
|
9
|
+
import requests
|
|
10
|
+
import tqdm
|
|
11
|
+
import pyzstd
|
|
12
|
+
import re
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
def seed_everything(seed: int):
|
|
16
|
+
|
|
17
|
+
random.seed(seed)
|
|
18
|
+
os.environ['PYTHONHASHSEED'] = str(seed)
|
|
19
|
+
np.random.seed(seed)
|
|
20
|
+
torch.manual_seed(seed)
|
|
21
|
+
torch.cuda.manual_seed(seed)
|
|
22
|
+
torch.cuda.manual_seed_all(seed)
|
|
23
|
+
torch.backends.cudnn.deterministic = True
|
|
24
|
+
torch.backends.cudnn.benchmark = False
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
def delete_file(filename):
|
|
28
|
+
|
|
29
|
+
if os.path.exists(filename):
|
|
30
|
+
os.remove(filename)
|
|
31
|
+
print(f"Data {filename} has been deleted.")
|
|
32
|
+
else:
|
|
33
|
+
print(f"The file '{filename}' does not exist.")
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
def readable_num(num):
|
|
37
|
+
|
|
38
|
+
if num >= 1e9: # if parameters are in the billions
|
|
39
|
+
return f'{num / 1e9:.2f}B'
|
|
40
|
+
elif num >= 1e6: # if parameters are in the millions
|
|
41
|
+
return f'{num / 1e6:.2f}M'
|
|
42
|
+
elif num >= 1e3: # if parameters are in the thousands
|
|
43
|
+
return f'{num / 1e3:.2f}K'
|
|
44
|
+
else:
|
|
45
|
+
return str(num)
|
|
46
|
+
|
|
47
|
+
|
|
48
|
+
def readable_time(elapsed_time):
|
|
49
|
+
|
|
50
|
+
hours, rem = divmod(elapsed_time, 3600)
|
|
51
|
+
minutes, seconds = divmod(rem, 60)
|
|
52
|
+
|
|
53
|
+
if hours > 0:
|
|
54
|
+
return f"{int(hours)}h {int(minutes)}m {seconds:.2f}s"
|
|
55
|
+
elif minutes > 0:
|
|
56
|
+
return f"{int(minutes)}m {seconds:.2f}s"
|
|
57
|
+
else:
|
|
58
|
+
return f"{seconds:.2f}s"
|
|
59
|
+
|
|
60
|
+
|
|
61
|
+
def count_parameters(model):
|
|
62
|
+
|
|
63
|
+
total_params = sum(p.numel() for p in model.parameters() if p.requires_grad)
|
|
64
|
+
|
|
65
|
+
return readable_num(total_params)
|
|
66
|
+
|
|
67
|
+
|
|
68
|
+
def create_elo_dict():
|
|
69
|
+
|
|
70
|
+
inteval = 100
|
|
71
|
+
start = 1100
|
|
72
|
+
end = 2000
|
|
73
|
+
|
|
74
|
+
range_dict = {f"<{start}": 0}
|
|
75
|
+
range_index = 1
|
|
76
|
+
|
|
77
|
+
for lower_bound in range(start, end - 1, inteval):
|
|
78
|
+
upper_bound = lower_bound + inteval
|
|
79
|
+
range_dict[f"{lower_bound}-{upper_bound - 1}"] = range_index
|
|
80
|
+
range_index += 1
|
|
81
|
+
|
|
82
|
+
range_dict[f">={end}"] = range_index
|
|
83
|
+
|
|
84
|
+
# print(range_dict, flush=True)
|
|
85
|
+
|
|
86
|
+
return range_dict
|
|
87
|
+
|
|
88
|
+
|
|
89
|
+
def map_to_category(elo, elo_dict):
|
|
90
|
+
|
|
91
|
+
inteval = 100
|
|
92
|
+
start = 1100
|
|
93
|
+
end = 2000
|
|
94
|
+
|
|
95
|
+
if elo < start:
|
|
96
|
+
return elo_dict[f"<{start}"]
|
|
97
|
+
elif elo >= end:
|
|
98
|
+
return elo_dict[f">={end}"]
|
|
99
|
+
else:
|
|
100
|
+
for lower_bound in range(start, end - 1, inteval):
|
|
101
|
+
upper_bound = lower_bound + inteval
|
|
102
|
+
if lower_bound <= elo < upper_bound:
|
|
103
|
+
return elo_dict[f"{lower_bound}-{upper_bound - 1}"]
|
|
104
|
+
|
|
105
|
+
|
|
106
|
+
def get_side_info(board, move_uci, all_moves_dict):
|
|
107
|
+
move = chess.Move.from_uci(move_uci)
|
|
108
|
+
|
|
109
|
+
moving_piece = board.piece_at(move.from_square)
|
|
110
|
+
captured_piece = board.piece_at(move.to_square)
|
|
111
|
+
|
|
112
|
+
from_square_encoded = torch.zeros(64)
|
|
113
|
+
from_square_encoded[move.from_square] = 1
|
|
114
|
+
|
|
115
|
+
to_square_encoded = torch.zeros(64)
|
|
116
|
+
to_square_encoded[move.to_square] = 1
|
|
117
|
+
|
|
118
|
+
if move_uci == 'e1g1':
|
|
119
|
+
rook_move = chess.Move.from_uci('h1f1')
|
|
120
|
+
from_square_encoded[rook_move.from_square] = 1
|
|
121
|
+
to_square_encoded[rook_move.to_square] = 1
|
|
122
|
+
|
|
123
|
+
if move_uci == 'e1c1':
|
|
124
|
+
rook_move = chess.Move.from_uci('a1d1')
|
|
125
|
+
from_square_encoded[rook_move.from_square] = 1
|
|
126
|
+
to_square_encoded[rook_move.to_square] = 1
|
|
127
|
+
|
|
128
|
+
board.push(move)
|
|
129
|
+
is_check = board.is_check()
|
|
130
|
+
board.pop()
|
|
131
|
+
|
|
132
|
+
# Order: Pawn, Knight, Bishop, Rook, Queen, King
|
|
133
|
+
side_info = torch.zeros(6 + 6 + 1)
|
|
134
|
+
side_info[moving_piece.piece_type - 1] = 1
|
|
135
|
+
if move_uci in ['e1g1', 'e1c1']:
|
|
136
|
+
side_info[3] = 1
|
|
137
|
+
if captured_piece:
|
|
138
|
+
side_info[6 + captured_piece.piece_type - 1] = 1
|
|
139
|
+
if is_check:
|
|
140
|
+
side_info[-1] = 1
|
|
141
|
+
|
|
142
|
+
legal_moves = torch.zeros(len(all_moves_dict))
|
|
143
|
+
legal_moves_idx = torch.tensor([all_moves_dict[move.uci()] for move in board.legal_moves])
|
|
144
|
+
legal_moves[legal_moves_idx] = 1
|
|
145
|
+
|
|
146
|
+
side_info = torch.cat([side_info, from_square_encoded, to_square_encoded, legal_moves], dim=0)
|
|
147
|
+
|
|
148
|
+
return legal_moves, side_info
|
|
149
|
+
|
|
150
|
+
|
|
151
|
+
def extract_clock_time(comment):
|
|
152
|
+
|
|
153
|
+
match = re.search(r'\[%clk (\d+):(\d+):(\d+)\]', comment)
|
|
154
|
+
if match:
|
|
155
|
+
hours, minutes, seconds = map(int, match.groups())
|
|
156
|
+
return hours * 3600 + minutes * 60 + seconds
|
|
157
|
+
return None
|
|
158
|
+
|
|
159
|
+
|
|
160
|
+
def read_or_create_chunks(pgn_path, cfg):
|
|
161
|
+
|
|
162
|
+
cache_file = pgn_path.replace('.pgn', '_chunks.pkl')
|
|
163
|
+
|
|
164
|
+
if os.path.exists(cache_file):
|
|
165
|
+
print(f"Loading cached chunks from {cache_file}")
|
|
166
|
+
with open(cache_file, 'rb') as f:
|
|
167
|
+
pgn_chunks = pickle.load(f)
|
|
168
|
+
else:
|
|
169
|
+
print(f"Cache not found. Creating chunks for {pgn_path}")
|
|
170
|
+
start_time = time.time()
|
|
171
|
+
pgn_chunks = get_chunks(pgn_path, cfg.chunk_size)
|
|
172
|
+
print(f'Chunking took {readable_time(time.time() - start_time)}', flush=True)
|
|
173
|
+
|
|
174
|
+
with open(cache_file, 'wb') as f:
|
|
175
|
+
pickle.dump(pgn_chunks, f)
|
|
176
|
+
|
|
177
|
+
return pgn_chunks
|
|
178
|
+
|
|
179
|
+
|
|
180
|
+
def board_to_tensor(board):
|
|
181
|
+
|
|
182
|
+
piece_types = [chess.PAWN, chess.KNIGHT, chess.BISHOP, chess.ROOK, chess.QUEEN, chess.KING]
|
|
183
|
+
num_piece_channels = 12 # 6 piece types * 2 colors
|
|
184
|
+
additional_channels = 6 # 1 for player's turn, 4 for castling rights, 1 for en passant
|
|
185
|
+
tensor = torch.zeros((num_piece_channels + additional_channels, 8, 8), dtype=torch.float32)
|
|
186
|
+
|
|
187
|
+
# Precompute indices for each piece type
|
|
188
|
+
piece_indices = {piece: i for i, piece in enumerate(piece_types)}
|
|
189
|
+
|
|
190
|
+
# Fill tensor for each piece type
|
|
191
|
+
for piece_type in piece_types:
|
|
192
|
+
for color in [True, False]: # True is White, False is Black
|
|
193
|
+
piece_map = board.pieces(piece_type, color)
|
|
194
|
+
index = piece_indices[piece_type] + (0 if color else 6)
|
|
195
|
+
for square in piece_map:
|
|
196
|
+
row, col = divmod(square, 8)
|
|
197
|
+
tensor[index, row, col] = 1.0
|
|
198
|
+
|
|
199
|
+
# Player's turn channel (White = 1, Black = 0)
|
|
200
|
+
turn_channel = num_piece_channels
|
|
201
|
+
if board.turn == chess.WHITE:
|
|
202
|
+
tensor[turn_channel, :, :] = 1.0
|
|
203
|
+
|
|
204
|
+
# Castling rights channels
|
|
205
|
+
castling_rights = [board.has_kingside_castling_rights(chess.WHITE),
|
|
206
|
+
board.has_queenside_castling_rights(chess.WHITE),
|
|
207
|
+
board.has_kingside_castling_rights(chess.BLACK),
|
|
208
|
+
board.has_queenside_castling_rights(chess.BLACK)]
|
|
209
|
+
for i, has_right in enumerate(castling_rights):
|
|
210
|
+
if has_right:
|
|
211
|
+
tensor[num_piece_channels + 1 + i, :, :] = 1.0
|
|
212
|
+
|
|
213
|
+
# En passant target channel
|
|
214
|
+
ep_channel = num_piece_channels + 5
|
|
215
|
+
if board.ep_square is not None:
|
|
216
|
+
row, col = divmod(board.ep_square, 8)
|
|
217
|
+
tensor[ep_channel, row, col] = 1.0
|
|
218
|
+
|
|
219
|
+
return tensor
|
|
220
|
+
|
|
221
|
+
def generate_pawn_promotions():
|
|
222
|
+
# Define the promotion rows for both colors and the promotion pieces
|
|
223
|
+
# promotion_rows = {'white': '7', 'black': '2'}
|
|
224
|
+
promotion_rows = {'white': '7'}
|
|
225
|
+
promotion_pieces = ['q', 'r', 'b', 'n']
|
|
226
|
+
promotions = []
|
|
227
|
+
|
|
228
|
+
# Iterate over each color
|
|
229
|
+
for color, row in promotion_rows.items():
|
|
230
|
+
# Target rows for promotion (8 for white, 1 for black)
|
|
231
|
+
target_row = '8' if color == 'white' else '1'
|
|
232
|
+
|
|
233
|
+
# Each file from 'a' to 'h'
|
|
234
|
+
for file in 'abcdefgh':
|
|
235
|
+
# Direct move to promotion
|
|
236
|
+
for piece in promotion_pieces:
|
|
237
|
+
promotions.append(f'{file}{row}{file}{target_row}{piece}')
|
|
238
|
+
|
|
239
|
+
# Capturing moves to the left and right (if not on the edges of the board)
|
|
240
|
+
if file != 'a':
|
|
241
|
+
left_file = chr(ord(file) - 1) # File to the left
|
|
242
|
+
for piece in promotion_pieces:
|
|
243
|
+
promotions.append(f'{file}{row}{left_file}{target_row}{piece}')
|
|
244
|
+
|
|
245
|
+
if file != 'h':
|
|
246
|
+
right_file = chr(ord(file) + 1) # File to the right
|
|
247
|
+
for piece in promotion_pieces:
|
|
248
|
+
promotions.append(f'{file}{row}{right_file}{target_row}{piece}')
|
|
249
|
+
|
|
250
|
+
return promotions
|
|
251
|
+
|
|
252
|
+
|
|
253
|
+
def mirror_square(square):
|
|
254
|
+
|
|
255
|
+
file = square[0]
|
|
256
|
+
rank = str(9 - int(square[1]))
|
|
257
|
+
|
|
258
|
+
return file + rank
|
|
259
|
+
|
|
260
|
+
|
|
261
|
+
def mirror_move(move_uci):
|
|
262
|
+
# Check if the move is a promotion (length of UCI string will be more than 4)
|
|
263
|
+
is_promotion = len(move_uci) > 4
|
|
264
|
+
|
|
265
|
+
# Extract the start and end squares, and the promotion piece if applicable
|
|
266
|
+
start_square = move_uci[:2]
|
|
267
|
+
end_square = move_uci[2:4]
|
|
268
|
+
promotion_piece = move_uci[4:] if is_promotion else ""
|
|
269
|
+
|
|
270
|
+
# Mirror the start and end squares
|
|
271
|
+
mirrored_start = mirror_square(start_square)
|
|
272
|
+
mirrored_end = mirror_square(end_square)
|
|
273
|
+
|
|
274
|
+
# Return the mirrored move, including the promotion piece if applicable
|
|
275
|
+
return mirrored_start + mirrored_end + promotion_piece
|
|
276
|
+
|
|
277
|
+
|
|
278
|
+
def get_chunks(pgn_path, chunk_size):
|
|
279
|
+
|
|
280
|
+
chunks = []
|
|
281
|
+
with open(pgn_path, 'r', encoding='utf-8') as pgn_file:
|
|
282
|
+
while True:
|
|
283
|
+
start_pos = pgn_file.tell()
|
|
284
|
+
game_count = 0
|
|
285
|
+
while game_count < chunk_size:
|
|
286
|
+
line = pgn_file.readline()
|
|
287
|
+
if not line:
|
|
288
|
+
break
|
|
289
|
+
if line[-4:] == "1-0\n" or line[-4:] == "0-1\n":
|
|
290
|
+
game_count += 1
|
|
291
|
+
if line[-8:] == "1/2-1/2\n":
|
|
292
|
+
game_count += 1
|
|
293
|
+
if line[-2:] == "*\n":
|
|
294
|
+
game_count += 1
|
|
295
|
+
line = pgn_file.readline()
|
|
296
|
+
if line not in ["\n", ""]:
|
|
297
|
+
raise ValueError
|
|
298
|
+
end_pos = pgn_file.tell()
|
|
299
|
+
chunks.append((start_pos, end_pos))
|
|
300
|
+
if not line:
|
|
301
|
+
break
|
|
302
|
+
|
|
303
|
+
return chunks
|
|
304
|
+
|
|
305
|
+
|
|
306
|
+
def decompress_zst(file_path, decompressed_path):
|
|
307
|
+
""" Decompress a .zst file using pyzstd """
|
|
308
|
+
with open(file_path, 'rb') as compressed_file, open(decompressed_path, 'wb') as decompressed_file:
|
|
309
|
+
pyzstd.decompress_stream(compressed_file, decompressed_file)
|
|
310
|
+
|
|
311
|
+
|
|
312
|
+
def get_all_possible_moves():
|
|
313
|
+
|
|
314
|
+
all_moves = []
|
|
315
|
+
|
|
316
|
+
for rank in range(8):
|
|
317
|
+
for file in range(8):
|
|
318
|
+
square = chess.square(file, rank)
|
|
319
|
+
|
|
320
|
+
board = chess.Board(None)
|
|
321
|
+
board.set_piece_at(square, chess.Piece(chess.QUEEN, chess.WHITE))
|
|
322
|
+
legal_moves = list(board.legal_moves)
|
|
323
|
+
all_moves.extend(legal_moves)
|
|
324
|
+
|
|
325
|
+
board = chess.Board(None)
|
|
326
|
+
board.set_piece_at(square, chess.Piece(chess.KNIGHT, chess.WHITE))
|
|
327
|
+
legal_moves = list(board.legal_moves)
|
|
328
|
+
all_moves.extend(legal_moves)
|
|
329
|
+
|
|
330
|
+
all_moves = [all_moves[i].uci() for i in range(len(all_moves))]
|
|
331
|
+
|
|
332
|
+
pawn_promotions = generate_pawn_promotions()
|
|
333
|
+
|
|
334
|
+
return all_moves + pawn_promotions
|
|
335
|
+
|
|
336
|
+
|
|
337
|
+
def chunks(lst, n):
|
|
338
|
+
"""Yield successive n-sized chunks from lst."""
|
|
339
|
+
for i in range(0, len(lst), n):
|
|
340
|
+
yield lst[i:i + n]
|
|
341
|
+
|
|
342
|
+
|
|
343
|
+
if __name__ == '__main__':
|
|
344
|
+
|
|
345
|
+
pass
|
|
346
|
+
|
|
347
|
+
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
BSD 2-Clause License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2024, CSSLab
|
|
4
|
+
|
|
5
|
+
Redistribution and use in source and binary forms, with or without
|
|
6
|
+
modification, are permitted provided that the following conditions are met:
|
|
7
|
+
|
|
8
|
+
1. Redistributions of source code must retain the above copyright notice, this
|
|
9
|
+
list of conditions and the following disclaimer.
|
|
10
|
+
|
|
11
|
+
2. Redistributions in binary form must reproduce the above copyright notice,
|
|
12
|
+
this list of conditions and the following disclaimer in the documentation
|
|
13
|
+
and/or other materials provided with the distribution.
|
|
14
|
+
|
|
15
|
+
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
|
16
|
+
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
|
17
|
+
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
|
18
|
+
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
|
|
19
|
+
FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
|
20
|
+
DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
|
|
21
|
+
SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
|
|
22
|
+
CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
|
|
23
|
+
OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
|
24
|
+
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
Metadata-Version: 2.1
|
|
2
|
+
Name: maia2
|
|
3
|
+
Version: 0.1
|
|
4
|
+
Summary: An amazing sample package for maia2.
|
|
5
|
+
Author-email: csslab <josephtang@cs.toronto.edu>
|
|
6
|
+
Description-Content-Type: text/markdown
|
|
7
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
8
|
+
Project-URL: Home, https://github.com/CSSLab/maia2
|
|
9
|
+
|
|
10
|
+
# maia2
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
maia2/__init__.py,sha256=8z7obJwpM5l5hGYw8MvEwuViELcuO-D991YkvWhMJh0,64
|
|
2
|
+
maia2/fetch_data.sh,sha256=LefV6tal7pp0a34ODhcx6y1-i0p4m1mLvS1ALIB0PeM,746
|
|
3
|
+
maia2/inference.py,sha256=43rHIrV2oYVLvxYrVF9L_5C4Zad7BjsKGKsVcKPRBuA,5542
|
|
4
|
+
maia2/main.py,sha256=A1YM01iJN2XzGfK1a8vWTB7HbwrjFhgRo2A65qXR6VY,26106
|
|
5
|
+
maia2/utils.py,sha256=dvriII8I6zzS-ZIsNEDDV6tAXfiqaa0OZ0NTOARlgd0,10560
|
|
6
|
+
maia2-0.1.dist-info/LICENSE,sha256=T0nFxRZvtWtfqPnjU6vIWkdL0t13hvAAhwHRZhluelc,1295
|
|
7
|
+
maia2-0.1.dist-info/WHEEL,sha256=Sgu64hAMa6g5FdzHxXv9Xdse9yxpGGMeagVtPMWpJQY,99
|
|
8
|
+
maia2-0.1.dist-info/METADATA,sha256=QWXxHkV-w_G_sm_NgNuFtkyMXsr9hHBm1Yj10JGGf5w,293
|
|
9
|
+
maia2-0.1.dist-info/RECORD,,
|