形態素解析器比較 Sudachi vs Mecab+Neologd

ブレインパッドさんのpodcast「白金鉱業.FM」の聞いてたらSudachiの開発の話を聞いて興味が出たので触ってみました。
shirokane-kougyou.fm
(「白金鉱業.FM」はデータ分析現場の生の声が聴けるのでなかなか面白いです。)

Sudachiとは

ワークスアプリケーションズ徳島人工知能NLP研究所でオープンソース開発されている形態素解析器です。
www.worksap.co.jp

形態素解析器とは日本語を単語に分かち書きしたり、品詞を特定する機能を有するもので、日本語の自然言語処理では必須です。
同じ様なものにはMecabやJuman++などがあります。
Sudachiの強みは辞書にあるらしく、長年研究してる専門家が辞書のメンテナンスをしているそうです。
また、分かち書きの方法(モード)が複数あり、切る長さを選ぶことが出来ます。

使ってみる

pythonのモジュールはここにあります。
https://github.com/WorksApplications/SudachiPy

まずはインストールします。

pip install sudachipy # 本体のインストール
pip install sudachidict_core #辞書のインストール

インポートしてそれぞれモードで分かち書きしてみる。

from sudachipy import tokenizer
from sudachipy import dictionary

tokenizer_obj = dictionary.Dictionary().create()


モードA

mode = tokenizer.Tokenizer.SplitMode.A
[
    (
        m.surface(), 
        m.dictionary_form(), 
        m.reading_form(),
        m.part_of_speech()
    )
    for m in tokenizer_obj.tokenize("国家公務員", mode)]

出力:

[('国家', '国家', 'コッカ', ['名詞', '普通名詞', '一般', '*', '*', '*']),
 ('公務', '公務', 'コウム', ['名詞', '普通名詞', '一般', '*', '*', '*']),
 ('員', '員', 'イン', ['接尾辞', '名詞的', '一般', '*', '*', '*'])]


モードB

mode = tokenizer.Tokenizer.SplitMode.B
[
    (
        m.surface(), 
        m.dictionary_form(), 
        m.reading_form(),
        m.part_of_speech()
    )
    for m in tokenizer_obj.tokenize("国家公務員", mode)]

出力:

[('国家', '国家', 'コッカ', ['名詞', '普通名詞', '一般', '*', '*', '*']),
 ('公務員', '公務員', 'コウムイン', ['名詞', '普通名詞', '一般', '*', '*', '*'])]


モードC

mode = tokenizer.Tokenizer.SplitMode.C
[
    (
        m.surface(), 
        m.dictionary_form(), 
        m.reading_form(),
        m.part_of_speech()
    )
    for m in tokenizer_obj.tokenize("国家公務員", mode)]

出力:

[('国家公務員', '国家公務員', 'コッカコウムイン', ['名詞', '普通名詞', '一般', '*', '*', '*'])]

モードAは細かく分解するのに対してモードCはあまり分解しない様です。
モジュールとしての使い勝手としてもよさそうです。

また正規化もしてくれます。

tokenizer_obj.tokenize("SUMMER", mode)[0].normalized_form()

出力:

'サマー'

すばらしい。

比較

ニュース記事分類のタスクで性能を比較してみます。
具体的にはSudachiとMecab+Neologdで分かち書きしたものをそれぞれtf-idfでベクトル化してロジスティック回帰で分類してみます。

データセット

データセットにはお馴染み(?)のlivedoor ニュースコーパスを使います。
https://www.rondhuit.com/download.html#ldcc

9種類の記事があり全部で7376記事あります。

使用したモジュール

この辺のモジュールを使いました。

import re
import math
import resource
import numpy as np
from urllib import request 
from pathlib import Path


import MeCab
import neologdn
import gensim
from gensim import corpora
from gensim.corpora import Dictionary


import matplotlib.pyplot as plt
from sklearn.metrics import confusion_matrix
from sklearn.model_selection import train_test_split
from sklearn.metrics import recall_score, precision_score, accuracy_score
from sklearn.linear_model import LogisticRegression

from sudachipy import tokenizer
from sudachipy import dictionary

トークナイザー

SudachiとMecabそれぞれに対してほぼ同じ処理をするトークナイザーを作りました。

class SudachiTokenizer():
    def __init__(self, mode="C", stopwords=None, include_pos=None):
        
        if mode not in ["A", "B", "C"]:
            raise Exception("invalid mode. 'A' ,'B' or 'C'")
        self.mode = getattr(tokenizer.Tokenizer.SplitMode, mode)
        print(self.mode )
        
        if stopwords is None:
            self.stopwords = []
        else:
            self.stopwords = stopwords
        if include_pos is None:
            self.include_pos = ["名詞", "動詞", "形容詞"]
        else:
            self.include_pos = include_pos
    
    def parser(self, text):
        return tokenizer_obj.tokenize(text, self.mode)
    
    
    def tokenize(self, text, pos=False):
        res = []
        for m in self.parser(text):
            p = m.part_of_speech()
            base = m.normalized_form() #.dictionary_form()
            #print(base, ": ", p)
            if p[0] in self.include_pos and base not in self.stopwords and p[1] != "数詞":
                if pos:
                    res.append((base, p[0]))
                else:
                    res.append(base)
        return res
