<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0">
  <channel>
    <title>나의 큰 O는 log x야</title>
    <link>https://bab2min.tistory.com/</link>
    <description>제가 안 것의 대부분은 인터넷으로부터 왔으니,
다시 인터넷에게 돌려주어야 합니다.
bab2min@gmail.com</description>
    <language>ko</language>
    <pubDate>Mon, 20 Apr 2026 01:19:33 +0900</pubDate>
    <generator>TISTORY</generator>
    <ttl>100</ttl>
    <managingEditor>&amp;int;2tdt=t&amp;sup2;+c</managingEditor>
    <image>
      <title>나의 큰 O는 log x야</title>
      <url>https://t1.daumcdn.net/cfile/tistory/2503E44757131F760D</url>
      <link>https://bab2min.tistory.com</link>
    </image>
    <item>
      <title>형태소 분석기 Kiwi 신조어/미등재어 탐지 성능 강화!</title>
      <link>https://bab2min.tistory.com/682</link>
      <description>&lt;div&gt;
&lt;style&gt;
  .std-table sub {font-size:70%}
&lt;/style&gt;
&lt;/div&gt;
&lt;p style=&quot;text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;최근 Kiwi의 신버전인 v0.23.0가 출시되었습니다. 이 버전에서는 신조어나 사전 미등재어처럼 Kiwi가 처음 보는 단어도 비교적 잘 분석할 수 있도록 OOV(Out-of-Vocabulary, 사전에 없는 단어) 탐지 성능이 강화되었고, 또 오타 교정시 메모리 사용량이 최적화되고 다어절에 대해서도 오타 교정이 적용되는 등 여러 가지 품질 개선이 있었습니다. 이번 포스트에서는 새 버전에서 어떻게 신조어 및 미등재어 처리가 강화되었는지 간단하게 설명하도록 하겠습니다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;Kiwi의 고질적인 약점&lt;/h3&gt;
&lt;p style=&quot;text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;Kiwi는 모호성 해소 능력이 뛰어나고 방언 처리나 오타 교정 같은 까다로운 작업도 해낼 수 있지만 한편으로는 사전에 없는 단어를 지나치게 세밀하게 분해하려는 태생적인 단점도 안고 있었습니다. 이름바 '모르는 단어의 파편화' 현상은 신조어가 쏟아지는 환경에서 Kiwi의 활용도를 제한하는 고질적인 병목 구간이었죠. 왜 Kiwi는 유독 이런 약점을 가지고 있을까요? 이는 형태소 기반의 언어모델로 점수를 매기기 때문입니다. 이 언어모델 덕분에 뛰어난 모호성 해소 성능과 같은 장점뿐만 아니라 미등재어 분절이라는 단점도 동시에 얻은 셈입니다. 왜 형태소 기반의 언어 모델이 미등재어 처리에 약한 걸까요? 기존 버전의 Kiwi에서 신조어가 어떻게 처리되는지 실제 사례를 가지고 함께 살펴보시죠.&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;오늘의 예시문장은 바로 최근 아주 유행했던 메뉴인 '두쫀쿠'입니다.&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style3&quot;&gt;오늘 친구가 &lt;b&gt;두쫀쿠&lt;/b&gt;를 줬다.&lt;/blockquote&gt;
&lt;p style=&quot;text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;위와 같은 문장을 입력 받으면 분석기는 먼저 이를 다음과 같이 가능한 형태소의 조합으로 분해합니다.&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style3&quot;&gt;오늘&lt;br /&gt;- 오늘/NNG&lt;br /&gt;&lt;br /&gt;친구가&lt;br /&gt;- 친구/NNG 가/JKS&lt;br /&gt;- 친/XPN 구가/NNG&lt;br /&gt;&lt;br /&gt;두쫀쿠를&lt;br /&gt;- 두/MM 쪼/VV ㄴ/ETM 쿠/NNG 를/JKO&lt;br /&gt;- 두쫀쿠/? 를/JKO&lt;br /&gt;&lt;br /&gt;줬다&lt;br /&gt;- 주/VV 었/EP 다/EF&lt;br /&gt;- 주/VV 었/EP 다/EC&lt;/blockquote&gt;
&lt;p style=&quot;text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;그리고 형태소 언어 모델을 통해 가장 확률이 높은 형태소 배열을 탐색하죠. '오늘/NNG' 다음에는 '친구/NNG + 가/JKS' 가 올 수도 있고 '친/XPN + 구가/NNG' 가 올 수도 있지만 이미 대량의 한국어 문장을 학습한 언어모델은 '친/XPN + 구가/NNG' 보다는 '친구/NNG + 가/JKS'가 올 확률이 높다는 걸 알고 있기에 '친구/NNG + 가/JKS'를 선택하는 식입니다.&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;이 방식은 이미 알고 있는 형태소들에 대해서는 아주 완벽하게 잘 작동합니다. 문제는 &quot;&lt;b&gt;두쫀쿠&lt;/b&gt;&quot;처럼 처음 보는 단어가 나왔을때 시작됩니다. 시스템은 두쫀쿠를 자신이 알고 있는 형태소 조합으로 분해해보지만 '두/MM 쪼/VV ㄴ/ETM 쿠/NNG'처럼 형편 없는 조합만 나올 뿐입니다. 물론 그럴 때를 대비해서 입력 텍스트를 쪼개지 않고 전체를 신조어로 보는 Plan B도 준비되어 있죠. '두쫀쿠/?'처럼 원본을 쪼개지 않고 시스템이 잘 모르는 새로운 형태소일것으로 간주하고 처리하는 경로도 추가합니다. 이제 '친구/NNG + 가/JKS' 다음에 '두/MM 쪼/VV ㄴ/ETM 쿠/NNG'가 오는 게 확률이 높을 지 '두쫀쿠/?'가 오는 게 확률이 높을 지 언어모델이 계산해주면 되겠네요.&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;근데 언어모델 입장에서 아예 처음보는 형태소의 확률을 계산하는 건 원칙적으로 불가능합니다. 한번도 본적이 없으니 모델 입장에서는 '두쫀쿠/?'에 대해 추정하는 확률값이 0이 될수밖에 없거든요. (log를 취하면 -&amp;infin;가 나와버립니다.) 확률이 0이 나오면 이 조합이 절대 선택될수 없으므로 실제로는 편법을 사용해서 확률 계산을 우회합니다. 모델이 마주치는 OOV는 주로 명사일것이므로 처음 보는 형태소에 대해서 확률을 계산하는게 아니라 임의의 명사(NNG 또는 NNP)에 대한 확률을 계산하는 것이죠. 그러면 0이 아닌 확률이 나와서 '두쫀쿠/?'가 나오는 경로를 선택하는 것도 얼마든지 가능해집니다.&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;자 이제 모델이 '두/MM 쪼/VV ㄴ/ETM 쿠/NNG' 대신 '두쫀쿠/?'를 선택하면 성공적으로 분석이 완료되는 것인데요, 이러려면 '두쫀쿠/?'의 확률이 '두/MM 쪼/VV ㄴ/ETM 쿠/NNG'보다 높기만 하면 되겠죠? 애초에 '두/MM 쪼/VV ㄴ/ETM 쿠/NNG'는 문법적으로 전혀 말이 안되는 형태소 배열이므로 확률값이 아주 낮게 나와서 임의의 명사가 등장할 확률을 계산한 값인 '두쫀쿠/?'가 더 높게 됩니다. 따라서 자연스레 다음과 같이 '두쫀쿠/?'가 선택되고 분석은 성공적으로 끝납니다.&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style3&quot;&gt;오늘/NNG 친구/NNG 가/JKS &lt;b&gt;두쫀쿠/NNG&lt;/b&gt; 를/JKO 주/VV 었/EP 다/EF&lt;/blockquote&gt;
&lt;p style=&quot;text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;아주 이상적인 상황이네요. 근데 이 방식은 치명적인 약점이 있습니다. '두쫀쿠'는 분해하면 안되는 사례여서 문제가 없었지만 동일한 과정으로 분해해야 하는 상황에서도 분해를 안해버리는 문제가 나올 수 있기 때문이죠. 다음과 같은 문장을 생각해볼 수 있습니다.&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style3&quot;&gt;&lt;b&gt;플라밍고사려다&lt;/b&gt; 유니콘샀는데&lt;/blockquote&gt;
&lt;p style=&quot;text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;이 역시 다음과 같이 형태소 조합으로 먼저 분해가 되는데요&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style3&quot;&gt;플라밍고사려다&lt;br /&gt;- 플라밍고/NNG 사/VV 려다/EC&lt;br /&gt;- 플라밍고사려다/?&lt;br /&gt;&lt;br /&gt;유니콘샀는데&lt;br /&gt;- 유니콘/NNG 사/VV 었/EP 는데/EC&lt;br /&gt;- 유니콘샀/? 는데/EC&lt;/blockquote&gt;
&lt;p style=&quot;text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;'플라밍고/NNG 사/VV 려다/EC'의 확률이 '플라밍고사려다/?'의 확률보다 높으면 전자가 선택되어 아주 깔끔한 결과가 나오겠죠? 문제는 '플라밍고 사려다'라는 문장이 한국어에서 아주 흔하게 나오는 패턴은 아니다보니 언어모델이 이 형태소 배열의 확률에 비교적 낮은 확률을 부여하게 된다는 것이죠. 반면 '플라밍고사려다/?'는 아예 처음 보는 형태소이지만 아까처럼 처음 보는 형태소에는 임의의 명사 확률을 부여하기로 했으므로 이게 더 높은 확률을 받게 됩니다. 그래서 아래처럼 분해할 것을 분해 안하는 오류를 범하게 되죠.&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style3&quot;&gt;&lt;b&gt;플라밍고사려다/NNG&lt;/b&gt; 유니콘/NNG 사/VV&amp;nbsp;었/EP 는데/EC&lt;/blockquote&gt;
&lt;p style=&quot;text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;왜 이런 현상이 발생했을까요? 처음 보는 형태소에 대해 비교적 높은 확률을 부여했기 때문입니다. 이걸 막으려면 '플라밍고사려다/?' 같은 OOV 형태소에는 낮은 확률을 부여해야합니다. 그러나 모델은 주어진 텍스트가 실제로 처음보는 형태소인지 아니면 드물게 등장하지만 말이 되는 형태소들이 차례로 연결된 것인지 미리 알 수가 없습니다. 따라서 OOV 형태소에 낮은 확률을 부여하겠다고 확률값을 조정하면 이번에는 이전 사례였던 두쫀쿠가 '두/MM 쪼/VV ㄴ/ETM 쿠/NNG'로 잘못 분해되는 문제가 발생할 겁니다. 골치가 아프죠? 상황을 표로 정리해보면 다음과 같이 되겠네요.&lt;/p&gt;
&lt;table class=&quot;std-table&quot; data-ke-align=&quot;alignLeft&quot;&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;th&gt;&amp;nbsp;&lt;/th&gt;
&lt;th&gt;OOV 형태소에 &lt;br /&gt;높은 확률 부여&lt;/th&gt;
&lt;th&gt;OOV 형태소에 &lt;br /&gt;낮은 확률 부여&lt;/th&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;th&gt;신조어 / 미등재어&lt;/th&gt;
&lt;td&gt;분해하지 않음(O)&lt;br /&gt;e.g. 두쫀쿠/NNG&lt;/td&gt;
&lt;td&gt;분해함(X)&lt;br /&gt;e.g. 두/MM&amp;nbsp;쪼/VV&amp;nbsp;ㄴ/ETM&amp;nbsp;쿠/NNG&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;th&gt;알고 있는 형태소가 &lt;br /&gt;그저 드문 패턴으로 조합된 것&lt;/th&gt;
&lt;td&gt;분해하지 않음(X)&lt;br /&gt;e.g. 플라밍고사려다/NNG&lt;/td&gt;
&lt;td&gt;분해함(O)&lt;br /&gt;e.g. 플라밍고/NNG&amp;nbsp;사/VV&amp;nbsp;려다/EC&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p style=&quot;text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;즉 OOV 형태소의 확률이 0이 되는것을 막고자 적당히 임의의 명사로 대체해서 확률을 계산했는데 그렇다보니 이 확률이 높은지 낮은지에 따라 신조어 / 미등재어를 바르게 분석하거나 또는 알고 있는 형태소이지만 그저 드문 패턴으로 조합된 경우를 바르게 분석할 수 있는 거죠. 둘 중 하나를 선택할 순 있어도 어떻게 해도 양쪽을 동시에 만족시킬 수 없습니다. 기존 Kiwi 버전에서는 알고 있는 경우를 제대로 분석하는게 조금 더 낫겠다는 판단하에 후자를 선택한 것이구요. 이 때문에 기존의 Kiwi에서는 미등재어를 잘게 쪼개는 경향이 두드러졌던 것입니다.&lt;/p&gt;
&lt;h3 style=&quot;text-align: justify;&quot; data-ke-size=&quot;size23&quot;&gt;사람은 어떻게 OOV를 잘 처리할까?&lt;/h3&gt;
&lt;p style=&quot;color: #333333; text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;형태소 분석기가 OOV를 깔끔하게 잘 처리하는게 어렵다는 건 이제 충분히 알았습니다. 근데 사람은 어떻게 OOV를 잘 처리할까요? 한 번도 본적 없는 단어가 문장 속에 섞여 있어도 사람들은 당황하지 않고 내가 모르는 새로운 단어일거라는걸 자연스럽게 어떻게 알아낼까요? 다음과 같은 예시 문장을 보면서 고민해봅시다.&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style3&quot;&gt;야 &lt;b&gt;두쫀쿠&lt;/b&gt;&amp;nbsp;먹어봤냐? 오늘 친구가 &lt;b&gt;두쫀쿠&lt;/b&gt;를 줬는데 먹어보니 생각보다 맛있더라. 근데 그게 얼만줄 알아? 이 쪼끄만 &lt;b&gt;두쫀쿠&lt;/b&gt;가 1만원이라는거야.&lt;/blockquote&gt;
&lt;p style=&quot;text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;'두쫀쿠'라는 말을 아예 처음 봐서 이게 뭔지 명사인지 아닌지도 모른다고 가정하에, 사람이 두쫀쿠라는 신조어를 파악해내는 과정은 다음과 같을 겁니다.&lt;/p&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;&lt;b&gt;문맥 정보&lt;/b&gt;: 일단 '두쫀쿠'가 정확히 뭔지는 모르겠지만 '먹어봤냐'의 목적어 위치에 등장하고 조사 '-를'과 '-가'의 앞에 등장하는 걸 봅니다. 즉 문맥 상 명사가 나타날 위치에 나왔으니 '두쫀쿠'는 아마 내가 모르는 명사일거라고 생각합니다.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;형태 정보&lt;/b&gt;: 그리고 또 '두쫀쿠'라는 형태가 충분히 명사처럼 생겼으므로 (뭔가의 줄임말처럼 보임) 확실히 명사가 맞다고 확신합니다.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;빈도 정보&lt;/b&gt;: 또 이 형태가 동일하게 여러 번 반복해서 등장한다는 것을 통해 이게 오타 등의 실수가 아니라 실제로 새로 등장한 무언가를 가리키는 명사일 것이라고 결론을 내립니다.&lt;/li&gt;
&lt;/ol&gt;
&lt;p style=&quot;text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #333333; text-align: justify;&quot;&gt;사람은 이렇게 문맥, 형태, 빈도 정보를 종합해서 유연하게 판단합니다. 그렇다면 형태소 분석기에게도 이 세 가지 '인간의 센스'를 가르쳐줄 수 있다면 어떨까요? &lt;/span&gt;&lt;span style=&quot;color: #333333; text-align: justify;&quot;&gt;OOV 처리 성능을 개선할 수 있지 않을까요?&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;3320&quot; data-origin-height=&quot;2010&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/b7iHil/dJMcaaY1eBH/D8bdvERULJStZ3ioHYMdq1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/b7iHil/dJMcaaY1eBH/D8bdvERULJStZ3ioHYMdq1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/b7iHil/dJMcaaY1eBH/D8bdvERULJStZ3ioHYMdq1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fb7iHil%2FdJMcaaY1eBH%2FD8bdvERULJStZ3ioHYMdq1%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;480&quot; height=&quot;291&quot; data-origin-width=&quot;3320&quot; data-origin-height=&quot;2010&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;h3 style=&quot;text-align: justify;&quot; data-ke-size=&quot;size23&quot;&gt;&lt;span style=&quot;color: #333333; text-align: justify;&quot;&gt;Kiwi에게 형태 &amp;amp; 빈도 정보를 주입하자&lt;/span&gt;&lt;/h3&gt;
&lt;p style=&quot;text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #333333; text-align: justify;&quot;&gt;사실 이미 Kiwi는 문맥, 형태, 빈도 정보 중 하나는 잘 알고 있습니다. 바로 문맥 정보입니다. 앞에서 설명했듯 이미 형태소 기반의 언어모델을 사용하기 때문에 국소적인 문맥 정보, 즉 특정 위치가 명사가 등장할 만한 위치인지는 비교적 정확하게 판단할 수 있습니다. 하지만 형태 정보, 즉 '&lt;b&gt;이 형태가 얼마나 한국어에서 명사다운 형태인지&lt;/b&gt;'를 판단하는 능력과 빈도 정보를 분석 단계에서 통합하는 능력은 전무한 상태지요. 이 정보들을 주입하는 게 이번 Kiwi 업데이트의 핵심 목표였습니다. 어떻게 형태 정보와 빈도 정보를 주입했을까요?&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #333333; text-align: justify;&quot;&gt;바로 Kiwi의 약점을 설명하면서 언급했던 OOV 형태소의 등장 확률입니다. 확률 계산 방법을 변경하여 OOV가 명사다운 형태일수록 높은 확률을 받도록, 또 자주 등장할 수록 높은 확률을 받도록 한 것이죠. 이를 통해 형태 정보와 빈도 정보가 분석 과정에 자연스럽게 녹아들게 했습니다. 그럼 구체적으로 어떻게 OOV 형태소의 확률 계산을 수행했는지 이제 설명하도록 하겠습니다.&lt;/span&gt;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;미등재어는 어떻게 언어 모델로 처리할 수 있을까&lt;/h3&gt;
&lt;p style=&quot;text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;앞서 살펴보았듯 Kiwi가 OOV처리를 어려워하는 근본적인 원인은 형태소 기반의 언어 모델이 미등재어의 확률을 계산할 수 없기 때문입니다. 사실 이 문제는 Vocabulary기반으로 동작하는 모든 언어 모델이 예전부터 가지고 있던 고질적인 한계입니다. 이 때문에 OOV 처리를 위한 다양한 기법들이 연구되어 왔죠. 그런데 최근 LLM 시대부터는 이런 걱정이 싹 사라졌습니다. Subword Tokenization이 도입되었기 때문이죠. 즉 텍스트를 항상 단어(or 형태소) 단위로 쪼개지 않고 더 작은 단위(e.g. 글자 or 바이트)로 쪼개기 때문입니다. 즉, 사전(Dictionary)에 '두쫀쿠'라는 단어는 들어가 있지 않더라도 '두', '쫀', '쿠'라는 글자는 전부 들어가 있으므로 '두쫀쿠'의 확률을 구하는 대신 '두', '쫀', '쿠'가 연속해서 등장할 확률을 계산할 수 있거든요.&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;물론 Subword Tokenization은 모델이 충분히 커서 일반화 능력이 갖춰줘야 작동이 가능합니다. 수십 GB의 파라미터를 가진 거대 언어모델은 수조 개의 토큰을 학습하며 '두', '쫀', '쿠'라는 낯선 조합 뒤에 조사 '가'나 '를'이 붙는 맥락을 통해 이것이 명사임을 스스로 체득하기 때문에 이게 글자 단위로 쪼개져서 입력되어도 전체 의미를 대략적으로 파악할 수 있습니다.&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;하지만 형태소 분석기는 웹 서비스의 가장 앞단에서 밀리초(ms) 단위의 성능을 다투는 경량 라이브러리입니다. Kiwi가 형태소 기반의 언어 모델을 고집한 이유는 연산량과 메모리가 제한된 상황에서는 데이터를 기본적인 의미 단위인 형태소에 맞게 쪼개는 게 모델이 처리하기에 효율적이기 때문입니다. 따라서 미등재어를 처리하겠다고 섣부르게 형태소 분석기에 Subword Tokenization을 도입하게 되면 오히려 효율성과 품질 두 마리 토끼를 모두 놓칠 수도 있습니다. 그러면 어떻게 해야 LLM만큼 무겁지 않으면서도 미등재어의 확률을 영리하게 추론할 수 있을까요?&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;문자 기반 단어 모델로 명사 점수 계산하기&lt;/h3&gt;
&lt;p style=&quot;text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;복잡한 시퀀스의 등장 확률을 추정하는 것은 어려운 일입니다. 예를 들어 전체 사건들 중 ABCDE가 연속해서 발생하는 사건의 확률 $P(A B C D E)$ 을 계산하는 상황을 상상해봅시다. 이 5개가 통으로 발생하는 경우는 사실 잘 없으므로 직접 확률을 구하기 보다는 이 시퀀스를 분해해서 계산하는 방식으로 접근하죠. 이 때 조건부 확률 공식을 사용합니다. 즉 $P(A B) = P(A) \cdot P(B|A)$라는 사실을 이용하는 겁니다. 이를 재귀적으로 적용하면 $P(A B C D E) = P(A) \cdot P(B | A) \cdot P(C | AB) \cdot P(D | ABC) \cdot P(E | ABCD)$처럼 ABCDE의 등장 확률을 5개의 작은 사건들 확률의 곱으로 대신 계산할 수 있게 됩니다.&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;그런데 이 분해는 꼭 순서대로 할 필요도 없고 하나씩 할 필요도 없습니다. $A$와 $B$가 연속해서 발생하는 사건인 $AB$를 하나의 큰 사건으로 보고, 또 $D$와 $E$가 연속해서 발생하는 사건인 $DE$를 또 다른 하나의 큰 사건으로 본다면, $P(ABCDE) = P(AB) \cdot P(C | AB) \cdot P(DE | ABC)$와 같이 분해할 수도 있습니다. 혹은 뒤에서부터 분해해서 $P(E)$부터 거꾸로 계산해오는 것도 가능하구요. 즉 분해를 어떤 단위로 하든지 어떤 순서로 하든지 상관 없이 우리는 동일한 확률을 계산할 수 있다는 것입니다.&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;이걸 문장에도 적용해볼 수 있습니다. &quot;오늘 친구가 두쫀쿠를 줬다&quot;는 문장의 확률을 계산하기 위해서 문장을 형태소 단위로 쪼개어 확률을 구해볼 수도 있습니다.&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;$$P_{morpheme-level} = P(오늘) \cdot P(친구 | 오늘) \cdot P(가 | 오늘\:친구) \\ \cdot P(두쫀쿠 | 오늘\:친구가) \cdot ... $$&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;혹은 이걸 글자 단위로 쪼개서 확률을 구할 수도 있겠죠.&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;$$P_{character-level} = P(오) \cdot P(늘 | 오) \cdot P(친 | 오늘) \cdot P(구 | 오늘\:친) \cdot P(가 | 오늘\:친구) \\ \cdot P(두 | 오늘\:친구가) \cdot P(쫀 | 오늘\:친구가\:두) \cdot P(쿠 | 오늘\:친구가\:두쫀) \cdot ... $$&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;우리가 확률을 어떻게 계산하든 두 확률은 결국 동일한 값이 됩니다. $P_{morpheme-level} = P_{character-level}$&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;더 나아가 둘을 섞어서 형태소 단위에서 확률을 계산하다가 글자 단위로 계산하는 것도 가능하며 이 경우에도 마찬가지로 결과값은 동일합니다.&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;$$P_{hybrid} = P(오늘) \cdot P(친구 | 오늘) \cdot P(가 | 오늘\:친구) \\ \cdot P(두 | 오늘\:친구가) \cdot P(쫀 | 오늘\:친구가\:두) \cdot P(쿠 | 오늘\:친구가\:두쫀) ...$$&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;이 하이브리드 방법이 바로 경량 모델에서 OOV를 처리하기 위한 가장 핵심적인 아이디어입니다. 즉 대부분의 텍스트에서는 형태소 기반으로 확률 계산을 수행하다가 특정한 부분에서만 글자 단위로 확률 계산을 수행하고 이를 합친다면 아주 작은 크기의 문자 모델만 추가하고 전체 계산량을 거의 그대로 유지하면서 OOV의 확률 계산을 고도화할 수 있습니다.&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;한국어에서 발생하는 대부분의 OOV는 명사이므로 OOV가 일단 명사일거라고 한정하면 OOV를 위한 문자 모델은 명사들의 글자 분포를 학습하는 문자 수준의 명사 모델이 됩니다. 따라서 LLM이 수많은 문장을 Subword 단위로 쪼개어 입력 받고 그 패턴을 학습하는 것처럼, 명사 모델은 다양한 종류의 명사를 글자 단위로 쪼개어 입력 받아 명사의 패턴을 학습하도록 하면 됩니다.&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;아주 가벼운 모델을 사용하고 싶으므로 이전에 Kiwi의 형태소 모델을 경량 신경망 기반으로 교체하면서 개발한 &lt;a href=&quot;https://bab2min.tistory.com/677&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;CoNg모델&lt;/a&gt;을 그대로 재사용하기로 했습니다. 다만 이번에는 vocab이 훨씬 줄어들었고(약 500여개. 한글 초+중성 음절, 종성, 알파벳, 숫자 등만 포함시켰습니다), embedding dimension이 64로 아주아주 작게 잡았습니다. 학습 데이터는 모두의 말뭉치에서 제공하는 형태분석 말뭉치 및 개체명 분석 말뭉치에서 추출된 명사들을 이용해서 구축했구요. 이를 통해 약 8MB 수준의 작은 문제 수준의 명사 모델이 구축되었습니다.&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;이 모델이 실제로 다음 문자열들의 확률을 어떻게 예측하는지 살짝 보여드리겠습니다.&lt;/p&gt;
&lt;table class=&quot;std-table&quot; data-ke-align=&quot;alignLeft&quot;&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;th colspan=&quot;3&quot;&gt;두쫀쿠&lt;/th&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;th&gt;&amp;nbsp;&lt;/th&gt;
&lt;th&gt;Log Prob.&lt;/th&gt;
&lt;th&gt;누적 Log Prob.&lt;/th&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;$P(두)$&lt;/td&gt;
&lt;td&gt;-5.773&lt;/td&gt;
&lt;td&gt;-5.773&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;$P(쪼|두)$&lt;/td&gt;
&lt;td&gt;-7.826&lt;/td&gt;
&lt;td&gt;-13.599&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;$P(ㄴ|두쪼)$&lt;/td&gt;
&lt;td&gt;-3.718&lt;/td&gt;
&lt;td&gt;-17.318&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;$P(쿠|두쫀)$&lt;/td&gt;
&lt;td&gt;-7.354&lt;/td&gt;
&lt;td&gt;-24.672&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;$P(\$|두쫀쿠)$&lt;/td&gt;
&lt;td&gt;-2.666&lt;/td&gt;
&lt;td&gt;-27.339&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;th colspan=&quot;3&quot;&gt;두쫀쿠를&lt;/th&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;th&gt;&amp;nbsp;&lt;/th&gt;
&lt;th&gt;Log Prob.&lt;/th&gt;
&lt;th&gt;누적 Log Prob.&lt;/th&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;$P(두)$&lt;/td&gt;
&lt;td&gt;-5.773&lt;/td&gt;
&lt;td&gt;-5.773&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;$P(쪼|두)$&lt;/td&gt;
&lt;td&gt;-7.826&lt;/td&gt;
&lt;td&gt;-13.599&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;$P(ㄴ|두쪼)$&lt;/td&gt;
&lt;td&gt;-3.718&lt;/td&gt;
&lt;td&gt;-17.318&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;$P(쿠|두쫀)$&lt;/td&gt;
&lt;td&gt;-7.354&lt;/td&gt;
&lt;td&gt;-24.672&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;$P(르|두쫀쿠)$&lt;/td&gt;
&lt;td&gt;-3.825&lt;/td&gt;
&lt;td&gt;-28.498&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;$P(ㄹ|두쫀쿠르)$&lt;/td&gt;
&lt;td&gt;-4.667&lt;/td&gt;
&lt;td&gt;-33.165&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;$P(\$|두쫀쿠를)$&lt;/td&gt;
&lt;td&gt;-4.594&lt;/td&gt;
&lt;td&gt;-37.759&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;th colspan=&quot;3&quot;&gt;한국어&lt;/th&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;th&gt;&amp;nbsp;&lt;/th&gt;
&lt;th&gt;Log Prob.&lt;/th&gt;
&lt;th&gt;누적 Log Prob.&lt;/th&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;$P(하)$&lt;/td&gt;
&lt;td&gt;-4.197&lt;/td&gt;
&lt;td&gt;-4.197&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;$P(ㄴ|하)$&lt;/td&gt;
&lt;td&gt;-2.446&lt;/td&gt;
&lt;td&gt;-6.643&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;$P(구|한)$&lt;/td&gt;
&lt;td&gt;-1.924&lt;/td&gt;
&lt;td&gt;-8.568&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;$P(ㄱ|한구)$&lt;/td&gt;
&lt;td&gt;-0.133&lt;/td&gt;
&lt;td&gt;-8.702&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;$P(어|한국)$&lt;/td&gt;
&lt;td&gt;-3.928&lt;/td&gt;
&lt;td&gt;-12.630&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;$P(\$|한국어)$&lt;/td&gt;
&lt;td&gt;-1.803&lt;/td&gt;
&lt;td&gt;-14.434&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;th colspan=&quot;3&quot;&gt;됐습니다.&lt;/th&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;th&gt;&amp;nbsp;&lt;/th&gt;
&lt;th&gt;Log Prob.&lt;/th&gt;
&lt;th&gt;누적 Log Prob.&lt;/th&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;$P(돼)$&lt;/td&gt;
&lt;td&gt;-7.104&lt;/td&gt;
&lt;td&gt;-7.104&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;$P(ㅆ|돼)$&lt;/td&gt;
&lt;td&gt;-4.947&lt;/td&gt;
&lt;td&gt;-12.052&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;$P(스|됐)$&lt;/td&gt;
&lt;td&gt;-5.494&lt;/td&gt;
&lt;td&gt;-17.547&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;$P(ㅂ|됐스)$&lt;/td&gt;
&lt;td&gt;-6.270&lt;/td&gt;
&lt;td&gt;-23.817&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;$P(니|됐습)$&lt;/td&gt;
&lt;td&gt;-6.294&lt;/td&gt;
&lt;td&gt;-30.111&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;$P(다|됐습니)$&lt;/td&gt;
&lt;td&gt;-4.888&lt;/td&gt;
&lt;td&gt;-34.999&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;$P(.|됐습니다)$&lt;/td&gt;
&lt;td&gt;-8.096&lt;/td&gt;
&lt;td&gt;-43.096&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;$P(\$|됐습니다)$&lt;/td&gt;
&lt;td&gt;-8.839&lt;/td&gt;
&lt;td&gt;-51.936&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;(여기에서 &lt;b&gt;$&lt;/b&gt;기호는 명사의 끝을 지칭하는 특수 토큰입니다.) 우리 모델이 '한국어'라는 형태에 대해서는 명사 확률 점수를 -14.434, '됐습니다.'에 대해서는 -51.936, '두쫀쿠'에 대해서는 -27.339, '두쫀쿠를'에 대해서는 -37.759로 계산해주고 있네요. 확실히 명사다운 명사(한국어)에는 높은 확률값(-14)을, 명사가 아닌 것(됐습니다)에 대해서는 아주 낮은 확률값(-51)을 부여하고 있습니다. 두쫀쿠는 '한국어'에 비해서는 다소 낮은 점수를 받긴했으나 그래도 명사가 아닌것에 비해서는 확실히 높은 점수를 받았네요. 그리고 '두쫀쿠' 뒤에 '를'이 붙어면 확률 점수가 10점 이상 깎이기 때문에 '두쫀쿠를'이라는 형태를 보면 이 전체를 하나의 명사로 보기보다는 '두쫀쿠' + '를/JKO'로 분해할 가능성이 충분히 높아지구요.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이렇게 형태 정보의 역할을 하는 명사 점수를 계산하는 것은 완성되었습니다. 이제 빈도 정보를 통합해볼까요?&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;반복되는 문자열에 더 높은 점수를 부여하기&lt;/h3&gt;
&lt;p style=&quot;text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;지금까지 살펴보았듯 문맥 정보와 형태 정보는 전부 특정 패턴이 등장할 확률을 통해서 정의되었습니다. 따라서 빈도 정보 역시 확률이라는 틀에 녹여내야 자연스럽게 앞의 두 정보와 통합이 가능하겠죠? 빈도 정보를 확률의 관점에서 다시 풀어써보면 '특정 문맥에서 자주 등장했던 단어는 그 문맥에서 다시 등장할 확률이 높다' 정도가 되겠습니다. 여기서 '단어'는 그저 계산을 위해 글자의 나열로 환원할 수 있겠구요.&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style3&quot;&gt;야 &lt;b&gt;두쫀쿠&lt;/b&gt; 먹어봤냐? &lt;b&gt;두리안&lt;/b&gt; 말고 &lt;b&gt;두쫀쿠&lt;/b&gt;! 오늘 친구가 &lt;b&gt;두쫀쿠&lt;/b&gt;를 줬는데 먹어보니 생각보다 맛있더라. 근데 그게 얼만줄 알아? 이 쪼끄만 &lt;b&gt;두쫀쿠&lt;/b&gt;가 1만원이라는거야.&lt;/blockquote&gt;
&lt;p style=&quot;text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;위의 빈도 정보 정의를 바탕으로 이 문맥에서 두쫀쿠라는 문자열이 다시 등장할 확률을 계산해보겠습니다. 일단 형태 정보를 계산할때와 동일하게 $P(두쫀쿠)$는 조건부 확률로 분해한 뒤 이를 곱해서 계산할 수가 있습니다.&lt;/p&gt;
&lt;table class=&quot;std-table&quot; data-ke-align=&quot;alignLeft&quot;&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;th&gt;&amp;nbsp;&lt;/th&gt;
&lt;th&gt;이전 문맥의 빈도&lt;/th&gt;
&lt;th&gt;다음 글자의 빈도&lt;/th&gt;
&lt;th&gt;Log Prob.&lt;/th&gt;
&lt;th&gt;누적 Log Prob.&lt;/th&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;$P(두)$&lt;/td&gt;
&lt;td&gt;70&lt;/td&gt;
&lt;td&gt;5&lt;/td&gt;
&lt;td&gt;-2.639&lt;/td&gt;
&lt;td&gt;-2.639&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;$P(쫀|두)$&lt;/td&gt;
&lt;td&gt;5&lt;/td&gt;
&lt;td&gt;4&lt;/td&gt;
&lt;td&gt;-0.223&lt;/td&gt;
&lt;td&gt;-2.862&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;$P(쿠|두쫀)$&lt;/td&gt;
&lt;td&gt;4&lt;/td&gt;
&lt;td&gt;4&lt;/td&gt;
&lt;td&gt;-0.000&lt;/td&gt;
&lt;td&gt;-2.862&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p style=&quot;text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;빈도 기반으로 확률을 추정하는건 매우 간단합니다. 조건이 발생할 빈도를 세고 해당 조건 하에서 원하는 사건이 일어날 빈도를 세서 비율만 계산하면 됩니다. 즉, '두'라는 문자가 출현할 확률 $P(두)$은 문자가 출현하는 모든 경우의 빈도(=전체 글자수) 분의 '두'라는 문자가 출현한 빈도를 계산하면 되고, '두' 다음에 '쫀'이 출현할 확률 $P(쫀|뚜)$는 '두'가 출현하는 빈도 분의 '두쫀'이 출현하는 빈도를 계산하면 되는 식이지요. 여기까지는 쉽습니다. 근데 이 빈도 정보의 확률을 형태 정보의 확률과 단순히 산술적으로 더해서 사용하게 되면 문제가 발생할 수 있습니다. 한국어에서는 특정 문맥에서 자주 등장하는 명사 말고도 그저 문법적인 이유로 반복적으로 쓰이는 조사, 어미, 동사 등이 많습니다. 빈도 정보를 그냥 가져다 사용하게 되면 그저 자주 반복된다는 이유로 OOV 명사일 확률을 높게 받을 수 있기 때문이죠. 우리는 한국어 특성상 자주 반복적으로 등장하는 문자열과 그렇지 않은 문자열을 구분해서 보는게 필요합니다.&lt;/p&gt;
&lt;table class=&quot;std-table&quot; data-ke-align=&quot;alignLeft&quot;&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;th&gt;&amp;nbsp;&lt;/th&gt;
&lt;th&gt;국소적으로 자주 출현&lt;/th&gt;
&lt;th&gt;국소적으로 드물게 출현&lt;/th&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;th&gt;전역적으로 자주 출현&lt;/th&gt;
&lt;td&gt;일반적인 명사 / 동사 or 조사, 어미일 가능성이 높다&lt;/td&gt;
&lt;td&gt;-&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;th&gt;전역적으로 드물게 출현&lt;/th&gt;
&lt;td&gt;OOV일 가능성이 높다&lt;/td&gt;
&lt;td&gt;-&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p style=&quot;text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;지금 분석 대상이 되는 문맥 내에서의 문자열 빈도를 국소적 빈도(Local Frequency), 일반적인 한국어 말뭉치 전체 내에서의 문자열 빈도를 전역적 빈도(Global Frequency)라고 구분해 보겠습니다. (빈도 정보는 국소적 빈도와, 형태 정보는 전역적 빈도와 연관되어 있다고 볼수도 있겠네요) 우리의 관심 대상인 미등재어는 전역적 빈도는 낮지만 국소적 빈도는 높은 상황에 속합니다. 이 경우에만 빈도 정보가 강하게 반영되고, 나머지 경우에는 상대적으로 덜 반영되게 해야 일반 명사, 동사 등에 대한 분석 오류를 막을 수 있겠죠?&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;그래서 여기에서 베이지안 이론이 등장합니다. 전역적 빈도에서 추정되는 확률을 사전 확률(Prior)로 두고, 현재 국소적인 문맥에서 관찰되는 빈도(Observation)를 반영했을때의 사후 확률(Posterior)를 실제 특정 문자열 패턴이 OOV 명사일 점수로 사용하자는 아이디어입니다. 그리고 여기에서 사전 확률은 앞에서 만든 형태 정보를 추정하기 위한 문자 수준의 명사 모델에서 가져오는 것이죠. 단 이 모델은 그저 이전 문맥에서 다음 문자가 등장할 확률만을 계산해줄 수 있으므로 전역 빈도 정보까지는 제공하지 못합니다. 그렇다고 말뭉치 내의 모든 문자열 패턴에 대해서 미리 빈도수를 구해서 저장해놓을수도 없으므로 CoNg모델의 문맥 데이터에 빈도수 근사치를 함께 저장해두는 방식을 사용했습니다. 즉 $P(쪼|두)$가 뭔지 알수 있고, 또 '두'의 빈도수 근사치를 알고 있으므로 이를 바탕으로 '두쪼'의 빈도수도 추정해보는 것이죠.&lt;/p&gt;
&lt;table class=&quot;std-table&quot; data-ke-align=&quot;alignLeft&quot;&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;th colspan=&quot;6&quot;&gt;두쫀쿠&lt;/th&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;th&gt;&amp;nbsp;&lt;/th&gt;
&lt;th&gt;사전 Log Prob.&lt;/th&gt;
&lt;th&gt;전역 빈도(추정)&lt;/th&gt;
&lt;th&gt;국소 빈도&lt;/th&gt;
&lt;th&gt;사후 분포&lt;/th&gt;
&lt;th&gt;사후 Log Prob.&lt;/th&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;th&gt;$P(두)$&lt;/th&gt;
&lt;td&gt;-5.773&lt;/td&gt;
&lt;td&gt;- / -&lt;/td&gt;
&lt;td&gt;5 / -&lt;/td&gt;
&lt;td&gt;- / -&lt;/td&gt;
&lt;td&gt;-5.773&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;th&gt;$P(쪼|두)$&lt;/th&gt;
&lt;td&gt;-7.826&lt;/td&gt;
&lt;td&gt;0.647 / 1623&lt;/td&gt;
&lt;td&gt;4 / 5&lt;/td&gt;
&lt;td&gt;3.647 / 1627&lt;/td&gt;
&lt;td&gt;-6.100&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;th&gt;$P(ㄴ|두쪼)$&lt;/th&gt;
&lt;td&gt;-3.718&lt;/td&gt;
&lt;td&gt;0.194 / 8&lt;/td&gt;
&lt;td&gt;4 / 4&lt;/td&gt;
&lt;td&gt;3.194 / 11&lt;/td&gt;
&lt;td&gt;-1.236&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;th&gt;$P(쿠|두쫀)$&lt;/th&gt;
&lt;td&gt;-7.354&lt;/td&gt;
&lt;td&gt;$\epsilon$ / $\epsilon$&lt;/td&gt;
&lt;td&gt;4 / 4&lt;/td&gt;
&lt;td&gt;3 / 3&lt;/td&gt;
&lt;td&gt;-0.000&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;th&gt;$P(\$|두쫀쿠)$&lt;/th&gt;
&lt;td&gt;-2.666&lt;/td&gt;
&lt;td&gt;$\epsilon$ / $\epsilon$&lt;/td&gt;
&lt;td&gt;-&lt;/td&gt;
&lt;td&gt;-&lt;/td&gt;
&lt;td&gt;-2.666&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;th colspan=&quot;6&quot;&gt;됐습니다&lt;/th&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;th&gt;&amp;nbsp;&lt;/th&gt;
&lt;th&gt;사전 Log Prob.&lt;/th&gt;
&lt;th&gt;전역 빈도(추정)&lt;/th&gt;
&lt;th&gt;국소 빈도&lt;/th&gt;
&lt;th&gt;사후 분포&lt;/th&gt;
&lt;th&gt;사후 Log Prob.&lt;/th&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;th&gt;$P(돼)$&lt;/th&gt;
&lt;td&gt;-7.104&lt;/td&gt;
&lt;td&gt;- / -&lt;/td&gt;
&lt;td&gt;5 / -&lt;/td&gt;
&lt;td&gt;- / -&lt;/td&gt;
&lt;td&gt;-7.104&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;th&gt;$P(ㅆ|돼)$&lt;/th&gt;
&lt;td&gt;-4.947&lt;/td&gt;
&lt;td&gt;6.117 / 861&lt;/td&gt;
&lt;td&gt;5 / 5&lt;/td&gt;
&lt;td&gt;10.117 / 865&lt;/td&gt;
&lt;td&gt;-4.448&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;th&gt;$P(스|됐)$&lt;/th&gt;
&lt;td&gt;-5.494&lt;/td&gt;
&lt;td&gt;1.767 / 430&lt;/td&gt;
&lt;td&gt;5 / 5&lt;/td&gt;
&lt;td&gt;5.767 / 434&lt;/td&gt;
&lt;td&gt;-4.320&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;th&gt;$P(ㅂ|됐스)$&lt;/th&gt;
&lt;td&gt;-6.270&lt;/td&gt;
&lt;td&gt;1.644 / 400&lt;/td&gt;
&lt;td&gt;5 / 5&lt;/td&gt;
&lt;td&gt;5.644 / 404&lt;/td&gt;
&lt;td&gt;-4.270&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;th&gt;$P(니|됐습)$&lt;/th&gt;
&lt;td&gt;-6.292&lt;/td&gt;
&lt;td&gt;0.731 / 395&lt;/td&gt;
&lt;td&gt;5 / 5&lt;/td&gt;
&lt;td&gt;4.731 / 399&lt;/td&gt;
&lt;td&gt;-4.434&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;th&gt;$P(다|됐습니)$&lt;/th&gt;
&lt;td&gt;-4.888&lt;/td&gt;
&lt;td&gt;2.954 / 392&lt;/td&gt;
&lt;td&gt;5 / 5&lt;/td&gt;
&lt;td&gt;6.954 / 396&lt;/td&gt;
&lt;td&gt;-4.042&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;th&gt;$P(.|됐습니다)$&lt;/th&gt;
&lt;td&gt;-8.096&lt;/td&gt;
&lt;td&gt;$\epsilon$ / $\epsilon$&lt;/td&gt;
&lt;td&gt;-&lt;/td&gt;
&lt;td&gt;-&lt;/td&gt;
&lt;td&gt;-8.096&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;th&gt;$P(\$|됐습니다.)$&lt;/th&gt;
&lt;td&gt;-8.839&lt;/td&gt;
&lt;td&gt;$\epsilon$ / $\epsilon$&lt;/td&gt;
&lt;td&gt;-&lt;/td&gt;
&lt;td&gt;-&lt;/td&gt;
&lt;td&gt;-8.839&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p style=&quot;text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;실제 계산 결과를 보면서 설명해보겠습니다. $P(쪼|두)$, 즉 명사 환경에서 '두' &lt;span style=&quot;letter-spacing: 0px;&quot;&gt;다음에 '쪼'가 등장할 로그 확률은 문자 기반의 명사 모델에서 약 -7.826로 추정해줍니다. 퍼센트로 나타내면 0.39% 정도인데요, 말뭉치 내에서 '두'가 등장한 빈도가 대략 1623이라는 것을 알고 있으면 '두쪼'은 약 0.647 ($1623 \times 0.39\%$)라고 추정해볼 수 있습니다(여기까지가 사전확률입니다). 그런데 주어진 문맥에서 실제로 '두'는 총 5번 등장했고 '두쪼'는 4번 등장했다고 합시다(관측). 물론 이 중 1번은 당연히 현재 분석하는 글자 자기자신이므로 이 값을 제외한 나머지 빈도 (3 / 4)를 전연 빈도에 더해서 더 정교한 사후 분포를 구할 수 있습니다. 즉, '두'는 전역과 국소 빈도 전체를 합쳐서 총 1627번, '두쪼'는 약 3.647번 등장한 셈이죠. 이걸로 새롭게 로그 확률을 계산해보면 약 -6.100이 나옵니다. 현재 문맥의 빈도 정보를 고려한 결과 '두' 다음에 '쪼'가 등장할 확률이 전역 문맥 대비 조금 더 증가(-7.826 &amp;rarr; -6.100)했다고 볼 수 있습니다.&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;letter-spacing: 0px;&quot;&gt;마찬가지 계산을 $P(ㄴ|두쪼)$, $P(쿠|두쫀)$ 등에 대해서도 수행해볼 수 있습니다. 이 경우 '두'와는 다르게 '두쪼'의 추정 빈도는 약 8회, 심지어 '두쫀'은 말뭉치 내에서 출현빈도가 아예 없는데요, 이런 경우는 아주 작은 양수인 $\epsilon$으로 전역 빈도를 대신합니다. 똑같이 국소빈도를 더해주더라도 이번에는 '두쪼'와 '두쫀'의 전역 빈도가 워낙 작기 때문에 똑같이 국소 빈도를 증가시켰음에도 사후 로그 확률은 더 크게 증가($P(ㄴ|두쪼)$는 -3.718 &amp;rarr; -1.236, $P(쿠|두쫀)$는 -7.354 &amp;rarr; -0.000)합니다. 따라서 이 사후 확률을 OOV의 명사 점수로 사용하게 되면 여러 번 반복된 문자열인 '두쫀쿠'는 한 번 쓰인 '두쫀쿠'보다는 더 높은 점수를 받게 될 겁니다.&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;letter-spacing: 0px;&quot;&gt;반면 &quot;됐습니다.&quot; 와 같이 워낙 자주 반복되는 문자열이지만 실제로 OOV는 아닌 일반적인 형태는 어떨까요? '됐습니다'의 주어진 문맥에서의 국소빈도가 5라고 가정해보고 마찬가지로 사후 확률을 계산해보면 이번에는 '됐습니다' 자체의 전역 빈도가 높기 때문에 국소빈도를 더해도 사후 분포가 크게 변화하지 않고 그 결과 사후 로그 확률도 처음과 거의 유사하게 유지됩니다. 따라서 '됐습니다'는 현재 문맥에서 많이 쓰였다해도 여전히 OOV 명사 점수는 낮게 추정되고 OOV로 묶이는게 아니라 정상적으로 형태소 분석을 거치게 될 겁니다.&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;letter-spacing: 0px;&quot;&gt;그런데 만약 '됐습니다'가 수 천번 등장하는 문맥이 있다면 어떻게 될까요? 비현실적인 가정이긴 하지만, 국소빈도가 워낙 크기 때문에 사후 로그 확률이 크게 증가하게 되어서 '됐습니다'가 OOV 명사로 잘못 분석될 가능성이 생기게 되겠죠. 그래서 전역 빈도와 국소 빈도를 그대로 사용하지 않고 다음과 같이 상한을 가지도록 약간 변형했습니다. 여기에서 $C_g$는 전역 빈도, $C_l$는 국소 빈도, 그리고 $w_g$는 전역 빈도의 상한, $w_l$는 지역 빈도의 상한입니다. $\tanh(x)$ 함수는 $x$가 커짐에 따라 꾸준히 우상향하지만 아무리 커져도 1을 넘지는 못한다는 특성이 있습니다. 이를 활용해 $C'_g$는 아무리 커도 $w_g$를 넘지 않도록, $C'_l$는 $w_l$를 넘지 못하도록 조정해주었습니다. 여러 실험을 통해 $w_g$는 35로, $w_l$는 3으로 결정했습니다.&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;letter-spacing: 0px;&quot;&gt;$$C'_g = \tanh\left ( C_g / w_g \right ) \cdot w_g$$&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;$$C'_l = \tanh\left ( C_l / w_l \right ) \cdot w_l$$&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;성능 비교 &amp;amp; 벤치마크 결과&lt;/h3&gt;
&lt;p style=&quot;text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;좋습니다. 이론 상으로는 완벽한것 같은데 실제로도 잘 작동할까요? 이를 위해선 미등재어들이 태깅된 평가 데이터셋이 필요합니다. Kiwi가 이미 잘 알고 있는, 사전에 등재된 명사들만 포함된 문장을 분석하게 되면 OOV 탐지 성능 평가가 아니라 그냥 명사 찾기 평가가 되어버리니깐요. 그렇다면 Kiwi가 한번도 못 본 명사들이 포함된 문장은 어디서 구할 수 있을까요? 개체명인식(Named Entity Recognition, NER)이라는 자연어처리와 정보 검색 분야에서 오랫동안 연구된 과제가 있습니다. 말 그대로 문장 내에서 사람, 장소, 단체 등의 개체명을 인식해내는 작업인데요, 대체로 이런 이름들은 고유 명사이고 끊임없이 새로 만들어지기 때문에 대부분이 미등재어라고 봐도 무방합니다. 따라서 개체명 인식 평가에서 쓰이는 개체명들을 OOV라고 간주하고 Kiwi가 이를 잘못 분해하지 않고 제대로 분석하는지를 살펴본다면 Kiwi의 OOV 탐지 성능을 확인할 수 있습니다. 다행히도 &lt;a href=&quot;https://kli.korean.go.kr/corpus/main/requestMain.do?lang=ko&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;모두의 말뭉치&lt;/a&gt;에서 개체명 분석 말뭉치를 제공하고 있으므로 이걸 그대로 가져다가 약간만 수정하여 평가 데이터를 구축했습니다.&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;다음과 같이 개체명 분석 말뭉치에서 임의로 문장 하나를 뽑고 태깅된 개체명 전체를 그대로 &amp;lt;n&amp;gt; 태그로 감싸고 개체명 타입을 추가했습니다. 추가로 원문을 형태소 분석하여 일반 명사도 찾아 &amp;lt;n&amp;gt;으로 감쌌습니다.&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style3&quot;&gt;원문: &lt;b&gt;CNP차앤박&lt;/b&gt;&lt;sub&gt;(OG, 기관)&lt;/sub&gt;의 화장품&amp;nbsp;&lt;b&gt;클렌징퍼펙타&lt;/b&gt;&lt;sub&gt;(AF, 인공물)&lt;/sub&gt;는 25000원이다.&lt;br /&gt;레이블: &lt;span style=&quot;color: #006dd7;&quot;&gt;&amp;lt;n e=&quot;OG&quot;&amp;gt;&lt;/span&gt;&lt;b&gt;CNP차앤박&lt;/b&gt;&lt;span style=&quot;color: #006dd7;&quot;&gt;&amp;lt;/n&amp;gt;&lt;/span&gt;의 &lt;span style=&quot;color: #006dd7;&quot;&gt;&amp;lt;n&amp;gt;&lt;/span&gt;화장품&lt;span style=&quot;color: #006dd7;&quot;&gt;&amp;lt;/n&amp;gt;&lt;/span&gt; &lt;span style=&quot;color: #006dd7;&quot;&gt;&amp;lt;n e=&quot;AF&quot;&amp;gt;&lt;/span&gt;&lt;b&gt;클렌징퍼펙타&lt;/b&gt;&lt;span style=&quot;color: #006dd7;&quot;&gt;&amp;lt;/n&amp;gt;&lt;/span&gt;는 25000원이다.&lt;/blockquote&gt;
&lt;p style=&quot;text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;빈도 정보의 효과도 검증하기 위해 동일한 OOV가 여러번 반복되는 데이터도 필요한데요, 불행히도 개체명 분석 말뭉치에서 동일한 OOV가 여러 번 등장하는 사례가 너무 적었습니다. 그래서 LLM을 이용해 원문 데이터를 증강하는 방식을 사용했습니다. 크기에 비해 한국어 성능이 좋다고 잘 알려진 &lt;a href=&quot;https://huggingface.co/kakaocorp/kanana-2-30b-a3b-instruct-2601&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;kanana-2 instruct 모델&lt;/a&gt;을 활용했어요. (덕분에 놀던 제 GPU가 오랜만에 열심히 일했습니다~)&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style3&quot;&gt;증강 1차: &lt;b&gt;CNP차앤박&lt;/b&gt;의 화장품 &lt;b&gt;클렌징퍼펙타&lt;/b&gt;는 25000원이다. 이 제품은 뛰어난 클렌징 효과로 유명한데, 얼굴의 메이크업과 불순물을 효과적으로 제거해주는 제품입니다. &lt;b&gt;CNP차앤박&lt;/b&gt;은 피부과와의 협력을 통해 개발된 다양한 스킨케어 제품들로 잘 알려져 있죠. 특히 &lt;b&gt;클렌징퍼펙타&lt;/b&gt;는 자극이 적고 피부에 부드럽게 작용하여 민감한 피부를 가진 사람들도 안심하고 사용할 수 있습니다.&lt;br /&gt;(각 개체명이 2번 반복됨)&lt;br /&gt;&lt;br /&gt;증강 2차: &lt;b&gt;CNP차앤박&lt;/b&gt;의 화장품 &lt;b&gt;클렌징퍼펙타&lt;/b&gt;는 25000원이다. 이 제품은 뛰어난 클렌징 효과로 유명한데, 얼굴의 메이크업과 불순물을 효과적으로 제거해주는 제품입니다. &lt;b&gt;CNP차앤박&lt;/b&gt;은 피부과와의 협력을 통해 개발된 다양한 스킨케어 제품들로 잘 알려져 있죠. 특히 &lt;b&gt;클렌징퍼펙타&lt;/b&gt;는 자극이 적고 피부에 부드럽게 작용하여 민감한 피부를 가진 사람들도 안심하고 사용할 수 있습니다. &lt;b&gt;CNP차앤박&lt;/b&gt; &lt;b&gt;클렌징퍼펙타&lt;/b&gt;를 사용하면 하루의 피로를 말끔히 씻어내고, 깨끗하고 맑은 피부를 유지할 수 있을 거예요. &lt;b&gt;클렌징퍼펙타&lt;/b&gt;를 통해 &lt;b&gt;CNP차앤박&lt;/b&gt;의 뛰어난 품질을 경험해보세요!&lt;br /&gt;(각 개체명이 4번 반복됨)&lt;/blockquote&gt;
&lt;p style=&quot;text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;이렇게 각 개체명이 1번만 등장하는 데이터셋(ne_1), 2번 등장하는 데이터셋(ne_2), 4번 등장하는 데이터셋(ne_4)을 구축했습니다. (평가데이터셋 전체는 &lt;a href=&quot;https://github.com/bab2min/kiwipiepy/tree/v0.23.0/benchmark/oov/testset&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;kiwipiepy의 Github 저장소&lt;/a&gt;에서 확인하실 수 있습니다.)&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot; data-ke-size=&quot;size16&quot;&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;NE Recall&lt;/b&gt;: 태깅된 개체명들 중 분석기가 올바르게 분석해낸 개체명의 비율&lt;/li&gt;
&lt;li&gt;&lt;b&gt;명사 F1&lt;/b&gt;: 태깅된 모든 명사와 분석기가 찾아낸 명사가 얼마나 일치하는지 F1으로 계산한 것.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;명사 ChrF1&lt;/b&gt;: 명사 F1과 동일한데 F1 계산시에 단어 단위가 아니라 글자 단위로 점수를 계산한 것.&lt;/li&gt;
&lt;/ul&gt;
&lt;p style=&quot;text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;NE Recall은 미등재어를 잘못 분해하지 않고 얼마나 제대로 쪼개는지를 보기 위한 것입니다. 이번 평가에서 제일 눈여겨봐야 할 지표라고 볼 수 있죠. 그런데 미등재어를 분해하지 않으려고 하다 보면 멀쩡한 일반 명사들도 쪼개지 않고 묶어서 잘못 분석할 수도 있습니다. 그래서 일반 명사들을 분석하는 성능이 떨어지지 않는지를 보기 위해 명사 F1, ChrF1를 보조 지표로 두었습니다. 즉 이번 Kiwi 신기능 평가의 관전 포인트는 과연 명사 F1, ChrF1은 이전 버전보다 떨어지지 않으면서 NE Recall이 올라가는지가 되겠습니다.&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;형태 정보, 빈도 정보를 반영하는 방법을 바꿔가며 총 4종의 Kiwi를 실험해보았습니다.&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;Kiwi (v0.22.2)&lt;/b&gt;: 형태, 빈도 정보가 전혀 반영되지 않는 베이스라인 시스템&lt;/li&gt;
&lt;li&gt;&lt;b&gt;Kiwi (v0.23.0, rule)&lt;/b&gt;: 규칙 기반으로 형태 정보만 반영합니다. 구체적으로 OOV 점수를 길이에 비례하여 부여합니다.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;Kiwi (v0.23.0, chr)&lt;/b&gt;: 형태 정보를 문자 기반의 명사 모델을 통해 반영합니다.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;Kiwi (v0.23.0, chr_freq)&lt;/b&gt;: 형태 정보에 빈도 정보까지 반영한 사후 확률을 사용합니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1172&quot; data-origin-height=&quot;720&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/v3MnJ/dJMcaciddBQ/JGuFozZVNWQuLgokqmoFAK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/v3MnJ/dJMcaciddBQ/JGuFozZVNWQuLgokqmoFAK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/v3MnJ/dJMcaciddBQ/JGuFozZVNWQuLgokqmoFAK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fv3MnJ%2FdJMcaciddBQ%2FJGuFozZVNWQuLgokqmoFAK%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;640&quot; height=&quot;393&quot; data-origin-width=&quot;1172&quot; data-origin-height=&quot;720&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;1150&quot; data-origin-height=&quot;698&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/dYrh5p/dJMcaa5LzN4/0hXeYMFG4Jn0tdJw0drET0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/dYrh5p/dJMcaa5LzN4/0hXeYMFG4Jn0tdJw0drET0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/dYrh5p/dJMcaa5LzN4/0hXeYMFG4Jn0tdJw0drET0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FdYrh5p%2FdJMcaa5LzN4%2F0hXeYMFG4Jn0tdJw0drET0%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;640&quot; height=&quot;388&quot; data-origin-width=&quot;1150&quot; data-origin-height=&quot;698&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;1160&quot; data-origin-height=&quot;704&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bcUYMl/dJMcaipcq2n/PakqW8pz8KWknkEhvwSlrK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bcUYMl/dJMcaipcq2n/PakqW8pz8KWknkEhvwSlrK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bcUYMl/dJMcaipcq2n/PakqW8pz8KWknkEhvwSlrK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbcUYMl%2FdJMcaipcq2n%2FPakqW8pz8KWknkEhvwSlrK%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;640&quot; height=&quot;388&quot; data-origin-width=&quot;1160&quot; data-origin-height=&quot;704&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;결과부터 보면서 이야기해볼까요? Kiwi 이전 버전(v0.22.2)와 최신 버전의 3가지 OOV 처리 모드(rule, chr, chr_freq), 그리고 다른 형태소 분석기들의 평가 결과를 비교한 것입니다. rule, chr, chr_freq 모든 경우에서 이전 버전보다는 개선되었습니다. 그리고 특히 rule과 chr을 비교해보면 chr을 사용시 NE Recall이 50% 이상으로 크게 올라가는 걸 볼 수 있습니다. 즉 문자 기반 모델로 점수를 매기는게 OOV 탐지에 크게 도움이 된다는 걸 알 수 있습니다. 그리고 chr과 chr_freq를 비교해보면, OOV가 한번씩만 등장한 ne_1 데이터셋에서는 chr, chr_freq 간의 점수 차이가 거의 없지만, 각각 2번, 4번씩 등장한 ne_2, ne_4 데이터셋에서는 chr_freq의 점수가 크게 올라가는 것을 확인할 수 있습니다. 즉 빈도 정보까지 활용하는 경우 확실히 OOV 탐지 성능이 올라간다는게 입증된 셈입니다. 보조 지표인 명사 F1와 ChrF1도 Kiwi v0.23.0에서 하락한 경우는 없고 일관되게 개선된 것으로 측정되었습니다. 즉 일반 명사 분석 성능에는 해를 끼치지 않으면서 OOV만 더 잘 잡게 되었다고 주장할 수 있겠네요.&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;다른 형태소 분석기들의 결과도 살펴볼까요? 먼저 주목할 만한 것은 Hannanum입니다. Hannanum의 경우 Kiwi보다 NE Recall이 아주 높게 나오는데요, 사실 이건 Hannanum이 잘 모르는 글자 배열에 대해서는 임의로 분할하는 대신 통으로 묶어 버리는 전략을 취하기 때문에 그렇습니다. 그래서 명사 F1, ChrF1에서는 반대로 Hannanum이 Kiwi보다 낮게 나옵니다. 즉 애매한 것들을 다 OOV 취급하다보니 잘 아는 단어들까지도 분석하지 않고 OOV로 묶어 버리는 실수를 한다고 볼 수 있겠네요. Khaiii와 Bareun은 비교적 NE Recall과 명사 F1, ChrF1이 균형있게 높은 점수를 기록하고 있습니다. 특히 Bareun은 ne_1에서 NE Recall이 제일 잘 나오고 있어서 확실히 Transformer 모델의 강력함을 잘 보여줍니다. 다만 빈도 정보까지는 활용하지 못하는지 ne_2, ne_4에서는 chr_freq 기반의 Kiwi보다는 약간 성능이 낮네요.&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;다음은 최적의 문자 모델을 고르기 위해 하이퍼파라미터를 탐색했던 과정입니다. 먼저 문자 모델의 문맥 길이별로 OOV 탐지 성능이 어떻게 바뀌는지 살펴봤습니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1154&quot; data-origin-height=&quot;694&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bt33lD/dJMcajn1rkH/z7npR4lRi2SpWKMVdzzhkk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bt33lD/dJMcajn1rkH/z7npR4lRi2SpWKMVdzzhkk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bt33lD/dJMcajn1rkH/z7npR4lRi2SpWKMVdzzhkk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fbt33lD%2FdJMcajn1rkH%2Fz7npR4lRi2SpWKMVdzzhkk%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;640&quot; height=&quot;385&quot; data-origin-width=&quot;1154&quot; data-origin-height=&quot;694&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;1148&quot; data-origin-height=&quot;698&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/c12iAJ/dJMcaarakp5/K1mg5k9uzwWfFkGVZwk9SK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/c12iAJ/dJMcaarakp5/K1mg5k9uzwWfFkGVZwk9SK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/c12iAJ/dJMcaarakp5/K1mg5k9uzwWfFkGVZwk9SK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fc12iAJ%2FdJMcaarakp5%2FK1mg5k9uzwWfFkGVZwk9SK%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;640&quot; height=&quot;389&quot; data-origin-width=&quot;1148&quot; data-origin-height=&quot;698&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;1142&quot; data-origin-height=&quot;696&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/TckLb/dJMcaf0hoEB/9Salpih3Goc6ddfGgFhnq0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/TckLb/dJMcaf0hoEB/9Salpih3Goc6ddfGgFhnq0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/TckLb/dJMcaf0hoEB/9Salpih3Goc6ddfGgFhnq0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FTckLb%2FdJMcaf0hoEB%2F9Salpih3Goc6ddfGgFhnq0%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;640&quot; height=&quot;390&quot; data-origin-width=&quot;1142&quot; data-origin-height=&quot;696&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;문맥이 길수록 좋을거라는 예상과는 다르게 3~4일때 가장 높은 성능을 찍고 서서히 우하향하는 모습을 보였습니다. CoNg이 신경망 구조이긴 해도 문맥 처리는 근본적으로 n-gram과 유사하기 때문에 이런 부분에서는 n-gram과 유사한 모습을 보이는 것 같네요. OOV가 여러번 등장하는 경우보다 1번만 등장할 경우가 더 잦을 것이라고 생각했기에 ne_1에서 제일 높은 성능을 보일 수 있도록 문맥 길이는 4로 잡았습니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1174&quot; data-origin-height=&quot;686&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/D5wuV/dJMcagEWTcD/WOUjexSTxWeat2rZnv4TV1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/D5wuV/dJMcagEWTcD/WOUjexSTxWeat2rZnv4TV1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/D5wuV/dJMcagEWTcD/WOUjexSTxWeat2rZnv4TV1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FD5wuV%2FdJMcagEWTcD%2FWOUjexSTxWeat2rZnv4TV1%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;640&quot; height=&quot;374&quot; data-origin-width=&quot;1174&quot; data-origin-height=&quot;686&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;그리고 전역 빈도의 상한을 결정하는 하이퍼파라미터 $w_g$에 대한 실험도 진행했습니다. $w_g$가 작으면 작은 국소 빈도도 상대적으로 많이 반영되기 때문에 ne_2, ne_4 데이터셋의 정확도는 높아집니다. 반면 실제로 OOV 명사가 아님에도 우연히 여러번 등장한 문자열이 OOV로 오분석될 가능성이 있기 때문에 ne_1에서는 정확도가 살짝 떨어집니다. 반대로 $w_g$가 커질수록 ne_1에서의 정확도는 상승하지만 ne_2, ne_4의 정확도는 떨어지죠. 최적의 문맥 길이를 고를때와 마찬가지 이유로 ne_1의 점수가 안정적인게 가장 중요하다는 판단 하에 ne_1 정확도가 더이상 오르지 않는 $w_g$ = 35 지점을 최적값으로 선택하게 되었습니다.&lt;/p&gt;
&lt;h3 style=&quot;text-align: justify;&quot; data-ke-size=&quot;size23&quot;&gt;속도 비교&lt;/h3&gt;
&lt;p style=&quot;text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;OOV 분석 성능이 개선된 건 확실해 보이는데요, 문자 기반의 명사 모델을 도입하고 또 국소 빈도를 세고 사후 확률을 계산하는 등의 작업 때문에 형태소 분석 과정이 느려지지 않을까요? 다행히도 그렇지는 않습니다. 분석 과정에서 OOV의 발생빈도가 높지 않고 또 OOV의 명사 점수의 경우 한 번 계산해두면 동일한 형태가 다시 등장했을 때 재사용이 가능하기 때문에 연산량을 추가로 줄일 수 있는 방법이 얼마든지 있기 때문이죠.&lt;/p&gt;
&lt;table class=&quot;std-table&quot; data-ke-align=&quot;alignLeft&quot;&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;th colspan=&quot;2&quot;&gt;&amp;nbsp;&lt;/th&gt;
&lt;th&gt;분석 속도(KB/s)&lt;/th&gt;
&lt;th&gt;속도 증감(%)&lt;/th&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;th&gt;Kiwi 0.22.2&lt;/th&gt;
&lt;th&gt;-&lt;/th&gt;
&lt;td&gt;335.7681&lt;/td&gt;
&lt;td&gt;0.0%&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;th rowspan=&quot;3&quot;&gt;Kiwi 0.23.0&lt;/th&gt;
&lt;th&gt;rule&lt;/th&gt;
&lt;td&gt;348.8715&lt;/td&gt;
&lt;td&gt;+3.9%&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;th&gt;chr&lt;/th&gt;
&lt;td&gt;337.2581&lt;/td&gt;
&lt;td&gt;+0.4%&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;th&gt;chr_freq&lt;/th&gt;
&lt;td&gt;330.7070&lt;/td&gt;
&lt;td&gt;-1.5%&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p style=&quot;text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;실제로 이전 버전의 분석 속도를 베이스라인으로 삼고, 0.23.0에서 도입된 3가지 기법의 분석 속도를 측정해본 결과는 위와 같습니다. oov_handling = chr를 사용한 경우 rule보다 속도가 3% 가량 감소하는 것을 확인할 수 있습니다. 다만 0.23.0 업데이트에서 적용한 다양한 최적화 덕분에 oov_handling = chr일때의 속도를 이전 버전과 유사하게 맞출 수 있었습니다. chr_freq의 경우 국소 문맥의 빈도를 조사하고 이를 사후 확률에 통합하는 과정이 포함되어 속도 하락이 조금 더 발생하지만 여전히 이전 버전과 비교해서 크게 느린 수준은 아닙니다. 즉 사실 상 이전 버전과 유사한 속도를 유지하면서 OOV 처리를 개선하는데에 성공했다고 볼 수 있겠습니다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;실제 사례 분석&lt;/h3&gt;
&lt;p style=&quot;text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;수치로만 보면 감이 잘 안 오죠? 실제 형태소 분석 사례들을 살펴보면서 chr, chr_freq 기법이 어떤 효과가 있는지 확인해봅시다. (참고로 &lt;a style=&quot;color: #0070d1; text-align: justify;&quot; href=&quot;https://kiwi.bab2min.pe.kr/&quot;&gt;Kiwi 데모 페이지&lt;/a&gt;나 &lt;a style=&quot;color: #0070d1; text-align: justify;&quot; href=&quot;https://github.com/bab2min/kiwi-gui/releases&quot;&gt;kiwi-gui 저장소&lt;/a&gt;에서 다음의 분석 결과를 직접 테스트해보실 수 있습니다.)&lt;/p&gt;
&lt;table class=&quot;std-table&quot; data-ke-align=&quot;alignLeft&quot;&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;th&gt;원문&lt;/th&gt;
&lt;th&gt;oov_handling = rule&lt;/th&gt;
&lt;th&gt;oov_handling = chr&lt;/th&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;b&gt;배그&lt;/b&gt;또하고싶다 진짜재미있었는데&lt;/td&gt;
&lt;td&gt;&lt;span style=&quot;font-weight: bold; color: #e20;&quot;&gt;배그또&lt;sub&gt;/NNG&lt;/sub&gt;&lt;/span&gt; 하&lt;sub&gt;/XSV&lt;/sub&gt; 고&lt;sub&gt;/EC&lt;/sub&gt; 싶&lt;sub&gt;/VX&lt;/sub&gt; 다&lt;sub&gt;/EF&lt;/sub&gt; 진짜&lt;sub&gt;/MAG&lt;/sub&gt; 재미있&lt;sub&gt;/VA&lt;/sub&gt; 었&lt;sub&gt;/EP&lt;/sub&gt; 는데&lt;sub&gt;/EC&lt;/sub&gt;&lt;/td&gt;
&lt;td&gt;&lt;span style=&quot;font-weight: bold; color: #0c3;&quot;&gt;배그&lt;sub&gt;/NNG&lt;/sub&gt;&lt;/span&gt; 또&lt;sub&gt;/MAG&lt;/sub&gt; 하&lt;sub&gt;/VV&lt;/sub&gt; 고&lt;sub&gt;/EC&lt;/sub&gt; 싶&lt;sub&gt;/VX&lt;/sub&gt; 다&lt;sub&gt;/EF&lt;/sub&gt; 진짜&lt;sub&gt;/MAG&lt;/sub&gt; 재미있&lt;sub&gt;/VA&lt;/sub&gt; 었&lt;sub&gt;/EP&lt;/sub&gt; 는데&lt;sub&gt;/EC&lt;/sub&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;b&gt;김병근&lt;/b&gt; 국립해양문화재연구소 학예연구관은 27일 논문을 발표했다.&lt;/td&gt;
&lt;td&gt;&lt;span style=&quot;font-weight: bold; color: #e20;&quot;&gt;김&lt;sub&gt;/NNP&lt;/sub&gt; 병&lt;sub&gt;/NNG&lt;/sub&gt; 근&lt;sub&gt;/NNG&lt;/sub&gt;&lt;/span&gt; 국립해양문화재연구소&lt;sub&gt;/NNP&lt;/sub&gt; 학예&lt;sub&gt;/NNG&lt;/sub&gt; 연구관&lt;sub&gt;/NNG&lt;/sub&gt; 은&lt;sub&gt;/JX&lt;/sub&gt; 27&lt;sub&gt;/SN&lt;/sub&gt; 일&lt;sub&gt;/NNB&lt;/sub&gt; 논문&lt;sub&gt;/NNG&lt;/sub&gt; 을&lt;sub&gt;/JKO&lt;/sub&gt; 발표&lt;sub&gt;/NNG&lt;/sub&gt; 하&lt;sub&gt;/XSV&lt;/sub&gt; 었&lt;sub&gt;/EP&lt;/sub&gt; 다&lt;sub&gt;/EF&lt;/sub&gt; .&lt;sub&gt;/SF&lt;/sub&gt;&lt;/td&gt;
&lt;td&gt;&lt;span style=&quot;font-weight: bold; color: #0c3;&quot;&gt;김병근&lt;sub&gt;/NNP&lt;/sub&gt;&lt;/span&gt; 국립해양문화재연구소&lt;sub&gt;/NNP&lt;/sub&gt; 학예&lt;sub&gt;/NNG&lt;/sub&gt; 연구관&lt;sub&gt;/NNG&lt;/sub&gt; 은&lt;sub&gt;/JX&lt;/sub&gt; 27&lt;sub&gt;/SN&lt;/sub&gt; 일&lt;sub&gt;/NNB&lt;/sub&gt; 논문&lt;sub&gt;/NNG&lt;/sub&gt; 을&lt;sub&gt;/JKO&lt;/sub&gt; 발표&lt;sub&gt;/NNG&lt;/sub&gt; 하&lt;sub&gt;/XSV&lt;/sub&gt; 었&lt;sub&gt;/EP&lt;/sub&gt; 다&lt;sub&gt;/EF&lt;/sub&gt; .&lt;sub&gt;/SF&lt;/sub&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;아니 이 &lt;b&gt;맹고스틴&lt;/b&gt;들 뭐야 진짜 맛있잖아&lt;/td&gt;
&lt;td&gt;아니&lt;sub&gt;/IC&lt;/sub&gt; 이&lt;sub&gt;/MM&lt;/sub&gt; &lt;span style=&quot;font-weight: bold; color: #e20;&quot;&gt;맹고스틴들&lt;sub&gt;/NNG&lt;/sub&gt;&lt;/span&gt; 뭐&lt;sub&gt;/NP&lt;/sub&gt; 이&lt;sub&gt;/VCP&lt;/sub&gt; 야&lt;sub&gt;/EF&lt;/sub&gt; 진짜&lt;sub&gt;/MAG&lt;/sub&gt; 맛있&lt;sub&gt;/VA&lt;/sub&gt; 잖아&lt;sub&gt;/EF&lt;/sub&gt;&lt;/td&gt;
&lt;td&gt;아니&lt;sub&gt;/IC&lt;/sub&gt; 이&lt;sub&gt;/MM&lt;/sub&gt; &lt;span style=&quot;font-weight: bold; color: #0c3;&quot;&gt;맹고스틴&lt;sub&gt;/NNG&lt;/sub&gt;&lt;/span&gt; 들&lt;sub&gt;/XSN&lt;/sub&gt; 뭐&lt;sub&gt;/NP&lt;/sub&gt; 이&lt;sub&gt;/VCP&lt;/sub&gt; 야&lt;sub&gt;/EF&lt;/sub&gt; 진짜&lt;sub&gt;/MAG&lt;/sub&gt; 맛있&lt;sub&gt;/VA&lt;/sub&gt; 잖아&lt;sub&gt;/EF&lt;/sub&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;오늘 저녁 롯데리아 &lt;b&gt;모짜새우버거&lt;/b&gt;는 어때?&lt;/td&gt;
&lt;td&gt;오늘&lt;sub&gt;/NNG&lt;/sub&gt; 저녁&lt;sub&gt;/NNG&lt;/sub&gt; 롯데리아&lt;sub&gt;/NNP&lt;/sub&gt; &lt;span style=&quot;font-weight: bold; color: #0c3;&quot;&gt;모짜새우버거&lt;sub&gt;/NNG&lt;/sub&gt;&lt;/span&gt; 는&lt;sub&gt;/JX&lt;/sub&gt; 어떻&lt;sub&gt;/VA-I&lt;/sub&gt; 어&lt;sub&gt;/EF&lt;/sub&gt; ?&lt;sub&gt;/SF&lt;/sub&gt;&lt;/td&gt;
&lt;td&gt;오늘&lt;sub&gt;/NNG&lt;/sub&gt; 저녁&lt;sub&gt;/NNG&lt;/sub&gt; 롯데리아&lt;sub&gt;/NNP&lt;/sub&gt; &lt;span style=&quot;font-weight: bold; color: #e20;&quot;&gt;모짜&lt;sub&gt;/NNG&lt;/sub&gt; 새우&lt;sub&gt;/NNG&lt;/sub&gt; 버거&lt;sub&gt;/NNG&lt;/sub&gt;&lt;/span&gt; 는&lt;sub&gt;/JX&lt;/sub&gt; 어떻&lt;sub&gt;/VA-I&lt;/sub&gt; 어&lt;sub&gt;/EF&lt;/sub&gt; ?&lt;sub&gt;/SF&lt;/sub&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;마켓컬리에있어요! &lt;b&gt;금미옥떡볶이&lt;/b&gt;라구 되게맛나요~&lt;/td&gt;
&lt;td&gt;마켓컬리&lt;sub&gt;/NNP&lt;/sub&gt; 에&lt;sub&gt;/JKB&lt;/sub&gt; 있&lt;sub&gt;/VA&lt;/sub&gt; 어요&lt;sub&gt;/EF&lt;/sub&gt; !&lt;sub&gt;/SF&lt;/sub&gt; &lt;span style=&quot;font-weight: bold; color: #e20;&quot;&gt;금미옥떡볶이라구&lt;sub&gt;/NNG&lt;/sub&gt;&lt;/span&gt; 되게&lt;sub&gt;/MAG&lt;/sub&gt; 맛나&lt;sub&gt;/VA&lt;/sub&gt; 어요&lt;sub&gt;/EF&lt;/sub&gt; ~&lt;sub&gt;/SO&lt;/sub&gt;&lt;/td&gt;
&lt;td&gt;마켓컬리&lt;sub&gt;/NNP&lt;/sub&gt; 에&lt;sub&gt;/JKB&lt;/sub&gt; 있&lt;sub&gt;/VA&lt;/sub&gt; 어요&lt;sub&gt;/EF&lt;/sub&gt; !&lt;sub&gt;/SF&lt;/sub&gt; &lt;span style=&quot;font-weight: bold; color: #0c3;&quot;&gt;금미옥떡볶이&lt;sub&gt;/NNG&lt;/sub&gt;&lt;/span&gt; 이&lt;sub&gt;/VCP&lt;/sub&gt; 라구&lt;sub&gt;/EC&lt;/sub&gt; 되게&lt;sub&gt;/MAG&lt;/sub&gt; 맛나&lt;sub&gt;/VA&lt;/sub&gt; 어요&lt;sub&gt;/EF&lt;/sub&gt; ~&lt;sub&gt;/SO&lt;/sub&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p style=&quot;text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;OOV의 길이만 가지고 추정하는 기법인 oov_handling=rule에서는 미등재어에 뒤에 붙은 조사나 접미사들을 대체로 분해하지 않고 통으로 분석하는 경향이 있습니다. 사람 이름 같은 경우는 아예 쪼개버리기도 하구요. 반면 문자 모델로 OOV의 확률을 추정하면 oov_handling=chr에서 보이는것처럼 조사나 접미사들이 붙은 경우에 비교적 경계를 잘 잡아줍니다. &quot;배그또&quot;는 명사답지 못하지만 &quot;배그&quot;는 명사답다는 것, &quot;금미옥떡볶이라구&quot;는 명사답지 못하지만 &quot;금미옥떡볶이&quot;는 명사답다는 것을 알려주기 때문이죠. 물론 &quot;모짜새우버거&quot;처럼 통으로 분석하는게 맞는 상황에서도 쪼개는 경우도 종종 있긴 합니다.&lt;/p&gt;
&lt;table class=&quot;std-table&quot; data-ke-align=&quot;alignLeft&quot;&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;th&gt;원문&lt;/th&gt;
&lt;th&gt;oov_handling = chr&lt;/th&gt;
&lt;th&gt;oov_handling = chr_freq&lt;/th&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;삼성증권의 '&lt;b&gt;슈퍼스텝다운주가연계증권&lt;/b&gt;'은 지난달 출시 후 한달여 만에 900억원이 넘는 자금을 끌어 모았다. &lt;b&gt;슈퍼스텝다운주가연계증권&lt;/b&gt;은 주가의 변동에 따라 수익률이 결정되는 상품으로&lt;/td&gt;
&lt;td&gt;삼성증권&lt;sub&gt;/NNP&lt;/sub&gt; 의&lt;sub&gt;/JKG&lt;/sub&gt; '&lt;sub&gt;/SSO&lt;/sub&gt; &lt;span style=&quot;font-weight: bold; color: #e20;&quot;&gt;슈퍼&lt;sub&gt;/NNG&lt;/sub&gt; 스텝&lt;sub&gt;/NNG&lt;/sub&gt; 답&lt;sub&gt;/XSA-I&lt;/sub&gt; 은&lt;sub&gt;/ETM&lt;/sub&gt; 주가&lt;sub&gt;/NNG&lt;/sub&gt; 연계&lt;sub&gt;/NNG&lt;/sub&gt; 증권&lt;sub&gt;/NNG&lt;/sub&gt;&lt;/span&gt; '&lt;sub&gt;/SSC&lt;/sub&gt; 은&lt;sub&gt;/JX&lt;/sub&gt; 지난달&lt;sub&gt;/NNG&lt;/sub&gt; 출시&lt;sub&gt;/NNG&lt;/sub&gt; 후&lt;sub&gt;/NNG&lt;/sub&gt; 한&lt;sub&gt;/MM&lt;/sub&gt; 달&lt;sub&gt;/NNB&lt;/sub&gt; 여&lt;sub&gt;/XSN&lt;/sub&gt; 만&lt;sub&gt;/NNB&lt;/sub&gt; 에&lt;sub&gt;/JKB&lt;/sub&gt; 900&lt;sub&gt;/SN&lt;/sub&gt; 억&lt;sub&gt;/NR&lt;/sub&gt; 원&lt;sub&gt;/NNB&lt;/sub&gt; 이&lt;sub&gt;/JKS&lt;/sub&gt; 넘&lt;sub&gt;/VV&lt;/sub&gt; 는&lt;sub&gt;/ETM&lt;/sub&gt; 자금&lt;sub&gt;/NNG&lt;/sub&gt; 을&lt;sub&gt;/JKO&lt;/sub&gt; 끌&lt;sub&gt;/VV&lt;/sub&gt; 어&lt;sub&gt;/EC&lt;/sub&gt; 모으&lt;sub&gt;/VV&lt;/sub&gt; 었&lt;sub&gt;/EP&lt;/sub&gt; 다&lt;sub&gt;/EF&lt;/sub&gt; .&lt;sub&gt;/SF&lt;/sub&gt; &lt;span style=&quot;font-weight: bold; color: #e20;&quot;&gt;슈퍼스텝&lt;sub&gt;/NNG&lt;/sub&gt; 답&lt;sub&gt;/XSA-I&lt;/sub&gt; 은&lt;sub&gt;/ETM&lt;/sub&gt; 주가&lt;sub&gt;/NNG&lt;/sub&gt; 연계&lt;sub&gt;/NNG&lt;/sub&gt; 증권&lt;sub&gt;/NNG&lt;/sub&gt;&lt;/span&gt; 은&lt;sub&gt;/JX&lt;/sub&gt; 주가&lt;sub&gt;/NNG&lt;/sub&gt; 의&lt;sub&gt;/JKG&lt;/sub&gt; 변동&lt;sub&gt;/NNG&lt;/sub&gt; 에&lt;sub&gt;/JKB&lt;/sub&gt; 따르&lt;sub&gt;/VV&lt;/sub&gt; 어&lt;sub&gt;/EC&lt;/sub&gt; 수익&lt;sub&gt;/NNG&lt;/sub&gt; 률&lt;sub&gt;/XSN&lt;/sub&gt; 이&lt;sub&gt;/JKS&lt;/sub&gt; 결정&lt;sub&gt;/NNG&lt;/sub&gt; 되&lt;sub&gt;/XSV&lt;/sub&gt; 는&lt;sub&gt;/ETM&lt;/sub&gt; 상품&lt;sub&gt;/NNG&lt;/sub&gt; 으로&lt;sub&gt;/JKB&lt;/sub&gt;&lt;/td&gt;
&lt;td&gt;삼성증권&lt;sub&gt;/NNP&lt;/sub&gt; 의&lt;sub&gt;/JKG&lt;/sub&gt; '&lt;sub&gt;/SSO&lt;/sub&gt; &lt;span style=&quot;font-weight: bold; color: #0c3;&quot;&gt;슈퍼스텝다운주가연계증권&lt;sub&gt;/NNG&lt;/sub&gt;&lt;/span&gt; '&lt;sub&gt;/SSC&lt;/sub&gt; 은&lt;sub&gt;/JX&lt;/sub&gt; 지난달&lt;sub&gt;/NNG&lt;/sub&gt; 출시&lt;sub&gt;/NNG&lt;/sub&gt; 후&lt;sub&gt;/NNG&lt;/sub&gt; 한&lt;sub&gt;/MM&lt;/sub&gt; 달&lt;sub&gt;/NNB&lt;/sub&gt; 여&lt;sub&gt;/XSN&lt;/sub&gt; 만&lt;sub&gt;/NNB&lt;/sub&gt; 에&lt;sub&gt;/JKB&lt;/sub&gt; 900&lt;sub&gt;/SN&lt;/sub&gt; 억&lt;sub&gt;/NR&lt;/sub&gt; 원&lt;sub&gt;/NNB&lt;/sub&gt; 이&lt;sub&gt;/JKS&lt;/sub&gt; 넘&lt;sub&gt;/VV&lt;/sub&gt; 는&lt;sub&gt;/ETM&lt;/sub&gt; 자금&lt;sub&gt;/NNG&lt;/sub&gt; 을&lt;sub&gt;/JKO&lt;/sub&gt; 끌&lt;sub&gt;/VV&lt;/sub&gt; 어&lt;sub&gt;/EC&lt;/sub&gt; 모으&lt;sub&gt;/VV&lt;/sub&gt; 었&lt;sub&gt;/EP&lt;/sub&gt; 다&lt;sub&gt;/EF&lt;/sub&gt; .&lt;sub&gt;/SF&lt;/sub&gt; &lt;span style=&quot;font-weight: bold; color: #0c3;&quot;&gt;슈퍼스텝다운주가연계증권&lt;sub&gt;/NNP&lt;/sub&gt;&lt;/span&gt; 은&lt;sub&gt;/JX&lt;/sub&gt; 주가&lt;sub&gt;/NNG&lt;/sub&gt; 의&lt;sub&gt;/JKG&lt;/sub&gt; 변동&lt;sub&gt;/NNG&lt;/sub&gt; 에&lt;sub&gt;/JKB&lt;/sub&gt; 따르&lt;sub&gt;/VV&lt;/sub&gt; 어&lt;sub&gt;/EC&lt;/sub&gt; 수익&lt;sub&gt;/NNG&lt;/sub&gt; 률&lt;sub&gt;/XSN&lt;/sub&gt; 이&lt;sub&gt;/JKS&lt;/sub&gt; 결정&lt;sub&gt;/NNG&lt;/sub&gt; 되&lt;sub&gt;/XSV&lt;/sub&gt; 는&lt;sub&gt;/ETM&lt;/sub&gt; 상품&lt;sub&gt;/NNG&lt;/sub&gt; 으로&lt;sub&gt;/JKB&lt;/sub&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;수능문제가 &lt;b&gt;한능검&lt;/b&gt;보다 더 어렵습니다. &lt;b&gt;한능검&lt;/b&gt; 시험은 한국사에 대한 이해를 평가하는 시험이죠.&lt;/td&gt;
&lt;td&gt;수능&lt;sub&gt;/NNG&lt;/sub&gt; 문제&lt;sub&gt;/NNG&lt;/sub&gt; 가&lt;sub&gt;/JKS&lt;/sub&gt; &lt;span style=&quot;font-weight: bold; color: #e20;&quot;&gt;한&lt;sub&gt;/MM&lt;/sub&gt; 능&lt;sub&gt;/NNG&lt;/sub&gt; 검&lt;sub&gt;/NNG&lt;/sub&gt;&lt;/span&gt; 보다&lt;sub&gt;/JKB&lt;/sub&gt; 더&lt;sub&gt;/MAG&lt;/sub&gt; 어렵&lt;sub&gt;/VA-I&lt;/sub&gt; 습니다&lt;sub&gt;/EF&lt;/sub&gt; .&lt;sub&gt;/SF&lt;/sub&gt; &lt;span style=&quot;font-weight: bold; color: #e20;&quot;&gt;한&lt;sub&gt;/MM&lt;/sub&gt; 능&lt;sub&gt;/NNG&lt;/sub&gt; 검&lt;sub&gt;/NNG&lt;/sub&gt;&lt;/span&gt; 시험&lt;sub&gt;/NNG&lt;/sub&gt; 은&lt;sub&gt;/JX&lt;/sub&gt; 한국사&lt;sub&gt;/NNG&lt;/sub&gt; 에&lt;sub&gt;/JKB&lt;/sub&gt; 대하&lt;sub&gt;/VV&lt;/sub&gt; ᆫ&lt;sub&gt;/ETM&lt;/sub&gt; 이해&lt;sub&gt;/NNG&lt;/sub&gt; 를&lt;sub&gt;/JKO&lt;/sub&gt; 평가&lt;sub&gt;/NNG&lt;/sub&gt; 하&lt;sub&gt;/XSV&lt;/sub&gt; 는&lt;sub&gt;/ETM&lt;/sub&gt; 시험&lt;sub&gt;/NNG&lt;/sub&gt; 이&lt;sub&gt;/VCP&lt;/sub&gt; 죠&lt;sub&gt;/EF&lt;/sub&gt; .&lt;sub&gt;/SF&lt;/sub&gt;&lt;/td&gt;
&lt;td&gt;수능&lt;sub&gt;/NNG&lt;/sub&gt; 문제&lt;sub&gt;/NNG&lt;/sub&gt; 가&lt;sub&gt;/JKS&lt;/sub&gt; &lt;span style=&quot;font-weight: bold; color: #0c3;&quot;&gt;한능검&lt;sub&gt;/NNP&lt;/sub&gt;&lt;/span&gt; 보다&lt;sub&gt;/JKB&lt;/sub&gt; 더&lt;sub&gt;/MAG&lt;/sub&gt; 어렵&lt;sub&gt;/VA-I&lt;/sub&gt; 습니다&lt;sub&gt;/EF&lt;/sub&gt; .&lt;sub&gt;/SF&lt;/sub&gt; &lt;span style=&quot;font-weight: bold; color: #0c3;&quot;&gt;한능검&lt;sub&gt;/NNG&lt;/sub&gt;&lt;/span&gt; 시험&lt;sub&gt;/NNG&lt;/sub&gt; 은&lt;sub&gt;/JX&lt;/sub&gt; 한국사&lt;sub&gt;/NNG&lt;/sub&gt; 에&lt;sub&gt;/JKB&lt;/sub&gt; 대하&lt;sub&gt;/VV&lt;/sub&gt; ᆫ&lt;sub&gt;/ETM&lt;/sub&gt; 이해&lt;sub&gt;/NNG&lt;/sub&gt; 를&lt;sub&gt;/JKO&lt;/sub&gt; 평가&lt;sub&gt;/NNG&lt;/sub&gt; 하&lt;sub&gt;/XSV&lt;/sub&gt; 는&lt;sub&gt;/ETM&lt;/sub&gt; 시험&lt;sub&gt;/NNG&lt;/sub&gt; 이&lt;sub&gt;/VCP&lt;/sub&gt; 죠&lt;sub&gt;/EF&lt;/sub&gt; .&lt;sub&gt;/SF&lt;/sub&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;IFC몰 지하 &lt;b&gt;올리브영&lt;/b&gt;에 갔지만, 식품판매 제외 매장이여서 결국 못사고, 다른 지점 &lt;b&gt;올리브영&lt;/b&gt;을 찾아가서 통밀당단백칩을 사왔어요.&lt;/td&gt;
&lt;td&gt;IFC&lt;sub&gt;/SL&lt;/sub&gt; 몰&lt;sub&gt;/NNG&lt;/sub&gt; 지하&lt;sub&gt;/NNG&lt;/sub&gt; &lt;span style=&quot;font-weight: bold; color: #e20;&quot;&gt;올리브&lt;sub&gt;/NNG&lt;/sub&gt; 영&lt;sub&gt;/NNG&lt;/sub&gt;&lt;/span&gt; 에&lt;sub&gt;/JKB&lt;/sub&gt; 가&lt;sub&gt;/VV&lt;/sub&gt; 었&lt;sub&gt;/EP&lt;/sub&gt; 지만&lt;sub&gt;/EC&lt;/sub&gt; ,&lt;sub&gt;/SP&lt;/sub&gt; 식품&lt;sub&gt;/NNG&lt;/sub&gt; 판매&lt;sub&gt;/NNG&lt;/sub&gt; 제외&lt;sub&gt;/NNG&lt;/sub&gt; 매장&lt;sub&gt;/NNG&lt;/sub&gt; 이&lt;sub&gt;/VCP&lt;/sub&gt; 여서&lt;sub&gt;/EC&lt;/sub&gt; 결국&lt;sub&gt;/MAG&lt;/sub&gt; 못&lt;sub&gt;/MAG&lt;/sub&gt; 사&lt;sub&gt;/VV&lt;/sub&gt; 고&lt;sub&gt;/EC&lt;/sub&gt; ,&lt;sub&gt;/SP&lt;/sub&gt; 다른&lt;sub&gt;/MM&lt;/sub&gt; 지점&lt;sub&gt;/NNG&lt;/sub&gt; &lt;span style=&quot;font-weight: bold; color: #e20;&quot;&gt;올리브&lt;sub&gt;/NNG&lt;/sub&gt; 영&lt;sub&gt;/NNG&lt;/sub&gt;&lt;/span&gt; 을&lt;sub&gt;/JKO&lt;/sub&gt; 찾아가&lt;sub&gt;/VV&lt;/sub&gt; 어서&lt;sub&gt;/EC&lt;/sub&gt; 통밀당단백칩&lt;sub&gt;/NNG&lt;/sub&gt; 을&lt;sub&gt;/JKO&lt;/sub&gt; 사&lt;sub&gt;/VV&lt;/sub&gt; 어&lt;sub&gt;/EC&lt;/sub&gt; 오&lt;sub&gt;/VV&lt;/sub&gt; 었&lt;sub&gt;/EP&lt;/sub&gt; 어요&lt;sub&gt;/EF&lt;/sub&gt; .&lt;sub&gt;/SF&lt;/sub&gt;&lt;/td&gt;
&lt;td&gt;IFC&lt;sub&gt;/SL&lt;/sub&gt; 몰&lt;sub&gt;/NNG&lt;/sub&gt; 지하&lt;sub&gt;/NNG&lt;/sub&gt; &lt;span style=&quot;font-weight: bold; color: #0c3;&quot;&gt;올리브영&lt;sub&gt;/NNG&lt;/sub&gt;&lt;/span&gt; 에&lt;sub&gt;/JKB&lt;/sub&gt; 가&lt;sub&gt;/VV&lt;/sub&gt; 었&lt;sub&gt;/EP&lt;/sub&gt; 지만&lt;sub&gt;/EC&lt;/sub&gt; ,&lt;sub&gt;/SP&lt;/sub&gt; 식품&lt;sub&gt;/NNG&lt;/sub&gt; 판매&lt;sub&gt;/NNG&lt;/sub&gt; 제외&lt;sub&gt;/NNG&lt;/sub&gt; 매장&lt;sub&gt;/NNG&lt;/sub&gt; 이&lt;sub&gt;/VCP&lt;/sub&gt; 여서&lt;sub&gt;/EC&lt;/sub&gt; 결국&lt;sub&gt;/MAG&lt;/sub&gt; 못&lt;sub&gt;/MAG&lt;/sub&gt; 사&lt;sub&gt;/VV&lt;/sub&gt; 고&lt;sub&gt;/EC&lt;/sub&gt; ,&lt;sub&gt;/SP&lt;/sub&gt; 다른&lt;sub&gt;/MM&lt;/sub&gt; 지점&lt;sub&gt;/NNG&lt;/sub&gt; &lt;span style=&quot;font-weight: bold; color: #0c3;&quot;&gt;올리브영&lt;sub&gt;/NNP&lt;/sub&gt;&lt;/span&gt; 을&lt;sub&gt;/JKO&lt;/sub&gt; 찾아가&lt;sub&gt;/VV&lt;/sub&gt; 어서&lt;sub&gt;/EC&lt;/sub&gt; 통밀당단백칩&lt;sub&gt;/NNG&lt;/sub&gt; 을&lt;sub&gt;/JKO&lt;/sub&gt; 사&lt;sub&gt;/VV&lt;/sub&gt; 어&lt;sub&gt;/EC&lt;/sub&gt; 오&lt;sub&gt;/VV&lt;/sub&gt; 었&lt;sub&gt;/EP&lt;/sub&gt; 어요&lt;sub&gt;/EF&lt;/sub&gt; .&lt;sub&gt;/SF&lt;/sub&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;b&gt;앤디워홀&lt;/b&gt;의 대표적인 실크스크린 작품 &amp;lsquo;메릴린먼로&amp;rsquo; 시리즈 10점 등 대중과 친숙한 작품들이 수두룩하다. 특히 &lt;b&gt;앤디워홀&lt;/b&gt;의 실크스크린 기법은 현대 미술사에서 매우 중요한 위치를 차지하고 있는데&lt;/td&gt;
&lt;td&gt;&lt;span style=&quot;font-weight: bold; color: #e20;&quot;&gt;앤디&lt;sub&gt;/NNP&lt;/sub&gt; 워홀&lt;sub&gt;/NNP&lt;/sub&gt;&lt;/span&gt; 의&lt;sub&gt;/JKG&lt;/sub&gt; 대표&lt;sub&gt;/NNG&lt;/sub&gt; 적&lt;sub&gt;/XSN&lt;/sub&gt; 이&lt;sub&gt;/VCP&lt;/sub&gt; ᆫ&lt;sub&gt;/ETM&lt;/sub&gt; 실크&lt;sub&gt;/NNG&lt;/sub&gt; 스크린&lt;sub&gt;/NNG&lt;/sub&gt; 작품&lt;sub&gt;/NNG&lt;/sub&gt; &amp;lsquo;&lt;sub&gt;/SSO&lt;/sub&gt; 메릴린먼로&lt;sub&gt;/NNP&lt;/sub&gt; &amp;rsquo;&lt;sub&gt;/SSC&lt;/sub&gt; 시리즈&lt;sub&gt;/NNG&lt;/sub&gt; 10&lt;sub&gt;/SN&lt;/sub&gt; 점&lt;sub&gt;/NNB&lt;/sub&gt; 등&lt;sub&gt;/NNB&lt;/sub&gt; 대중&lt;sub&gt;/NNG&lt;/sub&gt; 과&lt;sub&gt;/JKB&lt;/sub&gt; 친숙&lt;sub&gt;/NNG&lt;/sub&gt; 하&lt;sub&gt;/XSA&lt;/sub&gt; ᆫ&lt;sub&gt;/ETM&lt;/sub&gt; 작품&lt;sub&gt;/NNG&lt;/sub&gt; 들&lt;sub&gt;/XSN&lt;/sub&gt; 이&lt;sub&gt;/JKS&lt;/sub&gt; 수두룩하&lt;sub&gt;/VA&lt;/sub&gt; 다&lt;sub&gt;/EF&lt;/sub&gt; .&lt;sub&gt;/SF&lt;/sub&gt; 특히&lt;sub&gt;/MAG&lt;/sub&gt; &lt;span style=&quot;font-weight: bold; color: #e20;&quot;&gt;앤디&lt;sub&gt;/NNP&lt;/sub&gt; 워홀&lt;sub&gt;/NNP&lt;/sub&gt;&lt;/span&gt; 의&lt;sub&gt;/JKG&lt;/sub&gt; 실크&lt;sub&gt;/NNG&lt;/sub&gt; 스크린&lt;sub&gt;/NNG&lt;/sub&gt; 기법&lt;sub&gt;/NNG&lt;/sub&gt; 은&lt;sub&gt;/JX&lt;/sub&gt; 현대&lt;sub&gt;/NNG&lt;/sub&gt; 미술사&lt;sub&gt;/NNG&lt;/sub&gt; 에서&lt;sub&gt;/JKB&lt;/sub&gt; 매우&lt;sub&gt;/MAG&lt;/sub&gt; 중요&lt;sub&gt;/NNG&lt;/sub&gt; 하&lt;sub&gt;/XSA&lt;/sub&gt; ᆫ&lt;sub&gt;/ETM&lt;/sub&gt; 위치&lt;sub&gt;/NNG&lt;/sub&gt; 를&lt;sub&gt;/JKO&lt;/sub&gt; 차지&lt;sub&gt;/NNG&lt;/sub&gt; 하&lt;sub&gt;/XSV&lt;/sub&gt; 고&lt;sub&gt;/EC&lt;/sub&gt; 있&lt;sub&gt;/VX&lt;/sub&gt; 는데&lt;sub&gt;/EC&lt;/sub&gt;&lt;/td&gt;
&lt;td&gt;&lt;span style=&quot;font-weight: bold; color: #e20;&quot;&gt;앤디&lt;sub&gt;/NNP&lt;/sub&gt; 워홀&lt;sub&gt;/NNP&lt;/sub&gt;&lt;/span&gt; 의&lt;sub&gt;/JKG&lt;/sub&gt; 대표&lt;sub&gt;/NNG&lt;/sub&gt; 적&lt;sub&gt;/XSN&lt;/sub&gt; 이&lt;sub&gt;/VCP&lt;/sub&gt; ᆫ&lt;sub&gt;/ETM&lt;/sub&gt; 실크&lt;sub&gt;/NNG&lt;/sub&gt; 스크린&lt;sub&gt;/NNG&lt;/sub&gt; 작품&lt;sub&gt;/NNG&lt;/sub&gt; &amp;lsquo;&lt;sub&gt;/SSO&lt;/sub&gt; 메릴린먼로&lt;sub&gt;/NNP&lt;/sub&gt; &amp;rsquo;&lt;sub&gt;/SSC&lt;/sub&gt; 시리즈&lt;sub&gt;/NNG&lt;/sub&gt; 10&lt;sub&gt;/SN&lt;/sub&gt; 점&lt;sub&gt;/NNB&lt;/sub&gt; 등&lt;sub&gt;/NNB&lt;/sub&gt; 대중&lt;sub&gt;/NNG&lt;/sub&gt; 과&lt;sub&gt;/JKB&lt;/sub&gt; 친숙&lt;sub&gt;/NNG&lt;/sub&gt; 하&lt;sub&gt;/XSA&lt;/sub&gt; ᆫ&lt;sub&gt;/ETM&lt;/sub&gt; 작품&lt;sub&gt;/NNG&lt;/sub&gt; 들&lt;sub&gt;/XSN&lt;/sub&gt; 이&lt;sub&gt;/JKS&lt;/sub&gt; 수두룩하&lt;sub&gt;/VA&lt;/sub&gt; 다&lt;sub&gt;/EF&lt;/sub&gt; .&lt;sub&gt;/SF&lt;/sub&gt; 특히&lt;sub&gt;/MAG&lt;/sub&gt; &lt;span style=&quot;font-weight: bold; color: #0c3;&quot;&gt;앤디워홀&lt;sub&gt;/NNP&lt;/sub&gt;&lt;/span&gt; 의&lt;sub&gt;/JKG&lt;/sub&gt; 실크&lt;sub&gt;/NNG&lt;/sub&gt; 스크린&lt;sub&gt;/NNG&lt;/sub&gt; 기법&lt;sub&gt;/NNG&lt;/sub&gt; 은&lt;sub&gt;/JX&lt;/sub&gt; 현대&lt;sub&gt;/NNG&lt;/sub&gt; 미술사&lt;sub&gt;/NNG&lt;/sub&gt; 에서&lt;sub&gt;/JKB&lt;/sub&gt; 매우&lt;sub&gt;/MAG&lt;/sub&gt; 중요&lt;sub&gt;/NNG&lt;/sub&gt; 하&lt;sub&gt;/XSA&lt;/sub&gt; ᆫ&lt;sub&gt;/ETM&lt;/sub&gt; 위치&lt;sub&gt;/NNG&lt;/sub&gt; 를&lt;sub&gt;/JKO&lt;/sub&gt; 차지&lt;sub&gt;/NNG&lt;/sub&gt; 하&lt;sub&gt;/XSV&lt;/sub&gt; 고&lt;sub&gt;/EC&lt;/sub&gt; 있&lt;sub&gt;/VX&lt;/sub&gt; 는데&lt;sub&gt;/EC&lt;/sub&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;b&gt;MIT테크놀로지리뷰&lt;/b&gt;는 올해 주목해야 할 기술로 폴더블 디스플레이와 스마트폰을 꼽았다. &lt;b&gt;MIT테크놀로지리뷰&lt;/b&gt;는 기술의 발전이 앞으로의 전자기기 시장에 어떤 변화를 가져올지에 대해 깊이 있게 다루고 있다.&lt;/td&gt;
&lt;td&gt;&lt;span style=&quot;font-weight: bold; color: #e20;&quot;&gt;MIT&lt;sub&gt;/SL&lt;/sub&gt; 테크놀로지&lt;sub&gt;/NNG&lt;/sub&gt; 리뷰&lt;sub&gt;/NNG&lt;/sub&gt;&lt;/span&gt; 는&lt;sub&gt;/JX&lt;/sub&gt; 올해&lt;sub&gt;/NNG&lt;/sub&gt; 주목&lt;sub&gt;/NNG&lt;/sub&gt; 하&lt;sub&gt;/XSV&lt;/sub&gt; 어야&lt;sub&gt;/EC&lt;/sub&gt; 하&lt;sub&gt;/VX&lt;/sub&gt; ᆯ&lt;sub&gt;/ETM&lt;/sub&gt; 기술&lt;sub&gt;/NNG&lt;/sub&gt; 로&lt;sub&gt;/JKB&lt;/sub&gt; 폴&lt;sub&gt;/NNG&lt;/sub&gt; 더블&lt;sub&gt;/NNG&lt;/sub&gt; 디스플레이&lt;sub&gt;/NNG&lt;/sub&gt; 와&lt;sub&gt;/JC&lt;/sub&gt; 스마트폰&lt;sub&gt;/NNG&lt;/sub&gt; 을&lt;sub&gt;/JKO&lt;/sub&gt; 꼽&lt;sub&gt;/VV-R&lt;/sub&gt; 었&lt;sub&gt;/EP&lt;/sub&gt; 다&lt;sub&gt;/EF&lt;/sub&gt; .&lt;sub&gt;/SF&lt;/sub&gt; &lt;span style=&quot;font-weight: bold; color: #e20;&quot;&gt;MIT&lt;sub&gt;/SL&lt;/sub&gt; 테크놀로지&lt;sub&gt;/NNG&lt;/sub&gt; 리뷰&lt;sub&gt;/NNG&lt;/sub&gt;&lt;/span&gt; 는&lt;sub&gt;/JX&lt;/sub&gt; 기술&lt;sub&gt;/NNG&lt;/sub&gt; 의&lt;sub&gt;/JKG&lt;/sub&gt; 발전&lt;sub&gt;/NNG&lt;/sub&gt; 이&lt;sub&gt;/JKS&lt;/sub&gt; 앞&lt;sub&gt;/NNG&lt;/sub&gt; 으로&lt;sub&gt;/JKB&lt;/sub&gt; 의&lt;sub&gt;/JKG&lt;/sub&gt; 전자&lt;sub&gt;/NNG&lt;/sub&gt; 기기&lt;sub&gt;/NNG&lt;/sub&gt; 시장&lt;sub&gt;/NNG&lt;/sub&gt; 에&lt;sub&gt;/JKB&lt;/sub&gt; 어떤&lt;sub&gt;/MM&lt;/sub&gt; 변화&lt;sub&gt;/NNG&lt;/sub&gt; 를&lt;sub&gt;/JKO&lt;/sub&gt; 가져오&lt;sub&gt;/VV&lt;/sub&gt; ᆯ지&lt;sub&gt;/EC&lt;/sub&gt; 에&lt;sub&gt;/JKB&lt;/sub&gt; 대하&lt;sub&gt;/VV&lt;/sub&gt; 어&lt;sub&gt;/EC&lt;/sub&gt; 깊이&lt;sub&gt;/MAG&lt;/sub&gt; 있&lt;sub&gt;/VV&lt;/sub&gt; 게&lt;sub&gt;/EC&lt;/sub&gt; 다루&lt;sub&gt;/VV&lt;/sub&gt; 고&lt;sub&gt;/EC&lt;/sub&gt; 있&lt;sub&gt;/VX&lt;/sub&gt; 다&lt;sub&gt;/EF&lt;/sub&gt; .&lt;sub&gt;/SF&lt;/sub&gt;&lt;/td&gt;
&lt;td&gt;&lt;span style=&quot;font-weight: bold; color: #0c3;&quot;&gt;MIT테크놀로지리뷰&lt;sub&gt;/NNP&lt;/sub&gt;&lt;/span&gt; 는&lt;sub&gt;/JX&lt;/sub&gt; 올해&lt;sub&gt;/NNG&lt;/sub&gt; 주목&lt;sub&gt;/NNG&lt;/sub&gt; 하&lt;sub&gt;/XSV&lt;/sub&gt; 어야&lt;sub&gt;/EC&lt;/sub&gt; 하&lt;sub&gt;/VX&lt;/sub&gt; ᆯ&lt;sub&gt;/ETM&lt;/sub&gt; 기술&lt;sub&gt;/NNG&lt;/sub&gt; 로&lt;sub&gt;/JKB&lt;/sub&gt; 폴&lt;sub&gt;/NNG&lt;/sub&gt; 더블&lt;sub&gt;/NNG&lt;/sub&gt; 디스플레이&lt;sub&gt;/NNG&lt;/sub&gt; 와&lt;sub&gt;/JC&lt;/sub&gt; 스마트폰&lt;sub&gt;/NNG&lt;/sub&gt; 을&lt;sub&gt;/JKO&lt;/sub&gt; 꼽&lt;sub&gt;/VV-R&lt;/sub&gt; 었&lt;sub&gt;/EP&lt;/sub&gt; 다&lt;sub&gt;/EF&lt;/sub&gt; .&lt;sub&gt;/SF&lt;/sub&gt; &lt;span style=&quot;font-weight: bold; color: #e20;&quot;&gt;MIT&lt;sub&gt;/SL&lt;/sub&gt; 테크놀로지리뷰&lt;sub&gt;/NNG&lt;/sub&gt;&lt;/span&gt; 는&lt;sub&gt;/JX&lt;/sub&gt; 기술&lt;sub&gt;/NNG&lt;/sub&gt; 의&lt;sub&gt;/JKG&lt;/sub&gt; 발전&lt;sub&gt;/NNG&lt;/sub&gt; 이&lt;sub&gt;/JKS&lt;/sub&gt; 앞&lt;sub&gt;/NNG&lt;/sub&gt; 으로&lt;sub&gt;/JKB&lt;/sub&gt; 의&lt;sub&gt;/JKG&lt;/sub&gt; 전자&lt;sub&gt;/NNG&lt;/sub&gt; 기기&lt;sub&gt;/NNG&lt;/sub&gt; 시장&lt;sub&gt;/NNG&lt;/sub&gt; 에&lt;sub&gt;/JKB&lt;/sub&gt; 어떤&lt;sub&gt;/MM&lt;/sub&gt; 변화&lt;sub&gt;/NNG&lt;/sub&gt; 를&lt;sub&gt;/JKO&lt;/sub&gt; 가져오&lt;sub&gt;/VV&lt;/sub&gt; ᆯ지&lt;sub&gt;/EC&lt;/sub&gt; 에&lt;sub&gt;/JKB&lt;/sub&gt; 대하&lt;sub&gt;/VV&lt;/sub&gt; 어&lt;sub&gt;/EC&lt;/sub&gt; 깊이&lt;sub&gt;/MAG&lt;/sub&gt; 있&lt;sub&gt;/VV&lt;/sub&gt; 게&lt;sub&gt;/EC&lt;/sub&gt; 다루&lt;sub&gt;/VV&lt;/sub&gt; 고&lt;sub&gt;/EC&lt;/sub&gt; 있&lt;sub&gt;/VX&lt;/sub&gt; 다&lt;sub&gt;/EF&lt;/sub&gt; .&lt;sub&gt;/SF&lt;/sub&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p style=&quot;text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;이번에는 빈도정보는 고려하지 않는 chr와 빈도정보까지 고려한 chr_freq 간의 형태소 분석 결과 비교입니다.&amp;nbsp;동일한 OOV가 두 번 반복될때 어떻게 분석 결과가 달라지는지를 볼 수 있네요. chr에서는 잘개 쪼개는 OOV를 chr_freq에서는 통으로 묶어내는 비율이 늘어났습니다. 물론 chr_freq도 완벽하지는 않아서 앤디워홀이나 MIT테크놀로지리뷰의 사례처럼 첫번째는 '앤디 + 워홀'로 분할하고 두번째는 '앤디워홀'로 묶어서 분석하는 경우가 보입니다. 이는 각 단어가 위치한 문맥에 따라 '앤디 + 워홀'로 분석하는 게 언어 모델 상 확률이 높기도 하고 또 다른 경우에는 '앤디워홀'로 묶어서 분석하는게 확률이 높기도 해서 발생하는 현상입니다. 즉, 두 확률이 매우 근접한 값에서 서로 경쟁하고 있는 상황인데요 이 때문에 같은 형태의 단어를 입력해도 주변 문맥에 따라 분석 결과가 전혀 다르게 나타날 수 있어 아쉽게도 결과의 예측이 조금 떨어지고 있습니다.&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;이 때문에 chr_freq는 현재 선택 가능한 옵션으로만 두고 Kiwi v0.23.0에서는 빈도 정보는 고려하지 않더라도 결과가 일정하게 나오는 chr를 oov_handling의 기본값으로 채택했습니다. 빈도 정보를 활용하는 chr_freq은 텍스트가 바뀔 때마다 분석 결과가 요동칠 수 있는 반면 chr은 빈도 정보를 활용 못하더라도 예측성 있는 결과가 나오기 때문입니다.&amp;nbsp;확률값 경쟁 때문에 동일한 형태가 문맥에 따라 서로 다르게 분석되는 문제는 어떻게 개선할지 추가적으로 고민해볼 필요가 있습니다.&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;또 눈치 채셨을지도 모르지만, OOV에 일반명사 NNG를 태깅하는지 고유명사 NNP를 태깅하는지도 입력마다 제각각입니다. 사실 일반명사와 고유명사의 경계는 굉장히 모호하기도 하고 모델이 이를 정확히 예측하는게 불가능하기 때문에 다소 무작위적인 태깅 결과가 나오게 되는데 이를 하나로 일관성 있게 통일하는 작업도 필요한 상황입니다. 아니면 아예 OOV로 분석된 명사에는 별도의 태그를 할당하는게 나을지도 모르겠습니다. 추후 업데이트에서는 이 부분을 좀 더 고도화해봐야겠네요. 긴 글 읽어주셔서 감사드리며 다음에도 재미난 업데이트 들고 돌아오도록 하겠습니다.&lt;/p&gt;</description>
      <category>프로그래밍/NLP</category>
      <category>kiwi</category>
      <category>베이즈</category>
      <category>언어 모델</category>
      <category>형태소 분석기</category>
      <author>&amp;int;2tdt=t&amp;sup2;+c</author>
      <guid isPermaLink="true">https://bab2min.tistory.com/682</guid>
      <comments>https://bab2min.tistory.com/682#entry682comment</comments>
      <pubDate>Tue, 31 Mar 2026 16:26:42 +0900</pubDate>
    </item>
    <item>
      <title>형태소 분석기 Kiwi에 사투리 분석 기능 추가!</title>
      <link>https://bab2min.tistory.com/680</link>
      <description>&lt;p style=&quot;text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;드디어 Kiwi v0.22.0에서부터 아주 기초적이긴 하지만 그래도 한국어 사투리들을 분석할 수 있는 기능이 추가되었습니다. 이번 포스팅에서는 Kiwi에 방언 분석 기능을 추가한 방법과 그 과정을 간략하게 설명하고 현재 모델의 성능을 소개하고자 합니다.&lt;/p&gt;
&lt;h3 style=&quot;text-align: justify;&quot; data-ke-size=&quot;size23&quot;&gt;CoNg의 장점을 부각시키기 위한 도전&lt;/h3&gt;
&lt;p style=&quot;text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;사투리에 대한 형태소 분석을 시도한 것은 사실 Kiwi에 최근 적용된 신경망 기반의&amp;nbsp;&lt;a href=&quot;https://bab2min.tistory.com/676&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;CoNg 모델&lt;/a&gt;의 유연성을 입증하기 위함이었습니다. 신경망 모델은 고전적인 통계 모델보다 노이즈에 강건하고 학습 때 배우지 않았던 패턴에 대해서도 더 잘 작동하기 때문에 학습 데이터가 사실상 전무한 한국어 사투리에 대해서도 잘 작동할 수 있을 것이라고 추정해볼 수 있습니다.&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;즉 CoNg 모델이 기존의 통계 모델보다 사투리 분석에서 실제로 높은 성능을 보인다면 통계 모델에서 신경망 모델로 갈아탄 게 정말로 좋은 선택이었다는 걸 다시 한번 입증할 수 있는 것이지요. 그래서 제가 비록 한국어 사투리 0개국어 구사자이지만, 아무리 사투리라도 한국어인만큼 얼마나 어렵겠냐며 방언 형태소 분석기에 도전하게 되었습니다.&lt;/p&gt;
&lt;h3 style=&quot;text-align: justify;&quot; data-ke-size=&quot;size23&quot;&gt;아무리 사투리라도 한국어다&lt;/h3&gt;
&lt;p style=&quot;text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;그런데 도대체 사투리는 얼마나 표준어와 어떤 점이 얼마나 유사한 것이고 과연 언어 모델이 이 유사성을 활용할 수 있을까요? 일단 언어학에서 방언을 정의하는 데에 흔히 사용되는 기준은 &lt;b&gt;상호 의사 소통성&lt;/b&gt;입니다. 즉 &lt;b&gt;A언어와 B언어를 쓰는 화자가 별다른 노력 없이도 서로 의사소통을 하는데에 문제가 없다면 A와 B는 같은 언어에 속한 방언&lt;/b&gt;으로 묶어서 보는 것입니다. 정말 모호한 기준이지요. A언어 화자와 B언어 화자가 서로 의사소통을 하는데 지장이 없는 이유를 좀더 분석해보면 보통 다음과 같이 여러 언어학적 층위에서 유사성이 있기 때문입니다.&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;음운 체계&lt;/b&gt;: 두 언어에서 사용하는 음운들이 동일. 다만 음운이 실제 음성으로 실현되는 과정에서 나타나는 변이음의 양상이 다를 수 있음.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;형태론&lt;/b&gt;: 형태소를 다루는 규칙이 대체로 동일. 차이점이 있다면 일부 형태소의 표면형이 다르거나 종종 같은 대상을 가리키는 형태소가 아예 다른 경우가 있음.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;통사론(문법)&lt;/b&gt;: 문장을 구성하는 방법이 거의 동일. 어순을 비롯한 문법 시스템은 대체로 거의 동일함.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;핵심 어휘&lt;/b&gt;: 일상에서 자주 쓰이는 단어들이 거의 동일.&lt;/li&gt;
&lt;/ul&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;blockquote data-ke-style=&quot;style3&quot;&gt;벳복지를&amp;nbsp;두드리는&amp;nbsp;거&amp;nbsp;보난&amp;nbsp;하영&amp;nbsp;먹어진&amp;nbsp;셍이라.&lt;/blockquote&gt;
&lt;p style=&quot;text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;제주 사투리를 모르는 사람은 거의 이해할 수가 없습니다. 문법 구조나 조사, 어미 등의 핵심 어휘는 모두 동일하지만 사용하는 형태소가 너무 달라서 제주어 사전이 있지 않는 한 알수가 없죠. 그런데 다음과 같이 사전을 제공해준다면 한국어를 아는 사람은 이 문장을 쉽게 해석해낼 수 있게 됩니다.&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style3&quot;&gt;벳복지: 배때기&lt;br /&gt;-난: -니&lt;br /&gt;하영: 많이&lt;br /&gt;셍: 모양&lt;/blockquote&gt;
&lt;blockquote data-ke-style=&quot;style3&quot;&gt;&lt;b&gt;벳복지&lt;/b&gt;를 두드리는 거 보&lt;b&gt;난&lt;/b&gt; &lt;b&gt;하영&lt;/b&gt; 먹어진 &lt;b&gt;셍&lt;/b&gt;이라.&lt;br /&gt;&lt;b&gt;배때기&lt;/b&gt;를 두드리는 거 보&lt;b&gt;니&lt;/b&gt; &lt;b&gt;많이&lt;/b&gt; 먹은 &lt;b&gt;모양&lt;/b&gt;이야.&lt;/blockquote&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;이 특성을 이용하면 방언별 형태 분석 말뭉치가 전혀 없는 상황에서도 형태소 분석기를 만드는게 가능합니다. 잘 아시다시피 한국어의 경우 공개된 말뭉치의 규모가 크지 않고, &lt;b&gt;사투리로 들어가면 사실상 존재하는 말뭉치가 없다&lt;/b&gt;고 볼 수 있죠. 사투리용 형태소 분석기를 만들기 위해 방언 말뭉치를 대량으로 구축하는 건 개인이 감당할 수도 없고 배보다 배꼽이 훨씬 더 커지는 일입니다. 따라서 표준어를 기반으로 학습한 모델에 사투리 사전을 통해 사투리와 표준어 사이에 연결 다리를 놓아주는게 가장 현실적인 방안일 것입니다.&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;이미 Kiwi에는 표준어를 위한 언어 모델이 내장되어 있고, 사투리 - 표준어 연결다리는 국립국어원에서 편찬하고 있는 사전을 통해 확보할 수 있으니 일단 필요한 재료는 다 구할 수 있습니다. 이 정도면 충분히 도전해볼법한 과제라고 생각되지 않나요?&lt;/p&gt;
&lt;h3 style=&quot;text-align: justify;&quot; data-ke-size=&quot;size23&quot;&gt;생성 모델의 이점&lt;/h3&gt;
&lt;p style=&quot;text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;판별 모델(Discriminative Model) 다른 형태소 분석기들과는 다르게 Kiwi는 생성 모델(Generative Model)에 기반하고 있습니다. 두 모델은 사실 역할이 아예 다릅니다. &lt;b&gt;판별 모델의 경우 주어진 입력 데이터가 어떤 분류에 속할지 레이블을 매기는데에 집중&lt;/b&gt;한다면 &lt;b&gt;생성 모델은 주어진 데이터가 어떤 분포를 따르는지 찾고 그 분포를 최대한 비슷하게 학습&lt;/b&gt;하는 것이 목표입니다. 형태소 분석기는 결국 주어진 문자열에 어떤 품사 태그(&amp;rarr; 레이블)가 붙어야하는지를 판단하는 것이므로 전통적으로는 판별 모델(CRF를 주로 사용)을 이용해 구축했습니다. 그렇지만 생성 모델로도 형태소 분석을 풀 수 있습니다. 닭 잡는 데 소 칼을 쓰는 느낌이라서 다들 시도하지 않았던 것 뿐이지요.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;812&quot; data-origin-height=&quot;362&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/c3xUF2/dJMcacIxLFO/0qNGbK3e4wgDkZ9MBEvGPK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/c3xUF2/dJMcacIxLFO/0qNGbK3e4wgDkZ9MBEvGPK/img.png&quot; data-alt=&quot;판별 모델을 사용한 형태소 분석 과정&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/c3xUF2/dJMcacIxLFO/0qNGbK3e4wgDkZ9MBEvGPK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fc3xUF2%2FdJMcacIxLFO%2F0qNGbK3e4wgDkZ9MBEvGPK%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;400&quot; height=&quot;178&quot; data-origin-width=&quot;812&quot; data-origin-height=&quot;362&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;판별 모델을 사용한 형태소 분석 과정&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1140&quot; data-origin-height=&quot;396&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bW5ofN/dJMcabQnQlF/NIJrsXYpdfUDplk6kgQhjK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bW5ofN/dJMcabQnQlF/NIJrsXYpdfUDplk6kgQhjK/img.png&quot; data-alt=&quot;생성 모델을 사용한 형태소 분석 과정&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bW5ofN/dJMcabQnQlF/NIJrsXYpdfUDplk6kgQhjK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbW5ofN%2FdJMcabQnQlF%2FNIJrsXYpdfUDplk6kgQhjK%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;400&quot; height=&quot;139&quot; data-origin-width=&quot;1140&quot; data-origin-height=&quot;396&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;생성 모델을 사용한 형태소 분석 과정&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;구체적인 예시를 통해 살펴봅시다. 판별 모델을 기반으로 형태소 분석을 수행하는 건 간단히 보면 &quot;&lt;b&gt;아버지가방에들어...&lt;/b&gt;&quot;라는 입력 문자열이 있을 때 각 글자별로 품사 태그가 무엇일지(NNG, JKS, JKB, VV, EC 등), 형태소의 시작과 끝은 어디일지(B, I, O 등)를 판별하는 것과 동일합니다. 모델은 아버지라는 글자를 보고 이런 글자 패턴이 있을때는 주로 NNG로 판별했다는 걸 학습했단 사실을 떠올리고, 그 뒤에 있는 '가'는 JKS였다는 걸 떠올리는 식으로 본인이 배웠던 패턴을 바탕으로 각 글자에 알맞은 태그를 부여해줍니다.&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;생성 모델은 전혀 다른 과정으로 형태소 분석을 수행합니다. 일단 누군가가 입력된 문자열 &quot;&lt;b&gt;아버지가방에들어...&lt;/b&gt;&quot;를 잘게 쪼개어 형태소 배열로 가능한 조합들을 모두 찾아줍니다(보통은 사람이 직접 작성한 한국어 문법 규칙이 이 역할을 수행합니다). 생성 모델은 자신이 학습했던 한국어의 형태소 배열 분포를 바탕으로 &lt;b&gt;각 후보들이 한국어에서 얼마나 자연스러운지&lt;/b&gt;(한국어에서 해당 형태소 배열이 나올 확률이 얼마나 높은지) &lt;b&gt;순위를 매깁&lt;/b&gt;니다. 그리고 가장 자연스러운 배열이 되는 형태소 조합 1위를 찾아 최종 결과로 선택합니다. 설명을 들으면 알 수 있다시피 &lt;b&gt;생성 모델을 사용하는 쪽이 분석 절차도 더 복잡&lt;/b&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;그러나 사투리 분석 시나리오에서는 전세가 뒤집힙니다. &lt;b&gt;판별 모델에서는 사투리를 분석하려면 모델을 새로 학습&lt;/b&gt;해야만 합니다. 애초에 모델이 형태소 분석이라는 판별 작업에만 특화되어 있고, 특히 그 판별은 표준어만을 중심으로 학습되었기 때문입니다. 그래서 '&lt;b&gt;벳복지&lt;/b&gt;' 같은 단어가 나오면 이게 NNG라는 걸 알수가 없기에 모델이 이를 추가로 학습하지 않는 이상에는 제대로 분석하는게 어렵습니다.&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;b&gt;배때기&lt;/b&gt;'와 동일한 의미라는 걸 알려주면 이에 대해서도 &lt;b&gt;생성 모델은 자연스럽게 확률을 계산할 수가 있&lt;/b&gt;거든요. 따라서 '&lt;b&gt;배때기&lt;/b&gt;' 뒤에 '&lt;b&gt;두드리다&lt;/b&gt;/&lt;b&gt;부르다&lt;/b&gt;'가 자주 나온다는걸 알고 있는 생성 모델은 '&lt;b&gt;벳복지&lt;/b&gt;' 뒤에도 당연히 '&lt;b&gt;두드리다&lt;/b&gt;/&lt;b&gt;부르다&lt;/b&gt;' 등의 단어가 쓰일 수 있다는 걸 알 수 있습니다. 이런 모델의 지식은 사투리를 분석할 때 큰 도움이 됩니다.&lt;/p&gt;
&lt;h3 style=&quot;text-align: justify;&quot; data-ke-size=&quot;size23&quot;&gt;(참고) 사투리 사전 데이터 확보&lt;/h3&gt;
&lt;p style=&quot;text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;다양한 한국어 방언에서 쓰이는 형태소들과 그 형태소의 의미 정보를 확보하기 위해서 국립국어원에서 관리 중인&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;a style=&quot;color: #0070d1;&quot; href=&quot;https://opendict.korean.go.kr/main&quot;&gt;우리말샘 사전&lt;/a&gt;을 활용했습니다. 사전 전체의 데이터는&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;a style=&quot;color: #0070d1;&quot; href=&quot;https://opendict.korean.go.kr/member/memberDownloadList&quot;&gt;여기&lt;/a&gt;에서 JSON 혹은 XML 포맷으로 다운로드가 가능합니다.&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;1068&quot; data-origin-height=&quot;1098&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/Sd85S/dJMcafkW1SC/sIWPHYAota3fliHPqhtP5k/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/Sd85S/dJMcafkW1SC/sIWPHYAota3fliHPqhtP5k/img.png&quot; data-alt=&quot;우리말샘에서 제공하는 Raw 사전 데이터 예시&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/Sd85S/dJMcafkW1SC/sIWPHYAota3fliHPqhtP5k/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FSd85S%2FdJMcafkW1SC%2FsIWPHYAota3fliHPqhtP5k%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;1068&quot; height=&quot;1098&quot; data-origin-width=&quot;1068&quot; data-origin-height=&quot;1098&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;우리말샘에서 제공하는 Raw 사전 데이터 예시&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;위와 같이 JSON 타입으로 친절하게 데이터를 제공 중이므로 여기에서 사투리 형태소와 그에 대응하는 표준어 형태소를 추출하는 것은 아주 식은 죽 먹기죠.&lt;/p&gt;
&lt;h3 style=&quot;text-align: justify;&quot; data-ke-size=&quot;size23&quot;&gt;Kiwi의 방언 분석 기능 구현 방법&lt;/h3&gt;
&lt;p style=&quot;text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;구체적으로 Kiwi에서 방언 분석 기능을 구현하기 위해 사용한 방법은 다음과 같습니다.&lt;/p&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;표준어 형태분석 말뭉치 데이터를 이용하여 형태소 기반의 언어 모델을 확보&lt;/li&gt;
&lt;li&gt;사투리 사전 데이터 확보&lt;/li&gt;
&lt;li&gt;표준어 형태소의 임베딩 값을 가져와서 각 표준어에 대응하는 사투리 형태소들의 임베딩에 그대로 대입&lt;/li&gt;
&lt;li&gt;사투리&amp;nbsp;전용&amp;nbsp;형태소&amp;nbsp;결합&amp;nbsp;규칙&amp;nbsp;추가&amp;nbsp;기술&lt;/li&gt;
&lt;/ol&gt;
&lt;p style=&quot;text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;1번은 사실 기존에 표준어 기반 형태소 분석기를 개발할 때 한 것과 완전하게 동일한 작업입니다. 여기에 사투리 사전 데이터를 모으는 추가적인 작업을 수행한 것이구요, 가장 핵심적인 부분은 3번입니다. CoNg 모델에서 각 형태소의 의미를 표현하기 위해 적당한 크기의 벡터(여러 개의 숫자로 이뤄진 묶음)를 사용합니다. 이를 임베딩(Embedding)이라고 합니다. 의미적으로 유사한 단어들의 임베딩은 벡터 공간 상에서도 서로 가까이 위치하는 특징이 있죠. &lt;b&gt;사투리 형태소는 우리가 1번에서 모델을 학습시킬 때 데이터 내에 들어가 있지 않아 모델이 전혀 학습한 적이 없&lt;/b&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;사투리 형태소들의 너무 많아서 모델이 기억해야할 용량이 너무 커지진 않을까 걱정할 필요도 없습니다. 사실 표준어 형태소와 임베딩을 완전히 공유하기 때문에 사투리 형태소의 임베딩을 굳이 저장하지 않고 표준어를 참조하도록 시스템을 구현할 수 있습니다. 이렇게 하면 모델 크기를 전혀 키우지 않고도 사투리 분석 능력을 갖출 수 있습니다.&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;물론 이것만으로 모든게 해결되면 좋겠지만 사투리에 따라 독특한 어법 규칙을 가지는 경우가 있습니다. 이는 사람이 직접 기술해주는 게 빠르고 정확하죠. 그래서 마지막으로 4번에서 &lt;b&gt;결합 규칙을 수작업으로 추가하는 과정&lt;/b&gt;이 필요합니다. 사투리별 결합규칙에는 다음과 같은게 있습니다.&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style3&quot;&gt;&amp;lt;경상 방언&amp;gt; &lt;b&gt;고&lt;/b&gt; + &lt;b&gt;하&lt;/b&gt; -&amp;gt; &lt;b&gt;카&lt;/b&gt; (ex: 뭐라고 + 했는데 -&amp;gt; 뭐라캤는데)&lt;br /&gt;&amp;lt;충청 방언&amp;gt; &lt;b&gt;하&lt;/b&gt; + &lt;b&gt;어&lt;/b&gt; -&amp;gt; &lt;b&gt;혀&lt;/b&gt; (ex: 하 + 었슈 -&amp;gt; 혔)&lt;br /&gt;&amp;lt;제주 방언&amp;gt; &lt;b&gt;[ㅅㅈㅊ]&lt;/b&gt; + &lt;b&gt;으&lt;/b&gt; -&amp;gt; &lt;b&gt;[시지치]&lt;/b&gt; (ex: 하 + 엄ㅅ + 으니 -&amp;gt; 햄시니)&lt;/blockquote&gt;
&lt;p style=&quot;text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;이 규칙은 주어진 입력 텍스트에서 사투리에 알맞는 형태소 조합 후보를 찾는데에 쓰입니다. 모델에게 애초에 부적절한 후보를 순위에 넣지 않도록 할 수 있으므로 모델을 수정하지도 않고 분석 품질을 개선하는데에도 효과가 있죠. 전체 규칙 목록은 &lt;a href=&quot;https://github.com/bab2min/Kiwi/blob/v0.22.2/models/cong/base/combiningRule.txt&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;GitHub 저장소&lt;/a&gt;에서 살펴볼 수 있습니다.&lt;/p&gt;
&lt;h3 style=&quot;text-align: justify;&quot; data-ke-size=&quot;size23&quot;&gt;방언 분석 정확도 평가&lt;/h3&gt;
&lt;p style=&quot;text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;자 그래서 이렇게 구축한 사투리 분석 기능이 얼마나 정확한지 평가하는 게 필요하겠죠? 그래서 최첨단 Human Intelligence를 투입하여 9개 지역 방언에 대한 형태소 분석 평가 데이터를 구축했습니다. (사투리 하나도 모르는 와중에 데이터를 구축하려다 보니 나무위키 한국어 지역 방언 관련 문서도 여러 번 정독하고 사전도 열심히 찾으면서 공부했네요...) 사투리 정보와 마찬가지로 예문 역시 &lt;a href=&quot;https://opendict.korean.go.kr/&quot;&gt;우리말샘&lt;/a&gt;에서 제공하는 데이터에서 추출하였습니다. 평가 데이터가 구축된 방언 목록은 다음과 같고 데이터 전체는 &lt;a href=&quot;https://github.com/bab2min/Kiwi/tree/v0.22.2/eval_data/dialect&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;GitHub 저장소&lt;/a&gt;에서 살펴볼 수 있습니다. 가능하면 다 같은 개수로 분포를 맞추고 싶었으나 경기, 황해, 평안 방언은 예문이 적어서 살짝 평가 데이터의 크기가 작습니다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;chungcheong: 충청, 충남, 충북 (140문장)&lt;/li&gt;
&lt;li&gt;gangwon: 강원 (140문장)&lt;/li&gt;
&lt;li&gt;gyeonggi: 경기 (37문장)&lt;/li&gt;
&lt;li&gt;gyeongsang: 경상, 경북, 경남 (140문장)&lt;/li&gt;
&lt;li&gt;hamgyeong: 함경, 함남, 함북 (140문장)&lt;/li&gt;
&lt;li&gt;hwanghae: 황해 (105문장)&lt;/li&gt;
&lt;li&gt;jeolla: 전라, 전북, 전남 (140문장)&lt;/li&gt;
&lt;li&gt;jeju: 제주 (140문장)&lt;/li&gt;
&lt;li&gt;pyeongan: 평안, 평남, 평북 (41문장)&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 데이터로 평가한 결과는 다음과 같습니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1400&quot; data-origin-height=&quot;858&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/BwudT/dJMcagRGBux/z12l0kWJKLx2cHBvkzu6iK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/BwudT/dJMcagRGBux/z12l0kWJKLx2cHBvkzu6iK/img.png&quot; data-alt=&quot;Kiwi 및 다른 형태소 분석기의 방언별 분석 정확도&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/BwudT/dJMcagRGBux/z12l0kWJKLx2cHBvkzu6iK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FBwudT%2FdJMcagRGBux%2Fz12l0kWJKLx2cHBvkzu6iK%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;1400&quot; height=&quot;858&quot; data-origin-width=&quot;1400&quot; data-origin-height=&quot;858&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;Kiwi 및 다른 형태소 분석기의 방언별 분석 정확도&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;평가 척도는 F1으로 100%에 가까울수록 분석기의 분석 결과가 정답과 일치한다는 것이고 0%에 가까울수록 일치하는 게 없다는 뜻입니다&lt;/span&gt;&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;. &lt;/span&gt;&lt;span style=&quot;letter-spacing: 0px;&quot;&gt;Kiwi는 두 가지 모드로 평가를 진행했습니다. 기존 버전과 동일하게 표준어만 지원하는 분석 모드와 위에서 설명한 방언 분석 기능이 추가된 모드로요. &lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;letter-spacing: 0px;&quot;&gt;일단 Kiwi 외의 &lt;b&gt;나머지 분석기 중에서는 Khaiii의 성능이 제일 높은 것&lt;/b&gt;으로 나왔습니다. 그리고 Khaiii 성능은 표준어 모드인 Kiwi와 거의 유사합니다. 반면 방언 옵션이 활성화된 &lt;b&gt;Kiwi의 경우 타 분석기들과 20%p 이상의 격차를 보이며 높은 정확도&lt;/b&gt;를 달성한 것을 확인할 수 있습니다. 사실 타 분석기들은 방언에 대한 대응이 거의 없기 때문에 아주 공정한 비교라고 볼 수는 없습니다. 사투리가 포함된 텍스트를 일반적인 형태소 분석기로 분석할 때 오류가 얼마나 발생하는지를 보여주는 결과라고 받아들이는게 더 맞을 것 같습니다. &lt;/span&gt;&lt;span style=&quot;letter-spacing: 0px;&quot;&gt;방언 활성화 모드의 &lt;b&gt;Kiwi는 평균적으로 80%를 살짝 넘는 정확도&lt;/b&gt;를 보였지만 &lt;/span&gt;&lt;b&gt;&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;표준어 텍스트의 형태소 분석 정확도가&amp;nbsp;&lt;/span&gt;&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;일반적으로 &lt;/span&gt;&lt;/b&gt;&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;&lt;b&gt;95% 이상&lt;/b&gt;이 나온다는 점을 생각해볼 때, 방언 활성화 모드의 Kiwi도 여전히 갈 길이 멀다는 것을 알 수 있습니다.&lt;/span&gt;&lt;/p&gt;
&lt;h3 style=&quot;text-align: justify;&quot; data-ke-size=&quot;size23&quot;&gt;&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;분석 오류 사례 비교&lt;/span&gt;&lt;/h3&gt;
&lt;p style=&quot;text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;수치만 봐서는 감이 잘 안 오죠? 실제로 분석기들이 방언을 어떻게 분석했는지 오류 사례들을 살펴봅시다. 방언별로 한 문장 씩 뽑아보았습니다. Kiwi v0.21.0은 방언 기능이 추가되기 전, v0.22.2는 방언 기능이 추가된 이후입니다. 심각하게 틀린 사례들에 대해서는 붉은 색으로 칠해보았습니다.&lt;/p&gt;
&lt;div&gt;
&lt;style&gt;
  .incorrect {background-color:#ffefef;} 
  .correct {background-color:#f0ffff;}
&lt;/style&gt;
&lt;/div&gt;
&lt;table class=&quot;std-table&quot; data-ke-align=&quot;alignLeft&quot;&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;th rowspan=&quot;6&quot;&gt;&amp;lt;충청&amp;gt; 쌀이서 뉘를 골러내야 되는 겨. &lt;br /&gt;(쌀에서 뉘를 골라내야 되는 거야.)&lt;/th&gt;
&lt;th class=&quot;&quot;&gt;정답&lt;/th&gt;
&lt;td class=&quot;&quot;&gt;쌀/N 이서/J 뉘/N 를/J 골러내/VV 어야/E 되/VV 는/E 기/N 이/VC 여/E ./S&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;th class=&quot;&quot;&gt;Kiwi v0.21.0&lt;/th&gt;
&lt;td class=&quot;&quot;&gt;쌀/N &lt;span class=&quot;incorrect&quot;&gt;이/J 서/M&lt;/span&gt; 뉘/N 를/J &lt;span class=&quot;incorrect&quot;&gt;골/VV 러/E 내/VX&lt;/span&gt; 어야/E 되/VV 는/E &lt;span class=&quot;incorrect&quot;&gt;겨/N&lt;/span&gt; ./S&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;th class=&quot;&quot;&gt;&lt;a href=&quot;https://kiwi.bab2min.pe.kr/?q=%EC%8C%80%EC%9D%B4%EC%84%9C%20%EB%89%98%EB%A5%BC%20%EA%B3%A8%EB%9F%AC%EB%82%B4%EC%95%BC%20%EB%90%98%EB%8A%94%20%EA%B2%A8.&amp;amp;dialects=chungcheong&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;Kiwi v0.22.2&lt;/a&gt;&lt;/th&gt;
&lt;td class=&quot;&quot;&gt;쌀/N 이서/J 뉘/N 를/J 골러내/VV 어야/E 되/VV 는/E 기/N 이/VC 어/E ./S&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;th class=&quot;&quot;&gt;Komoran&lt;/th&gt;
&lt;td class=&quot;&quot;&gt;쌀/N 이서/J 뉘/N 를/J &lt;span class=&quot;incorrect&quot;&gt;골/N 러/N 내야/N&lt;/span&gt; 되/VV 는/E &lt;span class=&quot;incorrect&quot;&gt;겨/N&lt;/span&gt; ./S&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;th class=&quot;&quot;&gt;Mecab&lt;/th&gt;
&lt;td class=&quot;&quot;&gt;쌀/N &lt;span class=&quot;incorrect&quot;&gt;이/VC 서/E&lt;/span&gt; 뉘/N 를/J 고르/VV 어/E 내/VX 야/E 되/VV 는/E 것/N 이/VC 여/E ./S&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;th class=&quot;&quot;&gt;Khaiii&lt;/th&gt;
&lt;td class=&quot;&quot;&gt;쌀/N 이서/J 뉘/N 를/J 고르/VV 어/E 내/VV 어야/E 되/VV 는/E 것/N 이/VC 여/E ./S&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;th rowspan=&quot;6&quot;&gt;&amp;lt;강원&amp;gt; 콩국시가 아주 걸찌한 기 맛이 참 고만이네. &lt;br /&gt;(콩국수가 아주 걸쭉한 게 맛이 참 고만이네.)&lt;/th&gt;
&lt;th class=&quot;&quot;&gt;정답&lt;/th&gt;
&lt;td class=&quot;&quot;&gt;콩국시/N 가/J 아주/M 걸찌/X 하/X ᆫ/E 기/N 이/J 맛/N 이/J 참/M 고만/M 이/VC 네/E ./S&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;th class=&quot;&quot;&gt;Kiwi v0.21.0&lt;/th&gt;
&lt;td class=&quot;&quot;&gt;콩/N 국시/N 가/J 아주/M 걸찌/N 하/X ᆫ/E 기/N 맛/N 이/J 참/M 고만/M 이/VC 네/E ./S&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;th class=&quot;&quot;&gt;&lt;a href=&quot;https://kiwi.bab2min.pe.kr/?q=%EC%BD%A9%EA%B5%AD%EC%8B%9C%EA%B0%80%20%EC%95%84%EC%A3%BC%20%EA%B1%B8%EC%B0%8C%ED%95%9C%20%EA%B8%B0%20%EB%A7%9B%EC%9D%B4%20%EC%B0%B8%20%EA%B3%A0%EB%A7%8C%EC%9D%B4%EB%84%A4.&amp;amp;dialects=gangwon&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;Kiwi v0.22.2&lt;/a&gt;&lt;/th&gt;
&lt;td class=&quot;&quot;&gt;콩국시/N 가/J 아주/M 걸찌하/VA ᆫ/E 기/N 맛/N 이/J 참/M 고만/M 이/VC 네/E ./S&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;th class=&quot;&quot;&gt;Komoran&lt;/th&gt;
&lt;td class=&quot;&quot;&gt;콩/N 국시/N 가/J 아주/M &lt;span class=&quot;incorrect&quot;&gt;걸/N 찌/N&lt;/span&gt; 하/X ㄴ/E 기/N 맛/N 이/J 참/M 고만/M 이/VC 네/E ./S&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;th class=&quot;&quot;&gt;Mecab&lt;/th&gt;
&lt;td class=&quot;&quot;&gt;&lt;span class=&quot;incorrect&quot;&gt;콩국/N 시/N&lt;/span&gt; 가/J 아주/M &lt;span class=&quot;incorrect&quot;&gt;걸/VV ᆯ/E 찌/N&lt;/span&gt; 하/X ᆫ/E 기/N 맛/N 이/J 참/M 고만/N 이/VC 네/E ./S&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;th class=&quot;&quot;&gt;Khaiii&lt;/th&gt;
&lt;td class=&quot;&quot;&gt;콩국시/N 가/J 아주/M 걸찌/M 하/VV ㄴ/E 기/N 맛/N 이/J 참/M 고만/N 이/VC 네/E ./S&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;th rowspan=&quot;6&quot;&gt;&amp;lt;경상&amp;gt; 공빼기 좋아하면 머리 버진다 카드라. &lt;br /&gt;(공짜 좋아하면 머리 벗어진다고 하더라.)&lt;/th&gt;
&lt;th class=&quot;&quot;&gt;정답&lt;/th&gt;
&lt;td class=&quot;&quot;&gt;공빼기/N 좋아하/VV 면/E 머리/N 버지/VV ᆫ다고/E 하/VV 드라/E ./S&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;th class=&quot;&quot;&gt;Kiwi v0.21.0&lt;/th&gt;
&lt;td class=&quot;&quot;&gt;&lt;span class=&quot;incorrect&quot;&gt;공/N 빼/VV 기/E&lt;/span&gt; 좋아하/VV 면/E 머리/N &lt;span class=&quot;incorrect&quot;&gt;버진/N 다/M 카드/N 이/VC 라/E&lt;/span&gt; ./S&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;th class=&quot;&quot;&gt;&lt;a href=&quot;https://kiwi.bab2min.pe.kr/?q=%EA%B3%B5%EB%B9%BC%EA%B8%B0%20%EC%A2%8B%EC%95%84%ED%95%98%EB%A9%B4%20%EB%A8%B8%EB%A6%AC%20%EB%B2%84%EC%A7%84%EB%8B%A4%20%EC%B9%B4%EB%93%9C%EB%9D%BC.&amp;amp;dialects=gyeongsang&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;Kiwi v0.22.2&lt;/a&gt;&lt;/th&gt;
&lt;td class=&quot;&quot;&gt;공빼기/N 좋아하/VV 면/E 머리/N 이/J 버지/VV ᆫ다고/E 하/VV 드라/E ./S&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;th class=&quot;&quot;&gt;Komoran&lt;/th&gt;
&lt;td class=&quot;&quot;&gt;&lt;span class=&quot;incorrect&quot;&gt;공/N 빼기/N&lt;/span&gt; 좋아하/VV 면/E 머리/N &lt;span class=&quot;incorrect&quot;&gt;버진/N 다/J 카드/N 이/VC 라/E&lt;/span&gt; ./S&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;th class=&quot;&quot;&gt;Mecab&lt;/th&gt;
&lt;td class=&quot;&quot;&gt;&lt;span class=&quot;incorrect&quot;&gt;공/N 빼/VV 기/E&lt;/span&gt; 좋/VA 아/E 하/VV 면/E 머리/N &lt;span class=&quot;incorrect&quot;&gt;벌/VV 지/E ᆫ다/E 카드/N 이/VC 라/E&lt;/span&gt; ./S&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;th class=&quot;&quot;&gt;Khaiii&lt;/th&gt;
&lt;td class=&quot;&quot;&gt;공빼기/N 좋아하/VV 면/E 머리/N &lt;span class=&quot;incorrect&quot;&gt;버/VV 진/VC 다/E 카드/N 라/E&lt;/span&gt; ./S&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;th rowspan=&quot;6&quot;&gt;&amp;lt;함경&amp;gt; 명이 길댸닣구 따른 사름이 잇갯습지. &lt;br /&gt;(명이 길지 않고 짧은 사람이 있겠지요.)&lt;/th&gt;
&lt;th class=&quot;&quot;&gt;정답&lt;/th&gt;
&lt;td class=&quot;&quot;&gt;명/N 이/J 길/VA 디/E 애닣/VX 구/E 따르/VA ᆫ/E 사름/N 이/J 있/VA 갯/E 습지/E ./S&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;th class=&quot;&quot;&gt;Kiwi v0.21.0&lt;/th&gt;
&lt;td class=&quot;&quot;&gt;&lt;span class=&quot;incorrect&quot;&gt;명이/N 길댸닣구/N 따르/VV&lt;/span&gt; ᆫ/E &lt;span class=&quot;incorrect&quot;&gt;사르/VV ᆷ/E&lt;/span&gt; 이/J &lt;span class=&quot;incorrect&quot;&gt;잇/VV 개/VV 엇/E 습/N 지/E&lt;/span&gt; ./S&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;th class=&quot;&quot;&gt;&lt;a href=&quot;https://kiwi.bab2min.pe.kr/?q=%EB%AA%85%EC%9D%B4%20%EA%B8%B8%EB%8C%B8%EB%8B%A3%EA%B5%AC%20%EB%94%B0%EB%A5%B8%20%EC%82%AC%EB%A6%84%EC%9D%B4%20%EC%9E%87%EA%B0%AF%EC%8A%B5%EC%A7%80.&amp;amp;dialects=hamgyeong&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;Kiwi v0.22.2&lt;/a&gt;&lt;/th&gt;
&lt;td class=&quot;&quot;&gt;명/N 이/J 길/VA 디/E 애닣/VX 구/E 따르/VA ᆫ/E 사름/N 이/J &lt;span class=&quot;incorrect&quot;&gt;잇/VV 개/VV 엇/E&lt;/span&gt; 습지/E ./S&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;th class=&quot;&quot;&gt;Komoran&lt;/th&gt;
&lt;td class=&quot;&quot;&gt;명/N 이/J &lt;span class=&quot;incorrect&quot;&gt;길댸닣구/N 따르/VV&lt;/span&gt; ㄴ/E &lt;span class=&quot;incorrect&quot;&gt;사르/VV ㅁ/E&lt;/span&gt; 이/J &lt;span class=&quot;incorrect&quot;&gt;잇갯습지./N&lt;/span&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;th class=&quot;&quot;&gt;Mecab&lt;/th&gt;
&lt;td class=&quot;&quot;&gt;명/N 이/J 길/VA &lt;span class=&quot;incorrect&quot;&gt;ᆯ/E 댸닣구/U 따르/VV&lt;/span&gt; ᆫ/E 사름/N 이/J &lt;span class=&quot;incorrect&quot;&gt;잇/N 갯/N&lt;/span&gt; 습지/E ./S&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;th class=&quot;&quot;&gt;Khaiii&lt;/th&gt;
&lt;td class=&quot;&quot;&gt;명/N 이/J &lt;span class=&quot;incorrect&quot;&gt;길/N 댸닣/X&lt;/span&gt; 구/E &lt;span class=&quot;incorrect&quot;&gt;따르/VV&lt;/span&gt; ㄴ/E 사름/N 이/J &lt;span class=&quot;incorrect&quot;&gt;잇갯습지/N&lt;/span&gt; ./S&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;th rowspan=&quot;6&quot;&gt;&amp;lt;황해&amp;gt; 난 동구막질하고 싶은 마엄이 쌸도 없어야. &lt;br /&gt;(난 소꿉놀이하고 싶은 마음이 조금도 없어.)&lt;/th&gt;
&lt;th class=&quot;&quot;&gt;정답&lt;/th&gt;
&lt;td class=&quot;&quot;&gt;나/N ᆫ/J 동구막질/N 하/X 고/E 싶/VX 은/E 마엄/N 이/J 쌸/N 도/J 없/VA 어야/E ./S&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;th class=&quot;&quot;&gt;Kiwi v0.21.0&lt;/th&gt;
&lt;td class=&quot;&quot;&gt;나/N ᆫ/J &lt;span class=&quot;incorrect&quot;&gt;동구/N 막/N 질/X&lt;/span&gt; 하/X 고/E 싶/VX 은/E 마엄/N 이/J 쌸/N 도/J 없/VA 어야/E ./S&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;th class=&quot;&quot;&gt;&lt;a href=&quot;https://kiwi.bab2min.pe.kr/?q=%EB%82%9C%20%EB%8F%99%EA%B5%AC%EB%A7%89%EC%A7%88%ED%95%98%EA%B3%A0%20%EC%8B%B6%EC%9D%80%20%EB%A7%88%EC%97%84%EC%9D%B4%20%EC%8C%B8%EB%8F%84%20%EC%97%86%EC%96%B4%EC%95%BC.&amp;amp;dialects=hwanghae&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;Kiwi v0.22.2&lt;/a&gt;&lt;/th&gt;
&lt;td class=&quot;&quot;&gt;나/N ᆫ/J &lt;span class=&quot;incorrect&quot;&gt;동구/N 막/N 질/X&lt;/span&gt; 하/X 고/E 싶/VX 은/E &lt;span class=&quot;incorrect&quot;&gt;마/M 엄/X&lt;/span&gt; 이/J 쌸/N 도/J 없/VA 어야/E ./S&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;th class=&quot;&quot;&gt;Komoran&lt;/th&gt;
&lt;td class=&quot;&quot;&gt;&lt;span class=&quot;incorrect&quot;&gt;나/VV ㄴ/E 동구/N 막/N 질/X&lt;/span&gt; 하/X 고/E 싶/VX 은/E &lt;span class=&quot;incorrect&quot;&gt;마/N 엄/N&lt;/span&gt; 이/J &lt;span class=&quot;incorrect&quot;&gt;쌸도/N&lt;/span&gt; 없/VA 어야/E ./S&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;th class=&quot;&quot;&gt;Mecab&lt;/th&gt;
&lt;td class=&quot;&quot;&gt;나/N ᆫ/J 동구막질/N 하/X 고/E 싶/VX 은/E &lt;span class=&quot;incorrect&quot;&gt;마/N 엄이/N 쌸도/U&lt;/span&gt; 없/VA 어야/E ./S&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;th class=&quot;&quot;&gt;Khaiii&lt;/th&gt;
&lt;td class=&quot;&quot;&gt;나/N ㄴ/J &lt;span class=&quot;incorrect&quot;&gt;동구/N 막/N 질/X&lt;/span&gt; 하/X 고/E 싶/VX 은/E 마엄/N 이/J 쌸/N 도/J 없/VA 어야/E ./S&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;th rowspan=&quot;6&quot;&gt;&amp;lt;제주&amp;gt; 항에 물 거둑고 ᄒᆞ영 지뻐 감쭈. &lt;br /&gt;(항아리에 물 차고 해서 기뻐서 가지요.)&lt;/th&gt;
&lt;th class=&quot;&quot;&gt;정답&lt;/th&gt;
&lt;td class=&quot;&quot;&gt;항/N 에/J 물/N 거둑/VV 고/E ᄒᆞ/VX 엉/E 지쁘/VA 어/E 가/VV 어ᇝ/E 주/E ./S&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;th class=&quot;&quot;&gt;Kiwi v0.21.0&lt;/th&gt;
&lt;td class=&quot;&quot;&gt;항/N 에/J 물/N &lt;span class=&quot;incorrect&quot;&gt;거둑고/N ᄒᆞ영/N 지뻐/N 감쭈/N&lt;/span&gt; ./S&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;th class=&quot;&quot;&gt;&lt;a href=&quot;https://kiwi.bab2min.pe.kr/?q=%ED%95%AD%EC%97%90%20%EB%AC%BC%20%EA%B1%B0%EB%91%91%EA%B3%A0%20%E1%84%92%E1%86%9E%EC%98%81%20%EC%A7%80%EB%BB%90%20%EA%B0%90%EC%AD%88.&amp;amp;dialects=jeju&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;Kiwi v0.22.2&lt;/a&gt;&lt;/th&gt;
&lt;td class=&quot;&quot;&gt;항/N 에/J 물/N 거둑/VV 고/E &lt;span class=&quot;incorrect&quot;&gt;ᄒᆞ/VV&lt;/span&gt; 엉/E 지쁘/VA 어/E 가/VV 어ᇝ/E 주/E ./S&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;th class=&quot;&quot;&gt;Komoran&lt;/th&gt;
&lt;td class=&quot;&quot;&gt;항/N 에/J 물/N &lt;span class=&quot;incorrect&quot;&gt;거/N 둑/N 이/VC&lt;/span&gt; 고/E &lt;span class=&quot;incorrect&quot;&gt;ᄒᆞ영/N 지뻐/N 감쭈./N&lt;/span&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;th class=&quot;&quot;&gt;Mecab&lt;/th&gt;
&lt;td class=&quot;&quot;&gt;항/N 에/J &lt;span class=&quot;incorrect&quot;&gt;물/VV ᆯ/E 거/N 둑/N 이/VC&lt;/span&gt; 고/E ᄒ&lt;span class=&quot;incorrect&quot;&gt;ᆞ영/U 지/VX 뻐/U 가/VV ᄆ/E 쭈/U&lt;/span&gt; ./S&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;th class=&quot;&quot;&gt;Khaiii&lt;/th&gt;
&lt;td class=&quot;&quot;&gt;항/N 에/J 물/N &lt;span class=&quot;incorrect&quot;&gt;거둑고/N ᄒ/N ᆞ/S 영/N 지/VA 쁘/VV 어/E 감/VV 쭈/E&lt;/span&gt; ./S&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;th rowspan=&quot;6&quot;&gt;&amp;lt;전라&amp;gt; 어지께 닥이 흑을 헤번지르고 구녁을 파드랑께. &lt;br /&gt;(어저께 닭이 흙을 헤치고 구멍을 파더라니까.)&lt;/th&gt;
&lt;th class=&quot;&quot;&gt;정답&lt;/th&gt;
&lt;td class=&quot;&quot;&gt;어지께/N 닥/N 이/J 흑/N 을/J 헤번지르/VV 고/E 구녁/N 을/J 파/VV 드랑께/E ./S&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;th class=&quot;&quot;&gt;Kiwi v0.21.0&lt;/th&gt;
&lt;td class=&quot;&quot;&gt;어지께/N &lt;span class=&quot;incorrect&quot;&gt;닥이/N&lt;/span&gt; 흑/N 을/J &lt;span class=&quot;incorrect&quot;&gt;헤/VV 어/E 번/N 지르/VV&lt;/span&gt; 고/E 구녁/N 을/J 파/N &lt;span class=&quot;incorrect&quot;&gt;드/N 이/VC 랑께/E&lt;/span&gt; ./S&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;th class=&quot;&quot;&gt;&lt;a href=&quot;https://kiwi.bab2min.pe.kr/?q=%EC%96%B4%EC%A7%80%EA%BB%98%20%EB%8B%A5%EC%9D%B4%20%ED%9D%91%EC%9D%84%20%ED%97%A4%EB%B2%88%EC%A7%80%EB%A5%B4%EA%B3%A0%20%EA%B5%AC%EB%85%81%EC%9D%84%20%ED%8C%8C%EB%93%9C%EB%9E%91%EA%BB%98.&amp;amp;dialects=jeolla&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;Kiwi v0.22.2&lt;/a&gt;&lt;/th&gt;
&lt;td class=&quot;&quot;&gt;어지께/M 닥/N 이/J 흑/N 을/J 헤번지르/VV 고/E 구녁/N 을/J 파/VV 드라/E ᆼ께/E ./S&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;th class=&quot;&quot;&gt;Komoran&lt;/th&gt;
&lt;td class=&quot;&quot;&gt;&lt;span class=&quot;incorrect&quot;&gt;어/N 지/N 께/J 다고/E 학/N&lt;/span&gt; 이/J 흑/N 을/J &lt;span class=&quot;incorrect&quot;&gt;헤/VV 벌/VV ㄴ지/E 르/N 이/VC&lt;/span&gt; 고/E &lt;span class=&quot;incorrect&quot;&gt;구녁을/N 파드랑/N 께/J&lt;/span&gt; ./S&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;th class=&quot;&quot;&gt;Mecab&lt;/th&gt;
&lt;td class=&quot;&quot;&gt;&lt;span class=&quot;incorrect&quot;&gt;어/I 지께/N&lt;/span&gt; 닥/N 이/J 흑/N 을/J &lt;span class=&quot;incorrect&quot;&gt;하/VV 아/E 번/N 지르/VV&lt;/span&gt; 고/E 구녁/N 을/J 파/VV &lt;span class=&quot;incorrect&quot;&gt;어/E 들/VV 랑께/E&lt;/span&gt; ./S&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;th class=&quot;&quot;&gt;Khaiii&lt;/th&gt;
&lt;td class=&quot;&quot;&gt;&lt;span class=&quot;incorrect&quot;&gt;어/N 지/N 께/M&lt;/span&gt; 닥/N 이/J 흑/N 을/J 헤번지르/VV 고/E 구녁/N 을/J &lt;span class=&quot;incorrect&quot;&gt;파/N 드/VV&lt;/span&gt; 랑께/E ./S&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;th rowspan=&quot;6&quot;&gt;&amp;lt;평안&amp;gt; 닛못이 아푸문 고상이 막심합네다. &lt;br /&gt;(잇몸이 아프면 고생이 막심합니다.)&lt;/th&gt;
&lt;th class=&quot;&quot;&gt;정답&lt;/th&gt;
&lt;td class=&quot;&quot;&gt;닛못/N 이/J 아푸/VA 문/E 고상/N 이/J 막심/N 하/X ᆸ네다/E ./S&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;th class=&quot;&quot;&gt;Kiwi v0.21.0&lt;/th&gt;
&lt;td class=&quot;&quot;&gt;닛못/N 이/J 아푸/VA 문/N 고상/N 이/J 막심/N 하/X &lt;span class=&quot;incorrect&quot;&gt;ᆸ네/E 이/VC 다/E&lt;/span&gt; ./S&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;th class=&quot;&quot;&gt;&lt;a href=&quot;https://kiwi.bab2min.pe.kr/?q=%EB%8B%9B%EB%AA%BB%EC%9D%B4%20%EC%95%84%ED%91%B8%EB%AC%B8%20%EA%B3%A0%EC%83%81%EC%9D%B4%20%EB%A7%89%EC%8B%AC%ED%95%A9%EB%84%A4%EB%8B%A4.&amp;amp;dialects=pyeongan&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;Kiwi v0.22.2&lt;/a&gt;&lt;/th&gt;
&lt;td class=&quot;&quot;&gt;닛못/N 이/J 아푸/VA 문/E 고상/N 이/J 막심/N 하/X ᆸ네다/E ./S&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;th class=&quot;&quot;&gt;Komoran&lt;/th&gt;
&lt;td class=&quot;&quot;&gt;&lt;span class=&quot;incorrect&quot;&gt;닛못이/N 아/I 푸/VV 물/VV ㄴ/E&lt;/span&gt; 고상/N 이/J 막심/N 하/X ㅂ네다/E ./S&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;th class=&quot;&quot;&gt;Mecab&lt;/th&gt;
&lt;td class=&quot;&quot;&gt;&lt;span class=&quot;incorrect&quot;&gt;닛못이/U&lt;/span&gt; 아프/VA &lt;span class=&quot;incorrect&quot;&gt;우/E 물/VV ᆫ/E&lt;/span&gt; 고상/N 이/J 막심/N 하/X ᄇ네다/E ./S&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;th class=&quot;&quot;&gt;Khaiii&lt;/th&gt;
&lt;td class=&quot;&quot;&gt;&lt;span class=&quot;incorrect&quot;&gt;닛/N 못/N&lt;/span&gt; 이/J &lt;span class=&quot;incorrect&quot;&gt;아푸/M 문/N&lt;/span&gt; 고상/N 이/J &lt;span class=&quot;incorrect&quot;&gt;막/N 심/X&lt;/span&gt; 하/X ㅂ네다/E ./S&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p style=&quot;text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;확실히 표준어 중심의 형태소 분석기들은 사투리에서만 쓰이는 형태소들을 거의 인식하지 못하고 엉뚱하게 오분석하는 경우가 많다는 것을 확인할 수 있습니다. 제주 사투리의 경우 아래아와 ㅯ 받침에 대한 처리도 추가로 필요한데 이는 시스템 차원에서 지원이 필요한 부분입니다. 다행히도 Kiwi에서는 옛한글 지원이 추가되었기 때문에 이런 문자열도 문제 없이 분석해낼 수 있습니다.&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;Kiwi v0.22.2 셀마다 &lt;a href=&quot;https://kiwi.bab2min.pe.kr/&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;데모 페이지 링크&lt;/a&gt;를 걸어두었으니 들어가서 더 자세한 분석 결과를 살펴보실 수 있습니다.&lt;/p&gt;
&lt;h3 style=&quot;text-align: justify;&quot; data-ke-size=&quot;size23&quot;&gt;앞으로 나아가야할 길&lt;/h3&gt;
&lt;p style=&quot;text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;Kiwi는 0.22.0버전에서 방언 분석 기능을 추가하면서 표준어 언어 모델과 사투리 사전, 사투리 전용 형태소 결합 규칙만을 통해 실제로 사투리 분석이 어느 정도 가능하다는 것을 입증했습니다. 물론 아직 정확도가 80% 정도 밖에 되지 않아서 믿고 사용하기에는 부족한 점이 있습니다. 현재 Kiwi의 사투리 분석 오류는 크게 세 가지 요인에 의해 발생합니다.&lt;/p&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;사투리는 표준화된 맞춤법이 없기에 같은 형태소라도 사람마다 적는 방식이 다양함&lt;/li&gt;
&lt;li&gt;사투리&amp;nbsp;전용&amp;nbsp;형태소&amp;nbsp;결합&amp;nbsp;규칙이&amp;nbsp;아직&amp;nbsp;완전하지&amp;nbsp;못함&lt;/li&gt;
&lt;li&gt;아직 사전에 등재되지 않은 사투리 단어들이 제법 있음&lt;/li&gt;
&lt;/ol&gt;
&lt;p style=&quot;text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;1번 요인은 오타 교정과도 어느 정도 맞닿아있는 부분이 있습니다. 표준화된 표기법이 없기 때문에 발음을 소리나는 대로 적는 경우가 제법 많아서 오타 교정 기능을 통해 어느 정도 개선이 가능할 것으로 예상하고 있습니다. 다만 현재 Kiwi 분석기 구조 상 오타 교정을 모든 방언 형태소에 적용하면 메모리 사용량이 너무 커지는 문제가 있어서 &lt;b&gt;시스템이 메모리를 효율적으로 사용하면서 오타 교정을 할 수 있도록 개선하는 작업이 선행&lt;/b&gt;되어야 합니다.&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;2번은 결합 규칙 자체가 다양한 사투리 문장들을 제가 대충 살펴보면서 주먹구구식으로 만든 것이기에 발생하는 현상입니다. 체계적으로 사투리 문장들을 모으고 거기서 발생하는 표면형의 변동 &amp;amp; 결합 현상을 기술하는 작업이 필요한데 이 작업에는 &lt;b&gt;방언을 전문적으로 연구하는 언어학자가 필요&lt;/b&gt;할 것 같아서 일단은 미뤄두고 있습니다.&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;마지막으로 3번은 몇 명의 개인이 해결할 수 있는 수준의 문제는 아니라서 &lt;b&gt;실제 사투리를 구사하는 화자분들이나 연구자들의 집단적인 도움&lt;/b&gt;이 필요한 상황입니다. 이렇게 세 가지 문제가 해결이 된다면 Kiwi의 사투리 분석 정확도도 90% 이상으로 개선되어 다양한 분야에서 유용하게 쓰일 수 있겠죠~! 저는 1번 문제를 열심히 해결하고 있을테니 전국의 방언학 연구자, 그리고 다양한 (네이티브) 사투리 구사자 분들께서는 2, 3번 문제를 맡아주시면 정말 좋겠네요.&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;앞으로도 Kiwi에 대해 많은 관심과 사랑 부탁드리고 또 이렇게 두서 없는 포스팅 읽어주셔서 감사합니다! 새해복 많이 받으세요! 2026년에도 다양한 업데이트를 들고 돌아오도록 하겠습니다.&lt;/p&gt;</description>
      <category>프로그래밍/NLP</category>
      <category>CONG</category>
      <category>kiwi</category>
      <category>언어 모델</category>
      <category>형태소 분석기</category>
      <author>&amp;int;2tdt=t&amp;sup2;+c</author>
      <guid isPermaLink="true">https://bab2min.tistory.com/680</guid>
      <comments>https://bab2min.tistory.com/680#entry680comment</comments>
      <pubDate>Tue, 30 Dec 2025 00:30:31 +0900</pubDate>
    </item>
    <item>
      <title>형태소 분석기 Kiwi CoNg (4/4): 속도 최적화</title>
      <link>https://bab2min.tistory.com/679</link>
      <description>&lt;div&gt;
&lt;style&gt;.std-table th {font-size:85%} .std-table td {font-size:85%} &lt;/style&gt;
&lt;/div&gt;
&lt;p style=&quot;background-color: #ffffff; color: #000000; text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;지난 포스팅에서는 CoNg 모델이 실제로 형태소 분석을 비롯한 모호성 해소, 문장 분리 등의 과제에서 높은 성능을 보이며 Kiwi에 들어갈 차세대 언어 모델로서의 자격이 충분히 있다는 것을 확인했습니다. 이제 남은 것은 코드를 실제로 짜서 CoNg 모델이 Kiwi 안에서 빠르고 정확하게 잘 돌아가도록 만들기만 하면 됩니다.(참 쉽죠?) 근데 제일 처음 신경망 모델을 도입하기로 결심했을때 설정했던 조건이 있었습니다. 그래서 사실 그냥 잘 돌아가는게 아니라 아래의 두 조건을 만족시키면서 돌아가야합니다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc; text-align: start;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li style=&quot;list-style-type: disc;&quot;&gt;모델 크기는 100MB이내.&lt;/li&gt;
&lt;li style=&quot;list-style-type: disc;&quot;&gt;속도는 현재 KnLM 기반의 분석기와 유사할 것. 혹시나 느려지더라도 1.5배 이상 느려지면 안됨.&lt;/li&gt;
&lt;/ul&gt;
&lt;p style=&quot;text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;둘다 최적화와 관련된 문제가 되겠네요. 전자는 크기 최적화, 후자는 속도 최적화입니다. 둘을 동시에 해결하려면 머리가 아프니 저는 실제 개발 시에는 일단 각개격파로 먼저 속도 최적화를 수행하고 그 이후 모델의 용량을 줄이는 방식으로 접근했는데요, 이번 포스팅에서도 그때와 마찬가지의 순서로 설명을 진행하도록 하겠습니다.&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #000000; text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;Kiwi CoNg 포스팅 시리즈&lt;/p&gt;
&lt;ol style=&quot;list-style-type: decimal; text-align: start;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li style=&quot;list-style-type: decimal;&quot;&gt;&lt;a href=&quot;https://bab2min.tistory.com/676&quot;&gt;형태소 분석기 Kiwi CoNg (1/4): 신경망 모델 도입기&lt;/a&gt;&lt;/li&gt;
&lt;li style=&quot;list-style-type: decimal;&quot;&gt;&lt;a href=&quot;https://bab2min.tistory.com/677&quot;&gt;형태소 분석기 Kiwi CoNg (2/4): CoNg 모델 소개&lt;/a&gt;&lt;/li&gt;
&lt;li style=&quot;list-style-type: decimal;&quot;&gt;&lt;a href=&quot;https://bab2min.tistory.com/678&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;형태소 분석기 Kiwi CoNg (3/4): 모델 성능 비교&lt;/a&gt;&lt;/li&gt;
&lt;li style=&quot;list-style-type: decimal;&quot;&gt;&lt;b&gt;형태소 분석기 Kiwi CoNg (4/4): 모델 최적화&lt;/b&gt;&lt;/li&gt;
&lt;/ol&gt;
&lt;h3 style=&quot;text-align: justify;&quot; data-ke-size=&quot;size23&quot;&gt;속도 최적화&lt;/h3&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;1514&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/H99aC/btsOb6xpcV4/NLNYBZI5wG6eulWgk33Ko0/img.jpg&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/H99aC/btsOb6xpcV4/NLNYBZI5wG6eulWgk33Ko0/img.jpg&quot; data-alt=&quot;이것은 올바른 속도 최적화가 아닙니다.&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/H99aC/btsOb6xpcV4/NLNYBZI5wG6eulWgk33Ko0/img.jpg&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FH99aC%2FbtsOb6xpcV4%2FNLNYBZI5wG6eulWgk33Ko0%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;400&quot; height=&quot;473&quot; data-origin-width=&quot;1280&quot; data-origin-height=&quot;1514&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;이것은 올바른 속도 최적화가 아닙니다.&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;속도 최적화는 모델이 동일한 결과를 내도록 정확도는 유지하면서 연산 시간을 최대한 줄이는 것을 말합니다. 속도는 아주 빠르게 만들었으나 모델의 정확도가 팍 떨어져버린다면 아무 의미가 없습니다. 따라서 각 최적화 단계별로 속도가 빨라지면서 정확도가 동일하게 유지된다는 것을 확인하는게 필요한데, 이 포스팅에서는 여백이 부족하여 정확도는 함께 기재하지 못했음을 미리 알리는 바입니다. (마지막 단계인 dimension 축소를 제외하고는 모두 정확도 손실이 없는 최적화였습니다~) 일단 결과부터 보겠습니다.&lt;/p&gt;
&lt;table class=&quot;std-table&quot; data-ke-align=&quot;alignLeft&quot;&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;th&gt;&amp;nbsp;&lt;/th&gt;
&lt;th&gt;문장당 평균 처리 시간(ms)&lt;/th&gt;
&lt;th&gt;모델 메모리 사용량(MB)&lt;/th&gt;
&lt;th&gt;문장당 평균 처리 시간(ms)&lt;/th&gt;
&lt;th&gt;모델 메모리 사용량(MB)&lt;/th&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;th&gt;&amp;nbsp;&lt;/th&gt;
&lt;th colspan=&quot;2&quot;&gt;KnLM&lt;/th&gt;
&lt;th colspan=&quot;2&quot;&gt;SBG&lt;/th&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;th&gt;베이스라인&lt;/th&gt;
&lt;td&gt;0.92&lt;/td&gt;
&lt;td&gt;368.7&lt;/td&gt;
&lt;td&gt;2.30&lt;/td&gt;
&lt;td&gt;377.9&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;th&gt;&amp;nbsp;&lt;/th&gt;
&lt;th colspan=&quot;2&quot;&gt;CoNg&lt;/th&gt;
&lt;th colspan=&quot;2&quot;&gt;CoNg Global&lt;/th&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;th&gt;최초 구현&lt;/th&gt;
&lt;td&gt;64.13&lt;/td&gt;
&lt;td&gt;926.1&lt;/td&gt;
&lt;td&gt;114.910&lt;/td&gt;
&lt;td&gt;1058.0&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;th&gt;+ Batch화&lt;/th&gt;
&lt;td&gt;13.21&lt;/td&gt;
&lt;td&gt;926.1&lt;/td&gt;
&lt;td&gt;31.10&lt;/td&gt;
&lt;td&gt;1058.0&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;th&gt;+ 불필요한 탐색 제거&lt;/th&gt;
&lt;td&gt;3.49&lt;/td&gt;
&lt;td&gt;926.1&lt;/td&gt;
&lt;td&gt;19.50&lt;/td&gt;
&lt;td&gt;1058.0&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;th&gt;+ 8bit 양자화&lt;/th&gt;
&lt;td&gt;1.65&lt;/td&gt;
&lt;td&gt;420.2&lt;/td&gt;
&lt;td&gt;3.50&lt;/td&gt;
&lt;td&gt;443.9&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;th&gt;+ GEMM 커널 최적화&lt;/th&gt;
&lt;td&gt;0.89&lt;/td&gt;
&lt;td&gt;420.2&lt;/td&gt;
&lt;td&gt;1.92&lt;/td&gt;
&lt;td&gt;443.9&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;th&gt;+ 탐색 결과 캐싱&lt;/th&gt;
&lt;td&gt;0.80&lt;/td&gt;
&lt;td&gt;420.2&lt;/td&gt;
&lt;td&gt;1.81&lt;/td&gt;
&lt;td&gt;443.9&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;th&gt;+ Search SIMD 최적화&lt;/th&gt;
&lt;td&gt;0.70&lt;/td&gt;
&lt;td&gt;420.2&lt;/td&gt;
&lt;td&gt;1.74&lt;/td&gt;
&lt;td&gt;443.9&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;th&gt;+ dimension 축소&lt;/th&gt;
&lt;td&gt;0.58&lt;/td&gt;
&lt;td&gt;336.9&lt;/td&gt;
&lt;td&gt;1.58&lt;/td&gt;
&lt;td&gt;357.1&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p style=&quot;text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;왼쪽에는 Local 모델인 CoNg(및 베이스라인인 KnLM)의 결과가, 오른쪽에는 Global 모델인 CoNg Global(및 베이스라인인 SBG)의 결과가 표기되어 있습니다. Local 모델 기준으로 &lt;b&gt;최초 구현체는 베이스라인보다 60배 이상 느린 상황&lt;/b&gt;이었는데요, 다양한 최적화를 통해 &lt;b&gt;최종적으로는 문장당 평균 0.58ms로 베이스라인인 0.92ms보다 50%이상 더 빨라&lt;/b&gt;지게 되었습니다. Global 모델에서도 마찬가지로 SBG보다 더 빨라지는 데에 성공했구요. 각 단계에 대해 차근차근 설명해보도록 하겠습니다.&lt;/p&gt;
&lt;h4 style=&quot;text-align: justify;&quot; data-ke-size=&quot;size20&quot;&gt;Kiwi가 언어 모델을 사용하는 방법&lt;/h4&gt;
&lt;p style=&quot;text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;각 최적화 단계를 설명하기에 앞서 먼저 Kiwi가 언어 모델을 사용하여 어떤 식으로 문장을 형태소로 분석하는지 간단하게 살펴보겠습니다. Kiwi의 형태소 분석은 크게 2단계로 진행됩니다. 먼저 입력된 텍스트에서 가능한 형태소들을 최대한 발견하여 그래프로 변환하는 단계가 있고, 이렇게 변환된 그래프를 탐색하면서 가장 적합한 형태소 배열을 찾는 단계가 있습니다. 두 번째 단계에서 적합한 형태소 배열을 찾기 위해서 언어 모델을 사용하는 것이구요.&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;말로만 설명하면 감이 잘 안 잡히니 예시를 들면서 소개해보겠습니다. 형태소 분석하면 늘 떠오르는 그 문장~ 바로 &quot;&lt;b&gt;아버지가방에들어가신다.&lt;/b&gt;&quot;를 생각해볼까요? 첫번째 단계는 이 텍스트에서 가능한 형태소들을 발견하는 것입니다. 앞부분부터 찾아보면 &quot;&lt;b&gt;아버지&lt;/b&gt;&quot;, &quot;&lt;b&gt;가방&lt;/b&gt;&quot;, &quot;&lt;b&gt;가&lt;/b&gt;&quot;, &quot;&lt;b&gt;방&lt;/b&gt;&quot;, &quot;&lt;b&gt;에&lt;/b&gt;&quot; 등이 있을텐데요, 일단은 어떤게 맞는지는 고민하지 않고 최대한 나열하는게 첫번째 단계의 역할입니다. 이렇게 나열한 형태소들을 잘 연결하면 아래와 같이 그래프 구조를 만들 수 있습니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;2300&quot; data-origin-height=&quot;710&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/b65Dao/btsOb40KLG9/Jq4k47Yf8k8toQPk7Pf7NK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/b65Dao/btsOb40KLG9/Jq4k47Yf8k8toQPk7Pf7NK/img.png&quot; data-alt=&quot;그림1. &amp;quot;아버지가방에들어가신다&amp;quot;를 분석할때 구축되는 그래프 구조&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/b65Dao/btsOb40KLG9/Jq4k47Yf8k8toQPk7Pf7NK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fb65Dao%2FbtsOb40KLG9%2FJq4k47Yf8k8toQPk7Pf7NK%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;2300&quot; height=&quot;710&quot; data-origin-width=&quot;2300&quot; data-origin-height=&quot;710&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;그림1. &quot;아버지가방에들어가신다&quot;를 분석할때 구축되는 그래프 구조&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;여백이 부족한 관계로 &quot;&lt;b&gt;아버지가방에&lt;/b&gt;&quot;까지만 표현해봤습니다. 이 그래프는 항상 &amp;lt;BOS&amp;gt;에서 시작하여 &amp;lt;EOS&amp;gt;로 끝나는 방향성 그래프입니다. 즉 BOS에서부터 시작하여 여러 갈림길 중 하나를 선택하면서 나아가다보면 항상 EOS를 만날 수 있게 됩니다. 그리고 각 노드(주황색 동그라미)에는 해당 형태에 대응되는 형태소 목록이 나열되어 있습니다. 1번 &lt;b&gt;아버지&lt;/b&gt;의 경우에는 &lt;b&gt;아버지/NNG&lt;/b&gt; 하나 밖에 없지만, 2번 &lt;b&gt;가&lt;/b&gt; 같은 경우는 &lt;b&gt;가/VV&lt;/b&gt;, &lt;b&gt;가/JKS&lt;/b&gt;, &lt;b&gt;가/NNG&lt;/b&gt; 처럼 여러 형태소가 나열되어 있는 경우도 있죠. 이제 BOS 노드에서 시작해서 노드를 하나씩 건너가면서 해당 노드에 포함된 형태소 1개를 고르고 또 다음 노드로 건너가는 그런 상황을 생각해봅시다. 이건 주어진 입력 문장에서 가능한 모든 형태소 조합을 따져보는것과 동일합니다. 예를 들어 0번 노드에서 시작해 5번까지 가능 경우를 생각해보면&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;0 &amp;rarr; 1 &amp;rarr; 2 &amp;rarr; 4 &amp;rarr; 5&lt;/li&gt;
&lt;li&gt;0 &amp;rarr; 1 &amp;rarr; 3 &amp;rarr; 5&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;노드로는 위와 같이 2가지 경로가 있을 것이고, 각 경로에서 선택할 수 있는 형태소의 조합도 따져보자면&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;0(1가지) &amp;rarr; 1(1가지)&lt;span&gt;&amp;nbsp;&lt;/span&gt;&amp;rarr; 2(3가지)&lt;span&gt;&amp;nbsp;&lt;/span&gt;&amp;rarr; 4(3가지)&lt;span&gt;&amp;nbsp;&lt;/span&gt;&amp;rarr; 5(2가지): 1&amp;times;1&amp;times;3&amp;times;3&amp;times;2 = 18&lt;/li&gt;
&lt;li&gt;0(1가지)&lt;span&gt;&amp;nbsp;&lt;/span&gt;&amp;rarr; 1(1가지)&lt;span&gt;&amp;nbsp;&lt;/span&gt;&amp;rarr; 3(1가지)&lt;span&gt;&amp;nbsp;&lt;/span&gt;&amp;rarr; 5(2가지): &lt;span style=&quot;color: #333333; text-align: left;&quot;&gt;1&amp;times;1&lt;/span&gt;&amp;times;2 = 2&lt;/li&gt;
&lt;/ul&gt;
&lt;p style=&quot;text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;첫번째 경로에서는 18가지, 두번째 경로에서는 2가지가 나와서 총 20가지의 형태소 조합이 가능하다는 걸 알 수 있습니다. 이 20가지 중에서 어떤 형태소 조합이 가장 적절한지를 알아야 &quot;아버지가방에&quot;를 바르게 분석할 수 있을텐데요, 이것은 이제 언어 모델이 할 일입니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1296&quot; data-origin-height=&quot;1380&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/c0RokW/btsOaR9aQh8/pueLxTPluc85oqxWxcV38k/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/c0RokW/btsOaR9aQh8/pueLxTPluc85oqxWxcV38k/img.png&quot; data-alt=&quot;그림2. 언어 모델을 이용해 최적의 경로는 찾는 과정을 시각화한 것&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/c0RokW/btsOaR9aQh8/pueLxTPluc85oqxWxcV38k/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fc0RokW%2FbtsOaR9aQh8%2FpueLxTPluc85oqxWxcV38k%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;600&quot; height=&quot;639&quot; data-origin-width=&quot;1296&quot; data-origin-height=&quot;1380&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;그림2. 언어 모델을 이용해 최적의 경로는 찾는 과정을 시각화한 것&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;두번째 단계는 위에서 구성한 방향성 그래프의 노드를 전부 순회하면서 최적의 경로를 찾는 것입니다. 최적의 경로를 보관해두기 위해서 Kiwi는 위와 같이 (&lt;b&gt;ID&lt;/b&gt;, &lt;b&gt;부모ID&lt;/b&gt;, &lt;b&gt;형태소&lt;/b&gt;, &lt;b&gt;누적점수&lt;/b&gt;)로 구성되는 표를 만들어서 메모를 합니다. 그리고 0번 노드부터 시작하여 언어 모델을 이용해 점수를 계산해나갑니다. (사실 0번 노드는 시작 노드이므로 별도로 계산을 할 필요는 없이 초기화만 해둡니다.) 1번 노드에서는 &lt;b&gt;아버지/NNG&lt;/b&gt;라는 형태소가 이전의 결과(ID=0) 다음에 올 로그 확률을 계산합니다. 계산해보니 $-8.5$였는데 그 이전의 결과(ID=0)의 로그 확률 점수가 $0.0$이었으므로 합해서 $-8.5$로 적어둡니다. 1번 노드의 모든 형태소에 대해 계산을 끝냈으니 다음으로 2번 노드로 넘어갑니다. 2번 노드에서도 마찬가지로 이전의 결과(ID=1) 다음에 &lt;b&gt;가/VV&lt;/b&gt;, &lt;b&gt;가/JKS&lt;/b&gt;, &lt;b&gt;가/NNG&lt;/b&gt;가 올 확률을 각각 계산합니다. 로그 확률을 계산해보니 $-0.6$, $-1.1$, $-8.7$ 이었고, 이전 결과(ID=1)에 이를 누산하니 $-9.1$, $-9.6$, $-17.2$가 되었습니다. 이와 같이 바로 이전의 결과에 가능한 다음의 결과를 모두 나열하여 언어 모델로 확률 점수를 계산하고 이를 누적해 나가는 것을 반복하다 보면 최적의 경로를 찾을 수 있습니다.&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;5번 노드까지 탐색했을때의 결과를 보면 ID=17이 $-16.9$으로 가장 점수가 높네요. 이게 최적의 분석 결과라는 건데요, 실제로 이게 어떤 형태소로 구성되어있는지 알고 싶다면 부모ID를 따라서 거슬러 올라가보면 됩니다. 부모 ID를 따라 거슬러 올라가면서 어떤 형태소들이 들어있는지 나열해보면 아래와 같습니다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;ID=17, 에/JKB&lt;/li&gt;
&lt;li&gt;ID=9, 방/NNG&lt;/li&gt;
&lt;li&gt;ID=3, 가/JKS&lt;/li&gt;
&lt;li&gt;ID=1, 아버지/NNG&lt;/li&gt;
&lt;li&gt;ID=0, &amp;lt;BOS&amp;gt; (끝)&lt;/li&gt;
&lt;/ul&gt;
&lt;p style=&quot;text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;이 결과를 역으로 다시 쓰면 &lt;b&gt;&amp;lt;BOS&amp;gt;&lt;/b&gt;, &lt;b&gt;아버지/NNG&lt;/b&gt;, &lt;b&gt;가/JKS&lt;/b&gt;, &lt;b&gt;방/NNG&lt;/b&gt;, &lt;b&gt;에/JKB&lt;/b&gt; 가 됩니다. 다행히도 아버지께서 가방에 들어가시는 참사가 발생하지는 않았군요! 근데 사실 이 탐색 방법을 그대로 쓰긴 어렵습니다. 지금은 문장이 짧아서 노드 개수가 적었고, 각 노드에 들어있는 형태소 목록도 1~3개 정도였으니 망정이지 만약 노드가 수백개가 되고, 각 노드의 형태소 목록도 10개 가까이 된다고 하면 노드를 탐색해나갈때마다 메모하는 표의 크기가 매번 10배가까이 늘어날 것이기 때문입니다. 메모리가 터져나가겠죠? 그래서 탐색 후보를 줄이는 몇 가지 트릭을 사용합니다.&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;위의 그림에서 5번 노드의 표를 보면 부모ID가 5, 9, 10, 11인 경우만 있고 나머지(6,7,8,12,13,14)는 없습니다. 우리가 알고 싶은 것은 점수가 가장 높은 경로 1가지이므로 사실 1등의 가망성이 없는 경로는 가능하면 미리 쳐내는게 효율적입니다. 그런데 부모가 지금 부진하다고 해서 그 후손들까지 &lt;span style=&quot;color: #333333; text-align: justify;&quot;&gt;반드시&lt;span&gt; &lt;/span&gt;&lt;/span&gt;부진한게 아니라 역전이 충분히 일어날 수 있다는 게 문제를 어렵게 합니다. 그래서 언어 모델이 이전의 n(예시에서는 1)개 형태소만 고려한다는 가정을 도입합니다.&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;그러면 이전 형태소가동일한 경우 후손들의 언어 모델 점수도 동일해지므로 우리는 이전 형태소가 동일한 것들중 가장 점수가 높은 것만 남기고 나머지는 미리 버릴 수 있습니다. 그래서 4번 노드에서는 ID=9,10,11만 선택하고 나머지는 다음 후손으로 연결되지 않게 미리 잘라낸 것이죠. 이런 과정을 통해 각 표에 저장되는 메모의 크기는 줄이면서 최적의 경로를 탐색하는게 가능해집니다.&lt;/p&gt;
&lt;h4 style=&quot;text-align: justify;&quot; data-ke-size=&quot;size20&quot;&gt;CoNg: 최초 구현체&lt;/h4&gt;
&lt;p style=&quot;text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;CoNg 모델 구조에 관한 포스팅에서 설명했듯 CoNg은 이전의 n-gram 문맥을 하나의 임베딩 벡터로 만들고, 다음에 나타날 형태소 1개를 임베딩 벡터로 만들어서 둘을 내적한 값을 이용해 확률을 계산합니다. 원래는 이 내적값들에 Softmax를 취해서 확률을 구해야하지만, 앞에서 설명한 것처럼 미리 분모항을 계산하여 저장해두는 트릭을 통해 우리는 임베딩 벡터 한 쌍끼리 내적을 구하기만 해도 Softmax의 연산결과를 알아낼 수 있습니다. 초기 구현체는 이 방식을 따라 충실하게 동작하도록 만들었고, 각 임베딩들은 전부 FP32로 저장했습니다. 벡터 간 내적에는 Eigen 라이브러리를 사용했구요. 그 결과는 제일 위에서 본것처럼 60배 가까이 처참하게 느렸습니다.&lt;/p&gt;
&lt;h4 style=&quot;text-align: justify;&quot; data-ke-size=&quot;size20&quot;&gt;Batch화 적용, 불필요한 탐색 제거&lt;/h4&gt;
&lt;table class=&quot;std-table&quot; data-ke-align=&quot;alignLeft&quot;&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;th&gt;&amp;nbsp;&lt;/th&gt;
&lt;th&gt;문장당 평균 처리 시간(ms)&lt;/th&gt;
&lt;th&gt;모델 메모리 사용량(MB)&lt;/th&gt;
&lt;th&gt;문장당 평균 처리 시간(ms)&lt;/th&gt;
&lt;th&gt;모델 메모리 사용량(MB)&lt;/th&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;th&gt;&amp;nbsp;&lt;/th&gt;
&lt;th colspan=&quot;2&quot;&gt;CoNg&lt;/th&gt;
&lt;th colspan=&quot;2&quot;&gt;CoNg Global&lt;/th&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;th&gt;최초 구현&lt;/th&gt;
&lt;td&gt;64.13&lt;/td&gt;
&lt;td&gt;926.1&lt;/td&gt;
&lt;td&gt;114.910&lt;/td&gt;
&lt;td&gt;1058.0&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;th&gt;+ Batch화&lt;/th&gt;
&lt;td&gt;13.21&lt;/td&gt;
&lt;td&gt;926.1&lt;/td&gt;
&lt;td&gt;31.10&lt;/td&gt;
&lt;td&gt;1058.0&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;th&gt;+ 불필요한 탐색 제거&lt;/th&gt;
&lt;td&gt;3.49&lt;/td&gt;
&lt;td&gt;926.1&lt;/td&gt;
&lt;td&gt;19.50&lt;/td&gt;
&lt;td&gt;1058.0&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p style=&quot;text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;가장 먼저 수행한 최적화는 언어 모델 계산의 Batch화입니다. 그림2에서 ID=9, 10, 11 뒤에 &lt;b&gt;에/JKB&lt;/b&gt;가 올 확률과 &lt;b&gt;에/IC&lt;/b&gt;가 올 확률을 계산하려면 총 6가지 조합에 대한 내적을 구해야합니다. 임베딩의 크기를 4라고 가정하면 아래와 같이 4차원 벡터 간의 내적을 총 6번 계산해야하는 것입니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;834&quot; data-origin-height=&quot;386&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/W9i1C/btsOb9OBI4Q/3ac9UaUWwlsg2KKitWtO80/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/W9i1C/btsOb9OBI4Q/3ac9UaUWwlsg2KKitWtO80/img.png&quot; data-alt=&quot;4차원 벡터 간의 내적&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/W9i1C/btsOb9OBI4Q/3ac9UaUWwlsg2KKitWtO80/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FW9i1C%2FbtsOb9OBI4Q%2F3ac9UaUWwlsg2KKitWtO80%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;400&quot; height=&quot;185&quot; data-origin-width=&quot;834&quot; data-origin-height=&quot;386&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;4차원 벡터 간의 내적&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;그러나 내적을 구하기 전에 ID=9, 10, 11의 문맥 임베딩을 가져와서 세로로 연결하여 행렬로 만들고, 또 &lt;b&gt;에/JKB&lt;/b&gt;와 &lt;b&gt;에/IC&lt;/b&gt;에 해당하는 임베딩을 가져와서 가로로 연결하여 행렬을 만들면, 내적연산을 따로 수행하는 대신 아래 그림처럼 두 행렬의 곱셈을 계산하는 방식으로 6가지 조합의 계산 결과를 한번에 얻을 수 있습니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;922&quot; data-origin-height=&quot;366&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/mvHpF/btsObUDUBRN/YLvmECdIbLkaByNKjVPNsK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/mvHpF/btsObUDUBRN/YLvmECdIbLkaByNKjVPNsK/img.png&quot; data-alt=&quot;(3,4) 행렬과 (4,2) 행렬의 곱셈&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/mvHpF/btsObUDUBRN/YLvmECdIbLkaByNKjVPNsK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FmvHpF%2FbtsObUDUBRN%2FYLvmECdIbLkaByNKjVPNsK%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;400&quot; height=&quot;159&quot; data-origin-width=&quot;922&quot; data-origin-height=&quot;366&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;(3,4) 행렬과 (4,2) 행렬의 곱셈&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;근데 사실 위처럼 내적을 따로 6번 계산하는거나 아래처럼 행렬 곱셈 1번을 하는 것이나 최종적인 연산량은 동일합니다. 오히려 행렬을 구성하는 단계에서 추가적인 오버헤드가 발생하므로 더 비효율적인것 아닐까요? 다행히도 그렇지 않습니다! 아래와 같이 행렬로 구성하여 한번에 곱셈을 수행하는 것이 캐시 메모리 재사용 때문에 더 빠릅니다. 사실 위의 내적에서 병목이 되는 부분은 곱셈 연산 그 자체가 아니라 문맥에 해당하는 임베딩과 형태소에 해당하는 임베딩을 RAM에서 읽어오는 부분이기 때문입니다.&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;그래서 추가적인 전처리를 수행하더라도 연산 자체를 한번에 모아서 하게 되면, 임베딩을 메모리로부터 읽어오는 작업은 1번만 하면 되고, 그 이후에는 캐시에 올라간 메모리를 재사용할 수 있어서 속도가 훨씬 빨라지게 됩니다. 이렇게 언어 모델 계산을 Batch화 하는 작업을 통해서 3~5배 정도 소요 시간을 줄일 수 있었습니다.&lt;/p&gt;
&lt;h4 style=&quot;text-align: justify;&quot; data-ke-size=&quot;size20&quot;&gt;8bit 양자화, 최적화된 GEMM 커널 구현&lt;/h4&gt;
&lt;p style=&quot;text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;위에서 설명했다시피 주요 병목은 RAM에 흩어져 있는 임베딩을 로드하는 작업입니다. 이 작업은 임베딩의 메모리 상 크기에 비례해서 시간이 걸리므로 메모리 상 크기를 줄일 수 있다면 속도를 높일 수 있습니다. 일반적으로 실수를 저장하는데에 FP32(C언어의 float 타입, 32비트 단정밀도 부동소수점)를 사용하는데 사실 아주 정밀한 연산 결과가 필요한게 아닌 이상 신경망 모델에서 굳이 FP32를 쓰지 않아도 동일한 결과를 얻을 수 있다는게 널리 알려져 있습니다. 그래서 자주 사용하는 기법이 양자화(Quantization)입니다. 양자화의 사전적인 의미는 연속인 값을 불연속적인 값으로 바꾸는 것을 이야기하지만 신경망에서 쓰일때는 흔히 32비트 실수를 더 적은 비트로 나타내는 작업을 가리킵니다.&lt;/p&gt;
&lt;table class=&quot;std-table&quot; data-ke-align=&quot;alignLeft&quot;&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;th&gt;&amp;nbsp;&lt;/th&gt;
&lt;th&gt;문장당 평균 처리 시간(ms)&lt;/th&gt;
&lt;th&gt;모델 메모리 사용량(MB)&lt;/th&gt;
&lt;th&gt;문장당 평균 처리 시간(ms)&lt;/th&gt;
&lt;th&gt;모델 메모리 사용량(MB)&lt;/th&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;th&gt;&amp;nbsp;&lt;/th&gt;
&lt;th colspan=&quot;2&quot;&gt;CoNg&lt;/th&gt;
&lt;th colspan=&quot;2&quot;&gt;CoNg Global&lt;/th&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;th&gt;+ 불필요한 탐색 제거&lt;/th&gt;
&lt;td&gt;3.49&lt;/td&gt;
&lt;td&gt;926.1&lt;/td&gt;
&lt;td&gt;19.50&lt;/td&gt;
&lt;td&gt;1058.0&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;th&gt;+ 8bit 양자화&lt;/th&gt;
&lt;td&gt;1.65&lt;/td&gt;
&lt;td&gt;420.2&lt;/td&gt;
&lt;td&gt;3.50&lt;/td&gt;
&lt;td&gt;443.9&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;th&gt;+ GEMM 커널 최적화&lt;/th&gt;
&lt;td&gt;0.89&lt;/td&gt;
&lt;td&gt;420.2&lt;/td&gt;
&lt;td&gt;1.92&lt;/td&gt;
&lt;td&gt;443.9&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p style=&quot;text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;예를 들어 FP32를 Int8(8bit 정수)로 양자화하면 표현 가능한 값은 덜 촘촘해지지만 메모리 사용량이 1/4이 되어서 속도 향상을 기대할수 있는 것이죠. 뿐만 아니라 Int8 양자화는 가장 널리 쓰이는 일반적인 양자화 기법이기 때문에 최신 CPU에서는 Int8로 양자화된 신경망을 처리할 때 사용할 수 있는 가속 명령어들을 갖추고 있습니다. 따라서 Int8로 양자화를 수행할 경우, 메모리 병목이 1/4로 줄어들고 추가로 연산 속도 역시 최신 CPU에서 이론상 최대 4배까지 빨라질 수 있습니다.&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;단점은 정밀도가 낮아져서 모델의 정확도가 낮아질수 있다는 것인데요, 이는 여러 겹의 레이어로 구성된 모델에서 레이어마다 오차가 전파되면서 누적되는 상황에서 문제가 되지 CoNg처럼 단일 레이어로 구성된 모델에서는 사실 전혀 문제가 되지 않습니다. 즉, &lt;b&gt;CoNg 입장에서 Int8 양자화는 단점은 없고 장점만 있는 최적화 기법&lt;/b&gt;이니 고민할 필요 없이 도입하는게 맞습니다. 실제로 베이스라인 Int8 GEMM 커널에서도 속도가&lt;span style=&quot;color: #333333; text-align: justify;&quot;&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;2배 이상&lt;span&gt; &lt;/span&gt;&lt;/span&gt;크게 개선된 것을 확인할 수 있었습니다.&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;Int8 양자화의 가능성을 보았으니 이제 커널 최적화를 진행해서 속도를 극한으로 쥐어짜내는 작업을 해야합니다. 바퀴를 다시 발명하지 말라는 격언처럼, 굳이 직접 최적화를 하기보다는 이미 잘 최적화된 &lt;a href=&quot;https://github.com/microsoft/onnxruntime/blob/main/onnxruntime/core/mlas/lib/qgemm.cpp&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;Int8 GEMM (qgemm) 커널&lt;/a&gt;을 그대로 가져와서 사용하는 것도 나쁘지 않은 선택입니다. 그러나 몇 가지 프로파일링을 해본 결과 현재 오픈소스로 공개된 qgemm 커널들이 CoNg의 상황에서 최적으로 동작하지 않는다는 걸 발견했습니다. 원인은 크게 메모리 병목과 SIMD 연산 상에서의 병목이었습니다.&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;메모리 병목은 CoNg에서는 Matrix A를 구성하기 위해서 흩어져있는 문맥 임베딩에서 우리가 원하는 값들만 선택해서 모으는 작업(Gather)을 먼저 수행해야한다는 점 때문에 발생했습니다. Gather연산은 Batch화 과정에서 피할수 없기 때문에 무조건 메모리 복사 연산이 1회는 일어나야할 것으로 보입니다. 문제는 GEMM Kernel 내부에서도 실은 Matrix A를 다시 Sub block로 쪼개어 복사하는 과정이 있기 때문에 CoNg 시나리오에서는 임베딩 값을 총 2회 복사하게 됩니다. GEMM Kernel 내부에서는 왜 굳이 Matrix A를 Sub block로 쪼개어 복사할까요? 일일히 설명하기에는 좀 복잡하므로 &lt;a href=&quot;https://yunmorning.tistory.com/37&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;링크&lt;/a&gt;로 대체하고 요점만 적어보자면 &lt;b&gt;Cache 활용을 최대한 높여서 메모리에 직접 접근하는 연산 횟수를 줄이기 위해서&lt;/b&gt;입니다.&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;이를 위해서 보통 L1 Cache에 들어갈 수 있는만큼 Matrix A의 일부 Block을 Cache용 메모리 공간에 복사하는 작업을 GEMM Kernel에서 수행합니다. 그럼 어차피 GEMM Kernel 내부에서 Matrix A를 복사해서 Cache용 공간에 넣을거라면 &lt;b&gt;애초에 Gather를 하지 않고 바로 Embeddings에서 원하는 Index만 선택해서 Cache용 메모리 공간에 직접 복사&lt;/b&gt;하도록 하면 Gather 연산을 생략할 수 있지 않을까요? 맞습니다! 그래서 Gather + GEMM Kernel을 한번에 수행하기 위해 직접 intrinsic function(어셈블리 명령어와 1대1 대응되는 함수)들을 활용해 Kernel을 작성했습니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1382&quot; data-origin-height=&quot;684&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/1VXVC/btsOd2pqxyw/1ckGbenwV247P0KUEvLuY0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/1VXVC/btsOd2pqxyw/1ckGbenwV247P0KUEvLuY0/img.png&quot; data-alt=&quot;Gather 연산 후에 GEMM Kernel을 수행하게 되면 사실상 Embedding 복사가 2회 일어나는 꼴이다.&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/1VXVC/btsOd2pqxyw/1ckGbenwV247P0KUEvLuY0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2F1VXVC%2FbtsOd2pqxyw%2F1ckGbenwV247P0KUEvLuY0%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;600&quot; height=&quot;297&quot; data-origin-width=&quot;1382&quot; data-origin-height=&quot;684&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;Gather 연산 후에 GEMM Kernel을 수행하게 되면 사실상 Embedding 복사가 2회 일어나는 꼴이다.&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1398&quot; data-origin-height=&quot;692&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/boxHmE/btsOe8bkBZw/a1oxzRITI7LM1xCo5heF0K/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/boxHmE/btsOe8bkBZw/a1oxzRITI7LM1xCo5heF0K/img.png&quot; data-alt=&quot;Gather와 Gemm Kernel을 합치면 불필요한 메모리 복사를 줄일 수 있어서 메모리 병목을 최소화할 수 있다.&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/boxHmE/btsOe8bkBZw/a1oxzRITI7LM1xCo5heF0K/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FboxHmE%2FbtsOe8bkBZw%2Fa1oxzRITI7LM1xCo5heF0K%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;600&quot; height=&quot;297&quot; data-origin-width=&quot;1398&quot; data-origin-height=&quot;692&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;Gather와 Gemm Kernel을 합치면 불필요한 메모리 복사를 줄일 수 있어서 메모리 병목을 최소화할 수 있다.&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;그리고 SIMD 연산 상의 병목도 있었는데요, GEMM 커널은 (m, n) 행렬과 (n, k) 행렬을 곱한 결과인 (m, n) 행렬을 구하는게 목적인데 여기서 보통 m, n, k의 크기가 충분히 크고 비슷한 경우에 최고의 효율을 내도록 최적화됩니다. m, n이 너무 작거나 k가 큰 경우에는 Matrix를 Sub block으로 나눠도 재사용할만한 경우가 많이 나오지 않기 때문이죠. 그래서 m, n이 작거나 k가 너무 큰 경우에는 보통 내적 연산을 수행하는 함수로 분기해서 처리하는게 일반적입니다.&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;그럼 실제로 CoNg 모델에서 사용하는 GEMM 연산의 입력은 어떤 크기일까요? k는 임베딩의 크기이므로 적당히 큰 256~384 정도가 되지만, m, n는 통계를 내보니 아래와 같이 주로 m=1~16, n=1~4인 경우가 대부분이었고 그보다 더 큰 Matrix 입력은 거의 없는 것을 확인할 수 있었습니다. 따라서 기존 커널과는 다르게 아주 작은 크기의 m, n에 대해 GEMM 커널을 최적화하는게 필요했습니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;476&quot; data-origin-height=&quot;618&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/mvMTo/btsOecZQKvI/ePqYh5bgQHYDJTG78knQrK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/mvMTo/btsOecZQKvI/ePqYh5bgQHYDJTG78knQrK/img.png&quot; data-alt=&quot;CoNg내의 GEMM에서 주로 사용되는 (m, n)의 분포&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/mvMTo/btsOecZQKvI/ePqYh5bgQHYDJTG78knQrK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FmvMTo%2FbtsOecZQKvI%2FePqYh5bgQHYDJTG78knQrK%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;250&quot; height=&quot;325&quot; data-origin-width=&quot;476&quot; data-origin-height=&quot;618&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;CoNg내의 GEMM에서 주로 사용되는 (m, n)의 분포&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;먼저 가장 많이 쓰이는 n=1, 2, 3, 4에 대해서 AVX512 VNNI instruction을 이용해 전용 커널을 구현했고(2.93 -&amp;gt; 2.49ms), 커널에 Loop Unrolling, Thread-Local Storage를 공유하는 최적화를 적용(2.49 -&amp;gt; 1.92 ms)했습니다. n &amp;gt; 4에 대해서는 n=1~4 커널을 여러 번 수행하는 것으로 대체했습니다. (이게 최적은 아니겠지만 자주 사용되지 않는 입력에 대해 일일히 최적화하기에는 개발자의 시간도 소중하니깐요~)&amp;nbsp;&lt;/p&gt;
&lt;h4 style=&quot;text-align: justify;&quot; data-ke-size=&quot;size20&quot;&gt;탐색 결과 캐싱, Search SIMD 최적화&lt;/h4&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1830&quot; data-origin-height=&quot;1032&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/RPt1P/btsOhUx3WcZ/kZrZwcqyBwIp7F1xkkshK1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/RPt1P/btsOhUx3WcZ/kZrZwcqyBwIp7F1xkkshK1/img.png&quot; data-alt=&quot;함수별 소요시간 프로파일링한 결과&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/RPt1P/btsOhUx3WcZ/kZrZwcqyBwIp7F1xkkshK1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FRPt1P%2FbtsOhUx3WcZ%2FkZrZwcqyBwIp7F1xkkshK1%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;1830&quot; height=&quot;1032&quot; data-origin-width=&quot;1830&quot; data-origin-height=&quot;1032&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;함수별 소요시간 프로파일링한 결과&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;여기까지 오니 이제 GEMM 연산이 전체 실행시간에서 차지하는 비율이 약 22% 밖에 되지 않게 되었습니다. 오히려 나머지 부분에서 시간을 더 쓰고 있는 건데요, 나머지 부분에서 가장 병목이었던 구간은 1) 문맥 임베딩의 ID를 찾기 위해 Trie를 탐색하는 부분과 2) 현재 노드까지의 중간 결과에서 이전 문맥 ID가 동일한 사례들을 찾기 위해 해시맵에 값을 넣고 찾는 부분이었습니다. 전자는 Trie 탐색이라는 특성상 메모리 접근 지점이 매번 바뀌는게, 후자는 C++ 표준 라이브러리의&amp;nbsp; std::unordered_map 특성 상 해시에 저장되는 값들이 불연속적으로 위치하여 메모리 접근 지점이 불연속적인게 L1 Cache 적중률을 낮춰서 병목을 일으키고 있었습니다.&amp;nbsp;&lt;/p&gt;
&lt;table class=&quot;std-table&quot; data-ke-align=&quot;alignLeft&quot;&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;th&gt;&amp;nbsp;&lt;/th&gt;
&lt;th&gt;문장당 평균 처리 시간(ms)&lt;/th&gt;
&lt;th&gt;모델 메모리 사용량(MB)&lt;/th&gt;
&lt;th&gt;문장당 평균 처리 시간(ms)&lt;/th&gt;
&lt;th&gt;모델 메모리 사용량(MB)&lt;/th&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;th&gt;&amp;nbsp;&lt;/th&gt;
&lt;th colspan=&quot;2&quot;&gt;CoNg&lt;/th&gt;
&lt;th colspan=&quot;2&quot;&gt;CoNg Global&lt;/th&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;th&gt;+ GEMM 커널 최적화&lt;/th&gt;
&lt;td&gt;0.89&lt;/td&gt;
&lt;td&gt;420.2&lt;/td&gt;
&lt;td&gt;1.92&lt;/td&gt;
&lt;td&gt;443.9&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;th&gt;+ 탐색 결과 캐싱&lt;/th&gt;
&lt;td&gt;0.80&lt;/td&gt;
&lt;td&gt;420.2&lt;/td&gt;
&lt;td&gt;1.81&lt;/td&gt;
&lt;td&gt;443.9&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;th&gt;+ Search SIMD 최적화&lt;/th&gt;
&lt;td&gt;0.70&lt;/td&gt;
&lt;td&gt;420.2&lt;/td&gt;
&lt;td&gt;1.74&lt;/td&gt;
&lt;td&gt;443.9&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p style=&quot;text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;먼저 문맥 Trie 탐색은 뭘해도 불규칙한 메모리 접근 패턴을 보일 수 밖에 없는데요, 다행히도 동일한 노드 내에서는 대체로 문맥이 유사하기 때문에 비슷한 Trie 경로를 따라가는 경우가 많았습니다. 이를 이용해 최근에 탐색한 Trie 노드의 결과를 캐싱해두고 다음에 동일한 경로를 탈 경우 캐시를 사용하도록 수정하여 병목을 줄일 수 있었습니다.&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;그리고 해시맵 최적화는 아예 해시맵을 포기하는 것으로 달성했습니다. 해시맵이 점근적으로는 임의의 key에 대해 O(1) 접근 시간을 보장하는 반면 선형 탐색은 임의의 key를 찾기 위해 O(n)이 소요됩니다. 그렇지만 형태소 분석 경로를 탐색할때 컨테이너의 크기는 주로 몇 십 정도의 크기이므로 사실 O(1)이냐 O(n)이냐는 속도에 큰 영향을 주지 않고 오히려 캐시 적중률이 속도에 더 큰 영향을 미칩니다. 또 해시맵은 key가 연속된 위치에 배치되지 않으므로 SIMD 연산으로 한번에 n개의 값을 비교할 수 없는 반면, 선형 탐색은 key가 연속되어 배치되므로 SIMD로 가속이 가능합니다.&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;이런 점들을 모두 종합한 결과 &lt;b&gt;해시맵을 버리고 선형 탐색 기반의 dictionary 자료 구조를 구현&lt;/b&gt;했습니다. 해시 크기는 1바이트로 줄이고 이를 연속된 배열에 배치해놓아 AVX512에서는 한번에 64개의 값(AVX2에서는 32개의 값)을 조회할 수 있게 만들었습니다. 프로파일링 결과 컨테이너의 크기가 64개를 넘어가는 경우가 거의 없었으므로 사실상 메모리 로드 1번, 비교 1번을 통해 원하는 hash가 컨테이너에 없다는 걸 알아낼수 있고, 동일한 hash가 존재하는 경우에만 key를 비교하여 그 값을 찾아낼수 있었습니다.&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;앞만 보고 달리다가 문득 뒤를 돌아서 Kiwi CoNg과 베이스라인를 비교해보니, 벌써 Local 모델에서는 KnLM(&lt;span style=&quot;color: #333333; text-align: justify;&quot;&gt;0.92ms&lt;/span&gt;)보다 CoNg이 0.70ms로 더 빨라졌고, 마찬가지로 Global 모델에서도 SBG(2.30ms)보다 CoNg Global이 1.74ms로 더 빨라졌습니다. 신경망 모델이라서 많이 느려지지 않을까 걱정했는데, 당초 예상을 뛰어 넘어서 속도 향상을 달성했네요! 이제 남은건 모델 크기입니다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;크기 최적화&lt;/h3&gt;
&lt;table class=&quot;std-table&quot; data-ke-align=&quot;alignLeft&quot;&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;th&gt;&amp;nbsp;&lt;/th&gt;
&lt;th&gt;모델 크기 (MB)&lt;/th&gt;
&lt;th&gt;ZIP 압축 후 모델 크기 (MB)&lt;/th&gt;
&lt;th&gt;형태소 분석 정확도 (F1)&lt;/th&gt;
&lt;th&gt;모호성 해소 정확도 (%)&lt;/th&gt;
&lt;th&gt;문장 분리 정확도 (Norm F1)&lt;/th&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;th&gt;베이스라인(SBG)&lt;/th&gt;
&lt;td&gt;37.84&lt;/td&gt;
&lt;td&gt;28.30&lt;/td&gt;
&lt;td&gt;81.787&lt;/td&gt;
&lt;td&gt;64.025&lt;/td&gt;
&lt;td&gt;81.77&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;th&gt;CoNg Global 8bit 양자화&lt;/th&gt;
&lt;td&gt;111.43&lt;/td&gt;
&lt;td&gt;105.83&lt;/td&gt;
&lt;td&gt;81.536&lt;/td&gt;
&lt;td&gt;87.500&lt;/td&gt;
&lt;td&gt;83.76&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;th&gt;7bit&lt;/th&gt;
&lt;td&gt;111.43&lt;/td&gt;
&lt;td&gt;94.62&lt;/td&gt;
&lt;td&gt;81.625&lt;/td&gt;
&lt;td&gt;87.812&lt;/td&gt;
&lt;td&gt;84.01&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;th&gt;6bit&lt;/th&gt;
&lt;td&gt;111.43&lt;/td&gt;
&lt;td&gt;84.50&lt;/td&gt;
&lt;td&gt;81.525&lt;/td&gt;
&lt;td&gt;87.468&lt;/td&gt;
&lt;td&gt;83.76&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;th&gt;5bit&lt;/th&gt;
&lt;td&gt;111.43&lt;/td&gt;
&lt;td&gt;75.46&lt;/td&gt;
&lt;td&gt;81.602&lt;/td&gt;
&lt;td&gt;87.737&lt;/td&gt;
&lt;td&gt;84.11&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;th&gt;4bit&lt;/th&gt;
&lt;td&gt;111.43&lt;/td&gt;
&lt;td&gt;61.62&lt;/td&gt;
&lt;td&gt;81.236&lt;/td&gt;
&lt;td&gt;86.102&lt;/td&gt;
&lt;td&gt;83.26&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;th&gt;Packed 4bit Group=8&lt;/th&gt;
&lt;td&gt;74.56&lt;/td&gt;
&lt;td&gt;70.52&lt;/td&gt;
&lt;td&gt;81.432&lt;/td&gt;
&lt;td&gt;87.340&lt;/td&gt;
&lt;td&gt;84.25&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;th&gt;Sparsity=50%&lt;/th&gt;
&lt;td&gt;111.43&lt;/td&gt;
&lt;td&gt;77.32&lt;/td&gt;
&lt;td&gt;81.610&lt;/td&gt;
&lt;td&gt;87.468&lt;/td&gt;
&lt;td&gt;84.11&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;th&gt;Packed 4bit Group=8, Sparsity=50%&lt;/th&gt;
&lt;td&gt;74.56&lt;/td&gt;
&lt;td&gt;66.96&lt;/td&gt;
&lt;td&gt;81.450&lt;/td&gt;
&lt;td&gt;87.145&lt;/td&gt;
&lt;td&gt;83.24&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;th&gt;Packed 4bit Sparsity=50% Block=4&lt;/th&gt;
&lt;td&gt;86.85&lt;/td&gt;
&lt;td&gt;63.76&lt;/td&gt;
&lt;td&gt;81.414&lt;/td&gt;
&lt;td&gt;86.235&lt;/td&gt;
&lt;td&gt;83.60&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;th&gt;Packed 4bit Sparsity=50% Block=8&lt;/th&gt;
&lt;td&gt;74.56&lt;/td&gt;
&lt;td&gt;52.91&lt;/td&gt;
&lt;td&gt;81.144&lt;/td&gt;
&lt;td&gt;86.838&lt;/td&gt;
&lt;td&gt;84.22&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p style=&quot;text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;앞서 양자화에서 설명한 것처럼 Int8 양자화는 사실상 양자화의 표준과도 같습니다. 그래서 8bit를 베이스라인으로 잡고 모델 크기와 성능을 비교해나가기 시작했습니다. 참고로 CPU에서 가속이 지원되는 데이터 타입은 Int8까지이므로 8bit 보다 적은 비트로 양자화하는 것은 모델의 크기를 줄일수 있어도 속도를 올릴 순 없습니다. 임베딩이 lower-bit 타입으로 저장되어있다 할지라도 어차피 다시 8bit로 변환한 다음 연산에 투입되어야하니깐요. 그래서 4~7bit는 모델을 저장할때도 아예 8bit를 유지하면서 저장하도록 했습니다. (그래서 모델 크기도 모두 111.43MB로 동일합니다.)&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;5bit까지는 bit수가 줄어들어도 전반적으로 모델이 성능이 유지되는걸 볼 수 있는데요, &lt;b&gt;4bit부터는 정확도가 급락&lt;/b&gt;하는게 보이기 시작합니다. 현실적으로 사용 가능한 최소 정밀도는 5bit정도라고 볼 수 있겠습니다. 단순히 정밀도를 줄이는 대신 Packed 4bit라는 방식도 고안하여 시도해봤습니다. 8개의 Weight를 1개의 Group으로 묶어서 해당 Group에 Local Zeropoint와 Local Scale을 도입하는 방식입니다. 각 Weight는 4비트로 표현되므로 0~15 사이의 값을 가질 수 있고, Local Zeropoint는 2비트로 6, 7, 8, 9 중 하나의 값을 갖습니다. 그리고 Local Scale은 6비트로 9~72의 값을 갖습니다. 이 때 실제 Weight가 가리키는 값은 다음과 같이 계산됩니다:&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;$$R = (W - Z) \times S / 9&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;498&quot; data-origin-height=&quot;462&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bqgfoR/btsOjW1WCcw/9YpBJAYfSKZq5YO8bR6Bbk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bqgfoR/btsOjW1WCcw/9YpBJAYfSKZq5YO8bR6Bbk/img.png&quot; data-alt=&quot;Packed 4bit&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bqgfoR/btsOjW1WCcw/9YpBJAYfSKZq5YO8bR6Bbk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbqgfoR%2FbtsOjW1WCcw%2F9YpBJAYfSKZq5YO8bR6Bbk%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;300&quot; height=&quot;278&quot; data-origin-width=&quot;498&quot; data-origin-height=&quot;462&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;Packed 4bit&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;8개마다 Local Zeropoint와 Local Scale이 추가되므로 &lt;b&gt;더 높은 다이내믹을 표현할 수 있게 되어 4bit로 인한 정밀도 손실을 보완&lt;/b&gt;해줍니다. 물론 8개의 가중치를 결국 5바이트로 표현하는 것이므로 실제 저장공간은 비압축시 5bit와 동일하게 사용하게 됩니다. 대신 압축한 경우에는 &lt;b&gt;5bit보다 모델 크기가 더 많이 줄어들고 또 모델의 성능 역시 5~6bit 사이 정도&lt;/b&gt;가 되어 나쁘지 않은 절충안인 것으로 확인되었습니다.&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;양자화 말고도 모델 크기를 줄이는데에 자주 쓰이는 방법으로 희소화(Sparsification)가 있습니다. 희소화는 가중치 행렬에서 0인 부분의 비율을 늘리는 것이라고 정의할 수 있으며, 이때 0인 값의 비율을 희소성(Sparsity)라고 합니다. 당연히 희소성이 높아질수록 대부분의 값이 0이 되므로 압축 효율이 좋아지는 대신 모델이 더 많은 정보를 잃어버리게 됩니다. 그리고 희소화 역시 8bit 미만의 양자화처럼 CPU에서 직접 가속 연산을 지원하지 않기 때문에 속도 향상에는 도움이 거의 안되고 일반적으로 모델 크기를 줄이는 용도로 자주 쓰입니다. 희소화의 효과에 대해서도 실험해본 결과 &lt;b&gt;Sparsity=50%인 경우&lt;/b&gt; 압축후 &lt;b&gt;모델 크기는 5~6bit 사이&lt;/b&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;양자화와 희소화는 서로 별개의 경량화 기법이므로 둘을 동시에 적용하는 것도 가능합니다. 실제로 Packed 4bit 양자화와 Sparsity=50%를 동시에 적용한 경우 압축 후 모델크기가 67MB까지 내려갔음에도 성능은 4bit보다 높은 것을 확인할 수 있었습니다. 이 정도면 성능 손실도 감내할만하고 모델 크기도 충분히 합리적인 것 같습니다.&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;만약 압축률을 더 높이고 싶다면 희소화시 &lt;b&gt;구조적 희소화&lt;/b&gt;(Structured Sparsification)를 적용할 수도 있습니다. 구조적 희소화는 0이 아무 위치에나 등장하는 것이 아니라 블럭 단위로 연속해서 나타나도록 강제하는 것입니다. 당연히 연속해서 0이 나타나므로 압축 시 효율이 더 높아집니다. 위의 표에서 Block=4, Block=8가 적힌 부분이 구조적 희소화를 적용한 것이구요, 실제로 성능은 좀 더 낮아지지만 모델 크기는 최대 53MB까지 압축되는 것을 볼 수 있습니다. 고무적인 건 이렇게까지 압축하더라도 기존 베이스라인인 SBG모델보다는 높은 성능을 유지한다는 점입니다~&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;다양하게 양자화와 희소화 실험을 해본 결과, 성능 손실과 압축 크기의 균형점에 위치한 Packed 4bit Group=8 + Sparsity=50%이 배포용으로 가장 적합하다고 판단되어 이를 최종 모델로 선정했습니다. 이로써 처음 설정했던 두 가지 기준을 모두 만족하는 CoNg 모델을 완성했습니다!!!&lt;/p&gt;
&lt;h3 style=&quot;text-align: justify;&quot; data-ke-size=&quot;size23&quot;&gt;최종 결과&lt;/h3&gt;
&lt;p style=&quot;text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;다음은 한국어 위키백과에서 1000개 문서를 임의로 뽑아 형태소 분석을 실시했을때의 초당 분석 속도(KB/s)를 측정한 결과입니다. 모든 경우에서 CoNg &amp;gt; KnLM, 그리고 CoNg Global &amp;gt; SBG인 양상을 보여주고 있습니다.&lt;/p&gt;
&lt;table class=&quot;std-table&quot; data-ke-align=&quot;alignLeft&quot;&gt;&lt;caption&gt;아키텍처별 Kiwi 모델의 텍스트 분석 속도(KB/s)&lt;/caption&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;th&gt;장비&lt;/th&gt;
&lt;th&gt;아키텍처&lt;/th&gt;
&lt;th&gt;KnLM&lt;/th&gt;
&lt;th&gt;SBG&lt;/th&gt;
&lt;th&gt;CoNg&lt;/th&gt;
&lt;th&gt;CoNg Global&lt;/th&gt;
&lt;th&gt;CoNg Global 메모리 사용량&lt;/th&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;th rowspan=&quot;4&quot;&gt;Ryzen 9700X&lt;/th&gt;
&lt;th&gt;AVX512-VNNI&lt;/th&gt;
&lt;td&gt;195.72&lt;/td&gt;
&lt;td&gt;82.14&lt;/td&gt;
&lt;td&gt;243.12&lt;/td&gt;
&lt;td&gt;124.13&lt;/td&gt;
&lt;td&gt;357.1MB&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;th&gt;AVX2&lt;/th&gt;
&lt;td&gt;193.44&lt;/td&gt;
&lt;td&gt;80.48&lt;/td&gt;
&lt;td&gt;240.88&lt;/td&gt;
&lt;td&gt;117.78&lt;/td&gt;
&lt;td&gt;357.1MB&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;th&gt;SSE4.1&lt;/th&gt;
&lt;td&gt;181.26&lt;/td&gt;
&lt;td&gt;77.72&lt;/td&gt;
&lt;td&gt;237.69&lt;/td&gt;
&lt;td&gt;107.08&lt;/td&gt;
&lt;td&gt;357.1MB&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;th&gt;SSE2*&lt;/th&gt;
&lt;td&gt;187.27&lt;/td&gt;
&lt;td&gt;68.86&lt;/td&gt;
&lt;td&gt;201.76&lt;/td&gt;
&lt;td&gt;75.69&lt;/td&gt;
&lt;td&gt;646.1MB&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;th&gt;Apple M2&lt;/th&gt;
&lt;th&gt;ARM Neon*&lt;/th&gt;
&lt;td&gt;200.31&lt;/td&gt;
&lt;td&gt;55.53&lt;/td&gt;
&lt;td&gt;228.78&lt;/td&gt;
&lt;td&gt;83.03&lt;/td&gt;
&lt;td&gt;646.1MB&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p style=&quot;text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;AVX512 VNNI 명령어까지 최대한 활용해서 최적화한 덕분에 최신 CPU에서 가장 빠르게 동작합니다. 참고로 SSE2는 너무 오래전 명령어셋이라서 양자화 가속이 지원되지 않는 문제로 FP32로 처리되고, Apple M시리즈는 AMX(행렬 곱셈 가속 연산 명령어)가 내장되어 있는데 &lt;a href=&quot;https://medium.com/swlh/apples-m1-secret-coprocessor-6599492fc1e1&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;자기네들만 사용하려고 숨겨놓은&lt;/a&gt;게 괘씸해서(사실은 쓰고 싶어도 어떻게 써야하는지 잘 몰라서) 최적화 커널을 작성하지 못했습니다. FP32로 폴백하여 동작해도 메모리 사용량이 좀 늘어날뿐 속도가 크게 느려지는 것은 아니라서 구형 CPU나 ARM 등 다른 아키텍처에서도 무리 없이 사용할 수 있어보입니다!&lt;/p&gt;
&lt;h3 style=&quot;text-align: justify;&quot; data-ke-size=&quot;size23&quot;&gt;끝이 아니라 새로운 시작!&lt;/h3&gt;
&lt;p style=&quot;text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;지금까지 총 4회에 걸쳐 Kiwi CoNg 모델의 도입 이유와 구조, 성능, 최적화에 대해서 소개해드렸습니다. 오래 전부터 구상해오던 모델을 마침내 성공적으로 구현해내서 무거운 짐을 내려놓은 기분인데요, 사실 CoNg의 구현 완료는 끝이 아니라 새로운 시작이라고 할 수 있습니다. 제일 첫 포스팅에서 언급했듯이 신경망 모델을 도입한 것은 학습 데이터가 부족한 상황에서도 더 높은 일반화 성능을 얻기 위함입니다. 즉 다시 말하면 이제 데이터 부족으로 시도해보지 못했던 다양한 분야에 대해서 Kiwi가 도전할 수 있게 되었다는 것입니다.&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;예를 들자면 사투리라든지 전근대 한국어처럼 표준어와 약간 유사하지만 알고보면 꽤 다른 언어들에 대한 분석, 혹은 단어 의미 중의성 해소와 같은 더 세밀한 변별이 필요한 분석 등 흥미롭지만 쉽지 않은 과제들이 기다리고 있습니다. 물론 CoNg 모델만으로는 역부족일 수도 있습니다만, 적어도 기존의 통계 기반 모델보다는 훨씬 잘할 것이라는 건 확실하겠죠?&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;앞으로도 더욱 발전할 한국어 형태소 분석기 Kiwi에 많은 관심가져주시길 바라면서 이번 Kiwi CoNg 포스팅 시리즈는 여기에서 마무리하도록 하겠습니다. 감사합니다~!&lt;/p&gt;</description>
      <category>프로그래밍/NLP</category>
      <category>CONG</category>
      <category>kiwi</category>
      <category>언어 모델</category>
      <category>형태소 분석기</category>
      <author>&amp;int;2tdt=t&amp;sup2;+c</author>
      <guid isPermaLink="true">https://bab2min.tistory.com/679</guid>
      <comments>https://bab2min.tistory.com/679#entry679comment</comments>
      <pubDate>Fri, 30 May 2025 02:41:01 +0900</pubDate>
    </item>
    <item>
      <title>형태소 분석기 Kiwi CoNg (3/4): 모델 성능 비교</title>
      <link>https://bab2min.tistory.com/678</link>
      <description>&lt;div&gt;
&lt;style&gt;.correct {background:#f6fff6;} .incorrect {background:#fff4f4;} .sep {font-size:115%; font-weight: bold; text-shadow:0px 0px 4px #ff6} .std-table th {font-size:90%} .std-table td {font-size:85%}&lt;/style&gt;
&lt;/div&gt;
&lt;p style=&quot;text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;지난 포스팅까지는 Kiwi에서 도입한 CoNg 모델이 어떤 구조인지, 또 왜 이 모델을 도입했는지 간단하게 소개해드렸습니다. 이번 포스팅에서는 신경망 모델인 CoNg이 기존의 통계 기반 모델과 비교해 실제로 얼마나 강력해졌는지 비교 분석해보도록 하겠습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Kiwi CoNg 포스팅 시리즈&lt;/p&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li style=&quot;list-style-type: decimal;&quot;&gt;&lt;a href=&quot;https://bab2min.tistory.com/676&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;형태소 분석기 Kiwi CoNg (1/4): 신경망 모델 도입기&lt;/a&gt;&lt;/li&gt;
&lt;li style=&quot;list-style-type: decimal;&quot;&gt;&lt;a href=&quot;https://bab2min.tistory.com/677&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;형태소 분석기 Kiwi CoNg (2/4): CoNg 모델 소개&lt;/a&gt;&lt;/li&gt;
&lt;li style=&quot;list-style-type: decimal;&quot;&gt;&lt;b&gt;형태소 분석기 Kiwi CoNg (3/4): 모델 성능 비교&lt;/b&gt;&lt;/li&gt;
&lt;li style=&quot;list-style-type: decimal;&quot;&gt;&lt;a href=&quot;https://bab2min.tistory.com/679&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;형태소 분석기 Kiwi CoNg (4/4): 모델 최적화&lt;/a&gt;&lt;/li&gt;
&lt;/ol&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;형태소 분석기의 성능 평가&lt;/h3&gt;
&lt;p style=&quot;text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;형태소 분석기의 성능을 평가하는 가장 정석적인 방법은 입력 문장과 그 문장의 올바른 형태소 분석 결과를 쌍으로 하여 평가 데이터를 구축하고, 형태소 분석기에 해당 문장을 입력했을때 올바른 형태소 분석 결과와 얼마나 유사한 결과가 나오는지를 비교해보는 것입니다. 제일 일반적인 방법이지만 다음과 같은 이유 때문에 실제로는 그대로 따르기가 어렵습니다.&lt;/p&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;평가 데이터를 구축하는 데 품이 많이 든다.&lt;/li&gt;
&lt;li&gt;형태소 분석기마다 사용하는 품사 체계가 달라서 다양한 분석기들 간의 성능을 비교하는 것이 불가능하다.&lt;/li&gt;
&lt;li&gt;자주 쓰여서 분석하기 쉬운 어구에서부터 모호해서 분석하기 어려운 어구까지 모두 동등하게 점수에 반영되기 때문에 전반적으로 점수의 변별력이 낮고 원하는 능력만 비교하기가 어렵다.&lt;/li&gt;
&lt;/ol&gt;
&lt;p style=&quot;text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;다양한 도메인의 문장을 잘 분석해내는지 평가하는게 필수적인데, 이 방법으로 평가를 진행하려면 품사 태그별로 평가 데이터를 다양한 도메인에 대해 전부 구축해야합니다. (누가 데이터 구축하라고 돈 주는 것도 아닌데...) 이렇게 평가 데이터를 구축하는 것은 현실적이지 않다고 판단했기 때문에 형태소 분석 결과 그 자체를 비교하는 것은 &lt;a href=&quot;https://github.com/bab2min/Kiwi/tree/4d4a5e333f8ec478f524f29affc2eff96cd9b6f1/eval_data&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;기존에 구축해둔 소량의 데이터&lt;/a&gt;에 대해서만 수행하기로 했고, 이 평가 결과는 성능 자체를 측정한다기보다는 분석기가 문제 없이 잘 작동한다는 것을 확인하기 위한 리그레션 테스트(Regression Test)의 목적으로 수행하기로 했습니다.&lt;/p&gt;
&lt;table class=&quot;std-table&quot; data-ke-align=&quot;alignLeft&quot;&gt;&lt;caption&gt;형태소 분석 평가 예시&lt;/caption&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;th&gt;입력&lt;/th&gt;
&lt;td&gt;내일이면 또 불금에 주말이 다가오네요~~&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;th&gt;모범 답안&lt;/th&gt;
&lt;td&gt;내일/NNG 이/VCP 면/EC 또/MAG &lt;b&gt;불금/NNG&lt;/b&gt; 에/JKB 주말/NNG 이/JKS 다가오/VV 네요/EF ~~/SO&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;th&gt;분석기 출력&lt;/th&gt;
&lt;td&gt;내일/NNG 이/VCP 면/EC 또/MAG &lt;b&gt;불/VV ᆯ/ETM 금/NNG&lt;/b&gt; 에/JKB 주말/NNG 이/JKS 다가오/VV 네요/EF ~~/SO&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;th&gt;평가 결과(F1)&lt;/th&gt;
&lt;td&gt;0.7142&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p style=&quot;text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;그래서 형태소 분석 결과 비교 대신 도입한 것은 모호성 해소 평가입니다. 이 평가에 대해서는 &lt;a href=&quot;https://bab2min.tistory.com/672&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;예전에 한번 다룬 적&lt;/a&gt;이 있습니다. 저 당시에 구축했던 모호성 평가 데이터셋은 200여 문장으로 구성되어 있었는데 Kiwi의 내장 언어 모델이 개선되면서 전반적으로 난이도가 낮아졌다고 판단하여 더 어려운 평가 사례를 추가하여 &lt;a href=&quot;https://github.com/bab2min/kiwipiepy/tree/f6b65b752f790f147d059e0544f26849477b9749/benchmark/disambiguate/testset&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;총 510문장으로 데이터셋을 확충&lt;/a&gt;했습니다.&lt;/p&gt;
&lt;table class=&quot;std-table&quot; data-ke-align=&quot;alignLeft&quot;&gt;&lt;caption&gt;모호성 해소 평가 예시&lt;/caption&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;th&gt;입력&lt;/th&gt;
&lt;td&gt;이집트 역시 물물교환을 위한 시장을 가지고 있었고, 그리스도 마찬가지였다.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;th&gt;모범 답안&lt;/th&gt;
&lt;td&gt;그리스/NNP&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;th&gt;분석기 출력&lt;/th&gt;
&lt;td&gt;이집트/NNP 역시/MAG 물물교환/NNG 을/JKO 위하/VV ㄴ/ETM 시장/NNG 을/JKO 가지/VV 고/EC 있/VV 었/EP 고/EC ,/SP &lt;b&gt;그리스도/NNP&lt;/b&gt; 마찬가지/NNG 이/VCP 었/EP 다/EF ./SF&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;th&gt;평가 결과(1 or 0)&lt;/th&gt;
&lt;td&gt;0&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p style=&quot;text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;그리고 모호성 해소 평가와 더불어서 문장 분리 성능도 분석기의 성능을 평가하는데에 사용할 수 있습니다. 문장 분리 과제는 결국 문장이 끝나는 지점을 찾아내는 것이고, 이는 특정 어미가 종결어미인지 비종결어미인지 판단하는 것과 동일합니다. 이것은 어떻게 보면 종결어미일 수도 있고 종결어미가 아닐 수도 있는 형태소의 모호성을 해소하는 것인 셈이니 사실 모호성 해소 평가의 또 다른 버전이라고 볼 수도 있습니다. 문장 분리 평가 데이터셋은 &lt;a href=&quot;https://github.com/bab2min/kiwipiepy/tree/4a205e5a6d25d51b47bb7ced74fd4fdfe139f9c1/benchmark/sentence_split/testset&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;2023년 3월에 구축된 버전&lt;/a&gt;도 여전히 충분히 어렵다고 판단해 그대로 사용하기로 했습니다.&lt;/p&gt;
&lt;table class=&quot;std-table&quot; data-ke-align=&quot;alignLeft&quot;&gt;&lt;caption&gt;문장 분리 평가 예시&lt;/caption&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;th&gt;입력&lt;/th&gt;
&lt;td&gt;알겠지 그러니 나와 친해지지 마 가까워지지 마&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;th&gt;모범 답안&lt;/th&gt;
  &lt;td&gt;알겠지 &lt;span class=&quot;sep&quot;&gt;/&lt;/span&gt; 그러니 나와 친해지지 마 &lt;span class=&quot;sep&quot;&gt;/&lt;/span&gt; 가까워지지 마&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;th&gt;분석기 출력&lt;/th&gt;
&lt;td&gt;알겠지 그러니 나와 친해지지 마 &lt;span class=&quot;sep&quot;&gt;/&lt;/span&gt; 가까워지지 마&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;th&gt;평가 결과(Norm F1)&lt;/th&gt;
&lt;td&gt;0.6667&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;h3 style=&quot;text-align: justify;&quot; data-ke-size=&quot;size23&quot;&gt;베이스라인&lt;/h3&gt;
&lt;p style=&quot;text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;새 모델이 얼마나 잘하는지를 알기 위해서는 기존 분석기들의 성능을 먼저 파악해야합니다. 그래서 다음과 같이 자주 쓰이는 형태소 분석기들을 뽑아 보았습니다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;Komoran: &lt;a href=&quot;https://www.shineware.co.kr/products/komoran/&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;Shineware에서 개발한 오픈소스 형태소 분석기&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;MeCab: 원래는&amp;nbsp;&lt;a href=&quot;https://taku910.github.io/mecab/&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;일본 교토대에서 개발한 오픈소스 형태소 분석기&lt;/a&gt;이나 언어 구조가 유사한 한국어를 분석하는 데에도 사용가능하여 &lt;a href=&quot;https://github.com/hephaex/mecab-ko/blob/master/mecab-ko-dic/seed/README.md&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;은전한닢이라는 프로젝트&lt;/a&gt;를 통해 한국어도 지원하게 됨&lt;/li&gt;
&lt;li&gt;Kkma: &lt;a href=&quot;http://kkma.snu.ac.kr/&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;서울대학교 IDS연구실에서 개발한 오픈소스 형태소 분석기&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;Hannanum: &lt;a href=&quot;https://kldp.net/hannanum/&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;KAIST Semantic Web Research Center에서 개발한 오픈소스 형태소 분석기&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;Okt: &lt;a href=&quot;https://github.com/open-korean-text/open-korean-text&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;구 Twitter-Korean-Text라는 분석기의 공식 포크 버전&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;Khaiii: &lt;a href=&quot;https://github.com/kakao/khaiii&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;카카오에서 개발한 오픈소스 형태소 분석기&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;Bareun (v3.0.rc2-1): &lt;a href=&quot;https://bareun.ai/&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;바른 형태소 분석기&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;Kiwi (v0.20.4, KnLM &amp;amp; SBG 모델)&lt;/li&gt;
&lt;/ul&gt;
&lt;p style=&quot;text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;베이스라인으로 삼은 분석기들이 모호성 해소나 문장 분리 평가를 모두 지원하는 건 아니지만, 일부 평가만으로도 전반적인 경향성을 파악하는 데에는 문제가 없습니다. 베이스라인과 1차로 학습한 CoNg Global 모델의 평가 결과를 정리해보면 아래와 같습니다.&lt;/p&gt;
&lt;table class=&quot;std-table&quot; data-ke-align=&quot;alignLeft&quot;&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;th&gt;분석기&lt;/th&gt;
&lt;th&gt;형태소 분석(F1, %)&lt;/th&gt;
&lt;th&gt;모호성 해소(Acc, %)&lt;/th&gt;
&lt;th&gt;문장 분리(Norm F1, %)&lt;/th&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;th&gt;Komoran&lt;/th&gt;
&lt;td&gt;-&lt;/td&gt;
&lt;td&gt;46.65&lt;/td&gt;
&lt;td&gt;-&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;th&gt;MeCab&lt;/th&gt;
&lt;td&gt;-&lt;/td&gt;
&lt;td&gt;58.55&lt;/td&gt;
&lt;td&gt;-&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;th&gt;Kkma&lt;/th&gt;
&lt;td&gt;-&lt;/td&gt;
&lt;td&gt;51.20&lt;/td&gt;
&lt;td&gt;-&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;th&gt;Hannanum&lt;/th&gt;
&lt;td&gt;-&lt;/td&gt;
&lt;td&gt;-&lt;/td&gt;
&lt;td&gt;56.73&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;th&gt;Okt&lt;/th&gt;
&lt;td&gt;-&lt;/td&gt;
&lt;td&gt;-&lt;/td&gt;
&lt;td&gt;58.74&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;th&gt;Khaiii&lt;/th&gt;
&lt;td&gt;-&lt;/td&gt;
&lt;td&gt;54.32&lt;/td&gt;
&lt;td&gt;-&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;th&gt;Bareun&lt;/th&gt;
&lt;td&gt;-&lt;/td&gt;
&lt;td&gt;69.86&lt;/td&gt;
&lt;td&gt;53.78&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;th&gt;Kiwi KnLM&lt;/th&gt;
&lt;td&gt;&lt;b&gt;81.95&lt;/b&gt;&lt;/td&gt;
&lt;td&gt;54.03&lt;/td&gt;
&lt;td&gt;80.55&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;th&gt;Kiwi SBG&lt;/th&gt;
&lt;td&gt;81.89&lt;/td&gt;
&lt;td&gt;65.59&lt;/td&gt;
&lt;td&gt;81.77&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;th colspan=&quot;4&quot;&gt;&amp;nbsp;&lt;/th&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;th&gt;Kiwi CoNg Global&lt;/th&gt;
&lt;td&gt;81.36&lt;/td&gt;
&lt;td&gt;&lt;b&gt;82.03&lt;/b&gt;&lt;/td&gt;
&lt;td&gt;&lt;b&gt;83.76&lt;/b&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p style=&quot;text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;형태소 분석 평가의 경우 Kiwi에 업데이트를 거치며 여러 독자적인 품사 태그가 추가되었기에 기존 분석기들과 호환되지 않아서 Kiwi 간의 비교만 가능합니다. 모호성 해소 평가의 경우 Hannanum이나 Okt는 동사와 형용사를 구별하지 못하기 평가가 불가능하구요, 또 문장 분리를 직접 지원하지 않는 분석기도 제법 있습니다. 이런 결측값은 무시하기로 하고 베이스라인의 성능들을 살펴보면 모호성 해소에 대해서는 &lt;b&gt;Bareun&lt;/b&gt;의 성능이 가장 높았고, 문장 분리는 타 분석기에서는 &lt;b&gt;Okt&lt;/b&gt;, Kiwi에서는 &lt;b&gt;Kiwi SBG&lt;/b&gt;가 가장 높은 성능을 보였습니다.&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;새로 개발한 CoNg 모델의 경우 별도의 튜닝도 없이 모호성 해소 성능과 문장 분리 성능에서 기존 분석기들을 압도했습니다! 그런데 이상하게 형태소 분석 성능은 81.95 &amp;rarr; 81.36으로 기존보다 내려가는 양상을 보이고 있습니다. 아니 어째서?! 이론상으로는 CoNg이 KnLM보다 아주 우월한 모델이어야 하는데요, 도대체 어떤 부분이 문제였는지 살펴봐야겠네요.&lt;/p&gt;
&lt;h3 style=&quot;text-align: justify;&quot; data-ke-size=&quot;size23&quot;&gt;형태소 분석 성능 하락 원인 탐색 &amp;amp; 해결책&lt;/h3&gt;
&lt;p style=&quot;text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;Kiwi CoNg Global의 성능이 내려간 원인을 찾기 위해 KnLM이나 SBG 모델의 분석 결과와 비교해서 CoNg Global의 분석 결과가 어떻게 달라졌는지 살펴봅시다.&lt;/p&gt;
&lt;table class=&quot;std-table&quot; data-ke-align=&quot;alignLeft&quot;&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;th&gt;입력 문장&lt;/th&gt;
&lt;td&gt;그점에서 &amp;lt;두들버그&amp;gt;의 유일한 출연진이자 주연이었던&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;th&gt;모범 답안&lt;/th&gt;
&lt;td&gt;그/MM 점/NNG 에서/JKB &amp;lt;/SSO 두들버그/NNP &amp;gt;/SSC 의/JKG 유일/NNG 하/XSA ᆫ/ETM 출연진/NNG 이/VCP 자/EC 주연/NNG 이/VCP 었/EP 던/ETM&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;th&gt;KnLM 결과&lt;/th&gt;
&lt;td&gt;그/MM 점/NNG 에서/JKB &amp;lt;/SSO &lt;b&gt;두들버그/NNP&lt;/b&gt; &amp;gt;/SSC 의/JKG 유일/NNG 하/XSA ᆫ/ETM 출연진/NNG 이/VCP 자/EC 주연/NNG 이/VCP 었/EP 던/ETM&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;th&gt;CoNg 결과&lt;/th&gt;
&lt;td&gt;그/MM 점/NNG 에서/JKB &amp;lt;/SSO &lt;b&gt;두/MM 들/XSN 버그/NNG&lt;/b&gt; &amp;gt;/SSC 의/JKG 유일/NNG 하/XSA ᆫ/ETM 출연진/NNG 이/VCP 자/EC 주연/NNG 이/VCP 었/EP 던/ETM&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;table class=&quot;std-table&quot; data-ke-align=&quot;alignLeft&quot;&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;th&gt;입력 문장&lt;/th&gt;
&lt;td&gt;문래역 근처 맛집 화덕구이 피자 - 피자팩토리&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;th&gt;모범 답안&lt;/th&gt;
&lt;td&gt;문래역/NNP 근처/NNG 맛집/NNG 화덕/NNG 구이/NNG 피자/NNG -/SO 피자팩토리/NNP&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;th&gt;KnLM 결과&lt;/th&gt;
&lt;td&gt;&lt;b&gt;문래역/NNP&lt;/b&gt; 근처/NNG 맛집/NNG 화덕/NNG 구이/NNG 피자/NNG -/SO &lt;b&gt;피자팩토리/NNP&lt;/b&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;th&gt;CoNg 결과&lt;/th&gt;
&lt;td&gt;&lt;b&gt;문/NNP 래/NNG 역/NNG&lt;/b&gt; 근처/NNG 맛집/NNG 화덕/NNG 구이/NNG 피자/NNG -/SO &lt;b&gt;피자/NNG 팩토리/NNP&lt;/b&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p style=&quot;text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;위의 두 사례가 CoNg 및 CoNg Global에서 형태소 분석 성능이 떨어지는 이유를 잘 보여주고 있습니다. 모델이 잘 모르는 단어를 자꾸 자기 멋대로 분해하는 현상입니다. 이 현상은 현재 Kiwi의 대표적인 한계 중 하나로 OOV(Out-Of-Vocabulary: 분석기 사전에 등록되지 않은 단어)에서 기인하는 오류 때문입니다. 문장을 올바르게 형태소로 쪼개기 위해서는 분석기는 어떤 형태소들이 있는지 미리 알고 있어야 합니다. 분석기에 최대한 많은 형태소를 입력해둔다고 해도 언어는 끊임없이 변화하면서 새로운 형태소가 만들어지기 때문에 형태소 분석기는 어쩔 수 없이 맞닥뜨릴 수 밖에 없습니다.&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;&quot;두들버그&quot;를 예로 들어보면, 모델은 두들버그라는 게 영화 이름이라는 것을 모르므로 이를 하나의 형태소로 분석하는 대신 자신이 아는 형태소들의 조합으로 표현하게 됩니다. &quot;&lt;b&gt;두/MM 들/XSN 버그/NNG&lt;/b&gt;&quot;도 그런 조합 중 하나인데요, 사실 한국어 지식이 있는 사람이라면 누구나 관형사(MM) 뒤에 명사형 접미사(XSN)가 붙는건 불가능하므로 말도 안되는 분석이라는 걸 알 수 있습니다. KnLM 모델에서는 왜 이런 현상이 안 나타날까요? 사실은 KnLM에서도 동일한 현상이 발생하지만 Kiwi의 보정 로직이 이를 방지합니다. &lt;b&gt;언어 모델 탐색 점수가 너무 낮은 경우&lt;/b&gt;(&lt;b&gt;두/MM&lt;/b&gt; 뒤에 &lt;b&gt;들/XSN&lt;/b&gt;이 오는 경우는 아예 없으므로 KnLM에서는 거의 -20에 가까운 로그 확률을 부여하게 됩니다.) 뭔가 &lt;b&gt;잘못된 분석이라는 판단&lt;/b&gt;하에 해당 문자열을 형태소로 쪼개는 대신 전체를 신조어로 추정하는 보정 로직이 있거든요. 이 덕분에 분석기는 &quot;두들버그&quot;가 뭔지 모르지만 일단 쪼개지 않고 NNP로 태그를 부여하게 됩니다.&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;동일한 보정 로직이 왜 CoNg 모델에도 들어가 있는데 왜 동작하지 않았을까요? 이는 신경망 모델의 (과도한) 일반화 능력 때문이라고 볼 수 있습니다. 실제로 CoNg 모델은 &lt;b&gt;두/MM&lt;/b&gt; 뒤에 &lt;b&gt;들/XSN&lt;/b&gt;이 나타나는 일을 -10 정도의 로그 확률로 추정합니다. KnLM 대비 후하게 점수를 주는 것이죠. 모델은 자신이 학습한 사례에서 두/MM 들/XSN이 연속해서 나오는 패턴이 없었음에도 다른 사례들을 보고 일반화하여 자기멋대로 &quot;두/MM 들/XSN&quot;도 가능하다고 판단한 것입니다. 근데 사실 좋은 모델이라면 학습 과정을 통해 저런 이상한 패턴은 생성이 불가능하다는 걸 스스로 깨닫고 저런 배열에 대해서는 아주 낮은 확률을 부여해야하는 것 아닐까요?&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;모델이 이렇게 학습되는 이유는 학습 목표와 관련되어 있습니다. 일반적으로 언어 모델을 학습할때는 실제로 쓰이는 문장들을 입력하기 때문에 모델은 올바른 형태소 배열을 학습하게 됩니다. 확률의 관점에서 이야기하자면 올바른 형태소 배열에 대한 확률을 높이는 과정(Likelihood Maximization)에서, 그렇지 않은 배열들에 대한 확률이 자동적으로 낮아지는 구조인 것입니다. 즉, 모델은 잘못된 형태소 배열에 대해서는 한번도 따로 배우지 않습니다. 이 방법은 학습에 쓰는 말뭉치가 아주 큰 경우에는 정말정말 &quot;바른 문장&quot;들을 다양하게 대량으로 보게 되기 때문에 크게 문제가 되지 않습니다. 문제는 말뭉치가 충분히 크지 않은 경우지요. 모델이 보지 못한 문장이 있을때, 이게 올바른 문장이지만 데이터의 다양성이 적어서 모델이 못 봤던 것인지 아니면 실제로 잘못된 문장이라서 못 봤던 것인지 모델 입장에서는 구별할 방법이 없습니다. 이 때문에 올바른 배열과 그렇지 않은 배열 간의 확률 차이를 크게 키우는게 불가능해지는 것이지요.&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;Kiwi가 필요로 하는 것은 형태소 기반의 언어 모델이므로 이 문제를 풀려면 형태소 분석이 되어있는 대량의 말뭉치가 필요합니다. 그러나 이를 대량으로 확보하는건 불가능한 상황이죠. 따라서 올바른 분석 결과를 더 추가하는 대신 모델에게 잘못된 분석 결과를 보여주면서 이 배열들의 확률을 낮추도록 하는 학습 목표(Unlikelihood Minimization)를 추가했습니다. 기존 방식이 틀린 분석 결과에 대해서는 암묵적으로 피드백을 주었다면 이 방식은 명시적으로 피드백을 준 것이라고 볼 수 있습니다. Unlikelihood Training에 대해 더 궁금하신 분들은 &lt;a href=&quot;https://arxiv.org/pdf/1908.04319&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;Welleck, Sean, et al. &quot;Neural text generation with unlikelihood training.&quot;&amp;nbsp;&lt;i&gt;arXiv preprint arXiv:1908.04319&lt;/i&gt;&amp;nbsp;(2019).&lt;/a&gt;&amp;nbsp; 논문을 참고하시길 바랍니다.&amp;nbsp;&lt;/p&gt;
&lt;table class=&quot;std-table&quot; data-ke-align=&quot;alignLeft&quot;&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;th&gt;기존의 언어 모델 학습 목표(Likelihood Maximization)&lt;/th&gt;
&lt;td&gt;$P($ 그/MM 점/NNG 에서/JKB &amp;lt;/SSO 두들버그/NNP &amp;gt;/SSC 의/JKG 유일/NNG 하/XSA ᆫ/ETM $)$ :&lt;big&gt;&amp;uarr;&lt;/big&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;th rowspan=&quot;2&quot;&gt;변경된 언어 모델 학습 목표(+Unlikelihood Minimization)&lt;/th&gt;
&lt;td&gt;$P($ 그/MM 점/NNG 에서/JKB &amp;lt;/SSO 두들버그/NNP &amp;gt;/SSC 의/JKG 유일/NNG 하/XSA ᆫ/ETM $)$ :&lt;big&gt;&amp;uarr;&lt;/big&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;$P($ 그/MM 점/NNG 에서/JKB &amp;lt;/SSO 두/MM 들/XSN 버그/NNG &amp;gt;/SSC 의/JKG 유일/NNG 하/XSA ᆫ/ETM $)$ :&lt;big&gt;&amp;darr;&lt;/big&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p style=&quot;text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;잘못된 분석 결과를 대량으로 만드는 것은 상대적으로 쉽게 해결 가능합니다. 일단 올바르게 형태소 분석된 결과를 알고 있으니 여기에서 고유 명사를 일부러 강제로 더 쪼개도록 형태소 분석기에게 시키면 됩니다. 그럼 1개의 문장에 대해서도 수십개의 잘못된 분석 결과가 나오게 되죠. 이를 통해서 Unlikelihood Minimization을 수행할 수 있습니다.&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;추가로 모델이 더 다양한 문장을 학습할 수 있도록 웹 말뭉치를 추가했습니다. 웹 텍스트의 경우 형태분석 결과가 라벨링된 데이터가 별도로 없었으므로 먼저 Kiw KnLM과 Kiwi CoNg이 동일하게 분석하는 사례들만 모아서 자동 라벨링된 데이터셋을 확보했습니다. 자동으로 만들어진 데이터셋의 특성상 품질의 문제가 있을수 있으므로 학습 시에 이 데이터는 50%까지만 보고 나머지 50%는 사람이 직접 구축한 양질의 기존 데이터셋을 보도록 비율을 맞췄습니다. 이렇게 v2 버전을 학습해본 결과는 아래와 같습니다.&lt;/p&gt;
&lt;table class=&quot;std-table&quot; data-ke-align=&quot;alignLeft&quot;&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;th&gt;분석기&lt;/th&gt;
&lt;th&gt;형태소 분석(F1, %)&lt;/th&gt;
&lt;th&gt;모호성 해소(Acc, %)&lt;/th&gt;
&lt;th&gt;문장 분리(Norm F1, %)&lt;/th&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;th&gt;Kiwi KnLM&lt;/th&gt;
&lt;td&gt;&lt;b&gt;81.95&lt;/b&gt;&lt;/td&gt;
&lt;td&gt;54.03&lt;/td&gt;
&lt;td&gt;80.55&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;th&gt;Kiwi SBG&lt;/th&gt;
&lt;td&gt;81.89&lt;/td&gt;
&lt;td&gt;65.59&lt;/td&gt;
&lt;td&gt;81.77&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;th colspan=&quot;4&quot;&gt;&amp;nbsp;&lt;/th&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;th&gt;Kiwi CoNg Global&lt;/th&gt;
&lt;td&gt;81.36&lt;/td&gt;
&lt;td&gt;82.03&lt;/td&gt;
&lt;td&gt;83.76&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;th&gt;Kiwi CoNg Global v2&lt;/th&gt;
&lt;td&gt;81.61&lt;/td&gt;
&lt;td&gt;&lt;b&gt;87.46&lt;/b&gt;&lt;/td&gt;
&lt;td&gt;&lt;b&gt;84.11&lt;/b&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p style=&quot;text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;사실 형태소 분석 쪽의 성능이 크게 개선되길 바라고 진행한 튜닝이었으나, 실제 개선 폭은 모호성 해소 쪽에서 제일 크다는 의외의 결과를 얻었습니다. 형태소 분석 평가만 보자면 여전히 KnLM이 제일 높아서 몇 가지 튜닝을 더 시도해보았으나 더 개선된 결과를 얻을 순 없었습니다. 어쨌든 모든 과제에서 성능이 올랐으니 이 정도에서 만족하고 최종 모델을 위한 학습 방법을 결정 지었습니다. (이하에서 CoNg이라고 표기되는 모델들은 모두 이 개선된 학습 방법을 채용한 모델을 가리킵니다.)&lt;/p&gt;
&lt;h3 style=&quot;text-align: justify;&quot; data-ke-size=&quot;size23&quot;&gt;모호성 해소 성능의 극적인 개선&lt;/h3&gt;
&lt;p style=&quot;text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;KnLM 및 SBG 모델 대비 성능이 가장 크게 개선된 과제는 바로 모호성 해소입니다. 과연 실제로 어떤 부분에서 개선이 있었는지 살펴보도록 할까요? 일단 평가 항목별로 점수를 나눠서 측정하면 아래와 같습니다. (Kiwi 외에도 높은 모호성 해소 성능을 보였던 바른 형태소 분석기도 함께 분석의 대상으로 잡아보았습니다. 아래의 결과는 &lt;a href=&quot;https://github.com/bab2min/kiwipiepy/tree/main/benchmark/disambiguate&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;이 코드&lt;/a&gt;를 사용하여 재현해 볼 수 있습니다.)&lt;/p&gt;
&lt;table class=&quot;std-table&quot; data-ke-align=&quot;alignLeft&quot;&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;th&gt;분석기&lt;/th&gt;
&lt;th&gt;명사&lt;/th&gt;
&lt;th&gt;불규칙 동사&lt;/th&gt;
&lt;th&gt;동사/형용사&lt;/th&gt;
&lt;th&gt;원거리&lt;/th&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;th&gt;Kiwi KnLM&lt;/th&gt;
&lt;td&gt;46.02&lt;/td&gt;
&lt;td&gt;47.77&lt;/td&gt;
&lt;td&gt;65.94&lt;/td&gt;
&lt;td&gt;56.41&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;th&gt;Kiwi SBG&lt;/th&gt;
&lt;td&gt;62.50&lt;/td&gt;
&lt;td&gt;56.68&lt;/td&gt;
&lt;td&gt;68.84&lt;/td&gt;
&lt;td&gt;74.35&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;th&gt;Kiwi CoNg&lt;/th&gt;
&lt;td&gt;89.77&lt;/td&gt;
&lt;td&gt;74.52&lt;/td&gt;
&lt;td&gt;84.05&lt;/td&gt;
&lt;td&gt;66.66&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;th&gt;Kiwi CoNg Global&lt;/th&gt;
&lt;td&gt;&lt;b&gt;94.31&lt;/b&gt;&lt;/td&gt;
&lt;td&gt;&lt;b&gt;77.07&lt;/b&gt;&lt;/td&gt;
&lt;td&gt;&lt;b&gt;86.23&lt;/b&gt;&lt;/td&gt;
&lt;td&gt;&lt;b&gt;89.74&lt;/b&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;th&gt;Bareun&lt;/th&gt;
&lt;td&gt;71.59&lt;/td&gt;
&lt;td&gt;66.88&lt;/td&gt;
&lt;td&gt;71.74&lt;/td&gt;
&lt;td&gt;69.23&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p style=&quot;text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;CoNg과 CoNg Global의 경우 모든 부분에서 크게 개선되었지만 특히 명사의 모호성 해소 성능이 제일 극적이네요. 명사부터 살펴보도록 하겠습니다.&lt;/p&gt;
&lt;div data-ke-type=&quot;moreLess&quot; data-text-more=&quot;명사 개선 사례 보기&quot; data-text-less=&quot;명사 개선 사례 닫기&quot;&gt;&lt;a class=&quot;btn-toggle-moreless&quot;&gt;더보기&lt;/a&gt;
&lt;div class=&quot;moreless-content&quot;&gt;
&lt;table class=&quot;std-table&quot; data-ke-align=&quot;alignLeft&quot;&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;th rowspan=&quot;5&quot;&gt;박씨를 귀가 조치하고 불구속 수사하기로 결정했다. &lt;br /&gt;(귀가/N)&lt;/th&gt;
&lt;th class=&quot;correct&quot;&gt;kiwi-knlm&lt;/th&gt;
&lt;td class=&quot;correct&quot;&gt;박/N 씨/N 를/J &lt;b&gt;귀가/N&lt;/b&gt; 조치/N 하/X 고/E 불/X 구속/N 수사/N 하/X 기/E 로/J 결정/N 하/X 었/E 다/E ./S&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;th class=&quot;correct&quot;&gt;kiwi-sbg&lt;/th&gt;
&lt;td class=&quot;correct&quot;&gt;박/N 씨/N 를/J &lt;b&gt;귀가/N&lt;/b&gt; 조치/N 하/X 고/E 불/X 구속/N 수사/N 하/X 기/E 로/J 결정/N 하/X 었/E 다/E ./S&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;th class=&quot;correct&quot;&gt;kiwi-cong&lt;/th&gt;
&lt;td class=&quot;correct&quot;&gt;박/N 씨/N 를/J &lt;b&gt;귀가/N&lt;/b&gt; 조치/N 하/X 고/E 불/X 구속/N 수사/N 하/X 기/E 로/J 결정/N 하/X 었/E 다/E ./S&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;th class=&quot;correct&quot;&gt;kiwi-cong-global&lt;/th&gt;
&lt;td class=&quot;correct&quot;&gt;박/N 씨/N 를/J &lt;b&gt;귀가/N&lt;/b&gt; 조치/N 하/X 고/E 불/X 구속/N 수사/N 하/X 기/E 로/J 결정/N 하/X 었/E 다/E ./S&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;th class=&quot;incorrect&quot;&gt;bareun&lt;/th&gt;
&lt;td class=&quot;incorrect&quot;&gt;박씨/N 를/J &lt;b&gt;귀/N 가/J&lt;/b&gt; 조치하/VV 고/E 불구속/N 수사하/VV 기/E 로/J 결정하/VV 았/E 다/E ./S&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;th rowspan=&quot;5&quot;&gt;모두의 눈과 귀가 집중된 행사!&lt;br /&gt;(귀/N 가/J)&lt;/th&gt;
&lt;th class=&quot;correct&quot;&gt;kiwi-knlm&lt;/th&gt;
&lt;td class=&quot;correct&quot;&gt;모두/N 의/J 눈/N 과/J &lt;b&gt;귀/N&lt;/b&gt; 가/J 집중/N 되/X ᆫ/E 행사/N !/S&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;th class=&quot;correct&quot;&gt;kiwi-sbg&lt;/th&gt;
&lt;td class=&quot;correct&quot;&gt;모두/N 의/J 눈/N 과/J &lt;b&gt;귀/N&lt;/b&gt; 가/J 집중/N 되/X ᆫ/E 행사/N !/S&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;th class=&quot;correct&quot;&gt;kiwi-cong&lt;/th&gt;
&lt;td class=&quot;correct&quot;&gt;모두/N 의/J 눈/N 과/J &lt;b&gt;귀/N&lt;/b&gt; 가/J 집중/N 되/X ᆫ/E 행사/N !/S&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;th class=&quot;correct&quot;&gt;kiwi-cong-global&lt;/th&gt;
&lt;td class=&quot;correct&quot;&gt;모두/N 의/J 눈/N 과/J &lt;b&gt;귀/N&lt;/b&gt; 가/J 집중/N 되/X ᆫ/E 행사/N !/S&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;th class=&quot;correct&quot;&gt;bareun&lt;/th&gt;
&lt;td class=&quot;correct&quot;&gt;모두/N 의/J 눈/N 과/J &lt;b&gt;귀/N&lt;/b&gt; 가/J 집중되/VV ㄴ/E 행사/N !/S&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;th rowspan=&quot;5&quot;&gt;그런데 강의 듣는 사람들 입장은 또 다르대.&lt;br /&gt;(강의/N)&lt;/th&gt;
&lt;th class=&quot;correct&quot;&gt;kiwi-knlm&lt;/th&gt;
&lt;td class=&quot;correct&quot;&gt;그런데/M &lt;b&gt;강의/N&lt;/b&gt; 듣/VV 는/E 사람/N 들/X 입장/N 은/J 또/M 다르/VA 대/E ./S&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;th class=&quot;correct&quot;&gt;kiwi-sbg&lt;/th&gt;
&lt;td class=&quot;correct&quot;&gt;그런데/M &lt;b&gt;강의/N&lt;/b&gt; 듣/VV 는/E 사람/N 들/X 입장/N 은/J 또/M 다르/VA 대/E ./S&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;th class=&quot;correct&quot;&gt;kiwi-cong&lt;/th&gt;
&lt;td class=&quot;correct&quot;&gt;그런데/M &lt;b&gt;강의/N&lt;/b&gt; 듣/VV 는/E 사람/N 들/X 입장/N 은/J 또/M 다르/VA 대/E ./S&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;th class=&quot;correct&quot;&gt;kiwi-cong-global&lt;/th&gt;
&lt;td class=&quot;correct&quot;&gt;그런데/M &lt;b&gt;강의/N&lt;/b&gt; 듣/VV 는/E 사람/N 들/X 입장/N 은/J 또/M 다르/VA 대/E ./S&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;th class=&quot;correct&quot;&gt;bareun&lt;/th&gt;
&lt;td class=&quot;correct&quot;&gt;그런데/M &lt;b&gt;강의/N&lt;/b&gt; 듣/VV 는/E 사람/N 들/X 입장/N 은/J 또/M 다르/VA 대/E ./S&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;th rowspan=&quot;5&quot;&gt;4대 강의 수문을 열고 진실을 파헤쳐야한다.&lt;br /&gt;(강/N 의/J)&lt;/th&gt;
&lt;th class=&quot;correct&quot;&gt;kiwi-knlm&lt;/th&gt;
&lt;td class=&quot;correct&quot;&gt;4/S 대/N &lt;b&gt;강/N&lt;/b&gt; 의/J 수문/N 을/J 열/VV 고/E 진실/N 을/J 파헤치/VV 어야/E 하/VX ᆫ다/E ./S&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;th class=&quot;correct&quot;&gt;kiwi-sbg&lt;/th&gt;
&lt;td class=&quot;correct&quot;&gt;4/S 대/N &lt;b&gt;강/N&lt;/b&gt; 의/J 수문/N 을/J 열/VV 고/E 진실/N 을/J 파헤치/VV 어야/E 하/VX ᆫ다/E ./S&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;th class=&quot;correct&quot;&gt;kiwi-cong&lt;/th&gt;
&lt;td class=&quot;correct&quot;&gt;4/S 대/N &lt;b&gt;강/N&lt;/b&gt; 의/J 수문/N 을/J 열/VV 고/E 진실/N 을/J 파헤치/VV 어야/E 하/VX ᆫ다/E ./S&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;th class=&quot;correct&quot;&gt;kiwi-cong-global&lt;/th&gt;
&lt;td class=&quot;correct&quot;&gt;4/S 대/N &lt;b&gt;강/N&lt;/b&gt; 의/J 수문/N 을/J 열/VV 고/E 진실/N 을/J 파헤치/VV 어야/E 하/VX ᆫ다/E ./S&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;th class=&quot;incorrect&quot;&gt;bareun&lt;/th&gt;
&lt;td class=&quot;incorrect&quot;&gt;4/S 대/N &lt;b&gt;강의/N&lt;/b&gt; 수문/N 을/J 열/VV 고/E 진실/N 을/J 파헤치/VV 어야/E 하/VX ㄴ다/E ./S&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;th rowspan=&quot;5&quot;&gt;이에 입각하여 보니 어느 정도 납득이 갔습니다.&lt;br /&gt;(정도/N)&lt;/th&gt;
&lt;th class=&quot;incorrect&quot;&gt;kiwi-knlm&lt;/th&gt;
&lt;td class=&quot;incorrect&quot;&gt;이/N 에/J 입각/N 하/X 어/E 보/VX 니/E 어느/M &lt;b&gt;정/N 도/J&lt;/b&gt; 납득/N 이/J 가/VV 었/E 습니다/E ./S&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;th class=&quot;correct&quot;&gt;kiwi-sbg&lt;/th&gt;
&lt;td class=&quot;correct&quot;&gt;이/N 에/J 입각/N 하/X 어/E 보/VX 니/E 어느/M &lt;b&gt;정도/N&lt;/b&gt; 납득/N 이/J 가/VV 었/E 습니다/E ./S&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;th class=&quot;correct&quot;&gt;kiwi-cong&lt;/th&gt;
&lt;td class=&quot;correct&quot;&gt;이/N 에/J 입각/N 하/X 어/E 보/VX 니/E 어느/M &lt;b&gt;정도/N&lt;/b&gt; 납득/N 이/J 가/VV 었/E 습니다/E ./S&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;th class=&quot;correct&quot;&gt;kiwi-cong-global&lt;/th&gt;
&lt;td class=&quot;correct&quot;&gt;이/N 에/J 입각/N 하/X 어/E 보/VX 니/E 어느/M &lt;b&gt;정도/N&lt;/b&gt; 납득/N 이/J 가/VV 었/E 습니다/E ./S&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;th class=&quot;correct&quot;&gt;bareun&lt;/th&gt;
&lt;td class=&quot;correct&quot;&gt;이/N 에/J 입각하/VV 아/E 보/VX 니/E 어느/M &lt;b&gt;정도/N&lt;/b&gt; 납득/N 이/J 가/VV 았/E 습니다/E ./S&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;th rowspan=&quot;5&quot;&gt;정도 많고 눈물도 많습니다.&lt;br /&gt;(정/N 도/J)&lt;/th&gt;
&lt;th class=&quot;correct&quot;&gt;kiwi-knlm&lt;/th&gt;
&lt;td class=&quot;correct&quot;&gt;&lt;b&gt;정/N&lt;/b&gt; 도/J 많/VA 고/E 눈물/N 도/J 많/VA 습니다/E ./S&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;th class=&quot;correct&quot;&gt;kiwi-sbg&lt;/th&gt;
&lt;td class=&quot;correct&quot;&gt;&lt;b&gt;정/N&lt;/b&gt; 도/J 많/VA 고/E 눈물/N 도/J 많/VA 습니다/E ./S&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;th class=&quot;correct&quot;&gt;kiwi-cong&lt;/th&gt;
&lt;td class=&quot;correct&quot;&gt;&lt;b&gt;정/N&lt;/b&gt; 도/J 많/VA 고/E 눈물/N 도/J 많/VA 습니다/E ./S&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;th class=&quot;correct&quot;&gt;kiwi-cong-global&lt;/th&gt;
&lt;td class=&quot;correct&quot;&gt;&lt;b&gt;정/N&lt;/b&gt; 도/J 많/VA 고/E 눈물/N 도/J 많/VA 습니다/E ./S&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;th class=&quot;incorrect&quot;&gt;bareun&lt;/th&gt;
&lt;td class=&quot;incorrect&quot;&gt;&lt;b&gt;정도/N&lt;/b&gt; 많/VA 고/E 눈물/N 도/J 많/VA 습니다/E ./S&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;th rowspan=&quot;5&quot;&gt;일반적으로 회의 주도자는 상사다.&lt;br /&gt;(회의/N)&lt;/th&gt;
&lt;th class=&quot;incorrect&quot;&gt;kiwi-knlm&lt;/th&gt;
&lt;td class=&quot;incorrect&quot;&gt;일반/N 적/X 으로/J &lt;b&gt;회/N 의/J&lt;/b&gt; 주도자/N 는/J 상사/N 이/VC 다/E ./S&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;th class=&quot;incorrect&quot;&gt;kiwi-sbg&lt;/th&gt;
&lt;td class=&quot;incorrect&quot;&gt;일반/N 적/X 으로/J &lt;b&gt;회/N 의/J&lt;/b&gt; 주도자/N 는/J 상사/N 이/VC 다/E ./S&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;th class=&quot;correct&quot;&gt;kiwi-cong&lt;/th&gt;
&lt;td class=&quot;correct&quot;&gt;일반/N 적/X 으로/J &lt;b&gt;회의/N&lt;/b&gt; 주도자/N 는/J 상사/N 이/VC 다/E ./S&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;th class=&quot;correct&quot;&gt;kiwi-cong-global&lt;/th&gt;
&lt;td class=&quot;correct&quot;&gt;일반/N 적/X 으로/J &lt;b&gt;회의/N&lt;/b&gt; 주도자/N 는/J 상사/N 이/VC 다/E ./S&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;th class=&quot;correct&quot;&gt;bareun&lt;/th&gt;
&lt;td class=&quot;correct&quot;&gt;일반적/N 으로/J &lt;b&gt;회의/N&lt;/b&gt; 주도자/N 는/J 상사/N 이/VC 다/E ./S&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;th rowspan=&quot;5&quot;&gt;귀한 있는 생선을 이 가격에, 심지어 회의 두께와 크기도 제가 딱 좋아하는 정도&lt;br /&gt;(회/N 의/J)&lt;/th&gt;
&lt;th class=&quot;correct&quot;&gt;kiwi-knlm&lt;/th&gt;
&lt;td class=&quot;correct&quot;&gt;귀하/VA ᆫ/E 있/VV 는/E 생선/N 을/J 이/M 가격/N 에/J ,/S 심지어/M &lt;b&gt;회/N&lt;/b&gt; 의/J 두께/N 와/J 크기/N 도/J 저/N 가/J 딱/M 좋아하/VV 는/E 정도/N&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;th class=&quot;correct&quot;&gt;kiwi-sbg&lt;/th&gt;
&lt;td class=&quot;correct&quot;&gt;귀하/VA ᆫ/E 있/VV 는/E 생선/N 을/J 이/M 가격/N 에/J ,/S 심지어/M &lt;b&gt;회/N&lt;/b&gt; 의/J 두께/N 와/J 크기/N 도/J 저/N 가/J 딱/M 좋아하/VV 는/E 정도/N&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;th class=&quot;correct&quot;&gt;kiwi-cong&lt;/th&gt;
&lt;td class=&quot;correct&quot;&gt;귀하/VA ᆫ/E 있/VV 는/E 생선/N 을/J 이/M 가격/N 에/J ,/S 심지어/M &lt;b&gt;회/N&lt;/b&gt; 의/J 두께/N 와/J 크기/N 도/J 저/N 가/J 딱/M 좋아하/VV 는/E 정도/N&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;th class=&quot;correct&quot;&gt;kiwi-cong-global&lt;/th&gt;
&lt;td class=&quot;correct&quot;&gt;귀하/VA ᆫ/E 있/VV 는/E 생선/N 을/J 이/M 가격/N 에/J ,/S 심지어/M &lt;b&gt;회/N&lt;/b&gt; 의/J 두께/N 와/J 크기/N 도/J 저/N 가/J 딱/M 좋아하/VV 는/E 정도/N&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;th class=&quot;incorrect&quot;&gt;bareun&lt;/th&gt;
&lt;td class=&quot;incorrect&quot;&gt;귀하/VA ㄴ/E 있/VA 는/E 생선/N 을/J 이/M 가격/N 에/J ,/S 심지어/M &lt;b&gt;회의/N&lt;/b&gt; 두께/N 와/J 크기/N 도/J 제/N 가/J 딱/M 좋아하/VV 는/E 정도/N&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p style=&quot;text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;확실히 Kiwi CoNg 계열의 모델들은 맥락에 맞는 명사를 선택하는 데에 있어서 거의 틀리지를 않고 있습니다. 반면 Kiwi KnLM, SBG나 Bareun은 종종 헷갈린다는걸 볼 수 있습니다.&lt;/p&gt;
&lt;div data-ke-type=&quot;moreLess&quot; data-text-more=&quot;불규칙 활용 개선 사례 보기&quot; data-text-less=&quot;불규칙 활용 개선 사례 닫기&quot;&gt;&lt;a class=&quot;btn-toggle-moreless&quot;&gt;더보기&lt;/a&gt;
&lt;div class=&quot;moreless-content&quot;&gt;
&lt;table class=&quot;std-table&quot; data-ke-align=&quot;alignLeft&quot;&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;th rowspan=&quot;5&quot;&gt;구름 속을 나는 용은 무엇을 상징하나&lt;br /&gt;(날/VV)&lt;/th&gt;
&lt;th class=&quot;correct&quot;&gt;kiwi-knlm&lt;/th&gt;
&lt;td class=&quot;correct&quot;&gt;구름/N 속/N 을/J &lt;b&gt;날/VV&lt;/b&gt; 는/E 용/N 은/J 무엇/N 을/J 상징/N 하/X 나/E&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;th class=&quot;incorrect&quot;&gt;kiwi-sbg&lt;/th&gt;
&lt;td class=&quot;incorrect&quot;&gt;구름/N 속/N 을/J &lt;b&gt;나/N&lt;/b&gt; 는/J 용/N 은/J 무엇/N 을/J 상징/N 하/X 나/E&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;th class=&quot;correct&quot;&gt;kiwi-cong&lt;/th&gt;
&lt;td class=&quot;correct&quot;&gt;구름/N 속/N 을/J &lt;b&gt;날/VV&lt;/b&gt; 는/E 용/N 은/J 무엇/N 을/J 상징/N 하/X 나/E&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;th class=&quot;incorrect&quot;&gt;kiwi-cong-global&lt;/th&gt;
&lt;td class=&quot;incorrect&quot;&gt;구름/N 속/N 을/J &lt;b&gt;나/N&lt;/b&gt; 는/J 용/N 은/J 무엇/N 을/J 상징/N 하/X 나/E&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;th class=&quot;incorrect&quot;&gt;bareun&lt;/th&gt;
&lt;td class=&quot;incorrect&quot;&gt;구름/N 속/N 을/J &lt;b&gt;나/N&lt;/b&gt; 는/E 용/N 은/J 무엇/N 을/J 상징하/VV 나/E&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;th rowspan=&quot;5&quot;&gt;63년 동안 나는 반역자였다.&lt;br /&gt;(나/N)&lt;/th&gt;
&lt;th class=&quot;correct&quot;&gt;kiwi-knlm&lt;/th&gt;
&lt;td class=&quot;correct&quot;&gt;63/S 년/N 동안/N &lt;b&gt;나/N&lt;/b&gt; 는/J 반역자/N 이/VC 었/E 다/E ./S&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;th class=&quot;correct&quot;&gt;kiwi-sbg&lt;/th&gt;
&lt;td class=&quot;correct&quot;&gt;63/S 년/N 동안/N &lt;b&gt;나/N&lt;/b&gt; 는/J 반역자/N 이/VC 었/E 다/E ./S&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;th class=&quot;correct&quot;&gt;kiwi-cong&lt;/th&gt;
&lt;td class=&quot;correct&quot;&gt;63/S 년/N 동안/N &lt;b&gt;나/N&lt;/b&gt; 는/J 반역자/N 이/VC 었/E 다/E ./S&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;th class=&quot;correct&quot;&gt;kiwi-cong-global&lt;/th&gt;
&lt;td class=&quot;correct&quot;&gt;63/S 년/N 동안/N &lt;b&gt;나/N&lt;/b&gt; 는/J 반역자/N 이/VC 었/E 다/E ./S&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;th class=&quot;correct&quot;&gt;bareun&lt;/th&gt;
&lt;td class=&quot;correct&quot;&gt;63/S 년/N 동안/N &lt;b&gt;나/N&lt;/b&gt; 는/J 반역자/N 이/VC 었/E 다/E ./S&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;th rowspan=&quot;5&quot;&gt;반대로 쉽게 떠날 갈 수 있는 생리를 몸으로 체득해&lt;br /&gt;(가/VV)&lt;/th&gt;
&lt;th class=&quot;incorrect&quot;&gt;kiwi-knlm&lt;/th&gt;
&lt;td class=&quot;incorrect&quot;&gt;반대/N 로/J 쉽/VA 게/E 떠나/VV ᆯ/E &lt;b&gt;갈/VV&lt;/b&gt; ᆯ/E 수/N 있/VA 는/E 생리/N 를/J 몸/N 으로/J 체득/N 하/X 어/E&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;th class=&quot;incorrect&quot;&gt;kiwi-sbg&lt;/th&gt;
&lt;td class=&quot;incorrect&quot;&gt;반대/N 로/J 쉽/VA 게/E 떠나/VV ᆯ/E &lt;b&gt;갈/VV&lt;/b&gt; ᆯ/E 수/N 있/VA 는/E 생리/N 를/J 몸/N 으로/J 체득/N 하/X 어/E&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;th class=&quot;correct&quot;&gt;kiwi-cong&lt;/th&gt;
&lt;td class=&quot;correct&quot;&gt;반대/N 로/J 쉽/VA 게/E 떠나/VV ᆯ/E &lt;b&gt;가/VV&lt;/b&gt; ᆯ/E 수/N 있/VA 는/E 생리/N 를/J 몸/N 으로/J 체득/N 하/X 어/E&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;th class=&quot;correct&quot;&gt;kiwi-cong-global&lt;/th&gt;
&lt;td class=&quot;correct&quot;&gt;반대/N 로/J 쉽/VA 게/E 떠나/VV ᆯ/E &lt;b&gt;가/VV&lt;/b&gt; ᆯ/E 수/N 있/VA 는/E 생리/N 를/J 몸/N 으로/J 체득/N 하/X 어/E&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;th class=&quot;correct&quot;&gt;bareun&lt;/th&gt;
&lt;td class=&quot;correct&quot;&gt;반대/N 로/J 쉽/VA 게/E 떠나/VV ㄹ/E &lt;b&gt;가/VV&lt;/b&gt; ㄹ/E 수/N 있/VA 는/E 생리/N 를/J 몸/N 으로/J 체득하/VV 아/E&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;th rowspan=&quot;5&quot;&gt;기저귀 가는 테이블은 허리를 굽힐 필요가 없을 정도로 적당한 높이에 있어야 한다.&lt;br /&gt;(갈/VV)&lt;/th&gt;
&lt;th class=&quot;incorrect&quot;&gt;kiwi-knlm&lt;/th&gt;
&lt;td class=&quot;incorrect&quot;&gt;기저귀/N &lt;b&gt;가/VV&lt;/b&gt; 는/E 테이블/N 은/J 허리/N 를/J 굽히/VV ᆯ/E 필요/N 가/J 없/VA 을/E 정도/N 로/J 적당/X 하/X ᆫ/E 높이/N 에/J 있/VV 어야/E 하/VX ᆫ다/E ./S&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;th class=&quot;incorrect&quot;&gt;kiwi-sbg&lt;/th&gt;
&lt;td class=&quot;incorrect&quot;&gt;기저귀/N &lt;b&gt;가늘/VA&lt;/b&gt; ᆫ/E 테이블/N 은/J 허리/N 를/J 굽히/VV ᆯ/E 필요/N 가/J 없/VA 을/E 정도/N 로/J 적당/X 하/X ᆫ/E 높이/N 에/J 있/VV 어야/E 하/VX ᆫ다/E ./S&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;th class=&quot;incorrect&quot;&gt;kiwi-cong&lt;/th&gt;
&lt;td class=&quot;incorrect&quot;&gt;기저귀/N &lt;b&gt;가/VV&lt;/b&gt; 는/E 테이블/N 은/J 허리/N 를/J 굽히/VV ᆯ/E 필요/N 가/J 없/VA 을/E 정도/N 로/J 적당/X 하/X ᆫ/E 높이/N 에/J 있/VV 어야/E 하/VX ᆫ다/E ./S&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;th class=&quot;incorrect&quot;&gt;kiwi-cong-global&lt;/th&gt;
&lt;td class=&quot;incorrect&quot;&gt;기저귀/N &lt;b&gt;가/VV&lt;/b&gt; 는/E 테이블/N 은/J 허리/N 를/J 굽히/VV ᆯ/E 필요/N 가/J 없/VA 을/E 정도/N 로/J 적당/X 하/X ᆫ/E 높이/N 에/J 있/VV 어야/E 하/VX ᆫ다/E ./S&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;th class=&quot;incorrect&quot;&gt;bareun&lt;/th&gt;
&lt;td class=&quot;incorrect&quot;&gt;기저귀/N &lt;b&gt;가/VV&lt;/b&gt; 는/E 테이블/N 은/J 허리/N 를/J 굽히/VV ㄹ/E 필요/N 가/J 없/VA 을/E 정도/N 로/J 적당하/VA ㄴ/E 높이/N 에/J 있/VA 어야/E 하/VX ㄴ다/E ./S&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;th rowspan=&quot;5&quot;&gt;기록하기 위해 나는 이 일기를 쓰노라.&lt;br /&gt;(쓰/VV)&lt;/th&gt;
&lt;th class=&quot;incorrect&quot;&gt;kiwi-knlm&lt;/th&gt;
&lt;td class=&quot;incorrect&quot;&gt;기록/N 하/X 기/E 위하/VV 어/E 나/N 는/J 이/M 일기/N 를/J &lt;b&gt;쓸/VV&lt;/b&gt; 노라/E ./S&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;th class=&quot;incorrect&quot;&gt;kiwi-sbg&lt;/th&gt;
&lt;td class=&quot;incorrect&quot;&gt;기록/N 하/X 기/E 위하/VV 어/E 나/N 는/J 이/M 일기/N 를/J &lt;b&gt;쓸/VV&lt;/b&gt; 노라/E ./S&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;th class=&quot;correct&quot;&gt;kiwi-cong&lt;/th&gt;
&lt;td class=&quot;correct&quot;&gt;기록/N 하/X 기/E 위하/VV 어/E 나/N 는/J 이/M 일기/N 를/J &lt;b&gt;쓰/VV&lt;/b&gt; 노라/E ./S&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;th class=&quot;correct&quot;&gt;kiwi-cong-global&lt;/th&gt;
&lt;td class=&quot;correct&quot;&gt;기록/N 하/X 기/E 위하/VV 어/E 나/N 는/J 이/M 일기/N 를/J &lt;b&gt;쓰/VV&lt;/b&gt; 노라/E ./S&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;th class=&quot;correct&quot;&gt;bareun&lt;/th&gt;
&lt;td class=&quot;correct&quot;&gt;기록하/VV 기/E 위하/VV 아/E 나/N 는/J 이/M 일기/N 를/J &lt;b&gt;쓰/VV&lt;/b&gt; 노라/E ./S&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;th rowspan=&quot;5&quot;&gt;마누라(법적 부인은 아니고 과부지만)가 마당 쓸 때 사용하는 나일론 빗자루&lt;br /&gt;(쓸/VV)&lt;/th&gt;
&lt;th class=&quot;correct&quot;&gt;kiwi-knlm&lt;/th&gt;
&lt;td class=&quot;correct&quot;&gt;마누라/N (/S 법/N 적/X 부인/N 은/J 아니/VC 고/E 과부/N 이/VC 지만/E )/S 가/J 마당/N &lt;b&gt;쓸/VV&lt;/b&gt; ᆯ/E 때/N 사용/N 하/X 는/E 나일론/N 빗자루/N&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;th class=&quot;correct&quot;&gt;kiwi-sbg&lt;/th&gt;
&lt;td class=&quot;correct&quot;&gt;마누라/N (/S 법/N 적/X 부인/N 은/J 아니/VC 고/E 과부/N 이/VC 지만/E )/S 가/J 마당/N &lt;b&gt;쓸/VV&lt;/b&gt; ᆯ/E 때/N 사용/N 하/X 는/E 나일론/N 빗자루/N&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;th class=&quot;correct&quot;&gt;kiwi-cong&lt;/th&gt;
&lt;td class=&quot;correct&quot;&gt;마누라/N (/S 법/N 적/X 부인/N 은/J 아니/VC 고/E 과부/N 이/VC 지만/E )/S 가/J 마당/N &lt;b&gt;쓸/VV&lt;/b&gt; ᆯ/E 때/N 사용/N 하/X 는/E 나일론/N 빗자루/N&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;th class=&quot;correct&quot;&gt;kiwi-cong-global&lt;/th&gt;
&lt;td class=&quot;correct&quot;&gt;마누라/N (/S 법/N 적/X 부인/N 은/J 아니/VC 고/E 과부/N 이/VC 지만/E )/S 가/J 마당/N &lt;b&gt;쓸/VV&lt;/b&gt; ᆯ/E 때/N 사용/N 하/X 는/E 나일론/N 빗자루/N&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;th class=&quot;incorrect&quot;&gt;bareun&lt;/th&gt;
&lt;td class=&quot;incorrect&quot;&gt;마누라/N (/S 법적/N 부인/N 은/J 아니/VC 고/E 과부/N 지만/E )/S 가/J 마당/N &lt;b&gt;쓰/VV&lt;/b&gt; ㄹ/E 때/N 사용하/VV 는/E 나일론/N 빗자루/N&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;th rowspan=&quot;5&quot;&gt;그렇다고 해서 개인이 소액으로 살 수 없는 것은 아닙니다.&lt;br /&gt;(사/VV)&lt;/th&gt;
&lt;th class=&quot;incorrect&quot;&gt;kiwi-knlm&lt;/th&gt;
&lt;td class=&quot;incorrect&quot;&gt;그렇/VA 다고/E 하/VV 어서/E 개인/N 이/J 소액/N 으로/J &lt;b&gt;살/VV&lt;/b&gt; ᆯ/E 수/N 없/VA 는/E 것/N 은/J 아니/VC ᆸ니다/E ./S&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;th class=&quot;incorrect&quot;&gt;kiwi-sbg&lt;/th&gt;
&lt;td class=&quot;incorrect&quot;&gt;그렇/VA 다고/E 하/VV 어서/E 개인/N 이/J 소액/N 으로/J &lt;b&gt;살/VV&lt;/b&gt; ᆯ/E 수/N 없/VA 는/E 것/N 은/J 아니/VC ᆸ니다/E ./S&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;th class=&quot;correct&quot;&gt;kiwi-cong&lt;/th&gt;
&lt;td class=&quot;correct&quot;&gt;그렇/VA 다고/E 하/VV 어서/E 개인/N 이/J 소액/N 으로/J &lt;b&gt;사/VV&lt;/b&gt; ᆯ/E 수/N 없/VA 는/E 것/N 은/J 아니/VC ᆸ니다/E ./S&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;th class=&quot;correct&quot;&gt;kiwi-cong-global&lt;/th&gt;
&lt;td class=&quot;correct&quot;&gt;그렇/VA 다고/E 하/VV 어서/E 개인/N 이/J 소액/N 으로/J &lt;b&gt;사/VV&lt;/b&gt; ᆯ/E 수/N 없/VA 는/E 것/N 은/J 아니/VC ᆸ니다/E ./S&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;th class=&quot;correct&quot;&gt;bareun&lt;/th&gt;
&lt;td class=&quot;correct&quot;&gt;그렇/VA 다고/E 하/VV 아서/E 개인/N 이/J 소액/N 으로/J &lt;b&gt;사/VV&lt;/b&gt; ㄹ/E 수/N 없/VA 는/E 것/N 은/J 아니/VC ㅂ니다/E ./S&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;th rowspan=&quot;5&quot;&gt;나는 청계천 끝자락에 사는 내내 그런 여자들을 보았다.&lt;br /&gt;(살/VV)&lt;/th&gt;
&lt;th class=&quot;incorrect&quot;&gt;kiwi-knlm&lt;/th&gt;
&lt;td class=&quot;incorrect&quot;&gt;나/N 는/J 청계천/N 끝/N 자락/N 에/J &lt;b&gt;사/VV&lt;/b&gt; 는/E 내내/M 그런/M 여자/N 들/X 을/J 보/VV 었/E 다/E ./S&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;th class=&quot;incorrect&quot;&gt;kiwi-sbg&lt;/th&gt;
&lt;td class=&quot;incorrect&quot;&gt;나/N 는/J 청계천/N 끝자락/N 에/J &lt;b&gt;사/VV&lt;/b&gt; 는/E 내내/M 그런/M 여자/N 들/X 을/J 보/VV 었/E 다/E ./S&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;th class=&quot;incorrect&quot;&gt;kiwi-cong&lt;/th&gt;
&lt;td class=&quot;incorrect&quot;&gt;나/N 는/J 청계천/N 끝자락/N 에/J &lt;b&gt;사/VV&lt;/b&gt; 는/E 내내/M 그런/M 여자/N 들/X 을/J 보/VV 었/E 다/E ./S&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;th class=&quot;incorrect&quot;&gt;kiwi-cong-global&lt;/th&gt;
&lt;td class=&quot;incorrect&quot;&gt;나/N 는/J 청계천/N 끝자락/N 에/J &lt;b&gt;사/VV&lt;/b&gt; 는/E 내내/M 그런/M 여자/N 들/X 을/J 보/VV 었/E 다/E ./S&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;th class=&quot;correct&quot;&gt;bareun&lt;/th&gt;
&lt;td class=&quot;correct&quot;&gt;나/N 는/J 청계천/N 끝자락/N 에/J &lt;b&gt;살/VV&lt;/b&gt; 는/E 내내/M 그런/M 여자/N 들/X 을/J 보/VV 았/E 다/E ./S&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p style=&quot;text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;모호한 맥락에서 동사를 바르게 분석하는 것은 명사보다 전반적으로 훨씬 어렵습니다. 한국어를 충분히 배운 사람도 깜빡하면 틀릴 수도 있을 정도니깐요. 실제로 이 영역에서는 모든 분석기가 틀리는 사례도 제법 있습니다. Kiwi CoNg Global이 가장 높은 성능을 달성했음에도 정확도가 약 77%에 불과해서 앞으로도 추가적인 개선이 필요한 영역으로 보입니다.&lt;/p&gt;
&lt;div data-ke-type=&quot;moreLess&quot; data-text-more=&quot;동/형용사 개선 사례 보기&quot; data-text-less=&quot;동/형용사 개선 사례 닫기&quot;&gt;&lt;a class=&quot;btn-toggle-moreless&quot;&gt;더보기&lt;/a&gt;
&lt;div class=&quot;moreless-content&quot;&gt;
&lt;table class=&quot;std-table&quot; data-ke-align=&quot;alignLeft&quot;&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;th rowspan=&quot;5&quot;&gt;시기나 질투 어린 부정적인 말이나 태도 대신에&lt;br /&gt;(어리/VV)&lt;/th&gt;
&lt;th class=&quot;correct&quot;&gt;kiwi-knlm&lt;/th&gt;
&lt;td class=&quot;correct&quot;&gt;시기/N 나/J 질투/N &lt;b&gt;어리/VV&lt;/b&gt; ᆫ/E 부정/N 적/X 이/VC ᆫ/E 말/N 이나/J 태도/N 대신/N 에/J&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;th class=&quot;correct&quot;&gt;kiwi-sbg&lt;/th&gt;
&lt;td class=&quot;correct&quot;&gt;시기/N 나/J 질투/N &lt;b&gt;어리/VV&lt;/b&gt; ᆫ/E 부정/N 적/X 이/VC ᆫ/E 말/N 이나/J 태도/N 대신/N 에/J&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;th class=&quot;correct&quot;&gt;kiwi-cong&lt;/th&gt;
&lt;td class=&quot;correct&quot;&gt;시기/N 나/J 질투/N &lt;b&gt;어리/VV&lt;/b&gt; ᆫ/E 부정/N 적/X 이/VC ᆫ/E 말/N 이나/J 태도/N 대신/N 에/J&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;th class=&quot;correct&quot;&gt;kiwi-cong-global&lt;/th&gt;
&lt;td class=&quot;correct&quot;&gt;시기/N 나/J 질투/N &lt;b&gt;어리/VV&lt;/b&gt; ᆫ/E 부정/N 적/X 이/VC ᆫ/E 말/N 이나/J 태도/N 대신/N 에/J&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;th class=&quot;incorrect&quot;&gt;bareun&lt;/th&gt;
&lt;td class=&quot;incorrect&quot;&gt;시기/N 나/J 질투/N &lt;b&gt;어리/VA&lt;/b&gt; ㄴ/E 부정/N 적/X 이/VC ㄴ/E 말/N 이나/J 태도/N 대신/N 에/J&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;th rowspan=&quot;5&quot;&gt;정인은 억지로 눈물을 참으며 어린 남매를 한 차례씩 껴안아 주며 말했다.&lt;br /&gt;(어리/VA)&lt;/th&gt;
&lt;th class=&quot;correct&quot;&gt;kiwi-knlm&lt;/th&gt;
&lt;td class=&quot;correct&quot;&gt;정인/N 은/J 억지로/M 눈물/N 을/J 참/VV 으며/E &lt;b&gt;어리/VA&lt;/b&gt; ᆫ/E 남매/N 를/J 한/M 차례/N 씩/X 껴안/VV 어/E 주/VX 며/E 말/N 하/X 었/E 다/E ./S&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;th class=&quot;correct&quot;&gt;kiwi-sbg&lt;/th&gt;
&lt;td class=&quot;correct&quot;&gt;정인/N 은/J 억지로/M 눈물/N 을/J 참/VV 으며/E &lt;b&gt;어리/VA&lt;/b&gt; ᆫ/E 남매/N 를/J 한/M 차례/N 씩/X 껴안/VV 어/E 주/VX 며/E 말/N 하/X 었/E 다/E ./S&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;th class=&quot;correct&quot;&gt;kiwi-cong&lt;/th&gt;
&lt;td class=&quot;correct&quot;&gt;정인/N 은/J 억지로/M 눈물/N 을/J 참/VV 으며/E &lt;b&gt;어리/VA&lt;/b&gt; ᆫ/E 남매/N 를/J 한/M 차례/N 씩/X 껴안/VV 어/E 주/VX 며/E 말/N 하/X 었/E 다/E ./S&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;th class=&quot;correct&quot;&gt;kiwi-cong-global&lt;/th&gt;
&lt;td class=&quot;correct&quot;&gt;정인/N 은/J 억지로/M 눈물/N 을/J 참/VV 으며/E &lt;b&gt;어리/VA&lt;/b&gt; ᆫ/E 남매/N 를/J 한/M 차례/N 씩/X 껴안/VV 어/E 주/VX 며/E 말/N 하/X 었/E 다/E ./S&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;th class=&quot;correct&quot;&gt;bareun&lt;/th&gt;
&lt;td class=&quot;correct&quot;&gt;정인/N 은/J 억지로/M 눈물/N 을/J 참/VV 으며/E &lt;b&gt;어리/VA&lt;/b&gt; ㄴ/E 남매/N 를/J 한/M 차례/N 씩/X 껴안/VV 아/E 주/VX 며/E 말하/VV 았/E 다/E ./S&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;th rowspan=&quot;5&quot;&gt;심종수는 심오한 경학의 이론을 강한다.&lt;br /&gt;(강하/VV)&lt;/th&gt;
&lt;th class=&quot;incorrect&quot;&gt;kiwi-knlm&lt;/th&gt;
&lt;td class=&quot;incorrect&quot;&gt;심/X 종수/N 는/J 심오/X 하/X ᆫ/E 경학/N 의/J 이론/N 을/J &lt;b&gt;강/N 하/X&lt;/b&gt; ᆫ다/E ./S&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;th class=&quot;incorrect&quot;&gt;kiwi-sbg&lt;/th&gt;
&lt;td class=&quot;incorrect&quot;&gt;심/X 종수/N 는/J 심오/X 하/X ᆫ/E 경학/N 의/J 이론/N 을/J &lt;b&gt;강/N 하/X&lt;/b&gt; ᆫ다/E ./S&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;th class=&quot;incorrect&quot;&gt;kiwi-cong&lt;/th&gt;
&lt;td class=&quot;incorrect&quot;&gt;심/X 종/N 수/N 는/J 심오/X 하/X ᆫ/E 경학/N 의/J 이론/N 을/J &lt;b&gt;강/X 하/X&lt;/b&gt; ᆫ다/E ./S&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;th class=&quot;incorrect&quot;&gt;kiwi-cong-global&lt;/th&gt;
&lt;td class=&quot;incorrect&quot;&gt;심/X 종/N 수/N 는/J 심오/X 하/X ᆫ/E 경학/N 의/J 이론/N 을/J &lt;b&gt;강/X 하/X&lt;/b&gt; ᆫ다/E ./S&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;th class=&quot;incorrect&quot;&gt;bareun&lt;/th&gt;
&lt;td class=&quot;incorrect&quot;&gt;심종/N 수/N 는/J 심오하/VA ㄴ/E 경학/N 의/J 이론/N 을/J &lt;b&gt;강하/VA&lt;/b&gt; ㄴ다/E ./S&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;th rowspan=&quot;5&quot;&gt;쇠가 너무 강하면 부러진다.&lt;br /&gt;(강하/VA)&lt;/th&gt;
&lt;th class=&quot;correct&quot;&gt;kiwi-knlm&lt;/th&gt;
&lt;td class=&quot;correct&quot;&gt;쇠/N 가/J 너무/M &lt;b&gt;강하/VA&lt;/b&gt; 면/E 부러지/VV ᆫ다/E ./S&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;th class=&quot;correct&quot;&gt;kiwi-sbg&lt;/th&gt;
&lt;td class=&quot;correct&quot;&gt;쇠/N 가/J 너무/M &lt;b&gt;강하/VA&lt;/b&gt; 면/E 부러지/VV ᆫ다/E ./S&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;th class=&quot;correct&quot;&gt;kiwi-cong&lt;/th&gt;
&lt;td class=&quot;correct&quot;&gt;쇠/N 가/J 너무/M &lt;b&gt;강하/VA&lt;/b&gt; 면/E 부러지/VV ᆫ다/E ./S&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;th class=&quot;correct&quot;&gt;kiwi-cong-global&lt;/th&gt;
&lt;td class=&quot;correct&quot;&gt;쇠/N 가/J 너무/M &lt;b&gt;강하/VA&lt;/b&gt; 면/E 부러지/VV ᆫ다/E ./S&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;th class=&quot;correct&quot;&gt;bareun&lt;/th&gt;
&lt;td class=&quot;correct&quot;&gt;쇠/N 가/J 너무/M &lt;b&gt;강하/VA&lt;/b&gt; 면/E 부러지/VV ㄴ다/E ./S&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;th rowspan=&quot;5&quot;&gt;바른 부분과 안 바른 부분이 딱 표시가 날 정도로 반질하게 윤기가 나는데요,&lt;br /&gt;(바르/VV)&lt;/th&gt;
&lt;th class=&quot;incorrect&quot;&gt;kiwi-knlm&lt;/th&gt;
&lt;td class=&quot;incorrect&quot;&gt;&lt;b&gt;바르/VA&lt;/b&gt; ᆫ/E 부분/N 과/J 알/VV ᆫ/E &lt;b&gt;바르/VA&lt;/b&gt; ᆫ/E 부분/N 이/J 딱/M 표시/N 가/J 나/VV ᆯ/E 정도/N 로/J 반/N 질/X 하/X 게/E 윤기/N 가/J 나/VV 는데요/E ,/S&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;th class=&quot;incorrect&quot;&gt;kiwi-sbg&lt;/th&gt;
&lt;td class=&quot;incorrect&quot;&gt;&lt;b&gt;바르/VA&lt;/b&gt; ᆫ/E 부분/N 과/J 알/VV ᆫ/E &lt;b&gt;바르/VA&lt;/b&gt; ᆫ/E 부분/N 이/J 딱/M 표시/N 가/J 나/VV ᆯ/E 정도/N 로/J 반/N 질/X 하/X 게/E 윤기/N 가/J 나/VV 는데요/E ,/S&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;th class=&quot;correct&quot;&gt;kiwi-cong&lt;/th&gt;
&lt;td class=&quot;correct&quot;&gt;&lt;b&gt;바르/VA&lt;/b&gt; ᆫ/E 부분/N 과/J 안/M &lt;b&gt;바르/VV&lt;/b&gt; ᆫ/E 부분/N 이/J 딱/M 표시/N 가/J 나/VV ᆯ/E 정도/N 로/J 반/N 질/X 하/X 게/E 윤기/N 가/J 나/VV 는데요/E ,/S&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;th class=&quot;incorrect&quot;&gt;kiwi-cong-global&lt;/th&gt;
&lt;td class=&quot;incorrect&quot;&gt;&lt;b&gt;바르/VA&lt;/b&gt; ᆫ/E 부분/N 과/J 안/M &lt;b&gt;바르/VA&lt;/b&gt; ᆫ/E 부분/N 이/J 딱/M 표시/N 가/J 나/VV ᆯ/E 정도/N 로/J 반/N 질/X 하/VV 게/E 윤기/N 가/J 나/VV 는데요/E ,/S&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;th class=&quot;incorrect&quot;&gt;bareun&lt;/th&gt;
&lt;td class=&quot;incorrect&quot;&gt;&lt;b&gt;바른/N&lt;/b&gt; 부분/N 과/J 안/M &lt;b&gt;바른/N&lt;/b&gt; 부분/N 이/J 딱/M 표시/N 가/J 나/VV ㄹ/E 정도/N 로/J 반질하/VV 게/E 윤기/N 가/J 나/VV 는데/E 요/J ,/S&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;th rowspan=&quot;5&quot;&gt;사람들의 마음에 바른 것을 향한 갈망과 참된 복음을 사모하는 열정들이 불타오르고 있다.&lt;br /&gt;(바르/VA)&lt;/th&gt;
&lt;th class=&quot;incorrect&quot;&gt;kiwi-knlm&lt;/th&gt;
&lt;td class=&quot;incorrect&quot;&gt;사람/N 들/X 의/J 마음/N 에/J 바르/VV ᆫ/E 것/N 을/J 향하/VV ᆫ/E 갈망/N 과/J 참되/VA ᆫ/E 복음/N 을/J 사모/N 하/X 는/E 열정/N 들/X 이/J 불타오르/VV 고/E 있/VX 다/E ./S&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;th class=&quot;correct&quot;&gt;kiwi-sbg&lt;/th&gt;
&lt;td class=&quot;correct&quot;&gt;사람/N 들/X 의/J 마음/N 에/J &lt;b&gt;바르/VA&lt;/b&gt; ᆫ/E 것/N 을/J 향하/VV ᆫ/E 갈망/N 과/J 참되/VA ᆫ/E 복음/N 을/J 사모/N 하/X 는/E 열정/N 들/X 이/J 불타/VV 어/E 오르/VV 고/E 있/VX 다/E ./S&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;th class=&quot;incorrect&quot;&gt;kiwi-cong&lt;/th&gt;
&lt;td class=&quot;incorrect&quot;&gt;사람/N 들/X 의/J 마음/N 에/J &lt;b&gt;바르/VV&lt;/b&gt; ᆫ/E 것/N 을/J 향하/VV ᆫ/E 갈망/N 과/J 참되/VA ᆫ/E 복음/N 을/J 사모/N 하/X 는/E 열정/N 들/X 이/J 불타오르/VV 고/E 있/VX 다/E ./S&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;th class=&quot;correct&quot;&gt;kiwi-cong-global&lt;/th&gt;
&lt;td class=&quot;correct&quot;&gt;사람/N 들/X 의/J 마음/N 에/J &lt;b&gt;바르/VA&lt;/b&gt; ᆫ/E 것/N 을/J 향하/VV ᆫ/E 갈망/N 과/J 참되/VA ᆫ/E 복음/N 을/J 사모/N 하/X 는/E 열정/N 들/X 이/J 불타오르/VV 고/E 있/VX 다/E ./S&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;th class=&quot;incorrect&quot;&gt;bareun&lt;/th&gt;
&lt;td class=&quot;incorrect&quot;&gt;사람/N 들/X 의/J 마음/N 에/J &lt;b&gt;바른/N&lt;/b&gt; 것/N 을/J 향하/VV ㄴ/E 갈망/N 과/J 참되/VA ㄴ/E 복음/N 을/J 사모하/VV 는/E 열정/N 들/X 이/J 불타오르/VV 고/E 있/VX 다/E ./S&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;th rowspan=&quot;5&quot;&gt;더러운 솥을 광이 나도록 부셨다.&lt;br /&gt;(부시/VV)&lt;/th&gt;
&lt;th class=&quot;correct&quot;&gt;kiwi-knlm&lt;/th&gt;
&lt;td class=&quot;correct&quot;&gt;더럽/VA 은/E 솥/N 을/J 광/N 이/J 나/VV 도록/E &lt;b&gt;부시/VV&lt;/b&gt; 었/E 다/E ./S&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;th class=&quot;correct&quot;&gt;kiwi-sbg&lt;/th&gt;
&lt;td class=&quot;correct&quot;&gt;더럽/VA 은/E 솥/N 을/J 광/N 이/J 나/VV 도록/E &lt;b&gt;부시/VV&lt;/b&gt; 었/E 다/E ./S&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;th class=&quot;correct&quot;&gt;kiwi-cong&lt;/th&gt;
&lt;td class=&quot;correct&quot;&gt;더럽/VA 은/E 솥/N 을/J 광/N 이/J 나/VV 도록/E &lt;b&gt;부시/VV&lt;/b&gt; 었/E 다/E ./S&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;th class=&quot;correct&quot;&gt;kiwi-cong-global&lt;/th&gt;
&lt;td class=&quot;correct&quot;&gt;더럽/VA 은/E 솥/N 을/J 광/N 이/J 나/VV 도록/E &lt;b&gt;부시/VV&lt;/b&gt; 었/E 다/E ./S&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;th class=&quot;correct&quot;&gt;bareun&lt;/th&gt;
&lt;td class=&quot;correct&quot;&gt;더럽/VA ㄴ/E 솥/N 을/J 광/N 이/J 나/VV 도록/E &lt;b&gt;부시/VV&lt;/b&gt; 었/E 다/E ./S&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;th rowspan=&quot;5&quot;&gt;밖으로 나오자 눈이 부셔서 눈을 뜰 수가 없다.&lt;br /&gt;(부시/VA)&lt;/th&gt;
&lt;th class=&quot;correct&quot;&gt;kiwi-knlm&lt;/th&gt;
&lt;td class=&quot;correct&quot;&gt;밖/N 으로/J 나오/VV 자/E 눈/N 이/J &lt;b&gt;부시/VA&lt;/b&gt; 어서/E 눈/N 을/J 뜨/VV ᆯ/E 수/N 가/J 없/VA 다/E ./S&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;th class=&quot;correct&quot;&gt;kiwi-sbg&lt;/th&gt;
&lt;td class=&quot;correct&quot;&gt;밖/N 으로/J 나오/VV 자/E 눈/N 이/J &lt;b&gt;부시/VA&lt;/b&gt; 어서/E 눈/N 을/J 뜨/VV ᆯ/E 수/N 가/J 없/VA 다/E ./S&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;th class=&quot;correct&quot;&gt;kiwi-cong&lt;/th&gt;
&lt;td class=&quot;correct&quot;&gt;밖/N 으로/J 나오/VV 자/E 눈/N 이/J &lt;b&gt;부시/VA&lt;/b&gt; 어서/E 눈/N 을/J 뜨/VV ᆯ/E 수/N 가/J 없/VA 다/E ./S&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;th class=&quot;correct&quot;&gt;kiwi-cong-global&lt;/th&gt;
&lt;td class=&quot;correct&quot;&gt;밖/N 으로/J 나오/VV 자/E 눈/N 이/J &lt;b&gt;부시/VA&lt;/b&gt; 어서/E 눈/N 을/J 뜨/VV ᆯ/E 수/N 가/J 없/VA 다/E ./S&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;th class=&quot;correct&quot;&gt;bareun&lt;/th&gt;
&lt;td class=&quot;correct&quot;&gt;밖/N 으로/J 나오/VV 자/E 눈/N 이/J &lt;b&gt;부시/VA&lt;/b&gt; 어서/E 눈/N 을/J 뜨/VV ㄹ/E 수/N 가/J 없/VA 다/E ./S&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p style=&quot;text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;형태가 동일한 동사와 형용사를 구분하는 것은 불규칙 동사보다는 전반적으로 정확도가 높습니다. 사실 이 부분은 오히려 국어를 전문적으로 배우지 않은 일반 한국인들이 분석기보다 더 못 풀 수도 있을 것 같네요. 그러나 분석기 측면에서는 동사와 형용사가 취할수 있는 문법 구조의 차이(목적어의 유무, 형용사가 취할 수 없는 일부 어미들) 때문에 의미를 아주 명확히 파악하지 못해도 적절하게 동사와 형용사를 구분할 수 있는 것으로 보입니다.&lt;/p&gt;
&lt;div data-ke-type=&quot;moreLess&quot; data-text-more=&quot;원거리 개선 사례 보기&quot; data-text-less=&quot;원거리 개선 사례 닫기&quot;&gt;&lt;a class=&quot;btn-toggle-moreless&quot;&gt;더보기&lt;/a&gt;
&lt;div class=&quot;moreless-content&quot;&gt;
&lt;table class=&quot;std-table&quot; data-ke-align=&quot;alignLeft&quot;&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;th rowspan=&quot;5&quot;&gt;새끼를 입으로 이렇게 이렇게 물어서 옮긴다. &lt;br /&gt;(물/VV)&lt;/th&gt;
&lt;th class=&quot;incorrect&quot;&gt;kiwi-knlm&lt;/th&gt;
&lt;td class=&quot;incorrect&quot;&gt;새끼/N 를/J 입/N 으로/J 이렇/VA 게/E 이렇/VA 게/E &lt;b&gt;묻/VV&lt;/b&gt; 어서/E 옮기/VV ᆫ다/E ./S&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;th class=&quot;correct&quot;&gt;kiwi-sbg&lt;/th&gt;
&lt;td class=&quot;correct&quot;&gt;새끼/N 를/J 입/N 으로/J 이렇게/M 이렇게/M &lt;b&gt;물/VV&lt;/b&gt; 어서/E 옮기/VV ᆫ다/E ./S&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;th class=&quot;incorrect&quot;&gt;kiwi-cong&lt;/th&gt;
&lt;td class=&quot;incorrect&quot;&gt;새끼/N 를/J 입/N 으로/J 이렇게/M 이렇게/M &lt;b&gt;묻/VV&lt;/b&gt; 어서/E 옮기/VV ᆫ다/E ./S&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;th class=&quot;correct&quot;&gt;kiwi-cong-global&lt;/th&gt;
&lt;td class=&quot;correct&quot;&gt;새끼/N 를/J 입/N 으로/J 이렇/VA 게/E 이렇/VA 게/E &lt;b&gt;물/VV&lt;/b&gt; 어서/E 옮기/VV ᆫ다/E ./S&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;th class=&quot;incorrect&quot;&gt;bareun&lt;/th&gt;
&lt;td class=&quot;incorrect&quot;&gt;새끼/N 를/J 입/N 으로/J 이렇/VA 게/E 이렇/VA 게/E &lt;b&gt;묻/VV&lt;/b&gt; 어서/E 옮기/VV ㄴ다/E ./S&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;th rowspan=&quot;5&quot;&gt;여기에 좋았는지 나빴는지 적어주세요.&lt;br /&gt;(적/VV)&lt;/th&gt;
&lt;th class=&quot;correct&quot;&gt;kiwi-knlm&lt;/th&gt;
&lt;td class=&quot;correct&quot;&gt;여기/N 에/J 좋/VA 었/E 는지/E 나쁘/VA 었/E 는지/E &lt;b&gt;적/VV&lt;/b&gt; 어/E 주/VX 세요/E ./S&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;th class=&quot;correct&quot;&gt;kiwi-sbg&lt;/th&gt;
&lt;td class=&quot;correct&quot;&gt;여기/N 에/J 좋/VA 었/E 는지/E 나쁘/VA 었/E 는지/E &lt;b&gt;적/VV&lt;/b&gt; 어/E 주/VX 세요/E ./S&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;th class=&quot;correct&quot;&gt;kiwi-cong&lt;/th&gt;
&lt;td class=&quot;correct&quot;&gt;여기/N 에/J 좋/VA 었/E 는지/E 나쁘/VA 었/E 는지/E &lt;b&gt;적/VV&lt;/b&gt; 어/E 주/VX 세요/E ./S&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;th class=&quot;correct&quot;&gt;kiwi-cong-global&lt;/th&gt;
&lt;td class=&quot;correct&quot;&gt;여기/N 에/J 좋/VA 었/E 는지/E 나쁘/VA 었/E 는지/E &lt;b&gt;적/VV&lt;/b&gt; 어/E 주/VX 세요/E ./S&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;th class=&quot;correct&quot;&gt;bareun&lt;/th&gt;
&lt;td class=&quot;correct&quot;&gt;여기/N 에/J 좋/VA 았/E 는지/E 나쁘/VA 았/E 는지/E &lt;b&gt;적/VV&lt;/b&gt; 어/E 주/VX 시/E 어요/E ./S&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;th rowspan=&quot;5&quot;&gt;그거 다듬을 때 말이야 길이라든가 아니면 모양이라든가 좀 맞춰서 예쁘게 해.&lt;br /&gt;(길이/N)&lt;/th&gt;
&lt;th class=&quot;incorrect&quot;&gt;kiwi-knlm&lt;/th&gt;
&lt;td class=&quot;incorrect&quot;&gt;그거/N 다듬/VV 을/E 때/N 말/N 이/VC 야/E &lt;b&gt;길/N&lt;/b&gt; 이라든가/J 아니/VC 면/E 모양/N 이/VC 라든가/E 좀/M 맞추/VV 어서/E 예쁘/VA 게/E 하/VV 어/E ./S&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;th class=&quot;correct&quot;&gt;kiwi-sbg&lt;/th&gt;
&lt;td class=&quot;correct&quot;&gt;그거/N 다듬/VV 을/E 때/N 말/N 이/VC 야/E &lt;b&gt;길이/N&lt;/b&gt; 라든가/J 아니/VC 면/E 모양/N 이/VC 라든가/E 좀/M 맞추/VV 어서/E 예쁘/VA 게/E 하/VV 어/E ./S&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;th class=&quot;incorrect&quot;&gt;kiwi-cong&lt;/th&gt;
&lt;td class=&quot;incorrect&quot;&gt;그거/N 다듬/VV 을/E 때/N 말/N 이/VC 야/E &lt;b&gt;길/N&lt;/b&gt; 이/VC 라든가/E 아니/VC 면/E 모양/N 이라든가/J 좀/M 맞추/VV 어서/E 예쁘/VA 게/E 하/VV 어/E ./S&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;th class=&quot;correct&quot;&gt;kiwi-cong-global&lt;/th&gt;
&lt;td class=&quot;correct&quot;&gt;그거/N 다듬/VV 을/E 때/N 말/N 이/VC 야/E &lt;b&gt;길이/N&lt;/b&gt; 라든가/J 아니/VC 면/E 모양/N 이/VC 라든가/E 좀/M 맞추/VV 어서/E 예쁘/VA 게/E 하/VV 어/E ./S&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;th class=&quot;incorrect&quot;&gt;bareun&lt;/th&gt;
&lt;td class=&quot;incorrect&quot;&gt;그거/N 다듬/VV 을/E 때/N 말/N 이/VC 야/E &lt;b&gt;길/N&lt;/b&gt; 이라든가/J 아니/VC 면/E 모양/N 이라든가/J 좀/M 맞추/VV 어서/E 예쁘/VA 게/E 하/VX 아/E ./S&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;th rowspan=&quot;5&quot;&gt;언제나 제멋대로 굴고, 철도 아직까지 덜 들고.&lt;br /&gt;(철/N)&lt;/th&gt;
&lt;th class=&quot;correct&quot;&gt;kiwi-knlm&lt;/th&gt;
&lt;td class=&quot;correct&quot;&gt;언제나/M 제멋대로/M 굴/VV 고/E ,/S &lt;b&gt;철/N&lt;/b&gt; 도/J 아직/M 까지/J 덜/M 들/VV 고/E ./S&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;th class=&quot;correct&quot;&gt;kiwi-sbg&lt;/th&gt;
&lt;td class=&quot;correct&quot;&gt;언제나/M 제멋대로/M 굴/VV 고/E ,/S &lt;b&gt;철/N&lt;/b&gt; 도/J 아직/M 까지/J 덜/M 들/VV 고/E ./S&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;th class=&quot;incorrect&quot;&gt;kiwi-cong&lt;/th&gt;
&lt;td class=&quot;incorrect&quot;&gt;언제나/M 제멋대로/M 굴/VV 고/E ,/S &lt;b&gt;철도/N&lt;/b&gt; 아직/M 까지/J 덜/M 들/VV 고/E ./S&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;th class=&quot;correct&quot;&gt;kiwi-cong-global&lt;/th&gt;
&lt;td class=&quot;correct&quot;&gt;언제나/M 제멋대로/M 굴/VV 고/E ,/S &lt;b&gt;철/N&lt;/b&gt; 도/J 아직/M 까지/J 덜/M 들/VV 고/E ./S&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;th class=&quot;correct&quot;&gt;bareun&lt;/th&gt;
&lt;td class=&quot;correct&quot;&gt;언제나/M 제멋대로/M 굴/VV 고/E ,/S &lt;b&gt;철/N&lt;/b&gt; 도/J 아직/M 까지/J 덜/M 들/VV 고/E ./S&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p style=&quot;text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;마지막으로 원거리 영역입니다. 이 영역에서는 모호성 해소의 힌트가 되는 단어가 멀리 떨어져 있어서 모델이 멀리 떨어지 형태소 사이의 관계를 고려할 수 있어야 합니다. 이 때문에 실제로 KnLM이나 CoNg 모델은 전반적으로 점수가 낮게 나오고 반대로 SBG와 CoNg Global은 점수가 높게 나오죠. 실제 사례에서도 마찬가지의 결과가 나왔습니다.&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;지금까지 살펴본 내용을 정리해보면, CoNg 모델에서는 전반적으로 모호성 해소 성능이 크게 개선되었고, 특히 명사 영역에서의 개선이 가장 두드러진다고 할 수 있습니다. 불규칙 활용 역시 다른 분석기보다는 잘하고 있으나 아직 더 많은 개선이 필요한 상황이구요. 또한 Global 모델이 실제로 예상했던것처럼 원거리 관계를 잘 고려하고 있다는 것도 확인할 수 있었습니다.&lt;/p&gt;
&lt;h3 style=&quot;text-align: justify;&quot; data-ke-size=&quot;size23&quot;&gt;문장 분리 성능 개선&lt;/h3&gt;
&lt;table class=&quot;std-table&quot; data-ke-align=&quot;alignLeft&quot;&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;th&gt;분석기&lt;/th&gt;
&lt;th&gt;웹(블로그)&lt;/th&gt;
&lt;th&gt;웹(트위터)&lt;/th&gt;
&lt;th&gt;웹(위키백과)&lt;/th&gt;
&lt;th&gt;안은문장&lt;/th&gt;
&lt;th&gt;명사형 어미&lt;/th&gt;
&lt;th&gt;다양한 어미&lt;/th&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;th&gt;Kiwi KnLM&lt;/th&gt;
&lt;td&gt;81.3&lt;/td&gt;
&lt;td&gt;79.3&lt;/td&gt;
&lt;td&gt;97.5&lt;/td&gt;
&lt;td&gt;89.4&lt;/td&gt;
&lt;td&gt;85.7&lt;/td&gt;
&lt;td&gt;37.7&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;th&gt;Kiwi SBG&lt;/th&gt;
&lt;td&gt;81.1&lt;/td&gt;
&lt;td&gt;80.9&lt;/td&gt;
&lt;td&gt;97.8&lt;/td&gt;
&lt;td&gt;&lt;b&gt;91.7&lt;/b&gt;&lt;/td&gt;
&lt;td&gt;85.8&lt;/td&gt;
&lt;td&gt;39.9&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;th&gt;Kiwi CoNg&lt;/th&gt;
&lt;td&gt;81.6&lt;/td&gt;
&lt;td&gt;81.1&lt;/td&gt;
&lt;td&gt;98.3&lt;/td&gt;
&lt;td&gt;88.0&lt;/td&gt;
&lt;td&gt;&lt;b&gt;88.1&lt;/b&gt;&lt;/td&gt;
&lt;td&gt;49.7&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;th&gt;Kiwi CoNg Global&lt;/th&gt;
&lt;td&gt;&lt;b&gt;83.1&lt;/b&gt;&lt;/td&gt;
&lt;td&gt;&lt;b&gt;83.1&lt;/b&gt;&lt;/td&gt;
&lt;td&gt;&lt;b&gt;98.4&lt;/b&gt;&lt;/td&gt;
&lt;td&gt;91.1&lt;/td&gt;
&lt;td&gt;83.9&lt;/td&gt;
&lt;td&gt;&lt;b&gt;51.6&lt;/b&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;th&gt;Bareun&lt;/th&gt;
&lt;td&gt;46.0&lt;/td&gt;
&lt;td&gt;53.5&lt;/td&gt;
&lt;td&gt;91.6&lt;/td&gt;
&lt;td&gt;80.4&lt;/td&gt;
&lt;td&gt;54.6&lt;/td&gt;
&lt;td&gt;11.4&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p style=&quot;text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;문장 분리는 다소 일관되지 못한 결과를 보여주고 있습니다. 특히 안은 문장이나 명사형 어미가 있는 경우 오히려 CoNg 계열 모델에서 문장 분리 정확도가 떨어지는 모습을 보여주고 있습니다. 그럼에도 다양한 어미 영역에서 극적으로 점수 개선이 있었습니다. 다양한 어미 영역은 사투리를 비롯하여 흔하게 쓰이지 않는 문장 종결어미로 구성된 데이터셋입니다. 표준어를 기반으로한 통계 모델을 태생적으로 취약할 수 밖에 없는 평가 영역이죠. 신경망 모델도 마찬가지로 사투리 종결 어미를 많이 배우지는 못했지만, 그래도 통계모델보다는 일반화 성능이 높으므로 이렇게 데이터가 부족한 영역에서 성능이 개선되었다고 볼 수 있습니다.&lt;/p&gt;
&lt;div data-ke-type=&quot;moreLess&quot; data-text-more=&quot;다양한 어미 개선 사례 보기&quot; data-text-less=&quot;다양한 어미 개선 사례 닫기&quot;&gt;&lt;a class=&quot;btn-toggle-moreless&quot;&gt;더보기&lt;/a&gt;
&lt;div class=&quot;moreless-content&quot;&gt;
&lt;table class=&quot;std-table&quot; data-ke-align=&quot;alignLeft&quot;&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;th rowspan=&quot;6&quot;&gt;&amp;nbsp;&lt;/th&gt;
&lt;th&gt;입력&lt;/th&gt;
&lt;td&gt;우리 외가는 원채 충남 예산 사람인지라 부추를 솔이라고 혀 &lt;b&gt;/&lt;/b&gt; 근디 충주, 대전, 요래 이사 다니셔서 그런가 &lt;b&gt;/&lt;/b&gt; 엄니는 정구지라고 카는 겨&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;th&gt;Kiwi KnLM&lt;/th&gt;
&lt;td&gt;우리 외가는 원채 충남 예산 사람인지라 부추를 솔이라고 혀 근디 충주, 대전, 요래 이사 다니셔서 그런가 엄니는 정구지라고 카는 겨&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;th&gt;Kiwi SBG&lt;/th&gt;
&lt;td&gt;우리 외가는 원채 충남 예산 사람인지라 부추를 솔이라고 혀 근디 충주, 대전, 요래 이사 다니셔서 그런가 엄니는 정구지라고 카는 겨&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;th&gt;Kiwi CoNg&lt;/th&gt;
&lt;td&gt;우리 외가는 원채 충남 예산 사람인지라 부추를 솔이라고 혀 근디 충주, 대전, 요래 이사 다니셔서 그런가 &lt;b&gt;/&lt;/b&gt; 엄니는 정구지라고 카는 겨&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;th&gt;Kiwi CoNg Global&lt;/th&gt;
&lt;td&gt;우리 외가는 원채 충남 예산 사람인지라 부추를 솔이라고 혀 근디 충주, 대전, 요래 이사 다니셔서 그런가 &lt;b&gt;/&lt;/b&gt; 엄니는 정구지라고 카는 겨&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;th&gt;Bareun&lt;/th&gt;
&lt;td&gt;우리 외가는 원채 충남 예산 사람인지라 부추를 솔이라고 혀 근디 충주, 대전, 요래 이사 다니셔서 그런가 엄니는 정구지라고 카는 겨&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;th rowspan=&quot;6&quot;&gt;&amp;nbsp;&lt;/th&gt;
&lt;th&gt;입력&lt;/th&gt;
&lt;td&gt;속 이바구할 인간도 없재 &lt;b&gt;/&lt;/b&gt; 일 이바구 밖에 할 게 더 있긋나 &lt;b&gt;/&lt;/b&gt; 6년 전에 뱅기 몰다가 사하라 사막에 떨어졌붓지 &lt;b&gt;/&lt;/b&gt; 모다에 머가 나간기라 &lt;b&gt;/&lt;/b&gt; 기관사가 있나 &lt;b&gt;/&lt;/b&gt; 손님이 있었긋나&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;th&gt;Kiwi KnLM&lt;/th&gt;
&lt;td&gt;속 이바구할 인간도 없재 일 이바구 밖에 할 게 더 있긋나 6년 전에 뱅기 몰다가 사하라 사막에 떨어졌붓지 모다에 머가 나간기라 기관사가 있나 손님이 있었긋나&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;th&gt;Kiwi SBG&lt;/th&gt;
&lt;td&gt;속 이바구할 인간도 없재 일 이바구 밖에 할 게 더 있긋나 6년 전에 뱅기 몰다가 사하라 사막에 떨어졌붓지 모다에 머가 나간기라 기관사가 있나 손님이 있었긋나&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;th&gt;Kiwi CoNg&lt;/th&gt;
&lt;td&gt;속 이바구할 인간도 없재 일 이바구 밖에 할 게 더 있긋나 6년 전에 뱅기 몰다가 사하라 사막에 떨어졌붓지 모다에 머가 나간기라 &lt;b&gt;/&lt;/b&gt; 기관사가 있나 손님이 있었긋나&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;th&gt;Kiwi CoNg Global&lt;/th&gt;
&lt;td&gt;속 이바구할 인간도 없재 &lt;b&gt;/&lt;/b&gt; 일 이바구 밖에 할 게 더 있긋나 6년 전에 뱅기 몰다가 사하라 사막에 떨어졌붓지 &lt;b&gt;/&lt;/b&gt; 모다에 머가 나간기라 &lt;b&gt;/&lt;/b&gt; 기관사가 있나 손님이 있었긋나&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;th&gt;Bareun&lt;/th&gt;
&lt;td&gt;속 이바구할 인간도 없재 일 이바구 밖에 할 게 더 있긋나 6년 전에 뱅기 몰다가 사하라 사막에 떨어졌붓지 모다에 머가 나간기라 기관사가 있나 손님이 있었긋나&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p style=&quot;text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;웹 텍스트에서도 CoNg의 효과를 살펴보면 좋을것 같습니다. 블로그나 위키백과에 비해 더 지저분한 것으로 유명한 트위터 텍스트를 보면 노이즈에 대한 강건성 차이가 명확하게 드러나겠죠?&lt;/p&gt;
&lt;div data-ke-type=&quot;moreLess&quot; data-text-more=&quot;트위터 개선 사례 보기&quot; data-text-less=&quot;트위터 개선 사례 닫기&quot;&gt;&lt;a class=&quot;btn-toggle-moreless&quot;&gt;더보기&lt;/a&gt;
&lt;div class=&quot;moreless-content&quot;&gt;
&lt;table class=&quot;std-table&quot; data-ke-align=&quot;alignLeft&quot;&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;th rowspan=&quot;6&quot;&gt;&amp;nbsp;&lt;/th&gt;
&lt;th&gt;입력&lt;/th&gt;
&lt;td&gt;알겠지 &lt;b&gt;/&lt;/b&gt; 그러니 나와 친해지지 마 &lt;b&gt;/&lt;/b&gt; 가까워지지 마 &lt;b&gt;/&lt;/b&gt; 아픔을 나누지도 동조하지도 마 &lt;b&gt;/&lt;/b&gt; _주하림, 우리는 사랑의 계절에 굶주린 새&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;th&gt;Kiwi KnLM&lt;/th&gt;
&lt;td&gt;알겠지 &lt;b&gt;/&lt;/b&gt; 그러니 나와 친해지지 마 가까워지지 마 아픔을 나누지도 동조하지도 마 _주하림, 우리는 사랑의 계절에 굶주린 새&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;th&gt;Kiwi SBG&lt;/th&gt;
&lt;td&gt;알겠지 &lt;b&gt;/&lt;/b&gt; 그러니 나와 친해지지 마 가까워지지 마 아픔을 나누지도 동조하지도 마 _주하림, 우리는 사랑의 계절에 굶주린 새&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;th&gt;Kiwi CoNg&lt;/th&gt;
&lt;td&gt;알겠지 &lt;b&gt;/&lt;/b&gt; 그러니 나와 친해지지 마 가까워지지 마 &lt;b&gt;/&lt;/b&gt; 아픔을 나누지도 동조하지도 마 &lt;b&gt;/&lt;/b&gt; _주하림, 우리는 사랑의 계절에 굶주린 새&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;th&gt;Kiwi CoNg Global&lt;/th&gt;
&lt;td&gt;알겠지 &lt;b&gt;/&lt;/b&gt; 그러니 나와 친해지지 마 &lt;b&gt;/&lt;/b&gt; 가까워지지 마 &lt;b&gt;/&lt;/b&gt; 아픔을 나누지도 동조하지도 마 &lt;b&gt;/&lt;/b&gt; _주하림, 우리는 사랑의 계절에 굶주린 새&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;th&gt;Bareun&lt;/th&gt;
&lt;td&gt;알겠지 그러니 나와 친해지지 마 가까워지지 마 아픔을 나누지도 동조하지도 마 _주하림, 우리는 사랑의 계절에 굶주린 새&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;th rowspan=&quot;6&quot;&gt;&amp;nbsp;&lt;/th&gt;
&lt;th&gt;입력&lt;/th&gt;
&lt;td&gt;위험하다고 안된다고 하는 거 분명 집으로 갈려고 15층을 눌렀는데 1층인거야 &lt;b&gt;/&lt;/b&gt; 엘리베이터 문이 열리더니 그 옆에 있는 사람이 내리는 거야 &lt;b&gt;/&lt;/b&gt; 그러더니 엘리배이터 앞에서 대기타던 괴물이 그 아저씨 목을 물어뜯는 그 때 딱 깨어남&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;th&gt;Kiwi KnLM&lt;/th&gt;
&lt;td&gt;위험하다고 안된다고 하는 거 분명 집으로 갈려고 15층을 눌렀는데 1층인거야 엘리베이터 문이 열리더니 그 옆에 있는 사람이 내리는 거야 그러더니 엘리배이터 앞에서 대기타던 괴물이 그 아저씨 목을 물어뜯는 그 때 딱 깨어남&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;th&gt;Kiwi SBG&lt;/th&gt;
&lt;td&gt;위험하다고 안된다고 하는 거 분명 집으로 갈려고 15층을 눌렀는데 1층인거야 엘리베이터 문이 열리더니 그 옆에 있는 사람이 내리는 거야 그러더니 엘리배이터 앞에서 대기타던 괴물이 그 아저씨 목을 물어뜯는 그 때 딱 깨어남&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;th&gt;Kiwi CoNg&lt;/th&gt;
&lt;td&gt;위험하다고 안된다고 하는 거 분명 집으로 갈려고 15층을 눌렀는데 1층인거야 &lt;b&gt;/&lt;/b&gt; 엘리베이터 문이 열리더니 그 옆에 있는 사람이 내리는 거야 그러더니 엘리배이터 앞에서 대기타던 괴물이 그 아저씨 목을 물어뜯는 그 때 딱 깨어남&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;th&gt;Kiwi CoNg Global&lt;/th&gt;
&lt;td&gt;위험하다고 안된다고 하는 거 분명 집으로 갈려고 15층을 눌렀는데 1층인거야 &lt;b&gt;/&lt;/b&gt; 엘리베이터 문이 열리더니 그 옆에 있는 사람이 내리는 거야 그러더니 엘리배이터 앞에서 대기타던 괴물이 그 아저씨 목을 물어뜯는 그 때 딱 깨어남&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;th&gt;Bareun&lt;/th&gt;
&lt;td&gt;위험하다고 안된다고 하는 거 분명 집으로 갈려고 15층을 눌렀는데 1층인거야 엘리베이터 문이 열리더니 그 옆에 있는 사람이 내리는 거야 그러더니 엘리배이터 앞에서 대기타던 괴물이 그 아저씨 목을 물어뜯는 그 때 딱 깨어남&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p style=&quot;text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;구두점이 거의 없는 트위터 텍스트 특성상 기존의 KnLM이나 Bareun 분석기 등은 문장을 거의 나누지 못합니다. CoNg 계열의 모델도 완벽하게 해내는 것은 아니지만 그래도 어느정도 문장의 경계를 찾아내는 것을 확인할 수 있습니다. 확실히 노이즈에 대해 좀 더 강한 모습을 보이고 있습니다. 마지막으로 CoNg-Global에서 오히려 성능이 하락한 것으로 드러난 명사형 어미에 대해서도 살펴보겠습니다.&lt;/p&gt;
&lt;div data-ke-type=&quot;moreLess&quot; data-text-more=&quot;명사형 어미 개선 사례 보기&quot; data-text-less=&quot;명사형 어미 개선 사례 닫기&quot;&gt;&lt;a class=&quot;btn-toggle-moreless&quot;&gt;더보기&lt;/a&gt;
&lt;div class=&quot;moreless-content&quot;&gt;
&lt;table class=&quot;std-table&quot; data-ke-align=&quot;alignLeft&quot;&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;th rowspan=&quot;6&quot;&gt;&amp;nbsp;&lt;/th&gt;
&lt;th&gt;입력&lt;/th&gt;
&lt;td&gt;술을 잘 안하는데 어제 마심 ㅠㅠ &lt;b&gt;/&lt;/b&gt; 지난 20키로 런때 허벅지 쓸림때문에 오른쪽 다리안닿게 하려고 뛰어서 무릎이 좀 안좋았음.. &lt;b&gt;/&lt;/b&gt; 그 핑계로 4일정도 운동 하나도 안했음 &lt;b&gt;/&lt;/b&gt; 다리에 무릎에 발까지 아프고 배아팠음..&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;th&gt;Kiwi KnLM&lt;/th&gt;
&lt;td&gt;술을 잘 안하는데 어제 마심 ㅠㅠ &lt;b&gt;/&lt;/b&gt; 지난 20키로 런때 허벅지 쓸림때문에 오른쪽 다리안닿게 하려고 뛰어서 무릎이 좀 안좋았음.. &lt;b&gt;/&lt;/b&gt; 그 핑계로 4일정도 운동 하나도 안했음 &lt;b&gt;/&lt;/b&gt; 다리에 무릎에 발까지 아프고 배아팠음..&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;th&gt;Kiwi SBG&lt;/th&gt;
&lt;td&gt;술을 잘 안하는데 어제 마심 ㅠㅠ &lt;b&gt;/&lt;/b&gt; 지난 20키로 런때 허벅지 쓸림때문에 오른쪽 다리안닿게 하려고 뛰어서 무릎이 좀 안좋았음.. &lt;b&gt;/&lt;/b&gt; 그 핑계로 4일정도 운동 하나도 안했음 &lt;b&gt;/&lt;/b&gt; 다리에 무릎에 발까지 아프고 배아팠음..&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;th&gt;Kiwi CoNg&lt;/th&gt;
&lt;td&gt;술을 잘 안하는데 어제 마심 ㅠㅠ &lt;b&gt;/&lt;/b&gt; 지난 20키로 런때 허벅지 쓸림때문에 오른쪽 다리안닿게 하려고 뛰어서 무릎이 좀 안좋았음.. &lt;b&gt;/&lt;/b&gt; 그 핑계로 4일정도 운동 하나도 안했음 &lt;b&gt;/&lt;/b&gt; 다리에 무릎에 발까지 아프고 배아팠음..&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;th&gt;Kiwi CoNg Global&lt;/th&gt;
&lt;td&gt;술을 잘 안하는데 어제 마심 ㅠㅠ 지난 20키로 런때 허벅지 쓸림때문에 오른쪽 다리안닿게 하려고 뛰어서 무릎이 좀 안좋았음.. &lt;b&gt;/&lt;/b&gt; 그 핑계로 4일정도 운동 하나도 안했음 &lt;b&gt;/&lt;/b&gt; 다리에 무릎에 발까지 아프고 배아팠음..&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;th&gt;Bareun&lt;/th&gt;
&lt;td&gt;술을 잘 안하는데 어제 마심 ㅠㅠ 지난 20키로 런때 허벅지 쓸림때문에 오른쪽 다리안닿게 하려고 뛰어서 무릎이 좀 안좋았음.. 그 핑계로 4일정도 운동 하나도 안했음 다리에 무릎에 발까지 아프고 배아팠음..&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;th rowspan=&quot;6&quot;&gt;&amp;nbsp;&lt;/th&gt;
&lt;th&gt;입력&lt;/th&gt;
&lt;td&gt;저에게 옥수동 하면 가장 떠오르는 것은 다름 아닌 `서울의 달`입니다. &lt;b&gt;/&lt;/b&gt; 긍정적인 요인은 길음 뉴타운의 대장주인 센터피스의 고공행진입니다.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;th&gt;Kiwi KnLM&lt;/th&gt;
&lt;td&gt;저에게 옥수동 하면 가장 떠오르는 것은 다름 아닌 `서울의 달`입니다. &lt;b&gt;/&lt;/b&gt; 긍정적인 요인은 길음 뉴타운의 대장주인 센터피스의 고공행진입니다.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;th&gt;Kiwi SBG&lt;/th&gt;
&lt;td&gt;저에게 옥수동 하면 가장 떠오르는 것은 다름 아닌 `서울의 달`입니다. &lt;b&gt;/&lt;/b&gt; 긍정적인 요인은 길음 &lt;b&gt;/&lt;/b&gt; 뉴타운의 대장주인 센터피스의 고공행진입니다.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;th&gt;Kiwi CoNg&lt;/th&gt;
&lt;td&gt;저에게 옥수동 하면 가장 떠오르는 것은 다름 아닌 `서울의 달`입니다. &lt;b&gt;/&lt;/b&gt; 긍정적인 요인은 길음 &lt;b&gt;/&lt;/b&gt; 뉴타운의 대장주인 센터피스의 고공행진입니다.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;th&gt;Kiwi CoNg Global&lt;/th&gt;
&lt;td&gt;저에게 옥수동 하면 가장 떠오르는 것은 다름 아닌 `서울의 달`입니다. &lt;b&gt;/&lt;/b&gt; 긍정적인 요인은 길음 &lt;b&gt;/&lt;/b&gt; 뉴타운의 대장주인 센터피스의 고공행진입니다.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;th&gt;Bareun&lt;/th&gt;
&lt;td&gt;저에게 옥수동 하면 가장 떠오르는 것은 다름 아닌 `서울의 달`입니다. &lt;b&gt;/&lt;/b&gt; 긍정적인 요인은 길음 뉴타운의 대장주인 센터피스의 고공행진입니다.&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p style=&quot;text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;&quot;어제 마심 ㅠㅠ&quot;과 같이 이모티콘이 따라붙는 경우를 잘못 해석한 경우가 있고, 또 &quot;길음 뉴타운&quot;의 길음을 종결 어미로 잘못 해석한 경우도 있습니다. 후자는 처음에 Unlikelihood Minimization을 도입할때 직면했던것과 유사한 문제네요. &quot;길음&quot;이라는 고유명사를 몰라서 &lt;b&gt;길/VA + 음/ETN&lt;/b&gt;으로 잘못 분석했고 이게 문장 분리까지 영향을 미친 것입니다. 여전히 이런 오류들이 있는 것으로 보아 지명과 같은 고유 명사에 대해서는 추가적인 학습이 필요할지도 모르겠습니다.&lt;/p&gt;
&lt;h3 style=&quot;text-align: justify;&quot; data-ke-size=&quot;size23&quot;&gt;정리&lt;/h3&gt;
&lt;p style=&quot;text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;지금까지 형태소 분석, 모호성 해소, 문장 분리 3가지 평가 과제를 통해 Kiwi CoNg 모델이 기존 모델 대비 실제로 성능이 개선되었는지 확인해보았습니다. 형태소 분석의 경우 잘 모르는 패턴을 접한 경우 자기 멋대로 분석하려는 경향성이 강해져서 이를 방지하기 위해 &lt;b&gt;Unlikelihood Training을 도입&lt;/b&gt;하였고, 덕분에 해당 문제가 크게 줄어들었으나 여전히 &lt;b&gt;기존의 KnLM 대비 분석 정확도는 약간 낮은 것&lt;/b&gt;으로 드러났습니다. 반면 &lt;b&gt;모호성 해소와 문장 분리 성능은 기존 모델 대비 개선&lt;/b&gt;되었으며 특히 &lt;b&gt;모호성 해소 과제에서는 압도적으로 높은 향상&lt;/b&gt;이 있었기에 &lt;b&gt;CoNg 모델 도입의 이득이 손실보다 더 크다&lt;/b&gt;는 결론을 내릴 수 있었습니다.&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;b&gt;불규칙 활용 모호성 해소&lt;/b&gt;나 &lt;b&gt;사투리 등의 다양한 어미가 포함된 문장 분리 문제&lt;/b&gt;는 추가적인 연구가 필요한 상황이라고 할 수 있겠습니다. 추후 연구 과제도 중요하지만 이건 일단 미래에 맡기고, 옛말처럼 구슬이 서 말이어도 꿰지 않으면 아무 쓸모가 없겠죠? CoNg 모델의 효과가 아무리 입증되었다고 한들 실제로 Kiwi 소스코드 내에 최적화된 채로 구현이 들어가야 형태소 분석기로 사용을 할 수가 있을 겁니다. 특히 이전 포스팅에서 CoNg모델을 사용할 경우 형태소 분석에 들어가는 연산량이 적을 수 있다는 것을 이론상으로만 추정해보았고 실제로 어느정도 속도가 나오는지는 미래의 저에게 맡겨두었는데요, 이제 그 '미래의 제'가 일해야할 시간입니다. 다음 포스팅에서는 어떤 방식으로 경량화 &amp;amp; 최적화를 적용하여 CoNg모델이 Kiwi에 통합되어 빠르게 동작할 수 있었는지 그 최적화 과정을 소개하도록 하겠습니다.&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>프로그래밍/NLP</category>
      <category>CONG</category>
      <category>GPT</category>
      <category>kiwi</category>
      <category>언어 모델</category>
      <category>형태소 분석기</category>
      <author>&amp;int;2tdt=t&amp;sup2;+c</author>
      <guid isPermaLink="true">https://bab2min.tistory.com/678</guid>
      <comments>https://bab2min.tistory.com/678#entry678comment</comments>
      <pubDate>Mon, 19 May 2025 03:17:31 +0900</pubDate>
    </item>
    <item>
      <title>형태소 분석기 Kiwi CoNg (2/4): CoNg 모델 소개</title>
      <link>https://bab2min.tistory.com/677</link>
      <description>&lt;p style=&quot;text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;지난 포스팅에서는 Kiwi에 내장된 통계 기반 언어 모델이 가지는 한계를 살펴보고 신경망 모델 도입 시 얻을 수 있는 혜택이 무엇인지 다뤘습니다. 그리고 어떤 신경망 구조를 사용할지 결정하기 위해서 다음과 같은 조건을 설정했다고 했었죠.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li style=&quot;list-style-type: disc;&quot;&gt;모델 크기는 100MB이내&lt;/li&gt;
&lt;li style=&quot;list-style-type: disc;&quot;&gt;속도는 현재 KnLM 기반의 분석기와 유사할 것. 혹시나 느려지더라도 1.5배 이상 느려지면 안됨.&lt;/li&gt;
&lt;/ul&gt;
&lt;p style=&quot;text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;또 신경망 모델 중 가장 정교한 것은 Transformer Decoder를 사용한 GPT 계열 생성 모델이고, 가장 가벼운 것은 Word2Vec CBOW라고 언급했었습니다. 당연히 GPT 모델을 사용할 수 있으면 최고겠지만, 위의 두 조건을 달성할 수 없으니 현실과 타협하면서 모델 구조를 경량화해야했는데요, 오늘 포스팅에서는 경량화를 통해 도달하게 된 CoNg 모델의 구조와 그 학습 방법을 소개하고자 합니다.&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;Kiwi CoNg 포스팅 시리즈&lt;/p&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;&lt;a href=&quot;https://bab2min.tistory.com/676&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;형태소 분석기 Kiwi CoNg (1/4): 신경망 모델 도입기&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;b&gt;형태소 분석기 Kiwi CoNg (2/4): CoNg 모델 소개&lt;/b&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://bab2min.tistory.com/678&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;형태소 분석기 Kiwi CoNg (3/4): 모델 성능 비교&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://bab2min.tistory.com/679&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;형태소 분석기 Kiwi CoNg (4/4): 모델 최적화&lt;/a&gt;&lt;/li&gt;
&lt;/ol&gt;
&lt;h3 style=&quot;text-align: justify;&quot; data-ke-size=&quot;size23&quot;&gt;무겁지만 뛰어난 GPT 모델&lt;/h3&gt;
&lt;p style=&quot;text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;무엇이든 탐색을 시작할 때는 베이스라인을 잘 잡고 가는 것이 중요합니다. 오늘날 신경망 기반 언어 모델에서 사실상 표준은 GPT이므로 일단 GPT모델을 베이스라인으로 잡고 시작해봅시다. GPT 모델은 1부터 시작해서 현재 4.5까지 다양한 버전이 나왔는데, 사실 오픈소스로 공개된 것은 GPT-1, 2밖에 없고 나머지는 그 실체나 내부구조를 정확히 알 수 없습니다. 따라서 GPT-2를 베이스라인으로 삼았습니다. GPT-2는 작은 크기인 Base부터 가장 큰 크기인 XL까지 총 4가지 규모로 공개가 되었습니다. 각 크기별로 하이퍼 파라미터의 값을 정리해보면 다음 표와 같습니다.&lt;/p&gt;
&lt;table class=&quot;std-table&quot; data-ke-align=&quot;alignLeft&quot;&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;th&gt;모델 크기&lt;/th&gt;
&lt;th style=&quot;text-align: center;&quot;&gt;Vocab Size&lt;/th&gt;
&lt;th style=&quot;text-align: center;&quot;&gt;Hidden Size&lt;/th&gt;
&lt;th style=&quot;text-align: center;&quot;&gt;Num Heads&lt;/th&gt;
&lt;th style=&quot;text-align: center;&quot;&gt;Num Layers&lt;/th&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;th style=&quot;text-align: center;&quot;&gt;(Small)&lt;/th&gt;
&lt;td style=&quot;text-align: right;&quot;&gt;50256&lt;/td&gt;
&lt;td style=&quot;text-align: right;&quot;&gt;512&lt;/td&gt;
&lt;td style=&quot;text-align: right;&quot;&gt;8&lt;/td&gt;
&lt;td style=&quot;text-align: right;&quot;&gt;6&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;th style=&quot;text-align: center;&quot;&gt;Base&lt;/th&gt;
&lt;td style=&quot;text-align: right;&quot;&gt;50256&lt;/td&gt;
&lt;td style=&quot;text-align: right;&quot;&gt;768&lt;/td&gt;
&lt;td style=&quot;text-align: right;&quot;&gt;12&lt;/td&gt;
&lt;td style=&quot;text-align: right;&quot;&gt;12&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;th style=&quot;text-align: center;&quot;&gt;Medium&lt;/th&gt;
&lt;td style=&quot;text-align: right;&quot;&gt;50256&lt;/td&gt;
&lt;td style=&quot;text-align: right;&quot;&gt;1024&lt;/td&gt;
&lt;td style=&quot;text-align: right;&quot;&gt;16&lt;/td&gt;
&lt;td style=&quot;text-align: right;&quot;&gt;24&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;th style=&quot;text-align: center;&quot;&gt;Large&lt;/th&gt;
&lt;td style=&quot;text-align: right;&quot;&gt;50256&lt;/td&gt;
&lt;td style=&quot;text-align: right;&quot;&gt;1280&lt;/td&gt;
&lt;td style=&quot;text-align: right;&quot;&gt;20&lt;/td&gt;
&lt;td style=&quot;text-align: right;&quot;&gt;36&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;th style=&quot;text-align: center;&quot;&gt;XL&lt;/th&gt;
&lt;td style=&quot;text-align: right;&quot;&gt;50256&lt;/td&gt;
&lt;td style=&quot;text-align: right;&quot;&gt;1600&lt;/td&gt;
&lt;td style=&quot;text-align: right;&quot;&gt;25&lt;/td&gt;
&lt;td style=&quot;text-align: right;&quot;&gt;48&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p style=&quot;text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;Small사이즈는 OpenAI에서 공개한 바는 없지만 개인적으로 판단하기에 Base조차도 CPU에서 돌리기에는 다소 부담이 있다고 생각하여 더 작은 크기인 Small을 임의로 비교군에 추가하였습니다. 이 모델들을 가지고 $P(x|y)$, 즉 문맥 $y$ 다음에 형태소 $x$가 등장할 확률이 얼마인지 추정할때 얼마나 많은 연산이 필요한지 FLOPs단위로 연산량을 추정해보았습니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1306&quot; data-origin-height=&quot;708&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bCsqWM/btsNJ1xZflA/JWAYUiDIaSi4OFhRLL7Fm1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bCsqWM/btsNJ1xZflA/JWAYUiDIaSi4OFhRLL7Fm1/img.png&quot; data-alt=&quot;문맥 길이가 4일때 모델 크기에 따른 각 컴포넌트별 연산량 FLOPs 비교 (직접 계산함)&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bCsqWM/btsNJ1xZflA/JWAYUiDIaSi4OFhRLL7Fm1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbCsqWM%2FbtsNJ1xZflA%2FJWAYUiDIaSi4OFhRLL7Fm1%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;1306&quot; height=&quot;708&quot; data-origin-width=&quot;1306&quot; data-origin-height=&quot;708&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;문맥 길이가 4일때 모델 크기에 따른 각 컴포넌트별 연산량 FLOPs 비교 (직접 계산함)&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1324&quot; data-origin-height=&quot;720&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bzjcHM/btsNKxpxUy4/tK2d8PtPInmkKWWPAhTbF1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bzjcHM/btsNKxpxUy4/tK2d8PtPInmkKWWPAhTbF1/img.png&quot; data-alt=&quot;문맥 길이가 16일때 모델 크기에 따른 각 컴포넌트별 연산량 FLOPs 비교 (직접 계산함)&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bzjcHM/btsNKxpxUy4/tK2d8PtPInmkKWWPAhTbF1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbzjcHM%2FbtsNKxpxUy4%2FtK2d8PtPInmkKWWPAhTbF1%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;1324&quot; height=&quot;720&quot; data-origin-width=&quot;1324&quot; data-origin-height=&quot;720&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;문맥 길이가 16일때 모델 크기에 따른 각 컴포넌트별 연산량 FLOPs 비교 (직접 계산함)&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1346&quot; data-origin-height=&quot;726&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bPJg7x/btsNJ2ReRmL/tEkncbZkcAh8Ku4nQjhTK1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bPJg7x/btsNJ2ReRmL/tEkncbZkcAh8Ku4nQjhTK1/img.png&quot; data-alt=&quot;문맥 길이가 256일때 모델 크기에 따른 각 컴포넌트별 연산량 FLOPs 비교 (직접 계산함)&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bPJg7x/btsNJ2ReRmL/tEkncbZkcAh8Ku4nQjhTK1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbPJg7x%2FbtsNJ2ReRmL%2FtEkncbZkcAh8Ku4nQjhTK1%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;1346&quot; height=&quot;726&quot; data-origin-width=&quot;1346&quot; data-origin-height=&quot;726&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;문맥 길이가 256일때 모델 크기에 따른 각 컴포넌트별 연산량 FLOPs 비교 (직접 계산함)&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;총 연산량은 문맥 길이에 크게 좌우됩니다. 그래서 문맥 길이가 4인 경우, 16인 경우, 256인 경우를 나누어 보았습니다. &lt;a href=&quot;https://www.intel.co.kr/content/www/kr/ko/support/articles/000005755/processors.html&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;Intel이 공개한 자료&lt;/a&gt;에 의하면 최신 CPU들의 일반적인 부동소수점 연산능력은 100~1000 GFLOPs정도(초당 100~1000 GFLOP을 연산 가능)가 된다고 합니다. 위에서 추산한 것은 형태소 1개의 확률을 계산하는데에 드는 연산량입니다. 문장 하나를 형태소 분석한다고 하면 이와 같은 형태소 확률 계산을 문장 길이에 따라 다르겠지만 최소 몇 백 개에 몇 천 개까지 해야할 수 있습니다. 대략 1000번 이 연산을 수행한다고 가정해봅시다. 아주 작은 (Small) 모델에 문맥길이도 아주 짧게 4를 사용한다고 쳐도, 1회에 0.1GFLOP의 연산이 필요합니다. 1000회 한다고 치면 총 100GFLOP이 필요하구요, 연산성능이 1000GFLOPs인 CPU가 아주 이상적으로 작동하면 이걸 0.1초만에 처리할 수 있을 것입니다(형태소 분석에 들어가는 다른 연산들은 일단 무시하구요). 그럼 &lt;b&gt;1초에 10문장 정도를 처리&lt;/b&gt;할 수 있겠네요. 참고로 현재 &lt;b&gt;KnLM 기반의 형태소 분석기는 초당 1000~2000개 정도의 문장을 처리&lt;/b&gt;할 수 있습니다. 즉 아주 작은 &lt;b&gt;Small 모델을 쓴다해도 KnLM보다 100배 이상 느려진다&lt;/b&gt;는 것입니다. 즉 GPT 구조를 Kiwi내에 그대로 넣을 생각은 꿈도 꾸지 말라는 결론이 되겠네요~&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;그래도 GPT가 잘하니깐 포기하기는 아쉽습니다. 모델 크기를 더 줄이거나 문맥을 더 줄이면 어떻게 안될까요? 극단적으로 문맥 길이를 1로 줄인다고 하면 연산량을 약 1/4로 줄일 수 있습니다. 근데 문맥 길이가 1이라고 하는 것은 바로 이전 단어만 보고 다음 단어를 예측한다는 건데 이럴거면 GPT모델을 쓸 필요가 있느냐는 근본적인 물음에 직면하게 됩니다. 이 물음에 대해서는 이따가 더 고민하는 것으로 하고 일단 대척점에 서 있는 제일 가벼운 모델인 Word2Vec으로 가봅시다.&lt;/p&gt;
&lt;h3 style=&quot;text-align: justify;&quot; data-ke-size=&quot;size23&quot;&gt;가볍지만 어설픈 Word2Vec CBOW 모델&lt;/h3&gt;
&lt;p style=&quot;text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;Word2Vec CBOW(Continuous Bag Of Words)은 사실 다음 단어를 예측하는 모델은 아닙니다. 주변의 단어들을 이용해서 중심 단어를 예측하는 모델입니다. 예를 들자면 다음과 같습니다.&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style3&quot;&gt;오늘 점심은 ___을 먹자&lt;/blockquote&gt;
&lt;p style=&quot;text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;여기서 빈 칸에 들어갈 내용을 찾는데, 주변의 N개의 단어를 이용하겠다는게 Word2Vec CBOW의 접근법이고, 이 때 이 N개의 단어는 순서를 전혀 고려하지 않습니다. 즉 순서 상관없이 (오늘, 점심, 은, 을, 먹자)가 주변에 있을 때 어떤 단어 $x$가 등장할 확률을 구하는게 목적인 것이지요. 하지만 우리는 다음 형태소를 예측하는데에 이걸 쓰고 싶은 것이므로 CBOW의 정의에서 &quot;&lt;b&gt;주변의 단어&lt;/b&gt;&quot;를 &quot;&lt;b&gt;왼쪽에 있는 단어&lt;/b&gt;&quot;로 바꾸어서 사용하도록 하겠습니다. 따라서 (오늘, 점심, 은)이 순서 상관없이 왼쪽에 있을 때 어떤 단어 $x$가 등장할 확률을 구하는게 변경된 목적이 됩니다. 이러면 다음 형태소의 확률을 예측하는데에 충분히 쓸 수 있습니다. 여기서 $P(x | y_1, y_2, \cdots , y_L)$는 길이가 $L$인 문맥 $y$ 다음에 형태소 $x$가 등장할 확률을 계산하는 함수입니다. $\mathbf H$는 입력 문맥의 임베딩을 평균내는 함수(-&amp;gt;문맥을 임베딩으로 변환), $\mathbf D$는 출력 형태소의 임베딩을 구하는 함수(-&amp;gt;다음 형태소를 임베딩으로 변환)이 됩니다.&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;$$ P(x | y_1, y_2, \cdots , y_L) = \mathbf{Softmax}( \mathbf{H}(y_1, y_2, \cdots , y_L) \cdot \mathbf{D}(x) ) $$&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;Word2Vec CBOW의 연산량을 관장하는 하이퍼파라미터는 딱 3가지 밖에 없습니다. 전체 Vocab Size와 문맥의 길이, 그리고 Embedding의 크기입니다. 위의 GPT와 직접 비교가 가능하도록 Small 모델과 마찬가지로 Vocab Size는 50256, Embedding 크기는 512라고 해봅시다. CBOW의 연산량은 다음과 같이 간단하게 계산할 수 있습니다.&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;$$\mathbf{CBOW\_FLOP} = \mathbf{ContextLength} \cdot \mathbf{Dim} + \mathbf{VocabSize} \cdot \mathbf{Dim} $$&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;2개의 항으로 이뤄져있는데 왼쪽은 입력 문맥 임베딩들의 평균을 구하는 부분이고 오른쪽은 출력 임베딩들의 확률을 계산하는 부분입니다. 입력 문맥 길이가 4라면 왼쪽 항은 4 * 512가 되어 매우 작은 값이고, 사실상 오른쪽 항(50256 * 512)이 전체 연산량을 좌우하는 부분이됩니다. 실제로 계산해보면 약 0.026 GFLOP이 나오네요. GPT Small보다 4배 정도밖에 연산량이 안 줄었습니다. 가장 가볍다는 녀석도 이렇게 연산량이 많아서야 도저히 Kiwi 내장 모델로 사용할수가 없겠습니다.&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;병목은 위에서 언급했다시피 출력 임베딩들의 확률 계산하는 부분에 있습니다. 전체 형태소가 50256가지라면 50256가지에 대해 그 확률을 모두 계산해야하기 때문에 연산량이 늘어나는 것이죠. 근데 Kiwi에 내장된 언어 모델이 해야할 것은 다음 형태소를 예측하는게 아니라 &lt;b&gt;다음 형태소가 주어졌을때 그 확률을 계산하는 것&lt;/b&gt;이라고 했죠? 그런데도 꼭 전체 형태소들에 대해 계산을 해야할까요? 답부터 말하자면 예, 계산해야 합니다. 어쩔수가 없습니다. 그 이유는 출력부분에 붙은 &lt;b&gt;Softmax&lt;/b&gt; 때문입니다. Softmax연산을 풀어 적으면 다음과 같습니다.&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;$$P(a) = \frac{e^{s_a}}{\sum_i{e^{s_i}}}$$&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;예를 들어 위와 같이 &quot;오늘 점심은&quot;이라는 왼쪽 문맥이 주어졌고, 우리가 가지고 있는 후보 형태소는 &quot;짜장면, 제육, 냉면, 돈까스, 국밥&quot; 5가지가 있다고 가정해봅시다. $\mathbf H$의 결과와 $\mathbf D$의 결과를 가져와 내적을 구했더니 다음과 같은 값이 나왔네요.&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;$$s_{짜장면} = 1.2 \\ &lt;br /&gt;s_{제육} = 5.1 \\ &lt;br /&gt;s_{냉면} = -0.1 \\ &lt;br /&gt;s_{돈까스} = 2.7 \\ &lt;br /&gt;s_{국밥} = -0.6 $$&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;이 값들은 확률의 범위가 아니기에 Softmax 연산을 통해서 0~1사이 값으로 정규화해줘야 제대로된 확률값이 나오게됩니다.&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;$$P(짜장면) \approx 0.018 \\&lt;br /&gt;P(제육) \approx 0.892 \\&lt;br /&gt;P(냉면) \approx 0.004 \\&lt;br /&gt;P(돈까스) \approx 0.081 \\&lt;br /&gt;P(국밥) \approx 0.002 $$&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;그래서 만약 다음 형태소가 &quot;짜장면&quot;일 확률만 알고싶다고 하더라도, 짜장면부터 국밥까지 모든 $s_i$값을 구해야 짜장면의 확률을 제대로 알 수 있습니다. 이게 출력항에서 모든 형태소에 대한 임베딩을 다 계산하는 이유입니다. 그 중 1개 형태소 값만 알고싶은데 전체를 다 계산해야한다니 너무 낭비가 심하다고 볼수도 있죠. 만약 우리가 $\sum_i{e^{s_i}}$를 알고 있다면 $s_{짜장면}$만 구해도 $P(짜장면)$을 구할 수 있을텐데요. 이게 가능하다고 하면 전체 CBOW의 연산량은 다음과 같이 줄어들 겁니다.&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;$$\mathbf{CBOW\_FLOP'} = \mathbf{ContextLength} \cdot \mathbf{Dim} + \mathbf{Dim} $$&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;이 경우 총 연산량은 약 0.000003 GFLOP이 되어 전보다 만 분의 일 정도로 적은 연산만으로도 확률을 추정할 수 있게 됩니다! 근데 과연 모든 $s_i$를 구하지 않고도 분모항인 $\sum_i{e^{s_i}}$를 구할 수 있는 방법이 있을까요? $s_i$값은 입력 문맥 임베딩의 평균에 좌우됩니다. 즉 특정한 입력 문맥 임베딩의 평균에 대해 분모항 $\sum_i{e^{s_i}}$를 미리 계산해두고, 실제로 이 값이 필요할때는 미리 계산된 값을 가져다가 쓴다면 분모항 $\sum_i{e^{s_i}}$ 계산을 우회할 수 있습니다! 그런데 가능한 입력 임베딩의 평균은 몇 가지나 될까요? 문맥 길이를 $L$이라고 하고, 알고 있는 형태소의 개수가 $V$개라고 하면 총 L개의 형태소가 문맥에 들어갈 것이고, 각 형태소는 $V$개 중에 1개일 것이므로 $V^L$이 가능한 입력의 가짓수일 것입니다. $L=4$, $V=50256$이라고 하면 $6.38 \times 10^{18}$ 정도가 되네요. 이 걸 미리 다 계산해서 저장해두는 건 말이 안됩니다. 만약 문맥 길이를 줄여서 $L=1$ ~ $2$ 정도로 한다면 충분히 미리 계산해둘만한 크기가 되긴 하겠네요. 근데 지금 KnLM이 사용하는 문맥 길이가 3~4이므로 &lt;b&gt;왼쪽 형태소 1개 혹은 2개만을 보는 신경망 모델은 크게 메리트가 없&lt;/b&gt;습니다.&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;가볍고 빠른 신경망 모델을 향한 이렇게 여정은 실패로 끝나고 마는 것일까요? 다행히도 기존 Kiwi가 사용하던 KnLM에 돌파구가 있었습니다.&lt;/p&gt;
&lt;h3 style=&quot;text-align: justify;&quot; data-ke-size=&quot;size23&quot;&gt;자주 쓰이는 표현을 미리 계산해두자.&lt;/h3&gt;
&lt;p style=&quot;text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;문맥 길이가 4인 형태소들을 모두 찾아서 분모항을 미리 계산해두자는게 위의 최적화 아이디어였는데, 굳이 모든 형태소 조합을 고려해야할까요? &quot;&lt;b&gt;오늘, 점심, 메뉴, 는&lt;/b&gt;&quot;이라는 형태소 조합과 &quot;&lt;b&gt;알고리즘, 초콜렛, 뛰, 는&lt;/b&gt;&quot;이라는 형태소 조합이 있다고 했을때 둘을 동등하게 저장해두는게 맞을까요? 사실 전자는 충분히 접할 가능성이 있는 반면 후자는 형태소 분석기가 거의 접할 일이 없는 형태소 조합입니다. 물론 후자와 같은 표현도 언젠가 자연스레 나타날 가능성이 있겠지만, 현대 한국어에서는 그럴 일이 없을것 같습니다. 따라서 문맥 길이가 4인 형태소 조합들의 빈도수를 조사하여 자주 쓰이는 것들만 미리 계산해둔다면 저장해둬야할 분모항의 개수도 줄어들면서 연산량도 크게 줄일 수 있을 것입니다. 사실 이렇게 자주 쓰이는 표현들만 골라서 사용하는 것은 이미 KnLM에서 쓰이는 아이디어입니다. 실제로 KnLM에서도 문맥 길이를 최대 4까지 확장하여 쓰지만, 실제로 다루게 되는 문맥은 1000만 종류 안쪽입니다. 1000만 개 이내라면 충분히 계산해서 저장해둘만한 크기이지요.&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;그런데 자주 쓰이는 문맥 표현만 선택해서 계산해둔다는 방식이 꼭 CBOW에만 적용될 필요는 없습니다. 동일하게 GPT에도 적용할 수 있습니다.&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;$$ P(x | y_1, y_2, \cdots , y_L) = \mathbf{Softmax}( \mathbf{H}(y_1, y_2, \cdots , y_L) \cdot \mathbf{D}(x) ) $$&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;GPT모델이 다음 토큰의 확률을 계산하는 과정을 크게 멀리서 보면 결국 Word2Vec CBOW과 다를게 없습니다. 다만 $\mathbf H$가 단순히 입력 문맥의 임베딩 평균을 계산하는게 아니고, 문맥 내의 토큰들에 대해서 Self-attention과 Feed Forward Network처럼 좀 더 복잡한 연산을 수행한다는 차이만 있을 뿐이지요. 어쨌든 $y$가 이미 결정되어 있다면 $ \mathbf{H}(y_1, y_2, \cdots , y_L) $도 항상 값이 결정되므로 CBOW와 마찬가지로 미리 계산해둘 수 있습니다. 그러면 우리는 실제 모델 추론 시에는 그냥 두 임베딩 간의 내적 연산 한번만 수행해주면 GPT와 동일한 결과를 얻을 수 있습니다.&amp;nbsp;&lt;/p&gt;
&lt;h3 style=&quot;text-align: justify;&quot; data-ke-size=&quot;size23&quot;&gt;CoNg: Contextual N-gram embedding&lt;/h3&gt;
&lt;p style=&quot;text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;지금까지의 아이디어를 통해 등장한 것이 바로 &lt;b&gt;Contextual N-gram embedding&lt;/b&gt;(줄여서 &lt;b&gt;CoNg&lt;/b&gt;)입니다. n-gram 자체가 의미를 반영한 문맥 임베딩으로 바로 대응된다는 점에서 이렇게 이름 붙였습니다. (사실 Context2Vec, Ngram2Vec 같은 이름을 쓰고 싶었는데 이미 다른 연구에서 쓰이고 있어서...) 바로 위에서 설명한 것처럼 자주 쓰이는 문맥에 대해서는 미리 계산을 하는 방식을 사용하고, 여기에 추가로 저장할 문맥의 숫자를 줄이기 위해 유사한 문맥들은 묶어서 하나의 클러스터를 구성하도록 한 것입니다. 예를 들어 &quot;오늘 점심 메뉴는&quot;, &quot;내일 점심 메뉴는&quot;, &quot;이번 중간 고사는&quot;, &quot;이번 기말 고사는&quot;이라는 문맥이 있다고 생각해볼까요? 해당 문맥 다음에 등장할 형태소들을 생각해 볼 때 &quot;오늘 점심 메뉴는&quot;과 &quot;내일 점심 메뉴는&quot;은 서로 굉장히 유사할 것이고, &quot;이번 중간 고사는&quot;과 &quot;이번 기말 고사는&quot;도 역시 서로 굉장히 유사할 것입니다. 따라서&amp;nbsp; 다음과 같이 유사한 문맥들은 동일한 문맥 클러스터에 할당합니다.&lt;/p&gt;
&lt;table class=&quot;std-table&quot; data-ke-align=&quot;alignLeft&quot;&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;th&gt;문맥 클러스터&lt;/th&gt;
&lt;th&gt;문맥들&lt;/th&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;th&gt;$c_0$&lt;/th&gt;
&lt;td&gt;오늘 점심 메뉴는, 내일 점심 메뉴는, ...&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;th&gt;$c_1$&lt;/th&gt;
&lt;td&gt;이번 중간 고사는, 이번 기말 고사는, ...&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;th&gt;$c_2$&lt;/th&gt;
&lt;td&gt;...&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;th&gt;...&lt;/th&gt;
&lt;td&gt;...&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p style=&quot;text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;이렇게 길이가 $L$이하인 자주 쓰이는 문맥들에 대해서 유사한 것끼리 동일한 클러스터로 할당하고, 실제로 $\mathbf H$를 계산할 때도 문맥 클러스터를 인자로 넣어줍니다. 어차피 $\mathbf H$의 결과는 입력 $c_k$에 의해 결정되는 것이므로 내부에 복잡한 연산을 둘 필요도 없이 Embedding Layer에서 $k$에 해당하는 임베딩 값을 읽어오는 식으로 구현해도 상관없습니다.&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;$$ P(x | c_k) = \mathbf{Softmax}( \mathbf{H}(c_k) \cdot \mathbf{D}(x) ) $$&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;이제 주어진 문맥을 대응하는 문맥 클러스터로 적절하게 바꿔서 넣어주기만 하면 다음 형태소의 확률을 계산할 수 있게 된 것입니다. 근데 우리는 길이가 $L$인 모든 종류의 형태소 조합을 다루지는 않을 것이므로 만약 어느 문맥 클러스터에도 속하지 않는 문맥이 들어왔을 경우의 대책을 마련해놓아야합니다. 백업 플랜으로 어느 문맥 클러스터에도 속하지 않는 문맥이 입력되면 문맥에서 제일 왼쪽의 형태소를 잘라버려서 문맥을 줄이도록 합니다. 또한 문맥 클러스터를 구성할때 길이가 $L$인 것만을 포함하지 말고, 1~$L$까지 모두를 포함하는 것으로 합시다. 그리고 길이가 1인 문맥은 저빈도여도 버리지 않고 항상 대응되는 문맥 클러스터가 존재하도록 배정하겠습니다. 이렇게 하면 우리는 어떤 문맥이 들어오더라도 항상 대응되는 문맥 클러스터를 찾을 수 있습니다.&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;예를 들어 &quot;어제 점심 메뉴는&quot;이라는 문맥이 입력되었는데, 대응되는 문맥 클러스터가 없다고 가정해봅시다. 그럼 여기에서 왼쪽 형태소 하나를 잘라버려서 &quot;점심 메뉴는&quot;이라는 문맥을 찾는 것으로 계획을 변경합니다. 만약 &quot;점심 메뉴는&quot;에 대응되는 클러스터를 찾았다면 여기서 마무리하고, 그렇지 못했다면 형태소를 한 번 더 잘라서 &quot;메뉴는&quot;을 찾아봅니다. 만약 &quot;메뉴는&quot;도 없다면 마지막으로 한 번 더 형태소를 잘라서 &quot;는&quot;을 찾아봅니다. 길이가 1인 문맥은 저빈도여도 모두 포함하기로 했으므로 이 지점에서 항상 대응되는 클러스터를 찾을 수 있습니다. 왼쪽 형태소가 하나씩 잘려나갈때마다 원 문맥과의 유사성이 조금씩 낮아지긴 하겠지만, 그래도 모르는 문맥이라고 아예 오동작하는 것보다는 훨씬 낫겠죠?&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;자 이제 문맥 클러스터를 잘 구성하기만 하면 CoNg 모델이 최종적으로 완성됩니다. 당연히 실제로 의미가 유사한 문맥들을 같은 클러스터에 모아두어야지 모델이 GPT 모델과 유사한 품질에 도달할 수 있겠죠? 수많은 문맥들 중 의미가 유사한 것들을 어떻게 묶어낼 수 있을까요? GPT모델의 $\mathbf H$함수를 생각해봅시다. 이 함수의 역할은 결국 주어진 문맥을 그 의미가 잘 반영되는 임베딩으로 바꾸어서 $\mathbf D$와 내적을 구하면 다음 형태소가 적절하게 나오게 하는 것입니다. 즉 $\mathbf H$ 함수의 출력값이 그 자체로 의미를 잘 반영하는 임베딩 값이라고 볼 수 있다는 것이죠. 따라서 먼저 일반적인 GPT 모델을 학습시키고, 이 $\mathbf H$의 출력들을 모아서 클러스터링을 수행하면 유사한 문맥 클러스터를 얻을 수 있을 겁니다.&amp;nbsp;&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;CoNg 학습 과정&lt;/b&gt;&lt;/li&gt;
&lt;li&gt;입력: 형태소로 구성된 학습 말뭉치, 문맥의 최대 길이 $L$, 문맥의 최소 빈도수 $t$, 문맥 클러스터의 개수 $s$&lt;/li&gt;
&lt;/ul&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;(문맥 후보 추출) 학습 말뭉치에서 Suffix Array를 구축하여 빈도수가 $t$이상이고 길이가 $L$이하인 모든 Suffix를 추출한다. 길이가 1인 Suffix Array는 빈도와 상관없이 모두 추출한다.&lt;/li&gt;
&lt;li&gt;(문맥 클러스터 생성용 GPT 모델 학습) 학습 말뭉치로 적당한 크기의 GPT 모델을 학습시킨다. 이 때 input embedding과 output embedding의 tie는 해제한다.&lt;/li&gt;
&lt;li&gt;(문맥 클러스터를 위한 임베딩 계산) 2번에서 학습된 GPT모델에 1에서 구한 문맥 후보를 각각 넣어서 그 때의 $\mathbf H$ 값들을 모두 계산한다.&lt;/li&gt;
&lt;li&gt;(문맥 클러스터 생성) 3번에서 계산된 임베딩들을 입력으로 하여 클러스터의 개수가 $s$인 K-means 클러스터링을 수행한다. 이 클러스터링 결과를 바탕으로 문맥 클러스터를 할당한다.&lt;/li&gt;
&lt;li&gt;(실제 CoNg모델 학습) 문맥 클러스터별 임베딩은 2번에서 학습한 GPT모델의 $\mathbf H$ 값으로 초기화. output embedding은 2번 GPT모델에서 그대로 가져온다. 학습 말뭉치를 (문맥 클러스터 ID, 다음 형태소)로 구성되도록 변환한 뒤, GPT와 유사하게 Next Token Prediction loss를 사용하여 CoNg 모델을 학습시킨다.&lt;/li&gt;
&lt;/ol&gt;
&lt;p style=&quot;text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;위 과정을 통해 우리는 GPT모델을 CoNg 모델로 변환할 수 있습니다. 복잡한 Transformer레이어들로 구성된 GPT 모델의 $\mathbf H$는 선택된 문맥 클러스터들에 대해서 미리 계산되어 CoNg에서는 단순한 Embedding lookup 연산으로 바뀌게 됩니다. 속도는 빨라지면서 정확도 손실은 최소화하는 것이죠. 실제로 효과가 있는지 간단한 파일럿 실험을 해보았습니다.&lt;/p&gt;
&lt;table class=&quot;std-table&quot; data-ke-align=&quot;alignLeft&quot;&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;th&gt;Vocab Size&lt;/th&gt;
&lt;td&gt;70128&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;th&gt;Hidden Size&lt;/th&gt;
&lt;td&gt;384&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;th&gt;Num Heads&lt;/th&gt;
&lt;td&gt;6&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;th&gt;Num Layers&lt;/th&gt;
&lt;td&gt;4&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;th&gt;학습데이터&lt;/th&gt;
&lt;td&gt;[모두의 말뭉치] 형태 분석 말뭉치&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p style=&quot;text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;모두의 말뭉치에 있는 형태 분석 말뭉치를 대상으로 형태소들을 뽑아 Vocab을 구성한뒤 이를 통해서 GPT 모델을 학습시켰습니다. 그리고 위에서 설명한 절차대로 CoNg 모델을 학습시켰습니다. 문맥의 최소 빈도수는 5, 최대 길이는 4로 고정했고, 문맥 클러스터의 개수를 바꿔가면서 성능이 어떻게 바뀌는지 측정해보았습니다.&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;평가 척도는 Next Token Prediction의 Loss, 즉 Negative Log-likelihood입니다. 낮을수록 다음 형태소를 더 정확하게 예측한다는 뜻이죠. 다음과 같이 3가지 데이터셋에 대해 평가해보았습니다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;Train: 학습에 사용한 데이터셋 그대로 사용&lt;/li&gt;
&lt;li&gt;In-Domain(ID): 학습에 쓰이지는 않았지만 Train과 출처가 동일한 데이터셋&lt;/li&gt;
&lt;li&gt;Out-of-Domain(OOD): Train과 출처가 아예 다른 데이터셋(웹 텍스트)&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1204&quot; data-origin-height=&quot;734&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/dqstMe/btsNSIcErhu/RbLK6PpikBKZqQvwU8uKQ0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/dqstMe/btsNSIcErhu/RbLK6PpikBKZqQvwU8uKQ0/img.png&quot; data-alt=&quot;CoNg 모델의 가능성을 입증하기 위한 파일럿 실험 결과. GPT보다는 예측력이 조금 떨어지지만, KnLM보다는 크게 개선되었다.&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/dqstMe/btsNSIcErhu/RbLK6PpikBKZqQvwU8uKQ0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FdqstMe%2FbtsNSIcErhu%2FRbLK6PpikBKZqQvwU8uKQ0%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;1204&quot; height=&quot;734&quot; data-origin-width=&quot;1204&quot; data-origin-height=&quot;734&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;CoNg 모델의 가능성을 입증하기 위한 파일럿 실험 결과. GPT보다는 예측력이 조금 떨어지지만, KnLM보다는 크게 개선되었다.&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;가장 기본적인 베이스라인은 통계 언어 모델인 KnLM입니다. KnLM은 학습 때 배운것과 동일한 데이터셋(In-Domain)에서는 아주 높은 예측력을 보이지만 배우지 못한 입력(Out-of-Domain)에서는 매우 취약한 모습을 보입니다(주의: 실제 loss값은 5.6정도였는데 차트 내에 끼워넣고자 억지로 5.0으로 끌고 내려왔음).&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;반면 GPT를 사용할 경우 ID에서는 KnLM보다 살짝 예측력이 떨어지지만 OOD에서 예측력이 크게 개선됩니다. 즉 모델이 배우지 않은 것에 대해서도 제법 잘 예측할 수 있다는 것이지요. 그리고 실제로 CoNg의 경우 문맥 클러스터 개수가 작을 때는 예측력이 많이 낮았지만, 클러스터 개수를 증가시킬수록 점차 GPT모델과 유사한 성능에 다가간다는 것을 확인할 수 있습니다. 실제로 실험하는 것은 불가능하지만 길이가 4일때 가능한 모든 문맥의 개수($70128^4 \approx 2.379 \cdot 10^{16} k$)로 클러스터 개수를 늘리면 이론상 GPT와 완전히 동일한 성능이 되겠죠. 물론 저만한 크기의 문맥 클러스터를 전부 메모리에 담는것은 불가능하기 때문에 적당한 위치에서 타협하는게 필요합니다.&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;ID 성능이 KnLM보다는 많이 떨어지는게 아쉽긴 하지만, 사실 현재 학습에 쓸 수 있는 형태 분석 말뭉치의 규모가 작다는 것을 생각해볼때 ID은 굉장히 좁고, 실제로 분석 대상이 될 텍스트들은 대부분 OOD일 것이 자명하기 때문에 ID에서의 예측력을 살짝 잃더라도 OOD에서 예측력을 높이는게 중요하다고 판단했습니다.&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;모델 성능은 그럭저럭 나오는것 같으니 모델 크기가 어떻게 될지 계산해봅시다. CoNg 모델의 파라미터는 문맥 클러스터 개수 $s$, Vocab Size $v$, 그리고 Hidden Size $d$ 세 가지 하이퍼파라미터에 의해 결정됩니다.&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;$$\mathbf {Num\:of\:Param\:of\:CoNg} = sd + vd$$&lt;/p&gt;
&lt;table class=&quot;std-table&quot; data-ke-align=&quot;alignLeft&quot;&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;th&gt;GPT-Baseline&lt;/th&gt;
&lt;td&gt;61.03M&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;th&gt;CoNg(32k)&lt;/th&gt;
&lt;td&gt;39.56M&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;th&gt;CoNg(64k)&lt;/th&gt;
&lt;td&gt;52.14M&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;th&gt;CoNg(128k)&lt;/th&gt;
&lt;td&gt;77.30M&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;th&gt;CoNg(256k)&lt;/th&gt;
&lt;td&gt;127.64M&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;th&gt;CoNg(512k)&lt;/th&gt;
&lt;td&gt;228.30M&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p style=&quot;text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;문맥 컨텍스트 개수 $s$를 늘릴수록 모델의 파라미터가 팍팍 늘어나는 것을 확인할 수 있습니다. 8비트 양자화를 한다고 가정해도 CoNg(256k)면 벌써 모델 크기가 100MB를 넘어갑니다. 모델 크기는 아쉽지만 그렇다고 $s$를 작게 잡으면 성능이 낮아지니 일단은 넉넉하게 $s$를 잡고 다양한 경량화 및 최적화 기법을 적용하는게 좋아보입니다.&lt;/p&gt;
&lt;div data-ke-type=&quot;moreLess&quot; data-text-more=&quot;관련 연구 더 보기&quot; data-text-less=&quot;닫기&quot;&gt;&lt;a class=&quot;btn-toggle-moreless&quot;&gt;더보기&lt;/a&gt;
&lt;div class=&quot;moreless-content&quot;&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;CoNg 구조를 떠올린 다음 유사한 연구가 있나 찾아보니 다음과 같이 Scone이라는 모델에 대한 연구가 있더라구요.&amp;nbsp;&lt;/p&gt;
&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1626&quot; data-origin-height=&quot;650&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/HLvbT/btsNJ6lK8EH/tZa0qdhZ5DT4limP02Xf6k/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/HLvbT/btsNJ6lK8EH/tZa0qdhZ5DT4limP02Xf6k/img.png&quot; data-alt=&quot;Yu, Da, et al. &amp;quot;Scaling Embedding Layers in Language Models.&amp;quot;&amp;amp;nbsp; arXiv preprint arXiv:2502.01637 (2025). ( https://arxiv.org/pdf/2502.01637 )&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/HLvbT/btsNJ6lK8EH/tZa0qdhZ5DT4limP02Xf6k/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FHLvbT%2FbtsNJ6lK8EH%2FtZa0qdhZ5DT4limP02Xf6k%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;1626&quot; height=&quot;650&quot; data-origin-width=&quot;1626&quot; data-origin-height=&quot;650&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;Yu, Da, et al. &quot;Scaling Embedding Layers in Language Models.&quot;&amp;nbsp; arXiv preprint arXiv:2502.01637 (2025). ( https://arxiv.org/pdf/2502.01637 )&lt;/figcaption&gt;
&lt;/figure&gt;

&lt;p style=&quot;text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;Scone과 비교해보면 CoNg는 Frequent n-gram을 사용한다는 점에서는 동일하지만 Frequent n-gram을 그대로 모델의 입력으로 사용하는 대신 문맥 클러스터를 만들어서 클러스터ID를 입력으로 사용했다는 점, 모델의 최종 구조가 Transformer 레이어가 전혀 포함되지 않고 클러스터ID가 들어가는 임베딩 레이어 하나와 출력 형태소의 LM Head 하나만 존재하는 단순한 구조라는 결정적인 차이점이 있습니다.&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;CoNg Global 모델&lt;/h3&gt;
&lt;p style=&quot;text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;위에서 살펴보았다시피 CoNg 모델은 입력 문맥의 길이를 극도로 짧게 잡은 GPT 모델에서 미리 계산 가능한 부분은 다 계산해두어 연산량을 극적으로 줄인 버전에 가깝습니다. 간단한 글에서는 바로 이전의 3~4개의 형태소만 봐도 문맥을 파악할수 있으므로 입력 문맥의 길이가 짧은 것이 문제가 되지 않으나, 실제로 글이 좀만 복잡해져도 긴 문맥을 다루지 못한다는 것은 치명적인 문제로 다가옵니다. 그래서 KnLM에 Skip-Bigram 모델을 도입하여 먼 거리의 형태소 간의 관계를 모델링한 것처럼 CoNg 모델에도 비슷한 아이디어를 적용하기로 했습니다.&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;b&gt;실질 형태소&lt;/b&gt;(가리키는 대상이 존재)일 것이므로 다음 형태소를 예측할때 바로 이전에 등장했던 실질 형태소 $m$개를 고려하도록 한 것이죠. (이 각각의 실질 형태소들을 $t_i$로 표현하도록 하겠음) 그리고 모델 구조를 단순화하기 위해 각각의 실질 형태소들은 다음 형태소 예측에 독립적 &amp;amp; 선형적으로 영향을 준다고 가정하겠습니다. 다소 과감한 가정이긴 하지만, 여러 실질 형태소들이 동시에 등장하며 영향을 주고 받는 것을 모델링하기 위해서는 다시 Transformer와 같은 복잡한 모델 구조로 회귀해야하기 때문에 정밀성 대신에 단순성을 택한 것입니다. 이렇게 이전의 실질 형태소들이 선형적으로 다음 형태소 예측에 영향을 준다고 가정하고 이들에 대한 항을 추가한 것이 &lt;b&gt;CoNg Global&lt;/b&gt; 모델입니다. 수식으로 표현하면 다음과 같겠죠~&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;$$ P(x | c_k, t_1, t_2, \cdots , t_m) = w_0 \mathbf{Softmax}\left ( \mathbf{H}(c_k) \cdot \mathbf{D}(x) \right) + \sum_{i=1}^m w_i \mathbf{Softmax}\left (\mathbf E (t_i) \cdot \mathbf D x\right ) $$&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;여기에서 $w_0$ CoNg 모델의 확률 가중치, $w_{i\neq 0}$는 $i$번째 실질형태소의 확률 가중치이고&amp;nbsp; $\sum_{i=0}^m w_i = 1$ 입니다. $w_0$이 1에 가까워지고 $w_{i\neq 0}$가 0에 가까워질수록 바로 이전 문맥만 고려하게 되고, 반대로 $w_{i \neq 0}$들이 커질수록 먼 거리의 실질 형태소를 더 많이 고려하게 되는 식입니다. 먼 거리의 형태소가 다음 형태소에 미치는 영향을 모형화하기 위해 $\mathbf E$라는 실질 형태소 각각에 대한 Embedding Layer를 추가했습니다. 이 확률은 CoNg에서와 마찬가지로 다음 형태소의 임베딩과 내적을 구한뒤 Softmax를 취해 실제 확률을 계산하게 됩니다. 마찬가지로 $E(t_i)$는 $t_i$가 결정되면 알 수 있으므로 Softmax의 분모항을 미리 계산해두는 것이 여기에서도 가능합니다.&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;CoNg Global 모델이 잘 동작하려면 확률 가중치 $w_i$를 잘 조정하는게 필수입니다. 이를 사람이 조정하는 것보다는 모델이 학습을 통해 스스로 판단하는게 좋겠다고 생각하여 다음과 같이 계산하도록 모델을 구성했습니다.&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;$$w_0, w_1, \cdots , w_m = \mathbf{Softmax}(v_0, v_1, \cdots , v_m)$$&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;$$v_0 = \mathbf V (c_k)$$&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;$$v_{i \neq 0} = \mathbf U (t_i)$$&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;즉 각각의 가중치 $w_i$는 Softmax를 통해서 계산을 하는데 $v_0$은 이전 문맥의 중요성을 추정하는 함수 $\mathbf V$로부터 가져오고 $v_{i \neq 0}$는 이전 실질 형태소의 중요성을 추정하는 함수 $\mathbf U$로부터 가져오도록 했습니다. 실제로 $\mathbf V(\cdot)$, $\mathbf U(\cdot)$는 그저 학습가능한 실수 파라미터로 두어 모델이 적절한 값을 추정하도록 했구요.&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;CoNg Global 모델을 학습하는 것은 CoNg을 학습하는것과 거의 동일합니다. 5번 과정에서 CoNg 자리에 CoNg Global 모델이 들어가고 새로 추가된 $\mathbf E$ 임베딩은 랜덤으로 초기화한다는 점만 다를뿐입니다. CoNg Global도 실제로 잘 작동하는지 확인하기 위해 위와 동일한 세팅으로 실험을 진행해보았습니다. CoNg Global에서 중요한 하이퍼파라미터는 고려할 실질 형태소의 개수 $m$입니다. 아래 실험에서는 $m=7$로 고정하였습니다. 7을 선택한 이유는 적당히 높으면서 효율적인 숫자이기 때문으로 7로 잡을 경우 $w_0$부터 $w_7$까지 총 8개의 실수를 연산하게 되는데, 이 개수가 요즘 대부분의 CPU에서 동시에 처리할수 있는 최대 실수의 개수이기 때문입니다.&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;1198&quot; data-origin-height=&quot;728&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/ltnMB/btsNRIdyIMc/TmUVBMTsixi3ek6D0ZBh00/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/ltnMB/btsNRIdyIMc/TmUVBMTsixi3ek6D0ZBh00/img.png&quot; data-alt=&quot;CoNg과 CoNg Global의 Loss 비교&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/ltnMB/btsNRIdyIMc/TmUVBMTsixi3ek6D0ZBh00/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FltnMB%2FbtsNRIdyIMc%2FTmUVBMTsixi3ek6D0ZBh00%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;1198&quot; height=&quot;728&quot; data-origin-width=&quot;1198&quot; data-origin-height=&quot;728&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;CoNg과 CoNg Global의 Loss 비교&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;이전과 동일하게 값이 낮을수록 예측력이 높은것이고, CoNg Global의 효과를 비교하기 위해 동일한 세팅의 CoNg 모델을 같이 차트에 그려놓았습니다. 모든 경우에서 전반적으로 Loss가 내려간 것을 확인할 수 있죠? 먼 거리의 실질형태소를 보는 것이 실제로 도움이 되었다고 할 수 있겠습니다.&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;그럼 CoNg Global의 모델 크기는 어떻게 될까요? 실질 형태소에 대한 임베딩인 $\mathbf E$, 가중치 $w_i$를 계산하는데 필요한 $\mathbf V$와 $\mathbf U$가 추가되었으니 CoNg모델의 파라미터 개수에 이 파라미터 크기를 추가해주면 되겠죠. 실질 형태소 개수는 전체 Vocab Size보다 작겠지만, 사실 형식 형태소의 비중은 낮으므로 실질 형태소의 개수도 사실상 Vocab Size와 동일하다고 가정하면 $\mathbf E$에는 $vd$개의 파라미터, $\mathbf V$에는 $s$개의 파라미터, $\mathbf U$에는 $v$개의 파라미터가 들어갑니다. 따라서 추가되는 파라미터 양은 $vd + s + v$입니다. 이를 바탕으로 파라미터 개수를 다시 한번 계산해보면 아래와 같습니다.&lt;/p&gt;
&lt;table class=&quot;std-table&quot; data-ke-align=&quot;alignLeft&quot;&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;th&gt;&amp;nbsp;&lt;/th&gt;
&lt;th&gt;CoNg&lt;/th&gt;
&lt;th&gt;CoNg Global&lt;/th&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;th&gt;GPT-Baseline&lt;/th&gt;
&lt;td colspan=&quot;2&quot;&gt;61.03M&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;th&gt;32k&lt;/th&gt;
&lt;td&gt;39.56M&lt;/td&gt;
&lt;td&gt;66.64M&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;th&gt;64k&lt;/th&gt;
&lt;td&gt;52.14M&lt;/td&gt;
&lt;td&gt;79.25M&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;th&gt;128k&lt;/th&gt;
&lt;td&gt;77.30M&lt;/td&gt;
&lt;td&gt;104.48M&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;th&gt;256k&lt;/th&gt;
&lt;td&gt;127.64M&lt;/td&gt;
&lt;td&gt;154.95M&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;th&gt;512k&lt;/th&gt;
&lt;td&gt;228.30M&lt;/td&gt;
&lt;td&gt;255.87M&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;전반적으로 30M정도씩 늘어났습니다. 좀 뚱뚱한 것 같긴하네요. 경량화 및 최적화 쪽에서 해야할 일이 쉽지 않아보이지만 일단 그 전에 이 방식이 실제로 얼마나 효과가 있는지 검증해보는게 먼저겠죠? 다음 포스팅에서는 CoNg 및 CoNg Global 모델의 실제 형태소 분석 성능에 대해서 분석해보도록 하겠습니다.&lt;/p&gt;</description>
      <category>프로그래밍/NLP</category>
      <category>CONG</category>
      <category>GPT</category>
      <category>kiwi</category>
      <category>언어 모델</category>
      <category>형태소 분석기</category>
      <author>&amp;int;2tdt=t&amp;sup2;+c</author>
      <guid isPermaLink="true">https://bab2min.tistory.com/677</guid>
      <comments>https://bab2min.tistory.com/677#entry677comment</comments>
      <pubDate>Sun, 11 May 2025 17:23:50 +0900</pubDate>
    </item>
    <item>
      <title>형태소 분석기 Kiwi CoNg (1/4): 신경망 모델 도입기</title>
      <link>https://bab2min.tistory.com/676</link>
      <description>&lt;p style=&quot;text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;오랜만에 Kiwi 이야기를 들고 블로그로 돌아왔습니다. 최근에 Kiwi v0.21.0에 신경망 모델을 도입하면서 분석 정확도를 크게 향상시켰는데요, 그 시작부터 진행 과정, 결과까지를 함께 나눠보고자 이렇게 시리즈 포스팅을 작성하게 되었습니다. 길고 지루한 이야기가 될 수도 있지만 최대한 재미나게 구성해볼테니 마지막 편까지 &quot;채널 고정&quot; 해주시면 감사드리겠습니다~&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;Kiwi CoNg 포스팅 시리즈&lt;/p&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;&lt;b&gt;형태소 분석기 Kiwi CoNg (1/4): 신경망 모델 도입기 &lt;/b&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://bab2min.tistory.com/677&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;형태소 분석기 Kiwi CoNg (2/4): CoNg 모델 소개&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://bab2min.tistory.com/678&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;형태소 분석기 Kiwi CoNg (3/4): 모델 성능 비교&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://bab2min.tistory.com/679&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;형태소 분석기 Kiwi CoNg (4/4): 모델 최적화&lt;/a&gt;&lt;/li&gt;
&lt;/ol&gt;
&lt;h3 style=&quot;text-align: justify;&quot; data-ke-size=&quot;size23&quot;&gt;왜 신경망 모델인가?&lt;/h3&gt;
&lt;p style=&quot;text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;잘 돌아가고 있는 Kiwi에 갑자기 왜 신경망 언어 모델(Neural Language Model)을 도입하게 되었을까요? 내부 언어 모델 엔진은 사실 상 Kiwi의 본체나 다름없기 때문에 이걸 갈아치운다는 결정은 즉흥적으로 내릴만한게 아닙니다. 세상이 맨날 AI를 외치고 거대 언어 모델을 하니깐 우리도 해야한다는 그런 안일한 마음가짐으로는 Kiwi는 살아남을 수 없어요. 정부 지원 과제를 타내기 위해서 윗분들이 시켜서 강제로 하게 된 것도 당연히 아니구요. 신경망 모델을 고민하게 된 가장 근본적인 원인은 기존 방식으로는 Kiwi 성능 개선이 한계에 봉착했기 때문이었습니다. &lt;span style=&quot;color: #333333; text-align: justify;&quot;&gt;크게 정리해보면&lt;span&gt; &lt;/span&gt;&lt;/span&gt;기존 Kiwi가 가지고 있던 한계들은 다음과 같습니다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;맥락이 모호한 경우 가능한 분석 후보 여럿 중 엉뚱한 것을 선택하는 경우가 자주 발생함&lt;/li&gt;
&lt;li&gt;신조어 등 사전에 등재되지 않은 단어들에 대한 분석 정확도 부족&lt;/li&gt;
&lt;li&gt;성능 개선을 위한 지도학습 데이터를 더 이상 확보하기 어려움&lt;/li&gt;
&lt;li&gt;방언, 은어, 비속어 등 비표준어에 대한 분석 능력 부족&lt;/li&gt;
&lt;li&gt;모델 구조 상 더 이상 속도를 높이기 어려움&lt;/li&gt;
&lt;/ul&gt;
&lt;p style=&quot;text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;마지막 하나는 속도에 관한 문제고, 나머지 넷은 정확도에 대한 문제입니다. 정확도 부분에 있어서 다양한 이슈가 있는데 이를 하나로 묶어보자면 전반적으로 모델이 융통성이 없어서 배운 것에 대해서만 잘하고 그 외의 입력에 대해서는 잘 못한다는 말이 되겠습니다. 그럴수 밖에 없는게 기존 Kiwi가 채용하고 있던 언어 모델은 KnLM(&lt;a href=&quot;https://bab2min.tistory.com/603&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;Kneser-ney smoothing을 적용한 통계 기반의 언어 모델&lt;/a&gt;)인데, 이는 간단히 말하면 학습데이터에서 패턴을 찾아서 기억해두고, 모델이 추론할때 입력이 들어오면 기억해둔 패턴에서 찾아서 해당 패턴 뒤에 어떤 형태소가 주로 나왔는지 확률을 계산하는 방식입니다. 즉, 학습 데이터에서 보지 못한 패턴이 들어오면 정확도가 크게 떨어질 수 밖에 없는 구조라는 것입니다.&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;좀 더 구체적인 사례를 볼까요? 다음 두 문장에는 동일하게 &quot;논의&quot;라는 형태가 들어 있지만 한국인이라면 누구나 이 두 &quot;논의&quot;가 서로 다르게 분석되어야한다는 것을 압니다.&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style3&quot;&gt;(가) 이 글의 &lt;b&gt;논의&lt;/b&gt; 전개는 다음과 같다.&lt;br /&gt;(나) 가을철 추수 전에 양쪽 &lt;b&gt;논의&lt;/b&gt; 미꾸라지를 잡아 보았다.&lt;/blockquote&gt;
&lt;p style=&quot;text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;(가)의 &quot;&lt;b&gt;논의&lt;/b&gt;&quot;는 무언가를 토론하고 의견을 나눈다는 의미의 명사인 반면, (나)의 &quot;&lt;b&gt;논의&lt;/b&gt;&quot;는 벼를 기르기 위한 장소인 &quot;&lt;b&gt;논&lt;/b&gt;&quot;에 조사 &quot;&lt;b&gt;의&lt;/b&gt;&quot;가 붙은 형태입니다. Kiwi에서 언어 모델을 이용해 이 문장을 형태소 분석하는 과정을 살펴보면 다음과 같습니다. 여기서 $P(x|y)$는 $y$ 문맥 다음에 $x$라는 형태소가 등장할 확률을 뜻합니다.&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;$$P_{a1} = P(\mathbf{논의}\: | \:\mathbf{이\: 글의}) ,\\ &lt;br /&gt;P_{a2} = P(\mathbf{논}\: | \:\mathbf{이\: 글의}) \cdot P(\mathbf{의}\: | \:\mathbf{이\: 글의\: 논}) ,\\ &lt;br /&gt;P_{b1} = P(\mathbf{논의}\: | \:\mathbf{가을철\: 추수\: 전에\: 양쪽}) ,\\ &lt;br /&gt;P_{b2} = P(\mathbf{논}\: | \:\mathbf{ 가을철\: 추수\: 전에\: 양쪽 }) ~~~~~~~ \\ ~~~~~~~ \cdot P(\mathbf{의}\: | \:\mathbf{ 가을철\: 추수\: 전에\: 양쪽\: 논}) $$&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #333333; text-align: justify;&quot;&gt;(가) 문장에서&lt;/span&gt; $P_{a1} &amp;gt; P_{a2}$라면 &quot;논의&quot;는 &quot;논의&quot;로 분석되고, 반대로 $P_{a1} &amp;lt; P_{a2}$라면 &quot;논 + 의&quot;로 분석됩니다. (나) 문장에서도 마찬가지로 $P_{b1} &amp;gt; P_{b2}$라면 &quot;논의&quot;는 &quot;논의&quot;로 분석되고, 반대로 $P_{b1} &amp;lt; P_{b2}$라면 &quot;논 + 의&quot;로 분석됩니다. 확률 모델 $P$가 위의 확률들을 실제 의미에 맞게 잘 계산해준다면 가)에서는 $P_{a1} &amp;gt; P_{a2}$, 나)에서는 $P_{b1} &amp;lt; P_{b2}$가 나와야겠죠? 그러나 Kiwi 0.20.4의 분석 결과는 다음과 같습니다.&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style3&quot;&gt;(가) 이 글의&amp;nbsp;&lt;b&gt;논의&amp;nbsp;&lt;/b&gt;전개는 다음과 같다.&lt;br /&gt;이/MM 글/NNG 의/JKG &lt;b&gt;논/NNG 의/JKG&lt;/b&gt; 전개/NNG 는/JX 다음/NNG 과/JKB 같/VA 다/EF ./SF&lt;br /&gt;&lt;br /&gt;(나) 가을철 추수 전에 양쪽 &lt;b&gt;논의&lt;/b&gt; 미꾸라지를 잡아 보았다.&lt;br /&gt;가을철/NNG 추수/NNG 전/NNG 에/JKB 양쪽/NNG &lt;b&gt;논의/NNG&lt;/b&gt; 미꾸라지/NNG 를/JKO 잡/VV-R 어/EC 보/VX 었/EP 다/EF ./SF&lt;/blockquote&gt;
&lt;p style=&quot;text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;저런, 정반대의 결과가 나왔네요. 이는 Kiwi의 언어 모델이 판단하기로는 $P_{a1} &amp;lt; P_{a2}$ ,&amp;nbsp; &amp;nbsp;$P_{b1} &amp;gt; P_{b2}$이라는 것입니다. 왜 그랬을까요? 바로 통계 기반 언어 모델의 한계 때문입니다. $ P(\mathbf{논의}\: | \:\mathbf{이\: 글의}) $를 계산하고 싶은데 모델이 학습할때 &quot;이 글의&quot; 다음에 &quot;논의&quot;가 등장하는 사례를 많이 보지 못했다면 이 확률을 제대로 계산할 수가 없습니다. 그래서 문맥을 줄여서 그 대신 $ P(\mathbf{논의}\: | \:\mathbf{글의}) $를 계산하게 되고, 이 마저도 자주 본적이 없다면 $ P(\mathbf{논의}\: | \:\mathbf{의}) $를 계산하게 됩니다. 마찬가지로 (나) 문장에서도 $ P(\mathbf{논}\: | \:\mathbf{ 가을철\: 추수\: 전에\: 양쪽 }) $를 계산할때 &quot;가을철 추수 전에 양쪽&quot;이라는 패턴을 보지 못했을 가능성이 크므로, 차선책으로 $ P(\mathbf{논}\: | \:\mathbf{ 추수\: 전에\: 양쪽 }) $을 시도하고, 그 다음에는 $P(\mathbf{논}\: | \:\mathbf{ 전에\: 양쪽 })$, 또 차차선책으로 $P(\mathbf{논}\: | \:\mathbf{ 에\: 양쪽 })$, 이마저도 본적이 없다면 마지막으로 $ P(\mathbf{논}\: | \:\mathbf{ 양쪽 }) $을 대신 계산하게 됩니다. Kiwi가 알고 있는 형태소의 개수를 대략 5만개라고 가정할 경우, 문맥의 길이가 3 정도만 되어도 $50000^3 = 125000000000000$가지의 모든 경우를 모델이 학습할때 미리 살펴보고 기억해둬야합니다. 하물며 더 긴 문맥을 모델이 미리 학습해두려면 어마어마한 메모리와 학습 데이터가 필요하게 됩니다. 따라서 통계 기반의 언어 모델은 자주 쓰이는 조사, 어미 형태소들을 제외하면 모델이 긴 문맥이 들어가는 확률을 정확하게 계산하는 것이 사실상 불가능합니다.&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;그런데 왜 Kiwi에서는 이렇게 한계가 명확한 KnLM을 내장 언어 모델로 채택한걸까요? 가능한 형태소 조합을 빠르게 탐색하기 위해서 모델이 가볍고 빨라야했기 때문입니다. KnLM은 통계기반 모델이기 때문에 추론 과정에서 별도의 복잡한 연산은 필요없고 그저 들어온 패턴을 검색해서 그에 대응하는 확률만 반환하면 되기에 구식의 CPU에서도 아주 빠르게 동작할 수 있습니다. 그리고 위와 같이 모호성이 없는 경우에는 긴 문맥을 보지 않고 바로 근처의 형태소 1~2개만 봐도 충분히 괜찮은 분석 결과를 내기도 하기에 주어진 제약 조건에서는 나쁘지 않은 선택이었다 볼 수 있습니다. (이민철.&amp;nbsp;(2024).&amp;nbsp;Kiwi:&amp;nbsp;통계적&amp;nbsp;언어&amp;nbsp;모델과&amp;nbsp;Skip-Bigram을&amp;nbsp;이용한&amp;nbsp;한국어&amp;nbsp;형태소&amp;nbsp;분석기&amp;nbsp;구현.&amp;nbsp;,&amp;nbsp;1(1),&amp;nbsp;109-136,&amp;nbsp;&lt;a href=&quot;https://doi.org/10.23287/KJDH.2024.1.1.6&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://doi.org/10.23287/KJDH.2024.1.1.6&lt;/a&gt; 참조)&lt;/p&gt;
&lt;h3 style=&quot;text-align: justify;&quot; data-ke-size=&quot;size23&quot;&gt;신경망 모델의 장점&lt;/h3&gt;
&lt;p style=&quot;text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;KnLM와 같은 통계 기반 모델이 가지고 있던 한계를 한 마디로 표현하자면 &quot;융통성 부족&quot;입니다. 그리고 신경망 모델이 가지고 있는 장점은 한 마디로 &quot;융통성&quot;입니다. 신경망 모델은 학습 과정을 통해 파라미터를 스스로 적절한 값으로 조절하고, 이렇게 학습한 파라미터는 처음 보는 패턴에 대해서도 어느 정도 통합니다. 또 신경망 구조는 현재 AI연구의 대세이기 때문에 관련된 다양한 최신 연구들을 그대로 적용해볼 수 있어서 확장 가능성도 있지요.&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;학습 가능한 양질의 데이터를 추가로 확보하기 어렵다는 문제도 (노이즈가 섞여있지만) 대량의 웹 텍스트를 확보함으로써 해결할 수 있습니다. 통계 기반 모델에서는 노이즈가 섞인 데이터가 학습과정에 들어가는 순간 모델 자체가 오염되기 때문에 그만큼 깨끗한 데이터를 더 많이 넣어서 일명 &quot;물타기&quot;를 해줘야합니다. 반면 신경망 모델은 학습 과정에서 노이즈 데이터가 대량으로 섞였다고 하더라도 마지막에만 양질의 데이터를 조금만 학습하면 금세 양질의 데이터에 적응합니다. 그래서 데이터 문제에서도 비교적 자유롭죠.&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;정확도를 올리는 것도 비교적 용이합니다. 신경망 모델은 그저 모델의 크기를 키우고 더 많은 데이터를 투입하는 것만으로도 성능이 향상된다(&lt;a href=&quot;https://arxiv.org/abs/2001.08361&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;Kaplan, Jared, et al. &quot;Scaling laws for neural language models.&quot;&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;i&gt;arXiv preprint arXiv:2001.08361&lt;/i&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;(2020).&lt;/a&gt; )고 잘 알려져 있기 때문에 정확도를 올리기가 쉽습니다. 반면 통계 기반 모델은 모델 크기를 키우는 것(기하급수적)에 비해 성능 향상폭이 제한적(산술급수적)이라서 모델 크기를 키우는 방식을 취하기 어렵습니다.&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;이렇게 좋은 데 진작 신경망 모델을 쓰지~ 왜 굳이 다른 걸 쓰는걸까요? 당연히도 신경망 모델의 연산 비용 때문입니다. 신경망 모델은 그 특성 상 통계 기반 모델과는 다르게 큰 행렬곱셈을 자주 수행해야해서 연산 비용이 비싼 편입니다. 그래서 쓸만한 정확도가 나오는 큰 모델은 연산량이 너무 많아서 CPU로 빠르게 돌리기 버겁고, 그렇다고 모델 크기를 줄이면 속도는 충분히 빨라지지만 정확도도 급락하는 한계가 있습니다. 그나마 다행인것은 요즘 로컬 AI가 보편화되면서 개인용 PC에서도 AI 모델들을 구동할 수 있게 CPU에도 AI용 다양한 전용 명령어(AVX, AMX)들이 추가되고 있다는 것입니다. 그래서 이 명령어들을 잘 활용할 수 있도록 신경망 모델을 설계한다면 최신 CPU에서 빠르면서도 높은 정확도를 달성하는 모델을 만들 수 있습니다. (반면 통계 기반 모델 구조를 위한 전용 명령어 같은 건 존재하지 않습니다. 앞으로 추가될걸로 보이지도 않구요. 그래서 장기적으로 보면 통계 기반 모델의 &lt;span style=&quot;color: #333333; text-align: justify;&quot;&gt;빠르다는&lt;span&gt; &lt;/span&gt;&lt;/span&gt;장점은 퇴색될 가능성이 높습니다.)&lt;/p&gt;
&lt;h3 style=&quot;text-align: justify;&quot; data-ke-size=&quot;size23&quot;&gt;어떤 신경망 모델을 써야할까?&lt;/h3&gt;
&lt;p style=&quot;text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;좋습니다~ 신경망 모델의 장점을 훑어보니 Kiwi의 코어 엔진을 갈아엎더라도 도입하는게 장기적으로 이득이 될 것 같습니다. 그런데 어떤 모델을 써야할까요? Kiwi는 범용적으로 사용할 수 있는 가벼운 분석기를 지향하기 때문에 가능하면 너무 무겁고 느린 모델을 도입하고 싶지는 않습니다. 그래서 품질이 좋으면서도 다음 두 가지 기준을 만족하는 모델을 찾아보기로 결정했습니다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;모델 크기는 100MB이내. (Python으로 용이하게 배포하려면 pypi에 패키지를 올려야 하는데 이 때 일반적인 크기 제한이 100MB입니다. 그래서 이 수치를 잡았습니다.)&lt;/li&gt;
&lt;li&gt;속도는 현재 KnLM 기반의 분석기와 유사할 것. 혹시나 느려지더라도 1.5배 이상 느려지면 안됨.&lt;/li&gt;
&lt;/ul&gt;
&lt;p style=&quot;text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;일단 베이스라인으로 잡아볼 수 있는 것은 요즘 신경망 기반 언어 모델의 사실상 표준이라고 할 수 있는 GPT입니다. GPT3이후로는 모델 크기도 너무 커졌고 그 구조도 알려져 있지 않으니 현실적으로는 GPT-2정도가 시도해볼 수 있는 선택지가 되겠습니다. 실제로 GPT-2도 충분히 좋은 생성능력을 가졌으니깐요. 성능과 크기면에서 GPT가 상한이라면 하한으로는 Word2Vec 모델을 꼽아볼 수 있겠네요. 신경망 언어 모델 중 가장 단순한 구조를 가지고 있어서 속도도 아주 빠르겠지만 성능도 많이 낮을 것입니다. 그리고 GPT-2와 Word2Vec 사이에 존재하는 다양한 신경망 구조들도 있을텐데요 이 중에서 품질도 괜찮으면서 위의 두 조건을 만족하는 것을 찾을 수 있느냐가 관건이 되겠습니다!&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;850&quot; data-origin-height=&quot;701&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/YKYHd/btsNKe4pbFP/p0MW92ixvqKl4qbAMfO3fk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/YKYHd/btsNKe4pbFP/p0MW92ixvqKl4qbAMfO3fk/img.png&quot; data-alt=&quot;GPT-2 모델의 구조. Self-attention과 Feed forward network라는 레이어로 이뤄지는 Transformer block을 여러 개 쌓은 형태이다. 구조는 복잡하지만 그 덕에 이전의 문맥을 고려해서 다음 단어를 정확하게 예측해낸다.&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/YKYHd/btsNKe4pbFP/p0MW92ixvqKl4qbAMfO3fk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FYKYHd%2FbtsNKe4pbFP%2Fp0MW92ixvqKl4qbAMfO3fk%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;850&quot; height=&quot;701&quot; data-origin-width=&quot;850&quot; data-origin-height=&quot;701&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;GPT-2 모델의 구조. Self-attention과 Feed forward network라는 레이어로 이뤄지는 Transformer block을 여러 개 쌓은 형태이다. 구조는 복잡하지만 그 덕에 이전의 문맥을 고려해서 다음 단어를 정확하게 예측해낸다.&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;blob&quot; data-origin-width=&quot;703&quot; data-origin-height=&quot;770&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/24VV0/btsNK4NNaOk/lQhQMrK31EAzpWlt1MjRK1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/24VV0/btsNK4NNaOk/lQhQMrK31EAzpWlt1MjRK1/img.png&quot; data-alt=&quot;Word2Vec CBOW 모델의 구조. Input과 Output 2개의 임베딩 레이어로만 구성된다. 주변 단어를 이용해 중심 단어를 예측하는 구조로 어순을 고려하지 못하지만 굉장히 빠르게 연산할 수 있다.&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/24VV0/btsNK4NNaOk/lQhQMrK31EAzpWlt1MjRK1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2F24VV0%2FbtsNK4NNaOk%2FlQhQMrK31EAzpWlt1MjRK1%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;703&quot; height=&quot;770&quot; data-filename=&quot;blob&quot; data-origin-width=&quot;703&quot; data-origin-height=&quot;770&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;Word2Vec CBOW 모델의 구조. Input과 Output 2개의 임베딩 레이어로만 구성된다. 주변 단어를 이용해 중심 단어를 예측하는 구조로 어순을 고려하지 못하지만 굉장히 빠르게 연산할 수 있다.&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;다양한 탐색 끝에 제가 최종적으로 만들어낸 구조는 GPT의 성격을 지녔지만 Word2Vec CBOW와 닮은 CoNg(Contextual N-gram embedding)이라는 모델입니다. 이 모델은 실제로 KnLM보다 모호성 해소 성능은 크게 높으면서도 속도는 더 빠르고, 모델 크기도 50~70MB로 줄이는데에 성공하여 위의 모든 조건을 만족시켰고 결국 Kiwi에 포함되게 되었습니다. 신경망 모델의 도입이 실제로 얼마나 효과가 있었을까요?&lt;/p&gt;
&lt;h3 style=&quot;text-align: justify;&quot; data-ke-size=&quot;size23&quot;&gt;CoNg 모델의 성능은?&lt;/h3&gt;
&lt;p style=&quot;text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;모델 성능에 대해서는 이후 포스팅들에서 좀 더 구체적으로 다룰 예정이니 이 자리에서는 대략적인 결과만 공유하도록 하겠습니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;edited_DisambAcc.PNG&quot; data-origin-width=&quot;1678&quot; data-origin-height=&quot;1132&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bt9zbW/btsNLo6TCNg/v6Bko7hyAoOsDcX5KQflZk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bt9zbW/btsNLo6TCNg/v6Bko7hyAoOsDcX5KQflZk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bt9zbW/btsNLo6TCNg/v6Bko7hyAoOsDcX5KQflZk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fbt9zbW%2FbtsNLo6TCNg%2Fv6Bko7hyAoOsDcX5KQflZk%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;1678&quot; height=&quot;1132&quot; data-filename=&quot;edited_DisambAcc.PNG&quot; data-origin-width=&quot;1678&quot; data-origin-height=&quot;1132&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;더 어려워진 모호성 해소 v2025.05 데이터셋에 대해서 기존 KnLM, SBG 모델들보다 10%p 이상 정확도를 개선하는 데에 성공했고,&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;SentSplit_F1.PNG&quot; data-origin-width=&quot;2110&quot; data-origin-height=&quot;848&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bqEcYW/btsNMaz79Lu/cYXGhuZmUcEZNtOZMSkWg0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bqEcYW/btsNMaz79Lu/cYXGhuZmUcEZNtOZMSkWg0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bqEcYW/btsNMaz79Lu/cYXGhuZmUcEZNtOZMSkWg0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbqEcYW%2FbtsNMaz79Lu%2FcYXGhuZmUcEZNtOZMSkWg0%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;2110&quot; height=&quot;848&quot; data-filename=&quot;SentSplit_F1.PNG&quot; data-origin-width=&quot;2110&quot; data-origin-height=&quot;848&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;문장 분리에 있어서도 기존 모델보다 3%p이상 성능을 개선하여 타 분리기 대비 가장 높은 정확도를 달성했습니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1168&quot; data-origin-height=&quot;706&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/70G8O/btsNKZzzWyW/ydQrIDnkeKJRIfDk6gYEWK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/70G8O/btsNKZzzWyW/ydQrIDnkeKJRIfDk6gYEWK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/70G8O/btsNKZzzWyW/ydQrIDnkeKJRIfDk6gYEWK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2F70G8O%2FbtsNKZzzWyW%2FydQrIDnkeKJRIfDk6gYEWK%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;1168&quot; height=&quot;706&quot; data-origin-width=&quot;1168&quot; data-origin-height=&quot;706&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;신경망 모델을 도입했음에도 분석 속도는 오히려 빨라져서 인접한 문맥만 다루는 cong 모델은 유사하게 인접한 문맥만 다루던 이전 모델인 knlm보다 20%이상 빨라졌으며, 먼 거리의 관계를 분석하는 cong-global 모델은 마찬가지로 먼 거리를 다루던 이전 모델인 sbg보다 30~40%가량 빨라졌습니다.&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;종합해보면 속도는 빨라졌고 분석 정확도는 개선되었습니다. 도대체 CoNg모델이 어떤 구조이길래 이렇게 속도와 정확도를 동시에 올릴 수 있었을까요? CoNg 모델의 구조와 학습 방법에 대해서는 다음 포스팅에서 계속 설명하도록 하겠습니다.&lt;/p&gt;</description>
      <category>프로그래밍/NLP</category>
      <category>GPT</category>
      <category>kiwi</category>
      <category>언어 모델</category>
      <category>형태소 분석기</category>
      <author>&amp;int;2tdt=t&amp;sup2;+c</author>
      <guid isPermaLink="true">https://bab2min.tistory.com/676</guid>
      <comments>https://bab2min.tistory.com/676#entry676comment</comments>
      <pubDate>Sun, 4 May 2025 19:30:29 +0900</pubDate>
    </item>
    <item>
      <title>한국어 말뭉치를 통한 사이시옷 사용 실태 조사</title>
      <link>https://bab2min.tistory.com/675</link>
      <description>&lt;h3 style=&quot;text-align: justify;&quot; data-ke-size=&quot;size23&quot;&gt;헷갈리는 사이시옷&lt;/h3&gt;
&lt;p style=&quot;text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;사이시옷은 참으로 묘한 녀석입니다. 이름은 분명 사이&quot;시옷&quot;인데 ㅅ소리가 전혀 나지 않거든요. 사이시옷의 역할은 두 개의 명사가 합쳐져서 복합 명사가 되는 과정에서 뒤의 명사가 된소리화(ㄲ/ㄸ/ㅃ/ㅆ/ㅉ)되거나 ㄴ소리가 첨가되는 현상을 반영하기 위해서 명사 사이에 ㅅ을 적기로 약속한 것이기 때문입니다. 그래서 적기는 ㅅ으로 적더라도 실제로는 ㅅ 소리가 절대 나지 않습니다. ('&lt;span style=&quot;color: #333333; text-align: justify;&quot;&gt;숫자'는 [숫자]로 소리나는 것이 아니라 [수짜]로 소리나고,&lt;/span&gt; '깻잎'은 [깨십]으로 소리나는 것이 아니라 [깬닙]으로 소리납니다.)&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;사실 발음보다 더 골이 아픈 문제는 사이시옷을 언제 써야하는지, 언제 쓰지 말아야하는지를 구분하는게 참 어렵다는 겁니다. 잘 모르는 복합명사를 받아적을 일이 생겨서, 그 명사에 사이시옷이 들어가는지 아닌지를 추정해야하는 상황이라고 생각해봅시다. 원칙상 다음과 같이 맞춤법 규정을 따라 차근차근 접근하면 문제를 풀수는 있습니다.&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;일단 맞춤법 규정에는 한자어와 한자어가 결합하는 경우, 외래어와 순우리말이 결합하는 경우에는 사이시옷을 적지 않도록 했습니다. 즉, 한자어 + 순우리말, 순우리말 + 한자어, 순우리말 + 순우리말이 결합하는 경우 중 뒤의 명사가 된소리화되거나 ㄴ소리가 붙는 경우에 사이시옷을 쓰면 된다는 겁니다. (즉 뒷 명사가 이미 된소리이거나 거센소리인 경우에는 된소리화가 된것도 아니고 ㄴ 소리가 붙는 것도 아니므로 사이시옷을 안 적습니다. 또 당연히 이미 앞 명사에 받침이 있는 경우에도 ㅅ을 적을 수 없으므로 적지 않구요.)&amp;nbsp; 물론 예외가 있습니다. 한자어끼리 결합한 것 중 다음 6개는 예외적으로 사이시옷을 적어요.&amp;nbsp;&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style1&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;곳간,&amp;nbsp;셋방,&amp;nbsp;숫자,&amp;nbsp;찻간,&amp;nbsp;툇간,&amp;nbsp;횟수&lt;/span&gt;&lt;/blockquote&gt;
&lt;p style=&quot;text-align: justify;&quot; data-ke-size=&quot;size16&quot;&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;/li&gt;
&lt;li&gt;사이비&lt;/li&gt;
&lt;li&gt;댐&lt;/li&gt;
&lt;li&gt;지금(말하고 있는 바로 이때)&lt;/li&gt;
&lt;li&gt;저녁&lt;/li&gt;
&lt;li&gt;물론&lt;/li&gt;
&lt;li&gt;고무&lt;/li&gt;
&lt;li&gt;어차피&lt;/li&gt;
&lt;li&gt;비닐&lt;/li&gt;
&lt;li&gt;서랍&lt;/li&gt;
&lt;li&gt;녹초(기가&amp;nbsp;빠지거나&amp;nbsp;맥이&amp;nbsp;풀어져&amp;nbsp;힘을&amp;nbsp;쓰지&amp;nbsp;못할&amp;nbsp;상태)&lt;/li&gt;
&lt;li&gt;빵&lt;/li&gt;
&lt;li&gt;포도&lt;/li&gt;
&lt;/ul&gt;
&lt;div data-ke-type=&quot;moreLess&quot; data-text-more=&quot;정답 보기&quot; data-text-less=&quot;닫기&quot;&gt;&lt;a class=&quot;btn-toggle-moreless&quot;&gt;더보기&lt;/a&gt;
&lt;div class=&quot;moreless-content&quot;&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;생각: 순우리말&lt;/li&gt;
&lt;li&gt;사이비(似而非): 한자어&lt;/li&gt;
&lt;li&gt;댐(dam): 외래어&lt;/li&gt;
&lt;li&gt;지금(只今): 한자어&lt;/li&gt;
&lt;li&gt;저녁: 순우리말&lt;/li&gt;
&lt;li&gt;물론(勿論): 한자어&lt;/li&gt;
&lt;li&gt;고무(gomme): 외래어&lt;/li&gt;
&lt;li&gt;어차피(於此彼): 한자어&lt;/li&gt;
&lt;li&gt;비닐(vinyl): 외래어&lt;/li&gt;
&lt;li&gt;서랍: 순우리말&lt;/li&gt;
&lt;li&gt;녹초: 순우리말&lt;/li&gt;
&lt;li&gt;빵(p&amp;atilde;o): 외래어&lt;/li&gt;
&lt;li&gt;포도(葡萄): 한자어&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p style=&quot;text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;한국어 원어민들은 다들 전부 맞히셨겠...으면 아주 좋겠으나 불행히도 전부 맞히신 분은 거의 없을 겁니다. 국어학자가 아닌 이상 각 단어의 어원을 훤히 꿰고 있을리가 없고, 사람들은 그저 단어의 발음을 보고 한자어 같은 발음이면 한자어로, 뭔가 외국어 같은 발음이면 외래어로, 그것도 아니면 순우리말로 추정하기 때문에 정확도가 매우 낮을 수 밖에 없습니다. 그래서 어떤 복합명사를 받아적을 때 사이시옷을 써야하는지 아닌지를 알려면 결국 각 명사의 어원이 뭔지를 사전을 열어봐서 확인하는 수 밖에 없겠구요. 일이 점점 어려워집니다.&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot; data-ke-size=&quot;size16&quot;&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;/li&gt;
&lt;li&gt;배추잎[배추입] / 배춧잎[배춘닙]&lt;/li&gt;
&lt;li&gt;머리말[머리말] / 머릿말[머린말]&lt;/li&gt;
&lt;li&gt;바다장어[바다장어] / 바닷장어[바다짱어]&lt;/li&gt;
&lt;li&gt;처가집[처가집] / 처갓집[처가찝]&lt;/li&gt;
&lt;li&gt;시계줄[시계줄] / 시곗줄[시계쭐]&lt;/li&gt;
&lt;li&gt;머리고기[머리고기] / 머릿고기[머리꼬기]&lt;/li&gt;
&lt;li&gt;꼬리살[꼬리살] / 꼬릿살[꼬리쌀]&lt;/li&gt;
&lt;li&gt;(사이시옷 문제는 아니지만 표준 발음 문제) 김밥[김밥] / 김밥[김빱]&lt;/li&gt;
&lt;/ul&gt;
&lt;div data-text-less=&quot;닫기&quot; data-text-more=&quot;정답 보기&quot; data-ke-type=&quot;moreLess&quot;&gt;&lt;a class=&quot;btn-toggle-moreless&quot;&gt;더보기&lt;/a&gt;
&lt;div class=&quot;moreless-content&quot;&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;깻잎[깬닙]&lt;/li&gt;
&lt;li&gt;배춧잎[배춘닙]&lt;/li&gt;
&lt;li&gt;머리말[머리말]&lt;/li&gt;
&lt;li&gt;바다장어[바다장어]&lt;/li&gt;
&lt;li&gt;처갓집[처가찝]&lt;/li&gt;
&lt;li&gt;시곗줄[시계쭐]&lt;/li&gt;
&lt;li&gt;머릿고기[머리꼬기]&lt;/li&gt;
&lt;li&gt;꼬리살[꼬리살] / 꼬릿살[꼬리쌀] ??? (꼬리살/꼬릿살은 사전에 등재되어있지 않은 단어라서 표준 발음을 알 수가 없어요.)&lt;/li&gt;
&lt;li&gt;김밥[김밥] / 김밥[김빱] (둘다 표준 발음입니다.)&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p style=&quot;text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;솔직히 다 맞힐 자신이 없습니다. 잘 모르겠으면 사전을 찾아보면 어찌저찌 풀 수 있겠지만, 더 큰 문제는 사람들이 이미 사용하고 있지만 아직 사전에 등재되지 않은 단어도 종종 있고, 단어에 따라 된소리화된것과 그렇지 않은게 복수 표준인 경우도 있다는 겁니다. 이쯤되면 사이시옷을 적을지 말지를 그냥 찍는게 더 편할 것 같습니다.&lt;/p&gt;
&lt;h3 style=&quot;text-align: justify;&quot; data-ke-size=&quot;size23&quot;&gt;Kiwi 0.20.0의 신기능&lt;/h3&gt;
&lt;p style=&quot;text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;그래서 맞춤법을 잘 지켜쓰려는 사람들도 자주 쓰이지 않는 복합명사나 새로 만들어진 복합명사에 대해서는 &quot;사이시옷 찍기&quot;를 행하는 듯합니다. 실제로 인터넷 검색을 해보면 동일한 단어에 대해 사이시옷을 쓴 경우와 그렇지 않은 경우가 모두 비등비등하게 등장하는 사례도 제법 있어요. 사실 글을 읽는 게 사람이라면 사이시옷을 좀 잘못 써도 탈이 나는게 아니라서 이게 문제가 되지 않습니다. 그러나 글을 읽는 게 컴퓨터라면 이야기가 달라집니다. &quot;머리말&quot;은 컴퓨터의 사전에 등록되어 있겠지만 &quot;머릿말&quot;은 모르는 단어일거고, &quot;처갓집&quot;은 처리할 수 있겠지만 &quot;처가집&quot;은 처리불가능할지도 모르거든요. 그래서 웹상의 텍스트를 자동분석할때 사이시옷 오류는 치명적으로 다가옵니다.&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;이에 대응하는 가장 간단한 방법은 사이시옷이 들어간 형태와 들어가지 않은 형태 모두를 분석기 사전에 등재시켜 놓는 것이지만, 문제는 복합명사는 열린 집합이기에 얼마든지 새로운 단어가 추가될 수가 있고 이 때문에 모든 복합명사를 사전에 미리 등재하는 것은 불가능하다는 것입니다. 그래서 &lt;a href=&quot;https://github.com/bab2min/Kiwi/releases/tag/v0.20.0&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;한국어 형태소 분석기 Kiwi 0.20.0 버전&lt;/a&gt;에서는 이를 해결하기 위해 다음과 같이 사이시옷을 위한 품사태그 Z_SIOT을 추가하고 이 태그가 필수적이 아닌 선택적으로 등장할 수 있게 하여서 문제를 해결했습니다.&lt;/p&gt;
&lt;p&gt;&lt;textarea class=&quot;python&quot; name=&quot;code&quot;&gt;&amp;gt;&amp;gt;&amp;gt; from kiwipiepy import Kiwi
&amp;gt;&amp;gt;&amp;gt; kiwi = Kiwi()
# 기본 모드에서는 별도로 사이시옷 처리를 하지 않음
# (이 상태는 saisiot=None으로 준 경우와 동일)
# 때문에 &quot;시곗바늘아&quot;가 잘못 분석되고 있음
&amp;gt;&amp;gt;&amp;gt; kiwi.tokenize('시곗바늘아 달려봐')
[Token(form='시곗바늘아', tag='NNG', start=0, len=5),
 Token(form='달리', tag='VV', start=6, len=2),
 Token(form='어', tag='EC', start=7, len=1),
 Token(form='보', tag='VX', start=8, len=1),
 Token(form='어', tag='EF', start=8, len=1)]

# saisiot=True로 설정 시 사이시옷을 분리해냄
&amp;gt;&amp;gt;&amp;gt; kiwi.tokenize('시곗바늘아 달려봐', saisiot=True)
[Token(form='시계', tag='NNG', start=0, len=2),
 Token(form='ᆺ', tag='Z_SIOT', start=1, len=1),
 Token(form='바늘', tag='NNG', start=2, len=2),
 Token(form='아', tag='JKV', start=4, len=1),
 Token(form='달리', tag='VV', start=6, len=2),
 Token(form='어', tag='EC', start=7, len=1),
 Token(form='보', tag='VX', start=8, len=1),
 Token(form='어', tag='EF', start=8, len=1)]

# saisiot=False로 설정 시 사이시옷을 먼저 분리해낸 뒤
# 사이시옷 앞뒤의 명사를 찾아 하나의 복합명사로 결합함
&amp;gt;&amp;gt;&amp;gt; kiwi.tokenize('시곗바늘아 달려봐', saisiot=False)
[Token(form='시곗바늘', tag='NNG', start=0, len=4),
 Token(form='아', tag='JKV', start=4, len=1),
 Token(form='달리', tag='VV', start=6, len=2),
 Token(form='어', tag='EC', start=7, len=1),
 Token(form='보', tag='VX', start=8, len=1),
 Token(form='어', tag='EF', start=8, len=1)]
&lt;/textarea&gt;&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;사실 사이시옷은 그 자체로 형태소는 아니기 때문에 이를 별도로 처리하는 한국어 형태소 분석기는 없었습니다. 이번에 Kiwi에서 새로 시도한 것이지요. 과연 새로 개발한 기능이 제대로 작동하는지 확인하기 위해 간단한 실험을 해보기로 했습니다. 다양한 한국어 말뭉치를 대상으로 사이시옷이 포함된 것으로 추정되는 명사들의 통계를 내보았습니다.&lt;/p&gt;
&lt;h3 style=&quot;text-align: justify;&quot; data-ke-size=&quot;size23&quot;&gt;사이시옷&amp;nbsp;사용&amp;nbsp;실태&amp;nbsp;조사&lt;/h3&gt;
&lt;p style=&quot;text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;먼저 국립국어원 모두의 말뭉치 중 웹 텍스트를 대상으로 형태소 분석을 수행하여 사이시옷이 포함된 복합명사와 그에 대응하는 사이시옷이 없는 명사의 빈도를 추출해보았습니다. 맞춤법 상 올바른 표기법은 굵게 나타냈습니다.&lt;/p&gt;
&lt;table class=&quot;std-table&quot; data-ke-align=&quot;alignLeft&quot;&gt;&lt;caption&gt;모두의 말뭉치 웹 텍스트에서 추출된 사이시옷이 포함된 명사들 통계&lt;/caption&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;th colspan=&quot;2&quot;&gt;사이시옷 있는 형태&lt;/th&gt;
&lt;th colspan=&quot;2&quot;&gt;사이시옷 없는 형태&lt;/th&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;b&gt;고깃집&lt;/b&gt;&lt;/td&gt;
&lt;td&gt;1794&lt;/td&gt;
&lt;td&gt;고기집&lt;/td&gt;
&lt;td&gt;1382&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;b&gt;깻잎&lt;/b&gt;&lt;/td&gt;
&lt;td&gt;2746&lt;/td&gt;
&lt;td&gt;&amp;nbsp;&lt;/td&gt;
&lt;td&gt;&amp;nbsp;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;b&gt;숫자&lt;/b&gt;&lt;/td&gt;
&lt;td&gt;2074&lt;/td&gt;
&lt;td&gt;수자*&lt;/td&gt;
&lt;td&gt;10&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;b&gt;오랫동안&lt;/b&gt;&lt;/td&gt;
&lt;td&gt;1764&lt;/td&gt;
&lt;td&gt;&amp;nbsp;&lt;/td&gt;
&lt;td&gt;&amp;nbsp;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;아랫쪽&lt;/td&gt;
&lt;td&gt;7&lt;/td&gt;
&lt;td&gt;&lt;b&gt;아래쪽&lt;/b&gt;&lt;/td&gt;
&lt;td&gt;1540&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;b&gt;공깃밥&lt;/b&gt;&lt;/td&gt;
&lt;td&gt;376&lt;/td&gt;
&lt;td&gt;공기밥&lt;/td&gt;
&lt;td&gt;1152&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;촛점&lt;/td&gt;
&lt;td&gt;122&lt;/td&gt;
&lt;td&gt;&lt;b&gt;초점&lt;/b&gt;&lt;/td&gt;
&lt;td&gt;1143&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;b&gt;머릿결&lt;/b&gt;&lt;/td&gt;
&lt;td&gt;997&lt;/td&gt;
&lt;td&gt;머리결&lt;/td&gt;
&lt;td&gt;172&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;b&gt;머릿속&lt;/b&gt;&lt;/td&gt;
&lt;td&gt;933&lt;/td&gt;
&lt;td&gt;머리속&lt;/td&gt;
&lt;td&gt;198&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;b&gt;횟수&lt;/b&gt;&lt;/td&gt;
&lt;td&gt;644&lt;/td&gt;
&lt;td&gt;회수*&lt;/td&gt;
&lt;td&gt;350&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;b&gt;바닷가&lt;/b&gt;&lt;/td&gt;
&lt;td&gt;865&lt;/td&gt;
&lt;td&gt;&amp;nbsp;&lt;/td&gt;
&lt;td&gt;&amp;nbsp;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;홋수&lt;/td&gt;
&lt;td&gt;8&lt;/td&gt;
&lt;td&gt;&lt;b&gt;호수&lt;/b&gt;*&lt;/td&gt;
&lt;td&gt;806&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;b&gt;고춧가루&lt;/b&gt;&lt;/td&gt;
&lt;td&gt;537&lt;/td&gt;
&lt;td&gt;고추가루&lt;/td&gt;
&lt;td&gt;271&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;b&gt;갈빗살&lt;/b&gt;&lt;/td&gt;
&lt;td&gt;18&lt;/td&gt;
&lt;td&gt;갈비살&lt;/td&gt;
&lt;td&gt;773&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;b&gt;뱃살&lt;/b&gt;&lt;/td&gt;
&lt;td&gt;771&lt;/td&gt;
&lt;td&gt;&amp;nbsp;&lt;/td&gt;
&lt;td&gt;&amp;nbsp;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;피붓결&lt;/td&gt;
&lt;td&gt;5&lt;/td&gt;
&lt;td&gt;피부결&lt;/td&gt;
&lt;td&gt;732&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;갯수&lt;/td&gt;
&lt;td&gt;415&lt;/td&gt;
&lt;td&gt;&lt;b&gt;개수&lt;/b&gt;&lt;/td&gt;
&lt;td&gt;286&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;b&gt;순댓국&lt;/b&gt;&lt;/td&gt;
&lt;td&gt;59&lt;/td&gt;
&lt;td&gt;순대국&lt;/td&gt;
&lt;td&gt;611&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;b&gt;뱃속&lt;/b&gt;&lt;/td&gt;
&lt;td&gt;457&lt;/td&gt;
&lt;td&gt;배속*&lt;/td&gt;
&lt;td&gt;86&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;땟깔&lt;/td&gt;
&lt;td&gt;8&lt;/td&gt;
&lt;td&gt;&lt;b&gt;때깔&lt;/b&gt;&lt;/td&gt;
&lt;td&gt;447&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;b&gt;부챗살&lt;/b&gt;&lt;/td&gt;
&lt;td&gt;29&lt;/td&gt;
&lt;td&gt;부채살&lt;/td&gt;
&lt;td&gt;357&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;b&gt;가짓수&lt;/b&gt;&lt;/td&gt;
&lt;td&gt;317&lt;/td&gt;
&lt;td&gt;가지수&lt;/td&gt;
&lt;td&gt;39&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;둘쨋날&lt;/td&gt;
&lt;td&gt;58&lt;/td&gt;
&lt;td&gt;둘째날&lt;/td&gt;
&lt;td&gt;253&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;b&gt;갯벌&lt;/b&gt;&lt;/td&gt;
&lt;td&gt;302&lt;/td&gt;
&lt;td&gt;&amp;nbsp;&lt;/td&gt;
&lt;td&gt;&amp;nbsp;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;b&gt;나뭇잎&lt;/b&gt;&lt;/td&gt;
&lt;td&gt;252&lt;/td&gt;
&lt;td&gt;나무잎&lt;/td&gt;
&lt;td&gt;5&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;b&gt;바닷물&lt;/b&gt;&lt;/td&gt;
&lt;td&gt;205&lt;/td&gt;
&lt;td&gt;바다물&lt;/td&gt;
&lt;td&gt;9&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;b&gt;텃밭&lt;/b&gt;&lt;/td&gt;
&lt;td&gt;205&lt;/td&gt;
&lt;td&gt;&amp;nbsp;&lt;/td&gt;
&lt;td&gt;&amp;nbsp;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;b&gt;수돗물&lt;/b&gt;&lt;/td&gt;
&lt;td&gt;195&lt;/td&gt;
&lt;td&gt;수도물&lt;/td&gt;
&lt;td&gt;6&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;b&gt;바닷속&lt;/b&gt;&lt;/td&gt;
&lt;td&gt;140&lt;/td&gt;
&lt;td&gt;바다속&lt;/td&gt;
&lt;td&gt;56&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;b&gt;어젯밤&lt;/b&gt;&lt;/td&gt;
&lt;td&gt;174&lt;/td&gt;
&lt;td&gt;어제밤&lt;/td&gt;
&lt;td&gt;19&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;b&gt;만둣국&lt;/b&gt;&lt;/td&gt;
&lt;td&gt;66&lt;/td&gt;
&lt;td&gt;만두국&lt;/td&gt;
&lt;td&gt;114&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;b&gt;미숫가루&lt;/b&gt;&lt;/td&gt;
&lt;td&gt;167&lt;/td&gt;
&lt;td&gt;&amp;nbsp;&lt;/td&gt;
&lt;td&gt;&amp;nbsp;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;b&gt;찻잔&lt;/b&gt;&lt;/td&gt;
&lt;td&gt;164&lt;/td&gt;
&lt;td&gt;&amp;nbsp;&lt;/td&gt;
&lt;td&gt;&amp;nbsp;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;콧끝&lt;/td&gt;
&lt;td&gt;5&lt;/td&gt;
&lt;td&gt;&lt;b&gt;코끝&lt;/b&gt;&lt;/td&gt;
&lt;td&gt;158&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;b&gt;다릿살&lt;/b&gt;&lt;/td&gt;
&lt;td&gt;60&lt;/td&gt;
&lt;td&gt;다리살&lt;/td&gt;
&lt;td&gt;100&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;b&gt;찻집&lt;/b&gt;&lt;/td&gt;
&lt;td&gt;156&lt;/td&gt;
&lt;td&gt;&amp;nbsp;&lt;/td&gt;
&lt;td&gt;&amp;nbsp;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;b&gt;갈빗대&lt;/b&gt;&lt;/td&gt;
&lt;td&gt;120&lt;/td&gt;
&lt;td&gt;갈비대&lt;/td&gt;
&lt;td&gt;34&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;피잣집&lt;/td&gt;
&lt;td&gt;7&lt;/td&gt;
&lt;td&gt;&lt;b&gt;피자집&lt;/b&gt;&lt;/td&gt;
&lt;td&gt;146&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;b&gt;귓속&lt;/b&gt;&lt;/td&gt;
&lt;td&gt;60&lt;/td&gt;
&lt;td&gt;귀속*&lt;/td&gt;
&lt;td&gt;90&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;b&gt;보랏빛&lt;/b&gt;&lt;/td&gt;
&lt;td&gt;89&lt;/td&gt;
&lt;td&gt;보라빛&lt;/td&gt;
&lt;td&gt;45&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;b&gt;바닷바람&lt;/b&gt;&lt;/td&gt;
&lt;td&gt;119&lt;/td&gt;
&lt;td&gt;바다바람&lt;/td&gt;
&lt;td&gt;14&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;b&gt;국숫집&lt;/b&gt;&lt;/td&gt;
&lt;td&gt;16&lt;/td&gt;
&lt;td&gt;국수집&lt;/td&gt;
&lt;td&gt;115&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;b&gt;부잣집&lt;/b&gt;&lt;/td&gt;
&lt;td&gt;110&lt;/td&gt;
&lt;td&gt;부자집&lt;/td&gt;
&lt;td&gt;20&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;b&gt;갈빗집&lt;/b&gt;&lt;/td&gt;
&lt;td&gt;10&lt;/td&gt;
&lt;td&gt;갈비집&lt;/td&gt;
&lt;td&gt;117&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;b&gt;비눗방울&lt;/b&gt;&lt;/td&gt;
&lt;td&gt;94&lt;/td&gt;
&lt;td&gt;비누방울&lt;/td&gt;
&lt;td&gt;30&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;b&gt;방앗간&lt;/b&gt;&lt;/td&gt;
&lt;td&gt;121&lt;/td&gt;
&lt;td&gt;&amp;nbsp;&lt;/td&gt;
&lt;td&gt;&amp;nbsp;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;첫쨋날&lt;/td&gt;
&lt;td&gt;5&lt;/td&gt;
&lt;td&gt;첫째날&lt;/td&gt;
&lt;td&gt;112&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;b&gt;조갯살&lt;/b&gt;&lt;/td&gt;
&lt;td&gt;49&lt;/td&gt;
&lt;td&gt;조개살&lt;/td&gt;
&lt;td&gt;62&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;b&gt;인삿말&lt;/b&gt;&lt;/td&gt;
&lt;td&gt;22&lt;/td&gt;
&lt;td&gt;인사말&lt;/td&gt;
&lt;td&gt;89&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;b&gt;맥줏집&lt;/b&gt;&lt;/td&gt;
&lt;td&gt;5&lt;/td&gt;
&lt;td&gt;맥주집&lt;/td&gt;
&lt;td&gt;99&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;b&gt;뼛속&lt;/b&gt;&lt;/td&gt;
&lt;td&gt;80&lt;/td&gt;
&lt;td&gt;뼈속&lt;/td&gt;
&lt;td&gt;22&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;b&gt;시래깃국&lt;/b&gt;&lt;/td&gt;
&lt;td&gt;6&lt;/td&gt;
&lt;td&gt;시래기국&lt;/td&gt;
&lt;td&gt;93&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;치맛살&lt;/td&gt;
&lt;td&gt;36&lt;/td&gt;
&lt;td&gt;&lt;b&gt;치마살&lt;/b&gt;&lt;/td&gt;
&lt;td&gt;60&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;b&gt;죗값&lt;/b&gt;&lt;/td&gt;
&lt;td&gt;73&lt;/td&gt;
&lt;td&gt;죄값&lt;/td&gt;
&lt;td&gt;17&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;b&gt;처갓집&lt;/b&gt;&lt;/td&gt;
&lt;td&gt;39&lt;/td&gt;
&lt;td&gt;처가집&lt;/td&gt;
&lt;td&gt;44&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;b&gt;낚싯대&lt;/b&gt;&lt;/td&gt;
&lt;td&gt;32&lt;/td&gt;
&lt;td&gt;낚시대&lt;/td&gt;
&lt;td&gt;51&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;바닷장어&lt;/td&gt;
&lt;td&gt;9&lt;/td&gt;
&lt;td&gt;&lt;b&gt;바다장어&lt;/b&gt;&lt;/td&gt;
&lt;td&gt;74&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;b&gt;뭇국&lt;/b&gt;&lt;/td&gt;
&lt;td&gt;6&lt;/td&gt;
&lt;td&gt;무국&lt;/td&gt;
&lt;td&gt;75&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;b&gt;이야깃거리&lt;/b&gt;&lt;/td&gt;
&lt;td&gt;65&lt;/td&gt;
&lt;td&gt;이야기거리&lt;/td&gt;
&lt;td&gt;15&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;바닷마을&lt;/td&gt;
&lt;td&gt;69&lt;/td&gt;
&lt;td&gt;바다마을&lt;/td&gt;
&lt;td&gt;10&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;기왓집&lt;/td&gt;
&lt;td&gt;5&lt;/td&gt;
&lt;td&gt;&lt;b&gt;기와집&lt;/b&gt;&lt;/td&gt;
&lt;td&gt;73&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;b&gt;안줏거리&lt;/b&gt;&lt;/td&gt;
&lt;td&gt;7&lt;/td&gt;
&lt;td&gt;안주거리&lt;/td&gt;
&lt;td&gt;65&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;b&gt;마룻바닥&lt;/b&gt;&lt;/td&gt;
&lt;td&gt;49&lt;/td&gt;
&lt;td&gt;마루바닥&lt;/td&gt;
&lt;td&gt;22&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;b&gt;김칫국&lt;/b&gt;&lt;/td&gt;
&lt;td&gt;38&lt;/td&gt;
&lt;td&gt;김치국&lt;/td&gt;
&lt;td&gt;33&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;b&gt;자릿세&lt;/b&gt;&lt;/td&gt;
&lt;td&gt;59&lt;/td&gt;
&lt;td&gt;자리세&lt;/td&gt;
&lt;td&gt;9&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;b&gt;놀잇감&lt;/b&gt;&lt;/td&gt;
&lt;td&gt;56&lt;/td&gt;
&lt;td&gt;놀이감&lt;/td&gt;
&lt;td&gt;10&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;b&gt;선짓국&lt;/b&gt;&lt;/td&gt;
&lt;td&gt;5&lt;/td&gt;
&lt;td&gt;선지국&lt;/td&gt;
&lt;td&gt;61&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;b&gt;곳간&lt;/b&gt;&lt;/td&gt;
&lt;td&gt;64&lt;/td&gt;
&lt;td&gt;&amp;nbsp;&lt;/td&gt;
&lt;td&gt;&amp;nbsp;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;b&gt;우스갯소리&lt;/b&gt;&lt;/td&gt;
&lt;td&gt;53&lt;/td&gt;
&lt;td&gt;우스개소리&lt;/td&gt;
&lt;td&gt;10&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;b&gt;장밋빛&lt;/b&gt;&lt;/td&gt;
&lt;td&gt;36&lt;/td&gt;
&lt;td&gt;장미빛&lt;/td&gt;
&lt;td&gt;27&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;b&gt;반딧불&lt;/b&gt;&lt;/td&gt;
&lt;td&gt;62&lt;/td&gt;
&lt;td&gt;&amp;nbsp;&lt;/td&gt;
&lt;td&gt;&amp;nbsp;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;b&gt;뱃사공&lt;/b&gt;&lt;/td&gt;
&lt;td&gt;60&lt;/td&gt;
&lt;td&gt;&amp;nbsp;&lt;/td&gt;
&lt;td&gt;&amp;nbsp;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;b&gt;혼잣말&lt;/b&gt;&lt;/td&gt;
&lt;td&gt;59&lt;/td&gt;
&lt;td&gt;&amp;nbsp;&lt;/td&gt;
&lt;td&gt;&amp;nbsp;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;b&gt;사잇길&lt;/b&gt;&lt;/td&gt;
&lt;td&gt;22&lt;/td&gt;
&lt;td&gt;사이길&lt;/td&gt;
&lt;td&gt;35&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;b&gt;콧등&lt;/b&gt;&lt;/td&gt;
&lt;td&gt;56&lt;/td&gt;
&lt;td&gt;&amp;nbsp;&lt;/td&gt;
&lt;td&gt;&amp;nbsp;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;b&gt;콧속&lt;/b&gt;&lt;/td&gt;
&lt;td&gt;47&lt;/td&gt;
&lt;td&gt;코속&lt;/td&gt;
&lt;td&gt;9&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;b&gt;노랫소리&lt;/b&gt;&lt;/td&gt;
&lt;td&gt;31&lt;/td&gt;
&lt;td&gt;노래소리&lt;/td&gt;
&lt;td&gt;23&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;b&gt;머릿고기&lt;/b&gt;&lt;/td&gt;
&lt;td&gt;27&lt;/td&gt;
&lt;td&gt;머리고기&lt;/td&gt;
&lt;td&gt;25&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;b&gt;탯줄&lt;/b&gt;&lt;/td&gt;
&lt;td&gt;51&lt;/td&gt;
&lt;td&gt;&amp;nbsp;&lt;/td&gt;
&lt;td&gt;&amp;nbsp;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;b&gt;고깃국&lt;/b&gt;&lt;/td&gt;
&lt;td&gt;34&lt;/td&gt;
&lt;td&gt;고기국&lt;/td&gt;
&lt;td&gt;17&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;b&gt;시곗줄&lt;/b&gt;&lt;/td&gt;
&lt;td&gt;10&lt;/td&gt;
&lt;td&gt;시계줄&lt;/td&gt;
&lt;td&gt;40&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;b&gt;김칫국물&lt;/b&gt;&lt;/td&gt;
&lt;td&gt;8&lt;/td&gt;
&lt;td&gt;김치국물&lt;/td&gt;
&lt;td&gt;41&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;b&gt;노랫말&lt;/b&gt;&lt;/td&gt;
&lt;td&gt;48&lt;/td&gt;
&lt;td&gt;&amp;nbsp;&lt;/td&gt;
&lt;td&gt;&amp;nbsp;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;b&gt;찻잎&lt;/b&gt;&lt;/td&gt;
&lt;td&gt;42&lt;/td&gt;
&lt;td&gt;차잎&lt;/td&gt;
&lt;td&gt;6&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;b&gt;먹잇감&lt;/b&gt;&lt;/td&gt;
&lt;td&gt;40&lt;/td&gt;
&lt;td&gt;먹이감&lt;/td&gt;
&lt;td&gt;8&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;b&gt;호숫가&lt;/b&gt;&lt;/td&gt;
&lt;td&gt;46&lt;/td&gt;
&lt;td&gt;&amp;nbsp;&lt;/td&gt;
&lt;td&gt;&amp;nbsp;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;b&gt;샛별&lt;/b&gt;&lt;/td&gt;
&lt;td&gt;46&lt;/td&gt;
&lt;td&gt;&amp;nbsp;&lt;/td&gt;
&lt;td&gt;&amp;nbsp;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;b&gt;만둣집&lt;/b&gt;&lt;/td&gt;
&lt;td&gt;10&lt;/td&gt;
&lt;td&gt;만두집&lt;/td&gt;
&lt;td&gt;35&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;b&gt;북엇국&lt;/b&gt;&lt;/td&gt;
&lt;td&gt;12&lt;/td&gt;
&lt;td&gt;북어국&lt;/td&gt;
&lt;td&gt;32&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;아랫칸&lt;/td&gt;
&lt;td&gt;9&lt;/td&gt;
&lt;td&gt;&lt;b&gt;아래칸&lt;/b&gt;&lt;/td&gt;
&lt;td&gt;35&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;b&gt;귓밥&lt;/b&gt;&lt;/td&gt;
&lt;td&gt;42&lt;/td&gt;
&lt;td&gt;&amp;nbsp;&lt;/td&gt;
&lt;td&gt;&amp;nbsp;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;b&gt;우윳빛&lt;/b&gt;&lt;/td&gt;
&lt;td&gt;21&lt;/td&gt;
&lt;td&gt;우유빛&lt;/td&gt;
&lt;td&gt;21&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;b&gt;빨랫감&lt;/b&gt;&lt;/td&gt;
&lt;td&gt;18&lt;/td&gt;
&lt;td&gt;빨래감&lt;/td&gt;
&lt;td&gt;24&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;b&gt;자릿수&lt;/b&gt;&lt;/td&gt;
&lt;td&gt;34&lt;/td&gt;
&lt;td&gt;자리수&lt;/td&gt;
&lt;td&gt;7&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;b&gt;날갯짓&lt;/b&gt;&lt;/td&gt;
&lt;td&gt;27&lt;/td&gt;
&lt;td&gt;날개짓&lt;/td&gt;
&lt;td&gt;13&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;b&gt;골칫거리&lt;/b&gt;&lt;/td&gt;
&lt;td&gt;33&lt;/td&gt;
&lt;td&gt;골치거리&lt;/td&gt;
&lt;td&gt;6&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;b&gt;세숫대야&lt;/b&gt;&lt;/td&gt;
&lt;td&gt;28&lt;/td&gt;
&lt;td&gt;세수대야&lt;/td&gt;
&lt;td&gt;11&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;b&gt;외갓집&lt;/b&gt;&lt;/td&gt;
&lt;td&gt;23&lt;/td&gt;
&lt;td&gt;외가집&lt;/td&gt;
&lt;td&gt;16&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;머릿말&lt;/td&gt;
&lt;td&gt;8&lt;/td&gt;
&lt;td&gt;&lt;b&gt;머리말&lt;/b&gt;&lt;/td&gt;
&lt;td&gt;31&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;b&gt;종잣돈&lt;/b&gt;&lt;/td&gt;
&lt;td&gt;26&lt;/td&gt;
&lt;td&gt;종자돈&lt;/td&gt;
&lt;td&gt;12&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;주윗사람&lt;/td&gt;
&lt;td&gt;5&lt;/td&gt;
&lt;td&gt;&lt;b&gt;주위사람&lt;/b&gt;&lt;/td&gt;
&lt;td&gt;33&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;b&gt;저잣거리&lt;/b&gt;&lt;/td&gt;
&lt;td&gt;37&lt;/td&gt;
&lt;td&gt;&amp;nbsp;&lt;/td&gt;
&lt;td&gt;&amp;nbsp;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;b&gt;이삿짐&lt;/b&gt;&lt;/td&gt;
&lt;td&gt;29&lt;/td&gt;
&lt;td&gt;이사짐&lt;/td&gt;
&lt;td&gt;8&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;b&gt;후춧가루&lt;/b&gt;&lt;/td&gt;
&lt;td&gt;17&lt;/td&gt;
&lt;td&gt;후추가루&lt;/td&gt;
&lt;td&gt;17&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;b&gt;고깃값&lt;/b&gt;&lt;/td&gt;
&lt;td&gt;13&lt;/td&gt;
&lt;td&gt;고기값&lt;/td&gt;
&lt;td&gt;21&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;b&gt;진돗개&lt;/b&gt;&lt;/td&gt;
&lt;td&gt;33&lt;/td&gt;
&lt;td&gt;&amp;nbsp;&lt;/td&gt;
&lt;td&gt;&amp;nbsp;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;b&gt;샛길&lt;/b&gt;&lt;/td&gt;
&lt;td&gt;32&lt;/td&gt;
&lt;td&gt;&amp;nbsp;&lt;/td&gt;
&lt;td&gt;&amp;nbsp;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;b&gt;존댓말&lt;/b&gt;&lt;/td&gt;
&lt;td&gt;32&lt;/td&gt;
&lt;td&gt;&amp;nbsp;&lt;/td&gt;
&lt;td&gt;&amp;nbsp;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;b&gt;콧바람&lt;/b&gt;&lt;/td&gt;
&lt;td&gt;31&lt;/td&gt;
&lt;td&gt;&amp;nbsp;&lt;/td&gt;
&lt;td&gt;&amp;nbsp;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;b&gt;배춧잎&lt;/b&gt;&lt;/td&gt;
&lt;td&gt;18&lt;/td&gt;
&lt;td&gt;배추잎&lt;/td&gt;
&lt;td&gt;13&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;b&gt;등굣길&lt;/b&gt;&lt;/td&gt;
&lt;td&gt;12&lt;/td&gt;
&lt;td&gt;등교길&lt;/td&gt;
&lt;td&gt;19&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;b&gt;종갓집&lt;/b&gt;&lt;/td&gt;
&lt;td&gt;7&lt;/td&gt;
&lt;td&gt;종가집&lt;/td&gt;
&lt;td&gt;24&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;b&gt;전봇대&lt;/b&gt;&lt;/td&gt;
&lt;td&gt;29&lt;/td&gt;
&lt;td&gt;&amp;nbsp;&lt;/td&gt;
&lt;td&gt;&amp;nbsp;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;b&gt;머릿살&lt;/b&gt;&lt;/td&gt;
&lt;td&gt;14&lt;/td&gt;
&lt;td&gt;머리살&lt;/td&gt;
&lt;td&gt;15&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;b&gt;고깃살&lt;/b&gt;&lt;/td&gt;
&lt;td&gt;6&lt;/td&gt;
&lt;td&gt;고기살&lt;/td&gt;
&lt;td&gt;22&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;b&gt;꼭짓점&lt;/b&gt;&lt;/td&gt;
&lt;td&gt;6&lt;/td&gt;
&lt;td&gt;꼭지점&lt;/td&gt;
&lt;td&gt;22&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td colspan=&quot;4&quot;&gt;* 동철이의어가 포함되어 있어 빈도수가 부정확할 수 있음&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p style=&quot;text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;웹 상의 텍스트라 그런지 사이시옷을 잘못 사용하는 경우가 제법 되는 것 같네요. 그럼 출판된 서적에서 가져온 문어 말뭉치에서는 좀 더 정확한 결과가 나왔을까요? 모두의 말뭉치 문어 말뭉치에 대해서도 동일한 분석을 수행해보았습니다.&lt;/p&gt;
&lt;table class=&quot;std-table&quot; data-ke-align=&quot;alignLeft&quot;&gt;&lt;caption&gt;모두의 말뭉치 문어 말뭉치에서 추출된 사이시옷이 포함된 명사들 통계&lt;/caption&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;th colspan=&quot;2&quot;&gt;사이시옷 있는 형태&lt;/th&gt;
&lt;th colspan=&quot;2&quot;&gt;사이시옷 없는 형태&lt;/th&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;b&gt;오랫동안&lt;/b&gt;&lt;/td&gt;
&lt;td&gt;68447&lt;/td&gt;
&lt;td&gt;&amp;nbsp;&lt;/td&gt;
&lt;td&gt;&amp;nbsp;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;b&gt;숫자&lt;/b&gt;&lt;/td&gt;
&lt;td&gt;66953&lt;/td&gt;
&lt;td&gt;수자*&lt;/td&gt;
&lt;td&gt;934&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;b&gt;머릿속&lt;/b&gt;&lt;/td&gt;
&lt;td&gt;51100&lt;/td&gt;
&lt;td&gt;머리속&lt;/td&gt;
&lt;td&gt;746&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;촛점&lt;/td&gt;
&lt;td&gt;340&lt;/td&gt;
&lt;td&gt;&lt;b&gt;초점&lt;/b&gt;&lt;/td&gt;
&lt;td&gt;51206&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;문젯점&lt;/td&gt;
&lt;td&gt;5&lt;/td&gt;
&lt;td&gt;&lt;b&gt;문제점&lt;/b&gt;&lt;/td&gt;
&lt;td&gt;50666&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;홋수&lt;/td&gt;
&lt;td&gt;5&lt;/td&gt;
&lt;td&gt;&lt;b&gt;호수&lt;/b&gt;*&lt;/td&gt;
&lt;td&gt;31111&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;솟장&lt;/td&gt;
&lt;td&gt;12&lt;/td&gt;
&lt;td&gt;&lt;b&gt;소장&lt;/b&gt;*&lt;/td&gt;
&lt;td&gt;30021&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;b&gt;횟수&lt;/b&gt;&lt;/td&gt;
&lt;td&gt;12947&lt;/td&gt;
&lt;td&gt;회수*&lt;/td&gt;
&lt;td&gt;15507&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;b&gt;잿물&lt;/b&gt;&lt;/td&gt;
&lt;td&gt;407&lt;/td&gt;
&lt;td&gt;재물*&lt;/td&gt;
&lt;td&gt;17870&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;b&gt;바닷가&lt;/b&gt;&lt;/td&gt;
&lt;td&gt;16861&lt;/td&gt;
&lt;td&gt;&amp;nbsp;&lt;/td&gt;
&lt;td&gt;&amp;nbsp;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;b&gt;뱃속&lt;/b&gt;&lt;/td&gt;
&lt;td&gt;13437&lt;/td&gt;
&lt;td&gt;배속*&lt;/td&gt;
&lt;td&gt;2028&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;b&gt;어젯밤&lt;/b&gt;&lt;/td&gt;
&lt;td&gt;14577&lt;/td&gt;
&lt;td&gt;어제밤&lt;/td&gt;
&lt;td&gt;138&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;b&gt;귓속&lt;/b&gt;&lt;/td&gt;
&lt;td&gt;2263&lt;/td&gt;
&lt;td&gt;귀속*&lt;/td&gt;
&lt;td&gt;11792&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;아랫쪽&lt;/td&gt;
&lt;td&gt;11&lt;/td&gt;
&lt;td&gt;&lt;b&gt;아래쪽&lt;/b&gt;&lt;/td&gt;
&lt;td&gt;12867&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;윗쪽&lt;/td&gt;
&lt;td&gt;6&lt;/td&gt;
&lt;td&gt;&lt;b&gt;위쪽&lt;/b&gt;&lt;/td&gt;
&lt;td&gt;12172&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;댓수&lt;/td&gt;
&lt;td&gt;6&lt;/td&gt;
&lt;td&gt;&lt;b&gt;대수&lt;/b&gt;&lt;/td&gt;
&lt;td&gt;11751&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;숫양(X)&lt;/td&gt;
&lt;td&gt;356&lt;/td&gt;
&lt;td&gt;수양&lt;/td&gt;
&lt;td&gt;11143&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;b&gt;나뭇잎&lt;/b&gt;&lt;/td&gt;
&lt;td&gt;11316&lt;/td&gt;
&lt;td&gt;나무잎&lt;/td&gt;
&lt;td&gt;59&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;숫법&lt;/td&gt;
&lt;td&gt;6&lt;/td&gt;
&lt;td&gt;&lt;b&gt;수법&lt;/b&gt;&lt;/td&gt;
&lt;td&gt;10821&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;b&gt;귓결&lt;/b&gt;&lt;/td&gt;
&lt;td&gt;107&lt;/td&gt;
&lt;td&gt;귀결*&lt;/td&gt;
&lt;td&gt;10676&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;b&gt;바닷물&lt;/b&gt;&lt;/td&gt;
&lt;td&gt;9834&lt;/td&gt;
&lt;td&gt;바다물&lt;/td&gt;
&lt;td&gt;100&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;b&gt;혼잣말&lt;/b&gt;&lt;/td&gt;
&lt;td&gt;8771&lt;/td&gt;
&lt;td&gt;혼자말&lt;/td&gt;
&lt;td&gt;809&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;머릿칼&lt;/td&gt;
&lt;td&gt;7&lt;/td&gt;
&lt;td&gt;&lt;b&gt;머리칼&lt;/b&gt;&lt;/td&gt;
&lt;td&gt;9241&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;b&gt;찻장&lt;/b&gt;&lt;/td&gt;
&lt;td&gt;7&lt;/td&gt;
&lt;td&gt;차장*&lt;/td&gt;
&lt;td&gt;9117&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;숫난(X)&lt;/td&gt;
&lt;td&gt;9&lt;/td&gt;
&lt;td&gt;수난&lt;/td&gt;
&lt;td&gt;6898&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;b&gt;찻잔&lt;/b&gt;&lt;/td&gt;
&lt;td&gt;6786&lt;/td&gt;
&lt;td&gt;차잔&lt;/td&gt;
&lt;td&gt;10&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;b&gt;갯벌&lt;/b&gt;&lt;/td&gt;
&lt;td&gt;6435&lt;/td&gt;
&lt;td&gt;개벌&lt;/td&gt;
&lt;td&gt;25&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;b&gt;부잣집&lt;/b&gt;&lt;/td&gt;
&lt;td&gt;5879&lt;/td&gt;
&lt;td&gt;부자집&lt;/td&gt;
&lt;td&gt;224&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;머릿말&lt;/td&gt;
&lt;td&gt;46&lt;/td&gt;
&lt;td&gt;&lt;b&gt;머리말&lt;/b&gt;&lt;/td&gt;
&lt;td&gt;5964&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;제삿장&lt;/td&gt;
&lt;td&gt;8&lt;/td&gt;
&lt;td&gt;&lt;b&gt;제사장&lt;/b&gt;&lt;/td&gt;
&lt;td&gt;5381&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;b&gt;인삿말&lt;/b&gt;&lt;/td&gt;
&lt;td&gt;94&lt;/td&gt;
&lt;td&gt;인사말&lt;/td&gt;
&lt;td&gt;5245&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;b&gt;노랫소리&lt;/b&gt;&lt;/td&gt;
&lt;td&gt;4748&lt;/td&gt;
&lt;td&gt;노래소리&lt;/td&gt;
&lt;td&gt;217&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;훗세인(X)&lt;/td&gt;
&lt;td&gt;10&lt;/td&gt;
&lt;td&gt;후세인&lt;/td&gt;
&lt;td&gt;4844&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;갯수&lt;/td&gt;
&lt;td&gt;305&lt;/td&gt;
&lt;td&gt;&lt;b&gt;개수&lt;/b&gt;&lt;/td&gt;
&lt;td&gt;4498&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;b&gt;종잣돈&lt;/b&gt;&lt;/td&gt;
&lt;td&gt;353&lt;/td&gt;
&lt;td&gt;종자돈&lt;/td&gt;
&lt;td&gt;4378&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;b&gt;찻집&lt;/b&gt;&lt;/td&gt;
&lt;td&gt;4690&lt;/td&gt;
&lt;td&gt;차집&lt;/td&gt;
&lt;td&gt;11&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;윗층&lt;/td&gt;
&lt;td&gt;10&lt;/td&gt;
&lt;td&gt;&lt;b&gt;위층&lt;/b&gt;&lt;/td&gt;
&lt;td&gt;4472&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;머릿통&lt;/td&gt;
&lt;td&gt;7&lt;/td&gt;
&lt;td&gt;&lt;b&gt;머리통&lt;/b&gt;&lt;/td&gt;
&lt;td&gt;4469&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;콧웃음&lt;/td&gt;
&lt;td&gt;9&lt;/td&gt;
&lt;td&gt;&lt;b&gt;코웃음&lt;/b&gt;&lt;/td&gt;
&lt;td&gt;4322&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;b&gt;텃밭&lt;/b&gt;&lt;/td&gt;
&lt;td&gt;4097&lt;/td&gt;
&lt;td&gt;터밭&lt;/td&gt;
&lt;td&gt;50&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;머릿맡&lt;/td&gt;
&lt;td&gt;5&lt;/td&gt;
&lt;td&gt;&lt;b&gt;머리맡&lt;/b&gt;&lt;/td&gt;
&lt;td&gt;4123&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;농삿일&lt;/td&gt;
&lt;td&gt;132&lt;/td&gt;
&lt;td&gt;&lt;b&gt;농사일&lt;/b&gt;&lt;/td&gt;
&lt;td&gt;3900&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;b&gt;호숫가&lt;/b&gt;&lt;/td&gt;
&lt;td&gt;3822&lt;/td&gt;
&lt;td&gt;호수가&lt;/td&gt;
&lt;td&gt;100&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;b&gt;귓전&lt;/b&gt;&lt;/td&gt;
&lt;td&gt;3748&lt;/td&gt;
&lt;td&gt;귀전&lt;/td&gt;
&lt;td&gt;63&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;b&gt;날갯짓&lt;/b&gt;&lt;/td&gt;
&lt;td&gt;3138&lt;/td&gt;
&lt;td&gt;날개짓&lt;/td&gt;
&lt;td&gt;451&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;b&gt;보랏빛&lt;/b&gt;&lt;/td&gt;
&lt;td&gt;3351&lt;/td&gt;
&lt;td&gt;보라빛&lt;/td&gt;
&lt;td&gt;189&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;b&gt;장밋빛&lt;/b&gt;&lt;/td&gt;
&lt;td&gt;3052&lt;/td&gt;
&lt;td&gt;장미빛&lt;/td&gt;
&lt;td&gt;421&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;b&gt;마룻바닥&lt;/b&gt;&lt;/td&gt;
&lt;td&gt;3025&lt;/td&gt;
&lt;td&gt;마루바닥&lt;/td&gt;
&lt;td&gt;411&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;b&gt;홧술&lt;/b&gt;&lt;/td&gt;
&lt;td&gt;12&lt;/td&gt;
&lt;td&gt;화술*&lt;/td&gt;
&lt;td&gt;3281&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;b&gt;고춧가루&lt;/b&gt;&lt;/td&gt;
&lt;td&gt;3155&lt;/td&gt;
&lt;td&gt;고추가루&lt;/td&gt;
&lt;td&gt;95&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;b&gt;수돗물&lt;/b&gt;&lt;/td&gt;
&lt;td&gt;3062&lt;/td&gt;
&lt;td&gt;수도물&lt;/td&gt;
&lt;td&gt;105&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;b&gt;이야깃거리&lt;/b&gt;&lt;/td&gt;
&lt;td&gt;2886&lt;/td&gt;
&lt;td&gt;이야기거리&lt;/td&gt;
&lt;td&gt;191&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;b&gt;잿간&lt;/b&gt;&lt;/td&gt;
&lt;td&gt;32&lt;/td&gt;
&lt;td&gt;재간*&lt;/td&gt;
&lt;td&gt;2896&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;b&gt;뱃전&lt;/b&gt;&lt;/td&gt;
&lt;td&gt;2371&lt;/td&gt;
&lt;td&gt;배전*&lt;/td&gt;
&lt;td&gt;555&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;b&gt;우스갯소리&lt;/b&gt;&lt;/td&gt;
&lt;td&gt;2826&lt;/td&gt;
&lt;td&gt;우스개소리&lt;/td&gt;
&lt;td&gt;36&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;b&gt;낚싯대&lt;/b&gt;&lt;/td&gt;
&lt;td&gt;2667&lt;/td&gt;
&lt;td&gt;낚시대&lt;/td&gt;
&lt;td&gt;136&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;b&gt;골칫거리&lt;/b&gt;&lt;/td&gt;
&lt;td&gt;2627&lt;/td&gt;
&lt;td&gt;골치거리&lt;/td&gt;
&lt;td&gt;86&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;b&gt;지렛대&lt;/b&gt;&lt;/td&gt;
&lt;td&gt;2678&lt;/td&gt;
&lt;td&gt;&amp;nbsp;&lt;/td&gt;
&lt;td&gt;&amp;nbsp;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;b&gt;뼛속&lt;/b&gt;&lt;/td&gt;
&lt;td&gt;2589&lt;/td&gt;
&lt;td&gt;뼈속&lt;/td&gt;
&lt;td&gt;58&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;b&gt;잿더미&lt;/b&gt;&lt;/td&gt;
&lt;td&gt;2616&lt;/td&gt;
&lt;td&gt;재더미&lt;/td&gt;
&lt;td&gt;9&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;b&gt;바닷속&lt;/b&gt;&lt;/td&gt;
&lt;td&gt;2497&lt;/td&gt;
&lt;td&gt;바다속&lt;/td&gt;
&lt;td&gt;75&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;b&gt;아랫입술&lt;/b&gt;&lt;/td&gt;
&lt;td&gt;2528&lt;/td&gt;
&lt;td&gt;아래입술&lt;/td&gt;
&lt;td&gt;10&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;b&gt;방앗간&lt;/b&gt;&lt;/td&gt;
&lt;td&gt;2498&lt;/td&gt;
&lt;td&gt;방아간&lt;/td&gt;
&lt;td&gt;13&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;b&gt;노랫말&lt;/b&gt;&lt;/td&gt;
&lt;td&gt;2176&lt;/td&gt;
&lt;td&gt;노래말&lt;/td&gt;
&lt;td&gt;292&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;b&gt;뱃머리&lt;/b&gt;&lt;/td&gt;
&lt;td&gt;2461&lt;/td&gt;
&lt;td&gt;배머리&lt;/td&gt;
&lt;td&gt;6&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;댓자&lt;/td&gt;
&lt;td&gt;5&lt;/td&gt;
&lt;td&gt;&lt;b&gt;대자&lt;/b&gt;&lt;/td&gt;
&lt;td&gt;2432&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;b&gt;뱃사람&lt;/b&gt;&lt;/td&gt;
&lt;td&gt;2363&lt;/td&gt;
&lt;td&gt;&amp;nbsp;&lt;/td&gt;
&lt;td&gt;&amp;nbsp;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;b&gt;툇마루&lt;/b&gt;&lt;/td&gt;
&lt;td&gt;2350&lt;/td&gt;
&lt;td&gt;퇴마루&lt;/td&gt;
&lt;td&gt;12&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;b&gt;뱃길&lt;/b&gt;&lt;/td&gt;
&lt;td&gt;2330&lt;/td&gt;
&lt;td&gt;배길&lt;/td&gt;
&lt;td&gt;18&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;기왓집&lt;/td&gt;
&lt;td&gt;5&lt;/td&gt;
&lt;td&gt;&lt;b&gt;기와집&lt;/b&gt;&lt;/td&gt;
&lt;td&gt;2337&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;b&gt;콧등&lt;/b&gt;&lt;/td&gt;
&lt;td&gt;2307&lt;/td&gt;
&lt;td&gt;코등&lt;/td&gt;
&lt;td&gt;32&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;b&gt;자줏빛&lt;/b&gt;&lt;/td&gt;
&lt;td&gt;2070&lt;/td&gt;
&lt;td&gt;자주빛&lt;/td&gt;
&lt;td&gt;224&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;b&gt;곳간&lt;/b&gt;&lt;/td&gt;
&lt;td&gt;1919&lt;/td&gt;
&lt;td&gt;고간*&lt;/td&gt;
&lt;td&gt;246&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;b&gt;담뱃갑&lt;/b&gt;&lt;/td&gt;
&lt;td&gt;1907&lt;/td&gt;
&lt;td&gt;담배갑&lt;/td&gt;
&lt;td&gt;249&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;b&gt;콧방귀&lt;/b&gt;&lt;/td&gt;
&lt;td&gt;1678&lt;/td&gt;
&lt;td&gt;코방귀&lt;/td&gt;
&lt;td&gt;474&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;b&gt;치맛자락&lt;/b&gt;&lt;/td&gt;
&lt;td&gt;2073&lt;/td&gt;
&lt;td&gt;치마자락&lt;/td&gt;
&lt;td&gt;77&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;b&gt;샛길&lt;/b&gt;&lt;/td&gt;
&lt;td&gt;2065&lt;/td&gt;
&lt;td&gt;새길&lt;/td&gt;
&lt;td&gt;15&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;b&gt;이삿짐&lt;/b&gt;&lt;/td&gt;
&lt;td&gt;2000&lt;/td&gt;
&lt;td&gt;이사짐&lt;/td&gt;
&lt;td&gt;47&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;b&gt;바닷바람&lt;/b&gt;&lt;/td&gt;
&lt;td&gt;1990&lt;/td&gt;
&lt;td&gt;바다바람&lt;/td&gt;
&lt;td&gt;24&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;b&gt;먹잇감&lt;/b&gt;&lt;/td&gt;
&lt;td&gt;1646&lt;/td&gt;
&lt;td&gt;먹이감&lt;/td&gt;
&lt;td&gt;357&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;b&gt;담뱃불&lt;/b&gt;&lt;/td&gt;
&lt;td&gt;1852&lt;/td&gt;
&lt;td&gt;담배불&lt;/td&gt;
&lt;td&gt;94&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;장삿꾼&lt;/td&gt;
&lt;td&gt;17&lt;/td&gt;
&lt;td&gt;&lt;b&gt;장사꾼&lt;/b&gt;&lt;/td&gt;
&lt;td&gt;1923&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;b&gt;나룻배&lt;/b&gt;&lt;/td&gt;
&lt;td&gt;1934&lt;/td&gt;
&lt;td&gt;나루배&lt;/td&gt;
&lt;td&gt;5&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;b&gt;전봇대&lt;/b&gt;&lt;/td&gt;
&lt;td&gt;1918&lt;/td&gt;
&lt;td&gt;&amp;nbsp;&lt;/td&gt;
&lt;td&gt;&amp;nbsp;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;b&gt;저잣거리&lt;/b&gt;&lt;/td&gt;
&lt;td&gt;1528&lt;/td&gt;
&lt;td&gt;저자거리&lt;/td&gt;
&lt;td&gt;341&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;b&gt;샛별&lt;/b&gt;&lt;/td&gt;
&lt;td&gt;1810&lt;/td&gt;
&lt;td&gt;새별&lt;/td&gt;
&lt;td&gt;9&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;커핏잔&lt;/td&gt;
&lt;td&gt;7&lt;/td&gt;
&lt;td&gt;&lt;b&gt;커피잔&lt;/b&gt;&lt;/td&gt;
&lt;td&gt;1811&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;b&gt;탯줄&lt;/b&gt;&lt;/td&gt;
&lt;td&gt;1680&lt;/td&gt;
&lt;td&gt;&amp;nbsp;&lt;/td&gt;
&lt;td&gt;&amp;nbsp;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;b&gt;존댓말&lt;/b&gt;&lt;/td&gt;
&lt;td&gt;1434&lt;/td&gt;
&lt;td&gt;존대말&lt;/td&gt;
&lt;td&gt;243&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;나룻터&lt;/td&gt;
&lt;td&gt;7&lt;/td&gt;
&lt;td&gt;&lt;b&gt;나루터&lt;/b&gt;&lt;/td&gt;
&lt;td&gt;1661&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;소줏잔&lt;/td&gt;
&lt;td&gt;83&lt;/td&gt;
&lt;td&gt;&lt;b&gt;소주잔&lt;/b&gt;&lt;/td&gt;
&lt;td&gt;1582&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;b&gt;낚싯줄&lt;/b&gt;&lt;/td&gt;
&lt;td&gt;1548&lt;/td&gt;
&lt;td&gt;낚시줄&lt;/td&gt;
&lt;td&gt;115&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;b&gt;바닷길&lt;/b&gt;&lt;/td&gt;
&lt;td&gt;1650&lt;/td&gt;
&lt;td&gt;바다길&lt;/td&gt;
&lt;td&gt;6&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;소줏병&lt;/td&gt;
&lt;td&gt;51&lt;/td&gt;
&lt;td&gt;&lt;b&gt;소주병&lt;/b&gt;&lt;/td&gt;
&lt;td&gt;1604&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;b&gt;고갯길&lt;/b&gt;&lt;/td&gt;
&lt;td&gt;1627&lt;/td&gt;
&lt;td&gt;고개길&lt;/td&gt;
&lt;td&gt;26&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;b&gt;판잣집&lt;/b&gt;&lt;/td&gt;
&lt;td&gt;1419&lt;/td&gt;
&lt;td&gt;판자집&lt;/td&gt;
&lt;td&gt;180&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;b&gt;찻잎&lt;/b&gt;&lt;/td&gt;
&lt;td&gt;1373&lt;/td&gt;
&lt;td&gt;차잎&lt;/td&gt;
&lt;td&gt;195&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;b&gt;뱃사공&lt;/b&gt;&lt;/td&gt;
&lt;td&gt;1559&lt;/td&gt;
&lt;td&gt;배사공&lt;/td&gt;
&lt;td&gt;6&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;b&gt;뱃살&lt;/b&gt;&lt;/td&gt;
&lt;td&gt;1560&lt;/td&gt;
&lt;td&gt;&amp;nbsp;&lt;/td&gt;
&lt;td&gt;&amp;nbsp;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;b&gt;머릿결&lt;/b&gt;&lt;/td&gt;
&lt;td&gt;1344&lt;/td&gt;
&lt;td&gt;머리결&lt;/td&gt;
&lt;td&gt;164&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;b&gt;찻길&lt;/b&gt;&lt;/td&gt;
&lt;td&gt;1438&lt;/td&gt;
&lt;td&gt;차길&lt;/td&gt;
&lt;td&gt;56&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;b&gt;자릿수&lt;/b&gt;&lt;/td&gt;
&lt;td&gt;1239&lt;/td&gt;
&lt;td&gt;자리수&lt;/td&gt;
&lt;td&gt;214&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;b&gt;화젯거리&lt;/b&gt;&lt;/td&gt;
&lt;td&gt;1247&lt;/td&gt;
&lt;td&gt;화제거리&lt;/td&gt;
&lt;td&gt;191&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;b&gt;담뱃대&lt;/b&gt;&lt;/td&gt;
&lt;td&gt;1411&lt;/td&gt;
&lt;td&gt;담배대&lt;/td&gt;
&lt;td&gt;17&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;앗시리아(X)&lt;/td&gt;
&lt;td&gt;20&lt;/td&gt;
&lt;td&gt;아시리아&lt;/td&gt;
&lt;td&gt;1405&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;b&gt;허드렛일&lt;/b&gt;&lt;/td&gt;
&lt;td&gt;1358&lt;/td&gt;
&lt;td&gt;허드레일&lt;/td&gt;
&lt;td&gt;5&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;귓청&lt;/td&gt;
&lt;td&gt;7&lt;/td&gt;
&lt;td&gt;&lt;b&gt;귀청&lt;/b&gt;&lt;/td&gt;
&lt;td&gt;1354&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;b&gt;진돗개&lt;/b&gt;&lt;/td&gt;
&lt;td&gt;1278&lt;/td&gt;
&lt;td&gt;진도개&lt;/td&gt;
&lt;td&gt;52&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;b&gt;번갯불&lt;/b&gt;&lt;/td&gt;
&lt;td&gt;1275&lt;/td&gt;
&lt;td&gt;번개불&lt;/td&gt;
&lt;td&gt;47&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td colspan=&quot;4&quot;&gt;* 동철이의어가 포함되어 있어 빈도수가 부정확할 수 있음&lt;br /&gt;(X): 사이시옷이 아니지만 Kiwi가 오분석한 사례&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p style=&quot;text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;문어 말뭉치의 규모가 크다보니 다양한 사례가 등장하는 걸 확인할 수 있습니다. 특히 Kiwi가 사이시옷이 아닌 단어를 사이시옷으로 분석하는 오류를 범하는 것도 확인할 수 있네요(숫양, 숫난, 훗세인, 앗시리아 등). 그 외에도 회수나 고간처럼 중의성이 있어서 맥락에 따라 사이시옷을 쓰는 경우가 맞을 수도(반복하는 수를 나타내는 경우에는 횟수, 창고를 나타내는 경우에는 곳간), 사이시옷을 쓰지 않는 경우가 맞을 수도(돌려받는 것을 나타내는 경우에는 회수, 다리 사이를 나타내는 경우에는 고간) 있는 사례가 제법 있어서 단순히 빈도만 보고서는 정확한 분석이 어려운 경우도 제법 있습니다. 좀더 정확한 통계를 위해서는 WSD(Word Sense Disambiguation)도 필요할듯하네요. 문어 말뭉치의 결과는 보면 확실히 웹 텍스트보다는 좀더 바른 표기법을 쓰고 있는 것이 보이나 여전히 틀리는 비율이 높은 단어들도 꽤 있습니다. 교열하는 사람들도 많이 헷갈리나봐요.&lt;/p&gt;
&lt;h3 style=&quot;text-align: justify;&quot; data-ke-size=&quot;size23&quot;&gt;왜 이렇게 헷갈리게 됐을까?&lt;/h3&gt;
&lt;p style=&quot;text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;학자들은 사실 사이시옷이 중세 한국어에서 문법적 역할을 수행하는 속격조사(=소유격)였다고 추정하고 있습니다. 영어에서 소유격을 나타낼 때 's를 붙이는 것(Mother&lt;b&gt;'s&lt;/b&gt; day = 어미니&lt;b&gt;의&lt;/b&gt; 날)과 비슷한 역할을 했다는 것이죠. 그래서 사이시옷이 들어간 단어들을 분리해서 사이시옷을 소유의 의미로 해석하면 뜻이 통한다는 걸 확인할 수 있습니다(ex: 깻잎 -&amp;gt; 깨의 잎, 진돗개 -&amp;gt; 진도의 개). 그러나 언어 변천 과정에서 조사 역할을 하던 사이시옷은 차츰 잊혀지고 그 발음만이 잔재로 남은 것이죠. 문제는 한국어 화자들이 예나 지금이나 조사를 자주 생략하는 성향이 있었다는 것이고 당연히 조사였던 사이시옷도 반드시 쓰는게 아니라 상황에 따라 생략하는 경우가 많았겠지요. 때문에 명사와 명사가 결합할 때 ㅅ이 들어간 경우뿐만 아니라 그렇지 않은 경우도 동시에 흔적으로 이어져 내려오게 됩니다.&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;그래서 현대에 들어 맞춤법을 정비하려고 보니, 어떤 복합명사는 중세 사이시옷의 잔재가 남아 된소리화나 ㄴ소리가 첨가되는 게 있고 또 어떤 것은 그렇지 않은데, 이걸 무시하자니 동일하게 적자니 발음과 표기법이 따로 놀 것이라 어쩔수 없이 중세에 쓰이던 사이시옷 표기를 되살리게 된 거라고 하네요. 애초에 불규칙하게 발음에 남은 잔재를 표기하려다보니 표기법 역시 불규칙할 수 밖에 없는 안타까운 상황이라고 볼 수 있습니다. 다만 이 혼란을 더 크게 한것은 한자어나 외래에는 사이시옷을 쓰지 않고 순우리말 혹은 순우리말 + 한자어에서만 쓰게 한 1988년의 맞춤법 규정입니다. 어원과는 관계 없이 발생하는 불규칙한 발음 현상인데 표기시에는 어원을 고려하게 만들었기 때문입니다.&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;</description>
      <category>프로그래밍/NLP</category>
      <category>kiwi</category>
      <category>자연어처리</category>
      <author>&amp;int;2tdt=t&amp;sup2;+c</author>
      <guid isPermaLink="true">https://bab2min.tistory.com/675</guid>
      <comments>https://bab2min.tistory.com/675#entry675comment</comments>
      <pubDate>Mon, 4 Nov 2024 02:20:59 +0900</pubDate>
    </item>
    <item>
      <title>LLM으로 게임 텍스트 번역해보기</title>
      <link>https://bab2min.tistory.com/674</link>
      <description>&lt;h3 style=&quot;text-align: justify;&quot; data-ke-size=&quot;size23&quot;&gt;게임 텍스트 번역이라는 과제에서 LLM이 가지는 장점&lt;/h3&gt;
&lt;p style=&quot;text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;바야흐로 LLM(Large Language Model)의 시대가 도래했고, 오픈소스 모델들이 속속들이 등장하면서 누구나 집 컴퓨터에서 딸각(과 코드 몇 백 줄을 작성)하면 텍스트 관련한 귀찮은 일들을 모두 AI한테 맡길수 있는 세상이 왔습니다. 나무가 충분히 자란것 같으니 이제는 슬슬 열매를 따먹어봐야할 시간이 온거지요.&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;그래서 저는 그동안 머릿속으로 상상만하고 여력이 없어서 이루지 못한 일들을 AI발달에 힘입어 하나씩 이뤄나가보려고 하는데요, 첫번째 타겟은 게임 텍스트 자동 번역입니다. 자동 번역은 이미 충분히 발달된 분야가 아닌가 생각하실 수 있겠지만, 게임 텍스트 자동 번역은 생각보다 난이도가 있는 과제입니다.&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;먼저 게임 내 세계관에서만 사용되는 고유명사가 굉장히 많기 때문에 이를 일관되게 번역하기가 쉽지 않습니다. 예를 들어 hybrid라는 단어는 일반적으로 혼합, 잡종 등의 의미로 쓰이지만 스타크래프트 세계관에서는 이를 &quot;혼종&quot;이라는 말로 번역하고 있습니다. 만약 스타크래프트 세계관의 텍스트를 번역한다면 hybrid를 &quot;혼종&quot;이라고 일관되게 번역해야겠지만 일반 번역기를 이를 해내지 못합니다.&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;둘째로 게임 내 캐릭터들의 대사의 경우 청자와의 관계, 캐릭터의 성격 등에 따라 알맞은 어조로 번역되어야 하는데 이 역시 어렵습니다. 일반적으로 존댓말을 쓰지만, 친한 관계인 B에게만 반말로 이야기하는 &lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;캐릭터 A의 대사를 자동 번역하는 상황을 생각해보면 되겠습니다. 일반 번역기에서는 말투가 제멋대로 섞인 번역결과가 나오게 되겠죠. 이는 영화나 연극 스크립트 등을 번역할때도 자주 마주하는 문제입니다.&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;마지막으로 게임 텍스트는 그 특성상 다양한 서식지정자나 채움문자(placeholder) 등과 함께 쓰이는 경우가 많은데 이를 그대로 유지하면서 번역하기가 쉽지 않습니다. 텍스트에 강조를 표시하거나 색상을 바꾸기 위해 게임마다 고유한 서식지정자를 사용하기도 하고 (예시: &quot;&lt;span style=&quot;background-color: #ffffe8;&quot;&gt;&lt;b&gt;[Surprise 55]&lt;/b&gt;The crystal is free of defenders.&lt;b&gt;[/Surprise]&lt;/b&gt; Our warriors can destroy it whenever you are ready!&lt;/span&gt;&quot;), 상황에 따라 들어가는 텍스트가 바뀌는 경우를 대비해 채움문자를 사용하기도 합니다 (예시: &quot;&lt;span style=&quot;background-color: #ffffe8;&quot;&gt;Good morning &lt;b&gt;{PlayerName}&lt;/b&gt;, what can I do for you?&lt;/span&gt;&quot;). 이런 서식지정자나 채움문자는 정확하게 원본형태를 유지해야지만 프로그램에서 이를 올바르게 처리할 수 있는데, 번역기들은 이 마저도 번역 대상으로 간주하거나 아니면 번역 시에 빼버리는 만행을 저지르곤 합니다. 이래서야 자동 번역을 전혀 써먹을수가 없죠.&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;하지만 LLM은 적당한 예제를 몇 가지 보여주면 예제와 비슷하게 작업을 해내는 In Context Learning(ICL)이라는 능력을 가지고 있기 때문에 많은 품을 들이지 않고도 위의 3가지 문제를 해결하는게 가능합니다(당연히 완벽하지는 못하고 다양한 하이퍼파라미터 조합을 탐색해야하는 등 번거로운 일들도 있지만 별도의 시스템을 새로 개발하는것보다는 쉽습니다).&lt;/span&gt;&lt;/p&gt;
&lt;h3 style=&quot;text-align: justify;&quot; data-ke-size=&quot;size23&quot;&gt;&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;대상 게임: Starcraft Mass Recall에 포함된 Dark Vengeance&lt;/span&gt;&lt;/h3&gt;
&lt;p style=&quot;text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;대상으로 잡은 것은 스타크래프트2에 나온 스타1 시절 캠페인 모드인 &lt;a href=&quot;https://www.sc2mapster.com/projects/starcraft-mass-recall&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;Starcraft Mass Recall&lt;/a&gt;, 그 중에서 Dark Vengeance입니다. 이걸 설정한 이유는 스타1~2 (준) 공식 캠페인 중 유일하게 플레이해보지 않은거라 한번 해보고 싶다는 생각을 가지고 있었는데 막상 해보니 영어의 압박에 즐겜하기가 쉽지 않았기 때문이죠~ 옛날 같으면 그냥 참고 했겠지만 현지화 잘된 스타2 캠페인들을 하다가 Full text English를 보려고 하니 도저히 참을 수가 없었습니다.&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;다행히도 스타1~2는 내부 구조가 잘 파헤쳐져 있고, 특히 스타2의 경우 유저가 손쉽게 Mod를 제작할 수 있게 게임 차원에서 원할하게 지원하고 있기 때문에 ICL에 사용할 텍스트를 추출하고 텍스트/폰트 등을 교체하는게 쉽다는것도 선택의 요인 중 하나였습니다.&lt;/p&gt;
&lt;h3 style=&quot;text-align: justify;&quot; data-ke-size=&quot;size23&quot;&gt;&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;대상 모델&lt;/span&gt;&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;a href=&quot;https://huggingface.co/meta-llama/Meta-Llama-3.1-8B-Instruct&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;Llama3.1 8B Instruct&lt;/a&gt;: Meta(페이스북)에서 공개한 LLM. 사실상 오픈 LLM의 대표 주자입니다. 다만 학습 데이터가 영어에 치우쳐져 있어 한국어 능력이 다소 떨어진다고 알려져 있습니다.&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://huggingface.co/MLP-KTLim/llama-3-Korean-Bllossom-8B&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;Bllossom 8B&lt;/a&gt; (Instruct): 서울과기대, 테디썸, 연세대 언어자원 연구실이 협업해 공개한 LLM. Llama3에 한국어 어휘를 확장하여 한국어 능력을 강화시킨 버전입니다.&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://huggingface.co/Qwen/Qwen2-7B-Instruct&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;Qwen2 7B Instruct&lt;/a&gt;: 중국 알리바바에서 공개한 LLM. Llama가 미국의 대표 오픈 LLM이라면 Qwen은 중국의 대표 오픈 LLM이라 볼 수 있습니다. 학습 데이터가 영어/중국어에 치우쳐져 있어 한국어가 다소 약하다고 합니다.&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://huggingface.co/beomi/Solar-Ko-Recovery-11B&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;Solar Ko Recovery 11B&lt;/a&gt; (PLM): 이준범(Beomi)님이 학습시킨 LLM으로 Upstage에서 공개한 Solar 모델에 한국어 능력을 강화시킨 버전입니다. 이 모델은 나머지 모델들과 다르게 인스트럭션 튜닝이 되지 않은 모델입니다. 그래서 자연어로 구성된 지시사항을 잘 이해하지 못하고 텍스트를 이어서 생성하는 능력만 가지고 있습니다.&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://huggingface.co/mistralai/Mistral-Nemo-Instruct-2407&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;Mistral 12B Instruct&lt;/a&gt;: 프랑스의 Mistral과 NVIDIA가 협업하여 개발한 LLM. 다국어를 지원한다고 알려져 있습니다.&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://huggingface.co/maywell/EXAONE-3.0-7.8B-Instruct-Llamafied&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;EXAONE 3.0 8B Instruct&lt;/a&gt;: LG AI Research에서 개발한 LLM. 한국어와 영어를 중점적으로 학습했다고 합니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p style=&quot;text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;테스트 시점에서 한국어가 가능한 최신 모델들을 다양하게 모아봤습니다. 데스크톱 GPU의 VRAM이 16GB인 관계로 FP8로 inference를 돌린다해도 12B 정도가 가능한 최대 크기인듯하더라구요. 더 큰 모델을 쓰면 더 좋았겠지만 이 정도 지점에서 현실적으로 타협했습니다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;프롬프팅 포맷&lt;/h3&gt;
&lt;p style=&quot;text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;번역 요청을 내릴때 어떤 형식으로 요청을 내리는지에 따라 모델이 동일하더라도 결과 품질이 크게 달라질 수 있습니다. 따라서 다양한 포맷을 시도해보며 제일 말을 잘 듣는 포맷을 찾는게 필수적입니다. 이번 실험에서 시도한 포맷은 다음과 같이 3종류입니다. A1~A&lt;i&gt;n&lt;/i&gt;은 영어 문장, B1~B&lt;i&gt;n&lt;/i&gt;은 각 영어 문장에 대응하는 한국어 번역 문장입니다. 그리고 A&lt;i&gt;x&lt;/i&gt;는 우리가 실제로 번역하고 대상 영어 문장이구요. A1~An이 B1~Bn으로 번역된다는 사실을 먼저 모델에게 예제로 보여주고 A&lt;i&gt;x&lt;/i&gt;를 번역하라고 시킬 것입니다. Instruction 모델에게 전달하는 입력 텍스트는 크게 System, User, Assistant의 3가지 역할로 구분되는데, 아래에서 이 역할에 해당하는 텍스트는 각각 &lt;b&gt;&amp;lt;System&amp;gt;&lt;/b&gt;, &lt;b&gt;&amp;lt;User&amp;gt;&lt;/b&gt;, &lt;b&gt;&amp;lt;Assistant&amp;gt;&lt;/b&gt;라는 접두사를 붙여서 표시했습니다. 흔히 System은 AI모델 전체에게 내리는 지시사항을 나타내는 역할을, User는 유저가 AI모델에게 요청한 텍스트, Assistant는 AI모델이 유저에게 응답한 텍스트를 나타내는 역할을 합니다.&lt;/p&gt;
&lt;h4 style=&quot;text-align: justify;&quot; data-ke-size=&quot;size20&quot;&gt;Default&lt;/h4&gt;
&lt;blockquote data-ke-style=&quot;style3&quot;&gt;&lt;b&gt;&amp;lt;System&amp;gt;&lt;/b&gt; Please translate the following English text to Korean.&lt;br /&gt;&lt;b&gt;&amp;lt;User&amp;gt;&lt;/b&gt; Input: A1&lt;br /&gt;Output: B1&lt;br /&gt;&lt;br /&gt;Input: A2&lt;br /&gt;Output: B2&lt;br /&gt;&lt;br /&gt;&lt;b&gt;...&lt;/b&gt;&lt;br /&gt;Input: A&lt;b&gt;&lt;i&gt;n&lt;/i&gt;&lt;/b&gt;&lt;br /&gt;Output: B&lt;b&gt;&lt;i&gt;n&lt;br /&gt;&lt;/i&gt;&lt;/b&gt;&lt;br /&gt;Input: A&lt;b&gt;&lt;i&gt;x&lt;/i&gt;&lt;/b&gt;&lt;br /&gt;Output:&lt;/blockquote&gt;
&lt;p style=&quot;text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;가장 기본적인 형태는 System 프롬프트로 번역하라는 지시사항을 주고, User 입력으로 Input, Output 예제를 차례로 준 뒤 마지막으로 우리가 번역하길 원하는 Input을 줍니다. 그러면 다음 턴에서 Assistant는 Output: 뒤에 이어질 텍스트를 생성하겠죠.&lt;/p&gt;
&lt;h4 style=&quot;text-align: justify;&quot; data-ke-size=&quot;size20&quot;&gt;Alternative&lt;/h4&gt;
&lt;blockquote style=&quot;background-color: #fcfcfc; color: #666666; text-align: left;&quot; data-ke-style=&quot;style3&quot;&gt;&lt;b&gt;&amp;lt;System&amp;gt;&lt;/b&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;Please translate the following English text to Korean.&lt;br /&gt;&lt;b&gt;&amp;lt;User&amp;gt;&lt;/b&gt;&lt;span&gt; &lt;/span&gt;A1&lt;br /&gt;&lt;b&gt;&amp;lt;Assistant&amp;gt;&lt;/b&gt; B1&lt;br /&gt;&lt;b&gt;&amp;lt;User&amp;gt;&lt;/b&gt; A2&lt;br /&gt;&lt;b&gt;&amp;lt;Assistant&amp;gt;&lt;/b&gt; B2&lt;br /&gt;&lt;b&gt;...&lt;/b&gt;&lt;br /&gt;&lt;b&gt;&amp;lt;User&amp;gt;&lt;/b&gt; A&lt;b&gt;&lt;i&gt;n&lt;/i&gt;&lt;/b&gt;&lt;br /&gt;&lt;b&gt;&amp;lt;Assistant&amp;gt;&lt;/b&gt; B&lt;b&gt;&lt;i&gt;n&lt;/i&gt;&lt;/b&gt;&lt;br /&gt;&lt;b&gt;&amp;lt;User&amp;gt;&lt;/b&gt; A&lt;b&gt;&lt;i&gt;x&lt;/i&gt;&lt;/b&gt;&lt;/blockquote&gt;
&lt;p style=&quot;text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;형태를 바꿔서 Input, Output이라는 구질구질한 접두어를 생략하고, User턴에 Input을, Assistant턴에 Output을 번걸아가며 넣었습니다. 그리고 마지막 User턴에 우리가 번역하길 원하는 Input을 줍니다. 그러면 다음 턴에서 Assistant는 Input에 대한 번역 결과를 생성할 겁니다. 주목해야할 사항은 중간에 들어가는 Assistant턴들입니다. 이 턴은 실제로 모델이 생성한 결과는 아니지만 Assistant의 역할로 모델에게 입력되므로, 모델은 유저가 A1을 입력했을때 자신이 B1이라고 답했고, 유저가 A2를 입력했을때 자신이 B2라고 답했다고 받아들이게 됩니다. 이는 오늘날 널리 쓰이는 decoder only 기반의 모델들은 태생적으로 기억력이라는게 전혀 없어서 다음 텍스트을 생성할때 오직 이전의 입출력 결과만을 기준으로 현재 상황을 판단하기 때문입니다. (영화 메멘토의 주인공마냥 기억상실증에 걸려서, 주변에 남겨진 기록들만 가지고 자신이 과거에 무엇을 했는지 판단하는 것이라고 보면 되겠습니다.) 따라서 위와 같이 프롬프트를 넣어주게 되면 모델은 실제로 그렇게 답한 적은 없지만 마치 과거에 자신이 그렇게 했다고 여기고 그런 상황에서 A&lt;i&gt;x&lt;/i&gt;가 입력되었을때 적절한 B&lt;i&gt;x&lt;/i&gt;를 생성하고자 노력하게 되겠죠.&lt;/p&gt;
&lt;h4 style=&quot;text-align: justify;&quot; data-ke-size=&quot;size20&quot;&gt;Numbered&lt;/h4&gt;
&lt;blockquote style=&quot;background-color: #fcfcfc; color: #666666; text-align: left;&quot; data-ke-style=&quot;style3&quot;&gt;&lt;b&gt;&amp;lt;System&amp;gt;&lt;/b&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;Please translate the following English text to Korean.&lt;br /&gt;&lt;b&gt;&amp;lt;User&amp;gt;&lt;/b&gt;&lt;span&gt; [1]&lt;/span&gt; A1&lt;br /&gt;[2] A2&lt;br /&gt;&lt;b&gt;...&lt;/b&gt;&lt;br /&gt;[&lt;b&gt;&lt;i&gt;n&lt;/i&gt;&lt;/b&gt;] A&lt;b&gt;&lt;i&gt;n&lt;/i&gt;&lt;/b&gt;&lt;br /&gt;[&lt;b&gt;&lt;i&gt;n+1&lt;/i&gt;&lt;/b&gt;] A&lt;b&gt;&lt;i&gt;x&lt;/i&gt;&lt;/b&gt;&lt;br /&gt;&lt;b&gt;&amp;lt;Assistant&amp;gt;&lt;/b&gt; [1] B1&lt;br /&gt;[2] B2&lt;br /&gt;&lt;b&gt;...&lt;/b&gt;&lt;br /&gt;[&lt;b&gt;&lt;i&gt;n&lt;/i&gt;&lt;/b&gt;] B&lt;b&gt;&lt;i&gt;n&lt;/i&gt;&lt;/b&gt;&lt;br /&gt;[&lt;b&gt;&lt;i&gt;n+1&lt;/i&gt;&lt;/b&gt;]&lt;/blockquote&gt;
&lt;p style=&quot;text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;이 포맷에서는 Input과 Output이 교대로 들어가는 대신 Input이 먼저 쭉 들어가고, Output이 쭉 나오는 형태로 구성해봤습니다. 마찬가지로 중간에 삽입된 Assistant 턴 덕분에 모델은 유저가 A1~A&lt;i&gt;n&lt;/i&gt;, A&lt;i&gt;x&lt;/i&gt;을 입력했을때 자신이 B1~B&lt;i&gt;n&lt;/i&gt;이라고 답했다고 받아들이게 됩니다. 그리고 생성 작업을 계속 이어서 그 다음 할일인 B&lt;i&gt;x&lt;/i&gt;를 생성하는 일을 자연스레 수행하게 될 겁니다.&lt;/p&gt;
&lt;h3 style=&quot;text-align: justify;&quot; data-ke-size=&quot;size23&quot;&gt;예제 문장 선택하기&lt;/h3&gt;
&lt;p style=&quot;text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;프롬프트를 구성할때 사용할 영어, 한국어 문장 예제 문장(A&lt;i&gt;n&lt;/i&gt;, B&lt;i&gt;n&lt;/i&gt;)을 어떻게 선택하느냐도 성능에 중요한 영향을 미칠 겁니다. 직관적으로 생각해볼 때 번역하려고 하는 문장 Ax와 유사한 A&lt;i&gt;n&lt;/i&gt;들을 가져오는게 실제 번역 작업에 도움이 될 것이라는건 자명합니다. 유사한 문장을 검색하는 방법에는 크게 키워드 검색(Keyword Search)과 벡터 검색(Vector Search, =시맨틱 검색, Semantic Search)이 있는데요, 최근 딥러닝 방식 검색이 대두되면서 검색하면 일단 벡터 검색부터 떠올리는 경우가 많습니다. 그런데 지금과 같은 상황에서는 키워드 검색이 더 유용합니다. 우리는 특정 단어가 어떻게 번역되는지 예제 문장을 보는게 필요하므로 단어별 일치가 잘 되는 유사 문장을 찾아야하기 때문입니다. 그래서 아주 전통적인 BM25 검색을 사용해서 예제 문장을 찾기로 했습니다.&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;예제 문장은 10개 정도 보여주면 적당할 것이라고 생각해서 개수를 10으로 고정했습니다(물론 엄밀하게 하려면 이것도 바꿔가보면서 실험해봐야겠지만 어디 논문 낼것도 아니니깐...). 근데 단순히 검색 한 번 해서 Top 10 문장만 뽑아오면 다 될까요? 테스트를 위해 &quot;Focus on destroying targets that endanger the Odin, like Siege Tanks, Banshees, and Battlecruisers.&quot; 라는 문장을 입력으로 Top 10을 뽑아보면 다음과 같습니다.&lt;/p&gt;
&lt;table class=&quot;std-table&quot; data-ke-align=&quot;alignLeft&quot;&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;th&gt;순위&lt;/th&gt;
&lt;th&gt;영어&lt;/th&gt;
&lt;th&gt;한국어&lt;/th&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;th&gt;1&lt;/th&gt;
&lt;td&gt;Laser Targeting System Siege Tank&lt;/td&gt;
&lt;td&gt;레이저 조준 시스템 공성 전차&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;th&gt;2&lt;/th&gt;
&lt;td&gt;Focused Siege&lt;/td&gt;
&lt;td&gt;집중 공성 공격&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;th&gt;3&lt;/th&gt;
&lt;td&gt;Siege Tank (Sieged)&lt;/td&gt;
&lt;td&gt;공성 전차 (공성 모드)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;th&gt;4&lt;/th&gt;
&lt;td&gt;Siege Tank Sieged&lt;/td&gt;
&lt;td&gt;공성 전차 공성 모드&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;th&gt;5&lt;/th&gt;
&lt;td&gt;{Siege Tank} - You may now build {Siege Tank} from the {Factory}.&lt;/td&gt;
&lt;td&gt;{공성 전차} - 이제 {군수공장}에서 {공성 전차}를 생산할 수 있습니다.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;th&gt;6&lt;/th&gt;
&lt;td&gt;{Siege Tank} -- You may now build a {Siege Tank} from the {Factory}.&lt;/td&gt;
&lt;td&gt;{공성 전차} -- 이제 {군수공장}에서 {공성 전차}를 생산할 수 있습니다.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;th&gt;7&lt;/th&gt;
&lt;td&gt;Siege Tank Merc Veterancy Aura Target Bonus&lt;/td&gt;
&lt;td&gt;공성 전차 용병 실전 경험 오라 대상 보너스&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;th&gt;8&lt;/th&gt;
&lt;td&gt;Commando Siege Tank&lt;/td&gt;
&lt;td&gt;코만도 공성 전차&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;th&gt;9&lt;/th&gt;
&lt;td&gt;CRUCIO SIEGE TANK&lt;/td&gt;
&lt;td&gt;크루시오 공성 전차&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;th&gt;10&lt;/th&gt;
&lt;td&gt;Hover Siege Tank&lt;/td&gt;
&lt;td&gt;체공 공성 전차&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p style=&quot;text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;키워드 검색 특성 상 드물게 등장하는 단어가 많이 겹칠수록 높은 점수를 받아서 검색 순위 상위에 위치하게 됩니다. 이 때문에 Siege Tank라는 표현이 높은 점수를 받게 되어서(고유명사지만 혼자서 2단어입니다), 검색 결과 상위 10개 안에 모두 Siege Tank와 관련된 단어만 나왔습니다. 모델이 이걸 예제로 보고 번역을 실시하면 Siege Tank는 공성 전차라고 바르게 번역하겠지만, Odin, Banshees, Battlecruisers는 어떻게 번역해야할지 모르겠죠. 그래서 검색 1번에 모든 결과를 가져오는 대신 그룹을 나눠서 여러 번 결과를 가져오는 방법을 떠올려 봤습니다. X와 유사한 문장 n개를, 총 g개의 그룹으로 가져오는 절차는 다음과 같습니다.&lt;/p&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;X를 검색어로 던져서 n/g개의 예제를 가져온다.&lt;/li&gt;
&lt;li&gt;가져온 예제 문장에 포함된 단어들은 X에서 제거한다.&lt;/li&gt;
&lt;li&gt;1~2번을 총 g번 반복한다.&lt;/li&gt;
&lt;li&gt;가져온 예제 문장을 모아서 총 n개의 예제 문장을 확보한다.&lt;/li&gt;
&lt;/ol&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 절차를 통해 3개 그룹으로 총 10개의 예제 문장을 가져오면 다음과 같이 되겠습니다.&lt;/p&gt;
&lt;table class=&quot;std-table&quot; data-ke-align=&quot;alignLeft&quot;&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;th&gt;그룹-순위&lt;/th&gt;
&lt;th&gt;영어&lt;/th&gt;
&lt;th&gt;한국어&lt;/th&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;th&gt;1-1&lt;/th&gt;
&lt;td&gt;Laser Targeting System Siege Tank&lt;/td&gt;
&lt;td&gt;레이저 조준 시스템 공성 전차&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;th&gt;1-2&lt;/th&gt;
&lt;td&gt;Focused Siege&lt;/td&gt;
&lt;td&gt;집중 공성 공격&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;th&gt;1-3&lt;/th&gt;
&lt;td&gt;Siege Tank (Sieged)&lt;/td&gt;
&lt;td&gt;공성 전차 (공성 모드)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;th&gt;2-1&lt;/th&gt;
&lt;td&gt;The Odin has been destroyed.&lt;/td&gt;
&lt;td&gt;오딘이 파괴되었습니다.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;th&gt;2-2&lt;/th&gt;
&lt;td&gt;Sir, I'm picking up Dominion battlecruisers on an intercept course with the Odin.&lt;/td&gt;
&lt;td&gt;대장님, 자치령 전투순양함이 오딘 쪽으로 향하는 걸 감지했습니다.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;th&gt;2-3&lt;/th&gt;
&lt;td&gt;So Swann, what're the chances we can build something like that Odin?&lt;/td&gt;
&lt;td&gt;아... 스완, 우리도 오딘 같은 걸 자체 생산할 수 있을까요?&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;th&gt;2-4&lt;/th&gt;
&lt;td&gt;A Molten Salamander has emerged from the lava! Conservationists say they're endangered, but who's in danger now?&lt;/td&gt;
&lt;td&gt;용암 도롱뇽이 용암 속에서 나타났습니다! 환경 보호론자들은 이것이 멸종 위기종이라 하지만, 지금은 누가 진짜 멸종 위기일까요?&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;th&gt;3-1&lt;/th&gt;
&lt;td&gt;BANSHEE&lt;/td&gt;
&lt;td&gt;밴시&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;th&gt;3-2&lt;/th&gt;
&lt;td&gt;Has Banshees&lt;/td&gt;
&lt;td&gt;밴시 있음&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;th&gt;3-3&lt;/th&gt;
&lt;td&gt;Banshee&lt;/td&gt;
&lt;td&gt;밴시&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p style=&quot;text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;첫번째 검색 결과에서는 1-1 ~ 1-3 문장을 가져옵니다. 이후 검색 결과에 포함된 Siege Tank 등의 단어를 검색어에서 제거하고 다시 검색을 수행하면 2-1 ~ 2-4의 결과가 나옵니다. 마찬가지로 2-1 ~ 2-4에 포함된 단어들을 검색어에서 제거하고 다시 검색을 수행하면 Banshee만 남아서 3-1 ~ 3-3과 같은 결과를 얻습니다. 이렇게 10개의 예제 문장을 구성하면 모델이 Siege Tank뿐만 아니라 Odin, Banshee, Battlecruiser와 같은 고유 명사도 어떻게 번역해야하는지 잘 알수 있을 겁니다.&lt;/p&gt;
&lt;table class=&quot;std-table&quot; data-ke-align=&quot;alignLeft&quot;&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;th&gt;그룹-순위&lt;/th&gt;
&lt;th&gt;영어&lt;/th&gt;
&lt;th&gt;한국어&lt;/th&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;th&gt;1-1&lt;/th&gt;
&lt;td&gt;Laser Targeting System Siege Tank&lt;/td&gt;
&lt;td&gt;레이저 조준 시스템 공성 전차&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;th&gt;1-2&lt;/th&gt;
&lt;td&gt;Focused Siege&lt;/td&gt;
&lt;td&gt;집중 공성 공격&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;th&gt;2-1&lt;/th&gt;
&lt;td&gt;The Odin has been destroyed.&lt;/td&gt;
&lt;td&gt;오딘이 파괴되었습니다.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;th&gt;2-2&lt;/th&gt;
&lt;td&gt;All of your banshees have been destroyed.&lt;/td&gt;
&lt;td&gt;모든 밴시가 파괴되었습니다.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;th&gt;3-1&lt;/th&gt;
&lt;td&gt;Battlecruiser&lt;/td&gt;
&lt;td&gt;전투순양함&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;th&gt;3-2&lt;/th&gt;
&lt;td&gt;Brood Lords are particularly vulnerable to flying units like Battlecruisers, Vikings, and Wraiths.&lt;/td&gt;
&lt;td&gt;무리 군주는 전투순양함, 바이킹, 망령 같은 공중 유닛에 특히 약합니다.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;th&gt;4-1&lt;/th&gt;
&lt;td&gt;A Molten Salamander has emerged from the lava! Conservationists say they're endangered, but who's in danger now?&lt;/td&gt;
&lt;td&gt;용암 도롱뇽이 용암 속에서 나타났습니다! 환경 보호론자들은 이것이 멸종 위기종이라 하지만, 지금은 누가 진짜 멸종 위기일까요?&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;th&gt;4-2&lt;/th&gt;
&lt;td&gt;Units on higher ground can attack without endangering themselves. To counter this, gain vision of the attackers by running up ramps or bringing air units.&lt;/td&gt;
&lt;td&gt;고지대에 있는 유닛들은 공격을 받지 않으면서 적을 공격할 수 있습니다. 이에 대응하려면 비탈길로 올라가거나 공중 유닛을 데려와서 시야를 확보해야 합니다.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;th&gt;5-1&lt;/th&gt;
&lt;td&gt;Laser Targeting System Banshee&lt;/td&gt;
&lt;td&gt;레이저 조준 시스템 밴시&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;th&gt;5-2&lt;/th&gt;
&lt;td&gt;Nothin' like getting' TANKED with your buddies!&lt;/td&gt;
&lt;td&gt;친구들하고 폭탄 돌리는 것만 한 게 있나!&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그룹 개수를 5개로 늘리면 위와 같이 될거구요. 너무 많이 늘리게 되면 검색에 이상한 결과들이 잡힐수도 있으니 실험을 통해 적당한 값을 잡는게 중요해보이겠습니다.&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;검색시에 추가로 고려하면 또 좋은 것이 바로 화자(Speaker) 정보입니다. 똑같은 Hi?라는 영어 문장이라도 누가 말했는지에 따라 한국어로는 &quot;안녕?&quot;, &quot;안녕하시오?&quot;, &quot;안녕하십니까?&quot; 등 다양하게 옮겨질 수 있습니다. 그래서 문서를 색인하는 단계에서 화자 정보라는 카테고리를 주고, 옵션에 따라서 해당 화자 정보 내에서만 검색을 수행할 수 있게 하였습니다. 그래서 똑같이 Hi?라는 문장을 검색해도 이 대사가 Zeratul의 것이면 Zeratul의 대사 내에서만 Hi를 검색하고, Raynor의 것이라면 Raynor의 대사 내에서만 Hi를 검색하는 식으로 하여 검색 결과가 달라지게 하였습니다.&lt;/p&gt;
&lt;h3 style=&quot;text-align: justify;&quot; data-ke-size=&quot;size23&quot;&gt;실험&lt;/h3&gt;
&lt;p style=&quot;text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;번역 성능을 평가하기 위해서는 평가 데이터셋이 있어야합니다. 이를 위해 스타크래프트2 내의 LocalizationData에서 영어 텍스트와 한국어 텍스트 60183쌍을 추출했습니다. 이 중 세 단어 이상으로 구성된 문장 2000쌍을 랜덤하게 골라 평가셋으로 설정했고 나머지는 ICL에 사용할 Few shot 예제 문장으로 설정했습니다. 평가셋에는 Few-shot 예제 문장에 있는 문장과 너무 동일한 문장(문장 간 차이가 단어 한 개 이하인 경우)은 들어가지 않도록 배제했습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;추출된 Few shot 예제 문장과 평가셋 일부를 보여드리자면 다음과 같습니다.&lt;/p&gt;
&lt;div class=&quot;table-overflow&quot;&gt;
&lt;table class=&quot;std-table&quot; style=&quot;min-width: 36em;&quot; data-ke-align=&quot;alignLeft&quot;&gt;&lt;caption&gt;Few-shot 예제 문장으로 사용되는 데이터&lt;/caption&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;th&gt;src&lt;/th&gt;
&lt;th&gt;id&lt;/th&gt;
&lt;th&gt;speaker&lt;/th&gt;
&lt;th&gt;en&lt;/th&gt;
&lt;th&gt;ko&lt;/th&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;th&gt;campaigns/&lt;br /&gt;swarmstory.sc2campaign&lt;/th&gt;
&lt;th&gt;Conversation/&lt;br /&gt;zMission_Zerus02/Line00158&lt;/th&gt;
&lt;td&gt;Kerrigan&lt;/td&gt;
&lt;td&gt;Here they come.&lt;/td&gt;
&lt;td&gt;놈들이 온다.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;th&gt;campaigns/&lt;br /&gt;swarmstory.sc2campaign&lt;/th&gt;
&lt;th&gt;UserData/&lt;br /&gt;CampaignTips/Siege Tanks_Name&lt;/th&gt;
&lt;td&gt;-&lt;/td&gt;
&lt;td&gt;Siege Tanks&lt;/td&gt;
&lt;td&gt;공성전차&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;th&gt;campaigns/&lt;br /&gt;liberty.sc2campaign&lt;/th&gt;
&lt;th&gt;Achievement/&lt;br /&gt;Name/AiurChef_05&lt;/th&gt;
&lt;td&gt;-&lt;/td&gt;
&lt;td&gt;Allez Cuisine!&lt;/td&gt;
&lt;td&gt;요리의 모든 것!&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;th&gt;mods/&lt;br /&gt;liberty.sc2mod&lt;/th&gt;
&lt;th&gt;Skin/Info/&lt;br /&gt;TempestIhanrii&lt;/th&gt;
&lt;td&gt;-&lt;/td&gt;
&lt;td&gt;The dampening effect of the Ihan-rii tempest's stone surface enhances its ability to focus the incredible power output of its kinetic matrix.&lt;/td&gt;
&lt;td&gt;이한 리 폭풍함 석재 장갑의 완충 효과는 키네틱 매트릭스의 무시무시한 정격 출력 집중 능력을 향상시켜 줍니다.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;th&gt;mods/&lt;br /&gt;starcoop/starcoop.sc2mod&lt;/th&gt;
&lt;th&gt;Button/&lt;br /&gt;Tooltip/ACSwannEngineeringBayUpgraded&lt;/th&gt;
&lt;td&gt;Tooltip&lt;/td&gt;
&lt;td&gt;Contains upgrades for structures. &amp;lt;n/&amp;gt;&amp;lt;n/&amp;gt;&amp;lt;c val=&quot;ffff8a&quot;&amp;gt;Enables:&amp;lt;/c&amp;gt;&amp;lt;n/&amp;gt;- Spinning Dizzys from SCVs&lt;/td&gt;
&lt;td&gt;구조물을 업그레이드합니다.&amp;lt;n/&amp;gt;&amp;lt;n/&amp;gt;&amp;lt;c val=&quot;ffff8a&quot;&amp;gt;사용 가능:&amp;lt;/c&amp;gt;&amp;lt;n/&amp;gt;- 회전 화포 (건설로봇 건설)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;th&gt;campaigns/&lt;br /&gt;voidstory.sc2campaign&lt;/th&gt;
&lt;th&gt;Conversation/&lt;br /&gt;Convo_Communicator/Line00078&lt;/th&gt;
&lt;td&gt;Artanis&lt;/td&gt;
&lt;td&gt;Zeratul&amp;nbsp;found&amp;nbsp;me.&amp;nbsp;It&amp;nbsp;was&amp;nbsp;there&amp;nbsp;that&amp;nbsp;he...&amp;nbsp;he...&lt;/td&gt;
&lt;td&gt;그때&amp;nbsp;제라툴이&amp;nbsp;날&amp;nbsp;발견했고,&amp;nbsp;그곳에서&amp;nbsp;그는...&amp;nbsp;그는...&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;th&gt;campaigns/&lt;br /&gt;libertystory.sc2campaign&lt;/th&gt;
&lt;th&gt;Conversation/&lt;br /&gt;BridgeTychus/Line00003&lt;/th&gt;
&lt;td&gt;Raynor&lt;/td&gt;
&lt;td&gt;Well,&amp;nbsp;so&amp;nbsp;ya&amp;nbsp;thinking&amp;nbsp;about&amp;nbsp;giving&amp;nbsp;up&amp;nbsp;this&amp;nbsp;life&amp;nbsp;of&amp;nbsp;luxury&amp;nbsp;and&amp;nbsp;becoming&amp;nbsp;a&amp;nbsp;professional&amp;nbsp;broadcaster?&lt;/td&gt;
&lt;td&gt;자네,&amp;nbsp;이러다가&amp;nbsp;다&amp;nbsp;집어치우고&amp;nbsp;전문&amp;nbsp;방송인으로&amp;nbsp;나가는&amp;nbsp;거&amp;nbsp;아닌가?&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;th&gt;campaigns/&lt;br /&gt;swarmstory.sc2campaign&lt;/th&gt;
&lt;th&gt;Conversation/&lt;br /&gt;zMission_Korhal03/Line00021&lt;/th&gt;
&lt;td&gt;Mengsk&lt;/td&gt;
&lt;td&gt;This&amp;nbsp;is&amp;nbsp;my&amp;nbsp;world,&amp;nbsp;Kerrigan.&amp;nbsp;You&amp;nbsp;are&amp;nbsp;not&amp;nbsp;welcome&amp;nbsp;here!&lt;/td&gt;
&lt;td&gt;여긴&amp;nbsp;내&amp;nbsp;세상이다,&amp;nbsp;케리건.&amp;nbsp;너는&amp;nbsp;불청객일&amp;nbsp;뿐이야.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;th&gt;campaigns/&lt;br /&gt;voidstory.sc2campaign&lt;/th&gt;
&lt;th&gt;Conversation/&lt;br /&gt;pConvo_TaldarimHero/Line00143&lt;/th&gt;
&lt;td&gt;TaldarimHero&lt;/td&gt;
&lt;td&gt;[Happy&amp;nbsp;80]Such&amp;nbsp;hubris.[/Happy]&amp;nbsp;[Surprise&amp;nbsp;60]To&amp;nbsp;think&amp;nbsp;that&amp;nbsp;they&amp;nbsp;could&amp;nbsp;control[/Surprise]&amp;nbsp;what&amp;nbsp;[Angry&amp;nbsp;60]they&amp;nbsp;did&amp;nbsp;not&amp;nbsp;understand.[/Angry]&lt;/td&gt;
&lt;td&gt;[Happy&amp;nbsp;80]오만하기도&amp;nbsp;하지.[/Happy]&amp;nbsp;[Angry&amp;nbsp;60]알지도&amp;nbsp;못하는&amp;nbsp;걸[/Angry]&amp;nbsp;[Surprise&amp;nbsp;60]통제할&amp;nbsp;수&amp;nbsp;있다고&amp;nbsp;믿다니.[/Surprise]&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;/div&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;평가셋은 &lt;a href=&quot;https://docs.google.com/spreadsheets/d/1BiTiMDSm3t-dAIOLMy5jckIL9P6L8G977w4bXnAtE7E/edit?gid=521420125#gid=521420125&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;구글 스프레드시트 페이지&lt;/a&gt;에서 살펴볼 수 있습니다.&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;평가 척도로는 자동 번역을 평가할때 흔히 쓰이는 ROUGE를 사용하기로 했습니다. ROUGE에 대해 자세히 알고 싶으시다면 &lt;a href=&quot;https://huffon.github.io/2019/12/07/rouge/&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;이 글&lt;/a&gt;을 읽어보시길 추천드립니다. ROUGE에서는 모델이 번역해낸 문장이 정답 문장과 얼마나 일치하는지를 기계적으로 평가하는데, 일반적으로 단어 단위로 일치여부를 계산합니다. 그런데 단어 단위의 평가는 영어 문장에는 잘 통하지만 한국어에서는 어미/조사 등의 문제 때문에 잘 통하지 않습니다(e.g. 했습니다/하였습니다, 레이너가/레이너는 : 이런 경우는 의미적으로 동일하거나 유사하지만 단어 단위의 비교에서는 완전히 다른것으로 판단됩니다). 그래서 최종적으로는 글자를 단위로 한 Chr ROUGE와 형태소를 단위로 한 Morph ROUGE를 사용했습니다. 특히 Morph ROUGE를 계산할때는 &lt;span style=&quot;background-color: #ffffe0;&quot;&gt;&lt;b&gt;[Happy 80]&lt;/b&gt;&lt;/span&gt;이나 &lt;span style=&quot;background-color: #ffffe0;&quot;&gt;&lt;b&gt;&amp;lt;c val=&quot;ffff8a&quot;&amp;gt;&lt;/b&gt;&lt;/span&gt;같은 서식지정자 혹은 채움문자는 모두 한 토큰으로 분석되도록 형태소 분석기의 옵션을 손봤습니다. 이런 서식지정자나 채움문자는 한 글자라도 달라지면 그 의미가 아예 달라지므로 토큰 내 문자열이 완전 일치한 경우만 맞았다고 판단할 수 있기 때문입니다.&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;&gt;본격적인 실험에 앞서 베이스라인이 어느정도인지 확인해봐야겠죠? 번역 예제 데이터를 전혀 주지 않은 경우(Zero-shot)와 파파고와 DeepL 번역기를 사용했을 때의 번역 성능을 측정해보았습니다.&lt;/p&gt;
&lt;div class=&quot;table-overflow&quot;&gt;
&lt;table class=&quot;std-table&quot; style=&quot;min-width: 48em;&quot; data-ke-align=&quot;alignLeft&quot;&gt;&lt;caption&gt;모델별 Zero-shot 성능 &amp;amp; 번역기 베이스라인 성능&lt;/caption&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;th&gt;모델&lt;/th&gt;
&lt;th&gt;Chr ROUGE-2&lt;/th&gt;
&lt;th&gt;Chr ROUGE-3&lt;/th&gt;
&lt;th&gt;Chr ROUGE-L&lt;/th&gt;
&lt;th&gt;Mor ROUGE-1&lt;/th&gt;
&lt;th&gt;Mor ROUGE-2&lt;/th&gt;
&lt;th&gt;Mor ROUGE-L&lt;/th&gt;
&lt;th&gt;Elapsed Sec&lt;/th&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;th&gt;Llama3.1 8B Inst&lt;/th&gt;
&lt;td&gt;0.3436&lt;/td&gt;
&lt;td&gt;0.2338&lt;/td&gt;
&lt;td&gt;0.4493&lt;/td&gt;
&lt;td&gt;0.3697&lt;/td&gt;
&lt;td&gt;0.1798&lt;/td&gt;
&lt;td&gt;0.3295&lt;/td&gt;
&lt;td&gt;158.69&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;th&gt;Bllossom 8B Inst&lt;/th&gt;
&lt;td&gt;&lt;b&gt;0.3634&lt;/b&gt;&lt;/td&gt;
&lt;td&gt;&lt;b&gt;0.2489&lt;/b&gt;&lt;/td&gt;
&lt;td&gt;&lt;b&gt;0.4706&lt;/b&gt;&lt;/td&gt;
&lt;td&gt;0.3847&lt;/td&gt;
&lt;td&gt;&lt;b&gt;0.1885&lt;/b&gt;&lt;/td&gt;
&lt;td&gt;0.3443&lt;/td&gt;
&lt;td&gt;156.71&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;th&gt;Qwen2 7B Inst&lt;/th&gt;
&lt;td&gt;0.3408&lt;/td&gt;
&lt;td&gt;0.2300&lt;/td&gt;
&lt;td&gt;0.4516&lt;/td&gt;
&lt;td&gt;&lt;b&gt;0.3873&lt;/b&gt;&lt;/td&gt;
&lt;td&gt;0.1736&lt;/td&gt;
&lt;td&gt;&lt;b&gt;0.3469&lt;/b&gt;&lt;/td&gt;
&lt;td&gt;163.26&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;th&gt;Solar Ko 11B&lt;/th&gt;
&lt;td&gt;0.0006&lt;/td&gt;
&lt;td&gt;0.0001&lt;/td&gt;
&lt;td&gt;0.0013&lt;/td&gt;
&lt;td&gt;0.0000&lt;/td&gt;
&lt;td&gt;0.0000&lt;/td&gt;
&lt;td&gt;0.0000&lt;/td&gt;
&lt;td&gt;31.27&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;th&gt;Mistral 12B Inst&lt;/th&gt;
&lt;td&gt;0.0326&lt;/td&gt;
&lt;td&gt;0.0117&lt;/td&gt;
&lt;td&gt;0.1280&lt;/td&gt;
&lt;td&gt;0.0074&lt;/td&gt;
&lt;td&gt;0.0028&lt;/td&gt;
&lt;td&gt;0.0074&lt;/td&gt;
&lt;td&gt;284.80&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;th&gt;EXAONE 3.0 8B Inst&lt;/th&gt;
&lt;td&gt;0.1722&lt;/td&gt;
&lt;td&gt;0.1254&lt;/td&gt;
&lt;td&gt;0.2794&lt;/td&gt;
&lt;td&gt;0.1815&lt;/td&gt;
&lt;td&gt;0.1001&lt;/td&gt;
&lt;td&gt;0.1653&lt;/td&gt;
&lt;td&gt;100.64&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;th&gt;PAPAGO (2024-08-24)&lt;/th&gt;
&lt;td&gt;0.4326&lt;/td&gt;
&lt;td&gt;0.3111&lt;/td&gt;
&lt;td&gt;0.5342&lt;/td&gt;
&lt;td&gt;0.4493&lt;/td&gt;
&lt;td&gt;0.2430&lt;/td&gt;
&lt;td&gt;0.4086&lt;/td&gt;
&lt;td&gt;-&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;th&gt;DeepL (2024-08-24)&lt;/th&gt;
&lt;td&gt;&lt;b&gt;0.4723&lt;/b&gt;&lt;/td&gt;
&lt;td&gt;&lt;b&gt;0.3535&lt;/b&gt;&lt;/td&gt;
&lt;td&gt;&lt;b&gt;0.5693&lt;/b&gt;&lt;/td&gt;
&lt;td&gt;&lt;b&gt;0.5024&lt;/b&gt;&lt;/td&gt;
&lt;td&gt;&lt;b&gt;0.2966&lt;/b&gt;&lt;/td&gt;
&lt;td&gt;&lt;b&gt;0.4637&lt;/b&gt;&lt;/td&gt;
&lt;td&gt;-&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;/div&gt;
&lt;p style=&quot;text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;LLM의 경우 Zero-shot에서는 일반 번역기보다 크게 뒤떨어지는 성능을 보였습니다. 특히 Solar Ko Recovery 모델의 경우 인스트럭션 튜닝이 전혀되지 않아 사실상 성능이 0이라고 볼 수 있습니다. 그나마 Bllossom이랑 Qwen2가 조금이나마 쓸만한 성능을 보이고 있네요.&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;프롬프팅 포맷이 번역 성능에 얼마나 영향을 미칠까요? Solar Ko 11B의 결과를 통해서 살펴봅시다. Default 0는 위에서 보여줬던 Zero-shot 베이스라인 성능이고, 예제 개수가 0이 아닌 것들은 실제 ICL의 효과가 반영된 Few-shot의 결과입니다. 인스트럭션 튜닝이 되지 않은 Solar Ko모델은 Default 프롬프트에서는 예제를 아무리 주어도 전혀 번역을 못했지만, 포맷을 Alternative나 Numbered로 바꾸자 갑자기 말문이 터져나옵니다. Alternative, Numbered 포맷에서는 모든 경우에 파파고, DeepL보다 훨씬 나은 번역 성능을 보이며, 특히 예제 그룹이 3개일때 전반적으로 좋은 성능을 보여주었습니다.&lt;/p&gt;
&lt;div class=&quot;table-overflow&quot;&gt;
&lt;table class=&quot;std-table&quot; style=&quot;min-width: 48em;&quot; data-ke-align=&quot;alignLeft&quot;&gt;&lt;caption&gt;Solar Ko 11B 모델의 프롬프팅 포맷별 번역 성능(화자 정보 미사용)&lt;/caption&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;th&gt;프롬프팅 포맷&lt;/th&gt;
&lt;th&gt;예제 개수 / 그룹&lt;/th&gt;
&lt;th&gt;Chr ROUGE-2&lt;/th&gt;
&lt;th&gt;Chr ROUGE-3&lt;/th&gt;
&lt;th&gt;Chr ROUGE-L&lt;/th&gt;
&lt;th&gt;Mor ROUGE-1&lt;/th&gt;
&lt;th&gt;Mor ROUGE-2&lt;/th&gt;
&lt;th&gt;Mor ROUGE-L&lt;/th&gt;
&lt;th&gt;Elapsed Sec.&lt;/th&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;th&gt;Default&lt;/th&gt;
&lt;th&gt;0&lt;/th&gt;
&lt;td&gt;0.0006&lt;/td&gt;
&lt;td&gt;0.0001&lt;/td&gt;
&lt;td&gt;0.0013&lt;/td&gt;
&lt;td&gt;0.0000&lt;/td&gt;
&lt;td&gt;0.0000&lt;/td&gt;
&lt;td&gt;0.0000&lt;/td&gt;
&lt;td&gt;31.27&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;th&gt;Default&lt;/th&gt;
&lt;th&gt;10 / 1&lt;/th&gt;
&lt;td&gt;0.0203&lt;/td&gt;
&lt;td&gt;0.0017&lt;/td&gt;
&lt;td&gt;0.1246&lt;/td&gt;
&lt;td&gt;0.0009&lt;/td&gt;
&lt;td&gt;0.0003&lt;/td&gt;
&lt;td&gt;0.0007&lt;/td&gt;
&lt;td&gt;1649.35&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;th&gt;Default&lt;/th&gt;
&lt;th&gt;10 / 3&lt;/th&gt;
&lt;td&gt;0.0234&lt;/td&gt;
&lt;td&gt;0.0038&lt;/td&gt;
&lt;td&gt;0.1300&lt;/td&gt;
&lt;td&gt;0.0025&lt;/td&gt;
&lt;td&gt;0.0011&lt;/td&gt;
&lt;td&gt;0.0025&lt;/td&gt;
&lt;td&gt;1231.26&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;th&gt;Default&lt;/th&gt;
&lt;th&gt;10 / 5&lt;/th&gt;
&lt;td&gt;0.0232&lt;/td&gt;
&lt;td&gt;0.0035&lt;/td&gt;
&lt;td&gt;0.1301&lt;/td&gt;
&lt;td&gt;0.0025&lt;/td&gt;
&lt;td&gt;0.0010&lt;/td&gt;
&lt;td&gt;0.0024&lt;/td&gt;
&lt;td&gt;1216.85&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;th&gt;Alternative&lt;/th&gt;
&lt;th&gt;10 / 1&lt;/th&gt;
&lt;td&gt;0.6058&lt;/td&gt;
&lt;td&gt;0.5098&lt;/td&gt;
&lt;td&gt;0.6683&lt;/td&gt;
&lt;td&gt;0.6169&lt;/td&gt;
&lt;td&gt;0.4447&lt;/td&gt;
&lt;td&gt;0.5778&lt;/td&gt;
&lt;td&gt;1705.69&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;th&gt;Alternative&lt;/th&gt;
&lt;th&gt;10 / 3&lt;/th&gt;
&lt;td&gt;0.6095&lt;/td&gt;
&lt;td&gt;0.5119&lt;/td&gt;
&lt;td&gt;0.6705&lt;/td&gt;
&lt;td&gt;&lt;b&gt;0.6225&lt;/b&gt;&lt;/td&gt;
&lt;td&gt;0.4448&lt;/td&gt;
&lt;td&gt;0.5801&lt;/td&gt;
&lt;td&gt;1269.33&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;th&gt;Alternative&lt;/th&gt;
&lt;th&gt;10 / 5&lt;/th&gt;
&lt;td&gt;&lt;b&gt;0.6116&lt;/b&gt;&lt;/td&gt;
&lt;td&gt;&lt;b&gt;0.5146&lt;/b&gt;&lt;/td&gt;
&lt;td&gt;&lt;b&gt;0.6739&lt;/b&gt;&lt;/td&gt;
&lt;td&gt;&lt;b&gt;0.6232&lt;/b&gt;&lt;/td&gt;
&lt;td&gt;&lt;b&gt;0.4484&lt;/b&gt;&lt;/td&gt;
&lt;td&gt;&lt;b&gt;0.5830&lt;/b&gt;&lt;/td&gt;
&lt;td&gt;1304.94&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;th&gt;Numbered&lt;/th&gt;
&lt;th&gt;10 / 1&lt;/th&gt;
&lt;td&gt;0.6045&lt;/td&gt;
&lt;td&gt;0.5079&lt;/td&gt;
&lt;td&gt;0.6671&lt;/td&gt;
&lt;td&gt;0.6144&lt;/td&gt;
&lt;td&gt;0.4412&lt;/td&gt;
&lt;td&gt;0.5749&lt;/td&gt;
&lt;td&gt;1666.70&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;th&gt;Numbered&lt;/th&gt;
&lt;th&gt;10 / 3&lt;/th&gt;
&lt;td&gt;&lt;b&gt;0.6112&lt;/b&gt;&lt;/td&gt;
&lt;td&gt;&lt;b&gt;0.5149&lt;/b&gt;&lt;/td&gt;
&lt;td&gt;&lt;b&gt;0.6740&lt;/b&gt;&lt;/td&gt;
&lt;td&gt;0.6208&lt;/td&gt;
&lt;td&gt;&lt;b&gt;0.4479&lt;/b&gt;&lt;/td&gt;
&lt;td&gt;&lt;b&gt;0.5822&lt;/b&gt;&lt;/td&gt;
&lt;td&gt;1318.63&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;th&gt;Numbered&lt;/th&gt;
&lt;th&gt;10 / 5&lt;/th&gt;
&lt;td&gt;0.6097&lt;/td&gt;
&lt;td&gt;0.5136&lt;/td&gt;
&lt;td&gt;0.6726&lt;/td&gt;
&lt;td&gt;0.6188&lt;/td&gt;
&lt;td&gt;0.4454&lt;/td&gt;
&lt;td&gt;0.5799&lt;/td&gt;
&lt;td&gt;1351.18&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;/div&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;화자 정보를 사용하는건 번역 품질이 얼마나 도움이 될까요? 동일하게 Solar Ko 모델에 프롬프팅 포맷을 Alternative로 고정하고 화자 정보를 사용한경우와 그렇지 않은 경우를 비교해보았습니다. 화자 정보를 사용한 경우가 모든 경우에서 일관성 있게 성능 향상을 보였습니다. 특히 10 / 3에서는 Mor ROUGE-L이 처음으로 60%를 넘기기도 했습니다.&lt;/p&gt;
&lt;div class=&quot;table-overflow&quot;&gt;
&lt;table class=&quot;std-table&quot; style=&quot;min-width: 48em;&quot; data-ke-align=&quot;alignLeft&quot;&gt;&lt;caption&gt;Solar Ko 11B 모델, Alterantive 포맷에서 화자 정보의 효과&lt;/caption&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;th&gt;예제 개수 / 그룹&lt;/th&gt;
&lt;th&gt;화자 정보&lt;/th&gt;
&lt;th&gt;Chr ROUGE-2&lt;/th&gt;
&lt;th&gt;Chr ROUGE-3&lt;/th&gt;
&lt;th&gt;Chr ROUGE-L&lt;/th&gt;
&lt;th&gt;Mor ROUGE-1&lt;/th&gt;
&lt;th&gt;Mor ROUGE-2&lt;/th&gt;
&lt;th&gt;Mor ROUGE-L&lt;/th&gt;
&lt;th&gt;Elapsed Sec.&lt;/th&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;th rowspan=&quot;2&quot;&gt;10 / 1&lt;/th&gt;
&lt;th&gt;미사용&lt;/th&gt;
&lt;td&gt;0.6058&lt;/td&gt;
&lt;td&gt;0.5098&lt;/td&gt;
&lt;td&gt;0.6683&lt;/td&gt;
&lt;td&gt;0.6169&lt;/td&gt;
&lt;td&gt;0.4447&lt;/td&gt;
&lt;td&gt;0.5778&lt;/td&gt;
&lt;td&gt;1705.69&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;th&gt;사용&lt;/th&gt;
&lt;td&gt;0.6215&lt;/td&gt;
&lt;td&gt;0.5268&lt;/td&gt;
&lt;td&gt;0.6800&lt;/td&gt;
&lt;td&gt;0.6330&lt;/td&gt;
&lt;td&gt;0.4632&lt;/td&gt;
&lt;td&gt;0.5930&lt;/td&gt;
&lt;td&gt;1797.56&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;th rowspan=&quot;2&quot;&gt;10 / 3&lt;/th&gt;
&lt;th&gt;미사용&lt;/th&gt;
&lt;td&gt;0.6095&lt;/td&gt;
&lt;td&gt;0.5119&lt;/td&gt;
&lt;td&gt;0.6705&lt;/td&gt;
&lt;td&gt;0.6225&lt;/td&gt;
&lt;td&gt;0.4448&lt;/td&gt;
&lt;td&gt;0.5801&lt;/td&gt;
&lt;td&gt;1269.33&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;th&gt;사용&lt;/th&gt;
&lt;td&gt;&lt;b&gt;0.6296&lt;/b&gt;&lt;/td&gt;
&lt;td&gt;&lt;b&gt;0.5355&lt;/b&gt;&lt;/td&gt;
&lt;td&gt;&lt;b&gt;0.6856&lt;/b&gt;&lt;/td&gt;
&lt;td&gt;&lt;b&gt;0.6433&lt;/b&gt;&lt;/td&gt;
&lt;td&gt;&lt;b&gt;0.4714&lt;/b&gt;&lt;/td&gt;
&lt;td&gt;&lt;b&gt;0.6017&lt;/b&gt;&lt;/td&gt;
&lt;td&gt;1428.34&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;th rowspan=&quot;2&quot;&gt;10 / 5&lt;/th&gt;
&lt;th&gt;미사용&lt;/th&gt;
&lt;td&gt;0.6116&lt;/td&gt;
&lt;td&gt;0.5146&lt;/td&gt;
&lt;td&gt;0.6739&lt;/td&gt;
&lt;td&gt;0.6232&lt;/td&gt;
&lt;td&gt;0.4484&lt;/td&gt;
&lt;td&gt;0.5830&lt;/td&gt;
&lt;td&gt;1304.94&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;th&gt;사용&lt;/th&gt;
&lt;td&gt;0.6287&lt;/td&gt;
&lt;td&gt;0.5340&lt;/td&gt;
&lt;td&gt;0.6868&lt;/td&gt;
&lt;td&gt;0.6405&lt;/td&gt;
&lt;td&gt;0.4684&lt;/td&gt;
&lt;td&gt;0.6002&lt;/td&gt;
&lt;td&gt;1362.16&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;/div&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;전반적으로 제일 좋은 성능을 보였던 조합인 Alternative 포맷, 예제 10개, 그룹 3개, 화자 정보까지 사용한 옵션으로 모든 모델의 번역 성능을 정리해봤습니다. 역시 LLM은 크기빨인지 Solar Ko 11B가 제일 높은 성능, 그 다음으로 Mistral 12B가 높은 성능을 보였네요. 7~8B 규모에서는 Llama3에 한국어 튜닝을 수행한 Bllossom이 제일 높은 성능을 달성했습니다. 한국어/영어 전용 모델인 EXAONE은 중국 모델에다가 크기도 살짝 작은 Qwen2와 거의 동일한 성능을 보여 아쉬움을 남겼습니다.&lt;/p&gt;
&lt;div class=&quot;table-overflow&quot;&gt;
&lt;table class=&quot;std-table&quot; style=&quot;min-width: 48em;&quot; data-ke-align=&quot;alignLeft&quot;&gt;&lt;caption&gt;Alternative 포맷, 예제 10개, 그룹 3개, 화자 정보 사용할 때의 모델별 성능 비교&lt;/caption&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;th&gt;모델&lt;/th&gt;
&lt;th&gt;Chr ROUGE-2&lt;/th&gt;
&lt;th&gt;Chr ROUGE-3&lt;/th&gt;
&lt;th&gt;Chr ROUGE-L&lt;/th&gt;
&lt;th&gt;Mor ROUGE-1&lt;/th&gt;
&lt;th&gt;Mor ROUGE-2&lt;/th&gt;
&lt;th&gt;Mor ROUGE-L&lt;/th&gt;
&lt;th&gt;Elapsed Sec.&lt;/th&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;th&gt;Llama3.1 8B Inst&lt;/th&gt;
&lt;td&gt;0.5963&lt;/td&gt;
&lt;td&gt;0.4944&lt;/td&gt;
&lt;td&gt;0.6631&lt;/td&gt;
&lt;td&gt;0.6045&lt;/td&gt;
&lt;td&gt;0.4210&lt;/td&gt;
&lt;td&gt;0.5602&lt;/td&gt;
&lt;td&gt;266.28&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;th&gt;Bllossom 8B Inst&lt;/th&gt;
&lt;td&gt;0.5982&lt;/td&gt;
&lt;td&gt;0.4955&lt;/td&gt;
&lt;td&gt;0.6649&lt;/td&gt;
&lt;td&gt;0.6063&lt;/td&gt;
&lt;td&gt;0.4215&lt;/td&gt;
&lt;td&gt;0.5638&lt;/td&gt;
&lt;td&gt;472.31&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;th&gt;Qwen2 7B Inst&lt;/th&gt;
&lt;td&gt;0.5847&lt;/td&gt;
&lt;td&gt;0.4805&lt;/td&gt;
&lt;td&gt;0.6529&lt;/td&gt;
&lt;td&gt;0.5937&lt;/td&gt;
&lt;td&gt;0.4041&lt;/td&gt;
&lt;td&gt;0.5461&lt;/td&gt;
&lt;td&gt;267.39&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;th&gt;Solar Ko 11B&lt;/th&gt;
&lt;td&gt;&lt;b&gt;0.6296&lt;/b&gt;&lt;/td&gt;
&lt;td&gt;&lt;b&gt;0.5355&lt;/b&gt;&lt;/td&gt;
&lt;td&gt;&lt;b&gt;0.6856&lt;/b&gt;&lt;/td&gt;
&lt;td&gt;&lt;b&gt;0.6433&lt;/b&gt;&lt;/td&gt;
&lt;td&gt;&lt;b&gt;0.4714&lt;/b&gt;&lt;/td&gt;
&lt;td&gt;&lt;b&gt;0.6017&lt;/b&gt;&lt;/td&gt;
&lt;td&gt;1428.34&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;th&gt;Mistral 12B Inst&lt;/th&gt;
&lt;td&gt;&lt;b&gt;0.6114&lt;/b&gt;&lt;/td&gt;
&lt;td&gt;&lt;b&gt;0.5133&lt;/b&gt;&lt;/td&gt;
&lt;td&gt;&lt;b&gt;0.6750&lt;/b&gt;&lt;/td&gt;
&lt;td&gt;&lt;b&gt;0.6201&lt;/b&gt;&lt;/td&gt;
&lt;td&gt;&lt;b&gt;0.4405&lt;/b&gt;&lt;/td&gt;
&lt;td&gt;&lt;b&gt;0.5784&lt;/b&gt;&lt;/td&gt;
&lt;td&gt;606.88&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;th&gt;EXAONE 3.0 8B Inst&lt;/th&gt;
&lt;td&gt;0.5776&lt;/td&gt;
&lt;td&gt;0.4765&lt;/td&gt;
&lt;td&gt;0.6462&lt;/td&gt;
&lt;td&gt;0.5934&lt;/td&gt;
&lt;td&gt;0.4082&lt;/td&gt;
&lt;td&gt;0.5493&lt;/td&gt;
&lt;td&gt;240.72&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;/div&gt;
&lt;p style=&quot;text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;생성에 소요된 시간은 엄밀하게 측정된 것은 아니라 참고용으로만 보시길 바랍니다. 실험을 돌린 장비가 VRAM 16GB짜리라 10B 이상 모델은 간신히 올릴수 있고, 이 경우 특히 생성길이가 길어지면 KV Cache를 CPU RAM과 교환해가면서 생성을 해야해서 속도가 확 떨어지거든요. Solar Ko 11B가 유난히 오래 걸린것도 번역을 잘해서 생성길이가 길어지다보니 그런것이라 보시면 됩니다.&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;지면 상 실험 결과 전부를 담지는 못했지만 스프레드시트로 정리해둔게 있으니 관심 있으신 분들은 &lt;a href=&quot;https://docs.google.com/spreadsheets/d/1BiTiMDSm3t-dAIOLMy5jckIL9P6L8G977w4bXnAtE7E/edit?gid=0#gid=0&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;여기&lt;/a&gt;에서 자세한 결과를 확인하시길 바랍니다!&lt;/p&gt;
&lt;h3 style=&quot;text-align: justify;&quot; data-ke-size=&quot;size23&quot;&gt;실험 결과 - 사례 분석&lt;/h3&gt;
&lt;p style=&quot;text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;결과를 숫자로만 보면 재미가 없죠? 실제 번역된 문장들을 살펴봅시다.&lt;/p&gt;
&lt;table class=&quot;std-table&quot; style=&quot;margin-bottom: 1em;&quot; data-ke-align=&quot;alignLeft&quot;&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;th&gt;영어 원문&lt;/th&gt;
&lt;td&gt;Focus on destroying targets that endanger the Odin, like Siege Tanks, Banshees, and Battlecruisers.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;th&gt;공식 번역&lt;/th&gt;
&lt;td&gt;공성 전차, 밴시, 전투순양함과 같이 오딘을 위협하는 목표물을 우선적으로 파괴하십시오.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;th&gt;Papago&lt;/th&gt;
&lt;td&gt;공성전차, 밴시, 배틀크루저 등 오딘을 위험에 빠뜨리는 목표물을 파괴하는 데 집중해야 합니다.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;th&gt;DeepL&lt;/th&gt;
&lt;td&gt;공성 전차, 밴시, 전투순양함 등 오딘을 위협하는 대상을 파괴하는 데 집중하세요.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;th&gt;Llama3.1, Def., ex=0&lt;/th&gt;
&lt;td&gt;Odin을 위협하는 목표를 파괴하는 것을 중점으로 하세요. 예를 들어, 시즈탱크, 반시, 배틀크루저 등입니다.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;th&gt;Llama3.1, Alt., ex=10/1&lt;/th&gt;
&lt;td&gt;오딘을 위협하는 목표물, 즉 공성 전차, 반시, 전함을 파괴하는 것을 주로 목표로 하세요.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;th&gt;Llama3.1, Alt., ex=10/3&lt;/th&gt;
&lt;td&gt;오딘을 위협하는 목표물인 공성 전차, 밴시, 전투순양함을 파괴하는 것을 우선으로 하세요.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td colspan=&quot;2&quot;&gt;다양한 고유 명사가 등장하는 사례입니다. 예상과 다르게 잘 나온 파파고, DeepL의 번역 결과를 보면 이 번역기들은 스타크래프트 도메인에 대해서도 어느 정도 학습을 했나봅니다. 반면 예제를 전혀 사용하지 않은 Llama3.1에서는 고유 명사를 제멋대로 번역하는 걸 볼 수 있습니다. Alternative 포맷을 사용하고, 예제를 늘리고, 그룹을 다양하게 하면 고유 명사 번역 정확도가 개선됩니다.&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;table class=&quot;std-table&quot; style=&quot;margin-bottom: 1em;&quot; data-ke-align=&quot;alignLeft&quot;&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;th&gt;영어 원문&lt;/th&gt;
&lt;td&gt;Cloaks&amp;nbsp;friendly&amp;nbsp;units&amp;nbsp;near&amp;nbsp;the&amp;nbsp;Oracle.&amp;nbsp;Lasts&amp;nbsp;&amp;lt;d&amp;nbsp;ref=&quot;Behavior,OracleCloakField,Duration&quot;/&amp;gt;&amp;nbsp;seconds.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;th&gt;공식 번역&lt;/th&gt;
&lt;td&gt;예언자&amp;nbsp;근처의&amp;nbsp;아군&amp;nbsp;유닛을&amp;nbsp;은폐시킵니다.&amp;nbsp;&amp;lt;d&amp;nbsp;ref=&quot;Behavior,OracleCloakField,Duration&quot;/&amp;gt;&amp;nbsp;동안&amp;nbsp;지속됩니다.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;th&gt;Papago&lt;/th&gt;
&lt;td&gt;오라클&amp;nbsp;근처의&amp;nbsp;아군&amp;nbsp;유닛을&amp;nbsp;차단합니다.&amp;nbsp;&amp;lt;dref=&quot;Behavior,&amp;nbsp;Oracle&amp;nbsp;Cloak&amp;nbsp;Field,&amp;nbsp;Duration&quot;/&amp;gt;초&amp;nbsp;지속됩니다.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;th&gt;DeepL&lt;/th&gt;
&lt;td&gt;오라클&amp;nbsp;근처의&amp;nbsp;아군&amp;nbsp;유닛을&amp;nbsp;은폐합니다.&amp;nbsp;지속&amp;nbsp;시간&amp;nbsp;&amp;lt;d&amp;nbsp;ref=&amp;ldquo;행동,오라클클로크필드,지속&amp;nbsp;시간&amp;rdquo;/&amp;gt;&amp;nbsp;초입니다.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;th&gt;Bllossom, Def., ex=0&lt;/th&gt;
&lt;td&gt;입력: 오라클 근처의 친절한 유닛에 가면. 지속 시간은 &amp;lt;d ref=&quot;Behavior,OracleCloakField,Duration&quot;/&amp;gt; 초입니다.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;th&gt;Bllossom, Def., ex=10/1&lt;/th&gt;
&lt;td&gt;Input:&amp;nbsp;아군&amp;nbsp;유닛을&amp;nbsp;오라클&amp;nbsp;근처에&amp;nbsp;은폐합니다.&amp;nbsp;&amp;lt;d&amp;nbsp;ref=&quot;Behavior,OracleCloakField,Duration&quot;/&amp;gt;초&amp;nbsp;동안&amp;nbsp;지속됩니다.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;th&gt;Bllossom, Def., ex=10/3&lt;/th&gt;
&lt;td&gt;아군&amp;nbsp;유닛이&amp;nbsp;예언자&amp;nbsp;근처에&amp;nbsp;위치한&amp;nbsp;은폐장에&amp;nbsp;의해&amp;nbsp;은폐됩니다.&amp;nbsp;&amp;lt;d&amp;nbsp;ref=&quot;Behavior,OracleCloakField,Duration&quot;/&amp;gt;초&amp;nbsp;동안&amp;nbsp;지속됩니다.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td colspan=&quot;2&quot;&gt;채움문자가 쓰이는 사례입니다. 채움문자를 수정하지 않고 그대로 번역하는게 중요한데요, 파파고는 공백을 임의로 바꾸었고, DeepL은 맘대로 번역했습니다. Bllossom의 경우 어떤 경우에도 채움문자를 건드리지 않고 그대로 잘 옮겨 주었습니다. 다만 Default 포맷에서는 입력:, Input: 과 같이 의도하지 않은 텍스트가 덧붙는것을 볼 수 있습니다. (이는 Bllossom에서만 발생하는게 아니라 Default 포맷을 쓰는 모든 인스트럭션 모델에서 종종 발생했습니다.)&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;table class=&quot;std-table&quot; style=&quot;margin-bottom: 1em;&quot; data-ke-align=&quot;alignLeft&quot;&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;th&gt;영어 원문&lt;/th&gt;
&lt;td&gt;(화자: Abathur) Enemy will not destroy objective. Must first defeat zerg forces. Impossible.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;th&gt;공식 번역&lt;/th&gt;
&lt;td&gt;적은&amp;nbsp;목표를&amp;nbsp;파괴할&amp;nbsp;수&amp;nbsp;없음.&amp;nbsp;먼저&amp;nbsp;저그를&amp;nbsp;물리쳐야&amp;nbsp;함.&amp;nbsp;그건&amp;nbsp;불가능함.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;th&gt;Papago&lt;/th&gt;
&lt;td&gt;적이&amp;nbsp;목표물을&amp;nbsp;파괴하지는&amp;nbsp;않을&amp;nbsp;것입니다.&amp;nbsp;먼저&amp;nbsp;저그&amp;nbsp;병력을&amp;nbsp;물리쳐야&amp;nbsp;합니다.&amp;nbsp;불가능합니다.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;th&gt;DeepL&lt;/th&gt;
&lt;td&gt;적은&amp;nbsp;목표물을&amp;nbsp;파괴하지&amp;nbsp;않습니다.&amp;nbsp;먼저&amp;nbsp;저그&amp;nbsp;부대를&amp;nbsp;처치해야&amp;nbsp;합니다.&amp;nbsp;불가능합니다.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;th&gt;Solar Ko, Alt., ex=10/1&lt;/th&gt;
&lt;td&gt;적은&amp;nbsp;목표를&amp;nbsp;파괴하지&amp;nbsp;않을&amp;nbsp;것입니다.&amp;nbsp;먼저&amp;nbsp;저그&amp;nbsp;병력을&amp;nbsp;물리쳐야&amp;nbsp;합니다.&amp;nbsp;불가능합니다.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;th&gt;Solar Ko, Alt., Speaker, ex=10/1&lt;/th&gt;
&lt;td&gt;적,&amp;nbsp;목표&amp;nbsp;파괴하지&amp;nbsp;않음.&amp;nbsp;먼저&amp;nbsp;저그&amp;nbsp;병력&amp;nbsp;물리쳐야&amp;nbsp;함.&amp;nbsp;불가능함.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;th&gt;Solar Ko, Alt., Speaker, ex=10/3&lt;/th&gt;
&lt;td&gt;저그&amp;nbsp;병력을&amp;nbsp;먼저&amp;nbsp;물리치지&amp;nbsp;않는&amp;nbsp;한,&amp;nbsp;적의&amp;nbsp;목표&amp;nbsp;파괴는&amp;nbsp;불가능함.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td colspan=&quot;2&quot;&gt;화자 정보가 중요한 사례입니다. 극한의 효율만을 추구하는 아바투르는 비효율적인 언어요소(영어에서는 be 동사, 한국어에서는 조사/어미 등)를 사용하지 않는 특이한 화법을 사용합니다. 공식 번역에서는 이 특성이 잘 반영되어있지만 파파고, DeepL 번역에서는 이걸 반영하는게 어렵습니다. 화자 정보를 사용하지 않은 LLM 번역에서도 마찬가지구요. 하지만 화자정보를 사용한 경우에서는 실제 아바투르의 말투와 유사하게 번역이 된 걸 확인할 수 있습니다.&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;table class=&quot;std-table&quot; style=&quot;margin-bottom: 1em;&quot; data-ke-align=&quot;alignLeft&quot;&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;th&gt;영어 원문&lt;/th&gt;
&lt;td&gt;(화자: Valerian) I&amp;nbsp;am&amp;nbsp;not&amp;nbsp;the&amp;nbsp;man&amp;nbsp;my&amp;nbsp;father&amp;nbsp;was.&amp;nbsp;I&amp;nbsp;wish&amp;nbsp;to&amp;nbsp;serve&amp;nbsp;a&amp;nbsp;greater&amp;nbsp;good...&amp;nbsp;I&amp;nbsp;think&amp;nbsp;we&amp;nbsp;have&amp;nbsp;that&amp;nbsp;much&amp;nbsp;in&amp;nbsp;common.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;th&gt;공식 번역&lt;/th&gt;
&lt;td&gt;나는&amp;nbsp;내&amp;nbsp;아버지와는&amp;nbsp;다르오.&amp;nbsp;난&amp;nbsp;더&amp;nbsp;큰&amp;nbsp;선을&amp;nbsp;추구하고&amp;nbsp;있소...&amp;nbsp;그&amp;nbsp;점은&amp;nbsp;나나&amp;nbsp;당신이나&amp;nbsp;같지&amp;nbsp;않소?&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;th&gt;Papago&lt;/th&gt;
&lt;td&gt;저는&amp;nbsp;아버지와&amp;nbsp;같은&amp;nbsp;사람이&amp;nbsp;아닙니다.&amp;nbsp;저는&amp;nbsp;더&amp;nbsp;큰&amp;nbsp;이익을&amp;nbsp;위해&amp;nbsp;봉사하고&amp;nbsp;싶습니다...&amp;nbsp;저는&amp;nbsp;우리가&amp;nbsp;그만큼&amp;nbsp;많은&amp;nbsp;공통점을&amp;nbsp;가지고&amp;nbsp;있다고&amp;nbsp;생각합니다.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;th&gt;DeepL&lt;/th&gt;
&lt;td&gt;저는&amp;nbsp;아버지와&amp;nbsp;같은&amp;nbsp;사람이&amp;nbsp;아닙니다.&amp;nbsp;저는&amp;nbsp;더&amp;nbsp;큰&amp;nbsp;선을&amp;nbsp;위해&amp;nbsp;봉사하고&amp;nbsp;싶습니다...&amp;nbsp;우린&amp;nbsp;공통점이&amp;nbsp;많다고&amp;nbsp;생각해요.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;th&gt;EXAONE, Def., ex=0&lt;/th&gt;
&lt;td&gt;제가&amp;nbsp;아버지였던&amp;nbsp;사람이&amp;nbsp;아닙니다.&amp;nbsp;더&amp;nbsp;큰&amp;nbsp;선을&amp;nbsp;위해&amp;nbsp;봉사하고&amp;nbsp;싶습니다...&amp;nbsp;우리가&amp;nbsp;그만큼&amp;nbsp;공통점이&amp;nbsp;있다고&amp;nbsp;생각합니다.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;th&gt;EXAONE, Alt., ex=10/3&lt;/th&gt;
&lt;td&gt;나는&amp;nbsp;내&amp;nbsp;아버지가&amp;nbsp;아니었다.&amp;nbsp;더&amp;nbsp;큰&amp;nbsp;선을&amp;nbsp;위해&amp;nbsp;섬기고&amp;nbsp;싶다...&amp;nbsp;우리가&amp;nbsp;그렇게&amp;nbsp;생각할&amp;nbsp;만큼&amp;nbsp;공통점이&amp;nbsp;있다고&amp;nbsp;본다.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;th&gt;EXAONE, Alt., Speaker, ex=10/3&lt;/th&gt;
&lt;td&gt;나는&amp;nbsp;아버지가&amp;nbsp;아니오.&amp;nbsp;나는&amp;nbsp;더&amp;nbsp;큰&amp;nbsp;선을&amp;nbsp;섬기고&amp;nbsp;싶소...&amp;nbsp;우리가&amp;nbsp;그렇게&amp;nbsp;다를&amp;nbsp;것이&amp;nbsp;없다고&amp;nbsp;생각하오.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td colspan=&quot;2&quot;&gt;마찬가지로 화자 정보가 중요한 사례입니다. 발레리안은 공식 번역에서 &quot;-하오&quot;체를 사용하는 캐릭터로 설정되어 있습니다. 파파고나 DeepL, 화자 정보를 주지 않은 EXAONE은 그런걸 모르겠죠? 하지만 화자 정보를 줄 경우 &quot;-하오&quot;체를 잘 살려서 번역해줍니다.&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;table class=&quot;std-table&quot; style=&quot;margin-bottom: 1em;&quot; data-ke-align=&quot;alignLeft&quot;&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;th&gt;영어 원문&lt;/th&gt;
&lt;td&gt;(화자: Zeratul) Only&amp;nbsp;by&amp;nbsp;making&amp;nbsp;contact&amp;nbsp;with&amp;nbsp;the&amp;nbsp;gargantuan&amp;nbsp;tendrils&amp;nbsp;connected&amp;nbsp;directly&amp;nbsp;to&amp;nbsp;its&amp;nbsp;cortex&amp;nbsp;could&amp;nbsp;I&amp;nbsp;learn&amp;nbsp;its&amp;nbsp;secrets.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;th&gt;공식 번역&lt;/th&gt;
&lt;td&gt;그&amp;nbsp;기억을&amp;nbsp;얻으려면,&amp;nbsp;초월체의&amp;nbsp;대뇌에&amp;nbsp;연결된&amp;nbsp;거대한&amp;nbsp;촉수와&amp;nbsp;접촉해야만&amp;nbsp;했지.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;th&gt;Papago&lt;/th&gt;
&lt;td&gt;피질과&amp;nbsp;직접&amp;nbsp;연결된&amp;nbsp;거대한&amp;nbsp;힘줄과&amp;nbsp;접촉해야만&amp;nbsp;그&amp;nbsp;비밀을&amp;nbsp;알&amp;nbsp;수&amp;nbsp;있었습니다.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;th&gt;DeepL&lt;/th&gt;
&lt;td&gt;피질과 직접 연결된 거대한 덩굴손과 접촉해야만 그 비밀을 알 수 있었습니다.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;th&gt;Qwen2, Def., ex=0&lt;/th&gt;
&lt;td&gt;입력:&amp;nbsp;그의&amp;nbsp;비밀을&amp;nbsp;알&amp;nbsp;수는仅有&amp;nbsp;그의&amp;nbsp;대형&amp;nbsp;뿔모양의&amp;nbsp;뼛대와&amp;nbsp;직접적으로&amp;nbsp;연결된&amp;nbsp;코르TEX와&amp;nbsp;접촉을&amp;nbsp;통해&amp;nbsp;가능하다.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;th&gt;Qwen2, Num., ex=10/3&lt;/th&gt;
&lt;td&gt;초대형&amp;nbsp;촉수와&amp;nbsp;직접적으로&amp;nbsp;피질과&amp;nbsp;연결되어&amp;nbsp;있는&amp;nbsp;것만으로도&amp;nbsp;그&amp;nbsp;비밀을&amp;nbsp;알&amp;nbsp;수&amp;nbsp;있었을&amp;nbsp;것이다.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;th&gt;Qwen2, Num., Speaker, ex=10/3&lt;/th&gt;
&lt;td&gt;그대는&amp;nbsp;그대의&amp;nbsp;피질과&amp;nbsp;직접&amp;nbsp;연결되어&amp;nbsp;있는&amp;nbsp;거대한&amp;nbsp;촉수를&amp;nbsp;통해&amp;nbsp;그&amp;nbsp;비밀을&amp;nbsp;알&amp;nbsp;수&amp;nbsp;있었다.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td colspan=&quot;2&quot;&gt;LLM이 흔히 접하지 못한 어려운 단어들이 쓰이는 표현입니다. Qwen2는 0shot 번역시에 순간적인 상황변화를 받아들이지 못하고 그만 중국어와 영어를 섞어서 내뱉고 말았습니다. 그래도 예제를 적당히 준 경우에는 어느 정도 번역에 성공해내지만, 역시 굉장히 어눌한 한국어를 보여줍니다.&amp;nbsp;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p style=&quot;text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;더 많은 사례들을 살펴보고 싶으신 분들은 &lt;a href=&quot;https://docs.google.com/spreadsheets/d/1BiTiMDSm3t-dAIOLMy5jckIL9P6L8G977w4bXnAtE7E/edit?gid=521420125#gid=521420125&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;이 구글 시트&lt;/a&gt;를 참조하시길 바랍니다. 어떤가요? 예제를 잘 준다면 LLM이 공식 번역의 톤을 제법 살려서 자동 번역을 할 수 있겠다는 생각이 들지 않나요? 7~12B 모델이 이 정도인걸 보면 더 크고 한국어에 좀 더 특화된 모델은 훨씬 더 잘할 수 있을거라고 예상됩니다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;실제 적용 결과!&lt;/h3&gt;
&lt;p style=&quot;text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;실제 Dark Vengeance 텍스트를 추출해서 번역을 돌린 결과를 살짝 보여드리면 아래와 같습니다. 모델은 위의 실험에서 제일 높은 성능을 보였던 Solar Ko 11B를 사용했고, 예제 개수는 10개, 그룹은 3개, 프롬프팅 포맷은 Alternative로 고정하고 화자 정보를 사용했습니다. 총 3139개의 번역 대상 텍스트가 추출되었고 이 중 번역에 실패한 경우는 79건(약 2.5%)이었습니다. (서식지정자나 채움문자가 원본 텍스트와 동일하지 않은 경우만 번역 실패로 간주했습니다. 서식지정자나 채움문자가 깨진 번역 텍스트를 사용하면 게임 플레이에 문제가 될 수 있기 때문입니다. 번역 품질은 별도로 평가하지 않았습니다.)&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;Screenshot2024-09-03 02_00_18.jpg&quot; data-origin-width=&quot;3840&quot; data-origin-height=&quot;2160&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/rUWGZ/btsJu87zkPv/ZpCnQvgnS00oy3mXGdNPaK/img.jpg&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/rUWGZ/btsJu87zkPv/ZpCnQvgnS00oy3mXGdNPaK/img.jpg&quot; data-alt=&quot;짠! 자동 번역이 반영된 스샷입니다!&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/rUWGZ/btsJu87zkPv/ZpCnQvgnS00oy3mXGdNPaK/img.jpg&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FrUWGZ%2FbtsJu87zkPv%2FZpCnQvgnS00oy3mXGdNPaK%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;3840&quot; height=&quot;2160&quot; data-filename=&quot;Screenshot2024-09-03 02_00_18.jpg&quot; data-origin-width=&quot;3840&quot; data-origin-height=&quot;2160&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;짠! 자동 번역이 반영된 스샷입니다!&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;Screenshot2024-09-03 02_01_55.jpg&quot; data-origin-width=&quot;3840&quot; data-origin-height=&quot;2160&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/dMCRCc/btsJtEUtp82/wR23SQnkoFTDKdknPgq2zk/img.jpg&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/dMCRCc/btsJtEUtp82/wR23SQnkoFTDKdknPgq2zk/img.jpg&quot; data-alt=&quot;브리핑 장면! 참고로 브리핑 텍스트가 &amp;quot;당신&amp;quot;에서 끊긴건 오류가 아니라 텍스트 타이핑 애니메이션 중간에 스샷을 찍어서 그런겁니다.&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/dMCRCc/btsJtEUtp82/wR23SQnkoFTDKdknPgq2zk/img.jpg&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FdMCRCc%2FbtsJtEUtp82%2FwR23SQnkoFTDKdknPgq2zk%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;3840&quot; height=&quot;2160&quot; data-filename=&quot;Screenshot2024-09-03 02_01_55.jpg&quot; data-origin-width=&quot;3840&quot; data-origin-height=&quot;2160&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;브리핑 장면! 참고로 브리핑 텍스트가 &quot;당신&quot;에서 끊긴건 오류가 아니라 텍스트 타이핑 애니메이션 중간에 스샷을 찍어서 그런겁니다.&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;Screenshot2024-09-03 02_06_16.jpg&quot; data-origin-width=&quot;3840&quot; data-origin-height=&quot;2160&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cIV9lF/btsJtMSzfET/ecuJ84JHfKbFwGLIR2k3Qk/img.jpg&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cIV9lF/btsJtMSzfET/ecuJ84JHfKbFwGLIR2k3Qk/img.jpg&quot; data-alt=&quot;울레자즈가 존댓말을 하지 않고 반말로 오만하게 대사를 치는게 참 보기 좋습니다. 존댓말로 자동번역됐으면 게임하다 김 팍 샜을듯&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cIV9lF/btsJtMSzfET/ecuJ84JHfKbFwGLIR2k3Qk/img.jpg&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcIV9lF%2FbtsJtMSzfET%2FecuJ84JHfKbFwGLIR2k3Qk%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;3840&quot; height=&quot;2160&quot; data-filename=&quot;Screenshot2024-09-03 02_06_16.jpg&quot; data-origin-width=&quot;3840&quot; data-origin-height=&quot;2160&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;울레자즈가 존댓말을 하지 않고 반말로 오만하게 대사를 치는게 참 보기 좋습니다. 존댓말로 자동번역됐으면 게임하다 김 팍 샜을듯&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;Screenshot2024-09-03 02_09_44.jpg&quot; data-origin-width=&quot;3840&quot; data-origin-height=&quot;2160&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/DUJXe/btsJtHXYZES/U64dM4kSKjaYFR4mRLKjrK/img.jpg&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/DUJXe/btsJtHXYZES/U64dM4kSKjaYFR4mRLKjrK/img.jpg&quot; data-alt=&quot;툴팁도 잘 번역됐습니다. 아쉽게도 트리거 내의 일부 텍스트는 번역에 실패했네요(Total, Cap 등)&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/DUJXe/btsJtHXYZES/U64dM4kSKjaYFR4mRLKjrK/img.jpg&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FDUJXe%2FbtsJtHXYZES%2FU64dM4kSKjaYFR4mRLKjrK%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;3840&quot; height=&quot;2160&quot; data-filename=&quot;Screenshot2024-09-03 02_09_44.jpg&quot; data-origin-width=&quot;3840&quot; data-origin-height=&quot;2160&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;툴팁도 잘 번역됐습니다. 아쉽게도 트리거 내의 일부 텍스트는 번역에 실패했네요(Total, Cap 등)&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;실제로 플레이를 하면서 느꼈던 것은 만족스럽게 잘 하다가도 간혹 도저히 이해할수 없어서 영어 원문을 보고 싶은 번역체들이 튀어나와서 아쉽다는 거였습니다(물론 이렇게 이상한 번역들만 뇌리에 남아서 확증 편향이 되었을 가능성도 있습니다). 이런 사례는 주로 문장이 아주 긴 경우에 발생했는데요 긴 문장에 대해서는 적당히 잘라서 번역을 수행하면 좀더 개선할 수 있을 것 같다고 생각했습니다. 근데 약 10시간 정도 플레이할 게임을 위해서 20시간 넘게 자동 번역 시스템을 개발하는 건 또 선을 넘는것 같아서 일단 이 정도에서 마무리하고 좀 더 게임을 즐기기로 하였습니다.&lt;/p&gt;</description>
      <category>프로그래밍/NLP</category>
      <category>LLM</category>
      <author>&amp;int;2tdt=t&amp;sup2;+c</author>
      <guid isPermaLink="true">https://bab2min.tistory.com/674</guid>
      <comments>https://bab2min.tistory.com/674#entry674comment</comments>
      <pubDate>Sat, 31 Aug 2024 12:35:59 +0900</pubDate>
    </item>
    <item>
      <title>[C++11] 인덱스 정보를 유지하면서 효율적으로 정렬하기</title>
      <link>https://bab2min.tistory.com/673</link>
      <description>&lt;p style=&quot;text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;정렬은 컴퓨터 알고리즘에서 매우 자주 쓰이는 중요한 알고리즘입니다. 그래서 대부분의 프로그래밍 언어들은 &lt;span&gt;정렬하는 방법을&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;언어 문법 차원에서든 기본 라이브러리로든 반드시 제공합니다. 언어 차원에서 제공해주는 정렬 함수는 대체로 최적화가 잘 되어 있어 빠르므로, 컴공 과제를 푸는게 아닌 이상은 정렬을 직접 구현해서 쓸 일은 거의 없지요. 그러나 종종 기본적으로 제공되는 정렬 함수만으로는 부족함을 느낄 때가 있습니다. 바로 인덱스 정보를 유지하면서 정렬을 해야하는 경우입니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1004&quot; data-origin-height=&quot;417&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/dgtPcP/btrDBtJyw9x/fQRpjiUkVTuSKzZpKrOWZk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/dgtPcP/btrDBtJyw9x/fQRpjiUkVTuSKzZpKrOWZk/img.png&quot; data-alt=&quot;배열을 정렬할 때 원본의 인덱스 정보를 유지하는 작업은 생각보다 자주 필요하다.&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/dgtPcP/btrDBtJyw9x/fQRpjiUkVTuSKzZpKrOWZk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FdgtPcP%2FbtrDBtJyw9x%2FfQRpjiUkVTuSKzZpKrOWZk%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;1004&quot; height=&quot;417&quot; data-origin-width=&quot;1004&quot; data-origin-height=&quot;417&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;배열을 정렬할 때 원본의 인덱스 정보를 유지하는 작업은 생각보다 자주 필요하다.&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;위의 그림에서와 같이 총 5개의 문자열로 이뤄진 배열을 정렬하는데, 정렬 후 배열의 각 요소들이 원래 어느 위치에 있었는지를 함께 파악해야하는 경우가 있습니다. 배열과 관련된 다양한 문제를 해결하다보면 생각보다 자주 필요한 작업인데요, 이에 대한 기능을 자체적으로 제공해주는 언어는 거의 없습니다. 왜냐면 사실 몇 가지 손질만 하면 기본 정렬 함수를 통해 위의 문제를 해결할 수 있거든요. 가장 간단한 방법은 배열의 각 요소들을 직접 정렬하는 대신 &lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;(요소, 인덱스)&lt;/span&gt; 형태의 튜플의 배열을 만든 뒤, 이를 정렬하는 것입니다. 그리고 다시 요소와 인덱스를 떼어서 필요한 곳에 쓰면 대성공이지요.&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;그런데 C++을 사용하는 개발자 입장에서는 이 방법이 영 탐탁지 않을지 모릅니다. 왠지 이 방법이 가장 빠른 방법이 아닐것 같거든요. 그래서 이번 포스팅에서는 C++11을 기준으로, 인덱스 정보를 유지하면서 효율적으로 배열을 정렬하는 방법에 대해서 고민해보았습니다.&lt;/p&gt;
&lt;h3 style=&quot;text-align: justify;&quot; data-ke-size=&quot;size23&quot;&gt;문제 상황 정의&amp;nbsp;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;먼저 우리가 최적화하여 해결해보고자 하는 문제를 명확히 정의해보도록 하겠습니다. 1) 정렬가능한 타입 T의 값 n개가 연속한 메모리 공간에 위치해 있습니다. 2) n개의 T값을 inplace&lt;sup class=&quot;footnote&quot;&gt;&lt;a href=&quot;#footnote_673_1&quot; id=&quot;footnote_link_673_1&quot; onmouseover=&quot;tistoryFootnote.show(this, 673, 1)&quot; onmouseout=&quot;tistoryFootnote.hide(673, 1)&quot; style=&quot;color:#f9650d; font-family: Verdana, Sans-serif; display: inline;&quot;&gt;&lt;span style=&quot;display: none;&quot;&gt;[각주:&lt;/span&gt;1&lt;span style=&quot;display: none;&quot;&gt;]&lt;/span&gt;&lt;/a&gt;&lt;/sup&gt;로 정렬하고, 추가로 각 T의 요소들의 원래 위치를 구하여 함께 반환해야 합니다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;장황하게 적어놨지만 아주 명확한 요구사항이죠? 이 요구사항에 맞춰서 바로 떠올릴 수 있는 구현은 다음과 같은 모양일 겁니다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;가장 기본적인 방법&lt;/h3&gt;
&lt;p&gt;&lt;textarea class=&quot;cpp&quot; name=&quot;code&quot;&gt;#include &amp;lt;vector&amp;gt;
#include &amp;lt;algorithm&amp;gt;
  
using namespace std;

template&amp;lt;class A&amp;gt; // 임의의 타입 A에 대해
vector&amp;lt;size_t&amp;gt; sortBaseline(vector&amp;lt;A&amp;gt;&amp;amp; v) // vector&amp;lt;A&amp;gt; v를 입력 받아서
{
    vector&amp;lt;pair&amp;lt;A, size_t&amp;gt;&amp;gt; t;
    vector&amp;lt;size_t&amp;gt; ret;
    // 임시 벡터 t에 A와 그 인덱스 번호 쌍을 함께 입력
    for(size_t i = 0; i &amp;lt; v.size(); ++i)
    {
        t.emplace_back(v[i], i);
    }

    // 임시 벡터 t를 대신 정렬하고
    sort(t.begin(), t.end());
    // A의 정렬 결과를 다시 v에 쓰고, 원본의 인덱스들은 ret에 쓴다
    for(size_t i = 0; i &amp;lt; v.size(); ++i)
    {
        v[i] = t[i].first;
        ret.emplace_back(t[i].second);
    }
    return ret;
}
&lt;/textarea&gt;&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;쉽게 짠 만큼 아주 비효율적인 코드지요. 일단 임시 벡터 t를 쓰는게 마음에 걸립니다. 입력 받은 벡터 v에서 모든 값들을 t에 다 복사해서 옮긴 뒤, 이를 정렬하고, 그 결과를 다시 또 v에 옮기고 있으니깐요. 아주 비효율적입니다. 만약 임의의 타입인 A가 복사하기에 매우 비싸고 거대한 타입이라면 여기서 v에서 t로, t에서 v로 값을 복사하는 데에 아주 큰 오버헤드가 발생할 것입니다. 다행히도 C++11부터는 값을 복사하는 대신 이동시키는 방법이 추가됐죠. 이를 적극 활용해봅시다.&lt;/p&gt;
&lt;p&gt;&lt;textarea class=&quot;cpp&quot; name=&quot;code&quot;&gt;template&amp;lt;class A&amp;gt;
vector&amp;lt;size_t&amp;gt; sortMoveBaseline(vector&amp;lt;A&amp;gt;&amp;amp; v)
{
    vector&amp;lt;pair&amp;lt;A, size_t&amp;gt;&amp;gt; t;
    vector&amp;lt;size_t&amp;gt; ret;
    for(size_t i = 0; i &amp;lt; v.size(); ++i)
    {
        t.emplace_back(move(v[i]), i); // move가 추가됐음
    }

    sort(t.begin(), t.end());
    for(size_t i = 0; i &amp;lt; v.size(); ++i)
    {
        v[i] = move(t[i].first); // 여기도 move가 추가됐음
        ret.emplace_back(t[i].second);
    }
    return ret;
}

&lt;/textarea&gt;&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;기본적으로는 sortBaseline과 동일한 함수인데 A 값을 옮길때 move 함수를 덧붙여 값이 복사 대신 이동되도록 한 것만 차이가 있습니다. A를 복사하는 문제는 해결했으나, 이동해야하는 것 역시 찝찝합니다. 꼭 v에서 t로 값을 이동해서 정렬하고, 다시 이걸 역으로 t에서 v로 이동시켜야만하는 걸까요? 그러지 말고 v에다 대고 바로 정렬을 수행할 순 없을까요?&lt;/p&gt;
&lt;h3 style=&quot;text-align: justify;&quot; data-ke-size=&quot;size23&quot;&gt;레퍼런스 사용하기&lt;/h3&gt;
&lt;p style=&quot;text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;C++에서는 레퍼런스라는 타입을 제공합니다. 포인터처럼 다른 변수를 참조하는 녀석인데, 초기화할때만 참조 대상을 설정할 수 있고, 그 이후에는 해당 변수의 별칭처럼 사용된다는 특징이 있죠.&lt;/p&gt;
&lt;p&gt;&lt;textarea class=&quot;cpp&quot; name=&quot;code&quot;&gt;int a = 10;
int&amp;amp; b = a; // b는 a의 별칭
b = 50; // b에 값을 대입하는 건 a에 값을 대입하는 것과 동일한 효력을 지님
&lt;/textarea&gt;&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;그리고 또 레퍼런스 타입은 std::pair 안에 들어갈 수도 있습니다. 그럼 레퍼런스를 이용해서 위의 문제를 풀어볼 수도 있지 않을까요?&lt;/p&gt;
&lt;p&gt;&lt;textarea class=&quot;cpp&quot; name=&quot;code&quot;&gt;template&amp;lt;class A&amp;gt;
vector&amp;lt;size_t&amp;gt; sortReference(vector&amp;lt;A&amp;gt;&amp;amp; v)
{
    vector&amp;lt;pair&amp;lt;A&amp;amp;, size_t&amp;gt;&amp;gt; t; // A의 레퍼런스와 size_t로 구성된 pair들의 배열
    vector&amp;lt;size_t&amp;gt; ret;
    // 임시 벡터 t에 A와 그 인덱스 번호 쌍을 함께 입력
    for(size_t i = 0; i &amp;lt; v.size(); ++i)
    {
        t.emplace_back(v[i], i); // A&amp;amp; 레퍼런스가 초기화 된다.
    }

    // t를 정렬하게 되면, pair&amp;lt;A&amp;amp;, size_t&amp;gt;에 대한 수정 연산들이 적용되는데,
    // 이 때 A&amp;amp;에 대한 값의 수정은 A&amp;amp;가 참조하는 원본인 v에 적용될 것
    sort(t.begin(), t.end());
    // A의 정렬 결과는 이미 v에 반영되어 있을테니, 원본의 인덱스들만 ret에 쓴다
    for(size_t i = 0; i &amp;lt; v.size(); ++i)
    {
        ret.emplace_back(t[i].second);
    }
    return ret;
}
&lt;/textarea&gt;&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;레퍼런스가 별칭을 지어주는것이라는 걸 이해하면, pair&amp;lt;A&amp;amp;, size_t&amp;gt;의 값을 수정하면 결국 A&amp;amp;가 가리키는 대상인 v가 바로 수정된다는 것도 이해할 수가 있습니다. 이렇게해서 정렬을 수행하면, t를 정렬하는게 바로 v에 반영되겠죠. 참 쉽죠?&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;그런데 사실 위 코드는 제대로 정렬을 수행하지 못합니다. 레퍼런스 타입의 한계 때문입니다. 특정 타입에 대해 정렬이 작동하려면 다음 코드가 문제 없이 작동해야합니다.&lt;/p&gt;
&lt;p&gt;&lt;textarea class=&quot;cpp&quot; name=&quot;code&quot;&gt;X a, b; // X타입의 변수 a, b가 있다고 가정
X t = a; // a의 값을 t에 넣고
a = b; // b의 값을 a에 넣고
b = t; // t의 값을 다시 b에 넣으면
// 이 시점에서 a와 b는 서로 값이 바뀌어 있어야 함
&lt;/textarea&gt;&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;두 변수의 값을 바꿀 때(swap) 흔히 사용하는 방법이죠. 근데 만약 X자리에 일반 타입이 아닌 레퍼런스 타입 X&amp;amp;가 대신 들어간다면 어떻게 될까요?&lt;/p&gt;
&lt;p&gt;&lt;textarea class=&quot;cpp&quot; name=&quot;code&quot;&gt;X&amp;amp; t = a; // t는 a의 별칭으로 선언됨.
a = b; // b의 값을 a에 넣고
b = t; // t(==a)의 값을 다시 b에 넣으면
// t는 그저 a에 대한 별칭이었기 때문에, a = b;를 수행한 뒤 b = a;를 수행할 꼴.
// 결국 두 값이 같아져 버린다!
&lt;/textarea&gt;&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;두 값이 서로 맞바뀌는게 아니라 같아져 버리게 됩니다. 이런!!! 이래서는 제대로 정렬이 수행될 수가 없습니다. 따라서 pair에 A&amp;amp;를 넣더라도, 위와 같이 값을 잠시 저장해둘때에는 레퍼런스(A&amp;amp;) 대신 일반 타입(A)을 사용하도록 해야합니다. std::sort 함수 내부를 뜯어 고쳐야하는 걸까요?&lt;/p&gt;
&lt;h3 style=&quot;text-align: justify;&quot; data-ke-size=&quot;size23&quot;&gt;맞춤형 Iterator를 구현하자&lt;/h3&gt;
&lt;p style=&quot;text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;다행히도 STL에서 제공하는 algorithm들은 내부를 직접 수정하지 않고도, 입력으로 사용하는 iterator를 조절하여서 그 동작을 맞춤형으로 바꿀 수 있게 구현되어 있습니다. 즉, pair&amp;lt;A&amp;amp;, size_t&amp;gt; 타입에 대한 iterator이지만, 값을 잠시 저장해둘 때는 pair&amp;lt;A&amp;amp;, size_t&amp;gt; 대신 pair&amp;lt;A, size_t&amp;gt;를 사용하도록 하는 맞춤형 iterator를 구현하면 위의 문제를 피해갈 수 있다는 것이죠. 이를 위해 iterator에서는 value_type, reference, pointer 등을 직접 지정할 수 있게 하고 있습니다. 그럼 바로 구현해봅시다.&lt;/p&gt;
&lt;p&gt;&lt;textarea class=&quot;cpp&quot; name=&quot;code&quot;&gt;template&amp;lt;class Iter&amp;gt; // pair&amp;lt;A&amp;amp;, size_t&amp;gt;에 대한 Iterator 타입이라고 가정
class RefIdxIterator : public Iter
{
    using FirstRefTy = typename Iter::value_type::first_type; // pair&amp;lt;A&amp;amp;, size_t&amp;gt;의 첫번째 타입, 즉 A&amp;amp;
    using SecondRefTy = typename Iter::value_type::second_type; // 두번째 타입인 size_t
    using FirstValueTy = typename std::remove_reference&amp;lt;FirstRefTy&amp;gt;::type; // A&amp;amp;에서 reference를 뗀 A 타입
    using SecondValueTy = typename std::remove_reference&amp;lt;SecondRefTy&amp;gt;::type; // size_t에서는 reference를 떼어봤자 size_t
public:

    using value_type = pair&amp;lt;FirstValueTy, SecondValueTy&amp;gt;; // pair&amp;lt;A, size_t&amp;gt;이 됨!!!!
    // 여기서 위의 value_type만이 일반 iterator와의 유일한 차이점. 일반적인 iterator라면 value_type이 pair&amp;lt;A&amp;amp;, size_t&amp;gt;가 됨.
    using reference = pair&amp;lt;FirstRefTy, SecondRefTy&amp;gt;&amp;amp;; // pair&amp;lt;A&amp;amp;, size_t&amp;gt;&amp;amp;이 됨
    using pointer = pair&amp;lt;FirstRefTy, SecondRefTy&amp;gt;*; // pair&amp;lt;A&amp;amp;, size_t&amp;gt;&amp;amp;이 됨
    using difference_type = typename Iter::difference_type;

    // 대부분의 연산들은 부모클래스인 Iter에서 상속받고
    // RefIdxIterator에서 쓰일 수 있도록 래핑만 해준다
    RefIdxIterator() {}

    RefIdxIterator(Iter _p) : Iter{ _p }
    {
    }

    RefIdxIterator operator+(difference_type n) const
    {
        return { static_cast&amp;lt;const Iter&amp;amp;&amp;gt;(*this) + n };
    }

    RefIdxIterator operator-(difference_type n) const
    {
        return { static_cast&amp;lt;const Iter&amp;amp;&amp;gt;(*this) - n };
    }

    difference_type operator-(const RefIdxIterator&amp;amp; o) const
    {
        return static_cast&amp;lt;const Iter&amp;amp;&amp;gt;(*this) - static_cast&amp;lt;const Iter&amp;amp;&amp;gt;(o);
    }
};

// 다음은 임의의 Iterator를 RefIdxIterator로 래핑해주는 헬퍼 함수
template&amp;lt;class Ty&amp;gt;
RefIdxIterator&amp;lt;typename std::remove_reference&amp;lt;Ty&amp;gt;::type&amp;gt; makeRefIdxIterator(Ty it)
{
    return { it };
}
&lt;/textarea&gt;&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;아주 간단한 iterator입니다. 대부분의 기능은 부모인 Iter에서 상속받고, 오직 value_type 부분만 약간 손봐줬습니다. 기존 Iterator를 그대로 가져다가 makeRefIdxIterator 함수에 넣으면 자동으로 RefIdxIterator가 되지요. 이 녀석을 이용해 std::sort를 수행하면 이제 레퍼런스가 포함된 pair도 문제 없이 정렬해낼 수 있습니다.&lt;/p&gt;
&lt;p&gt;&lt;textarea class=&quot;cpp&quot; name=&quot;code&quot;&gt;template&amp;lt;class A&amp;gt;
vector&amp;lt;size_t&amp;gt; sortReferenceFixed(vector&amp;lt;A&amp;gt;&amp;amp; v)
{
    vector&amp;lt;pair&amp;lt;A&amp;amp;, size_t&amp;gt;&amp;gt; t; // A의 레퍼런스와 size_t로 구성된 pair들의 배열
    vector&amp;lt;size_t&amp;gt; ret;
    // 임시 벡터 t에 A와 그 인덱스 번호 쌍을 함께 입력
    for(size_t i = 0; i &amp;lt; v.size(); ++i)
    {
        t.emplace_back(v[i], i); // A&amp;amp; 레퍼런스가 초기화 된다.
    }

    // t.begin(), t.end()에 makeRefIdxIterator를 씌워서 sort에 넣는다!
    sort(makeRefIdxIterator(t.begin()), makeRefIdxIterator(t.end()));
    // A의 정렬 결과는 이미 v에 반영되어 있을테니, 원본의 인덱스들만 ret에 쓴다
    for(size_t i = 0; i &amp;lt; v.size(); ++i)
    {
        ret.emplace_back(t[i].second);
    }
    return ret;
}
&lt;/textarea&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;h3 style=&quot;text-align: justify;&quot; data-ke-size=&quot;size23&quot;&gt;Copy와 Move가 호출되는 횟수 비교&lt;/h3&gt;
&lt;p style=&quot;text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;위의 방법들을 기준으로 각각 copy와 move가 얼마나 자주 호출되는지를 세어보면, 실제로 이 방법이 효율적인지 아닌지를 확인해 볼 수 있겠죠. 다음과 같이 copy와 move 연산의 횟수를 세어주는 래퍼 템플릿을 하나 정의해보도록 하겠습니다.&lt;/p&gt;
&lt;p&gt;&lt;textarea class=&quot;cpp&quot; name=&quot;code&quot;&gt;template&amp;lt;class Ty&amp;gt; // 임의의 타입 Ty에 대해
class AssignmentCounter : public Ty // Ty를 상속받은 클래스 AssignmentCounter를 선언
{
    static size_t copy_cnt, move_cnt; // copy와 move 호출 횟수를 저장할 static 변수
public:
    AssignmentCounter() = default;
    ~AssignmentCounter() = default;
    AssignmentCounter(const AssignmentCounter&amp;amp; o) : Ty{o} {copy_cnt++;} // 복사 생성자
    AssignmentCounter(AssignmentCounter&amp;amp;&amp;amp; o) noexcept : Ty{move(o)} {move_cnt++;} // 이동 생성자
    AssignmentCounter&amp;amp; operator=(const AssignmentCounter&amp;amp; o) {static_cast&amp;lt;Ty&amp;amp;&amp;gt;(*this) = o; copy_cnt++; return *this;} // 복사 대입연산
    AssignmentCounter&amp;amp; operator=(AssignmentCounter&amp;amp;&amp;amp; o) noexcept {static_cast&amp;lt;Ty&amp;amp;&amp;gt;(*this) = move(o); move_cnt++; return *this;} // 이동 대입연산

    static void reset() // 카운터 초기화
    {
        copy_cnt = 0;
        move_cnt = 0;
    }

    static void print(ostream&amp;amp; out) // 출력
    {
        out &amp;lt;&amp;lt; &quot;Copy: &quot; &amp;lt;&amp;lt; copy_cnt &amp;lt;&amp;lt; &quot;, Move: &quot; &amp;lt;&amp;lt; move_cnt &amp;lt;&amp;lt; endl;
    }
};
  
  
template&amp;lt;class Ty&amp;gt;
size_t AssignmentCounter&amp;lt;Ty&amp;gt;::copy_cnt = 0;
template&amp;lt;class Ty&amp;gt;
size_t AssignmentCounter&amp;lt;Ty&amp;gt;::move_cnt = 0;
&lt;/textarea&gt;&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;이제 타입 A 대신에 AssignmentCounter&amp;lt;A&amp;gt;를 사용한다면, A의 복사/이동 횟수를 세어볼 수 있습니다. 그럼 테스트를 위해 다음과 같이 A = vector&amp;lt;size_t&amp;gt;로 두고, 이에 대한 정렬을 수행해보겠습니다.&lt;/p&gt;
&lt;p&gt;&lt;textarea class=&quot;cpp&quot; name=&quot;code&quot;&gt;#include &amp;lt;random&amp;gt;

int main()
{
    constexpr size_t vec_size = 2560; // 총 2560개의 요소를 정렬해볼것
    vector&amp;lt;AssignmentCounter&amp;lt;vector&amp;lt;size_t&amp;gt;&amp;gt;&amp;gt; vecs(vec_size), baseline, ref;
    vector&amp;lt;size_t&amp;gt; baseline_idx, ref_idx;
    mt19937_64 rng;
    // 각 벡터에 100개의 랜덤 정수를 삽입
    for(auto&amp;amp; v : vecs)
    {
        for(size_t i = 0; i &amp;lt; 100; ++i) v.emplace_back(rng());
    }

    baseline = vecs;
    AssignmentCounter&amp;lt;vector&amp;lt;size_t&amp;gt;&amp;gt;::reset();
    sort(baseline.begin(), baseline.end()); // 인덱스 정보는 고려하지 않는 단순 배열 정렬
    AssignmentCounter&amp;lt;vector&amp;lt;size_t&amp;gt;&amp;gt;::print(cout &amp;lt;&amp;lt; &quot;[SimpleSort Ops] &quot;);

    baseline = vecs;
    AssignmentCounter&amp;lt;vector&amp;lt;size_t&amp;gt;&amp;gt;::reset();
    baseline_idx = sortBaseline(baseline); // 복사를 이용해 임시 벡터에 값을 옮긴 뒤 정렬
    AssignmentCounter&amp;lt;vector&amp;lt;size_t&amp;gt;&amp;gt;::print(cout &amp;lt;&amp;lt; &quot;[Baseline Ops] &quot;);

    baseline = vecs;
    AssignmentCounter&amp;lt;vector&amp;lt;size_t&amp;gt;&amp;gt;::reset();
    baseline_idx = sortMoveBaseline(baseline); // 이동을 이용해 임시 벡터에 값을 옮기 뒤 정렬
    AssignmentCounter&amp;lt;vector&amp;lt;size_t&amp;gt;&amp;gt;::print(cout &amp;lt;&amp;lt; &quot;[MoveBaseline Ops] &quot;);

    ref = vecs;
    AssignmentCounter&amp;lt;vector&amp;lt;size_t&amp;gt;&amp;gt;::reset();
    ref_idx = sortReferenceFixed(ref); // 레퍼런스를 이용해 정렬
    AssignmentCounter&amp;lt;vector&amp;lt;size_t&amp;gt;&amp;gt;::print(cout &amp;lt;&amp;lt; &quot;[Reference Ops] &quot;);

    if(baseline == ref &amp;amp;&amp;amp; baseline_idx == ref_idx) // 결과가 동일한 경우
    {
        cout &amp;lt;&amp;lt; &quot;All Result Matched&quot; &amp;lt;&amp;lt; endl;
    }
    else // 동일하지 않은 경우
    {
        cout &amp;lt;&amp;lt; &quot;Error!! Mismatched Result!!&quot; &amp;lt;&amp;lt; endl;
    }
    return 0;
}

&lt;/textarea&gt;&lt;textarea class=&quot;text&quot; name=&quot;code&quot;&gt;[SimpleSort Ops] Copy: 0, Move: 26871
[Baseline Ops] Copy: 5120, Move: 30966
[MoveBaseline Ops] Copy: 0, Move: 36086
[Reference Ops] Copy: 9606, Move: 17265
All Result Matched
&lt;/textarea&gt;&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;좋습니다~ 일단 결과는 일치하는 것으로 잘 나오네요. 확실히 sortBaseline이나 sortMoveBaseline은 불필요한 copy 및 move가 발생하고 있는것을 확인할 수 있구요. 그런데 sortReferenceFixed의 경우 copy 연산이 일부 발생하고 있는 걸 확인할 수 있습니다. 어디서 문제가 발생했을까요? copy 연산 쪽에 디버그 브레이킹포인트를 찍고 살펴보면 다음과 같이 pair의 이동 생성자 쪽에서 복사가 발생하고 있음을 확인할 수 있습니다. (다음은 GCC 4.8에 포함된 STL pair의 구현 중 일부입니다.)&lt;/p&gt;
&lt;script src=&quot;https://emgithub.com/embed.js?target=https%3A%2F%2Fgithub.com%2Fgcc-mirror%2Fgcc%2Fblob%2Freleases%2Fgcc-4.8%2Flibstdc%2B%2B-v3%2Finclude%2Fbits%2Fstl_pair.h%23L147-152&amp;amp;style=github&amp;amp;showBorder=on&amp;amp;showLineNumbers=on&amp;amp;showFileMeta=on&amp;amp;showCopy=on&quot;&gt;&lt;/script&gt;
&lt;script src=&quot;https://emgithub.com/embed.js?target=https%3A%2F%2Fgithub.com%2Fgcc-mirror%2Fgcc%2Fblob%2Freleases%2Fgcc-4.8%2Flibstdc%2B%2B-v3%2Finclude%2Fbits%2Fstl_pair.h%23L184-191&amp;amp;style=github&amp;amp;showBorder=on&amp;amp;showLineNumbers=on&amp;amp;showFileMeta=on&amp;amp;showCopy=on&quot;&gt;&lt;/script&gt;
&lt;p style=&quot;text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;U1이 A&amp;amp; 타입이라면 std::forward 후에도 A&amp;amp;타입이 되므로, 이동 생성자(A(A&amp;amp;&amp;amp;))가 호출되는 대신 복사 생성자(A(const A&amp;amp;))가 호출됩니다. 이 부분을 강제로 std::move로 고칠수 있다면 복사를 피할 수 있겠죠? STL pair의 내부 구현을 고칠 수 있는 방법이 있을까요........? 아쉽게도 그런 방법은 없기 때문에 위와 같은 상황에서 move가 호출되도록 하는 MovingPair를 직접 구현해보았습니다.&lt;/p&gt;
&lt;p&gt;&lt;textarea class=&quot;cpp&quot; name=&quot;code&quot;&gt;// 기본적인 구현은 표준에 있는 std::pair와 동일하지만, 
// 주석으로 표시된 3개 함수만 forward 대신에 move를 사용한다는 차이점이 있음
template&amp;lt;class T1, class T2&amp;gt;
class MovingPair
{
public:
    T1 first;
    T2 second;

    using first_type = T1;
    using second_type = T2;

    constexpr MovingPair() {}

    template &amp;lt;class U1 = T1, class U2 = T2,
        typename std::enable_if&amp;lt;conjunction&amp;lt;
            std::is_convertible&amp;lt;const U1&amp;amp;, U1&amp;gt;, std::is_convertible&amp;lt;const U2&amp;amp;, U2&amp;gt;
        &amp;gt;::value, int&amp;gt;::type = 0&amp;gt;
    constexpr MovingPair(const T1&amp;amp; _Val1, const T2&amp;amp; _Val2)
        : first(_Val1), second(_Val2) 
    {}

    template &amp;lt;class U1 = T1, class U2 = T2,
        typename std::enable_if&amp;lt;!conjunction&amp;lt;
            std::is_convertible&amp;lt;const U1&amp;amp;, U1&amp;gt;, std::is_convertible&amp;lt;const U2&amp;amp;, U2&amp;gt;
        &amp;gt;::value, int&amp;gt;::type = 0&amp;gt;
    constexpr explicit MovingPair(const T1&amp;amp; _Val1, const T2&amp;amp; _Val2)
        : first(_Val1), second(_Val2) 
    {}

    template &amp;lt;class U1, class U2,
        typename std::enable_if&amp;lt;conjunction&amp;lt;
        std::is_convertible&amp;lt;U1, T1&amp;gt;, std::is_convertible&amp;lt;U2, T2&amp;gt;&amp;gt;::value,
        int&amp;gt;::type = 0&amp;gt;
    constexpr MovingPair(U1&amp;amp;&amp;amp; _Val1, U2&amp;amp;&amp;amp; _Val2)
        : first(std::forward&amp;lt;U1&amp;gt;(_Val1)), second(std::forward&amp;lt;U2&amp;gt;(_Val2)) 
    {}

    template &amp;lt;class U1, class U2,
        typename std::enable_if&amp;lt;!conjunction&amp;lt;
            std::is_convertible&amp;lt;U1, T1&amp;gt;, std::is_convertible&amp;lt;U2, T2&amp;gt;
        &amp;gt;::value, int&amp;gt;::type = 0&amp;gt;
    constexpr explicit MovingPair(U1&amp;amp;&amp;amp; _Val1, U2&amp;amp;&amp;amp; _Val2)
        : first(std::forward&amp;lt;U1&amp;gt;(_Val1)), second(std::forward&amp;lt;U2&amp;gt;(_Val2)) 
    {}

    template &amp;lt;class U1, class U2,
        typename std::enable_if&amp;lt;conjunction&amp;lt;
            std::is_convertible&amp;lt;const U1&amp;amp;, T1&amp;gt;, std::is_convertible&amp;lt;const U2&amp;amp;, T2&amp;gt;
        &amp;gt;::value, int&amp;gt;::type = 0&amp;gt;
    constexpr MovingPair(const MovingPair&amp;lt;U1, U2&amp;gt;&amp;amp; o)
        : first(o.first), second(o.second) 
    {}

    template &amp;lt;class U1, class U2,
        typename std::enable_if&amp;lt;!conjunction&amp;lt;
            std::is_convertible&amp;lt;const U1&amp;amp;, T1&amp;gt;, std::is_convertible&amp;lt;const U2&amp;amp;, T2&amp;gt;
        &amp;gt;::value, int&amp;gt;::type = 0&amp;gt;
    constexpr explicit MovingPair(const MovingPair&amp;lt;U1, U2&amp;gt;&amp;amp; o)
        : first(o.first), second(o.second) 
    {}

    template &amp;lt;class U1, class U2,
        typename std::enable_if&amp;lt;conjunction&amp;lt;
            std::is_convertible&amp;lt;U1, T1&amp;gt;, std::is_convertible&amp;lt;U2, T2&amp;gt;
        &amp;gt;::value, int&amp;gt;::type = 0&amp;gt;
    constexpr MovingPair(MovingPair&amp;lt;U1, U2&amp;gt;&amp;amp;&amp;amp; o) // 여기
        : first(std::move(o.first)), second(std::move(o.second)) 
    {}

    template &amp;lt;class U1, class U2,
        typename std::enable_if&amp;lt;!conjunction&amp;lt;
            std::is_convertible&amp;lt;U1, T1&amp;gt;, std::is_convertible&amp;lt;U2, T2&amp;gt;
        &amp;gt;::value, int&amp;gt;::type = 0&amp;gt;
    constexpr explicit MovingPair(MovingPair&amp;lt;U1, U2&amp;gt;&amp;amp;&amp;amp; o) // 여기
        : first(std::move(o.first)), second(std::move(o.second)) 
    {}

    template &amp;lt;class U1, class U2&amp;gt;
    MovingPair&amp;amp; operator=(const MovingPair&amp;lt;U1, U2&amp;gt;&amp;amp; o)
    {
        first = o.first;
        second = o.second;
        return *this;
    }

    template &amp;lt;class U1, class U2&amp;gt; // 여기
    MovingPair&amp;amp; operator=(MovingPair&amp;lt;U1, U2&amp;gt;&amp;amp;&amp;amp; o)
    {
        first = std::move(o.first);
        second = std::move(o.second);
        return *this;
    }

    template&amp;lt;class U1, class U2&amp;gt;
    bool operator&amp;lt;(const MovingPair&amp;lt;U1, U2&amp;gt;&amp;amp; o) const
    {
        if (first &amp;lt; o.first) return true;
        if (o.first &amp;lt; first) return false;
        return second &amp;lt; o.second;
    }

    friend void swap(MovingPair&amp;amp; a, MovingPair&amp;amp; b)
    {
        using std::swap;
        swap(a.first, b.first);
        swap(a.second, b.second);
    }
};
&lt;/textarea&gt;&lt;textarea class=&quot;cpp&quot; name=&quot;code&quot;&gt;// 기본적으로는 위의 sortReferenceFixed 함수와 동일
template&amp;lt;class A&amp;gt;
vector&amp;lt;size_t&amp;gt; sortReferenceFixed2(vector&amp;lt;A&amp;gt;&amp;amp; v)
{
    vector&amp;lt;MovingPair&amp;lt;A&amp;amp;, size_t&amp;gt;&amp;gt; t; // pair 대신 MovingPair 사용
    vector&amp;lt;size_t&amp;gt; ret;
    for(size_t i = 0; i &amp;lt; v.size(); ++i)
    {
        t.emplace_back(v[i], i);
    }

    sort(makeRefIdxIterator(t.begin()), makeRefIdxIterator(t.end()));
    for(size_t i = 0; i &amp;lt; v.size(); ++i)
    {
        ret.emplace_back(t[i].second);
    }
    return ret;
}
&lt;/textarea&gt;&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;그리고 sortReferenceFixed2에서 pair 대신 MovingPair를 사용하게 함으로써 복사생성자가 불필요하게 호출되지 않고 이동생성자만 호출되도록 고칠 수 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://godbolt.org/z/Y1hqo4PM9&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;https://godbolt.org/z/Y1hqo4PM9&lt;/a&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;작동되는 전체 코드는 위의 링크에서 확인해 볼 수 있어요~!&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;유용성과 한계&lt;/h3&gt;
&lt;p style=&quot;text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;다소 복잡한 과정을 거치긴 했지만, 이제 레퍼런스를 통한 정렬이 가능해졌습니다. 이를 단순히 정렬 전의 인덱스 정보를 계산하는데에만 쓰긴 아깝습니다. &lt;span&gt;곰곰히 생각해보면&lt;span&gt; 이 기능이 다양한 곳에 유용하게 쓰일 수 있다는 걸 알수 있는데요, 먼저 임의의 배열 2개 이상을 묶어서 함께 정렬하는데에 사용할 수 있죠(아래의 zipped sort 단락 참조). &lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;또 Random Access를 지원하지 않는 Container에 대해서도 각 요소의 reference만 떼어와서 vector로 만들고, 이를 정렬하는 방식으로 빠르게 정렬을 수행하는데에도 쓸 수 있습니다. C++의 레퍼런스 타입은 컴파일 이후 대체로 포인터로 치환되므로 전체 Container를 복사할 필요 없이 포인터 배열만 추가로 사용한다는 점은 최적화 시 큰 이점이 됩니다.&lt;/span&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;&lt;span&gt;다만 이 방법이 항상 최적은 아닐 수 있습니다. 여기서는 비교적 무거운 타입을 예시로 사용했지만, 단순 정수/실수 타입 등은 레퍼런스/포인터를 통해 간접 참조를 하게 되는게 오히려 성능 상 손해일 수 있습니다. 이 경우는 차라리 전체 값을 복사하는게 더 효율적일 수도 있죠. 이에 대해서는 어떤 방법이 더 효율적인지 구체적으로 실험해볼 필요가 있겠습니다.&lt;/span&gt;&lt;/span&gt;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;Zipped Sort&lt;/h3&gt;
&lt;p style=&quot;text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;여기서 다룬, 둘 이상의 배열을 튜플로 엮어서 함께 정렬을 한다는 개념은 Zipped Sort라고 불립니다. 그리고 앞으로 C++ 표준 라이브러리 ranges v3에 들어갈 수 있도록 현재 표준 작업을 진행 중에 있습니다. (&lt;a href=&quot;https://stackoverflow.com/a/32720638&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;참고 링크&lt;/a&gt;)&lt;/p&gt;
&lt;p&gt;&lt;textarea class=&quot;cpp&quot; name=&quot;code&quot;&gt;#include &amp;lt;range/v3/all.hpp&amp;gt;
#include &amp;lt;vector&amp;gt;
#include &amp;lt;string&amp;gt;
#include &amp;lt;iostream&amp;gt;

using namespace ranges;

int main() 
{
    std::vector&amp;lt;std::string&amp;gt; a1{&quot;Kiwi&quot;, &quot;Tomato&quot;, &quot;Lemon&quot;, &quot;Apple&quot;, &quot;Orange&quot; };
    std::vector&amp;lt;int&amp;gt; a2{ 0, 1, 2, 3, 4 };
    sort(view::zip(a1, a2), std::less&amp;lt;&amp;gt;{}, &amp;amp;std::pair&amp;lt;std::string, int&amp;gt;::first); 
    std::cout &amp;lt;&amp;lt; view::all(a1) &amp;lt;&amp;lt; '\n'; // [Apple,Kiwi,Lemon,Orange,Tomato]
    std::cout &amp;lt;&amp;lt; view::all(a2) &amp;lt;&amp;lt; '\n'; // [3,0,2,4,1]
}&lt;/textarea&gt;&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;정말 기대되는 기능이죠? 하지만 표준에 들어가서 현업에서 널리 쓰이게 되는 때는 머나먼 미래가 될 것 같네요. 대부분의 컴파일러가 ranges 라이브러리를 완벽하게 지원하게 되어, 이 포스팅과 같은 고민 없이 그냥 ranges를 쓸 수 있으면 제일 좋겠지만, 일단 그 전까지는 위와 같이 우회하는 방법을 고려해봐야겠습니다.&lt;/p&gt;
&lt;div class=&quot;footnotes&quot;&gt;
  &lt;ol class=&quot;footnotes&quot;&gt;
    &lt;li id=&quot;footnote_673_1&quot;&gt;정렬한 결과 값을 입력한 값과 동일한 위치에 쓰는 것을 뜻함. 이 경우 결과값을 별도의 메모리에 저장할 필요가 없기 때문에 메모리 효율적임. &lt;a href=&quot;#footnote_link_673_1&quot;&gt;[본문으로]&lt;/a&gt;&lt;/li&gt;
  &lt;/ol&gt;
&lt;/div&gt;</description>
      <category>프로그래밍/테크닉</category>
      <category>c++</category>
      <author>&amp;int;2tdt=t&amp;sup2;+c</author>
      <guid isPermaLink="true">https://bab2min.tistory.com/673</guid>
      <comments>https://bab2min.tistory.com/673#entry673comment</comments>
      <pubDate>Mon, 30 May 2022 01:01:40 +0900</pubDate>
    </item>
    <item>
      <title>형태소 분석기의 모호성 해소 성능을 평가해보자</title>
      <link>https://bab2min.tistory.com/672</link>
      <description>&lt;h3 data-ke-size=&quot;size23&quot;&gt;문제 상황&lt;/h3&gt;
&lt;p style=&quot;text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;한국어 텍스트를 기계를 통해 분석하다 보면 기계가 아직 얼마나 한국어를 모르는지 다시 한 번 느끼게 됩니다. 한국인이 봤을땐 누가봐도 명백하게 모호하지 않은 문장을 헷갈려할 때가 바로 대표적인 예일겁니다. 규칙 활용하는 동사 &quot;묻다&quot;와 불규칙 활용하는 동사 &quot;묻다&quot;, 또 규칙활용하는 &quot;물다&quot;는 그 활용형이 서로 겹칩니다. 아래 표로 정리하면 더 명확해지죠:&lt;/p&gt;
&lt;table class=&quot;std-table&quot; data-ke-align=&quot;alignLeft&quot;&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;th&gt;&amp;nbsp;&lt;/th&gt;
&lt;th&gt;묻다 2&lt;/th&gt;
&lt;th&gt;묻다 3&lt;/th&gt;
&lt;th&gt;물다 1&lt;/th&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;th&gt;뜻&lt;/th&gt;
&lt;td&gt;물건을 특정 장소 속에 넣고 다른 물질로 위를 덮어서 가리다.&lt;/td&gt;
&lt;td&gt;대답이나 설명을 요구하며 말하다.&lt;/td&gt;
&lt;td&gt;어떤 것을 윗입술과 아랫입술 사이에 또는 윗니와 아랫니 사이에 끼워 넣고 벌어진 두 입술이나 이를 다물어 누르다.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;th&gt;예문&lt;/th&gt;
&lt;td&gt;거름을 묻다, 시신을 묻다&lt;/td&gt;
&lt;td&gt;견해를 묻다, 근황을 묻다&lt;/td&gt;
&lt;td&gt;담배를 물다, 젖병을 물다&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;th&gt;활용&lt;/th&gt;
&lt;td&gt;묻어, 묻으니&lt;/td&gt;
&lt;td&gt;물어, 물으니&lt;/td&gt;
&lt;td&gt;물어, 물으니&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;th&gt;활용&lt;/th&gt;
&lt;td&gt;묻고&lt;/td&gt;
&lt;td&gt;묻고&lt;/td&gt;
&lt;td&gt;물고&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;(위 표의 내용은 &lt;a href=&quot;https://krdict.korean.go.kr/&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;한국어 기초사전&lt;/a&gt;을 바탕으로 하였음)&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;묻다2,3과 물다1은 그 기본형이 명확히 다르기 때문에 형태소 분석기를 돌리고 나면 둘이 명확하게 구분되어야합니다. 즉, &quot;견해를 &lt;b&gt;물었다&lt;/b&gt;&quot;에서의 &quot;물었다&quot;는 &lt;b&gt;묻/VV 었/EP 다/EF&lt;/b&gt;가 되어야하는 반면, &quot;젖병을 &lt;b&gt;물었다&lt;/b&gt;&quot;에서의 &quot;물었다&quot;는 &lt;b&gt;물/VV 었/EP 다/EF&lt;/b&gt;가 되어야하죠. 문제는 이 둘의 활용형이 자음이 이어지는 경우에만 다르고(묻고 vs 물고), 모음이 이어지는 경우에는 동일하다(물어, 물으니)는 것입니다. 따라서 단순히 형태를 분석하는 것만으로는 올바른 결과를 선택할 수가 없습니다. 분석기가 주변 맥락까지 모두 고려해야하는 것이죠.&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;반대로 묻다2와 묻다3은 모음으로 이어지는 활용형(묻어 vs 물어)에서는 명확히 구분할 수 있으나, 자음이 이어지는 활용형(묻고, 묻다)에서는 구분할 수 없습니다. 따라서 마찬가지로 묻다2와 묻다3을 구분하는 것 역시 형태 분석뿐만 아니라 의미 분석까지 필요하지만, 이 경우는 일단 이번 포스팅에서는 다루지 않을 예정입니다. 아직 이에 대한 단어 의미 중의성 해소 관련하여 비교할 만한 오픈소스 분석기를 찾아보기 어려운 상황이기 때문입니다.&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;또 다른 사례도 있습니다. 한국어에는 의미는 전혀 다르지만 형태만 동일한 동사와 형용사가 있습니다.&amp;nbsp;&lt;/p&gt;
&lt;table class=&quot;std-table&quot; data-ke-align=&quot;alignLeft&quot;&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;th&gt;&amp;nbsp;&lt;/th&gt;
&lt;th&gt;낫다 1 (동사)&lt;/th&gt;
&lt;th&gt;낫다 2 (형용사)&lt;/th&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;th&gt;뜻&lt;/th&gt;
&lt;td&gt;병이나 상처 등이 없어져 본래대로 되다.&lt;/td&gt;
&lt;td&gt;어떤 것이 다른 것보다 더 좋다.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;th&gt;예문&lt;/th&gt;
&lt;td&gt;감기가 낫다, 다리가 낫다&lt;/td&gt;
&lt;td&gt;다른 사람보다 낫다, 친구보다 낫다&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p style=&quot;text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;대표적인 사례로 '&lt;b&gt;낫다&lt;/b&gt;'가 있는데요, 동사인 &lt;b&gt;낫다&lt;/b&gt;와 형용사인 &lt;b&gt;낫다&lt;/b&gt;가 의미상으로 전혀 다르다보니 한국어 화자는 절대 헷갈리지 않지만 분석기는 이를 자주 헷갈립니다.&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;혹은 일부 명사가 마치 조사가 붙은 것처럼 생겨서 헷갈리는 경우도 있습니다. '종이'라는 형태는 맥락에 따라 &lt;b&gt;종이/NNG&lt;/b&gt;로 분석되어야하기도 하고, 조사 '이'가 붙은 걸로 보아 &lt;b&gt;종/NNG 이/JKS&lt;/b&gt;로 분석되어야하기도 합니다.&lt;/p&gt;
&lt;table class=&quot;std-table&quot; data-ke-align=&quot;alignLeft&quot;&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;th&gt;&amp;nbsp;&lt;/th&gt;
&lt;th&gt;종이&lt;/th&gt;
&lt;th&gt;종 3&lt;/th&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;th&gt;뜻&lt;/th&gt;
&lt;td&gt;나무를 원료로 하여 얇게 만든, 글씨를 쓰고 그림을 그리고 인쇄를 하는 등 여러 가지 일에 쓰는 물건.&lt;/td&gt;
&lt;td&gt;금속을 깊고 둥근 그릇처럼 만들어 거꾸로 매어 달고 안에는 추를 달아, 치거나 흔들어 소리를 내는 물건.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;th&gt;예문&lt;/th&gt;
&lt;td&gt;여기 있는 &lt;b&gt;종이&lt;/b&gt; 좀 복사기에 넣어줘&lt;/td&gt;
&lt;td&gt;여기 있는 &lt;b&gt;종이&lt;/b&gt; 시끄럽게 울린다&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p style=&quot;text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;형태소 분석기가 각 형태소의 세부적인 의미번호까지 분류는 못해준다고 해도, 위의 사례처럼 명확하게 품사나 형태소 배열이 달라지는 경우는 제대로 분석해주는게 맞겠지요. 그래야 이 분석기 결과를 바탕으로 하는 상위 작업들에 오류를 덜 전파할테니깐요. 그래서 이런 저런 텍스트 분석 결과를 보다가 문득 궁금해졌습니다: &quot;과연 공개된 형태소 분석기들은 이런 모호한 문장을 얼마나 정확하게 분석하고 있을까?&quot;&lt;/p&gt;
&lt;h3 style=&quot;text-align: justify;&quot; data-ke-size=&quot;size23&quot;&gt;데이터 구축 &amp;amp; 평가 척도&lt;/h3&gt;
&lt;p style=&quot;text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;평가를 위해서 간단하게 데이터를 구축해보았습니다. 헷갈릴법한 형태소 목록을 몇개 선정하여 그 형태소가 들어간 예문을 생성하는 식으로 진행했습니다. 총 207개의 예문을 생성했으며 전체 데이터셋은 &lt;a href=&quot;https://github.com/bab2min/kiwipiepy/tree/main/benchmark/disambiguate/testset&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;여기&lt;/a&gt;에서 볼 수 있으며, 일부 예시를 뽑자면 다음과 같습니다:&lt;/p&gt;
&lt;table class=&quot;std-table&quot; data-ke-align=&quot;alignLeft&quot;&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;th&gt;형태소&lt;/th&gt;
&lt;th&gt;예문&lt;/th&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;걷/VV&lt;/td&gt;
&lt;td&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;4km 구간을 아주 오래 걸었다.&lt;/li&gt;
&lt;li&gt;걸어서 5분 거리에 있음&lt;/li&gt;
&lt;li&gt;칼날 위를 걸어야 했던 모든 사람들&lt;/li&gt;
&lt;/ul&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;걸/VV&lt;/td&gt;
&lt;td&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;이 번호로 전화를 걸어.&lt;/li&gt;
&lt;li&gt;금메달을 목에 걸었다.&lt;/li&gt;
&lt;li&gt;약속을 지켜야한다는 조건을 걸었는데.&lt;/li&gt;
&lt;/ul&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;적/VV&lt;/td&gt;
&lt;td&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;자세한 건 여기에 적어주세요.&lt;/li&gt;
&lt;li&gt;지금까지 적은 걸 그대로 제출하십시오.&lt;/li&gt;
&lt;li&gt;&quot;그러면 못 써&quot;라고 적었다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;적/VA&lt;/td&gt;
&lt;td&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;협회의 우려가 적지 않다.&lt;/li&gt;
&lt;li&gt;운이 좋았는지 피해가 적었다.&lt;/li&gt;
&lt;li&gt;액수가 너무 적어, 더 늘려줘!&lt;/li&gt;
&lt;/ul&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;화가/NNG&lt;/td&gt;
&lt;td&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;19세기 독일 화가 작품이다.&lt;/li&gt;
&lt;li&gt;르네상스 시기의 대표적인 화가 다빈치&lt;/li&gt;
&lt;li&gt;그는 취미라고 했지만 화가 수준의 그림을 그렸다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;화/NNG&lt;/td&gt;
&lt;td&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;스트레스를 받거나 화가 날 때는?&lt;/li&gt;
&lt;li&gt;화가 치밀어 올라서 밖으로 나갔다.&lt;/li&gt;
&lt;li&gt;그것 때문에 화가 많이 났다고 한다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p style=&quot;text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;평가 방법은 아주 단순합니다. 특정 형태소 분석기에 예문을 입력하고, 그 예문의 분석 결과에서 원하던 형태소가 등장하면 맞힌 걸로, 그렇지 않으면 틀린 걸로 평가합니다. 전체 평가 문장 개수 대비 맞힌 개수의 비율을 계산하여 정확도를 산출합니다. 참 쉽죠?&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;평가 결과&lt;/h3&gt;
&lt;p style=&quot;text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;평가에는 최근 릴리즈된 Kiwi 0.11.0와 konlpy에 등록된 형태소 분석기 중 Komoran, Mecab, Kkma를 사용했습니다. (나머지 Hannanum이나 Okt의 경우 동사/형용사를 구분하여 분석하는 기능이 없어서 아예 평가에서 배제했습니다.) 실험용 코드는 &lt;a href=&quot;https://github.com/bab2min/kiwipiepy/tree/main/benchmark/disambiguate&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;여기&lt;/a&gt; 공개되어 있구요, 일단 최종 결과부터 공유드리면 아래와 같습니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;857&quot; data-origin-height=&quot;730&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/ZWpvL/btrxj7HqnjU/HVbvkRhRXlqRAVH0ym5Zrk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/ZWpvL/btrxj7HqnjU/HVbvkRhRXlqRAVH0ym5Zrk/img.png&quot; data-alt=&quot;자체 구축한 평가 데이터로 각종 형태소 분석기의 모호성 해소 정확도를 평가한 결과&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/ZWpvL/btrxj7HqnjU/HVbvkRhRXlqRAVH0ym5Zrk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FZWpvL%2Fbtrxj7HqnjU%2FHVbvkRhRXlqRAVH0ym5Zrk%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;857&quot; height=&quot;730&quot; data-origin-width=&quot;857&quot; data-origin-height=&quot;730&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;자체 구축한 평가 데이터로 각종 형태소 분석기의 모호성 해소 정확도를 평가한 결과&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;Kiwi가 압도적인 점수 차이로 1등을 했네요. 0.11.0 기준으로 평균 약 80%의 정확도를 달성했습니다. 나머지 형태소 분석기들이 40~50%대에 머무르고 있는 걸 보면 크게 우수하다는 걸 알 수 있습니다. 실제 분석 결과를 일부 살펴볼까요? 먼저 불규칙 활용을 해서 헷갈리는 동사들입니다.&lt;/p&gt;
&lt;div style=&quot;overflow-x: auto;&quot;&gt;
&lt;table class=&quot;std-table&quot; style=&quot;width: 160%; height: 486px;&quot; data-ke-align=&quot;alignLeft&quot;&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;th&gt;&amp;nbsp;&lt;/th&gt;
&lt;th&gt;Kiwi&lt;/th&gt;
&lt;th&gt;Komoran&lt;/th&gt;
&lt;th&gt;Mecab&lt;/th&gt;
&lt;th&gt;Kkma&lt;/th&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;th rowspan=&quot;3&quot;&gt;걷/VV&lt;/th&gt;
&lt;td class=&quot;green&quot;&gt;즐기/VV 는/E 여행/N ,/S &lt;b&gt;걷/VV&lt;/b&gt; 으면서/E 보/VV 고/E 듣/VV 은/E 것/N !/S&lt;/td&gt;
&lt;td class=&quot;green&quot;&gt;즐기/VV 는/E 여행/N ,/S &lt;b&gt;걷/VV&lt;/b&gt; 으면서/E 보/VV 고/E 들/VV 은/E 것/N !/S&lt;/td&gt;
&lt;td class=&quot;green&quot;&gt;즐기/VV 는/E 여행/N ,/S &lt;b&gt;걷/VV&lt;/b&gt; 으면서/E 보/VX 고/E 들/VV 은/E 것/N !/S&lt;/td&gt;
&lt;td class=&quot;green&quot;&gt;즐기/VV 는/E 여행/N ,/S &lt;b&gt;걷/VV&lt;/b&gt; 으면서/E 보/VV 고/E 듣/VV 은/E 것/N !/S&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td class=&quot;green&quot;&gt;조금/N 만/J &lt;b&gt;걷/VV&lt;/b&gt; 어도/E 다리/N 가/J 아프/VA 어요/E&lt;/td&gt;
&lt;td class=&quot;green&quot;&gt;조금/N 만/J &lt;b&gt;걷/VV&lt;/b&gt; 어도/E 다리/N 가/J 아프/VA 아요/E&lt;/td&gt;
&lt;td class=&quot;green&quot;&gt;조금/M 만/J &lt;b&gt;걷/VV&lt;/b&gt; 어도/E 다리/N 가/J 아프/VA 아요/E&lt;/td&gt;
&lt;td class=&quot;green&quot;&gt;조금/N 만/J &lt;b&gt;걷/VV&lt;/b&gt; 어도/E 다리/N 가/J 아프/VA 아요/E&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td class=&quot;green&quot;&gt;사막/N 을/J &lt;b&gt;걷/VV&lt;/b&gt; 어/E 다니/VV 는/E 배/N 이/VC ᆫ/E 낙타/N 는/J ~/S&lt;/td&gt;
&lt;td class=&quot;green&quot;&gt;사막/N 을/J &lt;b&gt;걷/VV&lt;/b&gt; 어/E 다니/VV 는/E 배/N 이/VC ㄴ/E 낙타는~/N&lt;/td&gt;
&lt;td class=&quot;red&quot;&gt;사막/N 을/J &lt;b&gt;걸/VV&lt;/b&gt; 어/E 다니/VV 는/E 배/N 이/VC ᆫ/E 낙타/N 는/J ~/S&lt;/td&gt;
&lt;td class=&quot;green&quot;&gt;사막/N 을/J &lt;b&gt;걷/VV&lt;/b&gt; 어/E 다니/VV 는/E 배/N 이/VC ㄴ/E 낙타/N 는/J ~/S&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;th rowspan=&quot;4&quot;&gt;걸/VV&lt;/th&gt;
&lt;td class=&quot;green&quot;&gt;다시/M 시동/N 을/J &lt;b&gt;걸/VV&lt;/b&gt; 었/E 다/E ./S&lt;/td&gt;
&lt;td class=&quot;red&quot;&gt;다시/M 시동/N 을/J &lt;b&gt;걷/VV&lt;/b&gt; 었/E 다/E ./S&lt;/td&gt;
&lt;td class=&quot;green&quot;&gt;다시/M 시동/N 을/J &lt;b&gt;걸/VV&lt;/b&gt; 었/E 다/E ./S&lt;/td&gt;
&lt;td class=&quot;red&quot;&gt;다시/M 시동/N 을/J &lt;b&gt;걷/VV&lt;/b&gt; 었/E 다/E ./S&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td class=&quot;green&quot;&gt;입학/N 사정관/N 제/X 에/J 제동/N 을/J &lt;b&gt;걸/VV&lt;/b&gt; 었/E 던/E 정부/N 의/J 법안/N&lt;/td&gt;
&lt;td class=&quot;red&quot;&gt;입학/N 사정관/N 제/X 에/J 제동/N 을/J &lt;b&gt;걷/VV&lt;/b&gt; 었/E 던/E 정부/N 의/J 법안/N&lt;/td&gt;
&lt;td class=&quot;green&quot;&gt;입학/N 사정/N 관제/N 에/J 제동/N 을/J &lt;b&gt;걸/VV&lt;/b&gt; 었/E 던/E 정부/N 의/J 법안/N&lt;/td&gt;
&lt;td class=&quot;red&quot;&gt;입학/N 사정/N 관제/N 에/J 제동/N 을/J &lt;b&gt;걷/VV&lt;/b&gt; 었/E 더/E ㄴ/E 정부/N 의/J 법안/N&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td class=&quot;green&quot;&gt;이/M 번호/N 로/J 전화/N 를/J &lt;b&gt;걸/VV&lt;/b&gt; 어/E ./S&lt;/td&gt;
&lt;td class=&quot;red&quot;&gt;이/M 번호/N 로/J 전화/N 를/J &lt;b&gt;걷/VV&lt;/b&gt; 어/E ./S&lt;/td&gt;
&lt;td class=&quot;green&quot;&gt;이/M 번호/N 로/J 전화/N 를/J &lt;b&gt;걸/VV&lt;/b&gt; 어/E ./S&lt;/td&gt;
&lt;td class=&quot;red&quot;&gt;이/M 번호/N 로/J 전화/N 를/J &lt;b&gt;걷/VV&lt;/b&gt; 어/E ./S&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td class=&quot;green&quot;&gt;금메달/N 을/J 목/N 에/J &lt;b&gt;걸/VV&lt;/b&gt; 었/E 다/E ./S&lt;/td&gt;
&lt;td class=&quot;red&quot;&gt;금메달/N 을/J 목/N 에/J &lt;b&gt;걷/VV&lt;/b&gt; 었/E 다/E ./S&lt;/td&gt;
&lt;td class=&quot;green&quot;&gt;금메달/N 을/J 목/N 에/J &lt;b&gt;걸/VV&lt;/b&gt; 었/E 다/E ./S&lt;/td&gt;
&lt;td class=&quot;red&quot;&gt;금메달/N 을/J 목/N 에/J &lt;b&gt;걷/VV&lt;/b&gt; 었/E 다/E ./S&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;th rowspan=&quot;3&quot;&gt;묻/VV&lt;/th&gt;
&lt;td class=&quot;green&quot;&gt;외압/N 유무/N 를/J &lt;b&gt;묻/VV&lt;/b&gt; 어/E 보/VX ᆫ다/E&lt;/td&gt;
&lt;td class=&quot;green&quot;&gt;외압/N 유무/N 를/J &lt;b&gt;묻/VV&lt;/b&gt; 어/E 보/VV ㄴ다/E&lt;/td&gt;
&lt;td class=&quot;red&quot;&gt;외압/N 유무/N 를/J &lt;b&gt;물/VV&lt;/b&gt; 어/E 보/VV ᆫ다/E&lt;/td&gt;
&lt;td class=&quot;green&quot;&gt;외압/N 유무/N 를/J &lt;b&gt;묻/VV&lt;/b&gt; 어/E 보/VV ㄴ다/E&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td class=&quot;green&quot;&gt;그것/N 에/J 대하/VV 어/E &lt;b&gt;묻/VV&lt;/b&gt; 을/E 때/N 부터/J 이/M 고민/N 은/J 시작/N 되/X ᆫ다/E ./S&lt;/td&gt;
&lt;td class=&quot;red&quot;&gt;그것/N 에/J 대하/VV 아/E &lt;b&gt;물/N&lt;/b&gt; 을/J 때/N 부터/J 이/M 고민/N 은/J 시작/N 되/X ㄴ다/E ./S&lt;/td&gt;
&lt;td class=&quot;red&quot;&gt;그것/N 에/J 대하/VV 아/E &lt;b&gt;물/N&lt;/b&gt; 을/J 때/N 부터/J 이/M 고민/N 은/J 시작/N 되/X ᆫ다/E ./S&lt;/td&gt;
&lt;td class=&quot;green&quot;&gt;그것/N 에/J 대하/VV 어/E &lt;b&gt;묻/VV&lt;/b&gt; 을/E 때/N 부터/J 이/M 고민/N 은/J 시작/N 되/X ㄴ다/E ./S&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td class=&quot;green&quot;&gt;그것/N 이/J 가능/N 하/X ᆫ지/E 를/J &lt;b&gt;묻/VV&lt;/b&gt; 어/E !/S&lt;/td&gt;
&lt;td class=&quot;green&quot;&gt;그/N 게/E 가능/X 하/X ㄴ지/E 를/J &lt;b&gt;묻/VV&lt;/b&gt; 어/E !/S&lt;/td&gt;
&lt;td class=&quot;red&quot;&gt;그것/N 이/J 가능/N 하/X ㄴ지/E 를/J &lt;b&gt;물/VV&lt;/b&gt; 어/E !/S&lt;/td&gt;
&lt;td class=&quot;green&quot;&gt;그/VV 게/E 가능/N 하/X ㄴ/E 지/N 를/J &lt;b&gt;묻/VV&lt;/b&gt; 어/E !/S&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;th rowspan=&quot;4&quot;&gt;물/VV&lt;/th&gt;
&lt;td class=&quot;green&quot;&gt;아주/M 비싸/VA ᆫ/E 이자/N 를/J &lt;b&gt;물/VV&lt;/b&gt; 었/E 다/E&lt;/td&gt;
&lt;td class=&quot;red&quot;&gt;아주/M 비싸/VA ㄴ/E 이자/N 를/J &lt;b&gt;묻/VV&lt;/b&gt; 었/E 다/E&lt;/td&gt;
&lt;td class=&quot;green&quot;&gt;아주/M 비싸/VA ᆫ/E 이자/N 를/J &lt;b&gt;물/VV&lt;/b&gt; 었/E 다/E&lt;/td&gt;
&lt;td class=&quot;red&quot;&gt;아주/M 비싸/VA ㄴ/E 이자/N 를/J &lt;b&gt;묻/VV&lt;/b&gt; 었/E 다/E&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td class=&quot;red&quot;&gt;세금/N 을/J 많이/M &lt;b&gt;묻/VV&lt;/b&gt; 었/E 다/E ./S&lt;/td&gt;
&lt;td class=&quot;red&quot;&gt;세금/N 을/J 많이/M &lt;b&gt;묻/VV&lt;/b&gt; 었/E 다/E ./S&lt;/td&gt;
&lt;td class=&quot;green&quot;&gt;세금/N 을/J 많이/M &lt;b&gt;물/VV&lt;/b&gt; 었/E 다/E ./S&lt;/td&gt;
&lt;td class=&quot;red&quot;&gt;세금/N 을/J 많이/M &lt;b&gt;묻/VV&lt;/b&gt; 었/E 다/E ./S&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td class=&quot;green&quot;&gt;사과/N 하/X 며/E 치료비/N 를/J &lt;b&gt;물/VV&lt;/b&gt; 어/E 주/VX 겠/E 다고/E 하/VV 었/E 다/E ./S&lt;/td&gt;
&lt;td class=&quot;red&quot;&gt;사과/N 하/X 며/E 치료비/N 를/J &lt;b&gt;묻/VV&lt;/b&gt; 어/E 주/VX 겠/E 다고/E 하/VV 았/E 다/E ./S&lt;/td&gt;
&lt;td class=&quot;green&quot;&gt;사과/N 하/X 며/E 치료비/N 를/J &lt;b&gt;물/VV&lt;/b&gt; 어/E 주/VX 겠/E 다고/E 하/VX 았/E 다/E ./S&lt;/td&gt;
&lt;td class=&quot;red&quot;&gt;사과/N 하/X 며/E 치료비/N 를/J &lt;b&gt;묻/VV&lt;/b&gt; 어/E 주/VX 겠/E 다고/E 하/VV 었/E 다/E ./S&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td class=&quot;red&quot;&gt;새끼/N 를/J 입/N 으로/J &lt;b&gt;묻/VV&lt;/b&gt; 어서/E 옮기/VV ᆫ다/E ./S&lt;/td&gt;
&lt;td class=&quot;red&quot;&gt;새끼/N 를/J 입/N 으로/J &lt;b&gt;묻/VV&lt;/b&gt; 어서/E 옮기/VV ㄴ다/E ./S&lt;/td&gt;
&lt;td class=&quot;green&quot;&gt;새끼/N 를/J 입/N 으로/J &lt;b&gt;물/VV&lt;/b&gt; 어서/E 옮기/VV ᆫ다/E ./S&lt;/td&gt;
&lt;td class=&quot;red&quot;&gt;새끼/N 를/J 입/N 으로/J &lt;b&gt;묻/VV&lt;/b&gt; 어서/E 옮기/VV ㄴ다/E ./S&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;/div&gt;
&lt;p style=&quot;text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;Kiwi에서는 맥락을 반영해 걷/걸, 묻/물 등을 비교적 잘 맞히는 반면, 나머지 분석기에서는 주로 한쪽으로 쏠리는 모양을 보입니다. 동사와 형용사의 구분 결과도 살펴볼까요?&lt;/p&gt;
&lt;div style=&quot;overflow-x: auto;&quot;&gt;
&lt;table class=&quot;std-table&quot; style=&quot;width: 160%;&quot; data-ke-align=&quot;alignLeft&quot;&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;th&gt;&amp;nbsp;&lt;/th&gt;
&lt;th&gt;Kiwi&lt;/th&gt;
&lt;th&gt;Komoran&lt;/th&gt;
&lt;th&gt;Mecab&lt;/th&gt;
&lt;th&gt;Kkma&lt;/th&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;th rowspan=&quot;2&quot;&gt;낫/VV&lt;/th&gt;
&lt;td class=&quot;green&quot;&gt;잘못/M 되/X 면/E 환자/N 가/J &lt;b&gt;낫/VV&lt;/b&gt; 지/E 못하/VX ᆫ다/E ./S&lt;/td&gt;
&lt;td class=&quot;red&quot;&gt;잘못/N 되/X 면/E 환자/N 가/J &lt;b&gt;낫/VA&lt;/b&gt; 지/E 못하/VX ㄴ다/E ./S&lt;/td&gt;
&lt;td class=&quot;red&quot;&gt;잘못/M 되/VV 면/E 환자/N 가/J &lt;b&gt;낫/VA&lt;/b&gt; 지/E 못하/VX ᆫ다/E ./S&lt;/td&gt;
&lt;td class=&quot;red&quot;&gt;잘못되/VV 면/E 환자/N 가/J &lt;b&gt;낫/VA&lt;/b&gt; 지/E 못하/VX ㄴ다/E ./S&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td class=&quot;green&quot;&gt;기적/N 처럼/J 병/N 이/J &lt;b&gt;낫/VV&lt;/b&gt; 다/E ./S&lt;/td&gt;
&lt;td class=&quot;red&quot;&gt;기적처럼/N 병/N 이/J &lt;b&gt;낫/VA&lt;/b&gt; 다/E ./S&lt;/td&gt;
&lt;td class=&quot;red&quot;&gt;기적/N 처럼/J 병/N 이/J &lt;b&gt;낫/VA&lt;/b&gt; 다/E ./S&lt;/td&gt;
&lt;td class=&quot;red&quot;&gt;기적/N 처럼/J 병/N 이/J &lt;b&gt;낫/VA&lt;/b&gt; 다/E ./S&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;th rowspan=&quot;2&quot;&gt;낫/VA&lt;/th&gt;
&lt;td class=&quot;green&quot;&gt;나/N 가/J 너/N 보다/J 는/J &lt;b&gt;낫/VA&lt;/b&gt; 지/E ./S&lt;/td&gt;
&lt;td class=&quot;green&quot;&gt;내/N 가/J 너/N 보다/J 는/J &lt;b&gt;낫/VA&lt;/b&gt; 지/E ./S&lt;/td&gt;
&lt;td class=&quot;green&quot;&gt;내/N 가/J 너/N 보다/J 는/J &lt;b&gt;낫/VA&lt;/b&gt; 지/E ./S&lt;/td&gt;
&lt;td class=&quot;red&quot;&gt;내가/N 너/N 보다/J 는/J &lt;b&gt;낫/VV&lt;/b&gt; 지/E ./S&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td class=&quot;green&quot;&gt;포기/N 하/X 는/E 것/N 이/J 훨씬/M &lt;b&gt;낫/VA&lt;/b&gt; 다/E ./S&lt;/td&gt;
&lt;td class=&quot;green&quot;&gt;포기/N 하/X 는/E 게/E 훨씬/M &lt;b&gt;낫/VA&lt;/b&gt; 다/E ./S&lt;/td&gt;
&lt;td class=&quot;green&quot;&gt;포기/N 하/X 는/E 것/N 이/J 훨씬/M &lt;b&gt;낫/VA&lt;/b&gt; 다/E ./S&lt;/td&gt;
&lt;td class=&quot;green&quot;&gt;포기/N 하/X 는/E 것/N 이/J 훨씬/M &lt;b&gt;낫/VA&lt;/b&gt; 다/E ./S&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;th rowspan=&quot;3&quot;&gt;적/VV&lt;/th&gt;
&lt;td class=&quot;green&quot;&gt;자세/X 하/X ᆫ/E 거/N ᆫ/J 여기/N 에/J &lt;b&gt;적/VV&lt;/b&gt; 어/E 주/VX 시/E 어요/E ./S&lt;/td&gt;
&lt;td class=&quot;red&quot;&gt;자세/X 하/X ㄴ/E 건/N 여기/N 에/J &lt;b&gt;적/VA&lt;/b&gt; 어/E 주/VX 시/E 어요/E ./S&lt;/td&gt;
&lt;td class=&quot;green&quot;&gt;자세/X 하/X ᆫ/E 것/N ᆫ/J 여기/N 에/J &lt;b&gt;적/VV&lt;/b&gt; 어/E 주/VX 시/E 어요/E ./S&lt;/td&gt;
&lt;td class=&quot;green&quot;&gt;자세/N 하/X ㄴ/E 것/N 은/J 여기/N 에/J &lt;b&gt;적/VV&lt;/b&gt; 어/E 주/VX 세요/E ./S&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td class=&quot;green&quot;&gt;지금/N 까지/J &lt;b&gt;적/VV&lt;/b&gt; 은/E 거/N ᆯ/J 그대로/M 제출/N 하/X 시/E ᆸ시오/E ./S&lt;/td&gt;
&lt;td class=&quot;red&quot;&gt;지금/N 까지/J &lt;b&gt;적/VA&lt;/b&gt; 은/E 걸/VV ㄹ/E 그대로/M 제출/N 하/X 시/E ㅂ시오/E ./S&lt;/td&gt;
&lt;td class=&quot;red&quot;&gt;지금/N 까지/J &lt;b&gt;적/VA&lt;/b&gt; 은/E 것/N ᆯ/J 그대로/M 제출/N 하/X 시/E ᄇ시오/E ./S&lt;/td&gt;
&lt;td class=&quot;red&quot;&gt;지금/N 까지/J &lt;b&gt;적/VA&lt;/b&gt; 은/E 것/N 을/J 그대로/M 제출/N 하/X 시/E ㅂ시오/E ./S&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td class=&quot;green&quot;&gt;&quot;/S 그러면/M 못/M 쓰/VV 어/E &quot;/S 라고/J &lt;b&gt;적/VV&lt;/b&gt; 었/E 다/E ./S&lt;/td&gt;
&lt;td class=&quot;red&quot;&gt;&quot;/S 그러면/M 못/M 쓰/VV 어/E &quot;/S 라고/J &lt;b&gt;적/VA&lt;/b&gt; 었/E 다/E ./S&lt;/td&gt;
&lt;td class=&quot;red&quot;&gt;&quot;/S 그러면/M 못/M 쓰/VV 어/E &quot;/S 라고/J &lt;b&gt;적/VA&lt;/b&gt; 었/E 다/E ./S&lt;/td&gt;
&lt;td class=&quot;red&quot;&gt;&quot;/S 그러/VV 면/E 못/M 쓰/VV 어/E &quot;/S 라고/J &lt;b&gt;적/VA&lt;/b&gt; 었/E 다/E ./S&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;th rowspan=&quot;3&quot;&gt;적/VA&lt;/th&gt;
&lt;td class=&quot;green&quot;&gt;협회/N 의/J 우려/N 가/J &lt;b&gt;적/VA&lt;/b&gt; 지/E 않/VX 다/E ./S&lt;/td&gt;
&lt;td class=&quot;green&quot;&gt;협회/N 의/J 우려/N 가/J &lt;b&gt;적/VA&lt;/b&gt; 지/E 않/VX 다/E ./S&lt;/td&gt;
&lt;td class=&quot;green&quot;&gt;협회/N 의/J 우려/N 가/J &lt;b&gt;적/VA&lt;/b&gt; 지/E 않/VX 다/E ./S&lt;/td&gt;
&lt;td class=&quot;green&quot;&gt;협회/N 의/J 우려/N 가/J &lt;b&gt;적/VA&lt;/b&gt; 지/E 않/VX 다/E ./S&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td class=&quot;green&quot;&gt;운/N 이/J 좋/VA 었/E 는지/E 피해/N 가/J &lt;b&gt;적/VA&lt;/b&gt; 었/E 다/E ./S&lt;/td&gt;
&lt;td class=&quot;green&quot;&gt;운/N 이/J 좋/VA 았/E 는지/E 피해/N 가/J &lt;b&gt;적/VA&lt;/b&gt; 었/E 다/E ./S&lt;/td&gt;
&lt;td class=&quot;green&quot;&gt;운/N 이/J 좋/VA 았/E 는지/E 피해/N 가/J &lt;b&gt;적/VA&lt;/b&gt; 었/E 다/E ./S&lt;/td&gt;
&lt;td class=&quot;green&quot;&gt;운/N 이/J 좋/VA 았/E 는지/E 피해/N 가/J &lt;b&gt;적/VA&lt;/b&gt; 었/E 다/E ./S&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td class=&quot;green&quot;&gt;액수/N 가/J 너무/M &lt;b&gt;적/VA&lt;/b&gt; 어/E ,/S 더/M 늘리/VV 어/E 주/VX 어/E !/S&lt;/td&gt;
&lt;td class=&quot;green&quot;&gt;액수/N 가/J 너무/M &lt;b&gt;적/VA&lt;/b&gt; 어/E ,/S 더/M 늘리/VV 어/E 주/VX 어/E !/S&lt;/td&gt;
&lt;td class=&quot;green&quot;&gt;액수/N 가/J 너무/M &lt;b&gt;적/VA&lt;/b&gt; 어/E ,/S 더/M 늘리/VV 어/E 주/VX ㅓ/E !/S&lt;/td&gt;
&lt;td class=&quot;red&quot;&gt;액수/N 가/J 너무/M &lt;b&gt;적/VV&lt;/b&gt; 어/E ,/S 더/M 늘리/VV 어/E 주/VX 어/E !/S&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;th rowspan=&quot;2&quot;&gt;차/VV&lt;/th&gt;
&lt;td class=&quot;green&quot;&gt;발/N 로/J 힘껏/M &lt;b&gt;차/VV&lt;/b&gt; 었/E 다/E ./S&lt;/td&gt;
&lt;td class=&quot;green&quot;&gt;발/N 로/J 힘껏/M &lt;b&gt;차/VV&lt;/b&gt; 았/E 다/E ./S&lt;/td&gt;
&lt;td class=&quot;green&quot;&gt;발/N 로/J 힘껏/M &lt;b&gt;차/VV&lt;/b&gt; 았/E 다/E ./S&lt;/td&gt;
&lt;td class=&quot;green&quot;&gt;발/N 로/J 힘껏/M &lt;b&gt;차/VV&lt;/b&gt; 었/E 다/E ./S&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td class=&quot;green&quot;&gt;허리/N 에/J &lt;b&gt;차/VV&lt;/b&gt; 지/E 말/VX 고/E 어깨/N 에/J 묶/VV 어/E&lt;/td&gt;
&lt;td class=&quot;red&quot;&gt;허리/N 에/J &lt;b&gt;차지/N&lt;/b&gt; 말/VX 고/E 어깨/N 에/J 묶/VV 어/E&lt;/td&gt;
&lt;td class=&quot;green&quot;&gt;허리/N 에/J &lt;b&gt;차/VV&lt;/b&gt; 지/E 말/VX 고/E 어깨/N 에/J 묶/VV 어/E&lt;/td&gt;
&lt;td class=&quot;red&quot;&gt;허리/N 에/J &lt;b&gt;차지/N&lt;/b&gt; 말/VV 고/E 어깨/N 에/J 묶/VV 어/E&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;th rowspan=&quot;3&quot;&gt;차/VA&lt;/th&gt;
&lt;td class=&quot;green&quot;&gt;북서쪽/N 에서/J 내려오/VV ᆫ/E &lt;b&gt;차/VA&lt;/b&gt; ᆫ/E 공기/N 의/J 영향/N&lt;/td&gt;
&lt;td class=&quot;red&quot;&gt;북서쪽/N 에서/J 내려오/VV ㄴ/E &lt;b&gt;차/VV&lt;/b&gt; ㄴ/E 공기/N 의/J 영향/N&lt;/td&gt;
&lt;td class=&quot;green&quot;&gt;북서쪽/N 에서/J 내려오/VV ᆫ/E &lt;b&gt;차/VA&lt;/b&gt; ᆫ/E 공기/N 의/J 영향/N&lt;/td&gt;
&lt;td class=&quot;red&quot;&gt;북서쪽/N 에서/J 내려오/VV ㄴ/E &lt;b&gt;찬/N&lt;/b&gt; 공기/N 의/J 영향/N&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td class=&quot;green&quot;&gt;&lt;b&gt;차/VA&lt;/b&gt; 고/E 건조/N 하/X ᆫ/E 시베리아/N 고/X 기압/N&lt;/td&gt;
&lt;td class=&quot;red&quot;&gt;&lt;b&gt;차고/N&lt;/b&gt; 건조/N 하/X ㄴ/E 시베리아/N 고기압/N&lt;/td&gt;
&lt;td class=&quot;red&quot;&gt;&lt;b&gt;차고/N&lt;/b&gt; 건조/N 하/X ᆫ/E 시베리아/N 고기압/N&lt;/td&gt;
&lt;td class=&quot;red&quot;&gt;&lt;b&gt;차/VV&lt;/b&gt; 고/E 건조/N 하/X ㄴ/E 시베리아/N 고기압/N&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td class=&quot;green&quot;&gt;삶/N 은/J 면/N 은/J &lt;b&gt;차/VA&lt;/b&gt; ᆫ/E 물/N 에/J 넣/VV 어/E 주/VX 시/E 어요/E !/S&lt;/td&gt;
&lt;td class=&quot;red&quot;&gt;삶/N 은/J 면/N 은/J &lt;b&gt;차/VV&lt;/b&gt; ㄴ/E 물/N 에/J 넣/VV 어/E 주/VX 시/E 어요/E !/S&lt;/td&gt;
&lt;td class=&quot;green&quot;&gt;삶/VV 은/E 면/N 은/J &lt;b&gt;차/VA&lt;/b&gt; ᆫ/E 물/N 에/J 넣/VV 어/E 주/VX 시/E 어요/E !/S&lt;/td&gt;
&lt;td class=&quot;red&quot;&gt;삶/N 은/J 면/N 은/J &lt;b&gt;차/VV&lt;/b&gt; ㄴ/E 물/N 에/J 넣/VV 어/E 주/VX 세요/E !/S&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;/div&gt;
&lt;p style=&quot;text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;Kiwi 0.11.0에서 가장 정확한 결과를 보인 부류입니다. 나머지 분석기에서는 동사와 형용사를 제대로 구분하지 못하거나, 혹은 아예 엉뚱하게 명사로 분석하는 경우도 보입니다. 마지막으로 명사 부류입니다.&lt;/p&gt;
&lt;div style=&quot;overflow-x: auto;&quot;&gt;
&lt;table class=&quot;std-table&quot; style=&quot;width: 160%;&quot; data-ke-align=&quot;alignLeft&quot;&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;th&gt;&amp;nbsp;&lt;/th&gt;
&lt;th&gt;Kiwi&lt;/th&gt;
&lt;th&gt;Komoran&lt;/th&gt;
&lt;th&gt;Mecab&lt;/th&gt;
&lt;th&gt;Kkma&lt;/th&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;th rowspan=&quot;2&quot;&gt;종이/N&lt;/th&gt;
&lt;td class=&quot;green&quot;&gt;여기/N 있/VV 는/E &lt;b&gt;종이/N&lt;/b&gt; 복사기/N 에/J 넣/VV 어/E 주/VX 어/E ./S&lt;/td&gt;
&lt;td class=&quot;green&quot;&gt;여기/N 있/VV 는/E &lt;b&gt;종이/N&lt;/b&gt; 복사기/N 에/J 넣/VV 어/E 주/VX 어/E ./S&lt;/td&gt;
&lt;td class=&quot;green&quot;&gt;여기/N 있/VA 는/E &lt;b&gt;종이/N&lt;/b&gt; 복사기/N 에/J 넣/VV 어/E 주/VX ㅓ/E ./S&lt;/td&gt;
&lt;td class=&quot;green&quot;&gt;여기/N 있/VV 는/E &lt;b&gt;종이/N&lt;/b&gt; 복사기/N 에/J 넣/VV 어/E 주/VX 어/E ./S&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td class=&quot;green&quot;&gt;시험/N 점수/N 가/J 적히/VV ᆫ/E &lt;b&gt;종이/N&lt;/b&gt; ,/S 절대/M 잃어버리/VV 지/E 마/VX 라/E !/S&lt;/td&gt;
&lt;td class=&quot;green&quot;&gt;시험/N 점수/N 가/J 적히/VV ㄴ/E &lt;b&gt;종이/N&lt;/b&gt; ,/S 절대/N 잃어버리/VV 지/E 말/VV 라/E !/S&lt;/td&gt;
&lt;td class=&quot;green&quot;&gt;시험/N 점수/N 가/J 적히/VV ᆫ/E &lt;b&gt;종이/N&lt;/b&gt; ,/S 절대/M 잃/VV 어/E 버리/VX 지/E 말/VX 라/E !/S&lt;/td&gt;
&lt;td class=&quot;green&quot;&gt;시험/N 점수/N 가/J 적히/VV ㄴ/E &lt;b&gt;종이/N&lt;/b&gt; ,/S 절대/M 잃어버리/VV 지/E 마라/N !/S&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;th rowspan=&quot;2&quot;&gt;종/N&lt;/th&gt;
&lt;td class=&quot;green&quot;&gt;여기/N 있/VV 는/E &lt;b&gt;종/N&lt;/b&gt; 이/J 시끄럽/VA 게/E 울리/VV ᆫ다/E ./S&lt;/td&gt;
&lt;td class=&quot;red&quot;&gt;여기/N 있/VV 는/E &lt;b&gt;종이/N&lt;/b&gt; 시끄럽/VA 게/E 울리/VV ㄴ다/E ./S&lt;/td&gt;
&lt;td class=&quot;green&quot;&gt;여기/N 있/VA 는/E &lt;b&gt;종/N&lt;/b&gt; 이/J 시끄럽/VA 게/E 울리/VV ᆫ다/E ./S&lt;/td&gt;
&lt;td class=&quot;red&quot;&gt;여기/N 있/VV 는/E &lt;b&gt;종이/N&lt;/b&gt; 시끄럽/VA 게/E 울리/VV ㄴ다/E ./S&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td class=&quot;green&quot;&gt;혼혈/N 이/J 없이/M 두/M &lt;b&gt;종/N&lt;/b&gt; 이/J 완전히/M 분리/N 되/X 면/E&lt;/td&gt;
&lt;td class=&quot;red&quot;&gt;혼혈/N 이/J 없이/M 두/M &lt;b&gt;종이/N&lt;/b&gt; 완전히/M 분리/N 되/X 면/E&lt;/td&gt;
&lt;td class=&quot;red&quot;&gt;혼혈/N 이/J 없이/M 두/M &lt;b&gt;종이/N&lt;/b&gt; 완전히/M 분리/N 되/X 면/E&lt;/td&gt;
&lt;td class=&quot;green&quot;&gt;혼혈/N 이/J 없이/M 두/M &lt;b&gt;종/N&lt;/b&gt; 이/J 완전히/M 분리/N 되/X 면/E&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;th&gt;길이/N&lt;/th&gt;
&lt;td class=&quot;red&quot;&gt;그거/N 다듬/VV 을/E 때/N &lt;b&gt;길/N&lt;/b&gt; 이라든가/J 모양/N 을/J 좀/M 맞추/VV 어서/E 예쁘/VA 게/E 하/VV 어/E ./S&lt;/td&gt;
&lt;td class=&quot;red&quot;&gt;그것/N 다듬/VV 을/E 때/N &lt;b&gt;길/N 이/VC&lt;/b&gt; 라든가/E 모양/N 을/J 좀/M 맞추/VV 어서/E 예쁘/VA 게/E 하/VX 아/E ./S&lt;/td&gt;
&lt;td class=&quot;red&quot;&gt;그것/N 다듬/VV 을/E 때/N &lt;b&gt;길/N&lt;/b&gt; 이라든가/J 모양/N 을/J 좀/M 맞추/VV 어서/E 예쁘/VA 게/E 하/VX ㅕ/E ./S&lt;/td&gt;
&lt;td class=&quot;red&quot;&gt;그/M 거/N 다듬/VV 을/E 때/N &lt;b&gt;길/N 이/VC&lt;/b&gt; 라/E 들/VA ㄴ가/E 모양/N 을/J 좀/M 맞추/VV 어서/E 예쁘/VA 게/E 하/VV 어/E ./S&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;th&gt;길/N&lt;/th&gt;
&lt;td class=&quot;green&quot;&gt;대전/N 으로/J 가/VV 는/E &lt;b&gt;길/N&lt;/b&gt; 이/VC 다/E ./S&lt;/td&gt;
&lt;td class=&quot;green&quot;&gt;대전/N 으로/J 가/VV 는/E &lt;b&gt;길/N&lt;/b&gt; 이/VC 다/E ./S&lt;/td&gt;
&lt;td class=&quot;green&quot;&gt;대전/N 으로/J 가/VV 는/E &lt;b&gt;길/N&lt;/b&gt; 이/VC 다/E ./S&lt;/td&gt;
&lt;td class=&quot;green&quot;&gt;대전/N 으로/J 가/VV 는/E &lt;b&gt;길/N&lt;/b&gt; 이/VC 다/E ./S&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;th rowspan=&quot;3&quot;&gt;화가/N&lt;/th&gt;
&lt;td class=&quot;green&quot;&gt;19/S 세기/N 독일/N &lt;b&gt;화가/N&lt;/b&gt; 작품/N 이/VC 다/E ./S&lt;/td&gt;
&lt;td class=&quot;green&quot;&gt;19세기/N 독일/N &lt;b&gt;화가/N&lt;/b&gt; 작품/N 이/VC 다/E ./S&lt;/td&gt;
&lt;td class=&quot;red&quot;&gt;19/S 세기/N 독일/N &lt;b&gt;화/N 가/J&lt;/b&gt; 작품/N 이/VC 다/E ./S&lt;/td&gt;
&lt;td class=&quot;green&quot;&gt;19/N 세기/N 독일/N &lt;b&gt;화가/N&lt;/b&gt; 작품/N 이/VC 다/E ./S&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td class=&quot;green&quot;&gt;르네상스/N 시기/N 의/J 대표/N 적/X 이/VC ᆫ/E &lt;b&gt;화가/N&lt;/b&gt; 다빈치/N&lt;/td&gt;
&lt;td class=&quot;green&quot;&gt;르네상스/N 시기/N 의/J 대표/N 적/X 이/VC ㄴ/E &lt;b&gt;화가/N&lt;/b&gt; 다빈치/N&lt;/td&gt;
&lt;td class=&quot;red&quot;&gt;르네상스/N 시기/N 의/J 대표/N 적/X 이/VC ᆫ/E &lt;b&gt;화/N 가/J&lt;/b&gt; 다빈치/N&lt;/td&gt;
&lt;td class=&quot;green&quot;&gt;르네상스/N 시기/N 의/J 대표적/N 이/VC ㄴ/E &lt;b&gt;화가/N&lt;/b&gt; 다빈치/N&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td class=&quot;green&quot;&gt;그/N 는/J 취미/N 이/VC 라고/E 하/VV 었/E 지만/E &lt;b&gt;화가/N&lt;/b&gt; 수준/N 의/J 그림/N 을/J 그리/VV 었/E 다/E ./S&lt;/td&gt;
&lt;td class=&quot;green&quot;&gt;그/N 는/J 취미/N 이/VC 라고/E 하/VV 았/E 지만/E &lt;b&gt;화가/N&lt;/b&gt; 수준/N 의/J 그림/N 을/J 그리/VV 었/E 다/E ./S&lt;/td&gt;
&lt;td class=&quot;red&quot;&gt;그/N 는/J 취미/N 이/VC 라고/E 하/VX 았/E 지만/E &lt;b&gt;화/N 가/J&lt;/b&gt; 수준/N 의/J 그림/N 을/J 그리/VV 었/E 다/E ./S&lt;/td&gt;
&lt;td class=&quot;green&quot;&gt;그/VV 는/E 취미/N 라고/J 하/VV 었/E 지만/E &lt;b&gt;화가/N&lt;/b&gt; 수준/N 의/J 그림/N 을/J 그리/VV 었/E 다/E ./S&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;th rowspan=&quot;3&quot;&gt;화/N&lt;/th&gt;
&lt;td class=&quot;green&quot;&gt;스트레스/N 를/J 받/VV 거나/E &lt;b&gt;화/N&lt;/b&gt; 가/J 나/VV ᆯ/E 때/N 는/J ?/S&lt;/td&gt;
&lt;td class=&quot;red&quot;&gt;스트레스/N 를/J 받/VV 거나/E &lt;b&gt;화가/N&lt;/b&gt; 날/N 때/N 는/J ?/S&lt;/td&gt;
&lt;td class=&quot;green&quot;&gt;스트레스/N 를/J 받/VV 거나/E &lt;b&gt;화/N&lt;/b&gt; 가/J 날/N 때/N 는/J ?/S&lt;/td&gt;
&lt;td class=&quot;red&quot;&gt;스트레스/N 를/J 받/VV 거나/E &lt;b&gt;화가/N&lt;/b&gt; 낳/VV ㄹ/E 때/N 는/J ?/S&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td class=&quot;green&quot;&gt;&lt;b&gt;화/N&lt;/b&gt; 가/J 치밀/VV 어/E 오르/VV 어서/E 밖/N 으로/J 나가/VV 었/E 다/E ./S&lt;/td&gt;
&lt;td class=&quot;red&quot;&gt;&lt;b&gt;화가/N&lt;/b&gt; 치밀/VV 어/E 오르/VV 아서/E 밖/N 으로/J 나가/VV 았/E 다/E ./S&lt;/td&gt;
&lt;td class=&quot;green&quot;&gt;&lt;b&gt;화/N&lt;/b&gt; 가/J 치밀/VV 어/E 오르/VV 아서/E 밖/N 으로/J 나가/VV 았/E 다/E ./S&lt;/td&gt;
&lt;td class=&quot;green&quot;&gt;&lt;b&gt;화/N&lt;/b&gt; 가/J 치밀/VV 어/E 오르/VV 아서/E 밖/N 으로/J 나가/VV 었/E 다/E ./S&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td class=&quot;green&quot;&gt;그것/N 때문/N 에/J &lt;b&gt;화/N&lt;/b&gt; 가/J 많이/M 나/VV 었/E 다고/E 하/VV ᆫ다/E ./S&lt;/td&gt;
&lt;td class=&quot;red&quot;&gt;그것/N 때문/N 에/J &lt;b&gt;화가/N&lt;/b&gt; 많이/M 나/VV 았/E 다고/E 하/VX ㄴ다/E ./S&lt;/td&gt;
&lt;td class=&quot;green&quot;&gt;그것/N 때문/N 에/J &lt;b&gt;화/N&lt;/b&gt; 가/J 많이/M 나/VV 았/E 다고/E 하/VX ᆫ다/E ./S&lt;/td&gt;
&lt;td class=&quot;red&quot;&gt;그것/N 때문/N 에/J &lt;b&gt;화가/N&lt;/b&gt; 많이/M 나/VV 었/E 다고/E 하/VV ㄴ다/E ./S&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;th&gt;성의/N&lt;/th&gt;
&lt;td class=&quot;green&quot;&gt;&lt;b&gt;성의/N&lt;/b&gt; 있/VA 는/E 협조/N 를/J 부탁/N 드리/VV ᆸ니다/E ./S&lt;/td&gt;
&lt;td class=&quot;green&quot;&gt;&lt;b&gt;성의/N&lt;/b&gt; 있/VV 는/E 협조/N 를/J 부탁/N 드리/VV ㅂ니다/E ./S&lt;/td&gt;
&lt;td class=&quot;red&quot;&gt;&lt;b&gt;성/N 의/J&lt;/b&gt; 있/VA 는/E 협조/N 를/J 부탁드리/VV ᄇ니다/E ./S&lt;/td&gt;
&lt;td class=&quot;green&quot;&gt;&lt;b&gt;성의/N&lt;/b&gt; 있/VV 는/E 협조/N 를/J 부탁/N 드리/VV ㅂ니다/E ./S&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;th rowspan=&quot;2&quot;&gt;성/N&lt;/th&gt;
&lt;td class=&quot;green&quot;&gt;이/M &lt;b&gt;성/N&lt;/b&gt; 의/J 꼭대기/N 에/J 는/J 비밀/N 스럽/X 은/E 공간/N 이/J 있/VV 다/E ./S&lt;/td&gt;
&lt;td class=&quot;red&quot;&gt;이/M &lt;b&gt;성의/N&lt;/b&gt; 꼭대기/N 에/J 는/J 비밀/N 스럽/X ㄴ/E 공간/N 이/J 있/VX 다/E ./S&lt;/td&gt;
&lt;td class=&quot;green&quot;&gt;이/M &lt;b&gt;성/N&lt;/b&gt; 의/J 꼭대기/N 에/J 는/J 비밀/N 스럽/X ᆫ/E 공간/N 이/J 있/VA 다/E ./S&lt;/td&gt;
&lt;td class=&quot;red&quot;&gt;이/M &lt;b&gt;성의/N&lt;/b&gt; 꼭대기/N 에/J 는/J 비밀/N 스럽/X ㄴ/E 공간/N 이/J 있/VV 다/E ./S&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td class=&quot;green&quot;&gt;&lt;b&gt;성/N&lt;/b&gt; 의/J 역할/N 과/J 능력/N 은/J 사회/N 에/J 의하/VV 어/E 결정/N 되/X 는가/E ?/S&lt;/td&gt;
&lt;td class=&quot;red&quot;&gt;&lt;b&gt;성의/N&lt;/b&gt; 역할/N 과/J 능력/N 은/J 사회/N 에/J 의하/VV 아/E 결정/N 되/X 는가/E ?/S&lt;/td&gt;
&lt;td class=&quot;green&quot;&gt;&lt;b&gt;성/N&lt;/b&gt; 의/J 역할/N 과/J 능력/N 은/J 사회/N 에/J 의하/VV 아/E 결정/N 되/X 는가/E ?/S&lt;/td&gt;
&lt;td class=&quot;red&quot;&gt;&lt;b&gt;성의/N&lt;/b&gt; 역할/N 과/J 능력/N 은/J 사회/N 에/J 의하/VV 어/E 결정/N 되/X 는가/E ?/S&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;/div&gt;
&lt;p style=&quot;text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;Komoran은 대체로 최장일치를 우선하는 걸로 보입니다. '종이'는 무조건 &lt;b&gt;종이/NNG&lt;/b&gt;로, '화가'는 무조건 &lt;b&gt;화가/NNG&lt;/b&gt;로 분석하네요. Mecab은 반대로 최대한 조사를 분리하려 합니다. '화가'는 &lt;b&gt;화/NNG 가/JKS&lt;/b&gt;로, '성의'는 &lt;b&gt;성/NNG 의/JKG&lt;/b&gt;로 분석합니다. Kkma는 양 극단의 중간쯤에 있는듯합니다. Kiwi의 경우 맥락을 반영해 비교적 정확하게 해당 형태가 조사가 붙은 명사인지, 아니면 통째로 한 명사인지 구분해내고 있습니다.&lt;/p&gt;
&lt;h3 style=&quot;text-align: justify;&quot; data-ke-size=&quot;size23&quot;&gt;왜 잘 될까?&lt;/h3&gt;
&lt;p style=&quot;text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;이는 사실 처음부터 Kiwi에게 유리한 대결이었는데요, 이는 Kiwi는 다른 형태소 분석기들과는 다르게 언어모델을 기반으로 작동하기 때문입니다. 보통 형태소 분석기는 입력 문장을 형태소가 될만한 단위로 쪼갠 뒤, 각 형태들의 품사를 결정하는 모델을 사용합니다. 특히 이는 순서에 영향을 받기 때문에 HMM이나 CRF등의 확률 모델을 자주 사용합니다. 이는 각 형태에 어떤 품사 태그를 다는게 제일 적합할지 찾는것에 주 목적이 있습니다.&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;반면 언어모델은 특정한 단어열(형태소열)이 등장할 확률을 계산해주는 모델로 각 형태에 대해 품사 태그를 다는 것보다 더 많은 것을 고려합니다. 특히 이 형태소 다음에는 어떤 형태소가 등장할지 등을 구체적으로 계산할 수 있습니다. Kiwi에서는 가능한 형태소 조합을 최대한 많이 구해낸 다음, 이 중에서 확률이 제일 높은 형태소 배열을 골라내는 방식을 사용합니다. 단순히 각 형태들의 품사 태그를 구하는 작업에는 어찌보면 닭 잡는데 소 잡는 칼을 쓰는 셈입니다만, 그 덕분에 형태소와 형태소 간의 관계(특정 형태소 뒤에는 어떤 형태소가 등장할 가능성이 제일 높은지 등)를 명확하게 파악하기 때문에 이와 같이 모호성 해소 작업에서는 강력한 모습을 보이는 것이죠.&lt;/p&gt;
&lt;h3 style=&quot;text-align: justify;&quot; data-ke-size=&quot;size23&quot;&gt;현재 Kiwi의 모호성 해소 한계&lt;/h3&gt;
&lt;p style=&quot;text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;그러나 Kiwi의 모호성 해소에도 큰 약점이 있습니다. Kiwi 0.11.0 버전에서 사용하는 언어모델은 3+1-gram 기반의 Kneser-ney LM입니다. 즉 하나의 형태소를 결정할때 앞의 형태소 두개와 그 앞의 품사 태그 하나를 고려한다는 것입니다. 따라서 한 형태소는 앞으로 2개, 뒤로 2개의 형태소들에만 의해 그 확률이 결정됩니다. 따라서 모호한 형태소와 그 모호성을 해소시켜줄 단서 형태소가 2 단어 이상 떨어져 있으면 모델이 이를 잡아낼 수 없다는 것입니다.&lt;/p&gt;
&lt;table class=&quot;std-table&quot; data-ke-align=&quot;alignLeft&quot;&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;th&gt;&amp;nbsp;&lt;/th&gt;
&lt;th&gt;거리가 짧은 경우&lt;/th&gt;
&lt;th&gt;거리가 긴 경우&lt;/th&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;th&gt;종이/NNG&lt;/th&gt;
&lt;td&gt;여기/N 있/VV 는/E &lt;b&gt;종이/N&lt;/b&gt; &lt;b&gt;복사기/N&lt;/b&gt; 에/J 넣/VV 어/E 주/VX 어/E&lt;/td&gt;
&lt;td&gt;여기/N 있/VV 는/E &lt;b&gt;종/N 이/J &lt;span style=&quot;background-color: #99cefa;&quot;&gt;저기/N 에/J 있/VA 는/E&lt;/span&gt; 복사기/N&lt;/b&gt; 에/J 넣/VV 어/E 주/VX 어/E&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;th&gt;걸/VV&lt;/th&gt;
&lt;td&gt;이/M 번호/N 로/J &lt;b&gt;전화/N 를/J 걸/VV&lt;/b&gt; 어/E&lt;/td&gt;
&lt;td&gt;이/M 번호/N 로/J &lt;b&gt;전화/N 를/J &lt;span style=&quot;background-color: #99cefa;&quot;&gt;이따가/M 꼭/M 반드시/M&lt;/span&gt; 걷/VV&lt;/b&gt; 어/E&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;th&gt;적/VV&lt;/th&gt;
&lt;td&gt;&lt;b&gt;지금/N 까지/J 적/VV&lt;/b&gt; 은/E 거/N ᆯ/J 그대로/M 제출/N 하/X 시/E ᆸ시오/E&lt;/td&gt;
&lt;td&gt;&lt;b&gt;지금/N 까지/J &lt;span style=&quot;background-color: #99cefa;&quot;&gt;음/I ~/S&lt;/span&gt; 적/VA&lt;/b&gt; 은/E 거/N ᆯ/J 그대로/M 제출/N 하/X 시/E ᆸ시오/E&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p style=&quot;text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;'거리가 짧은 경우' 열에 있는 굵은 표시는 모호한 형태소와 그 모호성을 해소시켜줄 단서인데요, 이 사이에 '거리가 긴 경우' 열의 하늘색 형태소가 끼어들게 되면 위와 같이 분석 결과가 틀어지게 됩니다. 이 때문에 문장이 길고 꾸밈이 잦은 문체라든가, 중간에 잉여표현(감탄사) 등이 자주 끼어드는 구어체에서는 모호성 해소의 정확도가 떨어질 수 있습니다.&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;안타깝게도 현재의 n-gram 기반 모델로는 사실상 이를 해결하기 어렵습니다. 더 긴 거리의 정보를 포착하기 위해서는 n-gram 모델의 n값을 키워야하는데 이를 키울수록 모델 크기와 메모리 사용량이 기하급수적으로 늘어나기 때문이죠. 이에 대한 대안으로 skip n-gram을 사용하거나 Neural LM을 사용하는 방법이 있겠습니다. 물론 둘 다 현재 방법에 비해 크게 느릴 것이기 때문에 속도와 정확도 사이에서 저울질을 해야할 때가 다시 온 듯하네요.&lt;/p&gt;</description>
      <category>프로그래밍/NLP</category>
      <author>&amp;int;2tdt=t&amp;sup2;+c</author>
      <guid isPermaLink="true">https://bab2min.tistory.com/672</guid>
      <comments>https://bab2min.tistory.com/672#entry672comment</comments>
      <pubDate>Sun, 27 Mar 2022 01:11:05 +0900</pubDate>
    </item>
  </channel>
</rss>