본문 바로가기
  • 우당탕탕속의 잔잔함
Programming/Deep Learning Network

[Code] XNect

by zpstls 2023. 4. 4.
반응형

이번 포스트는 3D Pose Estimation과 관련된 XNect이라는 논문을 PyTorch를 이용해 구현해볼 것입니다.

 

 

우선, Pose Estimation과 XNect이라는 논문에 관한 내용은 다음 포스트를 참조해주세요.

 

[Pose Estimation] 2D/3D Pose Estimation에 관한 내용

Computer Vision과 관련된 AI, Deep Learning 분야에서 거의 필수적으로 다루는 주제가 있습니다. 바로 Pose Estimation인데요. 이번 포스트에서는 이 Pose Estimation에 관한 내용을 다루고자 합니다. Pose Estimation

mj-thump-thump-story.tistory.com

 

[Model] VNect과 XNect

이번 포스트는 Pose Estimation과 관련된 논문 중 하나인, VNect과 XNect이라는 것에 대해 다뤄보도록 하겠습니다. 해당 논문은 RGB 카메라를 통해 사람의 자세를 추정하는 방법을 다룬 것입니다. 우선,

mj-thump-thump-story.tistory.com

 

 

XNect의 핵심 구조는 SelecSLS(= Selective Short and Long Range Skip Connections)입니다. 이는 XNect의 Stage1에서 활용되며, 2D pose를 추정하고 3D pose로 추론하여 눈에 보이는 신체 관절로 인코딩하는 작업을 담당합니다.

SelecSLS는 ResNet-50을 Backbone으로 하고 Convolution과 Deconvolution, Batch Normailzation, ReLU 등으로 구성되어 있으며, 특히 1x1 Conv와 3x3 Conv를 교차하여 배치하였습니다. 그리고 CS(Concatenation Skip)구조를 활용하였는데, 이는 내부 연쇄 스킵 연결과 외부 연쇄 스킵 연결을 의미합니다. 즉 Layer와 Layer간의 Skip Connection 뿐만 아니라 Module과 Module간의 Skip Connection도 수행한다는 것을 의미합니다.

 

해당 부분을 이미지로 표현하면 다음과 같습니다.

(s = stride, k = the number of intermediate features, $n_{0}$ = the number of module outputs)

SelecSLS

위와 같은 Skip 연결을 통해 정확도와 연산 속도를 높일 수 있다고 합니다.

 

그럼 이제, 위와 같은 구조를 가진 SelecSLS를 코드로 구현해보도록 하겠습니다.

전반적인 프로그램은 SelecSLS를 ImageNet을 통해 성능 체크를 하는 것으로 합니다. 해당 코드는 PyTorch로 작성할 것입니다. 참고로 ImageNet Dataset과 관련된 코드는 여기서 다루진 않겠습니다!

 

우선, Main 부분입니다.

Main에서는 ImageNet Dataset을 SelecSLS에 넣고 Prediction하여 Error율을 체크하는 기능을 수행합니다.

전체 코드는 다음과 같습니다.

def accuracy(output, target, topk=(1,)):
    """Computes the precision@k for the specified values of k"""
    maxk = max(topk)
    batch_size = target.size(0)

    _, pred = output.topk(maxk, 1, True, True)
    pred = pred.t()
    correct = pred.eq(target.view(1, -1).expand_as(pred))

    res = []
    for k in topk:
        correct_k = correct[:k].view(-1).float().sum(0, keepdim=True)
        res.append(correct_k.mul_(100.0 / batch_size))
    return res


def main():
    torch.manual_seed(1234)
    model_class = 'selecsls'
    model_config = 'SelecSLS42' or 'SelecSLS42_B' or 'SelecSLS60' etc...
    model_weight = './weights/SelecSLS60_statedict.pth'
    imagenet_base_path = './'
    gpu_id = 0

    # run
    model_module = importlib.import_module('models.'+model_class)
    net = model_module.Net(nClasses=1000, config=model_config)
    net.load_state_dict(torch.load(model_weights, map_location= lambda storage, loc: storage))
    device = torch.device("cuda:"+str(gpu_id) if torch.cuda.is_available() else "cpu")
    net = net.to(device)
    net.eval()
    _,test_loader = get_data_loader(augment=False, batch_size=100, base_path=imagenet_base_path)
    with torch.no_grad():
        val1_err = []
        val5_err = []
        for x, y in test_loader:
            pred = F.log_softmax(net(x.to(device)))
            top1, top5 = accuracy(pred, y.to(device), topk=(1, 5))
            val1_err.append(100-top1)
            val5_err.append(100-top5)
        avg1_err=  float(np.sum(val1_err)) / len(val1_err)
        avg5_err=  float(np.sum(val5_err)) / len(val5_err)
    print('Top-1 Error: {} Top-5 Error {}'.format(avg1_err, avg5_err))

