<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom">
  <channel>
    <title>NCSOFT DANBI</title>
    <description>NCSOFT 데이터 분석 기술 공유 블로그.
</description>
    <link>https://danbi-ncsoft.github.io//</link>
    <atom:link href="https://danbi-ncsoft.github.io//feed.xml" rel="self" type="application/rss+xml"/>
    <pubDate>Tue, 02 Apr 2024 05:40:43 +0000</pubDate>
    <lastBuildDate>Tue, 02 Apr 2024 05:40:43 +0000</lastBuildDate>
    <generator>Jekyll v3.9.5</generator>
    
      <item>
        <title>직장인 익명 게시판을 통해 본 재택근무</title>
        <description>&lt;h1 id=&quot;직장인-익명-게시판을-통해-본-재택근무&quot;&gt;&lt;strong&gt;직장인 익명 게시판을 통해 본 재택근무&lt;/strong&gt;&lt;/h1&gt;

&lt;p&gt;​&lt;/p&gt;

&lt;p&gt;​	2020년 2월 국내에 상륙한 코로나와 함께 우리 직장인의 일상에 갑작스럽게 이제껏 경험하지 못했던 재택근무가 찾아왔다. 재택근무가 막 시작될 때만 해도 ‘이게 가능해?’라는 의구심이 강했다. 그런데 지금 2022년에는 재택근무가 자연스러운 일상의 한 부분으로 자리잡고 있다. 물론 재택근무가 코로나 펜데믹 상황에서 시작되기는 했지만 일시적인 대비책이 아니라 하나의 근무 형태로 자리 잡을 수 있다는 예측들이 등장하고 있다. 국내외 많은 기업들이 오프라인 기반의 오피스를 줄이기도 하고, 물리적 제약을 없애서 우수한 인재를 영입하려는 시도가 이어지고 있다. 이렇게 과감한 전략에 따라 ‘재택근무가 효과적인가’에 대한 질문이 등장하고 있다. 많은 회사들이 재택근무의 효과성과 생산성에 대해 분석하고 고민하고 있는 상황이다. 그런데 재택근무가 피할 수 없는 트렌드가 된다면, “재택근무를 어떻게 효과적으로 만들 것인지”도 함께 고민해야 하지 않을까? 재택근무를 효과적으로 만들기 위해서는 가장 먼저 재택근무의 당사자인 직장인들의 의견을 들여다볼 필요가 있다고 생각했다.&lt;/p&gt;

&lt;p&gt;​	직장인들은 재택근무를 어떻게 인식하고, 어떻게 반응하고 있을까? 이것이 이번 블로그 글의 핵심 질문이다. 직장인들이 자유롭게 의견을 나누는 SNS 공간을 살펴보면 그 단서를 찾을 수 있을 것이라 생각했다. 직장인들이 재택근무를 이야기하면서 자주 등장하는 키워드, 혹은 주제들, 변화 양상 등을 확인해서 직장인들의 인식을 파악해보려고 하였다. 이는 결과적으로 재택근무가 효과적으로 정착하기 위한 기초 자료가 될 것이라는 나름의 기대도 갖고 있었다. 분석 주제에 맞게 직장인 대상 익명 SNS 게시판의 텍스트 데이터를 수집했다. ‘재택’이라는 키워드를 포함한 게시글과 댓글 총 11만 건의 데이터를 확보했다. 주제의 제한없이 사용하는 해당 게시판은 직장인들의 일반적인 인식과 관심사를 확인하기에 좋은 데이터 소스였다. 다만, 해당 게시판의 특성 상 회사에 대한 부정적인 의견이 과장되고 확대되어 표현될 수 있어서 이 점을 감안하여 아래 분석 결과를 해석할 필요가 있다.&lt;/p&gt;

&lt;h3 id=&quot;어떤-키워드들이-주로-등장할까&quot;&gt;어떤 키워드들이 주로 등장할까?&lt;/h3&gt;

&lt;p&gt;​	재택근무 관련 게시글과 댓글에서는 어떤 키워드들이 주로 사용되고 있는지를 먼저 확인했다. 추출한 데이터에서 명사 키워드를 추출하였고, 각 단어별 출현 빈도를 산출하였다. 키워드 출현 빈도는 아래 워드 클라우드와 차트를 통해 시각화하였다. 워드 클라우드를 보면, ‘회사’, ‘출근’, ‘시간’, ‘ 출근’, ‘업무’ 등 회사생활과 관련된 키워드들이 눈에 띈다. 이는 직장인을 대상으로 한 익명 게시판이어서 나타난 결과이기도 하겠지만, 재택근무의 의사결정 주체인 회사, 직장과 관련된 상황에 대한 이야기가 많이 등장했음을 추측해볼 수 있다. 회사생활을 제외하면 ‘코로나’, ‘친구’, ‘남편’, ‘추천’, ‘운동’ 등 다양한 주제의 이야기가 게시되고 논의되고 있음을 추측해볼 수 있다.&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/assets/works/blind_wfh_text/blind_01.jpg&quot; alt=&quot;blind_01&quot; /&gt;&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/assets/works/blind_wfh_text/blind_02.jpg&quot; alt=&quot;blind_02&quot; /&gt;&lt;/p&gt;

&lt;p&gt;​	다음으로는 이런 키워드들이 텍스트에서 어떻게 연결되어 있는지를 확인했다. 분석에는 엔그램(n-gram)과 키워드 간의 상관계수를 활용했다. 엔그램은 연이어서 사용된 n개의 단어를 말한다. 동일한 키워드라고 하더라도 연결되는 단어에 따라 의미가 달라지기 때문에 연이어 등장하는 단어쌍을 활용하면 텍스트에서 키워드가 사용되는 대략의 양상을 확인할 수 있지 않을까 기대했다. 데이터에서 추출한 명사 2-gram(bigram) 결과를 보면, 우리-회사, 재택-회사, 회사-재택의 단어 쌍이 많이 등장했다. 해당 바이그램을 포함한 실제 텍스트를 해보면 ‘재택하는 회사’, ‘회사가 재택을 해서’ 식의 서술이 많이 등장했다. 다시 말해서, 어떤 회사가 재택을 하고 있는지, 회사에서 재택을 하다보니 어떤 이슈가 생겼는지에 대한 의견과 그에 대한 반응이 많이 논의되고 있다는 것을 발견할 수 있었다.&lt;/p&gt;

&lt;table&gt;
  &lt;thead&gt;
    &lt;tr&gt;
      &lt;th style=&quot;text-align: center&quot;&gt;Rank&lt;/th&gt;
      &lt;th style=&quot;text-align: center&quot;&gt;word1&lt;/th&gt;
      &lt;th style=&quot;text-align: center&quot;&gt;word2&lt;/th&gt;
      &lt;th style=&quot;text-align: center&quot;&gt;n&lt;/th&gt;
      &lt;th style=&quot;text-align: center&quot;&gt;Rank&lt;/th&gt;
      &lt;th style=&quot;text-align: center&quot;&gt;word1&lt;/th&gt;
      &lt;th style=&quot;text-align: center&quot;&gt;word2&lt;/th&gt;
      &lt;th style=&quot;text-align: center&quot;&gt;n&lt;/th&gt;
    &lt;/tr&gt;
  &lt;/thead&gt;
  &lt;tbody&gt;
    &lt;tr&gt;
      &lt;td style=&quot;text-align: center&quot;&gt;1&lt;/td&gt;
      &lt;td style=&quot;text-align: center&quot;&gt;&lt;strong&gt;우리&lt;/strong&gt;&lt;/td&gt;
      &lt;td style=&quot;text-align: center&quot;&gt;&lt;strong&gt;회사&lt;/strong&gt;&lt;/td&gt;
      &lt;td style=&quot;text-align: center&quot;&gt;683&lt;/td&gt;
      &lt;td style=&quot;text-align: center&quot;&gt;11&lt;/td&gt;
      &lt;td style=&quot;text-align: center&quot;&gt;출근&lt;/td&gt;
      &lt;td style=&quot;text-align: center&quot;&gt;재택&lt;/td&gt;
      &lt;td style=&quot;text-align: center&quot;&gt;287&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td style=&quot;text-align: center&quot;&gt;2&lt;/td&gt;
      &lt;td style=&quot;text-align: center&quot;&gt;&lt;strong&gt;재택&lt;/strong&gt;&lt;/td&gt;
      &lt;td style=&quot;text-align: center&quot;&gt;&lt;strong&gt;회사&lt;/strong&gt;&lt;/td&gt;
      &lt;td style=&quot;text-align: center&quot;&gt;612&lt;/td&gt;
      &lt;td style=&quot;text-align: center&quot;&gt;12&lt;/td&gt;
      &lt;td style=&quot;text-align: center&quot;&gt;점심&lt;/td&gt;
      &lt;td style=&quot;text-align: center&quot;&gt;시간&lt;/td&gt;
      &lt;td style=&quot;text-align: center&quot;&gt;286&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td style=&quot;text-align: center&quot;&gt;3&lt;/td&gt;
      &lt;td style=&quot;text-align: center&quot;&gt;&lt;strong&gt;회사&lt;/strong&gt;&lt;/td&gt;
      &lt;td style=&quot;text-align: center&quot;&gt;&lt;strong&gt;재택&lt;/strong&gt;&lt;/td&gt;
      &lt;td style=&quot;text-align: center&quot;&gt;605&lt;/td&gt;
      &lt;td style=&quot;text-align: center&quot;&gt;13&lt;/td&gt;
      &lt;td style=&quot;text-align: center&quot;&gt;근무&lt;/td&gt;
      &lt;td style=&quot;text-align: center&quot;&gt;시간&lt;/td&gt;
      &lt;td style=&quot;text-align: center&quot;&gt;285&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td style=&quot;text-align: center&quot;&gt;4&lt;/td&gt;
      &lt;td style=&quot;text-align: center&quot;&gt;재택&lt;/td&gt;
      &lt;td style=&quot;text-align: center&quot;&gt;근무&lt;/td&gt;
      &lt;td style=&quot;text-align: center&quot;&gt;560&lt;/td&gt;
      &lt;td style=&quot;text-align: center&quot;&gt;14&lt;/td&gt;
      &lt;td style=&quot;text-align: center&quot;&gt;우리&lt;/td&gt;
      &lt;td style=&quot;text-align: center&quot;&gt;재택&lt;/td&gt;
      &lt;td style=&quot;text-align: center&quot;&gt;234&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td style=&quot;text-align: center&quot;&gt;5&lt;/td&gt;
      &lt;td style=&quot;text-align: center&quot;&gt;재택&lt;/td&gt;
      &lt;td style=&quot;text-align: center&quot;&gt;출근&lt;/td&gt;
      &lt;td style=&quot;text-align: center&quot;&gt;484&lt;/td&gt;
      &lt;td style=&quot;text-align: center&quot;&gt;15&lt;/td&gt;
      &lt;td style=&quot;text-align: center&quot;&gt;호재&lt;/td&gt;
      &lt;td style=&quot;text-align: center&quot;&gt;악재&lt;/td&gt;
      &lt;td style=&quot;text-align: center&quot;&gt;220&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td style=&quot;text-align: center&quot;&gt;6&lt;/td&gt;
      &lt;td style=&quot;text-align: center&quot;&gt;재택&lt;/td&gt;
      &lt;td style=&quot;text-align: center&quot;&gt;사람&lt;/td&gt;
      &lt;td style=&quot;text-align: center&quot;&gt;427&lt;/td&gt;
      &lt;td style=&quot;text-align: center&quot;&gt;16&lt;/td&gt;
      &lt;td style=&quot;text-align: center&quot;&gt;출퇴근&lt;/td&gt;
      &lt;td style=&quot;text-align: center&quot;&gt;시간&lt;/td&gt;
      &lt;td style=&quot;text-align: center&quot;&gt;219&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td style=&quot;text-align: center&quot;&gt;7&lt;/td&gt;
      &lt;td style=&quot;text-align: center&quot;&gt;하루&lt;/td&gt;
      &lt;td style=&quot;text-align: center&quot;&gt;종일&lt;/td&gt;
      &lt;td style=&quot;text-align: center&quot;&gt;410&lt;/td&gt;
      &lt;td style=&quot;text-align: center&quot;&gt;17&lt;/td&gt;
      &lt;td style=&quot;text-align: center&quot;&gt;사람&lt;/td&gt;
      &lt;td style=&quot;text-align: center&quot;&gt;재택&lt;/td&gt;
      &lt;td style=&quot;text-align: center&quot;&gt;215&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td style=&quot;text-align: center&quot;&gt;8&lt;/td&gt;
      &lt;td style=&quot;text-align: center&quot;&gt;재택&lt;/td&gt;
      &lt;td style=&quot;text-align: center&quot;&gt;가능&lt;/td&gt;
      &lt;td style=&quot;text-align: center&quot;&gt;383&lt;/td&gt;
      &lt;td style=&quot;text-align: center&quot;&gt;18&lt;/td&gt;
      &lt;td style=&quot;text-align: center&quot;&gt;회사&lt;/td&gt;
      &lt;td style=&quot;text-align: center&quot;&gt;사람&lt;/td&gt;
      &lt;td style=&quot;text-align: center&quot;&gt;211&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td style=&quot;text-align: center&quot;&gt;9&lt;/td&gt;
      &lt;td style=&quot;text-align: center&quot;&gt;코로나&lt;/td&gt;
      &lt;td style=&quot;text-align: center&quot;&gt;재택&lt;/td&gt;
      &lt;td style=&quot;text-align: center&quot;&gt;357&lt;/td&gt;
      &lt;td style=&quot;text-align: center&quot;&gt;19&lt;/td&gt;
      &lt;td style=&quot;text-align: center&quot;&gt;거리&lt;/td&gt;
      &lt;td style=&quot;text-align: center&quot;&gt;두기&lt;/td&gt;
      &lt;td style=&quot;text-align: center&quot;&gt;208&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td style=&quot;text-align: center&quot;&gt;10&lt;/td&gt;
      &lt;td style=&quot;text-align: center&quot;&gt;코로나&lt;/td&gt;
      &lt;td style=&quot;text-align: center&quot;&gt;때문&lt;/td&gt;
      &lt;td style=&quot;text-align: center&quot;&gt;345&lt;/td&gt;
      &lt;td style=&quot;text-align: center&quot;&gt;20&lt;/td&gt;
      &lt;td style=&quot;text-align: center&quot;&gt;재택&lt;/td&gt;
      &lt;td style=&quot;text-align: center&quot;&gt;업무&lt;/td&gt;
      &lt;td style=&quot;text-align: center&quot;&gt;196&lt;/td&gt;
    &lt;/tr&gt;
  &lt;/tbody&gt;
&lt;/table&gt;

&lt;p&gt;​	그렇다면 동일한 텍스트에서 같이 연결되어 많이 사용되는 단어쌍은 어떤 키워드들일까? 대량의 텍스트 데이터들 내에서 주로 연결되어 등장하는 단어 조합을 보면 그 텍스트의 맥락을 살펴볼 수 있지 않을까 하는 기대를 갖고 키워드 간의 상관계수를 확인해 보았다. 여기서 활용한 지수는 파이 계수(phi coefficient)다. 파이계수는 두 개의 단어가 얼마나 많이 함께 쓰이는지를 각각 사용되는 경우와 비교해서 나타내는 지표다. 파이계수를 통해 두 단어의 연관성을 확인할 수 있고, 텍스트에서 어떤 단어들이 함께 사용되고 있는지를 파악할 수 있다. 두 단어 X와 Y가 있을 때, 텍스트에 두 단어가 모두 있는 경우, 각각 있는 경우, 모두 없는 경우로 나눠볼 수 있고, 이를 표로 나타내면 다음과 같다.&lt;/p&gt;

&lt;table&gt;
  &lt;thead&gt;
    &lt;tr&gt;
      &lt;th style=&quot;text-align: center&quot;&gt; &lt;/th&gt;
      &lt;th style=&quot;text-align: center&quot;&gt;단어 Y 있음&lt;/th&gt;
      &lt;th style=&quot;text-align: center&quot;&gt;단어 Y 없음&lt;/th&gt;
      &lt;th style=&quot;text-align: center&quot;&gt; &lt;/th&gt;
    &lt;/tr&gt;
  &lt;/thead&gt;
  &lt;tbody&gt;
    &lt;tr&gt;
      &lt;td style=&quot;text-align: center&quot;&gt;단어 X 있음&lt;/td&gt;
      &lt;td style=&quot;text-align: center&quot;&gt;a&lt;/td&gt;
      &lt;td style=&quot;text-align: center&quot;&gt;b&lt;/td&gt;
      &lt;td style=&quot;text-align: center&quot;&gt;a+b&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td style=&quot;text-align: center&quot;&gt;단어 X 없음&lt;/td&gt;
      &lt;td style=&quot;text-align: center&quot;&gt;c&lt;/td&gt;
      &lt;td style=&quot;text-align: center&quot;&gt;d&lt;/td&gt;
      &lt;td style=&quot;text-align: center&quot;&gt;c+d&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td style=&quot;text-align: center&quot;&gt; &lt;/td&gt;
      &lt;td style=&quot;text-align: center&quot;&gt;a+c&lt;/td&gt;
      &lt;td style=&quot;text-align: center&quot;&gt;b+d&lt;/td&gt;
      &lt;td style=&quot;text-align: center&quot;&gt; &lt;/td&gt;
    &lt;/tr&gt;
  &lt;/tbody&gt;
&lt;/table&gt;

&lt;p&gt;​	이 경우에 파이 계수는 아래 공식을 통해서 산출할 수 있다. 지수의 범위는 -1에서 1 사이의 값을 가지며, 1에 가까울수록 자주 함께 사용되는 관련성이 높은 키워드라고 해석할 수 있다. 본 분석에서는 두 키워드의 파이계수는 높으나 전체 텍스트에서 등장한 빈도가 너무 낮은 경우는 특정 텍스트의 결과가 과대해석될 가능성이 있어 제외했다.&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/assets/works/blind_wfh_text/blind_03.jpg&quot; alt=&quot;blind_03&quot; /&gt;&lt;/p&gt;

&lt;p&gt;​	파이 계수 기반의 분석 결과는 아래와 같이 정리할 수 있다. 분석 결과를 보면 사회적-거리두기와 같이 재택 관련으로 자주 연결될 수밖에 없는 키워드 쌍을 주로 확인할 수 있는데, ‘연봉-이직’ 키워드 쌍이 재택근무 관련 텍스트에서 논의되고 있다는 점이 인상적이다. ‘연봉-이직’은 물론 기존에도 상관성이 높은 단어이지만, 재택 관련 게시물에서도 동일하게 등장하고 있다는 점은 주목할 만하다. 덧붙여 ‘카카오-네이버’가 재택 관련 게시물에서 같이 언급되고 있었는데, 해당 키워드가 등장한 텍스트에서는 국내 IT기업들이 재택근무를 어떻게 하고 있는지를 묻고 답하는 과정에서 함께 언급되고 있었다. 아무래도 IT기업들이 업무 특성 상 재택근무가 가능한 회사로 생각되다보니 이런 결과가 나타나는 것으로 보인다. 침대-눕다, 노래-틀다의 단어쌍도 흥미로운데, 재택근무가 주는 편안한 업무 환경을 체감할 수 있는 단어 조합이다.&lt;/p&gt;

&lt;table&gt;
  &lt;thead&gt;
    &lt;tr&gt;
      &lt;th style=&quot;text-align: center&quot;&gt;Rank&lt;/th&gt;
      &lt;th style=&quot;text-align: center&quot;&gt;word1&lt;/th&gt;
      &lt;th style=&quot;text-align: center&quot;&gt;word2&lt;/th&gt;
      &lt;th style=&quot;text-align: center&quot;&gt;phi-coef&lt;/th&gt;
      &lt;th style=&quot;text-align: center&quot;&gt;Rank&lt;/th&gt;
      &lt;th style=&quot;text-align: center&quot;&gt;word1&lt;/th&gt;
      &lt;th style=&quot;text-align: center&quot;&gt;word2&lt;/th&gt;
      &lt;th style=&quot;text-align: center&quot;&gt;phi-coef&lt;/th&gt;
    &lt;/tr&gt;
  &lt;/thead&gt;
  &lt;tbody&gt;
    &lt;tr&gt;
      &lt;td style=&quot;text-align: center&quot;&gt;1&lt;/td&gt;
      &lt;td style=&quot;text-align: center&quot;&gt;사회적&lt;/td&gt;
      &lt;td style=&quot;text-align: center&quot;&gt;거리두기&lt;/td&gt;
      &lt;td style=&quot;text-align: center&quot;&gt;0.52&lt;/td&gt;
      &lt;td style=&quot;text-align: center&quot;&gt;11&lt;/td&gt;
      &lt;td style=&quot;text-align: center&quot;&gt;건물&lt;/td&gt;
      &lt;td style=&quot;text-align: center&quot;&gt;확진자&lt;/td&gt;
      &lt;td style=&quot;text-align: center&quot;&gt;0.27&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td style=&quot;text-align: center&quot;&gt;2&lt;/td&gt;
      &lt;td style=&quot;text-align: center&quot;&gt;인치&lt;/td&gt;
      &lt;td style=&quot;text-align: center&quot;&gt;모니터&lt;/td&gt;
      &lt;td style=&quot;text-align: center&quot;&gt;0.39&lt;/td&gt;
      &lt;td style=&quot;text-align: center&quot;&gt;12&lt;/td&gt;
      &lt;td style=&quot;text-align: center&quot;&gt;단점&lt;/td&gt;
      &lt;td style=&quot;text-align: center&quot;&gt;장점&lt;/td&gt;
      &lt;td style=&quot;text-align: center&quot;&gt;0.27&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td style=&quot;text-align: center&quot;&gt;3&lt;/td&gt;
      &lt;td style=&quot;text-align: center&quot;&gt;&lt;strong&gt;카카오&lt;/strong&gt;&lt;/td&gt;
      &lt;td style=&quot;text-align: center&quot;&gt;&lt;strong&gt;네이버&lt;/strong&gt;&lt;/td&gt;
      &lt;td style=&quot;text-align: center&quot;&gt;0.34&lt;/td&gt;
      &lt;td style=&quot;text-align: center&quot;&gt;13&lt;/td&gt;
      &lt;td style=&quot;text-align: center&quot;&gt;틀다&lt;/td&gt;
      &lt;td style=&quot;text-align: center&quot;&gt;노래&lt;/td&gt;
      &lt;td style=&quot;text-align: center&quot;&gt;0.25&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td style=&quot;text-align: center&quot;&gt;4&lt;/td&gt;
      &lt;td style=&quot;text-align: center&quot;&gt;백신&lt;/td&gt;
      &lt;td style=&quot;text-align: center&quot;&gt;맞다&lt;/td&gt;
      &lt;td style=&quot;text-align: center&quot;&gt;0.34&lt;/td&gt;
      &lt;td style=&quot;text-align: center&quot;&gt;14&lt;/td&gt;
      &lt;td style=&quot;text-align: center&quot;&gt;점심&lt;/td&gt;
      &lt;td style=&quot;text-align: center&quot;&gt;먹다&lt;/td&gt;
      &lt;td style=&quot;text-align: center&quot;&gt;0.25&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td style=&quot;text-align: center&quot;&gt;5&lt;/td&gt;
      &lt;td style=&quot;text-align: center&quot;&gt;마시다&lt;/td&gt;
      &lt;td style=&quot;text-align: center&quot;&gt;커피&lt;/td&gt;
      &lt;td style=&quot;text-align: center&quot;&gt;0.32&lt;/td&gt;
      &lt;td style=&quot;text-align: center&quot;&gt;15&lt;/td&gt;
      &lt;td style=&quot;text-align: center&quot;&gt;댓글&lt;/td&gt;
      &lt;td style=&quot;text-align: center&quot;&gt;달다&lt;/td&gt;
      &lt;td style=&quot;text-align: center&quot;&gt;0.24&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td style=&quot;text-align: center&quot;&gt;6&lt;/td&gt;
      &lt;td style=&quot;text-align: center&quot;&gt;눕다&lt;/td&gt;
      &lt;td style=&quot;text-align: center&quot;&gt;침대&lt;/td&gt;
      &lt;td style=&quot;text-align: center&quot;&gt;0.28&lt;/td&gt;
      &lt;td style=&quot;text-align: center&quot;&gt;16&lt;/td&gt;
      &lt;td style=&quot;text-align: center&quot;&gt;에어컨&lt;/td&gt;
      &lt;td style=&quot;text-align: center&quot;&gt;틀다&lt;/td&gt;
      &lt;td style=&quot;text-align: center&quot;&gt;0.24&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td style=&quot;text-align: center&quot;&gt;7&lt;/td&gt;
      &lt;td style=&quot;text-align: center&quot;&gt;확진자&lt;/td&gt;
      &lt;td style=&quot;text-align: center&quot;&gt;나오다&lt;/td&gt;
      &lt;td style=&quot;text-align: center&quot;&gt;0.28&lt;/td&gt;
      &lt;td style=&quot;text-align: center&quot;&gt;17&lt;/td&gt;
      &lt;td style=&quot;text-align: center&quot;&gt;삼성&lt;/td&gt;
      &lt;td style=&quot;text-align: center&quot;&gt;인치&lt;/td&gt;
      &lt;td style=&quot;text-align: center&quot;&gt;0.24&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td style=&quot;text-align: center&quot;&gt;8&lt;/td&gt;
      &lt;td style=&quot;text-align: center&quot;&gt;키우다&lt;/td&gt;
      &lt;td style=&quot;text-align: center&quot;&gt;강아지&lt;/td&gt;
      &lt;td style=&quot;text-align: center&quot;&gt;0.28&lt;/td&gt;
      &lt;td style=&quot;text-align: center&quot;&gt;18&lt;/td&gt;
      &lt;td style=&quot;text-align: center&quot;&gt;고양이&lt;/td&gt;
      &lt;td style=&quot;text-align: center&quot;&gt;키우다&lt;/td&gt;
      &lt;td style=&quot;text-align: center&quot;&gt;0.24&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td style=&quot;text-align: center&quot;&gt;9&lt;/td&gt;
      &lt;td style=&quot;text-align: center&quot;&gt;오전&lt;/td&gt;
      &lt;td style=&quot;text-align: center&quot;&gt;오후&lt;/td&gt;
      &lt;td style=&quot;text-align: center&quot;&gt;0.27&lt;/td&gt;
      &lt;td style=&quot;text-align: center&quot;&gt;19&lt;/td&gt;
      &lt;td style=&quot;text-align: center&quot;&gt;다이어트&lt;/td&gt;
      &lt;td style=&quot;text-align: center&quot;&gt;식단&lt;/td&gt;
      &lt;td style=&quot;text-align: center&quot;&gt;0.24&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td style=&quot;text-align: center&quot;&gt;10&lt;/td&gt;
      &lt;td style=&quot;text-align: center&quot;&gt;&lt;strong&gt;연봉&lt;/strong&gt;&lt;/td&gt;
      &lt;td style=&quot;text-align: center&quot;&gt;&lt;strong&gt;이직&lt;/strong&gt;&lt;/td&gt;
      &lt;td style=&quot;text-align: center&quot;&gt;0.27&lt;/td&gt;
      &lt;td style=&quot;text-align: center&quot;&gt;20&lt;/td&gt;
      &lt;td style=&quot;text-align: center&quot;&gt;조언&lt;/td&gt;
      &lt;td style=&quot;text-align: center&quot;&gt;부탁드리다&lt;/td&gt;
      &lt;td style=&quot;text-align: center&quot;&gt;0.24&lt;/td&gt;
    &lt;/tr&gt;
  &lt;/tbody&gt;
&lt;/table&gt;

&lt;p&gt;​	지금까지는 키워드 간의 연관성을 바탕으로 어떤 논의들이 진행되고 있었는지를 간단하게 살펴보았다. 물론 특징적인 몇몇 부분을 확인할 수 있었지만, 어떤 주제의 이야기가 어느 정도의 비중을 가지고 다뤄지고 있는지는 파악하고 결론 내리기에는 뭔가 아직 부족한 점이 있다.&lt;/p&gt;

&lt;h3 id=&quot;어떤-주제가-논의되고-있는가&quot;&gt;&lt;strong&gt;어떤 주제가 논의되고 있는가?&lt;/strong&gt;&lt;/h3&gt;

&lt;p&gt;​	직장인 익명 게시판에서 직장인들은 어떤 주제를 이야기하고 있는지를 좀 더 명확하게 파악하기 위해 토픽모델링 분석을 실시했다. 토픽모델링이란 텍스트의 핵심주제를 찾고 비슷한 내용끼리 분류하는 모형을 의미한다. 다양한 토픽모델링 방법이 있지만, 본 분석에서는 가장 보편적으로 사용하는 LDA(Latent Dirichlet Allocation, 잠재디리클레할당) 기법을 활용하였다.&lt;/p&gt;

&lt;p&gt;​	토픽모델링은 문서와 단어 간의 관계를 이용하며, 모델의 결과를 통해 문서에 사용된 단어가 어느 토픽에서 등장할 확률이 높은지를 산출할 수 있었다. LDA모델은 단어가 토픽을 구성하고, 토픽이 문서를 구성하고 있다고 전제한다. 먼저 하나의 토픽은 여러 단어의 혼합으로 구성된다는 가정에서 출발한다. 이에 따라 하나의 토픽에 존재하는 여러 단어들이 해당 토픽에 각각 등장할 확률값을 가지며, 동시에 같은 단어라도 여러 토픽에 등장할 서로 다른 확률값을 갖는다. 나아가 문서는 여러 토픽의 혼합으로 구성되어 있다. 각각의 문서에는 여러 토픽의 단어가 서로 다른 비율로 들어 있다. 다만 문서를 분류할 때는 단어 확률이 높은 쪽으로 분류한다. 결론적으로는 다량의 문서에서 주제들이 어떻게 분류할 수 있는가에 대한 답을 내리는 과정으로 이해하면 된다.&lt;/p&gt;

&lt;p&gt;​	이번 분석에서는 하나의 게시글에 있는 제목, 본문, 댓글을 하나의 문서(Document)로 구분해서 분석을 실시했다. 적절한 토픽 개수를 선정하기 위해서는 토픽의 개수를 조절하면서 의미가 구분되는 토픽 개수를 선정해야 한다. 해석 가능성을 고려하여 15개로 분류된 모형을 선정하였고, 그 중에 의미가 뚜렷한 10개의 토픽에 아래의 표와 같이 이름을 붙였다. ( 1) 회사생활, 2) 연애, 3) 커리어/이직, 4) 코로나, 5) 가정/육아, 6) 식사, 7) 사무기기, 8) 투자, 9) 건강/헬스, 10) 업무환경)&lt;/p&gt;

&lt;p&gt;​	토픽모델링 분류 결과를 활용해서 특정 토픽에서 특징적으로 등장하는 키워드를 확인할 수 있는데, 10개의 토픽에서는 아래 키워드들이 주로 나타났다.&lt;/p&gt;

&lt;table&gt;
  &lt;thead&gt;
    &lt;tr&gt;
      &lt;th style=&quot;text-align: center&quot;&gt;no.&lt;/th&gt;
      &lt;th style=&quot;text-align: center&quot;&gt;토픽명&lt;/th&gt;
      &lt;th style=&quot;text-align: center&quot;&gt;토픽 비중 (%)&lt;/th&gt;
      &lt;th style=&quot;text-align: center&quot;&gt;주요 단어&lt;/th&gt;
    &lt;/tr&gt;
  &lt;/thead&gt;
  &lt;tbody&gt;
    &lt;tr&gt;
      &lt;td style=&quot;text-align: center&quot;&gt;1&lt;/td&gt;
      &lt;td style=&quot;text-align: center&quot;&gt;&lt;strong&gt;회사생활&lt;/strong&gt;&lt;/td&gt;
      &lt;td style=&quot;text-align: center&quot;&gt;20.8&lt;/td&gt;
      &lt;td style=&quot;text-align: center&quot;&gt;재택, 출근, 재택근무, 회사, 퇴근, 근무, 사무실, 메신저, 회의, 휴가&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td style=&quot;text-align: center&quot;&gt;2&lt;/td&gt;
      &lt;td style=&quot;text-align: center&quot;&gt;&lt;strong&gt;연애&lt;/strong&gt;&lt;/td&gt;
      &lt;td style=&quot;text-align: center&quot;&gt;15.0&lt;/td&gt;
      &lt;td style=&quot;text-align: center&quot;&gt;여자, 남자, 친구, 남친, 연애, 소개팅, 여친, 얘기, 연락, 데이트, 약속&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td style=&quot;text-align: center&quot;&gt;3&lt;/td&gt;
      &lt;td style=&quot;text-align: center&quot;&gt;&lt;strong&gt;커리어/이직&lt;/strong&gt;&lt;/td&gt;
      &lt;td style=&quot;text-align: center&quot;&gt;9.0&lt;/td&gt;
      &lt;td style=&quot;text-align: center&quot;&gt;연봉, 이직, 복지, 워라, 면접, 개발자, 개발, 퇴사, 신입, 입사&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td style=&quot;text-align: center&quot;&gt;4&lt;/td&gt;
      &lt;td style=&quot;text-align: center&quot;&gt;&lt;strong&gt;코로나&lt;/strong&gt;&lt;/td&gt;
      &lt;td style=&quot;text-align: center&quot;&gt;8.2&lt;/td&gt;
      &lt;td style=&quot;text-align: center&quot;&gt;확진, 백신, 격리, 방역, 접종, 정부, 감염, 단계, 두기, 마스크&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td style=&quot;text-align: center&quot;&gt;5&lt;/td&gt;
      &lt;td style=&quot;text-align: center&quot;&gt;&lt;strong&gt;가정/육아&lt;/strong&gt;&lt;/td&gt;
      &lt;td style=&quot;text-align: center&quot;&gt;6.7&lt;/td&gt;
      &lt;td style=&quot;text-align: center&quot;&gt;남편, 아이, 와이프, 육아, 아빠, 엄마, 아기, 맞벌이, 휴직, 부부&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td style=&quot;text-align: center&quot;&gt;6&lt;/td&gt;
      &lt;td style=&quot;text-align: center&quot;&gt;&lt;strong&gt;식사&lt;/strong&gt;&lt;/td&gt;
      &lt;td style=&quot;text-align: center&quot;&gt;5.3&lt;/td&gt;
      &lt;td style=&quot;text-align: center&quot;&gt;배달, 커피, 점심, 맥주, 음식, 식비, 메뉴, 소주, 도시락, 반찬, 캡슐&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td style=&quot;text-align: center&quot;&gt;7&lt;/td&gt;
      &lt;td style=&quot;text-align: center&quot;&gt;&lt;strong&gt;사무기기&lt;/strong&gt;&lt;/td&gt;
      &lt;td style=&quot;text-align: center&quot;&gt;4.6&lt;/td&gt;
      &lt;td style=&quot;text-align: center&quot;&gt;모니터, 노트북, 인터넷, 인치, 맥북, 그램, 마우스, 모델, 키보드&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td style=&quot;text-align: center&quot;&gt;8&lt;/td&gt;
      &lt;td style=&quot;text-align: center&quot;&gt;&lt;strong&gt;투자&lt;/strong&gt;&lt;/td&gt;
      &lt;td style=&quot;text-align: center&quot;&gt;4.0&lt;/td&gt;
      &lt;td style=&quot;text-align: center&quot;&gt;주식, 부동산, 판교, 강남, 종목, 분당, 코인, 금리, 매수, 상승, 하락&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td style=&quot;text-align: center&quot;&gt;9&lt;/td&gt;
      &lt;td style=&quot;text-align: center&quot;&gt;&lt;strong&gt;건강/헬스&lt;/strong&gt;&lt;/td&gt;
      &lt;td style=&quot;text-align: center&quot;&gt;3.8&lt;/td&gt;
      &lt;td style=&quot;text-align: center&quot;&gt;운동, 다이어트, 헬스, 식단, 근육, 유산소, 몸무게, 체지방, 피티&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td style=&quot;text-align: center&quot;&gt;10&lt;/td&gt;
      &lt;td style=&quot;text-align: center&quot;&gt;&lt;strong&gt;업무환경&lt;/strong&gt;&lt;/td&gt;
      &lt;td style=&quot;text-align: center&quot;&gt;2.8&lt;/td&gt;
      &lt;td style=&quot;text-align: center&quot;&gt;소음, 윗집, 의자, 층간, 허리, 매트, 허먼, 밀러, 옆집, 공사, 아랫집&lt;/td&gt;
    &lt;/tr&gt;
  &lt;/tbody&gt;
&lt;/table&gt;

&lt;p&gt;​&lt;/p&gt;

&lt;p&gt;​	앞서 키워드 빈도분석 결과에서도 확인할 수 있었듯이, 많은 부분이 회사생활과 관련된 내용이었다. 재택근무를 하고 있는지, 하고 있다면 어떻게 하고 있는지를 묻기도 하고, 재택근무를 하면서 회사에서 생기는 갈등 상황들을 나누고 있었다. 연애와 관련된 주제의 게시글도 있었고, 연봉, 이직, 복지와 관련된 커리어/이직 토픽의 주제들도 상당한 비중을 가지고 있었다. 실제 텍스트를 살펴보면 회사의 근무 여건을 나열하면서 ‘재택근무 같은 복지제도’ 와 같이 언급되는데, 이는 직장인들이 재택근무를 회사를 평가하는 기준과 복지제도로 인식하고 있음을 보여주는 부분이다. 다음으로는 펜데믹 상황 관련 주제가 뒤를 이었다. 남편, 아이, 와이프 등과 같이 재택근무와 관련해서 가정 내에서 일어나는 이슈들에 대한 논의가 많았다. 실제 텍스트 내용을 살펴보면, 코로나 상황에서 맞벌이로서의 고통, 재택근무로 인해 가족 구성원끼리 자주 부딪히면서 발생하는 갈등 등이 담겨있었다.&lt;/p&gt;

&lt;p&gt;​	재택근무로 인해서 식사 관련 이슈를 담은 게시글들도 있었다. 해당 토픽이 포함된 텍스트를 보면, ‘재택하는 사람들 점심, 저녁 어떻게 먹나’, ‘식비는 얼마나 나오나’, ‘점심 메뉴 추천’ 등의 내용이 담겼다. 또한 재택근무가 장기화됨에 따라 재택근무용 사무기기를 추천 받거나 구매하는 토픽들도 있었다. 여기에 집에서 근무할 때 발생하는 층간 소음, 불편한 의자에 대한 이야기 등도 등장했다. 이는 재택근무 상황에서 직장인들이 업무 환경에 나름의 스트레스를 받고 있으며, 사무기기 구비 등을 통해서 업무 환경 개선에 관심을 가지고 있음을 보여주는 부분이다.&lt;/p&gt;

&lt;p&gt;​	이러한 분석 결과는 회사에서 재택근무를 어떻게 지원할 것인지에 대한 기초 자료로도 활용할 수 있을 것으로 생각된다. 이미 국내 IT 기업 중에는 재택근무자를 위한 물품 키트를 제공하거나, 밀키트를 배송해주는 등의 복지제도를 운영하고 있는 곳도 있다고 한다. 본 분석 결과를 살펴보면 이런 지원은 재택근무자들이 보다 효과적으로 업무를 할 수 있도록 그들이 어려움을 느끼는 요인을 일부 해소한다는 점에서 큰 의미가 있어 보인다.&lt;/p&gt;

&lt;p&gt;​	본 토픽모델링 분석에는 pyLDAvis 라이브러리를 활용하였다. (R에서는 LDAvis package입니다.) pyLDAvis 라이브러리에서는 LDA 토픽모델링 분석만 아니라 직관적인 시각화 툴을 제공한다. 시각화 결과물은 크게 아래와 같은 버블차트와 bar 차트로 확인할 수 있다. 먼저 버블차트에서 원의 크기는 데이터로부터 생성된 코퍼스 내 단어들이 할당된 토픽들의 비중의 크기를 뜻한다. 또한 분류된 토픽 간의 거리(distance)를 보여준다. 이번 결과에서는 우측 하단에 식사와 건강/헬스 토픽이 서로 가까이 위치하고 있고, 오른쪽 중간 위치에 사무기기와 업무환경 토픽이 비교적 가까운 거리를 보이고 있다는 점이 특징적이다. Bar 차트에서는 해당 relevance term을 기준으로 토픽과 관련이 높은 단어들이 정렬되어 있다. 붉은색 바는 해당 토픽 내에 특정 단어가 등장하는 빈도이고, 파란색 바는 전체 토픽에 특정 단어가 등장한 빈도를 의미한다. 직관적으로 해당 키워드가 특정 토픽에 어느 정도 비중으로 나타나는지를 색 차이를 통해 확인할 수 있다. 각 토픽별로 해당 차트가 도출 가능하며 본 글에서는 예시로 회사생활과 커리어/이직에 해당하는 bar 차트를 표시하였다.&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/assets/works/blind_wfh_text/blind_04.jpg&quot; alt=&quot;blind_04&quot; /&gt;&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/assets/works/blind_wfh_text/blind_05.png&quot; alt=&quot;blind_05&quot; /&gt;&lt;/p&gt;

&lt;h3 id=&quot;직장인들의-생각은-시간의-경과에-따라-달라졌을까&quot;&gt;&lt;strong&gt;직장인들의 생각은 시간의 경과에 따라 달라졌을까?&lt;/strong&gt;&lt;/h3&gt;

&lt;p&gt;​	2020년 2월 국내 첫 확진자가 발생한 이후, 약 2년 간의 시간이 지났다. 직장인에게 있어 재택근무는 코로나 펜데믹 상황과 함께 큰 화두였다. 그렇다면 이런 코로나 펜데믹 상황이 변화함에 따라 재택근무와 관련된 직장인 익명 게시판의 글의 빈도와 주제는 어떻게 달라졌을까? 2020년 초에 재택근무에 대한 논의점과 2022년의 논의는 분명 달라졌을텐데 어떤 부분이 달라졌을지를 확인해보고자 시계열 분석을 수행했다.&lt;/p&gt;

&lt;p&gt;​	먼저 게시글의 수는 어떻게 변화했을까? 아래 그래프는 코로나 국내 확진자 수와 재택관련 게시글 수의 추이를 비교한 결과다. 재택근무 관련 게시글이 가장 많이 등장한 시기는 2020년 3월로, 국내에 코로나 1차 유행이 시작되면서 재택근무 관련 논의와 관심이 많아지는 시기로 보인다. 재택근무라는 근무 형태가 낯설고 생소했기 때문에 관련 정보를 공유하면서 게시글이 많아졌을 것으로 추정된다. 이후에는 코로나 확진자 수가 이전 대비 급격하게 증가하는 시기인 2020년 8월, 2020년 12월, 2021년 7월에 재택 관련 게시글 수가 급증하고 있음을 확인할 수 있다. 펜데믹 상황의 변곡점에서 재택 관련 논의가 많이 등장하고 있다는 점이 인상적인 부분이다.&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/assets/works/blind_wfh_text/blind_06.jpg&quot; alt=&quot;blind_07&quot; /&gt;&lt;/p&gt;

&lt;p&gt;​	그렇다면, 재택근무 관련 게시글의 주제들은 펜데믹 초기와 비교했을 때 어떻게 달라지는 양상을 보일까? 변화 양상을 확인하기 위해 앞서 토픽모델링에서 특징적으로 도출되는 키워드를 포함하고 있는 게시글의 수를 집계하였고, 그 집계치를 바탕으로 월별 순위 변화를 비교했다. 먼저는 회사생활과 관련된 게시글 비중이 시기와 상관없이 가장 많이 등장했다. 분석 결과에서 한 가지 주목할 부분은 백신이나 확진자 등과 관련된 언급은 지속적으로 많은 비중을 차지하고 있지만, 시간이 점점 지나갈수록 순위가 낮아지고 있다는 점이다. 반면에 커리어/이직 관련한 주제들이 2020년에 들어 높은 순위를 차지하고 있음을 확인할 수 있다. 실제 텍스트를 살펴보면, 이직을 고민 중인 회사, 혹은 제안을 받은 회사의 근무 여건을 비교하면서, 재택근무 제도 여부를 고려하는 게시글들이 눈에 띄었다.&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/assets/works/blind_wfh_text/blind_07.jpg&quot; alt=&quot;blind_08&quot; /&gt;&lt;/p&gt;

&lt;h3 id=&quot;정리하면서&quot;&gt;&lt;strong&gt;정리하면서&lt;/strong&gt;&lt;/h3&gt;

&lt;p&gt;​	텍스트 분석을 활용해서 재택근무와 관련된 직장인들의 인식을 확인하려는 취지에서 위 분석을 진행했다. 주요 분석 결과를 살펴보면 다음과 같이 정리해볼 수 있다. 첫 번째로, 재택근무를 단순히 일시적인 근무 형태가 아니라 회사의 근무 여건, 복지 제도로 인식하는 경향이 드러난다는 점이다. 물론 펜데믹 이후에 재택근무가 일반적인 근무 형태로 자리잡을 수 있을지는 의문이지만, 적어도 직장인들은 재택근무가 가지는 편의성에 공감하고 직장 선택에 있어 고려할 수 있는 조건으로 생각하고 있다는 점은 주목할만 하다. 두 번째로, 직장인들이 재택근무와 관련해서 겪고 있는 애로사항들을 확인할 수 있었다. 가정과 육아에 있어서 겪는 현실적인 문제와 업무 환경과 사무기기가 가지는 불편함, 집에서 식사를 해결해야 한다는 부담감 등을 확인할 수 있었다. 물론 재택근무를 경험한 직장인이라면 당연히 공감할 수 있는 부분이고 어쩌면 당연한 결과일 수도 있다. 하지만 본 결과는 재택근무가 부분적으로 혹은 비슷한 펜데믹 상황으로 인해 불가피하게 실시해야 하는 상황에서 어떻게 구성원들을 지원하고 보다 성과를 만들어 내기 위해서 필요한 것이 무엇인지 고민해볼 수 있다는 점에서 의의가 있다고 생각한다.&lt;/p&gt;
</description>
        <pubDate>Wed, 30 Mar 2022 07:00:00 +0000</pubDate>
        <link>https://danbi-ncsoft.github.io//works/2022/03/30/work-blind-wfh-text.html</link>
        <guid isPermaLink="true">https://danbi-ncsoft.github.io//works/2022/03/30/work-blind-wfh-text.html</guid>
        
        
        <category>Works</category>
        
      </item>
    
      <item>
        <title>ETL 성능 향상을 위한 몇 가지 팁들</title>
        <description>&lt;h2 id=&quot;0-시작하며&quot;&gt;0. 시작하며&lt;/h2&gt;
&lt;p&gt;개발자라면 누구나 성능 향상에 대한 고민을 해 본 적이 있을 것입니다. 다른 분야도 마찬가지겠지만, ETL에 있어서도 성능을 향상시키는 것은 굉장히 중요한 부분입니다. 성능이 저하됨에 따라서 원하는 시간 내에 ETL 작업이 완료되지 못할 수도 있고, 특정 작업이 자원을 많이 차지함에 따라서 다른 작업의 수행 속도에 안 좋은 영향을 끼칠 수도 있으니 말이에요. 이번 게시글에서는 Hive측면과 RDBMS 측면에서 쿼리 수정을 통해 성능을 향상시키는 방법과 ETL 작업을 개발할 때 효율적으로 진행하는 방법에 대해서 이야기 해보려고 합니다.&lt;/p&gt;

&lt;p&gt;&lt;br /&gt;&lt;/p&gt;

&lt;hr /&gt;

&lt;p&gt;&lt;br /&gt;&lt;/p&gt;

&lt;h2 id=&quot;1-hiveql-측면에서-성능-향상하기&quot;&gt;1. HiveQL 측면에서 성능 향상하기&lt;/h2&gt;
&lt;p&gt;첫 번째 파트로는 HiveQL 측면에서 성능을 향상하는 세 가지 방법에 대해 소개하려 합니다. 딥하게 Hive의 구조를 뜯어보고, 실행 옵션을 수정할 필요 없이 쿼리를 일부분 수정하는 것만으로 성능을 향상시킬 수 있는 방법들입니다. 생각했던 것보다 HiveQL로 짜여진 ETL 로직의 실행 시간이 오래 걸릴 때, 아래 방법들을 먼저 적용해보는 것도 좋은 선택이 되겠죠. (생각보다 큰 효과를 볼 수 있을지도 모르구요!)&lt;/p&gt;

&lt;p&gt;&lt;br /&gt;&lt;/p&gt;

&lt;h3 id=&quot;1-1-조건절-내의-사용자-정의-함수-제거&quot;&gt;1-1. 조건절 내의 사용자 정의 함수 제거&lt;/h3&gt;
&lt;p&gt;사용자 정의 함수(UDF)는 Hive 옵티마이저가 제대로 인식할 수 없는 함수입니다. 제대로 인식할 수 없다는 것은 옵티마이저가 쿼리에 대한 실행 계획을 수립할 때 해당 함수는 비용 계산에서 제외되게 된다는 것을 뜻하죠. 즉, 사용자 정의 함수를 WHERE 조건 절에 사용하는 경우에는 해당 UDF 조건을 통해 추출 데이터의 건수가 줄어들 수 있음에도 불구하고 쿼리 수행 비용 계산 시에는 반영이 되지 않습니다. 옵티마이저 입장에서는 없는 함수니까요.&lt;/p&gt;

&lt;p&gt;특히, 파티션 키 컬럼의 필터링 조건으로 UDF 가 사용되는 경우는 성능 상의 이슈가 조금 더 심각해집니다. 쿼리 수행 비용 계산에 함수가 반영되지 않는 것보다 큰 문제가 발생하기 때문이죠. 분명히 내가 짠 쿼리에는 파티션 키 컬럼의 필터링 조건이 존재하지만 &lt;strong&gt;Partitions Pruning&lt;/strong&gt;이 일어나지 않게 됩니다.&lt;/p&gt;

&lt;p&gt;Partitions Pruning이 무엇이길래 성능에 영향을 끼치는 것일까요? Partitions Pruning이란 말 그대로 파티션 가지치기를 의미합니다. 실행 시점에서 쿼리의 조건 절을 분석하여 읽지 않아도 되는 파티션 세그먼트를 액세스 대상에서 제외하는 기능인데, 쉽게 말하면 SQL 수행 시 필요 없는 파티션에 대해서는 가지를 치고 필요한 파티션만을 읽게 하는 기능입니다.&lt;/p&gt;

&lt;p&gt;이제 상황을 정리해봅시다. 원하는 값만 필터링해서 읽어오고자 UDF로 파티션 키 컬럼에 조건을 주었지만, 옵티마이저 입장에서는 없는 필터링이었고 결국에는 Partitions Pruning 없이 전체 테이블의 데이터를 읽는 방식으로 쿼리가 실행됩니다. 특정 파티션의 데이터 만을 읽어오는 것과, 전체 테이블의 데이터를 읽어와서 필터링 하는 것. 전자가 훨씬 더 퍼포먼스가 좋을 것이 명백하죠.&lt;/p&gt;

&lt;p&gt;따라서, 최대한 조건 절 내에서는 사용자 정의 함수의 사용을 피하고 제공되는 기본 함수를 사용해야 합니다.&lt;/p&gt;
&lt;div class=&quot;language-sql highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;k&quot;&gt;SELECT&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;p_date&lt;/span&gt;
	&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;COUNT&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;DISTINCT&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;column_name&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;FROM&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;table_name&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;WHERE&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;p_date&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;BETWEEN&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;nc_to_char&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;'20211006'&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;s1&quot;&gt;'yyyy-MM-dd'&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;AND&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;nc_to_char&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;'20211008'&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;s1&quot;&gt;'yyyy-MM-dd'&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;GROUP&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;BY&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;p_date&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;
&lt;p&gt;위의 쿼리에서 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;p_date&lt;/code&gt;는 파티션 키 컬럼이고 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;nc_to_char(a, b)&lt;/code&gt;라는 함수는 a값을 b의 포맷에 맞게 변환시켜주는 UDF 입니다. 이 때, 해당 쿼리의 성능을 생각해본다면 해당 UDF 대신 다른 기본 제공 함수를 써야하겠죠. 이 경우, 동일한 기능을 하는 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;DATE_FORMAT(a, b)&lt;/code&gt; 라는 함수를 사용하여 아래처럼 쿼리를 수정할 수 있습니다.&lt;/p&gt;

&lt;div class=&quot;language-sql highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;k&quot;&gt;SELECT&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;p_date&lt;/span&gt;
	&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;COUNT&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;DISTINCT&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;column_name&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;FROM&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;table_name&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;WHERE&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;p_date&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;BETWEEN&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;DATE_FORMAT&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;'20211006'&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;s1&quot;&gt;'yyyy-MM-dd'&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;AND&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;DATE_FORMAT&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;'20211008'&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;s1&quot;&gt;'yyyy-MM-dd'&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;GROUP&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;BY&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;p_date&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;&lt;br /&gt;&lt;/p&gt;

&lt;h3 id=&quot;1-2-distinct-count-연산-피하기&quot;&gt;1-2. DISTINCT COUNT 연산 피하기&lt;/h3&gt;
&lt;p&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;COUNT(DISTINCT 컬럼명)&lt;/code&gt; 함수는 컬럼 내 중복을 제외한 데이터의 건수를 제외하기 위해 사용합니다. 이 때, 전체 데이터에서 중복이 제거된 건수를 계산해야 하므로 단 하나의 리듀스 테스크가 해당 작업을 맡아서 처리하게 되죠. 즉, 데이터의 건수가 얼마나 많은지와는 상관없이 1개의 리듀스 테스크만이 할당되게 됩니다. 이 경우, 하둡의 장점인 분산처리의 이점을 전혀 활용하지 못하게 됩니다.&lt;/p&gt;
&lt;div class=&quot;language-sql highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;k&quot;&gt;SELECT&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;p_date&lt;/span&gt;
	&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;COUNT&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;DISTINCT&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;column_name&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;FROM&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;table_name&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;WHERE&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;p_date&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;BETWEEN&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;DATE_FORMAT&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;'20211006'&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;s1&quot;&gt;'yyyy-MM-dd'&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;AND&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;DATE_FORMAT&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;'20211008'&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;s1&quot;&gt;'yyyy-MM-dd'&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;GROUP&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;BY&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;p_date&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;
&lt;p&gt;위의 예시 쿼리는 설명한 것처럼 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;COUNT&lt;/code&gt; 함수 안에서 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;DISTINCT&lt;/code&gt; 작업이 이루어지는 형태이기 때문에 단 1개의 리듀서만을 할당 받게 됩니다. 이를 방지하기 위해서 아래처럼 쿼리를 수정할 수 있습니다.&lt;/p&gt;
&lt;div class=&quot;language-sql highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;k&quot;&gt;SELECT&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;p_date&lt;/span&gt;
	&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;COUNT&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;column_name&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;FROM&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;
	&lt;span class=&quot;k&quot;&gt;SELECT&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;p_date&lt;/span&gt;
	    &lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;column_name&lt;/span&gt;
	&lt;span class=&quot;k&quot;&gt;FROM&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;table_name&lt;/span&gt;
	&lt;span class=&quot;k&quot;&gt;WHERE&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;p_date&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;BETWEEN&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;DATE_FORMAT&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;'20211006'&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;s1&quot;&gt;'yyyy-MM-dd'&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;AND&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;DATE_FORMAT&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;'20211008'&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;s1&quot;&gt;'yyyy-MM-dd'&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
	&lt;span class=&quot;k&quot;&gt;GROUP&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;BY&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;p_date&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;column_name&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;tmp&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;GROUP&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;BY&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;p_date&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;
&lt;p&gt;수정된 쿼리를 보면, 서브 쿼리 안에서 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;GROUP BY&lt;/code&gt;를 통해 중복이 제거된 중간 데이터 결과 셋을 생성하고 있습니다. &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;COUNT(DISTINCT 컬럼명)&lt;/code&gt;의 방법과 달리 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;GROUP BY&lt;/code&gt; 연산은 다수의 리듀스가 작업을 분배하여 처리하게 됩니다. 결과적으로 제일 바깥에 있는 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;COUNT&lt;/code&gt; 연산은 크기가 감소된 중간 데이터 결과 셋을 입력으로 사용하게 될 것입니다.&lt;/p&gt;

&lt;p&gt;&lt;br /&gt;&lt;/p&gt;

&lt;h3 id=&quot;1-3-join-사용-시-고려사항&quot;&gt;1-3. JOIN 사용 시 고려사항&lt;/h3&gt;
&lt;p&gt;HiveQL에서 조인을 사용할 때는 몇 가지 고려해야 할 사항들이 있습니다.&lt;/p&gt;

&lt;p&gt;첫번째로 가장 큰 테이블이 가장 마지막 순서로 오도록 해야 합니다. 이는 HIVE 옵티마이저는 쿼리의 마지막 테이블이 가장 크다고  가정하기 때문입니다. 마지막 테이블을 가장 큰 테이블로 가정하고 있기 때문에 상대적으로 작은 크기라고 생각되는 앞의 테이블들을 버퍼링하려고 시도하고, 각 레코드에 대해서 조인을 수행한 후, 제일 마지막 테이블로 보내는 방식으로 조인 연산이 일어나게 됩니다. 즉, 가장 큰 테이블이 마지막에 위치하지 않을 경우에는 해당 테이블이 버퍼에 올라가기 때문에 성능 저하가 발생할 수 있겠죠.&lt;/p&gt;

&lt;p&gt;하지만, 때로는 쿼리의 가독성 측면에서 아니면 다른 이유로 가장 큰 테이블을 쿼리의 마지막에 위치시키지 못하는 경우가 있습니다. 이럴 때는 다음과 같이 처리 가능합니다.&lt;/p&gt;
&lt;div class=&quot;language-sql highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;k&quot;&gt;SELECT&lt;/span&gt; &lt;span class=&quot;cm&quot;&gt;/*+STREAMTABLE(a)*/&lt;/span&gt;
	&lt;span class=&quot;n&quot;&gt;a&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;*&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;b&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;*&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;FROM&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;a&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;JOIN&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;b&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;ON&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;a&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;column_name&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;b&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;column_name&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;WHERE&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;a&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;column_name&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;IS&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;NOT&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;NULL&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;
&lt;p&gt;위의 쿼리에서처럼 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;/*+STREAMTABLE(a)&lt;/code&gt; 구문은 쿼리상 a 테이블이 제일 마지막에 위치하지는 않았지만, HIVE 옵티마이저에게 a 테이블을 가장 마지막에 사용하게끔 합니다.&lt;/p&gt;

&lt;p&gt;두번째는 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;OUTER JOIN&lt;/code&gt;과 파티션 필터의 순서를 이해하는 것입니다. 쿼리를 짤 때, 검색 최적화를 위해 우리는 파티션 필터를 종종 채택합니다. 하지만, &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;OUTER JOIN&lt;/code&gt; 연산은 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;WHERE&lt;/code&gt; 조건 절의 파티션 필터를 무시한 채 진행됩니다. 이유는 바로 조인을 수행한 후에 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;WHERE&lt;/code&gt; 절을 평가하기 때문입니다. 즉 순서상으로 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;JOIN&lt;/code&gt; 이후에 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;WHERE&lt;/code&gt; 조건절을 진행하기 때문에 파티션 조건이 적용되지 않은 상태에서 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;JOIN&lt;/code&gt; 연산이 먼저 수행되는 것이죠. 이 경우, 당연히 성능이 저하될 수 밖에 없습니다.&lt;/p&gt;

&lt;div class=&quot;language-sql highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;k&quot;&gt;SELECT&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;a&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;column_name&lt;/span&gt;
	&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;b&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;column_name&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;FROM&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;a&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;LEFT&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;OUTER&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;JOIN&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;b&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;ON&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;a&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;condition&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;b&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;condition&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;WHERE&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;a&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;partition_column&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;s1&quot;&gt;'a_value'&lt;/span&gt;
  &lt;span class=&quot;k&quot;&gt;AND&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;b&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;partition_column&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;s1&quot;&gt;'b_value'&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;
&lt;p&gt;이 때, 아래 쿼리와 같이 모든 조인에서 &lt;strong&gt;중첩 SELECT 문&lt;/strong&gt;을 사용하여 파티션 필터를 사용하도록 만들 수 있습니다.&lt;/p&gt;
&lt;div class=&quot;language-sql highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;k&quot;&gt;SELECT&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;a&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;column_name&lt;/span&gt;
	&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;b&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;column_name&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;FROM&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;
	&lt;span class=&quot;k&quot;&gt;SELECT&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;*&lt;/span&gt;
	&lt;span class=&quot;k&quot;&gt;FROM&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;a&lt;/span&gt;
	&lt;span class=&quot;k&quot;&gt;WHERE&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;partition_column&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;s1&quot;&gt;'a_value'&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;a&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;LEFT&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;OUTER&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;JOIN&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;
	&lt;span class=&quot;k&quot;&gt;SELECT&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;*&lt;/span&gt;
	&lt;span class=&quot;k&quot;&gt;FROM&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;b&lt;/span&gt;
	&lt;span class=&quot;k&quot;&gt;WHERE&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;partition_column&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;s1&quot;&gt;'b_value'&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;b&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;ON&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;a&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;condition&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;b&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;condition&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;&lt;br /&gt;&lt;/p&gt;

&lt;h3 id=&quot;1-4-select-사용-시-고려사항&quot;&gt;1-4. SELECT 사용 시 고려사항&lt;/h3&gt;
&lt;p&gt;SELECT 절에서도 성능을 위해서 고려해야 하는 부분이 있습니다. 바로 필요한 컬럼만을 가져와서 사용하게 하는 것이죠. ETL에서 사용하는 테이블들은 대부분 대규모의 테이블입니다. 많게는 수 십개의 컬럼을 가지는 테이블도 있죠. 이렇게 많은 갯수의 컬럼들로 이루어져 있을 때 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;SELECT *&lt;/code&gt; 대신 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;SELECT a, b, c&lt;/code&gt;와 같이 필요한 컬럼만을 명시하여 사용하는 것이 훨씬 좋습니다.&lt;/p&gt;

&lt;p&gt;특히 Parquet 형식의 데이터의 경우는 컬럼 단위로 파일이 적재됩니다. 즉, 컬럼을 명시하지 않으면 모든 데이터를 불러와서 사용해야 하지만 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;SELECT&lt;/code&gt; 절에서 컬럼명을 명시해주는 것만으로도 필요한 파일만을 불러와 사용할 수 있게 되는 것입니다.&lt;/p&gt;

&lt;div class=&quot;language-sql highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;k&quot;&gt;SELECT&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;a&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;column_name&lt;/span&gt;
	&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;b&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;column_name&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;FROM&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;
	&lt;span class=&quot;k&quot;&gt;SELECT&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;*&lt;/span&gt;
	&lt;span class=&quot;k&quot;&gt;FROM&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;a&lt;/span&gt;
	&lt;span class=&quot;k&quot;&gt;WHERE&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;partition_column&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;s1&quot;&gt;'a_value'&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;a&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;LEFT&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;OUTER&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;JOIN&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;
	&lt;span class=&quot;k&quot;&gt;SELECT&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;*&lt;/span&gt;
	&lt;span class=&quot;k&quot;&gt;FROM&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;b&lt;/span&gt;
	&lt;span class=&quot;k&quot;&gt;WHERE&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;partition_column&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;s1&quot;&gt;'b_value'&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;b&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;ON&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;a&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;condition&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;b&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;condition&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;
&lt;p&gt;앞서 사용한 쿼리입니다. 중접 SELECT 문을 사용하여 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;JOIN&lt;/code&gt; 연산 전에 파티션 필터를 거치기 때문에 원래의 쿼리보다 성능은 향상되었지만 여전히 중첩 SELECT 문 안에서는 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;*&lt;/code&gt;를 통해 모든 컬럼을 불러오고 있는 상황입니다. 이 경우 필요한 컬럼만을 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;SELECT&lt;/code&gt; 해오도록 아래와 같이 수정한다면 성능이 훨씬 향상될 것입니다.&lt;/p&gt;

&lt;div class=&quot;language-sql highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;k&quot;&gt;SELECT&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;a&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;column_name&lt;/span&gt;
	&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;b&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;column_name&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;FROM&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;
	&lt;span class=&quot;k&quot;&gt;SELECT&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;column_name&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;condition&lt;/span&gt;
	&lt;span class=&quot;k&quot;&gt;FROM&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;a&lt;/span&gt;
	&lt;span class=&quot;k&quot;&gt;WHERE&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;partition_column&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;s1&quot;&gt;'a_value'&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;a&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;LEFT&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;OUTER&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;JOIN&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;
	&lt;span class=&quot;k&quot;&gt;SELECT&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;column_name&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;condition&lt;/span&gt;
	&lt;span class=&quot;k&quot;&gt;FROM&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;b&lt;/span&gt;
	&lt;span class=&quot;k&quot;&gt;WHERE&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;partition_column&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;s1&quot;&gt;'b_value'&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;b&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;ON&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;a&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;condition&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;b&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;condition&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;hr /&gt;

&lt;p&gt;&lt;br /&gt;&lt;/p&gt;

&lt;h2 id=&quot;2-rdbms-쿼리-측면에서-성능-향상하기&quot;&gt;2. RDBMS 쿼리 측면에서 성능 향상하기&lt;/h2&gt;
&lt;p&gt;앞서 HiveQL에 대해서 이야기하였으니, 이번에는 RDBMS 쿼리 측면에서 어떻게 성능을 향상시킬 수 있을지에 대해 이야기해 볼 차례입니다.&lt;/p&gt;

&lt;p&gt;&lt;br /&gt;&lt;/p&gt;

&lt;h3 id=&quot;2-1-불필요한-인덱스-줄이기&quot;&gt;2-1. 불필요한 인덱스 줄이기&lt;/h3&gt;
&lt;p&gt;평소 인지하지 못할 수 있지만, 데이터 웨어하우스가 과도하게 인덱싱되어 있는 경우는 상당히 빈번하게 발생합니다. 인덱스 생성은 성능 문제를 해결하기 위해 주로 채택되는 접근 방식이기 때문이죠. 하지만, ETL 작업의 경우에는 일반적으로 성능 향상에 인덱싱은 도움이 되지 않을 뿐더러 로드 시간도 늘어나게 됩니다. 모든 추가 인덱스는 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;INSERT&lt;/code&gt;, &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;UPDATE&lt;/code&gt; 또는 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;MERGE&lt;/code&gt; 문의 DML 성능을 낮추며, 최악의 경우에는 옵티마이저가 조인의 방식으로 &lt;strong&gt;Nested Loops Join&lt;/strong&gt;을 사용하도록 합니다.&lt;/p&gt;

&lt;p&gt;옵티마이저가 조인의 방식으로 Nested Loops Join을 채택하는 것이 왜 좋지 않은 것일까요?
Nested Loops Join 이란 바깥 테이블의 처리 범위를 하나씩 액세스하면서 추출된 값으로 안 쪽 테이블을 조인하는 방식입니다. 흔히 사용하는 이중 FOR문을 도는 방식으로 조인 연산을 처리한다고 생각하면 이해하기가 더 쉬울 것 같습니다.
이러한 Nested Loops Join의 특징과 사용 경우는 다음과 같습니다.&lt;/p&gt;
&lt;ul&gt;
  &lt;li&gt;특징
    &lt;ul&gt;
      &lt;li&gt;순차적으로 처리&lt;/li&gt;
      &lt;li&gt;순차적으로 돌기 때문에 안 쪽 테이블에 인덱스 필요&lt;/li&gt;
      &lt;li&gt;메모리 사용량이 가장 적음&lt;/li&gt;
      &lt;li&gt;두 테이블의 랜덤 I/O가 높게 나옴&lt;/li&gt;
      &lt;li&gt;많은 양의 데이터를 조회하는 경우의 성능은 낮음&lt;/li&gt;
    &lt;/ul&gt;
  &lt;/li&gt;
  &lt;li&gt;사용 경우
    &lt;ul&gt;
      &lt;li&gt;외부 테이블로 사용되는 테이블의 양이 작고 내부 테이블로 사용되는 테이블의 양이 많을 때&lt;/li&gt;
      &lt;li&gt;내부 테이블에 인덱스가 생성되어 있을 때&lt;/li&gt;
      &lt;li&gt;적은 양의 행에 대해서 일어나는 트랜잭션이 다수 발생하는 경우&lt;/li&gt;
    &lt;/ul&gt;
  &lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;ETL 작업들은 주로 대량의 데이터를 대상으로 합니다. 이 경우에 Nested Loops Join은 적합한 조인 방식이 아닌 것이죠. 또한 큰 데이터 집합을 사용하는 ETL 작업의 특성 상 테이블의 작은 비율만 반환하도록 설계된 인덱스는 성능 향상에 있어서 당연히 적절한 방법이 아닙니다.&lt;/p&gt;

&lt;p&gt;따라서, 가능한 한 적은 수의 인덱스를 사용해야 합니다. 기본키, 고유 제약 조건에 대해서만 인덱스를 두는 것이 베스트겠죠?&lt;/p&gt;

&lt;p&gt;&lt;br /&gt;&lt;/p&gt;

&lt;h3 id=&quot;2-2-where-조건에서-함수-피하기&quot;&gt;2-2. WHERE 조건에서 함수 피하기&lt;/h3&gt;
&lt;p&gt;WHERE 조건에서 표현식 또는 함수 호출을 하는 것을 피해야 합니다. 필터 조건에서 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;UPPER&lt;/code&gt;, &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;SUBSTR&lt;/code&gt; 등과 같은 SQL 함수를 사용하면 수정되지 않은 열만 사용하는 경우보다 옵티마이저가 카디널리티를 추정하기 훨씬 어려워집니다. 당연히, 원하는 모양의 데이터를 얻어야 하니 모든 필터 조건에서 표현식 또는 함수를 제거할 수는 없지만, 때로는 이전 단계에서 변환을 수행하는 것이 성능에 있어서 유리합니다.&lt;/p&gt;

&lt;p&gt;이해하기 쉽게 예시를 들어볼게요. 백만 개의 행이 있는 테이블에 대한 쿼리를 가정해 봅시다.&lt;/p&gt;
&lt;ul&gt;
  &lt;li&gt;쿼리에는 AND 연산자로 이루어진 SQL 함수를 호출하는 필터 조건 3개가 포함되어 있습니다.&lt;/li&gt;
  &lt;li&gt;옵티마이저는 각 필터 조건에 대해 1%의 선택도를 가정합니다.&lt;/li&gt;
  &lt;li&gt;해당 쿼리의 카디널리티는 0.01 x 0.01 x 0.01 x 1000000 = 1(행)이 됩니다.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;즉, 해당 필터 조건들이 실제로는 저렇게 낮은 선택도를 가지지 않더라도 함수로 이루어진 조건들에  대해서 옵티마이저가 실제보다 낮은 선택도를 가정하게 되고 카디널리티는 아주 작게 계산되게 됩니다. 이처럼 작은 카디널리티일 때, 옵티마이저가 Nested Loops Join을 선택할 확률은 매우 높아집니다. (Nested Loops Join의 단점은 위에서 설명했으니 넘어가겠습니다.)&lt;/p&gt;

&lt;p&gt;&lt;br /&gt;&lt;/p&gt;

&lt;h3 id=&quot;2-3-where절-안에-있는-or-조건에-주의하기&quot;&gt;2-3. WHERE절 안에 있는 OR 조건에 주의하기&lt;/h3&gt;
&lt;p&gt;옵티마이저는 OR로 결합된 복잡한 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;WHERE&lt;/code&gt; 조건에 대해서 최적화 되지 않은 실행계획을 만들 가능성이 높습니다. 
&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;column = 1111 OR column IS NULL&lt;/code&gt; 과 같은 구문은 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;IFNULL(column, 1111) = 1111&lt;/code&gt; 로 대체할 수 있듯이, 최대한 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;WHERE&lt;/code&gt; 조건에서 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;OR&lt;/code&gt; 조건으로 결합되는 상황을 피하는 것이 좋습니다.
혹은, &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;OR&lt;/code&gt; 이 있는 SQL 문을 두 개의 개별 쿼리문으로 분할하고 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;UNION&lt;/code&gt; 처리하는 것이 원래의 SQL 문보다 훨씬 빠르게 처리됩니다.&lt;/p&gt;

&lt;p&gt;&lt;br /&gt;&lt;/p&gt;

&lt;hr /&gt;

&lt;p&gt;&lt;br /&gt;&lt;/p&gt;

&lt;h2 id=&quot;3-etl-작업-측면에서-성능-향상하기&quot;&gt;3. ETL 작업 측면에서 성능 향상하기&lt;/h2&gt;
&lt;p&gt;지금까지는 HiveQL과 RDBMS 쿼리를 수정하는 것으로 ETL 자체의 성능을 향상시키는 방법에 대해서 소개했다면, 이번 파트에서는 ETL 작업 설계를 어떻게 하면 수월하게 할 수 있을지, ETL 수정 빈도를 어떻게 하면 줄일 수 있을지와 같은 ETL 작업을 하는 과정에서의 성능 향상에 대해 이야기 해보려 합니다.&lt;/p&gt;

&lt;p&gt;&lt;br /&gt;&lt;/p&gt;

&lt;h3 id=&quot;3-1-가능한-빨리-테스트-데이터로-채우기&quot;&gt;3-1. 가능한 빨리 테스트 데이터로 채우기&lt;/h3&gt;
&lt;p&gt;ETL 프로세스의 핵심 단계는 최종적으로 적재되는 데이터의 형태를 이해하는 것입니다. 결과적으로 내가 얻고 싶은 데이터 모델이 어떤 형태인지, 어떤 모양의 데이터를 채울 지 이해하는 것이 가장 중요합니다.&lt;/p&gt;

&lt;p&gt;샘플 데이터로 데이터 웨어하우스의 팩트 및 디멘젼 테이블을 채우면, 그 이후로 이어질 모든 작업들이 최종적으로 우리가 목표하고 있는 결과물과 일치하는지 확인하는 것에 많은 도움이 됩니다. 이 때 다수의 데이터가 아니라 소수의 행만 채워도 ETL 개발자는 실제 로직을 통해 목표 테이블의 데이터를 채우는 데 사용할 ETL 모델의 구조를 수월하게 설계할 수 있습니다.&lt;/p&gt;

&lt;p&gt;&lt;br /&gt;&lt;/p&gt;

&lt;h3 id=&quot;3-2-소스-데이터-검증-및-키-정의&quot;&gt;3-2. 소스 데이터 검증 및 키 정의&lt;/h3&gt;
&lt;p&gt;ETL 작업을 개발하기 전에 주요 데이터 관계, 값의 범위 열 이상 등의 식별은 꼭 이루어져야 하는 단계입니다. 특히, 모든 소스 테이블에서 기본 키 정의와 해당 키와 관련된 정보들을 식별하는 것이 중요합니다. 해당 테이블의 기본 키가 의미하는 바가 무엇인지, 자료형은 무엇인지, 이 기본키와 다른 테이블의 컬럼과는 어떤 관계가 형성되어있는지 등이 모두 파악되어야 하는 정보에 포함되겠죠.&lt;/p&gt;

&lt;p&gt;소스 데이터에 누락으로 인한 구멍이 있는지도 확인이 필요한 부분입니다. 현재 소스 테이블의 모든 데이터가 정상적으로 적재되고 있는지, 누락되고 있는 데이터가 있다면 누락의 원인이 무엇인지, 해당 데이터의 누락이 이번 ETL 작업에 영향을 얼마나 끼칠지 등이 파악되어야 합니다.&lt;/p&gt;

&lt;p&gt;이처럼 소스 데이터에 대한 이해와 검증이 이루어지지 않았다면 올바르게 해당 소스 데이터를 사용할 수 없습니다. 데이터 유형을 미리 확인하지 않아, 다 완성한 ETL 작업에서 후에 데이터 유형에 따른 문제가 발생할 수도 있고 소스 데이터의 의미를 제대로 파악하지 못해서 처음 의도와는 다른 ETL이 만들어져 전체적인 로직을 수정해야 할 수도 있습니다. 이런 불상사를 막기 위해서 소스 데이터 검증은 ETL 개발을 시작하는 단계에서 필수적으로 이루어져야 합니다.&lt;/p&gt;

&lt;p&gt;&lt;br /&gt;&lt;/p&gt;

&lt;h3 id=&quot;3-3-etl-로그&quot;&gt;3-3. ETL 로그&lt;/h3&gt;
&lt;p&gt;비단 ETL 뿐만이 아니라 모든 개발 작업에서 로그 유지는 상당히 중요합니다. ETL 로그 역시 매우 중요한데요. 과거의 로그를 기반으로 현재 ETL 작업의 문제점을 파악하고 해결할 수 있기 때문입니다.
유지해야 하는 로그의 종류는 다음과 같습니다.&lt;/p&gt;
&lt;ul&gt;
  &lt;li&gt;ETL 작업 시작 시간 및 실행에 걸린 시간
    &lt;ul&gt;
      &lt;li&gt;평소 ETL 작업 시간에 대한 로그를 통해 유달리 빨리 끝났다던가, 아니면 이상하게 오래 걸린 경우를 판별하여 ETL 오류를 잡아낼 수 있습니다.&lt;/li&gt;
    &lt;/ul&gt;
  &lt;/li&gt;
  &lt;li&gt;모든 오류 로그
    &lt;ul&gt;
      &lt;li&gt;동일 오류 발생 시 해결 과정을 찾기에 매우 유리합니다.&lt;/li&gt;
    &lt;/ul&gt;
  &lt;/li&gt;
  &lt;li&gt;삽입/변경/삭제가 일어난 데이터 행 수
    &lt;ul&gt;
      &lt;li&gt;해당 로그를 통해 정상적으로 ETL이 동작하였는지 확인 가능합니다.&lt;/li&gt;
    &lt;/ul&gt;
  &lt;/li&gt;
  &lt;li&gt;이번 ETL 작업으로 인하여 생성된 키
    &lt;ul&gt;
      &lt;li&gt;목표했던 데이터가 이번 ETL로 잘 적재되었는지 확인할 수 있습니다.&lt;/li&gt;
    &lt;/ul&gt;
  &lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;br /&gt;&lt;/p&gt;

&lt;h3 id=&quot;3-4-소스-시스템-검증&quot;&gt;3-4. 소스 시스템 검증&lt;/h3&gt;
&lt;p&gt;앞서 말했던 소스 데이터 검증이 소스 데이터에 누락된 데이터가 있는지, 어떤 자료형을 가지고 있는지 등의 내용을 검증하는 것이라면, 시스템 검증은 소스 데이터를 적재하는 시스템에 관한 검증입니다. 평소에 추출되던 소스 데이터 양보다 급격하게 적은 행 수의 소스 데이터가 적재되고 있는지, 소스 데이터가 적재되는 로직에 변경사항이 있었는지 혹은 같은 로직이 적용되는 소스 데이터가 동일한 데이터 값을 가지고 있는지 등이 이에 포함됩니다.&lt;/p&gt;

&lt;p&gt;그렇다면 소스 시스템 검증은 어떻게 할 수 있을까요? 총 두 가지 위치에서 검증을 진행할 수 있습니다. 먼저, 처음 해당 소스 데이터를 로드하는 부분입니다. 이 부분에서 불일치가 일어났다면 소스 데이터를 추출하는 시스템에 문제가 있다는 의미입니다. 두 번째는 결과적으로 추출된 소스 데이터를 적재하는 부분입니다. 이 부분에서 불일치가 일어났다면, 소스 추출은 성공적으로 작동했지만 최종 데이터 모델에 적재할 때 데이터가 누락되거나 또는 이중 계산이 이루어지고 있는 것입니다. 이렇게 어느 부분에서 문제가 있는지 확인하면 훨씬 수월하게 소스 시스템 문제의 원인을 파악할 수 있겠죠?&lt;/p&gt;

&lt;p&gt;&lt;br /&gt;&lt;/p&gt;

&lt;h3 id=&quot;3-5-스케줄링&quot;&gt;3-5. 스케줄링&lt;/h3&gt;
&lt;p&gt;ETL 작업에서 스케줄링은 매우 중요한 역할을 합니다. 스케줄링을 어떻게 하느냐에 따라서 작업의 속도와 효율이 향상될 수도 저하될 수도 있기 때문이죠. 여기서는 이해하기 쉽게 스케줄링을 크게 세 가지로 세분화하여 설명해보겠습니다.&lt;/p&gt;

&lt;p&gt;먼저, &lt;strong&gt;작업을 실행해야 하는 시기&lt;/strong&gt;에 대한 스케줄링입니다. 대부분의 ETL 작업이 그렇듯이, 기존의 데이터를 가지고 최종 데이터 모델에 적재할 데이터를 추출하는 경우를 말합니다. 전 날의 데이터를 가지고 작업을 실행해야 할 때, 오늘의 데이터로 실행되는 ETL 작업의 스케줄 일자는 내일이 될 것입니다. 같은 원리로 한 시간 전의 데이터를 사용할 때는 현재의 데이터를 사용하여 1시간 후의 ETL 스케줄을 돌리게 될 것입니다.&lt;/p&gt;

&lt;p&gt;두 번째는 &lt;strong&gt;이벤트&lt;/strong&gt;에 따른 스케줄링입니다. 다른 작업이 완료될 때까지 특정 작업을 시작할 수 없거나, 혹은 소스 데이터가 적재되기 전까지 작업을 시작할 수 없는 경우가 이에 해당합니다. 이렇게 소스 데이터 혹은 앞 쪽의 작업에 대해 의존성이 생기는 경우, 해당 작업의 완료 여부를 알아야 하겠죠. Airflow를 사용한다고 가정했을 때, 해당 문제는 Sensor를 사용하여 쉽게 해결이 가능합니다. 앞의 작업에 영향을 받는 경우라면 해당 작업이 완료되었는지를 체크하는 Sensor를, 소스 데이터에 영향을 받는 경우라면 해당 데이터가 적재되었는지를 체크하는 Sensor를 둔다면 보다 깔끔한 스케줄링이 가능합니다.&lt;/p&gt;

&lt;p&gt;마지막은 &lt;strong&gt;병렬처리&lt;/strong&gt;에 따른 스케줄링입니다. 연속적으로 로직이 적용되는 경우가 아니라면 병렬로 ETL 작업을 실행하는 게 훨씬 더 성능에 도움이 됩니다. 전 일, 전 시간의 데이터가 현재 데이터에 영향을 미치지 않고 독립적으로 적재되는 경우, 굳이 순서대로 ETL 작업을 실행할 필요가 없겠죠? 이럴 때는 병렬로 동시에 여러 작업을 실행하여 성능을 향상시킬 수 있습니다.&lt;/p&gt;

&lt;p&gt;&lt;br /&gt;&lt;/p&gt;

&lt;h3 id=&quot;3-6-용량계획&quot;&gt;3-6. 용량계획&lt;/h3&gt;
&lt;p&gt;실제 늘어날 용량을 미리 정확하게 에측할 수는 없지만, 대략적으로라도 데이터베이스의 예상 성장을 추측하는 것이 앞으로의 작업을 대비하기 위해서는 필수적입니다.&lt;/p&gt;

&lt;p&gt;데이터베이스의 예상 성장을 결정할 때 고려해야 하는 항목은 다음과 같습니다.&lt;/p&gt;
&lt;ul&gt;
  &lt;li&gt;새롭게 추가될 것이라고 예상되는 데이터
    &lt;ul&gt;
      &lt;li&gt;앞으로 추가될 예정인 데이터에 대한 정보가 있다면 더욱 세밀한 용량 계획이 가능합니다.&lt;/li&gt;
    &lt;/ul&gt;
  &lt;/li&gt;
  &lt;li&gt;백업 데이터
    &lt;ul&gt;
      &lt;li&gt;백업 데이터의 생성주기와 너무 오래된 백업 데이터의 삭제 주기, 크기 등이 해당됩니다.&lt;/li&gt;
    &lt;/ul&gt;
  &lt;/li&gt;
  &lt;li&gt;정기 스냅샷
    &lt;ul&gt;
      &lt;li&gt;복구를 위해 정기적으로 저장하는 스냅샷에 대해서도 당연히 고려가 되어야 합니다.&lt;/li&gt;
    &lt;/ul&gt;
  &lt;/li&gt;
  &lt;li&gt;운영 데이터 혹은 중간 영역 데이터
    &lt;ul&gt;
      &lt;li&gt;현재 스케줄링이 되어 돌아가고 있는 ETL 작업과정에서 생겨나는 중간 영역 데이터, 그를 관리하기 위한 운영데이터 등이 해당됩니다.&lt;/li&gt;
    &lt;/ul&gt;
  &lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;위와 같이 다양한 항목들을 고려하고 팀과 함께 주기적으로 데이터베이스의 증가 패턴 등을 공유하고 검토하는 것이 필요합니다. 더 많은 항목들을 고려할 수록 더욱 디테일한 용량 계획이 가능하겠죠? 주로 분기별로 용량 계획을 검토하는 것이 적절하다고 합니다.&lt;/p&gt;

&lt;p&gt;&lt;br /&gt;&lt;/p&gt;

&lt;hr /&gt;

&lt;p&gt;&lt;br /&gt;&lt;/p&gt;

&lt;h2 id=&quot;4-마치며&quot;&gt;4. 마치며&lt;/h2&gt;
&lt;p&gt;지금까지 HiveQL, RDBMS 쿼리 측면에서 성능을 향상시킬 수 있는 간단한 방법들과 ETL 작업을 하는 것에 있어서 효율적인 작업을 위해 도움이 될 만한 팁들을 몇 가지 소개드렸습니다.&lt;/p&gt;

&lt;p&gt;원하는 결과 데이터를 추출 및 적재하는 것도 물론 중요하지만, 그 과정에서 성능까지 고려하여 작업한다면 금상첨화겠지요. 저도 작성하면서 그동안 너무 기능에만 치중한 쿼리 작성과 ETL 작업을 해오지 않았나 하는 생각이 많이 들었습니다.&lt;/p&gt;

&lt;p&gt;아래에 해당 게시글을 작성하며 참고한 자료들을 첨부할 테니, 시간 나실 때 다들 읽어보시면 좋을 것 같습니다.&lt;/p&gt;

&lt;p&gt;그럼 또 다음 게시글로 찾아뵙겠습니다. 감사합니다.&lt;/p&gt;

&lt;p&gt;&lt;br /&gt;&lt;/p&gt;

&lt;hr /&gt;

&lt;p&gt;&lt;br /&gt;&lt;/p&gt;

&lt;h2 id=&quot;appendix&quot;&gt;Appendix.&lt;/h2&gt;
&lt;ul&gt;
  &lt;li&gt;&lt;a href=&quot;https://danischnider.wordpress.com/2017/07/23/10-tips-to-improve-etl-performance/&quot;&gt;https://danischnider.wordpress.com/2017/07/23/10-tips-to-improve-etl-performance/&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;https://dataintegrationinfo.com/improve-etl-performance/&quot;&gt;https://dataintegrationinfo.com/improve-etl-performance/&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;https://docs.oracle.com/en/database/oracle/oracle-database/19/vldbg/partition-pruning.html#GUID-E677C85E-C5E3-4927-B3DF-684007A7B05D&quot;&gt;https://docs.oracle.com/en/database/oracle/oracle-database/19/vldbg/partition-pruning.html#GUID-E677C85E-C5E3-4927-B3DF-684007A7B05D&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;https://www.sqlshack.com/introduction-to-nested-loop-joins-in-sql-server/&quot;&gt;https://www.sqlshack.com/introduction-to-nested-loop-joins-in-sql-server/&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
</description>
        <pubDate>Fri, 05 Nov 2021 16:30:00 +0000</pubDate>
        <link>https://danbi-ncsoft.github.io//works/2021/11/05/etl-performace-tips.html</link>
        <guid isPermaLink="true">https://danbi-ncsoft.github.io//works/2021/11/05/etl-performace-tips.html</guid>
        
        
        <category>Works</category>
        
      </item>
    
      <item>
        <title>생성모델(Generation Model)이란 무엇인가?</title>
        <description>&lt;h2 id=&quot;introduction&quot;&gt;Introduction&lt;/h2&gt;

&lt;p&gt;Machine Learning/Deep Learning의 발전이 급격히 이루어지면서 다양한 인공지능 모델이 연구되고 만들어지고 있습니다. 최근에는 OpenAI의 GPT-3, NAVER CLOVA의 HYPERCLOVA등 거대 AI모델들이 출연하고 있습니다. 필자는 이런 모델들을 연구하고 공부하면서 이 모델자체를 어떻게 분류하는게 맞는가 하는 고민을 항상 같이 합니다. 예를 들어 어떤 모델을 사용하면 이 모델 자체를 분류모델로 보는것이 맞나? 생성모델로 보는것이 맞나? 라는 질문을 스스로 계속합니다. 사실, Pre-Trained 된 모델을 이용하여 다양한 Downstream Task에 사용하는 경우가 많아지다 보니, 어떤 Task에 사용하느냐에 따라 사용성이 결정되는 모델이 많기 때문에 이런 질문자체가 무의미한 경우도 많습니다. (ex) BERT를 이용하여 문장을 분류한다거나, Language Model의 특성을 이용하여 문장을 생성해내기도 합니다. - 물론 학습방식때문에 문장 생성은 BERT보다는 GPT-3를 권하는 경우가 많습니다.)&lt;/p&gt;

&lt;p&gt;쉴새없이 나오는 모델들과 알고리즘을 따라가기 위해 쉼없이 공부하다 보니, 정작 분류모델과 생성모델은 근본적으로 무엇이고 어떤 차이가 있는 것인가?라는 기본적인 질문에 대한 대답을 못하고 있는 자신을 발견하게 되었습니다. 생성모델이 무엇이냐는 질문을 받으면, 데이터를 생성해주는 모델! 이라는 정도의 대답을 못하였습니다. 그렇기에 몇주~몇개월간 자료조사 및 연구를 거쳐서 분류모델과 생성모델의 차이점 그리고 생성모델은 무엇인지에 대해 정리하게 되었습니다. 다만, 자료조사를 하면서 느꼈던 점은, 생성모델 자체를 정의하고 어떤 모델이 생성모델이다라고 정확히 분류한 자료는 많지 않다는 것입니다. 그 이유는 생성모델 자체가 보통 데이터의 분포를 추정하는 모델이 대부분이다 보니 추정된 분포를 이용하여 다른 TASK에 사용되는 경우로 알려져 있는 경우가 많이 있기 때문입니다. 예를들어 아래에서 설명할 Kernel Density Estimation은 데이터의 이상치를 찾는데 많이 활용되는 모델입니다. 하지만 이는 데이터 분포를 추정하여 데이터를 생성할 수 있는 생성모델이기도 합니다. 즉 이처럼 다른 Task로 알려져있는 경우도 많이 있어 자신이 생성모델을 활용하고 있으나 이를 모르는 경우도 많이 있습니다. 필자도 그랬습니다.&lt;/p&gt;

&lt;p&gt;여기에 정리되는 내용은 필자의 주관적인 생각도 많이 개입되어 있습니다. 어떤 것이 생성모델인가?에 대한 해답을 찾기 위해 스스로 조사하고 정리한 자료이다 보니, 잘못된 내용도 있을 수 있습니다. 발견하시면 언제든 댓글 및 저희 I&amp;amp;I실 이메일로 연락주시면 감사하겠습니다.&lt;/p&gt;

&lt;h2 id=&quot;판별모델-vs-생성모델&quot;&gt;판별모델 VS 생성모델&lt;/h2&gt;

&lt;p&gt;우선 판별모델(Discriminative Model)과 생성 모델(Generative Model)의 차이를 살펴봅시다.&lt;/p&gt;

&lt;p&gt;판별모델(Discriminative Model)이란 데이터 X가 주어졌을때 Label Y가 나타날 조건부확률 P(Y|X)를 직접적으로 반환하는 모델입니다. 분류모델이라고도 부르죠. 현재 저희 I&amp;amp;I실에서도 가장 많이 사용하고 있는 모델이기도 하죠. 당연히 LABEL 정보가 있어야하기 때문에 지도학습 모델이라고 볼 수 있습니다.&lt;/p&gt;

&lt;p&gt;생성모델(Generative Model)은 두가지로 나눌 수 있습니다.&lt;/p&gt;

&lt;ol&gt;
  &lt;li&gt;&lt;strong&gt;지도적 생성모델&lt;/strong&gt; : LABEL이 있는 데이터에 대해서 각 클래스 별 특징 데이터의 확률분포 P(X|Y)를 추정한 다음 베이즈 정리를 사용하여 P(Y|X)를 계산하는 방법입니다. P(Y|X)를 계산할 수 있기 때문에 분류모델로 활용할 수도 있고 클래스별 Conditional 확률 P(X|Y)를 추정했기 때문에 확률분포 상에서의 새로운 가상의 데이터를 생성하거나 확률분포 끝자락에 있는 데이터를 이상치로도 판단하는 이상치 판별 모델로도 활용할 수 있습니다. 아래 그림을 보면 조금 더 이해하기 편하실 것 같습니다.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;&lt;img src=&quot;https://danbi-ncsoft.github.io/assets/works/generator/그림1.png&quot; alt=&quot;7fb4eda6aa89713e268cf3d8fafcd283&quot; width=&quot;30&quot; height=&quot;400&quot; /&gt;&lt;/p&gt;

&lt;p&gt;​                                                        출처 : https://sites.google.com/site/machlearnwiki/bayesian-learning/saengseong-model&lt;/p&gt;

&lt;ol&gt;
  &lt;li&gt;&lt;strong&gt;비지도적 생성모델&lt;/strong&gt; : LABEL이 없어서, 데이터 X자체의 분포를 학습하여 X의 모분포를 추정하는 학습데이터의 분포를 학습하는 모델입니다. 실제 대부분의 생성모델은 이 비지도적 생성모델이라고 생각하면 될 것 같습니다. GAN기반의 모델류들을 이용하여 새롭고 신기한 사진을 생성해내는 경우를 많이 보셨을텐데요. 원본이미지(X)의 확률분포를 학습하여 새로운 데이터를 생성해내는 비지도적 생성모델의 대표적인 예시입니다.&lt;/li&gt;
&lt;/ol&gt;

&lt;h2 id=&quot;생성모델의-종류&quot;&gt;생성모델의 종류&lt;/h2&gt;

&lt;p&gt;위에서 설명하였듯이 생성모델은 지도적 생성모델 / 비지도적 생성모델로 나눌수 있습니다. 실제 예시와 함께 더 깊게 살펴봅시다.&lt;/p&gt;

&lt;h4 id=&quot;1-지도적-생성모델&quot;&gt;1. 지도적 생성모델&lt;/h4&gt;

&lt;p&gt;지도적 생성모델의 대표적인 예시로는 선형판별분석법(LDA), 이차판별분석법(QDA)가 있습니다. 다음은 지도적 생성모델을 모델링하는 방법인데요.&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;https://danbi-ncsoft.github.io/assets/works/generator/그림2.png&quot; alt=&quot;7fb4eda6aa89713e268cf3d8fafcd283&quot; /&gt;&lt;/p&gt;

&lt;p&gt;​                                                                                        출처 : https://datascienceschool.net/intro.html&lt;/p&gt;

&lt;p&gt;가능도, 즉 y의 클래스값에 따른 X의 분포에 대한 정보를 먼저 알아낸 후 , 베이즈 정리를 사용하여 주어진 X에 대한 Y의 확률분포를 찾는 것입니다. 사전확률은 보통 Y의 비율로 두는 경우가 많죠. 이차판별분석법(QDA)의 경우에는 다음과 같이 우도를 가정하는 것입니다. 즉, 독립변수 X가 실수이고 확률분포가 다변수 정규분포라고 가정하는 것입니다. 이 분포들을 알고 있으면, 독립변수 X에 대한 Y클래스의 조건부확률분포는 위의 식을 따라 구할 수 있겠죠?&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;https://danbi-ncsoft.github.io/assets/works/generator/그림3.png&quot; alt=&quot;7fb4eda6aa89713e268cf3d8fafcd283&quot; /&gt;&lt;/p&gt;

&lt;p&gt;​                                                                                       출처 :  https://datascienceschool.net/intro.html&lt;/p&gt;

&lt;p&gt;선형판별분석법과 이차판별분석법에 대해서 조금 더 자세하게 알고 싶다면 위 수식 출처에 있는 블로그를 추천드립니다.(제가 본 곳중에서 가장 상세하고 쉽게 설명이 되어 있네요.) 추가적으로, 선형판별분석법의 경우는 PCA와 같은 차원축소 기법으로 알려져있기도 하고, 분류모델로도 알려져있습니다. Introduction에서 설명드린것처럼, 이렇게 생성모델은 다른 task에서 쓰이고 있는 경우가 많습니다. 그래서 필자도 원래는 이게 생성모델이라고 생각을 못했었죠.&lt;/p&gt;

&lt;h4 id=&quot;2-비지도적-생성-모델&quot;&gt;2. 비지도적 생성 모델&lt;/h4&gt;

&lt;p&gt;다음은 생성모델의 대부분을 차지하고 있는 비지도적 생성모델을 확인해봅시다. 비지도적 생성 모델은 종류가 많아 통계적 생성 모델 그리고 딥러닝에서의 생성 모델로 분류를 해봤습니다.&lt;/p&gt;

&lt;h5 id=&quot;2-1-통계적-생성-모델&quot;&gt;2-1. 통계적 생성 모델&lt;/h5&gt;

&lt;p&gt;통계적 생성 모델이란 밀도 추정을 의미한다고 생각하면 좋을것 같습니다. 관측된 데이터들의 분포로부터 원래 변수의 확률분포를 추정하고자 하는 것이 밀도 추정입니다. 사실상 통계 책에 나오는 모수적/비모수적 추정과 같죠. 대표적인 예시로 커널 밀도 추정(Kernel Density Estimation)이 있습니다.  랜덤변수 X에 대한 PDF를 다음과 같이 추정하는 것입니다. 어떤 커널 모양이냐에 따라 PDF가 달라지겠죠?&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;https://danbi-ncsoft.github.io/assets/works/generator/그림4.png&quot; alt=&quot;7fb4eda6aa89713e268cf3d8fafcd283&quot; /&gt;&lt;/p&gt;

&lt;p&gt;아래 왼쪽 그래프를 보시면, 검은색 선이 원 데이터를 나타내고 , 이에 걸맞는 분포를 Kernel값을 통해 추정하는 겁니다. 여기서 커널은 ‘가우시안’, ‘TOPHAT’, ‘EPANECHNIKOV’를 사용했는데요, 원 데이터의 분포를 잘 추정하고 있음을 확인할 수 있습니다. 이처럼 원 데이터의 분포를 추정하는 생성모델을 Kernel Density Estimation이라고 합니다. 원 데이터의 분포를 추정했기 때문에 추정한 분포로부터 새로운 데이터를 생성할 수 있겠죠?오른쪽 MNIST 사진을 보시면 MNIST 픽셀들의 분포를 학습하여, KDE로 새로운 숫자이미지를 생성한 것입니다.&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;https://danbi-ncsoft.github.io/assets/works/generator/그림5.png&quot; alt=&quot;7fb4eda6aa89713e268cf3d8fafcd283&quot; /&gt;&lt;/p&gt;

&lt;p&gt;​                                                출처 : https://jayhey.github.io/novelty%20detection/2017/11/08/Novelty_detection_Kernel/&lt;/p&gt;

&lt;p&gt;또 하나 소개드릴 통계적 생성모델은 가우시안 밀도 추정/혼합 가우시안 밀도 추정 모델입니다. 사실 간단한데요. 원래의 분포가 정규분포를 따른다고 가정하는 것입니다. 혼합가우시안 밀도 추정 모델은 여러 가우시안 분포의 합으로 이루어져있다고 가정하는 것이죠. 왼쪽 아래 그림을 보시면 파란색 데이터 분포를 정규분포로 가정하고, 각각의 끝자락에 있는 부분들을 이상치로 판단하죠. 이렇게 원 데이터의 분포를 학습해 이상치 탐지 모델로도 쓰입니다. 혼합가우시안 모델 같은 경우는 군집화 방법으로도 활용이 됩니다. 우리가 많이 사용하는 k-means 같은 경우는 하나의 데이터는 하나의 군집에만 속할 수 있지만, GMM(Gaussian Mixture Model)같은 경우에는 한데이터가 여러군집에 속할 수 있으며, 각각의 군집에 속할 확률을 나타냅니다. GMM에서 쓰이는 EM알고리즘은 너무 복잡해서 여기서는 생략할게요…ㅎㅎ (여담으로 EM알고리즘은 Youtube를 통해 Expectation, Maximization과정이 어떻게 순차적으로 이뤄지는지 동영상을 통해 보는게 더 좋습니다. 수식으로 하면 머리 깨져요..)&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;https://danbi-ncsoft.github.io/assets/works/generator/그림6.png&quot; alt=&quot;7fb4eda6aa89713e268cf3d8fafcd283&quot; /&gt;&lt;/p&gt;

&lt;p&gt;​                                             출처 : https://jayhey.github.io/novelty%20detection/2017/11/02/Novelty_detection_Gaussian/&lt;/p&gt;

&lt;h5 id=&quot;2-2-딥러닝을-이용한-생성-모델&quot;&gt;2-2. 딥러닝을 이용한 생성 모델&lt;/h5&gt;

&lt;p&gt;다음은 딥러닝을 이용한 생성모델입니다. 생성모델이라는 것은 결국 많은 데이터를 필요로 합니다. 그러다 보니 대량의 데이터를 학습하는 딥러닝 분야에서 그 발전이 더 두드러지죠. 딥러닝을 이용한 생성모델 같은 경우는 많은 모델들이 있고 이를 이미 분류해주신 분이 계신데요. 바로 선구자이신 Ian Goodfellow님께서 gan tutorial을 하시면서 설명해주신 ppt자료입니다. (아래사진부터는 https://minsuksung-ai.tistory.com/12 에서 가져온 사진들입니다. )&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;https://danbi-ncsoft.github.io/assets/works/generator/그림7.png&quot; alt=&quot;7fb4eda6aa89713e268cf3d8fafcd283&quot; /&gt;&lt;/p&gt;

&lt;p&gt;Ian Goodfellow께서생성모델을 세분화해서 크게 3가지로 나눠주셨는데요. 결국 위의 통계적 생성모델처럼 확률분포를 추정하는 건 똑같으나, 어떤 방식으로 추정할지에 따라서 구분하셨다고 합니다.&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;Tractable Density : 데이터 X를 보고 확률분포를 ‘직접’ 구하는 방법&lt;/li&gt;
  &lt;li&gt;Approximate Density: 데이터 X를 보고 확률분포를 ‘추정’하는 방법&lt;/li&gt;
  &lt;li&gt;Implicit Density : 데이터 X의 분포를 몰라도 되는 방법&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;하나씩 예시와 함께 설명해볼게요.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Tractable Density : Pixel RNN&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;이전 픽셀이 어떤 것인지에 따라 다음 픽셀이 무엇이 나오는지를 순서대로 확인(학습)하는 모델입니다. Chain Rule을 통해서 직접적으로 학습데이터의 분포를 구하는 것이라고 볼 수 있죠. 아래 그림을 보면 조금더 이해하기 쉬울 겁니다. 이렇게 해서 이미지 전체에 대한 확률분포를 직접적으로 구하는 모델을 만드는 것이죠.이처럼 이전 픽셀의 정보를 가지고 있어야 다음 픽셀의 분포를 알 수 있기에, 직접적으로 확률분포를 구하는 방법이라고 볼 수 있습니다.&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;https://danbi-ncsoft.github.io/assets/works/generator/그림8.png&quot; alt=&quot;7fb4eda6aa89713e268cf3d8fafcd283&quot; /&gt;&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;https://danbi-ncsoft.github.io/assets/works/generator/그림9.png&quot; alt=&quot;7fb4eda6aa89713e268cf3d8fafcd283&quot; /&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Approximate Density : VAE(Variational Auto-Encoder)&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;실제 많이 사용하는 VAE입니다. 원 데이터의 Feature를 압축해서 복원하는 Encoder-Decoder 관게를 이용하죠. 하지만 VAE의 경우는 Auto-Encoder와는 살짝 차이가 있습니다. latent vector에 정규분포 항을 추가하여, latent vector를 조금씩 변형하면서 복원하는게 그 목적이죠. 그렇다면 다양한 종류의 데이터를 생성해낼 수 있겠죠. VAE는 학습시 두가지 에러를 사용합니다. Reconstruction Loss, Regularization Loss. 직관적으로는 생성모델이 만든 새로운 데이터와 원래 데이터와의 관계를 살펴보는 것이 Reconstrucion Loss 입니다. 데이터가 원래 가지는 분포와 동일한 분포를 가지게 학습하기 위해 True분포를 추정한 함수의 분포에 대한 loss term이 Regularization loss입니다. 자세한 설명은 생략하였지만, 이처럼 VAE는 원 데이터의 분포를 추정하고 이를 맞추려고 하면서 학습되는 방법을 사용합니다.&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;https://danbi-ncsoft.github.io/assets/works/generator/그림10.png&quot; alt=&quot;7fb4eda6aa89713e268cf3d8fafcd283&quot; /&gt;&lt;/p&gt;

&lt;p&gt;​                   출처 : https://velog.io/@ohado/%EB%94%A5%EB%9F%AC%EB%8B%9D-%EA%B0%9C%EB%85%90-1.-VAEVariational-Auto-Encoder&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Implicit Density : GAN&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;위에서 Implicit Density의 경우 데이터 X의 분포를 ‘몰라도’ 된다고 했습니다. 이는 GAN의 특이한 점 때문인데요. 바로 판별기, 생성기가 서로 경쟁하면서 학습이 되고, 학습과정을 자세하게 들여다보면 생성기에는 실제 데이터가 들어가지도 않습니다. 오직 판별기가 차이를 구분해주죠. 그렇기에 생성기는 데이터 X를 쳐다본적도 없게 된 겁니다. 이런 특성으로 인해 실제 데이터를 쳐다보지 않는다고 표현한 것입니다.   아래 사진처럼, 원 데이터 분포와의 차이를 줄여나가는 방향으로 판별기와 생성기가 경쟁학습을 해서 원 데이터의 분포를 찾게 되는 것이죠. 물론 LATENT VECTOR Z의 역할이 중요합니다만, 여기서는 다루지 않겠습니다.&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;https://danbi-ncsoft.github.io/assets/works/generator/그림11.png&quot; alt=&quot;7fb4eda6aa89713e268cf3d8fafcd283&quot; /&gt;&lt;/p&gt;

&lt;p&gt;GAN에 대해서는 너무나 많은 자료와 연구가 있기에 더 자세하게 다루지는 않겠습니다. 다만, 저희 I&amp;amp;I실처럼 다량의 시계열 데이터를 다루는 곳에서 GAN을 사용하면 어떨까 하는 고민을 해본적이 있습니다. 보통 GAN은 시계열 데이터보다는 이미지/영상에 많이 쓰이는데요. MIT연구팀이 개발한 TadGAN 알고리즘은 시계열 데이터를 분석하여 이상치탐지를 하는데에 있어 좋은 성능을 나타내는 것으로 알려져 있습니다. 아래 그림을 보시면 TRAIN 데이터를 기반으로 Reconstructed data를 구축하고, 새로운 데이터가 들어오면 기 구축된 Reconstructed data와 새로운 데이터 사이에 이상 구간을 탐지하여 Anomaly error score를 계산하고 threshold에 맞게 이상치 구간을 탐지합니다.  현재 TadGAN은 놀라운 효과를 내고 있으며 많은 곳에서 연구와 적용이 이루어지고 있습니다. 앞으로 게임에서 발생하는 이상치(이상현상들)에 대해서 적용해보는 연구를 할 계획입니다.&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;https://danbi-ncsoft.github.io/assets/works/generator/그림12.png&quot; alt=&quot;7fb4eda6aa89713e268cf3d8fafcd283&quot; /&gt;&lt;/p&gt;

&lt;p&gt;​                                                                                   출처 : https://smilegate.ai/2021/06/01/tadgan/&lt;/p&gt;

&lt;h2 id=&quot;마치며&quot;&gt;마치며&lt;/h2&gt;

&lt;p&gt;지금까지 필자가 정리한 생성 모델의 내용을 소개해드렸습니다. 저만의 기준, 그리고 여러가지 소스에서 복잡하게 짬뽕한 내용이라, 틀린 부분도 있을 수 있습니다. 언제든 피드백을 환영합니다. 또한 생성모델을 어떻게 분류해볼 수 있을지에 대해 정리한 내용이라, 각각의 알고리즘에 대한 설명은 많이 부족합니다. 상세한 부분은 잘 정리된 자료가 많이 있으니 참고하시기 바랍니다.&lt;/p&gt;

&lt;p&gt;현재 이루어지는 연구들 많은 부분들이 생성모델의 관점에서 이루어지고 있습니다. 그만큼 AI분야에서는 중요한 연구주제이고, 이를 I&amp;amp;I실에서 주로 다루는 게임 데이터에 적용하고 그 결과도 포스팅하려 합니다.&lt;/p&gt;
</description>
        <pubDate>Fri, 01 Oct 2021 09:00:00 +0000</pubDate>
        <link>https://danbi-ncsoft.github.io//works/2021/10/01/Generator.html</link>
        <guid isPermaLink="true">https://danbi-ncsoft.github.io//works/2021/10/01/Generator.html</guid>
        
        
        <category>Works</category>
        
      </item>
    
      <item>
        <title>HR Analytics와 구조방정식</title>
        <description>&lt;h2 id=&quot;hr-analytics와-구조방정식&quot;&gt;&lt;strong&gt;HR Analytics와 구조방정식&lt;/strong&gt;&lt;/h2&gt;

&lt;p&gt;​&lt;/p&gt;

&lt;p&gt;​	최근 HR Analytics, People Analytics에 대한 관심이 뜨겁다. 기존 조직과 구성원에 대한 문제들을 데이터 분석을 통해 확인하려는 시도가 주목을 받고 있다. HR Analytics는 다양한 분석 주제들을 가지고 있는데, 그 중에서도 ‘성과’, ‘역량’, ‘리더십’, ‘조직문화’ 등의 주제에 관심이 깊고, 관련된 연구나 분석 사례가 많이 소개되고 있다. 구체적인 가설을 생각해보면, ‘조직장의 리더십에 따라 구성원의 조직 몰입은 어떻게 변할까?’, ‘소속 조직 문화의 인식에 따라 구성원의 이직 의도는 어떻게 달라질까?’와 같은 질문들이 있다.&lt;/p&gt;

&lt;p&gt;​	그런데 이러한 연구에서 사용되는 변수들은 모두 추상적인 개념들이다. 다시 말해서, 직접 관찰되거나 혹은 하나의 지표로 표현하기가 어렵다. 예를 들어, 연봉, 근무 연수, 직급, 성별 등은 명확하게 관찰되는 데 반해, ‘조직 몰입’. ‘직무 만족’, ‘이직 의도’는 그렇지 못하다. 이러한 문제를 해결하기 위해 검사, 설문 문항을 통해 데이터를 측정하고 변수 간의 관계를 확인하게 된다. 아래 문항은 국내 한 연구에서 구성원의 조직 몰입도를 측정하기 위해 활용한 문항이다.&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/assets/study/hr_analytics_sem/sem_01.jpg&quot; alt=&quot;sem_01&quot; /&gt;&lt;/p&gt;

&lt;p&gt;출처. 안정원, 이순묵(2015), 조직몰입 3요소 모형의 내적 구조 검토 : 탐색적 구조방정식 모형(ESEM)의 적용, 한국심리학회지: 산업 및 조직: Vol. 28(4), 795-827.&lt;/p&gt;

&lt;p&gt;​	문항의 구성을 살펴보면, 조직몰입을 정서적 몰입, 지속적 몰입, 규범적 몰입이라는 하위 요인으로 나누고, 해당 요인을 측정하기 위한 문항을 각각 5개씩 사용하여 구성원의 조직 몰입을 측정하고자 하였다.&lt;/p&gt;

&lt;p&gt;​	그런데 이런 식의 문항과 데이터는 HR Analytics라는 거창한 이름을 붙이지 않더라도 HR 현업에서 흔히 활용되고 있다. 구성원의 역량 평가 문항, 리더십 진단 문항, 조직문화 진단, 지원자의 역량 면접 평가표, 교육 만족도 설문 등이 보통 이러한 문항 형태를 활용하고 있다. 확인하려는 개념이 추상적이기 때문에 복수의 문항을 활용하는 것이다.&lt;/p&gt;

&lt;p&gt;​	이렇게 추상적인 개념들을 측정하고, 그 개념 사이의 관계를 확인하려는 상황에서 적절하게 활용할 수 있는 분석 방법론을 이번 블로그에서 소개하고자 한다. 바로 심리학, 교육학과 같은 행동과학 연구에서 주로 활용되고 있는 구조방정식 모형(SEM : structural equation model)이다. 앞서 제안한 상황을 살펴보자. 두 가지의 분석 이슈가 있다. 먼저 추상적인 개념을 여러 문항을 통해 측정한다는 점과 이러한 개념 간의 관계를 확인하고자 한다는 점이다. 구조방정식은 이러한 상황을 측정모형과 구조모형을 결합하여 분석하는 방법을 취한다. 먼저 복수의 문항에서 우리가 관심을 가지고 있는 개념이 잘 측정되었는지를 측정모형을 통해 확인하고, 문항 간의 공통 분산을 활용하여 측정오차를 제거한다. 다음으로 측정오차가 제거된 개념들 사이의 회귀관계를 구조모형을 통해 밝히게 된다.&lt;/p&gt;

&lt;p&gt;​	모형에 대한 보다 상세한 설명은 뒤에서 설명하기로 하고, 앞서 제안한 상황에서 구조방정식이 가지는 장점을 가상의 분석 주제를 가지고 설명해보고자 한다. 결론부터 밝히자면, 구조방정식은 ‘잠재변수’를 분석에서 활용할 수 있다. 예를 들어 재택근무의 효과를 분석하면서 상사와의 유대감이 재택근무 만족도에 긍정적인 영향을 미칠 것이라는 가설을 세웠다고 가정해보자. 유대감과 만족도는 추상적인 개념이기 때문에 각각 3개의 문항으로 나누어 측정했다. 상사와의 유대감은 ‘1) 나의 상사는 나와 자유롭게 서로의 아이디어와 감정을 주고받을 수 있는 관계이다.’, ‘2) 나는 직장에서의 애로사항에 대하여 상사에게 허심탄회하게 말할 수 있으며, 상사도 나의 말을 경청할 것이다.’, ‘3) 나의 문제를 나의 상사와 의논한다면, 나의 상사는 내 문제를 진심으로 대하고 건설적이고 사려 깊은 조언을 해줄 것이다.’ 라는 세 가지 문항을 활용했다고 가정하자. (물론 문항은 측정하고자 하는 변수에 대한 이론을 검토하여 설계하여야 한다.) 비슷한 맥락에서 재택근무 만족도를 묻는 3가지 문항이 있다고 가정해보겠다.&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/assets/study/hr_analytics_sem/sem_02.png&quot; alt=&quot;sem_02&quot; /&gt;&lt;/p&gt;

&lt;p&gt;​	일반적으로 HR 현업에서 복수의 문항을 활용한 경우, 해당 응답의 평균을 내서 분석에 활용하곤 한다. 그런데 이런 방식에서는 문제가 있다. 아래 그림을 보자. 상사와의 유대감을 묻는 문항에 대한 답변은 진짜 ‘상사와의 유대감’을 의미하는 실제 값(True)과 우연히 발생했거나 그 문항의 특성에 따라 발생한 측정오차 값(error)으로 이루어져 있다. 검사나 설문 문항을 통해 측정한 경우 필연적으로 발생하는 상황이다. 그런데 해당 문항들의 평균 응답값을 분석에 활용한 경우, 아래 오른쪽 그림처럼 빨간색의 측정오차가 그대로 남아있어 분석 결과가 오염되거나 왜곡될 수 있다.&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/assets/study/hr_analytics_sem/sem_03.png&quot; alt=&quot;sem_03&quot; /&gt;&lt;/p&gt;

&lt;p&gt;​	구조방정식은 같은 개념을 측정하는 문항들에 대한 공통 분산 값을 이용해서 잠재변수(latent variable)를 도출한다. 아래 왼쪽 그림을 보면, 각 문항에 있는 실제 값(True)에 해당하는 부분을 잠재변수로 활용하는 것이다. 이러한 관계를 도식화해서 표현한 그림이 아래 오른쪽 그림이고, 구조방정식 모형에서는 이를 ‘경로도’라고 부른다. 자세히 살펴보면, 각 문항에서 잠재변수를 도출하고, 아래 상사의 유대감으로 설명되지 않는 부분을 오차(e)로 남겨놓는다. 그리고 도출된 잠재변수 간의 영향관계를 화살표로 표현했다.&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/assets/study/hr_analytics_sem/sem_04.png&quot; alt=&quot;sem_04&quot; /&gt;&lt;/p&gt;

&lt;p&gt;​	이처럼 구조방정식은 측정오차를 통제할 수 있다는 장점이 있다. 이외에도 구조방정식은 매개변수 활용에 용이하다는 점과 적합도 지수를 활용한 모형 전체의 통계적 평가가 가능하다는 점에서 장점을 가지고 있다(홍세희, 2003). 매개변수의 특성 상 독립변수와 종속변수의 역할을 동시에 수행해야 하는데 구조방정식 모형은 이러한 변수 간의 매개효과를 분석하고 평가하는 데 용이하다. 또한 적합도 지수라는 통계지표를 통해 연구자의 연구모형과 실제 자료가 얼마나 부합하는지 평가하고 필요에 따라 추가 수정할 수 있다는 장점이 있다.&lt;/p&gt;

&lt;p&gt;​	이 글에서는 구조방정식의 기본개념과 HR Analytics 분석에서 활용한 사례를 소개하고자 한다. HR Analytics 특성 상 분석에 활용한 구체적인 문항과 분석 결과를 외부에 공유하기 어려워 관련해서 발간된 국내 연구 사례를 중심으로 소개하고자 한다. 보다 자세한 이론과 개념이 궁금하다면 ‘구조방정식 모형의 기본과 확장(학지사)’ 교재를 참고하길 바란다.&lt;/p&gt;

&lt;h2 id=&quot;구조방정식의-기본-개념&quot;&gt;&lt;strong&gt;구조방정식의 기본 개념&lt;/strong&gt;&lt;/h2&gt;

&lt;h3 id=&quot;구조방정식-경로도&quot;&gt;구조방정식 경로도&lt;/h3&gt;

&lt;p&gt;​	경로도는 연구 모형에서 활용하는 변수들 간의 인과 구조를 시각화하여 표현한 그림으로, 구조방정식 모형을 직관적으로 이해하는 데 도움이 된다. 경로도의 모습은 다음과 같다.&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/assets/study/hr_analytics_sem/sem_05.png&quot; alt=&quot;sem_05&quot; /&gt;&lt;/p&gt;

&lt;p&gt;​	경로도를 이해하기 위해서는 구조방정식 모형에서 사용하는 주요 용어를 살펴봐야 한다. 먼저 관찰변수와 잠재변수이다. 관찰변수는 직접 측정이나 관찰이 가능한 변수로 성적, 소득, 길이, 온도, 기압, 속도처럼 명확하게 측정이 가능한 변수다. 반면 잠재변수는 무력감, 성취, 언어능력 등과 같이 추상성을 가지고 있어 직접 측정이나 관찰이 불가능한 변수를 말한다. 이러한 추상적인 변인은 복수의 문항으로 구성된 검사나 설문을 통해 측정 되는데, 이때 각 문항은 해당 문항에 대한 답변이기 때문에 관찰변수가 되고, 여러 관찰변수 저변에 잠재되어 있는 수준을 추출한 값이 잠재변수가 된다. 경로도에서는 관찰변수는 직사각형, 잠재변수는 타원으로 표시한다.&lt;/p&gt;

&lt;p&gt;​	다음은 외생변수와 내생변수이다. 경로도를 보면 변수들 간의 관계를 화살표로 표현하고 있는데, 직선 일방 화살표는 변수 간의 인과관계를 나타내고 곡선 양방 화살표는 변수 간의 상관관계를 의미한다. 직선 일방 화살표를 기준으로 화살표를 보내는 변수를 외생변수, 화살표를 맞는 변수를 내생변수라고 한다. 외생변수는 다른 변수의 변화에 원인이나 동기의 역할을 하는 변수로, 모형에서 어떤 변수에 의해서도 설명되지 않는 변수다. 반면, 내생변수는 외생변수나 다른 내생변수에 의해 영향을 받는 변수이다.&lt;/p&gt;

&lt;p&gt;​	다음은 오차이다. 오차도 직접 관찰되지 않고 변하는 값이므로 오차 변수(잠재변수)로 표현할 수 있는데, 구조방정식 모형에서 오차는 측정오차와 설명오차 두 가지로 구분할 수 있다. 측정오차는 관찰변수들이 잠재변수를 측정하는 과정에서 발생한 오차이다. 조금 더 풀어서 설명하자면, 각각의 관찰변수들에서 잠재변수를 설명하고 난 나머지 값을 나타낸다. 설명오차는 변수들 간의 구조적인 회귀관계에서 발생한 오차로, 내생변수의 분산 중 외생변수 혹은 외생변수들로 설명하고 난 나머지를 뜻한다. 전통적인 통계에서 오차변수는 서로 독립적인 것으로 가정하지만, 연구 모형에 따라 오차 간의 상관을 가정하여 분석하는 것도 가능하다.&lt;/p&gt;

&lt;h3 id=&quot;측정모형과-구조모형&quot;&gt;측정모형과 구조모형&lt;/h3&gt;

&lt;p&gt;​	구조방정식은 측정모형과 구조모형이 결합된 형태로 구성되어 있다. 측정모형은 관찰변수에 의해서 잠재변수가 어떻게 측정 되는지 원리를 보여주는 모형이다.&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/assets/study/hr_analytics_sem/sem_06.png&quot; alt=&quot;sem_06&quot; /&gt;&lt;/p&gt;

&lt;p&gt;​	측정모형은 잠재변수 사이의 인과적인 가설이 설정되지 않은 모형이다. 잠재변수 간의 양방 곡선 화살표를 사용한 것은 두 변수 간의 상관이 있음을 가정한 모형이라는 의미이며, 따라서 분석되지 않는 관계(unanalyzed association)이다. 오차는 서로 독립적인 것으로 가정하지만, 오차 간의 상관을 가정할 수도 있다. 도식화된 표현을 수식으로 바꾸면 아래와 같다. 이 때 계수는 잠재변수가 관찰변수를 설명하는 정도를 나타내며, 요인 부하(factor loading)라고 한다. 절편은 해당 관찰변수의 평균이며, 오차는 측정오차이다.&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/assets/study/hr_analytics_sem/sem_07.png&quot; alt=&quot;sem_07&quot; /&gt;&lt;/p&gt;

&lt;p&gt;​	구조모형은 잠재변수 사이의 회귀관계를 연구하는 모형을 말한다. 구조방정식은 측정모형 분석한 후, 다음 단계로 경로모형을 분석하게 된다. 아래 그림은 전체 경로도에서 측정모형을 제외한 것으로 잠재변수 간의 영향 관계를 보여준다. 물론 경로모형에서 관찰변수를 분석에 활용할 수도 있다.&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/assets/study/hr_analytics_sem/sem_08.jpg&quot; alt=&quot;sem_08&quot; /&gt;&lt;/p&gt;

&lt;p&gt;​	측정된 잠재변수 사이의 영향 관계를 수식으로 표현하면 아래와 같다.&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/assets/study/hr_analytics_sem/sem_09.png&quot; alt=&quot;sem_09&quot; /&gt;&lt;/p&gt;

&lt;h2 id=&quot;구조방정식-적용-사례&quot;&gt;&lt;strong&gt;구조방정식 적용 사례&lt;/strong&gt;&lt;/h2&gt;

&lt;p&gt;​	구조방정식의 기본 개념과 용어를 앞서 살펴보았다. 구조방정식 방법론이 분석에 어떻게 활용되는지 실제 사례를 하나 소개하겠다. 참고한 논문은 조직문화가 일과 삶의 균형(Work-Life Balance; 워라밸) 을 매개로 직장인의 안녕감과 우울감에 미치는 영향을 확인한 논문이다. 아래는 잠재변수들 간의 경로모형으로 전체 연구 모형을 나타낸다. 연구에서는 워라밸 조직 문화, 워라밸. 안녕감, 우울감에 대한 복수의 문항을 바탕으로 자료를 수집했다.&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/assets/study/hr_analytics_sem/sem_10.jpg&quot; alt=&quot;sem_10&quot; /&gt;&lt;/p&gt;

&lt;p&gt;​	논문에서 제시한 연구 결과를 바탕으로 구조방정식 모형을 활용한 분석이 어떤 절차를 거쳐서 어떤 결과를 보고하는지를 살펴보고자 한다. 첫 번째 단계에서는 활용한 문항과 데이터가 연구자가 관심을 가지고 있는 개념을 잘 측정하고 있는지 확인한다. 이를 위해 앞서 설명한 측정모형으로 ‘확인적 요인분석’을 실시한다. 아래 표는 확인적 요인분석 결과이다. 표준화 계수로 보고하고 있는 값은 앞서 설명한 ‘요인부하’에 해당한다. 해당 연구에서는 표준화된 요인부하가 0.5 이상이며, 통계적으로 유의하며, 각 변수들이 측정하고자 하는 개념을 잘 설명하고 있다고 추정하였다. (보다 엄격한 기준을 적용하는 연구에서는 요인부하를 0.7 이상으로 보고, 기준을 충족하지 못한 문항을 제외하기도 한다.)&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/assets/study/hr_analytics_sem/sem_11.jpg&quot; alt=&quot;sem_11&quot; /&gt;&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/assets/study/hr_analytics_sem/sem_12.jpg&quot; alt=&quot;sem_12&quot; /&gt;&lt;/p&gt;

&lt;p&gt;​	다음 단계에서는 앞서 요인분석을 통해 도출한 잠재변수들 간의 관계를 파악하기 위해 경로분석을 실시한다. 아래 분석에 의하면, 모든 경로가 유의하였고, 워라벨 조직문화가 우울에 미치는 직접효과와 워라벨이 우울에 미치는 영향은 부적인(negative) 영향임을 확인할 수 있다.&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/assets/study/hr_analytics_sem/sem_13.jpg&quot; alt=&quot;sem_13&quot; /&gt;&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/assets/study/hr_analytics_sem/sem_14.jpg&quot; alt=&quot;sem_14&quot; /&gt;&lt;/p&gt;

&lt;p&gt;​	해당 모형은 매개모형으로 워라밸을 매개로 워라밸 지원 조직문화가 구성원의 안녕감과 우울감에 미치는 영향을 확인하고자 하는 연구다. 때문에 간접효과에 대해 검증해야 하는데 확률 분포를 정의할 수 없어서 부트스트래핑을 통해 유의성을 검증한다.&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/assets/study/hr_analytics_sem/sem_15.jpg&quot; alt=&quot;sem_15&quot; /&gt;&lt;/p&gt;

&lt;p&gt;​	서론에서 밝힌대로 구조방정식은 모형 전체의 통계적 평가가 가능하다. 바로 적합도 지수를 통해 확인 가능하다. 본 연구에서는 적합도 지수 TLI = .910, CFI = .926이 좋은 적합도 기준인 0.9를 넘었고, RMSEA = 0.080으로 좋은 적합도 기준을 충족했음을 보고했다. 적합도 지수는 모형의 적절성을 통계적 지표로 나타내는데 일반적으로 아래 4개 지수를 사용한다. 자세한 내용은 논문(구조 방정식 모형의 적합도 지수 선정기준과 그 근거)을 통해 확인 가능하다.&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;
    &lt;p&gt;TLI(Tucker-Lewis’ index) : .90 이상일 경우, 좋은 적합도&lt;/p&gt;
  &lt;/li&gt;
  &lt;li&gt;
    &lt;p&gt;CFI(comparative fit index) : TLI가 0과 1 사이의 값을 갖도록 조정, .90 이상일 경우 좋은 적합도&lt;/p&gt;
  &lt;/li&gt;
  &lt;li&gt;
    &lt;p&gt;RMSEA(root mean square error of approximation)&lt;/p&gt;

    &lt;ul&gt;
      &lt;li&gt;0.05 이하일 때 매우 좋은 적합도(Close fit)&lt;/li&gt;
    &lt;/ul&gt;
  &lt;/li&gt;
  &lt;li&gt;
    &lt;p&gt;SRMR(standardized root meansquare residual)&lt;/p&gt;

    &lt;ul&gt;
      &lt;li&gt;0과 1 사이의 값, 0.08 이하가 좋은 적합도&lt;/li&gt;
    &lt;/ul&gt;
  &lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;​	해당 연구는 산업조직심리학 학회지에 게재된 논문이기 때문에 안녕감, 우울감과 같은 내생변수를 사용하였지만, 실무에서 HR Analytics 를 수행할때는 조직몰입, 직무만족, 이직의도, 성과 등의 변수를 활용하여 워라벨을 지원하는 조직문화가 조직과 구성원에 미치는 효과를 밝힐 수 있을 것이다. 이처럼 구조방정식 모형을 활용하면, 측정하고자 하는 추상적인 개념과 그 개념 사이의 복잡한 관계를 밝힐 수 있다는 점에서 HR Analytics 분석 주제들을 분석하는 데 있어 활용 가능성이 높다.&lt;/p&gt;

&lt;p&gt;​	지금까지 구조방정식 모형의 기본 개념과 보고된 연구 결과를 통해 어떤 결과를 도출해낼 수 있는지를 확인해보았다. 구조방정식은 기존의 HR 현업에서 수집하고 있었던 데이터를 분석하는 데 용이하며 추상적인 개념 간의 복잡한 관계를 확인할 수 있다는 장점이 있다. 다음 블로그에서는 구조방정식 모형을 응용한 분석 모형 중 HR Analytics 분석의 특수성에 부합하는 모형들을 소개하고자 한다.&lt;/p&gt;
</description>
        <pubDate>Fri, 17 Sep 2021 14:00:00 +0000</pubDate>
        <link>https://danbi-ncsoft.github.io//study/2021/09/17/study-hranalytics-sem.html</link>
        <guid isPermaLink="true">https://danbi-ncsoft.github.io//study/2021/09/17/study-hranalytics-sem.html</guid>
        
        
        <category>Study</category>
        
      </item>
    
      <item>
        <title>자연어처리와 HR analytics</title>
        <description>&lt;h2 id=&quot;자연어처리와-hr-analytics&quot;&gt;&lt;strong&gt;자연어처리와 HR analytics&lt;/strong&gt;&lt;/h2&gt;

&lt;p&gt;자연어처리가 각광을 받는 이유에는 여러가지가 있겠지만, 해당 분야가 “언어” 를 다루는 분야라는 사실이 적지 않은 비중을 차지하리라 생각된다. 언어는 인간 사이 발생하는 소통 중 가장 많은 정보를 짧은 시간 내에 전달할 수 있는 독보적인 수단이며, 인류는 오랜 시간 언어를 활용해 다양한 사회적 활동을 수행해 왔다. 다시 말해 자연어처리는 인간이 축적해온 다양한 문화적, 사회적, 기술적 데이터를 효과적으로 활용할 수 있는 수단들 중 하나인 셈이다. 자연어처리는 기본적으로 언어와 관련된 데이터가 존재하는 모든 분야에 적용이 가능한데, 필자가 소속된 조직에서는 그 중에서도 HR analytics와 접목될 수 있는 자연어처리에 대한 영역에 관심을 갖고 있다. 이번 글에서는 이러한 관심을 바탕으로 찾아본 자연어처리 분야에서 관심을 갖고 해결하고자 연구되고 있는 세부 도전과제들과, 해당 과제가 HR analytics에 어떤 영향을 줄 수 있을지에 대해 이야기해 보고자 한다.&lt;/p&gt;

&lt;p&gt;&lt;br /&gt;&lt;/p&gt;

&lt;hr /&gt;

&lt;p&gt;&lt;br /&gt;&lt;/p&gt;

&lt;h2 id=&quot;자연어처리-과제와-모델-학습-예시&quot;&gt;&lt;strong&gt;자연어처리 과제와 모델, 학습 예시&lt;/strong&gt;&lt;/h2&gt;

&lt;p&gt;자연어처리 모델의 구성은 1) 어떤 데이터로 2) 어떤 과제를 3) 어떻게 학습하여 해결할지를 결정하는 과정에서 구체화 되어간다. 이에 대한 내용은 2018년 공개된 BERT에 잘 요약되어 있다. 사실 BERT에서 요약한 것은 그들의 사전학습 모델을 전이학습에 적용하여 어떤 문제들을 해결할 수 있는가에 대한 설명이었지만, 현존하는 NLP 과제의 많은 부분을 포함하고 있어 각 과제에 대한 파악과 적용 사례를 살펴보기에 적절한 지표로 생각된다. 하지만 해당 논문에서는 데이터셋과 딥러닝 모델 구성 방식 관점에서 설명을 전개하였는데, 이는 공개되어있는 데이터셋의 구조와 목적에 맞는 딥러닝 모델을 4종류로 구성한 후 여러 데이터로 해당 모델의 성능을 검증하기 위함이였다. 우리 글에서는, 본 글의 초점에 맞게 딥러닝 모델의 각 구조와 상응하는 데이터셋으로 어떤 문제를 해결할 수 있는지에 대한 관점으로 다시 한 번 정리하였다.&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/assets/works/nlp_and_hr_analytics/bert_tasks.png&quot; alt=&quot;nlp_and_hr_analytics_image1&quot; /&gt;&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;(a) Sentence Pair Classification Tasks
    &lt;ul&gt;
      &lt;li&gt;MNLI (Multi-Genre Natural Language Inference) : 자연어추론&lt;/li&gt;
      &lt;li&gt;QQP (Quora Question Pairs) : 두 질문이 같은 질문인지 아닌지 구분하는 문제&lt;/li&gt;
      &lt;li&gt;QNLI (Question-answering Natural Language Inference) : 질의응답 (Q, A) 구조로 되어있으며, A가 질문 Q에 대한 대답으로 적합한지를 분류&lt;/li&gt;
      &lt;li&gt;STS-B (Semantic Textual Similarity) : 의미론적 문장 유사도 판단 문제&lt;/li&gt;
      &lt;li&gt;MRPC (Microsoft Research Paraphrase Corpus) : 문장 유사도 문제&lt;/li&gt;
      &lt;li&gt;RTE (Recognizing Textual Entailment) 텍스트 함의 인식&lt;/li&gt;
      &lt;li&gt;SWAG (Situations With Adversarial Generations) : 기초 상식 추론&lt;/li&gt;
    &lt;/ul&gt;
  &lt;/li&gt;
  &lt;li&gt;(b) Single Sentence Classification Tasks
    &lt;ul&gt;
      &lt;li&gt;SST-2 (Stanford Sentiment Treebank) : 감정분석 데이터셋&lt;/li&gt;
      &lt;li&gt;CoLA (The Corpus of Linguistic Acceptability) : 언어적 용인 가능성 판별 문제&lt;/li&gt;
    &lt;/ul&gt;
  &lt;/li&gt;
  &lt;li&gt;(c) Question Answering Tasks
    &lt;ul&gt;
      &lt;li&gt;SQuAD v1.1 : Stanford Question Answering Dataset : 질의응답 문제&lt;/li&gt;
    &lt;/ul&gt;
  &lt;/li&gt;
  &lt;li&gt;(d) Single Sentence Tagging Tasks
    &lt;ul&gt;
      &lt;li&gt;CoNLL-2003 NER (Named Entity Recognition) : 자연어에서 이름을 찾아내는 문제&lt;/li&gt;
    &lt;/ul&gt;
  &lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;br /&gt;
&lt;br /&gt;&lt;/p&gt;

&lt;h4 id=&quot;a-문장-페어pair-분류-문제-sentence-pair-classification-tasks&quot;&gt;(a) 문장 페어(pair) 분류 문제 (Sentence Pair Classification Tasks)&lt;/h4&gt;

&lt;p&gt;BERT의 따르면 그들은 BERT로 해결할 수 있는 4 종류의 학습 방법에 대해 설명하고 있다. 여기에서 각 모델에 대한 구분은 데이터로의 input과 output의 구성에 따른 차이이다. 먼저 (a) Sentence Pair Classification Tasks는 두 개의 sentences를 [SEP] 토큰으로 구분해 pair의 형태로 입력하여 출력으로 class label을 받는 구조이다. 이 구조는 서로 다른 두 개의 sentences로 부터 (True or False) 등과 같이 단순한 정보를 추출하는 과제에 적합하다. 해당 구조로 학습하기에 적합한 데이터셋으로는 MNLI, QQP, QNLI, STS-B, MRPC, RTE, SWAG이 언급되어 있는데, 이를 과제 종류에 따라 분류하면 아래와 같이 두 종류로 구분할 수 있다.&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;자연어 추론 : MNLI, QNLI, RTE, SWAG&lt;/li&gt;
  &lt;li&gt;문장 유사도 확인 : QQP, STS-B, MRPC&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;언급한 자연어추론을 위한 데이터셋 (MNLI, QNLI, RTE, SWAG 등) 은 모두 비슷한 포멧을 갖고 있는데, 2종의 sentences로 구성된 컬럼과, 1종의 class label 컬럼을 갖고 있다. 여러 데이터셋 중 QNLI의 예를 확인해보자. 아래 예시에서 Question은 질문, Answer은 답변이며 Class label은 Answer이 Question에 대한 답변이 될 수 있는지를 나타낸다.&lt;/p&gt;

&lt;table&gt;
  &lt;thead&gt;
    &lt;tr&gt;
      &lt;th style=&quot;text-align: center&quot;&gt;구분&lt;/th&gt;
      &lt;th style=&quot;text-align: center&quot;&gt;설명&lt;/th&gt;
    &lt;/tr&gt;
  &lt;/thead&gt;
  &lt;tbody&gt;
    &lt;tr&gt;
      &lt;td style=&quot;text-align: center&quot;&gt;Question&lt;/td&gt;
      &lt;td style=&quot;text-align: center&quot;&gt;When did the third Digimon series begin? (세 번째 디지몬 시리즈는 언제 시작되었습니까?)&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td style=&quot;text-align: center&quot;&gt;Answer&lt;/td&gt;
      &lt;td style=&quot;text-align: center&quot;&gt;Unlike the two seasons before it and most of the seasons that followed, Digimon Tamers takes a darker and more realistic approach to its story featuring Digimon who do not reincarnate after their deaths and more complex character development in the original Japanese.(디지몬 테이머즈(*세 번째 디지몬 시리즈)는 죽은 뒤 되살아나지 않는 디지몬과 좀 더 복잡한 캐릭터 설정으로 인해 이전 두 시즌과 그 이후의 대부분의 시즌과 달리 어둡고 더 현실적인 스토리를 보여줍니다.)&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td style=&quot;text-align: center&quot;&gt;Class label&lt;/td&gt;
      &lt;td style=&quot;text-align: center&quot;&gt;entailment / &lt;strong&gt;not_entailment&lt;/strong&gt;&lt;/td&gt;
    &lt;/tr&gt;
  &lt;/tbody&gt;
&lt;/table&gt;

&lt;p&gt;또한 (a) 구조는 문장에 대한 유사도 여부를 판별하는 모델로도 학습이 가능한데, 이 경우 입출력 데이터의 구조는 아래와 같다. 해당 데이터는 QQP의 예시이며, 결과적으로 딥러닝 모델은 입력 문장 1과 입력 문장 2가 같은 의미의 문장인지 아닌지에 대한 class label을 기반으로 weight propagation을 수행하여 학습하게 된다.&lt;/p&gt;

&lt;table&gt;
  &lt;thead&gt;
    &lt;tr&gt;
      &lt;th style=&quot;text-align: center&quot;&gt;구분&lt;/th&gt;
      &lt;th style=&quot;text-align: center&quot;&gt;설명&lt;/th&gt;
    &lt;/tr&gt;
  &lt;/thead&gt;
  &lt;tbody&gt;
    &lt;tr&gt;
      &lt;td style=&quot;text-align: center&quot;&gt;Sentence 1&lt;/td&gt;
      &lt;td style=&quot;text-align: center&quot;&gt;Why are so many Quora users posting questions that are readily answered on Google?(왜 많은 Quora 유저들은 Google을 통해 쉽게 답을 찾을 수 있는 질문을 올릴까요?)&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td style=&quot;text-align: center&quot;&gt;Sentence 2&lt;/td&gt;
      &lt;td style=&quot;text-align: center&quot;&gt;Why do people ask Quora questions which can be answered easily by Google?(왜 사람들은 Google에서 쉽게 답변받을 수 있는 질문을 Quora에서 할까요?)&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td style=&quot;text-align: center&quot;&gt;Class label&lt;/td&gt;
      &lt;td style=&quot;text-align: center&quot;&gt;&lt;strong&gt;similar&lt;/strong&gt; / different&lt;/td&gt;
    &lt;/tr&gt;
  &lt;/tbody&gt;
&lt;/table&gt;

&lt;p&gt;&lt;br /&gt;
&lt;br /&gt;&lt;/p&gt;
&lt;h4 id=&quot;b-단일-문장-분류-문제-single-sentence-classification-tasks&quot;&gt;(b) 단일 문장 분류 문제 (Single Sentence Classification Tasks)&lt;/h4&gt;

&lt;p&gt;두 번째 설명할 내용은 (b) Single Sentence Classification Tasks를 해결하기 위한 구조이다. 본 구조는 사실 가장 기본적인 Text classification에 사용되는 구조이며, 단일 sentence 입력을 받아 class label을 출력하는 구조로 되어있다. 다시 말해 단일 문장에 대한 단순한 판단을 필요로 하는 경우에 적합하다. 해당 구조로 학습하기에 적합한 데이터셋으로는 SST-2와 CoLA가 존재하며, 이를 과제 종류에 따라 분류하면 아래와 같다.&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;
    &lt;p&gt;감정 분석 : SST-2&lt;/p&gt;
  &lt;/li&gt;
  &lt;li&gt;
    &lt;p&gt;언어적 용인 가능성 판별 : CoLA&lt;/p&gt;
  &lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;(b) 구조로 학습할 수 있는 데이터는 단일 문장과 단일 class label를 입력 받아 학습하는 구조이다. 예시는 SST-2 데이터셋이며, 입력 문장과 해당 문장이 긍정문인지 부정문인지를 나타내는 class label로 구성이 되어있다.&lt;/p&gt;

&lt;table&gt;
  &lt;thead&gt;
    &lt;tr&gt;
      &lt;th style=&quot;text-align: center&quot;&gt;구분&lt;/th&gt;
      &lt;th style=&quot;text-align: center&quot;&gt;설명&lt;/th&gt;
    &lt;/tr&gt;
  &lt;/thead&gt;
  &lt;tbody&gt;
    &lt;tr&gt;
      &lt;td style=&quot;text-align: center&quot;&gt;Sentence&lt;/td&gt;
      &lt;td style=&quot;text-align: center&quot;&gt;that loves its characters and communicates something rather beautiful about human nature(그의 캐릭터들을 사랑하고 인간 본성에 대한 아름다음을 전달한다)&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td style=&quot;text-align: center&quot;&gt;Class label&lt;/td&gt;
      &lt;td style=&quot;text-align: center&quot;&gt;&lt;strong&gt;positive&lt;/strong&gt; / negative&lt;/td&gt;
    &lt;/tr&gt;
  &lt;/tbody&gt;
&lt;/table&gt;

&lt;p&gt;언어적 용인 가능성을 판별해주는 딥러닝 모델 역시 (b) 구조로 학습하게 되며, 입력 문장과 해당 문장이 언어적으로 용인이 가능한지 여부를 판별해주는 class label로 구성이 되어있다. 해당 문장은 CoLA 데이터셋의 예시이다.&lt;/p&gt;

&lt;table&gt;
  &lt;thead&gt;
    &lt;tr&gt;
      &lt;th style=&quot;text-align: center&quot;&gt;구분&lt;/th&gt;
      &lt;th style=&quot;text-align: center&quot;&gt;설명&lt;/th&gt;
    &lt;/tr&gt;
  &lt;/thead&gt;
  &lt;tbody&gt;
    &lt;tr&gt;
      &lt;td style=&quot;text-align: center&quot;&gt;Sentence&lt;/td&gt;
      &lt;td style=&quot;text-align: center&quot;&gt;In which way is Sandy very anxious to see if the students will be able to solve the homework problem?&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td style=&quot;text-align: center&quot;&gt;Class label&lt;/td&gt;
      &lt;td style=&quot;text-align: center&quot;&gt;acceptable / &lt;strong&gt;unacceptable&lt;/strong&gt;&lt;/td&gt;
    &lt;/tr&gt;
  &lt;/tbody&gt;
&lt;/table&gt;

&lt;p&gt;&lt;br /&gt;
&lt;br /&gt;&lt;/p&gt;

&lt;h4 id=&quot;c-질의응답-문제-question-answering-tasks&quot;&gt;(c) 질의응답 문제 (Question Answering Tasks)&lt;/h4&gt;

&lt;p&gt;다음으로 소개할 (c) Question Answering Tasks의 경우에는 단어 자체에서 과제의 유형을 추론할 수 있듯, 질의응답에 관한 데이터에 사용되는 구조이다. 관련 데이터셋으로는 SQuAD가 존재하며, (a)와 같이 두 개의 sentences를 입력해 단일 sentence를 출력 받는 구조로 되어있다. 사실 (a) 에서도 질의응답 관련 데이터셋 (QNLI) 이 등장하는데, 모델의 출력이 class label이냐, sentence이냐에 따라 차이가 있다. (a) 구조에서는 출력 포멧이 class label로, 입력된 질문에 대한 응답으로써 각 pair가 적절한지를 확인하는 과제였다면, (c) 구조로 해결할 수 있는 과제는 질문에 대한 응답을 문장 형태로 출력하여 질문에 대한 보다 직접적인 응답을 제공한다. SQuAD의 경우 Context와 Question을 입력으로 하여 Answer을 출력하는 방식으로 학습을 진행하며, 여기에서 Context는 Question에 대한 Answer를 추론하기에 배경 정보가 담겨있는 문단에 해당한다.&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;질의응답 : SQuAD v1.1&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;(c)는 아래와 같은 데이터셋을 학습하여 동작한다. 해당 데이터는 SQuAD v1.1에 해당하는 데이터로 Context와 Question을 입력으로 받고, Answer을 label로 하여 학습을 수행한다. 예시에서 나타나는 바와 같이, Question은 Context에 포함된 내용에 대한 질문으로 구성되어 있으며, Answer은 해당 질문에 대한 정답으로 구성된다.&lt;/p&gt;

&lt;table&gt;
  &lt;thead&gt;
    &lt;tr&gt;
      &lt;th style=&quot;text-align: center&quot;&gt;구분&lt;/th&gt;
      &lt;th style=&quot;text-align: center&quot;&gt;설명&lt;/th&gt;
    &lt;/tr&gt;
  &lt;/thead&gt;
  &lt;tbody&gt;
    &lt;tr&gt;
      &lt;td style=&quot;text-align: center&quot;&gt;Context&lt;/td&gt;
      &lt;td style=&quot;text-align: center&quot;&gt;Architecturally, the school has a Catholic character. Atop the Main Building’s gold dome is a golden statue of the Virgin Mary. Immediately in front of the Main Building and facing it, is a copper statue of Christ with arms upraised with the legend “Venite Ad Me Omnes”. Next to the Main Building is the Basilica of the Sacred Heart. Immediately behind the basilica is the Grotto, a Marian place of prayer and reflection. It is a replica of the grotto at Lourdes, France where the Virgin Mary reputedly appeared to Saint Bernadette Soubirous in 1858. At the end of the main drive (and in a direct line that connects through 3 statues and the Gold Dome), is a simple, modern stone statue of Mary.&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td style=&quot;text-align: center&quot;&gt;Question 1&lt;/td&gt;
      &lt;td style=&quot;text-align: center&quot;&gt;Where is the headquarters of the Congregation of the Holy Cross?(the Congregation of the Holy Cross의 본부는 어디에 있습니까?)&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td style=&quot;text-align: center&quot;&gt;Answer 1&lt;/td&gt;
      &lt;td style=&quot;text-align: center&quot;&gt;{“text”: “Rome”, “answer_start”: 119}&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td style=&quot;text-align: center&quot;&gt;Qeustion 2&lt;/td&gt;
      &lt;td style=&quot;text-align: center&quot;&gt;What is the oldest structure at Notre Dame?(노트르담에서 가장 오래된 건축물은 무엇입니까?)&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td style=&quot;text-align: center&quot;&gt;Answer 2&lt;/td&gt;
      &lt;td style=&quot;text-align: center&quot;&gt;{“text”: “Old College”, “answer_start”: 234}&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td style=&quot;text-align: center&quot;&gt;…&lt;/td&gt;
      &lt;td style=&quot;text-align: center&quot;&gt;…&lt;/td&gt;
    &lt;/tr&gt;
  &lt;/tbody&gt;
&lt;/table&gt;

&lt;p&gt;&lt;br /&gt;
&lt;br /&gt;&lt;/p&gt;
&lt;h4 id=&quot;d-문장-태깅-문제-single-sentence-tagging-tasks&quot;&gt;(d) 문장 태깅 문제 (Single Sentence Tagging Tasks)&lt;/h4&gt;

&lt;p&gt;마지막으로 언급된 (d) Single Sentence Tagging Tasks는 단일 문장을 입력받고, 해당 문장에 포함된 token 들에 대해 tagging을 하는 과제에 적합한 구조이다. 논문에 언급된 CoNLL-2003 NER은 Named Entity Recognition 문제를 위한 데이터셋이며 자연어 문장에서 개체명 (Named Entity)를 찾아내는 문제에 해당한다. 개체명의 종류로는 인명, 단체, 장소, 의학 코드, 시간 표현, 양, 금전적 가치, 퍼센트 등이 해당한다.&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;개체명 인식 : CoNLL-2003 NER (Named Entity Recognition)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;마지막으로 개체명 인식을 위한 데이터셋은 아래와 같이 구성되어있다. BERT 논문에서 예로 들었던 CoNLL-2003 NER 데이터셋이며, sentence의 각 단어 (또는 token) 에 대한 label로 구성되어 학습된다. 여기에서 O는 outside of named entity로 개체명에 해당하지 않음을 의미하며, PER은 인명, LOC은 지역, ORG는 기관, MICS는 특정되지 않은 개체명을 의미한다.&lt;/p&gt;

&lt;table&gt;
  &lt;thead&gt;
    &lt;tr&gt;
      &lt;th style=&quot;text-align: center&quot;&gt;구분&lt;/th&gt;
      &lt;th style=&quot;text-align: center&quot;&gt; &lt;/th&gt;
      &lt;th style=&quot;text-align: center&quot;&gt; &lt;/th&gt;
      &lt;th style=&quot;text-align: center&quot;&gt; &lt;/th&gt;
      &lt;th style=&quot;text-align: center&quot;&gt;설명&lt;/th&gt;
      &lt;th style=&quot;text-align: center&quot;&gt; &lt;/th&gt;
      &lt;th style=&quot;text-align: center&quot;&gt; &lt;/th&gt;
      &lt;th style=&quot;text-align: center&quot;&gt; &lt;/th&gt;
      &lt;th style=&quot;text-align: center&quot;&gt; &lt;/th&gt;
      &lt;th style=&quot;text-align: center&quot;&gt; &lt;/th&gt;
    &lt;/tr&gt;
  &lt;/thead&gt;
  &lt;tbody&gt;
    &lt;tr&gt;
      &lt;td style=&quot;text-align: center&quot;&gt;Sentence&lt;/td&gt;
      &lt;td style=&quot;text-align: center&quot;&gt;EU&lt;/td&gt;
      &lt;td style=&quot;text-align: center&quot;&gt;rejects&lt;/td&gt;
      &lt;td style=&quot;text-align: center&quot;&gt;German&lt;/td&gt;
      &lt;td style=&quot;text-align: center&quot;&gt;call&lt;/td&gt;
      &lt;td style=&quot;text-align: center&quot;&gt;to&lt;/td&gt;
      &lt;td style=&quot;text-align: center&quot;&gt;boycott&lt;/td&gt;
      &lt;td style=&quot;text-align: center&quot;&gt;British&lt;/td&gt;
      &lt;td style=&quot;text-align: center&quot;&gt;lamb&lt;/td&gt;
      &lt;td style=&quot;text-align: center&quot;&gt;.&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td style=&quot;text-align: center&quot;&gt;Tagging&lt;/td&gt;
      &lt;td style=&quot;text-align: center&quot;&gt;B-ORG&lt;/td&gt;
      &lt;td style=&quot;text-align: center&quot;&gt;O&lt;/td&gt;
      &lt;td style=&quot;text-align: center&quot;&gt;B-MISC&lt;/td&gt;
      &lt;td style=&quot;text-align: center&quot;&gt;O&lt;/td&gt;
      &lt;td style=&quot;text-align: center&quot;&gt;O&lt;/td&gt;
      &lt;td style=&quot;text-align: center&quot;&gt;O&lt;/td&gt;
      &lt;td style=&quot;text-align: center&quot;&gt;B-MISC&lt;/td&gt;
      &lt;td style=&quot;text-align: center&quot;&gt;O&lt;/td&gt;
      &lt;td style=&quot;text-align: center&quot;&gt;O&lt;/td&gt;
    &lt;/tr&gt;
  &lt;/tbody&gt;
&lt;/table&gt;

&lt;p&gt;&lt;br /&gt;&lt;/p&gt;

&lt;hr /&gt;

&lt;p&gt;&lt;br /&gt;&lt;/p&gt;

&lt;h2 id=&quot;자연어처리-과제와-응용&quot;&gt;&lt;strong&gt;자연어처리 과제와 응용&lt;/strong&gt;&lt;/h2&gt;

&lt;p&gt;지금까지 자연어처리 모델로 해결할 수 있는 과제들을 BERT에 언급된 목록을 활용해 열거해 보았다. 그렇다면 각각의 과제를 현업에는 어떻게 적용할 수 있을까? 아래 제시된 표를 통해 자연어처리의 HR Analytics관점에서의 활용점을 나열한다.&lt;/p&gt;

&lt;table&gt;
  &lt;thead&gt;
    &lt;tr&gt;
      &lt;th style=&quot;text-align: center&quot;&gt;문제&lt;/th&gt;
      &lt;th style=&quot;text-align: center&quot;&gt;데이터 분석 적용 사례&lt;/th&gt;
    &lt;/tr&gt;
  &lt;/thead&gt;
  &lt;tbody&gt;
    &lt;tr&gt;
      &lt;td style=&quot;text-align: center&quot;&gt;자연어 추론&lt;/td&gt;
      &lt;td style=&quot;text-align: center&quot;&gt;학벌, 인종, 성별 유추 가능한 내용 존재할 경우 필터링&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td style=&quot;text-align: center&quot;&gt;문장 유사도 확인&lt;/td&gt;
      &lt;td style=&quot;text-align: center&quot;&gt;유사 이력서/지원자 검색 등&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td style=&quot;text-align: center&quot;&gt;감정분석&lt;/td&gt;
      &lt;td style=&quot;text-align: center&quot;&gt;개인별 업무 평가나 코멘트 의미 파악에 활용 가능&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td style=&quot;text-align: center&quot;&gt;언어적 용인 가능성&lt;/td&gt;
      &lt;td style=&quot;text-align: center&quot;&gt;각종 문법 검사기나 표현 추천 기능에 활용중&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td style=&quot;text-align: center&quot;&gt;질의응답&lt;/td&gt;
      &lt;td style=&quot;text-align: center&quot;&gt;챗봇 기능을 활용해 모의 면접이나, 스트레스 관리 등에 활용할 수 있을 것&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td style=&quot;text-align: center&quot;&gt;개체명 인식&lt;/td&gt;
      &lt;td style=&quot;text-align: center&quot;&gt;평가에서 핵심 단어를 추출 및 요약하는 용도로 활용&lt;/td&gt;
    &lt;/tr&gt;
  &lt;/tbody&gt;
&lt;/table&gt;

&lt;p&gt;&lt;br /&gt;
&lt;br /&gt;&lt;/p&gt;
&lt;h4 id=&quot;자연어-추론&quot;&gt;&lt;strong&gt;자연어 추론&lt;/strong&gt;&lt;/h4&gt;

&lt;p&gt;먼저 자연어 추론의 경우 인사데이터 분석 관점에서 살펴봤을 때 특정 이력서에 학벌, 인종, 성별 등 민감한 정보가 포함되었는지에 대한 정보를 찾아내는데 사용할 수 있을 것이다. 사실 이런 정보는 굉장히 찾기 쉬우면서도 또한 어려운 부분일 수 있다. 해당 표현이 직접적인 키워드와 함께 언급된 경우에는 단순히 금지어 사전을 만들어 검색함으로 필터링 할 수 있겠지만, 우회적으로 표현된 경우에는 찾기가 어려워지기 때문이다. 예를 들어, 이력서에 아래와 같은 두 가지 표현이 올라왔다고 가정하자.&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;저는 외동아들로 태어나 …&lt;/li&gt;
  &lt;li&gt;대학교 1학년을 마친 후 카투사에 합격해 …&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;해당 사례에서, 전자의 경우 문장 자체에 지원자의 성별을 나타내는 단어가 등장하기 때문에 면접관이 이력서를 열람하기 전에 해당 사항에 대해 사전 필터링을 수행할 수 있다. 하지만 후자의 경우에는 어떤가? 본문에 성별을 직접적으로 판단할 수 있는 단어는 존재하지 않고, 카투사에 합격했다는 문장으로 지원자가 남성임을 유추해야 되는 상황이다. 따라서 머신에게 자연어를 이해할 수 있도록 학습하는 방법론들의 적용이 요구된다.&lt;/p&gt;

&lt;p&gt;&lt;br /&gt;
&lt;br /&gt;&lt;/p&gt;

&lt;h4 id=&quot;문장-유사도-확인--언어적-용인-가능성&quot;&gt;&lt;strong&gt;문장 유사도 확인 + 언어적 용인 가능성&lt;/strong&gt;&lt;/h4&gt;

&lt;p&gt;문장 유사도와 언어적 용인 가능성은 회사 지원자들의 편의를 위해 활용될 수 있을 것이다. 예를 들면, 나와 유사한 기술 혹은 적성을 갖고 있는 선배 지원자들의 사례를 조회하는 기능이나, 해당 직무 관련 질문들 중 유사한 질문들을 검색하여 지원자에게 보여주는 기능으로 활용될 수 있다. 또한 언어적 용인 가능성의 경우, 에디터 보조 서비스로 작문 시 오타 점검, 문맥 확인등을 해주어 보다 완성도 높은 글을 작성할 수 있도록 도움을 줄 수 있다. 실제 영어 버전으로 구현된 서비스의 경우, 조건에 따라 formal 및 informal 한 영작 옵션을 설정할 수 있고, 문체까지 설정해주는 기능이 존재하여 오탈자나 동의어 추천 기능 등이 매우 유용하게 사용되고 있다. 뿐만 아니라 작성한 문장에 대한 가독성과, 어휘 수준을 체크해주는 기능을 통해 세밀한 피드백을 받을 수 있어 영작 공부 시에도 많이 활용된다.&lt;/p&gt;

&lt;p&gt;실제 서비스로 적용된 사례를 살펴보면 이러한 기능은 공고를 게시하는 회사 측에도 도움이 되고 있다. T모 서비스의 경우 자연어 처리(NLP) 로 공고의 뉘앙스를 점검하는 서비스로, 공고문에 의도치 않게 포함된 부정적인 이미지를 사전에 찾아내어 해소하는 역할을 수행한다. 해당 서비스를 통해 공고 작성자는 지원자들에게 보다 매력적으로 보일 수 있도록 채용 공고를 첨삭받을 수 있다. 예를 들어, 특정 성별을 주요 채용 대상으로 둘 때 같은 의미라도 뉘앙스에 따라 공고의 매력도가 다르다는 사실을 알려주거나 하는 식이다. 해당 서비스에서는 이 기능을 통해 자격을 갖춘 후보자의 지원이 30% 증가했다고 밝히고 있다. 맥도날드, 슬랙, 스포티파이 등 여러 기업에서 해당 서비스를 사용 중 이라고 한다.&lt;/p&gt;

&lt;p&gt;&lt;br /&gt;
&lt;br /&gt;&lt;/p&gt;

&lt;h4 id=&quot;감정분석--개체명-인식&quot;&gt;&lt;strong&gt;감정분석 + 개체명 인식&lt;/strong&gt;&lt;/h4&gt;

&lt;p&gt;감정분석과 개체명 인식은 코멘트를 기반으로한 마케팅에서 활용된 사례가 있으며, 이를 평가 데이터에 접목할 경우 코멘트를 요약하는 용도로 활용할 수 있다. 가령 개체명 인식을 통해 역량들에 대한 NER 태깅을 수행한 후, 감정분석을 통해 해당 역량에 대한 코멘트가 긍정 코멘트로 사용되었는지, 부정 코멘트로 사용되었는지를 확인하는 식이다. 사실 대부분의 직원들은 이미 실력이 검증되어 채용된 사람들일 것이나, 조직이나 업무적 특성에 따라 어떤 실력을 갖춘 사람을 더 우수한 인재로 바라보는가에는 차이가 생길 것이다. 따라서 이렇게 결과를 추출 후 높은 평가를 받는 직원들의 코멘트에 어떤 키워드들이 주로 나타나는지를 확인하면, 각 회사나 팀 단위에서 추구하는 인재상이나 리더십 모델에 대한 특징을 보다 구체적으로 확인할 수 있을 것이며, 추후 채용에도 이를 활용해 각 팀과 성향이 잘 맞는 인재를 채용할 수 있을 것이다. 예를 들면, 아래와 같은 코멘트에 대한 결과를 요약 및 추출할 수 있다.&lt;/p&gt;

&lt;table&gt;
  &lt;thead&gt;
    &lt;tr&gt;
      &lt;th style=&quot;text-align: center&quot;&gt;원문&lt;/th&gt;
      &lt;th style=&quot;text-align: center&quot;&gt;인식 개체명&lt;/th&gt;
      &lt;th style=&quot;text-align: center&quot;&gt;주요 키워드&lt;/th&gt;
      &lt;th style=&quot;text-align: center&quot;&gt;긍부정&lt;/th&gt;
    &lt;/tr&gt;
  &lt;/thead&gt;
  &lt;tbody&gt;
    &lt;tr&gt;
      &lt;td style=&quot;text-align: center&quot;&gt;&lt;strong&gt;직원1님(EMP)&lt;/strong&gt;은 &lt;strong&gt;개발업무(SKIL)&lt;/strong&gt;에 있어 우수한 역량을 바탕으로 솔선수범 하는 모습을 보여주셨습니다.&lt;/td&gt;
      &lt;td style=&quot;text-align: center&quot;&gt;직원1, 개발업무&lt;/td&gt;
      &lt;td style=&quot;text-align: center&quot;&gt;우수한, 솔선수범&lt;/td&gt;
      &lt;td style=&quot;text-align: center&quot;&gt;긍정&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td style=&quot;text-align: center&quot;&gt;&lt;strong&gt;직원2님(EMP)&lt;/strong&gt;은 원활한 &lt;strong&gt;인간관계(CAPA)&lt;/strong&gt;를 바탕으로 동료들을 잘 다독여 주셨고, 그 결과 기한에 맞춰 작업을 완료할 수 있었습니다.&lt;/td&gt;
      &lt;td style=&quot;text-align: center&quot;&gt;직원2, 인간관계&lt;/td&gt;
      &lt;td style=&quot;text-align: center&quot;&gt;원활한&lt;/td&gt;
      &lt;td style=&quot;text-align: center&quot;&gt;긍정&lt;/td&gt;
    &lt;/tr&gt;
  &lt;/tbody&gt;
&lt;/table&gt;

&lt;p&gt;&lt;br /&gt;
&lt;br /&gt;&lt;/p&gt;

&lt;h4 id=&quot;질의응답&quot;&gt;질의응답&lt;/h4&gt;

&lt;p&gt;말 그대로 질의응답 기법을 활용해 지원자에게 채용 관련 실시간 Q&amp;amp;A 기능을 제공하거나, 실제 채용 프로세스에 접목하는 등의 효과를 기대할 수 있을 것이다. 실제로 M사와 W 외국 회사의 경우 NLP 기반 AI 인터뷰를 진행하고 있으며, 그들의 NLP 모델은 지원자의 의도, 문맥, 이해도 등을 파악하여 채용 데이터베이스에 자동으로 업데이트 하는 기능을 제공한다. 또한 지원자들의 이력서나 채팅 면접 시 발생된 지원자들의 특성을 요약해 어떤 직무에 적합할지를 판단해주는 기능을 갖고 있어 채용담당자들의 시간을 기하급수적으로 감축시킬 수 있다고 한다.&lt;/p&gt;

&lt;p&gt;&lt;br /&gt;&lt;/p&gt;

&lt;hr /&gt;

&lt;p&gt;&lt;br /&gt;&lt;/p&gt;

&lt;h2 id=&quot;자연어-처리-기술을-이용한-hr-analytics-사례&quot;&gt;자연어 처리 기술을 이용한 HR Analytics 사례&lt;/h2&gt;

&lt;p&gt;일반적으로 평가제도에서는 직원의 성과 및 역량에 대해 등급을 매기는 정량 평가와 개선과 성장을 위한 피드백을 제공하는 정성 평가로 이루어진다. 우리는 자연어 처리 기술을 이용해 그 중 정성 평가에 대한 분석을 수행하였다. 정성 평가 데이터는 여러 종류의 평가 제도를 통해 수집되는데 일반적으로 생각하는 조직장이 조직원에게 하는 피드백 뿐 아니라, 동료간 피드백이나 조직원이 조직장에게 하는 피드백도 포함되어 있다.&lt;/p&gt;

&lt;p&gt;우리는 분석을 위해 먼저 데이터 익명화를 수행하였다. 데이터 익명화는 분석가가 해당 피드백이 어떤 직원에 대한 코멘트인지를 알 수 없도록 이름이나 개인정보를 익명화 하는 과정이다. 익명화된 코멘트는 문장단위로 분리하여 감정분석을 수행하였다. 아무래도 평가 코멘트는 동료의 역량에 따라 피드백 성향이 달라질테니 이렇게 코멘트를 문장단위로 분리하여 감정분석을 하면 평가 등급에 따라 피드백의 양상이 얼마나 다르게 나타나는지 확인할 수 있으리라 생각했다. 즉, 성과가 좋은 동료에게는 좋은 피드백을, 개선이 필요한 동료들에게는 개선점에 대한 피드백을 줄텐데 그런 차이가 수치적으로 얼마나 차이가 나는지 확인하고 싶었다. 하위 표는 평가 등급에 따른 피드백 내 긍부정문 포함 비율을 확인한 것이다. 평가 등급은 1~5 등급으로 나뉘며 수치가 낮을수록 높은 평가를 의미한다.&lt;/p&gt;

&lt;p&gt;우리가 활용한 감정분석 모델은 각 문장의 긍부정 여부를 [0, 1] 범주의 값으로 나타내는데 1에 가까울 수록 긍정, 0에 가까울 수록 부정을 의미한다. 우리는 긍부정 여부에 대한 임계값을 설정하여 긍부정 확률이 0.15 이하인 문장을 부정으로, 0.85 이상인 문장을 긍정으로 취급하였으며, 0.15~0.85에 해당하는 문장은 중도로 구분하였다. 이렇게 기준을 설정하고 등급에 따라 긍정 코멘트와 부정 코멘트 수치를 비교했을 때, 가장 역량 및 성과가 높은 1등급은 부정 코멘트의 수 대비 긍정 코멘트의 수가 약 46배 정도 많게 타나났다. 또한 해당 결과를 통해 평가자가 각 평가 등급의 차이를 얼마나 명확하게 인지하고 있는지를 수치로 확인해볼 수 있었다. 본 결과에서는 1등급과 2등급의 긍부정 비율 차이는 크지 않은 반면 2에서 3으로 내려가는 구간에서 해당 수치가 급격히 감소하였다. 각 등급의 차이는 동일하게 한 단계이나, 평가자는 체감상 1~2 등급의 차이를 2~3 등급의 그것과 다르게 생각한다고 추정된다. 본 주제와는 조금 다른 맥락이나, 해당 차이를 다른 방면에서 봤을 때도 마찬가지였다. 코멘트에 등장한 3-gram 명사 기준의 빈출 단어 유사도를 확인해 본 결과 역시 1~2등급 사이 유사도 보다 2~3등급 사이 단어 유사도가 더 낮게 나타났다. 이러한 결과는 향후 평가 등급 조정이나 등급별 보상 체계 등을 개선할 때 참고할 수 있다.&lt;/p&gt;

&lt;table&gt;
  &lt;thead&gt;
    &lt;tr&gt;
      &lt;th style=&quot;text-align: center&quot;&gt;평가 등급&lt;/th&gt;
      &lt;th style=&quot;text-align: center&quot;&gt;(긍정/부정) 비율&lt;/th&gt;
    &lt;/tr&gt;
  &lt;/thead&gt;
  &lt;tbody&gt;
    &lt;tr&gt;
      &lt;td style=&quot;text-align: center&quot;&gt;1&lt;/td&gt;
      &lt;td style=&quot;text-align: center&quot;&gt;46.32&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td style=&quot;text-align: center&quot;&gt;2&lt;/td&gt;
      &lt;td style=&quot;text-align: center&quot;&gt;34.73&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td style=&quot;text-align: center&quot;&gt;3&lt;/td&gt;
      &lt;td style=&quot;text-align: center&quot;&gt;17.25&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td style=&quot;text-align: center&quot;&gt;4&lt;/td&gt;
      &lt;td style=&quot;text-align: center&quot;&gt;1.28&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td style=&quot;text-align: center&quot;&gt;5&lt;/td&gt;
      &lt;td style=&quot;text-align: center&quot;&gt;0.31&lt;/td&gt;
    &lt;/tr&gt;
  &lt;/tbody&gt;
&lt;/table&gt;

&lt;p&gt;더 나아가 자연어 처리 기술을 이용하면 평가 코멘트를 요약하는데 활용할 수 있다. 상기 분석에서 익명화를 수행할 때, 우리는 3종류의 태그를 활용하였는데 이는 각각 EMP, CAPA, SKIL이다. 여기에서 EMP는 익명화를 수행할 때 필요한 태그이고, 다른 두 태그는 역량 또는 기술에 해당하는 태그이다. 역량 및 기술 태그를 활용하면 해당 문장이 어떤 역량 및 기술에 대한 코멘트인지를 확인할 수 있으며, 이를 활용하면 각 사원들의 장점과 역량들을 아래와 같이 정보화 할 수 있다.&lt;/p&gt;

&lt;table&gt;
  &lt;thead&gt;
    &lt;tr&gt;
      &lt;th style=&quot;text-align: center&quot;&gt;원문&lt;/th&gt;
      &lt;th style=&quot;text-align: center&quot;&gt;직원 ID&lt;/th&gt;
      &lt;th style=&quot;text-align: center&quot;&gt;역량 or 스킬&lt;/th&gt;
      &lt;th style=&quot;text-align: center&quot;&gt;주요 키워드&lt;/th&gt;
      &lt;th style=&quot;text-align: center&quot;&gt;긍부정&lt;/th&gt;
    &lt;/tr&gt;
  &lt;/thead&gt;
  &lt;tbody&gt;
    &lt;tr&gt;
      &lt;td style=&quot;text-align: center&quot;&gt;&lt;strong&gt;직원1님(EMP)&lt;/strong&gt;은 &lt;strong&gt;개발업무(SKIL)&lt;/strong&gt;에 있어 우수한 역량을 바탕으로 솔선수범 하는 모습을 보여주셨습니다.&lt;/td&gt;
      &lt;td style=&quot;text-align: center&quot;&gt;직원1&lt;/td&gt;
      &lt;td style=&quot;text-align: center&quot;&gt;개발업무&lt;/td&gt;
      &lt;td style=&quot;text-align: center&quot;&gt;우수한, 솔선수범&lt;/td&gt;
      &lt;td style=&quot;text-align: center&quot;&gt;긍정&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td style=&quot;text-align: center&quot;&gt;&lt;strong&gt;직원2님(EMP)&lt;/strong&gt;은 원활한 &lt;strong&gt;인간관계(CAPA)&lt;/strong&gt;를 바탕으로 동료들을 잘 다독여 주셨고, 그 결과 기한에 맞춰 작업을 완료할 수 있었습니다.&lt;/td&gt;
      &lt;td style=&quot;text-align: center&quot;&gt;직원2&lt;/td&gt;
      &lt;td style=&quot;text-align: center&quot;&gt;인간관계&lt;/td&gt;
      &lt;td style=&quot;text-align: center&quot;&gt;원활한&lt;/td&gt;
      &lt;td style=&quot;text-align: center&quot;&gt;긍정&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td style=&quot;text-align: center&quot;&gt;&lt;strong&gt;직원3님(EMP)&lt;/strong&gt;은 새로운 &lt;strong&gt;디자인을 고안하는 작업(SKIL)&lt;/strong&gt;에 있어 매우 창의적이고 독창적인 결과물을 만들어내십니다.&lt;/td&gt;
      &lt;td style=&quot;text-align: center&quot;&gt;직원3&lt;/td&gt;
      &lt;td style=&quot;text-align: center&quot;&gt;디자인 고안&lt;/td&gt;
      &lt;td style=&quot;text-align: center&quot;&gt;창의적, 독창적&lt;/td&gt;
      &lt;td style=&quot;text-align: center&quot;&gt;긍정&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td style=&quot;text-align: center&quot;&gt;(윗문장에 이어) 하지만 &lt;strong&gt;보고서 작성(SKIL)&lt;/strong&gt;등의 업무에 있어서는 꼼꼼함이 필요합니다.&lt;/td&gt;
      &lt;td style=&quot;text-align: center&quot;&gt;직원3&lt;/td&gt;
      &lt;td style=&quot;text-align: center&quot;&gt;보고서 작성&lt;/td&gt;
      &lt;td style=&quot;text-align: center&quot;&gt;꼼꼼함&lt;/td&gt;
      &lt;td style=&quot;text-align: center&quot;&gt;부정&lt;/td&gt;
    &lt;/tr&gt;
  &lt;/tbody&gt;
&lt;/table&gt;

&lt;p&gt;&lt;br /&gt;&lt;/p&gt;

&lt;hr /&gt;

&lt;p&gt;&lt;br /&gt;&lt;/p&gt;

&lt;h2 id=&quot;마치며&quot;&gt;&lt;strong&gt;마치며&lt;/strong&gt;&lt;/h2&gt;

&lt;p&gt;본 포스팅을 통해 자연어처리 연구에서 해결하고자 하는 다양한 과제들과, 해당 과제들이 현업 및 서비스에 어떻게 활용되고 있는지를 확인해 보았다. 위에서 언급한 바와 같이 자연어처리는 인간의 언어를 다루는 기술로 언어가 포함된 모든 영역에 확장이 가능하며, 본 팀의 업무 분야인 HR Analytics도 자연어처리가 필요한 분야중 하나이다. 어떤 영역에 있어 자연어처리 기술은 이미 현업에 깊숙히 녹아들어 있으며, 아직 적용되지 않은 부분들에 있어서도 향후 활용될 여지가 많아 기술의 수요가 매우 높다고 생각된다. 이러한 이유로 본 포스팅에서는 자연어처리 연구에서 주로 관심을 갖고 있는 문제들과 해당 문제를 학습하기 위한 데이터, 또 실제 적용사례를 정리해보았다.&lt;/p&gt;

&lt;p&gt;&lt;br /&gt;&lt;/p&gt;

&lt;hr /&gt;

&lt;p&gt;&lt;br /&gt;&lt;/p&gt;

&lt;h2 id=&quot;참고자료&quot;&gt;&lt;strong&gt;참고자료&lt;/strong&gt;&lt;/h2&gt;

&lt;ol&gt;
  &lt;li&gt;자연어처리 문제 정의 - https://www.ibm.com/blogs/watson/2020/11/nlp-vs-nlu-vs-nlg-the-differences-between-three-natural-language-processing-concepts/&lt;/li&gt;
  &lt;li&gt;Predictive Analytics in Human Resources: Tutorial and 7 case studies - https://www.aihr.com/blog/predictive-analytics-human-resources/&lt;/li&gt;
  &lt;li&gt;How Natural Language Processing can Revolutionize Human Resources - https://www.aihr.com/blog/natural-language-processing-revolutionize-human-resources/&lt;/li&gt;
  &lt;li&gt;텍스티오 - https://www.codingworldnews.com/news/articleView.html?idxno=4166&lt;/li&gt;
  &lt;li&gt;BERT - https://arxiv.org/abs/1810.04805&lt;/li&gt;
  &lt;li&gt;AI 적용 HR 서비스 요약 - https://towardsdatascience.com/5-companies-that-are-revolutionizing-recruiting-using-artificial-intelligence-9a70986c7a7e&lt;/li&gt;
&lt;/ol&gt;
</description>
        <pubDate>Thu, 16 Sep 2021 12:00:00 +0000</pubDate>
        <link>https://danbi-ncsoft.github.io//works/2021/09/16/NLP-and-HR-analytics.html</link>
        <guid isPermaLink="true">https://danbi-ncsoft.github.io//works/2021/09/16/NLP-and-HR-analytics.html</guid>
        
        
        <category>Works</category>
        
      </item>
    
      <item>
        <title>ETL 개념과 ETL 개발 시 고려해야 하는 원칙들</title>
        <description>&lt;h2 id=&quot;0-시작하며&quot;&gt;0. 시작하며&lt;/h2&gt;
&lt;p&gt;정답이 없다는 것은 참 어려운 것 같습니다. 마치 이 도입부를 쓰는 데에 제가 1시간 30분이 넘는 시간 동안 하얀색 화면을 보며 어떠한 말로 이 포스팅을 시작하면 좋을지 고민한 것처럼요. 본문은 쉽습니다. 제가 이 포스팅에서 쓰고자 한 내용을 전달하면 되니까요. 마무리 역시 모든 내용을 정리하며 끝마치는 내용을 적으면 되니 어려울 것도 없습니다. 그러나 도입부는 그러한 가이드라인이 없어서 어떻게 해야 잘 쓴 도입부일까 하는 고민을 하게 되고야 맙니다.&lt;/p&gt;

&lt;p&gt;ETL을 개발하시는 분이라면 작업하면서 위에서 제가 한 것과 같은 고민을 많이 하실 것입니다.  “이렇게 설계하는 게 맞을까?”, “이게 가장 효율적인 구조일까?” 혹은 “ETL 작업을 하면서 지켜야하는 주의사항 같은 것은 없을까?” 등등…&lt;del&gt;(일단 저는이런 생각을 꽤 자주 했습니다)&lt;/del&gt; ETL 작업 역시도 정답이랄 게 없으니 말이죠. 어떤 것이 잘 쓴 도입부인지 딱 정의내리기 어려운 것처럼 ETL 역시 어떻게 해야 현 상태에서 내가 해낼 수 있는 최고로 효율적이고, 완벽하며, 다른 사람이 보아도 깔끔하게 보이는 결과물을 만들어낼 수 있을지 파악하기가 어렵습니다.&lt;/p&gt;

&lt;p&gt;이번 포스팅에서는 저런 고민을 하고 계시는 분들에게 조금이나마 도움이 될 만한 &lt;strong&gt;ETL에서 지켜야하는 원칙들&lt;/strong&gt;에 대해서 다뤄볼까 합니다. 물론 이 원칙들을 지킨다고 해서 최고로 효율적이고, 완벽하며, 다른 사람이 보아도 깔끔하게 보이는 결과물을 만들어낼 수 있을거라고 장담하기는 어렵습니다만, 해당 원칙들을 지키고 고려해보는 것만으로도 기존보다는 훨씬 나은 모습의 ETL 작업이 가능하리라고 생각합니다.&lt;/p&gt;

&lt;p&gt;&lt;br /&gt;&lt;/p&gt;

&lt;hr /&gt;

&lt;p&gt;&lt;br /&gt;&lt;/p&gt;

&lt;h2 id=&quot;1-etl의-개념&quot;&gt;1. ETL의 개념&lt;/h2&gt;
&lt;p&gt;ETL의 원칙에 들어가기 전에, 먼저 정확한 ETL의 개념에 대해 정리해보았습니다. 해당 파트에서는 ETL이 정확히 어떤 것을 의미하는지, ETL의 역할이 어떤 것인지, 정확히 어떤 부분부터 어디까지를 아우르는지에 대해 설명하고자 했습니다.&lt;/p&gt;

&lt;p&gt;&lt;br /&gt;&lt;/p&gt;

&lt;h3 id=&quot;1-1-역할&quot;&gt;1-1. 역할&lt;/h3&gt;
&lt;p&gt;ETL에서는 데이터 웨어하우스를 설계, 구축하고 유지 관리하는 일을 합니다. 여러 시스템의 데이터를 단일 데이터베이스, 데이터 저장소 등에 결합하여 데이터 웨어하우스를 구축하고, 기존 데이터를 저장하거나 집계, 분석하여 이를 비즈니스 결정에 활용할 수 있도록 하는 것이죠. 이와 같은 ETL의 역할을 한 문장으로 정리하자면 아래와 같습니다.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;“수많은 팀에서 관리하는 구조화된 데이터와 구조화되지 않은 데이터를 비롯한 전체 데이터를 가져와 비즈니스 목적에 실질적으로 유용한 상태로 변환하는 프로세스”&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;br /&gt;&lt;/p&gt;

&lt;h3 id=&quot;1-2-etl-프로세스&quot;&gt;1-2. ETL 프로세스&lt;/h3&gt;
&lt;p&gt;ETL은 총 세 단계로 이루어져있습니다. 바로 추출(&lt;strong&gt;E&lt;/strong&gt;xtract), 변환(&lt;strong&gt;T&lt;/strong&gt;ransform), 적재(&lt;strong&gt;L&lt;/strong&gt;oad) 단계 입니다. ETL이라는 약어 역시 각 단계의 첫 글자를 따와 만들어졌습니다.&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/assets/works/etl_principles/etl_process.png&quot; alt=&quot;idkzqprcs59z2fn0vsw88unw1yz972ym&quot; /&gt;&lt;/p&gt;
&lt;div style=&quot;color:grey;font-size:9pt;text-align:center;&quot;&gt;&lt;i&gt;이미지 출처 - https://www.xplenty.com/blog/etl-data-warehousing-explained-etl-tool-basics/&lt;/i&gt;&lt;/div&gt;

&lt;p&gt;&lt;br /&gt;&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;추출
    &lt;ul&gt;
      &lt;li&gt;다양한 소스들로부터 데이터를 추출합니다. 이 때 추출 대상들은 같은 데이터베이스일 수도, 다른 데이터 베이스일 수도 있습니다.&lt;/li&gt;
      &lt;li&gt;이렇게 추출된 데이터는 결과적으로 변환 단계를 거쳐 단일 데이터베이스(데이터 웨어하우스)에 적재됩니다.&lt;/li&gt;
    &lt;/ul&gt;
  &lt;/li&gt;
  &lt;li&gt;변환
    &lt;ul&gt;
      &lt;li&gt;추출한 데이터를 구체적인 비즈니스 로직에 의해 수정하고 변형하는 단계입니다.&lt;/li&gt;
      &lt;li&gt;재구성만 필요한 경우도 있으나, 중복을 제거하고 일관성을 확보하는 등의 정제 과정 역시 해당 단계에 포함되며, 각 데이터 필드를 검사하고 규칙을 적용합니다. (e.g. 컬럼 도메인 적용)&lt;/li&gt;
      &lt;li&gt;결과적으로 적재되었으면 하는 데이터의 형태가 되도록 로직을 적용하는 과정이 이루어집니다.&lt;/li&gt;
    &lt;/ul&gt;
  &lt;/li&gt;
  &lt;li&gt;적재
    &lt;ul&gt;
      &lt;li&gt;위의 두 단계를 거쳐 추출/변형된 데이터를 타겟 데이터베이스 내에 저장하는 단계입니다.&lt;/li&gt;
    &lt;/ul&gt;
  &lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;br /&gt;&lt;/p&gt;

&lt;h3 id=&quot;1-3-특징&quot;&gt;1-3. 특징&lt;/h3&gt;
&lt;p&gt;세 단계로 이루어져 있기 때문에 ETL은 연속적이고 지속적인 프로세스라는 특징을 가집니다. 동종(혹은 이종)의 데이터 소스에서 데이터를 추출하여 스테이징 영역에 추출한 데이터를 임시 보관한 후, 스테이징 영역에서 데이터를 필터링, Reshaping하는 등의 프로세스를 거쳐 데이터웨어하우스에 저장되기까지의 단계들이 연속적으로 이루어져야 한다는 것이죠. 해당 단계들을 진행하는 데에 있어서 데이터 엔지니어 및 개발자의 계획, 감독 및 코딩 역시 필요합니다.&lt;/p&gt;

&lt;p&gt;덧붙여, 최신 ETL 솔루션의 경우는 쉽고 빠르다는 특징 또한 가지고 있습니다. 아래의 예시는 클라우드 기반 ETL 솔루션인 Xplenty입니다. 프로그래밍 전문 지식이 없어도 다양한 소스에서 ETL을 수행할 수 있도록 설계되어 있는 것을 볼 수 있습니다.&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/assets/works/etl_principles/Xplenty.png&quot; alt=&quot;q1669a645b3vzwq4q4ubmlloi866c4y3&quot; /&gt;&lt;/p&gt;
&lt;div style=&quot;color:grey;font-size:8pt;text-align:center;&quot;&gt;&lt;i&gt;이미지 출처 - https://elements.heroku.com/addons/xplenty&lt;/i&gt;&lt;/div&gt;

&lt;p&gt;&lt;br /&gt;&lt;/p&gt;

&lt;hr /&gt;

&lt;p&gt;&lt;br /&gt;&lt;/p&gt;

&lt;h2 id=&quot;2-etl-원칙&quot;&gt;2. ETL 원칙&lt;/h2&gt;
&lt;p&gt;해당 파트에서는 ETL 작업을 진행하면서 지켜야 하는 원칙들에 대해서 정리해보았습니다. 해당 원칙들의 목적은 데이터의 품질 및 보안을 유지하는 것으로 데이터를 사용하는 사용자가 올바른 장소에서 적절한 시간에 올바른 정보를 사용할 수 있도록 합니다. 이렇게 올바른 데이터를 제공함으로써 사용자가 적절한 의사결정을 할 수 있도록 합니다.&lt;/p&gt;

&lt;h3 id=&quot;2-1-멱등성-제약&quot;&gt;2-1. 멱등성 제약&lt;/h3&gt;
&lt;p&gt;멱등성이란 같은 작업을 실행할 때마다, 동일한 결과를 보장한다는 것을 의미합니다. 즉, 프로세스가 다른 날짜/시간/조건에서 &lt;strong&gt;동일한 매개변수&lt;/strong&gt;로 실행되는 경우 결과가 동일하게 유지된다는 것이죠.&lt;/p&gt;

&lt;p&gt;일반적으로 모든 ETL 실행의 결과는 항상 멱등성 특정을 가져야 합니다. 이러한 멱등성의 개념을 ETL에 실제로 적용해보면, 다음과 같은 경우가 될 수 있습니다.&lt;/p&gt;
&lt;ul&gt;
  &lt;li&gt;ETL 프로세스를 재실행하여 재적재를 진행했을 때, 데이터가 중복 적재되어서는 안됨&lt;/li&gt;
  &lt;li&gt;ETL 프로세스가 중단되었을 경우, 다시 실행했을 때 기존 실행이 중단되지 않은 것처럼 동일한 결과가 보장되어야 함&lt;/li&gt;
  &lt;li&gt;과거 어느 날의 ETL 프로세스를 재실행했을 때, 당시의 실행결과와 동일한 결과가 보장되어야 함&lt;/li&gt;
  &lt;li&gt;예약된 ETL 프로세스를 수동으로 실행했을 경우, 둘 간의 차이가 없어야 함&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;br /&gt;&lt;/p&gt;

&lt;p&gt;아래는 멱등성이 보장되지 않는 경우의 상황입니다. ETL 프로세스가 왼쪽 그림처럼 진행되던 중 예기치 못한 상황으로 인해 중단되는 경우 재실행했다고 가정해보죠. 그랬을 때 멱등성이 보장되지 않는다면, 오른쪽 그림과 같이 중단된 시점부터 다시 데이터가 적재될 것입니다. 이 경우, 데이터가 중복 적재되는 것은 물론 해당 ETL 프로세스로부터 원헸던 결과를 얻을 수 없게 되는 것이죠.&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/assets/works/etl_principles/example.png&quot; alt=&quot;l8n0acm62eiz799pd1we8a0td4129v22&quot; /&gt;&lt;/p&gt;
&lt;div style=&quot;color:grey;font-size:8pt;text-align:center;&quot;&gt;&lt;i&gt;이미지 출처 - https://fivetran.com/blog/idempotence-failure-proofs-data-pipeline&lt;/i&gt;&lt;/div&gt;

&lt;p&gt;&lt;br /&gt;&lt;/p&gt;

&lt;p&gt;그렇다면 멱등성을 보장함으로써 해결되는 문제상황은 어떤 것들이 있을까요? 다음과 같이 정리해 볼 수 있습니다.&lt;/p&gt;
&lt;ul&gt;
  &lt;li&gt;ETL 실행 실패
    &lt;ul&gt;
      &lt;li&gt;ETL은 때때로 실패하여 실행의 누락이 발생할 수 있습니다. 이 때 ETL의 결과가 실행 타이밍에 따라 달라진다면, ETL 실패의 경우 재실행을 했을 때 원래 의도했던 것과는 다른 결과가 도출될 수 있습니다.&lt;/li&gt;
      &lt;li&gt;멱등성이 보장된다면, 재실행의 경우에도 실패가 발생하지 않은 것처럼 데이터 적재가 가능합니다.&lt;/li&gt;
    &lt;/ul&gt;
  &lt;/li&gt;
  &lt;li&gt;비즈니스 로직 변경
    &lt;ul&gt;
      &lt;li&gt;비즈니스 로직은 시간이 지남에 따라 변경될 가능성이 있습니다. 이러한 경우, 멱등성이 보장되어야만 과거의 데이터를 수정된 비즈니스 로직으로 재적재를 진행할 때도 원하는 결과를 얻을 수 있습니다.&lt;/li&gt;
    &lt;/ul&gt;
  &lt;/li&gt;
  &lt;li&gt;개발 환경 구축
    &lt;ul&gt;
      &lt;li&gt;데이터 변환 결과가 실행 타이밍에 다라 달라진다면, 라이브 환경을 미러링하는 개발 환경을 구축하는 것이 제한적입니다. 개발 환경에서 제대로 작동하는 로직도 라이브 환경에서 (실행하는 시점이 달라졌기 때문에) 결과가 다르게 나오면서 제대로 작동하지 않을 수 있습니다.&lt;/li&gt;
      &lt;li&gt;멱등성이 보장된다면, 해당 경우에도 동일한 결과를 보장하기 때문에 라이브 환경을 미러링하는 개발 환경 구축이 가능해집니다.&lt;/li&gt;
    &lt;/ul&gt;
  &lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;br /&gt;&lt;/p&gt;

&lt;p&gt;위에서 정리한 것처럼 멱등성을 보장함으로써 ETL 작업 상황에서 발생할 수 있는 상당히 크리티컬한 문제들이 해결이 됩니다. 이처럼 중요한 멱등성을 보장하기 위해서는 어떠한 방법들을 사용할 수 있을까요? 아래는 참고용으로 간단히 정리한 멱등성을 보장하기 위해 사용하는 방법들입니다.&lt;/p&gt;
&lt;ul&gt;
  &lt;li&gt;비즈니스 로직의 &lt;strong&gt;기본 키&lt;/strong&gt; 찾아 해당 키를 기준으로 OVERWRITE 하는 방식
    &lt;ul&gt;
      &lt;li&gt;다른 실행 시간에 ETL이 실행되어도 유지될 수 있는 비즈니스 로직의 고유 키를 판별해야 합니다. 이는 테이블의 기본 키와는 다를 수도, 같을 수도 있습니다.&lt;/li&gt;
      &lt;li&gt;해당 키를 기준으로 데이터를 적재하고, 필요한 경우에는 파티셔닝에도 해당 키를 사용합니다.&lt;/li&gt;
      &lt;li&gt;특정 키를 가진 데이터 전체를 덮어씌우는 방식이기 때문에 데이터의 중복 적재를 방지할 수 있습니다.&lt;/li&gt;
    &lt;/ul&gt;
  &lt;/li&gt;
  &lt;li&gt;MERGE INTO 커맨드 사용
    &lt;ul&gt;
      &lt;li&gt;테이블에 데이터가 이미 존재한다면 업데이트를 하고 존재하지 않으면 입력하는 방식으로 동작하는 커맨드입니다. 해당 커맨드로도 데이터의 중복 적재를 미연에 방지할 수 있습니다.&lt;/li&gt;
      &lt;li&gt;다만, 많은 데이터를 다룰 경우 성능이 저하될 가능성도 있기 때문에 그런 경우는 위의 비즈니스 로직의 기본키를 사용하여 OVERWRITE를 하는 방식이 성능 상 유리합니다.&lt;/li&gt;
    &lt;/ul&gt;
  &lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;br /&gt;&lt;/p&gt;

&lt;h3 id=&quot;2-2-점진적-데이터-로드&quot;&gt;2-2. 점진적 데이터 로드&lt;/h3&gt;
&lt;p&gt;점진적으로 데이터를 적재한다는 것은 각 ETL 실행에서 전채 팩트 테이블을 스캔하는 것이 아니라 이전 날짜 파티션만 스캔하여 데이터를 적재하는 방식을 말합니다. 테이블이 작은 크기일 때는 전체 팩트 테이블을 가져다가 쓰는 것도 별 무리가 없지만, 테이블이나 데이터 셋의 크기와 복잡성이 증가할 수록 전체 팩트 테이블을 사용하는 것이 부담이 되기 때문에 되도록이면 해당 원칙을 지켜주는 것이 좋습니다.&lt;/p&gt;

&lt;p&gt;아래 예시의 두 가지 쿼리를 보면 점진적으로 데이터를 적재한다는 것이 어떤 것인지 훨씬 더 이해가 쉬울 것 같습니다. 첫번째 쿼리가 기존에 작성하던 전체 팩트 테이블을 스캔하는 쿼리고, 두 번째 쿼리가 해당 쿼리를 점진적으로 데이터를 적재하도록 수정한 쿼리입니다.&lt;/p&gt;

&lt;div class=&quot;language-sql highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;o&quot;&gt;###&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;as&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;-&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;is&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;INSERT&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;OVERWRITE&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;TABLE&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;sample_summary_table&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;PARTITION&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;pdt&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;s1&quot;&gt;'20210716'&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;SELECT&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;customer&lt;/span&gt;
	&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;COUNT&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;AS&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;buy_cnt&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;FROM&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;sample_fact_table&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;WHERE&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;pdt&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;&amp;lt;=&lt;/span&gt; &lt;span class=&quot;s1&quot;&gt;'20210716'&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;GROUP&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;BY&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;customer&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;
&lt;div class=&quot;language-sql highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;o&quot;&gt;###&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;to&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;-&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;be&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;INSERT&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;OVERWRITE&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;TABLE&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;sample_summary_table&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;PARTITION&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;pdt&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;s1&quot;&gt;'20210716'&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;SELECT&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;customer&lt;/span&gt;
	&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;SUM&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;buy_cnt&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;AS&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;buy_cnt&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;FROM&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;
	&lt;span class=&quot;k&quot;&gt;SELECT&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;customer&lt;/span&gt;
		&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;buy_cnt&lt;/span&gt;
	&lt;span class=&quot;k&quot;&gt;FROM&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;sample_summary_table&lt;/span&gt;
	&lt;span class=&quot;k&quot;&gt;WHERE&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;pdt&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;s1&quot;&gt;'20210715'&lt;/span&gt;
	&lt;span class=&quot;k&quot;&gt;UNION&lt;/span&gt;
	&lt;span class=&quot;k&quot;&gt;SELECT&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;customer&lt;/span&gt;
		&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;COUNT&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;AS&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;buy_cnt&lt;/span&gt;
	&lt;span class=&quot;k&quot;&gt;FROM&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;sample_fact_table&lt;/span&gt;
	&lt;span class=&quot;k&quot;&gt;WHERE&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;pdt&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;s1&quot;&gt;'20210716'&lt;/span&gt;
	&lt;span class=&quot;k&quot;&gt;GROUP&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;BY&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;customer&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;GROUP&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;BY&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;customer&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;
&lt;p&gt;&lt;br /&gt;&lt;/p&gt;

&lt;h3 id=&quot;2-3-효율적인-과거-데이터-적재&quot;&gt;2-3. 효율적인 과거 데이터 적재&lt;/h3&gt;
&lt;p&gt;비즈니스 로직이 바뀌는 등의 경우, 이전의 날짜로 돌아가서 과거 데이터를 처리해야 하는 경우가 발생하게 됩니다. 이런 과거 데이터를 효율적으로 적재하기 위해서는 &lt;strong&gt;시작 매개변수&lt;/strong&gt;를 자율적으로 변경할 수 있어야 합니다. 즉, 비즈니스 로직을 실행시키는 날짜 또는 시간에 관계없이 &lt;strong&gt;시작 매개변수&lt;/strong&gt;만 해당 과거 날짜로 지정된다면 과거 데이터를 다시 적재할 수 있도록 해야한다는 것이죠.&lt;/p&gt;

&lt;p&gt;&lt;br /&gt;&lt;/p&gt;

&lt;h3 id=&quot;2-4-데이터-분할&quot;&gt;2-4. 데이터 분할&lt;/h3&gt;
&lt;p&gt;해당 원칙은 상당한 과거 데이터가 계속해서 적재되어 온 대형 테이블을 다룰 때 유용한 원칙입니다. 대부분의 경우는 날짜 스탬프를 사용하여 데이터를 분할하며 해당 방식으로 분할하게 되면 기존 데이터를 적재하는 경우, 여러 날짜를 병렬화하여 진행할 수 있다는 장점이 있습니다.&lt;/p&gt;

&lt;p&gt;특히 UPDATE, APPEND 및 DELETE와 같은 DML 작업은 데이터의 변형 가능성이 있기 때문에 파티션으로 데이터를 분할하여 특정 파티션을 덮어쓰는 것이 데이터 베이스 이상현상을 방지해주는 방법이기도 합니다.
&lt;img src=&quot;/assets/works/etl_principles/data_partition.png&quot; alt=&quot;t0hzmvews8uji2i4lejrqrn7xnizezav&quot; /&gt;&lt;/p&gt;
&lt;div style=&quot;color:grey;font-size:8pt;text-align:center;&quot;&gt;&lt;i&gt;이미지 출처 - https://maximebeauchemin.medium.com/functional-data-engineering-a-modern-paradigm-for-batch-data-processing-2327ec32c42a&lt;/i&gt;&lt;/div&gt;
&lt;p&gt;&lt;br /&gt;&lt;/p&gt;

&lt;p&gt;이때 파티션은 날짜 뿐만 아니라 주로 불변하는 객체를 사용하여 지정해주면 됩니다.&lt;/p&gt;

&lt;p&gt;&lt;br /&gt;&lt;/p&gt;

&lt;h3 id=&quot;2-5-중간-데이터-저장&quot;&gt;2-5. 중간 데이터 저장&lt;/h3&gt;
&lt;p&gt;여러 후속 Task에서 사용해야 하는 데이터의 경우는, 임시 로컬 파일을 만들어 사용하는 것을 지양해야 합니다. 해당 소스를 사용해야 하는 다른 Task가 다른 Task에서 생성한 임시 로컬 파일에 접근할 수 없는 경우가 생기기 때문입니다. 위의 경우를 방지하기 위해서는 모든 Task가 액세스할 수 있는 영역에 중간 데이터를 적재하는 것이 중요합니다. 저희 팀에서는 중간 데이터만 저장하는 DB를 따로 두어 관리하고 있습니다.&lt;/p&gt;

&lt;p&gt;&lt;br /&gt;&lt;/p&gt;

&lt;h3 id=&quot;2-6-시간에-따른-비즈니스-로직-관리&quot;&gt;2-6. 시간에 따른 비즈니스 로직 관리&lt;/h3&gt;
&lt;p&gt;해당 원칙은 비즈니스 로직이 시간에 따라 &lt;strong&gt;소급 적용&lt;/strong&gt;되는 경우에 고려해야 합니다. (소급이 아닌 경우=변경된 비즈니스 로직이 전체 데이터에 적용되는 경우는 과거 데이터 적재로 처리할 수 있기 때문에 해당 원칙의 고려 대상이 아님)&lt;/p&gt;

&lt;p&gt;아래 예시를 보시면 비즈니스 로직의 소급적용이라는 게 어떤 건지 이해가 쉬울 것 같습니다. 예를 들어, 2021년에 새로운 세금 계산 방법으로 인해 비즈니스 로직의 변경이 생겼다고 가정해 봅시다. 앞으로 이 새로운 비즈니스 로직을 반영하도록 ETL 프로세스 자체를 업데이트 해버리면 어떻게 될까요? 2019년도의 과거 데이터를 재적재할 때, 2021년도의 새로운 세금 계산 방법이 반영된 비즈니스 로직을 적용하게 됩니다.&lt;/p&gt;

&lt;p&gt;위처럼 시간에 따라서 각각 다른 비즈니스 로직이 적용되어야 하는 경우 유효 날짜를 사용하여 매개변수 테이블에 저장하고 프로세스를 처리할 때 올바른 매개변수를 결합하는 방식이 가장 바람직합니다.(하드코딩 지양) 2021년도 이후의 데이터를 적재하는 경우에는 새로운 비즈니스 로직을 적용하고, 그 이전의 데이터를 적재하는 경우에는 기존 로직을 적용하도록 말이죠.&lt;/p&gt;

&lt;p&gt;&lt;br /&gt;&lt;/p&gt;

&lt;h3 id=&quot;2-7-작업-단위의-명확성&quot;&gt;2-7. 작업 단위의 명확성&lt;/h3&gt;
&lt;p&gt;작업단위의 명확성이 지켜진다는 것은 작업단위가 각각의 단일 파티션으로 출력되어야 함을 의미합니다. 해당 원칙이 지켜져야지만, 각 논리 테이블을 작업에 매핑하고 각 파티션을 작업 인스턴스에 매핑하는 것이 간단해집니다.&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/assets/works/etl_principles/airflow_tree_view.png&quot; alt=&quot;c66f7ifkybt4ja4yn4ki6t5tmbm0ktki&quot; /&gt;&lt;/p&gt;
&lt;div style=&quot;color:grey;font-size:8pt;text-align:center;&quot;&gt;&lt;i&gt;이미지 출처 - https://airflow.apache.org/docs/apache-airflow/stable/ui.html&lt;/i&gt;&lt;/div&gt;
&lt;p&gt;&lt;br /&gt;&lt;/p&gt;

&lt;p&gt;위의 그림은 Airflow에서 각 작업을 나타내는 방법입니다. 각 행이 테이블에 해당하는 작업을 의미하고, 그 행 안에서의 각 셀이 하나의 작업 인스턴스를 의미하죠. 이렇게 표시함으로써 개발자는 특정 파티션에 해당하는 로그파일을 셀 하나만 선택함으로써 쉽게 추적할 수 있게 됩니다.&lt;/p&gt;

&lt;p&gt;위의 그림처럼 작업단위가 명확해지면, 작업의 재실행이 필요한 경우 전체 작업을 재실행하는 것이 아니라 원하는 셀만을 선택하여 다시 실행시킬 수 있습니다. (이를 위해서는 멱등성이 보장되어야 합니다.)&lt;/p&gt;

&lt;p&gt;&lt;br /&gt;&lt;/p&gt;

&lt;h3 id=&quot;2-8-데이터-검사테스트&quot;&gt;2-8. 데이터 검사(테스트)&lt;/h3&gt;
&lt;p&gt;데이터의 품질을 보장하기 위해 꼭 지켜야 하는 원칙입니다. 주로 데이터를 처리할 때 테스트 환경에서 충분히 데이터의 품질을 확인한 후, 라이브 환경에 적용시키도록 합니다. 특히나 ETL 로직을 라이브 환경에 적용시키기 전에 아래의 항목들을 충분히 검사/테스트/확인 해보아야 합니다.&lt;/p&gt;
&lt;ul&gt;
  &lt;li&gt;메타데이터 검사
    &lt;ul&gt;
      &lt;li&gt;설계했던 스키마의 데이터 타입과 결과물의 데이터 타입이 동일한지&lt;/li&gt;
      &lt;li&gt;각 테이블마다 적절한 제약조건이 적용되어 있는지&lt;/li&gt;
      &lt;li&gt;DB, 테이블, 컬럼 등이 정의된 도메인에 맞게 적용되어 있는지&lt;/li&gt;
    &lt;/ul&gt;
  &lt;/li&gt;
  &lt;li&gt;대상 DB에 적재된 데이터가 예상했던 결과 레코드의 수와 일치하는지
    &lt;ul&gt;
      &lt;li&gt;예상했던 것보다 훨씬 많은 레코드가 적재되는 경우 JOIN등의 과정에서 오류가 발생했을 가능성이 있습니다. 반대로 예상했던 것보다 훨씬 적은 레코드가 적재되는 경우에는 로직이 원하는 대로 작동하지 않았을 가능성이 있겠죠.&lt;/li&gt;
    &lt;/ul&gt;
  &lt;/li&gt;
  &lt;li&gt;적재된 결과 데이터들이 손실된 데이터 없이 대상 DB에 적재되고 의도대로 잘 변환되었는지&lt;/li&gt;
  &lt;li&gt;각각의 데이터가 잘못된 문자와 패턴 혹은 NULL을 포함하는 등의 부적절한 레코드의 형태로 적재되지는 않았는지&lt;/li&gt;
  &lt;li&gt;대상 테이블들에 모든 결과 데이터들이 올바르게 적재되었는지…등등&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;br /&gt;&lt;/p&gt;

&lt;h3 id=&quot;2-9-etl-모니터링&quot;&gt;2-9. ETL 모니터링&lt;/h3&gt;
&lt;p&gt;ETL 작업은 실행하는데 있어서 짧게는 수 분이내로 끝날 수도 있지만, 길게는 몇 시간까지 걸리는 경우도 있습니다. 이런 경우는 개발자가 몇 시간 동안 작업이 정상적으로 진행되는지 지속적으로 체크를 할 수 없기 때문에 모니터링 방법이 필요합니다. 모니터링 방법을 따로 둠으로서, 작업의 진행상황 등을 지속적으로 확인할 수도 있습니다. 대표적으로 Airflow를 사용하는 경우는 빌트인 되어있는 시각화 차트를 이용하여 확인이 가능하고, 따로 메일링 시스템을 두는 경우도 있습니다.&lt;/p&gt;

&lt;p&gt;Airflow에서 제공하는 다양한 시각화 차트들을 예시로 가져와보았습니다. 먼저 왼쪽 사진은 Airflow에 접속하면 가장 먼저 볼 수 있는 &lt;strong&gt;DAGs 탭&lt;/strong&gt; 입니다. 해당 화면을 통해 우리는 각각의 DAG가 현재 어떤 상태인지, 오류가 발생한 DAG는 없는지 등을 확인할 수 있습니다. 오른쪽 사진은 각 DAG를 이루고 있는 Task들의 상태를 보여주는 &lt;strong&gt;Graph View&lt;/strong&gt; 입니다. 해당 차트에서는 만약 DAG가 작업 실패 상태로 처리가 되었다면 해당 DAG안의 어떤 Task로 인해 작업이 실패했는지를 한눈에 파악할 수 있습니다.&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/assets/works/etl_principles/airflow_status_check.png&quot; alt=&quot;qz59mo2u68zhs8bgcuv0te41r39hrqap&quot; /&gt;&lt;/p&gt;
&lt;div style=&quot;color:grey;font-size:8pt;text-align:center;&quot;&gt;&lt;i&gt;이미지 출처 - https://airflow.apache.org/docs/apache-airflow/stable/ui.html&lt;/i&gt;&lt;/div&gt;
&lt;p&gt;&lt;br /&gt;&lt;/p&gt;

&lt;p&gt;위의 두 화면에서는 전체적은 DAG와 Task의 상태를 알 수 있었다면, 아래의 두 차트는 조금 더 세부적인 정보들을 제공합니다. 왼쪽의 &lt;strong&gt;Gantt Chart&lt;/strong&gt; 에서는 각각의 Task 당 얼마만큼의 시간이 소요되었는지를 보여줍니다. 비정상적으로 긴 러닝타입을 가지는 Task를 캐치할 수 있겠죠. 오른쪽의 &lt;strong&gt;Task Tries&lt;/strong&gt; 차트는 일자별로 어떤 Task를 몇 회정도 시도했는지를 차트로 볼 수 있습니다. 해당 차트를 통해 주로 실패하여 Retry가 실행된 Task가 어떤 Task인지를 확인할 수 있습니다.&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/assets/works/etl_principles/airflow_detail_check.png&quot; alt=&quot;qz59mo2u68zhs8bgcuv0te41r39hrqap&quot; /&gt;&lt;/p&gt;
&lt;div style=&quot;color:grey;font-size:8pt;text-align:center;&quot;&gt;&lt;i&gt;이미지(좌) 출처 - https://airflow.apache.org/docs/apache-airflow/stable/ui.html&lt;/i&gt;&lt;/div&gt;
&lt;div style=&quot;color:grey;font-size:8pt;text-align:center;&quot;&gt;&lt;i&gt;이미지(우) 출처 - https://www.agari.com/email-security-blog/a-summer-interns-journey-into-airflow-agari/&lt;/i&gt;&lt;/div&gt;
&lt;p&gt;&lt;br /&gt;&lt;/p&gt;

&lt;p&gt;&lt;br /&gt;&lt;/p&gt;

&lt;hr /&gt;

&lt;p&gt;&lt;br /&gt;&lt;/p&gt;

&lt;h2 id=&quot;3-추가로-고려해야-하는-사항들&quot;&gt;3. 추가로 고려해야 하는 사항들&lt;/h2&gt;
&lt;p&gt;해당 파트에서는 위에서 말한 ETL 원칙 뿐만 아니라 추가로 고려해야 하는 사항들 4가지를 정리해보았습니다. 위의 원칙들처럼 ETL 작업을 하는 것에 있어서 필수적으로 지켜야 하는 것은 아니지만, 단발적인 ETL 작업이 아닌 지속적으로 ETL을 개발하고 관리하는 작업을 한다면 언젠가는 해당 사항들을 고려해야 할 때가 올 것입니다.&lt;/p&gt;

&lt;p&gt;&lt;br /&gt;&lt;/p&gt;

&lt;h3 id=&quot;3-1-리소스-사용&quot;&gt;3-1. 리소스 사용&lt;/h3&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;※Airflow DAG란?

Directed Acyclic Graph(유향 비순환 그래프)의 형태를 가지는 Task들의 모음입니다. 
Task들을 어떤 순서와 어떤 dependency로 실행할지, 어떤 스케줄로 실행할지 등의 정보를 가지고 있습니다.
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;리소스의 &lt;strong&gt;‘유연하고 효율적인’&lt;/strong&gt; 사용은 ETL 뿐만 아니라 다른 어떤 작업을 하더라도 필수적으로 고려해야 하는 부분입니다.  데이터와 서비스가 추가되고 또 성장할 수록 ETL 작업에서 실행시켜야 하는 DAG의 수도 늘어나기 마련입니다. 한정된 리소스 안에서 DAG 수의 증가는 각 DAG의 실행 속도를 점점 느려지게 할 것이고, 이는 곧 DAG의 대기시간 증가로 이어질 것입니다.&lt;/p&gt;

&lt;p&gt;배치 주기가 긴 DAG이라면 대기시간의 증가가 치명적이지 않을 수 있지만 만일 해당 DAG이 1시간마다 돌아야 하는 작업이라면? 그리고 대기시간이 2시간을 넘어간다면? 해당 DAG는 개발자가 목적했던 시간에 동작하지 않을 가능성이 높습니다. 해당 예시에서 알 수 있듯이 DAG의 대기시간 증가는 배치주기가 짧은 DAG일수록 크리티컬합니다. 이를 방지하기 위해서는 끊임없이 한정된 리소스를 어떻게 효율적으로, 유연하게 사용할 수 있을 것인가에 대한 고민이 계속되어야 합니다.&lt;/p&gt;

&lt;p&gt;&lt;br /&gt;&lt;/p&gt;

&lt;h3 id=&quot;3-2-작명규칙-관리&quot;&gt;3-2. 작명규칙 관리&lt;/h3&gt;
&lt;p&gt;두번째 고려해야 할 사항은 ETL 작업의 컨벤션을 관리하는 것입니다. 여기서 말하는 컨벤션이란 DAG 내에 작성된 코드의 컨벤션 혹은 DAG의 네이밍 룰, 컬럼 네이밍 룰 등의 모든 규칙을 이릅니다.&lt;/p&gt;

&lt;p&gt;이 부분이 고려되지 않는 경우, 규칙 없이 제각각으로 코드가 작성되어 컨벤션이 무너지거나 유지보수가 까다로워질 수 있습니다. 같은 데이터를 저장하는 컬럼인데도 테이블마다 각각 컬럼명이 다른 경우를 가정해봅시다. 작업을 할 때마다 해당 테이블을 열어보고 컬럼명을 확인해야 하는 불필요한 단계가 계속해서 추가되겠죠. 혹은 컬럼명이 매치되지 않아 오류가 발생하는 상황이 빈번히 생길 것입니다.&lt;/p&gt;

&lt;p&gt;또는, DAG 모듈의 이름을 정하는 네이밍 룰이 존재하지 않아 모니터링 로그를 봤을 때 에러를 발생시킨 DAG가 실제로 어떤 작업에 영향을 끼치는 DAG인지 파악하기 어려워지는 문제가 생길 수도 있을 것입니다. 또는 문제의 DAG에 포함되어 있는 파일들이 구체적으로 어떤 것인지 모든 코드를 열어봐야 하는 그런 끔찍한 일(…) 도 생길 수 있으니 미리미리 규칙을 정하는 것도 좋은 방법입니다.&lt;/p&gt;

&lt;p&gt;&lt;br /&gt;&lt;/p&gt;

&lt;h3 id=&quot;3-3-의존성&quot;&gt;3-3. 의존성&lt;/h3&gt;
&lt;p&gt;ETL을 실행시키는 Tool과 DAG코드의 의존성, DAG를 실행시키는 컨테이너와 스크립트에서 사용하는 라이브러리 간의 의존성, Airflow의 경우에는 Airflow의 버전과 파이썬 버전의 의존성 모두 고려 대상에 포함됩니다. 처음 설정부터 이러한 의존관계를 명확히 하거나 분리하지 않으면 서비스가 커질 수록, 여러 DAG가 추가될 수록 의존성의 늪에서 빠져나오지 못할 수 있습니다.&lt;/p&gt;

&lt;p&gt;&lt;br /&gt;&lt;/p&gt;

&lt;h3 id=&quot;3-4-운영과-테스트의-분리&quot;&gt;3-4. 운영과 테스트의 분리&lt;/h3&gt;
&lt;p&gt;네 가지 중에 어쩌면 가장 먼저 고려가 되어야 하는 부분입니다. 어떤 로직을 ETL에 올리기 위해서는 반드시 테스트를 거쳐야 하기 때문이죠. 이 때 테스트를 하기 위해 테스트용 코드를 실행하거나, 테스트용 테이블을 따로 만들텐데 그런 상황에서는 반드시 운영과 테스트 환경을 분리하는 것이 고려되어야 합니다. 해당 부분을 고려하지 않았을 때 일어날 수 있는 상황을 나열해보면 다음과 같습니다.&lt;/p&gt;
&lt;ul&gt;
  &lt;li&gt;테스트를 하는 과정에서 실제 운영 중인 테이블에 직접 접근하는 로직을 실행시키는 경우&lt;/li&gt;
  &lt;li&gt;테스트를 하는 과정에서 테스트용 테이블 이름을 사용하는 것을 깜빡하는 경우
    &lt;ul&gt;
      &lt;li&gt;실제 운영되는 테이블을 덮어쓰거나, 과거 데이터를 건드릴 수 있음 (제일 절망적)&lt;/li&gt;
    &lt;/ul&gt;
  &lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;br /&gt;&lt;/p&gt;

&lt;hr /&gt;

&lt;p&gt;&lt;br /&gt;&lt;/p&gt;

&lt;h2 id=&quot;4-자동화-가능한-워크플로우-패턴들&quot;&gt;4. 자동화 가능한 워크플로우 패턴들&lt;/h2&gt;
&lt;p&gt;ETL 업무를 진행하다 보면, 여러 유형의 워크플로우를 개발하게 됩니다. 그 중에서는 입력 값 혹은 사용하는 테이블만 다르고 기조가 되는 로직은 같은 경우가 종종 있습니다. 이렇게 유사하거나 동일한 워크플로우에 대한 작업을 진행할 때마다 로직을 새로 개발하지 않기 위해서는 같은 유형의 워크플로우를 하나로 묶어 자동화하는 방법이 있습니다.&lt;/p&gt;

&lt;p&gt;워크플로우를 자동화한다는 것은 곧, 워크플로우를 동적으로 만들어 입력만 주어져도 개발자가 원하는 작업이 수행될 수 있도록 한다는 것을 의미합니다. 즉, 비슷한 유형의 워크플로우를 실행시킬 때 입력값만 다르게 준다면 내가 원했던 워크플로우의 결과가 나와야 한다는 것이죠. 이를 위해서 워크플로우를 자동화할 때는 일반적으로 워크플로우를 입력, 데이터 처리, 출력 세 단계로 구분하여 설계한 뒤 자동화를 합니다. 각 단계가 명확히 구분되도록 설계되어야 동적으로 워크플로우를 실행시키기 수월하기 때문입니다.&lt;/p&gt;

&lt;p&gt;아래는 대표적으로 자동화가 가능한 세 가지 워크플로우 패턴입니다. 자동화가 필요할 만큼 자주 쓰이는 워크플로우 패턴들이기도 하니, 참고하시면 좋을 것 같습니다.&lt;/p&gt;

&lt;p&gt;&lt;br /&gt;&lt;/p&gt;

&lt;h3 id=&quot;4-1-증분-계산&quot;&gt;4-1. 증분 계산&lt;/h3&gt;
&lt;p&gt;증분 계산이란 계속해서 누적값을 집계하는 워크플로우를 의미합니다. 보통 누적 합계 또는 특정 이벤트 이후 시간이 지남에 따른 집약적인 지표를 계산하는 경우에 많이 이용하는 워크플로우이기도 합니다. 예를 들어, &lt;strong&gt;“새로운 제품 출시 이후에 출시일부터 현재까지 해당 제품을 사용한 적이 있는 총 사용자 수”&lt;/strong&gt; 같은 집계 데이터를 생산할 때 많이 사용하는 워크플로우죠.&lt;/p&gt;

&lt;p&gt;위의 ETL 원칙 파트에서 말씀드렸던 것처럼 이 경우, 팩트 테이블에서 모든 날짜 파티션에 대해 SUM, MAX 또는 MIN 함수 등을 사용하여 데이터를 집계하는 것은 ETL 원칙에 위반됩니다. (효율적인 데이터 적재를 위해 ETL에서는 데이터를 점진적으로 적재해야 하기 때문이죠.) 이 경우의 이상적인 솔루션은 요약 테이블의 어제 날짜 파티션의 데이터와 팩트 테이블의 오늘 날짜 파티션의 데이터만을 가지고 집계하는 것입니다.&lt;/p&gt;

&lt;p&gt;해당 워크플로우를 자동화하기 위한 입력, 데이터 처리, 출력 단계는 다음과 같이 설계될 수 있습니다.&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;입력 : 사용자가 사전 계산할 데이터, 혹은 쿼리할 팩트 테이블 등&lt;/li&gt;
  &lt;li&gt;데이터 처리 : 데이터를 점진적으로 로드(ETL 원칙에 적합하도록)하는 스크립트&lt;/li&gt;
  &lt;li&gt;출력 : 누적 합계가 저장된 요약 테이블&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;br /&gt;&lt;/p&gt;

&lt;h3 id=&quot;4-2-과거-데이터-적재&quot;&gt;4-2. 과거 데이터 적재&lt;/h3&gt;
&lt;p&gt;ETL 작업을 진행하면서 가장 자주 만난다고 할 수 있는 워크플로우입니다. 대부분의 경우, ETL 파이프 라인을 구축하고 나서 히스토리를 재구성하는 작업이 필요하기 때문이죠. 이 경우, 현재 시점을 기준으로 과거의 데이터들을 적재해야 합니다. 또는, 기존 로직의 오류를 발견하였을 때도 해당 워크플로우가 필요합니다. 오류를 수정한 새로운 로직을 가지고 잘못된 로직에 따라 적재되었던 데이터를 새로 소급하여 적재해야 하니까요.&lt;/p&gt;

&lt;p&gt;해당 워크플로우의 데이터 처리 단계에서 고려해야 하는 것은 바로 적재하려는 기간입니다. 데이터를 백필할 때 무작정 순차적으로 적재하는 것보다 미니 백필로 분할 후, 병렬화하여 적재 작업을 진행하는 것이 훨씬 더 효율적이기 때문입니다. 단기간의 데이터를 적재할 때는 병렬화의 필요성이 느껴지지 않을 수도 있지만, 몇 달 혹은 몇 년 분량의 데이터를 적재해야 하는 상황이라면 병렬화만큼 현명한 친구가 없습니다.&lt;/p&gt;

&lt;p&gt;또, 데이터 처리 단계에 포함되어야 하는 작업은 바로 데이터 검사 작업입니다. 백필을 통해 적재되는 데이터가 내가 원하는 데이터의 모양이 맞는지, 잘못된 값이 들어가지는 않는지, 데이터의 누락이 발생하지는 않는지 등의 작업 또한 이루어져야 해당 워크플로우를 자동화하였을 때 데이터의 품질이 보장될 수 있습니다.&lt;/p&gt;

&lt;p&gt;해당 워크플로우를 자동화하기 위한 입력, 데이터 처리, 출력 단계는 다음과 같이 설계될 수 있습니다.&lt;/p&gt;
&lt;ul&gt;
  &lt;li&gt;입력 : 작업 이름, 시작 날짜, 종료 날짜, 병렬화 할 프로세스 수 등&lt;/li&gt;
  &lt;li&gt;데이터 처리 : 백필 작업 병렬화, 데이터 검사 수행, 백필한 테이블과 실제 테이블을 교체하는 프로세스&lt;/li&gt;
  &lt;li&gt;출력 : 과거 데이터가 완전히 적재된 테이블&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;br /&gt;&lt;/p&gt;

&lt;h3 id=&quot;4-3-비정규화-작업&quot;&gt;4-3. 비정규화 작업&lt;/h3&gt;
&lt;p&gt;대시보드 ETL등을 구축할 때 자주 접하게 되는 워크플로우입니다. 대시보드에서 보여주고 싶은 차트, 표에 맞추어서 데이터를 적재할 때, 팩트 테이블에서 원하는 컬럼들만을 가져와 다른 팩트 테이블들과 조인하여 최종적으로는 비정규화된 테이블의 상태로 적재를 합니다.&lt;/p&gt;

&lt;p&gt;해당 워크플로우를 자동화하기 위한 입력, 데이터 처리, 출력 단계는 다음과 같이 설계될 수 있습니다.&lt;/p&gt;
&lt;ul&gt;
  &lt;li&gt;입력 : 하나 이상의 팩트 테이블, 최종 테이블에 포함하려는 컬럼 집합, 조인에 사용할 키 등&lt;/li&gt;
  &lt;li&gt;데이터 처리 : 필요한 컬럼들을 식별하고 조인하여 비정규화된 테이블을 자동으로 생성하는 프로세스&lt;/li&gt;
  &lt;li&gt;출력 : 하나 이상의 비정규화된 테이블&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;br /&gt;&lt;/p&gt;

&lt;hr /&gt;

&lt;p&gt;&lt;br /&gt;&lt;/p&gt;

&lt;h2 id=&quot;5-마치며&quot;&gt;5. 마치며&lt;/h2&gt;
&lt;p&gt;지금까지 ETL이란 무엇인지, 어디까지의 역할을 말하는지와 ETL 작업을 할 때 고려해야하는 원칙들에 대해서 소개드렸습니다.&lt;/p&gt;

&lt;p&gt;더 효율적인 ETL, 더 나은 ETL 작업을 위해서는 위에 작성해둔 원칙들과 고려해야 하는 사항들보다 더 많은 것들을 염두에 두어야 합니다. 위의 원칙들 중에서도 평소에 무의식적으로 지키고 있던 원칙이 있는가 하면, 이런 걸 고려해야 하는구나 처음 깨닫게 되는 원칙들이 있을 수 있겠죠. 너무 많은 것을 고려해야 하는 것이 아닌지 하는 의문점이 들 수도 있지만, 게시글을 훑어보면 알 수 있듯이 이유 없는 원칙은 없는 것 같습니다.&lt;/p&gt;

&lt;p&gt;아래에 해당 게시글을 작성하며 참고한 자료 리스트를 첨부할테니, 시간 나실 때 다들 읽어보시면 좋을 것 같습니다. (제가 생략한 부분이 많거든요)&lt;/p&gt;

&lt;p&gt;그럼 또 다음 게시글로 찾아뵙겠습니다. 감사합니다.&lt;/p&gt;

&lt;p&gt;&lt;br /&gt;&lt;/p&gt;

&lt;hr /&gt;

&lt;p&gt;&lt;br /&gt;&lt;/p&gt;

&lt;h2 id=&quot;appendix&quot;&gt;Appendix.&lt;/h2&gt;
&lt;ul&gt;
  &lt;li&gt;&lt;a href=&quot;https://airflow.apache.org/docs/&quot;&gt;https://airflow.apache.org/docs/&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;https://www.confessionsofadataguy.com/the-elusive-idempotent-data-load-etl/&quot;&gt;https://www.confessionsofadataguy.com/the-elusive-idempotent-data-load-etl/&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;https://fivetran.com/blog/idempotence-failure-proofs-data-pipeline&quot;&gt;https://fivetran.com/blog/idempotence-failure-proofs-data-pipeline&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;https://blog.koresoftware.com/blog/etl-principles&quot;&gt;https://blog.koresoftware.com/blog/etl-principles&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;https://gtoonstra.github.io/etl-with-airflow/principles.html&quot;&gt;https://gtoonstra.github.io/etl-with-airflow/principles.html&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;https://maximebeauchemin.medium.com/functional-data-engineering-a-modern-paradigm-for-batch-data-processing-2327ec32c42a&quot;&gt;https://maximebeauchemin.medium.com/functional-data-engineering-a-modern-paradigm-for-batch-data-processing-2327ec32c42a&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;https://tech.socarcorp.kr/data/2021/06/01/data-engineering-with-airflow.html&quot;&gt;https://tech.socarcorp.kr/data/2021/06/01/data-engineering-with-airflow.html&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;https://medium.com/hashmapinc/etl-understanding-it-and-effectively-using-it-f827a5b3e54d&quot;&gt;https://medium.com/hashmapinc/etl-understanding-it-and-effectively-using-it-f827a5b3e54d&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;https://medium.com/@rchang/a-beginners-guide-to-data-engineering-the-series-finale-2cc92ff14b0&quot;&gt;https://medium.com/@rchang/a-beginners-guide-to-data-engineering-the-series-finale-2cc92ff14b0&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
</description>
        <pubDate>Fri, 23 Jul 2021 16:26:00 +0000</pubDate>
        <link>https://danbi-ncsoft.github.io//works/2021/07/23/etl_principles.html</link>
        <guid isPermaLink="true">https://danbi-ncsoft.github.io//works/2021/07/23/etl_principles.html</guid>
        
        
        <category>Works</category>
        
      </item>
    
      <item>
        <title>아이템에 대한 만족도는 플레이어가 게임을 열심히 하는데 영향을 미칠까??</title>
        <description>&lt;h1 id=&quot;들어가며&quot;&gt;&lt;strong&gt;들어가며&lt;/strong&gt;&lt;/h1&gt;

&lt;p&gt;앞선 포스팅에서는 저희 회사 게임을 대상으로 좋은 아이템(영웅/전설 등급 전직 아이템)이 유저의 게임 플레이에 영향을 미치는지 인과 분석한 내용을 소개해 드렸습니다.(&lt;a href=&quot;https://danbi-ncsoft.github.io/works/2021/05/13/class_get_causal_analysis.html&quot;&gt;게임 플레이어는 좋은 아이템을 획득하면 게임을 더 열심히 하게 될까?&lt;/a&gt;). 접속 일수, 결제 금액 등 아이템 획득 전후 30일(단기간) 간 게임 플레이 활동의 변화값을 보았을 때 전반적으로 좋은 아이템을 획득한 사건이 플레이 활동 증대에 영향을 미친다는 결과가 나왔습니다.&lt;/p&gt;

&lt;p&gt;그런데 분석을 하던 중 “획득한 아이템에 만족스럽지 않다면 플레이 변화가 없거나 이후 활동량이 감소하지 않을까?” 라는 궁금증이 생겼습니다. 캐릭터가 갖는 직업에 따라 사용하는 무기가 정해지는데 최초 획득한 영웅/전설 등급 전직이 다른 직업인 경우 무기 및 스탯, 스킬을 다시 세팅해야하는 불편함이 있기 때문입니다. (예를 들면 기존에 사용하던 직업은 원거리 무기를 사용하는 직업인데, 근거리 무기를 사용하는 영웅/전설 등급 전직을 획득한 경우입니다.)&lt;/p&gt;

&lt;p&gt;아무리 기존의 전직보다 스탯, 속도 측면에서 좋은 영웅/전설 등급을 얻었다고 할지라도, 내가 사용하지 않던 직업 아이템이라면 이에 만족하지 않을 수 있고, 이에 따라 플레이 변화가 다르게 나타날 수 있지 않을까? 라는 생각이 들어 후속 분석을 진행하게 되었습니다.&lt;/p&gt;

&lt;p&gt;상세 내용을 공유드리기 전에 배경을 간략히 설명 드리면, 영웅 등급 이상 전직 아이템은 획득 후 동일 등급의 다른 전직 아이템으로 랜덤 변경할 수 있는 기회가 주어집니다. 무제한으로 변경 가능한 건 아니고, 최대 변경 횟수가 있으며 변경 시에는 소정의 비용이 요구됩니다. 영웅 등급 이상은 매우 얻기 어렵기 때문에 만약 원하는 전직 아이템이 아닌 경우, 아이템을 변경하는 것이 새로 획득하는 것보다 나은 경우가 많습니다.&lt;/p&gt;

&lt;p align=&quot;center&quot;&gt;&lt;img src=&quot;/assets/works/class_get_causal_analysis2/image1.png&quot; style=&quot;width:6.2in&quot; /&gt;&lt;br /&gt;[그림1] 영웅/전설 획득 ~ 전직까지의 절차&lt;/p&gt;

&lt;p&gt;그래서 본 포스팅에서는 “최초로 획득한 영웅/전설 등급 전직에 대한 만족도에 따라 향후 플레이 변화 차이가 있을까?”, 즉 “최초 획득한 영웅/전설 등급 전직에 대한 만족도는 향후 플레이 변화 차이를 일으키는 원인이 될까?”로 분석한 내용을 공유드리고자 합니다.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;본 포스팅은 앞선 분석의 후속 분석으로 설계 및 결과 부분에 유사한 내용이 많아 간략히 작성하였습니다. 해당 내용을 먼저 보고 오시는 것을 추천드립니다 :)&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;→ (보고오기) &lt;a href=&quot;https://danbi-ncsoft.github.io/works/2021/05/13/class_get_causal_analysis.html&quot;&gt;게임 플레이어는 좋은 아이템을 획득하면 게임을 더 열심히 하게 될까?&lt;/a&gt;&lt;/p&gt;

&lt;h1 id=&quot;분석-설계&quot;&gt;&lt;strong&gt;분석 설계&lt;/strong&gt;&lt;/h1&gt;

&lt;p&gt;분석 설계를 간단히 말씀드리면 다음과 같습니다.&lt;/p&gt;

&lt;p&gt;영웅/전설 등급을 획득한 유저를 대상으로 획득한 등급 전직에 대한 만족 여부에 따라 획득 전후 30일 간 접속 일수, 결제 금액 등 플레이 변화의 평균 비교 및 통계적 유의성을 확인합니다.&lt;/p&gt;

&lt;p&gt;여기서 만족도를 세밀하게 측정하기 위해 최초 획득한 영웅/전설에 대한 최초 만족도와 최종적으로 사용하게 될 영웅/전설에 대한 최종 만족도 두 가지를 같이 보았습니다. 최초 만족도는 유저를 그룹으로 구분하는 기준으로 사용하였고 실제 영웅/전설을 전직하여 플레이하며 느끼는 것은 최종 만족도에 해당하므로 이를 기준으로 두 집단으로 나누어 플레이 변화를 비교하였습니다.&lt;/p&gt;

&lt;p&gt;그 후 두 집단 간 공정한 비교가 가능하도록 설계하기 위해 앞서 분석한 인과 분석 방식을 주로 참고하였습니다.&lt;/p&gt;

&lt;h2 id=&quot;1-변수-정의&quot;&gt;1. 변수 정의&lt;/h2&gt;

&lt;h3 id=&quot;1-1-원인-변수-및-결과-변수&quot;&gt;1-1. 원인 변수 및 결과 변수&lt;/h3&gt;

&lt;p&gt;원인 변수는 “획득한 영웅/전설 등급 전직에 대한 만족도”입니다. 위에서 설명드린 것처럼 획득한 영웅/전설 등급을 변경할 수 있는 시스템이 있기 때문에 만족도는 변경 전과 변경 후 두 단계로 걸쳐 측정하는 것이 적절하다고 판단하였습니다. (변경하지 않은 경우 최초 획득한 영웅/전설은 최종 획득한 영웅/전설과 같습니다. 이 경우 최종 만족도는 최초에 획득한 영웅/전설에 대한 동일 무기 포함 여부를 의미합니다.)&lt;/p&gt;

&lt;ol&gt;
  &lt;li&gt;최초 만족도 : &lt;strong&gt;최초 획득&lt;/strong&gt;한 영웅/전설을 유지 or 변경하였는가?(“유지 여부”)
    &lt;ul&gt;
      &lt;li&gt;유지 : 만족함&lt;/li&gt;
      &lt;li&gt;변경 : 불만족함&lt;/li&gt;
    &lt;/ul&gt;
  &lt;/li&gt;
  &lt;li&gt;최종 만족도 : &lt;strong&gt;최종 획득&lt;/strong&gt;한 영웅/전설이 기존에 사용한 무기를 사용할 수 있는 직업인가?(“동일 무기 포함 여부”)
    &lt;ul&gt;
      &lt;li&gt;포함 : 만족함&lt;/li&gt;
      &lt;li&gt;미포함 : 불만족함&lt;/li&gt;
    &lt;/ul&gt;
  &lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;이 두 변수 중 전직을 한 후 실제로 플레이하며 느끼는 영웅/전설에 대한 만족도는 최종 만족도이므로 플레이 변화에 미치는 인과 효과를 확인하기 위해서는 최종 만족도를 사용하는 것이 적절하다고 판단하였습니다.&lt;/p&gt;

&lt;p&gt;그리고 추가로 만족도를 세밀하게 측정하기 위해 최종 획득한 영웅/전설에 대한 만족 여부(최종 만족도) 뿐만 아니라 최초 획득한 영웅/전설에 대한 만족 여부(최초 만족도)도 같이 고려하는 것이 중요하다고 판단하였습니다. 예를 들어, 똑같이 최종 영웅/전설에 만족한 유저라도 최초 만족 여부에 따라 최종 영웅/전설에 느끼는 애착이 다를 것이고, 이는 플레이 변화에 영향을 줄 수 있습니다.&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;case1) 최초 만족 → 최종 만족 : 처음부터 만족하는 영웅/전설을 획득하여 변경 없이 사용&lt;/li&gt;
  &lt;li&gt;case2) 최초 불만족 → 최종 만족 : 처음 획득한 영웅/전설에 불만족하여 랜덤 변경하였고, 만족하는 것을 얻음
    &lt;ul&gt;
      &lt;li&gt;case1과 달리 랜덤 변경이라는 본인의 노력을 들여 최종 만족하는 영웅/전설을 획득한 경우임&lt;/li&gt;
      &lt;li&gt;획득한 영웅/전설에 대한 애착은 case1보다 높을 수 있음 → 애착은 플레이 변화에서 다르게 나타날 것&lt;/li&gt;
    &lt;/ul&gt;
  &lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;즉, 최초 만족도도 같이 고려하여야 유저가 획득한 영웅/전설에 대해 느끼는 만족도를 세밀하게 측정할 수 있을 것입니다. 이를 반영하기 위해 최초 만족 여부가 같은 유저끼리 그룹화하여 각각 최종 만족도가 플레이 변화에 미치는 인과 관계를 측정하는 것으로 설계하였습니다. 정리하면 다음과 같습니다.&lt;/p&gt;

&lt;p align=&quot;center&quot;&gt;&lt;img src=&quot;/assets/works/class_get_causal_analysis2/image2.png&quot; style=&quot;width:8in&quot; /&gt;&lt;br /&gt;[그림2] 최초 만족도와 최종 만족도에 따른 인과 분석 설계&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;최초 만족도(유지 여부) : 그룹을 나누는 기준으로 사용&lt;/li&gt;
  &lt;li&gt;최종 만족도(동일 무기 포함 여부) : 영웅/전설에 대한 만족도를 측정하는 기준으로 사용&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;결과 변수는 앞서 진행한 분석과 동일하게 3개의 결과 변수를 사용하였습니다.&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;전후 30일 간 접속 일수 변화&lt;/li&gt;
  &lt;li&gt;전후 30일 간 결제 금액 변화&lt;/li&gt;
  &lt;li&gt;1/2/3/4주 후 유저 그룹 지표 상향 여부&lt;/li&gt;
&lt;/ul&gt;

&lt;h3 id=&quot;1-2-통제-변수-후보&quot;&gt;1-2. 통제 변수 후보&lt;/h3&gt;

&lt;p&gt;통제 변수도 마찬가지로 동일한 변수 9개를 후보 변수로 사용하였습니다.&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;성장 : 희귀 등급 수, 컬렉션 완성 횟수, 스킬 습득 수, 인챈트 성공 횟수, 등급 합성 성공 횟수&lt;/li&gt;
  &lt;li&gt;정체 : 사망 횟수&lt;/li&gt;
  &lt;li&gt;매몰 비용 : 계정 생성 후 경과일, 레벨, 총 결제액&lt;/li&gt;
&lt;/ul&gt;

&lt;h2 id=&quot;2-분석-기간-및-대상-선정&quot;&gt;2. 분석 기간 및 대상 선정&lt;/h2&gt;

&lt;p&gt;앞서 분석한 5개의 기간에 대해 유사한 결과가 나왔기 때문에 본 분석은 그 기간들 중 하나의 기간을 선택하여 분석을 진행하였습니다.&lt;/p&gt;

&lt;p&gt;원인 변수가 “획득한 영웅/전설 등급에 대한 만족 여부”이므로 영웅/전설을 획득한 유저에 한해서 분석을 진행하였습니다.&lt;/p&gt;

&lt;h2 id=&quot;3-세부-그룹&quot;&gt;3. 세부 그룹&lt;/h2&gt;

&lt;p&gt;앞서 진행한 분석에서 유저 등급 지표(그룹1, 그룹2)와 기존 결제 여부(과금 그룹, 무과금 그룹)에 따라 인과 효과가 다르게 나타날 것으로 판단하여 각 그룹별로 인과 효과를 추정하였습니다. 본 분석에서도 해당 기준을 참고하였으나 약간의 변화를 주었습니다.&lt;/p&gt;

&lt;p&gt;결제 금액 변화 인과 효과 추정 시 유저 등급 지표와 기존 결제 여부 2개의 기준으로 총 4개의 그룹별 인과 효과를 추정하였으나, 유저 등급 지표에 따라서는 인과 효과의 차이가 크지 않았고 기존 결제 여부 그룹에 따라 차이가 크게 나타났습니다. 그래서 본 분석에서는 그룹을 너무 많이 나눔으로써 발생하는 샘플 수 부족을 방지하고 분석 결과의 복잡도를 낮추기 위해, 결제 금액 변화 인과 효과를 추정할 때는 그룹을 나누는 기준으로 기존 결제 여부만 사용하였습니다.&lt;/p&gt;

&lt;p&gt;그 후 위의 원인 변수 설계 시 설명드린 최초 만족도(“유지 여부”)를 그룹을 나누는 기준으로 추가하였습니다. 최종적으로 인과 효과를 분석할 그룹은 다음과 같습니다.&lt;/p&gt;

&lt;table&gt;
  &lt;thead&gt;
    &lt;tr&gt;
      &lt;th style=&quot;text-align: center&quot;&gt;인과 효과 추정&lt;/th&gt;
      &lt;th style=&quot;text-align: center&quot;&gt;세부 그룹&lt;/th&gt;
    &lt;/tr&gt;
  &lt;/thead&gt;
  &lt;tbody&gt;
    &lt;tr&gt;
      &lt;td style=&quot;text-align: center&quot;&gt;접속 일수 변화&lt;/td&gt;
      &lt;td style=&quot;text-align: center&quot;&gt;유저 그룹 지표 &amp;amp; 유지 여부 → 총 4개의 그룹&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td style=&quot;text-align: center&quot;&gt;결제 금액 변화&lt;/td&gt;
      &lt;td style=&quot;text-align: center&quot;&gt;기존 결제 여부 &amp;amp; 유지 여부 → 총 4개의 그룹&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td style=&quot;text-align: center&quot;&gt;유저 그룹 지표 상향 여부&lt;/td&gt;
      &lt;td style=&quot;text-align: center&quot;&gt;유저 그룹 지표 &amp;amp; 유지 여부 → 총 4개의 그룹&lt;/td&gt;
    &lt;/tr&gt;
  &lt;/tbody&gt;
&lt;/table&gt;

&lt;p&gt;&lt;em&gt;이렇게 원인 변수와 합쳐져 결과 변수에 영향을 미치는 것을 “상호작용”이라고 합니다. 상세 내용은 Appendix에 작성하였으니 참고하시면 좋을 것 같습니다.&lt;/em&gt;&lt;/p&gt;

&lt;h2 id=&quot;4-인과-다이어그램을-통한-통제-변수-구성&quot;&gt;4. 인과 다이어그램을 통한 통제 변수 구성&lt;/h2&gt;

&lt;p&gt;인과 다이어그램의 전체적인 틀은 앞서 진행한 분석을 참고하였고 그 중 일부 인과 관계를 변경하여 본 분석에 사용하였습니다. 일부 변경 사항은 아래와 같습니다.&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;원인 변수인 “만족 여부” 노드가 추가
    &lt;ul&gt;
      &lt;li&gt;영웅/전설 획득 → 만족 여부 → 결과 변수 경로(V1, V2)가 추가&lt;/li&gt;
    &lt;/ul&gt;
  &lt;/li&gt;
  &lt;li&gt;분석 대상은 영웅/전설을 획득한 유저로만 선정하였으므로 “영웅/전설 획득” 노드에 미치는 영향이 사라집니다
    &lt;ul&gt;
      &lt;li&gt;희귀 등급 수 → 영웅/전설 획득(C2)&lt;/li&gt;
    &lt;/ul&gt;
  &lt;/li&gt;
  &lt;li&gt;그리고 이에 따라 원인 변수인 만족 여부로 흐르는 영향이 차단되게 됩니다
    &lt;ul&gt;
      &lt;li&gt;희귀 등급 수 → 영웅/전설 획득 → 만족 여부(C2 → V1)&lt;/li&gt;
    &lt;/ul&gt;
  &lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;이렇게 인과 다이어그램을 수정한 후 인과 효과(V1; 만족 여부 → 결과 변수) 측정을 위해 실제로 통제할 변수를 선정하는 절차는 아래와 같이 동일하게 진행하였습니다.&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;step1) 원인 변수, 결과 변수에 직접적인 영향을 주지 않는 인과 관계 무시를 위한 노드 통제&lt;/li&gt;
  &lt;li&gt;step2) 원인 변수 외에 결과 변수에 영향을 주는 노드 통제&lt;/li&gt;
  &lt;li&gt;step3) 원인 변수, 결과 변수에 동시에 영향을 받는 노드(collider)&lt;/li&gt;
  &lt;li&gt;step4) 최종 통제 변수 선정
    &lt;ul&gt;
      &lt;li&gt;step1과 step2에서 선정된 전체 통제 변수들 중 step3에 포함되는 변수는 제외&lt;/li&gt;
    &lt;/ul&gt;
  &lt;/li&gt;
&lt;/ul&gt;

&lt;h3 id=&quot;5-1-접속-일수-변화-인과-다이어그램&quot;&gt;5-1. 접속 일수 변화 인과 다이어그램&lt;/h3&gt;

&lt;p align=&quot;center&quot;&gt;&lt;img src=&quot;/assets/works/class_get_causal_analysis2/image3.png&quot; style=&quot;width:9in&quot; /&gt;&lt;br /&gt;[그림3] 접속 일수 변화에 대한 인과 다이어그램&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;step1)&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;만족 여부, 접속 일수 변화 노드에 연결되지 않는 엣지를 의미합니다
(C4, C5, C6, C12, C13)&lt;/li&gt;
  &lt;li&gt;해당 엣지가 향하는 노드를 통제함으로써 해당 인과 관계를 무시할 수 있습니다.
(컬렉션 완성 횟수, 스킬 습득 횟수, 인챈트 성공 횟수, 레벨, 총 결제액 노드 통제)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;step2)&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;접속 일수 변화 노드에 직접적으로 인과 영향을 미치는 영웅/전설 획득을 제외한 노드입니다
(C7, C8, C9, C10, C11, C14)&lt;/li&gt;
  &lt;li&gt;해당 노드들은 인과 효과(V1) 이외에 접속 일수 변화에 영향을 미치는 변수이므로 통제가 필요합니다
(컬렉션 완성 횟수, 스킬 습득 횟수, 인챈트 성공 횟수, 레벨, 총 결제액, 사망 횟수 노드 통제)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;step3)&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;만족 여부, 접속 일수 변화 노드로부터 동시에 영향을 받는 노드를 의미합니다(없음)&lt;/li&gt;
  &lt;li&gt;해당 변수는 collider이므로, 통제 변수에서 제외하여야 합니다.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;step4)&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;step1, step2에서 선정된 통제 변수들 중 step3에 포함되는 변수를 제외합니다.
(컬렉션 완성 횟수, 스킬 습득 횟수, 인챈트 성공 횟수, 레벨, 총 결제액, 사망 횟수)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;이제 인과 효과 추정을 위해 선정된 통제 변수를 포함하여 모델을 구성합니다.&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;접속 일수 변화 ~ 만족 여부(동일 무기 포함 여부) + 컬렉션 완성 횟수 + 스킬 습득 횟수 + 인챈트 성공 횟수 + 레벨 + 총 결제액 + 사망 횟수&lt;/li&gt;
&lt;/ul&gt;

&lt;h3 id=&quot;5-2-결제-금액-변화-인과-다이어그램&quot;&gt;5-2. 결제 금액 변화 인과 다이어그램&lt;/h3&gt;

&lt;p align=&quot;center&quot;&gt;&lt;img src=&quot;/assets/works/class_get_causal_analysis2/image4.png&quot; style=&quot;width:7.5in&quot; /&gt;&lt;br /&gt;[그림4] 결제 금액 변화에 대한 인과 다이어그램&lt;/p&gt;

&lt;p&gt;위의 방식과 마찬가지로 최종 통제 변수를 선정하여 모델을 설계하면 아래와 같습니다.&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;결제 금액 변화 ~ 만족 여부(동일 무기 포함 여부) + 등급 합성 성공횟수 + 총 결제액 + 사망 횟수&lt;/li&gt;
&lt;/ul&gt;

&lt;h3 id=&quot;5-3-유저-그룹-지표-상향-인과-다이어그램&quot;&gt;5-3. 유저 그룹 지표 상향 인과 다이어그램&lt;/h3&gt;

&lt;p&gt;유저 그룹 지표 상향 여부는 마찬가지로 통제 변수를 접속 일수, 결제 금액 인과 추정 시 선정된 통제 변수의 집합을 사용하였습니다.(유저 그룹 지표는 접속 일수, 결제 금액 등 전반적인 게임 플레이 활동량에 따라 산출되기 때문입니다)&lt;/p&gt;

&lt;p&gt;그리고 변수의 값이 binary type(0 또는 1)이므로 로지스틱 회귀 모델을 사용하여 인과 효과를 추정하였습니다.&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;유저 그룹 상향 여부 ~ 만족 여부(동일 무기 포함 여부) + 등급 합성 성공횟수 + 컬렉션 완성 횟수 + 스킬 습득 횟수 + 인챈트 성공 횟수 + 레벨 + 총 결제액 + 사망 횟수&lt;/li&gt;
&lt;/ul&gt;

&lt;h1 id=&quot;분석-결과&quot;&gt;&lt;strong&gt;분석 결과&lt;/strong&gt;&lt;/h1&gt;

&lt;p&gt;아래의 그래프들은 최종 만족도인 최종 획득한 영웅/전설의 동일 무기 포함 여부(2번 원인 변수)에 따른 플레이 변화의 평균 차이를 보여줍니다. 동일 무기를 미포함하였을 때 대비 포함하였을 때 평균적으로 플레이 변화가 1) 얼마나 높은지와 2) 이에 대한 신뢰 구간을 나타냅니다. 즉, 동일 무기 포함 여부 기준으로 불만족하였을 때 대비 만족하였을 때 플레이 변화 차이가 있는지, 있다면 얼마나 큰지에 대한 인과 효과를 측정한 것입니다.&lt;/p&gt;

&lt;p&gt;신뢰 구간이 점선(값이 0인 부분) 우측에 위치한다면 “획득한 영웅/전설 전직에 대해 만족한 경우 불만족하였을 때보다 플레이 변화가 증가한다”라고 해석합니다.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;결과를 간단히 요약해드리면, 전반적으로 최초 획득한 영웅/전설에 대한 만족도에 따라 플레이 변화 차이는 보이지 않는 것으로 보입니다. 이를 해석하면 전반적인 능력치가 대폭 향상되는 영웅/전설 등급을 최초로 획득한 것이므로 동일 무기 포함 여부에 따른 만족도는 플레이에 큰 영향을 미치지 않는 것으로 보입니다.&lt;/strong&gt;&lt;/p&gt;

&lt;h3 id=&quot;5-1-접속-일수-변화에-미치는-인과-효과-추정&quot;&gt;5-1. 접속 일수 변화에 미치는 인과 효과 추정&lt;/h3&gt;

&lt;table&gt;
  &lt;thead&gt;
    &lt;tr&gt;
      &lt;th style=&quot;text-align: center&quot;&gt;최초 영웅/전설 유지&lt;/th&gt;
      &lt;th style=&quot;text-align: center&quot;&gt;최초 영웅/전설 변경&lt;/th&gt;
    &lt;/tr&gt;
  &lt;/thead&gt;
  &lt;tbody&gt;
    &lt;tr&gt;
      &lt;td style=&quot;text-align: center&quot;&gt;&lt;img src=&quot;/assets/works/class_get_causal_analysis2/image5_1.png&quot; style=&quot;width:7in&quot; /&gt;&lt;/td&gt;
      &lt;td style=&quot;text-align: center&quot;&gt;&lt;img src=&quot;/assets/works/class_get_causal_analysis2/image5_2.png&quot; style=&quot;width:7in&quot; /&gt;&lt;/td&gt;
    &lt;/tr&gt;
  &lt;/tbody&gt;
&lt;/table&gt;

&lt;p&gt;모든 그룹에서 영웅/전설 등급의 동일 무기 포함 여부는 접속 일수 변화에 유의한 영향을 미치지 않는 것으로 보입니다.&lt;/p&gt;

&lt;h3 id=&quot;5-2-결제-금액-변화에-미치는-인과-효과-추정&quot;&gt;5-2. 결제 금액 변화에 미치는 인과 효과 추정&lt;/h3&gt;

&lt;table&gt;
  &lt;thead&gt;
    &lt;tr&gt;
      &lt;th style=&quot;text-align: center&quot;&gt;무과금&lt;/th&gt;
      &lt;th style=&quot;text-align: center&quot;&gt;과금&lt;/th&gt;
    &lt;/tr&gt;
  &lt;/thead&gt;
  &lt;tbody&gt;
    &lt;tr&gt;
      &lt;td style=&quot;text-align: center&quot;&gt;&lt;img src=&quot;/assets/works/class_get_causal_analysis2/image6_1.png&quot; style=&quot;width:7.3in&quot; /&gt;&lt;/td&gt;
      &lt;td style=&quot;text-align: center&quot;&gt;&lt;img src=&quot;/assets/works/class_get_causal_analysis2/image6_2.png&quot; style=&quot;width:7.3in&quot; /&gt;&lt;/td&gt;
    &lt;/tr&gt;
  &lt;/tbody&gt;
&lt;/table&gt;

&lt;p&gt;(x축의 범위는 과금 그룹이 무과금 그룹에 비해 5배 넓습니다)&lt;/p&gt;

&lt;p&gt;마찬가지로 모든 그룹에서 영웅/전설 등급의 동일 무기 포함 여부는 결제 금액 변화에도 유의한 영향을 미치지 않는 것으로 보입니다.&lt;/p&gt;

&lt;h3 id=&quot;5-3--4주-후-유저-등급-상향-확률에-미치는-인과-효과-추정&quot;&gt;5-3.  4주 후 유저 등급 상향 확률에 미치는 인과 효과 추정&lt;/h3&gt;

&lt;table&gt;
  &lt;thead&gt;
    &lt;tr&gt;
      &lt;th style=&quot;text-align: center&quot;&gt;최초 영웅/전설 유지&lt;/th&gt;
      &lt;th style=&quot;text-align: center&quot;&gt;최초 영웅/전설 변경&lt;/th&gt;
    &lt;/tr&gt;
  &lt;/thead&gt;
  &lt;tbody&gt;
    &lt;tr&gt;
      &lt;td style=&quot;text-align: center&quot;&gt;&lt;img src=&quot;/assets/works/class_get_causal_analysis2/image7_1.png&quot; style=&quot;width:7.3in&quot; /&gt;&lt;/td&gt;
      &lt;td style=&quot;text-align: center&quot;&gt;&lt;img src=&quot;/assets/works/class_get_causal_analysis2/image7_2.png&quot; style=&quot;width:7.3in&quot; /&gt;&lt;/td&gt;
    &lt;/tr&gt;
  &lt;/tbody&gt;
&lt;/table&gt;

&lt;p&gt;(x축이 나타내는 것은 유저 등급이 상향될 확률입니다)&lt;/p&gt;

&lt;p&gt;대부분 그룹에서 영웅/전설 등급의 동일 무기 포함 여부는 유저 등급 상향 확률에 유의한 영향을 미치지 않습니다.&lt;/p&gt;

&lt;p&gt;단, 특정 그룹(그룹1이며 최초 획득 영웅/전설을 변경한 유저)에서는 6.7%p 정도의 인과 효과가 나타났습니다. 이는 해당 그룹에서 최초 획득한 영웅/전설에 불만족하여 변경한 유저 중 최종적으로 획득한 영웅/전설 전직에 불만족한 유저 대비 만족한 유저가 향후 전반적인 플레이 증대가 일어날 확률이 높다는 것을 의미합니다.&lt;/p&gt;

&lt;p&gt;그룹2에서는 위와 같은 현상이 나타나지 않았는데, 게임 참여도가 낮은 유저 층이므로 영웅/전설 등급 자체는 매우 중요하지만 동일 무기 포함 여부는 크게 중요하게 작용하지 않는 것으로 생각됩니다.&lt;/p&gt;

&lt;h1 id=&quot;마치며&quot;&gt;&lt;strong&gt;마치며&lt;/strong&gt;&lt;/h1&gt;

&lt;p&gt;지금까지 영웅/전설을 획득한 유저의 만족도에 따른 향후 플레이 변화에 인과 관계가 존재하는지 소개드렸습니다.&lt;/p&gt;

&lt;p&gt;유저의 만족도는 전직 아이템 유지 여부, 동일 무기를 사용하는 직업인지 여부로 2단계에 걸쳐 살펴보았고 이를 통해 최초 만족도 → 최종 만족도로 나누어 정의하였습니다.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;이전 포스팅의 결과와 함께 보자면, “최초로 획득한 높은 등급의 전직이다보니 획득 자체가 중요하고, 만족도는 향후 플레이가 감소하거나 증가하는데 영향을 미치지 않는 것으로 보인다”라고 결론 지을 수 있습니다.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;이번에도 쓰다보니 글이 길어졌네요. 다음에는 더 재미있는 주제로 찾아오겠습니다 :D&lt;/p&gt;

&lt;h1 id=&quot;appendix&quot;&gt;&lt;strong&gt;Appendix.&lt;/strong&gt;&lt;/h1&gt;

&lt;p&gt;&lt;em&gt;아래의 그림의 출처는 [&lt;a href=&quot;https://en.wikipedia.org/wiki/Interaction_(statistics)&quot;&gt;wikipedia] Interaction&lt;/a&gt;입니다.&lt;/em&gt;&lt;/p&gt;

&lt;h3 id=&quot;상호작용interaction&quot;&gt;상호작용(interaction)&lt;/h3&gt;

&lt;ul&gt;
  &lt;li&gt;
    &lt;p&gt;결과변수에 합쳐져 영향을 주는 변수 간 관계&lt;/p&gt;
  &lt;/li&gt;
  &lt;li&gt;
    &lt;p&gt;각 변수가 결과 변수에 독립적으로 영향을 미치지 않고 하나의 변수가 변함에 따라 다른 하나의 변수가 결과 변수에 미치는 영향이 바뀌는 것&lt;/p&gt;
  &lt;/li&gt;
&lt;/ul&gt;

&lt;h3 id=&quot;상호작용을-고려해야하는-이유&quot;&gt;상호작용을 고려해야하는 이유&lt;/h3&gt;

&lt;p&gt;변수 간 상호작용을 고려하지 않고, 개별 변수가 결과변수에 미치는 영향만을 고려할 시 해석에 오류가 발생할 수 있음&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;\(Y\) : 결과 변수, \(T_1\) : 상호작용 변수1, \(T_2\) : 상호작용 변수2&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;\(T_2\)가 변함에 따라 \(T_1\)이 \(Y\)에 미치는 영향이 바뀐다. 예를 들어 \(T_2=a\)인 경우 \(T_1\)이 \(Y\)에 미치는 영향은 \(A\)이지만, \(T_2=b\)인 경우 \(T_1\)이 \(Y\)에 미치는 영향은 \(B\)이다. (\(a \ne  b\), \(A \ne B\))&lt;/p&gt;

&lt;p&gt;따라서 \(T_1\)이 \(Y\)에 미치는 영향을 확인하기 위해서는 \(T_2\)를 고정시켜놓고 측정해야 함&lt;/p&gt;

&lt;h3 id=&quot;상호작용을-고려한-영향-추정-방법&quot;&gt;상호작용을 고려한 영향 추정 방법&lt;/h3&gt;

&lt;p&gt;주로 2번이 해석이 용이하여 2번 방식을 사용함&lt;/p&gt;

&lt;ol&gt;
  &lt;li&gt;상호작용을 일으키는 두 변수를 곱하여 회귀항에 추가하여 해결할 수 있으나 해석에 어려움이 있음&lt;/li&gt;
  &lt;li&gt;하나의 상호작용 변수의 값에 따라 sub group으로 나누어 효과를 추정하는 방식이 해석에 용이함
    &lt;ul&gt;
      &lt;li&gt;위의 예시에서 \(T_2\)를 고정하고 \(T_1\)에 따른 \(Y\)의 변화를 본다.&lt;/li&gt;
    &lt;/ul&gt;
  &lt;/li&gt;
&lt;/ol&gt;

&lt;h3 id=&quot;예시-과자의-수율--온도의-높낮이&quot;&gt;예시) 과자의 수율 ~ 온도의 높낮이&lt;/h3&gt;

&lt;p&gt;데이터는 아래와 같음&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;과자의 수율(yield) : float&lt;/li&gt;
  &lt;li&gt;온도(temperature) : ordinal (1 : 낮음, 2 : 높음)&lt;/li&gt;
  &lt;li&gt;시간(time) : ordinal (1 : 낮음, 2 : 높음)&lt;/li&gt;
&lt;/ul&gt;

&lt;table&gt;
  &lt;thead&gt;
    &lt;tr&gt;
      &lt;th style=&quot;text-align: center&quot;&gt;&lt;strong&gt;temperature&lt;/strong&gt;&lt;/th&gt;
      &lt;th style=&quot;text-align: center&quot;&gt;&lt;strong&gt;time&lt;/strong&gt;&lt;/th&gt;
      &lt;th style=&quot;text-align: center&quot;&gt;&lt;strong&gt;yield&lt;/strong&gt;&lt;/th&gt;
    &lt;/tr&gt;
  &lt;/thead&gt;
  &lt;tbody&gt;
    &lt;tr&gt;
      &lt;td style=&quot;text-align: center&quot;&gt;1&lt;/td&gt;
      &lt;td style=&quot;text-align: center&quot;&gt;1&lt;/td&gt;
      &lt;td style=&quot;text-align: center&quot;&gt;30&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td style=&quot;text-align: center&quot;&gt;1&lt;/td&gt;
      &lt;td style=&quot;text-align: center&quot;&gt;1&lt;/td&gt;
      &lt;td style=&quot;text-align: center&quot;&gt;35&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td style=&quot;text-align: center&quot;&gt;1&lt;/td&gt;
      &lt;td style=&quot;text-align: center&quot;&gt;2&lt;/td&gt;
      &lt;td style=&quot;text-align: center&quot;&gt;60&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td style=&quot;text-align: center&quot;&gt;1&lt;/td&gt;
      &lt;td style=&quot;text-align: center&quot;&gt;2&lt;/td&gt;
      &lt;td style=&quot;text-align: center&quot;&gt;58&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td style=&quot;text-align: center&quot;&gt;2&lt;/td&gt;
      &lt;td style=&quot;text-align: center&quot;&gt;1&lt;/td&gt;
      &lt;td style=&quot;text-align: center&quot;&gt;60&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td style=&quot;text-align: center&quot;&gt;2&lt;/td&gt;
      &lt;td style=&quot;text-align: center&quot;&gt;1&lt;/td&gt;
      &lt;td style=&quot;text-align: center&quot;&gt;64&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td style=&quot;text-align: center&quot;&gt;2&lt;/td&gt;
      &lt;td style=&quot;text-align: center&quot;&gt;2&lt;/td&gt;
      &lt;td style=&quot;text-align: center&quot;&gt;30&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td style=&quot;text-align: center&quot;&gt;2&lt;/td&gt;
      &lt;td style=&quot;text-align: center&quot;&gt;2&lt;/td&gt;
      &lt;td style=&quot;text-align: center&quot;&gt;35&lt;/td&gt;
    &lt;/tr&gt;
  &lt;/tbody&gt;
&lt;/table&gt;

&lt;p&gt;그룹별 수율의 평균을 구하면 아래와 같음&lt;/p&gt;

&lt;table&gt;
  &lt;thead&gt;
    &lt;tr&gt;
      &lt;th style=&quot;text-align: center&quot;&gt; &lt;/th&gt;
      &lt;th style=&quot;text-align: center&quot;&gt;시간 - Short(1)&lt;/th&gt;
      &lt;th style=&quot;text-align: center&quot;&gt;시간 - Long(2)&lt;/th&gt;
    &lt;/tr&gt;
  &lt;/thead&gt;
  &lt;tbody&gt;
    &lt;tr&gt;
      &lt;td style=&quot;text-align: center&quot;&gt;&lt;strong&gt;온도 - Low(1)&lt;/strong&gt;&lt;/td&gt;
      &lt;td style=&quot;text-align: center&quot;&gt;32.5&lt;/td&gt;
      &lt;td style=&quot;text-align: center&quot;&gt;59&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td style=&quot;text-align: center&quot;&gt;&lt;strong&gt;온도 - High(2)&lt;/strong&gt;&lt;/td&gt;
      &lt;td style=&quot;text-align: center&quot;&gt;62&lt;/td&gt;
      &lt;td style=&quot;text-align: center&quot;&gt;32.5&lt;/td&gt;
    &lt;/tr&gt;
  &lt;/tbody&gt;
&lt;/table&gt;

&lt;p&gt;그래프로 확인하면 다음과 같음&lt;/p&gt;

&lt;p align=&quot;center&quot;&gt;&lt;img src=&quot;/assets/works/class_get_causal_analysis2/image8.png&quot; style=&quot;width:6in&quot; /&gt;&lt;br /&gt;&lt;/p&gt;

&lt;p&gt;상호작용을 고려하지 않았을 때&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;시간을 고정하지 않은 채 온도의 증가(Low → High)가 수율에 미치는 평균 차이를 구하면 (62+32.5) / 2 - (59+32.5) / 2 = 1.5&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;→ 온도가 수율에 영향을 미치지 않는다고 판단할 수 있음&lt;/strong&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;상호작용을 고려하였을 때&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;time을 Short로 고정
    &lt;ul&gt;
      &lt;li&gt;온도의 증가(Low → High)가 수율에 미치는 영향의 평균 = 62-32.5 = 29.5&lt;/li&gt;
    &lt;/ul&gt;
  &lt;/li&gt;
  &lt;li&gt;time을 High로 고정
    &lt;ul&gt;
      &lt;li&gt;온도의 증가(Low → High)가 수율에 미치는 영향의 평균 = -26.5&lt;/li&gt;
    &lt;/ul&gt;
  &lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;→ 수율에 대해 온도와 시간이 상호작용을 일으키므로 하나의 변수를 고정한 후 나머지 하나의 영향을 살펴보는 것이 타당함&lt;/strong&gt;&lt;/li&gt;
&lt;/ul&gt;

</description>
        <pubDate>Thu, 20 May 2021 15:45:00 +0000</pubDate>
        <link>https://danbi-ncsoft.github.io//works/2021/05/20/class_get_causal_analysis2.html</link>
        <guid isPermaLink="true">https://danbi-ncsoft.github.io//works/2021/05/20/class_get_causal_analysis2.html</guid>
        
        
        <category>Works</category>
        
      </item>
    
      <item>
        <title>게임 플레이어는 좋은 아이템을 획득하면 게임을 더 열심히 하게 될까?</title>
        <description>&lt;h1 id=&quot;들어가며&quot;&gt;&lt;strong&gt;들어가며&lt;/strong&gt;&lt;/h1&gt;

&lt;p&gt;최근에 많은 IT기업들이 연봉을 큰 폭으로 인상했다는 기사를 종종 보곤 합니다. 사실 보상이 직원의 생산성에 어떤 영향을 미치는가에 대해선 의견이 분분합니다. 얼핏 생각해 보면 보상이 크면 직원은 그만큼 회사에 대한 로열티와 만족도가 올라가서 더 열심히 일할 것 같은데, 또 한편으로 생각해 보면 열심히 일해야 한다는 목적을 충족했기 때문에 동기 부여가 안되어 오히려 덜 열심히 일할수도 있을 것 같습니다.&lt;/p&gt;

&lt;p&gt;예를 들어 메이저리그에서는 선수들이 FA 자격이 주어지기 직전에 성적이 좋다가 FA 이후 높은 연봉을 받고 이적한 뒤에 성적이 하락하는 경우가 많습니다. 그래서 이것만 보면 높은 보상은 오히려 부정적인 효과를 주는 것 같습니다. 물론 이렇게 단정하는 것은 위험합니다. 왜냐하면, 대개 FA 자격이 주어지는 선수들은 어느 정도 나이가 많기 때문에 선수로서의 전성기를 지난 경우가 많기 때문이죠. 따라서 단순히 나타난 현상만 갖고 어떻다 단정하는 것은 위험합니다.&lt;/p&gt;

&lt;p&gt;아마 가장 정확하게 확인하려면 다른 조건은 모두 동일한 어떤 두 사람에게 차별 보상을 했을 때 그 이후에 이 둘의 생산성이 어떻게 변하는지를 관찰하는 건데 실제 이런 실험을 할수는 없겠죠 ^^; 대신 사회과학자들은 여러 사례를 수집한 후 데이터 분석을 통해 보상과 생산성에 대한 인과 분석을 합니다. 하지만 좋은 데이터를 수집하기도 힘들뿐더러 각 사례에서 다양한 조건과 상황을 모두 고려한 정확한 분석을 하기는 쉽지 않습니다.&lt;/p&gt;

&lt;p&gt;그런데 문득 게임에서는 어떤 큰 보상이 주어졌을 때 플레이어의 게임 활동이 어떻게 변할까? 라는 궁금증이 생겼습니다 (물론 일을 열심히 하는 것과 게임을 열심히 하는 것은 다르지만요). 그리고 마침 저희 회사 게임에 이와 비슷한 상황을 관찰할 수 있는 경우가 있어서 분석을 진행해 보았습니다.&lt;/p&gt;

&lt;p&gt;분석 내용에 앞서 배경 설명을 간략히 드리면 분석 대상 게임에서 모든 캐릭터는 생성부터 직업을 갖게 됩니다. 그리고 각 직업마다 등급(일반, 고급, 희귀, 영웅, 전설, 신화)이 있고 특정 등급으로 전직하기 위해서는 특별한 아이템이 필요합니다. 해당 아이템을 사용하여 높은 등급으로 전직하면 전반적인 캐릭터의 능력치가 올라가는데 영웅 등급부터 대폭 향상되기 때문에 영웅 등급 이후가 매우 중요합니다. 하지만 영웅 등급 전직을 위한 아이템을 얻기 매우 어려워 대부분 유저들이 게임을 시작하고 첫 목표를 영웅 전직(주로 “영변”이라 부릅니다)으로 잡습니다.&lt;/p&gt;

&lt;p align=&quot;center&quot;&gt;&lt;img src=&quot;/assets/works/class_get_causal_analysis/image0.png&quot; style=&quot;width:4in&quot; /&gt;&lt;br /&gt;
    [그림1] 첫 영변을 얻었을 때의 기분이란!&lt;/p&gt;

&lt;p&gt;보상을 획득하는데 우연성이 중요한 점과 획득하였을 때 큰 이점이 존재한다는 점 그리고 많은 플레이어들이 이 영웅 전직을 게임을 열심히 하려는 주요 목표로 삼는다는 점으로 보았을 때, 게임 내 “영변”을 획득하는 것은 현실 세계에서 근로자들에게 큰 보상이 주어지는 상황과 비슷하다고 생각했습니다. 그래서 “유저가 최초로 영웅/전설 등급 전직을 획득하면 그 이후 플레이를 더 열심히 할까?”, “유저가 플레이를 열심히 하게되는 이유가 최초로 영웅/전설 등급을 획득해서일까?” 라는 인과 효과에 대해 궁금증이 생겼고 분석을 진행하게 되었습니다.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;(이하 문장의 간결성을 위해 “최초로 영웅/전설 등급 전직 획득”을 “영웅/전설 획득” 이라고 칭하겠습니다)&lt;/em&gt;&lt;/p&gt;

&lt;h1 id=&quot;분석-방식&quot;&gt;&lt;strong&gt;분석 방식&lt;/strong&gt;&lt;/h1&gt;

&lt;p&gt;분석 방법은 다음과 같습니다. 기존에 영웅/전설 획득을 한 적이 없는 유저들을 일주일간 관측하여 그 기간 동안 최초로 획득한 집단 (실험군) 과 그렇지 못한 집단 (대조군) 을 분류합니다. 이 후 두 집단의 플레이 시간 및 결제 금액 등의 게임 활동량과 관련된 값들이 이전에 비해 얼마나 달라졌는지 그 변화값을 측정한 후 두 집단의 차이가 통계적으로 유의한지 확인합니다. 이렇게 하면 영웅/전설 획득을 하지 않았을 때 대비 획득하였을 때의 플레이 변화를 볼 수 있기 때문에 영웅/전설 획득이 플레이 변화에 미치는 영향을 확인할 수 있을 것입니다.&lt;/p&gt;

&lt;h4 id=&quot;그러면-영웅전설-획득이-플레이-변화에-원인이-되는지-알-수-있겠네요-아니요-그렇게-단순하지-않습니다&quot;&gt;&lt;strong&gt;그러면 영웅/전설 획득이 플레이 변화에 원인이 되는지 알 수 있겠네요? 아니요, 그렇게 단순하지 않습니다..&lt;/strong&gt;&lt;/h4&gt;

&lt;p&gt;이제 분석의 최종 목적인 “영웅/전설 획득이 플레이 변화에 원인이 되는가?”를 생각해봅시다. 위에서 확인한 두 집단 간 플레이 변화 차이를 영웅/전설 획득이 플레이 변화에 미치는 인과 효과라고 할 수 있을까요? 그렇지 않을 가능성이 매우 높습니다. 왜냐하면 인과 효과를 주장하기 위해서는 두 집단(실험군, 대조군) 사이에 “영웅/전설 획득”을 제외하고 다른 성질이 동일해야하는데 그렇지 않은 확률이 높기 때문입니다. 플레이 변화에 영향을 줄 수 있는 다른 것들을 동일하게 유지하고 영웅/전설 획득만 바꾸었을 때 플레이 변화 차이가 발생하는지 측정해야 하는 것이죠.&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;예를 들어, 어떤 변수 A가 플레이 변화에 영향을 미치는 원인이고 두 집단(실험군, 대조군) 간 A가 매우 다른 상태라고 해봅시다.&lt;/li&gt;
  &lt;li&gt;만약 “두 집단의 플레이 변화 차이가 존재한다”라는 결론이 나왔을 때, 이것이 영웅/전설 획득에 때문에 일어난 결과라고 할 수 있을까요?&lt;/li&gt;
  &lt;li&gt;아닙니다. 이것은 변수 A에 의해 발생한 결과일 수 있습니다.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;이렇듯 현실에서 어떤 기준으로 두 집단을 나누는 경우, 해당 기준을 제외하고 다른 특성들이 두 집단 간에 동일하지 않은 경우가 많습니다. 이것을 고려하지 못한 채 단순히 두 집단의 플레이 변화를 비교한다면 영웅/전설 획득이 플레이 변화에 원인이 아니었음에도 우리는 원인이다라고 잘못된 인과 효과를 주장할 수도 있습니다.&lt;/p&gt;

&lt;p&gt;때문에 본 분석에서 실험군, 대조군 간에 성질을 동일하게 맞춰주는 “인과 추론”이라는 절차가 매우 중요하였고 그 과정을 먼저 소개드리려고 합니다.&lt;/p&gt;

&lt;h2 id=&quot;인과-추론이-뭐에요&quot;&gt;인과 추론이 뭐에요?&lt;/h2&gt;

&lt;p&gt;인과 추론은 우리가 확인하기 원하는 인과 효과를 측정하기 위한 과정으로 인과 효과를 제외한 다른 영향을 동일하게 맞추기 위해 어떤 요인을 건드려야 하는지(통제) 찾는 것으로 이해하시면 좋을 것 같습니다. 그렇다면 외부 영향에는 어떤 것이 존재할까요?&lt;/p&gt;

&lt;h3 id=&quot;confounder교란-변수&quot;&gt;confounder(교란 변수)&lt;/h3&gt;

&lt;p&gt;confounder는 원인 변수와 결과 변수에 동시에 영향을 주면서, 원인 변수와 결과 변수 사이에 상관 관계를 만드는 변수를 말합니다.&lt;/p&gt;

&lt;p align=&quot;center&quot;&gt;&lt;img src=&quot;/assets/works/class_get_causal_analysis/image1_1.png&quot; style=&quot;width:5in&quot; /&gt;&lt;br /&gt;[그림2-1] 원인 변수와 결과 변수에 동시에 영향을 주는 교란 변수&lt;/p&gt;

&lt;p align=&quot;center&quot;&gt;&lt;img src=&quot;/assets/works/class_get_causal_analysis/image1_2.png&quot; style=&quot;width:7in&quot; /&gt;&lt;br /&gt;[그림2-2] 교란 변수 때문에 애초에 두 그룹의 공정한 비교가 불가능합니다(교란 변수의 개념적 이해)&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;애초에 게임에 대해 애착이 높은 유저는 영웅/전설 획득 가능성이 높을 수 있으며, 플레이 변화가 증가할 가능성(게임을 열심히 함)도 높을 수 있습니다.&lt;/li&gt;
  &lt;li&gt;게임에 대한 애착이 영웅/전설 획득과 플레이 변화에 동시에 영향을 주기 때문에, 실제로 영웅/전설 획득과 플레이 변화 간에 인과 관계가 없더라도 “상관 관계”가 생기게 됩니다.&lt;/li&gt;
  &lt;li&gt;즉, 실제로는 영웅/전설 획득으로 인해 플레이 변화 차이가 발생한 것이 아님에도 불구하고, 영웅/전설 획득이 플레이 변화를 야기하는 원인이다라는 잘못된 판단을 하게 될 수 있습니다.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;이처럼 교란 변수는 인과 관계 추정에 혼란을 야기하는 요소입니다. 교란 변수의 영향을 제거하지 못한 채 단순히 실험군, 대조군의 “플레이 변화” 차이를 통해 인과 효과를 추정하려 한다면 잘못된 결과를 얻을 수 있습니다.&lt;/p&gt;

&lt;p&gt;그렇다면 교란 변수에 의한 “상관 관계”를 제거하고 우리가 원하는 인과 효과를 추정하려면 어떻게 해야할까요? 바로 교란 변수를 통제한 후 두 집단의 “플레이 변화”를 비교하면 됩니다. 여기서 교란 변수를 통제한다는 것은 교란 변수의 값이 같은 샘플끼리 묶어서 비교한다는 것과 같습니다.(이렇게 하면 공정한 비교가 가능해지겠죠!) 실제 분석 시에는 추정 모델에 교란 변수를 통제 변수로 추가하는 방법을 사용합니다.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;교란 변수를 통제해야 하는 이유는 Appendix에 수식으로 설명되어 있으니 참고하시면 좋을 것 같습니다.&lt;/em&gt;&lt;/p&gt;

&lt;h3 id=&quot;collider&quot;&gt;collider&lt;/h3&gt;

&lt;p&gt;그렇다면 교란 변수로 판단되는 가능한 많은 변수를 통제하여 인과 관계를 추정하면 될까요? 여기서 조심해야할 것이 있습니다. 원인 변수와 결과 변수의 영향을 동시에 받는 변수는 통제 대상에서 제외하여야 합니다.(이런 변수를 collider라고 부릅니다)&lt;/p&gt;

&lt;p align=&quot;center&quot;&gt;&lt;img src=&quot;/assets/works/class_get_causal_analysis/image2_1.png&quot; style=&quot;width:5in&quot; /&gt;&lt;br /&gt;[그림3-1] 원인 변수와 결과 변수에 동시에 영향을 받는 collider&lt;/p&gt;

&lt;p&gt;원인 변수와 결과 변수의 영향이 합쳐져 만들어진 결과인 collider를 통제하는 것은 원인 변수와 결과 변수를 특정 관계로 고립시키는 것입니다. 이것을 조금 더 분석적인 관점에서 설명하면 collider를 통제하면 원인 변수, 결과 변수 사이에 관계가 생기면서 이 관계를 만족하는 샘플들만 남게 된다는 의미입니다. 전체 샘플을 분석에 사용하지 못하고 원인 변수, 결과 변수 간 관계가 있는 샘플들만 사용되기 때문에 결과에 편향을 유발합니다. 이를 collider bias라고 부릅니다.&lt;/p&gt;

&lt;p&gt;다시 말해, collider를 통제하여 인과 관계를 추정하면 원인 변수와 결과 변수 간에 종속관계가 있는 샘플만으로 영향을 추정하는 것이므로 실제 인과 관계가 아닌 상관 관계를 추정하게 됩니다. 그리고 우리는 그 상관 관계를 인과 관계로 오해할 수 있기 때문에 collider는 통제하지 말아야 합니다.&lt;/p&gt;

&lt;p align=&quot;center&quot;&gt;&lt;img src=&quot;/assets/works/class_get_causal_analysis/image2_2.png&quot; style=&quot;width:8.5in&quot; /&gt;&lt;br /&gt;[그림3-2] collider를 통제하면 일부 샘플만 분석에 사용됩니다(collider bias의 개념적 이해)&lt;/p&gt;

&lt;p&gt;&lt;em&gt;collider를 통제하지 말아야 하는 이유는 Appendix에 수식으로 설명되어 있으니 참고하시면 좋을 것 같습니다.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;정리하면, 우리가 보고자 하는 원인 변수(영웅/전설 획득)가 결과 변수(플레이 변화)에 미치는 인과 효과를 추정하기 위해서는 다양한 외부 변수와의 관계를 고려한 후 실제로 통제가 필요한 변수만을 선별하는 과정이 필요합니다.&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;&lt;strong&gt;통제 변수가 될 수 있는 변수 후보를 생각하고 정리해봅니다.&lt;/strong&gt;&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;원인 변수, 결과 변수, 통제 변수 후보 간 인과 관계를 인과 다이어그램으로 표시합니다.&lt;/strong&gt;&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;인과 효과 추정을 위해 인과 다이어그램에서 외부 영향을 제거할 수 있는 변수를 통제 변수로 선정합니다&lt;/strong&gt;&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;선정한 통제 변수들을 통제하여 인과 효과를 추정합니다.(본 분석에서는 회귀식에 독립 변수로 추가하여 통제하였습니다)&lt;/strong&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h1 id=&quot;분석-설계&quot;&gt;&lt;strong&gt;분석 설계&lt;/strong&gt;&lt;/h1&gt;

&lt;p&gt;위에서 정리한 내용을 바탕으로 다음과 같이 인과 분석을 설계하였습니다.&lt;/p&gt;

&lt;ol&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;/ol&gt;

&lt;h2 id=&quot;1-변수-정의&quot;&gt;1. 변수 정의&lt;/h2&gt;

&lt;h4 id=&quot;1-1-원인-변수-및-결과-변수&quot;&gt;1-1. 원인 변수 및 결과 변수&lt;/h4&gt;

&lt;p&gt;원인 변수는 “최초 영웅/전설 획득 여부”로 정의하여 획득 유저는 실험군, 획득하지 못한 유저는 대조군을 분류하였습니다. 샘플 수 확보를 위해 측정 기간은 7일로 설정하였습니다.&lt;/p&gt;

&lt;p&gt;결과 변수(플레이 변화)는 유저가 게임을 열심히 하는지를 반영할 수 있도록 1) 유저가 게임에 투입하는 시간 및 자본, 2) 유저가 즐기는 컨텐츠의 변화 두 가지 관점으로 살펴보았습니다. 유저가 게임에 투입하는 시간 및 자본이 증가하는 경우, 투입하는 시간의 변화가 적더라도 상위 컨텐츠를 이용하게 되는 경우 둘 다 게임을 열심히 하게 되는 것 이라고 정의하였습니다.&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;원인 변수
    &lt;ul&gt;
      &lt;li&gt;최초 영웅/전설 획득 여부 (획득 : 1, 미획득 : 0)&lt;/li&gt;
    &lt;/ul&gt;
  &lt;/li&gt;
  &lt;li&gt;결과 변수(플레이 변화)
    &lt;ul&gt;
      &lt;li&gt;전후 30일 간 접속 일수 변화(이후 30일 간 접속 일수 - 이전 30일 간 접속 일수)&lt;/li&gt;
      &lt;li&gt;전후 30일 간 결제 금액 변화(이후 30일 간 결제 금액 - 이전 30일 간 결제 금액)&lt;/li&gt;
      &lt;li&gt;1/2/3/4주 후 유저 그룹 지표 상향 여부(1주 전 유저 그룹 지표 대비 n주 후 유저 그룹 지표의 상향 여부)
        &lt;ul&gt;
          &lt;li&gt;상향 시 1&lt;/li&gt;
          &lt;li&gt;아닌 경우 0&lt;/li&gt;
        &lt;/ul&gt;
      &lt;/li&gt;
    &lt;/ul&gt;
  &lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;정의한 원인 변수, 결과 변수를 바탕으로 본 분석에서 확인하고자 하는 인과효과를 구체적으로 정의하면 다음과 같습니다.&lt;/p&gt;

&lt;ol&gt;
  &lt;li&gt;최초 영웅/전설 등급 획득이 접속 일수 변화에 원인이 되는가? 그렇다면 어떠한 영향을 주는가?&lt;/li&gt;
  &lt;li&gt;최초 영웅/전설 등급 획득이 결제 금액 변화에 원인이 되는가? 그렇다면 어떠한 영향을 주는가?&lt;/li&gt;
  &lt;li&gt;최초 영웅/전설 등급 획득이 유저 등급 지표 상향에 원인이 되는가? 그렇다면 어떠한 영향을 주는가?&lt;/li&gt;
&lt;/ol&gt;

&lt;h4 id=&quot;1-2-통제-변수-후보&quot;&gt;1-2. 통제 변수 후보&lt;/h4&gt;

&lt;p&gt;통제 변수 후보로는 영웅/전설 획득, 플레이 변화에 영향을 미칠 것으로 예상되는 변수를 탐색하였습니다.&lt;/p&gt;

&lt;p&gt;MMORPG 특성을 고려하였을 때, 유저가 게임을 하며 겪는 전체 과정을 성장 → 정체 → 매몰 비용 으로 나눌 수 있었고 각 단계별로 영웅/전설 획득과 플레이 변화에 영향을 미칠 것으로 예상되는 변수를 탐색하였습니다. 변수 집계 기간은 14일로 하였고(원인 변수 측정 기간 14일 전 ~ 원인 변수 측정 기간 1일 전) 총 9개의 변수를 통제 변수가 될 수 있는 후보로 선정하였습니다.&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;성장 : 유저가 성장하는 재미를 느낄수록 향후 플레이가 증가할 것이다. (no.1 ~ 5)&lt;/li&gt;
  &lt;li&gt;정체 : 유저가 성장하지 못하고 정체해 있을수록 향후 플레이가 감소할 것이다. (no.6)&lt;/li&gt;
  &lt;li&gt;매몰 비용 : 게임에 대한 투자가 많을수록 향후 플레이 감소가 적을 것이다. (no.7 ~ 9)&lt;/li&gt;
&lt;/ul&gt;

&lt;table&gt;
  &lt;thead&gt;
    &lt;tr&gt;
      &lt;th style=&quot;text-align: center&quot;&gt;no&lt;/th&gt;
      &lt;th style=&quot;text-align: center&quot;&gt;변수명&lt;/th&gt;
      &lt;th style=&quot;text-align: center&quot;&gt;영향을 받을 것으로 예상되는 결과 변수&lt;/th&gt;
    &lt;/tr&gt;
  &lt;/thead&gt;
  &lt;tbody&gt;
    &lt;tr&gt;
      &lt;td style=&quot;text-align: center&quot;&gt;1&lt;/td&gt;
      &lt;td style=&quot;text-align: center&quot;&gt;희귀 등급 수&lt;/td&gt;
      &lt;td style=&quot;text-align: center&quot;&gt;접속 일수, 결제금액&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td style=&quot;text-align: center&quot;&gt;2&lt;/td&gt;
      &lt;td style=&quot;text-align: center&quot;&gt;컬렉션 완성 횟수&lt;/td&gt;
      &lt;td style=&quot;text-align: center&quot;&gt;접속 일수&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td style=&quot;text-align: center&quot;&gt;3&lt;/td&gt;
      &lt;td style=&quot;text-align: center&quot;&gt;스킬 습득 횟수&lt;/td&gt;
      &lt;td style=&quot;text-align: center&quot;&gt;접속 일수&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td style=&quot;text-align: center&quot;&gt;4&lt;/td&gt;
      &lt;td style=&quot;text-align: center&quot;&gt;인챈트 성공 횟수&lt;/td&gt;
      &lt;td style=&quot;text-align: center&quot;&gt;접속 일수&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td style=&quot;text-align: center&quot;&gt;5&lt;/td&gt;
      &lt;td style=&quot;text-align: center&quot;&gt;등급 합성 성공 횟수&lt;/td&gt;
      &lt;td style=&quot;text-align: center&quot;&gt;결제 금액&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td style=&quot;text-align: center&quot;&gt;6&lt;/td&gt;
      &lt;td style=&quot;text-align: center&quot;&gt;사망 횟수&lt;/td&gt;
      &lt;td style=&quot;text-align: center&quot;&gt;접속 일수, 결제 금액&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td style=&quot;text-align: center&quot;&gt;7&lt;/td&gt;
      &lt;td style=&quot;text-align: center&quot;&gt;계정 생성 후 경과일&lt;/td&gt;
      &lt;td style=&quot;text-align: center&quot;&gt;접속 일수&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td style=&quot;text-align: center&quot;&gt;8&lt;/td&gt;
      &lt;td style=&quot;text-align: center&quot;&gt;레벨&lt;/td&gt;
      &lt;td style=&quot;text-align: center&quot;&gt;접속 일수&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td style=&quot;text-align: center&quot;&gt;9&lt;/td&gt;
      &lt;td style=&quot;text-align: center&quot;&gt;총 결제액&lt;/td&gt;
      &lt;td style=&quot;text-align: center&quot;&gt;접속 일수, 결제금액&lt;/td&gt;
    &lt;/tr&gt;
  &lt;/tbody&gt;
&lt;/table&gt;

&lt;h2 id=&quot;2-분석-기간-선정&quot;&gt;2. 분석 기간 선정&lt;/h2&gt;

&lt;p&gt;인과 효과가 일부 기간에만 일시적으로 나타나는지 또는 여러 기간에 걸쳐 동일하게 나타나는지 재현성을 확인하기 위해 주요 업데이트 시점을 기준으로 총 5개의 분석 기간을 선정하였습니다.&lt;/p&gt;

&lt;p&gt;주요 업데이트 시점을 기준으로 한 이유는 해당 시점 이후 게임 내 영웅/전설 등급의 가치가 변할 것이고 이에 따라 영웅/전설 획득이 플레이 변화에 미치는 인과 효과도 변할 것이라고 판단하였기 때문입니다. 크게 대규모 업데이트, 소규모 업데이트 두 종류로 나뉘었는데 이에 따라서도 인과 효과가 다르게 나타나는지 확인할 수 있을 것이라 판단하였습니다.&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;대규모 업데이트 : 2개의 분석 기간&lt;/li&gt;
  &lt;li&gt;소규모 업데이트 : 3개의 분석 기간&lt;/li&gt;
&lt;/ul&gt;

&lt;h2 id=&quot;3-대상-선정&quot;&gt;3. 대상 선정&lt;/h2&gt;

&lt;p align=&quot;center&quot;&gt;&lt;img src=&quot;/assets/works/class_get_causal_analysis/image3.png&quot; style=&quot;width:11in&quot; /&gt;&lt;br /&gt;[그림4] 변수 집계 기간 선정을 위한 실험군, 대조군 d시점 매칭&lt;/p&gt;

&lt;p&gt;분석 대상은 측정 기간 전에 영웅/전설 획득을 한 적이 없는 유저 중 측정 기간 동안 게임에 접속하여 획득 시도를 한 유저로 선정하였습니다. 여기서 측정 기간 동안 게임에 접속한 유저라는 조건으로 둔 이유는 역인과 관계로 인한 편향(접속 → 영웅/전설 획득)을 방지하기 위해서입니다. 이러한 조건 없이 대상을 선택하면 대조군에 측정 기간 동안 게임에 접속하지 않아 영웅/전설 획득을 하지 못한 유저가 포함되고, 실험군은 전부 측정 기간에 접속했던 유저들이기 때문에 공정한 비교가 어려울 것입니다.&lt;/p&gt;

&lt;p&gt;이렇게 대상 유저를 선정한 후, 분석을 위해 영웅/전설 획득 여부에 따라 실험군(획득 유저), 대조군(미획득 유저) 두 집단으로 나누었습니다. 마지막으로 변수 집계 기간을 설정하기 위해 측정 기간 7일 간 일자별로 유저를 매칭하였습니다&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;실험군(d시점) : 측정 기간 7일 간 영웅/전설 획득 유저 중 최초 획득을 d시점에 한 유저&lt;/li&gt;
  &lt;li&gt;대조군(d시점) : 측정 기간 7일 간 영웅/전설 미획득 유저 중 최초 획득 시도를 d시점에 한 유저&lt;/li&gt;
&lt;/ul&gt;

&lt;h2 id=&quot;4-세부-그룹&quot;&gt;4. 세부 그룹&lt;/h2&gt;

&lt;p&gt;영웅/전설 획득이 플레이 변화에 미치는 인과 효과가 특정 그룹에 따라 다르게 나타날 수가 있기 때문에 그룹별로 나누어 인과 효과를 추정하는 것이 맞다고 판단하였습니다.&lt;/p&gt;

&lt;p&gt;가령, 유저가 PVP나 전투를 즐기는지에 따라 영웅/전설 획득이 플레이에 변화에 미치는 영향이 다를 수 있습니다. PVP나 전투를 즐기는 유저는 영웅/전설 획득이 매우 중요하여 향후 플레이 증가에 큰 영향을 미칠 수 있고, 사냥 위주 플레이를 즐기는 유저에게는 큰 영향이 없을 수 있습니다.&lt;/p&gt;

&lt;p&gt;그런데 PVP 및 전투 특성에 따라 그룹을 나누지 않고 전체 집단에 대해 인과효과를 추정하게 되면, 두 그룹에서 각각 발생하는 인과 효과가 상쇄되어 인과 효과가 존재하지 않는다는 결과가 나올 수도 있습니다. 따라서, 인과 효과가 다르게 나타날 것으로 생각되는 특성을 기준으로 그룹을 나누어 각각 인과 효과를 추정하였습니다.&lt;/p&gt;

&lt;p&gt;본 분석에서는 전반적인 플레이 성향을 나타내는 “유저 그룹 지표”에 따라 인과 효과가 다르게 나타날 것으로 판단하여 해당 기준으로 그룹을 나누었습니다.&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;그룹1 : 그룹2 대비 사용 시간과 게임 컨텐츠 참여도가 높은 유저(d시점 전 주 기준)&lt;/li&gt;
  &lt;li&gt;그룹2 : 사용 시간이 적고 게임 컨텐츠 참여도가 적은 유저(d시점 전 주 기준)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;그리고, 결제 금액 변화에 대한 인과 효과 추정 시에는 “기존에 결제를 하였는지”에 따라 인과 효과가 다르게 나타날 것으로 판단하여 추가로 유저를 나누어 분석을 진행하였습니다.&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;과금 유저 : 결제 이력이 있는 유저(계정 생성 ~ d-1시점)&lt;/li&gt;
  &lt;li&gt;무과금 유저 : 결제 이력이 없는 유저(계정 생성 ~ d-1시점)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;각 결과 변수에 대한 영웅/전설 획득의 인과 효과 추정 시 세부 그룹을 정리하면 다음과 같습니다.&lt;/p&gt;

&lt;table&gt;
  &lt;thead&gt;
    &lt;tr&gt;
      &lt;th style=&quot;text-align: center&quot;&gt;인과 효과 추정&lt;/th&gt;
      &lt;th style=&quot;text-align: center&quot;&gt;세부 그룹&lt;/th&gt;
    &lt;/tr&gt;
  &lt;/thead&gt;
  &lt;tbody&gt;
    &lt;tr&gt;
      &lt;td style=&quot;text-align: center&quot;&gt;접속 일수 변화&lt;/td&gt;
      &lt;td style=&quot;text-align: center&quot;&gt;유저 그룹 지표 → 총 2개의 그룹&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td style=&quot;text-align: center&quot;&gt;결제 금액 변화&lt;/td&gt;
      &lt;td style=&quot;text-align: center&quot;&gt;유저 그룹 지표 &amp;amp; 기존 결제 여부 → 총 4개의 그룹&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td style=&quot;text-align: center&quot;&gt;유저 그룹 지표 상향 여부&lt;/td&gt;
      &lt;td style=&quot;text-align: center&quot;&gt;유저 그룹 지표 → 총 2개의 그룹&lt;/td&gt;
    &lt;/tr&gt;
  &lt;/tbody&gt;
&lt;/table&gt;

&lt;h2 id=&quot;5-인과-다이어그램을-통한-통제-변수-구성&quot;&gt;5. 인과 다이어그램을 통한 통제 변수 구성&lt;/h2&gt;

&lt;p&gt;인과 효과 추정을 위해서는 통제 변수의 후보(외부 변수)가 원인 변수, 결과 변수와 어떤 관계를 맺고 있는지(confounder, collider) 확인하고 실제로 통제가 필요한 변수를 선별해야 합니다. 그래서 변수 간 전체적인 관계를 파악하는 과정이 필요한데 이 때 인과 다이어그램을 사용합니다.&lt;/p&gt;

&lt;h5 id=&quot;인과-다이어그램-요소&quot;&gt;인과 다이어그램 요소&lt;/h5&gt;

&lt;p&gt;인과 다이어그램은 노드(Node), 엣지(Edge) 두 가지 요소로 구성됩니다. 노드는 변수를 의미하고 엣지는 노드 간 인과 관계를 의미합니다. 엣지는 화살표로 표현되며 엣지가 출발하는 노드가 원인, 도착하는 노드가 결과입니다.&lt;/p&gt;

&lt;h5 id=&quot;인과-다이어그램-표현-방법&quot;&gt;인과 다이어그램 표현 방법&lt;/h5&gt;

&lt;p&gt;예를 들어 “노드2의 원인은 노드1이다”라는 인과 관계를 표현하고자 한다면 노드1 → 노드2로 화살표(엣지)로 연결해주면 됩니다. 그리고 인과 관계가 없다고 판단되는 경우 엣지로 연결하지 않으면 됩니다. 이것을 모든 노드 쌍에 대해 표시해주면 인과 다이어그램이 완성됩니다.&lt;/p&gt;

&lt;p&gt;여기서 주의할 점은 인과 다이어그램 내에서 cycle이 존재하지 않아야 합니다.&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;예를 들어 노드1 → 노드2, 노드2 → 노드3, 노드3 → 노드1 인 관계가 존재하는 경우 노드1 → 노드2 → 노드3 → 노드1… 로 cycle이 생기게 됩니다.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;본 분석에서는 A → B인 경우 B의 집계 시점을 A보다 더 나중으로 설정하여 cycle을 방지하였습니다.&lt;/p&gt;

&lt;h5 id=&quot;인과-다이어그램을-이용한-최종-통제-변수-선정-절차&quot;&gt;인과 다이어그램을 이용한 최종 통제 변수 선정 절차&lt;/h5&gt;

&lt;p&gt;아래의 인과 다이어그램에서는 인과 효과는 C1을 의미하는데 C1을 제외한 원인 변수에서 결과 변수로 이어지는 모든 엣지의 영향을 차단할 수 있는 변수를 선정하는 절차를 의미합니다. 이를 위해 아래와 같이 4가지 단계를 거쳤습니다.&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;step1) 원인 변수, 결과 변수에 직접적인 영향을 주지 않는 인과 관계 무시를 위한 노드 통제&lt;/li&gt;
  &lt;li&gt;step2) 원인 변수 외에 결과 변수에 영향을 주는 노드 통제&lt;/li&gt;
  &lt;li&gt;step3) 원인 변수, 결과 변수에 동시에 영향을 받는 노드(collider)&lt;/li&gt;
  &lt;li&gt;step4) 최종 통제 변수 선정
    &lt;ul&gt;
      &lt;li&gt;step1과 step2에서 선정된 전체 통제 변수들 중 step3에 포함되는 변수는 제외&lt;/li&gt;
    &lt;/ul&gt;
  &lt;/li&gt;
&lt;/ul&gt;

&lt;h3 id=&quot;5-1-접속-일수-변화-인과-다이어그램&quot;&gt;5-1. 접속 일수 변화 인과 다이어그램&lt;/h3&gt;

&lt;p align=&quot;center&quot;&gt;&lt;img src=&quot;/assets/works/class_get_causal_analysis/image4.png&quot; style=&quot;width:9in&quot; /&gt;&lt;br /&gt;[그림5] 접속 일수 변화에 대한 인과 다이어그램&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;step1)&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;영웅/전설 획득, 접속 일수 변화 노드에 연결되지 않는 엣지를 의미합니다
(C3, C4, C5, C6, C12, C13)&lt;/li&gt;
  &lt;li&gt;해당 엣지가 향하는 노드를 통제함으로써 해당 인과 관계를 무시할 수 있습니다.
(희귀 등급 수, 컬렉션 완성 횟수, 스킬 습득 횟수, 인챈트 성공 횟수, 레벨, 총 결제액 노드 통제)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;step2)&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;접속 일수 변화 노드에 직접적으로 인과 영향을 미치는 영웅/전설 획득을 제외한 노드입니다
(C7, C8, C9, C10, C11, C14)&lt;/li&gt;
  &lt;li&gt;해당 노드들은 인과 효과(C1) 이외에 접속 일수 변화에 영향을 미치는 변수이므로 통제가 필요합니다
(컬렉션 완성 횟수, 스킬 습득 횟수, 인챈트 성공 횟수, 레벨, 총 결제액, 사망 횟수 노드 통제)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;step3)&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;영웅/전설 획득, 접속 일수 변화 노드로부터 동시에 영향을 받는 노드를 의미합니다(없음)&lt;/li&gt;
  &lt;li&gt;해당 변수는 위에서 설명한 collider이므로, 통제 변수에서 제외하여야 합니다.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;step4)&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;step1, step2에서 선정된 통제 변수들 중 step3에 포함되는 변수를 제외합니다.
(희귀 등급 수, 컬렉션 완성 횟수, 스킬 습득 횟수, 인챈트 성공 횟수, 레벨, 총 결제액, 사망 횟수)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;이제 인과 효과 추정을 위해 선정된 통제 변수를 포함하여 모델을 구성합니다. 본 분석에서는 원인 변수와 통제 변수를 독립 변수로 하고 결과 변수를 종속 변수로 하는 선형 회귀 모델을 사용하였습니다. 이렇게 모델을 설계하면 통제 변수를 통제한 상태에서 원인 변수가 결과 변수에 미치는 인과 효과를 추정할 수 있습니다.&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;접속 일수 변화 ~ 영웅/전설 획득 여부 + 희귀 등급 수 + 컬렉션 완성 횟수 + 스킬 습득 횟수 + 인챈트 성공 횟수 + 레벨 + 총 결제액 + 사망 횟수&lt;/li&gt;
&lt;/ul&gt;

&lt;h3 id=&quot;5-2-결제-금액-변화-인과-다이어그램&quot;&gt;5-2. 결제 금액 변화 인과 다이어그램&lt;/h3&gt;

&lt;p align=&quot;center&quot;&gt;&lt;img src=&quot;/assets/works/class_get_causal_analysis/image5.png&quot; style=&quot;width:7.5in&quot; /&gt;&lt;br /&gt;[그림6] 결제 금액 변화에 대한 인과 다이어그램&lt;/p&gt;

&lt;p&gt;위의 방식과 마찬가지로 최종 통제 변수를 선정하여 모델을 설계하면 아래와 같습니다.&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;결제 금액 변화 ~ 영웅/전설 획득 여부 + 희귀 등급 수 + 등급 합성 성공횟수 + 총 결제액 + 사망 횟수&lt;/li&gt;
&lt;/ul&gt;

&lt;h3 id=&quot;5-3-유저-그룹-지표-상향--인과-다이어그램&quot;&gt;5-3. 유저 그룹 지표 상향  인과 다이어그램&lt;/h3&gt;

&lt;p&gt;유저 그룹 지표 상향 여부는 접속 일수 변화, 결제 금액 변화와 다르게 변수의 값이 binary type(0 또는 1)이므로, 종속 변수가 binary type일 때 사용하는 로지스틱 회귀 모델을 사용하였습니다.&lt;/p&gt;

&lt;p&gt;그리고 유저 그룹 지표 상향 여부는 접속 일수, 결제 금액 등 전반적인 게임 플레이 활동량에 따라 산출되는 변수이기 때문에 위에서 접속 일수, 결제 금액 변화에서 선정한 통제 변수의 합집합을 통제 변수로 사용하였습니다.&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;유저 그룹 상향 여부 ~ 영웅/전설 획득 여부 + 희귀 등급 수 + 등급 합성 성공횟수 + 컬렉션 완성 횟수 + 스킬 습득 횟수 + 인챈트 성공 횟수 + 레벨 + 총 결제액 + 사망 횟수&lt;/li&gt;
&lt;/ul&gt;

&lt;h1 id=&quot;분석-결과&quot;&gt;&lt;strong&gt;분석 결과&lt;/strong&gt;&lt;/h1&gt;

&lt;p&gt;5개의 기간에 대해 모두 분석을 진행한 결과 인과 효과의 크기는 조금씩 다르지만 공통적으로 “최초 영웅/전설 획득은 유저의 플레이 증가에 유의한 영향을 미친다”라는 결과가 나왔습니다. 그래서 아래에는 업데이트 종류별로 대표 결과를 하나씩 첨부하였습니다.&lt;/p&gt;

&lt;h5 id=&quot;결과-해석-방법&quot;&gt;결과 해석 방법&lt;/h5&gt;

&lt;p&gt;아래의 그래프가 나타내는 것은 영웅/전설을 획득하지 못한 그룹(대조군)에 비해 획득한 그룹(실험군)의 플레이 변화가 1)평균적으로 얼마나 큰지 or 작은지와 2) 신뢰구간을 나타냅니다. 해석 방법은 다음과 같습니다.&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;인과 효과 존재 여부
    &lt;ul&gt;
      &lt;li&gt;신뢰 구간에 0인 값이 포함되지 않는다면 “인과 효과가 있다(원인이 된다)”라고 해석합니다.&lt;/li&gt;
      &lt;li&gt;반대로 포함된다면 “인과 효과가 없다”라고 해석합니다.&lt;/li&gt;
    &lt;/ul&gt;
  &lt;/li&gt;
  &lt;li&gt;(인과 효과가 존재하는 경우) 인과 효과 방향 및 크기 확인
    &lt;ul&gt;
      &lt;li&gt;신뢰 구간이 0인 값 우측에 있으면 “영웅/전설 획득은 플레이 변화의 증가에 영향을 준다” 라고 해석합니다.&lt;/li&gt;
      &lt;li&gt;반대로 좌측에 있으면 “영웅/전설 획득은 플레이 변화의 감소에 영향을 준다”라고 해석합니다.&lt;/li&gt;
    &lt;/ul&gt;
  &lt;/li&gt;
&lt;/ul&gt;

&lt;h3 id=&quot;5-1-접속-일수-변화에-미치는-인과-효과-추정&quot;&gt;5-1. 접속 일수 변화에 미치는 인과 효과 추정&lt;/h3&gt;

&lt;table&gt;
  &lt;thead&gt;
    &lt;tr&gt;
      &lt;th style=&quot;text-align: center&quot;&gt;대규모 업데이트 이후&lt;/th&gt;
      &lt;th style=&quot;text-align: center&quot;&gt;소규모 업데이트 이후&lt;/th&gt;
    &lt;/tr&gt;
  &lt;/thead&gt;
  &lt;tbody&gt;
    &lt;tr&gt;
      &lt;td style=&quot;text-align: center&quot;&gt;&lt;img src=&quot;/assets/works/class_get_causal_analysis/image6_1.png&quot; style=&quot;width:6in&quot; /&gt;&lt;/td&gt;
      &lt;td style=&quot;text-align: center&quot;&gt;&lt;img src=&quot;/assets/works/class_get_causal_analysis/image6_2.png&quot; style=&quot;width:6in&quot; /&gt;&lt;/td&gt;
    &lt;/tr&gt;
  &lt;/tbody&gt;
&lt;/table&gt;

&lt;p&gt;모든 유저 그룹에서 최초 영웅/전설 획득은 30일 간 플레이 증가에 유의한 영향을 미치는 것으로 나타납니다.&lt;/p&gt;

&lt;p&gt;여기서 유저 그룹 간 영향의 크기 차이가 존재하는데, 대규모 업데이트 이후(왼쪽 그래프) 그룹1에서는 인과 효과가 약 1.7일인 반면 게임 참여도가 낮은 그룹2에서는 5.1일로 더 큰 효과가 나타나는 것을 알 수 있습니다. 이를 보았을 때 기존에 접속을 자주 하지 않던 유저에게 최초 영웅/전설 획득은 더 자주 접속하게 하는 요인이 된다는 것을 알 수 있습니다.&lt;/p&gt;

&lt;p&gt;그리고, 소규모 업데이트 이후보다 대규모 업데이트 이후에서 영향이 크게 나타난 것을 알 수 있습니다.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;→ 영웅/전설 획득은 유저의 접속을 독려하는 효과가 있으며, 특히 게임 참여도가 낮은 그룹에 효과가 크다&lt;/strong&gt;&lt;/p&gt;

&lt;h4 id=&quot;참고를-위한-단순-비교인과-효과가-아닌-상관-관계&quot;&gt;참고를 위한 단순 비교(인과 효과가 아닌 상관 관계)&lt;/h4&gt;

&lt;p&gt;참고로 외부 영향을 통제하지 않은 단순 비교의 결과는 아래와 같습니다. 여기서 단순 비교란 통제 변수 없이 영웅/전설 획득으로 분류한 집단(실험군, 대조군) 간 접속 일수 변화의 평균 값을 비교한 “상관 관계”를 의미합니다. 측정 시 그룹1, 그룹2 전체 유저 대상으로 측정하였는데 그 이유는 전 주의 유저 그룹인 그룹1, 그룹2를 나누어 접속 일수 변화를 비교하는 방식 또한 외부 영향을 통제하는 것 중 하나이기 때문입니다.&lt;/p&gt;

&lt;p&gt;셀 안의 숫자는 대조군에 비해 실험군의 접속 일수 변화의 평균이 얼마나 높은지 나타낸 것이며, 우측 괄호 안의 숫자는 대조군의 접속 일수 변화의 평균 값을 의미합니다. (괄호 안의 값이 양수인 경우 대조군의 평균 접속 일수가 이전 30일 대비 증가함, 반대의 경우 대조군의 평균 접속 일수가 감소함)&lt;/p&gt;

&lt;p&gt;모두 실험군이 대조군에 비해 통계적으로 접속 일수 변화의 평균 차이가 유의하다는 결과가 나왔습니다.&lt;/p&gt;

&lt;table&gt;
  &lt;thead&gt;
    &lt;tr&gt;
      &lt;th style=&quot;text-align: center&quot;&gt;대규모 업데이트 이후&lt;/th&gt;
      &lt;th style=&quot;text-align: center&quot;&gt;소규모 업데이트 이후&lt;/th&gt;
    &lt;/tr&gt;
  &lt;/thead&gt;
  &lt;tbody&gt;
    &lt;tr&gt;
      &lt;td style=&quot;text-align: center&quot;&gt;2.01일 높음 (-1.37일)&lt;/td&gt;
      &lt;td style=&quot;text-align: center&quot;&gt;2.30일 높음 (-0.01일)&lt;/td&gt;
    &lt;/tr&gt;
  &lt;/tbody&gt;
&lt;/table&gt;

&lt;h3 id=&quot;5-2-결제-금액-변화에-미치는-인과-효과-추정&quot;&gt;5-2. 결제 금액 변화에 미치는 인과 효과 추정&lt;/h3&gt;

&lt;table&gt;
  &lt;thead&gt;
    &lt;tr&gt;
      &lt;th style=&quot;text-align: center&quot;&gt;대규모 업데이트 이후&lt;/th&gt;
      &lt;th style=&quot;text-align: center&quot;&gt;소규모 업데이트 이후&lt;/th&gt;
    &lt;/tr&gt;
  &lt;/thead&gt;
  &lt;tbody&gt;
    &lt;tr&gt;
      &lt;td style=&quot;text-align: center&quot;&gt;&lt;img src=&quot;/assets/works/class_get_causal_analysis/image7_1.png&quot; style=&quot;width:6in&quot; /&gt;&lt;/td&gt;
      &lt;td style=&quot;text-align: center&quot;&gt;&lt;img src=&quot;/assets/works/class_get_causal_analysis/image7_2.png&quot; style=&quot;width:6in&quot; /&gt;&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td style=&quot;text-align: center&quot;&gt;&lt;img src=&quot;/assets/works/class_get_causal_analysis/image7_3.png&quot; style=&quot;width:6in&quot; /&gt;&lt;/td&gt;
      &lt;td style=&quot;text-align: center&quot;&gt;&lt;img src=&quot;/assets/works/class_get_causal_analysis/image7_4.png&quot; style=&quot;width:6in&quot; /&gt;&lt;/td&gt;
    &lt;/tr&gt;
  &lt;/tbody&gt;
&lt;/table&gt;

&lt;p&gt;(x축의 범위는 과금 그룹이 무과금 그룹에 비해 5배 넓습니다)&lt;/p&gt;

&lt;p&gt;결제 금액 변화에 대해서는 과대 추정을 방지하기 위해 전후 결제 금액이 증가한 유저(이후 30일 간 결제 금액 &amp;gt; 이전 30일 간 결제금액) 대상으로 이상치인 유저를 제외한 후 분석을 진행하였습니다.&lt;/p&gt;

&lt;p&gt;모든 유저 그룹에서 최초 영웅/전설 획득은 30일 간 결제 금액 증가에 유의한 영향을 미치는 것으로 나타납니다. 여기서 주목할 점은 무과금과 과금 그룹에 미치는 인과 효과의 크기 차이입니다. 대규모 업데이트 이후(왼쪽 그래프) 그룹1(무과금)에 비해 그룹1(과금)의 인과 효과는 약 3,980%(39.8배)가, 그룹2(무과금)에 비해 그룹2(과금)의 인과 효과는 3,830%(38.3배)인 것으로 보아 과금 그룹에서 더 큰 영향을 미치는 것을 알 수 있습니다.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;→ 영웅/전설 획득은 유저의 결제 증가에 영향을 미치며, 과금 그룹에서 효과가 더 크게 나타난다&lt;/strong&gt;&lt;/p&gt;

&lt;h3 id=&quot;5-3-유저-그룹-상향-여부에-미치는-인과-효과-추정&quot;&gt;5-3. 유저 그룹 상향 여부에 미치는 인과 효과 추정&lt;/h3&gt;

&lt;table&gt;
  &lt;thead&gt;
    &lt;tr&gt;
      &lt;th style=&quot;text-align: center&quot;&gt;대규모 업데이트 이후&lt;/th&gt;
      &lt;th style=&quot;text-align: center&quot;&gt;소규모 업데이트 이후&lt;/th&gt;
    &lt;/tr&gt;
  &lt;/thead&gt;
  &lt;tbody&gt;
    &lt;tr&gt;
      &lt;td style=&quot;text-align: center&quot;&gt;&lt;img src=&quot;/assets/works/class_get_causal_analysis/image8_1.png&quot; style=&quot;width:6in&quot; /&gt;&lt;/td&gt;
      &lt;td style=&quot;text-align: center&quot;&gt;&lt;img src=&quot;/assets/works/class_get_causal_analysis/image8_2.png&quot; style=&quot;width:6in&quot; /&gt;&lt;/td&gt;
    &lt;/tr&gt;
  &lt;/tbody&gt;
&lt;/table&gt;

&lt;p&gt;사용한 로지스틱 회귀 모델에서 회귀 계수는 로그 오즈비(영웅/전설 미획득 대비 획득 시 유저 그룹 지표가 상향될 확률이 몇 배나 더 큰지)를 나타냅니다. 본 분석에서는 결과 해석에 편의를 위해 유저 그룹 지표가 상향될 확률 자체에 대해 인과 효과를 해석하였습니다.(유저 그룹 지표 상향 확률에 대해 영웅/전설 획득 여부가 미치는 영향의 미분값을 이용해 marginal effect 를 구하였습니다.) 즉, 그래프가 의미하는 것은 유저 그룹 상향에 확률에 대한 최초 영웅/전설 획득의 인과 효과를 의미합니다.&lt;/p&gt;

&lt;p&gt;모든 유저 그룹에서 최초 영웅/전설 획득은 유저 그룹 상향 확률 증가에 유의한 영향을 미치는 것으로 나타납니다. 여기서 유저 그룹 간 영향 크기 차이가 존재하는데 대규모 업데이트 이후(왼쪽 그래프) 그룹1에서는 인과효과가 1.3%p인 반면 게임 참여도가 낮은 그룹2에서는 10.5%p로 더 큰 효과가 나타나는 것을 알 수 있습니다.&lt;/p&gt;

&lt;p&gt;유저 그룹 상향은 전반적인 게임 참여도 및 활동량 증가를 의미하므로, 아래와 같이 해석할 수 있습니다.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;→ 영웅/전설 획득은 유저의 전반적인 게임 플레이 증가에 영향을 미치며, 특히 게임 참여도가 저조한 그룹에 효과가 크게 나타난다&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;세 결과를 종합하면, 다음과 같습니다.&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;&lt;strong&gt;최초 영웅/전설 획득은 30일 간 전반적인 게임 플레이 증가(접속 일수, 결제 금액, 게임 참여도)에 유의한 영향을 미친다.&lt;/strong&gt;&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;소규모 업데이트보다 대규모 업데이트 이후 효과의 크기가 더 크게 나타난다.&lt;/strong&gt;&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;게임 참여도가 저조하였던 그룹과 기존에 결제를 한 그룹에게 효과가 더 크게 나타난다.&lt;/strong&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h1 id=&quot;마치며&quot;&gt;&lt;strong&gt;마치며&lt;/strong&gt;&lt;/h1&gt;

&lt;p&gt;지금까지 게임 내 컨텐츠가 유저의 플레이 변화에 원인이 되는지 인과 분석에 대해 소개 드렸습니다. 최초 영웅/전설 획득 자체가 유저의 단기간(30일) 플레이 증대에 긍정적인 영향을 주었고, 기존의 유저 성향 및 게임 참여도에 따라 영향이 다르게 나타난 것을 알 수 있었습니다.&lt;/p&gt;

&lt;p&gt;본 포스팅에서는 주로 분석 기법 및 결과에 대한 내용을 다루었지만 실제로 분석을 진행하며 가장 중요하다고 느낀 것은 도메인 지식입니다. 본 분석에서 핵심이라고 할 수 있는 인과 다이어그램 자체가 도메인에 대한 이해를 바탕으로 설계되어야 하기 때문이죠. 예를 들어 도메인 지식이 없이 실제로 collider인 변수를 confounder로 착각하여 분석한다면 실제 인과 효과가 아닌 편향된 결과를 얻었을 것입니다.&lt;/p&gt;

&lt;p&gt;즉, 분석 기법이 아무리 훌륭하여도 도메인 지식이 없다면 설계 단계부터 잘못되어 있을 가능성이 높고 결국 잘못된 결과를 얻게 됩니다. 그만큼 설계 부분에 많은 고민과 시간을 투자해야 하는 것이 중요하다는 것을 느낄 수 있었습니다.&lt;/p&gt;

&lt;p&gt;추가로 본 분석을 진행하며 영웅/전설을 획득하였더라도 유저가 만족하지 않는 경우 플레이 변화가 다르게 나타나지 않을까? 라는 궁금증이 생겼습니다. 최초 획득한 영웅/전설 등급의 직업이 기존에 사용하던 직업과 다른 경우 장비 및 스탯을 다시 맞춰야 하는 경우가 발생하기 때문에 획득한 영웅/전설 등급의 만족도에 대한 분석도 가치가 있다고 판단하였습니다. 그래서 2탄으로는 “최초 획득한 영웅/전설 등급에 대한 만족도는 플레이 변화에 영향을 미칠까?”라는 주제로 찾아 뵙겠습니다.&lt;/p&gt;

&lt;p&gt;다소 장황하게 설명하였는데 긴 글 읽어주셔서 감사합니다 :)&lt;/p&gt;

&lt;h1 id=&quot;appendix&quot;&gt;&lt;strong&gt;Appendix.&lt;/strong&gt;&lt;/h1&gt;

&lt;h4 id=&quot;인과효과-추정-시-고려사항&quot;&gt;인과효과 추정 시 고려사항&lt;/h4&gt;

&lt;p&gt;&lt;em&gt;아래 그림들의 출처는 &lt;a href=&quot;https://www.youtube.com/c/BradyNealCausalInference/featured&quot;&gt;(YOUTUBE) Brady Neal - Causal Inference&lt;/a&gt;입니다.&lt;/em&gt;&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;T : 원인변수&lt;/li&gt;
  &lt;li&gt;Y : 결과변수&lt;/li&gt;
  &lt;li&gt;X : 외생 변수&lt;/li&gt;
&lt;/ul&gt;

&lt;p align=&quot;left&quot;&gt;&lt;img src=&quot;/assets/works/class_get_causal_analysis/image9_1.png&quot; style=&quot;width:2.5in&quot; /&gt;&lt;br /&gt;&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;T → Y : Causal Association (분석가가 알고 싶어하는 T와 Y 사이의 인과효과)&lt;/li&gt;
  &lt;li&gt;T ← X → Y : Non-Causal Association (인과효과 이외에 T와 Y를 연결하는 path)
    &lt;ul&gt;
      &lt;li&gt;path : edge의 방향에 상관 없이 두 노드 간 연결될 수 있는 경로&lt;/li&gt;
    &lt;/ul&gt;
  &lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;T가 Y에 미치는 인과효과 추정을 위해서는 non-causal association을 block해야 합니다&lt;/p&gt;

&lt;p align=&quot;left&quot;&gt;&lt;img src=&quot;/assets/works/class_get_causal_analysis/image9_2.png&quot; style=&quot;width:2.5in&quot; /&gt;&lt;br /&gt;&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;block : 통제를 통해 non-causal association 상에서 T와 Y의 관계를 독립으로 만들어 주는 것&lt;/li&gt;
  &lt;li&gt;노드 간의 관계(화살표 방향)에 따라 통제하는 방법이 다름&lt;/li&gt;
&lt;/ul&gt;

&lt;h4 id=&quot;인과-그래프에서-association의-종류&quot;&gt;인과 그래프에서 association의 종류&lt;/h4&gt;

&lt;p&gt;x1과 x3의 association이 형성되는 세 가지 경우&lt;/p&gt;

&lt;ol&gt;
  &lt;li&gt;
    &lt;p&gt;Chain&lt;/p&gt;

    &lt;p align=&quot;left&quot;&gt;&lt;img src=&quot;/assets/works/class_get_causal_analysis/image9_3.png&quot; style=&quot;width:2.5in&quot; /&gt;&lt;br /&gt;&lt;/p&gt;
  &lt;/li&gt;
  &lt;li&gt;
    &lt;p&gt;Fork&lt;/p&gt;

    &lt;p align=&quot;left&quot;&gt;&lt;img src=&quot;/assets/works/class_get_causal_analysis/image9_4.png&quot; style=&quot;width:2.5in&quot; /&gt;&lt;br /&gt;&lt;/p&gt;

    &lt;ul&gt;
      &lt;li&gt;\(x_2\)는 \(x_1\)과 \(x_3\)에 &lt;strong&gt;영향을 줌&lt;/strong&gt; : \(x_2\)는 \(x_1\)과 \(x_3\)의 Confounder&lt;/li&gt;
    &lt;/ul&gt;
  &lt;/li&gt;
  &lt;li&gt;
    &lt;p&gt;Immorality&lt;/p&gt;

    &lt;p align=&quot;left&quot;&gt;&lt;img src=&quot;/assets/works/class_get_causal_analysis/image9_5.png&quot; style=&quot;width:2.5in&quot; /&gt;&lt;br /&gt;&lt;/p&gt;

    &lt;ul&gt;
      &lt;li&gt;\(x_2\)는 \(x_1\)과 \(x_3\)의 &lt;strong&gt;영향을 받음&lt;/strong&gt; : \(x_2\)는 \(x_1\)과 \(x_3\)의 collider&lt;/li&gt;
    &lt;/ul&gt;
  &lt;/li&gt;
&lt;/ol&gt;

&lt;h4 id=&quot;non-causal-association의-영향을-제거block&quot;&gt;non-causal association의 영향을 제거(block)&lt;/h4&gt;

&lt;p&gt;위의 Chain, Fork, Immorality의 각 관계에서 non-causal association의 영향을 제거하기 위해 통제할 변수를 선정&lt;/p&gt;

&lt;p&gt;(위의 예시에서 \(x_1\)를 원인변수, \(x_3\)를 결과변수로 간주함)&lt;/p&gt;

&lt;h5 id=&quot;확인-절차&quot;&gt;확인 절차&lt;/h5&gt;

&lt;p&gt;1) bayesian network factorization을 통해 확률을 계산&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;
    &lt;p&gt;chain rule of probability에 local markov assumption을 적용한 개념&lt;/p&gt;

    &lt;ul&gt;
      &lt;li&gt;chain rule of probability
        &lt;ul&gt;
          &lt;li&gt;
\[p(x_1, x_2, ..., x_n) = \prod_i {p(x_i | x_{i-1}, x_{i-2}, ..., x_1)}\]
          &lt;/li&gt;
        &lt;/ul&gt;
      &lt;/li&gt;
      &lt;li&gt;local markov assumption
        &lt;ul&gt;
          &lt;li&gt;DAG에서 노드 X(\(x_i\))는 부모노드(\(pa_i\))에게만 영향을 받음&lt;/li&gt;
          &lt;li&gt;자식 노드에게는 영향을 받지 않음&lt;/li&gt;
        &lt;/ul&gt;
      &lt;/li&gt;
      &lt;li&gt;&lt;u&gt;bayesian network factorization&lt;/u&gt;
        &lt;ul&gt;
          &lt;li&gt;
\[p(x_1, x_2, ..., x_n) =  \prod_i {p(x_i | pa_i)}\]
          &lt;/li&gt;
        &lt;/ul&gt;
      &lt;/li&gt;
    &lt;/ul&gt;
  &lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;2) 변수 통제(조건부 확률) 시 원인 변수와 결과 변수의 독립성 확인&lt;/p&gt;

&lt;h5 id=&quot;독립성-만족을-위한-통제-변수-확인-결과&quot;&gt;&lt;strong&gt;독립성 만족을 위한 통제 변수 확인 결과&lt;/strong&gt;&lt;/h5&gt;

&lt;ol&gt;
  &lt;li&gt;
    &lt;p&gt;&lt;strong&gt;Chain에 의한 non-causal association&lt;/strong&gt;&lt;/p&gt;

    &lt;ul&gt;
      &lt;li&gt;
        &lt;p&gt;bayesian network factorization&lt;/p&gt;

        &lt;ul&gt;
          &lt;li&gt;
\[p(x_1,x_2,x_3) = p(x_1) * p(x_2 | x_1) *  p(x_3|x_2)\]
          &lt;/li&gt;
        &lt;/ul&gt;
      &lt;/li&gt;
      &lt;li&gt;
        &lt;p&gt;x2 통제 시 x1과 x3의 독립성 확인&lt;/p&gt;

        &lt;ul&gt;
          &lt;li&gt;
\[p({x_1, x_3} | x_2) = {p(x_1, x_2, x_3) \over p(x_2)} = \frac{p(x_1) * p(x_2 | x_1) * p(x_3 | x_2)}{p(x_2)}\]
          &lt;/li&gt;
          &lt;li&gt;
\[\frac{p(x_1) * p(x_2 | x_1) * p(x_3 | x_2)}{p(x_2)} = \frac{p(x_2, x_1) * p(x_3|x_2)}{p(x_2)} = \frac{p(x_2, x_1)}{p(x_2)} * p(x_3|x_2) = p(x_1 | x_2) * p(x_3 | x_2)\]
          &lt;/li&gt;
          &lt;li&gt;
\[\newcommand{\indep}{\perp \!\!\! \perp}
x_1 \indep x_3 | x_2\]

            &lt;p&gt;(x2를 통제하는 경우, x1과 x3가 독립이 됨)&lt;/p&gt;
          &lt;/li&gt;
          &lt;li&gt;&lt;strong&gt;즉, chain의 경우 중간에 위치한 노드를 통제하면 해당 non-causal association(x1 → x3)가 block 됨&lt;/strong&gt;&lt;/li&gt;
        &lt;/ul&gt;
      &lt;/li&gt;
    &lt;/ul&gt;
  &lt;/li&gt;
  &lt;li&gt;
    &lt;p&gt;&lt;strong&gt;Fork에 의한 non-causal association&lt;/strong&gt;&lt;/p&gt;

    &lt;ul&gt;
      &lt;li&gt;bayesian network factorization
        &lt;ul&gt;
          &lt;li&gt;
\[p(x_1,x_2,x_3) = p(x_2) * p(x_1 | x_2) *  p(x_3|x_2)\]
          &lt;/li&gt;
        &lt;/ul&gt;
      &lt;/li&gt;
      &lt;li&gt;x2 통제 시 x1과 x3의 독립성 확인
        &lt;ul&gt;
          &lt;li&gt;
\[p(x_1, x_3 | x_2) = \frac{p(x_1, x_2, x_3)}{p(x_2)} = \frac{p(x_2) * p(x_1 | x_2) *  p(x_3|x_2)}{p(x_2)}\]
          &lt;/li&gt;
          &lt;li&gt;
\[\frac{p(x_2) * p(x_1 | x_2) *  p(x_3|x_2)}{p(x_2)} = \frac{p(x_1, x_2) * p(x_3|x_2)}{p(x_2)} = \frac{p(x_1, x_2)}{p(x_2)} * p(x_3|x_2) = p(x_1 | x_2) * p(x_3 | x_2)\]
          &lt;/li&gt;
          &lt;li&gt;
\[\newcommand{\indep}{\perp \!\!\! \perp}
x_1 \indep x_3 | x_2\]

            &lt;p&gt;(x2를 통제하는 경우, x1과 x3가 독립이 됨)&lt;/p&gt;
          &lt;/li&gt;
          &lt;li&gt;&lt;strong&gt;즉, fork의 경우 confounder를 통제하면 해당 non-causal association(x1 → x3)가 block 됨&lt;/strong&gt;&lt;/li&gt;
        &lt;/ul&gt;
      &lt;/li&gt;
    &lt;/ul&gt;
  &lt;/li&gt;
  &lt;li&gt;
    &lt;p&gt;&lt;strong&gt;Immorality에 의한 non-causal association&lt;/strong&gt;&lt;/p&gt;

    &lt;ul&gt;
      &lt;li&gt;bayesian network factorization
        &lt;ul&gt;
          &lt;li&gt;
\[p(x_1,x_2,x_3) = p(x_1) * p(x_3) *  p(x_2|x_1, x_3)\]
          &lt;/li&gt;
        &lt;/ul&gt;
      &lt;/li&gt;
      &lt;li&gt;x2 통제 시 x1과 x3의 독립성 확인
        &lt;ul&gt;
          &lt;li&gt;
\[p(x_1, x_3 | x_2) = \frac{p(x_1, x_2, x_3)}{p(x_2)} = \frac{p(x_1) * p(x_3) *  p(x_2 | x_1, x_3)}{p(x_2)}\]
          &lt;/li&gt;
          &lt;li&gt;\(\newcommand{\indep}{\perp \!\!\! \perp}
x_1 \ \not\indep x_3 | x_2\) (&lt;strong&gt;x2를 통제하는 경우, x1과 x2는 독립이 아님&lt;/strong&gt;)&lt;/li&gt;
        &lt;/ul&gt;
      &lt;/li&gt;
      &lt;li&gt;x2를 통제하지 않고 x1, x3의 독립성 확인
        &lt;ul&gt;
          &lt;li&gt;
\[p(x_1,x_3) = \sum_{x_2} {p(x_1, x_3, x_2)} = \sum_{x_2} {p(x_1) * p(x_3) *  p(x_2 | x_1, x_3)}\]

            &lt;p&gt;(marginalizing)&lt;/p&gt;
          &lt;/li&gt;
          &lt;li&gt;
            &lt;p&gt;이것을 풀어쓰면&lt;/p&gt;
          &lt;/li&gt;
          &lt;li&gt;
\[\sum_{x_2} {p(x_1) * p(x_3) * p(x_2|x_1, x_3)} =p(x_1) * p(x_3) * \sum_{x_2} { p(x_2|x_1, x_3) } = p(x_1) * p(x_3) * 1 = p(x_1) * p(x_3)\]
          &lt;/li&gt;
          &lt;li&gt;
            &lt;p&gt;즉, \(p(x_1, x_3) = p(x_1) * p(x_3)\)&lt;/p&gt;
          &lt;/li&gt;
          &lt;li&gt;
\[\newcommand{\indep}{\perp \!\!\! \perp}
x_1 \indep x_3\]

            &lt;p&gt;(x2를 통제하지 않는 경우, x1과 x3가 독립이 됨)&lt;/p&gt;
          &lt;/li&gt;
          &lt;li&gt;즉, immorality의 경우 colllider를 통제하지 않을 때 이를 제외한 두 노드는 독립임
            &lt;ul&gt;
              &lt;li&gt;&lt;strong&gt;collider를 통제하면 해당 non-causal association이 발생(collider bias)&lt;/strong&gt;&lt;/li&gt;
              &lt;li&gt;&lt;strong&gt;따라서, immorality에 의한 원인 변수와 결과 변수의 association을 제거하기 위해서는 collider를 통제하지 않고 그대로 두어야 함&lt;/strong&gt;&lt;/li&gt;
            &lt;/ul&gt;
          &lt;/li&gt;
        &lt;/ul&gt;
      &lt;/li&gt;
    &lt;/ul&gt;
  &lt;/li&gt;
&lt;/ol&gt;
</description>
        <pubDate>Thu, 13 May 2021 15:50:00 +0000</pubDate>
        <link>https://danbi-ncsoft.github.io//works/2021/05/13/class_get_causal_analysis.html</link>
        <guid isPermaLink="true">https://danbi-ncsoft.github.io//works/2021/05/13/class_get_causal_analysis.html</guid>
        
        
        <category>Works</category>
        
      </item>
    
      <item>
        <title>Transformer를 이용해 대량의 게임 데이터를 임베딩 해보자!</title>
        <description>&lt;h2 id=&quot;introduction&quot;&gt;Introduction&lt;/h2&gt;

&lt;p&gt;NCSOFT Data Center에는 대량의 로그 데이터가 쌓여있습니다. 이를 활용해 Data Center의 분석가들은 다양한 주제의 분석 목표를 설정하고 인사이트를 도출하고 모델을 생성합니다. 이렇게 진행되고 있는 프로젝트들의 대부분은 다양한 종류의 로그 데이터 중 유저 행동 로그를 사용하여 주로 진행됩니다. 그렇다면, 많은 분석가들이 사용하는 유저 행동 로그 데이터를 그 특징과 정보를 잘 함축시킬 수 있게 임베딩 해 놓는다면 여러 프로젝트에서 분석하고 모델링하는데 좋지 않을까? 라는 생각에서 이 글은 출발하게 되었습니다.
  기본적으로 로그 데이터는 Sequential Data입니다. 시간에 따라 순차적으로 쌓이는 데이터이죠. 그렇다면 임베딩을 위하여 우선적으로 생각해볼 수 있는건 순차적인 데이터를 처리하는 시계열 분석 방법(ARIMA, 지수평활법) 혹은 RNN/LSTM 기반의 딥러닝 모델들이 있을 것입니다. Sequence Autoencoder도 그 중 하나가 될 수 있겠네요. 하지만 이번 시간에는 대량의 로그 데이터를 사용하다보니 전통적인 통계적 방법론보다는 대량의 데이터를 처리하는데 강점을 가진 딥러닝에 집중해보려 합니다.&lt;/p&gt;

&lt;h2 id=&quot;why-transformer&quot;&gt;Why Transformer?&lt;/h2&gt;

&lt;p&gt;유저의 행동 로그 데이터를 자세히 살펴보면 정말 다양한 종류(게임 접속, 맵 이동, 아이템 획득, 게임머니 증가/감소 등)가 있습니다. 그렇기에 처음 시도한 방법은 모든 로그 데이터를 사용하기보다는 유저의 지역 이동 (ex) A지역 1층 → A지역 2층 → …) 로그만을 이용하여 유저별로 임베딩하는 것이었습니다. 임베딩하기 위해서 시도했던 알고리즘은 LSTM 기반의 Seq2Seq-Autoencoder 입니다. Seq2Seq-Autoencoder 를 이용하여 추출한 임베딩 벡터를 사용하여 다른 프로젝트에 적용하였을 때, 기대한만큼의 좋은 성능을 나타냄을 확인할 수 있었습니다. 다만, LSTM 기반의 Seq2Seq-Autoencoder를 사용하니 다음과 같은 문제가 발생하였습니다.&lt;/p&gt;

&lt;ol&gt;
  &lt;li&gt;LSTM 기반 모델의 특성상 Layer내에서 GPU를 사용한 병렬 처리가 효율적으로 이루어지지 않다보니, 학습 및 추론 시간이 무척 길어지는 현상이 발생했습니다. 학습에 사용할 사전(Dictionary)의 크기를 조절하여 이런 부분을 개선했지만 지역 이동 로그만 사용하지 않고 다른 종류의 로그들까지 같이 사용할 경우에는 사전의 크기가 매우 커져, 학습 소요 시간이 기하급수적으로 증가할 것으로 예상됩니다.&lt;/li&gt;
  &lt;li&gt;임베딩 벡터(Context Vector)가 고정 길이로 추출되다보니, 임베딩 벡터를 다른 모델에 입력으로 넣는 것은 유용했으나, 역시 입력 차원보다 작은 차원으로 임베딩을 하다보니 정보 손실이 일어나는 부분이 존재했습니다. 대부분의 경우는 비슷한 행동(지역 이동)을 한 유저끼리는 임베딩 벡터간의 거리(Cosine similarity)가 가깝게 나왔으나, 흔히 말하는 작업장 캐릭터와 같은 비슷하거나 이상 행동을 반복하는 부정사용자와 전혀 다른 행동을 하는 일반 유저간의 임베딩 벡터간 거리가 가깝게 나타나는 경우도 발생했습니다.&lt;/li&gt;
  &lt;li&gt;유저의 하루 동안의 로그 데이터는 보통 몇천~몇만개까지 쌓입니다. 우리의 목표는 유저별로 임베딩 데이터를 뽑아내는 것이기 때문에 제대로 유저의 특성이 반영된 벡터를 뽑아내려면 이 로그 데이터를 최대한 많이 사용하는 것이 유리할 수 밖에 없습니다. 물론, 필요없는 로그 데이터의 경우는 삭제를 하거나 도메인 지식에 기반하여 데이터 처리를 진행할 수 있지만 여전히 Input Data의 시퀀스 길이는 매우 길 수 밖에 없습니다. 그러다보니 매우 긴 시퀀스 길이를 입력으로 넣었을 경우 학습/추론 시간도 길어지고, 원하지 않는 방향으로 임베딩 벡터가 추출되기도 하였습니다. 이는 이전 Timestamp들을 참조하는 RNN/LSTM의 문제인 Long-Term-Dependency Problem을 그 원인으로 추측했습니다.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;위의 문제들을 해결할 수 있는 방법으로 시도해본 알고리즘이 Transformer입니다. 현재 딥러닝계에서 가장 핫한 Transformer를 게임 데이터에도 사용해봐야겠죠?  ‘Attention is all you need’ 논문에서 Transformer 구조가 나온 이후 현재 NLP분야에서는 Transformer 구조를 이용한 BERT, XLNET 등의 엄청나게 커다란 모델들이 다양한 Task에서 SOTA를 이루어내고 있습니다. 심지어 Attention Mechanism을 활용한 방법들은 작년부터 이미지/영상인식 쪽에도 사용되기 시작했죠. 그렇기에 우선적으로 생각해본 것이 BERT 구조였습니다. Self-Attention을 이용하여 긴 시퀀스도 잘 처리할 수 있고, LSTM 기반 Seq2seq-Autoencoder보다 정보를 더 잘 저장할 거라고 생각했습니다. 그런데, BERT를 이용하였을 경우에 MLM(Masked - Language Model), NSP(Next Sentence Prediction) 등의 학습 자체는 무척 잘되었으나 모델을 Fine-Tuning하여 다른 downstream Task에 적용하였을때 성능이 기대한만큼 나오지 않았습니다. 또한 Baseline Model로 학습한 LSTM 모델보다 성능 및 학습시간 측면에서도 상대적으로 밀리는 모습을 보여주었습니다. 물론 Downstream Task에서 사용된 모델의 구조나 Input이 이유가 될 수 있겠지만 전체적인 Task에서 성능이 잘 나오지 않았는데, 그 이유를 생각했을 때 아래의 두 가지로 추려볼 수 있었습니다.&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;사용한 데이터의 Dictionary Size에 비해 모델이 너무 커서, 과적합이 이루어졌다.&lt;/li&gt;
  &lt;li&gt;게임 유저의 행동 로그는 반복적인 행동을 하는 부분이 많다. 그러다보니, 유저의 행동 로그를 특정 기준으로 나누어 이전 문장의 다음을 예측하는 NSP의 경우는 게임 로그에는 적합하지 않은 방법이다.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;물론, 위의 두가지 문제를 Input을 바꾸거나 NSP를 사용하지 않는 ALBERT 구조를 이용하는 방법도 있었지만, BERT보다는 조금 더 단순한 모델에 적합하는 것이 성능 측면에서 나을 것이라고 생각을 하였고, 학습/추론 시간이 너무 오래 걸리는 현상도 해결해야했기 때문에 기본적인 Transformer Encoder-Decoder + Autoencoder 구조를 이용해서 임베딩 해보는 방법을 생각하였습니다.&lt;/p&gt;

&lt;h2 id=&quot;transformer-autoencoder&quot;&gt;Transformer-Autoencoder&lt;/h2&gt;

&lt;h4 id=&quot;0-transformer-autoencoder-architecture&quot;&gt;0. Transformer-Autoencoder Architecture&lt;/h4&gt;

&lt;p align=&quot;center&quot;&gt;
&lt;img src=&quot;/assets/works/transformer\image1.png&quot; style=&quot;width:8in&quot; /&gt;
    &lt;br /&gt;
    출처: https://medium.com/platfarm/%EC%96%B4%ED%85%90%EC%85%98-%EB%A9%94%EC%BB%A4%EB%8B%88%EC%A6%98%EA%B3%BC-transfomer-self-attention-842498fd3225
 &lt;/p&gt;

&lt;p&gt;위의 그림은 Transformer를 이용하여 ‘I love you’라는 영어 문장을 ‘나는 너를 사랑해’로 번역하는 모델입니다. Transformer를 보신 분들이라면 많이 보셨을 그림일 것 같습니다. 사실, 전체적인 형태는 Seq2Seq와 같습니다. encoder에 입력 문장을 넣고 이를 통해 나온 context vector를 이용하여 decoder에서 teacher forcing과 label을 사용하여 학습하는 형태입니다. 여기서 주목할점은 encoder layer를 거쳐서 나온 임베딩 벡터가 각각의 decoder layer로 들어간다는 점입니다. 밑에서 attention score를 구할 때 하나의 score가 나오는 것이 아닌 decoder layer개수만큼 score가 나오는 것은 이 때문입니다. 아, 그리고 저희는 autoencoder로 Transformer 구조를 사용하기 때문에 입력과 출력이 같겠죠?&lt;/p&gt;

&lt;h4 id=&quot;1-input&quot;&gt;1. Input&lt;/h4&gt;

&lt;p&gt;Transformer - Autoencoder 구조의 입력(출력)으로는 Action Code(유저가 어떤 행동을 취했는지) + Context Code(Action Code를 더욱 세분화한 카테고리) + Zone Code(유저가 어떤 지역에서 행동을 했는지) , 이 3가지 Feature를 이용하여 데이터를 생성하고 사전(Dictionary)을 구축하였습니다. 당연히 위에서 시도한, 지역 이동 임베딩 데이터보다는 사전의 크기가 무척 커졌습니다. 지역 이동 데이터만을 사용하여 학습할 수도 있지만, Transformer 모델 크기에 비해 Dictionary의 크기가 상대적으로 작아 다른 Feature들도 같이 사용하면 더 나은 학습 효과를 얻을 것으로 생각했습니다. 이제 이렇게 입력을 넣는다면 각 사용자가 어떤 지역에서 어떤 행동을 하였는지에 대한 순차적인 정보를 내포하는 임베딩 벡터가 추출되겠죠? Feature들의 예시를 살펴보면 다음과 같습니다.&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;Action Code : 게임머니 증가, 게임머니 감소, zone 이동, 로그아웃 등&lt;/li&gt;
  &lt;li&gt;Context Code : NPC상점판매, 혈맹 기부, Portal진입 등&lt;/li&gt;
  &lt;li&gt;Zone Code :  A지역 1층, B지역, C지역 등&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Transformer로 들어가는 Input data는 캐릭터별로  ‘Action Code_Context Code_Zone Code’ 로 구성하였습니다.  다음 예시는 이해를 돕기위해 임의로 만들어본 예시입니다. 예를들어 캐릭터 1은 C지역에 있는 NPC상점에서 물건을 판매해 게임머니가 증가하였고 그 후 A지역1층에서 드랍아이템을 획득한 것입니다.&lt;/p&gt;

&lt;p&gt;​          캐릭터 1 =  [’‘&lt;strong&gt;게임머니증가 _ NPC상점판매 _ C지역&lt;/strong&gt; ‘’, &lt;strong&gt;'’아이템증가 _ 드랍아이템획득 _ A지역 1층’‘&lt;/strong&gt; ,  ‘‘&lt;strong&gt;아이템증가 _ 드랍아이템획득 _ A지역 2층&lt;/strong&gt;’’ , …  ]&lt;/p&gt;

&lt;p&gt;​          캐릭터 2 =  [’‘&lt;strong&gt;게임머니감소 _ 혈맹기부 _ A지역’‘&lt;/strong&gt; , ‘‘&lt;strong&gt;아이템증가 _ 드랍아이템획득 _B지역’‘&lt;/strong&gt; ,  ‘‘&lt;strong&gt;아이템증가 _ 드랍아이템획득 _ A지역 3층’‘&lt;/strong&gt; , …  ]&lt;/p&gt;

&lt;h4 id=&quot;2-training-time&quot;&gt;2. Training Time&lt;/h4&gt;

&lt;p&gt;Hyperparameter(인코더 레이어 층, Feed-Forward Layer의 Node 개수 등)에 따라 학습 시간은 천차만별이기에, NVIDIA-GPU를 사용하였을 때의 학습 시간 감소효과 정도만 소개하려고 합니다.
  LSTM 기반의 Seq2Seq-Autoencoder의 경우 Only CPU만을 사용하여 학습한 것과  GPU(1개)를 사용하여 학습한 것을 비교하였을 때 학습 시간 감소가 23%정도(Epoch당)밖에 줄어들진 않았지만, Transformer의 경우 1epoch당 Only CPU만 사용했을 경우 40분(Epoch당) 에서 10분(Epoch당)으로 줄어들었습니다. 거의 75%의 감소 효과를 이루어낸 것이죠! 이런 차이가 나타난 이유는 아래 그림을 통해서 설명할 수 있을 것 같습니다.&lt;/p&gt;

&lt;p&gt;​&lt;/p&gt;

&lt;p align=&quot;center&quot;&gt;
&lt;img src=&quot;/assets/works/transformer\image2.png&quot; style=&quot;width:8in&quot; /&gt;
    &lt;br /&gt;
    출처: https://20chally.tistory.com/222
 &lt;/p&gt;

&lt;p&gt;‘Attention is all you need’ 논문에서 나온 계산 복잡도 부분을 뽑아왔습니다. 우선 레이어당 총 계산 복잡도는 n &amp;lt; d인 경우 self-attention이 더 적은 계산 복잡도를 가집니다. (그리고 대부분의 경우 n &amp;lt; d 입니다.) 그리고 GPU를 이용한 병렬 계산을 수행하기 위한 sequential operation의 최소수는 rnn만 O(n)의 복잡도를 가집니다. 즉, GPU 사용을 위해 얻을 수 있는 이득은 Self-Attention이 훨씬 큰 것입니다. 추가적으로 NLP Task에서 대부분의 경우 한 문장에서 단어의 개수는 모델의 dimension에 비해 월등히 작기 때문에 엄청난 계산적 이득을 얻는다고 볼 수 있는 것입니다. (여담으로, 필자는 Transformer 구조를 구현한데 있어서는 Tensorflow 보다는 Pytorch를 더 권장합니다. NLP에서 활용되는 모델들(Transformer, BERT, XLNET 등)은 아무래도 Huggingface처럼 대부분 Pytorch로 구현이 되어있는 라이브러리가 많다보니 학습하는데 있어 정신 건강에 이롭습니다. 더욱이 GPU는 잘 사용하길 바랍니다. 1개만 사용하지 말고 Multi-Gpu로 텐서 분산처리를 꼭 활용하시길.. 안 그렇다면 학습이 너무 오래 걸려 일 못하는 직원이 될 수도 있습니다… )&lt;/p&gt;

&lt;h4 id=&quot;3-학습결과&quot;&gt;3. 학습결과&lt;/h4&gt;

&lt;p&gt;Transformer - Autoencoder는 비지도 학습이다보니 어떻게 성능을 평가할지에 대해서는 고민이 좀 더 필요합니다. 물론 다른 Downstream Task 모델의 Input으로 넣어 정확도나 F1-Score 등을 계산할 수는 있지만 이는 Task의 목적이나 모델의 구조에 따라 다르기 때문에 좀 더 정성적으로 평가할 수 있는 기준이 있으면 좋을 것 같습니다. 학습이 잘 되었는지를 확인하기 위해 Test Data에 대해서는 두가지 방법을 적용하였습니다.&lt;/p&gt;

&lt;ol&gt;
  &lt;li&gt;input을 넣었을때, context vector가 output을 얼마나 input에 가깝게 잘 생성해내는지&lt;/li&gt;
  &lt;li&gt;Encoder - Decoder 간 attention score를 확인 (Autoencoder output의 sequence는 input과 동일하기 때문에 시퀀스 내 특정 위치의 데이터는 자기자신을 많이 참조할 것으로 추측하였습니다)&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Attention Score에 대해서 설명을 조금 더 직관적으로 돕기 위해, Pytorch로 구현한 번역 모델의 attention score 시각화 결과를 확인해보면 이해가 조금 더 편할 것입니다. 다음은 https://nlpinkorean.github.io/visualizing-neural-machine-translation-mechanics-of-seq2seq-models-with-attention/ 블로그에서 참조한 잘 학습된 번역 모델의 Attention Score 그림 예시입니다. 이 경우 입력은 영어 문장, 출력은 영어 문장을 번역한 불어 문장입니다. (이 경우에는 seq2seq 구조에 attention을 붙인 모델이지만 encoder - decoder 간의 attention score 계산 과정이나 결과 그래프 해석은 동일합니다)&lt;/p&gt;

&lt;p align=&quot;center&quot;&gt;
&lt;img src=&quot;/assets/works/transformer\image3.png&quot; style=&quot;width:6in&quot; /&gt;
    &lt;br /&gt;
    출처 : https://nlpinkorean.github.io/visualizing-neural-machine-translation-mechanics-of-seq2seq-models-with-attention/



 &lt;/p&gt;

&lt;p&gt;흰색에 가까울수록 Attention Score가 높은 것입니다. 위 그림에서 볼 수 있듯이, output의 단어가 많이 집중(Attention)하는 부분은 같은 뜻을 가진 단어에 해당하는 부분을 많이 참조(높은 Attention Score)하는 것을 확인할 수 있습니다. 예를 들면 모델이 “European Economic Area”를 출력할 때 모델이 어떤 단어를 Attention(주의) 하고 있는지를 확인해보면 “européenne économique zone” 을 거꾸로 참조하는 것을 확인할 수 있습니다. 영어와는 달리 불어에서는 이 단어들의 순서가 반대이기 때문입니다. 이 외의 단어 순서는 입력/출력 모두 순서가 같다보니 Attention Score가 대각선 계단 형태(downward staircae) 로 나타납니다. 행동 로그를 이용해 Transformer 구조로 넣는 Input Data는 시퀀스 순서가 Input과 Output간 동일하니 위 그림과 비슷하게 대각선 계단 형태의 그래프가 나올 것으로 기대하였습니다.&lt;/p&gt;

&lt;p align=&quot;center&quot;&gt;
&lt;img src=&quot;/assets/works/transformer\image4.png&quot; style=&quot;width:10in&quot; /&gt;
    &lt;/p&gt;

&lt;p&gt;먼저, 사용한 모델의 구조와 함께 Test Data를 Input으로 넣었을 때, Output이 어떻게 나오는지를 확인하였습니다. (Output의 경우 &lt;eos&gt; 토큰이 나올때까지 반복적으로 예측을 합니다. ) 다행히도 Input과 output이 거의 동일하게 나오는 것을 확인할 수 있었습니다. 실제로 Test Data 셋 중 90% 이상의 경우 Input과 Output이 동일하게 나오는 것을 확인할 수 있었습니다. 지면 관계상, 다양한 경우를 보여 드리진 못하지만 대부분의 Test Data 셋에서 Context Vector가 입력 시퀀스를 다시 잘 생성해내는 것을 확인할 수 있다고 말씀드릴 수 있습니다.&lt;/eos&gt;&lt;/p&gt;

&lt;p align=&quot;center&quot;&gt;
&lt;img src=&quot;/assets/works/transformer\image5.png&quot; style=&quot;width:10in&quot; /&gt;
    &lt;/p&gt;

&lt;p&gt;​    위의 두 그래프는 Test data를 모델에 넣었을 때, attention score 를 시각화한 결과입니다. x축은 test data의 sequence(130개)를 나타내고, y축은 test data로 encoding한 후 decoding한 문장 Sequence(131개 - &lt;sos&gt; 토큰까지) 결과입니다. (위의 번역 모델의 그래프와는 축이 반대입니다) 그리고 입출력간의 attention score를 위의 번역 모델처럼 그래프로 나타내 보았습니다. 위에서 설명했듯이, decoder layer 여러 개에 embedding vector를 넣었기 때문에 각각의 decoder layer마다 attention score가 나옴을 확인할 수 있습니다. 첫번째 그래프는 첫번째 decoder layer의 attention score, 두번째 그래프는 두번째 decoder layer의 attention score를 나타냅니다. 아무래도 decoder layer를 한번 더 거칠수록 attention score가 더욱 잘 나오는 것도 확인할 수 있었고 두 경우 모두 기대한대로 계단 모양(downward staircase)의 attention score를 보여주었습니다.&lt;/sos&gt;&lt;/p&gt;

&lt;h2 id=&quot;downstream-taskbad-user-detection-system&quot;&gt;Downstream Task(Bad User Detection System)&lt;/h2&gt;

&lt;p&gt;이와 같이 학습이 완료되었다면 Donwstream Task에 적용해볼 수 있을 것입니다. 이 중 NCSOFT에서 진행하는 ‘부정사용자탐지’ 프로젝트에 모델을 적용시켜보려합니다. 부정사용자란 NCSOFT 게임에 존재하는 불법프로그램 사용자, 작업장 등 게임의 정책 및 경제에 악영향을 주는 유저들을 지칭합니다. 이는 게임 운영에 있어서 중요한 문제를 야기하는 요소들이며 이를 제재하고 탐지하기 위해서 다양한 방법들을 적용하고 있습니다. 물론, 부정사용자탐지는 앞선 ‘Snorkel을 이용한 Label 보정’ 글에서도 알 수 있듯이 Weak Supervision 개념이나 여러 모델들의 앙상블, 다양한 데이터 전처리를 통해 진행하는 매우 복잡한 프로젝트이지만, 여기서는 임베딩 벡터를 추출한 결과가 어느 정도로 일반 사용자와 부정사용자를 구분해냈는지만을 확인해보고 이를 고도화한다면 프로젝트에 기여할 수 있을지 정도를 확인해보았습니다.&lt;br /&gt;
  부정사용자도 다양한 행동을 하지만 특정 패턴들이 존재하는 경우가 많습니다. 여러 아이디에 있는 게임머니를 한 아이디로 몰아준다거나, 특정 Zone에서 특정 행동을 반복하는 것을 그 예로 들 수 있을 것 같습니다. 이러한 행동 패턴들은 일반사용자는 하지 않을 행동들이며 행동 로그 데이터에서 그 차이를 확인할 수 있습니다. 그렇기에 이런 행동 로그를 이용해 학습한 Transformer 모델은 부정사용자와 일반사용자간의 임베딩 벡터간 유사도가 적게 나타날 것입니다.&lt;br /&gt;
  현재 Transformer - Autoencoder로 만든 모델에서 추출된 유저별(캐릭터별) 임베딩 벡터를 Input으로 사용하고 이진분류 딥러닝 모델을 이용하여 Task에 적용해보았습니다. (NCSOFT 운영정책상으로 실제 제재된 캐릭터들과 그렇지 않은 일반사용자를 대상으로 데이터를 추출하였습니다) 아래는 Baseline Model로 사용한 Seq2Seq(LSTM) Autoencoder 와 성능을 비교한 Confusion Matrix 입니다. Baseline Model과 학습데이터 및 Downstream Task Model 구조등은 동일하게 맞추었습니다. 아직 모델을 더 고도화하고 하이퍼파라미터를 조절하는 등의 연구가 진행되어야 하지만 Transformer 구조가 좋은 결과를 보여주는 것 같습니다.&lt;/p&gt;

&lt;h4 id=&quot;seq2seqlstm-autoencoder의-임베딩-벡터를-이용한-이진-분류&quot;&gt;Seq2Seq(LSTM) Autoencoder의 임베딩 벡터를 이용한 이진 분류&lt;/h4&gt;

&lt;table&gt;
  &lt;thead&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;/thead&gt;
  &lt;tbody&gt;
    &lt;tr&gt;
      &lt;td&gt;실제 &lt;strong&gt;부정사용자&lt;/strong&gt;&lt;/td&gt;
      &lt;td&gt;2143 (True Positive)&lt;/td&gt;
      &lt;td&gt;404 (False Negative)&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;실제 &lt;strong&gt;일반사용자&lt;/strong&gt;&lt;/td&gt;
      &lt;td&gt;620 (False Positive)&lt;/td&gt;
      &lt;td&gt;2957 (True Negative)&lt;/td&gt;
    &lt;/tr&gt;
  &lt;/tbody&gt;
&lt;/table&gt;

&lt;ul&gt;
  &lt;li&gt;f-1 score : 0.807&lt;/li&gt;
  &lt;li&gt;precision : 0.776&lt;/li&gt;
  &lt;li&gt;recall : 0.841&lt;/li&gt;
&lt;/ul&gt;

&lt;h4 id=&quot;transformer---autoencoder의-임베딩-벡터를-이용한-이진-분류&quot;&gt;Transformer - Autoencoder의 임베딩 벡터를 이용한 이진 분류&lt;/h4&gt;

&lt;table&gt;
  &lt;thead&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;/thead&gt;
  &lt;tbody&gt;
    &lt;tr&gt;
      &lt;td&gt;실제 &lt;strong&gt;부정사용자&lt;/strong&gt;&lt;/td&gt;
      &lt;td&gt;2320 (True Positive)&lt;/td&gt;
      &lt;td&gt;227 (False Negative)&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;실제 &lt;strong&gt;일반사용자&lt;/strong&gt;&lt;/td&gt;
      &lt;td&gt;156 (False Positive)&lt;/td&gt;
      &lt;td&gt;3421 (True Negative)&lt;/td&gt;
    &lt;/tr&gt;
  &lt;/tbody&gt;
&lt;/table&gt;

&lt;ul&gt;
  &lt;li&gt;f-1 score : 0.924&lt;/li&gt;
  &lt;li&gt;precision : 0.937&lt;/li&gt;
  &lt;li&gt;recall : 0.911&lt;/li&gt;
&lt;/ul&gt;

&lt;h2 id=&quot;마치며&quot;&gt;마치며&lt;/h2&gt;

&lt;p&gt;지금까지 Transformer를 이용한 대량의 로그 데이터 임베딩을 소개 해드렸습니다. 생각보다 학습에서 만족스러운 Attention Score나 학습 결과가 나왔지만 아직 고려해야할 점이 많이 있습니다.&lt;/p&gt;

&lt;ol&gt;
  &lt;li&gt;게임 데이터의 특성상 업데이트가 진행되어 새로운 zone이나 새로운 로그들이 추가된다면 이를 OOV(Out-Of-Vocabulary)로 처리하기보다는 새로 재학습을 진행해야 모델이 계속해서 좋은 성능을 보여줄 것입니다.&lt;/li&gt;
  &lt;li&gt;현재는 도메인 지식에 기반하여 사용할 로그/사용하지 않을 로그들을 구분하였지만 더 나은 로그 데이터 처리 방식에 대한 고민이 이루어져야 할 것입니다.&lt;/li&gt;
  &lt;li&gt;Action Code / Context Code/ Zone Code 3가지만을 이용해서 유저의 행동 로그를 임베딩하였지만 유저는 정말 다양한 행동들을 합니다. 그렇기에 더 많은 Feature를 사용한다면 더 좋은 임베딩 결과가 나오겠지만 Feature를 많이 사용할수록 모델의 성능과 trade off 관계에 있기 때문에 이를 잘 조율해 볼 연구가 더 진행되어야 할 것입니다.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;GPU 분산 병렬 처리도 더 진행해야할 부분입니다. 대용량의 데이터를 처리하다보니 Training/Inference Time이 많이 소요되어 GPU 사용은 필수이고, 이를 어떻게 분산 처리를 잘! 할지가 참 중요한 부분입니다. 메인 GPU 메모리 이슈, Tensor를 어떻게 분배할지 등 고려할 요소는 정말 많은 듯 합니다.&lt;/p&gt;

&lt;h2 id=&quot;참고자료&quot;&gt;참고자료&lt;/h2&gt;

&lt;ul&gt;
  &lt;li&gt;https://arxiv.org/abs/1706.03762&lt;/li&gt;
  &lt;li&gt;https://nlpinkorean.github.io/visualizing-neural-machine-translation-mechanics-of-seq2seq-models-with-attention/&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;https://arxiv.org/abs/1703.00854&quot;&gt;https://20chally.tistory.com/222&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;http://ai.stanford.edu/blog/weak-supervision/&quot;&gt;https://medium.com/platfarm/%EC%96%B4%ED%85%90%EC%85%98-%EB%A9%94%EC%BB%A4%EB%8B%88%EC%A6%98%EA%B3%BC-transfomer-self-attention-842498fd3225&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;https://github.com/snorkel-team/snorkel&quot;&gt;https://pytorch.org/tutorials/intermediate/seq2seq_translation_tutorial.html&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
</description>
        <pubDate>Tue, 13 Apr 2021 09:43:00 +0000</pubDate>
        <link>https://danbi-ncsoft.github.io//works/2021/04/13/transformer_embedding.html</link>
        <guid isPermaLink="true">https://danbi-ncsoft.github.io//works/2021/04/13/transformer_embedding.html</guid>
        
        
        <category>Works</category>
        
      </item>
    
      <item>
        <title>PC의 시스템 로그를 활용하여 위협 행위 탐지하기</title>
        <description>&lt;h2 id=&quot;시작하며&quot;&gt;&lt;strong&gt;시작하며&lt;/strong&gt;&lt;/h2&gt;

&lt;p&gt;하루에도 수통 씩 들어오는 스팸문자나 주기적인 개인 정보 유출 사고 뉴스를 접하다 보면 평소 보안에 관심이 없던 사람이라도 자신이 얼마나 위험에 노출되어 있는지 걱정되기 마련이다. 이런 이들을 위해 다양한 보안 프로그램과 서비스들이 등장하였지만 그에 맞춰 공격자들 또한 더욱 고도화된 방법들을 시도하고 있다. 대표적으로 기술적인 틈을 노리는 것이 아니라, 사용자의 인지적인 헛점을 공략하는 사회공학적 해킹 기법의 하나인 스피어피싱 (Spear phishing)이 있다. 스피어피싱은 불특정다수가 아니라 특정 개인/ 조직을 타겟으로 설정하고, 그들이 의문 없이 열람하도록 위장한 메일을 통해 악성코드를 감염시키는 공격 방식이다. 조직원이 공격 의도를 파악하지 못하고 메일 내 링크나 첨부파일을 열람할 때 악성코드가 실행되므로 방화벽과 같은 기존 보안 방식으로 사전에 이를 감지하여 막는 것은 쉽지 않다.&lt;/p&gt;

&lt;p&gt;일단 치명적인 공격이 발생했다면 이에 대한 대응은 빠를수록 좋다. 그러나 공격자들은 최대한 많은 정보를 빼내기 위하여 공격 시도를 은폐하기 위한 조치들을 취해 놓는다. 앞서 예로 들은 스피어피싱의 경우 사용자가 문제되는 첨부파일 실행 시 악성 코드 감염 사실을 눈치채지 못하도록 정상 파일을 보여주는 식이다. 이렇게 침투한 악성 코드는 피해자의 PC를 경유해 조직의 DB나 서버에까지 접근하여 중요한 정보들을 탈취할 수 있게 된다.&lt;/p&gt;

&lt;p&gt;보안 분야에서는 고도화되는 위협에 대응하기 위하여 위협 탐지 지점을 네트워크 경계 영역만이 아니라 실제 위협이 실현되는 엔드포인트(Endpoint)로 확장하고 있다. 엔드포인트란 PC, 노트북, 핸드폰 등과 같이 네트워크와 최종적으로 연결되는 IT 디바이스를 의미한다. 엔드포인트에서의 보안 분야 중 하나로 엔드포인트 탐색 및 대응 (Endpoint Detection and response, EDR)이 있는데, 원천적인 예방보다는 수상한 행동이 발생했을 경우에 대한 빠른 탐지와 초기 조치를 중요시한다. 이를 위해 엔드포인트에 설치된 에이전트가 시스템에서 발생하는 각종 활동을 끊임없이 모니터링하고 중앙 서버에 해당 정보를 전송하며 의심스러운 행동을 발견하면 자동으로 이를 수정하거나 보안 전문가에게 즉각 확인하도록 알림으로써 가능한 빠르게 위협 행위를 감지하고 대응할 수 있도록 한다. 이는 앞서 예시로 들었던 스피어피싱과 같이 사용자의 허점을 노려 시스템에 잠입한 뒤 장기간에 걸쳐 필요한 정보를 탈취한 다음 흔적을 지우고 사라지는 공격에 대해 특히 유효할 수 있다.&lt;/p&gt;

&lt;p&gt;여기서 중요한 것은 의심스러운 행동을 최대한 빠르게 감지하는 것이다. 그렇다면 의심스러운 행동이란 무엇일까? 그 기준은 어떻게 정할 수 있을까? 이번 글에서는 이 질문에 대한 답을 찾기 위해 PC로부터 수집한 시스템 이벤트의 로그를 활용해본 방법과 그 결과를 소개해보고자 한다.&lt;/p&gt;

&lt;h2 id=&quot;정상-상태-정의와-이상-상태-탐지&quot;&gt;&lt;strong&gt;정상 상태 정의와 이상 상태 탐지&lt;/strong&gt;&lt;/h2&gt;

&lt;p&gt;인사팀에 근무하는 A씨는 평소 9시에 출근하여 6시에 퇴근한다. 가끔 야근을 할 때도 있지만 12시를 넘기는 일은 흔치 않다. 그는 주로 자신의 PC로 엑셀과 워드, 파워포인트를 이용해서 작업을 하며 이메일과 사내 메신저를 사용해 협업을 한다.&lt;/p&gt;

&lt;p&gt;만약 A씨의 PC로부터 새벽 2시에 쉘 스크립트를 통하여 전사 직원의 계좌 정보에 접근한 로그가 발생하였다면 이는 그의 평소 행동 패턴과는 매우 상이하다고 판단할 수 있을 것이다. A씨의 행동은 그 자신의 평소 패턴으로부터도 떨어져 있을 뿐 아니라 그가 속해 있는 인사팀의 일반적인 행동 범위로부터도 벗어났다는 점에서 상당히 의심스럽다.&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;https://danbi-ncsoft.github.io/assets/works/threat_detection/image_1.png&quot; alt=&quot;image_1&quot; /&gt;&lt;/p&gt;

&lt;p&gt;위의 예시와 같이 PC 사용자들은 그들의 작업 방식 및 생활 패턴에 따라 각자의 정상 상태를 갖고 있을 것이다. 설령 개개인의 업무나 생활 방식에 변화가 발생할지라도 그가 수행하는 업무 특성 및 소속된 조직 단위로보면 일정한 범위로 정상 행동을 표현할 수 있을 것이다. 만약 악성 코드에 감염된 사용자의 PC에서 위협 행위가 시도될 경우 그로 인해 발생하는 로그는 정상 행동 범위에서 벗어나게 될 가능성이 높을 것이다. 이번 글에서 설명하려는 위협 행위 탐지 방법론은 이상의 가설에 기초하여 아래와 같은 동작 방식을 상정하고 있다.&lt;/p&gt;

&lt;p&gt;먼저 각 사용자의 PC로부터 시스템 이벤트의 로그들을 수집한다. 로그가 충분히 자세하고 양이 풍부하다면 각 사용자의 PC 사용 패턴을 수집된 로그들을 사용하여 표현할 수 있을 것이다. 이렇게 각각의 정상적인 PC 사용 상태를 정의한 다음, 평소의 이용 패턴과 크게 상이한 이벤트 로그가 발생하였을 때 이를 위협 가능성이 있는 행위 후보로써 감지하고 실제 위협 행위여부를 판단할 수 있도록 보안 전문가에게 알람을 제공한다.&lt;/p&gt;

&lt;p&gt;그렇다면 마이크로초 단위로 발생하는 시스템 이벤트의 로그들을 사용해서 어떻게 각 사용자의 정상 상태를 정의할 수 있을까? 그리고 이렇게 표현된 정상 상태가 사용자의 행동 특성을 충실하게 담고 있다는 것을 어떻게 확인할 수 있을까? 여기서 문서 임베딩 방식의 대표주자 중 하나인 Doc2Vec이 등장한다.&lt;/p&gt;

&lt;h2 id=&quot;doc2vec을-사용한-사용자-행동패턴-표현과-이상탐지&quot;&gt;&lt;strong&gt;Doc2Vec을 사용한 사용자 행동패턴 표현과 이상탐지&lt;/strong&gt;&lt;/h2&gt;

&lt;p&gt;Doc2Vec에 대해 이야기하기에 앞서 먼저 Word2Vec에 대해 간단하게 알아보자. Word2Vec은 단어를 벡터와 같은 연산 가능한 형태의 수학적 객체로 표현하기 위한 단어 표현 (Word Representation) 방법론 중 하나이다. Word2Vec을 사용하여 ‘사과’, ‘자동차’와 같은 단어들을 [2.351, -0.217, 1.242], [-4.121, 0.887, 1.298]과 같은 벡터로 변환할 수 있다는 것이다. Word2Vec은 이렇게 변환된 유사한 의미의 단어들이 벡터 공간 상에서도 비슷한 좌표값을 갖는다는 특징을 갖고 있다.&lt;/p&gt;

&lt;p&gt;Doc2Vec은 단어(Word)가 아니라 문서(Document)를 벡터로 변환하는 방법론이다. Doc2Vec은 단어들로 구성된 문서와 문서의 ID에 해당하는 paragraph vector를 함께 학습하여 문서를 위치 좌표 공간상의 벡터로 변환한다. Word2Vec에서 의미가 유사한 단어들이 벡터 공간 상에서 비슷한 좌표계에 위치하는 것과 같이 Doc2Vec으로 학습된 문서들도 해당 문서를 구성하는 단어의 분포가 유사하면 비슷한 벡터를 지니게 되며, 이에 따라 주제 및 내용이 유사한 문서들이 비슷한 벡터로 변환되어 유사한 좌표 공간상에 위치한다.&lt;/p&gt;

&lt;p&gt;이번 분석에서 정상 상태 정의를 위한 방법으로 Doc2Vec을 선택한 것은 이 특징 때문이다. 주제 및 내용이 유사한 문서들이 비슷한 벡터로 변환된다면, 각 사용자의 PC로부터 발생한 시스템 이벤트 로그의 집합(시퀀스)를 Doc2Vec으로 변환할 경우에도 사용자에 따라 벡터들이 비슷하게 생성될 것이다.&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;https://danbi-ncsoft.github.io/assets/works/threat_detection/image_2.png&quot; alt=&quot;image_2&quot; /&gt;&lt;/p&gt;

&lt;p&gt;앞선 A씨의 PC에서 발생한 시스템 이벤트의 로그들을 모아 하루치 씩 Doc2Vec에 적용해서 벡터들을 생성했다고 해 보자. A씨의 행동 패턴에 큰 변화가 없었다면 7월 1일의 로그 시퀀스를 구성하는 이벤트 로그의 분포와 7월 2일의 분포는 유사할 것이므로, 7월 1일의 벡터와 7월 2일의 벡터는 좌표 공간 상에서 비슷한 곳에 위치하게 될 것이다. 그리고 로그 데이터가 충분히 많고 정밀하다면 A씨의 옆 팀 동료인 B씨의 로그 시퀀스 벡터는 A씨와 구분 가능할 정도로 떨어진 공간 상에 배치될 것이다. 만약 일정 기간에 걸쳐 각 사용자의 시간별 또는 일별 로그 시퀀스 벡터들을 생성해서 그들이 일정 크기의 클러스터를 구성한다면, 이후 해당 클러스터로부터 크게 떨어진 행동 로그 시퀀스가 발생했을 때 이를 위협 가능성이 존재하는 행위로써 탐지할 수 있을 것이다.&lt;/p&gt;

&lt;p&gt;여기까지 해서 방법론에 대한 개괄 설명은 끝났다. 다음부터는 실제 데이터를 사용해서 어떤 식으로 분석을 진행하였고 그 결과가 어떻게 나왔는지 보여 드리고자 한다.&lt;/p&gt;

&lt;h2 id=&quot;적용-과정&quot;&gt;&lt;strong&gt;적용 과정&lt;/strong&gt;&lt;/h2&gt;

&lt;p&gt;제안된 방법론에 적용하기 위한 데이터는 단비 블로그를 운영하고 있는 I&amp;amp;I실 실원들의 PC로부터 수집하였다. 수집 기간 중 인력 변동 및 장비 교체가 발생하여 최종적으로 14명의 유저가 사용한 15대의 PC로부터 총 100일간 (2019년 7월 3일 ~ 2019년 10월 10일) 발생한 로그를 확보하였다.&lt;/p&gt;

&lt;p&gt;보안 서비스 업체인 Carbon Black에서 제공하는 엔드포인트 보안 솔루션의 내장 센서로부터 수집되는 로그는 json 또는 LEEF 구조의 key-value쌍의 형태로 기록되는데, 발생한 시스템 이벤트의 성격(종류)에 따라 key의 구성 및 value의 형식에 차이가 있다. 이번 분석에서는 시스템 이벤트가 발생한 시각, 발생한 PC의 ID, 발생한 이벤트의 종류 및 발생 경로에 대한 정보를 사용하였다.&lt;/p&gt;

&lt;p&gt;시스템 로그는 PC가 동작하는 동안 지속적으로 발생하므로 하나의 문서로써 취급할 로그 시퀀스를 생성하기 위해서는 연속되는 로그(단어)들을 일정 기간 단위로 쪼갤 필요가 있다. 일반적으로 PC를 사용해서 업무를 수행하는 개인의 행동 패턴은 출근과 오전 근무, 점심 식사, 오후 근무 후 퇴근과 같은 반복성을 띄게 되며, 근무일과 휴일의 차이 및 요일별 차이도 고려할 필요가 있다. 이를 위하여 연속 발생하는 시스템 로그를 사용자별로 일-시간 단위로 분절하는 방법과 일 단위로 분할하는 방법을 각각 수행하여 결과를 확인하였다. 총 100일의 수집 기간 중 전반기 (2019년 7월 3일 ~ 7월 12일, 총 10일) 데이터는 시간 단위로 로그 시퀀스를 생성하였고, 후반기 (2019년 8월 22일 ~ 10월 10일, 총 50일) 데이터는 일 단위 로그 시퀀스를 생성하였다.&lt;/p&gt;

&lt;p&gt;이렇게 전처리를 마친 로그 시퀀스를 문서로, 해당 로그 시퀀스가 발생한 시간대 혹은 날짜를 문서 ID로 설정하여 Doc2Vec에 적용하여 30차원의 벡터로 변환하였다. 그리고 변환된 각 벡터가 발생한 PC의 ID를 라벨로 붙여 앞서 가정했던 것처럼 벡터들이 사용자의 행동 정보를 잘 포함하고 있는지 확인하기 위한 검증을 진행했다.&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;https://danbi-ncsoft.github.io/assets/works/threat_detection/image_3.png&quot; alt=&quot;image_3&quot; /&gt;&lt;/p&gt;

&lt;p&gt;먼저 전반기와 후반기 각각에 대해 로그 시퀀스가 발생한 시점에 따라 학습기간과 평가기간을 나눴다. 그리고 학습기간 동안 발생한 로그 시퀀스의 벡터들로 이들이 발생한 PC(의 ID)를 분류하도록 학습시킨 다음, 평가기간 발생한 로그 시퀀스의 벡터가 주어졌을 때 각 벡터가 발생한 PC를 제대로 예측할 수 있는지 알아보았다. 더불어 생성된 로그 시퀀스 벡터들을 시각화하여 처음 예상했던 것처럼 이들이 일정 범위의 클러스터를 구성하고 있는지 확인해 보았다.&lt;/p&gt;

&lt;h2 id=&quot;적용-결과&quot;&gt;적용 결과&lt;/h2&gt;

&lt;h3 id=&quot;차원-축소-및-시각화&quot;&gt;&lt;strong&gt;차원 축소 및 시각화&lt;/strong&gt;&lt;/h3&gt;

&lt;p&gt;30차원의 벡터를 2차원 평면 상에 도시하기 위해 t-SNE를 사용하여 벡터의 차원을 축소하였다. 만약 사용자의 행동 특성이 정상적으로 반영되었다면 동일한 PC로부터 발생한 로그 시퀀스의 벡터들은 2차원 공간상에서 밀집된 형태로 나타날 것이다. 또한, 사용자별 행동 특성이 상이하다면 발생한 PC에 따라 벡터들이 다른 위치에 배치되어야 할 것이다.&lt;/p&gt;

&lt;p&gt;먼저 전반기 데이터를 사용해 시간대별 로그 시퀀스를 생성한 결과는 다음과 같다. 점의 색깔은 각 로그 시퀀스가 발생한 PC의 사용자를 의미하는데, 각 사용자의 벡터들이 클러스터 형태로 분포되는 것을 확인할 수 있다.&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;https://danbi-ncsoft.github.io/assets/works/threat_detection/image_4.png&quot; alt=&quot;image_4&quot; /&gt;&lt;/p&gt;

&lt;p&gt;일별 로그 시퀀스를 생성한 후반기의 결과도 비슷하다. 다만 시간대별 결과에 비해서 생성된 포인트의 개수가 적어 클러스터 간 거리가 더 떨어지는 형태로 나타남을 알 수 있다.&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;https://danbi-ncsoft.github.io/assets/works/threat_detection/image_5.png&quot; alt=&quot;image_5&quot; /&gt;&lt;/p&gt;

&lt;p&gt;기간 변동에 따른 차이는 있었을까? 시간이 경과하여도 로그 수집에 참가한 대부분의 사용자들의 벡터들은 비슷한 공간 상에 mapping되었다. 다만, 전반기에서는 User 13, 후반기에서는 User 12와 User 13의 결과에서 학습 기간 벡터와 평가 기간 벡터의 영역이 비슷하게 위치하고는 있으나 겹치는 부분은 거의 없는 것으로 나타났다.&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;https://danbi-ncsoft.github.io/assets/works/threat_detection/image_6.png&quot; alt=&quot;image_6&quot; /&gt;&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;https://danbi-ncsoft.github.io/assets/works/threat_detection/image_7.png&quot; alt=&quot;image_7&quot; /&gt;&lt;/p&gt;

&lt;h3 id=&quot;분류-모델을-통한-성능-측정-결과&quot;&gt;&lt;strong&gt;분류 모델을 통한 성능 측정 결과&lt;/strong&gt;&lt;/h3&gt;

&lt;p&gt;마지막으로 로그 시퀀스로 생성한 벡터들이 개별 사용자의 행동을 잘 표현하고 있는지 두 종류의 분류 모델을 통해 확인해 보았다. 앞서 설명한 것처럼 각 분류 모델의 목표는 특정 로그 시퀀스 벡터가 어느 PC로부터 생성되었는지를 분류하는 것이다. 분류 알고리즘은 Logistic Regression과 Random Forest를 사용하였다.&lt;/p&gt;

&lt;p&gt;먼저 전반기의 시간별 로그 시퀀스 벡터를 설명변수로 사용한 두 분류 모델의 성능은 Macro F1-Measure 기준으로 0.83이었다. 전체적으로 Logistic Regression과 Random Forest 모두 로그 시퀀스 벡터가 생성된 PC를 준수하게 예측하였다. 다만 아래에서 확인할 수 있듯이 User 13의 PC에서 생성된 로그 시퀀스는 두 분류모델에서 대부분 오분류되는 결과가 발생했다.&lt;/p&gt;

&lt;p&gt;왜 한 사람의 PC에서만 이런 현상이 나타난 것일까? 이 질문에 대한 답을 구하기 위해 User 13과의 인터뷰를 수행해 보았다. 그에 따르면 User 13은 두 대의 PC를 사용하여 작업을 하는데, 학습 기간에는 보안 솔루션이 탑재된 PC를 주로 사용하였던 반면 평가 기간에는 솔루션이 설치되지 않은 다른 PC를 중점적으로 사용했다는 것을 알 수 있었다. 즉, 학습기간의 행동 패턴과 평가기간의 행동 패턴이 변화함에 따라 이러한 결과가 나타났을 것으로 예상해 볼 수 있었다.&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;https://danbi-ncsoft.github.io/assets/works/threat_detection/image_8.png&quot; alt=&quot;image_8&quot; /&gt;&lt;/p&gt;

&lt;p&gt;후반기의 일별 로그 시퀀스 벡터로 학습시킨 분류 모델들의 성능도 전반기와 비슷하였다. Logistic Regression의 Macro F1-Measure는 0.82, Random Forest는 0.81이었으며, 여기에서는 User 12의 로그만 대부분 오분류되는 현상이 발생하였다. User 12에게도 동일한 인터뷰를 진행해 본 결과, 9월 말부터 보안 솔루션이 설치되지 않은 PC를 주로 사용하는 업무를 수행했다는 것을 알 수 있었다.&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;https://danbi-ncsoft.github.io/assets/works/threat_detection/image_9.png&quot; alt=&quot;image_9&quot; /&gt;&lt;/p&gt;

&lt;p&gt;두 번의 분류 모델링과 관계자 인터뷰를 통해 사용자의 업무 방식 변화가 벡터화된 로그 시퀀스에 반영된 것이 오분류의 원인일 것으로 추정할 수 있었다. 이 추정에 대한 검증을 위해 후반기의 오분류 당사자였던 User 12의 인터뷰 내용을 활용해보기로 했다. User 12는 업무 방식의 변화가 9월 말에 발생했다고 말했다. 그렇다면 후반기 모델의 학습기간을 9월 중순까지 설정하고 평가기간을 9월 하순으로 두었을 경우, User12의 로그 시퀀스 벡터에 대한 분류 성능의 정확도는 어떻게 변화할 것인가?&lt;/p&gt;

&lt;p&gt;그 결과는 다음과 같다. 해당 기간에 대한 전체적인 분류 모델 두개의 성능이 Macro F1-Measure 기준 0.93~0.95로 상승하였고, 관심의 대상이었던 User12의 7일간의 로그 시퀀스 벡터 중 4개를 정확히 분류하였다. 특히 전체 평가기간 중 9월 24일부터 27일까지의 나흘 간의 데이터를 정확히 분류하고 이후 사흘(9월 28일~30일)을 오분류 하였는데, 이는 9월 말에 업무 방식이 변화했다는 사용자의 응답 내용과 일치하는 결과라고 할 수 있다.&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;https://danbi-ncsoft.github.io/assets/works/threat_detection/image_10.png&quot; alt=&quot;image_10&quot; /&gt;&lt;/p&gt;

&lt;h2 id=&quot;마치며&quot;&gt;마치며&lt;/h2&gt;

&lt;p&gt;이 글에서는 앞서 사용자의 정상 행동 상태를 잘 정의할 수 있다면 향후 보안 위협에 의해 정상 범위로부터 크게 벗어난 행위가 발생했을 시 이를 위험 가능성이 있는 행동으로써 탐지하여 신속한 대응이 가능하리라 가정하였다. Doc2Vec을 사용해 시간별 또는 일별로 변환한 로그 시퀀스의 벡터에는 사용자의 행동 특성이 반영되어 있었고, PC 사용 행동 패턴의 변화 또한 변환되는 벡터에 반영되는 것을 알 수 있었다. 따라서 이를 잘 활용하면 목표로 했던 신속한 위협 행위 탐지 및 알람 시스템을 구축해 볼 수 있으리라 생각한다.&lt;/p&gt;

&lt;p&gt;다만 이번 분석에서는 데이터의 수집 대상이 동일한 실의 한정된 인원에 국한되어 있었다. 만약 이번 분석에서 설계한 것처럼 사용자 개개인에 대한 정상상태를 정의하는 방식을 취하게 된다면, 데이터의 수집 대상이 증가할수록 분류할 범주의 개수 또한 늘어나 모델의 성능 저하 및 학습 소요 시간과 필요 리소스가 증가하게 될 것이다.&lt;/p&gt;

&lt;p&gt;그리고 분류 모델의 성능에서 확인할 수 있듯이 변환된 로그 시퀀스 벡터는 사용자의 행동 변화에 매우 민감하게 반응하는 것으로 나타났다. 따라서 단순 업무 패턴의 변화를 위협 가능성이 있는 행위로 탐지하여 보안 담당자에게 알려주는 false positive의 가능성이 존재한다. 위협 탐지 시스템이 ‘양치기 소년’이 된다면 보안 담당자는 점점 알람 기능을 신뢰하지 않게 될 것이다.&lt;/p&gt;

&lt;p&gt;예상되는 문제점을 개선할 실마리를 찾기 위해 이 글의 초반부에 등장했던 A씨의 사례를 떠올려 보자. A씨의 PC가 새벽 2시에 쉘 스크립트로 인사정보 DB에 접근한 행위는 그의 평소 업무 방식과 차이가 날 뿐 아니라 그가 속한 조직의 일반적인 업무 행동에서도 크게 벗어난 행위였다. 즉, 개인의 정상 행동 상태 뿐 아니라 그가 속하는 조직 및 직무에 따른 정상 행동 상태 범위를 설정하고, 특정 사용자의 행위 로그가 본인이 소속된 조직 및 담당 직무의 정상 패턴으로부터 극단적으로 이상성을 보일 때 위협으로 탐지한다면 앞서 제시된 ‘양치기 소년’ 문제를 해결함과 동시에 생성해야 할 분류 모델 개수를 줄여 구축 및 관리 비용도 절감할 수 있을 것이다.&lt;/p&gt;
</description>
        <pubDate>Mon, 05 Apr 2021 16:00:00 +0000</pubDate>
        <link>https://danbi-ncsoft.github.io//works/2021/04/05/threat_detection.html</link>
        <guid isPermaLink="true">https://danbi-ncsoft.github.io//works/2021/04/05/threat_detection.html</guid>
        
        
        <category>Works</category>
        
      </item>
    
  </channel>
</rss>
