๊ธฐ์กด ๊ณ ์คํธ ๋ชจ๋ 2D โ 3D ์ปจ๋ณผ๋ฃจ์
์ค์
๊ณ ์คํธ๋ท ๋
ผ๋ฌธ ๋งํฌ : https://arxiv.org/pdf/1911.11907
๊ธฐ์กด์ ๋ง๋ค์ด ๋์ ๊ณ ์คํธ๋ท ๋ชจ๋์์ 2D Convolution๋ถ๋ถ์ 3D ์ปจ๋ณผ๋ฃจ์
์ผ๋ก ๋ณ๊ฒฝ ๋ฐ ๊ฐ๋จํ ๋ชจ๋ธ์ ๋ง๋ค์ด์ ํ์ตํด๋ณด๋ ค๊ณ ํฉ๋๋ค.
์ ๊ณ ์คํธ๋ท์ด๋
์๋ ๊ทธ๋ฆผ์ ๋
ผ๋ฌธ์์ ๋ฐ์ตํ ๊ทธ๋ฆผ์ธ๋ฐ์.
์ผ๋ฐ ๊ฐ๋ฐ์๋ค๋ ์ฝ๊ฒ ์ดํดํ ์ ์๋ ์ค๋ช
์ผ๋ก๋ ์..
๊ณ ์คํธ๋ท ์ ์๋ค์ด ์ฑ๋ฅ์ด ์ข์ ๋ชจ๋ธ๋ค์ ํผ์ณ๋ฅผ ๊น์ ์กฐ์ฌ๋ฅผ ๋ค ํด๋ณด๋
๋น์ทํ๊ฒ ์๊ธด ํผ์ณ๋ค์ด ๋ง๋ค๋ ๊ฒ์ ํ์ธ ํ์ต๋๋ค.
(ํญ์ ๋น์ทํ๊ฒ ์๊ธด ํผ์ณ๊ฐ ๋ง๋ค๊ณ ์ฑ๋ฅ์ด ์ข๋ค๋ ๊ฑด ์๋๋๋ค. ๋์ ๋ฐ๋ผ์ ๋ค๋ฆ
๋๋ค)
๊ทธ๋ผ ์ ์๋น์ด ํ๊ฒน๊ฒ ์ปจ๋ณผ๋ฃจ์
ํํฐ ๊ฐฏ์๋ฅผ ๋๋ ค์ ๋น์ทํ ๋
์ ๋ง๋ค์ง ๋ง๊ณ
์์ ์๋ ํผ์ณ๋ ์ ์ฌํ๊ฒ ๋น์ทํ๊ฒ ๋ฎ๋๋ก ์์ ์๋ ๋
์์ ๋น๋ฒผ์ (์ปจ๋ณผ๋ฃจ์
) ๊ฒฐ๊ณผ๋ฅผ ๋ง๋ค์๋ ๊ฒ์ด ์์ด๋์ด์
๋๋ค.
(์ด๋ Depth wise conolution์ ํ๋๋ฐ ์ด๊ฑด ๋๋ฌด ๊ธธ์ด์ง๋โฆ ์๋ตํ๊ฒ ์ต๋๋ค.)
๊ทธ๋์.. ์ด ๊ณ ์คํธ ๋ชจ๋์ด ์ ์ ์ฐ์ฐ์ผ๋ก ๋น์ทํ ํผ์ณ๋ฅผ ๋ง์ด ๋ง๋ค์ด๋ด๋๋ฐ ์ฅ์ ์ด ์์ผ๋ ์ด ์ฐ์ฐ๋ ์์ ๊ณ ์คํธ ๋ชจ๋์ Gesture ์ธ์์ ๋ง๊ฒ ์์ ํ์ฌ ์จ๋ณด๋ ค๊ณ ํฉ๋๋ค.
๊ณผ๊ฑฐ์ ์์ฑํด ๋์ ์ฝ๋
class GhostModule(torch.nn.Module):
def __init__(self, in_channels, out_channels, kernel_size=1, ratio=2, dw_size=3, stride=1, use_activation=True, activation=torch.nn.ReLU):
super(GhostModule, self).__init__()
self.oup = out_channels
init_channels = math.ceil(out_channels / ratio)
new_channels = init_channels*(ratio-1)
self.primary_conv = torch.nn.Sequential(
torch.nn.Conv2d(in_channels, init_channels, kernel_size, stride, kernel_size//2, bias=False),
torch.nn.BatchNorm2d(init_channels),
activation(inplace=True) if use_activation else torch.nn.Sequential(),
)
self.cheap_operation = torch.nn.Sequential(
torch.nn.Conv2d(init_channels, new_channels, dw_size, 1, dw_size//2, groups=init_channels, bias=False),
torch.nn.BatchNorm2d(new_channels),
activation(inplace=True) if use_activation else torch.nn.Sequential(),
)
def forward(self, x):
x1 = self.primary_conv(x)
x2 = self.cheap_operation(x1)
out = torch.cat([x1,x2], dim=1)
return out[:,:self.oup,:,:]
ํ
์คํธ ์ค์ธ ๋ณ๊ฒฝ๋ ์ฝ๋
import torch
import math
class GhostLayer3D(torch.nn.Module):
def __init__(self,
in_channels: int,
out_channels: int,
ratio: int = 2,
stride=1, #T, H ,W
use_activation: bool = True,
activation=torch.nn.ReLU):
super().__init__()
if ratio < 1:
raise Exception("ratio must be >= 1")
self.oup = out_channels
if isinstance(stride, int):
stride_3d = (stride, stride, stride)
else:
if len(stride) != 3:
raise Exception("stride must be int or tuple of length 3")
stride_3d = tuple(int(s) for s in stride)
init_channels = int(math.ceil(out_channels / ratio))
new_channels = init_channels * (ratio - 1)
# ์๊ฐ๊ณผ ๊ณต๊ฐ๋ง ๋น๋น๊ธฐ
self.primary_conv = torch.nn.Sequential(
torch.nn.Conv3d(
in_channels=in_channels,
out_channels=init_channels,
kernel_size=(3, 3, 3),
stride=stride_3d,
padding=(1, 1, 1),
bias=False
),
torch.nn.BatchNorm3d(init_channels),
activation(inplace=True) if use_activation else torch.nn.Identity(),
)
# ๊ณต๊ฐ๋ง ๋น๋น๊ธฐ
if new_channels > 0:
self.cheap_operation = torch.nn.Sequential(
torch.nn.Conv3d(
in_channels=init_channels,
out_channels=new_channels,
kernel_size=(1, 3, 3),
stride=(1, 1, 1),
padding=(0, 1, 1),
groups=init_channels, # ๋์ค์์ด์ฆ ์ปจ๋ณผ๋ฃจ์
bias=False
),
torch.nn.BatchNorm3d(new_channels),
activation(inplace=True) if use_activation else torch.nn.Identity(),
)
else:
self.cheap_operation = None
def forward(self, x: torch.Tensor) -> torch.Tensor:
x1 = self.primary_conv(x)
if self.cheap_operation is None:
out = x1
else:
x2 = self.cheap_operation(x1)
out = torch.cat([x1, x2], dim=1)
return out[:, :self.oup, :, :, :]
์์ Gesture ์ธ์์์ ํต์ฌ ๊ณผ์
์ ์ค์ณ๋ ์ผ๋ฐ ์ ์ง ์์๊ณผ ๋ค๋ฅด๊ฒ T0~Tn ๊น์ง ํ๋ฅด๋ ์ ์ง ์์๋ค์ ๋ฌถ์์ ๋ค ๋ณด์์ผ๋ง
๋ชจ๋ธ์ด ์ป๊ณ ์ํ๋ ๊ฒฐ๊ณผ๋ฅผ ์์ธก ํ ์ ์์ต๋๋ค.
(์: ์์ ํ๋ค๊ฑฐ๋, ์์ ๋จธ๋ฆฌ ์๋ก ์ฌ๋ฆฐ๋ค๊ฑฐ๋, ๋์ ์ธ ์์ง์ ํ ์ธํธ์ ๋ถ์)
๊ทธ๋์ ์ผ๋ฐ์ ์ธ 2D ์ปจ๋ณผ๋ฃจ์
์ R,G,B ์์์ด ์๋ ์ด๋ฏธ์ง๋ด์์ Width์ Height ๋ฐฉํฅ์ผ๋ก๋ง ์์ง์ด์ง๋ง. 3D ์ปจ๋ณผ๋ฃจ์
์๊ฐ ์ถ T๋ฅผ ์ดํดํด์ผ ํ๊ธฐ ๋๋ฌธ์ T์ถ์ผ๋ก ์ปจ๋ณผ๋ฃจ์
์ปค๋์ด ์์ง์ด๊ฒ ๋์์ธ ๋์ด ์์ต๋๋ค.
2D ์ปจ๋ณผ๋ฃจ์
์์ :

3D ์ปจ๋ณผ๋ฃจ์
์์ :

์ธํฐ๋ท์ ๋์๋ค๋๋ gif๋ฅผ ๊ธ์ด๋ค๊ฐ ๊ฐ์ ธ์๋๋ฐ์.
๋ณด์๋ค์ํผ 3D ์ปจ๋ณผ๋ฃจ์
์ ๋ด๊ฐ ์ ์ํ (gif์์๋ z์ถ) ๋ฐฉํฅ์ผ๋ก ์ปค๋์ด ์์ ๋กญ๊ฒ ์ด๋ํฉ๋๋ค.
Ghost๋ชจ๋์ ์ ์ฉํ ์ ์ค์ณ ์ธ์์ฉ ๋ชจ๋ธ ์ฝ๋
import torch
from layers.ghost_layer3d import GhostLayer3D
class GhostNet3D(torch.nn.Module):
def __init__(self,
in_channels,
class_num,
):
super(GhostNet3D, self).__init__()
# ์๊ฐ์ถ frame 16์ผ๋ก ์์
# ๊ณต๊ฐ ํฌ๊ธฐ 128x64
self.stem = torch.nn.Sequential(
GhostLayer3D(in_channels=in_channels, out_channels=16)
)
# ์๊ฐ์ถ frame 8๋ก ์์ถ
# ๊ณต๊ฐ ํฌ๊ธฐ 64x32๋ก ์์ถ
self.layer1 = torch.nn.Sequential(
GhostLayer3D(in_channels=16, out_channels=24, stride=2),
GhostLayer3D(in_channels=24, out_channels=24)
)
# ์๊ฐ์ถ frame 8๋ก ์ ์ง
# ๊ณต๊ฐ ํฌ๊ธฐ 32x16
self.layer2 = torch.nn.Sequential(
GhostLayer3D(in_channels=24, out_channels=40, stride=(1,2,2)),
GhostLayer3D(in_channels=40, out_channels=40),
)
# ์๊ฐ์ถ frame 8๋ก ์ ์ง
# ๊ณต๊ฐ ํฌ๊ธฐ 16x8
self.layer3 = torch.nn.Sequential(
GhostLayer3D(in_channels=40, out_channels=80, stride=(1,2,2)),
GhostLayer3D(in_channels=80, out_channels=80),
GhostLayer3D(in_channels=80, out_channels=80),
GhostLayer3D(in_channels=80, out_channels=80),
)
# ์๊ฐ์ถ frame 8๋ก ์ ์ง
# ๊ณต๊ฐ ํฌ๊ธฐ 8x4
self.layer4 = torch.nn.Sequential(
GhostLayer3D(in_channels=80, out_channels=80, stride=(1,2,2)),
GhostLayer3D(in_channels=80, out_channels=80),
GhostLayer3D(in_channels=80, out_channels=80),
GhostLayer3D(in_channels=80, out_channels=80),
)
self.gap = torch.nn.AdaptiveAvgPool3d((1,1,1))
self.fc = torch.nn.Linear(80, class_num)
def forward(self, x):
## ๋๋ฒ๊น
์ฉ ์๊ฐ์ถ 16์์ ์์ ํ์ธ์ฉ
B, C, T, H, W = x.shape
x = self.stem(x)
x = self.layer1(x)
x = self.layer2(x)
x = self.layer3(x)
x = self.layer4(x)
x = self.gap(x)
x = x.view(x.size(0), -1)
x = self.fc(x)
return x
ํ์ต ์ฝ๋ ๋ฐ ๋ฐ์ดํฐ์
๋งํฌ
๋ฐ์ดํฐ์
: 20bn-jester | Kaggle
ํ์ต ์คํฌ๋ฆฝํธ : HGR/python/train.py at main ยท gellston/HGR ยท GitHub
Gesture ์ฐ์๋์ ๋ฐ์ดํฐ์
์ด์ฌ์ ๊ทธ๋ฐ์ง ์์ฒญ ํ์ต์ด ๋๋ฆฌ๊ตฐ์.. ๋ด์ผ ์์นจ๊น์ง ๊ธฐ๋ค๋ ค๋ณด๊ฒ ์ต๋๋ค.