Home Landmark classification
Post
Cancel

Landmark classification

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
# 라이브러리
import pandas as pd
import numpy as np
import os
os.environ["CUDA_VISIBLE_DEVICES"] = "0"

import argparse
import torch

from glob import glob
from PIL import Image
from tqdm import tqdm
from torch.utils.data import Dataset, DataLoader
from torch import nn, optim
from torch.nn import Conv2d, AdaptiveAvgPool2d, Linear
import torch.nn.functional as F
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
# data 파일을 copy해서 옮기는 방법
import zipfile
import os
from tqdm import tqdm

too_long = []

zipf = os.listdir('/content/drive/My Drive/d/train_zip')
zipf = sorted([os.path.join('/content/drive/My Drive/d/train_zip', z) for z in zipf])

os.makedirs('train', exist_ok=True)
for z in tqdm(zipf):
    try:        # s = z.split('.')[0].split('/')[-1]
        zipfile.ZipFile(z).extractall()
    except:
        too_long += [z]

from shutil import copyfile
for p in os.listdir('/content/drive/My Drive/d/648'):
    copyfile(os.path.join('/content/drive/My Drive/d/648/', p) , os.path.join('/content/train/648/', p))

a=0
for i in os.listdir('train'):
    a+=len(os.listdir(os.path.join('train', i)))

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
# arguments
# train_csv_exist, test_csv_exist는 glob.glob이 생각보다 시간을 많이 잡아먹어서 iteration 시간을 줄이기 위해 생성되는 파일입니다.
# 이미 생성되어 있을 경우 train_csv_exist.csv 파일로 Dataset을 생성합니다.
parser = argparse.ArgumentParser()

parser.add_argument('--train_dir', dest='train_dir', default="./public/train/")
parser.add_argument('--train_csv_dir', dest='train_csv_dir', default="./public/train.csv")
parser.add_argument('--train_csv_exist_dir', dest='train_csv_exist_dir', default="./public/train_exist.csv")

parser.add_argument('--test_dir', dest='test_dir', default="./public/test/")
parser.add_argument('--test_csv_dir', dest='test_csv_dir', default="./public/sample_submission.csv")
parser.add_argument('--test_csv_exist_dir', dest='test_csv_exist_dir', default="./public/sample_submission_exist.csv")

parser.add_argument('--test_csv_submission_dir', dest='test_csv_submission_dir', default="./public/my_submission.csv")
parser.add_argument('--model_dir', dest='model_dir', default="./ckpt/")

parser.add_argument('--image_size', dest='image_size', type=int, default=256)
parser.add_argument('--epochs', dest='epochs', type=int, default=100)
parser.add_argument('--learning_rate', dest='learning_rate', type=float, default=0.001)
parser.add_argument('--wd', dest='wd', type=float, default=1e-5)
parser.add_argument('--batch_size', dest='batch_size', type=int, default=64)

parser.add_argument('--train', dest='train', type=bool, default=True)
parser.add_argument('--load_epoch', dest='load_epoch', type=int, default=29)

args = parser.parse_args()
1
2
3
# 경로 생성
if not os.path.isdir(args.model_dir) :
    os.ma   kedirs(args.model_dir)
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
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
# 파이토치 agrs.Dataset 생성 for Train / Test
class TrainDataset(Dataset) :
    def __init__(self, args) :
        self.train_dir = args.train_dir
        self.train_csv_dir = args.train_csv_dir
        self.train_csv_exist_dir = args.train_csv_exist_dir
        self.args = args
        self.train_image = list()
        self.train_label = list()
        if not os.path.isfile(self.train_csv_exist_dir) :
            self.train_csv = pd.read_csv(self.train_csv_dir)
            self.train_csv_exist = self.train_csv.copy()
            self.load_full_data()
            self.train_csv_exist.to_csv(self.train_csv_exist_dir, index=False)
        else :
            self.load_exist_data()

    def load_full_data(self) :
        for i in tqdm(range(len(self.train_csv))) :
            filename = self.train_csv['id'][i]
            fullpath = glob(self.train_dir + "*/*/" + filename.replace('[', '[[]') + ".JPG")[0]
            label = self.train_csv['landmark_id'][i]
            self.train_csv_exist.loc[i,'id'] = fullpath
            self.train_image.append(fullpath)
            self.train_label.append(label)


    def load_exist_data(self) :
        self.train_csv_exist = pd.read_csv(self.train_csv_exist_dir)
        for i in tqdm(range(len(self.train_csv_exist))) :
            fullpath = self.train_csv_exist['id'][i]
            label = self.train_csv_exist['landmark_id'][i]
            self.train_image.append(fullpath)
            self.train_label.append(label)


    def __len__(self) :
        return len(self.train_image)

    def __getitem__(self, idx) :
        image = Image.open(self.train_image[idx])
        image = image.resize((self.args.image_size, self.args.image_size))
        image = np.array(image) / 255.
        image = np.transpose(image, axes=(2, 0, 1))
        label = self.train_label[idx]

        return {'image' : image, 'label' :label}