if __name__ == '__main__':
    main()

 

 

 

 

다음은 SelecSLS의 Network에 대한 코드를 작성해보도록 하겠습니다.

참고로 SelecSLS는 42, 42_B, 60, 60_B, 84 등과 같이 여러 버전이 있습니다. 이러한 여러가지 버전 중, Paper에 소개된 구조는 SelecSLS 60이며, 이는 다음과 같습니다.

표에 구성되어 있는 대로 Input Size, Stride, Feature 개수(=k), Module 개수(=$n_{0}$), Skip Connection의 여부를 배열의 형태로 구현합니다. 이들 각각은 Block의 형태로 구현되며, 해당 부분은 다음과 같습니다.

각각의 Layer를 구현하고 연결 순서에 맞게 연결해줍니다. 이때 Skip Connection의 여부에 따라 Layer간의 연결 여부가 달라집니다.

전반적인 Network의 요소들을 구현하였으니 이제는 Network를 어떤 순서로 어떻게 통과시킬지 구현해보도록 합니다.

전반적으로 SelecSLS Network가 forward되는 순서는 stem → features → head → classifier입니다. 참고로 classifier는 Linear, 즉 Softmax 연산을 수행합니다.

 

위와 같은 내용들에 대한 전체 코드는 다음과 같습니다.

def conv_bn(inp, oup, stride):
    return nn.Sequential(
        nn.Conv2d(inp, oup, 3, stride, 1, bias=False),
        nn.BatchNorm2d(oup),
        nn.ReLU(inplace=True)
    )

def conv_1x1_bn(inp, oup):
    return nn.Sequential(
        nn.Conv2d(inp, oup, 1, 1, 0, bias=False),
        nn.BatchNorm2d(oup),
        nn.ReLU(inplace=True)
    )
