Jekyll2019-12-06T14:33:58+00:00https://heiwais25.github.io//feed.xmlJH programmingStudy and enjoy MLTarvis CI를 이용한 electron test 및 자동 배포2019-12-06T21:12:00+00:002019-12-06T21:12:00+00:00https://heiwais25.github.io//electron/2019/12/06/electron-travis-ci<p><code class="language-plaintext highlighter-rouge">electron</code>을 개발한다면 배포를 위해 자연스럽게 사용하는 것은 <code class="language-plaintext highlighter-rouge">electron-builder</code>일 것이다. <code class="language-plaintext highlighter-rouge">electron-builder</code>를 이용하면 각 운영체제에 맞는 실행 파일을 손쉽게 만들어낼 수 있는데, 이 밖에도 정말 좋은 점은 Github이나 AWS S3등에 <strong>배포</strong>를 할 수 있다는 것이다. 이 글에서는 <code class="language-plaintext highlighter-rouge">electron-builder</code>를 사용한 Github 배포에 대해 알아보고, Travis CI를 통해 이 과정을 자동화하는 것을 알아보겠다.</p>
<h2 id="electron-builder-publish-on-github"><code class="language-plaintext highlighter-rouge">electron-builder</code> publish on github</h2>
<p>Github에 publish하기 위해서는 먼저 repository에 접근할 권한이 있는 Github token이 필요하다. 다음 <a href="https://help.github.com/en/github/authenticating-to-github/creating-a-personal-access-token-for-the-command-line#creating-a-token">링크</a>에 자세한 설명이 적혀있는데, 개인 페이지에서 <strong>developer settings</strong>에 보면 access token에 대한 항목이 있다. token 설정은 <strong>repo</strong>만 설정해놓으면 된다. 여기서 얻은 token은 앞으로 계속 사용하게 될 것이므로 다른 곳에 적어두도록 하자. (github 계정에 대한 권한이 있기 때문에 항상 잘 관리해야 한다.)</p>
<p>Github token이 준비됬다면 이제 <code class="language-plaintext highlighter-rouge">package.json</code>의 <code class="language-plaintext highlighter-rouge">build</code>에 github repository에 대한 정보와 build target을 추가해주자. 이 post에서는 linux용 appImage배포만을 고려한다.</p>
<div class="language-js highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="p">{</span>
<span class="dl">"</span><span class="s2">build</span><span class="dl">"</span><span class="p">:</span> <span class="p">{</span> <span class="c1">// build 예시</span>
<span class="p">...,</span>
<span class="dl">"</span><span class="s2">linux</span><span class="dl">"</span><span class="p">:</span> <span class="p">{</span>
<span class="dl">"</span><span class="s2">target</span><span class="dl">"</span><span class="p">:</span> <span class="p">[</span>
<span class="p">{</span>
<span class="dl">"</span><span class="s2">target</span><span class="dl">"</span><span class="p">:</span> <span class="dl">"</span><span class="s2">appImage</span><span class="dl">"</span><span class="p">,</span>
<span class="dl">"</span><span class="s2">arch</span><span class="dl">"</span><span class="p">:</span> <span class="p">[</span><span class="dl">"</span><span class="s2">ia32</span><span class="dl">"</span><span class="p">,</span> <span class="dl">"</span><span class="s2">x64</span><span class="dl">"</span><span class="p">]</span>
<span class="p">}</span>
<span class="p">]</span>
<span class="p">},</span>
<span class="dl">"</span><span class="s2">publish</span><span class="dl">"</span><span class="p">:</span> <span class="p">[</span>
<span class="p">{</span>
<span class="dl">"</span><span class="s2">provider</span><span class="dl">"</span><span class="p">:</span> <span class="dl">"</span><span class="s2">github</span><span class="dl">"</span><span class="p">,</span>
<span class="dl">"</span><span class="s2">owner</span><span class="dl">"</span><span class="p">:</span> <span class="dl">"</span><span class="s2">{id}</span><span class="dl">"</span><span class="p">,</span> <span class="c1">// owner name</span>
<span class="dl">"</span><span class="s2">repo</span><span class="dl">"</span><span class="p">:</span> <span class="dl">"</span><span class="s2">{repo name}</span><span class="dl">"</span> <span class="c1">// repository name</span>
<span class="p">}</span>
<span class="p">]</span>
<span class="p">}</span>
<span class="p">}</span>
</code></pre></div></div>
<p>이제는 다음 command를 통해 쉽게 publish할 수 있다.</p>
<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nv">GH_TOKEN</span><span class="o">={</span>Github token<span class="o">}</span> electron-builder <span class="nt">-p</span> always
</code></pre></div></div>
<p>위와 같이 <code class="language-plaintext highlighter-rouge">GH_TOKEN</code> 값을 설정해주면 <code class="language-plaintext highlighter-rouge">electron-builder</code>는 알아서 github에 publish하게 된다. bintray에 publish하고 싶다면 해당 token값은 <code class="language-plaintext highlighter-rouge">BH_TOKEN</code>으로 설정해주면 된다.</p>
<p><code class="language-plaintext highlighter-rouge">electron-builder</code>가 github에 publish하는 과정은 다음과 같다.</p>
<ol>
<li><code class="language-plaintext highlighter-rouge">package.json</code>에 설정된 <code class="language-plaintext highlighter-rouge">version</code>값으로 build</li>
<li>해당 <code class="language-plaintext highlighter-rouge">version</code>으로 github repository에 release draft가 있는 지 확인</li>
<li>만약 없다면 release draft를 새로 만든다.</li>
<li>build된 결과물을 release draft의 asset으로 등록</li>
</ol>
<p>여기서 명심해야 하는 것은 이미 같은 version의 release가 있어도 상관없지만 무조건 <strong>release draft</strong>이어야 한다. 만약에 draft가 아니라면 type이 맞지 않는다며 오류가 발생할 것이다. 여기까지 성공적으로 진행했다면 Github repo에 <code class="language-plaintext highlighter-rouge">package.json</code>의 version에 맞는 draft release가 생성되어있고 asset들도 함께 확인할 수 있을 것이다.</p>
<p>매번 이렇게 배포해도 상관없지만 개발이 끝나고 매번 이렇게 하는 건 굉장히 귀찮은 일일 것이다. 이 귀찮음을 해결하기 위해서 이번엔 <strong>Travis CI</strong>를 통해 push할 때마다 자동으로 publish하도록 만들어보자.</p>
<h2 id="travis를-이용한-자동-배포">Travis를 이용한 자동 배포</h2>
<p>보통 Travis와 같은 CI는 github에서 push나 pull request를 할 때마다 자동으로 code test를 하기 위해 사용하는데, 우리가 사용할 기능은 code test 이후에 추가적인 deploy option이다. travis를 사용하기 때문에 추가적인 설정 파일이 필요하지만 그 안에서 돌아가는 배포 과정은 위에서 사용한 electron-builder의 publish option을 그대로 사용하게 된다.</p>
<p>먼저, publish과정에서 사용하게 될 <code class="language-plaintext highlighter-rouge">GH_TOKEN</code>을 Travis의 대상 branch 환경 변수에 <strong>private</strong>으로 저장하자. (Token이 private으로 저장되었음을 꼭 확인하자!!) 만약 Travis가 익숙하지 않다면 다음 <a href="https://hrmrzizon.github.io/2017/04/28/using-travis-ci/">링크</a>를 참고하자. 이렇게 <code class="language-plaintext highlighter-rouge">GH_TOKEN</code>을 환경 변수로 설정하면 이후, Travis build과정에서는 <code class="language-plaintext highlighter-rouge">$GH_TOKEN</code>로 값을 사용할 수 있게 된다.</p>
<p>이제는 Token도 준비되었으므로 다음과 같이 <code class="language-plaintext highlighter-rouge">travis.yml</code> 파일을 작성하자. 가장 기본적인 예시 파일로 이 파일을 기반으로 바꾸어나가면 된다. 여기서 가정한 사항들은 다음과 같다.</p>
<ol>
<li>linux 환경에서만 빌드</li>
<li>deploy는 master branch에 대해서만 수행</li>
<li>deploy를 제외한 나머지 Test는 master, release, develop branch에 대해 수행</li>
</ol>
<p>즉, 아래 설정 중 script 항목까지는 master, release, develop branch에 대한 push, pull request가 발생하면 수행되고 master branch에 대해서만 추가적으로 deploy까지 진행된다.</p>
<div class="language-yml highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1"># .travis.yml</span>
<span class="na">language</span><span class="pi">:</span> <span class="s">node_js</span>
<span class="na">node_js</span><span class="pi">:</span>
<span class="pi">-</span> <span class="m">13.1</span> <span class="c1"># Node version</span>
<span class="na">matrix</span><span class="pi">:</span>
<span class="na">include</span><span class="pi">:</span> <span class="c1"># 원하는 빌드 os</span>
<span class="pi">-</span> <span class="na">os</span><span class="pi">:</span> <span class="s">linux</span>
<span class="na">services</span><span class="pi">:</span> <span class="s">docker</span>
<span class="na">language</span><span class="pi">:</span> <span class="s">generic</span>
<span class="na">sudo</span><span class="pi">:</span> <span class="s">required</span>
<span class="na">dist</span><span class="pi">:</span> <span class="s">trusty</span>
<span class="na">notifications</span><span class="pi">:</span>
<span class="na">email</span><span class="pi">:</span> <span class="no">false</span>
<span class="na">cache</span><span class="pi">:</span>
<span class="na">yarn</span><span class="pi">:</span> <span class="no">true</span>
<span class="na">directories</span><span class="pi">:</span>
<span class="pi">-</span> <span class="s">node_modules</span>
<span class="pi">-</span> <span class="s">$HOME/.cache/electron</span>
<span class="pi">-</span> <span class="s">$HOME/.cache/electron-builder</span>
<span class="na">before_script</span><span class="pi">:</span>
<span class="pi">-</span> <span class="s">git lfs pull</span>
<span class="na">script</span><span class="pi">:</span>
<span class="pi">-</span> <span class="pi">|</span>
<span class="s">if [ "$TRAVIS_OS_NAME" == "linux" ]; then</span>
<span class="s">docker run --rm \</span>
<span class="s">-v ${PWD}:/project \</span>
<span class="s">-v ~/.cache/electron:/root/.cache/electron \</span>
<span class="s">-v ~/.cache/electron-builder:/root/.cache/electron-builder \</span>
<span class="s">electronuserland/builder:12-11.19 \</span>
<span class="s">/bin/bash -c "node --version && yarn install && yarn test"</span>
<span class="s">else</span>
<span class="s">yarn test</span>
<span class="s">fi</span>
<span class="na">deploy</span><span class="pi">:</span>
<span class="na">provider</span><span class="pi">:</span> <span class="s">script</span>
<span class="na">script</span><span class="pi">:</span> <span class="s">bash deploy.travis.sh</span>
<span class="na">skip_cleanup</span><span class="pi">:</span> <span class="no">true</span>
<span class="na">on</span><span class="pi">:</span> <span class="c1"># master branch에만 deploy할 것으로 설정</span>
<span class="na">branch</span><span class="pi">:</span> <span class="s">master</span>
<span class="na">branches</span><span class="pi">:</span> <span class="c1"># 해당 branch에 대해서만 deploy를 제외한 나머지 사항을 공통적으로 적용</span>
<span class="na">only</span><span class="pi">:</span>
<span class="pi">-</span> <span class="s">master</span>
<span class="pi">-</span> <span class="s">release</span>
<span class="pi">-</span> <span class="s">develop</span>
</code></pre></div></div>
<p>위 <code class="language-plaintext highlighter-rouge">.travis.yml</code>에서 사용하는 <code class="language-plaintext highlighter-rouge">deploy.travis.sh</code> script는 다음과 같다.</p>
<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c"># deploy.travis.sh</span>
<span class="c">#! /bin/bash</span>
<span class="k">if</span> <span class="o">[</span> <span class="s2">"</span><span class="nv">$TRAVIS_OS_NAME</span><span class="s2">"</span> <span class="o">==</span> osx <span class="o">]</span><span class="p">;</span> <span class="k">then</span>
<span class="c"># deploy on mac</span>
yarn build
<span class="k">else</span>
<span class="c"># deploy on windows and linux</span>
docker run <span class="nt">--rm</span> <span class="nt">-e</span> <span class="nv">GH_TOKEN</span><span class="o">=</span><span class="nv">$GH_TOKEN</span> <span class="nt">-v</span> <span class="s2">"</span><span class="k">${</span><span class="nv">PWD</span><span class="k">}</span><span class="s2">"</span>:/project <span class="nt">-v</span> ~/.cache/electron:/root/.cache/electron <span class="nt">-v</span> ~/.cache/electron-builder:/root/.cache/electron-builder electronuserland/builder:12-11.19 /bin/bash <span class="nt">-c</span> <span class="s2">"yarn install && yarn release"</span>
<span class="k">fi</span>
</code></pre></div></div>
<p>위 script에서 주목할 점은 docker 환경을 구성할 때 <code class="language-plaintext highlighter-rouge">GH_TOKEN=$GH_TOKEN</code>으로 travis에 설정해놓은 token값을 환경변수로 설정했다는 것이다. 마지막으로 전체 프로젝트를 빌드 후 <code class="language-plaintext highlighter-rouge">electron-builder</code>까지 실행시키도록 <code class="language-plaintext highlighter-rouge">package.json</code>의 <code class="language-plaintext highlighter-rouge">script.release</code>를 설정해주자.</p>
<div class="language-js highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="p">{</span>
<span class="dl">"</span><span class="s2">script</span><span class="dl">"</span><span class="p">:</span> <span class="p">{</span>
<span class="dl">"</span><span class="s2">build</span><span class="dl">"</span><span class="p">:</span> <span class="dl">"</span><span class="s2">run-s react:build electron:build</span><span class="dl">"</span><span class="p">,</span>
<span class="dl">"</span><span class="s2">release:electron</span><span class="dl">"</span><span class="p">:</span> <span class="dl">"</span><span class="s2">electron-builder --publish=always</span><span class="dl">"</span><span class="p">,</span>
<span class="dl">"</span><span class="s2">release</span><span class="dl">"</span><span class="p">:</span> <span class="dl">"</span><span class="s2">cross-env NODE_ENV=production run-s build release:electron</span><span class="dl">"</span>
<span class="p">}</span>
<span class="p">}</span>
</code></pre></div></div>
<p><code class="language-plaintext highlighter-rouge">yarn release</code>를 실행하면 먼저 관련 source code를 빌드한 후에 <code class="language-plaintext highlighter-rouge">electron-builder</code>를 실행하여 publish를 진행한다. (기존의 프로젝트가 react와 electron을 사용한 것이었기 때문에 위와 같은 설정이 사용되었다.) 여기서 주목할 점은 <code class="language-plaintext highlighter-rouge">electron-builder --publish=always</code>을 통해 publish를 진행하는 것이다. 이전의 source code 빌드는 본인의 프로젝트에 맞추어 설정해놓으면 된다.</p>
<blockquote>
<p>사실 위의 예제에서 <code class="language-plaintext highlighter-rouge">yarn release</code>로 했던 것은 <code class="language-plaintext highlighter-rouge">electron builder</code> <a href="https://www.electron.build/configuration/publish#how-to-publish">문서</a>에서 <code class="language-plaintext highlighter-rouge">yarn release</code>로 실행시키면 자동으로 publish option이 추가된다는 내용때문이었는데, 왠지 option 적용이 안되어서 <code class="language-plaintext highlighter-rouge">--publish=always</code> 따로 추가하였다…</p>
</blockquote>
<p>여기까지 설정을 완료했다면 master branch에 push또는 pull request를 해보자. 세부 진행 상황은 travis에서 실시간으로 확인할 수 있으며 결과물은 github에 다음과 같이 asset으로 추가된다.</p>
<center><img src="/img/electron/191206_travis_result.png" alt="structure" />
<figcaption>Fig.1 - Deploy result</figcaption></center>
<hr />
<hr />
<p>이렇게 travis를 통해 자동 배포하는 것을 알아보았는데, 이 기능을 통해 electron auto update까지 할 수 있다고 한다. 관련 post는 기존에 진행중인 프로젝트에 auto update를 적용해보고 작성해야겠다 :)</p>
<h2 id="관련-내용">관련 내용</h2>
<ol>
<li><code class="language-plaintext highlighter-rouge">electron-builder</code> publish <a href="https://www.electron.build/configuration/publish#githuboptions">링크</a></li>
<li><code class="language-plaintext highlighter-rouge">electron</code>과 <code class="language-plaintext highlighter-rouge">react</code>를 사용한 예시 프로젝트 <a href="https://github.com/heiwais25/Turtle">링크</a></li>
<li>travis build 결과 예시 <a href="https://travis-ci.org/heiwais25/Turtle/builds/621120467">링크</a></li>
</ol>JongHyunelectron을 개발한다면 배포를 위해 자연스럽게 사용하는 것은 electron-builder일 것이다. electron-builder를 이용하면 각 운영체제에 맞는 실행 파일을 손쉽게 만들어낼 수 있는데, 이 밖에도 정말 좋은 점은 Github이나 AWS S3등에 배포를 할 수 있다는 것이다. 이 글에서는 electron-builder를 사용한 Github 배포에 대해 알아보고, Travis CI를 통해 이 과정을 자동화하는 것을 알아보겠다.create-react-app과 electron을 사용시에 경로 문제 해결2019-12-05T11:40:00+00:002019-12-05T11:40:00+00:00https://heiwais25.github.io//electron/2019/12/05/electron-react-path<p><code class="language-plaintext highlighter-rouge">electron</code>과 <code class="language-plaintext highlighter-rouge">create-react-app</code>를 개발 과정에서는 별 문제가 없다가도 빌드후에 <code class="language-plaintext highlighter-rouge">electron</code>에서 빌드된 react static file을 참조하는 경우에 다음과 같은 문제가 생길 수 있다.</p>
<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code>Failed to load resource: net::ERR_FILE_NOT_FOUND
</code></pre></div></div>
<p>이는 <code class="language-plaintext highlighter-rouge">create-react-app</code> 빌드시에 기본 경로가 <code class="language-plaintext highlighter-rouge">package.json</code>의 homepage를 기반으로 설정되기 때문에 생기는 문제이다.</p>
<p>이를 해결하기 위해서는 간단하게 <code class="language-plaintext highlighter-rouge">package.json</code>의 homepage를 다음과 같이 설정해주자.</p>
<div class="language-json highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="p">{</span><span class="w">
</span><span class="nl">"homepage"</span><span class="p">:</span><span class="s2">"./"</span><span class="w">
</span><span class="p">}</span><span class="w">
</span></code></pre></div></div>
<p>보다 자세한 내용은 다음 <a href="https://github.com/electron/electron/issues/1769#issuecomment-272069773">링크</a>에서 확인할 수 있다.</p>JongHyunelectron과 create-react-app를 개발 과정에서는 별 문제가 없다가도 빌드후에 electron에서 빌드된 react static file을 참조하는 경우에 다음과 같은 문제가 생길 수 있다.electron에서 nedb를 사용시, nedb 경로 설정 방법과 build시 생길 수 있는 문제 해결2019-12-05T11:12:00+00:002019-12-05T11:12:00+00:00https://heiwais25.github.io//electron/2019/12/05/electron-nedb-path<h2 id="nedb-경로-설정">neDB 경로 설정</h2>
<p><code class="language-plaintext highlighter-rouge">electron</code>에서 nedb와 같은 embedding DB를 사용하기 위해서는 <code class="language-plaintext highlighter-rouge">electron</code>을 배포 시에 접근 가능한 static file을 가지고 있어야 한다. 개발 과정에서는 보통 다음과 같이 경로를 설정해준다.</p>
<div class="language-js highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kd">const</span> <span class="nx">db</span> <span class="o">=</span> <span class="k">new</span> <span class="nx">Datastore</span><span class="p">({</span>
<span class="na">filename</span><span class="p">:</span> <span class="nx">isDev</span> <span class="p">?</span> <span class="dl">"</span><span class="s2">.</span><span class="dl">"</span> <span class="p">:</span> <span class="s2">`</span><span class="p">${</span><span class="nx">app</span><span class="p">.</span><span class="nx">getPath</span><span class="p">(</span><span class="dl">"</span><span class="s2">userData</span><span class="dl">"</span><span class="p">)}</span><span class="s2">/data`</span><span class="p">,</span>
<span class="na">timestampData</span><span class="p">:</span> <span class="kc">true</span><span class="p">,</span>
<span class="na">autoload</span><span class="p">:</span> <span class="kc">true</span>
<span class="p">})</span>
</code></pre></div></div>
<p>위와 같은 방식으로 neDB를 다루면 개발 과정에서는 현재 directory에, 빌드시에는 electron에 고유하게 지정되어 있는 path중 <code class="language-plaintext highlighter-rouge">userData</code> path를 사용하게 된다. 어떤 예제들에서는 기본 경로로 <code class="language-plaintext highlighter-rouge">app.getAppPath()</code>를 사용하는 경우도 있는데, 그렇게 되면 다음과 같은 문제를 낳게 된다.</p>
<h2 id="appgetapppath의-문제점"><code class="language-plaintext highlighter-rouge">app.getAppPath()</code>의 문제점</h2>
<p>DB 경로로 위와 같은 <code class="language-plaintext highlighter-rouge">app.getPath()</code>가 아니라 <code class="language-plaintext highlighter-rouge">app.getAppPath()</code>를 사용하면 <strong>“아마 현재 db가 없거나 cannot write”</strong> 에러가 발생한다.</p>
<p>왜 문제가 발생하는지는 electron에서 배포 파일을 어떻게 관리하는지 먼저 파악해야 한다.</p>
<p><code class="language-plaintext highlighter-rouge">electron</code>을 배포하기 위해서 <code class="language-plaintext highlighter-rouge">elctron-builder</code>을 사용한다고 해보자. 그렇게 electron packaging을 하면 electron에서는 기본적으로 성능 향상을 위해 <code class="language-plaintext highlighter-rouge">asar</code>이라는 format으로 파일을 압축시킨다. 사실상 진짜 파일을 압축했다고 하기보다는 electron application을 실행시에 resource를 빠르게 읽고 메모리에 올릴 수 있게 해준다. 앞에서 언급한 <code class="language-plaintext highlighter-rouge">app.getAppPath()</code>가 바로 이 <code class="language-plaintext highlighter-rouge">asar</code> file의 경로를 가리킨다.</p>
<p>이 경로를 그대로 DB에도 쓸 수 있었으면 좋았겠지만 문제가 하나 있는데 바로 <code class="language-plaintext highlighter-rouge">asar</code>이 바로 <strong>Read-Only</strong>라는 것이다. 즉, <code class="language-plaintext highlighter-rouge">app.getAppPath()</code>를 DB 경로로 사용하게 되면 file을 읽을 수는 있어도 쓸 수 없기 때문에 오류가 발생하게 되는 것이다.</p>
<p>요약하자면 다음과 같다.</p>
<ol>
<li>Static file의 경우 <code class="language-plaintext highlighter-rouge">app.getAppPath()</code>를 쓰자.</li>
<li>DB처럼 읽고 써야하는 경우에는 <code class="language-plaintext highlighter-rouge">app.getPath('userData')</code>처럼 미리 지정된 수정가능한 경로를 사용하자.</li>
</ol>
<p>보다 자세한 내용은 다음 링크들에서 확인할 수 있다.</p>
<h2 id="관련-내용">관련 내용</h2>
<ol>
<li><code class="language-plaintext highlighter-rouge">electron</code>에서 제공하는 경로 설정값 <a href="https://electronjs.org/docs/api/app#appgetpathname">링크</a></li>
<li><code class="language-plaintext highlighter-rouge">electron</code>의 application package process <a href="https://electronjs.org/docs/tutorial/application-packaging#%EC%9D%91%EC%9A%A9-%ED%94%84%EB%A1%9C%EA%B7%B8%EB%9E%A8-%ED%8C%A8%ED%82%A4%EC%A7%95">링크</a></li>
<li>local db를 접근할 수 없는 이유 <a href="https://stackoverflow.com/questions/42900015/electron-packager-can-not-locate-local-db-file-after-packing/42929720#42929720">링크</a></li>
</ol>JongHyunneDB 경로 설정Language Model (3) Entropy, Perplexity derivation2019-10-13T11:01:00+00:002019-10-13T11:01:00+00:00https://heiwais25.github.io//nlp/2019/10/13/Language-model-3<h4 id="language-model-series-post">Language Model Series Post</h4>
<ol>
<li><a href="/nlp/2019/10/06/language-model-1/">N-gram, Perplexity</a></li>
<li><a href="/nlp/2019/10/06/Language-model-2/">Smoothing Method</a></li>
<li><strong>Entropy, Perplexity derivation</strong></li>
</ol>
<hr />
<p>Language Model 첫번째 Post에서 다루었던 Perplexity의 정의는 다음과 같다.</p>
<script type="math/tex; mode=display">PP(W) = P(w_1,\cdots, w_N)^{-\frac{1}{N}}</script>
<p>확률의 역수를 단어의 수로 Normalize한 형태인데, 사실 왜 Perplexity가 이 형태인지는 언급하지 않았고 그냥 Perplexity의 정의가 이러하다라는 느낌으로 지나갔다. Perplexity의 개념을 보다 자세히 이해하고 식을 유도하기 위해서는 정보학적인 관점에서 접근해야 한다. 이번 Post에서는 Entropy에서 시작하여 Perplexity의 유도로 마무리해보겠다.</p>
<h3 id="entropy">Entropy</h3>
<p>Entropy는 정보에 대한 측정 방법으로 측정하고자 하는 변수 <script type="math/tex">X</script>(word, letters, …)에 대한 Entropy <script type="math/tex">H(X)</script>는 다음과 같이 구할 수 있다.</p>
<script type="math/tex; mode=display">H(X)=-\Sigma_{x\in X}p(x)\log_2p(x)</script>
<p><strong>Entropy를 이해하는 Intuitive way는 측정하고자 하는 변수에 대한 정보(distribution, model,…)가 있을 때 이를 평균적으로 어느정도의 값으로 표현할 수 있느냐에 대한 값으로 보는 것</strong>이다. 특히, Log의 base가 2라면 Entropy의 값을 bit 단위로 표현할 수가 있게 된다.</p>
<p>Coin toss를 한 번 예로 들어보자. Fair coin이라면 앞(H)과 뒤(T)가 나올 확률을 0.5씩일 것이다. 그렇다면 이 Coin에 대한 정보를 표현하기 위해 필요한 bit는 몇일까? 경우의 수가 단 2개이며 각각이 같은 확률이기에 0,1을 나타낼 수 있는 1bit만 있어도 될 것 같다. 이를 한번 Entropy를 적용해서 한 번 풀어보아도 역시 1bit가 나오는 걸 확인할 수 있다.</p>
<script type="math/tex; mode=display">% <![CDATA[
\begin{align}
H(coin) &= -p(H)\log_2p(H) -p(T)\log_2P(T) \cr
&= -0.5\log_20.5 -0.5\log_20.5 \cr
&= 1
\end{align} %]]></script>
<h3 id="entropy-of-language">Entropy of Language</h3>
<p>Coin의 값과 같은 간단한 변수에 대해서는 방금 전처럼 쉽게 Entropy를 계산하였지만 여러 단어들로 이루어진 문장의 Entropy는 어떻게 구할 수 있을까?</p>
<p>기본적으로 문장의 Entropy를 구하는 것은 문장의 길이를 정하고 해당 길이 안에서 문장을 구성하는 단어들의 경우의 수를 고려하여야 한다. 언어 L에 대해서 길이기 n인 문장에 대한 Entropy는 다음과 같이 구할 수 있다.</p>
<script type="math/tex; mode=display">H(w_1, \cdots, w_n)=-\Sigma_{w\in L}p(w)\log p(w)</script>
<p>Coin과는 달리 이제는 Entropy가 문장의 길이에 Dependent하기 때문에 이에 대한 효과를 배제하고 보기 위해서는 다음과 같이 <strong>Entropy를 문장의 길이로 나누어주어 Entropy rate</strong>를 사용할 필요가 있다.</p>
<script type="math/tex; mode=display">\text{Entropy rate : }\space \frac{1}{n}H(w_1, \cdots, w_n)=-\frac{1}{n}\Sigma_{w\in L}p(w)\log p(w)</script>
<p>Entropy rate를 이용하면 우리가 다루는 문장들을 포함하는 Language에 대한 Entropy를 구할 수 있게 된다. <strong>Language에 대한 Entropy는 길이를 정의할 수 없기에 무한한 길이에 대한 Entropy rate로 생각해볼 수 있다.</strong></p>
<script type="math/tex; mode=display">H(L)=\lim_{n\rightarrow\infty}-\frac{1}{n}H(w_1, \cdots, w_n)=\lim_{n\rightarrow\infty}-\frac{1}{n}\Sigma_{w\in L}p(w)\log p(w)</script>
<p>하지만 문장의 모든 경우의 수를 고려하고 단어들의 True probability를 알아야 하기에 Language에 대한 Entropy는 불가능하다고 볼 수 있는데, 다행히도 <strong>Shannon-McMillan-Breiman theorem</strong>와 <strong>cross entropy</strong>에 의해 해결할 수 있다.</p>
<h4 id="stationary-and-ergodic-process">Stationary and ergodic process</h4>
<p>이 theorem에 따르면 <strong>언어가 Stationary와 Ergodic인 경우</strong> Language의 Entropy는 다음과 같이 계산할 수 있다.</p>
<script type="math/tex; mode=display">H(L)=\lim_{n\rightarrow\infty} -\frac{1}{n}p(w_1,\cdots,w_n)</script>
<p>즉, 이전 Entropy의 계산처럼 전체 경우의 수를 고려해야 하는게 아니라 <strong>하나의 긴 문장에는 이미 보다 짧은 문장들의 정보가 포함되어 있기 때문에 하나의 긴 문장만 고려해도 된다</strong>는 것이다. 이제는 기존의 Entropy보다 훨씬 간단한 Entropy를 얻을 수 있게 되었는데, Theorem의 전제로 삼은 stationary, ergodic이 신경쓰일 수 있다.</p>
<p>보통 <strong>Stochastic process (random process)의 경우 확률 함수가 시간에 무관한 경우 Stationary라고 하며 시간에 따른 평균이 확률 공간에서의 평균과과 같으면 Ergodic</strong>이라고 할 수 있다. 쉽게 말해서 Random process가 확률이 시간에 따라 바뀌지 않으면 Stationary, ergodic process라고 할 수 있다. <strong>Language model에서의 n gram의 경우 항상 새로운 확률은 앞에서의 단어에만 의존할 뿐, 시간은 고려하지 않기 때문</strong>에 Language는 Stationary ergodic process라고 볼 수 있다. 물론 실제 Language는 앞에서의 단어에만 의존하는 것이 아니라 시간의 경과에 따라 바뀔 수도 있기에 완벽히 Stationary, ergodic process라고 할 수는 없으나 적당한 Error가 있더라도 충분히 계산적인 이점을 얻을 수 있기에 Language를 stationary ergodic process라고 하는 것이다.</p>
<h4 id="cross-entropy">Cross entropy</h4>
<p>앞에서 다룬 Coin의 경우 Fair하다는 가정을 했기에, 우리는 쉽게 Coin의 실제 확률을 알 수 있었고 Entropy도 또한 구할 수 있었다. 하지만, Language처럼 경우의 수가 너무 다양하고 실제 Statistics를 알 수 없는 경우 단어들의 True probability는 구하기가 불가능하다고 볼 수 있다. Cross entropy는 이렇게 Entropy는 구하고 싶지만 True probability는 알 수 없는 경우에 아주 유용한 Metric이다. True probability가 p이고 우리가 가지고 있는 data로 부터 얻은 model probability가 m인 경우의 Cross entropy H(p,m)의 정의는 다음과 같다.</p>
<script type="math/tex; mode=display">H(p,m) = \Sigma_{w} p(w)\log m(w)</script>
<p>물론 data로 부터 얻은 probability distribution m은 True probability와 차이가 있기에 다음과 같이 실제 Variable에 대한 Entropy보다는 항상 크거나 같다. 이는 어찌보면 당연한 결과인데, <strong>우리는 실제 Probability를 모르기 때문에 우리가 추측한 model probability m으로 variable information을 해석하기 위해서는 더 많은 양의 bit가 필요한 것이다.</strong></p>
<script type="math/tex; mode=display">H(p)\le H(p,m)</script>
<p>Cross entropy가 Entropy H(p)의 값에 가까울 수록 variable의 modeling을 더 잘한 것으로 볼 수 있으며 그 척도는 다음과 같이 구할 수 있다.</p>
<script type="math/tex; mode=display">\text{How well accurate model : }\space|H(p,m) - H(p)|</script>
<p>실제 <strong>Variable의 Entropy를 구함에 있어서 Cross entropy로 대체하면 역시 어느정도의 오차가 존재하지만 data로부터 얻은 model probability를 통해 entropy를 구할 수 있기에 오차를 감수하고 entropy를 계산하기 위해 대신 사용</strong>한다.</p>
<p>앞에서 다룬 Stationary ergodic process와 Cross entropy를 적용한 Language의 Entropy는 다음과 같이 구할 수 있다.</p>
<script type="math/tex; mode=display">% <![CDATA[
\begin{align}
H(L) \sim H(p,m) &= \lim_{n\rightarrow\infty}\Sigma_{w \in L} -\frac{1}{n}p(w_1,\cdots,w_n)\log_2 m(w_1,\cdots,w_n)\cr
&\sim \lim_{n\rightarrow\infty}-\frac{1}{n}\log_2 m(w_1,\cdots,w_n)
\end{align} %]]></script>
<h3 id="perplexity-derivation">Perplexity Derivation</h3>
<p>이제 Language의 Entropy를 구할 수 있게 되었으므로 Language의 Entropy구하고 이로부터 Perplexity를 유도해보자. 먼저 Language의 Entropy는 꼭 무한대가 아니더라도 충분히 긴 단어들의 문장을 사용하면 아래의 식처럼 entropy를 근사값으로 구할 수 있을 것이다. (여기서부터는 다시 우리가 구한 Model probability m을 편의상 p로 쓰겠다)</p>
<script type="math/tex; mode=display">H(W)\sim-\frac{1}{n}\log_2 p(w_1, \cdots, w_n)</script>
<p>Entropy는 대상 Variable의 정보를 표현하기 위해 필요한 bit의 수로 볼 수 있다고 했으므로 Entropy로부터 Variable의 정보의 크기를 나타내기 위해서는 다음과 같이 할 수 있을 것이다.</p>
<script type="math/tex; mode=display">% <![CDATA[
\begin{align}
2^{H(W)} &= 2^{-\frac{1}{n}\log_2 p(w_1, \cdots, w_n)} \cr
&= 2^{\log_2 p(w_1, \cdots, w_n)^{-\frac{1}{n}}} \cr
&= p(w_1, \cdots, w_n)^{-\frac{1}{n}} \cr
&= PP(W)
\end{align} %]]></script>
<p>Variable을 나타내는 정보의 양을 Entropy로부터 계산하고 이로부터 자연스럽게 Perplexity를 유도할 수 있었는데, 이는 Perplexity를 다음과 같이 해석해볼 수 있을 것이다.</p>
<blockquote>
<p><strong>Perplexity : Train set과 같은 data로부터 얻은 Model probability를 통해 Language를 해석했을 때 필요한 정보의 양</strong></p>
</blockquote>
<p>Cross entropy와 entropy의 관계에서 보았듯이 <strong>같은 대상을 해석하는 데 있어서 필요한 정보의 양은 적으면 적을수록 우수한 model이라고 할 수 있기에 첫 post에서 언급했던 perplexity가 낮을 수록 우수한 model이라는 말도 자연스럽게 이해가 될 수 있다.</strong></p>
<hr />
<p>이번 Post에서는 Language의 Entropy의 정의와 Entropy를 계산하기 위해 필요한 방법들을 다루었고 마지막으로 Entropy로부터 Perplexity를 유도하였다. 비록 Language model이 최근 들어 주류를 이루고 있는 것은 아니지만 NLP와 계속해서 함께 사용되고 있기에 Language processing이 어떤 길을 걸어왔는지 공부하는 것은 큰 도움이 될 것이다.</p>
<ol>
<li><a href="/nlp/2019/10/06/language-model-1/">N-gram, Perplexity</a></li>
<li><a href="/nlp/2019/10/06/Language-model-2/">Smoothing Method</a></li>
<li><strong>Entropy, Perplexity derivation</strong></li>
</ol>
<h4 id="출처">출처</h4>
<ol>
<li><a href="https://web.stanford.edu/~jurafsky/slp3/">Stanford Speech and Language Processing</a></li>
</ol>JongHyunLanguage Model Series PostLanguage Model (1) N-Gram, Perplexity2019-10-06T22:49:00+00:002019-10-06T22:49:00+00:00https://heiwais25.github.io//nlp/2019/10/06/Language-model-1<h4 id="language-model-series-post">Language Model Series Post</h4>
<ol>
<li><strong>N-gram, Perplexity</strong></li>
<li><a href="/nlp/2019/10/06/language-model-2/">Smoothing Method</a></li>
<li><a href="/nlp/2019/10/13/Language-model-3/">Entropy, Perplexity derivation</a></li>
</ol>
<hr />
<h3 id="what-is-language-model">What is language model?</h3>
<p><strong>Language Model은 단어들로 이루어진 문장에 대한 확률을 계산하는 일종의 언어에 대한 Probability Distribution Function</strong>으로 볼 수 있다. 현재의 NLP가 대세가 되기전 가장 주로 사용되었는데 처음에 Model을 구성하고 나면 그 다음부터는 굉장히 빠르게 <strong>문장의 확률을 계산, 현재 문장 다음에 올 단어 예측등</strong>을 할 수 있다. 속도가 굉장히 빠름에도 불구하고 NLP에게 자리를 빼았긴 건 Train Set에 없던 문장들에 대해 <strong>Out of vocabulary (이하 OOV)</strong>가 Smoothing과 같은 해결책에도 불구하고 결국 큰 문제점으로 작용했기 때문이었다. 현재는 NLP가 주로 사용됨에도 불구하고 Langauge Model은 NLP에 부가적으로 자주 사용되기 때문에 꼭 필요한 개념으로 볼 수 있다.</p>
<p>Language Model에 대한 예로 다음과 같은 문장에 대한 확률을 생각해보자.</p>
<script type="math/tex; mode=display">\text{I can play the piano}</script>
<p>이 문장에 대한 확률을 계산하기 위해서는 앞에서부터 차례대로 각각의 <strong>조건부 확률</strong>을 계산하고 이를 <strong>Chain Rule</strong>로 묶어주면 된다.</p>
<blockquote>
<ol>
<li>I can play the 다음에 piano가 나올 확률</li>
<li>I can play 다음에 the가 나올 확률</li>
<li>I can 다음에 play가 나올 확률</li>
<li>I 다음에 can이 나올 확률</li>
<li>I가 나올 확률</li>
</ol>
</blockquote>
<script type="math/tex; mode=display">P(\text{I, can, play, the, piano}) = \\ P(\text{piano|I, can, play, the})\\ \cdot P(\text{the|I, can, play})\\ \cdot P(\text{play|I,can}) \\ \cdot P(\text{can|I}) \\ \cdot P(\text{I})</script>
<p>위의 식을 일반화하면 다음과 같다.</p>
<script type="math/tex; mode=display">P(w_1, \cdots, w_n) = \Pi_i P(w_i|w_1, \cdots, w_{n-1})</script>
<p>이제 남은 건 다음과 같이 Trainig Corpus안에서 각 Case에 대한 Count를 세어주고 이를 이용해 조건부 확률 계산하여 곱하는 것 뿐이다.</p>
<script type="math/tex; mode=display">P(\text{piano|I,can,play,the})=\\ \frac{count(\text{I, can, play, the, piano})}{count(\text{I, can, play, the})}</script>
<p>굉장히 심플하고 직관적인 방법이지만 이 과정에서는 큰 문제가 존재한다. 만약 우리가 I can play the piano에 대한 확률을 성공적으로 계산할 수 있다고 해보자. 그렇다면, 이를 기반으로 I can play the violin에 대한 확률은 계산할 수 있을까? 이는 불가능한데, <strong>매번 문장의 처음부터 각 단어에 대한 확률을 계산하며 늘려나가기 때문에 I can play the violin에 대해 확률을 계산하려면 무조건 Trainig Corpus에 I can play the violin이 존재해야 한다.</strong> 즉, 위와 같은 방법으로 Language Model을 구성하려면 문장이 구성되는 모든 경우가 Train Set에 있어야 하고 이는 현실적으로 말이 안되는 조건이다.</p>
<h3 id="n-gram">N-gram</h3>
<p>N-gram은 위의 문제를 해결한 방법인데 먼저 <strong>Markov Process</strong>를 살펴보자. Markov process와 Markov Property를 서술한 Markov Assumption의 정의는 다음과 같다.</p>
<blockquote>
<ul>
<li><strong>Markov Process</strong> : Stochastic Process (Random Process) 중 미래 사건에 대한 Conditional Prbability가 오로지 현재의 State에 의전하는 Markov proprty를 가진 process</li>
<li><strong>Markov Assumption</strong> : Markov property가 가정된 대상을 서술하기 위한 용어이다.</li>
</ul>
</blockquote>
<p>이 개념을 Language Model에 적용해보면 이전처럼 현재 단어 이전의 모든 단어에 대한 조건부 확률을 계산할 필요가 없이 Markov assumption에 따라 현재 단어만 고려하거나 앞의 한개 또는 두개 이상의 단어들에 대해서만 조건부확률을 계산하면 된다. 여기서 <strong>몇개의 단어를 고려하느냐에 따라 N-gram이 정해지게 된다.(보통 Trigram까지가 자주 쓰인다)</strong></p>
<ul>
<li>Unigram : 현재의 단어만 고려</li>
</ul>
<script type="math/tex; mode=display">P(w_1, \cdots, w_n) = \Pi_i P(w_i)</script>
<ul>
<li>Bigram : 이전 단어 1개를 함께 고려</li>
</ul>
<script type="math/tex; mode=display">P(w_1, \cdots, w_n) = \Pi_i P(w_i|w_{i-1})</script>
<ul>
<li>Trigram : 이전 단어 2개를 함께 고려</li>
</ul>
<script type="math/tex; mode=display">P(w_1, \cdots, w_n) = \Pi_i P(w_i|w_{i-1}, w_{i-2})</script>
<p>앞에서 다룬 I can play the piano에 대한 확률을 Bigram model을 통해 계산하면 다음과 같다.</p>
<script type="math/tex; mode=display">P(\text{I, can, play, the, piano}) = P(\text{piano|the})\\ \cdot P(\text{the|play})\\ \cdot P(\text{play|can}) \\ \cdot P(\text{can|I}) \\ \cdot P(\text{I})</script>
<p>이제는 함께 고려하는 단어의 수가 줄었기 때문에 적당한 경우의 수에 대해 충분한 통계를 얻을 수 있다. 만약에 Bigram에서 I can play the 다음에 violin이 올 확률을 구하고 싶다면 앞의 한 단어만 고려하기에 다음과 같이 쉽게 계산할 수 있다.</p>
<script type="math/tex; mode=display">P(\text{violin} | \text{I, can, play, the}) \\= P(\text{violin} | \text{the}) = \frac{\text{count(the violin)}}{\text{count(the)}}</script>
<p>N-gram을 통해 Language Model을 만드는 과정은 다음과 같다.</p>
<ol>
<li>Train set에서 전체 단어를 통한 Vocab 구성</li>
<li>Vocab의 각 단어들에 대한 Co-occurence matrix 생성
<ul>
<li>ex) Vocab에 Hello, world가 있다면 Hello가 Hello와 사용된 횟수, world와 사용된 횟수, world가 Hello와 사용된 횟수, world와 world가 사용된 횟수</li>
<li>Unigram의 경우는 이를 구성할 필요없이 각 단어들의 Count만 계산</li>
<li>위 예시는 Bigram의 경우이며 Trigram이상의 Model에 대해서는 함께 고려하는 단어의 수가 하나씩 많아진다.</li>
</ul>
</li>
<li>Co-occurence matrix를 Normalize하여 조건부 확률 계산</li>
</ol>
<h3 id="estimation-of-language-model">Estimation of Language Model</h3>
<p>Language Model은 Unigram, Bigram, Trigram등 여러 가지 Model이 존재할 수 있는데 이들을 평가하는 방법으로는 크게 2가지 방법이 존재한다.</p>
<ol>
<li><strong>Extrinsic</strong> : <strong>특정한 Task</strong>에 대해 End-to-End로 돌려보는 것으로 확실히 비교할 수 있지만 Task Cost가 너무 크다.</li>
<li><strong>Intrinsic</strong> : <strong>Model만으로 바로 비교</strong>할 수 있는 척도를 계산하는 것으로 <strong>application에 무관하게 성능을 평가</strong>할 수 있다.</li>
</ol>
<h4 id="train-set-dev-set-test-set">Train set, dev set, test set</h4>
<p>Intrinsic Method를 적용하기 위해서는 먼저 Train set과 구별된 (held-out) set을 얻을 필요가 있다. 모델 비교를 보다 객관적으로 하기 위함이며 보통 이 set을 test set이라고 한다. Intrinsic method를 이용하여 모델을 비교하는 과정은 다음과 같다.</p>
<ol>
<li>여러 모델들을 Train set을 이용하여 구성</li>
<li>각 모델들을 Train set과 구별된 별도의 Test set에 대한 확률 계산</li>
<li>Intrinsic Method 척도에 따라 최적의 모델 선별</li>
</ol>
<p>하지만 이 방법은 자칫 잘못하면 Test set에만 최적화된 Model을 얻을 수 있기 때문에 중간 검토 set인 dev set을 만들어 가장 적절한 모델을 만들고 최종 비교를 test set로 하기도 한다.</p>
<h4 id="perplexity">Perplexity</h4>
<p>Perplexity(PP)은 Instrinc method 중 가장 대표적인 method로 <strong>얼마나 확률 분포가 데이터를 잘 설명하는지에 대한 값</strong>으로 <strong>문장의 길이로 정규화된 문장 확률의 역수</strong>로 계산하며 그 값이 작을수록 (확률의 값이 클수록) 좋은 모델로 고려한다.</p>
<script type="math/tex; mode=display">PP(W) = P(w_1,\cdots, w_N)^{-\frac{1}{N}} \\= (\Pi_i P(w_i|w_{i-k}, w_{i-1}))^{-\frac{1}{N}} \text{(For k-gram)}</script>
<p>Perplexity는 다음 Post에서 다시 다루겠지만 언어의 <strong>Weighted average branching-factor, 다시 말해 평균적으로 아무 단어에나 뒤따라 나타날 수 있는 단어의 수로 해석</strong>해볼 수 있다. 이를 알 수 있는 좋은 예로 주사위를 들 수 있다. 만약 1~6까지의 숫자 6개로 이루어진 문장에 대한 Perplexity를 계산해보면 다음과 같다. 각 단어의 뒤에 나올 수 있는 단어 경우의 수가 6이라는 걸 생각해보면 branching factor에 대한 이해가 보다 잘 될 수 있을 것이다.</p>
<script type="math/tex; mode=display">PP(dice) = (\frac{1}{6}\cdot\frac{1}{6}\cdot\frac{1}{6}\cdot\frac{1}{6}\cdot\frac{1}{6}\cdot\frac{1}{6})^{-\frac{1}{6}} = 6</script>
<p>Perplexity를 test set에 대해 계산하는 과정은 다음과 같다.</p>
<ol>
<li>Test set을 하나의 긴 문장으로 잇는다.</li>
<li>각 문장을 구분하기 위해 문장의 앞과 뒤에 <code class="language-plaintext highlighter-rouge"><s></code>, <code class="language-plaintext highlighter-rouge"><s /></code>를 추가</li>
<li>문장 전체에 대해 Perplexity를 계산</li>
</ol>
<h3 id="language-model의-한계">Language Model의 한계</h3>
<p>Language Model은 보통 Train set에 전적으로 의존하기 때문에 만약 <strong>Test set이 다른 Domain이거나 dialect를 사용한다면 기존의 모델은 잘 학습되었더라도 형편없게 사용될 수 있다.</strong> 물론, 이는 Domain이나 dialect를 통일하면 어느정도 해결할 수 있겠지만 더 큰 문제는 따로 있다.</p>
<p>N-gram이더라도 항상 Train set에 모든 경우의 수가 있는 것이 아니기 때문에 Co-occurence matrix에는 count가 0인 sparsity problem이 존재한다. 만약 새로운 데이터에 대해 확률을 계산하는 과정에서 해당하는 값에 대한 통계가 없으면 division by zero가 되어 Language Model을 적용할 수 없게 된다. 이를 해결하기 위한 방법 중 대표적인 방법으로는 count가 0인 Matrix 값에 대해 1을 더해주어 확률을 0이 되지 않게 만드는 Smoothing 기법이 있다.</p>
<hr />
<p>여기까지 Language Model의 정의, Language Model의 Statistics 문제를 해결하기 위한 N-gram과 성능 측정을 위한 Perplexity 등을 알아보았는데 다음 Post에서는 Zero probability문제를 해결하기 위해 사용된 Smoothing 기법들과 정보학적 관점에서의 Perplexity에 대해 다뤄보겠다.</p>
<ol>
<li><strong>N-gram, Perplexity</strong></li>
<li><a href="/nlp/2019/10/06/language-model-2/">Smoothing Method</a></li>
<li><a href="/nlp/2019/10/13/Language-model-3/">Entropy, Perplexity derivation</a></li>
</ol>
<h4 id="출처">출처</h4>
<ol>
<li><a href="https://web.stanford.edu/~jurafsky/slp3/">Stanford Speech and Language Processing</a></li>
</ol>JongHyunLanguage Model Series PostLanguage Model (2) Smoothing2019-10-06T22:49:00+00:002019-10-06T22:49:00+00:00https://heiwais25.github.io//nlp/2019/10/06/Language-model-2<h4 id="language-model-series-post">Language Model Series Post</h4>
<ol>
<li><a href="/nlp/2019/10/06/language-model-1/">N-gram, Perplexity</a></li>
<li><strong>Smoothing Method</strong></li>
<li><a href="/nlp/2019/10/13/Language-model-3/">Entropy, Perplexity derivation</a></li>
</ol>
<hr />
<h3 id="smoothing">Smoothing</h3>
<p>Language Model은 Train set의 통계에 기반하여 만들어지는 것이기 때문에 Train set이 언어의 모든 경우를 포함하는 경우(사실상 불가능)를 제외하고는 Co-occurrence matrix 중간 중간에 count가 0인 값들이 존재하게 된다. 이렇게 Count가 0인 항목들에 대해서는 확률이 0으로 나오는 zero probability 문제가 발생하여 Language Model을 범용적으로 사용하기 힘들게 만든다. 이러한 문제점들을 해결하기 위해 사용된 <strong>Smoothing</strong>의 개념은 간단히 말해서 <strong>Train set에서 한번도 나오지 않은 Co-occurrence matrix값들에 대해 0이 아니라 특정값을 추가하여 확률이 Vocab안에 있는 쌍에 대해서는 절대 확률이 0이 나오지 않도록 완화</strong>하는 것이다. Smoothing을 어떻게 하느냐에 따라서 크게 다음과 같이 3개로 나눌 수 있다.</p>
<ol>
<li>Laplace smoothing
<ol>
<li>Add one smoothing</li>
<li>Add-K smoothing</li>
</ol>
</li>
<li>Back off and interpolation</li>
<li>Kneser-Ney smoothing</li>
</ol>
<h4 id="laplace-smoothing-additive-smoothing">Laplace smoothing (Additive smoothing)</h4>
<p>Laplace smoothing은 Smoothing 중 가장 간단한 개념으로 상수를 더해주어 Zero count를 없애서 zero probability 문제를 해결하고자 한 방법이다. 어떤 상수를 더하느냐에 따라 그 모델이 달라지게 된다.</p>
<h5 id="add-one-smoothing">Add-one smoothing</h5>
<p>Add-one smoothing은 생각해볼 수 있는 가장 간단한 Laplace Smoothing으로 <strong>N-gram의 모든 count에 대해 1을 더해주는 것</strong>이다. 방법이 간단하기 때문에 구현하기도 쉽지만 그만큼 성능이 잘 나오지는 않아 최근 잘 사용하지는 않는다. 하지만, Text classification과 같은 비교적 간단한 task에 대해서는 아직까지도 잘 쓰인다고 한다. 그리고 가장 기초적인 Smoothing method이기 때문에 다른 method들의 Baseline 역활을 한다. Unigram에 Add-one smoothing 을 적용한 예시를 들어보면 다음과 같다.</p>
<script type="math/tex; mode=display">\text{Original:}\quad P(w_i)=\frac{C_i}{N}</script>
<script type="math/tex; mode=display">% <![CDATA[
\eqalign{
\text{Add-one:}\quad P(w_i) &=\frac{C_i + 1}{N + V} \\
&=\frac{C_i^*}{N}
} %]]></script>
<script type="math/tex; mode=display">C_i : \text{Count of i-th word}, N = \Sigma_i C_i\\
V: \text{Number of vocab}, C_i^* = \frac{C_i+1}{C_i}\frac{N}{N+V}</script>
<p>위에서 새로운 Count로 바꾼 것은 Add-one smoothing이 적용되었을 때 기존의 Count와 보다 쉽게 비교하기 위함이다. 결과적으로 기존의 Zero였던 값들을 제외하고 나머지의 값들은 전체적으로 Count가 감소하게 된다. 보다 Count가 많을수록 많이 상대적으로 감소하게 되는데 이러한 측면때문에 Smoothing method를 기존의 Count를 깍아서 zero count에 나눠준다는 의미로 <strong>discount method</strong>라고도 한다. 어느정도 Discount가 되었는지는 다음 discount ratio를 통해 구할 수 있다.</p>
<script type="math/tex; mode=display">d_c = \frac{C^*}{C}</script>
<p><strong>Unigram이외에도 Bigram, Trigram 이상에 대해서도 위와 같이 각각에 대해 Count를 1씩 더해주고 이를 Normalize하는 방식</strong>으로 Add-one smoothing을 적용하면 된다.</p>
<h5 id="add-k-smoothing">Add-k smoothing</h5>
<p>Add-one smoothing이 전체 Statistics에 1씩 더해주었다면 Add-k smoothing은 이름에서 짐작할 수 있다시피 전체 Statistics에 k만큼 더해주는 방법이다. 1대신에 조금 더 나은 결과를 얻기 위해 0.2, 0.5등 여러 k 값을 사용하며 여러 시도를 하는 것으로 생각할 수 있는데, train set에 대한 적절한 k값은 dev set을 통해 찾을 수 있을지라도 여전히 제대로 작동하지는 않는다. Add-k smoothing의 확률 함수는 다음과 같이 구할 수 있다.</p>
<script type="math/tex; mode=display">P^*_{add-k}(w_n|w_{n-1}) = \frac{C(w_{n-1}, w_n) + k}{C(w_{n-1}) + kV}</script>
<h3 id="back-off-and-interpolation">back off and interpolation</h3>
<p>하나의 Language Model(Unigram, Bigram 등…)의 성능을 향상시키기 위해 Statistics에 상수를 추가하던 Add-k smoothing과는 달리 back off and interpolation은 여러 Language Model을 함께 사용하여 보다 나은 성능을 얻으려는 방법이다. 여러 Model을 사용한다는 점에서 back off, interpolation method는 공통점을 가지고 있지만 어떻게 적용한지에 따라서 차이가 존재한다.</p>
<h4 id="back-off">back off</h4>
<p>back off를 사전에 검색하면 “뒤로 물러나다”라는 뜻임을 알 수 있는데 language model에서 back off는 그 뜻 그대로 적용된다. 예를 들어, 하나의 Train set으로부터 Unigram, bigram, trigram을 만들었다고 생각해보자. 보통은 trigram이 보다 우수하기 때문에 trigram을 사용하기 마련이다. 하지만, 그만큼 statistics가 부족해 zero-probability 문제가 발생할 수 있는데 그러한 상황에는 trigram이 아니라 bigram의 결과를 사용하는 것이다. 즉, Higher-order (Higher n gram)에서 zero count라면 n-1 gram의 값을 사용하며 이를 Recursive로 사용하게 된다. 기본적인 back off algorithm은 다음 식을 통해 표현할 수 있다.</p>
<script type="math/tex; mode=display">% <![CDATA[
P_{BO}(w_n|w_{n-N+1}, \cdots, w_{n-1}) = \cases{
P(w_n|w_{n-N+1}, \cdots, w_{n-1}) & \text{if} c_{n-1}, \cdots, c_n >0 \cr
P_{BO}(w_n|w_{n-N+2}, \cdots, w_{n-1})
} %]]></script>
<p>하지만 이대로 사용하면 큰 문제가 있는데, <strong>Order가 다른 N-gram을 같이 사용하면 전체 확률의 합이 1을 넘어갈 수도 있게 된다.</strong> 이러한 문제는 각 N-gram에 대한 statistics가 다르기 때문에 이를 해결하기 위해서는 앞에서 Smoothing에서 나온 <strong>discount개념</strong>이 필요하다. 각각에 대해 적절한 값을 곱해주어 보정해주면 된든데 먼저, Back off을 적용할 때 count가 있어서 바로 확률값을 사용할 수 있는 경우에는 discounting된 확률을 사용한다. 그리고 count가 0이어서 한단계 낮은 N-gram에 대해 recursive하게 진행되는 경우에는 <strong>distributing function <script type="math/tex">\alpha</script></strong>를 곱해준다. 이렇게 back off에 smoothing이 적용된 걸 <strong>Katz back off</strong>라고 한다. Katz back off를 적용한 확률은 다음과 같다.</p>
<script type="math/tex; mode=display">% <![CDATA[
P_{BO}(w_n|w_{n-N+1}, \cdots, w_{n-1}) = \cases{
P^*(w_n|w_{n-N+1}, \cdots, w_{n-1}) & \text{if} c_{n-1}, \cdots, c_n >0 \cr
\alpha(w_{n-N+1}, \cdots, w_{n-1})P_{BO}(w_n|w_{n-N+2}, \cdots, w_{n-1})
} %]]></script>
<p>Katz backoff는 보통 <strong>Good Turing</strong>이라 불리는 방법과도 같이 사용되는데 Good-Turing backoff 방법은 Good-Turing smoothing, <script type="math/tex">P^*</script>와 <script type="math/tex">\alpha</script>를 계산하는 과정이 수반된다.</p>
<h4 id="interpolation">Interpolation</h4>
<p>앞에서의 Backoff가 Count가 0인 경우 Lower order n gram의 결과를 Recursive하게 사용하는 방법이었다면 Interpolation은 여러 order n gram 값을 linear combination하여 한번에 사용한다. Unigram부터 Trigram까지의 model을 interpolation하는 경우의 확률은 다음과 같다.</p>
<script type="math/tex; mode=display">% <![CDATA[
\begin{align}
P_{simple}(w_n|w_{n-2}, w_{n-1}) &= \lambda_1P(w_n|w_{n-2},w_{n-1}) &\text{Trigram} \cr
&= \lambda_2P(w_n|w_{n-1}) &\text{Bigram} \cr
&= \lambda_3P(w_n) &\text{Unigram}
\end{align} %]]></script>
<script type="math/tex; mode=display">(\Sigma_i (\lambda_i) = 1)</script>
<p>여기서 각 n gram값의 <script type="math/tex">\lambda</script>는 dev set과 같이 train set과 구별된 held-out corpus로부터 적절한 값을 찾아내면 된다. 위의 Interpolation에서 기본적으로 <script type="math/tex">\lambda</script>는 단순한 상수이지만 이 <strong><script type="math/tex">\lambda</script>를 단어의 함수로 만들어 Context정보를 적용할 수도 있다.</strong> Context가 적용된 Interpolation 확률은 다음과 같다.</p>
<script type="math/tex; mode=display">% <![CDATA[
\begin{align}
P_{context}(w_n|w_{n-2}, w_{n-1}) &= \lambda_1(w_{n-2}, w_{n-1}) P(w_n|w_{n-2},w_{n-1}) &\text{Trigram} \cr
&= \lambda_2(w_{n-2}, w_{n-1}) P(w_n|w_{n-1}) &\text{Bigram} \cr
&= \lambda_3(w_{n-2}, w_{n-1}) P(w_n) &\text{Unigram}
\end{align} %]]></script>
<h3 id="kneser-ney-smoothing">Kneser-Ney smoothing</h3>
<h4 id="absolute-discounting">Absolute Discounting</h4>
<p>마지막 순서답게 이 Smoothing은 가장 잘 작동하는 smoothing으로 <strong>absolute discouning</strong>에 뿌리를 두고 있다. absolute discounting은 Train set과 test set(heldout set) 각각에 대한 bigram의 count를 비교한 Church와 Gale의 실험으로부터 제시되었다. 해당 실험에서는 Train set과 test set으로 각각 2200만개의 단어를 사용하였는데, 아래 표에서 볼 수 있듯이 신기하게도 <strong>train set에서 0, 1번씩 등장한 bigram을 제외하고 나머지 2번 이상 등장한 bigram 쌍들은 평균적으로 test set에서 약 0.75정도번 정도 적은만큼 등장</strong>하였다.</p>
<center><img src="/img/nlp/language_model_2_1.jpg" alt="table" /></center>
<p>이 실험 결과를 보고 쉽게 생각할 수 있는 것은 <strong>전체 통계에 0.75처럼 특정한 값을 빼주는 것</strong>이다. Absolute discounting은 이 아이디어에 기반한 것으로 수정된 특정한 상수를 빼는 Bigram 확률에 unigram을 interpolation한 확률은 다음과 같다.</p>
<script type="math/tex; mode=display">P_{absolute discounting}(w_i|w_{i-1})=\frac{C(w_{i-1}w_i) - d}{\Sigma_vC(w_{i-1}v)} + \lambda(w_{i-1}P_{w_i})</script>
<p>먼저 앞의 항은 <strong>discounted bigram</strong>으로 기존의 bigram 식을 구하는 확률의 분모에 d를 빼는 것을 확인할 수 있다. 여기서의 d는 0.75로 고정해서 사용하거나 보통 0.5를 사용하고 count가 1인 bigram에 대해서만 1로 적용할 수도 있다.</p>
<h4 id="kneser-ney-discounting">Kneser-Ney discounting</h4>
<p>Absolute discounting의 수정된 버전인 Kneser-Ney discounting에서는 <strong>Continuation</strong>이라는 새로운 개념이 등장한다. Continuation에 대해 알기 위해 다음과 같은 문장를 살펴보자.</p>
<script type="math/tex; mode=display">\text{I can't see without my reading ___}</script>
<p>glasses와 같은 단어가 적합해보이지만 만약 Unigram에서 Hong Kong이라는 단어가 많이 나와 Kong이 빈도수가 가장 높다면 Kong이 나오게 될 것이다. Unigram에서는 Context에 대한 정보를 담을 수가 없기 때문인데 이 점을 해결하기 위해서 Continuation이 사용되었다. <strong>Continuation은 단순히 단어의 빈도수를 보는 것이 아니라 다른 단어와 얼마나 함께 사용되었는지</strong>에 주목한다. 이를 적용하기 위해서 Continuation에서는 구하고자 하는 단어가 Bigram의 2번째 단어로 얼마나 사용되었는지 계산하고 전체 Bigram count로 나누어주어 확률을 계산한다.</p>
<script type="math/tex; mode=display">P_{CONTINUATION}(w) = \frac{|\{v:C(vw)>0\}|}{|\{(u', v'):C(u'w')>0)\}|}</script>
<p>앞의 예제에서 다룬 Kong에 대한 Continuation확률은 다음과 같이 구할 수 있다.</p>
<ol>
<li>Kong이 두번째 단어로 나오는 Bigram count를 더한다. (만약, Train set에서 Kong이 Hong Kong에서 6번, King Kong에서 2번나왔다면 전체 Count는 6+2=8이 된다.)</li>
<li>Bigram의 전체 Count를 더한다.</li>
<li>1번에서 구한 Kong의 Continuation Count를 2번에서 구한 값으로 나누면 Kong의 Continuation 확률이 된다.</li>
</ol>
<p>Kneser Ney smoothing은 기본적인 Absolute discounting에 Unigram 대신에 continuation 개념을 적용한 것으로 다음과 같이 얻을 수 있다.</p>
<script type="math/tex; mode=display">P_{KN}(w_i|w_{i-1}) = \frac{max(c_{KN}(w_{i-1}w_i) -d,0)}{C(w_{i-1}) + \lambda(w_{i-1})P_{CONTINUATION}(w_i)}</script>
<script type="math/tex; mode=display">\lambda(w_{i-1}) = \frac{d}{\Sigma_vC(w_{i-1}v)}|\{w:C(w_{i-1}w)>0\}|</script>
<p><script type="math/tex">\lambda</script>는 continuation을 적용하기 위한 normalizing constant로서 discount ratio와 continuation 가중치가 곱해진 것으로 볼 수 있다. 앞에서의 Kneser Ney smoothing은 Bigram에 대한 것으로 일반적인 n gram에 대해서는 다음의 식을 통해 얻을 수 있다.</p>
<script type="math/tex; mode=display">P_{KN}(w_i|w_{n+1}^{i-1}) = \frac{max(c_{KN}(w_{n+1}^{i}) -d,0)}{\Sigma_v c_{KN}((w_{n+1}^{i-1}v))} + \lambda(w_{n+1}^{i-1})P_{KN}(w_i|w_{n+2}^{i})</script>
<p><strong>Back off를 통해 Recursive하게 구한 값을 Interpolation을 통해 더하는 형태</strong>인데 핵심은 <script type="math/tex">c_{KN}</script>에 있다. 이 값은 <strong>Highest order n gram(Unigram, Bigram, Trigram을 쓴 경우 Trigram)의 경우 count 그대로 적용하고 나머지 n gram들에 대해서는 continuation count를 적용</strong>한다. Kneser Ney 확률을 Recursive하게 계속 구하면 마지막 Unigram에 도착할텐데, Unigram의 확률은 absolute discounting에 uniform distribution을 적용하여 다음과 같다.</p>
<script type="math/tex; mode=display">P_{KN}(w)=\frac{max(c_{KN}-d,0)}{\Sigma_{w'}c_{KN}(w')} + \lambda(\epsilon)\frac{1}{V}</script>
<p><script type="math/tex">\lambda(\epsilon)</script>은 Empty string or unknown word <UNK>에 대한 것으로 만약 Vocab에 없는 단어가 나타나면 이에 대한 확률은 $$\frac{\lambda(\epsilon)}{V}$$이 되며 $$\lambda(\epsilon)$$를 조정하여 그 비율을 조정할 수 있다.</UNK></p>
<p>Kneser-Ney smoothing 중 가장 성능이 우스수한 버전은 <strong>Chen과 Goodman (1998)</strong>이 만든 modified Kneser-Ney smoothing으로 discount (d)를 하나만 쓰는 것이 아니라 여러 개를 쓰는 것이다.</p>
<hr />
<p>여기까지 Language model에서 등장하는 zero probability 문제 해결과 성능 향상을 위해 사용된 Smoothing의 여러 방법들에 대해 살펴보았다. 다음 Language model 마지막 Post에서는 이전 Post에서 다루었던 Perplexity의 entropy와의 관계를 Informatics 관점에서 살펴보겠다.</p>
<ol>
<li><a href="/nlp/2019/10/06/language-model-1/">N-gram, Perplexity</a></li>
<li><strong>Smoothing Method</strong></li>
<li><a href="/nlp/2019/10/13/Language-model-3/">Entropy, Perplexity derivation</a></li>
</ol>
<h4 id="출처">출처</h4>
<ol>
<li><a href="https://web.stanford.edu/~jurafsky/slp3/">Stanford Speech and Language Processing</a></li>
</ol>JongHyunLanguage Model Series PostAttention Is All You Need 리뷰2019-08-05T00:00:00+00:002019-08-05T00:00:00+00:00https://heiwais25.github.io//nlp/2019/08/05/Attention-Is-All-You-Need<blockquote>
<ul>
<li>저자 : Ashish Vaswani를 포함한 Google Brain, Google Research</li>
<li>링크 : <a href="https://arxiv.org/abs/1706.03762">Arxiv.1706.03762</a></li>
</ul>
</blockquote>
<p> 이 논문은 NLP에 있어서 한 획을 그었다고 말할 수 있는 논문이다. 저자는 그동안 당연시 여겨졌던 RNN과 같은 Sequential 구조에서 벗어나 <strong>오로지 Attention Mechanism</strong>만을 사용한 구조를 통해 빠른 학습 속도와 SOTA 성능을 보여주었다. 논문을 읽고 나면 Attention is all you need라는 논문의 제목이 정말 잘 지은 것이라고 생각하게 될 것이다.</p>
<h3 id="1-main-idea">1. Main Idea</h3>
<hr />
<p> 한동안 NLP에 있어 RNN 기반의 모델은 항상 주류를 이루었다. LSTM이나 GRU와 같은 모델들이 등장하였고, Attention Mechanism을 사용하여 성능을 향상시켜왔었지만 모두 다 RNN Encoder-Decoder를 기반으로 하고 있었다. 물론 이밖에도 CNN을 기반으로 한 모델들도 좋은 성능을 보여주었었으나 각각은 다음과 같은 문제점들을 가지고 있었다.</p>
<ul>
<li>RNN
<ul>
<li>Sentence의 symbol들을 따라 계산을 진행하기 때문에 <strong>길이에 비례하여 계산량 증가</strong></li>
<li>각각의 단계(Layer)에서 hidden state를 계산하는 과정이 이전 계산값을 기반으로 하기 때문에 <strong>병렬화가 어려움</strong></li>
</ul>
</li>
<li>CNN
<ul>
<li>보다 나은 병렬화가 가능하지만 일반적으로 RNN보다 kernel width만큼 더 계산량이 많음</li>
</ul>
</li>
<li>공통
<ul>
<li>문장의 길이가 길어짐에 따라 Attention Mechanism에 대한 계산량이 많아짐</li>
</ul>
</li>
</ul>
<p>결국 주된 문제는 <strong>Sequential Computation</strong>에서 온다고 할 수 있다. 저자는 이를 해결하기 위해 Attention을 Encoder-Decoder 사이에만 적용하는 것이 아니라 Attention을 Encoder와 Decoder 내부에 Self-Attention의 개념으로 적용한 <strong>Transformer</strong>를 제시하였다. 이 모델은 다음과 같은 장점을 가지고 있다.</p>
<ol>
<li>Sentence에 대한 Sequential Computation을 사용하지 않아 <strong>병렬화 가능</strong></li>
<li>Input-Output에 대한 <strong>Global dependency를 상수 시간에 계산 가능</strong></li>
<li><strong>단 12시간</strong>, P100 GPU를 8개 사용하여 SOTA 달성</li>
</ol>
<h3 id="2-model-구조">2. Model 구조</h3>
<hr />
<p> Transformer의 구조는 다음 Figure 1과 같이 이루어져 있다. Encoder와 Decoder는 Figure 1에 나타난 Layer가 stack되어 있는 구조이며 논문에서는 각각 6개의 Layer를 사용했다고 한다.</p>
<center><img src="/img/nlp/arxiv1706_img1.png" alt="structure" /></center>
<h4 id="21-encoder-and-decoder-stacks">2.1 Encoder and Decoder stacks</h4>
<p><strong>Encoder</strong></p>
<p> 각 layer는 다음 2개의 part로 이루어져있으며 각각의 계산을 진행할 때는 <strong>Residual Connection</strong>과 <strong>Layer normalization</strong>을 적용한다.</p>
<ul>
<li>Multi-head attention</li>
<li>Fully connected layer</li>
</ul>
<p><strong>Decoder</strong></p>
<p> Encoder와 비슷하지만 RNN처럼 정보가 왼쪽에서 오른쪽으로만 전해질 수 있도록 한 Masked Multi-head attention이 추가되어있다.</p>
<ul>
<li>Masked Multi-head attention</li>
<li>Multi-head attention</li>
<li>Fully connected layer</li>
</ul>
<h4 id="22-attention">2.2 Attention</h4>
<center><img src="/img/nlp/arxiv1706_img2.png" alt="structure" /></center>
<p> Transformer에서의 attention은 먼저 Multi-head attention을 이루고 있는 <strong>Scaled Dot-product attention</strong>을 살펴보아야 한다. 크게 Attention에 사용되는 값은 3가지로 나눌 수 있는데, 각각은 <strong>Query(Q), Key(K), Value(V)</strong>이다. 이 3개의 값은 Encoder-Decoder의 각 부분에 따라 값이 달라지는데 계산 과정을 쉽게 생각해보면 다음과 같이 생각해볼 수 있다.</p>
<ol>
<li>Query는 단어 그대로 <strong>질의하는 입장</strong>으로 생각하자.</li>
<li>Query가 들어오게 되면 Key값과의 계산을 통하여 기준 값 Key에 대한 Query의 상대적인 <strong>weight(softmax)를 계산</strong>한다.</li>
<li>이 Weight를 Value에 곱함으로써 Query와 Key의 <strong>연관성</strong>을 기준으로 한 새로운 값을 얻게 된다.</li>
</ol>
<p> 이 과정을 통해 Attention이 적용된 값을 얻을 수가 있는데, Transformer에서는 여기서 멈추지 않고 <strong>Scaled Dot-Product attention을 겹쳐놓은 Multi-Head attention</strong>을 사용하였다. Figure 2의 오른쪽을 보면 h개만큼 Scaled Dot-Product Attention이 사용된 것을 알 수 있다. 각각의 Attention에는 이전처럼 Query, Key, Value가 사용되었는데, 차이점은 이 세 값들을 바로 사용하는 것이 아니라 <strong>h개의 Attention각각에 대해서 다르게 초기화된 Parameter Matrix</strong>를 곱하여 (Projection) 사용한다. 이후, Scaled Dot-Product Attention을 통하여 h개의 다른 값들을 얻게 되고 이를 Concat하여 Attention 결과물을 얻게 된다.</p>
<p>h개의 다른 Parameter Matrix를 사용하여 Attention을 계산하기 때문에 Multi-head attention은 단 하나의 Scaled Dot-Product Attention을 사용할 때보다 주어진 Query, Key, Value에 대해서 보다 다양한 상황(subspace)에 대한 attention을 계산할 수 있게 된다. 이 Multi-head attention은 Transformer에서 크게 세 부분에 사용된다.</p>
<center><img src="/img/nlp/arxiv1706_img1.png" alt="structure" /></center>
<ol>
<li>
<p><strong>Encoder의 Multi-head attention</strong></p>
<ul>
<li>Self attention에서의 <strong>Query, Key, Value는 모두 같은 Input</strong>값이다. 즉, Input으로 주어진 같은 문장의 요소간의 연관성(Attention)을 계산함으로써 말그대로 스스로에 대한 <strong>연관성(Self-attention)</strong>을 계산하는 것이다. 이 과정을 통하여 이전 RNN에서 hidden state를 통해 계산했던 context vector를 대체한다.</li>
</ul>
</li>
<li>
<p><strong>Decoder의 Masked multi-head attention</strong></p>
<ul>
<li>Encoder와 비슷하게 <strong>Query, Key, Value로 같을 값을 사용하지만 여기서는 Masked가 추가되있음</strong>에 주목하자. Encoder와 달리 Decoder에서는 Input Sentence가 전부 주어지는 것이 아니라 하나의 Symbol씩 계산해나간다. 하지만, Transformer에서의 attention을 계산할 때는 하나의 sentence 전체를 한번에 계산하기 때문에 decoder의 경우 아직 계산되지 않은 Sentence 뒤쪽 값에 대한 attention이 포함될 수 있다. 이를 막기 위해 Mask 과정이 추가되었는데, 이는 Query와 Key값 과정 중 현재 계산 대상이 된 symbol 뒤의 값들을 모두 $-\inf$ 처리하여 weight 자체를 0으로 만들어버린다. 이 과정은 다르게 말하면 <strong>정보의 전달이 Sentence 뒤에서 앞이 아닌 무조건 앞에서 뒤로 흘러갈 수 있도록 설정</strong>했다고 할 수 있다.</li>
</ul>
</li>
<li>
<p><strong>Decoder의 Multi-head attention</strong></p>
<ul>
<li>여기서는 Query로 Decoder의 이전 Layer output값이 사용되며 Key, Value로 Encoder output layer의 값이 사용된다. 이전에 이야기한것처럼 Decoder의 계산되는 output에 Encoder에서 계산된 input sentence의 information이 적용되었다고 생각해볼 수도 있을 것이다.</li>
</ul>
</li>
</ol>
<p>저자가 이야기하는 기존 모델들과 비교해보았을 때, Self-attention이 같는 장점은 다음과 같으며 Table 1을 보면 보다 자세히 알 수 있다.</p>
<ol>
<li>Layer당 <strong>전체 계산량이 줄어든다.</strong></li>
<li><strong>계산이 병렬화될 수 있다.</strong></li>
<li><strong>dependency를 계산할 때 필요한 Path length의 최대값이 상수</strong>이다. (RNN은 Sentence의 길이만큼 CNN은 Sentence의 길이의 kernel size로 log를 취한 값만큼 필요하다.)</li>
<li>이전 Sequential Computation Model들보다 <strong>Context vector를 구함에 있어 Dependency를 기반으로 하여 설명이 용이</strong>하다.</li>
</ol>
<center><img src="/img/nlp/arxiv1706_img3.png" alt="structure" /></center>
<h4 id="23-position-wise-feed-forward-networks">2.3 Position-wise Feed-Forward Networks</h4>
<p>Encoder, Decoder의 각 layer 마지막에는 Fully connect network가 사용되는데, 여기서는 ReLU가 적용된 두번의 Linear transformation이 계산된다.</p>
<script type="math/tex; mode=display">\text{FFN}(x) = max(0,xW_1+b_1)W_2+b_2</script>
<h4 id="24-embeddings-and-softmax">2.4 Embeddings and Softmax</h4>
<p>다른 번역 모델처럼 Input, output token들에 대해 기존에 학습된 embedding을 사용하였고, 각각의 embedding layer는 공통된 weight matrix를 가지고 있다.</p>
<h4 id="25-positional-encoding">2.5 Positional Encoding</h4>
<p>Attention에서 언급했던 것처럼 Transformer는 하나의 sentence를 한번에 계산하기 때문에 기본적으로 단어의 앞 뒤 순서 정보를 따로 가지고 있지 않다. 이를 위해 저자는 Positional Encoding을 Encoder, Decoder stack 각각에 사용하였는데, 먼저 Positional encoding을 계산하고 이를 input 값에 더하였다. Positional Encoding으로는 다음과 같이 이론화하기 쉽고 학습에 용이성을 가진 position에 따른 $\sin$, $\cos$모델을 사용하였다. (아래 식에서 $i$는 dimension $pos$는 sentence에서의 position을 의미한다.)</p>
<p>
$$PE_{(pos,2i)}=\sin(pos/10000^{2i/d_{model}})$$
$$PE_{(pos,2i+1)}=\cos(pos/10000^{2i/d_{model}})$$
</p>
<h3 id="3-conclusion">3. Conclusion</h3>
<hr />
<p>저자는 총 다음과 같이 3가지의 연구를 진행하였다.</p>
<ol>
<li>
<p><strong>Transformer base Model과 big model을 사용한 WMT’14 En → Fr , En → De Data 학습</strong></p>
<ul>
<li>Table 2와 같이 <strong>보다 짧은 시간에 적은 Training cost로 SOTA 달성</strong></li>
</ul>
<center><img src="/img/nlp/arxiv1706_img4.PNG" alt="structure" /></center>
</li>
<li>
<p><strong>다음 Table3과 같이 Transformer의 구조 hyper parameter variation에 대한 성능, 복잡도 비교</strong></p>
<center><img src="/img/nlp/arxiv1706_img5.PNG" alt="structure" /></center>
</li>
<li>
<p><strong>다음 Table4와 같이 작은 모델, 적은 데이터를 통한 English constituency (long sentence)에 대한 연구</strong></p>
<center><img src="/img/nlp/arxiv1706_img6.PNG" alt="structure" /></center>
</li>
</ol>
<h3 id="4-key-points">4. Key points</h3>
<hr />
<ul>
<li>Attention을 모델의 일부분으로 사용한 것이 아니라 attention만을 사용한 첫 모델</li>
<li>Self-attention을 통한 Input sentence에 대한 dependency 계산</li>
<li>기존 모델들 대비 훨씬 적은 계산량</li>
<li>Sentence를 한 번에 계산하여 병렬 계산 가능</li>
<li>짧은 시간의 학습을 통하여 SOTA 달성</li>
</ul>JongHyun저자 : Ashish Vaswani를 포함한 Google Brain, Google Research 링크 : Arxiv.1706.03762Effective Approaches to Attention-based Neural Machine Translation 리뷰2019-06-26T00:00:00+00:002019-06-26T00:00:00+00:00https://heiwais25.github.io//nlp/2019/06/26/Effiective-Approaches-to-Attention-based-Neural-Machine-Translation<blockquote>
<ul>
<li>저자 : Minh-Thang Luong Hieu Pham Christopher D. Manning</li>
<li>링크 : <a href="https://arxiv.org/abs/1508.04025">Arxiv.1508.04025</a></li>
</ul>
</blockquote>
<p> CS 224n으로도 유명한 Manning 교수가 이끌고 있는 Stanford NLP group에서 작성한 논문이다. 이 논문에 제시한 Neural Machine Translation (이하 NMT) 모델은 Attention Mechanism 기반의 모델로서 당시 몇몇 Task에서 State-of-the-art를 보여주는 놀라운 성과를 보여주었다. 다른 분야에서 각광받던 <strong>Attention Mechanism을 저자의 말에 따르면 NMT에 처음으로 적용한 사례</strong>였고 놀라운 결과와 분석을 통하여 NMT에서의 Attention Mechanism의 시작을 알렸다고 할 수 있을 것 같다.</p>
<h3 id="basic-idea">Basic Idea</h3>
<p> 우선, NMT에 대해서 잠깐 짚고 넘어가자. 그동안 많이 봤듯이, NMT에 있어서 가장 널리 쓰이는 Sequence to Sequence의 기본적인 구조는 다음 Figure와 같으며 주요 계산은 다음 3단계로 구분할 수 있다.</p>
<ol>
<li>Encoder에서 Source sequence를 차례대로 계산</li>
<li>한 Input Sequence에 대한 Context vector를 계산</li>
<li>Decoder에서 이를 바탕으로 Target sequence를 계산</li>
</ol>
<center><img src="/img/nlp/arxiv1508_img1.png" alt="structure" /></center>
<p> <strong>Context vector는 Source sequence의 정보를 하나의 vector에 담고 있는 것</strong>으로 볼 수 있는데 이는 <strong>그동안의 모델에서는 Decoder의 첫 hidden state를 계산할 때만 사용</strong>되었다. 이 접근 방식은 구조를 간단하게 만들 수는 있지만 Source와 Target의 관련성을 이용하는 Alignment 측면에서는 몇가지 문제점이 생각된다. 먼저, Context vector를 처음에만 사용하기에 나중 Target symbol을 계산함에 있어서는 충분히 정보가 반영되지 않을 수 있다. 그리고 무엇보다 <strong>Source와 Target의 관계는 symbol마다 다르기 때문에 고정된 Context vector를 사용하는 것은 제대로된 context를 전달해주지 못할 수 있다.</strong></p>
<p> 이러한 문제들을 해결하는 것이 Attention Mechanism인데 저자들은 이를 적용하기 위해 LSTM으로 Encoder Decoder를 구성하고 Decoder에서 target symbol을 계산하기 위해 <strong>각 Decoder의 hidden state와 함께 현재 target과 관련된 정보들을 충분히 담고 있는 context vector를 계산하여 사용</strong>했다. 이는 계산량은 늘었을지는 몰라도 Decoder에서 각 state에 맞는 source sentence의 정보를 반영할 수 있는 해결책이었다. 다음 수식을 통해 조금 더 자세히 살펴보자.</p>
<p>
$$\tilde{h}_t=\text{tanh}(W_c[c_t;h_t])$$
$$p(y_t|y_{\lt t}, x)=\text{softmax}(W_s\tilde{h}_t)$$
$$\begin{cases}
c_t &: \text{context vector at time t} \\
h_t &: \text{hidden state in decoder at time t} \\
\tilde{h}_t &: \text{attentional vector at time t} \\
\end{cases}$$
</p>
<p> 수식에서는 딱 한 가지만 주목하면 되는데, <script type="math/tex">\tilde{h}_t</script>를 계산할 때 각 state에 따라 다른 context vector를 사용한다는 점이다. 이 차이점은 <strong>Target symbol들과 Source symbol들간의 alignment를 적용할 수 있게 해주었으며 충분히 Source의 context를 전달할 수 있게 해주었다.</strong> 이 아이디어를 기반으로 하여 제시된 모델은 총 2가지로 각각은 context vector를 계산할 때 어느정도 범위의 Source sentence를 사용하는지에 있어 차이를 보인다.</p>
<blockquote>
<ol>
<li>Global Attention : 모든 Source 단어들을 고려한다.</li>
<li>Local Attention : 특정 영역의 한정된 숫자의 단어들을 고려한다.</li>
</ol>
</blockquote>
<h4 id="global-attention">Global Attention</h4>
<p> Global Attention은 특정 target symbol과 source와의 alignment를 계산함에 있어 다음 Figure에서 볼 수 있듯이 전체 source를 사용한다. alignment되는 정도는 다음의 식을 통해 계산되는데, 저자들은 가장 기본적인 location 방법 이외에 총 3가지의 방법을 이용하여 score를 계산하였다.</p>
<center><img src="/img/nlp/arxiv1508_img2.png" alt="structure" /></center>
<p>
$$a_t(s)=\text{align}(h_t,\bar{h}_s)= \frac{\exp{(\text{score}(h_t,\bar{h}_s)})}{\Sigma_{s^{'}}\exp{(\text{score}(h_t,\bar{h}_{s^{'}})})}$$
$$\text{score}(h_t,\bar{h}_s)=\begin{cases}
h_t^{\intercal}\bar{h}_s & \text{dot} \\
h_t^{\intercal}W_a\bar{h}_s & \text{general} \\
W_a[h_t^{\intercal};\bar{h}_s] & \text{dot} \\
\end{cases}$$
$$a_t = \text{softmax}(W_ah_t) \text{: location}$$
</p>
<p> 여기서 얻어지는 alignment vector는 후에 context vector <script type="math/tex">c_t</script>를 계산하기 위해서 source의 각 hidden state <script type="math/tex">h_t</script>에 대한 weighted factor로 적용된다. 즉, 현재 target에 대한 average context로 볼 수 있겠다.</p>
<script type="math/tex; mode=display">c_t = \Sigma_i h_i a_i</script>
<p> 저자가 추가적으로 언급하였듯이 이 모델과 2015년 Bahdanau가 제시한 모델과 비슷해보일 수 있지만, Bahdanau보다 간단히 top layer의 hidden state를 사용하였으며 이전 hidden state가 아니라 현재 hidden state를 사용하여 보다 간단하고 일반화되어있다는 점에서 차별화를 둘 수 있겠다.</p>
<h4 id="local-attention">Local Attention</h4>
<p> Global Attention는 항상 Source 전체를 계산해야하기 때문에 <strong>Paragraph와 같이 깉 문서일 수록 Compuational Cost가 커진다.</strong> 이를 해결하기 위해서 제시된 두번째 모델 <strong>Local Attention</strong>은 다음 Figure처럼 <strong>Source의 일부분만을 이용하여 alignment를 계산한다.</strong> 이는 Xu가 제안한 soft and hard attention으로부터 영향을 받은 것으로 <strong>alignment position</strong>을 계산하여 주변 값만을 사용한다.</p>
<center><img src="/img/nlp/arxiv1508_img3.png" alt="structure" /></center>
<p> alignment position을 계산하기 위해서 저자는 2가지 방법을 제시하였다. 첫번째는 <strong>monotonic alignment</strong>로 source와 target 단어의 순서가 비슷하다고 보는 것이며, 두번째는 <strong>predictive alignment</strong>로 다음의 식을 통해서 계산한다. sigmoid의 결과값은 항상 0~1이므로 alignment point가 0~S임을 알 수 있다.</p>
<p>
$$p_t=S\cdot \text{sigmoid}(v_p^{\intercal}\tanh(W_ph_t))$$
$$\begin{cases}
W_p, v_p &: \text{parameter of prediction to be trained} \\
S &: \text{Source sentence length} \\
\end{cases}$$
</p>
<p> 위에서 구한 alignment point를 기준으로 alignment vector를 계산하는데, 이는 global attention 때와 같은 align 함수에 point를 중심으로 주변의 gaussian distribution을 곱해서 구해낸다. 이 접근 방법은 좋은 성능을 보여주었던 2015년 Gregordml 모델이 <strong>image generation에서 사용한 target을 중심으로 한 zoom</strong>과 비슷하게도 볼 수 있다.</p>
<script type="math/tex; mode=display">a_t(s) = \text{align}(h_t,\bar{h}_s)\exp(-\frac{(s-p_t)^2}{2\sigma^2})</script>
<h4 id="input-feeding-approach">Input-feeding Approach</h4>
<p> 마지막으로 저자가 사용한 테크닉은 다음 Figure처럼 <strong>attention layer를 encoder와 decoder에 걸쳐 적용하는 것</strong>이었다. 이 방법을 적용한 목적은 이전에 <strong>어떤 align이 된지에 대한 정보까지 뒤의 decoding 과정에 적용하는 것이며 또한 수평, 수직으로 길게 확장되는 네트워크를 만들기 위함</strong>이라고 한다. 심플하면서도 의도한 바를 적용하기 위한 좋은 방법으로 보인다.</p>
<center><img src="/img/nlp/arxiv1508_img4.png" alt="structure" /></center>
<h3 id="experimental-setup">Experimental Setup</h3>
<ul>
<li><strong>Dataset</strong> : WMT English-German을 사용하였으며 newstest2013이 development set으로 사용되었다. 각 언어에서 빈번하게 사용되는 50,000개의 단어를 사용하였고, 여기 포함되지 않은 단어들은 [UNK] Token 처리되었다.</li>
</ul>
<h3 id="result">Result</h3>
<p> 결과 비교는 WMT ‘14와 WMT ‘15를 사용하였는데, 결과가 굉장히 인상적이다. WMT ‘14, WMT ‘15 English to German에서는 Best result가 <strong>기존 SOTA system을 능가하는 모습</strong>을 보여주었고 <strong>Attention을 포함한 여러 가지 테크닉을 하나씩 적용함에 따라 증가하는 점수들</strong>을 일일히 보여주고 있다. (첫번째 표는 WMT’14, 두번째 표는 WMT’15이다.)</p>
<center><img src="/img/nlp/arxiv1508_img5.png" alt="structure" /></center>
<center><img src="/img/nlp/arxiv1508_img6.png" alt="structure" /></center>
<p>WMT ‘15 German to English에서는 기존 SOTA만큼은 아니지만 그 만큼의 점수를 보여주었다.</p>
<center><img src="/img/nlp/arxiv1508_img7.png" alt="structure" /></center>
<p> 결과를 비교하는 것 이외에도 저자는 newstest2014를 이용한 Learning Curve 분석, Long sentence에 대한 BLEU, Attentional Architecture 그리고 Alignment quality에 대한 분석을 수행하였다.</p>
<h3 id="conclusion">Conclusion</h3>
<p> 이 논문에서 저자는 기존의 Attention Mechanism을 적용한 2가지 종류의 모델을 제시하였으며 그 결과는 기존 SOTA를 능가하거나 근접하여 굉장히 성공적이었다. 전체 Source sentence를 참고하는 global attention과 일부분을 보는 local attention 중 결과적으로 local attention이 더 좋은 성능을 보여주었는데, 이는 전체 sentence를 계산할 때 source에서 중요한 부분이 상대적으로 덜 반영됬기 때문으로 생각된다. 성공적인 결과 외에도 Attention을 포함한 technique을 세세하게 분석한 내용은 앞으로 연구를 진행함에 있어서 참고할 만한 좋은 방향으로 생각된다.</p>JongHyun저자 : Minh-Thang Luong Hieu Pham Christopher D. Manning 링크 : Arxiv.1508.04025Google’s Neural Machine Translation System 리뷰2019-06-25T00:00:00+00:002019-06-25T00:00:00+00:00https://heiwais25.github.io//nlp/2019/06/25/GNMT<blockquote>
<ul>
<li>저자 : Yonghui Wu, Mike Schuster… (Google)</li>
<li>링크 : [Arxiv.1609.08144][https://arxiv.org/abs/1609.08144]</li>
</ul>
</blockquote>
<p>읽으면 읽어갈수록 역시 구글이라는 소리가 나오게 하는 논문이다. 당시 NMT의 문제점들을 먼저 명시하고 이를 어떻게 해결하였는지 아주 세세하게 설명을 해주고 있다. 비록 3년이 지난 논문이지만 NMT를 공부해가는 입장에서 많은 도움을 받을 수 있었다. 새롭게 NMT, Attention 관련 공부를 하는 상황이라면 이 논문을 제대로 읽어보는 것을 추천한다.</p>
<h3 id="1-main-idea">1. Main Idea</h3>
<hr />
<p>기존의 NMT는 큰 성공을 거두고 있었지만, Large Scale 시스템의 경우 정확도 측면에서 여러 어려움을 가지고 있었다. 저자가 제시한 주요 문제점 3가지는 다음과 같다.</p>
<ol>
<li>Training과 Inference에 있어서 <strong>너무 느리다</strong>.</li>
<li><strong>빈도수가 적은 단어의 처리</strong>가 비효율적이다.</li>
<li>때때로 Source <strong>문장 전체가 번역이 안될때가 있다</strong>.</li>
</ol>
<p>실제로 당시 Google Translator과 같이 상용에 사용되고 있던 Large Scale 시스템들은 통계 기반 SMT의 대표격인 PBMT를 쓰고 있었다. 이러한 문제들을 해결하기 위해서 저자가 새롭게 제시한 방법 3가지는 다음과 같다.</p>
<ol>
<li>속도 측면에서의 향상을 주기 위하여 parallelism과 quantization, TPU 사용</li>
<li>Wordpiece model을 적용</li>
<li>Length normalization과 coverage penalty를 decoder beam search 과정에 적용</li>
</ol>
<p>이 밖에도 Residual Connection이나 강화학습 등을 적용하였는데, 새롭게 제시된 모델 GNMT는 기존의 문제들을 효과적으로 해결하여 점수를 향상시켰을 뿐만 아니라 기존 모델들 대비 Training, Inference 속도까지 향상시켰다.</p>
<h3 id="2-model-구조">2. Model 구조</h3>
<hr />
<p>기본적인 구조는 Encoder - Attention - Decoder로 이루어져있다. (Fig.1)</p>
<ul>
<li>Encoder : 8개의 LSTM (1 bi-direction + 7 uni-direction)</li>
<li>Attention : 1 Hidden layer의 Feed Forward Neural Network</li>
<li>Decoder : 8개의 LSTM (8 uni-direciton)</li>
</ul>
<center><img src="/img/nlp/arxiv1609_img1.png" alt="structure" />
<figcaption>Fig.1 - GNMT Structure</figcaption></center>
<h4 id="residual-connection">Residual Connection</h4>
<p>통산 Deep LSTM이 Shallow LSTM보다 좋은 성능을 내는 것은 알려졌지만, 이를 학습시키는 과정에서는 Deep한 구조때문에 <strong>Vanishing or exploding gradient문제가 발생</strong>했었다. <strong>Residual connection</strong>은 이를 해결하기 위해 사용되었는데, LSTM의 Output을 다음 LSTM의 Input으로 사용하는 과정에서 추가적으로 <strong>이전 Input을 더하는 방법</strong>이다. (Fig.1의 Encoder part) 이를 통하여 Deep network에서의 gradient problem을 해결할 수 있었다고 한다.</p>
<h4 id="bi-directional-encoder-for-first-layer">Bi-directional Encoder for first layer</h4>
<p>보통 Source sentence들을 <strong>Left → right의 방향성</strong>을 띄고 있기 때문에 Encoder에서는 이를 순서대로 계산해나가지만 language pair의 경우에 따라 <strong>target에 대한 information이 source에서 역순 또는 넓게 분포</strong>해있을 수도 있다. 이를 해결하기 위해서 한 방향으로만 계산하는 것이 아니라 양 방향으로 source sentence를 계산해나가는 bi-directional encoder를 사용하였다. (Fig.1의 Encoder의 첫번째 Layer) Bi-directional encoder는 <strong>Encoder의 첫번째 layer에만 사용</strong>되었는데, 그 이유는 최대한 parallelism을 반영하여 속도를 높이기 위함이었다.</p>
<h4 id="model-parallelism">Model parallelism</h4>
<p>이 모델은 속도를 향상시키기 위해서 Model parallelism과 Data parallelism을 적용하였다.</p>
<ul>
<li>Data parallelism : 같은 모델의 parameter를 공유하는 <strong>n개의 모델 replica</strong>를 만들었으며 <strong>Downpour SGD algorithm</strong>를 사용하여 각각 학습해 나감과 동시에 각각의 <strong>replica와 비동기적으로 ADAM과 SGD algorithm을 이용</strong>하여 학습하였다.</li>
<li>Model paralleism : 이 논문에서 사용한 모델은 첫번째 layer를 제외하고는 <strong>uni-direction</strong>이기 때문에 각각의 LSTM layer를 다른 GPU에서 계산하면 <strong>이전 layer의 계산이 끝나기 전에 다음 layer의 계산을 진행</strong>시킬 수 있었다. 첫번째 layer는 bi-direction이므로 2개의 GPU를 사용하여 계산을 먼저 진행시킨 후, 해당 Layer의 계산이 끝나면 나머지 Layer는 parallelize 하여 계산을 진행하였다. Attention을 적용하는 Decoder부분에 대해서도 비슷한 방법이 적용되었는데, <strong>최대한 parallelism을 적용하기 위해서 첫번째 layer에 대해서만 계산을 하여 이 상수 값을 전체 layer에 대해서 적용</strong>하는 방법을 사용하였다.</li>
</ul>
<h3 id="3-segmentation-approach">3. Segmentation approach</h3>
<hr />
<p>처음에 언급한 것처럼 NMT가 가지고 있는 문제 중에 하나는 <strong>빈도수가 적은 단어들을 처리하기가 힘들다</strong>는 점이었다. 이러한 문제들을 <strong>translation of out-of-vocabulrary (OOV)</strong>라고 하며 이러한 문제점에 대한 해결책으로는 먼저 attention이나 추가적인 alignment model을 사용하여 Source의 단어를 그대로 copy하는 방법이 있으나 이는 언어에 대한 dependency가 있으며 네트워크가 deep해질수록 적용하기 어려운 문제점이 있다. 논문에서 사용된 방법은 이와 달리 <strong>sub-word unit을 사용하여 기존 단어들을 쪼개는 방식을 선택</strong>하였다.</p>
<h4 id="wordpiece-model">Wordpiece Model</h4>
<p>Wordpiece model은 구글 음성 인식 시스템에서 한국어/일본어 segmentation을 위해 만들었던 모델로 <strong>기존의 단어들을 작은 조각들로 나눌 수가 있다.</strong> 예를 들어, “나는 감자를 좋아합니다”와 같은 문장을 단어 기반으로 해석하는 것이 아니라 “ _나 는 _감자 를 _좋아 합니다” 와 같은 식으로 쪼개는 것이다. (실제 wordpiece를 적용한 결과는 다를 수 있습니다). 각각의 wordpiece로 나눌 때에는 앞에 “_”와 같은 기호를 사용하여 각각의 segment가 단어의 시작 부분임을 표시한다.</p>
<p>논문에서는 이를 적용하기 위해 먼저 기존 Source sentence에 wordpiece model을 적용하여 생성된 segments를 새로운 source setence로 encoder에 사용하였고, decoder에서는 얻어지는 단어들이 wordpiece일 것이기 때문에 이를 단어의 시작을 알려주는 “_”기호를 통하여 기존의 단어들로 바꾸어주었다. 뒤의 결과에서 나오지만 wordpiece를 적용한 결과 BLEU score가 좋아짐과 동시에 적은 빈도의 단어들에 대해서도 보다 robust한 결과를 얻을 수 있었다.</p>
<h4 id="mixed-word--character-model">Mixed word / character model</h4>
<p>Wordpiece model에서도 처리하기 힘든 OOV case들이 있는데, 이러한 문제들은 mixed word/character model을 사용하여 해결하였다. 보통은 OOV word들을 <UNK> 토큰으로 바꾸어 다루는 것과는 <strong>달리 각각의 character들을 나누어 관리</strong>하였는데 각각의 character에는 단어에서의 위치를 알려주고 일반적인 단어들과는 구별하기 위한 prefix을 사용하였다. 예를 들면, Miki라는 단어가 있을 때 이를 단어의 시작, 중간, 끝을 의미하는 <B><M><E> prefix를 붙여 <B>M <M>i <M>k <E>i 로 나누어 처리하였다. Decoder에서 이 prefix가 들어간 토큰이 들어간 결과물이 나온다면 이를 다시 이어붙이면 된다.</p>
<h3 id="4-training-criteria">4. Training Criteria</h3>
<hr />
<p>일반적인 학습과정에서는 Maximum likelihood method를 사용하여 학습을 진행하지만 <strong>이는 BLEU score의 기준과는 다른 방식으로 학습이 진행될 여지가 잇다.</strong> 이 논문에서는 <strong>GLEU score를 이용한 강화학습을 이용하여 해결</strong>하고자 했다. GLEU score는 output, target sequence n-gram을 이용하여 recall, precision을 계산하고 두 값 중의 minimum을 계산한 값이다. 이들 실험에 따르면 이 방식은 보다 BLEU score metric에 가까우면서도 기존의 성능을 깍지 않았다고 한다. 이 새로운 방법을 적용하기 위해서 다음과 같은 순서로 학습을 진행했다고 한다.</p>
<ol>
<li>기존의 Maximum likelihood method를 이용하여 학습을 진행</li>
<li>학습이 Convergence에 이르렀을 때, RL을 이용하여 학습을 추가적으로 진행</li>
</ol>
<h4 id="quantizable-model-and-quantized-inference">Quantizable Model and Quantized inference</h4>
<p>NMT를 사용하고자 하는 상황에서 성능이 좋더라도 느린 속도와 큰 사이즈는 큰 어려움일수밖에 없다. 특히, 구글 번역기처럼 서비스를 하는 입장에서 속도적인 측면은 절대적으로 작용할 수 밖에 없다. GPU를 사용한 병렬화이외에도 속도를 향상시키기 위한 방법으로 사용된 것은 <strong>Quantization</strong>이었다. 이는 <strong>기존 모델의 parameter들이 32 비트 float 변수였다면 이를 8bit나 16bit로 낮추어 사용하는 방법</strong>이다.</p>
<p>이를 적용하기 위해서 <strong>Training 과정에서는 따로 quantization을 적용하지 않고 full-precision floating point를 사용하였지만 후에 quantization을 적용하는 변수들에 대해서 min, max의 constraint를 적용</strong>하였다. 결과에 따르면 constraint에 의한 악영향은 거의 없었으며 이를 통해 inference 과정에서 quantized variable을 사용 시에 성능적인 감소가 거의 없었다고 한다. <strong>Inferece과정에서 softmax와 attention model을 제외하고 대부분의 변수들은 8bit나 16bit로 quantize</strong> 되었다. 특히, 주목할 점은 이 방법론은 TPU에 최적화되어 아래의 표에서 볼 수 있듯이 기존 GPU를 사용한 방법 대비 다른 성능은 유지하면서 Decoding time은 거의 10분의 1로 줄인 모습을 확인할 수 있었다. 최적화된 Training, Inference를 위해 다음과 같은 순서로 연구를 진행하였다.</p>
<ol>
<li>Training : 먼저 GPU를 통해 학습을 진행하고 CPU를 사용하여 model decoding을 진행 (full resolution)</li>
<li>Inference : TPU를 사용하여 decoding time을 최소화 (quantization)</li>
</ol>
<h3 id="5-decoder">5. Decoder</h3>
<hr />
<p>초반에 언급했던 NMT의 문제 중 하나는 <strong>문장이 길어질 수록 아예 잘못된 번역을 할 가능성이 높아진다</strong>는 것이었다. 이를 해결하기 위해 제시된 방법들은 <strong>coverage penalty</strong>와 <strong>length normalization</strong>이다.</p>
<ul>
<li>Coverage penalty : attention mechanism에 따라 source sentence를 보다 많이 cover하는 결과를 우선시하는 방법</li>
<li>Length normalization : 문장의 길이를 계산 결과에 나누어주는 것으로 이는 짧은 문장 결과를 선호하는 beam search를 해결하는 방법</li>
</ul>
<p>이 두 가지 방법들을 decoder에 적용하기 위해서 먼저 여러가지 상수들에 대해서 BLEU score를 계산하고 비교하여 그 중 가장 최선의 상수들을 선택하였다.</p>
<h3 id="6-conclusion">6. Conclusion</h3>
<hr />
<ul>
<li>Data : WMT’14 En -> Fr, En -> De, Large scale data testing용 Google 번역기 데이터</li>
<li>Evaluation metric : multu-bleu score, 번역가들이 직접 번역 품질을 0부터 6까지 비교한 점수를 사용</li>
</ul>
<p>실험 결과는 당시 SOTA를 얻었으며 번역 품질 면에서는 기존 구글 번역기에 사용되던 PBMT를 능가함은 물론 전문 번역가들이 번역한 점수에 근접하는 결과를 보여주었다. RL을 적용한 결과는 기존 대비 성능이 소폭 향상된 모습을 보여주었다.</p>
<h3 id="7-key-points">7. Key points</h3>
<hr />
<ul>
<li>Deep Network 문제를 해결하기 위한 residual connection</li>
<li>속도적인 측면을 향상시키기 위한 parallelism, quantization</li>
<li>Wordpiece model 사용</li>
<li>Length normalization, coverage penalty 적용</li>
<li>번역 품질을 높이고자 새로운 Score인 GLEU 도입과 강화학습 사용</li>
<li>CPU, GPU, TPU를 각각의 목적에 맞게 사용</li>
<li>번역 품질 비교를 위한 0~6 Score 사용</li>
</ul>JongHyun저자 : Yonghui Wu, Mike Schuster… (Google) 링크 : [Arxiv.1609.08144][https://arxiv.org/abs/1609.08144]NMT By Jointly Learning To Align And Translate 리뷰2019-06-18T00:00:00+00:002019-06-18T00:00:00+00:00https://heiwais25.github.io//nlp/2019/06/18/neural-machine-translation-by-jointly-learning-to-align-and-translate<blockquote>
<p>5년이나 지났지만 기초부터 다지자는 생각에 이 논문을 시작으로 NLP 관련 논문들을 부족하지만 리뷰해보고자 합니다. 틀린 내용에 대한 이야기는 언제든지 환영합니다.</p>
</blockquote>
<blockquote>
<ul>
<li>저자 : Dzmitry Bahdanau, Kyunghyun Cho, Yoshua Bengio</li>
<li>링크 : <a href="https://arxiv.org/abs/1409.0473">Arxiv.1409.0473</a></li>
</ul>
</blockquote>
<p> 이 논문 이전의 Machine Translation에서 쓰이던 방법들은 주로 <strong>Statistical Machine Translation</strong>였으며 Neural Network은 Main으로 쓰였다기 보다는 기존 System에 성능을 더하거나 결과를 Re-Rank하는 용도로 쓰였다.
그에 비해 이 논문에서 제안하는 System은 저자가 결론에서 언급한 것처럼 오로지 Neural Network만을 사용한 것으로 진정한 <strong>Neural Machine Translation</strong>의 시작이라고 할 수 있다.</p>
<p> 한 가지 흥미로운 점이 하나 있었는데, Input sentence를 학습하는 과정에서 <strong>input word에 대한 attention</strong> 개념을 적용했다는 것이다. 현재 Attention을 기반으로 한 Transformer model이 점령한 세상에서 이 내용을 보니 감회가 새롭다.</p>
<h3 id="main-idea">Main Idea</h3>
<p> 논문에 사용된 Model 14년 Cho나 Sutskever가 언급했던, RNN Encoder-Decoder을 기반으로 하고 있다. RNN Encoder-Decoder는 Encoder에서 input sentence X를 차례대로 읽어나가 <script type="math/tex">\text{vector c}</script>를 만들고 이를 Decoder에서 output sentence를 만드는 구조인데,
Encoding 과정을 좀 더 자세하게 보면 다음 수식과 같다.</p>
<p>
$$h_t = f(x_t, h_{t-1})$$
$$c = q({h_1, \cdots, h_{T_x}})$$
$$\begin{cases}
h_t &: \text{hidden state at time t}\\
q &: \text{non linear function (like LSTM)}
\end{cases}$$
</p>
<p> Decoding 과정에서는 Encoder와 반대로 이전까지의 hidden state와 Encoder에서 얻은 context vector c를 이용하여 현재 단계의 단어를 예측하여 output을 만들어낸다. 이 과정을 자세히 살펴보면 다음 수식과 같다.</p>
<p>
$$p(\bf{y}\rm) = \Pi^T_{t=1} p(y_t | {y_1, \cdots, y_{t_1}}, c)$$
$$p(y_t | {y_1, \cdots, y_{t_1}}, c) = g(y_{t_1}, s_t, c)$$
$$\begin{cases}
\bf y\rm&: (y_1, \cdots, y_t)\\
g&: \text{non linear function} \\
s_t&: \text{hidden state at time t}
\end{cases}$$
</p>
<p> 이렇게 Encoder-Decoder에 대해 대략적으로 살펴보았는데, 저자가 주목한 부분은 Encoder에서 Context Vector <script type="math/tex">\text{c}</script>를 만들어내는 부분이다. 저자는 고정된 길이의 vector에 Input Sentence 정보를 모두 다 담아내기에는 힘들어 길이가 길어질수록 Output Sentence에 대한 결과가 나쁘게 나올 것이라고 생각하였고 이에 대한 해결책으로 <strong>Alignment</strong>를 생각해냈다. 전체적인 구조는 다음 Figure와 같다.</p>
<center><img src="/img/nlp/arxiv1409_img1.jpg" alt="structure" /></center>
<p> Alignment는 <strong>Output sentence의 단어가 Input sentence의 어느 부분에 주목해야 하는지에 대한 개념</strong>으로 이를 계산하기 위해서는 Encoder에서 context vector를 계산하기 위해 사용되고 버려지는 hidden state와 Decoder에서의 hidden state를 이용한다. 이는 Decoder에 사용되는 수식을 통해 보다 쉽게 이해할 수 있다.</p>
<p>
$$p(y_t | {y_1, \cdots, y_{t_1}}, \bf{x}\rm) = g(y_{t_1}, s_i, c_i)$$
$$s_i = f(s_{i-1}, y_{i-1}, c_i)$$
</p>
<p> 기존의 Decoder와의 차이점은 각각의 output 단어에 대한 conditional probability를 계산함에 있어서 하나의 context vector를 사용하는 것이 아니라 <strong>각각 다른 context vector를 사용한다</strong>는 것이다. 이 context vector는 Encoder에서의 hidden state와 Decoder에서의 hidden state 그리고 weight를 곱하여 계산된다.</p>
<p>
$$c_i = \Sigma^{T_x}_{j=1}\alpha_{ij}h_j$$
$$\alpha_{ij} = {\exp{e_{ij}} \above 1pt \Sigma^{T_x}_{k=1}\exp{e_{ik}}}$$
$$e_{ij} = a(s_{i-1}, h_j)$$
$$a : \text{alignment model}$$
</p>
<p>여기서 사용되는 alignment model <script type="math/tex">\text{a}</script>는 하나의 feed forward neural network로 전체 system과 함께 학습되어 Soft alignment로서의 역활을 한다 (여기서 Soft의 의미는 단어와 단어가 1:1관계가 아니라 여러 단어에 걸쳐 관련이 있음을 말한다. 보다 자세한 건 뒤에서 나올 Graph를 보면 알 수 있다.).</p>
<p> 저자는 Encoder에서의 hidden state를 annotation으로 context vector <script type="math/tex">c_{i}</script>, 해당 단계에 대한 context vector를 expected annotation으로 보았다. 즉, 현재 output 단어에 대한 input sentence의 평균 alignment라고 생각해볼 수도 있을 것이다. expected annotation을 계산하는 데 있어서 사용된 weight는 굉장히 큰 의미를 가지고 있는데 <strong>input word가 output word에 대한 align될 확률, 정도</strong>로 볼 수 있으며 이는 자연스럽게 최근 사용되는 <strong>attention 개념</strong>과도 일치함을 알 수 있다.</p>
<p> 마지막으로 RNN에서 가장 최근 단어가 focus되는 특징을 해결하고자 저자는 RNN의 계산을 정방향, 역방향으로 한 번씩 계산하여 concat하였고 이를 annotation을 계산할 때 사용하였다.</p>
<p>
$$h_j = [\overrightarrow{h^T_j};\overleftarrow{h^T_j}]^T$$
</p>
<h3 id="experimental-setup">Experimental Setup</h3>
<ul>
<li>
<p><strong>Dataset</strong> : WMT ‘14 English-French를 사용하였으며 각 언어에서 빈번하게 사용되는 30,000개의 단어를 사용하였다. 여기 포함되지 않은 단어들은 [UNK] Token 처리되었다.</p>
</li>
<li>
<p><strong>Model</strong> : 모델 비교를 위해 같은 데이터를 사용하여 기존 모델인 <strong>RNN Encoder-Decoder</strong>와 새로운 모델인 <strong>RNNsearch</strong>를 학습하였다. 각 모델을 두 번 학습하였는데 먼저는 Sentence의 단어 갯수가 30개까지만을 학습키고 (RNNencdec-30, RNNsearch-30)와 50개까지 학습시켰다 (RNNencdec-50, RNNsearch-50). Training은 <strong>80문장씩으로 이루어진 mini batch SGD</strong>를 사용하였다. 학습이 된 후에 prediction에서는 beam search를 사용하였다.</p>
</li>
</ul>
<h3 id="result">Result</h3>
<p> 결과는 굉장히 성공적으로 나왔는데, 먼저 아래 그래프에서 볼 수 있듯이 RNNsearch가 BLEU Score에서 기존 모델을 능가하는 모습을 보여주었으며 특히 RNNsearch-50은 <strong>sentence가 길어지더라도 성능 저하가 일어나지 않는 모습</strong>을 확인할 수 있었다.</p>
<center><img src="/img/nlp/arxiv1409_img2.jpg" alt="structure" /></center>
<p> 다음 표는 보다 자세한 BLEU Score 점수를 보여주는데, RNNsearch가 기존 모델을 능가하는 모습을 다시한번 확인할 수 있으며 특히, 더이상 성능이 증가하지 않을 때까지 학습시킨 RNNsearch-50*의 경우 기존 conventional phrase-based 번역 시스템인 Moses를 능가하는 모습을 확인할 수 있었다.</p>
<center><img src="/img/nlp/arxiv1409_img3.jpg" alt="structure" /></center>
<p> Alignment(Attention) 측면에서의 결과는 다음 Graph에서 확인할 수 있다. <em>(검은색이 0, 흰색이 1을 의미)</em> input 단어들에 대한 output 단어들의 alignment 2D graph인데 먼저 크게는 단어들이 거의 monotonic 관계를 이루고 있는 것을 볼 수 있다. 하지만, 이는 1대1관계만 있는 것이 아니라 몇몇 output 단어들은 <strong>여러 input 단어들에 걸쳐 align되어있는 soft alignment</strong>를 확인할 수 있다. 이를 통해 이 <strong>Model이 Decoder에서 output 단어를 계산할 때에 있어 특정 input 단어만을 보는 것이 아니라 그 앞 뒤로 여러 단어를 보고 문맥을 파악</strong>한다고 해석할 수도 있을 것이다.</p>
<center><img src="/img/nlp/arxiv1409_img4.jpg" alt="structure" /></center>
<p> 이 밖에도 Long Sentence에 대한 결과 분석이 있는데, 이 부분에 대해서는 어떤 프랑스어가 번역이 잘 된 것인지 잘 모르기 때문에 새로운 모델이 보다 Robust하다는 저자의 말을 그대로 따르도록 하겠다.</p>
<h3 id="conclusion">Conclusion</h3>
<p> 이 논문 이전 많은 논문들이 Neural Network를 Machine Translation에 사용하려고 했지만 이는 기존 Statistical Translation에 부수적으로 사용하는 것일 뿐, Pure한 Machine Translation은 찾아보기 힘들었다. 여기서 제안된 Model은 Pure하게 Encoder-Decoder에서 Neural Network를 사용한 Neural Machine Translation으로 기존 Encoder-Decoder 모델의 성능을 능가하였으며 기존의 Phrase-based statistical translation만큼의 성능을 보여주었다.</p>
<p> 우리가 생각해볼 수 있는 이 논문의 의의는 그동안 Statistical Translation이 주류를 이루고 있던 상황에서 <strong>새롭게 성능도 비슷한 Pure한 Neural Translation System을 제안</strong>하였다는 것과 Long Sentence에 대한 Encoder-Decoder 문제를 해결하기 위해서 현재 주류를 이루고 있는 <strong>Attention 개념의 Alignment를 도입</strong>했다는 것으로 볼 수 있다.</p>JongHyun5년이나 지났지만 기초부터 다지자는 생각에 이 논문을 시작으로 NLP 관련 논문들을 부족하지만 리뷰해보고자 합니다. 틀린 내용에 대한 이야기는 언제든지 환영합니다.