2017年6月26日月曜日

TensorFlowのRNNのチュートリアル、translateの動作確認

概要

Sequence-to-Sequence Models | TensorFlowに記された通り、TensorFlow main repoとTensorFlow models repoをcloneした。続けてmodels/tutorials/rnn/translate/のtranslate.pyを実行しコーパスのダウンロード後、訓練を行った。3日前後*1でbucket 0、bucket 1のperplexity*2が1桁となったため、--decode引数を付加しtranslate.pyを実行した所、英文の仏訳が得られた。

2016年5月4日水曜日

Rでtwitterの検索結果から同表記異義語をフィルタする その3 集計とRStudio Projectファイル

この記事はRでtwitterの検索結果から同表記異義語をフィルタする その2 Wikipediaの記事抽出のつづきです。

対象となる文書が集められたら、あとは集計するのみ。

各クラスを特徴づける語彙のスコアを算出する

RMeCabパッケージの docMatrix 関数を使用した。想像以上に簡単。

tf_idf <- docMatrix(WIKIPEDIA_CONTENT_DIR, weight="tf*idf")

データフレームから関心のない列を取り除く

Rの intersect(arg1, arg2) 関数を使い、arg1arg2双方に存在する文字列を名前にもつ列だけを取り出す。

tf_idf <- tf_idf[,intersect(colnames(tf_idf),kTargetClasses)]

データフレームにおいて特定の行名を持つ列のデータを操作する

tf_idf[Term=target_word,] <- (tf_idf[Term=target_word,] * 0)

結果

そのクラスに所属する確度(全クラスのスコアの合計で正規化した値)に対しヒューリスティックに設定した値(0.27)でフィルタリングした結果。検索結果に含まれるツイートを一つ一つ人手で分類するよりかは生産性が高そう。

GitHubで公開したRStudio Projectファイルについて

今回作成したRファイルをGitHubで公開しました。
twitter-word-sense-disambiguation-w-tf-idf-of-wikipedia
constants.Rファイルを編集すると、お好きなキーワードで試すことができます。使い方についてはREADME.mdファイルとこの連載記事(初回)を参考にしてください。

課題

  • Wikipediaに記事がない地味に有名な固有名詞は識別することができない。
  • 抽出したい意味が、他の同表記語と比べて明らかに違う分類(例:抽出対象は地名で、他の同表記語は人名など)である場合、格フレームを活用した分類も有効に働くのではないか。

備忘

  • Rのapply関数便利。cbind等でテーブルを作ろうとすると、matrixができあがる。matrixだと複数のデータタイプを持たせるのに向いてない(numericだったmatrixに文字列から構成されるmatrixをcbindすると、numericだったデータがfactor型になって不等号を使用したフィルタリングができなくなる)
  • 1行のみのデータに対しapplyを掛けたいときはlapply関数(list-apply?)を使用する。applyを使おうとすると次元数が足りないというエラーが出力されて止まる。

2016年5月3日火曜日

Rでtwitterの検索結果から同表記異義語をフィルタする その2 Wikipediaの記事抽出

この記事はRでtwitterの検索結果から同表記異義語をフィルタする その1のつづきです。

RでWikipediaの記事を抽出する

WikipediR という便利なRパッケージがあったため、それを使う。WikipediR が提供する text というプロパティを使うと、Wikipediaの記事のヘッダやサイドバーを取り除いたHTMLが簡単に取得できるので便利。

それでも text が返却する文字には<a></a>の様なタグが含まれ、文章が途切れるので、xml2 パッケージを使い、テキストのみを抽出した。

この後、RMeCabパッケージを使い、各記事に書かれた文書の特徴となる語彙リスト(tf-idf)を作成するが、この作業では対象となる文書をファイルで指定する必要があるため、変数texts に記憶されたデータをファイルに書き出した。

install.packages("WikipediR")
install.packages("xml2")

library(WikipediR)
library(xml2)

page_name <- "小笠原諸島"

wp_html <- page_content("ja", "wikipedia", page_name=page_name)
# page_content関数は独自のpccontentクラスを返すため、
# その中のparse->text->"*"プロパティを明示しxml2ライブラリに文字列を渡す
tree <- read_html(wp_html$parse$text$"*", encoding="UTF-8")
content_text <- xml_find_all(tree, "//body//text()")
# content_text はxmlnode型のリストなので、テキストに変換する
# (xml2パッケージが提供するxml_text関数を使用)
texts <- sapply(content_text, xml_text)
# 改行のみ含まれる要素を削除する
texts <- texts[texts != "\n"]

