気が向いたら書くやつ

なんかプログラミングのことについて

NLTKのsentence_bleuで送出される警告について

機械翻訳などの論文を読むと評価尺度としてBLEUというものが用いられています。

NLTKMosesといった有名な言語処理ライブラリにBLEUを計算する関数が実装されており、私のようによく知らずとも簡単に翻訳文の評価を行うことができます。

その場合、NLTKでは以下のように警告されることがあります。

UserWarning:
Corpus/Sentence contains 0 counts of 4-gram overlaps.
BLEU scores might be undesirable; use SmoothingFunction().
  warnings.warn(_msg)

これが発生する理由について、この記事では述べたいと思います。

sentence_bleuとcorpus_bleuの違い

pythonのライブラリであるNLTKにはbleuを計算する関数としてsentence_bleuとcorpus_bleuという2つの関数が実装されています。

ソースを読んでみるとsentence_bleuは1文をcorpus_bleuに渡しているだけなので基本的に処理自体は変わりません。

しかし、corpus_bleuで警告がでなくとも、一文ずつsentence_bleuで計算すると警告が発生してしまうことが多くあります。

この理由については、(気が向けば)別の記事で述べたいと思います。

BLEUの計算方法

BLEUの求め方は至って簡単です。
参照文(正しい翻訳)Rと仮定文(機械翻訳)Hがあるとして、まずngram精度P_nを求めます。

\displaystyle
P_n=\frac{Rの\mathrm{ngram}に含まれるHの\mathrm{ngram}の数}{Hの\mathrm{ngram}の数}

これはつまり、R=[a,b,c]とH=[a,d,a]があるとすれば

n=1\displaystyle\frac{1}{3}

n=2\displaystyle\frac{0}{2}

となるわけです。
このngram精度を使ってBLEU値は以下のように求めることができます。

\displaystyle
BLEU = BP * \exp(\frac{1}{N}\sum_{i=1}^{N}\log{P_n})\\
BP = \begin{cases} \exp(1-len_{R}/len_{H}) & len_{H} \leq len_{R}\\1 & else \end{cases}

ここでNは考慮するngramの最大値です。NLTKでもMosesでもデフォルト値は4になっており、基本は4のようです。
また、短い文のngram精度は高くなるので、BPでペナルティを与えます。

計算できない

そうです、上の例におけるn=2P_n=0になるので\log{0}となり、計算することができないのです。

この時にNLTKから2gramの一致数が0であるためBLEUスコアが正しくない可能性があると、以下のように警告されます。

UserWarning:
Corpus/Sentence contains 0 counts of 2-gram overlaps.
BLEU scores might be undesirable; use SmoothingFunction().
  warnings.warn(_msg)

算出される値は何か

この警告がなされた場合でもNLTKは無理やりBLEU値を計算して出力します。

P_{n}=0の場合、NLTKでは\log{P_n}=0として計算します。

また、ngramに分割できないn=4の場合も0として計算します(したがって、文の長さが4未満なら必ず警告が出ます)。

上の例を用いて、NLTKのsentence_bleuで計算すると

from nltk.translate import bleu_score

ref = [["a", "b", "c"]]
hypo = ["a", "d", "a"]

print(bleu_score.sentence_bleu(ref, hypo))

"""
出力
UserWarning:
Corpus/Sentence contains 0 counts of 2-gram overlaps.
BLEU scores might be undesirable; use SmoothingFunction().
  warnings.warn(_msg)
0.7598356856515925
"""

この値は

\displaystyle
P_{1}=\frac{1}{3}, P_{2}=0, P_{3}=0, P_{4}=0

なので

\displaystyle
\exp(\frac{1}{4}(\log{P_{1}} + \log{P_{2}} + \log{P_{3}} + \log{P_{4}}))

この式は計算できないため

\displaystyle
\exp(\frac{1}{4}(\log{\frac{1}{3}} + 0 + 0 + 0))

と同じになります。

from nltk.translate import bleu_score
import math

ref = [["a", "b", "c"]]
hypo = ["a", "d", "a"]

print("nltk:", bleu_score.sentence_bleu(ref, hypo))
print("math:", math.exp(math.log(1 / 3) / 4))

"""
出力
UserWarning:
Corpus/Sentence contains 0 counts of 2-gram overlaps.
BLEU scores might be undesirable; use SmoothingFunction().
  warnings.warn(_msg)
nltk: 0.7598356856515925
math: 0.7598356856515925
"""

