<?xml version="1.0" encoding="utf-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom">
    <channel>
        <title>kyoooong.log</title>
        <link>https://velog.io/</link>
        <description>CS @ SKKU | ex-intern @ Upstage | Recipient of Minister’s Award (ICT)</description>
        <lastBuildDate>Tue, 25 Feb 2025 05:47:29 GMT</lastBuildDate>
        <docs>https://validator.w3.org/feed/docs/rss2.html</docs>
        <generator>https://github.com/jpmonette/feed</generator>
        <image>
            <title>kyoooong.log</title>
            <url>https://velog.velcdn.com/images/t_wave/profile/c43640d8-8674-4570-8301-46c9c1a0c989/image.png</url>
            <link>https://velog.io/</link>
        </image>
        <copyright>Copyright (C) 2019. kyoooong.log. All rights reserved.</copyright>
        <atom:link href="https://v2.velog.io/rss/t_wave" rel="self" type="application/rss+xml"/>
        <item>
            <title><![CDATA[🔥 LangGraph Code Assistant]]></title>
            <link>https://velog.io/@t_wave/LangGraph-Code-Assistant</link>
            <guid>https://velog.io/@t_wave/LangGraph-Code-Assistant</guid>
            <pubDate>Tue, 25 Feb 2025 05:47:29 GMT</pubDate>
            <description><![CDATA[<h2 id="📝-프로젝트-개요">📝 프로젝트 개요</h2>
<h3 id="langgraph">LangGraph?</h3>
<p>내가 <strong>LangGraph</strong>를 좋아하는 이유는 필요한 모듈을 온라인에서 가져와 내 마음대로 커스터마이징하기 편하기 때문이다. <strong>LangChain</strong>처럼 모든 기능이 일체형으로 묶여 있지 않고, Node 단위로 구성되어 있어 코드 수정과 확장이 훨씬 유연하다.  </p>
<p>다른 사람들의 코드를 참고하며 많은 도움을 받은 만큼, 나도 누군가의 프로젝트에 작은 기여를 할 수 있으면 좋겠다는 생각이 들었다. 물론 코드 어시스턴트 예제는 이미 <a href="https://github.com/langchain-ai/langgraph/blob/main/docs/docs/tutorials/code_assistant/langgraph_code_assistant.ipynb">LangGraph 공식 문서</a>에 나와 있지만, 한국어 자료는 부족하고 공식 문서는 내용이 다소 무겁게 느껴질 수 있다. 그래서 이 글에서는 처음 접하는 사람도 부담 없이 이해할 수 있도록 더 <strong>가볍고 실용적인 코드 쿡북</strong>을 만들어봤다. 특히 <strong>데이터 시각화</strong> 측면에 초점을 맞춰 코드를 짜봤다.  </p>
<p>이 코드가 누군가의 프로젝트에 인용된다면 정말 뿌듯할 것 같다.<br>그럼 시작해보자! 🚀</p>
<hr>
<h2 id="🧩-langgraph의-기본-요소">🧩 LangGraph의 기본 요소</h2>
<p><strong>Agentic Code Generator</strong>를 만들기 전에, LangGraph의 핵심 개념부터 가볍게 훑어보자.  </p>
<ol>
<li><strong>상태(State)</strong>: 작업 중 데이터를 저장하는 공간이야. 예를 들어 사용자 입력, 전처리 결과, 코드 실행 결과 같은 게 여기에 담긴다.  </li>
<li><strong>노드(Node)</strong>: 특정 작업을 처리하는 단위다. 데이터 로드, 전처리, 코드 생성, 시각화 등 단계별 작업이 여기서 이뤄진다.  </li>
<li><strong>간선(Edge)</strong>: 노드들끼리 연결해주는 다리 같은 존재다. 이걸로 작업 순서나 조건 분기를 설정할 수 있어.  </li>
</ol>
<p>이 구조 덕분에 작업 흐름을 시각적으로 파악하기도 쉽고, 코드 관리도 훨씬 편하다. 🙌</p>
<hr>
<h2 id="🛠️-agentic-code-generator-플로우-설계">🛠️ Agentic Code Generator 플로우 설계</h2>
<p>Agentic Code Generator는 아래 흐름으로 돌아간다.</p>
<ol>
<li><strong>데이터 로드</strong>  </li>
<li><strong>데이터 전처리</strong>  </li>
<li><strong>사용자 요구사항 분석 및 코드 생성</strong>  </li>
<li><strong>코드 실행 및 결과 확인</strong>  </li>
<li><strong>에러 발생 시 코드 재생성 및 재실행</strong>  </li>
</ol>
<h3 id="🔍-플로우-다이어그램">🔍 플로우 다이어그램</h3>
<p><img src="https://velog.velcdn.com/images/t_wave/post/76f9c86c-313e-47a2-8536-6aae6fc50e35/image.png" alt="다이어그램"></p>
<p>위의 그림은 Agentic Code Generator의 플로우 다이어그램이다. 최대한 간소화해서 만들어보았다.</p>
<pre><code class="language-mermaid">flowchart LR
    A(START) --&gt; B[데이터 로드]
    B --&gt; C[데이터 전처리]
    C --&gt; D[코드 생성]
    D --&gt; E[시각화]
    E --&gt;|에러 발생?| D
    E --&gt;|에러 없음| F(END)</code></pre>
<p>에러가 발생하면 자동으로 코드 생성 단계로 돌아가 반복 실행한다. 🔄</p>
<hr>
<h2 id="💻-코드-구현-과정">💻 코드 구현 과정</h2>
<h3 id="1️⃣-상태state-정의">1️⃣ 상태(State) 정의</h3>
<p>먼저 작업에 필요한 데이터를 저장할 공간을 만들어보자.</p>
<pre><code class="language-python">from typing import TypedDict

class State(TypedDict):
    input: str
    data: str
    processed_data: str
    code: str
    is_error: bool
    result: str</code></pre>
<ul>
<li><code>input</code>: 사용자의 요청 (input query)  </li>
<li><code>data</code>: 로드된 데이터  </li>
<li><code>processed_data</code>: 전처리된 데이터  </li>
<li><code>code</code>: 생성된 코드  </li>
<li><code>is_error</code>: 에러 발생 여부  </li>
<li><code>result</code>: 실행 결과  </li>
</ul>
<p>각각의 쓰임새가 있는데, 아래에서 조금씩 설명해보겠다.</p>
<hr>
<h3 id="2️⃣-노드node-함수-정의">2️⃣ 노드(Node) 함수 정의</h3>
<p>각 작업 단계별로 함수를 만들어보자.</p>
<h4 id="📥-데이터-로드-노드">📥 데이터 로드 노드</h4>
<pre><code class="language-python">import pandas as pd

def load_data(state: State) -&gt; State:
    data = pd.read_csv(&quot;data/example.csv&quot;)
    return {**state, &quot;data&quot;: data}</code></pre>
<p>데이터를 가져오는 간단한 코드다. 데이터 시각화를 위해서 CSV 파일을 가져온다. 참고로 해당 <code>example.csv</code> 파일은 <a href="https://www.kaggle.com/competitions/titanic/data">Kaggle의 Titanic 생존자 예측 데이터</a>를 사용했다.</p>
<h4 id="🧹-데이터-전처리-노드">🧹 데이터 전처리 노드</h4>
<pre><code class="language-python">def preprocess_data(state: State) -&gt; State:
    processed_data = state[&quot;data&quot;].head(100)
    return {**state, &quot;processed_data&quot;: processed_data}</code></pre>
<p>데이터가 너무 많으면 처리 시간이 길어지니까 상위 100개만 추려보자.  </p>
<h4 id="🧠-코드-생성-노드">🧠 코드 생성 노드</h4>
<pre><code class="language-python">from langchain_openai import ChatOpenAI

def analyze_data(state: State) -&gt; State:
    model = ChatOpenAI(model=&quot;gpt-4o-mini&quot;)
    prompt = f&quot;{state[&#39;input&#39;]} 데이터: {state[&#39;processed_data&#39;]} 단, 오직 Code만 출력하세요.&quot;
    response = model.invoke(prompt)
    return {**state, &quot;code&quot;: response.content}</code></pre>
<p>모델에게 코드 생성을 요청하면, 사용자 입력과 전처리된 데이터를 기반으로 코드를 뚝딱 만들어준다. </p>
<p>만약 코드를 한번만 만든다면 위처럼 정의하면 되겠지만, 우리는 다이어그램에서 에러가 발생하면 다시 코드를 작성하기로 했으니까 코드를 조금 더 추가해야 한다.</p>
<pre><code class="language-python">def analyze_data(state: State) -&gt; State:
    # 데이터 분석 로직 구현
    model = ChatOpenAI(model=&quot;gpt-4o-mini&quot;)
    if state[&quot;is_error&quot;] is False:
        # 일반 코드 제작 프롬프트
        response = model.invoke(
            (
                f&quot;{state[&#39;input&#39;]} &quot;
                &quot;단, 오직 Code만 출력하세요. &quot;
                f&quot;데이터: {state[&#39;processed_data&#39;]} &quot;
            )
        )
    else:
        # Visualization 노드에서 코드 에러 발생시 다시 코드를 생성하는 프롬프트
        response = model.invoke(
            (
                f&quot;{state[&#39;input&#39;]} &quot;
                &quot;단, 오직 Code만 출력하세요. &quot;
                f&quot;데이터: {state[&#39;processed_data&#39;]} &quot;
                &quot;====================== &quot;
                f&quot;[AI] {state[&#39;code&#39;]} &quot;
                f&quot;{state[&#39;result&#39;]} &quot;
                &quot;====================== &quot;
                &quot;실행 중 발생한 버그를 참고하여 다시 코드를 작성해주세요.&quot;
            )
        )
    code = response.content
    return State(code=code)
</code></pre>
<p>만약 처음으로 코드를 생성하는 상태라면 <code>state[&quot;is_error&quot;]</code>가 <code>False</code>이므로 첫번째 if 문에서 &quot;일반 코드 제작 프롬프트&quot;로 LLM을 호출한다.</p>
<p>하지만 visualization 노드에서 코드를 실행했는데 error가 뜬 상황이라면 <code>state[&quot;is_error&quot;]</code>가 <code>True</code>이므로 else 문에서 &quot;다시 코드를 생성하는 프롬프트&quot;로 LLM을 호출한다. 여기에는 에러 메시지와 다시 코드를 작성하라는 문구가 포함되어 있는 것을 볼 수 있다.</p>
<h4 id="📊-시각화-노드">📊 시각화 노드</h4>
<pre><code class="language-python">def visualization(state: State) -&gt; State:
    import seaborn as sns
    import matplotlib.pyplot as plt
    from langchain_experimental.tools.python.tool import PythonAstREPLTool

    code = state[&quot;code&quot;]
    repl_tool = PythonAstREPLTool(locals={&quot;sns&quot;: sns, &quot;plt&quot;: plt})

    try:
        exec_result = repl_tool.invoke(code)
        return {**state, &quot;result&quot;: exec_result, &quot;is_error&quot;: False}
    except Exception as e:
        return {**state, &quot;result&quot;: str(e), &quot;is_error&quot;: True}</code></pre>
<p>AI가 생성한 코드를 실행해서 시각화를 출력한다. 실행 중 문제가 있으면 에러 메시지가 반환된다.</p>
<blockquote>
<h3 id="🚀-tips">🚀 <em>Tips</em></h3>
<p><strong>PythonAstREPLTool</strong>이 코드를 실행하는 Tool이다. PythonREPLTool과의 차이점은 보안이 조금 더 강하다는 점이다. <strong>PythonAstREPLTool</strong>은 코드 실행 전에 추상 구문 트리(Abstract Syntax Tree, AST) 분석을 통해 잠재적으로 위험한 코드나 금지된 연산을 사전에 차단할 수 있다. 이를 통해 외부 라이브러리 접근, 파일 시스템 조작, 무한 루프 실행 등 의도치 않은 동작을 방지하며 보다 안전한 코드 실행 환경을 제공한다. 따라서, 사용자 입력을 기반으로 코드를 실행해야 하는 시나리오에서 <strong>PythonAstREPLTool</strong>은 안정성과 보안을 모두 확보할 수 있는 적절한 선택지이다.</p>
</blockquote>
<hr>
<h3 id="3️⃣-플로우-구성하기">3️⃣ 플로우 구성하기</h3>
<p>각 노드를 연결해 흐름을 완성하자.</p>
<pre><code class="language-python">from langgraph.graph import StateGraph, START, END

def build_flow():
    flow = StateGraph(State)

    flow.add_node(&quot;load_data&quot;, load_data)
    flow.add_node(&quot;preprocess_data&quot;, preprocess_data)
    flow.add_node(&quot;analyze_data&quot;, analyze_data)
    flow.add_node(&quot;visualization&quot;, visualization)

    flow.add_edge(START, &quot;load_data&quot;)
    flow.add_edge(&quot;load_data&quot;, &quot;preprocess_data&quot;)
    flow.add_edge(&quot;preprocess_data&quot;, &quot;analyze_data&quot;)
    flow.add_edge(&quot;analyze_data&quot;, &quot;visualization&quot;)

    flow.add_conditional_edges(
        &quot;visualization&quot;,
        lambda state: &quot;analyze_data&quot; if state[&quot;is_error&quot;] else &quot;end&quot;,
        {&quot;analyze_data&quot;: &quot;analyze_data&quot;, &quot;end&quot;: END},
    )

    return flow.compile()</code></pre>
<p>에러가 발생하면 자동으로 코드 생성 단계로 돌아가 재실행할 수 있다. <code>flow.add_conditional_edges</code>에서 <code>&quot;visualization&quot;</code> 노드로부터 이어지는 conditional edges를 정한다. <code>state[&quot;is_error&quot;]</code>가 <code>True</code>라면 <code>&quot;analyze_data&quot;</code> 노드로 이어지고, <code>False</code>라면 <code>END</code> 로 이어진다.</p>
<blockquote>
<h3 id="🚀-tips-1">🚀 <strong>Tips</strong></h3>
<p>LangGraph의 <code>State</code> 객체는 직렬화를 거치기 때문에 <code>pandas.DataFrame</code>처럼 직렬화가 까다로운 객체는 직접 저장하기 어려울 수 있다. 특히 해당 Graph를 SubGraph로 추가할 때 직렬화와 관련된 Pydantic Error가 발생한다면 아래 솔루션을 시도해보자. 
✅ 해결 방법:</p>
</blockquote>
<ul>
<li><strong>CSV, JSON 등 문자열 형태로 변환하여 저장:</strong>  <pre><code class="language-python">state[&quot;data&quot;] = df.to_json()  # 저장 시
df = pd.read_json(state[&quot;data&quot;])  # 불러올 때</code></pre>
이렇게 하면 상태 전환 시 데이터 손실을 방지하면서도 안정적인 처리가 가능하다.</li>
</ul>
<hr>
<h3 id="4️⃣-실행-및-결과-확인">4️⃣ 실행 및 결과 확인</h3>
<pre><code class="language-python">if __name__ == &quot;__main__&quot;:
    flow = build_flow()
    initial_state = State(
        input=&quot;Titanic 데이터를 시각적으로 멋지게 보여줘!&quot;,
        data=&quot;&quot;,
        processed_data=&quot;&quot;,
        code=&quot;&quot;,
        is_error=False,
        result=&quot;&quot;
    )

    for event in flow.stream(initial_state):
        for step, output in event.items():
            print(f&quot;\n🚀 STEP: {step}\n{output}\n&quot;)</code></pre>
<h3 id="✅-실행-결과-예시">✅ 실행 결과 예시</h3>
<h4 id="🚀-step별-진행-로그">🚀 <strong>STEP별 진행 로그</strong></h4>
<pre><code>🚀 STEP: load_data
데이터 로드 완료!

🚀 STEP: preprocess_data
상위 100개 데이터 전처리 완료!

🚀 STEP: analyze_data
코드 생성 성공! 📝

🚀 STEP: visualization
시각화 성공 🎉 (에러 없음)</code></pre><hr>
<h4 id="📄-실제-state-데이터-dump">📄 <strong>실제 State 데이터 (Dump)</strong></h4>
<h5 id="📥-step-load_data">📥 <strong>STEP: load_data</strong></h5>
<p>데이터 로드 후 State에 저장된 내용:</p>
<pre><code class="language-python">{
  &#39;data&#39;: 
      PassengerId  Survived  Pclass                                               Name     Sex  ...  Fare Cabin  Embarked
0              1         0       3                            Braund, Mr. Owen Harris    male  ...  7.2500   NaN      S
1              2         1       1  Cumings, Mrs. John Bradley (Florence Briggs Th...  female  ... 71.2833   C85      C
2              3         1       3                             Heikkinen, Miss. Laina  female  ...  7.9250   NaN      S
...          ...       ...     ...                                                ...     ...  ...     ...   ...    ...
890          891         0       3                                Dooley, Mr. Patrick    male  ...  7.7500   NaN      Q

[891 rows x 12 columns]
}</code></pre>
<hr>
<h5 id="🧹-step-preprocess_data">🧹 <strong>STEP: preprocess_data</strong></h5>
<p>상위 100개 데이터 전처리 후 State에 저장된 내용:</p>
<pre><code class="language-python">{
  &#39;processed_data&#39;: 
      PassengerId  Survived  Pclass                                               Name  ...     Fare  Cabin Embarked
0              1         0       3                            Braund, Mr. Owen Harris  ...    7.2500   NaN       S
1              2         1       1  Cumings, Mrs. John Bradley (Florence Briggs Th...)  ...   71.2833    C85      C
2              3         1       3                             Heikkinen, Miss. Laina  ...    7.9250   NaN       S
...          ...       ...     ...                                                ...  ...      ...    ...     ...
99           100         0       2                                  Kantor, Mr. Sinai  ...   26.0000    NaN       S

[100 rows x 12 columns]
}</code></pre>
<hr>
<h5 id="📊-step-analyze_data">📊 <strong>STEP: analyze_data</strong></h5>
<p>분석 코드 생성 후 State에 저장된 내용:</p>
<pre><code class="language-python">{
  &#39;code&#39;: 
&quot;&quot;&quot;
import pandas as pd
import matplotlib.pyplot as plt
import seaborn as sns

# 데이터 프레임 생성
data = {
    &#39;PassengerId&#39;: range(1, 101),
    &#39;Survived&#39;: [0, 1, 1, 1, 0] * 20,
    &#39;Pclass&#39;: [3, 1, 3, 1, 3] * 20,
    &#39;Fare&#39;: [7.25, 71.2833, 7.925, 53.1, 8.05] * 20,
    &#39;Embarked&#39;: [&#39;S&#39;, &#39;C&#39;, &#39;S&#39;, &#39;S&#39;, &#39;S&#39;] * 20
}
df = pd.DataFrame(data)

# 시각화
plt.figure(figsize=(12, 8))

# 1. 생존자 수
plt.subplot(2, 2, 1)
sns.countplot(data=df, x=&#39;Survived&#39;, palette=&#39;pastel&#39;)
plt.title(&#39;Survival Count&#39;)

# 2. 클래스별 생존율
plt.subplot(2, 2, 2)
sns.barplot(data=df, x=&#39;Pclass&#39;, y=&#39;Survived&#39;, palette=&#39;pastel&#39;)
plt.title(&#39;Survival Rate by Class&#39;)

# 3. 요금 분포
plt.subplot(2, 2, 3)
sns.histplot(data=df, x=&#39;Fare&#39;, kde=True, bins=30, color=&#39;purple&#39;)
plt.title(&#39;Fare Distribution&#39;)

# 4. 탑승 항구별 생존자 수
plt.subplot(2, 2, 4)
sns.countplot(data=df, x=&#39;Embarked&#39;, hue=&#39;Survived&#39;, palette=&#39;pastel&#39;)
plt.title(&#39;Survival Count by Embarked&#39;)

plt.tight_layout()
plt.show()
&quot;&quot;&quot;
}</code></pre>
<hr>
<h5 id="🖼️-step-visualization">🖼️ <strong>STEP: visualization</strong></h5>
<p>시각화 결과 및 실행 상태:</p>
<pre><code class="language-python">{
  &#39;result&#39;: &#39;&#39;,
  &#39;is_error&#39;: False
}</code></pre>
<p>✅ <strong>📈 시각화된 Figure:</strong><br><img src="https://velog.velcdn.com/images/t_wave/post/84edc236-36a4-4c0e-a982-4923b853f0b1/image.png" alt="시각화 결과"></p>
<p><strong>Titanic 데이터 시각화 결과:</strong><br>🧍‍♂️ 생존자 수 비교 | 🏷️ 클래스별 생존율<br>💰 요금 분포      | 🚢 탑승 항구별 생존자 수  </p>
<hr>
<h3 id="🌊-플로우-시각화">🌊 <strong>플로우 시각화</strong></h3>
<pre><code class="language-python">from IPython.display import display, Image
from main import build_flow

flow = build_flow()
display(Image(flow.get_graph(xray=False).draw_mermaid_png()))</code></pre>
<p><img src="https://velog.velcdn.com/images/t_wave/post/f9a92740-ffd3-4f52-b262-0e7e8cc082b2/image.png" alt=""></p>
<ul>
<li>각 단계(<code>load_data</code> → <code>preprocess_data</code> → <code>analyze_data</code> → <code>visualization</code>)에서 처리 결과가 <code>State</code>에 저장된다.  </li>
<li>시각화 단계에서 에러가 발생하면 <code>analyze_data</code> 단계로 되돌아가 코드가 자동으로 수정 및 재실행된다.  </li>
<li>정상적으로 실행될 경우 위와 같은 시각화 결과가 출력된다.  </li>
</ul>
<h3 id="전체-코드">전체 코드</h3>
<pre><code class="language-python">from typing import TypedDict
import pandas as pd

from langchain_openai import ChatOpenAI
from langchain_experimental.tools.python.tool import PythonAstREPLTool
from langgraph.graph import StateGraph, START, END


# State 정의
class State(TypedDict):
    input: str
    data: str
    processed_data: str
    code: str
    is_error: bool
    result: str


# 각 Node에 해당하는 함수 정의
def load_data(state: State) -&gt; State:
    # 데이터 로드 로직 구현
    data = pd.read_csv(&quot;./data/example.csv&quot;)
    return State(data=data)


def preprocess_data(state: State) -&gt; State:
    # 데이터 전처리 로직 구현
    data = state[&quot;data&quot;]
    processed_data = data.head(100)
    return State(processed_data=processed_data)


def analyze_data(state: State) -&gt; State:
    # 데이터 분석 로직 구현
    model = ChatOpenAI(model=&quot;gpt-4o-mini&quot;)
    if state[&quot;is_error&quot;] is False:
        response = model.invoke(
            (
                f&quot;{state[&#39;input&#39;]} &quot;
                &quot;단, 오직 Code만 출력하세요. &quot;
                f&quot;데이터: {state[&#39;processed_data&#39;]} &quot;
            )
        )
    else:
        response = model.invoke(
            (
                f&quot;{state[&#39;input&#39;]} &quot;
                &quot;단, 오직 Code만 출력하세요. &quot;
                f&quot;데이터: {state[&#39;processed_data&#39;]} &quot;
                &quot;====================== &quot;
                f&quot;[AI] {state[&#39;code&#39;]} &quot;
                f&quot;{state[&#39;result&#39;]} &quot;
                &quot;====================== &quot;
                &quot;실행 중 발생한 버그를 참고하여 다시 코드를 작성해주세요.&quot;
            )
        )
    code = response.content
    return State(code=code)


def visualization(state: State) -&gt; State:
    # 코드 실행
    import seaborn as sns
    import matplotlib.pyplot as plt

    code = state[&quot;code&quot;]
    processed_code = None
    if code.startswith(&quot;```&quot;):
        processed_code = &quot;\n&quot;.join(code.split(&quot;\n&quot;)[1:-1])
    else:
        processed_code = code

    python_repl_tool = PythonAstREPLTool(locals={&quot;sns&quot;: sns, &quot;plt&quot;: plt})
    try:
        exec_result = str(python_repl_tool.invoke(processed_code))
        return State(result=exec_result, is_error=False)
    except Exception as e:
        return State(result=e, is_error=True)


def build_flow():
    # flow init
    flow = StateGraph(State)

    # node 정의
    flow.add_node(&quot;load_data&quot;, load_data)
    flow.add_node(&quot;preprocess_data&quot;, preprocess_data)
    flow.add_node(&quot;analyze_data&quot;, analyze_data)
    flow.add_node(&quot;visualization&quot;, visualization)

    # edge 연결
    flow.add_edge(START, &quot;load_data&quot;)
    flow.add_edge(&quot;load_data&quot;, &quot;preprocess_data&quot;)
    flow.add_edge(&quot;preprocess_data&quot;, &quot;analyze_data&quot;)
    flow.add_edge(&quot;analyze_data&quot;, &quot;visualization&quot;)

    # codintional edge 연결
    def is_error_check(state: State) -&gt; str:
        return &quot;analyze_data&quot; if state[&quot;is_error&quot;] else &quot;end&quot;

    flow.add_conditional_edges(
        &quot;visualization&quot;,
        is_error_check,
        {&quot;analyze_data&quot;: &quot;analyze_data&quot;, &quot;end&quot;: END},
    )

    # compile
    return flow.compile()


if __name__ == &quot;__main__&quot;:
    flow = build_flow()
    initial_state = State(
        input=&quot;다음 데이터를 전문적으로 시각화하여 하나의 Figure로 표현해주세요.&quot;,
        is_error=False,
        code=&quot;&quot;,
        reulst=&quot;&quot;,
    )
    for event in flow.stream(initial_state):
        for node_name, value in event.items():
            print(f&quot;\n==============\nSTEP: {node_name}\n==============\n&quot;)
            print(value)
</code></pre>
<hr>
<h2 id="🔔-마무리">🔔 마무리</h2>
<p>이번 글에서는 <strong>LangGraph</strong>를 활용한 데이터 처리 파이프라인 구축 방법과 단계별 시각화 과정을 다뤘다.<br>각 단계별 <strong>에러 처리 흐름</strong>, <strong>조건부 플로우 구성</strong>, 그리고 <strong>State 직렬화 팁</strong> 등을 포함해 실전 프로젝트에 적용할 수 있는 내용을 정리했다. 🚀  </p>
<hr>
<h2 id="🔗-추가-자료--코드-확인하기">🔗 추가 자료 &amp; 코드 확인하기</h2>
<p>📂 <strong>전체 코드 GitHub에서 확인하기:</strong><br>🔗 <a href="https://github.com/Twave1717/Agentic-Code-Generator">GitHub - Agentic Code Generator</a>  </p>
<p>📚 <strong>참고 자료:</strong><br>🔗 <a href="https://github.com/langchain-ai/langgraph/blob/main/docs/docs/tutorials/code_assistant/langgraph_code_assistant.ipynb">LangGraph Code Assistant Tutorial</a>  </p>
<hr>
<p>💬 <strong>읽어주셔서 감사합니다!</strong><br>🙌 도움이 되셨다면 ⭐️ <strong>스타</strong>를 눌러주시면 큰 힘이 됩니다.<br>✅ <strong>추가로 다뤄보면 좋을 주제</strong>나 궁금한 내용이 있다면 댓글로 알려주세요. 관련 글을 작성해 보겠습니다. 😊  </p>
]]></description>
        </item>
        <item>
            <title><![CDATA[구글처럼 딥러닝 프로젝트 기획하기]]></title>
            <link>https://velog.io/@t_wave/machinlearningworkflow</link>
            <guid>https://velog.io/@t_wave/machinlearningworkflow</guid>
            <pubDate>Fri, 18 Aug 2023 08:32:53 GMT</pubDate>
            <description><![CDATA[<style>
.orange{color:orange}
</style>

<blockquote>
</blockquote>
<p>🎁🎁 <span style="color:orange"> 케라스 창시자에게 배우는 딥러닝 개정2판 5~6장 </span>을 참고하였습니다!</p>
<p>전체적인 머신러닝 워크플로를 정리해봤다. 책을 많이 참고했고 책보다 최대한 재미있게 써보려고 노력했다. <del>제목 어그로 죄송</del></p>
<p>맨날 따라치는 실습만 하다가 이 책에서 전체적인 개념과 관점을 배울 수 있었다. 대학 강의처럼 딱딱하지도 않고 훨씬 재밌다. 딥러닝에 관심있다면 <a href="https://www.yes24.com/Product/Goods/112012471">위에 책을</a> 한 번 꼭 읽어보길 바란다.</p>
<hr>

<h1 id="1-문제가-뭐지">1. 문제가 뭐지?</h1>
<p>흔히 머신러닝 워크플로에서 데이터 정제가 가장 중요하다고 한다.
하지만 가장 간지나는 일은 모델 개발 아닌가? 그래서 다들 모델 개발 쪽에 공부하는 시간을 많이 투자하는 것 같다. 사실 나도 모델 개발 공부가 가장 전문적이고 재밌어보인다.</p>
<p>재밌는 일만 할 수는 없으니까.. 
1번에서는 작업 시간의 대부분을 잡아먹는 문제 정의 및 데이터 정제 파트를 이야기한다. 나름 이 파트도 깊게 파고들면 확률적인 데이터 클리닝 방법론이 많다. 물론 여기서는 전체적인 워크플로를 볼 것이기 때문에 깊게 들어가진 않는다.</p>
<h2 id="✔-어떤-데이터가-문제를-잘-설명하지-🧐">✔ 어떤 데이터가 문제를 잘 설명하지? 🧐</h2>
<h3 id="❓❓❓❗">❓❓❓❗</h3>
<p><img src="https://velog.velcdn.com/images/t_wave/post/6a202d97-292d-4f9e-ac5a-0d056a82d24a/image.png" alt=""></p>
<p>머신러닝을 쓴다고 항상 문제가 해결되진 않는다. 오히려 전통적인 통계 모델링이 더 효과적일 수 있다. 그렇기에 문제를 자세히 파고들어 룰을 이해하고 가장 적합한 솔루션을 찾아야 한다. 물론 인공지능 성능이 너무 좋아서 대충 때려넣어도 해결될 때가 있지만... 그러면 안된다.</p>
<blockquote>
</blockquote>
<ul>
<li>입력 데이터는 무엇인가? 무엇을 예측하려고 하는가?</li>
<li>이진분류인가? 다중분류인가? 스칼라 회귀인가? 벡터 회귀인가? 이미지 분할? 강화학습?</li>
<li>기존 솔루션은 어떤 것이 있을까?</li>
<li>고려해야 할 특별한 제약이 있을까? 모델이 엔드-투-엔드로 엄격히 암호화되어 있어 모델을 꼭 사용자의 디바이스에 넣어야 한다든지..</li>
</ul>
<p>일단 문제를 들어가기 전에 물음표를 계속 던질 필요가 있다. 기획 단계에서 최-대한 꼼꼼하게 해두지 않아서 후반 단계에서 차질이 생기면 일을 다시해야 할 수도 있다.</p>
<p>또 이 문제를 머신러닝으로 풀겠다는 것은 </p>
<ol>
<li><strong>주어진 입력으로 타깃을 예측</strong>할 수 있고</li>
<li><strong>데이터가 결과에 대한 충분한 정보를 담고있다</strong>고 가정한다.</li>
</ol>
<p><del>나는 이걸 제대로 안해서 프로젝트를 통째로 날려먹은 적이 있다...</del></p>
<h3 id="라벨링-노가다-😫">라벨링 노가다 😫</h3>
<p>문제가 뭔지 파헤치면서 작업 특성을 이해하고, 입력과 타깃이 무엇인지 알게되면 데이터를 수집하기 시작한다. 책에서는 &#39;노동집약적인 파트&#39;라고 고급지게 표현했지만 내 표현이 더 적절한 거 같다.</p>
<p><img src="https://velog.velcdn.com/images/t_wave/post/f0afbd12-e7de-4891-895f-0b504ecab665/image.png" alt="막내의 비애"></p>
<p>대부분 연구소에서는 이런 일을 막내에게 시키거나 외주를 맡긴다 <del>(그게 나다)</del>. 혹시 학교 학부연구생에게 라벨링을 시킨다고 하면, 내가 연구소에 배우러 가는지 일하러 가는지 비율을 진지하게 고민해봐야 한다.</p>
<p>시간은 많이 들겠지만 <span style="color:orange">최대한 편한 라벨링 작업을 하기 위해</span> 개발자들은 고민해야한다. 예를 들어 이미지, 영상에서 바인딩 박스를 어떻게 효율적으로 체크할 수 있을지 고민해보자. 온라인에서 &#39;바인딩 박스 툴&#39;을 다운받아 열심히 수작업으로 할 수도 있다. 하지만 이는 한계가 있으니 바인딩 박스를 만들어주는 인공지능 모델을 따로 만들 수도 있다. 모델이 작업한 박스를 검수만 하면 되니 시간이 훨씬 빨라진다. 간단한 토이 프로젝트일 경우 전자가, 규모가 비교적 큰 프로젝트면 후자가 좋은 선택지일 것이다.</p>
<p><span style="color:orange">또 라벨링을 누구나 할 수 있는지, 아니면 전문지식이 있는 사람만 할 수 있는지</span>도 고려해야한다. 위의 바인딩 박스는 누구나 할 수 있다. 한편 신약 개발을 위한 유력 시료 판단을 한다고 해보자. 시료 후보군이 1만개가 있고, 하나의 시료를 약으로 만들어 실험해보는데 3시간이 걸린다고 하자. 만약 1만개를 전부 실험해보려면 3만시간이 걸린다. 이 신약 개발 주제로 박사 논문을 쓰다 잘못하면 졸업까지 10년이 걸릴 수도 있다. 그러니 유력 시료를 판단하는 딥러닝 모델이 필요하다. 이 모델에 들어가는 데이터는 시료에 대한 전문 지식일 것이다. 이런 모델은 전문가만 라벨링할 수 있다. 과연 전문 인력을 라벨링에 쓸 정도로 좋은 주제인가? 고민해봐야 한다.</p>
<p><span style="color:orange">데이터의 실효성은 반드시 고민해야 한다</span>. 온라인에 올라온 음식점 음식 사진들과 내가 찍은 음식 사진은 많이 차이가 난다. 포토샵과 전문성에서 많이 밀린다. 만약 온라인의 전문 사진사가 찍은 사진으로만 모델을 학습시키고, 내 사진으로 예측을 한다면? 못 맞힐 수도 있다. 그러니 실제 예측에 사용될 데이터와 모델 학습 데이터를 최대한 일치시켜야 한다. </p>
<p>마지막으로 <span style="color:orange">&#39;개념 이동(concept dift)&#39;도 주의해야 한다</span>. 2013년에 훈련한 음악 추천 엔진으로 지금 추천을 부탁하면 영 시원찮은 결과가 나올 수 있다. 어휘, 표현, 장르의 트렌드가 시기에 따라 변하기 때문이다. 모델의 데이터도 트렌드에 맞춰야 한다. 지속적인 데이터 수집과 재훈련이 필요하다.</p>
<h2 id="✔-우리의-문샷은-이거야-🌚">✔ 우리의 문샷은 이거야! 🌚</h2>
<p>앞서 말했듯 데이터에 대한 이해 없이 대충 모델에 때려넣으면 될거라는 믿음은 가지면 안된다. 모델 훈련에 들어가기 전, 데이터를 탐색하고 시각화하여 예측 능력을 가진 특성이 뭔지 고민해봐야한다. 이를 <span style="color:orange">특성 공학</span>이라고 한다.</p>
<blockquote>
</blockquote>
<ul>
<li>이미지나 자연어 텍스트는 직접 확인해볼 수 있다. </li>
<li>수치는 히스토그램을 그려 범위, 빈도를 확인해볼 수 있다.</li>
<li>데이터가 위치 정보를 포함하면 지도에 그려본다.</li>
<li>누락된 값을 전처리도 해야한다.</li>
<li>분류 작업이라면 각 클래스 별로 데이터의 수가 균등하게 있는지 고민해본다. 만약 하나에만 데이터가 몰려있다면 정확도에 악영향을 준다.</li>
<li>&#39;타깃 누출&#39;을 확인한다. 타깃에 너무 직접적인 영향을 주는 특성이 섞여있다면 그 특성에만 가중치가 몰릴 수 있다.</li>
</ul>
<p>그 다음 성공 지표를 선택한다. 정확도, 정밀도, 재현율, 고객 재방문율 등 모델의 목적에 따라 성공 지표가 달라진다. <span style="color:orange">모델의 단순한 수치 연산 결과를 고수준의 목표로 연결시키려면 논리적인 설명이 뒷받침되어야 한다</span>. </p>
<hr>

<h1 id="2-딥-러닝-모델-개발">2. 딥-러닝 모델 개발!</h1>
<p><img src="https://velog.velcdn.com/images/t_wave/post/b1c97f66-2f7e-40cb-b14b-416a4e24c2a2/image.png" alt="짱구짱짱맨"></p>
<h2 id="✔-데이터-클리닝-✨">✔ 데이터 클리닝 ✨</h2>
<h3 id="벡터화">벡터화</h3>
<p>데이터를 신경망에 넣으려면 부동 소수점 데이터로 이루어진 텐서 형태로 변환해줘야 한다. 이 단계를 <strong>데이터 벡터화</strong>라고 부른다. </p>
<h3 id="값-정규화">값 정규화</h3>
<p>이미지 데이터를 그레이스케일 인코딩인 0<del>255 사이의 정수로 인코딩하거나, 각기 다른 특성들의 범위를 0</del>1의 확률공간으로 압축시켜버리는 등의 정규화 작업을 할 수 있다. 키(cm)랑 몸무게(kg)로 성별을 맞추는데 값을 정규화 없이 넣으면 비교적 숫자가 높은 키에 편향된 결과가 나올 수 있다.</p>
<h3 id="누락된-값-처리">누락된 값 처리</h3>
<p>데이터에 NaN이나 Null 같은 빵꾸가 나있으면 학습이 덜 된다. 평균값, 최빈값, 앞에 값 끌어다 쓰기 등 일반적인 처리 방법부터 통계학적인 방법까지 다양한 처리 방법이 있다.</p>
<h2 id="✔-과대적합-모델-만들기-📈">✔ 과대적합 모델 만들기 📈</h2>
<p>데이터를 넣고 훈련을 할 때 신경망 가운데 층이 너무 작지도, 너무 크지도 않아야 한다. 적당한 크기를 찾기 위해 작은 모델부터 점점 크기를 키워가며 적당한 신경망을 찾는다. 크기를 기우는 방법은</p>
<blockquote>
</blockquote>
<ol>
<li><p>층 추가하기</p>
</li>
<li><p>층 크기를 키우기!</p>
</li>
<li><p>더 오래 훈련하기 (에포크 Up)</p>
</li>
</ol>
<p>가 있다.</p>
<p>또 다른 방법으로 모델 규제와 하이퍼파라미터 튜닝이 있다. </p>
<blockquote>
</blockquote>
<ol>
<li>다른 구조를 시도해본다. 층을 추가하거나 제거한다.</li>
<li>드롭아웃을 추가한다.</li>
<li>모델이 작다면 L1 또는 L2규제를 추가한다.</li>
<li>최적의 설정을 찾기 위해 하이퍼파라미터를 바꿔서 시도해본다.</li>
<li>데이터 큐레이션이나 특성 공학을 시도해본다.</li>
</ol>
<hr>

<h1 id="3-유저님-맛이-어떠신가요">3. 유저님, 맛이 어떠신가요?</h1>
<h2 id="✔-모델-설명하기">✔ 모델 설명하기</h2>
<p><img src="https://velog.velcdn.com/images/t_wave/post/01c6bd33-982a-4968-8036-a6c19ee2d3c2/image.jpg" alt="나는일을잘해"></p>
<p>인공지능 모델을 다 만들었다고 끝난게 아니다. 마지막으로 유저님 밥상에 가져다 놔야 한다. </p>
<p>우선 AI를 잘 모르는 사람들에게 내가 만든 AI를 잘 설명해야 한다. 이때 &quot;98% 정확도의 모델입니다&quot;라고 설명하면 비전문가는 반올림해서 100%로 알아듣는다. 이는 자칫 오해를 초래한다. 매일 평균 1만건의 메일 중에서 스팸 메일을 판별하는 모델이 있다면 하루 평균 2천건의 거짓 판별이 나온다. 그러므로 비전문가에게 말할 때는 거짓 음성 비율과 거짓 양성 비율을 정확히 말할 필요가 있다. 책에는 다음과 같은 예시를 들어주었다.</p>
<blockquote>
</blockquote>
<p>&quot;이런 설정에서는 부정 거래 감지 모델이 5% 거짓 음성 비율과 2.5%의 거짓 양성 비율을 달성합니다. 매일 평균 200건의 유효한 거래가 부정 거래로 표시되어 수동 검토를 위해 전달됩니다. 평균적으로 14개의 부정 거래를 놓치며 266개의 부정 거래를 정확하게 감지합니다.&quot;</p>
<h2 id="✔-모델-배포하기-🍻">✔ 모델 배포하기 🍻</h2>
<p>제품의 환경이 파이썬을 지원하지 않는다면 파이썬이 아닌 다른 방식으로 모델을 배포해야 한다. 여러가지 방법이 있는데 한번 훑어보자.</p>
<h3 id="서버에서-돌리기-rest-api">서버에서 돌리기: REST API</h3>
<p>서버나 클라우드 인스턴스에 텐서플로를 설치하고 REST API로 모델의 예측을 요청하는 방식이다. 요새는 클라우드에서 Docker를 활용해 그 안에 파이썬 코드를 넣는다. Docker 사용법은 내 velog에 나와있으니 확인해보길 바란다..ㅎㅎ</p>
<p>플라스크로 직접 서빙 앱을 만들거나 텐서플로 자체 라이브러리인 텐서플로 서빙을 사용하면 된다. 단, 이 방법을 사용하려면 클라이언트가 서버에 접속 가능해야 하고, 응답 속도 면에서도 고려해야 하며, 데이터가 크게 민감하지 않아야 클라우드로 주고 받을 수 있다. 이런 부분들을 고려해야 한다.</p>
<h3 id="로컬에서-돌리기-장치로-배포">로컬에서 돌리기: 장치로 배포</h3>
<p>모델을 장치에서 직접 구동해야할 경우 장치 안에 모델을 임베딩해야 한다. 이런 경우 모델을 장치에 들어갈 정도로 작게 최적화해야 한다. 장치의 사양에 따라 다양한 변수가 생길 수 있다.예를 들면, 바다를 가로지르는 배에 자율주행 모델을 심는다면 커다란 컴퓨터 장치를 싣을수 있으니 크기가 상관 없겠지만 드론은 꽤 제약이 있다. 해양에서는 클라우드로 육지의 서버와 통신도 어려우니 드론을 조종하는 장치에서 모든게 처리되어야 한다. 이런 작은 컴퓨터에서도 모델이 돌아갈 수 있도록 Llama2 같은 소형 모델들이 나오는 것이다.</p>
<p>아니면 회사 기밀 문서들을 학습시키는 모델 같은 경우 클라우드로 정보를 주고 받기 어렵다. 이러면 사내 장치로 모델을 돌릴 수 밖에 없다.</p>
<h3 id="추론모델-최적화">추론모델 최적화</h3>
<p>로컬에서 모델을 돌리려면 모델을 작게 만드는게 좋다. 최적화를 위해 보통 가중치 가지치기, 가중치 양자화 기법이 사용된다.</p>
<blockquote>
</blockquote>
<ul>
<li>가중치 가지치기: 드롭아웃처럼 신경망 내의 텐서를 죽인다. 단, 여기서는 랜덤이 아니라 철저하게 필요 없는 텐서를 삭제한다.</li>
<li>가중치 양자화: 딥러닝 모델은 부동 소수점(float32) 가중치로 훈련된다. 이를 정수(int8)로 반올림하면 크기가 1/4로 줄어든다.</li>
</ul>
<h3 id="모델-유지-관리">모델 유지 관리</h3>
<p>모델 배포를 마쳤다면 끊임없이 성과 지표를 확인하며 개선 방안을 모색한다. MLOps 직무가 이런 일을 하는 것 같다.</p>
<hr>

<h1 id="글을-정리하며">글을 정리하며</h1>
<p>대학생이 이 전 과정을 거칠 수 있는 가장 좋은 방법은 공모전이 아닐까? Kaggle처럼 정제된 데이터로 연습할 수 있는 플랫폼이 있어 얼마나 다행인지 모른다. </p>
<h5 id="끝">끝!<img src="https://velog.velcdn.com/images/t_wave/post/851bee24-6e9f-4c73-be0e-50db232606d3/image.png" alt=""></h5>
]]></description>
        </item>
        <item>
            <title><![CDATA[5분만에 챗봇 만들고 호스팅까지 (Gradio, LangChain)]]></title>
            <link>https://velog.io/@t_wave/gradiolangchainchatbot</link>
            <guid>https://velog.io/@t_wave/gradiolangchainchatbot</guid>
            <pubDate>Wed, 09 Aug 2023 01:43:50 GMT</pubDate>
            <description><![CDATA[<p><strong>그라디오(Gradio)</strong>
  <a href="https://www.gradio.app/">그라디오(Gradio)</a>는 인공지능 서비스의 UI를 프로토타입으로 간편히 제작하기 좋은 프레임워크다. 현재 버전 3.39.0까지 릴리즈되었고 파이썬과 자바스크립트를 지원한다. <a href="https://huggingface.co/">허깅페이스(Hugging Face)</a> 플랫폼으로 독자적인 생태계를 구축하여 앞으로도 꾸준히 성장할 것으로 기대된다.</p>
<p><strong>랭체인(LangChain)</strong>
      <a href="https://www.langchain.com/">랭체인(LangChain)</a>은 언어 모델을 간편하게 개발하기 좋은 프레임워크다. 현재 버전 0.0.256까지 릴리즈되어 비교적 신생이지만, 한 달에 400만 이상의 사람이 다운받는 등 관심도와 참여도로 보아 무섭게 성장할 것으로 보인다. 여러 종류의 언어 모델은 각기 다른 입출력 형식과 자잘한 차이점들을 가지고 있다. 서로 다른 모델을 모듈화하여 레고 조립하듯이 언어 모델 서비스를 개발할 수 있다.</p>
<p><strong>그라디오와 랭체인을 활용한 챗봇 서비스 개발</strong>
  이 글에서는 그라디오와 랭체인을 활용하여 기초적인 챗봇 서비스를 개발하는 과정을 다룬다. 그라디오로 UI(User Interface)를 구현하고, 랭체인으로 간단한 챗봇을 개발하여 그라디오에 연결시킨다.</p>
<h1 id="1-그라디오의-기본-구조">1. 그라디오의 기본 구조</h1>
<p>그라디오는 블록을 쌓듯이 UI를 구현할 수 있는 프레임워크다. React.js에서 컴포넌트를 만들어 구조화하듯이 그라디오에서 제공하는 컴포넌트를 쌓고 함수를 연결하여 사용한다.  그라디오를 사용하기 위해 다음 명령어를 명령 프롬프트(cmd)에 입력한다.</p>
<pre><code>pip install gradio</code></pre><p>아래 표는 자주 사용하는 그라디오 컴포넌트에 대한 설명이다. 이를 참고하여 아래 그라디오의 기본 기능이 들어간 예제 코드를 살펴보자. </p>
<table border="1">
    <th>컴포넌트 이름</th>
    <th>설명</th>
    <tr>
        <td>Blocks()</td>
        <td>인터페이스를 정의한다. 블록을 쌓을 기반이다.</td>
    </tr>
    <tr>
        <td>Row()</td>
        <td>내부 블록들을 가로로 쌓는다.</td>
    </tr>
  <tr>
    <td>
      Column()
    </td>
    <td>
      내부 블록들을 세로로 쌓는다.
    </td>
  </tr>
  <tr>
    <td>
      Markdown()
    </td>
    <td>
      파라미터에 문자열을 넣으면 마크다운 문법에 따라 인터페이스에 추가된다.
    </td>
  </tr>
  <tr>
    <td>
      Textbox()
    </td>
    <td>
      HTML의 input(type=“text”)에 해당한다. 인터페이스에 입력 칸을 추가한다. 
    </td>
  </tr>
  <tr>
    <td>
      Button()
    </td>
    <td>
      HTML의 button에 해당한다. 인터페이스에 버튼을 추가한다.
    </td>
  </tr>
  <tr>
    <td>
      Chatbot()
    </td>
    <td>
      인터페이스에 채팅 화면을 추가한다. 
    </td>
  </tr>
  <tr>
    <td>
      State()
    </td>
    <td>
      그라디오에서 사용할 변수를 추가한다. 그라디오 객체 형태로 만든다.
    </td>
  </tr>
  <tr>
    <td>
      queue()
    </td>
    <td>
      그라디오에서 사용할 명령어 큐를 정의한다. 큐의 작업은 순차적(동기)으로 실행된다.
    </td>
  </tr>
  <tr>
    <td>
      launch()
    </td>
    <td>
      인터페이스를 로컬(127.0.0.1)에서 호스팅한다. 파라미터에 ‘share=True’로 설정하면 일정 기간동안 월드와이드웹으로 호스팅해준다.
    </td>
  </tr>
</table>


<pre><code class="language-python">
# gradio library를 가져온다. gr로 줄여서 칭한다.
import gradio as gr

# gradio 내부에서 사용할 함수다.
def update(name): 
    return f&quot;Welcome to Gradio, {name}!&quot;

with gr.Blocks() as iface:
    gr.Markdown(&quot;Hello, World! I’m yelling in gradio!&quot;)
    with gr.Row():
        inp = gr.Textbox(placeholder=&quot;이름이 무엇인가요?&quot;)
        out = gr.Textbox()
    btn = gr.Button(&quot;제출&quot;)

    # 버튼에 이벤트 리스너를 추가한다. 
    # 버튼 클릭시 update함수를 호출하고, inp에 입력된 문자열을 파라미터로 보낸다. 함수의 반환값은 out에 출력한다.
    btn.click(fn=update, inputs=inp, outputs=out)

iface.launch()</code></pre>
<p><img src="https://velog.velcdn.com/images/t_wave/post/654e2a30-d66b-47b4-8a1c-ef8dd2b79bd8/image.png" alt="Fig 1. 예제 출력 화면">
위 코드를 실행시키고 웹 브라우저(Chrome)로 접속한 화면이다. </p>
<p><img src="https://velog.velcdn.com/images/t_wave/post/a629f903-ea54-4c80-a32c-ade598bd5054/image.png" alt="Fig 2. 예제 Input 입력 결과"></p>
<p>Input(왼쪽 텍스트박스)에 이름을 입력한 결과다. Input 입력 후 버튼 클릭시 update 함수를 호출하고, Input에 입력된 문자열을 파라미터로 보낸다. 함수의 반환값은 Output(오른쪽 텍스트박스)에 출력한다.</p>
<p><img src="https://velog.velcdn.com/images/t_wave/post/ace5f438-25d5-4a94-b05d-244b8c08f3d3/image.png" alt="Fig 3. 그라디오 화면 구성 (블록 쌓기)"></p>
<p>그라디오는 UI를 만들 때 블록을 쌓듯 컴포넌트를 세로로 차곡차곡 쌓는다. Fig3은 예제 코드의 컴포넌트가 어떻게 쌓여있는지 이미지로 보여준다.</p>
<h1 id="2-그라디오로-챗봇-ui-만들기">2. 그라디오로 챗봇 UI 만들기</h1>
<p>그라디오의 옛 버전에서는 Blocks 객체 내에서 여러 블록을 쌓고, 이벤트리스너(Event Listener)를 추가하여 챗봇을 구현하였다. 하지만 업데이트된 그라디오 3.39.0버전에서는 챗봇의 기능이 대부분 담긴 ChatInterface 컴포넌트가 생겼다. ChatInterface 컴포넌트 안에 챗봇UI, 입력, 버튼, 추가 옵션들을 모두 파라미터로 설정할 수 있다.</p>
<table>
<thead>
<tr>
<th>ChatInterface 옵션</th>
<th>설명</th>
</tr>
</thead>
<tbody><tr>
<td>fn</td>
<td>챗봇에게 말을 걸면 호출되는 함수입니다.</td>
</tr>
<tr>
<td>chatbot</td>
<td>챗봇 채팅 화면에 대해 정의합니다.</td>
</tr>
<tr>
<td>textbox</td>
<td>챗봇 채팅 입력칸을 정의합니다.</td>
</tr>
<tr>
<td>title</td>
<td>챗봇 제목을 정의합니다.</td>
</tr>
<tr>
<td>description</td>
<td>챗봇 설명을 정의합니다. 제목 밑에 작성합니다.</td>
</tr>
<tr>
<td>theme</td>
<td>챗봇 디자인 테마를 결정합니다.</td>
</tr>
<tr>
<td>examples</td>
<td>챗봇에게 물어볼 수 있는 예시 질문을 정의합니다.</td>
</tr>
<tr>
<td>retry_btn</td>
<td>가장 최근 물어본 질문을 다시 물어봅니다.</td>
</tr>
<tr>
<td>undo_btn</td>
<td>가장 최근 물어본 질문을 삭제합니다.</td>
</tr>
<tr>
<td>clear_btn</td>
<td>채팅 기록을 전부 삭제합니다.</td>
</tr>
<tr>
<td>additional_inputs</td>
<td>추가 블록을 정의합니다. fn에 추가 블록의 정보를 손쉽게 보낼 수 있습니다.</td>
</tr>
</tbody></table>
<pre><code class="language-python">
# 챗봇에 채팅이 입력되면 이 함수를 호출합니다. 
# message는 유저의 채팅 메시지, history는 채팅 기록, additional_input_info는 additional_inputs안 블록의 정보를 받습니다.
def response(message, history, additional_input_info):
    # additional_input_info의 텍스트를 챗봇의 대답 뒤에 추가합니다.
    return &quot;챗봇을 미완성하였습니다 &quot; + additional_input_info

gr.ChatInterface(
        fn=response,
        textbox=gr.Textbox(placeholder=&quot;말걸어주세요..&quot;, container=False, scale=7),
        title=&quot;어떤 챗봇을 원하심미까?&quot;,
        description=&quot;물어보면 답하는 챗봇임미다.&quot;,
        theme=&quot;soft&quot;,
        examples=[[&quot;안뇽&quot;], [&quot;요즘 덥다 ㅠㅠ&quot;], [&quot;점심메뉴 추천바람, 짜장 짬뽕 택 1&quot;]],
        retry_btn=&quot;다시보내기 ↩&quot;,
        undo_btn=&quot;이전챗 삭제 ❌&quot;,
        clear_btn=&quot;전챗 삭제 💫&quot;,
        additional_inputs=[
            gr.Textbox(&quot;!!!&quot;, label=&quot;끝말잇기&quot;)
        ]
).launch()</code></pre>
<p><img src="https://velog.velcdn.com/images/t_wave/post/3f4e9ca1-cedc-47e7-91db-09866ee225ac/image.png" alt="Fig 4. ChatInterface로 만든 챗봇 화면"></p>
<p>ChatInterface로 만든 챗봇 화면이다. 원하는 값만 넣었는데 결과가 꽤 멋있다!</p>
<h1 id="3-랭체인langchain으로-챗봇-함수-만들기">3. 랭체인(LangChain)으로 챗봇 함수 만들기</h1>
<p>그라디오로 유저와 상호작용할 수 있는 UI를 만들었다. 랭체인은 유저에게서 받은 메시지를 가공할 함수를 만드는 언어모델 프레임워크다. 랭체인을 활용해 OpenAI의 GPT-3.5모델을 연결하여 답변을 생성한 후 반환하는 함수를 만들어보자. 랭체인은 언어 생성 모델과 임베딩 모델을 패키징해줘 쉽게 import해 쓸 수 있다.
랭체인을 다운받기 위해 아래 커맨드를 명령 프롬프트(cmd)에 입력한다.</p>
<pre><code>pip install langchain</code></pre><p>아래는 그라디오 예제의 response함수를 랭체인을 활용해 OpenAI의 GPT-3.5모델을 연결한 코드다.</p>
<pre><code class="language-python">
from langchain.chat_models import ChatOpenAI
from langchain.schema import AIMessage, HumanMessage
import os

# os의 환경변수로 OPENAI_API_KEY를 설정한다. “...”에 api key를 입력해야 동작한다.
os.envrion[&quot;OPENAI_API_KEY&quot;] = &quot;...&quot;  

# langchain의 GPT-3.5 모델을 가져온다.
# temperature는 챗봇 대답의 자유도를 의미한다.
llm = ChatOpenAI(temperature=1.0, model=&#39;gpt-3.5-turbo-0613&#39;)

# 그라디오에서 유저의 질문과 채팅기록을 받아 llm의 input형식에 맞게 변환 및 입력 후 GPT-3.5의 대답을 리턴한다.
def response(message, history, additional_input_info):
        history_langchain_format = []
        for human, ai in history:
                history_langchain_format.append(HumanMessage(content=human))
                history_langchain_format.append(AIMessage(content=ai))
        history_langchain_format.append(HumanMessage(content=message))
        gpt_response = llm(history_langchain_format)
        return gpt_response.content</code></pre>
<p>랭체인과 그라디오를 활용한 챗봇 만들기
위에서 살펴본 랭체인과 그라디오를 합친 전체 코드다. additional_input에서 시스템 프롬프트를 입력받아 langchain에게 전달하는 기능을 추가했다.</p>
<h1 id="최종-랭체인과-그라디오를-활용한-챗봇-만들기">최종! 랭체인과 그라디오를 활용한 챗봇 만들기</h1>
<pre><code class="language-python">
from langchain.chat_models import ChatOpenAI
from langchain.schema import AIMessage, HumanMessage, SystemMessage
import gradio as gr
import os

os.envrion[&quot;OPENAI_API_KEY&quot;] = &quot;...&quot; 

llm = ChatOpenAI(temperature=1.0, model=&#39;gpt-3.5-turbo-0613&#39;)

def response(message, history, additional_input_info):
        history_langchain_format = []
        # additional_input_info로 받은 시스템 프롬프트를 랭체인에게 전달할 메시지에 포함시킨다.
        history_langchain_format.append(SystemMessage(content= additional_input_info))
        for human, ai in history:
                history_langchain_format.append(HumanMessage(content=human))
                history_langchain_format.append(AIMessage(content=ai))
        history_langchain_format.append(HumanMessage(content=message))
        gpt_response = llm(history_langchain_format)
        return gpt_response.content

gr.ChatInterface(
        fn=response,
        textbox=gr.Textbox(placeholder=&quot;말걸어주세요..&quot;, container=False, scale=7),
        # 채팅창의 크기를 조절한다.
        chatbot=gr.Chatbot(height=1000),
        title=&quot;어떤 챗봇을 원하심미까?&quot;,
        description=&quot;물어보면 답하는 챗봇임미다.&quot;,
        theme=&quot;soft&quot;,
        examples=[[&quot;안뇽&quot;], [&quot;요즘 덥다 ㅠㅠ&quot;], [&quot;점심메뉴 추천바람, 짜장 짬뽕 택 1&quot;]],
        retry_btn=&quot;다시보내기 ↩&quot;,
        undo_btn=&quot;이전챗 삭제 ❌&quot;,
        clear_btn=&quot;전챗 삭제 💫&quot;,
        additional_inputs=[
            gr.Textbox(&quot;&quot;, label=&quot;System Prompt를 입력해주세요&quot;, placeholder=&quot;I&#39;m lovely chatbot.&quot;)
        ]
).launch()</code></pre>
<p><img src="https://velog.velcdn.com/images/t_wave/post/a84e895b-7135-428f-b060-75ca1f47cdf0/image.png" alt="Fig 5. 최종 챗봇 UI, 채팅 결과"></p>
<p>꽤 괜찮다</p>
<h1 id="정리하며">정리하며</h1>
<p>챗봇 프로토타입 개발 과정을 살펴보았다. 이를 응용하여 다양한 챗봇을 만들 수 있을 것이다. 그라디오와 랭체인은 메인 인공지능 설계를 제외한 작업을 간단히 만들어준다. 그러면 인공지능 설계에 들일 시간과 노력을 확보할 수 있다. Fig5의 짜장면과 짬뽕 질문에 챗봇이 두툼한 카라멜이라는 엉뚱한 대답을 내놓은 것처럼, UI와 기능을 간결하게 만들어도 핵심은 인공지능의 성능이다. 프레임워크를 활용해 인공지능을 연구할 시간을 확보할 수 있다.</p>
<blockquote>
</blockquote>
<p>참고 문헌
LangChain. LangChain Docs. 2023.08.09 검색, <a href="https://python.langchain.com/docs">https://python.langchain.com/docs</a>
Gradio. Gradio Docs. 2023.08.09 검색, <a href="https://www.gradio.app/docs">https://www.gradio.app/docs</a></p>
<h5 id="끝">끝</h5>
]]></description>
        </item>
        <item>
            <title><![CDATA[도커에서 챗봇 호스팅하기]]></title>
            <link>https://velog.io/@t_wave/%EB%8F%84%EC%BB%A4%EC%97%90%EC%84%9C-%EC%B1%97%EB%B4%87-%ED%98%B8%EC%8A%A4%ED%8C%85%ED%95%98%EA%B8%B0</link>
            <guid>https://velog.io/@t_wave/%EB%8F%84%EC%BB%A4%EC%97%90%EC%84%9C-%EC%B1%97%EB%B4%87-%ED%98%B8%EC%8A%A4%ED%8C%85%ED%95%98%EA%B8%B0</guid>
            <pubDate>Fri, 04 Aug 2023 01:45:52 GMT</pubDate>
            <description><![CDATA[<p><img src="https://velog.velcdn.com/images/t_wave/post/c543efdb-aa4c-46c1-ab21-a539a78daf56/image.png" alt=""></p>
<h1 id="도커docker에서-웹서버-호스팅하기">도커(Docker)에서 웹서버 호스팅하기</h1>
<p>도커(Docker)는 컨테이너 단위로 애플리케이션을 실행해주는 플랫폼이다. 로컬 환경에서 개발한 애플리케이션을 다른 환경에서 실행하면 패키지의 버전이 충돌하여 오류가 뜨는 경우가 많다. 라이브러리가 업데이트되며 이전 버전에서 사용하던 함수가 사장되거나, 각기 다른 라이브러리가 함께 의존중인 라이브러리에게 요구하는 버전이 다를 수 있다. 이런 충돌을 방지하고자 도커는 컨테이너에 애플리케이션을 위한 최적 환경을 구성한다. 그리고 컨테이너의 환경 정보와 애플리케이션이 모두 저장된 이미지 파일을 주고받는다. 도커는 로컬에서 작업한 애플리케이션을 클라우드 서버로 옮겨 실행하거나, 수십 년 전 작성된 코드를 당시 환경의 컨테이너에서 쾌적하게 실행할 수 있도록 돕는다. 어떤 작업이든 초기 환경설정에 소요되는 시간을 크게 절약할 수 있다.</p>
<p>도커의 컨테이너는 호스트 컴퓨터(컨테이너를 실행시킨 컴퓨터) 안에 독자적인 환경을 구축하여 애플리케이션을 실행한다. 때문에 컨테이너에서 웹서버를 호스팅할 때 호스트에서 하는 것과 몇 가지 차이점이 있다. 이 글에서 챗봇을 호스팅해보고 과정에서 필요한 설정들을 알아볼 것이다. </p>
<h1 id="1-도커-파일-작성">1. 도커 파일 작성</h1>
<p><img src="https://velog.velcdn.com/images/t_wave/post/ae31ee47-9055-4618-a07c-dcf3f1245664/image.png" alt=""></p>
<pre><code class="language-python">FROM python:3.10.11-bullsey
# FROM은 도커의 베이스 이미지를 나타낸다. 
# 챗봇은 python(.py)으로 작성됐으므로 python:3.10.11-bullseye을 사용했다.

COPY docker_chatbot_package docker_chatbot_package
# COPY는 로컬파일에서 작업한 파일들을 이미지에 저장하겠다는 의미다.
# 첫 번째 ‘docker_chatbot_package’는 로컬에서 복사할 폴더를 의미하고 두 번째는 컨테이너 안에 저장할 위치다.

WORKDIR /docker_chatbot_package
# WORKDIR은 컨테이너 안에서 커맨드의 시작 위치를 바꾼다. 
# ‘docker_chatbot_package’ 폴더 안에서 시작하도록 설정했다.

RUN pip install -r /docker_chatbot_package/requirements.txt
# RUN 은 커멘드 라인을 실행할 수 있도록 해준다. 
# requirements.txt 안에 적힌 패키지를 모두 다운받도록 했다. requirements.txt 의 내용은 다음과 같다.

EXPOSE 7860
# EXPOSE는 웹서버를 호스팅할 포트를 명시한다. 
# 7860번 포트로 호스팅을 하였다. 여기서 명시하더라도 컨테이너를 실행할 때 –p 옵션으로 컨테이너와 호스트 컴퓨터의 포트를 연결해야한다. 밑에서 컨테이너를 실행할 때 추가로 설명하겠다.

CMD [&quot;python3&quot;, &quot;web_test_gradio_ui_memo.py&quot;]
# CMD는 컨테이너가 실행될 때 한 번만 실행할 명령어를 기술한다.
# ‘python3’로 ‘web_test_gradio_ui_memo.py’ 파일을 실행하도록 했다.
</code></pre>
<pre><code class="language-txt"># ‘requirements.txt ’의 내용이다.
# 다운로드 받을 패키지 목록이다.
gradio
tiktoken
openai
pandas
scipy</code></pre>
<h1 id="2-gradio를-활용한-챗봇-호스팅">2. Gradio를 활용한 챗봇 호스팅</h1>
<p><img src="https://velog.velcdn.com/images/t_wave/post/5ddbf175-7bff-421e-a4f9-916a00dcfe0d/image.png" alt=""></p>
<p>  이 글에서 챗봇의 구조를 자세히 다루지는 않는다. 하지만 호스팅 부분을 설명하기 위해 챗봇 제작에 사용한 Gradio를 간단히 소개하겠다. Gradio는 인공지능을 개발하고 테스트하기에 유용한 파이썬 프레임워크다. 파이썬으로 블록을 쌓듯이 UI를 구현하고, 호스팅할 수 있도록 해준다. 아래는 Fig 4.를 구현한 Gradio 코드의 일부다.</p>
<pre><code class="language-python">import gradio as gr
with gr.Blocks() as iface:
  … 화면 구현 …
iface.launch(share=True, server_name=&quot;0.0.0.0&quot;)
</code></pre>
<p>  iface.launch()의 매개변수가 호스팅과 관련있다. ‘Share=True’로 설정시 월드와이드웹(World Wide Web, WWW)에 Gradio가 호스팅해준다. ‘Server_name’ 옵션은 파일을 호스팅할 로컬 주소를 뜻한다. ‘127.0.0.1‘로 설정하면 로컬에서 호스팅이 된다. ’0.0.0.0‘으로 설정하면 모든 IP주소에서 접속이 가능하다. </p>
<h2 id="컨테이너라는-독자적-생태계">컨테이너라는 독자적 생태계</h2>
<p>  컨테이너는 고유의 환경 변수와 IP주소를 갖고 있다. 컨테이너 안의 IP주소는 도커가 부여한 가상의 값이기 때문에 호스트 컴퓨터의 IP주소 체계와 별개다. 컨테이너에서 ‘127.0.0.1’로 IP주소를 설정하면 컨테이너 자신을 의미한다. </p>
<pre><code class="language-bash">
&gt; docker run –p 127.0.0.1:8000:8000 [IMAGE_NAME]
# 이미지를 빌드할 때, –p 옵션으로 컨테이너의 8000번 포트와 호스트 컴퓨터의 8000번 포트를 연결한다.
# 호스트 컴퓨터에서만 서버에 접속할 수 있다.

&gt; docker run –p 8000:8000 [IMAGE_NAME]
# 마찬가지로 컨테이너와 호스트 컴퓨터의 8000번 포트를 연결한다.
# 외부 컴퓨터(IP가 127.0.0.1이 아닌 컴퓨터)도 포트를 통해 호스팅 서비스에 접근 가능하다.
</code></pre>
<p>  Gradio에서 연결 IP주소를 ‘0.0.0.0’으로 설정해 두면 어떤 IP주소를 가진 컴퓨터도 접근할 수 있다. 하지만 컨테이너에 접속하려면 호스트 컴퓨터를 통해야 하므로 결국 호스트 컴퓨터만 접근이 가능하다. ‘docker run –p’ 옵션에서 호스트의 IP주소를 127.0.0.1:8000으로 명시하면 호스트만 포트를 연결한다. 이를 생략하면 호스트 이외의 컴퓨터도 ‘외부 컴퓨터-호스트-컨테이너’ 순서로 컨테이너의 서비스에 접근이 가능하다.</p>
<h1 id="3-도커파일-빌드-및-챗봇-컨테이너-호스팅">3. 도커파일 빌드 및 챗봇 컨테이너 호스팅</h1>
<p>  앞의 도커 파일을 빌드 하여 챗봇 이미지를 만든다. 이미지를 RUN하여 컨테이너를 만들고 호스팅해보자. 아래는 이와 관련된 코드다.</p>
<pre><code class="language-bash">&gt; docker build -t dokdo_chatbot .  
# 현재 폴더(마지막 . 이 현재 폴더 의미)의 ‘dockerfile’을 찾아 이미지로 build한다. 
# 이미지의 이름은 ‘dokdo_chatbot’이다 (-t 옵션).

&gt; docker images
# 이미지 목록을 출력한다.
# 이미지 목록에 ‘dokdo_chatbot’이 있다면 이미지가 정상적으로 빌드된 것이다.

&gt; docker volume create chatbot_chatlog
# 볼륨을 생성한다. 이름은 ‘chatbot_chatlog’이다.

&gt; docker run -d –p 7860:7860 –v chatbot_chatlog:/dokdo_chatbot_package/data  --name my_chatbot dokdo_chatbot
# 챗봇 이미지를 실행한다. -p 옵션으로 7860포트를 연결한다.
# -d 옵션으로 컨테이너를 백그라운드에서 실행했다. 이 옵션을 없애면 컨테이너의 로그를 볼 수 있다.
# -v 옵션으로 컨테이너에 볼륨을 마운트했다. ‘chatbot_chatlog’ 볼륨을 컨테이너의 /dokdo_chatbot_package/data에 배치했다.
# --name 옵션으로 컨테이너의 이름을 ‘my_chatbot’으로 만들었다.
</code></pre>
<p>실행된 어플리케이션은 Fig 4.와 같다. ‘localhost:7860’에서 실행된 것을 확인할 수 있다.</p>
<h1 id="정리하며">정리하며</h1>
<p>  챗봇을 예제로 웹서버를 호스팅해보았다. 응용한다면 다양한 서비스를 도커에서 운영할 수 있을 것이다. </p>
<blockquote>
</blockquote>
<p>참고문헌
엘튼 스톤맨. 『도커 교과서』. 심효섭(역). 길벗, 2022.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[텐서플로우로 케라스 객체 따라 만들기]]></title>
            <link>https://velog.io/@t_wave/%ED%85%90%EC%84%9C%ED%94%8C%EB%A1%9C%EC%9A%B0%EB%A1%9C-%EC%BC%80%EB%9D%BC%EC%8A%A4-%EA%B0%9D%EC%B2%B4-%EB%94%B0%EB%9D%BC-%EB%A7%8C%EB%93%A4%EA%B8%B0</link>
            <guid>https://velog.io/@t_wave/%ED%85%90%EC%84%9C%ED%94%8C%EB%A1%9C%EC%9A%B0%EB%A1%9C-%EC%BC%80%EB%9D%BC%EC%8A%A4-%EA%B0%9D%EC%B2%B4-%EB%94%B0%EB%9D%BC-%EB%A7%8C%EB%93%A4%EA%B8%B0</guid>
            <pubDate>Wed, 02 Aug 2023 16:03:02 GMT</pubDate>
            <description><![CDATA[<style> .tomato{
    color: tomato
  }</style>




<blockquote>
<p>참고한 책: <a href="https://www.yes24.com/Product/Goods/112012471">케라스 창시자에게 배우는 딥러닝 2판</a></p>
</blockquote>
<p><strong>케라스(Keras)는 최고(Best)다.</strong></p>
<p><a href="https://velog.io/@t_wave/%EB%82%B4%EA%B0%80-%EB%B3%B4%EB%A0%A4%EA%B3%A0-%EC%A0%95%EB%A6%AC%ED%95%9C-%EC%BC%80%EB%9D%BC%EC%8A%A4-%EC%B0%BD%EC%8B%9C%EC%9E%90%EC%97%90%EA%B2%8C-%EB%B0%B0%EC%9A%B0%EB%8A%94-%EB%94%A5%EB%9F%AC%EB%8B%9D-2%ED%8C%90-12%EC%9E%A5">지난 글에서 정리한 1~2장</a>은 수학적 배경지식과 딥러닝에 대한 설명이었다면 3장에서는 텐서플로우와 케라스에 대한 이해도를 높일 수 있었다. 이 장을 읽으면서 단순히 케라스의 모델을 import해서 쓰는게 아니라 케라스가 어떻게 동작하는지, tensorflow로 단순한 케라스 객체를 따라 만들어보며 이해할 수 있었다.</p>
<p>케라스를 만든 이 책의 저자는 얼마나 이론을 깊게 이해하고 또 구현 능력을 갖춘 것일까? 나도 열심히 공부해서 케라스나 텐서플로우 같은 대형 프레임워크의 컨트리뷰터로 활동하고 싶다. 그 날을 위해 오늘도 열심히 공부하자!</p>
<h1 id="텐서플로우-tensorflow">텐서플로우 (TensorFlow)</h1>
<p><img src="https://velog.velcdn.com/images/t_wave/post/01eaceb7-d3e3-442d-9759-064d2ad99e71/image.png" alt=""></p>
<p>텐서플로우는 넘파이처럼 수치를 나타내기 위한 라이브러리다. 넘파이처럼 빠른 병렬 연산이 가능하다. 그럼 넘파이와 차이점은 무엇일까?</p>
<blockquote>
</blockquote>
<ul>
<li>미분 가능한 식을 넣으면 자동으로 Gradient를 계산해주는 <strong>&quot;GradientTape&quot;</strong> 기능이 있다.</li>
<li>GPU와 TPU에서 실행할 수 있다.</li>
<li>연산을 분산하기 쉽다. (tf로 정의한 식을 분산 연산한다.)</li>
<li>배포가 쉽다.</li>
</ul>
<hr>

<h2 id="텐서플로우-연산">텐서플로우 연산</h2>
<p>텐서플로우에서 사용하는 기본 연산을 알아보자.
기본적인 연산 예제와 주석을 달아두었다. 간편하게 복습하기 좋다.</p>
<pre><code class="language-python">import tensorflow as tf</code></pre>
<pre><code class="language-python">a = tf.zeros(shape=(2, 1))     # (2, 1) 행렬을 만들고 0으로 채움
b = tf.ones(shape=(2, 1))     # (2, 1) 행렬을 만들고 1로 채움

c = tf.Variable(3.) # 변수
d = tf.constant(3.) # 상수

e = tf.random.normal(shape=(3,1), mean=0, stddev=1)  # 평균 0, 표준편차 1 정규분포에서 (3, 1) 행렬 뽑음
f = tf.random.uniform(shape=(3,1), minval=0, maxval=1) # 0~1 균등분포에서 (3, 1) 행렬 뽑음</code></pre>
<p>tf.~ 식으로 쉽게 행렬을 만들 수 있다. 0으로 채우기, 1로 채우기, 정규분포로 채우기, 균등분포로 채우기 등 다양한 데이터를 만들 수 있다. 변수는 tf.Variable()로 첫번 째 문자가 대문자이다!</p>
<pre><code class="language-python">a = tf.Variable(a)
a.assign(tf.ones(((2,1))))     # numpy와 다르게 tf는 object형태라 assign 메소드로 따로 할당
a[0,0].assign(3.)             # 개별 할당 가능
a.assign_add(a)     # +=
a.assign_sub(a)     # -=
a = tf.square(a)      # 제곱
a = tf.sqrt(a)        # 제곱근
b = tf.transpose(a)    # 전치
e = tf.matmul(a, b) # 점곱
e *= e                # 원소별 곱셈</code></pre>
<p>tf는 변수와 상수 모두 tf 객체로 만들기 때문에 assign 메소드로 값을 할당해야 한다. 인덱싱 기능을 제공해서 행렬 원소만 할당할 수 있다. </p>
<pre><code class="language-python"># GradientTape는 미분 가능한 식을 넣으면 자동미분해준다.
# 아래는 위치, 속도, 가속도 예제이다.

time = tf.Variable(0.)
with tf.GradientTape() as outer_tape:
  with tf.GradientTape() as inner_tape:
    position = 4.9 * time ** 2
  speed = inner_tape.gradient(position, time)
acceleration = outer_tape.gradient(speed, time)</code></pre>
<p>위는 GradienTape를 활용한 속도, 가속도 구하기 예제이다. with ~ tape: 안에 수식을 넣고 밖에서 tape의 gradient(미분값)을 구할 수 있다. </p>
<p><span style="color:tomato">수식은 미분 가능해야 한다</span>. 수식의 tf 변수 객체만 미분 값을 제공하므로 timed을 tf.constant()로 지정하면 미분이 되질 않는다.</p>
<p>케라스 대신 텐서플로우로 딥러닝 모델을 구현해보면 깊이 공부한 건 줄 알았는데, 텐서플로우 안에 들어있는 수식 함수들과 C언어 연산이라는 심해를 보았다. <span style="color:tomato">GradientTape 안에 들어있는 함수를 만들 수 있는 사람은 얼마나 될까?</span> 경쟁력은 이런 기초에서 나오는 것이다! 정말이지 공부할 건 넘쳐나는 것 같다. 우선 텐서플로우와 수학적 원리를 조금씩 이해해보자! 라는 마음으로 책을 읽었다.</p>
<h2 id="텐서플로우로-선형분류기-구현하기">텐서플로우로 선형분류기 구현하기</h2>
<p>그럼 이제 본격적으로 텐서플로우를 활용해 선형분류기를 만들자!
선형분류기는 케라스의 기본적인 객체 중 하나이다. 일반적인 레이어 하나가 선형분류기라고 보면 된다. 물론 층을 쌓을 수록 활성화함수를 거쳐 비선형이 된다. 여기서는 하나의 레이어가 어떻게 구성되어있는지 텐서플로우로 구현하며 이해하겠다.</p>
<pre><code class="language-python">import numpy as np

num_samples_per_class = 1000
negative_samples = np.random.multivariate_normal(
    mean=[0, 3],                # 평균
    cov=[[1, 0.5], [0.5, 1]],    # 공분산
    size=num_samples_per_class    # 데이터 개수
)
positive_samples = np.random.multivariate_normal(
    mean=[3, 0],
    cov=[[1, 0.5], [0.5, 1]],
    size=num_samples_per_class
)</code></pre>
<p>우선 임의의 데이터를 만든다. 두 개의 집단을 만들고 집단 당 데이터는 1000개씩 만든다. 각 집단은 두 개의 특성을 갖고있다 (x축 y축으로 통칭). negative_samples는 정규분포에서 뽑으며 평균, 공분산, 자료의 개수를 지정해주었다. positive_samples도 마찬가지다. </p>
<pre><code class="language-python">inputs = np.vstack((negative_samples, positive_samples)).astype(np.float32)
targets = np.vstack((np.zeros((num_samples_per_class, 1), dtype=&quot;float32&quot;),
                   np.ones((num_samples_per_class, 1), dtype=&quot;float32&quot;)))</code></pre>
<p>inputs에 두 자료를 뭉쳐놓고, targets에서 0과 1로 라벨링을 해준다.</p>
<pre><code class="language-python">import matplotlib.pyplot as plt

plt.scatter(inputs[:, 0], inputs[:, 1], c=targets[:, 0])
plt.show()</code></pre>
<p><img src="https://velog.velcdn.com/images/t_wave/post/d50cb6f4-a08c-40dc-a505-f22f12569670/image.png" alt=""></p>
<p>plt.scatter( )에서 c 옵션을 target[ : , 0 ], 즉 라벨 번호로 지정해준다. c 옵션의 활용법을 배웠다.</p>
<pre><code class="language-python">input_dim = 2    # 특성 2개
output_dim = 1    # 결과는 Class 분류

W = tf.Variable(initial_value=tf.random.uniform(shape=(input_dim, output_dim)))
b = tf.Variable(initial_value=tf.zeros(shape=(output_dim,)))</code></pre>
<p>W는 initial_value의 shape가 (2, 1)이다. x축과 y축 각각의 특성에 대한 W1, W2가 필요하기 때문이다. 반면 b는 (1, )로 하나만 붙는다.</p>
<pre><code class="language-python"># 선형 분류기
def model(inputs):
  return tf.matmul(inputs, W) + b</code></pre>
<p>정방향 패스의 연산을 반환한다. inputs의 shape은 (1000, 2) W는 (2, 1)이므로 점곱을 하면 (1000, 1)이 된다. 이후 b를 더해준다.</p>
<pre><code class="language-python"># 손실함수
def square_loss(targets, predictions):
  per_sample_losses = tf.square(targets - predictions)
  return tf.reduce_mean(per_sample_losses)</code></pre>
<p>손실함수는 target과 prediction의 오차를 계산해 제곱한다. 여기서 target은 0 또는 1 뿐이다. model은 앞서 만든 선형 분류기이므로 랜덤이다. tf.reduce_mean( ) 함수가 모든 loss의 평균을 Scala값으로 만들어준다.</p>
<pre><code class="language-python">learning_rate = 0.1

def training_step(inputs, targets):
  with tf.GradientTape() as tape:
    predictions = model(inputs)
    loss = square_loss(targets, predictions)
  grad_loss_wrt_W, grad_loss_wrt_b = tape.gradient(loss, [W, b])
  W.assign_sub(grad_loss_wrt_W * learning_rate)
  b.assign_sub(grad_loss_wrt_b * learning_rate)
  return loss</code></pre>
<p>GradientTape( )를 이용해서 loss에 대한 W와 b의 편미분값을 계산한다. loss를 최대한 줄이는게 목적이다. </p>
<pre><code class="language-python">for step in range(40):
  loss = training_step(inputs, targets)
  print(f&quot;{step} 번째 스텝의 손실: {loss:.4f}&quot;)</code></pre>
<pre><code>0 번째 스텝의 손실: 2.1274
1 번째 스텝의 손실: 0.2540
2 번째 스텝의 손실: 0.1287
3 번째 스텝의 손실: 0.1079 
...
37 번째 스텝의 손실: 0.0276
38 번째 스텝의 손실: 0.0273
39 번째 스텝의 손실: 0.0270</code></pre><p>40번 학습시킨다. 여기서는 게산의 편의를 위해 전체 데이터를 계속 학습했지만, 데이터가 커지면 미니배치 경사하강법으로 학습을 시키는게 일반적이다.</p>
<pre><code class="language-python">predictions = model(inputs)
plt.scatter(inputs[:, 0], inputs[:, 1], c=predictions[:, 0] &gt; 0.5)
plt.show()</code></pre>
<p><img src="https://velog.velcdn.com/images/t_wave/post/f95e02bf-f070-4d76-a864-89ea90c9a5e8/image.png" alt=""></p>
<p>잘 분류해낸 모습이다. 위의 원래 데이터에서 특이값(보라색이지만 노란 집단에 가까움)은 잡아내지 못했다. 선형분류기의 한계이다. 딥러닝은 직선이 아니라 고차원의 기준을 만들어내므로 특이값에 대한 분류가 더 용이할 것이다. 물론 여기서는 데이터가 너무 깔끔해서 그럴 필요는 없어보인다.</p>
<pre><code class="language-python">x = np.linspace(-1, 4, 100)
y = - W[0] / W[1] * x + (0.5 - b) / W[1]    # x * W[0] + y * W[1] + b = 0.5 식 이항
plt.plot(x, y, &quot;-r&quot;)
plt.scatter(inputs[:, 0], inputs[:, 1], c=predictions[:, 0] &gt; 0.5)
plt.show()</code></pre>
<p><img src="https://velog.velcdn.com/images/t_wave/post/c3a849ad-b001-4096-85e2-a456ac651ad3/image.png" alt=""></p>
<pre><code class="language-python">x * W[0] + y * W[1] + b = 0.5    #prediction의 예측 값. 0.5 미만이면 class 0, 초과면 class 1 이다.</code></pre>
<p>선형분류기의 기준선을 그려보았다. x축 [-1, 4] 범위에 점을 100개 찍어서 선을 만든다. 이에 대응하는 y축 값들을 계산하여 plot한다. 이 기준선이 선형분류기가 데이터의 클래스를 판별하는 기준이다. </p>
<h2 id="선형분류기-객체로-만들기">선형분류기 객체로 만들기</h2>
<pre><code class="language-python">class SimpleDense(keras.layers.Layer):    # keras.layers.Layer 상속
  def __init__(self, units, activation=None):
    super().__init__()
    self.units = units                    # 출력 차원의 크기
    self.activation = activation        # 활성화함수

  def build(self, input_shape):
    input_dim = input_shape[-1]            
    self.W = self.add_weight(shape=(input_dim, self.units), initializer=&quot;random_normal&quot;)
    self.b = self.add_weight(shape=(self.units, ), initializer=&quot;zeros&quot;)

  def call(self, inputs):
    y = tf.matmul(inputs, self.W) + self.b
    if self.activation is not None:
      y = self.activation(y)
    return y</code></pre>
<pre><code class="language-python"># keras.layers.Layer class의 __call__ 메소드
def __call__(self, inputs):
    if not self.built:
        self.build(inputs.shape)
        self.built = True
    return self.call(inputs)
</code></pre>
<p>앞서 살펴본 선형 분류기를 객체로 만든다면 이런 모습일 것이다.
만약 keras.layers.Layer class를 상속하지 않고 <strong><strong>call</strong></strong>() 함수에서 직접 build했다면 input_dim을 파라미터로 받아야 한다. 수동적으로 파라미터를 받지 않고 동적으로 input_dim을 추론하기 위해 call과 build 메소드를 따로 만든 것이다.</p>
<h1 id="케라스-keras">케라스 (Keras)</h1>
<p>그럼 케라스는 무엇인가?</p>
<blockquote>
</blockquote>
<ul>
<li>TensorFlow 기반 딥러닝 프레임워크</li>
<li>텐서플로우에서 한단계 더 나아간 사람 친화적 프레임워크!</li>
</ul>
<p>텐서플로우로 선형분류기를 구현해보았다. 그 다음에 케라스를 이용한다면? 훨씬 편하다!
케라스를 사용해서 앞서 만든 선형분류기를 구현해보자.</p>
<h2 id="케라스로-선형분류기-구현하기">케라스로 선형분류기 구현하기</h2>
<pre><code class="language-python">from tensorflow.keras import layers
layer = layers.Dense(32, activation=&quot;relu&quot;)</code></pre>
<p>케라스는 레이어가 이미 구현되어 객체로 포장되어 있다. layers.Dense가 앞서 만든 SimpleDense와 유사한 구조를 갖고 있다. 물론 더 다양한 기능들이 포함되어 있다. <strong><em>오픈소스 코드에 들어가서 코드를 뜯어보는 것도 재밌는 작업일 것 같다.</em></strong></p>
<pre><code class="language-python">from tensorflow.keras import models
from tensorflow.keras import layers

model = models.Sequential([
    layers.Dense(32, activation=&quot;relu&quot;),
    layers.Dense(32)
])</code></pre>
<p>Sequential을 통해 여러 레이어를 블록 쌓듯이 연결한다.</p>
<h2 id="신경망-설계에-필요한-것들">신경망 설계에 필요한 것들</h2>
<h3 id="가설공간-정의하기">가설공간 정의하기</h3>
<p><span style="color:tomato">신경망을 설계할 때 가설공간을 정의하는 직관은 매우 중요하다.</span> 이 직관은 튼튼한 기초 지식과 경험에서 나온다. 엔지니어의 진짜 실력 중 하나라고 말할 수 있다. 예를 들면 위에서 두 개의 집단이 선형으로 충분히 분류가 가능하다고 판단하면 단순히 선형분류기(아핀 변환)으로 해결이 가능하다. 하지만 데이터의 분포가 복잡해질 수록 적합한 신경망 모델의 설계가 중요해진다.</p>
<h3 id="손실함수-목적함수">손실함수 (=목적함수)</h3>
<p><img src="https://velog.velcdn.com/images/t_wave/post/0ff8e50f-e66b-4794-b658-c22a5af1f3f8/image.png" alt=""></p>
<p>손실함수는 최소화할 값을 의미한다. 책에 재밌는 예가 나온다. 만약 인공지능에게 &quot;인류의 평균 행복지수 높이기&quot;라는 목적을 갖고 손실함수로 인류의 평균 행복지수를 설정하면 어떻게 될까? 행복한 인간을 제외한 모든 인간을 사살한다는 답을 도출할 수도 있다. 손실함수를 어떻게 섬세하게 설정하느냐에 따라 인공지능의 결과가 만족스러울 수도 있고, 불만족스러울 수도 있다. 아래는 대표적인 손실함수 모델 리스트다.</p>
<pre><code>CategoricalCrossentropy,
SparseCategoricalCrossentropy,
BinaryCrossentopy,
MeanSquareError,
KLDivergence,
CosineSimilarity,
...</code></pre><h3 id="옵티마이저">옵티마이저</h3>
<p>옵티마이저는 손실함수를 업데이트하는 방법이다. 기본적으로 신경망은 <a href="https://ko.wikipedia.org/wiki/%EA%B2%BD%EC%82%AC_%ED%95%98%EA%B0%95%EB%B2%95">확률적 경사하강법(Stochastic Gradient descent)</a>의 발전된 모델들을 많이 사용한다. 이전 포스트에서 정리한 <a href="https://paperswithcode.com/method/sgd-with-momentum">모멘텀(Momentum)</a> 개념 등 다양한 개선 모델이 있다. 아래는 대표적인 옵티마이저 리스트다.</p>
<pre><code>SGD,
RMSprop,
Adam,
Adagrad,
...</code></pre><h3 id="측정-지표">측정 지표</h3>
<p>측정 지표는 훈련 과정에서 모니터링할 성공의 척도다. 손실함수는 항상 0에 수렴하도록 작동한다. 측정 지표는 분류가 얼마나 정확히 되었는지 퍼센트로 표시하는 등의 역할을 한다. 측정 지표는 모델 최적화에 직접 관여하는게 아니라 모니터링 용도이므로 미분 가능하지 않아도 된다. </p>
<pre><code>CategoricalAccuracy,
SparseCategoricalAccuracy,
BinaryAccuraccy,
AUC,
Precision,
Recall</code></pre><h2 id="케라스-메서드">케라스 메서드</h2>
<p>케라스 모델에는 여러 메서드가 있다. 알아보자.</p>
<h3 id="modelcompile-">model.compile( )</h3>
<p>메서드 훈련 과정을 설명하는 메서드다. </p>
<pre><code class="language-python">model.compile(
    optimizer=&quot;rmsprop&quot;,
    loss=&quot;meann_squared_error&quot;,
    metrics=[&quot;accuracy&quot;]
)</code></pre>
<h3 id="modelfit-">model.fit( )</h3>
<p>메서드 훈련 루프를 설명하는 메서드다.</p>
<pre><code class="language-python">history = model.fit(
    inputs,
    targets,
    epochs=5,
    batch_size=128,
    validation_data=(val_inputs, val_targets)
)</code></pre>
<pre><code class="language-python">validation = model.evaluate()</code></pre>
<h3 id="modelpredict-">model.predict( )</h3>
<p>예측 값을 반환하는 메서드다. 그냥 model()로 전체 예측 값을 출력해도 되지만 한번에 병렬로 출력하려면 GPU에 부담이 갈 수 있다. predict 메서드는 batch_size로 계산하기 때문에 부담이 덜하다.</p>
<h1 id="정리하며">정리하며</h1>
<p>그냥 모델 임포트해서 쓰는 것보다 이렇게 하나씩 텐서플로우로 만들고 이해하려 하니까 더 공부한 느낌이 든다. 나중에 나도 저런 모델을 직접 연구할 날이 올 것이다. 그 때를 위해 꾸준히 공부하자!</p>
<h5 id="끝">끝</h5>
]]></description>
        </item>
        <item>
            <title><![CDATA[내가 보려고 정리한 [케라스 창시자에게 배우는 딥러닝 2판] 1~2장]]></title>
            <link>https://velog.io/@t_wave/%EB%82%B4%EA%B0%80-%EB%B3%B4%EB%A0%A4%EA%B3%A0-%EC%A0%95%EB%A6%AC%ED%95%9C-%EC%BC%80%EB%9D%BC%EC%8A%A4-%EC%B0%BD%EC%8B%9C%EC%9E%90%EC%97%90%EA%B2%8C-%EB%B0%B0%EC%9A%B0%EB%8A%94-%EB%94%A5%EB%9F%AC%EB%8B%9D-2%ED%8C%90-12%EC%9E%A5</link>
            <guid>https://velog.io/@t_wave/%EB%82%B4%EA%B0%80-%EB%B3%B4%EB%A0%A4%EA%B3%A0-%EC%A0%95%EB%A6%AC%ED%95%9C-%EC%BC%80%EB%9D%BC%EC%8A%A4-%EC%B0%BD%EC%8B%9C%EC%9E%90%EC%97%90%EA%B2%8C-%EB%B0%B0%EC%9A%B0%EB%8A%94-%EB%94%A5%EB%9F%AC%EB%8B%9D-2%ED%8C%90-12%EC%9E%A5</guid>
            <pubDate>Mon, 24 Jul 2023 09:51:04 GMT</pubDate>
            <description><![CDATA[<p>드디어 딥러닝을 배워보자!
저자는 머신러닝을 먼저 공부하고 딥러닝을 배우는게 넓은 시야를 갖는 길이라고 말했지만, 나는 지금 딥러닝이 더 배우고 싶다. 하고 싶은 거 먼저 공부해야 더 빠르게 공부할 수 있겠지 ㅎㅎ 라는 마음으로 <a href="https://www.yes24.com/Product/Goods/112012471">[케라스 창시자에게 배우는 딥러닝 2판]</a>을 읽어보자 😁😁</p>
<h1 id="딥러닝은-머신러닝의-하위-개념이다">딥러닝은 머신러닝의 하위 개념이다</h1>
<p>질리도록 들었지만 딥러닝은 머신러닝의 하위 개념이다.
머신러닝은 3가지로 이루어진다.</p>
<blockquote>
<ul>
<li><strong>입력 데이터 포인트</strong>: 데이터 전처리 및 가공 = 어떤 특성이 중요한가?</li>
</ul>
</blockquote>
<ul>
<li><strong>기대 출력</strong>: 라벨링 = 어떤 답을 원하는가?</li>
<li><strong>알고리즘의 성능 측정</strong>: 학습(손실함수 값 0으로 만들기) = 얼마나 정확한가?</li>
</ul>
<p>어떤 데이터를 넣으면 어떤 답이 나와야 하는지 알려주고, 머신러닝(ML) 모델이 가져온 해결안에 대해 평가를 하는 방식이다.</p>
<p>기존에 데이터와 해결안을 미리 프로그래머가 정의해두고, 새로운 데이터가 들어오면 코드를 돌리기만 하던 방식과는 다르다. 해답이 미리 주어져야 한다.</p>
<p>머신러닝의 역사를 뼈대만 살펴보면 아래와 같다.</p>
<blockquote>
</blockquote>
<ul>
<li>확률적 모델링 시작: <strong>나이브베이즈</strong>(베이즈 정리 활용), <strong>로지스틱 회귀</strong>(분류 모델)</li>
<li>초창기 신경망 제안: <strong>LeNet</strong></li>
<li>커널 방법: <strong>서포트 벡터 머신</strong>(SVM) -&gt; <strong>특성 공학</strong>(유용한 특성만 추출)</li>
<li><strong>결정트리, 랜덤포레스트, 그레디언트 부스팅 머신</strong></li>
<li>발전한 신경망(<strong>딥러닝</strong>)</li>
</ul>
<p>정말 키워드만 나열해봤다. 책에서는 더 자세히 나와있지만 나의 관심사는 딥러닝이니 저 부분은 나중에 공부하기로 마음먹고 넘어갔다.</p>
<h3 id="커널-방법">커널 방법</h3>
<p>커널 방법만 간단하게 소개하고 넘어가겠다. 분류 문제를 간단하게 만들기 위해서는 데이터를 고차원 표현으로 매핑하는 기법이 이론상으로는 좋아 보이지만 실제로는 컴퓨터로 구현하기 어려운 경우가 많다. 그래서 커널 기법이 등장했다. 새로운 공간에서의 두 데이터 포인트 사이의 거리를 계산만 할 수 있다면, 굳이 새로운 공간의 좌표 자체를 구할 필요가 없다. 커널 함수는 두 포인트 사이의 거리를 효율적으로 계산하는 함수다. (나중에 공부 ^^)</p>
<p><img src="https://velog.velcdn.com/images/t_wave/post/bc743a4a-60f8-48ea-9f41-09318bc1b4bc/image.png" alt=""></p>
<p>딥러닝은 신경망 레이어를 깊게(Deep) 연결한 머신러닝 모델이다. 위 그림에서 보면 딥러닝에서는 특징 추출 부분이 분류와 합쳐졌다. 서포트 벡터 머신은 좋은 성능을 가졌지만 올바른 특성을 일일이 떠먹여줘야하는 수고가 있었다. 예를 들면 손글씨 숫자를 분류할 때 원시 픽셀 값을 사용할 수는 없다. 픽셀 히스토그램처럼 문제를 쉽게 만드는 유용한 표현을 수동으로 먼저 찾아야한다. 이런 작업을 특성 공학이라고 한다. 딥러닝은 이런 특성을 알아서 찾아낸다. 책에서 마음에 들었던 비유가 있다. </p>
<p><img src="https://velog.velcdn.com/images/t_wave/post/e356e29d-ed4b-452b-8aa6-ef34d61c96c8/image.png" alt=""></p>
<p>빨간색 종이와 파란색 종이 두개를 마구 구겨서 공으로 만들어냈다고 해보자. 이 공을 다시 빨간색과 파란색 종이로 다시 분리하려면, 조금씩 종이를 살살 펼칠 것이다. 아무리 꼬여있는 데이터라 하더라도 거기서 최대한 올바른 정보를 추출해낸다. 물론 그럼에도 아무런 의미가 없다면 Garbage-In-Grabage-Out일 것이다.</p>
<p>이런 딥러닝이 발전할 수 있는 이유라면 3가지다.</p>
<blockquote>
</blockquote>
<ul>
<li>하드웨어의 발전</li>
<li>데이터의 축적</li>
<li>알고리즘: 병렬 연산, 활성화 함수, 가중치 초기화, 옵티마이저 등</li>
</ul>
<h1 id="신경망의-수학적-이해">신경망의 수학적 이해</h1>
<p>물론 여기서 수식을 엄청나게 이해하지는 못했다. 간단하게 훑어본 기분으로 넘어가보자.
우선 케라스로 구현한 간단한 신경망 코드를 살펴보자.</p>
<pre><code class="language-python">from tensorflow import keras
from tensorflow.keras import layers
from tensorflow.keras.datasets import mnist # 기본적인 손글씨 판별 데이터셋 MNIST import

model = keras.Sequential([
    layers.Dense(512, activation=&quot;relu&quot;) # relu로 비선형 함수 만들기, 512차원 layer 하나
    layers.Dense(10, activation=&quot;softmax&quot;) # softmax로 10개의 class에 각각 확률 값 계산하기 -&gt; 0~9까지 숫자 예측
])

model.compile(
    optimizer=&quot;rmsprop&quot;, # 최적화함수 (역전파 기법)
    loss = &quot;sparse_categorical_crossentropy&quot;, # 손실함수
    metrics = [&quot;accuracy&quot;] # 정확도 지표만 고려해서 훈련과 테스트 과정 모니터링
)

train_images = train_images.reshape((60000, 28 * 28))
train_images = train_images.astype(&quot;float32&quot;) / 255
test_images = test_images.reshape((60000, 28 * 28))
test_images = test_images.astype(&quot;float32&quot;) / 255

model.fit(train_images, train_labels, epochs=5, batch_size=128)
</code></pre>
<p>일단 완성체를 한번 지르고 하나씩 코드를 뜯어보자.</p>
<h2 id="텐서-연산">텐서 연산</h2>
<pre><code class="language-python">keras.layers.Dense(512, activation=&quot;relu&quot;)</code></pre>
<pre><code class="language-python">for output in range(512):
    output = relu(dot(W, input) + b)</code></pre>
<p>위 두 코드는 같은 결과를 갖는다.
딥러닝에서 위와 같은 layer를 겹겹이 쌓아 수천만 그 이상의 output 계산이 생긴다. 이를 선형으로 하나씩 계산하면 시간이 많이 걸린다. 병렬 계산을 위해 Numpy가 필요하다. Numpy는 BLAS(Basic Linear Algebra Subprogram)을 매우 효율적이고 저수준의 텐서 조작으로 구현해놓았다. 포트란이나 C언어로 구현되어있다. 케라스는 넘파이 기반으로 병렬 연산을 지원한다.</p>
<h3 id="브로드-캐스팅">브로드 캐스팅</h3>
<p>큰 텐서와 작은 텐서를 연산할 때 작은 텐서의 차원을 큰 텐서에 맞춰줘야 한다. 이 작업을 브로드 캐스팅(broadcasting)이라고 부른다. </p>
<pre><code class="language-bash">X -&gt; (32, 10) 행렬
y -&gt; (10, ) 벡터</code></pre>
<p>위 두 텐서를 연산하려면 두 가지 과정을 거친다.</p>
<ol>
<li>큰 텐서의 ndim에 맞도록 작은 텐서에 브로딩캐스팅 축이 추가된다.</li>
<li>작은 텐서가 새 축을 따라서 큰 텐서의 크기에 맞도록 반복된다.</li>
</ol>
<p>코드로 보면 다음과 같다.</p>
<pre><code class="language-python">y = np,expand_dims(y, axis=0) # y에 브로드캐스팅 축 추가 -&gt; y의 크기가 (1, 10)이 됨
Y = np.concatenate([y] * 32, axis=0) # 축 0을 따라 y를 32번 반복하여 크기가 (32,10)인 Y를 얻음</code></pre>
<p>행렬로 이해하면 행마다 똑같은 열을 32번 복사한 행렬을 만든 것이다. 정말 계산의 편의를 위한 작업이다.</p>
<h3 id="점곱">점곱</h3>
<p>텐서 곱셈(tensor product) 또는 점곱(dot product)라고 불리는 연산은 가장 널리 사용되고 유용한 텐서 연산이다.</p>
<pre><code class="language-python">output = relu(dot(W, input) + b)</code></pre>
<p>여기서 dot이 점곱이다. 이름이 점곱인 이름은 수학 기호가 점이다.</p>
<pre><code>z = x ⋅ y</code></pre><p>점곱은 어떻게 하는가?
이 책은 참 특이한게 설명이 수식보다는 코드다. 이 글에서도 코드로 간다.</p>
<pre><code class="language-python">for i in range(x.shape[0]):
    z += x[i] * y[i]</code></pre>
<p>for 문으로 이해하기 좋다. 4차원 텐서는 4중 for문이고 5차원 텐서는 5중 for문일 것이다. 
x 와 y 두 행렬이 있을 때 이렇게 계산한다. 그림으로 보자.</p>
<p><img src="https://velog.velcdn.com/images/t_wave/post/3f3fb909-9c18-42ae-ac0f-3e6210b79f7a/image.png" alt=""></p>
<p>수식은 이렇다. </p>
<p><img src="https://velog.velcdn.com/images/t_wave/post/64f5d54f-a3f5-43b2-9498-7029951b1b78/image.png" alt=""></p>
<p>텐서의 형태로 보면 아래와 같다. 마지막 차원과 첫번째 차원이 맞물린다.</p>
<pre><code class="language-bash">(a, b, c, d) ⋅ (d, e) = (a, b, c, e)</code></pre>
<h3 id="텐서-크기-변환">텐서 크기 변환</h3>
<pre><code class="language-python">x = np.array([
    [0., 1.],
    [2., 3.],
    [4., 5.]
])
x.shape # (3, 2)
x.reshape((2, 3))

&gt;&gt;&gt; #reshape 결과
x = 
[[0., 1., 2.],
[3., 4., 5.]]
</code></pre>
<p>텐서의 크기를 변환한다는 것은 특정 크기에 맞게 열과 행을 재배열한다는 뜻이다.</p>
<pre><code class="language-python">x = np.array([
    [0., 1.],
    [2., 3.],
    [4., 5.]
])
x = np.transpose(x)

&gt;&gt;&gt; #transpose == 전치 결과
x = 
[[[0., 2., 4.],
[1., 3., 5.]]</code></pre>
<p>전치는 행과 열을 바꾸는 연산이다. 그냥 reshape하는 것과 모양이 조금 다르다.</p>
<h3 id="텐서-연산의-기하학적-해석">텐서 연산의 기하학적 해석</h3>
<p>이동, 회전, 크기변형, 선형변환, 아핀변환, relu 활성화 함수를 사용하는 Dense층 등의 내용이 들어있다. </p>
<p><img src="https://velog.velcdn.com/images/t_wave/post/9dd00144-7c2c-4930-bb0d-7276a6f00b42/image.jpg" alt=""></p>
<p>.. 수식 쓰기가 너무 귀찮았다.</p>
<p>딥러닝의 기하학적 해석은 여기서도 종이 구겼다 펼치는 예시가 적용된다. </p>
<h2 id="경사하강법">경사하강법</h2>
<p>신경망의 모든 함수 (변환)은 미분가능한 수식이다.
텐서 함수의 도함수 == 그레디언트(gradient)</p>
<pre><code class="language-python">y_pred = dot(W, x) # W = 가중치, x = 입력 데이터
loss_value = loss(y_pred, y_true) # y_pred는 예측 값, y_true는 라벨</code></pre>
<p>위 수식에서 x, y_true는 상수다. 그러므로 다음 수식은 </p>
<pre><code>loss_value = f(w)</code></pre><p>라고 할 수 있다. 여기서는 도함수를</p>
<pre><code>grad(loss_value, W)</code></pre><p>로 표현한다.</p>
<h3 id="연쇄-법칙-chain-rule">연쇄 법칙 (chain rule)</h3>
<p><img src="https://velog.velcdn.com/images/t_wave/post/7d6a128a-af2b-4cb4-9eee-a10b7a9d79c9/image.png" alt=""></p>
<p>자세한 설명은 생략한다..ㅎ</p>
<h3 id="계산-그래프를-활용한-자동-미분">계산 그래프를 활용한 자동 미분</h3>
<p><img src="https://velog.velcdn.com/images/t_wave/post/0bbce3fd-ae2c-4ec3-9f14-ffca2c0b5b08/image.png" alt=""></p>
<p>계산 그래프로 표현하고 이를 분산(병렬)처리할 수 있다는 개념을 알아갔다.<br>계산 과정을 도표로 작성 및 동일 계산을 &quot;분산&quot;해서 병렬 처리하는 코드? tensorflow에 탑재되어있다. GradientTape라고 부른다. 나중에 공부해볼 수도 있겠다.</p>
<p>계산 그래프를 활용하니 순방향, 역전파 설명이 더 잘 되었다. 나중에 설명할 일 생기면 써먹자.</p>
<h6 id="끝">끝</h6>
]]></description>
        </item>
        <item>
            <title><![CDATA[도커파일로 이미지 빌드 및 공유하기]]></title>
            <link>https://velog.io/@t_wave/DockerHub</link>
            <guid>https://velog.io/@t_wave/DockerHub</guid>
            <pubDate>Wed, 19 Jul 2023 08:31:48 GMT</pubDate>
            <description><![CDATA[<p>도커의 기본 개념과 이미지를 다운받아 실행하는 방법은 “<a href="https://velog.io/@t_wave/BasicDockerPractice">도커의 기초지식과 실습</a>” 글에서 설명하였다.</p>
<p>도커는 이미지와 컨테이너 형태로 작동하기 때문에 환경에 구애받지 않고 쉽게 공유가 가능하다. Docker File을 통하여 현재 작업물을 이미지로 빌드하고, 도커 허브에 업로드하여 사람들과 공유할 수 있다. 그 과정을 이 글에서 실습한다.</p>
<h1 id="1-도커-파일을-작성한다">1. 도커 파일을 작성한다.</h1>
<p>도커 파일은 <span style="color:orange"><strong>이미지를 빌드하기 위한 명세서</strong></span>이다. 이미지의 베이스가 되는 이미지 레이어(Python버전 등), 환경 변수, 필요한 파일들을 어떻게 조합하여 새로운 이미지를 만들어낼지 작성하는 레시피다. 일반 메모장이나 선호하는 텍스트편집기에서 내용을 작성하고 파일 이름을 ‘dockerfile’로 만든다. 아래는 책 “도커 교과서”에 나오는 도커 파일 예제이다.</p>
<pre><code class="language-shell">FROM diamol/node

ENV TARGET=“blog.sixeyed.com”
ENV METHOD=“HEAD”
ENV INTERVAL=“3000”

WORKDIR /web-ping
COPY app.js .

CMD [“node”, “/web-ping/app.js”]</code></pre>
<h3 id="자주쓰는-도커-명령어">자주쓰는 도커 명령어</h3>
<ul>
<li><p>FROM: 모든 이미지는 다른 이미지로부터 출발한다. Python, Node, Ubuntu 등 다양한 시작 이미지 레이어를 지정한다.</p>
</li>
<li><p>ENV: 환경 변수 값을 지정하기 위한 인스트럭션이다. 값을 지정하기 위해 [key]=“[value]”형식을 따른다.</p>
</li>
<li><p>WORKDIR: 컨테이너 이미지 파일 시스템에 디렉터리를 만들고, 해당 디렉터리를 작업 디렉터리로 지정하는 인스트럭션이다. 리눅스는 “/temp”와 같은 방식으로 폴더를 만들고, 윈도 컨테이너에서는 “C:\temp”와 같은 방식으로 폴더를 만든다.</p>
</li>
<li><p>COPY: 로컬 파일 시스템의 파일 혹은 디렉터리를 컨테이너 이미지로 복사하는 인스트럭션이다. [원본경로] [복사경로] 형식으로 지정하면 된다. 예시 스크립트에서는 로컬 파일 시스템에 있는 app.js 파일을 이미지의 작업 디렉터리로 복사했다.</p>
</li>
<li><p>RUN: 쉘(shell)에서 커맨드를 실행하는 것처럼 이미지 빌드 과정에서 필요한 커맨드를 실행하기 위해 사용됩니다.</p>
</li>
<li><p>ENTRYPOINT: 이미지를 컨테이너로 띄울 때 항상 실행되야 하는 커맨드를 지정할 때 사용합니다. Docker 이미지를 마치 하나의 실행 파일처럼 사용할 때 유용합니다. 왜냐하면 컨테이너가 뜰 때 ENTRYPOINT 명령문으로 지정된 커맨드가 실행되고, 이 커맨드로 실행된 프로세스가 죽을 때, 컨테이너를 따라서 종료되기 때문입니다.</p>
</li>
<li><p>CMD: 해당 이미지를 컨테이너로 듸울 때 디폴트로 실행할 커맨드나, ENTRYPOINT명령문으로 지정된 커맨드에 디폴트로 넘길 파라미터를 지정할 때 사용합니다. CMD 명령문은 많은 경우, ENTRYPOINT 명령문과 함께 사용하게 되는데, ENTRYPOINT 명령문으로는 커맨드를 지정하고, CMD 명령문으로 디폴트 파리미터를 지정해주면 매우 유연하게 이미지를 실행할 수 있게 됩니다.</p>
</li>
</ul>
<h3 id="도표-정리">도표 정리</h3>
<table>
<thead>
<tr>
<th>Command</th>
<th>설명</th>
</tr>
</thead>
<tbody><tr>
<td>FROM</td>
<td>베이스 이미지 설정</td>
</tr>
<tr>
<td>ENV</td>
<td>환경 변수 설정</td>
</tr>
<tr>
<td>WORKDIR</td>
<td>작업 디렉토리 설정</td>
</tr>
<tr>
<td>COPY</td>
<td>이미지의 파일 시스템으로 파일 또는 디렉토리 복사</td>
</tr>
<tr>
<td>RUN</td>
<td>이미지 빌드 시 커맨드 실행</td>
</tr>
<tr>
<td>CMD</td>
<td>이미지 실행시 디폴트 커맨드 또는 파라미터 설정</td>
</tr>
<tr>
<td>ENTRYPOINT</td>
<td>이미지 실행 시 항상 실행되야 하는 커맨드 설정</td>
</tr>
</tbody></table>
<h1 id="2-도커를-연다">2. 도커를 연다.</h1>
<p>(Ubuntu) DockerFile이 있는 디렉토리로 이동하여 커맨드라인에서 도커를 실행한다.
(Window) DockerFile이 있는 디렉토리로 이동하여 cmd창을 켠다.</p>
<h1 id="3-도커에서-이미지를-빌드한다">3. 도커에서 이미지를 빌드한다.</h1>
<pre><code class="language-bash">&gt; docker image build –t test .</code></pre>
<p>docker image build로 도커파일에서 이미지를 빌드한다. -t는 image tag를 test로 지정한다는 뜻이다. 마지막 상대경로 ‘ . ’은 현재 디렉토리에서 DockerFile을 찾아서 빌드한다는 뜻이다.</p>
<h1 id="4-도커-허브에-이미지를-업로드한다">4. 도커 허브에 이미지를 업로드한다.</h1>
<p>우선 도커 허브에 가입해야한다. 가입 후 ‘유저이름’과 ‘비밀번호’를 잘 기억해둔다.
로컬 cmd에서 도커에 로그인한다.</p>
<pre><code class="language-bash">&gt; docker login —username [유저 이름]
&gt; [비밀번호]</code></pre>
<p>이제 내 계정으로 도커 허브에 이미지를 업로드(Push)할 수 있다.
우선 로컬에서 빌드한 이미지의 이름을 도커허브 형식에 맞게 변경한다.</p>
<pre><code class="language-bash">&gt; docker image tag test-image [dockerID/image-name:tag]</code></pre>
<p>그리고 이미지를 도커허브에 업로드한다.</p>
<pre><code class="language-bash">&gt; docker image push [dockerID/image-name:tag]</code></pre>
<p>도커 허브 웹페이지에서 내 이미지가 업로드 된 것을 확인할 수 있다.</p>
<h1 id=""><img src="https://velog.velcdn.com/images/t_wave/post/9ba9ab88-f370-4d9d-b693-b370844df0c8/image.png" alt=""></h1>
<ol start="5">
<li>도커 허브에서 이미지를 다운받는다.</li>
</ol>
<p>이제 다른 컴퓨터에서도 도커 허브 접근 권한만 있다면 자유롭게 이미지를 다운받아 바로 파일을 실행할 수 있다.</p>
<pre><code class="language-bash">&gt; docker container run [dockerID/image-name:tag]</code></pre>
]]></description>
        </item>
        <item>
            <title><![CDATA[도커의 기초 지식과 실습]]></title>
            <link>https://velog.io/@t_wave/BasicDockerPractice</link>
            <guid>https://velog.io/@t_wave/BasicDockerPractice</guid>
            <pubDate>Wed, 19 Jul 2023 07:49:32 GMT</pubDate>
            <description><![CDATA[<p>이 글은 Docker를 쓰는 이유, VM과 Docker의 차이점, Docker의 기본 개념과 특성, Docker 실습을 담고있다.</p>
<h1 id="도커를-쓰는-이유">도커를 쓰는 이유</h1>
<p><img src="https://velog.velcdn.com/images/t_wave/post/25c555d4-f7e4-41c1-8130-7d9a261bb3b0/image.png" alt=""></p>
<p>도커의 최고 장점은 <span style="color:orange">간편한 환경 설정</span>이다. </p>
<p>도커를 사용하면 15년 전에 작성된 Java코드와 1년전에 작성된 Go코드를 하나의 컴퓨터에서 돌릴 수 있다. 코드의 배경이 되는 Library Dependency를 컨테이너별로 설정할 수 있기 때문이다. 라이브러리의 버전 충돌때문에 낭비되는 시간을 줄일 수 있다.</p>
<p>규모가 있는 프로젝트를 인수인계할 때 특히 빛을 발한다. 인수인계를 하면 대여섯 가지나 되는 도구를 특정한 버전으로 설치해야 하고 운영 팀도 또 다른 도구 대여섯 가지를 설치해야 하는 경우가 다반사다. 도커를 도입하면서 모든 툴체인이 중앙집중화돼 어떤 역할의 조직에서도 이런 환경설정 작업이 간단해졌다.</p>
<h1 id="도커와-virtual-machinevm의-차이점">도커와 Virtual Machine(VM)의 차이점</h1>
<p>VM도 도커와 마찬가지로 서로 다른 환경에서 동작하는 코드들을 하나의 컴퓨터에서 실행시키기 위한 툴이다. 도커와 VM의 차이를 살펴보자.</p>
<p><img src="https://velog.velcdn.com/images/t_wave/post/83d88c3e-b592-4083-9143-f9750bef4459/image.png" alt=""></p>
<p>VM은 컴퓨터 자원을 Guest OS로 분할하여 컴퓨터 자원을 사용한다. Guest OS간에는 어떠한 간섭도 없이 별개의 컴퓨터처럼 사용된다. OS위에 OS가 올라가기 때문에 비교적 무겁다. Docker는 Guest OS를 만들지 않는다. 그래서 같은 컴퓨터 자원으로 5배 정도 많은 어플리케이션을 실행할 수 있다.</p>
<p><img src="https://velog.velcdn.com/images/t_wave/post/2ed141b3-072b-4b14-a93f-53eb6398ddd8/image.png" alt=""></p>
<p>도커의 구조는 Linux 커널로 도커 Container들을 관리하는 방식이다. 리눅스 커널 기능으로 각 컨테이너들은 격리된다. </p>
<p><img src="https://velog.velcdn.com/images/t_wave/post/b52419f7-3042-4690-a405-ceaed4f8c6ee/image.jpg" alt=""></p>
<p>하나의 컨테이너는 OS처럼 고유의 환경변수, IP주소를 갖는다. 컨테이너 내부에서 사용되는 환경변수, IP주소는 도커가 임의로 부여한 가상의 값들이다. 그래서 Host(컴퓨터)의 환경변수, IP주소와 호환되지 않는다.</p>
<h1 id="도커의-이미지와-레이어-컨테이너">도커의 이미지와 레이어, 컨테이너</h1>
<h3 id="이미지image--정적인-상태">이미지(image) = 정적인 상태</h3>
<p>서버 프로그램, 소스코드 및 라이브러리, 컴파일된 <span style="color:orange">모든 실행 파일을 묶어둔 Read-Only 파일</span>이다. 보통 용량은 수백MB ~ 수GB으로 VM의 이미지보다는 작다. 파일들의 상태를 저장해둔 패키지라 이해하면 된다. Ubuntu이미지는 Ubuntu를 실행하기 위한 모든 파일을 가지고 있으며, Oracle이미지는 Oracle을 실행하는데 필요한 파일과 실행명령어, port정보를 가지고 있다. Ubuntu, Oracle 같은 유명한 이미지는 도커 허브 등의 공식 레지스트리에서 쉽게 다운받아 사용할 수 있다.</p>
<h3 id="레이어layer">레이어(layer)</h3>
<p><img src="https://velog.velcdn.com/images/t_wave/post/4e56a251-68cb-420c-9185-e3a992658434/image.png" alt=""></p>
<p><span style="color:orange">이미지는 팬케이크처럼 여러겹의 레이어로 이루어진다.</span> 기존 이미지(레이어들)에 파일 하나를 추가하기 위해서 전체 이미지를 다시 빌드하지 않고, 추가 파일 레이어 하나만 쌓아서 이미지를 만든다. 레이어는 읽기 전용(read only)이므로 각각의 레이어를 수정할 수 없다. 새로운 레이어를 쌓기만 할 수 있다. 도커 파일(Docker File)은 레이어를 어떤 순서로 쌓아서 이미지를 만들지 적는 파일이다. DockerHub 및 개인 레지스트리에서 이미지를 공유할 때 바뀐 부분만 주고받을 수 있다.</p>
<h3 id="도커-컨테이너docker-container--동적인-상태">도커 컨테이너(Docker container) = 동적인 상태</h3>
<p><img src="https://velog.velcdn.com/images/t_wave/post/e90ed3af-c8d2-4b0b-94c7-2b0b7fedb5ae/image.png" alt=""></p>
<p><span style="color:orange">이미지(Image)의 구조를 가져와 실행한 상태</span>이다. 이미지는 Read-Only 파일로 직접 코드를 작성할 수 없다. 이미지의 레시피를 그대로 요리해 실제 음식으로 만든게 컨테이너다.  “Docker conatiner run [이미지 이름]”을 통해 실행한다.</p>
<h3 id="도커-파일docker-file">도커 파일(Docker File)</h3>
<p>도커 이미지를 자동으로 빌드하기 위한 파일이다. 이 파일에서 라이브러리의 버전 충돌을 제어하여 쉽게 이미지를 빌드할 수 있다.</p>
<h1 id="컨테이너의-구성과-볼륨">컨테이너의 구성과 볼륨</h1>
<p><img src="https://velog.velcdn.com/images/t_wave/post/6991f196-7977-47eb-b656-fc2915e1b2e5/image.png" alt=""></p>
<p>이미지에서 컨테이너를 구성할 때 이미지의 레시피를 가져와 컨테이너를 만든다고 했다. <span style="color:orange">이때 이미지의 내용을 복사하는 것이 아니라 Read-Only 형태로 참조하는 방식을 사용한다.</span> 그래서 똑같은 내용으로 용량을 두번 차지하는 비효율을 없엤다.</p>
<p>컨테이너에서 수정되는 파일은 Container layer에서 이루어지고, 원래 이미지에서 수정할 내용이 있다면 Container layer로 가져와 수정한다. 이미지 레이어 자체는 건드리지 않는다.</p>
<p>컨테이너의 생명주기가 다해서 Exit되면 Container layer도 사라진다. 만약 DB와 같은 중요 정보를 컨테이너에서 수정하다가, DB의 이미지가 업데이트되어 새로운 컨테이너로 옮길 때 기존의 수정된 정보가 손실된다면 대참사가 일어날 것이다. 이를 방지하기 위해 <strong><span style="color:orange">도커 볼륨(Docker Volume)</span></strong>이 필요하다.</p>
<p>도커 볼륨은 이미지, 컨테이너와 다른 주기를 갖는 디렉토리이다. 도커 버전의 USB라고 생각하면 된다. 이 글에서는 내용이 너무 길어지므로 개념만 설명하고 넘어가겠다.</p>
<h1 id="도커-사용하기-container의-5가지-상태">도커 사용하기 (Container의 5가지 상태)</h1>
<p><img src="https://velog.velcdn.com/images/t_wave/post/45fe0c3a-775f-44d6-9559-b7b31427c4ba/image.png" alt=""></p>
<ul>
<li>Created: Image의 상태를 받아 Container를 생성한 상태이다.</li>
<li>Running: Container에 실제 컴퓨터 자원을 할당하여 프로그램을 실행시킨 상태이다.</li>
<li>Paused: Container에 <span style="color:orange">할당된 컴퓨터 자원을 빼앗지 않으나</span> 잠깐 프로그램의 실행을 멈춘 상태이다. 다시 Running상태로 돌아가면 프로그램을 그대로 실행할 수 있다. <strong>(Window 절전모드)</strong></li>
<li>Exited: Container에 <span style="color:orange">할당된 컴퓨터 자원을 빼앗은 상태이다 (Memory Free)</span>. 다시 Running 상태로 돌아가도 실행하던 프로그램을 이어서 실행 할 수 없다. <strong>(Window 전원 끈 상태)</strong></li>
<li>Deleted: Container 자체가 삭제된 상태이다.</li>
</ul>
<h1 id="도커-실습하기">도커 실습하기</h1>
<p>도커 기본 명령어 형식</p>
<pre><code class="language-bash">&gt; docker [container/image/volume...] [명령어]</code></pre>
<h3 id="1-컨테이너-실행하기">1) 컨테이너 실행하기</h3>
<pre><code class="language-bash">&gt; docker run [OPTIONS] [IMAGE:TAG]</code></pre>
<p>run 명령어는 image를 컨테이너로 실행시킨다. 이미지가 로컬 registry에 없을 경우, 자동으로 docker hub를 통해 image를 pull 받아온 뒤 실행시킨다.</p>
<h4 id="예시-실행해보기">예시 실행해보기</h4>
<pre><code class="language-bash">&gt; docker run ubuntu:18.04</code></pre>
<p><img src="https://velog.velcdn.com/images/t_wave/post/439a88cc-8c20-435e-a22b-bda229a0b668/image.png" alt=""></p>
<pre><code class="language-bash">&gt; docker ps -a</code></pre>
<p>ps는 실행중인 컨테이너를 확인하는 명령어이다. -a는 모든 상태의 컨테이너를 확인한다.
<img src="https://velog.velcdn.com/images/t_wave/post/40b2e7b9-171a-4094-81dd-10c89b4af5f9/image.png" alt=""></p>
<table>
<thead>
<tr>
<th>docker run 옵션</th>
<th>옵션 설명 (docker run [option])</th>
</tr>
</thead>
<tbody><tr>
<td>-d / --detach</td>
<td>detached mode로 백그라운드 모드를 의미합니다. 호스팅 서버 등에 사용됩니다.</td>
</tr>
<tr>
<td>-p / --publish</td>
<td>Host(로컬 컴퓨터)와 컨테이너의 포트를 연결해주는 옵션입니다.</td>
</tr>
<tr>
<td>-v</td>
<td>Host와 컨테이너의 디렉토리를 연결해 볼륨을 만드는 옵션입니다.</td>
</tr>
<tr>
<td>-e</td>
<td>컨테이너 내 환경변수를 설정할 때 사용합니다.</td>
</tr>
<tr>
<td>--name</td>
<td>컨테이너의 이름을 설정합니다.</td>
</tr>
<tr>
<td>-rm</td>
<td>프로세스가 종료되면 자동으로 컨테이너를 제거하는 옵션입니다.</td>
</tr>
<tr>
<td>-it / --tty</td>
<td>-i 옵션과 -t 옵션을 함께 사용한 것입니다. 직접 컨테이너에 접속해 터미널 입력을 위한 옵션입니다.</td>
</tr>
<tr>
<td>-link</td>
<td>컨테이너를 연결해주는 옵션입니다.</td>
</tr>
</tbody></table>
<h3 id="2-컨테이너-pause하기">2) 컨테이너 Pause하기</h3>
<pre><code class="language-bash">&gt; docker stop [CONTAINER ID]</code></pre>
<p><img src="https://velog.velcdn.com/images/t_wave/post/4f54a100-6e6e-441c-8cd1-f3dfd56c3a18/image.png" alt=""></p>
<p>docker ps의 CONTAINER ID를 확인하여 ID의 앞 4자리정도만 입력하면 알아서 식별하여 컨테이너를 stop한다.</p>
<h3 id="3-pause된-컨테이너-삭제하기">3) Pause된 컨테이너 삭제하기</h3>
<pre><code class="language-bash">&gt; docker rm [CONTAINER ID]</code></pre>
<p><img src="https://velog.velcdn.com/images/t_wave/post/ab758a08-3d82-43df-99ce-97457918e8ab/image.png" alt=""></p>
<p>stop과 동일하다.</p>
<h3 id="4-실행중인-컨테이너-exit">4) 실행중인 컨테이너 Exit</h3>
<pre><code class="language-bash">&gt; docker kill [CONTAINER ID]</code></pre>
<p>ㅇㅇ</p>
<h3 id="5-docker-file로-image-만들기">5) Docker File로 image 만들기</h3>
<p>도커를 쓰면 우분투나 파이썬 하나만 있는 이미지를 쓰지 않는다. 도커파일로 모든 레이어를 기술하여 한번에 빌드할 수 있다. (도커파일 사용법 알아보기)</p>
<p><img src="https://velog.velcdn.com/images/t_wave/post/821bb314-cc35-4bb1-a742-ba5c09cfe19b/image.png" alt=""></p>
<pre><code class="language-bash">&gt; docker build –t [Docker File]</code></pre>
<p>docker file을 빌드할 수 있다.</p>
<p><img src="https://velog.velcdn.com/images/t_wave/post/9ab1fb32-2e78-4ada-839f-5b9d18cae375/image.png" alt=""></p>
<p>빌드를 완료하면 위처럼 image가 생성된다.</p>
<h3 id="6-컨테이너-이미지로-만들기">6) 컨테이너 이미지로 만들기</h3>
<p>이미지로 만들기 전에 컨테이너를 Pause(멈춤) 상태로 만든다.</p>
<pre><code class="language-bash">&gt; docker stop [컨테이너 이름 또는 아이디]</code></pre>
<p>컨테이너의 이름 또는 아이디를 알아낸다.</p>
<pre><code class="language-bash">&gt; docker ps –a
또는
&gt; docker container ls -all</code></pre>
<p>컨테이너의 아이디를 넣어서 이미지로 만든다.</p>
<pre><code class="language-bash">&gt; docker commit container_id image_name:tag</code></pre>
<p>끝!</p>
<blockquote>
<p>(참고자료)
도커 교과서, 엘튼 스톤맨, <a href="https://www.yes24.com/Product/Goods/111408749">링크</a></p>
</blockquote>
]]></description>
        </item>
        <item>
            <title><![CDATA[Docker 설치 및 실행 (Ubuntu 환경)]]></title>
            <link>https://velog.io/@t_wave/Docker-%EC%84%A4%EC%B9%98-%EB%B0%8F-%EC%8B%A4%ED%96%89-Ubuntu-%ED%99%98%EA%B2%BD</link>
            <guid>https://velog.io/@t_wave/Docker-%EC%84%A4%EC%B9%98-%EB%B0%8F-%EC%8B%A4%ED%96%89-Ubuntu-%ED%99%98%EA%B2%BD</guid>
            <pubDate>Wed, 19 Jul 2023 00:22:11 GMT</pubDate>
            <description><![CDATA[<p><img src="https://velog.velcdn.com/images/t_wave/post/ded573e9-a7b7-4e80-8820-b4f163c6f3a0/image.png" alt="">
수많은 에러들을 뚫고 설치할 수 있는 방법.</p>
<h1 id="기본-설치">기본 설치</h1>
<p>(선택사항) root 계정에 접속한다.</p>
<pre><code class="language-bash">&gt; su
&gt; [password 입력]</code></pre>
<p>다운로드에 필요한 패키지들을 설치한다. (ca-certificates, curl, gnupg, lsb_release)</p>
<pre><code class="language-bash">&gt;  sudo apt-get update
&gt;  sudo apt-get install ca-certificates curl gnupg</code></pre>
<p>docker download script를 docker 홈페이지에서 다운 받아 실행한다.</p>
<pre><code class="language-bash">&gt;  curl -fsSL https://get.docker.com -o get-docker.sh
&gt;  sudo sh get-docker.sh</code></pre>
<p>설치가 되었는지 확인하기 위해 hello-world:image를 실행시켜본다.</p>
<pre><code class="language-bash">&gt;  sudo docker run hello-world</code></pre>
<blockquote>
<p>“”“
Unable to find image &#39;hello-world:latest&#39; locally
latest: Pulling from library/hello-world
2db29710123e: Pull complete
Digest: sha256:---
Status: Downloaded newer image for hello-world:latest
Hello from Docker!
This message shows that your installation appears to be working correctly.
To generate this message, Docker took the following steps:</p>
</blockquote>
<ol>
<li>The Docker client contacted the Docker daemon.</li>
<li>The Docker daemon pulled the &quot;hello-world&quot; image from the Docker Hub.
(amd64)</li>
<li>The Docker daemon created a new container from that image which runs the
executable that produces the output you are currently reading.</li>
<li>The Docker daemon streamed that output to the Docker client, which sent it
to your terminal.
“”“</li>
</ol>
<p>도커가 잘 실행됐다면 위 메시지가 나온다.
실행이 안됐다면 아래 방법들을 시도해서 해결할 수 있다.</p>
<h1 id="에러-해결하기">에러 해결하기</h1>
<h3 id="hyper-v-사용권한-설정하기">Hyper-v 사용권한 설정하기</h3>
<p>Hyper-v를 사용할 수 있도록 설정한다.</p>
<pre><code class="language-bash">&gt;  bcdedit /set hypervisorlaunchtype auto</code></pre>
<h3 id="bios에서-vm-사용권한-설정하기">BIOS에서 VM 사용권한 설정하기</h3>
<p>그리고 다음 과정을 통해 BIOS 환경에서 VM 사용 제한을 해제한다.</p>
<ol>
<li>BIOS에서 VM을 설정하기 위해 컴퓨터 리부팅한다.</li>
<li>리부팅 과정(검정색 화면)에서 F2와 또는 Delete 키를 연타한다. (<a href="https://www.youtube.com/watch?v=NbnPWhI4xis">연타 방법 동영상</a>)</li>
<li>이후 BIOS 설정에서 advanced mode에서 Intel Virtualization Technology 설정을 enable로 변경한다. (<a href="https://www.youtube.com/watch?v=co2b3RjMQeQ&amp;t=59s">과정 동영상</a>)</li>
</ol>
<p>다시 컴퓨터를 키면 Docker를 실행할 수 있다. </p>
<pre><code class="language-bash">&gt;  docker version</code></pre>
<h3 id="리눅스-관리자-권한-설정하기">리눅스 관리자 권한 설정하기</h3>
<p>일반 사용자가 관리자 권한이 없어 sudo 명령어로만 docker가 실행된다면 사용자를 Docker 그룹에 추가할 수 있다.</p>
<pre><code class="language-bash">&gt;  sudo usermod -aG docker $USER</code></pre>
]]></description>
        </item>
        <item>
            <title><![CDATA[[FFmpeg] 동영상 편집할 때는 OpenCV말고 FFmpeg쓰는게 훨 낫다.]]></title>
            <link>https://velog.io/@t_wave/FFmpeg-%EB%8F%99%EC%98%81%EC%83%81-%ED%8E%B8%EC%A7%91%ED%95%A0-%EB%95%8C%EB%8A%94-OpenCV%EB%A7%90%EA%B3%A0-FFmpeg%EC%93%B0%EB%8A%94%EA%B2%8C-%ED%9B%A8-%EB%82%AB%EB%8B%A4</link>
            <guid>https://velog.io/@t_wave/FFmpeg-%EB%8F%99%EC%98%81%EC%83%81-%ED%8E%B8%EC%A7%91%ED%95%A0-%EB%95%8C%EB%8A%94-OpenCV%EB%A7%90%EA%B3%A0-FFmpeg%EC%93%B0%EB%8A%94%EA%B2%8C-%ED%9B%A8-%EB%82%AB%EB%8B%A4</guid>
            <pubDate>Mon, 30 Jan 2023 12:42:10 GMT</pubDate>
            <description><![CDATA[<h1 id="목표">목표</h1>
<p><img src="https://velog.velcdn.com/images/t_wave/post/1bb2fcdb-6956-45ed-84a7-d28b8ef671e2/image.png" alt=""></p>
<p>AIhub에 있는 &#39;<a href="https://aihub.or.kr/aihubdata/data/view.do?currMenu=115&amp;topMenu=100&amp;aihubDataSe=realm&amp;dataSetSn=171">이상행동 CCTV 동영상 데이터셋</a>&#39;에서 이상행동을 하는 부분만 잘라내어 학습 데이터로 쓰려고 합니다. </p>
<h1 id="1-데이터-살펴보기">1. 데이터 살펴보기</h1>
<ul>
<li>데이터 종류와 크기   </li>
</ul>
<table>
<thead>
<tr>
<th align="center">이상행동명칭</th>
<th align="center">영상 개수</th>
<th align="center">영상 시간</th>
</tr>
</thead>
<tbody><tr>
<td align="center">01.폭행(Assault)</td>
<td align="center">913</td>
<td align="center">78:05:41</td>
</tr>
<tr>
<td align="center">02.싸움(Fight)</td>
<td align="center">1174</td>
<td align="center">99:54:45</td>
</tr>
<tr>
<td align="center">03.절도(Burglary)</td>
<td align="center">839</td>
<td align="center">69:33:24</td>
</tr>
<tr>
<td align="center">04.기물파손(Vandalism)</td>
<td align="center">490</td>
<td align="center">41:28:46</td>
</tr>
<tr>
<td align="center">05.실신(Swoon)</td>
<td align="center">912</td>
<td align="center">84:26:16</td>
</tr>
<tr>
<td align="center">06.배회(Wander)</td>
<td align="center">645</td>
<td align="center">55:24:50</td>
</tr>
<tr>
<td align="center">07.침입(Trespass)</td>
<td align="center">259</td>
<td align="center">22:03:15</td>
</tr>
<tr>
<td align="center">08.투기(Dump)</td>
<td align="center">259</td>
<td align="center">22:03:15</td>
</tr>
<tr>
<td align="center">09.강도(Robbery)</td>
<td align="center">259</td>
<td align="center">22:03:15</td>
</tr>
<tr>
<td align="center">10.데이트폭력 및 추행(Datefight)</td>
<td align="center">693</td>
<td align="center">58:21:45</td>
</tr>
<tr>
<td align="center">11.납치(Kidnap)</td>
<td align="center">262</td>
<td align="center">22:24:08</td>
</tr>
<tr>
<td align="center">12.주취행동(Drunken)</td>
<td align="center">1262</td>
<td align="center">104:20:37</td>
</tr>
<tr>
<td align="center">합계</td>
<td align="center">8436</td>
<td align="center">717:03:33</td>
</tr>
</tbody></table>
<ul>
<li>라벨 (XML파일)
라벨 <code>&lt;action&gt;</code>안에 <code>&lt;actionname&gt;</code>과 <code>&lt;frame&gt;</code>이 필요한 정보입니다. 영상 안에서 Kick이나 punch등 다양한 이상행동들이 나와요. 우리는 영상에서 이상행동이 나오는 부분을 잘라 행동 종류별로 각각 폴더에 담을겁니다.
<img src="https://velog.velcdn.com/images/t_wave/post/09fd3efe-327b-4e1c-9424-f189901b8e51/image.png" alt=""></li>
</ul>
<h1 id="2-ffmpeg-간단-소개">2. FFmpeg 간단 소개</h1>
<p>FFmpeg는 영상 처리를 위한 라이브러리입니다. 자세하게 알고 싶다면 <a href="https://wikidocs.net/book/4080">여기 wikidocs</a>로 들어가 보세요. 저는 대략 이해하는데 도움이 됐어요.</p>
<pre><code class="language-python">!ffmpeg -i {input_file} -vf scale=3840x2160 
-vf trim=start_frame={start_frame}:end_frame={end_frame} {output_file}</code></pre>
<p>보통 cmd창에서 영상을 편집하는데 저는 python의 매직커맨드 기능을 사용해서 cmd 명령어를 python에서 사용했어요. 앞에 &#39;!&#39; 를 붙이면 python에서 cmd 명령어를 사용할 수 있어요.</p>
<p>코드를 설명은 아래 있어요.</p>
<ul>
<li>-i 뒤에 편집할 동영상 경로 입력</li>
<li>vf scale= 뒤에 화면 사이즈 입력 (영상 속성에서 확인 가능)</li>
<li>vf trim= 뒤에 시작 frame과 끝 frame을 입력</li>
<li>output_file칸에 저장할 파일 이름 입력 (확장자까지 입력)</li>
</ul>
<p>다른 포스트 보니까 -ss 같은 옵션을 쓰면 초단위로 설정해서 자를 수도 있다고 하네요.</p>
<pre><code class="language-python">!ffplay -i {file_path} -video_size 3840x2160 -loop 0</code></pre>
<p>편집한 영상을 코드로 실행하려면 위 코드를 사용하시면 됩니다.</p>
<h1 id="3-python에서-xml파일-읽기">3. Python에서 XML파일 읽기</h1>
<p>우선 파일들을 가져옵시다.</p>
<pre><code class="language-python">import os

data_base_folder = &#39;C:\\Users\\luna_f1\\Desktop\\aihub_video_edit&#39;
video_path = data_base_folder + &#39;\\videos&#39;
label_path = data_base_folder + &#39;\\labels&#39;
save_folder = data_base_folder + &#39;\\save&#39;

video_file_list = os.listdir(video_path)
label_file_list = os.listdir(label_path)

video_file_list.sort()
label_file_list.sort()

print(&#39;img len: {}, label len: {}\n&#39;.format(len(video_file_list), len(label_file_list)))
print(video_file_list[:10])
print(label_file_list[:10])


## 출력 예시
## img len: 1, label len: 1
## [&#39;488-3_cam03_vandalism01_place09_day_winter.mp4&#39;]
## [&#39;488-3_cam03_vandalism01_place09_day_winter.xml&#39;]</code></pre>
<p>label_file_list에는 xml파일들이, video_file_list에는 영상들 파일 이름이 들어있어요.</p>
<p>그리고 먼저 영상 편집을 할 함수를 설계해봅시다.</p>
<pre><code class="language-python">def cut_video(start_frame, end_frame, input_file, output_file):
    !ffmpeg -i {input_file} -vf scale=3840x2160 
    -vf trim=start_frame={start_frame}:end_frame={end_frame} {output_file}
    return</code></pre>
<p>함수에서 시작프레임, 끝프레임, 입력 파일, 출력 파일을 매개변수로 받아 편집하도록 만들었어요.</p>
<p>그러면 xml파일을 읽어서 함수를 호출하기만 하면 됩니다. 아래는 그 코드에요.</p>
<pre><code class="language-python">import xml.etree.ElementTree as ET
import os

# 영상과 xml 라벨을 짝꿍을 맞춰 가져옴
for video_file, label_file in zip(video_file_list, label_file_list):
    video_file_path = video_path + &#39;\\&#39; + video_file
    label_file_path = label_path + &#39;\\&#39; + label_file
    label = ET.parse(label_file_path).getroot()

   # 잘라낼 영상 부분을 actionname에 따라 분류함.
    highlights = { }
    for clss in label.iter(&quot;actionname&quot;):
        clss = clss.text
        highlights[clss] = []

        # 영상들을 저장할 폴더를 actionname별로 각각 만듦
        try:
            os.mkdir(save_folder + &#39;\\&#39; + clss)
        except:
            pass

    # 영상 안의 이상행동들의 시작프레임, 끝프레임을 리스트로 묶어 이상행동 종류별로 dict에 저장함.
    # 구조: highlights[&#39;punching&#39;] = [[start, end], [start, end]...]
    for action in label.iter(&quot;action&quot;):
        hlight_class = action.find(&#39;actionname&#39;).text
        for start, end in zip(action.iter(&#39;start&#39;), action.iter(&#39;end&#39;)):
            highlights[hlight_class].append([start.text, end.text])

    # 이상행동 부분들을 잘라서 now_save_folder_path에 저장함
    for clss in highlights.keys():
        now_save_folder_path = save_folder + &#39;\\&#39; + clss
        #video_file_path = video_path + &#39;\\&#39; + video_file
        for [start, end] in highlights[clss]:
            cut_video(start, end, video_file_path, now_save_folder_path + &#39;\\&#39; + start + end + &#39;.yuv&#39;)</code></pre>
<h3 id="opencv-뻘짓-기록">OpenCV 뻘짓 기록</h3>
<p>이미지 데이터에서 OpenCV를 써서 동영상 자르는것도 이걸로 하려고 했는데 생각보다 뻘짓이 길어졌네요. 내가 실력이 모자라서 그런거 같습니. 혹시 누군가에게 도움이 될지 모르니 실패한 과정을 적어놓겠습니다.</p>
<pre><code class="language-python">######### 동영상 처리는 OpenCV보다는FFmpeg를 쓰자!


import cv2
import numpy as np
import xml.etree.ElementTree as ET

for video_file, label_file in zip(video_file_list, label_file_list):
    video_file_path = video_path + &#39;\\&#39; + video_file
    label_file_path = label_path + &#39;\\&#39; + label_file

    cap = cv2.VideoCapture(video_file_path)
    label = ET.parse(label_file_path).getroot()

   # 잘라낼 영상 부분을 punching, kicking, pushing 3가지로 분류함. (각각 여러개 있을 수 있음)
    highlight = {
        &#39;punching&#39; : [],
        &#39;kicking&#39; : [],
        &#39;pushing&#39; : []
    }
    # 구조: highlight[&#39;punching&#39;] = [[start, end], [start, end]...]
    for action in label.iter(&quot;action&quot;):
        hlight_class = action.find(&#39;actionname&#39;).text
        for start, end in zip(action.iter(&#39;start&#39;), action.iter(&#39;end&#39;)):
            highlight[hlight_class].append([int(start.text), int(end.text)])

    #재생할 파일의 넓이와 높이
    width = cap.get(cv2.CAP_PROP_FRAME_WIDTH)
    height = cap.get(cv2.CAP_PROP_FRAME_HEIGHT)
    fourcc = cv2.VideoWriter_fourcc(*&#39;DIVX&#39;)

    # 영상을 여러개 출력해야하므로 영상 하나마다 out writer 하나를 지정함.
    outs = {
        &#39;punching&#39; : [],
        &#39;kicking&#39; : [],
        &#39;pushing&#39; : []
    }
    for hlight_class in highlight.keys():
        for i in range(len(highlight[hlight_class])):
            outs[hlight_class].append(cv2.VideoWriter(save_folder + &quot;\\output_&quot;+ hlight_class + str(i) + &#39;_&#39;+ video_file, fourcc, 30.0, (int(width), int(height))))

    while cap.isOpened():
        success, image = cap.read()
        if not success:
            print(&quot;카메라를 찾을 수 없습니다.&quot;)
            break

        now_frame = int(cap.get(cv2.CAP_PROP_POS_FRAMES))
        if not now_frame % 20 == 0: continue

        # 필요에 따라 성능 향상을 위해 이미지 작성을 불가능함으로 기본 설정합니다.
        image.flags.writeable = False
        image = cv2.cvtColor(image, cv2.COLOR_BGR2RGB)

        # 비디오 저장 파트입니다.
        cv2.imshow(&#39;video_show&#39;, image)
        for hlight_class in highlight.keys():
            for i in range(len(outs[hlight_class])):
                if now_frame &gt;= highlight[hlight_class][i][0] and now_frame &lt;= highlight[hlight_class][i][1]: # event frame 값이면
                    outs[hlight_class][i].write(image) # 이 부분이 write하는 부분.
        if cv2.waitKey(2) &amp; 0xFF == 27:
            break

    cap.release()
    for key in outs.keys():
        for out in outs[key]:
            out.release()
    cv2.destroyAllWindows()</code></pre>
<p>영상을 쭉 진행하다가 현재 프레임이 이상행동 프레임과 일치하면 동영상으로 저장하는 식으로 설계했습니다. MediaPipe에서 쓰던 코드를 재활용하다 이렇게 비효율적을 짠것같습니다. 여러모로 고칠점이 많아서 배울것도 많았어요.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[[MediaPipe] 동영상 속 사람 포즈 인식하기]]></title>
            <link>https://velog.io/@t_wave/MediaPipe-%EB%8F%99%EC%98%81%EC%83%81-%EC%86%8D-%EC%82%AC%EB%9E%8C-%ED%8F%AC%EC%A6%88-%EC%9D%B8%EC%8B%9D%ED%95%98%EA%B8%B0</link>
            <guid>https://velog.io/@t_wave/MediaPipe-%EB%8F%99%EC%98%81%EC%83%81-%EC%86%8D-%EC%82%AC%EB%9E%8C-%ED%8F%AC%EC%A6%88-%EC%9D%B8%EC%8B%9D%ED%95%98%EA%B8%B0</guid>
            <pubDate>Thu, 26 Jan 2023 12:49:00 GMT</pubDate>
            <description><![CDATA[<h1 id="목표">목표</h1>
<ul>
<li>MediaPipe를 사용하여 영상 속 사람의 특징점을 점으로 인식하고 그린다.</li>
<li>cv2를 활용해 동영상을 불러들이고 쓸 수 있다.</li>
</ul>
<h1 id="1-mediapipe-설치">1. <a href="https://google.github.io/mediapipe/">MediaPipe</a> 설치</h1>
<p>MediaPipe는 Google에서 만든 사람 인식 Library이다. 포즈 뿐만 아니라 다양한 카테고리를 인식할 수 있다.
<img src="https://velog.velcdn.com/images/t_wave/post/53949b7a-8442-45ca-a06f-09b02c168b36/image.png" alt=""></p>
<pre><code class="language-python"># 다운로드 코드
!pip install mediapipe</code></pre>
<p>홈페이지에 자세하게 MediaPipe에 대한 설명이 나와있지만 영어다. 한국어로 간단하게 설명해보겠다.</p>
<h2 id="간단-mediapipe-설명">간단 MediaPipe 설명</h2>
<p>MediaPipe는 아래와 같은 사람의 특징점들을 인식하는 머신러닝 라이브러리다.
<img src="https://velog.velcdn.com/images/t_wave/post/4cb819c8-af59-4455-b5ac-ecd1798645d3/image.png" alt=""></p>
<p>동영상의 경우 각 프레임별로 아래의 프로세스가 수행된다. </p>
<ol>
<li>가장 <strong>중심이 되는 사람을 감지(detect)</strong>한다.</li>
<li>그 사람의 특징점을 찾는다.
여기서 사람 하나를 먼저 감지하는 단계가 있다. 이 부분을 수정해 여러 사람을 감지하게 만들면 한 프레임 안의 여러사람에게서 특징점을 도출할 수 있다. 그만큼 시간이 오래 걸리겠지만.</li>
</ol>
<h1 id="2-코드">2. 코드</h1>
<p>MediaPipe 홈페이지에 사용 코드가 나와있다. 하지만 동영상을 돌리려면 코드를 약간 수정할 필요가 있다. 아래는 내가 수정한 코드다. 설명은 주석으로 달아놓았다.</p>
<pre><code class="language-python">import cv2
import mediapipe as mp
import numpy as np
mp_drawing = mp.solutions.drawing_utils
mp_drawing_styles = mp.solutions.drawing_styles
mp_pose = mp.solutions.pose

#파일 위치 미리 지정
input_video_path = &quot;/content/in_theft.mp4&quot;
save_video_path = &#39;/content/in_theft_output.mp4&#39;

cap = cv2.VideoCapture(input_video_path)

#재생할 파일의 넓이와 높이
width = cap.get(cv2.CAP_PROP_FRAME_WIDTH)
height = cap.get(cv2.CAP_PROP_FRAME_HEIGHT)
#video controller
fourcc = cv2.VideoWriter_fourcc(*&#39;DIVX&#39;)
out = cv2.VideoWriter(save_video_path, fourcc, 30.0, (int(width), int(height)))


with mp_pose.Pose(
        min_detection_confidence=0.7,
        min_tracking_confidence=0.7) as pose:
    while cap.isOpened():
        success, image = cap.read()
        if not success:
            print(&quot;카메라를 찾을 수 없습니다.&quot;)
            # 웹캠을 불러올 경우는 &#39;continue&#39;, 동영상을 불러올 경우 &#39;break&#39;를 사용합니다.
            break

        # 필요에 따라 성능 향상을 위해 이미지 작성을 불가능함으로 기본 설정합니다.
        image.flags.writeable = False
        image = cv2.cvtColor(image, cv2.COLOR_BGR2RGB)
        results = pose.process(image)

        # 포즈 주석을 이미지 위에 그립니다.
        image.flags.writeable = True
        image = cv2.cvtColor(image, cv2.COLOR_RGB2BGR)
        mp_drawing.draw_landmarks(
            image,
            results.pose_landmarks,
            mp_pose.POSE_CONNECTIONS,
            landmark_drawing_spec=mp_drawing_styles.get_default_pose_landmarks_style())
        # 보기 편하게 이미지를 좌우 반전합니다.
        cv2.imshow(&#39;MediaPipe Pose&#39;, image) #코랩에서 돌릴거면 imshow()문은 주석처리할 것.
        out.write(image)
        if cv2.waitKey(5) &amp; 0xFF == 27:
            break
cap.release()
out.release()
cv2.destroyAllWindows()</code></pre>
<p>결과는 아래 사진을 클릭하면 볼 수 있다. </p>
<p><a href="https://www.youtube.com/video/1HeSStFNBRY"><img src="https://velog.velcdn.com/images/t_wave/post/36076926-0cf1-498a-bdd1-7ca4a1f0f1ce/image.png" alt="유튜브 바로가기"></a> </p>
<p>  보면 잘못 인식한 부분이 보인다. 사람이 여러명이라 그런듯 하다. 다음에 고쳐보자.!</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[[YOLOv5] 욜로로 코랩에서 객체 인식 및 Crop, 좌표 구하기]]></title>
            <link>https://velog.io/@t_wave/YOLOv5-%EC%9A%9C%EB%A1%9C%EB%A1%9C-%EC%BD%94%EB%9E%A9%EC%97%90%EC%84%9C-%EA%B0%9D%EC%B2%B4-%EC%9D%B8%EC%8B%9D%ED%95%98%EA%B8%B0</link>
            <guid>https://velog.io/@t_wave/YOLOv5-%EC%9A%9C%EB%A1%9C%EB%A1%9C-%EC%BD%94%EB%9E%A9%EC%97%90%EC%84%9C-%EA%B0%9D%EC%B2%B4-%EC%9D%B8%EC%8B%9D%ED%95%98%EA%B8%B0</guid>
            <pubDate>Wed, 25 Jan 2023 13:38:45 GMT</pubDate>
            <description><![CDATA[<p>지난 글에서 학습을 했기 때문에 여기서는 detect한 부분만 간단히 적겠다. (<a href="https://velog.io/@t_wave/YOLOv5-Custom-Data-Set-%ED%95%99%EC%8A%B5%EC%8B%9C%ED%82%A4%EA%B8%B0-colab-%EC%82%AC%EC%9A%A9">지난글 바로가기</a>)</p>
<h1 id="목표">목표</h1>
<p><img src="https://velog.velcdn.com/images/t_wave/post/94836a22-d4df-4ddf-894e-c3b629da541c/image.png" alt=""></p>
<ul>
<li>위 사진처럼 detect하는 YOLO모델을 실행시켜본다.</li>
</ul>
<p>기본적인 detect를 하는 코드는 아래와 같다.</p>
<pre><code class="language-python">from IPython.display import Image
import os

!python /content/drive/MyDrive/yolov5/detect.py --weights /content/carplate_best.pt --source /content/drive/MyDrive/crop_img/temp/same_data_masking</code></pre>
<p>3개의 파일 주소를 넣어야하는데 차례대로 다음과 같다.</p>
<ul>
<li>욜로를 git에서 클론할때 딸려온 detect.py의 위치</li>
<li>학습시킨 pt의 위치</li>
<li>yolo에 넣을 파일들 위치</li>
</ul>
<p>참고로 학습시킨 pt의 위치는 
yolo &gt; train &gt; 학습시킨 모델이름 &gt; weight에 .pt형태로 저장되어있다.</p>
<p>여기에 아래와 같은 옵션을 넣으면 추가로 좌표를 활용한 이미지를 저장할 수 있다. 여러개 넣어도 폴더가 따로 생성되며 전부 저장된다.</p>
<blockquote>
<p>--save-text
--save-crop</p>
</blockquote>
<p>아래는 코드 예시이다.</p>
<pre><code class="language-python">from IPython.display import Image
import os

!python /content/drive/MyDrive/yolov5/detect.py --weights /content/carplate_best.pt --source /content/drive/MyDrive/crop_img/temp/same_data_masking --save-crop</code></pre>
<pre><code class="language-python">from IPython.display import Image
import os

!python /content/drive/MyDrive/yolov5/detect.py --weights /content/carplate_best.pt --source /content/drive/MyDrive/crop_img/temp/same_data_masking --save-text</code></pre>
<p><img src="https://velog.velcdn.com/images/t_wave/post/5b7cd087-117c-4be1-ac4e-27519462f205/image.png" alt=""></p>
<p>왼쪽이 yolo 실행 전, 오른쪽이 yolo 실행 후다.</p>
<p><img src="https://velog.velcdn.com/images/t_wave/post/75cbda1d-d2d7-4ccb-b979-41fe60451935/image.png" alt=""></p>
<p>저장된 사진의 위치는 python log 마지막에 나온다.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[[OpenCV] 코랩으로 이미지 Crop(자르기), Mask(가리기)하기]]></title>
            <link>https://velog.io/@t_wave/OpenCV-%EC%BD%94%EB%9E%A9%EC%9C%BC%EB%A1%9C-%EC%9D%B4%EB%AF%B8%EC%A7%80-Crop%EC%9E%90%EB%A5%B4%EA%B8%B0-Mask%EA%B0%80%EB%A6%AC%EA%B8%B0%ED%95%98%EA%B8%B0</link>
            <guid>https://velog.io/@t_wave/OpenCV-%EC%BD%94%EB%9E%A9%EC%9C%BC%EB%A1%9C-%EC%9D%B4%EB%AF%B8%EC%A7%80-Crop%EC%9E%90%EB%A5%B4%EA%B8%B0-Mask%EA%B0%80%EB%A6%AC%EA%B8%B0%ED%95%98%EA%B8%B0</guid>
            <pubDate>Fri, 20 Jan 2023 13:41:05 GMT</pubDate>
            <description><![CDATA[<h1 id="목표">#목표</h1>
<ul>
<li>라벨링한 이미지 1000장으로 Crop과 Mask를 한다.</li>
<li>클래스 0은 차량 번호판으로, Mask할 것이다.</li>
<li>클래스 1, 2는 각각 차량 앞면부와 차량 전체로, Crop할 것이다.</li>
</ul>
<p>환경은 코랩으로 해보았다. </p>
<p>이미지 업로드와 다운로드하는데 시간이 오래걸리니 <strong>로컬</strong>(주피터 노트북 등등)에서 돌리는게 낫다. </p>
<p>나는 로컬에 cv2 설정하다가 안돼서 그냥 코랩에 돌렸다.
코랩에서는 imshow()도 안되니 더더욱 로컬에서 돌리자.</p>
<h1 id="1-구글-드라이브-연결하기">1. 구글 드라이브 연결하기</h1>
<p><img src="https://velog.velcdn.com/images/t_wave/post/37bebe4e-b651-459c-944d-00eb00b494d2/image.png" alt="">    </p>
<p>구글 드라이브에 이미지 압축파일을 업로드 한다.
압축 안해서 올리면 엄청 오래걸린다. 압축파일을 올린 후 밑에서 코드로 압축을 해제해준다.</p>
<p><img src="https://velog.velcdn.com/images/t_wave/post/1356c3a8-934c-45a6-8c3a-97d909015c97/image.png" alt="">   </p>
<p>그리고 코랩에 연결해준다. (버튼 클릭 후 생성된 코드 실행)</p>
<h1 id="2-이미지-리스트-불러오기--정렬">2. 이미지 리스트 불러오기 &amp; 정렬</h1>
<pre><code class="language-python">import os

base_path = &#39;/content/drive/MyDrive/yolov5/data/car+plate+carfront/&#39;
img_path = base_path + &#39;images&#39;
label_path = base_path + &#39;labels&#39;

img_file_list = os.listdir(img_path)
label_file_list = os.listdir(label_path)</code></pre>
<p>나는 하나의 디렉토리에 images와 labels를 분리해서 넣었다.
os.listdir로 각각 폴더 위치를 지정해 폴더의 하위 파일 이름을 리스트로 가져온다.</p>
<pre><code class="language-python">img_file_list.sort()
label_file_list.sort()

print(&#39;img len: {}, label len: {}\n&#39;.format(len(img_file_list), len(label_file_list)))
print(img_file_list[:100])
print(label_file_list[:100])</code></pre>
<blockquote>
<p>그리고 리스트를 <strong>정렬해준다</strong>. </p>
</blockquote>
<p>정렬 안하면 밑에서 이미지와 라벨 짝이 안맞아 이상 부분을 crop &amp; mask한다.</p>
<h2 id="2_1-검수코드">2_1. 검수코드</h2>
<pre><code class="language-python">## 이미지 파일과 라벨 파일 이름 똑같음
cnt=0

for a, b in zip (img_file_list, label_file_list):
  a = a.rstrip(&#39;jpgtxt&#39;)
  a = a.rstrip(&#39;.&#39;)
  b = b.rstrip(&#39;jpgtxt&#39;)
  b = b.rstrip(&#39;.&#39;)
  if a != b: print(cnt)
  cnt+=1</code></pre>
<ul>
<li>이미지와 라벨 짝이 맞는지 검사하는 코드다.</li>
</ul>
<p>아무것도 출력이 안되면 잘 짝이 맞는 것이다.
짝이 안맞을 경우 해당 인덱스를 출력한다.</p>
<h1 id="3-이미지-좌표-구하기">3. 이미지 좌표 구하기</h1>
<pre><code class="language-python">import cv2

cnt = 0

for img_file, label_file in zip(img_file_list, label_file_list):

    cnt += 1
    print(cnt)


    img = cv2.imread(img_path + &#39;/&#39; + img_file)

    with open(label_path + &#39;/&#39; + label_file, &#39;r&#39;) as f:
        labels = f.readlines()
        car_whole = list(map(float, labels[2].split()[1:]))  ## 차 전체 이미지 바운딩박스
        car_front = list(map(float, labels[1].split()[1:]))  ## 차 앞부분 이미지 바운딩박스
        carplate_masking = list(map(float, labels[0].split()[1:]))  ## 차 앞부분 이미지 바운딩박스
        h, w, _ = img.shape  ## 사진 전체 크기 (heigt, width)</code></pre>
<p>우선 for문에서 이미지 파일과 라벨 파일을 정렬한 순서대로 하나씩 선택한다.
cv2.imread로 img에 이미지를 저장한다.
with open~ 으로 f에 라벨을 저장한다.</p>
<pre><code class="language-python">  ## 라벨 예시
  0 0.281785 0.303789 0.193897 0.092345
  1 0.425525 0.287692 0.581912 0.378795
  2 0.455954 0.285394 0.730389 0.509354</code></pre>
<ul>
<li>라벨 설명<ul>
<li>라벨 클래스는 0(자동차 번호판), 1(자동차 앞면 그릴), 2(자동차 전체)이다.</li>
<li>뒤에 소수점으로 표시된 좌표는 각각 (바운딩박스 중점 x좌표, 바운딩박스 중점 y좌표, 바운딩박스 가로 넓이, 바운딩박스 세로 넓이) 이다.</li>
<li>전부 소수점으로, 사진 전체 중 비율을 기준으로 라벨링이 되어있다. </li>
</ul>
</li>
</ul>
<p>각각 list로 변수에 좌표를 저장한다.</p>
<h2 id="3_1-좌표-진짜로-구하기">3_1. 좌표 진짜로 구하기</h2>
<p>원하는 부분을 사각형모양으로 crop하거나 mask하려면 왼쪽 위 꼭짓점 좌표와 오른쪽 아래 꼭짓점 좌표를 알아야한다.       </p>
<p>다음은 이를 계산한 좌표다.</p>
<pre><code class="language-python">starty = car_whole[1] - (car_whole[3] / 2)
        startx = car_whole[0] - (car_whole[2] / 2)
        endy = car_whole[1] + (car_whole[3] / 2)
        endx = car_whole[0] + (car_whole[2] / 2)</code></pre>
<p>그리고 비율이 0과 1을 넘지 않도록 다음 함수를 통과시켜준다.</p>
<pre><code class="language-python">def size_control(sx, sy, ex, ey):
  if sx &lt; 0: sx = 0
  if sy &lt; 0: sy = 0
  if ex &gt; 1: ex = 1
  if ey &gt; 1: ey = 1
  return sx, sy, ex, ey</code></pre>
<p>합치면 다음과 같다.</p>
<pre><code class="language-python">def size_control(sx, sy, ex, ey):
  if sx &lt; 0: sx = 0
  if sy &lt; 0: sy = 0
  if ex &gt; 1: ex = 1
  if ey &gt; 1: ey = 1
  return sx, sy, ex, ey


starty = car_whole[1] - (car_whole[3] / 2)
        startx = car_whole[0] - (car_whole[2] / 2)
        endy = car_whole[1] + (car_whole[3] / 2)
        endx = car_whole[0] + (car_whole[2] / 2)

        startx, starty, endx, endy = size_control(startx, starty, endx, endy)</code></pre>
<h1 id="4-이미지-crop하기--저장">4. 이미지 Crop하기 &amp; 저장</h1>
<p>이미지 크롭은 슬라이싱으로 간단하게 할 수 있다.
이미지는 numpy array로 되어있어 슬라이싱이 가능하다.</p>
<pre><code class="language-python">
        whole_crop_img = img[int(starty * h):int(endy * h), int(startx * w):int(endx * w)]</code></pre>
<p>int 안붙이면 슬라이싱이 안먹힌다.
라벨링이 비율로 되어있으므로 h와 w를 곱해준다.</p>
<pre><code class="language-python">cv2.imwrite(&#39;/content/drive/MyDrive/crop_img/car_whole/&#39; + &#39;whole_crop_&#39; + img_file, whole_crop_img)</code></pre>
<p>그리고 저장해준다.</p>
<h1 id="5-이미지-mask하기">5. 이미지 mask하기</h1>
<p>이미지 마스크는 원하는 영역에 속이 꽉찬  사각형을 그려서 할 수 있다.</p>
<pre><code class="language-python">masking_img = img
        cv2.rectangle(masking_img, (int(startx * w), int(starty * h)), (int(endx * w), int(endy * h)),(255,255,255), -1)</code></pre>
<p>뒤에 (255,255,255)는 하얀색을 가리킨다. (0,0,0)으로 하면 까만색 사각형을 그린다.</p>
<pre><code class="language-python">cv2.imwrite(&#39;/content/drive/MyDrive/crop_img/carplate_masking/&#39; + &#39;carplate_masking_&#39; + img_file, masking_img)</code></pre>
<p>그리고 저장</p>
<h3 id="crop--mask-전체-코드">crop &amp; mask 전체 코드</h3>
<pre><code class="language-python">import cv2

cnt = 0

for img_file, label_file in zip(img_file_list, label_file_list):

    cnt += 1
    print(cnt)


    img = cv2.imread(img_path + &#39;/&#39; + img_file)

    with open(label_path + &#39;/&#39; + label_file, &#39;r&#39;) as f:
        labels = f.readlines()
        car_whole = list(map(float, labels[2].split()[1:]))  ## 차 전체 이미지 바운딩박스
        car_front = list(map(float, labels[1].split()[1:]))  ## 차 앞부분 이미지 바운딩박스
        carplate_masking = list(map(float, labels[0].split()[1:]))  ## 차 앞부분 이미지 바운딩박스
        h, w, _ = img.shape  ## 사진 전체 크기 (heigt, width)

        ### car_whole
        starty = car_whole[1] - (car_whole[3] / 2)
        startx = car_whole[0] - (car_whole[2] / 2)
        endy = car_whole[1] + (car_whole[3] / 2)
        endx = car_whole[0] + (car_whole[2] / 2)
        startx, starty, endx, endy = size_control(startx, starty, endx, endy)


        whole_crop_img = img[int(starty * h):int(endy * h), int(startx * w):int(endx * w)]
        try:
          cv2.imwrite(&#39;/content/drive/MyDrive/crop_img/car_whole/&#39; + &#39;whole_crop_&#39; + img_file, whole_crop_img)
        except:
          pass


        ### car_front
        starty = car_front[1] - (car_front[3] / 2)
        startx = car_front[0] - (car_front[2] / 2)
        endy = car_front[1] + (car_front[3] / 2)
        endx = car_front[0] + (car_front[2] / 2)
        startx, starty, endx, endy = size_control(startx, starty, endx, endy)

        carfront_crop_img = img[int(starty * h):int(endy * h), int(startx * w):int(endx * w)]
        try:
          cv2.imwrite(&#39;/content/drive/MyDrive/crop_img/car_front/&#39; + &#39;carfront_crop_&#39; + img_file, carfront_crop_img)
        except:
          pass


        ## carplate_masking
        starty = carplate_masking[1] - (carplate_masking[3] / 2)
        startx = carplate_masking[0] - (carplate_masking[2] / 2)
        endy = carplate_masking[1] + (carplate_masking[3] / 2)
        endx = carplate_masking[0] + (carplate_masking[2] / 2)

        masking_img = img
        cv2.rectangle(masking_img, (int(startx * w), int(starty * h)), (int(endx * w), int(endy * h)),(255,255,255), -1)
        cv2.imwrite(&#39;/content/drive/MyDrive/crop_img/carplate_masking/&#39; + &#39;carplate_masking_&#39; + img_file, masking_img)

        f.close()</code></pre>
<h4 id="욜로-사용해보는-post는-다음-포스트에서"><a href="https://velog.io/@t_wave/YOLOv5-%EC%9A%9C%EB%A1%9C%EB%A1%9C-%EC%BD%94%EB%9E%A9%EC%97%90%EC%84%9C-%EA%B0%9D%EC%B2%B4-%EC%9D%B8%EC%8B%9D%ED%95%98%EA%B8%B0">욜로 사용해보는 post는 다음 포스트에서</a></h4>
]]></description>
        </item>
        <item>
            <title><![CDATA[[YOLOv5] Custom Data Set 학습시키기 - colab 사용]]></title>
            <link>https://velog.io/@t_wave/YOLOv5-Custom-Data-Set-%ED%95%99%EC%8A%B5%EC%8B%9C%ED%82%A4%EA%B8%B0-colab-%EC%82%AC%EC%9A%A9</link>
            <guid>https://velog.io/@t_wave/YOLOv5-Custom-Data-Set-%ED%95%99%EC%8A%B5%EC%8B%9C%ED%82%A4%EA%B8%B0-colab-%EC%82%AC%EC%9A%A9</guid>
            <pubDate>Thu, 19 Jan 2023 13:05:35 GMT</pubDate>
            <description><![CDATA[<h2 id="1-구글-드라이브-연결">1. 구글 드라이브 연결</h2>
<pre><code class="language-python">from google.colab import drive
drive.mount(&#39;/content/drive&#39;)</code></pre>
<p><img src="https://velog.velcdn.com/images/t_wave/post/1a445455-48e0-4070-869a-90c095be7c06/image.png" alt=""></p>
<ul>
<li>구글 드라이브를 연결합니다.</li>
<li>저 버튼 누르면 자동으로 코드가 생깁니다.</li>
</ul>
<h2 id="2-파일-업로드-및-압축해제">2. 파일 업로드 및 압축해제</h2>
<p><img src="https://velog.velcdn.com/images/t_wave/post/93323274-6101-405f-8c7d-0f26a0b3456e/image.png" alt=""></p>
<ul>
<li>구글 드라이브에 들어가서 이미지와 라벨 압축 파일을 업로드합니다.</li>
<li>그냥 올려도 괜찮지만, 시간이 오래걸려서 압축파일로 업로드합니다.</li>
</ul>
<pre><code class="language-python"># 압축파일들 풀 폴더 지정 (폴더 새로 안생기고 파일 바로 풀림 주의)
%cd /content/drive/MyDrive/yolov5/data/images   
!unzip -qq &quot;/content/drive/MyDrive/temp/images.zip&quot;

%cd /content/drive/MyDrive/yolov5/data/labels
!unzip -qq &quot;/content/drive/MyDrive/temp/labels.zip&quot;</code></pre>
<ul>
<li>코드로 압축해제를 합니다.</li>
<li>%cd로 이동한 경로에 압축 파일이 풀립니다. (폴더가 따로 안생기니 미리 만든 폴더로 이동하세요.)</li>
<li>unzip -qq 뒤에 압축 해제할 파일 경로를 지정합니다.     </li>
</ul>
<h4 id="잘못-풀린-파일-삭제하기">잘못 풀린 파일 삭제하기</h4>
<pre><code class="language-python">import os
import glob

[os.remove(f) for f in glob.glob(&#39;/content/drive/MyDrive/yolov5/data/*.jpg&#39;)] #jpg파일
[os.remove(f) for f in glob.glob(&#39;/content/drive/MyDrive/yolov5/data/*.txt&#39;)] #txt파일</code></pre>
<ul>
<li>압축해제를 잘못한 경우 이 코드를 사용하면 됩니다.</li>
<li>경로상의 jpg, txt 파일을 모두 삭제합니다. (데이터셋이 아닌 txt폴더, jpg폴더도 모두 삭제되니 주의하세요.)</li>
</ul>
<h4 id="파일-개수-확인하기">파일 개수 확인하기</h4>
<pre><code class="language-python"># 이미지 파일 개수 확인

import glob 

path = &#39;/content/drive/MyDrive/yolov5/data/car+plate+carfront/images_1/*.jpg&#39;
file = glob.glob(path)

print(len(file))</code></pre>
<pre><code class="language-python"># 라벨 파일 개수 확인

import glob 

path = &#39;/content/drive/MyDrive/yolov5/data/car+plate+carfront/labels_1/*.txt&#39;
file = glob.glob(path)

print(len(file))</code></pre>
<ul>
<li>압축이 잘 풀렸나 코드로 확인해봅니다.</li>
</ul>
<h2 id="3-yolov5-깃-클론하기--환경설정">3. YOLOv5 깃 클론하기 &amp; 환경설정</h2>
<p><a href="https://github.com/ultralytics/yolov5">YOLOv5 GitHub 바로가기</a></p>
<pre><code class="language-python"># YOLOv5 깃 클론

%cd /content/drive/MyDrive
!git clone https://github.com/ultralytics/yolov5  # clone repo
%cd yolov5
!git reset --hard fbe67e465375231474a2ad80a4389efc77ecff99  # YOLOv5 브랜치 설정</code></pre>
<ul>
<li>깃헙에서 YOLOv5를 클론해옵니다.</li>
<li>YOLOv5도 계속 업데이트가 되기 때문에 에러를 방지하고자 브랜치를 임의로 설정합니다.</li>
</ul>
<pre><code class="language-python">%cd /content/drive/MyDrive/yolov5/
!pip install -r requirements.txt</code></pre>
<ul>
<li>requirements.txt에 YOLOv5를 돌리기 위한 라이브러리들이 적혀있습니다.</li>
<li>코드를 실행하면 필요한 파일을 전부 다운받습니다.</li>
</ul>
<h2 id="4-yaml파일-설정">4. yaml파일 설정</h2>
<pre><code class="language-python">%cat /content/drive/MyDrive/yolov5/data/data.yaml</code></pre>
<ul>
<li>yaml파일은 YOLOv5에게 학습시킬 파일과 class 등을 지정해주는 파일입니다.</li>
<li>왼쪽 파일창에서 drive &gt; MyDrive &gt; yolov5 &gt; data &gt; data.yaml파일 을 더블클릭합니다.</li>
<li>더블클릭하면 오른쪽에 메모장처럼 편집할 수 있는 창이 뜹니다.</li>
</ul>
<p><img src="https://velog.velcdn.com/images/t_wave/post/177e382c-dc13-467e-bb0b-cb4793265d53/image.png" alt=""></p>
<ul>
<li>names -&gt; 라벨 이름 설정 (순서대로)</li>
<li>nc -&gt; number of classes, class 개수</li>
<li>밑에는 train, val 이미지 데이터 경로 설정</li>
</ul>
<h4 id="에러-주의">에러 주의</h4>
<p>데이터 저장은 다음 사항을 준수해야합니다.</p>
<ol>
<li>이미지와 라벨은 이름이 같고 확장자만 달라야함. (img.jpg, img.txt)
2_1. 이미지와 라벨을 같은 폴더에 넣고 그걸 train, val 데이터 경로로 설정한다.
2_2. 같은 디렉토리 하위에 images폴더와 labels폴더를 만들고 그 안에 사진과 라벨을 저장한다(폴더이름 꼭 images, labels이어야함). 그리고 images폴더를 train, val 경로로 설정한다.
<img src="https://velog.velcdn.com/images/t_wave/post/3271b23c-0d6c-4c1b-8e2f-df4f0e06d171/image.png" alt=""></li>
</ol>
<h2 id="5-yolov5-학습">5. YOLOv5 학습</h2>
<pre><code class="language-python">%cd /content/drive/MyDrive/yolov5
!python /content/drive/MyDrive/yolov5/train.py --epochs 50 --data /content/drive/MyDrive/yolov5/data/data.yaml --name car+carfront+plate</code></pre>
<ul>
<li>첫번째 경로는 yolov5 하위의 train.py의 경로를 지정한다.</li>
<li>--epochs는 학습을 몇번 반복할지 결정한다.</li>
<li>--data는 yaml파일의 경로를 지정한다.</li>
<li>--name은 완성할 모델의 이름을 지정한다.</li>
<li>--weights는 시작할 기본 가중치 파일을 설정한다. 없으면 랜덤으로 정해진 값으로 시작한다.</li>
</ul>
<h2 id="6-결과-확인하기">6. 결과 확인하기</h2>
<pre><code class="language-python">%load_ext tensorboard
%tensorboard --logdir /content/drive/MyDrive/yolov5/runs/train/carfront_basic</code></pre>
<ul>
<li>--logdir 뒤에 모델의 위치를 지정하면 멋진 그래프가 나온다.</li>
</ul>
]]></description>
        </item>
    </channel>
</rss>