class MeCabTokenizer:
    def __init__(self, dic_dir=None, stopwords=None, include_pos=None):
        tagger_cmd = "-Ochasen"
        if dic_dir:
            tagger_cmd += " -d {}".format(dic_dir)
        mecab = MeCab.Tagger(tagger_cmd)
        self.parser = mecab.parse
        if stopwords is None:
            self.stopwords = []
        else:
            self.stopwords = stopwords
        if include_pos is None:
            self.include_pos = ["名詞", "動詞", "形容詞"]
        else:
            self.include_pos = include_pos

    def tokenize(self, text, pos=False):
        l = [line.split("\t") for line in self.parser(text).split("\n")]
        res = []
        for w in l:
            if len(w) >=4: # check nomal words (e.g. not EOS)
                p = w[3]
                group_pos = p.split("-")[0]
                base = w[2]
                if group_pos in self.include_pos and base not in self.stopwords and "数" not in p:
                    if pos:
                        res.append((base, p))
                    else:
                        res.append(base)
        return res

また、これらのトークナイザーに通す前には共通の関数でノーマライズしてます。

kaomoji_reg = r'[\[|\(][^あ-ん\u30A1-\u30F4\u2E80-\u2FDF\u3005-\u3007\u3400-\u4DBF\u4E00-\u9FFF\uF900-\uFAFF\U00020000-\U0002EBEF]+?[\]|\)]'
m = re.compile(kaomoji_reg)
def normalize(text):
    text = str(text)
    text = text.replace("\n", " ")
    text = re.sub(r"http(s)?:\/{2}[\d\w-]+(\.[\d\w-]+)*(?:(?:\/[^\s/]*))*", " ", text)
    text = re.sub(r"\S*@\S*\s?" ," ", text)
    text = text.lower()
    text = re.sub(kaomoji_reg, " ", text)
    text = re.sub(r'\d+', '', text)
    text = neologdn.normalize(text)
    return text

ストップワードも用意します。

sw_filename = "stopwords.txt"
if not Path(sw_filename).exists():
    res = request.urlopen("http://svn.sourceforge.jp/svnroot/slothlib/CSharp/Version1/SlothLib/NLP/Filter/StopWord/word/Japanese.txt").read().decode("utf-8")
    with open(sw_filename, "w") as f:
        f.write(res)
else:
    with open(sw_filename) as f:
        res = f.read()
stopwords = [line.strip() for line in res.split("\n")]
print(len(stopwords)) #331
print(stopwords[:3]) # ['あそこ', 'あたり', 'あちら']

トークナイザー使用例

include_pos = ["名詞", "動詞", "形容詞"]
mecab_tokenizer = MeCabTokenizer(dic_dir="/usr/local/lib/mecab/dic/mecab-ipadic-neologd", stopwords=stopwords, include_pos=include_pos)
sudachi_tokenizer = SudachiTokenizer(mode="B", stopwords=stopwords, include_pos=include_pos+["形状詞"])

mecab_tokenizer.tokenize(normalize(text), pos=False)
# 出力 ['コード', '命令', '冗長', '算出', 'プロパティ', '利用', 'する', 'バージョン', '比較', 'する', 'みる']

sudachi_tokenizer.tokenize(normalize(text), pos=False)
# 出力 ['コード', '命令', '冗長', '算出', 'プロパティー', '利用', '為る', 'バージョン', '比較', '為る', '見る']

「する」と「為る」、「みる」と「見る」の違いはsudachiのnormalized_form()から来ています。

辞書の統計的フィルター

今回は分類のタスクなので、全ての記事の中で1回しか出てない単語や、全体の9割より多く出現してる単語を除去しました。

dictinonary_mecab = Dictionary(sentences_mecab)
dictinonary_mecab.filter_extremes(no_below=2, no_above=0.9)
dictinonary_mecab.compactify()
corpus_mecab = [dictinonary_mecab.doc2bow(w) for w in sentences_mecab]

dictinonary_sudachi = Dictionary(sentences_sudachi)
dictinonary_sudachi.filter_extremes(no_below=2, no_above=0.9)
dictinonary_sudachi.compactify()
corpus_sudachi = [dictinonary_sudachi.doc2bow(w) for w in sentences_sudachi]

ベクトル化

tf-idfでベクトル化します。つまり一つの記事は語彙数次元のスパースなベクトルになります。
モジュールにはgensimを使いました。

