<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0">
  <channel>
    <title>ok-lab</title>
    <link>https://ok-lab.tistory.com/</link>
    <description>Machine Learning, Deep Learning을 전반적으로 다룹니다.</description>
    <language>ko</language>
    <pubDate>Wed, 15 Apr 2026 01:35:06 +0900</pubDate>
    <generator>TISTORY</generator>
    <ttl>100</ttl>
    <managingEditor>언킴</managingEditor>
    <image>
      <title>ok-lab</title>
      <url>https://tistory1.daumcdn.net/tistory/4790649/attach/b6769af5aba14f619b7881d62f704c33</url>
      <link>https://ok-lab.tistory.com</link>
    </image>
    <item>
      <title>Embedding을 활용한 Topic Modeling (이론을 기반으로)</title>
      <link>https://ok-lab.tistory.com/361</link>
      <description>&lt;p data-ke-size=&quot;size16&quot; style=&quot;text-align: justify;&quot;&gt;&amp;nbsp;&lt;br&gt;Topic Modeling은 입력으로 들어오는 문장(Sentence) 혹은 문서(Document)를 임베딩(Embedding)하고, 입력값을 대표하는 토픽을 도출하는 분야다. 문장 내에 n-gram을 이용해, 반복적으로 언급되는 단어를 도출하는 경우도 있으며, 사전에 정의한 K개의 단어 중 가장 유사하다고 판단되는 단어를 대표 주제로 선정하는 경우도 있다.&amp;nbsp;&lt;br&gt;&amp;nbsp;&lt;br&gt;비즈니스 관점으로 바라본다면, 두 가지를 모두 적절히 적용하여야 활용성이 높다고 생각한다. 전자의 경우에는 사전에 정의하지 않은 단어 외에도 새로운 단어들을 반영하여 주제를 선정할 수 있으나, 새로운 주제가 계속 나타날 수 있으며 이를 다시 군집화하는 것이 어려울 수 있다. 반면에, 후자의 경우에는 사전에 정의한 토픽을 명확하게 군집화하여 볼 수 있으나, 새로운 주제가 입력으로 들어오더라도 사전에 정의한 토픽 외에는 파악하는 것이 어렵다는 단점이 존재한다.&lt;br&gt;&amp;nbsp;&lt;br&gt;과거에 외국계 A사와 협업하여 Topic Modeling을 수행하였을 때는 두 가지 방식을 적절히 사용하여, 문장과 토픽 간의 유사도가 일정 임계값보다 낮은 문장들은 n-gram 혹은 LLM을 통해 새로운 주제를 선정하는 2-stage 방식으로 접근하였다.&amp;nbsp;&lt;br&gt;&amp;nbsp;&lt;/p&gt;&lt;h3 style=&quot;text-align: justify;&quot; data-ke-size=&quot;size23&quot;&gt;언어학(Linguistics)&lt;/h3&gt;&lt;p data-ke-size=&quot;size16&quot; style=&quot;text-align: justify;&quot;&gt;갑자기 뜬금없이 Topic Modeling을 하는데, 언어학(Linguistics)이 왜 나오는가? 할 수 있다. 이번 글의 제목을 보면 알 수 있듯, Topic Modeling에 대해서 이론적인 접근을 기반으로 한다. 이론적인 접근을 하기 위해서는 언어학을 우선으로 알고 있어야 한다. &lt;br&gt;&lt;br&gt;최근에 Topic Modeling에서 많이 사용하는 Embedding 모델은 긴 문장을 Embedding할 수 있는 Titan (Amazon)이나, 대표적인 Multi-Lingual 모델인 bge-m3 모델일 것이다.&lt;br&gt;&amp;nbsp;&lt;br&gt;우리는 모델의 크기가 크면 클수록 성능이 좋다라고 알고 있으며(Scaling Laws, &lt;a href=&quot;https://arxiv.org/pdf/2001.08361/1000&quot; target=&quot;_blank&quot;&gt;&lt;span&gt;https://arxiv.org/pdf/2001.08361/1000&lt;/span&gt;&lt;/a&gt;), 가장 크고 SoTA인 Embedding 모델을 가지고 와서 사용하면 된다고 생각할 것이다. 그러나, 기업에서 혹은 일부 환경에서는 비용 혹은 보안 문제로 인해 공개된 Open Source 모델을 사용하는 것에 제약이 있을 수 있다. 이러한 환경에서는 언어학 관점에서의 해석이 좋은 모델을 선정하는 것에 있어서 도움을 줄 수 있다.&lt;br&gt;&amp;nbsp;&lt;br&gt;그렇다면, 자연어 처리에서의 언어학은 무엇을 의미하는가? 이 글에서 언급하고 싶은 것은 &lt;b&gt;굴절어, 고립어, 교착어&lt;/b&gt;를 의미한다.&lt;/p&gt;&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;&lt;li&gt;&lt;b&gt;굴절어 (Inflected Language)&lt;/b&gt;&lt;br&gt;&lt;span style=&quot;background-color: #ffffff;&quot;&gt;&lt;span style=&quot;color: #0a0a0a;&quot;&gt;단어의 형태(어미)가 문법적 기능(수, 격, 인칭 등)에 따라 굴절(변화)함. 어간과 접사의 경계가 불분명한 경우가 많으며, 대표적인 언어로는 영어, 프랑스어 등이 있다.&lt;/span&gt;&lt;/span&gt;&lt;/li&gt;&lt;li&gt;&lt;span style=&quot;background-color: #ffffff;&quot;&gt;&lt;span style=&quot;color: #0a0a0a;&quot;&gt;&lt;b&gt;고립어 (Isolating Language)&lt;/b&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&quot;background-color: #ffffff;&quot;&gt;&lt;span style=&quot;color: #0a0a0a;&quot;&gt;&lt;br&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&quot;background-color: #ffffff;&quot;&gt;&lt;span style=&quot;color: #0a0a0a;&quot;&gt;단어의 형태 변화가 전혀 없고, 문장 내 위치(어순)가 곧 문법적 역할을 결정함. 분석어라고도 하며, 대표적인 언어는 중국어가 있다.&lt;/span&gt;&lt;/span&gt;&lt;span style=&quot;background-color: #ffffff;&quot;&gt;&lt;span style=&quot;color: #0a0a0a;&quot;&gt;&lt;br&gt;&lt;/span&gt;&lt;/span&gt;&lt;/li&gt;&lt;li&gt;&lt;span style=&quot;background-color: #ffffff;&quot;&gt;&lt;span style=&quot;color: #0a0a0a;&quot;&gt;&lt;b&gt;교착어 (&lt;/b&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&quot;background-color: #ffffff;&quot;&gt;&lt;span style=&quot;color: #001d35;&quot;&gt;&lt;b&gt;Agglutinative Language)&lt;/b&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&quot;background-color: #ffffff;&quot;&gt;&lt;span style=&quot;color: #001d35;&quot;&gt;&lt;br&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&quot;background-color: #ffffff;&quot;&gt;&lt;span style=&quot;color: #0a0a0a;&quot;&gt;어근(실질적 의미)에 접사(문법적 기능)가 덧붙어 단어가 구성되며, 어간과 어미가 비교적 명확히 분리되며, 대표적인 언어는 한국어, 일본어 터키어가 있다.&lt;/span&gt;&lt;/span&gt;&lt;/li&gt;&lt;/ul&gt;&lt;p data-ke-size=&quot;size16&quot; style=&quot;text-align: justify;&quot;&gt;&amp;nbsp;&lt;br&gt;&lt;span style=&quot;background-color: #ffffff;&quot;&gt;&lt;span style=&quot;color: #0a0a0a;&quot;&gt;글로 보면 되게 어려워보일 수 있다. 쉽게 설명하자면, 굴절어는 I &amp;gt; My, Like &amp;gt; Likes 등과 같이 단어의 어미가 굴절(변화)하는 언어며, 교착어는 은, 는, 이, 가 와 같은 조사가 붙는 것으로 이해할 수 있다. 고립어는 我(나), 我的(나의) 와 같이 단어의 뜻 자체는 변화하지 않으나 위치에 따라 뜻이 달라지는 것을 의미한다. 영어는 굴절어의 특성을 근간으로 하고 있으나, 고립어의 성질도 일부 가지고 있다(동사, 주어 향태로 위치가 달라지면 의문문으로 변화). 한국어도 마찬가지로, 가+었다 &amp;gt; 갔다. 와 같은 경우에는 굴절어의 특성도 일부 가지고 있다라고 할 수 있다.&amp;nbsp;&lt;/span&gt;&lt;/span&gt;&lt;br&gt;&amp;nbsp;&lt;br&gt;&lt;span style=&quot;background-color: #ffffff;&quot;&gt;&lt;span style=&quot;color: #0a0a0a;&quot;&gt;이 언어학적 특성을 파악하게 된다면, &lt;/span&gt;&lt;/span&gt;&lt;span style=&quot;background-color: #ffffff;&quot;&gt;&lt;span style=&quot;color: #0a0a0a;&quot;&gt;&lt;b&gt;Positional Embedding&lt;/b&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&quot;background-color: #ffffff;&quot;&gt;&lt;span style=&quot;color: #0a0a0a;&quot;&gt;의 중요성을 깨닫게 될 것이다.&amp;nbsp;&amp;nbsp;Transformer 구조를 보면, Input Embedding과 Positional Embedding을 합산하여 모델의 입력으로 사용하고 있다(Segment Embedding도 있으나, 일단 여기서는 Skip한다). 문장 내 단어의 순서에 대한 Embedding 값을 부여하는 Positional Embedding은 실제 학습하는 언어의 유형에 따라 다르게 설정될 수 있다라는 것이다(교착어는 단어 위치에 대한 중요도가 낮으나, 고립어는 매우 중요하다). 즉, 학습한 데이터의 종류, 언어 등에 따라 같은 단어라도 모델에 따라, 위치에 따라 Embedding 값이 달라질 것이다. 따라서 모델의 선정함에 있어서 원래 하던 방식 뿐만 아니라, 언어학 관점으로 바라보고 모델을 선정하는 새로운 관점이 생길 수 있다라는 것이다. (언어학 관점은 모델의 선정에 있어서 하나의 도구로 사용하며, 실제 모델 선택에 있어서는 추가적인 것들을 다 고려하여 선정하여야 한다)&lt;/span&gt;&lt;/span&gt;&lt;span style=&quot;background-color: #ffffff;&quot;&gt;&lt;span style=&quot;color: #0a0a0a;&quot;&gt;&lt;br&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&quot;background-color: #ffffff;&quot;&gt;&lt;span style=&quot;color: #0a0a0a;&quot;&gt;&lt;br&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&quot;background-color: #ffffff;&quot;&gt;&lt;span style=&quot;color: #0a0a0a;&quot;&gt;연구 환경에 따라, distillation 된 작은 모델을 사용해야 된다면, 우리가 다루어야할 데이터의 성질에 따라 근간 모델의 학습한 데이터의 종류, 양, 언어 비중 등을 파악해야된다는 것을 시사한다.&lt;/span&gt;&lt;/span&gt;&lt;span style=&quot;background-color: #ffffff;&quot;&gt;&lt;span style=&quot;color: #0a0a0a;&quot;&gt;&lt;br&gt;&lt;/span&gt;&lt;/span&gt;&lt;/p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;380&quot; data-origin-height=&quot;560&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/b6fWbD/dJMcadnHsc6/x3Oiao0umg0Vedhb8a7Zuk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/b6fWbD/dJMcadnHsc6/x3Oiao0umg0Vedhb8a7Zuk/img.png&quot; data-alt=&quot;Transformer Architecture&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/b6fWbD/dJMcadnHsc6/x3Oiao0umg0Vedhb8a7Zuk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fb6fWbD%2FdJMcadnHsc6%2Fx3Oiao0umg0Vedhb8a7Zuk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;356&quot; height=&quot;525&quot; data-origin-width=&quot;380&quot; data-origin-height=&quot;560&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;Transformer Architecture&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;h3 style=&quot;text-align: justify;&quot; data-ke-size=&quot;size23&quot;&gt;문장 임베딩 (Sentence Embedding)&lt;/h3&gt;&lt;p data-ke-size=&quot;size16&quot; style=&quot;text-align: justify;&quot;&gt;Topic Modeling에 있어서 입력으로 주어지는 문장을 어떻게 잘 표현할 지 파악하는 것이 가장 중요하다. 문장을 Embedding하게 된다면, $\mathcal{S} \in \mathbb{R}^{n \times m}$ 형태로 나올 것이다. 가장 기본적인 문장 Embedding Vector를 생성하는 것은 각 토큰별 평균을 취해, $mean(\mathcal{S}) \in \mathbb{R}^{1 \times m}$ 을 하는 것이다($n$는 토큰 수, $m$는 임베딩 차원 수). 단순히 평균을 취하는 것이 문장을 대표하는 하나의 차원으로 볼 수 있으나, 가장 큰 실수 중 하나다.&amp;nbsp;&lt;br&gt;&amp;nbsp;&lt;/p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1280&quot; data-origin-height=&quot;448&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/DZ5p3/dJMb99ZYaiw/wHn4aHv7OtolITQLwou3mK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/DZ5p3/dJMb99ZYaiw/wHn4aHv7OtolITQLwou3mK/img.png&quot; data-alt=&quot;word2vec&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/DZ5p3/dJMb99ZYaiw/wHn4aHv7OtolITQLwou3mK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FDZ5p3%2FdJMb99ZYaiw%2FwHn4aHv7OtolITQLwou3mK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1280&quot; height=&quot;448&quot; data-origin-width=&quot;1280&quot; data-origin-height=&quot;448&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;word2vec&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;&lt;p data-ke-size=&quot;size16&quot; style=&quot;text-align: justify;&quot;&gt;Word2Vec에서 각 단어끼리 서로 유사한 의미를 지니고 있다면, +/- 를 통해 King에서 Queen으로 바꿀 수 있다는 것을 들어본 적이 있을 것이다. 반면에, 문장은 여러 단어(토큰)들로 구성되어 있기 때문에 단순히 +/- 를 통한 평균을 취하는 것은 많은 정보의 손실을 야기할 수 있다. 잠시 Fourier Transform의 개념을 가지고 와보자. Fourier Transform은 하나의 신호를 여러 신호로 분해하여 다양한 주기를 가지는 sin, cos 형태로 변형하는 것을 의미한다. 갑자기 왜 Fourier Transform을 갖고 오는지 의문이 들 수 있다. 우리가 문장을 신호의 합으로 보고, 문장을 합한 값을 0이라고 가정해보자. 이때, 하나의 토큰은 -1의 값을 가지고, 다른 하나의 토큰은 +1을 가진다면, 우리가 보는 값은 0으로 아무런 시그널이 없는 하나의 점으로 할당될 것이다. 이것이 바로 단순한 합산 혹은 평균으로 문장을 대표하는 점을 할당하는 것에 대한 가장 큰 문제가 될 수 있다.&lt;br&gt;&amp;nbsp;&lt;br&gt;그렇다면, 합산, 평균 외에 어떤 것을 활용할 수 있을까? 가장 대표적인 방법이 PCA, UMAP, tSNE 등의 차원 축소 기법이 있을 것이다. PCA는 문장을 대표하는 eigen vector (주성분1)를 우선으로 선정하고, eigen vector에 직교(orthogonal)하는 다른 하나의 eigen vector (주성분2)를 선택해 나아가는 방식이다. 원래의 차원 $m$까지 가면 갈수록 설명력은 증가하고, 차원의 크기는 커진다.&amp;nbsp;&lt;br&gt;$$ \mathcal{S}\in \mathbb{R}^{n \times m} \approx PCA(\mathcal{S}) \in \mathbb{R}^{n \times p}, p \leq m$$&lt;br&gt;&amp;nbsp;&lt;br&gt;그러나 PCA는 선형대수 관점에서의 접근이기 때문에 만약 단어 간의 비선형 관계가 존재한다면 고려하기 어렵다. 따라서, 이를 고려하기 위해 제안된 차원 축소 기법들이 tSNE, UMAP에 해당한다. 이는 다양체(Manifold) 즉, 위상공간을 고려한 차원 축소 기법이며, 차원 축소 기법에서 널리 사용되고 있다.&amp;nbsp;&lt;br&gt;&amp;nbsp;&lt;br&gt;선형 관계만 고려하면 왜 좋지 않은가?에 대한 답변으로는, A4 종이를 예시로 많이 설명한다. A4 용지 양 끝 단에 점을 하나씩 찍어보자. 2차원 A4 용지에는 양 두 점이 가장 끝단에 존재하기 때문에 유사하지 않아보일 수 있다. 이때, A4용지의 양끝을 마주보도록 돌돌 말아보자. 이때에는 가장 가까운 점이 될 수 있다. 전자의 경우를 선형이라고 생각하고, 후자의 경우를 비선형이라고 생각하면 이해하기 쉬울 것이다.&lt;br&gt;&amp;nbsp;&lt;br&gt;다시 돌아가서, 우리가 문장을 임베딩하고, 비선형적인 구조를 파악하기 위한 최종 Matrix 혹은 Tensor가 도출되면, 사전에 정의해둔 단어의 임베딩 값과 내적하고 크기를 나누어주어 유사도를 계산한다. 이때, 유사도를 기준으로 일정 유사도 이하인 단어에 대해서는 새로운 토픽을 생성하는 식으로 모델을 설계할 수 있다.&amp;nbsp;&lt;br&gt;&amp;nbsp;&lt;/p&gt;&lt;h3 style=&quot;text-align: justify;&quot; data-ke-size=&quot;size23&quot;&gt;새로운 토픽 생성&lt;/h3&gt;&lt;p data-ke-size=&quot;size16&quot; style=&quot;text-align: justify;&quot;&gt;새로운 토픽을 생성하는 방법은 이전 단계에서 유사도가 낮은 문장들을 발췌하여 n-gram으로 문장을 대표하는 단어를 선정할 수 있다. 해당 방식이 일반적으로 사용되나, 최근에는 LLM을 사용하여 토픽을 자동으로 선정하는 방식을 많이 사용한다. n-gram을 사용하게 되면, 실제 문장 내에 문장을 대표하는 단어가 포함되어 있으면 좋은 방법이지만, 대표하는 단어가 포함되지 않은 경우에도 가장 대표할 법한 단어를 도출한다는 것이 문제다. 만약 뉴스에 대한 토픽을 선정하는 경우에는 KeyBERT와 모델을 사용하여 도출할 수 있지만, SNS 와 같은 주제에서는 다소 성능이 떨어질 수 있다.&amp;nbsp;&lt;br&gt;&amp;nbsp;&lt;br&gt;이럴 때에는 LLM을 활용하여 문장으로 토픽이 될 법한 단어를 추론하는 방법을 사용한다. LLM을 토픽 추출에 사용하려면 LLM의 구조에 대해서 먼저 알아야 한다. LLM은 Next Token Prediction이며, 문장을 입력으로 받고, 다음으로 나올 확률이 가장 높은 단어를 생성하여 답변을 종료한다. 여기서 이야기하는 &lt;b&gt;다음으로 나올 확률이 가장 높은 단어&lt;/b&gt;라는 것은 모델의 어휘 사전(Vocabulary) 내에 가장 유사한 단어를 의미한다. 다시 말하자면, 모델의 어휘 사전 내에 우리가 원하는 단어가 없다면 답변 자체를 못한다는 것이다. 이것이 바로 대표적인 할루시네이션(Hallucination) 현상 중 하나다.&amp;nbsp;&lt;br&gt;&amp;nbsp;&lt;br&gt;그럼, 모델이 할루시네이션을 발생하는 지 안하는지 어떻게 알 수 있을까? 만약 모델을 학습하고 예측값을 볼 수 있다면, &lt;b&gt;Perplexity&lt;/b&gt;를 확인하면 된다. Perplexity는 모델이 헷갈려하는 정도를 측정한 지표다. LLM은 Next Token Prediction 과정에서 Beam Search를 활용한다. 다음 생성될 후보 토큰을 선정하고, 계속 확장해 나아가면서 Perplexity가 최소화되는 문장을 선정하는 것이다. 하지만, Perplexity를 고려한다고 하더라도 할루시네이션 현상을 0%로 만드는 것은 어렵다.&lt;br&gt;&amp;nbsp;&lt;br&gt;그 다음으로는 할루시네이션을 완화할 수 있는 방법에 대해서 알아보자. 가장 대표적인 방법으로는 &lt;b&gt;RAG (Retrieval Agmented Generation), DPO (Direct Preference Optimization), TTT (Test-Time Training)&lt;/b&gt; 등이 존재한다. 이번 글에서는 가장 간단한 RAG에 대해서만 다루어 볼 것이다.&amp;nbsp;&lt;br&gt;&amp;nbsp;&lt;br&gt;RAG는 검색 혹은 Vector Search를 통해, 입력과 유사한 K개의 참고자료를 제공하여 Prompt에 함께 제공하여 모델의 할루시네이션을 줄이는 방식 중 하나다. (최근에는 MCP 를 활용하여 다양한 정보 혹은 LLM을 연결하는 Agentic AI 등도 많이 적용되고 있다) 새로운 주제의 문장이 들어올 때, Vector DB 내에 가장 유사한 문서를 제공하고, 해당 문서가 어떤 주제로 할당되었는지 Q&amp;amp;A 형태로 제공하고, 이에 대해서 LLM이 판단해 새로운 토픽을 생성하게 될 것이다.&amp;nbsp;&lt;br&gt;&amp;nbsp;&lt;br&gt;위 방식으로 새로운 토픽이 생성되고, 문장에 할당되면 할당된 토픽이 제대로 할당되었는지 확인하고 적용하는 것이 가장 중요하다. 이때, 사용하는 방식이 DPO, TTT 등이 존재한다. 이는 실제 학습까지 이루어져야 하는 것이기 때문에 다음에 다루어 보도록 하자.&amp;nbsp;&lt;br&gt;&amp;nbsp;&lt;/p&gt;&lt;h3 style=&quot;text-align: justify;&quot; data-ke-size=&quot;size23&quot;&gt;마무리&lt;/h3&gt;&lt;p data-ke-size=&quot;size16&quot; style=&quot;text-align: justify;&quot;&gt;이렇게 Topic Modeling을 하는 과정에서 내가 이론적으로 고려한 부분에 대해서 간략하게나마 설명하였다. 실제 현업에서는 더 다양한 것들을 고려해야하고, ROI가 나와야하기 때문에 LLM을 최대한 적게 사용하여 비용을 절감하는 방식으로도 많이 사용한다. 최근에 LLM이 발전하면서 많은 사람들이 LLM을 통해 모델링을 하고 있다. OpenClaw 등의 Agentic AI를 사용하고 Claude의 Skills.md에 몇 줄만 작성해도 개발을 할 수 있는 세상이 도래하였다. 그 과정에서 이론적으로 접근하는 사람들이 많이 줄어들고, 만능 AI 시대로 발전해 나아가는 것 같아서, 이론으로 접근하는 방식에 대한 글이 있었으면 하는 마음에 작성하게 되었다.&amp;nbsp;&lt;br&gt;&amp;nbsp;&lt;br&gt;&amp;nbsp;&lt;br&gt;코드는 여기를 참고하면 된다. (&lt;a href=&quot;https://github.com/ceo21ckim/BERTopic-Tutorial&quot; target=&quot;_blank&quot;&gt;&lt;span&gt;https://github.com/ceo21ckim/BERTopic-Tutorial&lt;/span&gt;&lt;/a&gt;)&lt;/p&gt;</description>
      <category>Deep Learning/Natural Language Processing</category>
      <category>BERTopic</category>
      <category>embedding</category>
      <category>sentence embedding</category>
      <category>Topic Modeling</category>
      <category>임베딩</category>
      <category>자연어처리</category>
      <category>토픽 모델링</category>
      <author>언킴</author>
      <guid isPermaLink="true">https://ok-lab.tistory.com/361</guid>
      <comments>https://ok-lab.tistory.com/361#entry361comment</comments>
      <pubDate>Sun, 15 Mar 2026 17:11:44 +0900</pubDate>
    </item>
    <item>
      <title>[Spring] Spring MVC 구조 파악</title>
      <link>https://ok-lab.tistory.com/360</link>
      <description>&lt;div class=&quot;book-toc&quot;&gt;
&lt;p data-ke-size=&quot;size32&quot;&gt;Contents&lt;/p&gt;
&lt;ul id=&quot;toc&quot; style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;&lt;/ul&gt;
&lt;/div&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;script src=&quot;https://polyfill.io/v3/polyfill.min.js?features=es6&quot;&gt;&lt;/script&gt;
&lt;script src=&quot;https://cdn.jsdelivr.net/npm/mathjax@3/es5/tex-mml-chtml.js&quot;&gt;&lt;/script&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;Spring MVC&lt;/h3&gt;
&lt;p style=&quot;text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;Spring MVC는 Model + View + Controller를 의미하며, 웹 사이트에서 들어오는 요청을 역할을 구분지어 관리하기 위한 기본적인 구조라고 볼 수 있다. Model은 비즈니스 로직, 데이터, DB 접근 등을 담당하고, View는 화면 단계에서 어떤 것을 보여줄지(View)에 대한 부분만 집중한다. 마지막으로 Controller는 웹 상에서의 사용자의 요청을 받아 적절한 Model을 호출하고, 결과를 View에 전송하는 중간 역할을 담당한다.&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1764470103836&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;src
 ├─ main
 │   ├─ java/com/example/demo
 │   │    ├─ controller
 │   │    │     └─ HomeController.java      &amp;larr; Controller
 │   │    ├─ domain
 │   │    │     └─ Member.java              &amp;larr; Model (DTO/Entity)
 │   │    └─ service
 │   │          └─ MemberService.java       &amp;larr; Model (비즈니스 로직)
 │   └─ resources/templates
 │          └─ home.html                    &amp;larr; View (화면)&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;Intellij에서 Spring Boot 로 프로젝트를 생성하면, src/main/java/com/example/demo 경로로 파일을 생성해준다. 이때, demo 안에 controller와 member, service, home.html이 어떤 역할을 하는지, 그리고 어떤 코드로 작성하여야 하는지 알아보자.&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 style=&quot;text-align: justify;&quot; data-ke-size=&quot;size23&quot;&gt;Controller&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Controller는 웹에서 사용자의 요청 (Enter, 화면 이동 등)이 들어왔을 때 요청에 맞는 비즈니스 로직를 수행하고 View에 데이터를 보내주는 역할을 수행한다. localhost:8080 port에 가상의 웹을 만들었다라고 가정해보자. 그러면 해당 주소로 접속했을 때, home.html로 안내하는 비즈니스 로직을 수행하는 것이다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1764470793863&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;package com.example.demo.controller;

import com.example.demo.domain.Member;
import com.example.demo.service.MemberService;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestParam;

@Controller
public class HomeController {

    private final MemberService memberService;

    public HomeController(MemberService memberService) {
        this.memberService = memberService;
    }

    @GetMapping(&quot;/&quot;) // 사용자가 &quot;/&quot;로 접근
    public String home() {
        return &quot;home&quot;; // View 파일 이름
    }

    @PostMapping(&quot;/add&quot;)
    public String addMember(@RequestParam String name, Model model) {
        memberService.join(new Member(name));      // Model &amp;rarr; Service 호출
        model.addAttribute(&quot;members&quot;, memberService.findAll());  // View에 데이터 전달
        return &quot;home&quot;; // 응답할 View 이름
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;이때 public class의 명칭은 파일의 명칭과 동일하여야 한다. HomeController 내에 @GetMapping(&quot;/&quot;) 즉, 기본 경로로 접근했을 때에는 home.html로 안내한다. 그 이후 @PostMapping은 /add라는 추가 경로가 지정되었을 때, 요청에 맞는 Member 정보에 맞는 Service를 호출하고, 다시 home.html로 안내하는 구조다.&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 style=&quot;text-align: justify;&quot; data-ke-size=&quot;size23&quot;&gt;Model&lt;/h3&gt;
&lt;p style=&quot;text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;Model은 데이터 + 비즈니스 로직 전체를 포함한다고 볼 수 있다. 단순 조회만 하는 VO (View Object), DB에 접근하여 CRUD를 담당하는 DAO (Data Acess Object), 데이터를 전달하는 역할을 담당하는 DTO (Data Transfer Object) 로 구성되어 있다.&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1764471818484&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;// Member.java
package com.example.demo.domain;

public class Member {
    private String name;

    public Member() {}

    public Member(String name) {
        this.name = name;
    }

    public String getName() {
        return name;
    }
}

// Service.java
package com.example.demo.service;

import com.example.demo.domain.Member;
import org.springframework.stereotype.Service;
import java.util.ArrayList;
import java.util.List;

@Service
public class MemberService {

    private final List&amp;lt;Member&amp;gt; store = new ArrayList&amp;lt;&amp;gt;();

    public void join(Member member) {
        store.add(member);
    }

    public List&amp;lt;Member&amp;gt; findAll() {
        return store;
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;예시 코드는 아주 간단한 구조로 되어 있으며, DB를 조회할 수 있는 경우 Controller의 요청에 따른 적재된 값을 호출하는 코드로 응용할 수 있다.&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 style=&quot;text-align: justify;&quot; data-ke-size=&quot;size23&quot;&gt;View&lt;/h3&gt;
&lt;p style=&quot;text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;View는 말그대로, html, xml 등의 화면을 의미한다. 화면은 구성하기 나름이며, Controller를 통해 요청에 따른 비즈니스 로직을 수행하고 값이 적재된 값을 보여주는 것도 가능하고, 기존에 적재된 값을 보여주는 화면을 만들 수도 있다.&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1764472096909&quot; class=&quot;html xml&quot; data-ke-language=&quot;html&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;&amp;lt;!DOCTYPE html&amp;gt;
&amp;lt;html xmlns:th=&quot;http://www.thymeleaf.org&quot;&amp;gt;
&amp;lt;body&amp;gt;
&amp;lt;h1 th:text=&quot;${name}&quot;&amp;gt;default text&amp;lt;/h1&amp;gt;
&amp;lt;/body&amp;gt;
&amp;lt;/html&amp;gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;위 코드는 기존에 적재되어 있는 name이라는 entity를 보여주고, default text라고 보여주는 것을 의미한다. html이나 xml 등 뿐만 아니라 vue.js 등으로 화면을 구성하고, 각 속성에 맞는 값들을 적합한 위치에 보여줌으로써 Spring MVC 구조를 이해할 수 있다.&amp;nbsp;&lt;/p&gt;</description>
      <category>controller</category>
      <category>java</category>
      <category>model</category>
      <category>MVC</category>
      <category>Spring</category>
      <category>Spring MVC</category>
      <category>view</category>
      <author>언킴</author>
      <guid isPermaLink="true">https://ok-lab.tistory.com/360</guid>
      <comments>https://ok-lab.tistory.com/360#entry360comment</comments>
      <pubDate>Sun, 30 Nov 2025 12:09:55 +0900</pubDate>
    </item>
    <item>
      <title>[Python] requirements.txt 작성하기</title>
      <link>https://ok-lab.tistory.com/359</link>
      <description>&lt;p style=&quot;text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;협업을 하다보면 requirements.txt 를 통해 본인이 실험하고 있는 환경에 대한 정보를 전달할 때가 존재한다. 가장 확실한 방법은 Dockerfile을 공유하여 docker image를 pull해서 서로 같은 가상환경 내에서 작업을 하는 것이다. 그러나, 가상환경을 잡고 하는 부분은 처음 시작하는 사용자에게는 어려울 수 있고, 해당 언어에 대한 내용을 알아야 사용할 수 있기 때문에 requirements.txt를 사용하게 된다. (추가로 poetry를 사용하여 버전 freeze를 할 수도 있다.)&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;그렇다면 requirements.txt를 어떻게 작성하는지에 대해서 알아보자. 예를 들어 pandas 를 설치하게 된다면 아래와 같은 코드를 사용하여 설치할 것이다.&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1755581869362&quot; class=&quot;python&quot; data-ke-language=&quot;python&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;!pip install pandas&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;단순히 버전 명시 없이 pandas를 설치하게 되면 가장 최신 버전(latest)의 pandas가 설치될 것이다. 그러나, 같이 협업하는 사람은 옛날 버전의 pandas를 사용해서 현재 버전에 추가가 된 함수나 명령어에 차이가 존재할 수 있다. 그렇기 때문에 pandas의 버전이 어떻게 되는지를 명시해야 한다.&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;각 package의 버전을 확인하는 방법은 간단하다.&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1755581966041&quot; class=&quot;python&quot; data-ke-language=&quot;python&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;import pandas as pd 
pd.__version__
# 2.3.1&lt;/code&gt;&lt;/pre&gt;
&lt;p style=&quot;text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;위 코드로 쉽게 pandas의 버전을 확인할 수 있다. 지금까지 pandas의 버전을 확인하는 법을 다루었다. 그렇다면 requirements.txt는 어떻게 작성하는 것일까? 매우 간단하다. 같은 경로에 requirements.txt라는 파일을 생성하고 하기 내용 처럼 작성하기만 하면 된다.&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1755582041852&quot; class=&quot;python&quot; data-ke-language=&quot;python&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;# requirements.txt

pandas==2.3.1&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;추가하고 싶은 package가 있다면 아래에 계속해서 작성해주면 된다. 그러나 package를 하나하나 버전을 확인하고 requirements.txt를 작성하는 것은 매우 번거로운 작업이다. 따라서, 아래와 같은 코드를 작성하면 package 이름만 넣어서 바로 requirements.txt를 작성할 수 있다.&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1755582162173&quot; class=&quot;python&quot; data-ke-language=&quot;python&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;import os

from pandas as pd, numpy as np
BASE_DIR = os.path.dirname(__file__)

def package_version_check(packs, requirements=False, base_path=None) -&amp;gt; None:
    versions = dict()
    if isinstance(packs, list):
        for pack in packs:
            print(f'{pack.__name__} version: {pack.__version__}')
            versions[pack.__name__] = pack.__version__
    else:
        print(f'{packs.__name__} version: {packs.__version__}')
        versions[pack.__name__] = pack.__version__
    
    if requirements:
        base_path = os.path.dirname(__file__) if not base_path else base_path
        with open(os.path.join(BASE_DIR, 'requirements.txt'), 'w') as f: 
            for pack, ver in versions.items():
                f.write(f'{pack}=={ver}\n')
                
package_version_check([pd, np], requirments=True)&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;자동으로 requirements.txt를 생성하고, 적재한 위치에 package별 version을 명시해서 작성한다. 만약 모든 package에 대해서 한꺼번에 requirements.txt를 작성하는 것은 매우 번거로운 작업이다. 이를 해결하는 방법은 cmd 창에서 코드 한줄로 가능하다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1755582278741&quot; class=&quot;python&quot; data-ke-language=&quot;python&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;pip3 freeze &amp;gt; requirements.txt&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;가상환경 내에 접근해서 위 명령어를 작성하게 되면 해당 경로에 requirements.txt가 바로 작성된다. 만약 특정 패키지에 대해서 명시하고 주석을 작성하면서 만들고 싶은 경우에는 위 함수를 사용해서 적용이 가능하지만, 일괄 적용하고 싶은 경우에는 freeze를 사용하는 것이 유용할 수 있다.&amp;nbsp;&lt;/p&gt;</description>
      <category>Python</category>
      <category>code freeze</category>
      <category>Python</category>
      <category>requirements.txt</category>
      <category>파이썬</category>
      <category>환경 공유</category>
      <author>언킴</author>
      <guid isPermaLink="true">https://ok-lab.tistory.com/359</guid>
      <comments>https://ok-lab.tistory.com/359#entry359comment</comments>
      <pubDate>Tue, 19 Aug 2025 14:46:08 +0900</pubDate>
    </item>
    <item>
      <title>[Python] 폴더 내 모든 경로의 파일 조회하기</title>
      <link>https://ok-lab.tistory.com/358</link>
      <description>&lt;div class=&quot;book-toc&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;p style=&quot;text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;파이썬을 사용하다 보면 경로에 대해 중요시하게 된다. Dataset의 경로 내에 있는 '.csv' 등의 파일을 모두 호출해서 해당 파일을 for 문 형태로 받아오는 일이 생길 수 있다. 이때 주로 사용하는 패키지는 os와 glob가 있다.&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 style=&quot;text-align: justify;&quot; data-ke-size=&quot;size23&quot;&gt;os.walk&lt;/h3&gt;
&lt;pre id=&quot;code_1755579015788&quot; class=&quot;python&quot; data-ke-language=&quot;python&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;BASE_DIR = os.path.dirname(__file__) # os.path.dirname(os.getcwd())

def find_file(ext='.csv'):
    &quot;&quot;&quot;
    확장자가 일치하는 파일을 호출함.
    &quot;&quot;&quot;
    paths = []
    for (path, _, files) in os.walk(BASE_DIR):
        for filename in files:
            ex = os.path.splitext(filename)[-1]
            if ex == ext:
            	p = os.path.join(path, filename)
                print(p)
                paths.append(p)
    return paths&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;os.walk는 폴더를 순회하면서 전체 폴더 내에 있는 모든 확장자에 대해서 호출하는 형태다. 만약 특정 폴더 내에 있는 csv 파일만 호출하기 위해서는 glob를 사용할 수도 있다. glob는 조금 더 간소화된 패키지라고 보면 된다. __file__이 실행이 안된다면, os.getcwd()로 경로를 설정해서 사용할 수도 있다.&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 style=&quot;text-align: justify;&quot; data-ke-size=&quot;size23&quot;&gt;glob&lt;/h3&gt;
&lt;pre id=&quot;code_1755579182208&quot; class=&quot;python&quot; data-ke-language=&quot;python&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;import glob 

BASE_DIR = os.path.dirname(__file__)

glob.glob(os.path.join(BASE_DIR, '*.csv'))&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;glob는 정규표현식에 만족하는 파일을 모두 호출할 수 있다. *.csv를 사용하게 되면, .csv로 끝나는 모든 파일을 호출하게 되고, ? 혹은 + 등을 사용하여 처리할 수 있다. os.walk보다는 효율적이지만 만약 하나 더 깊이 있는 파일을 조회하기 위해서는 &quot;*/*.csv&quot;로 처리해야 되기 때문에 다소 번거로울 수 있다.&lt;/p&gt;</description>
      <category>Python</category>
      <category>glob</category>
      <category>os</category>
      <category>os.walk</category>
      <category>경로설정</category>
      <category>파일</category>
      <category>파일 조회하기</category>
      <author>언킴</author>
      <guid isPermaLink="true">https://ok-lab.tistory.com/358</guid>
      <comments>https://ok-lab.tistory.com/358#entry358comment</comments>
      <pubDate>Tue, 19 Aug 2025 13:54:58 +0900</pubDate>
    </item>
    <item>
      <title>[LangGraph] LangGraph 톺아보기</title>
      <link>https://ok-lab.tistory.com/357</link>
      <description>&lt;div class=&quot;book-toc&quot;&gt;
&lt;p style=&quot;text-align: justify;&quot; data-ke-size=&quot;size32&quot;&gt;Contents&lt;/p&gt;
&lt;ul id=&quot;toc&quot; style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;&lt;/ul&gt;
&lt;/div&gt;
&lt;p style=&quot;text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;
&lt;script src=&quot;https://polyfill.io/v3/polyfill.min.js?features=es6&quot;&gt;&lt;/script&gt;
&lt;script src=&quot;https://cdn.jsdelivr.net/npm/mathjax@3/es5/tex-mml-chtml.js&quot;&gt;&lt;/script&gt;
&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 style=&quot;text-align: justify;&quot; data-ke-size=&quot;size23&quot;&gt;LangGraph Introduction&lt;/h3&gt;
&lt;p style=&quot;text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;LLM을 사용하다 보면 LangChain과 LangGraph에 대해서 들어보았을 것이다. 두 패키지는 LLM을 쉽게 사용할 수 있고, LLM이 가지고 있는 &lt;b&gt;환각(Hallucination)을 완화&lt;/b&gt;하기 위해 제안되었다. LLM의 가장 고질적인 문제인 환각 현상을 회피하기 위해 &lt;b&gt;RAG(Retrieval-Augmented Generation)&lt;/b&gt;이 제안되었고, RAG는 LLM에서 학습되지 않은 정보에 대해서 답변할 때 인터넷 혹은 논문 등과 같은 정보를 활용하여 환각이 발생하지 않고 정확한 답변을 내는 기법 중 하나라고 볼 수 있다.&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;금융권이나 업무 보조 등으로 LLM을 사용하는 경우에는 관련 약관을 제공하거나 법률 등의 질문에 대한 답변에서 환각이 발생하게 되면 큰 문제로 이어지기 때문에 속도가 느리더라도 정확도가 매우 중요하다. 이런 경우에는 실제 내부의 보험 약관 자체를 RAG로 구성하여 검색을 통해 정확한 답변을 생성하게 된다.&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;이번 글에서는 LangGraph에 대해서만 다루고, RAG와 Lanchain의 디테일한 정보는 다른 글에서 다룰 예정이다. LangGraph가 무엇인지, 어떻게 사용하는지에 대해서 알아보자.&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;LangGraph는 기본적으로 Node와 Edge로 구성되어 있기 때문에 Graph로 표현한다. 이때 Node는 각 세부 과정들을 의미하고, Edge는 Node와 Node를 연결하는 간선으로 볼 수 있다. 이때 조건부 Edge를 사용할 수 있는데, 조건부 Edge는 특정 조건에 만족하는 경우에 다음 Node를 실행하는 로직이라고 보면 된다.&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;일반적인 Q&amp;amp;A 과정은 질문(Question) - 검색(Retrieve) - 답변 생성(Generation) - 답변(Answer) 형태로 구성된다. 이때 질문, 검색, 생성, 답변을 Node로 생각하면 되고, 둘을 순서대로 이어주는 &lt;b&gt;'-'&lt;/b&gt;을 Edge라고 이해하면 된다. 위 과정에서 웹 검색 (Web Search) 라는 Node를 추가하고 나서 질문 후 검색을 수행하고, 웹 검색을 통해 서로 검증 후 답변하는 조건문 Edge를 생성할 수 있다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1280&quot; data-origin-height=&quot;613&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/OWCSb/btsPTAJSrd7/nOYIHjyOuxkAXzgF0uMKak/img.jpg&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/OWCSb/btsPTAJSrd7/nOYIHjyOuxkAXzgF0uMKak/img.jpg&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/OWCSb/btsPTAJSrd7/nOYIHjyOuxkAXzgF0uMKak/img.jpg&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FOWCSb%2FbtsPTAJSrd7%2FnOYIHjyOuxkAXzgF0uMKak%2Fimg.jpg&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;657&quot; height=&quot;315&quot; data-origin-width=&quot;1280&quot; data-origin-height=&quot;613&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;Node를 서로 연결하기만 한다면, 관계만 파악하기 때문에 작동이 되지 않을 것이다. 그래서 State를 따로 사용하여 Node에서 다음 Node로 Edge를 활용하여 State를 전달해 조건부 Edge의 종료/진행 여부를 정할 수도 있고, CheckPoint 등을 만들 수도 있다. 개략적인 LangGraph에 대한 내용은 알아봤으니 코드로 더 자세히 알아보자.&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 style=&quot;text-align: justify;&quot; data-ke-size=&quot;size23&quot;&gt;Implementation&lt;/h3&gt;
&lt;h4 style=&quot;text-align: justify;&quot; data-ke-size=&quot;size20&quot;&gt;Graph State 정의&lt;/h4&gt;
&lt;p style=&quot;text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;LangGraph에서는 기본적으로 typing을 명시하여 사용하고 있다. 실제 입력으로 들어오는 값이 int인지 str인지, list인지를 명확하게 구분하여 어떤 데이터 유형이 들어오더라도 해당 type으로 변환하여 사용하기 위함이다.&lt;/p&gt;
&lt;pre id=&quot;code_1755137541323&quot; class=&quot;python&quot; data-ke-language=&quot;python&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;from typing import Annotated, TypedDict, List, Dict
from langchain_core.documents import Document
from langgraph.graph.message import add_messages
# import operator

class State(TypedDict):
    messages: Annotated[list, add_messages] # operator.add&lt;/code&gt;&lt;/pre&gt;
&lt;p style=&quot;text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;State는 Node에서 다음 Node로 제공해주는 GraphState를 의미하고, 해당 정보에는 답변 뿐만 아니라, 질문, 검색 결과, 관련성(Relevance) 등을 전달할 수 있다. add_message는 과거에 전달 받았던 Message를 list에 계속 append한다는 것을 의미한다. 만약에 단순히 &lt;b&gt;Annotated[str, &quot;Answer&quot;]&lt;/b&gt;으로 표현한다면 과거에 받은 정보는 없어지고 계속 Update되는 형태가 된다. 과거에 나누었던 대화 정보들을 계속 기록하도록 LLM을 구성하려면 add_message를 사용하여야 한다. (add_message 대신 operator.add 사용 가능)&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 style=&quot;text-align: justify;&quot; data-ke-size=&quot;size20&quot;&gt;Node 정의&lt;/h4&gt;
&lt;p style=&quot;text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;Node는 본인이 정의하기 나름이며, Node는 입력으로 State를 받고 출력으로 State를 반환하면 된다.&lt;/p&gt;
&lt;pre id=&quot;code_1755146630377&quot; class=&quot;python&quot; data-ke-language=&quot;python&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;from langgraph.graph import StateGraph

def retrieve(state: State) -&amp;gt; State:
    documents = 'answer'
    return State(context=documents)

def claude_execute(state: State) -&amp;gt; State:
    answer = 'answer'
    return State(answer=answer)

def relevance_check(state: State) -&amp;gt; State:
    binary_score = 'binary_score'
    return State(binary_score=binary_score)

def sum_up(state: State) -&amp;gt; State:
    answer = 'sum'
    return State(answer=answer)
    
    
workflow = StateGraph(State)

workflow.add_node('retrieve', retrieve)

workflow.add_node('question', claude_execute)

workflow.add_node('relevance', relevance_check)

workflow.add_node('results', sum_up)&lt;/code&gt;&lt;/pre&gt;
&lt;p style=&quot;text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;지금 구조는 질문 - 답변 - 정리 과정으로 보면 된다. 각각의 Node에 해당하는 정보를 작성하고 우리가 원하는 흐름의 Edge를 연결하면 기본적인 세팅은 완료된다. StateGraph는 LangGraph 내에 존재한다.&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 style=&quot;text-align: justify;&quot; data-ke-size=&quot;size20&quot;&gt;Edge 정의&lt;/h4&gt;
&lt;pre id=&quot;code_1755147275689&quot; class=&quot;python&quot; data-ke-language=&quot;python&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;from langgraph.graph import END

workflow.add_edge('retrieve', 'question')

workflow.add_edge('question', 'relevance')

workflow.add_edge('relevance', 'results')

workflow.add_edge('results', END)&lt;/code&gt;&lt;/pre&gt;
&lt;p style=&quot;text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;조건부 Edge가 아닌 일반적인 Edge의 경우에는 단순히 이렇게 연결만 하면 된다. 중요한 부분은 LangGraph의 마지막 지점을 END로 지정해서 실제 workflow의 마지막을 지정해주어야 한다.&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 style=&quot;text-align: justify;&quot; data-ke-size=&quot;size20&quot;&gt;Conditional Edge 정의&lt;/h4&gt;
&lt;pre id=&quot;code_1755148561970&quot; class=&quot;python&quot; data-ke-language=&quot;python&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;def decision(state: State) -&amp;gt; State:
    decision = 'decision'
    return decision

workflow.add_conditional_edges(
    'summary',
    decision,
    {
        're-retrieve': 'retrieve',
        'end': END
    }
)

# workflow.add_edge('results', END) 주석필요&lt;/code&gt;&lt;/pre&gt;
&lt;p style=&quot;text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;Conditional Edge를 설정하기 위해서는 add_edge가 아닌 add_conditional_edge로 쉽게 활용이 가능하다. 먼저, 조건에 따라 결정을 내릴 수 있는 Node를 추가로 하나 더 설정하여야 하고, 그 결과에 따라서 if인 경우에는 재검색(re-retrieve)을 수행하고 아닌 경우에는 END로 종결하는 형태의 논리 구조를 짤 수 있다.&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;langgraph.graph 내의 START로 시작지점을 지정해주어도 되고, 하기 코드를 통해 시작 State가 어딘지 지정해주어도 된다. 그리고 memory를 할당하여 graph를 compile하면 llm의 논리 구조를 전부 만들었다.&lt;/p&gt;
&lt;pre id=&quot;code_1755147536622&quot; class=&quot;python&quot; data-ke-language=&quot;python&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;from IPython.display import Image, display

workflow.set_entry_point('retrieve')

memory = MemorySaver()
from langgraph.checkpoint.memory import MemorySaver

graph = workflow.compile(checkpointer=memory)

# visualization
display(Image(graph.get_graph().draw_mermaid_png()))&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;115&quot; data-origin-height=&quot;531&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bafy4r/btsPSZjFkjh/8KMMBEoGibBcO8se1eyuM0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bafy4r/btsPSZjFkjh/8KMMBEoGibBcO8se1eyuM0/img.png&quot; data-alt=&quot;Edge로만 구성된 Graph&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bafy4r/btsPSZjFkjh/8KMMBEoGibBcO8se1eyuM0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fbafy4r%2FbtsPSZjFkjh%2F8KMMBEoGibBcO8se1eyuM0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;103&quot; height=&quot;476&quot; data-origin-width=&quot;115&quot; data-origin-height=&quot;531&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;Edge로만 구성된 Graph&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;191&quot; data-origin-height=&quot;579&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/6hP77/btsPUFKYPuT/GjkNyjYUEhxXweK8gpkt10/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/6hP77/btsPUFKYPuT/GjkNyjYUEhxXweK8gpkt10/img.png&quot; data-alt=&quot;조건부 Edge가 추가된 Graph&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/6hP77/btsPUFKYPuT/GjkNyjYUEhxXweK8gpkt10/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2F6hP77%2FbtsPUFKYPuT%2FGjkNyjYUEhxXweK8gpkt10%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;144&quot; height=&quot;437&quot; data-origin-width=&quot;191&quot; data-origin-height=&quot;579&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;조건부 Edge가 추가된 Graph&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;최종 graph를 시각화하게 된다면, Retrieve - Question - Results 형태로 나오는 것을 확인할 수 있고, set_entry_point를 통해 시작지점을 설정하고 END를 통해 끝지점을 설정했기 때문에 __start__, __end__도 함께 출력되는 것을 확인할 수 있다. 조건부 Edge를 넣은 것과 넣지 않은 것과는 Loop가 생기냐 생기지 않냐 정도로 볼 수 있다.&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;재검색 과정이 없는 경우에는 LLM이 잘못된 답변을 하더라도 사용자는 무조건 그 정보를 제공받아야 한다. 만약에 if else로 구성된 추가의 조건부 Edge를 설정하여 재검색 과정을 추가하면 LLM이 첫 번째로 잘못된 답변을 수행하더라도 한 번 다시 생각하고 수정할 수 있어서 보다 정확한 정보를 제공받을 수 있다.&amp;nbsp;&lt;/p&gt;</description>
      <category>Python/Langgraph</category>
      <category>LangChain</category>
      <category>langgraph</category>
      <category>llm</category>
      <category>랭그래프</category>
      <category>환각</category>
      <author>언킴</author>
      <guid isPermaLink="true">https://ok-lab.tistory.com/357</guid>
      <comments>https://ok-lab.tistory.com/357#entry357comment</comments>
      <pubDate>Thu, 14 Aug 2025 13:57:18 +0900</pubDate>
    </item>
    <item>
      <title>[Python] dotenv로 api key 환경변수 설정하기 / 숨기기</title>
      <link>https://ok-lab.tistory.com/356</link>
      <description>&lt;p style=&quot;text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;파이썬에서 환경변수를 설정하는 방법은 다양하다. 실제 제어판에서 환경변수를 설정하고 경로를 지정하는 방법 외에 dotenv를 통해서도 지정이 가능하다. 도커를 사용하면 Docker Image 파일에서 설정하는 것이 가능하지만, Conda 환경에서는 .env 파일로 관리하는 것이 효율적이다. 주로 LLM 모델을 사용할 때 API_KEY를 지정하는 경우 주로 사용된다.&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1755046966481&quot; class=&quot;python&quot; data-ke-language=&quot;python&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;from dotenv import load_dotenv
load_dotenv()
# True&lt;/code&gt;&lt;/pre&gt;
&lt;p style=&quot;text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;위 과정을 수행하려면 실행하는 ipynb 혹은 py 파일와 같은 경로상에 .env라는 파일이 필요하다. 해당 파일 내에 API_KEY를 설정해두고 호출하면 환경변수가 설정된 것을 확인할 수 있다.&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1755048046828&quot; class=&quot;python&quot; data-ke-language=&quot;python&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;# .env
ANTHROPIC_API_KEY='your api key'
OPENAI_API_KEY='your api key'

'''
├─datasets
│  └─samples.csv
├─testing.ipynb
└─.env
'''&lt;/code&gt;&lt;/pre&gt;
&lt;p style=&quot;text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1755048178003&quot; class=&quot;python&quot; data-ke-language=&quot;python&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;import os

# os.environ['OPENAI_API_KEY'] = 'your api key'

os.environ['OPENAI_API_KEY']
print(os.environ['OPENAI_API_KEY'])
# your api key&lt;/code&gt;&lt;/pre&gt;
&lt;p style=&quot;text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;실제로 직접 os.environ에 지정해서 설정할 수 있으나, api key를 매번 지정하는 것보다는 load_dotenv()로 활용하는 것이 효율적이며 가독성이 좋다.&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>Python</category>
      <category>api key 환경변수</category>
      <category>dotenv</category>
      <category>env</category>
      <category>load_dotenv</category>
      <category>openai api key</category>
      <category>openai api 설정</category>
      <category>환경변수</category>
      <author>언킴</author>
      <guid isPermaLink="true">https://ok-lab.tistory.com/356</guid>
      <comments>https://ok-lab.tistory.com/356#entry356comment</comments>
      <pubDate>Wed, 13 Aug 2025 10:25:02 +0900</pubDate>
    </item>
    <item>
      <title>[python] pip 설치 시 SSLError 문제 해결하기</title>
      <link>https://ok-lab.tistory.com/355</link>
      <description>&lt;div class=&quot;book-toc&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;p style=&quot;text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #222222; text-align: justify;&quot;&gt;SSL(&lt;span style=&quot;color: #222222; text-align: justify;&quot;&gt;Secure Sockets Layer&lt;/span&gt;)은 인터넷상에서 데이터 통신 보안을 제공하는 암호 프로토콜을 의미한다. 파이썬에서 해당 오류가 발생하는 것은 회사 내 방화벽 문제나 SSL certification이 제대로 되지 않았음을 의미한다.&amp;nbsp;&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #222222; text-align: justify;&quot;&gt;위 문제를 해결하는 방법은 두 가지가 존재한다.&lt;b&gt; 첫 번째로는 pip 시 해당 url 정보는 신뢰할 수 있는 사이트라는 것을 명시하고 설치&lt;/b&gt;를 할 수 있다. &lt;/span&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1754973467919&quot; class=&quot;python&quot; data-ke-language=&quot;python&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;!pip install {packages} --trusted-host pypi.org --trusted-host files.pythonhosted.org&lt;/code&gt;&lt;/pre&gt;
&lt;p style=&quot;text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;설치하는 packages는 신뢰할 수 있는 파일이라고 명시하고 방화벽 이슈가 발생하지 않도록 처리하는 것이다. &lt;b&gt;두 번째 방법은 ssl 자체에 검증을 수행하는 과정을 False로 전환&lt;/b&gt;하는 것이다.&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1754973561450&quot; class=&quot;python&quot; data-ke-language=&quot;python&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;import ssl 
ssl._create_default_https_context = ssl._create_unverified_context&lt;/code&gt;&lt;/pre&gt;
&lt;p style=&quot;text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;pip를 만약 github나 request를 통해 가지고 오게 된다면, request 자체의 verify를 하지 않도록 처리해주어야 한다.&lt;/p&gt;
&lt;pre id=&quot;code_1754973611521&quot; class=&quot;python&quot; data-ke-language=&quot;python&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;import requests
response = requests.get(url, verify=False)&lt;/code&gt;&lt;/pre&gt;
&lt;p style=&quot;text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;대부분 첫 번째 단계에서 해결이 되며, ssl이나 requests 까지는 접근하는 경우가 없다. 첫 번째에 해결되지 않으면, httpx나 ssl, request를 통해 API를 호출할 시 verify=False로 지정해야 하는 방식을 사용하여야 한다.&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;그 외 방법으로는 환경변수를 설정하여 pip가 설치되도록 하는 방법이 있으나, 실제 회사에서 접근하거나 하는 경우에는 환경변수에 접근하는 것이 불가하기 때문에 권장하지는 않는다. (&lt;a href=&quot;https://pip.pypa.io/en/stable/topics/configuration/&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://pip.pypa.io/en/stable/topics/configuration/&lt;/a&gt;) 참고&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1754973966219&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;%APPDATA%\pip\pip.ini

The legacy &amp;ldquo;per-user&amp;rdquo; configuration file is also loaded, if it exists: %HOME%\pip\pip.ini&lt;/code&gt;&lt;/pre&gt;
&lt;p style=&quot;text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;위 경로에 pip\pip.ini 을 생성하고, 하기 코드를 작성하게 되면, [global]로 지정하여 pip 설치 시 trusted-hosted 를 별도 기입하지 않아도 된다.&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1754974560178&quot; class=&quot;html xml&quot; data-ke-language=&quot;html&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;[global]
trusted-host = pypi.org
               files.pythonhosted.org&lt;/code&gt;&lt;/pre&gt;</description>
      <category>Python</category>
      <category>pip</category>
      <category>pip SSL 에러</category>
      <category>pip 설치 오류</category>
      <category>SSL</category>
      <category>ssl error</category>
      <category>보안</category>
      <author>언킴</author>
      <guid isPermaLink="true">https://ok-lab.tistory.com/355</guid>
      <comments>https://ok-lab.tistory.com/355#entry355comment</comments>
      <pubDate>Tue, 12 Aug 2025 13:41:42 +0900</pubDate>
    </item>
    <item>
      <title>Shell file로 딥러닝 학습하기</title>
      <link>https://ok-lab.tistory.com/354</link>
      <description>&lt;p style=&quot;text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;딥러닝에는 다양한 하이퍼파라미터가 존재한다. 이때, Wandb의 Sweep을 사용하지 않고, 학습하고 싶은 경우에는 shell 파일을 통해 로그를 저장하면서 학습할 수 있다.&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;아래와 같이 learning rate, batch size, sequence length 등을 지정하고, 모델을 돌리게 된다면, 각 인자를 변환하면서 모델이 백그라운드로 실행되게 된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1708956876494&quot; class=&quot;shell&quot; data-ke-language=&quot;shell&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;for lr in 0.001 0.0001 0.00001
do
    for batch in 8 16 32 64
    do
        for len in 64 128 256 512
        do
            nohup python3 train.py --lr ${lr} --batch_size ${batch} --max_length ${len} &amp;amp;&amp;gt; logs/logs-${lr}-${batch}-${len}.txt
        done
    done
done&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>Python</category>
      <category>Shell</category>
      <category>shell로 딥러닝 학습하기</category>
      <category>딥러닝</category>
      <category>딥러닝 학습</category>
      <author>언킴</author>
      <guid isPermaLink="true">https://ok-lab.tistory.com/354</guid>
      <comments>https://ok-lab.tistory.com/354#entry354comment</comments>
      <pubDate>Mon, 26 Feb 2024 23:16:04 +0900</pubDate>
    </item>
    <item>
      <title>FY24 딜로이트컨설팅 AID 팀 채용연계형 인턴 채용 후기</title>
      <link>https://ok-lab.tistory.com/353</link>
      <description>&lt;p style=&quot;text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;이번에는 딜로이트(Deloitte) 컨설팅에 지원한 후기를 작성하고자 한다. 딜로이트는 딜로이트 안진회계법인으로 유명하고, 세계 빅4 회계법인 중 하나다.&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;딜로이트 컨설팅은 자문, 컨설팅 등을 하는 업체로 최근에는 생성형 모델 개발 등에 업무를 컨설팅 및 자문을 하는 것으로 보인다. 이번에 AI 관련 인원을 채용하기에 지원을 해보았다.&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;지원 내용을 한 번 확인해보면, AI 분석 컨설팅 및 Python 기반 생성형 AI 모델 개발 지원, 기 구축 AI/ML 모델 개발 및 검증 업무라고 되어 있다. 회사 홈페이지를 찾아보니, Whisper, DALL-E, LLM 등의 모델을 이용해서 컨설팅을 하는 것으로 보인다. 추가적으로 통계 데이터 분석을 통해 관련 업무로도 컨설팅을 진행하는 것 같다.&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;우대사항을 보면, Dash, Streamlit 등 Python 기반 데이터 시각화 대시보드 구현 경험을 우대하는 것을 보아, 아무래도 컨설팅 회사다 보니 너무 기술적인 접근 보다는 대시보드 개발을 통한 분석 결과를 제공하는 형태의 컨설팅이지 않을까 싶다.&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;전형 프로세스는 &lt;b&gt;서류 -&amp;gt; 면접 (1차, 2차) -&amp;gt; 인성 검사 -&amp;gt; 최종 합격&lt;/b&gt;으로 구성되어 있다. 딜로이트는 여의도에 있으며, OneIFC 몰에 있다. 위치적으로도 너무 좋고, 회사에서 한강이 보이는 위치에 있어서, 업무 환경, 업무 위치적으로는 매우 좋은 환경이지 않을까 싶다.&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;971&quot; data-origin-height=&quot;382&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bahgmB/btsEvgCRqBn/utr7fkjG4IY12rjdvqL3j1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bahgmB/btsEvgCRqBn/utr7fkjG4IY12rjdvqL3j1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bahgmB/btsEvgCRqBn/utr7fkjG4IY12rjdvqL3j1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbahgmB%2FbtsEvgCRqBn%2Futr7fkjG4IY12rjdvqL3j1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;992&quot; height=&quot;390&quot; data-origin-width=&quot;971&quot; data-origin-height=&quot;382&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;943&quot; data-origin-height=&quot;184&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/tlpjo/btsExO6TKuu/G4ctHrrhuGZDLzDcUeKS50/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/tlpjo/btsExO6TKuu/G4ctHrrhuGZDLzDcUeKS50/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/tlpjo/btsExO6TKuu/G4ctHrrhuGZDLzDcUeKS50/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Ftlpjo%2FbtsExO6TKuu%2FG4ctHrrhuGZDLzDcUeKS50%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;943&quot; height=&quot;184&quot; data-origin-width=&quot;943&quot; data-origin-height=&quot;184&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;서류 전형&lt;/h2&gt;
&lt;p style=&quot;text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;딜로이트는 01.24 ~ 01.30 까지 지원서를 받고 있다. 그런데, 01.29 일에 서류 &lt;b&gt;합격&lt;/b&gt; 결과가 나왔다. 채용 과정에서 마음에 드는 사람이 있는 경우 미리 채용을 마감하는 경우가 있는데, 이번에 뽑으려고 하는 인재상에 적절하다 생각해서 아마 미리 채용 프로세스를 진행한 것이지 않을까 라는 생각이 든다. 기분 좋은 출발이다.&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;480&quot; data-origin-height=&quot;129&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/HPQ5x/btsEoMiwmsF/eskKh5gCVJdjNgXNgoEPw0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/HPQ5x/btsEoMiwmsF/eskKh5gCVJdjNgXNgoEPw0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/HPQ5x/btsEoMiwmsF/eskKh5gCVJdjNgXNgoEPw0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FHPQ5x%2FbtsEoMiwmsF%2FeskKh5gCVJdjNgXNgoEPw0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;591&quot; height=&quot;159&quot; data-origin-width=&quot;480&quot; data-origin-height=&quot;129&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;면접 전형&lt;/h2&gt;
&lt;p style=&quot;text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;면접의 경우, 다대일 면접으로 진행되고, 3:1 면접이었다. 내가 만약 근무하게 된다면, 함께 일할 분들이 면접관으로 오셨고, 처음에는 자기소개를 하면서 면접이 시작되었다.&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;본인이 가장 공들였던 프로젝트에 대해서 설명하고, 그 프로젝트에서 면접관분들이 궁금한 부분을 서로 질문하는 형태로 진행되었다. 그리고 나서, 데이터 분석, OLS (최소자승제곱법), Boosting 계열 모델 등 머신러닝 관점이나 통계학 관련으로 질문을 받았고, 최근 생성형 모델을 알고 있다면 거기에 관련된 내용을 말해달라고 질문 받았다.&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;나는 최근에 Whipser, LLM, Stable Diffusion 쪽으로 관심이 있어서 거기에 관련된 내용을 말씀드리고, LLM 계열에서는 LoRA, QLoRA를 이용한 Fine-tuning, 그리고 LangChain을 활용한 CoT, RAG 등을 다루어 본 적이 있다고 말씀드렸다.&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;그런 다음, 시각화 툴은 어떤 것을 다루어 봤냐는 질문에, 파이썬에서는 Matplotlib, Seaborn, Streamlit 등을 다루고 다른 프로그램은 태블로, G-GIS 등을 사용해봤다고 말씀드렸다.&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;추가적으로, 회사에서 어떤 업무를 담당하고 싶은지, 만약에 인턴이 정규직으로 전환이 된다면 7월 이후에도 계속 근무가 가능한지, 회사 특성상 타 회사로 파견을 가는 경우도 있는데 이 부분은 어떤지 등을 질문하고, 회사에 대해서 궁금한 부분이 있다면 역으로 질문을 부탁해서 궁금한 부분에 대해서 요청한 후 면접을 마무리했다.&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;면접의 경우 30분 진행인데, 질문을 계속하다 보니 40분을 조금 넘게 면접을 진행했다. 원래는 3:1 면접이지만, 면접관 한 분께서 일정이 있어가지고 참석을 못했다. 그래서 추후에 추가적으로 면접을 한 번 더 진행할 수도 있다고 전달 받았다.&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 style=&quot;text-align: justify;&quot; data-ke-size=&quot;size26&quot;&gt;최종 합격&lt;/h2&gt;
&lt;p style=&quot;text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;전형 결과는 운이 좋게도 합격하게 되었다. 본사는 여의도에서 근무를 하고, 파견을 갈 수도 있고, 다양한 업무를 담당한다고 연락 받았다. 이번 기회가 좋은 경험이 되면 좋겠다.&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;554&quot; data-origin-height=&quot;197&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/d7naD3/btsFsawfULI/TZ334AFQumFwQ6RyflxIeK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/d7naD3/btsFsawfULI/TZ334AFQumFwQ6RyflxIeK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/d7naD3/btsFsawfULI/TZ334AFQumFwQ6RyflxIeK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fd7naD3%2FbtsFsawfULI%2FTZ334AFQumFwQ6RyflxIeK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;554&quot; height=&quot;197&quot; data-origin-width=&quot;554&quot; data-origin-height=&quot;197&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;</description>
      <category>Interview</category>
      <category>FY24 딜로이트</category>
      <category>FY24 딜로이트 채용</category>
      <category>딜로이트</category>
      <category>딜로이트AID</category>
      <author>언킴</author>
      <guid isPermaLink="true">https://ok-lab.tistory.com/353</guid>
      <comments>https://ok-lab.tistory.com/353#entry353comment</comments>
      <pubDate>Tue, 6 Feb 2024 14:34:33 +0900</pubDate>
    </item>
    <item>
      <title>YG 엔터테인먼트 데이터사이언스팀 채용 후기</title>
      <link>https://ok-lab.tistory.com/352</link>
      <description>&lt;p style=&quot;text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;최근 이곳 저곳 다양한 회사에 지원서를 넣으면서 YG 엔터테인먼트에서도 데이터사이언스 관련 직무를 채용하는 것을 발견했다. &lt;b&gt;&quot;팬들의 감정을 내포한 언어적 표현&quot;&lt;/b&gt; 으로 된 것으로 보아 감성분석 모델링이나, 예측 모델 등을 다루고 시각화 대시보드를 중점적으로 다루는 업무로 보인다.&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;853&quot; data-origin-height=&quot;125&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/kAm0E/btsEnW6jBeF/SNGouYQ4kokayTNx6M2QKk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/kAm0E/btsEnW6jBeF/SNGouYQ4kokayTNx6M2QKk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/kAm0E/btsEnW6jBeF/SNGouYQ4kokayTNx6M2QKk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FkAm0E%2FbtsEnW6jBeF%2FSNGouYQ4kokayTNx6M2QKk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;853&quot; height=&quot;125&quot; data-origin-width=&quot;853&quot; data-origin-height=&quot;125&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;445&quot; data-origin-height=&quot;143&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/KYkV0/btsEnOVfTJf/HZdWYs91ZKNfsE5SYfahmK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/KYkV0/btsEnOVfTJf/HZdWYs91ZKNfsE5SYfahmK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/KYkV0/btsEnOVfTJf/HZdWYs91ZKNfsE5SYfahmK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FKYkV0%2FbtsEnOVfTJf%2FHZdWYs91ZKNfsE5SYfahmK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;445&quot; height=&quot;143&quot; data-origin-width=&quot;445&quot; data-origin-height=&quot;143&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;경력직으로 되어있으나 2년차부터 11년차까지 채용하기 때문에 석졸으로 경력 지원을 해보았다. 채용 프로세스는 &lt;b&gt;서류 전형 -&amp;gt; 실무면접 -&amp;gt; 임원면접 -&amp;gt; 건강검진 -&amp;gt; 최종합격&lt;/b&gt; 으로 구성되어 있으며, 실무면접 단계에서 코딩테스트를 따로 보는 것 같다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;577&quot; data-origin-height=&quot;245&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/5X40d/btsEvwrFCu1/zrtprcK48yRVgZMaWnfI80/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/5X40d/btsEvwrFCu1/zrtprcK48yRVgZMaWnfI80/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/5X40d/btsEvwrFCu1/zrtprcK48yRVgZMaWnfI80/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2F5X40d%2FbtsEvwrFCu1%2FzrtprcK48yRVgZMaWnfI80%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;577&quot; height=&quot;245&quot; data-origin-width=&quot;577&quot; data-origin-height=&quot;245&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;서류 전형&lt;/h2&gt;
&lt;p style=&quot;text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;서류 전형은 기본적인 조건만 일치한다면, 합격하는 것 같다. 그래서 나도 무난하게 합격하였다. 회사마다 다르지만, 석사를 경력으로 인정해주지 않는 곳도 있다. YG 엔터테인먼트에서는 경력으로 인정을 하기 때문에 서류에서 합격된 것 같다.&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;439&quot; data-origin-height=&quot;130&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/brKoLD/btsEnVmroOV/dZdsPv0vG2n7yrRypH9VM1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/brKoLD/btsEnVmroOV/dZdsPv0vG2n7yrRypH9VM1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/brKoLD/btsEnVmroOV/dZdsPv0vG2n7yrRypH9VM1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbrKoLD%2FbtsEnVmroOV%2FdZdsPv0vG2n7yrRypH9VM1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;598&quot; height=&quot;177&quot; data-origin-width=&quot;439&quot; data-origin-height=&quot;130&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;실무 면접&lt;/h2&gt;
&lt;p style=&quot;text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;원래는 코딩 테스트가 실무 면접 이전에 있는 걸로 확인되었는데, 1차 합격자에 한하여 임원 면접 전에 과제 전형을 수행한다고 전달받았다. 내용을 전달받았을 때 코딩 테스트보다는 과제 테스트에 가까운 것 같다. &lt;b&gt;[EDA, Sentiment Analysis, Regression 등을 하는 것으로 전달 받음]&lt;/b&gt;&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;면접에서는 &quot;다른 IT회사가 아니라 엔터테인먼트에 &lt;b&gt;지원한 이유&lt;/b&gt;가 무엇인지?&quot;, &quot;본인이 진행한 프로젝트 중 가장 &lt;b&gt;열심히 진행한 프로젝트&lt;/b&gt;가 있다면?&quot;, &quot;SK 플래닛에서 인턴을 진행했는데, 거기서 &lt;b&gt;얻은 인사이트&lt;/b&gt;가 어떤 것인지?&quot;, &quot;그때 그 결과에서 Loss Function은 어떤 것을 사용했고, 차원 축소 기법은 왜 그 모델을 사용했는지?&quot; 등으로 In-Depth 방식으로 질문이 들어왔다.&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;마지막 질문에 대한 답변으로는, Anomaly Detection 기술을 사용할 때 Supervised Learning (ResNet, EfficientNet 계열)을 사용했는데, Unspervised Learning (AutoEncoder 계열)을 사용하지 않은 근거에 대해서 설명하고, Anomaly Detection 분야에서 주로 사용하는 Focal Loss 를 사용했고, Focal Loss를 사용한 이유에 대해서 간략하게 말씀드렸다.&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;데이터를 사용해서 분석을 수행할 때 단순히 모델링을 했다. 라는 관점보다는 모델링을 할 때 왜 이런 값을 사용했는지, 왜 이런 모델을 사용했는지 등에 대해서 상세히 기술하고, 왜? 라는 것에 중점을 두는 면접이었다.&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;그리고 HR 관점에서의 질문도 있었는데,&lt;b&gt; &quot;IT 업계가 아닌 엔터테인먼트에 지원하게 되면, 커리어에 조금은 이슈가 있을 것 같은데 어떻게 생각하는가?&quot;&lt;/b&gt; 등의 질문도 받았었다. 나의 경우, 데이터 분석을 수행하고, 해당 회사에서 내가 맡은 업무를 정확하게 전달할 수 있다면, 크게 문제가 되지 않는다고 답변했었다.&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;마지막 질문으로는, 경력직으로 지원했는데, &lt;b&gt;&quot;인턴 + 석사 경력이 있으면 경력 몇 년을 인정 받고 싶은지?&quot;&lt;/b&gt;에 대해서 질문했고, 석사 경력을 인정 받아서 2년을 인정받고 싶다고 했다.&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;마지막으로, 코딩 테스트에 어떤 문제가 나오면 잘 해결할 수 있을 것인가? 라는 질문을 받았고, 나는 Sentiment Analysis나 이탈 고객 모델링 등의 내용으로 나온다면 쉽게 해결할 수 있다. 라고 답변했다.&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;면접장의 분위기 자체는 매우 좋았었고, 면접에서 받은 질문에 크게 막힘없이 답변했기 때문에 좋은 결과를 기대할 수 있지 않을까 싶다. 만약에 떨어진 경우에는 다른 지원자 분들 중 경력이 높고 아무래도 엔터테인먼트 관련 업종에서 근무하신 이력이 있는 분이지 않을까 싶다.&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;결과는 &lt;b&gt;합격&lt;/b&gt;이었다. 석사 신분으로 여기까지 온 것 자체가 아무래도 운이 좋았지 않을까 싶다.&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;452&quot; data-origin-height=&quot;127&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bHkJWi/btsEnO8KGLA/ChpO9yZmLh3hCpJm0wsse1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bHkJWi/btsEnO8KGLA/ChpO9yZmLh3hCpJm0wsse1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bHkJWi/btsEnO8KGLA/ChpO9yZmLh3hCpJm0wsse1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbHkJWi%2FbtsEnO8KGLA%2FChpO9yZmLh3hCpJm0wsse1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;658&quot; height=&quot;185&quot; data-origin-width=&quot;452&quot; data-origin-height=&quot;127&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 style=&quot;text-align: justify;&quot; data-ke-size=&quot;size26&quot;&gt;임원 면접 / DS 실습 면접&lt;/h2&gt;
&lt;p style=&quot;text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;이번 절차에서는 DS 실습 면접 1시간 가량 먼저 수행하고, 임원 면접을 30분 수행한다. 2월 16일에 진행하였다. DS 실습은 간단한 EDA와 데이터 분석을 수행하는 과제였다. 난이도는 어렵지 않았으나, 시간이 부족해 제대로 마무리하지 못했었다.&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;임원 면접의 경우 DS에서 작성한 코드를 리뷰하고, 해당 내용으로 임원 분들과 함께 이야기하는 형식으로 진행하였다. 그리고, 엔터 사에 관심이 있는지, 특정 사건을 주고 해당 사건에 대해서 문제를 해결하라고 나온다면 어떻게 진행할 것인지, 만약 입사하게 된다면 어떤 프로젝트를 담당하고 싶은지 등에 대해서 질문을 받았다.&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 style=&quot;text-align: justify;&quot; data-ke-size=&quot;size26&quot;&gt;처우 협의&lt;/h2&gt;
&lt;p style=&quot;text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;임원 면접 이후, YG 사에서 연락이 왔다. 연봉 및 처우 협의를 진행하는 과정에서 조율이 되지 않아 최종적으로는 갈 수 없게 되었다.&lt;/p&gt;</description>
      <category>Interview</category>
      <category>YG 데이터분석</category>
      <category>yg 엔터테인먼트</category>
      <category>YG 엔터테인먼트 채용</category>
      <category>YG 채용</category>
      <author>언킴</author>
      <guid isPermaLink="true">https://ok-lab.tistory.com/352</guid>
      <comments>https://ok-lab.tistory.com/352#entry352comment</comments>
      <pubDate>Tue, 6 Feb 2024 10:57:40 +0900</pubDate>
    </item>
  </channel>
</rss>