# 保存先パスを生成する
file_path_and_name = file.path(WIKIPEDIA_CONTENT_DIR,
                                       # 保存先ファイル名
                                       paste(page_name, ".txt", sep=""))
# ファイルに保存する
write.table(texts, file=file_path_and_name, row.names=FALSE, quote=FALSE)

備忘

  • 最初はWikipediRを使わず、Wikimediaが提供する記事一覧のアーカイブ(ここの jawiki-latest-pages-articles.xml.bz2)をダウンロードし、それを使用する予定であったが、Wiki文法を取り除き平文にするシンプルな方法がみつからなかった。
  • 最終的にファイルに書き出すのであれば、ここの部分だけpythonなどで書いたほうが早かった気がする。

RでWikipediaの記事をランダムに抽出する

扱う文書量が少ない場合、一般的な名詞、たとえば「りんご」がたまたまクラスAの文書だけに出現していると、たとえ「りんご」とクラスAとの関連が弱い場合でも「りんご」が出現するツイートはクラスAに重み付けされて評価されてしまう。

こういった事態を避けるため、収集する文書を増やし、各クラスに含まれる一般的な語彙はなるべく打ち消されるようにした(tf-idf)。

WikipediRはランダムにWikipediaの記事を取得する random_page という関数を提供しており、これを利用した。はじめ namespace 引数の指定を省略して実行すると、Wikipediaの改版履歴など意図とは異なるページも取得したため、Built-in namespaces を参考に、記事(0)のみを指定するようにした。

wp_html <- random_page("ja", "wikipedia", 
           # 記事のみを対象とする(会話ノートやテンプレートページを含めない)
           namespaces = list(0))

Rでtwitterの検索結果から同表記異義語をフィルタする その3 集計とRStudio Projectファイルに続きます。

Rでtwitterの検索結果から同表記異義語をフィルタする その1

背景

twitterにおいて検索キーワードを指定し、そのキーワードを含むツイート一覧結果を受け取っても、違う意味の言葉が入ってくる。たとえば、検索したい人物を指し示す固有名詞を入力しても、同じ名字を持つ他の人物や、地名もヒットしてしまう。なんとかして固有名詞を更に詳細に区分できないだろうか。

今回は、東京都の小笠原に関するツイート調べるつもりで「小笠原」で検索した場合、野球やサッカーの小笠原選手に関するツイートもヒットしてしまう問題を取り扱い、小笠原選手に関するツイートを取り除く方法を考察する。

方針

(検証中)Wikipediaの記事を使い、東京都の小笠原と一緒に出現しやすい語彙一覧、野球の小笠原選手と一緒に出現しやすい語彙一覧、サッカーの小笠原選手と一緒に出現しやすい語彙一覧をそれぞれ作成する。

その後「小笠原」を含むツイートにて、そのツイート内の語彙から、どの小笠原に所属するツイートかを判定する。

自然言語処理の分野において、この手のタスクは曖昧語義性解消(Word Sense Disambiguation)と呼ばれる。Wikipediaを用いたアルゴリズムも既にいくつか発表されてるようだが、一方である書籍によれば「(意味解析は)実用レベルの性能には達しておらず,フリーのツールというものもいまのところ存在しない1」と記され、実際すぐに使えるツールというのもないようだ。
1奥村 2010 p.81

ツール

PythonでもRでも達成できそうで、どちらを使うか迷ったが、今後個人的にRを使う予定があるので勉強の意味も兼ねRを採用した。

手順

twitteRのセットアップ

フィルタリング対象となるツイートを収集するため、パッケージを利用する。

install.packages("twitterR")
Warning in install.packages :
  package ‘twitterR’ is not available (for R version 3.2.4)

早速twitterのデータ取得に使うライブラリが利用不可のようで、あせる。 問題は単純で、パッケージ名は "twitter R" ではなく "twitteR" 。