分類器

分類器にはロジスティック回帰を使いました。
今回はweightの設定もハイパラのチューニングもせずにscikit-learnのデフォルト値で学習

結果

Sudachi(モードA)

f:id:tdualdir:20200713160434p:plain
sudachi_A

Sudachi(モードB)

f:id:tdualdir:20200713161020p:plain
sudachi_B

Sudachi(モードC)

f:id:tdualdir:20200713160721p:plain
sudachi_C

Mecab+Neologd

f:id:tdualdir:20200713154222p:plain
mecab

正解率
Sudachi(モードA) : 0.936
Sudachi(モードB) : 0.934
Sudachi(モードC) : 0.935
Mecab+Neologd: 0.943

Mecab+Neologdが一番良いです。

速度について

気になったのが速度です。
訓練データとして5532個のニュース記事をトークナイズした結果です。
f:id:tdualdir:20200713141431p:plain
Mecabが30秒かからずに終わっていますが、Sudachiは7分30秒ほどかかっています。

その他

品詞の付与について

品詞の特定はMecabとほとんど変わらないのですが、ちょいちょい違う場合もある様です。
f:id:tdualdir:20200713142212p:plain

なので今回は"名詞", "動詞", "形容詞"のみを抽出する予定でしたが、Sudachiの場合は"形状詞"も抽出しました。

終わりに

今回のタスクにおいてはMecab+Neologdの方が良かったです。
podcastでも少し言ってましたが、今は検索タスクを優先して改善してる様なので今回の使い方はまだフォーカスしてないのかもしれません。
この先10年はメンテしていく予定らしいので、ビジネスに使うとかシステムに組み込むという話になった時は一つ選択肢には上がると思います。
(組み込んだ後のNeologdの更新って皆んなどうしてるんだろう🤔)

また今回のコードはここにあります。こうした方が良いとかアドバイスください!
github.com


↓今すぐフォローすべきキラキラ アカウント


↓今すぐ登録すべきキラキラAIサービス
www.matrixflow.net


じゃあの。

くそだったワイが起業してやったこと。むしろやってない奴はやばい。

初めまして。株式会社MatrixFlow(マトリックスフロー)のCEOをやってるものです。
株式会社MatrixFlowは,ビジネスマン向けのAIを構築・活用・運用出来るプラットフォームサービス「MatrixFlow」を運営しています。
www.matrixflow.net
おかげさまでユーザーも2000名を超え、経済産業新聞さんや東洋経済さんに取り上げてもらってユーザーも週で20名以上伸びています。
会社自体はまだ2年も立ってないひよっこですが、経験を少しでもこれから起業しようと思ってる人、起業に興味がある人に還元できればと思ってます。

1.起業したら、アクセラレータープログラムに入る

スタートアップを各方面から支援するアクセラレータープログラムと言うものを大企業や行政が実施しています。
メンターの教えによる会社経営の示唆や知識は学びは大きく非常に感謝してます。
また、そこの業界ネットワークに参加できることも大きいです。今でもそこで築いたネットワーク関係で仕事が入って来てます。
同業種の同期であることの仲間意識は思った以上に強いです。
起業した際には是非とも自分の分野のアクセラレータープログラムを検索して申し込むことをオススメします。
(MatrixFlow社はバイトルでお馴染みのdipがやっているAIアクセラレーターと、海外VCのPlug and Playの日本法人がやってるアクセラレータプログラムに参加してます。)

2.VCからもらうのは資金よりも知恵や経験

起業したての頃にいくつかのVCをまわりました。
最初は資金調達を目的としてましたが、優秀なVCさんと話していると資金よりも知恵や数々のスタートアップを見て来た経験からくる助言が非常に重く役に立ちます。
もちろん腹が立つような言葉を浴びせられたこともありますが、今振り返ると決して間違ってることを言ってるわけではないのでVCの言うことはまずは素直に聞いて実践することをオススメします。
また、ラウンドが違うVCからは他のVCの紹介や仕事をもらったこともあるのでVCとは積極的に会うべきです。

3.ベンチャー融資の申請

日本金融公庫がスタートアップに対して最大2000万円の融資をしている。(2020年7月の現状)
受けない手はない。手続き等で不安がある場合は申請の手助けをするサービスもあるのでそこに頼るのもありです。

4.ツールに頼るべき

事業をやる上で専用のツールは沢山あるので有効活用すべき。間違ってもエクセルでなんとかしようと思うべきじゃない。
MatrixFlow社は
進捗管理はTrello(無料)
・営業・カスタマーサクセスはHubspot(無料)
・会計はfreee(有料)
・コミュニケーションはslack(無料)
・Gsuite (メール、スライド、ドキュメント、スプレッドシート、アンケートフォーム等) (有料)
を使っています。
少なくともslack、TrelloとHubspotは使うべき。

