<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0">
  <channel>
    <title>ryuminhyuk</title>
    <link>https://icedtea.tistory.com/</link>
    <description></description>
    <language>ko</language>
    <pubDate>Sat, 8 Aug 2026 08:44:39 +0900</pubDate>
    <generator>TISTORY</generator>
    <ttl>100</ttl>
    <managingEditor>rminhyuk</managingEditor>
    <item>
      <title>[리뷰]You Only Look Once:Unified, Real-Time Object Detection(CVPR 2016)</title>
      <link>https://icedtea.tistory.com/entry/%EB%A6%AC%EB%B7%B0You-Only-Look-OnceUnified-Real-Time-Object-DetectionCVPR-2016</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;
&lt;script&gt;  MathJax = {    tex: {inlineMath: [['$', '$']]}  };&lt;/script&gt;
&lt;script src=&quot;https://cdn.jsdelivr.net/npm/mathjax@3/es5/tex-mml-chtml.js&quot;&gt;&lt;/script&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;지난번에는 2-stage detector 모델인 Faster R-CNN모델을 리뷰했으니, 다른 분야로 넘어가기 전에 1-stage detector 모델까지만 리뷰하려고 한다. 1-stage 모델 중 가장 유명한 YOLO 모델을 가볍게 다뤄보려고 한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style5&quot; /&gt;
&lt;ul style=&quot;list-style-type: disc; color: #333333; text-align: start;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li style=&quot;list-style-type: disc; color: #000000;&quot;&gt;제목: You&amp;nbsp;Only&amp;nbsp;Look&amp;nbsp;Once:Unified,&amp;nbsp;Real-Time&amp;nbsp;Object&amp;nbsp;Detection&lt;/li&gt;
&lt;li style=&quot;list-style-type: disc; color: #000000;&quot;&gt;저자: Joseph Redmon, Santosh Divvala, Ross Girshick, Ali Farhadi&lt;/li&gt;
&lt;li style=&quot;list-style-type: disc; color: #000000;&quot;&gt;게재 학회: CVPR 2016&lt;/li&gt;
&lt;li style=&quot;list-style-type: disc; color: #000000;&quot;&gt;인용수: 80107회(2026.07.27)&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;Abstract&lt;/b&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;객체탐지를 위한 기존 모델들은 탐지를 위해 분류기를 사용했다. 우리는 객체탐지를 회귀로 바라보는 모델인 YOLO를 소개한다. 한번의 계산으로 bbox와 클래스 예측 확률을 동시에 계산하는 single network로, 엄청나게 빠른 속도를 자랑한다.(45 frames per second) 다른 SOTA 객체 탐지 모델들과 비교할때, localization error은 더 많지만 배경에서 false positive를 예측하는 일은 더 적다. 마지막으로 general representation을 잘 학습한다.(natural image에서 artwork로 도메인을 전환할때의 일반화)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;Introduction&lt;/b&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;YOLO는 post processing과 classification을 따로 학습하는 기존의 모델들과 달리 객체탐지를 하나의 회귀 문제로 보아서 image를 오직 한번만 사용하고도 예측이 가능하다.(You only look once) 단일 CNN이 bbox와 class probability를 동시에 예측한다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;849&quot; data-origin-height=&quot;443&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/3YQek/dJMcagzA8IG/FVt0l3LUec8kvhpr6lMiI1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/3YQek/dJMcagzA8IG/FVt0l3LUec8kvhpr6lMiI1/img.png&quot; data-alt=&quot;단일 CNN network로 한번에 예측&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/3YQek/dJMcagzA8IG/FVt0l3LUec8kvhpr6lMiI1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2F3YQek%2FdJMcagzA8IG%2FFVt0l3LUec8kvhpr6lMiI1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;747&quot; height=&quot;390&quot; data-origin-width=&quot;849&quot; data-origin-height=&quot;443&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;단일 CNN network로 한번에 예측&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;첫번째로, YOLO는 무척 빠르다. 기본 모델은 45fps, fast YOLO는 150fps의 속도를 가진다. 따라서 동영상에서도 25millisec 이하의 latency를 가진다. 다른 real-time system과 비교하면 mAP는 2배 이상 크다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;두번째로, YOLO는 이미지를 전체적으로 봐서(reasons globally) 주변 맥락 정보를 인코딩한다. Fast R-CNN의 경우에는 영역 제안을 통해 예측을 한다. 이 방법은 큰 맥락을 보지 못하기 때문에 배경에서 오류가 많이 발생한다. 반면 YOLO는 전체 이미지를 한번에 보기 때문에 배경 오류가 더 적다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;세번째로, general representation을 잘 학습한다. DPM이나 R-CNN보다 natural image로 학습하고 artwork으로 테스트 했을때 월등한 성능을 보였다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;YOLO&lt;/b&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;입력받은 이미지는 $S \times S$ 크기의 grid로 나눠진다. 타켓의 중심이 grid cell에 놓인다면, 해당 grid cell이 그 타겟 탐지를 책임지게 된다. 정확히 말하면, 그 cell의 predictor 중 IOU가 가장 높은 하나가 responsible predictor로 지정되고, 그 predictor에 대해서만 좌표/confidence loss가 계산된다.(나머지는confidence loss에 confidence score가 0인체로 반영) 각 grid cell은 B개의 bbox와 confidence score을 계산한다. confidence score은 아래와 같이 정의된다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;284&quot; data-origin-height=&quot;48&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/TyJUx/dJMcacqvLGg/YuDDaK04yLWQQnbX7iN731/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/TyJUx/dJMcacqvLGg/YuDDaK04yLWQQnbX7iN731/img.png&quot; data-alt=&quot;만약 cell에 object가 없다면 이 score은 0이 되어야 한다.&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/TyJUx/dJMcacqvLGg/YuDDaK04yLWQQnbX7iN731/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FTyJUx%2FdJMcacqvLGg%2FYuDDaK04yLWQQnbX7iN731%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;254&quot; height=&quot;43&quot; data-origin-width=&quot;284&quot; data-origin-height=&quot;48&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;만약 cell에 object가 없다면 이 score은 0이 되어야 한다.&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;각 bbox당 bbox의 x,y,w,h 4개, 그리고 confidence score까지 총 5개의 값을 예측하게 된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;또한 각 cell은 C개의 conditional class probability 를 계산한다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;240&quot; data-origin-height=&quot;38&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/blemMr/dJMcaazrszc/1x9rY0T3TiP58jn7zF7Vr1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/blemMr/dJMcaazrszc/1x9rY0T3TiP58jn7zF7Vr1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/blemMr/dJMcaazrszc/1x9rY0T3TiP58jn7zF7Vr1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FblemMr%2FdJMcaazrszc%2F1x9rY0T3TiP58jn7zF7Vr1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;227&quot; height=&quot;36&quot; data-origin-width=&quot;240&quot; data-origin-height=&quot;38&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이는 bbox가 몇개인든지 간에 한 cell당 한 세트의 class probability만 예측하게 괸다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;테스트를 할때에는&amp;nbsp; class-apecific confidence score을 박스당 계산하게 된다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;692&quot; data-origin-height=&quot;72&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/nCKqU/dJMb991BcGL/em2kmcvChPdCeVIoNpeGHK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/nCKqU/dJMb991BcGL/em2kmcvChPdCeVIoNpeGHK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/nCKqU/dJMb991BcGL/em2kmcvChPdCeVIoNpeGHK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FnCKqU%2FdJMb991BcGL%2Fem2kmcvChPdCeVIoNpeGHK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;577&quot; height=&quot;60&quot; data-origin-width=&quot;692&quot; data-origin-height=&quot;72&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 점수는 박스에 나타나는 물체가 해당 클래스에 속할 확률과 박스가 얼마나 물체를 잘 예측하는지, 이 2개의 값을 곱해서 구한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;620&quot; data-origin-height=&quot;696&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bUXbvG/dJMcaaTHbDi/qtKw02ThEFHt7GLIX4TFkk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bUXbvG/dJMcaaTHbDi/qtKw02ThEFHt7GLIX4TFkk/img.png&quot; data-alt=&quot;VOC실험에서 bbox개수는 2개, grid는 7로 설정. class는 20개&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bUXbvG/dJMcaaTHbDi/qtKw02ThEFHt7GLIX4TFkk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbUXbvG%2FdJMcaaTHbDi%2FqtKw02ThEFHt7GLIX4TFkk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;543&quot; height=&quot;610&quot; data-origin-width=&quot;620&quot; data-origin-height=&quot;696&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;VOC실험에서 bbox개수는 2개, grid는 7로 설정. class는 20개&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;Network Design&lt;/b&gt;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1331&quot; data-origin-height=&quot;578&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bNTBLB/dJMcahFfygb/wcdQSJ6gI3P4NKtKrqYfJ1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bNTBLB/dJMcahFfygb/wcdQSJ6gI3P4NKtKrqYfJ1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bNTBLB/dJMcahFfygb/wcdQSJ6gI3P4NKtKrqYfJ1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbNTBLB%2FdJMcahFfygb%2FwcdQSJ6gI3P4NKtKrqYfJ1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1331&quot; height=&quot;578&quot; data-origin-width=&quot;1331&quot; data-origin-height=&quot;578&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;처음에는 CNN을 이용해 이미지에서 feature을 뽑아낸 다음, fully connected layer을 2개 연결해서 좌표와 확률 예측을 한다. 최종 output은 7*7*30 크기의 텐서이다. 30은 (bbox당 5개 예측( x,y,w,h,confidence))*2+(클래스 개수 20개)를 해서 나온 값이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;Training&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;먼저 처음 20개의 CNN은 ImageNet을 사용해 사전학습 시킨다. Ren et al.의 논문에 따르면 CNN와 fc layer을 동시에 사용해주면 객체 탐지 성능이 향상된다고 해서, 4개의 CNN layer과 2개의 fc layer을 추가로 더했다. 탐지는 세밀한 정보를 요구하므로 이미지 해상도도 224에서 448로 증가시켰다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;활성화 함수는 각 layer마다 leaky rectified linear activation을 사용했다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;336&quot; data-origin-height=&quot;104&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/pxZ0B/dJMb991Bc6j/iuDxtS704yDtLr96UANuk1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/pxZ0B/dJMb991Bc6j/iuDxtS704yDtLr96UANuk1/img.png&quot; data-alt=&quot;활성화 함수&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/pxZ0B/dJMb991Bc6j/iuDxtS704yDtLr96UANuk1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FpxZ0B%2FdJMb991Bc6j%2FiuDxtS704yDtLr96UANuk1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;303&quot; height=&quot;94&quot; data-origin-width=&quot;336&quot; data-origin-height=&quot;104&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;활성화 함수&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;손실함수로는 최적화하기 쉬운 SSE(sum-squared error)을 사용했는데, 이는 AP를 최대화 하기 위한 우리의 목표와 완벽히 맞지는 않는다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;박스 회귀 손실과 분류 손실의 가중치를 같게 하는 것이 이상적이지 않을 수 있고, 많은 경우 cell에는 object가 없기 때문에 object가 있는 cell의 gradient를 overpowering할 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이를 해결하기 위해 박스 회귀 손실의 가중치를 높게 주었고, object를 포함하지 않는 박스의 confidence 손실 가중치를 낮게 주었다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;SSE는 또한 큰 상자와 작은 상자의 손실 가중치를 동일하게 평가한다.따라서&amp;nbsp;절대 오차가 같으면 박스 크기와 무관하게 동일한 loss를 부여한다. 하지만 같은 절대 오차라도 작은 박스에서는 IOU에 미치는 영향이 훨씬 크기 때문에, 이 불균형을 보정하기 위해 (w, h) 대신 (&amp;radic;w, &amp;radic;h)를 회귀 대상으로 삼아 작은 박스의 오차에 더 큰 gradient가 실리도록 했다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;YOLO는 한 gird cell당 여러개의 bbox를 예측하는데, 앞서 언급했듯이 한 물체당 하나의 predictor가 담당해도록 학습시킨다. 이 하나의 predictor은 gt와 IoU가 가장 높은 predictor로 결정된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;730&quot; data-origin-height=&quot;517&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bE6izZ/dJMcaiYpy8j/zhlbCKjKooxoOYyFdurTf1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bE6izZ/dJMcaiYpy8j/zhlbCKjKooxoOYyFdurTf1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bE6izZ/dJMcaiYpy8j/zhlbCKjKooxoOYyFdurTf1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbE6izZ%2FdJMcaiYpy8j%2FzhlbCKjKooxoOYyFdurTf1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;635&quot; height=&quot;450&quot; data-origin-width=&quot;730&quot; data-origin-height=&quot;517&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;1,2 항은 Box regression 손실, 3,4 항은 confidence 손실, 5항은 class probability 손실이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;박스 회귀 손실에는 $\lambda_{\text{coord}} = 5$를, object가 없는 박스의 confidence 손실에는 $\lambda_{\text{noobj}} = 0.5$를 가중치로 부여했다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;Limitations of YOLO&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;한 grid cell당 2개의 bbox만 예측하기 때문에 가까이 있는 여러 물체들의 예측이 어렵다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;두번째로, 데이터로부터 bbox를 예측하기 때문에 처음보는 비율이나 형태의 물체를 예측하기 어려워한다. 또한 CNN을 활용한 downsampling layer을 거친 데이터를 활용하기 때문에 상대적으로 거친(coarse) 데이터를 사용한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;마지막으로 큰 상자와 작은 상자의 손실 가중치를 동일하게 평가한다.&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style5&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;논문에서는 두가지의 generalization을 말하는데, 첫번째는 도메인 일반화 문제이고, 두번째는 비율과 형태의 일반화 문제이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;도메인 일반화 문제의 경우, selective search를 사용하는 기존 알고리즘과 달리 data의 색상, 텍스쳐 등의 통계가 아예 달라지는 경우에도 좋은 예측 성능을 보인다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;비율과 형태의 일반화 문제의 경우에는 YOlO 모델이 bbox를 아예 데이터로부터 만들다보니 처음 보는 형채에 취약한 문제이다. 예를 들이 서있는 사람으로만 학습했을 경우 앉아 있는 사람은 사람으로 분류하기 힘들 수도 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;논문을 읽다가 이 두가지 구분이 햇갈려서 정리해보았다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;모델의 구조 자체는 그렇게 복잡하지 않지만, 앵커가 있었던 Faster-RCNN과 달리 아예 처음부터 bbox를 예측한다는 점이 신기했다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;논문 중간에 bbox의 specialization에 대한 얘기가 나온다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;683&quot; data-origin-height=&quot;309&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cyRE2i/dJMcagsQvtE/g4bpIdcRtzw7HRtCeD4Tek/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cyRE2i/dJMcagsQvtE/g4bpIdcRtzw7HRtCeD4Tek/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cyRE2i/dJMcagsQvtE/g4bpIdcRtzw7HRtCeD4Tek/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcyRE2i%2FdJMcagsQvtE%2Fg4bpIdcRtzw7HRtCeD4Tek%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;683&quot; height=&quot;309&quot; data-origin-width=&quot;683&quot; data-origin-height=&quot;309&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;하나의 predictor가 하나의 odject를 담당하면서, 해당 물체를 탐지하는데 특화된다는 이야기인데 anchor와 다르게 predictor가 실체가 없다는 점이 해당 문장을 이해하기 어렵게 만들었다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: AppleSDGothicNeo-Regular, 'Malgun Gothic', '맑은 고딕', dotum, 돋움, sans-serif;&quot;&gt;해당 object 검출에 특화된다는 뜻은, 이미지에 object와 비슷한 패턴이 있으면 해당 predictor가 작은 bbox를 예측한다는 것이다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: AppleSDGothicNeo-Regular, 'Malgun Gothic', '맑은 고딕', dotum, 돋움, sans-serif;&quot;&gt;결국 픽셀을 보고 CNN을 사용해 값을 예측하는 모델이라는 것이 기존의 모델들과 크게 다르지 않지만, 그럼에도 몇가지의 새로운 아이디어가 뛰어난 결과를 낼 수 있다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;YOLO는 이후에도 계속 발전하며 여러 모델들이 나오고 있다. 기회가 된다면 후속 모델들고 리뷰해보고 싶다.&lt;/p&gt;</description>
      <author>rminhyuk</author>
      <guid isPermaLink="true">https://icedtea.tistory.com/9</guid>
      <comments>https://icedtea.tistory.com/entry/%EB%A6%AC%EB%B7%B0You-Only-Look-OnceUnified-Real-Time-Object-DetectionCVPR-2016#entry9comment</comments>
      <pubDate>Tue, 28 Jul 2026 09:21:52 +0900</pubDate>
    </item>
    <item>
      <title>[리뷰]Faster R-CNN:Towards Real-Time Object Detection with Region Proposal Networks(NIPS 2015)</title>
      <link>https://icedtea.tistory.com/entry/%EB%A6%AC%EB%B7%B0Faster-R-CNNTowards-Real-Time-Object-Detection-with-Region-Proposal-NetworksNIPS-2015</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;script&gt;  MathJax = {    tex: {inlineMath: [['$', '$']]}  };&lt;/script&gt;