Setting up the Twitter R package for text analytics | R-bloggers を参考にtwitter appを新規作成し、そのappのconsumer keyなどを setup_twitter_oauth 関数を使い twitteR に設定。下記コマンドでツイートが収集できることを確認した。

searchTwitter("小笠原")

Rでtwitterの検索結果から同表記異義語をフィルタする その2 Wikipediaの記事抽出に続きます。

今回使用したRファイルをGitHubで公開しました。その3 集計とRStudio Projectファイル

参考書籍

2016年1月3日日曜日

再帰型ニューラルネットの一種、LSTMをsynaptic.jsで試す

再帰型ニューラルネット

再帰型ニューラルネットは回帰(型)ニューラルネット、あるいは循環ニューラルネットと訳されることもあるがどちらも同じもの1。再帰型ニューラルネットワークとは、内部に閉路をもつニューラルネットの総称2

1岡谷 2015 p.112
2岡谷 2015 p.114

synaptic.js

synaptic.jsはアーキテクチャによらないJavaScriptのnode.jsおよびブラウザ向けのライブラリ。
今回はDemoとして公開されている下記Discrete Sequence Recallを試す。

Discrete Sequence Recall (処理に時間が掛かるためPCブラウザでの閲覧を推奨)

このデモでは入力として色で分けられた●からなるSequenceを受け取り、灰色、黒色●(いずれもPromptsクラス)を受け取った時に水色と緑色(Targets)を、Sequence内の順番通りに出力する。Sequenceの中には、Targets、Promptsいずれにも含まれないDistractors()が含まれる。このネットワークはどれがTargetsか、Promptsか、Distractorsかを事前に知らない。

var LSTM = new Architect.LSTM(6,7,2);
LSTM.trainer.DSR({  
    targets: [2,4],  
    distractors: [3,5],  
    prompts: [0,1], 
    length: 10 
});

Architest.LSTM

ソースコード
Synaptic.jsが提供するLayerオブジェクトを活用し、ネットワークの構築が下記の様に行われる。peepholesが何を意味しているのかは不明。
architest.js 90行目-159行目
architest.js 165行目-173行目
165行目以降で新たに作成するTrainerオブジェクトのプロパティ値を用意する。new Trainer()の第一引数として指定された値は、Trainerのnetworkプロパティとなる。

Trainer.DSR

ソースコード
iterationの値(規定値:100000)の数だけ、シーケンスの作成、訓練を行う。

"TRY ANOTHER SEQUENCE"ボタンをクリックした時の挙動

ソースコード

validate関数が呼び出される。その後一連の手続きを経て(next関数に入る)新たなinput配列が生成され、それを引数として LSTM.activateが呼び出される。LSTM.activateはpredictionを返却し、このpredictionを引数としvalue関数を呼び出すと、その戻り値としてLSTMの出力が何番目のTargetsに当たるのかが得られる(Targetの出力がない場合はvalue関数の戻り値が"none"となる)。
next関数はinput, output行を出力する度に呼び出される。そのためnext関数内で実行されるLSTM.activateもその分だけ呼び出しを受ける。

参考書籍

  • 岡谷貴之(2015)『深層学習 』(機械学習プロフェッショナルシリーズ)講談社

2015年8月22日土曜日

Deep Learning のフレームワーク Chainer を使った画像分類 その4

前回記事: Deep Learning のフレームワーク Chainer を使った画像分類 その3

最初から: Deep Learning のフレームワーク Chainer を使った画像分類 その1

Optimizer

train_mnist.py 58-60行目

# Setup optimizer
optimizer = optimizers.Adam()
optimizer.setup(model.collect_parameters())

optimizerはこの後、訓練中に前述の関数 forward より返却された F.softmax_cross_entropy() の戻り値(オブジェクト)に対し backward() された(83行目)後に optimizer.update() される(関数名末尾の括弧は関数の実行を示す)。Optimizerはパラメータと勾配からなりたち、update()を実行するたびに、対応する勾配にもとづき、パラメータを更新する。

勾配について、学習のゴールは、誤差関数 E(w) に対し最小値を与える重み w を求める事だが、E(w) は一般に凸関数ではなく、大域的な最小解を直接得るのは通常不可能1。 代わりに E(w) の局所的な極小点 w を考えるが、E(w) の極小点は一般に多数存在するので、たまたまみつけた局所的な極小点が E(w) の大域的な極小解とは限らない1。それでも E(w) の値がある程度小さいなら、クラス分類の問題をそれなりにうまく解決できるという様に考える1。このとき、勾配とは

