File size: 2,946 Bytes
3f9559b
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
"""Model architectures extracted from the Master's project notebooks.

Used to load the trained .pth weights and export them to ONNX. The backend
serves the ONNX models, so torch is only needed at conversion time.

  - PieceImageClassifier : MobileNetV2 -> 12 classes (square -> piece, digitization)
  - PieceClassifier      : CNN, 12x8x8 -> 6  (which piece type to move)
  - SquareClassifier     : CNN, 12x8x8 -> 64 (destination square; one per piece type)
"""

from __future__ import annotations

import torch.nn as nn
import torchvision.models as models


class PieceImageClassifier(nn.Module):
    """Digitization: classify a single board square crop into one of 12 pieces."""

    def __init__(self):
        super().__init__()
        # weights=None: we load our own trained state_dict, no ImageNet download.
        self.model = models.mobilenet_v2(weights=None)
        self.model.classifier = nn.Sequential(
            nn.AdaptiveAvgPool2d(1),
            nn.Flatten(),
            nn.Linear(1280, 512),
            nn.ReLU(),
            nn.Linear(512, 12),
        )

    def forward(self, x):
        return self.model.classifier(self.model.features(x))


def _conv_block() -> nn.Sequential:
    return nn.Sequential(
        nn.Conv2d(12, 32, 3, padding=1), nn.BatchNorm2d(32), nn.ReLU(),
        nn.Conv2d(32, 64, 3, padding=1), nn.BatchNorm2d(64), nn.ReLU(),
        nn.MaxPool2d(2, 2),
        nn.Conv2d(64, 128, 3, padding=1), nn.BatchNorm2d(128), nn.ReLU(),
        nn.Conv2d(128, 256, 3, padding=1), nn.BatchNorm2d(256), nn.ReLU(),
        nn.MaxPool2d(2, 2),
        nn.Conv2d(256, 512, 3, padding=1), nn.BatchNorm2d(512), nn.ReLU(),
        nn.Conv2d(512, 512, 3, padding=1), nn.BatchNorm2d(512), nn.ReLU(),
        nn.MaxPool2d(2, 2),
    )


class PieceClassifier(nn.Module):
    """Move prediction stage 1: which piece type (P,N,B,R,Q,K) is likely to move."""

    def __init__(self):
        super().__init__()
        self.conv_layers = _conv_block()
        self.fc_layers = nn.Sequential(
            nn.Dropout(0.25), nn.Linear(512, 1024), nn.BatchNorm1d(1024), nn.ReLU(),
            nn.Dropout(0.25), nn.Linear(1024, 512), nn.BatchNorm1d(512), nn.ReLU(),
            nn.Linear(512, 6),
        )

    def forward(self, x):
        x = self.conv_layers(x)
        x = x.view(x.size(0), -1)
        return self.fc_layers(x)


class SquareClassifier(nn.Module):
    """Move prediction stage 2: destination square (0-63) for a given piece type."""

    def __init__(self):
        super().__init__()
        self.conv_layers = _conv_block()
        self.fc_layers = nn.Sequential(
            nn.Dropout(0.25), nn.Linear(512, 1024), nn.BatchNorm1d(1024), nn.ReLU(),
            nn.Dropout(0.25), nn.Linear(1024, 512), nn.BatchNorm1d(512), nn.ReLU(),
            nn.Linear(512, 64),
        )

    def forward(self, x):
        x = self.conv_layers(x)
        x = x.view(x.size(0), -1)
        return self.fc_layers(x)