| """MyResNet ๋ชจ๋ธ ๊ตฌํ. |
| |
| ResNet (Deep Residual Learning for Image Recognition, He et al., 2015) |
| ๋
ผ๋ฌธ์ PyTorch + ํ๊น
ํ์ด์ค transformers ํฌ๋งท์ผ๋ก ๊ตฌํํ ํ์ผ์
๋๋ค. |
| """ |
| from typing import Optional, Union, Tuple |
|
|
| import torch |
| import torch.nn as nn |
| from transformers import PreTrainedModel |
| from transformers.modeling_outputs import ImageClassifierOutput |
|
|
| from configuration_myresnet import MyResNetConfig |
|
|
|
|
| |
| |
| |
| class BasicBlock(nn.Module): |
| """2๊ฐ์ 3x3 conv๋ก ์ด๋ฃจ์ด์ง ๊ธฐ๋ณธ residual block. |
| |
| y = ReLU( BN(conv(ReLU(BN(conv(x))))) + shortcut(x) ) |
| """ |
|
|
| expansion = 1 |
|
|
| def __init__(self, in_channels: int, out_channels: int, stride: int = 1): |
| super().__init__() |
| |
| self.conv1 = nn.Conv2d( |
| in_channels, out_channels, kernel_size=3, stride=stride, padding=1, bias=False |
| ) |
| self.bn1 = nn.BatchNorm2d(out_channels) |
|
|
| |
| self.conv2 = nn.Conv2d( |
| out_channels, out_channels, kernel_size=3, stride=1, padding=1, bias=False |
| ) |
| self.bn2 = nn.BatchNorm2d(out_channels) |
| self.relu = nn.ReLU(inplace=True) |
|
|
| |
| if stride != 1 or in_channels != out_channels * self.expansion: |
| self.shortcut = nn.Sequential( |
| nn.Conv2d( |
| in_channels, |
| out_channels * self.expansion, |
| kernel_size=1, |
| stride=stride, |
| bias=False, |
| ), |
| nn.BatchNorm2d(out_channels * self.expansion), |
| ) |
| else: |
| self.shortcut = nn.Identity() |
|
|
| def forward(self, x: torch.Tensor) -> torch.Tensor: |
| identity = self.shortcut(x) |
| out = self.relu(self.bn1(self.conv1(x))) |
| out = self.bn2(self.conv2(out)) |
| out = out + identity |
| return self.relu(out) |
|
|
|
|
| |
| |
| |
| class BottleneckBlock(nn.Module): |
| """1x1 -> 3x3 -> 1x1 ๊ตฌ์กฐ๋ก ์ฑ๋์ ์ค์๋ค๊ฐ ๋ค์ ๋๋ฆฌ๋ bottleneck block. |
| |
| ๋ง์ง๋ง 1x1 conv์์ ์ฑ๋์ expansion(=4)๋ฐฐ๋ก ํ์ฅํฉ๋๋ค. |
| """ |
|
|
| expansion = 4 |
|
|
| def __init__(self, in_channels: int, out_channels: int, stride: int = 1): |
| super().__init__() |
| |
| self.conv1 = nn.Conv2d(in_channels, out_channels, kernel_size=1, bias=False) |
| self.bn1 = nn.BatchNorm2d(out_channels) |
|
|
| |
| self.conv2 = nn.Conv2d( |
| out_channels, out_channels, kernel_size=3, stride=stride, padding=1, bias=False |
| ) |
| self.bn2 = nn.BatchNorm2d(out_channels) |
|
|
| |
| self.conv3 = nn.Conv2d( |
| out_channels, out_channels * self.expansion, kernel_size=1, bias=False |
| ) |
| self.bn3 = nn.BatchNorm2d(out_channels * self.expansion) |
| self.relu = nn.ReLU(inplace=True) |
|
|
| if stride != 1 or in_channels != out_channels * self.expansion: |
| self.shortcut = nn.Sequential( |
| nn.Conv2d( |
| in_channels, |
| out_channels * self.expansion, |
| kernel_size=1, |
| stride=stride, |
| bias=False, |
| ), |
| nn.BatchNorm2d(out_channels * self.expansion), |
| ) |
| else: |
| self.shortcut = nn.Identity() |
|
|
| def forward(self, x: torch.Tensor) -> torch.Tensor: |
| identity = self.shortcut(x) |
| out = self.relu(self.bn1(self.conv1(x))) |
| out = self.relu(self.bn2(self.conv2(out))) |
| out = self.bn3(self.conv3(out)) |
| out = out + identity |
| return self.relu(out) |
|
|
|
|
| |
| |
| |
| class MyResNetPreTrainedModel(PreTrainedModel): |
| """from_pretrained / save_pretrained ๋ฑ์ ์ง์ํ๋ ๋ฒ ์ด์ค ํด๋์ค.""" |
|
|
| config_class = MyResNetConfig |
| base_model_prefix = "myresnet" |
| main_input_name = "pixel_values" |
| supports_gradient_checkpointing = False |
|
|
| def _init_weights(self, module): |
| """๋
ผ๋ฌธ Sec 3.4: He initialization ์ฌ์ฉ.""" |
| if isinstance(module, nn.Conv2d): |
| nn.init.kaiming_normal_(module.weight, mode="fan_out", nonlinearity="relu") |
| elif isinstance(module, nn.BatchNorm2d): |
| nn.init.constant_(module.weight, 1) |
| nn.init.constant_(module.bias, 0) |
| elif isinstance(module, nn.Linear): |
| nn.init.normal_(module.weight, mean=0.0, std=0.01) |
| if module.bias is not None: |
| nn.init.constant_(module.bias, 0) |
|
|
|
|
| |
| |
| |
| class MyResNetForImageClassification(MyResNetPreTrainedModel): |
| """์ด๋ฏธ์ง ๋ถ๋ฅ์ฉ ResNet ๋ชจ๋ธ. |
| |
| ์
๋ ฅ: pixel_values (batch, num_channels, H, W) |
| ์ถ๋ ฅ: ImageClassifierOutput(loss, logits) |
| |
| ๋
ผ๋ฌธ Table 1์ ๊ตฌ์กฐ๋ฅผ ๋ฐ๋ฆ
๋๋ค: |
| conv1 (7x7, stride=2) -> maxpool -> stage1~4 -> avgpool -> fc |
| """ |
|
|
| def __init__(self, config: MyResNetConfig): |
| super().__init__(config) |
| self.config = config |
|
|
| |
| block = BasicBlock if config.block_type == "basic" else BottleneckBlock |
| self.in_channels = 64 |
|
|
| |
| |
| self.stem = nn.Sequential( |
| nn.Conv2d( |
| config.num_channels, 64, kernel_size=7, stride=2, padding=3, bias=False |
| ), |
| nn.BatchNorm2d(64), |
| nn.ReLU(inplace=True), |
| nn.MaxPool2d(kernel_size=3, stride=2, padding=1), |
| ) |
|
|
| |
| self.stage1 = self._make_stage( |
| block, config.hidden_sizes[0], config.layers[0], stride=1 |
| ) |
| self.stage2 = self._make_stage( |
| block, config.hidden_sizes[1], config.layers[1], stride=2 |
| ) |
| self.stage3 = self._make_stage( |
| block, config.hidden_sizes[2], config.layers[2], stride=2 |
| ) |
| self.stage4 = self._make_stage( |
| block, config.hidden_sizes[3], config.layers[3], stride=2 |
| ) |
|
|
| |
| self.avgpool = nn.AdaptiveAvgPool2d(output_size=1) |
| self.classifier = nn.Linear( |
| config.hidden_sizes[3] * block.expansion, config.num_labels |
| ) |
|
|
| |
| self.post_init() |
|
|
| def _make_stage(self, block, out_channels: int, num_blocks: int, stride: int): |
| """ํ๋์ stage(๋์ผ ํด์๋์ ๋ธ๋ก๋ค)๋ฅผ ๋ง๋๋ ํฌํผ. |
| |
| ์ฒซ ๋ธ๋ก๋ง stride๋ก ๋ค์ด์ํ๋งํ๊ณ , ๋๋จธ์ง๋ stride=1. |
| """ |
| strides = [stride] + [1] * (num_blocks - 1) |
| layers = [] |
| for s in strides: |
| layers.append(block(self.in_channels, out_channels, stride=s)) |
| self.in_channels = out_channels * block.expansion |
| return nn.Sequential(*layers) |
|
|
| def forward( |
| self, |
| pixel_values: torch.Tensor, |
| labels: Optional[torch.Tensor] = None, |
| return_dict: Optional[bool] = None, |
| ) -> Union[Tuple, ImageClassifierOutput]: |
| """์์ ํ. |
| |
| Args: |
| pixel_values: (batch, num_channels, H, W) ํํ์ ์ด๋ฏธ์ง ํ
์. |
| labels: (batch,) ํํ์ ์ ๋ต ๋ ์ด๋ธ. ์ฃผ์ด์ง๋ฉด loss๋ ํจ๊ป ๋ฐํ. |
| return_dict: True๋ฉด ImageClassifierOutput, False๋ฉด ํํ ๋ฐํ. |
| """ |
| return_dict = return_dict if return_dict is not None else self.config.use_return_dict |
|
|
| |
| x = self.stem(pixel_values) |
| x = self.stage1(x) |
| x = self.stage2(x) |
| x = self.stage3(x) |
| x = self.stage4(x) |
|
|
| x = self.avgpool(x) |
| x = torch.flatten(x, 1) |
| logits = self.classifier(x) |
|
|
| |
| loss = None |
| if labels is not None: |
| loss_fn = nn.CrossEntropyLoss() |
| loss = loss_fn(logits, labels) |
|
|
| if not return_dict: |
| output = (logits,) |
| return ((loss,) + output) if loss is not None else output |
|
|
| return ImageClassifierOutput(loss=loss, logits=logits) |
|
|