というベクトル(Mはwの成分数)1

今回、train_mnist.py は Optimizer として Adam法を使用する。理由、その他理論については不明。なお、Chainer のチュートリアルにおいてはSGD(確率的勾配降下法)が利用されている。

setup 関数は与えられたパラメータ/勾配に対し状態(?)を準備するOptimizerはパラメータと勾配を実際に管理している関数を知らないので、一度パラメータと勾配を指定したら、forword と backward を通して同じパラメータと勾配を使用する必要がある。

1岡谷 2015 p.24

エポックの走査(パラメータの更新)・訓練・訓練結果のテスト

訓練

train_mnist.py 62-90行目

# Learning loop
for epoch in six.moves.range(1, n_epoch + 1): 
    print('epoch', epoch)

    # training
    perm = np.random.permutation(N)
    sum_accuracy = 0
    sum_loss = 0
    for i in six.moves.range(0, N, batchsize):
        x_batch = x_train[perm[i:i + batchsize]]
        y_batch = y_train[perm[i:i + batchsize]]
        if args.gpu >= 0:
            x_batch = cuda.to_gpu(x_batch)
            y_batch = cuda.to_gpu(y_batch)

        optimizer.zero_grads()
        loss, acc = forward(x_batch, y_batch)
        loss.backward()
        optimizer.update()

        sum_loss += float(cuda.to_cpu(loss.data)) * batchsize
        sum_accuracy += float(cuda.to_cpu(acc.data)) * batchsize

    print('train mean loss={}, accuracy={}'.format(
        sum_loss / N, sum_accuracy / N))

train_mnist.py 24行目で指定されたエポック回数(20回)分、訓練と訓練の評価を繰り返す(forループ)。np.random.permutation 関数を使い毎回N(=60000← train_mnist.py 35行目より, =訓練用データの量)までの数値をランダムに並び替えた順列を生成し、その順列 perm を使い x_train および y_train よりデータセットを選択しているため、毎回対象となるデータが異なる。エポックとは岡谷 2015 p.28 によると「パラメータ更新回数」と同義の様に読み取れる。

np.random.permutation(N) より訓練が開始される。

訓練の結果のネットワークの性能を代入する sum_accuracy および sum_loss 変数の0による初期化に続き、さらに内側の forループが開始される。このループは0〜N(=60000)までを batchsize (=100) で刻んだ分だけ繰り返し、i には繰り返すごとに、0, 100, 200, .. , 59000 までの値が代入される。

訓練用データ x_train、y_train から x_batch および y_batch にランダムに batchsize (=100) 分のデータが代入される。GPU(一部のPCに入っている並列計算が得意なCPU)を使う場合は、データをGPUに転送する。

optimizer.zero_grad 関数で optimizer が管理する勾配のベクトルをゼロで初期化し、あらかじめ train_mnist.py の中で定義した forward 関数を実行する。

forward 関数では、その第一戻り値として F.softmax_cross_entropy の実行時の戻り値を指定した。そのため、変数 loss は関数 F.softmax_cross_entropy が返却する chainer.Variable型となる。loss.backward()では chainer.Variable が提供する関数 chainer.Variable.backward が実行される。この backward 関数は誤差逆伝搬を行う。「誤差逆伝搬」とは「順伝播」型ネットワークよりも効率的にネットワークの重みとバイアスに関する誤差関数の微分を計算する方法の1つ1。この前に forward 関数を使い順伝播を行っているにも関わらず、あらためて「逆」伝搬を行う事に違和感も感じるが、「順伝播(各層における入力 u = Wx + B、出力 z = f(u) の計算)を行った後に最終層の出力を使い、逆方向に(デルタを使い2)各層のパラメータに関する微分の式を求める」のが、誤差逆伝搬による誤差勾配の計算手順3

その後 optimizer に対し、関数 update を実行し、対応する勾配を使いすべてのパラメータと状態(?)を更新する。

内包されたforループの最後の処理として、今回の損失と正確さを変数、sum_loss と sum_accuray にあらかじめ代入されていた値に加算する形で記録する。