5.スタートアップ専門の弁護士をつける

弁護士っていうと大袈裟な感じがするが、スタートアップこそ不利な契約が結ばれそうになることがある(損害賠償の範囲も大きく賠償金の上限も無しなど)。
そこで創業2年以内なら格安のサービスをしてる事務所があるのでそこにお願いする。
MatrixFlow社はAZXにお願いしてる。(ちゃんと疑問にも答えてくれるので正直めっちゃ満足してる)

6.メディアには露出すべき

メディアにばっか出る経営者は胡散臭いとか自己顕示欲が強いとか言われがちだが、メディアの効果はやはり大きい。
MatrixFlow社もインタビュー記事きっかけで商談が組まれて最終的に数千万円の案件に繋がった。
よっぽど嫌ではなければメディアには露出した方が良い。

7.悪いのは常に自分

社員がミスをした時、悪いのは社員じゃなくてミスをするような体制を作ってる経営者である。
ミスは人ではなくてシステムが引き起こすと思ってシステムの改善をすべきです。
例えば、営業部の人が商談のあとのお礼メールを出すのを忘れるならカレンダーにお礼メールを出すというスケジュールを追加するようにしたり、管理ツールにお礼メールというチェックボックスを作ったりと人ベースではなくシステムベースでミスを減らす努力するのが経営者です。

8.取引相手が欲しいのは製品やサービスではなく、効果やメンツ

究極的なことを言えば、取引相手が欲しいのは質の高い製品・サービスではなくて、
そこから得られる効果やそれによって得られる組織内でのメンツであることを意識する。
如何に性能が良いかではくて、如何にこれの導入によって組織やあなたが得をするか言うべき。

おわり

まだありますが、長くなったのでこのあたりで一旦終了です!
好評ならpart2をやります。

↓今すぐフォローすべきキラキラ アカウント


↓今すぐ登録すべきキラキラAIサービス
www.matrixflow.net


じゃあの。

de:code2019に行ったらMicrosoftのAI戦略が見えてきた

お久しぶりです。半年ぶりくらいにブログを書きます。

5/29と5/30にザ・プリンス パークタワー東京で開催されたde:code2019に参加してきてました。
Microsoftが毎年開催しているエンジニア向けの一大イベントです。

www.microsoft.com


まずは「マイクロソフト リサーチの AI / 自然言語処理研究 最前線」を聞きました。

Microsoft Research のこれまでの自然言語処理の基礎研究の成果の紹介でした。
歴史の中でブレイクスルーに確実に絡んでいるのはさすがだなぁと思って聞いていました。
また「りんな」についても触れていて何かを効率化するAIではなく、Emotional AIだと言ってたのが印象に残りました。



次は「開発者のための機械学習入門:Azure Machine Learning Studio で構造化データから予測分析」を聞きました。

目的は弊社が開発しているサービス「MatrixFlow」と似ているので敵状視察的なやつです。
セッションの内容としては機械学習の基本的な考え方から始まって後半からようやくML Studioの話が出てきました。
初めて知ったことも多かったです。
例えば、ML StudioはGUIで前処理や機械学習のトレーニングが可能で学習済みモデルをそのままWebサービスにデプロイできるのですが、モデルの更新はPower shellでやらないといけないと言う若干ズコー感がある事や、エクセルから推論テストが出来ることを知りました。
最後にはML Studioの上位互換になる(予定の?)Azure Machine Learning Service Visual Interface(まだプレビュー版)の説明もありました。
ML Studioと比べた時に何が変わったかと言うと、GPUが使用可能、デプロイ先がKubernatesを使えるようになったりとクラウドらしい構成になったようです。GA(General Available)が楽しみです。

個人的にはML Studioにエクセルのプラグインが用意され連携できるのがMicrosoftらしいと思いましたが、推論だけでもエクセル上からできると言うのはAIを意識的に使うのではなく、いつの間にか裏でAIが動いていたと言う世界を作るには良いのかもしれません🤔



1日目の最後は「Build 2019 Azure AI & Data Platform 最新アップデート」を聞きました。
機械学習系の話を期待して行ったのですが、ほとんどがDB周りの話でした。しかし面白かったです。
例のAzure SQL Database Serverless(まだプレビュー版)の話がありました。データベースの負荷が全くない場合はPausedして料金がゼロになると言うのはすごい。
またCosmos DBのSpark対応でSparkがCosmos DBと同じサーバー内にあるのでロードする必要なく使えると言うは面白いし実際に便利そうでした。



2日目の最初は「こうすれば Deep Learning 推論は速くなる!Intel AI ソフトウェア製品を活用した推論高速化手法のご紹介」を聞きました。
Intelのチップを効率よく使う方法やopenVINOの説明でした。
pip install intel-tensorflowでインテルチップ用にオプティマイズしたTensorflowが使えるなんて初めて知りました。
またopenVINOと言うインテルに特化した推論SDKも初耳でした。
物体検知のデモをやっていましたが、確かに早くなっていました。iGPUがあるならエッジでも爆速になるらしいです。

