hemisus
Profile
Projects
Audio
Computer Network
Computer Vision
CS231n (2025)
Data Structure
Deep Learning
Linear Algebra
Machine Learning
Python
PyTorch
Reinforcement Learning
Home
Contact
Copyright © 2026 | Hemisus
Home
>
Computer Vision
> CS231n (2025)
Now Loading ...
CS231n (2025)
CS231n 9강. Object Detection, Image Segmentation, Visualizing
Youtube: Stanford CS231N 2025 강의자료 컴퓨터 비전 분야의 다양한 core task를 다룬다. 1. Semantic Segmentation 이미지 분할(Image Segmentation)의 하위 분류 중 하나로, 이미지 속의 각 픽셀이 무엇인지 파악하여 같은 종류의 객체는 같은 라벨로 묶어 분할한다. 학습 시에 training data의 image에는 각 픽셀에 대해 semantic category가 레이블되어 있다. Test 단계에서는 새로운 image의 각 pixel이 어떤 클래스인지 분류하는 문제가 된다. 각 픽셀에 대해 분류를 진행하지만 같은 클래스에 속하는 서로 다른 instance(객체)를 구분하지는 않는다. 즉, 사진 속에 소가 두 마리 있어도 둘 다 똑같이 “cow” 픽셀로만 칠해질 뿐 개체 단위로 나뉘지 않는다. Semantic Segmentation의 가장 단순한 수행 방식은 Sliding Window이다. 일정 크기의 window를 이동시키며 그 안의 context를 이용해 중심 픽셀을 분류한다. 하지만 이 방식은 window 영역이 매번 겹치게 되는데도 겹치는 영역의 feature를 공유하여 재사용하지 않으므로 매우 비효율적이다. 이를 해결하기 위해 전체 이미지를 한 번에 합성곱 레이어에 통과시키는 방식을 생각할 수 있다. 다만 분류 문제와 달리 Semantic Segmentation의 output은 input과 같은 해상도여야 하므로, 해상도를 그대로 유지한 채 깊은 Conv 연산을 쌓으면 연산량과 메모리 요구량이 매우 커진다. Fully Convolutional Network 이로 인해 제안된 구조가, 모든 레이어를 합성곱 연산으로 구성하면서 네트워크 내부에서 downsampling과 upsampling이 일어나는 Fully Convolutional Network(FCN)이다. 앞부분에서 해상도를 줄여 연산량을 아끼고 넓은 receptive field를 확보한 뒤, 뒷부분에서 다시 원본 해상도로 복원한다. 합성곱이 큰 이미지를 작은 특성맵으로 압축했다면, upsampling은 작은 특성맵을 다시 큰 크기로 복원하는 연산을 수행한다. Upsampling에는 다양한 방법이 존재한다. 가장 기본적인 방법으로 Nearest Neighbor, Bed of Nails, Max Unpooling이 소개되며, 이들은 모두 학습 파라미터 없이 정해진 고정된 규칙으로 upsampling을 수행한다. 이와 달리 학습 가능한 Upsampling으로 Transposed Convolution이 있다. 일반 합성곱과 동일하게 filter가 학습되는 파라미터이며, stride $> 1$인 경우 출력 해상도가 입력보다 커진다. U-Net U-Net은 이름처럼 구조가 U자 형태를 띄고 있으며, 왼쪽의 수축 경로(contracting path)와 오른쪽의 확장 경로(expanding path)로 구성된다. 수축 경로에서 합성곱층으로 이미지를 점점 압축하고, 확장 경로에서 upsampling으로 다시 원본 크기로 복원한다는 점은 기존 FCN과 동일하다. U-Net의 핵심 아이디어는 Skip Connection으로, 수축 경로에서 만들어진 중간 특성맵을 따로 저장해두었다가 복원 단계에서 같은 해상도의 특성맵과 결합하여 사용한다. 압축 과정에서 손실되던 세밀한 경계 정보를 복원 단계에서 다시 활용할 수 있게 되므로, 더 정밀한 분할이 가능해진다. 2. Object Detection 객체 탐지는 말 그대로 사진이나 영상에 있는 객체를 찾아내는 기술이다. 각 객체가 무엇인지 분류하는 문제와 그 객체가 어디에 있는지 위치를 찾아내는 문제를 함께 포함한다. 객체 탐지는 1-stage 방식과 2-stage 방식으로 나뉘는데, 1-stage는 분류와 위치 검출을 한 번에 수행하는 방법이고, 2-stage는 후보 영역을 먼저 뽑고 그 후에 분류를 수행하는 방법이다. 이렇듯 객체 탐지는 클래스 분류와 위치를 찾기 위한 $(x, y, w, h)$ 회귀가 합쳐진 문제이므로, 손실 함수 또한 두 손실의 가중합으로 표현된다. \[L = L_{\text{cls}} + \lambda L_{\text{loc}}\] 여기서 $\lambda$는 두 손실 사이의 균형을 맞추는 하이퍼파라미터이다. Region Proposal 처음에는 이미지에 객체가 몇 개 있는지 알지 못하므로, CNN 연산을 이미지의 여러 부분에 대해 반복 수행해야 한다. 이때 CNN 연산을 수행할 후보 영역(Region of Interest, RoI)을 먼저 탐색해야 하며, 이러한 후보 영역을 찾는 대표적인 알고리즘이 Selective Search(선택적 탐색)이다. 입력으로 들어온 사진을 색상, 질감, 밝기와 같은 기준으로 매우 작은 영역들로 먼저 나눈 뒤, 나누어진 영역들을 색상, 질감, 인접성 등의 기준으로 비슷한 것끼리 병합한다. 이렇게 병합된 영역들을 기반으로 객체가 있을 것으로 판단되는 후보 영역(바운딩 박스)을 얻게 되고, 이 과정을 반복하여 다양한 크기의 후보 영역을 추출한다. (후보 영역의 개수는 하이퍼파라미터로, 아래에서는 2000개가 사용된다.) R-CNN R-CNN은 선택적 탐색으로 먼저 후보 영역을 구하고, 그 영역들을 각각 CNN의 입력으로 전달해 객체 탐지를 수행하는 기술이다. 이전의 객체 탐지 기술은 슬라이딩 윈도우 방식으로 이미지의 모든 영역을 검사했는데, R-CNN은 객체가 있을 만한 후보 영역을 먼저 구한 뒤 그것만 CNN에 입력한다는 점이 핵심이다. 추출한 RoI에 대해 클래스 분류와 Bbox Regression을 적용하여, 해당 영역의 레이블과 함께 바운딩 박스를 어디로 얼마나 이동·확대해야 하는지 조정값을 예측한다. R-CNN의 단점은 다음과 같다. 합성곱층에 후보 영역을 넣기 위해 각 영역을 강제로 리사이즈하는데, 이 과정에서 이미지가 왜곡되어 원본의 형태 정보가 훼손될 수 있다. 2000개의 후보 영역을 각각 독립적으로 CNN에 통과시켜야 하므로 연산량이 매우 크다. Spatial Pyramid Pooling (공간 피라미드 풀링, SPP) SPP는 이를 해결하기 위해 처리 순서를 바꾼다. 이전과 같이 원본 사진에서 선택적 탐색으로 RoI 좌표 2000개를 저장해두되, CNN에 통과시키는 것은 후보 영역이 아니라 원본 이미지 전체이다. 이러면 특성맵 1개가 생성되는데, 여기에 앞서 구한 RoI 좌표를 대응시킨 뒤 완전연결층에 들어가기 직전에 크기를 통일시켜 분류를 수행한다. 이 방식은 크기 조정을 합성곱층 이전이 아니라 완전연결층 직전에 수행하므로 CNN이 왜곡이 덜한 특성맵을 얻을 수 있고, 2000번이 아니라 딱 1번만 CNN을 통과시키면 되므로 훨씬 빠르고 효율적이다. Fast R-CNN SPP의 아이디어로 R-CNN을 개선한 방식이 Fast R-CNN이다. “원본 이미지를 CNN에 한 번만 통과시킨다”는 핵심을 그대로 유지하면서, SPP층을 RoI Pooling이라는 더 단순하고 효율적인 방식으로 교체했다. 또한 분류기로 SVM 대신 softmax를 사용하고 bbox regression까지 하나의 네트워크에서 함께 학습하도록 통합했다. RoI Pooling: 크기가 각각 다른 후보 영역을 풀링할 때, 영역을 목표 출력 크기만큼의 격자로 나눈 뒤 각 칸마다 max pooling을 적용해 결과 크기를 통일시키는 방법. 예시: $8 \times 8$ 특성맵 위의 $7 \times 5$ 크기 RoI를 $2 \times 2$ 형태의 고정된 출력으로 만들고자 하면, 가로 방향은 stride $= 3$, 세로 방향은 stride $= 2$를 적용해 $2 \times 2$ 크기의 결과를 얻을 수 있다. Fast R-CNN에 와서는 CNN 연산이 더 이상 병목이 아니게 되었고, 신경망 바깥에서 CPU로 돌아가는 선택적 탐색이 전체 추론 시간의 대부분을 차지하는 새로운 병목이 되었다. Faster R-CNN Fast R-CNN에서 한 번 더 개선된 방식이다. 지금까지 본 R-CNN, Fast R-CNN은 모두 후보 영역을 추출할 때 선택적 탐색이라는 외부 알고리즘을 신경망 바깥에서 수행했는데, Faster R-CNN은 이것을 신경망 내부의 레이어로 대체한 구조이다. 이로써 CPU 대신 GPU에서 계산이 가능해져 속도가 크게 빨라졌고, 기존에는 후보 영역 추출과 객체 분류가 별개의 과정이어서 따로따로 학습해야 했다면 이제는 두 과정을 하나의 네트워크로 통합해 End-to-End 학습이 가능한 구조가 되었다. Region Proposal Network (RPN) Faster R-CNN에서 후보 영역을 추출하는 레이어이다. RPN은 CNN을 통과해서 얻은 특성맵 위에서 동작한다. 슬라이딩 윈도우 방식으로 특성맵 전체를 탐색하며, 윈도우가 멈춘 위치에서 $N \times N$ 크기의 특성맵 값을 읽어 RPN 내부의 작은 신경망에 입력으로 사용한다. 이때 윈도우 크기가 $N \times N$으로 고정되어 있으면 사진 속 다양한 크기와 비율의 객체를 수용하기 어렵다는 문제가 생긴다. 이 문제를 해결하기 위해 도입한 것이 앵커(anchor)로, 앵커는 각 윈도우 위치를 중심으로 미리 정해둔 다양한 크기와 비율의 기준 박스이다. 원본 논문에서는 크기 3종류와 비율 3종류를 조합해 위치당 총 $3 \times 3 = 9$개의 앵커를 생성했고, 이 9개의 앵커가 특성맵의 모든 위치를 중심으로 생성된다. 따라서 특성맵 크기가 $W \times H$라면 전체 앵커 수는 \[W \times H \times 9\] 개가 된다. 이렇게 다양한 크기와 비율의 앵커를 이미지 전체에 배치해서 다양한 크기와 위치의 객체를 더 수월하게 인식할 수 있다. RPN은 각 앵커마다 두 가지를 예측한다. 첫 번째는 해당 앵커 안에 객체가 있는지 없는지를 판단하는 이진 분류이다. 이때 학습 과정에서는 IoU(Intersection over Union)라는 기준을 사용하는데, IoU는 앵커 박스 $A$와 실제 정답 박스 $B$가 얼마나 겹치는지를 $0$에서 $1$ 사이의 값으로 나타낸 것이다. \[\text{IoU}(A, B) = \frac{|A \cap B|}{|A \cup B|}\] Faster R-CNN 논문에서는 $\text{IoU} \ge 0.7$인 앵커를 positive(객체), $\text{IoU} < 0.3$인 앵커를 negative(배경)로 라벨링하고 negative 앵커는 학습에서 제외한다. 두 번째는 바운딩 박스 회귀이다. 앵커는 규칙에 따라 미리 정해둔 박스이기 때문에 실제 객체의 위치/크기와 정확히 맞지 않는다. 바운딩 박스 회귀는 이 앵커를 실제 객체에 맞도록 미세 조정하는 과정이다. 이때 절대 좌표를 직접 예측하는 것이 아니라, 앵커 $(x_a, y_a, w_a, h_a)$ 대비 상대적인 보정값 $t = (t_x, t_y, t_w, t_h)$를 예측한다. \[t_x = \frac{x - x_a}{w_a}, \quad t_y = \frac{y - y_a}{h_a}, \quad t_w = \log \frac{w}{w_a}, \quad t_h = \log \frac{h}{h_a}\] 중심 좌표는 앵커의 크기로 나누어 정규화하고, 너비·높이는 비율에 로그를 취해 예측한다. 이렇게 하면 객체 크기에 관계없이 예측값의 스케일이 일정하게 유지되어 학습이 안정된다. 이진 분류에서 객체로 판단된 앵커에 한해서만 이 보정을 적용하고, 배경으로 판단된 앵커는 후보 영역이 될 수 없으므로 보정 없이 버려진다. RPN의 전체 손실 함수는 다음과 같다. \[L(\{p_i\}, \{t_i\}) = \frac{1}{N_{\text{cls}}} \sum_i L_{\text{cls}}(p_i, p_i^{*}) + \lambda \frac{1}{N_{\text{reg}}} \sum_i p_i^{*} L_{\text{reg}}(t_i, t_i^{*})\] 여기서 $p_i$는 앵커 $i$가 객체일 예측 확률, $p_i^{\ast}$ 는 정답 라벨($0$ 또는 $1$)이다. 회귀 항에 $p_i^{*}$가 곱해져 있으므로 positive 앵커에서만 회귀 손실이 발생한다. 이렇게 모든 앵커에 대해 예측이 끝나면, 점수가 높은 순으로 정렬하고 NMS(Non-Maximum Suppression)로 중복된 박스를 제거한 뒤 상위 $n$개의 후보 영역을 추출한다. 이후는 Fast R-CNN과 동일하게 RoI Pooling을 거쳐 최종 클래스 분류와 박스 보정이 이루어진다. 결과적으로 Faster R-CNN은 하나의 네트워크 안에서 RPN의 이진 분류 + 회귀, 최종 다중 클래스 분류 + 회귀로 총 4개의 손실을 함께 학습하게 된다. Single-Stage Object Detectors: YOLO / SSD / RetinaNet 분류와 위치 검출을 한 번에 수행하는 1-stage 객체 탐지 기술이다. 별도의 region proposal 단계 없이 특성맵의 각 위치에서 곧바로 클래스와 박스를 예측하므로 훨씬 빠르지만, 일반적으로 정확도에서는 2-stage 방식에 다소 밀린다. 이후 개별 포스트에서 따로 다룰 예정이다. Object Detection with Transformers: DETR DETR(DEtection TRansformer)은 객체 탐지를 집합 예측(set prediction) 문제로 재정의한 모델이다. 앵커 생성이나 NMS 같은 후처리 과정을 전부 제거한 것이 핵심이다. CNN이 이미지에서 특성맵을 추출하고, 위치 정보를 담기 위해 positional encoding을 더한다. 이를 Transformer 인코더에 넣어 이미지 전역의 관계를 파악한 표현을 얻는다. 디코더에는 학습 가능한 $N$개의 object query를 입력한다. 각 query는 “이미지 어딘가에 있을 객체 하나”를 담당하는 슬롯 역할을 한다. 각 query의 출력은 작은 FFN을 거쳐 클래스 하나 + 박스 하나로 변환된다. 객체가 없으면 “no object($\varnothing$)” 클래스를 출력한다. 학습에서는 $N$개의 예측과 실제 정답 박스들 사이를 헝가리안 알고리즘(Hungarian matching)으로 1:1 최적 매칭한 뒤, 매칭된 쌍에 대해서만 분류·박스 손실을 계산한다. \[\hat{\sigma} = \arg\min_{\sigma \in \mathfrak{S}_N} \sum_{i=1}^{N} \mathcal{L}_{\text{match}}(y_i, \hat{y}_{\sigma(i)})\] 이 1:1 매칭 덕분에 같은 객체에 여러 박스가 중복 예측되는 일이 구조적으로 억제되며, 그 결과 NMS가 필요 없어진다. 다만 수렴이 매우 느리고 작은 객체에 대한 성능이 떨어진다는 단점이 있어, 이후 속 연구로 개선된다. 3. Instance Segmentation 픽셀 단위 레이블 분류에 더해, 같은 클래스에 속하더라도 서로 다른 객체를 개별적으로 분리하는 task이다. Object Detection이 박스 단위로 객체를 구분하고 Semantic Segmentation이 픽셀 단위로 클래스를 나눈다면, Instance Segmentation은 두 성질을 모두 갖는다고 볼 수 있다. Mask R-CNN 한 줄로 요약하면 Faster R-CNN에 “마스크를 그리는 브랜치” 하나를 덧붙인 것이다. Faster R-CNN은 각 후보 영역에 대해 두 가지를 예측했다. 이건 무슨 클래스인가? (분류) 박스를 어디로 옮겨야 하나? (회귀) Mask R-CNN은 여기에 세 번째 문제를 추가한다. 이 박스 안에서 어느 픽셀이 객체이고 어느 픽셀이 배경인가? 박스 안을 색칠하는 작업이 하나 더 붙은 것뿐이고, 나머지는 Faster R-CNN 그대로다. 전체 흐름 위 그림을 왼쪽부터 따라가면 된다. 1) CNN + RPN 입력 이미지를 백본 CNN에 통과시켜 특성맵을 만들고, RPN이 객체가 있을 만한 후보 영역을 뽑는다. Faster R-CNN과 완전히 동일하다. 2) RoIAlign 후보 영역을 특성맵에서 잘라내 고정 크기로 맞춘다. 그림에서는 채널 $256$, 크기 $14 \times 14$가 된다. Fast R-CNN의 RoI Pooling 자리를 대체한 것으로, 뒤에서 따로 설명한다. 3) 두 갈래로 분기 위쪽 (기존 헤드): 클래스 점수 $C$개와, 클래스별 박스 좌표 $4 \times C$개를 예측한다. 좌표가 클래스마다 따로 있는 이유는 “개일 때의 최적 박스”와 “고양이일 때의 최적 박스”가 다를 수 있기 때문이다. 아래쪽 (마스크 헤드): $256 \times 14 \times 14$ 특성맵에 Conv를 몇 번 더 적용한 뒤, 마지막에 크기를 두 배로 키워 $C \times 28 \times 28$ 마스크를 출력한다. 여기서 중요한 건 마스크가 클래스 개수 $C$장만큼 나온다는 점이다. 즉 “개일 경우의 마스크”, “고양이일 경우의 마스크”를 전부 그려놓는다. 왜 마스크를 클래스마다 따로 그릴까? 마스크를 한 장만 그리게 하면 그 브랜치가 “이 픽셀은 객체인가?”와 “무슨 객체인가?”를 동시에 풀어야 한다. 반면 클래스마다 한 장씩 그리게 하면, 각 마스크는 모양(shape)만 신경 쓰면 되고 클래스 판단은 위쪽 분류 헤드에 맡길 수 있다. 그래서 학습할 때도 $C$장 전부가 아니라 정답 클래스에 해당하는 한 장에 대해서만 손실을 계산한다. 나머지 $C-1$장은 그냥 무시된다. 추론할 때도 분류 헤드가 “개”라고 답하면 $C$장 중 개 마스크만 골라 쓴다. \[L_{\text{mask}} = \text{(정답 클래스 마스크의 픽셀별 이진 분류 손실 평균)}\] 각 픽셀은 softmax가 아니라 sigmoid로 0/1을 독립적으로 판단한다. 클래스끼리 경쟁시킬 필요가 없어졌기 때문이다. 논문은 이 분리가 성능 향상에 결정적이라고 보고했다. RoIAlign: 반올림을 없앤 RoI Pooling Mask R-CNN의 또 다른 핵심이다. 기존 RoI Pooling에는 반올림이 두 번 들어간다. 특성맵은 원본 이미지보다 해상도가 낮다. 예를 들어 원본 이미지를 $16$배 줄였다면, 원본의 $x = 100$은 특성맵에서 $100 \div 16 = 6.25$번째 칸에 해당한다. 그런데 특성맵에는 소수점 칸이 없으니 RoI Pooling은 이걸 $6$으로 잘라버린다. 그리고 이 영역을 여러 칸으로 나눌 때 또 한 번 반올림한다. 분류에서는 이 정도 오차가 별문제가 아니다. 박스가 조금 밀려도 “개”라는 답은 바뀌지 않는다. 하지만 마스크는 픽셀 단위로 정확히 맞춰야 하는 작업이라 치명적이다. 특성맵에서 한 칸 밀린 것이 원본 이미지에서는 16픽셀이 밀린 것이기 때문이다. RoIAlign은 이 반올림을 전부 없앤다. $6.25$번째 위치를 그대로 두고, 주변 격자값들로 보간(bilinear interpolation)해서 그 지점의 값을 만들어낸다. 6번 칸과 7번 칸 사이 $0.25$ 지점의 값이 필요하면, 6번과 7번 값을 $3:1$로 섞어서 쓴다는 뜻이다. RoI Pooling은 칸 안의 격자값 중 최댓값을 골랐지만, RoIAlign은 칸 경계가 소수라서 그럴 수가 없다. 대신 칸을 2×2로 나눠 각 조각의 중심에 점을 4개 찍고, 각 점의 값을 주변 격자로부터 보간해 만든 뒤 평균낸다. 손실 함수 세 가지 답을 하니 손실도 세 가지를 전부 더한다. \[L = L_{\text{cls}} + L_{\text{box}} + L_{\text{mask}}\] 추론 과정 RPN이 후보 영역을 제안한다. 분류·회귀 헤드가 클래스와 보정된 박스를 예측하고, NMS로 겹치는 박스를 정리한다. 살아남은 박스에 대해서만 마스크 브랜치를 실행한다. (학습 때와 달리 RPN 제안이 아니라 보정이 끝난 최종 박스를 쓴다.) 예측된 클래스의 $28 \times 28$ 마스크를 골라 박스 크기로 확대하고, $0.5$를 기준으로 이진화한다. 확장: Human Pose Estimation Human Pose Estimation은 사람 사진에서 어깨, 팔꿈치, 손목, 무릎 같은 관절의 위치를 찍어내는 작업이다. 찾아낸 점들을 순서대로 이으면 막대인간 형태의 뼈대가 만들어진다. Mask R-CNN은 이 작업을 구조를 전혀 바꾸지 않고, 마스크 브랜치가 그리는 대상만 교체해서 수행한다. 핵심 아이디어: 관절 위치를 마스크로 표현한다 보통은 “팔꿈치가 $(x, y)$에 있다”를 좌표 회귀로 풀 것 같지만, Mask R-CNN은 그렇게 하지 않는다. 대신 정답을 격자에서 팔꿈치가 있는 칸만 $1$이고 나머지는 전부 $0$인 그림(one-hot 마스크)으로 만든다. 이렇게 하면 좌표를 맞히는 회귀 문제가 “어느 칸이 정답인지 고르는 분류 문제”로 바뀐다. 그래서 왼쪽 팔꿈치용 한 장, 오른쪽 무릎용 한 장… 이런 식으로 관절마다 한 장씩 총 $K$장을 출력한다. 마스크에서 클래스마다 한 장씩 그렸던 것과 완전히 같은 패턴이고, “클래스” 자리에 “관절”이 들어갔을 뿐이다. 마스크는 픽셀마다 독립적으로 판단하면 되지만, 관절은 정답 위치가 하나뿐이므로 칸들끼리 경쟁시켜야 한다. 그래서 마스크 한 장 안의 $m^2$개 픽셀 전체에 softmax를 적용한다. 마스크 키포인트 질문 이 픽셀이 객체인가? 관절이 어느 칸에 있는가? 정답 픽셀 수 여러 개 정확히 하나 출력 함수 픽셀별 독립 sigmoid 한 장 전체에 softmax 장 수 $C$장 (클래스 수) $K$장 (관절 수) 추론 과정 각 장에서 값이 가장 높은 칸을 하나씩 고르면 관절 $K$개의 좌표가 나오고, 어깨–팔꿈치–손목처럼 미리 정해진 연결 규칙에 따라 이으면 뼈대가 완성된다. 정확한 위치 예측이 필요한 작업이라 마스크($28 \times 28$)보다 높은 $56 \times 56$ 해상도를 사용한다. 이처럼 Mask R-CNN은 하나의 구조로 detection, instance segmentation, keypoint detection을 모두 처리할 수 있다. 강의 후반부의 Visualization & Understanding 내용은 별도 포스트에서 다룬다.
Computer Vision
· 2026-07-22
CS231n 8강. Attention and Transformers
Youtube: Stanford CS231N 2025 강의자료 추가 참고자료: 위키독스: 딥 러닝 파이토치 교과서 - 입문부터 LLM 파인튜닝까지 1. Seq2Seq Seq2Seq(Sequence-to-Sequence)은 크게 인코더(Encoder)와 디코더(Decoder)로 구성된 RNN 기반 아키텍처이다. 인코더는 현재 시점의 입력 $x_t$와 이전 시점의 은닉 상태 $h_{t-1}$을 받아 다음 은닉 상태 $h_t$를 계산한다. \[h_t = f(x_t, h_{t-1})\] 이 은닉 상태 $h_t$는 시점 $t$까지의 입력 정보를 누적하여 담고 있는 벡터이다. 인코더의 마지막 시점의 은닉 상태를 컨텍스트 벡터(context vector) $c$라고 부르며, 이를 디코더의 첫 은닉 상태로 그대로 사용하거나 초기 은닉 상태를 계산하는 데 이용한다. 디코더는 이전 시점의 은닉 상태 $s_{t-1}$, 이전 시점의 예측값(출력 토큰) $y_{t-1}$, 그리고 컨텍스트 벡터 $c$를 이용하여 매 시점마다 예측값을 출력한다. \[s_t = g(y_{t-1}, s_{t-1}, c)\] 슬라이드와 같이 디코더는 매 시점마다 단어를 하나씩 순차적으로 번역해나가는 방식으로 동작한다. 하지만 이러한 작동 방식에는 한계가 존재한다. 문장의 길이가 매우 길거나 입력이 여러 문장으로 이루어진 글일 경우, 모든 정보를 고정된 크기의 컨텍스트 벡터 $c$ 하나에 압축해서 담는 것은 매우 어렵다. 특히 입력 시퀀스의 앞쪽 정보일수록 인코더를 거치는 동안 점점 손실되는 장기 의존성 문제(long-term dependency problem)가 발생한다. 이 문제를 완화하기 위해 제안된 방식이 바로 Attention이다. 2. Attention Attention은 디코더가 하나의 고정된 컨텍스트 벡터에만 의존하는 대신, 인코더의 모든 시점의 은닉 상태를 매 예측 시점마다 다시 참고할 수 있도록 하는 방식이다. 이때 모든 은닉 상태를 동일한 비율로 참고하는 것이 아니라, 해당 시점에 필요한 정보에 더 높은 비중을 두어 집중(attend)하여 참고한다. 작동 과정 인코더의 동작은 Seq2Seq과 동일하다고 가정하고, 디코더의 첫 은닉 상태 $s_0$이 있다고 가정하자. 먼저 Alignment Score(Attention Score라고도 불림)를 구한다. 이는 인코더의 각 은닉 상태가 디코더의 현재 시점 은닉 상태와 얼마나 유사한지를 나타내는 값이다. 인코더의 각 은닉 상태 $h_i$를 디코더의 은닉 상태 $s_0$과 내적하여 시점마다 스칼라 값을 구한다. \[e_{1,i} = h_i \cdot s_0\] 이렇게 구한 스코어를 softmax에 통과시켜, 0~1 사이의 값을 가지며 합이 1인 확률 분포 형태의 Attention 가중치 $a_{1,i}$를 얻는다. \[a_{1,i} = \frac{\exp(e_{1,i})}{\sum_j \exp(e_{1,j})}\] 이후 컨텍스트 벡터 $c_1$을 구한다. 모든 인코더 은닉 상태 $h_i$를 방금 구한 Attention 가중치 $a_{1,i}$로 가중합하여 구한다. \[c_1 = \sum_i a_{1,i} h_i\] 이제 디코더는 이전 시점의 은닉 상태 $s_{t-1}$, 이전 시점의 예측값(출력 토큰) $y_{t-1}$, 그리고 현재 시점의 컨텍스트 벡터 $c_t$를 이용하여 매 시점마다 예측값을 출력한다. Seq2Seq과의 차이점은, 컨텍스트 벡터를 매 시점 새롭게 계산하여 사용한다는 점이다. 이 과정을 반복하면 매 시점 동일한 컨텍스트 벡터를 사용하는 대신, 입력에서 그때그때 연관된 정보를 참고하여 더 좋은 성능으로 예측을 수행할 수 있다. 이로써 입력 시퀀스 전체를 하나의 고정된 벡터로 압축해야 했던 bottleneck이 사라졌고, 매 시점마다 입력 시퀀스의 다른 부분에 더 집중할 수 있게 되면서 예측 성능이 향상되었다. 아래 슬라이드를 보면, 영어를 프랑스어로 번역하는 작업을 수행할 때 더 연관된 단어에 높은 가중치가 할당된 것을 시각적으로 확인할 수 있다. 이때 어떤 단어가 더 연관이 있고 어떤 정보를 더 참조해야 하는지는 인간이 직접 지시하는 것이 아니라, 모델이 역전파를 통해 직접 optimize되며 학습한 결과이다. 즉 위의 Attention 과정은 모두 미분 가능한(differentiable) 연산으로 구성되어 있다. Query, Key, Value 위에서 살펴본 attention 과정을 조금 더 일반화된 형태로 표현하면, Query, Key, Value라는 세 종류의 벡터를 이용해 정리할 수 있다. 이 표현 방식은 이후 self-attention과 Transformer의 핵심 구성 요소가 되므로 중요하다. 앞서 구한 attention 과정을 다시 보면, 두 가지 역할이 섞여 있다는 것을 알 수 있다. 디코더의 은닉 상태 $s_{t-1}$은 “지금 무엇을 찾고 싶은가”에 대한 질의 역할을 한다. –> Query 인코더의 은닉 상태 $h_i$는 Query와의 유사도를 계산하는 데 사용되는 동시에(–> Key), 실제로 가중합되어 컨텍스트 벡터를 구성하는 데도 사용된다(–> Value). 즉, 위 방식에서는 $h_i$가 Key와 Value 역할을 동시에 수행하고 있었던 것이다. 이를 일반화하면, 하나의 입력 벡터로부터 서로 다른 선형 변환(Matrix Multiply)을 통해 Query, Key, Value를 각각 만들어낼 수 있다. Attention Layer \[Q = XW_Q, \quad K = XW_K, \quad V = XW_V\] 여기서 $X$는 입력 벡터(들의 집합), $W_Q, W_K, W_V$는 각각 Query, Key, Value를 만들기 위해 학습되는 가중치 행렬이다. Key와 Value를 분리함으로써 “어떤 기준으로 유사도를 비교할지”(Key)와 “실제로 어떤 정보를 가져올지”(Value)를 독립적으로 학습할 수 있게 된다. 행렬의 shape을 구체적으로 살펴보면 다음과 같다. 입력 시퀀스에 $N$개의 벡터(토큰)가 있고, 각 입력 벡터의 차원이 $D$라고 하자. 즉 $X$의 shape은 $(N, D)$이며, $X$의 각 행(row)이 하나의 토큰에 대응한다. $W_Q, W_K$: shape $(D, D_Q)$ → $Q, K$의 shape은 $(N, D_Q)$ $W_V$: shape $(D, D_V)$ → $V$의 shape은 $(N, D_V)$ $Q$와 $K$는 이후 내적을 해야 하므로 반드시 같은 차원 $D_Q$를 가져야 한다. 반면 $V$는 내적 연산에 참여하지 않고 가중합에만 쓰이기 때문에 이론적으로는 $Q, K$와 다른 차원 $D_V$를 가져도 무방하다. 다만 실제 구현에서는 연산이 편리하도록 $D_Q = D_K = D_V$로 동일하게 설정하는 것이 일반적이다. $Q$의 $i$번째 행: $i$번째 토큰이 “무엇을 찾고 싶은가”를 나타내는 쿼리 벡터 $K$의 $j$번째 행: $j$번째 토큰의 정보를 드러내는 키 벡터 $V$의 $j$번째 행: $j$번째 토큰이 실제로 “전달할 정보”를 담은 값 벡터 전체 계산 과정은 다음과 같이 정리된다. 1. 유사도 계산 Query와 모든 Key 사이의 유사도를 내적으로 구한다. \[\text{score} = \frac{QK^T}{\sqrt{D_Q}}\] $Q$의 shape은 $(N, D_Q)$, $K^T$의 shape은 $(D_Q, N)$이므로, $QK^T$의 shape은 $(N, N)$이 된다. 이 행렬의 $(i, j)$ 원소는 $i$번째 토큰의 Query와 $j$번째 토큰의 Key 사이의 유사도를 의미한다. 즉, 행(row) $i$: $i$번째 토큰(쿼리)이 나머지 모든 토큰(키)과 얼마나 유사한지를 담은 벡터 열(column) $j$: $j$번째 토큰(키)이 나머지 모든 토큰(쿼리)으로부터 얼마나 주목받는지를 담은 벡터 벡터의 차원 $D_Q$가 커질수록 내적 값의 분산이 커져 softmax의 gradient가 매우 작아지는 문제가 있어, 이를 방지하기 위해 $\sqrt{D_Q}$로 나누어 스케일링한다. 이를 Scaled Dot-Product Attention이라 한다. 2. 가중치 계산 (softmax) \[A = \text{softmax}\left(\frac{QK^T}{\sqrt{D_Q}}\right)\] 여기서 핵심은 softmax를 행(row) 방향, 즉 axis=1로 적용한다는 점이다. $(N, N)$ 행렬에서 행 $i$는 “토큰 $i$가 쿼리일 때, 모든 토큰(키) $j = 1, \dots, N$에 대해 계산한 유사도”를 담고 있다. 이 행에 softmax를 적용하면, 하나의 쿼리 $i$가 나머지 모든 키들에 나눠주는 가중치의 합이 1이 되도록 정규화된다. \[A_{i,j} = \frac{\exp(\text{score}_{i,j})}{\sum_{k=1}^{N} \exp(\text{score}_{i,k})}\] 만약 axis=0(열 방향)으로 softmax를 적용한다면 이는 “하나의 키가 모든 쿼리들로부터 받는 관심의 합이 1”이 되도록 정규화하는 것이 되어 의미가 완전히 달라진다. 우리가 원하는 것은 “각 쿼리가 자신의 관심을 여러 키에 어떻게 분배하는가”이므로, 반드시 행(row) 방향, axis=1로 계산해야 한다. 3. 출력 계산 이 가중치로 Value들을 가중합하여 최종 출력을 구한다. \[\text{Attention}(Q, K, V) = AV = \text{softmax}\left(\frac{QK^T}{\sqrt{D_Q}}\right)V\] $A$의 shape은 $(N, N)$, $V$의 shape은 $(N, D_V)$이므로 $AV$의 shape은 $(N, D_V)$가 된다. 즉 출력의 $i$번째 행은, $i$번째 토큰(쿼리)이 자신의 attention 가중치(행 $i$)로 모든 토큰의 Value 벡터를 가중합한 결과이다. \[\text{output}_i = AV_i = \sum_{j=1}^{N} A_{i,j} V_j\] 여기서 $V_j$는 $V$의 $j$번째 행, 즉 $j$번째 토큰의 value 벡터 전체를 가리킨다 ($X, Q, K$와 마찬가지로 $V$도 “행 = 토큰”). $A$의 열 인덱스 $j$와 $V$의 행 인덱스 $j$가 정확히 같은 토큰을 가리키기 때문에, 별도의 transpose 없이 $AV$가 그대로 계산된다. Note. $AV$를 실제로 계산할 때는 $D_V$개의 열에 대해 각각 $AV_{:,k} = \sum_j A_{i,j}V_{j,k}$ 형태로 (병렬로) 계산되는데, 열 하나만 떼어보면 “모든 토큰의 $k$번째 feature 값들”일 뿐 특정 토큰의 정보가 아니다. 하지만 모든 열에 걸쳐 동일한 가중치 $A_{i,j}$가 재사용되므로, $D_V$개 차원별 계산 결과를 이어붙이면 $\sum_j A_{i,j}V_j$, 즉 토큰 단위로 value 벡터 전체를 가중합한 것과 정확히 같아진다. 입력이 $N$개의 벡터였던 것처럼, 출력 역시 $N$개의 벡터(shape $(N, D_V)$)로, 입력 토큰 개수는 그대로 유지되면서 각 토큰의 표현이 attention을 통해 재구성된다. 이것이 기본적인 하나의 Attention Layer를 이룬다. Cross-Attention Cross-Attention은 글의 맨 처음에 다뤘던 Seq2Seq + Attention 구조와 동일한 구조로, 디코더의 은닉 상태 $s_{t-1}$이 Query로, 인코더의 은닉 상태들 $h_i$가 Key/Value로 사용되었던 과정을, Q/K/V 프레임워크로 다시 표현한 것이 바로 cross-attention이다. (Query와 Key&Value의 출처가 동일하지 않음) \[Q = X_1 W_Q, \quad K = X_2 W_K, \quad V = X_2 W_V\] Query: 정보를 요청하는 쪽의 $X_1$으로부터 계산 Key, Value: 정보를 제공하는 쪽의 $X_2$로부터 계산 Self-Attention Self-Attention은 Query, Key, Value가 모두 동일한 입력으로부터 계산되는 경우이다. \[Q = XW_Q, \quad K = XW_K, \quad V = XW_V\] 앞서 정리한 Attention Layer의 수식 자체는 동일하고, $Q, K, V$가 모두 같은 입력 $X$로부터 계산된다는 점이 핵심이다. 병렬 처리 가능 (RNN과의 결정적 차이): RNN 기반 Seq2Seq에서는 $h_t$를 구하기 위해 반드시 $h_{t-1}$이 먼저 계산되어야 했으므로, 시퀀스를 순차적으로만 처리할 수 있었다. 반면 self-attention은 $QK^T$ 연산이 하나의 행렬곱으로 이루어지므로, 시퀀스 내 모든 토큰에 대한 attention을 한 번에(병렬로) 계산할 수 있다. Permutation Equivariant: self-attention 연산 자체는 입력 토큰의 순서를 구분할 수 있는 어떤 정보도 담고 있지 않다. 입력 토큰들의 순서를 뒤섞으면(permute), 출력 역시 동일하게 순서만 뒤섞인 채로 나온다. 하지만 자연어처럼 순서가 의미에 중요한 영향을 미치는 데이터를 다룰 때 이는 문제가 되므로, Transformer는 각 토큰에 positional encoding을 더해준다. Masked Self-Attention: 디코더에서 self-attention을 사용할 경우, 아직 생성되지 않은 미래 시점의 토큰 정보를 미리 참고해버리면 문제가 된다. 이를 방지하기 위해 attention score 행렬에서 미래 위치에 해당하는 원소를 $-\infty$로 마스킹한 뒤 softmax를 적용하여, 해당 위치의 가중치가 0이 되도록 만든다. 이를 masked self-attention이라 하며, Transformer 디코더의 핵심 구성 요소이다. Multi-Head Attention 지금까지 살펴본 attention layer는, 입력을 $D_Q$차원의 Q, K, V벡터로 선형변환해서 어텐션을 수행하였다. 그런데 문장 안에는 동시에 여러 종류의 관계가 존재한다(인접한 단어 관계, 주어-동사 관계, 의미적으로 먼 관계). Attention layer가 단 하나의 Q, K, V만 사용한다면, 이런 다양한 종류의 관계를 전부 하나의 유사도 계산에 넣어야 해서 표현력이 제한된다. Multi-Head Attention은 하나의 큰 attention을 계산하는 대신 여러 개의 작은 attention을 독립적으로 병렬 계산한 뒤 합치는 방식으로 해결한다. 각각의 작은 attention을 head라고 부른다. 계산 과정 head의 개수를 $h$라 하자. $i$번째 head는 자신만의 가중치 행렬 $W_Q^{(i)}, W_K^{(i)}, W_V^{(i)}$를 가지고, 입력 $X$ (shape $(N, D)$)로부터 자신만의 $Q^{(i)}, K^{(i)}, V^{(i)}$를 계산한다. \[Q^{(i)} = XW_Q^{(i)}, \quad K^{(i)} = XW_K^{(i)}, \quad V^{(i)} = XW_V^{(i)}\] 이때 각 head는 전체 차원 $D_Q$를 $h$등분한 $D_Q / h$ 차원을 가진다. 즉 $W_Q^{(i)}, W_K^{(i)}$의 shape은 $(D, D_Q/h)$, $W_V^{(i)}$의 shape은 $(D, D_V/h)$이며, 각 head의 $Q^{(i)}, K^{(i)}$는 shape $(N, D_Q/h)$, $V^{(i)}$는 shape $(N, D_V/h)$를 갖는다. (전체 $h$개 head의 차원을 모두 합치면 원래의 $D_Q$, $D_V$가 된다.) 각 head는 앞서 정의한 attention 연산을 독립적으로 그대로 수행한다. \[\text{head}_i = \text{Attention}(Q^{(i)}, K^{(i)}, V^{(i)}) = \text{softmax}\left(\frac{Q^{(i)}{K^{(i)}}^T}{\sqrt{D_Q/h}}\right)V^{(i)}\] 이 $h$개의 head 출력을 마지막 축(feature 축)을 따라 이어붙이면(concatenate) shape $(N, D_V)$가 되고, 여기에 최종적으로 학습 가능한 행렬 $W_O$ (shape $(D_V, D_{out})$)를 곱해 최종 결과물을 얻는다. \[\text{MultiHead}(Q, K, V) = \text{Concat}(\text{head}_1, \dots, \text{head}_h)\,W_O\] 3. Transformer Transformer는 (Multi-Head Self-Attention + MLP)로 구성된 하나의 블록을 반복해서 쌓아 만든 아키텍처이다. Attention이 토큰들 사이에서 정보를 주고받는 역할을 한다면, MLP는 각 토큰이 받은 정보를 개별적으로 가공하는 역할을 한다. 3.1 Transformer Block 하나의 Transformer block은 다음 네 가지 구성 요소로 이루어진다. Multi-Head Self-Attention: 토큰들 사이에서 정보를 교환한다. (지금까지 다룬 유일하게 토큰 간 상호작용이 일어나는 연산이다.) Residual Connection: 입력을 sub-layer의 출력에 더해준다. Layer Normalization (Norm): 각 토큰 벡터를 정규화한다. MLP (Feed-Forward Network): 각 토큰에 독립적으로 적용되는 두 개의 linear layer 이를 수식으로 정리하면 다음과 같다. 입력 $X$ (shape $(N, D)$)에 대해: \[X' = \text{LayerNorm}\big(X + \text{MultiHeadSelfAttention}(X)\big)\] \[Z = \text{LayerNorm}\big(X' + \text{MLP}(X')\big)\] Residual Connection: Transformer block을 매우 깊게(수십~수백 층) 쌓다 보면, 역전파 과정에서 gradient가 소실되거나 최적화가 어려워지는 문제가 발생한다. Residual connection은 입력을 그대로 다음 층까지 우회 경로(shortcut)로 전달해, gradient가 손실 없이 앞쪽 층까지 흘러갈 수 있게 해준다. Layer Normalization: 학습을 안정화하고 각 층의 입력 분포를 일정하게 유지하기 위함이다. 각 토큰 벡터 하나마다 feature 차원 $D$ 방향으로 평균과 분산을 구해 정규화한다. 즉 토큰 하나하나가 독립적으로 정규화되므로, 시퀀스 길이나 배치 크기에 영향받지 않는다. ((seq_len, embedding_dim) 행렬이 있으면, 각 행(row) - 즉 토큰 하나마다 embedding_dim 차원 방향으로 평균/분산을 구해서 정규화) MLP (Feed-Forward Network): 시퀀스의 모든 위치(토큰)에 대해 동일한 가중치를 공유하는 두 개의 linear layer와 그 사이의 활성화 함수(ReLU 또는 GELU)로 구성되며, 보통 중간 차원을 원래보다 확장했다가 다시 원래 차원으로 축소한다. \[\text{MLP}(x) = W_2\,\sigma(W_1 x + b_1) + b_2\] MLP는 토큰 $i$의 벡터만 입력으로 받아 토큰 $i$의 벡터만 출력하므로, 토큰 간 정보 교환이 전혀 일어나지 않는다 –> 즉 Transformer block 하나는 “attention으로 정보를 모으고(토큰 간 정보교환) → MLP로 그 정보를 가공한다(연산)”는 두 단계를 반복하는 구조이다. 3.2 Positional Encoding 앞서 self-attention의 특징에서 다뤘듯, self-attention과 MLP는 모두 permutation equivariant하다. 입력 토큰의 순서를 뒤섞으면 출력도 그대로 순서만 뒤섞여 나올 뿐, 순서 정보 자체를 활용하지 못한다. 하지만 언어처럼 순서가 의미에 결정적인 데이터를 다루려면 각 토큰이 시퀀스 내에서 몇 번째 위치에 있는지를 모델에게 알려줘야 한다. 이를 위해 각 토큰의 임베딩에 위치 정보를 담은 Positional Encoding 벡터를 더해준다. \[X_{\text{input}} = \text{Embedding}(\text{tokens}) + PE\] 원 논문(Attention is All You Need)에서는 서로 다른 주기를 갖는 sin, cos 함수를 이용한 sinusoidal positional encoding을 사용했다. 위치 $pos$, 차원 인덱스 $i$에 대해: \[PE_{(pos,\, 2i)} = \sin\!\left(\frac{pos}{10000^{2i/D}}\right), \qquad PE_{(pos,\, 2i+1)} = \cos\!\left(\frac{pos}{10000^{2i/D}}\right)\] 이 방식의 한계점을 보완한 RoPE(Rotary Position Embedding) 방식이 이후 제안되었다. 3.3 Encoder-Decoder 구조 Transformer block을 여러 개 쌓아 Encoder와 Decoder를 구성하고, 이 둘을 연결하면 원 논문의 전체 Transformer 아키텍처가 된다. Encoder: 입력 시퀀스 전체를 받아, Transformer block($=$ Self-Attention + MLP)을 $N$번 반복해서 쌓는다. 각 block을 지날 때마다 모든 토큰이 서로를 참고하며 문맥 정보를 더 풍부하게 담은 표현으로 갱신한다. Decoder: 출력 시퀀스를 한 토큰씩 생성하며, 각 Transformer block마다 두 종류의 attention을 사용한다. Masked Self-Attention: 지금까지 생성된 출력 토큰들끼리 self-attention을 수행하되, 미래 시점의 토큰은 볼 수 없도록 마스킹한다(masked self-attention). Cross-Attention: Query는 디코더 자신의 표현에서, Key와 Value는 encoder의 최종 출력에서 가져온다. 디코더가 매 시점 인코더의 정보를 참고해 다음 토큰을 예측한다. 각 attention 다음에는 동일하게 residual connection, LayerNorm, MLP가 뒤따른다. 이러한 Decoder block을 $N$번 쌓은 뒤, 마지막에 linear layer와 softmax를 거쳐 다음 토큰에 대한 확률 분포를 출력한다. 3.4 RNN과의 비교 RNN (Seq2Seq) Transformer 토큰 간 정보 전달 경로 순차적 ($O(N)$ 단계를 거쳐야 먼 토큰에 도달) 모든 토큰이 attention을 통해 1단계($O(1)$)만에 서로 직접 연결 학습 시 병렬화 불가능 (이전 시점의 은닉 상태가 있어야 다음 시점 계산 가능) 가능 (전체 시퀀스에 대한 attention을 행렬곱으로 한 번에 계산) 시간복잡도/메모리 시퀀스 길이 $N$에 대해 선형 ($O(N)$), 은닉 차원 $D$에 대해 $O(D^2)$ attention score 행렬이 $(N,N)$이므로 $N$에 대해 이차 ($O(N^2)$) 순서 정보 재귀 구조 자체에 순서가 내재됨 별도의 positional encoding으로 순서를 명시적으로 주입해야 함
Computer Vision
· 2026-07-13
CS231n 7강. Recurrent Neural Networks
Youtube: Stanford CS231N 2025 강의자료 1. Recurrent Neural Network 순환 신경망(RNN)은 입력과 출력을 시퀀스 단위로 처리하는 모델이다. 기존의 Feed-Forward 신경망이 고정된 크기의 입력만 받을 수 있었던 것과 달리, 가변적인 크기의 입력을 받을 수 있고, 데이터의 “순서(Order)”와 “시간적 흐름(Time-step)”을 처리할 수 있는 것이 가장 큰 특징이다. 아래 슬라이드는 RNN의 유형을 입력, 출력의 갯수에 따라 분류한 것이다. 다른 신경망들과 마찬가지로 입력에 대해 가중치(Weight) 연산을 거쳐 출력을 내는 것은 동일하지만, RNN의 가장 큰 차이점은 은닉 상태(hidden state)라는 내부 메모리를 가진다는 점이다. 은닉 상태 $h_t$는 RNN 셀이 다음 시점 $t+1$의 자신에게 보내는 값으로, 해당 시점까지의 과거 정보들을 압축한 벡터(Vector)이다. 이를 현재 시점의 입력값 $x_t$와 함께 사용하여 새로운 결과를 계산한다. Vanilla RNN의 수식 먼저, RNN의 hidden state update를 점화식으로 표현하면 다음과 같다. \[h_t = f_W(h_{t-1}, x_t)\] 함수 $f_W$는 파라미터(가중치) $W$를 가지며, 매 타임스텝마다 동일한 함수와 동일한 가중치가 재사용된다. Hidden State가 아닌 실제 Output을 출력할때는 또 다른 가중치인 $W_{hy}$를 사용한다. 가장 기본적인 형태인 Vanilla RNN의 수식으로 전개하면 아래와 같다. \[h_t = \tanh(W_{hh}h_{t-1} + W_{xh}x_t)\] \[y_t = W_{hy}h_t\] 이전 상태의 정보 $h_{t-1}$와 현재의 입력 $x_t$가 각각의 가중치 행렬 $W_{hh}$, $W_{xh}$와 곱해지고, 이 두 값을 더한 뒤, 활성화 함수인( $\tanh$ )를 통과시켜 현재 시점의 은닉 상태 $h_t$를 업데이트한다. Backpropagation through time (BPTT) RNN은 시간 흐름에 따라 연산 과정을 펼쳐놓았을 때, 각 시점의 Loss를 이용하여 역전파를 통해 학습한다. 이처럼 시간의 흐름에 거슬러 올라가는 역전파를 Backpropagation Through Time (BPTT)라고 한다. 이후 설명은 Many to Many 유형을 기준으로 설명된다. \[L = \sum_{t=1}^{T} L_t\] 모든 시점의 출력에 대한 손실을 구해 더한 전체 손실 $L$을 기반으로, 매 시점마다 공유되는 가중치 $W$의 그래디언트를 누적하여 업데이트한다. 이때 핵심은 “모든 시점에서 동일한 가중치 행렬 $W$($W_{hh}, W_{xh}, W_{hy}$)가 공유된다”는 점이다. 일반적인 깊은 신경망(DNN)은 층(Layer)마다 서로 다른 가중치를 가지지만, RNN은 시간에 따라 펼쳐진 각 단계마다 완전히 똑같은 가중치 $W$가 사용된 상태이다. 다변수 함수의 연쇄 법칙에 의해, 동일한 변수 $W$가 여러 결과($L_1, L_2, \dots, L_T$)에 영향을 미쳤다면, 전체 그래디언트는 각 손실이 $W$에 미치는 그래디언트를 모두 더한 값이 된다. \[\frac{\partial L}{\partial W} = \sum_{t=1}^{T} \frac{\partial L_t}{\partial W}\] 나아가, 특정 시점 $t$의 손실 $L_t$가 가중치 $W$에 미치는 영향($\frac{\partial L_t}{\partial W}$)을 구하려면, 현재 시점 $t$뿐만 아니라 과거의 모든 시점($k=1, 2, \dots, t$)에서 $W$가 은닉 상태에 기여한 영향을 시간 역순으로 전부 거슬러 올라가며 곱하고 더해야 한다. \[\frac{\partial L_t}{\partial W} = \sum_{k=1}^{t} \frac{\partial L_t}{\partial y_t} \frac{\partial y_t}{\partial h_t} \frac{\partial h_t}{\partial h_k} \frac{\partial h_k}{\partial W}\] 계산 및 업데이트 과정 요약: 시퀀스의 맨 마지막 시점 $T$부터 시작하여 과거 방향(왼쪽)으로 역전파를 진행. 각 시점 $t$에서 역전파되어 내려오는 그래디언트는 두 갈래로 나뉜다: 하나는 아래로 내려가 입력층의 가중치에 대한 그래디언트를 구하고, 다른 하나는 이전 시점 $t-1$의 은닉 상태($h_{t-1}$) 방향으로 흘러감 이렇게 시간의 역방향으로 흘러가는 그래디언트들이 각 시점의 가중치 $W$ 노드를 지나칠 때마다, 해당 위치에서 계산된 그래디언트 값들을 각각의 가중치 행렬( $dW_{hh}, dW_{xh}, dW_{hy}$ )에 계속해서 누적하여 저장. 시퀀스의 맨 처음($t=1$)까지 역전파가 모두 끝나면, 최종적으로 누적된 그래디언트를 이용해 가중치 $W$를 단 한 번 업데이트한다. Truncated BPTT 이론적으로는 전체 시퀀스에 대해 Full BPTT를 수행하는 것이 맞지만, 실무적으로 시퀀스의 길이($T$)가 매우 길어지면(예: 수천 단어가 넘는 문장이나 긴 비디오) 다음과 같은 문제가 발생한다. 메모리 및 계산량 폭발: 역전파를 수행하려면 순전파 때 계산했던 모든 타임스텝의 은닉 상태($h_t$)와 활성화 값을 메모리에 유지해야 한다. 시퀀스가 길어질수록 필요한 메모리와 계산량이 증가. 기울기 소실/폭발 (Gradient Vanishing / Exploding): 동일한 가중치 행렬 $W$가 $T$만큼 반복해서 곱해지기 때문에, 역전파 과정에서 그래디언트가 사라지거나(Vanishing) 무한대로 커지는(Exploding) 문제가 발생. 이러한 대규모 시퀀스 데이터를 학습시키기 위한 현실적인 해결책으로 Truncated BPTT를 소개한다. 전체 시퀀스를 한 번에 처리하지 않고, 일정한 타임스텝 크기(t=10, 20…)로 쪼개어 학습하는 방식이다. 지정된 청크 크기만큼만 순전파를 진행하고, 해당 구간 내의 시점별 손실($L_t$)을 계산한다. 해당 청크 내에서만 역전파를 수행하여 가중치 $W$를 업데이트. 다음 청크 구간으로 넘어갈 때, 이전 청크의 마지막 은닉 상태 $h_t$ 값을 전달하여 다음 순전파의 초기값으로 사용한다. 다음 청크의 역전파를 계산할 때는 이전 청크로 그래디언트가 넘어가지 않도록 한다. (메모리를 절약하면서 과거의 정보 맥락을 유지) 위의 설명은 매 타임스텝마다 Loss가 발생하는 Many-to-Many를 가정한 것이고, Many-to-One 구조에서는 Loss가 마지막 청크의 최종 시점에만 존재한다. 따라서 마지막 출력층이 포함된 청크의 그래디언트만 계산하고 이전 청크는 아예 업데이트 하지 않거나(엄격한 Truncated BPTT), 출력층 청크의 가장 왼쪽 그래디언트만 메모리에 저장해두고 이전 청크에게 넘겨주는 방식을 적용할 수 있다.(Memory-Efficient BPTT) Memory-Efficient BPTT 일부 중간 시점의 값만 캐시하여 완전히 그래디언트를 단절시키지 않는 방법을 Memory-Efficient BPTT라고 한다. 전체를 청크로 나누어 순전파를 진행할 때, 내부의 모든 연산 결과를 저장하지 않고 각 청크의 시작 지점의 은닉 상태(이전 청크에서 넘어온 최종 은닉 상태)만 메모리에 저장 마지막 출력층이 포함된 청크에서 최종 Loss를 기반으로 역전파를 수행할 때, 역전파가 해당 청크의 시작점(가장 왼쪽)에 도달하면, 그 위치의 그래디언트를 끊지 않고 저장해둠. 1번 과정에서 저장해 둔 은닉 상태 경계값을 초기값으로 삼아 해당 청크 구간만 순전파를 다시 한 번 계산. 이를 통해 지워졌던 활성화 값들을 복구한 뒤, 2번에서 기억해 둔 그래디언트를 이어받아 역전파를 마저 진행. 이 방식을 사용하면 역전파 시 순전파를 중복해서 다시 계산해야 하므로 연산 시간은 늘어나지만, 메모리 사용량을 단일 청크 크기만큼으로 고정시킨 채 Full BPTT의 효과를 얻을 수 있다. 2. LSTM (Long Short-Term Memory) Vanilla RNN의 한계 앞서 말했듯이, Vanilla RNN은 시퀀스의 Time step이 길어질수록 과거의 정보가 뒤로 갈수록 점점 손실되는 장기 의존성 문제(Long-term dependency problem)를 겪는다. 역전파(Backpropagation) 과정에서 은닉 상태 $h_t$의 미분값을 구하려면 동일한 가중치 행렬 $W_{hh}$가 타임스텝의 수만큼 거듭제곱($W_{hh}^t$)되어 곱해진다. 이때 가중치 행렬의 가장 큰 특이값(Singular value)이 1보다 크면 기울기가 무한대로 치솟는 Gradient Exploding이, 1보다 작으면 0으로 수렴해버리는 Gradient Vanishing 현상이 발생한다. (보통 활성화 함수인 $\tanh$의 미분값이 1보다 작기 때문에 Vanishing 문제가 빈번하게 발생함.) 이러한 고질적인 Vanilla RNN의 한계를 극복하기 위해 제안된 구조가 LSTM(Long Short-Term Memory)이다. LSTM(Long Short Term Memory)의 핵심 구조 LSTM은 기존 RNN의 은닉 상태 $h_t$ 외에도, 정보가 단절 없이 흐를 수 있는 셀 상태(Cell State, $c_t$)를 추가로 도입하고, 은닉 상태 또한 계산하는 방식이 조금 달라진다. “은닉 상태 = 단기기억, 셀 상태 = 장기기억” Vanilla RNN은 이전 은닉 상태 $h_{t-1}$와 현재 입력 $x_t$을 받아 $\tanh$ 함수를 거쳐 새로운 $h_t$를 만들었다면, LSTM은 이 두 값을 입력으로 받아 4개의 Gate를 통과시키며 정교하게 정보를 조작한다. 이전 은닉 상태 $h_{t-1}$과 현재 입력 $x_t$ 벡터에 각각 다른 가중치 행렬 $W$를 곱하여 4개의 게이트 값을 계산한다. 수식적으로는 표기의 간결함을 위해 두 벡터를 병합하여(Concat) 하나의 큰 가중치 행렬 $W$를 곱하는 것으로 표현한다. \[\begin{pmatrix} i \\ f \\ o \\ g \end{pmatrix} = \begin{pmatrix} \sigma \\ \sigma \\ \sigma \\ \tanh \end{pmatrix} W \begin{pmatrix} h_{t-1} \\ x_t \end{pmatrix}\] $i$ (Input Gate): 새로운 정보를 ‘얼마나(How much)’ 반영할지 결정하는 필터이다. 시그모이드($\sigma$)를 통과하여 0~1 사이의 값을 가지며, 클수록 정보를 더 받아들임. $f$ (Forget Gate): 과거의 정보를 얼마나 잊을지 결정한다. 시그모이드($\sigma$)를 통과하므로 0~1 사이의 값을 가지며, 0에 가까울수록 과거 정보($c_{t-1}$)를 많이 지우고, 1에 가까울수록 정보를 보존. $g$ (Gate Gate(?)): 셀 상태에 새롭게 기록하고 싶은 ‘정보의 내용물(What)’ 자체이다. $\tanh$를 통과하여 -1~1 사이의 값을 가지며, 어떤 방향(+ 또는 -)으로 상태를 변화시킬지 그 후보값을 나타냄. $o$ (Output Gate): 업데이트된 최종 셀 상태($c_t$)를 바탕으로, 다음 은닉 상태($h_t$)로 얼마나 내보낼지 결정함. State 업데이트 위에서 구한 4개의 Gate 값을 이용하여 LSTM은 두 가지 상태(Cell State와 Hidden State)를 업데이트한다. ($\odot$ 기호는 요소별 곱셈(Element-wise multiplication)을 의미.) 1) Cell State 업데이트 ($c_t$) \[c_t = f \odot c_{t-1} + i \odot g\] $f \odot c_{t-1}$ (과거 기억 필터링): 이전 셀 상태에 Forget Gate를 곱해 불필요한 과거 기억을 삭제. $i \odot g$ (새로운 기억 융합): 현재 시점의 정보 내용물($g$)에 Input Gate($i$)의 반영 비율을 곱하여 필터링한 뒤, 그 결과를 과거 기억에 덧셈(+)으로 추가. 2) Hidden State 업데이트 ($h_t$) \[h_t = o \odot \tanh(c_t)\] 새롭게 완성된 $c_t$를 $\tanh$에 통과시킨 뒤, Output Gate $o$와 곱해 최종적으로 외부(출력층 및 다음 시점)로 보낼 은닉 상태 $h_t$를 계산. Do LSTMs solve the vanishing gradient problem? 핵심은 Cell State($c_t$)의 덧셈(+) 연산에 있다. Vanilla RNN은 매 타임스텝마다 가중치 행렬 $W$를 ‘행렬 곱’으로 누적하기 때문에 그래디언트가 소실되었다. 하지만 LSTM의 $c_t$ 업데이트 식($c_t = f \odot c_{t-1} + i \odot g$)을 보면, $c_{t-1}$에서 $c_t$로 넘어갈 때 행렬 곱셈이 아닌 덧셈(+)을 통해 과거 정보를 이어받는다. 덧셈 연산은 역전파 시 그래디언트를 1 그대로 흘려보내주는 역할을 한다. 즉, Forget Gate $f$가 1에 가까운 값을 유지해 준다면, 아무리 먼 과거의 정보라도 방해를 받지 않고 전달될 수 있다. 이는 ResNet의 Skip Connection과 유사한 성질이다.
Computer Vision
· 2026-06-22
CS231n 6강. CNN Architectures
Youtube: Stanford CS231N 2025 강의자료 1. Components of CNNs 이전 강의에서 CNN의 핵심인 Convolution Layer와, Pooling Layer에 대해 살펴보았었다. 이외에도 CNN을 이루는 몇가지 Layer들을 소개한다. Normalization Layer Normalization Layer(정규화 계층)은 Input data의 값 분포를 최적의 분포(Optimal Distribution)로 정규화하기 위해 학습하는 계층이다. 기본적으로 표준정규화를 통해 $N(0, 1)$의 분포를 따르게 만든 후 학습한 파라미터에 따라 Scale/Shift를 진행한다. 이렇게 정규화를 하면 다음과 같은 효과를 얻을 수 있다: 최적화가 안정화된다: 각 레이어로 들어가는 입력의 분포가 일정하게 유지되어 Loss의 변동도 안정됨. 학습 과정이 훨씬 안정적으로 진행된다. 더 큰 Learning Rate를 사용할 수 있다: 그래디언트의 스케일이 안정적으로 유지되어, 정규화 없이는 발산했을 큰 학습률로도 수렴할 수 있다. 그래디언트 소실/폭주가 완화된다: 데이터 분포가 활성화 함수의 saturating 구간(예: sigmoid, tanh의 양 끝)으로 치우치는 것을 막아, 역전파 시 그래디언트가 안정적으로 전달된다. 약한 규제(regularization) 효과가 있다: 특히 BatchNorm처럼 미니배치 단위로 통계를 구하는 경우, 배치마다 통계값이 조금씩 달라지는 노이즈가 더해져 일종의 regularization처럼 작동. 대부분의 정규화 계층은 이 표준정규화 방식을 사용하며, 각 종류별 차이점은 이 data를 정규화할 통계적 수치를 구하는 방식이다. 해당 강의에서는 LayerNorm이 대표적으로 소개되었다. 정육면체에서 세 축은 각각 batch($N$), channel($C$), spatial($H, W$)을 의미하고, 색칠된 영역이 하나의 평균/분산을 공유하는 단위다. 즉 정규화 종류별 차이는 “이 색칠 영역을 어떻게 잡느냐”의 차이다. Batch Norm 각 채널마다 배치 전체와 공간 차원 $(N, H, W)$에 걸쳐 통계를 계산한다. 정육면체로 보면 채널 축의 한 칸을 고정하고 N · H,W 방향으로 펼쳐진 영역이 하나의 정규화 단위가 된다. 배치 통계에 의존하므로 배치 크기에 민감하다. 배치가 작으면 통계가 불안정해져 성능이 떨어진다. 학습과 추론의 동작이 다르다. 학습 중에는 미니배치 통계를 사용하면서 running mean/variance를 EMA로 누적하고, 추론 시에는 이 누적값을 사용한다. CNN에서 표준적으로 사용된다. :: EMA(Exponential Moving Average - 지수이동평균) \[\mu_{\text{running}} \leftarrow \alpha \cdot \mu_{\text{running}} + (1-\alpha)\cdot \mu_B\] \[\sigma_{\text{running}}^2 \leftarrow \alpha \cdot \sigma_{\text{running}}^2 + (1-\alpha)\cdot \sigma_B^2\] Layer Norm 각 샘플마다 채널과 공간 차원 $(C, H, W)$ 전체에 걸쳐 통계를 계산한다. 배치 방향으로는 전혀 섞이지 않으므로 배치 크기에 독립적이고, 학습·추론 동작이 동일하다. 배치 통계를 쓰지 않아 배치가 작거나 1이어도 잘 동작한다. RNN, Transformer처럼 시퀀스 길이가 가변적이고 배치 의존성을 피하고 싶은 구조에서 주로 사용된다. Instance Norm 각 샘플의 각 채널마다 공간 차원 $(H, W)$에 대해서만 통계를 계산한다. Group Norm 채널을 $G$개의 그룹으로 나눈 뒤, 각 샘플의 그룹마다 (그룹 내 채널들 + 공간)에 걸쳐 통계를 계산한다. $G=1$이면 LayerNorm, $G=C$(채널 수)이면 InstanceNorm과 같아지는, 둘 사이를 일반화한 형태다. Regularization: Dropout 각 순전파(forward pass)마다 레이어의 뉴런 출력(활성값) 일부를 랜덤하게 0으로 만든다. 각 뉴런을 비활성화할 확률 $p$는 하이퍼파라미터이며, 보통 0.5 정도를 사용한다. 만약 특정 뉴런들이 항상 함께 켜져 어떤 특징을 “분담”해서 학습하면, 그 조합에 과하게 의존하게 된다. 랜덤하게 뉴런을 꺼버리면 어떤 뉴런이 사라질지 모르므로, 각 뉴런은 서로 의존하지 않고 그 자체로 유용한 특징을 중복해서 학습할 수 있다. 추론 시에는 뉴런을 끄지 않고 전부 사용한다. 그런데 학습 때는 평균적으로 $(1-p)$ 비율의 뉴런만 켜져 있었는데, 추론 때는 전부 켜지므로 다음 레이어로 들어가는 입력의 기댓값(스케일)이 학습 때보다 커진다. 이 차이를 맞춰주기 위해 스케일을 보정하는 과정이 필요하다. Activation Functions 각 층의 출력에 비선형성을 추가해주는 역할을 하며, 일반적으로 신경망은 선형 층과 활성화 함수를 번갈아 쌓는 구조로 이루어진다. Sigmoid 입력값을 $[0,1]$ 범위의 값으로 압축한다. 이전에는 인기가 많았으나 양 끝쪽의 gradient가 0에 가까워 중첩되어 사용될 경우 역전파에서 기울기가 소실되는 Vanishing Gradient문제로 인해 오늘날 잘 사용되지 않음. 다만 출력을 확률처럼 $[0, 1]$로 해석할 수 있어, 이진 분류의 마지막 출력층으로는 여전히 사용된다. ReLU $f(x) = max(0,x)$ 로 표현되어 굉장히 계산이 단순하고 양수 범위에서는 기울기 소실 문제가 완화되었다. 실제로 sigmoid/tanh보다 수렴도 빠르다. 다만 $X<0$의 음수에서 기울기가 죽는 Dead Cell이 되어 버리는 점과, 출력에 음수가 없어, sigmoid와 마찬가지로 zero-centered가 아니다. 이러한 단점을 완화한 변형으로 음수 구간에 작은 기울기를 부여하는 Leaky ReLU, Transformer 계열에서 표준적으로 쓰이는 GELU 등이 있으며, 그래도 대부분의 경우 기본 ReLU로 충분하다. 2. CNN Architectures VGGNet, ResNet 등 대표적인 CNN 구조들이 간략히 소개되었다. 이 글에서는 7x7 conv 한 번 대신 3x3 conv를 세 번 쌓는 것이 더 효율적이라는 점만 짚고 넘어간다. (둘은 동일한 receptive field를 갖지만, 3x3을 쌓는 쪽이 파라미터 수는 더 적고 비선형성(ReLU)이 더 많이 들어간다.) ResNet에 대한 내용은 다른 글에서 이미 다루었다: ResNet 포스트 3. Build and Train CNNs 모델을 성공적으로 학습시키기 위한 다양한 전략을 소개한다. Weight Initialization 가중치의 초기값은 학습 과정에서 굉장히 중요한 요소이다. 학습이 시작될 때 각 층을 거친 활성값(activation)의 분포가 어떻게 변하는지를 좌우하기 때문이다. 먼저, 모든 가중치를 0이나 동일한 값으로 초기화하면 안 된다. 모든 뉴런이 완전히 똑같은 출력과 똑같은 gradient를 갖게 되어 학습 내내 같은 값으로만 업데이트되기 때문이다. 그래서 가중치는 랜덤하게 초기화하되, 스케일을 잘 잡아야 한다. 너무 작으면: 층을 거칠수록 활성값이 점점 줄어 0으로 수렴한다. 활성값이 0이면 그에 의존하는 gradient도 0이 되어, 깊은 층일수록 학습이 되지 않는다. 너무 크면: 활성값이 점점 커지며, sigmoid·tanh 같은 함수는 양 끝 구간으로 몰려 gradient가 소실되고, 그렇지 않은 경우엔 값이 발산하며 Gradient Exploding이 일어난다. Xavier Initialization 층의 입력 차원 $D_{in}$을 이용해 가중치의 분산을 $\frac{1}{D_{in}}$로 맞추는 방식이다. 즉 표준정규분포에서 뽑은 값에 $\sqrt{\frac{1}{D_{in}}}$를 곱한다. 입력 뉴런이 많을수록 가중치를 작게 만든다. Kaiming Initialization 위 방식을 ReLU에 맞춰 보정한 방식이다. ReLU는 입력의 음수 절반을 0으로 죽여버리므로 출력의 분산이 대략 절반으로 줄어드는데, 이 손실을 보상하기 위해 분산에 2를 곱한 $\frac{2}{D_{in}}$을 사용하여 스케일한다. ReLU 계열을 쓰는 현대 CNN에서 표준적으로 사용된다. Data Preprocessing 학습 데이터 전체에서 구한 채널별 평균과 표준편차를 이용해, 입력 이미지를 평균 0, 표준편차 1에 가깝게 정규화하는 것이 일반적이다. 데이터를 zero-centered하게 만들어두면 학습 초기의 최적화가 안정적으로 진행된다. 이때 사용한 통계값(평균·표준편차)은 검증·테스트 데이터에도 동일하게 적용한다. Data augmentation 데이터 증강이라 불리며, 훈련 데이터를 변형하여 데이터의 크기와 다양성을 늘리는 기법이다. 좌우 반전, 크기 조절, 색상 변형, 일부 자르기(crop) 등으로 데이터를 변형시켜 학습시킨다. 모델이 이런 변형에 불변(invariant)하도록 유도해 일반화 성능을 높이는 효과를 갖는다. 변형은 훈련 시에만 적용한다. Transfer Learning 학습시키려는 데이터가 너무 적거나 컴퓨팅 자원이 충분하지 않을 때, 사전 훈련된(pre-trained) 모델을 활용하는 방법이다. 보통 CNN모델의 앞쪽 레이어는 edge·texture 같은 범용적인 특징을 학습하므로 다른 task에도 그대로 재사용할 수 있다는 점을 이용한다. Feature Extractor: 기존 모델의 CNN 레이어들은 동결(freeze)하고, 분류층만 새 데이터에 맞게 훈련한다. 데이터가 매우 적을 때 적합하다. Fine-Tuning: 분류층뿐 아니라 기존 CNN 레이어의 일부(또는 전체)도 함께 학습시킨다. 데이터가 어느 정도 확보됐을 때 사용하며, 보통 사전 학습된 가중치가 망가지지 않도록 작은 learning rate로 학습한다.
Computer Vision
· 2026-06-18
CS231n 5강. Image Classification with CNNs
Youtube: Stanford CS231N 2025 강의자료 딥러닝 아키텍처가 주목받기 이전에는, 이미지 분류와 같은 대부분의 task에서 Feature Extraction을 사람이 직접 수행하였다. 분류에 중요하다고 판단되는 feature를 직접 정의하는 코드를 작성하고, 이를 벡터와 같은 수치 배열로 변환한 뒤 Linear Classifier 등의 분류기에 입력으로 사용하는 방식이었다. 반면 오늘날의 딥러닝 아키텍처는, 이러한 Feature Extraction 과정조차 모델이 학습을 통해 스스로 수행한다. 이 방식이 사람이 직접 feature를 설계하는 것보다 더 정확할 뿐 아니라, 사람이 미처 포착하지 못한 feature까지 찾아낼 수 있다는 것이 핵심 아이디어다. 실제로 이 아이디어는 옳았고, 지난 십여 년간 End-to-End Learning 기반의 Neural Network가 다양한 분야에서 뛰어난 성능을 보여줬다. 이번 강의에서는 이미지 분류 task에서 이러한 feature 학습을 가능하게 하는 CNN(Convolutional Neural Network) 구조에 대해 다룬다. 1. CNN: Convolutional Neural Network 앞선 강의에서 이미지를 모델의 입력으로 사용할 때, 이를 1차원 벡터로 flatten하는 과정에서 이미지의 공간적 구조(spatial structure)가 손실되는 문제가 있었다. CNN은 이 문제를 해결하면서 이미지 분류에서 훨씬 뛰어난 성능을 보이는 아키텍처이다. CNN 아키텍처는 기본적으로 합성곱층(Convolutional Layer) 과 풀링층(Pooling Layer), 그리고 활성화 함수가 번갈아 쌓이는 구조로 이루어진다. Fully Connected Layer와 Template Matching Fully Connected Layer에서의 dot product는 본질적으로 template matching이다. 두 벡터가 같은 방향을 가리킬수록 내적값이 커지고, 직교하면 0에 가까워지기 때문이다. Fully Connected Layer의 핵심을 정리하면 다음과 같다: 가중치 행렬은 일종의 template의 집합(bank of templates) 이다. 각 template은 입력과 동일한 크기(size)를 가진다. 출력은 각 template과 입력 간의 matching score이다. Convolutional Layer CNN은 이러한 template matching과 filter bank의 개념을 Convolutional Layer로 일반화한다. Fully Connected Layer와의 핵심적인 차이는 두 가지다: 필터가 입력 size와 같을 필요가 없다. 이미지를 flatten하지 않는다. 더이상 이미지를 펼치지 않고 입력 이미지의 공간적 구조 (C × H × W) 를 그대로 유지한다. 필터(filter, 혹은 kernel) 는 작은 크기의 sub-image(가중치 텐서)로, 입력 이미지 전체를 슬라이딩 윈도우(sliding window) 방식으로 이동하며 각 위치에서 dot product를 수행한다. 이때 필터의 채널 수는 입력 이미지의 채널 수와 반드시 일치해야 한다. 이러한 필터와 이미지의 부분 내적을 모든 위치에 대해 반복하면 2D activation map이 생성되며, 이 map의 각 값은 이미지의 해당 위치가 필터 패턴과 얼마나 일치하는지를 나타낸다. 필터를 여러 개 사용하면 각 필터마다 하나의 activation map이 생성될 것이고, 이를 쌓으면 최종적으로 (필터 수 × H’ × W’) 크기의 출력 텐서가 만들어진다. 이때 필터의 수가 곧 출력의 채널 수가 된다. 정리하면, Convolutional Layer의 출력은 activation map이며, 학습을 통해 조정되는 값은 필터의 가중치이다. CNN은 이러한 합성곱층(선형 연산) 과 활성화 함수 또는 풀링층(비선형 연산) 이 번갈아 쌓이는 구조이다. What do Conv filters learn? 필터가 직접 경사하강법을 통해 학습한 결과는 이미지의 특징을 추출할 수 있는 값들일 것이다. 실제로 AlexNet의 첫번째 layer의 filter를 확인해보면 다음과 같다. 이미지를 학습한 대부분의 CNN에서 앞쪽 레이어는 특정 방향의 edge, 색상 대비와 같은 저수준(low-level) 특징을 학습하고, 깊은 레이어로 갈수록 이러한 저수준 특징들이 조합되어 텍스처, 패턴, 물체의 일부나 전체와 같은 고수준(high-level) 특징을 학습하게 된다. 이렇게 모델이 데이터를 단순한 특징에서 점점 추상적인 특징을 학습하는 과정을 계층적 특징 학습(hierarchical feature learning)이라고 하며, CNN이 이러한 특성을 보이는 것은 각 필터의 수용 영역(receptive field)의 크기가 점점 넓어지기 때문이다. Receptive Field Receptive Field란 activation map의 한 픽셀이 영향을 받는 원본 입력 이미지의 영역을 말한다. 예를 들어 필터 크기가 3×3이라면, 1번째 레이어 activation map의 한 픽셀은 원본 이미지의 3x3의 정보를 담고 있을 것이다. 이때 그 다음 레이어는 이 activation map 위에서 또 3×3을 보는데, 해당 map이 이미 원본의 3x3을 담고 있으니 2번째 activaion map의 한 픽셀은 원본 이미지의 5×5 영역에 영향을 받는 것이다. 이렇게 레이어가 쌓일수록 receptive field는 점점 커져, $L$번째 레이어의 receptive field 크기는 대략 $1 + L*(필터 크기-1)$로 증가한다. \[\text{Receptive Field Size} = 1 + L \times (K - 1)\] 결국 깊은 레이어의 뉴런일수록 원본 이미지의 더 넓은 영역을 종합해서 하나의 값을 만들어내기 때문에, 자연스럽게 지역적인 특징보다 더 추상적인 특징을 학습하는 것이다. 한편, 필터들이 서로 다른 특징으로 수렴할 수 있는 이유는 학습 시작 시 필터가 서로 다른 값으로 랜덤 초기화되기 때문이다. 만약 모든 필터가 동일한 값으로 초기화된다면, 역전파 시 동일한 gradient를 받아 결국 같은 특징만을 학습하는 대칭 문제(Symmetry Problem) 가 발생한다. Stride, Padding Stride: 필터가 이미지(데이터) 위를 이동할 때의 이동 간격이다. Padding: 이미지의 가장자리를 특정 값(0)으로 채우는 것이며, 보통 합성곱 연산 후에도 이미지의 크기를 유지하고 싶을때 사용된다. 입력 크기 $W$, 필터 크기 $K$, 패딩 $P$, 스트라이드 $S$가 주어졌을 때, 출력 크기 $W’$는 다음과 같다: \[W' = \frac{W - K + 2P}{S} + 1\] 예를 들어 입력이 7×7, 필터가 3×3, Padding=0, Stride=1이면: \[W' = \frac{7 - 3 + 0}{1} + 1 = 5\] 출력은 5×5가 된다. Padding=1로 설정하면: \[W' = \frac{7 - 3 + 2}{1} + 1 = 7\] 입력과 동일한 7×7 크기가 유지된다. Pooling activation map의 크기를 줄이는데 stride를 크게하는 방법 외에도 Pooling을 사용한다. 합성곱과 비슷하게 kernel을 map위에서 슬라이딩하는데, 합성곱 대신 풀링의 종류에 따라 값을 계산한다. 대표적인 풀링 방식은 두 가지다: Max Pooling: kernel 영역 내의 최댓값을 취한다. 영역에서 가장 강한 신호만을 남김. Average Pooling: kernel 영역 내의 평균값을 취한다. 아래는 kernel 크기 2×2, stride=2로 Max Pooling을 수행하는 예시로, 출력 크기가 절반으로 줄어드는 것을 확인할 수 있다. 주의할 점은 Average Pooling은 합성곱과 마찬가지로 선형(linear) 연산인 반면, Max Pooling은 최댓값을 선택하는 비선형(non-linear) 연산이라는 점이다. 이 때문에 Max Pooling은 별도의 활성화 함수 없이도 비선형성을 추가하는 효과가 있다. Translation Equivariance Equivariance(등변성)은 입력에 변환을 가했을 때 출력도 동일한 변환이 적용되는 성질이다. \[f(T(x))=T(f(x))\] 입력 x를 먼저 변환하고 함수 f를 적용한 결과와, 함수 f를 먼저 적용하고 변환한 결과가 같다. 합성곱 연산은 같은 필터가 이미지의 모든 위치에 동일하게 적용(weight sharing)되기 때문에 사진처럼 개구리의 위치가 달라도 feature map에서 동일한 패턴이 이동한 위치에 그대로 나타난다.
Computer Vision
· 2026-06-04
CS231n 4강. Neural Networks and Backpropagation
Youtube: Stanford CS231N 2025 강의자료 1. Activation Function 선형 연산만으로 신경망을 구성하면 여러 개의 선형층을 쌓더라도 결국 하나의 선형층과 동일하게 표현된다. 이는 기존에 Linear Classifier와 마찬가지로 선형적으로 구분 가능한 문제만 해결할 수 있으며, 복잡한 비선형 관계를 학습할 수 없다. 이 한계를 극복하기 위해 활성화 함수(Activation Function) 를 사용한다. 활성화 함수는 각 층의 출력에 비선형성을 추가해주며, 일반적으로 신경망은 선형 층과 활성화 함수를 번갈아 쌓는 구조로 이루어진다. 또한, 한 층의 모든 뉴런이 다음 층의 모든 뉴런과 연결된 구조를 Fully Connected Layer(Dense Layer) 라고 한다. 대표적인 활성화 함수로는 ReLU, Sigmoid, tanh 등이 있으며, 각 함수의 세부적인 특징과 장단점은 이후 강의에서 다뤄질 예정이다. 2. Backpropagation 이전 강의에서 다룬 optimizer가 loss의 gradient를 이용해 파라미터를 업데이트 하는 역할을 했다면, 역전파(Backpropagation)는 딥러닝에서 각 파라미터에 대한 gradient를 실제로 계산하는 표준적인 방법이다. 역전파에 대해 본격적으로 다루기 전에, 강의에서는 신경망 구조를 계산 그래프(Computational Graph) 형태로 나타낸다. Computaional Graph Computational Graph는 계산의 구조를 node와 edge로 나타낸 것으로, 노드에는 연산을 표기하고 엣지에는 연산 결과를 화살표와 함께 표시한다. 이로써 전체 연산 구조를 시각화할 수 있음과 동시에, 특정 부분의 지역적인 계산에만 집중할 수도 있다. 역전파의 원리 우리가 역전파를 하는 목적은 Loss를 최소화 시키기 위해, 모델의 파라미터(가중치W나 편향b)를 어느 방향으로 얼마나 조정해야 하는지 알아내기 위함이다. 이를 식으로 나타내보면 Loss L에 대한 W의 편미분으로 나타낼 수 있다. \[∂L/∂W\] 이때, 딥러닝 모델은 복합적인 여러 수학적 연산의 결합물이다. 단순히 L을 W에 대해 편미분하는 것은 간단하지만, 조정해야할 파라미터가 Loss에서 여러 단계의 연산을 거쳐야만 접근 가능한 경우도 있다. 이를 연쇄법칙(Chain Rule)을 통해 loss에서부터의 변화량을 모델의 모든 노드에까지 전파할 수 있다. 위의 슬라이드를 보면 초록색 숫자는 순전파 결과값이고, 빨간색 숫자가 각 노드의 gradient이다. 이때, 위의 계산 그래프를 보다시피 역방향으로 gradient를 구할때 전체 연산을 처음부터 끝까지 풀어서 미분할 필요가 없다. 자신의 상위 노드(upstream)에서 전달된 gradient에 자신의 연산에 대한 local gradient를 곱하기만 하면, 해당 노드의 gradient를 구할 수 있다. 지역 그래디언트(Local Gradient): 현재 노드의 연산에 대한 미분값 상류 그래디언트(Upstream Gradient): 현재 노드보다 출력(Loss) 쪽에 가까운, 이미 계산된 gradient 하류 그래디언트(Downstream Gradient): 현재 노드보다 입력 쪽에 가까운 노드의 gradient Details PyTorch와 같은 라이브러리의 autograd도 이와 동일한 방식으로 동작한다. 연산의 종류에 따라 gradient를 구하는 규칙이 미리 정의되어 있으니, 그 값을 downstream으로 계속 전달하고 누적하는 방식이다. 대표적인 두 연산을 예시로 보면: 덧셈 f(x) = c + x에서 x에 대한 미분은 1이다. 따라서 downstream으로는 1 × upstream gradient, 즉 upstream gradient를 그대로 전달한다. 곱셈 f = a × b에서 ∂f/∂a = b, ∂f/∂b = a이다. 따라서 각각 b × upstream gradient, a × upstream gradient를 downstream으로 전달한다. 3. Backpropagation in Tensor 앞서 원리 설명을 위해 역전파 대상이 scalar였다면, 실제 파라미터는 vector, matrix와 같은 n-dimensional array일 것이다. 기본적인 원리는 동일하나, scalar에서의 미분 대신 야코비안 행렬(Jacobian Matrix) 이 local gradient로 등장한다. 야코비안 행렬 J의 (i, j) 원소는 ∂y_i / ∂x_j로 정의된다. 그런데 ReLU와 같은 원소별 연산(element-wise operation) 에서는 출력 y_i가 오직 입력 x_i에만 의존하므로 i ≠ j인 경우 ∂y_i / ∂x_j = 0이 된다. 즉 야코비안이 대각 행렬(diagonal matrix) 이 되어 대부분의 원소가 0인 sparse matrix가 된다. 파라미터 수가 많아질수록 야코비안 행렬의 크기도 급격히 커지기 때문에, 이를 명시적으로 구성하는 것은 메모리와 연산 측면에서 매우 비효율적이다. 따라서 실제로는 야코비안 행렬을 직접 만들지 않고, upstream gradient와의 곱 결과만 원소별 곱셈으로 바로 계산하는 방식을 사용한다. Backprop with Matrices 행렬 곱셈 y = xw (x: [N×D], w: [D×M], y: [N×M])의 역전파를 살펴보면, upstream gradient dL/dy의 shape은 y와 동일하게 [N×M]이다. 각 파라미터에 대한 gradient는 다음과 같이 계산된다: \[\frac{\partial L}{\partial x} = \frac{\partial L}{\partial y} w^T \quad \text{([N×D] = [N×M] × [M×D])}\] \[\frac{\partial L}{\partial w} = x^T \frac{\partial L}{\partial y} \quad \text{([D×M] = [D×N] × [N×M])}\] 이 공식은 외울 필요 없이, 결과의 shape이 원래 파라미터의 shape과 일치하도록 행렬의 순서와 전치(transpose)를 맞추면 자연스럽게 유도된다. “the only way to make shapes match up!”
Computer Vision
· 2026-06-02
CS231n 3강. Regularization and Optimization
Youtube: Stanford CS231N 2025 강의자료 1. Regularization 정규화 항은 손실함수에 추가되어 data loss이외에도 패널티를 부여해 모델이 Training Data에 과적합되는 것을 방지한다. 아래와 같이 학습된 모델 f1, f2가 있을때, f1은 모든 데이터의 지점을 통과하여 Training Loss는 f2보다 낮겠지만, 새로운 데이터 (Test Data)에 대한 Loss는 데이터의 추세를 더 잘 반영하는 f2가 더 낮을 것을 알 수 있다. 이처럼 Training loss는 f1이 더 작았지만 f2가 더 학습이 잘 된 모델이라고 할 수 있다. 이렇게 모델이 적당하게 학습할 수 있도록 패널티를 부여하는 것이 Regularization이다. +) 오컴의 면도날(Occam’s Razor): ‘같은 현상을 설명하는 두 개의 주장이 있다면, 간단한 쪽을 선택하라, 1.1 L2 Regularization L2 Regularization은 모든 가중치의 제곱합을 손실함수에 추가한다. 제곱을 하기 때문에 가중치가 클수록 패널티가 크게 작용하여 가중치를 빠르게 감소시키는 weight decay효과가 있다. 반대로 이미 값이 작은 가중치는 제곱하면 더 작아져 수치에 거의 영향을 주지 않는다. 이러한 특성으로 인해 L2는 가중치를 전체적으로 작지만 고르고 분산된(spread out) 형태로 만드는 경향이 있다. 전체 Loss는 다음과 같다: \[L_{total} = L_{data} + \frac{\lambda}{2} W^2\] 여기서 1/2이 붙는 이유는 미분을 깔끔하게 하기 위한 수학적 편의이다. \[\frac{\lambda}{2} W^2 \quad \xrightarrow{\text{미분}} \quad \frac{\lambda}{2} \cdot 2W = \lambda W\] 1.2 L1 Regularization L1은 가중치를 제곱하는 대신 절댓값을 취해 더한 값을 사용한다. 제곱을 하지 않기 때문에 작은 가중치의 값을 0이 되게 만들어 해당 feature를 모델에서 제거하는 효과가 있다. 이는 모델을 더 단순하고 희소한(Sparse) 형태로 만든다. \[L_{total} = L_{data} + \lambda \sum |W|\] 또한 L1, L2 공통적으로 조율모수 λ가 붙는데, 이 값 또한 커질수록 패널티의 영향이 커지며, 분산(Variance)은 감소하고 편향(Bias)이 증가한다. 아래와 같이 x=[1,1,1,1]; w1=[1,0,0,0]; w2=[0.25,0.25,0.25,0.25]일 때, w · x는 모두 1이지만 L2 Regularization을 할 경우 w2가 더 선호하는 가중치가 될 것이다. L1 Regularization의 경우 이 예제에서는 w1, w2 둘 다 동일한 정규화 값을 가지지만 L1은 w1처럼 Sparse한 형태의 가중치를 선호할 것이다. 항상 어떤 정규화를 옳다거나, 정규화가 무조건 모델을 단순하게 만든다는 원칙은 없으며, 데이터와 모델에 따라 자신이 적절한 정규화 방법을 선택해야 한다. 1.3 ElasticNet ElasticNet은 이 L1과 L2를 복합적으로 활용하는 정규화 기법이다. 하이퍼파라미터 r을 활용하여 각 기법의 영향도를 조절할 수 있으며, 한가지 기법만을 활용하는 것 또한 가능하다. \[L_{total} = L_{data} + \lambda \left( r \sum |W| + \frac{(1-r)}{2} \sum W^2 \right)\] 2. Optimization 손실 함수의 값을 최소화하는 모델의 가중치를 찾는 과정이 바로 Optimization(최적화)이다. 이 최적화의 가장 기본이 되는 알고리즘은 Gradient Descent(경사하강법)이다. 손실함수의 경사(기울기)를 따라 내려가면서 가중치를 업데이트하여 손실함수가 가장 작아지는 지점을 찾는 방법이다. \[W ← W − η · ∇_W L\] 이 수식은 Gradient Descent의 가중치 업데이트 수식이다. 기호 의미 W (우변) 현재 가중치 η (eta) Learning Rate (학습률) — 한 번에 얼마나 이동할지 결정하는 하이퍼파라미터 ∇_W L 손실 함수 L을 가중치 W로 편미분한 값 (Gradient) W (좌변) 업데이트된 새로운 가중치 Gradient(∇_W L) 는 수학적 정의상 함수값이 가장 가파르게 증가하는 방향과 그 크기를 나타내는 벡터이다. 우리의 목표는 Loss를 최소화하는 것이므로, Gradient의 반대 방향(−) 으로 W를 조정하는 것이다. 딥러닝의 최적화에서는 거의 대부분 이러한 Analytic Gradient를 계산하여 최적화를 진행하고, Numeric Gradient는 구현이 올바른지 디버깅하는 용도로만 활용된다. Numeric Gradient Analytic Gradient 방법 h를 아주 작게 설정하여 (f(x+h) - f(x)) / h 로 근사 계산 미적분을 이용해 Gradient를 수식으로 직접 유도 정확도 근사값 (부정확) 정확한 값 속도 매우 느림 빠름 사용 목적 Gradient 구현이 올바른지 검증할 때 (Gradient Check) 실제 학습에 사용 3. Variant of Gradient Descent 3.1. Stochastic Gradient Descent (SGD) 기본적인 경사하강법은 전체 데이터에 대한 Loss와 Gradient를 계산하고 나서야 1회의 가중치 업데이트가 진행이 되었다. 이 방식은 데이터의 크기가 커질 경우 학습 속도가 매우 느릴 것이다. 이를 해결하기 위해 SGD에서는 전체 데이터에서 무작위로 일부 데이터를 뽑은 mini-batch에서 그래디언트를 계산해 W를 여러번 업데이트한다. 원래 SGD는 데이터를 1개씩 뽑아 업데이트 하는 것을 의미하지만, 실제로는 Mini-batch SGD를 통칭하여 SGD라고 부르는 경우가 대부분이다. Epoch 전체 데이터에 대해 한 번 학습을 완료하는 시점 Batch Size 한 배치에 있는 데이터의 수 Iteration 한 Epoch를 완료하기 위해 반복하는 횟수 (한 Epoch에서 이루어지는 파라미터 업데이트 횟수) GD SGD Mini-batch SGD Batch Size 전체 데이터 (N) 1 일부 (32, 64, 128) 1 Epoch 당 Iteration 1 N N / Batch Size 학습 속도 느림 빠르지만 불안정 빠르고 안정적 SGD의 문제점 1. Loss Surface의 경우에 따라 진동이 발생함 손실 함수가 어떤 방향으로는 매우 가파르고, 다른 방향으로는 매우 완만한 형태를 가지는 경우, 우리가 실제로 가고 싶은 곳은 완만한 방향의 끝에 있는 최솟값인데, 가파른 방향의 진동 때문에 지그재그로 비효율적으로 이동하게 된다. 좁고 긴 협곡의 바닥으로 내려가야 하는 상황을 비유하면, 협곡의 양 벽면(가파른 방향) 사이에서 왔다갔다 튕기거나 바깥으로 벗어날 수 있으며, 실제로 가야 할 아래쪽(완만한 방향)으로는 아주 조금씩만 전진하게 된다. 2. 지역 최솟값(Local Minimum) / 안장점(Saddle Point)에 갇힘 손실 함수의 Gradient가 0이 되는 지점에 도달하면 W 업데이트가 멈추게 된다. 이 지점이 전역 최솟값(Global Minimum)이 아니더라도 Gradient가 0이므로 더 이상 탈출할 수 없다. 3. Mini-batch 샘플링에 의한 Gradient 노이즈 모든 데이터가 아닌 일부 샘플링된 데이터를 기반으로 Gradient를 계산하기 때문에 전체 데이터에 대한 손실함수의 그래디언트를 정확히 반영하지는 못한다. 장기적으로는 Loss의 최소 지점을 찾아갈 수 있으나, 매 Step이 항상 올바른 방향을 보장하지는 않는다. 3.2. SGD + Momentum 기존 파라미터가 업데이트되어 온 방향을 기억하여 다음 업데이트에 반영하는 방식이다. 마치 물체가 움직이던 방향으로 관성을 가지는 것과 같다. 아래 식과 코드처럼 감쇠 계수 ρ(rho)가 곱해지며 누적된 Gradient(Vt)를 이용해 파라미터를 업데이트한다. 이로써 gradient=0이 되더라도 누적된 속도(Vt)가 남아있어 빠져나올 수 있고, 여전히 진동 문제는 남아있으나 최솟값을 향하는 방향의 Gradient가 지속적으로 누적되면서 점차 올바른 방향으로의 이동이 강화된다. 3.3. RMSProp RMSProp은 Gradient의 크기를 추적하여 얼만큼 다음 step의 이동량을 조절하는 알고리즘이다. Momentum에서는 Gradient의 방향과 크기가 함께 누적되었다면, RMSProp에서는 Gradient를 제곱하여 각 방향이 얼마나 가파른지 크기만을 추적한다. 이 누적된 제곱값으로 파라미터 업데이트 시 Learning Rate를 나눠줌으로써, 가파를수록 적게 움직이고, 완만한 구간에서는 크게 움직이는 효과를 보여준다. \[S_t = \rho \cdot S_{t-1} + (1 - \rho) \cdot (\nabla_W L)^2\] \[W \leftarrow W - \eta \cdot \frac{\nabla_W L}{\sqrt{S_t} + \epsilon}\] 기호 의미 $S_t$ Gradient 제곱의 누적값 (이동 평균) $\rho$ 감쇠 계수 (보통 0.9 또는 0.99) $(\nabla_W L)^2$ 현재 Gradient의 제곱 $\epsilon$ 0으로 나누는 것을 방지하기 위한 아주 작은 값 (예: $10^{-7}$) 사람이 경사가 심한 곳은 조심히 작은 보폭으로 가고, 완만한 평지는 빠르게 이동하는 것을 생각하면된다. 3.4 Adam Momentum SGD와 RMSProp의 기법을 합친 알고리즘으로, 오늘날 가장 많이 사용되는 최적화 기법이다. Momentum: Gradient의 방향을 누적하여 관성을 부여 (1차 모멘트) RMSProp: Gradient의 제곱을 누적하여 방향별 Learning Rate를 조절 (2차 모멘트) 1차 모멘트 (Momentum) \[m_t = \beta_1 \cdot m_{t-1} + (1 - \beta_1) \cdot \nabla_W L\] 2차 모멘트 (RMSProp) \[v_t = \beta_2 \cdot v_{t-1} + (1 - \beta_2) \cdot (\nabla_W L)^2\] Bias Correction Momentum과 RMSProp의 계산식은 거의 동일한데, 학습 초기에는 누적된 값이 거의 없어 $m_t$, $v_t$ 모두 0에 가깝게 되고, 이로 인해 업데이트 시 비정상적으로 큰 Step을 뛰게 될 수 있다. 이를 보정하기 위해 $1 - \beta^t$로 나누어주는 Bias Correction을 적용한다. 학습 초기($t$가 작을 때)에는 $\beta^t$가 1에 가까워 보정 효과가 크고, 학습이 진행될수록 $\beta^t$가 0에 수렴하면서 보정의 영향이 사라진다. \[\hat{m}_t = \frac{m_t}{1 - \beta_1^t}, \quad \hat{v}_t = \frac{v_t}{1 - \beta_2^t}\] \[W \leftarrow W - \eta \cdot \frac{\hat{m}_t}{\sqrt{\hat{v}_t} + \epsilon}\] 기호 의미 일반적인 기본값 $m_t$ Gradient의 이동 평균 (1차 모멘트) - $v_t$ Gradient 제곱의 이동 평균 (2차 모멘트) - $\beta_1$ 1차 모멘트 감쇠 계수 0.9 $\beta_2$ 2차 모멘트 감쇠 계수 0.999 $\eta$ Learning Rate $10^{-3}$ $\epsilon$ 0으로 나누는 것을 방지하기 위한 작은 값 $10^{-8}$ +) AdamW 기존 Adam에서 L2 Regularization을 적용하면, 정규화 항이 Gradient에 포함된 채로 1차/2차 모멘트 계산에 들어간다. 이렇게 되면 Adaptive Learning Rate(√v_t로 나누는 부분)가 Data Loss의 Gradient뿐만 아니라 Regularization 항까지 함께 스케일링해버린다. AdamW는 Weight Decay를 Gradient 계산과 분리하여, 파라미터 업데이트 단계에서 직접 적용한다. \[W \leftarrow W - \eta \cdot \left( \frac{\hat{m}_t}{\sqrt{\hat{v}_t} + \epsilon} + \lambda W \right)\] (Weight Decay $\lambda W$를 업데이트 시 별도로 적용. $\lambda W$ 는 L2정규화 항의 미분 결과) 4. Learning Rate Scheduling 전체 학습 과정 동안 반드시 같은 Learning Rate를 유지할 필요는 없으며, 오히려 이를 조절하는 것이 좋은 방법이다. 보통 학습이 진행될수록 LR을 감소시켜 Loss의 최소 지점 근처에서 수렴할 수 있도록 한다. 4.1 Step Decay 고정된 학습 반복 횟수(Epoch)마다 Learning Rate를 일정 비율로 줄이는 방식이다. (ResNet 등에서 사용) 4.2 Cosine Decay Learning Rate를 코사인 함수의 곡선을 따라 부드럽게 감소시키는 방식이다. 초반에는 천천히 감소하다가 중반에 빠르게 감소하고, 후반에 다시 천천히 감소하는 특성을 가진다. 4.3 Linear Decay Learning Rate를 학습이 진행됨에 따라 선형적으로 균일하게 감소시키는 방식이다. 4.4 Linear Warmup 초기부터 큰 Learning Rate를 사용하면 학습이 불안정할 수 있다. Linear Warmup은 학습 초기에 LR을 0에서 시작하여 목표 LR까지 점점 증가시켜 초기 학습의 안정성을 확보한다.
Computer Vision
· 2026-05-30
CS231n 2강. Image Classification with Linear Classifiers
Youtube: Stanford CS231N 2025 강의자료 1. Image Classification: A Core Task in Computer Vision 인간의 시각적 인지 능력은 짧은 순간에도 사진 속 객체가 무엇인지 판단할 수 있다. 고양이와 개를 구분하는 일은 우리에게 무의식적으로도 가능한 쉬운 문제지만, 컴퓨터에게 이는 어려운 도전 과제이다. 먼저 컴퓨터가 이미지를 보는 방식을 알아야 한다. 컴퓨터에서 이미지는 각 픽셀 값의 행렬 또는 텐서로 인식되며, 각 픽셀이 0~255값을 가지는 총 3개의 채널 (R/G/B)로 구성된다. 이처럼 이미 인간의 이미지 인식 방식과 매우 다르기 때문에, 컴퓨터의 이미지 분류에는 다양한 난관이 존재한다. 컴퓨터의 이미지 이해의 난관들 문제 설명 Viewpoint Variation 같은 물체도 촬영 각도에 따라 픽셀 값이 완전히 달라짐 Illumination 조명의 강도나 방향에 따라 동일 객체가 다르게 표현됨 Background Clutter 객체와 배경의 픽셀 값이 유사하면 구분이 어려움 Occlusion 객체 일부가 가려져 있어도 전체를 추론할 수 있어야 함 Deformation 같은 클래스의 객체라도 다양한 형태로 존재함 Intraclass Variation 같은 클래스 내에서도 크기, 색상, 패턴이 다양함 Context 주변 환경, 맥락을 이해해야 올바르게 분류 가능 1. Viewpoint Variation 같은 물체를 찍었더라도 찍은 위치에 따라 그 이미지 픽셀은 완전히 변화된 값을 가지게 된다. 2. Illumination 같은 객체여도 빛의 강도에 따라 이미지에는 다르게 표현된다. 우리는 조명의 차이를 바로 인지할 수 있으나 컴퓨터에겐 이것 또한 어려운 과제이다. 3. Background Clutter 인식하고자 하는 객체가 이미지의 배경과 비슷한 픽셀 값을 가지면 컴퓨터는 구분하기가 힘들어진다. 4. Occlusion 사진과 같이 동물의 일부가 다른 것에 가려져 있을 경우, 일부를 보고 물체를 추론할 수 있어야 한다. 5. Deformation 같은 클래스의 객체여도 다양한 형태로 존재 가능하다. 6. Intraclass variation 같은 클래스의 객체여도 다양한 종류가 있다는 것을 인지해야 한다. (size, color, pattern 이 전부 달라도 모두 cat이다) 7. Context 그림과 같이 우리는 그림자로 인해 개에게 줄무늬 그림자가 생긴 것을 이해할 수 있으나, 컴퓨터는 이를 호랑이로 분류할 수도 있을 것이다. 이처럼 이미지 속 상황에 대한 이해를 하는 능력이 요구된다. 2. K-Nearest Neighbor (KNN) Classifier KNN은 가장 단순한 데이터 기반 접근법(Data-Driven Approach)의 분류기이다. 분류하려는 이미지와 학습으로 저장된 이미지들 간의 거리(픽셀값의 차이)를 계산하여, 가장 가까운 k개의 이미지를 찾고, 그 중 가장 많은 클래스로 분류한다. 이때 이미지들간의 거리를 측정하는데 다음과 같은 방식이 있다. L1 Distance (맨해튼 거리): 각 픽셀 값의 차이를 절댓값하여 전부 더한 값. L2 Distance (유클리드 거리): 각 픽셀 값의 차이를 제곱한 것을 전부 더한 후 제곱근. (일반적인 두 점 사이의 직선 거리) \[d_1(I_1, I_2) = \sum_p |I_1^p - I_2^p|\] \[d_2(I_1, I_2) = \sqrt{\sum_p (I_1^p - I_2^p)^2}\] 학습 방식: Lazy Learning def train(self, X, y): self.Xtr = X # 전체 학습 데이터를 그대로 저장 self.ytr = y 코드와 같이 train함수에서 그냥 전체 데이터셋을 메모리에 그대로 복사하고 있다. (주석 확인) 이처럼 일반적인 모델처럼 “학습(가중치 업데이트)”을 하는 게 아니라 훈련 데이터를 통째로 외워버리는 것이 KNN의 특징이며, 이를 “게으른 학습(Lazy Learning)” 이라고도 한다. 하지만 이러한 방식으로 인해 한계가 명확하다. 예측 속도: 학습은 빠르지만, 예측 시 전체 데이터와 거리를 계산해야 하므로 매우 느림 낮은 표현력: 픽셀 간 거리는 이미지의 의미적 정보를 담지 못함(Not informative). 이미지를 한 픽셀씩 밀거나, 가리거나, 밝기를 바꾸면 거리값이 크게 변해버림 3. Linear Classifier 파라미터 기반 접근법 (Parametric Approach)의 분류기로, 학습할 때 데이터를 단순히 저장하지 않고, 원하는 결과를 얻기 위해 파라미터(W)에 데이터로부터 얻은 정보를 저장, 최적화 하는 방식이다. \[f(x, W) = Wx + b\] $x$: 입력 이미지 (flatten된 벡터, 예: 32×32×3 → 3072차원) $W$: 가중치 행렬 (학습으로 최적화할 파라미터) $b$: 편향(Bias) 출력: 각 클래스에 대한 점수(Score) 선형 분류기는 f(x,W) = Wx + b로 나타낼 수 있으며 여기서 x는 입력 이미지로, 보통 선형 분류기에 들어올때 flatten된 상태로 들어온다. (Array of 32x32x3 ->3072 numbers total) 슬라이드의 예시처럼 입력데이터와 W가 곱해지는데, W의 각 행이 해당 클래스의 정보를 담고 있어 결과 값이 그 클래스에 대한 점수를 나타내게 된다. Visual Viewpoint W의 각 행을 이미지처럼 2차원의 이미지로 시각화하면, 각 클래스에 대한 특징이 추출된 형상을 확인할 수 있다. Geometric Viewpoint $Wx + b$를 공간에 나타내보면, 해당 클래스와 아닌 것을 분리하는 초평면(Hyperplane) 의 법선 벡터 역할을 한다. 하지만 이처럼 선형 분류기는 선형적으로 분리 가능하지 않으면 제대로 클래스를 분류할 수 없다는 치명적인 단점이 있다. 4. Loss Function & Softmax Loss 손실 함수는 모델의 예측이 실제 정답에 비해 얼마나 잘못 되었는지를 측정하는 함수이다. 모델을 학습시킨다는 것은 이 손실함수 값을 최소화하도록 모델의 파라미터를 조정하는 것이다. Softmax 선형 분류기의 출력인 점수(logit) 그 자체로는 확률적 해석이 불가능하다. Softmax함수는 여러 개의 출력값을 모두 합쳐서 1(100%)이 되는 확률 분포로 변환해준다. 이로써 분류기의 예측값을 각 클래스에 대한 예측 확률로 변환할 수 있다. \[\text{softmax}(z_i) = \frac{e^{z_i}}{\sum_j e^{z_j}}\] 아래의 공식은 softmax의 개선된 공식으로, 지수에 입력 z의 최댓값을 빼줌으로써 수치 안정성을 확보한다. \[\text{softmax}(z_i) = \frac{e^{z_i - \max(z)}}{\sum_j e^{z_j - \max(z)}}\] Cross-Entropy Loss 확률 분포와 실제 정답(One-hot 벡터)과의 차이를 측정하는 손실 함수이다. “모델이 예측한 확률 분포가 정답 분포와 얼마나 멀리 있는가?” \[L = -\sum_{c} y_c \log(\hat{p}_c)\] $y_c$: 정답 레이블 (정답 클래스는 1, 나머지는 0인 One-hot 벡터) $\hat{p}_c$: 클래스 $c$의 예측 확률 정답 클래스를 제외한 나머지 $y_c$는 모두 0이므로, 결국 정답 클래스의 예측 확률만 남는다. 만약 정답으로 예측한 클래스의 확률이 1 이라면 손실함수의 값이 0이므로 최소이고, 정답 클래스의 예측확률이 작을 수록 앞에 - 부호로 인해 점점 손실함수의 값은 커지는 식이다. \[L = -\log(\hat{p}_{y_i})\] Softmax Loss Softmax Loss는 Softmax 함수 + Cross-Entropy Loss 를 결합한 것으로, 정답 클래스의 예측 확률에 음의 로그를 취한 형태이다. \[L_i = -\log\left(\frac{e^{z_{y_i}}}{\sum_j e^{z_j}}\right)\]
Computer Vision
· 2026-05-27
<
>