테슬라 AI Day 자료를 보던 중 테슬라는 Backbone으로 regnet을, Neck으로 BiFPN을 사용하여 객체인지를 하고 있음을 알게 되었다.
테슬라의 Vision Network
현재 자율주행 자동차 회사에서 실제 사용하는 모델이라는 사실이 흥미로웠고, 자세한 구조나 구현방식에 대한 정보는 없어서 직접 regnet과 BiFPN을 이용한 이미지 분류 모델을 만드는 프로젝트를 시작하게 되었다. (원래는 데이콘 데이터를 활용한 객체 탐지를 목표하였으나 ram용량으로 인한 에러가 발생하여 cifar10 dataset을 이용한 이미지 분류로 목표를 수정하였다.)
프로젝트 계획
1. regnet과 BiFPN에 대한 배경지식이 없기에 이들의 탄생 목적과 작동 과정을 학습한다.
2. 학습한 개념을 바탕으로 regnet과 BiFPN의 코드를 이해하고 목적에 맞게 변형 및 추가하여 이미지 분류 모델을 만든다.
3. train 과 test과정을 거치며 모델이 정상적으로 작동하는지 확인하고, 문제가 발생시 해결해 나간다.
4.결과 및 아쉬운 점
1.regnet 및 BiFPN 학습
regnet 의 탄생목적
기존의 네트워크 구조들은 구체적인 특정 상황에 대하여 설계된 고정된 구조를 가졌다. 이 네트워크들은 해당 상황에 대해서는 잘 작동하지만, 새로운 상황에 대해서는 대처능력이 떨어지는 문제가 있었다. 이러한 범용성이 떨어지는 기존의 고정된 네트워크들의 문제를 해결하기 위한 목적으 파라메터를 통해 깊이, 너비, 해상도 등의 구조를 쉽게 변형할 수 있도록 설계된 가변형 네트워크 구조가 regnet이다. 이러한 가변성을 통해 regnet은 다양한 상황에 대한 범용성을 갖는다.
regnet 의 구조 및 작동 과정
Stem Layer (시작 레이어): 입력 데이터를 받아들이고 초기 특징을 추출하는 역할을 합니다.
Stage (스테이지): 모델의 주요 블록으로, 각 스테이지는 여러 레이어로 구성되어 있습니다. 깊이, 너비, 해상도 등이 각 스테이지에서 조절됩니다.
Bottleneck Blocks (병목 블록): ResNet과 유사한 병목 아키텍처를 사용하여, 모델의 효율성을 높이고 계산 비용을 줄입니다.
Width Scaling (너비 확장): 각 스테이지에서 블록의 채널 수를 조절하여 네트워크의 너비를 조절합니다.
Depth Scaling (깊이 확장): 스테이지의 수를 조절하여 전체 네트워크의 깊이를 조절합니다.
이미지 상에는 서로 다른 크기의 객체가 존재하기 때문에 다양한 scale의 feature를 참조하는 것이 효과적이다. 이를 위해 기존에 사용되던 FPN(Feature Pyramid Network)는 scale에 따라 feature를 피라미드 형태로 쌓은 후 낮은 scale의 feature를 높은 scale의 feature에 더하는 방식으로 feature fusion을 진행한다. 이 방식은 각 feature 별로 BiFPN의 output에 미치는 영향력의 크기가 다름에도 단순히 단방향으로 더하는 문제점이 있으며 이를 개선하기 위해 각 feature별로 가중치를 다르게 적용하고, 양방향으로 더하는 BiFPN층이 만들어졌다.
FPN의 구조
BiFPN의 구조 및 작동 과정
BiFPN구조
Bottom-Up Pathway:
이미지에서 추출된 특성 맵은 다양한 스케일에서 생성됩니다.
Top-Down Pathway:
Bottom-Up Pathway에서 생성된 특성 맵은 상위 수준의 스케일에서 업샘플링됩니다.
Bi-directional & lateral connection 을 통한 Feature Fusion :
각 레이어에서, 해당 레이어의 특성 맵과 다운샘플링된 특성 맵, 업샘플링된 샘플링을 가중치에 따라 결합한 후(Bi-directional connection), 해당 레이어 특성 맵의 edge를 가중치에 따라 추가한다(lateral connection).
nn.AdaptiveAvgPool2d(output_size=(1, 1)), # 공간적인 정보를 보존하기 위해 사용한다.
nn.Flatten(),
nn.ReLU(),
nn.Linear(80,32),
nn.ReLU(),
nn.Linear(32,10),
)
lr=1e-5 #학습률 설정
parameters = list(model.parameters())
optim = Adam(parameters,lr=lr) # 옵티마이저 설정
3.모델 학습, 테스트 및 문제 발생 시 해결하기
사용 데이터 : cifar10
모델 학습 및 테스트
문제 1. inplace error 발생
모델 학습을 시작하니 " RuntimeError: one of the variables needed for gradient computation has been modified by an inplace operation: [torch.FloatTensor [3, 4]], which is output 0 of ReluBackward0, is at version 1; expected version 0 instead. Hint: enable anomaly detection to find the operation that failed to compute its gradient, with torch.autograd.set_detect_anomaly(True). "
에러가 발생하였다. 이는 Autograd가 기울기를 계산하는 동안 어떤 변수가 inplace 연산으로 수정되었거나 다른 곳에서 수정되었음을 의미한다. 일반적으로 PyTorch의 Autograd는 inplace 연산을 추적할 수 없기 때문에 inplace 연산을 사용하는 경우에는 gradient 계산에 문제가 발생할 수 있다. 따라서 오류를 해결하려면 inplace연산이 일어나지 않게 코드를 수정하여야 한다. " [torch.FloatTensor [3, 4]] " 정보를 통해
# TODO: Init weights
self.w1 = nn.Parameter(torch.Tensor(2,4))
self.w1_relu = nn.ReLU()
self.w2 = nn.Parameter(torch.Tensor(3,4))
self.w2_relu = nn.ReLU()
defforward(self, inputs):
p3_x, p4_x, p5_x, p6_x, p7_x = inputs
# Calculate Top-Down Pathway
w1 = self.w1_relu(self.w1)
w1 /= torch.sum(w1, dim=0) + self.epsilon
w2 = self.w2_relu(self.w2)
w2 /= torch.sum(w2, dim=0) + self.epsilon
이 부분의 코드에 문제가 있다고 판단하였다.
문제 1에 대한 해결시도 1. clone() 사용하여 연산하기 전에 텐서의 독립적인 복사본을 생성한다.
# TODO: Init weights
self.w1 = nn.Parameter(torch.Tensor(2,4))
self.w1_1 = self.w1.clone()
self.w1_relu = nn.ReLU()
self.w2 = nn.Parameter(torch.Tensor(3,4))
self.w2_1 =self.w2.clone()
self.w2_relu = nn.ReLU()
defforward(self, inputs):
p3_x, p4_x, p5_x, p6_x, p7_x = inputs
# Calculate Top-Down Pathway
w1 = self.w1_relu(self.w1_1)
w1 /= torch.sum(w1, dim=0) + self.epsilon
w2 = self.w2_relu(self.w2_1)
w2 /= torch.sum(w2, dim=0) + self.epsilon
결과. 여전히 inplace error 발생
문제 1에 대한 해결시도 2. detach() 사용하여 텐서의 gradients를 분리하여 gradient 계산을 수행하지 않도록 한다.
# TODO: Init weights
self.w1 = nn.Parameter(torch.Tensor(2,4))
self.w1_1 = self.w1.detach()
self.w1_relu = nn.ReLU()
self.w2 = nn.Parameter(torch.Tensor(3,4))
self.w2_1 = self.w2.detach()
self.w2_relu = nn.ReLU()
defforward(self, inputs):
p3_x, p4_x, p5_x, p6_x, p7_x = inputs
# Calculate Top-Down Pathway
w1 = self.w1_relu(self.w1_1)
w1 /= torch.sum(w1, dim=0) + self.epsilon
w2 = self.w2_relu(self.w2_1)
w2 /= torch.sum(w2, dim=0) + self.epsilon
결과. inplace error 발생 문제는 해결되었다. 그러나 모델의 훈련 정확도가 0.2 이상으로 올라가지 않는 문제가 발생하였다.
문제 2. 모델의 훈련 정확도가 0.2 이하
원인추론. 파라미터인 self.w1 과 self.w2의 값이 훈련과정에서 변하지 않음이 발견되었고, 이로 인한 정확도 문제가 발생했을 것이다.
파라미터의 값이 훈련을 진행하였음에도 변하지 않았다.
문제 1에 대한 해결시도 2는 inplace error는 해결하였으나 파라미터의 값을 변하지 않게 만드는 문제가 있다. 따라서, inplace error도 해결하는 동시에 파라미터의 값을 변하도록 놔두는 해결 방법이 필요하다.
문제 2에 대한 해결시도 1. 연산자 축약 방식을 사용하지 않는다.
stackoverflow에서 찾은 해결방법
stackoverflow에서 나와 같은 문제에 대한 질문을 여러 개 찾아보았고 그 중 연산자 축약을 사용한 코드를 수정하여 문제를 해결하는 방법을 발견하였다. 이 방법을 적용해보았다.
# TODO: Init weights
self.w1 = nn.Parameter(torch.Tensor(2,4))
self.w1_relu = nn.ReLU()
self.w2 = nn.Parameter(torch.Tensor(3,4))
self.w2_relu = nn.ReLU()
defforward(self, inputs):
p3_x, p4_x, p5_x, p6_x, p7_x = inputs
# Calculate Top-Down Pathway
w1 = self.w1_relu(self.w1)
#w1 /= torch.sum(w1, dim=0) + self.epsilon <== 연산자 축약 방식의 코드
w1 = w1 / ( torch.sum(w1,dim=0)+self.epsilon )
w2 = self.w2_relu(self.w2)
# w2 /= torch.sum(w2, dim=0) + self.epsilon <== 연산자 축약 방식의 코드
w2 = w2 / ( torch.sum(w2, dim=0) + self.epsilon )
결과. inplace error 문제와 self.w1, self.w2가 변하지 않던 문제 둘 다 해결되었다. 그러나 여전히 모델의 훈련 정확도가 0.2 이상으로 올라가지 않았다.
epoch가 진행됨에 따라 self.w1, self.w2의 값이 변화하였다.
문제 2에 대한 해결시도 2. model에서 nn.AdaptiveAvgPool2d(output_size=(1,1)) 층을 제거한다.
nn.AdaptiveAvgPool2d(output_size=(1,1)) 층은 특성 맵의 공간적인 구조를 유지해주는 장점이 있으나, 정보 손실을 발생시키는 단점이 존재한다. 현 상황에서는 장점보다는 단점이 크다고 판단하여 제거하였다.
훈련 정확도는accuracy:0.81508를 기록하였으나 테스트 정확도는Accuracy:0.5499를 기록하였다. 과적합 문제를 해결하기 위해 Dropout층 추가, 은닉층의 노드 수 조절,L2 regularization,BatchNormalization 을 시도하였으나 해결되지 않았다. 더 다양한 조합을 시도하고 싶지만 학습에 걸리는 시간이 길어서 다양하게 시도할 수 없다는 것이 아쉽다. 비록 과적합 문제는 해결하지 못했지만 inplace error, 파라메터가 학습되지 않아서 훈련 정확도가 올라가지 않는 문제를
연산자 축약 방식 사용하지 않음,nn.AdaptiveAvgPool2층 제거를 통해 해결하는 방법을 알아냈다. 조금 더 여유로운 시기가 되면 Dropout층 개수와 비율, 은닉층의 노드 수, L2 regularization의 비율을 조절하여 과적합 문제를 해결하려고 한다.