<?xml version="1.0" encoding="utf-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom">
    <channel>
        <title>sea_panda</title>
        <link>https://velog.io/</link>
        <description></description>
        <lastBuildDate>Tue, 02 May 2023 08:45:54 GMT</lastBuildDate>
        <docs>https://validator.w3.org/feed/docs/rss2.html</docs>
        <generator>https://github.com/jpmonette/feed</generator>
        <image>
            <title>sea_panda</title>
            <url>https://velog.velcdn.com/images/sea_panda/profile/0419c3df-80fc-4b7b-b737-935d5587e0e1/image.jpg</url>
            <link>https://velog.io/</link>
        </image>
        <copyright>Copyright (C) 2019. sea_panda. All rights reserved.</copyright>
        <atom:link href="https://v2.velog.io/rss/sea_panda" rel="self" type="application/rss+xml"/>
        <item>
            <title><![CDATA[부동소수점이란? (0.1 + 0.2 == 0.3 -> False)]]></title>
            <link>https://velog.io/@sea_panda/%EB%B6%80%EB%8F%99%EC%86%8C%EC%88%98%EC%A0%90%EC%9D%B4%EB%9E%80-0.1-0.2-0.3-False</link>
            <guid>https://velog.io/@sea_panda/%EB%B6%80%EB%8F%99%EC%86%8C%EC%88%98%EC%A0%90%EC%9D%B4%EB%9E%80-0.1-0.2-0.3-False</guid>
            <pubDate>Tue, 02 May 2023 08:45:54 GMT</pubDate>
            <description><![CDATA[<p><img src="https://velog.velcdn.com/images/sea_panda/post/360171d8-290d-46eb-a057-cf471997db1a/image.png" alt=""></p>
<p>위와 같은 문제를 많이 접해봤을 것이다. 이번 글에서는 미루고 미뤘던 부동소수점에 대해서 다루어 보려고 한다.</p>
<h1 id="컴퓨터의-수">컴퓨터의 수</h1>
<p>대부분의 프로그래밍 언어에서는 수를 표현하기 위해 크게 두 가지 타입을 제공한다. 바로 정수 타입(<code>int</code> in python)과, 부동소수점 타입(<code>float</code> in python)이다. </p>
<p>정수도 실수에 포함이 되는데 왜 정수타입과 부동소수점 타입을 애써 나눠 놓은 것일까? 이유는 무척 간단하다. 부동소주점이 실수를 완벽하게 표한할 수 없기 때문이다. 정확하게 따지고 들어가면 정수조차도 제대로 표현하지 못한다고 한다. 즉, 정수를 가지고 계산을 할 때, 부동소수점 타입을 사용할 경우 정확한 결과를 얻을 수 없게 된다. </p>
<p>그리고 또 한 가지 이유는, 보통 부동소수점이 1보다 작은 <strong>소수</strong>를 표현하기 위해 도입된 개념이라는 인식을 많이들 가지고 있다. 아마도 이름 자체에 <strong>&quot;소수점&quot;</strong>이라는 표현이 들어가있기 때문일 것이다. 분명 그런 목적도 있긴 하지만 본래 목적의 일부분만을 나타낼 뿐이다. <strong><span style="color:orange">부동소수점은 아주 작은 수와 아주 큰 수 양쪽을 모두! 표현하기 위해서 도입됐다.</span></strong></p>
<hr>
<h1 id="고정소수점과-부동소수점">고정소수점과 부동소수점</h1>
<p>부동소수점은 <strong>부동</strong>이라는 말에서 알 수 있듯이 실수를 표현할 때 소수점의 위치를 고정하지 않는 것을 말한다. 그렇다면 왜 고정하지 않는 것일까. 만약 <code>123.456</code>를 표현한다고 할 때 고정소수점은 정수 부분 <code>123</code>과 <code>456</code>을 나눠서 표현해야 한다. 결국 한정된 비트에 정수와 소수 부분을 <strong><span style="color:orange">분할해 배치</span></strong>할 경우 <strong><span style="color:orange">고정소수점이 나타낼 수 있는 범위가 무척 한정된다.</span></strong></p>
<p>그에 비하여 <strong>부동소수점</strong>에서는 <code>123.456</code>을 123456이라는 유효숫자와 3이라는 소수점 위치를 통해서 고정소수점보다 훨씬 넓은 범위의 수를 표현할 수 있다는 장점이 있다. 그래서 프로그래밍에서 실수를 표현할 때는 부동수서점을 주로 사용하게 된다. 이런 부동소수점은 계산기에서도 볼 수 있다. 계산기가 표현할 수 있는 한계(칸)을 넘어설 경우 e가 나오면서 지수 표기법이 나오는 것을 확인할 수 있다.(주로 공학용계산기에서 쉽게 확인할 수 있다.) 여기서 e가 바로 부동소수점, 즉 소수점의 위치를 표시하겠다는 의미다.(C++도 이런 방식을 이용하여 부동 소수점을 표현한다.)</p>
<blockquote>
<p><strong>❓</strong><em>123과 456을 저장하는게 왜 123456과 3을 저장하는 것보다 왜 넓은 범위의 수를 표현할 수 있다는건가요?</em><br>
<img src="https://velog.velcdn.com/images/sea_panda/post/d9cd7e8c-5a0f-4a39-96a4-3a3bb0c01cc6/image.png" alt="">
보통 한정된 비트안에서 각각의 부분을 할당하여 수를 표시하게됩니다. 그리고 위의 이미지는 고정소수점 방식에서 비트할당을 나타낸 것입니다. 정수를 표현하는 bit를 늘리면 큰 숫자는 표현할 수 있지만 소수부가 작아져서 그만큼 정밀한 숫자를 표현하기 힘들어집니다. 그 반대 역시 Trade-off관계이기 때문에 마찬가지입니다.
<br> 이와 달리 부동소수점 방식은 하나의 실수를 가수부와 지수부로 나누어 표현합니다. 이런 표현방식은 매우 큰 실수까지 표현할 수 있습니다. 따라서 현재 대부분의 시스템에서는 부동 소수점 방식으로 실수를 표현하고 있습니다.<img src="https://velog.velcdn.com/images/sea_panda/post/7e67cae0-cded-4cf0-9a43-7343368e4c38/image.png" alt="">
<img src="https://velog.velcdn.com/images/sea_panda/post/a098197e-2278-4a6c-afa1-3e7e26ecfe7c/image.png" alt="">
부호 비트는 0일 경우 양수, 1일 때 음수를 나타냅니다. 만일 어떤 수를 이진법으로 나타내면 <code>1001101.011001011</code> 라고 가정해봅시다. 이러면 부호는 양수이기 때문에 0이 됩니다. 그리고 주어진 수를 맨 앞에 있는 1 바로 뒤로 소수점을 옮겨서 표현하도록 변환합니다. 그러면 <code>1.001101011001011*2^6</code>으로 표현됩니다. 이때 6을 이진법으로 나타내면 <code>110</code>이고 IEEE754 표현방식에서는 127을 더해서 지수를 기록하기 때문에 지수부분은 <code>10000110</code>이 되고, 가수부분이 <code>001101011001011</code>이 되는 것입니다.<br>
이런 방식을 통하여 고정소수점보다 훨씬 넓은 범위의 수를 표현할 수 있게 됩니다. 하지만 이런 방식의 표현은 항상 오차가 존재한다는 단점을 가지고 있습니다.</p>
</blockquote>
<hr>
<h1 id="2진법과-부동소수점">2진법과 부동소수점</h1>
<p>정수를 2진법으로 나타내는 것은 무척 간단하다. 그렇다면 소수부분은 어떻게 2진법으로 표현할까? 이는 초등학교에서 배운 방식의 반대로 생각하면 된다. 정수 부분을 2진수로 변환할 때는 2로 나눠서 나머지를 구했다면, 소수 부분의 경우 2를 곱해서 정수 부분을 취하면 된다. 만약 0.6875를 2진수로 변환한다면 절차는 다음 그림과 같다.
<img src="https://velog.velcdn.com/images/sea_panda/post/70a1b39a-41df-4c83-a6bb-3f1232e12d75/image.png" alt=""></p>
<p>위의 그림에서는 0.0이 나와서 멈췄지만, 보통의 경우 대부분의 실수는 무한히 순환하면 반복된다. 즉, 적당한 개수의 유효숫자만을 취하게 되고, 여기서 앞 서 언급한 오차가 필히 발생하게 된다.</p>
<p>그리고 위의 그림에서도 볼 수 있듯이 32비트 방식에서 가수부는 23비트가 할당되기 때문에 만약 $2^{23}$보다 큰 실수의 소수점은 남지 못하고 삭제된다. 즉, 지수부의 지수가 23 이상일 경우에는 더 이상 소수점을 표현할 수 없다는 의미이다.</p>
<hr>
<h1 id="다시-처음으로">다시 처음으로..</h1>
<p>그렇다면 이제 0.1 + 0.2 == 0.3이 왜 False인가에 대해서 대답하면 &quot;파이썬의 Float는 부동소수점 방식으로 수를 저장하기 때문이다.&quot;라고 대답할 수 있다. 
<img src="https://velog.velcdn.com/images/sea_panda/post/00843e33-d80b-46b7-9040-0ab61952fda3/image.png" alt=""></p>
<p><img src="https://velog.velcdn.com/images/sea_panda/post/da35ef89-433e-4757-8881-b17c8916a2db/image.png" alt=""></p>
<p>실제로 0.1과 0.2(두번째 사진, 소수점 30자리까지 표현)을 파이썬에서 저장하는 값은 위와 같다고 한다.그리고 0.3(소수점 30자리까지 표현)은 다음과 같은 값은 가진다.
<img src="https://velog.velcdn.com/images/sea_panda/post/dfd29bc4-0276-4207-b804-99b0b1ff2a82/image.png" alt=""></p>
<blockquote>
<p><strong>❗️출처</strong></p>
</blockquote>
<ol>
<li><a href="https://www.pylenin.com/blogs/python-float-arithmetics/">파이썬 0.1+0.2 not same as 0.3 사진출처</a></li>
<li><a href="https://dataonair.or.kr/db-tech-reference/d-lounge/expert-column/?mod=document&amp;uid=52381">C++ 프로그래밍: 부동소수점 구조와 원리</a></li>
<li><a href="http://www.tcpschool.com/cpp/cpp_datatype_floatingPointNumber">부동 소수점 수</a></li>
<li><a href="https://docs.python.org/ko/3/tutorial/floatingpoint.html">부동 소수점 산술: 문제점 및 한계</a></li>
</ol>
]]></description>
        </item>
        <item>
            <title><![CDATA[Velog 자동 비공개 버그(라고 느껴지는 필터링 기능)]]></title>
            <link>https://velog.io/@sea_panda/Velog-%EC%9E%90%EB%8F%99-%EB%B9%84%EA%B3%B5%EA%B0%9C-%EB%B2%84%EA%B7%B8</link>
            <guid>https://velog.io/@sea_panda/Velog-%EC%9E%90%EB%8F%99-%EB%B9%84%EA%B3%B5%EA%B0%9C-%EB%B2%84%EA%B7%B8</guid>
            <pubDate>Wed, 08 Mar 2023 07:15:46 GMT</pubDate>
            <description><![CDATA[<h1 id="💡-자동-비공개">💡 자동 비공개?</h1>
<p>열심히 정리글을 작성했는데 아무리 글을 전체공개로 바꿔도 자동 비공개로 바뀌었다. 이런 현상이 궁금해서 공식 깃허브 Repo issue탭에서 검색해봤다.
<img src="https://velog.velcdn.com/images/sea_panda/post/95a06591-861a-4db9-b651-7b5c8ec1c0c4/image.png" alt=""></p>
<p>나 말고도 이런 버그를 경험하신 분들이 많은 것 같다 대표적으로 <code>ㅂㅏㄷㅜㄱㅇㅣ</code>를 정상적으로 입력하면 비공개로 글이 자동으로 전환된다.</p>
<p>그냥 전체 글에 저런 단어가 들어가는 순간 글을 전체공개로 돌릴 수 없는 것이다. 아무래도 스팸관리 차원에서 글을 전체공개 못하도록 시스템이 구성되어 있는 것 같다.</p>
<p>우선, 이런 경험을 겪으신 분들의 해결 내용을 정리하면 다음과 같다.</p>
<h2 id="1-외국에서-글을-작성했을-때">1. 외국에서 글을 작성했을 때</h2>
<p>외국에서 글을 작성하면 글이 전체공개가 안되고, 한국으로 귀국해서 재작성하니 해당 버그가 해결되었다는 분이 있다. 만일 해외에서 글을 작성했는데 전체공개가 안된다면 한국으로 돌아와서 글을 업로드 해보시는 것을 추천드린다.</p>
<h2 id="2-글에-특정-단어가-포함된-경우">2. 글에 특정 단어가 포함된 경우</h2>
<ul>
<li><code>ㅂㅏㄷㅜㄱㅇㅣ</code> -&gt; 바둑은 가능합니다!</li>
<li><code>ㅁㅏㅈㅣㄴㅗㅅㅓㄴ</code></li>
<li><code>ㅋㅏㅈㅣㄴㅗ</code></li>
<li><code>ㅎㅜ ㄹㅣ</code> -&gt; 후 뒤에 리가 바로 오면 글이 비공개처리 됩니다.</li>
<li>일본어, 한자로만 이루어진 제목 또는 글: 둘 중 하나라도 한자 또는 일본어로만 이루어져 있으면 비공개</li>
</ul>
<p>...등 이라고 할 수 있겠다. <code>password</code>, <code>coinbase</code>, <code>center</code> 등도 스팸으로 처리되어 비공개 설정이 되는 버그가 있었던 것 같은데 이는 해결된 것으로 보인다. 이글이 보인다는 것이 그 증거이다.</p>
<p>참고로 <code>ㅁㅏㅈㅣㄴㅗㅅㅓㄴ</code>은 한글이 아니라 영어로 maginot line이라고 입력하면 또 된다.</p>
<h2 id="3-위에-해당사항이-없는데도-비공개">3. 위에 해당사항이 없는데도 비공개?</h2>
<p>이런 경우는 노가다로 문단 -&gt; 문장 -&gt; 단어 순으로 찾아내려가는 방법외에는 없습니다.</p>
<h1 id="정리">정리</h1>
<p><img src="https://velog.velcdn.com/images/sea_panda/post/b68118c7-9bbc-41fe-87cd-096cceecf4e8/image.png" alt=""></p>
<p>이 드라마 이야기 Velog에서 못합니다. 이상.</p>
<p>장난이고 아무래도 불법 토토나 도박 사이트들이 판을 치다보니까 이런 필터링 기능이 만들어진 것 같다. 그러니 만일 동일한 버그를 겪는다면 문단을 삭제하면서 어떤 단어가 필터링 되는지 확인해보시길 추천드린다.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[[N534] 내용정리]]></title>
            <link>https://velog.io/@sea_panda/N534-%EB%82%B4%EC%9A%A9%EC%A0%95%EB%A6%AC</link>
            <guid>https://velog.io/@sea_panda/N534-%EB%82%B4%EC%9A%A9%EC%A0%95%EB%A6%AC</guid>
            <pubDate>Wed, 08 Mar 2023 07:03:50 GMT</pubDate>
            <description><![CDATA[<h1 id="💡-학습목표">💡 학습목표</h1>
<ul>
<li>알고리즘 개념을 숲을 보는 시점으로 생각하기.</li>
<li>Dynamic Programming(동적계획법)에 대해 배운다.</li>
<li>Greedy Algorithm(그리디 알고리즘 - 탐욕 알고리즘)에 대해 배운다.</li>
</ul>
<h1 id="✏️-알고리즘-설계-기법-문제-해결-접근-전략">✏️ 알고리즘 설계 기법/ 문제 해결 접근 전략</h1>
<ul>
<li>브루트 포스(Brute Force)<ul>
<li>무차별 대입 공격, 가능한 모든 조합을 대입해보는 방식</li>
<li>예: 네자리 비밀번호를 풀 때 0부터 9999까지 다 넣어보기</li>
</ul>
</li>
<li>분할 정복(Divide and Conquer)<ul>
<li>문제를 분할(Divide)해서 해결(Conquer)한다.</li>
<li>복잡한 문제를 간단한 문제로 나누고, 하위 문제들의 결과를 다시 합쳐서 해결한다.</li>
</ul>
</li>
<li><strong>동적 계획법(Dynamic Programming)</strong></li>
<li><strong>탐욕법(Greedy)</strong></li>
</ul>
<hr>
<h2 id="📌-피보나치-수열-문제">📌 피보나치 수열 문제</h2>
<p><img src="https://velog.velcdn.com/images/sea_panda/post/4e930bd5-5408-4ee7-83b3-205cab53b818/image.png" alt=""></p>
<p>피보나치 수열이란 앞의 두 수의 합이 바로 뒤의 수가 되는 수열을 의미한다. 피보나치 수 $F_n$은 다음과 같은 초기값 및 점화식으로 정의되는 수열이다.
$$
F_1 = F_2 = 1\
F_n = F_{n_1} + F_{n_2}
$$
이 피보나치 수열을 간단하게 재귀함수로 구현하면 다음과 같다.</p>
<pre><code class="language-python"># 재귀함수로 구현
def fibo(n):
        if n == 1 or n == 2:
                return 1
        return fibo(n-1) + fibo(n-2)
        # 한 줄로도 가능
        # return fibo(n-1) + fibo(n-2) if n &gt;= 2 else n

print(fibo(10))</code></pre>
<p>알고리즘 설계 기법과 문제 해결 접근 전략을 이야기하는데 갑자기 피보나치가 등장해서 의아할 수 있다. </p>
<p>피보나치 수열의 경우 재귀함수를 설명하는데 사용되는 대표적인 문제이다. 하지만 이 코드는 치명적인 단점이 존재하는데, 바로 항이 커질수록 계산속도도 매우 느려진다는 점이다.
<img src="https://velog.velcdn.com/images/sea_panda/post/d5995701-15b3-4434-b1a4-b118ec698b83/image.png" alt=""></p>
<p>동일한 계산이 반복적으로 수행되며 시간 복잡도는 $O(2^n)$이 된다. 이런 문제를 해결하기 위해서 재귀함수로 구성하는 것이 아닌 <strong>Dynamic Programming</strong>방법을 이용한다면 단점을 보완하며 더 빠른 속도로 문제를 해결할 수 있다.</p>
<hr>
<h1 id="✏️-dynamic-programming동적-프로그래밍">✏️ Dynamic Programming(동적 프로그래밍)</h1>
<blockquote>
<p><em>하나의 문제를 여러 작은 문제로 나누고, 작은 문제의 답을 <span style="color:orange"><strong>재사용</strong></span>하여 문제를 효율적으로 푸는 것</em></p>
</blockquote>
<ul>
<li><strong>기억하며 풀기</strong> 또는 <strong>기억하기 알고리즘</strong>이라고도 할 수 있다.</li>
<li>분할정복과 차이: 문제의 답을 <span style="color:orange"><strong>재사용</strong></span>하는 것<ul>
<li>다이나믹 프로그래밍은 이미 했던 계산은 반복하지 않는다.</li>
<li>메모리를 조금 더 사용해서 연산속도를 비약적으로 증가시킨다.(메모리에 계산결과를 저장)</li>
</ul>
</li>
<li><strong>Memoization(메모이제이션)</strong>, <strong>Tabulation(테뷸레이션)</strong> 두 가지 방법론이 있다.</li>
<li><strong><em>&quot;동적&quot;</em></strong>이라는 말에 몰입하여 어느 부분이 동적으로 작동하는지 찾을 필요는 없다. 이 말을 처음 사용한 사람도 그저 이름이 멋있어서 붙인 이름이다.</li>
</ul>
<blockquote>
<p>💡<strong><a href="https://galid1.tistory.com/507">Dynamic Programming의 조건</a></strong>
    1. 작은 문제들이 반복된다.
    2. 같은 문제는 구할때 마다 정답이 동일하다.
    <br> 위와 같은 조건을 충족할때만 동적 프로그래밍을 사용할 수 있다. 작은 문제의 결과 값이 항상 같다는 점을 이용해서 큰 문제를 해결하는 방법이니 당연한 것이다.</p>
</blockquote>
<h2 id="👉-memoization메모이제이션-top-down하향식방식">👉 Memoization(메모이제이션, Top-down(하향식)방식)</h2>
<ul>
<li>메인 문제를 분할하면서 해결하는 하향식 방법(Top-Down)</li>
<li>재귀를 이용하여 값을 위에서 부터 계산한다.</li>
<li>주어진 입력값에 대한 결과를 저장해 같은 입력값에 대해서는 함수가 한 번만 실행된다. -&gt; 함수 실행 횟수 감소</li>
<li>답을 재활용한다는 의미로 동일한 계산을 할 경우 한 번 계산한 결과를 메모리에 저장해 두었다가 꺼내 씀으로써 <strong>중복 계산을 방지할 수 있게 하는 기법</strong>이다.</li>
<li>이 기법은 메모리라는 공간적 비용을 투입해 시간적 비용을 줄이는 방식이다.</li>
</ul>
<p>이제 위에서 나온 피보나치 수열문제를 메모이제이션으로 구성하면 다음과 같은 코드가 된다.</p>
<pre><code class="language-python"># 메모이제이션

def fibo_memo(n):
    # n이 2보다 작거나 같은 경우
    if n &lt;= 2:
        return 1
    else:
        # 계산된 이력이 있는 경우
        if memo_[n] != 0:
            # 해당 함수값 반환하고 호출 종료
            return memo_[n]
        else:
            # f(n) = f(n-1) + f(n-2)
            memo_[n] = fibo_memo(n-1) + fibo_memo(n-2)
            # 결과값 반환 (f(n))
            return memo_[n]

num = 10
# 값 저장용 리스트(계산된 숫자는 해당 숫자의 인덱스에 값이 저장됨)
memo_ = [0]*(num+1)
print(fibo_memo(num))</code></pre>
<p>코드를 보게 되면 계산된 이력이 있는지 살펴보고 없다면 아래의 재귀함수 부분으로 들어가게 된다. 즉 위에서부터(큰 수부터) 계산된 이력이 있는지 파악하면서 작은 수로 내려가기 때문에 하향식 방식이라고 부른다.</p>
<p>위 코드의 시간 복잡도는 $O(n)$의 시간복잡도를 가지게 된다.</p>
<h2 id="👉-tabulation타뷸레이션-bottom-up상향식-방식">👉 Tabulation(타뷸레이션, Bottom-Up(상향식) 방식)</h2>
<ul>
<li><strong>가장 작은 문제를 먼저 해결</strong>하고 최종적으로 메인 문제를 해결하는 상향식 방법</li>
<li>반복문을 이용해 밑에서부터 계산하기 때문에 memoization과 달리 재귀함수를 사용하지 않는다.</li>
<li>값을 미리 계산해두고 필요하지 않는 값도 미리 계산한다.<ul>
<li><strong>반복문</strong>을 통해 부분 문제에 대한 해답을 하나씩 저장한다.</li>
</ul>
</li>
<li>메모하기 부분에서 Memoization이라고 했는데, Bottom-up(상향식)일때는 Tabulation이라고 부른다.
왜냐하면 반복을 통하여 Dp[0]부터 채우는 과정을 <strong>&quot;table-fillint&quot;</strong>라고 하며, 이런 방식을 이용하여 작은 문제부터 큰 문제까지 하나하나 테이블을 채워나간다는 의미이다. 근복적인 개념은 결과값을 <strong>기억</strong>하고 <strong>재활용</strong>한다는 측면에서 Memoization과 크게 다르지는 않다.</li>
</ul>
<pre><code class="language-python"># 태뷸레이션
def fibo_tabul(n):
    # 태뷸레이션 : Botton-Top 방식
    # 0~2번째 값을 먼저 설정
    tab = [0, 1, 1]

    # 3번째 인덱스부터 진행. i번째 자리에 i-1, i-2의 값을 합쳐서 append -&gt; for문
    for i in range(3, n + 1):
        tab.append(tab[i-1] + tab[i-2])

    return tab[n]

print(fibo_tabul(10))</code></pre>
<p>이 코드 역시 시간복잡도는 $O(n)$에 해당한다. 하지만 이미 계산된 값을 저장한 리스트에서 값을 가져오는 작업 자체는 $O(1)$의 시간 복잡도를 가진다.
<br></p>
<h3 id="두-가지-방법-중-더-다은-것이-있을까-하나만-가능한-경우는">두 가지 방법 중 더 다은 것이 있을까?, 하나만 가능한 경우는?</h3>
<p>두 방법 중 어느 것이 시간적으로 더 효율적인지 묻는다면, 그에 대한 답은 <span style="color:red"><strong>&quot;알 수 없다&quot;</strong></span>이다.<br>
Top-Down 방식을 사용하는 Memoization은 재귀를 통해 답을 찾아 내려간다. 그렇다보니 재귀함수 호출로인한 Stack이 쌓여서 <code>stackOverFlow</code>같은 에러가 발생할 수 있다. 특히 Python에서는 해당 케이스가 잦다보니 이럴 경우 Bottom-up방식의 Tabulation으로 풀면된다.<br>
두 방법 중 두 가지를 모두 사용하지 못하고 하나만 사용할 수 있는 경우가 있는지에 대해서는 <strong>있다고 할 수 있지만</strong> 그것은 동적 프로그래밍에 익숙해지고 경험적으로 알아낼 수 있는 부분이라고 한다.</p>
<h2 id="dynamic-programming의-목적">Dynamic programming의 목적</h2>
<p>우리가 프로그래밍을 배울 때 항상 기억해야하는 것은 <span style="color:black;background-color:orange"><strong>모든 알고리즘은 기존에 있던 문제를 해결하기 위해서 고안되었다는 것</strong></span>이다.</p>
<p><strong>완전 탐색, DFS,BFS</strong>와 같은 알고리즘은 수많은 경우의 수를 전부 따져봐야 하는데 그 경우의 수가 너무 많아서 속도가 느려지는 문제를 개선하고자, 수행 시간을 단축하고자 만들어진 알고리즘이 Dynamic programming이다.</p>
<blockquote>
<p>💡 <strong>Dynamic Programming의 유형</strong></p>
</blockquote>
<ul>
<li>DFS/BFS로 풀 수 있지만 경우의 수가 너무 많을 때<ul>
<li>패턴을 파악하여 경우의 수가 얼마나 증가할지 고려</li>
<li>DFS나 완전 탐색으로 진행하는 maginot line은 500만 개의 경우의 수로 볼 수 있다.</li>
<li>$5*10^6$을 넘어가는 경우의 수라면 동적 프로그래밍방법 사용을 고려해 볼 것.</li>
</ul>
</li>
<li>경우의 수들에 <strong>중복적인 연산</strong>이 많은 경우</li>
<li><strong>DP를 사용하게 되는 상황</strong>: 이진 검색, 최단경로 알고리즘, 최적화 문제, 외판원 문제</li>
</ul>
<h2 id="dynamic-programming과-분할정복">Dynamic Programming과 분할정복</h2>
<p><img src="https://velog.velcdn.com/images/sea_panda/post/d97a4597-56f8-4a99-bb7c-fe9b3561fa97/image.png" alt=""></p>
<ul>
<li>DP는 분할정복에 다음과 같은 개념이 추가된 것이라고 할 수 있다.<ul>
<li>중복된(반복되는) 서브문제(Overlapping Subproblems)<ul>
<li>메인과 서브문제를 같은 방법(반복)으로 해결할 수 있어야 한다.(문제해결관점)</li>
</ul>
</li>
<li>최적 부분 구조(Optimal Substructure)<ul>
<li>메인문제 해결방법을 <strong>서브문제에서 구하여 재사용하는 구조</strong>여야 한다.(문제의 구조관점)</li>
</ul>
</li>
</ul>
</li>
</ul>
<blockquote>
<p>👉 <strong>정리</strong>: DP는 최적 부분 구조로 구성된 중복된 서브문제를 분할정복으로 해결한다.</p>
</blockquote>
<h1 id="greedy-algorithm탐욕법">Greedy Algorithm(탐욕법)</h1>
<p><img src="https://velog.velcdn.com/images/sea_panda/post/88012854-7d9a-49d6-89ec-a15a76070bee/image.png" alt=""></p>
<blockquote>
<p><em>뒤는 생각하지 않고 오로지 <strong>매 순간 현재 최선인 답</strong>을 선택하여 최종적으로 최적값, 또는 근사값을 구하는 방법</em></p>
</blockquote>
<ul>
<li>중복되지 않는 서브문제를 더 빨리 풀 수 있는 방법론이다.</li>
<li>매 순간 현재 최선인 답을 선택해서 최종적으로 최적값, 또는 근사값을 구한다.<ul>
<li>전체에서 최적값(가장 좋은 결과)을 항상 보장하지는 않는다.</li>
<li>코드 작성이 쉽고 연산 시간도 빠르다.</li>
<li>완벽한 베스트는 구하지 못하더라도 최악의 결과는 아닐 수 있다.</li>
</ul>
</li>
<li>탐욕 알고리즘은 <span style="color:black;background-color:orange"><strong>모든 경우의 수를계산하는데 시간이 오래 걸리거나 방법이 복잡한 경우 간단한 방법으로 비교적 빠르게 최적의 결과 또는 최적의 근사값을 얻을 수 있을 때 주로 사용</strong></span>한다.</li>
<li>그리디 알고리즘 문제는 문제를 푸는 것보다 문제 파악이 더 어려울 때가 많다.<ul>
<li>어떤 문제가 주어지면 문제를 파악하는 능력과 함께 어떤 알고리즘이 가장 효율적인지 알아내는 것은 매우 중요하다. 어떤 알고리즘이 효율적인지 알아내는 것은 직관에 의존하기 때문에 문제를 많이 풀어봐야 한다.</li>
</ul>
</li>
<li>Greedy Algorithm은 특별한 코드가 있는 알고리즘이 아닌 개념적인 알고리즘이다. 어떠한 문제에도 적용할 수 있지만, 문제마다 적용하는 방식은 모두 다르다.</li>
<li>Greedy Algorithm에서 가장 어려운 점은 다음과 같다.<ul>
<li>이 문제에 Greedy Algorithm을 써야 할지 다른 알고리즘을 써야할지 알아내는 것.</li>
<li>Greedy Algorithm적용 시 최선의 선택 기준을 어떻게 알아내느냐 하는 것.</li>
</ul>
</li>
</ul>
<ul>
<li><strong>모든 경우의 수를 확인하는 방법(=무차별 대입)</strong>을 <strong>Brute-Force(브루트 포스)방법</strong>이라고 한다. 무차별 대입법은 알고리즘이라고 불리기는 조금 민망하지만, 모든 경우의 수를 일일이 확인하기 때문에 시간을 오래 걸려도 정답을 확실히 찾을 수 있다는 장점이 있다.</li>
</ul>
<h3 id="greedy-algorithm-언제-사용할까">Greedy Algorithm 언제 사용할까?</h3>
<p>Greedy Algorithm은 크게 2가지 경우에서 사용한다.</p>
<blockquote>
<ol>
<li><strong>Greedy Algorithm으로 최적의 해를 찾을 수 있는 경우</strong>
Greedy Algorithm은 다른 알고리즘에 비해 코드를 쉽게 작성할 수 있고 처리 속도 또한 뛰어나다.</li>
<li><strong>최적의 해를 계산하는데 시간이 오래걸리는 문제에 대해 Greedy Algorithm을 이용하면 적당히 빠르면서 괜찮은 근사해를 구할 수 있는 경우</strong></li>
</ol>
</blockquote>
<blockquote>
<p>💡 <strong>풀이 방식</strong>
기본적으로 <strong>그때그때</strong> 가장 좋은 해결책을 찾아가는 기법이다. 해를 구하는 일련의 선택과정 마다 가장 좋아보이는 최선을 선택하면, 전체적으로 최적 해를 구할 수 있다는 방법론이다. 각 단계마다 최상으로 보이는 해결핵으로 구한 해들을 모아서 제시하게 된다.</p>
</blockquote>
<blockquote>
<p>💡 <strong>적용문제</strong></p>
</blockquote>
<ul>
<li>동전 거스름돈을 가장 적은 수의 동전으로 주는 문제</li>
<li>최단경로 알고리즘(다익스트라 알고리즘)</li>
<li>최소비용 신장트리(Spanning Tree)를 구하는 알고리즘(크루스칼 알고리즘, 프림 알고리즘 등)(<a href="https://ongveloper.tistory.com/376">참고</a>)</li>
</ul>
<pre><code class="language-python"># 탐욕 알고리즘 예제: 잔돈
# 잔돈갯수를 구하자.(갖고 있는 돈 : 100원)
price = int(input(&#39;물건값을 입력하세요.&#39;))

# 거스름돈
change = 100 - price
print(f&#39;잔돈은 {change}원입니다.&#39;)

coin_list = [50, 40, 20, 10, 5]   # 받을 수 있는 잔돈의 종류. 크기순으로 적는다.(중요)

change_count = []   # 잔돈갯수

while change != 0:
    for coin in coin_list:
        change_bool = 0 # (동전 종류마다)동전 갯수에 대한 변수 생성
        # Greedy: 우선 금액이 큰 동전부터 거슬러준다.
        change_bool = change_bool + (change // coin)  # 몫이 동전의 갯수.
        print(change_bool)
        change_count.append(change_bool) # 잔돈 갯수 리스트에 추가
        change = change - (change_bool * coin) # 잔돈 갱신
        print(coin, change_count)

print(&#39;잔돈갯수 :&#39;,sum(change_count)) # 잔돈의 갯수를 합한다.(sum 내장함수 활용)</code></pre>
<h1 id="dp와-greedy">DP와 Greedy</h1>
<ul>
<li>최적 부분 구조 문제를 푼다는 점에서 비교된다.<ul>
<li>Dynamic programming<ol>
<li>문제를 작은 단위로 분할하여 해결한 후, 해결된 중복문제들의 결과를 기반으로 전체문제를 해결한다.</li>
</ol>
</li>
<li>Greedy<ol>
<li>각 단계마다 최적해를 찾는 문제로 접근한다.</li>
<li>해결해야 할 전체 문제의 갯수를 줄이기 위해서 개별적으로 문제를 해결해나가는 선택을 한다.</li>
</ol>
</li>
</ul>
</li>
</ul>
<blockquote>
<p>❗️ <strong>참고자료</strong></p>
</blockquote>
<ol>
<li>코드스테이츠 교육자료</li>
</ol>
]]></description>
        </item>
        <item>
            <title><![CDATA[[N531] 내용정리]]></title>
            <link>https://velog.io/@sea_panda/N531-%EB%82%B4%EC%9A%A9%EC%A0%95%EB%A6%AC</link>
            <guid>https://velog.io/@sea_panda/N531-%EB%82%B4%EC%9A%A9%EC%A0%95%EB%A6%AC</guid>
            <pubDate>Tue, 07 Mar 2023 16:19:26 GMT</pubDate>
            <description><![CDATA[<blockquote>
<p>❗️ <strong>화이트 모드 권장</strong></p>
</blockquote>
<h1 id="학습목표">학습목표</h1>
<ul>
<li>중요한 자료구조인 <strong>Hash Table</strong>에 대해 학습한다.</li>
<li>N531~N534의 방향은 기본적인 자료구조를 활용하여 다양한 프로그램을 위한 자료구조와 알고리즘에 대해서 익힌다.</li>
</ul>
<hr>
<h1 id="💡-해시테이블hash-table이란">💡 해시테이블(Hash Table)이란?</h1>
<blockquote>
<p>해시 테이블은 <strong>Key(키)를 활용하여 Value(값)</strong>에 직접 접근이 가능한 자료구조를 의미한다.</p>
</blockquote>
<p><img src="https://velog.velcdn.com/images/sea_panda/post/e64e2f4c-65b4-43e2-bba9-a2b70a347aec/image.png" alt=""></p>
<p>만일 정렬된 배열(array)에서 원소를 추가한다고 생각해보자. 원소의 수는 12개이고(이미지에서는 보이지 않는 공간이 있다고 생각), 앞에서부터 10개의 데이터가 오름차순으로 저장되어 있다. 여기에 만일 70을 추가한다고하면 과정은 다음과 같다.</p>
<ol>
<li>index 5번과 6번 사이에 값이 추가되도록 이진 검색법을 사용하여 검사</li>
<li>6번 이후의 모든 원소를 한 칸씩 뒤로 이동.</li>
<li>6번 인덱스에 70 대입</li>
</ol>
<p>원소가 이동하는데 필요한 복잡도는 $O(n)$이고 그 비용은 결코 작지한다. 물론 데이터를 삭제하는 경우에도 똑같은 비용이 발생하며, 이런 프로세스는 파이썬에서 <code>pop()</code>을 사용할 때 <code>()</code>안에 숫자를 넣게 되면 실행시간이 달라지는 이유이다.</p>
<h2 id="해시법hashing">해시법(Hashing)</h2>
<p><img src="https://velog.velcdn.com/images/sea_panda/post/db4013fe-9022-44f5-aff5-999762fe2d5e/image.png" alt=""></p>
<p>위에서 나타난 배열의 인덱스를 하나씩 미루어서 바꾸고 다시 데이터를 삽입하는 과정없이 바로 데이터를 넣기 위해 만들어진 방법이 <strong>해시법(Hashing)</strong>이다.</p>
<p>해시법은 <strong><code>&quot;데이터를 저장할 위치 = 인덱스&quot;</code></strong>를 간단한 연산으로 구하는 것을 말한다. 이 방법을 이용한다면 원소의 검색뿐 아니라 추가, 삭제도 효율적으로 수행할 수 있다. 정렬알고리즘과는 다르게 값을 정렬할 필요없이 <strong>해시함수(Hash Function)</strong>을 통해 <strong>해시값(Hash value)</strong>을 얻어서 값을 <strong>검색</strong>하는 것이 목적이다. </p>
<p><strong>해시함수(Hash Function)</strong>는 말그대로 함수로, 나누기 연산 등 다양한 방식으로 구현할 수 있다. 이런 방식으로 값을 저장하면 원소를 이동할 필요없이 해시함수를 통해 출력된 <strong>해시값</strong>을 통해 저장된다. 그리고 이렇게 저장되어 만들어진 원소들을 <strong>Bucket</strong>이라고 한다.</p>
<p>그리고 이런 해시법을 이용하여 만들어진 <strong>자료구조</strong>를 <strong>Hash Table</strong>이라고 한다.</p>
<p><span style="color:black;background-color:orange"><strong>파이썬의 Dictionary는 내부적으로 해시테이블 구조로 구현</strong></span>되어 있다. 해시 테이블은 검색을 위한 역할도 하고, 딕셔너리르 위한 자료구조의 역할도 수행한다. </p>
<blockquote>
<p>📌 <strong>중간정리</strong></p>
</blockquote>
<ul>
<li>해시(Hash)는 해시 함수를 통해 나온 값이다.</li>
<li>해시테이블은 키를 빠르게 <strong>저장 및 검색할 수 있는 테이블 형태의 자료구조</strong>이다.</li>
<li>해시함수는 여러 키를 분할하기 위해 키를 해시값(정수값)으로 <strong>매칭시키는 역할</strong>을 한다.</li>
<li><strong>해싱(Hashing)은 쉽게 말해서 다 흩뜨려놓고, 키와 매칭되는 값을 검색하는 과정</strong>이다.</li>
</ul>
<p>파이썬의 딕셔너리, 리스트와 튜플을 이용하여 해시테이블을 작성해보자.</p>
<pre><code class="language-python">import time
# case 1 - 딕셔너리로 활용되는 해시테이블을 확인할 수 있다.
test_dict = {i:chr(i) for i in range(1,91)}

time_1 = time.time()
print(test_dict[33]) 
print(test_dict[38])
print(test_dict[90])
time_2 = time.time()
time_interval = time_2 - time_1
print(time_interval)</code></pre>
<pre><code class="language-python"># case 2 - 리스트와 튜플을 활용해서 해시테이블을 확인한다.
test_list = [(i,chr(i)) for i in range(1,91)]

def insert(item_list, key, value):
    item_list.append((key, value))

def search(item_list, key):
    # 데이터를 검색하려면 딕셔너리보다 오래 걸린다.(키, 값 쌍이 없어서 개별 값으로 반복해서 검색하기 때문이다.)
    for item in item_list:
        if item[0] == key:
            return item[1]      
    print(&#39;not matching&#39;)   

time_1 = time.time()
print(search(test_list, 33))
print(search(test_list, 38))
print(search(test_list, 90))
time_2 = time.time()
time_interval = time_2 - time_1
print(time_interval)</code></pre>
<h2 id="해시함수hash-function">해시함수(Hash Function)</h2>
<p><img src="https://velog.velcdn.com/images/sea_panda/post/80891566-82b8-4438-a08b-7044926b69f9/image.png" alt=""></p>
<blockquote>
</blockquote>
<ul>
<li>위의 그림처럼 해시함수는 키를 해시테이블 내의 버킷(=Hashes=해시값)으로 매핑시킨다.</li>
<li>해시함수: 입력값의 형태는 다양하고, 출력값의 형태는 정수이다.<br></li>
<li>해수함수 요구조건:<ul>
<li>해시함수는 **입력값이 같다면, 동일한 출력값을 받아야 한다.</li>
<li>입출력값이 일정하지 않다면 적절한 해시함수가 아니다.<ul>
<li>예를 들어, 입력값 &#39;aqua&#39;가 4를 반환한다면, 입력값 &#39;beige&#39;는 4를 반환할 수 없다.
하지만 같은 경우가 출력될 수도 있는데 이를 <strong>해시충돌</strong>이라고 한다.<ul>
<li>해시함수는 특정 범위 안에 있는 숫자를 반환해야 한다.<br></li>
</ul>
</li>
</ul>
</li>
</ul>
</li>
<li>하나의 해시함수가 입력 데이터별로 다른 숫자와 매핑된다면, 그것은 <strong>완벽한 해시함수</strong>이다. <ul>
<li>해시함수가 입력데이터에 따라 다른 숫자를 반환하게 되면 <strong>해시충돌을 최소화</strong>하는 것이다.</li>
</ul>
</li>
</ul>
<p>해시함수는 보통은 문자열 입력값에 정수형 출력값을 반환한다. 정수형에서 문자열로 변환하기 위해서, 해시함수는 문자열에 해당하는 개별적인 단어를 활용한다. </p>
<p>다음 예시는 파이썬에서 <code>encode()</code>메소드를 활용하여 문자열에서 바이트 코드로 인코드하는 것이다. 인코딩 된 후에 정수형은 각 단어를 나타낸다.</p>
<pre><code class="language-python"># 인코딩 예제
bytes_representation = &quot;hello&quot;.encode()

for byte in bytes_representation:
    print(byte)
----
&gt; 
104
101
108
108
111</code></pre>
<p>이제 정수값의 합을 반환하는 방법을 활용하여 여러개의 정수들을 하나의 문자열로 변환하여보자.</p>
<pre><code class="language-python"># 정수값의 합 반환
bytes_representation = &quot;hello&quot;.encode()

sum = 0
for byte in bytes_representation:
    sum += byte

print(sum)
----
&gt; 534</code></pre>
<pre><code class="language-python"># 해시함수를 만들고 활용해보자.
def my_hashing_func(str, list_size):
    bytes_representation = str.encode()    
    sum = 0
    for byte in bytes_representation:
        sum += byte

    return sum % list_size

print(my_hashing_func(&#39;hello&#39;,5))
----
&gt; 2</code></pre>
<p>위의 해시함수를 활용하는 예시를 더 보여주면 다음과 같다.</p>
<p>먼저 5개의 빈 슬롯이 들어가는 리스트를 초기화 시킨 후 리스트에 있는 적합한 인덱스에 색상 이름 문자열이 매핑되기 위해 해시함수를 사용하며, 인덱스에 해당 헥사코드값을 저장하면 해시함수를 사용하여 값을 검색할 수 있다.</p>
<pre><code class="language-python"># 위의 my_hashing_func이라는 해시함수를 활용하여 아래처럼 값을 확인할 수 있다.
my_list = [None] * 5

# 해시테이블 값을 입력
my_list[my_hashing_func(&quot;aqua&quot;, len(my_list))] = &quot;#00FFFF&quot;

# 해시테이블 있는 값을 출력
print(my_list[my_hashing_func(&quot;aqua&quot;, len(my_list))])

# 전체 해시테이블 출력
print(my_list)
---
#00FFFF
[None, None, None, None, &#39;#00FFFF&#39;]</code></pre>
<h3 id="좋은-해시함수란">좋은 해시함수란?</h3>
<ul>
<li>해시함수를 어떻게 구현하는지에 따라서 해시의 성능이 결정된다.</li>
<li>좋은 해시함수의 조건 <ul>
<li>키와 값의 <strong>계산과정이 쉬워야 한다.</strong></li>
<li><strong>충돌을 피할 수 있어야 한다.</strong><ul>
<li>해시함수는 가능한 해시값의 전체 집합에 데이터를 <strong>균일하게 배포</strong>한다.<ul>
<li>해시함수는 유사한 문자열에 대해 다른 해시값을 생성한다.</li>
</ul>
</li>
</ul>
</li>
</ul>
</li>
</ul>
<h2 id="해시성능">해시성능</h2>
<p>해시테이블 자체는 해시충돌을 해결해주지는 않는다. </p>
<p>만일 <strong>해시충돌</strong>로 인해서 모든 Bucket의 Value를 찾아야해서 반복문이 필요한 경우를 제외하고는 해시테이블 자체는 $O(1)$ 시간복잡도 안에 <strong>검색, 삽입, 삭제</strong>를 할 수 있다. 검색/삽입/삭제 무엇을 하든지 <strong>해시함수</strong>는 키를 통해 저장된 값에 연관된 인덱스를 반환하기 때문이다.</p>
<h1 id="해시충돌">해시충돌</h1>
<p><img src="https://velog.velcdn.com/images/sea_panda/post/376fa012-cc61-4ce8-ac41-18683eb4620d/image.png" alt=""></p>
<p>일반적인 경우에서 모든 입력에 대해서 1:1로 대응되는 해시함수는 존재하지 않는다. 일반적으로는 n:1의 관계이다. 이처럼 저장할 버킷이 중복되는 현상을 <strong>충돌(Collision)</strong>이라고 한다. 그리고 이런 충돌이 가장 적은 해시함수를 만드는 것이 해시테이블의 가장 중요한 목적이다.</p>
<p>그리고 이런 충돌이 발생하는 경우 다음 2가지 방법으로 대처할 수 있다.</p>
<blockquote>
<p><strong>1. 체인법(Chaining)</strong>: 해시값이 같은 원소를 연결 리스트로 관리
<strong>2. 오픈 주소법(Open adressing)</strong>: 빈 버킷을 찾을 때까지 해시를 반</p>
</blockquote>
<h2 id="체인법chaining">체인법(Chaining)</h2>
<p><img src="https://velog.velcdn.com/images/sea_panda/post/6de599e7-ee74-43f3-85f9-53c7040cdfcb/image.png" alt=""></p>
<p>체인법은 해시값이 같은 데이터를 <strong>체인(chain)</strong>모양의 연결 리스트로 연결하는 방법을 말하며 <strong>오픈 해시법(Open hashing)</strong>이라고도 한다.</p>
<p>해시테이블에서 동일한 해시값에 대해 충돌이 일어나면, 그 위치에 있던 버킷에 키값을 뒤이어 연결한다. 데이터의 형태는 위의 그림처럼 연결리스트의 형태를 갖게 된다.</p>
<blockquote>
<p>💡 <strong>Chaining</strong>의 원리</p>
</blockquote>
<ol>
<li>키의 해시값을 계산한다.</li>
<li>해시값을 이용해 리스트의 인덱스를 구한다.</li>
<li>같은 해시값이 있다면(충돌한다면) 리스트로 연결한다.</li>
</ol>
<p><img src="https://velog.velcdn.com/images/sea_panda/post/88256338-bdf1-4ae7-92e7-6e00e85cc620/image.png" alt=""></p>
<p>위의 그림은 <strong>나누기 방법(Division method)</strong>을 사용한 것인데, 나누기 방법은 쉽기 때문에 많이 사용되는 기본적인 해시함수로서 키값이 정수로 가정된다. 위 그림에서 해시함수의 공식은 $키의,값%,13$이다. 69와 17 두 수 모두 13으로 나눈 나머지가 4이기 때문에 해시값 4에서 충돌이 발생하여 체이닝을 통해 연결되는 것을 확인할 수 있다.</p>
<pre><code class="language-python"># 체이닝을 예시코드로 배워보자.
# 아래와 같이 리스트안에 중첩되는 리스트를 만들어서 연결개념으로 해시테이블을 생성한다.
chain_hash_table = [[] for _ in range(10)]  # 이번에는 10의 길이로 테스트를 진행한다.(0~9, 총 10개의 인덱스)
print(chain_hash_table)
---
&gt; [[], [], [], [], [], [], [], [], [], []]</code></pre>
<pre><code class="language-python"># 해시함수는 위와 동일하게 테스트할 수 있다.
def chain_hash_func(key):
    return key % len(chain_hash_table)

print(chain_hash_func(10)) 
print(chain_hash_func(20)) 
print(chain_hash_func(25))
---
&gt;
0
0
5</code></pre>
<pre><code class="language-python"># append를 활용해서 키-값 쌍을 해시테이블에 삽입한다.
def chain_insert_func(chain_hash_table, key, value):
    hash_key = chain_hash_func(key)
    chain_hash_table[hash_key].append(value)

chain_insert_func(chain_hash_table, 10, &#39;A&#39;)
print(chain_hash_table)

chain_insert_func(chain_hash_table, 25, &#39;B&#39;)    # 5번째 인덱스에 B가 삽입된다.
print(chain_hash_table)

# 아래 결과값과 같이 중첩되는 결과값이 있더라도 값이 대체(충돌)되는 것이 아니다.
# 리스트 메소드 개념(list.append)이 활용되어 값을 이어 붙인다.(&#39;A&#39; -&gt; &#39;C&#39;)
chain_insert_func(chain_hash_table, 20, &#39;C&#39;)    
print(chain_hash_table)
---
[[&#39;A&#39;], [], [], [], [], [], [], [], [], []]
[[&#39;A&#39;], [], [], [], [], [&#39;B&#39;], [], [], [], []]
[[&#39;A&#39;, &#39;C&#39;], [], [], [], [], [&#39;B&#39;], [], [], [], []]</code></pre>
<p>위의 코드는 체인법의 개념을 초기에 이해하기에 좋은 예시코드이다. 실제로 Class와 함수를 이용하여 체인법을 구성하면 다음과 같이 구성할 수 있다.</p>
<pre><code class="language-python">from __future__ import annotations # 변수의 type에 대한 주석
from typing import Any, Type       # 타입힌트를 지원하는 모듈
import hashlib                     # 문자열을 해싱할 때 사용하는 모듈</code></pre>
<p>구현 시 사용할 라이브러리들이다. 대체로 어노테이션과 관련된 라이브러리로 타입힌트 등을 사용하지 않으면 <code>hashlib</code>라이브러리를 제외하고 사용하지 않아도 이상이 없을 것으로 예상한다.</p>
<pre><code class="language-python">class Node:
    &#39;&#39;&#39;해시를 구성하는 노드&#39;&#39;&#39;
    def __init__(self, key:Any, value: Any, next: Node) -&gt; Node:
        &#39;&#39;&#39;초기화&#39;&#39;&#39;
        self.key = key      #키
        self.value = value  # 값
        self.next = next    # 뒤쪽 노드를 참조</code></pre>
<p>Node 클래스는 개별 버킷을 나타낸다. Node 클래스는 키와 값이 짝을 이루는 구조이다. 키에 해시 함수를 적용하여 해시값을 구한다.</p>
<pre><code class="language-python">class ChainedHash:
    &quot;&quot;&quot;
    체인법으로 해시 클래스 구현
    &quot;&quot;&quot;
    def __init__(self, capacity: int)-&gt; None:
        &#39;&#39;&#39;초기화&#39;&#39;&#39;
        self.capacity = capacity            # 해시 테이블의 크기를 지정
        self.table = [None]*self.capacity   # 해시 테이블(리스트)를 선언

    def hash_value(self, key: Any) -&gt; int:
        &#39;&#39;&#39;해시값을 구함&#39;&#39;&#39;
        if isinstance(key, int):
            return key % self.capacity
        return(int(hashlib.sha256(str(key).encode()).hexdigest(), 16) % self.capacity)

    def search(self, key:Any) -&gt; Any:
        hash = self.hash_value(key) # 검색하는 키의 해시값
        p = self.table[hash]        # 노드를 주목

        while p is not None:
            if p.key == key:
                return p.value  # 검색 성공
            p = p.next          # 뒤쪽 노드를 주목
        return None

    def add(self, key:Any, value:Any) -&gt; bool:
        &#39;&#39;&#39;키가 key이고 값이 value인 원소를 추가&#39;&#39;&#39;
        hash = self.hash_value(key)
        p = self.table[hash]

        while p is not None:
            if p.key == key:
                return False # 추가실패
            p = p.next

        temp = Node(key, value, self.table[hash]) # self.table[hash]는 None이 된다.
        self.table[hash] = temp # 노드를 추가
        return True

    def removes(self, key:Any) -&gt; bool:
        &#39;&#39;&#39;키가 key인 원소를 삭세&#39;&#39;&#39;
        hash = self.hash_value(key)
        p = self.table[hash]
        pp = None

        while p is not None:
            if p.key == key:
                if pp is None:
                    self.table[hash] = p.next
                else:
                    pp.next = p.next
                return True # key 삭제 성공
            pp = p
            p = p.next  # 뒤쪽 노드를 주목
        return False    # 삭제 실패(key가 존재하지 않음)</code></pre>
<p>그리고 각각의 기능을 클래스에 구현하여 주었다. 다른 설명은 제외하고 <code>hash_value</code>함수의 key가 <code>int</code>형이 아닌 경우에 대해서 설명하겠다.</p>
<p>Key가 정수가 아닌 경우에는 그 값으로는 바로 나눌 수 없다. 그래서 <code>sha256</code>알고리즘, <code>encode()</code>함수, <code>hexdigets()</code>함수, <code>int()</code>함수 등의 표준 라이브러리로 형 변환을 해야 해시값을 얻을 수 있다.</p>
<blockquote>
<ul>
<li><strong>sha256 알고리즘</strong>
hashlib모듈에서 제공하는 sha256은 RSA의 FIPS알고리즘을 바탕으로 하며, 주어진 바이트(byte) 문자열의 해시값을 구하는 해시 알고리즘의 생성자(Constructor)이다. Hashlib모듈은 sha256외에도 MD5 알고리즘인 md5 등 다양한 해시 알고리즘을 제공한다.</li>
</ul>
</blockquote>
<ul>
<li><strong>encode( )함수</strong>
hashlib.sha256에는 바이트 문자열의 인수를 전달해야 한다. 그래서 Key를 str형 문자열로 변환한 뒤 그 문자열을 <code>encode()</code>함수에 전달하여 바이트 문자열을 생성한다.</li>
<li><strong>hexdigest( )함수</strong>
sha256알고리즘에서 해시값을 16진수 문자열로 꺼낸다.</li>
<li><strong>int(문자열,16) 함수</strong>
hexdigest()함수로 꺼낸 문자열을 16진수 문자열로 하는 int형으로 변환한다.</li>
</ul>
<p>만일 위에서 원소를 출력하여 보고 싶다면 다음과 같은 함수를 추가하면 된다.</p>
<pre><code class="language-python">def dump(self) -&gt; None:
    for i in range(self.capacity):
        p = self.table[i]
        print(i, end=&quot;&quot;)
        while p is not None:
            print(f&#39; -&gt; {p.key}({p.value})&#39;, end =&quot;&quot;)
            p = p.next
        print()</code></pre>
<p>다음과 같이 객체를 생성하여 테스트하면 다음과 같이 출력된다.</p>
<pre><code class="language-python">hash_table = ChainedHash(13)
hash_table.add(1, 14)
hash_table.add(3, 29)
hash_table.add(4, 69)
hash_table.add(9, 17)
hash_table.add(19, 5)
hash_table.add(6, 6)
hash_table.add(&quot;이것은 Key&quot;, 46)
hash_table.add(15, 20)
hash_table.add(23, 33)
hash_table.add(&quot;key&quot;, &quot;value&quot;)
hash_table.dump()
---
0
1 -&gt; 이것은 Key(46) -&gt; 1(14)
2 -&gt; 15(20)
3 -&gt; 3(29)
4 -&gt; 4(69)
5 -&gt; key(value)
6 -&gt; 6(6) -&gt; 19(5)
7
8
9 -&gt; 9(17)
10 -&gt; 23(33)
11
12</code></pre>
<h2 id="오픈-주소법open-addressing">오픈 주소법(Open addressing)</h2>
<p>해시 충돌이 발생할 때 해결하는 또 다른 방법으로 <strong><span style="color:orange">오픈 주소법<sup>open addressing</sup></span></strong>이 있다. 오픈 주소법은 <strong>충돌이 발생했을 때 재해시<sup>rehashing</sup></strong>를 수행하여 <strong>빈 버킷을 찾는 방법</strong>을 말하며 <strong><span style="color:green">닫힌 해시법<sup>closed hashing</sup></span></strong>이라고도 한다.
<img src="https://velog.velcdn.com/images/sea_panda/post/e69af157-dbf4-4712-8074-b75671d58fff/image.png" alt=""></p>
<p><a href="https://velog.io/@gayo03/%EC%9E%90%EB%A3%8C%EA%B5%AC%EC%A1%B0-%ED%95%B4%EC%8B%9C">이미지 출처</a></p>
<p>위의 그림처럼 오픈 주소법은 빈 버킷이 나올 때까지 재해시를 반복하므로 <strong>선형 탐사법<sup>linear probing</span></strong>이라고도 한다.</p>
<p>이처럼 재해시를 하게되면 원소를 삭제할 때 문제가 발생한다. 위의 그림의 가장 마지막 배열에서 5를 삭제한다고 가정해보자. 인덱스가 5인 버킷을 비우기만 하면 될 것 같지만 실제로는 그렇지 않다. 해시값이 같은 18을 검색할 때 해시값이 5인 데이터는 존재하지 않는다고 착각하여 검색에 실패하기 때문이다. 18이 재해시한 것으로 보이지만 해시함수 자체는 변하지 않기 때문에 방금 추가한 18의 해시값은 여전히 5이기 때문이다.</p>
<p>이러한 오류를 방지하기 위해서 각 버킷에 다음과 같은 속성을 부여한다. </p>
<blockquote>
<ul>
<li>데이터가 저장되어 있음(숫자)</li>
</ul>
</blockquote>
<ul>
<li>비어 있음(-)</li>
<li>삭제 완료(⭐️)</li>
</ul>
<p>속성이 부여되면 인덱스가 5인 버켓의 데이터가 삭제되도 삭제 완료라는 속성으로 인해서 재해시를 진행하여 검색하는 값이 나올 때까지 탐색하게 된다.</p>
<p>위의 그림에서도 알 수 있듯이 오픈 주소법은 체이닝과 다르게 <strong>저장공간</strong>이 정해져 있다.</p>
<h2 id="❗️-파이썬의-해시충돌-해결법은">❗️ 파이썬의 해시충돌 해결법은?</h2>
<p>파이썬에서 해시테이블로 구현된 자료형은 <strong>딕셔너리(Dictionary)</strong>이다. 그렇다면 이 딕셔너리 자료형은 해시충돌이 발생했을 때 어떤 방식으로 해결할까?</p>
<p>파이썬의 경우 <strong>내부적으로 오픈 주소법 방식</strong>을 활용한다고 한다. 파이썬에서 오픈 주소법을 활용하기 때문에 빈 공간이 없는 경우 시간이 오래 걸릴 수 있다. 따라서 <strong>로드 팩터를 작게 설정하여 성능 저하 문제를 해결</strong>한다.</p>
<p>그렇다면 또 이런 의문이 들것이다. 이에 대해 찾아보니 <a href="https://docs.python.org/ko/3.11/faq/design.html?highlight=%EB%94%95%EC%85%94%EB%84%88%EB%A6%AC#how-are-dictionaries-implemented-in-cpython">공식문서 QnA</a>에서 그저 크기 조정이 가능한 해시테이블로 구현한다고 나와있다. 여기서부터는 정확하지 않지만 어느 정도의 기본적인 크기를 지정하고 해시테이블이 모두 채워지면 더 많은 버킷을 가진 테이블을 새로 생성하는 것 같다.</p>
<h3 id="load-factor로드-팩터">Load Factor(로드 팩터)?</h3>
<p><img src="https://velog.velcdn.com/images/sea_panda/post/3b51e719-58bf-4109-ae48-5eb4e9ce7c63/image.png" alt=""></p>
<ul>
<li>해시 테이블에 저장된 항목 수(해시 테이블에 <strong>입력된 키 갯수</strong>)를 슬롯 수(해시 테이블 <strong>전체 인덱스 갯수</strong>)로 나눈 값이다.<ul>
<li>오픈 주소법을 사용하면 최대 로드 팩터는 1정도 나온다.</li>
<li>체이닝을 사용하면 로드 팩터는 오픈 주소법보다 좋은 성능(Load Factor &lt;= 1)을 보일 수 있다. 하지만 체이닝 기법에 필요한 연결 리스트 구현에 큰 오버헤드가 요구된다.<ul>
<li>로드 팩터를 낮추면 해시값이 비어있는 슬롯을 가르킬 확률이 높기 때문에 해시에 대한 성능이 올라간다.<br></li>
</ul>
</li>
</ul>
</li>
<li>위의 공식에 나와있는 방식으로 로드 팩터를 계산하여 비율에 따라 <strong>해시함수 재작성 여부, 해시테이블 크기 조정 여부</strong>가 결정된다. <ul>
<li>로드 팩터값을 통해 해시 테이블의 성능정도를 파악할 수 있다.<br></li>
</ul>
</li>
<li>하지만 어디까지나 대략적인 성능측정도구로 절대적이지 않다.<ul>
<li>로드 팩터에는 해시 테이블의 상황, 입출력의 상황, 메모리에 적재되는 시간 등 영향을 주는 요소가 다양하다.</li>
<li>로드 팩터에서 발생할 수 있는 상대성을 고려하며 개념을 활용하기 보다 이러한 개념이 있다는 것을 인지하여야 한다.</li>
</ul>
</li>
</ul>
<h2 id="💡-해시테이블의-다양한-실생활-사례">💡 해시테이블의 다양한 실생활 사례</h2>
<ul>
<li>전화번호부(사람의 이름을 전화 번호에 매핑)</li>
<li>DNS확인(웹 주소를 IP주소에 매핑)</li>
<li>학생 기록(고유한 학생 ID가 학생 정보에 매핑)</li>
<li>도서관 시스템(책의 고유 식별자가 자세한 책 정보에 매핑)</li>
</ul>
<hr>
<h1 id="정리">정리</h1>
<p><img src="https://velog.velcdn.com/images/sea_panda/post/da10b12e-548c-40e3-8713-eefbe3648849/image.png" alt=""></p>
<hr>
<blockquote>
<p>❗️<strong>참고자료</strong></p>
</blockquote>
<ol>
<li>코드스테이츠 교육자료</li>
<li><a href="https://fierycoding.tistory.com/68">파이썬 딕셔너리(네이버 블로그)</a></li>
<li><a href="https://docs.python.org/ko/3/index.html">파이썬 공식문서</a></li>
<li>BohYoh Shibata, 옮긴이 강민, DO it! 자료구조와 함께 배우는 알고리즘 입문 파이썬 편, 서울:이지스퍼블리싱, 2022</li>
</ol>
]]></description>
        </item>
        <item>
            <title><![CDATA[[N533] 내용정리]]></title>
            <link>https://velog.io/@sea_panda/N533-%EB%82%B4%EC%9A%A9%EC%A0%95%EB%A6%AC</link>
            <guid>https://velog.io/@sea_panda/N533-%EB%82%B4%EC%9A%A9%EC%A0%95%EB%A6%AC</guid>
            <pubDate>Tue, 07 Mar 2023 00:55:45 GMT</pubDate>
            <description><![CDATA[<h1 id="학습목표">학습목표</h1>
<ul>
<li><strong>깊이 우선 탐색(Depth-First Search, DFS)</strong>을 배우고 <strong>DFS</strong> 코드를 이해한다.</li>
<li><strong>너비 우선 탐색(Breadth-First Serach, BFS)</strong>을 배우고 <strong>BFS</strong> 코드를 이해한다.</li>
<li>위의 개념과 기존에 배웠던 기본적인 <strong>스택, 재귀, 트리, 그래프 등</strong>을 연관지어 이해한다.</li>
</ul>
<hr>
<h1 id="bfs--dfs">BFS &amp; DFS</h1>
<p>이전 시간에 그래프와 트리의 순회에 대해소 학습했다. 순회란 그래프에서 <span style="color:black;background-color:#F7DDBE"><strong>모든 노드를 방문하는 것을 말한다.</strong></span> 그리고 이러한 순회의 방법으로 <strong>DFS와 BFS</strong>가 있다.
<img src="https://velog.velcdn.com/images/sea_panda/post/0282ef08-55c3-4dab-8cf0-0530c333fa73/image.png" alt=""></p>
<ul>
<li><p>목적: 모든 정점을 1번씩 방문하기 위한 것</p>
</li>
<li><p>순회(방문)하면서 탐색하는 탐색 알고리즘</p>
</li>
<li><p>출발 노드와 그래프/트리 구조에 따라 탐색하는 순서와 노드가 달라질 수 있다.</p>
</li>
<li><p>주의사항: 방문한 노드인지 아닌지에 대한 확인이 필요하다.</p>
<center>

<p><img src="https://velog.velcdn.com/images/sea_panda/post/654a5d27-c76d-4a02-ac3e-dbeb4727c8b5/image.gif" alt=""></p>
</center>
## 깊이 우선 탐색(Depth-First-Serach, DFS)
깊이 우선 탐색이란 현재 정점에서 갈 수 있는 점들까지 최대한 **깊게 들어가면서 탐색**한다. 자료구조 중 **스택(Stack)** 또는 **재귀**를 이용하여 구현할 수 있다.
<center>

<p><img src="https://velog.velcdn.com/images/sea_panda/post/e7b229d0-9178-4024-827c-49d551efabaa/image.gif" alt=""></p>
</center>
</li>
<li><p>시작 정점으로부터 하나의 분기를 전부 방문한 후 다음 분기로 넘어가는 방식</p>
<ul>
<li>DFS는 깊이를 우선적으로 탐색 후, 재귀적으로 아래에서부터 탐색하지 않은 정점이 있는지 확인하는 방법</li>
</ul>
</li>
<li><p>이전 경로의 정보가 필요한 경우나, 모든 노드를 방문하는 경우 사용된다.</p>
</li>
<li><p>활용예시</p>
<ul>
<li>가중 그래프의 최소 스패닝 트리 찾기</li>
<li>길 찾기</li>
<li>그래프에서 주기 감지</li>
<li>미로 문제<blockquote>
<p>💡 <strong>DFS 절차(재귀)</strong></p>
</blockquote>
</li>
</ul>
<ol>
<li>노드를 방문 리스트에 기록</li>
<li>현재 노드에 인접한 노드를 기준으로 반복</li>
<li>노드의 인접리스트가 비었을 경우 종료(Base Case)</li>
<li>방문하지 않은 노드인 경우 DFS함수 재귀호출<pre><code class="language-python">def dfs_recur(start, graph, visited=[]):
# 방문리스트에 체크
visited.append(start)
# 현재 노드에 인접한 노드를 기준으로 반복
for node in graph[start]:
  # 방문하지 않은 노드인 경우:
  if node not in visited:
      # dfs 재귀 수행
      dfs_recur(node, graph, visited)
return visited</code></pre>
</li>
</ol>
</li>
</ul>
<blockquote>
<p>💡 <strong>DFS 절차(Stack)</strong>
    1. 방문 리스트에 시작 노드 기록
    2. Stack에 시작 노드의 인접 노드 삽입(Push)
    3. Stack에서 노드를 POP하면서 방문처리(출력) 한다.
    4. 꺼내온 노드와 이웃한 노드를 Stack에 넣는다.(Push) 그 후 방문했던 노드인지 아닌지 체크한다.
    5. Stack에 아무것도 남지 않을 때까지 2<del>4를 반복한다.
    6. 모든 노드를 방문할때까지 1</del>5를 반복한다.</p>
</blockquote>
<pre><code class="language-python">def dfs_stack(start_node, graph):
    visited = []   # 방문 리스트
    stack = [start_node]
    # 반복문(스택 안에 값이 있는 동안 반복):
    while stack:
        # 스택에서 pop
        node = stack.pop() 
        # 방문 리스트에 없는 경우:
        if node not in visited:
            # 스택에 인접 노드 push
            visited.append(node)
            # 방문리스트에 기록
            stack.extend(graph[node])
    return visited</code></pre>
<p>위 두 코드를 실행하면 사실 다른 결과가 나온다.</p>
<pre><code class="language-python"># 테스트 해보기

graph_0 = {
    1: [2,3,4],
    2: [5],
    3: [6],
    4: [],
    5: [7],
    6: [5],
    7: [6]
}

print(dfs_recur(1, graph_0, visited=[])) # [1, 2, 5, 7, 6, 3, 4]
print(dfs_stack(1, graph_0))             # [1, 4, 3, 6, 5, 7, 2]</code></pre>
<p><img src="https://velog.velcdn.com/images/sea_panda/post/caca6abb-6026-49e8-9c16-c1dbb85fd4d6/image.png" alt=""></p>
<p>이는 Stack의 경우 POP이라는 명령어를 이용해서 제일 끝 단 노드를 꺼내오면서 탐색을 시작하기 때문이다. 만일 재귀함수로 구현한 DFS와 Stack을 통해 구현한 DFS의 결과값을 동일하게 하고 싶다면 다음과 같이 코드를 수정해주면 된다.</p>
<pre><code class="language-python"># 재귀와 똑같이 하려면? 순서를 바꾸면 됨.

# DFS 구현 2. 스택
def dfs_stack(start_node, graph):
    visited = []   # 방문 리스트
    stack = [start_node]

    # 반복문(스택 안에 값이 있는 동안 반복):
    while stack:
        # 스택에서 pop
        node = stack.pop() 
        # 방문 리스트에 없는 경우:
        if node not in visited:
            # 스택에 인접 노드 push
            visited.append(node)
            # 방문리스트에 기록
            stack.extend(graph[node][::-1])# 이 부분 변경
            # [::-1] : 연결리스트 안에있는 노드를 역순으로 꺼내면 됨
    return visited

print(dfs_stack_2(1, graph_0))   # [1, 2, 5, 7, 6, 3, 4]</code></pre>
<h3 id="재귀와-스택의-차이점">재귀와 스택의 차이점</h3>
<ul>
<li>로직이 직관적이고 이해하기 쉽다.<ul>
<li>스택은 리스트의 메소드만을 활용하였기 때문에 위에서 아래로  코드를 해석하면 되므로</li>
</ul>
</li>
<li>스택이 실행 속도 또한 재귀보다 빠르다.<ul>
<li>스택의 특징인 후입선출 개념을 적용하였기 때문에, 마지막에 삽입된 노드를 기준으로 깊이우선탐색을 진행한다. </li>
</ul>
</li>
<li>재귀구현의 경우는 자기함수를 호출하는 형태이기 때문에 코드가 간결해진다는 장점이 있다.<ul>
<li>재귀와 스택 방법의 차이점을 찾을 수 있도록 각 방법의 특징을 이해해야 한다.</li>
</ul>
</li>
</ul>
<h3 id="dfs와-백트랙킹backtracking">DFS와 백트랙킹(Backtracking)</h3>
<p><strong>DFS는 가능한 모든 경로(후보)를 탐색한다.</strong> 따라서, 불필요할 것 같은 경로를 사전에 차단하는 등의 행동이 없으므로 경우의 수를 줄이지 못한다.</p>
<p><span style="color:black;background-color:#F7DDBE"><strong>탐색하는 방향에 답이 없다고 판단되면, 되돌아가서 다른 방향을 탐색하는 기법</strong></span>을 바로 <strong>백트래킹</strong>이라고 한다. 즉, 반목문의 횟수를 줄일 수 있으므로 효율적이다. 이를 <strong>가지치기</strong>라고 하는데, 불필요한 부분을 쳐내고 최대한 올바른 쪽으로 간다는 의미이다.</p>
<blockquote>
<p>❗️ 백트래킹 예시 문제: 백준 9663번 (<a href="https://www.acmicpc.net/problem/9663">문제 링크</a>)</p>
</blockquote>
<h2 id="너비-우선-탐색breadth-first-search-bfs">너비 우선 탐색(Breadth-First Search, BFS)</h2>
<p>현재 정점에 연결된 가까운 점들부터 최대한 넓게 탐색한다. 자료구조 중 <strong>큐(Queue)</strong>를 이용해 구현할 수 있다.
<img src="https://velog.velcdn.com/images/sea_panda/post/e473058b-dddb-428d-9007-eaa73b457c89/image.gif" alt=""></p>
<ul>
<li>노드가 적은 그래프를 순회하거나, 최단경로를 탐색할 때 유용</li>
<li>단점: Queue를 활용하므로 노드가 많아지는 경우 필요한 메모지 저장공간이 증가한다.<ul>
<li>인접한 노드의 정보를 모두 가진 상태에서 밑으로 내려간다.</li>
</ul>
</li>
<li>활용예시<ul>
<li>길 찾기, 라우팅</li>
<li>BitTorrent와 같은 P2P 네트워크에서 인접 노드 찾기</li>
<li>웹 크롤러</li>
<li>소셜 네트워크에서 멀리 떨어진 사람 찾기</li>
<li>그래프에서 주변 위치 찾기</li>
<li>네트워크에서 방송</li>
<li>그래프에서 주기 감지</li>
<li>연결된 구성 요소 찾기</li>
<li>몇 가지 이론적 그래프 문제 풀기<blockquote>
<p>💡 <strong>BFS</strong> 절차</p>
</blockquote>
</li>
</ul>
<ol>
<li>방문 리스트에 시작 노드 기록</li>
<li>Queue에 시작 노드의 인접 노드 삽입(Enqueue)</li>
<li>Queue에서 노드를 Dequeue하면서 방문처리(출력)한다.</li>
<li>꺼내온 노드와 인접한 노드를 큐에 넣는다.(Enqueue) 그 후 방문했던 노드인지 아닌지 체크한다.</li>
<li>Queue에 아무것도 남지 않을 때까지 2~4를 반복한다.</li>
<li>모든 노드를 방문할때까지 1~5를 반복한다.</li>
</ol>
</li>
</ul>
<pre><code class="language-python"># deque 라이브러리를 활용한 queue 구현하기

# 우선 deque를 위해 자료구조의 큐에서 배웠던 내용을 복습한다.
from collections import deque

queue = deque([&quot;Eric&quot;, &quot;John&quot;, &quot;Michael&quot;])
queue.append(&quot;Terry&quot;)             # append: 오른쪽끝 삽입   
queue.append(&quot;Graham&quot;)
# print(queue.pop())
print(queue.popleft())            # popleft: 왼쪽끝 빼오기 pop(0)와 같은 역할이지만 상수시간 보장
# print(queue.popleft())
print(queue)</code></pre>
<pre><code class="language-python"># BFS 구현 - deque 사용
&quot;&quot;&quot;
1. 방문 리스트에 시작 노드를 기록
2. Queue에 시작노드의 인접 노드를 삽입(enqueue)
3. 큐에서 노드를 Dequeue하면서 방문처리(출력)한다.
4. 꺼내온 노드와 이웃한 노드를 큐에 넣는다.(enqueue)
  방문했던 노드인지 아닌지 체크한다.
5. 큐에 아무 것도 남지 않을때까지 2-4 반복
6. 모든 노드를 방문할때까지 1-5를 반복
&quot;&quot;&quot;

# deque 라이브러리 불러오기
from collections import deque

# BFS 메서드 정의
def bfs(start_node, graph):
    # 방문 처리용 리스트 만들기
    visited = []
    # 시작 노드를 큐에 삽입
    queue = deque([start_node])

    # 큐가 완전히 빌 때까지 반복
    while queue:
        # 큐에서 값을 뽑아낸다.
        cur_node = queue.popleft()  # 리스트의 queue.pop(0)과 같다. 그러나 시간복잡도 상수시간 보장
        # 해당 노드가 방문처리 된 노드라면
        if cur_node not in visited:
            # 방문처리용 큐에 노드 추가
            visited.append(cur_node)
            # 해당 노드의 인접한 노드를 큐에 추가
            queue.extend(graph[cur_node])
    return visited</code></pre>
<hr>
<h1 id="정리">정리</h1>
<ul>
<li><p>먼저 생각해볼 점</p>
<ul>
<li>각 알고리즘이 요구하는 메모리는 주어진 자료구조(그래프, 트리 등)의 행태와 알고리즘의 목적(탐색, 정렬 등)에 따라 달라질 수 있다.</li>
</ul>
</li>
<li><p><strong>DFS</strong></p>
<ul>
<li>Search할 노드의 세로 위치가 깊을 수록, BFS보다 노드를 찾는 속다가 빠르다.</li>
<li>노드의 갯수가 주어진 컴퓨터의 자원(메모리, 소프트웨어 등)이 감당할 수 있는 범위 이상으로 증가하는 경우, Stack과 재귀방법을 활용하여 탐색을 진행하기 때문에 무한루프 에러가 발생할 확률이 높아진다.</li>
</ul>
</li>
<li><p><strong>BFS</strong></p>
<ul>
<li>Search할 노드가 가로 위치로 인접한 경우, DFS보다 효과적일 수 있다.</li>
<li>Queue를 이용해 노드를 저장하는데, Queue는 탐색할 모든 노드를 저장하는 특징이 있다.</li>
<li>때문에 메모리를 벗어날 정도로 노드의 갯수가 증가하는 경우 DFS보다 메모미를 많이 소비할 수 있다.</li>
</ul>
</li>
</ul>
<blockquote>
<p>❗️<strong>참고자료</strong></p>
</blockquote>
<ol>
<li>코드스테이츠 교육자료</li>
</ol>
]]></description>
        </item>
        <item>
            <title><![CDATA[[N532] TIL 및 회고(TIL이라 했지만 일주일이 지난..)]]></title>
            <link>https://velog.io/@sea_panda/N532-TIL-%EB%B0%8F-%ED%9A%8C%EA%B3%A0TIL%EC%9D%B4%EB%9D%BC-%ED%96%88%EC%A7%80%EB%A7%8C-%EC%9D%BC%EC%A3%BC%EC%9D%BC%EC%9D%B4-%EC%A7%80%EB%82%9C</link>
            <guid>https://velog.io/@sea_panda/N532-TIL-%EB%B0%8F-%ED%9A%8C%EA%B3%A0TIL%EC%9D%B4%EB%9D%BC-%ED%96%88%EC%A7%80%EB%A7%8C-%EC%9D%BC%EC%A3%BC%EC%9D%BC%EC%9D%B4-%EC%A7%80%EB%82%9C</guid>
            <pubDate>Mon, 06 Mar 2023 14:36:03 GMT</pubDate>
            <description><![CDATA[<h1 id="학습내용">학습내용</h1>
<ul>
<li>쾨니히스베르크의 다리 (<a href="https://namu.wiki/w/%EC%BE%A8%EB%8B%88%ED%9E%88%EC%8A%A4%EB%B2%A0%EB%A5%B4%ED%81%AC%20%EB%8B%A4%EB%A6%AC%20%EA%B1%B4%EB%84%88%EA%B8%B0%20%EB%AC%B8%EC%A0%9C">링크</a>)</li>
</ul>
<p><img src="https://velog.velcdn.com/images/sea_panda/post/3a8bd238-1522-438f-a2aa-8c3a839df581/image.png" alt=""></p>
<p> &quot;임의의 지점에서 출발하여 일곱 개의 다리를 한 번씩만 건너서 원래 위치로 돌아오는 방법&quot;에 대한 문제가 있었고, 많은 사람들이 이 문제의 답을 찾기 위해 노력을 했다.</p>
<p>그렇다면 정답은 무엇일까.</p>
<blockquote>
<p>없다.</p>
</blockquote>
<p>당시 <a href="https://namu.wiki/w/%EB%A0%88%EC%98%A8%ED%95%98%EB%A5%B4%ED%8A%B8%20%EC%98%A4%EC%9D%BC%EB%9F%AC">레온하르트 오일러</a>는 이 문제를 다음 그림과 형태로 각각의 다리에 a부터 g까지 이름을 부여하고 도식화했다.</p>
<p><img src="https://velog.velcdn.com/images/sea_panda/post/0c39e60b-a5b0-453a-b9e0-c75de3a852a1/image.png" alt=""></p>
<p>오일러의 스케치를 현대식 그래프 구조에 따라 나타낸 아래 그림에서는 A부터 D까지를 정점(Vertex), a부터 g까지는 간선(Edge)으로 구성된 <strong><code>그래프</code></strong>라는 수학적 구조를 찾아볼 수 있다.</p>
<h2 id="그래프">그래프</h2>
<ul>
<li><strong>정점(vertex=node)과 간선(=edge=link)으로 이루어진 자료구조</strong></li>
<li>$G = (V, E)$
<img src="https://velog.velcdn.com/images/sea_panda/post/11369a4b-1f86-47bd-8030-d92541f0db18/image.png" alt=""></li>
</ul>
<h2 id="그래프의-유형">그래프의 유형</h2>
<p><strong>방향성, 순환,  가중치</strong></p>
<ul>
<li><p>그래프의 특성은 <strong>directed(방향성)</strong> 또는 <strong>undirected(무방향성) 그래프</strong>가 있다.</p>
</li>
<li><p><strong>Directed Graph</strong> : 방향성이 있는 그래프이다. (유향 그래프 또는 방향성 그래프라고 불림)
  <img src="https://velog.velcdn.com/images/sea_panda/post/95abc813-0749-4eb6-97e1-217bb6455cbb/image.png" alt="“한쪽 방향(one-way)”으로 설명될 수 있다면 directed가 가장 적합"></p>
<p>  “한쪽 방향(one-way)”으로 설명될 수 있다면 directed가 가장 적합하다.
  방향성그래프는 보는 것처럼 순서가 있으므로 <strong>마지막 노드(리프, leaf)</strong>가 있다. 위 그림에서는 &#39;F&#39;가 리프노드이다.</p>
</li>
<li><p><strong>bidirectional(양방향)</strong>
<img src="https://velog.velcdn.com/images/sea_panda/post/efe57940-73c5-49de-8a7c-16e187de8dd3/image.png" alt=""></p>
<p>위처럼 Directed Graph는 양방향이 될 수 있다.</p>
<p>하지만 노드연결관계의 목적이 상호 교환이라면, <strong>undirected graph</strong>가 가장 적합하다.</p>
<ul>
<li>상호 교환 : 화살표로 연결된 노드들이 서로 노드정보를 공유하는 것</li>
</ul>
</li>
<li><p><strong>Undirected Graph</strong> : 방향성이 없는 그래프이다.
  <img src="https://velog.velcdn.com/images/sea_panda/post/1ce8023f-e741-489b-b1e9-a9fb06e1d0da/image.png" alt=""></p>
<p>  위처럼 무방향성은 방향(화살표)이 따로 지정되어있지 않다. 간선으로 연결된 노드들끼리 서로 인접(adjacent)해있다고 하며, 이웃(neighbor)라고 칭한다.</p>
</li>
<li><p><strong>Weighted Graphs(가중 그래프)</strong></p>
<ul>
<li><p><strong>가중 그래프에는 edge(간선)</strong>와 관련된 값이 있다.
  <img src="https://velog.velcdn.com/images/sea_panda/post/00d4eef7-a637-4584-8be0-a8225828dbf5/image.png" alt=""></p>
<ul>
<li>각 edge의 가중치에 할당된 특정값을 호출한다.</li>
<li>가중치는 서로 다른 그래프에서 서로 다른 데이터를 나타낸다.</li>
<li>일상 예시<ul>
<li>교대역 —2분→ 강남역 —1분—&gt;역삼역</li>
</ul>
</li>
</ul>
</li>
</ul>
</li>
<li><p><strong>Cyclic and Acyclic Graphs(순환 및 비순환 그래프)</strong></p>
<ul>
<li><strong>순환(루프)을 형성할 수 있는 경우(예 : 방문한 노드에 다시 방문) 그래프는 순환 그래프</strong>이다.<ul>
<li>순환 그래프
<img src="https://velog.velcdn.com/images/sea_panda/post/6be3a8dc-3457-47cc-ac33-010eb93d0329/image.png" alt=""></li>
<li>비순환 그래프
  <img src="https://velog.velcdn.com/images/sea_panda/post/39aa5492-9a0c-488a-84c3-3a06242822c0/image.png" alt=""><ul>
<li>참고 : <strong>undirected</strong> 그래프는 항상 동일한 노드에 재방문할 수 있으므로 <strong>순환 그래프이다.</strong></li>
<li>순환을 형성할 수 없는 경우(예 : 모서리를 따라 이미 방문한 노드에 방문할 수 없음) <strong>비순환</strong>
그래프라고 한다.</li>
</ul>
</li>
</ul>
</li>
</ul>
</li>
<li><p><strong>Directed Acyclic Graphs (DAGs) - 방향성 비순환 그래프</strong></p>
<p>  <img src="https://velog.velcdn.com/images/sea_panda/post/0c8d06cc-0703-46ba-80df-949fd2428d37/image.png" alt=""></p>
<ul>
<li><strong>순환되지 않고 특정한 단방향</strong> 그래프이다.</li>
<li>그림처럼 edge가 순서대로 향하도록 DAG의 노드를 <strong>선형(단방향)으로 정렬</strong>할 수 있다.</li>
<li>트리도 DAGs의 일종이다.</li>
<li>DAG는 작업들의 우선순위를 표현을 할 때 DAG를 많이 사용하게 된다. 
예를들어 공장에서 작업 스케줄링을 할 때 A 라는 작업이 끝나고 B를 해야하고 B 가 끝난 다음에는 C,D를 해야한다는 것을 DAG로 표현할 수 있다.<ul>
<li>활용 예시 : airflow DAG(<a href="https://www.bearpooh.com/151">링크</a>)</li>
</ul>
</li>
<li>또한 사이클이 없는 방향그래프라는 정의를 통해 모든 트리는 DAG임을 알 수 있다. 
(어떤 그래프가 주어졌을 때 이 그래프가 DAG인지 판단하기 위해서는 사이클의 존재여부를 확인하면 된다.)<ul>
<li>처음 출발한 노드(정점) v에서 시작하여 끝내 다시 v로 돌아가 순환 반복될 수 있는 방법이 없는 그래프라고 이해하면된다.</li>
</ul>
</li>
</ul>
</li>
</ul>
<h2 id="트리">트리</h2>
<ul>
<li><p>트리의 특성: 루트가 있고, 아래로 차일드 노드들이 있고, 노드를 연결하는 엣지가 있고,</p>
<p>  엣지의 방향성은 위에서 아래로 진핸된다.</p>
</li>
<li><p>트리는 그래프의 한 형태이다.</p>
<ul>
<li>트리는 루트가 있고, 사이클이 없는, 아래로만 흐르는 방향그래프이다.<h3 id="트리와-그래프의-차이">트리와 그래프의 차이</h3>
<img src="https://velog.velcdn.com/images/sea_panda/post/ddf9d352-d146-461c-a4e1-4140ca8c0cfd/image.png" alt=""></li>
</ul>
</li>
</ul>
<p>참고 : <a href="https://gmlwjd9405.github.io/2018/08/13/data-structure-graph.html">https://gmlwjd9405.github.io/2018/08/13/data-structure-graph.html</a></p>
<h2 id="그래프의-활용">그래프의 활용</h2>
<p><strong>인접행렬, 인접리스트</strong></p>
<ul>
<li>그래프를 표현한다는 것은 인접성을 표현한다는 것을 이야기 합니다. 어떤 노드와 어떤 노드가 edge로 연결되어 있는지를 표현하는 것</li>
<li>두 노드가 간선으로 연결되어 있다면 ‘<strong>두 노드는 인접하다</strong>’라고 표현한다.</li>
</ul>
<p>그래프를 나타내는 방법</p>
<ul>
<li><strong>인접행렬(adjacency matrices)</strong> : 이차원 배열에 표시하는 방법</li>
<li><strong>인접리스트(adjacency lists)</strong> : 배열의 노드들을 나열하고 관계를 연결리스트로 표현하는 방법</li>
</ul>
<p><img src="https://velog.velcdn.com/images/sea_panda/post/177394b7-6195-41f9-b7bd-8cd600356dfb/image.png" alt=""></p>
<p>• 각 유형을 사용할 때, verts C와 D 사이의 관계를 어떻게 표현하는지가 중요하다.</p>
<h3 id="인접행렬-adjacency-matrix">인접행렬 (Adjacency Matrix)</h3>
<ul>
<li>인접 행렬은 표 형태로 표현하는 방법으로, 2차원 배열에 각 노드가 연결된 형태를 기록하는 방식이다.
파이썬에서는 2차원 리스트로 구현할 수 있다.</li>
</ul>
<p><img src="https://velog.velcdn.com/images/sea_panda/post/8571b4fa-7d2d-472c-946e-c81f82189298/image.png" alt=""></p>
<pre><code class="language-python"># 리스트로 구현한 인접행렬
# 아래 코드처럼 위의 간선 가중치는 1이다.
class Graph:
    def __init__(self):
        self.edges = [[0,1,0,0,0,0,0],
                      [0,0,1,1,0,0,0],
                      [0,0,0,0,1,0,0],
                      [0,0,0,0,0,1,1],
                      [0,0,1,0,0,0,0],
                      [0,0,1,0,0,0,0],
                      [1,0,0,0,0,1,0]]</code></pre>
<ul>
<li>노드가 n개면 n by n 행렬을 만들게 된다.</li>
</ul>
<h2 id="인접리스트adjacency-list"><strong>인접리스트(Adjacency List)</strong></h2>
<ul>
<li><p>인접리스트에서 그래프는 <strong>전체 노드 목록을 저장</strong>한다.</p>
<p>  <img src="https://velog.velcdn.com/images/sea_panda/post/5e1e2552-7ceb-4210-91d9-2e66b0f35a75/image.png" alt=""></p>
<p>  노드와 인접한 노드들을 연결리스트로 쭉 나열하여 저장하는 것</p>
</li>
</ul>
<p><img src="https://velog.velcdn.com/images/sea_panda/post/689a7796-a9ec-4aa6-9917-81df426f0797/image.png" alt=""></p>
<pre><code class="language-python"># 위 그림에 대해 딕셔너리를 사용한 인접리스트 예시
# 노드가 키가 되고, 인접노드가 값이 되는 딕셔너리이다.
# 가장자리 노드들은 set으로 구현되어 있다.
class Graph:
    def __init__(self):
        self.vertices = {
                            &quot;A&quot;: {&quot;B&quot;},      # 여기서 {&quot;B&quot;}가 set의 형태이다.
                            &quot;B&quot;: {&quot;C&quot;, &quot;D&quot;}, # {&quot;B&quot; : {}}의 형태는 딕셔너리
                            &quot;C&quot;: {&quot;E&quot;},      # 즉, 딕셔너리 안에 set이 있는 것이다.
                            &quot;D&quot;: {&quot;F&quot;, &quot;G&quot;},
                            &quot;E&quot;: {&quot;C&quot;},
                            &quot;F&quot;: {&quot;C&quot;},
                            &quot;G&quot;: {&quot;A&quot;, &quot;F&quot;}
                        }</code></pre>
<ul>
<li>메모리 측면에서 보자면 인접 행렬 방식은 모든 관계를 저장하므로 노드 개수가 많을수록 메모리가 불필요하게 낭비된다.</li>
<li>반면에 인접리스트 방식은 연결된 정보만을 저장하기 때문에 메모리를 효율적으로 사용한다.</li>
<li>하지만 인접 리스트 방식은 연결된 데이터를 하나씩 확인해야 하기 때문에, 인접 행렬 방식에 비해 특정한 두 노드가 연결되어 있는지에 대한 정보를 얻는 속도가 느리다.
<img src="https://velog.velcdn.com/images/sea_panda/post/13c3c98e-28ce-4285-b648-9d97be74db1a/image.png" alt=""></li>
</ul>
<h2 id="예시-퀴즈-1">예시 퀴즈 (1)</h2>
<p><img src="https://velog.velcdn.com/images/sea_panda/post/a6bc8d1e-becc-41b3-9f0e-1c4309e7309d/image.png" alt=""></p>
<p>위 사진의 그래프를 인접행렬과 인접 리스트로 나타내어보아라.</p>
<pre><code class="language-python"># 무방향 그래프

# 인접 행렬
graph = [#0 1 2 3 4 5 6
         [0,0,0,0,0,0,0], # 0
         [0,0,0,0,0,0,0], # 1
         [0,0,0,0,0,0,0], # 2
         [0,0,0,0,0,0,0], # 3
         [0,0,0,0,0,0,0], # 4
         [0,0,0,0,0,0,0], # 5
         [0,0,0,0,0,0,0]  # 6
        ]

# 인접 리스트
graph = {
    0: {},
    1: {},
    2: {},
    3: {},
    4: {},
    5: {},
    6: {}
}</code></pre>
<ul>
<li><p>정답</p>
<pre><code class="language-python">  graph = [# 0 1 2 3 4 5 6
             [0,1,1,0,0,0,0], #0
             [1,0,1,1,0,0,0], #1
             [1,1,0,0,1,0,0], #2
             [0,1,0,0,1,0,0], #3
             [0,0,1,1,0,1,0], #4
             [0,0,0,0,1,0,1], #5
             [0,0,0,0,0,1,0]  #6
          ]

  graph = {
      0: {1, 2},
      1: {0, 2, 3},
      2: {0, 1, 4},
      3: {1, 4},
      4: {2, 3, 5},
      5: {4, 6},
      6: {5}
  }</code></pre>
</li>
</ul>
<h2 id="예시-퀴즈-2">예시 퀴즈 (2)</h2>
<p><img src="https://velog.velcdn.com/images/sea_panda/post/23e4d883-d10e-457e-a364-8c20f835677e/image.png" alt=""></p>
<pre><code class="language-python"># 단방향 그래프

# 인접행렬
graph = [#0 1 2 3 4 5
         [0,0,0,0,0,0], # 0
         [0,0,0,0,0,0], # 1
         [0,0,0,0,0,0], # 2
         [0,0,0,0,0,0], # 3
         [0,0,0,0,0,0], # 4
         [0,0,0,0,0,0]  # 5
        ]

# 인접리스트
graph = {
    0: {},
    1: {},
    2: {},
    3: {},
    4: {},
    5: {}
}</code></pre>
<ul>
<li><p>정답</p>
<pre><code class="language-python">  graph = [#0 1 2 3 4 5
           [0,1,0,1,0,0], # 0
           [0,0,1,0,0,0], # 1
           [0,0,0,0,1,1], # 2
           [0,0,0,0,1,0], # 3
           [0,0,0,0,0,1], # 4
           [0,0,0,0,0,0]  # 5
          ]

  # 인접리스트
  graph = {
      0: {1, 3},
      1: {2},
      2: {4,5},
      3: {4},
      4: {5},
      5: {}
  }</code></pre>
</li>
</ul>
<hr>
<h1 id="순회traversal">순회<strong>(Traversal)</strong></h1>
<p><img src="https://velog.velcdn.com/images/sea_panda/post/8f1ee12e-0df4-456c-ba61-bf434f4d1bbc/image.png" alt=""></p>
<p><img src="https://velog.velcdn.com/images/sea_panda/post/144deabd-073f-4b41-8f69-8e90eeff15b6/image.png" alt=""></p>
<p>순회 화살표를 잘 보자.</p>
<h2 id="순회-기본개념">순회 기본개념</h2>
<ul>
<li>순회는 Traversal로 명명되며, 그래프 또는 트리처럼 연결된 구조에서 <strong>노드를 한 번씩 탐색</strong>하는 개념이다.<ul>
<li>순회의 목적은 모든 노드 또는 특정 <strong>노드를 방문하는 방법</strong>을 찾는 것이다.</li>
<li>BST(이진검색트리)와 다른 규칙이 적용되며 <strong>방향에 따라 탐색방법</strong>이 달라질 수 있다.</li>
</ul>
</li>
</ul>
<h2 id="그래프와-트리의-순회구분">그래프와 트리의 순회구분</h2>
<p><strong>트리의 순회(전위, 중위, 후위)</strong></p>
<ul>
<li>그래프의 순회는 DFS(깊이우선탐색), BFS(너비우선탐색) 방법이 있다.<ul>
<li>DFS, BFS는 탐색 알고리즘이다.<ul>
<li>그래프는 루트, 부모, 자식노드 개념이 없지만 전위, 중위, 후위 순회의 순회개념을 활용하여 DFS, BFS를 구현할 수 있다.</li>
</ul>
</li>
</ul>
</li>
<li>트리의 순회는 전위, 중위, 후위순회이다.<ul>
<li>전위순회(preorder traverse): 루트를 먼저 방문 , root node → left node → right node</li>
<li>중위순회(inorder traverse): 왼쪽 서브트리를 방문 후 루트 방문, left → root → right</li>
<li>후위순회(postorder traverse): 왼쪽 서브트리, 오른쪽 서브트리, 루트 방문, left → right → root</li>
</ul>
</li>
</ul>
<h3 id="순회-실습-해보기">순회 실습 해보기</h3>
<p><img src="https://velog.velcdn.com/images/sea_panda/post/5600768c-ab0e-4ce2-9bb0-7bb8fa328a33/image.png" alt=""></p>
<pre><code class="language-python"># 이진 트리의 노드 클래스
class TreeNode:
    def __init__(self, val):
        self.val = val
        self.left = None
        self.right = None

# 전위 순회(pre-order) 함수
def preOrder(root):
    if root:
        print(root.val, end=&quot; &quot;)
        preOrder(root.left)
        preOrder(root.right)

# 중위 순회(in-order) 함수
def inOrder(root):
    if root:
        inOrder(root.left)
        print(root.val, end=&quot; &quot;)
        inOrder(root.right)

# 후위 순회(post-order) 함수
def postOrder(root):
    if root:
        postOrder(root.left)
        postOrder(root.right)
        print(root.val, end=&quot; &quot;)</code></pre>
<pre><code class="language-python">root = TreeNode(10)
root.left = TreeNode(8)
root.right = TreeNode(9)
root.left.left = TreeNode(7)
root.left.right = TreeNode(1)
root.right.left = TreeNode(11)
root.right.right = TreeNode(12)
root.left.right.left = TreeNode(3)
root.left.right.right = TreeNode(2)
root.right.right.left = TreeNode(13)

preOrder(root) # 전위 순회
print(&quot; &quot;)
inOrder(root)  # 중위 순회
print(&quot; &quot;)
postOrder(root) # 후위 순회</code></pre>
<p>결과가 아래와 같은지 확인해 봅시다.</p>
<pre><code class="language-python">전위순회 결과: 10 8 7 1 3 2 9 11 12 13

중위순회 결과: 7 8 3 1 2 10 11 9 13 12

후위순회 결과: 7 3 2 1 8 11 13 12 9 10</code></pre>
<hr>
<h1 id="인접행렬-인접리스트-구현하기">인접행렬, 인접리스트 구현하기</h1>
<p><img src="https://velog.velcdn.com/images/sea_panda/post/2157212d-c3a9-4ff7-99e0-3aefd83a38b0/image.png" alt=""></p>
<pre><code class="language-python"># 인접 행렬
input_list = [
[4, 1, 10], # 노드4번에서 노드1번으로 연결되는 가중치가 10입니다.
[3, 5, 24],
[5, 6, 2],
[3, 1, 41],
[5, 1, 24],
[4, 6, 50],
[2, 4, 66],
[2, 3, 22],
[1, 6, 25]
]

arr = [[0 for col in range(6)] for row in range(6)]

for a, b, c in input_list:
        arr[a-1][b-1] = c

arr</code></pre>
<pre><code class="language-python"># 인접 리스트

adj_dict = {}

for a,b,c in input_list:
        if a in adj_dict:
            adj_dict[a].update({b:c})
        else:
            adj_dict.update({a: {b:c}})</code></pre>
<h1 id="회고">회고</h1>
<p>사실 정리도 되게 오랜만에 하는 것 같다. 그간 다양한 일이 있었는데 일단 Section4 Project를 진행했고 Section5가 종료되었다. Section5는 CS파트로 자료구조와 알고리즘을 학습했다. 앞으로 프로젝트까지 약 3일?에서 4일 정도 남았는데 그간 정리 못한 파트들을 몰아서 정리할 계획이다.</p>
<p>참고로 N532부터 N534는 내가 정리한 내용이 아닌 코치님께서 정리해주신 내용을 사실상 싸악 긁어서 Velog에서 정상적으로 보이게 편집만 한 것이다.</p>
<p>이야기가 잠깐 딴 길로 갔는데, 일단 3일동안 배운 내용 복습하는 겸 정리할 계획이다. 최종적으로는 이 글이 N531뒤에 오겠지만, 작성은 Section5에서 어떤 글보다 먼저했다...</p>
<p>그리고 오늘 빅데이터분석기사 필기접수로 디스코드에서 동기분들이 이야기하시던데 나는 일단 일반기계기사를 공부하면서 고민을 좀 해봐야겠다. 원래는 전공을 아예 내팽겨치고 분야를 갈아타려했는데, 그것보다는 내 전공을 살리면서 인공지능을 다루면 보다 더 좋을 것 같아서 일단 병행하면서 공부할 계획이다.</p>
<p>영어 공부도 하려고 시원스쿨 학습지도 샀는데... 공부할 것들은 쌓아놓고 해결하지 못하는 것 같다. 진짜 다시 정신차리고 열심히 살아보자...</p>
<blockquote>
<p>❗️<strong>참고자료</strong></p>
</blockquote>
<ol>
<li>코드스테이츠 교육자료</li>
</ol>
]]></description>
        </item>
        <item>
            <title><![CDATA[[N432] TIL 및 회고]]></title>
            <link>https://velog.io/@sea_panda/N432-TIL-%EB%B0%8F-%ED%9A%8C%EA%B3%A0</link>
            <guid>https://velog.io/@sea_panda/N432-TIL-%EB%B0%8F-%ED%9A%8C%EA%B3%A0</guid>
            <pubDate>Mon, 30 Jan 2023 12:30:58 GMT</pubDate>
            <description><![CDATA[<p><img src="https://velog.velcdn.com/images/sea_panda/post/1abc6711-caa6-4882-a22f-e7d81aff0a43/image.png" alt="">
이제 이 정리글을 <strong>TIL</strong>이라고 할 수 있는지 의문이다. 처음에 시작할 때 매일 꾸준히 정리하자는 의미에서 TIL을 사용했는데 이게 참 나라는 사람이 꾸준하지 못한 것 같다. N432도 분명 지난주에 배웠는데 주말이 지나고서야 정리를 시작한다. </p>
<p>ADsP자격증 시험도 신청했는데 허허허...그것도 지난주 목요일에 하루 공부하고 미루고 있다. 인터넷에 2주만에 따기 3일 공부하기 취득하기 이런 블로그 글들이 즐비하니까 뭔가 나도 할 수 있지 않을까 하는 생각에 마음이 느슨해지는 것 같다.</p>
<p>이번주 목요일부터는 다시 프로젝트 기간인데 그 전에는 컴퓨터 비전 쪽 노트는 다 정리해야지...</p>
<h1 id="0-학습목표">0. 학습목표</h1>
<ul>
<li><strong>Segmentation</strong>의 동작 방식 및 Semantic Segmentation/Instance Segmentation 을 구분하여 설명할 수 있다.</li>
<li><strong>Transpose Convolution</strong>의 필요성과 동작 방식에 대해 설명할 수 있다.</li>
<li>기존 모델을 사용하여 <strong>U-net</strong> 모델을 만든 코드를 이해하고 참고하여 다시 작성할 수 있다.</li>
<li>Object Detection 의 2가지 방식과 지표에 대해 설명할 수 있다.</li>
<li>Objection Detection 모델을 직접 구현한 코드를 보고 이해할 수 있다.</li>
<li>U-net 을 직접 구현한 코드를 보고 이해할 수 있다.</li>
<li>여러 Object Detection 모델에 대해 알아보고 어떤 방식에 해당되는지 구분할 수 있으며 특정 모델의 적절한 예제를 선택하여 다른 데이터셋에 적용해 볼 수 있다.<h1 id="1-segmentation분할">1. Segmentation(분할)</h1>
<img src="https://velog.velcdn.com/images/sea_panda/post/472fc89a-2c1e-4956-8b73-681d5b0f7cc7/image.png" alt=""></li>
</ul>
<p><span style="color:orange">분할(Segmentation)</span>은 위 이미지와 같이 하나의 이미지에서 <span style="color:green">같은 의미</span>를 가지고 있는 부분을 구분해내는 Task이다.
<img src="https://velog.velcdn.com/images/sea_panda/post/7b248e5c-580c-4dd8-824c-f12e04dd6f05/image.png" alt=""></p>
<p>이미지 분류에서는 이미지를 하나의 단위로 레이블을 예측하였다면 <strong>Segmentation</strong>은 더 낮은 단위로 분류한다. 위의 이미지에서 볼 수 있듯이 <strong>동일한 의미</strong>마다 해당되는 <strong>픽셀</strong>이 모두 레이블링 되어있는 데이터셋을 <span style="color:orange">픽셀 단위에서 레이블을 예측</span>하게 된다.</p>
<p>이런 Segmentation에서 같은 의미를 가지는 개체들을 동일하게 라벨링을 하는지, 아니면 각 개체마다 다르게 라벨링을 하는지에 따라서 2가지로 나눌 수 있다. 바로 <strong>Semantic Segmentation</strong>과 <strong>Instance Segmentation</strong>이다.</p>
<h2 id="semantic-segmentation--instance-segmentation">Semantic Segmentation &amp; Instance Segmentation</h2>
<p><img src="https://velog.velcdn.com/images/sea_panda/post/97d44d08-48cf-4490-a93c-e093b234a58e/image.png" alt=""></p>
<p><strong>Semantic Segmentation</strong>에서 각 픽셀은 위의 그림에서 보는 것처럼 픽셀이 속한 객체의 클래스로 분류된다. 그리고 클래스가 같은 물체는 따로 구별하지 않는다. 예를 들자면 차종이 다 다르더라도 모든 자동차는 <strong>&quot;자동차&quot;</strong>라는 클래스로 분류되는 것이다.</p>
<p>이 픽셀을 분류하는 작업에 있어서 어려운 점은 이미지가 일반적인 CNN을 통과할 때 점진적으로 위치 정보를 잃는다는 것이다. 따라서 보통의 CNN은 이미지 왼쪽 아래 어딘가에 사람이 있다고 알 수 있지만 그보다 더 정확히 알지 못한다.</p>
<p>이 문제를 해결하기 위한 다양한 접근 방법이 있고 어떤 솔루션은 매우 복잡하다. 하지만 조너선 롱 등이 2015년 발표한 논문에서 매우 단순한 해결책을 제시하였다. 바로 <span style="color:orange"><strong>Fully Convolutional Networks(FCN)</strong></span>이다. 이는 따로 아래에서 더욱 자세히 다루도록 하겠다.</p>
<p><img src="https://velog.velcdn.com/images/sea_panda/post/e7cd7e84-1dce-4592-884b-d30e2540e4ab/image.png" alt=""></p>
<p><span style="color:orange"><strong>Instance Segmentation</strong></span>은 Semantic Segmentation과 비슷하지만 동일한 클래스 물체를 하나의 덩어리로 합치는 것이 아닌 각 물체를 구분하여 표시한다.</p>
<p>현재 텐서플로 모델 프로젝트에 포함된 인스턴스 분할 모델은 2017년 한 논문에서 제안된 <strong>Mask R-CNN</strong>이다. 이 모델은 <strong>Faster R-CNN</strong>모델을 확장하여 각 바운딩 박스에 대해 픽셀 마스크를 추가로 생성했다. 따라서 물체마다 클래스 추정 확률과 바운딩 박스를 얻는 것뿐만 아니라 바운딩 박스 안에 들어 있는 물체의 픽셀을 구분하는 픽셀 마스크도 얻을 수 있다.</p>
<h1 id="2-fully-convolutional-networksfcn">2. Fully Convolutional Networks(FCN)</h1>
<p><strong>Fully Convolutional Networks(FCN)</strong>은 2015년 조너선 롱 등이 작성한 논문에서 처음 등장한 모델이다. </p>
<p>이 모델은 이미지 분류를 위한 신경망에 사용되었던 CNN의 분류기 부분, 즉 <strong>완전 연결 신경망(Fully Connected Layer)부분을 합성곱 층(Convolution Layer)로 대체</strong>한 모델이다. </p>
<p>앞서 말했듯이 Segmentation 은 픽셀 단위로 분류가 이루어지기 때문에 픽셀의 위치 정보를 끝까지 보존해주어야 한다. 하지만 CNN은 합성곱 층(Convolution Layer)을 통과할 때마다 이런 위치 정보를 잃게 된다. 이를 해결 하기 위해서 완전 연결 신경망(Fully Connected Layer)부분을 합성곱 층(Convolution Layer)로 대체한 것이다.
<img src="https://velog.velcdn.com/images/sea_panda/post/f6cbecf3-946f-434e-acad-fd7ac2ba1e6b/image.png" alt=""></p>
<p>위 그림은 FCN의 구조를 도식화한 그림이다. 그림을 보면 이미지의 크기가 커지는 부분이 존재한다. 이는 Segmantation은 픽셀렬로 분류를 진행하기 때문에 마지막 층이 입력 이미지보다 작은 특성 맵을 출력한다. 이는 정보의 손실을 의미하기 때문에 원래 이미지와 비슷하게 크기를 키워주는(해상도를 늘리는) <strong>Upsampling</strong>을 해주어야 한다. 그리고 이런 Upsampling을 진행해주는 층을 <strong>Upsampling Layer</strong>라고 한다.</p>
<h2 id="upsampling">Upsampling</h2>
<p>CNN에서 사용되는 것처럼 Convolution과 Pooling을 사용하여 이미지의 특징을 추출하는 과정을 <strong>Downsampling</strong>이라고 한다. </p>
<p>이와 반대로 원래 이미지 크기로 키우는 과정을 <span style="color:orange"><strong>Upsampling(업샘플링)</strong></span>이라고 한다.</p>
<p>Upsampling에는 기존 Convolution과는 다른 <span style="color:orange"><strong>Transpose Convolution</strong></span>이 적용된다. <strong>Transpose Convolution</strong>에서는 각 픽셀에 커널을 곱한 값에 Stride를 주어 나타냄으로써 이미지 크기를 키워나간다.
<img src="https://velog.velcdn.com/images/sea_panda/post/594e8cc8-5a2d-4bd8-bc3d-c7551f80908f/image.gif" alt=""></p>
<p>위는 2X2이미지가 입력되었을 때 3X3필터에 의하여 <strong>Transpose Convolution</strong>되는 과정을 나타낸 것이다.</p>
<blockquote>
<p>❗ <strong>FCN 논문리뷰</strong>
<a href="https://medium.com/@msmapark2/fcn-%EB%85%BC%EB%AC%B8-%EB%A6%AC%EB%B7%B0-fully-convolutional-networks-for-semantic-segmentation-81f016d76204">https://medium.com/@msmapark2/fcn-%EB%85%BC%EB%AC%B8-%EB%A6%AC%EB%B7%B0-fully-convolutional-networks-for-semantic-segmentation-81f016d76204</a></p>
</blockquote>
<h1 id="3-u-net">3. U-Net</h1>
<p><img src="https://velog.velcdn.com/images/sea_panda/post/ea0b685b-ed86-40db-836e-8a92ef1c5ba6/image.png" alt=""></p>
<p><span style="color:orange"><strong>U-Net</strong></span>은 Biomedical분야에서 이미지 분할(Image <strong>Segmentation</strong>)을 목적으로 제안된 <strong>End-to-End</strong>방식의 <strong>FCN(Fully-Convolutional Network)</strong>기반 모델이다. 위의 이미지에서 볼 수 있듯이 네트워크의 구성 형태가 <strong>&quot;U&quot;</strong>자형 이라서 U-Net이라는 이름이 붙여졌다.</p>
<p><img src="https://velog.velcdn.com/images/sea_panda/post/9910a721-6329-495a-bfff-eb8b92e19f9d/image.png" alt=""></p>
<p><img src="https://velog.velcdn.com/images/sea_panda/post/155249dd-8505-4417-9eb2-e7c5c6238cac/image.png" alt=""></p>
<p>U-Net은 이미지의 <strong>전반적인 컨텍스트 정보를 얻기 위한 네트워크</strong>와 <strong>정확한 지역화(Localization)</strong>을 위한 네트워크가 대칭 형태로 구성되어 있다.</p>
<p>전반적인 컨텍스트 정보를 얻기 위한 네트워크는 <strong>Downsampling</strong>을 진행한다. 그리고 정확한 지역화를 위한 네트워크인 <strong>Expanding Path</strong>의 경우 <strong>Upsampling</strong>을 진행한다. Upsampling에서는 Convolution 과 <strong>Transpose Convolution</strong>을 거치면서 <strong>원본 이미지와 비슷한 크기로 복원</strong>한다.</p>
<p>다시 말하자면, Coarse Map(=Feature Map)에서 <strong>Dense Prediction</strong>을 얻기 위한 구조인 것이다. U-Net은 FCN(Fully convolutional network)을 토대로 확장한 개념이기 때문에 명확한 이해를 돕기 위해 우선적으로 FCN을 이해하는 것이 좋다.</p>
<p>또한 Coarse Map to Dense Map 개념 뿐만 아니라 U-Net은 FCN의 <strong>Skip Architecture</strong> 개념도 활용하여 얕은 층의 특징맵을 깊은 층의 특징맵과 결합하는 방식을 제안하였다. 위의 <strong>U-Net</strong>이미지의 사진을 보면 <strong>Copy and Crop</strong>이라고 색인되어 있는 회색 선을 볼 수 있다. 바로 이 부분이 Skip Architecture이다.</p>
<p>이러한 CNN 네트워크의 특성 계층의 결합을 통해 Segmentation이 내제하는 Localization과 Context(Semantic Information) 사이의 트레이드 오프를 해결할 수 있다.</p>
<p>이제 Contracting Path(Downsampling)과 Expanding Path에 대해서 더 자세히 알아보자.</p>
<hr>
<h2 id="contracting-path">Contracting Path</h2>
<p><img src="https://velog.velcdn.com/images/sea_panda/post/308c8822-a626-42a9-940d-65006da33805/image.png" alt=""></p>
<ul>
<li>3X3 Convolutions을 두 차례씩 반복(패딩 없음)</li>
<li>활성화 함수는 ReLU 이용</li>
<li>2X2 max-pooling, 이때 <code>stride</code>는 <code>2</code></li>
<li>Down-sampling 마다 채널의 수를 2배로 늘림</li>
</ul>
<h2 id="expanding-path">Expanding Path</h2>
<p><img src="https://velog.velcdn.com/images/sea_panda/post/320edef6-5553-447a-8bbe-945488f32b63/image.png" alt="">
<img src="https://velog.velcdn.com/images/sea_panda/post/4579d88f-6d28-4a80-a10f-9920b179cf00/image.png" alt=""></p>
<p>Expanding Path는 Contracting Path와 반대의 연산으로 특징맵을 확장한다.</p>
<ul>
<li>2x2 convolution (“up-convolution”)</li>
<li>3x3 convolutions을 두 차례씩 반복 (패딩 없음)</li>
<li>Up-Conv를 통한 Up-sampling 마다 채널의 수를 반으로 줄임</li>
<li>활성화 함수는 ReLU</li>
<li>Up-Conv 된 특징맵은 Contracting path의 테두리가 Cropped된 특징맵과 concatenation 함</li>
<li>마지막 레이어에 1x1 convolution 연산</li>
</ul>
<hr>
<p>위와 같은 구성으로 총 23-Layers Fully Convolutional Networks구조이다. 주목해야하는 점은 최종출력인 Segmentation Map의 크기는 Input Image크기보다 작다는 것이다. Convolution연산에서 패딩을 사용하지 않았기 때문이다.</p>
<p>U-Net 정리에 있어서 이미지와 많은 글들을 아래 참고자료의 3번 블로그를 참조했다. 더 자세한 사항은 참고자료에서 링크를 통해 볼 수 있다.</p>
<blockquote>
<p>💡 <strong>용어설명</strong></p>
</blockquote>
<ul>
<li><strong>End-to-End</strong>
종단간 기계학습이라고도 불리며, 입력에서 출력까지 &quot;파이프라인 네트워크&quot;없이 신경망으로 한 번에 처리하는 방식을 의미한다.
<img src="https://velog.velcdn.com/images/sea_panda/post/507d3a93-57e7-4005-a36b-79a1172e0a73/image.png" alt=""><blockquote>
<br></blockquote>
</li>
<li><strong>Dense Prediction</strong>
Semantic Segmentation과 같은 의미로, 이미지의 각 픽셀이 어느 클래스에 속하는에 대해서 이미지 내의 모든 픽셀에 대해 예측을 진행하기 때문에 Segmentation을 Dense Prediction이라고 부르기도 한다.<blockquote>
</blockquote>
</li>
<li><strong>Localization과 Context(Semantic Information) 사이의 트레이드 오프를 해결할 수 있다?</strong>
일반적으로 이미지를 볼 때 패치를 슬라이딩 하면서 보게 된다. 이런 방식의 단점 중 하나가 바로 Localization 정확도와 Context정보간에 Trade-off가 발생한다는 것이다. 큰 패치를 사용하면 더 큰 Max-Pooling layer를 요구하는데 이는 localization accuracy를 감소시키게 되고, 그렇다고 패치를 작게하면 Context를 거의 활용하지 못하게 되는 것이다.<br><blockquote>
<p>쉽게 말하자면 작은 창으로 작은 부분까지 보면 그 픽셀의 위치정보를 정확히 알 수는 있지만, 그 옆에 뭐가있는지 큰 맥락은 알지 못하게 되는 것이다. 크게 보면 대충 이런 것들이 여기 있는지는 알지만 정확이 그 큰 창 안 어디에 있는지 알지 못하게 된다고 할 수 있다.</p>
</blockquote>
</li>
</ul>
<h2 id="u-net-예제-1수정된-u-net">U-Net 예제 1.(수정된 U-Net)</h2>
<p>TensorFlow의 공식문서를 참조하여 작성된 예제이다. 따라서 더 상세한 설명은 공식문서를 참고하면 볼 수 있다.</p>
<pre><code class="language-python">!pip install git+https://github.com/tensorflow/examples.git</code></pre>
<pre><code class="language-python">import tensorflow as tf

import tensorflow_datasets as tfds</code></pre>
<p><code>pip</code>으로 설치해주는 것은 <code>pix2pix</code>라는 예제에서 구현된 업샘플 블록을 사용하기 위하여 설치해주는 것이다. 그 후 우선 텐서플로우와 데이터를 <code>import</code>해준다.</p>
<p>다음으로는 <code>pix2pix</code>와 시각화에 필요한 라이브러리를 <code>import</code>한다.</p>
<pre><code class="language-python">from tensorflow_examples.models.pix2pix import pix2pix

from IPython.display import clear_output
import matplotlib.pyplot as plt</code></pre>
<p>이제 <code>tfds</code>를 통해서 Oxford-IIIT Pets데이터를 다운로드 하여 준다. 세분화 마스크는 버전 3+에 포함되어 있기 때문에 버전 3이상의 데이터를 다운로드한다.</p>
<p>이 데이터세트는 37개의 애완동물 품종의 이미지로 구성되어 있으며 품종당 200개의 이미지가 있다(훈련 및 테스트 분할에 각각 ~100개). 각 이미지에는 해당 레이블과 픽셀 단위 마스크가 포함된다. 여기서 마스크는 각 픽셀에 대한 클래스 레이블을 의미한다. 그리고 각 픽셀에는 세 가지 범주 중 하나가 지정된다.</p>
<ul>
<li>클래스 1: 애완 동물에 속하는 픽셀</li>
<li>클래스 2: 애완동물과 접하는 픽셀</li>
<li>클래스 3: 위에 속하지 않음/주변 픽셀</li>
</ul>
<p>이미지 색상 값은 [0,1] 범위로 정규화된다. 편의를 위해 세분화 마스크에서 1을 빼면 {0, 1, 2}와 같은 레이블이 생성된다.</p>
<pre><code class="language-python">dataset, info = tfds.load(&#39;oxford_iiit_pet:3.*.*&#39;, with_info=True)</code></pre>
<pre><code class="language-python">def normalize(input_image, input_mask):
  input_image = tf.cast(input_image, tf.float32) / 255.0
  input_mask -= 1
  return input_image, input_mask</code></pre>
<pre><code class="language-python">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))

  input_image, input_mask = normalize(input_image, input_mask)

  return input_image, input_mask</code></pre>
<p>데이터세트에는 이미 필요한 훈련 및 테스트 분할이 포함되어 있으므로 동일한 분할을 계속 사용하면 된다.</p>
<pre><code class="language-python">TRAIN_LENGTH = info.splits[&#39;train&#39;].num_examples
BATCH_SIZE = 64
BUFFER_SIZE = 1000
STEPS_PER_EPOCH = TRAIN_LENGTH // BATCH_SIZE</code></pre>
<pre><code class="language-python">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)</code></pre>
<p>그 후 이미지를 무작위로 뒤집어 간단한 <a href="https://velog.io/@sea_panda/N431-TIL#4-%EC%9D%B4%EB%AF%B8%EC%A7%80-%EC%A6%9D%EA%B0%95">이미지 증강</a>을 수행한다.</p>
<pre><code class="language-python">class Augment(tf.keras.layers.Layer):
  def __init__(self, seed=42):
    super().__init__()
    # both use the same seed, so they&#39;ll make the same random changes.
    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</code></pre>
<p>입력을 일괄 처리한 후에 증강을 적용하여 입력 <a href="https://velog.io/@sea_panda/N212-TIL-%EB%B0%8F-%ED%9A%8C%EA%B3%A0#3-library-sklearnpipeline">파이프라인</a>을 빌드한다. 이때 테스트 이미지에는 증강을 적용하지 않는다.</p>
<pre><code class="language-python">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)</code></pre>
<p>이미지 예제와 해당 이미지의 실제 마스크를 시각화 한다.</p>
<pre><code class="language-python">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()</code></pre>
<pre><code class="language-python">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/sea_panda/post/1097f3b5-0600-45db-8ae2-951c658177d0/image.png" alt=""></p>
<p>이제 모델을 정의하여 준다. U-Net은 인코더(다운 샘플러)와 디코더(업샘플러)로 구성된다.</p>
<p>모델을 구성함에 있어서 사전학습 모델은 <code>MobileNetV2</code>를 인코더로 사용한다. 디코더의 경우에는 앞서 언급한 TensorFlow 예제 레포지토리의 <code>Pix2Pix</code>예제에서 이미 구현된 업샘플 블록을 사용한다. </p>
<p>인코더는 모델 중간 레이어에서 얻어지는 특정 출력으로 구성되며, 학습 과정에서 훈련하지 않고 사전 훈련된 가중치를 그대로 사용한다.</p>
<pre><code class="language-python">base_model = tf.keras.applications.MobileNetV2(input_shape=[128, 128, 3], include_top=False)

# Use the activations of these layers
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]

# Create the feature extraction model
down_stack = tf.keras.Model(inputs=base_model.input, outputs=base_model_outputs)

down_stack.trainable = False</code></pre>
<p>다음은 디코더(다운샘플러)의 코드이다.</p>
<pre><code class="language-python">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
]</code></pre>
<p>이제 앞에서 정의한 인코더와 디코더를 이용하여 U-Net모델을 생성한다. 이때 마지막 레이어의 필터 수는 <code>Output_channels</code>수로 설정된다. 이것은 클래스당 하나의 출력 채널이 된다.</p>
<pre><code class="language-python">def unet_model(output_channels:int):
  inputs = tf.keras.layers.Input(shape=[128, 128, 3])

  # Downsampling through the model
  skips = down_stack(inputs)
  x = skips[-1]
  skips = reversed(skips[:-1])

  # Upsampling and establishing the skip connections
  for up, skip in zip(up_stack, skips):
    x = up(x)
    concat = tf.keras.layers.Concatenate()
    x = concat([x, skip])

  # This is the last layer of the model
  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)</code></pre>
<p>이제 모델을 컴파일하고 훈련한다. 이때 이번 예제는 다중 클래스 분류 문제이기 때문에 <code>from_logits</code>인수가 <code>True</code>로 설정된 <code>tf.keras.losses.CategoricalCrossentropy</code>손실 함수를 사용한다. 레이블은 모든 클래스의 각 픽셀에 대한 점수 벡터가 아닌 정수 스칼라이기 때문이다.</p>
<pre><code class="language-python">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;])</code></pre>
<p><code>tf.keras.utils.plot_model</code>을 사용하면 모델 아키텍처를 볼 수 있다.</p>
<pre><code class="language-python">tf.keras.utils.plot_model(model, show_shapes=True)</code></pre>
<p><img src="https://velog.velcdn.com/images/sea_panda/post/0aa4a25d-4740-49fe-8b01-a01b1bebbf09/image.png" alt=""></p>
<p>다음으로 각 픽셀에 값이 가장 높은 채널을 할당하는 <code>Create_mask</code>함수를 생성한다.</p>
<pre><code class="language-python">def create_mask(pred_mask):
  pred_mask = tf.math.argmax(pred_mask, axis=-1)
  pred_mask = pred_mask[..., tf.newaxis]
  return pred_mask[0]</code></pre>
<p>모델을 훈련하기 전에 기본적인 모델이 어떻게 예측하는지 확인해보기 위해서 다음 코드를 실행해 볼 수 있다.</p>
<pre><code class="language-python">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, ...]))])</code></pre>
<pre><code class="language-python">show_predictions()</code></pre>
<p><img src="https://velog.velcdn.com/images/sea_panda/post/f4b6dc95-6ca6-481c-b4a3-98011363a1c3/image.png" alt=""></p>
<p>다음으로 모델이 훈련되는 동안 어떻게 개선되는지 관찰하기 위하여 콜백함수를 선언하여 준다.</p>
<pre><code class="language-python">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))</code></pre>
<pre><code class="language-python">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/sea_panda/post/1ee26e1f-8417-4fea-b5c1-5e3449d9bbc9/image.png" alt=""></p>
<p>실제로 코드를 구동시켜보면 각 에포크에 따라서 점점 Predicted Mask가 변하는 것을 확인할 수 있다.</p>
<p>다음으로 Train세트의 Loss와 Validation세트의 Loss를 확인하는 그래프를 출력하는 함수를 생성하여 준다.</p>
<pre><code class="language-python">loss = model_history.history[&#39;loss&#39;]
val_loss = model_history.history[&#39;val_loss&#39;]

plt.figure()
plt.plot(model_history.epoch, loss, &#39;r&#39;, label=&#39;Training loss&#39;)
plt.plot(model_history.epoch, val_loss, &#39;bo&#39;, label=&#39;Validation loss&#39;)
plt.title(&#39;Training and Validation Loss&#39;)
plt.xlabel(&#39;Epoch&#39;)
plt.ylabel(&#39;Loss Value&#39;)
plt.ylim([0, 1])
plt.legend()
plt.show()</code></pre>
<p><img src="https://velog.velcdn.com/images/sea_panda/post/12e51ad3-2f2b-46f1-9181-4a57fee3f0e4/image.png" alt=""></p>
<p>이제 훈련이 끝났기 때문에 다음과 같이 예측을 진행할 수 있다.</p>
<pre><code class="language-python">show_predictions(test_batches, 3)</code></pre>
<p><img src="https://velog.velcdn.com/images/sea_panda/post/f9d3852a-ce24-4d2d-81d8-f2d7ffdedc9b/image.png" alt=""></p>
<hr>
<p>위에 작성한 코드는 살짝 수정된 U-Net으로 실제 U-Net의 구조와는 약간의 차이가 존재한다.
<img src="https://velog.velcdn.com/images/sea_panda/post/f17adb68-b2cd-4dfb-ba2e-c67109a2fb27/image.png" alt=""></p>
<p>좌측은 위의 코드로 만든 모델이고, 우측이 실제 U-Net의 구조이다. 따라서 실제 U-Net과 같은 구조로 만들기 위해서는 다음과 같은 코드로 U-Net을 구축할 수 있다.</p>
<p>먼저 <code>Conv2D-ReLU-Conv2D-ReLU</code>로 구성된 층을 만드는 함수를 생성하여 준다. 이 함수는 인코더와 <code>bottleneck</code>에 사용된다.</p>
<pre><code class="language-python">def double_conv_block(x, n_filters):

   # Conv2D then ReLU activation
   x = layers.Conv2D(n_filters, 3, padding = &quot;same&quot;, activation = &quot;relu&quot;,
   kernel_initializer = &quot;he_normal&quot;)(x)
   # Conv2D then ReLU activation
   x = layers.Conv2D(n_filters, 3, padding = &quot;same&quot;, activation = &quot;relu&quot;,
   kernel_initializer = &quot;he_normal&quot;)(x)

   return x</code></pre>
<p>다음으로는 특징 추출을 위한 다운샘플러를 만들어 주는 함수를 생성한다.</p>
<pre><code class="language-python">def downsample_block(x, n_filters):
   f = double_conv_block(x, n_filters)
   p = layers.MaxPool2D(2)(f)
   p = layers.Dropout(0.3)(p)

   return f, p</code></pre>
<p>마지막으로 디코더에 사용되는 업샘플링 함수를 생성하여 준다.</p>
<pre><code class="language-python">def upsample_block(x, conv_features, n_filters):
   # upsample
   x = layers.Conv2DTranspose(n_filters, 3, 2, padding=&quot;same&quot;)(x)
   # concatenate
   x = layers.concatenate([x, conv_features])
   # dropout
   x = layers.Dropout(0.3)(x)
   # Conv2D twice with ReLU activation
   x = double_conv_block(x, n_filters)

   return x</code></pre>
<p>이제 위에서 만든 함수들을 이용하여 U-Net의 구조를 생성하여 준다.</p>
<pre><code class="language-python">def bulid_unet_model():
 # inputs
   inputs = layers.Input(shape=(128,128,3))

   # encoder: contracting path - downsample
   # 1 - downsample
   f1, p1 = downsample_block(inputs, 64)
   # 2 - downsample
   f2, p2 = downsample_block(p1, 128)
   # 3 - downsample
   f3, p3 = downsample_block(p2, 256)
   # 4 - downsample
   f4, p4 = downsample_block(p3, 512)

   # 5 - bottleneck
   bottleneck = double_conv_block(p4, 1024)

   # decoder: expanding path - upsample
   # 6 - upsample
   u6 = upsample_block(bottleneck, f4, 512)
   # 7 - upsample
   u7 = upsample_block(u6, f3, 256)
   # 8 - upsample
   u8 = upsample_block(u7, f2, 128)
   # 9 - upsample
   u9 = upsample_block(u8, f1, 64)

   # outputs
   outputs = layers.Conv2D(3, 1, padding=&quot;same&quot;, activation = &quot;softmax&quot;)(u9)

   # unet model with Keras Functional API
   unet_model = tf.keras.Model(inputs, outputs, name=&quot;U-Net&quot;)

   return unet_model</code></pre>
<p>위와 같이 작성하면 실제 U-Net모델을 구현할 수 있다. 이때 자세한 사항은 코드를 긁어온 사이트를 참고하자.</p>
<blockquote>
<p>💡 <strong>코드사이트 주소</strong>
<a href="https://pyimagesearch.com/2022/02/21/u-net-image-segmentation-in-keras/">https://pyimagesearch.com/2022/02/21/u-net-image-segmentation-in-keras/</a></p>
</blockquote>
<h1 id="4-obejct-detection객체-탐지인식">4. Obejct Detection(객체 탐지/인식)</h1>
<p><img src="https://velog.velcdn.com/images/sea_panda/post/e92746ab-c2d3-40c0-b9aa-85d788f2469b/image.png" alt=""></p>
<p>하나의 이미지에서 여러 물체를 분류하고 위치를 추정하는 작업을 <span style="color:green"><strong>Object Detection(객체 탐지)</strong></span>라고 한다. </p>
<p>과거에 널리 사용되던 방식은 하나의 물체를 분류하고 위치를 찾는 분류기를 훈련한 다음에 이미지 전체를 훑는 방식을 주로 사용했다. 이 방식은 매구 간단하지만 조금씩 다른 위치에서 동일한 물체를 여러번 감지하기 때문에 불필요한 바운딩 박스를 제거하기 위한 사후 처리가 필요하다. 흔히 사용하는 방법으로는 <strong>NMS<sup>non-max suppression</sup></strong>이 있다. </p>
<p>이런 간단한 객체 탐지 방식은 상당히 잘 동작하지만 CNN을 여러 번 실행시켜야 해서 많이 느리다는 단점이 있다. 다행히 앞에서 다룬 <strong>FCN(완전 합성곱 신경망)</strong>을 사용하면 CNN을 훨씬 빠르게 이미지에 슬라이딩 시킬 수 있다.</p>
<p>대표적인 객체 탐지 모델은 아래와 같이 발전하여 왔다.
<img src="https://velog.velcdn.com/images/sea_panda/post/26d58893-ace1-400c-b29a-01a65ed94e9e/image.png" alt=""></p>
<p>객체 탐지 모델들은 어떤 단계를 거쳐 분류가 진행되는지에 따라서 <strong>Two Stage</strong>방식과 <strong>One Stage</strong>방식으로 나눌 수 있다.</p>
<hr>
<h2 id="one-stage-dector">One Stage Dector</h2>
<p><span style="color:orange"><strong>One Stage Detector</strong></span>는 특정 지역을 추천받지 않고 입력 이미지를 Gride등의 같은 작은 공간으로 나눈 뒤에 해당 공간을 탐색하며 분류를 수행하는 방식이다. 즉, Regional Proposal과 Classification이 동시에 이루어지는 것이다.</p>
<p>그림으로는 아래와 같이 나타낼 수 있다.
<img src="https://velog.velcdn.com/images/sea_panda/post/08c027ac-ef0c-454d-a251-2d9c1136209d/image.png" alt=""></p>
<p>지역 추천을 먼저 받지 않고 동시에 이루어지기 때문에 Two stage방식보다는 빠르다는 장점이 있다. 하지만 빠른만큼 정확도가 낮다는 단점이 존재한다.</p>
<p>대표적인 모델로는 <code>SSD(Single Shot multibox Detector)</code>계열과 최근 자율주행 등에서 각광을 받고있는 <code>YOLO(You Only Look Once)</code>계열의 모델이 있다.</p>
<h2 id="two-stage-dector">Two Stage Dector</h2>
<p><span style="color:orange"><strong>Two Stage Detector</strong></span>는 일련의 알고리즘을 통해서 객체가 있을 만함 곳을 추천받은(Regional Proposal) 뒤에 추천받은 Region, 즉 <strong>RoI(Region of Interest)</strong>에 대해 분류를 수행하는 방식이다.</p>
<p>앞에서도 언급하였지만 Regional Proposal 이란 기존에 이미지를 탐색하는 방식의 비효율성을 개선하기 위하여 등장한 것이다. 기존에는 이미지에서 object detection을 위해 sliding window방식을 이용했었다. Sliding window 방식은 이미지에서 모든 영역을 다양한 크기의 window (differenct scale &amp; ratio)로 탐색하는 것이다. 말 그대로 모든 영역을 탐색하기 때문에 물체가 없는 부분까지 탐색하여 비효율적이다. </p>
<p>이런 비효율성을 개선하기 위해서 &quot;물체가 있을만한&quot;영역을 빠르게 찾아내는 알고리즘들을 <strong>Region proposal</strong>이라고 하며, 대표적으로 <code>Selective search</code>, <code>Edge boxes</code>들이 있다.</p>
<p>Two Stage Dector를 그림으로 나타내면 다음과 같이 나타낼 수 있다.
<img src="https://velog.velcdn.com/images/sea_panda/post/52e0a176-164d-49a5-a9eb-56b517593146/image.png" alt=""></p>
<p>대표적인 Two stage모델로는 <code>R-CNN</code>계열(<code>R-CNN</code>,<code>Fast R-CNN</code>, <code>Faster R-CNN</code> 등)의 모델이 있다.</p>
<h2 id="객체-탐지-성능지표iou-map">객체 탐지 성능지표(IoU, mAP)</h2>
<p>객체 탐지의 결과는 우리가 지금까지 사용한 지표와는 다른 지표를 사용한다. <strong>IoU(Intersection over Union)</strong>과 <strong>mAP(mean Average precision)</strong>이 그 예이다.</p>
<h3 id="iou">IoU</h3>
<p><img src="https://velog.velcdn.com/images/sea_panda/post/c923607d-34c8-4c06-9c3a-e7b043e93209/image.png" alt=""></p>
<p>위 그림의 초록색 박스처럼 정답에 해당하는 Bounding Box를 <span style="color:green"><strong>Ground-truth</strong></span>라고 한다. 모델이 빨간색 박스처럼 예측했을 때 <strong>IoU</strong>는 다음과 같은 식을 사용하여 구할 수 있다.
<img src="https://velog.velcdn.com/images/sea_panda/post/1110b9e4-075b-4ea3-b615-99e09008bbff/image.png" alt=""></p>
<p>IoU 를 사용하면 객체가 포함되어 있지만 너무 큰 범위를 잡는 문제를 해결할 수 있다. 아래 그림은 Ground-truth/Prediction에 해당하는 Bounding Box 에 따라 IoU가 구해지는 예시를 나타내고 있다.
<img src="https://velog.velcdn.com/images/sea_panda/post/d835d975-5c04-4de9-9efe-c8d029bff8e3/image.png" alt=""></p>
<h3 id="map">mAP</h3>
<p><img src="https://velog.velcdn.com/images/sea_panda/post/80646a41-6892-467d-bbdd-d1264e43e30f/image.png" alt=""></p>
<p>객체 탐지에서 널리사용되는 성능지표이다. <strong>&quot;Mean Average&quot;</strong>라는 표현은 의미가 중복된 것처럼 보인다.</p>
<p>이 지표를 이해하기 위해서는 <a href="https://velog.io/@sea_panda/N223-TIL-%EB%B0%8F-%ED%9A%8C%EA%B3%A0#2-1-%EC%A0%95%EB%B0%80%EB%8F%84precision">정밀도와 재현율</a>을 이해하고 있어야 한다. 이 두 지표는 Trade-off관계에 있다. 따라서 이 두 값을 정밀도/재현율 곡선으로 그려볼 수 있다. 이 곡선을 하나의 숫자로 요약하려면 곡선의 아래 면적(<a href="https://velog.io/@sea_panda/N223-TIL-%EB%B0%8F-%ED%9A%8C%EA%B3%A0#3-roc-curve">AUC</a>)를 계산한다.</p>
<p>하지만 정밀도/재현율 곡선에서 재현율이 증가할 때 정밀도도 상승하는 영역이 포함될 수 있다. 특히 재현율 값이 낮을 때 그러하다. 이것이 바로 mAP지표가 만들어진 이유 중 하나이다.</p>
<p>한 분류기가 10%재현율에서 90% 정밀도를 달성하고 20% 재현율에서는 96%의 정밀도를 달성한다고 가정해보자. 여기서는 Trade-off가 없다. 재현율과 정밀도가 모두 상승하기 때문에 10%재현율보다는 20%재현율의 분류기를 사용하는 것이 당연하다. 따라서 10% 재현율에서 정밀도를 보는 것이 아니라 <strong>최소 10%</strong>재현율에서 분류기가 제공할 수 있는 최대 정밀도를 찾아야 한다. 이값은 90%가 아니라 96%이다. 따라서 공정한 모델의 성능을 측정하는 한 가지 방법은 최소 0% 재현율에서 얻을 수 있는 최대 정밀도, 그 다음 10%, 20%에서 100%까지 재현율에서의 최대 정밀도를 계산한다. 그 다음 이 최대 정밀도를 평균한다. 이를 <span style="color:green"><strong>평균 정밀도<sup>average precision</sup>(AP)</strong></span>라고 부른다. 만일 두 개 이상의 클래스가 있다면 각 클래스에 대해 AP를 계산단 다음 <strong>평균 AP</strong>를 계산한다. 이것이 바로 <span style="color:orange"><strong>mAP</strong></span>이다.</p>
<p>객체 탐지의 경우 조금 더 복잡해진다. 시스템에 정확한 클래스를 탐지했지만 위치가 잘못됐다면 이는 올바른 예측으로 볼 수 없다. 이를 위한 한 가지 방법은 IOU임계점을 정의하는 것이다. 예를 들어 IoU가 0.5보다 크고 예측 클래스가 맞다면 올바른 예측으로 간주한다. 이에 해당하는 mAP는 일반적으로 <code>mAP@0.5</code>라고 쓴다. </p>
<p><br><br><br></p>
<blockquote>
<p>❗ <strong>참고자료</strong></p>
</blockquote>
<ol>
<li>오렐리앙 제옹, 핸즈온 머신러닝(2판), 서울:한빛미디어,O⋅REILLY, 2020</li>
<li>CodeStates Lecture Note - N432</li>
<li><a href="https://medium.com/@msmapark2/u-net-%EB%85%BC%EB%AC%B8-%EB%A6%AC%EB%B7%B0-u-net-convolutional-networks-for-biomedical-image-segmentation-456d6901b28a">U-Net논문 리뷰</a></li>
<li><a href="https://bskyvision.com/entry/semantic-segmentation%EC%9D%98-%EC%9D%98%EB%AF%B8%EC%99%80-%EB%AA%A9%EC%A0%81">Semantic Segmentation</a></li>
<li><a href="https://jonathan-hui.medium.com/map-mean-average-precision-for-object-detection-45c121a31173">mAP</a></li>
</ol>
]]></description>
        </item>
        <item>
            <title><![CDATA[[N431] TIL 및 회고]]></title>
            <link>https://velog.io/@sea_panda/N431-TIL</link>
            <guid>https://velog.io/@sea_panda/N431-TIL</guid>
            <pubDate>Thu, 26 Jan 2023 10:58:22 GMT</pubDate>
            <description><![CDATA[<h1 id="0-학습목표">0. 학습목표</h1>
<h2 id="level-1">Level 1.</h2>
<ul>
<li><strong>CNN(Convolutional Neural Network)</strong>의 기본 구조에 대해 설명할 수 있다.</li>
<li>Convolution &amp; Pooling Layer 의 동작 방식과 조정할 수 있는 값(Stride, Padding 등)에 대해 설명할 수 있다.</li>
<li><strong>전이 학습(Transfer Learning)</strong>을 설명할 수 있으며 이미지 처리를 위한 대표적인 사전 학습 모델을 2개 이상 설명할 수 있다.</li>
<li>직접 CNN 모델을 구축하거나 사전 학습 모델을 사용하여 이미지 분류를 하는 코드를 작성할 수 있다.<h2 id="level-2">Level 2.</h2>
</li>
<li>CNN 층이 깊어졌을 때의 장점에 대해 설명할 수 있다.</li>
<li><strong>이미지 데이터 증강(Image Data Augmentation)</strong>의 개념에 대해 이해하고 실제 학습에 적용하는 코드를 작성할 수 있다.<h2 id="level-3">Level 3.</h2>
</li>
<li>최근 발표되고 있는 이미지 분류를 위한 사전 학습 모델에는 어떤 것이 있는지 나열하고 각 모델의 특징에 대해 설명할 수 있다.</li>
</ul>
<hr>
<h1 id="1-cnnconvolution-neural-network">1. CNN(Convolution Neural Network)</h1>
<p><span style="color:orange"><strong>합성곱 신경망<sup>Convolution Neural Network</sup>(CNN)</strong></span>은 대뇌의 시각피질 연구에서 시작되었고 1980년대부터 이미지 인식 분야에 사용되었다. 본격적으로 주목받은 시기는 2012년으로, 2012년 <strong><a href="https://www.image-net.org/index.php">이미지넷(ImageNet)</a></strong> 데이터셋 분류 경진대회인 <strong>ILSVRC</strong>에서 AlexNet이라는 알고리즘이 우승하였기 때문이다.</p>
<p>이미지는 위치에 맞는 <strong>공간적인 특성</strong>이 존재한다. 하지만 앞 서 N41~에서 배운 다층 퍼셉트론 신경망(MLP)은 모든 입력 값을 <code>Flatten</code>으로 <strong>펴준 뒤에 연산</strong>하기 때문에 이런 공간적인 특성을 살려내지 못한다. MNIST 데이터 처럼 간단한 이미지 데이터는 MLP로도 분류가 가능하지만
패턴이 복잡한 컬러 이미지를 이런 방식으로 분류하는 것은 쉽지 않다. </p>
<p>반면 <strong>CNN</strong>은 학습 과정에서 이런 공간적 특성을 보존하면서 학습할 수 있기 때문에 층이 깊어지더라도 공간적인 특성을 보존할 수 있다는 장점 때문에 이미지 분류에서 주목받기 시작하였다.</p>
<p>본격적인 CNN의 구조를 알아보기 전에 CNN이 시작된 근본인 시각 피질 구조에 대해 먼저 알아보겠다.</p>
<h2 id="시각-피질-구조">시각 피질 구조</h2>
<p><img src="https://velog.velcdn.com/images/sea_panda/post/68820ec3-f282-4168-9699-ea2a10b2cdc6/image.png" alt=""></p>
<p>시각 피질 안의 많은 뉴런이 작은 <span style="color:green"><strong>국부 수용장<sup>local receptive field</sup></strong></span>을 가진다는 것을 데이비드 허블과 토르스텐 비셀이 1958~1959년 사이에 진행한 고양이 실험과 원숭이 실험을 통해서 밝혀냈다. 다시 말하자면 뉴런들이 시야의 <strong>일부 범위</strong> 안에 있는 시각 자극에만 반응한다는 것이다. </p>
<p>뉴런의 수용장들은 서로 겹칠 수 있어서, 합치면 전체 시야를 감싸게 된다. 또한 두 뉴런이 동일한 수용장을 가진다 하더라도 어떤 뉴런은 수평선의 이미지에만 반응하고 반면 다른 뉴런은 다른 각도의 선분에 반응한다는 점을 보였다. 또한 어떤 뉴런은 큰 수용장을 가져서 <strong>저수준 패턴이 조합된</strong> 더 복잡한 패턴에 반응한다는 것을 밝혔다. 이를 통해서 <strong>고수준 뉴런</strong>이 <strong>이웃한 저수준 뉴런의 출력에 기반</strong>한다는 아이디어를 이끌어냈다. 즉, 각 뉴런은 이전 층에 있는 몇 개의(이웃한) 뉴런에만 연결된다는 것이다. 이러한 강력한 구조가 전체 시야 영역에 포함된 모든 종류의 복잡한 패턴을 감지할 수 있게 한다.</p>
<p>시각 피질에 대한 이런 연구를 통해 얻은 아이디어가 지금의 <strong>CNN</strong>으로 점진적으로 진화되었다. 그로다 1998년에 발표된 얀 르쿤 등의 논문 <strong><em>&quot;Gradien-Based Learning Applied to Document Recognition&quot;</em></strong>이 이미지 분류의 중요한 전환점이 되었다. 이 논문에서는 수표에 쓰인 손글씨 숫자를 인식하는데 널리 사용된 유명한 <strong>LeNet-5</strong> 구조를 소개했다. 이 구조에서 안 르쿤 교수는 위에서 말한  <strong>고수준 뉴런</strong>이 <strong>이웃한 저수준 뉴런의 출력에 기반</strong>한다는 아이디어를 통해서 획기적인 인공신경망을 고안해냈고, 이것이 바로 <span style="color:green"><strong>합성곱 신경망<sup>Convolution Neural Network</sup>(CNN)</strong></span>이다.</p>
<h2 id="cnn-구조">CNN 구조</h2>
<p><img src="https://velog.velcdn.com/images/sea_panda/post/102fb2ca-0ebb-4e4c-990f-4d234387aacb/image.png" alt=""></p>
<p>기본적인 CNN의 구조는 위의 그림과 같이 특징 추출 부분(Conv-Pooling)과 분류를 위한 신경망, 2단계로 나눌 수 있다. 먼저 특징이 추출되는 <span style="color:orange"><strong>합성곱 층(Covolution Layer)과 풀링 층(Pooling Layer)</strong></span>에 대해서 알아보자.</p>
<h3 id="합성곱-층supconvolution-layersup">합성곱 층<sup>convolution Layer</sup></h3>
<p><img src="https://velog.velcdn.com/images/sea_panda/post/a6f8b681-332c-4224-9578-9d4a6f10bfae/image.gif" alt=""></p>
<p>합성곱 층에서는 <span style="color:orange"><strong>합성곱 필터(Convolution Filter)</strong></span>가 **슬라이딩(Sliding)하며 이미지 부분부분의 특징을 읽어나간다. 한 번에 여러개가 보이니, 보기 어려울 수 있으니 정지돈 상황에서의 예시를 살펴보겠다.
<img src="https://velog.velcdn.com/images/sea_panda/post/3e962d49-ce40-4d1e-99ee-01945009b1a5/image.png" alt=""></p>
<p><code>Gif</code>를 통해서 같은 방식으로 9번의 연산이 이뤄지며, 9칸이 모두 채워지는 것을 확인할 수 있다. 그리고 바로 위의 그림을 통해서 각 Patch단위로 어떠한 방식으로 합성곱이 연산되는지를 확인할 수 있다.</p>
<p>위 두 그림을 잘 살펴보면 입력 이미지의 모든 픽셀에 한 번에 연결하는 것이 아니라 <strong>합성곱 층 뉴런의 수용장(=Filter)안에 있는 픽셀에만 연결</strong>되고, 이를 합성곱을 통해서 <strong>다시 전체 이미지의 Convoluted Feature를 구성</strong>하는 것을 볼 수 있다. 이런 구조는 네트워크가 이미지의 작은 <strong>저수준 특성에 집중</strong>하고, 그 후 <strong>더 큰 고수준 특성으로 조합</strong>해나가도록 도와준다. 이런 계층적 구조는 실제 이미지에서 흔히 볼 수 있으며, 이는 <strong>CNN</strong>이 이미지 인식에서 잘 작동하는 이유 중 하나이다.</p>
<p>뉴런의 수용장. 즉, <strong>Filter</strong>는  하나의 특성 맵을 만들고, 이 맵은 필터를 가장 크게 활성화시키는 이미지의 영역을 강조한다. 물론 수동으로 필터를 정의할 필요는 없다. 필터의 크기와 필터의 수만 정의하면 훈련하는 동안 합성곱 층이 자동으로 해당 문제에 가장 유용한 필터를 찾고 상위층은 이들을 연결하여 더 복잡한 패턴을 학습하게 된다.</p>
<p>다음으로는 합성곱 층에 적용할 수 있는 <span style="color:orange"><strong>패딩(Padding)과 스트라이드(Stride)</strong></span>에 대해서 알아보도록 하겠다.</p>
<hr>
<h3 id="패딩suppaddingsup">패딩<sup>Padding</sup></h3>
<p><img src="https://velog.velcdn.com/images/sea_panda/post/304f2d41-b962-4e56-b4ce-f66bd3b37dec/image.png" alt=""></p>
<p><strong>패딩</strong>은 이미지 외부를 특정한 값으로 둘러싸서 처리해주는 방식이다. 보통 <code>0</code>으로 둘러싸주는 <strong>Zero-Padding</strong>이 가장 많이 사용된다. Padding을 사용하는 이유는 연산되어 나오는 Output, 즉 Feature map의 크기를 조절하고 실제 이미지 값을 충분히 활용하기 위해서이다.</p>
<p>만약 Padding이 되어있지 않다면 가장 첫번째 칸에 들어있는 값은 1번만 사용된다. 마지막 칸도 마찬가지이다. 이는 담고있는 정보가 충분히 활용되지 못한 것이다. 하지만 Padding을 한 후에 합성곱을 진행하면 정보를 충분히 활용할 수 있게 된다.</p>
<p>Padding의 형식은 3가지가 있다. 하지만 사실상 <code>Vaild</code>의 경우에는 패딩을 진행하지 않는 것이다. <code>Same</code>의 경우에는 상하좌우에 행과 열을 1줄씩 추가하는 것이다. 이렇게 하면 입력값과 출력값의 크기가 동일하게 출력된다. <code>Full</code>의 경우에는 필터의 크기만큼 상하좌우에 행과 열을 추가하는 것이다. 이 경우 정보가 더 많이 활용되며, 입력보다 출력의 크기가 더 커지게 된다.</p>
<p><code>Keras</code>에서는 <code>Valid</code>또는 <code>same</code> 두가지 방식만 지원한다.</p>
<h3 id="스트라이드supstridesup">스트라이드<sup>Stride</sup></h3>
<p><img src="https://velog.velcdn.com/images/sea_panda/post/b8c77f1b-f1c6-472d-9ff6-34b671bc931a/image.png" alt=""></p>
<p><span style="color:orange"><strong>스트라이드(Stride)</strong></span>는 &quot;보폭&quot;이라는 뜻을 가진 단어이다. Stride를 조절하면 슬라이딩(Sliding)시에 몇 칸 씩 건너뛸지를 나타낸다. 위에서 본 <code>gif</code>파일은 스트라이드가 1인 경우이다.</p>
<p><code>Keras</code>에서는 튜플 형태로 이를 전달하며 기본 Default값은 <code>(1,1)</code>이다. 즉 가로로 1칸씩 이동하면서, 끝에 도달하면 그 다음 1줄 내려간 후에 다시 가로로 이동하게된다.</p>
<p>위의 그림의 경우로 말하자면 첫 번째는 <code>Stride=(1,1)</code>인 경우이고, 두 번째는 <code>Stride=(2,2)</code>라고 할 수 있다.</p>
<hr>
<p>위에서 다룬 Padding과 Stride 그리고 Filter Size에 따라서 Feature map의 크기가 달라진다. 여기서 Feature Map은 Convoluted Feature와 같은 의미로 합성곱에 의한 출력을 의미한다. 그리고 이 Feature map의 크기 공식은 다음과 같다.
$$$
N_{\text{out}} = \bigg[\frac{N_{\text{in}} + 2p - k}{s}\bigg] + 1
$$$</p>
<blockquote>
<ul>
<li>$N_{\text{in}}$: 입력되는 이미지의 크기(=피처 수)</li>
</ul>
</blockquote>
<ul>
<li>$N_{\text{out}}$: 출력되는 이미지의 크기(=피처 수)</li>
<li>$k$: 합성곱에 사용되는 커널(=필터)의 크기</li>
<li>$p$: 합성곱에 적용한 패딩 값</li>
<li>$s$: 합성곱에 적용한 스트라이드 값</li>
</ul>
<h2 id="풀링suppoolingsup">풀링<sup>Pooling</sup></h2>
<p>어떻게 합성곱 층이 작동하는지 이해했다면 <span style="color:orange"><strong>풀링 층<sup>Pooling layer</sup></strong></span>은 매우 쉽게 이해할 수 있다. 이 층의 목적은 <strong>계산량과 메모리 사용량, (결과적으로 과대적합의 위험을 줄여주는) 파라미터 수</strong>를 줄이기 위해 입력 이미지의 <span style="color:orange"><strong>부표본(Subsample)</strong></span>, 즉, 축소본을 만드는 것이다. </p>
<p>풀링 방법에는 <strong>최대 풀링(Max Pooling)</strong>과 <strong>평균 풀링(Average Pooling)</strong>이 있다. 최대 풀링은 정해진 범위 내에서 가장 큰 값을 꺼내오는 방식이며, 평균 풀링은 정해진 범위 내에 있는 모든 요소의 평균을 가져오는 방식이다. 풀링의 방식을 살펴보면 <strong>가중치가 존재하지 않는다</strong>는 것을 알 수 있다. 단순히 값을 꺼내올 뿐인 것이다. 또한 <strong>채널 수 역시 변하지 않는다.</strong> 보통 최대 풀링이 평균 풀링보다 성능이 더 좋기 때문에 최대 풀링을 주로 사용한다. </p>
<p>합성곱 층에서와 마찬가지로 풀링 층의 각 뉴련은 이전 층의 작은 사각 영역의 수용장 안에 있는 뉴런의 출력과 연결되어 있다. 이전과 동일하게 크기, 스트라이드, 패딩 유형을 지정해야 한다. 이때 보통 <strong>크기와 스트라이드를 동일하게 설정</strong>한다.</p>
<p>아래 그림은 <code>2X2</code>크기의 최대 풀링과 평균 풀링을 처리하는 과정을 비교하여 나태낸 것이다.
<img src="https://velog.velcdn.com/images/sea_panda/post/3a188893-11e9-41c6-af85-e28187278cb7/image.png" alt=""></p>
<p>위 그림에서 왼쪽은 최대 풀링으로 각각의 <code>2×2</code>의 범위 내에서 가장 큰 요소인 <code>100,184,12,45</code> 출력 데이터로 가져온다. 오른쪽은 평균 풀링으로 각각의 <code>2×2</code>의 범위 내 요소의 평균값인 <code>36,80,12,15</code> 를 출력 데이터로 가져온다.</p>
<p>계산량, 메모리 사용량, 파라미터 수를 감소하는 것 외에도 최대 풀링의 경우 작은 변화에도 일정 수준의 불변성을 만들어 준다. 
<img src="https://velog.velcdn.com/images/sea_panda/post/0cadb659-a7f3-44e9-8e3e-3284ef1566cb/image.png" alt=""></p>
<p>위 그림에서 볼 수 있듯이 8이라는 형태가 전체 픽셀에서 어느 정도 이동하더라도 풀링 층을 거친 결과는 동일하다는 것을 볼 수 있다. 이를 <strong>이동 불변성</strong>이라고 한다. CNN에서 몇 개 층마다 최대 풀링 층을 추가하면 전체적으로 일정 수준의 이동 불변성을 얻을 수 있다. 또한 최대 풀링은 회전과 확대, 축소에 대해서 약간의 불변성을 제공한다. 이와 같은 불변성은 제한적이긴 하지만 분류 작업처럼 예측이 이런 작은 부분에서 영향을 받지 않는 경우 유용할 수 있다.</p>
<p>하지만 이 풀링은 단점 역시 가지고 있다. 풀링을 진행하게 되면 입력값의 정보가 일부 사라지게 된다. 대체로 최대 풀링이 평균 풀링보다 더 많은 정보 손실을 가져온다. 그리고 최대 풀링의 불변성의 경우에는 어떤 어플리케이션에서는 불필요하기 때문에 목표하는 어플리케이션의 기능이나 Task에 맞게 풀링을 사용할지 안할지 결정해야 한다.</p>
<blockquote>
<p>💡 <strong>추가 내용</strong>
위에서 말한 2가지의 풀링 층 외에도 현대적인 신경망 구조에서 볼 수 있는 <span style="color:green"><strong>전역 평균 풀링 층(GAP)</strong></span>이라는 풀링 층이라는 것도 존재한다. 이 층은 동작 방식이 매우 독특하다. 각 Feature map의 평균을 계산한다.
<img src="https://velog.velcdn.com/images/sea_panda/post/b943092f-3085-4cb1-8816-6194f33a15c4/image.png" alt="">
위 그림에서 볼 수 있듯이, 각 샘플의 Feature map마다 하나의 숫자를 출력한다는 것이다. 이 방식은 엄청난 정보 손실을 가져오지만 <strong>출력층에서는 유용할 수 있다.</strong> 
<br>이런 출력 층을 만들려면 <code>keras.layers.GlobalAvgPool2D</code>클래스를 사용하면 된다.</p>
</blockquote>
<h2 id="완전-연결-신경망fully-connected-layer">완전 연결 신경망(Fully Connected Layer)</h2>
<p>합성곱 층(Convolutional Layer)와 풀링 층(Pooling Layer)에서 충분히 특징을 추출했다면,
다음은 분류를 위한 완전 연결 신경망을 구축할 차례이다.</p>
<p>완전 연결 신경망은 여러분이 이전에 구축했던 다층 퍼셉트론 신경망으로 구성되어 있으며
풀어야 하는 문제에 따라서 출력층을 잘 설계해주는 것이 중요하다.</p>
<h1 id="2-cnn의-학습">2. CNN의 학습</h1>
<p>CNN에서 학습되는 부분은 바로 Convolution 층에 있는 Filter의 가중치와 편향, 완전 연결 신경망을 구성하는 다층 퍼셉트론 신경망의 가중치와 평향이다. </p>
<p>학습된 필터를 시각화하면 아래의 그림과 같은 형태로 나타난다. 참고로 아래의 그림은 ImageNet 데이터를 학습한 CNN의 Convolution층의 Filter의 가중치를 시각화한 것이다.
<img src="https://velog.velcdn.com/images/sea_panda/post/5fca0ba1-8143-47e1-b2f2-6e862d91142a/image.png" alt=""></p>
<p>층이 깊어지면 Convolution 층과 Pooling 층을 거치면서 이미지가 작아지고 Convolution 층의 Filter는 더 큰 특징을 포착하게 된다.
<img src="https://velog.velcdn.com/images/sea_panda/post/18081997-3729-4d22-bd04-80ccb47d1fb1/image.png" alt=""></p>
<p>위 그림을 보면 낮은 층에서는 단순하게 가로, 세로, 대각선 등의 특징을 학습하는 것을 볼 수 있고, 층을 지날수록 물체의 일부분을 포착하면서 결국 물체 전체의 윤곽에 해당하는 특징을 학습하는 것을 알 수 있다.</p>
<hr>
<h2 id="cnn-학습-example">CNN 학습 example</h2>
<p>일반적인 이미지를 10개의 클래스로 분류하는 데이터셋인 Cifal 10 데이터셋을 직접 구축한 CNN을 통하여 분류하는 예제를 풀어보겠다.</p>
<p>먼저 필요한 패키지와 라이브러리를 불러오고, 시드를 고정한다.</p>
<pre><code class="language-python">from tensorflow.keras.datasets import cifar10
from tensorflow.keras import Sequential
from tensorflow.keras.layers import Dense, Conv2D, MaxPooling2D, Flatten
from tensorflow.keras.layers import Dense, Flatten
from tensorflow.keras.models import Sequential
from tensorflow.keras.datasets import cifar10

from sklearn.model_selection import train_test_split

import numpy as np
import tensorflow as tf

np.random.seed(42)
tf.random.set_seed(42)</code></pre>
<p>그 후 데이터셋을 불러온 후 학습 데이터셋(Train Dataset)과 시험 데이터셋(Test Dataset)으로 나누어(Split)주고 픽셀값을 정규화 하여준다. </p>
<pre><code class="language-python"># 데이터 불러오기
(X_train, y_train), (X_test, y_test) = cifar10.load_data()

# 픽셀값 정규화
X_train = X_train.astype(&#39;float32&#39;) / 255.
X_test = X_test.astype(&#39;float32&#39;) / 255.

# 데이터셋 Split
X_train, X_val, y_train, y_val = train_test_split(X_train, y_train, test_size=.2)</code></pre>
<p>이제 본격적으로 신경망 모델을 구축해 보겠다. 3개의 Convolution 층 사이에 Pooling 층을 끼워넣어 특징 추출 부분을 구성하고, 1개의 은닉층과 출력층으로 구성된 완전 연결 신경망으로 분류기를 구축하여 보겠다.</p>
<pre><code class="language-python"># 모델 구축
model = Sequential()

# Conv-Pool layer 특징을 추출하는 부분
model.add(Conv2D(32, (3,3), padding=&#39;same&#39;, activation=&#39;relu&#39;))
model.add(MaxPooling2D(2,2))
model.add(Conv2D(64, (3,3), padding=&#39;same&#39;, activation=&#39;relu&#39;))
model.add(MaxPooling2D(2,2))
model.add(Conv2D(64, (3,3), padding=&#39;same&#39;, activation=&#39;relu&#39;))

# Dense layer에 입력 가능하도록 Data 펼치기
model.add(Flatten())

# 분류를 위한 신경망 구성
model.add(Dense(128, activation=&#39;relu&#39;))
model.add(Dense(10, activation=&#39;softmax&#39;))</code></pre>
<p><code>Conv2D</code>에 대해서 조금 더 자세히 살펴보면 다음과 같다.</p>
<pre><code class="language-python">tf.keras.layers.Conv2D(
    filters,
    kernel_size,
    strides=(1, 1),
    padding=&#39;valid&#39;,
    data_format=None,
    dilation_rate=(1, 1),
    groups=1,
    activation=None,
    use_bias=True,
    kernel_initializer=&#39;glorot_uniform&#39;,
    bias_initializer=&#39;zeros&#39;,
    kernel_regularizer=None,
    bias_regularizer=None,
    activity_regularizer=None,
    kernel_constraint=None,
    bias_constraint=None,
    **kwargs
)</code></pre>
<blockquote>
<ul>
<li><code>filters</code>: 정수로 Output shape의 차원을 결정한다. 더 쉽게 말하면 filter의 수를 의미한다.</li>
</ul>
</blockquote>
<ul>
<li><code>kernel_size</code>: 필터의 크기를 지정<blockquote>
<blockquote>
<p>위 두가지 파라미터에 대해서 조금 더 자세히 다루어 보겠다. Conv2D Layer를 지나게 되면 기본적으로 Chanel의 수가 <code>filters</code>에 입력된 수로 변하게 된다. 그 과정은 다음과 같다고 할 수 있다.
<img src="https://velog.velcdn.com/images/sea_panda/post/7f7a1c05-a5b9-47da-98bc-cc63b582534b/image.png" alt=""></p>
</blockquote>
<p><img src="https://velog.velcdn.com/images/sea_panda/post/24c1f759-79a9-4492-a99c-bff27a2e67eb/image.gif" alt=""></p>
<blockquote>
</blockquote>
<p>1개의 feature map은 다음과 같이 계산됩니다.</p>
<blockquote>
<blockquote>
<ol>
<li>각각의 channel에  대응되는 kernel을 통해 슬라이딩</li>
</ol>
</blockquote>
</blockquote>
</blockquote>
</li>
</ul>
<ol start="2">
<li>각 channel의 결과를 모두 더한다 → 하나의 feature map 생성</li>
<li>헤딩 과정을 생성하려는 feature map의 수만큼 반복<blockquote>
<blockquote>
</blockquote>
<p>그림에서 볼 수 있듯이 각 필터에는 입력된 채널에 대응하는 커널이 존재하고, 이 커널의 합으로 결국에 하나의 Ouput을 만들어 낸다. 그리고 이 Output을 결국 필터의 수 만큼 반복하기 때문에 <code>Filters</code>의 값이 출력값의 차원, 즉 Channel수가 된다.</p>
</blockquote>
</li>
</ol>
<ul>
<li><code>Padding</code>: padding 방식 지정, <code>vaild</code>와 <code>same</code>을 사용할 수 있다.</li>
</ul>
<p>이제 모델을 컴파일하고 훈련 시켜 준다.</p>
<pre><code class="language-python"># 모델 컴파일
model.compile(optimizer=&#39;adam&#39;,
              loss=&#39;sparse_categorical_crossentropy&#39;,
              metrics=[&#39;accuracy&#39;])
# 모델 훈련
model.fit(X_train, y_train,
          batch_size=128,
          validation_data=(X_val, y_val),
          epochs=10)</code></pre>
<p>이 학습한 신경망 모델을 사용하여 성능을 평가한다.</p>
<pre><code class="language-python">model.evaluate(X_test, y_test, verbose=2)</code></pre>
<hr>
<h1 id="3-전이학습transfer-learning">3. 전이학습(Transfer Learning)</h1>
<p><img src="https://velog.velcdn.com/images/sea_panda/post/5abca499-a1ad-4d25-946c-bb1379c981a7/image.png" alt=""></p>
<p>신경망의 계층 구조는 심층 신경망이 좋은 솔루션으로 빠르게 수렴하게끔 도와줄 뿐만 아니라 새로운 데이터에 일반화되는 능력도 향상시켜준다.</p>
<p>이런 계층 구조를 가진 새로운 신경망을 구축 할 때 처음에 가중치와 편향을 난수로 초기화 하는 대신에 <strong>사전 학습 모델(Pre-Trained Model)</strong>의 가중치를 그대로 가져와 사용하면 대부분의 사진과 같은 데이터에 나타나는 저수준 구조를 학습할 필요가 없게 된다. 즉, 고수준 구조만 학습하면 된다. 이를 <span style="color:green"><strong>전이 학습<sup>Transfer Learning</sup></strong></span>이라고 한다.</p>
<p>사전 학습 모델의 가중치는 <strong>대량의 데이터</strong>를 학습하여 얻어진다. 여러 데이터의 일반적인 특징을 많이 학습하였기 때문에 어떠한 데이터를 넣더라도 준수한 성능을 보인다. 일반적으로 사전 학습 가중치는 학습되지 않도록 <strong>고정(freeze)</strong>한 채로 진행되기 때문에 빠르게 좋은 결과를 얻을 수 있다는 장점이 있다.</p>
<p>이미지 분류를 위한 주요 사전 학습 모델로는 다음과 같은 것들이 있다. 블로그 정리 글을 링크로 남겨두었으니 나중에 복습할 때 참고하자.</p>
<blockquote>
<p>❗ <strong>사전 학습 모델</strong></p>
</blockquote>
<ol>
<li><a href="https://bskyvision.com/entry/CNN-%EC%95%8C%EA%B3%A0%EB%A6%AC%EC%A6%98%EB%93%A4-VGGNet%EC%9D%98-%EA%B5%AC%EC%A1%B0-1">VGG</a> - <a href="https://arxiv.org/abs/1409.1556">VGG 논문</a></li>
<li><a href="https://bskyvision.com/539">GoogLeNet(Inception)</a> - <a href="https://arxiv.org/abs/1409.4842">GoogLeNet 논문</a>
<sub>Inception에 대해서 다시 또 고찰한 _&quot;Rethinking the Inception Architecture for Computer Vision&quot;_라는 논문도 있다. Inception은 가로 방향으로 층을 넓게 구성한 구조를 의미한다.</sub></li>
<li><a href="https://bskyvision.com/644">ResNet</a> - <a href="https://arxiv.org/abs/1512.03385">Resnet 논문</a>
<sub>ResNet에서는 <strong>Residual Connection(=Skipped Connection)</strong>라는 중요한 특징이 있으니 추후 복습 시에 이 부분을 찾아보자.</sub></li>
</ol>
<h2 id="전이학습-예제">전이학습 예제</h2>
<pre><code class="language-python"># 패키지 및 라이브러리 불러오기
from tensorflow.keras.applications.vgg16 import VGG16
from tensorflow.keras.layers import Dense, GlobalAveragePooling2D
from tensorflow.keras.models import Sequential
from tensorflow.keras.datasets import cifar10

from sklearn.model_selection import train_test_split

import numpy as np
import tensorflow as tf

# Seed 고정
np.random.seed(42)
tf.random.set_seed(42)

# 데이터셋 불러오기
(X_train, y_train), (X_test, y_test) = cifar10.load_data()

# 픽셀값 정규화
X_train = X_train.astype(&#39;float32&#39;) / 255.
X_test = X_test.astype(&#39;float32&#39;) / 255.

# 데이터셋 Split
X_train, X_val, y_train, y_val = train_test_split(X_train, y_train, test_size=.2)</code></pre>
<p>이제 다음 코드로 사전 학습된 모델(<code>VGG16</code>)을 불러온다.</p>
<pre><code class="language-python">pretrained_model = VGG16(weights=&#39;imagenet&#39;, include_top=False)</code></pre>
<p>이제 사전 학습 모델 위에 완전 연결 신경망을 추가한다. 이때 <code>GlobalAveragePooling2d()</code>층은 <code>Flatten</code>과 비슷한 역할을 수행하는 층으로 데이터의 Shape을 <code>(None, None, None, 512)</code>에서 <code>(None, 512)</code>로 변화시켜주는 역할을 한다.</p>
<pre><code class="language-python"># 완전 연결 신경망 추가하여 모델 생성
model = Sequential()
model.add(pretrained_model)
model.add(GlobalAveragePooling2D())
model.add(Dense(128,activation=&#39;relu&#39;))
model.add(Dense(10,activation=&#39;softmax&#39;))

# 모델 컴파일
model.compile(optimizer=&#39;adam&#39;,
              loss=&#39;sparse_categorical_crossentropy&#39;,
              metrics=[&#39;accuracy&#39;])

# 모델 훈련
model.fit(X_train, y_train,
          batch_size=128,
          validation_data=(X_val, y_val),
          epochs=10)

# 신경망 모델을 사용하여 평가
model.evaluate(X_test, y_test, verbose=2)</code></pre>
<h1 id="4-이미지-증강">4. 이미지 증강</h1>
<p><strong>이미지 증강</strong>이란 회전, 반전, 자르기 ,밝기 혹은 채도 변화 등을 통하여 데이터를 늘리는 방법이다. 
<img src="https://velog.velcdn.com/images/sea_panda/post/92e4f95e-aa81-47c8-be72-ec1bc492d2c5/image.png" alt=""></p>
<p><img src="https://velog.velcdn.com/images/sea_panda/post/704fc60d-d282-4c54-99ae-1ecfca765862/image.png" alt=""></p>
<p>인간은 쉽지 않게 위 두 이미지를 고양이라고 판단할 수 있다. 하지만 컴퓨터의 경우 학습 시에 대부분 정면의 고양이 사진을 가지고 학습을 하고, 그렇기 때문에 모델이 기울어져 있거나 뒤집어진 이미지에서 잘 예측하지 못한다는 취약점을 가지고 있다.</p>
<p>따라서 사진을 일부로 회전, 반전, 자르기, 밝기 혹은 채도 변화, 늘리기 등의 가공을 하여 더 <strong>강건</strong>한 모델을 만들기 위해 진행한다.</p>
<p>다음과 같은 코드로 이미지 증강을 할 수 있으며 상하 반전과 회전을 준 경우의 코드이다.</p>
<pre><code class="language-python">data_augmentation = tf.keras.Sequential([
  layers.experimental.preprocessing.RandomFlip(&quot;vertical&quot;),
  layers.experimental.preprocessing.RandomRotation(0.2),
])
augmented_image = data_augmentation(image)</code></pre>
<p>이 방법 외에도 <code>ImageDataGenerator</code>를 사용하여 이미지 증강을 할 수 있다.</p>
<pre><code class="language-python">ImageDataGenerator(horizontal_filp=True, vertical_flip=True,
rescale=1/255.0, rotation=0.45, zoom_range=[0.5, 1.5]) </code></pre>
<p>더 자세한 내용은 <a href="https://www.tensorflow.org/api_docs/python/tf/keras/preprocessing/image/ImageDataGenerator">공식문서</a>를 확인하자</p>
<h1 id="5-1x1-convolution">5. 1X1 Convolution</h1>
<p><code>1X1 convolution</code>은 <code>GoogeLeNet</code>에서 많이 사용되는 방법으로 <strong>계산량 감소, 차원축소, 비선형성 증가, Overfitting 방지</strong>등의 장점을 가지고 있어서 많이 사용된다. 자세한 설명은 다음 블로그를 참고하자.</p>
<blockquote>
<p>💡 <a href="https://euneestella.github.io/research/2021-10-14-why-we-use-1x1-convolution-at-deep-learning/">1X1 Convolution</a></p>
</blockquote>
<h1 id="6-회고">6. 회고</h1>
<p>확실히 NLP보다 CV가 더 재미있는 것 같다. 그리고 오늘 ADsP시험을 신청했는데 열심히 준비해야겠다. 오늘 원래 대충 정리하고 <code>21:30</code>부터 ADsP공부 좀 하려 했는데...결국 11시까지 못 그만두고 다 해버렸다. 내일부터는 좀 계획된 시간대로 움직이고 더 부지런하게 움직여야 할 것 같다. 4일 연휴 연장 푹 쉬었으니까 다시 열심히 해겠다. 아 근데 아직 정리 안한 N33x들과 N42x들은 언제 정리하지....
<br><br><br></p>
<blockquote>
<p>❗ <strong>참고자료</strong></p>
</blockquote>
<ol>
<li><a href="https://ardino.tistory.com/38">합성곱 신경망(CNN)/고양이 눈에서 답을 얻다.</a></li>
<li>오렐리앙 제옹, 핸즈온 머신러닝(2판), 서울:한빛미디어,O<sup>$\cdot$</sup>REILLY, 2020</li>
<li>CodeStates Lecture Note - N431</li>
</ol>
]]></description>
        </item>
        <item>
            <title><![CDATA[[N422] TIL 및 회고]]></title>
            <link>https://velog.io/@sea_panda/N422-TIL-%EB%B0%8F-%ED%9A%8C%EA%B3%A0</link>
            <guid>https://velog.io/@sea_panda/N422-TIL-%EB%B0%8F-%ED%9A%8C%EA%B3%A0</guid>
            <pubDate>Thu, 19 Jan 2023 12:30:06 GMT</pubDate>
            <description><![CDATA[<h1 id="0-학습목표">0. 학습목표</h1>
<h2 id="level-1">Level 1.</h2>
<ul>
<li><strong>임베딩(Embedding)</strong>의 개념과 One-Hot Encoding과 비교되는 장점에 대해 설명할 수 있다.</li>
<li><strong>Word2Vec</strong>의 두 방법(CBoW, Skip-gram)의 차이와 Word2Vec으로 임베딩한 단어 벡터의 특징에 대해 설명할 수 있다.<h2 id="level-2">Level 2.</h2>
</li>
<li><strong>FastText</strong>에서 적용된 철자 단위 임베딩(Character-Level Embedding)방법의 장점에 대해 설명할 수 있다.<h2 id="level-3">Level 3.</h2>
</li>
<li><strong>임베딩(Embedding)</strong>이 다른 도메인에서는 어떻게 사용되는지 폭넓게 이해하며 예시를 들어 설명할 수 있다.</li>
</ul>
<h1 id="1-분산-기반-표현distributed-representation">1. 분산 기반 표현(Distributed Representation)</h1>
<p>지난 노트에서 <strong><a href="https://velog.io/@sea_panda/N421-TIL-%EB%B0%8F-%ED%9A%8C%EA%B3%A0#5-%EB%93%B1%EC%9E%A5-%ED%9A%9F%EC%88%98-%EA%B8%B0%EB%B0%98-%EB%8B%A8%EC%96%B4%ED%91%9C%ED%98%84count-based-representation">등장 횟수 기반 표현(Count-based Representation)</a></strong>에 대해서 학습하였다.</p>
<p>이번 노트에서는 단어 자체를 <strong>벡터화</strong>하는 방법에 대해서 다룬다. <span style="color:orange"><strong>Word2Vec</strong></span>에서는 벡터화하고자 하는 <strong>타겟 단어(Target word)의 표현</strong>이 해당 단어 <strong>주변 단어</strong>에 의해 결정된다. 단어 벡터를 이렇게 정의하는 이유는 <strong>분포 가설(Distribution hypothesis)</strong>때문이다.</p>
<blockquote>
<p>💡 <strong>분포 가설</strong>
&quot;비슷한 위치에서 등장하는 단어들은 비슷한 의미를 가진다&quot;
즉, 다시 정리하면 비슷한 의미를 지닌 단어는 주변 단어 분포 역시 비슷하다고 가정하는 것이 분포 가설의 핵심이다. </p>
</blockquote>
<p>이 분포 가설에 기반하여 주변 단어 분포를 기준으로 단어의 벡터 표현이 결정되기 때문에 <span style="color:orange"><strong>분산 표현(Distributed Representation)</strong></span>이라고 부른다. 이렇게 표현된 벡터들은 원-핫 벡터처럼 벡터의 차원이 단어 집합(vocabulary)의 크기일 필요가 없으므로, 벡터의 차원이 상대적으로 저차원으로 줄어든다.</p>
<p>본격적인 분산 표현을 학습하기에 앞 서 <a href="https://velog.io/@sea_panda/N213-TIL-%EB%B0%8F-%ED%9A%8C%EA%B3%A0#1-one-hot-encoding">원-핫 인코딩(One-Hot Encoding)</a>을 다시 한 번 읽고오자. 원-핫 인코딩은 범주형 변수를 벡터로 나타내는 방법 중 하나로, 쉽게 이해할 수 있는 직관적인 방법이지만 <strong>단어 간 유사도를 구할 수 없다</strong>는 치명적인 단점이 있다. 단어 간 유사도를 구할 때에는 <strong>코사인 유사도(Cosine Similarity)</strong>가 자주 사용된다. 그 식은 다음과 같다.
$$$
Cosine,,similarity={\vec a\cdot\vec b\over\vert\vec a\vert\vert\vec b\vert}
$$$
위 식에 원-핫 인코딩을 적용한 서로 다른 두 벡터를 대입하면 항상 0이 된다. 따라서 두 단어 사이의 유사도를 계산할 수 없게 된다. 다음과 같은 코드로 간단하게 구현도 가능하다.</p>
<pre><code class="language-python">import numpy as np

def cos_sim(a, b):
    &quot;&quot;&quot;
    코사인 유사도를 구하는 함수입니다.

    Args:
        a, b : 토큰 벡터입니다 -&gt; array
    &quot;&quot;&quot;
    arr_a = np.array(a)
    arr_b = np.array(b)

    result = np.dot(arr_a, arr_b)/(np.linalg.norm(arr_a)*np.linalg.norm(arr_b))
    return result

print(f&quot;I 와 am 의 코사인 유사도 : {cos_sim(word_dict[&#39;I&#39;], word_dict[&#39;am&#39;])}&quot;)
print(f&quot;I 와 student 의 코사인 유사도 : {cos_sim(word_dict[&#39;I&#39;], word_dict[&#39;student&#39;])}&quot;)
------------------------------------------------------------------------------
I 와 am 의 코사인 유사도 : 0.0
I 와 student 의 코사인 유사도 : 0.0</code></pre>
<h2 id="embedding임베딩">Embedding(임베딩)</h2>
<p>단어 사이의 관계를 나타낼 수 없다는 원-핫 인코딩의 단점을 해결하기 위해 등장한 것이 바로 <span style="color:orange"><strong>임베딩(Embedding)</strong></span>이다. 단어를 고정 길이의 벡터, 즉 차원이 일정한 벡터로 나타내기 때문에 &quot;Embedding(=박다, 끼워넣다)&quot;이라는 이름이 붙었다.</p>
<p><strong>임베딩</strong>을 거친 단어는 One-Hot Encoding을 거친 단어와는 다른 형태의 값을 가진다.</p>
<pre><code class="language-python">[0.04227, -0.0033, 0.1607, -0.0236, ...]</code></pre>
<p>위와 같이 벡터 내의 각 요소가 연속적인 값을 가지게 된다. 이런 벡터를 만드는 방법 중 가장 널리 알려진 임베딩 방법으로 <strong><code>Word2Vec</code></strong>이 있다.</p>
<h1 id="2-word2vec">2. Word2Vec</h1>
<p>2013년에 고안된 <strong><span style="color:orange">Word2Vec</span></strong>은 말 그대로 <strong>단어를 벡터로(Word to vector) 나타내는 방법</strong>으로 가장 널리 사용되는 임베딩 방법 중 하나이다.</p>
<p><code>Word2Vec</code>은 특정 단어 양 옆에 있는 두 단어(<code>Window size = 2</code>)의 관계를 활용하기 때문에 분포 가설을 잘 반영하고 있다.</p>
<p><code>Word2Vec</code>에는 <code>CBoW</code>와 <code>Skip-gram</code>의 2가지 방법이 있다. 두 가지 방법에 대해 알아보자.</p>
<h2 id="cbow와-skip-gram">CBoW와 Skip-gram</h2>
<p><code>CBoW</code>와 <code>Skip-gram</code>의 차이는 다음과 같다.</p>
<blockquote>
<p><strong>1. **주변 단어에 대한 정보를 기반으로 중심 단어의 정보를 예측하는 모델 -&gt; <span style="color:orange"></strong>CBoW(Continuous Bag-of-Words)<strong></span>
*<em>2. *</em>중심 단어의 정보를 기반으로 주변 단어의 정보를 예측하는 모델 -&gt; <span style="color:green"></strong>Skip-gram**</span></p>
</blockquote>
<p>아래 그림과 예시를 통하여 두 방식의 차이를 좀 더 잘 이해해 보겠다.
<img src="https://velog.velcdn.com/images/sea_panda/post/b567828c-7745-4b1c-8710-7f63a6e7c959/image.png" alt=""></p>
<p>다음 예시는 &lt;별 헤는 밤&gt;의 일부분에 형태소 분석기를 적용하여 토큰화한 것이다.</p>
<p>표시된 언어 정보를 바탕으로  아래의 [---]에 들어갈 단어를 예측하는 과정으로 학습이 진행된다.</p>
<blockquote>
<p>👉 <strong>CBoW</strong>
“… 나 는 [ -- ] 하나 에 … “
“… 는 별 [ ---- ] 에 아름다운 …”
“… 별 하나 [ -- ] 아름다운 말 …”
“… 하나 에 [ -------- ] 말 한마디 …”</p>
</blockquote>
<blockquote>
<p>👉 <strong>Skip-gram</strong>
“… [ -- ] [ -- ] 별 [ ---- ] [ -- ] …”
“… [ -- ] [ -- ] 하나 [ -- ] [ -------- ] …”
“… [ -- ] [ ---- ] 에 [ -------- ] [ -- ] …”
“… [ ---- ] [ -- ] 아름다운 [ -- ] [ ------ ] …”</p>
</blockquote>
<p>더 많은 정보를 바탕으로 특정 단어를 예측하기 때문에 CBoW의 성능이 더 좋을 것으로 생각하기 쉽지만, 역전파 관점에서 보면 Skip-gram에서 훨씬 더 많은 학습이 일어나기 때문에 <strong>Skip-gram</strong>의 성능이 조금 더 좋게 나타난다.</p>
<p>물론 계산량이 많기 때문에 Skip-gram에 드는 리소스가 더 큰 것도 사실이다.</p>
<h2 id="word2vec-모델의-구조">Word2Vec 모델의 구조</h2>
<p>성능 덕분에 조금 더 자주 사용되는 Skip-gram을 기준으로 Word2Vec의 구조에 대하여 알아보겠다.</p>
<ul>
<li><strong>입력</strong>: Word2Vec의 입력은 One-Hot Encoding된 단어 벡터이다.</li>
<li><strong>은닉층</strong>: 임베딩 벡터의 차원수 만큼의 노드로 구성된 은닉층이 1개인 신경망이다.</li>
<li><strong>출력층</strong>: 단어 개수 만큼의 노드로 이루어져 있으며 활성화 함수로 소프트맥스를 사용한다.</li>
</ul>
<p><img src="https://velog.velcdn.com/images/sea_panda/post/70465906-1c12-480a-9c4a-d248ae20b6f2/image.png" alt=""></p>
<p>해당 그림은 논문에서 구성한 Word2Vec모델의 개략적인 구조로, 총 10,000개의 단어에 대해서 300차원의 임베딩 벡터를 구했기 때문에 신경망 구조가 위와 같이 구성된다.</p>
<h2 id="word2vec-학습을-위한-학습-데이터-디자인">Word2Vec 학습을 위한 학습 데이터 디자인</h2>
<p>효율적인 Word2Vec학습을 위해서는 학습 데이터를 잘 구성해야 한다. Window사이즈가 2인 Word2Vec이므로 중심 단어 옆에 있는 2개 단어에 대해 단어쌍을 구성한다.</p>
<p>예를 들어,<strong>&quot;The tortoise jumped into the lake&quot;</strong>라는 문장에 대해 단어쌍을 구성해보겠다. 윈도우 크기가 2인 경우 다음과 같이 <strong>Skip-gram</strong>을 학습하기 위한 데이터 쌍을 구축할 수 있다.</p>
<blockquote>
<ul>
<li><strong>중심 단어</strong> : <strong>The</strong>, 주변 문맥 단어 : tortoise, jumped<ul>
<li>학습 샘플: (the, tortoise), (the, jumped)<br><br></li>
</ul>
</li>
</ul>
</blockquote>
<ul>
<li><strong>중심 단어</strong> : <strong>tortoise</strong>, 주변 문맥 단어 : the, jumped, into <ul>
<li>학습 샘플: (tortoise, the), (tortoise, jumped), (tortoise, into)<br><br></li>
</ul>
</li>
<li><strong>중심 단어</strong> : <strong>jumped</strong>, 주변 문맥 단어 : the, tortoise, into, the<ul>
<li>학습 샘플: (jumped, the), (jumped, tortoise), (jumped, into), (jumped, the)<br><br></li>
</ul>
</li>
<li><strong>중심 단어</strong> : <strong>into</strong>, 주변 문맥 단어 : tortoise, jumped, the, lake<ul>
<li>학습 샘플: (into, tortoise), (into, jumped), (into, the), (into, lake)</li>
</ul>
</li>
</ul>
<p>이를 DataFrame 형태로 정리하면 다음과 같은 데이터쌍이 만들어 진다.
<img src="https://velog.velcdn.com/images/sea_panda/post/c05bb9ad-9ba8-4362-a13a-39b219f59029/image.png" alt=""></p>
<p>Skip-gram에서는 중심단어를 입력으로, 문맥단어를 레이블로 하는 분류(Classification)를 통해 학습한다고 생각하면 된다.</p>
<h2 id="word2vec의-결과">Word2Vec의 결과</h2>
<p>학습이 모두 끝나면 10000개의 단어에 대해 300차원의 임베딩 벡터가 생성된다. 만약에 임베딩 벡터의 차원을 조절하고 싶다면 은닉층의 노드 수를 줄이거나 늘릴 수 있다.</p>
<p>아래 그림은 신경망 내부에 있는 $10000\times300$크기의 가중치 행렬에 의해서 10000개 단어에 대한 300차원의 벡터가 생성되는 모습을 나타낸 이미지이다.
<img src="https://velog.velcdn.com/images/sea_panda/post/8952be74-ae1e-4e9c-9a5a-4cd1209f78d3/image.png" alt=""></p>
<p>학습과정에서 Word2Vec의 계산량을 줄이기 위해 사용하는 기법들이 있지만 이 Sprint에서는 다루지 않는다. 추후 추가적인 학습을 원한다면 다음과 같은 키워드를 통해서 학습하면 된다고 한다.</p>
<ul>
<li>Sub-sampling</li>
<li>Negative-sampling</li>
</ul>
<p>결과적으로 Skip-gram 모델을 통하여 10000개 단어에 대한 임베딩 벡터를 얻을 수 있다. 이렇게 얻은 임베딩 벡터는 문장 간의 관련도 계산, 문서 분류같은 작업에 사용할 수 있다.</p>
<h2 id="word2vec으로-임베딩한-벡터-시각화">Word2Vec으로 임베딩한 벡터 시각화</h2>
<p>Word2Vec을 통해 얻은 임베딩 벡터는 <span style="color:orange"><strong>단어 간의 의미적, 문법적 관계를 잘 나타낸다.</strong></span> 이를 대표적으로 잘 보여주는 것이 아래 그림이다.
<img src="https://velog.velcdn.com/images/sea_panda/post/7c9452a0-3fc4-4be0-9031-5ec963f9ae15/image.png" alt=""></p>
<p><strong>1. *<em><code>man - woman</code> 사이의 관계와 <code>King-queen</code>사이의 관계가 매우 유사하게 나타난다. 이를 통하여 생성된 임베딩 벡터가 단어의 *</em>의미적(Semantic)관계</strong>를 잘 표현하는 것을 확인할 수 있다.</p>
<p><strong>2.</strong> <code>walking-walked</code> 사이의 관계와 <code>swimming - swam</code>사이의 관계가 매우 유사하게 나타난다. 이를 통하여 생성된 임베딩 벡터가 단어의 <strong>문법적(혹은 구조적, Syntactic)인 관계도 잘 표현</strong>하는 것을 확인할 수 있다.</p>
<p>*<em>3. *</em>고유명사에 대해서도 <code>나라-수도</code>와 같은 관계를 잘 나타내고 있는 것을 확인할 수 있다.</p>
<h2 id="gensim-패키지로-word2vec-실습하기">gensim 패키지로 word2Vec 실습하기</h2>
<p><code>gensim</code>은 <code>word2Vec</code>으로 사전 학습된 임베딩 벡터를 쉽게 사용해볼 수 있는 패키지이다. <code>gensim</code>을 사용하여 <code>Word2Vec</code>의 결과가 어떻게 도출되는지 알아보겠다.</p>
<p><strong>0. <code>gensim</code>패키지 업그레이드</strong>
<code>--upgrade</code> 셀을 실행하여 gensim 패키지를 업그레이드 한 후, Coloab의 메뉴 탭에서 &quot;런타임 &gt; 런타임 다시 시작&quot;을 클릭하여 런타임을 재시작 해준다.
이후 아래 <code>.__version__</code>을 활용하여 업그레이드가 잘 되었는지 확인한다.</p>
<pre><code class="language-python">!pip install gensim --upgrade</code></pre>
<pre><code class="language-python">import gensim

gensim.__version__</code></pre>
<p><strong>1. 구글 뉴스 말뭉치로 학습된 Word2Vec벡터를 다운받는다.</strong></p>
<pre><code class="language-python">import gensim.downloader as api

wv = api.load(&#39;word2vec-google-news-300&#39;)</code></pre>
<p><strong>2. 0~9 인덱스에 위치한 단어가 무엇인지 확인해본다.</strong></p>
<pre><code class="language-python">for idx, word in enumerate(wv.index_to_key):
    if idx == 10:
        break

    print(f&quot;word #{idx}/{len(wv.index_to_key)} is &#39;{word}&#39;&quot;)
-----
word #0/3000000 is &#39;&lt;/s&gt;&#39;
word #1/3000000 is &#39;in&#39;
word #2/3000000 is &#39;for&#39;
word #3/3000000 is &#39;that&#39;
word #4/3000000 is &#39;is&#39;
word #5/3000000 is &#39;on&#39;
word #6/3000000 is &#39;##&#39;
word #7/3000000 is &#39;The&#39;
word #8/3000000 is &#39;with&#39;
word #9/3000000 is &#39;said&#39;</code></pre>
<p><strong>3. 임베딩 벡터의 차원과 값을 눈으로 확인해보자.</strong>
&quot;king&quot;이라는 단어의 벡터의 Shape를 출력하여 임베딩 벡터의 차원을 확인해본다. 그리고 결과를 통해 <code>Word2Vec</code>을 통해 학습된 임베딩 300차원이며, 벡터의 요소가 One-Hot Encoding과는 다르다는 것을 확인할 수 있다.</p>
<pre><code class="language-python">vec_king = wv[&#39;king&#39;]

print(f&quot;임베딩 벡터의 차원 수 : {vec_king.shape}\n&quot;)
print(f&quot;&#39;king&#39; 의 임베딩 벡터 \n\n {vec_king}&quot;)
-----
임베딩 벡터의 차원 수 : (300,)

&#39;king&#39; 의 임베딩 벡터 

 [ 1.25976562e-01  2.97851562e-02  8.60595703e-03  1.39648438e-01
 -2.56347656e-02 -3.61328125e-02  1.11816406e-01 -1.98242188e-01
  5.12695312e-02  3.63281250e-01 -2.42187500e-01 -3.02734375e-01
 -1.77734375e-01 -2.49023438e-02 -1.67968750e-01 -1.69921875e-01
 .
 .
 .
 .
 .]</code></pre>
<p><strong>4. 말뭉치에 등장하지 않는 단어의 임베딩 벡터를 확인해 본다.</strong>
<strong>&quot;cameroon&quot;</strong>이라는 단어는 사전에 지정해 준 단어 집합(Vocabulary,<code>vocab</code>)에 등장하지 않는 단어(Unknown token)이다. 이 단어를 &quot;King&quot;같이 임베딩 벡터화 해보면 <code>KeyError</code>가 발생한다. 이처럼 <code>Word2Vec</code>은 단어 집합에 지정하지 않은 단어는 벡터화 할 수 없다는 단점이 있다.</p>
<pre><code class="language-python">unk = &#39;cameroon&#39;

try:
    vec_unk = wv[unk]
except KeyError:
    print(f&quot;&quot;&quot;단어 &quot;{unk}&quot;은 해당 모델에는 등장하지 않는 단어입니다.&quot;&quot;&quot;)
-----
단어 &quot;cameroon&quot;은 해당 모델에는 등장하지 않는 단어입니다.</code></pre>
<p><strong>5. 단어 간 유사도를 파악해보자.</strong>
<code>gensim</code> 패키지가 제공하는 <code>.similarity</code>를 활용하면 단어 간 유사도를 파악할 수 있다.</p>
<p>One-Hot encoding과 다르게 임베딩 벡터는 단어 간 유사도를 구했을 때 0이 아닌 값이 나오게 된다. 아래는 &#39;car&#39;와 몇몇 단어의 유사도를 비교한 결과이다.</p>
<pre><code class="language-python">pairs = [
    (&#39;car&#39;, &#39;minivan&#39;),   
    (&#39;car&#39;, &#39;bicycle&#39;),  
    (&#39;car&#39;, &#39;airplane&#39;),
    (&#39;car&#39;, &#39;cereal&#39;),    
    (&#39;car&#39;, &#39;democracy&#39;)
]

for w1, w2 in pairs:
    print(f&#39;{w1} ======= {w2}\t  {wv.similarity(w1, w2):.2f}&#39;)
------------------------
car ======= minivan          0.69
car ======= bicycle          0.54
car ======= airplane      0.42
car ======= cereal          0.14
car ======= democracy     0.08</code></pre>
<p><code>.most_similar</code> 메서드를 사용하여 <code>&#39;car&#39;</code> 벡터에 <code>&#39;minivan&#39;</code> 벡터를 더한 벡터와 가장 유사한 5개의 단어를 뽑아보겠다.</p>
<pre><code class="language-python">for i, (word, similarity) in enumerate(wv.most_similar(positive=[&#39;car&#39;, &#39;minivan&#39;], topn=5)):
    print(f&quot;Top {i+1} : {word}, {similarity}&quot;)
-----------------------
Top 1 : SUV, 0.8532192707061768
Top 2 : vehicle, 0.8175783753395081
Top 3 : pickup_truck, 0.7763688564300537
Top 4 : Jeep, 0.7567334175109863
Top 5 : Ford_Explorer, 0.7565720081329346</code></pre>
<p>시각화에서 확인한 것처럼 <code>king</code>벡터에 <code>women</code>벡터를 더한 뒤 <code>men</code>벡터를 빼준 벡터와 가장 유사한 벡터로 <code>queen</code>이 나오는 것과 <code>walking</code>벡터에 <code>swam</code>벡터를 더한 뒤 <code>walked</code>벡터를 빼준 벡터와 가장 유사한 벡터로 <code>swimming</code>이 나오는 것을 확인할 수 있다.</p>
<pre><code class="language-python">print(wv.most_similar(positive=[&#39;king&#39;, &#39;women&#39;], negative=[&#39;men&#39;], topn=1))
print(wv.most_similar(positive=[&#39;walking&#39;, &#39;swam&#39;], negative=[&#39;walked&#39;], topn=1))
------------------
[(&#39;queen&#39;, 0.6525818109512329)]
[(&#39;swimming&#39;, 0.7448815703392029)]</code></pre>
<p><code>.doesnt_match</code>메서드를 사용하면 가장 관계없는 단어를 뽑아낼 수 있다.</p>
<pre><code class="language-python">print(wv.doesnt_match([&#39;fire&#39;, &#39;water&#39;, &#39;land&#39;, &#39;sea&#39;, &#39;air&#39;, &#39;car&#39;]))
----
car</code></pre>
<h1 id="임베딩-벡터를-활용한-문장분류">임베딩 벡터를 활용한 문장분류</h1>
<p>이번 예제에서는 이미 학습된 임베딩 벡터를 사용하여 문장 분류를 수행하는 코드에 대해 알아보겠다. 아래 코드에서는 <strong>문서에 있는 단어 벡터의 평균을 해당 문서의 벡터로 사용</strong>하여 분류 문제를 수행한다.</p>
<p>예를 들어, &quot;I am a student&quot;라는 문장을 구성하는 단어의 임베딩 벡터가 아래와 같다고 해보겠다.</p>
<blockquote>
<p>$I = [0.0012,0.0345,...,-0.0048,-0.0425]\
am = [0.00232,-0.0141,...,-0.0542,0.0454]\
a = [0.0022,0.0905,...,-0.0241,0.0028]\
student = [-0.0110,0.0492,...,0.0008,-0.0420]$</p>
</blockquote>
<p>이때, &quot;I am a student&quot;라는 문장을 분류하기 위해서 최종적으로 아래 벡터를 사용한다.</p>
<blockquote>
<p>$[0.0039,0.0400,...,-0.0206,-0.0091]$</p>
</blockquote>
<p>이게 되나? 의문이들 정도로 간단하지만, 간단한 문서 분류 문제에서는 꽤 좋은 성능을 보이기 때문에 Baseline모델로 많이 사용된다.</p>
<p><strong>1. 필요한 모듈 <code>imoport</code></strong></p>
<pre><code class="language-python">import numpy as np
import tensorflow as tf

from tensorflow.keras.preprocessing.sequence import pad_sequences
from tensorflow.keras.models import Sequential
from tensorflow.keras.layers import Dense, Embedding, GlobalAveragePooling1D
from tensorflow.keras.preprocessing.text import Tokenizer
from tensorflow.keras.datasets import imdb
</code></pre>
<p><strong>2. 시드를 정해준다.</strong></p>
<pre><code class="language-python">tf.random.set_seed(42)</code></pre>
<p><strong>3. 데이터셋을 split 해준다.</strong></p>
<pre><code class="language-python">(X_train, y_train), (X_test, y_test) = imdb.load_data(num_words=20000)
print(f&quot;Train set shape : {X_train.shape}&quot;)
print(f&quot;Test set shape : {X_test.shape}&quot;)
----------
Downloading data from https://storage.googleapis.com/tensorflow/tf-keras-datasets/imdb.npz
17465344/17464789 [==============================] - 0s 0us/step
17473536/17464789 [==============================] - 0s 0us/step
Train set shape : (25000,)
Test set shape : (25000,)</code></pre>
<p><strong>4. 데이터셋이 어떻게 생겼는지 눈으로 확인해본다.</strong></p>
<pre><code class="language-python">print(X_train[0])
print(type(X_train[0]))</code></pre>
<p><img src="https://velog.velcdn.com/images/sea_panda/post/5eb8e835-e4ce-426e-8adf-31ce7765e84b/image.png" alt=""></p>
<p><strong>5. 인덱스로 된 데이터를 텍스트로 변경하는 함수를 구현한다.</strong>
첫 번째 데이터를 텍스트로 변경하고 확인하여 본다.</p>
<pre><code class="language-python">word_index = imdb.get_word_index()
reverse_word_index = dict([(value, key) for (key, value) in word_index.items()])

def decode_review(text):
    &quot;&quot;&quot;
    word_index 를 받아서 text 를 sequence 형태로 반환하는 함수입니다.

    Args:
        text: 텍스트 시퀀스입니다 -&gt; str
    &quot;&quot;&quot;
    return &#39; &#39;.join([reverse_word_index.get(i, &#39;?&#39;) for i in text])</code></pre>
<pre><code class="language-python">decode_review(X_train[0])
----
&gt;
&quot;the as you with out themselves powerful lets loves their becomes reaching had journalist of lot from anyone to have after out atmosphere never more room and it so heart shows to years of every never going and help moments or of every chest visual movie except her was several of enough more with is now current film as you of mine potentially unfortunately of you than him that with out themselves her get for was camp of you movie sometimes movie that with scary but pratfalls to story wonderful that in seeing in character to of 70s musicians with heart had shadows they of here that with her serious to have does when from why what have critics they is you that isn&#39;t one will very to as itself with other tricky in of seen over landed for anyone of and br show&#39;s to whether from than out themselves history he name half some br of &#39;n odd was two most of mean for 1 any an boat she he should is thought frog but of script you not while history he heart to real at barrel but when from one bit then have two of script their with her nobody most that with wasn&#39;t to with armed acting watch an for with heartfelt film want an&quot;</code></pre>
<p><strong>6. <code>keras</code>의 <code>tokenizer</code>에 텍스트를 학습시킨다.</strong></p>
<pre><code class="language-python">sentences = [decode_review(idx) for idx in X_train]

tokenizer = Tokenizer()
tokenizer.fit_on_texts(sentences)</code></pre>
<pre><code class="language-python"># 단어 집합(vocab)의 크기를 입력합니다. 패딩()을 고려하여 tokenizer의 단어 수에서 +1 해줍니다.
vocab_size = len(tokenizer.word_index) + 1
print(vocab_size)
-----
19999</code></pre>
<p><strong>7. <code>pad_sequence</code>를 통해 패딩 처리해준다.</strong>
자연어 처리를 하다보면 각 문장(또는 문서)은 서로 길이가 다를 수 있다. 그런데 기계는 길이가 전부 동일한 문서들에 대해서는 하나의 행렬로 보고, 한꺼번에 묶어서 처리할 수 있다. 다시 말해서 병렬 연산을 위해 여러 문장의 길이를 임의로 동일하게 맞춰주는 작업이 필요할 때가 있다. 그리고 이러한 작업을 <strong>패딩(Padding)</strong>이라고 한다.</p>
<p>그리고 <code>pad_sequence</code>는 Keras에서 패딩을 위해서 제공하는 메서드이다.</p>
<pre><code class="language-python">X_encoded = tokenizer.texts_to_sequences(sentences)

max_len = max(len(sent) for sent in X_encoded)
print(max_len)
------
2494</code></pre>
<pre><code class="language-python">print(f&#39;학습 데이터에 있는 문서의 평균 토큰 수: {np.mean([len(sent) for sent in X_train], dtype=int)}&#39;)
----
학습 데이터에 있는 문서의 평균 토큰 수: 238</code></pre>
<p><code>pad_sequences</code>의 파라미터인 <code>maxlen</code>을 평균보다 조금 더 긴 400으로 설정해준다.</p>
<pre><code class="language-python">maxlen_pad = 400

X_train=pad_sequences(X_encoded, maxlen=maxlen_pad, padding=&#39;post&#39;)
y_train=np.array(y_train</code></pre>
<p><strong>8. <code>word2vec</code>의 임베딩 가중치 행렬을 만들어줍니다.</strong>
사전 학습된 모든 단어(300만개)에 대해 만들 경우 너무 행렬이 커지기 때문에 개인 로컬환경에서 실행하기에는 무리가 있으므로 Vocab에 속하는 단어에 대해서만 만들어지도록 한다.</p>
<pre><code class="language-python">embedding_matrix = np.zeros((vocab_size, 300))

print(np.shape(embedding_matrix))
----
(19999, 300)</code></pre>
<pre><code class="language-python">def get_vector(word):
    &quot;&quot;&quot;
    입력 단어가 vocab 에 있는 단어일 경우 임베딩 벡터를 반환

    Args:
        word: 입력 단어 -&gt; str
    &quot;&quot;&quot;
    if word in wv:
        return wv[word]
    else:
        return None</code></pre>
<pre><code class="language-python">for word, i in tokenizer.word_index.items():
    temp = get_vector(word)
    if temp is not None:
        embedding_matrix[i] = temp</code></pre>
<p><strong>9. 신경망을 구성하기 위한 keras 모듈을 불러온 후 학습을 수행한다.</strong></p>
<pre><code class="language-python">from tensorflow.keras.models import Sequential
from tensorflow.keras.layers import Dense, Embedding, Flatten</code></pre>
<pre><code class="language-python">model = Sequential()
model.add(Embedding(vocab_size, 300, weights=[embedding_matrix], input_length=maxlen_pad, trainable=False))
model.add(GlobalAveragePooling1D()) # 입력되는 단어 벡터의 평균을 구하는 함수입니다.
model.add(Dense(1, activation=&#39;sigmoid&#39;))</code></pre>
<p><code>GlobalAveragePooling1D</code>층은 입력되는 행렬의 평균을 구하는 층, 즉 입력되는 단어 벡터의 평균을 구하는 층으로 알아두면 된다.</p>
<pre><code class="language-python">model.compile(loss=&#39;binary_crossentropy&#39;, optimizer=&#39;adam&#39;, metrics=[&#39;acc&#39;])
model.fit(X_train, y_train, batch_size=64, epochs=20, validation_split=0.2)
----
Epoch 1/20
313/313 [==============================] - 7s 18ms/step - loss: 0.6924 - acc: 0.5242 - val_loss: 0.6908 - val_acc: 0.5852
Epoch 2/20
313/313 [==============================] - 5s 17ms/step - loss: 0.6902 - acc: 0.5735 - val_loss: 0.6884 - val_acc: 0.5944
.
.
.
Epoch 19/20
313/313 [==============================] - 5s 17ms/step - loss: 0.6675 - acc: 0.6215 - val_loss: 0.6642 - val_acc: 0.6272
Epoch 20/20
313/313 [==============================] - 5s 18ms/step - loss: 0.6666 - acc: 0.6237 - val_loss: 0.6633 - val_acc: 0.6270
&lt;keras.callbacks.History at 0x7f37c6967c50&gt;</code></pre>
<pre><code class="language-python">test_sentences = [decode_review(idx) for idx in X_test]

X_test_encoded = tokenizer.texts_to_sequences(test_sentences)

X_test=pad_sequences(X_test_encoded, maxlen=400, padding=&#39;post&#39;)
y_test=np.array(y_test)</code></pre>
<pre><code class="language-python">model.evaluate(X_test, y_test)
-------------------------------
782/782 [==============================] - 6s 8ms/step - loss: 0.6679 - acc: 0.6102
[0.6679435968399048, 0.6101999878883362]</code></pre>
<h1 id="회고">회고</h1>
<p>NLP는 어렵고, 이걸 작성하는 지금 처음보는 것처럼 느껴진다. 정말 NLP공부 안했다는걸 다시금 느낀다.</p>
<p><br><br><br></p>
<blockquote>
<p>❗️ <strong>참고자료</strong></p>
</blockquote>
<ol>
<li>코드스테이츠 N422 Lecture Note</li>
</ol>
]]></description>
        </item>
        <item>
            <title><![CDATA[[N421] TIL 및 회고]]></title>
            <link>https://velog.io/@sea_panda/N421-TIL-%EB%B0%8F-%ED%9A%8C%EA%B3%A0</link>
            <guid>https://velog.io/@sea_panda/N421-TIL-%EB%B0%8F-%ED%9A%8C%EA%B3%A0</guid>
            <pubDate>Wed, 18 Jan 2023 13:47:00 GMT</pubDate>
            <description><![CDATA[<blockquote>
<p>❗ 이번 포스트는 화이트모드로 읽을 것.</p>
</blockquote>
<h1 id="0-학습목표">0. 학습목표</h1>
<h2 id="level-1">Level 1.</h2>
<ul>
<li>자연어처리를 통해 할 수 있는 Task에는 어떤 것이 있는지 설명할 수 있다.</li>
<li>토큰화(Tokenization)에 대해 설명할 수 있으며 SpaCy라이브러리를 활용하여 토큰화를 진행할 수 있다.</li>
<li>불용어(Stop words), 어간 추출(Stemming)과 표제어 추출(Lemmatization) 등에 대해 설명할 수 있고 이를 적용하는 코드를 작성할 수 있다.</li>
<li>Bag-of-words에 대해서 설명할 수 있으며 Scikit-learn라이브러리에서 이를 적용할 수 있다.</li>
<li>TF-IDF에서 TF, IDF에 대해서 설명하고 IDF를 적용하는 이유에 대해서 설명할 수있다.<h2 id="level-2">Level 2.</h2>
</li>
<li><strong>N-gram</strong>의 개념에 대해 이해하고 Bag-of-words에 적용해 볼 수 있다.</li>
<li><code>Spacy</code>라이브러리의 다른 기능을 텍스트에 적용하여 분석할 수 있다.<h2 id="level-3">Level 3.</h2>
</li>
<li><strong>LSA(잠재 의미 분석)</strong>에 대해 이해하고 코드로 적용해 볼 수 있다.</li>
</ul>
<hr>
<h1 id="1-자연어처리">1. 자연어처리</h1>
<p><span style="color:orange"><strong>자연어(Natural Language)</strong></span>혹은 <span style="color:orange"><strong>자연 언어</strong></span>는 사람들이 일상적으로 쓰는 언어를 인공적으로 만들어진 언어인 인공어와 구분하여 부르는 개념이다. 쉽게 말해서 <strong>자연적</strong>으로 발생된 <strong>언어</strong>를 자연어라고 한다. </p>
<p>그리고 이런 자연어를 <strong>컴퓨터</strong>로 처리하는 기술을 <span style="color:orange"><strong>자연어 처리(Natural Language Processing, NLP)</strong></span>라고 한다. 이는 넓은 의미로 음성 인식, 자연어 인식, 자연어 생성을 모두 의미한다. 하지만 일반적인 NLP는 다음과 같다.</p>
<ul>
<li>토큰화(Tokenization)</li>
<li>구문 분석(Parsing)</li>
<li>정보 추출(Information extraction)</li>
<li>유사성(Similarity)</li>
<li>음성 인식(Speech recognition)</li>
<li>자연어와 음성 생성 등(Natural language and speech generations and many others)</li>
</ul>
<p>자연어 처리를 비롯한 텍스트 마이닝의 중요한 요소들은 아래 그림과 같다.
<img src="https://velog.velcdn.com/images/sea_panda/post/35039a23-de70-405b-a67e-7145e941f1e6/image.png" alt=""></p>
<p>이런 자연어 처리로 할 수 있는 일들은 다양하다.
<img src="https://velog.velcdn.com/images/sea_panda/post/a4d84e91-2008-4c2f-b0df-ab29df2b7a63/image.png" alt=""></p>
<h2 id="1-자연어-이해nlu-natural-language-understanding">1. 자연어 이해(NLU, Natural Language Understanding)</h2>
<p><span style="color:orange"><strong>NLU</strong></span>란 자연어 표현을 기계가 이해할 수 있는 다른 표현으로 변환시키는 것을 뜻한다. 결국 가장 인간 같은 기계를 만드는 것이 목적이다. 이 자연어 이해를 통하여 구현할 수 있는 Task들은 다음과 같다.</p>
<ul>
<li><p><strong>분류(Classification)</strong>
뉴스 기사 분류, 감성 분석(Positive/Negative)</p>
</li>
<li><p><strong>자연어 추론(NLI, Natural Language Inference)</strong>
&quot;A는 B에게 암살당했다&quot;, 가설 : &quot;A는 죽었다&quot; -&gt; True or False?</p>
</li>
<li><p><strong>기계 독해(MRC, Machine Reading Comprehension), 질의 응답(QA, Question&amp;Answering)</strong>
비문학 문제 풀기</p>
</li>
<li><p><strong><a href="https://excelsior-cjh.tistory.com/71">품사 태깅(POS tagging)</a>, <a href="https://stellarway.tistory.com/29">개체명 인식(Named Entity Recognition)</a></strong></p>
<h2 id="2-자연어-생성nlg-natural-language-generation">2. 자연어 생성(NLG, Natural Language Generation)</h2>
<p><span style="color:orange"><strong>자연어 생성(NLG)</strong></span>과정은 자연어이해의 반대로써 생각하면 된다. 정보를 나타내는 구조를, 원하는 언어로 올바른 String으로 Mapping 시켜야 한다. 그러나 경우에 따라서는 전달할 정보가 어디로부터 얻어지는가를 생각하는 것도 중요한 문제이다. 자연어 생성에 대한 전체 과정은 다음과 같이 세 부분으로 나뉘어 질 수 있다.</p>
</li>
</ul>
<ol>
<li>전달할 정보를 나타내는 구조의 구성: 무엇을 말할 것인가를 결정</li>
<li>문장의 순서를 정하기 위한 대화 구조 및 문장에 대한 규칙을 적용</li>
<li>실제 문장을 생성하기 위하여, 단어에 대한 정보 및 문장론적 규칙을 적용한다.</li>
</ol>
<h2 id="3-nlu--nlg">3. NLU &amp; NLG</h2>
<p>두 가지를 조합하여 사용하면 다음과 같은 다양한 Task를 처리할 수 있다.</p>
<ul>
<li><p><strong>기계 번역(Machine Translation)</strong></p>
</li>
<li><p><strong>요약(Summerization)</strong></p>
<ul>
<li><p>생성 요약(Absractive Summerization)
해당 문서를 요약하는 요약문을 생성 👉 NLG에 가깝다.</p>
</li>
<li><p>추출 요약(Extractive Summerization)
문서 내에서 해당 문서를 가장 잘 요악하는 부분을 찾아냄. 👉 NLU에 가깝다.</p>
</li>
</ul>
</li>
<li><p><strong>Chatbot</strong></p>
<ul>
<li>Open Domain Dialog(ODD): 정해지지 않은 주제를 다루는 일반대화 챗봇</li>
<li>Task Oriented Dialog(TOD): 특정 Task를 처리하기 위한 챗봇<h2 id="4-자연어-처리-용어-정리">4. 자연어 처리 용어 정리</h2>
<h3 id="말뭉치corpus">말뭉치(Corpus)</h3>
자연어 연구를 위해 특정한 목적을 가지고 언어의 표본을 추출한 집합을 의미한다. 컴퓨터의 발달로 말뭉치 분석이 용이해졌으며 분석의 정확성을 위해 해당 자연언어를 형태소 분석하는 경우가 많다. 확률/통계적 기법과 시계열적인 접근으로 전체를 파악한다. 언어의 빈도와 분포를 확인할 수 있는 자료이며, 현대 언어학 연구에 필수적인 자료이다.<h3 id="문장setence">문장(Setence)</h3>
여러 개의 토큰(단어, 형태소 등)으로 구성된 문자열을 의미한다. 생각이나 감정을 말과 글로 표현할 때 완결된 내용을 나타내는 최소의 독립적인 형식단위이다. 마침표, 느낌표 등의 기호로 구분한다.<h3 id="문서document">문서(Document)</h3>
문장(Sentences)들의 집합이다. 기승전결이 완성된 하나의 글을 뜻하는 것이 아니라 그냥 하나의 데이터 단위이며, 형태 상으로는 <span style="color:blue"><strong>문단(paragraph)</strong></span>에 가깝다. 그러므로, <span style="color:blue"><strong>문장(Sentence)을 하나 또는 그 이상 포함</strong></span>한다면 문서로 볼 수 있다.</li>
</ul>
</li>
</ul>
<p>다시 정리하자면 우리가 일반적으로 생각하던 문서는 말뭉치에 가깝다. 믈론 말뭉치는 문서 다발인 경우가 대부분일 것이다. 그리고 문서 내의 문장들이 여기서 말하는 문서가 될 것이다. 예를 들어, 어떤 문서가 100문장으로 되어 있고 문장끼리 특별한 묶음이 없다면 그 문서는 100개의 문서로 구성된 말뭉치가 될 것이다.</p>
<h3 id="어휘집합vocabulary">어휘집합(Vocabulary)</h3>
<p>말뭉치에 있는 모든 문서, 문장을 토큰화한 후 중복을 제거한 토큰의 집합을 의미한다.</p>
<hr>
<h1 id="2-토큰화tokenization">2. 토큰화(Tokenization)</h1>
<p>자연어처리에서 얻은 말뭉치(Corpus) 데이터가 필요에 맞게 전처리되지 않은 상태라면, 해당 데이터를 사용하고자하는 용도에 맞게 <strong>토큰화(tokenization) &amp; 정제(cleaning) &amp; 정규화(normalization)</strong>하는 일을 하게 된다. </p>
<p>주어진 코퍼스(corpus)에서 <strong>토큰(token)</strong>이라 불리는 단위로 나누는 작업을 <strong>토큰화(tokenization)</strong>라고한다. 토큰의 단위가 상황에 따라 다르지만, 보통 의미있는 단위로 토큰을 정의한다.</p>
<h1 id="3-벡터화vectorize">3. 벡터화(Vectorize)</h1>
<p>컴퓨터는 자연어 자체를 받아들일 수 없다. <strong>그래서 컴퓨터가 이해할 수 있도록 벡터로 만들어주어야 한다.</strong> 이 과정을 <span style="color:orange"><strong>벡터화(Vectorize)</strong></span>라고 한다. 벡터화 방식은 자연어 처리 모델의 성능을 결정하는 중요한 역할을 한다.</p>
<p>자연어를 벡터화하는 방법은 크게 2가지로 나눌 수 있다.</p>
<h2 id="count-based-representation횟수-기반-표현">Count-based Representation(횟수 기반 표현)</h2>
<p>단어가 문서(혹은 문장)에 등장하는 횟수를 기반으로 벡터화하는 방법이다.</p>
<ul>
<li>Bag-of-Words(<code>CounterVectorizer</code>)</li>
<li>TF-IDF(<code>TfidfVectorizer</code>)<h2 id="distributed-representation분산-기반-표현">Distributed Representation(분산 기반 표현)</h2>
타겟 단어 주변에 있는 단어를 기반으로 벡터화하는 방법이다. <strong>&quot;비슷한 문맥에서 등장하는 단어들은 비슷한 의미를 가진다&quot;</strong>라는 분포가설 가정 하에 만들어진 표현 방법이다.</li>
</ul>
<p>이 방법으로 표현된 벡터들은 One-Hot Vector처럼 벡터의 차원이 단어 집합의 크기일 필요가 없으므로, 벡터의 차원이 상대적으로 저차원으로 줄어든다.</p>
<ul>
<li>Word2Vec</li>
<li>GloVe</li>
<li>fastText</li>
</ul>
<hr>
<h1 id="4-text-preprocessing텍스트-전처리">4. Text Preprocessing(텍스트 전처리)</h1>
<p>텍스트 데이터를 전처리하는 것은 자연어 처리의 시작이자 절반 이상을 차지하는 중요한 과정이다. 실제 텍스트 데이터를 다룰 때에는 데이터를 읽어보면서 어떤 특이사항이 있는지 파악해야 한다.</p>
<p>횟수 기반의 벡터 표현에서는 전체 말뭉치에 존재하는 단어의 종류가 데이터셋의 Feature, 즉 차원이 된다. 따라서, 단어의 종류(Feature)를 줄여주어야 차원의 저주를 어느 정도 해결할 수 있다. 차원의 저주에 대해서는 <a href="https://velog.io/@sea_panda/N132-TIL-%EB%B0%8F-%ED%9A%8C%EA%B3%A0#4-the-curse-of-dimensionality%EA%B3%A0%EC%B0%A8%EC%9B%90%EC%9D%98-%EC%A0%80%EC%A3%BC">N132</a>에서 다루었으니 확인하자. 정의만 간략하게 설명하면 다음과 같다.
<strong><center>“특성의 개수가 선형적으로 늘어날 때 동일한 설명력을 가지기 위해 필요한 인스턴스의 수는 지수적으로 증가한다. 즉 동일한 개수의 인스턴스를 가지는 데이터셋의 차원이 늘어날수록 설명력이 떨어지게 된다.”</center></strong></p>
<p>차원의 저주를 해결할 전처리 방법은 다음과 같은 것들이 있다.</p>
<blockquote>
<ul>
<li>내장 메서드를 사용한 전처리(<code>lower</code>, <code>replace</code>,...)</li>
</ul>
</blockquote>
<ul>
<li>정규 표현식(Regular expression Regex)</li>
<li>불용어(Stop words) 처리</li>
<li>통계적 트리밍(Trimming)</li>
<li>어간 추출(Stemming) 혹은 표제어 추출(Lemmatization)</li>
</ul>
<p>간단한 예시를 통해 전처리가 어떻게 단어의 수를 줄일 수 있는지 알아보겠다.</p>
<h2 id="내장-메서드-정규표현식을-사용한-전처리">내장 메서드, 정규표현식을 사용한 전처리</h2>
<pre><code class="language-python">import pandas as pd
df = pd.read_csv(&#39;https://ds-lecture-data.s3.ap-northeast-2.amazonaws.com/amazon/Datafiniti_Amazon_Consumer_Reviews_of_Amazon_Products_May19_sample.csv&#39;)

df[&#39;brand&#39;].value_counts()
-------------------------
Amazon          5977
Amazonbasics    4499
AmazonBasics       7
Name: brand, dtype: int64</code></pre>
<p>데이터의 출력을 확인하여 보면 <code>Amazonbasics</code>와 <code>AmazonBasics</code>는 같은 것을 지칭하는 단어임에도 대소문자 차이로 다른 카테고리로 취급되었다. <strong>대소문자를 통일</strong>하여 둘을 같은 범주로 만들어 줄 수 있다.</p>
<pre><code class="language-python">df[&#39;brand&#39;] = df[&#39;brand&#39;].apply(lambda x: x.lower()) # 대문자로 바꾸고 싶을 때는 upper()
df[&#39;brand&#39;].value_counts()
-------------------------------------
amazon          5977
amazonbasics    4506
Name: brand, dtype: int64</code></pre>
<p>다음으로는 <span style="color:orange"><strong>정규표현식</strong></span>이다. 구두점이나 특수문자 등 필요없는 문자가 말뭉치 내에 있을 경우 <strong>토큰화</strong>가 제대로 이루어지지 않는다. 이를 제거하기 위해서 <span style="color:orange"><strong>정규표현식</strong></span>을 사용한다.</p>
<p><code>a-z</code>(소문자), <code>A-Z</code>(대문자), <code>0-9</code>(숫자)를 <code>^</code>제외한 나머지 문자를 <code>regex</code>에 할당한 후, <code>.sub</code>메서드를 통해서 공백 문자열 <code>&quot;&quot;</code>로 치환한다.</p>
<pre><code class="language-python"># 파이썬 정규표현식 패키지 이름은 re 입니다.
import re

# 정규식
# []: [] 사이 문자를 매치, ^: not
regex = r&quot;[^a-zA-Z0-9 ]&quot;
subst = &quot;&quot;

# 정규표현식을 통한 데이터 가공 후 대소문자 통일 -&gt; 공백 문자 기준으로 분리
def tokenize(text):
    &quot;&quot;&quot;text 문자열을 의미있는 단어 단위로 list에 저장합니다.
    Args:
        text (str): 토큰화 할 문자열
    Returns:
        list: 토큰이 저장된 리스트
    &quot;&quot;&quot;
    # 정규식 적용
    tokens = re.sub(regex, subst, text)
    # 소문자로 치환 후 분리
    tokens = tokens.lower().split()
    return tokens</code></pre>
<p>위에서 다룬 아마존 리뷰 데이터 중 <code>reviews.text</code> 열에 <code>tokenize</code> 함수를 적용하여 전처리 해보겠다. 각 리뷰텍스트를 토크나이즈 하여 <code>tokens</code> 칼럼으로 만든다.</p>
<pre><code class="language-python">df[&#39;tokens&#39;] = df[&#39;reviews.text&#39;].apply(tokenize)</code></pre>
<p>이제 결과를 분석해보겠다.</p>
<pre><code class="language-python">from collections import Counter

# Counter 객체는 리스트요소의 값과 요소의 갯수를 카운트 하여 저장하고 있습니다.
# 카운터 객체는 .update 메소드로 계속 업데이트 가능합니다.
word_counts = Counter()

# 토큰화된 각 리뷰 리스트를 카운터 객체에 업데이트 합니다. 
df[&#39;tokens&#39;].apply(lambda x: word_counts.update(x))

# 가장 많이 존재하는 단어 순으로 10개를 나열합니다
word_counts.most_common(10)
----------------------------------------------------------------------
[(&#39;the&#39;, 10514),
 (&#39;and&#39;, 8137),
 (&#39;i&#39;, 7465),
 (&#39;to&#39;, 7150),
 (&#39;for&#39;, 6617),
 (&#39;a&#39;, 6421),
 (&#39;it&#39;, 6096),
 (&#39;my&#39;, 4119),
 (&#39;is&#39;, 4111),
 (&#39;this&#39;, 3752)]</code></pre>
<p>이제 다시 위 코드를 변형하여 말뭉치의 전체 워드 카운트, 랭크 등 정보가 담긴 DataFrame을 반환하는 함수를 구현하고 적용해보겠다. </p>
<pre><code class="language-python">def word_count(docs):
    &quot;&quot;&quot; 토큰화된 문서들을 입력받아 토큰을 카운트 하고 관련된 속성을 가진 데이터프레임을 리턴합니다.
    Args:
        docs (series or list): 토큰화된 문서가 들어있는 list
    Returns:
        list: Dataframe
    &quot;&quot;&quot;
    # 전체 코퍼스에서 단어 빈도 카운트
    word_counts = Counter()

    # 단어가 존재하는 문서의 빈도 카운트, 단어가 한 번 이상 존재하면 +1
    word_in_docs = Counter()

    # 전체 문서의 갯수
    total_docs = len(docs)

    for doc in docs:
        word_counts.update(doc)
        word_in_docs.update(set(doc))

    temp = zip(word_counts.keys(), word_counts.values())

    wc = pd.DataFrame(temp, columns = [&#39;word&#39;, &#39;count&#39;])

    # 단어의 순위
    # method=&#39;first&#39;: 같은 값의 경우 먼저나온 요소를 우선
    wc[&#39;rank&#39;] = wc[&#39;count&#39;].rank(method=&#39;first&#39;, ascending=False)
    total = wc[&#39;count&#39;].sum()

    # 코퍼스 내 단어의 비율
    wc[&#39;percent&#39;] = wc[&#39;count&#39;].apply(lambda x: x / total)

    wc = wc.sort_values(by=&#39;rank&#39;)

    # 누적 비율
    # cumsum() : cumulative sum
    wc[&#39;cul_percent&#39;] = wc[&#39;percent&#39;].cumsum()

    temp2 = zip(word_in_docs.keys(), word_in_docs.values())
    ac = pd.DataFrame(temp2, columns=[&#39;word&#39;, &#39;word_in_docs&#39;])
    wc = ac.merge(wc, on=&#39;word&#39;)

    # 전체 문서 중 존재하는 비율
    wc[&#39;word_in_docs_percent&#39;] = wc[&#39;word_in_docs&#39;].apply(lambda x: x / total_docs)

    return wc.sort_values(by=&#39;rank&#39;)</code></pre>
<pre><code class="language-python">wc = word_count(df[&#39;tokens&#39;])
wc.head()
-----
    word    word_in_docs    count    rank    percent     cul_percent    word_in_docs_percent
51    the                4909    10514    1.0        0.039353    0.039353                0.468282
1    and                5064    8137    2.0        0.030456    0.069809                0.483068
26    i                3781    7465    3.0        0.027941    0.097750                0.360679
123    to                4157    7150    4.0        0.026762    0.124512                0.396547
19    for                4477    6617    5.0        0.024767    0.149278                0.427072</code></pre>
<p><code>cur_percent</code>열을 활용하여 단어의 누적 분포 그래프를 그려보겠다.</p>
<pre><code class="language-python">import seaborn as sns

sns.lineplot(x=&#39;rank&#39;, y=&#39;cul_percent&#39;, data=wc);</code></pre>
<p><img src="https://velog.velcdn.com/images/sea_panda/post/80846132-93a5-4980-9b32-7934eca593c5/image.png" alt=""></p>
<pre><code class="language-python">wc[wc[&#39;rank&#39;] &lt;= 1000][&#39;cul_percent&#39;].max()
--------------------------------------------
0.9097585076280484</code></pre>
<p><code>Squarify</code>라이브러리를 사용하여 등장 비율 상위 20개 단어의 결과를 시각화하여 보겠다.</p>
<pre><code class="language-python"># squarify설치 과정은 스킵
import squarify
import matplotlib.pyplot as plt

wc_top20 = wc[wc[&#39;rank&#39;] &lt;= 20]
squarify.plot(sizes=wc_top20[&#39;percent&#39;], label=wc_top20[&#39;word&#39;], alpha=0.6)
plt.axis(&#39;off&#39;)
plt.show()</code></pre>
<p><img src="https://velog.velcdn.com/images/sea_panda/post/7d2f39b6-68fb-4b28-9652-f2fff5eba5ca/image.png" alt=""></p>
<h2 id="spacy를-사용하여-더욱-쉽게-처리하기">Spacy를 사용하여 더욱 쉽게 처리하기</h2>
<p><code>SpaCy</code>는 문서 구성요소를 다양한 구조에 나누어 저장하지 않고 요소를 색인화하여 검색 정보를 간단히 저장하는 라이브러리이다. 그렇기 때문에 실제 배포 단계에서 기존에 많이 사용되었던 <code>NLTK</code>라이브러리보다 <code>SpaCy</code>가 더 빠르다.</p>
<p><code>SpaCy</code>라이브러리를 사용하여 <strong>토큰화</strong>하는 방법에 대해서 알아보겠다.</p>
<pre><code class="language-python"># 필요한 모듈을 import 합니다
import spacy
from spacy.tokenizer import Tokenizer

nlp = spacy.load(&quot;en_core_web_sm&quot;)
tokenizer = Tokenizer(nlp.vocab)

# 토큰화를 위한 파이프라인을 구성합니다.

tokens = []

for doc in tokenizer.pipe(df[&#39;reviews.text&#39;]):
    doc_tokens = [re.sub(r&quot;[^a-z0-9]&quot;, &quot;&quot;, token.text.lower()) for token in doc]
    tokens.append(doc_tokens)

df[&#39;tokens&#39;] = tokens
df[&#39;tokens&#39;].head()

---------------------------------------------------------------------------
0    [though, i, have, got, it, for, cheap, price, ...
1    [i, purchased, the, 7, for, my, son, when, he,...
2    [great, price, and, great, batteries, i, will,...
3    [great, tablet, for, kids, my, boys, love, the...
4    [they, lasted, really, little, some, of, them,...
Name: tokens, dtype: object</code></pre>
<pre><code class="language-python"># word_count 함수를 사용하여 단어의 분포를 나타내어 봅시다.
wc = word_count(df[&#39;tokens&#39;])
wc.head()
-------------------------------------------------------
    word    word_in_docs    count    rank    percent        cul_percent        word_in_docs_percent
51    the                4909    10514    1.0        0.039229        0.039229                0.468282
1    and                5064    8137    2.0        0.030360        0.069589                0.483068
26    i                3781    7465    3.0        0.027853        0.097442                0.360679
124    to                4157    7150    4.0        0.026678        0.124120                0.396547
19    for                4477    6617    5.0        0.024689        0.148809                0.427072</code></pre>
<p><code>SpaCy</code>로 토큰화 한 문장에 대하여 등장 비율 상위 20개 단어의 결과를 시각화하면 다음과 같다.</p>
<pre><code class="language-python">wc_top20 = wc[wc[&#39;rank&#39;] &lt;= 20]

squarify.plot(sizes=wc_top20[&#39;percent&#39;], label=wc_top20[&#39;word&#39;], alpha=0.6 )
plt.axis(&#39;off&#39;)
plt.show()</code></pre>
<p><img src="https://velog.velcdn.com/images/sea_panda/post/27d4fc3b-e43d-4cb2-a44c-b38e7c92ffa8/image.png" alt=""></p>
<h2 id="불용어stop-words-처리">불용어(Stop words) 처리</h2>
<p>위의 사진을 보면 <code>i</code>,<code>and</code>,<code>of</code>같은 제품 리뷰를 이해하는데 별 도움이 되지 않는 단어들이 높은 등장 비율을 가지고 있는 것을 볼 수 있다. 이런 것들을 <span style='color:orange'><strong>Stop words(불용어)</strong></span>라고 한다. 다시 말하면 자주 등장하지만 분석 하는 것에 있어서 큰 도움이 되지 않는 단어들을 의미한다.</p>
<p>따라서 분석 시 해당 단어를 제외하고 진행한다. 대부분의 NLP 라이브러리는 접속사, 관사, 부사, 대명사, 일반동사 등을 포함한 일반적인 불용어를 내장하고 있다. 다음과 같은 명령어로 불용어를 확인할 수 있다.</p>
<pre><code class="language-python">print(nlp.Defaults.stop_words)</code></pre>
<p>해당 불용어를 제외하고 토크나이징을 진행한 결과는 다음과 같다.</p>
<pre><code class="language-python">tokens = []
# 토큰에서 불용어 제거, 소문자화 하여 업데이트
for doc in tokenizer.pipe(df[&#39;reviews.text&#39;]):
    doc_tokens = []

    # A doc is a sequence of Token()
    for token in doc:
        # 토큰이 불용어와 구두점이 아니면 저장
        if (token.is_stop == False) &amp; (token.is_punct == False):
            doc_tokens.append(token.text.lower())

    tokens.append(doc_tokens)

df[&#39;tokens&#39;] = tokens
df.tokens.head()
-----------------------------------------------------------------
0    [got, cheap, price, black, friday,, fire, grea...
1    [purchased, 7&quot;, son, 1.5, years, old,, broke, ...
2    [great, price, great, batteries!, buying, anyt...
3         [great, tablet, kids, boys, love, tablets!!]
4    [lasted, little.., (some, them), use, batterie...
Name: tokens, dtype: object</code></pre>
<pre><code class="language-python">wc = word_count(df[&#39;tokens&#39;])

wc_top20 = wc[wc[&#39;rank&#39;] &lt;= 20]

squarify.plot(sizes=wc_top20[&#39;percent&#39;], label=wc_top20[&#39;word&#39;], alpha=0.6)
plt.axis(&#39;off&#39;)
plt.show()</code></pre>
<p><img src="https://velog.velcdn.com/images/sea_panda/post/1a80ba82-f3eb-4101-b7e5-0261823ec2ae/image.png" alt=""></p>
<p>불용어들이 모두 제거가 되어 왼전히 다른 단어들이 상위에서 보이는 것을 확인할 수 있다.</p>
<p>그리고 불용어는 사용자가 직접 추가할 수도 있다. <code>union</code>이라는 메서드를 통해서 추가할 수 있다.</p>
<pre><code class="language-python">STOP_WORDS = nlp.Defaults.stop_words.union([&#39;batteries&#39;,&#39;I&#39;, &#39;amazon&#39;, &#39;i&#39;, &#39;Amazon&#39;, &#39;it&#39;, &quot;it&#39;s&quot;, &#39;it.&#39;, &#39;the&#39;, &#39;this&#39;])</code></pre>
<pre><code class="language-python">tokens = []

for doc in tokenizer.pipe(df[&#39;reviews.text&#39;]):

    doc_tokens = []

    for token in doc: 
        if token.text.lower() not in STOP_WORDS:
            doc_tokens.append(token.text.lower())

    tokens.append(doc_tokens)

df[&#39;tokens&#39;] = tokens

wc = word_count(df[&#39;tokens&#39;])
wc.head()
---------------------------------------------------
    word    word_in_docs    count    rank    percent     cul_percent    word_in_docs_percent
58    great            2709     3080     1.0    0.024609    0.024609                0.258418
14    good            1688     1870     2.0    0.014941    0.039549                0.161023
68    tablet            1469     1752     3.0    0.013998    0.053547                0.140132
64    love            1183     1287     4.0    0.010283    0.063830                0.112849
103    bought            1103     1179     5.0    0.009420    0.073250                0.105218</code></pre>
<pre><code class="language-python">wc_top20 = wc[wc[&#39;rank&#39;] &lt;= 20]

squarify.plot(sizes=wc_top20[&#39;percent&#39;], label=wc_top20[&#39;word&#39;], alpha=0.6)
plt.axis(&#39;off&#39;)
plt.show()</code></pre>
<p><img src="https://velog.velcdn.com/images/sea_panda/post/403bb632-8f01-465d-a7bd-1dac2d24c89c/image.png" alt=""></p>
<h2 id="통계적-트리밍trimming">통계적 트리밍(Trimming)</h2>
<p>불용어를 직접 제거하는 대신 통계적인 방법을 통해 말뭉치 내에서 너무 많거나, 너무 적은 토큰을 제거하는 방법도 있다. 단어들의 누적분포 그래프를 다시보면 다음과 같다.
<img src="https://velog.velcdn.com/images/sea_panda/post/61b5aea9-2a15-4c46-b3c3-a25e9afb3b4b/image.png" alt=""></p>
<p>이 그래프에서 알 수 있는 것은 몇몇 소수의 단어들이 전체 말뭉치의 80%를 차지한다는 것이다. 그래프 결과에서 나타나는 단어의 중요도를 다음과 같이 두가지로 해석할 수 있다.</p>
<ol>
<li><strong>자주 나타나는 단어들 (그래프의 왼쪽)</strong>
여러 문서에 두루 나타나기 때문에 문서 분류 단계에서 통찰력을 제공하지 않는다.</li>
<li><strong>자주 나타나지 않는 단어들 (그래프의 오른쪽)</strong>
너무 드물게 나타나기 때문에 큰 의미가 없을 확률이 높다.</li>
</ol>
<p>위의 가정을 바탕으로 랭크가 높거나 낮은 단어들을 제거하여 보겠다. <code>describe()</code>등의 함수로 값을 출력하는 것은 생략하고 그래프를 출력하고 제거하는 코드만 작성하였다.</p>
<pre><code class="language-python"># 문서에 나타나는 빈도
sns.displot(wc[&#39;word_in_docs_percent&#39;],kind=&#39;kde&#39;)</code></pre>
<p><img src="https://velog.velcdn.com/images/sea_panda/post/ebb56685-c9b8-4f30-b1b2-4ba82edb2d9a/image.png" alt=""></p>
<pre><code class="language-python"># 최소한 1% 이상 문서에 나타나는 단어들만 선택합니다.
wc = wc[wc[&#39;word_in_docs_percent&#39;] &gt;= 0.01]
sns.displot(wc[&#39;word_in_docs_percent&#39;], kind=&#39;kde&#39;);</code></pre>
<p><img src="https://velog.velcdn.com/images/sea_panda/post/a06eaa67-d076-402c-bd7b-55fb08fe9419/image.png" alt=""></p>
<h2 id="어간-추출stemming과-표제어-추출lemmatization">어간 추출(Stemming)과 표제어 추출(Lemmatization)</h2>
<p>토큰화된 단어들을 보면 <code>batteries</code>, <code>battery</code>와 같이 <strong>어근(root)</strong>이 동일한 단어를 볼 수 있다. 이런 단어들은 <strong>어간 추출(stemming)</strong>이나 <strong>표제어 추출(lemmatization)</strong>을 통해 정규화(Normalization) 해주어 단어의 수를 줄일 수 있다.</p>
<h3 id="어간-추출stemming">어간 추출(Stemming)</h3>
<p><span style="color:orange"><strong>어간(Stem)</strong></span>이란 단어의 의미가 포함된 부분으로 접사등이 제거된 형태이다. 이는 어근이나 단어의 원형과 같이 않을 수 있다. 예를 들자면 argue, argued, arguing, argus의 어간은 단어들의 뒷 부분이 제거된 argu가 어간이다. 어간 추출은 <code>ing</code>,<code>ed</code>,<code>s</code> 등과 같은 부분을 제거하게 된다.</p>
<p>Stemming 방법에는 Poter, Snowball, Dawson등의 알고리즘이 있다. 하지만 <code>Spacy</code>라이브러리는 Stemming을 제공하지 않고 Lemmatization만 제공한다. 그렇기 때문에 이번에는 <code>nltk</code>를 사용하여 Stemming을 제공하여 보겠다.</p>
<pre><code class="language-python">tokens = []
for doc in df[&#39;tokens&#39;]:
    doc_tokens = []
    for token in doc:
        doc_tokens.append(ps.stem(token))
    tokens.append(doc_tokens)

df[&#39;stems&#39;] = tokens

wc = word_count(df[&#39;stems&#39;])
wc_top20 = wc[wc[&#39;rank&#39;] &lt;= 20]

squarify.plot(sizes=wc_top20[&#39;percent&#39;], label=wc_top20[&#39;word&#39;], alpha=0.6 )
plt.axis(&#39;off&#39;)
plt.show()</code></pre>
<p><img src="https://velog.velcdn.com/images/sea_panda/post/6f9b4573-31a6-47bf-92d4-af48fa33b166/image.png" alt=""></p>
<p>Stemming은 단어의 끝 부분을 자르는 역할을 하기 때문에 사전에 없는 단어가 많이 나오게 된다. 이상하긴 해도 현실적으로 사용하기에 성능이 나쁘지 않다. 알고리즘이 간단하여 속도가 빠르기 때문에 속도가 중요한 검색 분야에서 Stemming을 많이 사용한다.</p>
<h3 id="표제어-추출lemmatization">표제어 추출(Lemmatization)</h3>
<p><span style="color:orange"><strong>표제어 추출(Lemmatization)</strong></span>은 단어들의 기본 사전형 단어 형태인 Lemma(표제어)로 변환된다.</p>
<p>명사의 복수형은 단수형으로, 동사는 모두 타동사로 변환된다. 이렇게 단어들로부터 표제어를 찾아가는 과정은 Stemming보다 많은 연상이 필요하다.</p>
<p><code>Spacy</code>를 통하여 Lemmatization을 진행해 보겠다.</p>
<pre><code class="language-python"># Lemmatization 과정을 함수로 구현
def get_lemmas(text):

    lemmas = []

    doc = nlp(text)

    for token in doc: 
        if ((token.is_stop == False) and (token.is_punct == False)) and (token.pos_ != &#39;PRON&#39;):
            lemmas.append(token.lemma_)

    return lemmas</code></pre>
<p>위 함수를 적용하여 텍스트 데이터 정규화를 진행한다.</p>
<pre><code class="language-python">df[&#39;lemmas&#39;] = df[&#39;reviews.text&#39;].apply(get_lemmas)
df[&#39;lemmas&#39;].head()
------------------------------------------------------
0    [get, cheap, price, black, friday, fire, great...
1    [purchase, 7, son, 1.5, year, old, break, wait...
2    [great, price, great, battery, buy, anytime, n...
3              [great, tablet, kid, boy, love, tablet]
4    [last, little, use, battery, lead, lamp, 2, 4,...
Name: lemmas, dtype: object</code></pre>
<pre><code class="language-python">wc = word_count(df[&#39;lemmas&#39;])
wc_top20 = wc[wc[&#39;rank&#39;] &lt;= 20]

squarify.plot(sizes=wc_top20[&#39;percent&#39;], label=wc_top20[&#39;word&#39;], alpha=0.6 )
plt.axis(&#39;off&#39;)
plt.show()</code></pre>
<p><img src="https://velog.velcdn.com/images/sea_panda/post/04642365-aa53-4617-9548-75006f348611/image.png" alt=""></p>
<h1 id="5-등장-횟수-기반-단어표현count-based-representation">5. 등장 횟수 기반 단어표현(Count-based Representation)</h1>
<p><span style="color:blue"><strong>등장 횟수 기반의 단어표현(Count-based Representation)</strong></span>은 단어가 특정 문서(혹은 문장)에 들어있는 횟수를 바탕으로 해당 문서를 벡터화한다.
<img src="https://velog.velcdn.com/images/sea_panda/post/0a774269-b9b6-4dd9-b229-14365bfd3af5/image.png" alt=""></p>
<p align="center">문서-단어 행렬(Document-Term Matrix, DTM)</p>

<p>벡터화 된 문서는 <span style="color:orange"><strong>문서-단어 행렬(Document-Term Matrix, DTM)</strong></span>의 형태로 나타내어진다. 문서-단어 행렬이란 각 행에는 문서(Document)가, 각 열에는 단어(Term)가 있는 행렬이다. 대표적인 방법으로는 Bag-of-Words(TF, TF-IDF)방식이 있다.</p>
<h2 id="tfterm-frequency">TF(Term Frequency)</h2>
<p><img src="https://velog.velcdn.com/images/sea_panda/post/9096aaa6-3f25-460e-b8ba-50a1290240a1/image.png" alt=""></p>
<p><span style="color:orange"><strong>Bag-of-Words(BoW)</strong></span>는 가장 단순한 벡터화 방법 중 하나이다. 문서(혹은 문장)에서 문법이나 단어의 순서 등을 무시하고 단순히 단어들의 빈도만 고려하여 벡터화한다. 위의 사진처럼 단어를 넣어놓은 가방(Bag of Words)을 두고 각 문장에 어떤 단어가 몇 번 나오는지를 세어서 해당 값을 문장의 벡터로 사용한다. </p>
<p>사이킷런(Scikit-learn, <code>Sklearn</code>) 의 <code>CounterVectorizer</code> 를 사용하면 Bag-of-Words 방식의 벡터화를 사용할 수 있다.</p>
<pre><code class="language-python"># 모듈에서 사용할 라이브러리와 spacy 모델을 불러옵니다.
import pandas as pd
import seaborn as sns
import matplotlib.pyplot as plt

from sklearn.feature_extraction.text import CountVectorizer, TfidfVectorizer
from sklearn.metrics.pairwise import cosine_similarity
from sklearn.neighbors import NearestNeighbors
from sklearn.decomposition import PCA

import spacy
nlp = spacy.load(&quot;en_core_web_sm&quot;)</code></pre>
<pre><code class="language-python"># 예제로 사용할 text를 선언합니다. 
text = &quot;&quot;&quot;In information retrieval, tf–idf or TFIDF, short for term frequency–inverse document frequency, is a numerical statistic that is intended to reflect how important a word is to a document in a collection or corpus.
It is often used as a weighting factor in searches of information retrieval, text mining, and user modeling.
The tf–idf value increases proportionally to the number of times a word appears in the document and is offset by the number of documents in the corpus that contain the word,
which helps to adjust for the fact that some words appear more frequently in general.
tf–idf is one of the most popular term-weighting schemes today.
A survey conducted in 2015 showed that 83% of text-based recommender systems in digital libraries use tf–idf.&quot;&quot;&quot;</code></pre>
<pre><code class="language-python"># spacy의 언어모델을 이용하여 token화된 단어들을 확인합니다. 
doc = nlp(text)
print([token.lemma_ for token in doc if (token.is_stop != True) and (token.is_punct != True)])
---------------
출력결과는 생략하였다. 토큰화된 단어들이 나오게 된다.</code></pre>
<pre><code class="language-python">from sklearn.feature_extraction.text import CountVectorizer

# 문장으로 이루어진 리스트를 저장합니다.
sentences_lst = text.split(&#39;\n&#39;)

# CountVectorizer를 변수에 저장합니다.
vect = CountVectorizer() # Stop_words나, max_features 등의 인자를 설정할 수 있다.

# 어휘 사전을 생성합니다.
vect.fit(sentences_lst)

# text를 DTM(document-term matrix)으로 변환(transform)
dtm_count = vect.transform(sentences_lst)</code></pre>
<p><code>.vocabulary_</code>메서드를 활용하면 vocabulary(모든 토큰)와 맵핑된 인덱스 정보를 확인할 수 있다.</p>
<pre><code class="language-python">vect.vocabulary_
-----------------
{&#39;2015&#39;: 0,
 &#39;83&#39;: 1,
 &#39;adjust&#39;: 2,
 &#39;and&#39;: 3,
 &#39;appear&#39;: 4,
 &#39;appears&#39;: 5,
 &#39;as&#39;: 6,
 &#39;based&#39;: 7,
 &#39;by&#39;: 8,
     :
     :</code></pre>
<p><code>get_feature_names()</code>를 사용하면 추출된 토큰을 볼 수 있으며, <code>get_feature_names()</code>메서드를 사용하면 추출된 토큰의 수를 알 수 있다. 사용 예시는 생략한다.</p>
<p>다음으로 <code>dtm_count</code>타입과 실제 출력을 살펴보겠다.</p>
<pre><code class="language-python"># CountVectorizer 로 제작한 dtm을 분석해 봅시다.
print(type(dtm_count))
print(dtm_count)
----------------------------------------------
&lt;class &#39;scipy.sparse.csr.csr_matrix&#39;&gt;
  (0, 9)    1
  (0, 12)    1
  (0, 14)    2
  (0, 18)    1
  (0, 19)    2
    :    :
    :   :</code></pre>
<p><code>dtm_count</code> 의 타입을 보면 CSR(Compressed Sparse Row matrix) 로 나오게 된다. 해당 타입은 행렬(matrix)에서 0을 표현하지 않는 타입이다. <code>dtm_count</code> 를 출력한 결과에서도 <code>(row, column) count</code> 형태로 출력되는 것을 확인할 수 있다.</p>
<p>만일 그대로의 <code>numpy.matrix</code>타입으로 보고 싶을 경우에는 <code>.todense()</code>메서드를 통해서 확인할 수 있다.</p>
<pre><code class="language-python">print(type(dtm_count))
print(type(dtm_count.todense()))
dtm_count.todense()
----------------------------------
&lt;class &#39;scipy.sparse.csr.csr_matrix&#39;&gt;
&lt;class &#39;numpy.matrix&#39;&gt;
matrix([[0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 1, 0, 2, 0, 0, 0, 1, 2, 0,
         0, 0, 1, 1, 1, 2, 0, 1, 1, 1, 3, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0,
         0, 0, 2, 0, 0, 0, 1, 1, 0, 0, 1, 0, 0, 1, 0, 0, 1, 0, 1, 1, 1,
         0, 0, 2, 0, 0, 0, 0, 0, 0, 0, 1, 0],
        [0, 0, 0, 1, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0,
         0, 0, 0, 0, 0, 1, 0, 1, 0, 0, 1, 1, 0, 1, 1, 0, 0, 0, 0, 1, 0,
         1, 0, 0, 0, 0, 0, 0, 1, 0, 1, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0,
         0, 0, 0, 0, 0, 1, 1, 0, 1, 0, 0, 0],
        [0, 0, 0, 1, 0, 1, 0, 0, 1, 0, 0, 1, 1, 0, 1, 1, 0, 0, 0, 0, 0,
         0, 0, 0, 1, 0, 2, 1, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 2, 0, 2, 1,
         0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 1,
         6, 1, 1, 0, 0, 0, 0, 1, 0, 0, 2, 0],
        [0, 0, 1, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 1, 0, 1,
         1, 1, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0,
         0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 1,
         1, 0, 1, 0, 0, 0, 0, 0, 0, 1, 0, 1],
        [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
         0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 1, 0, 0, 1, 0,
         0, 1, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 1, 0, 1, 0, 0,
         1, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0],
        [1, 1, 0, 0, 0, 0, 0, 1, 0, 0, 1, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0,
         0, 0, 0, 1, 0, 2, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 1, 0,
         0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 1, 0, 0, 1, 1, 0, 1, 1, 0, 1,
         0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0]])</code></pre>
<p>DataFrame으로 변환하여 확인하고 싶을 경우에는 다음과 같이 코드를 구성할 수 있다.</p>
<pre><code class="language-python">dtm_count = pd.DataFrame(dtm_count.todense(), columns=vect.get_feature_names())</code></pre>
<h2 id="tf-idf-term-frequency---inverse-document-frequency">TF-IDF (Term Frequency - Inverse Document Frequency)</h2>
<p>다른 문서에 등장하지 않는 단어. 즉, 특정 문서에만 등장하는 단어에 가중치를 두는 방법이 <span style="color:orange"><strong>TF-IDF(Term Frequency-Inverse Document Frequency)</strong></span>이다.</p>
<p>수식은 다음과 같다.
$$$
TF -IDF(w) = TF(w)\times IDF(w)\
$$$
TF(Term-Frequency)는 특정 문서에서 단어 w가 쓰인 빈도이다. 분석할 문서에서 단어$w$가 등장하는 횟수를 구하게 된다.
$$$
TF(w) = {특정,,문서,,내,,단어,,w의,수\over특정,,문서,,내,,단어,,수}
$$$
IDF(Inverse Document Frequency)는 분류 대상이 되는 모든 문서의 수를 단어 $w$가 들어있는 문서의 수로 나누어 준 뒤 로그를 취해준 값이다. 로그를 취해주는 이유는 <span style="color:orange"><strong><a href="https://medium.com/@uxdaysseoul/%EC%A7%80%ED%94%84%EC%9D%98-%EB%B2%95%EC%B9%99-zipfs-law-6b6590e9618c">지프의 법칙</a></strong></span>에 대해서 찾아보자.
$$$
IDF(w)=log({분류,,대상이,,되는,,모든,,문서의,,수\over단어,,w가,,들어있는,,문서의,,수})
$$$
이론적인 식은 위와 같지만 실제 계산에서는 0으로 나누어 주는 것을 방지하구 위하여 분모에 1을 더해준 값을 사용한다. 분류 대상이 되는 모든 문서의 수($n$단어 $w$가 들어있는 문서의 수)를 $df(w)$라 하면 IDF는 다음과 같이 구해진다.
$$$
IDF(w) = log({n\over 1+df(w)})
$$$
위 식에 따라 자주 사용하는 단어라도, 많은 문서에 나오는 단어들은 IDF가 낮아기 때문에 TF-IDF로 벡터화 했을 때 작은 값을 가지게 된다.</p>
<p>사이킷런(Scikit-learn, <code>Sklearn</code>) 의 <code>TfidfVectorizer</code>를 사용하면 TF-IDF벡터화도 사용할 수 있다.</p>
<pre><code class="language-python"># TF-IDF vectorizer. 테이블을 작게 만들기 위해 max_features=15로 제한하였습니다.
tfidf = TfidfVectorizer(stop_words=&#39;english&#39;, max_features=15)

# Fit 후 dtm을 만듭니다.(문서, 단어마다 tf-idf 값을 계산합니다)
dtm_tfidf = tfidf.fit_transform(sentences_lst)

dtm_tfidf = pd.DataFrame(dtm_tfidf.todense(), columns=tfidf.get_feature_names())
dtm_tfidf</code></pre>
<p><img src="https://velog.velcdn.com/images/sea_panda/post/42e17b0d-12e1-4e86-8e9b-63ac8b24ff6a/image.png" alt=""></p>
<p><code>TfidfVectorizer</code>를 사용하여 생성한 문서-단어 행렬(DTM)의 값을 <code>CountVectorizer</code>를 사용하여 생성한 DTM의 값과 비교하여 보자.</p>
<pre><code class="language-python">vect = CountVectorizer(stop_words=&#39;english&#39;, max_features=15)
dtm_count_vs_tfidf = vect.fit_transform(sentences_lst)
dtm_count_vs_tfidf = pd.DataFrame(dtm_count_vs_tfidf.todense(), columns=vect.get_feature_names())
dtm_count_vs_tfidf</code></pre>
<p><img src="https://velog.velcdn.com/images/sea_panda/post/6ccfbdd5-18f3-4c56-a6e0-3b96da36a31c/image.png" alt=""></p>
<p>이번에는 하이퍼파라미터를 튜닝해보고, SpaCy tokenizer를 사용해서 벡터화를 진행하여 보겠다.</p>
<pre><code class="language-python"># SpaCy 를 이용한 Tokenizing

def tokenize(document):
    doc = nlp(document)
    return [token.lemma_.strip() for token in doc if (token.is_stop != True) and (token.is_punct != True) and (token.is_alpha == True)]

    &quot;&quot;&quot;
    args:
        ngram_range = (min_n, max_n), min_n 개~ max_n 개를 갖는 n-gram(n개의 연속적인 토큰)을 토큰으로 사용합니다.
        min_df = n : int, 최소 n개의 문서에 나타나는 토큰만 사용합니다.
        max_df = m : float(0~1), m * 100% 이상 문서에 나타나는 토큰은 제거합니다.
    &quot;&quot;&quot;
tfidf_tuned = TfidfVectorizer(stop_words=&#39;english&#39;
                        ,tokenizer=tokenize
                        ,ngram_range=(1,2)
                        ,max_df=.7
                        ,min_df=3
                       )

dtm_tfidf_tuned = tfidf_tuned.fit_transform(df[&#39;reviews.text&#39;])
dtm_tfidf_tuned = pd.DataFrame(dtm_tfidf_tuned.todense(), columns=tfidf_tuned.get_feature_names())
dtm_tfidf_tuned.head()</code></pre>
<p><img src="https://velog.velcdn.com/images/sea_panda/post/26a3d469-77f6-4d31-ac57-7319deede018/image.png" alt=""></p>
<h2 id="유사도를-이용한-문서-검색">유사도를 이용한 문서 검색</h2>
<p><img src="https://velog.velcdn.com/images/sea_panda/post/8b54751d-cc96-489e-9c2b-ed20050cd776/image.png" alt=""></p>
<p>검색 엔진은 검색어(Query,쿼리)와 문서에 있는 단어(key,키)를 매칭(Matching)하여 결과를 보여준다. 매칭 방법에는 방법은 여러 가지가 있으나 이번에는 가장 클래식한 방법인 <strong>&quot;유사도 측정 방법&quot;</strong>을 시도해 보겠다.</p>
<h3 id="코사인-유사도cosine-similarity">코사인 유사도(Cosine Similarity)</h3>
<p><img src="https://velog.velcdn.com/images/sea_panda/post/0fefe5b3-81db-4a2b-917c-1d27674a2ed7/image.png" alt=""></p>
<p>코사인 유사도는 가장 많이 쓰이는 유사도 측정방법이다. 두 벡터가 이루는 각의 코사인 값을 이용하여 구할 수 있는 유사도 이다.</p>
<blockquote>
<ul>
<li>완전히 같을 경우: 1</li>
</ul>
</blockquote>
<ul>
<li>90도의 각을 이룰 경우: 0</li>
<li>완전히 반대방향일 경우: -1</li>
</ul>
<h3 id="nearestneighbork-nnk-최근접-이웃">NearestNeighbor(K-NN,K-최근접 이웃)</h3>
<p>K-최근접 이웃법은 쿼리와 가장 가까운 상위 K개의 근접한 데이터를 찾아서 K개 데이터의 유사성을 기반으로 <strong>점을 추정하거나 분류</strong>하는 예측 분석에 사용된다. 사이킷런 <code>sklearn</code>의 <code>NearestNeighbors</code>를 사용하면 K-최근접 이웃 알고리즘을 사용할 수 있다.</p>
<pre><code class="language-python">from sklearn.neighbors import NearestNeighbors

# dtm을 사용히 NN 모델을 학습시킵니다. (디폴트)최근접 5 이웃.
nn = NearestNeighbors(n_neighbors=5, algorithm=&#39;kd_tree&#39;)
nn.fit(dtm_tfidf_amazon)

# 2번째 인덱스에 해당하는 문서와 가장 가까운 문서(0포함) 5개의 거리와 문서의 인덱스 출력
nn.kneighbors([dtm_tfidf_amazon.iloc[2]])
--------------------------------------------
(array([[0.        , 0.64660432, 0.73047367, 0.76161463, 0.76161463]]),
 array([[   2, 7278, 6021, 1528, 4947]]))</code></pre>
<pre><code class="language-python">print(df[&#39;reviews.text&#39;][2][:300])
print(df[&#39;reviews.text&#39;][7278][:300])
-------------------------------------------------
Great price and great batteries! I will keep on buying these anytime I need more!
Always need batteries and these come at a great price</code></pre>
<h3 id="문서-검색-예제">문서 검색 예제</h3>
<p>Amazon Review의 Sample을 가져와서 문서검색에 사용하여 보겠다.</p>
<pre><code class="language-python"># 출처 : https://www.amazon.com/Samples/product-reviews/B000001HZ8?reviewerType=all_reviews
sample_review = [&quot;&quot;&quot;in 1989, I managed a crummy bicycle shop, &quot;Full Cycle&quot; in Boulder, Colorado.
The Samples had just recorded this album and they played most nights, at &quot;Tulagi&#39;s&quot; - a bar on 13th street.
They told me they had been so broke and hungry, that they lived on the free samples at the local supermarkets - thus, the name.
i used to fix their bikes for free, and even feed them, but they won&#39;t remember.
That Sean Kelly is a gifted songwriter and singer.&quot;&quot;&quot;]</code></pre>
<p>학습된 <code>TfidfVectorizer</code>를 통해 Sample Review를 변환하여 보겠다.</p>
<pre><code class="language-python">new = tfidf_vect.transform(sample_review)
nn.kneighbors(new.todense())
-------------------------------------------
(array([[0.69016304, 0.81838594, 0.83745037, 0.85257729, 0.85257729]]),
 array([[10035,  2770,  1882,  9373,  3468]]))</code></pre>
<pre><code class="language-python"># 가장 가깝게 나온 문서를 확인합니다.
df[&#39;reviews.text&#39;][10035]
-------------------------------
&quot;Doesn&#39;t get easier than this. Good products shipped to my office free, in two days:)&quot;</code></pre>
<h1 id="회고">회고</h1>
<p>어렵다. 너무 어렵다. 다루는 코드도 많아지고 방식들도 다양해지다보니까 머리에 잘 들어오지 않는 것 같다. 설상가상으로 코로나도 걸려서 집중도 잘 안되는 것 같다. 빨리 정신차리고 복습해야하는데 그게 잘 안되는 것 같다. 코로나로 몸이 힘든 것도 맞는데 좋은 핑계가 생겨서 게임하고, 유튜브보고 딴 짓을 너무 많이 하는 것 같다. 정신차리자.... 설날에 공부 좀 해야겠다.
<br><br><br></p>
<blockquote>
<p>❗ <strong>참고자료</strong></p>
</blockquote>
<ol>
<li><a href="http://www.aistudy.co.kr/linguistics/natural/natural_language_generation.htm">자연어 생성</a></li>
<li><a href="https://ko.wikipedia.org/wiki/%EB%A7%90%EB%AD%89%EC%B9%98">말뭉치</a></li>
<li><a href="https://swjman.tistory.com/125">문서</a></li>
<li><a href="https://excelsior-cjh.tistory.com/71">품사 태깅(POS tagging)</a></li>
<li><a href="https://stellarway.tistory.com/29">개체명 인식(Named Entity Recognition)</a></li>
<li><a href="https://wikidocs.net/21698">토큰화(Tokenization)</a></li>
</ol>
]]></description>
        </item>
        <item>
            <title><![CDATA[[N414] TIL 및 회고]]></title>
            <link>https://velog.io/@sea_panda/N414-TIL-%EB%B0%8F-%ED%9A%8C%EA%B3%A0-epbhu0s8</link>
            <guid>https://velog.io/@sea_panda/N414-TIL-%EB%B0%8F-%ED%9A%8C%EA%B3%A0-epbhu0s8</guid>
            <pubDate>Sun, 15 Jan 2023 13:31:33 GMT</pubDate>
            <description><![CDATA[<h1 id="0-학습목표">0. 학습목표</h1>
<h2 id="level-1">Level 1.</h2>
<ul>
<li>신경망에 <strong>교차 검증(Cross-Validation)</strong>을 적용할 수 있다.</li>
<li>하이퍼파라미터 탐색범 중 Grid 탐색법과 Random 탐색법에 대해 말하고 둘을 비교하여 설명할 수 있다.<h2 id="level-2">Level 2.</h2>
</li>
<li><strong>신경망 주요 용어에 대해 한 줄 이상으로 설명</strong>할 수 있다.<h2 id="level-3">Level 3.</h2>
</li>
<li>실험 계획 라이브러리인 WandB의 사용법을 익히고 Keras를 엮어서 사용해 볼 수 있다.<h1 id="1-교차-검증cross-validation">1. 교차 검증(Cross-Validation)</h1>
Section2의 <a href="https://velog.io/@sea_panda/N213-TIL-%EB%B0%8F-%ED%9A%8C%EA%B3%A0#4-%EA%B5%90%EC%B0%A8%EA%B2%80%EC%A6%9Dcv">N213</a>에서 교차 검증을 다루었다. 교차 검증을 실행할 경우 특정 데이터 세트에 대한 과적합을 방지할 수 있고, 더욱 <strong>일반화</strong>된 모델을 생성할 수 있다는 장점이 있다. 또한 데이터 세트의 규모가 적을 시 과소적합을 방지할 수도 있다. 하지만 교차 검증을 시행함에 따라서 모델 훈련 및 평가 소요시간이 증가한다는 단점이 있다.</li>
</ul>
<p>보스턴 집값 실습예제를 통해서 교차 검증이 실제 신경망에서 어떤 식으로 사용되는지 확인하여 보도록 한다.</p>
<pre><code class="language-python"># 필요한 라이브러리를 import한다.
from tensorflow.keras.datasets import boston_housing
from sklearn.model_selection import KFold, StratifiedKFold
from tensorflow.keras.preprocessing.image import ImageDataGenerator

import numpy as np
import pandas as pd
import tensorflow as tf
import os

# 데이터셋을 불러온다.
(x_train, y_train), (x_test, y_test) = boston_housing.load_data()

# 2가지의 Fold 방법
kf = KFold(n_splits = 5)
skf = StratifiedKFold(n_splits = 5, random_state = 42, shuffle = True) </code></pre>
<p>위의 코드에서 2가지 Fold방법이 등장한다. 각각의 차이는 다음과 같다.
<img src="https://velog.velcdn.com/images/sea_panda/post/e44530b6-3178-4860-9d61-65d4f0fb268b/image.png" alt=""></p>
<ul>
<li><p><strong><code>KFold</code></strong>: K개의 학습데이터 세트를 <strong>일정한 간격으로 나누어</strong> 평가 진행, 학습/검증 데이터 셋 나누어 진행</p>
</li>
<li><p><strong><code>StratifiedKFold</code></strong>: 불균형한 label비율을 가진 데이터 세트에 적용하는 Fold방법으로 label의 분포 비율을 그대로 유지하여 학습/검증 데이터를 나눈다. 따라서 <code>split()</code>메서드에 피처뿐만 아니라 label데이터 세트도 넣어주어야 한다.</p>
</li>
</ul>
<p>계속해서 예제를 진행한다.</p>
<pre><code class="language-python">x_train = pd.DataFrame(x_train)
y_train = pd.DataFrame(y_train)

# 모델 생성
from tensorflow.keras.models import Sequential
from tensorflow.keras.layers import Dense

model = Sequential()
model.add(Dense(64, activation=&#39;relu&#39;))
model.add(Dense(64, activation=&#39;relu&#39;))
model.add(Dense(1))

# 교차검증을 이용한 평과결과 출력
for train_index, val_index in kf.split(np.zeros(x_train.shape[0]),y_train):
    training_data = x_train.iloc[train_index, :]
    training_data_label = y_train.iloc[train_index]
    validation_data = x_train.iloc[val_index, :]
    validation_data_label = y_train.iloc[val_index]

    # compile
    model.compile(loss=&#39;mean_squared_error&#39;, optimizer=&#39;adam&#39;)

    # 모델 훈련
    model.fit(training_data, training_data_label,
                  epochs=10,
                  batch_size=32,
                  validation_data = (validation_data, validation_data_label),
                  )

    # 모델 평가
    results = model.evaluate(x_test, y_test, batch_size=32)
    print(&quot;test loss, test mse:&quot;, results)</code></pre>
<h1 id="2-하이퍼-파라미터-튜닝">2. 하이퍼 파라미터 튜닝</h1>
<p>하이퍼 파라미터란 모델링 시 <strong>사용자가 직접</strong>세팅하는 값을 뜻한다. 학습률, epoch, batch size, 각 층의 node 수, 은닉 층의 수 등을 하이퍼 파라미터라고 할 수 있다.</p>
<p>그리고 이런 하이퍼 파라미터 튜닝에는 다양한 것이 존재한다.
<img src="https://velog.velcdn.com/images/sea_panda/post/154d3306-08d5-4da8-89d0-17b159428bd9/image.png" alt=""></p>
<p>위 사진 외에도 정말 다양한 하이퍼 파리미터 튜닝 방식이 존재하며 manual Search방법을 제외한 나머지 방법을 <strong>Automated hyperparameter selection</strong>이라고 한다.</p>
<h2 id="1-babysitting-or-grad-student-descent">1. Babysitting or Grad Student Descent</h2>
<p>프로젝트나 스프린트에서 모델의 성능을 높이기 위해 여러 숫자를 직접 넣어보며 하이퍼 파라미터를 조정하였다. 이 방식을 육아(Babysitting) 혹은 대학원생 갈아넣기(Grad student descent)라고 한다. </p>
<p>100% <span style="color:orange"><strong>수작업(manual)</strong></span>으로 파라미터를 수정하는 방법으로 학계에서 논문을 출간할 수 있을 정도로 놀라운 정확도를 보여주는 하이퍼파라미터 수치를 찾아내기 위해 쓰는 방법이다.</p>
<h2 id="2-grid-search">2. Grid Search</h2>
<p>위의 방식을 자동화한 방법이 바로 <span style="color:orange"><strong>Grid Search</strong></span>이다. 이 방법에서는 하이퍼파라미터마다 탐색할 지점을 정해주면 모든 지점에 해당하는 조합을 알아서 수행한다. </p>
<p>자동으로 수행되는만큼 프로그램을 돌려놓기만 하면 끝이다. 하지만 범위를 너무 많이 설정하게 되면 프로그램이 끝날 줄 모르고 계속해서 연산을 수행할 수 있다.</p>
<p>그렇기 때문에 이 방법으로 많은 하이퍼 파라미터를 찾으려고 하는 것 보다는 1개, 혹은 최대 2개 정도의 파라미터 최적값을 찾는 용도로 적합하다. 모델 성능에 보다 직접적인 영향을 주는 하이퍼파라미터가 따로 있기 때문에 굳이 많은 하이퍼 파리미터 조합을 시도할 필요는 없다.</p>
<p>높은 영향을 주는 하이퍼 파라미터만 제대로 튜닝해서 최적값을 찾은 후 나머지 하이퍼 파라미터도 조정해 나가면 못해도 90% 이상의 성능을 확보할 수 있다.</p>
<p>당뇨병 데이터셋을 신경망에 적용해보고 배치 사이즈를 여러 개로 조정하면서 최적의 배치 사이즈와 은닉층의 노드 개수를 찾아보겠다.</p>
<p>인공 신경망 모델을 Scikit-learn에서 사용하기 위해 Wrapping을 해주어야 한다.
Wrapping하는 방법으로 <code>scikeras</code>를 사용해보겠다.</p>
<pre><code class="language-python">import numpy
import pandas as pd
from sklearn.model_selection import GridSearchCV
from tensorflow.keras.models import Sequential
from tensorflow.keras.layers import Dense

# scikeras는 별도 설치 필요하나 설치과정 생략.
from scikeras.wrappers import KerasClassifier

#재현성을 위한 랜덤시드 고정
numpy.random.seed(42)

# 데이터셋 불러오기
url =&quot;https://raw.githubusercontent.com/jbrownlee/Datasets/master/pima-indians-diabetes.data.csv&quot;

dataset = pd.read_csv(url, header=None).values

# Feature와 Label분리
X = dataset[:,0:8]
Y = dataset[:,8]</code></pre>
<p>이후 모델을 제작할 때는 <strong><code>KerasClassifier</code></strong>로 Wrapping하기 위하여 신경망 모델을 <strong>함수</strong> 형태로 정의한다. 그리고 이때 최초 노드의 개수를 정해주어야 정상 작동한다.</p>
<pre><code class="language-python"># 모델제작
def create_model(nodes=8):
    model = Sequential()
    model.add(Dense(nodes, input_dim=8, activation=&#39;relu&#39;))
    model.add(Dense(nodes, activation=&#39;relu&#39;))
    model.add(Dense(1, activation=&#39;sigmoid&#39;))

    model.compile(loss=&#39;binary_crossentropy&#39;, optimizer=&#39;adam&#39;, metrics=[&#39;accuracy&#39;])
    return model

# Wrapping
model = KerasClassifier(model=create_model, batch_size=8, verbose=False)

# 하이퍼파라미터 탐색
nodes = [16, 32, 64]
batch_size = [16, 32, 64]
param_grid = dict(model__nodes=nodes, batch_size=batch_size)

grid = GridSearchCV(estimator=model, param_grid=param_grid, n_jobs=1, cv=3)
grid_result = grid.fit(X, Y)

# 최적의 결과를 낸 하이퍼파라미터와 각각의 결과 출력
print(f&quot;Best: {grid_result.best_score_} using {grid_result.best_params_}&quot;)

means = grid_result.cv_results_[&#39;mean_test_score&#39;]
stds = grid_result.cv_results_[&#39;std_test_score&#39;]
params = grid_result.cv_results_[&#39;params&#39;]

for mean, stdev, param in zip(means, stds, params):
    print(f&quot;Means: {mean}, Stdev: {stdev} with: {param}&quot;) </code></pre>
<h2 id="3-random-search">3. Random Search</h2>
<p><span style="color:orange"><strong>Random Search</strong></span>는 무한 루프는 Grid Search의 단점을 해결하기 위해 나온 방법이다. Random Search는 지정된 범위 내에서 무작위로 모델을 돌려본 후 최고 성능의 모델을 반환한다. 시도 횟수를 정해줄 수 있기 때문에 Grid Search에 비해서 훨씬 적은 횟수로도 끝마칠 수 있다. </p>
<p>Grid Search에서는 하이퍼파라미터의 중요도가 모두 동등하다고 가정한다. 하지만 실제로는 더 중요한 하이퍼파라미터가 있다. <strong>Radom Search</strong>는 상대적으로 중요한 하이퍼파라미터에 대해서는 탐색을 더 하고, 덜 중요한 하이퍼파라미터에 대해서는 실험을 덜 하도록 한다. 하지만 Random Search는 절대적으로 완벽한 하이퍼파라미터를 찾아주지는 않는다.</p>
<p><img src="https://velog.velcdn.com/images/sea_panda/post/d9301740-15fd-4b34-abba-ef607d21a254/image.png" alt=""></p>
<h2 id="4-bayesian-methods">4. Bayesian Methods</h2>
<p><span style="color:orange"><strong>베이지안 방식(Bayesian Method)</strong></span>은 이전 탐색 결과 정보를 새로운 탐색에 활용하는 방법이다. 그렇기 때문에 베이지안 방법을 사용하면 하이퍼파라미터 탐색 효율을 높일 수 있다. </p>
<p><code>bayes_opt</code>나 <code>hyperopt</code>와 같은 패키지를 사용하면 베이지안 방식을 적용할 수 있다.</p>
<p>더 정확한 베이지안 정리에 대한 설명은 <a href="https://velog.io/@sea_panda/N121-TIL-%EB%B0%8F-%ED%9A%8C%EA%B3%A0#4-bayesian-theorem%EB%B2%A0%EC%9D%B4%EC%A7%80%EC%95%88-%EC%A0%95%EB%A6%AC">N121</a>을 참고하자.</p>
<h1 id="3-keras-tuner">3. Keras Tuner</h1>
<p><span style="color:orange"><strong>Keras Tuner</strong></span>는 케라스 프레임워크에서 하이퍼파라미터를 튜닝하는 데 도움이 되는 라이브러리이다. </p>
<p>Fashion MNIST예제에 Keras Tuner를 적용하여 하이퍼파라미터 튜닝을 수행하여 보겠다.</p>
<pre><code class="language-python">from tensorflow import keras
from tensorflow.keras.layers import Dense, Flatten

import tensorflow as tf
import IPython

# Keras Tuner Import하기. 설치과정은 생략함.
import keras_tuner as kt

# 데이터 불러오기 및 정규화(Normalizing)
(X_train, y_train), (X_test, y_test) = keras.datasets.fashion_mnist.load_data()
X_train = X_train.astype(&#39;float32&#39;) / 255.0
X_test = X_test.astype(&#39;float32&#39;) / 255.0</code></pre>
<p>다음으로는 모델을 제작하고 탐색할 하이퍼파라미터 범위와 지점을 정의한다. 이 과정에서 Model builder함수(<code>model_builder</code>)를 지정하는 과정이 필요하다. 먼저 <code>model_builder</code>라는 함수를 정의하고 해당 함수 내부에서 모델 설계와 하이퍼파라미터 튜닝까지 모두 수행해보겠다.</p>
<pre><code class="language-python">def model_builder(hp):
  model = keras.Sequential()
  model.add(Flatten(input_shape=(28, 28)))

  hp_units = hp.Int(&#39;units&#39;, min_value = 32, max_value = 512, step = 32)
  model.add(Dense(units = hp_units, activation = &#39;relu&#39;))
  model.add(Dense(10, activation=&#39;softmax&#39;))

  hp_learning_rate = hp.Choice(&#39;learning_rate&#39;, values = [1e-2, 1e-3, 1e-4]) 

  model.compile(optimizer = keras.optimizers.Adam(learning_rate = hp_learning_rate),
                loss = keras.losses.SparseCategoricalCrossentropy(), 
                metrics = [&#39;accuracy&#39;])

  return model</code></pre>
<p>다음으로는 하이퍼파라미터 튜닝을 수행할 튜너(Tuner)를 지정한다. Keras Tuner에서는 <strong>Random Search, Bayesian Optimization, Hyperband</strong>등의 최적화 방법을 수행할 수 있다.</p>
<p>이번에는 <code>Hyperband</code>를 통해서 튜닝을 수행해보도록 하겠다. Hyperband사용 시 Model builder function(<code>model_builder</code>), 훈련할 최대 epochs 수(<code>max_epochs</code>)등을 지정해주어야 한다. Hyperband 는 리소스를 알아서 조절하고 조기 종료(Early-stopping) 기능을 사용하여 높은 성능을 보이는 조합을 신속하게 통합한다는 장점을 가지고 있다.</p>
<pre><code class="language-python">tuner = kt.Hyperband(model_builder,
                     objective = &#39;val_accuracy&#39;, 
                     max_epochs = 10,
                     factor = 3,
                     directory = &#39;my_dir&#39;,
                     project_name = &#39;intro_to_kt&#39;)  </code></pre>
<p>하이퍼 파라미터 탐색을 실행하기 전에 학습이 끝날 때마다 이전 출력이 지워지도록 콜백 함수를 정의한다.</p>
<pre><code class="language-python">class ClearTrainingOutput(tf.keras.callbacks.Callback):
  def on_train_end(*args, **kwargs):
    IPython.display.clear_output(wait = True)</code></pre>
<p>이제 하이퍼 파라미터 탐색을 수행한다.</p>
<pre><code class="language-python">tuner.search(X_train, y_train, epochs = 10, validation_data = (X_test, y_test), callbacks = [ClearTrainingOutput()])

best_hps = tuner.get_best_hyperparameters(num_trials = 1)[0]

print(f&quot;&quot;&quot;
하이퍼 파라미터 검색이 완료되었습니다. 
최적화된 첫 번째 Dense 노드 수는 {best_hps.get(&#39;units&#39;)} 입니다.
최적의 학습 속도는 {best_hps.get(&#39;learning_rate&#39;)} 입니다.
&quot;&quot;&quot;)</code></pre>
<p>최고 성능을 보이는 하이퍼파라미터 조합으로 다시 학습을 진행해보겠다.</p>
<pre><code class="language-python">model = tuner.hypermodel.build(best_hps)
model.fit(X_train, y_train, epochs = 10, validation_data = (X_test, y_test))</code></pre>
]]></description>
        </item>
        <item>
            <title><![CDATA[[N413] TIL 및 회고]]></title>
            <link>https://velog.io/@sea_panda/N413-TIL-%EB%B0%8F-%ED%9A%8C%EA%B3%A0</link>
            <guid>https://velog.io/@sea_panda/N413-TIL-%EB%B0%8F-%ED%9A%8C%EA%B3%A0</guid>
            <pubDate>Sat, 14 Jan 2023 13:32:23 GMT</pubDate>
            <description><![CDATA[<h1 id="0-학습목표">0. 학습목표</h1>
<h2 id="level-1">Level 1.</h2>
<ul>
<li><strong>학습률(Learning rate)</strong>의 개념과 학습률이 너무 크거나 작은 경우 발생하는 문제에 대해 설명할 수 있다.</li>
<li>활성화 함수에 맞는 <strong>가중치 초기화(Weight Initialization)</strong>을 매칭할 수 있다.</li>
<li>신경망에 적용할 수 있는 <strong>과적합(Overfittion)을 방지할 수 있는 방법(Weight Decay, Dropout, Early stopping)</strong>의 개념에 대해 설명할 수 있고 이를 Keras로 적용할 수 있다.<h2 id="level-2">Level 2.</h2>
</li>
<li>지난 강의에서 배운 내용 외에 해당하는 Optimizer의 특징에 대해 개략적으로 설명할 수 있다.</li>
<li>Dropout의 효과와 Evaluation단계에서 Dropout이 어떻게 적용되는지 설명할 수 있다.<h2 id="level-3">Level 3.</h2>
</li>
<li><strong>배치 정규화(Batch Normalization)</strong>를 이해하고 이를 Keras 코드로 신경망에 적용할 수 있다.</li>
</ul>
<h1 id="1-학습률-감소-or-계획법learing-rate-decay-or-scheduling">1. 학습률 감소 or 계획법(Learing rate Decay or Scheduling)</h1>
<p><span style ="color:orange"><strong>학습률(Learing rate,<code>lr</code>)</strong></span>이란 매 가중치에 대해 구해진 기울기 값을 얼마나 경사 하강법에 적용할지를 결정하는 <code>하이퍼 파라미터</code>이다.</p>
<p>경사하강법이 산긴을 내려가는 과정을 의미한다면, 학습률은 <strong>보폭을 결정</strong>하게 된다. 따라서 학습률이 크면 Iteration마다 값이 크게 변하게 되고, 작으면 조금씩 이동하게 된다.
<img src="https://velog.velcdn.com/images/sea_panda/post/a5b125c2-e48f-417c-b917-cfcae1bbec23/image.png" alt=""></p>
<p>따라서 학습률을 잘못 설정하게 되면 위의 그림과 같이 된다. 위의 그림을 설명하면 다음과 같다.</p>
<blockquote>
<p>💡 <strong>학습률이 너무 낮을 때</strong>
최적점에 이르기까지 너무 오래 걸리거나, 주어진 Iteration 내에서 최적점에 도달하는 데 실패한다.</p>
</blockquote>
<blockquote>
<p>💡 <strong>학습률이 너무 클 때</strong>
경사하강 과정에서 발산하면서 모델이 최적값을 찾을 수 없게 된다.</p>
</blockquote>
<p>따라서 최적의 학습률을 찾는 것은 학습에서 중요한 요소이다. 따라서 최적의 학습률을 찾기 위하여 사용하는 방법이 <span style='color:orange'><strong>학습률 감소/계획법</strong></span>이다.</p>
<h2 id="1-학습률-감소learing-rate-decay">1. 학습률 감소(Learing rate Decay)</h2>
<p>학습률 감소는 <code>Adagrad</code>, <code>RMSprop</code>,<code>Adam</code>과 같은 <code>옵티마이저</code>에 이미 구현되어 있기 때문에 쉽게 적용할 수 있다. 위의 옵티마이저의 <span stlye='color:red'><code>하이퍼 파라미터</code></span>를 조정하면 감소 정도를 변화시킬 수 있다.</p>
<ul>
<li>옵티마이저(Optimizer)의 다양한 하이퍼파리미터를 조정하여 적용<pre><code class="language-python">tf.keras.optimizers.Adam(
learning_rate=0.001, beta_1=0.9, beta_2=0.999, epsilon=1e-07, amsgrad=False,
name=&#39;Adam&#39;
)</code></pre>
위에서 사용된 옵티마이저인 <code>Adam</code>알고리즘은 <code>Momentum</code>과 <code>RMSProp</code>을 조합한 옵티마이저이다. </li>
</ul>
<p>dadelta와 RMSprop이 직전의 단계인 $t-1$까지 경사의 제곱의 이동평균 $v_t = E[g^2]_t$를 지수함적으로 감쇠평균적으로 감쇠평균한 항을 저장해가며 유지하고 매개변수의 변경식에 이 값을 사용했던 것과는 달리 adam에서는 추가로 단순한 경사의 이동평균인 $m_t=E[g]_t$를 <span style='color:orange'><strong>지수함수적으로 감쇠시킨 항</strong></span>도 사용한다. 많은 수학적인 공식이 존재하지만 <code>adam</code>알고리즘에서 학습률의 계산은 다음과 같은 식으로 이루어진다.</p>
<p>$$$
\alpha_t = \alpha\cdot{\sqrt{1-\beta^t_2}\over 1-\beta^t_1}
$$$
보통 이 포스트에는 서술하지 않았지만 다른 식에 있는 $zeta$를 0으로 근사시키기 위해서 보통 $\beta_1=0.9,,\beta_2=0.999$로 설정한다. defalut값 역시 이로 설정되어 있다.</p>
<p><code>epsilon</code>은 아직 무엇인지 정확히 모르겠다. 그리고 <code>amsgrad</code>인자는 <strong>AMSGrad</strong>변형을 적용할지 여부를 설정하는 인자로, 자세한 설명은 참고자료의 2번을 참고하자.</p>
<ul>
<li>신경망을 <code>compile</code>하는 코드에 하이퍼파라미터를 조정하는 옵티마이저 적용<pre><code class="language-python">model.compile(optimizer=tf.keras.optimizers.Adam(lr=0.001, beta_1 = 0.89),
           loss=&#39;sparse_categorical_crossentropy&#39;,
           metrics=[&#39;accuracy&#39;])</code></pre>
<h2 id="2-학습률-계획법learning-rate-scheduling">2. 학습률 계획법(Learning rate Scheduling)</h2>
학습 과정에 학습률을 바꿔가는 메카니즘을 골라서 처리하는 과정을 학습률 계획 또는 학습률 스케쥴이라고 한다.</li>
</ul>
<p>아래의 그래프와 같이 Warm-up Step을 포함한 학습률 계획 방법을 적용하기도 한다. 그리고 그래프에 나타난 두가지 계획법에 대해서도 알아보자.
<img src="https://velog.velcdn.com/images/sea_panda/post/ed845f3a-7bdb-4def-ad39-6c497f785f62/image.png" alt=""></p>
<h3 id="2-1-step-learning-rate-decay-scheduling">2-1. Step Learning rate Decay Scheduling</h3>
<p><img src="https://velog.velcdn.com/images/sea_panda/post/158a27e9-72fc-4990-89fb-41bfcf4a794a/image.png" alt=""></p>
<pre><code class="language-python">from tensorflow.keras.models import Sequential
from tensorflow.keras.layers import Dense
from tensorflow.keras.optimizers import SGD
from tensorflow.keras.callbacks import LearningRateScheduler

import tensorflow as tf
import numpy as np

def step_decay(epoch):
    start = 0.1
    drop = 0.5
    epochs_drop = 5.0
    lr = start * (drop ** np.floor((epoch)/epochs_drop))
    return lr

model = Sequential([Dense(10)])
model.compile(optimizer=SGD(), loss=&#39;mse&#39;)

lr_scheduler = LearningRateScheduler(step_decay, verbose=1)

history = model.fit(np.arange(10).reshape(10, -1), np.zeros(10),
                    epochs=10, callbacks=[lr_scheduler], verbose=0)</code></pre>
<p>가장 많이 사용되는 학습률 스케줄 방법으로는 스텝 스케줄이 있다. <code>ResNet</code>에서 잘 사용하고 있는 계획법이다.</p>
<p>처음에는 상대적으로 큰 학습률로 시작하여 최적화 과정에서 특정 지점에서 학습률을 감쇄시키고자 더 낮은 학습률을 사용한다. 위의 왼쪽 그림은 학습률 감쇄 스케쥴이라고 부르는 건데, 여기서 비용 함수의 특성이 나타나는 곡선을 볼수 있다. 여기서 스텝 학습률 감쇠로 첫 30 에폭 페이스에선 상대적으로 큰 학습률을 사용해서 빠르게 진행을 하여, 큰 값으로 시작했던 초기 비용을 지수적으로 줄일수가 있었다.</p>
<p>하지만 30 에폭쯤에서 처음 처럼 빠르게 진행할 수가 없어, 이 30에폭 시점에서 학습률을 감쇄시켜 10으로 나눈뒤 학습을하면 다시 비용이 급격히 떨어져 지수적인 패턴이 나오기 시작한다. 또 다시 평탄한 부분이 나오면 60 에폭 쯤에서 학습률을 다시 감쇄하여 빠르게 떨어트리고 다시 평탄해지는 스케줄을 사용하였을때 이런 특성이 나타나게 된다. 이게 스탭 학습률 스캐쥴이란 방법으로 모델을 학습시킬때 볼수 있는 학습률 곡선의 특성 형태이다.</p>
<p>이 계획법의 경우 모델을 학습하는데 여러개의 하이퍼파라미터가 필요하고, 따라서 튜닝하는데 워낙 많은 경우의 수가 생겨 상당히 많은 시간이 소요된다. 그래서 최근에너는 이런 단점을 극복한 다양한 계획법이 나왔다.</p>
<h3 id="2-2-cosine-learning-rate-decay-scheduling">2-2. Cosine Learning rate decay Scheduling</h3>
<p><img src="https://velog.velcdn.com/images/sea_panda/post/f2ce42e9-6854-4bf2-85fb-c560effec3a1/image.png" alt=""></p>
<p>스텝 학습률 감소 계획법의 단점을 극본한 방법 중 하나이다. 특정 반복 회차, Epoch에서 감쇄하는 것이 아니라 <span style='color:orange'><strong>사간에 대한 함수를 사용한다.</strong></span> 학습률은 모든 Epoch 회차에 대한 함수로 정해진다.</p>
<p>이 계획법은 하이퍼파라미터가 초기 학습률로 사용할 $\alpha_0$와 학습할 에폭의 수 2개이다. 따라서 이전의 스텝 감쇄 계획법보다 다루기가 쉬우며, 일반적으로 학습을 길게 할수록 잘 동작하는 경향을 보인다.</p>
<pre><code class="language-python">first_decay_steps = 1000
initial_learning_rate = 0.01
lr_decayed_fn = (
  tf.keras.experimental.CosineDecayRestarts(    
      initial_learning_rate,
      first_decay_steps))


model.compile(optimizer=tf.keras.optimizers.Adam(learning_rate=lr_decayed_fn),
             loss=&#39;sparse_categorical_crossentropy&#39;,
             metrics=[&#39;accuracy&#39;])</code></pre>
<h1 id="2-가중치-초기화weight-initialization">2. 가중치 초기화(Weight Initialization)</h1>
<p><img src="https://velog.velcdn.com/images/sea_panda/post/8d722ca0-e53a-48ea-93c6-f0f9db6cdc70/image.png" alt=""></p>
<p>신경망 모델 학습의 목적은 파라미터 최적화이다. 이를 위해서 손실함수에 대해서 경사하강법을 수행한다. 그런데 동일한 경사하강법을 따라서 내려가더라도 위의 그림에서 보듯이 도달하는 최적점이 다른 것을 볼 수 있다. 시작 위치에 따라서 최적점이 달라지게 되는 것이다. 이처럼 첫 위치를 잘 정하는 것도 좋은 학습을 위한 조건 중 하나이다.</p>
<p>따라서 학습 시작 시점의 가중치를 잘 정해주야하고, 이를 위해서 상황에 맞는 적절한 <span style='color:orange'><strong>가중치 초기화(Weight initialization)</strong></span> 방법을 사용하게 된다.</p>
<h2 id="1-가중치-초깃값--0">1. 가중치 초깃값 = 0</h2>
<p>가중치의 초기값을 0으로 default를 주고 시작하면 올바른 학습을 기대하기 어렵다. 오차역전파에서 가중치의 값이 똑같이 갱신되기 때문이다. 가중치가 각각 영향이 있어야 하는데 고르게 되어버리는 상황이 발생하면 각각의 노드를 만든 의미를 잃어버리게 된다</p>
<p>그래서 Keras에서는 Default로 되어 있는 가중치초기화 옵션은 random initialization이다.  하지만 이방식은 역전파 과정에서 미분한 Gradient가 지나치게 커지거나 소실되는 문제에 빠질 위험성이 크다.</p>
<h2 id="2-sigmoid-함수-가중치-초깃값-설정-xavier">2. Sigmoid 함수 가중치 초깃값 설정: Xavier</h2>
<h3 id="2-1-표준편차를-1인-정규분포로-가중치-초기화">2-1. 표준편차를 1인 정규분포로 가중치 초기화</h3>
<p><img src="https://velog.velcdn.com/images/sea_panda/post/b9aabe24-f636-440d-9043-4b19e91c347f/image.png" alt=""></p>
<p>표준편차가 일정한 정규분포로 가중치를 초기화 해 줄 때에는 대부분의 활성화 값이 0과 1에 위치하는 것을 볼 수 있다. 이는 Sigmoid 함수의 특성으로 인한 것이다.</p>
<p>이렇게 활성값이 양 끝 단에 집중적으로 분포되어 고르지 못할 경우에는 학습이 제대로 이루어지지 않는다. 그렇기 때문에 가장 간단한 방법임에도 잘 사용하지 않는다.</p>
<h3 id="2-2-xavier-initialization">2-2. Xavier Initialization</h3>
<p><img src="https://velog.velcdn.com/images/sea_panda/post/a48c635d-67c2-416d-8401-4699787dd5cf/image.png" alt=""></p>
<p><span style='color:orange'><strong>Xavier 초기화(Xavier initialization)</strong></span>는 가중치를 표준편차가 고정값인 정규분포로 초기화 했을 때의 문제점을 해결하기 위하여 등장한 방법이다.</p>
<p>Xavier초기화는 이전 층의 노드가 $n$개 일 때, 현재 층의 가중치를 표준편차가 $1\over\sqrt{n}$인 정규분포로 초기화한다.</p>
<p><code>Keras</code>에서는 이전 층의 노드가 $n$개이고 현재 층의 노드가 $m$개일 때, 현재 층의 가중치를 표준편차가 $2\over{\sqrt{n+m}}$인 정규분포로 초기화 한다. 또한 <code>glorot</code>라는 이름으로 사용한다.</p>
<h2 id="3-relu-함수-가중치-초깃값-설정-he">3. ReLU 함수 가중치 초깃값 설정: He</h2>
<p>Xavier초기화는 활성화 함수가 시그모이드인 신경망에서는 잘 작동한다. 하지만 <strong>활성화 함수가 ReLU</strong>인 신경망에서는 층이 지날수록 활성값이 고르지 못하게 되는 문제를 보이게 된다.</p>
<p>이런 문제를 해결하기 위해 등장한 것이 바로 <span style='color:orange'><strong>He 초기화(He initialization</strong></span>이다. He초기화는 이전 층의 노드가 $n$개일 때, 현재 층의 가중치를 표준편차가 $2\over\sqrt{n}$인 정규분포로 초기화한다. He초기화를 적용하면 아래 그림처럼 층이 지나도 활성값이 고르게 유지되는 것을 확인할 수 있다.
<img src="https://velog.velcdn.com/images/sea_panda/post/64b0299c-145b-4b32-86c5-5e4a8c1b79be/image.png" alt=""></p>
<p>위의 방법 외에도 여러 가지가 있다. 케라스에서는 아래와 같은 가중치 초기화 방법을 제공하고 있다.</p>
<pre><code class="language-python">[&#39;uniform&#39;, &#39;lecun_uniform&#39;, &#39;normal&#39;, &#39;zero&#39;, &#39;glorot_normal&#39;, &#39;glorot_uniform&#39;, &#39;he_normal&#39;, &#39;he_uniform&#39;]</code></pre>
<p>케라스의 Dense layer에서는 default로 <code>glorot_uniform</code>이 설정되어 있다. 적용하는 방법은 다음과 같다.</p>
<pre><code class="language-python">Dense(32, activation=&#39;relu&#39;, kernel_initializer=&#39;he_uniform&#39;)</code></pre>
<p><img src="https://velog.velcdn.com/images/sea_panda/post/8798dcaf-b86a-47b2-8a32-e9c550e4f219/image.png" alt=""></p>
<p><img src="https://velog.velcdn.com/images/sea_panda/post/d174e647-e00a-44c0-a4b3-b472c66c49f4/image.png" alt=""></p>
<p><img src="https://velog.velcdn.com/images/sea_panda/post/23dce5b2-b731-4fd5-87a5-c3bd357ed001/image.png" alt=""></p>
<h1 id="3-과적합-방지를-위한-방법들">3. 과적합 방지를 위한 방법들</h1>
<p>인공 신경망의 노드 수와 층을 늘리다 보면 매개 변수가 상당히 많아진다. Fashion MNIST예제를 풀기 위해서 구축한 신경망에서는 은닉층 없이 출력층만 설계했음에도 7,850개의 파라미터가 있었다. 딥러닝, 즉 은닉층이 3개 이상인 신경망에는 훨씬 더 많은 수의 파라미터가 있다. </p>
<p>머신러닝에서는 모델이 복잡해지면 <strong>과적합(Overfitting)</strong>문제가 발생하는 경향이 있다. 이러한 과적합 방지를 위해서 사용되는 방법들에 대해 알아보자.</p>
<h2 id="1-weight-decay가중치-감소">1. Weight Decay(가중치 감소)</h2>
<p>$$$
L_1(\theta_w)={1\over2}\sum_i(output_i-target_i)^2+\lambda\cdot\vert\vert\theta_w\vert\vert_1
\L_2(\theta_w)={1\over2}\sum_i(output_i-target_i)^2+\lambda\cdot\vert\vert\theta_w\vert\vert_2
$$$
과적합은 가중치의 값이 클 때 주로 발생한다. 가중치 감소에서는 가중치가 너무 커지지 않도록 <strong>가중치 값이 너무 커지지 않도록 조건을 추가</strong>한다. 이 과정에서 <strong>손실 함수(Cost function)</strong>에 가중치와 관련된 항을 추가하게 된다.</p>
<p>조건을 어떻게 적용할지에 따라 L1 Regularization(LASSO), L2 Regularization(Ridge) 으로 나뉜다. 그 식은 위에 나타내었다.</p>
<p>Keras에서는 아래와 같이 가중치 감소를 적용하고 싶은 층에 <code>regularizer</code>파라미터를 추가하면 된다. </p>
<pre><code class="language-python">Dense(64,
      kernel_regularizer=regularizers.l2(0.01),
      activity_regularizer=regularizers.l1(0.01))</code></pre>
<h2 id="2-dropout드롭아웃">2. Dropout(드롭아웃)</h2>
<p><img src="https://velog.velcdn.com/images/sea_panda/post/d3756852-d524-4528-a18c-432f402aaddb/image.png" alt=""></p>
<p><span style='color:orange'><strong>Dropout(드롭아웃)</strong></span>은 Iteration마다 <strong>레이어 노드 중 일부를 사용하지 않으면서 학습을 진행하는 방법</strong>이다. 매 Iteration마다 랜덤하게 노드를 차단하여 다른 가중치를 학습하도록 조정하기 때문에 과적합을 방지할 수 있게 된다.</p>
<p>Dropout을 적용할 때에는 0 ~ 1 사이의 실수를 입력할 수 있지만, 보통 0.3 ~ 0.5 사이의 값을 사용한다. </p>
<p>Keras에서는 아래와 같이 Dropout을 적용하고 싶은 층 다음에 <code>Dropout</code>함수를 추가하면 된다.</p>
<pre><code class="language-python">Dense(64,
      kernel_regularizer=regularizers.l2(0.01),
      activity_regularizer=regularizers.l1(0.01))
Dropout(0.5)</code></pre>
<h2 id="3-early-stopping">3. Early Stopping</h2>
<p><img src="https://velog.velcdn.com/images/sea_panda/post/da200674-f202-4ef9-9125-663db846a5b0/image.png" alt=""></p>
<p>위의 그림에서 볼 수 있는 것처럼 <strong>학습(Train)</strong>데이터에 대한 손실은 계속 줄어들지만 <strong>검증(Validation)</strong>데이터셋에 대한 손실은 증가한다면 학습을 종료하도록 설정하는 방법이다.</p>
<p>이제 Fashion MNIST예제에서 구축 신경망에 <strong>조기 종료(Early Stopping)</strong>를 적용하여 보겠다.</p>
<pre><code class="language-python">from tensorflow.keras.datasets import fashion_mnist
from tensorflow.keras.models import Sequential
from tensorflow.keras.layers import Dense, Flatten, Dropout
from tensorflow.keras import regularizers

import os
import numpy as np
import tensorflow as tf
import keras

# 시드 고정
np.random.seed(42)
tf.random.set_seed(42)

# 데이터셋 불러오기
(X_train, y_train), (X_test, y_test) = fashion_mnist.load_data()

# 데이터 정규화(Normalization)
X_train = X_train / 255.
X_test = X_test / 255.</code></pre>
<p>신경망 모델을 구축하고 Compile한다. 이 과정에서 <strong>Weight Decay(가중치 감소), Dropout(드롭아웃)</strong>을 적용하여 본다.</p>
<pre><code class="language-python">model = Sequential([
    Flatten(input_shape=(28, 28)),
    Dense(64,
          kernel_regularizer=regularizers.l2(0.01),     # 가중치 감소
          activity_regularizer=regularizers.l1(0.01)),  # 가중치 감소
    Dropout(0.5), # Dropout 적용
    Dense(10, activation=&#39;softmax&#39;)
])</code></pre>
<p><code>compile</code>설정에서 <strong>힉습률 감소(Learning rate Decay)</strong>를 적용하여 본다.</p>
<pre><code class="language-python">model.compile(optimizer=tf.keras.optimizers.Adam(learning_rate=0.001, beta_1 = 0.89)
             , loss=&#39;sparse_categorical_crossentropy&#39;
             , metrics=[&#39;accuracy&#39;])</code></pre>
<p>그런 다음 신경망 모델을 학습하고 이 과정에서 <strong>Early Stopping</strong>을 적용할 수 있도록 파라미터 저장 경로와 조기 종료 옵션을 설정하여 준다.</p>
<pre><code class="language-python"># 파라미터 저장 경로를 설정하는 코드입니다.
checkpoint_filepath = &quot;FMbest.hdf5&quot;

early_stop = keras.callbacks.EarlyStopping(monitor=&#39;val_loss&#39;, min_delta=0, patience=10, verbose=1)

save_best = tf.keras.callbacks.ModelCheckpoint(
    filepath=checkpoint_filepath, monitor=&#39;val_loss&#39;, verbose=1, save_best_only=True,
    save_weights_only=True, mode=&#39;auto&#39;, save_freq=&#39;epoch&#39;, options=None)</code></pre>
<p>위의 코드에서 <code>tf.keras.callbacks.ModelCheckpoint</code>는 어떤 지표로 모니터하고, 어느 경로로 저장하며, 최적의 값만 저장하는지, 또 그중에서 가중치만 저장하는지 등 다양한 설정을 하는 것이다.</p>
<pre><code class="language-python">model.fit(X_train, y_train, batch_size=32, epochs=30, verbose=1, 
          validation_data=(X_test,y_test), 
          callbacks=[early_stop, save_best])</code></pre>
<p>콜백(Callback)에 의해 Best 모델의 파라미터가 제대로 저장되었는지 확인하고 해당 모델로 평가를 진행한다.</p>
<pre><code class="language-python">model.load_weights(checkpoint_filepath)
model.predict(X_test[0:1])
test_loss, test_acc = model.evaluate(X_test,  y_test, verbose=1)</code></pre>
<h2 id="4-batch-normalization배치정규화">4. Batch normalization(배치정규화)</h2>
<p>배치정규화 참고 사이트</p>
<ul>
<li><a href="https://gaussian37.github.io/dl-concept-batchnorm/">https://gaussian37.github.io/dl-concept-batchnorm/</a></li>
<li><a href="https://buomsoo-kim.github.io/keras/2018/04/24/Easy-deep-learning-with-Keras-5.md/">https://buomsoo-kim.github.io/keras/2018/04/24/Easy-deep-learning-with-Keras-5.md/</a></li>
</ul>
<p><br><br><br></p>
<blockquote>
<p>❗ <strong>참고자료</strong></p>
</blockquote>
<ol>
<li><a href="https://forensics.tistory.com/28">옵티마이저</a></li>
<li><a href="https://velog.io/@aacara/On-the-convergence-of-ADAM-andBEYOND">AMSgrad논문 정리 velog</a></li>
<li><a href="https://blog.naver.com/PostView.naver?blogId=handuelly&amp;logNo=221831940317&amp;parentCategoryNo=&amp;categoryNo=31&amp;viewDate=&amp;isShowPopularPosts=true&amp;from=search">가중치 초기화</a></li>
</ol>
]]></description>
        </item>
        <item>
            <title><![CDATA[[N412] TIL 및 회고]]></title>
            <link>https://velog.io/@sea_panda/N412-TIL-%EB%B0%8F-%ED%9A%8C%EA%B3%A0</link>
            <guid>https://velog.io/@sea_panda/N412-TIL-%EB%B0%8F-%ED%9A%8C%EA%B3%A0</guid>
            <pubDate>Wed, 11 Jan 2023 16:47:38 GMT</pubDate>
            <description><![CDATA[<h1 id="0-학습목표">0. 학습목표</h1>
<h2 id="level-1">Level 1.</h2>
<ul>
<li>신경망이 학습되는 메커니즘<strong>(순전파, 손실계산, 역전파)</strong>에 대해 적절한 비유를 들어 설명할 수 있다.</li>
<li><strong>경사 하강법(Gradient Descent, GD)</strong>을 통해 갱신되는 과정을 대략적으로 설명할 수 있다.</li>
<li><strong>옵티마이저(Optimizer)</strong>의 개념과 <strong>확률적 경사 하강법(Stochastic Gradient Descent, SGD)</strong> 및 <strong>미니 배치 경사 하강법(Mini-Batch Gradient Descent)</strong>의 개념에 대해 설명할 수 있다.<h2 id="level-2">Level 2.</h2>
</li>
<li><strong>편미분(Partial Derivatives)과 연쇄 법칙(Chain Rule)</strong>에 대해 이해하고 곱셉 노드, 덧셈 노드 및 활성화 함수에 대한 미분 예제를 풀 수 있다.</li>
<li>편미분과 연쇄법칙을 사용하여 역전파 과정을 설명할 수 있다.<h2 id="level-3">Level 3.</h2>
</li>
<li>이론적으로 이해한 내용을 파이썬 코드로 구현할 수 있다.</li>
<li>구현한 함수를 모두 엮어 신경망 학습을 파이썬 코드로 작성할 수 있다.<h1 id="1-신경망-학습training-neural-network">1. 신경망 학습(Training Neural Network)</h1>
<img src="https://velog.velcdn.com/images/sea_panda/post/48841d92-1117-4e18-8301-92ac9a55fb78/image.gif" alt=""></li>
</ul>
<p>신경망은 기본적으로 위의 <code>gif</code>파일처럼 학습된다.</p>
<ol>
<li>데이터가 입력되면 신경망 각 층에서 <span style="color:orange"><strong>가중치 및 활성화 함수 연산</strong></span>을 반복적으로 수행한다.</li>
<li>1의 과정을 모든 층에서 반복한 후에 <span style="color:orange"><strong>출력층에서 계산된 값을 출력</strong></span>한다.</li>
<li><span style="color:orange"><strong>손실함수</strong></span>를 사용하여 <span style="color:orange"><strong>예측값(Prediction)과 실제값(Target)의 차이</strong></span>를 계산한다.</li>
<li><span style="color:orange"><strong>경사하강법과 같은 방법과 역전파</strong></span>를 통해서 <span style="color:orange"><strong>각 가중치를 갱신</strong></span>한다.</li>
<li>학습 중지 기준을 만족할 때까지 <span style="color:orange"><strong>위의 과정을 반복</strong></span>한다.</li>
</ol>
<p>이때 1~4까지의 과정을 <span style="color:orange"><strong>Iteration</strong></span>이라고 하며 매 Iteration마다 가중치가 갱신된다. Iteration은 <span style="color:blue"><strong>순전파(1,2), 손실계산(3), 역전파(4)</strong></span>로 나눌 수 있다.</p>
<h2 id="1-순전파foward-propagation">1. 순전파(Foward Propagation)</h2>
<p>순전파는 <span style="color:orange"><strong>입력층에서 입력된 신호가 은닉층의 연산을 거쳐 출력층에 값을 내보내는 과정</strong></span>이다. 각 층에서 이루어지는 연산 과정은 다음과 같다.</p>
<ol>
<li>입력층(혹은 이전 은닉층)으로부터 신호를 전달받는다.</li>
<li>입력된 데이터에 <span style="color:orange"><strong>가중치-편향 연산</strong></span>을 수행한다.</li>
<li>가중합을 통해 구해진 값은 <span style="color:orange"><strong>활성화 함수</strong></span>를 통해 다음 층으로 전달된다.</li>
</ol>
<p>가중치-편향 연산과 활성화 함수를 적용하는 과정을 수식적으로 나타내면 다음과 같다.
$$$
x^{(l+1)}<em>i = g(s^{(l)}_i) \
,\
s^{(l)}_i = w^{(l)}</em>{0i} + w^{(l)}<em>{1i}x^{(l)}</em>{1} + w^{(l)}<em>{2i}x^{(l)}</em>{2}+\cdot\cdot\cdot,+w^{(l)}<em>{(n-1)i}x^{(l)}</em>{n-1} + w^{(l)}<em>{ni}x^{(l)}</em>{n}
$$$</p>
<blockquote>
<ul>
<li>$g()$: 활성화 함수</li>
</ul>
</blockquote>
<ul>
<li>$l$: Layer층 수</li>
<li>$i$: Node 번호</li>
<li>$s^{(l)}_i$: $l$번째 층 Layer에서 $(l+1)$번째 층 Layer의 i번째 노드로 출력되는 가중합</li>
<li>$w^{(l)}_{ji}$: $l$번째 층 Layer의 $j$번째 Node에서 $(l+1)$번째 층의 $i$번째 node로 연결된 가중치</li>
<li>$x^{(l)}_{i}$:$l$번째 층 Layer의 $i$번째 노드의 값</li>
</ul>
<h2 id="2-손실함수loss-function">2. 손실함수(Loss function)</h2>
<p>신경망은 손실 함수를 최소화 하는 방향으로 가중치를 갱신한다. 그렇기 때문에 손실 함수를 잘 정의해야 가중치가 제대로 갱신될 수 있다. 입력데이터를 신경망에 넣어 순전파를 거치면 마지막에는 출력층을 통과한 값이 도출된다. 이 때 출력된 값과 그 데이터의 타겟값을 <span style="color:orange"><strong>손실 함수에 넣어 손실(Loss or Error)를 계산</strong></span>하게 된다.</p>
<p>대표적인 손실 함수로는 앞 서 배운 Section2에서 다루었던 <span style="color:orange"><strong>MSE(Mean-Squared Error), CEE(Cross-Entropy Error)</strong></span> 등이 있다.</p>
<p>일반적으로 각 문제에서 사용하는 손실 함수가 있다.</p>
<blockquote>
<ul>
<li><strong>📌 회귀</strong>: MSE, MAE</li>
</ul>
</blockquote>
<ul>
<li><strong>📌 이진 분류</strong>: binary_crossentropy</li>
<li><strong>📌 다중 분류</strong><ul>
<li>One-Hot Encoding으로 라벨링 되었을 때: Categorical_crossentropy</li>
<li>Index 형식으로 라벨링 되었을 때: sparse_categorical_crossentropy</li>
</ul>
</li>
</ul>
<h2 id="3-역전파backward-propagation">3. 역전파(Backward Propagation)</h2>
<p><span style="color:orange"><strong>역전파(Backward Propagation)</strong></span>는 말 그대로 순전파와는 <strong>반대 방향으로 손실(Loss or Error) 정보를 전달</strong>해주는 과정이다.</p>
<p>순전파가 입력 신호 정보를 입력층부터 출력층까지 전달하여 값을 출력했다면, 역전파는 <span style="color:orange"><strong>구해진 손실 정보를 이용하여 출력층부터 입력층까지 전달하여 각 가중치를 얼마나 업데이트 해야할 지를 구하는</strong></span> 알고리즘이다.</p>
<p>이 알고리즘을 수식으로 정리하여보았다.
<img src="https://velog.velcdn.com/images/sea_panda/post/a9d5e0a1-57b5-4bb8-9eaa-340b39bcbef4/image.png" alt=""></p>
<p>역전파 알고리즘의 수식에서 가장 중요한 것은 <span style="color:orange"><strong>미분과 Chain Rule</strong></span>이다. 위의 그림에서도 복잡한 미분을 편미분을 통하여 각각의 변수에 대한 미분으로 나누어 진행하게 된다.</p>
<p>신경망은 매 반복마다 <span style="color:orange"><strong>손실(Loss)을 줄이는 방향</strong></span>으로 가중치를 업데이트한다. 그리고 위의 그림에 설명하였듯이 손실을 줄이기 위해서 <span style="color:orange"><strong>경사하강법(Gradient Descent), 확률적 경사하강법(Stochastic Gradient Descent, SGD), Mini-batch Gradient Descent</strong></span> 등의 <span style="color:orange"><strong>옵티마이저(Optimizer)</strong></span> 사용한다.</p>
<h2 id="4-옵티마이저optimizer">4. 옵티마이저(Optimizer)</h2>
<p><img src="https://velog.velcdn.com/images/sea_panda/post/a351e959-cf47-475f-9a88-147b50cd041c/image.png" alt=""></p>
<p><span style="color:orange"><strong>옵티마이저</strong></span>는 쉽게 말해 <strong>경사를 내려가는 방법을 결정</strong>한다.</p>
<hr>
<h3 id="4-1-경사하강법gradient-descent-gd">4-1. 경사하강법(Gradient Descent, GD)</h3>
<p><img src="https://velog.velcdn.com/images/sea_panda/post/c61f1423-4e32-4abd-82f3-d952fc8e9d30/image.png" alt=""></p>
<p><strong>경사하강법</strong>은 앞의 <a href="https://velog.io/@sea_panda/N134-TIL-%EB%B0%8F-%ED%9A%8C%EA%B3%A0-iagn8kpv">N134</a>에서 다루었었다. 다시 한 번 예시들어 설명하자면 앞이 보이지 않는 안개가 낀 산을 내려올 때, 모든 방향으로 산을 더듬어가며 산의 높이가 가장 낮아지는 방향으로 한 발씩 내딛어 가는 것이 경사하강법을 직관적으로 이해하기 가장 쉬운 예시이다.</p>
<p>오차함수가 최소가 되는 점을 찾기 위해서 <strong>미분</strong>을 활용한다. 경사하강법이라는 이름에서 알 수 있듯이 함수의 기울기를 활용하여 $\theta$의 값을 어디로 옮겼을 때 함수가 최솟값을 찾는지 알아본다.</p>
<p>일반적인 경사하강법에서는 모든 입력 데이터에 손실 함수의 기울기를 계산한 후에 가중치를 업데이트 하였다. 즉 1번의 Iteration마다 모든 데이터를 사용한다. 만약 입력데이터가 적다면 경사하강법으로도 빠르게 가중치를 금방 갱신할 수 있다.</p>
<p>자세한 수학적 공식과 알고리즘은 <a href="https://velog.io/@sea_panda/N134-TIL-%EB%B0%8F-%ED%9A%8C%EA%B3%A0-iagn8kpv">N134</a>를 참고하자.</p>
<h3 id="4-2-확률적-경사하강법stochastic-gradient-descent-sgd">4-2. 확률적 경사하강법(Stochastic Gradient Descent, SGD)</h3>
<p><img src="https://velog.velcdn.com/images/sea_panda/post/f69351ea-9593-4903-92e6-90a9c495e4eb/image.png" alt=""></p>
<p>경사하강법의 경우 많은 양의 데이터를 다루게 되면 모든 데이터에 대해 손실을 계산하기 때문에 이 과정이 굉장히 오래 걸리게 된다. 그래서 등장한 것이 바로 <span style="color:orange"><strong>확률적 경사 하강법과 미니 배치(Mini-batch) 경사 하강법</strong></span>이다.</p>
<p><span style="color:orange"><strong>확률적 경사하강법(SGD)</strong></span>은 전체 데이터에서 <code>batch_size</code>가 1인, 즉, <strong>하나의 데이터</strong>만을 뽑아서 신경망에 입력한 후 손실을 계산한다. 그리고 그 손실 정보를 역전파하여 신경망의 가중치를 업데이트하게 된다.다시 말하면 1번의 Iteration에는 1개의 데이터만 사용하게 되고, 따라서 1번의 epoch에서 전체 데이터 수만큼의 Iteration이 진행된다.</p>
<p>각 Iteration에서 1개의 데이터만 사용하기 때문에 <strong>가중치를 빠르게 업데이트</strong>할 수 있다는 장점이 있다. 물론 확률적 경사 하강법에도 단점이 있다. 1개의 데이터만 보기 때문에 학습 과정에서 <strong>불안정한 경사 하강</strong>을 보인다.</p>
<h3 id="4-3-mini-batch-gradient-descent">4-3. Mini-batch Gradient Descent</h3>
<p><img src="https://velog.velcdn.com/images/sea_panda/post/5cf07700-7cd9-4e4e-a745-9d07cf478946/image.png" alt=""></p>
<p>경사하강법의 단점과 확률적 경사하강법의 단점을 보완하기 위해서 그래서 두 방법을 적절히 융화한 <span style="color:orange"><strong>Mini-batch Gradient Descent</strong></span>가 등장한다.</p>
<p>N개의 데이터로 미니 배치를 구성하여 해당 미니 배치를 신경망에 입력한 후 이 결과를 바탕으로 가중치를 업데이트 한다. 즉 1번의 Iteration마다 N개(=batch size)의 데이터를 사용하게 되고 결국 Iteration 수는 다음과 같이 결정된다.
$$$
Iteration = {# , of , Data \over Batch,Size}
$$$
이때, Keras에서는 전체 데이터를 <code>batch_size</code>로 나누었을 때 나머지가 생기면 그 나머지 그대로를 사용하여 Iteration 진행한다. 즉, 나눈 값을 올림하여 주면 1epoch에서의 전체Iteration 수가 된다.</p>
<p>일반적으로 <code>batch_size</code>는 2의 배수로 결정되먀, 메모리가 허락한다면 큰 사이즈를 쓰는 것이 안정적인 학습에 도움이 된다. 실제로는 <strong>32~128</strong>정도 사이의 크기가 주로 쓰인다고 한다.</p>
<hr>
<p>위의 3가지 예시 외에도 다양한 옵티마이저들이 존재한다.
<img src="https://velog.velcdn.com/images/sea_panda/post/6fc5f7c1-a4e6-46b7-a3d2-c989f681a33c/image.gif" alt=""></p>
<ul>
<li><strong>확률적 경사하강법(SGD)을 변형한 알고리즘</strong>: Momentum, RMSProp, Adam 등</li>
<li><strong>Newton&#39;s method</strong>등의 2차 최적화 알고리즘 기반 방법: BFGS 등 (수치해석 책 참조하자.)</li>
</ul>
<p>여러가지 옵티마이저 중에서 어떤 것이 가장 좋다고 말하기는 어렵다. 문제마다, 데이터마다 달라지기 때문에 여러 옵티마이저를 적용하면서 서로 비교하여 보아야 한다.</p>
<h1 id="2-회고">2. 회고</h1>
<p>이번 Sprint는 뭔가 딥러닝을 위한 개념학습을 진행하는 것 같은 느낌이 든다. 레퍼런스에 다양한 수식과 파이썬으로 직접 신경망을 구성하는 코드들이 나와있는데, 아직 그것까지는 학습하지 못했다. 주말을 이용해서 추가적으로 학습해야할 것 같다. </p>
<p>그리고 뭔가 코드를 안만지니까 다시 전공 수업듣는 느낌이 든다. 조금 더 많은 딥러닝 알고리즘과 코드들을 만져보고 싶다.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[[N411] TIL 및 회고]]></title>
            <link>https://velog.io/@sea_panda/N411-TIL-%EB%B0%8F-%ED%9A%8C%EA%B3%A0</link>
            <guid>https://velog.io/@sea_panda/N411-TIL-%EB%B0%8F-%ED%9A%8C%EA%B3%A0</guid>
            <pubDate>Tue, 10 Jan 2023 16:51:04 GMT</pubDate>
            <description><![CDATA[<h1 id="0-학습목표">0. 학습목표</h1>
<h2 id="level-1">Level 1.</h2>
<ul>
<li><strong>퍼셉트론(Perceptron)</strong>의 개념과 구조에 대해 설명할 수 있다.</li>
<li>신경망을 왜 다층으로 구성해야 하는 지와 신경망 <strong>각 층(입력층, 은닉층, 출력층)의 역할</strong>에 대해 설명할 수 있다.</li>
<li>MINIST 예제 코드를 이해하고 재현할 수 있다.<h2 id="level-2">Level 2.</h2>
</li>
<li><strong>가중치 행렬의 Shape</strong>과 <strong>신경망 구조</strong>에 대해서 이해하고 설명할 수 있다.</li>
<li><strong>활성화 함수의 공통점</strong>과 신경망의 특징인 <strong>표현 학습</strong>에 대해 이해한다.<h2 id="level-3">Level 3.</h2>
</li>
<li>시그모이드 함수의 단점인 기울기 소실 문제와 ReLU함수를 쓰는 이유에 대해 이해할 수 있다.</li>
<li><strong>파이썬 기본 코드로 퍼셉트론을 구현</strong>할 수 있다.<h1 id="1-퍼셉트론perceptron과-인공신경망artificial-neural-networks">1. 퍼셉트론(Perceptron)과 인공신경망(Artificial Neural Networks)</h1>
<img src="https://velog.velcdn.com/images/sea_panda/post/0e6161eb-6434-48c3-b798-1ac8962a4ca8/image.png" alt=""></li>
</ul>
<p><strong>인공신경망(Artificial Neural Networks)</strong>는 1943년 <strong>워렌 맥컬록(Warren McCulloch)</strong>이라는 신경생리학자가 처음 제시한 개념으로 기계를 학습시키는데 있어서 인간의 신경세포인 뉴런을 모방하는 수학적인 모델을 제시하였다. </p>
<p>이후 1958년 <strong>프랑크 로젠블랫(Frank Rosenblatt)</strong>은 이런 인공신경망의 최소 단위를 <strong>퍼셉트론(Perceptron)</strong>으로 정의하고 이들의 연결로 인지과정을 이해할 수 있을 것이라고 기대했다.</p>
<p>퍼셉트론은 이진 분류(binary Classification)모델을 학습하기 위한 지도학습(Supervised Learing)기반의 알고리즘이다. 수학적인 측면에서 2가지 클래스를 특정 기준 하에 구분하는 방법이다. 그리고 클래스를 구분하는 기준(아래 그림에서는 빨간선)을 Decision Boundary라고 부른다.
<img src="https://velog.velcdn.com/images/sea_panda/post/0b8b65ba-44f9-40dd-b919-8e16e32aa495/image.png" alt=""></p>
<p>퍼셉트론은 뉴런이 다른 뉴런으로부터 신호를 입력받듯이 다수의 값 $x$를 입력받고, 입력된 값마다 <strong>가중치($weight$)</strong>를 곱한다. 여기서 가중치는 생물학 뉴련에서 뉴런 간 시냅스를 통한 결합의 세기와 같은 역할이며 가중치가 클수록 입력값이 중요하다는 것을 의미한다. 
<img src="https://velog.velcdn.com/images/sea_panda/post/3e41de71-7dd2-49c3-ae2b-8f421a05080d/image.png" alt=""></p>
<p>이때 위 그림을 살펴보면 입력값에 1과 가중치에 $b$가 있는 것을 볼 수 있다. 이것을 <strong>편향($bias$,b)</strong>이라고 한다. 이는 딥러닝 모델 최적화의 중요 변수 중 하나이다. 일반적으로 입력값을 1로 고정하고 편향 b를 곱한 변수로 표현한다. 입력값과 가중치의 곱, 편향은 퍼셉트론으로 전달된다. 퍼셉트론은 입력받은 값을 모두 합산하는데, 합산된 결괏값을 <strong>가중합</strong>이라고 부른다. 퍼셉트론은 이 가중합의 크기를 <strong>임계값</strong>과 비교하는 <strong>활성화 함수(Activation Function)</strong>을 거쳐 최종 출력값을 결정한다. 활성화 함수는 다시 말해서 계산된 가중합을 얼마 만큼의 신호로 출력할지 결정하는 출력과 관련이 있는 함수이다. 다양한 활성화 함수가 존재하며 이는 아래에서 다루도록 한다.</p>
<p>이러한 퍼셉트론이 처음 수행한 작업은 논리연산이다. 자동제어 시간에 들은 <strong>AND, OR, NAND Gate</strong>가 이런 논리연산의 예시로, 적절한 매개변수(가중치와 편향)를 선택하여 논리연산을 수행할 수 있다. 이러한 사실은 당시 과학자 들을 매우 흥분시켰다. 하지만 1969년 MIT 인공지능 연구소의 <strong>마빈 민스키(Marvin Minsky)</strong>와 <strong>페퍼트 세이모어(Papert Seymour)</strong>는 이런 퍼셉트론이 XOR Gate(배타적 논리합, 비선형 연산자)을 표현할 수 없다는 당시 퍼셉트론의 한계이자 맹점을 지적하였다. 
<img src="https://velog.velcdn.com/images/sea_panda/post/770bce18-f2f3-4690-bb52-8ca75220c7c3/image.png" alt=""></p>
<p>한 개의 퍼셉트론은 2차원 공간을 나누는 1차원 직선으로 해석할 수 있다. 입력이 2차원 이상인 n차원 초공간에서 퍼셉트론은 (n-1)차원 초평면에 해당한다. 이런 기하학적 해석에 따르면 위의 그림에서 보듯이 XOR은 퍼셉트론 한 개로 검은 점과 흰 점을 나눌 수 없음이 자명하다. 이런 퍼셉트론의 한계는 머신러닝의 첫 어둠의 시기를 촉발했다.
<img src="https://velog.velcdn.com/images/sea_panda/post/3b8a06d1-41c5-4db0-86c2-2f82c1d35abd/image.png" alt=""></p>
<p>이러한 문제를 해결하기 위하여 은닉층을 이용하여 여러 개의 퍼셉트론을 쌓는 방법이 제시되었다. 배타적 논리합 문제에서 직선 2개를 이용하면 검은 점과 흰 점을 구분할 수 있다. 이 직선을 수직으로 표현하면 다음과 같다.
$$$
S1 = W_{11}x_1+W_{12}x_2-b_1=0,\
S2 = W_{21}x_1+W_{22}x_2-b_2=0
$$$
여기서 입력 $(x_1,x_2)$의 좌표변환 $z_1=f(s_1)$과 $z_2=f(s_2)$를 통해 좌표계를 변환하여 주면 $(z_1,z_2)$에서는 검은 점과 흰 점을 비로소 한 개의 직선으로 나룰 수 있다. 이를 신경망의 신호흐름으로 나타내면 위의 그림에서처럼 입력 신호$(x_1,x_2)$가 은닉층(Hidden layer)의 숨은 뉴런의 활성화 상태$(z_1,z_2)$로 변환되고 이는 최종적으로 출력신호 $y$로 변환된다. 즉 은닉층을 추가함으로써 단일 퍼셉트론으로 구성된 신경망에서는 표현할 수 없는 XOR을 표현할 수 있게 된 것이다.</p>
<p>이 결론을 일반화 한 것이 <strong><a href="https://horizon.kias.re.kr/17443/">보편적 어림정리(Universal Approximation Theorem)</a></strong>정리이다. 충분히 많은 뉴런들로 구성된 은닉층을 가진 신경망은 임의의 입력 $x$와 출력 $y$사이의 함수관계 $y=f(x)$를 표현할 수 있다는 정리이다. 이는 임의의 입력 $x$와 출력 $y$사이의 관계를 표현할 수 있는 신경망이 반드시 존재함을 보장한다. 그렇다고 이 정리가 신경망 구성을 위한 최소한의 은닉 뉴런 수, 신경망의 매개변수 값들을 구체적으로 알려주진 않는다. </p>
<p>즉 주어진 문제를 풀 수 있는 매개변수의 최적값을 알 수 없다. 배타적 논리합과 같은 간단한 문제는 2차원 좌표 위의 기하학적 모습을 토대로 매개변수 값을 쉽게 정할 수 있지만, 다차원의 입력과 출력 사이의 관계를 다루는 일반적인 문제에서는 간단하지 않다. 이러한 문제를 해결하기 위하여 제시된 방법이 바로 <strong>역전파(Backpropagation) 알고리즘</strong>이다.</p>
<p><strong>역전파 알고리즘</strong>은 지도학습 문제에서 신경망을 학습시키는 방법이다. 결과를 알고 있기 때문에 Output에 대한 오차값을 이용하여 <strong><a href="https://velog.io/@sea_panda/N134-TIL-%EB%B0%8F-%ED%9A%8C%EA%B3%A0-iagn8kpv">경사하강법(Gradient Descent Method)</a></strong>을 이용하여 매개변수를 갱신한다. 이를 사용하여 순전파와 역전파를 반복해나아가면 이론적으로는 오차가 0에 가까워진다. 하지만 경사하강법의 한계로 인하여 항상 Global minimum을 찾는다고 보장할 수 없다. 또한 극소값이 두 개 이상 존재하는 함수에 대해서 가장 작은 최솟값을 가진다고 할 수 없고, 알고리즘 자체가 단순히 기울기가 작아지는 방향으로 나아가기 때문에, 출발지점에 따라서 결과가 달라질 수 있다.</p>
<p>다시 처음으로 돌아와서 <strong>인공신경망</strong>을 다시 설명하여 보자면 퍼셉트론으로 구성된 인간의 신경세포를 모방한 구조를 이용하여 컴퓨터를 가르치는 인공지능 방식이라고 할 수 있으며 줄여서 <strong>뉴럴넷(Neural-Net)</strong>으로 부르기도 한다. 그 구조는 입력층, 은닉층, 출력층으로 구성되어 있다.</p>
<p>신경망은 퍼셉트론을 여러 층으로 쌓아서 만들게 된다. 1개 층으로 이루어진 신경망은 단층 퍼셉트론 신경망이라고 한다. 그리고 1개 층으로는 해결할 수 없는 문제(ex. XOR)를 해결하기 위해서 2개 이상의 층으로 구성하여 여러 개의 층으로 쌓아 구축한 신경망을 <strong>다층 퍼셉트론 신경망(Multi-Layer Perceptron, MLP)</strong>라고 한다.</p>
<h2 id="1-신경망의-각-층">1. 신경망의 각 층</h2>
<h3 id="1-1-입력층input-layer">1-1. 입력층(Input Layer)</h3>
<ul>
<li>데이터가 입력되는 층이다.</li>
<li>데이터의 <strong>특징의 수</strong>에 따라서 입력층의 <strong>노드수가 결정</strong>된다.</li>
<li>그냥 값들을 전달하기만 하는 층이기 때문에 신경망의 층수를 셀 때 <strong>입력층은 포함되지 않는다.</strong><h3 id="1-2-은닉층hidden-layers">1-2. 은닉층(Hidden Layers)</h3>
</li>
<li>입력층으로부터 입력된 신호가 가중치, 편향을 이용하여 연산되는 층</li>
<li>입력층과 출력층 사이에 존재하는 층을 의미한다.</li>
<li>계산 결과를 사용자가 볼 수 없기 때문에 <strong>은닉층(Hidden Layer)</strong>라고 한다.</li>
<li>입력층의 노드수와 관계없이 노드수를 구성할 수 있다.</li>
<li>Deep-Learing 알고리즘은 이런 은닉층이 2개 이상인 신경망을 의미한다.<h3 id="1-3-출력층output-layer">1-3. 출력층(Output Layer)</h3>
</li>
<li>가장 마지막에 위치한 층이며 은닉층 연산을 마친 값이 출력되는 층이다.</li>
<li>다중 분류 문제에서는 활성화 함수로 Softmax를 주로 사용하고, 노드 수는 레이블의 Class 수가 된다.</li>
<li>이진 분류의 경우 sigmoid 함수를 활성화 함수로 사용하면 1개의 노드, Softmax를 사용하면 2개의 노드를 가지게 된다. 둘의 차이는 사실상 없기 때문에 굳이 노드의 수를 증가시키는 Softmax보다는 sigmoid함수를 사용한다.</li>
<li>회귀 문제는 일반적으로는 활성화 함수를 지정해주지 않으며 출력층의 노드 수는 출력값의 특성(Feature)수와 동일하게 설정한다.<h2 id="2-가중치-행렬">2. 가중치 행렬</h2>
<img src="https://velog.velcdn.com/images/sea_panda/post/ecadc6f4-c07b-41e2-b122-363f64f7a128/image.png" alt=""></li>
</ul>
<p>신경망에서 실제로 학습되는 부분이다. 위 그림에서 화살표 하나마다 각각의 가중치가 주어진다. 입력층에 3개의 노드, 은닉층에 4개의 노드가 있기 때문에 12개의 가중치가 존재한다. 이 12개의 가중치가 연산되는 과정을 컴퓨터에서 잘 연산하기 위해서는 행렬의 형태로 만들어주어야 한다.</p>
<p>퍼셉트론에 있는 가중치-편향 연산은 행렬 곱으로 연산이 된다. 입력 벡터의 형태에 따라서 가중치 행렬의 Shape가 결정된다. 관습적으로 표기할 때는 가중치 행렬을 $W$, 입력 벡터를 $x$라 하고, 연산의 결과로 출력되는 벡터는 $y$라 하면 다음과 같이 나타낸다.
$$$
y = Wx
$$$
하지만 실제 연산에서 위의 식을 그대로 이용하지는 않는다. 실제로는 아래의 그림과 같이 이루어진다.
<img src="https://velog.velcdn.com/images/sea_panda/post/fd844978-e6a8-44b1-95f5-49c59a3cf60b/image.png" alt=""></p>
<pre><code class="language-python">model = tf.keras.models.Sequential([
    tf.keras.layers.Dense(10, activation=&#39;relu&#39;, input_shape=100),    # 은닉층
    tf.keras.layers.Dense(1, activation=&#39;sigmoid&#39;)                    # 출력층
])</code></pre>
<p>위의 코드로 생성한 신경망의 경우 입력층의 노드수가 100개, 은닉층의 노드 개수가 10개이므로 두 층 사이에 생성되는 가중치 행렬의 Shape는 (100,10)이 된다. 은닉층과 출력층의 경우 출력층의 노드 개수가 1개 이므로 두 층 사이에 가중치 행렬의 Shape은 (10,1)이 된다.</p>
<h2 id="3-minist-예제손글씨-분류">3. MINIST 예제(손글씨 분류)</h2>
<pre><code class="language-python">### 패키지 &amp; 라이브러리
import pandas as pd
!pip install tensorflow-gpu==2.0.0-rc1
import tensorflow as tf

# 라이브러리 데이터셋을 불러온다. 
mnist = tf.keras.datasets.mnist

# Training Set, Test Set 분류. 
(x_train, y_train), (x_test, y_test) = mnist.load_data()

# Value normalization(정규화) 수행
# 픽셀값이 0~255사이의 값을 가지기 때문에 255로 나누어 준다.
# 이를 수행하지 않을 시 모델의 정확도가 매우 낮게 출력된다.
x_train, x_test = x_train / 255.0, x_test / 255.0

# 레이블의 구성 형태 확인
pd.unique(y_train)
------------------------------------------------------
-&gt; array([5, 0, 4, 1, 9, 2, 3, 6, 7, 8], dtype=uint8)

# 신경망 모델 구축
model = tf.keras.models.Sequential([
  tf.keras.layers.Flatten(input_shape=(28, 28)), # 전체 원소 개수를 유지하면서 다차원 자료를 전결합층에 전달하기 위해 1차원 자료로 바꿔주는 Layer이다.

  tf.keras.layers.Dense(100, activation=&#39;relu&#39;),
  tf.keras.layers.Dropout(0.2), # 과적합(Overfitting) 방지 역할
  tf.keras.layers.Dense(10, activation=&#39;softmax&#39;)
])

# 구축한 모델을 컴파일하며, 옵티마이저, loss function 등을 설정.
# 컴파일 : 모델을 학습시키기 위한 학습과정 설정 단계
model.compile(optimizer=&#39;adam&#39;,
              loss=&#39;sparse_categorical_crossentropy&#39;,
              metrics=[&#39;accuracy&#39;])

# 모델이 학습 하는 부분
model.fit(x_train, y_train, epochs=5) # epoch의 수를 변화시키면 더 많이 학습하거나 적게 학습할 수 있다. 

# 만들어진 모델을 이용하여 예측하는 부분
model.evaluate(x_test,  y_test, verbose=2)</code></pre>
<h2 id="4-신경망-종류-그림">4. 신경망 종류 그림</h2>
<p><img src="https://velog.velcdn.com/images/sea_panda/post/1f3e8263-d938-45f3-9fad-9cbc68708991/image.png" alt=""></p>
<h1 id="2-활성화-함수activation-function">2. 활성화 함수(Activation function)</h1>
<p>딥러닝에서 사용하는 인공신경망들은 일반적으로 이전 레이어로부터 값을 입력받아 <strong>활성화 함수</strong>를 통과시킨 후 그 결과를 다음 레이어로 출력한다. 활성화 함수의 종류는 다음과 같다.</p>
<blockquote>
<ul>
<li>이진 활성화 함수 (Binary step activation function)</li>
</ul>
</blockquote>
<ul>
<li>선형 활성화 함수(Linear activation function)</li>
<li>비선형 활성화 함수(Non-linear activation function)</li>
</ul>
<p>3가지 종류가 존재하지만 일반적으로 비선형 활성화 함수를 사용한다. 은닉층에서 이진 활성화 함수를 활성화 함수로 사용하면, 다중 출력이 불가능하다는 문제가 발생하고, 은닉층에서 선형 활성화 함수를 활성화 함수로 사용할 경우, 역전파가 불가능하며 Layer를 깊게 쌓는 의미가 사라진다는 문제가 생기기 때문이다.</p>
<p>이런 비선형 활성화 함수는 다양한 종류가 존재하지만 크게 4가지를 다뤄보도록 하겠다.</p>
<h2 id="1-step-function계단-함수">1. Step function(계단 함수)</h2>
<p><img src="https://velog.velcdn.com/images/sea_panda/post/eea40f99-03f9-4de4-9d25-747fabb18171/image.png" alt=""></p>
<p>가장 간단한 활성화 함수로 임계값을 넘으면 1 그렇지 않으면 0을 출력하는 함수이다. 하지만 신경망에서는 역전파를 통해 매개변수들을 수정하며 학습하게 되고 이 과정에서 경사하강법이 사용되어 미분 과정이 필요하기 때문에 임계값에서 미분 불가능한 지점을 가지는 Step function은 적합하지 않다.</p>
<h2 id="2-sigmoid-function시그모이드-함수">2. Sigmoid function(시그모이드 함수)</h2>
<p><img src="https://velog.velcdn.com/images/sea_panda/post/8c669f40-0acc-4609-9b8f-3b0cb6fe17ee/image.png" alt=""></p>
<p>미분 불가능한 점을 가지는 Step function의 단점을 해결하기 위하여 사용되는 함수이다. 계단 함수처럼 임계값보다 작은 부분은 0에 가까워지고, 큰 부분은 1에 가까워진다. 그리고 임계값에서 부드럽게 연결되어 있기 때문에 모든 지점에서 미분 가능하며, 미분값도 0이 아니다.</p>
<p>하지만 Sigmoid를 중복하여 사용하면 <strong>Vanishing Gradient(기울기 소실)</strong>문제가 발생하게 된다.</p>
<blockquote>
<p>❗️ <strong>Vanishing Gradient(기울기 소실)</strong>
<img src="https://velog.velcdn.com/images/sea_panda/post/85952ce2-bf5f-477e-9d44-6481431803d9/image.png" alt="">
딥러닝 분야에서 Layer를 많이 쌓을수록 데이터 표현력이 증가하기 때문에 학습이 잘 될 것 같지만, 실제로는 Layer가 많아질수록 학습이 잘 되지 않는다. 바로 <strong>기울기 소실</strong>현상 때문이다. 기울기 소실이란 역전파 과정에서 출력층에서 멀어질수록 Gradient값이 매우 작아지는 현상을 말한다.<br>
그 원인은 <strong>활성화 함수(Activation function)</strong>의 기울기와 관련이 깊다. Sigmoid함수를 예로 들어보면 아래의 그림에서 볼 수 있듯이, sigmoid함수의 미분 값은 입력값이 0일 때 가장 크지만 0.25에 불과하고 $x$값이 크거나 작아짐에 따라 기울기는 거의 0에 수렴하는 것을 볼 수 있다.
<img src="https://velog.velcdn.com/images/sea_panda/post/eed8c765-6cd5-4935-8350-c3ec24f6f779/image.png" alt="">
따라서, 역전파 과정에서 미분값이 거듭 곱해지면 출력층과 멀어질수록 Gradient값이 매우 작아질 수밖에 없다.(이는 역전파 수식을 찾아보자.) 더불어 $e$는 컴퓨터가 계산할 때 정확한 값이 아닌 근사값으로 계산해야 되기 때문에 역전파 과정에서 점차 학습 오차까지 증가하게 된다. 결국 Sigmoid함수를 활용하면 모델 학습이 제대로 이루어지지 않게 된다. 이를 해결하기 위한 방법 중 하나로 <strong>tanh</strong>함수가 제안되었다.<br>
출력값의 범위를 2배 늘렸지만, 여전히 가울기 소실 문제를 방지하는데 어려움이 있었고, 이를 또 해결하기 위해 <strong>ReLU</strong> 함수가 제안된다. 그리고 이 함수는 기울기 소실 문제를 잘 해결하였다고 평가받는다.</p>
</blockquote>
<h2 id="3-relu-function렐루-함수">3. ReLU function(렐루 함수)</h2>
<p><img src="https://velog.velcdn.com/images/sea_panda/post/ba90670b-d967-4deb-b0fe-bf9417bc0b21/image.png" alt=""></p>
<p><strong>ReLU function</strong>는 신경망 발전에 큰 영향을 미친 활성화 함수이다. 시그모이드 함수를 중복하여 사용하게 되면 <strong>기울기 소실</strong>문제가 발생하게 되는데, 기울기 소실 문제를 해결하기 위해서 등장한 것이 ReLU이다. </p>
<p><strong>ReLU function</strong>는 양의 값이 입력되면 그 값을 그대로 출력하고 음의 값이 입력되면 0을 반환한다. 식으로 나타내면 다음과 같다.
$$$
f(x) = max(0,x)
$$$
함수의 특성상 층이 깊어지더라도 1의 값이 계속 곱해지기 때문에 기울기 값이 과도하게 커지거나 작아지는 문제가 발생하지 않게 된다.</p>
<p>ReLU함수의 등장 이전까지는 은닉층을 깊게 쌓을 수가 없었기 때문에 복잡한 문제를 푸는 데에 딥러닝을 사용할 수 없었다. 하지만 ReLU함수가 고안되고 사용되면서 딥러닝은 더욱 더 발전할 수 이었다.</p>
<h2 id="4-softmax-function소프트맥스-함수">4. Softmax function(소프트맥스 함수)</h2>
<p><img src="https://velog.velcdn.com/images/sea_panda/post/1a81f75b-0ccd-4b3c-81ab-9935d5fb0be6/image.png" alt=""></p>
<p><strong>Softmax function</strong>는 다중 분류(Multi-classification)문제에 적용할 수 있도록 시그모이드 함수를 일반화한 활성화 함수이다. 가중값을 소프트맥스 함수에 통과시키면 모든 클래스의 값의 합이 1이 되는 확률값으로 변환된다.</p>
<h1 id="3-표현학습representation-learing">3. 표현학습(Representation Learing)</h1>
<p>기계 학습에서 특징 학습 또는 <strong>표현 학습(Representation)</strong>은 시스템이 원시 데이터에서 특징 탐지 또는 분류에 필요한 표현을 자동으로 검색할 수 있도록 하는 일련의 기술을 의미한다.</p>
<p>우리는 보통 어떤 Task를 해결하기 위해 Task와 관련된 정보들을 이용한다. 예를 들어, <strong>나누기(=Task)</strong>를 하려고 하면 <strong>수(=numeric)</strong>라는 정보를 이용한다. 하지만 이러한 <strong>수</strong>라는 정보들은 다양하게 <strong>표현(=Representation)</strong>될 수 있다. 로마숫자표기, 아라비아숫자표기 등이 그 예시이다.</p>
<p>보통 Task들의 난이도는 정보들을 어떻게 표현해주느냐에 따라서 결정된다. 즉 정보들을 특정 Task에 맞게 잘 표현해주면 해당 Task를 풀 수 있는 확률이 높아지는 것이다.</p>
<p>결국 어떤 Task를 해결할 때, 정보를 어떻게 가공하여 표현해줄지에 따라서 Task의 난이도가 결정되는 것이다. 그렇다면 딥러닝 모델에서 representation이란 개념은 어떻게 이해해야 할까?</p>
<p>딥러닝에서는 최종 Task의 유형에 따라서 <strong>new representation</strong>에 해당하는 <strong>new feature</strong>를 출력하게 된다. 이러한 <strong>new representation</strong>을 뽑게 학습하는 것을 <strong>representation learing</strong>이라고 부른다.</p>
<p>사실 너무 방대한 양의 글이라 완벽히 이해는 못했다. 표현학습에 대해서는 나중에 다시 아래의 참고자료를 읽어보자.</p>
<h1 id="4-tensorflow-신경망-예제---iris데이터-분류하기">4. Tensorflow 신경망 예제 - Iris데이터 분류하기</h1>
<p>전체 특성 중 2개의 특성만 선택하여 사용하고, 150개의 데이터 중 Setosa 50개, Versicolor 50개만 추출하여 100개의 데이터에 대해서 이진분류를 진행한다.</p>
<pre><code class="language-python"># 필요한 패키지와 라이브러리를 불러온다.
import pandas as pd
import numpy as np
import matplotlib.pyplot as plt
import tensorflow as tf

# 시드(Seed)를 고정한다.
np.random.seed(42)
tf.random.set_seed(42)

# Iris 데이터셋을 Dataframe 형태로 불러온다.
df = pd.read_csv(&#39;https://archive.ics.uci.edu/ml/machine-learning-databases/iris/iris.data&#39;, header=None)
# 데이터를 살펴보는 과정의 코드는 생략한다.

# Setosa, Versicolor 데이터만 추출하여 전처리 하여준다.
label = df.iloc[0:100, 4].values

# 타겟 레이블을 Setosa=0, Versicolor=1로 변경해준다.
label = np.where(label == &#39;Iris-setosa&#39;, 0, 1)</code></pre>
<p>데이터가 어떤 분포를 가지고 있는지 시각화를 통해 알아볼 수 있다.</p>
<pre><code class="language-python">features = df.iloc[0:100, [0,2]].values
plt.scatter(features[:50, 0], features[:50, 1], color=&#39;red&#39;, marker=&#39;o&#39;, label=&#39;setosa&#39;)
plt.scatter(features[50:100, 0], features[50:100, 1], color=&#39;blue&#39;, marker=&#39;x&#39;, label=&#39;versicolor&#39;)
plt.xlabel(&#39;sepal length&#39;)
plt.ylabel(&#39;petal length&#39;)
plt.legend(loc=&#39;upper left&#39;)
plt.show()</code></pre>
<p><img src="https://velog.velcdn.com/images/sea_panda/post/c06f063f-837a-4474-bda3-56036536580d/image.png" alt=""></p>
<p>Train dataset과 Test dataset으로 나누어준다. </p>
<pre><code class="language-python">from sklearn.model_selection import train_test_split

X_train, X_test, y_train, y_test = train_test_split(features, label, test_size=0.2, random_state=42)</code></pre>
<p>다음으로는 신경망 모델을 구축하고 Complie한 후 학습한다. 이번 예제에서는 단층, 즉 은닉층이 없이 출력층으로만 모델을 구성해본다.</p>
<p>먼저 <strong>Sequential API</strong>를 사용하여 모델을 구축하여 보겠다.</p>
<pre><code class="language-python">model = tf.keras.models.Sequential([
    tf.keras.layers.Dense(1, activation=&#39;sigmoid&#39;)
])</code></pre>
<p>위의 코드를 다음과 같이 다른 방식으로도 나타낼 수 있다.</p>
<pre><code class="language-python">model = tf.keras.models.Sequential()
model.add(tf.keras.layers.Dense(1, activation=&#39;sigmoid&#39;))</code></pre>
<p>Sequential API말고도 Keras에는 또 다른 방법인 <strong>Functional API</strong>도 존재한다. 사용법은 다음과 같다.</p>
<pre><code class="language-python">input = tf.keras.layers.Input(shape=(2,))
output = tf.keras.layers.Dense(1, activation=&#39;sigmoid&#39;)(input) # &lt;- 새로 추가

model = tf.keras.models.Model(inputs=input, outputs=output)</code></pre>
<p>함수형 API에서는 달라지는 점은 다음과 같다.</p>
<blockquote>
<ul>
<li>Input()함수에 입력의 크기를 정의한다.</li>
</ul>
</blockquote>
<ul>
<li>이전층을 다음층 함수의 입력으로 사용하고, 변수에 할당한다.</li>
<li>Model()함수에 입력과 출력을 정의한다.</li>
</ul>
<p><code>.compile</code>에서는 신경망에서 사용할 <code>optimizer</code>, <code>loss</code>, <code>metrics</code>를 설정한다.</p>
<pre><code class="language-python">model.compile(optimizer=&#39;sgd&#39;,
              loss=&#39;binary_crossentropy&#39;,
              metrics=[&#39;accuracy&#39;])</code></pre>
<p>각 파라미터를 설정함에 있어서 주로 다음과 같이 설정한다.</p>
<blockquote>
<p>💡 <strong>신경망 설계</strong> - 이진분류</p>
</blockquote>
<ul>
<li><strong>활성화 함수</strong>: Sigmoid function</li>
<li><strong>출력층 노드 수</strong>: 1개 (0 또는 1로 라벨링)</li>
<li><strong>손실함수</strong>: binary_crossentropy(이항 교차 엔트로피)</li>
</ul>
<blockquote>
<p>💡 <strong>신경망 설계</strong> - 다중분류</p>
</blockquote>
<ul>
<li><strong>활성화 함수</strong>: Softmax function</li>
<li><strong>출력층 노드 수</strong>: Label의 Class 수</li>
<li><strong>손실함수</strong><ul>
<li>categorical_crossentropy(범주형 교차 엔트로피, label이 One-Hot Encoding된 형태)</li>
<li>sparse_categorical_crossentropy(label이 정수 인코딩 된 형태, 즉, label이 class index를 값으로 가질 때 사용)</li>
</ul>
</li>
</ul>
<blockquote>
<p>💡 <strong>신경망 설계</strong> - 회귀</p>
</blockquote>
<ul>
<li><strong>활성화 함수</strong>: 사용하지 않음</li>
<li><strong>출력층 노드 수</strong>: 출력값의 특성(feature) 수</li>
<li><strong>손실함수</strong>: MSE(Mean_Squared_error, MSE)</li>
</ul>
<p><code>.fit</code>은 실제로 신경망 학습이 진행되는 부분이다. <code>epochs</code>를 조정하면 학습 횟수를 조정할 수 있다.</p>
<pre><code class="language-python">model.fit(X_train, y_train, epochs=30)

# 학습한 신경망 모델을 사용하여 평가한다.
model.evaluate(X_test, y_test, verbose=2)</code></pre>
<h1 id="5-회고">5. 회고</h1>
<p>진짜 진짜 진짜 너무 오랜만에 TIL을 작성하는 것 같다. 아직 N331~N334까지는 작성도 못했는데 밀린 거 하는 것 보다는 일단 Section4 배운 내용을 정리해나가면서 남는 시간에 정리하려고 한다. 후 그래도 뭔가 DE에 대해서 배우다가 다시 인공지능 쪽으로 넘어오니까 뭔가 훨씬 재미있는 것 같다. 일단 진짜로 이번 섹션은 안밀리고 복습하는 것이 목표다.</p>
<p><br><br><br></p>
<blockquote>
<p>❗️ 참고자료</p>
</blockquote>
<ol>
<li><a href="https://heytech.tistory.com/332">퍼셉트론</a></li>
<li><a href="https://horizon.kias.re.kr/17443/">퍼셉트론2</a></li>
<li><a href="https://velog.io/@ssu_hyun/n411-%EC%8B%A0%EA%B2%BD%EB%A7%9DArtificial-Neural-Networks-%EA%B8%B0%EC%B4%88">신경망의 각 층</a></li>
<li><a href="https://cheris8.github.io/artificial%20intelligence/DL-Activation-Function/">활성화 함수</a></li>
<li><a href="https://heytech.tistory.com/388">기울기 소실</a></li>
<li><a href="https://89douner.tistory.com/339">표현학습</a></li>
</ol>
]]></description>
        </item>
        <item>
            <title><![CDATA[[N332] TIL 및 회고]]></title>
            <link>https://velog.io/@sea_panda/N332-TIL-%EB%B0%8F-%ED%9A%8C%EA%B3%A0</link>
            <guid>https://velog.io/@sea_panda/N332-TIL-%EB%B0%8F-%ED%9A%8C%EA%B3%A0</guid>
            <pubDate>Tue, 27 Dec 2022 14:46:24 GMT</pubDate>
            <description><![CDATA[<h1 id="0-학습목표">0. 학습목표</h1>
<h2 id="level-1">Level 1.</h2>
<ul>
<li><strong>웹 애플리케이션과 웹 프레임워크</strong>에 대해 설명할 수 있다.</li>
<li><strong>Flask, JinJa, 라우팅, 블루프린터</strong>에 대해 설명할 수 있다.</li>
<li><strong>Flask</strong> 예제를 활용하여 간단한 웹 애플리케이션을 제작할 수 있다.</li>
<li>Bootstrap을 통해 HTML을 꾸미는 예제 코드를 재현할 수 있다.</li>
<li><strong>IP주소</strong>에 대해 설명할 수 있다.</li>
</ul>
<h2 id="level-2">Level 2.</h2>
<ul>
<li><strong>웹 애플리케이션을 제작</strong>하여 <strong>csv파일</strong>의 내용을 출력할 수 있다.</li>
<li><strong>JinJa템플릿</strong>을 활용하여 웹 애플리케이션의 사용자가 <strong>입력한 데이터</strong>를 활용할 수 있다.</li>
<li>웹 애플리케이션의 Endpoint마다 요구하는 기능을 구현할 수 있으며, <strong>HTTP 상태 코드를 전달</strong>할 수 있다.</li>
</ul>
<h2 id="level-3">Level 3.</h2>
<ul>
<li><strong>Application Factory</strong>를 기반으로 웹 애플리케이션을 제작할 수 있다.</li>
<li>제작한 웹 어플리케이션을 폴더와 라우팅에 따른 프레임워크 기반으로 설명할 수 있다.</li>
<li>Flask 기반 웹 어플리케이션에 데이터베이스를 연결하고 데이터를 읽고 쓰는 API를 제작할 수 있다.</li>
</ul>
<h2 id="level-4">Level 4.</h2>
<ul>
<li><strong>Flask-SQLAlchemy</strong>를 활용하여 웹 애플리케이션과 데이터베이스를 연결하고 조작할 수 있다.</li>
<li>Flask에서의 <strong>리다이렉트 및 사용자 세션처리</strong>에 대해 설명할 수 있다.</li>
<li><strong>웹 3계층 시스템 아키텍처</strong>에 대해서 설명할 수 있다.</li>
</ul>
<h1 id="1-주요개념">1. 주요개념</h1>
<h2 id="flask">Flask</h2>
<p><img src="https://velog.velcdn.com/images/sea_panda/post/68ca5442-1f90-4d5a-9067-ff4e69ccb79b/image.png" alt=""></p>
<p>장고(Django)나 플라스크(Flask)에 대해서는 파이썬을 접하게 되면 한 번은 들어봤을 수도 있다. 이 두 개의 프레임워크는 파이썬을 사용해 웹 애플리케이션을 작성할 수 있도록 도와준다.</p>
<p>그 중에서 Flask에 대해서 배우게 될 것이다. </p>
<p><a href="https://flask.palletsprojects.com/en/2.3.x/">Flask</a>는 마이크로 웹 프레임워크(Micro Web Framework)이다. 웹 프레임워크는 웹 애플리케이션을 개발할 수 있도록 웹 서비스(Web Service)나 웹 API(Web API)등을 제공하고 웹 개발과 배포를 할 수 있는 특정 방법을 제공한다. 즉, 뭔가를 만들어낼 수 있는 도구 모음을 제공한다고 보면 된다.</p>
<p>Flask는 <strong>웹 프레임워크</strong>이긴 하지만 <strong><span style='color:orange'>마이크로</span></strong>가 앞에 붙는다.즉, 이러한 도구 모음들이 최소한의 크기로 줄여진 것이다. 예를 들어 일반적인 웹 프레임워크를 손톱깎이 세트로 비유하자면 손톱깎이를 비롯한 여러 가지가 들어 있는 것이다. Flask는 여기에서 정말 최소한의 도구들을 모아놓은 것이다.</p>
<p>기본적으로 프레임워크에 따라 패키지와 라이브러리 등 모듈들의 컬렉션이 있어 개발이 수월할 수 있도록 도와준다. Jinja, Werkzeug, Flask-SQLAlchemy 등 다양한 패키지들과 라이브러리들이 존재한다.</p>
<h3 id="flask-시작하기">Flask 시작하기</h3>
<p><strong>1. Flask 시작</strong>
우선 파이썬 환경에 Flask를 먼저 설치해야 한다.</p>
<pre><code class="language-python">pip install flask</code></pre>
<br>

<p><strong>2. 폴더 생성하기</strong>
다음으로 Flask 애플리케이션을 저장할 폴더를 새로 만들어 준다. 이름은 통상적으로 애플리케이션 이름으로 지정해준다. 이번 예시에서는 <code>flask_app</code>을 사용하겠다.
그리고 폴더 안에 <code>__init__.py</code>이라는 파일을 하나 만들면 다음과 같은 구조가 된다.</p>
<pre><code>flask_app
└── __init__.py</code></pre><br>

<p><strong>3. Flask 애플리케이션 생성하기</strong>
이번에는 <code>__init__.py</code>파일에 코드를 담아보겠다. 먼저 Flask를 사용해서 웹 애플리케이션을 만드는 방법은 다음과 같다.</p>
<pre><code class="language-python"># __init__.py
from flask import Flask

app = Flask(__name__)</code></pre>
<p>코드를 보면 <code>Flask(__name__)</code>은 해당 애플리케이션의 이름을 지정해 주고 있다. 비록 아무 기능은 없지만 Flask 웹 애플리케이션을 생성한 것이다.</p>
<p><strong>4. CLI로 실행하기</strong>
CLI 명령어로 실행할 때에는 <strong>프로젝트 폴더 상위 디렉토리</strong>에서 다음과 같이 실행해 주면 된다.</p>
<pre><code>FLASK_APP = (폴더명) flask run</code></pre><p>위에서는 프로젝트 폴더명을 <code>flask_app</code>으로 했기 때문에 폴더명 자리에 <code>flask_app</code>이 들어간다.
그러면 다음과 같은 화면이 나타난다.
<img src="https://velog.velcdn.com/images/sea_panda/post/5d80d850-4b26-4e68-bd2f-2c283d9851c9/image.png" alt=""></p>
<p>여기서 제일 마지막에 적혀있는 문구를 살펴보겠다.</p>
<pre><code>Running on http://127.0.0.1:5000/ (Press CTRL+C to quit)</code></pre><p>실행한 웹 애플리케이션을 시작했는데 이 애플리케이션에 접속할 수 있는 주소를 알려주고 있다. 바로 <code>http://127.0.0.1:5000/</code>이다. <code>127.0.0.1</code>은 <code>localhost</code>이고 5000번 포트에서 작동하고 있다는 뜻이다.</p>
<p>웹 브라우저를 키고 접속하면 애플리케이션에 접근할 수 있지만 아무 설정도 없기 때문에 404(Not Found)페이지가 보일 것이다.
<img src="https://velog.velcdn.com/images/sea_panda/post/0e08ca18-c763-4cd7-a3e5-b3699e11c007/image.png" alt=""></p>
<p>어플리케이션을 종료하기 위해서는 <code>Press CTRL+C to quit</code>이 명령대로 해주면 된다. 만일 이 방법으로 종료하지 않으면 프로세스를 따로 찾아서 종료를 해줘야 한다. 터미널만 종료한다고 꺼지는 것이 아니다. 명심하자.</p>
<p><strong>5. 라우트 추가하기</strong>
이제 애플리케이션에 접속하면 <strong>&quot;Hello World!&quot;</strong>가 나타나도록 코드를 수정해보겠다.
기존 파일에 다음과 같은 작업을 추가한다.</p>
<pre><code class="language-python"># __init__.py

from flask import Flask

app = Flask(__name__)

@app.route(&#39;/&#39;)
def index():
    return &#39;Hello World!&#39;</code></pre>
<p>이제 코드를 하나씩 뜯어서 살펴보자.</p>
<ul>
<li><p><code>@app.route(&#39;/&#39;)</code>
애플리케이션의 루트 주소 (&#39;/&#39;)에 접속했을 때에 실행하라는 뜻이다. URL에 따라 실행하게 될 함수를 지정하는 역할이다. 즉, 엔드 포인트(endpoint)를 설정하게 되는데, 이때 엔드 포인트란 주소가 어떻게 끝나는지를 말하고 있는 것이다.<br>
기본적으로 기본 URL 뒤에는 슬래시(/)가 붙어야 하기 때문에 만일 &#39;/&#39;없이 그저 <code>@app.route(&#39;about&#39;)</code>와 같은 설정을 하게 되면 Flask를 실행할 때에 에러를 일으킨다.</p>
</li>
<li><p><code>def index()</code>
루트 주소로 접속했을 때 실행되는 함수로 &quot;Hello World!&quot;라는 문자열을 반환한다.</p>
</li>
</ul>
<p>이제 이 코드를 실행해보면 이전과 달리 <code>index()</code>함수가 실행되면서 &quot;Hello World&quot;라는 문구를 볼 수 있다.
<img src="https://velog.velcdn.com/images/sea_panda/post/4fef0850-16ce-4951-857e-16891e84ecf2/image.png" alt=""></p>
<blockquote>
<p>❗️ <strong>만약 동일한 라우트가 여러개라면?</strong>
라우트(Route) 자체는 경로, 서로 다른 네트워크 간 데이터를 전송하고 전송한 데이터를 받는 경로를 의미한다. 즉, 위의 <code>@app.route(&#39;/&#39;)</code>는 <code>기본경로 URL + /</code>라는 경로를 통해서 데이터를 전송하고 받는다는 것을 의미한다는 것이다.<br>
그런데 만약 동일한 라우트가 여러 개가 있다면 Flask에서는 HTTP Request를 받고 처리할 때에 가장 먼저 엔드 포인트 조건이 맞는 라우트를 사용한다. </p>
</blockquote>
<p><strong>6. 라우트 기능들</strong>
Flask에서는 라우트를 다양하게 설정할 수 있다. 앞서 했던 것처럼 기본적으로 엔드 포인트를 지정할 때도 있지만 URL 내에서 변수를 받을 상황도 있을 수 있다. 그리고 시각적인 부분 없이 접속이 되는 라우트들도 있을 수 있다. 이러한 기능들을 여기에서 다 다루지는 않지만 몇 가지만 살펴보겠다.
<br></p>
<ul>
<li><strong>HTTP Request 메서드</strong></li>
</ul>
<p>기본적으로 Flask의 라우트 데코레이터를 사용하게 되면 3가지 <a href="https://hahahoho5915.tistory.com/62">HTTP Request</a>메서드를 허용한다. 바로 GET, HEAD, OPTIONS이다. 하지만 이외에 POST, PUT, PATCH, DELETE등 다른 메서드들을 통과하다록 설정하기 위해서는 데코레이터 함수에 methods라는 인자를 추가하면 된다.</p>
<p>예를 들어 <code>@app.route(&#39;/&#39;)</code>에 POST메서드를 추가하려면 다음과 같이 할 수 있다.</p>
<pre><code class="language-python">@app.route(&#39;/&#39;,methods=[&#39;POST&#39;,&#39;GET&#39;])
def index():
    ...</code></pre>
<p>이제는 GET과 POST라는 메서드가 통과된다. 그리고 HEAD와 OPTIONS라는 메서드들은 이제 사용이 불가하다.<br><br></p>
<ul>
<li><strong>세부 엔드 포인트</strong></li>
</ul>
<p>URL을 보다 보면 슬래시(/)로 이어진 긴 엔드 포인트들이 있다. 예를 들어 유어클래스에서 코스 카드를 하나 선택하면 다음과 같은 URL이 하나 뜨게 된다.</p>
<p><code>https://urclass.codestates.com/course/37/curriculum</code></p>
<p>여기에서 <code>course</code> 뒤에 <code>37</code>이라는 숫자가 보이고 <code>curriculum</code>이라는 단어가 잇따라 온다. 이처럼 URL을 세부적으로 지정하고 주소에서 값을 전달할 수 있다.</p>
<p>index의 번호에 따라 해당 번호가 포함된 <code>Welcome to Index {번호}</code>를 리턴하도록 Flask애플리케이션에 index라우트를 만들어보겠다. </p>
<p>다음과 같이 라우트를 설정할 때 엔드포인트에 꺽쇠(&lt;&gt;)모양으로 어떠한 변수를 설정할 수 있다. 그리고 꺽쇠 안에는 변수의 이름을 지정해주고 함수에서는 변수를 그대로 받아 사용할 수 있다.</p>
<pre><code class="language-python">@app.route(&#39;/index/&lt;num&gt;&#39;)
def index_number(num):
    return &#39;Welcome to Index %i&#39; % int(num)</code></pre>
<p>한 번 살펴보자.</p>
<ul>
<li><p><strong>&lt; num &gt;</strong>
num이라는 이름의 변수를 만들어 받게 하도록 한다. 따라서 사용자가 입력하는 값과 상관없이 변수로 받게된다. 만약에 <code>index/hello</code>를 넘겨주면 <code>hello</code>가 값이 된다. 따라서 변수의 타입을 신경 써야 하기도 한다.</p>
</li>
<li><p><strong>int(num)</strong>
들어오는 변숫값의 타입을 변환해 준다. 그 이유는 기본적으로 URL을 통해 들어오게 되는 값은 문자열 타입이기 때문이다. 따라서 숫자로 표현하기 위해서는 변환해 주는 작업이 필요하다.</p>
</li>
</ul>
<p>만약에 숫자를 넘겨주지 않으면 어떻게 될까요? 예를 들어 숫자가 안 주어지면 0을 기본으로 설정해 주는 방법은 다음과 같다.</p>
<pre><code class="language-python">@app.route(&#39;/index/&#39;, defaults={ &#39;num&#39; : 0 })
@app.route(&#39;/index/&lt;num&gt;&#39;)
def index_number(num):
    return &#39;Welcome to Index %i&#39; % int(num)</code></pre>
<ul>
<li><strong>블루프린트 사용하기</strong></li>
</ul>
<p>기능이 많아질수록 라우트도 많아진다. 그렇기 때문에 이러한 라우트들을 하나의 파일로 모아서 사용하지 않고 기능별로 나눠서 블루프린트 기능을 사용한다. 블루프린트는 Flask에서 여러 개의 라우트를 한 곳에 묶어둘 수 있는 기능이 있다.</p>
<p>사용하는 방법은 다음과 같다.</p>
<p>그리고 <code>routes</code>라는 폴더를 만들어 그 안에 <code>user_routes.py</code>라는 파이썬 파일을 생성한다. 이제 프로젝트 구조는 다음과 같다.</p>
<pre><code>flask_app
├── __init__.py
└── routes
    └── user_routes.py</code></pre><p>그리고 <code>user_routes.py</code>파일에는 다음과 같이 코드를 작성한다.</p>
<pre><code class="language-python"># user_routes.py

from flask import Blueprint

bp = Blueprint(&#39;user&#39;, __name__, url_prefix=&#39;/user&#39;)

@bp.route(&#39;/&#39;)
def index():
    return &#39;User index page&#39;</code></pre>
<p>위에서 Blueprint의 인수로 들어가는 것은 다음과 같다.</p>
<ul>
<li><code>user</code>:블루프린터의 이름</li>
<li><code>__ name__</code>:블루프린트의 import이름</li>
<li><code>url_prefix=&#39;/user&#39;</code>: URL 접두어 설정(해당 블루프린트의 라우트는 URL 앞에 <code>/uesr</code>가 자동으로 붙게 된다.)</li>
</ul>
<p>그리고 <code>__init__.py</code>파일에서 해당 파일을 불러와서 사용할 수 있다.</p>
<pre><code class="language-python"># __init__.py

from flask import Flask
from flask_app.routes import user_routes

app = Flask(__name__)
app.register_blueprint(user_routes.bp)

@app.route(&#39;/&#39;)
def index():
    return &#39;Hello World!&#39;</code></pre>
<p>이제 위 코드를 실행하여 <code>http://127.0.0.1:5000/user/</code>로 접속하면 다음과 같은 문구가 보이게 된다.
<img src="https://velog.velcdn.com/images/sea_panda/post/5dd73196-dadb-4347-b493-b86cc7881c65/image.png" alt=""></p>
<h3 id="application-factory">Application Factory</h3>
<p>Flask를 통해서 애플리케이션을 만드는 방법을 살펴봤다. 하지만 추후에 프로젝트가 커지고 파일들이 많아지면 import를 사용할 일이 많아진다. 이때 파이썬에서 <a href="https://blog.mathpresso.com/python-circular-imports-e89c5bf16510">circular import(순환참조)</a>를 피하기 위해서 Flask에서는 애플리케이션 팩토리 패턴을 추천하고 있다.</p>
<p>말 그대로 애플리케이션을 만드는 <strong>&quot;공장&quot;</strong>을 세우는 것이다. 여태까지는 글로벌한 컨텍스트에서 <code>app</code>을 선언하고 사용했다. 하지만 이렇게 되면 여러 개의 애플리케이션을 동시에 사용하거나 <code>app</code>이 선언되어 있는 파일의 일부분만 필요할 때에도 문제가 발생할 수 있다.</p>
<p>이러한 잠재적인 문제점들을 피하기 위해 사용되는 방법이 함수를 따로 만드는 것이다. 기본적인 패턴은 다음과 같다.</p>
<p><code>__init__.py</code>파일에서 다음과 같은 코드가 들어가게 된다.</p>
<pre><code class="language-python">from flask import Flask

def create_app():
    app = Flask(__name__)

    from yourapplication.views.admin import admin
    from yourapplication.views.frontend import frontend
    app.register_blueprint(admin)
    app.register_blueprint(frontend)

    return app

if __name__ == &quot;__main__&quot;:
  app = create_app()
  app.run()</code></pre>
<p>보시는 것처럼 함수 안에 애플리케이션을 생성해 준다면 해당 함수를 실행하기만 하면 원하는 애플리케이션이 하나 만들어진다. 이제는 다른 파일에서 import를 해도 문제가 될 일도 줄어든다.</p>
<p>블루프린트도 함수 내에서 import를 해온 뒤에 애플리케이션에 추가해 준다.</p>
<p>앞으로 작성하는 Flask 애플리케이션은 이 패턴으로 작성하면 좋다.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[[N331] TIL 및 회고]]></title>
            <link>https://velog.io/@sea_panda/N331-TIL-%EB%B0%8F-%ED%9A%8C%EA%B3%A0</link>
            <guid>https://velog.io/@sea_panda/N331-TIL-%EB%B0%8F-%ED%9A%8C%EA%B3%A0</guid>
            <pubDate>Mon, 26 Dec 2022 15:09:40 GMT</pubDate>
            <description><![CDATA[<h1 id="0-학습목표">0. 학습목표</h1>
<h2 id="level-1">Level 1.</h2>
<ul>
<li>Docker가 필요한 이유를 설명할 수 있다.</li>
<li>Docker image에 대해 설명할 수 있으며, Whalesay예제를 재현할 수 있다.</li>
<li>Docker Container에 파일을 복사하는 예제를 재현할 수 있다.</li>
<li>Docker Hub, Docker container, Docker Image 사이의 관계를 알고 설명할 수 있다.<h2 id="level-2">Level 2.</h2>
</li>
<li>Docker Container가 Linux Container기술에서 시작했다는 것을 알고, LXC의 <strong>세 종류의 구획화</strong>에 대해 설명할 수 있다. </li>
<li>Docker Image 이름과 Docker Container 실행 명령어의 구성을 각각 설명할 수 있으며 Docker Container를 실행할 수 있다.</li>
<li>Docker Container의 터미널을 활용하여 Container 내의 폴더 구조를 파악할 수 있으며, 로컬 환경과 파일을 주고 받을 수 있다.</li>
<li><strong>포트</strong>에 대해서 설명할 수 있으며, Container에서 로컬로 <strong>포트 포워딩</strong>을 할 수 있다.<h2 id="level-3">Level 3.</h2>
</li>
<li>Docker Docs에서 필요한 명령어와 옵션을 찾아서 실행할 수 있다.</li>
<li><strong>Yaml문법</strong>을 이해하고, Docker-Compose를 활용하여 여러 개의 Container를 다룰 수 있다.<h2 id="level-4">Level 4.</h2>
</li>
<li>Dockerfile 에 사용되는 문법을 이해하고, 원하는 Docker Image 를 제작 후 배포할 수 있다.</li>
<li>Docker network 를 활용하여 여려 개의 Container 를 연결하고 활용할 수 있다.</li>
<li>Docker 와 Linux Container 사이의 관계를 설명할 수 있다.</li>
<li>Kubernetes 와 컨테이너 표준 사이의 관계를 설명할 수 있다.</li>
<li>가상 머신에 대해서 이해하고, Docker 와 가상 머신을 비교하여 설명할 수 있다.<h1 id="1-주요개념">1. 주요개념</h1>
<h2 id="docker도커">Docker(도커)?</h2>
<blockquote>
<p>“Docker takes away repetitive, mundane configuration tasks and is used throughout the development lifecycle for fast, easy and portable application development.”</p>
</blockquote>
</li>
</ul>
<p>일반적으로 <strong>Server(서버)</strong>를 관리한다는 것은 복잡하고 어려우며 고급 개발자들의 섬세한 작업이 필요한 영역이다.</p>
<p>개발을 하면서 시간이 흐르다보면 서버 환경이 계속 바뀐다. <code>CentOS</code>에 익숙해지면 <code>Ubuntu</code>를 써야할 일이 생긴다거나 하는 일이 생긴다.<em>(둘 다 Linux운영체제의 여러 버전)</em> 게다가 최근 <code>DevOps</code>의 등장으로 개발주기가 짧아지면서 배포는 더 자주 이루어지고 <a href="https://www.youtube.com/watch?v=ZRpsB3ODr6M">마이크로 서비스</a> 아키텍쳐가 유행하면서 프로그램은 더 잘게 쪼개어져 관리는 더 복잡해진다.</p>
<p>그리고 리소스 격리성에 대해서 이해하기 위해 IP와 Port Number에 대해 잠깐 이야기 해보겠다. IP주소는 인터넷상에 있는 컴퓨터의 고유 주소로, 인터넷상의 한 컴퓨터에서 다른 컴퓨터로 데이터를 주고받을 수 있게 해준다. Port number는 IP주소와 함께 쓰여 해당하는 프로토콜에 의해 사용된다. 비유하자면, 우리가 물건을 어떤 사람의 방까지 전달해준다고 하자. IP 주소는 단지 집 주소까지, Port Number는 방 주소까지라고 생각하면 된다. 이제 본격적으로 서버 관리자들에게 다음과 같은 요구가 들어온다고 가정해보자.</p>
<blockquote>
<ul>
<li>웹서버 1은 IP는 A로 하고 포트 번호는 A-1로 하고, 방화벽 규칙은 a의 규칙을 이용하라.</li>
</ul>
</blockquote>
<ul>
<li>웹서버 2는 IP는 B로 하고 포트 번호는 B-1로 하고, 방화벽 규칙은 b의 규칙을 이용하라.</li>
</ul>
<p>이런 두가지 요구에 대해서 서버가 하나빡에 없어서 IP주소를 구분하기 위해 <a href="https://ko.wikipedia.org/wiki/%EB%84%A4%ED%8A%B8%EC%9B%8C%ED%81%AC_%EB%B8%8C%EB%A6%AC%EC%A7%80">브릿지 설정</a>(뭔지 모르겠다...)을 변경해야 하고, 방화벽 규칙 a와 b가 서로 충돌이 일어날 것이다.</p>
<p>이러한 문제를 해결하고자, 하나의 컴퓨터에서 여러 개의 컴퓨터를 이용하는 것처럼 하기 위해서 <strong>&quot;리소스 격리성&quot;</strong>을 이용한다. 그리고 이런 리소스 격리성을 제공하는 기술로 <strong>VM( virtual machine)</strong>과 바로 이번에 배우는 <span style="color:orange"><strong>Docker</strong></span>가 있다. 두 방법 모두 격리성을 제공하기 때문에 각 애플리케이션마다 다른 컴퓨터에서 실행되는 것처럼 IP, Port 등을 다르게 설정할 수 있다.
<img src="https://velog.velcdn.com/images/sea_panda/post/810ec21b-21d8-4abd-aad1-8f7e7c3ede34/image.png" alt=""></p>
<blockquote>
<p>👉 <strong>Docker(좌측), Virtual Machine(우측)</strong></p>
</blockquote>
<ul>
<li>도커는 Virtual Machine만큼 견고한 격리성을 제공하지 않는다.</li>
<li>도커는 리눅스의 컨테이너를 이용한 기술로, OS 위에 다른 OS를 실행하는 것이 아니기 때문에 Virtual Machine보다 좋은 성능을 낼 수 없다.</li>
</ul>
<blockquote>
<p>👉 <strong>Virtual Machine(가상머신)이란?</strong>
Docker가 등장하기 전, 프로그래머들은 OS(Operation System)를 가상화하여 사용하는 방식을 사용했다. 구체적인 예를 들어 <strong>OS의 가상화</strong>가 무엇인지 설명해보겠다.<br>
A는 지금까지 Windows가 설치 된 개노트북에서 개발을 해 왔다. 그런데 A는 이번에 눈여겨보고 있던 최신 애플 노트북을 새로 사게 되었다. 지금까지 Windows에 사용한 프로그램들을 다시 MAC컴퓨터에서 다시 설치하려고 한다.<br>
여기서 문제가 발생한다. Windows에서 설치한 프로그램들이 IOS와 호환되지 않는 것들이 매우 많다는 것이다. 이미 설정해둔 설정값이나 자료들 역시 새로운 노트북에서 다시 설정하기 매우 힘이 드는 작업이다.<br>
이러한 문제를 해결하기 위해 새로 구매한 Mac컴퓨터 안에 원래 사용하고 있던 Windows환경을 설치하기로 했다. 이것이 바로 <strong>OS의 가상화</strong>이다.
<img src="https://velog.velcdn.com/images/sea_panda/post/a65982f3-32f7-481e-bc29-f45bf1954be8/image.png" alt=""></p>
</blockquote>
<p>위 그림은 또 다른 OS를 설치하는 과정을 그림으로 나타낸 것이다.</p>
<ul>
<li>Window내에서 VMware를 설치한다.</li>
<li>VMware를 사용하여 hypervisor를 설치한다.</li>
<li>hypervisor를 통해 Guest OS로 접근한다.</li>
<li>Guset OS 안에서 개발을 진행한다.<br>
우리는 원래 사용하는 OS를 <strong>Host OS</strong>,즉 <strong>주인 운영체제</strong>라고 부른다. 그리고 설치한 새로운 OS를 <strong>Guest OS</strong>, 즉 <strong>손님 운영체제</strong>라고 부른다. 컴푸터를 2대를 따로 사는 것 대신에, 하나의 하드웨어에서 2개의 운영체제를 가지고 있는 것이다.<br>
짧게 정리하자면, 하나의 하드웨어 안에서 또 다른 OS를 만드는 것을 Virtual Machine(가상머신)이라고 한다.<br>
이런 가상머신은 하나의 하드웨어에서 두 가지의 OS를 사용할 수 있다는 장점 덕분에 많은 인기를 누렸다. 하지만 단점도 존재했다. 가상머신은 하나의 하드웨어가 또 다른 OS를 유지하기 위해서 엄청나게 많은 자원(Resource)이 사용된다. 또, 하드웨어를 나눠써야 하는 점 때문에 효율성이 떨어지고 실행 속도도 느리다는 단점이 있다.</li>
</ul>
<blockquote>
<p>👉 <strong>Docker의 장점</strong></p>
</blockquote>
<ul>
<li><strong>인프라를 편하게 가져올 수 있다.</strong>
인프라를 이미지로 만들었기 때문에 저장된 이미지들만 관리한다.
중앙 보관소에 있는 이미지를 가져와서 체계적인 관리와 테스트를 할 수 있다.</li>
<li><strong>용량이 가볍고 빠르다.</strong>
OS와 컨테이너 환경을 분리하여 가볍고 어디서든 실행 가능하게 만들어 준다.</li>
<li><strong>쉽게 삭제하고 복수할 수 있다.</strong>
이미지를 사용하여 개발환경을 동시에 여러개 만들 수 있고 수정/배포가 간단하므로 테스트가 매우 쉽다.</li>
</ul>
<p><strong>Docker</strong>를 짧게 정의하자면, 애플리케이션 실행 환경을 코드로 작성할 수 있고 OS를 격리화하여 관리하는 기술,또는 그런 기술을 제공하는 <strong>컨테이너 기반의 오픈소스 가상화 플랫폼</strong>을 의미한다. 그리고 이 Docker는 <strong>Linux Container</strong>라는 기술에서 시작되었다.</p>
<p>따라서 이번 섹션에서는 Docker의 사용방법을 상세히 다루기 이전에 <strong>Linux Container</strong>기술에 대해서 학습한 후, Docker의 사용 방법등에 대해서 다룬다.</p>
<h2 id="linux-container">Linux Container</h2>
<p><img src="https://velog.velcdn.com/images/sea_panda/post/15d8543e-7eb9-4534-aa85-94842f0683eb/image.png" alt=""></p>
<p>리눅스 기반의 기술 중에 하나로 필요한 라이브러리와 어플리케이션을 모아서 마치 별도의 서버처럼 구성한 것을 말한다. 컨테이너를 이루는 네트워크 설정, 환경 변수 등의 시스템 자원은 각 컨테이너가 독립적으로 소유하고 있다.</p>
<h3 id="1-프로세스의-구획화">1. 프로세스의 구획화</h3>
<ul>
<li>특정 컨테이너에서 작동하는 프로세스는 기본적으로 그 컨테이너 안에서만 액세스 할 수 있다.</li>
<li>컨테이너 안에서 실행되는 프로세스는 다른 컨테이너의 프로세스에게 영향을 줄 수 없다.<h3 id="2-네트워크의-구획화">2. 네트워크의 구획화</h3>
</li>
<li>기본으로 컨테이너 하나에 IP주소가 할당되어 있다.<h3 id="3-파일시스템의-구획화">3. 파일시스템의 구획화</h3>
</li>
<li>컨테이너 안에서 사용되는 파일 시스템은 구획화되어 있다. 그렇기 때문에 해당 컨테이너에서의 명령이나 파일 등의 액세스를 제한할 수 있다.</li>
</ul>
<p>가상머신과 얼핏보면 동일해 보이지만 비슷할 뿐 가상머신(가상화)와는 다른 기술이다.</p>
<ul>
<li><p>Container아키텍쳐
<img src="https://velog.velcdn.com/images/sea_panda/post/2a194c2d-3311-4aca-a824-9a6c41579dc6/image.png" alt=""></p>
</li>
<li><p>VM아키텍쳐
<img src="https://velog.velcdn.com/images/sea_panda/post/d4a1a25c-c13c-4312-9fd2-5ccc728a40f2/image.png" alt=""></p>
</li>
<li><p>두 개의 차이 정리
<img src="https://velog.velcdn.com/images/sea_panda/post/dcdce331-62ce-4235-bb46-57564c8cef47/image.png" alt=""></p>
</li>
</ul>
<p>조금 더 상세히 설명하자면 기존 가상화 방식은 주로 OS를 가상화 했다. 위의 예시에서 등장한 <strong>VMware</strong>같은 VM은 호스트 OS위에 게스트 OS 전체를 가상화하여 사용하는 방식이다. 이 방식은 비교적 사용법이 간단하지만 무겁고 느려서 운영환경에선 사용할 수 없다.(Docker 컨테이너는 보통 MB단위 크기지만, VM은 GB크기를 가진다고 한다.) 이런 상황을 개선하기 위해서 반가상화 방식의 Xen이 등장한다.</p>
<p>하지만 전가상화든 반가상화든 추가적인 OS를 설치하여 가상화하는 방법은 어쨋든 성능 문제가 있었고 이를 개선하기 위해 <strong>프로세스를 격리</strong>하는 방식이 등장한다. 그리고 이런 프로세스를 격리하는 방식을 리눅스에서 <strong>리눅스 컨테이너</strong>라고 한다. 단순히 프로세스를 격리시키기 때문에 가볍고 빠르게 동작하게 되는 것이다. CPU나 메모리는 딱 프로세스가 필요한 만큼만 추가로 사용하고 성능적으로도 거의 손실이 없다.</p>
<h2 id="docker-container">Docker Container</h2>
<p>리눅스 컨테이너는 리눅스에서 프로세스를 격리하는 방식이라고 했다. <strong>Docker Conatainer</strong>는 Docker에서 이런 프로세스를 격리하는 방식이다. </p>
<p>도커는 <strong>Container</strong>라는 물체를 운반한다. 컨테이너는 <strong>인프라를 비롯한 프로그램을 어떤 환경에서나 실행 가능 할 수 있도록 해주는 개체</strong>를 의미한다. </p>
<p>프로그램을 만들기 위해서는 다양한 인프로가 필요한데, 도커는 이 인프라들을 각각의 장소에서 <strong>하나씩 가져오는 것이 아니라</strong>, <strong>Container</strong>라는 보관함에 담아서 한 장소에서 가져오는 것이다.</p>
<p>예를 들자면 쿠팡이나 지마켓과 같은 쇼핑몰 웹사이트를 만들기 위해서는 FE,BE, DB 등과 같은 구성요소들이 필요하다. 이때, 구성요소들을 모두 Docker Container형태로 가져오면, 각기 다른 장소에서 설치하는 시간을 줄일 수 있다.</p>
<h1 id="docker-사용하기">Docker 사용하기</h1>
<p><img src="https://velog.velcdn.com/images/sea_panda/post/fc6bbd2f-a3fc-47aa-b984-56de601ef18b/image.png" alt=""></p>
<p>도커를 이용하는 데 있어서 명령어, 옵션 등 사용법은 <a href="https://docs.docker.com/">Docker Docs</a>에서 확인할 수 있다. Docker CLI뿐만 아니라 사용법과 환경을 구성하는 방법에 대해서 설명되어 있다.</p>
<h2 id="docker-image">Docker Image</h2>
<p><img src="https://velog.velcdn.com/images/sea_panda/post/3547bfac-41fe-469d-a5e8-81b20ef52ebb/image.png" alt=""></p>
<p>본격적으로 도커를 사용하기 앞서 <strong>Docker Image</strong>라는 개념을 이해해야 한다. Image는 컨테이너와 함께 도커를 사용하는데 있어서 가장 중요한 개념이다. </p>
<p>이미지는 <strong>컨테이너 실행에 필요한 파일과 설정값 등을 포함하고 있는 것</strong>으로 상태 값을 가지지 않고 변하지 않는다.(Immutable). 컨테이너는 이미지를 실행한 상태라고 볼 수 있고 추가되거나 변하는 값은 컨테이너에 저장된다. 같은 이미지에서 여러개의 컨테이너를 생성할 수 있고 컨테이너의 상태가 바뀌거나 컨테이너가 삭제되더라도 이미지는 변하지 않고 그대로 남아있다.</p>
<p>말 그대로 이미지는 컨테이너를 실행하기 위한 모든 정보를 가지고 있기 때문에 더 이상 의존성 파일을 컴파일 하고 이것저것 설치할 필요가 없다. 이제 새로운 서버가 추가되면 미리 만들어 둔 이미지를 다운받고 컨테이너를 생성만 하면 된다. 한 서버에 여러 개의 컨테이너를 실행할 수 있고, 수십, 수백, 수천 대의 서버도 문제없다.</p>
<p>도커 이미지는 <a href="https://hub.docker.com/">Docker Hub</a>에 등록하거나 <strong>Docker Registry</strong> 저장소를 직접 만들어 관리할 수 있다.</p>
<h3 id="docker-image-예제">Docker Image 예제</h3>
<p><code>docker/whalesay</code>라는 이미지를 통해 예제를 실습해보겠다.
<img src="https://velog.velcdn.com/images/sea_panda/post/9e9d1bfc-36c7-4d1b-a2fa-70491761b765/image.png" alt=""></p>
<p>Docker Image의 이름은 레지스트리 계정, 레포지토리 이름, 태그 세 가지 정보로 구성되어 있다.</p>
<ul>
<li><p><strong>레지스트리(Registry)</strong></p>
<ul>
<li>도커 이미지가 관리되는 공간을 의미한다.</li>
<li>특별히 다른 것을 지정하지 않는다면 도커 허브(Docker Hub)를 기본 레지스트리로 설정한다.</li>
<li>레지스트리는 Docker Hub, Private Docker Hub, 회사 내부용 레지스트리 등으로 나뉠 수 있다.</li>
</ul>
</li>
<li><p><strong>레포지토리(Repository)</strong></p>
<ul>
<li>레지스트리 내에 도커 이미지가 저장되는 공간이다.</li>
<li>이미지 이름이 사용되기도 한다.</li>
<li>Github의 레포지토리와 비슷한 개념이다.</li>
</ul>
</li>
<li><p><strong>태크(Tag)</strong></p>
<ul>
<li>같은 이미지라고 할지라도 버전 별로 안의 내용이 조금 다를 수 있다.</li>
<li>해당 이미지를 설명하는 <strong>버전 정보</strong>를 주로 입력한다.</li>
<li>특별히 다른 것을 지정하지 않는다면 <code>latest</code>태그를 붙인 이미지를 가져온다.</li>
</ul>
</li>
</ul>
<p>자 그럼 다시 <code>docker/whalesay</code>라는 문장을 다시 읽어보면 다음과 같은 뜻을 가진 것을 알 수 있다.</p>
<blockquote>
<p>👉 <code>Docker Hub</code>라는 레지스트리에서 <code>docker</code>라는 계정이 등록한 <code>whalesay</code>레포지토리에서 <code>lastest</code>태크를 가진 이미지</p>
</blockquote>
<p><img src="https://velog.velcdn.com/images/sea_panda/post/6c54052f-12bf-4dec-8d7d-8d1777331b0e/image.png" alt=""></p>
<p>👆 실제 docker/whalesay</p>
<p>이제 실제 이미지를 가져와서 실행까지 진행해보자.</p>
<p>먼저 다음 명령어를 통해서 레지스트리에서 이미지 혹은 레포지토리를 가져온다. 이 과정을 <strong>Pull</strong>이라고 한다.</p>
<pre><code>$ docker image pull docker/whalesay:latest</code></pre><p>※ <code>docker image pull</code>만 아니라 <code>docker pull</code>을 사용하여 검색해도 많은 정보를 찾을 수 있다고 한다.</p>
<p>다음 명령어로는 이미지 리스트를 출력해 볼 수 있다.</p>
<pre><code>$ docker image ls</code></pre><p>받아온 이미지를 실행시켜 보자.(이미지 -&gt; 컨테이너)</p>
<pre><code>$ docker container run --name myName docker/whalesay:latest cowsay boo</code></pre><p><img src="https://velog.velcdn.com/images/sea_panda/post/8ce35e2e-78fb-417d-a572-67a2fecc4d9b/image.png" alt=""></p>
<p>각각의 명령에 대해서 간단히 정리하면 다음과 같다.</p>
<blockquote>
<ul>
<li><strong>{container} run</strong><ul>
<li>컨테이너를 실행한다.</li>
</ul>
</li>
</ul>
</blockquote>
<ul>
<li><strong>option</strong>:<ul>
<li><code>--name</code>: 컨테이너의 이름을 할당한다.</li>
</ul>
</li>
<li><strong>COMMAND</strong>:<ul>
<li>cowsay: 컨테이너에서 cowsay를 호출한다.</li>
</ul>
</li>
<li><strong>ARG..</strong>:<ul>
<li><code>boo</code>: COMMAND인 cowsay에 넘겨질 파라미터이다.</li>
</ul>
</li>
</ul>
<p>다음 명령어를 이용하면 종료된 컨테이너를 포함하여 모든 컨테이너를 볼 수 있다.</p>
<pre><code>$ docker container ps -a</code></pre><ul>
<li><strong>{container} ps</strong>: 컨테이너의 리스트를 출력한다.</li>
<li><strong>-a</strong>: Default로는 실행되는 컨테이너지만 종료된 컨테이너를 포함하여 모든 컨테이너를 출력한다.</li>
</ul>
<p>그리고 만일 컨테이너를 삭제하고 싶다면 다음과 같은 명령어로 삭제할 수 있다.</p>
<pre><code>$ docker container rm myName</code></pre><ul>
<li>{container} rm: 컨테이너를 지칭해서 삭제한다. 컨테이너를 명시할 때는 ps명령을 통해 확인할 수 있는 NAMES 혹은 CONTAINER ID를 사용한다.</li>
</ul>
<p>이미지는 다음과 같이 지우면 된다.</p>
<pre><code>$ docker image rm docker/whalesay</code></pre><p>위와 같이 각 과정을 따로 진행할 수 있지만 이미지를 받아오고, 컨테이너로 실행하고, 컨테이너와 관련된 리소스를 지우는 작업을 한 번에 실행할 수도 있다.</p>
<pre><code>$ docker container run --name my_name --rm docker/whalesay cowsay boo</code></pre><ul>
<li>{container} run: 컨테이너를 실행한다. <strong>이미지가 없다면 이미지를 받아온 뒤(pull) 실행한다.</strong></li>
<li><strong>--rm</strong>: <strong>컨테이너를 일회성으로 실행한다.</strong> 컨테이너가 종료될 때 컨테이너와 관련된 리소스를 모두 제거한다.
이미지까지 완벽하게 제거하려면 <pre><code>$ docker image rm docker/whalesay</code></pre>까지 실행하면 된다.</li>
</ul>
<p>도커는 같은 기능을 수행하더라도 여러 명령으로 실행될 수 있다. 아직 docker 측에서 특정 구문만 이용하라는 말이 없다. 그래서 웹서핑을 하면서 같은 기능을 하더라도 다른 docker 구문으로 구성되어 있는 경우를 잘 파악할 수 있어야 한다.</p>
<p>하나의 예시로 <code>image rm</code>과 <code>rmi</code>는 같은 기능을 수행한다.</p>
<p><br><br><br></p>
<blockquote>
<p>❗ <strong>참고자료</strong></p>
</blockquote>
<ol>
<li><a href="https://subicura.com/2017/01/19/docker-guide-for-beginners-1.html">초보자를 위한 도커 안내서</a></li>
<li>코드스테이츠 N331 Lecture Note</li>
</ol>
]]></description>
        </item>
        <item>
            <title><![CDATA[[N324] TIL 및 회고]]></title>
            <link>https://velog.io/@sea_panda/N323-TIL-%EB%B0%8F-%ED%9A%8C%EA%B3%A0-rqs0ntfe</link>
            <guid>https://velog.io/@sea_panda/N323-TIL-%EB%B0%8F-%ED%9A%8C%EA%B3%A0-rqs0ntfe</guid>
            <pubDate>Wed, 21 Dec 2022 15:12:43 GMT</pubDate>
            <description><![CDATA[<h1 id="0-학습목표">0. 학습목표</h1>
<ul>
<li>NoSQL 단어 유래에 대해 이해하고 설명할 수 있다.</li>
<li>NoSQL 종류를 이해하고 구분할 수 있다.</li>
<li>NoSQL 종류벼 특징에 대해 설명할 수 있다.</li>
<li>문서형(Document)데이터베이스를 활용할 수 있다.<h1 id="1-주요개념">1. 주요개념</h1>
<h2 id="1-nosqlnot-only-sql">1. NoSQL(Not Only SQL)</h2>
<span stlye="color:orange"><strong>NoSQL</strong></span>은 Not Only SQL의 약어로 <em>조핸 오스카슨(Johan Oskarsson)</em>이 2009년 6월 샌프란시스코에서 조직한 모임에서 관계형 데이터 모델을 쓰지않는 연구를 빠른 시간에 다양한 사람들과 논의하기 위해서 트위터의 해시태그에 사용할 중복되지 않으면서도, 부정적이여서 사람들의 끌 수 있는 단어를 생각하다 NoSQL을 찾았고, 이를 해시태그로 사용하여 현재 비관계형 데이터베이스 기술을 아우르는 말이 되었다.</li>
</ul>
<p>2000년대 초 웹 시장의 발전과 함께 데이터 소스와 데이터의 양이 폭발적으로 증가하기 시작했다. 이런 웹 서비스의 데이터는 XML, JSON으로 처리되는데 관계형 데이터베이스로 처리하기에는 데이터 설계시간이 오래걸리기 시작했다. 그리고 하나의 서버를 크게 만드는 것(수직확장)보다, 여러개의 서버를 연결(수평확장)시켜 확장하는 방법이 더 비용적으로 우수하게 되었다. 따라서 한 대에서 실행되도록 설계된 관계형 데이터베이스보다는 여러 대의 컴퓨터에 분산하여 저장할 수 있고, XML, JSON 등의 데이터를 처리하는데 시간이 짧은 NoSQL이 등장하게 된다.</p>
<p>관계형 DB와 비관계형 DB는 만들어진 방식, 저장하는 정보의 종류, 그리고 저장하는 방법 등에 차이가 있다. 관계형 DB는 테이블을 사전에 정의를 한 뒤에 그에 알맞은 형태의 데이터만 넣을 수 있다. 각각의 행은 하나의 속성에 대한 정보를 저장하고, 열에는 각각의 데이터 형식에 맞는 데이터가 저장된다. 특정한 형식을 지키기 때문에 데이터가 제대로 추가되었다면 꺼낼 때에는 수월하다. SQL을 활용해 원하는 정보를 쿼리할 수 있다. 즉, 관계형 DB에서는 스키마가 뚜렷이 보인다. 덕분에 테이블 간에 관계들이 어떻게 되는지 알 수 있다. </p>
<p>그렇다고 NoSQL이 스키마가 반드시 없는 것은 아니다. 관계형 DB가 데이터를 쓸 때 스키마에 맞춘다면, 반면에 비관계형 DB(NoSQL)은 데이터를 읽어올 때 스키마에 따라 읽어온다. 읽어올 때만 데이터 스키마가 사용되기 때문에 쓸 때는 따로 정해진 것이 없다는 의미는 아니다. 결국 어떻게 쓰냐가 어떻게 읽어와야 하는지에 대한 영향을 미친다.</p>
<table>
<thead>
<tr>
<th>SQL</th>
<th>NoSQL</th>
</tr>
</thead>
<tbody><tr>
<td><center>관계형 DB는 SQL을 이용해서 데이터를 테이블에 저장.<br>미리 작성된 스키마를 기반으로 정해진 형식에 맞게 데이터를 저장.</center></td>
<td><center>Key-value, document, graph, wide-column형식 등의<br> 방식으로 데이터를 저장할 수 있다.</center></td>
</tr>
<tr>
<td><center>고정된 스키마가 필요</center></td>
<td><center>동적인 스키마.<br>행 추가 시 즉시 열 추가 가능<br>개별 속성에 대해서 반드시 모든 열에 대한 데이터를 입력하지 않아도 됨.</center></td>
</tr>
<tr>
<td>구조화된 쿼리 언어를 정보 요청에 사용</td>
<td>데이터 그룹 자체를 조회하는 것에 초점<br>구조화 되지 않은 쿼리로도 요청가능<br>UnQL(Unstructured Query Language)라고도 함.</td>
</tr>
<tr>
<td><center>수직적 확장(높은 메모리, CPU를 사용하는 확장)<br>데이터베이스가 구축된 하드웨어의 성능을 많이 이용<br>고비용</center></td>
<td>수평적 확장(보다 값싼 서버 증설, 또는 클라우드 서비스를 이용하는 확장)<br>많은 트래픽 처리에 용이하도록 서버를 추가적으로 구축<br>높은 효율성</td>
</tr>
</tbody></table>
<blockquote>
<p><strong>NoSQL 기반 DB data type</strong>
<strong>- Key-Value type</strong>: 데이터를 <code>key-value</code>의 쌍을 속성으로 하는 배열의 형태로 저장한다. Redis, Dynamo 등이 대표적인 Key-value형식의 DB이다.<br>
<strong>- 문서형(Document) DB</strong>: 데이터를 테이블이 아닌 문서처럼 저장하는 DB를 의미한다. JSON유사 형식으로 데이터를 문서화하는 것이 일반적이다. 각각의 문서는 하나의 속성에 대한 데이터를 가지고 있고, 컬렉션이라고 하는 그룹으로 묶어서 관리한다. 대표적인 문서형 DB에는 MongoDB가 있다.<br>
<strong>- Wide-Column DB</strong>: DB의 열(Column)에 대한 데이터 관리를 집중하는 DB이다. 각 열에는 Key-value형식으로 데이터가 저장되고, 컬럼 패밀리(Column families)라고 하는 열의 집합체 단위로 데이터를 처리할 수 있다. 하나의 행에 많은 열을 포함할 수 있어서 높은 유연성을 자랑한다. 데이터 처리에 필요한 열을 유연하게 선택할 수 있다는 점에서 규모가 큰 데이터 분석에 주로 사용되는 DB형식이다. 대표적인 DB는 Cassandra, HBase가 있다.</p>
</blockquote>
<p>SQL기반의 관계형 DB는 DB의 ACID성질을 준수해야하는 경우, 소프트웨어에 사용되는 데이터가 구조적이고 일관적인 경우에 사용하면 좋다. ACID성질을 준수하면 DB의 무결성을 보호할 수 있다. 전자 상거래, 금융 서비스를 위한 소프트웨어 개발에서는 이 성질을 준수하는 것이 필수 옵션으로 되어 있어 SQL을 이용한 솬계형 DB를 이용하는 것이 보편적이다. 또한 프로젝트의 규모가 많은 서버를 필요로 하지 않고 일관된 데이터를 사용하는 경우, 보통 관계형 DB를 사용한다. 다양한 데이터 유형과 높은 트래픽을 지원하도록 설계된 NoSQL DB를 굳이 사용할 이유가 없다.</p>
<p>NoSQL기반의 DB는 데이터의 구조가 거의 또는 전혀 없는 대용량의 데이터를 저장하거나, 클라우드 컴퓨팅 및 저장공간을 최대한 활용하는 경우, 빠르게 서비스를 구축하고 데이터 구조를 자주 업데이트 하는 경우에 사용하면 좋다. 대부분의 NoSQL DB는 저장할 수 있는 데이터 유형에 제한을 설정하지 않는다. 필요에 따라서 데이터의 새 유형을 추가할 수 있다. 그렇기 때문에 소프트웨어 개발에 정형화 되지 않은 많은 양의 데이터가 필요한 경우, NoSQL을 적용하는 것이 효율적일 수 있다. 그리고 클라우드 기반으로 DB저장소를 구축하면 저렴한 비용의 솔루션을 제공 받을 수 있다. 소프트웨어에 데이터베이스의 확장성이 중요하다면 여러 데이터 센터에 걸쳐서 많은 번거로움 없이 확장할 수 있는 NoSQL DB를 사용하는 것이 좋다. 또한 스키마를 미리 준비할 필요가 없기 때문에 빠르게 개발하는 과정에 매우 유용하다. 또한 소프트웨어 버전별로 많은 다운타임없이 데이터 구조를 자주 업데이트 해야하는 경우, 일일이 스키마를 수정해주어야 하는 관계형 DB보다는 NoSQL 기반의 비관계형 DB를 사용하면 더 좋다.</p>
<h2 id="2-mongodb">2. MongoDB</h2>
<p><img src="https://velog.velcdn.com/images/sea_panda/post/18730c89-4a5e-440f-a4da-f1ab7f06e6ce/image.png" alt=""></p>
<p>MongoDB는 문서형 데이터베이스를 사용하는 NoSQL 기반의 비관계형 데이터베이스이다.
<img src="https://velog.velcdn.com/images/sea_panda/post/58ef1719-d9ad-432e-a3d2-c54a0cbea748/image.png" alt=""></p>
<p>문서들은 BSON(Binary JSON)형태로 저장되고 정해진 틀이나 데이터 타입이 없다. 따라서 자유롭게 문자, 숫자, 객체, 배열 등을 저장할 수 있다.</p>
<p>SQL과 비교하자면 자유롭게, 즉, 사전에 정의된 테이블 필드나 관계에 맞춰 할 필요없이 데이터를 추가할 수도 있다. 그렇기 때문에 보통은 일관되지 않은 데이터들을 추가해야하거나 혹은 재빠르게 데이터를 쌓아야 할 때 사용되기도 한다. SQL에 비해 비교적 자유롭지만 스키마가 아예 없는 것은 아니다. 각 문서를 저장할 때에는 자유롭게 저장해도 되지만 자유롭게 저장하는 만큼 읽어올 때에 일종의 스키마가 있어야 수월하게 가져올 수 있다. SQL에서는 각 테이블을 생성할 때에, 그리고 각 테이블의 관계들을 사전에 정의할 때에 스키마를 정했지만 NoSQL에서는 데이터를 읽을 때에 특정 스키마에 따라 데이터를 불러와야 하기도 한다.</p>
<p>MongoDB에서는 <strong>MongoDB Atlas</strong>라는 클라우드 데이터베이스 서비스로 인터넷을 이용해 어디서든 접속 가능한 서비스를 제공한다. 상세한 설정 방법은 [링크]를 참고하자.</p>
<h2 id="3-pymongo">3. Pymongo</h2>
<p>Python에서는 MongoDB를 사용하기 위해서 <code>pymongo</code>라이브러리를 사용한다. Database 커넥터이기 때문에 MongoDB연결과 해제, 데이터 입출력과 조작, 결과 반환 등의 기능을 제공한다.</p>
<h2 id="pymonogo-설치">pymonogo 설치</h2>
<pre><code class="language-python">pip install pymongo</code></pre>
<h2 id="mongodb-연결하기">MongoDB 연결하기</h2>
<pre><code class="language-python">from pymongo import MongoClient
client = MongoClient({URI})</code></pre>
<p>URI의 형식은 다음과 같으며 이는 절대적인 것이 아닌 Atlas의 경우이다.
실제 구체적인 URI는 다음 링크를 참고하자.</p>
<blockquote>
<p>💡 <strong>[URI]</strong></p>
</blockquote>
<pre><code class="language-python">mongodb+srv://{USER}:{PASSWORD}@{HOST}/{DATABASE_NAME}?retryWrites=true&amp;w=majority</code></pre>
<h2 id="작업흐름">작업흐름</h2>
<p>Pymongo의 작업 흐름은 SQL을 이용했던 흐름과 다르다. 이전에는 <code>connection</code>과 <code>cursor</code>를 통해서 연결을 맺고 쿼리를 실행했다면 이번에는 연결하는 것은 마찬가지로 <code>connection</code>으로 하지만 그 후에는 <code>database</code>를 선택하고 <code>documents</code>와 상호작용하는 방식은 다르다.</p>
<pre><code class="language-python">client = MongoClient({URI})        # 연결
database = client[DATABASE_NAME]   # DB 선택 or 생성
collection = database[COLLECTION_NAME]    #collection 조작</code></pre>
<p>콜렉션을 생성한 뒤에는 문서를 해당 콜렉션에 삽입할 수 있다.</p>
<p>이전에 진행했던 SQL쿼리문들과 달리 여기에서는 추가하는 것이 매우 쉽다. 특히 테이블에 대한 사전 정의나 구조가 없기 때문에 JSON형태를 바로 저장할 수 있다.</p>
<pre><code class="language-python">collection.insert_one({document}) # insert_one: 문서 하나 추가</code></pre>
<p>더 많은 명령어와 사용법은 공식문서를 참고하자.</p>
<blockquote>
<p>💡 <strong>[pymongo 공식문서]</strong></p>
</blockquote>
<h1 id="2-회고">2. 회고</h1>
<p>드디어 Sprint2의 정리를 끝냈다. 이번 단원에서는 다양한 프로그램을 사용해보는 거라 명령어 자체가 많이 없는게 좋다. 그리고 강의노트를 짧게 정리하면서 정말 내가 필요한 것만 찾아서 정리해야겠다고 다시금 다짐했다. 정리는 정말 복습하면서 정리한다는 느낌으로...짧게 가져가고 내가 하고싶은거 공부 해야겠다. 이제 곧 새해인데 일단 버텨...
[링크]:<a href="https://junho85.pe.kr/1979">https://junho85.pe.kr/1979</a>
[URI]:<a href="https://ko.wikipedia.org/wiki/%ED%86%B5%ED%95%A9_%EC%9E%90%EC%9B%90_%EC%8B%9D%EB%B3%84%EC%9E%90">https://ko.wikipedia.org/wiki/%ED%86%B5%ED%95%A9_%EC%9E%90%EC%9B%90_%EC%8B%9D%EB%B3%84%EC%9E%90</a>
[pymongo 공식문서]:<a href="https://pymongo.readthedocs.io/en/4.1.1/index.html">https://pymongo.readthedocs.io/en/4.1.1/index.html</a></p>
]]></description>
        </item>
        <item>
            <title><![CDATA[[N323] TIL 및 회고]]></title>
            <link>https://velog.io/@sea_panda/N323-TIL-%EB%B0%8F-%ED%9A%8C%EA%B3%A0</link>
            <guid>https://velog.io/@sea_panda/N323-TIL-%EB%B0%8F-%ED%9A%8C%EA%B3%A0</guid>
            <pubDate>Tue, 20 Dec 2022 19:17:00 GMT</pubDate>
            <description><![CDATA[<h1 id="0-학습목표">0. 학습목표</h1>
<ul>
<li>API를 이해하고 사용할 수 있어야한다.</li>
<li>RESTful API에 대해서 설명할 수 있어야한다.</li>
<li>API의 데이터를 받아와 데이터베이스에 저장할 수 있어야한다.</li>
</ul>
<h1 id="1-주요개념">1. 주요개념</h1>
<h2 id="1-api">1. API</h2>
<p>[N314]에서 API가 무엇인지 간단히 다루었다. 이번에는 조금 더 상세하게 다루어 보고자 한다.</p>
<p>API란 &quot;Application ProgrammingInterface&quot;의 줄임말로 정의 및 프로토콜 집합을 사용하여 두 소프트웨어 구성요소가 서로 통신할 수 있게 하는 메커니즘이라고 앞서 정리하였었다. 이 API를 더 이해하기 위해서는 <span style="color:orange"><strong>클라이언트</strong></span>와 <span style="color:orange"><strong>서버</strong></span>도 같이 이해해야 어떤 방식으로 작동하는지 이해할 수 있다.
<img src="https://velog.velcdn.com/images/sea_panda/post/8db17b06-ca89-4875-a997-d68fe3ff604f/image.png" alt=""></p>
<p>위의 사진을 통해서 API의 예시를 들어보면, 음식점에서 손님은 뭔가를 요청하고 있는 <span style="color:orange"><strong>클라이언트</strong></span>이고 메뉴는 <span style="color:orange"><strong>API</strong></span>이다. 손님은 주방에 들어가서 조리되는 음식을 보고 메뉴를 정하지 않는다. 또한 메뉴판에 없는 음식을 주문할 수도 없다. 메뉴판을 보고 음식을 고를 수 있는 이유는 메뉴판을 통해 어떤 음식이 나올지 어느 정도 예상하고 있기 때문이다. 그렇기 때문에 메뉴판은 손님과 주방 사이의 규칙으로 볼 수도 있다. 만약에 피자를 시켰는데 햄버거가 나온다면 메뉴판은 제 역할을 수행하지 못한 것이고, 손님 입장에서는 원했던 음식이 나오지 않게 되는 것이다.</p>
<p>여기서 중요한 점은 <strong>메뉴판(=API)</strong>은 단지 문서일 뿐이라는 것이다. 따라서 구체적인 실체가 존재하지 않는다. 하지만 몇몇 문서와 업체에서는 API를 어떤 특정한 서비스나 기능처럼 설명하기도 한다.</p>
<p>여기서 메뉴판의 주문을 주방장이 직접 받아서 요리까지 한다면 효율이 떨어질 것이다. 그래서 주문을 대신 받아 전달해주는 웨이터를 가게에 고용했다고 생각해보자 바로 이 웨이터가 <span style="color:orange"><strong>API Server</strong></span>이다. 웨이터가 음식 주문을 받고, 조리된 음식을 전달하듯이 API server도 Service Server의 결과를 전달해준다. 즉 클라이언트와 Service server가 조금 더 원할하게 소통할 수 있게 도와준다.</p>
<p>그리고 주방장에 해당하는 <span style="color:orange"><strong>Service server</strong></span>는 실제로 클라이언트의 요청에 대한 task를 처리하는 server이다.</p>
<p>API를 통해서 원하는 요리, 즉 데이터를 받으면 대체로 <strong>JSON형식</strong> 반환받는다. 대체로 그렇다는 것이지 API를 통한 서버의 응답에는 정해진 형식이 없으며 경우와 상황에 따라 다르다. 하지만 실제로 많이 사용하는 Web API는 앞서 말한 <strong>JSON</strong>형식을 가장 많이 사용한다.</p>
<blockquote>
<p>💡 <strong>JSON??</strong>
JSON(Javascript Object Notation)는 Javascript에서 Object를 표기하는 방식을 의미한다.<br>
얼핏 보기에는 파이썬이 &quot;Dictionary&quot;를 표기하는 방식과도 비슷하게 생겼다. 실제로도 아래의 JSON예시를 그대로 파이썬 변수값으로 입력해도 문제가 없다. 그대로 가져다 사용할 수 있는 셈이다. JSON은 다른 프로그래밍에서도 사용되고 있을 정도로 표준처럼 자리 잡았다. 그렇기 때문에 Javascript에 국한되어 있지 않고 널리 사용된다.<br>
또한 JSON을 이용한 구조는 사람들도 나름 쉽게 읽고 이해할 수 있고 어플리케이션에서도 쉽게 이해할 수 있는 장점이 있다.</p>
</blockquote>
<pre><code class="language-javascript">{
  &quot;glossary&quot;:{
    &quot;title&quot;:&quot;example glossary&quot;,
    &quot;GlossDiv&quot;:{
      &quot;title&quot;:&quot;S&quot;,
      &quot;GlossList&quot;:{
        &quot;GlossEntry&quot;:{
          &quot;ID&quot;:&quot;SGML&quot;,
          &quot;SortAs&quot;:&quot;SGML&quot;,
          &quot;GlossTerm&quot;:&quot;Standard Generalized Markup Language&quot;,
          &quot;Acronym&quot;:&quot;SGML&quot;,
          &quot;Abbrev&quot;:&quot;ISO 8879:1986&quot;,
          &quot;GlossDef&quot;:{
            &quot;para&quot;:&quot;A meta-markup language, used to create markup languages such as DocBook.&quot;,
            &quot;GlossSeeAlso&quot;:[
              &quot;GML&quot;,
              &quot;XML&quot;
            ]
          },
          &quot;GlossSee&quot;:&quot;markup&quot;
        }
      }
    }
  }
}</code></pre>
<h2 id="2-http-api">2. HTTP API</h2>
<p><strong>HTTP</strong>는 <span style="color:orange"><strong>HyperText Transfer Protocol</strong></span>이라는 약어로 컴퓨터들의 기술적인(알고리즘, 데이터 형식, 계층구조 등..) 통신규약(protocol) 중 하나이다. 하나의 컴퓨터가 다른 컴퓨터와 파일을 받거나 전송하거나 하는 등의 소통을 하고 싶을 때에 정해진 규칙과 틀을 준수해야 원활한 소통이 가능하다.</p>
<p>예를 들어 이메일을 주고 받을 때에는 이메일 사이트로 로그인해서 받은 편지함을 보면 되지만 실제로 받아야 하는 메일을 받아야 하는 이메일 주소로 보낼 수 있도록 해주는 규약들이 있다. <code>POP3</code>, <code>SMTP</code>, <code>IMAP</code>등이 그러한 규약들이다.
<img src="https://velog.velcdn.com/images/sea_panda/post/503b0bfa-8a7e-45ac-8ed3-0bd46020a1a5/image.png" alt=""></p>
<p><strong>HTTP</strong>는 웹에서 통신할 때 사용되는 규약이다. 따라서 모든 컴퓨터는 웹에서는 HTTP를 사용하여 소통한다. HTTP를 사용하게 된다면 웹과 관련된 작업을 한다는 것을 표현하는 것이기도 하다. 위의 그림에서 나오듯이 HTTP를 통한 소통은 크게 <strong>요청(HTTP Request)</strong>과 <strong>응답(HTTP Response)</strong>로 나눠져있다.</p>
<hr>
<p><strong>HTTP Request</strong>
한 컴퓨터가 다른 컴퓨터에 리소스 요청을 보낼 때 사용되는 말이다. 보통 요청을 하는 컴퓨터는 클라이언트라 부르고 요청을 받는 컴퓨터는 서버라고 부른다. 이러한 요청을 보낼 때 사용하는 것들을 HTTP 요청 메소드라고 하며, 다양한 메서드들을 [MDN HTTP Request Methods]에서 확인 가능하다.</p>
<p>다양한 메서드들 중에서 데이터를 다룰 때 큰 틀의 기준이 되는 4가지 요청인 <strong>[CRUD]</strong>에 해당하는 메소드는 다음과 같다.
<img src="https://velog.velcdn.com/images/sea_panda/post/4fbdca14-a808-4884-b1b0-0d3b85dccb1b/image.png" alt=""></p>
<p>이러한 요청들을 특정 방법으로 사용하도록 정해진 것은 아니다. <code>DELETE</code>메소드를 사용해 회원가입을 진행할 수 있고 <code>GET</code>으로 업데이트도 할 수 있다. 이렇게 할 수 있는 이유는 클라이언트와 서버가 사전에 약속된 방법만 있다면 작동에는 문제가 없기 때문이다.</p>
<p>물론 어느 HTTP메소드인지에 따라 제한이 있다. <code>GET</code>이나 <code>DELETE</code>와 같은 경우에는 주소에만 데이터를 담아 넘길 수 있다. 복잡한 데이터 형태를 넘기기에는 제한이 많다.</p>
<p>그리고 각각의 CRUD요청은 각각의 주소를 가지게 되고, 클라이언트는 각각의 주소로 요청을 보내게 되는데, 모든 CRUD요청마다 주소가 생기면 주소의 수가 너무 많아져 관리가 어려워지고, 기능이 겹치는 주소가 담긴 API에 버그가 생긴다. 이를 해결하기 위하여 사람들은 CRUD를 하나의 주소로 관리하는 API, <code>RESTful API</code>를 사용하기 시작했다. API를 제작할 때에는 보통 REST가이드라인을 따라 제작된다. 그리고 이 가이드라인을 따라 HTTP메소드들이 사용된다.</p>
<hr>
<p><strong>HTTP Response</strong>
HTTP Request를 보내면 이 요청은 HTTP규약을 통해서 보낸 요청이기 때문에 응답 또한 HTTP규약에 따른 응답을 받게 된다.</p>
<p>클라이언트 측에서 요청을 보내게 되는 경우 서버 측에서도 다양한 응답을 보내게 되고, 각 응답은 기본적으로 상태코드([Status Code])라는 것을 가지고 있다. HTTP요청에 대한 상태가 어떤지 알려주는 것이다. 상태 코드는 총 5개의 종류로 나누게 된다.</p>
<blockquote>
<p>💡 <strong>HTTP 상태 코드 분류</strong></p>
</blockquote>
<ul>
<li>100번대: 정보응답</li>
<li>200번대: 성공응답</li>
<li>300번대: 리다이렉션 메시지</li>
<li>400번대: 클라이언트 에러 응답</li>
<li>500번대: 서버 에러 응답</li>
</ul>
<p>이런 응답코드는 웹페이지를 열어 개발자 도구를 연 뒤에 네트워크 탭으로 들어가면 실제로 보내지는 HTTP요청과 응답을 볼 수 있다.
<img src="https://velog.velcdn.com/images/sea_panda/post/04fc0386-0707-41c3-9477-c32cb2d580ab/image.png" alt=""></p>
<h2 id="3-restful-api">3. RESTful API</h2>
<p><strong>[REST]</strong>는 REpresentational State of Transfer의 줄임말이다. 지금 널리 사용되고 있는 <strong>World Wide Web</strong>(WWW)와도 같은 분산 하이퍼미디어 시스템을 위한 소프트웨어 아키텍쳐의 한 형식이다. 여기서 중요한 것은 소프트웨어의 아키텍쳐를 어떻게 형성할지에 대한 가이드라인이라는 것이다. 총 6개의 가이드라인이 존재하는데 다 따르게 된다면 해당 아키텍처를 <code>RESTful</code>이라고 부르게 된다.</p>
<p>조금 더 <strong>REST</strong>에 대해서 간결하게 정리하면 다음과 같다.</p>
<blockquote>
<ol>
<li>HTTP URI를 통해 자원(Resource)을 명시한다. 👉 자원</li>
<li>HTTP Method를 사용하여 👉 자원에 대한 행위</li>
<li>해당 자원(URI)에 대한 CRUD Operation을 적용한다. 👉 자원에 대한 행위의 내용</li>
</ol>
</blockquote>
<p>REST의 특징으로는 5가지를 들수 있으며 다음과 같다.</p>
<blockquote>
<ol>
<li>Server-Client(서버-클라이언트 구조)</li>
<li>Stateless(무상태)</li>
<li>Cacheable(캐시 처리 가능)</li>
<li>Layered System(계층화)</li>
<li>Uniform Interface(인터페이스 일관성)</li>
</ol>
</blockquote>
<hr>
<p>REST아키텍처는 HTTP를 사용할 때 특정 가이드라인을 제시한다. 만약 서버에서 이미지를 요청할 때 어떤 서버는 <code>GET</code>을 통해서 이미즈를 전달할 수 있고, 다른 서버는 <code>POST</code>요청을 통해 이미지를 전달할 수 있다고하자. 각 서버의 API가 다르기 때문에 유저는 사용할 때마다 각 서버의 API활용법을 개별적으로 알고 있어야 한다. 만일 서버가 늘어난다면 유저가 알고있어야 할 API는 더 늘어나게 되고 유저들의 피로감은 엄청날 것이다. 그래서 REST아키텍쳐라는 것이 등장하여 HTTP를 사용할 때 일종의 가이드라인을 제시해서 웹 API의 혼란 속에 질서를 세우려고 하는 것이다. 하지만 말 그대로 <strong>가이드 라인</strong>이기 때문에 모든 API가 따라야 하는 것은 아니다. 보통 RESTful API를 작성했다고 하면 HTTP 메소드를 다음과 같이 사용한다.</p>
<blockquote>
<ul>
<li><strong>GET</strong>: 데이터를 조회</li>
</ul>
</blockquote>
<ul>
<li><strong>POST</strong>: 데이터를 생성</li>
<li><strong>PATCH</strong>: 데이터를 업데이트(일부 변경)</li>
<li><strong>PUT</strong>: 데이터를 업데이트(전체 변경)</li>
<li><strong>DELETE</strong>: 데이터 삭제</li>
</ul>
<h1 id="2-회고">2. 회고</h1>
<p>RESTful API는 사실 아직 잘 이해는 못했다. 나중에 좀 더 찾아보자.
<br><br><br>
[N314]:<a href="https://velog.io/@sea_panda/N314-TIL-%EB%B0%8F-%ED%9A%8C%EA%B3%A0">https://velog.io/@sea_panda/N314-TIL-%EB%B0%8F-%ED%9A%8C%EA%B3%A0</a>
[MDN HTTP Request Methods]:<a href="https://developer.mozilla.org/en-US/docs/Web/HTTP/Methods">https://developer.mozilla.org/en-US/docs/Web/HTTP/Methods</a>
[CRUD]:<a href="https://ko.wikipedia.org/wiki/CRUD">https://ko.wikipedia.org/wiki/CRUD</a>
[Status Code]:<a href="https://developer.mozilla.org/en-US/docs/Web/HTTP/Status">https://developer.mozilla.org/en-US/docs/Web/HTTP/Status</a>
[REST]:<a href="https://restfulapi.net/rest-api-design-tutorial-with-example/">https://restfulapi.net/rest-api-design-tutorial-with-example/</a></p>
]]></description>
        </item>
        <item>
            <title><![CDATA[[N322] TIL 및 회고]]></title>
            <link>https://velog.io/@sea_panda/N322-TIL-%EB%B0%8F-%ED%9A%8C%EA%B3%A0</link>
            <guid>https://velog.io/@sea_panda/N322-TIL-%EB%B0%8F-%ED%9A%8C%EA%B3%A0</guid>
            <pubDate>Mon, 19 Dec 2022 14:34:48 GMT</pubDate>
            <description><![CDATA[<p><img src="https://velog.velcdn.com/images/sea_panda/post/4c7ee62f-1a15-40c4-958e-715c293edcf6/image.png" alt=""></p>
<h1 id="0-학습목표">0. 학습목표</h1>
<ul>
<li>웹 스크레이핑을 이해하고 설명할 수 있다.</li>
<li>파이썬을 통해서 웹 스크레이픙을 할 수 있다.</li>
<li>HTML 혹은 CSS를 설명할 수 있다.</li>
<li>DOM에 대해서 설명할 수 있다.</li>
<li>requests 라이브러리를 사용할 수 있다.</li>
<li>beautifulsoup 라이브러리를 사용할 수 있다.</li>
</ul>
<h1 id="1-주요개념">1. 주요개념</h1>
<h2 id="1-html-css">1. HTML, CSS</h2>
<p><img src="https://velog.velcdn.com/images/sea_panda/post/01385ad7-10ce-4765-9080-0f37ef505a0e/image.png" alt=""></p>
<p><img src="https://velog.velcdn.com/images/sea_panda/post/d24e3c42-80f5-4a44-b122-004a51e99b96/image.png" alt=""></p>
<p>위 사진은 구글 홈페이지의 <code>HTML</code>과 <code>CSS</code>이다. 왼쪽이 <code>HTML</code>, 오른쪽이 <code>CSS</code>이다.</p>
<h3 id="1-1-html">1-1. HTML</h3>
<p><span style='color:orange'><strong>HyperText Markup Language</strong></span>의 약자로 웹에서 페이즈를 표시할 때 사용한다. [MDN]에서는 웹페이지가 어떻게 구성되어 있어야 하는지 알려주는 <strong>마크업 언어</strong>라고 소개되어 있다. 즉, 웹페이지에서 보여지는 각 요소들이 어디에 위치해야 하는지 알려주는 마크업 언어이다.</p>
<p>HTML에서는 요소(element)라는 것들이 존재한다. 그리고 각 요소들은 Tag를 통해서 표현된다. 즉, <code>head</code>라는 요소는 <code>&lt;head&gt; &lt;/head&gt;</code>처럼 표현된다는 것이다. 이 태그를 잘 보면 두가지 태그로 이루어진 것을 알 수 있는데, 하나는 열어주는 태그(Opening Tag)이고 두 번째는 닫아주는 태그(closing Tag)이다. 두 태그 모두 꺽쇠를 사용하지만 닫아주는 태그만 슬래시 <code>/</code>를 사용한다. </p>
<p>여기서 주의해야하는 것은 <span style='color:red'><strong>모든 요소들에서 닫아주는 태그가 있는 것은 아니다.</strong></span> 요소에 따라서 열어주는 태그만 사용할 때도 있다. 빈 줄을 추가하는 <code>&lt;br&gt;</code>이나 수평으로 줄을 그어주는 <code>&lt;hr&gt;</code>과 같은 요소들이 그 예시이다.</p>
<p>그리고 하나의 요소 안에 다른 요소(Children)을 추가할 수 있다. 방식은 다음과 같다.</p>
<pre><code class="language-html">&lt;ul&gt;
  &lt;li&gt;hello&lt;/li&gt;
  &lt;li&gt;World&lt;/li&gt;
  &lt;li&gt;!&lt;/li&gt;
&lt;/ul&gt;</code></pre>
<p>위는 리스트를 HTML에서 표현할 때 쓰는 표현방식으로 이렇게 표현되는 리스트는 내부에 HTML요소가 자동으로 포함되어야 한다.</p>
<h3 id="1-2-css">1-2. CSS</h3>
<p>HTML이 웹페이지가 어떻게 구성되어야 있어야 하는지 알려주는 <strong>뼈대</strong>와 같은 역할을 한다면 CSS는 이를 꾸며주는, 즉 어떻게 표현되는지 알려주는 스타일시트 언어이다. Cascading Style Sheets의 약자로 알 수 있듯이 CSS는 HTML이 표현한 문서가 어떻게 표현되는지 알려준다. </p>
<p>HTML에서도 태그 내에 스타일에 대해서 알려줄 수 있다. 하지만 스타일에 관한 내용이 많아지면 HTML이 복잡해지고 편의성도 떨어지기 때문에 분리해서 사용한다.</p>
<p>CSS에서는 특정 요소를 선택할 수 있는 방법인 셀렉터라는 것들이 존재한다. 다양한 종류의 셀렉터가 존재하며, 그 중에서 기본적인 몇 개만 살펴보면 다음과 같다.</p>
<blockquote>
<ol>
<li>Type selector: CSS 타입에 따라서 선택할 수 있다.(ex. p, div)</li>
<li>Class selector: 클래스에 따라 선택할 수 있다.</li>
<li>Id selector: id에 따라 선택할 수 있다.</li>
</ol>
</blockquote>
<hr>
<p><strong>CSS 상속</strong>
스타일에 대한 문서를 작성할 때 주의해햐 할 점 중 하나가 상속이다. CSS는 요소의 위치에 따라서 상위 요소의 스타일을 상속받도록 되어있다. 이런 특성으로 인해 스타일을 반복작업을 거치지 않고도 하위 자식요소들에게 적용되지만 이에 따라 상속을 어떻게 받을지 잘 생각해야 한다.</p>
<pre><code class="language-html">&lt;div style = &quot;color:red&quot;&gt;
  &lt;p&gt;I have no style&lt;/p&gt;
&lt;/div&gt;</code></pre>
<p><code>p</code>태그는 아무런 스타일이 적용되어 있지 않아도 상위 요소인 <code>div</code>의 스타일을 상속받는다.</p>
<hr>
<p><strong>CSS Class Selecotor</strong>
클래스는 어떤 특정 요소들의 스타일을 정하고 싶을 때 사용된다. 동시에 여러 개의 요소들에 대한 스타일을 정할 때에 보통 클래스를 지정해서 상속받도록 정한다.</p>
<pre><code class="language-css">.banana {
    color:&quot;yellow&quot;;
}</code></pre>
<p>CSS에서는 위와 같은 방법을 통하여 <code>.</code>을 통해서 클래스를 정의할 수 있다. 이렇게 정의한 클래스는 HTML에서는 다음과 같은 방법으로 적용할 수 있다.</p>
<pre><code class="language-html">&lt;p class=&quot;banana&quot;&gt;I have a banana class&lt;/p&gt;</code></pre>
<p>이때 여러 개의 클래스도 동시에 부여할 수도 있다.</p>
<pre><code class="language-html">&lt;p class=&quot;banana fruit orange&quot;&gt;I have a banana class&lt;/p&gt;</code></pre>
<hr>
<p><strong>CSS ID Selecotor</strong>
클래스와 비슷하게 사용할 수 있는 것이 ID이다. HTML에서는 클래스뿐만 아닌 ID도 지정할 수 있다. 다만 ID는 보통 특정 HTML 요소를 가리킬 때에만 사용된다. 클래스와 달리 보통 여러 개의 요소에 사용되지 않는다. 이러한 차이점을 인지하고 ID 혹은 클래스를 구분할 수 있어야 한다.</p>
<p>CSS에서는 <code>#</code>기호를 통해서 스타일을 정할 수 있다.</p>
<pre><code class="language-css">#pink{
    color:&quot;pink&quot;;
}</code></pre>
<p>이 ID를 이용하는 HTML예시는 다음과 같다.</p>
<pre><code class="language-html">&lt;p id=&#39;pink&#39;&gt;My id is pink&lt;/p&gt;</code></pre>
<h2 id="2-dom">2. DOM</h2>
<p><strong>DOM</strong>은 <span style='color:red'><strong>Document Object Model</strong></span>의 약어로 웹페이지에서 매우 중요한 역할을 하는 문서 객체 모델이라고 불린다. 여기서 중요한 역할이란 HTML문서에 접근하기 위한 일종의 인터페이스 역할로 문서 내의 모든 요소를 정의하고, 각각의 요서에 접근하는 방법을 제공한다. 이러한 기능 덕분에 프로그래밍 언어에서도 웹페이지의 요소나 스타일 등을 추가하거나 수정하는 등 다양한 작업을 진행할 수 있다.</p>
<p>특히 DOM은 객체(Object)로 표현을 하는데 이 때 object란 JS(Java Script)에서 사용되는 데이터 구조 중 하나를 의미한다. 파이썬에는 이와 비슷한 것으로 dictionary가 존재한다.</p>
<p>즉, DOM을 통해서 HTML을 프로그래밍 언어에서 사용할 수 있는 데이터 구조 형태로 작업을 수행할 수 있어서 <strong>크롤링</strong> 등 <strong>웹 페이지</strong>와 작업할 때 매우 중요한 개념 중 하나이다.</p>
<hr>
<p><strong>DOM의 종류</strong>
W3C DOM 표준은 세가지 모델로 구분된다.
<strong>1. Core DOM</strong>: 모든 문서를 타입을 위한 DOM모델
<strong>2. HTML DOM</strong>: HTML문서를 위한 DOM모델
<strong>3. XML DOM</strong>: XML문서를 위한 DOM모델</p>
<hr>
<p>DOM을 사용할 수 있는 가장 손쉬운 방법 중 하나는 개발자 도구를 열어서 Console 창으로 들어가 JS를 통하여 DOM을 사용해 보는 것이다. 
<img src="https://velog.velcdn.com/images/sea_panda/post/57f75afc-25ab-474f-a3e7-6e066ddf88d6/image.png" alt=""></p>
<p>위 사진은 Console창에서 JS를 통해서 <code>NodeList</code>라는 이름의 <code>p</code>태그를 사용하는 요소들을 담은 유사 배열이라는 것을 받은 것이다. 이처럼 HTML이나 XML 등 웹페이지의 문서 형식을 프로그래밍 언어에서도 사용할 수 있는 큰 장점이 있다.</p>
<p>이와 비슷하게 DOM에는 다양한 기능들이 존재하며 대표적인 몇가지만 나타내어 보겠다.</p>
<blockquote>
<ul>
<li><code>getElementsbyTagName</code>: 태그 이름으로 문서의 요소들을 리턴한다.</li>
</ul>
</blockquote>
<ul>
<li><code>getElementById</code>: id가 일치하는 요소들을 리턴한다.</li>
<li><code>getElementByClassName</code>: class가 일치하는 요소들을 리턴한다.</li>
<li><code>querySelector</code>: selector와 일치하는 요소들을 리턴한다.</li>
<li><code>querySelectorAll</code>: selector와 일치하는 모든 요소들을 리턴한다.</li>
</ul>
<p>DOM은 크롤링 할 때에도 DOM의 개념은 중요하다. 예를 들어 파이썬에서 크롤링을 한다고 해도 웹 페이지를 텍스트, 즉 문자열로 읽게되면 원하는 정보를 찾기가 쉽지 않을 것이다. 하나의 거대 문자열로 웹페이지를 인식하게 되면 텍스트를 해석하고 원하는 정보를 찾을 때 구별하기 쉽지 않게 된다. 따라서 보통은 웹페이즈를 텍스트 형식으로 사용하는 것이 아닌 DOM을 활용한다.</p>
<pre><code class="language-html">&lt;html&gt;
    &lt;head&gt;
    &lt;/head&gt;
    &lt;body&gt;
        &lt;h1&gt;h1 태그입니다.&lt;/h1&gt;
        &lt;p&gt;p 태그입니다.&lt;/p&gt;
    &lt;/body&gt;
&lt;/html&gt;</code></pre>
<p>위와 같은 간단한 html이 있다고 하자. 여기서 만일 <code>h1</code>의 내용을 알고 싶을 때 html이 전부 문자열이였다면 태그를 구분할 수 있는 방법을 먼저 고민하고, 그 후에 태그 내부에 있는 정보를 받아 사용할 수 있다. 하지만 이렇게 되면 단순한 작업을 하는 것도 오래 걸리게 된다.</p>
<p>반면에 DOM을 사용하면 위에서 봤던 예시처럼 간단한 명령어를 통해서 태그 내용을 추출할 수 있다. 이런 편의성으로 인해 DOM이 중요하다.</p>
<h2 id="3-웹-스크레핑과-크롤링">3. 웹 스크레핑과 크롤링</h2>
<h2 id="웹스크레핑">웹스크레핑</h2>
<p>웹 스크레핑은 <strong>특정</strong> 웹 사이트나 페이지에서 <strong>필요한 데이터</strong>를 자동으로 추출해 내는 것을 의미한다. 원하는 데이터를 추출하기 위해서 특정 웹 사이트에 콘텐츠를 다운로드하기 위한 HTTP GET요청을 보낸다. 이에 사이트가 응답하면 HTML문서를 분석하여 특정 패턴을 지닌 데이터를 뽑아낸다. 그리고 추출된 데이터를 원하는 대로 사용할 수 있도록 데이터베이스에 저장한다.</p>
<p>웹 스크래핑은 자동으로 수집된 특정 정보가 필요한 분야에서 다양하게 활용되고 있다. 금융 및 주식 시장의 경우, 스크래핑 기술을 활용하여 뉴스 정보를 모으기도 하고, 애널리스트들이 투자 자문을 위해 활용할 수 있는 기업 재무제표 정보를 자동으로 수집하기도 한다. 전자 상거래 시장의 경우 경쟁력 확보를 위해 경쟁사 상품의 정보를 수집하고 가격 변동 이슈를 빠르게 파악하기 위해 스크래핑 기술을 활용하기도 한다.</p>
<p>웹 스크레핑은 <strong>특정</strong>사이트에 <strong>필요한 데이터</strong>를 찾는데 집중하기 때문에 데이터 포인트를 정확히 잡고 확실한 정보만을 수집할 수 있다는 점에서 유용한다. 장기적으로 서비스 대역폭이나 비용을 절약할 수 있다는 장점이 있다.</p>
<p>그리고 스크래핑은 방식에 따라서 크게 <strong>정적</strong> 스크래핑과 <strong>동적</strong> 스크래핑 나눌 수 있다.</p>
<p>정적 스크래핑은 정적인 데이터를 수집하는 방법으로 한 페이지 안에서 원하는 정보가 모두 들어나는 경우에는 이 방식을 활용하여 스크래핑할 수 있다. <code>beautifulsoup4</code>가 대표적인 라이브러리이다.</p>
<p>동적 스크래핑은 페이지에서 로그인, 스크롤 등의 이동이나 실시간으로 페이지의 내용이 계속 추가되거나 수정될 때 사용할 수 있는 방식이다. <code>Selenium</code>이 대표적인 라이브러리이다.</p>
<h2 id="웹크롤링">웹크롤링</h2>
<p>웹 크롤링이란 &quot;기어다니다&quot;라는 뜻을 지닌 이름에서도 알 수 있듯이 여러 웹사이트들을 기어다니며 원하는 정보를 탐색하고 수집하는 작업을 의미한다. 인터넷에 존재하는 방대한 양의 정보를 사람이 일일히 파악하는 것은 불가능한 일이다. 때문에 규칙에 따라 자동으로 웹 문서를 탐색하는 컴퓨터 프로그램, 웹 크롤러(Crawler)를 만들었다.</p>
<p>크롤러는 인터넷을 돌아다니며 여러 웹 사이트에 접속한다. 그리고 페이지의 내용과 링크의 복사본을 생성하여 다운로드하고 요약본을 만든다. 그리고 검색 시 유용한 정보만을 노출하도록 검색 색인을 붙인다. 이는 도서관에서 책을 찾기 위해 분류 기준을 구성하는 것과 비슷한 작업이다.</p>
<p>웹 크롤링은 웹상을 돌아다니며 방대한 양의 정보를 수집하기 때문에, 특정 키워드에 대한 심층 분석이 필요할 때 유용하다. 또한 크롤러는 실시간 정보 수집을 위해 계속해서 작동하므로 자주 변화하는 데이터를 파악하기가 좋다.</p>
<h2 id="차이점">차이점</h2>
<p>둘은 <strong>&quot;원하는 데이터를 모을 수 있다.&quot;</strong>라는 점이 비슷하여 의미가 자주 혼용되고 한다. 또한 기술적으로 함께 사용되는 경우가 많아 더욱 헷갈린다. 하지만 웹 크롤링은 웹 페이지의 링크를 타고 계속해서 탐색을 이어나가지만, 웹 스크레핑은 데이터 추출을 원하는 대상이 명확하여 특정 웹 사이트만을 추적한다는 차이점이 있다.</p>
<p>또한 웹 크롤링은 페이지를 모아 색인화하고 검색 결과에 내가 찾는 키워드와 관련된 링크만 모아 볼 수 있도록 작동한다. 하지만 웹 스크래핑은 상품의 가격, 주식정보 등 원하는 데이터가 명확하여, 흩어져있는 해당 데이터를 자동으로 추출하여 전달한다. 이 외에 차이점은 아래와 같다.
<img src="https://velog.velcdn.com/images/sea_panda/post/14fb7241-0c01-4ecf-bd00-4ccd55654bca/image.png" alt=""></p>
<h2 id="4-library-requests">4. Library: requests</h2>
<blockquote>
<p><strong>공식문서</strong>: <a href="https://requests.readthedocs.io/en/latest/">https://requests.readthedocs.io/en/latest/</a></p>
</blockquote>
<p>파이썬에서 사용할 수 있는 웹과 소통을 편하게 해주는 라이브러리이다. 이 라이브러리는 파이썬에서 HTTP요청을 보낼 때 거의 표준으로 사용되는 정도로 많이 사용되고 있다. 특히 HTTP요청을 간단한 메소드를 통해 실행할 수 있도록 짜여졌다는 것이 가장 큰 장점 중 하나이다. 따라서 사용자는 HTTP요청이나 응답에 대한 고민을 줄이고 실제 서비스나 기능에 더욱 집중할 수 있게 된다.</p>
<p>먼저 <code>pip</code>을 사용하여 라이브러리를 설치해준다.</p>
<pre><code class="language-python">pip install requests</code></pre>
<p>다음으로 라이브러리를 불러와서 원하는 사이트의 콘텐츠를 다운로드 하기 위한 Get요청을 다음과 같이 보낼 수 있다.</p>
<pre><code class="language-python">import requests

requests.get(&#39;{사이트주소}&#39;)
-------------------------
&lt;Response [200]&gt;    # 정상적으로 연결됐을 시 출력</code></pre>
<p>응답 객체의 <code>type</code>을 확인하면 다음과 같은 결과가 출력된다.</p>
<pre><code class="language-python">&lt;class &#39;requests.models.Response&#39;&gt;</code></pre>
<p>requests라이브러리의 <code>Response</code>타입인 것을 알 수 있다.</p>
<pre><code class="language-python">import requests

url = &#39;https://google.com&#39;

resp = requests.get(url)
print(resp.status_code)</code></pre>
<p>위 코드를 통해서 응답의 상태 코드를 확인할 수 있다. <code>200</code>이라고 출력된다면 정상적으로 요청이 처리된 것이다. </p>
<p>다음으로는 응답 객체를 통해 응답 내용을 살펴보겠다. 먼저 웹 브라우저를 통해 웹 페이지에 접속하게 되면 보이게 되는 HTML은 사실 브러우저에서 뒷작업을 거치면서 보여지게 되는 것이다. 그러나 본질은 HTML, CSS 등 문서 파일이라는 것은 변함이 없다.</p>
<p>따라서 requests라이브러리를 활용해서 웹 브라우저가 받는 동일한 HTML문서를 받을 수 있다. 물론 이것 뿐만 아니라 서버에서 보내주는 데이터도 받을 수 있다. </p>
<p>일단 <code>Response</code>객체에는 <code>text</code>라는 속성이 존재한다. 이 속성은 서버에서 받은 응답을 텍스트 형식으로 보여주게 된다. 서버에서 받게되는 응답의 데이터는 실제로 bytes로 받게 된다. 따라서 해당 데이터를 텍스트로 인지하기 위해서는 알맞게 디코딩 작업을 거쳐야 한다. 이런 디코딩 작업을 <code>text</code>속성이 알아서 해주기 때문에 보통은 걱정하지 않아도 된다. 그리고 <code>requests</code>의 기본적인 인코딩 방법은 <code>utf-8</code>이다.</p>
<pre><code class="language-python">resp.text
----------
&#39;&lt;!doctype html&gt;&lt;html itemscope=&quot; &quot;......&gt;&lt;head&gt;...&lt;/head&gt;&lt;/html&gt;&#39;</code></pre>
<p>출력을 자세히 살펴보면 하나의 HTML파일이다. </p>
<h2 id="5-library-beautifulsoup">5. Library: BeautifulSoup</h2>
<blockquote>
<p><strong>공식문서</strong>: <a href="https://www.crummy.com/software/BeautifulSoup/bs4/doc/">https://www.crummy.com/software/BeautifulSoup/bs4/doc/</a></p>
</blockquote>
<p><code>requests</code>를 통하여 실질적으로 돌려받은 응답 내용을 <strong>파싱(parsing)</strong>하고 정보를 얻어낼 수 있어야 한다. <code>BeautifulSoup</code> 라이브러리는 받아온 HTML파일을 <strong>파싱</strong>해서 원하는 정보를 손쉽게 찾을 수 있도록 해준다.</p>
<blockquote>
<p>💡 <strong>파싱(Parsing)이란?</strong>
파싱은 구문 분석이라고 한다. 문장이 이루고 있는 구성 성분을 분해하고 분해된 성분의 위계 관계를 분석하여 구조를 결정하는 것이다. 즉 데이터를 분해 분석하여 원하는 형태로 조립하고 다시 빼내는 것을 말한다. 웹상에서 주어진 정보를 내가 원하는 형태로 가공하여 서버에서 불러들이는 것이다.<br>
더 쉽게 이야기하면 HTML 등을 파이썬 등에서도 사용할 수 있게 변환해주는 것이라 생각해도 된다.</p>
</blockquote>
<p>설치는 다음과 같이 <code>pip</code>을 사용하여 설치한다.</p>
<pre><code class="language-python">pip install beautifulsoup4</code></pre>
<h2 id="기본-파싱">기본 파싱</h2>
<p>먼저 파싱할 문자열과 어떻게 파싱할 것인지 정한다. 기본적으로 사용할 수 있는 parser는 <code>html.parser</code>이다. 이때 이 parser는 파이썬 기본 라이브러리에 포함되어 있기 때문에 별도의 설치가 필요하지 않지만, 만약에 XML문서나 다른 HTML파서(ex. <code>htm5lib</code>)등을 사용하려면 따로 설치를 진행해야 한다. 각 parser에 대한 장단점은 공식문서에 나와있다.</p>
<p>다음과 같은 코드를 이용하면 문자열로 된 HTML파일을 넘길 수 있다.</p>
<pre><code class="language-python">import requests
from bs4 import BeautifulSoup

url = &#39;https://google.com&#39;
page = requests.get(url)

soup = BeautifulSoup(page.content, &#39;html.parser&#39;)</code></pre>
<h2 id="요소찾기">요소찾기</h2>
<p>파싱을 완료했으면 원하는 요소를 찾아내야 한다. 기본적으로 id, class, tag 등의 특징들과 <code>find</code> 및 <code>find_all</code>메소드를 이용해 찾아내는 방법들을 알아보겠다.</p>
<p>bs4에서 한 개의 요소를 찾을 때에는 <code>find</code>, 여러 개의 요소들을 찾을 때는 <code>find_all</code>을 사용하게 된다. <code>find</code>는 조건에 일치하는 <strong>첫번째</strong> 결과를 리턴하고 <code>find_all</code>은 조건에 일치하는 <strong>모든 결과</strong>를 리스트에 담아 반환한다. 그리고 여기서 얻게 되는 결과들은 HTML문서이기 때문에 결과를 가지고 다시 <code>find</code>혹은 <code>find_all</code>과 같은 메소드를 실행할 수 있다.</p>
<pre><code class="language-python">&quot;&quot;&quot;
id = &#39;dog&#39;인 요소 찾기
id는 주로 한번만 사용되기 때문에 find 사용
&quot;&quot;&quot;
dog_element = soup.find(id=&#39;dog&#39;)
------------------------
&quot;&quot;&quot;
class = &#39;cat&#39;인 요소 찾기
class를 이용해 찾을 경우 class_을 사용해야 한다. 이는 파이썬의 class와 구분하기 위함이다.
class는 다양한 요소에 사용되기 때문에 find_all을 사용
&quot;&quot;&quot;
cat_elements = soup.find_all(class_=&#39;cat&#39;)
------------------------
&quot;&quot;&quot;
앞 서 설명했듯이 얻게된 결과 역시 HTML문서이기 때문에 결과를 가지고
다시 find 및 find_all사용 가능
&quot;&quot;&quot;
cat_elements = soup.find_all(class_=&#39;cat&#39;)

for cat_el in cat_elements:
    cat_el.find(class_=&#39;fish&#39;)</code></pre>
<h2 id="태그-활용">태그 활용</h2>
<p>조금 더 상세하게 찾고 싶을 경우 tag를 조합해서 사용할 수 있다.</p>
<p>예를 들어 <code>cat</code>이라는 클래스가 <code>div</code>라는 태그에도 있고 <code>p</code>태크에도 있다면 이 때 <code>div</code>태크를 사용하는 요소들만 가지고 오고 싶다면 다음과 같이 실행할 수 있다.</p>
<pre><code class="language-python">cat_div_elements = soup.find_all(&#39;div&#39;,class_ = &#39;cat&#39;)</code></pre>
<h2 id="string-활용">String 활용</h2>
<p>특정 문자열이 포함되어 있는 요소를 찾고 싶을 때 사용할 수 있다. 만일 <code>raining</code>이라는 문자열이 포함되어 있는 요소를 찾고 싶다면 <code>string</code>파라미터를 이용해 다음과 같은 코드를 사용할 수 있다.</p>
<pre><code class="language-python">soup.find_all(string=&#39;raining&#39;)</code></pre>
<p>이때 <code>string</code>의 단점은 명시한 문자열을 그대로 찾는다는 것이다. 즉, 정확히 <code>raining</code>이라는 문자열을 포함하는지 확인한다. 만약에 대소문자를 잘못 적어써나 띄어쓰기를 실수했다해도 동일한 문자열을 포함한 요소를 찾는다.</p>
<p>만일 이런 엄격한 기준으로 찾기보다 대소문자 구분없이 들어가 있는 것을 찾고 싶다면 익명함수를 활용할 수 있다.</p>
<pre><code class="language-python">soup.find_all(string=lambda text: &#39;raining&#39; in text.lower())</code></pre>
<p>이때 string을 사용하면 <code>.string</code>속성을 불러오는 것이기 때문에 요소가 아닌 문자열로 리턴된다. 따라서 하나의 요소로 받기 위해서는 태그도 같이 추가해야 된다.</p>
<pre><code class="language-python">soup.find_all(&#39;h3&#39;, string=&#39;raining&#39;)</code></pre>
<h2 id="정보-얻기">정보 얻기</h2>
<p>원하는 요소들을 선택했다면 이제부터는 정보를 얻어낼 수 있어야 한다. 기본적으로 <code>text</code>속성을 이용해서 내부 문자를 얻어낼 수 있다. 다음과 같은 HTML에서 <code>p</code>태그 내부 글을 얻으려면 <code>text</code>속성을 사용할 수 있다.</p>
<pre><code class="language-html">&lt;p class=&#39;cat&#39;&gt;This is a p-cat&lt;/p&gt;</code></pre>
<pre><code class="language-python">cat_el = soup.find(&#39;p&#39;, class_=&#39;cat&#39;)

cat_el.text
------------------------------------
&#39;This is a p-cat&#39;</code></pre>
<p>만일 불필요한 띄어쓰기가 있을 수 있다. 그럴 때에는 파이썬의 <code>strip</code>메소드를 사용해서 정리해줄 수 있다. <code>strip</code>은 특정 문자를 제거하는 함수로 아무 인자도 입력으로 주지 않으면 공백을 제거한다.</p>
<pre><code class="language-python">cat_el.text.strip()</code></pre>
<h1 id="2-회고">2. 회고</h1>
<p>음...할 말이 없다. 너무 오래 지났다. 일단 빨리 다음 것도 마무리하고 정상화 렛츠기릿 해야겠다.
<br><br><br></p>
<blockquote>
<p>❗️<strong>참고자료</strong></p>
</blockquote>
<ol>
<li><a href="https://blog.codef.io/crawling_vs_scraping/">크롤링과 스크래핑</a></li>
<li><a href="http://wiki.hash.kr/index.php/%ED%8C%8C%EC%8B%B1">파싱</a></li>
</ol>
]]></description>
        </item>
        <item>
            <title><![CDATA[[N321] TIL 및 회고]]></title>
            <link>https://velog.io/@sea_panda/N321-TIL-%EB%B0%8F-%ED%9A%8C%EA%B3%A0</link>
            <guid>https://velog.io/@sea_panda/N321-TIL-%EB%B0%8F-%ED%9A%8C%EA%B3%A0</guid>
            <pubDate>Sat, 17 Dec 2022 06:31:58 GMT</pubDate>
            <description><![CDATA[<blockquote>
<h1 id="center파이썬에서는-모든-것이-객체다center"><center>&quot;파이썬에서는 모든 것이 객체다&quot;</center></h1>
</blockquote>
<h1 id="0-학습목표">0. 학습목표</h1>
<ul>
<li>파이썬 개발자들이 공통으로 가진 철학을 말할 수 있다.</li>
<li><code>pdb</code>디버거를 반복문, 함수에 사용하여 문제가 일어나는 부분을 찾을 수 있다.</li>
<li>함수와 클래스를 이용해 파이썬 코드를 작성할 수 있다.</li>
</ul>
<h1 id="1-주요개념">1. 주요개념</h1>
<h2 id="1-debugging디버깅">1. Debugging(디버깅)</h2>
<p><strong>디버그(Debug)</strong>는 프로그래밍 과정중에 발생하는 오류나 비정상적인 연산, 즉 <span style='color:orange'><strong>Bug</strong></span>를 찾고 수정하는 것이다. 이 과정을 디버깅이라 하기도 한다.</p>
<p><strong>Debug</strong>의 어원의 유래는 초창기 컴퓨터에 나방이 들어가 고장을 일으킨데에 있다. 그 뒤로 버그는 조작하는데 발생한 오류의 은유적 표현이 되었다.</p>
<p>프로그래밍에서 버그는 일종의 <span style = 'color:red'><strong>불가항력</strong></span>같은 것이라서 아무리 능력이 좋거나 경험이 많더라도 버그가 없는 프로그램을 만들 수 없다. 다만 요령있는 사람은 오류 지향적인 설계보다는 견고한 설계를 지향하고 버그가 나더라도 잡아낼 수 있도록 유도하여 짠다.</p>
<p>파이썬에서는 디버깅을 위해서 <code>pdb</code>라는 표준 라이브러리에 포함된 디버깅 도구가 있다. 사용 방법은 다음과 같다.</p>
<pre><code class="language-python">import pdb

def simple_func(num):
    sum = 0
    pdb.set_trace()

    for i in range(1, num + 1):
        pdb.set_trace()
        sum += i

    return sum

simple_func(4)

#다음은 실행결과입니다.

-&gt; for i in range(1,num+1):
(pdb)</code></pre>
<blockquote>
<center>파이썬 3.7이상부터는 `breakpoint()`라는 함수를 실행하기만 하면 된다.<br>
즉, `pdb.set_trace()`대신에 `breakpoint()`를 입력하면 된다.</center>
</blockquote>
<p>위 코드를 실행하면 <code>(pdb)</code>라는 문구가 뜨고 코드가 여기에 멈춘다. 이렇게 멈춘 상태에서 특정 단계들을 검토한다던지 등 다양한 작업을 할 수가 있다. 위 코드의 결과에서 화살표로 보이는 것은 다음 줄을 표시하는 것이다. 현재는 <code>sum=0</code> 바로 다음 줄에 있는 함수에 멈춰있다. 여기에서 현재 상태에서는 변수에 어떤 것들이 저장되어 있는지 확인할 수 있다.</p>
<p><code>(pdb)</code>단계에서 쓸 수 있는 pdb command는 다음과 같다.
<img src="https://velog.velcdn.com/images/sea_panda/post/cae133c0-fe06-4a30-8273-b2384962b393/image.png" alt=""></p>
<h2 id="2-pythonic">2. Pythonic</h2>
<p><span style='color:orange'><strong>Pythonic</strong></span>은 파이썬답게 코드를 짜는 것을 말한다. 파이썬다운 코드라하면 파이썬의 기능들을 잘 이용하여 작성된 코드이고, 그렇기 때문에 가독성이 좋은 코드를 말할 것이다. 대게 파이썬 커뮤니티의 사람들이 쓰는 패턴을 pythonic(파이썬다운)코드라고 생각한다.</p>
<p>이러한 pythonic을 작성할수 있도록 가이드가 존재한다. <span style='color:orange'><strong>(<a href="https://spoqa.github.io/2012/08/03/about-python-coding-convention.html">Pythonic Coding Convention</a>)</strong></span> <strong><a href="https://peps.python.org/pep-0008/">PEP 8</a></strong>이 바로 그것이다. 이 가이드는 절대적인 것은 아니다. 잘 동작하고 읽기 쉽고 유지보수가 쉽다면 그보다 좋은 코드는 없다. 하지만 가이드를 따른다면 나뿐만 아니라 다른 사람에게도 읽기 쉽고, 유지하기 쉬운 코드가 된다. 이처럼 코드의 가독성과 일관성이라는 장점을 얻을 수 있다.</p>
<p>PEP 8의 주요 내용을 정리하여 번역한 <a href="https://codechacha.com/ko/pythonic-and-pep8/">codechacha.com</a>를 시간날 때 읽어보자.</p>
<h2 id="3-함수">3. 함수</h2>
<p>파이썬을 사용하면 피할 수 없는 것 중 하나가 <span style='color:orange'><strong>함수</strong></span>이다. 파이썬을 설치하면 sum, print 등의 내장함수를 사용할 수 있고, pandas, numpy 등의 외부 라이브러리에도 loc,iloc 등의 함수가 포함되어 있기 때문이다. 또한 파이썬, 라이브러리의 설치와 함께 사용될 수 있는 내장된 함수들이 있다. 이를 <strong>&quot;Built-in functions&quot;</strong>라고 한다. 이와 별개로 새로 만들어지는 함수들은 <strong>&quot;user-defined functions&quot;</strong>라고 한다.</p>
<p>함수는 특정한 기능을 반복적으로 실행할 수 있도록 도와주기 때문에 반복적인 작업들을 하지 않아도 된다.</p>
<p>파이썬에서 함수를 만들기 위해서는 몇가지 규칙이 존재한다. 함수의 문법을 정리하면 다음과 같다.</p>
<pre><code class="language-python">def 함수_이름( 파라미터 ):
   &quot;함수 문서&quot;
   함수 내용
   return [표현식]</code></pre>
<p>위 코드에서도 알 수 있듯이 함수를 정의하게 되면 함수에 포함되는 코드는 들여쓰기를 통해 구별짓는다. 가장 처음으로 오는 줄은 함수에 대한 문서가 될 수 있다(주석 및 설명). 이는 선택사항이라 존재유뮤 자체가 함수의 기능에 영향을 미치지는 않는다.</p>
<p>함수의 파라미터들은 위치에 따라 영향을 받는다. 따라서 파라미터를 어느 순으로 받는지 정한 것에 따라 함수를 호출할 때 순서를 지켜서 인수(Argument)를 넘겨야 한다.</p>
<pre><code class="language-python">def get_names(name_1, name_2, name_3):
    print(f&quot;{name_1}{name_2}, {name_3}&quot;)

get_names(&quot;sponge&quot;, &quot;bob&quot;, &quot;patrick&quot;)</code></pre>
<p>코드에서 순서를 바꾼다면 <code>spongebob patrick</code>이 출력되지 않는다.</p>
<p>파이썬에서는 인수들이 함수에 전달될 때 <span style='color:orange'><strong>참조</strong></span>로 전달된다. 즉, 객체의 주소값을 전달(ex. int, str)한다. 하지만 <span style='color:red'><strong>immutable</strong></span>인 객체(ex. list, dict)들은 값으로 전달된다.</p>
<hr>
<p>함수에 전달되는 인수는 크게 3가지로 나눠진다.</p>
<p><strong>- 필수 인수</strong></p>
<pre><code class="language-python">def person_info(first_name, last_name):
    print(f&quot;Hello {first_name}, {last_name}!&quot;)</code></pre>
<p>위의 예시처럼 위치에 따라서 전달되는 인수이다. 순서를 지켜야하며 함수에서 파라미터로 정의했기 때문에 필수로 넘겨야한다. 만일 모든 필수 인자를 전달하지 않고 코드를 실행하면 <code>TypeError</code>가 발생한다.</p>
<p><strong>- 키워드 인수</strong>
위치로 전달하지 않고 키워드를 사용하여 전달하는 인수를 의미한다. 아래처럼 키워드를 명시하여 실행 할 수 있다. 필수 인수들을 전달하기만 하면 순서는 상관없다.</p>
<pre><code class="language-python">person_info(last_name=&quot;bob&quot;, first_name=&quot;sponge&quot;)</code></pre>
<p><strong>- 기본 인수</strong>
만약에 함수에서 받게 되는 파라미터 값들에 대해서 기본 값들을 설정하면 인수를 넘기지 않을 경우 기본값들이 설정되어 함수를 실행하게 할 수 있다.</p>
<pre><code class="language-python">def person_data(name, type_p=&#39;human&#39;):
    print(f&quot;Hello {name}, you are {type_p}&quot;)</code></pre>
<p>위와 같은 경우에는 <code>type_p</code>인수를 넘기지 않으면 함수에서 기본값인 <code>human</code>으로 입력으로 받게된다.
여기서 주의할 점은 기본 값은 설정된 파라미터들은 기본값이 없는 파라미터 뒤에 등장해야 한다는 것이다. 만일 이를 어기면 <code>syntaxError</code>가 발생한다.</p>
<hr>
<p>파이썬에서는 함수를 종료하기 위해서는 <code>return</code>을 사용하여야 한다. 그리고 호출자에게 표현을 전달하는 기능도 있다. 즉, 함수에서 <code>return</code>을 만나게 되면 함수기 그 즉시 종료된다. </p>
<pre><code class="language-python">def print_hello_1(name):
    print(f&quot;Hello {name}&quot;)
    return None</code></pre>
<p>위 코드에서는 return을 따로 명시하지 않아도 print문이 출력된다. 하지만 이는 함수의 출력은 아니고 함수가 실행되면서 그 안에서 print문에 의해서 실행된 것에 불가하다. 함수의 출력결과는 None이다.</p>
<pre><code class="language-python">a = print_hello_1(&#39;sponge&#39;)
print(a)</code></pre>
<p>위의 출력 결과는 이름이 나오겠지만 함수의 출력값인 a는 None이라는 의미이다.</p>
<p>따로 리턴 문구를 넣어서 특정 결과를 리턴하게 할 수 있다.</p>
<pre><code class="language-python">def print_hello_4(name):
    return f&quot;Hello {name}&quot;</code></pre>
<p>위 처럼 실행하면 함수의 결과값은 &quot;Hello {name}&quot;이 된다.</p>
<h2 id="4-class와-객체object-or-instance">4. Class와 객체(object, or Instance)</h2>
<p>파이썬에서는 모든 것이 객체이다. 그리고 이러한 객체를 만들기 위한 설계 도면이 바로 <span style='color:orange'><strong>Class</strong></span>이다. 다시 말하자면, 함수와 비슷하게 설계 중심의 사고와 재사용성을 줄이기 위하여 만들어진 것이다. 조금 더 자세히 함수와 클래스의 차이를 알아보자면, 클래스가 조금 더 큰 범위라고 생각하면 된다. 클래스에서는 함수와 속성 두 가지를 전부 담을 수가 있다. 이때 클래스의 함수는 <code>Method</code>라고 한다. 두 가지를 전부 담을 수 있는 특성으로 인해서 다양한 정보와 기능들을 묶어서 따로 사용할 수 있다.</p>
<hr>
<p><strong>- 클래스 생성</strong></p>
<pre><code class="language-python">class Pokemon:
    pokemon_a = &#39;pikachu&#39;

    def print_pika(self):
        print(&quot;pika&quot;)</code></pre>
<p>클래스는 위와 같은 형태로 만들 수 있다. 클래스 내에서 속성과 함수 모두를 정의할 수 있다. 위 코드처럼 일반적인 함수와 동일하게 클래스 내에서도 함수를 선선해도 잘 작동한다.</p>
<pre><code class="language-python">Pokemon.print_pika()</code></pre>
<p>Class 코드에서 보면 <code>self</code>라는 것이 등장한다. 이는 인스턴스(객체)에서 해당 메서드를 실행할 때 자기자신을 인수로 넘겨주기 때문에 그것을 받는 자리이다.
<img src="https://velog.velcdn.com/images/sea_panda/post/c7a86ee1-bb91-4e00-8255-b0e4e0729114/image.png" alt=""></p>
<p><code>self</code>는 사실 단어 자체가 키워드는 아니다. 따라서 다른 단어로 대체해도 문제는 없다. 하지만 인스턴스에서 메서드나 특성을 사용할 때 첫 번째 인수, 파라미터로 넘겨지고 받아져야 한다는 것은 변함 없다. 여기서 또! 하지만! 위에서 다룬 Pythonic한 코드를 작성하기 위해서는 <code>self</code>를 사용하도록 가이드 하고 있다. 또한 class의 첫 번째 인자 이름은 <code>cls</code>를 사용하도록 권장한다.</p>
<hr>
<p><strong>- 생성자 함수</strong>
클래스의 함수 중에서 <span style='color:orange'><strong>생성자 함수</strong></span>는 클래스가 인스턴스화(instantiate)될 때 사용되는 함수이다.만일 이 함수가 따로 정의되어 있지 않으면 기본 생성자 함수를 사용한다. 이 함수는 다음과 같이 사용할 수 있다.</p>
<pre><code class="language-python">class Pokemon:
    def __init__(self, pokemon_a=&#39;pikachu&#39;):
        self.pokemon_a = pokemon_a

poke_a = Pokemon()
print(poke_a.pokemon_a) </code></pre>
<p>클래스 기반으로 생성되는 인스턴스는 클래스의 생성자 함수에 따라 인스턴스의 초기 속성들을 받을 수 있다. 하지만 이는 인스턴스마다의 속성이지 클래스 전체에 대한 속성은 아니게 된다. 따라서 클래스 자체에서는 이러한 속성들에 대한 접근이 힘들다. 다음과 같은 코드를 실행하면 <code>Pokemon</code>클래스는 인스턴스의 속성에 접근 못한다는 것을 확인할 수 있다.</p>
<pre><code class="language-python">Pokemon.pokemon_a
------
-&gt; AttributeError: type object &#39;Pokemon&#39; has no attribute &#39;pokemon_a&#39;</code></pre>
<h2 id="5-객체지향">5. 객체지향</h2>
<p><img src="https://velog.velcdn.com/images/sea_panda/post/bbc86965-9e8d-4bba-a84b-d269c7462a15/image.png" alt=""></p>
<p>우리가 실생활에서 쓰는 모든 것을 객체라 하며, 객체 지향 프로그래밍은 프로그램 구현에 필요한 객체를 파악하고 각각의 객체들의 역할이 무엇인지를 정의하여 객체들 간의 상호작용을 통해 프로그램을 만드는 것을 말한다. 객체는 클래스라는 틀에서 생겨난 실체(instance)이다. 따라서 객체 지향 프로그램은 객체와 객체 간의 연결로 되어 있으며 각각의 객체 안에 자료구조와 알고리즘이 들어있다.</p>
<p>객체 지향 모델링에서는 기능이 아닌 객체가 중심이 되며 &quot;누가 어떤 일을 할 것인가?&quot;가 핵심이 딘다. 즉 객체를 도출하고 각각의 역할을 정의해 나가는 것에 초점을 맞춘다.</p>
<blockquote>
<p>💡 <strong>객체? 인스턴스?</strong>
점프 투 파이썬의 예시를 들어보자. 쿠키를 만들기 위한 틀이 있다고 하자. 이 쿠키 틀을 바로 <strong>Class</strong>라고 한다. 그리고 이렇게 쿠키틀을 이용하여 찍어낸 쿠키가 객체이자 인스턴스가 되는 것이다. 둘의 차이는 관점의 차이이다. 쿠키 그 자체를 객체라고 한다면 인스터는 보다 관계적인 의미이다. &quot;쿠키 틀의 <strong>인스턴스</strong>는 쿠키이다.&quot;라고 할 수 있다.</p>
</blockquote>
<h2 id="6-파이썬-데코레이터">6. 파이썬 데코레이터(@)</h2>
<p>파이썬 데코레이터는 <strong>PEP 318</strong>에서 어색하고 반복적인 함수의 표현을 줄이기 위해 제안되었다. 현재는 함수뿐만 아니라 클래스, 함수, 제네레이터 등의 다른 타입에서도 사용되고 있다. 이 역시 Pythonic한 코드를 작성하는 것과 관련된 개념으로 <span style='color:orange'><strong>DRY원칙(Don&#39;t Repeat Yourself)</strong></span>을 따르기 위하여 사용하는 다양한 방법 중 하나로, 다른 형태보다 깔끔하고 간결한 코드를 만들면서 코드의 재사용을 줄이기 위해 많이 추천되는 기술이다.</p>
<hr>
<p>이번 수업에서는 함수 데코레이터에 대해서 다루었다.</p>
<p>만일 다양한 문장을 출력하는데 겹치는 문장이 있다고 하자.</p>
<pre><code class="language-python">def my():
    print(&quot;데코레이터&quot;)
    print(&quot;my 라는 함수입니다.&quot;)

def mine():
    print(&quot;데코레이터&quot;)
    print(&quot;mine 이라는 함수입니다.&quot;)

def iam():
    print(&quot;데코레이터&quot;)
    print(&quot;iam 이라는 함수입니다.&quot;)</code></pre>
<p>위 코드는 간단한 문장을 프린트하는 방식이였지만 복잡한 로직이 들어간다면 코드는 길어지고 같은 내용을 반복해서 작성해야할 것이다. 이를 데코레이터를 이용하면 다음과 같이 간단하게 작성할 수 있다.</p>
<pre><code class="language-python">def first_deco(func): # func 는 실행할 함수입니다.
    def first(): # 실행할 함수를 감싸는(wrap) 함수입니다.
        print(&quot;데코레이터&quot;)
        func()
    return first #first 함수를 return합니다

@first_deco
def my():
    print(&quot;my 라는 함수입니다.&quot;)

@first_deco
def mine():
    print(&quot;mine 이라는 함수입니다.&quot;)

@first_deco
def iam():
    print(&quot;iam 이라는 함수입니다.&quot;)</code></pre>
<p>만일 사용할 함수에 인자가 주어진 경우에는 인자를 처리하는 구문이 있어야 정상작동된다. 따라서 인자값이 정의되어 있을 땐 데코레이터에도 인자 값을 처리하는 <code>*args, **kwargs</code>구문이 필요하다.</p>
<pre><code class="language-python">def first_last_deco(func): # func 는 실행할 함수입니다.
    def first_last(*args, **kwargs): # 실행할 함수를 감싸는(wrap) 함수입니다.
        print(&quot;first&quot;)
        func(*args, **kwargs)
        print(&quot;last&quot;)
    return first_last

@first_last_deco
def you(name):
    print(f&quot;{name}! Hello&quot;)</code></pre>
<p>여기서 <code>*args</code>는 <code>arguments</code>의 줄임말이다. 이 지시어는 여러 개의 인자를 함수로 받고자 할 때 사용한다. 몇 개의 입력이 주어지는지 모를 때도 사용할 수 있다.</p>
<p><code>**kwargs</code>는 {키워드=특정값}의 형태로 함수를 호출할 때 사용하는 키워드이다. 딕셔너리 형태로도 전달할 수 있다. 대표적인 사용예시는 print문에서 <code>end</code>키워드가 있다.</p>
<pre><code class="language-python">print(&quot;end=?가 바로 **kwargs입니다.&quot;,end=&#39;이것은 키워드 밸류&#39;)</code></pre>
<p>키워드 인자를 받는 것이라고 생각하면 된다.</p>
<h1 id="2-명령어">2. 명령어</h1>
<p>이제부터는 대다수 필요한 명령어는 주요 개념에서 함께 다루는 것으로 바꿔보았다. 여기에는 과제 명령어나 위에서 다루지 않은 것들만 정리한다.</p>
<h2 id="1-_와-__">1. <code>_</code>와 <code>__</code></h2>
<p>예전의 수업에서도 <code>_</code>와 <code>__</code>에 대해서 다루었던 기억이 있는데 어딘지 모르겠다. 여하튼 다양한 기능을 내포하고 있는데 이번 수업에서는 변수나 함수명에 특별한 의미 또는 기능을 부여하고자 할 때 사용한다.</p>
<hr>
<p><strong>- Single underscore</strong>
파이썬 클래스 내부에서 따로 변수나 값을 저장할 때 사용된다. 보통 <code>private</code> 접근 제한자를 표현하기 위해서 사용한다. 하지만 여기서 중요한건 이는 <span style='color:orange'><strong>관례적인</strong></span> 표현일 뿐 실제로 private해지는 것은 아니다. 여기서 private라는 것은 클래스 내부에서만 접근 가능한 변수 혹은 값을 의미한다. </p>
<p>보통 이 sigle underscore를 쓰면 접근하지 않았으면 좋겠다는 의미이니 여기에 직접 접근하는 것은 별로 좋지 않다.</p>
<pre><code class="language-python">class Pokemon:
    _pokemon_health = 100

    def __init__(self, pokemon_a=&#39;pikachu&#39;):
        self.pokemon_a = pokemon_a

poke_a = Pokemon()
print(poke_a._pokemon_health) #=&gt; 100</code></pre>
<hr>
<p><strong>- double underscore (dunderscore)</strong>
파이썬 클래스 내부에서만 관리하도록 밑줄을 두 개 사용할 수도 있다.이때 <code>__</code>를 사용하면 진짜 private인 것처럼 결과가 출력되는데 이는 실제로 private한 것이 아니라 파이썬의 <strong>Name Mangling</strong>으로 발생하는 상황이다. 밑줄을 두 개 사용하게 된다면 정해준 이름을 사용하지 않고 <code>_&lt;클래스 이름&gt;_&lt;변수 혹은 함수 이름&gt;</code>으로 변경된다. 따라서 앞의 형식에 맞게 클래스 밖에서 이름을 선언하면 여전히 접근할 수 있다는 것을 알 수 있다. 하지만 보이는 것과 다르게 변수가 저장되기 때문에 실수로 속성에 접근하는 것을 막아준다.</p>
<pre><code class="language-python">class Pokemon:
    __pokemon_health = 100

    def __init__(self, pokemon_a=&#39;pikachu&#39;):
        self.pokemon_a = pokemon_a
-----------------------------------------------------------
poke_a = Pokemon()
print(poke_a.__pokemon_health) #=&gt; 에러
print(poke_a._Pokemon__pokemon_health) #=&gt; 100</code></pre>
<hr>
<p>이렇게 밑줄을 이용하여 클래스의 속성을 바깥에서 접근하지 못하도록 하는 것을 <strong>캡슐화</strong>라고 한다. </p>
<h2 id="2-getter과-setter-클래스-특성-가져오기-저장-변경하기">2. getter과 setter: 클래스 특성 가져오기, 저장 변경하기</h2>
<pre><code class="language-python">class Student:

  def __init__(self, name, age):
    self._name = name
    if age &lt;= 10:
      raise ValueError(&#39;11살 이상의 학생만 가능합니다&#39;)
    self._age = age

stu1 = Student(&#39;son&#39;, 20)
stu1 = Student(&#39;son&#39;, 8) # ValueError 발생

# __init__함수의 영향을 받지 않으므로 ValueError가 발생하지 않는다
stu1._age = 8</code></pre>
<p>문제가 없어보이는 코드처럼 보이지만 객체를 생성하고 나서 값을 변경하게 된다면 더 이상 <code>__init__</code>함수의 영향을 받지 않기 때문에 문제가 될 수 있다. 원래는 들어가지 못할 값이 들어가게 되는 것이다.</p>
<p>이러한 문제를 <code>getter</code>메서드와 <code>setter</code>메서드를 구현하여 해결할 수 있다.</p>
<pre><code class="language-python">class Student:

  def __init__(self, name, age):
    self.__name = name
    if age &lt;= 10:
      raise ValueError(&#39;11살 이상의 학생만 가능합니다&#39;)
    self.__age = age

  @age.getter
  def age(self):
    return self.__age

  @age.setter
  def age(self, age):
    if age &lt;= 10:
      raise ValueError(&#39;11살 이상의 학생만 가능합니다&#39;)
    self.__age = age
  -------------------------------------------------
a = Student(&#39;son&#39;, 21)
a.get_age() # 21
a.set_age(12)
a.get_age() # 12</code></pre>
<p>위는 <span style='color:orange'><strong>수동</strong></span>으로 직접 age에 대한 getter메소드와 setter메소드를 만든 것이다. 그리고 마지막에는 age라는 이름으로 property에 등록해주었다. 이런 방식의 특징은 get_age와 set_aget를 직접 사용할 수 있다는 것이다. </p>
<p>getter/setter메서드를 통해서 객체의 내부 데이터에 대한 접근을 조금 더 통제할 수 있게되었지만 기존에 필드명을 바로 사용할 때 보다는 코드가 지저분해 진 것을 알 수 있다. 그리고 호출할 때 역시 getter메서드와 setter 메서드를 호출하여야 하기 때문에 번거롭다. 이 점은 <code>property()</code>를 사용하면 해결할 수 있다. 위의 클래스 코드의 마지막에 다음과 같은 코드를 추가하여 준다.</p>
<pre><code class="language-python">age = property(get_age, set_age)
-------------------------------
a = Student(&#39;son&#39;,21)
a.age #=&gt; 21
a.age = 15
a.age #=&gt; 15</code></pre>
<p>위와 같이 <code>getter</code>와 <code>setter</code>를 한 번에 property안에 담겨서 값이 변경되고 얻어지는 것을 확인할 수 있다.</p>
<hr>
<p>위에서는 수동으로 각각의 메서드를 만들었다면 더 간단하게 데코레이터를 이용하여 만들 수 있다. getter를 만들기 위해 <code>@property</code>데코레이터를 사용한다. 함수 이름은 변수명과 동일하게 작성하는 관계가 있다. 그리고 <code>setter</code>를 만들기 위해 <code>@변수.setter</code>를 사용한다. 마찬가지로 변수명과 동일한 함수명을 추천한다.</p>
<p>이렇게 클래스 내부의 변수에<code>__</code>를 덧붙여서 private 속성을 만들고 값이 필요할 때 setter메소드와 getter메소드를 사용하는 것이 보편적인 객체지향 프로그래밍 방법 중 하나다.</p>
<pre><code class="language-python">class Student:

  def __init__(self, name, age):
    self.__name = name
    if age &lt;= 10:
      raise ValueError(&#39;11살 이상의 학생만 가능합니다&#39;)
    self.__age = age

  @property
  def age(self):
    return self.__age

  @age.setter
  def age(self, age):
    if age &lt;= 10:
      raise ValueError(&#39;11살 이상의 학생만 가능합니다&#39;)
    self.__age = age
---------------------------------------------------------------------
a = Student(&#39;son&#39;,21)
a.age #=&gt; 21
a.age = 15
a.age #=&gt; 15</code></pre>
<p>데코레이터를 사용하면 위의 수동으로 작성한 코드와 다르게 <code>get_age</code>, <code>set_age</code> 메소드는 존재하지 않는다. age는 실제로 메서드이지만 getter역할을 하는 <code>@property</code>로 인하여 외부에서는 속성처럼 사용할 수 있게된다.</p>
<hr>
<p><code>@property</code>는 클래스 메소드를 속성처럼 사용할 수 있게 해준다. <code>@property</code>를 붙인 메소드는 일반적으로 어떤 인스턴스 변수에 대한 <code>getter</code>메소드임을 나타낸다.</p>
<h2 id="3-assert-가정-설정문코드의-동작-보장">3. assert: 가정 설정문(코드의 동작 보장)</h2>
<p>조건문이 True가 아닐 경우, 예외를 일으킨다. 작동하던 코드에서 추가로 코드를 작성했을 때 예상하지 못한 다른 동작을 방지하기 위해서 주로 사용한다. 즉, 조건이 충족되지 않으면 코드가 실행되지 않는다.</p>
<pre><code class="language-python">kitai = 100
in_put = 1
assert kitai == in_put, &#39;기대한 값은 [{0}], 입력값은 [{1}]&#39;.format(kitai,in_put)
---
Traceback (most recent call last):
  File &quot;&lt;stdin&gt;&quot;, line 1, in &lt;module&gt;
AssertionError: 기대한 값은[100], 입력값은[1]</code></pre>
<h2 id="4-isinstance-주어진-객체는-해당-클래스의-인스턴스-인가">4. isinstance: 주어진 객체는 해당 클래스의 인스턴스 인가?</h2>
<p>확인하고자 하는 데이터나 인스턴스와 확인하고자하는 데이터 타입이나 클래스를 입력으로 받는다. 사실, 파이썬의 모든 것은 객체이고 데이터 타입도 일종의 클래스이기 때문에 객체가 클래스에 담겨있는지 확인하는 것이라고 생각하면 된다.</p>
<pre><code class="language-python">class KimChi:
    pass

kc = KimChi()

result6 = isinstance(kc, School)
print(f&#39; isinstance(kc, School) : {result6}&#39;)

result1 = isinstance(100, int)
print(f&#39;isinstance(100, int) : {result1}&#39;)
-------------------------------------------------
isinstance(kc, School): False
isinstance(100, int): True</code></pre>
<h2 id="5-raw-string-escape문에-영향-받지-않고-출력">5. raw String: escape문에 영향 받지 않고 출력</h2>
<p><code>\n</code>. <code>\t</code> 등의 escape문에 영향을 받지 않고 문자열을 출력하고 싶을 때 사용한다.</p>
<pre><code class="language-python">print(r&quot;raw string은 \n과 같은 개행문자가 실행되지 않는다.&quot;)
print(&quot;일반적으로는 \n 개행문자가 실행된다.&quot;)
-----------------------------------------------------
raw string은 \n과 같은 개행문자가 실행되지 않는다.
일반적으로는
개행문자가 실행된다.</code></pre>
<h2 id="6-class-속성">6. Class 속성</h2>
<h3 id="6-1-__class__">6-1. <code>__class__</code></h3>
<p>객체 뒤에 위치하면 어떤 class의 인스턴스 인지 확인 가능</p>
<h3 id="6-2-__name__">6-2. <code>__name__</code></h3>
<p>클래스 이름을 출력한다.</p>
<h3 id="6-3-__doc__">6-3. <code>__doc__</code></h3>
<p>해당 함수 또는 Class에 대한 설명을 출력 또는 변경할 수 있다.</p>
<pre><code class="language-python">class example:
    pass
print(example.__doc__)
example.__doc__ = &quot;이것은 설명을 추가하는 것입니다.&quot;
print(example.__doc__)
----------------------------------------------
None
이것은 설명을 추가하는 것입니다.</code></pre>
<p>documentation은 class시작 전 큰 따옴표 세 개로 둘러싸서 추가할 수도 있다.</p>
<h3 id="6-4-__dict__">6-4. <code>__dict__</code></h3>
<p>클래스 객체의 속성 정보를 확인하기 위해서 사용한다. 객체가 가진 여러가지 속성을 딕셔너리 형태로 편하게 확인할 수 있다.이를 활용하여 객체의 변수를 dict혀애로 변경할 수 있다. dictionary형태로 만들어 두면, 편하게 속성 값들을 가져올 수 있다.</p>
<pre><code class="language-python">class Test:
    def __init__(self, name):
        self.name = name
        self.test_dict = {&#39;a&#39;:1, &#39;b&#39;:2}
        self.test_list = [&#39;1&#39;,&#39;2&#39;,&#39;3&#39;]

test_object = Test(&quot;minimi&quot;)
print(type(test_object.__dict__)) # =&gt; &lt;class &#39;dict&#39;&gt;

print(test_object.__dict__)
-------------------------------------------------------
{&#39;name&#39;: &#39;minimi&#39;, &#39;test_dict&#39;: {&#39;a&#39;: 1, &#39;b&#39;: 2}, &#39;test_list&#39;: [&#39;1&#39;, &#39;2&#39;, &#39;3&#39;]}</code></pre>
<p>속성 값들이 딕셔너리 형태로 저장되기 때문에 key 값으로 조회시 바로 value를 얻을 수 있다.</p>
<h2 id="7-getattr-속성-값-가져오기">7. getattr: 속성 값 가져오기</h2>
<p>해당 객체에 속해있는 속성 값을 가져온다.</p>
<pre><code class="language-python">class sample:
    def __init__(self,x):
        self.x = x

c = sample(1)
print(getattr(c,&#39;x&#39;))
-----------------------
1</code></pre>
<p>보통은 <code>c.X</code>와 같은 방법으로 불러올 수도 있다. 하지만 클래스 안에서 선언된 이름이 다른 라이브러리의 메소드와 같아서 충돌을 일으킬 경우 앞의 방식보다 문자열로 전달하는 <code>getattr</code>가 유용하다.</p>
<h2 id="8-str과-repr-객체의-문자열-표현-반환">8. str과 repr: 객체의 문자열 표현 반환</h2>
<p>두 함수 모두 객체를 문자열로 반환하는 함수이다. 하지만 두 함수에는 약간의 차이가 있다.</p>
<pre><code class="language-python">a = &quot;Life is too short&quot;
str(a)
------------------------
&#39;Life is too short&#39;
------------------------
repr(a)
------------------------
&quot;&#39;Life is too short&#39;&quot;</code></pre>
<p><img src="https://velog.velcdn.com/images/sea_panda/post/1e1dc92f-ab49-46da-8b71-c6792eb12d7f/image.png" alt=""></p>
<p><code>repr</code>은 문자열로 다시 객체를 생성하는 것이 목적이기 때문에 <code>eval</code>을 통해서 문자열을 다시 객체로 만들 수 있다. 자세한 설명은 다음 사이트 주소를 참조하자.</p>
<blockquote>
<p>💡 위키독스
사이트 주소: <a href="https://wikidocs.net/134994">https://wikidocs.net/134994</a></p>
</blockquote>
<h2 id="9-with-assertraise-특정예외-발생-검증">9. with assertRaise: 특정예외 발생 검증</h2>
<p><code>assertRaise</code>에 전달된 어떤 위치 또는 키워드 인자와 함께 callable이 호출되었을 때 예외가 발생하는지 테스트한다. exception이 발생하면 테스트를 통과하고, 다른 예외가 발생하면 에러이고, 아무 예외도 발생하지 않으면 실패이다. 여러 예외 모음을 잡기 위해서 예외 클래스를 포함한 튜플을 expection으로 전달해도 된다.</p>
<pre><code class="language-python">with self.assertRaises(SomeException) as cm:
    do_something()</code></pre>
<p>위와 같은 형태로 작성하면 된다.</p>
<h2 id="10-issubclass-자식클래스의-부모클래스-확인">10. issubclass: 자식클래스의 부모클래스 확인</h2>
<p>상속 받은 자식 클래스가 부모 클래스에 포함되는지 확인해보기 위해서 사용하는 함수이다.</p>
<pre><code class="language-python">class Parent:         # 부모가 될 클래스 (기반 클래스)
    pass

class Child(Parent):  # 자식 클래스 (서브 클래스)
    pass

result5 = issubclass(Child, Parent)
print(f&#39;issubclass(Child, Parent) : {result5}&#39;)
--------------------------------------------------
issubclass(Child, Parent) : True</code></pre>
<h2 id="11-super-부모클래스에-정의된-메소드-재정의">11. super: 부모클래스에 정의된 메소드 재정의</h2>
<p>인스턴스 속성은 부모클래스 객체가 형성될 때 <code>__init__</code> 매직 메소드가 실행되면서 생성된다. 그러나 자식 클래스 어디에도 부모 클래스의 객체가 형성된 적이 없다. 그래서 super()라는 함수를 이용하여 부모 클래스의 <code>__init__</code>매직 메소드를 자식 클래스에서 실행하므로 문제를 해결할 수 있다.</p>
<pre><code class="language-python">class Person:
    def __init__(self):
        print(&#39;Person __init__&#39;)
        self.hello = &#39;안녕하세요.&#39;

class Student(Person):
    def __init__(self):
        print(&#39;Student __init__&#39;)
        self.school = &#39;파이썬 코딩 도장&#39;

james = Student()
print(james.school)
print(james.hello)    # 부모 클래스의 속성을 출력하려고 하면 에러가 발생함
--------------------------------------------------------------
Student __init__
파이썬 코딩 도장
Traceback (most recent call last):
  File &quot;C:\project\class_inheritance_attribute_error.py&quot;, line 14, in &lt;module&gt;
    print(james.hello)
AttributeError: &#39;Student&#39; object has no attribute &#39;hello&#39; 
--------------------------------------------------------------
class Person:
    def __init__(self):
        print(&#39;Person __init__&#39;)
        self.hello = &#39;안녕하세요.&#39;

class Student(Person):
    def __init__(self):
        print(&#39;Student __init__&#39;)
        super().__init__()   # super()로 기반 클래스의 __init__ 메서드 호출
        self.school = &#39;파이썬 코딩 도장&#39;

james = Student()
print(james.school)
print(james.hello)
-------------------------------------------------------------
Student __init__
Person __init__
파이썬 코딩 도장
안녕하세요.</code></pre>
<p>부모 클래스의 hello 속성을 찾는 과정은 다음과 같은 그림의 순서로 실행된다.
<img src="https://velog.velcdn.com/images/sea_panda/post/4f5a4a3a-b554-438a-9726-25419639fbd7/image.png" alt=""></p>
<h2 id="12-try--except-">12. try ~ except ~</h2>
<p>except란 코드를 실행하는 중에 발생한 에러를 뜻한다. <code>try~except~</code>는 <code>try</code> 뒤에 오는 코드를 실행하다가 에러가 발생했을 때 에러를 출력하며 코드를 정지하는 것이 아닌 except 뒤에 오는 코드를 실행하게 된다.</p>
<pre><code class="language-python">try:
    x = int(input(&#39;나눌 숫자를 입력하세요: &#39;))
    y = 10 / x
    print(y)
except:    # 예외가 발생했을 때 실행됨
    print(&#39;예외가 발생했습니다.&#39;)
------------------------------------------
나눌 숫자를 입력하세요: 0  # 입력  
예외가 발생했습니다.</code></pre>
<h1 id="3-회고">3. 회고</h1>
<p>연말은 연말인 것 같다. 약속은 많아지고 겨울 잠이 동면하듯이 내 잠도 늘어간다. 자연의 힘은 위대한 것 같다...이번 수업에서는 진짜 많은 것을 배웠다. 진짜 앞에 머신러닝보다 더 많은 것을 배우는 것 같다. 익숙하지 않은 툴과 기초에서 넘어가는 파이썬 내용이 나오니까 Section2까지는 강의 다듣고 과제 다하면 늦어도 13시 30분? 그랬는데 이제는 20시는 되어야 과제가 끝난다. 그리고 약속 있으면 잠깐 나갔다오면 12시...핑계긴 하지만 TIL이 늦어지는 이유....그래도 올해가 끝나기 전까지는 계속 이 TIL을 썼으면 좋겠다.</p>
<p>&lt;12월 21일&gt;
아직도 지난주 금요일에 쓴걸 못끝냈다. 진짜 간단히 간단히 속으로 말하는데 계속 길어진다...진짜 간단하게 해야지...</p>
<p><br><br><br></p>
<blockquote>
<p>❗️ <strong>참고자료</strong></p>
</blockquote>
<ol>
<li><a href="https://yjs-program.tistory.com/129">pdb 명령어</a></li>
<li><a href="http://www.incodom.kr/%EA%B0%9D%EC%B2%B4_%EC%A7%80%ED%96%A5">객체 지향</a></li>
<li><a href="https://jinmay.github.io/2019/11/23/python/python-class-first/">getter,setter</a></li>
<li><a href="https://blog.naver.com/PostView.naver?blogId=codeitofficial&amp;logNo=221684462326&amp;redirect=Dlog&amp;widgetTypeCall=true&amp;directAccess=false">property 1</a></li>
<li><a href="https://blog.naver.com/PostView.naver?blogId=codeitofficial&amp;logNo=221695196435&amp;parentCategoryNo=&amp;categoryNo=7&amp;viewDate=&amp;isShowPopularPosts=false&amp;from=postView">property 2</a></li>
<li><a href="https://minimilab.tistory.com/58"><code>__dict__</code></a></li>
</ol>
]]></description>
        </item>
    </channel>
</rss>