&lt;script src=&quot;https://cdn.jsdelivr.net/npm/mathjax@3/es5/tex-mml-chtml.js&quot;&gt;&lt;/script&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Kaggle BioHub 대회에 참여하고 싶어서 데이터를 보니 3D 객체탐지+추적 알고리즘이 쓰여서 예전에 공부했던 객체탐지 모델을 복습하려고 한다. 위키북스에서 나온 '파이토치 트랜스포머를 활용한 자연어 처리와 컴퓨터비전 심층학습' 이라는 책으로 객체탐지 모델들을 공부했었는데, 그때는 깊이 있는 이해보다는 피상적인 공부, 그리고 MS COCO 데이터셋으로 돌려본게 전부라 파이프라인을 익힐 목적으로 Faster R-CNN모델을 사용해 kaggle 불가사리 탐지 대회인 &lt;a href=&quot;https://www.kaggle.com/competitions/tensorflow-great-barrier-reef&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;'&lt;/a&gt;&lt;span style=&quot;background-color: #ffffff; color: #202124; text-align: start;&quot;&gt;&lt;a href=&quot;https://www.kaggle.com/competitions/tensorflow-great-barrier-reef&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;TensorFlow - Help Protect the Great Barrier Reef'&lt;/a&gt; 에 참여해보았다. 공부하면서 모델을 뜯어보다 보니 결국 논문을 읽는게 가장 빠른 길인 것 같아서 짧게 리뷰해보려고 한다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style5&quot; /&gt;
&lt;ul style=&quot;list-style-type: disc; color: #333333; text-align: start;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li style=&quot;list-style-type: disc; color: #000000;&quot;&gt;제목: Faster&amp;nbsp;R-CNN:Towards&amp;nbsp;Real-Time&amp;nbsp;Object&amp;nbsp;Detection&amp;nbsp;with&amp;nbsp;Region&amp;nbsp;Proposal&amp;nbsp;Networks&lt;/li&gt;
&lt;li style=&quot;list-style-type: disc; color: #000000;&quot;&gt;저자: Shaoqing Ren, Kaiming He, Ross Girshick, Jian Sun&lt;/li&gt;
&lt;li style=&quot;list-style-type: disc; color: #000000;&quot;&gt;게재 학회: NIPS 2015&lt;/li&gt;
&lt;li style=&quot;list-style-type: disc; color: #000000;&quot;&gt;인용수: 60729회(2026.07.19)&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;Abstract&lt;/b&gt;&lt;b&gt;&lt;/b&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;SOTA 객체탐지 모델들은 물체의 위치를 추정하기 위해 영역 제안 알고리즘을 사용한다. SPPnet이나 Fast RCNN같은 모델들은 탐지 시간을 줄였지만, 영역 추정 구간이 병목으로 작용한다. 본 논문에서는 Region Proposal Network(RPN)을 소개한다. RPN은 Fast RCNN과 같은 convolution feature을 공유하고, 객체 탐지에 있어서 매우 빠른 속도를 보인다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;Introduction&lt;/b&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;기존 Fast RCNN은 deep network를 사용해서 매우 빠른 속도를 보임에도 불구하고, 영역 제안 과정을 포함시킨다면 속도가 매우 느려진다. Fast RCNN의 경우에는 영역 제안을 Selective Search 알고리즘으로 수행하는데, 이는 CPU에서 이미지당 2초의 속도가 걸린다. &lt;span style=&quot;letter-spacing: 0px;&quot;&gt;이를 GPU로 옮기는 것은 유효한 엔지니어링적 해결책일 수 있으나, 이는 어디까지나 속도 개선에 그칠 뿐 후속 detection network를 고려하지 않은 방식이다. Proposal 생성과 detection이 완전히 분리된 시스템으로 남아있기 때문에, detection network가 이미 계산해놓은 convolutional feature map을 proposal 생성에 재사용할 기회, 즉 계산을 공유(sharing computation)할 기회를 놓치게 된다. 따라서 convolution feature map을 영역 제안에 활용하는 Region Proposal Networks(RPNs)를 소개한다. &lt;/span&gt;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1103&quot; data-origin-height=&quot;313&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/vtPd0/dJMcaiRGU0w/a8YlFjWAS4gMJK63nn1TQ1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/vtPd0/dJMcaiRGU0w/a8YlFjWAS4gMJK63nn1TQ1/img.png&quot; data-alt=&quot;RPN은 image의 scale을 다르게 하거나, 사이즈가 다른 필터를 여러개 사용하는 것이 아닌, 회귀 함수를 통해 multiple references를 만들어낸다.&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/vtPd0/dJMcaiRGU0w/a8YlFjWAS4gMJK63nn1TQ1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FvtPd0%2FdJMcaiRGU0w%2Fa8YlFjWAS4gMJK63nn1TQ1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1103&quot; height=&quot;313&quot; data-origin-width=&quot;1103&quot; data-origin-height=&quot;313&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;RPN은 image의 scale을 다르게 하거나, 사이즈가 다른 필터를 여러개 사용하는 것이 아닌, 회귀 함수를 통해 multiple references를 만들어낸다.&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;ILSVRC, COCO 2015 대회 등 여러 지표에서 빠르고 정확한 객체 탐지를 보였다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;Related Work&lt;/b&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;Object Proposals&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;RPN이 나오기 전까지의 영역 제안 방법들(Selective Search, CMPC, MCG, Edgeboxes 등)은 detector와 별개의 독립된 모듈이었다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;Deep Networks for Object Detection&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;먼저 R-CNN은 클래스 분류만 하고, 박스예측은 하지 않는다.(여기서 말하는 박스예측은 영역제안. 정밀 박스 회귀는 조정은 수행한다.) 따라서 영역 제안 모듈에 따라서 정확도가 결정된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;딥러닝을 활용한 영역 제안 방법에는 OverFeat, Multibox method등이 있는데 ,이들 역시 feature을 detector와 공유하지 않는다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;Faster R-CNN&lt;/b&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;RPN과 Fast R-CNN을 합쳐놓은 객체탐지 모델이다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;721&quot; data-origin-height=&quot;785&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/k3DyW/dJMcaa0pcp9/Xsslzj4txuG1kgOhudkEK0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/k3DyW/dJMcaa0pcp9/Xsslzj4txuG1kgOhudkEK0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/k3DyW/dJMcaa0pcp9/Xsslzj4txuG1kgOhudkEK0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fk3DyW%2FdJMcaa0pcp9%2FXsslzj4txuG1kgOhudkEK0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;570&quot; height=&quot;621&quot; data-origin-width=&quot;721&quot; data-origin-height=&quot;785&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;Region Proposal Networks&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이미지 크기에 상관없이 입력받아서, proposal과 objectness score을 출력한다. 최종 목적이 detector와 피처 맵을 공유하는 것이기 때문에 RPN과 Fast R-CNN은 동일한 convoution layer을 공유한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;출력은 개념적으로는 fully connected layer로 이루어지며, 마지막 피처 맵 위로 n*n 크기의 윈도우를 슬라이딩하여 계산한다. 이 슬라이드는 해당 영역을 저차원으로 바꿔준다.(ZF넷은 256차원, VGG16은 512차원) 슬라이드를 하며 계산하기 때문에 fc layer은 전체 지역에에서 공유된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;논문에서는 n=3을 사용하고 effective receptive field가 ZF는 171픽셀, VGG는 228픽셀이다.(effective receptive field란 feature map의 한 위치(한 점)가 원본 이미지에서 실제로 몇 픽셀 크기의 영역을 '보고' 계산된 것인가를 나타내는 값)&lt;br /&gt;이 구조는 n*n conv 계층과 이후에 나오는 two sibling(하나는 cls, 하나는 reg 위함) 1*1 conv 계층으로 이루어진다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;fully connected layer 얘기를 하다가 갑자기 conv layer얘기를 해서 햇갈릴수도 있는데, fc 연산을 conv로 대체한다는 의미이다. 3*3*channel 영역에 channel*256크기의 fc layer 대신 3*3*256의 필터를 적용하면 결과는 1*1*256 피처맵이 나오고, 여기에 256*18, 256*36 크기의 fc layer 대신 1*1*18, 1*1*36의 필터를 적용하면 각각 cls, reg 값이 나오게 된다.(9개의 앵커에서 하나당 객체 있는지 없는지(9*2=18), 그리고 박스의 위치 좌표 4개(9*4=36))&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;739&quot; data-origin-height=&quot;445&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/oN9of/dJMcadvXWyo/MIBQ4H0IcOVbofWIWa1Qy0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/oN9of/dJMcadvXWyo/MIBQ4H0IcOVbofWIWa1Qy0/img.png&quot; data-alt=&quot;RPN에서 수행하는 윈도우 계산은 이렇게 이루어진다.&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/oN9of/dJMcadvXWyo/MIBQ4H0IcOVbofWIWa1Qy0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FoN9of%2FdJMcadvXWyo%2FMIBQ4H0IcOVbofWIWa1Qy0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;684&quot; height=&quot;412&quot; data-origin-width=&quot;739&quot; data-origin-height=&quot;445&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;RPN에서 수행하는 윈도우 계산은 이렇게 이루어진다.&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;1. Anchors&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;3 scale과 3 aspect ratios를 사용해서 각 위치당 9개의 앵커를 사용했다. 피처맵의 크기가 W*H라 한다면 WHk 개수 만큼의 앵커가 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위에서 설명한 계산의 중요한 특성은, translation invarient 하다는 것이다. k-means를 사용해 800개의 앵커를 생성하는 Multibox의 경우에는 물체의 위치가 바뀌면 다르게 인식하지만 RPN은 같은 물체라면 같은 계산이 이루어진다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이는 각 슬라이딩 윈도우가 같은 conv가중치를 공유하기 때문이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;출력계층의 경우 Multibox는 (4+1)*800의 fc layer을 사용하지만, RPN은 (4+2)*9의 conv계층을 사용한다.(Multibox의 총 파라미터는 $6.1\times10^6$, RPN의 총 파라미터는 $2.8\times10^4$). 그러므로 계산량의 부담이 적고, 과대적합을 억제할 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;&lt;span&gt;필터를 여러 개 만들지 않고, 단일 크기의 슬라이딩 필터 하나만으로 다양한 크기의 앵커를 처리한다 &amp;nbsp;&lt;/span&gt;&lt;/span&gt;이 방식을 사용하면 extra cost없이 피처를 공유할 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;2. Loss Function&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;각 앵커에는 이진 라벨을 사용한다. IoU가 가장 높거나/IoU가 0.7이상인 앵커에는 positive 라벨, positive가 아니면서 IoU가 0.3이하인 앵커에는 negative라벨을 부여한다. 나머지 앵커는 계산에 활용되지 않는다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;손실함수는 아래와 같다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;576&quot; data-origin-height=&quot;171&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/r0LN6/dJMcafgpb7Q/xIJpYmpYTiJmTDoIUc6njk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/r0LN6/dJMcafgpb7Q/xIJpYmpYTiJmTDoIUc6njk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/r0LN6/dJMcafgpb7Q/xIJpYmpYTiJmTDoIUc6njk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fr0LN6%2FdJMcafgpb7Q%2FxIJpYmpYTiJmTDoIUc6njk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;576&quot; height=&quot;171&quot; data-origin-width=&quot;576&quot; data-origin-height=&quot;171&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;$N_ {\text{cls}} $은 미니배치의 크기, $N_{\text{reg}}$는 앵커의 개수. $\lambda$는 balancing parameter로 &lt;span data-index-in-node=&quot;13&quot; data-math=&quot;N_{\text{cls}}=256&quot;&gt;$N_{\text{cls}}=256$&lt;/span&gt;, &lt;span data-index-in-node=&quot;33&quot; data-math=&quot;N_{\text{reg}}\approx 2400&quot;&gt;$N_{\text{reg}}\approx 2400$&lt;/span&gt; 정도로 정규화 분모 크기가 달라서, &lt;span data-index-in-node=&quot;80&quot; data-math=&quot;\lambda=10&quot;&gt;$\lambda=10$&lt;/span&gt;을 곱해 두 손실(Classification, Regression)의 가중치를 거의 1:1 수준으로 맞추었다(Roughly Equal).&amp;nbsp; $L_ {\text{cls}} $로는 log loss(BCE), $L_ {\text{reg}} $로는 $smooth L_1$을 사용했다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;bbox regression에서 4개 좌표는 아래와 같이 offset으로 계산된다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;521&quot; data-origin-height=&quot;166&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bKue72/dJMcabZomsK/53zhWvzWToAsrvS0B4znVK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bKue72/dJMcabZomsK/53zhWvzWToAsrvS0B4znVK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bKue72/dJMcabZomsK/53zhWvzWToAsrvS0B4znVK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbKue72%2FdJMcabZomsK%2F53zhWvzWToAsrvS0B4znVK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;521&quot; height=&quot;166&quot; data-origin-width=&quot;521&quot; data-origin-height=&quot;166&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이때 같은 크기의 공간(3*3)에서 feature가 추출되었고, k개의 다른 크기/비율의 앵커 박스가 있으므로 regressor도 k개만큼 준비한다. 각 regressor은 같은 가중치를 공유하지 않는다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;3. Training RPNs&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;end-to-end 역전파와 SGD를 사용해서 학습시켰다. 모든 앵커를 사용해서 학습시키면 모델이 negative sample로 편향될 수 있어서 pos:neg 가 1:1비율이 되도록 랜덤하게 256개의 앵커를 샘플링해 학습시킨다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;새로운 계층들은 std가 0.01인 가우시안 분포를 가중치로 설정했고, 나머지는 ImageNet으로 사전학습시킨 계층을 활용했다. 미니배치 크기나 모멘텀, 규제는 아래 참고&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;641&quot; data-origin-height=&quot;148&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/c9Ih8N/dJMcabru7L7/4XTaGhQlmzZY9Cfh1o9Gv1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/c9Ih8N/dJMcabru7L7/4XTaGhQlmzZY9Cfh1o9Gv1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/c9Ih8N/dJMcabru7L7/4XTaGhQlmzZY9Cfh1o9Gv1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fc9Ih8N%2FdJMcabru7L7%2F4XTaGhQlmzZY9Cfh1o9Gv1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;641&quot; height=&quot;148&quot; data-origin-width=&quot;641&quot; data-origin-height=&quot;148&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;Sharing Features for RPN and Fast R-CNN&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;앞에서 설명했듯이 최종 목적이 detector와 피처 맵을 공유하는 것이기 때문에 학습을 시킬때도 피처 맵은 공유되어야 한다. 그러나 RPN과 Fast R-CNN은 손실함수가 달라서 각자 독립적으로 역전파를 하면 하나의 feature맵을 공유할 수 없게 된다. 이를 해결하기 위해 논문에서는 3가지 방법을 제시한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;1. 4-step alternating training&lt;/p&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;RPN이 backbone(VGG16)으로부터 학습&lt;/li&gt;
&lt;li&gt;1에서 만든 proposal로 Fast R-CNN 학습&lt;/li&gt;
&lt;li&gt;2에서 학습된 backbone으로 RPN학습(backbone은 freeze)&lt;/li&gt;
&lt;li&gt;다시 proposal로 Fast R-CNN 학습&lt;/li&gt;
&lt;/ol&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;2. Approxiamte joint training&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;RPN의 loss와 Fast R-CNN의 loss를 합친 값으로 역전파하는 방법이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 방법은 이론적으로 완전한 미분은 불가능한데, 그 이유는 proposal을 detector에 전달하는 RoI Pooling 연산이 well-defined가 아니기 때문이다..&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이게 무슨 뜻이냐면, 먼저 max pooling 계층의 backward부터 설명해보겠다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;ex1)Max Pool&lt;br /&gt;forward: 2&amp;times;2 윈도우 [1, 5, 3, 2] 중에서 max값 5를 선택 (위치는 고정, 미리 정해진 2&amp;times;2 영역) backward: gradient를 &quot;5가 있던 그 위치&quot;로만 흘려보냄, 나머지는 0&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;letter-spacing: 0px;&quot;&gt;여기서 미분하는 대상은 '윈도우 안의 각 값(4개 숫자)에 대해, 출력이 그 값에 얼마나 민감한가'이다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;part;(output)/&amp;part;(input_1) = 0 (1이 아니었으니)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;part;(output)/&amp;part;(input_2=5) = 1 (5가 선택됐으니, 5가 1만큼 변하면 output도 1만큼 변함)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;part;(output)/&amp;part;(input_3) = 0&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;part;(output)/&amp;part;(input_4) = 0&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;하지만&amp;nbsp; RoI Pool의 경우에는&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;ex2)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;RoI 영역이 (10,10)~(50,50)으로 이미 정해졌다고 가정하고 그 영역 안에서 max pooling : 이건 일반 max pooling과 완전히 동일한 방식으로 잘 계산됨&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그러나&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;1. &quot;만약 자르는 영역 자체가 옆으로 1픽셀 이동했다면, feature map에서 다른 grid cell들이 선택됐을 텐데, 그럼 출력이 얼마나 달라졌을까?&quot;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;2. 좌표를 연속적으로 아주 조금(예: 0.001픽셀) 움직여도, &quot;어느 grid cell을 선택하느냐&quot;는 대부분 안 바뀜 (정수 격자 위에서 이산적으로 결정되니까)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;3. 그러다가 특정 지점을 넘어서면 갑자기 다른 grid cell로 선택이 튐 (계단 함수처럼)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;4. 이런 &quot;선택이 이산적으로 튀는&quot; 함수는 대부분의 지점에서 gradient = 0(애초에 좌표는 grid cell 선택에 거의 영향 없음), 경계 지점에서는 미분 불가능(불연속)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;따라서 좌표에 대한 gradient를 계산할 수 없게 되는 문제가 발생하는데, 이 방법에서는 좌표에 대한 grad를 무시한다. 그래서 approxiamte(근사)이다. 이렇게 해도 alternating training과 결과가 비슷하다고 한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;3. Non-approximate joint training&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;RoI Warping같은 방식으로 미분 불가 문제를 해결한다고 하는데, 이는 논문의 scope를 넘어가는거라 다루지 않겠다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;논문에서는 1번을 사용해서 학습시켰다.&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style5&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;여러개의 필터를 직접 적용하는 것이 아니라 앵커라는 개념을 사용해서 9개 앵커의 cls값과 좌표를 출력하는 과정이 신기했다. multibox, overfeat등 선행 연구에 대한 지식이 없어서 이해하기 힘든 부분도 있었는데, faster r-cnn모델 자체가 상당히 복잡하다고 느꼈다.. 처음 공부할때 객체탐지라는 것이 도대체 어떻게 이루어지는지 궁금했는데 결국에는 픽셀 값을 보고 예측한다는 것이 CNN에서 크게 벗어나지 않은 것 같다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;COTS대회 코드는 &lt;a href=&quot;https://www.kaggle.com/code/ryuminhyuk/cots-fasterrcnn?scriptVersionId=337169349&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;여기&lt;/a&gt;를 누르면 된다. 첫번째 제출때는 시간 초과로 끝까지 코드가 안돌아갔다. faster rcnn이 너무 무거워서 그런 줄 알았는데, 앵커 박스를 15개에서 9개로 줄이고, 모델 자체의 min/max size를 낮췄더니 5시간 44분만에 코드가 다 돌아갔다...&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이미지 업스케일링을 막고, 앵커 박스를 줄이는 것만으로도 상당한 연산량 감소의 효과가 있는 것 같다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;생각보다 집중하게 힘들어서 리뷰를 오랬동안 했다. 다음으로는 YOLO를 리뷰해보려고 하고, 이제 슬슬 nlp를 공부해보고 싶다.&lt;/p&gt;</description>
      <author>rminhyuk</author>
      <guid isPermaLink="true">https://icedtea.tistory.com/8</guid>
      <comments>https://icedtea.tistory.com/entry/%EB%A6%AC%EB%B7%B0Faster-R-CNNTowards-Real-Time-Object-Detection-with-Region-Proposal-NetworksNIPS-2015#entry8comment</comments>
      <pubDate>Thu, 23 Jul 2026 22:02:13 +0900</pubDate>
    </item>
    <item>
      <title>선형사상과 딥러닝</title>
      <link>https://icedtea.tistory.com/entry/%EC%84%A0%ED%98%95%EC%82%AC%EC%83%81%EA%B3%BC-%EB%94%A5%EB%9F%AC%EB%8B%9D</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;딥러닝 공부를 하다보면 선형대수학이 매우 중요하다는 이야기를 많이 듣게 됩니다. 선형대수를 처음 공부할때는 행렬을 그냥 계산을 하기 위한 수단 정도로만 생각하는데, 최근에 선형대수학을 이상엽 선생님의 강의로 다시 복습하다가 행렬을 바라보는 관점을 다르게 볼 수 있다는 것을 깨달아서 정리해보려고 합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;작년 8월 즈음 머신러닝 기초를 공부하고, 처음 시작하는 딥러닝 공부를 위해 '밑바닥부터 시작하는 딥러닝' 이라는 책을 읽었습니다. 이 책에서 행렬을 처음 소개할때, 다차원 배열을 간소화해서 계산하기 위한 방법 정도로 소개합니다. 실제로도 그게 맞지만, 그때는 선형대수학을 같이 공부하면서 계산법들만 익혔습니다. 행렬이 함수다, 선형변환이다 같은 설명들은 말로만 들었지 실제로는 잘 이해되지 않았습니다. 공간을 늘리고, 회전시키고, 그런 것들을 하는 건 이해하겠는데 이게 도대체 딥러닝에서 왜 중요한지 몰랐습니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;2575&quot; data-origin-height=&quot;1089&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/mvjzp/dJMb991pMcd/DHNNAwpkxUofzv1cKAZJMK/img.webp&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/mvjzp/dJMb991pMcd/DHNNAwpkxUofzv1cKAZJMK/img.webp&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/mvjzp/dJMb991pMcd/DHNNAwpkxUofzv1cKAZJMK/img.webp&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fmvjzp%2FdJMb991pMcd%2FDHNNAwpkxUofzv1cKAZJMK%2Fimg.webp&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;638&quot; height=&quot;270&quot; data-origin-width=&quot;2575&quot; data-origin-height=&quot;1089&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;먼저 선형사상이 딥러닝 학습과 무슨 연결성이 있는지 살펴보겠습니다. 간단히 말해, &lt;b&gt;딥러닝의 학습&lt;/b&gt;이란, 데이터를 잘 분류할 수 있도록 공간을 변형하는 &lt;b&gt;선형사상과 비선형사상의 합성을 찾는 과정&lt;/b&gt;입니다. 여기서 &quot;추상적인 사상으로 해석하는 게 무슨 의미냐&quot;는 의문이 들 수 있습니다. 핵심은 선형사상의 조건인 &lt;b&gt;'가법성과 동차성'을 기하학적으로 해석&lt;/b&gt;했을 때 비로소 드러납니다. 이 규칙을 따르는 선형사상은 고차원 공간 속에서 데이터를 구기거나 찢지 않고, 원점을 고정한 채 회전(Rotation), 스케일(Scaling), 전단(Shearing) 등의 조합을 통해 공간을 변형합니다.. 즉, 얽혀 있는 고차원 데이터 공간을 이리저리 돌리고 늘려서 '&lt;b&gt;직선(또는 초평면)으로 분류하기 좋게 정렬하는 행위&lt;/b&gt;' 그 자체가 바로 선형사상의 본질인 것입니다.(실제 신경망의 각 레이어는 y = Wx + b로, 편향 b가 붙은 아핀(Affine) 사상입니다. 이 b가 바로 회전&amp;middot;스케일&amp;middot;전단만으로는 할 수 없는 '평행이동'을 담당하며, 그 덕분에 원점에 얽매이지 않고 자유롭게 공간을 재배치할 수 있습니다.) 그러나 이 회전&amp;middot;스케일&amp;middot;전단만으로는 XOR 문제처럼 하나의 초평면으로 절대 나눌 수 없는 형태로 얽힌 데이터는 풀어낼 수 없습니다. 여기서 ReLU나 sigmoid 같은 비선형사상이 개입합니다. 비선형 사상은 공간을 비선형적으로 왜곡하여 선형적으로 분리 불가능한 데이터를 새로운 표현 공간에서는 선형 분리 가능하게 만들어줍니다. 즉, 얽힌 공간을 다시 선형사상이 다룰 수 있는 형태로 바꿔주는 것입니다. &lt;b&gt;결국 딥러닝의 각 레이어는 이 &quot;선형으로 펴고 비선형으로 접는&quot; 과정을 반복해서 쌓는 구조입니다.&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그런데 여기서 회전, 스케일, 전단이라는 이 기하학적 연산, 선형사상을 컴퓨터는 대체 어떻게 실행하는지 의문이 생깁니다. 컴퓨터는 &quot;회전시켜라&quot;같은 추상적 명령을 이해하지 못합니다. 오직 숫자들의 곱셈과 덧셈만 할 수 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이때 임의의 선형사상 T: V &amp;rarr; W는 각 공간에 기저를 고정하는 순간 유일한 행렬로 표현되고, 이 대응은 벡터공간으로서의 동형사상(isomorphism)을 이룹니다.(체 F 위의 두 벡터공간 V, W에 대하여, 이들 사이의 선형사상 집합 L(V, W)가 이루는 벡터공간과, F위의 m*n 행렬 집합 M(F)가 이루는 벡터공간 사이에는 완벽한 동형사상(Isomorphism)이 존재함이 수학적으로 증명되어 있습니다.)&amp;mdash; &lt;b&gt;즉 선형사상들의 대수적 구조와 행렬들의 대수적 구조는 본질적으로 같습니다&lt;/b&gt;. 컴퓨터가 실수 좌표(숫자 배열)만 다룰 수 있다는 점에서, 이 동형사상이야말로 &quot;추상적인 선형사상&quot;을 &quot;컴퓨터가 계산할 수 있는 행렬곱&quot;으로 옮겨오는 다리 역할을 합니다. &lt;b&gt;선형사상을 행렬로 말할 수 있게 되는 것입니다.&lt;/b&gt; 그래서 &lt;b&gt;행렬을 단순히 일차식 여러 개를 한 번에 계산하기 위한 표기법으로 보기보다는, 선형사상 그 자체를 좌표로 옮겨 적은 것으로 이해&lt;/b&gt;하는 편이 낫습니다. 이 관점에서 보면 손실함수의 그래디언트 &amp;part;L/&amp;part;W 역시 원소 하나하나의 미분값 모음 이상의 의미를 갖습니다 &amp;mdash; W와 같은 shape을 가진 이 행렬은, 행렬 공간(그 자체로 벡터공간) 안에서 손실을 가장 빠르게 낮추는 국소적 방향을 가리킵니다.(다만 이는 어디까지나 지역적(local) 정보이며, 실제 loss landscape이 non-convex이기 때문에 이 방향이 전역적으로 &quot;더 나은 연산자&quot;를 보장하지는 않습니다. 이는 다음에 다시 다뤄보도록 하겠습니다.)&lt;/p&gt;</description>
      <author>rminhyuk</author>
      <guid isPermaLink="true">https://icedtea.tistory.com/7</guid>
      <comments>https://icedtea.tistory.com/entry/%EC%84%A0%ED%98%95%EC%82%AC%EC%83%81%EA%B3%BC-%EB%94%A5%EB%9F%AC%EB%8B%9D#entry7comment</comments>
      <pubDate>Sun, 12 Jul 2026 11:09:48 +0900</pubDate>
    </item>
    <item>
      <title>[리뷰]CheXNet: Radiologist-Level Pneumonia Detection on Chest X-Rays with Deep Learning(arXiv preprint, 2017)</title>
      <link>https://icedtea.tistory.com/entry/%EB%A6%AC%EB%B7%B0CheXNet-Radiologist-Level-Pneumonia-Detection-on-Chest-X-Rays-with-Deep-LearningarXiv-preprint-2017</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;딥러닝 공부를 시작한 후로 처음 읽은 논문입니다. 6월 초 즈음에 읽고 구현했어서 기억은 잘 안나지만 노트에 정리한 내용을 블로그에 작성해보려고 합니다. 처음으로 논문을 읽고, 재현, grad-cam까지 해보았던 프로젝트라 다시 정리해보고 싶었습니다. 논문 리뷰 후 재현, grad-cam 코드도 리뷰해보겠습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;논문을 읽게 된 계기는 지금까지 책을 보며 여러 CNN 모델 구조를 배웠지만 사전학습된 모델로 추론을 하거나 ImageNet데이터만을 사용해서 학습을 시켰습니다. 그래서 실제 데이터를 활용해서 유의미한 결과를 만들어보고 싶어서 읽게 되었고,&amp;nbsp; 임상 분야에 CNN을 적용한 연구들은 이 논문 말고도 피부암 분류, 당뇨망막병증 분류 등 다른 논문들도 많지만 ChestX-ray14가 캐글에 공개되어 있어서 직접 구현해볼 목적으로 선택하였습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;아래 링크는 캐글 데이터셋을 활용해 논문을 구현해본 노트북입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://www.kaggle.com/code/ryuminhyuk/chexnet-baseline?scriptVersionId=332615635&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;[논문 구현]&lt;/a&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://www.kaggle.com/code/ryuminhyuk/chexnet-gradcam&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;[Grad-CAM 시각화]&lt;/a&gt;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style5&quot; /&gt;