class TestDataset(Dataset) :
    def __init__(self, args) :
        self.test_dir = args.test_dir
        self.test_csv_dir = args.test_csv_dir
        self.test_csv_exist_dir = args.test_csv_exist_dir
        self.args = args
        self.test_image = list()
        self.test_label = list()
        if not os.path.isfile(self.test_csv_exist_dir) :
            self.test_csv = pd.read_csv(self.test_csv_dir)
            self.test_csv_exist = self.test_csv.copy()
            self.load_full_data()
            self.test_csv_exist.to_csv(self.test_csv_exist_dir, index=False)
        else :
            self.load_exist_data()

    def load_full_data(self) :
        for i in tqdm(range(len(self.test_csv))) :
            filename = self.test_csv['id'][i]
            fullpath = glob(self.test_dir + "*/" + filename.replace('[', '[[]') + ".JPG")[0]
            label = self.test_csv['id'][i]

            self.test_csv_exist.loc[i,'id'] = fullpath
            self.test_image.append(fullpath)
            self.test_label.append(label)


    def load_exist_data(self) :
        self.test_csv_exist = pd.read_csv(self.test_csv_exist_dir)
        for i in tqdm(range(len(self.test_csv_exist))) :
            fullpath = self.test_csv_exist['id'][i]
            label = self.test_csv_exist['id'][i]

            self.test_image.append(fullpath)
            self.test_label.append(label)


    def __len__(self) :
        return len(self.test_image)

    def __getitem__(self, idx) :
        image = Image.open(self.test_image[idx])
        image = image.resize((self.args.image_size, self.args.image_size))
        image = np.array(image) / 255.
        image = np.transpose(image, axes=(2, 0, 1))
        label = self.test_label[idx]

        return {'image' : image, 'label' :label}


# DataLoader 생성을 위한 collate_fn
def collate_fn(batch) :
    image = [x['image'] for x in batch]
    label = [x['label'] for x in batch]

    return torch.tensor(image).float().cuda(), torch.tensor(label).long().cuda()

def collate_fn_test(batch) :
    image = [x['image'] for x in batch]
    label = [x['label'] for x in batch]

    return torch.tensor(image).float().cuda(), label

# Dataset, Dataloader 정의
train_dataset = TrainDataset(args)
test_dataset = TestDataset(args)
train_dataloader = DataLoader(train_dataset, batch_size=args.batch_size, shuffle=True, collate_fn=collate_fn)
test_dataloader = DataLoader(test_dataset, batch_size=1, shuffle=False, collate_fn=collate_fn_test)
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
# glob.glob 사용하여 dataset 설정하는 방법
class LandmarkDataset(Dataset):
    def __init__(self, mode: str = 'train', transforms: transforms = None):
        self.mode = mode
        self.image_ids = glob.glob(f'./data/{mode}/**/**/*')
        if self.mode == 'train':
            with open('./data/train.csv') as f:
                labels = list(csv.reader(f))[1:]
                self.labels = {label[0]: int(label[1]) for label in labels}

        self.transforms = transforms

    def __len__(self):
        return len(self.image_ids)

    def __getitem__(self, index: int) -> Tuple[Tensor]:
        image = Image.open(self.image_ids[index]).convert('RGB')
        image_id = os.path.splitext(os.path.basename(self.image_ids[index]))[0]
        if self.transforms is not None:
            image = self.transforms(image)
        
        if self.mode == 'train':
            label = self.labels[image_id]
            return image, label
        else:
            return image_id, image


# transform
transforms_train = transforms.Compose([
    transforms.Resize((224, 224)),
    transforms.RandomHorizontalFlip(),
    transforms.RandomChoice([
        transforms.ColorJitter(0.2, 0.2, 0.2, 0.2),
        transforms.RandomResizedCrop(224),
        transforms.RandomAffine(
            degrees=15, translate=(0.2, 0.2),
            scale=(0.8, 1.2), shear=15, resample=Image.BILINEAR)
    ]),
    transforms.ToTensor(),
    transforms.Normalize((0.4452, 0.4457, 0.4464), (0.2592, 0.2596, 0.2600)),
])