そしてなぜかこのツイートが若干伸びた


次は「Custom Vision で出来ること & 出来ないからって諦めてませんか?」を聞きに行きました。
Custom Visionに特段興味があったわけではないですが、機械学習系のサービスとしてどう売り出しているのかを理解して最終的にMatrixFlowに活かそうと思って行きました。
どうやらディスカッション系のセッションだったらしくオーディエンスはスマホから質問を投稿し、それに答えていきつつオーディエンスにも積極的に質問をする形式でした。
登壇者も休むことなくずっと喋っていてエンターテイナーかwwwって思いました。
内容としては聞いているとCustom Visionは足がかりとしては良いかもしれないけど、色々制限があってガチで何かやろうとするにはまだ使いにくそうという印象でした。
ただ、dockerやiOS用にエクスポートできるのでエンジニアがPoCでサクッと何か作ってみるのには使えそうでした。


次は「機械学習のためのデータ加工 ~ 特徴量の見つけ方と作り方」を聞きました。

AutoMLが当たり前になっていく中で特徴量の作成は自動化出来ないので大事だと言う話をして、その後に失敗談を交えて特徴量の作り方の解説をしていました。
目的変数はビジネス課題になっているのかをしっかりと検討し、
説明変数は特徴量の洗い出しは5W2H (5W1H +how much)でやりモデルの活用のタイミングもイメージすべきという内容でした。
特徴量を作るときにビジネス課題を解決するためという視点を忘れてはいけないという非常に教育的なセッションでした。



最後に「Azure Machine Learning service Deep Dive ~自動機械学習から MLOps まで~」を聞きました。

PoCを超え、機械学習をサービスとして提供してる会社が次に必要でなるであろうMLOps。それをAzure DevOpsの応用で実現するという内容でした。
自動化パイプラインで精度などのモニタリングまでやって再学習やデプロイのサイクルを効率的に回そうという話。
Azure Machine Learning Workspaceでモデルを一元管理できるんですね。良さそうです。
全体のパイプライン構成も精度などの監視も含めると割とアリな構成なんじゃないかと思いました。
また、最後の方でAutomated MLの紹介がありましたが、前処理も含めて自動化してくれるらしいです🤔
そしてそのAutomated MLはSHAP、LIMEをモロそのままサービスに組み込んでいるのもInterpretabilityの時流に乗っていて素晴らしいと感じました。


まとめ
仕事柄、競合調査の一環で機械学習系のツールやサービスの情報収集をすることが多いのですが、Microsoft機械学習関係のサービスはチグハグ感があったのが今回のde:codeを通した印象としてはAzure上に綺麗にまとめて来ています。AIをAIとして世に提供するというよりはクラウドソリューションの一機能として提供したいという思いが伝わってきます。
エンタープライズに強いMicrosoftだからこそ、ビジネス活用されるAIのシェアはAzureと共に広げていくという戦略はシンプルで効果的だと思いました。







↓今すぐ登録すべきキラキラAIサービス
www.products.matrixflow.net



↓今すぐフォローすべきキラキラ アカウント

じゃあの。

私が実践した、エンジニアが超優良企業に転職するための最も簡単な方法

転職先を自分で作る。
(起業する。)

↓今すぐフォローすべきキラキラ アカウント

↑今すぐフォローすべきキラキラ アカウント

PyCon mini OsakaでCharacter-Level CNNについて話してきた。


東京在住ですが、なぜかグランフロント大阪で開催されたPyCon mini Osakaで登壇して来ました。
osaka.pycon.jp

前日から大阪に乗り込んでました。(久しぶりに弊社の大阪オフィスに行った。)

スライド

発表内容は以下のスライドになります。

www.slideshare.net
(画像がぼやけていてすまない・・・)
最初はCNNやTensorFlowの基礎的な説明をして後半からCharacter-Level CNNについて話しました。

Character-Level CNN

Character-Level CNNのところを説明します。

Character-Level CNNとは

文書を文字(Character)単位で区切ってそれをCNNに通してテキスト分類します。 ※単語単位でないのに注意

今回やること

今回は、テキストからネガティブかポジティブかを判断するタスク(いわゆる感情分析のネガポジ)をこなす分類器を作ることを考えます。
結果から言うと、オリジナル論文*1では上手く行かずに、違うアーキテクチャで上手く行きました。

なぜ文字レベルなのか?

文字レベルで自然言語処理をするというのは珍しいと思いますが(大体は単語レベルで区切る)、そのメリットは

  • 前処理がいらない(日本語なら分かち書きが必要ない)
  • タイポやスペルミスが多い文書でも有効(レビュー,チャットなど)
  • 文書じゃない物にも適応できる(URL, Tex, プログラミングコードなど)