Mosesではどうなっているか

Mosesにはmulti-bleu.perlというプログラムが含まれており、これがbleuを計算するプログラムになっています。

上述のようなP_{n}=0が含まれる場合、BLEU値は問答無用で0になります(警告文は出ます)。

警告を消す方法

警告を消す方法として最も簡単なものとして、sentence_bleu関数のemulate_multibleuのフラグをTrueにする方法が挙げられます。

これは、おそらくMosesのmulti-bleuと同じ動作にするフラグです(未調査)。

そのため、警告は出ませんがNLTKでもBLEU値が0になります。

from nltk.translate import bleu_score
import math

ref = [["a", "b", "c"]]
hypo = ["a", "d", "a"]

print("nltk:", bleu_score.sentence_bleu(ref, hypo, emulate_multibleu=True))
print("math:", math.exp( math.log(1/3) / 4))

"""
出力
nltk: 0.0
math: 0.7598356856515925
"""

どちらが正しいのか

わかりません。
誰か詳しい方教えてくださいお願いします。

chainerのExtensionとslackのIncoming Webhookを使う

動機

僕は研究室の端末からどこかに置いてあるサーバに繋いで研究をしているんですが、
1回学習を始めてしまうとbashを落とせなくて困っていました。

よくよく調べてみると、なんかnohupとか言うのを使うとバックグラウンドでもジョブを継続できるらしい。
www.codereading.com

これは素晴らしいと飛びついてみると、「これじゃいつ学習が終わったかわかんねぇぞ」となるわけです。

ということでslackのIncoming WebhookとchainerのExtensionを組み合わせてepochが終了するごとにメッセージを送ろうとういうお話です。

コード

とは言っても世の中に賢い人はいっぱいいるので僕が思うことなんて大抵は実装されています。
nonbiri-tereka.hatenablog.com

この記事を参考にしつつかなり簡素化したものが以下のコードになります。

from chainer.training import extension
import json
import requests

class SlackReport(extension.Extension):

    trigger = 1, "epoch"

    def __init__(self):
        self.post("実験を開始したよっ!")

    def __call__(self, trainer):

        log_report = trainer.get_extension("LogReport")
        log = log_report.log
        epoch = log[-1]["epoch"]
        self.post("{}epochが終了したよっ!".format(epoch))

    def post(self, text):
        payload = {"text":text}
        requests.post("<Webhook URL>", data=json.dumps(payload))

後はこれをtrainerにextendしてやるだけです。

trainer.extend(SlackReport())

シンプルイズベストだなぁ。

Unity1週間ゲームジャム「積む」に参加してオンラインゲームっぽいのを作った話。

Unity 1週間ゲームジャム | ゲーム投稿サイト unityroom - Unityのゲームをアップロードして公開しよう

今回は論文と重なったりしていて参加は無理かな~と思っていたのですが、

論文の提出期限が伸びたおかげで参加できることになりました。

やったね。

前回は

実は前回も参加していて、

はしれ!ユニティちゃん!

ていうのを公開しました。

お題「積む」について

発表されたときは今回もというか、Unityの物理エンジンを活かせそうなお題だなぁと感じました。

と思いつつも、

 とか、

困っているおじいちゃんとおばあちゃんを

目的地まで連れて行くことで「徳を積む」ゲーム

とか変な方向で考えていました(良さげなおじいちゃんとおばあちゃんのアセットが無かったので諦めました)。

さらに実験しながら、「積む、積む」と考えていたのですが

頭に浮かんでくるのはツムツムスキマスイッチ全力少年

 

ん?

「積み上げたものぶっ壊して」

 

確かに積んで壊すのもなかなか趣があります。さすがスキマスイッチ

しかしゲームにおいて自分が積んだものを自分で壊して何が楽しいのか分かりません。

何か面白い案がないかとツイッターを見てみると、

 

「Cube重ねておけば大丈夫」

 なるほど。

やはりシンプルイズベスト、Cubeを重ねてぶっ壊せばよさそうですね。

さらにここで思い出したのはDomino Onlineというゲーム。

そうだ「Tsumiki Online」を作ろう!

立ちはだかる技術的課題

とりあえず先にオフラインで積んで壊せるゲームを作り始めました。

それでてきたのがこれです。

f:id:Naomichi21:20170625103556p:plain

最初は積み木の色を自分で選択できてText Meshでラベルもつけれるようにしていました。