transforms_test = transforms.Compose([
    transforms.Resize((224, 224)),
    transforms.ToTensor(),
    transforms.Normalize((0.4452, 0.4457, 0.4464), (0.2592, 0.2596, 0.2600)),
])

# dataloader
trainset = LandmarkDataset('train', transforms_train)
testset = LandmarkDataset('test', transforms_test)

train_loader = DataLoader(
    trainset, batch_size=batch_size, shuffle=True, num_workers=num_workers)
test_loader = DataLoader(
    testset, batch_size=16, shuffle=False, num_workers=num_workers)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
# Model
# 여기서는 간단한 CNN 3개짜리 모델을 생성하였습니다.
class Network(nn.Module) :
    def __init__(self) :
        super(Network, self).__init__()
        self.conv1 = Conv2d(3, 64, (3,3), (1,1), (1,1))
        self.conv2 = Conv2d(64, 64, (3,3), (1,1), (1,1))
        self.conv3 = Conv2d(64, 64, (3,3), (1,1), (1,1))
        self.fc = Linear(64, 1049)

    def forward(self, x) :
        x = F.relu(self.conv1(x))
        x = F.relu(self.conv2(x))
        x = F.relu(self.conv3(x))
        x = AdaptiveAvgPool2d(1)(x).squeeze()
        x = self.fc(x)
        return x

model = Network()
model.cuda()

criterion = nn.CrossEntropyLoss()
optimizer = optim.Adam(model.parameters(), lr=args.learning_rate, weight_decay=args.wd)
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
# args.Training
# 매 epoch마다 ./ckpt 파일에 모델이 저장됩니다.
# validation dataset 없이 모든 train data를 train하는 방식입니다.
if args.train :
    model.train()
    for epoch in range(args.epochs) :
        epoch_loss = 0.
        for iter, (image, label) in enumerate(train_dataloader) :
            pred = model(image)
            loss = criterion(input=pred, target=label)

            optimizer.zero_grad()
            loss.backward()
            optimizer.step()
            epoch_loss += loss.detach().item()
            print('epoch : {0} step : [{1}/{2}] loss : {3}'.format(epoch, iter, len(train_dataloader), loss.detach().item()))
        epoch_loss /= len(train_dataloader)
        print('\nepoch : {0} epoch loss : {1}\n'.format(epoch, epoch_loss))

        torch.save(model.state_dict(), args.model_dir + "epoch_{0:03}.pth".format(epoch))
    # 모든 epoch이 끝난 뒤 test 진행
    model.eval()
    submission = pd.read_csv(args.test_csv_dir)
    for iter, (image, label) in enumerate(test_dataloader):
        pred = model(image)
        pred = nn.Softmax(dim=1)(pred)
        pred = pred.detach().cpu().numpy()
        landmark_id = np.argmax(pred, axis=1)
        confidence = pred[0,landmark_id]
        submission.loc[iter, 'landmark_id'] = landmark_id
        submission.loc[iter, 'conf'] = confidence
    submission.to_csv(args.test_csv_submission_dir, index=False)

# Test
# argument의 --train을 False로 두면 Test만 진행합니다.
# Softmax로 confidence score를 계산하고, argmax로 class를 추정하여 csv 파일로 저장합니다.
# 현재 batch=1로 불러와서 조금 느릴 수 있습니다.
else :
    model.load_state_dict(torch.load(args.model_dir + "epoch_{0:03}.pth".format(args.load_epoch)))
    model.eval()
    submission = pd.read_csv(args.test_csv_dir)
    for iter, (image, label) in enumerate(test_dataloader):
        pred = model(image)
        pred = nn.Softmax(dim=1)(pred)
        pred = pred.detach().cpu().numpy()
        landmark_id = np.argmax(pred, axis=1)
        confidence = pred[0,landmark_id]
        submission.loc[iter, 'landmark_id'] = landmark_id
        submission.loc[iter, 'conf'] = confidence
    submission.to_csv(args.test_csv_submission_dir, index=False)
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
def train(
    model: nn.Module, data_loader: DataLoader, criterion: nn.Module, 
    optimizer: optim, scheduler: optim.lr_scheduler, device: torch.device):
    
    epoch_size = len(trainset) // batch_size
    num_epochs = math.ceil(max_iter / epoch_size)
    
    iteration = 0
    losses = AverageMeter() #
    scores = AverageMeter() # 
    corrects = AverageMeter() #
    
    model.train()
    for epoch in range(num_epochs):
        if (epoch+1)*epoch_size < iteration:
            continue
            
        if iteration == max_iter:
            break
            
        correct = 0
        for inputs, labels in data_loader:
            inputs = inputs.to(device)
            labels = labels.to(device)
            outputs = model(inputs)
            loss = criterion(outputs, labels)
            confs, preds = torch.max(outputs.detach(), dim=1)
            
            optimizer.zero_grad()
            
            loss.backward()
            optimizer.step()

            losses.update(loss.item(), inputs.size(0))
            scores.update(gap(preds, confs, labels), inputs.size(0))
            corrects.update((preds == labels).float().sum(), input_size(0))
            
            iteration += 1

            if iteration % args.verbose_eval == 0:
                print(f'[{epoch+1}/{iteration}] Loss: {losses.val:.4f}' \
                      f' Acc: {corrects.val:.4f} GAP: {scores.val:.4f}')
         
            if iteration in [20000, 70000, 140000]:
                scheduler.step()

