데이터셋은 다음 링크의 MNIST 데이터를 사용하였다.
https://www.kaggle.com/competitions/digit-recognizer/data
Dataset

데이터셋이 위와 같은 .csv 파일이므로 이미지 출력을 위해 2차원 array로 만들어 준다.
def show_img_from_df(df, index):
plt.imshow(np.reshape(np.array(df.iloc[index, 1:]), (28, 28)), cmap="gray")
show_img_from_df(train_df, 17)

Object Detection 하기 위한 새로운 이미지를 생성한다. 아이디어는 다음과 같다.
- (90, 90) 사이즈의 새로운 빈 이미지를 생성한다.
- 사이즈 범위 내의 랜덤한 (x, y) 좌표를 정한 뒤 원래의 이미지를 해당 위치에 넣는다.
- 원래 이미지의 테두리를 bounding box로 설정한다.
new_size = 90
df = train_df
ind = randrange(1000)
img = np.reshape(np.array(df.iloc[ind, 1:]), (28, 28))
# create the new image
new_img = np.zeros((new_size, new_size))
# randomly select a bottom left corner to use for img
x_min = randrange(new_size - img.shape[0])
y_min = randrange(new_size - img.shape[0])
x_max = x_min + img.shape[0]
y_max = y_min + img.shape[0]
x_center = x_min + (x_max - x_min) / 2
y_center = y_min + (y_max - y_min) / 2
new_img[x_min:x_max, y_min:y_max] = img
new_img = cv2.rectangle(new_img, (y_max, x_min), (y_min, x_max), 255, 1)
plt.imshow(new_img, cmap="gray")
plt.show()

