<?xml version="1.0" encoding="utf-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom">
    <channel>
        <title>changh2.log</title>
        <link>https://velog.io/</link>
        <description>Shoot for the moon! 🔥</description>
        <lastBuildDate>Mon, 14 Jul 2025 03:26:39 GMT</lastBuildDate>
        <docs>https://validator.w3.org/feed/docs/rss2.html</docs>
        <generator>https://github.com/jpmonette/feed</generator>
        <image>
            <title>changh2.log</title>
            <url>https://velog.velcdn.com/images/changh2_00/profile/af07d9a4-12ad-4052-b0b6-112235aa4146/image.jpg</url>
            <link>https://velog.io/</link>
        </image>
        <copyright>Copyright (C) 2019. changh2.log. All rights reserved.</copyright>
        <atom:link href="https://v2.velog.io/rss/changh2_00" rel="self" type="application/rss+xml"/>
        <item>
            <title><![CDATA[[논문리뷰] U-Net: Convolutional Networks for Biomedical
Image Segmentation]]></title>
            <link>https://velog.io/@changh2_00/%EB%85%BC%EB%AC%B8%EB%A6%AC%EB%B7%B0-U-Net-Convolutional-Networks-for-BiomedicalImage-Segmentation</link>
            <guid>https://velog.io/@changh2_00/%EB%85%BC%EB%AC%B8%EB%A6%AC%EB%B7%B0-U-Net-Convolutional-Networks-for-BiomedicalImage-Segmentation</guid>
            <pubDate>Mon, 14 Jul 2025 03:26:39 GMT</pubDate>
            <description><![CDATA[<hr>
<a href = "https://arxiv.org/abs/1505.04597" target="_blank">
U-Net: Convolutional Networks for Biomedical
Image Segmentation</a>
논문을 기반으로 작성되었습니다.

<hr>
<h1 id="background-info">[Background info]</h1>
<h3 id="segmentation">Segmentation</h3>
<p><strong>&gt;</strong> 입력 이미지의 <strong>모든 픽셀</strong>에 대해 <strong>Classification</strong>해, 각 픽셀이 어느 클래스에 속하는지 라벨 할당 = <strong>분할</strong></p>
<p><img src="https://velog.velcdn.com/images/changh2_00/post/8a9b41c1-034b-42ef-89e7-23e4057d2776/image.png" alt=""></p>
<h3 id="semantic-vs-instance-segmentation">Semantic vs Instance (Segmentation)</h3>
<ul>
<li>Semantic segmentation: 같은 클래스 내부에서 <strong>객체 구분 불가능</strong></li>
<li>Instance segmentation: 같은 클래스 내부에서 <strong>객체 구분 가능</strong></li>
</ul>
<p><img src="https://velog.velcdn.com/images/changh2_00/post/0af20b46-def4-4950-8c28-122006eb6d97/image.png" alt=""></p>
<h3 id="biomedical-image-segmentation">Biomedical Image Segmentation</h3>
<ul>
<li>의료 또는 생물학 연구에서 얻은 영상(X-ray, CT, MRI, 현미경 사진 등) 내의 특정 구조물(종양, 세포 등)을 <strong>픽셀 단위</strong>로 정확하게 <strong>식별</strong>하고 <strong>경계</strong>를 나누는 기술</li>
<li>U-Net에선 주로 <strong>세포</strong> 구조를 분할</li>
<li>현미경 영상과 같은 <strong>미세 구조</strong>를 다루는 영상에서 픽셀 단위의 <strong>Segmentation</strong>이 중요</li>
</ul>
<p><img src="https://velog.velcdn.com/images/changh2_00/post/8f877311-204b-4972-bb30-2c6cc302ec09/image.png" alt=""></p>
<hr>
<h1 id="1-introduction">1. Introduction</h1>
<h2 id="before-u-net">Before U-Net</h2>
<h3 id="cnn--sliding-window">CNN + Sliding-Window</h3>
<ul>
<li>VGGNet처럼 conv layer들을 거친 후 최하단에 <strong>Fully-Connected Layer</strong>를 놓아 Classification 하는 전통 CNN 모델을 픽셀 단위로 적용</li>
<li>이미지의 한** 픽셀 주변값(patch)<strong>을 입력으로 받아, patch 중앙의 한 **픽셀 클래스</strong>를 <strong>출력(부여)</strong>하는 방식</li>
<li>모든 <strong>픽셀마다</strong> 네트워크가 <strong>독립적</strong>으로 <strong>수행</strong>돼 <span style='background-color:#ffdce0'>속도가 느리고</span>, 각 <strong>패치마다 겹치는</strong> 부분이 많아 중복으로 인한 낭비가 심함</li>
<li>공간 context를 확보하기 위해 <strong>patch 크기를 늘리면</strong> 더 많은 <strong>max pooling</strong>을 거쳐야하므로 <span style='background-color:#ffdce0'>localization 정확도가 떨어짐</span> (trade-off)</li>
</ul>
<p><img src="https://velog.velcdn.com/images/changh2_00/post/67476dc1-2a77-4722-a6d6-0ed14994ca0a/image.png" alt=""></p>
<h3 id="fcn-fully-convolutional-network">FCN: Fully Convolutional Network</h3>
<ul>
<li>기존 방식을 완전히 개편하여 <strong>FC Layer</strong>를 없애고 모두 <strong>1x1 conv layer</strong>로 <strong>대체</strong>해, <strong>이미지 전체</strong>를 <strong>입력</strong>으로 받아 <strong>전체 공간 특징맵(segmentation)</strong>을** 출력**하는 모델</li>
<li>Semantic Segmentation을 위해 제안됨</li>
<li>이 Base model을 수정, <strong>확장</strong>하여 제안된 모델이 <strong>U-Net</strong></li>
</ul>
<p><img src="https://velog.velcdn.com/images/changh2_00/post/5c1dc0cc-0356-45e2-854f-e9d0a97f76b7/image.png" alt=""></p>
<hr>
<h2 id="idea-of-u-net">Idea of U-Net</h2>
<ul>
<li>FCN보다 업샘플링 과정을 더 많이 가져가, U자형 대칭 인코더-디코더 구조</li>
<li>고해상도 특징의 연결을 위한 Skip Connection</li>
<li>Transposed Convolution(Up-Convolution)을 통한 업샘플링</li>
<li>Overlap-Tile, Mirroring</li>
<li>Weighted Loss, Data augmentation</li>
</ul>
<p><img src="https://velog.velcdn.com/images/changh2_00/post/18547d0e-f1bd-4980-ae9e-91af85350d04/image.png" alt=""></p>
<hr>
<h1 id="2-network-architecture">2. Network Architecture</h1>
<h2 id="architecture">Architecture</h2>
<ul>
<li><strong>Contracting Path</strong><ul>
<li>각 step 마다 3x3 conv를 두 차례식 반복 (패딩x)</li>
<li>conv layer에는 ReLU 연산이 포함</li>
<li>conv 이후엔 2x2 max-pooling (stride=2)</li>
<li>Down Sampling마다 채널 수를 2배로 늘림</li>
</ul>
</li>
</ul>
<p><img src="https://velog.velcdn.com/images/changh2_00/post/9b1b4325-370e-4b13-ba0d-10ab29e8cfac/image.png" alt=""></p>
<ul>
<li><strong>Expansive Path</strong><ul>
<li>각 step마다 <strong>2x2 Up-conv</strong>(stride: 2)을 통해 feature map의 해상도가 2배로 늘어남</li>
<li>3x3 conv를 두 차례씩 반복 (패딩 x)</li>
<li>conv layer에는 ReLU 연산이 포함</li>
<li>Up-conv된 feature map은 <strong>Contracting path에서 cropped된 feature map</strong>과 <strong>concatenate</strong> 시킴 (Skip Connection)</li>
<li>마지막 레이어로 <strong>1x1 conv</strong>를 통해 Segmentation map 출력 <strong>(필터 개수 = 총 클래스 수)</strong></li>
</ul>
</li>
</ul>
<p><img src="https://velog.velcdn.com/images/changh2_00/post/744e0551-392b-4bb0-9b88-eef41fd4034a/image.png" alt=""></p>
<hr>
<h2 id="overlap-tile">Overlap-Tile</h2>
<ul>
<li>크기가 큰 이미지의 경우엔 GPU <strong>Memory</strong>가 <strong>부족</strong>한 문제가 있으므로, 이미지 전체를 사용하는 대신 이미지를 각각의 <strong>Tile</strong>로 <strong>나눠</strong> segmentation </li>
<li>하단의 사진과 같이 이미지를 타일로 나눠서 입력으로 사용</li>
<li>segmentation을 위해 각 <strong>tile</strong>의 <strong>주변 픽셀</strong>을 같이 입력받기 때문에, 다음 tile에 대한 segmentation을 얻기 위해서는 <strong>이전 입력</strong>의 일부분이 <strong>포함</strong>됨 (overlap)</li>
<li>이미지의 <strong>경계 부분 픽셀</strong>에 대한 segmentation을 위해 <strong>미러링</strong>을 이용한 <strong>Extrapolation</strong> 기법 사용</li>
</ul>
<p><img src="https://velog.velcdn.com/images/changh2_00/post/8dd65931-9a2e-469c-a2ea-76ec7dce8fb9/image.png" alt=""></p>
<h2 id="mirroring-extrapolate">Mirroring Extrapolate</h2>
<ul>
<li>이미지의 <strong>경계 부분 픽셀</strong>에 대한 segmentation을 위해 0이나 임의의 패딩값을 사용하는 대신 이미지 경계 부분의 <strong>미러링</strong>을 이용한 <strong>Extrapolation</strong> 기법 사용</li>
<li>주요 데이터셋이 <strong>세포</strong>이므로, 비슷하게 퍼지는 세포의 특성에 따라 높은 성능을 낼 수 있음</li>
</ul>
<p><img src="https://velog.velcdn.com/images/changh2_00/post/d2385e15-dde8-411a-aade-8f4d114b0a52/image.png" alt=""></p>
<hr>
<h1 id="3-training">3. Training</h1>
<p><img src="https://velog.velcdn.com/images/changh2_00/post/d5b745ff-19c1-4fd5-ba23-97963fe5c646/image.png" alt=""></p>
<p>$$
p_{l,x}(x) = \frac{e^{z_{l,x}}}{\displaystyle\sum\nolimits_{j}e^{z_{j,x}}}
$$</p>
<p>*<em>1) SoftMax를 사용하여 예측값을 확률로 변환
*</em>픽셀단위 예측값에 SoftMax 함수 P를 적용하여, 각 클래스에 속할 확률을 계산</p>
<br>
<br>

<p>$$
\log\bigl(p_{l,x}(x)\bigr)
$$</p>
<p>*<em>2) 로그 확률 계산 (Cross Entropy Loss 적용)
*</em>정확한 예측일수록 손실이 작고 잘못된 예측일수록 손실이 커짐</p>
<br>
<br>

<p>$$
E = \sum_{x\in\Omega}w(x),\log\bigl(p_{\ell(x)}(x)\bigr)
$$</p>
<p>*<em>3) 픽셀별 Weight 적용하여 Loss 계산
*</em>경계선 픽셀을 더 강하게 학습하기 위해 Cross Entropy Loss에 
픽셀 고유의 Weight w(x)를 곱함</p>
<br>
<br>

<p>$$
w(x) = w_c(x) + w_0 \times ,\exp!\Bigl(-\frac{\bigl(d_1(x)+d_2(x)\bigr)^2}{2\sigma^2}\Bigr)
$$
*<em>4) Weight Map 설계
*</em>가우시안 분포를 기반으로 경계선 픽셀에 더 높은 가중치를 부여하여 학습을 집중 
→ 경계를 더 정교하게 예측할 수 있도록 유도</p>
<br>


<h2 id="31-data-augmentation">3.1 Data Augmentation</h2>
<ul>
<li><strong>Elastic Deformation</strong><ul>
<li>각 이미지 그리드에 확률적 노이즈를 더해 픽셀별로 서로 다른 방향으로 뒤틀림을 주기 때문에, 획일적이지 않고 자연스러운 형태 변형이 가능</li>
<li>실제 세포는 관측 시점마다 모양이 달라지므로, 이런 탄성 변형이 순간적인 형태 변화를 효과적으로 모사 가능</li>
<li>예시로, 아래의 전립선 MRI에 랜덤 탄성 변형을 적용하면 다양한 형태의 학습 데이터를 얻어, 데이터가 적어도 모델 성능을 크게 끌어올릴 수 있음
<img src="https://velog.velcdn.com/images/changh2_00/post/aff196f0-212f-428b-8c8b-f2a318419baa/image.png" alt=""></li>
</ul>
</li>
</ul>
<hr>
<h1 id="4-experiments">4. Experiments</h1>
<h3 id="em-segmentation-challenge">EM Segmentation Challenge</h3>
<p>전자 현미경 이미지에서 신경 구조 또는 세포 구조를 분할하는 문제를 다루는 대회
U-Net이 1위(0.000353 Warping Error) 기록</p>
<p><img src="https://velog.velcdn.com/images/changh2_00/post/b919576f-c93d-43e1-9c45-34bee3672ab4/image.png" alt=""></p>
<pre><code>Warping Error :  신경 조직의 구조적 왜곡 정도를 측정하는 지표
Rand Error :  클러스터링 기반으로 객체 간 차이를 측정하는 지표
Pixel Error :  픽셀 단위에서 예측과 정답의 차이를 측정하는 지표</code></pre><br>

<h3 id="isbi-cell-tracking-chanllenge">ISBI Cell Tracking Chanllenge</h3>
<p>세포의 성장, 분열 및 이동을 자동으로 추적하는 문제를 해결하는 대회
ISBI 2015 Cell Tracking Challenge에서 1위</p>
<p><img src="https://velog.velcdn.com/images/changh2_00/post/5dc3d111-9bae-4d8a-bf9b-755188e641af/image.png" alt=""></p>
<p><img src="https://velog.velcdn.com/images/changh2_00/post/3c80a9d8-604d-4a63-a212-a1fbbea5af1a/image.png" alt=""></p>
<pre><code>PhC-U373 :  위상차 현미경 이미지
DIC-HeLa :  DIC 방식으로 촬영된 HeLa 세포 이미지
모두 IoU(Intersection over Union)로 평가</code></pre><hr>
<h1 id="5-conclusion">5. Conclusion</h1>
<p>U-Net은 다양한 생물의학 이미지 분할 문제에서 매우 우수한 성능을 보임
Elastic Deformation을 활용한 데이터 증강 덕분에 적은 데이터로도 학습 가능
NVidia Titan GPU (6GB)에서 10시간 학습→빠르고 효율적인 모델</p>
<p>∴ U-Net은 의료 영상 분석 및 다양한 이미지 분할 문제에서 표준 모델로 자리 잡음</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[[논문리뷰] Very Deep Convolutional Networks for Large-Scale Image Recognition]]></title>
            <link>https://velog.io/@changh2_00/%EB%85%BC%EB%AC%B8%EB%A6%AC%EB%B7%B0-Very-Deep-Convolutional-Networks-for-Large-Scale-Image-Recognition</link>
            <guid>https://velog.io/@changh2_00/%EB%85%BC%EB%AC%B8%EB%A6%AC%EB%B7%B0-Very-Deep-Convolutional-Networks-for-Large-Scale-Image-Recognition</guid>
            <pubDate>Fri, 04 Jul 2025 06:43:59 GMT</pubDate>
            <description><![CDATA[<hr>
<a href = "https://arxiv.org/abs/1409.1556" target="_blank">
Very Deep Convolutional Networks for Large-Scale Image Recognition</a>
논문을 기반으로 작성되었습니다.

<hr>
<h2 id="background-info">[Background Info]</h2>
<h3 id="ilsvrc란">ILSVRC란?</h3>
<p><strong>ILSVR</strong>C는 “<strong>I</strong>mageNet <strong>L</strong>arge <strong>S</strong>cale <strong>V</strong>isual <strong>R</strong>ecognition <strong>C</strong>hallenge”의 약자로, 매년 ImageNet 데이터셋을 활용해 물체 분류(classification), 검출(detection), 위치(localization) 등의 성능을 겨루는 대표적인 컴퓨터 비전 경진 대회를 말함.</p>
<p><img src="https://velog.velcdn.com/images/changh2_00/post/6cba32f0-34a9-4f8c-82f0-b7a70d87b5c4/image.png" alt=""></p>
<h3 id="alexnet">AlexNet</h3>
<p><img src="https://velog.velcdn.com/images/changh2_00/post/211c7b65-56b5-4170-a425-c6a6f384826a/image.png" alt=""></p>
<ul>
<li>ILSVRC 2012에서 우승을 차지한 AlexNet은 총 <strong>8개의 얕은 layer</strong> 수를 가지고, 각 layer 마다 <strong>11x11, 5x5, 3x3의 필터들을 혼용</strong>하며 사용함</li>
<li><strong>LRN</strong>(Local Response Normalization)을 사용함</li>
</ul>
<hr>
<h1 id="1-introduction">1. Introduction</h1>
<ul>
<li>2012년의 AlexNet 이후, 2013년에 AlexNet의 모델 구조는 동일하되 필터 사이즈와 스트라이드를 줄인 ZFNet이 등장</li>
<li>이 논문에선, 필터 사이즈 뿐만 아닌 <strong>&quot;layer의 개수&quot;</strong>에 집중하여 <strong>&quot;깊이&quot;</strong>를 늘려봄.</li>
<li>위가 가능한 이유는, <strong>모든 Conv층에서 3x3의 작은 필터</strong>만을 사용했기에 깊이를 늘려도 파라미터수,연산량이 과하게 증가하지 않았기 때문.</li>
<li>이렇게 깊어진 VGGNet 모델은 ILSVRC의 분류와 위치추정에서 SOTA를 달성함</li>
</ul>
<hr>
<h1 id="2-convnet-configurations">2. ConvNet Configurations</h1>
<h2 id="21-architecture">2.1 Architecture</h2>
<p><img src="https://velog.velcdn.com/images/changh2_00/post/64c2b181-12dc-4983-8a5a-466fd29fe283/image.png" alt=""></p>
<h4 id="input-image">Input image</h4>
<ul>
<li>224 X 224 RGB</li>
<li>전처리는 RGB 평균값 빼주는것만 적용
 <h4 id="conv-layer">Conv layer</h4>
</li>
<li>3 x 3 Conv filter 사용 (이때, 3 x 3 사이즈가 이미지 요소의 left, right, up, down 등을 파악할 수 있는 최소한의 receptive field이기 때문에 3 x 3 사이즈를 사용한다.)</li>
<li>추가로, 1 x 1 Conv filter도 사용하는데, 이는 뒤에서 더 자세히 설명하겠지만, 차원을 줄이고 non-linearity를 증가시키기 위함이다.</li>
<li>stride는 1, padding 또한 1로 설정
 <h4 id="pooling-layer">Pooling layer</h4>
</li>
<li>Conv layer 다음에 적용되고, 총 5개의 max pooling layer로 구성된다.</li>
<li>2 x 2 사이즈, stride는 2</li>
</ul>
<h4 id="fc-layer">FC layer</h4>
<ul>
<li>처음 두 FC layer는 4,096 채널</li>
<li>마지막 FC layer는 1,000 채널
 
마지막으로, <strong>soft-max layer</strong>를 적용해준다.
 
그 외 모든 layer에는 <strong>ReLU</strong>를 사용하며, AlexNet에서 사용한 <strong>LRN 기법은 성능 개선은 없고 메모리 사용량 및 연산 시간만 늘어났기에 사용을 하지 않는다.</strong> 
(LRN이 적용된 구조는 A-LRN인데 뒤에서 성능 비교표가 나온다.)</li>
</ul>
<p>출처: <a href = "https://phil-baek.tistory.com/entry/1-Very-Deep-Convolutional-Networks-for-Large-Scale-Image-Recognition-VGGNet-논문-리뷰" target="_blank">[philBaek (백광록) 개발 일기장:티스토리]</a></p>
<hr>
<h2 id="22-configurations">2.2 Configurations</h2>
<p><img src="https://velog.velcdn.com/images/changh2_00/post/5ee9248a-eb84-4cf9-a8e5-6a53a6591c3b/image.png" alt=""></p>
<p>모델은 깊이에 따라 A(11depth)부터 E(19depth)까지 조금씩 구조가 달라지며, conv layer의 채널 수는 64로 시작해 각 max-pooling을 거칠 때마다 2배씩 늘어나 최대 512에 이르게됨. 
+) depth를 늘리면서도 개별 conv의 필터 크기를 작게 유지해(3x3) 큰 conv 필터를 가지며 얕은 depth를 갖는 네트워크보다 파라미터 수를 줄였음</p>
<hr>
<h2 id="23-discussion">2.3 Discussion</h2>
<h4 id="3개의-3x3-conv는-1개의-7x7-conv와-같은-효과를-주는데-7x7-대신-3x3를-쓰는-이유는">3개의 3x3 conv는 1개의 7x7 conv와 같은 효과를 주는데, 7x7 대신 3x3를 쓰는 이유는?</h4>
<ul>
<li>ReLU 단계가 더 많아 비선형적 의사결정을 더 잘하게 됨</li>
<li>파라미터 수를 줄여줌. (파라미터 수 = 필터크기 * 입력 채널수 * 출력 채널수)
채널 수 = C 라고 할때,
3(3*3*C*C) &lt; 7*7*C*C</li>
</ul>
<h4 id="c-모델의-1x1-conv는">C 모델의 1x1 conv는?</h4>
<ul>
<li>1x1 필터를 쓰면 수용 영역은 그대로 유지하면서 ReLU를 통해 비선형성을 주입할 수 있음</li>
<li>하지만, 픽셀 주변의 공간 정보를 추출할 수 없어 성능 낮아짐</li>
</ul>
<h4 id="다른-연구와의-차별점">다른 연구와의 차별점?</h4>
<ul>
<li>GoogLeNet은 22개의 layer에서 1x1, 3x3, 5x5의 복합 필터를 쓰되 구조가 더 복잡하고 공간 해상도를 급격히 줄여 계산량을 관리한 반면, VGGNet은 더 단순한 반복 구조만으로 우수한 정확도를 달성함.</li>
</ul>
<hr>
<h1 id="3-classification-framework">3. Classification Framework</h1>
<h2 id="31-training">3.1 Training</h2>
<p>다중회귀(Softmax+CrossEntropy)를 미니배치 경사하강법과 모멘텀을 사용해 최적화함</p>
<h4 id="하이퍼파라미터">하이퍼파라미터</h4>
<ul>
<li>배치 크기(batch size): 256</li>
<li>가중치 감쇠(weight decay, L2 정규화): 5 × 10⁻⁴</li>
<li>드롭아웃(dropout): 처음 두 개의 FC 층에 각 0.5 비율 적용</li>
<li>학습률(learning rate): 초기 10⁻²에서 출발해, 검증 정확도가 정체될 때마다 10분의 1씩 세 차례 감소</li>
<li>총 반복(iteration): 370,000번 (약 74 에폭)</li>
</ul>
<h4 id="가중치-초기화">가중치 초기화</h4>
<ol>
<li>가장 얕은 모델(<strong>A</strong> 구성)을 <strong>랜덤 initialization</strong>으로 먼저 학습</li>
<li>이후 <strong>더 깊은 모델 학습 시, A 구성의 “첫 4개 합성곱층 + 마지막 3개 FC층” 파라미터를 가져와서 초기값으로 사용</strong> (나머지는 랜덤)
(네트워크의 깊이가 깊어질수록 <strong>기울기(gradient)가 불안정</strong>해져 학습이 제대로 진행되지 않는 문제가 발생할 수 있기 때문)</li>
<li>랜덤 초기화 분포: 평균 0, 분산 10⁻², 편향은 0</li>
</ol>
<h4 id="데이터-증강data-augmentation">데이터 증강(data augmentation)</h4>
<ul>
<li>학습 이미지를 임의의 크기 스케일 S에서 224×224 크롭</li>
<li>랜덤 수평 뒤집기(horizontal flip)</li>
<li>RGB 색상 편차(random colour shift) 적용</li>
</ul>
<h4 id="스케일-설정">스케일 설정</h4>
<ul>
<li>단일-스케일 학습: S = 256 또는 S = 384 두 가지로 학습</li>
<li>다중-스케일 학습: S를 256~512 범위에서 무작위 선택해 크롭 (스케일 지터링) 후 S = 384 단일-스케일 모델을 미세조정(fine-tune)</li>
</ul>
<hr>
<h2 id="32-testing">3.2 Testing</h2>
<ul>
<li><p><strong>스케일 Q</strong>: 테스트 이미지를 가장 짧은 변이 Q로 등비 축소(rescale)</p>
</li>
<li><p><strong>Dense 평가</strong></p>
<ol>
<li>FC 층을 7×7, 1×1 합성곱으로 변환해 “완전-합성곱(conv-only)” 네트워크로 바꿈</li>
<li>이미지를 크롭 없이 한 번에 순전파하여 클래스 점수 맵(score map)을 얻음</li>
<li>공간 축소(sum-pooling)로 고정 길이 벡터 생성</li>
</ol>
</li>
<li><p><strong>테스트 증강</strong></p>
<ul>
<li>원본과 뒤집은 이미지의 소프트맥스 점수를 평균</li>
</ul>
</li>
<li><p><strong>Multi-crop vs Dense</strong></p>
<ul>
<li>여러 크롭을 쓰면 정확도는 소폭 오르지만, 네트워크를 반복 실행해야 해 비효율적</li>
<li>Dense 평가 시 패딩이 실제 이웃 픽셀로 채워지기 때문에 더 넓은 문맥(context)을 포착</li>
</ul>
</li>
</ul>
<blockquote>
<h4 id="왜-테스트-시에-fc-레이어를-conv-레이어로-변환하는지">왜 테스트 시에 FC 레이어를 Conv 레이어로 변환하는지?</h4>
<p>FC 레이어의 역할 (훈련 시):</p>
</blockquote>
<p>훈련 시에 ConvNet은 입력 이미지(이 논문에서는 224x224 크기)로부터 계층적인 특징을 추출하고, 마지막 Conv 레이어의 출력은 3차원 특징 맵이 됩니다.
이 특징 맵은 1차원 벡터로 펼쳐져(flatten) 첫 번째 FC 레이어의 입력으로 들어갑니다.
FC 레이어는 이 입력 벡터에 가중치 행렬을 곱하고 bias를 더하는 연산을 수행하여, 추출된 모든 특징들을 종합적으로 판단하여 최종 클래스를 분류하기 위한 점수를 계산합니다. 즉, FC 레이어는 고정된 크기의 입력에 대해 동작하는 &quot;분류기&quot; 역할을 합니다.</p>
<blockquote>
</blockquote>
<p>문제점 (테스트 시):</p>
<blockquote>
</blockquote>
<p>훈련 시에는 입력 이미지가 224x224로 고정되어 있기 때문에 FC 레이어를 그대로 사용할 수 있습니다.
하지만 테스트 시에는 다양한 크기의 이미지가 입력될 수 있습니다. 또한, 이 논문에서는 전체 이미지([Multi-scale training]된 이미지의 다양한 크기)에 대해 조밀하게(densely) 네트워크를 적용하여 평가하고 싶어합니다.
표준 FC 레이어는 입력 크기가 고정되어야 하므로, 다양한 크기의 전체 이미지에 직접 적용하기 어렵습니다. 이미지를 224x224로 잘라내야만 FC 레이어를 통과시킬 수 있게 됩니다.</p>
<blockquote>
</blockquote>
<p>FC 레이어를 Conv 레이어로 변환하는 이유 (테스트 시):</p>
<blockquote>
</blockquote>
<p>동일 계산의 활용: 놀랍게도, 특정 조건 하에서 Conv 레이어는 FC 레이어와 수학적으로 동일한 계산을 수행할 수 있습니다. 예를 들어, 마지막 Conv 레이어의 출력 크기가 W x H이고 채널 수가 C일 때, 이 특징 맵을 1차원으로 펼쳐 (WHC 차원의 벡터) N개의 뉴런을 가진 FC 레이어에 입력하는 연산은, 특징 맵에 (W x H) 크기의 커널(kernel)을 가지고 N개의 출력 채널을 갖는 Conv 레이어를 Stride 1, Padding 0으로 적용하는 연산과 동일하게 만들 수 있습니다. FC 레이어의 가중치 행렬을 적절히 재배열하면 Conv 레이어의 커널이 됩니다.
입력 크기의 유연성 확보: 이렇게 FC 레이어를 &#39;동일한 계산을 수행하는&#39; Conv 레이어 형태로 변환하면, 네트워크 전체가 Convolutional 레이어들로만 구성된 Fully-Convolutional Network가 됩니다. Fully-Convolutional Network는 입력 이미지의 크기에 상관없이 적용될 수 있습니다.</p>
<hr>
<h2 id="33-implementation-details">3.3 Implementation Details</h2>
<ul>
<li><p><strong>프레임워크:</strong> Caffe(C++ 기반)를 포크하여 사용</p>
</li>
<li><p><strong>다중 GPU 학습</strong></p>
<ul>
<li>데이터 병렬 처리(data parallelism): 배치를 GPU 개수만큼 쪼개서 병렬화</li>
<li>각 GPU에서 계산한 기울기를 동기화(synchronous) 후 평균</li>
<li>4 GPU 시스템에서 단일 GPU 대비 약 3.75배 속도 향상</li>
</ul>
</li>
<li><p><strong>학습 소요 시간:</strong> NVIDIA Titan Black GPU 4장 기준, 모델당 2~3주 소요</p>
</li>
</ul>
<hr>
<h1 id="4-classification-experiments">4. Classification Experiments</h1>
<h2 id="41-single-scale-evaluation">4.1 Single Scale Evaluation</h2>
<p><strong>[설정]</strong> 
학습·테스트 모두 하나의 스케일 S에서 진행 (예: S=256→Q=256).</p>
<p><strong>[결과]</strong></p>
<ul>
<li>깊이가 11→19로 증가함에 따라 top-1/5 오류율이 지속 감소 (A:29.6/10.4 → E:25.5/8.0).</li>
<li>Local Response Normalisation은 오히려 성능을 떨어뜨려 제외.</li>
<li>1×1 conv를 추가한 C보다, 3×3만 쓴 D가 더 우수해 “공간 컨텍스트” 포착의 중요성 확인.</li>
<li>학습 시 스케일 지터링(S∈[256,512])을 도입하면 단일-스케일 학습 대비 top-1 오류가 27.3→25.5 등 유의미하게 개선.</li>
</ul>
<p><img src="https://velog.velcdn.com/images/changh2_00/post/b60938df-ee14-4c68-9198-cdfef95896fd/image.png" alt=""></p>
<hr>
<h2 id="42-multi-scale-evaluation">4.2 Multi-Scale Evaluation</h2>
<p><strong>[설정]</strong>
테스트 시 Q를 {S–32,S,S+32} 또는 학습 지터링 모델은 Q∈{256,384,512}로 여러 크기에서 예측 후 평균.</p>
<p><strong>[결과]</strong></p>
<ul>
<li>D, E configuration에서 best 성능: 24.8% top-1 / 7.5% top-5</li>
<li>단일-스케일 테스트보다 1% 정도 오류율 감소.</li>
</ul>
<p><img src="https://velog.velcdn.com/images/changh2_00/post/159a5d67-0da4-4dca-ac78-72be993a094d/image.png" alt=""></p>
<hr>
<h2 id="43-multi-crop-evaluation">4.3 Multi-crop Evaluation</h2>
<p><strong>[설정]</strong></p>
<ol>
<li><p>여러 크롭 생성
원본 이미지를 224×224 크기의 여러 부분(모서리 4곳, 중앙 등)으로 잘라내고, 수평 뒤집기까지 적용해 스케일별로 총 50개의 크롭을 만듬</p>
</li>
<li><p>각 크롭별 추론
만들어진 각 크롭을 개별적으로 ConvNet에 입력해 클래스별 확률(Softmax)을 얻음</p>
</li>
<li><p>결과 결합
모든 크롭의 확률 분포를 평균해, 원본 이미지에 대한 최종 예측을 도출</p>
</li>
</ol>
<p><strong>[결과]</strong></p>
<ul>
<li>Multi-crop만 쓰면 Dense 대비 소폭 향상(24.8→24.6 top-1),</li>
<li>둘을 결합하면 24.4% top-1 / 7.2% top-5까지 추가 개선.</li>
</ul>
<p><img src="https://velog.velcdn.com/images/changh2_00/post/43bda7b8-446d-4b11-af82-03ab255f59f6/image.png" alt=""></p>
<hr>
<h2 id="44-convnet-fusion">4.4 ConvNet Fusion</h2>
<p><strong>[설정]</strong>
여러 모델 Configuration의 softmax 출력을 평균함</p>
<p><strong>[결과]</strong></p>
<ul>
<li>ILSVRC 제출 시(7개 모델) 7.3% test-5 error.</li>
<li>이후 최적의 두 모델(D/E, multi-scale)로만 앙상블:<ul>
<li>Dense eval: 7.0%,</li>
<li>Dense eval+Multi-crop: 6.8% test-5 error.</li>
<li>(둘의 softmax 평균으로 결과 산출)</li>
</ul>
</li>
</ul>
<p><img src="https://velog.velcdn.com/images/changh2_00/post/e0d40eb1-86d7-4e50-8a01-26f34d781372/image.png" alt=""></p>
<hr>
<h2 id="45-comparison-with-the-state-of-the-art">4.5 Comparison with the State of The Art</h2>
<p><strong>[결과]</strong></p>
<ul>
<li>VGG(2-net ensemble, multi-crop+dense): 23.7% top-1 / 6.8% top-5 (검증), 6.8% test-5.</li>
<li>단일-넷(1-net): 24.4%/7.1% (검증), 7.0% (test-5)로 GoogLeNet(6.7%)에 근접하거나, 단일-넷 기준으로는 0.9%p 앞서는 성과를 냈습니다.</li>
<li>ILSVRC-2012/13 우승 모델들(Clarifai, AlexNet, Zeiler&amp;Fergus 등)을 크게 뛰어넘는 결과입니다.</li>
</ul>
<p><img src="https://velog.velcdn.com/images/changh2_00/post/a76e5e5b-00fa-4398-845d-eb738ac47a9b/image.png" alt=""></p>
<hr>
<h1 id="5-conclusion">5. Conclusion</h1>
<ul>
<li>네트워크 깊이를 최대 19층까지 확장함으로써 <strong>깊이의 중요성</strong>을 강조함.</li>
<li>전통적인 ConvNet 아키텍처에 깊이만 추가해도 ImageNet 챌린지에서 최첨단 성능을 달성하며, <strong>깊이가 깊어질수록</strong> 이미지 분류 <strong>정확도가 크게 향상</strong>됨을 명확히 확인함.</li>
<li><strong>다양한 데이터셋</strong>과 과제에서도 깊은 표현이 복잡한 파이프라인에 견줄 만큼 <strong>뛰어난 일반화 능력</strong>을 발휘함.</li>
</ul>
]]></description>
        </item>
        <item>
            <title><![CDATA[[이미지처리바이블] 5.3장 이미지 분할: Segmentation]]></title>
            <link>https://velog.io/@changh2_00/%EC%9D%B4%EB%AF%B8%EC%A7%80%EC%B2%98%EB%A6%AC%EB%B0%94%EC%9D%B4%EB%B8%94-5.3%EC%9E%A5-%EC%9D%B4%EB%AF%B8%EC%A7%80-%EB%B6%84%ED%95%A0-Segmentation</link>
            <guid>https://velog.io/@changh2_00/%EC%9D%B4%EB%AF%B8%EC%A7%80%EC%B2%98%EB%A6%AC%EB%B0%94%EC%9D%B4%EB%B8%94-5.3%EC%9E%A5-%EC%9D%B4%EB%AF%B8%EC%A7%80-%EB%B6%84%ED%95%A0-Segmentation</guid>
            <pubDate>Sun, 01 Jun 2025 04:44:54 GMT</pubDate>
            <description><![CDATA[<hr>
<p>[이미지 처리 바이블] 교재 5장을 기반으로 작성되었습니다.</p>
<hr>
<h1 id="53-이미지-분할-segmentation">5.3 이미지 분할: Segmentation</h1>
<p>&gt; 이미지를 구성하는 각 픽셀이 어떤 객체에 속하는지 분류하는 과정</p>
<h2 id="531-fcn">5.3.1 FCN</h2>
<h3 id="fcn-등장-이전의-알고리즘">FCN 등장 이전의 알고리즘</h3>
<h4 id="색상-히스토그램">색상 히스토그램</h4>
<p>&gt; 이미지 내 특정 색상의 빈도를 그래픽으로 나타냄
&gt; 그로인해 다양한 정보를 얻을 수 있음</p>
<p><img src="https://velog.velcdn.com/images/changh2_00/post/9e7873c9-fa4e-419e-b9c3-5c4bc936e0e5/image.png" alt=""></p>
<p>특정 색상 c가 이미지 내에서 나타나는 빈도를 측정하는 수식:
<img src="https://velog.velcdn.com/images/changh2_00/post/ee61b5f9-a89f-4eb2-960b-36321249e3ea/image.png" alt="">
H(c) = 색상 c의 히스토그램 빈도,
I(x,y) = 좌표 (x,y)에 위치한 픽셀의 색상 값,
W = 이미지의 너비, H = 이미지의 높이</p>
<hr>
<h4 id="임계-값-처리">임계 값 처리</h4>
<p>&gt; 이미지 내의 픽셀을 특정 기준에 따라 분류하고 단순화하는 과정
&gt; 디지털 이미지를 이진화하여 객체를 감지하거나 분할할 때 사용하는 알고리즘
<img src="https://velog.velcdn.com/images/changh2_00/post/d97d5d4f-ad96-4ced-abc5-39615c12ab47/image.png" alt=""></p>
<hr>
<h4 id="opencv를-활용한-임계-값-처리-실습">OpenCV를 활용한 임계 값 처리 실습</h4>
<pre><code class="language-python">&quot;&quot;&quot; 📌 이미지 불러오기 &quot;&quot;&quot;
import cv2
import matplotlib.pyplot as plt

!wget https://raw.githubusercontent.com/Lilcob/test_colab/main/three%20young%20man.jpg

new_image_color = &#39;/content/three young man.jpg&#39;
new_image_color = cv2.imread(new_image_color)
image_gray = cv2.cvtColor(new_image_color, cv2.COLOR_BGR2GRAY)

&quot;&quot;&quot; 📌 임계 값 설정 &quot;&quot;&quot;
threshold_value = 128
_, thresholded_image = cv2.threshold(image_gray, threshold_value, 255, cv2.THRESH_BINARY)

&quot;&quot;&quot; 📌 결과 이미지 시각화 &quot;&quot;&quot;
plt.figure(figsize=(12, 6))
plt.subplot(1, 2, 1)
plt.imshow(image_gray, cmap=&#39;gray&#39;)
plt.title(&#39;Original Grayscale Image&#39;)
plt.axis(&#39;off&#39;)
plt.subplot(1, 2, 2)
plt.imshow(thresholded_image, cmap=&#39;gray&#39;)
plt.title(&#39;Binary Image after Thresholding&#39;)
plt.axis(&#39;off&#39;)</code></pre>
<p><img src="https://velog.velcdn.com/images/changh2_00/post/3cbcfee5-6b14-47c6-9d88-d0696f611ef9/image.png" alt=""></p>
<hr>
<h3 id="fcn">FCN</h3>
<p>&gt; <strong>Fully Convolutional Networks</strong>
&gt; 전통적인 CNN은 분류에는 탁월하지만, 픽셀 단위의 정밀한 영역분할에는 한계가 있었음
&gt; 이를 극복하기 위해 등장한 것이 FCN
&gt; <strong>Dense 층을 사용하지 않고 모든 층을 Conv 층으로 구성</strong>하여 공간 정보를 보존</p>
<h4 id="fcn-구조">FCN 구조</h4>
<p><img src="https://velog.velcdn.com/images/changh2_00/post/a68f31bf-0464-4288-bc40-5ea7ae43f1d1/image.png" alt=""></p>
<p>&gt; Dense 층의 제거는 네트워크가 전체 이미지를 통합적으로 분석하는 대신, 각각의 픽셀을 독립적으로 평가하고, 각 픽셀이 속한 클래스를 예측할 수 있게 함
&gt; FCN에서는 업샘플링을 통해 감소된 이미지 해상도를 다시 원본 사이즈로 복원함
&gt; 그 뒤 Skip 연결을 사용해 네트워크의 초기층에서 얻은 정보를 후반부의 업샘플링 층과 결합함</p>
<p>[업샘플링 방법론들]</p>
<ol>
<li>최근접 이웃 (nearest neighbor)</li>
<li>선형 보간 (linear interpolation)</li>
<li>전치 합성곱 (transposed convolution)</li>
</ol>
<p><img src="https://velog.velcdn.com/images/changh2_00/post/258d8e2d-b03e-4876-ba21-d6c7c2fcdfa7/image.png" alt=""></p>
<p><strong>히트맵:</strong> 각 픽셀에 대한 확률을 나타내는 맵</p>
<p><img src="https://velog.velcdn.com/images/changh2_00/post/99f81be0-9644-4c87-80b5-9b7bf4301d64/image.png" alt=""></p>
<hr>
<h3 id="텐서플로를-활용한-fcn-실습">텐서플로를 활용한 FCN 실습</h3>
<pre><code class="language-python">&quot;&quot;&quot; 📌 라이브러리 설정 &quot;&quot;&quot;
import tensorflow as tf
from tensorflow import keras
import matplotlib.pyplot as plt
import tensorflow_datasets as tfds
import numpy as np
keras.utils.set_random_seed(27)
tf.random.set_seed(27) 

AUTOTUNE = tf.data.AUTOTUNE   

&quot;&quot;&quot; 📌 하이퍼파라미터 설정 &quot;&quot;&quot;
NUM_CLASSES = 4
INPUT_HEIGHT = 224
INPUT_WIDTH = 224
LEARNING_RATE = 1e-3
WEIGHT_DECAY = 1e-4
EPOCHS = 20
BATCH_SIZE = 32
MIXED_PRECISION = True
SHUFFLE = True
if MIXED_PRECISION:
    policy = keras.mixed_precision.Policy(&quot;mixed_float16&quot;)
    keras.mixed_precision.set_global_policy(policy)

&quot;&quot;&quot; 📌 데이터셋 로드 &quot;&quot;&quot;
(train_ds, valid_ds, test_ds) = tfds.load(  
    &quot;oxford_iiit_pet&quot;,                           
    split=[&quot;train[:85%]&quot;, &quot;train[85%:]&quot;, &quot;test&quot;], 
    batch_size=BATCH_SIZE, 
    shuffle_files=SHUFFLE,  
)

&quot;&quot;&quot; 📌 분할, 사이즈 조정 함수 선언 &quot;&quot;&quot; 
def unpack_resize_data(section):
    image = section[&quot;image&quot;]
    segmentation_mask = section[&quot;segmentation_mask&quot;]

    resize_layer = keras.layers.Resizing(INPUT_HEIGHT, INPUT_WIDTH)

    image = resize_layer(image)
    segmentation_mask = resize_layer(segmentation_mask)

    return image, segmentation_mask

train_ds = train_ds.map(unpack_resize_data, num_parallel_calls=AUTOTUNE)
valid_ds = valid_ds.map(unpack_resize_data, num_parallel_calls=AUTOTUNE) 
test_ds = test_ds.map(unpack_resize_data, num_parallel_calls=AUTOTUNE)



&quot;&quot;&quot; 📌 테스트 세트에서 랜덤하게 이미지 추출, 시각화 &quot;&quot;&quot;
images, masks = next(iter(test_ds))
random_idx = tf.random.uniform([], minval=0, maxval=BATCH_SIZE, dtype=tf.int32)

test_image = images[random_idx].numpy().astype(&quot;float&quot;)
test_mask = masks[random_idx].numpy().astype(&quot;float&quot;)

fig, ax = plt.subplots(nrows=1, ncols=2, figsize=(10, 5))

ax[0].set_title(&quot;Image&quot;)
ax[0].imshow(test_image / 255.0)

ax[1].set_title(&quot;Image with segmentation mask overlay&quot;)
ax[1].imshow(test_image / 255.0)
ax[1].imshow(test_mask,cmap=&quot;inferno&quot;,alpha=0.6,)
plt.show()</code></pre>
<p><img src="https://velog.velcdn.com/images/changh2_00/post/6499530b-5c50-4f50-8b3e-0aa14982fe3f/image.png" alt=""></p>
<pre><code class="language-python">&quot;&quot;&quot; 📌 데이터 전처리, 로드 효율성 높임 &quot;&quot;&quot;
def preprocess_data(image, segmentation_mask): # ①
    image = keras.applications.vgg19.preprocess_input(image)
    return image, segmentation_mask

train_ds = (
    train_ds.map(preprocess_data, num_parallel_calls=AUTOTUNE).shuffle(buffer_size=1024).prefetch(buffer_size=1024)) # ②
valid_ds = (
    valid_ds.map(preprocess_data, num_parallel_calls=AUTOTUNE).shuffle(buffer_size=1024).prefetch(buffer_size=1024))
test_ds = (
    test_ds.map(preprocess_data, num_parallel_calls=AUTOTUNE).shuffle(buffer_size=1024).prefetch(buffer_size=1024)
)</code></pre>
<p><img src="https://velog.velcdn.com/images/changh2_00/post/9a3831aa-ec8e-4245-89bb-a99d4b3ae45e/image.png" alt=""></p>
<pre><code class="language-python">import tensorflow as tf
from tensorflow.keras import mixed_precision

# 이 한 줄로 mixed precision 정책을 float32 전용으로 바꿔버립니다.
mixed_precision.set_global_policy(&#39;float32&#39;)

&quot;&quot;&quot; 📌 모델 작성&quot;&quot;&quot;
input_layer = keras.Input(shape=(INPUT_HEIGHT, INPUT_WIDTH, 3))

vgg_model = keras.applications.vgg19.VGG19(include_top=True, weights=&quot;imagenet&quot;)

fcn_backbone = keras.models.Model(
    inputs=vgg_model.layers[1].input,
    outputs=[
        vgg_model.get_layer(block_name).output
        for block_name in [&quot;block3_pool&quot;, &quot;block4_pool&quot;, &quot;block5_pool&quot;]
    ],
)

# 모델 미세 조정을 위해 백본 네트워크의 가중치 값 고정
fcn_backbone.trainable = False
x = fcn_backbone(input_layer)

# conv층, 드롭아웃층 추가
units = [4096, 4096] 
dense_convs = []

for filter_idx in range(len(units)):
    dense_conv = keras.layers.Conv2D(
        filters=units[filter_idx], kernel_size=(7, 7) if filter_idx == 0 else (1, 
1),strides=(1, 1), activation=&quot;relu&quot;, padding=&quot;same&quot;, use_bias=False, kernel_initializer=tf.constant_initializer(1.0),
    )
    dense_convs.append(dense_conv)
    dropout_layer = keras.layers.Dropout(0.5)
    dense_convs.append(dropout_layer)

dense_convs = keras.Sequential(dense_convs) 
dense_convs.trainable = False 

x[-1] = dense_convs(x[-1])
pool3_output, pool4_output, pool5_output = x

# FCN 모델의 뒷부분은 fcn32s 아키텍처를 구성 (conv층과 업샘플링층을 정의, 연결)
pool5 = keras.layers.Conv2D(filters=NUM_CLASSES, kernel_size=(1, 1), padding=&quot;same&quot;, strides=(1, 1), activation=&quot;relu&quot;,)

fcn32s_conv_layer = keras.layers.Conv2D(filters=NUM_CLASSES, kernel_size=(1, 1), activation=&quot;softmax&quot;, padding=&quot;same&quot;, strides=(1, 1),)

# 최종 예측 맵을 원본 이미지 사이즈로 복원하는 업샘플링 과정 (32배 업샘플링)
fcn32s_upsampling = keras.layers.UpSampling2D(size=(32, 32),data_format=keras.backend.image_data_format(), interpolation=&quot;bilinear&quot;,)

# fcn32s의 최종 출력 생성
final_fcn32s_pool = pool5(pool5_output)
final_fcn32s_output = fcn32s_conv_layer(final_fcn32s_pool)
final_fcn32s_output = fcn32s_upsampling(final_fcn32s_output)
fcn32s_model = keras.Model(inputs=input_layer, outputs=final_fcn32s_output)


# 추가로 FCN의 두가지 변형인 FCN-16s, FCN-8s를 구현, 결합
# FCN-16은 더 세밀한 특성을, FCN-8s는 더욱 정밀한 영역 분할 제공
pool4 = keras.layers.Conv2D(filters=NUM_CLASSES,kernel_size=(1, 1),
padding=&quot;same&quot;,strides=(1, 1),activation=&quot;linear&quot;,kernel_initializer=keras.
initializers.Zeros(),)(pool4_output)

pool5 = keras.layers.UpSampling2D(size=(2, 2),data_format=keras.backend.image_data_format(),interpolation=&quot;bilinear&quot;,)(final_fcn32s_pool)

fcn16s_conv_layer = keras.layers.Conv2D(filters=NUM_CLASSES,kernel_size=(1, 1),
activation=&quot;softmax&quot;,padding=&quot;same&quot;,strides=(1, 1),)

fcn16s_upsample_layer = keras.layers.UpSampling2D(size=(16, 16),data_format=keras.
backend.image_data_format(),interpolation=&quot;bilinear&quot;,)

final_fcn16s_pool = keras.layers.Add()([pool4, pool5])
final_fcn16s_output = fcn16s_conv_layer(final_fcn16s_pool)
final_fcn16s_output = fcn16s_upsample_layer(final_fcn16s_output)

fcn16s_model = keras.models.Model(inputs=input_layer, outputs=final_fcn16s_output)

pool3 = keras.layers.Conv2D( filters=NUM_CLASSES, kernel_size=(1, 1), padding=&quot;same&quot;, strides=(1, 1), activation=&quot;linear&quot;, kernel_initializer=keras.initializers.Zeros(),)(pool3_output)

intermediate_pool_output = keras.layers.UpSampling2D(size=(2, 2),data_format=keras.
backend.image_data_format(),interpolation=&quot;bilinear&quot;,)(final_fcn16s_pool)

fcn8s_conv_layer = keras.layers.Conv2D(filters=NUM_CLASSES,kernel_size=(1, 1),
activation=&quot;softmax&quot;,padding=&quot;same&quot;,strides=(1, 1),)

fcn8s_upsample_layer = keras.layers.UpSampling2D(size=(8, 8),data_format=keras.
backend.image_data_format(),interpolation=&quot;bilinear&quot;,)

final_fcn8s_pool = keras.layers.Add()([pool3, intermediate_pool_output])
final_fcn8s_output = fcn8s_conv_layer(final_fcn8s_pool)
final_fcn8s_output = fcn8s_upsample_layer(final_fcn8s_output)

fcn8s_model = keras.models.Model(inputs=input_layer, outputs=final_fcn8s_output)

# VGG 모델의 마지막 두 층 가중치 추출 및 가중치 재구성, conv층에 적용
weights1 = vgg_model.get_layer(&quot;fc1&quot;).get_weights()[0]
weights2 = vgg_model.get_layer(&quot;fc2&quot;).get_weights()[0]

weights1 = weights1.reshape(7, 7, 512, 4096)
weights2 = weights2.reshape(1, 1, 4096, 4096)

dense_convs.layers[0].set_weights([weights1])
dense_convs.layers[2].set_weights([weights2])

# 앞서 설정한 하이퍼파라미터와, 다양한 버전의 FCN 모델 학습
fcn32s_optimizer = keras.optimizers.AdamW(learning_rate=LEARNING_RATE, weight_decay=WEIGHT_DECAY)

fcn32s_loss = keras.losses.SparseCategoricalCrossentropy()

fcn32s_model.compile(
    optimizer=fcn32s_optimizer,loss=fcn32s_loss,metrics=[keras.metrics.
MeanIoU(num_classes=NUM_CLASSES, sparse_y_pred=False),keras.metrics.
SparseCategoricalAccuracy(),],)
fcn32s_history = fcn32s_model.fit(train_ds, epochs=EPOCHS, validation_data=valid_ds)

fcn16s_optimizer = keras.optimizers.AdamW(learning_rate=LEARNING_RATE, weight_decay=WEIGHT_DECAY)

fcn16s_loss = keras.losses.SparseCategoricalCrossentropy()
fcn16s_model.compile(optimizer=fcn16s_optimizer,loss=fcn16s_loss,metrics=[keras.
metrics.MeanIoU(num_classes=NUM_CLASSES, sparse_y_pred=False),keras.metrics.
SparseCategoricalAccuracy(),],)

fcn16s_history = fcn16s_model.fit(train_ds, epochs=EPOCHS, validation_data=valid_ds)

fcn8s_optimizer = keras.optimizers.AdamW(learning_rate=LEARNING_RATE, weight_decay=WEIGHT_DECAY)

fcn8s_loss = keras.losses.SparseCategoricalCrossentropy()
fcn8s_model.compile(optimizer=fcn8s_optimizer,loss=fcn8s_loss,metrics=[keras.
metrics.MeanIoU(num_classes=NUM_CLASSES, sparse_y_pred=False),keras.metrics.
SparseCategoricalAccuracy(),],)

fcn8s_history = fcn8s_model.fit(train_ds, epochs=EPOCHS, validation_data=valid_ds)</code></pre>
<pre><code>&gt;&gt;&gt; Epoch 1/20
    98/98 ━━━━━━━━━━━━━━━━━━━━ 106s 807ms/step - loss: 1.1481 - mean_io_u_1: 0.1871 - sparse_categorical_accuracy: 0.4662 - val_loss: 0.9564 - val_mean_io_u_1: 0.2737 - val_sparse_categorical_accuracy: 0.5596
    Epoch 2/20
    98/98 ━━━━━━━━━━━━━━━━━━━━ 55s 563ms/step - loss: 0.8225 - mean_io_u_1: 0.3476 - sparse_categorical_accuracy: 0.6662 - val_loss: 0.7170 - val_mean_io_u_1: 0.4347 - val_sparse_categorical_accuracy: 0.7233
    ...(생략)...</code></pre><pre><code class="language-python">&quot;&quot;&quot; 📌 학습 완료된 모델 시각화 &quot;&quot;&quot;
images, masks = next(iter(test_ds))
random_idx = tf.random.uniform([], minval=0, maxval=BATCH_SIZE, dtype=tf.int32)

test_image = images[random_idx].numpy().astype(&quot;float&quot;)
test_mask = masks[random_idx].numpy().astype(&quot;float&quot;)

pred_image = tf.expand_dims(test_image, axis=0)
pred_image = keras.applications.vgg19.preprocess_input(pred_image)

pred_mask_32s = fcn32s_model.predict(pred_image, verbose=0).astype(&quot;float&quot;)
pred_mask_32s = np.argmax(pred_mask_32s, axis=-1)
pred_mask_32s = pred_mask_32s[0, ...]

pred_mask_16s = fcn16s_model.predict(pred_image, verbose=0).astype(&quot;float&quot;)
pred_mask_16s = np.argmax(pred_mask_16s, axis=-1)
pred_mask_16s = pred_mask_16s[0, ...]

pred_mask_8s = fcn8s_model.predict(pred_image, verbose=0).astype(&quot;float&quot;)
pred_mask_8s = np.argmax(pred_mask_8s, axis=-1)
pred_mask_8s = pred_mask_8s[0, ...]

fig, ax = plt.subplots(nrows=2, ncols=3, figsize=(15, 8))

fig.delaxes(ax[0, 2])

ax[0, 0].set_title(&quot;Image&quot;)
ax[0, 0].imshow(test_image / 255.0)

ax[0, 1].set_title(&quot;Image with ground truth overlay&quot;)
ax[0, 1].imshow(test_image / 255.0)
ax[0, 1].imshow( test_mask,cmap=&quot;inferno&quot;,alpha=0.6,)

ax[1, 0].set_title(&quot;Image with FCN-32S mask overlay&quot;)
ax[1, 0].imshow(test_image / 255.0)
ax[1, 0].imshow(pred_mask_32s, cmap=&quot;inferno&quot;, alpha=0.6)

ax[1, 1].set_title(&quot;Image with FCN-16S mask overlay&quot;)
ax[1, 1].imshow(test_image / 255.0)
ax[1, 1].imshow(pred_mask_16s, cmap=&quot;inferno&quot;, alpha=0.6)

ax[1, 2].set_title(&quot;Image with FCN-8S mask overlay&quot;)
ax[1, 2].imshow(test_image / 255.0)
ax[1, 2].imshow(pred_mask_8s, cmap=&quot;inferno&quot;, alpha=0.6)

plt.show()</code></pre>
<p><img src="https://velog.velcdn.com/images/changh2_00/post/dc811eab-819f-4b29-80f3-448fd7597307/image.png" alt=""></p>
<p>&gt;&gt; FCN-32와 비교했을때, FCN-8S가 비교적 더 정확한 결과를 보여줌
&gt;&gt; 영역 분할 과정에서 더 많은 중간 층의 정보를 통합하여 사용하기 때문</p>
<hr>
<h2 id="532-u-net">5.3.2 U-Net</h2>
<p>&gt; 의료 영상 처리와 같은 고해상도 이미지 영역 분할 작업에 특히 뛰어난 성능을 보이는 아키텍처
&gt; 구조가 U 형태를 닮았는데, &quot;수축 경로&quot;와 &quot;확장 경로&quot;의 두 가지 구성 요소를 가짐</p>
<p><img src="https://velog.velcdn.com/images/changh2_00/post/fe1b1b23-865e-4b76-aa9f-098c74ea3ad9/image.png" alt=""></p>
<hr>
<h3 id="수축-경로">수축 경로</h3>
<p>&gt; 네트워크의 초반에 위치하며, 전통적인 CNN 구조와 유사함
&gt; 이미지로부터 중요한 특징을 추출하는 것이 목적
&gt; 스테이지를 나누어 네트워크가 점점 더 깊은 특징을 학습할 수 있도록 설계됨</p>
<p><img src="https://velog.velcdn.com/images/changh2_00/post/4ec623ed-cd1d-4315-90b3-88331cdf8b62/image.png" alt=""></p>
<ol>
<li>초기 단계<pre><code> - 이미지의 가장 기본적인 특징 감지 (간단한 패턴, 에지, 색상 변화)</code></pre></li>
<li>중간 단계<pre><code> - 좀 더 복잡한 요소 감지 (텍스처, 패턴의 조합, 일부 형태나 구조)</code></pre></li>
<li>깊은 단계<pre><code> - 고수준 특징 처리 (객체의 큰 형태, 전체적인 구조)</code></pre></li>
</ol>
<hr>
<h3 id="확장-경로">확장 경로</h3>
<p>&gt; 네트워크의 후반에 위치하며, 수축 경로에서 얻은 특징 맵을 다시 원래 사이즈로 복원하는 역할
&gt; <strong>업샘플링 층</strong>, <strong>합성곱 층</strong>로 구성되고, <strong>스킵 연결</strong>이 중요한 역할을 함</p>
<p><img src="https://velog.velcdn.com/images/changh2_00/post/a104c78b-b719-4042-9d48-d4eb672a8fe6/image.png" alt=""></p>
<ul>
<li><strong>업샘플링 층</strong><pre><code>  - 이미지의 차원을 점차적으로 확대해, 수축 경로에서의 다운샘플링 과정으로 인해 손실된 상세 정보를 복구함
  - 업샘플링은 보통 transpose convolution이나 최근접 이웃 방법을 사용해 수행됨</code></pre></li>
<li><strong>합성곱 층</strong><pre><code>  - 업샘플링된 특징맵이 이후에 거치는 레이어.
  - 업샘플링으로 확대된 이미지에서 부드러운 특징 맵을 생성하고, 세부적인 부분을 세밀하게 다듬는 역할</code></pre></li>
<li><strong>스킵 연결</strong><pre><code>  - 수축 경로의 각 스테이지에서 얻은 특징 맵을 확장 경로의 해당 스테이지와 결합함
  - 확장 경로에서 생성된 특징 맵에, 수축 경로에서 추출된 상세한 위치 정보를 추가해주어 모델이 이미지의 구조를 더 잘 이해할 수 있도록 함</code></pre></li>
</ul>
<p><img src="https://velog.velcdn.com/images/changh2_00/post/239f4b46-c0d9-4a4f-9e86-90682dca82ba/image.png" alt=""></p>
<ol>
<li>초기 단계<pre><code> - 수축 겅로의 마지막 합성곱 층의 출력을 받아 업샘플링을 시작함.
 - 업샘플링된 특징 맵은 수축 경로의 마지막 합성곱 층의 출력과 스킵 연결을 통해 결합됨</code></pre></li>
<li>중간 단계<pre><code> - 업샘플링과 스킵 연결을 계속해서 이어감. 각 스테이지에 대응되는 출력과 결합, 스킵연결 됨</code></pre></li>
<li>최종 단계<pre><code> - 최종 업샘플링과 결합을 이루어 이미지를 원래의 해상도로 복원하며, 수축 경로의 첫번째 스테이지에서 얻은 특징 맵과 결합됨</code></pre></li>
</ol>
<hr>
<h3 id="텐서플로를-활용한-u-net-실습">텐서플로를 활용한 U-Net 실습</h3>
<pre><code class="language-python">&quot;&quot;&quot; 📌 라이브러리 준비 &quot;&quot;&quot;
!pip install git+https://github.com/tensorflow/examples.git

import tensorflow as tf
import tensorflow_datasets as tfds
import matplotlib.pyplot as plt
from tensorflow_examples.models.pix2pix import pix2pix
from IPython.display import clear_output

&quot;&quot;&quot; 📌 데이터셋 준비 &quot;&quot;&quot;
dataset, info = tfds.load(&#39;oxford_iiit_pet&#39;, with_info=True)

def normalize(input_image, input_mask):
    input_image = tf.cast(input_image, tf.float32) / 255.0
    input_mask -= 1
    return input_image, input_mask

def load_image(datapoint):
    input_image = tf.image.resize(datapoint[&#39;image&#39;], (128, 128))
    input_mask = tf.image.resize(
        datapoint[&#39;segmentation_mask&#39;],
        (128, 128),
        method = tf.image.ResizeMethod.NEAREST_NEIGHBOR,)
    input_image, input_mask = normalize(input_image, input_mask)

    return input_image, input_mask

&quot;&quot;&quot; 📌 데이터 로드 &quot;&quot;&quot;
TRAIN_LENGTH = info.splits[&#39;train&#39;].num_examples
BATCH_SIZE = 64
BUFFER_SIZE = 1000
STEPS_PER_EPOCH = TRAIN_LENGTH // BATCH_SIZE

train_images = dataset[&#39;train&#39;].map(load_image, num_parallel_calls=tf.data.AUTOTUNE)
test_images = dataset[&#39;test&#39;].map(load_image, num_parallel_calls=tf.data.AUTOTUNE)

&quot;&quot;&quot; 📌 데이터 증강 &quot;&quot;&quot;
class Augment(tf.keras.layers.Layer):
    def __init__(self, seed=42):
        super().__init__()
        self.augment_inputs = tf.keras.layers.RandomFlip(mode=&quot;horizontal&quot;, seed=seed)
        self.augment_labels = tf.keras.layers.RandomFlip(mode=&quot;horizontal&quot;, seed=seed)

    def call(self, inputs, labels):
        inputs = self.augment_inputs(inputs)
        labels = self.augment_labels(labels)
        return inputs, labels

train_batches = (
    train_images
    .cache()
    .shuffle(BUFFER_SIZE)
    .batch(BATCH_SIZE)
    .repeat()
    .map(Augment())
    .prefetch(buffer_size=tf.data.AUTOTUNE))

test_batches = test_images.batch(BATCH_SIZE)

&quot;&quot;&quot; 📌 학습 데이터 시각화 &quot;&quot;&quot;
def display(display_list):
    plt.figure(figsize=(15, 15))

    title = [&#39;Input Image&#39;, &#39;True Mask&#39;, &#39;Predicted Mask&#39;]

    for i in range(len(display_list)):
        plt.subplot(1, len(display_list), i+1)
        plt.title(title[i])
        plt.imshow(tf.keras.utils.array_to_img(display_list[i]))
        plt.axis(&#39;off&#39;)
    plt.show()

for images, masks in train_batches.take(2):
    sample_image, sample_mask = images[0], masks[0]
    display([sample_image, sample_mask])</code></pre>
<p><img src="https://velog.velcdn.com/images/changh2_00/post/34708435-b4d5-4284-b016-bc5e7eac75bc/image.png" alt=""></p>
<p><img src="https://velog.velcdn.com/images/changh2_00/post/eab6f6d2-256d-4bfb-b894-d6fe8bac41b7/image.png" alt=""></p>
<pre><code class="language-python">&quot;&quot;&quot; 📌 모델 정의 &quot;&quot;&quot;
&quot;&quot;&quot; 📌 백본 네트워크 호출 (MobileNetV2) &quot;&quot;&quot;
base_model = tf.keras.applications.MobileNetV2(input_shape=[128, 128, 3], include_top=False) 

layer_names = [
    &#39;block_1_expand_relu&#39;,   # 64x64
    &#39;block_3_expand_relu&#39;,   # 32x32
    &#39;block_6_expand_relu&#39;,   # 16x16
    &#39;block_13_expand_relu&#39;,  # 8x8
    &#39;block_16_project&#39;,      # 4x4
]
base_model_outputs = [base_model.get_layer(name).output for name in layer_names]

down_stack = tf.keras.Model(inputs=base_model.input, outputs=base_model_outputs)

down_stack.trainable = False

&quot;&quot;&quot; 📌 확장경로 (U-Net 모델 완성) &quot;&quot;&quot;
up_stack = [
    pix2pix.upsample(512, 3),  # 4x4 -&gt; 8x8
    pix2pix.upsample(256, 3),  # 8x8 -&gt; 16x16
    pix2pix.upsample(128, 3),  # 16x16 -&gt; 32x32
    pix2pix.upsample(64, 3),   # 32x32 -&gt; 64x64
] 

def UNET_model(output_channels:int):
    inputs = tf.keras.layers.Input(shape=[128, 128, 3])

    skips = down_stack(inputs)
    x = skips[-1] # ③
    skips = reversed(skips[:-1])

    for up, skip in zip(up_stack, skips):
        x = up(x)
        concat = tf.keras.layers.Concatenate()
        x = concat([x, skip])

    last = tf.keras.layers.Conv2DTranspose(
        filters=output_channels, kernel_size=3, strides=2, padding=&#39;same&#39;) # ④  
        # 64x64 -&gt; 128x128
    x = last(x)

    return tf.keras.Model(inputs=inputs, outputs=x)

&quot;&quot;&quot; 📌 컴파일, 모델 시각화 &quot;&quot;&quot;
OUTPUT_CLASSES = 3

model = UNET_model(output_channels=OUTPUT_CLASSES)
model.compile(optimizer=&#39;adam&#39;,
              loss=tf.keras.losses.SparseCategoricalCrossentropy(from_logits=True),
metrics=[&#39;accuracy&#39;])
tf.keras.utils.plot_model(model, show_shapes=True)</code></pre>
<p><img src="https://velog.velcdn.com/images/changh2_00/post/2c01fcc6-1667-49d0-a588-050a5f1b21c8/image.png" alt=""></p>
<pre><code class="language-python">&quot;&quot;&quot; 📌 모델 예측 결과 시각화 &quot;&quot;&quot;
def create_mask(pred_mask):
    pred_mask = tf.math.argmax(pred_mask, axis=-1)
    pred_mask = pred_mask[..., tf.newaxis]
    return pred_mask[0]

def show_predictions(dataset=None, num=1):
    if dataset:
        for image, mask in dataset.take(num):
            pred_mask = model.predict(image)
            display([image[0], mask[0], create_mask(pred_mask)])
    else:
        display([sample_image, sample_mask,
            create_mask(model.predict(sample_image[tf.newaxis, ...]))])

class DisplayCallback(tf.keras.callbacks.Callback):
    def on_epoch_end(self, epoch, logs=None):
        clear_output(wait=True)
        show_predictions()
        print (&#39;\nSample Prediction after epoch {}\n&#39;.format(epoch+1))

EPOCHS = 20
VAL_SUBSPLITS = 5
VALIDATION_STEPS = info.splits[&#39;test&#39;].num_examples//BATCH_SIZE//VAL_SUBSPLITS

model_history = model.fit(train_batches, epochs=EPOCHS,
                          steps_per_epoch=STEPS_PER_EPOCH,
                          validation_steps=VALIDATION_STEPS,
                          validation_data=test_batches,
                          callbacks=[DisplayCallback()])</code></pre>
<p><img src="https://velog.velcdn.com/images/changh2_00/post/d87304b6-634c-4235-b0f3-9b13e7844d5f/image.png" alt=""></p>
]]></description>
        </item>
        <item>
            <title><![CDATA[[이미지처리바이블] 5.1장 객체 탐지: two-stage detector]]></title>
            <link>https://velog.io/@changh2_00/%EC%9D%B4%EB%AF%B8%EC%A7%80%EC%B2%98%EB%A6%AC%EB%B0%94%EC%9D%B4%EB%B8%94-5%EC%9E%A5-%EA%B0%9D%EC%B2%B4-%ED%83%90%EC%A7%80</link>
            <guid>https://velog.io/@changh2_00/%EC%9D%B4%EB%AF%B8%EC%A7%80%EC%B2%98%EB%A6%AC%EB%B0%94%EC%9D%B4%EB%B8%94-5%EC%9E%A5-%EA%B0%9D%EC%B2%B4-%ED%83%90%EC%A7%80</guid>
            <pubDate>Thu, 22 May 2025 09:25:42 GMT</pubDate>
            <description><![CDATA[<hr>
<p>[이미지 처리 바이블] 교재 5장을 기반으로 작성되었습니다.</p>
<hr>
<h1 id="51-two-stage-detector">5.1 two-stage detector</h1>
<p><strong>객체 탐지:</strong> 이미지 내의 여러 객체를 정확하게 식별하고, 위치를 찾는 작업
&gt;&gt; 이를 위한 방법 중 하나가 <strong>two-stage detector</strong> (두 개의 주요 단계로 구성됨)</p>
<p><img src="https://velog.velcdn.com/images/changh2_00/post/49a52263-9cae-4d37-88cb-7b34a3c88812/image.png" alt=""></p>
<ol>
<li><strong>영역 제안</strong><pre><code> - 이미지 내에서 객체가 존재할 가능성이 있는 영역들을 추출함 </code></pre></li>
<li><strong>분류 + 바운딩 박스 회귀</strong><pre><code> - 위의 단계에서 추출된 영역들 내의 객체를 분류하고, 
 &amp;nbsp 객체의 위치를 나타내는 바운딩 박스를 조절함</code></pre></li>
</ol>
<p>&gt;&gt; one-stage detector 보다 정확도 ⬆,  처리 속도 ⬇</p>
<hr>
<h2 id="511-r-cnn">5.1.1 R-CNN</h2>
<p>&gt; 객체 탐지 분야에서 큰 변화를 가져온 알고리즘 중 하나
&gt; 기존의 방법들은 슬라이딩 윈도우 접근법으로 모든 영역을 독립적으로 평가했으나, 
&amp;nbsp&amp;nbsp&amp;nbsp R-CNN은 이미지의 관심 영역을 먼저 식별하고, 이를 CNN을 통해 분류하도록 설계함</p>
<p><img src="https://velog.velcdn.com/images/changh2_00/post/5c74b97a-eb68-47fc-9522-8e8c8ddf0bc8/image.png" alt=""></p>
<h3 id="영역-제안">영역 제안</h3>
<p>&gt; 기존의 연산량이 많은 슬라이딩 윈도우 방식을 해결하는 방법</p>
<p><img src="https://velog.velcdn.com/images/changh2_00/post/070e379b-9207-4887-8e2b-af62a14c4519/image.png" alt=""></p>
<p>R-CNN의 영역 제안 절차는 이와 같음</p>
<h4 id="1-초과-분할">1. 초과 분할</h4>
<p>&amp;nbsp&amp;nbsp&amp;nbsp - 많은 수의 지역별 segment(분할)을 생성함</p>
<p><img src="https://velog.velcdn.com/images/changh2_00/post/cf218d91-0b55-4427-a18f-86041b7edd08/image.png" alt=""></p>
<h4 id="2-계층적-그룹화">2. 계층적 그룹화</h4>
<p>&amp;nbsp&amp;nbsp&amp;nbsp - 위에서 생성된 segment들을 그룹화함</p>
<p><img src="https://velog.velcdn.com/images/changh2_00/post/2b87f0a9-0a16-4103-84ee-4f6cd86cc88c/image.png" alt=""></p>
<h4 id="3-전략의-다양화">3. 전략의 다양화</h4>
<p>&amp;nbsp&amp;nbsp&amp;nbsp - segment를 병합할때 여러 전략을 동시에 사용함 (색상 유사성, 질감 패턴 등)</p>
<h4 id="4-필터링-제안">4. 필터링 제안</h4>
<p>&amp;nbsp&amp;nbsp&amp;nbsp - 사이즈 기반 필터링: 너무 작거나 큰 영역 제안은 제외됨
&amp;nbsp&amp;nbsp&amp;nbsp&amp;nbsp - 다양성 최대화: 서로 비슷한 영역 제안 중 하나만 선택함
&amp;nbsp&amp;nbsp&amp;nbsp&amp;nbsp - 제안 수 제한: 최종적으로 약 2,000개의 영역 제안만을 선택함
&amp;nbsp&amp;nbsp&amp;nbsp&amp;nbsp &gt;&gt; 위의 필터링을 거쳐 가장 유용한 제안만을 선택하는 단계
<img src="https://velog.velcdn.com/images/changh2_00/post/909c94ac-4a14-458f-a025-063eb2457d5b/image.png" alt=""></p>
<hr>
<h3 id="cnn을-활용한-특징-추출">CNN을 활용한 특징 추출</h3>
<p>&gt; R-CNN은 선택적 검색으로 추출된 영역 제안들을 AlexNet 구조의 CNN을 통해 분석함
&gt; Conv 층 - 이미지의 지역적인 특징 학습
&gt; FCN 층 - 이미지의 전역적인 정보 학습
<img src="https://velog.velcdn.com/images/changh2_00/post/d2ee4bf8-fbcb-4a81-9b24-e0ef72ddecdb/image.png" alt=""></p>
<hr>
<h3 id="svm을-활용한-분류">SVM을 활용한 분류</h3>
<p>&gt; R-CNN은 객체 탐지의 마지막 단계로 <strong>S</strong>upport <strong>V</strong>ector <strong>M</strong>achine 분류기를 사용해 
&amp;nbsp&amp;nbsp&amp;nbsp 각각의 영역 제안을 특정 객체 클래스로 분류함</p>
<p><img src="https://velog.velcdn.com/images/changh2_00/post/c1e58091-e457-4585-b21c-2992554f85b1/image.png" alt=""></p>
<p><strong>SVM</strong>은 마진(margin)을 최대한으로 하며 공간을 둘로 나누는 초평면을 찾는 것을 목표로 함.
&gt;&gt; 이로 인해 일반적인 분류기보다 과적합을 방지할 수 있음</p>
<hr>
<h3 id="바운딩-박스-회귀">바운딩 박스 회귀</h3>
<p>R-CNN에서 바운딩 박스 회귀의 타깃은 4개의 값을 가지며, 각각은 GT(Ground Truth, 바운딩 박스)와 영역 제안 간의 상대적인 변화를 나타냄</p>
<p>아래의 수식으로 타깃값을 계산함</p>
<p>$\quad \quad \quad \quad \quad \quad P=(P_x, P_y, P_w, P_h)$ &amp;nbsp&amp;nbsp :&amp;nbsp 영역 제안의 중심 좌표, 너비, 높이
$\quad \quad \quad \quad \quad \quad G=(G_x, G_y, G_w, G_h)$  &amp;nbsp:&amp;nbsp GT의 중심 좌표, 너비, 높이
<br>
$$
t_x = \frac{G_x - P_x}{P_w}, \quad 
t_y = \frac{G_y - P_y}{P_h}
$$</p>
<p>$$
t_w = \log\left(\frac{G_w}{P_w}\right), \quad 
t_h = \log\left(\frac{G_h}{P_h}\right)
$$</p>
<p>$\quad \quad \quad \quad \quad \quad t_x, t_y, t_w, t_h$ &amp;nbsp&amp;nbsp :&amp;nbsp 바운딩 박스 회귀를 통해 얻고자 하는 보정된 값</p>
<hr>
<h3 id="nms와-iou">NMS와 IoU</h3>
<p>바운딩 박스 회귀로 여러 개의 박스가 동일한 객체에 중복되어 생성될 수 있음
&gt; 이를 처리하기 위해서 사용되는 것이 <strong>NMS</strong>(Non-Maximum Suppression)</p>
<p>[NMS의 작동 방식]</p>
<ol>
<li>모든 바운딩 박스를 객체 점수(신뢰도)에 따라 내림차순으로 정렬</li>
<li>사용자가 설정한 임계치 이상의 점수를 가진 바운딩 박스를 선택</li>
<li>선택된 경계 상자와 중복되는 다른 모든 경계 상자를 제거</li>
</ol>
<p>이때 중복되는 정도를 나타내는 점수는 <strong>IOU</strong>(Intersection over Union) 값을 기준으로 함
보통 IoU = 0.5 를 넘어가는 경우 겹치는 박스로 설정 </p>
<p><img src="https://velog.velcdn.com/images/changh2_00/post/11dd4758-bb80-4bd1-bb7a-19c1a98c9f67/image.png" alt=""></p>
<hr>
<h2 id="512-fast-r-cnn과-faster-r-cnn">5.1.2 Fast R-CNN과 Faster R-CNN</h2>
<p>&gt; R-CNN에서 연구를 거듭하며 이를 계승한 Fast R-CNN, Faster R-CNN이 등장함</p>
<h3 id="fast-r-cnn의-주요-개선점">Fast R-CNN의 주요 개선점</h3>
<p>가장 두드러지는 개선점 중 하나는 <strong>RoI</strong>(Region of Interest) <strong>풀링</strong></p>
<h4 id="roi-풀링">RoI 풀링</h4>
<p><img src="https://velog.velcdn.com/images/changh2_00/post/c82abd14-2524-4b9a-a9f9-65f789e24d22/image.png" alt=""></p>
<p><strong>R-CNN</strong>은 각각의 제안된 영역에 대해 독립적으로 CNN을 통과시키기 때문에 처리 시간이 오래 걸렸음</p>
<p><img src="https://velog.velcdn.com/images/changh2_00/post/3e159b73-92c1-470a-82ba-a4a028f0af31/image.png" alt=""></p>
<p><img src="https://velog.velcdn.com/images/changh2_00/post/71ed6d87-4cdf-4292-b709-24c8113afadb/image.png" alt=""></p>
<p><strong>Fast R-CNN</strong>은 모든 제안 영역을 개별적으로 모델에 넣지 않고 
먼저 CNN을 통해 특징 맵을 생성한 후 , 특징 맵에서 제안받은 영역을 따로 분리하여 분류함
하지만 출력되는 특징맵이 입력 이미지에 비해 축소되는 문제가 있었고,
그로 인해 탐지하고자 하는 관심 영역(RoI) 또한 그만큼 작아지게 됐음
&gt;&gt; 이 때문에 RoI 좌표가 정수형이 아닌 실수형으로 표현되는 문제가 생김</p>
<p><img src="https://velog.velcdn.com/images/changh2_00/post/06d30094-3093-4bda-a783-e079dfe21e5e/image.png" alt=""></p>
<p>이러한 문제를 <strong>RoI 풀링</strong>을 통해 해결함</p>
<p><img src="https://velog.velcdn.com/images/changh2_00/post/9fc7de4b-1c2d-4142-9689-935bff8b66a1/image.png" alt=""></p>
<hr>
<h3 id="faster-r-cnn의-주요-개선점">Faster R-CNN의 주요 개선점</h3>
<h4 id="영역-제안-방식의-변화-영역-제안-네트워크rpn">영역 제안 방식의 변화: 영역 제안 네트워크(RPN)</h4>
<p>Faster R-CNN에서는 영역 제안 방식을 선택적 영역 알고리즘을 사용하지 않고,
영역 제안 네트워크, 즉 <strong>RPN</strong>(Region Proposal Network)를 사용함</p>
<h4 id="앵커-박스">앵커 박스</h4>
<p>Fast R-CNN 처럼 CNN의 특징맵에서 영역 제안을 진행하는데,
여기서 Faster R-CNN의 RPN은 여러 사이즈와 비율을 가진 사각형 박스인 <strong>&quot;앵커 박스&quot;</strong>를 사용해 이미지의 모든 위치에 대해 정의함</p>
<p><img src="https://velog.velcdn.com/images/changh2_00/post/2d16a0a8-1e47-4239-b82e-325007b1d741/image.png" alt=""></p>
<p>[RPN에서 앵커 박스의 작업]</p>
<p><strong>1. 이진 분류(Objectness 점수)</strong>
&amp;nbsp&amp;nbsp&amp;nbsp - Objectness 점수는 특정 앵커가 객체를 포함할 확률을 의미
&amp;nbsp&amp;nbsp&amp;nbsp - RPN에서는 각 앵커가 배경(Negative)인지 객체(Positive)인지 구분하기 위해 이 점수를 사용</p>
<p><strong>2. 바운딩 박스 회귀</strong>
&amp;nbsp&amp;nbsp&amp;nbsp - 앵커박스의 좌표와 실제 객체의 좌표의 오차값인 오프셋(offset)을 사용해 바운딩 박스 조정</p>
<hr>
]]></description>
        </item>
        <item>
            <title><![CDATA[[이미지처리바이블] 4.3장 비전 트랜스포머]]></title>
            <link>https://velog.io/@changh2_00/%EC%9D%B4%EB%AF%B8%EC%A7%80%EC%B2%98%EB%A6%AC%EB%B0%94%EC%9D%B4%EB%B8%94-4.3%EC%9E%A5-%EB%B9%84%EC%A0%84-%ED%8A%B8%EB%9E%9C%EC%8A%A4%ED%8F%AC%EB%A8%B8</link>
            <guid>https://velog.io/@changh2_00/%EC%9D%B4%EB%AF%B8%EC%A7%80%EC%B2%98%EB%A6%AC%EB%B0%94%EC%9D%B4%EB%B8%94-4.3%EC%9E%A5-%EB%B9%84%EC%A0%84-%ED%8A%B8%EB%9E%9C%EC%8A%A4%ED%8F%AC%EB%A8%B8</guid>
            <pubDate>Fri, 09 May 2025 11:42:13 GMT</pubDate>
            <description><![CDATA[<hr>
<p>[이미지 처리 바이블] 교재 4장을 기반으로 작성되었습니다.</p>
<hr>
<h1 id="43-비전-트랜스포머">4.3 비전 트랜스포머</h1>
<h2 id="431-트랜스포머">4.3.1 트랜스포머</h2>
<p><strong>Transformer</strong>란 딥러닝 모델 중 하나로, 자연어 처리에서 좋은 성능을 내고 있는 모델 구조임
이 모델을 이미지 처리에 적용하기 위해 만들어진 모델이 <strong>Vision Transformer</strong></p>
<p>Google Brain 팀이 2017년에 발표한 트랜스포머 논문에 대한 설명은 아래 링크 참조
<a href = "https://velog.io/@changh2_00/%EB%85%BC%EB%AC%B8%EB%A6%AC%EB%B7%B0-Attention-is-All-you-Need">[논문리뷰] Attention is All you Need </a></p>
<hr>
<h3 id="어텐션">어텐션</h3>
<p>Attnetion 매커니즘은 트랜스포머에서 가장 중요한 개념 중 하나
&gt; 모델이 입력 데이터의 <strong>중요한 부분에 집중</strong>하도록 함 (어떻게?)</p>
<p><img src="https://velog.velcdn.com/images/changh2_00/post/a3596f75-ba18-4287-b3c4-798ce269e1ce/image.png" alt=""></p>
<p>CNN은 하나의 작은 윈도우를 전체 이미지에서 옮겨가며 데이터를 읽기 때문에, <strong>공간적 근접성의 귀납적 편향</strong>을 가지고 있음
&gt; CNN은 <strong>지역적</strong> 패턴과 텍스처를 효율적으로 인식하도록 설계됨</p>
<p>ViT는 이미지를 여러 작은 조각(패치)로 분할하고, 이 패치들 간의 관계를 직접 모델링함.
&gt; 이미지 전체의 <strong>전역적</strong> 컨텍스트 이해에 유리</p>
<p>&gt;&gt; 둘은 서로 다른 유형의 문제에 더 적할 수 있음</p>
<hr>
<h3 id="셀프-어텐션">셀프 어텐션</h3>
<p>셀프 어텐션은 주어진 입력에 대해 내부적으로 서로 다른 위치들 간에 어떤 관계가 있는지를 학습
기존의 seq2seq 모델은 입력 데이터의 순서, 위치에 의존하기 때문에, 문장이 길어질 수록 초반 입력 정보가 사라지는 문제(vanishing gradient problem)이 있었음</p>
<p>셀프 어텐션은 &#39;어텐션&#39; 방식을 사용해 위 문제를 해결함.
이는 seq2seq처럼 순차적으로 처리할 필요없이, 입력 데이터를 병렬 구조로 처리해 문장이 길어져 정보들이 멀어져도 연관성을 이끌어낼 수 있음 </p>
<p><img src="https://velog.velcdn.com/images/changh2_00/post/cfdf107c-bf13-462b-a49e-ac95ac369711/image.png" alt=""></p>
<p>$$
Attention(Q,K,V) = softmax(\frac {QK^T}{\sqrt d_k} )V
$$
<br></p>
<p>$$
MultiHead(Q,K,V) = Concat(head_1, ... , head_h)W^O$$
$$
where; head_i = Attention(QW_i^Q, KW_i^K, VW_i^V)
$$</p>
<blockquote>
<p><strong>$Q, K, V$ 는 어디서 오는가?</strong></p>
<p>$$
Q=XW_Q,\quad K=XW_K,\quad V=XW_V
$$</p>
</blockquote>
<p>입력인 단어(토큰) 시퀀스는 임베딩 레이어를 거치면 임베딩 벡터로 변환되고, 
Positional Encoding이 더해지면 <strong>입력 벡터 $X$</strong>로 전환됨.</p>
<blockquote>
</blockquote>
<p>이 입력 벡터에 각각의 가중치 행렬  $W_Q, W_K, W_V$ 를 곱하면 그제서야 $Q, K, V$ 가 됨!
(이 작업은 Multi-Head Attention 하단의 Linear 블록에서 이루어짐)</p>
<blockquote>
</blockquote>
<p>$W_Q, W_K, W_V$는 Transformer 모델이 처음 시작될 때 무작위로 초기화 되고, 훈련 과정을 거치면서 자동으로 업데이트 됨</p>
<blockquote>
<p>$Q, K, V$ <strong>각각의 역할</strong></p>
</blockquote>
<ul>
<li>$Q$ = Query: 현재 단어가 &quot;어떤 단어를 참고해야 하는지&quot; 결정하는 역할</li>
<li>$K$ = Key: 각 단어가 자기 자신을 설명하는 정보</li>
<li>$V$ = Value: 최종적으로 참고할 정보<blockquote>
</blockquote>
즉, Query와 Key를 비교해서 &quot;어떤 단어를 참고해야 하는지&quot;를 결정하고
실제 정보는 Value에서 가져오는 것!</li>
</ul>
<hr>
<h3 id="트랜스포머-모델-구조">트랜스포머 모델 구조</h3>
<p><img src="https://velog.velcdn.com/images/changh2_00/post/a281429b-47e9-413c-a783-d731f6da2e4e/image.png" alt=""></p>
<hr>
<h4 id="다중-헤드-어텐션">다중 헤드 어텐션</h4>
<p>Attention을 여러 번 병렬로 수행하는 것을 의미함
&gt; 입력 데이터의 서로 다른 특성을 동시에 모델링할 수 있게함</p>
<p><img src="https://velog.velcdn.com/images/changh2_00/post/dcc5067d-8be7-48cd-ba37-072a4b74f24f/image.png" alt=""></p>
<ol>
<li><strong>선형 변환:</strong>  입력 $Q, K, V$는 각각의 가중치 행렬 $W_Q, W_K, W_V$ 사용해 h번 변환됨 </li>
<li><strong>병렬 어텐션 계산:</strong> 변환된 $Q, K, V$에 대해 독립적인 어텐션을 수행함</li>
<li><strong>결합 및 최종 선형 변환:</strong> 각각의 어텐션의 출력 결합, 추가적인 가중치 행렬 $W_o$를 사용해 최종 결과 생성</li>
</ol>
<p>&gt; 여러개의 헤드(h)를 통해 입력 시퀀스의 다양한 부분에서 더 풍부한 정보를 추출하고 모델의 표현력을 향상시킴
&gt; 서로 다른 위치의 정보를 동시에 고려해 문맥에 대한 이해를 향상시킴</p>
<hr>
<h4 id="포지셔널-인코딩">포지셔널 인코딩</h4>
<p><strong>Positional Encoding</strong>은 입력의 토큰이 문장에서 갖는 위치 정보를 모델이 이해하기 위해 중요한 기법</p>
<ol>
<li>선형 포지셔널 인코딩</li>
<li>정규화된 선형 포지셔널 인코딩</li>
<li><strong>sin, cos 함수의 도입</strong><pre><code> - 주기성: 주기적인 패턴을 가지고 있어, 어느 시퀀스를 처리하던 일관된 방식으로 인코딩 가능
 - 상대적 위치 정보: 각 위치 간의 상대적 차이를 유지할 수 있음
 - 차원 독립성: 다양한 주파수의 함수를 사용함으로써 모델이 다른 차원에서 위치 정보를 독립적으로 인코딩 가능</code></pre><br>

</li>
</ol>
<h4 id="포지셔널-인코딩의-원리">포지셔널 인코딩의 원리</h4>
<p>$$
PE_{(pos,,2i)} = \sin\left( \frac{pos}{10000^{\frac{2i}{d_{\text{model}}}}} \right)
$$</p>
<p>$$
PE_{(pos,,2i+1)} = \cos\left( \frac{pos}{10000^{\frac{2i}{d_{\text{model}}}}} \right)
$$</p>
<ul>
<li>$pos$: 단어의 위치</li>
<li>$i$: 차원의 인덱스</li>
<li>$d_model$: 모델의 임베딩 차원<br>

</li>
</ul>
<h4 id="포지셔널-인코딩의-특징">포지셔널 인코딩의 특징</h4>
<ul>
<li><strong>순서 정보의 제공:</strong> 문장 내에서 단어의 위치 정보를 고려할 수 있게 함</li>
<li><strong>학습이 필요 없는 고정된 인코딩:</strong> 학습 파라미터가 아닌, 미리 정의된 함수에 의해 생성되므로 모델에 부담이 덜함</li>
<li><strong>길이 제한:</strong> 미리 정의된 최대 시퀀시 길이에 제한됨. 이를 위한 연구는 진행중</li>
</ul>
<hr>
<h2 id="432-비전-트랜스포머">4.3.2 비전 트랜스포머</h2>
<h3 id="vit">ViT</h3>
<p><img src="https://velog.velcdn.com/images/changh2_00/post/9d4bcc85-5fcf-4086-879b-21f5000ed9b3/image.png" alt=""></p>
<ol>
<li>자연어의 문장이 아닌 이미지를 처리하기 위해, 입력 이미지를 NxN의 작은 정사각형 조각(patch)으로 쪼갬</li>
<li>각 NxN의 patch를 flatten하여 1차원 벡터로 변환</li>
<li>이미지의 위치 정보를 모델에게 제공하기 위해 Position Embedding하고, 분류 작업을 위해 입력 스퀀스의 맨 앞에 특별한 [CLS] 토큰을 추가함. 이 토큰은 모델을 통과하며 전체 이미지에 대한 정보를 집약함</li>
<li>임베딩 된 patch는 ViT의 트랜스포머 인코더 블록의 입력으로 들어가 Multi-Head Attention을 통해 서로 다른 이미지 patch에서의 관련성을 학습함 (기존 Transformer와 정규화 위치 빼곤 차이 X)</li>
<li>인코더를 통과한 [CLS] 토큰의 출력은 이미지 전체를 대표하는 고차원 특징 벡터가 됨. 이 벡터가 MLP Head의 입력으로 들어가 모델의 최종 출력을 생성함</li>
</ol>
<hr>
<h3 id="vit-모델-구현-실습">ViT 모델 구현 실습</h3>
<pre><code class="language-python">import numpy as np
import tensorflow as tf
import matplotlib.pyplot as plt
from tensorflow.keras import layers

&quot;&quot;&quot; 📌 cifar100 데이터셋 로드 &quot;&quot;&quot;
(train_x, train_y), (test_x, test_y) = tf.keras.datasets.cifar100.load_data()
num_classes = 100

plt.figure(figsize=(10, 2))
for i in range(5):
    plt.subplot(1, 5, i + 1)
    plt.imshow(train_x[i])
    plt.title(f&quot;Label: {train_y[i][0]}&quot;)
    plt.axis(&#39;off&#39;)
plt.show()</code></pre>
<p><img src="https://velog.velcdn.com/images/changh2_00/post/5a877f3c-317a-4730-a0d3-b96ecb1333a7/image.png" alt=""></p>
<pre><code class="language-python">&quot;&quot;&quot; 📌 정규화 &quot;&quot;&quot;
print(f&quot;스케일링 전 픽셀의 최대 값과 최소 값:{train_x.min()} ~ {train_x.max()}&quot;)
train_x = train_x/255.
test_x = test_x/255.
print(f&quot;스케일링 후 픽셀의 최대 값과 최소 값:{train_x.min()} ~ {train_x.max()}&quot;)</code></pre>
<pre><code>&gt;&gt;&gt; 스케일링 전 픽셀의 최대 값과 최소 값:0 ~ 255
    스케일링 후 픽셀의 최대 값과 최소 값:0.0 ~ 1.0</code></pre><pre><code class="language-python">
&quot;&quot;&quot; 📌 모델 파라미터 정의 &quot;&quot;&quot;
input_shape = (32, 32, 3)
batch_size = 64           # 트랜스포머는 대규모 병렬 처리가 용이하므로 배치 사이즈를 더 키워보는 것이 좋음

image_size = 72           # 패치 분할을 위해 입력 이미지를 재조정할 사이즈
patch_size = 6
num_patches = (image_size // patch_size) ** 2   # 이미지에서 분할되는 patch의 총 개수

learning_rate = 1e-3
weight_decay = 1e-4       # 가중치 감소 규제 기법의 파라미터
epochs = 30

transformer_layers = 4  # 트랜스포머 내부의 인코더 레이어 수
projection_dim = 64       # 패치를 투영할 때의 차원 수
num_heads = 4            # Multi-Head Attention에서의 헤드 수 (h)
transformer_units = [projection_dim * 2, projection_dim]   # 트랜스포머의 FFN에서 사용되는 유닛의 수
mlp_head_units = [2048, 1024]   # 최종 부분의 MLP의 유닛 수</code></pre>
<pre><code class="language-python">&quot;&quot;&quot; 📌 이미지 patch를 만들어주는 클래스 정의 &quot;&quot;&quot;
class PatchTokenization(layers.Layer):
    def __init__(self, image_size=image_size, patch_size=patch_size, num_patches=num_patches, projection_dim=projection_dim, **kwargs):
        super().__init__(**kwargs)
        self.image_size = image_size
        self.patch_size = patch_size
        self.half_patch = patch_size // 2
        self.flatten_patches = layers.Reshape((num_patches, -1))   # patch 평탄화
        self.projection = layers.Dense(units=projection_dim)       # patch 투영 (고정된 사이즈의 벡터로 매핑)
        self.layer_norm = layers.LayerNormalization(epsilon=1e-6)  # 레이어 정규화

    def call(self, images):
        patches = tf.image.extract_patches(
            images=images,
            sizes=[1, self.patch_size, self.patch_size, 1],
            strides=[1, self.patch_size, self.patch_size, 1],
            rates=[1, 1, 1, 1],
            padding=&quot;VALID&quot;,
        )
        flat_patches = self.flatten_patches(patches)
        tokens = self.projection(flat_patches)
        return (tokens, patches)</code></pre>
<pre><code class="language-python">&quot;&quot;&quot; 📌 이미지 patch 시각화 &quot;&quot;&quot;
image = train_x[np.random.choice(range(train_x.shape[0]))]
resized_image = tf.image.resize(tf.convert_to_tensor([image]), size=(image_size, image_size))

(token, patch) = PatchTokenization()(resized_image)
(token, patch) = (token[0], patch[0])
n = patch.shape[0]
count = 1
plt.figure(figsize=(4, 4))
for row in range(n):
    for col in range(n):
        plt.subplot(n, n, count)
        count = count + 1
        image = tf.reshape(patch[row][col], (patch_size, patch_size, 3))
        plt.imshow(image)
        plt.axis(&quot;off&quot;)
plt.show()</code></pre>
<p><img src="https://velog.velcdn.com/images/changh2_00/post/545ae48c-6ae3-4686-85f5-27a62ed7fbc5/image.png" alt=""></p>
<pre><code class="language-python">&quot;&quot;&quot; 📌 patch 인코더 클래스 정의 &quot;&quot;&quot;
class PatchEncoder(layers.Layer):
    def __init__(self, num_patches=num_patches, projection_dim=projection_dim, **kwargs):
        super().__init__(**kwargs)
        self.num_patches = num_patches
        self.position_embedding = layers.Embedding(input_dim=num_patches, output_dim=projection_dim)  
        self.positions = tf.range(start=0, limit=self.num_patches, delta=1)

    def call(self, encoded_patches):
        encoded_positions = self.position_embedding(self.positions)
        encoded_patches = encoded_patches + encoded_positions
        return encoded_patches</code></pre>
<pre><code class="language-python">
&quot;&quot;&quot; 📌 MLP 클래스 정의 &quot;&quot;&quot;
def mlp(x, hidden_units, dropout_rate):
    for units in hidden_units:
        x = layers.Dense(units, activation=tf.nn.gelu)(x)
        x = layers.Dropout(dropout_rate)(x)
    return x</code></pre>
<pre><code class="language-python">&quot;&quot;&quot; 📌 ViT 이미지 분류기 생성 함수 정의 &quot;&quot;&quot;
def create_vit_classifier():
    inputs = layers.Input(shape=input_shape)
    x = layers.Normalization()(inputs)
    x = layers.Resizing(image_size, image_size)(x)
    (tokens, _) = PatchTokenization()(x)
    encoded_patches = PatchEncoder()(tokens)
    for _ in range(transformer_layers):
        x1 = layers.LayerNormalization(epsilon=1e-6)(encoded_patches)
        attention_output = layers.MultiHeadAttention(num_heads=num_heads, key_dim=projection_dim, dropout=0.1)(x1, x1)
        x2 = layers.Add()([attention_output, encoded_patches])
        x3 = layers.LayerNormalization(epsilon=1e-6)(x2)
        x3 = mlp(x3, hidden_units=transformer_units, dropout_rate=0.1)
        encoded_patches = layers.Add()([x3, x2])

    x = layers.LayerNormalization(epsilon=1e-6)(encoded_patches)
    x = layers.Flatten()(x)
    x = layers.Dropout(0.5)(x)
    x = mlp(x, hidden_units=mlp_head_units, dropout_rate=0.5)
    outputs = layers.Dense(num_classes)(x)
    model = tf.keras.Model(inputs=inputs, outputs=outputs)
    return model</code></pre>
<pre><code class="language-python">&quot;&quot;&quot; 📌 CosineDecay 클래스 정의 &quot;&quot;&quot;
# 학습률 조정 방법 중 하나
class CosineDecay(tf.keras.optimizers.schedules.LearningRateSchedule):
    def __init__(self, learning_rate_base, total_steps, warmup_learning_rate, warmup_steps):
        super().__init__()
        self.learning_rate_base = learning_rate_base
        self.total_steps = total_steps
        self.warmup_learning_rate = warmup_learning_rate
        self.warmup_steps = warmup_steps
        self.pi = tf.constant(np.pi)

    def __call__(self, step):
        if self.total_steps &lt; self.warmup_steps:
            raise ValueError(&quot;total_steps 값이 warmup_steps보다 크거나 같아야합니다.&quot;)

        cos_annealed_lr = tf.cos(self.pi * (tf.cast(step, tf.float32) - self.warmup_steps) / float(self.total_steps - self.warmup_steps))
        learning_rate = 0.5 * self.learning_rate_base * (1 + cos_annealed_lr)

        if self.warmup_steps &gt; 0:
            if self.learning_rate_base &lt; self.warmup_learning_rate:
                raise ValueError(
                    &quot;learning_rate_base 값이 warmup_learning_rate보다 크거나 같아야합니다.&quot;)
            slope = (self.learning_rate_base - self.warmup_learning_rate) / self.warmup_steps
            warmup_rate = slope * tf.cast(step, tf.float32) + self.warmup_learning_rate
            learning_rate = tf.where(step &lt; self.warmup_steps, warmup_rate, learning_rate)
        return tf.where(step &gt; self.total_steps, 0.0, learning_rate, name=&quot;learning_rate&quot;)

# CosineDecay 스케줄러 인스턴스 생성
total_steps = int((len(train_x) / batch_size) * epochs)
warmup_epoch_percentage = 0.10
warmup_steps = int(total_steps * warmup_epoch_percentage)
scheduled_lrs = CosineDecay(
    learning_rate_base=learning_rate,
    total_steps=total_steps,
    warmup_learning_rate=0.0,
    warmup_steps=warmup_steps,)</code></pre>
<br>

<h4 id="모델의-구성-단계">모델의 구성 단계</h4>
<p><img src="https://velog.velcdn.com/images/changh2_00/post/2d8f4815-8bfb-4e03-8840-ebf3d74e19dc/image.png" alt=""></p>
<pre><code class="language-python">&quot;&quot;&quot; 📌 ViT 분류기 생성 &amp; 모델 컴파일 &quot;&quot;&quot;
vit = create_vit_classifier()

optimizer = tf.keras.optimizers.AdamW(
    learning_rate=scheduled_lrs,   # CosinDecay 학습률 스케줄러 사용
    weight_decay=weight_decay)

vit.compile(
    optimizer=optimizer,
    loss=tf.keras.losses.SparseCategoricalCrossentropy(from_logits=True),
    metrics=[tf.keras.metrics.SparseCategoricalAccuracy(name=&quot;accuracy&quot;),
             tf.keras.metrics.SparseTopKCategoricalAccuracy(5, name=&quot;top-5-accuracy&quot;)])</code></pre>
<pre><code class="language-python">&quot;&quot;&quot; 📌 모델 학습 &quot;&quot;&quot;

history = vit.fit(
    x=train_x,
    y=train_y,
    batch_size=batch_size,
    epochs=epochs,
    validation_split=0.2)</code></pre>
<pre><code>&gt;&gt;&gt; Epoch 1/30
    625/625 [==============================] - 55s 64ms/step - loss: 4.9237 - accuracy: 0.0130 - top-5-accuracy: 0.0610 - val_loss: 4.4952 - val_accuracy: 0.0348 - val_top-5-accuracy: 0.1201
    Epoch 2/30
    625/625 [==============================] - 42s 67ms/step - loss: 4.4268 - accuracy: 0.0334 - top-5-accuracy: 0.1327 - val_loss: 4.2472 - val_accuracy: 0.0567 - val_top-5-accuracy: 0.1986
    ...(생략)...
    Epoch 30/30
    625/625 [==============================] - 41s 65ms/step - loss: 0.6335 - accuracy: 0.8090 - top-5-accuracy: 0.9720 - val_loss: 2.7370 - val_accuracy: 0.4057 - val_top-5-accuracy: 0.7010</code></pre><pre><code class="language-python">&quot;&quot;&quot; 📌 모델 결과 시각화 &quot;&quot;&quot;
plt.figure(figsize=(14, 5))

plt.subplot(1, 3, 1)
plt.plot(history.history[&#39;loss&#39;], label=&#39;Training Loss&#39;)
plt.plot(history.history[&#39;val_loss&#39;], label=&#39;Validation Loss&#39;)
plt.title(&#39;Training and Validation Loss&#39;)
plt.xlabel(&#39;Epoch&#39;)
plt.ylabel(&#39;Loss&#39;)
plt.legend()

plt.subplot(1, 3, 2)
plt.plot(history.history[&#39;accuracy&#39;], label=&#39;Training Accuracy&#39;)
plt.plot(history.history[&#39;val_accuracy&#39;], label=&#39;Validation Accuracy&#39;)
plt.title(&#39;Training and Validation Accuracy&#39;)
plt.xlabel(&#39;Epoch&#39;)
plt.ylabel(&#39;Accuracy&#39;)
plt.ylim(0, 1)
plt.legend()

plt.subplot(1, 3, 3)
plt.plot(history.history[&#39;top-5-accuracy&#39;], label=&#39;Training Top-5 Accuracy&#39;)
plt.plot(history.history[&#39;val_top-5-accuracy&#39;], label=&#39;Validation Top-5 Accuracy&#39;)
plt.title(&#39;Training and Validation Top-5 Accuracy&#39;)
plt.xlabel(&#39;Epoch&#39;)
plt.ylabel(&#39;Top-5 Accuracy&#39;)
plt.ylim(0, 1)
plt.legend()
plt.show()</code></pre>
<p><img src="https://velog.velcdn.com/images/changh2_00/post/73986065-1f71-4e49-bb54-f7ec8d8b05e7/image.png" alt=""></p>
<p>&gt;&gt; Validation 정확도는 40%이고, *Top-5 정확도는 70%를 기록함</p>
<blockquote>
<p>*Top-5 정확도: 
모델이 예측한 확률의 상위 5개 클래스 중에 정답 클래스가 하나라도 포함되어 있으면 맞은 것으로 처리</p>
</blockquote>
<pre><code class="language-python">&quot;&quot;&quot; 📌 테스트 데이터 결과 확인 &quot;&quot;&quot;
vit.evaluate(test_x, test_y)</code></pre>
<pre><code>&gt;&gt;&gt; 313/313 [==============================] - 4s 13ms/step - loss: 2.7125 - accuracy: 0.4114 - top-5-accuracy: 0.7034
    [2.7125136852264404, 0.4113999903202057, 0.7034000158309937]</code></pre><p>&gt;&gt; 테스트 정확도는 43%, Top-5 정확도는 72%를 기록함</p>
<hr>
<h3 id="스윈-트랜스포머">스윈 트랜스포머</h3>
<hr>
<h3 id="스윈-트랜스포머-실습">스윈 트랜스포머 실습</h3>
]]></description>
        </item>
        <item>
            <title><![CDATA[Brain MRI 데이터 구조 및 시퀀스]]></title>
            <link>https://velog.io/@changh2_00/Brain-MRI-%EB%8D%B0%EC%9D%B4%ED%84%B0-%EA%B5%AC%EC%A1%B0-%EB%B0%8F-%EC%8B%9C%ED%80%80%EC%8A%A4</link>
            <guid>https://velog.io/@changh2_00/Brain-MRI-%EB%8D%B0%EC%9D%B4%ED%84%B0-%EA%B5%AC%EC%A1%B0-%EB%B0%8F-%EC%8B%9C%ED%80%80%EC%8A%A4</guid>
            <pubDate>Thu, 03 Apr 2025 08:44:24 GMT</pubDate>
            <description><![CDATA[<p><img src="https://velog.velcdn.com/images/changh2_00/post/bad1fb20-f2eb-414f-b7ef-dfdc3ebaf8b2/image.png" alt=""></p>
<p><img src="https://velog.velcdn.com/images/changh2_00/post/4fe1a488-0f4a-4313-839a-4ea1fc3a2a82/image.png" alt=""></p>
<hr>
<h2 id="📦-1-mri-영상의-기본-구조">📦 1. MRI 영상의 기본 구조</h2>
<table>
<thead>
<tr>
<th>항목</th>
<th>설명</th>
</tr>
</thead>
<tbody><tr>
<td><strong>형태</strong></td>
<td>보통 3D 볼륨 데이터 (예: 240×240×155 voxel)</td>
</tr>
<tr>
<td><strong>시퀀스(Sequence)</strong></td>
<td>다양한 조직 특성 강조: T1, T2, T1ce, FLAIR 등</td>
</tr>
<tr>
<td><strong>채널</strong></td>
<td>여러 시퀀스를 함께 사용 → CNN에선 RGB처럼 채널로 다룸 (예: 4채널 MRI)</td>
</tr>
<tr>
<td><strong>Ground Truth</strong></td>
<td>뇌종양 등의 병변을 segmentation mask로 제공 (각 voxel에 클래스 레이블: 0, 1, 2...)</td>
</tr>
</tbody></table>
<p>&gt;&gt; MRI 데이터는 3차원 형태임. 
240x240x155 의 형태를 갖는 MRI 데이터는 
가로 240, 세로 240, 위치를 바꿔가며 찍은 155개의 단면으로 이루어짐 
MRI는 단일 이미지만 보는 게 아니라,
T1, T2, T1ce, FLAIR의 각각 서로 다른 MRI 이미지 파일을 각각 하나의 채널처럼 사용함
딥러닝 모델에 넣을 때는 위 4개의 시퀀스(채널)을 하나의 멀티채널 이미지처럼 묶는것!</p>
<hr>
<h2 id="📁-2-파일-형식--구조">📁 2. 파일 형식 &amp; 구조</h2>
<table>
<thead>
<tr>
<th>형식</th>
<th>설명</th>
</tr>
</thead>
<tbody><tr>
<td><code>.nii</code> / <code>.nii.gz</code></td>
<td>NIfTI 포맷, 의료 AI에서 가장 흔히 사용</td>
</tr>
<tr>
<td><code>.dcm</code></td>
<td>DICOM: 병원 표준 영상 포맷, 메타데이터 포함</td>
</tr>
<tr>
<td><code>.mha</code> / <code>.mhd</code></td>
<td>MetaImage format (일부 연구에서 사용)</td>
</tr>
</tbody></table>
<p>✅ 불러오기 편한 라이브러리: <strong><code>nibabel</code>, <code>SimpleITK</code>, <code>pydicom</code></strong></p>
<p>&gt;&gt; <code>.nii</code>, <code>.nii.gz</code> 포맷을 가장 많이 사용함.
위의 python 라이브러리로 쉽게 불러올 수 있음</p>
<hr>
<h2 id="⚙️-3-중요-전처리-과정들">⚙️ 3. 중요 전처리 과정들</h2>
<table>
<thead>
<tr>
<th>작업</th>
<th>설명</th>
</tr>
</thead>
<tbody><tr>
<td><strong>Resampling</strong></td>
<td>모든 MRI를 동일 voxel spacing으로 맞추기</td>
</tr>
<tr>
<td><strong>Registration</strong></td>
<td>시퀀스 정렬 (이미 수행된 경우도 있음)</td>
</tr>
<tr>
<td><strong>Skull Stripping</strong></td>
<td>뇌 외곽(두개골 등) 제거</td>
</tr>
<tr>
<td><strong>Normalization</strong></td>
<td>intensity 정규화 (Z-score 등)</td>
</tr>
<tr>
<td><strong>Cropping/Padding</strong></td>
<td>유효 영역 기준으로 자르거나 채움</td>
</tr>
</tbody></table>
<p>&gt;&gt; 
Resampling: 서로 다른 해상도를 통일해줘야함.
ex) 어떤 환자의 <em>voxel은 1mm고, 다른 환자는 1.25mm 면 비교가 안됨.
\</em>voxel = volume + pixel = 3차원 상의 pixel (하나의 작은 큐브)</p>
<p>Registration: 서로 다른 시퀀스들(T1, T2, ...)을 같은 위치에 정렬해야함.</p>
<p>Skull Stripping: 뇌만 봐야하므로 외각 정보 제거</p>
<p>Normalization: MRI 촬영기마다 밝기가 다르므로 픽셀값을 정규화해야함</p>
<p>Cropping: 뇌가 있는 부분만 잘라서 사용</p>
<hr>
<h2 id="🔍-4-mri-시퀀스-종류">🔍 4. MRI 시퀀스 종류</h2>
<table>
<thead>
<tr>
<th>시퀀스</th>
<th>정식 명칭</th>
<th>특징 및 용도</th>
</tr>
</thead>
<tbody><tr>
<td><strong>T1</strong></td>
<td>T1-weighted</td>
<td>해부학적 구조 선명히 표현, 정상적인 뇌 조직 파악 유용</td>
</tr>
<tr>
<td><strong>T1ce</strong></td>
<td>T1 + Contrast</td>
<td>T1에 조영제 주입하여 혈관이 풍부한 활성 종양 부위 밝게 표현. 활성 종양(ET) 확인에 유용</td>
</tr>
<tr>
<td><strong>T2</strong></td>
<td>T2-weighted</td>
<td>수분이 많은 부위 강조하여 부종 확인에 유용</td>
</tr>
<tr>
<td><strong>FLAIR</strong></td>
<td>Fluid-Attenuated</td>
<td>T2에서 뇌척수액(CSF) 제거해서 주변의 병변이나 부종을 선명히함</td>
</tr>
</tbody></table>
<hr>
<h2 id="🧠-5-라벨링-정보">🧠 5. 라벨링 정보</h2>
<table>
<thead>
<tr>
<th>종류</th>
<th>예시</th>
</tr>
</thead>
<tbody><tr>
<td><strong>Segmentation Mask</strong></td>
<td>각 voxel에 0~4 등 클래스 지정</td>
</tr>
<tr>
<td><strong>Bounding Box</strong></td>
<td>특정 영역 박스 지정 (적음)</td>
</tr>
<tr>
<td><strong>Clinical Labels</strong></td>
<td>종양 등급, 생존일, 치료 정보 등</td>
</tr>
</tbody></table>
<p>&gt;&gt; Medical AI 모델을 학습시키려면 정확한 정답이 필요한데, 
보통 전문가가 직접 voxel 단위로 그린 segmentation mask로 제공됨</p>
<hr>
<h2 id="🚨-6-딥러닝-모델에서의-유의사항">🚨 6. 딥러닝 모델에서의 유의사항</h2>
<table>
<thead>
<tr>
<th>항목</th>
<th>설명</th>
</tr>
</thead>
<tbody><tr>
<td><strong>Input Shape</strong></td>
<td>GPU 메모리 문제로 patch (128×128×64 등)</td>
</tr>
<tr>
<td><strong>Model Type</strong></td>
<td>3D U-Net, 3D ResNet, ViT 등</td>
</tr>
<tr>
<td><strong>Loss Function</strong></td>
<td>Dice Loss, Focal Loss 등</td>
</tr>
<tr>
<td><strong>Metric</strong></td>
<td>Dice, IoU, Hausdorff Distance 등</td>
</tr>
</tbody></table>
<p>&gt;&gt; MRI 데이터는 3차원이기 때문에, 모델도 3D 버전을 많이 씀.
보통 3D U-Net, 3D ResNet 같은 모델을 쓰고, 입력도 patch 단위로 잘라서 학습시킴 (GPU가 부족하므로)</p>
<p>정확도를 평가할땐 IoU, Dice Score 같은 segmentation 전용 지표를 사용함
IoU는 Intersection of Union의 약자로, Ground Truth와 모델이 예측한 박스가 얼마나 겹치는지 점수로 냄</p>
<p><img src="https://velog.velcdn.com/images/changh2_00/post/54e193f1-1c01-4e52-a012-c2c45b31e0a2/image.png" alt=""></p>
<p><img src="https://velog.velcdn.com/images/changh2_00/post/6e8782a3-f3eb-472a-b794-2ac28436eddc/image.png" alt=""></p>
<hr>
<h2 id="-유용한-python-라이브러리">+) 유용한 Python 라이브러리</h2>
<table>
<thead>
<tr>
<th>목적</th>
<th>라이브러리</th>
</tr>
</thead>
<tbody><tr>
<td>NIfTI 읽기/쓰기</td>
<td><code>nibabel</code>, <code>SimpleITK</code></td>
</tr>
<tr>
<td>DICOM 처리</td>
<td><code>pydicom</code></td>
</tr>
<tr>
<td>시각화</td>
<td><code>matplotlib</code>, <code>napari</code>, <code>nilearn</code>, <code>ITK-SNAP</code></td>
</tr>
<tr>
<td>전처리 파이프라인</td>
<td><code>torchio</code>, <code>MONAI</code></td>
</tr>
</tbody></table>
]]></description>
        </item>
        <item>
            <title><![CDATA[[이미지처리바이블] 3.2장 딥러닝을 활용한 이미지 처리]]></title>
            <link>https://velog.io/@changh2_00/%EC%9D%B4%EB%AF%B8%EC%A7%80%EC%B2%98%EB%A6%AC%EB%B0%94%EC%9D%B4%EB%B8%94-3.2%EC%9E%A5-%EB%94%A5%EB%9F%AC%EB%8B%9D%EC%9D%84-%ED%99%9C%EC%9A%A9%ED%95%9C-%EC%9D%B4%EB%AF%B8%EC%A7%80-%EC%B2%98%EB%A6%AC</link>
            <guid>https://velog.io/@changh2_00/%EC%9D%B4%EB%AF%B8%EC%A7%80%EC%B2%98%EB%A6%AC%EB%B0%94%EC%9D%B4%EB%B8%94-3.2%EC%9E%A5-%EB%94%A5%EB%9F%AC%EB%8B%9D%EC%9D%84-%ED%99%9C%EC%9A%A9%ED%95%9C-%EC%9D%B4%EB%AF%B8%EC%A7%80-%EC%B2%98%EB%A6%AC</guid>
            <pubDate>Tue, 01 Apr 2025 11:51:19 GMT</pubDate>
            <description><![CDATA[<hr>
<p>[이미지 처리 바이블] 교재 3장을 기반으로 작성되었습니다.</p>
<hr>
<h1 id="32-딥러닝을-활용한-이미지-처리">3.2 딥러닝을 활용한 이미지 처리</h1>
<h2 id="321-이미지-분류">3.2.1 이미지 분류</h2>
<h3 id="분류를-위한-데이터-전처리">분류를 위한 데이터 전처리</h3>
<h4 id="모듈-불러오기">모듈 불러오기</h4>
<pre><code class="language-python">import matplotlib.pyplot as plt
from tensorflow.keras.datasets import cifar10
from tensorflow.keras.models import Sequential
from tensorflow.keras.layers import Dense, Flatten
from tensorflow.keras.callbacks import ModelCheckpoint, EarlyStopping</code></pre>
<h4 id="데이터-불러오기">데이터 불러오기</h4>
<pre><code class="language-python"># CIFAR-10 데이터 세트를 불러옵니다.
(train_images, train_labels), (test_images, test_labels) = cifar10.load_data()</code></pre>
<h4 id="데이터-확인하기">데이터 확인하기</h4>
<pre><code class="language-python"># 데이터를 확인합니다.
print(train_images.shape, train_labels.shape)
print(test_images.shape, test_labels.shape)</code></pre>
<pre><code>&gt;&gt;&gt; (50000, 32, 32, 3) (50000, 1)
    (10000, 32, 32, 3) (10000, 1)</code></pre><p><img src="https://velog.velcdn.com/images/changh2_00/post/aa8b12bf-dafc-4f70-ab89-2fe45dfe5fe6/image.png" alt=""></p>
<p><img src="https://velog.velcdn.com/images/changh2_00/post/7206d38b-60e9-4df4-bbf7-2b85cffe4f85/image.png" alt=""></p>
<h4 id="데이터-정규화">데이터 정규화</h4>
<pre><code class="language-python"># 정규화된 이미지 데이터를 하나 확인합니다.
train_images[0]</code></pre>
<pre><code>&gt;&gt;&gt; ndarray (32, 32, 3)</code></pre><pre><code class="language-python"># 확인을 위해 출력
plt.figure(figsize=(1, 1))
plt.imshow(train_images[32])</code></pre>
<p><img src="https://velog.velcdn.com/images/changh2_00/post/dbc7b655-6e69-4797-b03f-aefe1a306863/image.png" alt=""></p>
<pre><code class="language-python"># 정규화를 위해 255로 나누어 줍니다.

train_images = train_images / 255.0
test_images = test_images / 255.0</code></pre>
<pre><code class="language-python"># 정규화된 이미지 데이터 출력
train_images[0]</code></pre>
<pre><code>&gt;&gt;&gt; array([[[0.23137255, 0.24313725, 0.24705882],
            [0.16862745, 0.18039216, 0.17647059],
            [0.19607843, 0.18823529, 0.16862745],
            ...(중략)...
              [0.84705882, 0.72156863, 0.54901961],
            [0.59215686, 0.4627451 , 0.32941176],
            [0.48235294, 0.36078431, 0.28235294]]]) 

# 모든 픽셀이 0에서 1사이의 값으로 정규화됨 --&gt; 최적화 단계에서 더 빠르게 수렴하고 결과를 좋게 만듬</code></pre><br>

<h4 id="데이터-분할훈련-검증">데이터 분할(훈련, 검증)</h4>
<p>데이터는 훈련(train), 검증(validation), 테스트(test) 세트로 나눔</p>
<p><img src="https://velog.velcdn.com/images/changh2_00/post/8a199333-40de-429d-a913-aeeabc7ab08d/image.png" alt=""></p>
<pre><code class="language-python"># 훈련 데이터셋과 검증 데이터셋으로 나누어 줍니다.

val_images = train_images[45000:]
val_labels = train_labels[45000:]

train_images = train_images[:45000]
train_labels = train_labels[:45000]</code></pre>
<p><img src="https://velog.velcdn.com/images/changh2_00/post/ea381d32-24ca-489f-9596-cd3b3f96686c/image.png" alt=""></p>
<hr>
<h3 id="mlp를-활용한-이미지-분류">MLP를 활용한 이미지 분류</h3>
<p>다층 퍼셉트론은 &quot;모든 뉴런들끼리 결합&quot;되어 있어 fully-connected neural network 라고도 부름
3개의 은닉층을 쌓은 다층 퍼셉트론 모델을 구현해볼 것임</p>
<p><img src="https://velog.velcdn.com/images/changh2_00/post/6c5a2eff-73c1-4d69-9f2d-8df32aabe082/image.png" alt=""></p>
<pre><code class="language-python"># 모델을 생성합니다.
mlp_model = Sequential([
    Flatten(input_shape=(32, 32, 3)),    # 1.입력층
    Dense(512, activation=&#39;relu&#39;),        # 2.은닉층        
    Dense(256, activation=&#39;relu&#39;),        # 2.은닉층
    Dense(128, activation=&#39;relu&#39;),        # 2.은닉층
    Dense(10, activation=&#39;softmax&#39;)        # 3.출력층
])</code></pre>
<p><strong>1. 입력층</strong>
&gt; Flatten 층을 사용하여 (32x32x3)의 3차원 이미지의 모든 픽셀 정보를 한 줄로 쫙 펴서 
&amp;nbsp&amp;nbsp&amp;nbsp 크기가 3,072인 1차원 벡터로 변환하고, 이를 밀집층에 연결함</p>
<p><strong>2. 은닉층</strong>
&gt; 신경망의 핵심적인 부분으로, 입력 데이터의 비선형 관계를 학습함</p>
<p><strong>3. 출력층</strong>
&gt; 신경망에서 최종적으로 나오는 결과 값을 표현하는 층
&amp;nbsp&amp;nbsp&amp;nbsp 분류에선 각 클래스에 대한 확률을 출력, 회귀에선 연속적인 값을 출력
&amp;nbsp&amp;nbsp&amp;nbsp <strong>&quot;출력층의 뉴런 개수는 우리가 예측하려는 레이블의 개수로 설정함&quot;</strong>
<br></p>
<pre><code class="language-python"># 모델의 구조를 확인합니다.
mlp_model.summary()</code></pre>
<pre><code>&gt;&gt;&gt; Model: &quot;sequential&quot;
┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┳━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┳━━━━━━━━━━━━━━━━━┓
┃ Layer (type)                         ┃ Output Shape                ┃        Param #  ┃
┡━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━╇━━━━━━━━━━━━━━━━━━━━━━━━━━━━━╇━━━━━━━━━━━━━━━━━┩
│ flatten (Flatten)                    │ (None, 3072)                │              0  │
├──────────────────────────────────────┼─────────────────────────────┼─────────────────┤
│ dense (Dense)                        │ (None, 512)                 │      1,573,376  │
├──────────────────────────────────────┼─────────────────────────────┼─────────────────┤
│ dense_1 (Dense)                      │ (None, 256)                 │        131,328  │
├──────────────────────────────────────┼─────────────────────────────┼─────────────────┤
│ dense_2 (Dense)                      │ (None, 128)                 │         32,896  │
├──────────────────────────────────────┼─────────────────────────────┼─────────────────┤
│ dense_3 (Dense)                      │ (None, 10)                  │          1,290  │
└──────────────────────────────────────┴─────────────────────────────┴─────────────────┘
 Total params: 1,738,890 (6.63 MB)
 Trainable params: 1,738,890 (6.63 MB)
 Non-trainable params: 0 (0.00 B)</code></pre><blockquote>
<p>Layer (type) = 모델이 사용하는 층의 이름과 타입
Output Shape = 층의 출력 형태를 나타냄 (배치 사이즈, 출력 형태). 
&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&amp;nbsp&amp;nbsp&amp;nbsp 모델 학습 전엔 배치 사이즈를 알 수 없기 때문에 None으로 적힘
Param # = 층의 학습 매개변수 개수를 나타냄
$$
\text{훈련 가능한 매개변수 수} = \text{입력 뉴런 수} \times \text{출력 뉴런 수} + \text{출력 뉴런 수}
$$</p>
</blockquote>
<br>

<h4 id="모델-컴파일하기">모델 컴파일하기</h4>
<pre><code class="language-python"># 모델을 컴파일합니다.
mlp_model.compile(optimizer=&#39;adam&#39;,                            # 최적화 알고리즘 설정 
                  loss=&#39;sparse_categorical_crossentropy&#39;,    # 손실 함수 설정
                  metrics=[&#39;accuracy&#39;])                        # 모델의 평가 방법 설정</code></pre>
<br>

<h4 id="모델-훈련하기">모델 훈련하기</h4>
<pre><code class="language-python"># 모델을 훈련합니다.
mlp_model.fit(train_images, train_labels, epochs=5, validation_data=(val_images, val_labels))</code></pre>
<pre><code>&gt;&gt;&gt; Epoch 1/5
    1407/1407 ━━━━━━━━━━━━━━━━━━━━ 9s 4ms/step - accuracy: 0.2646 - loss: 2.0126 - val_accuracy: 0.3544 - val_loss: 1.7919
    Epoch 2/5
    1407/1407 ━━━━━━━━━━━━━━━━━━━━ 6s 3ms/step - accuracy: 0.3832 - loss: 1.7241 - val_accuracy: 0.4104 - val_loss: 1.6439
    Epoch 3/5
    1407/1407 ━━━━━━━━━━━━━━━━━━━━ 4s 3ms/step - accuracy: 0.4129 - loss: 1.6310 - val_accuracy: 0.4270 - val_loss: 1.6039
    Epoch 4/5
    1407/1407 ━━━━━━━━━━━━━━━━━━━━ 5s 3ms/step - accuracy: 0.4397 - loss: 1.5578 - val_accuracy: 0.4438 - val_loss: 1.5901
    Epoch 5/5
    1407/1407 ━━━━━━━━━━━━━━━━━━━━ 4s 3ms/step - accuracy: 0.4539 - loss: 1.5135 - val_accuracy: 0.4512 - val_loss: 1.5468
    &lt;keras.src.callbacks.history.History at 0x7c8fa54a7710&gt;</code></pre><p>&gt;&gt; (미니 배치 경사하강법에서 하나의 배치에 해당하는 데이터의 개수를 의미)
&amp;nbsp&amp;nbsp&amp;nbsp&amp;nbsp&amp;nbsp batch_size를 따로 지정해주지 않아 기본 값인 32로 설정됨
&amp;nbsp&amp;nbsp&amp;nbsp&amp;nbsp&amp;nbsp 45,000개의 데이터를 32개씩 묶어서 학습했고, 따라서 에포크마다 총 1,407번의 이터레이션이 이루어짐
&amp;nbsp&amp;nbsp&amp;nbsp&amp;nbsp&amp;nbsp 5번째 에포크에서 검증용 데이터세트의 정확도는 약 45%를 기록함</p>
<blockquote>
<p><img src="https://velog.velcdn.com/images/changh2_00/post/9ed41a46-9036-4914-a819-2e5738ebc70e/image.png" alt="">
총 데이터 수 = 1000, batch_size = 100 일땐, 한 번의 에포크를 돌기 위해선 10회의 이터레이션을 반복해야함
이터레이션은 &#39;한 번의 에포크 학습을 위해 걸리는 배치 학습의 횟수&#39;임
<br></p>
</blockquote>
<h4 id="모델-평가하기">모델 평가하기</h4>
<pre><code># 모델을 평가합니다.
mlp_model.evaluate(test_images, test_labels)</code></pre><pre><code>&gt;&gt;&gt; 313/313 ━━━━━━━━━━━━━━━━━━━━ 1s 4ms/step - accuracy: 0.4506 - loss: 1.5365
    [1.5445533990859985, 0.44290000200271606]</code></pre><p>&gt; 약 45%의 정확도를 보임
&gt; 이미지 처리에서 다층 퍼셉트론은 한계가 존재함</p>
<ul>
<li><strong>고차원 데이터 처리의 비효율성:</strong> 모든 노드가 fully-connected 되어 있기에 가중치 수가 급격히 증가함</li>
<li><strong>공간적 구조 정보의 손실:</strong> 모든 픽셀값을 일렬로 펼치기에 이미지의 공간정보가 손실됨</li>
<li><strong>스케일과 변형에 대한 민감성:</strong> MLP는 이미지 내 객체의 변화에 취약함. 민감하게 반응하여 다른 객체로 인식함</li>
</ul>
<p>&gt; 더 높은 성능/효율을 위해 이미지 처리에 강한 CNN을 활용해볼것임.</p>
<hr>
<h3 id="cnn을-활용한-이미지-분류">CNN을 활용한 이미지 분류</h3>
<p><img src="https://velog.velcdn.com/images/changh2_00/post/06cb59bb-0b1e-4e1e-a198-13a6e25ccd1a/image.png" alt=""></p>
<h4 id="모델-생성하기">모델 생성하기</h4>
<pre><code class="language-python"># 모듈 불러오기
from tensorflow.keras.layers import Conv2D, MaxPooling2D, Dropout

# 모델을 만듭니다.
cnn_model = Sequential([
    Conv2D(32, (3, 3), padding=&#39;same&#39;, activation=&#39;relu&#39;, input_shape=(32, 32, 3)),    # 1.합성곱 층
    MaxPooling2D((2, 2)),                                                            # 2.풀링 층
    Conv2D(64, (3, 3), padding=&#39;same&#39;, activation=&#39;relu&#39;),                            # 1.합성곱 층
    MaxPooling2D((2, 2)),                                                            # 2.풀링 층
    Conv2D(64, (3, 3), padding=&#39;same&#39;, activation=&#39;relu&#39;),                            # 1.합성곱 층
    Flatten(),    # Dense 층은 1차원 벡터만 받을 수 있으므로 3D 데이터를 일렬로 펴줌
    Dropout(0.3),                                                                    # 3.드롭아웃 층
    Dense(64, activation=&#39;relu&#39;),                                                    # 4.밀집층
    Dropout(0.5),                                                                    # 3.드롭아웃 층
    Dense(10, activation=&#39;softmax&#39;)                                                    # 4.밀집층
    ])</code></pre>
<p><strong>1. 합성곱 층</strong>
&gt; Conv2D 층의 첫번째 매개변수는 필터의 개수를 의미함 (32개)
&amp;nbsp&amp;nbsp&amp;nbsp 두번째 매개변수는 필터의 사이즈를 의미함 (3x3)
&amp;nbsp&amp;nbsp&amp;nbsp 패딩을 추가했고, ReLU 활성화함수 사용, 입력이미지의 사이즈를 명시함</p>
<p><strong>2. 풀링 층</strong>
&gt; MaxPooling2D 층은 풀링의 사이즈를 설정할 수 있는데, (2x2)로 설정했음</p>
<p><strong>3. 드롭아웃 층</strong>
&gt; Dropout(0,3) 기법을 적용하여, 
&amp;nbsp&amp;nbsp&amp;nbsp 학습 과정에서 무작위로 30%의 뉴런을 일시적으로 비활성화해서
&amp;nbsp&amp;nbsp&amp;nbsp 네트워크 과적합을 방지함</p>
<p><strong>4. 밀집층</strong>
&gt; 첫번째 Dense층은 64개의 뉴런을 가지며 1차원으로 변환된 특징 맵의 뉴런 개수를 줄여주고,
&amp;nbsp&amp;nbsp&amp;nbsp 두번째 Dense층은 10개의 뉴런을 가지며 10개의 클래스에 대한 확률을 출력해줌</p>
<pre><code class="language-python"># 모델의 구조를 확인합니다.
cnn_model.summary()</code></pre>
<pre><code>&gt;&gt;&gt; Model: &quot;sequential_1&quot;
┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┳━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┳━━━━━━━━━━━━━━━━━┓
┃ Layer (type)                         ┃ Output Shape                ┃        Param #  ┃
┡━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━╇━━━━━━━━━━━━━━━━━━━━━━━━━━━━━╇━━━━━━━━━━━━━━━━━┩
│ conv2d (Conv2D)                      │ (None, 32, 32, 32)          │            896  │
├──────────────────────────────────────┼─────────────────────────────┼─────────────────┤
│ max_pooling2d (MaxPooling2D)         │ (None, 16, 16, 32)          │              0  │
├──────────────────────────────────────┼─────────────────────────────┼─────────────────┤
│ conv2d_1 (Conv2D)                    │ (None, 16, 16, 64)          │         18,496  │
├──────────────────────────────────────┼─────────────────────────────┼─────────────────┤
│ max_pooling2d_1 (MaxPooling2D)       │ (None, 8, 8, 64)            │              0  │
├──────────────────────────────────────┼─────────────────────────────┼─────────────────┤
│ conv2d_2 (Conv2D)                    │ (None, 8, 8, 64)            │         36,928  │
├──────────────────────────────────────┼─────────────────────────────┼─────────────────┤
│ flatten_1 (Flatten)                  │ (None, 4096)                │              0  │
├──────────────────────────────────────┼─────────────────────────────┼─────────────────┤
│ dropout (Dropout)                    │ (None, 4096)                │              0  │
├──────────────────────────────────────┼─────────────────────────────┼─────────────────┤
│ dense_4 (Dense)                      │ (None, 64)                  │        262,208  │
├──────────────────────────────────────┼─────────────────────────────┼─────────────────┤
│ dropout_1 (Dropout)                  │ (None, 64)                  │              0  │
├──────────────────────────────────────┼─────────────────────────────┼─────────────────┤
│ dense_5 (Dense)                      │ (None, 10)                  │            650  │
└──────────────────────────────────────┴─────────────────────────────┴─────────────────┘
 Total params: 319,178 (1.22 MB)
 Trainable params: 319,178 (1.22 MB)
 Non-trainable params: 0 (0.00 B)</code></pre><br>

<h4 id="모델-컴파일하기-1">모델 컴파일하기</h4>
<pre><code class="language-python"># 모델을 컴파일합니다. (MLP와 동일한 인수 사용)
cnn_model.compile(optimizer=&#39;adam&#39;,
                loss=&#39;sparse_categorical_crossentropy&#39;,
                metrics=[&#39;accuracy&#39;])</code></pre>
<h4 id="콜백-정의하기">콜백 정의하기</h4>
<p>모델을 얼만큼 학습해야 과소/과대적합이 아닌 적절한 모델을 얻을 수 있는지 알기위해,
마지막 에포크 전에 가장 좋은 성능인 best model을 가져올 수 있게 하기 위해 <strong>콜백</strong>을 사용함</p>
<pre><code class="language-python"># EarlyStopping, ModelCheckpoint 콜백을 정의합니다.

from tensorflow.keras.callbacks import EarlyStopping, ModelCheckpoint

early_stopping = EarlyStopping(monitor=&#39;val_loss&#39;, patience=5)
save_best_only = ModelCheckpoint(&#39;best_cifar10_cnn_model.h5&#39;, save_best_only=True)</code></pre>
<blockquote>
<p><strong>EarlyStopping 인수</strong>
        - monitor: 어떤 값을 기준으로 삼을 것인지 정함 (val_loss를 사용하여 검증 손실을 기준으로 삼음)
        - patience: 얼마나 기다릴 것인지 정함 (5번이상 검증 손실이 감소하지 않으면 학습 중단)</p>
<p><strong>ModelCheckPoint 인수</strong>
        - filepath: 모델을 저장할 파일 경로를 지정 (&#39;현재폴더에&#39; &#39;best_cifar10_cnn_model.h5&#39;라는 이름으로 저장)
        - save_best_only: 가장 좋은 성능의 모델만 저장할 것인지 정함</p>
</blockquote>
<br>

<h4 id="모델-학습하기">모델 학습하기</h4>
<pre><code class="language-python"># 모델을 학습시킵니다. (중간에 학습이 중단되는 것이 정상)
history = cnn_model.fit(train_images, train_labels, batch_size=512, epochs=100, validation_data=(val_images, val_labels), callbacks=[early_stopping, save_best_only])</code></pre>
<pre><code>&gt;&gt;&gt; Epoch 1/100
    88/88 ━━━━━━━━━━━━━━━━━━━━ 0s 49ms/step - accuracy: 0.2170 - loss: 2.1037WARNING:absl:You are saving your model as an HDF5 file via `model.save()` or `keras.saving.save_model(model)`. This file format is considered legacy. We recommend using instead the native Keras format, e.g. `model.save(&#39;my_model.keras&#39;)` or `keras.saving.save_model(model, &#39;my_model.keras&#39;)`. 
    88/88 ━━━━━━━━━━━━━━━━━━━━ 14s 83ms/step - accuracy: 0.2178 - loss: 2.1019 - val_accuracy: 0.4514 - val_loss: 1.5609
    Epoch 2/100
    87/88 ━━━━━━━━━━━━━━━━━━━━ 0s 16ms/step - accuracy: 0.4010 - loss: 1.6459WARNING:absl:You are saving your model as an HDF5 file via `model.save()` or `keras.saving.save_model(model)`. This file format is considered legacy. We recommend using instead the native Keras format, e.g. `model.save(&#39;my_model.keras&#39;)` or `keras.saving.save_model(model, &#39;my_model.keras&#39;)`. 
    88/88 ━━━━━━━━━━━━━━━━━━━━ 2s 19ms/step - accuracy: 0.4013 - loss: 1.6451 - val_accuracy: 0.5150 - val_loss: 1.3726
    ...(중략)...
    Epoch 47/100
    88/88 ━━━━━━━━━━━━━━━━━━━━ 3s 19ms/step - accuracy: 0.7862 - loss: 0.5999 - val_accuracy: 0.7700 - val_loss: 0.6895
    Epoch 48/100
    88/88 ━━━━━━━━━━━━━━━━━━━━ 2s 17ms/step - accuracy: 0.7884 - loss: 0.5881 - val_accuracy: 0.7756 - val_loss: 0.6873
</code></pre><p>&gt;&gt; 콜백에 따라 48번째 에포크에서 학습이 중단됨!</p>
<pre><code class="language-python"># 각 스텝의 학습 손실과 검증 손실을 그래프로 나타냅니다.
plt.plot(history.history[&#39;loss&#39;], &#39;b--&#39;)
plt.plot(history.history[&#39;val_loss&#39;], &#39;r--&#39;)
plt.xlabel(&#39;epoch&#39;)
plt.ylabel(&#39;loss&#39;)
plt.legend([&#39;train loss&#39;, &#39;validation loss&#39;])
plt.show()</code></pre>
<p><img src="https://velog.velcdn.com/images/changh2_00/post/b5a1c1d3-0393-4819-8d8c-7e5986a100d1/image.png" alt=""></p>
<h4 id="모델-평가하기-1">모델 평가하기</h4>
<pre><code class="language-python"># 모델을 평가합니다.
cnn_model.evaluate(test_images, test_labels)</code></pre>
<pre><code>&gt;&gt;&gt; 313/313 ━━━━━━━━━━━━━━━━━━━━ 2s 4ms/step - accuracy: 0.7687 - loss: 0.6937
    [0.7016852498054504, 0.7649999856948853]</code></pre><p>평가 결과, MLP 모델의 45%보다 약 31% 정도 높은 76%의 정확도를 얻었음</p>
<br>

<h4 id="예측-결과-시각화">예측 결과 시각화</h4>
<pre><code class="language-python">&quot;&quot;&quot;예측 레이블 배열 생성&quot;&quot;&quot;
predicted_labels = cnn_model.predict(test_images)
predicted_labels.shape</code></pre>
<pre><code>&gt;&gt;&gt; 313/313 ━━━━━━━━━━━━━━━━━━━━ 1s 3ms/step
    (10000, 10)</code></pre><p>하나의 레이블에 대해 길이 10의 배열이 반환됨. (10개의 클래스에 대한 각각의 확률값)
아래와 같이 이 중 <strong>가장 큰 값</strong>을 가지는 인덱스를 뽑아내 예측 레이블로 사용하도록 함</p>
<pre><code class="language-python">&quot;&quot;&quot;각 배열의 최대 값의 색인을 뽑아내어 예측 레이블로 사용&quot;&quot;&quot;

import tensorflow as tf

predicted_labels = tf.argmax(predicted_labels, axis=1)
predicted_labels</code></pre>
<pre><code>&gt;&gt;&gt; &lt;tf.Tensor: shape=(10000,), dtype=int64, numpy=array([3, 8, 8, ..., 5, 1, 7])&gt;</code></pre><pre><code class="language-python">&quot;&quot;&quot;레이블의 정수 값에 클래스 이름을 대응시켜 딕셔너리 생성&quot;&quot;&quot;

label_to_name = {
    0: &#39;airplane&#39;, 1: &#39;automobile&#39;, 2: &#39;bird&#39;, 3: &#39;cat&#39;, 4: &#39;deer&#39;,
    5: &#39;dog&#39;, 6: &#39;frog&#39;, 7: &#39;horse&#39;, 8: &#39;ship&#39;, 9: &#39;truck&#39;
}</code></pre>
<pre><code class="language-python">&quot;&quot;&quot;test_images를 시각화하여 test_labels와 predicted_labels를 비교합니다.
   test_labels와 predicted_labels가 다를 때, xlabel의 색깔을 빨강색으로 변경합니다.&quot;&quot;&quot;

import matplotlib.pyplot as plt

plt.figure(figsize=(10, 10))
for i in range(16):
    plt.subplot(4, 4, i+1)
    plt.xticks([])
    plt.yticks([])
    plt.imshow(test_images[i], cmap=plt.cm.binary)
    xlabel = f&quot;{label_to_name[int(test_labels[i][0])]} ({label_to_name[int(predicted_labels[i])]})&quot;
    plt.xlabel(xlabel, color=&#39;red&#39; if test_labels[i][0] != predicted_labels[i] else &#39;black&#39;)</code></pre>
<p><img src="https://velog.velcdn.com/images/changh2_00/post/0a07525d-9505-4579-b481-aa8b52be1b84/image.png" alt=""></p>
<hr>
<h2 id="322-객체-인식">3.2.2 객체 인식</h2>
<h3 id="하르-캐스케이드">하르 캐스케이드</h3>
<p>&gt; 2001년에 소개된 객체 탐지 알고리즘
&amp;nbsp&amp;nbsp&amp;nbsp 효과적인 특징 추출과 Adabooks 알고리즘을 사용해 작동
<br></p>
<h4 id="특징-추출">특징 추출</h4>
<p>&gt; 이미지의 픽셀은 그리드로 구성되어 있으며, 각 픽셀은 색상/강도 정보를 포함함
&amp;nbsp&amp;nbsp&amp;nbsp 이런 픽셀 정보를 이용해 영역의 특징을 추출함</p>
<p><img src="https://velog.velcdn.com/images/changh2_00/post/1c96875a-7e02-42d4-8423-179fad6fd083/image.png" alt=""></p>
<blockquote>
<p>다양한 사이즈/모양의 하르 캐스케이드 특징을 사용해 이미지의 다양한 특성 포착
각 영역에서 픽셀 강도의 차이를 계산해 특징 값을 추출함</p>
</blockquote>
<br>

<h4 id="하르-캐스케이드-특징의-스케일과-위치">하르 캐스케이드 특징의 스케일과 위치</h4>
<p>&gt; 객체는 위치나 크기에 따라 다양하게 나타나므로 다양한 크기와 위치를 고려해 탐지해야 함
&amp;nbsp&amp;nbsp&amp;nbsp 이를 위해 두 가지 스케일링 방식이 사용됨:</p>
<ul>
<li><strong>이미지 스케일링:</strong> 이미지를 여러 크기로 축소/확대해 객체를 탐지함.</li>
<li><strong>특징 스케일링:</strong> 하르 특징 자체의 크기를 조절해 다양한 크기의 객체를 탐지함.</li>
</ul>
<p>&gt; 이미지 내의 다양한 위치에서 객체가 나타날 수 있기 때문에, 
&amp;nbsp&amp;nbsp&amp;nbsp 위치를 검출하는데 사용되는 방법론들은 아래와 같음</p>
<ul>
<li><strong>슬라이딩 윈도우:</strong> 이미지 전체를 횡단하며 하르 캐스케이드 특징을 추출해 객체를 탐지</li>
<li><strong>스트라이드:</strong> 슬라이딩 윈도우를 얼마나 빠르게 이동시킬지 정하는 값</li>
</ul>
<p><img src="https://velog.velcdn.com/images/changh2_00/post/06a150a9-5b79-4cb9-adda-5e629061fa52/image.png" alt=""></p>
<hr>
<h3 id="에이다부스트">에이다부스트</h3>
<p>하르 캐스케이드 특징을 사용해 이미지에서 특징을 추출한 후 이 특징들을 분류기에 입력으로 제공하는데, &#39;단일 분류기&#39;만 사용하는 것은 제한적이기에 &#39;여러 개의 분류기&#39;를 결합하여 성능을 올린 <strong>앙상블 기법</strong></p>
<p><strong>부스팅(Boosting)</strong>은  머신러닝의 앙상블 기법 중 하나로, 여러 개의 개별 모델을 결합해 하나의 Best 모델을 생성하는 방법이다. AdaBoost가 가장 널리 사용됨</p>
<p><img src="https://velog.velcdn.com/images/changh2_00/post/101249d7-4d1e-41f9-ad02-5045411f399b/image.png" alt=""></p>
<blockquote>
<p><strong>Diagram of the AdaBoost Algorithm</strong>
<img src="https://velog.velcdn.com/images/changh2_00/post/4ce1e79b-9ad0-48ac-b59b-3dce5de6dcad/image.png" alt=""></p>
</blockquote>
<br>

<h4 id="데이터-샘플과-가중치-초기화">데이터 샘플과 가중치 초기화</h4>
<p>Adaboost는 모든 학습 샘플에 동일한 가중치를 부여하며 시작함 
&gt;&gt; (어떤 샘플이 알고리즘이 도움이 되는지 알 수 없기 때문)</p>
<p>$$
w_i = \frac {1}{N} 
$$</p>
<blockquote>
<p>- &amp;nbsp$w_i$ : $i$번째 샘플의 가중치
- &amp;nbsp$N$ : 전체 샘플의 수 </p>
</blockquote>
<br>

<h4 id="반복적인-학습">반복적인 학습</h4>
<p>약한 학습기(Weak learner)의 오차 계산 
$$
\varepsilon = \frac{\sum_{i=1}^N w_i \cdot \text{error}(i)}{\sum_{i=1}^N w_i}
$$</p>
<blockquote>
<p>- &amp;nbsp$\varepsilon$ : 약한 학습기의 가중치 오차
- &amp;nbsp$\text{error}(i)$ : $i$번째 샘플에 대한 예측 오차 (맞으면 0, 틀리면 1) </p>
</blockquote>
<p>학습기 가중치 수식
$$
\alpha = \frac{1}{2} \log \left( \frac{1 - \varepsilon}{\varepsilon} \right)
$$</p>
<blockquote>
<p>- &amp;nbsp$\alpha$ : 학습기의 가중치
&gt;&gt; 학습기가 잘 작동할수록 ($\varepsilon$이 작을수록) $\alpha$값이 커지는 원리</p>
</blockquote>
<p>샘플 가중치 업데이트 수식</p>
<p>$$
w_i \leftarrow w_i \cdot \exp\left( -\alpha \cdot y_i \cdot h(x_i) \right)
$$</p>
<blockquote>
<p>- &amp;nbsp$y_i$ : $i$번째 샘플의 실제 레이블
- &amp;nbsp$h(x_i)$ : $i$번째 샘플에 대한 학습기의 예측 값
&gt;&gt; 학습기의 오류를 계산하고, 이를 기반으로 학습기에 가중치를 부여
&gt;&gt; 잘못 분류된 샘플의 가중치는 증가, 올바르게 분류된 샘플의 가중치는 감소
&gt;&gt; Adaboost는 여러개의 약한 학습기를 순차적으로 학습시키는데, 
&amp;nbsp&amp;nbsp&amp;nbsp&amp;nbsp&amp;nbsp 잘못 맞춘 샘플에 집중해야 약한 학습기들이 보완되기 때문</p>
</blockquote>
<br>

<h4 id="결합">결합</h4>
<p>모든 약한 학습기를 결합해 강한 학습기를 생성함</p>
<p>$$
F(x) = \sum_{t=1}^{T} \alpha_t h_t(x)
$$</p>
<blockquote>
<p>- &amp;nbsp$F(x)$ : 최종 모델의 예측
- &amp;nbsp$T$ : 전체 학습기의 수</p>
</blockquote>
<br>

<h4 id="opencv를-활용한-하르-캐스케이드-구현-실습">OpenCV를 활용한 하르 캐스케이드 구현 실습</h4>
<pre><code class="language-python">import cv2
import matplotlib.pyplot as plt

!wget https://raw.githubusercontent.com/Lilcob/test_colab/main/three%20young%20man.jpg

# 이미지 로드
image_path = &quot;/content/three young man.jpg&quot;
image = cv2.imread(image_path)
gray = cv2.cvtColor(image, cv2.COLOR_BGR2GRAY)

# 하르 캐스케이드 로드 (사전 훈련된 하르 캐스케이드 XML 파일 포함)
face_cascade = cv2.CascadeClassifier(cv2.data.haarcascades + &quot;haarcascade_frontalface_default.xml&quot;)

# 정면 얼굴 탐지
faces = face_cascade.detectMultiScale(gray, scaleFactor=1.1, minNeighbors=5, minSize=(30, 30))
print(faces)</code></pre>
<pre><code>&gt;&gt;&gt; [[449 111 173 173]
     [721 106 170 170]
     [159 148 160 160]
     [371 376 116 116]]</code></pre><pre><code class="language-python"># 탐지된 얼굴에 사각형 그리기
for (x, y, w, h) in faces:
    cv2.rectangle(image, (x, y), (x+w, y+h), (255, 0, 0), 2)

# 결과 표시
plt.imshow(cv2.cvtColor(image, cv2.COLOR_BGR2RGB))
plt.axis(&#39;off&#39;)  # 축 정보 숨기기
plt.show()</code></pre>
<p><img src="https://velog.velcdn.com/images/changh2_00/post/c4df2490-5640-4ae2-a2cf-01c5e7fcd5c0/image.png" alt=""></p>
<hr>
<h2 id="323-스타일-전이">3.2.3 스타일 전이</h2>
<h3 id="스타일-전이란">스타일 전이란?</h3>
<p>&gt; 한 이미지의 스타일을 다른 이미지에 전이시키는 기술
&amp;nbsp&amp;nbsp&amp;nbsp ex) 내가 찍은 사진을 반 고흐의 그림 스타일로 변환 </p>
<p><img src="https://velog.velcdn.com/images/changh2_00/post/9ac24880-5c05-473f-bbc9-61338a431f7f/image.png" alt=""></p>
<p>스타일 전이의 두 가지 핵심 개념:</p>
<ul>
<li><strong>콘텐츠 포현:</strong> 이미지의 구조, 형태, 객체 등 내용적인 요소</li>
<li><strong>스타일 표현:</strong> 색상 조합, 붓질, 텍스처 등 느낌이나 방식</li>
</ul>
<hr>
<h3 id="vae를-활용한-잠재-벡터-추출">VAE를 활용한 잠재 벡터 추출</h3>
<h4 id="이미지-생성-모델의-구현-가능성">이미지 생성 모델의 구현 가능성</h4>
<p>원하는 특징을 가진 이미지를 생성하는 모델을 만들고 싶다면, 특징 벡터를 입력으로 받아 원하는 특징을 결과 값으로 반환하는 모델 구조를 구상하게 됨.
&gt; 여기서 특징을 표현하는 벡터를 <strong>잠재 벡터(Latent Vector)</strong>라고 부름</p>
<p><img src="https://velog.velcdn.com/images/changh2_00/post/3cc5e7d2-adf6-4577-9697-01a6a9928fc6/image.png" alt=""></p>
<p>&gt; 원하는 특징에 대한 이미지를 만들기 위해 가장 기본적이면서도 대표적인 모델로 <strong>VAE(Variational AutoEncoder)</strong>가 있음</p>
<br>

<p><strong>VAE의 목적: &quot;원하는 특징을 가진 이미지를 만들어낼 수 있는가?&quot;</strong></p>
<blockquote>
<p>곱규칙에 의해
$$
p_\theta(x, z) = p_\theta(z) p_\theta(x \mid z) = p_\theta(x) p_\theta(z \mid x)
$$
- &amp;nbsp$z$ : 잠재벡터
- &amp;nbsp$\theta$ : $z$를 받아 이미지를 만들어주는 <strong>디코더</strong> 모델
- &amp;nbsp$p_\theta$ : 모델 $\theta$에 따른 확률 분포
- &amp;nbsp$x$ : 실제 생성하고 싶은 이미지 데이터 세트의 분포 </p>
</blockquote>
<blockquote>
<p>전체 데이터 분포 X는 관측할 수 없기에 $p_\theta(X)$는 알 수 없고, $p_\theta(z \mid x)$ 도 알기 알기어려움
&gt;&gt; 근사 기법 적용해서 해결해봄</p>
<p>$$
q_\phi(z \mid x) \approx p_\theta(z \mid x)
$$</p>
<p>- &amp;nbsp$\phi$ : 원하는 이미지 $x$를 만들기 위한 잠재 벡터 $z$를 찾기위한 모델 (역추적모델) = <strong>인코더</strong>
&gt;&gt; $\phi$를 하나 구성해주면, 우리가 원하는 이미지의 특성을 조절하여 이미지를 변화시킬 수 있을것</p>
</blockquote>
<p><img src="https://velog.velcdn.com/images/changh2_00/post/8a3313e9-9d72-4458-a9b1-a8346b103f1d/image.png" alt=""></p>
<blockquote>
<p>모델 $\phi$의 결과값 잠재 벡터 $z$는 매번 다르게 나타날 수 있음
&gt; 이를 참조해서 잠재 벡터 $z$를 랜덤 추출해내는 방식인 <strong>variational inference</strong> 사용
$$
(\mu, \log \sigma) = \text{EncoderNeuralNet}_\phi(x)
$$
$$
q_\phi(z \mid x) = \mathcal{N}(z; \mu, \mathrm{diag}(\sigma))
$$</p>
<p>- &amp;nbsp$\mathcal{N}(z; \mu, \mathrm{diag}(\sigma))$:  평균 $\mu$, 표준편차 $\sigma$를 따르는 정규분포</p>
</blockquote>
<p>이렇게 랜덤 추출된 벡터는 모델이 데이터의 특성을 더 잘 학습할 수 있게끔 도와줌
&gt;&gt; 모델이 더 일반화된 특성을 배우도록 강제하기 때문!</p>
<p><img src="https://velog.velcdn.com/images/changh2_00/post/857de1d8-9179-4e6f-9e9f-8a763a2e7451/image.png" alt=""></p>
<p>&gt;&gt; 결국 인코더를 지난 벡터 z의 잠재 공간이 도출되고, 변하는 잠재 벡터 z에 따라 도출되는 이미지는 변함</p>
<br>

<h4 id="모델-학습을-위한-목적-함수-설계">모델 학습을 위한 목적 함수 설계</h4>
<p>(?)</p>
<hr>
<h3 id="텐서플로를-활용한-vae-모델-구현-및-학습">텐서플로를 활용한 VAE 모델 구현 및 학습</h3>
<pre><code class="language-python">from IPython import display

import glob
import imageio
import matplotlib.pyplot as plt
import numpy as np
import tensorflow as tf
import tensorflow_probability as tfp
import time

# ✅ 데이터 세트 준비
(train_images, _), (_, _) = tf.keras.datasets.mnist.load_data()

def preprocess_images(images):
    images = images.reshape((images.shape[0], 28, 28, 1)) / 255.
    return np.where(images &gt; .5, 1.0, 0.0).astype(&#39;float32&#39;)

train_images = preprocess_images(train_images)

train_shuffle = 60000
batch_size = 128

train_dataset = (tf.data.Dataset.from_tensor_slices(train_images)
                 .shuffle(train_shuffle).batch(batch_size))

# ✅ 모델 구현
class VAE(tf.keras.Model):
    &quot;&quot;&quot;Variational Autoencoder.&quot;&quot;&quot;

    def __init__(self, latent_dim):
        super(VAE, self).__init__()
        self.latent_dim = latent_dim
        self.encoder = tf.keras.Sequential(
            [
                tf.keras.layers.InputLayer(input_shape=(28, 28, 1)),
                tf.keras.layers.Flatten(),
                tf.keras.layers.Dense(512, activation=&#39;relu&#39;),
                tf.keras.layers.Dense(512, activation=&#39;relu&#39;),
                # No activation
                tf.keras.layers.Dense(latent_dim + latent_dim),
            ]
        )

        self.decoder = tf.keras.Sequential(
            [
                tf.keras.layers.InputLayer(input_shape=(latent_dim,)),
                tf.keras.layers.Dense(512, activation=&#39;relu&#39;),
                tf.keras.layers.Dense(512, activation=&#39;relu&#39;),
                # No activation
                tf.keras.layers.Dense(28*28*1),
                tf.keras.layers.Reshape(target_shape=(28, 28, 1)),
            ]
        )

    @tf.function
    def sample(self, eps=None):
        if eps is None:
            eps = tf.random.normal(shape=(100, self.latent_dim))
        return self.decode(eps, apply_sigmoid=True)

    def encode(self, x):
        mean, logvar = tf.split(self.encoder(x), num_or_size_splits=2, axis=1)
        return mean, logvar

    def reparameterize(self, mean, logvar):
        eps = tf.random.normal(shape=mean.shape)
        return eps * tf.exp(logvar * .5) + mean

    def decode(self, z, apply_sigmoid=False):
        logits = self.decoder(z)
        if apply_sigmoid:
            probs = tf.sigmoid(logits)
            return probs
        return logits

# ✅ 손실 함수 정의, 최적화 기법
optimizer = tf.keras.optimizers.Adam(4e-4)  # Adam 사용


def log_normal_pdf(sample, mean, logvar, raxis=1):
    log2pi = tf.math.log(2. * np.pi)
    return tf.reduce_sum(
        -.5 * ((sample - mean) ** 2. * tf.exp(-logvar) + logvar + log2pi),
        axis=raxis)


def compute_loss(model, x):
    mean, logvar = model.encode(x)
    z = model.reparameterize(mean, logvar)
    x_logit = model.decode(z)
    cross_ent = tf.nn.sigmoid_cross_entropy_with_logits(logits=x_logit, labels=x)
    logpx_z = -tf.reduce_sum(cross_ent, axis=[1, 2, 3])
    logpz = log_normal_pdf(z, 0., 0.)
    logqz_x = log_normal_pdf(z, mean, logvar)
    return -tf.reduce_mean(logpx_z + logpz - logqz_x)

@tf.function
def train_step(model, x, optimizer):
    with tf.GradientTape() as tape:
        loss = compute_loss(model, x)
    gradients = tape.gradient(loss, model.trainable_variables)
    optimizer.apply_gradients(zip(gradients, model.trainable_variables))

# ✅ 모델 정의
epochs = 50
# 2차원 잠재 백터를 준비합니다.
latent_dim = 2
num_examples_to_generate = 25

# 생성(예측)을 위해 랜덤 벡터를 일정하게 유지하여
# 개선 사항을 더 쉽게 볼 수 있습니다.
random_vector_for_generation = tf.random.normal(
    shape=[num_examples_to_generate, latent_dim])
model = VAE(latent_dim)

# ✅ 학습 중 중간 진행 사항을 확인하기 위한 함수 정의
def generate_and_save_images(model, epoch, test_sample):
    mean, logvar = model.encode(test_sample)
    z = model.reparameterize(mean, logvar)
    predictions = model.sample(z)
    fig = plt.figure(figsize=(4, 4))

    for i in range(predictions.shape[0]):
        plt.subplot(5, 5, i + 1)
        plt.imshow(predictions[i, :, :, 0], cmap=&#39;gray&#39;)
        plt.axis(&#39;off&#39;)

    plt.savefig(&#39;image_at_epoch_{:04d}.png&#39;.format(epoch))
    plt.show()

# 학습 중간에 확인할 데이터를 정해줍니다.
assert batch_size &gt;= num_examples_to_generate
for test_batch in train_dataset.take(1):
    test_sample = test_batch[0:num_examples_to_generate, :, :, :]

# ✅ 모델 학습 진행
generate_and_save_images(model, 0, test_sample)

for epoch in range(1, epochs + 1):
    start_time = time.time()
    for train_x in train_dataset:
        train_step(model, train_x, optimizer)
    end_time = time.time()

    loss = tf.keras.metrics.Mean()
    for test_x in train_dataset:
        loss(compute_loss(model, test_x))
    elbo = -loss.result()
    display.clear_output(wait=False)
    print(&#39;Epoch: {}, ELBO: {}, time elapse for current epoch: {}&#39;
          .format(epoch, elbo, end_time - start_time))
    generate_and_save_images(model, epoch, test_sample)
</code></pre>
<p><img src="https://velog.velcdn.com/images/changh2_00/post/fe7b9fac-73c6-456e-8b16-2be1e71ab7d1/image.png" alt=""></p>
<blockquote>
<p>&gt;&gt; 학습 중간중간 어떻게 변하는지 확인 가능</p>
</blockquote>
<pre><code class="language-python"># ✅ 2차원 잠재 공간의 벡터로부터 숫자가 어떻게 생성되는지 확인하기
def plot_latent_images(model, n, digit_size=28):
    &quot;&quot;&quot;Plots n x n digit images decoded from the latent space.&quot;&quot;&quot;

    norm = tfp.distributions.Normal(0, 1)
    grid_x = norm.quantile(np.linspace(0.05, 0.95, n))
    grid_y = norm.quantile(np.linspace(0.05, 0.95, n))
    image_width = digit_size*n
    image_height = image_width
    image = np.zeros((image_height, image_width))

    for i, yi in enumerate(grid_x):
        for j, xi in enumerate(grid_y):
            z = np.array([[xi, yi]])
            x_decoded = model.sample(z)
            digit = tf.reshape(x_decoded[0], (digit_size, digit_size))
            image[i * digit_size: (i + 1) * digit_size,
                  j * digit_size: (j + 1) * digit_size] = digit.numpy()

    plt.figure(figsize=(10, 10))
    plt.imshow(image, cmap=&#39;Greys_r&#39;)
    plt.axis(&#39;Off&#39;)
    plt.show()

plot_latent_images(model, 20)</code></pre>
<p><img src="https://velog.velcdn.com/images/changh2_00/post/1738ccf8-f24d-4c93-b3f4-83af8ea51b9c/image.png" alt=""></p>
<hr>
<h3 id="콘텐츠-표현과-스타일-표현의-추출">콘텐츠 표현과 스타일 표현의 추출</h3>
<ul>
<li><p><strong>콘텐츠 표현:</strong> 이미지의 기본적인 구조와 형태를 표현</p>
</li>
<li><p><strong>스타일 표현:</strong> 특징 맵을 통해 Gram Matrix(스타일 행렬)를 계산하고, 이를 통해 이미지의 텍스처와 패턴을 표현</p>
</li>
</ul>
<p>스타일 전이 과정에서 손실 함수는 아래와 같이 정의함:</p>
<p>$$
L_{total} = \alpha L_{content} + \beta L_{style} 
$$</p>
<blockquote>
<p>- &amp;nbsp$L_{total}$ : 전체 손실 함수
- &amp;nbsp$L_{content}$ : 콘텐츠 표현의 손실
- &amp;nbsp$L_{style}$ : 스타일 표현의 손실</p>
</blockquote>
<p>&gt;&gt; $\alpha$값과 $\beta$값을 조정해 이미지에 스타일이 적용되는 정도 조절</p>
<p><img src="https://velog.velcdn.com/images/changh2_00/post/9cfbd9ce-2f92-4081-a7f7-297480f72006/image.png" alt=""></p>
<p>+) 그림 설명</p>
<p>이 그림은 <strong>스타일 전이</strong>를 합성곱 신경망(CNN)을 사용하여 구현하는 전체 흐름을 시각화한 것이다.<br>한 이미지의 <strong>내용(content)</strong> 과 다른 이미지의 <strong>스타일(style)</strong> 을 합쳐서 새로운 이미지를 만드는 과정을 보여준다.</p>
<h4 id="✅-등장인물-요약">✅ 등장인물 요약</h4>
<table>
<thead>
<tr>
<th>기호</th>
<th>의미</th>
</tr>
</thead>
<tbody><tr>
<td>$\vec{p}$</td>
<td><strong>콘텐츠 이미지</strong> (예: 마을 사진)</td>
</tr>
<tr>
<td>$\vec{\alpha}$</td>
<td><strong>스타일 이미지</strong> (예: 고흐의 별이 빛나는 밤)</td>
</tr>
<tr>
<td>$\vec{x}$</td>
<td>우리가 최종적으로 만들고 싶은 <strong>결과 이미지</strong></td>
</tr>
<tr>
<td>$\mathcal{L}_{\text{content}}$</td>
<td>콘텐츠 손실 (내용이 얼마나 비슷한가)</td>
</tr>
<tr>
<td>$\mathcal{L}_{\text{style}}$</td>
<td>스타일 손실 (화풍이 얼마나 비슷한가)</td>
</tr>
<tr>
<td>$\mathcal{L}_{\text{total}}$</td>
<td>전체 손실 함수: 콘텐츠 + 스타일 가중합</td>
</tr>
</tbody></table>
<hr>
<h4 id="🧠-전체-흐름-설명">🧠 전체 흐름 설명</h4>
<h4 id="1️⃣-네트워크-사전-학습된-cnn-vgg-19">1️⃣ 네트워크: <strong>사전 학습된 CNN (VGG-19)</strong></h4>
<p>왼쪽, 오른쪽, 가운데에 똑같은 VGG가 등장한다.</p>
<ul>
<li>오른쪽: 콘텐츠 이미지 $\vec{p}$</li>
<li>왼쪽: 스타일 이미지 $\vec{\alpha}$</li>
<li>가운데: 결과 이미지 $\vec{x}$</li>
</ul>
<p>모두 VGG에 통과되어 <strong>feature map (특징맵)</strong> 을 뽑아낸다.</p>
<hr>
<h4 id="2️⃣-콘텐츠-손실-계산">2️⃣ 콘텐츠 손실 계산</h4>
<ul>
<li>$\vec{p}$ → 특정 층 (<code>conv4_2</code>)의 출력 = $P$</li>
<li>$\vec{x}$ → 같은 층의 출력 = $F$</li>
</ul>
<p>$$
\mathcal{L}_{\text{content}} = \sum (F^l - P^l)^2
$$</p>
<p>👉 콘텐츠 손실은 <strong>내용 유사도</strong>를 측정한다.</p>
<hr>
<h4 id="3️⃣-스타일-손실-계산">3️⃣ 스타일 손실 계산</h4>
<ul>
<li>스타일 이미지 $\vec{\alpha}$ 와 결과 이미지 $\vec{x}$ 를 여러 층 (<code>conv1_1</code>, <code>conv2_1</code>, ...)에 넣고,</li>
<li>출력된 feature map들을 <strong>Gram 행렬</strong>로 변환한다.</li>
</ul>
<p>$$
E_l = \sum (G^l - A^l)^2, \quad
\mathcal{L}_{\text{style}} = \sum \omega_l E_l
$$</p>
<p>👉 스타일 손실은 <strong>층 내부 피처 간 상관관계(GRAM)</strong> 유사도를 측정한다.</p>
<hr>
<h4 id="4️⃣-전체-손실-함수">4️⃣ 전체 손실 함수</h4>
<p>$$
\mathcal{L}<em>{\text{total}} = \alpha \mathcal{L}</em>{\text{content}} + \beta \mathcal{L}_{\text{style}}
$$</p>
<ul>
<li>$\alpha$ : 콘텐츠 중요도  </li>
<li>$\beta$ : 스타일 중요도</li>
</ul>
<hr>
<h4 id="5️⃣-최적화-gradient-descent">5️⃣ 최적화 (Gradient Descent)</h4>
<ul>
<li>$\vec{x}$는 <strong>무작위 노이즈 이미지</strong>에서 시작한다.</li>
<li>이미지 자체를 <strong>최적화 변수로 보고</strong>, 손실을 줄이도록 반복 업데이트:</li>
</ul>
<p>$$
\vec{x} \leftarrow \vec{x} - \lambda \frac{\partial \mathcal{L}_{\text{total}}}{\partial \vec{x}}
$$</p>
<p>👉 즉, 이미지 자체가 <strong>역전파를 통해 계속 수정되는 구조</strong>다.</p>
<hr>
<h4 id="📌-최종-결과">📌 최종 결과</h4>
<ul>
<li>콘텐츠 이미지의 구조를 가지면서  </li>
<li>스타일 이미지의 느낌을 표현한  </li>
<li><strong>새로운 이미지</strong> $\vec{x}$ 가 생성된다! 🎨</li>
</ul>
<hr>
<h4 id="🔥-한-줄-요약">🔥 한 줄 요약</h4>
<p> 콘텐츠 이미지 + 스타일 이미지 → VGG 통과<br> → 손실 함수 계산 → 결과 이미지 업데이트<br> → <strong>스타일을 입힌 콘텐츠 이미지 생성!</strong></p>
]]></description>
        </item>
        <item>
            <title><![CDATA[[이미지처리바이블] 3.1장 딥러닝이란?]]></title>
            <link>https://velog.io/@changh2_00/%EC%9D%B4%EB%AF%B8%EC%A7%80%EC%B2%98%EB%A6%AC%EB%B0%94%EC%9D%B4%EB%B8%94-3.1%EC%9E%A5-%EB%94%A5%EB%9F%AC%EB%8B%9D%EC%9D%B4%EB%9E%80</link>
            <guid>https://velog.io/@changh2_00/%EC%9D%B4%EB%AF%B8%EC%A7%80%EC%B2%98%EB%A6%AC%EB%B0%94%EC%9D%B4%EB%B8%94-3.1%EC%9E%A5-%EB%94%A5%EB%9F%AC%EB%8B%9D%EC%9D%B4%EB%9E%80</guid>
            <pubDate>Tue, 25 Mar 2025 17:29:53 GMT</pubDate>
            <description><![CDATA[<hr>
<p>[이미지 처리 바이블] 교재 3장을 기반으로 작성되었습니다.</p>
<hr>
<h1 id="31-딥러닝이란">3.1 딥러닝이란?</h1>
<h2 id="311-인공-신경망-기초">3.1.1 인공 신경망 기초</h2>
<h3 id="인공지능과-이미지-처리의-관계">인공지능과 이미지 처리의 관계</h3>
<p>인공지능 기술이 발전해감에 따라 이미지 처리에도 수많은 기법이 추가됨</p>
<p><strong>딥러닝:</strong> 인공지능의 한 분야. 인간의 뇌처럼 데이터를 처리/학습
<br>
<strong>이미지 처리의 한계</strong></p>
<p><img src="https://velog.velcdn.com/images/changh2_00/post/5a532183-0286-4e26-a6cf-354ceae8deeb/image.png" alt=""></p>
<p>*CIFAR-10: 컴퓨터 비전 인공지능 모델의 성능을 평가하는 벤치마크 데이터 세트</p>
<p>사람은 레이블을 확인하지 않아도 각 이미지가 무엇을 나타내는지 쉽게 알 수 있음
프로그램으로는 처리하기 어려우므로, 컴퓨터 비전 대회 <strong>ILSVRC</strong>(ImageNet Large Scale Visual Recognition Challenge)를 2010년부터 2017년까지 이어가며 인공지능의 발전을 이끔
<br></p>
<p><strong>인공지능 모델의 발전</strong></p>
<p>2012년 AlexNet이라는 깊은 구조의 신경망 모델이 ILSVRC에서 우승하며 인공지능 모델의 발전에 큰 변화를 가져옴
이후, GoogLeNet 모델, ResNet 모델 등이 깊이와 너비를 늘려 모델의 성능을 향상시킴</p>
<p><img src="https://velog.velcdn.com/images/changh2_00/post/f75734a2-49f4-459f-997d-fc708717067b/image.png" alt=""></p>
<hr>
<h3 id="퍼셉트론">퍼셉트론</h3>
<p>&gt; 아주 간단한 형태의 인공 신경망. 
&amp;nbsp&amp;nbsp&amp;nbsp 여러 개의 입력 값을 받아 이들의 가중합을 계산하고, 활성화 함수를 적용하여 출력 값 생성</p>
<p><img src="https://velog.velcdn.com/images/changh2_00/post/89c39c6e-15d8-4c59-bf10-dac71740e079/image.png" alt=""></p>
<p><img src="https://velog.velcdn.com/images/changh2_00/post/520a1570-221f-4d28-94a0-e215ace81c35/image.png" alt=""></p>
<br>

<p><strong>비용 함수</strong></p>
<p>퍼셉트론은 <strong>*델타 규칙</strong>에 따라 학습됨
*델타 규칙: 출력 값과 실제 정답과의 오차에 따라 가중치를 조절하는 기법</p>
<p>&gt;&gt; 이 오차를 정의하는 것이 <strong>비용 함수</strong>이고, 이를 통해 모델의 성능을 평가함 (값이 클 수록 오차가 큰 것)</p>
<hr>
<h3 id="and-or-nor-연산">AND, OR, NOR 연산</h3>
<p><img src="https://velog.velcdn.com/images/changh2_00/post/5abf5893-439f-4468-b329-f3800f1fb095/image.png" alt=""></p>
<pre><code class="language-python">
&quot;&quot;&quot;위의 세 진리표를 텐서플로의 텐서 데이터로 만듬&quot;&quot;&quot;

import tensorflow as tf

x1 = [0,0,1,1]
x2 = [0,1,0,1]
x = tf.transpose(tf.constant([x1,x2],dtype=tf.float32))

and_y = tf.constant([0,0,0,1],dtype=tf.float32)
or_y = tf.constant([0,1,1,1],dtype=tf.float32)
xor_y = tf.constant([0,1,1,0],dtype=tf.float32)

print(f&quot;x: \n{x}&quot;)
print(f&quot;AND y:\t{and_y}&quot;,f&quot;OR  y:\t{or_y}&quot;,f&quot;XOR y:\t{xor_y}&quot;,sep=&quot;\n&quot;)</code></pre>
<pre><code>&gt;&gt;&gt; x: 
    [[0. 0.]
     [0. 1.]
     [1. 0.]
     [1. 1.]]
    AND y:    [0. 0. 0. 1.]
    OR  y:    [0. 1. 1. 1.]
    XOR y:    [0. 1. 1. 0.]</code></pre><pre><code class="language-python">&quot;&quot;&quot;그래프로 표현&quot;&quot;&quot;

import matplotlib.pyplot as plt
import seaborn as sns

sns.set_style(&quot;darkgrid&quot;)
fig, axs = plt.subplots(1, 3, figsize=(15, 5))

# AND 문제
sns.scatterplot(x=x[:, 0], y=x[:, 1], hue=and_y, ax=axs[0])
axs[0].set_title(&quot;AND Problem&quot;)
axs[0].set_xlabel(&quot;X1&quot;)
axs[0].set_ylabel(&quot;X2&quot;, rotation=0)

# OR 문제
sns.scatterplot(x=x[:, 0], y=x[:, 1], hue=or_y, ax=axs[1])
axs[1].set_title(&quot;OR Problem&quot;)
axs[1].set_xlabel(&quot;X1&quot;)
axs[1].set_ylabel(&quot;X2&quot;, rotation=0)

# XOR 문제
sns.scatterplot(x=x[:, 0], y=x[:, 1], hue=xor_y, ax=axs[2])
axs[2].set_title(&quot;XOR Problem&quot;)
axs[2].set_xlabel(&quot;X1&quot;)
axs[2].set_ylabel(&quot;X2&quot;, rotation=0)
plt.tight_layout()
plt.show()</code></pre>
<p><img src="https://velog.velcdn.com/images/changh2_00/post/7ef1f439-2b7f-4b5a-a435-e4417dbe9dfa/image.png" alt=""></p>
<hr>
<h3 id="퍼셉트론-1">퍼셉트론</h3>
<p>퍼셉트론이 어떻게 학습하여 위의 문제를 해결하는지 봐보자.</p>
<pre><code class="language-python"># MLP 모델 정의
model = tf.keras.models.Sequential([
    tf.keras.layers.Dense(1, input_dim=2, activation=&#39;sigmoid&#39;)  # 활성화 함수로 시그모이드 사용
])</code></pre>
<p>$$
시그모이드 함수 : \quad \sigma (x) = \frac {1}{1+e^{-x}}
$$</p>
<pre><code class="language-python">&quot;&quot;&quot;AND 연산을 해결하기 위한 학습&quot;&quot;&quot;
# AND 연산
model.compile(optimizer=tf.keras.optimizers.SGD(learning_rate=1.0),
              loss=&#39;binary_crossentropy&#39;,
              metrics=[&#39;accuracy&#39;],)

model.fit(x, and_y, epochs=100, batch_size=4)

# 모델 평가
loss, accuracy = model.evaluate(x, and_y)
print(f&#39;Loss: {loss}, Accuracy: {accuracy}&#39;)

# 예측
predictions = model.predict(x)
print(f&#39;Predictions:\n{predictions}&#39;)</code></pre>
<pre><code>&gt;&gt;&gt; Epoch 1/100
    1/1 ━━━━━━━━━━━━━━━━━━━━ 2s 2s/step - accuracy: 0.7500 - loss: 0.8009
    Epoch 2/100
    1/1 ━━━━━━━━━━━━━━━━━━━━ 0s 465ms/step - accuracy: 0.7500 - loss: 0.7638
    Epoch 3/100
    1/1 ━━━━━━━━━━━━━━━━━━━━ 0s 55ms/step - accuracy: 0.7500 - loss: 0.7295
    ...(중략)...
    Epoch 99/100
    1/1 ━━━━━━━━━━━━━━━━━━━━ 0s 54ms/step - accuracy: 1.0000 - loss: 0.1496
    Epoch 100/100
    1/1 ━━━━━━━━━━━━━━━━━━━━ 0s 53ms/step - accuracy: 1.0000 - loss: 0.1485
    1/1 ━━━━━━━━━━━━━━━━━━━━ 0s 293ms/step - accuracy: 1.0000 - loss: 0.1473
    Loss: 0.1473318189382553, Accuracy: 1.0
    1/1 ━━━━━━━━━━━━━━━━━━━━ 0s 120ms/step
    Predictions:
    [[0.00911525]
     [0.1524532 ]
     [0.15414722]
     [0.78086656]]

# 금방 정답률이 1에 도달하고, Predictions의 각 근사치가 AND 연산의 결과와 동일함</code></pre><pre><code class="language-python">&quot;&quot;&quot;OR 연산을 해결하기 위한 학습&quot;&quot;&quot;
# OR 연산
model = tf.keras.models.Sequential([
    tf.keras.layers.Dense(1, input_dim=2, activation=&#39;sigmoid&#39;)
])

model.compile(optimizer=tf.keras.optimizers.SGD(learning_rate=1.0),
              loss=&#39;binary_crossentropy&#39;,
              metrics=[&#39;accuracy&#39;])

model.fit(x, or_y, epochs=100, batch_size=4)

loss, accuracy = model.evaluate(x, or_y)
print(f&#39;Loss: {loss}, Accuracy: {accuracy}&#39;)

predictions = model.predict(x)
print(f&#39;X:\n{x}&#39;)
print(f&#39;Predictions:\n{predictions}&#39;)</code></pre>
<pre><code>&gt;&gt;&gt; Epoch 1/100
    1/1 ━━━━━━━━━━━━━━━━━━━━ 1s 576ms/step - accuracy: 0.5000 - loss: 0.9692
    Epoch 2/100
    1/1 ━━━━━━━━━━━━━━━━━━━━ 0s 143ms/step - accuracy: 0.5000 - loss: 0.7042
    Epoch 3/100
    1/1 ━━━━━━━━━━━━━━━━━━━━ 0s 36ms/step - accuracy: 0.5000 - loss: 0.5838
    ...(중략)...
    Epoch 99/100
    1/1 ━━━━━━━━━━━━━━━━━━━━ 0s 57ms/step - accuracy: 1.0000 - loss: 0.0933
    Epoch 100/100
    1/1 ━━━━━━━━━━━━━━━━━━━━ 0s 60ms/step - accuracy: 1.0000 - loss: 0.0924
    1/1 ━━━━━━━━━━━━━━━━━━━━ 0s 216ms/step - accuracy: 1.0000 - loss: 0.0916
    Loss: 0.09159894287586212, Accuracy: 1.0
    1/1 ━━━━━━━━━━━━━━━━━━━━ 0s 95ms/step
    X:
    [[0. 0.]
     [0. 1.]
     [1. 0.]
     [1. 1.]]
    Predictions:
    [[0.19059952]
     [0.9221345 ]
     [0.9301786 ]
     [0.9985097 ]]

# 금방 정답률이 1에 도달하고, Predictions의 각 근사치가 OR 연산의 결과와 동일함
</code></pre><p><strong>하지만, XOR 연산은 해결할 수 없음!</strong></p>
<pre><code class="language-python">&quot;&quot;&quot;XOR 연산을 해결하기 위한 학습&quot;&quot;&quot;
# XOR 연산
model = tf.keras.models.Sequential([
    tf.keras.layers.Dense(1, input_dim=2, activation=&#39;sigmoid&#39;)
])

model.compile(optimizer=tf.keras.optimizers.SGD(learning_rate=1.0),
              loss=&#39;binary_crossentropy&#39;,
              metrics=[&#39;accuracy&#39;])

model.fit(x, xor_y, epochs=100, batch_size=4)

loss, accuracy = model.evaluate(x, xor_y)
print(f&#39;Loss: {loss}, Accuracy: {accuracy}&#39;)

predictions = model.predict(x)
print(f&#39;X:\n{x}&#39;)
print(f&#39;Predictions:\n{predictions}&#39;)</code></pre>
<pre><code>&gt;&gt;&gt; Epoch 1/100
    1/1 ━━━━━━━━━━━━━━━━━━━━ 0s 451ms/step - accuracy: 0.7500 - loss: 0.7563
    Epoch 2/100
    1/1 ━━━━━━━━━━━━━━━━━━━━ 0s 148ms/step - accuracy: 0.5000 - loss: 0.7375
    Epoch 3/100
    1/1 ━━━━━━━━━━━━━━━━━━━━ 0s 56ms/step - accuracy: 0.5000 - loss: 0.7276
    ...(중략)...
    Epoch 99/100
    1/1 ━━━━━━━━━━━━━━━━━━━━ 0s 50ms/step - accuracy: 0.5000 - loss: 0.6931
    Epoch 100/100
    1/1 ━━━━━━━━━━━━━━━━━━━━ 0s 34ms/step - accuracy: 0.5000 - loss: 0.6931
    1/1 ━━━━━━━━━━━━━━━━━━━━ 0s 215ms/step - accuracy: 0.5000 - loss: 0.6931
    Loss: 0.6931484937667847, Accuracy: 0.5
    1/1 ━━━━━━━━━━━━━━━━━━━━ 0s 95ms/step
    X:
    [[0. 0.]
     [0. 1.]
     [1. 0.]
     [1. 1.]]
    Predictions:
    [[0.50129217]
     [0.49989906]
     [0.5005066 ]
     [0.4991134 ]]

# XOR 문제를 만나면 정답률이 50% 정도로, 0과 1을 판단하지 못함!!</code></pre><p>페셉트론으론 해결 못하는 XOR 연산을 해결하기 위한 <strong>딥러닝</strong>의 등장하는 과정으로 <strong>다층 퍼셉트론</strong>이 도입됨</p>
<hr>
<h3 id="다층-퍼셉트론">다층 퍼셉트론</h3>
<p>&gt; 여러 개의 퍼셉트론 층을 사용해 XOR과 같은 비선형적인 문제를 해결함.
&amp;nbsp&amp;nbsp&amp;nbsp&amp;nbsp복잡해진 모델을 최적화하기 위해선 다음과 같은 과정을 거침</p>
<p><strong>1. 순전파</strong>
&amp;nbsp&amp;nbsp&amp;nbsp&amp;nbsp현재의 매개변수 $\theta$를 사용해 입력 $x$를 전달, 출력 생성
<img src="https://velog.velcdn.com/images/changh2_00/post/39cd7a6a-e19e-4edd-9c80-ac1bc68d2f46/image.png" alt=""></p>
<p><strong>2. 손실 계산</strong>
&amp;nbsp&amp;nbsp&amp;nbsp&amp;nbsp네트워크의 출력과 실제 정답 사이의 오차를 계산해 손실 $L(X, \theta)$ 를 얻음 
<strong>3. 역전파</strong>
&amp;nbsp&amp;nbsp&amp;nbsp&amp;nbsp손실 함수에 대한 그레디언트를 계산하기 위해 역전파 알고리즘 사용. 
&amp;nbsp&amp;nbsp&amp;nbsp&amp;nbsp이 단계에서는 각 매개변수에 대한 손실 함수의 편미분을 계산함
<img src="https://velog.velcdn.com/images/changh2_00/post/cf83d98c-669e-4b5d-aec6-f7d02450b356/image.png" alt=""></p>
<p><strong>4. 매개변수 업데이트</strong>
&amp;nbsp&amp;nbsp&amp;nbsp&amp;nbsp계산된 그레디언트를 사용해 매개변수를 업데이트함. </p>
<p>ex) 경사 하강법 사용해서 손실함수가 최소가 되는 방향으로 매개변수 조정</p>
<pre><code class="language-python">&quot;&quot;&quot;XOR 연산을 해결하기 위한 학습&quot;&quot;&quot;
model = tf.keras.models.Sequential([  # 2개의 밀집층을 가짐
    tf.keras.layers.Dense(16, input_dim=2, activation=&#39;relu&#39;),    # 2개의 데이터를 16개의 퍼셉트론에 대입,
    tf.keras.layers.Dense(1, activation=&#39;sigmoid&#39;)                # 다시 1개의 퍼셉트론에 대입 (relu 활성화함수로 비선형성 도입)
])

model.compile(optimizer=tf.keras.optimizers.SGD(learning_rate=1),
              loss=&#39;binary_crossentropy&#39;,
              metrics=[&#39;accuracy&#39;])

model.fit(x, xor_y, epochs=100, batch_size=4)

loss, accuracy = model.evaluate(x, xor_y)
print(f&#39;Loss: {loss}, Accuracy: {accuracy}&#39;)

predictions = model.predict(x)
print(f&#39;X:\n{x}&#39;)
print(f&#39;Predictions:\n{predictions}&#39;)</code></pre>
<pre><code>&gt;&gt;&gt; Epoch 1/100
    1/1 ━━━━━━━━━━━━━━━━━━━━ 1s 1s/step - accuracy: 0.7500 - loss: 0.6857
    Epoch 2/100
    1/1 ━━━━━━━━━━━━━━━━━━━━ 0s 35ms/step - accuracy: 0.5000 - loss: 0.6734
    Epoch 3/100
    1/1 ━━━━━━━━━━━━━━━━━━━━ 0s 35ms/step - accuracy: 0.7500 - loss: 0.6594
    ...(중략)...
    Epoch 99/100
    1/1 ━━━━━━━━━━━━━━━━━━━━ 0s 35ms/step - accuracy: 1.0000 - loss: 0.0291
    Epoch 100/100
    1/1 ━━━━━━━━━━━━━━━━━━━━ 0s 36ms/step - accuracy: 1.0000 - loss: 0.0286
    1/1 ━━━━━━━━━━━━━━━━━━━━ 0s 287ms/step - accuracy: 1.0000 - loss: 0.0282
    Loss: 0.02817685529589653, Accuracy: 1.0
    1/1 ━━━━━━━━━━━━━━━━━━━━ 0s 141ms/step
    X:
    [[0. 0.]
     [0. 1.]
     [1. 0.]
     [1. 1.]]
    Predictions:
    [[0.07319723]
     [0.9841941 ]
     [0.986696  ]
     [0.00734043]]

# 2개의 밀집층을 쌓아 XOR 연산을 해결함!</code></pre><hr>
<h3 id="활성화-함수">활성화 함수</h3>
<p>&gt; 복잡한 비선형 문제도 해결할 수  있는 능력을 갖게 해줌</p>
<p>초기엔 계단함수를 사용했지만, 모델이 학습을 하는 과정에서 오차 값을 줄이기 위해 &quot;미분&quot;의 개념인 그레디언트를 사용한다는 수학적 문제에 부딪힘.
그렇기에 부드럽게 변화하는 그래프를 갖는 <strong>시그모이드</strong> 함수를 사용함
시그모이드 함수는 0 ~ 1 사이값을 도출함</p>
<p><img src="https://velog.velcdn.com/images/changh2_00/post/0a24554d-2eb7-4c45-8d13-8ff362b35537/image.png" alt=""></p>
<br>

<p><strong>tanh</strong>
tanh함수는 -1 ~ 1 사이값을 도출함
$$
tanh(x) = \frac{e^x - e^{-x}}{e^x + e^{-x}}
$$
<img src="https://velog.velcdn.com/images/changh2_00/post/d4e61156-5c32-4c25-8bd8-d822c4c87445/image.png" alt=""></p>
<br>

<p><strong>ReLU</strong>
최근 가장 널리 사용 되는 활성화 함수 ReLU(Rectified Linear Unit)
&gt; 간단한 수학적 표현으로 연산속도가 빠르고, 
&gt; 비선형성을 유지하며 선형 함수의 성질도 일부 유지하고, 
&gt; 기울기 소실 문제를 어느정도 해결해줌
$$
ReLU(x) = max(0,x)
$$
<img src="https://velog.velcdn.com/images/changh2_00/post/5a0f2b2a-3452-40ed-9cd1-d6c4f80a2125/image.png" alt=""></p>
<br>

<p><strong>Leaky ReLU</strong>
ReLU 뉴런이 학습의 어느 순간부터 항상 0을 출력하게 되는 현상인 &#39;죽은 ReLU&#39; 문제를 막기위함</p>
<p>$$
Leaky ReLU(x) = max(0.1,x)
$$
<img src="https://velog.velcdn.com/images/changh2_00/post/02db65dd-e52e-4afe-90b8-4536762a1666/image.png" alt=""></p>
<br>

<p><strong>소프트맥스</strong>
&quot;다중 클래스 분류&quot; 문제에서 &quot;출력층&quot;의 활성화 함수로 사용
각 클래스에 대한 점수를 받아서 확률 분포로 변환함
$$
softmax(x_i) = \frac {e^{x_i}}{\sum_{j=1}^C e^{x_j}}
$$</p>
<br>

<hr>
<h3 id="손실-함수">손실 함수</h3>
<p><strong>평균 제곱 오차</strong>
주로 &quot;회귀&quot; 문제에 사용. 실제 값과 예측 값의 차이를 제곱하여 평균함
($y_i$는 실제 정답 데이터, $\hat y_i$는 모델이 예측한 예측 값)</p>
<p>$$
L_{\text{mse}} = \frac{1}{n} \displaystyle\sum_{i=1}^{n} (y_i - \hat y_i)^2
$$</p>
<br>

<p><strong>이진 교차 엔트로피</strong>
&quot;이진 분류&quot; 문제에서 사용. 실제 레이블과 예측 확률 사이의 차이를 측정함
($N$은 데이터 세트의 샘플 수, $y_i$는 $i$번째 샘플 $x_i$의 실제 레이블(0 또는 1), $P$는 $x_i$에 대해서 모델이 예측해본 예측 확률)
$$
L_{\text{binary-cross entropy}}(x) = -\frac{1}{N} \sum_{i=1}^{N} \left[ y_i \log(P(x_i)) + (1 - y_i) \log(1 - P(x_i)) \right]
$$</p>
<br>

<p><strong>교차 엔트로피</strong>
&quot;분류&quot; 문제에 주로 사용. 실제 클래스의 레이블과 모델이 예측한 확률 분포 사이의 차이를 측정함
($C$는 예측하려는 범주의 총 개수, $q$는 예측하려고 하는 실제 대상들의 확률 분포, $p$는 예측 모델의 결과로서 반환되는 확률 함수)
$$
L_{\text{cross entropy}}(\mathbf{y}, \hat{\mathbf{y}}) = -\sum_{i=1}^{c} \left[ q(y_i) \log(p(\hat{y}_i)) \right]
$$</p>
<br>

<hr>
<h3 id="모델-최적화">모델 최적화</h3>
<p>&gt; 모델의 가중치와 편향 값을 (손실 함수의 값을 최소화하기 위해) 업데이트하는 방법을 포함함</p>
<p><strong>경사 하강법</strong> 
손실함수의 기울기(그레디언트)를 계산하여, 그 반대 방향으로 조금씩 업데이트함으로써 
손실 함수의 값이 최소가 되는 지점을 찾음 
<img src="https://velog.velcdn.com/images/changh2_00/post/7f874887-d21e-4909-911e-d46259fac2b7/image.png" alt=""></p>
<p><img src="https://velog.velcdn.com/images/changh2_00/post/2fe08950-04f2-4317-ba86-398d04606f8f/image.png" alt=""></p>
<p><strong>확률적 경사 하강법</strong>(SGD, Stochastic Gradient Descent)
&gt; 훈련 세트 중 딱 하나의 샘플을 랜덤하게 뽑아 그 학습 데이터만을 사용하여 그레디언트를 계산, 매개변수 업데이트</p>
<p><strong>미니배치 경사 하강법</strong>
&gt; 훈련 세트에서 일정한 배치 사이즈 만큼의 데이터를 꺼내 사용하여 한 번에 그레디언트를 계산, 매개변수 업데이트</p>
<p><strong>RMSprop</strong>(Root Mean Square Propagation)
&gt; 미니 배치 경사 하강법의 확장
&gt; 학습률을 적응적으로 조정하여 학습 과정을 안정화시킴</p>
<p><strong>Adam</strong>(Adaptive Moment Estimation)
&gt; 모멘텀과 RMSprop의 아이디어를 합쳐 그레디언트의 1차 모멘트(평균)와 2차 모멘트(분산)를 추정하여 매개변수 업데이트</p>
<blockquote>
<p><strong>+) 모멘텀이란?</strong></p>
</blockquote>
<p><img src="https://velog.velcdn.com/images/changh2_00/post/0941bef0-1c67-427d-9fe6-e8b2e21a8549/image.png" alt=""></p>
<hr>
<h3 id="인공지능-모델의-설계-및-학습">인공지능 모델의 설계 및 학습</h3>
<p>선형 회귀를 실습해보기 위해 텐서플로를 사용해 임의 데이터 세트를 만들어보자</p>
<p><strong>데이터 세트의 구조</strong>
<img src="https://velog.velcdn.com/images/changh2_00/post/ff7be31f-d2e9-4b5b-a630-7b77416e982b/image.png" alt=""></p>
<p>선형 회귀를 위해 아래와 같이 선형 관계를 정의함</p>
<p>$$
Y = 1.5X + 3
$$
위 수식에서 기울기(1.5)와 절편(3)은 2차원 공간에서 직선을 정의하는 <strong>&quot;매개변수&quot;</strong>이다. (매개변수 업데이트)</p>
<br>

<p><strong>임의의 독립 변수 X값 생성</strong></p>
<pre><code class="language-python">import tensorflow as tf

x = tf.random.uniform(shape=[100], minval=1, maxval=4)  # 100개의 무작위 값 생성
print(x)</code></pre>
<pre><code>&gt;&gt;&gt; tf.Tensor(
    [2.372521  2.6322994 3.5555437 3.9232826 1.3707356 3.2020717 1.9272035
     2.8056293 3.2508655 2.6148477 2.3290071 3.668225  3.1298292 3.8164668
     ...(중략)...
     3.8406534 2.90824   3.6569834 2.2526617 1.9518126 2.2392645 2.2504907
     2.65007   2.5888472], shape=(100,), dtype=float32)</code></pre><br>

<p><strong>선형 관계가 있는 종속 변수 Y값 생성</strong></p>
<pre><code class="language-python">slope = 1.5
intercept = 3
epsilon = tf.random.truncated_normal(shape=[100], mean=0, stddev=0.3)
y = slope * x + intercept + epsilon  # 위의 수식에서 정의한 선형 관계
print(y)</code></pre>
<pre><code>&gt;&gt;&gt; tf.Tensor(
    [6.74476   7.2772617 8.622717  9.045624  5.0619035 7.6717353 5.905293
     7.0692835 7.927641  6.5626116 6.4334044 8.029643  7.4742274 8.898156
     ...(중략)...    
     8.803744  7.1300993 8.702858  6.7209067 5.7525964 6.617983  6.2661033
     6.777974  6.8323107], shape=(100,), dtype=float32)</code></pre><br>

<p><strong>Matplotlib을 사용한 데이터 시각화</strong></p>
<pre><code class="language-python">import matplotlib.pyplot as plt
import seaborn as sns

sns.set_style(&quot;darkgrid&quot;)
plt.scatter(x, y)
plt.xlabel(&#39;Feature (X)&#39;)
plt.ylabel(&#39;Label (Y)&#39;)
plt.title(&#39;sythetic dataset&#39;)
plt.show()</code></pre>
<p><img src="https://velog.velcdn.com/images/changh2_00/post/edd2bcf0-e21c-4b6e-b628-c4f6a53940d2/image.png" alt=""></p>
<hr>
<h3 id="선형-데이터-모델링">선형 데이터 모델링</h3>
<p><img src="https://velog.velcdn.com/images/changh2_00/post/281d5b77-8660-47bc-9ff6-8198338b4f48/image.png" alt=""></p>
<p><strong>모델 설계</strong></p>
<pre><code class="language-python">&quot;&quot;&quot;Sequential 모델: 한번에 하나의 층을 추가하여 쉽게 구축할 수 있는 선형층 스택&quot;&quot;&quot;
from tensorflow.keras import Sequential 

&quot;&quot;&quot;Dense층: 신경망에서의 뉴런 밀집층&quot;&quot;&quot;
from tensorflow.keras.layers import Dense

model = Sequential()  # 시퀀셜 모델 초기화
model.add(Dense(1, input_shape=(1,)))  # 모델에 밀집층 추가
&quot;&quot;&quot;
밀집층의 첫 번째 인수 1 == 층에 있는 뉴런의 수 == 입력값을 받아 계산을 해주는 수식의 수 ❗❗
input_shape=(1,) 부분의 인수는 입력 데이터의 모양을 지정
&quot;&quot;&quot;

model.compile(loss=&#39;mean_squared_error&#39;, optimizer=&#39;sgd&#39;)  
# 손실 함수와 최적화 함수 지정, 모델 컴파일
</code></pre>
<p><img src="https://velog.velcdn.com/images/changh2_00/post/29404e6d-d648-4ff3-bdae-4233433d57c7/image.png" alt=""></p>
<p><strong>모델 학습</strong></p>
<pre><code class="language-python">&quot;&quot;&quot;
입력이 (1,) 모양을 가질 것으로 예상되므로 X 텐서의 모양이 호환되는지 확인
(-1, 1) 에서 -1은 해당 차원의 사이즈를 텐서플로가 유추하도록 해줌 
&gt;&gt; 100개의 요소를 가진 1차원 텐서면 (100, 1)이 됨
&quot;&quot;&quot;
x_train = tf.reshape(x, (-1, 1))  
x_train.shape</code></pre>
<pre><code>&gt;&gt;&gt; TensorShape([100, 1])</code></pre><br>

<pre><code class="language-python">&quot;&quot;&quot;모델 학습 진행&quot;&quot;&quot;
history = model.fit(x_train, y, epochs=300)</code></pre>
<pre><code>&gt;&gt;&gt; Epoch 1/300
    4/4 [==============================] - 1s 4ms/step - loss: 40.4873
    Epoch 2/300
    4/4 [==============================] - 0s 3ms/step - loss: 10.9472
    Epoch 3/300
    4/4 [==============================] - 0s 3ms/step - loss: 2.9398
    ...(중략)...
    Epoch 299/300
    4/4 [==============================] - 0s 3ms/step - loss: 0.0652
    Epoch 300/300
    4/4 [==============================] - 0s 4ms/step - loss: 0.0659</code></pre><br>

<pre><code class="language-python">&quot;&quot;&quot;적절한 기울기, 절편을 찾았는지 확인&quot;&quot;&quot;
weights, bias = model.get_weights()
print(&quot;Weights (Slope):&quot;, weights)
print(&quot;Bias (Intercept):&quot;, bias)</code></pre>
<pre><code>&gt;&gt;&gt; Weights (Slope): [[-1.0370896]]
    Bias (Intercept): [0.]</code></pre><hr>
<h2 id="312-합성곱-신경망cnn">3.1.2 합성곱 신경망(CNN)</h2>
<p>이미지를 위와 같은 선형 신경망 모델로 처리하게 되면, 
이미지 데이터가 갖는 <strong>공간 정보가 소실</strong>되고, 모든 픽셀 간 연산이 이루어져 <strong>연산량이 지나치게 많아짐!</strong>
&gt;&gt; <strong>CNN</strong>이 이를 해결함</p>
<h3 id="합성곱-신경망의-구성-요소">합성곱 신경망의 구성 요소</h3>
<p>합성곱 층 (convolutional layer)
활성화 함수 (activation function)
풀링 층 (pooling layer)
완전 연결 층 (fully connected layer)
정규화 (normalization)
드롭아웃 (dropout) </p>
<hr>
<h3 id="합성곱-층">합성곱 층</h3>
<p><img src="https://velog.velcdn.com/images/changh2_00/post/0ba5122d-2962-4974-8c84-50b50e132592/image.png" alt=""></p>
<p>합성곱 층에 존재하는 사각형의 가중치 필터를 <strong>합성곱 필터</strong>라 하며, 입력된 이미지 데이터와 연산됨</p>
<br>

<p><strong>필터</strong>
&gt; 합성곱 층에서 입력 데이터의 특징을 추출하기 위해 연산되는 가중치들의 집합
&gt; 형태가 이미지 데이터와 동일한 <strong>3차원 행렬 (6면체)</strong>
&gt; 이미지의 좌측 상단부터 우측 하단까지 오른쪽으로 한 칸씩 이동
<img src="https://velog.velcdn.com/images/changh2_00/post/689e2b2e-595d-40ae-bc48-1832ab42a27d/image.png" alt=""></p>
<p>좌측 상단 픽셀들과 합성곱 필터의 연산 결과는 특징 맵의 좌측 상단에,
우측 하단 픽셀들과 합성곱 필터의 연산 결과는 특징 맵의 우측 하단에 위치함
&gt;&gt; <strong>공간 정보를 유지</strong>하여 특징 맵에 전달!</p>
<br>

<p><strong>커널</strong>
&gt; 종종 혼용되지만, <strong>커널 $\not=$ 필터</strong>
&gt; 커널은 이미지의 특정 패턴이나 특징을 감지하는데 사용되는 <strong>2차원 행렬 (사각형)</strong>임
&gt; 필터는 여러 개의 커널로 이루어짐!
&gt; 커널은 합성곱 필터 내에 포함되어 있고, 필터는 채널 축을 따라 이동하지 않는다 
&amp;nbsp&amp;nbsp&amp;nbsp&amp;nbsp필터는 입력 데이터와 항상 같은 채널을 가지기 때문!</p>
<br>

<p><strong>채널</strong>
&gt; 입력 행렬의 채널 수와 출력 행렬의 채널 수는 관련이 없음!
&amp;nbsp&amp;nbsp&amp;nbsp&amp;nbsp오히려 <strong>출력 행렬의 채널 수 == 사용된 필터 수</strong></p>
<p>ex) 채널 수가 128인 입력 데이터에 합성곱 필터를 1개만 사용하면,
&amp;nbsp&amp;nbsp&amp;nbsp&amp;nbsp&amp;nbsp&amp;nbsp생성되는 특징 맵의 채널 수는 1개
<img src="https://velog.velcdn.com/images/changh2_00/post/a1c6ac17-739b-4f4e-b6cc-d16fb7d17467/image.png" alt=""></p>
<p><img src="https://velog.velcdn.com/images/changh2_00/post/d8a6232c-690c-4093-8e52-aba619992b7c/image.png" alt=""></p>
<blockquote>
<p>RGB 이미지는 R,G,B 3개의 채널을 가짐</p>
</blockquote>
<br>

<p><strong>패딩</strong>
&gt; 입력 데이터 주변에 특정값(pad)을 추가하는 과정, 주로 0으로 이루어진 테두리 사용
&gt; 패딩 없이 합성곱 연산을 수행하면 출력의 사이즈는 입력보다 작아지기 때문에 사용됨
&amp;nbsp&amp;nbsp&amp;nbsp&amp;nbsp합성곱 층을 여러번 거쳐 사이즈가 줄어들면 점점 정보가 손실됨
&gt; 이미지의 모서리 픽셀은 필터가 단 한번만 거쳐 연산이 끝나는 반면,
&amp;nbsp&amp;nbsp&amp;nbsp&amp;nbsp가운데 픽셀은 몇번씩 거쳐 연산에 참여함
&gt;&gt; 패딩은 이런 정보의 손실을 방지하고, 모든 부분이 누락되지 않고 연산에 참여하게끔 함
<img src="https://velog.velcdn.com/images/changh2_00/post/c7294928-78ba-4ecf-84d0-1a8ea9715551/image.png" alt=""></p>
<pre><code class="language-python">&quot;&quot;&quot;패딩 표현 방법&quot;&quot;&quot;
from tensorflow.keras.layers import Conv2D

conv_layer = Conv2D(fliters=32, kernel_size=(3,3), activation=&#39;relu&#39;, padding=&#39;same&#39;)
# padding 인수 값을 same으로 지정하면 상황에 맞는 사이즈로 입력 데이터에 패딩이 더해짐
# padding 인수 값을 valid로 지정하면 패딩을 적용하지 않음
# 패딩없이 (3,3) 커널이라면 가로 세로 2픽셀 씩 줄어서 출력됨 (스트라이드가 1일때)</code></pre>
<p><img src="https://velog.velcdn.com/images/changh2_00/post/819f7a3b-b286-4dc8-abe1-acd58bc752ca/image.png" alt=""></p>
<br>

<p><strong>스트라이드</strong>
&gt; &quot;합성곱 필터가 연산을 한 번 수행한 후 움직이는 거리&quot;
&gt; 스트라이드가 작으면 윈도우가 이동 횟수가 증가하고, 크면 윈도우의 이동 횟수가 감소함 
&gt; 스트라이드를 크게 설정하면 연산이 줄어들지만, 특징을 놓칠 수 있다는 단점이 있음
&amp;nbsp&amp;nbsp&amp;nbsp&amp;nbsp특징 맵이 작아져 합성곱 층을 깊게 쌓기 어려워진다는 단점도 있음</p>
<p><img src="https://velog.velcdn.com/images/changh2_00/post/8eb0065b-5cbc-4ac2-ac6b-9fd8b686598f/image.png" alt=""></p>
<pre><code class="language-python">&quot;&quot;&quot;스트라이드 표현 방법&quot;&quot;&quot;
from tensorflow.keras.layers import Conv2D

# 방법 1
conv_layer = Conv2D(fliters=32, kernel_size=(3,3), activation=&#39;relu&#39;, padding=&#39;same&#39;, stride=(1,1))

# 방법 2
conv_layer = Conv2D(fliters=32, kernel_size=(3,3), activation=&#39;relu&#39;, padding=&#39;same&#39;, stride=1)

# 위의 두 가지 방식으로 표현 가능</code></pre>
<hr>
<h3 id="합성곱-연산-과정">합성곱 연산 과정</h3>
<p>(224, 224, 3) 형태의 이미지가 아래와 같은 두 단계의 합성곱 층을 거치며 어떤 과정을 겪는지 알아보자</p>
<pre><code class="language-python">from tensorflow.keras.models import Sequential
from tensorflow.keras.layers import Conv2D

input_shape = (224, 224, 3)

model = Sequential()

model.add(Conv2D(32, kernel_size=(3,3), strides=(1,1), padding=&#39;same&#39;, activation=&#39;relu&#39;, input_shape=input_shape))
model.add(Conv2D(64, kernel_size=(5,5), strides=(2,2), padding=&#39;valid&#39;, activation=&#39;relu&#39;))</code></pre>
<ul>
<li>첫번째 합성곱 층: 3x3 사이즈의 필터가 32개 존재, 스트라이드 1, 패딩 적용 O</li>
<li>두번째 합성곱 층: 5x5 사이즈의 필터가 64개 존재, 스트라이드 2, 패딩 적용 X</li>
</ul>
<p><img src="https://velog.velcdn.com/images/changh2_00/post/29f3b45f-2bef-4969-8a29-d05716e15a4e/image.png" alt=""></p>
<p><strong>첫번째 합성곱 층</strong>의 필터 사이즈 = (3,3,3) (=입력과 동일한 채널 수)
하나의 윈도우를 연산할때, 
(각각의 픽셀과 곱해진 합성곱 필터 내부 값) x (필터 채널 수) = 9x3 = 27
총 27개의 값이 전부 합쳐져 하나의 값으로 표현</p>
<p>이런 과정을 거쳐 좌측상단부터 우측하단까지 연산을 끝마치면, 패딩을 적용했기에 사이즈가 줄지않은
(224, 224, 1) 형태의 출력 데이터를 갖게되는데, 필터의 개수를 32개로 설정했으므로
(224, 224, 32) 형태의 특징 맵이 만들어짐</p>
<br>

<p><strong>두번째 합성곱 층</strong>의 필터 사이즈 = (5,5,32) (=입력과 동일한 채널 수)
스트라이드가 2이기 때문에,
좌측의 첫번째 픽셀에서 연산을 시작해 두 칸씩 이동하면 
한 줄의 마지막 픽셀에서 연산이 깔끔하게 나눠떨어지지 않음
&gt;&gt; 해당 픽셀은 연산 불가능으로 인식하고 그냥 아래로 이동함</p>
<p>위와 같은 특징을 반영해 합성곱 층을 통과하며 출력 데이터 사이즈가 어떻게 변하는지 식으로 표현됨
<br></p>
<p>$$
\text {output size} = \frac {\text{input size} - \text{filter size}}{\text{stride}} + 1
$$
<br></p>
<p>*주의 - 합성곱 연산 과정 중 패딩 등의 다른 연산은 적용하지 않고
&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&amp;nbsp&amp;nbsp&amp;nbsp&amp;nbsp식의 결과 값이 정수가 아닐 경우 소숫점은 버려야함</p>
<br>

<p>$$
\frac{224 - 5}{2} + 1 = 110.5 ;; \rarr ;; 110
$$
<br></p>
<p>패딩을 적용 안 했기에 (110, 110, 1) 형태의 출력 데이터를 갖게되는데, 필터의 개수를 64개로 설정했으므로
최종적으로 (110, 110, 64) 형태의 특징맵이 만들어짐</p>
<br>

<blockquote>
<p><strong>+) 전치 합성곱</strong>
합성곱 필터의 연산 방식을 수정한 방식, 특징 맵의 사이즈를 확장하는데 이용됨</p>
<p><strong>+) 확장 합성곱</strong>
합성곱 필터의 연산 방식을 수정한 방식, 일정한 간격만큼 떨어진 픽셀과 연산을  수행함</p>
<p><img src="https://velog.velcdn.com/images/changh2_00/post/79fe7e44-6141-48d7-a5e7-02c335d70c12/image.png" alt=""></p>
</blockquote>
<hr>
<h3 id="풀링">풀링</h3>
<p>일반적인 CNN은 합성곱 층을 여러번 배치해서 입력된 데이터의 채널의 사이즈는 늘리되,
가로와 세로 사이즈를 줄여가며 학습을 수행함
&gt; 합성곱 층만 사용해서 사이즈를 줄이는건 비효율적!
&gt; 풀링 층을 함께 사용해야함!!
&gt; 풀링 층은 합성곱 층의 뒤에 붙여 특징 맵을 입력으로 받고, 풀링 연산을 수행한 결과를 출력</p>
<ul>
<li>특징 맵의 사이즈를 줄이는 데 사용되는 기법.</li>
<li>합성곱 층보다 적고 빠른 연산, 적은 메모리 사용</li>
<li>객체가 약간 이동해도 결과가 안 달라지도록 모델의 강건함에 도움줌</li>
</ul>
<p>커널 윈도우와 유사하게 동작하여 윈도우 사이즈, 스트라이드, 패딩 등의 인수를 통해 제어 가능</p>
<pre><code class="language-python">&quot;&quot;&quot;합성곱층+풀링층 적용 예시&quot;&quot;&quot;
from tensorflow.keras.models import Sequential
from tensorflow.keras.layers import Conv2D, Maxpooling2D

input_shape = (224, 224, 3)

model = Sequential()

model.add(Conv2D(32, kernel_size=(3, 3), activation=&#39;relu&#39;, input_shape=input_shape))
model.add(MaxPooling2D(pool_size=(2, 2)))  # 풀링 윈도우 사이즈 = 2x2

model.add(Conv2D(64, kernel_size=(3, 3), activation=&#39;relu&#39;))
model.add(MaxPooling2D(pool_size=(2, 2)))  # 풀링 윈도우 사이즈 = 2x2
</code></pre>
<p><strong>최대풀링</strong>이 가장 많이 사용되고, 평균 풀링, 최소 풀링, 전역 풀링(특징 맵의 전역에서 대표값 추출) 등이 있음</p>
<p><img src="https://velog.velcdn.com/images/changh2_00/post/416c40ac-06e5-4bb4-adac-58b52342fc10/image.png" alt=""></p>
<blockquote>
<p>위는 스트라이드를 2로 설정하고 각각 최대 풀링, 평균 풀링 했을때의 결과이다.</p>
</blockquote>
<br>

<p>&gt;&gt; 여러 층으로 쌓인 합성곱 층과 풀링 층으로 정보를 축약하고 더 넓은 범위의 맥락을 이해</p>
<hr>
<h2 id="313-생성적-적대-신경망gan">3.1.3 생성적 적대 신경망(GAN)</h2>
<h3 id="이미지-생성">이미지 생성</h3>
<p>생성하는 모델은 데이터에서 자주 등장하는 패턴을 학습하고, 
이를 일반화시켜 재구성하여 결과물을 &#39;그럴듯&#39;하게 만들어냄</p>
<p><strong>이미지 생성 모델에 필요한 요소</strong>
&gt; 우선, 이미지에서 특징을 추출하는 능력이 필요함</p>
<ol>
<li>정밀하고 모호하지않은 이미지를 출력해야한다.</li>
<li>이미지를 사실적으로 묘사할 수 있어야한다.</li>
<li>이미지의 품질을 (수치 상으로) 평가하는 능력도 필요하다.</li>
</ol>
<hr>
<h3 id="이미지-생성과-비지도-학습">이미지 생성과 비지도 학습</h3>
<p><strong>지도학습</strong> 
&gt; 각각의 데이터에 레이블이 제공되어, 모델이 연산한 결과와 레이블을 비교하며 학습함
&gt; 라벨링 하는데에 큰 비용과 노동력이 요구됨
&gt; 라벨링 하는데에 논리적 근거가 충분치 않아 데이터를 못 만드는 경우도 있음 
&amp;nbsp&amp;nbsp&amp;nbsp&amp;nbsp이미지 생성을 위한 데이터세트의 레이블은 인간의 주관이 관여되기 때문</p>
<p>위와 같이 데이터를 만들기 어려운 경우, </p>
<p><strong>비지도 학습</strong>을 통해 모델을 학습시킬 수 있음
&gt; 레이블 없이 데이터 자체의 특성만을 기반으로 학습함
&amp;nbsp&amp;nbsp&amp;nbsp&amp;nbspex) 군집화, 차원 축소, 연관 규칙 학습 등</p>
<p><img src="https://velog.velcdn.com/images/changh2_00/post/88e47549-118a-4df5-85d1-ebee396ba0a1/image.png" alt=""></p>
<p>&gt;&gt; 레이블을 전혀 활용하지 못하기에 지도학습보단 학습효율이 떨어짐.
&gt;&gt; GAN에선 비지도 학습을 하되, 간이 레이블을 만들어 우회적 지도 학습 방식을 사용함</p>
<hr>
<h3 id="오토-인코더">오토 인코더</h3>
<p>&gt; GAN 이 생기기 전, GAN에 많은 영감을 제시한 대표적 <strong>이미지 생성 기법</strong></p>
<p><img src="https://velog.velcdn.com/images/changh2_00/post/48a54feb-99b5-4d9b-95c3-20cc894abada/image.png" alt=""></p>
<p>오토 인코더는 두개의 과정을 거침.</p>
<p><strong>1단계</strong>
&gt; 이미지의 표현을 파악하고, 이 정보를 작은 사이즈로 압축하는 과정  <strong>= 인코딩</strong></p>
<p><strong>2단계</strong>
&gt; 압축된 정보를 원본 이미지의 사이즈와 비슷하게 재구성 하는 과정 <strong>= 디코딩</strong></p>
<blockquote>
<p>$$
z = f(x)
$$
인코더$(f)$에서는 입력 데이터$(x)$를 $x$보다 작고 밀도 높은 표현인 잠재공간$(z)$으로 매핑함</p>
</blockquote>
<blockquote>
<p>$$
x&#39; = g(z)
$$
디코더$(g)$에서는 인코더에서 생상한 잠재공간 $z$를 다시 원래의 데이터 $x&#39;$로 복원하는 과정을 거침</p>
</blockquote>
<blockquote>
<p>$$
x \approx x&#39;
$$
재생성된 이미지$(x&#39;)$가 원본 이미지 $(x)$와 차이나지 않도록 모델을 학습시키는 것이 목표</p>
</blockquote>
<p>&gt;&gt; 오토 인코더는 <strong>비지도 학습</strong>을 한다는 것을 알 수 있음!</p>
<p><img src="https://velog.velcdn.com/images/changh2_00/post/1c0e8976-a685-4ef1-9e3a-adc0fe4397a1/image.png" alt=""></p>
<br>

<p><strong>인코더</strong>
&gt; 비지도 학습 중 <strong>차원 축소</strong>와 비슷한 역할을 수행함
&gt; 여러 층을 진행시키며 차원을 축소하고, 중요하고 의미 있는 정보를 탐색함
&gt; 비선형 특성까지 고려할 수 있음 (PCA는 선형 관계에만 효과적)
<br></p>
<p><strong>디코더</strong>
&gt; 축소된 잠재 공간을 원래의 데이터 공간으로 <strong>복원</strong>하는 역할
&gt; 즉, 원본 이미지와 같은 사이즈로 유사한 이미지를 생성해내도록 학습
<br></p>
<p><strong>오토 인코더의 다양한 응용과 한계</strong></p>
<p>응용
&gt; 특징 추출기, 이미지 복원
<img src="https://velog.velcdn.com/images/changh2_00/post/71712add-5802-499c-adf4-394ae9b095b2/image.png" alt=""></p>
<p>한계
&gt; 원하는 형태의 이미지를 생성하기 위해선 이에 상응하는 이미지를 입력으로 주어야함 (제한적임)
&gt; 구조가 단순해서 생성하는 표현이 제한됨 (입력 이외의 표현 묘사 불가능)</p>
<hr>
<h3 id="gan의-아이디어">GAN의 아이디어</h3>
<p>&gt; 정답이 없는 상태에서도 정답이 있는 것처럼 학습할 수 있음 (비지도 학습)
&gt; 실제 데이터와 유사한 생성 데이터를 만드는 <strong>&#39;생성자(Generator)&#39;,</strong>
&gt; 생성된 데이터가 원본인지 판별하는 <strong>&#39;판별자(Discriminator)&#39;</strong>가 서로 경쟁하는 구조</p>
<p><img src="https://velog.velcdn.com/images/changh2_00/post/0542f1e1-204c-4f79-9b57-777756d0e38d/image.png" alt=""></p>
<blockquote>
<p>판별자가 가짜 데이터를 진짜라고 구별하게될 확률을 최소화하려고 노력하는 동안,
생성자는 그 확률을 최대화하려고 노력함
&gt;&gt; 진행되면서 양측이 정교하게 학습되며, 결국 높은 품질의 생성 데이터를 만들게됨</p>
</blockquote>
<p><strong>생성자</strong>
&gt; 생성한 데이터로 판별자를 속여 가짜 데이터를 실제 데이터로 분류하게끔 만드는 것이 목적
&gt; 랜덤노이즈/잠재공간에서 샘플링된 벡터를 입력으로 받아,
&amp;nbsp&amp;nbsp&amp;nbsp&amp;nbsp생성자 신경망을 통과하며 최종적으로 실제 데이터와 동일한 형태를 가진 데이터를 출력함
<br></p>
<p><strong>판별자</strong>
&gt; 입력으로 주어진 샘플이 실제 데이터인지, 생성자가 만든 가짜 샘플인지 구별하는 것이 목적
&gt; 이진 분류 문제를 해결하는 모델 (진짜=1, 가짜=0)
&gt; 이진 교차 엔트로피 손실 함수를 사용해, 실제 레이블과 판별자의 출력 사이 오차를 최소화함</p>
<p>[판별자 학습 과정]
&amp;nbsp&amp;nbsp&amp;nbsp1. 생성자로 가짜 샘플 만듬
&amp;nbsp&amp;nbsp&amp;nbsp2. 실제 데이터 샘플과 가짜 샘플을 각각 입력받아 판별자가 출력하는 확률 갑 계산
&amp;nbsp&amp;nbsp&amp;nbsp3. 진짜 샘플에 대한 손실, 가짜 샘플에 대한 손실. 둘을 합해 총 손실을 계산
&amp;nbsp&amp;nbsp&amp;nbsp4. 총 손실에 대한 그레디언트를 계산하고, 이를 사용해 생성자와 판별자의 가중치 업데이트</p>
<p>&gt;&gt; GAN이 훈련이 진행될수록 판별자의 정확도는 개선되며, 
&amp;nbsp&amp;nbsp&amp;nbsp&amp;nbsp&amp;nbsp&amp;nbsp이는 생성자에게 더 높은 질의 샘플을 생성하도록 요구함</p>
<hr>
<h3 id="gan의-학습-실습">GAN의 학습 실습</h3>
<pre><code class="language-python">&quot;&quot;&quot;mnist데이터 로드&quot;&quot;&quot;
import numpy as np
import tensorflow as tf
from tensorflow.keras.layers import Dense, Flatten, Reshape
from tensorflow.keras.models import Sequential
from tensorflow.keras.datasets import mnist
from tqdm import tqdm
from google.colab.patches import cv2_imshow

(train_images, _), (_, _) = mnist.load_data()
train_images = (train_images - 127.5) / 127.5

cv2_imshow(train_images[0]*127.5+127.5)  # 이미지 하나 출력</code></pre>
<p><img src="https://velog.velcdn.com/images/changh2_00/post/2afe9fac-614d-4b40-abe6-0a38ae713986/image.png" alt=""></p>
<p><strong>모델 생성</strong></p>
<pre><code class="language-python">&quot;&quot;&quot;생성자 모델 함수&quot;&quot;&quot;
def build_generator(input_dim):
    model = Sequential()
    model.add(Dense(512, input_dim=input_dim, activation=&#39;relu&#39;))
    model.add(Dense(28*28, activation=&#39;tanh&#39;))
    model.add(Reshape((28, 28)))
    return model

&quot;&quot;&quot;판별자 모델 함수&quot;&quot;&quot;
def build_discriminator():
    model = Sequential()
    model.add(Flatten(input_shape=(28, 28)))
    model.add(Dense(256, activation=&#39;relu&#39;))
    model.add(Dense(1, activation=&#39;sigmoid&#39;))
    return model

&quot;&quot;&quot;하이퍼파라미터 정의&quot;&quot;&quot;
INPUT_DIM = 50      # 랜덤 노이즈의 차원 수
BATCH_SIZE = 64        # 미니 배치의 사이즈
EPOCHS = 10            # 전체 데이터를 총 학습할 횟수
BUFFER_SIZE = 600    # 데이터 세트를 섞어줄 단위

&quot;&quot;&quot;생성자/판별자 모델 선언&quot;&quot;&quot;
generator = build_generator(INPUT_DIM)
discriminator = build_discriminator()

&quot;&quot;&quot;최적화 기법 선언&quot;&quot;&quot;
generator_optimizer = tf.keras.optimizers.Adam(1e-3)
discriminator_optimizer = tf.keras.optimizers.Adam(1e-2)

&quot;&quot;&quot;학습데이터를 tf.data.Dataset으로 변환&quot;&quot;&quot;
train_dataset = tf.data.Dataset.from_tensor_slices(train_images)\
                  .shuffle(BUFFER_SIZE).batch(BATCH_SIZE)</code></pre>
<br>

<p><strong>손실 함수 정의</strong>
&gt; 생성자와 판별자의 손실 함수를 따로 정의해서 각각의 학습 목적을 정의함
&gt; 판별자는 원본과 생성본을 잘 구분하는지를, 생성자는 판별자를 잘 속였는지를 기준으로 정의 </p>
<pre><code class="language-python">binary_cross_entropy = tf.keras.losses.BinaryCrossentropy()
&quot;&quot;&quot;
real_output: 원본 이미지를 보고 판별자가 판단한 결과 값. (1이 정답)
fake_output: 생성 이미지를 보고 판별자가 판단한 결과 값. (0이 정답) &quot;&quot;&quot;
def discriminator_loss(real_output, fake_output): 
    real_loss = binary_cross_entropy(tf.ones_like(real_output), real_output)
    fake_loss = binary_cross_entropy(tf.zeros_like(fake_output), fake_output)
    total_loss = real_loss + fake_loss
    return total_loss

&quot;&quot;&quot; fake_output이 얼마나 1과 가까운지로 손실을 판별 &quot;&quot;&quot;
def generator_loss(fake_output):
    return binary_cross_entropy(tf.ones_like(fake_output), fake_output)</code></pre>
<pre><code class="language-python">&quot;&quot;&quot;학습하는 함수&quot;&quot;&quot;

@tf.function

# 단 한번의 가중치 업데이트를 위한 함수
def train_step(images):
    noise = tf.random.normal([BATCH_SIZE, INPUT_DIM])

    # 연산을 기록하고, 이를 사용해 손실 함수로부터 그레디언트 계산
    with tf.GradientTape() as gen_tape, tf.GradientTape() as disc_tape:
      generated_images = generator(noise, training=True)

      real_output = discriminator(images, training=True)
      fake_output = discriminator(generated_images, training=True)

      gen_loss = generator_loss(fake_output)                    # 생성자 손실 계산
      disc_loss = discriminator_loss(real_output, fake_output)    # 판별자 손실 계산

    gradients_of_generator = gen_tape.gradient(gen_loss, generator.trainable_variables)                # 생성자 그레디언트 계산
    gradients_of_discriminator = disc_tape.gradient(disc_loss, discriminator.trainable_variables)    # 판별자 그레디언트 계산

    generator_optimizer.apply_gradients(zip(gradients_of_generator, generator.trainable_variables))                # 생성자 가중치 업데이트
    discriminator_optimizer.apply_gradients(zip(gradients_of_discriminator, discriminator.trainable_variables))    # 판별자 가중치 업데이트

    return gen_loss, disc_loss, generated_images</code></pre>
<br>

<p><strong>모델 학습</strong></p>
<pre><code class="language-python">for epoch in range(1,EPOCHS+1):
    t = tqdm(train_dataset)
    for image_batch in t:
        g_loss, d_loss, fake_image = train_step(image_batch)
        t.set_description_str(f&quot;Epoch - {epoch}&quot;)
        t.set_postfix({&quot;G_loss&quot;:&quot;%0.3f&quot; %g_loss.numpy(),
                       &quot;D_loss&quot;:&quot;%0.3f&quot; %d_loss.numpy()})
    cv2_imshow(np.concatenate(
        list(fake_image.numpy()[:10]*127.5+127.5),axis=1))</code></pre>
<p><img src="https://velog.velcdn.com/images/changh2_00/post/2e43865b-1e79-4033-a9a4-2862aeda9ae3/image.png" alt=""></p>
<blockquote>
<p>G_loss, D_loss 각 손실값이 고르게 증가/감소하진 않지만 이미지가 점차 뚜렷해지고 숫자와 비슷해짐</p>
</blockquote>
<hr>
<h3 id="gan의-한계">GAN의 한계</h3>
<ul>
<li>노이즈로부터 만들어지는 이미지</li>
<li>개체 인식 불가능</li>
<li>학습의 불안정성<br>

</li>
</ul>
<p><strong>노이즈로부터 만들어지는 이미지</strong>
&gt; 일관성 없는 결과나 예측할 수 없는 변동이 발생할 수 있음
&gt; 제공된 노이즈를 받아 2를 만들어낼지, 3을 만들어낼지 알 수가 없음
&gt; 구체적으로 원하는 이미지를 만들긴 어려움
<img src="https://velog.velcdn.com/images/changh2_00/post/ba83162c-8d9e-4789-b14d-e259fbc01f71/image.png" alt=""></p>
<p><strong>개체 인식 불가능</strong>
&gt; GAN은 이미지 내부 개체의 세부적인 특성이나 위치를 인식하는 것에 한계가 있음
<br></p>
<p><strong>학습의 불안정성</strong>
&gt; 각 모델이 서로 경쟁하며 학습하는 구조는 불안정함. 균형을 유지하는 것이 쉽지 않기 때문</p>
<blockquote>
<p>** +)** 조건부 GAN
노이즈와 레이블 벡터(정답)를 추가적으로 받아 좀 더 원하는 모양을 만들어낼 순 있음</p>
<p><img src="https://velog.velcdn.com/images/changh2_00/post/b0cfcbfd-a27f-47b7-b1d3-a4afca6ef795/image.png" alt=""></p>
</blockquote>
]]></description>
        </item>
        <item>
            <title><![CDATA[[이미지처리바이블] 2장 이미지 처리 기초]]></title>
            <link>https://velog.io/@changh2_00/%EC%9D%B4%EB%AF%B8%EC%A7%80%EC%B2%98%EB%A6%AC%EB%B0%94%EC%9D%B4%EB%B8%94-2%EC%9E%A5-%EC%9D%B4%EB%AF%B8%EC%A7%80-%EC%B2%98%EB%A6%AC-%EA%B8%B0%EC%B4%88</link>
            <guid>https://velog.io/@changh2_00/%EC%9D%B4%EB%AF%B8%EC%A7%80%EC%B2%98%EB%A6%AC%EB%B0%94%EC%9D%B4%EB%B8%94-2%EC%9E%A5-%EC%9D%B4%EB%AF%B8%EC%A7%80-%EC%B2%98%EB%A6%AC-%EA%B8%B0%EC%B4%88</guid>
            <pubDate>Sat, 22 Mar 2025 03:07:12 GMT</pubDate>
            <description><![CDATA[<hr>
<p>[이미지 처리 바이블] 교재 2장을 기반으로 작성되었습니다.</p>
<hr>
<h1 id="21-이미지란">2.1 이미지란?</h1>
<h2 id="211-디지털-이미지의-구조">2.1.1 디지털 이미지의 구조</h2>
<h3 id="픽셀">픽셀</h3>
<p><strong>&gt;</strong> 모든 디지털 이미지의 핵심, <strong>화면을 구성하는 가장 작은 요소</strong></p>
<p><strong>해상도:</strong> 이미지가 보유하고 있는 픽셀의 양</p>
<p><strong>픽셀 밀도:</strong> 디스플레이의 픽셀이 얼마나 촘촘하게 배열되어 있는지를 나타내는 척도</p>
<hr>
<h3 id="무손실-압축과-손실-압축">무손실 압축과 손실 압축</h3>
<p><strong>무손실 압축:</strong> PNG (Portable Network Graphics)</p>
<p><strong>손실 압축:</strong> JPEG (Joint Photographic Experts Group)</p>
<p><strong>&gt;&gt;</strong> 이미지 편집 후 JPEG로 저장할 때마다 손실 압축이 다시 적용되므로 품질이 저하됨
<strong>&gt;&gt;</strong> 전처리 및 모델리 과정에선 무손실 형식인 PNG로 작업 후 최종 결과물 배포를 위해 JPEG로 저장함</p>
<hr>
<h2 id="212-색-공간-이해하기">2.1.2 색 공간 이해하기</h2>
<h3 id="색-공간">색 공간</h3>
<p><strong>그레이 스케일</strong></p>
<p><img src="https://velog.velcdn.com/images/changh2_00/post/488488c8-157e-4329-96a4-753e26b24bd0/image.png" alt=""></p>
<p>픽셀에 다양한 색상 스펙트럼 없이 &#39;밝기&#39;를 나타내는 값만 주어지며,
0은 빛이 없는 상태(검은색)를 나타내고, 255는 최대 밝기(흰색)를 나타냄
<strong>&gt;&gt;</strong> 단순하기에 저장 용량이 작고, 처리 속도가 높음
<strong>&gt;&gt;</strong> 에지 감지나 텍스처 분석과 같은 특정 이미지 처리 작업엔 색상이 방해되므로 적합함</p>
<p><strong>RGB</strong></p>
<p>Red, Green, Blue 세 가지 색상은 각각의 <strong>채널</strong>이라는 이름으로 표현됨
ex) Red 채널의 빛을 최대 강도로 발하고, Green, Blue 채널에선 빛을 발하지 않는 픽셀 
<strong>&gt;&gt;</strong> 빨간색 픽셀</p>
<p><img src="https://velog.velcdn.com/images/changh2_00/post/554c458b-c973-400a-af83-3613029e9d16/image.png" alt=""></p>
<p><strong>CMYK</strong></p>
<p>인쇄물에 사용되는 색 표현에 대한 다른 접근 방식
빛을 빼는 원리로 작동됨</p>
<p><strong>HSV</strong></p>
<p><img src="https://velog.velcdn.com/images/changh2_00/post/b9304f02-d0da-4c94-8dcd-4543fa1b77af/image.png" alt=""></p>
<p><strong>H</strong>ue(색조):
우리가 보고 있는 색의 유형(R,G,B) 또는 품질을 나타냄</p>
<p><strong>S</strong>aturation(채도):
색조의 강도 또는 선명도를 설명</p>
<p><strong>V</strong>alue(값/밝기):
색상의 밝기, 어두움을 알려줌 (값이 클수록 밝음)</p>
<hr>
<h3 id="비트">비트</h3>
<p><strong>디지털 이미지의 비트 심도</strong>
<strong>&gt;</strong> 이미지의 각 픽셀에 대한 컬러 혹은 그레이 스케일 정보를 표현하는 데 사용되는 비트 수</p>
<p><strong>1비트</strong> --&gt;  0과 1 두 가지 값을 제공
<img src="https://velog.velcdn.com/images/changh2_00/post/c0762404-ae0b-4e17-ae77-754b12d8e8dd/image.png" alt=""></p>
<p><strong>8비트</strong> --&gt; 2^8 = 0~255 사이값. 그레이 스펙트럼</p>
<p><img src="https://velog.velcdn.com/images/changh2_00/post/8c838820-5d3a-437e-9093-dcc0dffe6ab4/image.png" alt=""></p>
<p><strong>16비트</strong> --&gt; 2^16 = 0~65,535 사이값. 부드러운 색 표현
<img src="https://velog.velcdn.com/images/changh2_00/post/9070fa87-490b-4908-be4e-20818d71f972/image.png" alt=""></p>
<p><strong>&gt;&gt;</strong> <strong>이미지 품질:</strong> 비트 심도가 높을수록 그라데이션이 부드러워짐
<strong>&gt;&gt;</strong> <strong>파일 사이즈:</strong> 비트 심도가 높을수록 파일 사이즈 커짐
<strong>&gt;&gt;</strong> <strong>편집의 유연성:</strong> 비트 심도가 높을수록 이미지 품절 저하 없이 편집 가능
<strong>&gt;&gt;</strong> <strong>특수 이미징:</strong> 의료 영상과 같은 적용에선 디테일 손실이 없게하기 위해 특정 비트 심도가 필요함</p>
<hr>
<h2 id="213-이미지에서의-텐서-이해하기">2.1.3 이미지에서의 텐서 이해하기</h2>
<h3 id="텐서의-이미지-표현">텐서의 이미지 표현</h3>
<p>ex) 256x256 사이즈의 그레이 스케일 이미지의 shape = (256, 256)
ex) 256x256 사이즈의 RGB 컬러 이미지의 shape = (256, 256, 3)
ex) 100개 이미지 배치의 256x256 컬러 이미지의 shape = (100, 256, 256, 3)</p>
<p><strong>이미지 다운로드</strong></p>
<pre><code class="language-python">url = &#39;https://cobslab.com/wp-content/uploads/2022/02/ai-009-1.jpg&#39;

# 텐서플로의 tf.keras.utils.get_file 함수를 사용하여 지정된 URL에서 이미지 다운로드
# 🚨 TensorFlow 2.11 이후 버전부턴 fname 인자에 파일 이름 넣고, 저장 경로는 따로 cache_dir 인자로 지정 🚨
import tensorflow as tf
image_path = tf.keras.utils.get_file(
    fname=&#39;image.jpg&#39;,
    origin=url,
    cache_dir=&#39;/content&#39;
)</code></pre>
<p><strong>이미지 불러오기</strong></p>
<pre><code class="language-python">image = tf.io.read_file(image_path)
image</code></pre>
<pre><code>&gt;&gt;&gt; &lt;tf.Tensor: shape=(), dtype=string, numpy=b&#39;\xff\xd8\xff\xe0\x00\ . . . . .
# 파일의 내용은 이미지 변수에 원시 바이너리 문자열로 저장</code></pre><p>텐서플로의 <code>decode_jpeg</code> 함수를 사용해서 원시 바이너리 --&gt; 숫자 텐서로 디코딩
<code>channels=3</code> 인수는 컬러 이미지의 R, G, B 채널에 해당하는 3채널 형식으로 이미지 디코딩을 지정
다음 코드로 디코딩된 이미지는 높이, 너비, 색상 채널을 나타내는 3D 텐서로 이미지 변수에 저장됨</p>
<pre><code class="language-python"># 텐서플로의 tf.image.decode_jpeg 함수를 사용하여 원시 바이너리 문자열을 숫자 텐서로 디코딩
image = tf.image.decode_jpeg(image, channels=3)
image</code></pre>
<pre><code class="language-py">    # 텐서 객체. 높이 952, 너비 1048, 색상 채널의 개수 3
&gt;&gt;&gt; &lt;tf.Tensor: shape=(952, 1048, 3), dtype=uint8, numpy=
    array([[[ 1, 10, 39],
            [ 1, 10, 39],
            [ 1, 10, 39],
            ...(중략)...
            [ 1, 10, 39],
            [ 1, 10, 39],
            [ 1, 10, 39]]], dtype=uint8)&gt;</code></pre>
<pre><code class="language-python">&quot;&quot;&quot;plt.imshow 함수로 이미지 표시&quot;&quot;&quot;
import matplotlib.pyplot as plt

plt.imshow(image)
plt.axis(&#39;off&#39;)
plt.show()</code></pre>
<p><img src="https://velog.velcdn.com/images/changh2_00/post/3c3f40af-6552-4914-a3d4-6f3abb05f4e0/image.png" alt=""></p>
<hr>
<h3 id="다양한-색-공간으로-작업하기">다양한 색 공간으로 작업하기</h3>
<p><strong>색 공간의 선택</strong>은 컴퓨터 비전 <strong>작업의 성능과 정확성에 많은 영향</strong>을 줄 수 있음
각 색 공간의 <strong>장단점을 이해</strong>하는 것이 중요!
채널이 하나뿐인 <strong>그레이 스케일 이미지</strong>는 계산 효율이 좋고, <strong>모양</strong>과 <strong>구조 탐지</strong>에 성능이 좋음</p>
<p><strong>tf.random.unifrom</strong>
<code>tf.random.uniform</code>을 사용하여 랜덤한 값을 가진 텐서를 생성할 수 있음
&gt;&gt; 성능 평가를 위한 임시 이미지를 생성하거나, 시각화 작업에서 색상 패턴 실험에 사용</p>
<pre><code class="language-python"># 샘플 RGB 이미지
rgb_image = tf.random.uniform([100, 100, 3], maxval=255, dtype=tf.float32)
print(rgb_image)</code></pre>
<pre><code>&gt;&gt;&gt; tf.Tensor(
    [[[172.06085   105.57341   144.46169  ]
      [ 34.55243   107.85296    41.10486  ]
      [137.8115    110.60343   104.78278  ]
      ...(중략)...
     [199.63019   193.30444   235.1837   ]
     [162.5828    173.57964   139.23474  ]
     [203.17017   159.5853    162.96648  ]]], shape=(100, 100, 3), dtype=float32)</code></pre><pre><code class="language-python"># 생성된 이미지 시각화
plt.imshow(rgb_image)
plt.title(&#39;RGB Image&#39;)
plt.axis(&#39;off&#39;)
plt.show()</code></pre>
<p><img src="https://velog.velcdn.com/images/changh2_00/post/e62ea86e-cd49-4c8f-8791-d228f8a1e541/image.png" alt=""></p>
<p><strong>tf.image.rgb_to_grayscale</strong>
<code>rgb_to_grayscale</code>을 통해 쉽게 RGB --&gt; 그레이 스케일로 바꿀 수 있음</p>
<pre><code class="language-python"># 위에서 생성된 RGB 이미지를 그레이 스케일로 변환
grayscale_image = tf.image.rgb_to_grayscale(rgb_image)
print(grayscale_image.shape)</code></pre>
<pre><code>&gt;&gt;&gt; (100, 100, 1)</code></pre><pre><code class="language-python">plt.imshow(grayscale_image.numpy().squeeze(), cmap=&#39;gray&#39;)
plt.title(&#39;Grayscale Image&#39;)
plt.axis(&#39;off&#39;)
plt.show()</code></pre>
<p><img src="https://velog.velcdn.com/images/changh2_00/post/1ccdffaf-69a5-4b89-88f7-268e56615c39/image.png" alt=""></p>
<p><strong>tf.image.rgb_to_hsv</strong>
<code>rgb_to_hsv</code>는 RGB --&gt; HSV 로 변환하는데 사용</p>
<pre><code class="language-python">hsv_image = tf.image.rgb_to_hsv(rgb_image)

hue_channel = hsv_image[:,:,0]  # Hue(색조) 채널만 추출

plt.imshow(hue_channel, cmap=&#39;hsv&#39;)
plt.title(&#39;Hue Channel of HSV Image&#39;)
plt.axis(&#39;off&#39;)
plt.colorbar(label=&#39;Hue Value&#39;)
plt.show()</code></pre>
<p><img src="https://velog.velcdn.com/images/changh2_00/post/1928b41a-2a87-4322-b6cd-3b3786d04978/image.png" alt=""></p>
<hr>
<h3 id="픽셀-값의-정규화와-표준화">픽셀 값의 정규화와 표준화</h3>
<p>이미지 처리에서 픽셀 값의 사이즈를 조정하는 건 모델의 성능과 속도에 많은 영향을 끼침
<strong>픽셀 값의 사이즈를 조정</strong>하는 데 사용되는 두 가지 대표적 방법은 <strong>정규화</strong>, <strong>표준화</strong></p>
<p><strong>정규화</strong>
<strong>&gt;&gt; 픽셀 값을 [0, 1] 범위로 스케일링하는 과정</strong>
ex) rgb_image 텐서를 255로 나누면 자동으로 &#39;브로드캐스트&#39;하여 각각의 텐서에 동일한 연산을 함</p>
<pre><code class="language-python">normalized_image = rgb_image / 255.0
rgb_image[0][0] , normalized_image[0][0]</code></pre>
<pre><code class="language-py">&gt;&gt;&gt; (&lt;tf.Tensor: shape=(3,), dtype=float32, numpy=array([231.48172, 235.72926, 213.58714], dtype=float32)&gt;,
      &lt;tf.Tensor: shape=(3,), dtype=float32, numpy=array([0.90777147, 0.92442846, 0.83759665], dtype=float32)&gt;)</code></pre>
<p><strong>표준화</strong>
<strong>&gt;&gt; 픽셀 값을 평균 = 0, 표준 편차 = 1 이 되도록 스케일링하는 과정</strong>
최적화 환경을 균일하게 만들어 학습을 빠르게 함. <strong>중요</strong>한 전처리 단계!
픽셀 값을 표준화하면 <strong>중앙이 0</strong>이 되고, 대략 <strong>[-1, 1] 범위에 속하게</strong> 되어 일관된 훈련 데이터 제공 가능</p>
<pre><code class="language-python">mean = tf.reduce_mean(rgb_image)  # 평균값 계산
stddev = tf.math.reduce_std(rgb_image)  # 표준 편차 계산

standardized_image = (rgb_image - mean) / stddev  # 표준화
rgb_image[0][0] , standardized_image[0][0]</code></pre>
<pre><code class="language-py">&gt;&gt;&gt; (&lt;tf.Tensor: shape=(3,), dtype=float32, numpy=array([231.48172, 235.72926, 213.58714], dtype=float32)&gt;,
      &lt;tf.Tensor: shape=(3,), dtype=float32, numpy=array([1.415816 , 1.4735496, 1.1725885], dtype=float32)&gt;)</code></pre>
<hr>
<h1 id="22-이미지-처리-기법">2.2 이미지 처리 기법</h1>
<h2 id="221-이미지-필터링">2.2.1 이미지 필터링</h2>
<p>선명함을 드러내고, 노이즈에서 사용자가 원하는 값을 추출하는 것이 목표!
이러한 작업을 해주는 이미지 필터는 <strong>커널(kernal)</strong>이라는 이름으로 불리기도 함</p>
<h3 id="선형-필터와-비선형-필터">선형 필터와 비선형 필터</h3>
<p><strong>&gt;&gt;</strong> 필터는 이미지를 텐서로 변환하고, 텐서의 좌측 상단부터 우측 하단으로 이동하며 연산을 진행함</p>
<p><strong>선형 필터</strong>
픽셀을 필터 처리할 때 나온 결과 픽셀 값이, 입력으로 들어왔던 이웃 픽셀 값의 가중치 합이 됨
&gt;&gt; 선형 필터는 항상 <strong>고정</strong>되고 <strong>예측 가능</strong>한 방식으로 픽셀 값을 결합함</p>
<p><strong>비선형 필터</strong>
출력이 입력 값의 순위/순서 또는 기타 비선형 연산에 따라 달라짐
&gt;&gt; 고정된 방식으로 값을 결합하는 것이 아니라, 특정 <strong>조건</strong>이나 <strong>규칙</strong>에 따라 <strong>값을 선택</strong>/<strong>변경할 수 있음</strong>
ex) 중앙 값 필터링 기법</p>
<hr>
<h3 id="중앙-값-필터링">중앙 값 필터링</h3>
<p>중앙 값 필터의 핵심은 비선형 디지털 필터링 기법
주요 목적은 <strong>노이즈 감소</strong>, 특히 흰색/검은색 픽셀들인 <strong>소금과 후추</strong> 노이즈를 줄이는 것!
<img src="https://velog.velcdn.com/images/changh2_00/post/12120aa6-9b60-440c-aa73-ec4480ed70fb/image.png" alt=""></p>
<p><strong>&gt;&gt;</strong> 이미지의 각 픽셀에 대해 <strong>주변을 고려</strong>하고 해당 주변에서 <strong>강도 값의 중앙 값을 결정</strong>한 다음, 
&amp;nbsp &amp;nbsp &amp;nbsp &amp;nbsp  해당 <strong>픽셀의 값을 이 중앙 값으로 대체</strong>하는 개념</p>
<pre><code class="language-python">&quot;&quot;&quot;임의의 사진에 노이즈를 첨가&quot;&quot;&quot;
import numpy as np
import cv2

# 소금(흰색) 노이즈 생성 함수
def generate_salt_noise(image):
    num_salt = np.ceil(0.05 * image.size)
    coords = [np.random.randint(0, i - 1, int(num_salt))
              for i in image.shape]
    salted_image = image.copy()
    salted_image[coords[0], coords[1]] = 255
    return salted_image

# 후추(검은색) 노이즈 생성 함수
def generate_pepper_noise(image):
    num_pepper = np.ceil(0.05 * image.size)
    coords = [np.random.randint(0, i - 1, int(num_pepper))
              for i in image.shape]
    peppered_image = image.copy()
    peppered_image[coords[0], coords[1]] = 0
    return peppered_image
</code></pre>
<pre><code class="language-python">&quot;&quot;&quot;노이즈 생성 &amp; 중앙값 필터 적용 후 시각화&quot;&quot;&quot;
!wget https://raw.githubusercontent.com/Cobslab/imageBible/main/image/like_lenna.png
lenna_image = cv2.imread(&#39;like_lenna.png&#39;, cv2.IMREAD_GRAYSCALE)

salted_lenna = generate_salt_noise(lenna_image)
peppered_lenna = generate_pepper_noise(salted_lenna)
filtered_lenna = cv2.medianBlur(peppered_lenna, 5)


fig, axes = plt.subplots(1, 4, figsize=(20, 6))

import matplotlib.pyplot as plt

axes[0].imshow(lenna_image, cmap=&#39;gray&#39;)
axes[0].set_title(&#39;Original Lenna Image&#39;)
axes[0].axis(&#39;off&#39;)

axes[1].imshow(salted_lenna, cmap=&#39;gray&#39;)
axes[1].set_title(&#39;Salted Lenna Image&#39;)
axes[1].axis(&#39;off&#39;)

axes[2].imshow(peppered_lenna, cmap=&#39;gray&#39;)
axes[2].set_title(&#39;Salted &amp; Peppered Lenna&#39;)
axes[2].axis(&#39;off&#39;)

axes[3].imshow(filtered_lenna, cmap=&#39;gray&#39;)
axes[3].set_title(&#39;Median Filtered Lenna&#39;)
axes[3].axis(&#39;off&#39;)

plt.tight_layout()
plt.show()</code></pre>
<p><img src="https://velog.velcdn.com/images/changh2_00/post/b89724bf-71d3-47c2-b5f7-d3e13e0f717f/image.png" alt=""></p>
<blockquote>
<p><strong>&gt;&gt;</strong> 확실히 노이즈가 사라졌지만, 필터링된 이미지는 원본 이미지에 비해 화질이 흐려짐
&amp;nbsp &amp;nbsp &amp;nbsp &amp;nbsp <strong>중앙 값 필터</strong>는 <strong>에지와 가장자리를 보존</strong>함. = *<em>(선명도 유지) *</em>
&amp;nbsp &amp;nbsp &amp;nbsp &amp;nbsp 이는 중앙 값이 평균에 비해 이상치를 처리하는 데 더 우수한 지표이기 때문</p>
</blockquote>
<p>적용 사례
<strong>의료:</strong> 의료 영상에서 가장 중요한 것은 &quot;선명도&quot;이기 때문에 중앙 값 필터링이 도움이 됨
<strong>천체 영상:</strong> 우주 이미지는 우주선 간섭으로 밝은 노이즈가 발생하기에 선명도 손상없이 도움이 됨</p>
<hr>
<h3 id="가우시안-필터링">가우시안 필터링</h3>
<p>가우시안 필터링의 핵심은 이미지에 <strong>블러 효과</strong>를 부여하는 방법!
&#39;균일한 스무딩&#39; 효과를 적용하는 다른 블러링 방법과 달리,
가우시안 필터는 &#39;멀리 있는 픽셀보다 가까운 픽셀을 강조&#39;하여 시각적으로 돋보이게 함</p>
<p><strong>가우스 함수</strong></p>
<p>$$
1차원 ;가우스 함수: \quad G(x) = \frac {1}{\sqrt {2\pi\sigma}}e^{-\frac {x^2}{2\sigma^2}}
$$</p>
<p>$$
2차원 ;가우스 함수: \quad G(x,y) = \frac {1}{\sqrt {2\pi\sigma}}e^{-\frac {x^2+y^2}{2\sigma^2}}
$$</p>
<p>$\sigma$는 <strong>표준편차</strong>이며, 가우스 함수의 <strong>확산 정도를 결정</strong>함.
$\sigma$가 <strong>클수록</strong> 확산 범위가 넓어져 <strong>흐림 효과가 강해지고</strong>, <strong>작을수록</strong> 흐림이 국소적으로 적용돼 <strong>섬세해짐</strong>.</p>
<p>블러링이 유일한 용도는 아님!
    - <strong>노이즈 감소:</strong> 픽셀의 무작위 변동을 흐리게 해 노이즈를 줄일 수 있음
    - <strong>가장자리 감지를 위한 사전 처리:</strong> 에지를 감지하기 전에 가우시안을 적용하면 에지 변화를 부드럽게 함</p>
<blockquote>
<p><strong>뿌옇게 처리하는게 어떻게 노이즈 감소 방법이 되는건지?</strong>
&gt;&gt; &quot;노이즈&quot;란 원래 없어야 할, 갑자기 튀는 이상한 픽셀 값들.
&amp;nbsp &amp;nbsp &amp;nbsp &amp;nbsp &quot;뿌옇게 처리&quot;는 픽셀 값이 주변 환경과 비슷하게 맞춰지는 것.
&amp;nbsp &amp;nbsp &amp;nbsp &amp;nbsp 즉, 가우시안 필터를 적용하면 <strong>주변과 평균</strong>되며 <strong>튀는 값이 완화</strong>됨.</p>
</blockquote>
<pre><code class="language-python">&quot;&quot;&quot;이미지 불러온 후 무작위 노이즈 첨가&quot;&quot;&quot;
import cv2
import numpy as np
import matplotlib.pyplot as plt

image = cv2.imread(&#39;like_lenna.png&#39;, cv2.IMREAD_GRAYSCALE)  # 그레이 스케일 이미지 읽어오기
mean = 0
sigma = 1
gaussian_noise = np.random.normal(mean, sigma, image.shape).astype(&#39;uint8&#39;)
noisy_image = cv2.add(image, gaussian_noise)

plt.imshow(noisy_image, cmap=&#39;gray&#39;)
plt.title(&#39;Noisy Image&#39;)
plt.axis(&#39;off&#39;)
plt.show()</code></pre>
<p><img src="https://velog.velcdn.com/images/changh2_00/post/4c032b71-3815-45d3-b98c-28b0d4f0892e/image.png" alt=""></p>
<pre><code class="language-python">&quot;&quot;&quot;가우시안 필터 적용하여 뿌옇게 처리&quot;&quot;&quot;
sigma_values = [1, 5, 10]
denoised_images = []

for sigma in sigma_values:
    denoised = cv2.GaussianBlur(noisy_image, (0, 0), sigma)  # Kernel size computed from sigma
    denoised_images.append(denoised)

fig, axes = plt.subplots(1, 4, figsize=(20, 10))

axes[0].imshow(noisy_image, cmap=&#39;gray&#39;)
axes[0].set_title(&#39;Noisy Image&#39;)
axes[0].axis(&#39;off&#39;)

for ax, img, sigma in zip(axes[1:], denoised_images, sigma_values):
    ax.imshow(img, cmap=&#39;gray&#39;)
    ax.set_title(f&#39;Denoised (σ={sigma})&#39;)
    ax.axis(&#39;off&#39;)

plt.tight_layout()
plt.show()</code></pre>
<p><img src="https://velog.velcdn.com/images/changh2_00/post/a5372b7d-4d7e-4d0f-ac68-4f55c46f4a7d/image.png" alt=""></p>
<blockquote>
<p>$\sigma$값에 따라 <strong>이미지가 흐려질수록 노이즈는 사라짐</strong>
이미지 사이즈나 품질에 따라 적절한 시그마 값을 적용하여 사용</p>
</blockquote>
<hr>
<h2 id="222-이미지-변환">2.2.2 이미지 변환</h2>
<p>&gt;&gt; 이미지의 시각적 정보를 다른 형태로 재해석하거나 조정하여 특정 목적을 달성하는 것이 목표</p>
<h3 id="아핀-변환">아핀 변환</h3>
<p>라틴어 아피니스(&quot;연결된&quot;)에서 유래
점과 점 사이의 <strong>기본 관계를 변경하지 않고</strong> 개체의 <strong>기하학적 속성을 변경</strong>할 수 있는 변환</p>
<p><strong>특징</strong></p>
<ul>
<li><strong>점과 선의 보존</strong> - 변환 전에 세 점이 같은 선상에 있었다면 변환 후에도 유지됨</li>
<li><strong>평행성</strong> - 변환 전에 평행했던 선은 변환 후에도 유지됨</li>
<li><strong>평면 관계</strong> - 변환 전에 여러 점이 같은 평면에 놓여 있었다면 변환 후에도 유지됨</li>
<li><strong>아핀 변환의 구성</strong> - 아핀 변환을 여러번 적용해도 위 특징은 유지됨</li>
</ul>
<pre><code class="language-python">&quot;&quot;&quot;실습을 위한 원본이미지&quot;&quot;&quot;
import cv2
import numpy as np
import matplotlib.pyplot as plt

image_path = &quot;like_lenna.png&quot;
img = cv2.imread(image_path)
img = cv2.cvtColor(img, cv2.COLOR_BGR2RGB)  # 📌 기본값은 BGR임. RGB로 변환해주기 📌
plt.imshow(img)
plt.title(&quot;Original Image&quot;)
plt.show()</code></pre>
<p><img src="https://velog.velcdn.com/images/changh2_00/post/cb9cfc43-58ea-4156-933a-386df74dcee0/image.png" alt=""></p>
<p><strong>회전 변환</strong>
아핀 변환 중 회전을 진행해보자. 중심을 기준으로 각도를 주어 회전됨.</p>
<p>&quot;원점을 기준으로 회전할때&quot;
$$
회전행렬: \quad
\begin{pmatrix}cos(\theta)&amp;-sin(\theta)\sin(\theta)&amp;cos(\theta)\ \end{pmatrix}\begin{pmatrix}x\y\ \end{pmatrix}
$$
를 바로 적용하고,</p>
<p>&quot;다른 점(c)를 기준으로 회전할때&quot;
$$
x&#39; = x_1 + c_x\
y&#39; = y_1 + c_y
$$
와 같은 평행이동을 해준 후에 위의 &#39;회전행렬&#39;을 적용해 회전시킴</p>
<pre><code class="language-python">def rotate_image(image, angle, center=None):
    rows, cols, _ = image.shape
    if center is None:
        center = (cols // 2, rows // 2)
    M = cv2.getRotationMatrix2D(center, angle, 1)  # 회전행렬을 만들어줌!
    rotated = cv2.warpAffine(image, M, (cols, rows))
    return rotated

rotated_img = rotate_image(img, 45)  # 중심축을 기준으로 45도 회전
plt.imshow(rotated_img)
plt.title(&quot;Rotated Image&quot;)
plt.show()</code></pre>
<p><img src="https://velog.velcdn.com/images/changh2_00/post/30fdc325-b005-4743-865e-3fc9f648926b/image.png" alt=""></p>
<hr>
<h3 id="원근-변환">원근 변환</h3>
<p>원근 변환의 핵심은 <strong>호모그래피 행렬</strong>을 사용하는 것!
호모그래피 행렬은 소스 이미지의 점 4개와 대상 이미지의 점 4개를 사용하여 계산되는데,
이 행렬은 소스 이미지의 각 픽셀에 대해 새로운 좌표를 계산해서 이미지를 변환함.
&gt;&gt; 이 변환으로 이미지의 원근이 수정되어 대상 이미지에 맞게 보이게 됨</p>
<pre><code class="language-python">from matplotlib import pyplot as plt
import cv2
import numpy as np

!wget https://raw.githubusercontent.com/Lilcob/test_colab/main/perspective_test.jpg

# 이미지 로드
image_path = &#39;perspective_test.jpg&#39;
new_source_image = cv2.imread(image_path)

# 이미지에서 타겟의 꼭지점 좌표 지정 (좌표 순서 변경)
ordered_corners = np.array([[57, 630], [936, 330], [1404, 792], [550, 1431]], dtype=&#39;float32&#39;)

# 너비와 높이 계산
ordered_width = int(max(np.linalg.norm(ordered_corners[0] - ordered_corners[1]),
                        np.linalg.norm(ordered_corners[2] - ordered_corners[3])))
ordered_height = int(max(np.linalg.norm(ordered_corners[0] - ordered_corners[3]),
                         np.linalg.norm(ordered_corners[1] - ordered_corners[2])))

# 변환이 될 꼭지점 좌표 지정
ordered_rect_corners = np.array([[0, 0], [ordered_width, 0], [ordered_width, ordered_height], [0, ordered_height]], dtype=&#39;float32&#39;)

# 호모그래피 행렬 계산
ordered_scan_matrix = cv2.getPerspectiveTransform(ordered_corners, ordered_rect_corners)

# 원근 변환 다시 적용
ordered_scanned_image = cv2.warpPerspective(new_source_image, ordered_scan_matrix, (ordered_width, ordered_height))

# 스캔된 이미지 다시 출력
plt.figure(figsize=(10, 5))
plt.subplot(1, 2, 1)
plt.title(&quot;New Source Image&quot;)
plt.imshow(cv2.cvtColor(new_source_image, cv2.COLOR_BGR2RGB))
plt.subplot(1, 2, 2)
plt.title(&quot;Ordered Scanned Image&quot;)
plt.imshow(cv2.cvtColor(ordered_scanned_image, cv2.COLOR_BGR2RGB))
plt.show()</code></pre>
<p><img src="https://velog.velcdn.com/images/changh2_00/post/78ad0a0d-7103-46fc-bccc-5a67bbae9419/image.png" alt=""></p>
<hr>
<h2 id="223-주파수-도메인-기법">2.2.3 주파수 도메인 기법</h2>
<h3 id="푸리에-변환">푸리에 변환</h3>
<p>$$
F(t) = \int_{-\infin}^{\infin}{f(t)e^{-j2\pi f(t)}dt}
$$</p>
<blockquote>
<p>$f(t)$ :  시간 도메인 신호 함수
$e^{-j2\pi f(t)}$ : 복소 지수 함수. 푸리에 변환의 주파수 성분을 분해하는 데 사용
$f$ : 주파수 변수</p>
</blockquote>
<hr>
<h2 id="224-이미지-경계-검출">2.2.4 이미지 경계 검출</h2>
]]></description>
        </item>
        <item>
            <title><![CDATA[[논문리뷰] Attention is All you Need]]></title>
            <link>https://velog.io/@changh2_00/%EB%85%BC%EB%AC%B8%EB%A6%AC%EB%B7%B0-Attention-is-All-you-Need</link>
            <guid>https://velog.io/@changh2_00/%EB%85%BC%EB%AC%B8%EB%A6%AC%EB%B7%B0-Attention-is-All-you-Need</guid>
            <pubDate>Wed, 19 Mar 2025 08:43:45 GMT</pubDate>
            <description><![CDATA[<hr>
<a href = "https://arxiv.org/abs/1706.03762" target="_blank">
Attention is All you Need</a>
논문을 기반으로 작성되었습니다.

<hr>
<h2 id="background-info">[Background Info]</h2>
<h3 id="seq2seq-이란">seq2seq 이란?</h3>
<p>sequence-to-sequence 모델은 <strong>한 시퀀스를 다른 시퀀스로 변환하는 작업을 수행하는 딥러닝 모델</strong>로, 
주로 자연어 처리(NLP) 분야에서 활용되며 Sequence Transduction의 대표 기법이였음
<strong>인코더(Encoder)</strong>와 <strong>디코더(Decoder)</strong>라는 모듈을 갖고있기에 Encoder-Decoder 모델이라고도 부름.
<img src="https://velog.velcdn.com/images/changh2_00/post/1c6c8f89-de71-434d-99e3-e51ca238880c/image.png" alt=""></p>
<ul>
<li><p><strong>인코더</strong></p>
<ul>
<li>일반적으로 RNN, LSTM 등의 순환 신경망 구조를 사용하여 <strong>입력 시퀀스를 고정 길이의 벡터로 변환하는 역할</strong>을 수행함</li>
</ul>
</li>
<li><p><strong>디코더</strong></p>
<ul>
<li><strong>인코더의 출력인 고정 길이의 벡터를 기반으로 원하는 출력 시퀀스를 생성하는 역할</strong>을 수행함</li>
</ul>
</li>
</ul>
<br>

<blockquote>
<p><strong>ex) RNN(LSTM)을 기반으로한 seq2seq의 예시</strong>
<img src="https://velog.velcdn.com/images/changh2_00/post/92e311d5-fc6a-44c6-8ef2-ebf6e3cb8c92/image.png" alt="">
[RNN 기반 seq2seq 모델의 단점]</p>
</blockquote>
<ul>
<li>LSTM이 RNN의 <strong>장기 의존성 문제</strong>(long-term dependency)를 개선시켰지만, 
그럼에도 <strong>초반 입력 정보가 사라지는 문제</strong>(vanishing gradient problem)가 완전히 해결되지 않음</li>
<li>입력 시퀀스를 <strong>순차적으로 처리해야 하므로 병렬 연산이 불가능</strong>함!</li>
</ul>
<p>위의 예시들과 다르게, &quot;Attnetion is All you Need&quot; 논문에서 제시한 <strong>Transformer</strong>는 
인코더와 디코더의 구조에 RNN을 사용하지 않고, (<strong>Self-Attention</strong>과 <strong>FFN</strong>)으로만 이룸으로써
<strong>모든 토큰을 동시에 처리하므로 병렬 연산이 가능</strong>하고, 
<strong>문장 전체에서 중요한 정보를 선택하므로 효과적으로 장기 의존성을 처리</strong>함!</p>
<hr>
<h1 id="1-introduction">1. Introduction</h1>
<ul>
<li>기존의 sequence transduction(seq2seq) 모델들은 RNN 혹은 CNN에 의존함.</li>
<li><strong>RNN 모델은 입력시퀀스의 각 심볼(단어)을 시간 단계에 따라 &quot;하나씩&quot; 처리</strong>함 </li>
<li><em>(sequential computation)*</em> --&gt; 병렬화가 어려움</li>
<li>RNN을 배제하고, Attnetion 메커니즘을 통해 인코더, 디코더를 구성하면 최고의 성능을 낼 수 있음</li>
<li><strong>Attnetion은 입력, 출력 시퀀스에서 위치와 상관없이 정보를 참조</strong>할 수 있도록 설계됨</li>
<li>여러 기계 번역에서 높은 성능을 달성해 *SOTA(State Of The Art)를 갱신함.</li>
</ul>
<p><strong>*</strong> SOTA: 현재까지 가장 뛰어난 성능을 보인 기술 혹은 모델 </p>
<hr>
<h1 id="2-background">2. Background</h1>
<ul>
<li>ByteNet, ConvS2S 같은 기존 연구들이 sequential coputation을 줄여서 모델의 병렬성을 높이고 입력과 출력의 모든 위치에서 병렬 연산을 수행할 수 있도록 했음</li>
<li>하지만 각각의 연구에서 한계가 있었음</li>
<li><strong>Self-Attention</strong>은 <strong>하나의 시퀀스 내에서 서로 다른 위치를 연결하</strong>는 어텐션 메커니즘임</li>
<li>-&gt; 문장 전체의 모든 단어 간 관계를 고려하면서 문장을 표현할 수 있음</li>
<li>이를 이용한 <strong>Transformer</strong>는 기존의 RNN/CNN을 사용한 seq1seq 모델과 달리, <strong>온전히 Self-Attention을 기반으로 동작하는 최초의 모델</strong>임</li>
</ul>
<hr>
<h1 id="3-model-architecture">3. Model Architecture</h1>
<p><img src="https://velog.velcdn.com/images/changh2_00/post/a281429b-47e9-413c-a783-d731f6da2e4e/image.png" alt=""></p>
<p>Transformer는 기존 seq2seq 모델의 <strong>Encoder-Decoder 구조를 따르지만, 내부 구조를 다르게</strong> 구성함
<strong>&gt;&gt;</strong> RNN 구조 없이 <strong>Self-Attention + Fully Connected Layers</strong> 로만 구성됨</p>
<hr>
<h2 id="31-encoder-and-decoder-stacks">3.1 Encoder and Decoder Stacks</h2>
<p><img src="https://velog.velcdn.com/images/changh2_00/post/9ba6727d-eb8f-4278-922d-74b193dfdf12/image.png" alt=""></p>
<p>위 사진과 같은 구조를 갖는데, 인코더와 디코더 모두 총 6개의 동일한 Layer를 갖고, 그 중 하나를 보이면 Figure 1과 같은 구조를 보임
<strong>&gt;&gt;</strong> 동일한 레이어를 반복함으로써 모델의 표현력과 학습 능력을 향상시킬 수 있음</p>
<p><strong>인코더</strong></p>
<ul>
<li>인코더의 각 레이어는 <strong>Multi-Head Self-Attention</strong>과 <strong>FFN</strong>으로 이루어져 있는데, </li>
<li><strong>Residual Connection(잔차 연결)</strong>을 사용하여 각 서브레이어의 출력을 더한 후 <strong>Layer Normalization(레이어 정규화)</strong>를 적용함 --&gt; $LayerNorm(x + Sublayer(x))$</li>
</ul>
<ul>
<li>Resudial Connection을 용이하게 하기 위해 임베딩 레이어와 서브레이어의 출력의 차원은 동일하게 512로 설정</li>
</ul>
<p>*Residual Connection: 입력값과 출력값을 더해 학습을 돕는 기법</p>
<p><strong>디코더</strong></p>
<ul>
<li>대부분 인코더와 동일하지만, 
디코더엔 첫번째 서브레이어로 <strong>Masked Multi-Head Self-Attention</strong>이 있음</li>
<li>이는 미래 단어를 미리 볼 수 없도록 마스킹을 적용한것!</li>
</ul>
<hr>
<h2 id="32-attention">3.2 Attention</h2>
<p><strong>&gt;&gt;</strong> Query와 Key-Value 쌍을 입력으로 받아 출력을 생성하는 함수라고 정의할 수 있음 (모두 벡터값) 
<strong>&gt;&gt;</strong> 즉 , 입력(Query)과 관련성이 높은 Key-Value를 찾아내어, 그 정보를 가중치를 반영한 합으로 출력</p>
<h3 id="321-scaled-dot-product-attention">3.2.1 Scaled Dot-Product Attention</h3>
<p><img src="https://velog.velcdn.com/images/changh2_00/post/903f6aba-032d-423a-a02b-af6f33cde2fb/image.png" alt=""></p>
<p>$$
Attention(Q,K,V) = softmax(\frac {QK^T}{\sqrt d_k} )V
$$</p>
<p>Query와 Key의 내적을 구하여 유사도를 구하고 <strong>(MatMul)</strong>
Gradient Vanishing을 방지하기 위해 스케일링($\sqrt d_k$로 나눠주기) 적용 <strong>(Scale)</strong>
softmax 적용하여 유사도를 확률 분포 형태로 변환 <strong>(SoftMax)</strong>
softmax에서 구한 가중치를 각 Value에 곱해 최종 출력 계산 <strong>(MatMul)</strong></p>
<p>Additive Attention과 Dot-Product Attention 두 종류가 있는데 Transformer에선 Dot-Product Attention을 사용!
<strong>&gt;&gt;</strong> Additive는 *FFN을 사용하여 유사도를 계산하기에, Dot-Product가 훨씬 빠름</p>
<p>*FFN: Feed-Forward Neural Network</p>
<hr>
<h3 id="322-multi-head-attention">3.2.2 Multi-Head Attention</h3>
<p><img src="https://velog.velcdn.com/images/changh2_00/post/cfdf107c-bf13-462b-a49e-ac95ac369711/image.png" alt=""></p>
<p>$$
MultiHead(Q,K,V) = Concat(head_1, ... , head_h)W^O$$
$$
where; head_i = Attention(QW_i^Q, KW_i^K, VW_i^V)
$$</p>
<p>여러 개(8개)의 Attention을 병렬로 수행하여 정보를 더 풍부하게 학습할 수 있도록 함</p>
<p>head는 독립적으로 수행되는 Attention 연산의 단위를 말한다!
각 head 에서는 서로 다른 학습된 가중치 $W_Q, W_K, W_V$ 를 통해 Query, Key, Value를 반환함
h개의 head 출력을 결합(Concatenation)한 후, 최종적으로 하나의 가중치 $W^O$를 적용하여 출력을 생성</p>
<blockquote>
<p><strong>$Q, K, V$ 는 어디서 오는가?</strong></p>
<p>$$
Q=XW_Q,\quad K=XW_K,\quad V=XW_V
$$</p>
</blockquote>
<p>입력인 단어(토큰) 시퀀스는 임베딩 레이어를 거치면 임베딩 벡터로 변환되고, 
Positional Encoding이 더해지면 <strong>입력 벡터 $X$</strong>로 전환됨.</p>
<blockquote>
</blockquote>
<p>이 입력 벡터에 각각의 가중치 행렬  $W_Q, W_K, W_V$ 를 곱하면 그제서야 $Q, K, V$ 가 됨!
(이 작업은 Multi-Head Attention 하단의 Linear 블록에서 이루어짐)</p>
<blockquote>
</blockquote>
<p>$W_Q, W_K, W_V$는 Transformer 모델이 처음 시작될 때 무작위로 초기화 되고, 훈련 과정을 거치면서 자동으로 업데이트 됨</p>
<blockquote>
<p>$Q, K, V$ <strong>각각의 역할</strong></p>
</blockquote>
<ul>
<li>$Q$ = Query: 현재 단어가 &quot;어떤 단어를 참고해야 하는지&quot; 결정하는 역할</li>
<li>$K$ = Key: 각 단어가 자기 자신을 설명하는 정보</li>
<li>$V$ = Value: 최종적으로 참고할 정보<blockquote>
</blockquote>
즉, Query와 Key를 비교해서 &quot;어떤 단어를 참고해야 하는지&quot;를 결정하고
실제 정보는 Value에서 가져오는 것!</li>
</ul>
<hr>
<h3 id="323-applications-of-attention-in-our-model">3.2.3 Applications of Attention in our Model</h3>
<p><strong>1. 인코더-디코더 Attention</strong></p>
<ul>
<li>$Q$값은 디코더의 이전 레이어에서 가져오고, $K, V$값은 인코더의 최종 출력에서 가져옴</li>
<li>디코더가 인코더의 정보를 참고하며 출력 시퀀스를 생성할 수 있도록 함</li>
<li>seq2seq 모델의 context vetor의 역할과 유사함</li>
</ul>
<p><strong>2. 인코더의 Self-Attention</strong></p>
<ul>
<li>$Q, K, V$값 모두 인코더의 이전 레이어에서 가져옴</li>
<li>각 토큰이 인코더 내에서 다른 모든 토큰과 정보를 공유할 수 있도록 함.</li>
<li>입력 문장에서 단어들 간의 관계를 학습</li>
</ul>
<p><strong>3. 디코더의 Self-Attention</strong></p>
<ul>
<li>$Q, K, V$값 모두 디코더의 이전 레이어에서 가져옴</li>
<li>미래 단어를 보지 않기 위해 Masking이 적용됨</li>
</ul>
<hr>
<h2 id="33-position-wise-feed-forward-networks">3.3 Position-wise Feed-Forward Networks</h2>
<p><img src="https://velog.velcdn.com/images/changh2_00/post/5555eda5-3ead-47d9-9a01-43c682fc3957/image.png" alt=""></p>
<p>Self-Attention을 거친 후, 각 단어의 표현을 정제하고 비선형성을 추가하는 역할
선형 변환, ReLU 활성화, 선형 변환으로 이루어짐</p>
<p>$$
FFN(x) = max(0, xW_1 + b1)W_2 + b_2
$$</p>
<p>첫번째 <strong>선형 변환</strong>에서 <strong>더 큰차원으로 변환</strong>하고, 
<strong>ReLU</strong>를 통해 <strong>비선형성을 추가</strong>해 학습 능력을 강화,
두번째 <strong>선형 변환</strong>에서 <strong>다시 원래 차원</strong>으로 축소시킴</p>
<hr>
<p><strong>*전체 스키마*</strong>
<img src="https://velog.velcdn.com/images/changh2_00/post/bdb217db-9f16-4b04-852a-40fb2e33f6a5/image.png" alt=""></p>
<hr>
<h2 id="34-embeddings-and-softmax">3.4 Embeddings and Softmax</h2>
<p>임베딩레이어에서 입력 토큰 시퀀스를 벡터로 <strong>임베딩할때 사용하는 가중치 행렬</strong>과,
최종 출력을 위한 softmax 이전의 <strong>선형 변환에서 쓰이는 가중치 행렬</strong>은 같음. </p>
<p><strong>&gt;&gt;</strong> 즉, <strong>가중치를 공유함(Weight Sharing)</strong>으로써 성능을 올릴 수 있었음</p>
<hr>
<h2 id="35-positional-encoding">3.5 Positional Encoding</h2>
<p><strong>순서 정보를 처리하는 방법</strong>인 Positional Encoding이 왜 필요한가?</p>
<p>기존의 RNN 기반 모델은 입력 토큰들의 순서를 자연스럽게 반영할 수 있었으나,
Self-Attention을 사용해 문장을 한 번에 병렬처리하는 <strong>Transformer는 단어의 순서 정보가 없음</strong></p>
<p><strong>&gt;&gt;</strong> 문맥을 제대로 이해하기 위해 Positional Encoding을 통해 순서를 인식하도록 함!</p>
<p>자세한 내용은 <a href = "https://www.blossominkyung.com/deeplearning/transfomer-positional-encoding" target="_blank">트랜스포머 파헤치기 - Positional Encoding</a> 참고!</p>
<hr>
<h1 id="4-why-self-attention">4. Why Self-Attention</h1>
<p>왜 Self-Attention을 사용하는가?
<strong>&gt;&gt;</strong> <strong>연산 복잡도, 병렬화, 장거리 의존성 학습 능력</strong> 때문</p>
<p><img src="https://velog.velcdn.com/images/changh2_00/post/4018225f-a9bb-4c75-9b8f-2f164139baa9/image.png" alt=""></p>
<ul>
<li>Self-Attention은 문장의 <strong>모든 단어를 한번에 병렬처리하여 학습</strong>하기때문에 Sequential Operations가 O(1)이고, 그렇기에 O(n)인 RNN보다 훨씬 빠르게 학습 가능!</li>
<li>Maximum Path Length란 입력 문장의 단어 간 정보를 전달하는 데 필요한 최단 거리(레이어 수)를 말하는데, Self-Attention은 모든 단어가 한 번에 연결되기에 O(1)이고, <strong>장거리 관계를 쉽게 학습 가능</strong>함</li>
</ul>
<p>게다가, Attention의 head들이 각각
어떤 head는 문법적 관계, 어떤 head는 의미적 관계를 학습하는 것이 관찰됨
<strong>&gt;&gt;</strong> 모델의 해석 가능성을 높임 (Explainable AI)</p>
<hr>
<h1 id="5-training">5. Training</h1>
<h2 id="51-training-data-and-batching">5.1 Training Data and Batching</h2>
<p>WMT 2014 데이터셋 (영어-독일어, 영어-프랑스어) 사용
각각의 훈련 배치는 약 25,000개의 소스 토큰과 25,000개의 타겟 토큰을 포함함</p>
<h2 id="52-hardware-and-schedule">5.2 Hardware and Schedule</h2>
<p>1대의 머신에서 8개의 NVIDIA P100 GPU를 사용해 훈련
Basic Model은 약 12시간 훈련시키고, Big Model 은 약 84시간 훈련시킴</p>
<h2 id="53-optimizer">5.3 Optimizer</h2>
<p>옵티마이저로 학습률 스케줄링(Learning Rate Schedule)을 적용시킨 Adam을 사용함.</p>
<p>$$
lrate = d_{model}^{-0.5}\times min(step_num^{-0.5}, step_num\times warmup_steps^{-1.5})
$$</p>
<p>모델의 차원($d_{model}$=512)을 기반으로 학습률의 기반 크기를 조정
초반($warmup_step$=4000)에는 학습률을 선형 증가시키고 ($step_num\times warmup_steps^{-1.5}$) 
$warmup_steps$ 이후에는 학습률을 역제곱근으로 감소 ($step_num^{-0.5}$)</p>
<h2 id="54-regularization">5.4 Regularization</h2>
<p>과적합 방지/일반화 성능 향상을 위한 정규화로 Dropout과 Label Smoothing 사용</p>
<hr>
<h1 id="6-results">6. Results</h1>
<h2 id="61-machine-translation">6.1 Machine Translation</h2>
<p><img src="https://velog.velcdn.com/images/changh2_00/post/5724c7a9-aed8-4849-bb3a-0a727b80efb3/image.png" alt=""></p>
<p>(*BLEU: 기계 번역 결과와 사람이 직접 번역한 결과가 얼마나 유사한지 비교한 점수)</p>
<p><strong>English-Deutsch</strong> 성적을 보면, 이전 SOTA 모델보다 <em>BLEU 점수가 2.0 이상 높음
*</em>English-French** 성적을 보면, 이전 SOTA 모델과 BLEU가 비슷하거나 높지만, 
훈련 비용이 기존 SOTA 모델 대비 1/4 수준으로 낮음</p>
<p><strong>훈련 비용</strong> =  훈련에 사용된 연산량 = <strong>FLOPs</strong>(Floating Point Operations)
FLOPs = 훈련시간 x GPU 개수 x GPU의 단정밀도 연산량 </p>
<p>모델의 안정성(일반화성능)을 위해 체크포인트 평균(Checkpoint Averaging) 기법을 사용함.
Base 모델은 마지막 5개 체크포인트를, Big 모델은 20개 체크포인트를 평균함</p>
<h2 id="62-model-variations">6.2 Model Variations</h2>
<p><img src="https://velog.velcdn.com/images/changh2_00/post/d5464820-e149-45e3-b5cd-c6bf56993d7f/image.png" alt=""></p>
<p>(A) Attention의 head 개수 변화
(B) Attention의 $d_k$ (Key 차원) 감소
(C) 모델 크기 증가 --&gt; 클수록 성능 좋음
(D) Dropout 적용 여부
(E) Positional Encoding 변경</p>
<pre><code>sinusoidal encoding = sin/cos 기반 
learned positional encoding = 학습된 벡터</code></pre><hr>
<h1 id="7-conclusion">7. Conclusion</h1>
<p><strong>Transformer</strong>는 RNN 구조가 없는, <strong>온전한 Self-Attention를 기반</strong>으로 하는
첫번째 Sequence Transduction 모델임
<strong>병렬 연산</strong>으로 인해 학습 속도가 크게 향상되었고, <strong>번역에서 SOTA</strong>를 달성함</p>
<p><strong>다른 입출력 Modality로 확장</strong>될 수 있음
ex) Vision Transformer</p>
<hr>
]]></description>
        </item>
        <item>
            <title><![CDATA[[논문리뷰] 초분광 이미징 기술동향]]></title>
            <link>https://velog.io/@changh2_00/%EC%B4%88%EB%B6%84%EA%B4%91-%EC%9D%B4%EB%AF%B8%EC%A7%95-%EA%B8%B0%EC%88%A0%EB%8F%99%ED%96%A5</link>
            <guid>https://velog.io/@changh2_00/%EC%B4%88%EB%B6%84%EA%B4%91-%EC%9D%B4%EB%AF%B8%EC%A7%95-%EA%B8%B0%EC%88%A0%EB%8F%99%ED%96%A5</guid>
            <pubDate>Wed, 12 Mar 2025 07:03:42 GMT</pubDate>
            <description><![CDATA[<hr>
<a href = "https://ettrends.etri.re.kr/ettrends/175/0905175008/" target="_blank">
초분광 이미지 기술동향</a>
논문을 기반으로 작성되었습니다.

<hr>
<h1 id="초분광-이미징">초분광 이미징</h1>
<p><strong>= HSI: Hyper-Spectral Imaging</strong>
: 공간 정보에 분광 기술을 더한 것으로, 전자기파의 스펙트럼 밴드에 따른 2차원적인 영상정보를 초분광 큐브 형태로 구성하여 대상체의 상태, 구성, 특징, 변이 등을 도출하는 기술을 의미한다. </p>
<p>주로 적용되는 분야는 헬스케어, 국방, 환경, 식품 및 농업, 머신비전, 자원탐사 등이 있음
이 중 헬스케어 분야가 전체 시장 중 19.2%, 국방 분야가 19.5%의 비중을 차지하여 가장 큰 시장을 형성함.
특히, <strong>헬스케어 분야</strong>는 연평균 성장률 14.1%로 가장 큰 성장세를 보일 것으로 전망됨</p>
<hr>
<h3 id="스펙트럼-밴드">스펙트럼 밴드</h3>
<p>전자기파(빛)를 특정한 파장 범위로 나눈 것을 의미한다.
즉, 빛을 여러 개의 좁은 파장 구간으로 나누어 분석하는 단위</p>
<p><img src="https://velog.velcdn.com/images/changh2_00/post/45c16024-eaeb-4e76-94fe-07c1bbf90267/image.png" alt=""></p>
<p><strong>&gt;&gt;</strong> RGB 영상은 3개의 밴드를 갖고 있다 (Red, Green, Blue) 
<strong>&gt;&gt;</strong> 초분광 영상(HSI)은 수십~수백 개의 밴드를 가지고 있다</p>
<h3 id="초분광-큐브-초분광-데이터">초분광 큐브 (초분광 데이터)</h3>
<p>공간 정보(이미지)와 스펙트럼 정보를 결합한 3차원 데이터 구조.
일반적인 이미지가 2차원의 픽셀 데이터만 포함하는 반면, 
초분광 큐브는 각 픽셀마다 여러 개의 스펙트럼 밴드 정보를 포함한다. --&gt; 3차원 텐서 형태</p>
<p><img src="https://velog.velcdn.com/images/changh2_00/post/9bbd9a43-0209-460b-8ec6-1af747ddd467/image.png" alt=""></p>
<hr>
<h2 id="초분광-이미징-기술의-분류">초분광 이미징 기술의 분류</h2>
<h3 id="데이터-습득-형태에-따른-분류">데이터 습득 형태에 따른 분류</h3>
<p><strong>&gt;&gt;</strong> 데이터 습득형태는 크게 공간스캐닝, 스펙트럼 스캐닝 방식으로 구분</p>
<p><strong>공간 스캐닝</strong>
: 센서가 한 번에 전체 스펙트럼 데이터를 얻고, 공간 정보를 점진적으로 수집하는 방식</p>
<ul>
<li><strong>점 스캐닝(Point-Scanning)</strong><ul>
<li>한 번에 하나의 점(픽셀)의 스펙트럼 데이터를 측정하며, 점을 이동하면서 전체 초분광 데이터(초분광 큐브)를 구축</li>
</ul>
</li>
<li><strong>라인 스캐닝(Line-Scanning)</strong><ul>
<li>한 번에 한 줄(라인)의 스펙트럼 데이터를 측정하며, 줄을 이동하면서 전체 초분광 데이터(초분광 큐브)를 구축</li>
</ul>
</li>
</ul>
<p><strong>스펙트럼 스캐닝</strong>
: 한 번의 촬영으로 이미지의 전체 공간 정보를 한 번에 얻고, 스펙트럼 정보를 수집하는 방식</p>
<ul>
<li><strong>영역 스캐닝(Area-Scanning)</strong><ul>
<li>전체 공간 정보를 한 번에 촬영한 후, 스펙트럼 정보를 각 필터마다 특정 파장대만 걸러서 저장하는 방식<ul>
<li>필터를 바꿔가며(파장을 변화시키며) 초분광 데이터(초분광 큐브)를 구축</li>
</ul>
</li>
</ul>
</li>
<li><strong>스냅샷(Snapshot imaging)</strong><ul>
<li>한 번의 촬영으로 초분광 큐브 전체 데이터를 획득하는 방식<ul>
<li>초분광 카메라 내부에서 특수한 광학 시스템을 사용하여 전체 스펙트럼을 동시에 촬영</li>
</ul>
</li>
</ul>
</li>
</ul>
<p><img src="https://velog.velcdn.com/images/changh2_00/post/5bcdd2e0-efa9-4c33-a502-3946ae1eb3cd/image.png" alt=""></p>
<p><img src="https://velog.velcdn.com/images/changh2_00/post/c986f0b7-a829-405e-a235-690b71db023a/image.png" alt=""></p>
<hr>
<h3 id="파장-대역에-따른-분류">파장 대역에 따른 분류</h3>
<p>측정 스펙트럼 대역은 초분광 카메라의 응용분야를 결정하는 주요한 결정하는 주요한 분류기준이다.</p>
<p>스펙트럼의 범위에 따라 아래와 같이 구분됨
    - UV (200<del>400nm)
    - VIS (400</del>600)
    - NIR (700<del>1,100nm)
    - SWIR (1,100</del>2,500nm)
    - MWIR (2,500~7,000nm)</p>
<p><strong>분야별 사용 대역</strong></p>
<ul>
<li>바이오 분야: UV ~ SWIR 대역 </li>
<li>대기환경 분야: VIS ~ SWIR 대역</li>
<li>국방 분야 : SWIR ~ MWIR 대역 </li>
</ul>
<p><img src="https://velog.velcdn.com/images/changh2_00/post/aa76ded3-b765-48f3-afa5-dac34f55cbba/image.png" alt=""></p>
<hr>
<h3 id="스펙트럼-밴드해상도-수에-따른-분류">스펙트럼 밴드(해상도) 수에 따른 분류</h3>
<p>밴드 수에 따라 아래와 같이 구분된다.</p>
<p><img src="https://velog.velcdn.com/images/changh2_00/post/fe5c384b-0abd-4038-8b68-5f73e4495268/image.png" alt=""></p>
<p>이에 더해, 스펙트럴 해상도를 통해 초분광 이미징을 구분하기도 한다.</p>
<p>다분광 이미징: Δλ / λ ~ 0.01 이하
초분광 이미징: Δλ / λ ~ 0.01
극초분광 이미징: Δλ / λ ~ 0.01 이상</p>
<hr>
<h2 id="초분광-이미징-시스템">초분광 이미징 시스템</h2>
<p>광원, 광학계, 스펙트럼 분산 장치, 이미지 센서로 구성</p>
<p><img src="https://velog.velcdn.com/images/changh2_00/post/3940fc97-7329-428a-ba41-c8c643d059b9/image.png" alt=""></p>
<p><strong>광원</strong>은 실외 환경에서는 자연광을, 실내에서는 별도의 조명을 사용하고, 각 환경에 맞는 조건(기하학적 상태 등)을 고려하여 운용해야 한다.</p>
<p><strong>이미지 센서</strong>는 초분광 이미지 데이터를 얻는 방식에 따라 1D 또는 2D 이미지센서를 사용하고, 광학계 및 측정 스펙트럼 대역에 따라 단일 센서/다중 센서 방식으로 나뉜다</p>
<p><strong>분광 분산 장치</strong>는 모노크로메터 방식과 광학 가변 필터 방식으로 구분된다.</p>
<blockquote>
<p><strong>분광 분산 장치</strong> 
    - 빛을 여러 개의 파장으로 나누는 역할을 하는 장치
    - 초분광 이미징 시스템에서 하나의 광원에서 들어온 빛을 개별적인 스펙트럼 밴드로 분리하는 핵심 요소
    - 이렇게 나뉜 스펙트럼 데이터를 이미지 센서가 감지하여 초분광 이미지를 생성함</p>
</blockquote>
<h3 id="모노크로메터-방식">모노크로메터 방식</h3>
<p>*<em>&gt;&gt; *</em>프리즘, 격자(Grating) 등을 이용</p>
<p><strong>프리즘:</strong> 굴절률에 따른 파장의 진행 속도 차이 및 이동 경로차를 이용하여 분산시키는 소자이다. 주로 가시광(VIS)와 근적외선(NIR) 대역에서 사용됨
<strong>격자 소자(Grating):</strong> 유리 또는 광학 물질 기저에 등간격으로 선이나 일정 패턴을 새겨 넣은 형태로, 빛의 회절과 간섭 현상을 이용하여 입사빔을 파장 별로 분산시키는 기능을 함.</p>
<p>최근엔 프리즘과 격자를 결합한 형태의 Prism-Grating-Prism 소자가 개발됨. 
의료 분야를 비롯한 다양한 분야에서 연구가 진행줌</p>
<h3 id="가변-필터-방식">가변 필터 방식</h3>
<p><strong>&gt;&gt;</strong> 가변 필터는 크게 고정형과 가변형으로 나누어짐</p>
<p><strong>고정형 가변 필터</strong></p>
<ul>
<li><strong>필터 휠 방식</strong><ul>
<li>측정 밴드 수를 늘리기 위해 여러 개의 필터를 Wheel에 쉽게 장착 가능한 장점<ul>
<li>필터 수에 따라 크기가 커짐, 필터 회전에 따른 이미지의 편심 발생이 쉬운 다점</li>
</ul>
</li>
</ul>
</li>
</ul>
<ul>
<li><strong>패브리 페롯 간섭계 필터</strong><ul>
<li>다파장의 빛이 필터에 입사되면 공진층에서 다중간섭현상을 일으키고 그 결과 특정한 파장만을 투과, 나머지는 반사시키는 원리<ul>
<li>이런 간섭계 원리를 이용하여 이미지 센서 픽셀에 서로 다른 길이의 필터를 부착하여 여러 파장의 정보를 한 번에 얻을 수 있는 <strong>스냅샷 방식</strong>의 초분광이미지 카메라가 개발됨</li>
</ul>
</li>
</ul>
</li>
</ul>
<p><strong>가변형 가변 필터</strong></p>
<ul>
<li><p><strong>액상결정 가변 필터</strong></p>
<ul>
<li>Lyot 복굴절 필터 방식을 사용하며 여러 개의 편광자 사이에 두께가 다른 액살 결정 판을 각각 위치시킨 구조<ul>
<li>전류 변화를 통해 액상결정 분자의 방향을 변화시켜 투과 파장을 가변시키는 원리</li>
<li>높은 공간 분해능과 고해상도의 이미징을 얻을 수 있는 장점</li>
<li>느린 파장 튜닝 속도와 필터링 대역폭이 넓다는 단점</li>
</ul>
</li>
</ul>
</li>
<li><p><strong>음향광학 가변 필터</strong></p>
<ul>
<li>압전 트랜스 듀서의 진동을 통해 결정성 물질에 일정 주파수의 섭동을 형성하고 섭동과 입사빔의 광자와의 상호 작용으로 일정한 파장만을 통과하는 원리<ul>
<li>매우 빠른 튜닝 속도, 넓은 시야각 및 미세 파장 변조 가능한 장점</li>
<li>음파의 음향-광학 효과에 의존하기 때문에 영상 품질은 좋지 않은 단점</li>
</ul>
</li>
</ul>
</li>
</ul>
<hr>
<h2 id="초분광-이미징-분석-기술">초분광 이미징 분석 기술</h2>
<h3 id="분광-혼합분석">분광 혼합분석</h3>
<p><strong>분광 혼합:</strong> 초분광 큐브의 공간상의 화소에서 하나 이상의 물질에서 나오는 반사에너지가 정량적으로 혼합되어 나타나는 것.
<strong>&gt;&gt;</strong> 이런 혼합 화소를 분석하는 방법을 분광 혼합분석이라 함</p>
<p><strong>분광 혼합분석:</strong> 각 화소에 포함되어 있는 여러 물질의 고유한 분광반사특성을 이용하여 각 화소를 구성하는 물질의 비율을 분석하는 방법
<strong>&gt;&gt;</strong> 이때 각 화소를 이루고 있는 단일의 순수한 물질을 <strong>엔드멤버</strong>라 하고, 각 화소는 여러 엔드멤버의 구성 비율의 합으로 나타낼 수 있음</p>
<p>화소에 영향을 미치는 각 스펙트럼 성분을 독립적으로 가정할 수 있는가에 따라 선형 분광 혼합분석, 비선형 분광 혼합분석으로 나뉨.</p>
<ul>
<li><p><strong>선형 분광 혼합분석</strong></p>
<ul>
<li>화소값이 엔드멤버의 합으로 표현됨</li>
</ul>
</li>
<li><p><strong>비선형 분광 혼합분석</strong></p>
<ul>
<li>각 엔드멤버의 분광신호가 얽혀있거나 서로 영향을 주어, 화소값이 엔드멤버의 비선형 조합으로 표현됨
  <strong>&gt;&gt;</strong> 엔드멤버의 특성을 도출하는 것은 매우 어려운 문제임 </li>
</ul>
</li>
</ul>
<p>분광 혼합분석의 목적은 혼합모델에서 엔드멤버를 찾는 것!
이는 아래의 과정을 거친다.</p>
<p><strong>1) Dimensionality reduction</strong>
        - 분광영상에서 분광정보를 줄여 원하는 특정정보만을 추출하는 것
        - 비지도학습인 PCA와 LDA 등을 주로 이용</p>
<p><strong>2) Endmember detection</strong>
        - 화소에서 엔드멤버의 종류와 각 엔드멤버의 분광신호를 정의하고 영상에서 엔드멤버를 도출하는 것
        - 동시 알고리즘 / 순차 알고리즘으로 나뉨
        - 동시 알고리즘은 먼저 엔드멤버의 수를 알고 있다고 가정하고 엔드멤버를 도출
        - 순차 알고리즘은 엔드멤버가 순차적인 세트로서 구성되어 있다고 가정하고 엔드멤버를 도출</p>
<p><strong>3) Abundance estimation</strong></p>
<hr>
<h3 id="분류">분류</h3>
<p><strong>=</strong> 각 픽셀을 특정한 클래스로 할당하여 같은 특성을 가진 영역을 구분하는 방법</p>
<p>분류방식은 크게 스펙트럼 분류와 분광공간 분류로 구분할 수 있음</p>
<ul>
<li><strong>스펙트럼 분류</strong><ul>
<li>공간 정보를 고려하지 않고 단순히 각 픽셀의 스펙트럼 정보에 따라 분류</li>
</ul>
</li>
<li><strong>분광공간 분류</strong><ul>
<li>스펙트럼 정보와 공간 정보를 함께 고려하여 분류<ul>
<li>픽셀이 속한 주변의 이웃 픽셀들도 고려하여 더 정확한 분류를 수행</li>
</ul>
</li>
</ul>
</li>
</ul>
<p> 분류 알고리즘은 지도학습 / 비지도학습 / 반지도 접근법으로 구분할 수 있음</p>
<ul>
<li><strong>지도학습</strong><ul>
<li>이미 알려진 훈련 샘플을 이용하여 새로운 입력데이터를 분류함<ul>
<li>대표 알고리즘으론 SVM, ELMs이 있다</li>
</ul>
</li>
</ul>
</li>
<li><strong>비지도학습</strong><ul>
<li>훈련 샘플 없이 픽셀의 스펙트럼 유사성에 의존하여 초분광영상의 물질을 군집화함<ul>
<li>대표 알고리즘으론 SDS, K-means algorithm, SCS이 있다</li>
</ul>
</li>
</ul>
</li>
</ul>
<p>딥러닝의 발전에 따라 신경망 기반 알고리즘에 대한 연구도 크게 증가함</p>
<hr>
<h3 id="밴드-선택">밴드 선택</h3>
<p>분석의 계산 복잡성을 줄이기 위해, 중복 데이터를 제거하거나 목적에 적합한 파장 대역의 데이터만을 선택해 사용하는 등의 <strong>불필요한 이미지 밴드를 제거하는 방식</strong>이 사용된다.
이후 feature 선택 접근법인 &#39;유사도 전파 알고리즘을 통해 잡음이 제거된 데이터로부터&#39; <strong>대표적인 밴드를 선택</strong>한다.</p>
<hr>
<h3 id="압축-및-전송">압축 및 전송</h3>
<p>초분광 큐브는 고용량의 분광 데이터를 생성하므로, 데이터의 압축이 요구된다.
데이터 압축은 크게 무손실 압축 / 손실 압축으로 나누어진다.</p>
<ul>
<li><strong>무손실 압축</strong><ul>
<li>직접적으로 찹축된 데이터에서 원본 영상 그대로를 복원할 수 있음</li>
</ul>
</li>
<li><strong>손실 압축</strong><ul>
<li>원본영상의 근사로만 이루어져 데이터의 손실이 있음</li>
</ul>
</li>
</ul>
<p><strong>&gt;&gt;</strong> 초분광 큐브의 모든 부분이 사용되는 것은 아니기 때문에 <strong>손실 압축</strong>으로도 충분히 데이터 복원이 가능함</p>
<hr>
<h2 id="딥러닝-기반-초분광-이미징-분석-기술">딥러닝 기반 초분광 이미징 분석 기술</h2>
<h3 id="분광공간-분류">분광공간 분류</h3>
<h3 id="해상도-개선">해상도 개선</h3>
<h3 id="분광혼합-분석">분광혼합 분석</h3>
<h3 id="합성-학습데이터-생성">합성 학습데이터 생성</h3>
]]></description>
        </item>
        <item>
            <title><![CDATA[[이미지처리바이블] 1장 기본 개념과 도구]]></title>
            <link>https://velog.io/@changh2_00/%EC%9D%B4%EB%AF%B8%EC%A7%80%EC%B2%98%EB%A6%AC%EB%B0%94%EC%9D%B4%EB%B8%94-1%EC%9E%A5-%EA%B8%B0%EB%B3%B8-%EA%B0%9C%EB%85%90%EA%B3%BC-%EB%8F%84%EA%B5%AC</link>
            <guid>https://velog.io/@changh2_00/%EC%9D%B4%EB%AF%B8%EC%A7%80%EC%B2%98%EB%A6%AC%EB%B0%94%EC%9D%B4%EB%B8%94-1%EC%9E%A5-%EA%B8%B0%EB%B3%B8-%EA%B0%9C%EB%85%90%EA%B3%BC-%EB%8F%84%EA%B5%AC</guid>
            <pubDate>Mon, 10 Mar 2025 08:07:08 GMT</pubDate>
            <description><![CDATA[<hr>
<p>[이미지 처리 바이블] 교재 1장을 기반으로 작성되었습니다.</p>
<hr>
<h1 id="11-이미지-처리와-컴퓨터-비전">1.1 이미지 처리와 컴퓨터 비전</h1>
<h2 id="111-이미지-처리란">1.1.1 이미지 처리란?</h2>
<p>디지털 이미지 처리 
<strong>&gt;&gt;</strong> 수학적 알고리즘/ 계산 기술에 의존 (이미지 향상, 이미지 복원, 특징 추출 등)</p>
<p>디지털 이미지의 구성요소인 <strong>픽셀</strong> 의 역할을 이해하는 것이 중요
<span style='background-color:#dcffe4'>이미지 처리 알고리즘은 <strong>픽셀에서 작동하여</strong> 이미지에서 <strong>중요한 정보를 향상, 변환 또는 추출</strong>할 수 있게 함.</span></p>
<p><img src="https://velog.velcdn.com/images/changh2_00/post/72a84c7a-f864-4a0c-a8b7-6af883545fb5/image.jpg" alt=""></p>
<h3 id="이미지-획득">이미지 획득</h3>
<ul>
<li>카메라. 스캐너 등 장치를 통해 획득한 이미지를 디지털 형식으로 변환, 저장</li>
<li>품질을 최대한 보존하는 것이 중요<h3 id="이미지-개선">이미지 개선</h3>
</li>
<li>이미지의 품질을 향상하는 데 초점을 맞춤</li>
<li>노이즈 제거(noise reduction), 명암 조절(contrast adjustment), 색상 보정(color correction) 등의 작업</li>
<li>사용 기술: 필터링 기법, 히스토그램 평활화(histogram equalized), 샤프닝(sharpening) 등<h3 id="이미지-분석">이미지 분석</h3>
</li>
<li>이미지의 유용한 정보를 추출</li>
<li>특징 추출(feature extraction), 패턴 인식(pattern recognition), 객체 감지(object detection) 등의 작업</li>
<li>사용 기술: 에지 검출(edge detection), 코너 검출(corner detection), 텍스처 분석(texture analysis) 등<h3 id="이미지-해석-및-이해">이미지 해석 및 이해</h3>
</li>
<li>분석 단계에서 얻은 데이터를 이용하여 이미지를 해석하고 이해</li>
<li>이미지 분류(image classification), 이미지 검색(image retrieval), 이미지 분석(image recognition) 등의 작업</li>
<li>사용 기술: 패턴 매칭(pattern matching), 머신러닝, 딥러닝 등</li>
</ul>
<hr>
<h2 id="112-컴퓨터-비전이란">1.1.2 컴퓨터 비전이란?</h2>
<p><span style='background-color:#fff5b1'><strong>컴퓨터 비전:</strong></span> <span style='background-color:#fff5b1'>기계가 시각적 데이터를 이해하고 분석하는 능력을 개발하는 과학 분야</span>
--&gt; 주요 목표는 <strong>컴퓨터가 사람처럼 볼 수 있도록 하는 것!</strong></p>
<h3 id="이미지-처리와-컴퓨터-비전">이미지 처리와 컴퓨터 비전</h3>
<p>이미지처리와 컴퓨터비전은 공통점이 많지만, 그 <strong>목적과 접근 방식에서 중요한 차이점</strong>이 있음.</p>
<ul>
<li><strong>이미지처리</strong> - 주로 디지털 이미지의 향상, 변형, 복원 등에 중점을 둠</li>
<li><strong>컴퓨터비전</strong> - 이미지처리에서 생성된 이미지를 분석하고 해석하는 데 초점을 맞춤 
ex) 객체인식, 패턴분석, 이미지분류</li>
</ul>
<p>두 분야는 함께 작용하여 복잡한 문제를 해결함.</p>
<h3 id="컴퓨터-비전의-정의">컴퓨터 비전의 정의</h3>
<p>넓게 정의하면, <strong>시각 기계의 과학 및 기술</strong>
구체적으론, <strong>이미지에서 제공하는 정보를 추출하는 인공 시스템의 이론과 관련</strong></p>
<p>컴퓨터 비전 시스템은 패턴 인식과 학습 기법에 의존하여 이미지를 해석하는데, 이는 정교한 알고리즘을 필요로 함.
이러한 기술은 낮은 수준, 중간 수준, 높은 수준의 비전 작업으로 분류됨
<img src="https://velog.velcdn.com/images/changh2_00/post/39c8632c-6b6f-4ef9-a64e-a9fb65d3bbf3/image.jpg" alt=""></p>
<p>이러한 기술의 초석은 <span style='background-color:#fff5b1'>이미지에서 가장자리, 모서리 또는 텍스처와 같은 고유한 속성을 추출</span>하는 <span style='background-color:#fff5b1'><strong>특징 추출</strong></span>이다.
이런 <strong>특징</strong>은 패턴 인식이나 물체 감지와 같은 더 높은 수준의 작업을 위한 기초가 됨.</p>
<p><strong>합성공 신경망(CNN)</strong>은 제공된 데이터에서 추출된 특징의 공간적 계층 구조를 자동으로 학습하도록 설계됨.
이미지 분류(image classification), 물체 감지(object detection), 의미적 분할(semantic segmentation)과 같은 작업에서 사용, 입증됨.</p>
<hr>
<h2 id="113-이미지-처리와-컴퓨터-비전의-연관성">1.1.3 이미지 처리와 컴퓨터 비전의 연관성</h2>
<p>이미지 처리와 컴퓨터 비전은 다른 분야이지만 서로 연결되어 있다.
본질적으로 <strong>이미지 처리는 컴퓨터 비전이 구축되는 기반이 됨.</strong></p>
<ul>
<li><strong>이미지 처리</strong> <ul>
<li>&quot;이미지의 내용을 고려하지 않고&quot; <strong>이미지를 조작하고 향상</strong>시킴</li>
<li>노이즈 제거, 향상 및 분할과 같이 이미지 변환에 중점<ul>
<li>이미지 품질을 개선하거나 추가 처리를 위해 이미지를 준비하는 것이 목표</li>
</ul>
</li>
</ul>
</li>
<li><strong>컴퓨터 비전</strong> <ul>
<li>&quot;이미지에서 의미 있는 정보를 추출&quot;하여 <strong>이미지가 나타내는 장면을 해석</strong>함</li>
<li>인간의 시각 능력을 모방하여 디티절 이미지나 동영상에서 높은 수준의 이해를 추출하는 것을 목표로 함<ul>
<li>사물 인식, 분류, 장면에서 사물의 행동을 해석</li>
</ul>
</li>
</ul>
</li>
</ul>
<p><img src="https://velog.velcdn.com/images/changh2_00/post/4e8bbe32-43ec-4f7b-9d23-0c7b5633a9d2/image.jpg" alt=""></p>
<p><strong>&gt;&gt;</strong> 즉, <span style='background-color:#d1ebf9'><strong>이미지 처리</strong></span>와 <span style='background-color:#ffdce0'><strong>컴퓨터 비전</strong></span>은 <span style='background-color:#d1ebf9'><strong>기계가 &#39;볼 수 있도록&#39;</span>하는 과정과, <span style='background-color:#ffdce0'>&#39;봐왔던 것을 재창조&#39;</span>하는 과정</strong>에서 서로 보완해주며 얽혀있는 분야임.</p>
<hr>
<h1 id="12-필요한-도구들">1.2 필요한 도구들</h1>
<h2 id="121-파이썬-핵심-문법">1.2.1 파이썬 핵심 문법</h2>
<p>(생략)</p>
<hr>
<h2 id="122-opencv">1.2.2 OpenCV</h2>
<p><strong>&gt;&gt;</strong> 컴퓨터로 이미지나 영상을 읽고, 이미지의 사이즈 변환이나 회전, 선분 및 도형 그리기, 채널 분리 등의 연산을 처리할 수 있도록 만들어진 오픈 소스 라이브러리</p>
<p>현재 안면 인식, 지문 인식, 객체 검출, 이상 탐지 등 다양한 이미지 처리에서 널리 사용됨</p>
<h3 id="이미지-입출력">이미지 입출력</h3>
<pre><code class="language-bash"># like_lenna.png 다운로드
!wget https://raw.githubusercontent.com/Cobslab/imageBible/main/image/like_lenna224.png -O like_lenna.png</code></pre>
<pre><code class="language-python">import cv2

image = cv2.imread(&#39;like_lenna.png&#39;, cv2.IMREAD_GRAYSCALE)
if image is not None:
    print(&quot;이미지를 읽어왔습니다.&quot;)
else:
    print(&quot;이미지를 읽어오지 못했습니다.&quot;)
print(f&quot;변수 타입: {type(image)}&quot;)</code></pre>
<pre><code>&gt;&gt;&gt; 이미지를 읽어왔습니다.
    변수 타입: &lt;class &#39;numpy.ndarray&#39;&gt;

# 기본 자료형인 리스트가 아닌 Numpy.ndarray를 사용하면 빠른 속도, 적은 메모리 공간의 이점을 가짐</code></pre><pre><code class="language-python"># 코랩에선 OpenCV에서 지원하는 cv2.imshow를 사용할 수 X
# but, 비슷하게 사용 가능한 cv2_imshow 함수 제공
&quot;&quot;&quot;cv2.imshow(image)&quot;&quot;&quot;
from google.colab.patches import cv2_imshow
cv2_imshow(image)</code></pre>
<p><img src="https://velog.velcdn.com/images/changh2_00/post/63c974e1-2733-4a49-aeda-bf6e1aa7712c/image.png" alt=""></p>
<pre><code class="language-python">print(f&quot;이미지 배열의 형태: {image.shape}&quot;)</code></pre>
<pre><code>&gt;&gt;&gt; 이미지 배열의 형태: (224, 224)

# OpenCV는 이미지를 불러와 각 픽셀에 해당하는 수를 (numpy.ndarray)로 변수에 저장해줌
# 즉, 각 픽셀에 접근해서 수정/변환이 가능함</code></pre><h3 id="이미지-변환">이미지 변환</h3>
<p><strong>사이즈 변환</strong> : cv2.resize 함수를 이용해 이미지의 사이즈를 바꿀 수 있음</p>
<p><img src="https://velog.velcdn.com/images/changh2_00/post/385a0d38-4089-4713-b1e7-46b3c15e61ce/image.png" alt=""></p>
<p><img src="https://velog.velcdn.com/images/changh2_00/post/5dcaaa4f-0330-4f99-b3b9-6751082d4e24/image.png" alt=""></p>
<pre><code class="language-python">&quot;&quot;&quot;지정해서 사이즈 변환&quot;&quot;&quot;
image_small = cv2.resize(image,(100,100))
cv2_imshow(image_small)

# (224,224) 사이즈였던 이미지를 (100,100) 사이즈로 수정</code></pre>
<p><img src="https://velog.velcdn.com/images/changh2_00/post/5fe5eb62-1a84-41f3-831c-883c90c71bad/image.png" alt=""></p>
<pre><code class="language-python">&quot;&quot;&quot;배율로 사이즈 변환&quot;&quot;&quot;

image_big = cv2.resize(image,dsize=None,fx=2,fy=2,)
cv2_imshow(image_big)

# 가로, 세로 2배로 수정 </code></pre>
<p><img src="https://velog.velcdn.com/images/changh2_00/post/b78565f1-0b7e-4dba-9134-ac58885f5612/image.png" alt=""></p>
<p><strong>대칭 변환</strong></p>
<pre><code class="language-python">&quot;&quot;&quot;flip 함수에서 0으로 명시하면 수평축으로 반전&quot;&quot;&quot;

image_fliped = cv2.flip(image,0)
cv2_imshow(image_fliped)</code></pre>
<p><img src="https://velog.velcdn.com/images/changh2_00/post/faffed36-5960-4eed-b509-a36404727fcc/image.png" alt=""></p>
<pre><code class="language-python">&quot;&quot;&quot;flip 함수에서 1로 명시하면 세로축으로 반전&quot;&quot;&quot;

image_fliped = cv2.flip(image,1)
cv2_imshow(image_fliped)</code></pre>
<p><img src="https://velog.velcdn.com/images/changh2_00/post/c6d9db0a-036e-4ac7-aee5-9f9528bec9c1/image.png" alt=""></p>
<p><strong>회전 변환</strong>
cv2.getRotationMatrix2D 함수를 사용하여 회전 변환 행렬을 생성하고,
cv2.warpAffine 함수를 사용하여 실제로 이미지를 회전시킴</p>
<p><img src="https://velog.velcdn.com/images/changh2_00/post/7a288c7c-2167-4f19-bf60-e20d3bc8d708/image.png" alt=""></p>
<blockquote>
<p><strong>cv2.getRotationMatrix2D 함수 매개변수</strong>
    - center: 어떤 점을 기준으로 회전시킬지 지정
    - angle: 얼마나 회전시킬지 지정
    - scale: 결과 이미지의 배율을 어떻게 할지 지정</p>
</blockquote>
<pre><code class="language-python">&quot;&quot;&quot;두 함수를 사용하여 회전 변환&quot;&quot;&quot;
height, width = image.shape
matrix = cv2.getRotationMatrix2D((width/2, height/2), 90, 1)
result = cv2.warpAffine(image, matrix, (width, height))
cv2_imshow(result)</code></pre>
<p><img src="https://velog.velcdn.com/images/changh2_00/post/a483782a-0cbb-4b33-b630-0dae5c13aff4/image.png" alt=""></p>
<pre><code class="language-python">&quot;&quot;&quot;다른 각도로 회전&quot;&quot;&quot;
matrix = cv2.getRotationMatrix2D((width/2,height/2),30,1)
result = cv2.warpAffine(image,matrix,(width,height),borderValue=200)
cv2_imshow(result)</code></pre>
<p><img src="https://velog.velcdn.com/images/changh2_00/post/cd85a46d-e693-4cb1-9d1d-aef6ec87d627/image.png" alt=""></p>
<p><strong>자르기</strong>
파이썬 배열에서의 슬라이싱 기능을 사용하여 이미지를 자름</p>
<pre><code class="language-python">cv2_imshow(image[:100,:100])
# 왼쪽 상단을 기준으로 픽셀 단위 가로 100, 세로 100 만큼의 위치를 자름</code></pre>
<p><img src="https://velog.velcdn.com/images/changh2_00/post/8b97411b-a3ba-412a-9fac-40ff4a86d1c4/image.png" alt=""></p>
<pre><code class="language-python">cv2_imshow(image[50:150,50:150])
# 가로, 세로 모두 51번째 픽셀부터 150번째 픽셀까지의 위치를 자름</code></pre>
<p><img src="https://velog.velcdn.com/images/changh2_00/post/6b7b05e4-963d-44be-9a5d-78a8ce8664df/image.png" alt=""></p>
<pre><code class="language-python">croped_image = image[50:150,50:150]
croped_image[:] = 200
cv2_imshow(image)

# 원본 image 객체의 값을 그대로 참조
# croped_image의 모든 픽셀 값을 200(밝은 회색)으로 바꾸면 원본 image에 그대로 적용됨</code></pre>
<p><img src="https://velog.velcdn.com/images/changh2_00/post/2d886c9d-c62d-4697-825c-98c2d8536b83/image.png" alt=""></p>
<pre><code class="language-python"># 위처럼 원본 사진에 영향이 가는걸 원치 않다면,
# 아래와 같이 자른 객체에 대한 깊은 복사(copy)를 하여 사용해야함

image = cv2.imread(&#39;like_lenna.png&#39;, cv2.IMREAD_GRAYSCALE)
croped_image = image[50:150,50:150].copy()
croped_image[:] = 200
cv2_imshow(image)</code></pre>
<p><img src="https://velog.velcdn.com/images/changh2_00/post/9130a3d1-9db2-4714-8e11-65d952b5d531/image.png" alt=""></p>
<h3 id="도형-그리기">도형 그리기</h3>
<p>OpenCV는 읽어온 이미지 위에 원하는 도형을 그릴 수 있는 기능을 제공함</p>
<p><strong>선 그리기 - cv2.line</strong></p>
<p><img src="https://velog.velcdn.com/images/changh2_00/post/ab1c6e73-ab36-47d3-a5ca-8010eb06e712/image.png" alt=""></p>
<pre><code class="language-python">space = np.zeros((500, 1000), dtype=np.uint8)
line_color = 255
space = cv2.line(space, (100, 100), (800, 400), line_color, 3, 1)

cv2_imshow(space)</code></pre>
<p><img src="https://velog.velcdn.com/images/changh2_00/post/cccc4cbb-ce0f-47e0-9641-d63c1cc80ae8/image.png" alt=""></p>
<p><strong>원 그리기 - cv2.circle</strong></p>
<p><img src="https://velog.velcdn.com/images/changh2_00/post/ac56e92b-909c-4d81-bdcd-5424d444a794/image.png" alt=""></p>
<pre><code class="language-python">space = np.zeros((500, 1000), dtype=np.uint8)
color = 255
space = cv2.circle(space, (600, 200), 100, color, 4, 1)

cv2_imshow(space)</code></pre>
<p><img src="https://velog.velcdn.com/images/changh2_00/post/cf627802-b062-4cb8-a371-69200776031a/image.png" alt=""></p>
<p><strong>직사각형 그리기 - cv2.rectangle</strong></p>
<p><img src="https://velog.velcdn.com/images/changh2_00/post/e9049250-a043-4b82-b4d0-a10dbf7bf84c/image.png" alt=""></p>
<pre><code class="language-python">space = np.zeros((768, 1388), dtype=np.uint8)
color = 255
space = cv2.rectangle(space, (500, 200), (800, 400), color, 5, 1)

cv2_imshow(space)</code></pre>
<p><img src="https://velog.velcdn.com/images/changh2_00/post/dd4b55b5-66a7-40ab-8dbc-d36e103a2e36/image.png" alt=""></p>
<p><strong>타원 그리기 - cv2.ellipse</strong></p>
<p><img src="https://velog.velcdn.com/images/changh2_00/post/ef471598-d4bb-4b1d-a52d-5b5f6a37e621/image.png" alt=""></p>
<pre><code class="language-python"># startAngle: 타원을 그리기 시작할 각도
# endAngle: 타원 그리기를 끝낼 각도
space = np.zeros((768, 1388), dtype=np.uint8)
color = 255
space = cv2.ellipse(space, (500, 300), (300, 200), 0, 90, 250, color, 4)

cv2_imshow(space)</code></pre>
<p><img src="https://velog.velcdn.com/images/changh2_00/post/2d0e5774-797d-4879-84d1-2e4e9ce7e4d0/image.png" alt=""></p>
<p><strong>다각형 그리기 - cv2.polylines / cv2.fillPoly</strong></p>
<p><img src="https://velog.velcdn.com/images/changh2_00/post/1b9f90b6-0c29-4267-9053-f80bc41bc588/image.png" alt=""></p>
<pre><code class="language-python"># cv2.polylines 함수는 [[300, 500], [500, 500], [400, 600], [200, 600]] 위치의 평행사변형을 그려줌
# cv2.fillPoly 함수는 [[600, 500], [800, 500], [700, 200]] 위치의 삼각형을 그려줌
space = np.zeros((768, 1388), dtype=np.uint8)
color = 255
obj1 = np.array([[300, 500], [500, 500], [400, 600], [200, 600]])
obj2 = np.array([[600, 500], [800, 500], [700, 200]])
space = cv2.polylines(space, [obj1], True, color, 3)
space = cv2.fillPoly(space, [obj2], color, 1)

cv2_imshow(space)</code></pre>
<p><img src="https://velog.velcdn.com/images/changh2_00/post/8a266300-c215-4310-96b1-c3ba4eb37724/image.png" alt=""></p>
<hr>
<h2 id="123-텐서플로tensorflow">1.2.3 텐서플로(Tensorflow)</h2>
<p>구글 브레인 팀에서 개발되어 2015년에 공개된 오픈 소스 머신러닝 라이브러리.
고수준 API인 <strong>케라스(Keras)</strong>를 지원</p>
<p><img src="https://velog.velcdn.com/images/changh2_00/post/a5796796-a33f-4a87-9c37-67e5a943825e/image.png" alt=""></p>
<p><img src="https://velog.velcdn.com/images/changh2_00/post/c98c2e51-07fb-420b-806a-d079602e6e77/image.png" alt=""></p>
]]></description>
        </item>
        <item>
            <title><![CDATA[[혼공머신] 6-1장 군집 알고리즘]]></title>
            <link>https://velog.io/@changh2_00/6-1%EC%9E%A5-%EA%B5%B0%EC%A7%91-%EC%95%8C%EA%B3%A0%EB%A6%AC%EC%A6%98</link>
            <guid>https://velog.io/@changh2_00/6-1%EC%9E%A5-%EA%B5%B0%EC%A7%91-%EC%95%8C%EA%B3%A0%EB%A6%AC%EC%A6%98</guid>
            <pubDate>Tue, 19 Nov 2024 07:14:31 GMT</pubDate>
            <description><![CDATA[<hr>
<p>[혼자 공부하는 머신러닝+딥러닝] 교재 6장을 기반으로 작성되었습니다.</p>
<hr>
<p>새로운 이벤트를 기획하고 있다.
고객이 사고 싶은 과일 사진을 보내면 그중 많이 요청하는 과일을 판매 품목으로 선정하려한다.
그런데 이전 이벤트의 생선처럼 미리 과일 분류기를 훈련하기에는 
<strong>고객들이 어떤 과일 사진을 보낼지 알 수 없으니</strong> 곤란하다... </p>
<p><strong>사진에 대한 정답(타깃)을 알지 못하는데 어떻게 사진을 각 과일이라 분류할 수 있을까?</strong></p>
<hr>
<h2 id="타깃을-모르는-비지도-학습">타깃을 모르는 비지도 학습</h2>
<p>이렇게 <span style='background-color:#fff5b1'><strong>타깃이 없을 때</strong> 사용하는 머신러닝 알고리즘</span>이 바로 <span style='background-color:#fff5b1'><strong>비지도 학습</strong></span>이다.
사람이 가르쳐 주지 않아도 데이터에 있는 무언가를 학습하는 것이다.</p>
<p>그럼 어떻게 해야하는걸까?
<strong>사진의 픽셀값을 모두 평균 내면 비슷한 과일끼리 모이지 않을까?</strong></p>
<p>이런 아이디어를 확인하기 위해 먼저 데이터를 준비하고 픽셀값을 이용해서 사진을 분류해보자.</p>
<hr>
<h2 id="과일-사진-데이터-준비하기">과일 사진 데이터 준비하기</h2>
<p>캐글에 공개된 <strong><a href="https://www.kaggle.com/moltean/fruits">과일 데이터셋</a></strong>을 사용한다.</p>
<pre><code class="language-python">&quot;&quot;&quot;📌 데이터 파일 다운로드&quot;&quot;&quot;
!wget https://bit.ly/fruits_300_data -O fruits_300.npy

# 코랩의 코드 셀에서 ! 문자로 시작하면 이후 명령을 리눅스 shell 명령으로 받아들인다.
# wget 명령은 원격 주소에서 데이터를 다운로드하여 저장한다.
# -O 옵션에서 저장할 파일 이름을 지정한다.

&quot;&quot;&quot;📌 데이터 로드&quot;&quot;&quot;
import numpy as np
import matplotlib.pyplot as plt
fruits = np.load(&#39;fruits_300.npy&#39;)

&quot;&quot;&quot;📌 데이터 배열의 크기 확인&quot;&quot;&quot;
print(fruits.shape)</code></pre>
<pre><code>&gt;&gt;&gt; (300, 100, 100)

# 첫번째 차원(300)은 샘플의 개수를 나타내고, 두,세번째 차원은 각각 이미지의 높이, 너비를 나타낸다. (🐱)</code></pre><p><img src="https://velog.velcdn.com/images/changh2_00/post/f3786b4b-aac3-4566-9a53-c4d7806c0f1a/image.png" alt=""></p>
<pre><code class="language-python">&quot;&quot;&quot;📌 첫번째 이미지의 첫번째 행의 픽셀 100개 값 출력&quot;&quot;&quot;
print(fruits[0, 0, :])</code></pre>
<pre><code>&gt;&gt;&gt; [  1   1   1   1   1   1   1   1   1   1   1   1   1   1   1   1   2   1
       2   2   2   2   2   2   1   1   1   1   1   1   1   1   2   3   2   1
       2   1   1   1   1   2   1   3   2   1   3   1   4   1   2   5   5   5
      19 148 192 117  28   1   1   2   1   4   1   1   3   1   1   1   1   1
       2   2   1   1   1   1   1   1   1   1   1   1   1   1   1   1   1   1
       1   1   1   1   1   1   1   1   1   1]

# 이 넘파이 배열은 흑백 사진을 담고 있으므로 0~255까지의 정수값을 가진다.</code></pre><pre><code class="language-python">&quot;&quot;&quot;📌 첫번째 이미지 출력&quot;&quot;&quot;
plt.imshow(fruits[0], cmap=&#39;gray&#39;)  # 흑백 이미지이므로 cmap 매개변수를 gray로 설정
plt.show()</code></pre>
<p><img src="https://velog.velcdn.com/images/changh2_00/post/419afba7-6d4d-4e4b-b0f7-7709d4e56146/image.jpeg" alt=""></p>
<p><img src="https://velog.velcdn.com/images/changh2_00/post/bd39d01f-f5b5-4ee8-b6ed-ff1947e5c516/image.png" alt=""></p>
<pre><code>첫번째 이미지는 사과인 듯하다. 그림의 첫번째 행이 위에서 출력한 배열 값인데.
현재는 0에 가까울수록 검게 나타나고 높은 값은 밝게 표시되므로 잘 해당되는걸 볼 수 있다.</code></pre><p>근데 흑백이미지가 왜 반전되어서 나올까?</p>
<p><img src="https://velog.velcdn.com/images/changh2_00/post/facacb97-8513-4043-b004-be16b7c844bd/image.png" alt=""></p>
<p>사실 이 이미지는 넘파이 배열로 변환할 때 반전된 것이다!
이렇게 바꾼 이유는 컴퓨터가 255에 가까운 값에 집중하기 때문인데, 
원래의 이미지대로라면 밝은 값인 &#39;바탕&#39;에 집중할 테니 반전하여 저장한 것.</p>
<p>우리의 관심 대상인 사과가 잘 보이도록 다시 반전시키자! (픽셀의 값이 반대로 됨)</p>
<pre><code class="language-python">&quot;&quot;&quot;📌 반전하여 출력&quot;&quot;&quot;
plt.imshow(fruits[0], cmap=&#39;gray_r&#39;)
plt.show()</code></pre>
<p><img src="https://velog.velcdn.com/images/changh2_00/post/244977ce-b156-47fa-a5fa-ecd01b91f957/image.jpeg" alt=""></p>
<pre><code class="language-python">&quot;&quot;&quot;📌 파인애플과 바나나도 출력&quot;&quot;&quot;
# 이 데이터는 사과, 바나나, 파인애플이 각각 100개씩 들어있다.
fig, axs = plt.subplots(1, 2)
axs[0].imshow(fruits[100], cmap=&#39;gray_r&#39;)
axs[1].imshow(fruits[200], cmap=&#39;gray_r&#39;)
plt.show()</code></pre>
<p><img src="https://velog.velcdn.com/images/changh2_00/post/9b57e047-2f7e-491c-8145-f08c1bf16063/image.jpeg" alt=""></p>
<hr>
<h2 id="픽셀값-분석하기">픽셀값 분석하기</h2>
<p>사용하기 쉽게 fruits 데이터를 사과, 파인애플, 바나나로 각각 나누자.
배열을 계산하기 편하게 하기 위해, 100x100 이미지를 펼쳐 길이가 10,000인 1차원 배열로 만들자.
<img src="https://velog.velcdn.com/images/changh2_00/post/742bcbb3-93ea-45e7-9b60-e1e5c79314d7/image.png" alt=""></p>
<pre><code class="language-python">&quot;&quot;&quot;📌 각 과일로 나누고 1차원 배열로 변환&quot;&quot;&quot;
# 첫번째 차원을 -1로 지정하면 자동으로 남은 차원을 할당한다
apple = fruits[0:100].reshape(-1, 100*100)
pineapple = fruits[100:200].reshape(-1, 100*100)
banana = fruits[200:300].reshape(-1, 100*100)
print(apple.shape)</code></pre>
<pre><code>&gt;&gt;&gt; (100, 10000)</code></pre><p>이제 apple, pineapple, banana 각 배열에 들어있는 샘플의 픽셀 평균값을 구해보자.</p>
<pre><code class="language-python">&quot;&quot;&quot;📌 사과의 픽셀 평균값 출력&quot;&quot;&quot;
print(apple.mean(axis=1))</code></pre>
<pre><code>&gt;&gt;&gt; [ 88.3346  97.9249  87.3709  98.3703  92.8705  82.6439  94.4244  95.5999
      90.681   81.6226  87.0578  95.0745  93.8416  87.017   97.5078  87.2019
      88.9827 100.9158  92.7823 100.9184 104.9854  88.674   99.5643  97.2495
      94.1179  92.1935  95.1671  93.3322 102.8967  94.6695  90.5285  89.0744
      97.7641  97.2938 100.7564  90.5236 100.2542  85.8452  96.4615  97.1492
      90.711  102.3193  87.1629  89.8751  86.7327  86.3991  95.2865  89.1709
      96.8163  91.6604  96.1065  99.6829  94.9718  87.4812  89.2596  89.5268
      93.799   97.3983  87.151   97.825  103.22    94.4239  83.6657  83.5159
     102.8453  87.0379  91.2742 100.4848  93.8388  90.8568  97.4616  97.5022
      82.446   87.1789  96.9206  90.3135  90.565   97.6538  98.0919  93.6252
      87.3867  84.7073  89.1135  86.7646  88.7301  86.643   96.7323  97.2604
      81.9424  87.1687  97.2066  83.4712  95.9781  91.8096  98.4086 100.7823
     101.556  100.7027  91.6098  88.8976]</code></pre><blockquote>
<h3 id="axis란">axis란?</h3>
<p><img src="https://velog.velcdn.com/images/changh2_00/post/338eaba2-ea4c-4bc0-bdd6-812a8ea49afc/image.png" alt=""></p>
</blockquote>
<p>axis란 <strong>&quot;축&quot;</strong>을 말하는데, 0은 &#39;행&#39;을 따라 계산하는 걸 말하고, 1은 &#39;열&#39;을 따라 계산함을 말한다.
우리의 데이터 샘플은 모두 가로로 값을 나열했으니 axis=1로 지정하여 계산해야한다.</p>
<p>각 과일별로 히스토그램을 그려서 평균값이 어떻게 분포되어 있는지 한눈에 볼 수 있다.</p>
<pre><code class="language-python">&quot;&quot;&quot;📌 각 과일별 평균값 히스토그램 출력&quot;&quot;&quot;
plt.hist(np.mean(apple, axis=1), alpha=0.8)  # alpha 매개변수를 1보다 작게 하여 투명도를 줌
plt.hist(np.mean(pineapple, axis=1), alpha=0.8)
plt.hist(np.mean(banana, axis=1), alpha=0.8)
plt.legend([&#39;apple&#39;, &#39;pineapple&#39;, &#39;banana&#39;])  # 범례를 만듬
plt.show()</code></pre>
<p><img src="https://velog.velcdn.com/images/changh2_00/post/570c3096-1c04-472d-8f6a-f7fdacf5a2b7/image.jpeg" alt=""></p>
<p>바나나는 샘플의 평균값으로 잘 구분되나, 파인애플과 바나나는 비슷하여 <strong>구분이 어렵다...</strong>
샘플의 평균값이 아니라, <strong>픽셀별 평균값</strong>으로 비교하면 어떨까??</p>
<pre><code class="language-python">&quot;&quot;&quot;📌 각 과일의 픽셀별 평균값 히스토그램 출력&quot;&quot;&quot;
fig, axs = plt.subplots(1, 3, figsize=(20, 5))
axs[0].bar(range(10000), np.mean(apple, axis=0))
axs[1].bar(range(10000), np.mean(pineapple, axis=0))
axs[2].bar(range(10000), np.mean(banana, axis=0))
plt.show()</code></pre>
<p><img src="https://velog.velcdn.com/images/changh2_00/post/2ef1c8a7-98b3-423f-bdc8-ece2d9a9b069/image.png" alt=""></p>
<p>순서대로 사과, 파인애플, 바나나의 그래프인데, <strong>과일마다 값이 높은 구간이 다른 것</strong>을 볼 수 있다.
이 픽셀 평균값을 100x100 크기로 바꿔서 이미지처럼 출력하여 위 그래프와 비교하면 더 좋다.</p>
<pre><code class="language-python">&quot;&quot;&quot;📌 각 픽셀 평균값을 이미지처럼 출력&quot;&quot;&quot;
apple_mean = np.mean(apple, axis=0).reshape(100, 100)
pineapple_mean = np.mean(pineapple, axis=0).reshape(100, 100)
banana_mean = np.mean(banana, axis=0).reshape(100, 100)

fig, axs = plt.subplots(1, 3, figsize=(20, 5))
axs[0].imshow(apple_mean, cmap=&#39;gray_r&#39;)
axs[1].imshow(pineapple_mean, cmap=&#39;gray_r&#39;)
axs[2].imshow(banana_mean, cmap=&#39;gray_r&#39;)
plt.show()</code></pre>
<p><img src="https://velog.velcdn.com/images/changh2_00/post/8bb290f7-9c6b-4f2d-b540-34f76883cfc5/image.png" alt=""></p>
<p>세 과일이 픽셀 위치에 따라 값의 크기가 차이 난다.
그러므로 위의 이미지와 가까운 사진을 골라내면 각 과일을 구분할 수 있을 것 같다!</p>
<hr>
<h2 id="평균값과-가까운-사진-고르기">평균값과 가까운 사진 고르기</h2>
<p>사과 사진의 평균값인 apple_mean과 가장 가까운 사진을 골라보자.</p>
<pre><code class="language-python">&quot;&quot;&quot;📌 각 샘플의 오차 평균 abs_mean 계산&quot;&quot;&quot;
abs_diff = np.abs(fruits - apple_mean)  # (300,100,100) 크기의 배열
    # 절대값 오차를 사용하여, fruits 배열에 있는 모든 샘플에서 apple_mean을 뺀 절댓값의 평균을 계산한다.

abs_mean = np.mean(abs_diff, axis=(1,2))  # (300,) 크기의 1차원 배열
    # 각 샘플에 대한 평균을 구하기 위해 axis에 두번째, 세번째 차원을 모두 지정

print(abs_mean.shape)                                                  </code></pre>
<pre><code>&gt;&gt;&gt; (300,)</code></pre><pre><code class="language-python">&quot;&quot;&quot;📌 오차 평균이 작은 순서대로 100개를 출력&quot;&quot;&quot;
apple_index = np.argsort(abs_mean)[:100]  # 오름차순으로 나열한 abs_mean 배열의 인덱스 반환
fig, axs = plt.subplots(10, 10, figsize=(10,10))
for i in range(10):
    for j in range(10):
        axs[i, j].imshow(fruits[apple_index[i*10 + j]], cmap=&#39;gray_r&#39;)
        axs[i, j].axis(&#39;off&#39;)
plt.show()</code></pre>
<p><img src="https://velog.velcdn.com/images/changh2_00/post/b2c9c5ec-9421-411d-8cec-ea8c8ea204c8/image.jpeg" alt=""></p>
<p>100개 모두 사과이다!</p>
<p>이렇게 <span style='background-color: #fff5b1'>비슷한 샘플끼리 그룹으로 모으는 작업</span>을 <span style='background-color: #fff5b1'><strong>군집(clustering)</strong></span>이라 하고,
이 군집은 대표적인 <strong>비지도 학습 작업 중 하나</strong>이다.
또한, 군집 알고리즘에서 만든 그룹을 <strong>클러스터(cluster)</strong>라고 부른다.</p>
<hr>
<p>하지만 우린 이미 사과, 파인애플, 바나나가 있다는 것을 알고 있었는데, 
이말은 사실 타깃값을 알고있었다는 것이다.</p>
<p>실제 상황에선 이렇게 타깃값을 알 수 없는데, 이를 모르면서 어떻게 세 과일의 평균값을 찾을 수 있을까?
다음 6-2장의 k-평균 알고리즘이 이를 해결해준다!</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[[혼공머신] 5-3장 트리의 앙상블]]></title>
            <link>https://velog.io/@changh2_00/%ED%98%BC%EA%B3%B5%EB%A8%B8%EC%8B%A0-5-3%EC%9E%A5-%ED%8A%B8%EB%A6%AC%EC%9D%98-%EC%95%99%EC%83%81%EB%B8%94</link>
            <guid>https://velog.io/@changh2_00/%ED%98%BC%EA%B3%B5%EB%A8%B8%EC%8B%A0-5-3%EC%9E%A5-%ED%8A%B8%EB%A6%AC%EC%9D%98-%EC%95%99%EC%83%81%EB%B8%94</guid>
            <pubDate>Mon, 18 Nov 2024 10:16:03 GMT</pubDate>
            <description><![CDATA[<hr>
<p>[혼자 공부하는 머신러닝+딥러닝] 교재 5장을 기반으로 작성되었습니다.</p>
<hr>
<p>지금까지 여러가지 머신러닝 알고리즘을 배웠지만,
<span style='background-color: #fff5b1'><strong>대체적으로 가장 성능이 좋은 알고리즘</strong></span>은 무엇일까?</p>
<pre><code>+) 물론, 문제마다 각 알고리즘의 성능이 다르다. &quot;대체적으로&quot; 의 얘기이다.</code></pre><hr>
<h1 id="정형-데이터와-비정형-데이터">정형 데이터와 비정형 데이터</h1>
<p><img src="https://velog.velcdn.com/images/changh2_00/post/06227fc7-b027-4feb-821c-3b5546496970/image.png" alt=""></p>
<p>지금까지 우리가 사용한 CSV파일과 같이, 
어떤 구조로 가지런히 정리되어 있는 형태의 데이터를 <strong>정형 데이터</strong>라고 한다.</p>
<p>이와 반대로 사진, 음악 등 정갈한 데이터베이스로 표현하기 어려운 데이터를 <strong>비정형 데이터</strong>라고 한다.</p>
<p>위의 <span style='background-color: #fff5b1'><strong>정형 데이터</strong>를 다루는 데 가장 뛰어난 성과를 내는 알고리즘</span>이 <span style='background-color: #fff5b1'><strong>앙상블 학습</strong></span>이다.</p>
<blockquote>
<h3 id="앙상블-학습-ensemble-learning">앙상블 학습 (Ensemble Learning)</h3>
<p><img src="https://velog.velcdn.com/images/changh2_00/post/2be52e87-4bba-41f5-8491-cde13b3130c8/image.jpeg" alt=""></p>
</blockquote>
<p>앙상블 학습은 여러 개의 개별 모델을 조합하여 최적의 모델로 일반화하는 방법이다.
위의 사진은 회귀문제의 예시인데, weak classifier들을 결합하여 strong classifier을 만든다.
결정트리에서 과대적합되는 문제를 감소시킨다는 장점이 있다.</p>
<p>그럼 비정형 데이터는 어떤 알고리즘을 사용해야할까?
==&gt; <strong>신경망 알고리즘</strong> (7장에서 배울것)</p>
<hr>
<h1 id="랜덤-포레스트">랜덤 포레스트</h1>
<p><span style='background-color: #dcffe4'><strong>랜덤 포레스트</strong></span>는 <span style='background-color: #dcffe4'>앙상블 학습의 대표 주자 중 하나로, 안정적인 성능을 갖는다.</span>
<img src="https://velog.velcdn.com/images/changh2_00/post/c558c1e5-53e7-43a1-a655-e93caba0b282/image.png" alt=""></p>
<p>이름에서 알 수 있듯이, 랜덤 포레스트는 결정 트리를 랜덤하게 만들어 결정 트리의 숲을 만든다. 
그리고 각 결정 트리의 예측을 사용해 최종 예측을 만든다.
<span style='background-color: #f5f0ff'>여기서 <strong>각 트리를 훈련하기 위한 데이터를 랜덤하게</strong> 만드는데, <strong>부트스트랩 방식</strong>을 사용한다.</span>
<img src="https://velog.velcdn.com/images/changh2_00/post/2a39484c-0604-4fba-95de-91a868fec15d/image.png" alt=""></p>
<blockquote>
<h3 id="부트스트랩">부트스트랩</h3>
<p>데이터 세트에서 중복을 허용하여 데이터를 샘플링하는 방식. (복원 추출)
ex) 1,000개의 샘플이 들어있는 가방에서 100개의 샘플을 뽑는다면, 
먼저 1개를 뽑고, 뽑았던 1개를 다시 가방에 넣는다. 이 과정을 100번 반복.
이렇게 만들어진 샘플을 <strong>부트스트랩 샘플</strong>이라 한다.</p>
</blockquote>
<p><span style='background-color: #f5f0ff'>또한 각 노드를 분할할 때 전체 특성 중에서 <strong>일부 특성을 무작위로 고른 다음,
이 중에서 최선의 분할을 찾는다.</strong></span></p>
<p><strong>분류 모델</strong>인 <code>RandomForestClassifier</code> 는 (전체 특성 개수의 제곱근)만큼 특성을 선택하고,
<strong>회귀 모델</strong>인 <code>RandomForestRegressor</code> 는 전체 특성을 모두 사용한다.</p>
<p><img src="https://velog.velcdn.com/images/changh2_00/post/b01a22ed-2497-4b3c-8ef0-f8e6c09b9b2c/image.png" alt=""></p>
<p>사이킷런의 랜덤 포레스트는 기본적으로 이런 방식으로 100개의 결정 트리를 훈련하는데,
<strong>분류</strong>일 때는 각 트리의 클래스별 확률을 평균하여 가장 높은 확률을 가진 클래스를 예측으로 삼고,
<strong>회귀</strong>일 때는 단순히 각 트리의 예측을 평균한다.</p>
<p>이제 <code>RandomForestClassifier</code> 클래스를 화이트 와인을 분류하는 문제에 적용해보자.</p>
<pre><code class="language-python"># 📌 데이터 준비 
import numpy as np
import pandas as pd
from sklearn.model_selection import train_test_split
wine = pd.read_csv(&#39;https://bit.ly/wine_csv_data&#39;)
data = wine[[&#39;alcohol&#39;, &#39;sugar&#39;, &#39;pH&#39;]].to_numpy()
target = wine[&#39;class&#39;].to_numpy()
train_input, test_input, train_target, test_target = train_test_split(data, target, test_size=0.2, random_state=42)

# 📌 랜덤 포레스트의 교차검증 점수
from sklearn.model_selection import cross_validate
from sklearn.ensemble import RandomForestClassifier
rf = RandomForestClassifier(n_jobs=-1, random_state=42)
scores = cross_validate(rf, train_input, train_target, return_train_score=True, n_jobs=-1)
print(np.mean(scores[&#39;train_score&#39;]), np.mean(scores[&#39;test_score&#39;]))
# &gt;&gt;&gt; 0.9973541965122431 0.8905151032797809</code></pre>
<pre><code class="language-python"># 📌 특성 중요도 출력
&#39;&#39;&#39;랜덤 포레스트의 특성 중요도 = 각 결정트리의 특성 중요도의 취합&#39;&#39;&#39;
rf.fit(train_input, train_target)
print(rf.feature_importances_)</code></pre>
<pre><code>&gt;&gt;&gt; [0.23167441 0.50039841 0.26792718]</code></pre><pre><code>결정트리에서 얻은 특성 중요도는
[0.12345626 0.86862934 0.26792718] 였는데,
랜덤 포레스트가 특성의 일부를 랜덤하게 선택하여 결정트리를 훈련하기 때문에
하나의 특성에 과도하게 집중하지 않고 좀 더 많은 특성이 훈련에 기여하는 걸 볼 수 있다.
이는 *과대적합을 줄이고 일반화 성능을 높인다.*</code></pre><pre><code class="language-python"># 📌 OOB 점수 출력
&#39;&#39;&#39;OOB(Out_Of_Bag)샘플: 부트스트랩 샘플에 포함되지 않고 남는 샘플 &#39;&#39;&#39;
&#39;&#39;&#39;OOB점수는 교차검증과 비슷한 역할을 할 수 있다!&#39;&#39;&#39;
rf = RandomForestClassifier(oob_score=True, n_jobs=-1, random_state=42)
rf.fit(train_input, train_target)
print(rf.oob_score_)
# &gt;&gt;&gt; 0.8934000384837406
    # 교차검증에서 얻은 점수와 비슷한 결과!</code></pre>
<hr>
<h1 id="엑스트라-트리">엑스트라 트리</h1>
<p><span style='background-color: #dcffe4'><strong>엑스트라 트리</strong></span>도 <span style='background-color: #dcffe4'>앙상블 기법 중 하나로, 랜덤 포레스트와 매우 비슷하게 동작한다.</span> </p>
<p>랜덤 포레스트와 엑스트라 트리의 차이점은 아래와 같다.</p>
<p><span style='background-color: #f5f0ff'><strong>부트스트랩 샘플을 사용하지 않는다.</strong></span> 즉, 각 결정 트리를 만들 때 전체 훈련 세트를 사용한다.
<span style='background-color: #f5f0ff'>또한, <strong>노드를 분할할 때 가장 좋은 분할을 찾지 않고 무작위로 분할한다.</strong></span></p>
<pre><code class="language-python"># 📌 엑스트라 트리의 교차검증 점수
from sklearn.ensemble import ExtraTreesClassifier
et = ExtraTreesClassifier(n_jobs=-1, random_state=42)
scores = cross_validate(et, train_input, train_target, return_train_score=True, n_jobs=-1)
print(np.mean(scores[&#39;train_score&#39;]), np.mean(scores[&#39;test_score&#39;]))
# &gt;&gt;&gt; 0.9974503966084433 0.8887848893166506
    # 램덤 포레스트와 비슷한 결과</code></pre>
<p>결정 트리는 최적의 분할을 찾는데 시간을 많이 소모한다. (고려할 특성의 개수가 많을 땐 더욱)
그렇기에 랜덤하게 노드를 분할하는 <strong>엑스트라 트리는 빠른 계산 속도가 장점</strong>이다.</p>
<pre><code class="language-python"># 📌 특성 중요도 출력
et.fit(train_input, train_target)
print(et.feature_importances_)</code></pre>
<pre><code>&gt;&gt;&gt; [0.20183568 0.52242907 0.27573525]

결정트리보다 당도(두번째 클래스)에 대한 의존성이 작다.</code></pre><hr>
<h1 id="그레이디언트-부스팅">그레이디언트 부스팅</h1>
<p><span style='background-color: #dcffe4'><strong>그레이디언트 부스팅</strong></span>은 부스팅(boosting)에 속하는 앙상블 기법 중 하나로, 
<span style='background-color: #dcffe4'>깊이가 얕은 결정 트리를 사용하여 <strong>이전 트리의 오차를 보완하는 방식</strong>이다.</span>
bagging에서 각각의 모델이 독립적으로 학습하는 반면, boosting은 이전 모델의 학습 결과가 다음 모델에 영향을 미치며 순차적으로 보완된다.</p>
<p><strong>경사하강법</strong>을 사용하여 트리를 앙상블에 추가한다.
결정트리를 계속 추가하면서 가장 낮은 곳을 찾아 이동하는데, 그렇기 때문에 깊이가 얕은 트리를 사용한다.</p>
<pre><code class="language-python"># 📌 그레이디언트 부스팅 의 교차검증 점수
from sklearn.ensemble import GradientBoostingClassifier
gb = GradientBoostingClassifier(random_state=42)
scores = cross_validate(gb, train_input, train_target, return_train_score=True, n_jobs=-1)
print(np.mean(scores[&#39;train_score&#39;]), np.mean(scores[&#39;test_score&#39;]))
# &gt;&gt;&gt; 0.8881086892152563 0.8720430147331015

&quot;&quot;&quot;----------------------------------------------
과대적합이 거의 되지 않는다!
학습률을 증가시키고 트리의 개수를 늘려 성능을 향상시켜보자.
----------------------------------------------&quot;&quot;&quot;

gb = GradientBoostingClassifier(n_estimators=500, learning_rate=0.2, random_state=42)
scores = cross_validate(gb, train_input, train_target, return_train_score=True, n_jobs=-1)
print(np.mean(scores[&#39;train_score&#39;]), np.mean(scores[&#39;test_score&#39;]))
# &gt;&gt;&gt; 0.9464595437171814 0.8780082549788999</code></pre>
<pre><code class="language-python"># 📌 특성 중요도 출력
gb.fit(train_input, train_target)
print(gb.feature_importances_)</code></pre>
<pre><code>&gt;&gt;&gt; [0.15887763 0.6799705  0.16115187]

랜덤 포레스트보다 일부 특성에 더 집중한다.</code></pre><blockquote>
</blockquote>
<p>일반적으로 그레이디언트 부스팅이 랜덤 포레스트보다 조금 더 높은 성능을 보인다.
하지만 순차적으로 트리를 추가해 보완하기 때문에 훈련 속도가 느리다.
이런 그레이디언트 부스팅의 속도와 성능을 더욱 개선한 것이
<strong>히스토그램 기반 그레이디언트 부스팅</strong>이다.</p>
<hr>
<h1 id="히스토그램-기반-그레이디언트-부스팅">히스토그램 기반 그레이디언트 부스팅</h1>
<p><span style='background-color: #fff5b1'>정형 데이터를 다루는 머신러닝 알고리즘 중에 <strong>가장 인기가 높은 알고리즘</strong>이다.</span></p>
<p>히스토그램 기반 그레이디언트 부스팅은 먼저, 입력 특성을 256개의 구간으로 나눈다.
따라서 노드를 분할할 때 최적의 분할을 <strong>매우 빠르게 찾을 수 있다.</strong></p>
<pre><code class="language-python">from sklearn.ensemble import HistGradientBoostingClassifier
hgb = HistGradientBoostingClassifier(random_state=42)
scores = cross_validate(hgb, train_input, train_target, return_train_score=True)
print(np.mean(scores[&#39;train_score&#39;]), np.mean(scores[&#39;test_score&#39;]))</code></pre>
<pre><code>&gt;&gt;&gt; 0.9321723946453317 0.8801241948619236

과대적합을 잘 억제하며 그레이디언트 부스팅보다 좀 더 높은 성능을 보인다.</code></pre><p>히스토그램 기반 그레이디언트 부스팅의 특성 중요도를 계산하기 위해 <code>permutation_importance()</code> 함수를 사용한다.
이 함수는 특성을 하나씩 랜덤하게 섞어서 모델의 성능이 변화하는지를 관찰하여 어떤 특성이 중요한지를 계산한다.</p>
<pre><code class="language-python"># 📌 훈련세트에서의 특성 중요도
from sklearn.inspection import permutation_importance
hgb.fit(train_input, train_target)
result = permutation_importance(hgb, train_input, train_target, n_repeats=10, random_state=42, n_jobs=-1)
print(result.importances_mean)
# &gt;&gt;&gt; [0.08876275 0.23438522 0.08027708]</code></pre>
<pre><code class="language-python"># 📌 테스트세트에서의 특성 중요도
from sklearn.inspection import permutation_importance
hgb.fit(train_input, train_target)
result = permutation_importance(hgb, test_input, test_target, n_repeats=10, random_state=42, n_jobs=-1)
print(result.importances_mean)
# &gt;&gt;&gt; [0.05969231 0.20238462 0.049     ]</code></pre>
<pre><code>그레이디언트 부스팅과 비슷하게 조금 더 당도에 집중하고 있는 것을 볼 수 있다.</code></pre><p>그러면, 이제 <code>HistGradientBoostingClassifier</code> 를 사용해 테스트 세트에서의 성능을 최종적으로 확인해보자.</p>
<pre><code class="language-python">hgb.score(test_input, test_target)
# &gt;&gt;&gt; 0.8723076923076923</code></pre>
<p>이전 5-2장에서 랜덤 서치에서 최종 테스트 정화도로 86%를 얻은 것을 기억해보면,
확실히 <strong>앙상블 모델은 단일 결정트리보다 더 좋은 결과를 얻을 수 있다는 걸 확인할 수 있다!</strong></p>
<hr>
<p>사이킷런 외에도, 그레이디언트 부스팅 알고리즘을 구현한 라이브러리가 여럿 있는데,
대표적으로 <strong>XGBoost</strong>가 있다.</p>
<pre><code class="language-python">&quot;&quot;&quot;tree_method 매개변수를 hist로 지정하면 히스토그램 기반 그레이디언트 부스팅을 사용할 수 있다.&quot;&quot;&quot;
from xgboost import XGBClassifier

xgb = XGBClassifier(tree_method=&#39;hist&#39;, random_state=42)
scores = cross_validate(xgb, train_input, train_target, return_train_score=True, n_jobs=-1)

print(np.mean(scores[&#39;train_score&#39;]), np.mean(scores[&#39;test_score&#39;]))
# &gt;&gt;&gt; 0.9558403027491312 0.8782000074035686
</code></pre>
<p>또한, 히스토그램 기반 그레이디언트 부스팅 라이브러리는 <strong>LightGBM</strong>이 있다.</p>
<pre><code class="language-python">from lightgbm import LGBMClassifier

lgb = LGBMClassifier(random_state=42)
scores = cross_validate(lgb, train_input, train_target, return_train_score=True, n_jobs=-1)

print(np.mean(scores[&#39;train_score&#39;]), np.mean(scores[&#39;test_score&#39;]))
# &gt;&gt;&gt; 0.935828414851749 0.8801251203079884</code></pre>
<hr>
<blockquote>
</blockquote>
<p>앙상블 기법에는 <strong>보팅voting, 배깅bagging, 부스팅boosting, 스태킹stacking</strong>이 있다.</p>
<blockquote>
</blockquote>
<p><strong>bagging</strong>엔 랜덤 포레스트가 대표적인데, bootstrap aggregation 의 약자이다.
-bootstrap으로 데이터를 추출하고, 각 모델의 예측 결과를 평균 내서 종합(aggregate)해서 판단</p>
<blockquote>
</blockquote>
<p><strong>boosting</strong>엔 대표적으로 Gradient Boosting이 있고, XGBoost, LightGBM, AdaBoost가 있다.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[[혼공머신] 5-2장 교차 검증과 그리드 서치]]></title>
            <link>https://velog.io/@changh2_00/%ED%98%BC%EA%B3%B5%EB%A8%B8%EC%8B%A0-5-2%EC%9E%A5-%EA%B5%90%EC%B0%A8-%EA%B2%80%EC%A6%9D%EA%B3%BC-%EA%B7%B8%EB%A6%AC%EB%93%9C-%EC%84%9C%EC%B9%98</link>
            <guid>https://velog.io/@changh2_00/%ED%98%BC%EA%B3%B5%EB%A8%B8%EC%8B%A0-5-2%EC%9E%A5-%EA%B5%90%EC%B0%A8-%EA%B2%80%EC%A6%9D%EA%B3%BC-%EA%B7%B8%EB%A6%AC%EB%93%9C-%EC%84%9C%EC%B9%98</guid>
            <pubDate>Sun, 10 Nov 2024 10:03:47 GMT</pubDate>
            <description><![CDATA[<hr>
<p>[혼자 공부하는 머신러닝+딥러닝] 교재 5장을 기반으로 작성되었습니다.</p>
<hr>
<p>이전 챕터에서의 결정트리 모델의 매개변수 max_depth를 3말고 다른 값으로 하면 성능이 달라진다.
매개변수 값을 바꿔가며 테스트를 많이 하며 적절한 값을 찾으면 될 것이다.
하지만, <span style='background-color:#ffdce0'>이런저런 값으로 모델을 많이 만들어서 <strong>&#39;테스트 세트&#39;</strong>로 평가하면 결국 <strong>테스트 세트에 잘 맞는 모델</strong>이 만들어지는 것 아닐까?</span>
맞다. 그러므로 우린 모델을 개발하는 과정에선 테스트세트를 사용해선 안 된다.</p>
<h1 id="검증-세트">검증 세트</h1>
<p>테스트 세트를 사용하지 않으면 모델이 적절히 학습됐는지 판단하기 어렵다.
<span style='background-color:#fff5b1'>테스트 세트를 사용하지 않고 이를 측정하는 간단한 방법은 <strong>훈련 세트를 또 나누는 것이다!</strong></span>
이렇게 나눈 데이터를 <span style='background-color:#fff5b1'><strong>검증 세트</strong></span>라고 한다.</p>
<p><img src="https://velog.velcdn.com/images/changh2_00/post/3143743a-90ca-4832-aa2e-badab7436d22/image.jpeg" alt=""></p>
<p>5-1에서 전체 데이터 중 20%를 테스트 세트, 80%를 훈련 세트로 만들었다.
이 훈련 세트 중에서 다시 20%를 떼어 내어 검증 세트로 만들자.</p>
<pre><code class="language-python"># 📌 데이터 준비
import pandas as pd
wine = pd.read_csv(&#39;https://bit.ly/wine_csv_data&#39;)

data = wine[[&#39;alcohol&#39;, &#39;sugar&#39;, &#39;pH&#39;]].to_numpy()
target = wine[&#39;class&#39;].to_numpy()</code></pre>
<pre><code class="language-python"># 📌 훈련 세트, 테스트 세트 분할
from sklearn.model_selection import train_test_split
train_input, test_input, train_target, test_target = train_test_split(data, target, test_size=0.2, random_state=42)
    # 20%만 테스트 세트로 나눠준다. (test_size=0.2)</code></pre>
<pre><code class="language-python"># 📌 훈련 세트, 검증 세트 분할
sub_input, val_input, sub_target, val_target = train_test_split(train_input, train_target, test_size=0.2, random_state=42)
    # input은 data 대신에 train_input,
    # target은 target 대신에 train_target 을 사용해준다.
    # 20%만 검증 세트로 나눠준다. (test_size=0.2)

print(sub_input.shape, val_input.shape)  # 훈련 세트, 검증 세트의 크기 확인</code></pre>
<pre><code>&gt;&gt;&gt; (4157, 3) (1040, 3)

# 원래 훈련 세트: 5,197개 --&gt; 4,157개
# 검증 세트: 1,040개</code></pre><pre><code class="language-python"># 📌 검증 세트를 사용해 모델 훈련+평가
from sklearn.tree import DecisionTreeClassifier
dt = DecisionTreeClassifier(random_state=42)
dt.fit(sub_input, sub_target)
print(dt.score(sub_input, sub_target))
print(dt.score(val_input, val_target))</code></pre>
<pre><code>&gt;&gt;&gt; 0.9971133028626413
    0.864423076923077
</code></pre><p>결과를 보니 과대적합되어있다. <strong>매개변수를 바꿔 더 좋은 모델을 찾아야하는데, 어떻게?</strong></p>
<hr>
<h1 id="교차-검증">교차 검증</h1>
<p>== <strong>Cross Validation</strong></p>
<p><span style='background-color:#fff5b1'><strong>교차 검증</strong></span>은 <span style='background-color:#fff5b1'>검증 세트를 떼어 내어 평가하는 과정을 <strong>여러 번 반복</strong>한다.</span>
그 다음 <span style='background-color:#fff5b1'>이 <strong>점수를 평균</strong>하여 최종 검증 점수를 얻어낸다.</span>
아래는 3-폴드 교차 검증의 그림이다.
<img src="https://velog.velcdn.com/images/changh2_00/post/7c8ac302-02b0-41ba-8c8d-2f4952503a23/image.jpeg" alt=""></p>
<blockquote>
<h3 id="k-폴드-교차-검증-k-fold-cross-validation">K-폴드 교차 검증 (K-fold cross validation)</h3>
<p>훈련 세트를 k 부분으로 나눠서 교차 검증을 수행하는 것을 k-폴드 교차 검증이라 부른다.
사이킷런에서 <strong><code>cross_validate()</code></strong> 함수로 제공된다.</p>
</blockquote>
<blockquote>
<h3 id="cross_validate">cross_validate()</h3>
<p>교차 검증을 수행하는 함수이다.
첫번째 매개변수에 교차 검증을 수행할 모델 객체를, 두번째와 세번째 매개변수에 특성과 타깃 데이터를 전달한다.
<code>scoring</code> 매개변수는 검증에 사용할 평가 지표를 지정할 수 있다.
(분류 모델의 기본값 =  정확도를 의미하는 &#39;accuracy&#39;)
(회귀 모델의 기본값 = 결정계수를 의미하는 &#39;r2&#39;)
<strong><code>cv</code></strong> 매개변수는 교차 검증의 폴드 수(k)를 지정할 수 있다. (기본값 = 5)
<code>n_jobs</code> 매개변수는 교차 검증을 수행할 때 사용할 CPU 코어 수를 지정한다. (기본값 = 1)
(-1로 지정하면 시스템의 모든 코어 사용)
<code>return_train_score</code> 매개변수를 True로 지정하면 훈련 세트의 점수도 반환한다. 
(기본값 = False)</p>
</blockquote>
<pre><code class="language-python">from sklearn.model_selection import cross_validate
scores = cross_validate(dt, train_input, train_target)
print(scores)</code></pre>
<pre><code>&gt;&gt;&gt; {&#39;fit_time&#39;: array([0.01341891, 0.02167416, 0.02525187, 0.04882073, 0.03598666]), 
    &#39;score_time&#39;: array([0.0027864 , 0.0019815 , 0.00886154, 0.01437068, 0.02624893]), 
    &#39;test_score&#39;: array([0.86923077, 0.84615385, 0.87680462, 0.84889317, 0.83541867])}

# 3개의 키를 가진 딕셔너리를 반환한다.
# 기본적으로 5-폴드 교차 검증을 수행한 것을 볼 수 있다.</code></pre><p>교차 검증의 <strong>최종 점수</strong>는 test_score 키에 담긴 5개의 점수를 평균하여 얻을 수 있다.</p>
<pre><code class="language-python">import numpy as np
print(np.mean(scores[&#39;test_score&#39;]))</code></pre>
<pre><code>&gt;&gt;&gt; 0.855300214703487</code></pre><hr>
<p><strong>+) <span style='background-color:#ffdce0'> 주의</strong></span>
분류 모델은 훈련 세트를 섞어 폴드를 나누어주어야 하는데,
cross_validation() 함수는 기본적으로 훈련 세트를 섞어 폴드를 나누지 않는다.
교차 검증을 할때 훈련 세트를 섞으려면 분할기(splitter)를 지정해야한다.
분류 모델일 경우엔 StratifiedKFold 사용.</p>
<pre><code class="language-python"># 앞서 수행한 교차검증과 같은 내용을 수행
from sklearn.model_selection import StratifiedKFold
scores = cross_validate(dt, train_input, train_target, cv=StratifiedKFold())
print(np.mean(scores[&#39;test_score&#39;]))
# &gt;&gt;&gt; 0.855300214703487</code></pre>
<p>만약 훈련 세트를 섞은 후 10-폴드 교차 검증을 수행하려면 아래와 같이 작성한다.</p>
<pre><code class="language-python">                          # 몇(k)폴드 교차 검증을 할지 지정 
splitter = StratifiedKFold(n_splits=10, shuffle=True, random_state=42)
scores = cross_validate(dt, train_input, train_target, cv=StratifiedKFold())
print(np.mean(scores[&#39;test_score&#39;]))
# &gt;&gt;&gt; 0.855300214703487</code></pre>
<p>이제 교차 검증에 대해 이해했으니, 결정 트리의 매개변수 값을 바꿔가며 가장 좋은 성능이 나오는 모델을 찾아보자. (테스트 세트를 사용하지 않고 교차 검증을 통해서 좋은 모델을 고른다)</p>
<hr>
<h1 id="하이퍼파라미터-튜닝">하이퍼파라미터 튜닝</h1>
<p>하이퍼파리미터는 사용자가 지정해야만 하는 파라미터이다.
성능이 좋아지는 파라미터를 찾는 하이퍼파라미터 튜닝은 어떻게 하는걸까?</p>
<h2 id="그리드-서치">그리드 서치</h2>
<p>여러 매개변수의 최적의 값을 찾는건 여러 반복문을 통해 이뤄지는데, 이미 만들어진 도구가 있다. 
그 중 하나가 <span style='background-color:#fff5b1'><strong>그리드 서치</strong></span>인데, 사이킷런에서 <strong><code>GridSearchCV</code></strong> 클래스를 제공한다.
이 클래스는 친절하게도 <span style='background-color:#fff5b1'><strong>하이퍼파라미터 탐색</strong>과 <strong>교차 검증</strong>을 한 번에 수행해준다.</span></p>
<blockquote>
<h3 id="gridsearchcv">GridSearchCV()</h3>
<p>교차 검증으로 하이퍼파라미터 탐색을 수행한다.
최상의 모델을 찾은 후 훈련 세트 전체를 사용해 최종 모델을 훈련한다.
첫번째 매개변수로 그리드 서치를 수행할 모델 객체를, 두번째 매개변수에는 탐색할 모델의 매개변수와 값을 전달한다.
<code>scoring</code>, <code>cv</code>, <code>n_jobs</code>, <code>return_train_score</code> 매개변수는 cross_validate() 함수와 동일
<strong>탐색할 매개변수 값 모두에 대해 교차검증을 수행한다!</strong></p>
</blockquote>
<p>결정 트리 모델에서 <code>min_impurity_decrease</code> 매개변수의 최적값을 찾아보자.</p>
<pre><code class="language-python"># 📌 먼저, 탐색할 매개변수, 탐색할 값의 리스트(우선 임의로 지정)를 딕셔너리로 만든다.
from sklearn.model_selection import GridSearchCV
params = {&#39;min_impurity_decrease&#39;: [0.0001, 0.0002, 0.0003, 0.0004, 0.0005]}

# 📌 GridSearchCV 클래스에 탐색 대상 모델과 params 변수를 전달하여 객체 생성
gs = GridSearchCV(DecisionTreeClassifier(random_state=42),params, n_jobs=1)

# 📌 그리드 서치 수행
gs.fit(train_input, train_target)

# --&gt; cv매개변수의 기본값은 5이므로 5*5=25개의 모델을 훈련하게 된다.</code></pre>
<p>그리드 서치는 훈련이 끝나면 여러가지의 모델 중에서 검증 점수가 가장 높은 모델의 매개변수 조합으로 전체 훈련 세트에서 자동으로 다시 모델을 훈련한다!</p>
<pre><code class="language-python"># 📌 가장 높은 점수의 모델은 gs 객체의 best_estimator_ 속성에 저장된다.
dt = gs.best_estimator_
print(dt.score(train_input, train_target))
# &gt;&gt;&gt; 0.9615162593804117

# 📌 그리드 서치로 찾은 최적의 매개변수는 best_params_ 속성에 저장된다.
print(gs.best_params_)
# &gt;&gt;&gt; {&#39;min_impurity_decrease&#39;: 0.0001}

# 📌 각 매개변수에서 수행한 교차 검증의 평균 점수는 cv_results 속성의 &#39;mean_test_score&#39; 키에 저장된다.
print(gs.cv_results_[&#39;mean_test_score&#39;])
# &gt;&gt;&gt; [0.86819297 0.86453617 0.86492226 0.86780891 0.86761605]

# 📌 직접 구한 매개변수 조합이 gs.best_params_와 동일한지 확인해보자.
best_index = np.argmax(gs.cv_results_[&#39;mean_test_score&#39;])
print(gs.cv_results_[&#39;params&#39;][best_index])
# &gt;&gt;&gt; {&#39;min_impurity_decrease&#39;: 0.0001}</code></pre>
<p>이제 조금 더 복잡한 매개변수 조합을 탐색해보자.
아까는 &#39;min_impurity_decrease&#39; 매개변수 하나만을 
[0.0001, 0.0002, 0.0003, 0.0004, 0.0005]의 리스트 내에서만 탐색했지만,
이번엔 <strong>여러 매개변수</strong>에서 <strong>arange, range함수를 사용해 매개변수 값 리스트를 만들어 탐색</strong>할 것이다.</p>
<pre><code class="language-python"># 📌 탐색할 매개변수, 탐색할 값의 리스트를 딕셔너리로 만든다.
params = {&#39;min_impurity_decrease&#39;: np.arange(0.0001, 0.001, 0.0001),
          &#39;max_depth&#39;: range(5, 20, 1),
          &#39;min_samples_split&#39;: range(2, 100, 10)
          }
# arange 함수: 첫번째 매개변수 값에서 두번째 매개변수에 도달할 때까지 세번째 매개변수를 계속 더한 배열을 만듬
# range 함수: arange함수와 동일하나, 정수에서만 사용 가능

# 📌 그리드 서치 수행                                # 만들어지는 모델의 수가 많으므로 최대 CPU 사용
gs = GridSearchCV(DecisionTreeClassifier(random_state=42), params, n_jobs=-1)
gs.fit(train_input, train_target)

# 📌 최상의 매개변수 조합 확인
print(gs.best_params_)
# &gt;&gt;&gt; {&#39;max_depth&#39;: 14, &#39;min_impurity_decrease&#39;: 0.0004, &#39;min_samples_split&#39;: 12}

# 📌 최상의 교차 검증 점수 확인
print(np.max(gs.cv_results_[&#39;mean_test_score&#39;]))
# &gt;&gt;&gt; 0.8683865773302731</code></pre>
<p>좋은 결과가 나왔다! 하지만, 
<span style='background-color:#ffdce0'><strong>탐색할 매개변수의 간격</strong>을 0.0001 혹은 1로 <strong>임의로 설정</strong>했는데,
  이를 미리 정하기가 어려운거 같다.</span> <strong>어떻게 해야할까?</strong></p>
<h2 id="랜덤-서치">랜덤 서치</h2>
<p><span style='background-color:#fff5b1'><strong>랜덤 서치</strong></span>란, <span style='background-color:#fff5b1'>하이퍼파라미터 값들을 일정횟수 만큼 무작위로 샘플링하여 탐색하는 방법</span>이다.
사이킷런에서 <strong><code>RandomizedSearchCV</code></strong> 클래스로 제공된다.</p>
<blockquote>
<h3 id="randint-과-uniform">randint() 과 uniform()</h3>
<p>둘 다 주어진 범위에서 고르게 값을 뽑는다. (균등 분포에서 샘플링한다)
randint는 정숫값을, uniform은 실숫값을 뽑는다.</p>
</blockquote>
<pre><code class="language-python">from scipy.stats import uniform, randint
&gt;
# 📌 0~10 범위에서 randint로 10개의 정수 샘플링
rgen = randint(0, 10)
rgen.rvs(10)
# &gt;&gt;&gt; array([1, 3, 9, 4, 4, 5, 6, 7, 9, 2])
&gt;
# 📌 0~1 범위에서 uniform으로 10개의 실수 샘플링
ugen = uniform(0, 1)
ugen.rvs(10)
# &gt;&gt;&gt; array([0.98190469, 0.21027677, 0.55010361, 0.68042272, 0.84064129,
       0.95906507, 0.29501035, 0.22313601, 0.77810409, 0.14792308])</code></pre>
<blockquote>
<h3 id="randomizedsearchcv">RandomizedSearchCV()</h3>
<p>교차 검증으로 랜덤한 하이퍼파라미터 탐색을 수행한다.
최상의 모델을 찾은 후 훈련 세트 전체를 사용해 최종 모델을 훈련한다.
첫번째 매개변수로 그리드 서치를 수행할 모델 객체를, 두번째 매개변수에는 탐색할 모델의 매개변수와 값을 전달한다.
<code>scoring</code>, <code>cv</code>, <code>n_jobs</code>, <code>return_train_score</code> 매개변수는 cross_validate() 함수와 동일
<strong><code>n_iter</code></strong> 매개변수는 <strong>랜덤으로 몇번 샘플링하여 교차검증을 수행할지 지정한다.</strong>(기본값=10)</p>
</blockquote>
<p>랜덤 서치로 탐색해보자.</p>
<pre><code class="language-python"># 📌 탐색할 값의 리스트를 randint, uniform 을 사용해 랜덤하게 만든다.
from scipy.stats import uniform, randint
params = {&#39;min_impurity_decrease&#39;: uniform(0.0001, 0.001),
          &#39;max_depth&#39;: randint(20, 50),
          &#39;min_samples_split&#39;: randint(2, 25),
          &#39;min_samples_leaf&#39;: randint(1, 25),
          }  # 리프 노드가 되기 위한 최소 샘플의 개수 (min_samples_leaf) 매개변수 추가

# 📌 랜덤 서치 수행
from sklearn.model_selection import RandomizedSearchCV
gs = RandomizedSearchCV(DecisionTreeClassifier(random_state=42), params, n_iter=100, n_jobs=-1, random_state=42)
                                        # 총 100번을 (n_iter=100) 샘플링하여 교차검증 수행
gs.fit(train_input, train_target)

# 📌 최상의 매개변수 조합 확인
print(gs.best_params_)
# &gt;&gt;&gt; {&#39;max_depth&#39;: 39, &#39;min_impurity_decrease&#39;: 0.00034102546602601173, &#39;min_samples_leaf&#39;: 7, &#39;min_samples_split&#39;: 13}

# 📌 최상의 교차 검증 점수 확인
print(np.max(gs.cv_results_[&#39;mean_test_score&#39;]))
# &gt;&gt;&gt; 0.8695428296438884

# 📌 최상의 모델 best_estimator을 최종 모델로 결정하고, 테스트 세트의 성능을 확인해보자.
dt = gs.best_estimator_
print(dt.score(test_input, test_target))
# &gt;&gt;&gt; 0.86</code></pre>
<p>테스트 세트 점수는 검증 세트에 대한 점수보다 약간 작은 것이 일반적이다.
나름 만족스러운 점수가 나온듯 하다!</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[[혼공머신] 5-1장 결정 트리]]></title>
            <link>https://velog.io/@changh2_00/%ED%98%BC%EA%B3%B5%EB%A8%B8%EC%8B%A0-5%EC%9E%A5-%ED%8A%B8%EB%A6%AC-%EC%95%8C%EA%B3%A0%EB%A6%AC%EC%A6%98</link>
            <guid>https://velog.io/@changh2_00/%ED%98%BC%EA%B3%B5%EB%A8%B8%EC%8B%A0-5%EC%9E%A5-%ED%8A%B8%EB%A6%AC-%EC%95%8C%EA%B3%A0%EB%A6%AC%EC%A6%98</guid>
            <pubDate>Sun, 10 Nov 2024 06:04:54 GMT</pubDate>
            <description><![CDATA[<hr>
<p>[혼자 공부하는 머신러닝+딥러닝] 교재 5장을 기반으로 작성되었습니다.</p>
<hr>
<p>신상품으로 캔 와인을 판매하는데,
알코올 도수, 당도, PH 값으로 화이트 와인인지 레드 와인인지 종류를 구별하려고 한다.</p>
<h1 id="로지스틱-회귀로-와인-분류하기">로지스틱 회귀로 와인 분류하기</h1>
<p>이전에 배운 로지스틱 회귀로 문제를 해결해보자.</p>
<pre><code class="language-python"># 📌 데이터셋 준비
import pandas as pd
wine = pd.read_csv(&#39;https://bit.ly/wine_csv_data&#39;)

# 📌 샘플 확인
wine.head()</code></pre>
<pre><code>&gt;&gt;&gt;     alcohol     sugar     pH      class
     0      9.4      1.9   3.51   0.0
     1      9.8      2.6   3.20   0.0
     2      9.8      2.3   3.26   0.0
     3      9.8      1.9   3.16   0.0
     4      9.4      1.9   3.51   0.0

# class는 타깃값으로, 0이면 레드 와인, 1이면 화이트 와인을 뜻한다.
# (양성 클래스인 화이트 와인을 골라내는 이진 분류 문제)</code></pre><p>모델을 훈련하기 전에, 판다스의 유용한 메서드 2개로 데이터셋을 확인해보자.</p>
<blockquote>
<h3 id="info">info()</h3>
<p>판다스의 유용한 메서드 중 하나.
데이터프레임의 요약된 <strong>정보를 출력</strong>하는데, 각 열의 데이터 타입과 누락된 데이터가 있는지 확인하는 데 유용하다.</p>
<h3 id="describe">describe()</h3>
<p>판다스의 유용한 메서드 중 하나.
데이터프레임 열의 <strong>통계 값을 제공</strong>하는데, 
수치형일 경우 최소, 최대, 평균, 표준편차, 사분위값 등이 출력되고,
문자열 같은 객체 타입의 열의 가장 자주 등장하는 값과 횟수 등이 출력된다.</p>
</blockquote>
<pre><code class="language-python">wine.info()</code></pre>
<pre><code>&gt;&gt;&gt; &lt;class &#39;pandas.core.frame.DataFrame&#39;&gt;
    RangeIndex: 6497 entries, 0 to 6496
    Data columns (total 4 columns):
     #   Column   Non-Null Count  Dtype  
    ---  ------   --------------  -----  
     0   alcohol  6497 non-null   float64
     1   sugar    6497 non-null   float64
     2   pH       6497 non-null   float64
     3   class    6497 non-null   float64
    dtypes: float64(4)
    memory usage: 203.2 KB

# 총 6,497개의 샘플이 있고 4개의 열은 모두 실숫값이다.
# Non-Null Count가 모두 6497이므로 누락된 값은 없다.</code></pre><pre><code class="language-python">wine.describe()</code></pre>
<pre><code>&gt;&gt;&gt;            alcohol      sugar           pH          class
    count    6497.000000    6497.000000    6497.000000    6497.000000
    mean    10.491801    5.443235    3.218501    0.753886
    std        1.192712    4.757804    0.160787    0.430779
    min        8.000000    0.600000    2.720000    0.000000
    25%        9.500000    1.800000    3.110000    1.000000
    50%        10.300000    3.000000    3.210000    1.000000
    75%        11.300000    8.100000    3.320000    1.000000
    max        14.900000    65.800000    4.010000    1.000000

# 알코올 도수와 당도, PH 값의 스케일이 다르단걸 알 수 있다.
# 표준화를 해야한다!</code></pre><pre><code class="language-python"># 📌 넘파이 배열로 전환 
data = wine[[&#39;alcohol&#39;, &#39;sugar&#39;, &#39;pH&#39;]].to_numpy()
target = wine[&#39;class&#39;].to_numpy()</code></pre>
<pre><code class="language-python"># 📌 훈련 세트, 테스트 세트 분할
from sklearn.model_selection import train_test_split
train_input, test_input, train_target, test_target = train_test_split(data, target, test_size=0.2, random_state=42)
    # 샘플 개수가 충분치 않으므로 20% 정도만 테스트 세트로 나눠준다. (test_size=0.2)

print(train_input.shape, test_input.shape)  # 훈련, 테스트 세트의 크기 확인</code></pre>
<pre><code>&gt;&gt;&gt; (5197, 3) (1300, 3)</code></pre><pre><code class="language-python"># 📌 표준화 전처리
from sklearn.preprocessing import StandardScaler
ss = StandardScaler()
ss.fit(train_input)
train_scaled = ss.transform(train_input)
test_scaled = ss.transform(test_input)</code></pre>
<pre><code class="language-python"># 📌 로지스틱 회귀 모델 훈련
from sklearn.linear_model import LogisticRegression
lr = LogisticRegression()
lr.fit(train_scaled, train_target)
print(lr.score(train_scaled, train_target))
print(lr.score(test_scaled, test_target))</code></pre>
<pre><code>&gt;&gt;&gt; 0.7808350971714451
    0.7776923076923077</code></pre><p>훈련세트와 테스트세트의 결과 점수가 모두 낮으니 과소적합된거 같다.
우선 결과를 보고하자.</p>
<hr>
<h2 id="설명하기-쉬운-모델과-어려운-모델">설명하기 쉬운 모델과 어려운 모델</h2>
<p>결과를 제출할 보고서를 만드려는데, 이 모델을 설명하기 위해 이 모델이 학습한 계수와 절편을 출력하자.</p>
<pre><code class="language-python">print(lr.coef_, lr.intercept_)</code></pre>
<pre><code>&gt;&gt;&gt; [[ 0.51268071  1.67335441 -0.68775646]] [1.81773456]</code></pre><p><img src="https://velog.velcdn.com/images/changh2_00/post/b477aec9-3432-4f91-be7a-dbf27a80be6e/image.png" alt=""></p>
<p>이해하기가 너무 어렵다....
이해하기 쉽도록 <strong>&quot;순서도&quot;</strong>처럼 쉽게 설명해서 가져오라 하는데, 
이렇게 쉽게 설명할 수 있는 모델이 있을까?</p>
<p><img src="https://velog.velcdn.com/images/changh2_00/post/c63114a6-cfcd-4590-96db-e92dd0e99e78/image.jpeg" alt=""></p>
<hr>
<h1 id="결정-트리">결정 트리</h1>
<p><span style='background-color:#fff5b1'><strong>결정 트리</strong></span>란 <span style='background-color:#fff5b1'>예 / 아니오에 대한 질문을 이어나가면서 정답을 찾아 학습하는 알고리즘</span>으로,
비교적 예측 과정을 이해하기 쉽고 성능도 뛰어나다.
사이킷런의 <code>DecisionTreeClassifier</code> 클래스로 제공된다.</p>
<pre><code class="language-python"># 📌 결정트리 모델 훈련+평가
from sklearn.tree import DecisionTreeClassifier
dt = DecisionTreeClassifier(random_state=42)
dt.fit(train_scaled, train_target)
print(dt.score(train_scaled, train_target))  # 훈련 세트
print(dt.score(test_scaled, test_target))  # 테스트 세트</code></pre>
<pre><code>&gt;&gt;&gt; 0.996921300750433
    0.8592307692307692

# 훈련세트에 비해 테스트세트 점수가 낮다. (과대적합)</code></pre><p>이 모델을 그림으로 표현하기 위해 <strong><code>plot_tree()</code></strong> 함수를 사용해보자.</p>
<p><img src="https://velog.velcdn.com/images/changh2_00/post/4ad9150e-cbb3-4379-9186-b4efc84547bb/image.jpeg" alt=""></p>
<p>뭔가 엄청난 트리가 만들어졌다... 너무 복잡하니 max_depth 매개변수를 1로 조절해서 그려보자.</p>
<p><img src="https://velog.velcdn.com/images/changh2_00/post/19927046-d8ab-4db8-8e73-5851af733d21/image.jpeg" alt=""></p>
<p>기본적으로 이 그림이 담고 있는 정보는 아래와 같다.</p>
<p><img src="https://velog.velcdn.com/images/changh2_00/post/77aad337-f6c6-40d7-a81b-80d902784f03/image.jpeg" alt=""></p>
<p>위의 그림에서 루트 노드를 살펴보자.</p>
<p><img src="https://velog.velcdn.com/images/changh2_00/post/18006907-4517-43a8-bce9-1ec16b6cd71f/image.jpeg" alt=""></p>
<pre><code>루트 노드의 총 샘플 수 는 5197개 이고, 
이 샘플 중 음성 클래스(레드와인)는 1258개, 양성 클래스(화이트와인)는 3939개이다.
sugar가 -0.239와 같거나 작으면 왼쪽 가지로 가고, -0.239보다 크면 오른쪽 가지로 간다.</code></pre><p>이런 식으로 리프 노드까지 도달한 후, <span style='background-color:#fff5b1'><strong>리프 노드에서 가장 많은 클래스가 예측 클래스가 된다.</strong></span></p>
<p>근데, 그림에서 <strong>gini</strong>는 뭘 뜻하는 걸까?</p>
<hr>
<h2 id="불순도">불순도</h2>
<p>gini는 <strong>지니 불순도</strong>를 의미하는데, 결정트리 클래스의 criterion 매개변수 기본값이 &#39;gini&#39;이다.
(criterion은 노드에서 데이터를 분할할 기준을 정하는 기준이다.)
즉, 위에서 루트 노드는 &#39;어떻게 당도 -0.239를 기준으로&#39; 노드를 나눴는지 정하는 것이다.
지니 불순도를 계산하는 방법은 아래와 같다.</p>
<p><img src="https://velog.velcdn.com/images/changh2_00/post/40f06738-75ed-434d-be74-212bed63b154/image.png" alt=""></p>
<p>위의 루트 노드를 예시로 계산해보면 아래와 같다.
<img src="https://velog.velcdn.com/images/changh2_00/post/4cd63972-eec6-44fe-928f-f79e7463f981/image.png" alt=""></p>
<blockquote>
</blockquote>
<pre><code>만약 100개의 샘플이 음성 클래스 50개, 양성 클래스 50개로 구성되어있다면
지니 불순도는 0.5가 되어 최악이 된다.</code></pre><blockquote>
</blockquote>
<pre><code>노드에 하나의 클래스만 있다면, (100개의 샘플중 음성 클래스는 0개, 양성 클래스만 100개)
지니 불순도는 0이 되어 가장 작고, 이런 노드를 순수 노드라고 부른다.</code></pre><p><span style='background-color:#dcffe4'>결정 트리 모델은 <strong>부모 노드</strong>와 <strong>자식 노드</strong>의 <strong>불순도 차이가 가능한 크도록</strong> 트리를 성장시킨다.</span></p>
<p>이런 <span style='background-color:#fff5b1'>부모와 자식 노드 사이의 불순도 차이</span>를 <span style='background-color:#fff5b1'><strong>정보 이득</strong></span>이라고 부르고, 아래와 같이 계산된다.
<img src="https://velog.velcdn.com/images/changh2_00/post/42005baa-a474-47f7-ac11-60a83346d1b8/image.png" alt=""></p>
<p>+참고)
불순도는 gini 뿐만 아니라 <strong>entropy</strong>, 즉 <strong>엔트로피 불순도</strong>도 있는데, 
지니 불순도의 &#39;제곱 방식&#39; 대신에 로그를 사용하여 곱한다.</p>
<p><img src="https://velog.velcdn.com/images/changh2_00/post/0a857007-7a72-4fb2-a80c-0c35b39b8cf6/image.png" alt=""></p>
<hr>
<h2 id="가지치기">가지치기</h2>
<p>위의 결정트리 모델은 훈련세트보다 테스트세트에서 점수가 낮았는데, 
이는 트리가 <strong>제한 없이 자라났기 때문에 과대적합</strong>된 것이라 볼 수 있다.</p>
<p>실제 나무와 같이 <span style='background-color:#fff5b1'><strong>&#39;가지치기&#39;</strong></span>를 해줘야 하는데, <span style='background-color:#fff5b1'>트리의 <strong>최대깊이를 지정</strong>하는 것으로 쉽게 조절할 수 있다.</span></p>
<pre><code class="language-python">dt = DecisionTreeClassifier(max_depth=3, random_state=42)
dt.fit(train_scaled, train_target)
print(dt.score(train_scaled, train_target))
print(dt.score(test_scaled, test_target))</code></pre>
<pre><code>&gt;&gt;&gt; 0.8454877814123533
    0.8415384615384616

# 테스트 세트의 성능은 거의 그대로다...  다음장에서 더 알아보자</code></pre><p>이 모델의 트리 그래프를 <strong><code>plot_tree()</code></strong> 함수로 그려보자</p>
<pre><code class="language-python">plt.figure(figsize=(20,15))
plot_tree(dt, filled=True, feature_names=[&#39;alcohol&#39;,&#39;sugar&#39;,&#39;pH&#39;])
plt.show()</code></pre>
<p><img src="https://velog.velcdn.com/images/changh2_00/post/631b5b6a-83c8-43b2-8a5d-38b6c98472f1/image.jpeg" alt=""></p>
<p>각 리프 노드에서 가장 많은 클래스는 양성클래스(화이트와인)이므로 예측 클래스는 화이트와인이다.</p>
<hr>
<p>앞서, 한 노드에서 불순도를 기준으로 샘플을 나눈다고 했고, 불순도는 클래스별 비율을 가지고 계산했다.
이는 각 특성의 스케일이 샘플을 나눌때 영향을 주지 않는다는 것을 의미하고, 
<span style='background-color:#ffdce0'>결정 트리 모델은 <strong>표준화 전처리를 할 필요가 없다</strong>는 걸 뜻한다.</span></p>
<pre><code class="language-python"># 📌 python표준화가 안 된 데이터로 훈련
dt = DecisionTreeClassifier(max_depth=3, random_state=42)
dt.fit(train_input, train_target)
print(dt.score(train_input, train_target))
print(dt.score(test_input, test_target))</code></pre>
<pre><code>&gt;&gt;&gt; 0.8454877814123533
    0.8415384615384616

# 결과가 똑같다!</code></pre><pre><code class="language-python"># 📌 트리 그래프 확인
plt.figure(figsize=(20,15))
plot_tree(dt, filled=True, feature_names=[&#39;alcohol&#39;,&#39;sugar&#39;,&#39;pH&#39;])
plt.show()</code></pre>
<p><img src="https://velog.velcdn.com/images/changh2_00/post/79b8b2b0-4f1a-4453-a9db-58703613cd25/image.jpeg" alt=""></p>
<p>트리는 같지만, 특성값을 표준점수로 바꾸지 않아 이해하기가 훨씬 쉽다.</p>
<hr>
<p>마지막으로, 어떤 특성이 가장 유용한지 나타내는 <strong>특성 중요도</strong>를 알아보자.
이는 <code>feature_importances_</code> 속성에 저장되어 있다.</p>
<pre><code class="language-python">print(dt.feature_importances_)</code></pre>
<pre><code>&gt;&gt;&gt; [0.12345626 0.86862934 0.0079144 ]

# 두번째 특성인 당도가 0.87 정도로 가장 높은걸 확인할 수 있다.
</code></pre><p><span style='background-color:#ffdce0'><strong>특성 중요도</strong>를 활용하면 <strong>결정 트리 모델을 특성 선택에 활용할 수 있다.</strong></span></p>
]]></description>
        </item>
        <item>
            <title><![CDATA[[밑바닥딥러닝] 3장 신경망]]></title>
            <link>https://velog.io/@changh2_00/%EB%B0%91%EB%B0%94%EB%8B%A5%EB%94%A5%EB%9F%AC%EB%8B%9D-3%EC%9E%A5-%EC%8B%A0%EA%B2%BD%EB%A7%9D</link>
            <guid>https://velog.io/@changh2_00/%EB%B0%91%EB%B0%94%EB%8B%A5%EB%94%A5%EB%9F%AC%EB%8B%9D-3%EC%9E%A5-%EC%8B%A0%EA%B2%BD%EB%A7%9D</guid>
            <pubDate>Wed, 06 Nov 2024 05:37:24 GMT</pubDate>
            <description><![CDATA[<hr>
<p>[밑바닥부터 시작하는 딥러닝1] 교재 3장을 기반으로 작성되었습니다.</p>
<hr>
<p>퍼셉트론으로 복잡한 처리까지 할 수 있었지만, 가중치를 적절하게 사람이 설정해주어야하는 불편함이 있었다. 이 불편함을 <strong>신경망</strong>이 해결해줄 수 있다.</p>
<p><span style='background-color:#fff5b1'><strong>신경망</strong>은 <strong>가중치 매개변수의 적절한 값</strong>을 데이터로부터 <strong>자동으로 학습하는 능력</strong>이 있다!</span></p>
<h2 id="31-퍼셉트론에서-신경망으로">3.1 퍼셉트론에서 신경망으로</h2>
<h3 id="신경망의-예">신경망의 예</h3>
<p><img src="https://velog.velcdn.com/images/changh2_00/post/6c172e4a-4148-4a78-83b4-388f344e0b59/image.png" alt=""></p>
<p><strong>은닉층</strong>은 입력층이나 출력층과 달리 말 그대로 사람 눈에 보이지 않기 때문에 붙여진 이름이다.</p>
<p>이전에 본 퍼셉트론과 특별히 달라 보이지 않는다. 이번 장에선 이 둘의 차이점을 알아볼 것이다.</p>
<hr>
<h3 id="퍼셉트론-복습">퍼셉트론 복습</h3>
<p>차이점을 알아보기 위해 퍼셉트론을 복습해보자.</p>
<p><img src="https://velog.velcdn.com/images/changh2_00/post/bb831642-e28d-4022-8321-ed67b8d5a27e/image.png" alt=""></p>
<p><img src="https://velog.velcdn.com/images/changh2_00/post/aab6b1af-bbf4-46a6-95f1-be1afa3acc99/image.png" alt=""></p>
<p>두 신호를 입력받는 퍼셉트론은 위와 같이 나타내어진다. (b=편향, w1,w2=가중치)
하지만 [식 3.1]과 달리 [그림 3-2]에서는 편향 b가 보이지 않는데, 편향 b를 명시하면 아래와 같다.</p>
<p><img src="https://velog.velcdn.com/images/changh2_00/post/144d7749-030c-4de4-a12f-7070e58329f8/image.jpg" alt=""></p>
<p>이에 더해, [식3.1]을 더 간결한 형태로 나타내면 아래와 같다.</p>
<p><img src="https://velog.velcdn.com/images/changh2_00/post/f58822d0-de27-4b09-857d-05966cdcc798/image.png" alt=""></p>
<p>여기서 <strong>신호의 총합</strong>이 <strong>0을 넘으면 1을 출력</strong>하고, <strong>그렇지 않으면 0을 출력</strong>하는 <strong>함수</strong>를 <strong>h(x)</strong>로 나타내주었다. (중요)</p>
<hr>
<h3 id="활성화-함수의-등장">활성화 함수의 등장</h3>
<p>위의 h(x), <span style='background-color:#fff5b1'>입력 신호의 총합을 출력 신호로 변환하는 함수</span>를 <span style='background-color:#fff5b1'><strong>활성화 함수</strong></span>라고 한다. 
이 함수는 입력 신호의 총합이 <strong>&#39;활성화&#39;</strong>를 일으키는지 정하는 역할을 한다.</p>
<p>활성화 함수의 처리 과정을 나타내면 아래와 같다.</p>
<p><img src="https://velog.velcdn.com/images/changh2_00/post/70747ccd-c3f1-4229-b06c-e72b88db451f/image.png" alt=""></p>
<p><img src="https://velog.velcdn.com/images/changh2_00/post/c463cc6c-3f52-49de-8e93-3c6c7f6e6d66/image.jpg" alt=""></p>
<hr>
<h2 id="32-활성화-함수">3.2 활성화 함수</h2>
<blockquote>
<p><strong>퍼셉트론</strong>은 <strong>계단 함수</strong>를 활성화 함수로 사용하고, 
<strong>신경망</strong>은 <strong>시그모이드</strong>와 같은 <strong>매끈한 활성화 함수</strong>를 사용한다.
<strong>+)</strong> <span style='background-color:#dcffe4'><strong>퍼셉트론과 신경망의 가장 큰 차이는 &#39;활성화 함수의 차이&#39;이다!!!</strong></span></p>
</blockquote>
<h3 id="시그모이드-함수">시그모이드 함수</h3>
<p>신경망에서 자주 이용하는 활성화 함수인 <strong>시그모이드 함수</strong>를 식으로 나타내면 아래와 같다.</p>
<p><img src="https://velog.velcdn.com/images/changh2_00/post/c9cfb98e-2938-4ef7-9059-9e4a18b14d5d/image.jpg" alt=""></p>
<pre><code>시그모이드 함수의 구현과 그래프는 아래에서 알아보자.</code></pre><hr>
<h3 id="계단-함수-구현하기">계단 함수 구현하기</h3>
<p>퍼셉트론에서 사용되는 활성화 함수인 계단 함수를 코드로 구현해보자.</p>
<pre><code class="language-python">def step_function(x):
  if x &gt; 0:
    return 1
  else:
    return 0</code></pre>
<p>위의 구현은 직관적이고 간단하지만, 인수 x가 실수만 받을 수 있고 넘파이 배열을 인수로 받을 수 없다.</p>
<pre><code class="language-python">def step_function(x):
  y = x &gt; 0
  return y.astype(np.int)</code></pre>
<p>위와 같이 구현하면 넘파이 배열도 잘 지원된다. 구동원리를 아래를 통해 이해해보자.</p>
<pre><code class="language-python">import numpy as np

x = np.array([-1.0, 1.0, 2.0])
x
# &gt;&gt;&gt; array([-1.,  1.,  2.])
y = x &gt; 0
y
# &gt;&gt;&gt; array([False,  True,  True])
y = y.astype(int)  # 위의 bool 타입을 int 타입으로 타입변환 🚨 교재의 np.int는 더이상 안 쓰인다.🚨
y
# &gt;&gt;&gt; array([0, 1, 1])</code></pre>
<p>이제 계단 함수의 그래프를 그려보자.</p>
<pre><code class="language-python">import numpy as np
import matplotlib.pylab as plt

def step_function(x):
  return np.array(x&gt;0, dtype=int)  # 🚨 교재의 np.int는 더이상 안 쓰인다.🚨

x = np.arange(-5.0, 5.0, 0.1)
y = step_function(x)
plt.plot(x, y)
plt.ylim(-0.1, 1.1)  # y축의 범위 지정
plt.show()</code></pre>
<p><img src="https://velog.velcdn.com/images/changh2_00/post/8e8172e9-2441-4a89-89b9-134650f93ff8/image.png" alt=""></p>
<p><strong>계단 함수</strong>는 0을 경계로 출력이 0에서 1로 바뀐다. 이름처럼 &#39;계단&#39;처럼 생긴 것을 확인할 수 있다.</p>
<hr>
<h3 id="시그모이드-함수-구현하기">시그모이드 함수 구현하기</h3>
<p>이제 시그모이드 함수를 구현해보자.</p>
<pre><code class="language-python">def sigmoid(x):
  return 1 / (1 + np.exp(-x))</code></pre>
<p>위 코드는 넘파이 배열도 제대로 처리해주는데, <strong>브로드캐스팅</strong> 기능 덕분이다. 아래를 통해 확인/복습해보자.</p>
<pre><code class="language-python">&quot;&quot;&quot;넘파이 배열 처리 확인&quot;&quot;&quot;
x = np.array([-1.0, 1.0, 2.0])
sigmoid(x)
# &gt;&gt;&gt; array([0.26894142, 0.73105858, 0.88079708])

&quot;&quot;&quot;브로드 캐스팅 복습&quot;&quot;&quot;
t = np.array([1.0, 2.0, 3.0])
1.0 + t
# &gt;&gt;&gt; array([2., 3., 4.])
1.0 / t
# &gt;&gt;&gt; array([1.        , 0.5       , 0.33333333])

&quot;&quot;&quot;
[브로드 캐스트]
넘파이 배열과 스칼라값의 연산을 넘파이 배열의 원소 각각과 스칼라값의 연산으로 바꿔 수행해준다.
&quot;&quot;&quot;</code></pre>
<p>이제 시그모이드 함수의 그래프를 그려보자.</p>
<pre><code class="language-python">x = np.arange(-5.0, 5.0, 0.1)
y = sigmoid(x)
plt.plot(x, y)
plt.ylim(-0.1, 1.1)  # y축 범위 지정
plt.show()</code></pre>
<p><img src="https://velog.velcdn.com/images/changh2_00/post/90699d97-8929-494a-a9a0-2aa3d440bfae/image.png" alt=""></p>
<p><strong>시그모이드(sigmoid)</strong>란 <strong>&#39;S자 모양&#39;</strong>이라는 뜻이다. 계단 함수처럼 그 모양을 따 이름을 지은 것이다.</p>
<hr>
<h3 id="시그모이드-함수와-계단-함수-비교">시그모이드 함수와 계단 함수 비교</h3>
<p><img src="https://velog.velcdn.com/images/changh2_00/post/ea8f49e4-accc-4449-a971-7bd3835ac42f/image.png" alt=""></p>
<p><span style='background-color:#ffdce0'><strong>차이점</strong></span>
    - <strong>시그모이드 함수는 &#39;매끄러운&#39;</strong> 반면, <strong>계단 함수는 출력이 갑자기 바뀌어</strong>버린다.(0을 경계로)
    - <strong>시그모이드 함수는 실수</strong>(0.731, ..., 0.880 등)를 돌려주는 반면 <strong>계단함수는 0과 1</strong> 중 하나의 값만 돌려준다. 즉, 신경망에서는 &#39;연속적인 실수&#39;가 흐르고, 퍼셉트론에서는 0 혹은 1만 흐른다.</p>
<p><span style='background-color:#d1ebf9'><strong>공통점</strong></span>
    - <strong>입력이 중요하면 큰 값</strong>을 출력하고, <strong>중요하지 않으면 작은 값</strong>을 출력한다.
    - <strong>비선형 함수</strong>이다.</p>
<hr>
<h3 id="비선형-함수">비선형 함수</h3>
<p>계단 함수와 시그모이드 함수는 공통적으로 <strong>&#39;비선형 함수&#39;</strong>인데, 
계단 함수는 구부러진 직선, 시그모이드 함수는 곡선으로 나타나기 때문이다.</p>
<p><span style='background-color:#fff5b1'><strong>신경망</strong>에서는 활성화 함수로 <strong>비선형 함수를 사용해야만 하는데,</strong>
<strong>선형 함수를 이용하면 신경망의 층을 깊게 하는 의미가 없어지기 때문</strong>이다.</span></p>
<blockquote>
<p>의미가 없어지는 이유는,
층을 깊게 만든 네트워크가 &#39;층이 하나밖에 없는(은닉층이 없는) 네트워크&#39; 와 똑같은 기능을 하기 때문이다.
h(x) = c*x 를 활성화 함수(선형함수)로 사용한 3층 네트워크를 생각해보면,
y(x) = h(h(h(x)))로 나타내지는데, 이는 곧 y(x) = c^3*x로 나타내지고,
이는 곧 a = c^3 인 y(x) = a*x와 같게 된다. 
즉, 층이 하나밖에 없는(은닉층이 없는) 네트워크로 표현이 된다.</p>
</blockquote>
<hr>
<h3 id="relu-함수">ReLU 함수</h3>
<p>최근에는 시그모이드 함수 대신 <strong>ReLU(Rectified Linear Unit) 함수</strong>를 주로 이용한다.
<span style='background-color:#fff5b1'><strong>ReLU</strong>는 <strong>입력이 0을 넘으면 그 입력을 그대로 출력</strong>하고, <strong>0 이하면 0을 출력</strong>하는 함수이다.</span></p>
<p><img src="https://velog.velcdn.com/images/changh2_00/post/d6799054-289e-4730-b3a5-0b8a0c13d2f8/image.png" alt=""></p>
<p><img src="https://velog.velcdn.com/images/changh2_00/post/18f6f7c3-8d51-4415-a926-b38029ef7650/image.png" alt=""></p>
<p>그래프에 수식에서 보이듯이 ReLU는 간단한 함수인데, 구현도 아래와 같이 간단하다.</p>
<pre><code class="language-python">def relu(x):
  return np.maximum(0, x)</code></pre>
<hr>
<h2 id="33-다차원-배열의-계산">3.3 다차원 배열의 계산</h2>
<p>신경망을 구현하기에 앞서, 이를 효율적으로 구현하기 위해 
넘파이의 다차원 배열을 사용한 계산법에 대해 알아보자.</p>
<h3 id="다차원-배열">다차원 배열</h3>
<p>우선 1차원 배열을 작성해보자.</p>
<pre><code class="language-python">import numpy as np

A = np.array([1, 2, 3, 4])
print(A)
# &gt;&gt;&gt; [1 2 3 4]
np.ndim(A)  # 배열의 차원 수 확인
# &gt;&gt;&gt; 1
A.shape  # 배열의 형상 확인
# &gt;&gt;&gt; (4,)
A.shape[0]</code></pre>
<p>이어서 2차원 배열을 작성해보자.</p>
<pre><code class="language-python">B = np.array([[1,2], [3,4], [5,6]])
print(B)
# &gt;&gt;&gt; [[1 2]
#        [3 4]
#        [5 6]]
np.ndim(B)  # 배열의 차원 수 확인
# &gt;&gt;&gt; 2
B.shape  # 배열의 형상 확인
# &gt;&gt;&gt; (3, 2)</code></pre>
<p>알다시피, 위와 같은 2차원 배열은 행렬이라고 부른다.</p>
<p><img src="https://velog.velcdn.com/images/changh2_00/post/e958d205-0b58-4cf3-a79a-960482b6788b/image.png" alt=""></p>
<hr>
<h3 id="행렬의-곱">행렬의 곱</h3>
<p>알고 있겠지만, 행렬의 곱을 구하는 방법은 아래와 같다.</p>
<p><img src="https://velog.velcdn.com/images/changh2_00/post/61e47dce-4719-474e-821f-944a51c2a4fb/image.png" alt=""></p>
<p>이를 코드로 구현하면 아래와 같다.</p>
<pre><code class="language-python">&quot;&quot;&quot;형상이 2x2로 같은 행렬의 곱&quot;&quot;&quot;
A = np.array([[1,2], [3,4]])
A.shape
# &gt;&gt;&gt; (2, 2)
B = np.array([[5,6], [7,8]])
B.shape
# &gt;&gt;&gt; (2, 2)
np.dot(A, B)  # 행렬의 곱 연산
# &gt;&gt;&gt; array([[19, 22],
#            [43, 50]])

&quot;&quot;&quot;형상이 2x3 과 3x2로 다른 행렬의 곱&quot;&quot;&quot;
A = np.array([[1,2,3], [4,5,6]])
A.shape
# &gt;&gt;&gt; (2, 3)
B = np.array([[1,2], [3,4], [5,6]])
B.shape
# &gt;&gt;&gt; (3, 2)
np.dot(A, B)  # 행렬의 곱 연산
# &gt;&gt;&gt; array([[22, 28],
#            [49, 64]])

&quot;&quot;&quot;🚨 형상이 2x2 와 2x3 으로 차원의 원소 수가 일치하지 않는 행렬의 곱 -&gt; 오류🚨&quot;&quot;&quot;
C = np.array([[1,2], [3,4]])
C.shape
# &gt;&gt;&gt; (2, 2)
A.shape
# &gt;&gt;&gt; (2, 3)
np.dot(A, C)
# &gt;&gt;&gt; 
---------------------------------------------------------------------------
ValueError                                Traceback (most recent call last)
&lt;ipython-input-41-bb5afb89b162&gt; in &lt;cell line: 1&gt;()
----&gt; 1 np.dot(A, C)

ValueError: shapes (2,3) and (2,2) not aligned: 3 (dim 1) != 2 (dim 0)</code></pre>
<p><img src="https://velog.velcdn.com/images/changh2_00/post/df4a040d-74d8-428d-98a6-e855199afac9/image.png" alt=""></p>
<p><img src="https://velog.velcdn.com/images/changh2_00/post/9cf23ff3-5f3b-4628-9e42-25ff0b52a0a6/image.png" alt=""></p>
<p>[그림 3-13]을 구현하면 아래와 같다.</p>
<pre><code class="language-python">A = np.array([[1,2], [3,4], [5,6]])
A.shape
# &gt;&gt;&gt; (3, 2)
B = np.array([7,8])
B.shape
# &gt;&gt;&gt; (2,)
np.dot(A, B)  # 행렬의 곱 연산
# &gt;&gt;&gt; array([23, 53, 83])</code></pre>
<hr>
<h3 id="신경망에서의-행렬-곱">신경망에서의 행렬 곱</h3>
<p>넘파이 행렬을 사용해서, 편향과 활성화 함수를 생략한 간단한 신경망을 구현해보자.</p>
<p><img src="https://velog.velcdn.com/images/changh2_00/post/52e615ae-71d0-4e5a-8800-236983b1989c/image.jpeg" alt=""></p>
<pre><code class="language-python">X = np.array([1,2])
X.shape
# &gt;&gt;&gt; (2,)
W = np.array([[1,3,5], [2,4,6]])
print(W)
# &gt;&gt;&gt; [[1 3 5]
#        [2 4 6]]
W.shape
# &gt;&gt;&gt; (2, 3)
Y = np.dot(X, W)
print(Y)
# &gt;&gt;&gt; [ 5 11 17] </code></pre>
<p><strong><code>np.dot</code></strong> 함수를 사용하면 Y의 원소가 몇 만개이던 한 번의 연산으로 결과를 계산할 수 있다.
그렇기에 신경망을 구현할 때 행렬의 곱으로 한번에 계산해주는 기능이 아주 중요하다!</p>
<hr>
<h2 id="34-3층-신경망-구현하기">3.4 3층 신경망 구현하기</h2>
<p>이제 좀 더 본격적으로, 3층 신경망에서 수행되는 입력부터 출력까지의 처리(순방향)를 구현해보자.
<img src="https://velog.velcdn.com/images/changh2_00/post/14aebc35-2a76-4213-b867-6193a3e6d187/image.png" alt=""></p>
<blockquote>
<h3 id="표기법-설명">표기법 설명</h3>
<p><img src="https://velog.velcdn.com/images/changh2_00/post/f4dcd204-6fcc-4bb4-ad6c-85def2bed9ab/image.png" alt=""></p>
</blockquote>
<h3 id="각-층의-신호-전달-구현하기">각 층의 신호 전달 구현하기</h3>
<p>먼저, <strong>&#39;입력층 -&gt; 1층의 첫번째 뉴런&#39;</strong> 으로 가는 신호를 살펴보자.</p>
<p><img src="https://velog.velcdn.com/images/changh2_00/post/f71553ef-a0a2-4e13-9509-c654d527b178/image.png" alt=""></p>
<p><img src="https://velog.velcdn.com/images/changh2_00/post/ca5d443a-3383-40ff-8479-89da41adc524/image.png" alt=""></p>
<p>행렬의 곱을 이용하면** 1층의 &#39;가중치 부분&#39;**을 아래처럼 간소화 할 수 있다.
<img src="https://velog.velcdn.com/images/changh2_00/post/cfc9ab62-a907-4239-bff2-b307d5647da9/image.jpg" alt=""></p>
<p>이때 각 표기는 아래와 같다.
<img src="https://velog.velcdn.com/images/changh2_00/post/37a7089f-f318-40e1-8f89-3d6f5392bff6/image.png" alt=""></p>
<p>그럼 [식 3.9]를 코드로 구현해보자.</p>
<pre><code class="language-python">&quot;&quot;&quot; 입력층 -&gt; 1층 가중치 부분 &quot;&quot;&quot;
X = np.array([1.0, 0.5])
W1 = np.array([[0.1, 0.3, 0.5], [0.2, 0.4, 0.6]])
B1 = np.array([0.1, 0.2, 0.3])

print(W1.shape)    # &gt;&gt;&gt; (2, 3)
print(X.shape)    # &gt;&gt;&gt; (2,)
print(B1.shape)    # &gt;&gt;&gt; (3,)

A1 = np.dot(X, W1) + B1</code></pre>
<p>이어서 <strong>1층의 &#39;활성화 함수&#39;에서의 처리</strong>를 살펴보자.</p>
<p><img src="https://velog.velcdn.com/images/changh2_00/post/701ff9bd-1fb7-44ae-a255-fe5de63c88d5/image.png" alt=""></p>
<p>이를 코드로 구현하면 아래와 같다.</p>
<pre><code class="language-python">&quot;&quot;&quot; 입력층 -&gt; 1층 활성화 함수 부분 &quot;&quot;&quot;
Z1 = sigmoid(A1)

print(A1)  # &gt;&gt;&gt; [0.3 0.7 1.1]
print(Z1)  # &gt;&gt;&gt; [0.57444252 0.66818777 0.75026011]</code></pre>
<p>이제 <strong>1층에서 2층으로의 신호 전달 과정</strong>과 구현을 살펴보자.</p>
<p><img src="https://velog.velcdn.com/images/changh2_00/post/53ae3180-0c89-4f15-8f2e-eb33ebf06c39/image.png" alt=""></p>
<pre><code class="language-python">&quot;&quot;&quot; 1층 -&gt; 2층 신호 전달 &quot;&quot;&quot;
W2 = np.array([[0.1, 0.4], [0.2, 0.5], [0.3, 0.6]])
B2 = np.array([0.1, 0.2])

print(Z1.shape)  # &gt;&gt;&gt; (3,)
print(W2.shape)  # &gt;&gt;&gt; (3, 2)
print(B2.shape)  # &gt;&gt;&gt; (2,)

A2 = np.dot(Z1, W2) + B2
Z2 = sigmoid(A2)</code></pre>
<p>마지막으로 <strong>2층에서 출력층으로의 신호 전달 과정</strong>과 구현을 살펴보자.</p>
<p><img src="https://velog.velcdn.com/images/changh2_00/post/0afa3e2f-8a38-4450-be73-ed101eec4bb0/image.png" alt=""></p>
<pre><code class="language-python">&quot;&quot;&quot; 2층 -&gt; 3층 신호 전달 &quot;&quot;&quot;
def identity_function(x):
  return x

W3 = np.array([[0.1, 0.3], [0.2, 0.4]])
B3 = np.array([0.1, 0.2])

A3 = np.dot(Z2, W3) + B3
Y = identity_function(A3)  # 혹은 Y = A3</code></pre>
<p>지금은 <span style='background-color:#ffdce0'><strong>신경망의 출력층에</strong></span> 항등 함수인 <code>identity_function()</code> 을 정의하고 사용했지만,
실제로는 <span style='background-color:#ffdce0'><strong>모델이 풀고자 하는 문제의 성질에 맞게 활성화 함수를 적용</strong></span>한다.</p>
<blockquote>
</blockquote>
<p><strong>회귀</strong>에는 항등 함수를, 
<strong>2 클래스 분류 (이중 분류)</strong>에는 시그모이드 함수를, 
<strong>다중 클래스 분류(다중 분류)</strong>에는 소프트맥스 함수를 사용한다.</p>
<hr>
<h3 id="구현-정리">구현 정리</h3>
<pre><code class="language-python">def init_network():
  network = {}
  network[&#39;W1&#39;] = np.array([[0.1, 0.3, 0.5], [0.2, 0.4, 0.6]]) #2*3
  network[&#39;b1&#39;] = np.array([0.1, 0.2, 0.3]) #1*3
  network[&#39;W2&#39;] = np.array([[0.1, 0.4], [0.2, 0.5], [0.3, 0.6]]) #3*2
  network[&#39;b2&#39;] = np.array([0.1, 0.2]) #1*2
  network[&#39;W3&#39;] = np.array([[0.1, 0.3], [0.2, 0.4]]) #2*2
  network[&#39;b3&#39;] = np.array([0.1, 0.2]) #1*2

  return network

#신호가 순방향(입력에서 출력 방향)으로 전달됨(순전파)임을 알리기 위함이다.
def forward(network, x):
  W1, W2, W3 = network[&#39;W1&#39;], network[&#39;W2&#39;], network[&#39;W3&#39;]
  b1, b2, b3 = network[&#39;b1&#39;], network[&#39;b2&#39;], network[&#39;b3&#39;]

  a1 = np.dot(x, W1) + b1
  z1 = sigmoid(a1)
  a2 = np.dot(z1, W2) + b2
  z2 = sigmoid(a2)
  a3 = np.dot(z2, W3) + b3
  y = identity_function(a3)

  return y

network = init_network()
x = np.array([1.0, 0.5]) #1*2
y = forward(network, x)
print(y)</code></pre>
<hr>
<h2 id="35-출력층-설계하기">3.5 출력층 설계하기</h2>
<p>신경망에서 <span style='background-color:#dcffe4'><strong>출력층에 사용되는 활성화 함수</strong>에 따라 <strong>회귀</strong>인지 <strong>분류</strong>인지가 나뉘어진다.</span>
(일반적으로)
<strong>회귀</strong> -- 항등함수
<strong>분류</strong> -- 소프트맥스</p>
<h3 id="항등-함수와-소프트맥스-함수-구현">항등 함수와 소프트맥스 함수 구현</h3>
<p>회귀에 사용되는 <strong>항등 함수</strong>는 입력을 그대로 출력하기 때문에 아래의 그림과 같이 나타난다.</p>
<p><img src="https://velog.velcdn.com/images/changh2_00/post/f4c3b0cf-da2f-40c6-8d99-e84def403a07/image.jpeg" alt=""></p>
<p>분류에 사용되는 <strong>소프트맥스 함수</strong>를 나타내는 식은 아래와 같은데,
(n = 출력층의 뉴런 수, yk = 그 중 k번째 출력, ak = k번째 입력 신호)</p>
<p><img src="https://velog.velcdn.com/images/changh2_00/post/1d9fd6e7-cd86-40a9-89da-b035223b81e4/image.png" alt=""></p>
<p>출력층의 각 뉴런이 모든 입력 신호에서 영향을 받기 때문에, 이를 그림으로 나타내면 아래와 같다.</p>
<p><img src="https://velog.velcdn.com/images/changh2_00/post/30e720f0-0a4e-4b54-8f22-88479cfcbc5f/image.jpeg" alt=""></p>
<p>그럼 위의 소프트 맥스 함수를 구현해보자.</p>
<pre><code class="language-python">def softmax(a):
  exp_a = np.exp(a)
  sum_exp_a = np.sum(exp_a)
  y = exp_a / sum_exp_a

  return y</code></pre>
<hr>
<h3 id="소프트맥스-함수-구현-시-주의점">소프트맥스 함수 구현 시 주의점</h3>
<p>위에서 구현한 코드는 지수함수 e 때문에 <strong>오버플로</strong>의 문제가 있다.</p>
<blockquote>
<p>지수 함수는 쉽게 매우 큰 값을 내뱉는다.
e^100은 0이 40개가 넘는 큰 값이 되고, e^1000은 무한대를 뜻하는 inf를 내뱉는다.
이렇게 큰 값끼리 나눗셈을 하면 결과 수치가 <strong>불안정</strong>해진다</p>
</blockquote>
<p>이 문제를 해결하기 위해 </p>
<blockquote>
<p><img src="https://velog.velcdn.com/images/changh2_00/post/f4e0b4b6-88d0-47e6-91d5-cc74f40e2578/image.jpeg" alt=""></p>
</blockquote>
<p><img src="https://velog.velcdn.com/images/changh2_00/post/0421b301-e43d-4566-a884-9f1de9f01663/image.png" alt=""></p>
<p>위와 같이 식을 바꿔주는데, 여기서 <strong>C&#39;</strong>에 어떤 값을 대입해도 상관없지만, <strong>오버플로를 막을 목적으로는 입력 신호 중 최댓값에 (-)를 붙여 이용</strong>하는 것이 일반적이다.</p>
<p>아래 예시를 통해 확인해보자.</p>
<pre><code class="language-python">a = np.array([1010, 1000, 990])
np.exp(a) / np.sum(np.exp(a))
# &gt;&gt;&gt; array([nan, nan, nan])  # 🚨 큰 값들의 나눗셈이라 제대로 계산되지 않는다! 🚨
c = np.max(a)
a - c
# &gt;&gt;&gt; array([  0, -10, -20])
np.exp(a - c) / np.sum(np.exp(a - c))
# &gt;&gt;&gt; array([9.99954600e-01, 4.53978686e-05, 2.06106005e-09])  # 올바르게 계산된다!</code></pre>
<p>이렇게 수정한 뒤에 소프트맥스 함수를 다시 구현해보자.</p>
<pre><code class="language-python">def softmax(a):
  c = np.max(a)
  exp_a = np(np.exp(a - c))  # 오버플로 방지
  sum_exp_a = np.sum(exp_a)
  y = exp_a / sum_exp_a

  return y</code></pre>
<hr>
<h3 id="소프트맥스-함수의-특징">소프트맥스 함수의 특징</h3>
<pre><code class="language-python">a = np.array([0.3, 2.9, 4.0])
y = softmax(a)
print(y)
# &gt;&gt;&gt; [0.01821127 0.24519181 0.73659691]
np.sum(y)
# &gt;&gt;&gt; 1.0</code></pre>
<p><span style='background-color:#fff5b1'>각각의 <strong>출력은 0~1 사이의 실수</strong>이고, <strong>총합은 1</strong>이 된다는 성질이 있다.</span>
이 덕분에 소프트맥스 함수의 출력을 <span style='background-color:#fff5b1'><strong>&#39;확률&#39;</strong></span>로 해석할 수 있다!</p>
<p>위의 예시에서 &#39;0번째 클래스는 1.8%, 1번째 클래스는 24.5%, 2번째 클래스는 73% 이므로,
2번째 클래스가 가장 확률이 높아 분류의 답은 2번째 클래스이다&#39; 라고 해석된다.</p>
<p>그리고, 소프트맥스 함수를 적용해도 각 원소의 대소 관계는 변하지 않기 때문에, 
<strong>결과적으로,</strong> 신경망으로 분류할 때는 <strong>출력층의 소프트맥스 함수를 생략해도 된다!</strong> </p>
<blockquote>
<p>기계학습은 <strong>&#39;학습&#39;</strong> 과 <strong>&#39;추론&#39;</strong> 의 두 단계를 거쳐 이뤄지는데,
<strong>학습단계</strong>에서는 출력층에서 소프트맥스 함수를 사용해야하고,
<strong>추론단계</strong>에서는 출력층에서 소프트맥스 함수를 생략해도 된다. (성능을 위해 다들 생략한다)</p>
</blockquote>
<hr>
<h3 id="출력층의-뉴런-수-정하기">출력층의 뉴런 수 정하기</h3>
<p>출력층의 뉴런 수는 풀려는 문제에 맞게 적절히 정해야 하는데,
<strong>분류</strong>에서는 분류하고 싶은 클래스의 수로 설정하는게 일반적이다.</p>
<p>ex) 입력 이미지를 숫자 0~9 중 하나로 분류하는 문제라면 아래와 같이 출력층 뉴런을 10개로 설정한다.
<img src="https://velog.velcdn.com/images/changh2_00/post/d88c1935-fc40-433e-b846-2fc641877da3/image.png" alt=""></p>
<hr>
<h2 id="36-손글씨-숫자-인식">3.6 손글씨 숫자 인식</h2>
<blockquote>
<p>신경망도 기계학습과 동일하게 <strong>훈련</strong>과 <strong>추론</strong>과정을 거치는데,
훈련과정에서는 순전파 후에 역전파를 통해 가중치를 조정하지만, 
추론과정에서는 <strong>순전파</strong>를 통해 <strong>단순히 예측을 계산</strong>하기만 한다.</p>
</blockquote>
<h3 id="mnist-데이터셋">MNIST 데이터셋</h3>
<p><img src="https://velog.velcdn.com/images/changh2_00/post/de6fcbff-d13a-404a-bf6d-c5986042ca7d/image.png" alt=""></p>
<p>MNIST는 아주 유명한 데이터 셋으로, 손글씨 숫자 이미지들의 집합이다.
MINIST 데이터셋은 28x28 크기의 회색조 이미지이다.
아래 코드에서 데이터를 확인해보자.</p>
<blockquote>
<p><strong>+) Colab에서 교재의 MNIST 데이터셋 사용하는 방법</strong>
코랩에서 노트를 하나 만들어서 아래의 코드를 각 셀에 작성하고 실행하면 
내 구글 드라이브에 교재가 제공하는 깃헙 파일들이 클론되고, 코랩에서 바로 사용 가능하다!</p>
</blockquote>
<pre><code class="language-python">from google.colab import drive
drive.mount(&#39;/content/drive&#39;)</code></pre>
<pre><code class="language-bash">cd &quot;/content/drive/My Drive&quot;</code></pre>
<pre><code class="language-bash">!git clone https://github.com/kchcoo/WegraLee-deep-learning-from-scratch</code></pre>
<pre><code class="language-bash">cd WegraLee-deep-learning-from-scratch</code></pre>
<pre><code class="language-python">import sys, os
sys.path.append(os.pardir)  # 부모 디렉터리의 파일을 가져올 수 있도록 설정
from dataset.mnist import load_mnist

(x_train, t_train), (x_test, t_test) = load_mnist(flatten=True, normalize=False)

print(x_train.shape)  # 입력 데이터
print(t_train.shape)  # 타깃 데이터
print(x_test.shape)
print(t_train.shape)</code></pre>
<pre><code>&gt;&gt;&gt; (60000, 784)  #  60,000개의 784차원 벡터로 이루어진 2차원 배열
    (60000,)  #  60,000개의 단일 정수로 이루어진 1차원 배열
    (10000, 784)
    (60000,)

# 784차원인 이유는 28x28 크기의 이미지를 flatten=True 를 통해 1차원 배열로 평탄하게 만들었기 때문</code></pre><blockquote>
<p><strong>load_mnist</strong>의 매개변수
<code>flatten</code> : 입력 이미지를 평탄하게, 즉 1차원 배열로 만들지 정함
<code>normalize</code> : 입력 이미지의 픽셀값을 0.0~1.0 사이의 값으로 정규화할지를 정함
<code>one_hot_label</code> : 레이블을 원-핫 인코딩 형태로 저장할지를 정함</p>
</blockquote>
<pre><code>위의 결과를 이해하려면 아래를 참고하면 좋다. 

x_train = [
    [0, 0, 0, ..., 0, 0, 0],  # 1번째 이미지의 픽셀 값 (784개)
    [0, 255, 0, ..., 255, 0, 0],  # 2번째 이미지의 픽셀 값 (784개)
    ...
    [0, 255, 0, ..., 255, 0, 0]  # 60000번째 이미지의 픽셀 값(784개)
]

t_train = [5, 0, ... 7]
            # 60000개</code></pre><p>데이터를 확인해보기 위해 MNIST 이미지를 화면으로 띄워보자.</p>
<pre><code class="language-python">from matplotlib.pyplot import imshow

(x_train, t_train), (x_test, t_test) = load_mnist(flatten=True, normalize=False)

img = x_train[0]
label = t_train[0]
print(label)  # &gt;&gt;&gt; 5

print(img.shape)  # &gt;&gt;&gt; (784,)
img = img.reshape(28, 28)  # 형상을 원래 이미지의 크기로 변형
print(img.shape)  # &gt;&gt;&gt; (28, 28)

imshow(img)  
# 코랩에서 출력하기 위해 PIL을 사용하는 img_show 함수 대신에 pyplot의 imshow를 사용했다.</code></pre>
<p><img src="https://velog.velcdn.com/images/changh2_00/post/82d9e711-dc12-4b0e-983b-081585c5a6df/image.png" alt=""></p>
<hr>
<h3 id="신경망의-추론-처리">신경망의 추론 처리</h3>
<p>이제 이 MNIST 데이터셋을 가지고 <strong>추론</strong>을 수행하는 신경망을 구현해보자.
입력층 뉴런을 784(28x28)개, 출력층 뉴런을 10(0부터9)개로 설정한다.
2개의 은닉층을 갖는데, 각각 임의로 50개, 100개의 뉴런을 갖도록 설정한다.</p>
<pre><code class="language-python">import sys, os
sys.path.append(os.pardir)  
import numpy as np
import pickle
from dataset.mnist import load_mnist
from common.functions import sigmoid, softmax

def get_data():
    (x_train, t_train), (x_test, t_test) = load_mnist(normalize=True, flatten=True, one_hot_label=False)
    return x_test, t_test

def init_network():
    with open(&quot;/content/drive/My Drive/WegraLee-deep-learning-from-scratch/ch03/sample_weight.pkl&quot;, &#39;rb&#39;) as f:
        network = pickle.load(f)
    return network

def predict(network, x):
    w1, w2, w3 = network[&#39;W1&#39;], network[&#39;W2&#39;], network[&#39;W3&#39;]
    b1, b2, b3 = network[&#39;b1&#39;], network[&#39;b2&#39;], network[&#39;b3&#39;]

    a1 = np.dot(x, w1) + b1
    z1 = sigmoid(a1)
    a2 = np.dot(z1, w2) + b2
    z2 = sigmoid(a2)
    a3 = np.dot(z2, w3) + b3
    y = softmax(a3)

    return y</code></pre>
<pre><code class="language-python">x, t = get_data()
network = init_network()

accuracy_cnt = 0

for i in range(len(x)):
  y = predict(network, x[i])
  p = np.argmax(y)  # 확률이 가장 높은 원소의 인덱스를 얻는다
  if p == t[i]:
    accuracy_cnt += 1

print(&quot;Accuracy:&quot; + str(float(accuracy_cnt) / len(x)))</code></pre>
<pre><code>&gt;&gt;&gt; Accuracy:0.9352</code></pre><p>추론의 정확도(분류가 얼마나 올바른가)가 93%가 나온다.</p>
<p>이 예에서 load_mnist 함수의 매개변수인 <code>normalize</code>를 True로 설정했는데,
0<del>255 범위인 각 픽셀의 값을 0.0</del>1.0 범위로 변환해준다.
이처럼 <span style='background-color:#fff5b1'>데이터를 특정 범위로 변환하는 처리</span>를 <span style='background-color:#fff5b1'><strong>정규화</strong></span>라고 하고,  (스케일을 맞춤 -&gt; 보통 표준편차를 사용)
이렇게 입력 데이터에 특정 변환을 가하는 것을 <strong>전처리</strong>라고 한다.</p>
<hr>
<h3 id="배치-처리">배치 처리</h3>
<p>이번엔 위의 코드 구현에서 입력 데이터와 <strong>가중치 매개변수</strong>의 <strong>&#39;형상&#39;</strong>에 주의를 기울여보자.</p>
<pre><code class="language-python">x, _ = get_data()
network = init_network()
w1, w2, w3 = network[&#39;W1&#39;], network[&#39;W2&#39;], network[&#39;W3&#39;]

x.shape
# &gt;&gt;&gt; (10000, 784)
x[0].shape
# &gt;&gt;&gt; (784,)
W1.shape
# &gt;&gt;&gt; (784, 50)
W2.shape
# &gt;&gt;&gt; (50, 100)
W3.shape
# &gt;&gt;&gt; (100, 10)</code></pre>
<p><img src="https://velog.velcdn.com/images/changh2_00/post/259b4f20-2b71-43c0-aec3-8d5cf54f40f9/image.jpeg" alt=""></p>
<p>각 층의 가중치 형상을 보면, 다차원 배열의 대응하는 차원의 원소 수가 일치하는 것을 볼 수 있다.
<strong>원소가 784개인 1차원 배열이 입력</strong>되어 <strong>원소가 10개인 1차원 배열이 출력</strong>되는 흐름이다.
위는 데이터를 1장만 입력했을 때의 처리 흐름이다.</p>
<p>그러면, 이제 이미지 100장을 한꺼번에 입력하는 경우를 생각해보자.
(이미지 100개를 묶어 predict() 함수에 한번에 넘겨서)</p>
<p><img src="https://velog.velcdn.com/images/changh2_00/post/8892a5ae-c9be-4a88-96c7-0470b92d18e7/image.jpeg" alt=""></p>
<p>입력 데이터의 형상은 100x784, 출력 데이터의 형상은 100x10이 된다.
즉, <strong>100장 분량의 입력 데이터의 결과가 한 번에 출력된다!</strong></p>
<p>이렇게 <span style='background-color:#fff5b1'>하나로 묶은 입력 데이터</span>를 <span style='background-color:#fff5b1'><strong>배치(batch)</strong></span>라고 한다. 
위에선 이미지가 지폐처럼 다발로 묶여 있다고 생각하면 좋다.</p>
<blockquote>
<p><strong>배치 처리</strong>를 수행함으로써 큰 배열로 이뤄진 계산을 하게 되는데,
컴퓨터에서는 <strong>큰 배열을 한꺼번에 계산하는 것</strong>이 분할된 <strong>작은 배열</strong>을 여러번 계산하는 것보다 <strong>빠르기에 처리 시간을 대폭 줄여준다!</strong></p>
</blockquote>
<p>그럼 이제 배치 처리를 구현해보자. (위의 코드에서 약간 변형됨!)</p>
<pre><code class="language-python">x, t = get_data()
network = init_network()

batch_size = 100 # 배치 크기
accuracy_cnt = 0

for i in range(0, len(x), batch_size):
    x_batch = x[i:i+batch_size]
    y_batch = predict(network, x_batch)
    p = np.argmax(y_batch, axis=1)
    accuracy_cnt += np.sum(p == t[i:i+batch_size])

print(&quot;Accuracy:&quot; + str(float(accuracy_cnt) / len(x)))</code></pre>
<hr>
<h2 id="37-정리">3.7 정리</h2>
<ul>
<li>신경망에서는 활성화 함수로 시그모이드 함수와 ReLU 함수 같은 매끄럽게 변화하는 함수를 이용한다</li>
<li>넘파이의 다차원 배열을 잘 사용하면 신경망을 효율적으로 구현할 수 있다.</li>
<li>기계학습 문제는 크게 회귀와 분류로 나눌 수 있다.</li>
<li>출력층의 활성화 함수로는 주로 회귀에서는 항등 함수를, 분류에서는 소프트맥스 함수를 이용한다.</li>
<li>분류에서는 출력층의 뉴런 수를 분류하려는 클래스 수와 같게 설정한다.</li>
<li>입력 데이터를 묶은 것을 배치라하며, 추론 처리를 이 배치 단위로 진행하면 결과를 훨씬 빠르게 얻을 수 있다.</li>
</ul>
]]></description>
        </item>
        <item>
            <title><![CDATA[[혼공머신] 4-2장 확률적 경사 하강법]]></title>
            <link>https://velog.io/@changh2_00/%ED%98%BC%EA%B3%B5%EB%A8%B8%EC%8B%A0-4-2%EC%9E%A5-%ED%99%95%EB%A5%A0%EC%A0%81-%EA%B2%BD%EC%82%AC-%ED%95%98%EA%B0%95%EB%B2%95</link>
            <guid>https://velog.io/@changh2_00/%ED%98%BC%EA%B3%B5%EB%A8%B8%EC%8B%A0-4-2%EC%9E%A5-%ED%99%95%EB%A5%A0%EC%A0%81-%EA%B2%BD%EC%82%AC-%ED%95%98%EA%B0%95%EB%B2%95</guid>
            <pubDate>Tue, 05 Nov 2024 05:10:26 GMT</pubDate>
            <description><![CDATA[<hr>
<p>[혼자 공부하는 머신러닝+딥러닝] 교재 4장을 기반으로 작성되었습니다.</p>
<hr>
<p>럭키백 이벤트가 많은 인기를 얻어 새로운 생선을 많이 추가해야하는데 훈련데이터를 얻는데 시간이 오래 걸린다. 기다리기만 할 수는 없어서, 학습 방법을 생각해봤다.</p>
<ol>
<li>기존의 훈련 데이터에 새로운 데이터를 추가하여 모델을 매일 다시 훈련</li>
</ol>
<p>--&gt; 초반엔 괜찮지만 데이터가 쌓여갈수록 서버의 용량이 늘어난다.
2. 새로운 데이터를 추가할 때 이전 데이터를 버려 데이터의 크기를 일정하게 유지
--&gt; 아주 중요한 데이터가 버려질 수 있음
3. 이전에 훈련한 모델을 버리지 않고 새로운 데이터에 대해서만 조금씩 더 훈련
--&gt; <strong>Good</strong></p>
<h1 id="점진적인-학습">점진적인 학습</h1>
<p><span style='background-color:#fff5b1'>이전에 훈련한 모델을 버리지 않고 새로운 데이터에 대해서만 조금씩 더 훈련하는 방식</span>을 <span style='background-color:#fff5b1'>
  <strong>점진적 학습(온라인 학습)</strong></span>이라고 한다.
대표적인 점진적 학습 알고리즘으론 <span style='background-color:#fdd7d7'><strong>확률적 경사 하강법</strong></span>이 있다.</p>
<hr>
<h2 id="확률적-경사-하강법">확률적 경사 하강법</h2>
<p>** = SGD: Stochastic Gradient Descent**</p>
<p>우선, <strong>경사 하강법</strong>이란 말그대로 &#39;경사를 따라 내려가는 방법&#39;을 말하는데, 자세히는
손실함수의 그래프의 가장 낮은 지점을 향해 내려가는 방법이다. (가장 빠르게!) == (가장 가파른 길로!)</p>
<p>경사 하강법은 훈련 세트를 사용하여 가장 가파른 길을 찾는데, 
<strong>딱 하나의 샘플을 훈련 세트에서 랜덤하게 골라 가장 가파른 길을 찾는다.</strong>
여기서 <span style='background-color:#fdd7d7'><strong>&#39;랜덤하게&#39;</strong> 하나의 샘플을 고르는 것</span>이 바로 <span style='background-color:#fdd7d7'><strong>&#39;확률적&#39; 경사 하강법</strong></span>이다</p>
<p>모든 샘플을 다 사용해도 최하점을 찾지 못 했으면 어떻게 될까?
다시 처음부터 시작한다!
여기서 <strong>훈련 세트를 한번 모두 사용하는 과정</strong>을 <strong>에포크(epoch)</strong>라고 한다.
실제 경사하강법은 수십, 수백번의 에포크를 수행한다.</p>
<p>훈련 세트에서 여러 개의 샘플을 꺼내서 경사를 내려가면 <strong>미니배치 경사 하강법</strong>,
훈련 세트의 모든 샘플을 꺼내서 경사를 내려가면 <strong>배치 경사 하강법</strong> 이라고 한다.</p>
<p><img src="https://velog.velcdn.com/images/changh2_00/post/f2568469-53f4-41bc-ab85-1b22b52e9adb/image.png" alt=""></p>
<hr>
<h2 id="손실-함수">손실 함수</h2>
<p><span style='background-color:#fff5b1'><strong>손실 함수</strong></span>란, <span style='background-color:#fff5b1'>모델이 예측한 값과 실제 값 사이의 차이를 측정하는 함수   </span>이다.
이를 통해 알고리즘이 얼마나 엉터리인지 측정할 수 있다.</p>
<p>기본적으로 손실 함수는 미분 가능해야한다. 
즉, 값이 듬성듬성 있지 않고 연속적이어야 한다.</p>
<p><img src="https://velog.velcdn.com/images/changh2_00/post/1fd26599-7169-4ed2-b2bc-b0442777cb33/image.png" alt=""></p>
<p>이렇게 연속적인 손실 함수를 만들기 위해서 <strong>확률값</strong>을 사용한다.
(확률은 0과 1사이의 연속적인 값을 갖는다)</p>
<hr>
<h2 id="로지스틱-손실-함수">로지스틱 손실 함수</h2>
<pre><code>예측        정답(타깃)
 1      =       1
 0     !=       1
 0      =       0
 1     !=       0
</code></pre><p>로지스틱 회귀의 모델이 확률을 출력했던걸 다시 생각하고,
위 샘플 4개의 예측 확률을 각각 0.9, 0.3, 0.2, 0.8 이라고 가정해보자.
<img src="https://velog.velcdn.com/images/changh2_00/post/6f2c13b6-7d61-423a-a206-a4355c5f24aa/image.png" alt=""></p>
<p>위와 같이 예측값과 타깃값을 곱한 뒤 음수로 바꿔 적절한 손실 함수값을 구할 수 있다.
3번째, 4번째 샘플은 타깃이 0이므로 그냥 곱해주면 안 되고,
타깃을 1로 설정하고 예측값도 그에 맞게 1 - (0에대한 예측값)으로 바꿔줘야 한다.</p>
<p><img src="https://velog.velcdn.com/images/changh2_00/post/d06d463d-9751-421a-a5f6-313e2e977896/image.png" alt=""></p>
<p>이렇게 간단한 손실 함수가 얻어졌는데, 이보다 좋은 방법은 예측값에 로그함수를 씌우는 방법이다. 로그함수를 씌우면 모델에 끼치는 영향을 크게 만들 수 있다. (로그 함수는 0에 가까울수록 아주 큰 음수가 된다)</p>
<p><img src="https://velog.velcdn.com/images/changh2_00/post/ef3773f4-07d2-4ba3-a189-8c620d0c3a07/image.png" alt=""></p>
<p>이렇게 정의된 <strong>이진 분류</strong>에서의 손실 함수는 <span style='background-color:#fff5b1'><strong>로지스틱 손실함수</strong></span> 혹은 
<span style='background-color:#fff5b1'><strong>이진크로스엔트로피 손실 함수(binary cross-entropy)</strong></span> 라고 부른다.</p>
<p><strong>다중 분류</strong>에서의 손실 함수는 <span style='background-color:#fff5b1'><strong>크로스엔트로피 손실 함수(cross-entropy)</strong></span>라고 부른다.</p>
<p><strong>회귀 모델</strong>에서는 <span style='background-color:#fff5b1'><strong>평균 제곱 오차(MSE:Mean Squared Error)</strong></span>를 많이 사용한다.</p>
<hr>
<h1 id="sgdclassifier">SGDClassifier</h1>
<p>이제 확률적 경사 하강법을 사용한 분류 모델을 만들어 보자.
먼저, 데이터를 준비하고 전처리를 해주자.</p>
<pre><code class="language-python">import pandas as pd
fish = pd.read_csv(&#39;https://bit.ly/fish_csv_data&#39;)
                                            # Species 열을 제외하고 입력 데이터로 사용
fish_input = fish[[&#39;Weight&#39;,&#39;Length&#39;,&#39;Diagonal&#39;,&#39;Height&#39;,&#39;Width&#39;]].to_numpy()
fish_target = fish[&#39;Species&#39;].to_numpy()  # Species 열은 타깃 데이터</code></pre>
<pre><code class="language-python">from sklearn.model_selection import train_test_split
  # train_test_split() 함수 사용해 훈련 세트와 테스트 세트 나누기
train_input, test_input, train_target, test_target = train_test_split(fish_input, fish_target, random_state=42)</code></pre>
<pre><code class="language-python">from sklearn.preprocessing import StandardScaler
  # 훈련 세트와 테스트 세트의 특성을 표준화 전처리
  # 꼭 훈련 세트에서 학습한 통계값으로 테스트 세트도 변환해야한다!
ss = StandardScaler()
ss.fit(train_input)
train_scaled = ss.transform(train_input)
test_scaled = ss.transform(test_input)</code></pre>
<p>사이킷런에선 <strong><code>SGDClassifier()</code></strong>로 확률적 경사 하강법을 제공한다.</p>
<blockquote>
<h3 id="sgdclassifier의-매개변수">SGDClassifier의 매개변수</h3>
<p>SGDClassifier의 객체를 만들 때 여러 매개변수를 지정한다.
<code>loss</code>는 손실 함수의 종류를 지정하고 (기본값 = hinge 손실 함수를 위한 &#39;hinge&#39;),
<code>max_iter</code>는 수행할 에포크 횟수를 지정한다.(기본값 = 1000)
추가적으로, 
<code>penalty</code>는 규제의 종류를 지정할 수 있고 (기본값 = L2 규제를 위한 &#39;l2&#39;),
<code>tol</code>은 반복을 멈출 조건을 명시할 수 있는데, (기본값 = 0.001)
<code>n_iter_no_chage</code>에서 지정한 에포크 동안 손실이 tol 만큼 줄어들지 않으면 알고리즘이 중단된다. (기본값 = 5)</p>
</blockquote>
<pre><code class="language-python">from sklearn.linear_model import SGDClassifier

sc = SGDClassifier(loss=&#39;log_loss&#39;, max_iter=10, random_state=42)
                # 로지스틱 손실 함수 사용, 훈련 세트 10회 반복
sc.fit(train_scaled, train_target)
print(sc.score(train_scaled, train_target))
print(sc.score(test_scaled, test_target))</code></pre>
<pre><code>&gt;&gt;&gt; 0.773109243697479
    0.775

# 정확도가 낮은걸 보니 반복 횟수 10번으론 부족한 것 같다.</code></pre><p>글의 초반에 얘기한 것처럼 <span style='background-color:#dcffe4'>확률적 경사 하강법은 <strong>점진적 학습</strong>이 가능하다.</span>
즉, SGDClassifier <span style='background-color:#dcffe4'>객체를 다시 만들지 않고 <strong>이어서 데이터를 추가해 모델을 훈련</strong>시킬 수 있다.</span>
이렇게 이어서 훈련할 때는 <strong><code>partial_fit()</code></strong> 메서드를 사용한다.</p>
<blockquote>
<p><strong>partial_fit()</strong> 메서드는 호출할 때마다 1 에포크씩 이어서 훈련할 수 있다!</p>
</blockquote>
<pre><code class="language-python">sc.partial_fit(train_scaled, train_target)
print(sc.score(train_scaled, train_target))
print(sc.score(test_scaled, test_target))</code></pre>
<pre><code>&gt;&gt;&gt; 0.8151260504201681
    0.85</code></pre><p>결과를 보니 더 훈련해야될거 같은데, <strong>얼마나 더 훈련</strong>해야 되는걸까? <strong>기준이 필요할 것 같다.</strong></p>
<hr>
<h1 id="에포크와-과대과소적합">에포크와 과대/과소적합</h1>
<p><span style='background-color:#ffdce0 '><strong>확률적 경사 하강법</strong>을 사용한 모델은 <strong>에포크 횟수에 따라</strong> 과소적합이나 과대적합이 될 수 있다.</span>
이를 나타내는 그래프는 아래와 같다.
<img src="https://velog.velcdn.com/images/changh2_00/post/81e71b64-e73d-480f-aecf-49609eda8ec7/image.png" alt=""></p>
<p>그래프를 보면 테스트 세트 점수가 증가하다가 감소하는 순간이 있는데, 이 지점부터 과대적합된다.과대적합이 시작되기 전에 훈련을 멈추는 것을 <strong>조기 종료(early stopping)</strong>라고 한다.</p>
<p><span style='background-color:#fff5b1'><strong>적절한 반복횟수, 즉, 적절한 에포크를 찾기위해</strong></span>
<strong><code>partial_fit()</code></strong> 메서드를 사용해여 훈련을 반복 진행해보자.</p>
<pre><code class="language-python"># 점수 리스트, 생선 목록 준비
import numpy as np
sc = SGDClassifier(loss=&#39;log_loss&#39;, random_state=42)
train_score = []
test_score = []
classes = np.unique(train_target)  # train_target에 있는 7개 생선의 목록을 준비 

# 300번의 에포크 반복 훈련
for _ in range(0, 300):
  sc.partial_fit(train_scaled, train_target, classes=classes)
  train_score.append(sc.score(train_scaled, train_target))
  test_score.append(sc.score(test_scaled, test_target))

# 300번의 에포크 동안의 점수 그래프 출력
import matplotlib.pyplot as plt
plt.plot(train_score)
plt.plot(test_score)
plt.xlabel(&#39;epoch&#39;)
plt.ylabel(&#39;accuracy&#39;)
plt.show()</code></pre>
<p><img src="https://velog.velcdn.com/images/changh2_00/post/885c1d0e-e1db-4c60-8c51-5eda8473ea1b/image.png" alt=""></p>
<p>100 에포크 이후에는 훈련 세트와 테스트 세트 간의 점수 차이가 벌어지고 있다.
<strong>이 모델은 100 에포크가 적절한 반복 횟수로 보여진다!</strong>
SGDClassifier의 반복 횟수를 100에 맞추고 모델을 다시 훈련해보자.</p>
<pre><code class="language-python">sc = SGDClassifier(loss=&#39;log_loss&#39;, max_iter=100, tol=None, random_state=42)
sc.fit(train_scaled, train_target)
print(sc.score(train_scaled, train_target))
print(sc.score(test_scaled, test_target))</code></pre>
<pre><code>&gt;&gt;&gt; 0.957983193277311
    0.925</code></pre><p>성공적인 결과가 나왔다!</p>
<blockquote>
<p><strong>추가 +)</strong> 
<strong>SGDClassifier</strong>은 확률적 경사 하강법을 사용한 <strong>분류 모델</strong>이고
<strong>SGDRegressor</strong>로 확률적 경사 하강법을 사용한 <strong>회귀 모델</strong>이 제공된다.</p>
</blockquote>
]]></description>
        </item>
        <item>
            <title><![CDATA[[밑바닥딥러닝] 2장 퍼셉트론]]></title>
            <link>https://velog.io/@changh2_00/%EB%B0%91%EB%B0%94%EB%8B%A5%EB%94%A5%EB%9F%AC%EB%8B%9D-2%EC%9E%A5-%ED%8D%BC%EC%85%89%ED%8A%B8%EB%A1%A0</link>
            <guid>https://velog.io/@changh2_00/%EB%B0%91%EB%B0%94%EB%8B%A5%EB%94%A5%EB%9F%AC%EB%8B%9D-2%EC%9E%A5-%ED%8D%BC%EC%85%89%ED%8A%B8%EB%A1%A0</guid>
            <pubDate>Wed, 30 Oct 2024 06:45:38 GMT</pubDate>
            <description><![CDATA[<hr>
<p>[밑바닥부터 시작하는 딥러닝1] 교재 2장을 기반으로 작성되었습니다.</p>
<hr>
<h2 id="21-퍼셉트론이란">2.1 퍼셉트론이란?</h2>
<p><span style='background-color: #fff5b1'><strong>퍼셉트론</strong></span>이란 <span style='background-color: #fff5b1'>신경망(딥러닝)의 기원이 되는 알고리즘</span>이다. </p>
<p>퍼셉트론은 다수의 신호를 입력으로 받아 하나의 신호를 출력하는데, 
실제 전류와 달리 퍼셉트론 신호는 &#39;흐른다 / 안 흐른다&#39; 의 두 가지 값만 가진다. (1=흐른다, 0=안 흐른다)</p>
<p><img src="https://velog.velcdn.com/images/changh2_00/post/5381f4e0-79e3-4b31-9e3e-f21d3a8e288c/image.png" alt=""></p>
<p>위의 그림에서 x1,x2는 <strong>입력 신호</strong>, y는 <strong>출력 신호</strong>, w1,w2는 <strong>가중치</strong>를 뜻하고 원은 <strong>뉴런(노드)</strong>라고 부른다. 
<span style='background-color: #f5f0ff'>입력 신호가 뉴런에 보내질 때 각각의 고유한 가중치가 곱해진다.(w1x1, w2x2)
이렇게 뉴런에서 보내온 신호의 총합이 정해진 한계를 넘어설 때만 1을 출력하는데,
이 한계를 <strong>임계값</strong>이라 한다. (<strong>θ</strong>로 표시)</span>
이게 퍼셉트론 동작 원리의 전부인데, 수식으로 나타내면 아래와 같다.</p>
<p><img src="https://velog.velcdn.com/images/changh2_00/post/ec000e08-2e9b-49ff-be13-8789f6c714ee/image.png" alt=""></p>
<p>퍼셉트론은 여러 입력 신호에 고유한 가중치를 부여하는데, 
<strong>가중치</strong>는 각 신호가 결과에 주는 <strong>영향력을 조절하는 요소</strong>로 작용한다.
즉, <span style='background-color: #fff5b1'><strong>가중치가 클수록 해당 신호가 중요함을 뜻한다.</strong></span></p>
<hr>
<h2 id="22-단순한-논리-회로">2.2 단순한 논리 회로</h2>
<h3 id="and-게이트">AND 게이트</h3>
<p>퍼셉트론을 활용한 간단한 문제를 살펴보자. 아래는 AND 게이트의 진리표이다.
<img src="https://velog.velcdn.com/images/changh2_00/post/2f207818-458a-4479-af32-baa44bf5bb38/image.png" alt=""></p>
<p>이 AND 게이트를 퍼셉트론으로 표현하고자 한다.
<span style='background-color: #dcffe4'>가장 먼저 할 일은 <strong>진리표대로 작동하는 w1, w2, θ의 값을 정하는 것</strong>이다.</span></p>
<p>사실 만족하는 매개변수 조합은 무수히 많다. (w1, w2, θ) = (0.5, 0.5, 0.7) or (1.0, 1.0, 1.0) or . . .
이렇게 설정하면 x1, x2가 모두 1일때만 <strong>가중 신호의 총합(w1x1 + w2x2)</strong>이 주어진 <strong>임계값(θ)보다 크게 된다.</strong> </p>
<h3 id="nand-게이트와-or-게이트">NAND 게이트와 OR 게이트</h3>
<p>아래는 NAND 게이트의 진리표이다.</p>
<p><img src="https://velog.velcdn.com/images/changh2_00/post/a893e293-0cf2-4407-8ec6-5aad09eb9602/image.png" alt=""></p>
<p>NAND 게이트를 표현하려면 (w1, w2, θ) = (-0.5, -0.5, -0.7) 과 같은 조합이 있다.
사실, NAND 게이트는 Not AND 이므로 AND 게이트의 출력을 뒤집은 것이고, 
<span style='background-color: #dcffe4'>AND 게이트를 구현하는 매개변수의 부호를 모두 반전시키면 NAND 게이트가 된다.</span></p>
<p>아래는 OR 게이트의 진리표이다.</p>
<p><img src="https://velog.velcdn.com/images/changh2_00/post/8edfb090-4f00-463a-b015-c916a184f659/image.png" alt=""></p>
<p>매개변수를 어떻게 설정하면 OR 게이트를 구현할 수 있을까?</p>
<blockquote>
<p>(w1, w2, θ) = (0.5, 0.5, 0.2) 과 같이 설정하면 된다!</p>
</blockquote>
<p>이렇게 간단한 문제를 풀어봤는데, 가장 중요한 점은
<span style='background-color: #fff5b1'>각각의 다른 게이트들을 구현하는데 <strong>퍼셉트론의 구조는 똑같다는 것</strong>이다!</span>
똑같은 구조의 퍼셉트론이 매개변수의 값만 적적히 조정하여 AND, NAND, OR로 변신한다.</p>
<hr>
<h2 id="23-퍼셉트론-구현하기">2.3 퍼셉트론 구현하기</h2>
<h3 id="간단한-구현부터">간단한 구현부터</h3>
<p>이제 논리회로를 코드로 구현해보자.</p>
<pre><code class="language-python"># AND 게이트 구현
def AND(x1, x2):
  w1, w2, theta =  0.5, 0.5, 0.7
  tmp = x1*w1 + x2*w2
  if tmp &lt;= theta:
    return 0
  elif tmp &gt; theta:
    return 1</code></pre>
<pre><code class="language-python">print(AND(0, 0))  # 0 출력
print(AND(0, 1))  # 0 출력
print(AND(1, 0))  # 0 출력
print(AND(1, 1))  # 1 출력</code></pre>
<h3 id="가중치와-편향-도입">가중치와 편향 도입</h3>
<p>위에서 구현한 AND 게이트는 직관적이고 알기 쉽지만, 다른 방식으로 수정하고자 한다.
그 전에, [식 2.1]의 <span style='background-color: #d1ebf9'>θ를 -b로 치환</span>하면 수식은 아래와 같이 바뀐다.</p>
<p><img src="https://velog.velcdn.com/images/changh2_00/post/4583993a-7c3d-4e76-8896-6aa3625612bf/image.png" alt=""></p>
<p>두 가지의 수식은 표기만 바뀌었을 뿐 의미는 같은데, <span style='background-color: #d1ebf9'>여기서 <strong>b</strong>를 <strong>편향(bias)</strong>라고 한다.</span>
위 수식을 해석해보면 아래와 같다.
&#39;퍼셉트론은 입력 신호에 <strong>가중치를 곱한 값</strong>과 <strong>편향</strong>을 <strong>합하여</strong>, 그 값이 0을 넘으면 1을 출력하고 그렇지 않으면 0을 출력한다.&#39;
이를 파이썬 인터프리터로 순서대로 결과를 확인해보자.</p>
<pre><code class="language-python">import numpy as np

x = np.array([0, 1])
w = np.array([0.5, 0.5])
b = -0.7
w*x
# &gt;&gt;&gt; array([0. , 0.5])
np.sum(w*x)
# &gt;&gt;&gt; 0.5
np.sum(w*x) + b
# &gt;&gt;&gt; -0.19999999999999996</code></pre>
<p>위의 수식대로 계산이 완료된 것을 볼 수 있다.</p>
<h3 id="가중치와-편향-구현하기">가중치와 편향 구현하기</h3>
<p>이제 &#39;가중치와 편향을 도입한&#39; AND 게이트를 코드로 구현해보자.</p>
<pre><code class="language-python">def AND(x1, x2):
  x = np.array([x1, x2])
  w = np.array([0.5, 0.5])
  b = -0.7
  tmp = np.sum(w*x) + b
  if tmp &lt;= 0:
    return 0
  else:
    return 1</code></pre>
<p>-θ가 b로 치환되었는데, 여기서 편향 b와 가중치 w1, w2의 기능이 다른 것에 주의하자.
<span style='background-color: #dcffe4'> <strong>가중치</strong> </span> <strong>:</strong> <span style='background-color: #dcffe4'> 각 입력 신호가 결과에 주는 영향력(중요도)을 조절하는 매개변수</span>
<span style='background-color: #d1ebf9'> <strong>편향</strong> </span> <strong>:</strong> <span style='background-color: #d1ebf9'>뉴런이 얼마나 쉽게 활성화(결과로 1을 출력)하느냐를 조정하는 매개변수</span></p>
<p>이어서 NAND 게이트와 OR 게이트를 구현해보자.</p>
<pre><code class="language-python">def NAND(x1, x2):
  x = np.array([x1, x2])
  w = np.array([-0.5, -0.5])  
  b = 0.7 
  # AND와 가중치(w와 b)만 다르다!
  tmp = np.sum(w*x) + b
  if tmp &lt;= 0:
    return 0
  else:
    return 1</code></pre>
<pre><code class="language-python">def OR(x1, x2):
  x = np.array([x1, x2])
  w = np.array([0.5, 0.5])
  b = -0.2
  # AND와 가중치(w와 b)만 다르다!
  tmp = np.sum(w*x) + b
  if tmp &lt;= 0:
    return 0
  else:
    return 1</code></pre>
<hr>
<h2 id="24-퍼셉트론의-한계">2.4 퍼셉트론의 한계</h2>
<h3 id="도전-xor-게이트">도전! XOR 게이트</h3>
<p>XOR 게이트는 <strong>배타적 논리합</strong>을 뜻하는 논리회로이다. 
x1과 x2가 중 한쪽만 1일때만 1을 출력하는데, 진리표는 아래와 같다.</p>
<p><img src="https://velog.velcdn.com/images/changh2_00/post/2bad6e41-6677-47e4-8d9a-ce799ea3207d/image.png" alt=""></p>
<p>이 XOR 게이트를 퍼셉트론으로 구현하려면 매개변수를 어떻게 설정하면 될까?
<span style='background-color: #ffdce0'>사실, 지금까지 공부한 퍼셉트론으론 <strong>불가능</strong>하다.</span></p>
<p>불가능한 이유를 OR게이트를 예시로 들며 시각적으로 설명하겠다.
우선, OR 게이트의 퍼셉트론은 아래의 수식으로 표현된다.
<img src="https://velog.velcdn.com/images/changh2_00/post/f64c06ce-9558-473d-8b42-1fd206cd7409/image.png" alt=""></p>
<p>이 퍼셉트론은 아래처럼 직선으로 나뉜 두 영역을 만드는데, 
한쪽 영역은 1을 출력하고 다른 한쪽은 0을 출력한다.</p>
<p><img src="https://velog.velcdn.com/images/changh2_00/post/8967d377-f9c3-41ee-8251-e3825dcafdfb/image.jpg" alt=""></p>
<p>이제 XOR 게이트의 경우를 살펴보자. OR 게이트처럼 직선 하나로 영역을 나눌 수 있을까?</p>
<p><img src="https://velog.velcdn.com/images/changh2_00/post/d1c3191b-2b7b-4cc5-a1e2-e20f640eceec/image.png" alt=""></p>
<p><span style='background-color: #ffdce0'>위 그림과 같이 XOR 게이트는 <strong>직선 하나</strong>로 동그라미와 세모 <strong>영역을 나누는 것이 불가능</strong> 하다.</span></p>
<h3 id="선형과-비선형">선형과 비선형</h3>
<p>직선 하나로는 위의 영역을 나눌 수 없지만, &#39;직선&#39;이어야 한다는 제약을 없앤다면 가능하다.</p>
<p><img src="https://velog.velcdn.com/images/changh2_00/post/ae213895-add2-4690-b4c2-818bf63059fe/image.png" alt=""></p>
<p>위 그림과 같은 <span style='background-color: #fff5b1'>&#39;곡선&#39;의 영역을 <strong>비선형</strong> 영역</span> , <span style='background-color: #fff5b1'>&#39;직선&#39;의 영역을 <strong>선형</strong> 영역</span>이라 한다.
이 선형 영역은 &#39;다층 퍼셉트론&#39;이 표현할 수 있다.</p>
<hr>
<h2 id="25-다층-퍼셉트론이-충돌한다면">2.5 다층 퍼셉트론이 충돌한다면</h2>
<p>단층 퍼셉트론으로는 XOR 게이트를 표현할 수 없다.
하지만, 퍼셉트론의 진면모는 &#39;층을 쌓아&#39; <strong>다층 퍼셉트론</strong>을 만들 수 있다는데에 있다.
이를 알아보기 전에, XOR 게이트 문제를 다른 관점에서 생각해보자.</p>
<h3 id="기존-게이트-조합하기">기존 게이트 조합하기</h3>
<p>XOR 게이트를 만드는 방법 중 하나는 AND, NAND, OR 게이트를 조합하는 방법이 있다.</p>
<p><img src="https://velog.velcdn.com/images/changh2_00/post/3bd8fc7e-f6ef-4871-b8e1-1eb6b7287900/image.png" alt=""></p>
<p>이 세 가지 게이트를 조합하여 만든 XOR 게이트는 아래와 같다.</p>
<p><img src="https://velog.velcdn.com/images/changh2_00/post/9ad1f64b-9388-40e3-80ba-c9a5047d7d59/image.png" alt=""></p>
<p>이 논리회로의 진리표는 아래와 같으므로 XOR 게이트를 완성했음을 알 수 있다.
<img src="https://velog.velcdn.com/images/changh2_00/post/4d46ec24-32af-4730-8078-5d1439fa5e96/image.png" alt=""></p>
<h3 id="xor-게이트-구현하기">XOR 게이트 구현하기</h3>
<p>위와 같이 조합된 XOR 게이트를 코드로 구현해보자. 위에서 구현한 AND, NAND, OR을 사용하면 쉽다.</p>
<pre><code class="language-python">def XOR(x1, x2):
  s1 = NAND(x1, x2)
  s2 = OR(x1, x2)
  y = AND(s1, s2)
  return y</code></pre>
<pre><code class="language-python">print(XOR(0, 0))  # 0을 출력
print(XOR(0, 1))  # 1을 출력
print(XOR(1, 0))  # 1을 출력
print(XOR(1, 1))  # 0을 출력</code></pre>
<p>이렇게 성공적으로 구현된 XOR 게이트를 뉴련을 이용한 퍼셉트론으로 표현하면 아래와 같다.</p>
<p><img src="https://velog.velcdn.com/images/changh2_00/post/ea96981e-23cb-4b75-bd45-90926ab8ad44/image.png" alt=""></p>
<p>다층 구조의 네트워크를 이루는데, 이처럼 <span style='background-color: #fff5b1'>층이 여러 개인 퍼셉트론을 <strong>다층 퍼셉트론</strong>이라 한다.</span></p>
<p>단층 퍼셉트론으로는 표현하지 못한 XOR 게이트를 층을 하나 늘려 구현할 수 있게되었다.
이와 같이 <strong>퍼셉트론은 층을 쌓아 (깊게 하여) 더 다양한 것을 표현할 수 있다.</strong></p>
<hr>
<h2 id="26-nand에서-컴퓨터까지">2.6 NAND에서 컴퓨터까지</h2>
<p>다층 퍼셉트론은 생각보다 더 많이 복잡한 회로를 만들 수 있다.
사실 NAND 게이트의 조합만으로 컴퓨터를 만들 수 있기 때문에, 퍼셉트론으로 컴퓨터를 표현할 수 있다.
그러면 층을 얼마나 깊게 해야 컴퓨터가 만들어 질까?
미친 소리 같지만, 2층 퍼셉트론으로, 정확히는 &#39;비선형인 시그모이드 함수를 활성화 함수로 이용하면&#39; 표현할 수 있다. (활성화 함수는 3장에서 배워보자)</p>
<hr>
<h2 id="27-정리">2.7 정리</h2>
<ul>
<li>퍼셉트론은 입출력을 갖춘 알고리즘이다. 입력을 주면 정해진 규칙에 따른 값을 출력한다.</li>
<li>퍼셉트론에서는 &#39;가중치&#39;와 &#39;편향&#39;을 매개변수로 설정한다.</li>
<li>퍼셉트론으로 AND,NAND ,OR 게이트 등의 논리 회로를 표현할 수 있다.</li>
<li>XOR 게이트는 단층 퍼셉트론으로는 표현할 수 없다.</li>
<li>2층 퍼셉트론을 이용하면 XOR 게이트를 표현할 수 있다.</li>
<li>단층 퍼셉트론은 직선형 영역만 표현할 수 있고, 다층 퍼셉트론은 비선형 영역도 표현할 수 있다.</li>
<li>다층 퍼셉트론은 (이론상) 컴퓨터를 표현할 수 있다.</li>
</ul>
]]></description>
        </item>
        <item>
            <title><![CDATA[[혼공머신] 4-1장 로지스틱 회귀]]></title>
            <link>https://velog.io/@changh2_00/%ED%98%BC%EA%B3%B5%EB%A8%B8%EC%8B%A0-4%EC%9E%A5-%EB%8B%A4%EC%96%91%ED%95%9C-%EB%B6%84%EB%A5%98-%EC%95%8C%EA%B3%A0%EB%A6%AC%EC%A6%98</link>
            <guid>https://velog.io/@changh2_00/%ED%98%BC%EA%B3%B5%EB%A8%B8%EC%8B%A0-4%EC%9E%A5-%EB%8B%A4%EC%96%91%ED%95%9C-%EB%B6%84%EB%A5%98-%EC%95%8C%EA%B3%A0%EB%A6%AC%EC%A6%98</guid>
            <pubDate>Tue, 29 Oct 2024 09:11:29 GMT</pubDate>
            <description><![CDATA[<hr>
<p>[혼자 공부하는 머신러닝+딥러닝] 교재 4장을 기반으로 작성되었습니다.</p>
<hr>
<p>구성품을 모른 채 먼저 구매하고, 배송받은 다음에야 비로소 구성품을 알 수 있는 상품인 럭키백을 기획하기로 했다. 럭키백에 포함된 생선의 확률을 알려주는 방향으로 이벤트를 기획하므로, 머신러닝으로 럭키백의 생선이 어떤 타깃에 속하는지 확률을 구해보자.</p>
<h1 id="럭키백의-확률">럭키백의 확률</h1>
<p>이번에는 생선의 길이, 높이, 두께 외에도 대각선 길이와 무게도 사용 가능하다.</p>
<p><img src="https://velog.velcdn.com/images/changh2_00/post/0ac90340-5c1e-4bf4-b6c3-e29c66d874ef/image.png" alt=""></p>
<p>&quot;k-최근접 이웃은 주변 이웃을 찾아주니까 이웃의 클래스 비율을 확률이라고 출력하면 되지 않을까?&quot;</p>
<p><img src="https://velog.velcdn.com/images/changh2_00/post/25dfafa9-dd9c-45ed-9551-1d656167d22d/image.png" alt=""></p>
<p>위와 같이 k-최근접 이웃 분류기로 럭키백에 들어간 생선의 확률을 계산해보자.</p>
<h2 id="데이터-준비">데이터 준비</h2>
<p>먼저, 데이터를 준비하자.</p>
<pre><code class="language-python">import pandas as pd

fish = pd.read_csv(&#39;https://bit.ly/fish_csv_data&#39;)
fish.head()</code></pre>
<pre><code>&gt;&gt;&gt;
    Species     Weight     Length     Diagonal  Height   Width
0     Bream     242.0      25.4      30.0      11.5200    4.0200
1     Bream     290.0      26.3      31.2      12.4800    4.3056
2     Bream     340.0      26.5      31.1      12.3778    4.6961
3     Bream     363.0      29.0      33.5      12.7300    4.4555
4     Bream     430.0      29.0      34.0      12.4440    5.1340
</code></pre><p>어떤 종류의 생선이 있는지 <code>unique()</code> 함수를 사용해 확인해보자.</p>
<pre><code class="language-python">print(pd.unique(fish[&#39;Species&#39;]))</code></pre>
<pre><code>&gt;&gt;&gt; [&#39;Bream&#39; &#39;Roach&#39; &#39;Whitefish&#39; &#39;Parkki&#39; &#39;Perch&#39; &#39;Pike&#39; &#39;Smelt&#39;]</code></pre><p>이 데이터프레임에서  Species 열을 타깃으로 만들고 나머지 5개 열은 입력데이터로 사용할건데, 데이터프레임에서 열을 선택하려면 아래처럼 원하는 열을 리스트로 나열하면 된다.</p>
<pre><code class="language-python"># 입력 데이터 준비
fish_input = fish[[&#39;Weight&#39;, &#39;Length&#39;, &#39;Diagonal&#39;, &#39;Height&#39;, &#39;Width&#39;]].to_numpy()
print(fish_input[:5])</code></pre>
<pre><code>&gt;&gt;&gt; [[242.      25.4     30.      11.52     4.02  ]
      [290.      26.3     31.2     12.48     4.3056]
     [340.      26.5     31.1     12.3778   4.6961]
      [363.      29.      33.5     12.73     4.4555]
      [430.      29.      34.      12.444    5.134 ]]</code></pre><pre><code class="language-python"># 타깃 데이터 준비
fish_target = fish[&#39;Species&#39;].to_numpy()</code></pre>
<p>입력 데이터와 타깃 데이터가 준비되었으니, 이제 각 데이터를 훈련 세트와 테스트 세트로 나누자.</p>
<pre><code class="language-python">from sklearn.model_selection import train_test_split

train_input, test_input, train_target, test_target = train_test_split(fish_input, fish_target, random_state=42)
                                                                                             # 교재와 같은 결과를 위해 랜덤값 지정</code></pre>
<p>이제 마지막으로, <strong><code>StandardScaler</code></strong> 클래스를 사용해 훈련 세트와 테스트 세트를 표준화 전처리 해주자.</p>
<pre><code class="language-python">from sklearn.preprocessing import StandardScaler

ss = StandardScaler()
ss.fit(train_input)
train_scaled = ss.transform(train_input)
test_scaled = ss.transform(test_input)</code></pre>
<h2 id="k-최근접-이웃-분류기의-확률-예측">k-최근접 이웃 분류기의 확률 예측</h2>
<p>최근접 이웃 개수 k를 3으로 지정하여 모델을 훈련하고 점수를 확인해보자.</p>
<pre><code class="language-python">from sklearn.neighbors import KNeighborsClassifier

kn = KNeighborsClassifier(n_neighbors=3)
kn.fit(train_scaled, train_target)
print(kn.score(train_scaled, train_target))
print(kn.score(test_scaled, test_target))</code></pre>
<pre><code>&gt;&gt;&gt; 0.8907563025210085
    0.85</code></pre><p>아까 데이터프레임에서 타깃 데이터로 fish[&#39;Species&#39;]를 사용했기 때문에 7개의 생선 종류가 들어가 있는데, <span style='background-color: #fff5b1'>이렇게 타깃 데이터에 2개 이상의 클래스가 포함된 문제를 <strong>다중 분류</strong>라고 부른다.</span></p>
<p>근데, 타깃값을 사이킷런 모델에 전달하면 알파벳 순으로 순서가 자동으로 매겨지는데, 
<span style='background-color: #ffdce0'>이는 pd.unique(fish[&#39;Species&#39;]) 로 출력했던 순서와 다르다.</span> 
자동으로 정렬된 타깃값은 classes_ 속성에 저장되어 있다.</p>
<pre><code class="language-python">print(kn.classes_)</code></pre>
<pre><code>&gt;&gt;&gt; [&#39;Bream&#39; &#39;Parkki&#39; &#39;Perch&#39; &#39;Pike&#39; &#39;Roach&#39; &#39;Smelt&#39; &#39;Whitefish&#39;]</code></pre><p>이제 테스트 세트에 있는 처음 5개 샘플의 타깃값을 예측해 보자.</p>
<pre><code class="language-python">print(kn.predict(test_scaled[:5]))</code></pre>
<pre><code>&gt;&gt;&gt; [&#39;Perch&#39; &#39;Smelt&#39; &#39;Pike&#39; &#39;Perch&#39; &#39;Perch&#39;]</code></pre><p>이제 처음 5개 샘플에 대한 확률을 출력해보자.</p>
<pre><code class="language-python">import numpy as np

proba = kn.predict_proba(test_scaled[:5])
print(np.round(proba, decimals=4))</code></pre>
<pre><code>&gt;&gt;&gt; [[0.     0.     1.     0.     0.     0.     0.    ]
      [0.     0.     0.     0.     0.     1.     0.    ]
      [0.     0.     0.     1.     0.     0.     0.    ]
      [0.     0.     0.6667 0.     0.3333 0.     0.    ]
      [0.     0.     0.6667 0.     0.3333 0.     0.    ]]</code></pre><blockquote>
<h3 id="predict_proba">predict_proba()</h3>
<p>예측 확률을 반환하는 메서드.
<strong>이진 분류</strong>의 경우에는 샘플마다 음성 클래스와 양성 클래스에 대한 확률을 반환한다.
<strong>다중 분류</strong>의 경우에는 샘플마다 모든 클래스에 대한 확률을 반환한다.</p>
</blockquote>
<p><strong><code>predict_proba()</code></strong> 메서드의 출력은 아래와 같다.</p>
<p><img src="https://velog.velcdn.com/images/changh2_00/post/0d806e2d-3cf3-463e-a294-a61d6152e22a/image.png" alt=""></p>
<p>이 모델이 계산한 확률이 가장 가까운 이웃의 비율이 맞는지 확인해보자.</p>
<pre><code class="language-python">distances, indexes = kn.kneighbors(test_scaled[3:4])
print(train_target[indexes])</code></pre>
<pre><code>&gt;&gt;&gt; [[&#39;Roach&#39; &#39;Perch&#39; &#39;Perch&#39;]]</code></pre><p>이 샘플의 이웃은 다섯번째 클래스인 &#39;Roach&#39;가 1개, 세번째 클래스인 &#39;Perch&#39;가 2개이다.
따라서 다섯번째 클래스에 대한 확률은 1/3 = 0.3333,
세번째 클래스에 대한 확률은 2/3 = 0.6667이 되므로 확률을 성공적으로 예측했다.</p>
<p>하지만 아직은 확률이라 말하기엔 좀 어색하다. 더 좋은 방법을 찾아보자.</p>
<h1 id="로지스틱-회귀">로지스틱 회귀</h1>
<p><span style='background-color: #fff5b1'>로지스틱 회귀는 이름은 회귀이지만 <strong>분류 모델</strong></span>인데, 선형 회귀와 동일하게 선형 방적식을 학습한다.
예를 들면 아래와 같다.</p>
<p><img src="https://velog.velcdn.com/images/changh2_00/post/8474bfe3-903e-4987-bac0-0278273c2a81/image.png" alt=""></p>
<p>여기서 a,b,c,d,e는 가중치(계수)인데, z는 어떤 값이든 될 수 있다.
하지만 <strong>확률</strong>이 되려면 0~1 사이값이 되어야 하는데, 이 변환의 역할은 <strong>시그모이드 함수</strong>가 해준다.
<span style='background-color: #fff5b1'>이렇게 선형회귀에서 시그모이드 함수를 더해 확률값을 결과로 냄으로써 분류 하는 모델을 <strong>로지스틱 회귀 모델</strong>이라고 한다.</span></p>
<p><img src="https://velog.velcdn.com/images/changh2_00/post/7fb2f058-e457-4a43-ae3b-8f6286171b8a/image.png" alt=""></p>
<blockquote>
<h3 id="로지스틱-회귀의-목표">로지스틱 회귀의 목표</h3>
<p>로지스틱 회귀는 특정 데이터가 특정 클래스에 속할 확률을 예측하는데, 이 확률 값은 항상 0과 1 사이여야 합니다. 예를 들어, 이메일이 스팸인지 아닌지 예측할 때, 예측 확률이 0.7이라면 해당 이메일이 스팸일 확률이 70%라는 의미입니다.</p>
<h3 id="로지스틱-회귀와-시그모이드-함수의-관계">로지스틱 회귀와 시그모이드 함수의 관계</h3>
<p>로지스틱 회귀는 본질적으로 선형 회귀와 비슷한 원리로 작동하지만, 선형 회귀에서는 예측 값이 무한대 범위로 나올 수 있습니다. 분류 문제에서 확률을 다루기 위해, 이 값을 0과 1 사이의 범위로 조정할 필요가 있습니다. 시그모이드 함수는 입력 값을 0과 1 사이로 압축하여 확률 값을 제공하기 때문에, 이 요구에 잘 부합합니다.</p>
</blockquote>
<p>시그모이드 함수의 역할: 시그모이드 함수는 어떤 값이라도 0에서 1 사이의 값으로 압축할 수 있는 함수입니다. 로지스틱 회귀에서는 모델의 선형 결합을 시그모이드 함수에 넣어 확률로 변환합니다. </p>
<p>시그모이드 함수의 출력을 확인해보자.</p>
<pre><code class="language-python">import numpy as np
import matplotlib.pyplot as plt

z = np.arange(-5, 5, 0.1)
phi = 1/(1+np.exp(-z))
plt.plot(z, phi)
plt.xlabel(&#39;z&#39;)
plt.ylabel(&#39;phi&#39;)
plt.show()</code></pre>
<p><img src="https://velog.velcdn.com/images/changh2_00/post/60b3c8a3-8489-4441-a650-32a0f68924de/image.png" alt=""></p>
<p>실제로 시그모이드 함수의 출력이 0부터 1까지인 것을 볼 수 있다.</p>
<h2 id="로지스틱-회귀로-이진-분류-수행-sigmoid-함수">로지스틱 회귀로 이진 분류 수행 (sigmoid 함수)</h2>
<p>이제 로지스틱 회귀 모델을 훈련시켜보자.</p>
<blockquote>
<p><strong>불리언 인덱싱</strong>
넘파이 배열은 True, False 값을 전달하여 행을 선택할 수 있는데, 이를 불리언 인덱싱이라 한다.</p>
</blockquote>
<pre><code class="language-python">char_arr = np.array([&#39;A&#39;, &#39;B&#39;, &#39;C&#39;, &#39;D&#39;, &#39;E&#39;])
print(char_arr[[True, False, True, False, False]])</code></pre>
<pre><code>&gt;&gt;&gt; [&#39;A&#39; &#39;C&#39;]</code></pre><p>위와 같은 방식으로 훈련세트에서 도미(Bream)와 빙어(Smelt)의 행만 골라내겠다.</p>
<pre><code class="language-python">bream_smelt_indexes = (train_target == &#39;Bream&#39;) | (train_target == &#39;Smelt&#39;)
train_bream_smelt = train_scaled[bream_smelt_indexes]
target_brea_smelt = train_target[bream_smelt_indexes]</code></pre>
<p>이제 이 데이터로 로지스틱 회귀 모델을 훈련해보자. 
<strong><code>LogisticRegression</code></strong> 클래스는 선형 모델이므로 sklearn.linear_model 패키지 내에 있다.</p>
<pre><code class="language-python">from sklearn.linear_model import LogisticRegression

lr = LogisticRegression()
lr.fit(train_bream_smelt, target_bream_smelt)</code></pre>
<p>훈련한 모델을 사용해 처음 5개 샘플을 예측해보자.</p>
<pre><code class="language-python">print(lr.predict(train_bream_smelt[:5]))</code></pre>
<pre><code>&gt;&gt;&gt; [&#39;Bream&#39; &#39;Smelt&#39; &#39;Bream&#39; &#39;Bream&#39; &#39;Bream&#39;]</code></pre><p>처음 5개의 샘플의 예측 확률을 확인해보자.</p>
<pre><code class="language-python">print(lr.predict_proba(train_bream_smelt[:5]))</code></pre>
<pre><code>&gt;&gt;&gt; [[0.99760007 0.00239993]
      [0.02737325 0.97262675]
      [0.99486386 0.00513614]
      [0.98585047 0.01414953]
      [0.99767419 0.00232581]]</code></pre><p>샘플마다 두 개의 확률이 출력되었는데, 이는 각 음성 클래스(0), 양성 클래스(1)이다. 위에서 설명했듯이 사이킷런은 타깃값을 알파벳순으로 정렬하여 사용한다. classes_ 속성에서 확인해보자.</p>
<pre><code class="language-python">print(lr.classes_)</code></pre>
<pre><code>&gt;&gt;&gt; [&#39;Bream&#39; &#39;Smelt&#39;]</code></pre><p>음성 클래스는 도미(Bream), 양성 클래스는 빙어(Smelt)인 것을 볼 수 있다.
이를 토대로 분석해보면, 두번째 샘플만 빙어일 확률이 높고, 나머지는 모두 도미로 예측할 것이다.</p>
<p>성공적으로 이진 분류를 했는데, 선형 회귀에서 처럼, 모델이 학습한 계수를 확인해보자.</p>
<pre><code class="language-python">print(lr.coef_, lr.intercept_)</code></pre>
<pre><code>&gt;&gt;&gt; [[-0.40451732 -0.57582787 -0.66248158 -1.01329614 -0.73123131]] [-2.16172774]</code></pre><p>위에 의하면 이 로지스틱 회귀 모델이 학습한 방적식은 아래와 같다.</p>
<blockquote>
<p><img src="https://velog.velcdn.com/images/changh2_00/post/fcd9837a-9ada-4664-8805-9a84d4aaa48c/image.png" alt=""></p>
</blockquote>
<p>LogisticRegression 클래스는 <strong><code>decision_function()</code></strong> 메서드로 z값을 출력할 수 있다. </p>
<pre><code class="language-python">decisions = lr.decision_function(train_bream_smelt[:5])
print(decisions)  </code></pre>
<blockquote>
<h3 id="decision_function">decision_function()</h3>
<p>모델이 학습한 선형 방정식의 출력을 반환하는 메서드.
<strong>이진 분류</strong>의 경우 양성 클래스의 확률이 반환되는데, 
값이 0보다 크면 양성 클래스, 작거나 같으면 음성 클래스로 예측한다.
<strong>다중 분류</strong>의 경우 각 클래스마다 선형 방정식을 계산하는데,
가장 큰 값의 클래스가 예측 클래스가 된다.</p>
</blockquote>
<pre><code>&gt;&gt;&gt; [-6.02991358  3.57043428 -5.26630496 -4.24382314 -6.06135688]</code></pre><p><span style='background-color:#fff5b1'>이 z 값을 <strong>시그모이드 함수</strong>에 통과시키면 <strong>확률</strong>을 얻을 수 있는데</span>, 파이썬의 사이파이(scipy) 라이브러리에도 <strong>시그모이드 함수</strong>가 <strong><code>expit()</code></strong> 로 있다. decisions 배열의 값을 확률로 변환해보자.</p>
<pre><code class="language-python">from scipy.special import expit
print(expit(decisions))</code></pre>
<pre><code>&gt;&gt;&gt; [0.00239993 0.97262675 0.00513614 0.01414953 0.00232581]</code></pre><p>출력된 값을 보니 predict_proba() 메서드 출력의 두번째 열의 값, 즉 양성 클래스에 대한 z값과 동일한 것을 볼 수있다. 성공적으로 로지스틱 회귀모델을 훈련하고 이진 분류한 것이다!</p>
<h2 id="로지스틱-회귀로-다중-분류-수행-softmax함수">로지스틱 회귀로 다중 분류 수행 (softmax함수)</h2>
<p>다중 분류도 이중 분류와 크게 다르진 않지만, 아래 코드에서 <strong>7개</strong>의 생선 데이터가 모두 들어 있는 train_scaled와 train_target을 사용한 점을 눈여겨보자.</p>
<pre><code class="language-python">lr = LogisticRegression(C=20, max_iter=1000)
lr.fit(train_scaled, train_target)
print(lr.score(train_scaled, train_target))
print(lr.score(test_scaled, test_target))</code></pre>
<pre><code>&gt;&gt;&gt; 0.9327731092436975
    0.925

# 과대적합이나 과소적합으로 치우친 것 같진 않다. </code></pre><p>테스트 세트 처음 5개 샘플에 대한 예측을 출력해보자.</p>
<pre><code class="language-python">print(lr.predict(test_scaled[:5]))</code></pre>
<pre><code>&gt;&gt;&gt; [&#39;Perch&#39; &#39;Smelt&#39; &#39;Pike&#39; &#39;Roach&#39; &#39;Perch&#39;]</code></pre><p>이번엔 테스트 세트 처음 5개 샘플에 대한 예측 확률을 출력해보자.</p>
<pre><code class="language-python">proba = lr.predict_proba(test_scaled[:5])
print(np.round(proba, decimals=3))</code></pre>
<pre><code>&gt;&gt;&gt; [[0.    0.014 0.842 0.    0.135 0.007 0.003]
     [0.    0.003 0.044 0.    0.007 0.946 0.   ]
      [0.    0.    0.034 0.934 0.015 0.016 0.   ]
     [0.011 0.034 0.305 0.006 0.567 0.    0.076]
      [0.    0.    0.904 0.002 0.089 0.002 0.001]]</code></pre><p>첫번째 샘플을 보면 세번째 열의 확률이 가장 높은데, 세번째 열이 농어(Perch)에 대한 확률인지 classes_ 속성에서 확인해보자.</p>
<pre><code class="language-python">print(lr.classes_)</code></pre>
<pre><code>&gt;&gt;&gt; [&#39;Bream&#39; &#39;Parkki&#39; &#39;Perch&#39; &#39;Pike&#39; &#39;Roach&#39; &#39;Smelt&#39; &#39;Whitefish&#39;]

# 세번째 열이 농어(Perch)인 것을 볼 수 있다.
# 정확하게 다중 분류에 성공한 것!</code></pre><p>이렇게 다중 분류일 경우의 선형 방정식은 어떻게 되는지 확인해보자.</p>
<pre><code class="language-python">print(lr.coef_.shape, lr.intercept_.shape)</code></pre>
<pre><code>&gt;&gt;&gt; (7, 5) (7,)
# coef_ 배열의 열은 특성의 수 5개가 맞다. 근데 행이 7이다. intercept_도 7개나 있다.</code></pre><p>다중 분류는 클래스마다 z 값을 하나씩 계산한다. 당연히 가장 높은 z 값을 출력하는 클래스가 예측 클래스가 된다. 그럼 <strong>확률은 어떻게 계산한걸까?</strong></p>
<p>위에서 이중 분류는 <strong>시그모이드 함수</strong>를 사용해 z를 0과 1사이의 값으로 변환했다.
<span style='background-color:#fff5b1'>이와 달리 다중 분류는 <strong>소프트맥스 함수</strong>를 사용하여 여러 개의 z 값을 확률로 변환한다.</span></p>
<blockquote>
<h3 id="소프트맥스-함수">소프트맥스 함수</h3>
<p>하나의 선형 방정식의 출력값을 0<del>1 사이로 압축하는 시그모이드 함수와 달리,
**여러 개의 선형 방정식의 출력값을 0</del>1 사이로 압축<strong>하고 **전체 합이 1</strong>이 되도록 만든다.</p>
</blockquote>
<p>이진 분류에서 했던 것과 같이, <code>decision_function()</code>메서드로 z1<del>z7까지의 값을 구한 다음 소프트맥스 함수를 사용해 확률로 바꾸어 보자.
먼저, 테스트세트의 처음 5개 샘플의 z1</del>z7을 구해보자.</p>
<pre><code class="language-python">decision = lr.decision_function(test_scaled[:5])
print(np.round(decision, decimals=2))</code></pre>
<pre><code>&gt;&gt;&gt; [[ -6.51   1.04   5.17  -2.76   3.34   0.35  -0.63]
     [-10.88   1.94   4.78  -2.42   2.99   7.84  -4.25]
     [ -4.34  -6.24   3.17   6.48   2.36   2.43  -3.87]
     [ -0.69   0.45   2.64  -1.21   3.26  -5.7    1.26]
     [ -6.4   -1.99   5.82  -0.13   3.5   -0.09  -0.7 ]]</code></pre><p>사이파이는 소프트맥스 함수도 <strong><code>softmax()</code></strong>로 제공한다.</p>
<pre><code class="language-python">from scipy.special import softmax

proba = softmax(decision, axis=1)
print(np.round(proba, decimals=3))</code></pre>
<pre><code>&gt;&gt;&gt; [[0.    0.014 0.842 0.    0.135 0.007 0.003]
     [0.    0.003 0.044 0.    0.007 0.946 0.   ]
     [0.    0.    0.034 0.934 0.015 0.016 0.   ]
     [0.011 0.034 0.305 0.006 0.567 0.    0.076]
     [0.    0.    0.904 0.002 0.089 0.002 0.001]]</code></pre><p>위에서 구한 proba 배열과 정확히 일치하는 것을 볼 수 있다. 완벽하다!</p>
]]></description>
        </item>
    </channel>
</rss>