Download model.py from trabten/namsel_BUDA_CNN: direct link, hf CLI and curl.
- Browser
- Download file 5.08 kB
-
https://huggingface.co/trabten/namsel_BUDA_CNN/resolve/main/model.py
- Command line
-
hf download hf://trabten/namsel_BUDA_CNN/model.py
-
curl -L -o model.py https://huggingface.co/trabten/namsel_BUDA_CNN/resolve/main/model.py
5.08 kB
| """CNN model for Tibetan character recognition. | |
| CPU-optimized architecture based on deep learning best practices: | |
| - Depthwise separable convolutions (~8-18x fewer operations) | |
| - Batch normalization (faster convergence, higher learning rates) | |
| - Residual connections (deeper networks without vanishing gradients) | |
| - Global Average Pooling (eliminates millions of FC parameters) | |
| - He initialization for ReLU networks | |
| Input: 32x32 grayscale character images (1 channel) | |
| Output: num_classes logits | |
| """ | |
| import torch.nn as nn | |
| class DepthwiseSeparableConv(nn.Module): | |
| """Depthwise separable convolution: depthwise + pointwise. | |
| Factored convolution that reduces computation by ~8-18x compared | |
| to standard convolution for 3x3 kernels. | |
| """ | |
| def __init__(self, in_ch, out_ch, kernel_size=3, stride=1, padding=1): | |
| super().__init__() | |
| self.depthwise = nn.Conv2d( | |
| in_ch, in_ch, kernel_size, | |
| stride=stride, padding=padding, groups=in_ch, bias=False | |
| ) | |
| self.pointwise = nn.Conv2d(in_ch, out_ch, 1, bias=False) | |
| self.bn = nn.BatchNorm2d(out_ch) | |
| self.relu = nn.ReLU(inplace=True) | |
| def forward(self, x): | |
| x = self.depthwise(x) | |
| x = self.pointwise(x) | |
| x = self.bn(x) | |
| x = self.relu(x) | |
| return x | |
| class ResidualBlock(nn.Module): | |
| """Residual block with two depthwise separable convolutions. | |
| Skip connection adds input directly to output, enabling | |
| training of deeper networks without vanishing gradients. | |
| """ | |
| def __init__(self, channels): | |
| super().__init__() | |
| self.conv1 = DepthwiseSeparableConv(channels, channels) | |
| # Second conv without final ReLU (applied after residual add) | |
| self.conv2_dw = nn.Conv2d( | |
| channels, channels, 3, padding=1, groups=channels, bias=False | |
| ) | |
| self.conv2_pw = nn.Conv2d(channels, channels, 1, bias=False) | |
| self.bn2 = nn.BatchNorm2d(channels) | |
| self.relu = nn.ReLU(inplace=True) | |
| def forward(self, x): | |
| residual = x | |
| out = self.conv1(x) | |
| out = self.conv2_dw(out) | |
| out = self.conv2_pw(out) | |
| out = self.bn2(out) | |
| out = out + residual | |
| out = self.relu(out) | |
| return out | |
| class TibetanCNN(nn.Module): | |
| """CPU-optimized CNN for Tibetan character classification. | |
| Architecture: | |
| Input (1x32x32) | |
| -> Conv2d(32, 3x3) + BN + ReLU [32x32x32] stem | |
| -> DSConv(32->64, stride=2) [64x16x16] downsample | |
| -> ResidualBlock(64) [64x16x16] | |
| -> DSConv(64->128, stride=2) [128x8x8] downsample | |
| -> ResidualBlock(128) [128x8x8] | |
| -> DSConv(128->256, stride=2) [256x4x4] downsample | |
| -> Global Average Pooling [256] | |
| -> Dropout | |
| -> FC(256, num_classes) | |
| ~350K parameters. Inference: <5ms per character on CPU. | |
| """ | |
| def __init__(self, num_classes, dropout=0.3): | |
| super().__init__() | |
| # Stem: standard conv to learn from raw pixels | |
| self.stem = nn.Sequential( | |
| nn.Conv2d(1, 32, 3, padding=1, bias=False), | |
| nn.BatchNorm2d(32), | |
| nn.ReLU(inplace=True), | |
| ) | |
| # Downsample 32x32 -> 16x16, expand 32 -> 64 | |
| self.down1 = DepthwiseSeparableConv(32, 64, stride=2) | |
| self.res1 = ResidualBlock(64) | |
| # Downsample 16x16 -> 8x8, expand 64 -> 128 | |
| self.down2 = DepthwiseSeparableConv(64, 128, stride=2) | |
| self.res2 = ResidualBlock(128) | |
| # Downsample 8x8 -> 4x4, expand 128 -> 256 | |
| self.down3 = DepthwiseSeparableConv(128, 256, stride=2) | |
| # Classifier | |
| self.gap = nn.AdaptiveAvgPool2d(1) | |
| self.dropout = nn.Dropout(dropout) | |
| self.fc = nn.Linear(256, num_classes) | |
| self._init_weights() | |
| def _init_weights(self): | |
| """He initialization for ReLU networks.""" | |
| for m in self.modules(): | |
| if isinstance(m, nn.Conv2d): | |
| nn.init.kaiming_normal_(m.weight, mode='fan_out', nonlinearity='relu') | |
| elif isinstance(m, nn.BatchNorm2d): | |
| nn.init.constant_(m.weight, 1) | |
| nn.init.constant_(m.bias, 0) | |
| elif isinstance(m, nn.Linear): | |
| nn.init.kaiming_normal_(m.weight) | |
| nn.init.constant_(m.bias, 0) | |
| def forward(self, x): | |
| x = self.stem(x) # [B, 32, 32, 32] | |
| x = self.down1(x) # [B, 64, 16, 16] | |
| x = self.res1(x) # [B, 64, 16, 16] | |
| x = self.down2(x) # [B, 128, 8, 8] | |
| x = self.res2(x) # [B, 128, 8, 8] | |
| x = self.down3(x) # [B, 256, 4, 4] | |
| x = self.gap(x) # [B, 256, 1, 1] | |
| x = x.view(x.size(0), -1) # [B, 256] | |
| x = self.dropout(x) | |
| x = self.fc(x) # [B, num_classes] | |
| return x | |