생성된 이미지로 새로운 dataset을 생성한다.
class CustomDataset(Dataset):
def __init__(self, df):
self.df = df
def __getitem__(self, index):
image = np.reshape(np.array(self.df.iloc[index, 1:]), (28, 28)) / 255.
# create the new image
new_img = np.zeros((90, 90))
# randomly select a bottom left corner to use for img
x_min = randrange(90 - image.shape[0])
y_min = randrange(90 - image.shape[0])
x_max = x_min + image.shape[0]
y_max = y_min + image.shape[0]
new_img[x_min:x_max, y_min:y_max] = image
new_img = np.reshape(new_img, (1, 90, 90))
label = [int(self.df.iloc[index, 0]), np.array([y_max, x_min, y_min, x_max]).astype("float32")]
return new_img, label
def __len__(self):
return len(self.df)
Model
- 모델 끝단에class(digit) / bounding box를 예측하는 fully-connected-layer를 따로 둔다.
- class : 0 ~ 9 까지의 숫자 예측
- bounding box : matplotlib 기준 좌하단, 우상단 좌표 예측
class CustomNet(nn.Module):
def __init__(self):
super(CustomNet, self).__init__()
self.conv0 = nn.Conv2d(1, 16, 3, padding=2)
self.pool0 = nn.MaxPool2d(2, 2)
self.conv1 = nn.Conv2d(16, 16, 3, padding=3)
self.pool1 = nn.MaxPool2d(2, 2)
self.flatten = nn.Flatten()
self.linear_relu_stack = nn.Sequential(
nn.Linear(16*25*25, 256),
nn.ReLU()
)
self.linear = nn.Linear(256, 10) # class(digit) layer
self.linear_bbox = nn.Linear(256, 4) # bounding box layer
def forward(self, x):
x = self.conv0(x)
x = self.pool0(x)
x = F.relu(x)
x = self.conv1(x)
x = self.pool1(x)
x = F.relu(x)
x = self.flatten(x)
x = self.linear_relu_stack(x)
logits = self.linear(x) # class(digit)
bbox = self.linear_bbox(x) # bounding box
return logits, bbox
Training
파라미터 세팅은 다음과 같다.
- alpha : 100
- beta : 1
- class(digit) loss : CrossEntropy
- bounding box loss : MSE (Mean Squared Error)
- 전체 Loss : (alpha * class loss) + (beta * bounding box loss)
- optimizer : AdamW
- learning rate : 1e-3
loss_fn = nn.CrossEntropyLoss()
loss_mse = nn.MSELoss()
optimizer = optim.AdamW(model.parameters(), lr=1e-3)
alpha = 100
beta = 1
def train(model, dataloader, loss_fn, loss_mse, optimizer, alpha, beta):
model.train()
size = len(dataloader.dataset)
loss_cls_list = []
loss_point_list = []
for batch, (inputs, labels) in enumerate(dataloader):
inputs = inputs.to(device)
label_class = labels[0].to(device)
label_point = labels[1].to(device)
pred_cls, pred_point = model(inputs.float())
# 종합 loss
loss = alpha*loss_fn(pred_cls, label_class) + beta*loss_mse(pred_point, label_point.float())
# class(digit) loss
loss_cls = loss_fn(pred_cls, label_class)
# bounding box loss
loss_point = loss_mse(pred_point, label_point)
optimizer.zero_grad()
loss.backward()
optimizer.step()
# 100 batch 마다 출력
if (batch != 0) and (batch % 100 == 0):
loss = loss.item()
current = batch*len(inputs)
loss_cls = loss_cls.item()
loss_point = loss_point.item()
loss_cls_list.append(loss_cls)
loss_point_list.append(loss_point)
print(f"main loss: {loss:>7f} [{current:>5d}/{size:5d}]")
print(f"Digit prediction loss: {loss_cls:>7f} [{current:>5d}/{size:>5d}]")
print(f"Coordinate prediction loss: {loss_point:>7f} [{current:>5d}/{size:>5d}]")
print("-"*20)
Test
- 기존의 CustomDataset과 거의 유사하지만 label이 존재하지 않는다.
class TestDataset(Dataset):
def __init__(self, df):
self.df = df
def __getitem__(self, index):
image = np.reshape(np.array(self.df.iloc[index, :]), (28, 28)) / 255.
# create the new image
new_img = np.zeros((90, 90))
# randomly select a bottom left corner to use for img
x_min = randrange(90 - image.shape[0])
x_max = x_min + image.shape[0]
y_min = randrange(90 - image.shape[0])
y_max = y_min + image.shape[0]
new_img[x_min:x_max, y_min:y_max] = image
new_img = np.reshape(new_img, (1, 90, 90))
return new_img
def __len__(self):
return len(self.df)
test dataset을 생성한 뒤 학습된 모델을 테스트 한다.
inputs = next(iter(test_dataloader))
index = randrange(63)
with torch.no_grad():
pred_cls, pred_point = model(inputs.to(device).float())
pred_cls = np.argmax(pred_cls[index].cpu().detach().numpy())
img = np.reshape(inputs[index].cpu().detach().numpy(), (90, 90))
y_max, x_min, y_min, x_max = int(pred_point[index][0]), int(pred_point[index][1]), int(pred_point[index][2]), int(pred_point[index][3])
new_img = img.copy()
# 다음 line을 실행하면 숫자는 안보이고 bounding box만 그려진다. *해결못함*
# new_img = cv2e.rectangle(new_img, (y_max, x_min), (y_min, x_max), 255, 1)
plt.imshow(new_img, cmap="gray")
plt.plot(y_max, x_min, "or", markersize=10)
plt.plot(y_min, x_max, "og", markersize=10)
plt.show()
print("Digit:", int(pred_cls))
print(f"{y_max}, {x_min}, {y_min}, {x_max}")
어느정도 잘 동작하는 것을 볼 수 있다.

<전체 코드>
object_detection_custom_model.ipynb
0.08MB
📌 Reference
MNIST object detection
Explore and run machine learning code with Kaggle Notebooks | Using data from Digit Recognizer
www.kaggle.com
'ML & DL > 컴퓨터 비전' 카테고리의 다른 글
Custom Dataset으로 YOLOv5/YOLOv7 학습하기 (22.12.07 Update) (0) | 2022.12.07 |
---|---|
[OCR] deep-text-recognition-benchmark (Custom Data로 학습하기) (0) | 2022.01.22 |