|
|
https://github.com/pytorch/vision/blob/main/references/classification/train.py
분류 예제 코드 실행을 위해 수정해야 하는 부분
1. 출력 클래스 수 변경
원본 코드의 248번 째 코드는 다음과 같이 되어 있다.
model = torchvision.models.get_model(args.model, weights=args.weights, num_classes=num_classes)
여기서 num_classes = len(dataset.classes) 으로 되어 있기 때문에 num_classes 안에는 2가 저장될 것이다. (개 / 고양이)
근데 불러오려는 모델은 IMAGENET1K_V1 으로 훈련된 모델이므로 마지막 fc층의 출력값이 1000임.
(ImageNet 데이터셋이 1000개의 클래스를 포함하기 때문에. IMAGENET1K_V1에서 1K = 1000개의 클래스를 의미하는거같음)
다음과 같이 코드를 수정한 뒤에 실행하면 수정 전의 모델의 구조를 볼 수 있다.
따라서 마지막 출력층을 목적에 맞게 수정해줘야 함. 방법은 fc층의 출력값을 1000 → 2로 바꾸는 것
2. 컨볼루션 레이어의 가중치 고정
전이 학습을 위해서는 마지막 층을 제외하고 네트워크(층)를 고정시켜야 함. (사전 학습 모델의 특징 추출 능력을 보존하기 위해서)
공식 문서에는 모델의 모든 층을 고정시키고 fc 레이어를 새롭게 수정했음. (새롭게 만든 레이어는 requires_grad=True 가 기본값)
강의자료에서는 모델의 모든 층을 고정(requires_grad=False)시킨 뒤에, fc층의 가중치와 편향만 True로 설정해줌.
3. 클래스 개수 수정 / 모델 저장을 위한 코드 추가
54줄, 75줄의 코드를 수정
모델 저장하는 코드 추가
4. Mac에서 GPU 가속하기
M1칩 이후의 맥에서는 NVIDIA gpu가 아닌 애플이 자체 생산한 칩을 사용함. 따라서 cuda를 못 쓴다. 대신 자체 개발한 API인 MPS를 사용할 수 있다.
MPS란?
MPS(Metal Performance Shaders)는 애플이 개발한 GPU 가속 프레임워크. 파이토치에서 성능이 좋은것 같진않다.
cpu 사용할 때는 1에폭에 훈련이 1분 14초, 검증이 32초 걸린다.
mps 사용할 때는 1에폭에 훈련이 37초, 검증이 23초 걸린다.
OSError: [Errno 24] Too many open files
epoch18 도중에 다음과 같은 에러가 발생하면 훈련이 중단됨.
이 에러는 현재 프로세스에서 OS에서 허용하는 최대 파일 디스크립터의 개수를 초과했을 때 발생한다.
- 파일 디스크립터(File Descriptor)
파일 디스크립터는 운영체제가 파일을 다룰 때 사용하는 추상적인 개념으로, 프로세스가 파일을 열면 커널은 해당 파일을 관리하기 위해 음수가 아닌 정수의 파일 디스크립터를 할당한다.
이 정수는 프로세스 테이블에 저장되며, 프로세스가 파일에 접근할 때마다 이 번호를 통해 커널에게 어떤 파일에 대한 작업인지 알려준다.
일반적으로 0, 1, 2번 파일 디스크립터는 표준 입출력을 위해 사용된다. 0은 표준 입력(stdin), 1은 표준 출력(stdout), 2는 표준 에러(stderr)
훈련 코드에서 DataLoader를 통해 여러 개의 프로세스(num_workers > 0)를 사용해 데이터를 로드하는 과정에서 시스템의 최대 파일 디스크립터 한계를 초과한 것 같다.
- 최대 파일 디스크립터 개수와 사용중인 파일 디스크립터 개수 확인
1. 시스템 최대 파일 디스크립터 개수를 확인하려면 ulimit -n 명령어를 사용하면 된다. 맥의 경우는 256이 기본값
2. 훈련을 실행중인 프로세스가 열고 있는 파일의 개수를 확인하려면 먼저 프로세스 id(PID)를 알아야 한다.
그리고 알아낸 PID를 이용해 해당 프로세스가 열고 있는 파일의 수를 확인한다.
$ps -ef | grep <script_name> → PID 확인
ps (process status), -ef (every full format), | (파이프: 앞 명령어의 결과를 다음 명령어의 입력으로 보냄),
grep(global regular expression print) 텍스트에서 특정 패턴 검색 → 특정 프로세스 정보 출력. 출력값의 두번째 숫자가 PID
$lsof -p <PID> | wc -l → 파일 수 확인
lsof (list open files) , -p <PID> (프로세스 ID로 필터링), wc -l(word count -l, lsof -p <PID>로 출력된 파일의 줄의 수를 셈 = 파일 수)
파일 디스크립터 최댓값은 256이고 에폭8에서 이미 그 값을 넘었다.
바로 종료되지는 않고 에폭18까지실행되다가 OSError와 함께 멈췄다. (에폭18의 열린 파일 수는 353)
이를 해결하기 위해 파일 디스크립터 최댓값을 바꿔준다. ulimit -n <변경할 값> 이 변경한 값은 해당 세션이 끝나면 기본값으로 초기화된다.
변경해주니 에폭50까지 잘 실행된다.
훈련 결과
num_workers=6 device=mps epochs=50
훈련 결과 시각화
손실값과 정확도 구하는 방법
원본 코드에서는 MetricLogger 클래스를 통해서 손실값과 정확도를 포함한 여러 지표들을 관리하고 있음.
실제로 터미널에 출력되는 값들도 MetricLogger 클래스의 메소드 log_every()를 통해서 출력된다. 이 클래스는 utils.py에 있다
MetricLogger의 생성자에서 self.meters에 defaultdict(SmoothedValue)를 할당한다.
SmoothedValue도 utils.py에 있는 클래스로, 기본 포맷을 지정해주는 클래스인거같다.
fmt = "{median:.4f} ({global_avg:.4f})"
이런식으로 초기화하고 있는데 여기서 median은 특정 지역(window_size)의 중앙값(median)을 나타내고 global_avg는 전역 평균값을 의미한다. 그래서 log_every() 메소드 실행시 터미널 창에서 median값과 global_avg값(괄호 안에 있는 값) 둘 다 출력된다.
log_every() 메소드 실행시 log_msg를 출력하게 된다.
meter=str(self)에서 MetricLogger의 __str__() 메소드가 실행된다.
self.meters에는 다음과 같은 정보가 있다.
이런 지표들을 모두 연결하여 log_every() 함수에서 출력해준다.
따라서 median, global_avg 손실값과 정확도값을 얻으려면 MetricLogger 클래스의 객체를 이용하면 된다. 예제 코드에서는 metric_logger에 해당.
훈련함수(train_one_epoch)와 검증함수(evaluate)에서 손실값과 정확도값을 반환하도록 코드를 추가해준다.
return metric_logger.loss.global_avg ,metric_logger.acc1.global_avg
(loss와 acc1을 멤버변수와 같이 접근할 수 있는건 MetricLogger 클래스의 __getattr__() 메소드 덕분이다.)
median, global_avg loss값 말고 그냥 평범한 loss값을 얻기 위해서 loss.item()으로 얻을 수 있다.
- matplotlib을 이용한 시각화
1. 매 에폭의 손실값과, 정확도를 저장할 배열을 만들기
2. 매 에폭마다 훈련과 검증에서 발생한 손실값과 정확도를 반환해서 배열에 저장
3. 저장된 손실값과 정확도값의 배열을 이용해 시각화
매 에폭의 손실값과 정확도를 저장할 배열을 훈련을 위한 반복문 전에 만들어준다.
train_losses, val_losses = [], []
train_acc1s, val_acc1s = [], []
실시간으로 그래프를 업데이트하기 위해 interactive 모드를 활성화 시켜준다.
plt.ion()
반환된 손실값과 정확도값을 저장하고 저장된 데이터를 이용해 시각화한다.
훈련이 끝나면 그래프를 저장한다.
- 결과 그래프
https://github.com/ddong02/visionAI/blob/main/image_classification/train_and_plotting.py
- tensorboard를 이용한 시각화
main함수에 텐서보드 시각화를 위해서 SummaryWriter 객체 만들기
훈련/검증 함수에서 반환받은 손실값과 정확도값을 add_scalar() 함수를 이용해 텐서보드에 추가한다.
터미널에서 tensorboard --logdir <log가 저장된 폴더> 명령어를 실행해서 실시간 그래프를 확인할 수 있다.
결과 그래프
https://github.com/ddong02/visionAI/blob/main/image_classification/train_tensorboard.py
최종 모델 파일 테스트
1. python
- 테스트를 위한 이미지 전처리
예제 코드에서는 검증 데이터를 아래와 같이 전처리했음.
resize_size와 crop_size의 기본값은 각각 256, 224로 되어 있다.
테스트 코드에 테스트 데이터셋을 위한 custom Dataset을 만들고 예제와 동일하게 이미지 변환을 해준다.
훈련 환경과 동일하게 입력 이미지 형식(RGB → ImageNet 데이터)과 클래스 번호를 설정해줘야 함.
훈련 코드에서는 데이터셋을 만들 때 torchvision.datasets.ImageFolder 함수를 사용했고 이 함수는 알파벳 순으로 클래스 번호를 할당함.
따라서 cat → 0, dog → 1 이 된다.
- 테스트 이미지와 레이블 확인하기
- 테스트
강의 자료와 달리 img.unsqueeze(0)를 하지 않은 이유는 DataLoader를 통해 데이터를 얻을 때 자동으로 batch_size만큼의 차원이 추가되기 때문이다. 아래 코드에서 images.shape의 값은 torch.Size([10, 3, 224, 224])이다.
https://github.com/ddong02/visionAI/blob/main/image_classification/my_test.py
전이학습이란?
전이학습(Transfer Learning)은 딥러닝 모델을 처음부터 학습시키는 대신, 이미 대량의 데이터로 학습된 모델을 가져와서 새로운 작업에 맞게 미세 조정하는 기법이다.
전이학습은 이미 학습된 모델의 초기층과 중간층을 재사용하고, 마지막 층만 새로운 작업에 맞게 재구성하여 학습하는 방식으로 이루어진다.
훈련 과정에서 초기/중간층의 가중치는 고정시키고(freeze), 새로운 마지막 층만 학습시킴으로써 새로운 데이터에 최적화된 모델을 효율적으로 만들 수 있다. → 합성곱층(특징 추출기) 고정 / 완전 연결층(분류기) 재학습
전이학습을 하는 이유
- 학습 시간 단축 : 전이 학습은 이미 학습된 모델을 활용하기 때문에 훨씬 빠르게 모델을 학습시킬 수 있다.
- 데이터 요구량 감소 : 전이 학습은 상대적으로 적은 양의 데이터만으로도 높은 성능을 낼 수 있다.
- 높은 성능 달성 : 수많은 데이터로 검증된 모델의 특징 추출 능력을 활용하기 때문에, 새로운 데이터셋에서도 뛰어난 정확도를 얻을 수 있다.
필기체 분류기
1. 데이터셋 준비
• 훈련 데이터 변환
성능 향상을 위해서 훈련 데이터에 회전 변환 적용
ImageNet에서 사전 학습된 모델의 입력 크기에 맞게 (224, 244) 크기로 mnist 데이터를 업샘플링
1채널 mnist 데이터를 3채널로 변환
ImageNet의 평균과 표준편차에 맞게 정규화
• 검증 데이터 변환
훈련 데이터와 마찬가지로 크기 조절, 정규화
• 테스트 데이터 변환
검은 배경에 흰 글씨인 mnist 데이터에 맞게 테스트 데이터도 색상을 반전
훈련 데이터와 마찬가지로 크기 조절, 정규화
• 훈련/검증/테스트 데이터 출력
2. 모델 준비
사용 모델: ResNet-50
옵티마이저: Adam
손실함수: CrossEntropyLoss
특징 추출기 고정, fc층만 업데이트되도록 설정
3. 훈련
https://github.com/ddong02/visionAI/blob/main/image_classification/mnist_classification.py
4. 훈련 결과
에폭당 약 20분, num_epoch=35, batch_size=64, optimizer=Adam, criterion=CrossEntropyLoss, device='mps'
훈련 데이터 6만 장, 검증 데이터 1만 장, 테스트 데이터 100장 (https://github.com/kCWtb/number_recognization/blob/main/number.zip)
5. 추론
테스트 데이터에 대한 추론 정확도는 18%로 성능이 안좋다...
테스트 이미지와 예측 값을 출력해보면
1만 잘 맞춘다.
왜지..??