などが挙げられます。

論文

上手くいったアーキテクチャですが、
Joshua Saxe, Konstantin Berlin: eXpose:A Character-Level Convolutional Neural Network with Embeddings For Detecting Malicious URLs, File Paths and Registry Keys(https://arxiv.org/abs/1702.08568)
を参考にしました。
内容はCharacter-Level CNNを使って悪意あるURLやファイルパスを検出するという内容です。
かなり丁寧に書かれていてわかりやすい論文でした。

構成・アーキテクチャ

処理の流れとしては

  1. 文字を低次元に埋め込む
  2. カーネルサイズが違う複数のCNNに入れて特徴を出力
  3. 特徴を一つに結合
  4. 全結合層に通して分類

です。


1の埋め込みは、(文字 × 埋め込み次元)行列を埋め込み層として用意して、対応する文字の行ベクトルだけを更新します。

f:id:tdualdir:20180520142008p:plain:w300
図1.埋め込み層


2については論文に書いてる図を借りると次のようになります。

f:id:tdualdir:20180519210753p:plain
図2.CNNの処理
1×2,1×3,1×4,1×5というサイズのフィルターにそれぞれ通します。
様々なWindowで文字のCooccurrence(共起性)を学習してると考えられます。
f:id:tdualdir:20180519211149p:plain
図3.CNN部分の解釈


3.特徴を一つに結合、4.全結合層に通して分類はそのままの意味です。

TensorFlowで実装してTensorboardのグラフに書くと以下のようになります。

f:id:tdualdir:20180519205644p:plain
図4.アーキテクチャ

日本語のデータセット

日本語のデータセットとして最近公表されたばかりのchABSA-dataset*2を使いました。
上場企業の有価証券報告書をベースに作成されたデータセットで、特徴としては「何が」ポジティブ/ネガティブだったのかと言う情報を含んでいます。
これで、「何が」のネガティブとポジティブの数を比べて多い方を文書全体の感情としました。
その結果、訓練データとテストデータを合わせて2830文書が対象となりました。

結果

結果は以下のようになりました。

f:id:tdualdir:20180519212347p:plain
図6.loss
f:id:tdualdir:20180519212339p:plain
図7.accuracy

データセットが少ないので不安でしたがaccuracyも0.9程度にはなりました。

誤字・脱字に対する強さ

面白いのがここからで、わざと誤字・脱字をしてちゃんとネガポジを判定できるか試してみました。
f:id:tdualdir:20180519212917p:plain
「増加傾向」を「加向」とか、「減少傾向」を「減向」にしても問題なく判定できています。

なので、ここでテストデータの文書の文字を適当な文字に変換した際にどのくらい精度が下がるのかグラフを書いてみました。

f:id:tdualdir:20180519213508p:plain
図8.変えた文字数と精度(character-level)

50文字をランダムな文字に置き換えても精度が8割以上あるのは驚きです。(1つの文書は大体300文字程度なので役1/6が適当な文字と言うこと)

これをMecabを使って単語レベルで区切ってCNNで学習させたword-level CNNでも試しました。

f:id:tdualdir:20180519213821p:plain
図9.変えた文字数と精度(word-level)

50文字も変えると精度は0.5なので全く判別出来てないことになります。

結論

Character level CNNは誤字・脱字に強すぎなんじゃwwww
SNSメッセージ、チャット、レビューなど最適と言える。
さらに日本語だと分かち書きが不要と言うのも魅力的。また、単語で区切ったりしないので辞書が要らず、この実装だけであらゆる言語に対応可能。


終わりに

カンファレンスで発表するのは初めてでしたが、発表練習をするのを忘れていたので時間配分がちょっと不安でしたが、まあまあ時間ぴったりに終わったのでよかったです。
カンファレンスは楽しかった。

コードはここに置いてます。
GitHub - Tdual/char_level_cnn: Character level CNN


ツイッターやっているのでフォローお願いします。
↓今すぐフォローすべきキラキラ アカウント

じゃあの。

*1: Xiang Zhang, Junbo Zhao, Yann LeCun: Character-level Convolutional Networks for Text Classification (https://arxiv.org/abs/1509.01626)

*2: https://github.com/chakki-works/chABSA-dataset

「虚数はあるのか?」という話の物理学的な回答

はじめに

GW中にツイッターで「虚数はあるのか?」という話をタイムラインでよく見かけたので自分なりの回答をしようと思います。
私は大学/大学院で素粒子論・超弦理論をやってきた理論物理徒なので物理的な観点から回答します。

虚数はiで表現します。( i^2=-1)


電磁気学/波動・振動論

物理で初めて虚数を使うのはおそらく電磁気学か波動・振動論でしょう。
オイラーの公式という形で出て来ると思います。
オイラーの公式
 e^{i\theta} = \cos\theta +i\sin\theta \tag{1}
虚数によって指数関数と三角関数が関係付くという美しい公式です。

オイラーの公式と振動

ご存知の通り三角関数は振動を表しているのでe^{i\theta}も振動を表現しています。
では振動とはどう言う運動かというと、遠くに行こうとするものを中心に引き戻す力が働くので物体は振動してるわけです。
遠くに行けば行くほど強い力で引き戻す必要があるので、引き戻す力は距離に比例してるとすると振動は次の運動方程式でかけます。
m:振動してる物体の質量
x:位置
t:時間
k:定数(バネ定数)
 m\frac{d^2x}{dt^2}=-kx \tag{2}
左辺は質量かける加速度、右辺は中心に引き戻す力です。(ma=Fですね。)
三角関数  x = C\sin\left(\sqrt{\frac{k}{m}}t\right) x = C\cos\left(\sqrt{\frac{k}{m}}t\right)はもちろんこの式を満たしています。(Cは定数)

さて、 x=Ce^{i \sqrt{\frac{k}{m}}t}を(2)式に代入してみてください。(Cは定数)
ちゃんと等式が成立していることがわかると思います。つまり、 e^{i\theta}が振動を表しているということです。(今の場合は \theta=\sqrt{\frac{k}{m}}t
また、 x=Ce^{i \sqrt{\frac{k}{m}}t}の代わりに時間に依存しない項をeの肩に追加しても式を満たします。( C=C'e^{i\delta}と置き直す。)
 x=C'e^{i (\sqrt{\frac{k}{m}}t+\delta)}
この \deltaは位相(phase)と呼ばれいてtが0の時の値なのでどのような状態で振動が始まったのかを表現しています。

 e^{i\theta}は物理では様々な分野で出てきます。
ですので、物理学徒は e^{i\theta}をみたら「振動だ!」と思うように訓練されています。


インピーダンスの例

抵抗とコイルが直列につながっている回路に交流電流を流すことを考えます。
V:電圧
I:電流
R:抵抗
L:インダクタンス
t:時間
とすると、電位差の和は0になるというキルヒホッフの第二法則より
 V-RI-L\frac{dI}{dt} = 0 \tag{3}
となります。1項目は起電力で、2項目は抵抗にかかる電圧(オームの法則)、3項目はコイルにかかる電圧です。
さて、交流電流は振動しているので電流は I=I_{0}e^{i\omega t}と書けそうです。(I_0は定数)
これを代入すると、
 V = (R+i\omega L)I \tag{4}
になることがわかります。つまり全体として見れば、 R+i\omega Lの抵抗がかかっているように見えます。
この、VIの比である R+i\omega Lインピーダンスと呼びます。以後、インピーダンスをZと表記します。
 Z= R+i\omega L \tag{5}
(5)式を複素平面で表すで表現すると様々なことがわかります。図1となります。

f:id:tdualdir:20180507231918p:plain
図1.複素平面上のインピーダンス
この図から直ちに大きさ |Z|=\sqrt{R^2+(\omega L)^2},電圧と電流の位相差 \tan\alpha=\frac{\omega L}{R}であるとわかります。



インピーダンス虚数

インピーダンス、つまり電圧と電流の比に虚数が出ました。
つまり、虚数は存在する?
結論から言うとそうとは言い切れません。
オイラーの公式を(1)の\theta \frac{\pi}{2}を代入してみてください。
 e^{(i\frac{\pi}{2})} = iになります。
つまりインピーダンス R+\omega Le^{(i\frac{\pi}{2})}と書けます。
電流が I_0e^{i\omega t}であり、(4)の再び代入すると、
 V=RI_0e^{i\omega t} + \omega LI_0e^{i(\omega t+ \frac{\pi}{2})}となります。
これが示しているのはコイルに入ったら位相が \frac{\pi}{2}ずれるということを表現しているにすぎません。
実際に I=I_{0}e^{i\omega t}の代わりに I=I_{0}\sin(\omega t)と表現しても良いはずです。 V=V_0\sin(\omega t + \alpha)
これを(3)式に代入すると
\begin{align}
V_0\sin(\omega t + \alpha) &= RI_0\sin(\omega t) + \omega LI_0\cos(\omega t) \\
&= I_0\sqrt{R^2+(\omega L)^2}\sin(\omega t + \theta_0) \tag{6}
\end{align}
但し、 \tan\theta_0=\frac{\omega L}{R}
両辺を比べると、 V_0 =  I_0\sqrt{R^2+(\omega L)^2},  \tan\alpha=\frac{\omega L}{R}となって複素数を使わなくても同じ結果を得られます。

ではなぜ最初に I=I_{0}e^{i\omega t}を使ったのかというと(6)式の三角関数の合成を使うよりも複素平面の幾何として扱った方が簡単だからです。
つまり、ここで使っている複素数は本質的なものではなくあくまでも便利な道具として使っています。

実は他の分野で扱われる虚数も同じで、虚数を使わなくても解ける問題が殆どです。
次の量子力学を除いては。


量子力学

量子力学の基礎方程式として状態の変化を記述するシュレディンガー方程式というのがあります。
 ih\frac{d\psi(t,x,y,z)}{dt} =  -\frac{\hbar^2}{2m}\nabla^2 \psi(t,x,y,z) + V(x,y,z) \psi(t,x,y,z)  \tag{7}
h:プランク定数
m:が粒子の質量
V:ポテンシャル
 \psi:波動関数(状態を表す関数)
t:時間
x,y,z:それぞれ空間座標
 \nabla^2:ラプラシアン

この方程式には初めから虚数が入っています。
しかし、人間が観測する物理量は量子力学では波動関数に働く作用で、実数になります。(というか実数になるようにしてます。)
また、\psiに関しても物理として意味があるのは\psiではなく|\psi|^2です。
|\psi|^2が粒子の存在確率密度を表すというのが一般的な量子力学の解釈です。
確率密度なので空間で積分すると1になります。
 \int\int\int |\psi(t,x,y,z)|^2 dxdydz = 1 \tag{8}


では、量子力学においても虚数を使わずに表現できるのでしょうか?

その前に(7)式は両辺に複数共役をとっても成り立つはずなので\psi^*が成り立つ方程式は
 -ih\frac{d\psi^*(t,x,y,z)}{dt} =  -\frac{\hbar^2}{2m}\nabla^2 \psi^*(t,x,y,z) + V(x,y,z) \psi^*(t,x,y,z)  \tag{9}
となります。(これは後で使います。)


さて、確率密度の時間変化を考えます。
\begin{align}
\frac{d|\psi|^2}{dt} &= \frac{d}{dt}(\psi^*\psi) \\
& = \frac{d\psi^*}{dt}\psi + \psi^*\frac{d\psi}{dt} \\
& = -\frac{\hbar}{2mi}\left( \psi^\ast\nabla^2 \psi - (\nabla^2 \psi^\ast)\psi \right)\\
& = -\frac{\hbar}{2mi} \nabla \cdot \left( \psi^* \nabla \psi - (\nabla \psi^*)\psi \right)\\
& = - \nabla \cdot { \frac{\hbar}{2mi} \left(\psi^* \nabla \psi - (\nabla \psi^*)\psi \right) }
\end{align}
3行目の式変形で(7),(8)を使った。また、 \nabla \cdot divとも書かれダイバージェンスですね。


ここで、 \rho =|\psi|^2 ,  j =\frac{\hbar}{2mi} \left(\psi^* \nabla \psi - (\nabla \psi^*)\psi \right) とすると、
 \frac{d\rho}{dt} = - \nabla \cdot j \tag{10}
と書けます。
(10)式は連続方程式と呼ばれていて、確率密度 \rhoの時間変化がjという流れとなるという式です。(jは確率密度流と呼ばれています。)
この連続方程式は確率密度が急に湧いてきたり、無くなったりせず空間上で連続的に変化することを表してます。
確率密度なら当然満たすべき性質です。

さて、この確率密度流の係数に虚数が出てきました。
先ほどのインピーダンスの時と違うのは \psi複素数とした訳ではないのに始めから確率密度流に虚数が入っていることです。
さらに確率密度は実数なので(10)の左辺も実数で、確率密度流は実数になるべきです。そうするためには \psi複素数である必要があります。
(そもそも \psiが実数なら \psi^*=\psiとなって確率密度流は常に0になる。
また、(8)式より「右辺が1なんだから確率密度の時間微分は0になるのでは?・・・」と思った人はガウスの定理などを思い出しながら実際に計算してみて下さい。)

つまり、|\psi|^2 を確率密度と解釈するなら量子力学には虚数は必須であると言えます。


終わりに

これが「虚数はあるのか?」という問いに完璧に答えるかと言われてると微妙ではありますね。
そもそも量子力学の解釈が間違っているとか、実は虚数を使わなくて済む別の方程式があるのではないかとか色々ツッコミどころはあると思います。
ただ、少なくとも私は「虚数はあるの?」とか「虚数は必要なの?」って聞かれた場合は「虚数が無いと量子力学の確率解釈が成立しない。」と答えます。

追記(2018-5-9)

フィードバック



これらの議論を受けて、虚数を使わなくても量子力学は構成できる気もして来ました。

現段階での、理解としては以下のツイートになります。

詳しい方がいたら教えてください><


ツイッターやっているのでフォローお願いします。
↓今すぐフォローすべきキラキラ アカウント

じゃあの。