def test(model: nn.Module, data_loader: DataLoader, device: torch.device):
    submission = pd.read_csv('./data/sample_submission.csv', index_col='id')
                             
    model.eval()
    for image_id, inputs in data_loader:
        inputs = inputs.to(device)
        outputs = model(inputs)
        outputs = nn.Softmax(dim=1)(outputs)

        landmark_ids = np.argmax(outputs, axis=1)
        confidence = outputs[0, landmark_ids]
        submission.loc[image_id, 'landmark_id'] = landmark_ids
        submission.loc[image_id, 'conf'] = confidence
        
    submission.to_csv('submission.csv')
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
# FocalLoss
import torch
from torch import nn
import math

class FocalLoss(nn.Module):

    def __init__(self, gamma=0, eps=1e-7):
        super(FocalLoss, self).__init__()
        self.gamma = gamma
        #print(self.gamma)
        self.eps = eps
        self.ce = torch.nn.CrossEntropyLoss(reduction="none")

    def forward(self, input, target):
        logp = self.ce(input, target)
        p = torch.exp(-logp)
        loss = (1 - p) ** self.gamma * logp
        return loss.mean()
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
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
import numpy as np
import matplotlib.pyplot as plt

import torch
import torch.nn as nn
import torch.nn.functional as F
from torchvision import transforms, datasets

if torch.cuda.is_available():
    DEVICE = torch.device('cuda')
else:
    DEVICE = torch.device('cpu')
print('Using PyTorch version:', torch.__version__, ' Device:', DEVICE)

BATCH_SIZE = 32
EPOCHS = 10

train_dataset = datasets.CIFAR10(root = "../data/CIFAR_10",
                                  train = True,
                                  download = True,
                                  transform = transforms.Compose([                            # 불러오는 이미지 데이터에 전처리 및 augmentation을 다양하게 적용할 때 이용하는 메서드
                                    transforms.RandomHorizontalFlip(),                        # 해당 이미지를 50%의 확률로 좌우 반전하는 것을 의미
                                    transforms.ToTensor(),                                    # 0에서 1사이의 값으로 정규화하며 딥러닝 모델의 input으로 이용될 수 있도록 tensor 형태로 변환
                                    transforms.Normalize((0.5, 0.5, 0.5), (0.5, 0.5, 0.5))])) # totensor 형태로 전환된 이미지에 대해 또 다른 정규화를 진행하는 것을 의미
                                                                                              # 정규화를 진행할 때는 평균과 표준편차가 필요한데 rgb순으로 평균을 0.5씩 적용

test_dataset = datasets.CIFAR10(root = "../data/CIFAR_10",
                                train = False,
                                transform = transforms.Compose([
                                    transforms.RandomHorizontalFlip(),
                                    transforms.ToTensor(),
                                    transforms.Normalize((0.5, 0.5, 0.5), (0.5, 0.5, 0.5))]))

train_loader = torch.utils.data.DataLoader(dataset = train_dataset,
                                            batch_size = BATCH_SIZE,
                                            shuffle = True)

test_loader = torch.utils.data.DataLoader(dataset = test_dataset,
                                          batch_size = BATCH_SIZE,
                                          shuffle = False)

for (X_train, y_train) in train_loader:
    print('X_train:', X_train.size(), 'type:', X_train.type())
    print('y_train:', y_train.size(), 'type:', y_train.type())
    break

