Pythonで形態素解析して韓国語学習

# はじめに
私はK-POPが好きでよく聴くのですが、やはり日本語や英語の曲と比べると歌詞の意味がよく分からなくて十分に楽しめないという課題を抱えていました。
一から学習書を用いて勉強するのが王道かと思いますが、通常の学習書に載っている会話や単語を見てもあまりスッと入ってこなくて学習のモチベーションを保つのに苦労しました。
そこで、K-POPの歌詞から単語を抽出して覚えるために韓国語の形態素解析と単語別の翻訳をしてみようと思いました。
韓国語学習者の方にとって以外は少々読みづらい内容となっているかもしれませんがご容赦ください。


# 利用技術
- プログラミング言語: Python
- 形態素解析: [mecab-ko](https://bitbucket.org/eunjeon/mecab-ko/src/master/README.md), [open-korean-text](https://github.com/open-korean-text/open-korean-text)
- Pythonライブラリ: KoNLPy, python-mecab-ko
- 翻訳: GASのLanguageApp

# KoNLPyとは
https://konlpy.org/en/latest/
韓国語の自然言語処理を行うためのPythonのパッケージです。名称は`Korean Natural Language Processing in Python` の略語となっています。
[GitHub](https://github.com/konlpy/konlpy)のスター数も1.2kとなっておりPythonで韓国語の形態素解析をお手軽に行うライブラリとしては現在最もポピュラーな手段かと思います。

# taggerの選定
**POS tagging （Part-of-Speech Tagging/品詞タグ付け）**、つまり文章に品詞を振るために数種類の選択肢からtaggerを選ぶ必要があります。
今回は[KoNLPy公式](https://konlpy.org/en/latest/morph/#time-analysis)にもあるように最も実行時間の短い日本語の形態素解析でもおなじみのMeCabをカスタマイズした**mecab-ko**（以下、MeCabと記載）と、次いで実行時間が短く、辞書の中身も違うと思われるTwitter社製の**open-korean-text**（以下、Oktと記載）を利用します。

公式に掲載されている手順に従い環境構築を行ったあと下記のように実行してみると、文章が分かち書き（トークナイズ）されて、`MAG`や`VV`といった単語が割当されているのがわかるかと思います。こちらの`MAG`や`VV`は品詞（副詞と動詞）を表しています。これをタギングと呼びます。

```
$ python
>>> from konlpy.tag import Mecab
>>> from konlpy.utils import pprint
>>> mecab = Mecab()
>>> pprint(mecab.pos("빨리 가자."))
[('빨리', 'MAG'), ('가', 'VV'), ('자', 'EF'), ('.', 'SF')]
```

# デモ
ここではaespaのBlack Mambaという曲を題材に試してみようと思います。

https://www.youtube.com/watch?v=ZeerrnuLi5E

曲中にBlack Mamba（ヘビ）についての歌詞が出てくるので、こちらをサンプルに使用します。
> 널 유혹해 삼킨 건 Black Mamba

`君を誘惑して飲み込んだのは Black Mamba` という意味です。

## KoNLPy - MeCabの場合
```
mecab_poslist = Mecab().pos("널 유혹해 삼킨 건 Black Mamba")
print(mecab_poslist)
# 結果
 [('널', 'NP+JKO'), ('유혹', 'NNG'), ('해', 'XSV+EC'), ('삼킨', 'VV+ETM'), ('건', 'NNB+JX'), ('Black', 'SL'), ('Mamba', 'SL')]
```
結構細かめに、一つのトークンを`NP+JKO`（代名詞+目的格助詞）のように**語幹と活用語尾や合成された助詞の品詞を個別にタギング**してくれます。

## KONLPy - Oktの場合
```
okt_poslist = Okt().pos("널 유혹해 삼킨 건 Black Mamba", norm=False, stem=True)
print(okt_poslist)
# 結果
[('널', 'Noun'), ('유혹', 'Noun'), ('하다', 'Verb'), ('삼키다', 'Verb'), ('건', 'Noun'), ('Black', 'Alpha'), ('Mamba', 'Alpha')]
```
`stem`オプションを指定することで語幹+다で活用されている**動詞や形容詞の原形を作ってくれる**ところが高機能でいいですね。
ただ、代名詞がNoun（名詞）のように大きな括りに入ってしまったり、活用語尾について分類されていないところはMeCabに軍配が上がるかと思います。

## "널（neol/君を）" を "너（neo/君）"+ "을（eul/〜を）" に分解したい
韓国語には二つの品詞が合わさって一つの文字になるリエゾンの文字バージョンのような特徴があり、これも慣れている方は分かりますが初学者にとっては合成された文字であること分かりづらいです。
通常は**トークナイズした結果に対して品詞のタギング**を行いますが、**合成された1トークンをさらに分解**することができないか調べてみました。

MeCabの仕様を調べる中で、**mocab-ko-dic辞書**の構成（各項目のことを"素性"と呼びます）が下記のようになっており生のMeCabを使用すれば複数の品詞が合成されたトークンを分解した内容も取得できることが分かりました。（CSVの一番右です）

[mocab-ko-dic](https://bitbucket.org/eunjeon/mecab-ko-dic/src/master/)

```
$ mecab -d /usr/local/lib/mecab/dic/mecab-ko-dic
mecab-ko-dic은 MeCab을 사용하여, 한국어 형태소 분석을 하기 위한 프로젝트입니다.
mecab    SL,*,*,*,*,*,*,*
-    SY,*,*,*,*,*,*,*
ko    SL,*,*,*,*,*,*,*
-    SY,*,*,*,*,*,*,*
dic    SL,*,*,*,*,*,*,*
은    JX,*,T,은,*,*,*,*
MeCab    SL,*,*,*,*,*,*,*
을    JKO,*,T,을,*,*,*,*
사용    NNG,행위,T,사용,*,*,*,*
하    XSV,*,F,하,*,*,*,*
여    EC,*,F,여,*,*,*,*
,    SC,*,*,*,*,*,*,*
한국어    NNG,*,F,한국어,Compound,*,*,한국/NNG/*+어/NNG/*
형태소    NNG,*,F,형태소,Compound,*,*,형태/NNG/*+소/NNG/*
분석    NNG,행위,T,분석,*,*,*,*
을    JKO,*,T,을,*,*,*,*
하    VV,*,F,하,*,*,*,*
기    ETN,*,F,기,*,*,*,*
위한    VV+ETM,*,T,위한,Inflect,VV,ETM,위하/VV/*+ᆫ/ETM/*
프로젝트    NNG,*,F,프로젝트,*,*,*,*
입니다    VCP+EF,*,F,입니다,Inflect,VCP,EF,이/VCP/*+ᄇ니다/EF/*
.    SF,*,*,*,*,*,*,*
EOS
```

Pythonのライブラリに[jonghwanhyeon/python-mecab-ko](https://github.com/jonghwanhyeon/python-mecab-ko)というものがあり、これらの結果を返す`parse` APIがあるようなのでそちらを使用してみることにしました。

## python-mecab-ko - MeCabの場合
```
import mecab
tokens = mecab.MeCab().parse("널 유혹해 삼킨 건 Black Mamba")
print(tokens)
# 結果（+改行）
[('널', Feature(pos='NP+JKO', semantic=None, has_jongseong=True, reading='널', type='Inflect', start_pos='NP', end_pos='JKO',
expression='너/NP/*+ᆯ/JKO/*')),
('유혹', Feature(pos='NNG', semantic='행위', has_jongseong=True, reading='유혹', type=None, start_pos=None, end_pos=None,
expression=None)),
('해', Feature(pos='XSV+EC', semantic=None, has_jongseong=False, reading='해', type='Inflect', start_pos='XSV', end_pos='EC',
expression='하/XSV/*+아/EC/*')),
('삼킨', Feature(pos='VV+ETM', semantic=None, has_jongseong=True, reading='삼킨', type='Inflect', start_pos='VV', end_pos='ETM',
expression='삼키/VV/*+ᆫ/ETM/*')),
('건', Feature(pos='NNB+JX', semantic=None, has_jongseong=True, reading='건', type='Inflect', start_pos='NNB', end_pos='JX',
expression='것/NNB/*+ᆫ/JX/*')),
('Black', Feature(pos='SL', semantic=None, has_jongseong=None, reading=None, type=None, start_pos=None, end_pos=None, expression=None)),
('Mamba', Feature(pos='SL', semantic=None, has_jongseong=None, reading=None, type=None, start_pos=None, end_pos=None,
expression=None))]
```

当初の目論み通り、`널`が`'너/NP/*+ᆯ/JKO/*'`のように二つの品詞が合成されたトークンであるという情報を取得できました。

# トークンごとに妥当な日本語訳を付けたい

翻訳にはGASの`LanguageApp`が無料で利用できたためそちらを使用しました。
単純にトークンごとに翻訳をしてみるとやはり文脈がないため意味不明な結果が返ってきます。

```
널・・・ヌル
유혹・・・誘惑
해・・・太陽
삼킨・・・飲み込む
건・・・銃
Black・・・ブラック
Mamba・・・マンバ
```

そこで、**トークン単体で意味が取得しやすい品詞（名詞類、動詞、形容詞、副詞など）について翻訳を行い、助詞が合成されている名詞類（タグがNから始まるもの）は原形で翻訳**してみます。

```
널・・・あなた
유혹・・・誘惑
해・・・（対象外）
삼킨・・・飲み込む
건・・・もの
Black・・・（対象外）
Mamba・・・（対象外）
```

今度はだいぶ意味が通るようになったのではないでしょうか？
ここで`널`が「あなた**を**」と翻訳されるようになったらだいたい実現したいことが表現できるのであと一歩です。

最後にこれまで試した内容をまとめて見やすくした結果をコーディングし出力しました。

```
"tokens": [
    {
      "token": "널",
      "stem": "너",
      "suffix": "*+ᆯ",
      "translation": "あなた",
      "word_class": "代名詞+目的格助詞"
    },
    {
      "token": "유혹",
      "stem": null,
      "suffix": null,
      "translation": "誘惑",
      "word_class": "一般名詞"
    },
    {
      "token": "해",
      "stem": "하다",
      "suffix": "*+아",
      "translation": null,
      "word_class": "動詞の接尾辞+接続語尾"
    },
    {
      "token": "삼킨",
      "stem": "삼키다",
      "suffix": "*+ᆫ",
      "translation": "飲み込む",
      "word_class": "動詞+連体形語尾"
    },
    {
      "token": "건",
      "stem": "것",
      "suffix": "*+ᆫ",
      "translation": "もの",
      "word_class": "依存名詞+補助詞"
    },
    {
      "token": "Black",
      "stem": null,
      "suffix": null,
      "translation": null,
      "word_class": "外国語"
    },
    {
      "token": "Mamba",
      "stem": null,
      "suffix": null,
      "translation": null,
      "word_class": "外国語"
    }
]
```

# さいごに
今回は形態素解析+品詞タグづけをいろいろな方法で試してみました。
やってみて分かったのは、韓国語学習において文を形態素の単位で分解すると、トークンや文字をさらに分解したいパターンとちょっと分解の単位が細かすぎて翻訳に困るパターンの両方が発生することです。
構文解析など他にもいろいろなことをやってみたいと思うので実現できればまた記事にしていこうと思います。