あわよくばテクスチャなんてアップロード出来たり、なんてのも考えていました。

 

しかしここで問題になってくるのはサーバーです。

そもそもサーバーサイドプログラムに知識が無く、

AWS使えばなんとかなるでしょ」程度に思っていました。

しかし、AWSは超過料金は自動引き落とし制度らしいので怖くてさすがに手が付けられませんでした(WebGLで使えないことも後から判明しました)。

 

救世主NCMB

そんな時に目にとまったのが、

mb.cloud.nifty.com

そうNCMBです。

なんと太っ腹、このデータベースは結構無料で使えてしまうのです。

しかもUnity用のSDKなどもあったりして完璧です。

今回必要な機能はデータベースだけでも十分満たしていると思っていたので使わない手はありませんでした。

やっぱりニフティなんだよなぁ。

 

NCMBの罠

NCMBを使って結構実装した後に気づいたのですが、

「NCMB for UnityはWebGLには対応していない」

のです。

もう少し調べてからやればよかったとかなり萎えました。

ここで一瞬折れかけましたが、やはり参加したいので代替手法を色々と模索することとなりました。

qiita.com

を応用するも、レコードの削除方法と速度の問題から採用を諦めました。

 

NCMB REST API

そして今の手法であるREST APIを叩くという手法にたどり着きました。

https://anz-note.tumblr.com/post/161063598401/unityncmb-rest-apiを叩くぞwebgl向け

anz-note.tumblr.com

大変お世話になった杏zさんにはこの場でお礼申し上げたいと思います。

 コアの部分をほとんど写経しながら利用させてもらいました。

 

しかしここで、さらに問題が出てきました。

REST APIからレコードを削除する場合、ユニークなobjectIDを指定する必要があるのです。

 

これはAPIコール数に制限のあるBasicプランにおいては大問題です。

もし積み木が10個積んであった場合、全部削除するためには10回もAPIをコールする必要があります(オブジェクトIDの検索をするので正確には11回です)。

リセット時には全部の積み木のデータを削除するので、削除は非現実的な手法と言わざるをえません。 

 

データベースの使い方が間違っていたのではないかとここで気づかされました。

データベースは頻繁にレコードの削除を行わないのではないかと。

 

そしていまの仕様へと変更

レコードが削除できないとなると、RGB値やラベルなどは利用できません。

リセット時には最初から更新していくという手法も考えましたが、

なんかいろいろめんどくさそうなので諦めました。

 

しかし「今いくつ積み木が積んであるのか」という情報だけは、

リセット時に0にセットすればいいのですから、1回のAPIコールだけで実現できそうです。

 

ということでラベルは廃止、色は生成時にランダムという仕様に変更しました。

 

結局

f:id:Naomichi21:20170625112851p:plain

f:id:Naomichi21:20170625112905p:plain

という風に落ち着きました。

 

すごい企業や賢い人が公開してくれたツールやAPI、コードにおんぶにだっこで実装したため、あまりオンラインゲームの知見は得られませんでした。

 

…まぁそりゃそうなんですが。

 

次はサーバー借りてMySQL+PHPとかで色々できたらいいなぁと思いつつも、

自分が面白いと思ったことができたので良かったと思います。

今回ゲームとしての面白さは置いておきます。

 

主催者のnaichiさんには非常に感謝です。

ありがとうございました。

 

最後にゲームのリンク貼っておきます。

TsumikiOnline

UnityとWindowsとVisualStudioと改行コードの話

初心者がWindows環境下でUnityを使っていると

There are inconsistent line endings in the 'File.cs' script. Some are Mac OS X (UNIX) and some are Windows.
This might lead to incorrect line numbers in stacktraces and compiler errors. Many text editors can fix this using Convert Line Endings menu commands.

という警告がUnityEditorで発生します。スクリプトをCreateするたび発生するのでこれがとてもうざい。

意味は「改行コードが複数混じっているのでエラーの元になるよ」です。


で、UnityEditorでスクリプトを作成するときはテンプレートを参照して作ってます。

テンプレートはUnity\Editor\Data\Resources\ScriptTemplatesにあります。

普通のスクリプト作成に使われるテンプレートは81-C# Script-NewBehaviourScript.cs.txtです。

このテンプレートのデフォルト改行コードがLFなんですね。

このテンプレートの改行コードをWindowsのデフォルトであるCR+LFに変えてやればすべて解決します。

権限が必要なので注意でエディタを管理者権限で実行してから開いてやりましょう。