class CNN(nn.Module):
    def __init__(self):
        super(CNN, self).__init__()
        self.conv1 = nn.Conv2d(in_channels = 3, out_channels = 8, kernel_size = 3, padding = 1)
        self.conv2 = nn.Conv2d(in_channels = 8, out_channels = 16, kernel_size = 3, padding = 1)
        self.pool = nn.MaxPool2d(kernel_size = 2, stride = 2)
        self.fc1 = nn.Linear(8 * 8 * 16, 64)
        self.fc2 = nn.Linear(64, 32)
        self.fc3 = nn.Linear(32, 10)
        
    def forward(self, x):
        x = self.conv1(x)
        x = F.relu(x)
        x = self.pool(x)
        x = self.conv2(x)
        x = F.relu(x)
        x = self.pool(x)
        
        x = x.view(-1, 8 * 8 * 16)
        x = self.fc1(x)
        x = F.relu(x)
        x = self.fc2(x)
        x = F.relu(x)
        x = self.fc3(x)
        x = F.log_softmax(x)
        return x


model = CNN().to(DEVICE)
optimizer = torch.optim.Adam(model.parameters(), lr = 0.001)
criterion = nn.CrossEntropyLoss()

#ㅡㅡㅡ IMAGENET 데이터로 학습이 된 ResNet34 모델을 불러온 후 Fine Tuning 해보기 ㅡㅡㅡ
model = models.resnet34(pretrained = True)                                                # pretrained = true는 imageNet 데이터를 잘 분류할 수 있도록 학습된 파라미터를 resnet34 모델에 적용해 불러오는 것을 의미
num_ftrs = model.fc.in_features                                                           # CIFAR-10 데이터를 분류하기 위해 최종 output의 벡터를 10 크기로 설정해야 한다. 
                                                                                          # CIFAR-10 데이터의 클래스 종류는 10개이므로 각 클래스를 포현하는 원핫인코딩 값의 크기가 10이기 때문이다.
model.fc = nn.Linear(num_ftrs, 10)                                                  
model = model.cpu()
optimizer = torch.optim.Adam(model.parameters(), lr = 0.001)                          

for epoch in range(1, EPOCHS + 1):                                                        # ImageNet 데이터에 학습이 완료된, 즉 학습을 통해 얻게 된 파라미터를 resnet34 모델의 초기 파라미터로 설정한 후 
                                                                                          # CIFAR-10 이미지 데이터를 10개의
    train(model, train_loader, optimizer, log_interval = 200)                             # 클래스로 분류할 수 있도록 기존 실습 내용과 동일한 환경으로 실험을 진행
    test_loss, test_accuracy = evaluate(model, test_loader)
    print("\n[EPOCH: {}], \tTest Loss: {:.4f}, \tTest Accuracy: {:.2f} % \n".format(
        epoch, test_loss, test_accuracy))

# ㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡ


def train(model, train_loader, optimizer, log_interval):
    model.train()
    for batch_idx, (image, label) in enumerate(train_loader):
        image = image.to(DEVICE)
        label = label.to(DEVICE)
        optimizer.zero_grad()
        output = model(image)
        loss = criterion(output, label)
        loss.backward()
        optimizer.step()

        if batch_idx % log_interval == 0:
            print("Train Epoch: {} [{}/{} ({:.0f}%)]\tTrain Loss: {:.6f}".format(
                epoch, batch_idx * len(image), 
                len(train_loader.dataset), 100. * batch_idx / len(train_loader), 
                loss.item()))

def evaluate(model, test_loader):
    model.eval()
    test_loss = 0
    correct = 0

    with torch.no_grad():
        for image, label in test_loader:
            image = image.to(DEVICE)
            label = label.to(DEVICE)
            output = model(image)
            test_loss += criterion(output, label).item()
            prediction = output.max(1, keepdim = True)[1]
            correct += prediction.eq(label.view_as(prediction)).sum().item()
    
    test_loss /= (len(test_loader.dataset) / BATCH_SIZE)
    test_accuracy = 100. * correct / len(test_loader.dataset)
    return test_loss, test_accuracy

for epoch in range(1, EPOCHS + 1):
    train(model, train_loader, optimizer, log_interval = 200)
    test_loss, test_accuracy = evaluate(model, test_loader)
    print("\n[EPOCH: {}], \tTest Loss: {:.4f}, \tTest Accuracy: {:.2f} % \n".format(
        epoch, test_loss, test_accuracy))

Reference

This post is licensed under CC BY 4.0 by the author.