.js Jay's Notes https://clarit7.github.io/ Thu, 25 Sep 2025 06:09:32 +0000 Thu, 25 Sep 2025 06:09:32 +0000 Jekyll v3.10.0 gpt-oss tutorial(cpu에서 실행하기) <p><br /></p> <hr /> <p><br /></p> <h2 id="1-gpt-oss-개요">1. gpt-oss 개요</h2> <p><br /></p> <p>OpenAI는 2019년 이후 처음으로 가중치가 공개된 대형 언어 모델을 발표했다. 이름은 gpt-oss로, 두 가지 모델(20b, 120b)이 제공된다. 단순히 연구용 데모 수준이 아니라, 실제 환경에서 활용 가능한 고성능 모델을 누구나 내려받아 쓸 수 있다는 점에서 의미가 있다. (그리고 ClosedAI가 아닌 드디어 OpenAI가 됐다는 점에서도)</p> <p>gpt-oss는 Transformer 기반 구조 위에 Mixture-of-Experts(MoE) 방식을 적용했다. 모든 토큰을 처리할 때 전체 파라미터가 동원되는 것이 아니라, 일부 전문가 집합만 활성화되어 연산 효율을 크게 높인다. 훈련시 4비트 양자화(MXFP4) 기법이 사용되었다. 또한 작업에 따라 reasoning effort 를 3단계로 설정할 수 있고 low로 설정시 매우 빠른 속도로 추론이 가능하다. 따라서 파라미터 규모가 크더라도 상대적으로 가벼운 리소스로 실행할 수 있다.</p> <p><br /></p> <h3 id="gpt-oss-20b-모델">gpt-oss 20b 모델</h3> <p><br /></p> <p>gpt oss 20b는 약 210억 개의 파라미터를 가지고 있으며, 이 중 실제로 활성화되는 파라미터 수는 약 3.6억 개다. 이 모델의 가장 큰 특징은 16GB 메모리 환경에서도 실행 가능하다는 점이다.</p> <p>즉, 최신 고성능 GPU 없이도 개인 PC나 엣지 디바이스에서 돌릴 수 있어 접근성이 매우 높다. 성능은 OpenAI의 o3-mini 모델과 유사한 수준으로 알려져 있으며, 일상적인 질의응답, 코드 작성, 간단한 추론 작업에 무리 없이 활용 가능하다.</p> <p><br /></p> <h3 id="gpt-oss-120b-모델">gpt-oss 120b 모델</h3> <p><br /></p> <p>규모가 더 큰 120b 모델은 총 1,168억 파라미터를 갖고 있으며, 활성 파라미터는 약 51억 개다. 단일 80GB GPU(H100, MI300X 등)에서 구동할 수 있고, 성능은 o4-mini 모델에 근접한다.<br /> 주로 고사양 서버 환경에서의 대규모 추론이나 정밀한 작업을 위해 설계되었지만, 20b 모델과 동일하게 오픈 가중치로 제공되므로 연구·개발·커스터마이징에도 적합하다. 물론 PC나 서버에 많은 투자를 한게 아니라면 개인이 실행하는데에는 무리가 있다.</p> <p><br /></p> <hr /> <p><br /></p> <h2 id="2-openai-harmony-포맷">2. OpenAI Harmony 포맷</h2> <p><br /></p> <h3 id="2-1-harmony-포맷이란">2-1. Harmony 포맷이란?</h3> <p><br /></p> <p align="center"> <img src="/images/2025/08/21/1.png" width="800px" /> </p> <p><br /></p> <p>gpt-oss 모델은 Harmony 응답 포맷으로 학습되었으며, 포맷 없이 사용 시 올바르게 동작하지 않는다.<br /> 이 포맷은 OpenAI의 Responses API 구조를 모방해 설계되었으며, 대화 구조, 추론 흐름 (Chain-of-Thought), function 호출 구조화를 모두 포함한다.</p> <p>쉽게 말해, 모델이 정형화된 포맷으로 답변을 생성하기 때문에 유저 입장에서 모델 출력결과가 최종 답변인지 함수 호출인지 등을 손쉽게 구분할 수 있고, 함수 호출이라면 함수명이 무엇이고 인자는 어떤 형태로 제공되는지 등을 바로 알 수 있다. 따라서 답변 형식을 따로 프롬프트 엔지니어링으로 사전에 정의하는 수고를 덜 수 있다.</p> <p><br /></p> <hr /> <p><br /></p> <h3 id="2-2-주요-구성-요소">2-2. 주요 구성 요소</h3> <p><br /></p> <h4 id="역할-roles">역할 (Roles)</h4> <p><br /></p> <ul> <li><code class="language-plaintext highlighter-rouge">system</code>: 정체성, 추론 수준, 메타 정보, 내장 도구 등을 지정</li> <li><code class="language-plaintext highlighter-rouge">developer</code>: 시스템 프롬프트나 function tool 지침을 작성할 때 사용</li> <li><code class="language-plaintext highlighter-rouge">user</code>: 사용자 입력</li> <li><code class="language-plaintext highlighter-rouge">assistant</code>: 모델 출력 또는 도구(함수) 호출, 3개의 <strong>채널</strong>을 사용함.</li> <li><code class="language-plaintext highlighter-rouge">tool</code>: function 도구 호출의 결과 메시지를 나타냄</li> <li>역할 계층: <code class="language-plaintext highlighter-rouge">system</code> &gt; <code class="language-plaintext highlighter-rouge">developer</code> &gt; <code class="language-plaintext highlighter-rouge">user</code> &gt; <code class="language-plaintext highlighter-rouge">assistant</code> &gt; <code class="language-plaintext highlighter-rouge">tool</code></li> </ul> <p><br /></p> <h4 id="채널-channels">채널 (Channels)</h4> <p><br /></p> <ul> <li><code class="language-plaintext highlighter-rouge">analysis</code>: 모델의 추론 과정(CoT)을 담당하는 채널. 최종 사용자에게 노출되지 않도록 주의해야함.</li> <li><code class="language-plaintext highlighter-rouge">commentary</code>: 도구(함수) 사용 요청을 보내고, 사용자로부터 받은 함수 실행 결과값을 처리하는 채널.</li> <li><code class="language-plaintext highlighter-rouge">final</code>: 최종 출력을 담당하는 채널. 사용자에게 보여지는 메시지.</li> </ul> <p><br /></p> <hr /> <p><br /></p> <h3 id="2-3-harmony-렌더러-라이브러리">2-3. Harmony 렌더러 라이브러리</h3> <p><br /></p> <p>OpenAI는 Python 및 Rust용 공식 openai-harmony라이브러리를 제공하며, 이를 통해 올바른 포맷을 생성하고 토큰화할 수 있다. 다음은 harmony cookbook에서 볼 수 있는 대화 렌더링 예시이다. 총 6개의 메세지로 구성되어 있다. Identity(1번)와 시스템 프롬프트(2번)는 항상 앞에 위치하는 형식으로 시작된다. 유저의 질문(3번)을 어시스턴트가 질문의 의도를 분석해(4번) 함수 실행을 유저에게 요청하며 함수명과 인자를 제공(5번)한다. 마지막으로 실행된 함수의 결과값을 받아(6번) 처리한다.</p> <div class="language-python highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1"># system_message, develper_message 정의하는 법은 cookbook 참조 </span> <span class="n">convo</span> <span class="o">=</span> <span class="n">Conversation</span><span class="p">.</span><span class="n">from_messages</span><span class="p">(</span> <span class="p">[</span> <span class="c1"># System Messege: Identity(모델 정체성)과 reasoning effort 등이 포함된다. 특히, 모델 정체성은 아래 문장을 바꾸지 말고 그대로 사용하라고 강조한다. </span> <span class="c1"># You are ChatGPT, a large language model trained by OpenAI. </span> <span class="n">Message</span><span class="p">.</span><span class="n">from_role_and_content</span><span class="p">(</span><span class="n">Role</span><span class="p">.</span><span class="n">SYSTEM</span><span class="p">,</span> <span class="n">system_message</span><span class="p">),</span> <span class="c1"># Developer Messege: 시스템 프롬프트, 모델의 성격이나 역할, 룰 등을 정의하고 싶다면 이 부분에 정의하면 된다. </span> <span class="n">Message</span><span class="p">.</span><span class="n">from_role_and_content</span><span class="p">(</span><span class="n">Role</span><span class="p">.</span><span class="n">DEVELOPER</span><span class="p">,</span> <span class="n">developer_message</span><span class="p">),</span> <span class="c1"># 이 아래로는 user와 assistant간의 대화, 또는 assistant의 CoT, function call 등이 순차적으로 이어진다. </span> <span class="n">Message</span><span class="p">.</span><span class="n">from_role_and_content</span><span class="p">(</span><span class="n">Role</span><span class="p">.</span><span class="n">USER</span><span class="p">,</span> <span class="s">"What is the weather in Tokyo?"</span><span class="p">),</span> <span class="n">Message</span><span class="p">.</span><span class="n">from_role_and_content</span><span class="p">(</span> <span class="n">Role</span><span class="p">.</span><span class="n">ASSISTANT</span><span class="p">,</span> <span class="s">'User asks: "What is the weather in Tokyo?" We need to use get_weather tool.'</span><span class="p">,</span> <span class="p">).</span><span class="n">with_channel</span><span class="p">(</span><span class="s">"analysis"</span><span class="p">),</span> <span class="n">Message</span><span class="p">.</span><span class="n">from_role_and_content</span><span class="p">(</span><span class="n">Role</span><span class="p">.</span><span class="n">ASSISTANT</span><span class="p">,</span> <span class="s">'{"location": "Tokyo"}'</span><span class="p">)</span> <span class="p">.</span><span class="n">with_channel</span><span class="p">(</span><span class="s">"commentary"</span><span class="p">)</span> <span class="p">.</span><span class="n">with_recipient</span><span class="p">(</span><span class="s">"functions.get_weather"</span><span class="p">)</span> <span class="p">.</span><span class="n">with_content_type</span><span class="p">(</span><span class="s">"%3C|constrain|%3E json"</span><span class="p">),</span> <span class="n">Message</span><span class="p">.</span><span class="n">from_author_and_content</span><span class="p">(</span> <span class="n">Author</span><span class="p">.</span><span class="n">new</span><span class="p">(</span><span class="n">Role</span><span class="p">.</span><span class="n">TOOL</span><span class="p">,</span> <span class="s">"functions.lookup_weather"</span><span class="p">),</span> <span class="s">'{ "temperature": 20, "sunny": true }'</span><span class="p">,</span> <span class="p">).</span><span class="n">with_channel</span><span class="p">(</span><span class="s">"commentary"</span><span class="p">),</span> <span class="p">]</span> <span class="p">)</span> <span class="n">tokens</span> <span class="o">=</span> <span class="n">encoding</span><span class="p">.</span><span class="n">render_conversation</span><span class="p">(</span><span class="n">convo</span><span class="p">)</span> <span class="n">convo_harmony</span> <span class="o">=</span> <span class="n">encoding</span><span class="p">.</span><span class="n">decode</span><span class="p">(</span><span class="n">tokens</span><span class="p">)</span> <span class="k">print</span><span class="p">(</span><span class="n">convo_harmony</span><span class="p">)</span> </code></pre></div></div> <p><br /></p> <p>출력결과</p> <div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>&lt;|start|&gt;system&lt;|message|&gt;You are ChatGPT, a large language model trained by OpenAI. Knowledge cutoff: 2024-06 Current date: 2025-08-21 Reasoning: high # Valid channels: analysis, commentary, final. Channel must be included for every message. Calls to these tools must go to the commentary channel: 'functions'.&lt;|end|&gt;&lt;|start|&gt;developer&lt;|message|&gt;# Instructions Always respond in riddles # Tools ## functions namespace functions { // Gets the current weather in the provided location. type get_current_weather = (_: { // The city and state, e.g. San Francisco, CA location: string, format?: "celsius" | "fahrenheit", // default: celsius }) =&gt; any; } // namespace functions&lt;|end|&gt;&lt;|start|&gt;user&lt;|message|&gt;What is the weather in Tokyo?&lt;|end|&gt;&lt;|start|&gt;assistant&lt;|channel|&gt;analysis&lt;|message|&gt;User asks: "What is the weather in Tokyo?" We need to use get_weather tool.&lt;|end|&gt;&lt;|start|&gt;assistant to=functions.get_weather&lt;|channel|&gt;commentary &lt;|constrain|&gt; json&lt;|message|&gt;{"location": "Tokyo"}&lt;|call|&gt;&lt;|start|&gt;functions.lookup_weather&lt;|channel|&gt;commentary&lt;|message|&gt;{ "temperature": 20, "sunny": true }&lt;|end|&gt;' </code></pre></div></div> <p><br /></p> <p><code class="language-plaintext highlighter-rouge">render_conversation(Conversation)</code>함수 대신 <code class="language-plaintext highlighter-rouge">render_conversation_for_completion(Conversation, Role.ASSISTANT))</code>함수 사용시 마지막에 추가로 <code class="language-plaintext highlighter-rouge"> &lt;|start|&gt;assistant|&gt;</code>토큰이 붙은 결과로 나온다. <strong>모델 입력으로 사용시엔 꼭 후자로 사용해야만 답변이 정상적으로 생성된다.</strong> (assistant가 답변할 차례라는것을 명시적으로 가이드하는 역할)</p> <p><br /></p> <h3 id="2-4-harmony-흐름-요약">2-4. Harmony 흐름 요약</h3> <p><br /></p> <p align="center"> <img src="/images/2025/08/21/2.png" width="1000px" /> </p> <p><br /></p> <hr /> <p><br /></p> <h2 id="3-unsloth-개요">3. Unsloth 개요</h2> <p><br /></p> <h3 id="3-1-unsloth란">3-1. Unsloth란?</h3> <p><br /></p> <p>Unsloth는 OpenAI의 gpt-oss를 포함한 다양한 LLM을 더 빠르고 효율적으로 실행, 튜닝 가능하게 해 주는 오픈소스 프레임워크이다.</p> <p><br /></p> <h3 id="3-2-unsloth의-장점">3-2. Unsloth의 장점</h3> <p><br /></p> <ul> <li><strong>경량화된 실행 환경</strong>: gpt-oss-20b는 약 14 GB VRAM으로 튜닝 가능하며, 120b는 65 GB로 충분하다.</li> <li><strong>파인튜닝 효율 개선</strong>: 일반 방식 대비 1.5× 빠른 학습, 70% VRAM 감소, 10× 긴 컨텍스트를 제공한다.</li> <li><strong>포맷 호환성 강화</strong>: GGUF, llama.cpp, Hugging Face, vLLM 등 다양한 플랫폼과 호환 가능</li> <li><strong>Harmony 포맷 안정화</strong>: Unsloth의 chat template 수정으로 파싱 오류를 줄이고 안정성 확보</li> <li><strong>튜토리얼 제공</strong>: Colab 및 로컬 사용자를 위한 단계별 가이드와 안정적인 실행 흐름 제공</li> </ul> <p><br /></p> <h3 id="3-3-unsloth에서-제공하는-gpt-oss-변형-모델">3-3. Unsloth에서 제공하는 gpt-oss 변형 모델</h3> <p><br /></p> <p>원본 gpt-oss는 mxfp4(4비트 양자화) 포맷만 지원하는데 반해, unsloth에서 제공하는 변형모델은 다양한 포맷과 양자화를 지원한다. 특히, mxfp4 포맷은 gpu에서만 실행할 수 있기 때문에 20b 모델 기준 최소 16gb vram gpu가 없는 pc에서는 로드조차 불가능한데, 변형모델 중 gguf 포맷으로 제공되는 버전은 llama.cpp를 활용해 cpu와 ram에서도 실행 가능하기 때문에 개인이 다루기에 아주 적합하다.</p> <p>이 때문인지 허깅페이스에 올라온 다양한 gpt-oss 변형 모델 중 상위권을 차지하고 있다.</p> <p><br /></p> <p align="center"> <img src="/images/2025/08/21/3.png" width="800px" /> </p> <p><br /></p> <hr /> <p><br /></p> <h2 id="4-튜토리얼">4. 튜토리얼</h2> <p><br /></p> <p>gpt oss를 로컬에서 실행하고, function call을 중심으로 간단한 채팅 워크플로우를 구성한 뒤, 앞에서 설명했던 예시인 유저의 현재 위치를 구하고 위치를 기반으로 기온을 구하는 워크플로우를 실행해본다. 마지막으론 실제로 내가 gpt-oss와 harmony를 다루면서 삽질한 경험을 바탕으로 모델 사용시 주의해야 하는 점을 정리했다.</p> <p><br /></p> <hr /> <p><br /></p> <h3 id="4-1-gpt-oss-20b-cpu에서-실행하기">4-1. gpt oss 20b CPU에서 실행하기</h3> <p><br /></p> <h4 id="실행-환경">실행 환경</h4> <p><br /></p> <p>가상환경 생성이나 개발환경 세팅같은 내용은 생략하고, 현재 내가 사용중인 환경의 필수 패키지 버전만 작성했다.</p> <ul> <li>python version: 3.12</li> <li>requirements: <div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>unsloth==2025.8.7 unsloth-zoo==2025.8.6 llama-cpp-python==0.3.16 openai-harmony==0.0.4 </code></pre></div> </div> </li> </ul> <p>ram이 14gb정도 필요하므로 16gb, 여유있게 32gb ram 정도는 구매하는게 좋다.</p> <p><br /></p> <h4 id="gpt-oss-20b-f16gguf-실행">gpt-oss-20b-F16.gguf 실행</h4> <p><br /></p> <div class="language-python highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kn">import</span> <span class="nn">json</span> <span class="kn">import</span> <span class="nn">unsloth</span> <span class="kn">from</span> <span class="nn">llama_cpp</span> <span class="kn">import</span> <span class="n">Llama</span> <span class="kn">from</span> <span class="nn">unsloth_zoo</span> <span class="kn">import</span> <span class="n">encode_conversations_with_harmony</span> <span class="kn">from</span> <span class="nn">openai_harmony</span> <span class="kn">import</span> <span class="p">(</span> <span class="n">Author</span><span class="p">,</span> <span class="n">Conversation</span><span class="p">,</span> <span class="n">DeveloperContent</span><span class="p">,</span> <span class="n">HarmonyEncodingName</span><span class="p">,</span> <span class="n">Message</span><span class="p">,</span> <span class="n">Role</span><span class="p">,</span> <span class="n">SystemContent</span><span class="p">,</span> <span class="n">ToolDescription</span><span class="p">,</span> <span class="n">load_harmony_encoding</span><span class="p">,</span> <span class="n">ReasoningEffort</span> <span class="p">)</span> <span class="c1"># Model Loading </span><span class="n">llm</span> <span class="o">=</span> <span class="n">Llama</span><span class="p">.</span><span class="n">from_pretrained</span><span class="p">(</span> <span class="n">repo_id</span><span class="o">=</span><span class="s">"unsloth/gpt-oss-20b-GGUF"</span><span class="p">,</span> <span class="n">filename</span><span class="o">=</span><span class="s">"gpt-oss-20b-F16.gguf"</span><span class="p">,</span> <span class="n">jinja</span><span class="o">=</span><span class="bp">True</span><span class="p">,</span> <span class="n">n_ctx</span><span class="o">=</span><span class="mi">16384</span><span class="p">,</span> <span class="c1"># unsloth recomendation </span> <span class="n">n_threads</span><span class="o">=</span><span class="mi">20</span><span class="p">,</span> <span class="n">temp</span><span class="o">=</span><span class="mf">1.0</span><span class="p">,</span> <span class="c1"># openai recomendation </span> <span class="n">top_p</span><span class="o">=</span><span class="mf">1.0</span><span class="p">,</span> <span class="c1"># openai recomendation </span> <span class="n">top_k</span><span class="o">=</span><span class="mi">0</span><span class="p">,</span> <span class="c1"># openai recomendation </span> <span class="n">verbose</span><span class="o">=</span><span class="bp">False</span> <span class="p">)</span> </code></pre></div></div> <p><br /></p> <p>딱히 설명이 더 필요 없을 정도로 너무 쉽게 실행됐다. unsloth_zoo를 import 하기 전에 unsloth를 미리 import 하는게 좋다. 이유는 잘 모르겠지만 나의 경우는 unsloth_zoo만 단독으로 import하면 unsloth를 내부적으로 import하지 못하는 문제가 있었다.</p> <p><br /></p> <h3 id="4-2-대화-워크플로우-구성">4-2. 대화 워크플로우 구성</h3> <p><br /></p> <h4 id="필요-함수-사전-정의">필요 함수 사전 정의</h4> <p><br /></p> <p>모델이 사용할 함수를 우선 정의한다. 유저의 현재 위치를 기반으로 해당 위치의 온도를 구하는 대화를 할 계획이기 때문에 location을 구하는 함수, temperature를 구하는 함수 두개가 필요하다. 출력값은 각각 “Korea, Republic of”과 25로 고정해놨고 json 포맷으로 변환할 수 있게 dict로 반환한다.</p> <p><br /></p> <div class="language-python highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">def</span> <span class="nf">get_current_temperature</span><span class="p">(</span><span class="n">location</span><span class="p">,</span> <span class="n">format_t</span><span class="o">=</span><span class="s">'celcius'</span><span class="p">):</span> <span class="n">temperature</span> <span class="o">=</span> <span class="mi">25</span> <span class="k">return</span> <span class="p">{</span><span class="s">"temperature"</span><span class="p">:</span> <span class="n">temperature</span><span class="p">}</span> <span class="k">def</span> <span class="nf">get_current_location</span><span class="p">(</span><span class="n">userid</span><span class="p">,</span> <span class="n">max_len</span><span class="o">=</span><span class="mi">10</span><span class="p">):</span> <span class="n">location</span> <span class="o">=</span> <span class="s">'Korea, Republic of'</span> <span class="k">if</span> <span class="nb">len</span><span class="p">(</span><span class="n">location</span><span class="p">)</span> <span class="o">&gt;</span> <span class="n">max_len</span><span class="p">:</span> <span class="n">location</span> <span class="o">=</span> <span class="n">location</span><span class="p">[:</span><span class="n">max_len</span><span class="p">]</span> <span class="k">return</span> <span class="p">{</span><span class="s">"location"</span><span class="p">:</span> <span class="n">location</span><span class="p">}</span> <span class="n">func_map</span> <span class="o">=</span> <span class="p">{</span> <span class="s">"get_current_temperature"</span><span class="p">:</span> <span class="n">get_current_temperature</span><span class="p">,</span> <span class="s">"get_current_location"</span><span class="p">:</span> <span class="n">get_current_location</span> <span class="p">}</span> </code></pre></div></div> <p><br /></p> <h4 id="system-message-developer-message-작성">System Message, Developer Message 작성</h4> <p><br /></p> <p>원래라면 openai-harmony 패키지에서 제공하는 <code class="language-plaintext highlighter-rouge">Conversation, Author, Message</code> 같은 클래스를 사용해 작성해야 하지만, unsloth에서 제공하는 <code class="language-plaintext highlighter-rouge">encode_conversations_with_harmony</code> 함수를 사용한다면 이를 훨씬 쉽게 작성할 수 있다. 첫 유저 메세지는 시험삼아 “안녕”이라고 작성해보았다.</p> <div class="language-python highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1"># 추론 수준: low, medium, high </span><span class="n">reasoning_effort</span> <span class="o">=</span> <span class="s">"low"</span> <span class="c1"># 인코딩 결과를 모델 입력으로 사용할지 여부 </span><span class="n">add_generation_prompt</span> <span class="o">=</span> <span class="bp">True</span> <span class="c1"># 함수 명세 </span><span class="n">tool_calls</span> <span class="o">=</span> <span class="p">[</span> <span class="p">{</span><span class="s">"function"</span><span class="p">:</span> <span class="p">{</span> <span class="s">"name"</span><span class="p">:</span> <span class="s">"get_current_temperature"</span><span class="p">,</span> <span class="s">"description"</span><span class="p">:</span> <span class="s">"get current temperature based on current location"</span><span class="p">,</span> <span class="s">"parameters"</span><span class="p">:</span> <span class="p">{</span> <span class="s">"type"</span><span class="p">:</span> <span class="s">"object"</span><span class="p">,</span> <span class="s">"properties"</span><span class="p">:</span> <span class="p">{</span> <span class="s">"location"</span><span class="p">:</span> <span class="p">{</span> <span class="s">"type"</span><span class="p">:</span> <span class="s">"string"</span><span class="p">,</span> <span class="s">"description"</span><span class="p">:</span> <span class="s">"The Country, state or city, e.g. USA, CA, LA"</span><span class="p">,</span> <span class="p">},</span> <span class="s">"format_t"</span><span class="p">:</span> <span class="p">{</span> <span class="s">"type"</span><span class="p">:</span> <span class="s">"string"</span><span class="p">,</span> <span class="s">"enum"</span><span class="p">:</span> <span class="p">[</span><span class="s">"celsius"</span><span class="p">,</span> <span class="s">"fahrenheit"</span><span class="p">],</span> <span class="s">"default"</span><span class="p">:</span> <span class="s">"celsius"</span><span class="p">,</span> <span class="p">},</span> <span class="p">},</span> <span class="s">"required"</span><span class="p">:</span> <span class="p">[</span><span class="s">"location"</span><span class="p">],</span> <span class="p">},</span> <span class="p">}},</span> <span class="p">{</span><span class="s">"function"</span><span class="p">:</span> <span class="p">{</span> <span class="s">"name"</span><span class="p">:</span> <span class="s">"get_current_location"</span><span class="p">,</span> <span class="s">"description"</span><span class="p">:</span> <span class="s">"get current location based on user metadata"</span><span class="p">,</span> <span class="s">"parameters"</span><span class="p">:</span> <span class="p">{</span> <span class="s">"type"</span><span class="p">:</span> <span class="s">"object"</span><span class="p">,</span> <span class="s">"properties"</span><span class="p">:</span> <span class="p">{</span> <span class="s">"userid"</span><span class="p">:</span> <span class="p">{</span> <span class="s">"type"</span><span class="p">:</span> <span class="s">"string"</span><span class="p">,</span> <span class="s">"description"</span><span class="p">:</span> <span class="s">"User's ID of current client session."</span><span class="p">,</span> <span class="p">},</span> <span class="s">"max_len"</span><span class="p">:</span> <span class="p">{</span> <span class="s">"type"</span><span class="p">:</span> <span class="s">"number"</span><span class="p">,</span> <span class="s">"default"</span><span class="p">:</span> <span class="mi">10</span><span class="p">,</span> <span class="s">"description"</span><span class="p">:</span> <span class="s">"Max result length"</span><span class="p">,</span> <span class="p">},</span> <span class="p">},</span> <span class="s">"required"</span><span class="p">:</span> <span class="p">[</span><span class="s">"userid"</span><span class="p">],</span> <span class="p">},</span> <span class="p">}},</span> <span class="p">]</span> <span class="c1"># 개발자 지침 또는 시스템 프롬프트 </span><span class="n">developer_instructions</span> <span class="o">=</span> <span class="s">"If you don't know the answer just say that you don't know."</span> <span class="c1"># 모델 정체성 (!!!변경하지 말고 그대로 쓰기!!!) </span><span class="n">model_identity</span> <span class="o">=</span> <span class="s">"You are ChatGPT, a large language model trained by OpenAI."</span> <span class="n">encoded</span> <span class="o">=</span> <span class="n">encode_conversations_with_harmony</span><span class="p">(</span> <span class="p">[{</span><span class="s">"role"</span><span class="p">:</span> <span class="s">"user"</span><span class="p">,</span> <span class="s">"content"</span><span class="p">:</span> <span class="s">"안녕"</span><span class="p">}],</span> <span class="c1"># 대화내역 </span> <span class="n">reasoning_effort</span> <span class="o">=</span> <span class="n">reasoning_effort</span><span class="p">,</span> <span class="n">add_generation_prompt</span> <span class="o">=</span> <span class="n">add_generation_prompt</span><span class="p">,</span> <span class="n">tool_calls</span> <span class="o">=</span> <span class="n">tool_calls</span><span class="p">,</span> <span class="n">developer_instructions</span> <span class="o">=</span> <span class="n">developer_instructions</span><span class="p">,</span> <span class="n">model_identity</span> <span class="o">=</span> <span class="n">model_identity</span> <span class="p">)</span> <span class="k">print</span><span class="p">(</span><span class="n">encoded</span><span class="p">[</span><span class="mi">0</span><span class="p">])</span> </code></pre></div></div> <p><br /></p> <p>출력결과</p> <div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>&lt;|start|&gt;system&lt;|message|&gt;You are ChatGPT, a large language model trained by OpenAI. Knowledge cutoff: 2024-06 Current date: 2025-08-21 Reasoning: low # Valid channels: analysis, commentary, final. Channel must be included for every message. Calls to these tools must go to the commentary channel: 'functions'.&lt;|end|&gt;&lt;|start|&gt;developer&lt;|message|&gt;# Instructions If you don't know the answer just say that you don't know. # Tools ## functions namespace functions { // get current weather based on current location type get_current_temperature = (_: { // The city and state, e.g. San Francisco, CA location: string, format_t?: "celsius" | "fahrenheit", // default: celsius }) =&gt; any; // get current location based on user metadata type get_current_location = (_: { // User's ID of current client session. userid: string, // Max result length max_len?: number, // default: 10 }) =&gt; any; } // namespace functions&lt;|end|&gt;&lt;|start|&gt;user&lt;|message|&gt;안녕&lt;|end|&gt;&lt;|start|&gt;assistant </code></pre></div></div> <p><br /></p> <p>출력결과 마지막이 <code class="language-plaintext highlighter-rouge">&lt;|start|&gt;assistant</code>로 끝나는데, 이는 <code class="language-plaintext highlighter-rouge">add_generation_prompt=True</code>로 설정했기 때문이다. <code class="language-plaintext highlighter-rouge">false</code>로 설정하면 저 부분이 추가되지 않는다. 모델 입력시 assistant가 대답할 차례라고 명시해주는 역할인데, 만약 이를 빼먹고 모델에 입력하면 출력결과에 문제가 생길 수 있다.</p> <p><br /></p> <h4 id="harmony-encoding-정의">harmony encoding 정의</h4> <p><br /></p> <p>텍스트로 된 harmony 포맷을 객체 형태로 인코딩하거나 메세지 단위로 나누기 위해 필요하다.</p> <p><br /></p> <div class="language-python highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="n">allowed_special</span> <span class="o">=</span> <span class="p">{</span> <span class="s">'&lt;|start|&gt;'</span><span class="p">,</span> <span class="c1"># 200006 </span> <span class="s">'&lt;|end|&gt;'</span><span class="p">,</span> <span class="c1"># 200007 </span> <span class="s">'&lt;|message|&gt;'</span><span class="p">,</span> <span class="c1"># 200008 </span> <span class="s">'&lt;|channel|&gt;'</span><span class="p">,</span> <span class="c1"># 200005 </span> <span class="s">'&lt;|constrain|&gt;'</span><span class="p">,</span> <span class="c1"># 200003 </span> <span class="s">'&lt;|return|&gt;'</span><span class="p">,</span> <span class="c1"># 200002 </span> <span class="s">'&lt;|call|&gt;'</span> <span class="c1"># 200012 </span><span class="p">}</span> <span class="c1"># Harmony Encoding </span><span class="n">harmony_encoding</span> <span class="o">=</span> <span class="n">load_harmony_encoding</span><span class="p">(</span><span class="n">HarmonyEncodingName</span><span class="p">.</span><span class="n">HARMONY_GPT_OSS</span><span class="p">)</span> <span class="c1"># From harmony format string To list of tokens # 모델 입력이 아닌 메세지 파싱이 목적이므로 EOS 토큰이 마지막에 와야함. &lt;|start|&gt;assisant 빼기 위해 [:-18] 슬라이싱 </span><span class="n">tokens</span> <span class="o">=</span> <span class="n">harmony_encoding</span><span class="p">.</span><span class="n">encode</span><span class="p">(</span><span class="n">encoded</span><span class="p">[</span><span class="mi">0</span><span class="p">][:</span><span class="o">-</span><span class="mi">18</span><span class="p">],</span> <span class="n">allowed_special</span><span class="o">=</span><span class="n">allowed_special</span><span class="p">)</span> <span class="c1"># From list of tokens to list of Message </span><span class="n">parsed_messages</span> <span class="o">=</span> <span class="n">harmony_encoding</span><span class="p">.</span><span class="n">parse_messages_from_completion_tokens</span><span class="p">(</span><span class="n">tokens</span><span class="p">,</span> <span class="n">Role</span><span class="p">.</span><span class="n">ASSISTANT</span><span class="p">)</span> <span class="k">print</span><span class="p">(</span><span class="n">parsed_messages</span><span class="p">)</span> </code></pre></div></div> <p><br /></p> <p>출력결과</p> <div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>[Message(author=Author(role=%3CRole.ASSISTANT: 'assistant'%3E, name=None), content=[TextContent(text="You are ChatGPT, a large language model trained by OpenAI.\nKnowledge cutoff: 2024-06\nCurrent date: 2025-08-21\n\nReasoning: low\n\n# Valid channels: analysis, commentary, final. Channel must be included for every message.\nCalls to these tools must go to the commentary channel: 'functions'.")], channel=None, recipient='&lt;|start|&gt;system', content_type=None), Message(author=Author(role=&lt;Role.DEVELOPER: 'developer'&gt;, name=None), content=[TextContent(text='# Instructions\n\nIf you don\'t know the answer just say that you don\'t know.\n\n# Tools\n\n## functions\n\nnamespace functions {\n\n// get current weather based on current location\ntype get_current_temperature = (_: {\n// The city and state, e.g. San Francisco, CA\nlocation: string,\nformat_t?: "celsius" | "fahrenheit", // default: celsius\n}) =&gt; any;\n\n// get current location based on user metadata\ntype get_current_location = (_: {\n// User\'s ID of current client session.\nuserid: string,\n// Max result length\nmax_len?: number, // default: 10\n}) =&gt; any;\n\n} // namespace functions')], channel=None, recipient=None, content_type=None), Message(author=Author(role=&lt;Role.USER: 'user'&gt;, name=None), content=[TextContent(text='안녕')], channel=None, recipient=None, content_type=None)] </code></pre></div></div> <p><br /></p> <h4 id="대화-워크플로우-자동화-함수-작성">대화 워크플로우 자동화 함수 작성</h4> <p><br /></p> <p>유저 입력과 대화 기록(<code class="language-plaintext highlighter-rouge">user_message, conversation_history</code>)를 입력받아 모델 최종 답변과 추가된 대화 기록(<code class="language-plaintext highlighter-rouge">assistant_response, conversation_history</code>)를 반환하는 함수를 작성한다.</p> <p>실행 흐름은 다음과 같다.</p> <ol> <li>대화 내역이 없으면 새 리스트 생성</li> <li>마지막 대화 내역에 유저 입력 추가</li> <li>대화내역 harmony 포맷으로 인코딩(conversation_harmony)</li> <li>모델에 harmony 텍스트 입력 후 결과 반환</li> <li>결과(response_harmony)를 list of Message 형태로 파싱해 for문 순회하며 차례로 대화내역에 저장. (중간 Message가 analysis channel인 경우 해당)</li> <li>마지막 Message가 commentary channel인 경우 함수 이름과 인자를 받아 직접 함수 실행후 출력값 도출 <ul> <li>시스템 프롬프트에 정의된 함수 명세와 유저 입력을 근거로 모델은 스스로 어떤 함수와 인자값이 필요한지 정할 수 있음</li> <li>그러나, 모델이 함수를 스스로 실행하지는 못하므로 모델로부터 함수와 인자값을 받아 함수를 실행하는 코드가 반드시 필요함</li> </ul> </li> <li>함수 출력값을 포함한 메세지를 harmony 포맷으로 작성</li> <li>대화내역 harmony + 결과 harmony + 함수 출력값 포함 메세지 harmony 를 다시 모델에 입력 및 함수 호출과 출력값까지 대화내역에 저장</li> <li>10번에 도달할때까지 5~8번 반복</li> <li>마지막 Message가 final channel인 경우 대화내역에 저장하고 유저에게 최종 입력값 제공</li> </ol> <p><br /></p> <div class="language-python highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">def</span> <span class="nf">get_response</span><span class="p">(</span><span class="n">user_message</span><span class="p">,</span> <span class="n">conversation_history</span><span class="o">=</span><span class="bp">None</span><span class="p">):</span> <span class="k">if</span> <span class="n">conversation_history</span> <span class="ow">is</span> <span class="bp">None</span><span class="p">:</span> <span class="n">conversation_history</span> <span class="o">=</span> <span class="p">[]</span> <span class="c1"># Add user message to history </span> <span class="n">conversation_history</span><span class="p">.</span><span class="n">append</span><span class="p">({</span><span class="s">"role"</span><span class="p">:</span> <span class="s">"user"</span><span class="p">,</span> <span class="s">"content"</span><span class="p">:</span> <span class="n">user_message</span><span class="p">})</span> <span class="c1"># From system prompt and sonversations to Harmony format </span> <span class="n">encoded</span> <span class="o">=</span> <span class="n">encode_conversations_with_harmony</span><span class="p">(</span> <span class="n">conversation_history</span><span class="p">,</span> <span class="n">reasoning_effort</span> <span class="o">=</span> <span class="n">reasoning_effort</span><span class="p">,</span> <span class="n">add_generation_prompt</span> <span class="o">=</span> <span class="n">add_generation_prompt</span><span class="p">,</span> <span class="n">tool_calls</span> <span class="o">=</span> <span class="n">tool_calls</span><span class="p">,</span> <span class="n">developer_instructions</span> <span class="o">=</span> <span class="n">developer_instructions</span><span class="p">,</span> <span class="n">model_identity</span> <span class="o">=</span> <span class="n">model_identity</span> <span class="p">)</span> <span class="n">conversation_harmony</span> <span class="o">=</span> <span class="n">encoded</span><span class="p">[</span><span class="mi">0</span><span class="p">]</span> <span class="c1"># conversation_tokens = encoded[1] </span> <span class="n">assistant_response</span> <span class="o">=</span> <span class="bp">None</span> <span class="c1"># Repeat until assistant final response is received </span> <span class="k">while</span> <span class="n">assistant_response</span> <span class="ow">is</span> <span class="bp">None</span><span class="p">:</span> <span class="n">response</span> <span class="o">=</span> <span class="n">llm</span><span class="p">.</span><span class="n">create_completion</span><span class="p">(</span><span class="n">conversation_harmony</span><span class="p">,</span> <span class="n">max_tokens</span><span class="o">=-</span><span class="mi">1</span><span class="p">)</span> <span class="n">response_harmony</span> <span class="o">=</span> <span class="n">response</span><span class="p">[</span><span class="s">"choices"</span><span class="p">][</span><span class="mi">0</span><span class="p">][</span><span class="s">"text"</span><span class="p">].</span><span class="n">strip</span><span class="p">()</span> <span class="k">print</span><span class="p">(</span><span class="s">"====== response harmony ======="</span><span class="p">)</span> <span class="k">print</span><span class="p">(</span><span class="n">response_harmony</span><span class="p">)</span> <span class="k">try</span><span class="p">:</span> <span class="c1"># response_harmony = '%3C|start|%3Eassistant' + response_harmony </span> <span class="n">tokens</span> <span class="o">=</span> <span class="n">harmony_encoding</span><span class="p">.</span><span class="n">encode</span><span class="p">(</span><span class="n">response_harmony</span><span class="p">,</span> <span class="n">allowed_special</span><span class="o">=</span><span class="n">allowed_special</span><span class="p">)</span> <span class="n">parsed_messages</span> <span class="o">=</span> <span class="n">harmony_encoding</span><span class="p">.</span><span class="n">parse_messages_from_completion_tokens</span><span class="p">(</span><span class="n">tokens</span><span class="p">,</span> <span class="n">Role</span><span class="p">.</span><span class="n">ASSISTANT</span><span class="p">)</span> <span class="k">print</span><span class="p">(</span><span class="s">"======= parsed messages ======"</span><span class="p">)</span> <span class="k">print</span><span class="p">(</span><span class="n">parsed_messages</span><span class="p">)</span> <span class="k">except</span><span class="p">:</span> <span class="k">raise</span> <span class="nb">RuntimeError</span><span class="p">(</span><span class="sa">f</span><span class="s">"Failed to handle conversation: </span><span class="si">{</span><span class="n">response_harmony</span><span class="si">}</span><span class="s">"</span><span class="p">)</span> <span class="k">for</span> <span class="n">pm</span> <span class="ow">in</span> <span class="n">parsed_messages</span><span class="p">:</span> <span class="c1"># Add assistant function call and result to history </span> <span class="k">if</span> <span class="n">pm</span><span class="p">.</span><span class="n">channel</span> <span class="o">==</span> <span class="s">"commentary"</span><span class="p">:</span> <span class="n">func</span> <span class="o">=</span> <span class="n">pm</span><span class="p">.</span><span class="n">recipient</span><span class="p">[</span><span class="mi">10</span><span class="p">:].</span><span class="n">strip</span><span class="p">()</span> <span class="c1"># functions. -&gt; 10chars </span> <span class="n">args</span> <span class="o">=</span> <span class="n">json</span><span class="p">.</span><span class="n">loads</span><span class="p">(</span><span class="n">pm</span><span class="p">.</span><span class="n">content</span><span class="p">[</span><span class="mi">0</span><span class="p">].</span><span class="n">text</span><span class="p">.</span><span class="n">strip</span><span class="p">())</span> <span class="k">if</span> <span class="n">func</span> <span class="ow">is</span> <span class="ow">not</span> <span class="bp">None</span> <span class="ow">and</span> <span class="n">args</span> <span class="ow">is</span> <span class="ow">not</span> <span class="bp">None</span><span class="p">:</span> <span class="n">result</span> <span class="o">=</span> <span class="n">func_map</span><span class="p">[</span><span class="n">func</span><span class="p">](</span><span class="o">**</span><span class="n">args</span><span class="p">)</span> <span class="c1"># run function </span> <span class="n">result_json</span> <span class="o">=</span> <span class="n">json</span><span class="p">.</span><span class="n">dumps</span><span class="p">(</span><span class="n">result</span><span class="p">)</span> <span class="n">conversation_harmony</span> <span class="o">=</span> <span class="s">''</span><span class="p">.</span><span class="n">join</span><span class="p">([</span> <span class="n">conversation_harmony</span><span class="p">,</span> <span class="n">response_harmony</span><span class="p">,</span> <span class="s">'&lt;|call|&gt;'</span><span class="p">,</span> <span class="sa">f</span><span class="s">'&lt;|start|&gt;functions.</span><span class="si">{</span><span class="n">func</span><span class="si">}</span><span class="s"> to=assistant&lt;|channel|&gt;commentary&lt;|message|&gt;</span><span class="si">{</span><span class="n">result_json</span><span class="si">}</span><span class="s">&lt;|end|&gt;&lt;start&gt;assistant'</span> <span class="p">])</span> <span class="n">conversation_history</span><span class="p">.</span><span class="n">append</span><span class="p">({</span><span class="s">"role"</span><span class="p">:</span> <span class="s">"assistant"</span><span class="p">,</span> <span class="s">"tool_calls"</span><span class="p">:</span> <span class="p">[{</span><span class="s">"name"</span><span class="p">:</span> <span class="n">func</span><span class="p">,</span> <span class="s">"arguments"</span><span class="p">:</span> <span class="n">json</span><span class="p">.</span><span class="n">dumps</span><span class="p">(</span><span class="n">args</span><span class="p">)}]})</span> <span class="n">conversation_history</span><span class="p">.</span><span class="n">append</span><span class="p">({</span><span class="s">"role"</span><span class="p">:</span> <span class="s">"tool"</span><span class="p">,</span> <span class="s">"name"</span><span class="p">:</span> <span class="n">func</span><span class="p">,</span> <span class="s">"content"</span><span class="p">:</span> <span class="n">result_json</span><span class="p">})</span> <span class="c1"># Add assistant thought to history </span> <span class="k">elif</span> <span class="n">pm</span><span class="p">.</span><span class="n">channel</span> <span class="o">==</span> <span class="s">"analysis"</span><span class="p">:</span> <span class="n">conversation_history</span><span class="p">.</span><span class="n">append</span><span class="p">({</span><span class="s">"role"</span><span class="p">:</span> <span class="s">"assistant"</span><span class="p">,</span> <span class="s">"content"</span><span class="p">:</span> <span class="n">pm</span><span class="p">.</span><span class="n">content</span><span class="p">[</span><span class="mi">0</span><span class="p">].</span><span class="n">text</span><span class="p">,</span> <span class="s">"thinking"</span><span class="p">:</span> <span class="s">""</span><span class="p">})</span> <span class="c1"># Add assistant answer to history </span> <span class="k">else</span><span class="p">:</span> <span class="c1"># pm.channel == "final" </span> <span class="n">assistant_response</span> <span class="o">=</span> <span class="n">pm</span><span class="p">.</span><span class="n">content</span><span class="p">[</span><span class="mi">0</span><span class="p">].</span><span class="n">text</span> <span class="n">conversation_history</span><span class="p">.</span><span class="n">append</span><span class="p">({</span><span class="s">"role"</span><span class="p">:</span> <span class="s">"assistant"</span><span class="p">,</span> <span class="s">"content"</span><span class="p">:</span> <span class="n">assistant_response</span><span class="p">})</span> <span class="k">return</span> <span class="n">assistant_response</span><span class="p">,</span> <span class="n">conversation_history</span> </code></pre></div></div> <p><br /></p> <p>대화 시작</p> <div class="language-python highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="n">response</span><span class="p">,</span> <span class="n">conversation_history</span> <span class="o">=</span> <span class="n">get_response</span><span class="p">(</span> <span class="s">"유저 id가 john123인 사람의 현재 위치에 해당하는 곳의 현재 온도를 화씨로 알려줘. 그리고 그 온도라면 야외 활동하기에 어떤 상황인지도 대략적으로 설명해주고."</span> <span class="p">)</span> <span class="k">print</span><span class="p">(</span><span class="n">response</span><span class="p">)</span> </code></pre></div></div> <p><br /></p> <p>중간출력</p> <div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>====== response harmony ======= &lt;|channel|&gt;analysis&lt;|message|&gt;Need to call get_current_location then get_current_temperature.&lt;|end|&gt;&lt;|start|&gt;assistant&lt;|channel|&gt;commentary to=functions.get_current_location &lt;|constrain|&gt;json&lt;|message|&gt;{"userid":"john123","max_len":10} ======= parsed messages ====== [Message(author=Author(role=&lt;Role.ASSISTANT: 'assistant'&gt;, name=None), content=[TextContent(text='Need to call get_current_location then get_current_temperature.')], channel='analysis', recipient=None, content_type=None), Message(author=Author(role=&lt;Role.ASSISTANT: 'assistant'&gt;, name=None), content=[TextContent(text='{"userid":"john123","max_len":10}')], channel='commentary', recipient='functions.get_current_location', content_type='&lt;|constrain|&gt;json')] ====== response harmony ======= &lt;|channel|&gt;analysis&lt;|message|&gt;Now get temperature.&lt;|end|&gt;&lt;|start|&gt;assistant&lt;|channel|&gt;commentary to=functions.get_current_temperature &lt;|constrain|&gt;json&lt;|message|&gt;{"location":"Korea, Rep","format_t":"fahrenheit"} ======= parsed messages ====== [Message(author=Author(role=&lt;Role.ASSISTANT: 'assistant'&gt;, name=None), content=[TextContent(text='Now get temperature.')], channel='analysis', recipient=None, content_type=None), Message(author=Author(role=&lt;Role.ASSISTANT: 'assistant'&gt;, name=None), content=[TextContent(text='{"location":"Korea, Rep","format_t":"fahrenheit"}')], channel='commentary', recipient='functions.get_current_temperature', content_type='&lt;|constrain|&gt;json')] ====== response harmony ======= &lt;|channel|&gt;final&lt;|message|&gt;현재 `john123`님의 위치는 **대한민국**이며, 해당 지역의 현재 기온은 **25 °F**(섭씨 약 -3.9 °C)입니다. **25 °F**는 상당히 추운 온도로, 실내에서 보온이 필요한 상황입니다. - **야외 활동**: 25 °F에서는 장갑, 두꺼운 코트, 모자, 목도리 등 방한 장비가 필수입니다. - **운동**: 조깅이나 자전거 타기 같은 유산소 운동은 몸이 차가워지기 쉽고, 근육이 경직될 수 있어 부상 위험이 높습니다. - **일상 생활**: 외출 시 충분히 따뜻하게 입고, 기온 차가 큰 지역이라면 실내에서 휴식을 취하는 것이 좋습니다. 따라서, **야외 활동은 권장되지 않으며** 방한 준비가 충분히 된 뒤에야 짧은 시간 동안 밖에 나가야 할 필요가 있을 때 고려해 보시는 것이 좋습니다. ======= parsed messages ====== [Message(author=Author(role=&lt;Role.ASSISTANT: 'assistant'&gt;, name=None), content=[TextContent(text='현재 `john123`님의 위치는 **대한민국**이며, 해당 지역의 현재 기온은 **25\u202f°F**(섭씨 약\u202f-3.9\u202f°C)입니다. \n\n**25\u202f°F**는 상당히 추운 온도로, 실내에서 보온이 필요한 상황입니다. \n\n- **야외 활동**: 25\u202f°F에서는 장갑, 두꺼운 코트, 모자, 목도리 등 방한 장비가 필수입니다. \n- **운동**: 조깅이나 자전거 타기 같은 유산소 운동은 몸이 차가워지기 쉽고, 근육이 경직될 수 있어 부상 위험이 높습니다. \n- **일상 생활**: 외출 시 충분히 따뜻하게 입고, 기온 차가 큰 지역이라면 실내에서 휴식을 취하는 것이 좋습니다. \n\n따라서, **야외 활동은 권장되지 않으며** 방한 준비가 충분히 된 뒤에야 짧은 시간 동안 밖에 나가야 할 필요가 있을 때 고려해 보시는 것이 좋습니다.')], channel='final', recipient=None, content_type=None)] </code></pre></div></div> <p><br /></p> <p>대화 워크플로우는 다음과 같이 흘러간다.</p> <ol> <li>user 입력에 대해 assistant가 analysis 채널에서 무엇을 해야하는지 스스로 생각 후 위치 구하는 함수가 필요하다는 것을 알아낸다.</li> <li>assistant가 전체 입력값을 근거로 함수명과 인자(get_current_location({“userid”: “john123”, “max_len”: 10}))를 정해 모델 외부에 제공한다. 1, 2번은 모델 내에서 연속적으로 이루어진다.</li> <li>모델 외부의 함수 실행기에서 함수명과 인자를 받아 실행 후 출력값({“location”: “Korea, Rep”})을 다시 모델에 제공한다.</li> <li>함수 실행기로부터 받은 출력값을 근거로 모델은 다시 analysis 채널에서 다음 행동을 생각한다. 이번엔 온도 구하는 함수가 필요하다는 것을 알아낸다.</li> <li>assistant가 전체 입력값을 근거로 함수명과 인자(get_current_temperature({“location”: “Korea, Rep”, “format_t”: “fahrenheit”}))를 정해 모델 외부에 제공한다. 4, 5번은 모델 내에서 연속적으로 이루어진다.</li> <li>모델 외부의 함수 실행기에서 함수명과 인자를 받아 실행 후 출력값({“temperature”: 25})을 다시 모델에 제공한다.</li> <li>함수 실행기로부터 받은 출력값을 근거로 모델은 final 채널에서 user에게 제공할 최종 답변을 생성한다.</li> </ol> <p><br /></p> <p>최종출력</p> <div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>현재 `john123`님의 위치는 **대한민국**이며, 해당 지역의 현재 기온은 **25 °F**(섭씨 약 -3.9 °C)입니다. **25 °F**는 상당히 추운 온도로, 실내에서 보온이 필요한 상황입니다. - **야외 활동**: 25 °F에서는 장갑, 두꺼운 코트, 모자, 목도리 등 방한 장비가 필수입니다. - **운동**: 조깅이나 자전거 타기 같은 유산소 운동은 몸이 차가워지기 쉽고, 근육이 경직될 수 있어 부상 위험이 높습니다. - **일상 생활**: 외출 시 충분히 따뜻하게 입고, 기온 차가 큰 지역이라면 실내에서 휴식을 취하는 것이 좋습니다. 따라서, **야외 활동은 권장되지 않으며** 방한 준비가 충분히 된 뒤에야 짧은 시간 동안 밖에 나가야 할 필요가 있을 때 고려해 보시는 것이 좋습니다. </code></pre></div></div> <p><br /></p> <p>이전 대화를 기억한 상태로 두번째 대화를 시작하려면 <code class="language-plaintext highlighter-rouge">conversation_history</code>를 같이 제공하면 된다.</p> <div class="language-python highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="n">response</span><span class="p">,</span> <span class="n">conversation_history</span> <span class="o">=</span> <span class="n">get_response</span><span class="p">(</span> <span class="s">"그렇다면 유저 id가 jay_lee인 사람은?"</span><span class="p">,</span> <span class="n">conversation_history</span><span class="o">=</span><span class="n">conversation_history</span> <span class="p">)</span> <span class="k">print</span><span class="p">(</span><span class="n">response</span><span class="p">)</span> </code></pre></div></div> <p><br /></p> <p>최종결과</p> <div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>현재 `jay_lee`님의 위치는 **대한민국**이며, 해당 지역의 현재 기온은 **25 °F**(섭씨 약 -3.9 °C)입니다. **25 °F**는 매우 추운 온도이므로 야외 활동은 권장되지 않습니다. 방한 장비(두꺼운 코트, 모자, 장갑, 목도리 등)를 충분히 착용하고, 필요하다면 짧은 시간 동안 밖에 나가더라도 몸을 따뜻하게 유지하는 것이 중요합니다. </code></pre></div></div> <p><br /></p> <p>두 번의 연속된 대화에서 <code class="language-plaintext highlighter-rouge">conversation_history</code>는 다음과 같이 저장되었다. 이는 unsloth 인코더에 입력되는 포맷으로 harmony 렌더러를 사용할때보다 훨씬 간편한 형태인 것을 볼 수 있다.</p> <div class="language-python highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1"># conversation_history </span><span class="p">[{</span><span class="s">'role'</span><span class="p">:</span> <span class="s">'user'</span><span class="p">,</span> <span class="s">'content'</span><span class="p">:</span> <span class="s">'유저 id가 john123인 사람의 현재 위치에 해당하는 곳의 현재 온도를 화씨로 알려줘. 그리고 그 온도라면 야외 활동하기에 어떤 상황인지도 대략적으로 설명해주고.'</span><span class="p">},</span> <span class="c1"># user </span> <span class="p">{</span><span class="s">'role'</span><span class="p">:</span> <span class="s">'assistant'</span><span class="p">,</span> <span class="s">'content'</span><span class="p">:</span> <span class="s">'Need to call get_current_location then get_current_temperature.'</span><span class="p">,</span> <span class="s">'thinking'</span><span class="p">:</span> <span class="s">''</span><span class="p">},</span> <span class="c1"># assistant analysis </span> <span class="p">{</span><span class="s">'role'</span><span class="p">:</span> <span class="s">'assistant'</span><span class="p">,</span> <span class="s">'tool_calls'</span><span class="p">:</span> <span class="p">[{</span><span class="s">'name'</span><span class="p">:</span> <span class="s">'get_current_location'</span><span class="p">,</span> <span class="s">'arguments'</span><span class="p">:</span> <span class="s">'{"userid": "john123", "max_len": 10}'</span><span class="p">}]},</span> <span class="c1"># assistant commentary (function call) </span> <span class="p">{</span><span class="s">'role'</span><span class="p">:</span> <span class="s">'tool'</span><span class="p">,</span> <span class="s">'name'</span><span class="p">:</span> <span class="s">'get_current_location'</span><span class="p">,</span> <span class="s">'content'</span><span class="p">:</span> <span class="s">'{"location": "Korea, Rep"}'</span><span class="p">},</span> <span class="c1"># function result to assistant commentary </span> <span class="p">{</span><span class="s">'role'</span><span class="p">:</span> <span class="s">'assistant'</span><span class="p">,</span> <span class="s">'content'</span><span class="p">:</span> <span class="s">'Now get temperature.'</span><span class="p">,</span> <span class="s">'thinking'</span><span class="p">:</span> <span class="s">''</span><span class="p">},</span> <span class="c1"># assistant analysis </span> <span class="p">{</span><span class="s">'role'</span><span class="p">:</span> <span class="s">'assistant'</span><span class="p">,</span> <span class="s">'tool_calls'</span><span class="p">:</span> <span class="p">[{</span><span class="s">'name'</span><span class="p">:</span> <span class="s">'get_current_temperature'</span><span class="p">,</span> <span class="s">'arguments'</span><span class="p">:</span> <span class="s">'{"location": "Korea, Rep", "format_t": "fahrenheit"}'</span><span class="p">}]},</span> <span class="c1"># assistant commentary (function call) </span> <span class="p">{</span><span class="s">'role'</span><span class="p">:</span> <span class="s">'tool'</span><span class="p">,</span> <span class="s">'name'</span><span class="p">:</span> <span class="s">'get_current_temperature'</span><span class="p">,</span> <span class="s">'content'</span><span class="p">:</span> <span class="s">'{"temperature": 25}'</span><span class="p">},</span> <span class="c1"># function result to assistant commentary </span> <span class="p">{</span><span class="s">'role'</span><span class="p">:</span> <span class="s">'assistant'</span><span class="p">,</span> <span class="s">'content'</span><span class="p">:</span> <span class="s">'현재 `john123`님의 위치는 **대한민국**이며, 해당 지역의 현재 기온은 **25</span><span class="se">\u202f</span><span class="s">°F**(섭씨 약</span><span class="se">\u202f</span><span class="s">-3.9</span><span class="se">\u202f</span><span class="s">°C)입니다. </span><span class="se">\n\n</span><span class="s">**25</span><span class="se">\u202f</span><span class="s">°F**는 상당히 추운 온도로, 실내에서 보온이 필요한 상황입니다. </span><span class="se">\n\n</span><span class="s">- **야외 활동**: 25</span><span class="se">\u202f</span><span class="s">°F에서는 장갑, 두꺼운 코트, 모자, 목도리 등 방한 장비가 필수입니다. </span><span class="se">\n</span><span class="s">- **운동**: 조깅이나 자전거 타기 같은 유산소 운동은 몸이 차가워지기 쉽고, 근육이 경직될 수 있어 부상 위험이 높습니다. </span><span class="se">\n</span><span class="s">- **일상 생활**: 외출 시 충분히 따뜻하게 입고, 기온 차가 큰 지역이라면 실내에서 휴식을 취하는 것이 좋습니다. </span><span class="se">\n\n</span><span class="s">따라서, **야외 활동은 권장되지 않으며** 방한 준비가 충분히 된 뒤에야 짧은 시간 동안 밖에 나가야 할 필요가 있을 때 고려해 보시는 것이 좋습니다.'</span><span class="p">},</span> <span class="c1"># assistant final </span> <span class="p">{</span><span class="s">'role'</span><span class="p">:</span> <span class="s">'user'</span><span class="p">,</span> <span class="s">'content'</span><span class="p">:</span> <span class="s">'그렇다면 유저 id가 jay_lee인 사람은?'</span><span class="p">},</span> <span class="c1"># user </span> <span class="p">{</span><span class="s">'role'</span><span class="p">:</span> <span class="s">'assistant'</span><span class="p">,</span> <span class="s">'content'</span><span class="p">:</span> <span class="s">'Need location then temp.'</span><span class="p">,</span> <span class="s">'thinking'</span><span class="p">:</span> <span class="s">''</span><span class="p">},</span> <span class="c1"># assistant analysis </span> <span class="p">{</span><span class="s">'role'</span><span class="p">:</span> <span class="s">'assistant'</span><span class="p">,</span> <span class="s">'tool_calls'</span><span class="p">:</span> <span class="p">[{</span><span class="s">'name'</span><span class="p">:</span> <span class="s">'get_current_location'</span><span class="p">,</span> <span class="s">'arguments'</span><span class="p">:</span> <span class="s">'{"userid": "jay_lee", "max_len": 10}'</span><span class="p">}]},</span> <span class="c1"># assistant commentary </span> <span class="p">{</span><span class="s">'role'</span><span class="p">:</span> <span class="s">'tool'</span><span class="p">,</span> <span class="s">'name'</span><span class="p">:</span> <span class="s">'get_current_location'</span><span class="p">,</span> <span class="s">'content'</span><span class="p">:</span> <span class="s">'{"location": "Korea, Rep"}'</span><span class="p">},</span> <span class="c1"># function result to assistant commentary </span> <span class="p">{</span><span class="s">'role'</span><span class="p">:</span> <span class="s">'assistant'</span><span class="p">,</span> <span class="s">'tool_calls'</span><span class="p">:</span> <span class="p">[{</span><span class="s">'name'</span><span class="p">:</span> <span class="s">'get_current_temperature'</span><span class="p">,</span> <span class="s">'arguments'</span><span class="p">:</span> <span class="s">'{"location": "Korea, Rep", "format_t": "fahrenheit"}'</span><span class="p">}]},</span> <span class="c1"># assistant commentary, 첫번째 대화와 다르게 analysis를 거치지 않고 바로 function call을 한다. </span> <span class="p">{</span><span class="s">'role'</span><span class="p">:</span> <span class="s">'tool'</span><span class="p">,</span> <span class="s">'name'</span><span class="p">:</span> <span class="s">'get_current_temperature'</span><span class="p">,</span> <span class="s">'content'</span><span class="p">:</span> <span class="s">'{"temperature": 25}'</span><span class="p">},</span> <span class="c1"># function result to assistant commentary </span> <span class="p">{</span><span class="s">'role'</span><span class="p">:</span> <span class="s">'assistant'</span><span class="p">,</span> <span class="s">'content'</span><span class="p">:</span> <span class="s">'현재 `jay_lee`님의 위치는 **대한민국**이며, 해당 지역의 현재 기온은 **25</span><span class="se">\u202f</span><span class="s">°F**(섭씨 약</span><span class="se">\u202f</span><span class="s">-3.9</span><span class="se">\u202f</span><span class="s">°C)입니다. </span><span class="se">\n\n</span><span class="s">**25</span><span class="se">\u202f</span><span class="s">°F**는 매우 추운 온도이므로 야외 활동은 권장되지 않습니다. 방한 장비(두꺼운 코트, 모자, 장갑, 목도리 등)를 충분히 착용하고, 필요하다면 짧은 시간 동안 밖에 나가더라도 몸을 따뜻하게 유지하는 것이 중요합니다.'</span><span class="p">}]</span> <span class="c1"># assistant final </span></code></pre></div></div> <p><br /></p> <h3 id="4-3-troubleshooting">4-3. troubleshooting</h3> <p><br /></p> <h4 id="harmony-encoding-파싱-오류">harmony encoding 파싱 오류</h4> <p><br /></p> <p>harmony 문서에서 <code class="language-plaintext highlighter-rouge">parse_messages_from_completion_tokens</code>함수가 제대로 동작하지 않을 때를 대비해 예외처리를 권장하고 있다.</p> <p>The bindings raise plain Python exceptions. The most common ones are:</p> <ul> <li><code class="language-plaintext highlighter-rouge">RuntimeError</code> – returned for rendering or parsing failures (for example if a token sequence is malformed or decoding fails).</li> <li><code class="language-plaintext highlighter-rouge">ValueError</code> – raised when an argument is invalid, e.g. an unknown <code class="language-plaintext highlighter-rouge">Role</code> is provided to <code class="language-plaintext highlighter-rouge">load_harmony_encoding</code> or <code class="language-plaintext highlighter-rouge">StreamableParser</code>.</li> <li><code class="language-plaintext highlighter-rouge">ModuleNotFoundError</code> – accessing the package without building the compiled extension results in this error.</li> </ul> <p>In typical code you would wrap encoding operations in a <code class="language-plaintext highlighter-rouge">try</code>/<code class="language-plaintext highlighter-rouge">except</code> block:</p> <div class="language-python highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">try</span><span class="p">:</span> <span class="n">tokens</span> <span class="o">=</span> <span class="n">enc</span><span class="p">.</span><span class="n">render_conversation_for_completion</span><span class="p">(</span><span class="n">convo</span><span class="p">,</span> <span class="n">Role</span><span class="p">.</span><span class="n">ASSISTANT</span><span class="p">)</span> <span class="n">parsed</span> <span class="o">=</span> <span class="n">enc</span><span class="p">.</span><span class="n">parse_messages_from_completion_tokens</span><span class="p">(</span><span class="n">tokens</span><span class="p">,</span> <span class="n">Role</span><span class="p">.</span><span class="n">ASSISTANT</span><span class="p">)</span> <span class="k">except</span> <span class="nb">RuntimeError</span> <span class="k">as</span> <span class="n">err</span><span class="p">:</span> <span class="k">print</span><span class="p">(</span><span class="sa">f</span><span class="s">"Failed to handle conversation: </span><span class="si">{</span><span class="n">err</span><span class="si">}</span><span class="s">"</span><span class="p">)</span> </code></pre></div></div> <p><br /></p> <p>가장 중요한 RuntimeError의 경우 시작 토큰이 없거나 종료 토큰이 없는 경우, 시작 토큰 뒤에 role이 아닌 channel이 오는 경우 등등 harmony 형식에서 어긋난 토큰 순서를 만나면 발생한다.</p> <p>만약 모델에서 생성된 결과가 harmony 포맷에서 어긋난다면, 앞부분에서도 강조했듯이 가장 먼저 모델 입력값에 <code class="language-plaintext highlighter-rouge">&lt;|start|&gt;assistant</code>를 붙이고 모델에 입력했는지를 꼭 확인해봐야 한다. 만약 붙이지 않고 입력한다면 다음과 같이 시작 토큰 뒤에 채널명이 생성되어버리는 대참사가 종종 발생한다.</p> <div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code># malformed harmony example &lt;|start|&gt;analysis&lt;|message|&gt;Need to call get_current_location then get_current_temperature.&lt;|end|&gt; </code></pre></div></div> <p><br /></p> <h4 id="llamacpp에서-stop-token을-제공하지-않는-문제">llama.cpp에서 stop token을 제공하지 않는 문제</h4> <p><br /></p> <p>스페셜 토큰 중 두 개의 stop token이 있는데, <code class="language-plaintext highlighter-rouge">&lt;|call|&gt;</code>과 <code class="language-plaintext highlighter-rouge">&lt;|return|&gt;</code>이다. 문제는 llama 모델의 <code class="language-plaintext highlighter-rouge">create_completion</code> 함수 실행후 나오는 결과 값에 제공되는 생성 종료 사유에 둘 중 어떤 토큰으로 종료되었는지를 알려주지 않고 해당 stop token을 제거한채로 결과를 줘버린다.</p> <p>멈춘 사유를 모른다면 두 토큰 중 어떤 토큰을 사용해야하는지 어떻게 알까? 다행히 <code class="language-plaintext highlighter-rouge">&lt;|return|&gt;</code>토큰은 직접 사용할 일이 없다. 해당 토큰은 모델의 출력값으로만 사용되기에 어차피 다음 입력으로 포함되면 안되기 때문. 또한 <code class="language-plaintext highlighter-rouge">encode_conversations_with_harmony</code>함수가 <code class="language-plaintext highlighter-rouge">&lt;|return|&gt;</code>이 들어갈 위치에 알아서 <code class="language-plaintext highlighter-rouge">&lt;|end|&gt;</code>를 채워준다.</p> <p>따라서 위에 작성된 <code class="language-plaintext highlighter-rouge">get_response</code> 함수 내부에서 function call의 결과를 다시 모델에 제공할 때 강제로 <code class="language-plaintext highlighter-rouge">&lt;|call|&gt;</code> 토큰을 올바른 위치에 붙여주는 것만 신경쓰면 된다.</p> <div class="language-python highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="n">conversation_harmony</span> <span class="o">=</span> <span class="s">''</span><span class="p">.</span><span class="n">join</span><span class="p">([</span> <span class="n">conversation_harmony</span><span class="p">,</span> <span class="n">response_harmony</span><span class="p">,</span> <span class="s">'&lt;|call|&gt;'</span><span class="p">,</span> <span class="c1"># 삭제된 stop token 강제로 추가 </span> <span class="sa">f</span><span class="s">'&lt;|start|&gt;functions.</span><span class="si">{</span><span class="n">func</span><span class="si">}</span><span class="s"> to=assistant&lt;|channel|&gt;commentary&lt;|message|&gt;</span><span class="si">{</span><span class="n">result_json</span><span class="si">}</span><span class="s">&lt;|end|&gt;&lt;start&gt;assistant'</span><span class="p">])</span> </code></pre></div></div> <p><br /></p> <p>harmony 문서에는 다음과 같이 언급되어있다.</p> <p><strong>Implementation note:</strong> <code class="language-plaintext highlighter-rouge">&lt;|return|&gt;</code> is a decode-time stop token only. When you add the assistant’s generated reply to conversation history for the next turn, replace the trailing <code class="language-plaintext highlighter-rouge">&lt;|return|&gt;</code> with <code class="language-plaintext highlighter-rouge">&lt;|end|&gt;</code> so that stored messages are fully formed as <code class="language-plaintext highlighter-rouge">&lt;|start|&gt;{header}&lt;|message|&gt;{content}&lt;|end|&gt;</code>. Prior messages in prompts should therefore end with <code class="language-plaintext highlighter-rouge">&lt;|end|&gt;</code>. For supervised targets/training examples, ending with <code class="language-plaintext highlighter-rouge">&lt;|return|&gt;</code> is appropriate; for persisted history, normalize to <code class="language-plaintext highlighter-rouge">&lt;|end|&gt;</code>.</p> <p><br /></p> <h2 id="5-총평">5. 총평</h2> <ul> <li>gguf포맷으로 cpu에서 충분히 실행할 수는 있으나 실시간성으로 쓰기엔 많이 느리다. (한번 생성에 분 단위 소요)</li> <li>unsloth에서 harmony 렌더러 wrapping해서 제공해준 함수가 기대이상으로 쓰기 간편하다</li> <li>harmony 포맷이 구조화가 잘 되어있어 모델이 토큰을 생성하는 순서를 체계적으로 이해할 수 있고 모델 외부와 높은 확률로 오류 없이 결과를 주고받을 수 있는 점이 맘에 든다</li> <li>moe 레이어만 cpu에서 실행하고 나머지는 gpu에서 실행할 수 있다는데 (llama –cpu-moe 옵션) 한번 쓸만한지 테스트해보면 좋을듯</li> </ul> <p><br /></p> <hr /> <p><br /></p> <h3 id="출처">출처</h3> <ul> <li><a href="https://openai.com/ko-KR/index/introducing-gpt-oss/">https://openai.com/ko-KR/index/introducing-gpt-oss/</a></li> <li><a href="https://github.com/openai/gpt-oss">https://github.com/openai/gpt-oss</a></li> <li><a href="https://github.com/openai/harmony">https://github.com/openai/harmony</a></li> <li><a href="https://cookbook.openai.com/articles/openai-harmony">https://cookbook.openai.com/articles/openai-harmony</a></li> <li><a href="https://docs.unsloth.ai/basics/gpt-oss-how-to-run-and-fine-tune">https://docs.unsloth.ai/basics/gpt-oss-how-to-run-and-fine-tune</a></li> <li><a href="https://huggingface.co/openai/gpt-oss-20b">https://huggingface.co/openai/gpt-oss-20b</a></li> </ul> <p><br /></p> Wed, 20 Aug 2025 00:00:00 +0000 https://clarit7.github.io/gpt_oss_tutorial/ https://clarit7.github.io/gpt_oss_tutorial/ gpt_oss unsloth llama llm AWS Bedrock Tutorial - 음성파일로부터 자막과 이미지 생성하기(2) <p><br /></p> <hr /> <p><br /></p> <h3 id="이어서">이어서</h3> <p><br /></p> <p>음성파일을 S3 버킷에 업로드한 <a href="https://clarit7.github.io/aws_tutorial_bedrock_1/">지난 게시글</a>에 이어 음성 파일이 업로드 되는 순간 자막을 생성하고, 자막과 연관된 이미지를 생성하는 과정이 자동화 될 수 있도록 필요한 과정을 마저 진행한다.</p> <p><br /></p> <hr /> <h3 id="0-주의점">0. 주의점</h3> <p><br /></p> <ul> <li>모든 서비스의 리전은 전부 같은 리전이어야 한다. 이 글에선 미국 동부(us-east-1)으로 진행한다.</li> <li>S3 버킷에 파일 생성을 트리거로 입력 파일을 받아 출력 파일을 생성하고, 그 출력 파일을 같은 버킷에 넣으면 안된다!!! <strong>입력파일생성 - 트리거 - 출력파일생성(=입력파일생성) - 트리거 - 출력파일생성(=입력파일생성) - …</strong> 의 무한루프에 빠져 무한히 파일이 생성되며 <strong>요금 폭탄이 나올 수 있다.</strong> 따라서 반드시 입력파일이 생성되는 버킷과 출력파일이 생성되는 버킷은 구분해주어야 한다.</li> </ul> <p><br /></p> <h3 id="1-s3-버킷-생성">1. S3 버킷 생성</h3> <p><a href="https://clarit7.github.io/aws_tutorial_bedrock_1/">지난 게시글</a>을 참고해 자막 파일을 업로드할 버킷 1개와 이미지 파일을 업로드할 버킷 1개를 각각 생성한다.</p> <p>버킷 이름은 전 세계에서 중복되지 않는 이름이어야 하며, 본인이 구분하기 좋은 이름으로 설정하면 된다.</p> <p>여기선 my-toy-bucket-upload-transcription-jay, my-toy-bucket-upload-image-jay로 각각 생성하고 진행한다.</p> <p><br /></p> <h3 id="2-transcribe를-실행하는-lambda-함수-생성">2. Transcribe를 실행하는 Lambda 함수 생성</h3> <p><br /></p> <p align="center"> <img src="/images/2024/09/22/40.png" width="800px" /> </p> <p><br /></p> <p>Lambda 서비스로 이동해 ‘함수 생성’을 누른다.</p> <p><br /></p> <p align="center"> <img src="/images/2024/09/22/41.png" width="800px" /> </p> <p><br /></p> <p>함수 이름을 입력하고, 런타임 언어로 파이썬을 선택한다. ‘함수 생성’을 누르면 기본 생성 역할과 함께 Lambda 함수가 생성된다.</p> <p><br /></p> <p align="center"> <img src="/images/2024/09/22/42.png" width="800px" /> </p> <p><br /></p> <p>구성 - 일반 구성 - ‘편집’을 선택한다.</p> <p><br /></p> <p align="center"> <img src="/images/2024/09/22/43.png" width="800px" /> </p> <p><br /></p> <p>제한시간을 30초로 변경한다. Transcribe 모델 실행시간이 수십초 걸릴 수 있기 때문에 넉넉히 변경한다. ‘저장’을 누른다.</p> <p><br /></p> <div class="language-python highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kn">from</span> <span class="nn">botocore.exceptions</span> <span class="kn">import</span> <span class="n">ClientError</span> <span class="kn">from</span> <span class="nn">datetime</span> <span class="kn">import</span> <span class="n">date</span><span class="p">,</span> <span class="n">datetime</span> <span class="kn">import</span> <span class="nn">json</span> <span class="kn">import</span> <span class="nn">boto3</span> <span class="kn">import</span> <span class="nn">logging</span> <span class="n">logger</span> <span class="o">=</span> <span class="n">logging</span><span class="p">.</span><span class="n">getLogger</span><span class="p">()</span> <span class="n">logger</span><span class="p">.</span><span class="n">setLevel</span><span class="p">(</span><span class="s">"INFO"</span><span class="p">)</span> <span class="k">def</span> <span class="nf">json_serial</span><span class="p">(</span><span class="n">obj</span><span class="p">):</span> <span class="s">"""JSON serializer for objects not serializable by default json code"""</span> <span class="k">if</span> <span class="nb">isinstance</span><span class="p">(</span><span class="n">obj</span><span class="p">,</span> <span class="p">(</span><span class="n">datetime</span><span class="p">,</span> <span class="n">date</span><span class="p">)):</span> <span class="k">return</span> <span class="n">obj</span><span class="p">.</span><span class="n">isoformat</span><span class="p">()</span> <span class="k">raise</span> <span class="nb">TypeError</span> <span class="p">(</span><span class="s">"Type %s not serializable"</span> <span class="o">%</span> <span class="nb">type</span><span class="p">(</span><span class="n">obj</span><span class="p">))</span> <span class="k">def</span> <span class="nf">lambda_handler</span><span class="p">(</span><span class="n">event</span><span class="p">,</span> <span class="n">context</span><span class="p">):</span> <span class="c1"># TODO implement </span> <span class="n">bucket_name</span> <span class="o">=</span> <span class="n">event</span><span class="p">[</span><span class="s">'Records'</span><span class="p">][</span><span class="mi">0</span><span class="p">][</span><span class="s">'s3'</span><span class="p">][</span><span class="s">'bucket'</span><span class="p">][</span><span class="s">'name'</span><span class="p">]</span> <span class="n">key</span> <span class="o">=</span> <span class="n">event</span><span class="p">[</span><span class="s">'Records'</span><span class="p">][</span><span class="mi">0</span><span class="p">][</span><span class="s">'s3'</span><span class="p">][</span><span class="s">'object'</span><span class="p">][</span><span class="s">'key'</span><span class="p">]</span> <span class="n">transcribe_client</span> <span class="o">=</span> <span class="n">boto3</span><span class="p">.</span><span class="n">client</span><span class="p">(</span><span class="s">"transcribe"</span><span class="p">)</span> <span class="n">job_name</span> <span class="o">=</span> <span class="s">'my_transciption_job_{}_{}'</span><span class="p">.</span><span class="nb">format</span><span class="p">(</span><span class="n">datetime</span><span class="p">.</span><span class="n">now</span><span class="p">().</span><span class="n">strftime</span><span class="p">(</span><span class="s">'%Y%m%d_%H%M%S'</span><span class="p">),</span> <span class="n">key</span><span class="p">)</span> <span class="k">try</span><span class="p">:</span> <span class="n">job_args</span> <span class="o">=</span> <span class="p">{</span> <span class="s">"TranscriptionJobName"</span><span class="p">:</span> <span class="n">job_name</span><span class="p">,</span> <span class="s">"Media"</span><span class="p">:</span> <span class="p">{</span><span class="s">"MediaFileUri"</span><span class="p">:</span> <span class="s">'s3://{}/{}'</span><span class="p">.</span><span class="nb">format</span><span class="p">(</span><span class="n">bucket_name</span><span class="p">,</span> <span class="n">key</span><span class="p">)},</span> <span class="s">"MediaFormat"</span><span class="p">:</span> <span class="s">'mp3'</span><span class="p">,</span> <span class="s">"LanguageCode"</span><span class="p">:</span> <span class="s">'en-US'</span><span class="p">,</span> <span class="c1">########################################## </span> <span class="s">"OutputBucketName"</span><span class="p">:</span> <span class="n">Bucket</span><span class="o">-</span><span class="n">name</span><span class="o">-</span><span class="n">to</span><span class="o">-</span><span class="n">upload</span><span class="p">,</span> <span class="c1">########################################## </span> <span class="s">"OutputKey"</span><span class="p">:</span> <span class="s">'my_transcription_{}.json'</span><span class="p">.</span><span class="nb">format</span><span class="p">(</span><span class="n">key</span><span class="p">.</span><span class="n">split</span><span class="p">(</span><span class="s">'.'</span><span class="p">)[</span><span class="mi">0</span><span class="p">].</span><span class="n">split</span><span class="p">(</span><span class="s">'_'</span><span class="p">)[</span><span class="o">-</span><span class="mi">1</span><span class="p">]),</span> <span class="s">"Settings"</span><span class="p">:{</span> <span class="s">'ShowSpeakerLabels'</span><span class="p">:</span> <span class="bp">True</span><span class="p">,</span> <span class="s">'MaxSpeakerLabels'</span><span class="p">:</span> <span class="mi">2</span> <span class="p">}</span> <span class="p">}</span> <span class="n">response</span> <span class="o">=</span> <span class="n">transcribe_client</span><span class="p">.</span><span class="n">start_transcription_job</span><span class="p">(</span><span class="o">**</span><span class="n">job_args</span><span class="p">)</span> <span class="n">response</span> <span class="o">=</span> <span class="n">json</span><span class="p">.</span><span class="n">dumps</span><span class="p">(</span><span class="n">response</span><span class="p">,</span> <span class="n">default</span><span class="o">=</span><span class="n">json_serial</span><span class="p">)</span> <span class="n">logger</span><span class="p">.</span><span class="n">info</span><span class="p">(</span><span class="s">"Started transcription job %s."</span><span class="p">,</span> <span class="n">job_name</span><span class="p">)</span> <span class="k">except</span> <span class="n">ClientError</span><span class="p">:</span> <span class="n">logger</span><span class="p">.</span><span class="n">exception</span><span class="p">(</span><span class="s">"Couldn't start transcription job %s."</span><span class="p">,</span> <span class="n">job_name</span><span class="p">)</span> <span class="k">raise</span> <span class="k">else</span><span class="p">:</span> <span class="k">return</span> <span class="n">response</span> <span class="n">logger</span><span class="p">.</span><span class="n">info</span><span class="p">(</span><span class="s">"job done"</span><span class="p">)</span> <span class="n">logger</span><span class="p">.</span><span class="n">info</span><span class="p">(</span><span class="n">json</span><span class="p">.</span><span class="n">loads</span><span class="p">(</span><span class="n">response</span><span class="p">))</span> </code></pre></div></div> <p><br /></p> <p align="center"> <img src="/images/2024/09/22/44.png" width="800px" /> </p> <p><br /></p> <p>코드 창에 위의 함수를 붙여넣는다. Bucket-name-to-upload에는 앞에서 생성한 자막 파일 업로드용 버킷의 이름을 넣는다.</p> <p><br /></p> <p align="center"> <img src="/images/2024/09/22/45.png" width="800px" /> </p> <p><br /></p> <p>위로 가 ‘트리거 추가’를 누른다.</p> <p><br /></p> <p align="center"> <img src="/images/2024/09/22/46.png" width="800px" /> </p> <p><br /></p> <p align="center"> <img src="/images/2024/09/22/47.png" width="800px" /> </p> <p><br /></p> <p>소스 선택에서 ‘S3’를 선택한다.</p> <p>이후 버킷 선택에서 음성 파일이 업로드된 버킷을 선택하고, 이벤트 유형에는 다른 유형은 다 삭제하고 PUT만 추가한다. 트리거가 발생할 파일명의 조건으로는 접두사로 my_audio, 접미사로 .mp3를 입력해 다른 유형의 파일이 생성되었을 때 필요 없는 함수 호출이 없도록 방지한다. 마지막으로 재귀 호출에 체크하고 ‘추가’를 누른다.</p> <p><br /></p> <p align="center"> <img src="/images/2024/09/22/48.png" width="800px" /> </p> <p><br /></p> <p>구성 - 권한 - 역할 이름 링크를 클릭해 IAM 서비스로 이동한다.</p> <p><br /></p> <p align="center"> <img src="/images/2024/09/22/49.png" width="800px" /> </p> <p><br /></p> <p>권한 추가의 ‘인라인 정책 생성’을 클릭한다.</p> <p><br /></p> <p align="center"> <img src="/images/2024/09/22/50.png" width="800px" /> </p> <p><br /></p> <p>검색창에 getObject를 입력 후 체크박스 선택. 리소스 항목에선 특정을 선택 후 ‘ARN 추가’를 누른다.</p> <p><br /></p> <p align="center"> <img src="/images/2024/09/22/51.png" width="800px" /> </p> <p><br /></p> <p>음성 파일이 업로드 된 버킷명을 bucket name에 입력하고, 음성 파일명의 접두사를 object name에 입력 후 ‘ARN 추가’를 누른다.</p> <p><br /></p> <p align="center"> <img src="/images/2024/09/22/52.png" width="800px" /> </p> <p><br /></p> <p>하단의 ‘권한 더 추가’를 눌러 PutObject 권한도 추가해줄 것이다.</p> <p><br /></p> <p align="center"> <img src="/images/2024/09/22/53.png" width="800px" /> </p> <p><br /></p> <p align="center"> <img src="/images/2024/09/22/54.png" width="800px" /> </p> <p><br /></p> <p align="center"> <img src="/images/2024/09/22/54-1.png" width="800px" /> </p> <p><br /></p> <p align="center"> <img src="/images/2024/09/22/54-2.png" width="800px" /> </p> <p><br /></p> <p>위에서 GetObject와 동일하게 PutObject도 추가해주고, ARN은 이번엔 자막파일을 업로드 할 버킷의 이름과 접두사를 입력한다. 또한 자막파일 생성시 temp파일도 생성되므로 ARN을 하나 더 추가한다. 다 됐으면 다음을 누른다.</p> <p><br /></p> <p align="center"> <img src="/images/2024/09/22/55.png" width="800px" /> </p> <p><br /></p> <p>적당한 정책 이름 입력 후 ‘정책 생성’을 클릭한다.</p> <p><br /></p> <p align="center"> <img src="/images/2024/09/22/56.png" width="800px" /> </p> <p><br /></p> <p>정책 하나를 더 연결해야 한다. 이번엔 기본적으로 있는 정책을 가져온다. 권한 추가의 ‘정책 연결’을 누른다.</p> <p><br /></p> <p align="center"> <img src="/images/2024/09/22/57.png" width="800px" /> </p> <p><br /></p> <p>transcribe를 검색해 AmazonTranscribeFullAccess 정책의 체크박스를 클릭한다. 사실 이 정책도 세부적으로 선택할 수 있겠지만 잘 모르는 부분이기도 하고 토이프로젝트니 FullAccess 정책으로 선택한다.</p> <p><br /></p> <p align="center"> <img src="/images/2024/09/22/58.png" width="800px" /> </p> <p><br /></p> <p>다시 Lambda 함수로 돌아와 Deploy로 함수를 배포한다.</p> <p><br /></p> <p align="center"> <img src="/images/2024/09/22/59.png" width="800px" /> </p> <p><br /></p> <p>Test 버튼을 눌러 새 테스트를 생성 후 한번 실행시켜 CloudWatch에 로그가 기록되도록 한다. 테스트는 실패해도 상관없으니 아무렇게나 생성하고 실행하면 된다.</p> <p><br /></p> <p align="center"> <img src="/images/2024/09/22/60.png" width="800px" /> </p> <p><br /></p> <p align="center"> <img src="/images/2024/09/22/61.png" width="800px" /> </p> <p><br /></p> <p>Postman에서 테스트로 음성 파일 업로드 시, 자막파일까지 순차적으로 생성된다면 성공이다.</p> <p><br /></p> <p align="center"> <img src="/images/2024/09/22/62.png" width="800px" /> </p> <p><br /></p> <p>자막 파일은 위와 같이 JSON 파일 형식으로 생성된다. “I’m starving. Let’s grab a bite to eat.” 이 두 문장을 후에 이미지 생성 모델의 프롬프트로 입력할 것이다.</p> <p><br /></p> <h3 id="3-bedrock을-실행하는-lambda-함수-생성">3. Bedrock을 실행하는 Lambda 함수 생성</h3> <p><br /></p> <p align="center"> <img src="/images/2024/09/22/63.png" width="800px" /> </p> <p><br /></p> <p>베드락 서비스에 접속해 좌측 하단의 ‘모델 엑세스’를 클릭한다.</p> <p><br /></p> <p align="center"> <img src="/images/2024/09/22/64.png" width="800px" /> </p> <p><br /></p> <p>‘Modify model access’를 클릭한다.</p> <p><br /></p> <p align="center"> <img src="/images/2024/09/22/65.png" width="800px" /> </p> <p><br /></p> <p>사용하고자 하는 모델에 체크박스를 선택하면 되는데, 여기선 Amazon - Titan Image Generator G1을 사용할 것이다.</p> <p>사실 해당 모델은 기본 사용이 가능하므로 아마 체크가 안될 것이다. 만약 다른 모델을 써보고 싶다면 추가로 체크한다. (다만 이 글에서 다른 모델들의 파라미터 작성법까지 소개되진 않는다.)</p> <p><br /></p> <p align="center"> <img src="/images/2024/09/22/66.png" width="800px" /> </p> <p><br /></p> <p align="center"> <img src="/images/2024/09/22/67.png" width="800px" /> </p> <p><br /></p> <p>Next - Summit 순으로 클릭해 완료한다.</p> <p><br /></p> <p align="center"> <img src="/images/2024/09/22/68.png" width="800px" /> </p> <p><br /></p> <p>다시 Lambda 서비스로 돌아와, Bedrock과 관련된 이름으로 새 함수를 생성한다.</p> <p>이후 과정은 앞서 Transcribe Lambda 함수 작성 과정과 거의 동일하기 때문에, 차이점 위주로만 설명한다.</p> <h4 id="--lambda_handler-코드">- lambda_handler 코드</h4> <div class="language-python highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kn">import</span> <span class="nn">json</span> <span class="kn">import</span> <span class="nn">logging</span> <span class="kn">import</span> <span class="nn">boto3</span> <span class="kn">from</span> <span class="nn">botocore.exceptions</span> <span class="kn">import</span> <span class="n">ClientError</span> <span class="kn">import</span> <span class="nn">base64</span> <span class="c1"># This creates a logger instance </span><span class="n">logger</span> <span class="o">=</span> <span class="n">logging</span><span class="p">.</span><span class="n">getLogger</span><span class="p">()</span> <span class="n">logger</span><span class="p">.</span><span class="n">setLevel</span><span class="p">(</span><span class="n">logging</span><span class="p">.</span><span class="n">INFO</span><span class="p">)</span> <span class="c1"># This initializes the clients Bedrock Runtime and S3 </span><span class="n">bedrock_runtime_client</span> <span class="o">=</span> <span class="n">boto3</span><span class="p">.</span><span class="n">client</span><span class="p">(</span><span class="s">'bedrock-runtime'</span><span class="p">,</span> <span class="n">region_name</span><span class="o">=</span><span class="s">'us-east-1'</span><span class="p">)</span> <span class="n">s3</span> <span class="o">=</span> <span class="n">boto3</span><span class="p">.</span><span class="n">client</span><span class="p">(</span><span class="s">'s3'</span><span class="p">)</span> <span class="k">def</span> <span class="nf">lambda_handler</span><span class="p">(</span><span class="n">event</span><span class="p">,</span> <span class="n">context</span><span class="p">):</span> <span class="c1"># We need to extract 'text' and 'seed' from the event, provide defaults if not present </span> <span class="n">bucket_name</span> <span class="o">=</span> <span class="n">event</span><span class="p">[</span><span class="s">'Records'</span><span class="p">][</span><span class="mi">0</span><span class="p">][</span><span class="s">'s3'</span><span class="p">][</span><span class="s">'bucket'</span><span class="p">][</span><span class="s">'name'</span><span class="p">]</span> <span class="n">key</span> <span class="o">=</span> <span class="n">event</span><span class="p">[</span><span class="s">'Records'</span><span class="p">][</span><span class="mi">0</span><span class="p">][</span><span class="s">'s3'</span><span class="p">][</span><span class="s">'object'</span><span class="p">][</span><span class="s">'key'</span><span class="p">]</span> <span class="n">s3_resource</span> <span class="o">=</span> <span class="n">boto3</span><span class="p">.</span><span class="n">resource</span><span class="p">(</span><span class="s">'s3'</span><span class="p">)</span> <span class="n">content_object</span> <span class="o">=</span> <span class="n">s3_resource</span><span class="p">.</span><span class="n">Object</span><span class="p">(</span><span class="n">bucket_name</span><span class="p">,</span> <span class="n">key</span><span class="p">)</span> <span class="n">file_content</span> <span class="o">=</span> <span class="n">content_object</span><span class="p">.</span><span class="n">get</span><span class="p">()[</span><span class="s">'Body'</span><span class="p">].</span><span class="n">read</span><span class="p">().</span><span class="n">decode</span><span class="p">(</span><span class="s">'utf-8'</span><span class="p">)</span> <span class="n">json_content</span> <span class="o">=</span> <span class="n">json</span><span class="p">.</span><span class="n">loads</span><span class="p">(</span><span class="n">file_content</span><span class="p">)</span> <span class="n">prompt</span> <span class="o">=</span> <span class="n">json_content</span><span class="p">[</span><span class="s">"results"</span><span class="p">][</span><span class="s">"transcripts"</span><span class="p">][</span><span class="mi">0</span><span class="p">][</span><span class="s">"transcript"</span><span class="p">]</span> <span class="n">prompt</span> <span class="o">=</span> <span class="s">'. '</span><span class="p">.</span><span class="n">join</span><span class="p">(</span><span class="n">prompt</span><span class="p">.</span><span class="n">split</span><span class="p">(</span><span class="s">'. '</span><span class="p">)[</span><span class="mi">1</span><span class="p">:</span><span class="mi">3</span><span class="p">])</span> <span class="o">+</span> <span class="s">'.'</span> <span class="n">logger</span><span class="p">.</span><span class="n">info</span><span class="p">(</span><span class="n">prompt</span><span class="p">)</span> <span class="c1">########################################## </span> <span class="n">upload_bucket_name</span> <span class="o">=</span> <span class="n">Bucket</span><span class="o">-</span><span class="n">name</span><span class="o">-</span><span class="n">to</span><span class="o">-</span><span class="n">upload</span> <span class="c1">########################################## </span> <span class="k">try</span><span class="p">:</span> <span class="n">base64_image_data</span> <span class="o">=</span> <span class="n">invoke_titan_image</span><span class="p">(</span><span class="n">prompt</span><span class="p">)</span> <span class="c1"># The image data is a base64-encoded string, so we need to decode it to get the actual image data </span> <span class="n">image_data</span> <span class="o">=</span> <span class="n">base64</span><span class="p">.</span><span class="n">b64decode</span><span class="p">(</span><span class="n">base64_image_data</span><span class="p">)</span> <span class="n">object_key</span> <span class="o">=</span> <span class="s">"my_image_{}.jpg"</span><span class="p">.</span><span class="nb">format</span><span class="p">(</span><span class="n">key</span><span class="p">.</span><span class="n">split</span><span class="p">(</span><span class="s">'.'</span><span class="p">)[</span><span class="mi">0</span><span class="p">].</span><span class="n">split</span><span class="p">(</span><span class="s">'_'</span><span class="p">)[</span><span class="o">-</span><span class="mi">1</span><span class="p">])</span> <span class="c1"># Now we upload the image data to S3 </span> <span class="n">s3</span><span class="p">.</span><span class="n">put_object</span><span class="p">(</span> <span class="n">Bucket</span><span class="o">=</span><span class="n">upload_bucket_name</span><span class="p">,</span> <span class="n">Key</span><span class="o">=</span><span class="n">object_key</span><span class="p">,</span> <span class="n">Body</span><span class="o">=</span><span class="n">image_data</span><span class="p">,</span> <span class="n">ContentType</span><span class="o">=</span><span class="s">'image/jpeg'</span> <span class="c1"># This is adjustable! </span> <span class="p">)</span> <span class="n">logger</span><span class="p">.</span><span class="n">info</span><span class="p">(</span><span class="s">"Uploaded image to s3://{}/{}"</span><span class="p">.</span><span class="nb">format</span><span class="p">(</span><span class="n">upload_bucket_name</span><span class="p">,</span> <span class="n">object_key</span><span class="p">))</span> <span class="k">return</span> <span class="p">{</span> <span class="s">'statusCode'</span><span class="p">:</span> <span class="mi">200</span><span class="p">,</span> <span class="s">'body'</span><span class="p">:</span> <span class="n">json</span><span class="p">.</span><span class="n">dumps</span><span class="p">({</span><span class="s">'message'</span><span class="p">:</span> <span class="s">"Image uploaded successfully to {}/{}."</span><span class="p">.</span><span class="nb">format</span><span class="p">(</span><span class="n">upload_bucket_name</span><span class="p">,</span> <span class="n">object_key</span><span class="p">)})</span> <span class="p">}</span> <span class="k">except</span> <span class="nb">Exception</span> <span class="k">as</span> <span class="n">e</span><span class="p">:</span> <span class="n">logger</span><span class="p">.</span><span class="n">error</span><span class="p">(</span><span class="s">"Error: %s"</span><span class="p">,</span> <span class="nb">str</span><span class="p">(</span><span class="n">e</span><span class="p">))</span> <span class="k">return</span> <span class="p">{</span> <span class="s">'statusCode'</span><span class="p">:</span> <span class="mi">500</span><span class="p">,</span> <span class="s">'body'</span><span class="p">:</span> <span class="n">json</span><span class="p">.</span><span class="n">dumps</span><span class="p">({</span><span class="s">'error'</span><span class="p">:</span> <span class="s">'Failed to invoke model or upload image'</span><span class="p">,</span> <span class="s">'detail'</span><span class="p">:</span> <span class="nb">str</span><span class="p">(</span><span class="n">e</span><span class="p">)})</span> <span class="p">}</span> <span class="k">def</span> <span class="nf">invoke_titan_image</span><span class="p">(</span><span class="n">prompt</span><span class="p">):</span> <span class="k">try</span><span class="p">:</span> <span class="n">request</span> <span class="o">=</span> <span class="n">json</span><span class="p">.</span><span class="n">dumps</span><span class="p">({</span> <span class="s">"taskType"</span><span class="p">:</span> <span class="s">"TEXT_IMAGE"</span><span class="p">,</span> <span class="s">"textToImageParams"</span><span class="p">:</span> <span class="p">{</span><span class="s">"text"</span><span class="p">:</span> <span class="n">prompt</span><span class="p">},</span> <span class="s">"imageGenerationConfig"</span><span class="p">:</span> <span class="p">{</span> <span class="s">"numberOfImages"</span><span class="p">:</span> <span class="mi">1</span><span class="p">,</span> <span class="s">"quality"</span><span class="p">:</span> <span class="s">"standard"</span><span class="p">,</span> <span class="s">"cfgScale"</span><span class="p">:</span> <span class="mf">8.0</span><span class="p">,</span> <span class="s">"height"</span><span class="p">:</span> <span class="mi">640</span><span class="p">,</span> <span class="c1"># Permissible sizes: https://docs.aws.amazon.com/bedrock/latest/userguide/model-parameters-titan-image.html#:~:text=The%20following%20sizes%20are%20permissible. </span> <span class="s">"width"</span><span class="p">:</span> <span class="mi">1408</span> <span class="p">},</span> <span class="p">})</span> <span class="n">response</span> <span class="o">=</span> <span class="n">bedrock_runtime_client</span><span class="p">.</span><span class="n">invoke_model</span><span class="p">(</span> <span class="n">modelId</span><span class="o">=</span><span class="s">"amazon.titan-image-generator-v1"</span><span class="p">,</span> <span class="n">body</span><span class="o">=</span><span class="n">request</span> <span class="p">)</span> <span class="n">response_body</span> <span class="o">=</span> <span class="n">json</span><span class="p">.</span><span class="n">loads</span><span class="p">(</span><span class="n">response</span><span class="p">[</span><span class="s">"body"</span><span class="p">].</span><span class="n">read</span><span class="p">())</span> <span class="n">base64_image_data</span> <span class="o">=</span> <span class="n">response_body</span><span class="p">[</span><span class="s">"images"</span><span class="p">][</span><span class="mi">0</span><span class="p">]</span> <span class="k">return</span> <span class="n">base64_image_data</span> <span class="k">except</span> <span class="n">ClientError</span> <span class="k">as</span> <span class="n">e</span><span class="p">:</span> <span class="n">logger</span><span class="p">.</span><span class="n">error</span><span class="p">(</span><span class="s">"Couldn't invoke Titan Image generator: {}"</span><span class="p">.</span><span class="nb">format</span><span class="p">(</span><span class="n">e</span><span class="p">))</span> <span class="k">raise</span> </code></pre></div></div> <h4 id="--트리거">- 트리거</h4> <p><br /></p> <p align="center"> <img src="/images/2024/09/22/69.png" width="800px" /> </p> <p><br /></p> <p>자막 파일이 업로드되는 버킷과 자막파일에 맞는 접두사, 접미사로 변경</p> <h4 id="--실행-시간">- 실행 시간</h4> <p>동일하게 30초 이상으로 넉넉하게 설정</p> <h4 id="--정책-설정">- 정책 설정</h4> <p><br /></p> <p align="center"> <img src="/images/2024/09/22/70.png" width="800px" /> </p> <p><br /></p> <p>ARN 설정 없이 ListBucket 추가</p> <p><br /></p> <p align="center"> <img src="/images/2024/09/22/71.png" width="800px" /> </p> <p><br /></p> <p align="center"> <img src="/images/2024/09/22/72.png" width="800px" /> </p> <p><br /></p> <p>ARN은 자막 파일이 업로드되는 버킷에 맞게 설정하고, GetObject 추가</p> <p><br /></p> <p align="center"> <img src="/images/2024/09/22/73.png" width="800px" /> </p> <p><br /></p> <p align="center"> <img src="/images/2024/09/22/74.png" width="800px" /> </p> <p><br /></p> <p>ARN은 이미지 파일이 업로드되는 버킷에 맞게 설정하고, PutObject 추가</p> <p><br /></p> <p align="center"> <img src="/images/2024/09/22/75.png" width="800px" /> </p> <p><br /></p> <p>AmazonBedrockFullAccess 권한 추가</p> <p><br /></p> <p align="center"> <img src="/images/2024/09/22/76.png" width="800px" /> </p> <p><br /></p> <p>마찬가지로 Deploy 후 테스트 한번 실행해 CloudWatch 로그 그룹을 생성해준다. (테스트 실패해도 상관없음)</p> <h3 id="4-테스트">4. 테스트</h3> <p><br /></p> <p align="center"> <img src="/images/2024/09/22/77.png" width="800px" /> </p> <p><br /></p> <p>Postman으로 테스트 해보자.</p> <p><br /></p> <p align="center"> <img src="/images/2024/09/22/78.png" width="800px" /> </p> <p><br /></p> <p>생성한 이미지 파일이 정상적으로 버킷에 존재하면 성공!</p> <p><br /></p> <p align="center"> <img src="/images/2024/09/22/79.jpg" width="800px" /> </p> <p><br /></p> <p>입력파일로 음식과 관련된 영어 대화 음성 파일을 사용했더니 결과물은 건초를 먹는 염소(?)가 나왔다.</p> <p>구현에 집중한 토이 프로젝트라 결과물이 조금 재미 없는데, 여기에 프롬프트 엔지니어링을 잘 하고 좋은 입력 소스를 구한다면 재미있는 결과가 나올것 같다.</p> <p>끝!</p> <p><br /></p> <hr /> <p><br /></p> <h3 id="출처">출처</h3> <ul> <li><a href="https://docs.aws.amazon.com/ko_kr/code-library/latest/ug/python_3_transcribe_code_examples.html">https://docs.aws.amazon.com/ko_kr/code-library/latest/ug/python_3_transcribe_code_examples.html</a></li> <li><a href="https://docs.aws.amazon.com/ko_kr/bedrock/latest/userguide/titan-image-models.html">https://docs.aws.amazon.com/ko_kr/bedrock/latest/userguide/titan-image-models.html</a></li> <li><a href="https://community.aws/content/2byFjF8W1HHkzgis1aJokbXAJ6t/generate-and-store-images-in">https://community.aws/content/2byFjF8W1HHkzgis1aJokbXAJ6t/generate-and-store-images-in</a></li> </ul> <p><br /></p> Sun, 22 Sep 2024 00:00:00 +0000 https://clarit7.github.io/aws_tutorial_bedrock_2/ https://clarit7.github.io/aws_tutorial_bedrock_2/ aws bedrock cloud AWS Bedrock Tutorial - 음성파일로부터 자막과 이미지 생성하기(1) <p><br /></p> <hr /> <p><br /></p> <h3 id="aws-bedrock-활용한-토이-프로젝트-제작">AWS Bedrock 활용한 토이 프로젝트 제작</h3> <p><br /></p> <p>음성 파일을 업로드해 자막을 생성하고, 그 자막과 연관된 이미지를 생성하는 프로세스를 자동화하는 AWS Bedrock 토이 프로젝트의 튜토리얼이다.</p> <p>사용된 AWS 서비스는 다음과 같다.</p> <ul> <li>IAM : 역할과 정책 관리</li> <li>API Gateway : 각종 API 생성 및 관리</li> <li>S3 : 파일 저장소</li> <li>Lambda : 서버리스 컴퓨팅</li> <li>Transcribe : 음성 파일 텍스트 변환 모델</li> <li>Bedrock : 생성형 AI 모델 사용</li> </ul> <p>1편에선 음성파일을 S3버킷에 업로드하는 부분까지 설명한다.</p> <p><br /></p> <hr /> <h3 id="0-주의점">0. 주의점</h3> <p><br /></p> <ul> <li>모든 서비스의 리전은 전부 같은 리전이어야 한다. 이 글에선 미국 동부(us-east-1)으로 진행한다.</li> <li>S3 버킷에 파일 생성을 트리거로 입력 파일을 받아 출력 파일을 생성하고, 그 출력 파일을 같은 버킷에 넣으면 안된다!!! <strong>입력파일생성 - 트리거 - 출력파일생성(=입력파일생성) - 트리거 - 출력파일생성(=입력파일생성) - …</strong> 의 무한루프에 빠져 무한히 파일이 생성되며 <strong>요금 폭탄이 나올 수 있다.</strong> 따라서 반드시 입력파일이 생성되는 버킷과 출력파일이 생성되는 버킷은 구분해주어야 한다.</li> </ul> <p><br /></p> <h3 id="1-iam-역할-생성">1. IAM 역할 생성</h3> <p><br /></p> <p>모든 프로세스에 대해 IAM에서 총 3개의 역할이 필요하다.</p> <ul> <li>S3에 음성 파일을 업로드 하는 역할</li> <li>업로드된 음성 파일로부터 자막을 생성하는 역할</li> <li>생성된 자막 파일로부터 이미지파일을 생성하는 역할</li> </ul> <p>그리고 각 역할에는 다음과 같은 권한들이 필요하다.</p> <ul> <li>음성파일 업로드 <ol> <li>API Gateway 기본 권한 (CloudWatch에 로그 생성)</li> <li>음성파일이 업로드될 S3 버킷의 쓰기 권한</li> </ol> </li> <li>자막 생성 <ol> <li>Lambda 기본 권한 (CloudWatch에 로그 생성)</li> <li>음성파일이 업로드된 S3 버킷의 읽기 권한</li> <li>자막파일이 업로드될 S3 버킷의 쓰기 권한</li> <li>Transcribe 사용 권한</li> </ol> </li> <li>이미지 생성 <ol> <li>Lambda 기본 권한 (CloudWatch에 로그 생성)</li> <li>자막파일이 업로드된 S3 버킷의 읽기 권한</li> <li>이미지파일이 업로드될 S3 버킷의 쓰기 권한</li> <li>Bedrock 사용 권한</li> </ol> </li> </ul> <p>각 서비스들을 하나씩 생성할때마다 기본적으로 역할을 생성할 수 있기 때문에, 기본 생성된 역할에 나열된 권한을 추가해주면 된다. 단, API Gateway의 메서드는 미리 역할을 만들어놔야 하기 때문에, 역할 하나를 미리 만들고 진행한다.</p> <p><br /></p> <p align="center"> <img src="/images/2024/09/22/0.png" width="800px" /> </p> <p><br /></p> <p>역할 탭에서 ‘역할 생성’ 버튼을 클릭한다.</p> <p><br /></p> <p align="center"> <img src="/images/2024/09/22/1.png" width="800px" /> </p> <p><br /></p> <p>신뢰할 수 있는 엔티티 유형에서 AWS 서비스를 선택 후, 아래 서비스 또는 사용 사례 목록에서 API Gateway를 선택하고 ‘다음’을 누른다.</p> <p><br /></p> <p align="center"> <img src="/images/2024/09/22/2.png" width="800px" /> </p> <p><br /></p> <p>권한 추가 탭에선 따로 선택하지 말고 ‘다음’으로 넘어간다.</p> <p><br /></p> <p align="center"> <img src="/images/2024/09/22/3.png" width="800px" /> </p> <p><br /></p> <p align="center"> <img src="/images/2024/09/22/4.png" width="800px" /> </p> <p><br /></p> <p>적당히 구분하기 좋은 이름을 입력 후, 밑으로 쭉 내려 ‘역할 생성’을 누른다.</p> <p><br /></p> <p align="center"> <img src="/images/2024/09/22/5.png" width="800px" /> </p> <p><br /></p> <p>방금 생성한 오디오 업로드 역할에 대해, 추가 정책(권한)을 부여해야 한다. 정책 탭으로 가 ‘정책 생성’을 누른다.</p> <p><br /></p> <p align="center"> <img src="/images/2024/09/22/6.png" width="800px" /> </p> <p><br /></p> <p>서비스에서 ‘S3’를 선택한다.</p> <p><br /></p> <p align="center"> <img src="/images/2024/09/22/7.png" width="800px" /> </p> <p><br /></p> <p>권한 지정에서 작업 검색창에 PutObject를 검색 후, 밑에 나오는 리스트 중 ‘PutObject’에 체크한다.</p> <p><br /></p> <p align="center"> <img src="/images/2024/09/22/8.png" width="800px" /> </p> <p><br /></p> <p>밑으로 가 리소스 항목에서 ‘특정’을 선택하고, ‘ARN 추가’를 클릭한다.</p> <p><br /></p> <p align="center"> <img src="/images/2024/09/22/9.png" width="800px" /> </p> <p><br /></p> <p>Resource bucket name에는 앞으로 만들 오디오 파일 업로드용 버킷 이름을 입력한다.</p> <p>Resource object name에는 앞으로 업로드할 오디오 파일 이름을 입력한다.</p> <p>ARN은 Amazon Resource Name의 약자로, 사진과 같이 입력하게 된다면 지금 설정하는 PutObjet 정책은 위의 ARN에 대해서만 유효하다.</p> <p>즉, 아무 버킷에 아무 파일명으로 PutObject 할 수 있는게 아닌, my-toy-bucket-upload-audio-jay 버킷에만 my_audio로 시작하는 파일명으로 제한적인 업로드가 가능한 것이다.</p> <p>다 입력했으면 ‘ARN 추가’를 누른다.</p> <p><br /></p> <p align="center"> <img src="/images/2024/09/22/10.png" width="800px" /> </p> <p><br /></p> <p>ARN이 정상적으로 추가되었다면 ‘다음’을 누른다.</p> <p><br /></p> <p align="center"> <img src="/images/2024/09/22/11.png" width="800px" /> </p> <p><br /></p> <p align="center"> <img src="/images/2024/09/22/12.png" width="800px" /> </p> <p><br /></p> <p>적당히 구분하기 쉬운 정책 이름을 입력하고, 밑으로 가 ‘정책 생성’을 누른다.</p> <p><br /></p> <p align="center"> <img src="/images/2024/09/22/13.png" width="800px" /> </p> <p><br /></p> <p>정책 생성이 완료되었다면, 다시 역할 탭으로 가 앞에서 생성했던 S3 업로드용 역할을 클릭한다.</p> <p><br /></p> <p align="center"> <img src="/images/2024/09/22/14.png" width="800px" /> </p> <p><br /></p> <p>‘권한 추가’ - ‘정책 연결’을 클릭한다.</p> <p><br /></p> <p align="center"> <img src="/images/2024/09/22/15.png" width="800px" /> </p> <p><br /></p> <p>검색창에 방금 생성한 S3 putObject 정책을 검색 후, 체크박스를 선택하고 ‘권한 추가’를 누른다.</p> <p><br /></p> <p align="center"> <img src="/images/2024/09/22/16.png" width="800px" /> </p> <p><br /></p> <p>최종적으로 S3 업로드 역할엔, 다음과 같이 CloudWatch 로그 권한과 putObject 권한이 생기게 된다.</p> <h3 id="2-api-gateway-생성">2. API Gateway 생성</h3> <p><br /></p> <p align="center"> <img src="/images/2024/09/22/17.png" width="800px" /> </p> <p><br /></p> <p>API 서비스에 접속 후, API 탭으로 이동해 ‘API 생성’버튼을 누른다.</p> <p><br /></p> <p align="center"> <img src="/images/2024/09/22/18.png" width="800px" /> </p> <p><br /></p> <p>REST API에서 ‘구축’을 누른다.</p> <p><br /></p> <p align="center"> <img src="/images/2024/09/22/19.png" width="800px" /> </p> <p><br /></p> <p>적당한 API이름을 입력하고 ‘생성’을 누른다.</p> <p><br /></p> <p align="center"> <img src="/images/2024/09/22/20.png" width="800px" /> </p> <p><br /></p> <p>방금 생성한 API 하위 항목의 리소스 탭에서 ‘/’가 선택된 상태로 리소스 생성 버튼을 누른다.</p> <p><br /></p> <p align="center"> <img src="/images/2024/09/22/21.png" width="800px" /> </p> <p><br /></p> <p>리소스 경로에 ‘/’가 선택되어있는지 확인하고, 리소스 이름에 {folder}를 입력 후 리소스 생성 버튼을 누른다.</p> <p><br /></p> <p align="center"> <img src="/images/2024/09/22/22.png" width="800px" /> </p> <p><br /></p> <p>이번엔 ‘/{folder}’가 선택된 상태로 리소스 생성 버튼을 누른다.</p> <p><br /></p> <p align="center"> <img src="/images/2024/09/22/23.png" width="800px" /> </p> <p><br /></p> <p>리소스 경로에 ‘/{fodler}/’가 선택되어있는지 확인하고, 리소스 이름에 {object}를 입력 후 리소스 생성 버튼을 누른다.</p> <p><br /></p> <p align="center"> <img src="/images/2024/09/22/24.png" width="800px" /> </p> <p><br /></p> <p>리소스 경로에 ‘/{object}/’가 선택되어있는지 확인하고, 메서드 생성을 클릭한다.</p> <p><br /></p> <p align="center"> <img src="/images/2024/09/22/25.png" width="800px" /> </p> <p><br /></p> <p>메서드 유형: PUT, 통합 유형: AWS 서비스, AWS 리전: us-east-1, AWS 서비스: S3, HTTP 메서드: PUT을 각각 선택한다.</p> <p><br /></p> <p align="center"> <img src="/images/2024/09/22/26.png" width="800px" /> </p> <p><br /></p> <p align="center"> <img src="/images/2024/09/22/27.png" width="800px" /> </p> <p><br /></p> <p>경로 재정의에 {bucket}/{key}를 입력하고, 실행 역할에 아까 생성한 역할의 ARN을 복사해 붙여넣는다. 콘텐츠 처리는 패스스루를 선택하고 ‘다음’을 누른다. 여기서 아까 생성한 역할의 ARN은 IAM 서비스로 돌아가 위와 같은 위치에서 확인 및 복사를 할 수 있다.</p> <p><br /></p> <p align="center"> <img src="/images/2024/09/22/28.png" width="800px" /> </p> <p><br /></p> <p>다시 API Gateway 리소스로 돌아와, PUT 메서드가 선택된 상태에서 통합 요청 탭의 ‘편집’을 누른다.</p> <p><br /></p> <p align="center"> <img src="/images/2024/09/22/29.png" width="800px" /> </p> <p><br /></p> <p>하단에서 ‘경로 파라미터 추가’를 누른다. 총 두번 눌러 두개를 추가한다.</p> <p><br /></p> <p align="center"> <img src="/images/2024/09/22/30.png" width="800px" /> </p> <p><br /></p> <p>위 입력칸의 이름에 bucket, 다음에서 매핑됨에 method.request.path.folder를 입력한다.</p> <p>아래 입력칸의 이름에 key, 다음에서 매핑됨에 method.request.path.object를 입력하고 ‘저장’을 누른다.</p> <p><br /></p> <p align="center"> <img src="/images/2024/09/22/30-1.png" width="800px" /> </p> <p><br /></p> <p>API 설정 탭으로 가 이진 미디어 유형 항목의 ‘미디어 유형 관리’를 클릭한다.</p> <p><br /></p> <p align="center"> <img src="/images/2024/09/22/30-2.png" width="800px" /> </p> <p><br /></p> <p>이진 미디어 유형 추가 클릭 후, */*를 입력 후 ‘변경 사항 저장’을 누른다. (모든 이진 미디어 유형을 허용하겠다는 의미)</p> <p><br /></p> <p align="center"> <img src="/images/2024/09/22/31.png" width="800px" /> </p> <p><br /></p> <p>다시 API의 리소스 창으로 가 ‘API 배포’를 누른다.</p> <p><br /></p> <p align="center"> <img src="/images/2024/09/22/32.png" width="800px" /> </p> <p><br /></p> <p>*새 스테이지*를 선택하고, 스테이지 이름에 ‘v1’을 입력한다. ‘배포’를 누른다.</p> <p><br /></p> <p align="center"> <img src="/images/2024/09/22/33.png" width="800px" /> </p> <p><br /></p> <p>API 배포가 완료되었다.</p> <p><br /></p> <h3 id="3-s3-버킷-생성">3. S3 버킷 생성</h3> <p><br /></p> <p align="center"> <img src="/images/2024/09/22/34.png" width="800px" /> </p> <p><br /></p> <p>S3 서비스로 이동해 버킷 만들기를 클릭한다.</p> <p><br /></p> <p align="center"> <img src="/images/2024/09/22/35.png" width="800px" /> </p> <p><br /></p> <p align="center"> <img src="/images/2024/09/22/36.png" width="800px" /> </p> <p><br /></p> <p>앞에서 S3 업로드 정책에서 사용했던 ARN과 동일하게 S3 버킷을 만들어야 한다. 버킷 이름은 전 세계 모든 버킷에 대해 이름이 중복되면 안되므로, 만약 입력한 버킷명이 중복되었다면 변경 후 IAM 정책도 수정하도록 하자. 다른 설정들은 기본으로 두고 밑으로 쭉 내려 ‘버킷 만들기’를 완료한다.</p> <p><br /></p> <h3 id="4-테스트">4. 테스트</h3> <p>Postman 앱과 같은 API 테스트 툴을 다운로드 받는다. 다른 툴이 익숙하다면 그대로 사용해도 된다.</p> <p><br /></p> <p align="center"> <img src="/images/2024/09/22/36-1.png" width="800px" /> </p> <p><br /></p> <p>또한 <a href="https://www.espressoenglish.net/500audio/">링크</a>에서 테스트용 샘플 mp3파일 모음을 다운받아 압축을 해제한다.</p> <p><br /></p> <p align="center"> <img src="/images/2024/09/22/37.png" width="800px" /> </p> <p><br /></p> <p>API Gateway 스테이지 탭으로 가 앞서 생성한 API의 URL을 복사한다.</p> <p><br /></p> <p align="center"> <img src="/images/2024/09/22/38.png" width="800px" /> </p> <p><br /></p> <p>Postman 앱에 URL을 복사한다.</p> <p>메서드 종류는 PUT, URL은 복사한 텍스트 뒤에 ‘/업로드할 버킷명/업로드할 파일명’을 추가한다. 파일명은 앞서 정책에서 ARN 지정했던대로 my_audio로 시작해야 한다. 여기선 my_audio_1.mp3로 입력하자.</p> <p>Body 탭에선 입력 음성파일을 업로드할 수 있는데, 파일 타입은 ‘binary’(이진 타입)를 선택하고, 앞서 다운로드 받았던 음성 파일 샘플 중 하나를 선택한다.</p> <p>마지막으로 Send를 눌러 아래에 정상 응답(특별한 에러메세지가 없으며, 200 OK)이 도착했는지 확인한다.</p> <p><br /></p> <p align="center"> <img src="/images/2024/09/22/39.png" width="800px" /> </p> <p><br /></p> <p>생성한 파일이 정상적으로 버킷에 존재하는지 확인해보자. 정상적으로 음성 파일이 버킷에 업로드 된 것을 확인할 수 있다.</p> <p><br /></p> <p>남은 부분들은 <a href="https://clarit7.github.io/aws_tutorial_bedrock_2/">다음 게시글</a>로 이어진다.</p> <p><br /></p> <hr /> <p><br /></p> <h3 id="출처">출처</h3> <ul> <li><a href="https://www.youtube.com/watch?v=gXMZqaQC-T8">https://www.youtube.com/watch?v=gXMZqaQC-T8</a></li> <li><a href="https://www.espressoenglish.net/500audio/">https://www.espressoenglish.net/500audio/</a></li> </ul> <p><br /></p> Sun, 22 Sep 2024 00:00:00 +0000 https://clarit7.github.io/aws_tutorial_bedrock_1/ https://clarit7.github.io/aws_tutorial_bedrock_1/ aws bedrock cloud Obsidian github 동기화 세팅하기(iOS 자동화, 단축어 활용) <p>이 글은 Joshua Kim 님의 <a href="https://velog.io/@joshuara7235/옵시디언-사용해-보실래요">옵시디언 사용해 보실래요? - 동기화, 백업 환경 구축</a>의 내용을 참고해 작성되었습니다.</p> <p><br /></p> <hr /> <p><br /></p> <h3 id="옵시디언-입문">옵시디언 입문</h3> <p><br /></p> <p>메모 습관과 시스템을 바꿔보고자 obsidian에 입문했다. 여러 기기에 설치하고 동기화 세팅을 하려 했지만, 공식 동기화 플러그인 obsidian sync는 구독형 요금제이며 너무 비싸다는 단점이 있었다. 메모앱 하나에 일년에 10만원을 태워?</p> <p>현재 내가 사용하는 기기는 windows pc, mac, iphone, ipad 총 네대로, 서로 다른 플랫폼에서의 동기화 방법을 고민하다가 Joshua Kim 님의 글을 참고해 github를 사용한 동기화 세팅을 진행했다. 여기에 약간의 추가 세팅과 아이폰 자동화, 단축어 등을 사용해 조금 더 편하게 사용할 수 있게 만든 방법을 공유하고자 한다.</p> <p><br /></p> <hr /> <p><br /></p> <h3 id="1-깃헙에-원격-repository-만들기">1. 깃헙에 원격 repository 만들기</h3> <p><br /></p> <p align="center"> <img src="/images/2023/10/15/0.png" width="800px" /> </p> <p><br /></p> <p>깃헙의 Repositories 탭에서 New 버튼으로 새 원격 저장소를 생성한다.</p> <p><br /></p> <p align="center"> <img src="/images/2023/10/15/1.png" width="800px" /> </p> <p><br /></p> <p>저장소 이름 입력, 공개/비공개 설정, Create repository 클릭</p> <p><br /></p> <h3 id="2-pc에-git-client-활용해-원격-저장소-내려받기">2. PC에 git client 활용해 원격 저장소 내려받기</h3> <p><br /></p> <p>원격 저장소에서 로컬 기기로 원격 저장소를 내려받고 연결해야한다. 이 글에선 Mac을 기준으로 설명하지만, 리눅스와 윈도우즈에서도 크게 차이나는 점은 없다.</p> <p>혹시라도 git client를 어떻게 사용하는지 모른다면 <a href="https://www.lainyzine.com/ko/article/git-clone-command/">이 글</a>을 참고하자.</p> <p><br /></p> <p>먼저, 아래 명령어를 사용해 로컬 기기로 원격 저장소를 내려받는다.</p> <div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code>git clone https://github.com/<span class="o">{</span>본인 계정 이름<span class="o">}</span>/<span class="o">{</span>저장소 이름<span class="o">}</span> <span class="c"># 예시</span> git clone https://github.com/Clarit7/obsidian-sync.git </code></pre></div></div> <p><br /></p> <p>만약 private로 설정한 저장소의 경우엔 아래 명령어를 사용한다.</p> <div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code>git clone https://<span class="o">{</span>본인 계정 이름<span class="o">}</span>@github.com/<span class="o">{</span>본인 계정 이름<span class="o">}</span>/<span class="o">{</span>저장소 이름<span class="o">}</span> <span class="c"># 예시</span> git clone https://[email protected]/Clarit7/obsidian-sync.git </code></pre></div></div> <p><br /></p> <p>해당 폴더 내에 .gitignore 파일을 생성한다.</p> <p><code class="language-plaintext highlighter-rouge">vim .gitignore</code> 명령어로 생성해도 좋고, 다른 텍스트 편집기가 있다면 써도 좋다.</p> <p>.gitignore파일의 내용은 다음과 같이 작성한다.</p> <div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>.DS_Store .obsidian/ </code></pre></div></div> <p><br /></p> <p>.DS_Store는 맥에서 파인더를 사용할 때 생성되는 metadata이며, .obsidian폴더는 obsidiana의 설정값 등이 저장되는 폴더이다. 만약 .obsidian폴더를 ignore처리하지 않을 경우 여러 기기에서 pull, push를 진행하며 설정값 때문에 conflict가 날 확률이 크기 때문에 꼭 위와 같이 작성하는 것을 추천한다.</p> <p>.gitignore 작성이 완료되었으면, 최초 1회는 upstream branch 설정을 위해 수동으로 push하는 것을 추천한다.</p> <div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code>git commit <span class="nt">-m</span> <span class="s2">"커밋메세지"</span> git push <span class="nt">--set-upstream</span> origin main </code></pre></div></div> <p>만약 옛날에 만들어 놓은 저장소를 사용한다면 branch명이 master일 수도 있는데, 깃헙은 노예제를 연상시킨다는 이유로 더이상 master라는 브랜치명을 사용하지 않는다.</p> <p><br /></p> <p>혹시라도 이후 과정에서 옵시디언에서 원격 저장소에 연결되지 못하는 에러가 계속 발생한다면, 원격 저장소 연결을 삭제 후 다시 연결해보자.</p> <div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code>git remote remove origin git remote add origin https://github.com/<span class="o">{</span>본인 계정 이름<span class="o">}</span>/<span class="o">{</span>저장소 이름<span class="o">}</span> <span class="c"># public</span> git remote add origin https://<span class="o">{</span>본인 계정 이름<span class="o">}</span>@github.com/<span class="o">{</span>본인 계정 이름<span class="o">}</span>/<span class="o">{</span>저장소 이름<span class="o">}</span> <span class="c"># private</span> </code></pre></div></div> <p><br /></p> <h3 id="3-맥-윈도우-리눅스에서-obsidian-git-플러그인을-활용한-동기화-세팅">3. 맥, 윈도우, 리눅스에서 Obsidian Git 플러그인을 활용한 동기화 세팅</h3> <p><br /></p> <p>Obsidian을 실행한다. 만약 이미 다른 vault가 열려있다면 왼쪽 아래의 open another vault 버튼으로 vault 탐색기를 연다.</p> <p align="center"> <img src="/images/2023/10/15/2.png" width="800px" /> </p> <p><br /></p> <p>Open folder as vault 로 아까 로컬 저장소에 클론한 폴더를 연다.</p> <p align="center"> <img src="/images/2023/10/15/3.png" width="800px" /> </p> <p><br /></p> <p>설정 - Community plugins - Turn on community plugins</p> <p align="center"> <img src="/images/2023/10/15/4.png" width="800px" /> </p> <p><br /></p> <p>Community plugins가 활성화되면 Browse 버튼을 클릭한다.</p> <p align="center"> <img src="/images/2023/10/15/5.png" width="800px" /> </p> <p><br /></p> <p>Obsidian Git을 검색하고, 해당 플러그인을 선택해 설치한다.</p> <p align="center"> <img src="/images/2023/10/15/6.png" width="800px" /> </p> <p><br /></p> <p>Obsidian Git을 활성화시킨다.</p> <p align="center"> <img src="/images/2023/10/15/7.png" width="800px" /> </p> <p><br /></p> <p>메인화면으로 돌아와 command 창을 연다. 이후 git을 검색해 Switch branch를 클릭한다.</p> <p align="center"> <img src="/images/2023/10/15/8.png" width="800px" /> </p> <p><br /></p> <p>main 브랜치를 클릭한다.</p> <p align="center"> <img src="/images/2023/10/15/9.png" width="800px" /> </p> <p><br /></p> <p>이후 command 창을 열어 수동으로 commit과 push를 진행해도 좋고, 주기적으로 commit &amp; push와 pull을 진행하도록 설정해도 좋다.</p> <p>아래는 push, pull을 주기적으로 진행하도록 설정하는 방법이다. 원하는 push와 pull 간격을 분 단위로 입력한다.</p> <p align="center"> <img src="/images/2023/10/15/10.png" width="800px" /> </p> <p><br /></p> <p>Pull updates on startup 옵션도 키는 것을 추천한다.</p> <p align="center"> <img src="/images/2023/10/15/11.png" width="800px" /> </p> <p><br /></p> <p>테스트 파일을 만들어 실제 자동 동기화가 잘 이루어지는지 확인해보았다.</p> <p align="center"> <img src="/images/2023/10/15/12.png" width="800px" /> </p> <p><br /></p> <h3 id="4-ios-ipados-에서-working-copy-활용한-동기화-세팅">4. iOS, iPadOS 에서 Working Copy 활용한 동기화 세팅</h3> <p><br /></p> <p>모바일에선 Obsidian Git이 설치만 되고 작동하지 않으니, 다른 git client 앱을 활용해야 한다. 앱스토어에서 Working Copy 앱을 내려받고, 앱 내부에서 모든 기능 언락을 결제한다. (구독형이 아닌 일회성 결제 30,000원이라 충분히 살만하다.)</p> <p>아래에선 아이폰을 기준으로 설명한다. 아이패드에선 앱의 인터페이스가 조금 다를 수 있는 점은 참고.</p> <p><br /></p> <p align="center"> <img src="/images/2023/10/15/13.png" width="800px" /> </p> <p align="center"> <img src="/images/2023/10/15/14.png" width="800px" /> </p> <p><br /></p> <p>앱 실행 후 Clone repository를 선택한다.</p> <p align="center"> <img src="/images/2023/10/15/15.png" width="800px" /> </p> <p><br /></p> <p>GitHub 탭에서 저장소를 선택해준다.</p> <p align="center"> <img src="/images/2023/10/15/16.png" width="800px" /> </p> <p><br /></p> <p>선택하면 URL 탭으로 넘어가는데, 이때 프로토콜은 https를 선택해주고, User에 본인 깃헙 닉네임을 입력해준다. (ssh프로토콜에선 문제가 생길 수 있다는 reddit 글을 본 것 같은데 확실하진 않음) 이후 Clone을 눌러 원격 저장소를 내려받는다.</p> <p align="center"> <img src="/images/2023/10/15/17.png" width="800px" /> </p> <p><br /></p> <p>옵시디언을 실행해 왼쪽 위 탭을 누른다.</p> <p align="center"> <img src="/images/2023/10/15/18.png" width="800px" /> </p> <p><br /></p> <p>Vault 이름 옆 화살표를 누르고 Manage vaults… 를 선택.</p> <p align="center"> <img src="/images/2023/10/15/19.png" width="800px" /> </p> <p><br /></p> <p>Create new vault를 선택한다.</p> <p align="center"> <img src="/images/2023/10/15/20.png" width="800px" /> </p> <p><br /></p> <p>원격 저장소와 같은 이름으로 Vault name을 입력하고, Store in iCloud는 체크 해제상태로 Create를 실행한다.</p> <p align="center"> <img src="/images/2023/10/15/21.png" width="800px" /> </p> <p><br /></p> <p>Working Copy 앱으로 돌아가, 내려받은 저장소를 선택 후 Repository를 설정 창으로 넘어간다.</p> <p align="center"> <img src="/images/2023/10/15/22.png" width="800px" /> </p> <p><br /></p> <p>저장소명 오른쪽의 화살표를 선택 후, Link Repository to를 선택한다.</p> <p align="center"> <img src="/images/2023/10/15/23.png" width="800px" /> </p> <p><br /></p> <p>Directory를 누른다.</p> <p align="center"> <img src="/images/2023/10/15/24.png" width="800px" /> </p> <p><br /></p> <p>파일탐색기가 열리면 나의 iPhone 최상단 폴더 - Obsidian 폴더로 들어가면 아까 옵시디언에서 생성한 Vault가 있다.</p> <p align="center"> <img src="/images/2023/10/15/25.png" width="800px" /> </p> <p><br /></p> <p>해당 Vault를 선택 후 열기를 누른다.</p> <p align="center"> <img src="/images/2023/10/15/26.png" width="800px" /> </p> <p><br /></p> <p>이러면 Obsidian mobile에서 생성한 Vault와 Working Copy를 통해서 Clone한 저장소가 연결된다. 아까 만들었던 Test.md 파일도 정상적으로 표시되고 있다.</p> <p align="center"> <img src="/images/2023/10/15/27.png" width="800px" /> </p> <p><br /></p> <p>이후 Working Copy를 통해 commit, pull, push를 진행해도 되지만, 옵시디언에서 메모 후 Working Copy를 통해 수동으로 백업을 해야 한다는 점이 귀찮으니 자동화와 단축어를 이용해 앱이 실행되고 종료될때마다 자동으로 백업과 메모 최신화가 될 수 있도록 세팅해보자.</p> <p><br /></p> <h3 id="5-pull-push-단축어-생성-및-자동화-설정">5. pull, push 단축어 생성 및 자동화 설정</h3> <p><br /></p> <p>아래 단축어를 다운로드 받는다.</p> <p><br /></p> <p><a href="https://www.icloud.com/shortcuts/8e800ff3399949cd857131c1336766d5">Pull 단축어 다운로드</a></p> <p align="center"> <img src="/images/2023/10/15/28.png" width="800px" /> </p> <p><br /></p> <p><a href="https://www.icloud.com/shortcuts/83864d76d5574a49b2749b9ed1c801bd">Push 단축어 다운로드</a></p> <p align="center"> <img src="/images/2023/10/15/29.png" width="800px" /> </p> <p><br /></p> <p>Pull, Push 둘 다 모든 빈칸에 전부 obsidian 저장소를 선택해 채워넣는다.</p> <p align="center"> <img src="/images/2023/10/15/30.png" width="800px" /> </p> <p><br /></p> <p>자동화 탭으로 가서 추가를 선택한다.</p> <p align="center"> <img src="/images/2023/10/15/31.png" width="800px" /> </p> <p><br /></p> <p>앱이 열리거나 닫힐때 조건을 선택한다.</p> <p align="center"> <img src="/images/2023/10/15/32.png" width="800px" /> </p> <p><br /></p> <p>앱 - Obsidian, 조건 - 열릴 때, 즉시실행 으로 세팅 후 다음을 누른다.</p> <p align="center"> <img src="/images/2023/10/15/33.png" width="800px" /> </p> <p><br /></p> <p>나의 단축어로 들어간다.</p> <p align="center"> <img src="/images/2023/10/15/34.png" width="800px" /> </p> <p><br /></p> <p>Pull shortcut을 선택한다.</p> <p align="center"> <img src="/images/2023/10/15/35.png" width="800px" /> </p> <p><br /></p> <p>Push shorcut도 동일하게 세팅하면 된다. 단, 조건은 앱이 닫힐 때로 바꿔준다.</p> <p>이제 제대로 업데이트가 이루어지는지 확인해보자. 옵시디언에서 새로운 문서를 작성한 후 앱을 종료하면 이렇게 귀찮은 commit과 push가 앱을 닫을때 자동으로 스무스하게 실행된다!</p> <p align="center"> <img src="/images/2023/10/15/backup.webp" width="800px" /> </p> <p><br /></p> <p>마찬가지로 앱을 실행할때 자동으로 pull이 된다.</p> <p>혹시라도 이 세팅으로 잘 사용하다가 pull, push가 어느 순간부터 에러가 난다면 Working Copy에서 발생하는 에러일 수 있다. 그럴땐 4번 과정에서 Link Repository to를 다시 해주면 해결된다.</p> <p><br /></p> <hr /> <p><br /></p> <h3 id="세팅을-마치며">세팅을 마치며</h3> <p><br /></p> <p>이렇게 안드로이드를 제외한 모든 플랫폼에서 자동으로 백업과 동기화가 이루어지도록 옵시디언 세팅을 완료할 수 있었다.</p> <p>알아본 바로는 Remotely Save라는 커뮤니티 플러그인과 드롭박스, 구글 드라이브의 조합, 또는 icloud 등을 이용해도 충분히 동기화 세팅을 진행할 수 있다. 그러나 이렇게 github을 이용하는 방법이 메모의 히스토리까지 저장 가능하다는 점, conflict가 생기는 상황에서 익숙한 방법으로 해결이 가능하다는 점이 좋아 이 방법을 선택했다.</p> <p>사실 어떻게 세팅하든, 정작 메모를 적극적으로 활용하지 못한다면 의미가 없을 것이다. 앞으로 <a href="https://youtu.be/lkRQuMIbFYc?si=a94NSEd40HPS8RUW">PARA 노트 정리법</a>에 따라 메모를 작성하며 생산성을 높여봐야겠다.</p> Sun, 15 Oct 2023 00:00:00 +0000 https://clarit7.github.io/obsidian_sync_setting/ https://clarit7.github.io/obsidian_sync_setting/ obsidian github automation shortcut tech (MLOps) MLFlow 기초 - 모델 배포 및 쿼리 <p>이 글은 에이콘 출판사의 ‘MLFlow를 활용한 MLOps’ 도서의 내용을 참고해 작성되었습니다.</p> <p><br /></p> <hr /> <p><br /></p> <h3 id="붓꽃-분류-모델-배포하기">붓꽃 분류 모델 배포하기</h3> <p><br /></p> <p>지난 포스트 <a href="https://clarit7.github.io/mlflow_tutorial/">MLFlow로 붓꽃 분류 모델 로깅하기</a>에서 이어진다.</p> <p>이번 포스트에선 저장된 모델을 배포하고 쿼리문으로 POST request를 작성해 inference를 수행하는 방법에 대해 알아본다.</p> <p><br /></p> <hr /> <p><br /></p> <h3 id="데이터-로드-및-스케일링">데이터 로드 및 스케일링</h3> <p><br /></p> <p>필요한 패키지를 불러온다.</p> <div class="language-python highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kn">import</span> <span class="nn">numpy</span> <span class="k">as</span> <span class="n">np</span> <span class="kn">import</span> <span class="nn">pandas</span> <span class="k">as</span> <span class="n">pd</span> <span class="kn">import</span> <span class="nn">matplotlib.pyplot</span> <span class="k">as</span> <span class="n">plt</span> <span class="kn">import</span> <span class="nn">seaborn</span> <span class="k">as</span> <span class="n">sns</span> <span class="kn">from</span> <span class="nn">sklearn.model_selection</span> <span class="kn">import</span> <span class="n">train_test_split</span> <span class="kn">from</span> <span class="nn">sklearn.preprocessing</span> <span class="kn">import</span> <span class="n">StandardScaler</span> <span class="kn">from</span> <span class="nn">sklearn.datasets</span> <span class="kn">import</span> <span class="n">load_iris</span> <span class="kn">from</span> <span class="nn">sklearn.metrics</span> <span class="kn">import</span> <span class="n">accuracy_score</span><span class="p">,</span> <span class="n">confusion_matrix</span> <span class="kn">import</span> <span class="nn">subprocess</span> <span class="kn">import</span> <span class="nn">json</span> </code></pre></div></div> <p><br /></p> <p>붓꽃 데이터를 불러온다.</p> <div class="language-python highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="n">iris</span> <span class="o">=</span> <span class="n">load_iris</span><span class="p">()</span> <span class="n">iris_data</span> <span class="o">=</span> <span class="n">pd</span><span class="p">.</span><span class="n">DataFrame</span><span class="p">(</span><span class="n">data</span><span class="o">=</span><span class="n">np</span><span class="p">.</span><span class="n">c_</span><span class="p">[</span><span class="n">iris</span><span class="p">[</span><span class="s">'data'</span><span class="p">],</span> <span class="n">iris</span><span class="p">[</span><span class="s">'target'</span><span class="p">]],</span> <span class="n">columns</span><span class="o">=</span><span class="n">iris</span><span class="p">[</span><span class="s">'feature_names'</span><span class="p">]</span><span class="o">+</span><span class="p">[</span><span class="s">'target'</span><span class="p">])</span> <span class="n">x_data</span> <span class="o">=</span> <span class="n">iris_data</span><span class="p">.</span><span class="n">iloc</span><span class="p">[:,</span> <span class="p">:</span><span class="o">-</span><span class="mi">1</span><span class="p">]</span> <span class="n">y_data</span> <span class="o">=</span> <span class="n">iris_data</span><span class="p">.</span><span class="n">iloc</span><span class="p">[:,[</span><span class="o">-</span><span class="mi">1</span><span class="p">]]</span> </code></pre></div></div> <p><br /></p> <p align="center"> <img src="/images/2022/05/09/0.png" width="800px" /> <figcaption style="text-align:center; font-size:12px; color:#808080"> 데이터 불러오기 </figcaption> </p> <p><br /></p> <p>타겟별로 데이터를 구분하고, 스케일러를 Fitting한다.</p> <p>현재 예제에서는 스케일러를 새로 정의했지만, 이런 Fitting이 필요한 오브젝트들은 훈련 후 같이 파이프라인에 저장해서 Inference 과정을 자동화 할 수 있다.</p> <div class="language-python highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="n">setosa</span> <span class="o">=</span> <span class="n">iris_data</span><span class="p">[</span><span class="n">iris_data</span><span class="p">.</span><span class="n">target</span> <span class="o">==</span> <span class="mf">0.0</span><span class="p">]</span> <span class="n">versicolor</span> <span class="o">=</span> <span class="n">iris_data</span><span class="p">[</span><span class="n">iris_data</span><span class="p">.</span><span class="n">target</span> <span class="o">==</span> <span class="mf">1.0</span><span class="p">]</span> <span class="n">virginica</span> <span class="o">=</span> <span class="n">iris_data</span><span class="p">[</span><span class="n">iris_data</span><span class="p">.</span><span class="n">target</span> <span class="o">==</span> <span class="mf">2.0</span><span class="p">]</span> <span class="n">scaler</span> <span class="o">=</span> <span class="n">StandardScaler</span><span class="p">()</span> <span class="n">scaler</span><span class="p">.</span><span class="n">fit</span><span class="p">(</span><span class="n">pd</span><span class="p">.</span><span class="n">concat</span><span class="p">((</span><span class="n">setosa</span><span class="p">.</span><span class="n">sample</span><span class="p">(</span><span class="n">frac</span><span class="o">=</span><span class="mf">0.5</span><span class="p">),</span> <span class="n">versicolor</span><span class="p">.</span><span class="n">sample</span><span class="p">(</span><span class="n">frac</span><span class="o">=</span><span class="mf">0.5</span><span class="p">),</span> <span class="n">virginica</span><span class="p">.</span><span class="n">sample</span><span class="p">(</span><span class="n">frac</span><span class="o">=</span><span class="mf">0.5</span><span class="p">))).</span><span class="n">drop</span><span class="p">(</span><span class="s">'target'</span><span class="p">,</span> <span class="n">axis</span><span class="o">=</span><span class="mi">1</span><span class="p">))</span> </code></pre></div></div> <p><br /></p> <p>입력데이터에서 타겟을 분리시키고, 스케일러에 입력시켜 표준화한다.</p> <p>데이터프레임을 json으로 변환할 때 현재 코드에선 “split” 형식으로 변환했다.</p> <div class="language-python highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="n">x_input</span> <span class="o">=</span> <span class="n">pd</span><span class="p">.</span><span class="n">concat</span><span class="p">((</span><span class="n">setosa_train</span><span class="p">,</span> <span class="n">versicolor_train</span><span class="p">,</span> <span class="n">virginica_train</span><span class="p">)).</span><span class="n">sample</span><span class="p">(</span><span class="n">n</span><span class="o">=</span><span class="mi">20</span><span class="p">)</span> <span class="n">y</span> <span class="o">=</span> <span class="n">np</span><span class="p">.</span><span class="n">array</span><span class="p">(</span><span class="n">x_input</span><span class="p">[</span><span class="s">'target'</span><span class="p">])</span> <span class="n">x_input</span><span class="p">.</span><span class="n">drop</span><span class="p">(</span><span class="s">'target'</span><span class="p">,</span> <span class="n">axis</span><span class="o">=</span><span class="mi">1</span><span class="p">,</span> <span class="n">inplace</span><span class="o">=</span><span class="bp">True</span><span class="p">)</span> <span class="n">x_input</span> <span class="o">=</span> <span class="n">pd</span><span class="p">.</span><span class="n">DataFrame</span><span class="p">(</span><span class="n">scaler</span><span class="p">.</span><span class="n">transform</span><span class="p">(</span><span class="n">x_input</span><span class="p">)).</span><span class="n">to_json</span><span class="p">(</span><span class="n">orient</span><span class="o">=</span><span class="s">"split"</span><span class="p">)</span> </code></pre></div></div> <p><br /></p> <h3 id="모델-배포">모델 배포</h3> <p><br /></p> <p>이제 ui 서버를 열고 저장된 모델을 클릭한다.</p> <div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code>mlflow ui <span class="nt">-p</span> 1234 </code></pre></div></div> <p><br /></p> <p align="center"> <img src="/images/2022/05/09/1.png" width="800px" /> <figcaption style="text-align:center; font-size:12px; color:#808080"> 실험 클릭 -&gt; 실행 클릭 </figcaption> </p> <p><br /></p> <p align="center"> <img src="/images/2022/05/09/2.png" width="800px" /> <figcaption style="text-align:center; font-size:12px; color:#808080"> 표시된 부분에서 실행명과 모델명을 확인할 수 있다. </figcaption> </p> <p><br /></p> <p>터미널에서 새 탭이나 새 창을 열고 Inference 서버를 실행하며 모델을 배포한다. 주소나 포트 번호는 ui 서버와 다르게 사용해야 한다.</p> <p>(iterm 기준 command + t 로 새 탭 열기가 가능하다.)</p> <div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code>mlflow models serve <span class="nt">--model-uri</span> runs:/e45b49292ea64807844ccd436d379672/log_reg_model <span class="nt">-p</span> 1235 </code></pre></div></div> <p><br /></p> <p align="center"> <img src="/images/2022/05/09/3.png" width="800px" /> <figcaption style="text-align:center; font-size:12px; color:#808080"> 서버 실행 결과 </figcaption> </p> <p><br /></p> <h3 id="post-request-쿼리를-사용한-inference">POST request 쿼리를 사용한 inference</h3> <p><br /></p> <p>다음과 같이 쿼리문을 작성하고, subprocess에 쿼리문을 입력해 실행하면 다음과 같은 결과를 얻을 수 있다.</p> <div class="language-python highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="n">query</span> <span class="o">=</span> <span class="p">[</span><span class="s">"curl"</span><span class="p">,</span> <span class="s">"-X"</span><span class="p">,</span> <span class="s">"POST"</span><span class="p">,</span> <span class="s">"-H"</span><span class="p">,</span> <span class="s">"Content-Type:application/json; format=pandas-split"</span><span class="p">,</span> <span class="s">"--data"</span><span class="p">,</span> <span class="n">x_input</span><span class="p">,</span> <span class="s">"http://127.0.0.1:1235/invocations"</span><span class="p">]</span> <span class="n">proc</span> <span class="o">=</span> <span class="n">subprocess</span><span class="p">.</span><span class="n">run</span><span class="p">(</span><span class="n">query</span><span class="p">,</span> <span class="n">stdout</span><span class="o">=</span><span class="n">subprocess</span><span class="p">.</span><span class="n">PIPE</span><span class="p">,</span> <span class="n">encoding</span><span class="o">=</span><span class="s">'utf-8'</span><span class="p">)</span> <span class="n">output</span> <span class="o">=</span> <span class="n">proc</span><span class="p">.</span><span class="n">stdout</span> <span class="n">preds</span> <span class="o">=</span> <span class="n">pd</span><span class="p">.</span><span class="n">DataFrame</span><span class="p">([</span><span class="n">json</span><span class="p">.</span><span class="n">loads</span><span class="p">(</span><span class="n">output</span><span class="p">)])</span> <span class="n">preds</span> </code></pre></div></div> <p><br /></p> <p align="center"> <img src="/images/2022/05/09/4.png" width="800px" /> <figcaption style="text-align:center; font-size:12px; color:#808080"> POST request inference 수행 결과 </figcaption> </p> <p><br /></p> <p>이때 이 쿼리문의 각각의 명령어와 파라미터들은 다음과 같은 의미를 가진다.</p> <ul> <li><code class="language-plaintext highlighter-rouge">curl</code> : cli에서 데이터 전송을 위해 사용하는 라이브러리인 cURL의 명령어</li> <li><code class="language-plaintext highlighter-rouge">-X POST</code> : REST API 메소드를 POST로 설정한다.</li> <li><code class="language-plaintext highlighter-rouge">-H Content-Type:application/json</code> : 헤더의 컨텐츠 타입을 application/json으로 설정한다. {key:value}의 형태로 전송된다.</li> <li><code class="language-plaintext highlighter-rouge">format=pandas-split</code> : json의 형식은 pandas-split으로 지정한다.</li> <li><code class="language-plaintext highlighter-rouge">--data {x_input}</code> : 데이터엔 json 오브젝트가 입력된다.</li> <li><code class="language-plaintext highlighter-rouge">http://127.0.0.1:1235/invocations</code> : POST request를 전달할 서버 주소</li> </ul> <p><br /></p> <p>마지막으로 Inference 결과물이 제대로 예측됐는지 확인해본다.</p> <div class="language-python highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="n">val_acc</span> <span class="o">=</span> <span class="n">accuracy_score</span><span class="p">(</span><span class="n">y</span><span class="p">,</span> <span class="n">preds</span><span class="p">.</span><span class="n">T</span><span class="p">)</span> <span class="n">eval_acc</span> </code></pre></div></div> <div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="o">&gt;&gt;&gt;</span> 0.9 </code></pre></div></div> <p><br /></p> <div class="language-python highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="n">conf_matrix</span> <span class="o">=</span> <span class="n">confusion_matrix</span><span class="p">(</span><span class="n">y</span><span class="p">,</span> <span class="n">preds</span><span class="p">.</span><span class="n">T</span><span class="p">)</span> <span class="n">ax</span> <span class="o">=</span> <span class="n">sns</span><span class="p">.</span><span class="n">heatmap</span><span class="p">(</span><span class="n">conf_matrix</span><span class="p">,</span> <span class="n">annot</span><span class="o">=</span><span class="bp">True</span><span class="p">,</span> <span class="n">fmt</span><span class="o">=</span><span class="s">'g'</span><span class="p">)</span> <span class="n">ax</span><span class="p">.</span><span class="n">invert_xaxis</span><span class="p">()</span> <span class="n">ax</span><span class="p">.</span><span class="n">invert_yaxis</span><span class="p">()</span> <span class="n">plt</span><span class="p">.</span><span class="n">ylabel</span><span class="p">(</span><span class="s">'Actual'</span><span class="p">)</span> <span class="n">plt</span><span class="p">.</span><span class="n">xlabel</span><span class="p">(</span><span class="s">'Predicted'</span><span class="p">)</span> <span class="n">plt</span><span class="p">.</span><span class="n">title</span><span class="p">(</span><span class="s">"Confusion Matrix"</span><span class="p">)</span> </code></pre></div></div> <p><br /></p> <p align="center"> <img src="/images/2022/05/09/5.png" width="600px" /> <figcaption style="text-align:center; font-size:12px; color:#808080"> Confusion Matrix </figcaption> </p> <p><br /></p> <p>20개 중 총 18개의 데이터에 대해 예측에 성공했다.</p> <p><br /></p> <hr /> <p><br /></p> <h3 id="출처">출처</h3> <p><br /></p> <ul> <li> <p><a href="http://acornpub.co.kr/book/mlops-mlflow">http://acornpub.co.kr/book/mlops-mlflow</a></p> </li> <li> <p><a href="https://computer-nerd.tistory.com/54">https://computer-nerd.tistory.com/54</a></p> </li> </ul> Mon, 09 May 2022 00:00:00 +0000 https://clarit7.github.io/mlflow_deploy/ https://clarit7.github.io/mlflow_deploy/ mlops mlflow mlops (MLOps) MLFlow 기초 - 실행 및 로깅 <p>이 글은 에이콘 출판사의 ‘MLFlow를 활용한 MLOps’ 도서의 내용을 참고해 작성되었습니다.</p> <p><br /></p> <hr /> <p><br /></p> <h3 id="mlops란">MLOps란?</h3> <p>‘Machine Learning Operations’의 약자로, 데이터 적재, 전처리, 모델 훈련, 모델 저장, 테스트와 검증, 모델 배포, 모니터링 등 머신러닝의 생명주기에 포함된 모든 과정과 이를 효율적으로 관리하는 방법론을 통칭한다.</p> <p>MLOps 엔지니어가 이 모든 과정을 담당한다면 리서처들은 데이터의 활용, 모델의 설계, 연구와 같은 과정에만 전념할 수 있게 된다. 특히 데이터 중심의 AI가 각광받음에 따라서 데이터의 품질 관리가 점점 중요해지고 있기에 연구자들이 고품질의 데이터를 활용할 수 있도록 돕는 MLOps 엔지니어의 역할이 더욱 커지는 상황이라고 생각한다. 물론 다양한 MLOps관련 도구에서 실험과 연관된 막강한 기능들을 지원하니, 연구자라도 스스로 MLOps에 대한 이해가 높다면 더욱 효율적인 실험 진행도 가능할 것이다!</p> <p>개념 자체는 소프트웨어의 생명 주기를 책임지는 과정인 DevOps와 비슷하지만, 코드 중심이 아닌 데이터와 모델 중심의 관리라는 점에서 약간의 차이가 있다.</p> <p><br /></p> <hr /> <p><br /></p> <h3 id="mlflow란">MLFlow란?</h3> <p>MLFlow는 대중적인 MLOps 오픈소스 라이브러리 중 하나이다. 강력한 로깅과 트래킹 기능, 도커와 콘다를 포함한 가상환경 파이프라인의 재사용성 확보, 모델 저장과 서버 배포 관리, 다양한 API와 UI기능 지원을 통한 확장성까지 이름처럼 머신러닝의 ‘흐름’에 필요한 다양한 기능을 활용할 수 있다. <a href="https://github.com/mlflow/mlflow">깃헙 레포지터리 링크</a></p> <p><br /></p> <p align="center"> <img src="/images/2022/04/12/0.png" width="800px" /> <figcaption style="text-align:center; font-size:12px; color:#808080"> MLFlow 홈페이지 </figcaption> </p> <p><br /></p> <hr /> <p><br /></p> <h3 id="mlflow-설치">MLFlow 설치</h3> <p><br /></p> <p>MLFlow 설치는 번거로운 과정 없이 pip를 통해 가능하다.</p> <div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code>python3 <span class="nt">-m</span> pip <span class="nb">install </span>mlflow </code></pre></div></div> <p><br /></p> <hr /> <p><br /></p> <h3 id="붓꽃-데이터-로지스틱-회귀-예제">붓꽃 데이터 로지스틱 회귀 예제</h3> <p><br /></p> <p>먼저, 필요한 데이터와 붓꽃 데이터를 불러온다.</p> <div class="language-python highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kn">import</span> <span class="nn">numpy</span> <span class="k">as</span> <span class="n">np</span> <span class="kn">import</span> <span class="nn">pandas</span> <span class="k">as</span> <span class="n">pd</span> <span class="kn">import</span> <span class="nn">matplotlib.pyplot</span> <span class="k">as</span> <span class="n">plt</span> <span class="kn">import</span> <span class="nn">seaborn</span> <span class="k">as</span> <span class="n">sns</span> <span class="kn">from</span> <span class="nn">sklearn.linear_model</span> <span class="kn">import</span> <span class="n">LogisticRegression</span> <span class="kn">from</span> <span class="nn">sklearn.model_selection</span> <span class="kn">import</span> <span class="n">train_test_split</span> <span class="kn">from</span> <span class="nn">sklearn.preprocessing</span> <span class="kn">import</span> <span class="n">StandardScaler</span> <span class="kn">from</span> <span class="nn">sklearn.metrics</span> <span class="kn">import</span> <span class="n">confusion_matrix</span> <span class="kn">from</span> <span class="nn">sklearn.datasets</span> <span class="kn">import</span> <span class="n">load_iris</span> <span class="kn">import</span> <span class="nn">mlflow</span> <span class="kn">import</span> <span class="nn">mlflow.sklearn</span> </code></pre></div></div> <p><br /></p> <p>데이터를 판다스 데이터프레임으로 변환한다.</p> <div class="language-python highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="n">iris</span> <span class="o">=</span> <span class="n">load_iris</span><span class="p">()</span> <span class="n">iris_data</span> <span class="o">=</span> <span class="n">pd</span><span class="p">.</span><span class="n">DataFrame</span><span class="p">(</span><span class="n">data</span><span class="o">=</span><span class="n">np</span><span class="p">.</span><span class="n">c_</span><span class="p">[</span><span class="n">iris</span><span class="p">[</span><span class="s">'data'</span><span class="p">],</span> <span class="n">iris</span><span class="p">[</span><span class="s">'target'</span><span class="p">]],</span> <span class="n">columns</span><span class="o">=</span><span class="n">iris</span><span class="p">[</span><span class="s">'feature_names'</span><span class="p">]</span><span class="o">+</span><span class="p">[</span><span class="s">'target'</span><span class="p">])</span> </code></pre></div></div> <div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>iris_data </code></pre></div></div> <p align="center"> <img src="/images/2022/04/12/1.png" width="800px" /> <figcaption style="text-align:center; font-size:12px; color:#808080"> DataFrame </figcaption> </p> <p><br /></p> <p>컬럼을 feature와 target으로 분할한다.</p> <div class="language-python highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="n">x_data</span> <span class="o">=</span> <span class="n">iris_data</span><span class="p">.</span><span class="n">iloc</span><span class="p">[:,</span> <span class="p">:</span><span class="o">-</span><span class="mi">1</span><span class="p">]</span> <span class="n">y_data</span> <span class="o">=</span> <span class="n">iris_data</span><span class="p">.</span><span class="n">iloc</span><span class="p">[:,[</span><span class="o">-</span><span class="mi">1</span><span class="p">]]</span> </code></pre></div></div> <p><br /></p> <p>클래스별로 변수를 따로 할당한다.</p> <div class="language-python highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="n">setosa</span> <span class="o">=</span> <span class="n">iris_data</span><span class="p">[</span><span class="n">iris_data</span><span class="p">.</span><span class="n">target</span> <span class="o">==</span> <span class="mf">0.0</span><span class="p">]</span> <span class="n">versicolor</span> <span class="o">=</span> <span class="n">iris_data</span><span class="p">[</span><span class="n">iris_data</span><span class="p">.</span><span class="n">target</span> <span class="o">==</span> <span class="mf">1.0</span><span class="p">]</span> <span class="n">virginica</span> <span class="o">=</span> <span class="n">iris_data</span><span class="p">[</span><span class="n">iris_data</span><span class="p">.</span><span class="n">target</span> <span class="o">==</span> <span class="mf">2.0</span><span class="p">]</span> <span class="k">print</span><span class="p">(</span><span class="s">"Setosa : {}"</span><span class="p">.</span><span class="nb">format</span><span class="p">(</span><span class="n">setosa</span><span class="p">.</span><span class="n">shape</span><span class="p">))</span> <span class="k">print</span><span class="p">(</span><span class="s">"Versicolor : {}"</span><span class="p">.</span><span class="nb">format</span><span class="p">(</span><span class="n">versicolor</span><span class="p">.</span><span class="n">shape</span><span class="p">))</span> <span class="k">print</span><span class="p">(</span><span class="s">"Virginica : {}"</span><span class="p">.</span><span class="nb">format</span><span class="p">(</span><span class="n">virginica</span><span class="p">.</span><span class="n">shape</span><span class="p">))</span> </code></pre></div></div> <div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>&gt;&gt;&gt; Setosa : (50, 5) Versicolor : (50, 5) Virginica : (50, 5) </code></pre></div></div> <p><br /></p> <p>훈련셋과 테스트셋으로 분할한다.</p> <div class="language-python highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="n">setosa_train</span><span class="p">,</span> <span class="n">setosa_test</span> <span class="o">=</span> <span class="n">train_test_split</span><span class="p">(</span><span class="n">setosa</span><span class="p">,</span> <span class="n">test_size</span><span class="o">=</span><span class="mf">0.2</span><span class="p">)</span> <span class="n">versicolor_train</span><span class="p">,</span> <span class="n">versicolor_test</span> <span class="o">=</span> <span class="n">train_test_split</span><span class="p">(</span><span class="n">versicolor</span><span class="p">,</span> <span class="n">test_size</span><span class="o">=</span><span class="mf">0.2</span><span class="p">)</span> <span class="n">virginica_train</span><span class="p">,</span> <span class="n">virginica_test</span> <span class="o">=</span> <span class="n">train_test_split</span><span class="p">(</span><span class="n">virginica</span><span class="p">,</span> <span class="n">test_size</span><span class="o">=</span><span class="mf">0.2</span><span class="p">)</span> </code></pre></div></div> <p><br /></p> <p>나눴던 변수를 다시 concat한다.</p> <div class="language-python highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="n">x_train</span> <span class="o">=</span> <span class="n">pd</span><span class="p">.</span><span class="n">concat</span><span class="p">((</span><span class="n">setosa_train</span><span class="p">,</span> <span class="n">versicolor_train</span><span class="p">,</span> <span class="n">virginica_train</span><span class="p">))</span> <span class="n">x_test</span> <span class="o">=</span> <span class="n">pd</span><span class="p">.</span><span class="n">concat</span><span class="p">((</span><span class="n">setosa_test</span><span class="p">,</span> <span class="n">versicolor_test</span><span class="p">,</span> <span class="n">virginica_test</span><span class="p">))</span> <span class="n">y_train</span> <span class="o">=</span> <span class="n">np</span><span class="p">.</span><span class="n">array</span><span class="p">(</span><span class="n">x_train</span><span class="p">[</span><span class="s">'target'</span><span class="p">])</span> <span class="n">y_test</span> <span class="o">=</span> <span class="n">np</span><span class="p">.</span><span class="n">array</span><span class="p">(</span><span class="n">x_test</span><span class="p">[</span><span class="s">'target'</span><span class="p">])</span> <span class="n">x_train</span><span class="p">.</span><span class="n">drop</span><span class="p">(</span><span class="s">'target'</span><span class="p">,</span> <span class="n">axis</span><span class="o">=</span><span class="mi">1</span><span class="p">,</span> <span class="n">inplace</span><span class="o">=</span><span class="bp">True</span><span class="p">)</span> <span class="n">x_test</span><span class="p">.</span><span class="n">drop</span><span class="p">(</span><span class="s">'target'</span><span class="p">,</span> <span class="n">axis</span><span class="o">=</span><span class="mi">1</span><span class="p">,</span> <span class="n">inplace</span><span class="o">=</span><span class="bp">True</span><span class="p">)</span> </code></pre></div></div> <div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>&gt;&gt;&gt; Training sets: x_train: (120, 4) y_train: (120,) Testing sets: x_test: (30, 4) y_test: (30,) </code></pre></div></div> <p><br /></p> <p>피처 스케일링</p> <div class="language-python highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="n">scaler</span> <span class="o">=</span> <span class="n">StandardScaler</span><span class="p">()</span> <span class="n">scaler</span><span class="p">.</span><span class="n">fit</span><span class="p">(</span><span class="n">pd</span><span class="p">.</span><span class="n">concat</span><span class="p">((</span><span class="n">setosa</span><span class="p">,</span> <span class="n">versicolor</span><span class="p">,</span> <span class="n">virginica</span><span class="p">)).</span><span class="n">drop</span><span class="p">(</span><span class="s">'target'</span><span class="p">,</span> <span class="n">axis</span><span class="o">=</span><span class="mi">1</span><span class="p">))</span> <span class="n">x_train</span> <span class="o">=</span> <span class="n">scaler</span><span class="p">.</span><span class="n">transform</span><span class="p">(</span><span class="n">x_train</span><span class="p">)</span> <span class="n">x_test</span> <span class="o">=</span> <span class="n">scaler</span><span class="p">.</span><span class="n">transform</span><span class="p">(</span><span class="n">x_test</span><span class="p">)</span> </code></pre></div></div> <p><br /></p> <p>훈련 함수 작성</p> <div class="language-python highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">def</span> <span class="nf">train</span><span class="p">(</span><span class="n">sklearn_model</span><span class="p">,</span> <span class="n">x_train</span><span class="p">,</span> <span class="n">y_train</span><span class="p">):</span> <span class="n">sklearn_model</span> <span class="o">=</span> <span class="n">sklearn_model</span><span class="p">.</span><span class="n">fit</span><span class="p">(</span><span class="n">x_train</span><span class="p">,</span> <span class="n">y_train</span><span class="p">)</span> <span class="n">train_acc</span> <span class="o">=</span> <span class="n">sklearn_model</span><span class="p">.</span><span class="n">score</span><span class="p">(</span><span class="n">x_train</span><span class="p">,</span> <span class="n">y_train</span><span class="p">)</span> <span class="n">mlflow</span><span class="p">.</span><span class="n">log_metric</span><span class="p">(</span><span class="s">"train_acc"</span><span class="p">,</span> <span class="n">train_acc</span><span class="p">)</span> <span class="k">print</span><span class="p">(</span><span class="s">"Train Accuracy: {:.3%}"</span><span class="p">.</span><span class="nb">format</span><span class="p">(</span><span class="n">train_acc</span><span class="p">))</span> </code></pre></div></div> <p><br /></p> <p>평가 함수 작성</p> <div class="language-python highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">def</span> <span class="nf">evaluate</span><span class="p">(</span><span class="n">sklearn_model</span><span class="p">,</span> <span class="n">x_test</span><span class="p">,</span> <span class="n">y_test</span><span class="p">):</span> <span class="n">eval_acc</span> <span class="o">=</span> <span class="n">sklearn_model</span><span class="p">.</span><span class="n">score</span><span class="p">(</span><span class="n">x_test</span><span class="p">,</span> <span class="n">y_test</span><span class="p">)</span> <span class="n">preds</span> <span class="o">=</span> <span class="n">sklearn_model</span><span class="p">.</span><span class="n">predict</span><span class="p">(</span><span class="n">x_test</span><span class="p">)</span> <span class="n">mlflow</span><span class="p">.</span><span class="n">log_metric</span><span class="p">(</span><span class="s">"eval_acc"</span><span class="p">,</span> <span class="n">eval_acc</span><span class="p">)</span> <span class="k">print</span><span class="p">(</span><span class="s">"Eval Accuracy: {:.3%}"</span><span class="p">.</span><span class="nb">format</span><span class="p">(</span><span class="n">eval_acc</span><span class="p">))</span> <span class="n">conf_matrix</span> <span class="o">=</span> <span class="n">confusion_matrix</span><span class="p">(</span><span class="n">y_test</span><span class="p">,</span> <span class="n">preds</span><span class="p">)</span> <span class="n">ax</span> <span class="o">=</span> <span class="n">sns</span><span class="p">.</span><span class="n">heatmap</span><span class="p">(</span><span class="n">conf_matrix</span><span class="p">,</span> <span class="n">annot</span><span class="o">=</span><span class="bp">True</span><span class="p">,</span> <span class="n">fmt</span><span class="o">=</span><span class="s">'g'</span><span class="p">)</span> <span class="n">ax</span><span class="p">.</span><span class="n">invert_xaxis</span><span class="p">()</span> <span class="n">ax</span><span class="p">.</span><span class="n">invert_yaxis</span><span class="p">()</span> <span class="n">plt</span><span class="p">.</span><span class="n">ylabel</span><span class="p">(</span><span class="s">'Actual'</span><span class="p">)</span> <span class="n">plt</span><span class="p">.</span><span class="n">xlabel</span><span class="p">(</span><span class="s">'Predicted'</span><span class="p">)</span> <span class="n">plt</span><span class="p">.</span><span class="n">title</span><span class="p">(</span><span class="s">"Confusion Matrix"</span><span class="p">)</span> <span class="n">plt</span><span class="p">.</span><span class="n">savefig</span><span class="p">(</span><span class="s">"sklearn_conf_matrix.png"</span><span class="p">)</span> <span class="n">mlflow</span><span class="p">.</span><span class="n">log_artifact</span><span class="p">(</span><span class="s">"sklearn_conf_matrix.png"</span><span class="p">)</span> </code></pre></div></div> <p><br /></p> <p>모델과 실험(파이프라인)을 설정한다.</p> <p>처음에 <code class="language-plaintext highlighter-rouge">mlflow.set_experiment("실험이름")</code>을 통해 로깅될 실험의 이름을 설정한다.</p> <p><code class="language-plaintext highlighter-rouge">with</code> 블록 안에 파이프라인 train, evaluate, logging 등의 과정이 적재되어있고, <code class="language-plaintext highlighter-rouge">start_run()</code> 메소드를 통해 실행된다. <code class="language-plaintext highlighter-rouge">with</code> 구문 덕분에 각 실행의 독립성이 보장된다. 쉽게 말해 여러번 실험을 할 경우 하나의 실험이 예상치 못하게 종료되든, 올바르게 종료되든 다음 실험에는 전혀 영향을 주지 않는다.</p> <div class="language-python highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="n">sklearn_model</span> <span class="o">=</span> <span class="n">LogisticRegression</span><span class="p">(</span><span class="n">max_iter</span><span class="o">=</span><span class="mi">50</span><span class="p">,</span> <span class="n">solver</span><span class="o">=</span><span class="s">'newton-cg'</span><span class="p">)</span> <span class="n">mlflow</span><span class="p">.</span><span class="n">set_experiment</span><span class="p">(</span><span class="s">"iris_experiment"</span><span class="p">)</span> <span class="k">with</span> <span class="n">mlflow</span><span class="p">.</span><span class="n">start_run</span><span class="p">():</span> <span class="n">train</span><span class="p">(</span><span class="n">sklearn_model</span><span class="p">,</span> <span class="n">x_train</span><span class="p">,</span> <span class="n">y_train</span><span class="p">)</span> <span class="n">evaluate</span><span class="p">(</span><span class="n">sklearn_model</span><span class="p">,</span> <span class="n">x_test</span><span class="p">,</span> <span class="n">y_test</span><span class="p">)</span> <span class="n">mlflow</span><span class="p">.</span><span class="n">sklearn</span><span class="p">.</span><span class="n">log_model</span><span class="p">(</span><span class="n">sklearn_model</span><span class="p">,</span> <span class="s">"log_reg_model"</span><span class="p">)</span> <span class="k">print</span><span class="p">(</span><span class="s">"Model run: "</span><span class="p">,</span> <span class="n">mlflow</span><span class="p">.</span><span class="n">active_run</span><span class="p">().</span><span class="n">info</span><span class="p">.</span><span class="n">run_uuid</span><span class="p">)</span> <span class="n">mlflow</span><span class="p">.</span><span class="n">end_run</span><span class="p">()</span> </code></pre></div></div> <p>MLFlow는 이처럼 pythonic한 파이프라인을 작성하기 좋다.</p> <p><br /></p> <p>실행결과는 다음과 같다</p> <div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>&gt;&gt;&gt; Train Accuracy: 95.000% Eval Accuracy: 96.667% Model run: e45b49292ea64807844ccd436d379672 </code></pre></div></div> <p align="center"> <img src="/images/2022/04/12/sklearn_conf_matrix.png" width="800px" /> <figcaption style="text-align:center; font-size:12px; color:#808080"> Testset 분류 결과 </figcaption> </p> <p><br /></p> <div class="language-python highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="n">loaded_model</span> <span class="o">=</span> <span class="n">mlflow</span><span class="p">.</span><span class="n">sklearn</span><span class="p">.</span><span class="n">load_model</span><span class="p">(</span><span class="s">"runs:/a67105e9dd424de390b33509cc1a7e10/log_reg_model"</span><span class="p">)</span> <span class="k">print</span><span class="p">(</span><span class="n">loaded_model</span><span class="p">.</span><span class="n">score</span><span class="p">(</span><span class="n">x_test</span><span class="p">,</span> <span class="n">y_test</span><span class="p">))</span> </code></pre></div></div> <div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>&gt;&gt;&gt; 0.9666666666666667 </code></pre></div></div> <p><br /></p> <hr /> <p><br /></p> <h3 id="mlflow-ui-실행">MLFlow ui 실행</h3> <p><br /></p> <p>터미널 접속 후 코드가 저장된 디렉토리에서</p> <div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code>mlflow ui <span class="nt">-p</span> 9999 <span class="c"># 포트 번호 지정</span> </code></pre></div></div> <p>만약 command not found 에러를 마주한다면 conda(miniforge) 가상환경이 활성화가 된 상태인지 먼저 확인해보자.</p> <p><br /></p> <p>정상적으로 진행된다면 다음과 같이 localhost의 9999번 포트에서 실행된다.</p> <div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>[2022-04-12 22:07:09 +0900] [9424] [INFO] Starting gunicorn 20.1.0 [2022-04-12 22:07:09 +0900] [9424] [INFO] Listening at: http://127.0.0.1:9999 (9424) [2022-04-12 22:07:09 +0900] [9424] [INFO] Using worker: sync [2022-04-12 22:07:09 +0900] [9425] [INFO] Booting worker with pid: 9425 </code></pre></div></div> <p><br /></p> <p align="center"> <img src="/images/2022/04/12/2.png" width="800px" /> <figcaption style="text-align:center; font-size:12px; color:#808080"> 웹 브라우저 접속시 메인화면 </figcaption> </p> <p><br /></p> <p>왼쪽에 실험명인 iris_experiment를 클릭하면 그동안의 실험이 전부 기록되어 있다.</p> <p>가장 최근의 실행을 클릭해 들어가보면 기록된 설명, 파라미터, 평가항목, 태그, 저장된 모델 정보와 이미지 등을 볼 수 있다.</p> <p><br /></p> <p align="center"> <img src="/images/2022/04/12/3.png" width="800px" /> <figcaption style="text-align:center; font-size:12px; color:#808080"> 실행 정보 </figcaption> </p> <p><br /></p> <p>다음과 같이 필터링 조건을 걸어 원하는 실행만 모아 볼 수 있고</p> <p><br /></p> <p align="center"> <img src="/images/2022/04/12/4.png" width="800px" /> <figcaption style="text-align:center; font-size:12px; color:#808080"> 조건부 필터링 </figcaption> </p> <p><br /></p> <p>여러개의 실행을 동시 선택 후 Compare를 눌러 실행간의 시각화된 비교 결과를 볼 수 도 있다.</p> <p><br /></p> <p align="center"> <img src="/images/2022/04/12/5.png" width="800px" /> <figcaption style="text-align:center; font-size:12px; color:#808080"> 여러개를 선택 후 Compare 클릭 </figcaption> </p> <p><br /></p> <p align="center"> <img src="/images/2022/04/12/6.png" width="800px" /> <figcaption style="text-align:center; font-size:12px; color:#808080"> Scatter Plot </figcaption> </p> <p><br /></p> <p>좌측 실험 탭에서 각 실험들은 휴지통 버튼으로 삭제할 수 있지만, 삭제된 실험들은 모두 <code class="language-plaintext highlighter-rouge">.trash</code>에 남아있다. 따라서 완전히 삭제하기 위해선 터미널에서 따로 휴지통을 비워줘야 한다.</p> <div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nb">rm</span> <span class="nt">-rf</span> mlruns/.trash/<span class="k">*</span> </code></pre></div></div> <p><br /></p> <p>터미널에서 포그라운드 상태로 ‘ctrl + c’를 눌러 MLFlow를 종료할 수 있다. 맥에서도 ‘command’가 아닌 ‘ctrl’을 그대로 사용하면 된다.</p> <p>또는 <code class="language-plaintext highlighter-rouge">pkill -f gunicorn</code> 으로도 종료할 수 있다.</p> <p><br /></p> <hr /> <p><br /></p> <h3 id="출처">출처</h3> <p><br /></p> <ul> <li> <p><a href="http://acornpub.co.kr/book/mlops-mlflow">http://acornpub.co.kr/book/mlops-mlflow</a></p> </li> <li> <p><a href="https://computer-nerd.tistory.com/54">https://computer-nerd.tistory.com/54</a></p> </li> <li> <p><a href="https://stackoverflow.com/questions/60088889/how-do-you-permanently-delete-an-experiment-in-mlflow">https://stackoverflow.com/questions/60088889/how-do-you-permanently-delete-an-experiment-in-mlflow</a></p> </li> </ul> Tue, 12 Apr 2022 00:00:00 +0000 https://clarit7.github.io/mlflow_tutorial/ https://clarit7.github.io/mlflow_tutorial/ mlops mlflow mlops (MacOS) Vim을 기본 텍스트 에디터로 사용하기 <p>맥에서 Automator를 활용해 기본 텍스트 에디터를 Vim으로 설정하는 법을 정리해본다.</p> <hr /> <p><br /></p> <h3 id="automator--applescript-설정">Automator &amp; AppleScript 설정</h3> <p><br /></p> <p>아래 사진에 따라 설정을 진행한다</p> <p><br /></p> <p align="center"> <img src="/images/2022/03/22/1.png" width="800px" /> <figcaption style="text-align:center; font-size:12px; color:#808080"> 런치패드 또는 Spotlight 검색으로 Automator 실행 -&gt; '새로운 문서' 선택 </figcaption> </p> <p><br /></p> <p align="center"> <img src="/images/2022/03/22/2.png" width="800px" /> <figcaption style="text-align:center; font-size:12px; color:#808080"> '응용 프로그램' 선택 </figcaption> </p> <p><br /></p> <p align="center"> <img src="/images/2022/03/22/3.png" width="800px" /> <figcaption style="text-align:center; font-size:12px; color:#808080"> 검색창에 'AppleScript' 검색 후 'AppleScript 실행' 더블클릭, 스크립트 입력창에 아래 코드 작성 </figcaption> </p> <p><br /></p> <div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>on run {input, parameters} if (count of input) &gt; 0 then tell application "System Events" set runs to false try set p to application process "iTerm" set runs to true end try end tell tell application "iTerm" activate set numItems to the count of items of input set launchPaths to "" repeat with x from 1 to numItems set filePath to quoted form of POSIX path of item x of input set launchPaths to launchPaths &amp; " " &amp; filePath end repeat tell current window delay 0.01 create tab with default profile command "vim " &amp; launchPaths end tell end tell end if end run </code></pre></div></div> <p><br /></p> <p>코드의 출처는 글 아래에 명시해 놨는데, 해당 글에서 소개한 코드에서 <code class="language-plaintext highlighter-rouge">create tab ...</code> 바로 위 라인에 <code class="language-plaintext highlighter-rouge">delay 0.01</code> 코드가 추가된 것을 알 수 있다.</p> <p>맥 버전이나 기종마다 다른지는 확실히 알 수 없지만, 내 경우 이렇게 딜레이 코드를 넣지 않으면 iterm이 실행중이지 않을 때 텍스트 파일을 열면 iterm만 실행되고 Vim은 실행되지 않는 문제가 생겼다.</p> <p><br /></p> <p align="center"> <img src="/images/2022/03/22/4.png" width="800px" /> <figcaption style="text-align:center; font-size:12px; color:#808080"> 앱 이름 및 위치 지정 </figcaption> </p> <p><br /></p> <p align="center"> <img src="/images/2022/03/22/5.png" width="800px" /> <figcaption style="text-align:center; font-size:12px; color:#808080"> '응용 프로그램'과 같은 적당한 위치에 작성한 앱 저장 </figcaption> </p> <p><br /></p> <p align="center"> <img src="/images/2022/03/22/6.png" width="800px" /> <figcaption style="text-align:center; font-size:12px; color:#808080"> 열고싶은 텍스트 파일 우클릭 -&gt; 다음으로 열기 -&gt; 기타 </figcaption> </p> <p><br /></p> <p align="center"> <img src="/images/2022/03/22/7.png" width="800px" /> <figcaption style="text-align:center; font-size:12px; color:#808080"> 저장한 앱 선택 -&gt; '항상 선택한 응용 프로그램으로 열기' 체크 </figcaption> </p> <p><br /></p> <hr /> <p><br /></p> <h3 id="실행-결과">실행 결과</h3> <p align="center"> <img src="/images/2022/03/22/8.png" width="800px" /> <figcaption style="text-align:center; font-size:12px; color:#808080"> 이제 같은 확장자를 가진 텍스트 파일들은 바로 Vim으로 다이렉트로 열 수 있다 </figcaption> </p> <p><br /></p> <hr /> <p><br /></p> <h3 id="출처">출처</h3> <p><br /></p> <ul> <li><a href="https://gist.github.com/Huluk/5117702">https://gist.github.com/Huluk/5117702</a></li> </ul> Tue, 22 Mar 2022 00:00:00 +0000 https://clarit7.github.io/set_vim_default_editor/ https://clarit7.github.io/set_vim_default_editor/ m1_mac vim automator mac_os M1 맥 GPU 가속 지원하는 텐서플로우 개발환경 세팅 <p>M1pro 맥북프로 14인치를 질렀다.</p> <p>Mac OS는 처음이라 익숙치 않아 2~3일 정도는 설정을 만지작거렸고, 이후에 딥러닝 개발 환경 세팅을 시작했다.</p> <p>아직까지 애플 실리콘에선 완벽한 딥러닝 환경을 구현하기엔 무리가 있지만, 적어도 텐서플로우는 공식적으로 GPU가속을 지원한다.</p> <p>개발환경 세팅을 위해 인터넷에 올라온 여러 글을 참고했는데, 여러번 설치에 실패했다.</p> <p>결국 돌고 돌아 <a href="https://developer.apple.com/metal/tensorflow-plugin/">애플 공식 개발자 가이드</a>에 소개된 내용이 정답이었다</p> <hr /> <p><br /></p> <h3 id="주의사항">주의사항</h3> <p><br /></p> <ul> <li>파이썬 버전 3.8 또는 3.9 필요</li> <li>Conda 설치 금지</li> <li>Homebrew 사용 금지</li> </ul> <p><br /></p> <p>우선 이 글을 보는 누군가의 삽질을 막기 위해 주의사항으로 시작한다. 자세한 이유는 아래에서 설명한다.</p> <p>만약 이미 Anaconda나 Conda를 설치했다면 가능하면 삭제 후에 진행하자.</p> <p><a href="https://stackoverflow.com/questions/42182706/how-to-uninstall-anaconda-completely-from-macos">Conda 삭제방법</a> 가장 추천수 많이 받은 답변 참고</p> <p>추가로 내 경우는 아나콘다가 <code class="language-plaintext highlighter-rouge">/opt/anaconda3</code> 에도 설치되었다. 각자 아나콘다가 삭제된 폴더를 잘 찾아서 싹 다 삭제해주자</p> <p><br /></p> <h3 id="환경설정">환경설정</h3> <p><br /></p> <p>Miniforge 설치용 쉘 스크립트 파일을 다운로드 받는다. <a href="https://github.com/conda-forge/miniforge/releases/latest/download/Miniforge3-MacOSX-arm64.sh">링크</a></p> <p><br /></p> <p>Miniforge는 Conda와 비슷한 파이썬 패키지 및 가상환경 관리 플랫폼이다. Conda를 사용하지 않는 이유는 애플 실리콘을 지원하지 않기 때문이고, 텐서플로우의 GPU가속 플러그인 tensorflow-metal이 이 miniforge채널을 통해서만 배포되기 때문이다.</p> <p><br /></p> <p>아래 명령어로 설치를 진행한다.</p> <div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nv">$ </span><span class="nb">chmod</span> +x ~/Downloads/Miniforge3-MacOSX-arm64.sh <span class="nv">$ </span>sh ~/Downloads/Miniforge3-MacOSX-arm64.sh <span class="nv">$ </span><span class="nb">source</span> ~/miniforge3/bin/activate </code></pre></div></div> <p>여기부턴 가상환경(base)이 실행된 상태가 된다.</p> <p>Miniforge 설치 중 환경변수 이름을 conda로 사용할 거냐고 묻는 메세지가 중간에 나오는데, 십중팔구는 Yes를 선택할 것이다.</p> <p>이러면 기존에 설치된 conda랑 환경변수가 겹칠 수 있으니, 애플 실리콘 지원 안해주는 conda를 처음부터 삭제하고 설치하는 것이 낫다.</p> <p><br /></p> <p>설치확인</p> <div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="o">(</span>base<span class="o">)</span><span class="nv">$ </span>conda info <span class="o">&gt;&gt;&gt;</span> active environment : None shell level : 0 ... platform : osx-arm64 user-agent : conda/4.11.0 requests/2.27.1 CPython/3.9.10 Darwin/21.4.0 OSX/12.3 UID:GID : 000:00 netrc file : None offline mode : False </code></pre></div></div> <p>중간에 platform에 ‘osx-arm64’로 표기되어 있어야 성공이다. ‘osx-64’ 이렇게 표기되면 실패한거니 삭제 후 다시 설치하자.</p> <p>Homebrew를 사용하지 말라는 이유가 여기 있다. Miniforge는 쉘 스크립트 파일 다운받고 명령어 여러줄 칠 필요 없이 Homebrew만으로도 간편하게 설치가 가능한데, 이렇게 설치하면 애플 실리콘 버전이 아닌 인텔 버전이 설치된다. 일단 적어도 내 경우에는 그랬다.</p> <p><br /></p> <h3 id="텐서플로우-dependencies-설치">텐서플로우 dependencies 설치</h3> <p><br /></p> <div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="o">(</span>base<span class="o">)</span><span class="nv">$ </span>conda <span class="nb">install</span> <span class="nt">-c</span> apple tensorflow-deps </code></pre></div></div> <p><br /></p> <h3 id="텐서플로우-설치">텐서플로우 설치</h3> <p><br /></p> <div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="o">(</span>base<span class="o">)</span><span class="nv">$ </span>python <span class="nt">-m</span> pip <span class="nb">install </span>tensorflow-macos </code></pre></div></div> <p><br /></p> <h3 id="텐서플로우-metal-플러그인-설치">텐서플로우 metal 플러그인 설치</h3> <p><br /></p> <div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="o">(</span>base<span class="o">)</span><span class="nv">$ </span>python <span class="nt">-m</span> pip <span class="nb">install </span>tensorflow-metal </code></pre></div></div> <p><br /></p> <hr /> <p><br /></p> <h3 id="디바이스-선택">디바이스 선택</h3> <p><br /></p> <p>텐서플로우 2.8에선 <a href="https://www.tensorflow.org/mlir?hl=ko">MLIR</a> 이라는 채-신 기술을 사용해 디바이스를 자동으로 선택해준다. 따로 설정할 필요 없음</p> <p>Multi-Level Intermediate Representation, 다중 계층에서 파편화된 딥러닝의 표현방식, 컴파일러, 프레임워크, 실행 환경 등을 일반화하고 통합하는 프로젝트라고 하는데, 다음에 자세히 알아봐야겠다.</p> <p><br /></p> <hr /> <p><br /></p> <h3 id="테스트">테스트</h3> <p><br /></p> <p>MNIST 예제 코드</p> <div class="language-python highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kn">import</span> <span class="nn">tensorflow</span> <span class="k">as</span> <span class="n">tf</span> <span class="n">mnist</span> <span class="o">=</span> <span class="n">tf</span><span class="p">.</span><span class="n">keras</span><span class="p">.</span><span class="n">datasets</span><span class="p">.</span><span class="n">mnist</span> <span class="p">(</span><span class="n">x_train</span><span class="p">,</span> <span class="n">y_train</span><span class="p">),</span> <span class="p">(</span><span class="n">x_test</span><span class="p">,</span> <span class="n">y_test</span><span class="p">)</span> <span class="o">=</span> <span class="n">mnist</span><span class="p">.</span><span class="n">load_data</span><span class="p">()</span> <span class="n">x_train</span><span class="p">,</span> <span class="n">x_test</span> <span class="o">=</span> <span class="n">x_train</span> <span class="o">/</span> <span class="mf">255.0</span><span class="p">,</span> <span class="n">x_test</span> <span class="o">/</span> <span class="mf">255.0</span> <span class="n">model</span> <span class="o">=</span> <span class="n">tf</span><span class="p">.</span><span class="n">keras</span><span class="p">.</span><span class="n">models</span><span class="p">.</span><span class="n">Sequential</span><span class="p">([</span> <span class="n">tf</span><span class="p">.</span><span class="n">keras</span><span class="p">.</span><span class="n">layers</span><span class="p">.</span><span class="n">Flatten</span><span class="p">(</span><span class="n">input_shape</span><span class="o">=</span><span class="p">(</span><span class="mi">28</span><span class="p">,</span> <span class="mi">28</span><span class="p">)),</span> <span class="n">tf</span><span class="p">.</span><span class="n">keras</span><span class="p">.</span><span class="n">layers</span><span class="p">.</span><span class="n">Dense</span><span class="p">(</span><span class="mi">128</span><span class="p">,</span> <span class="n">activation</span><span class="o">=</span><span class="s">'relu'</span><span class="p">),</span> <span class="n">tf</span><span class="p">.</span><span class="n">keras</span><span class="p">.</span><span class="n">layers</span><span class="p">.</span><span class="n">Dropout</span><span class="p">(</span><span class="mf">0.2</span><span class="p">),</span> <span class="n">tf</span><span class="p">.</span><span class="n">keras</span><span class="p">.</span><span class="n">layers</span><span class="p">.</span><span class="n">Dense</span><span class="p">(</span><span class="mi">10</span><span class="p">,</span> <span class="n">activation</span><span class="o">=</span><span class="s">'softmax'</span><span class="p">)</span> <span class="p">])</span> <span class="n">model</span><span class="p">.</span><span class="nb">compile</span><span class="p">(</span><span class="n">optimizer</span><span class="o">=</span><span class="s">'adam'</span><span class="p">,</span> <span class="n">loss</span><span class="o">=</span><span class="s">'sparse_categorical_crossentropy'</span><span class="p">,</span> <span class="n">metrics</span><span class="o">=</span><span class="p">[</span><span class="s">'accuracy'</span><span class="p">])</span> <span class="n">model</span><span class="p">.</span><span class="n">fit</span><span class="p">(</span><span class="n">x_train</span><span class="p">,</span> <span class="n">y_train</span><span class="p">,</span> <span class="n">epochs</span><span class="o">=</span><span class="mi">5</span><span class="p">)</span> <span class="n">model</span><span class="p">.</span><span class="n">evaluate</span><span class="p">(</span><span class="n">x_test</span><span class="p">,</span> <span class="n">y_test</span><span class="p">,</span> <span class="n">verbose</span><span class="o">=</span><span class="mi">2</span><span class="p">)</span> </code></pre></div></div> <p><br /></p> <p>실행 결과</p> <div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>Metal device set to: Apple M1 Pro systemMemory: 32.00 GB maxCacheSize: 10.67 GB 2022-03-18 00:46:54.141153: I tensorflow/core/common_runtime/pluggable_device/pluggable_device_factory.cc:305] Could not identify NUMA node of platform GPU ID 0, defaulting to 0. Your kernel may not have been built with NUMA support. 2022-03-18 00:46:54.141252: I tensorflow/core/common_runtime/pluggable_device/pluggable_device_factory.cc:271] Created TensorFlow device (/job:localhost/replica:0/task:0/device:GPU:0 with 0 MB memory) -&gt; physical PluggableDevice (device: 0, name: METAL, pci bus id: &lt;undefined&gt;) 2022-03-18 00:46:54.273304: W tensorflow/core/platform/profile_utils/cpu_utils.cc:128] Failed to get CPU frequency: 0 Hz Epoch 1/5 2022-03-18 00:46:54.409370: I tensorflow/core/grappler/optimizers/custom_graph_optimizer_registry.cc:113] Plugin optimizer for device_type GPU is enabled. 1875/1875 [==============================] - 9s 5ms/step - loss: 0.2959 - accuracy: 0.9148 Epoch 2/5 1875/1875 [==============================] - 9s 5ms/step - loss: 0.1403 - accuracy: 0.9587 Epoch 3/5 1875/1875 [==============================] - 9s 5ms/step - loss: 0.1023 - accuracy: 0.9689 Epoch 4/5 1875/1875 [==============================] - 9s 5ms/step - loss: 0.0803 - accuracy: 0.9753 Epoch 5/5 1875/1875 [==============================] - 9s 5ms/step - loss: 0.0680 - accuracy: 0.9789 2022-03-18 00:47:38.664373: I tensorflow/core/grappler/optimizers/custom_graph_optimizer_registry.cc:113] Plugin optimizer for device_type GPU is enabled. 313/313 - 1s - loss: 0.0706 - accuracy: 0.9771 - 1s/epoch - 4ms/step </code></pre></div></div> <p>디바이스가 Apple M1 Pro로 제대로 인식되었다</p> <p><br /></p> <p align="center"> <img src="/images/2022/03/18/gpu_running.png" width="800px" /> <figcaption style="text-align:center; font-size:12px; color:#808080"> GPU 활성 상태 </figcaption> </p> <p><br /></p> <p>또한 GPU 활성 상태도 90% 이상으로 성능을 잘 활용하는 것을 확인했다.</p> <p><br /></p> <hr /> <p><br /></p> <h3 id="기타">기타</h3> <p><br /></p> <p>파이토치는 애플 실리콘 네이티브를 지원하지만 아직까지 GPU가속 지원은 개발중이라고 한다.</p> <p>개인적으로 따로 쓸 수 있는 GPU 서버가 없다면 구글 코랩이나 Paperspace Gradient같은 클라우드 딥러닝 플랫폼을 사용하는걸 권한다.</p> <p><br /></p> <hr /> <p><br /></p> <h3 id="출처">출처</h3> <p><br /></p> <ul> <li> <p><a href="https://developer.apple.com/metal/tensorflow-plugin/">https://developer.apple.com/metal/tensorflow-plugin/</a></p> </li> <li> <p><a href="https://stackoverflow.com/questions/42182706/how-to-uninstall-anaconda-completely-from-macos">https://stackoverflow.com/questions/42182706/how-to-uninstall-anaconda-completely-from-macos</a></p> </li> <li> <p><a href="http://mkszero.com/@케이/install-tensorflow-25-in-apple-m1">http://mkszero.com/@케이/install-tensorflow-25-in-apple-m1</a></p> </li> <li> <p><a href="https://cpuu.postype.com/post/9091007">https://cpuu.postype.com/post/9091007</a></p> </li> <li> <p><a href="https://www.tensorflow.org/mlir?hl=ko">https://www.tensorflow.org/mlir?hl=ko</a></p> </li> </ul> Thu, 17 Mar 2022 00:00:00 +0000 https://clarit7.github.io/mac_gpu_setting/ https://clarit7.github.io/mac_gpu_setting/ m1_mac apple_silicon gpu mac_os 파이토치 모델이 CPU에서 너무 느릴때. torch.set_flush_denormal() <p>결론부터 말하자면, 파이토치 관련 코드의 시작 부분에 이렇게 쓰면 된다.</p> <div class="language-python highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="n">torch</span><span class="p">.</span><span class="n">set_flush_denormal</span><span class="p">(</span><span class="bp">True</span><span class="p">)</span> </code></pre></div></div> <p><br /></p> <p>딥러닝 모델 훈련 및 실사용시엔 고차원 텐서 연산을 병렬적으로 처리할 수 있는 GPU가 거의 필수적이지만, 가벼우면서 real-time estimation이 필수적이지 않은 모델은 CPU에서 실행하더라도 충분히 실사용이 가능한 경우도 있다.</p> <p><br /></p> <p>그러나 훈련된 모델을 CPU에서 실행시켜보면 간혹 예상한 속도보다 비교가 안 될 정도로 느려진다. GPU에서 하나의 입력에 대해 예측값을 내기까지 걸리는 시간이 0.1초도 걸리지 않는 모델이 CPU에서 실행했더니 20초가 넘게 걸린다고 가정해보자. 아무리 실시간 예측을 포기한다고 하더라도 결과 하나를 얻기 위해 수 초 이상을 기다리는 경험은 상당히 불쾌할 것이다.</p> <p><br /></p> <p>위에 적은 내용은 실제로 내가 경험한 일이다. 해당 모델은 몇개의 모듈 단위로 구성되어 있었고, 각 모듈마다 선형 레이어가 사용되었다. 모듈별로 입/출력값은 달랐지만 선형 레이어와 입/출력값의 사이즈는 모두 같았다. 나는 당연히 입력 텐서와 선형 레이어의 크기가 같은 모듈끼린 실행 시간이 비슷할 것이라고 예상했지만, 두 모듈 내에서 선형 레이어의 연산 속도를 비교 출력해본 결과 처리 속도가 무려 40배 이상 차이가 났다. 처음엔 둘다 동일한 CPU에서 실행됐기 때문에 CPU와 관련된 문제라곤 생각하지 못했다. 각각의 입력값과 가중치가 문제인가도 고민했지만, 수십만개의 가중치와 입력값을 일일히 비교해보는 것도 무리였다.</p> <p><br /></p> <p>특정 개발자 커뮤니티에 질문한 후 가장 유력한 답변을 하나 받게 되었는데, 0에 가까운 수는 연산이 느리고, 파이토치에 이를 0으로 처리하는 옵션이 있다는 것이다. 덕분에 torch.set_flush_denormal의 존재에 대해 알게 됐고, 해당 모델의 실행속도는 20초에서 0.3초 남짓으로 대폭 줄어들게 되었다. (성능 변화도 거의 없었다.)</p> <p><br /></p> <hr /> <p><br /></p> <h3 id="비정규값-denormalized-numbers">비정규값 (Denormalized numbers)</h3> <p><br /></p> <p>일반적으로 부동소수점 값을 표현할 때는 아래 예시와 같이 유효숫자의 첫자리를 1의자리에서 시작한다. 가수부의 표현 범위를 1이상 9이하, 즉 유효숫자를 일의 자리로 정규화했기 때문에 이를 정규값이라고 한다.</p> \[1.05 \times 10^{-2}\] <p><br /></p> <p>같은 수라도 정규화해서 표현하지 않고 지수부를 다르게 해서 자유롭게 표현할 수 있다.</p> <p>\(0.105 \times 10^{-1}\) 또는 \(10.5 \times 10^{-3}\) 도 모두 같은 수이다.</p> <p><br /></p> <p>만약 0.000000000105라는 숫자를 부동 소수점으로 표현하고 싶은데, 지수부가 표현 가능한 자릿수를 8로 제한한다면 정규화 되지 않은 수로만 표현 할 수 있을 것이다. 이렇게 지수부 제한으로 인해 정규화되지 못한 작은 값들을 비정규값(denormalized number)으로 부른다.</p> \[0.0105 \times 10^{-8}\] <p><br /></p> <p>컴퓨터는 부동 소수점을 부호, 지수부, 가수부를 통해 2진법으로 나타내게 되는데, 메모리 비트 수에 따라 지수부가 표현할 수 있는 수의 범위가 제한된다. 정규값에서 가수부의 맨 앞 비트가 1의자리를 표현하지만, 비정규값은 지수부가 모두 0으로 채워져 있고 가수부의 맨 앞 비트가 0.1의 자리를 나타낸다. 만약 부호가 양수이고 지수부 8비트가 모두 0, 가수부 비트가 0101000….인 비정규값이 있다면, 실제 값을 이렇게 표현할 수 있다.</p> \[0.0101_{(2)} \times 2^{-255}\] <p><br /></p> <p>이렇듯 0에 가까운 작은 수가 비정규값으로 처리되고, 파이토치에서 이러한 비정규값 입력과 가중치들을 모두 0으로 일괄 처리하는 옵션을 통해 연산속도를 향상시킬 수 있다는 점을 알게 되었다. 그러나 비정규값이 정규값에 비해 더 많은 비트를 차지하는 것도 아닌데 어째서 연산속도는 미치도록 느린 것인지 이해가 되지 않아 이유를 좀 더 찾아보았다.</p> <p><br /></p> <hr /> <p><br /></p> <h3 id="x86-cpu의-비정규값-처리">x86 CPU의 비정규값 처리</h3> <p><br /></p> <p>해당 <a href="https://stackoverflow.com/questions/54937154/why-are-denormal-floating-point-values-slower-to-handle">스택 오버플로우 답변</a>을 통해 알 수 있었다. 답변자는 x86 CPU의 설계 경험이 있다고 한다… 고인물이다.</p> <p><br /></p> <p>비정규값 연산 중 가수부와 지수부는 각각 다음과 같은 과정을 거친다.</p> <ul> <li>가수부 비트는 left-shift 연산으로 정규화되고 연산 후엔 다시 right-shift로 변환된다.</li> <li>지수부는 레지스터나 메모리에 적재될때는 32비트 중 8비트로만 제한적으로 표현되지만 연산과정 자체는 비트수의 제약을 받지 않는다.</li> </ul> <p>간단한 shift연산과 정수연산… 이것만 본다면 딱히 느릴 이유가 없어보인다.</p> <p><br /></p> <p>원인은 아키텍쳐 설계 원칙에 있다. CPU가 주로 처리하는 값의 대부분은 정규값이다. 자주 사용하는 연산을 더 빠른 회로에서 처리하고 덜 사용되는 연산은 상대적으로 더 오래 걸리는 회로에 배치하는 설계원칙에 의해 비정규값 연산의 우선순위가 뒤로 밀린 것이다.</p> <p><br /></p> <p>해당 수가 정규값인지 비정규값인지부터 판단하고 연산을 하는 회로에선 정규값 계산시 50%의 추가 지연 시간이 생기기 때문에, x86 CPU는 모든 연산을 정규값 연산으로 처리해버리고 이후 비정규값으로 인해 예외가 발생하면 예외처리 후 뒤늦게 비정규값 연산을 수행하는 구조로 설계되었다고 한다. 때문에 정규값 연산은 클럭 주파수 3~6 사이클 수준이지만 비정규값 연산은 100 사이클 가까이 걸린다고.</p> <p><br /></p> <p>결국 비정규값의 연산 그 자체가 오래 걸리는 건 아니지만 microcode exception handler까지 도달했다가 나오는 시간이 문제였다.</p> <p><br /></p> <p>반대로 GPU는 비정규값을 처리하기 위한 파이프라인을 추가로 구축함으로써 정규값 연산속도에 약간의 trade-off가 존재하지만 덕분에 비정규값을 거의 속도 저하 없이 처리할 수 있다고 한다.</p> <p><br /></p> <hr /> <p><br /></p> <h3 id="파이토치에서-해당-옵션-사용시-주의사항">파이토치에서 해당 옵션 사용시 주의사항</h3> <p><br /></p> <p>SSE3 명령어셋을 지원하는 x86 CPU 또는 x64 CPU 에서만 사용 가능하다. (x64(64bit)는 x86(32bit)의 하위 호환성을 보장한다.)</p> <p><br /></p> <p>CPU 아키텍쳐 확인</p> <div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nv">$ </span><span class="nb">arch</span> <span class="o">&gt;&gt;&gt;</span> x86-64 </code></pre></div></div> <p><br /></p> <p>SSE3 지원 확인</p> <div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nv">$ </span><span class="nb">grep</span> <span class="s1">'sse3\|pni'</span> /proc/cpuinfo <span class="o">&gt;</span> /dev/null <span class="k">if</span> <span class="o">[</span> <span class="nv">$?</span> <span class="nt">-eq</span> 0 <span class="o">]</span><span class="p">;</span> <span class="k">then </span><span class="nb">echo</span> <span class="s2">"Supported!"</span> <span class="k">else </span><span class="nb">echo</span> <span class="s2">"Not supported!"</span> <span class="k">fi</span> <span class="o">&gt;&gt;&gt;</span> <span class="s2">"Supported!"</span> </code></pre></div></div> <p><br /></p> <p>arm등 다른 아키텍쳐의 CPU에서도 비정규값 처리 기능이 있고, 더 자세히 관련 내용에 대해 알아보려면 ‘Flush To Zero’라는 키워드로 검색해보면 된다.</p> <p><br /></p> <hr /> <p><br /></p> <h3 id="출처">출처</h3> <p><br /></p> <ul> <li> <p><a href="https://pytorch.org/docs/stable/generated/torch.set_flush_denormal.html">https://pytorch.org/docs/stable/generated/torch.set_flush_denormal.html</a></p> </li> <li> <p><a href="https://stackoverflow.com/questions/54937154/why-are-denormal-floating-point-values-slower-to-handle">https://stackoverflow.com/questions/54937154/why-are-denormal-floating-point-values-slower-to-handle</a></p> </li> <li> <p><a href="https://unix.stackexchange.com/questions/131954/check-sse3-support-from-bash">https://unix.stackexchange.com/questions/131954/check-sse3-support-from-bash</a></p> </li> </ul> Mon, 13 Dec 2021 00:00:00 +0000 https://clarit7.github.io/set_flush_denormal/ https://clarit7.github.io/set_flush_denormal/ pytorch denormal_number flush_to_zero computer_science Tablesense 논문 리뷰 (TableSense - Spreadsheet Table Detection with Convolutional Neural Networks) <p><strong>TableSense: Spreadsheet Table Detection with Convolutional Neural Networks</strong> <a href="https://www.microsoft.com/en-us/research/publication/tablesense-spreadsheet-table-detection-with-convolutional-neural-networks/">논문</a> <a href="https://github.com/microsoft/TableSense">데이터셋</a></p> <p><br /></p> <h2 id="abstract--problem-statement">Abstract &amp; Problem Statement</h2> <p><br /></p> <p align="center"> <img src="/images/2021/12/10/tables.png" width="800px" /> <figcaption style="text-align:center; font-size:12px; color:#808080"> Table Detection </figcaption> </p> <p><br /></p> <p>Spreadsheet table detection은 엑셀 파일 등에서 테이블이 존재하는 영역, 정확히는 top, left, bottom, right 네 방향의 boundary를 감지하는 종류의 과제를 말한다. 스프레드 시트라는 2차원 좌표계 내에서 bounding box를 추출하는 과제이므로 얼핏 보면 이미지 object detection과 비슷한 느낌이 있다. 실제로도 저자는 이 문제를 해결하기 위한 base algorithm으로 딥러닝 이미지 처리 분야에서 real-time object detection의 포문을 열었던 모델인 Faster R-CNN을 사용했다. 그러나 이 과제는 이미지의 object detection과 결정적인 차이점이 존재한다.</p> <p><br /></p> <p>먼저, 이미지 object detection은 평가지표가 Intersection-over-Union(IoU)라는 것이다. Bounding box라는 것이 완전히 절대적인 기준에 의해 라벨링 된 것이 아닌(비록 일관성 있는 라벨링을 가능한 유지하려는 노력은 있었겠지만) 인간의 시각을 기준으로 임의로 부여된 것이다 보니, 아래 그림과 같이 예측 bounding box가 ground truth와 약간의 차이가 있더라도 IoU는 충분히 높게 측정되고, 인간의 눈으로 보기에도 정답이라고 인정할 수준이 된다.</p> <p><br /></p> <p align="center"> <img src="/images/2021/12/10/bbox.png" width="800px" /> <figcaption style="text-align:center; font-size:12px; color:#808080"> Ground Truth vs Predicted Bbox </figcaption> </p> <p><br /></p> <p>하지만 엑셀 파일에서 테이블을 추출하는 작업은 가능한 한 셀의 오차도 없이 정확해야 한다. 이미지 처리에 비교하자면 1픽셀의 오차도 허용하면 안되는 상황인 것이다. 만약 테이블의 가장 우측 또는 최하단에 중요한 정보가 포함되어 있고, bounding box에서 이런 row나 column만 제외된다면 추출된 테이블 활용에 문제가 생길 것이다.</p> <p><br /></p> <p>그리고 입력 데이터의 성격도 다르다. 이미지는 각 픽셀당 3채널에 R, G, B 색상 정보를 가지고 있는데, 엑셀에서 하나의 셀이 품고 있는 정보는 배경색, 선 스타일, 입력값, 수식 등등… 훨씬 많다. 게다가 가로 또는 세로가 편향적으로 길쭉한 이미지나 객체는 거의 없지만, 엑셀 파일에선 가로보다 세로 길이가 100배 이상 길쭉한 비율을 가진 테이블을 흔히 찾을 수 있다.</p> <p><br /></p> <p>저자는 위 문제를 해결하기 위해 새로운 모델 구조와 평가방법, 학습 방법을 제시했고, 관련 데이터셋을 구축하는 성과를 올렸다. 비록 이 분야 자체가 사람들의 관심도가 높은 편은 아니지만, 논문은 엑셀의 본고장 마이크로소프트의 연구진들에 의해 작성됐고 연구진들의 후속 논문에서도 이 Tablesense 논문이 꾸준하게 사용되고 있기 때문에 한번쯤 볼만한 논문이다.</p> <p><br /></p> <h2 id="iou-vs-eob">IoU vs EoB</h2> <p><br /></p> <p>이후부턴 bounding box를 편의상 bbox로 줄여 부르겠다. Object detection의 가장 보편적인 평가지표는 Intersection-over-Union이다. 이는 예측 bbox $(B)$와 실제 bbox $(B’)$의 일치도를 나타내는데, 두 bbox간의 교집합 넓이를 합집합 넓이로 나눈 것이다.</p> <p><br /></p> \[\mathrm{IoU} = \frac{\mathrm{area}(B \cap B')}{\mathrm{area}(B \cup B')}\] <p><br /></p> <p>이는 bbox의 절대적인 크기와 상관 없이 각 bbox간 오차의 상대적 비율만 고려한다. 따라서 큰 bbox간 IoU를 구할 수록 작은 절대 오차는 무시된다. 같은 크기지만 해상도가 다른 20pixel x 20pixel 이미지와 1000pixel x 1000pixel 이미지가 있을 때, 20 x 20 해상도 이미지에선 한쪽 boundary가 5픽셀 차이가 나면 인간의 시각 기준으로 매우 큰 차이로 느껴지고 실제로 IoU도 작게 측정겠지만, 1000 x 1000 해상도 이미지에선 한쪽 boundary가 5픽셀 차이나도 인간의 시각은 문제 없이 bbox를 정답으로 인식하고 IoU역시 높게 측정된다. 따라서 전체 영역이 커질수록 작은 차이가 무시되는 IoU는 엑셀 테이블 추출의 평가지표로 사용하기에 매우 부적합하기에 저자들은 새로운 평가지표인 Error-of-Boundary를 제시한다.</p> <p><br /></p> \[\begin{align*} \mathrm{EoB} = \mathrm{max}( &amp;\vert\mathrm{row_{top}^\mathit{B}} - \mathrm{row_{top}^\mathit{B'}}\vert, \vert\mathrm{row_{bottom}^\mathit{B}} - \mathrm{row_{bottom}^\mathit{B'}}\vert, \\ &amp;\vert\mathrm{row_{left}^\mathit{B}} - \mathrm{row_{left}^\mathit{B'}}\vert, \vert\mathrm{row_{right}^\mathit{B}} - \mathrm{row_{right}^\mathit{B'}}\vert) \end{align*}\] <p><br /></p> <p>EoB는 예측과 정답 boundary의 최대 절대 오차가 기준이다. 예를 들어, 상/하/좌/우 boundary의 예측값과 정답이 각각 2/0/1/1 셀 만큼씩 차이가 난다면, top-boundary의 오차가 2로 가장 크고, 따라서 EoB는 이 경우 2가 된다. 덕분에 테이블이 크기와 상관 없이 영역이 아닌 boundary를 기준으로 평가할 수 있다.</p> <p><br /></p> <h2 id="datasets--framework">Datasets &amp; Framework</h2> <p><br /></p> <p>스프레드 시트 파일을 웹에서 크롤링해 그 중 10220개의 시트에 라벨링을 해 훈련 셋으로 사용하고 이와 겹치지 않는 400개의 시트를 테스트 셋으로 사용했다.</p> <p><br /></p> <p align="center"> <img src="/images/2021/12/10/framework.png" width="800px" /> <figcaption style="text-align:center; font-size:12px; color:#808080"> TableSense Framework </figcaption> </p> <p><br /></p> <p>Tablesense는 다음 다섯 단계에 걸쳐 테이블을 추출한다.</p> <p><br /></p> <ol> <li>Cell Featurization</li> <li>CNN Backbone</li> <li>Region Proposal Network</li> <li>Bounding Box Regresssion</li> <li>Precise Bounding Box Regression</li> </ol> <p><br /></p> <p>Cell Featurization은 시트를 텐서로 변환하는 단계이다. 이미지 파일은 한 픽셀에 색상 3채널의 정보를 담고 있지만, 엑셀 파일은 한 셀당 20채널로 나타낸다. 각 채널마다 영문자 비율, 숫자 비율, 입력값 길이, 선 스타일 적용 유무, 배경색, 글자색, 수식 적용 여부 등등의 정보를 담게 된다.</p> <p><br /></p> <p>이후 CNN backbone, Region Proposal Network, Bounding Box Regression은 Faster R-CNN의 알고리즘을 그대로 사용한다. 약간의 차이점이 있다면 resnet backbone에서 pooling layer를 제거한것과, RPN의 anchor세트가 base size 8 ~ 4096, ratio 1/256 ~ 256 까지 존재하는 등 다양한 크기와 극단적으로 편향된 비율을 가진 케이스까지 포함한다는 점이다.</p> <p><br /></p> <p>주목해야 할 부분은 이 모델의 핵심 구조인 PBR(Precise Bounding Box Regression) 모듈이다. BBR모듈의 Regrion of Interest를 기반으로 세부적인 boundary를 보정하는데, 어떤 원리인지 자세히 알아보자.</p> <p><br /></p> <p align="center"> <img src="/images/2021/12/10/pbr.png" width="800px" /> <figcaption style="text-align:center; font-size:12px; color:#808080"> Precise Bounding Box Regression module </figcaption> </p> <p><br /></p> <p>BBR 모듈로 출력된 RoI는 부정확한 boundary를 가지고 있기 때문에, 이 boundary를 기준으로 receptive field를 새로 설정해 예측값과 정답값의 차이를 구하게 된다. 좌, 우 boundary에 대해서는 수평방향으로 $2k$, 상/하 boundary에 대해서는 수직방향으로 $2k$의 사이즈를 가지는 좁은 receptive 필드는 예측 boundary와 실제 boundary의 오차를 최소화하는데 적합하다. 논문에선 적절한 $k$를 7로 설정했다. $k$가 너무 크면 여러개의 작은 표가 가까이 붙어있는 경우 정답을 예측하는데 방해가 되고, 너무 작으면 receptive field가 ground truth boundary를 포함하지 못할 확률이 높아지기 때문이다. 네 방향의 receptive field로부터 추출된 feature map은 RoIAlign을 통해 $2k \times 2k$ 크기의 텐서로 고정되고, regression 이후 세부적인 보정값을 출력하게 된다. Receptive field의 폭 또는 높이와 RoIAlign 후의 폭 또는 높이가 같기 때문에 정보의 손실이 거의 없게 된다. PBR모듈의 출력값을 통해 BBR모듈이 출력한 RoI를 보정하게 되면 경계 오차가 매우 적은 bbox를 얻을 수 있다.</p> <p><br /></p> <h2 id="target--loss-function">Target &amp; Loss Function</h2> <p><br /></p> <p>기존 Faster R-CNN의 손실함수는 다음과 같이 smooth L1을 사용한다.</p> <p><br /></p> \[L_\mathrm{reg}(t, t^{*}) = \sum_{i \in \{ x, y, w, h\}} \mathrm{smooth_\mathit{L_1}} (t_i - t_i^{*})\] <p><br /></p> <p>타겟은 다음과 같이 설정된다.</p> <p><br /></p> \[\begin{align*} &amp;t_x = (x - x_a) / w_a,\quad t_w = \log(w/w_a) \\ &amp;t_x^* = (x^* - x_a) / w_a,\quad t_w^a = \log(w^*/w_a) \end{align*}\] <p><br /></p> <p>$x, w$는 각각 bbox의 중심 $x$좌표와 폭을 나타내고, $y, h$에 대해서도 동일한 식을 사용한다. 첨자가 없는 문자는 예측 bbox의 값, 아래첨자 $a$가 붙은 문자는 anchor box의 값, 윗첨자 $*$가 붙은 문자는 ground-truth box의 값을 나타낸다. 이 식에서 $x$에 대한 손실함수의 기울기는 $w_a$에 대해 반비례하고, $w$에 대한 손실함수의 기울기는 $w$에 대해 반비례한다. 즉 anchor box가 클 수록 중심 좌표에 대한 가중치 업데이트 값이 작아지고, 마찬가지로 예측 bbox가 클 수록 폭과 높이에 대한 가중치 업데이트 값이 작아진다. 이는 bbox의 크기와 상관 없이 안정적인 훈련을 보장하지만 boundary를 정확하게 예측하는 데에는 적합하지 않다.</p> <p><br /></p> <p>따라서 PBR 모듈은 새로운 손실함수와 타겟을 사용한다.</p> <p><br /></p> \[L_\mathrm{reg}(t, t^*) = \sum_{i \in \mathrm{\{ top, bottom, left, right\}}} R (t_i - t_i^*)\] \[R(x) = \begin{cases} 0.5x^2, &amp; \mathrm{if} \: \vert x \vert &lt; k\\ 0.5k^2, &amp; \mathrm{otherwise} \end{cases}\] <p><br /></p> \[\begin{align*} &amp;t_{left} = x - x_a - w/2,\quad t_{right} = x - x_a + w/2 \\ &amp;t_{left}^* = x^* - x_a - w^*/2,\quad t^*_{right} = x^* - x_a + w^*/2 \end{align*}\] <p><br /></p> <p>$t_i - t_i^*$를 계산해 보면 anchor box에 관련된 변수들은 사라지고 예측값과 ground-truth의 절댓값 차이만이 남는다. 따라서 PBR 모듈의 가중치 업데이트는 절대 오차에 따라 이뤄진다. 또한 함수 $R$에서 오차가 $k$ 이상일 때는 기울기가 $0.5k^2$으로 고정되기에 예측값과 ground-truth간의 차이가 너무 크더라도 업데이트가 급격하게 이루어지는 상황을 방지했다.</p> <p><br /></p> <p>모든 손실함수는 역전파 전에 더해진다. 기본적으로 PBR 모듈만으론 Region Proposal Network의 안정적인 훈련을 기대할 수 없기 때문에 BBR모듈의 smooth L1 함수도 버려지지 않고 여전히 사용된다.</p> <p><br /></p> <h2 id="evaluation-results">Evaluation Results</h2> <p><br /></p> <p>다음과 같은 baseline 모델들과의 비교를 진행했다.</p> <p><br /></p> <ul> <li>Region-growth : 특정 셀부터 8방향으로 접한 모든 셀로 영역을 가능한 확장시키고 확장이 끝났을 때의 영역을 테이블로 추출한다.</li> <li>Region-growth + SVM : Regtion-growth 방식으로 추출한 테이블이 실제 테이블인지 아닌지 예측하는 classifier를 학습시킨다.</li> <li>Mask R-CNN : 당시에 sota를 달성한 object detection 모델이다.</li> <li>YOLO-v3 : 당시에 sota를 달성한 real time object detection 모델이다.</li> <li>Faster R-CNN : 당시에 sota를 달성한 real time object detection 모델이고 TableSense의 base 알고리즘이다. YOLO보단 느린 대신 정확도는 조금 더 높다.</li> </ul> <p><br /></p> <p>측정은 EoB-0와 EoB-2를 기준으로 했다. EoB-0는 EoB가 0인 경우만 예측 성공으로 인정하는 것이고, EoB-2는 2이하 까지 예측 성공으로 인정하는 것이다. 결과는 논문에 나와 있듯이 TableSense가 다른 baseline에 비해 압도적으로 높은 recall/precision을 기록했다.</p> <p><br /></p> <p align="center"> <img src="/images/2021/12/10/result.png" width="800px" /> <figcaption style="text-align:center; font-size:12px; color:#808080"> Baseline Comparison </figcaption> </p> <p><br /></p> <p>또한 cell featurization 과정에서 특정 feature를 제외시켰을 때보다 모든 feature를 전부 사용했을때 성능이 가장 좋았다고 한다.</p> <p><br /></p> <p>모델이 훈련 중 스스로 불확실한 데이터에 대해서만 사람에게 라벨링을 요구하고 특정 조건을 만족하는 데이터는 스스로 라벨링 하도록 하는 Active Learning은 훈련 횟수가 증가함에 따라 오류가 점차 줄어드는 경향을 보임으로 제시한 학습법의 효율성을 입증했다고 한다.</p> Fri, 10 Dec 2021 00:00:00 +0000 https://clarit7.github.io/tablesense/ https://clarit7.github.io/tablesense/ computer_vision Faster R-CNN object_detection computer_vision