1参考: 岡谷 2015 p.41, 2岡谷 2015 p.46, 3岡谷 2015 p.48

訓練結果のテスト

train_mnist.py 92-100行目

# forループ内手続き宣言の続き
    # evaluation
    sum_accuracy = 0 
    sum_loss = 0 
    for i in six.moves.range(0, N_test, batchsize):
        x_batch = x_test[i:i + batchsize]
        y_batch = y_test[i:i + batchsize]
        if args.gpu >= 0:
            x_batch = cuda.to_gpu(x_batch)
            y_batch = cuda.to_gpu(y_batch)

        loss, acc = forward(x_batch, y_batch, train=False)

        sum_loss += float(cuda.to_cpu(loss.data)) * batchsize
        sum_accuracy += float(cuda.to_cpu(acc.data)) * batchsize

    print('test  mean loss={}, accuracy={}'.format(
        sum_loss / N_test, sum_accuracy / N_test))

訓練で調整されたパラメータを持つネットワークを使い、テスト用データでネットワークの性能のチェックを行う。forward関数を実行する際に、訓練時とは異なり train 引数の値として False を指定する。Falseを指定した場合、関数 forward の内部にて関数 F.dropout の引数 train の値がFalseとなり、ドロップアウトが無効化される。

参考書籍

  • 岡谷貴之(2015)『深層学習 』(機械学習プロフェッショナルシリーズ)講談社

2015年8月9日日曜日

Deep Learning のフレームワーク Chainer を使った画像分類 その3

前回記事: Deep Learning のフレームワーク Chainer を使った画像分類 その2

最初から: Deep Learning のフレームワーク Chainer を使った画像分類 その1

ネットワーク内伝搬手順の定義

train_mnist.py 51-56行目

# Neural net architecture
def forward(x_data, y_data, train=True):
    x, t = chainer.Variable(x_data), chainer.Variable(y_data)
    h1 = F.dropout(F.relu(model.l1(x)),  train=train)
    h2 = F.dropout(F.relu(model.l2(h1)), train=train)
    y = model.l3(h2)

    return F.softmax_cross_entropy(y, t), F.accuracy(y, t)

このforward関数はこの後、train_mnist.py内82, 102行目で直接参照し実行される。実行時、第一引数 x_data には画像データが指定され、第二引数 y_data には分類の答えのデータが指定される。

forward関数内ではまず、引数 x_data と y_data 両方をchainer.Variable()で包み込み、追跡可能としている(→chainer.Variable)。x_data はl1〜l3層を通り、最終的に変数 y となる。l1〜l2を通る際、relu関数を使った活性化とドロップアウトがなされる。

活性化関数とは、ユニットが受け取る入力u=w1x1+w2x2+w3x3+w4x4+bより出力zを生成する関数f。z=f(u)。活性化関数は古くは出力が0~1に収まる、ロジスティックシグモイド関数あるいはロジスティック関数と呼ばれるが使われてきたが、近年では入力が0以上であれば出力もそれに従って増加する(0以下の場合は0となる)正規化線形関数(Rectified linear function)がより良い結果が得られ、また計算量が小さいためよく用いられている1。この関数をもつユニットはReLU(Rectified Linear Unit)と呼ばれる1

ドロップアウトとは学習時にネットワークの自由度を強制的に小さくし、過適合を避ける方法の1つ。具体的には学習時に出力層以外の層のユニットについて決まった割合pで選出しその他を無効化する。pは層ごとに異なる値を指定しても構わない2。過適合(=過剰適合, 過学習)とは訓練後にネットワークの性能をテストした際に、パラメータ更新回数を増やすにつれ、テストの誤差が大きくなってしまう状態3

forward関数は最終的に、多クラス分類の誤差関数(損失関数, loss function, とも呼ばれる4)である交差エントロピー式の算出結果と、このミニバッチの分類の正確さを返却(return)する。

1参考: 岡谷2015 p.10-11, 2岡谷2015 p.31, 3岡谷2015 p.28, 4岡谷2015 p.15

次回、Optimizerとは何か、学習と学習結果の評価手順を紐解く。→Deep Learning のフレームワーク Chainer を使った画像分類 その3

参考書籍

  • 岡谷貴之(2015)『深層学習』(機械学習プロフェッショナルシリーズ)講談社