class SelecSLSBlock(nn.Module):
    def __init__(self, inp, skip, k, oup, isFirst, stride):
        super(SelecSLSBlock, self).__init__()
        self.stride = stride
        self.isFirst = isFirst
        assert stride in [1, 2]

        #Process input with 4 conv blocks with the same number of input and output channels
        self.conv1 = nn.Sequential(
                nn.Conv2d(inp, k, 3, stride, 1,groups= 1, bias=False, dilation=1),
                nn.BatchNorm2d(k),
                nn.ReLU(inplace=True)
                )
        self.conv2 = nn.Sequential(
                nn.Conv2d(k, k, 1, 1, 0,groups= 1, bias=False, dilation=1),
                nn.BatchNorm2d(k),
                nn.ReLU(inplace=True)
                )
        self.conv3 = nn.Sequential(
                nn.Conv2d(k, k//2, 3, 1, 1,groups= 1, bias=False, dilation=1),
                nn.BatchNorm2d(k//2),
                nn.ReLU(inplace=True)
                )
        self.conv4 = nn.Sequential(
                nn.Conv2d(k//2, k, 1, 1, 0,groups= 1, bias=False, dilation=1),
                nn.BatchNorm2d(k),
                nn.ReLU(inplace=True)
                )
        self.conv5 = nn.Sequential(
                nn.Conv2d(k, k//2, 3, 1, 1,groups= 1, bias=False, dilation=1),
                nn.BatchNorm2d(k//2),
                nn.ReLU(inplace=True)
                )
        self.conv6 = nn.Sequential(
                nn.Conv2d(2*k + (0 if isFirst else skip), oup, 1, 1, 0,groups= 1, bias=False, dilation=1),
                nn.BatchNorm2d(oup),
                nn.ReLU(inplace=True)
                )

    def forward(self, x):
        assert isinstance(x,list)
        assert len(x) in [1,2]

        d1 = self.conv1(x[0])
        d2 = self.conv3(self.conv2(d1))
        d3 = self.conv5(self.conv4(d2))
        if self.isFirst:
            out = self.conv6(torch.cat([d1, d2, d3], 1))
            return [out, out]
        else:
            return [self.conv6(torch.cat([d1, d2, d3, x[1]], 1)) , x[1]]
class Net(nn.Module):
    def __init__(self, nClasses=1000, config='SelecSLS60'):
        super(Net, self).__init__()

        #Stem
        self.stem = conv_bn(3, 32, 2)

        #Core Network
        self.features = []
        if config=='SelecSLS42':
            print('SelecSLS42')
            #Define configuration of the network after the initial neck
            self.selecSLS_config = [
                #inp,skip, k, oup, isFirst, stride
                [ 32,   0,  64,  64,  True,  2],
                [ 64,  64,  64, 128,  False, 1],
                [128,   0, 144, 144,  True,  2],
                [144, 144, 144, 288,  False, 1],
                [288,   0, 304, 304,  True,  2],
                [304, 304, 304, 480,  False, 1],
            ]
            #Head can be replaced with alternative configurations depending on the problem
            self.head = nn.Sequential(
                    conv_bn(480, 960, 2),
                    conv_bn(960, 1024, 1),
                    conv_bn(1024, 1024, 2),
                    conv_1x1_bn(1024, 1280),
                    )
            self.num_features = 1280
        elif config=='SelecSLS42_B':
            print('SelecSLS42_B')
            #Define configuration of the network after the initial neck
            self.selecSLS_config = [
                #inp,skip, k, oup, isFirst, stride
                [ 32,   0,  64,  64,  True,  2],
                [ 64,  64,  64, 128,  False, 1],
                [128,   0, 144, 144,  True,  2],
                [144, 144, 144, 288,  False, 1],
                [288,   0, 304, 304,  True,  2],
                [304, 304, 304, 480,  False, 1],
            ]
            #Head can be replaced with alternative configurations depending on the problem
            self.head = nn.Sequential(
                    conv_bn(480, 960, 2),
                    conv_bn(960, 1024, 1),
                    conv_bn(1024, 1280, 2),
                    conv_1x1_bn(1280, 1024),
                    )
            self.num_features = 1024
        elif config=='SelecSLS60':
            print('SelecSLS60')
            #Define configuration of the network after the initial neck
            self.selecSLS_config = [
                #inp,skip, k, oup, isFirst, stride
                [ 32,   0,  64,  64,  True,  2],
                [ 64,  64,  64, 128,  False, 1],
                [128,   0, 128, 128,  True,  2],
                [128, 128, 128, 128,  False, 1],
                [128, 128, 128, 288,  False, 1],
                [288,   0, 288, 288,  True,  2],
                [288, 288, 288, 288,  False, 1],
                [288, 288, 288, 288,  False, 1],
                [288, 288, 288, 416,  False, 1],
            ]
            #Head can be replaced with alternative configurations depending on the problem
            self.head = nn.Sequential(
                    conv_bn(416, 756, 2),
                    conv_bn(756, 1024, 1),
                    conv_bn(1024, 1024, 2),
                    conv_1x1_bn(1024, 1280),
                    )
            self.num_features = 1280
        elif config=='SelecSLS60_B':
            print('SelecSLS60_B')
            #Define configuration of the network after the initial neck
            self.selecSLS_config = [
                #inp,skip, k, oup, isFirst, stride
                [ 32,   0,  64,  64,  True,  2],
                [ 64,  64,  64, 128,  False, 1],
                [128,   0, 128, 128,  True,  2],
                [128, 128, 128, 128,  False, 1],
                [128, 128, 128, 288,  False, 1],
                [288,   0, 288, 288,  True,  2],
                [288, 288, 288, 288,  False, 1],
                [288, 288, 288, 288,  False, 1],
                [288, 288, 288, 416,  False, 1],
            ]
            #Head can be replaced with alternative configurations depending on the problem
            self.head = nn.Sequential(
                    conv_bn(416, 756, 2),
                    conv_bn(756, 1024, 1),
                    conv_bn(1024, 1280, 2),
                    conv_1x1_bn(1280, 1024),
                    )
            self.num_features = 1024
        elif config=='SelecSLS84':
            print('SelecSLS84')
            #Define configuration of the network after the initial neck
            self.selecSLS_config = [
                #inp,skip, k, oup, isFirst, stride
                [ 32,   0,  64,  64,  True,  2],
                [ 64,  64,  64, 144,  False, 1],
                [144,   0, 144, 144,  True,  2],
                [144, 144, 144, 144,  False, 1],
                [144, 144, 144, 144,  False, 1],
                [144, 144, 144, 144,  False, 1],
                [144, 144, 144, 304,  False, 1],
                [304,   0, 304, 304,  True,  2],
                [304, 304, 304, 304,  False, 1],
                [304, 304, 304, 304,  False, 1],
                [304, 304, 304, 304,  False, 1],
                [304, 304, 304, 304,  False, 1],
                [304, 304, 304, 512,  False, 1],
            ]
            #Head can be replaced with alternative configurations depending on the problem
            self.head = nn.Sequential(
                    conv_bn(512, 960, 2),
                    conv_bn(960, 1024, 1),
                    conv_bn(1024, 1024, 2),
                    conv_1x1_bn(1024, 1280),
                    )
            self.num_features = 1280
        elif config=='SelecSLS102':
            print('SelecSLS102')
            #Define configuration of the network after the initial neck
            self.selecSLS_config = [
                #inp,skip, k, oup, isFirst, stride
                [ 32,   0,  64,  64,  True,  2],
                [ 64,  64,  64,  64,  False, 1],
                [ 64,  64,  64,  64,  False, 1],
                [ 64,  64,  64, 128,  False, 1],
                [128,   0, 128, 128,  True,  2],
                [128, 128, 128, 128,  False, 1],
                [128, 128, 128, 128,  False, 1],
                [128, 128, 128, 128,  False, 1],
                [128, 128, 128, 288,  False, 1],
                [288,   0, 288, 288,  True,  2],
                [288, 288, 288, 288,  False, 1],
                [288, 288, 288, 288,  False, 1],
                [288, 288, 288, 288,  False, 1],
                [288, 288, 288, 288,  False, 1],
                [288, 288, 288, 288,  False, 1],
                [288, 288, 288, 480,  False, 1],
            ]
            #Head can be replaced with alternative configurations depending on the problem
            self.head = nn.Sequential(
                    conv_bn(480, 960, 2),
                    conv_bn(960, 1024, 1),
                    conv_bn(1024, 1024, 2),
                    conv_1x1_bn(1024, 1280),
                    )
            self.num_features = 1280
        else:
            raise ValueError('Invalid net configuration '+config+' !!!')

        #Build SelecSLS Core 
        for inp, skip, k, oup, isFirst, stride  in self.selecSLS_config:
            self.features.append(SelecSLSBlock(inp, skip, k, oup, isFirst, stride))
        self.features = nn.Sequential(*self.features)

        #Classifier To Produce Inputs to Softmax
        self.classifier = nn.Sequential(
                nn.Linear(self.num_features, nClasses),
        )


    def forward(self, x):
        x = self.stem(x)
        x = self.features([x])
        x = self.head(x[0])
        x = x.mean(3).mean(2)
        x = self.classifier(x)
        #x = F.log_softmax(x)
        return x

 

이와 같이 SelecSLS를 PyTorch를 이용해 구현해보았습니다.

생각보다 간단하게 구현할 수 있었습니다. 다만, 논문에는 Parameter와 같은 정보가 아주 자세하게 나와있지 않는 경우도 있어 해당 부분은 논문 작성자의 블로그 등을 통해 확인해보면서 구현하는 것도 좋을 것 같습니다.

 

이번 포스트는 여기서 마무리하도록 하겠습니다.

반응형

'Programming > Deep Learning Network' 카테고리의 다른 글

[Model] About Self-Attention  (1) 2023.10.31
[Model] About seq2seq (Sequence-To-Sequence)  (0) 2023.10.31
[Code] VNect  (0) 2023.03.13
[Model] VNect과 XNect  (0) 2023.03.13
[Code] MobileNet v1  (0) 2023.03.08

댓글