&lt;ul style=&quot;list-style-type: disc; color: #333333; text-align: start;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li style=&quot;list-style-type: disc; color: #000000;&quot;&gt;제목: CheXNet:&amp;nbsp;Radiologist-Level&amp;nbsp;Pneumonia&amp;nbsp;Detection&amp;nbsp;on&amp;nbsp;Chest&amp;nbsp;X-Rays&amp;nbsp;with&amp;nbsp;Deep&amp;nbsp;Learning&lt;/li&gt;
&lt;li style=&quot;list-style-type: disc; color: #000000;&quot;&gt;저자:Pranav Rajpurkar*, Jeremy Irvin*, Kaylie Zhu, Brandon Yang, Hershel Mehta, Tony Duan, Daisy Ding, Aarti Bagul, Curtis Langlotz, Katie Shpanskaya, Matthew P. Lungren, Andrew Y. Ng&lt;span style=&quot;font-family: AppleSDGothicNeo-Regular, 'Malgun Gothic', '맑은 고딕', dotum, 돋움, sans-serif;&quot;&gt;&lt;/span&gt; (Stanford ML Group)&lt;/li&gt;
&lt;li style=&quot;list-style-type: disc; color: #000000;&quot;&gt;게재 학회: arXiv에 프리프린트로 공개&lt;/li&gt;
&lt;li style=&quot;list-style-type: disc; color: #000000;&quot;&gt;인용수: 5014회(2026.07.04)&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;images.jpg&quot; data-origin-width=&quot;300&quot; data-origin-height=&quot;666&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/tWeg9/dJMcajixe7J/YlmfEUCE0DPrZgJ2GaUKB1/img.jpg&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/tWeg9/dJMcajixe7J/YlmfEUCE0DPrZgJ2GaUKB1/img.jpg&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/tWeg9/dJMcajixe7J/YlmfEUCE0DPrZgJ2GaUKB1/img.jpg&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FtWeg9%2FdJMcajixe7J%2FYlmfEUCE0DPrZgJ2GaUKB1%2Fimg.jpg&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;300&quot; height=&quot;666&quot; data-filename=&quot;images.jpg&quot; data-origin-width=&quot;300&quot; data-origin-height=&quot;666&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;h2 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;Abstract&lt;/b&gt;&lt;/h2&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;14개의 질병에 대한 정보를 담고 있는 10만장 이상의 X-ray dataset을 사용해 학습시킨 CheXNet이 폐렴(pneumonia) 진단에 있어서 영상의학과 전문의(radiologist)를 능가하는 성능을 보였습니다. 전문의와 모델의 평가 지표는 F1 metric이였고, 14개 질환 전부를 예측했을 때는 SOTA를 달성하는 성능을 보여줬습니다.&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;CheXNet&lt;/b&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;1. Problem Formulation&lt;/b&gt;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;627&quot; data-origin-height=&quot;119&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bpsXSy/dJMcaa0bkds/krPx6vldRueBid5u4pbc81/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bpsXSy/dJMcaa0bkds/krPx6vldRueBid5u4pbc81/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bpsXSy/dJMcaa0bkds/krPx6vldRueBid5u4pbc81/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbpsXSy%2FdJMcaa0bkds%2FkrPx6vldRueBid5u4pbc81%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;627&quot; height=&quot;119&quot; data-origin-width=&quot;627&quot; data-origin-height=&quot;119&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;손실함수는 단일 라벨 예측에 있어서는 Binary cross entropy loss를 사용&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;2. Model Architecture and Training&lt;/b&gt;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;628&quot; data-origin-height=&quot;262&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/b1C7zl/dJMcagGaTA8/eoVIiHpGMYN6sMXSN1ntx0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/b1C7zl/dJMcagGaTA8/eoVIiHpGMYN6sMXSN1ntx0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/b1C7zl/dJMcagGaTA8/eoVIiHpGMYN6sMXSN1ntx0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fb1C7zl%2FdJMcagGaTA8%2FeoVIiHpGMYN6sMXSN1ntx0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;628&quot; height=&quot;262&quot; data-origin-width=&quot;628&quot; data-origin-height=&quot;262&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;기본적으로 121-layer의 DenseNet을 사용했고, 마지막 분류 계층은 출력값이 한개인 fc 레이어로 설정했습니다. 이후에 시그모이드 함수를 통과시켜서 loss 계산을 하였습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;DenseNet을 간단하게 설명하자면&lt;/b&gt;, 기존 ResNet의 residual connection은 입력값을 출력값에 더하는 방식으로 기울기 저하 문제를 해결했습니다. DenseNet은 피쳐맵을 더하는 방식이 아닌 n번째 피쳐맵을 1~(n-1)번째 피쳐맵까지 concat해서 구합니다. 이를 통해 정보를 더 보존할 수 있게 되었고, gradient가 훨씬 더 짧은 경로로 초기 레이어까지 직접 전파됩니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;651&quot; data-origin-height=&quot;303&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/Ffqrn/dJMcaicVxZx/evfQBNGHbNtUS2PUAKcMFk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/Ffqrn/dJMcaicVxZx/evfQBNGHbNtUS2PUAKcMFk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/Ffqrn/dJMcaicVxZx/evfQBNGHbNtUS2PUAKcMFk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FFfqrn%2FdJMcaicVxZx%2FevfQBNGHbNtUS2PUAKcMFk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;651&quot; height=&quot;303&quot; data-origin-width=&quot;651&quot; data-origin-height=&quot;303&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;ImageNet으로 사전학습된 DenseNet을 사용했으며, Adam Optimizer를 사용했습니다.(파라미터는 위 이미지 참고).&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;미니배치 사이즈는 16, LR은 0.001, scheduler은 ReduceLROnPlateau를 사용해서 가장 낮은 val loss를 시록하는 모델을 사용했습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;Data&lt;/b&gt;&lt;/h2&gt;
&lt;h2 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size26&quot;&gt;&amp;nbsp;&lt;/h2&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;630&quot; data-origin-height=&quot;585&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cOox9P/dJMcadP5rdb/9EJrDZjv9pY9ZnlrgtfkAk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cOox9P/dJMcadP5rdb/9EJrDZjv9pY9ZnlrgtfkAk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cOox9P/dJMcadP5rdb/9EJrDZjv9pY9ZnlrgtfkAk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcOox9P%2FdJMcadP5rdb%2F9EJrDZjv9pY9ZnlrgtfkAk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;630&quot; height=&quot;585&quot; data-origin-width=&quot;630&quot; data-origin-height=&quot;585&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Wang et al.이 radiology reports에서 automatic extraction methods를 사용해 라벨링한 30805명 환자의 112120개 이미지 데이터(ChestX-ray14)를 사용했습니다.(추가로 조사해보니 여기서 automatic extraction methods은 NegBio라는 NLP도구를 사용한 것으로, 이 데이터셋 자체가 완벽한 ground truth가 아니라 NLP로 추출된 weak label이라 노이즈가 있다고 합니다.) 폐렴 데이터의 예측 성능 측정을 위해 폐렴이 아닌 데이터는 다 Negative로 라벨링하였습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;train/valid/test데이터는 위와 같은 숫자로 나누었고, &lt;b&gt;의도치 않은 leakage를 방지하기 위해 세트간 환자가 겹치지 않도록 나누었습니다.&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;667&quot; data-origin-height=&quot;532&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bxz1pz/dJMcabLEDHV/YxeAi4uuj4Saey0NLwBLBk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bxz1pz/dJMcabLEDHV/YxeAi4uuj4Saey0NLwBLBk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bxz1pz/dJMcabLEDHV/YxeAi4uuj4Saey0NLwBLBk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fbxz1pz%2FdJMcabLEDHV%2FYxeAi4uuj4Saey0NLwBLBk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;667&quot; height=&quot;532&quot; data-origin-width=&quot;667&quot; data-origin-height=&quot;532&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;test 데이터 예측 결과에 따른 영상의학과 전문의 4명과 모델의 F1 Score 비교입니다. 테스트셋에서 복원추출(with replacement)로 10,000개의 부트스트랩 샘플을 생성하고, 각 샘플에 대해 F1 score를 계산한 뒤, 그 분포의 2.5th~97.5th 백분위수를 95% 신뢰구간으로 사용했습니다. 평균 F1 Score가 CheXNet이 더 높은 것을 확인할 수 있습니다. 또한 차이 = (그 샘플에서의 CheXNet F1 score) - (그 샘플에서의 방사선의 평균 F1 score)라고 했을 때 차이값 분포의 95% 신뢰구간이 [0.005, 0.084]로 나왔고, 0을 포함하지 않으므로 &lt;b&gt;유의미한 차이를 보여주었다고 주장&lt;/b&gt;합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;Limitations&lt;/b&gt;&lt;/h2&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;672&quot; data-origin-height=&quot;454&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/STrUH/dJMcabxZ4My/82Uqk6ViUCfwxBnal8pZx1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/STrUH/dJMcabxZ4My/82Uqk6ViUCfwxBnal8pZx1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/STrUH/dJMcabxZ4My/82Uqk6ViUCfwxBnal8pZx1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FSTrUH%2FdJMcabxZ4My%2F82Uqk6ViUCfwxBnal8pZx1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;672&quot; height=&quot;454&quot; data-origin-width=&quot;672&quot; data-origin-height=&quot;454&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;논문 원문인데 First랑 Third 사이에 second가 빠져있습니다. 편집상의 오류인지 애초에 2개밖에 없었는지는 확인이 필요할 것 같습니다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;논문에서 언급한 첫번째 한계는 데이터가 lateral view는 없고 frontal images들 밖에 없다는 것이고, 두번째 한계는 모델과 의사 모두 환자의 과거 이력을 사용해서 예측하지 않았다는 점입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이후에 논문에서는 14개 클래스를 모두 예측해서&amp;nbsp; AUROC로 예측 성능을 평가한 결과 SOTA를 달성했다고 언급하고,&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;CAM 을 사용해서 모델 예측을 해석합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style5&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;논문을 읽으면서 의아했던 부분이 여러개 있는데&lt;/p&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;Wang et al.이 radiology reports에서 automatic extraction methods를 사용해 라벨링한 데이터가 얼마나 정확한지 안나와있다.그냥 NegBio를 써서 예측했다고 하는데 이게 실제 의사들보다 정확하지 않으면 실험이 의미가 없어진다.&lt;/li&gt;
&lt;li&gt;시드값 고정 여부 등을 알려주지 않아 실험을 재현하기 쉽지 않다. 데이터를 나눈 비율만 말해주고 있기 때문에 논문과 같은 결과를 얻기는 쉽지 않을 것 같다.&lt;/li&gt;
&lt;li&gt;전문의와 F1 Score를 비교한 표를 보면 Radiologist 4는 CheXNet보다 점수가 높은데, 1,2,3번 의사가 숙련이 덜 된 의사일 수도 있겠다고 생각했다.&lt;/li&gt;
&lt;li&gt;limitations에서 Second는 어디로 간거지&lt;/li&gt;
&lt;/ol&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그럼에도 불구하고, chest x-ray데이터를 이용해 딥러닝을 학습시키고, 전문의들과 비교해서 유의미한 결과를 도출한 것이 임상에서도 활용 가능할 수 있다는 것을 보여준 것 같습니다. 처음부터 논문을 읽고 실제로 재현해 보는 것이 목적이여서 &lt;a href=&quot;https://www.kaggle.com/code/ryuminhyuk/chexnet-baseline&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;캐글 데이터를 활용해서 최대한 재현&lt;/a&gt;해보고, &lt;a href=&quot;https://www.kaggle.com/code/ryuminhyuk/chexnet-gradcam&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;grad-cam으로 시각화&lt;/a&gt;해보았습니다.&lt;/p&gt;</description>
      <category>chexnet</category>
      <author>rminhyuk</author>
      <guid isPermaLink="true">https://icedtea.tistory.com/6</guid>
      <comments>https://icedtea.tistory.com/entry/%EB%A6%AC%EB%B7%B0CheXNet-Radiologist-Level-Pneumonia-Detection-on-Chest-X-Rays-with-Deep-LearningarXiv-preprint-2017#entry6comment</comments>
      <pubDate>Sat, 4 Jul 2026 16:26:15 +0900</pubDate>
    </item>
    <item>
      <title>코헨 카파(Cohen's Kappa), 이차 가중 카파(QWK)</title>
      <link>https://icedtea.tistory.com/entry/%EC%BD%94%ED%97%A8-%EC%B9%B4%ED%8C%8CCohens-Kappa-%EC%9D%B4%EC%B0%A8-%EA%B0%80%EC%A4%91-%EC%B9%B4%ED%8C%8C</link>
      <description>&lt;p style=&quot;text-align: left;&quot; data-ke-size=&quot;size16&quot;&gt;
&lt;script&gt;  MathJax = {    tex: {inlineMath: [['$', '$']]}  };&lt;/script&gt;
&lt;script src=&quot;https://cdn.jsdelivr.net/npm/mathjax@3/es5/tex-mml-chtml.js&quot;&gt;&lt;/script&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: left;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://icedtea.tistory.com/3&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;APTOS ViT&lt;/a&gt; 리뷰하다가 평가지표 설명이 너무 길어질 것 같아서 따로 정리하려고 합니다.&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style5&quot; /&gt;
&lt;h3 style=&quot;text-align: left;&quot; data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;1.코헨 카파(Cohen's Kappa)&lt;/b&gt;&lt;/h3&gt;
&lt;p style=&quot;text-align: left;&quot; data-ke-size=&quot;size16&quot;&gt;두 평가자의 범주형 데이터를 비교할 때, 그 평가 결과가 우연이 아니라 얼마나 실제로 일치하는지 평가하는 지표.&lt;/p&gt;
&lt;p style=&quot;text-align: left;&quot; data-ke-size=&quot;size16&quot;&gt;왜 그냥 비교($P_o$)를 하지 않고 이 지표를 사용하는지 예시를 들어 비교해보겠습니다.&lt;/p&gt;
&lt;p style=&quot;text-align: left;&quot; data-ke-size=&quot;size16&quot;&gt;$$\begin{aligned}&lt;br /&gt;\kappa&amp;nbsp;&amp;amp;=&amp;nbsp;\frac{P_o&amp;nbsp;-&amp;nbsp;P_e}{1&amp;nbsp;-&amp;nbsp;P_e}&amp;nbsp;\\&lt;br /&gt;&amp;amp;=&amp;nbsp;1&amp;nbsp;-&amp;nbsp;\frac{1&amp;nbsp;-&amp;nbsp;P_o}{1&amp;nbsp;-&amp;nbsp;P_e}&amp;nbsp;\\&lt;br /&gt;&amp;amp;=&amp;nbsp;1&amp;nbsp;-&amp;nbsp;\frac{\text{실제&amp;nbsp;불일치할&amp;nbsp;확률}}{\text{우연히&amp;nbsp;불일치할&amp;nbsp;확률}}&lt;br /&gt;\end{aligned}$$&lt;/p&gt;
&lt;p style=&quot;text-align: left;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;예를 들어 클래스가 2개(양성/음성)뿐인 문제를 생각해봅시다. 두 평가자 A, B가 100개 샘플을 각각 평가했는데, 우연히 둘 다 &quot;거의 항상 양성&quot;이라고 찍는 경향이 있다고 하면,&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;A는 95개를 양성, 5개를 음성이라고 판정&lt;/li&gt;
&lt;li&gt;B도 95개를 양성, 5개를 음성이라고 판정&lt;/li&gt;
&lt;li&gt;실제로 둘이 같은 샘플에 동의한 게 90개&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;$P_o=90%$. 그런데 둘 다 그냥 &quot;무조건 양성&quot;이라고 찍는 성향이 강해서, 실제로 각 샘플을 제대로 판단해서 일치한 게 아니라 &lt;b&gt;둘 다 편향되게 같은 답을 자주 냈을 뿐입니다.&lt;/b&gt; 즉 우연히 겹칠 확률 자체가 이미 높은 상황&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;$P_e$가 하는 일&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;&lt;span aria-hidden=&quot;true&quot;&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;​$P_e$&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;는 &quot;두 평가자가 각자의 라벨 분포를 유지한 채, &lt;b&gt;완전히 독립적으로 무작위&lt;/b&gt;로 라벨을 매겼다면 우연히 일치했을 확률&quot;을 계산합니다. 위 예시라면 대략:&lt;/p&gt;
&lt;p style=&quot;text-align: left;&quot; data-ke-size=&quot;size16&quot;&gt;$$P_e&amp;nbsp;=&amp;nbsp;(0.95&amp;nbsp;\times&amp;nbsp;0.95)&amp;nbsp;+&amp;nbsp;(0.05&amp;nbsp;\times&amp;nbsp;0.05)&amp;nbsp;=&amp;nbsp;0.905$$&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;즉 아무 실력 없이 그냥 분포만 맞춰서 찍어도 90.5%는 우연히 일치합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그런데 이때 실제 관측된 일치율이 90%였으므로&lt;/p&gt;
&lt;p style=&quot;text-align: left;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;&lt;span&gt;&lt;span aria-hidden=&quot;true&quot;&gt;&lt;span&gt;&lt;span&gt;$$\kappa=\frac{0.90 - 0.905}{1 - 0.905} \approx -0.05$$ &lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;카파 값이 아주 작게 나와서, 실제로는 거의 일치하지 않는다고 판단할 수 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;2.이차 가중 카파(Quadretic weighted kappa)&lt;/b&gt;&lt;/h3&gt;
&lt;p style=&quot;text-align: left;&quot; data-ke-size=&quot;size16&quot;&gt;코헨 카파에서 나온 평가지표로, 정답과 예측값의 거리 차이의 제곱(Quadratic)만큼 페널티를 부여하게 됩니다.&lt;/p&gt;
&lt;div data-math=&quot;QWK = 1 - \frac{\sum W_{ij} O_{ij}}{\sum W_{ij} E_{ij}}&quot;&gt;이때 행렬 $O$는 정답 예측 결과를 비교한&amp;nbsp; $k \times k$행렬&lt;/div&gt;
&lt;div data-math=&quot;QWK = 1 - \frac{\sum W_{ij} O_{ij}}{\sum W_{ij} E_{ij}}&quot;&gt;
&lt;ul style=&quot;list-style-type: disc; color: #333333; text-align: start;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;span data-math=&quot;k&quot; data-index-in-node=&quot;0&quot;&gt;$k$&lt;/span&gt;: 전체 클래스의 개수 (APTOS 대회의 경우 0, 1, 2, 3, 4 총&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;b data-path-to-node=&quot;3,2,0&quot; data-index-in-node=&quot;44&quot;&gt;5&lt;/b&gt;개이므로&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;span data-math=&quot;k = 5&quot; data-index-in-node=&quot;50&quot;&gt;$k = 5$&lt;/span&gt;)&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;
&lt;div data-math=&quot;QWK = 1 - \frac{\sum W_{ij} O_{ij}}{\sum W_{ij} E_{ij}}&quot;&gt;행렬 $W$는 가중치 행렬($k \times k$)로&lt;/div&gt;
&lt;div data-math=&quot;QWK = 1 - \frac{\sum W_{ij} O_{ij}}{\sum W_{ij} E_{ij}}&quot;&gt;&lt;span style=&quot;letter-spacing: 0px;&quot;&gt;$$W_{ij} = \frac{(i - j)^2}{(k - 1)^2}$$&lt;/span&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;span data-index-in-node=&quot;0&quot; data-math=&quot;i&quot;&gt;$i$&lt;/span&gt;: 실제 정답의 클래스 번호 (0부터 시작)&lt;/li&gt;
&lt;li&gt;&lt;span data-index-in-node=&quot;0&quot; data-math=&quot;j&quot;&gt;$j$&lt;/span&gt;: 모델이 예측한 클래스 번호 (0부터 시작)&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;/div&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;행렬 $E$는 코헨 카파의 ​$P_e$와 같은 역할을 합니다. &lt;span data-index-in-node=&quot;93&quot; data-math=&quot;E_{ij}&quot;&gt;$E_{ij}$는 정답은 &lt;span data-index-in-node=&quot;107&quot; data-math=&quot;i&quot;&gt;$i$&lt;/span&gt;인데 모델이 &lt;span data-index-in-node=&quot;115&quot; data-math=&quot;j&quot;&gt;$j$&lt;/span&gt;라고 우연히 예측할 '예상 빈도수'를 의미합니다.&lt;/span&gt;&lt;/p&gt;
&lt;div data-math=&quot;E_{ij} = \frac{(\text{정답이 } i\text{인 실제 개수}) \times (\text{모델이 } j\text{라고 예측한 총 개수})}{N}&quot;&gt;$$E_{ij} = \frac{(\text{정답이 } i\text{인 실제 개수}) \times (\text{모델이 } j\text{라고 예측한 총 개수})}{N}$$&lt;/div&gt;
&lt;div data-math=&quot;E_{ij} = \frac{(\text{정답이 } i\text{인 실제 개수}) \times (\text{모델이 } j\text{라고 예측한 총 개수})}{N}&quot;&gt;
&lt;p data-path-to-node=&quot;12&quot; data-ke-size=&quot;size16&quot;&gt;예를 들어 클래스를 3개(0, 1, 2)라 치고, 전체 데이터 &lt;span data-index-in-node=&quot;39&quot; data-math=&quot;N = 100&quot;&gt;$N = 100$&lt;/span&gt;장이라고 해봅시다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-path-to-node=&quot;13&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;정답은 100장 중: 0을 60장, 1을 30장, 2를 10장 입니다.&lt;/li&gt;
&lt;li&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;13,1,0&quot;&gt;모델&lt;/b&gt;은 100장 중: 0을 50장, 1을 40장, 2를 10장 예측했습니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-path-to-node=&quot;14&quot; data-ke-size=&quot;size16&quot;&gt;이제 두 평가자가 아무 생각 없이 막 던졌을 때 만들어질 기댓값 행렬 &lt;span data-index-in-node=&quot;39&quot; data-math=&quot;E&quot;&gt;$E$&lt;/span&gt;의 각 칸을 채워보면 이렇습니다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-path-to-node=&quot;15&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;15,0,0&quot;&gt;&lt;span data-index-in-node=&quot;0&quot; data-math=&quot;E_{00}&quot;&gt;$E_{00}$&lt;/span&gt; 칸 (정답 0, 예측 0):&lt;/b&gt; &lt;span data-index-in-node=&quot;23&quot; data-math=&quot;\frac{60 \times 50}{100} = 30&quot;&gt;$\frac{60 \times 50}{100} = 30$&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;15,1,0&quot;&gt;&lt;span data-index-in-node=&quot;0&quot; data-math=&quot;E_{01}&quot;&gt;$E_{01}$&lt;/span&gt; 칸 (정답 0, 예측 1):&lt;/b&gt; &lt;span data-index-in-node=&quot;23&quot; data-math=&quot;\frac{60 \times 40}{100} = 24&quot;&gt;$\frac{60 \times 40}{100} = 24$&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;15,2,0&quot;&gt;&lt;span data-index-in-node=&quot;0&quot; data-math=&quot;E_{21}&quot;&gt;$E_{21}$&lt;/span&gt; 칸 (정답 2, 예측 1):&lt;/b&gt; &lt;span data-index-in-node=&quot;23&quot; data-math=&quot;\frac{10 \times 40}{100} = 4&quot;&gt;$\frac{10 \times 40}{100} = 4$&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-path-to-node=&quot;16&quot; data-ke-size=&quot;size16&quot;&gt;이런 식으로 &lt;span data-index-in-node=&quot;7&quot; data-math=&quot;3 \times 3&quot;&gt;$3 \times 3$&lt;/span&gt;의 모든 칸을 채우면 행렬 &lt;span data-index-in-node=&quot;53&quot; data-math=&quot;E&quot;&gt;$E$&lt;/span&gt;가 완성됩니다.&lt;/p&gt;
&lt;/div&gt;
&lt;div data-math=&quot;E_{ij} = \frac{(\text{정답이 } i\text{인 실제 개수}) \times (\text{모델이 } j\text{라고 예측한 총 개수})}{N}&quot;&gt;
&lt;p data-path-to-node=&quot;22&quot; data-ke-size=&quot;size16&quot;&gt;최종 QWK 점수는 이 세 행렬을 가지고 아래 공식을 거쳐 계산됩니다.&lt;/p&gt;
&lt;div data-path-to-node=&quot;23&quot;&gt;
&lt;div data-math=&quot;QWK = 1 - \frac{\sum W_{ij} O_{ij}}{\sum W_{ij} E_{ij}}&quot;&gt;$$QWK = 1 - \frac{\sum W_{ij} O_{ij}}{\sum W_{ij} E_{ij}}$$&lt;/div&gt;
&lt;div data-math=&quot;QWK = 1 - \frac{\sum W_{ij} O_{ij}}{\sum W_{ij} E_{ij}}&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div data-math=&quot;QWK = 1 - \frac{\sum W_{ij} O_{ij}}{\sum W_{ij} E_{ij}}&quot;&gt;이는 곧&lt;/div&gt;
&lt;div data-math=&quot;QWK = 1 - \frac{\sum W_{ij} O_{ij}}{\sum W_{ij} E_{ij}}&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div data-math=&quot;QWK = 1 - \frac{\sum W_{ij} O_{ij}}{\sum W_{ij} E_{ij}}&quot;&gt;$$QWK&amp;nbsp;=&amp;nbsp;1&amp;nbsp;-&amp;nbsp;\frac{\text{모델&amp;nbsp;예측값이&amp;nbsp;발생하는&amp;nbsp;총&amp;nbsp;패널티}}{\text{찍었을&amp;nbsp;때&amp;nbsp;발생하는&amp;nbsp;총&amp;nbsp;패널티}}$$&lt;/div&gt;
&lt;div data-math=&quot;QWK = 1 - \frac{\sum W_{ij} O_{ij}}{\sum W_{ij} E_{ij}}&quot;&gt;를 의미합니다.&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;div data-math=&quot;QWK = 1 - \frac{\sum W_{ij} O_{ij}}{\sum W_{ij} E_{ij}}&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;p style=&quot;text-align: left;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: left;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>카파</category>
      <author>rminhyuk</author>
      <guid isPermaLink="true">https://icedtea.tistory.com/4</guid>
      <comments>https://icedtea.tistory.com/entry/%EC%BD%94%ED%97%A8-%EC%B9%B4%ED%8C%8CCohens-Kappa-%EC%9D%B4%EC%B0%A8-%EA%B0%80%EC%A4%91-%EC%B9%B4%ED%8C%8C#entry4comment</comments>
      <pubDate>Fri, 3 Jul 2026 08:48:08 +0900</pubDate>
    </item>
    <item>
      <title>Kaggle- APTOS 2019 Blindness Detection 리뷰(ViT)</title>
      <link>https://icedtea.tistory.com/entry/Kaggle-APTOS-2019-Blindness-Detection-%EB%A6%AC%EB%B7%B0ViT</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;
&lt;script&gt;  MathJax = {    tex: {inlineMath: [['$', '$']]}  };&lt;/script&gt;
&lt;script src=&quot;https://cdn.jsdelivr.net/npm/mathjax@3/es5/tex-mml-chtml.js&quot;&gt;&lt;/script&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이번에는 APTOS 2019 Blindness Detection 대회를 리뷰해보려고 합니다. 2019년은 구글의 EfficientNet이 발표된지 얼마 안된 시점이여서 상위권 팀들의 코드를 보면 EfficientNet 계열을 앙상블한 코드가 많이 보입니다. &lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;ViT 모델을 공부하고, 적용해볼만한 데이터셋을 찾던 와중 2019년에 캐글에서 3600장의 실제 안저 사진을 이용해 주최한 대회를 알게되었습니다.&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt; 대회가 끝난지는 오래되었지만, 의료데이터에 비전 트랜스포머 모델을 적용해보고 싶어서 늦게나마 참여하게 되었습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;아래는 해당 노트북 링크입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://www.kaggle.com/code/ryuminhyuk/aptos-vit-baseline&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;[캐글 노트북]&lt;/a&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;주최: APTOS(Asia Pacific Tele-Ophthalmology Society, 아시아 태평양 원격안과 의학회)&lt;/li&gt;
&lt;li&gt;목표: 인도의 Aravind Eye Hospital이 의료 스크리닝이 어려운 시골 지역에서 당뇨망막병증 환자를 발견하고 예방&lt;/li&gt;
&lt;/ul&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style5&quot; /&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;대회 개요&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://www.kaggle.com/competitions/aptos2019-blindness-detection/overview&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://www.kaggle.com/competitions/aptos2019-blindness-detection/overview&lt;/a&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1782988560335&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-ke-align=&quot;alignCenter&quot; data-og-type=&quot;website&quot; data-og-title=&quot;APTOS 2019 Blindness Detection&quot; data-og-description=&quot;Detect diabetic retinopathy to stop blindness before it's too late&quot; data-og-host=&quot;www.kaggle.com&quot; data-og-source-url=&quot;https://www.kaggle.com/competitions/aptos2019-blindness-detection/overview&quot; data-og-url=&quot;https://kaggle.com/aptos2019-blindness-detection&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/dZeske/dJMb8UaaOys/MuTt00StLvTwTKXo9AU3BK/img.jpg?width=1900&amp;amp;height=400&amp;amp;face=0_0_1900_400,https://scrap.kakaocdn.net/dn/b0C0xb/dJMb8WeKRTB/ybCmrkPasVy0Fk0HuEHRZK/img.jpg?width=1900&amp;amp;height=400&amp;amp;face=0_0_1900_400&quot;&gt;&lt;a href=&quot;https://www.kaggle.com/competitions/aptos2019-blindness-detection/overview&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://www.kaggle.com/competitions/aptos2019-blindness-detection/overview&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/dZeske/dJMb8UaaOys/MuTt00StLvTwTKXo9AU3BK/img.jpg?width=1900&amp;amp;height=400&amp;amp;face=0_0_1900_400,https://scrap.kakaocdn.net/dn/b0C0xb/dJMb8WeKRTB/ybCmrkPasVy0Fk0HuEHRZK/img.jpg?width=1900&amp;amp;height=400&amp;amp;face=0_0_1900_400');&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;og-text&quot;&gt;
&lt;p class=&quot;og-title&quot; data-ke-size=&quot;size16&quot;&gt;APTOS 2019 Blindness Detection&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;Detect diabetic retinopathy to stop blindness before it's too late&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;www.kaggle.com&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;background-color: #ffffff; color: #3c4043; text-align: start;&quot;&gt;Kernels-only competition이며, 여러 지역에서 얻은 &lt;span style=&quot;background-color: #ffffff; color: #3c4043; text-align: start;&quot;&gt;retina images를 사용해 모델을 학습시키고, 예측하는 대회입니다.&lt;/span&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;label 0은 무증상, 4로 갈수록 증상이 심해집니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;평가지표&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;&lt;span style=&quot;background-color: #ffffff; color: #3c4043; text-align: start;&quot;&gt;Quadratic weighted kappa(이차 가중 카파)&lt;/span&gt;&lt;/b&gt;&lt;span style=&quot;background-color: #ffffff; color: #3c4043; text-align: start;&quot;&gt;를 사용합니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;자세한 내용은 &lt;a href=&quot;https://icedtea.tistory.com/4&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;이차 가중 카파&lt;/a&gt;를 읽어보시기 바랍니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;간단하게 설명하자면, 예측과 정답의 차이가 클수록 점수를 더 크게 깎는 지표입니다. label 4데이터를 0으로 예측한것과 3으로 에측한 것이 똑같은 오답 처리가 되면 안되기 때문에 사용합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;또한, 그 평가 결과가 우연이 아니라 얼마나 실제로 일치하는지 평가하기도 합니다. 이는 데이터에 특정 클래스(무증상 등)이 많을때 모델이 무조건 무증상으로 예측하는 것을 막기 위함입니다.&lt;/p&gt;
&lt;div data-math=&quot;QWK = 1 - \frac{\sum W_{ij} O_{ij}}{\sum W_{ij} E_{ij}}&quot;&gt;$$QWK = 1 - \frac{\sum W_{ij} O_{ij}}{\sum W_{ij} E_{ij}}$$&lt;/div&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style5&quot; /&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;탐색적 데이터 분석(Exploratory Data Analysis)&lt;/b&gt;&lt;span data-copy-service-computed-style=&quot;font-family: Arial, sans-serif; font-size: 16px; font-weight: 500; margin: 0px; text-decoration: none; border-bottom: 0px none rgb(0, 29, 53);&quot; data-sfc-cb=&quot;&quot; data-sfc-root=&quot;ep&quot; data-sfc-cp=&quot;&quot;&gt; &lt;/span&gt;&lt;/h3&gt;
&lt;pre class=&quot;coffeescript&quot;&gt;&lt;code&gt;# This Python 3 environment comes with many helpful analytics libraries installed
# It is defined by the kaggle/python Docker image: https://github.com/kaggle/docker-python
# For example, here's several helpful packages to load

import numpy as np # linear algebra
import pandas as pd # data processing, CSV file I/O (e.g. pd.read_csv)

# Input data files are available in the read-only &quot;../input/&quot; directory
# For example, running this (by clicking run or pressing Shift+Enter) will list all files under the input directory

import os

count=0

for dirname, _, filenames in os.walk('/kaggle/input'):
    for filename in filenames:
        print(os.path.join(dirname, filename))
        count+=1

        if count==20:
            break

    if count==20:
        break


# You can write up to 20GB to the current directory (/kaggle/working/) that gets preserved as output when you create a version using &quot;Save &amp;amp; Run All&quot; 
# You can also write temporary files to /kaggle/temp/, but they won't be saved outside of the current session

# Use the kagglehub client library to attach Kaggle resources like competitions, datasets, and models to your session
# Learn more about kagglehub: https://github.com/Kaggle/kagglehub/blob/main/README.md

import kagglehub
# kagglehub.dataset_download('&amp;lt;owner&amp;gt;/&amp;lt;dataset-slug&amp;gt;')&lt;/code&gt;&lt;/pre&gt;
&lt;pre class=&quot;yaml&quot; style=&quot;background-color: #f8f8f8; color: #383a42; text-align: start;&quot;&gt;&lt;code&gt;from dataclasses import dataclass

@dataclass        #__init__을 생략해줌. 기존 방식(클래스에 속성 부여)은 속성 하나 바꾸려면 클래스 자체가 바뀌어야 했는데, 이 방식은 a=BaseConfig(BATCH_SIZE=64)으로 변경 가능 
class BaseConfig:
    EPOCHS: int = 10
    BATCH_SIZE: int = 32
    LR: float = 3e-5


    IMG_SIZE: int = 384
    SEED: int = 42&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;dataclass모듈을 사용해서 Config를 정의합니다. 이렇게 하면 클래스 자체를 건들지 않고도 속성들을 조정할 수 있게 됩니다.&lt;/p&gt;
&lt;pre class=&quot;elm&quot;&gt;&lt;code&gt;import matplotlib.pyplot as plt
from glob import glob  #glob 라이브러리 안에 glob 함수 사용
from PIL import Image
import cv2
import numpy as np&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre class=&quot;bash&quot; data-ke-language=&quot;bash&quot;&gt;&lt;code&gt;import pandas as pd
import numpy as np

set_seed()
SEED=42
config=BaseConfig()

#학습/테스트 데이터 목록 확인
train_df=pd.read_csv('../input/competitions/aptos2019-blindness-detection/train.csv')
test_df=pd.read_csv('../input/competitions/aptos2019-blindness-detection/test.csv')
train_df = train_df.sample(frac=1, random_state=config.SEED).reset_index(drop=True)  #df의 행을 랜덤 샘플링(frac은 샘플링 비율을 설정)-아렇게 해야 같은 병변끼리 모여있는 상황 최소화
#random state 안쓰면 넘파이 난수 엔진 참고, 따로 고정할 필요 없음

train_df.head(3)&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;데이터 분포 확인&lt;/h3&gt;
&lt;pre class=&quot;routeros&quot;&gt;&lt;code&gt;from sklearn.model_selection import train_test_split

x=train_df['id_code']
y=train_df['diagnosis']

train_x, valid_x, train_y, valid_y=train_test_split(x, y, test_size=0.15, stratify=y)
print(train_x.shape, valid_x.shape)

fig, axes=plt.subplots(1,2, figsize=(10,4))
train_y.hist(ax=axes[0])
axes[0].set_title('Train Class Distribution')

valid_y.hist(ax=axes[1])
axes[1].set_title('Valid Class Distribution')&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;화면 캡처 2026-07-03 101013.png&quot; data-origin-width=&quot;841&quot; data-origin-height=&quot;506&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/dMyYCc/dJMcai4T32k/Sk9GFJX4iIqJ8iMW08Wsbk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/dMyYCc/dJMcai4T32k/Sk9GFJX4iIqJ8iMW08Wsbk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/dMyYCc/dJMcai4T32k/Sk9GFJX4iIqJ8iMW08Wsbk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FdMyYCc%2FdJMcai4T32k%2FSk9GFJX4iIqJ8iMW08Wsbk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;761&quot; height=&quot;458&quot; data-filename=&quot;화면 캡처 2026-07-03 101013.png&quot; data-origin-width=&quot;841&quot; data-origin-height=&quot;506&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;데이터 분포를 확인해보면 무증상(0) 데이터가 가장 많고, 1,3,4 클래스의 데이터는 많이 부족한 불균형 데이터입니다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&amp;nbsp;&lt;/h3&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;Image Plot&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;실제 이미지를 확인해보겠습니다.&lt;/p&gt;
&lt;pre class=&quot;lua&quot;&gt;&lt;code&gt;#train_df에 이미지 path 만들기
all_image_path={os.path.basename(x).replace('.png',''):x for x in
               glob(os.path.join('..','input','competitions','aptos2019-blindness-detection','train_images','*.png'))}
print('Scans Found:', len(all_image_path), 'Total image Index:', len(train_df))

train_df['path']=train_df['id_code'].map(all_image_path.get)
train_df.head()&lt;/code&gt;&lt;/pre&gt;
&lt;pre class=&quot;lua&quot;&gt;&lt;code&gt;#test_df에 이미지 path 만들기
all_image_path={os.path.basename(x).replace('.png',''):x for x in
               glob(os.path.join('..','input','competitions','aptos2019-blindness-detection','test_images','*.png'))}
print('Scans Found:', len(all_image_path), 'Total image Index:', len(test_df))

test_df['path']=test_df['id_code'].map(all_image_path.get)
test_df.head()&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre class=&quot;pgsql&quot;&gt;&lt;code&gt;#image plot 함수
def plot_images(df, rows, columns, figsize):
    fig, axes=plt.subplots(rows, columns, figsize=figsize)
    idx=0
    for i in range(rows):
        for j in range(columns):
            image=np.array(Image.open(df['path'].values[idx]).convert('RGB'))
            axes[i,j].imshow(image)
            axes[i,j].set_title(f'Label: {df['diagnosis'].values[idx]}, id: {df['id_code'].values[idx]}')

            idx+=1

    plt.show()


plot_images(train_df, 6,6, (25,25))&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;화면 캡처 2026-07-03 101213.png&quot; data-origin-width=&quot;908&quot; data-origin-height=&quot;715&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bmFMrN/dJMcaijBXK5/iSKkjd0A1FNwWMk1UPdfg0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bmFMrN/dJMcaijBXK5/iSKkjd0A1FNwWMk1UPdfg0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bmFMrN/dJMcaijBXK5/iSKkjd0A1FNwWMk1UPdfg0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbmFMrN%2FdJMcaijBXK5%2FiSKkjd0A1FNwWMk1UPdfg0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;908&quot; height=&quot;715&quot; data-filename=&quot;화면 캡처 2026-07-03 101213.png&quot; data-origin-width=&quot;908&quot; data-origin-height=&quot;715&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;주최 측 설명에 따르면, ' &lt;span style=&quot;background-color: #ffffff; color: #3c4043; text-align: start;&quot;&gt;The images were gathered from multiple clinics using a variety of cameras over an extended period of time, which will introduce further variation.'라고 합니다. 여러 병원에서 가져온 데이터이기 때문에 장애물이 있을 수도 있고, 오염되어 있을 수도 있습니다. 실제 이미지를 보면 어떤건 검은 배경이 많고, 어떤건 잘려있고, 또 어떤 이미지는 모델이 학습하기 어려울 정도로 어두워보입니다.&lt;/span&gt;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style5&quot; /&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;이미지 전처리&lt;/h3&gt;
&lt;pre class=&quot;routeros&quot;&gt;&lt;code&gt;#이미지에 여백 문제/조도, 색상 문제가 있는 것으로 확인
#Autocrop과 Ben Graham 전처리로 해결

#AutoCrop 코드
def AutoCrop(img, tol=7):   #tol=tolerance(허용 오차)-밝기 7 이하인 픽셀은 크롭
    gray_image=cv2.cvtColor(img, cv2.COLOR_RGB2GRAY)

    mask=gray_image&amp;gt;tol
    row_mask=mask.any(1)  #열을 흝으며 해당 행에 true를 찾음-그래서 1
    col_mask=mask.any(0)

    check_shape=img[:,:,0][np.ix_(row_mask, col_mask)].shape   
    #np.ix_(mask.any(1), mask.any(0)) 브로드케스팅 계산이 가능한 형태로 튜플 반환((3,1),(1,3) 형태의 array)

    if check_shape[0]==0:
        return img

    else:
        img1=img[:,:,0][np.ix_(row_mask, col_mask)]
        img2=img[:,:,1][np.ix_(row_mask, col_mask)]
        img3=img[:,:,2][np.ix_(row_mask, col_mask)]

        img=np.stack([img1, img2, img3], axis=-1)

        return img&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;검은 배경이 많은 이미지의 배경을 잘라내는 Autocrop 함수입니다. mask를 이용해서 밝기 7 이하의 픽셀을 모조리 잘라냅니다.&lt;/p&gt;
&lt;pre class=&quot;angelscript&quot;&gt;&lt;code&gt;#Ben Graham 전처리 코드
def ben_color(img):
    blur=cv2.GaussianBlur(img, (0,0), sigmaX=20)  #가우시안 커널 (0,0)으로 두면 시그마 값에 맞춰서 자동 설정

    #조도 보정 공식 구현 (4*original-4*blur+128)
    result=cv2.addWeighted(img, 4, blur, -4, 128)
    return result&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 대회에서 유명한 전처리 중 하나인 Ben Graham의 전처리 코드입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;가우시안 블러를 이용해 배경 밝기만 있는 이미지를 만들어서 원본에서 제거해주면, 배경 이미지 전체에 깔린 '노란색/갈색의 불균일한 바탕 조명(배경)'을 평균값으로 상쇄시켜 배경의 영향력을 지워버리는 효과를 냅니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;단순히 블러를 빼기만 하면 이미지가 너무 흐려지기 때문에, 4배의 가중치를 주어 빼줌으로써 배경 조명은 깔끔하게 소거하고, 남은 미세 혈관과 병변의 명암 대비는 4배로 강하게 증폭시킵니다.&lt;/p&gt;
&lt;pre class=&quot;maxima&quot;&gt;&lt;code&gt;#시각화 해보기
image=np.array(Image.open(train_df['path'].values[0]).convert('RGB'))
img1=AutoCrop(image)
img2=ben_color(img1)

fig, axes=plt.subplots(1,3, figsize=(10,4))
axes[0].imshow(image)
axes[0].set_title('Original')

axes[1].imshow(img1)
axes[1].set_title('Autocrop')

axes[2].imshow(img2)
axes[2].set_title('Autocrop+Ben Color')

for ax in axes:
    ax.axis('off')&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;894&quot; data-origin-height=&quot;325&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bINTwX/dJMcafmV1dB/HH1OIhg49jwpzjLRouZu80/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bINTwX/dJMcafmV1dB/HH1OIhg49jwpzjLRouZu80/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bINTwX/dJMcafmV1dB/HH1OIhg49jwpzjLRouZu80/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbINTwX%2FdJMcafmV1dB%2FHH1OIhg49jwpzjLRouZu80%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;894&quot; height=&quot;325&quot; data-origin-width=&quot;894&quot; data-origin-height=&quot;325&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style5&quot; /&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;Train Valid split&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre class=&quot;routeros&quot;&gt;&lt;code&gt;train_df, valid_df=train_test_split(train_df, test_size=0.15, random_state=SEED, stratify=train_df['diagnosis'])

print(train_df.shape, valid_df.shape)&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre class=&quot;angelscript&quot; style=&quot;color: #3c4043; text-align: left;&quot;&gt;&lt;code&gt;(3112, 3) (550, 3)&lt;/code&gt;&lt;/pre&gt;
&lt;p style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;테스트 데이터는 3112개, 검증 데이터는 550개로 고정합니다.&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style5&quot; /&gt;
&lt;h3 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size23&quot;&gt;시드 고정&lt;/h3&gt;
&lt;pre class=&quot;monkey&quot; style=&quot;background-color: #f8f8f8; color: #383a42; text-align: start;&quot;&gt;&lt;code&gt;import random
import torch

def set_seed(seed=42):
    random.seed(seed)    #파이선 기본 내장 random 모듈 시드
    np.random.seed(seed)    #numpy 랜덤 시드, albumentation가 numpy랜덤 모듈을 사용
    torch.manual_seed(seed)    #CPU에서 동작하는 pytorch 랜덤 시드(모델 가중치 초기화 등)
    torch.cuda.manual_seed(seed)       # 단일 GPU용
    torch.cuda.manual_seed_all(seed)    #멀티 GPU에서 동작하는 pytorch 랜덤 시드(dropout 등)


#worker init fn(서브 프로세스 시드 고정)
def seed_worker(worker_id):
    worker_seed = torch.initial_seed() % 2**32
    np.random.seed(worker_seed)
    random.seed(worker_seed)

g = torch.Generator()  # 제너레이터 생성
g.manual_seed(0)  # 제너레이터 시드값 고정
#전역 시드 고정해도 프로세스들의 순서가 달라지면 학습되는 데이터 순서가 달라질 수 있으므로 shuffle을 통제&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;재현성을 위해 시드를 고정해줍니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;seed_worker, generator는 num_workers=2와 같은 멀티 프로세스 환경에서 데이터 로딩의 재현성을 보장하기 위해 설정합니다. 각 CPU 코어가 독립적으로 데이터를 할당받을 때도 시드가 일정하게 유지됩니다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;Dataset&lt;/h3&gt;
&lt;pre class=&quot;nix&quot;&gt;&lt;code&gt;#훈련 데이터용 transform
import albumentations as A  #cv2기반으로 만들어진 라이브러리
from albumentations.pytorch import ToTensorV2

#3000개의 데이터수는 ViT에게 너무 적음

train_transform=A.Compose([
    A.Resize(384,384),
    A.HorizontalFlip(p=0.5),  #좌우 뒤집에도 질환 찾는데 문제 없음(좌안 우안 변경)
    A.VerticalFlip(p=0.5),    #상하 뒤집에도 질환 찾는데 문제 없음
    A.RandomRotate90(p=0.5),
    A.Normalize(),  #ImageNet std/mean 으로 정규화
    ToTensorV2()]
)


#검증/테스트 데이터용 transform   #이미지 transform 안하는 이유-불필요한 왜곡 추가가 목적이라 원본 이미지 예측에는 사용 안함
test_transform=A.Compose([
    A.Resize(384,384),
    A.Normalize(),
    ToTensorV2()]
)&lt;/code&gt;&lt;/pre&gt;
&lt;p data-path-to-node=&quot;4,0&quot; data-ke-size=&quot;size16&quot;&gt;대회에서 제공하는 3,112개의 데이터는 CNN과 달리 합성곱 특성(Inductive Bias)이 없는 ViT모델을 학습시키기에 턱없이 부족한 양입니다. 이를 극복하기 위해 Albumentations 라이브러리를 활용해 데이터 증강을 진행했습니다.&lt;/p&gt;
&lt;p data-path-to-node=&quot;4,0&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-path-to-node=&quot;4,1&quot; data-ke-size=&quot;size16&quot;&gt;특히 일반적인 의료 영상과 달리 상하 반전(Vertical Flip) 증강을 적극적으로 활용했습니다. 당뇨망막병증은 미세혈관류, 망막 출혈, 삼출물, 신생 혈관 등의 병변이 특정 위치가 아닌 망막 전반에 무작위로 발생하여 진단 기준(존재 여부, 개수, 심각도)을 형성하기 때문입니다. 따라서 상하를 뒤집어도 질병의 특징적인 정보는 훼손되지 않으므로, 안전하면서도 효과적인 데이터 증강이 가능합니다&lt;/p&gt;
&lt;pre class=&quot;ruby&quot;&gt;&lt;code&gt;from torch.utils.data import Dataset
from torch.utils.data import DataLoader

class APTOSData(Dataset):
    def __init__(self, df, transform=None, is_test=False):
        self.df=df
        self.transform=transform
        self.is_test=is_test

    def __getitem__(self,idx):
        image=np.array(Image.open(self.df['path'].iloc[idx]).convert('RGB'))


        img=AutoCrop(image)
        img=ben_color(img)

        if self.transform:
            img=self.transform(image=img)['image']   #딕셔너리 반환(mask같은거 넘기면 같이 반환)

        if self.is_test:
            return img


        labels=self.df['diagnosis'].iloc[idx]
        labels=torch.tensor(labels, dtype=torch.float)  #Smooth L1 Loss를 손실함수로 사용

        return img, labels

    def __len__(self):
        return len(self.df)&lt;/code&gt;&lt;/pre&gt;
&lt;pre class=&quot;routeros&quot;&gt;&lt;code&gt;#dataset 생성
train_dataset=APTOSData(train_df, transform=train_transform)
valid_dataset=APTOSData(valid_df, transform=train_transform)
test_dataset=APTOSData(test_df, transform=test_transform, is_test=True)&lt;/code&gt;&lt;/pre&gt;
&lt;pre class=&quot;routeros&quot;&gt;&lt;code&gt;#dataloader 생성
config=BaseConfig()

train_dataloader=DataLoader(train_dataset, batch_size=config.BATCH_SIZE, worker_init_fn=seed_worker, shuffle=True, generator=g, num_workers=2)
valid_dataloader=DataLoader(valid_dataset, batch_size=config.BATCH_SIZE, worker_init_fn=seed_worker, shuffle=True, generator=g, num_workers=2,
                           drop_last=False)  #default도 false이다. valid랑 test는 모든 데이터 대상으로 적용되야 하기 때문에 False.
test_dataloader=DataLoader(test_dataset, batch_size=config.BATCH_SIZE, worker_init_fn=seed_worker, shuffle=True, generator=g, num_workers=2,
                          drop_last=False)&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style5&quot; /&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;ViT&lt;/h3&gt;
&lt;pre class=&quot;cmake&quot;&gt;&lt;code&gt;!pip install timm  #torch image model 라이브러리(허깅페이스는 태생이 nlp라 비전에는 timm을 더 많이 사용)&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre class=&quot;routeros&quot;&gt;&lt;code&gt;import timm

model=timm.create_model('vit_base_patch16_384', pretrained=True, num_classes=1)  #224모델을 384로 파인튜닝 한 모델, 헤드는 초기화

dummy_img=torch.randn(2,3,384,384)
output=model(dummy_img)
print(output.shape)&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;표준이 되는 224 사이즈의 이미지로 학습한 모델이 아닌 384 사이즈의 모델을 선택한 이유는 당뇨 망막병증 특유의 미세 병변을 포착하기 위해서입니다. 연산 부담을 감수하더라도 더 정확한 예측을 위해 이미지의 해상도를 384로 맞췄습니다.&lt;/p&gt;
&lt;pre class=&quot;fortran&quot;&gt;&lt;code&gt;for name, params in model.named_parameters():
    print(name)&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre class=&quot;cmake&quot;&gt;&lt;code&gt;!pip install torchinfo&lt;/code&gt;&lt;/pre&gt;
&lt;pre class=&quot;angelscript&quot;&gt;&lt;code&gt;from torchinfo import summary

summary(model, input_size=(2,3,384,384))&lt;/code&gt;&lt;/pre&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style5&quot; /&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;파라미터 분리&lt;/b&gt;&lt;/h3&gt;
&lt;pre class=&quot;routeros&quot;&gt;&lt;code&gt;from torch import optim
from torch import nn

device='cuda' if torch.cuda.is_available() else 'cpu'

model=model.to(device)

grad_params=[p for p in model.named_parameters() if p[1].requires_grad]

no_decay = [&quot;bias&quot;, &quot;norm&quot;]   # Bias나 LayerNorm은 weight decay 사용 안하고 보호(bias는 과적합에 영향x, layernorm은 모델 표현력을 올려주는 계층)

optimizer_grouped_parameters = [
    {
        &quot;params&quot;: [p for n, p in grad_params if not any(nd in n for nd in no_decay)],
        &quot;weight_decay&quot;: 1e-2,  #weight decay가 그래디언트 항하고 분리, lr과 곱해져서 빼지기 때문에 큰 값 아님.그리고 vit는 과적합에 취약
    },
    {
        &quot;params&quot;: [p for n, p in grad_params if any(nd in n for nd in no_decay)],
        &quot;weight_decay&quot;: 0.0, 
    },
]

optimizer = optim.AdamW(optimizer_grouped_parameters, lr=BaseConfig.LR)&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;ViT는 과적합되기 쉬운 모델이기 때문에 weight decay로 가중치에 패널티를 줍니다. 이때 bias, 정규화 계층에 weight decay로 패널티를 부여하지 않기 위해 파라미터를 분리해줍니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;bias는 입력에 곱해지지 않고 그냥 더해지기 때문에 입력에 대한 민감도와 관련이 없고,&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;정규화 계층에서 에서 감마(scale)랑 베타(shift)는 정규화가 지나치게 강제한 표현력 제약을 풀어주는 것이기 때문에 weight decay의 타깃이 아닙니다.&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style5&quot; /&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;손실함수&lt;/b&gt;&lt;/h3&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;balance loss 만들기&lt;/h4&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;범주형 데이터의 분류 문제에서 주로 사용하는 Cross Entropy Loss를 사용하지 않고 회귀 계열의 손실함수를 적용했습니다. CEE는 두 경우 모두 정답 클래스(4단계)의 확률만 보기 때문에, 두 예측이 오답 클래스에 확률을 전혀 다르게 분배했더라도, 정답 클래스 확률만 같다면 동일한 loss를 갖게 됩니다.&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;br /&gt;예를 들어 4단계가 정답인 데이터를 예측한다고 해봅시다.&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;첫번째 경우 조금의 오차로 예측을 잘못해서&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-path-to-node=&quot;9&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;예측 확률 분포 &lt;span data-index-in-node=&quot;9&quot; data-math=&quot;\hat{Y}_A = [0.05, \; 0.05, \; 0.10, \; \mathbf{0.60}, \; \mathbf{0.20}]&quot;&gt;$\hat{Y}_A = [0.05, \; 0.05, \; 0.10, \; \mathbf{0.60}, \; \mathbf{0.20}]$&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;가장 높은 확률을 3단계(0.60)에 주었고, 정답인 4단계에는 &lt;b data-index-in-node=&quot;36&quot; data-path-to-node=&quot;9,1,0&quot;&gt;0.20&lt;/b&gt;의 확률을 주었습니다.&lt;/li&gt;
&lt;li&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;9,2,0&quot;&gt;CEE 손실 계산:&lt;/b&gt; &lt;span data-index-in-node=&quot;11&quot; data-math=&quot;-\log(0.20) \approx \mathbf{1.609}&quot;&gt;$-\log(0.20) \approx \mathbf{1.609}$&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-path-to-node=&quot;10&quot; data-ke-size=&quot;size16&quot;&gt;두번째 경우 완전히 예측을 잘못해서&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-path-to-node=&quot;11&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;예측 확률 분포 &lt;span data-index-in-node=&quot;9&quot; data-math=&quot;\hat{Y}_B = [\mathbf{0.60}, \; 0.05, \; 0.05, \; 0.10, \; \mathbf{0.20}]&quot;&gt;$\hat{Y}_B = [\mathbf{0.60}, \; 0.05, \; 0.05, \; 0.10, \; \mathbf{0.20}]$&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;가장 높은 확률을 0단계(0.60)에 주었고, 정답인 4단계에는 마찬가지로 &lt;b data-index-in-node=&quot;42&quot; data-path-to-node=&quot;11,1,0&quot;&gt;0.20&lt;/b&gt;의 확률을 주었습니다.&lt;/li&gt;
&lt;li&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;11,2,0&quot;&gt;CEE 손실 계산:&lt;/b&gt; &lt;span data-index-in-node=&quot;11&quot; data-math=&quot;-\log(0.20) \approx \mathbf{1.609}&quot;&gt;$-\log(0.20) \approx \mathbf{1.609}$&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;정답이 4단계인데 3단계로 틀린 loss와 0단계로 틀린 loss가 같아질 수도 있습니다.&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;그러기 때문에 이런 순서형(Ordinal) 데이터에서는 회귀 계열의 손실함수를 적용하는 것이 더 점수를 높여줄 것이라고 판단했습니다.&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;회귀계열의 손실함수로는 Smooth L1 loss를 선택했습니다. MSE는&amp;nbsp;오차가&amp;nbsp;클&amp;nbsp;때&amp;nbsp;제곱으로&amp;nbsp;패널티가&amp;nbsp;커져서&amp;nbsp;이상치(outlier)나&amp;nbsp;초반&amp;nbsp;학습&amp;nbsp;시&amp;nbsp;예측이&amp;nbsp;크게&amp;nbsp;틀렸을&amp;nbsp;때&amp;nbsp;그래디언트가&amp;nbsp;폭발적으로&amp;nbsp;커질&amp;nbsp;위험이&amp;nbsp;있고,&amp;nbsp;MAE는&amp;nbsp;미분&amp;nbsp;불가능한&amp;nbsp;지점(0)이&amp;nbsp;있어&amp;nbsp;최적화가&amp;nbsp;불안정할&amp;nbsp;수&amp;nbsp;있습니다.&amp;nbsp;Smooth&amp;nbsp;L1은&amp;nbsp;오차가&amp;nbsp;작을&amp;nbsp;땐&amp;nbsp;MSE처럼(부드러운&amp;nbsp;그래디언트),&amp;nbsp;오차가&amp;nbsp;클&amp;nbsp;땐&amp;nbsp;MAE처럼(그래디언트가&amp;nbsp;일정하게&amp;nbsp;유지)&amp;nbsp;동작해서&amp;nbsp;두&amp;nbsp;손실의&amp;nbsp;장점을&amp;nbsp;절충한&amp;nbsp;선택입니다.&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;또한, EDA에서 데이터 불균형이 심한 것을 확인하여 커스텀 손실 함수를 제작해주었습니다. loss를 구할때 개수의 역수를 곱해 적은 개수의 데이터가 더 큰 가중치를 가지도록 설계했습니다. 이때 역수는 단순 역수($w_c= \frac{1}{n_c}​$)를 사용했는데, 단순 역수를 그대로 쓰면 클래스별 가중치 비율이 과도하게 벌어져서 학습이 불안정해질 수 있다고 합니다. 다음번에는 Focal Loss의 balance 방법을 고려해보려고 합니다.&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;$$\mathcal{L}&amp;nbsp;=&amp;nbsp;w_c&amp;nbsp;\cdot&amp;nbsp;\text{SmoothL1}(\hat{y},&amp;nbsp;y)$$&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;$w_c$는&amp;nbsp;클래스&amp;nbsp;$c$의&amp;nbsp;가중치,&amp;nbsp;$\text{SmoothL1}$은&amp;nbsp;순서&amp;nbsp;정보를&amp;nbsp;반영하는&amp;nbsp;손실입니다.&lt;/p&gt;
&lt;pre class=&quot;routeros&quot;&gt;&lt;code&gt;#클래스별 불균형이 심해서 Smooth L1 Loss에 클래스 개수 역수 곱한 가중치 추가(balanced loss)
from collections import Counter

labels=train_df['diagnosis']
counter=Counter(labels)
total=len(labels)

class_weights={c: total/(len(counter)*count) for c, count in counter.items()}
#len(counter)을 곱해주는건 가중치 평균을 1로 맞추기 위함(가중치 너무 커지거나 작아지면 loss 영향)
print(class_weights)
sorted_weights=[class_weights[i] for i in range(len(counter))]
print(sorted_weights)&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre class=&quot;ruby&quot;&gt;&lt;code&gt;class BalancedLoss(nn.Module):
    def __init__(self, class_weights):
        super().__init__()
        #weight 버퍼로 등록(device 변경할떄 필요), self.class_weights 속성으로 등록
        self.register_buffer('class_weights', torch.FloatTensor(class_weights))    
        self.loss=nn.SmoothL1Loss(reduction='none')

    def forward(self, pred, target):
        # pred가 (32, 1)로 들어오면 (32,)로 짜부러뜨리기
        if pred.dim() == 2 and pred.size(1) == 1:
            pred = pred.squeeze(1)
        loss=self.loss(pred, target)
        weight=self.class_weights[target.long()]

        return (loss*weight).mean()&lt;/code&gt;&lt;/pre&gt;
&lt;pre class=&quot;reasonml&quot;&gt;&lt;code&gt;criterion=BalancedLoss(sorted_weights).to(device)&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;scheduler은 ReduceLROnPlateau로 설정.&lt;/p&gt;
&lt;pre class=&quot;bash&quot; data-ke-language=&quot;bash&quot;&gt;&lt;code&gt;#스케쥴러 설정-0.5배씩 줄이기
scheduler=optim.lr_scheduler.ReduceLROnPlateau(optimizer, mode='min', factor=0.5, patience=2)&lt;/code&gt;&lt;/pre&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style5&quot; /&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;Training&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre class=&quot;routeros&quot;&gt;&lt;code&gt;from tqdm.notebook import tqdm

best_val_loss=float('inf')

for epoch in range(BaseConfig.EPOCHS):


    save_path='best_aptos_model.pt'

    #모델 훈련
    model.train()
    train_loss=0.0

    for images, labels in tqdm(train_dataloader):
        images=images.to(device)
        labels=labels.to(device)

        output=model(images)
        loss=criterion(output, labels)

        optimizer.zero_grad()
        loss.backward()
        optimizer.step()

        train_loss+=loss.item()*images.shape[0]


    train_loss=train_loss/len(train_dataloader.dataset)
    print(f'Epoch: {epoch+1:4d}, Train Loss: {train_loss:.4f}')

    model.eval()
    val_loss=0

    with torch.no_grad():
        for images, labels in tqdm(valid_dataloader):
            images=images.to(device)
            labels=labels.to(device)

            output=model(images)
            loss=criterion(output, labels)

            val_loss+=loss.item()*images.shape[0]

    val_loss=val_loss/len(valid_dataloader.dataset)

    scheduler.step(val_loss)
    current_lr=optimizer.param_groups[0]['lr']
    print(f'Validation Loss: {val_loss:.4f}, Current LR: {current_lr:.6f}')

    if val_loss&amp;lt;best_val_loss:
        print('가장 낮은 val loss 기록, 모델 저장')

        best_val_loss=val_loss
        torch.save(model.state_dict(), save_path)
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;767&quot; data-origin-height=&quot;693&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bE1vzh/dJMcaaFWUC9/rKfszaXl9brmQqwHkKhkMk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bE1vzh/dJMcaaFWUC9/rKfszaXl9brmQqwHkKhkMk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bE1vzh/dJMcaaFWUC9/rKfszaXl9brmQqwHkKhkMk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbE1vzh%2FdJMcaaFWUC9%2FrKfszaXl9brmQqwHkKhkMk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;708&quot; height=&quot;640&quot; data-origin-width=&quot;767&quot; data-origin-height=&quot;693&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style5&quot; /&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;추론&lt;/b&gt;&lt;/h3&gt;
&lt;pre class=&quot;reasonml&quot;&gt;&lt;code&gt;submission=pd.read_csv('../input/competitions/aptos2019-blindness-detection/sample_submission.csv')
print(submission.head())
test_df.head()&lt;/code&gt;&lt;/pre&gt;
&lt;pre class=&quot;routeros&quot;&gt;&lt;code&gt;model.load_state_dict(torch.load('best_aptos_model.pt', map_location=device))

pred_list=[]

model.eval()

with torch.no_grad():

    for images in tqdm(test_dataloader):
        images=images.to(device)
        output=model(images)

        pred_list.append(output.cpu())  #VRAM용량이 부족해질 수 있으므로 cpu로 옮기기

    pred=torch.cat(pred_list, dim=0)
    pred=pred.squeeze()
    pred=torch.clamp(pred, 0, 4)
    pred=torch.round(pred).long()

submission['diagnosis']=pred.numpy()
submission.head()&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;소수로 나온 예측값을 단순히 반올림해서 결과를 제출했습니다.&lt;/p&gt;
&lt;pre class=&quot;routeros&quot;&gt;&lt;code&gt;submission.to_csv('submission.csv', index=False)
print('성공적으로 제출 완료')&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;최종 결과&lt;/b&gt;&lt;/h3&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1289&quot; data-origin-height=&quot;514&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/I6vaS/dJMcaixaxmO/9CaVOhNkQKS9TIPRvInPcK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/I6vaS/dJMcaixaxmO/9CaVOhNkQKS9TIPRvInPcK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/I6vaS/dJMcaixaxmO/9CaVOhNkQKS9TIPRvInPcK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FI6vaS%2FdJMcaixaxmO%2F9CaVOhNkQKS9TIPRvInPcK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1289&quot; height=&quot;514&quot; data-origin-width=&quot;1289&quot; data-origin-height=&quot;514&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;첫번째 submission 때는 test_dataloader shuffle을 true로 해버려서 대참사가 발생해버렸습니다. 덕분에 QWK의 음수 결과를 실제로 경험해볼 수 있었습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;상위 리더보드는 QWK 0.93~0.94대가 나오던데, 저의 베이스라인에서 점수를 올리기 위한 추가적인 방법들로는 다음과 같은 것들이 있겠습니다.&lt;/p&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;반올림 대신 threshold 최적화-무조건 반올림 하는 것이 아니라 OptimizedRounder를 사용해서 최적의 올림/내림 지점을 찾기&lt;/li&gt;
&lt;li&gt;ViT는 일단 CNN보다 훨씬 많은 데이터를 학습에 필요로 하기 때문에 외부 데이터로 학습.논문 (An Image is Worth 16x16 Words: Transformers for Image Recognition at Scale)에서도 ViT가 ImageNet-1k(120만 장) 정도 스케일에서는 ResNet보다 오히려 성능이 떨어지고, JFT-300M처럼 훨씬 큰 데이터로 사전학습해야 강점이 드러난다고 명시되어 있습니다.&lt;/li&gt;
&lt;li&gt;손실함수 자체를 QWK로 바꿔버리기&lt;/li&gt;
&lt;/ol&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style5&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;실제 이미지 데이터에 ViT를 활용해서 학습시켜보며, 이미지 전처리, 손실함수 커스텀, 파라미터 분리 학습 등 많은 것을 배운 것 같습니다. 이후 시각화를 위해 &lt;a href=&quot;https://icedtea.tistory.com/1&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;attention flow/rollout논문을 읽고&lt;/a&gt;, ViT 시각화 기법 중 하나인 &lt;a href=&quot;https://icedtea.tistory.com/2&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;rollout을 사용&lt;/a&gt;해보았습니다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;보통 캐글 코드 리뷰나 대회 리뷰 같은 건 잘 안하는거 같던데, 왜 안하는지 이번 글을 쓰면서 알게 되었습니다. 대부분의 내용들은 주석으로 달면 되고, 굳이 블로그까지 코드를 들고 와서 리뷰를 하는 것은 꽤나 수고로운 과정 같습니다. 그래도 이미지의 어떤 특성 때문에 해당 전처리 방식을 사용했는지, 왜 그 손실함수를 사용했는지 기록하고, 설명하는 측면에서는 블로그 리뷰도 꽤 괜찮은 것 같습니다.&lt;/p&gt;</description>
      <author>rminhyuk</author>
      <guid isPermaLink="true">https://icedtea.tistory.com/3</guid>
      <comments>https://icedtea.tistory.com/entry/Kaggle-APTOS-2019-Blindness-Detection-%EB%A6%AC%EB%B7%B0ViT#entry3comment</comments>
      <pubDate>Thu, 2 Jul 2026 19:57:28 +0900</pubDate>
    </item>
    <item>
      <title>Attention Rollout 코드 구현(APTOS 2019 Dataset)</title>
      <link>https://icedtea.tistory.com/entry/APTOS-ViT-Attention-Rollout</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;
&lt;script&gt;  MathJax = {    tex: {inlineMath: [['$', '$']]}  };&lt;/script&gt;
&lt;script src=&quot;https://cdn.jsdelivr.net/npm/mathjax@3/es5/tex-mml-chtml.js&quot;&gt;&lt;/script&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;블로그의 첫번째 글로 쓴 논문리뷰를 다시 읽어보니 독자 입장에서 평어채보다는 경어채가 읽기 편할 것 같아 말투를 바꿔 보겠습니다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;background-color: #ffffff; color: #212529; text-align: start;&quot;&gt;이번에 작성할 글은, 저번 &lt;a href=&quot;https://icedtea.tistory.com/1&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;논문리뷰&lt;/a&gt;에 이어 핵심 아이디어인 Attention Rollout을 실전에 적용해본 과정을 적어보려고 합니다.&lt;/span&gt;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style5&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Kaggle &lt;i&gt;APTOS 2019 Blindness Detection&lt;/i&gt; 대회의 dataset으로 ViT를 학습시키고, 모델 내부를 해석하기 위한 시각화 기법중 하나인 Rollout을 적용하는 코드입니다.(&lt;a href=&quot;https://icedtea.tistory.com/3&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;kaggle ViT학습 리뷰&lt;/a&gt;)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://icedtea.tistory.com/1&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;Quantifying Attention Flow in Transformers(ACL 2020)&lt;/a&gt; 논문을 읽고 해당 아이디어를 적용했습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;아래는 해당 노트북 링크입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://www.kaggle.com/code/ryuminhyuk/aptos-vit-attention-rollout&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;[Attention Rollout 노트북]&lt;/a&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;먼저 dataset과 사전학습된 모델을 불러오겠습니다.( best_aptos_model.pt로 저장해두었습니다.)&lt;/p&gt;
&lt;pre class=&quot;coffeescript&quot;&gt;&lt;code&gt;# This Python 3 environment comes with many helpful analytics libraries installed
# It is defined by the kaggle/python Docker image: https://github.com/kaggle/docker-python
# For example, here's several helpful packages to load

import numpy as np # linear algebra
import pandas as pd # data processing, CSV file I/O (e.g. pd.read_csv)

# Input data files are available in the read-only &quot;../input/&quot; directory
# For example, running this (by clicking run or pressing Shift+Enter) will list all files under the input directory

import os

count=0

for dirname, _, filenames in os.walk('/kaggle/input'):
    for filename in filenames:
        print(os.path.join(dirname, filename))
        count+=1
        if count&amp;gt;20:
            break

    if count&amp;gt;20:
        break

# You can write up to 20GB to the current directory (/kaggle/working/) that gets preserved as output when you create a version using &quot;Save &amp;amp; Run All&quot; 
# You can also write temporary files to /kaggle/temp/, but they won't be saved outside of the current session

# Use the kagglehub client library to attach Kaggle resources like competitions, datasets, and models to your session
# Learn more about kagglehub: https://github.com/Kaggle/kagglehub/blob/main/README.md

import kagglehub
# kagglehub.dataset_download('&amp;lt;owner&amp;gt;/&amp;lt;dataset-slug&amp;gt;')&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;br /&gt;/kaggle/input/notebooks/ryuminhyuk/aptos-vit-baseline/&lt;b&gt;results&lt;/b&gt;.html&lt;br /&gt;/kaggle/input/notebooks/ryuminhyuk/aptos-vit-baseline/&lt;b&gt;huggingface_repos&lt;/b&gt;.json&lt;br /&gt;/kaggle/input/notebooks/ryuminhyuk/aptos-vit-baseline/submission.csv&lt;br /&gt;/kaggle/input/notebooks/ryuminhyuk/aptos-vit-baseline/&lt;b&gt;notebook&lt;/b&gt;.ipynb&lt;br /&gt;/kaggle/input/notebooks/ryuminhyuk/aptos-vit-baseline/&lt;b&gt;output&lt;/b&gt;.json&lt;br /&gt;/kaggle/input/notebooks/ryuminhyuk/aptos-vit-baseline/best_aptos_model.pt&lt;br /&gt;/kaggle/input/notebooks/ryuminhyuk/aptos-vit-baseline/custom.css&lt;br /&gt;/kaggle/input/notebooks/ryuminhyuk/aptos-vit-baseline/&lt;b&gt;results_&lt;/b&gt;files/&lt;b&gt;results_&lt;/b&gt;15_0.png&lt;br /&gt;/kaggle/input/notebooks/ryuminhyuk/aptos-vit-baseline/&lt;b&gt;results_&lt;/b&gt;files/&lt;b&gt;results_&lt;/b&gt;11_0.png&lt;br /&gt;/kaggle/input/notebooks/ryuminhyuk/aptos-vit-baseline/&lt;b&gt;results_&lt;/b&gt;files/&lt;b&gt;results_&lt;/b&gt;7_2.png&lt;br /&gt;/kaggle/input/competitions/aptos2019-blindness-detection/sample_submission.csv&lt;br /&gt;/kaggle/input/competitions/aptos2019-blindness-detection/train.csv&lt;br /&gt;/kaggle/input/competitions/aptos2019-blindness-detection/test.csv&lt;br /&gt;/kaggle/input/competitions/aptos2019-blindness-detection/train_images/ef476be214d4.png&lt;br /&gt;/kaggle/input/competitions/aptos2019-blindness-detection/train_images/6dcde47060f9.png&lt;br /&gt;/kaggle/input/competitions/aptos2019-blindness-detection/train_images/ec363f48867b.png&lt;br /&gt;/kaggle/input/competitions/aptos2019-blindness-detection/train_images/17f6c7072f61.png&lt;br /&gt;/kaggle/input/competitions/aptos2019-blindness-detection/train_images/b49b2fac2514.png&lt;br /&gt;/kaggle/input/competitions/aptos2019-blindness-detection/train_images/af6166d57f13.png&lt;br /&gt;/kaggle/input/competitions/aptos2019-blindness-detection/train_images/8d13c46e7d75.png&lt;br /&gt;/kaggle/input/competitions/aptos2019-blindness-detection/train_images/c3b15bf9b4bc.png&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&amp;nbsp;&lt;/h2&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&amp;nbsp;&lt;/h2&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;Plot할 이미지 고르기&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;train data중에 여러개의 이미지를 띄워보고 가장 깔끔한 형태의 이미지를 찾아보겠습니다.&lt;/p&gt;
&lt;pre class=&quot;elm&quot;&gt;&lt;code&gt;import pandas as pd
from glob import glob
import matplotlib.pyplot as plt
from PIL import Image
import numpy as np
import cv2
import albumentations as A
from albumentations.pytorch import ToTensorV2
import torch&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre class=&quot;lua&quot;&gt;&lt;code&gt;test_df=pd.read_csv('../input/competitions/aptos2019-blindness-detection/test.csv')

#test_df에 이미지 path 만들기
all_image_path={os.path.basename(x).replace('.png',''):x for x in
               glob(os.path.join('..','input','competitions','aptos2019-blindness-detection','test_images','*.png'))}
print('Scans Found:', len(all_image_path), 'Total image Index:', len(test_df))

test_df['path']=test_df['id_code'].map(all_image_path.get)
test_df.head()&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;화면 캡처 2026-07-01 215113.png&quot; data-origin-width=&quot;437&quot; data-origin-height=&quot;209&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cSd8pF/dJMcahyeo38/7eGxh8aIsk3OiGx2mKjzN1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cSd8pF/dJMcahyeo38/7eGxh8aIsk3OiGx2mKjzN1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cSd8pF/dJMcahyeo38/7eGxh8aIsk3OiGx2mKjzN1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcSd8pF%2FdJMcahyeo38%2F7eGxh8aIsk3OiGx2mKjzN1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;437&quot; height=&quot;209&quot; data-filename=&quot;화면 캡처 2026-07-01 215113.png&quot; data-origin-width=&quot;437&quot; data-origin-height=&quot;209&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;pre class=&quot;routeros&quot;&gt;&lt;code&gt;diagnosis_df=pd.read_csv('../input/notebooks/ryuminhyuk/aptos-vit-baseline/submission.csv')

df=pd.merge(test_df, diagnosis_df, on='id_code', how='inner')
df.head()&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;화면 캡처 2026-07-01 215147.png&quot; data-origin-width=&quot;522&quot; data-origin-height=&quot;189&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bkXJ8U/dJMcag0j1xO/Z6PxhSz68TKlPzl8w7LW90/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bkXJ8U/dJMcag0j1xO/Z6PxhSz68TKlPzl8w7LW90/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bkXJ8U/dJMcag0j1xO/Z6PxhSz68TKlPzl8w7LW90/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbkXJ8U%2FdJMcag0j1xO%2FZ6PxhSz68TKlPzl8w7LW90%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;522&quot; height=&quot;189&quot; data-filename=&quot;화면 캡처 2026-07-01 215147.png&quot; data-origin-width=&quot;522&quot; data-origin-height=&quot;189&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;diagnosis_df는 사전학습한 모델이 test data를 예측한 값을 저장해둔 df입니다.&lt;/p&gt;
&lt;pre class=&quot;pgsql&quot;&gt;&lt;code&gt;#image plot 함수
def plot_images(df, rows, columns, figsize):
    fig, axes=plt.subplots(rows, columns, figsize=figsize)
    idx=0
    for i in range(rows):
        for j in range(columns):
            image=np.array(Image.open(df['path'].values[idx]).convert('RGB'))
            axes[i,j].imshow(image)
            axes[i,j].set_title(f'Label: {df['diagnosis'].values[idx]}, id: {df['id_code'].values[idx]}')

            idx+=1

    plt.show()


plot_images(df, 3,3, (10,10))&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;화면 캡처 2026-07-01 214859.png&quot; data-origin-width=&quot;788&quot; data-origin-height=&quot;741&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/b5NXzk/dJMcaicTif9/zFsxGol3S0w6C0k3cu1qmK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/b5NXzk/dJMcaicTif9/zFsxGol3S0w6C0k3cu1qmK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/b5NXzk/dJMcaicTif9/zFsxGol3S0w6C0k3cu1qmK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fb5NXzk%2FdJMcaicTif9%2FzFsxGol3S0w6C0k3cu1qmK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;788&quot; height=&quot;741&quot; data-filename=&quot;화면 캡처 2026-07-01 214859.png&quot; data-origin-width=&quot;788&quot; data-origin-height=&quot;741&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;8번 이미지가 가장 깔끔해보입니다.&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;8번째 이미지로 attention rollout&lt;/h2&gt;
&lt;pre class=&quot;angelscript&quot;&gt;&lt;code&gt;#AutoCrop 코드
def AutoCrop(img, tol=7):   #tol=tolerance(허용 오차)-밝기 7 이하인 픽셀은 크롭
    gray_image=cv2.cvtColor(img, cv2.COLOR_RGB2GRAY)

    mask=gray_image&amp;gt;tol
    row_mask=mask.any(1)  #열을 흝으며 해당 행에 true를 찾음-그래서 1
    col_mask=mask.any(0)

    check_shape=img[:,:,0][np.ix_(row_mask, col_mask)].shape   
    #np.ix_(mask.any(1), mask.any(0)) 브로드케스팅 계산이 가능한 형태로 튜플 반환((3,1),(1,3) 형태의 array)

    if check_shape[0]==0:
        return img

    else:
        img1=img[:,:,0][np.ix_(row_mask, col_mask)]
        img2=img[:,:,1][np.ix_(row_mask, col_mask)]
        img3=img[:,:,2][np.ix_(row_mask, col_mask)]

        img=np.stack([img1, img2, img3], axis=-1)

        return img


#Ben Graham 전처리 코드
def ben_color(img):
    blur=cv2.GaussianBlur(img, (0,0), sigmaX=20)  #가우시안 커널 (0,0)으로 두면 시그마 값에 맞춰서 자동 설정

    #조도 보정 공식 구현 (4*original-4*blur+128)
    result=cv2.addWeighted(img, 4, blur, -4, 128)
    return result&lt;/code&gt;&lt;/pre&gt;
&lt;pre class=&quot;maxima&quot;&gt;&lt;code&gt;#시각화 해보기
image=np.array(Image.open(df['path'].values[7]).convert('RGB'))
img1=AutoCrop(image)
img2=ben_color(img1)

fig, axes=plt.subplots(1,3, figsize=(10,4))
axes[0].imshow(image)
axes[0].set_title('Original')

axes[1].imshow(img1)
axes[1].set_title('Autocrop')

axes[2].imshow(img2)
axes[2].set_title('Autocrop+Ben Color')

for ax in axes:
    ax.axis('off')&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;화면 캡처 2026-07-01 215333.png&quot; data-origin-width=&quot;805&quot; data-origin-height=&quot;272&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/5DPJH/dJMcacwVa8X/Go8yuC05POePkZvdiabVX0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/5DPJH/dJMcacwVa8X/Go8yuC05POePkZvdiabVX0/img.png&quot; data-alt=&quot;Label-3&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/5DPJH/dJMcacwVa8X/Go8yuC05POePkZvdiabVX0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2F5DPJH%2FdJMcacwVa8X%2FGo8yuC05POePkZvdiabVX0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;805&quot; height=&quot;272&quot; data-filename=&quot;화면 캡처 2026-07-01 215333.png&quot; data-origin-width=&quot;805&quot; data-origin-height=&quot;272&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;Label-3&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;pre class=&quot;makefile&quot;&gt;&lt;code&gt;#전처리
test_transform=A.Compose([
    A.Resize(384,384),
    A.Normalize(),
    ToTensorV2()]
)

img=test_transform(image=img2)['image']   #AutoCrop, ben 전처리 완료 후 리사이즈, 정규화

input=img.unsqueeze(0) #배치 축 생성(albumentation은 배치 축 없는걸 받음)&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;input을 모델에 넣기 위해 배치 축을 생성해줍니다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;모델 생성&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;timm 라이브러리에서 패치 사이즈16/이미지 사이즈 384 모델을 불러온 다음, 사전 학습된 모델의 가중치를 불러옵니다.&lt;/p&gt;
&lt;pre class=&quot;cmake&quot;&gt;&lt;code&gt;!pip install timm&lt;/code&gt;&lt;/pre&gt;
&lt;pre class=&quot;routeros&quot;&gt;&lt;code&gt;import timm

device='cuda' if torch.cuda.is_available() else 'cpu'

model=timm.create_model('vit_base_patch16_384', pretrained=True, num_classes=1)  #224모델을 384로 파인튜닝 한 모델, 헤드는 초기화

input=input.to(device)
model.to(device)
output=model(input)
print(output.shape)&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;br /&gt;torch.Size([1, 1])&lt;/p&gt;
&lt;pre class=&quot;reasonml&quot;&gt;&lt;code&gt;model.load_state_dict(torch.load('../input/notebooks/ryuminhyuk/aptos-vit-baseline/best_aptos_model.pt', map_location=device))&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;Rollout 구현&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;[Code Reference]&lt;/b&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;GitHub Repository:&lt;br /&gt;&lt;a href=&quot;https://github.com/jacobgil/vit-explain&quot;&gt;jacobgil/vit-explain&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;Author: Jacob Gildenblat&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;아래 있는 VITAttentionRollout 코드의 출처입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;훅을 걸기 위해 레이어 이름을 확인해줍니다.&lt;/p&gt;
&lt;pre class=&quot;fortran&quot;&gt;&lt;code&gt;#timm 모델의 레이어 이름 출력해서 확인하기
for name, module in model.named_modules():
    if 'attn_drop' in name:
        print(name)&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;blocks.0.attn.attn_drop&lt;br /&gt;blocks.1.attn.attn_drop&lt;br /&gt;blocks.2.attn.attn_drop&lt;br /&gt;blocks.3.attn.attn_drop&lt;br /&gt;blocks.4.attn.attn_drop&lt;br /&gt;blocks.5.attn.attn_drop&lt;br /&gt;blocks.6.attn.attn_drop&lt;br /&gt;blocks.7.attn.attn_drop&lt;br /&gt;blocks.8.attn.attn_drop&lt;br /&gt;blocks.9.attn.attn_drop&lt;br /&gt;blocks.10.attn.attn_drop&lt;br /&gt;blocks.11.attn.attn_drop&lt;/p&gt;
&lt;pre class=&quot;bash&quot; data-ke-language=&quot;bash&quot;&gt;&lt;code&gt;def rollout(attentions, discard_ratio, head_fusion):
    result = torch.eye(attentions[0].size(-1))   #어텐션 맵 하나의 크기는 [1, 12, 577, 577](배치, 헤드, 토큰, 토큰)
    #처음 곱셈을 시작할 단위행렬 생성
    with torch.no_grad():
        for attention in attentions:    #헤드를 섞는 방식. 논문에서는 mean
            if head_fusion == &quot;mean&quot;:
                attention_heads_fused = attention.mean(axis=1)
            elif head_fusion == &quot;max&quot;:
                attention_heads_fused = attention.max(axis=1)[0]  #[0]을 붙인 이유는 max() 함수가 (최대값, 인덱스) 쌍을 뱉기 때문
            elif head_fusion == &quot;min&quot;:
                attention_heads_fused = attention.min(axis=1)[0]
            else:
                raise &quot;Attention head fusion type Not supported&quot;

            # Drop the lowest attentions, but
            # don't drop the class token    
            #자잘한 노이즈 쳐내기 위해 discard_ratio만큼의 데이터는 0으로 만들기
            #작은 값이어도 행렬 곱셈을 계속 누적하다보면 배경이 하얗게 번지는 블러 생김
            flat = attention_heads_fused.view(attention_heads_fused.size(0), -1)
            _, indices = flat.topk(int(flat.size(-1)*discard_ratio), -1, False)
            indices = indices[indices != 0]  #cls 토큰은 드롭 안하기
            flat[0, indices] = 0

            I = torch.eye(attention_heads_fused.size(-1))  #잔차연결 계산을 위한 단위행렬
            a = (attention_heads_fused + 1.0*I)/2  #논문 구현대로 0.5의 비율로 잔차연결
            a = a / a.sum(dim=-1)  #이론상으로 a의 총합은 1이지만 컴퓨터의 소수점 연산 오차의 누적을 막기 위해 레이어 생성 때마다 정규화

            result = torch.matmul(a, result)

    # Look at the total attention between the class token,
    # and the image patches
    mask = result[0, 0 , 1:]  
    #cls 벡터에서 cls토큰 제외하기
    # 이미지 크기 384, 패치 크기 16 모델이다. 576개의 이미지 패치(24*24)
    width = int(mask.size(-1)**0.5)
    mask = mask.reshape(width, width).numpy()
    mask = mask / np.max(mask)   #시각적 품질 향상을 위해 최댓값으로 정규화
    return mask  #(24,24)

class VITAttentionRollout:
    def __init__(self, model, attention_layer_name='attn_drop', head_fusion=&quot;mean&quot;,
        discard_ratio=0.9):
        self.model = model
        self.head_fusion = head_fusion
        self.discard_ratio = discard_ratio
        for name, module in self.model.named_modules():
            if attention_layer_name in name:
                module.register_forward_hook(self.get_attention)   #forward에서 어텐션맵 빼오는 예약 걸어둠. model에 input흘리면 실행

        self.attentions = []

    def get_attention(self, module, input, output):
        self.attentions.append(output.cpu())

    def __call__(self, input_tensor):
        self.attentions = []
        with torch.no_grad():
            output = self.model(input_tensor)

        return rollout(self.attentions, self.discard_ratio, self.head_fusion)&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;간략하게 설명하면,&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;먼저 헤드를 섞는 방식을 고릅니다.(해당 모델의 헤드 개수는 12개)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;자잘한 노이즈를 없에기 위해 마스크를 이용해서 하위90%의 픽셀은 0으로 만들어버리고, 잔차연결을 더해줍니다.(0.5)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;어텐션 맵들을 훅을 이용해서 리스트에 담고, 순서대로 matmul 해줍니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;마지막에 0번째 행, 즉 cls 벡터에서 cls토큰을 제외하고 일렬로 나열된 나머지 576개의 이미지 패치를 2차원으로 바꿔줍니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이해가 잘 안되는 부분이 있다면 댓글 남겨주시기 바랍니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre class=&quot;isbl&quot; data-ke-language=&quot;bash&quot;&gt;&lt;code&gt;rollout_output=VITAttentionRollout(model)
mask=rollout_output(input)
print(mask.shape)&lt;/code&gt;&lt;/pre&gt;
&lt;pre id=&quot;code_1782911029187&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;---------------------------------------------------------------------------
IndexError                                Traceback (most recent call last)
/tmp/ipykernel_58/1133337965.py in &amp;lt;cell line: 0&amp;gt;()
      1 rollout_output=VITAttentionRollout(model)
----&amp;gt; 2 mask=rollout_output(input)
      3 print(mask.shape)

/tmp/ipykernel_58/4086692976.py in __call__(self, input_tensor)
     56             output = self.model(input_tensor)
     57 
---&amp;gt; 58         return rollout(self.attentions, self.discard_ratio, self.head_fusion)

/tmp/ipykernel_58/4086692976.py in rollout(attentions, discard_ratio, head_fusion)
      1 def rollout(attentions, discard_ratio, head_fusion):
----&amp;gt; 2     result = torch.eye(attentions[0].size(-1))   #어텐션 맵 하나의 크기는 [1, 12, 577, 577](배치, 헤드, 토큰, 토큰)
      3     #처음 곱셈을 시작할 단위행렬 생성
      4     with torch.no_grad():
      5         for attention in attentions:    #헤드를 섞는 방식. 논문에서는 mean

IndexError: list index out of range&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;여기서 문제가 발생합니다.&amp;nbsp; 분명 알맞는 레이어에 훅을 걸었는데 list index out of range 에러가 나왔습니다. 아마도 훅이 제대로 걸리지 않은 것 같아 호출되는지 테스트를 해보았습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre class=&quot;ruby&quot;&gt;&lt;code&gt;# hook 직접 걸어서 호출되는지 테스트(훅 안걸리는 문제 발생)
handles = []
test_outputs = []

def test_hook(module, input, output):
    test_outputs.append(output)
    print(&quot;hook 호출됨!&quot;, output.shape)

for name, module in model.named_modules():
    if 'attn_drop' in name:
        handles.append(module.register_forward_hook(test_hook))

with torch.no_grad():
    output = model(input)

print(f&quot;총 호출 횟수: {len(test_outputs)}&quot;)

# hook 제거
for h in handles:
    h.remove()&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;총 호출 횟수: 0&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;역시나 훅이 걸리지 않은 것이 문제였습니다. 한참동안 뭐가 문제인지 고민하다가 claude의 도움을 받아 모델의 forward를 확인해보고, attn_drop 계층이 호출되는지 직접 확인해보라는 조언을 받았습니다.&lt;/p&gt;
&lt;pre class=&quot;css&quot;&gt;&lt;code&gt;#모델 forward에서 attn_drop계층이 호출되는지 확인
#inspect는 소스 코드 자체를 텍스트로 보여주는 라이브러리
import inspect
print(inspect.getsource(model.blocks[0].attn.forward))&lt;/code&gt;&lt;/pre&gt;
&lt;pre class=&quot;bash&quot; data-ke-language=&quot;bash&quot;&gt;&lt;code&gt;   def forward(
            self,
            x: torch.Tensor,
            attn_mask: Optional[torch.Tensor] = None,
            is_causal: bool = False,
    ) -&amp;gt; torch.Tensor:
        B, N, C = x.shape
        qkv = self.qkv(x).reshape(B, N, 3, self.num_heads, self.head_dim).permute(2, 0, 3, 1, 4)
        q, k, v = qkv.unbind(0)
        q, k = self.q_norm(q), self.k_norm(k)

        if self.fused_attn:
            x = F.scaled_dot_product_attention(
                q, k, v,
                attn_mask=attn_mask,
                dropout_p=self.attn_drop.p if self.training else 0.,
                is_causal=is_causal,
            )
        else:
            q = q * self.scale
            attn = q @ k.transpose(-2, -1)
            attn_bias = resolve_self_attn_mask(N, attn, attn_mask, is_causal)
            attn = maybe_add_mask(attn, attn_bias)
            attn = attn.softmax(dim=-1)
            attn = self.attn_drop(attn)
            x = attn @ v

        x = x.transpose(1, 2).reshape(B, N, self.attn_dim)
        x = self.norm(x)
        x = self.proj(x)
        x = self.proj_drop(x)
        return x&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;self.fused_attn이 True라면 self.attn_drop 대신 F.scaled_dot_product_attention이 실행되고, 이는 attention 행렬을 중간에 메모리에 올리지 않고 바로 Value matrix까지 곱해버린다고 합니다. attention map이 메모리에 올라온 적이 없기 때문에 훅이 절대로 걸리지 않았던 것이 문제였습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre class=&quot;bash&quot; data-ke-language=&quot;bash&quot;&gt;&lt;code&gt;#F.scaled_dot_product_attention은 PyTorch 내장 커널이라 attention 행렬을 중간에 메모리에 올리지 않고 한 번에 처리. (FlashAttention 방식)
#attention map을 잡을 수가 없다.
#fused_attn을 false로 해주기
for block in model.blocks:
    block.attn.fused_attn = False&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;self.fused_attn을 False로 바꿔서 self.attn_drop이 호출될 수 있도록 해줍니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre class=&quot;bash&quot; data-ke-language=&quot;bash&quot;&gt;&lt;code&gt;rollout_output=VITAttentionRollout(model)
mask=rollout_output(input)
print(mask.shape)&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;(24,24)&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&amp;nbsp;&lt;/h3&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;Attention Map 시각화&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;보간함수를 이용해 mask를 원본 이미지 위에 덧씌워보겠습니다.&lt;/p&gt;
&lt;pre class=&quot;routeros&quot;&gt;&lt;code&gt;from torch.nn import functional as F

attn_map=F.interpolate(
    input=torch.tensor(mask).unsqueeze(0).unsqueeze(0),   #F.interpolate는 [배치, 채널, H, W] 텐서 입력받음
    size=(image.shape[0], image.shape[1]),
    mode='bilinear'
).squeeze().detach().numpy()


plt.figure(figsize=(5,5))
plt.imshow(image)
plt.imshow(attn_map, cmap='jet', alpha=0.6)

plt.axis('off')
plt.show() &lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;화면 캡처 2026-07-01 220312.png&quot; data-origin-width=&quot;406&quot; data-origin-height=&quot;399&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/Lob1A/dJMcadJghDw/I4NXru23iFYDft4Jy10YAK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/Lob1A/dJMcadJghDw/I4NXru23iFYDft4Jy10YAK/img.png&quot; data-alt=&quot;attention rollout으로 구한 mask를 원본 이미지 위에 덧씌운 모습&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/Lob1A/dJMcadJghDw/I4NXru23iFYDft4Jy10YAK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FLob1A%2FdJMcadJghDw%2FI4NXru23iFYDft4Jy10YAK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;420&quot; height=&quot;413&quot; data-filename=&quot;화면 캡처 2026-07-01 220312.png&quot; data-origin-width=&quot;406&quot; data-origin-height=&quot;399&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;attention rollout으로 구한 mask를 원본 이미지 위에 덧씌운 모습&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;망막 이미지 내에 잡힌 2개의 어텐션이 확인됩니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;화면 캡처 2026-07-01 222424.png&quot; data-origin-width=&quot;840&quot; data-origin-height=&quot;456&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/ptATS/dJMcadP3gVX/73m9824NCpGVcG6c8qi03k/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/ptATS/dJMcadP3gVX/73m9824NCpGVcG6c8qi03k/img.png&quot; data-alt=&quot;Diabetic Retinopathy를 진단하는 5가지 증상. 원본 이미지에서 육안으로 어떤 증상인지 예측이 안됩니다.&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/ptATS/dJMcadP3gVX/73m9824NCpGVcG6c8qi03k/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FptATS%2FdJMcadP3gVX%2F73m9824NCpGVcG6c8qi03k%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;591&quot; height=&quot;321&quot; data-filename=&quot;화면 캡처 2026-07-01 222424.png&quot; data-origin-width=&quot;840&quot; data-origin-height=&quot;456&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;Diabetic Retinopathy를 진단하는 5가지 증상. 원본 이미지에서 육안으로 어떤 증상인지 예측이 안됩니다.&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;배경과 망막을 구분하는 경계선은 어텐션이 잘 된 모습을 확인할 수 있는데, 배경에 어텐션이 강하게 된 두 지점을 확인할 수 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;왜 배경에 어텐션이 강하게 되었는지 이해가 가지 않아서 두가지 가설을 세워보았습니다.&lt;/p&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;Rollout을 구현하는 코드를 보면 자잘한 노이즈를 쳐내기 위해 하위 90%의 데이터 값은 전부 0으로 만들어버리는 과정에서, 저 부분만 특별히 강하게 어텐션된 것처럼 보인다.&lt;/li&gt;
&lt;li&gt;저번에 rollout 논문을 리뷰하면서 들었던 생각인데, 무의미한 정보가 증폭될 수도 있겠다는 생각이 들었습니다. 예를 들어&lt;br /&gt;$$\text{레이어 1: 무의미한 토큰에 } 0.1 \text{ attention}$$ $$\text{레이어 2, 3, 4: 해당 노드를 } 0.9 \text{씩 attention}$$을 하는 상황으로 설정해보고 rollout을 계산하면&lt;br /&gt;$$\text{Rollout} = 0.1 \times 0.9 \times 0.9 \times 0.9 = 0.0729$$&lt;br /&gt;$$\text{원래 기여량: } 0.1 \quad \xrightarrow{\text{레이어 누적}} \quad 0.0729$$ $$\text{&amp;rarr; 상위 레이어의 높은 가중치가 하위의 작은 정보량을 유지시킵니다.}$$&lt;br /&gt;더 극단적인 상황을 가정해보면&lt;br /&gt;$$0.1&amp;nbsp;\times&amp;nbsp;0.9^{11}&amp;nbsp;\approx&amp;nbsp;0.031$$&lt;br /&gt;$$\text{12개 레이어를 거쳐도 여전히 3%의 영향력이 유지됩니다.}$$&lt;br /&gt;이런 경우 배경처럼 무의미한 토큰이 사라지지 않고 끈질기게 살아남아서 어텐션 맵을 오염시킬 수도 있습니다. &lt;br /&gt;&lt;br /&gt;&lt;/li&gt;
&lt;/ol&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;첫번째 가설을 실험하기 위해 discard_ratio를 0으로 하고 mask를 다시 씌워봤는데&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;화면 캡처 2026-07-01 223747.png&quot; data-origin-width=&quot;406&quot; data-origin-height=&quot;400&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bqPeNF/dJMb991h8pJ/PLFPElZ1zMaXOtiKQWkEF1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bqPeNF/dJMb991h8pJ/PLFPElZ1zMaXOtiKQWkEF1/img.png&quot; data-alt=&quot;배경 4 부분이 모두 어텐션됨&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bqPeNF/dJMb991h8pJ/PLFPElZ1zMaXOtiKQWkEF1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbqPeNF%2FdJMb991h8pJ%2FPLFPElZ1zMaXOtiKQWkEF1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;323&quot; height=&quot;318&quot; data-filename=&quot;화면 캡처 2026-07-01 223747.png&quot; data-origin-width=&quot;406&quot; data-origin-height=&quot;400&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;배경 4 부분이 모두 어텐션됨&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이런 결과가 나왔습니다. 모든 배경이 강하게 어텐션 된 것을 보아 1번 가설이 유력하고, 아마 배경을 학습하기 위해 어텐션 된 것이 아닐까 생각해보았습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;모델 해석 능력을 더 확실하게 평가하기 위해 이번에는 다른 이미지로 attention map을 구해보았습니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;화면 캡처 2026-07-01 224124.png&quot; data-origin-width=&quot;834&quot; data-origin-height=&quot;234&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/JwZZq/dJMcaf1qzXb/d7SQUXAuJOPfIfa4GU5fsk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/JwZZq/dJMcaf1qzXb/d7SQUXAuJOPfIfa4GU5fsk/img.png&quot; data-alt=&quot;위 5 가지 증상 중 정확하게는 모르겠지만 왼쪽 하단 반점들이 증상 중 하나일 것으로 예측됩니다.(Label-2)&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/JwZZq/dJMcaf1qzXb/d7SQUXAuJOPfIfa4GU5fsk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FJwZZq%2FdJMcaf1qzXb%2Fd7SQUXAuJOPfIfa4GU5fsk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;834&quot; height=&quot;234&quot; data-filename=&quot;화면 캡처 2026-07-01 224124.png&quot; data-origin-width=&quot;834&quot; data-origin-height=&quot;234&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;위 5 가지 증상 중 정확하게는 모르겠지만 왼쪽 하단 반점들이 증상 중 하나일 것으로 예측됩니다.(Label-2)&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;왼쪽 하단의 반점들이&lt;span style=&quot;color: #000000;&quot;&gt; &lt;span style=&quot;text-align: center;&quot;&gt;Diabetic Retinopathy의 증상 중 하나일 것으로 예측됩니다.&lt;/span&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;&lt;span style=&quot;text-align: center;&quot;&gt;Attention Rollout으로 attention map을 구해보면&lt;/span&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;화면 캡처 2026-07-01 224416.png&quot; data-origin-width=&quot;412&quot; data-origin-height=&quot;290&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/P1jTN/dJMcafmUS62/1CLxThURx4GNRLonek9VvK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/P1jTN/dJMcafmUS62/1CLxThURx4GNRLonek9VvK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/P1jTN/dJMcafmUS62/1CLxThURx4GNRLonek9VvK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FP1jTN%2FdJMcafmUS62%2F1CLxThURx4GNRLonek9VvK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;412&quot; height=&quot;290&quot; data-filename=&quot;화면 캡처 2026-07-01 224416.png&quot; data-origin-width=&quot;412&quot; data-origin-height=&quot;290&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;경계부분과 증상 중 하나일 것으로 예측한 반점에 어텐션을 하고 있는 모습입니다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&amp;nbsp;&lt;/h3&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;Conclusion&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;논문 리뷰로만 공부했던 attention rollout을 실제 데이터와 모델에 적용해보았습니다. CNN grad-cam처럼 간단할 줄 알았는데 예상외로 시간이 많이 소요되었습니다. 그래도 시행착오를 겪으면서 배우고, 실제 모델에 적용해 결과를 봤다는 것만으로도 의미가 있었습니다. 제가 세운 가정 2가 실제로 rollout의 문제점으로 작용할 수 있는지 궁금한데, 언젠가 관련 실험을 해보고 싶어졌습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;다음 글은 APTOS dataset/ViT 학습을 시키며 공부한 내용으로 찾아뵙도록 하겠습니다.&lt;/p&gt;</description>
      <author>rminhyuk</author>
      <guid isPermaLink="true">https://icedtea.tistory.com/2</guid>
      <comments>https://icedtea.tistory.com/entry/APTOS-ViT-Attention-Rollout#entry2comment</comments>
      <pubDate>Wed, 1 Jul 2026 16:39:01 +0900</pubDate>
    </item>
    <item>
      <title>[리뷰]Quantifying Attention Flow in Transformers(ACL 2020)</title>
      <link>https://icedtea.tistory.com/entry/%ED%99%98%EC%98%81%ED%95%A9%EB%8B%88%EB%8B%A4</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;
&lt;script&gt;  MathJax = {    tex: {inlineMath: [['$', '$']]}  };&lt;/script&gt;
&lt;script src=&quot;https://cdn.jsdelivr.net/npm/mathjax@3/es5/tex-mml-chtml.js&quot;&gt;&lt;/script&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Kaggle에서 ViT APTOS 2019 Blindness Detection 데이터셋으로 학습시키고(&lt;a href=&quot;https://icedtea.tistory.com/3&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;kaggle ViT 학습 리뷰&lt;/a&gt;), ViT에도 grad-cam같은 기술이 있는지 찾아보다가 Attention Rollout/Flow를 알게 되었다. 원래는 공부한 내용들을 노트에 손필기로 정리했었는데, 블로그에 정리하는 것이 더 깔끔하고 다른 사람들과 정보를 공유할 수 있기 때문에 블로그를 시작해보려고 한다. 시간이 된다면 내가 노트에 정리한 내용을 하나씩 블로그에 작성해볼 계획이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style5&quot; /&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;제목: Quantifying&amp;nbsp;Attention&amp;nbsp;Flow&amp;nbsp;in&amp;nbsp;Transformers&lt;/li&gt;
&lt;li&gt;저자: Samira Abnar/Willem Zuidema(ILLC, University of Amsterdam)&lt;/li&gt;
&lt;li&gt;게재 학회: ACL 2020&lt;/li&gt;
&lt;li&gt;인용수: 1803회(2026.07.01)&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;Abstract&lt;/b&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;트랜스포머 모델은 레이어를 거치면서 정보가 무분별하게 섞이게 된다. 이런 이유 때문에 raw attention을 모델을 해석하기 위해 사용했을때, 모델 내부를 정확하게 설명하지 못하는 한계가 있다. 논문은 attention rollout과 attention flow라는 post hoc method 을 제안하는데, 두 방법 모두 raw attention보다 ablation method와 input gradient를 사용했을 때 입력 토큰의 중요도 점수와 높은 상관관계가 있는것으로 나타났다. 간단하게 설명하면 어텐션 가중치를 역으로 추적하여 최종 출력에서 입력 토큰까지의 정보 흐름을 추적해서 근사(approxiamte)한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;Setups and Problem Statement&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;모델이 정확히 어떤 단어를 어텐션하고 있는지 명확하게 알 수 있는 '동사 복수형 맞히는 문제(verb number prediction task)'를 사용했다. 모델은 GPT-2 인코더를 사용.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;raw attn.png&quot; data-origin-width=&quot;1443&quot; data-origin-height=&quot;594&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/5OIjN/dJMcabSfBHl/9uOXpG3nMTuPhWxQeskEN1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/5OIjN/dJMcabSfBHl/9uOXpG3nMTuPhWxQeskEN1/img.png&quot; data-alt=&quot;레이어가 높아질수록 어텐션 가중치 비슷&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/5OIjN/dJMcabSfBHl/9uOXpG3nMTuPhWxQeskEN1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2F5OIjN%2FdJMcabSfBHl%2F9uOXpG3nMTuPhWxQeskEN1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;748&quot; height=&quot;308&quot; data-filename=&quot;raw attn.png&quot; data-origin-width=&quot;1443&quot; data-origin-height=&quot;594&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;레이어가 높아질수록 어텐션 가중치 비슷&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;lt;verb&amp;gt;를 맞히는 문제에 대해 raw attention map을 시각화한 figure이다. 논문에서는 상위 계층으로 갈수록 가중치가 비슷(Uniform)해져서 같은 정보를 가진다고 한다.(carry all similar information). 이는 레이어를 거듭할수록 주변 단어의 정보가 무분별하게 섞이면서(Contextualized), 상위 레이어의 토큰들이 결국 다 비슷비슷한 정보를 공유하게 되기 때문이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;따라서 input layer까지 어텐션 가중치를 확인해봐야 하고, 그 방법들이 attention rollout/flow이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&amp;nbsp;&lt;/h3&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;Residual Connection&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;논문에서는 어텐션 가중치 분석을 할때 잔차 연결을 반드시 고려해야 한다고 한다. 잔차 연결은 다른 layer의 같은 위치를 tying하는 핵심 역할을 하기 때문에 중요하다고 주장한다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;rescon.png&quot; data-origin-width=&quot;484&quot; data-origin-height=&quot;379&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cUsgys/dJMcajv18mR/l3ZARUQqJb0LhrXItnhk2K/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cUsgys/dJMcajv18mR/l3ZARUQqJb0LhrXItnhk2K/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cUsgys/dJMcajv18mR/l3ZARUQqJb0LhrXItnhk2K/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcUsgys%2FdJMcajv18mR%2Fl3ZARUQqJb0LhrXItnhk2K%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;508&quot; height=&quot;398&quot; data-filename=&quot;rescon.png&quot; data-origin-width=&quot;484&quot; data-origin-height=&quot;379&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;따라서 실제 계산을 할 때에는 어텐션 행렬에 identity metrix를 0.5의 비율로 더해서 새로운 행렬을 만들고, 새로운 행렬을 이용해 계산한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;Attention Rollout&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;논문은 introduction에서 rollout은 레이어에서 정보가 넘어갈때 곱하기와 더하기만으로 정보가 전달된다는 선형 가정을 한다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;가정.png&quot; data-origin-width=&quot;478&quot; data-origin-height=&quot;87&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/r62vA/dJMcabEJkvk/lrHNzgTvev9LU8seOHvwy1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/r62vA/dJMcabEJkvk/lrHNzgTvev9LU8seOHvwy1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/r62vA/dJMcabEJkvk/lrHNzgTvev9LU8seOHvwy1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fr62vA%2FdJMcabEJkvk%2FlrHNzgTvev9LU8seOHvwy1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;555&quot; height=&quot;101&quot; data-filename=&quot;가정.png&quot; data-origin-width=&quot;478&quot; data-origin-height=&quot;87&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;먼저 논문에 나온 path는 정보의 이동 통로, node는 내부의 단어, edge는 어텐션 가중치라고 생각하면 이해하기 쉽다. Attention flow는 한 노드에서 다른 노드로 가는 정보의 양을 edges를 곱해서 계산한 다음, 모든 path를 더한다. 이 연산은 행렬끼리의 곱셈으로 계산할 수 있는데, 구체적인 식은 아래와 같다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;rollout ㅛㅣㅅ.png&quot; data-origin-width=&quot;638&quot; data-origin-height=&quot;274&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/nYmkw/dJMcajbDkEK/5uqgtE8o8KHsYmInZgNgI1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/nYmkw/dJMcajbDkEK/5uqgtE8o8KHsYmInZgNgI1/img.png&quot; data-alt=&quot;재귀적으로 어텐션 가중치들을 곱한다.&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/nYmkw/dJMcajbDkEK/5uqgtE8o8KHsYmInZgNgI1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FnYmkw%2FdJMcajbDkEK%2F5uqgtE8o8KHsYmInZgNgI1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;638&quot; height=&quot;274&quot; data-filename=&quot;rollout ㅛㅣㅅ.png&quot; data-origin-width=&quot;638&quot; data-origin-height=&quot;274&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;재귀적으로 어텐션 가중치들을 곱한다.&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;예시를 들어서 설명해보겠다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;2개의 입력 단어(토큰)를 &lt;span data-index-in-node=&quot;25&quot; data-math=&quot;V_1, V_2&quot;&gt;$V_1, V_2$&lt;/span&gt;라고 하고, 최종 레이어의 출력 토큰이&amp;nbsp;&lt;span data-index-in-node=&quot;55&quot; data-math=&quot;CLS&quot;&gt;$CLS$인 &lt;/span&gt;레이어 2개의 Transformer가 있다고 해보자.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;먼저 Layer 1 의 어텐션 맵을&amp;nbsp; $A_1 $= $\begin{pmatrix} 0.7 &amp;amp; 0.3 \\ 0.2 &amp;amp; 0.8 \end{pmatrix}$ 라고 하고,&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;Layer 2의 CLS 벡터를 $A_2$= $\begin{pmatrix} 0.6 &amp;amp; 0.4 \end{pmatrix}$ 라고 하자.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;두 어텐션 행렬의 곱으로 attention rollout을 구하는 과정은 아래와 같다.&lt;/p&gt;
&lt;div data-math=&quot;A_2 \times A_1 = \begin{pmatrix} 0.6 &amp;amp; 0.4 \end{pmatrix} \begin{pmatrix} 0.7 &amp;amp; 0.3 \\ 0.2 &amp;amp; 0.8 \end{pmatrix} = \begin{pmatrix} (0.6 \times 0.7 + 0.4 \times 0.2) &amp;amp; (0.6 \times 0.3 + 0.4 \times 0.8) \end{pmatrix}&quot;&gt;$$A_2 \times A_1 = \begin{pmatrix} 0.6 &amp;amp; 0.4 \end{pmatrix} \begin{pmatrix} 0.7 &amp;amp; 0.3 \\ 0.2 &amp;amp; 0.8 \end{pmatrix} = \begin{pmatrix} (0.6 \times 0.7 + 0.4 \times 0.2) &amp;amp; (0.6 \times 0.3 + 0.4 \times 0.8) \end{pmatrix}$$&lt;/div&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이때 $CLS$토큰이 최초 $V_1$ 토큰으로부터 받은 정보의 경로를 분석해보면&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;1. 첫번째 경로(&amp;nbsp;&lt;span data-index-in-node=&quot;9&quot; data-math=&quot;0.6 \times 0.7&quot;&gt;$0.6 \times 0.7$)&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span data-index-in-node=&quot;4&quot; data-math=&quot;CLS \xrightarrow{0.6} V_1 \xrightarrow{0.7} 원본V_1&quot;&gt;$CLS \xrightarrow{0.6} V_1 \xrightarrow{0.7} V_1$&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span data-index-in-node=&quot;4&quot; data-math=&quot;CLS \xrightarrow{0.6} V_1 \xrightarrow{0.7} 원본V_1&quot;&gt;2.두번째 경로( &lt;span data-index-in-node=&quot;9&quot; data-math=&quot;0.4 \times 0.2&quot;&gt;$0.4 \times 0.2$)&lt;/span&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;$CLS \xrightarrow{0.4} V_2 \xrightarrow{0.2} V_1$&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;다만 이는 단순화된 버전이고, 실제 rollout을 계산할때는 0.5의 비율로 잔차연결을 더한 행렬을 이용해 rollout을 구한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;계산 복잡도는 $O(d \times n^2)$ (d는 모델의 depth, n은 토큰개수)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;Attention Flow&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;attention flow는 최대 유량 알고리즘(max flow algorithm)를 이용하여 정보의 경로를 분석한다. 간단하게 설명해서, rolllot은 정보 전달량을 product로 계산했다면 flow는 경로에 존재하는 간선의 최솟값으로 정의한다. 파이프에 물이 흐를때, 전체 유량은 가장 좁은 구간에서 결정되는 것과 같은 원리로, 상위 layer의 어텐션이 아무리 높아도 하위 layer의 낮은 어텐션이 실제 정보 전달량의 상한을 결정한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;path에서 가장 작은 값을 찾아서 다 더하고/겹치는 path는 분배해준다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;예시를 들면&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;$ex.1$&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; h1: 0.1 &amp;rarr; 0.1 &lt;br /&gt;입력(capacity=1.0) &amp;rarr; h2: 0.1 &amp;rarr; 0.1&amp;nbsp;&amp;nbsp;&amp;rarr; 출력 &lt;br /&gt;&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; h3: 0.8 &amp;rarr; 0.0&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt; $$\text{h1 경로: } \min(0.1,\ 0.1) = 0.1$$ $$\text{h2 경로: } \min(0.1,\ 0.1) = 0.1$$ $$\text{h3 경로: } \min(0.8,\ 0.0) = 0.0$$ $$\text{최대 유량} = 0.1 + 0.1 + 0.0 = 0.2$$&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;$ex.2$&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;입력(capacity=0.5) &amp;rarr; h1: 0.4 &amp;rarr;&amp;nbsp; 출력&lt;br /&gt;&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp;&amp;rarr; h2: 0.3 &amp;rarr;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt; $$\text{capacity: } h_1 = 0.4,\ h_2 = 0.3,\ \text{입력} = 0.5$$ $$h_1\text{에 } 0.4 \text{ 흘림} \Rightarrow h_2\text{에 남은 양} = 0.5 - 0.4 = 0.1$$ $$\therefore\ h_2\text{의 실제 flow} = 0.1 \neq \min(0.5, 0.3) = 0.3$$ $$\text{최대 유량} = 0.4 + 0.1 = 0.5$$ &amp;nbsp;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;두번째 예시에서 가장 최소 capacity를 더하면 0.7이지만(overflow), 앞의 공유 파이프의 최대 capacity가 0.5이기 때문에 h2의 실제 flow는 0.1이 된다. ex.1에서 0.2를 구하고, ex.2에서 0.5를 구하는 과정이 최대 유량 알고리즘이다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;계산 복잡도는 $O(d^2 \times n^4)$ (d는 모델의 depth, n은 토큰개수)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;Analysis &amp;amp; Discussion&lt;/b&gt;&lt;/h3&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;화면 캡처 2026-07-01 150910.png&quot; data-origin-width=&quot;1369&quot; data-origin-height=&quot;416&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/FDgUQ/dJMcagzhRPI/4rWcQzjkVFkAoJK14B0cg1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/FDgUQ/dJMcagzhRPI/4rWcQzjkVFkAoJK14B0cg1/img.png&quot; data-alt=&quot;(b), (c)에서 첫 레이어의 어텐션은 같음. 상위 계층으로 갈수록 레이어 누적되면서 차이 생긴다. 잔차 연결도 희미해짐&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/FDgUQ/dJMcagzhRPI/4rWcQzjkVFkAoJK14B0cg1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FFDgUQ%2FdJMcagzhRPI%2F4rWcQzjkVFkAoJK14B0cg1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;913&quot; height=&quot;277&quot; data-filename=&quot;화면 캡처 2026-07-01 150910.png&quot; data-origin-width=&quot;1369&quot; data-origin-height=&quot;416&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;(b), (c)에서 첫 레이어의 어텐션은 같음. 상위 계층으로 갈수록 레이어 누적되면서 차이 생긴다. 잔차 연결도 희미해짐&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;논문에서 실험결과 attention flow의 정확도가 attention rollout보다 높다고 한다. flow는 최대 전파 가능 용량을 측정하고, rollout은 정확한 양을 측정하는데 이때 rollout은 단순화된 가정(선형 가정 등)을 사용해서 보다 유연한 방식인 flow보다 정확도가 떨어진다고 한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스피어만 상관계수.png&quot; data-origin-width=&quot;625&quot; data-origin-height=&quot;245&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/b0w2Ro/dJMcaiRqcYa/aXqkdfhoSxDAluk4Tcnkh0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/b0w2Ro/dJMcaiRqcYa/aXqkdfhoSxDAluk4Tcnkh0/img.png&quot; data-alt=&quot;스피어만 상관계수: flow가 rollout보다 근소하게 높다.&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/b0w2Ro/dJMcaiRqcYa/aXqkdfhoSxDAluk4Tcnkh0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fb0w2Ro%2FdJMcaiRqcYa%2FaXqkdfhoSxDAluk4Tcnkh0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;625&quot; height=&quot;245&quot; data-filename=&quot;스피어만 상관계수.png&quot; data-origin-width=&quot;625&quot; data-origin-height=&quot;245&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;스피어만 상관계수: flow가 rollout보다 근소하게 높다.&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;화면 캡처 2026-07-01 150854.png&quot; data-origin-width=&quot;619&quot; data-origin-height=&quot;711&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/b2StTA/dJMcaasl1T9/PYbKbiRxROQYR3kmRyXZm0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/b2StTA/dJMcaasl1T9/PYbKbiRxROQYR3kmRyXZm0/img.png&quot; data-alt=&quot;예시(b)에서 모델은 Mary에 어텐션, 유일하게 flow만 정확하게 모델을 파악했다.&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/b2StTA/dJMcaasl1T9/PYbKbiRxROQYR3kmRyXZm0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fb2StTA%2FdJMcaasl1T9%2FPYbKbiRxROQYR3kmRyXZm0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;619&quot; height=&quot;711&quot; data-filename=&quot;화면 캡처 2026-07-01 150854.png&quot; data-origin-width=&quot;619&quot; data-origin-height=&quot;711&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;예시(b)에서 모델은 Mary에 어텐션, 유일하게 flow만 정확하게 모델을 파악했다.&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;화면 캡처 2026-07-01 150842.png&quot; data-origin-width=&quot;652&quot; data-origin-height=&quot;395&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/MIlWO/dJMcadJfZ2g/PQVsCilPu7APNgwjxFhZAk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/MIlWO/dJMcadJfZ2g/PQVsCilPu7APNgwjxFhZAk/img.png&quot; data-alt=&quot;rollout은 flow보다 strict하고 focused&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/MIlWO/dJMcadJfZ2g/PQVsCilPu7APNgwjxFhZAk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FMIlWO%2FdJMcadJfZ2g%2FPQVsCilPu7APNgwjxFhZAk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;652&quot; height=&quot;395&quot; data-filename=&quot;화면 캡처 2026-07-01 150842.png&quot; data-origin-width=&quot;652&quot; data-origin-height=&quot;395&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;rollout은 flow보다 strict하고 focused&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;Conclusion&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;논문에서는 Transformer 모델의 내부를 설명하는 방법으로 정보가 무분별하게 섞여서 정확도가 떨어지는 기존의 raw attention map 대신 잔차연결을 고려하고, token attention을 재귀적으로 계산하는 방식인 attention rollout/attention flow&amp;nbsp; 방법을 제안했다. ablation method, input gradient를 사용해 실험해본 결과 기존 raw attention map보다 모델을 잘 설명하는 것으로 드러났다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;flow 방식대로라면 처음에 0.1만 어텐션한 path라면 나중에 0.9들 높은 값으로 어텐션되나 0.01로 어텐션되나 같은 정보 전달로 파악될텐데 이 점이 attention flow의 약점이 될 수 있다고 생각했다. 이 논문이 나온 이후 flow를 vit에 적용한 논문으로 &lt;i data-path-to-node=&quot;6,0&quot; data-index-in-node=&quot;10&quot;&gt;Transformer Interpretability Beyond Attention Visualization&lt;/i&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;(CVPR 2021)가 있다고 하는데, 다음에 읽어봐야겠다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;Attention Rollout 구현&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://icedtea.tistory.com/2&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt; &lt;b&gt;Attention Rollout 코드 구현(APTOS 2019)&lt;/b&gt; &lt;/a&gt;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style5&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;APTOS 캐글 데이터셋 ViT로 학습시키다가 여기까지 와버렸다. 블로그 작성은 계획에 없었는데 너무 기록을 대충 하는 것 같아서 이렇게라도 해야겠다고 생각했다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;논문 읽는것도 거의 처음이고, 초등학생 때 이후로 인터넷 공간에 글을 작성하는게 처음이라 너무 어색하고 힘들다... 꾸준하게 해봐야지.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이제 APTOS 데이터셋을 학습시킨 ViT를 Rollout기법으로 시각화한 코드를 리뷰해보겠다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>논문리뷰</category>
      <category>attentionflow #attentionrollout #transformers #vit</category>
      <author>rminhyuk</author>
      <guid isPermaLink="true">https://icedtea.tistory.com/1</guid>
      <comments>https://icedtea.tistory.com/entry/%ED%99%98%EC%98%81%ED%95%A9%EB%8B%88%EB%8B%A4#entry1comment</comments>
      <pubDate>Wed, 1 Jul 2026 15:43:52 +0900</pubDate>
    </item>
  </channel>
</rss>