羊をめぐるブログ

趣味の色々について書きます

Multi-Modal Learningのサーベイ論文を読んだ

 

マルチモーダル学習についてのサーベイ論文を読んだので,見返しようにまとめてみたいと思います.

論文情報

Multimodal Machine Learning: A Survey and Taxonomy

Introduction

マルチモーダル学習の分野はそのタスクに寄って大まかに5つのカテゴリに分けられる.

Representation

マルチモーダルなデータをどう表現したり要約したりするかについて.テキスト情報や信号データなどを同一空間で扱えるかなど.

Translation

あるモダリティのデータを別のモダリティのデータに変換するタスク.画像からその説明文を生成するなど.正解が1つに絞られない,

Alignment

複数のモダリティ間の直接的な関係を明らかにするタスク.

料理を作るビデオを例とすると,その各シーンを正確に並び替えるには料理のレシピの情報と画像中の情報を結びつける必要がある.

Fusion

ある予測をするために複数のモダリティの情報を利用するもの.例として,スピーチの内容を正確に判定するために,音声に加えて動画中の話者の口の動きを使うなど.

Co-learning

あるモダリティ内で作られた予測モデル,ベクトル表現などを別のモダリティに転移させるようなもの.Zero-shot learningやco-training,conceptual groundingなどが例としてあげられる.あるモダリティ上で得られた知識が別のモダリティでどう活かされるかに興味が置かれる.

応用先

  • 唇の動きと音声を利用したスピーチ認識(Audio Visual Speech Recognition:AVSR).信号がノイジーな場合などに有効.
  • マルチメディアのインデクシングと検索.画像や音声をキーワードベースでなく直接画像などで検索する.
  • マルチモーダルデータを用いて人間の振る舞いを理解する.ビデオや発言からの感情認識など.
  • 言語と画像を対象としたもの.画像のキャプショニングなど.画像キャプショニングでは評価方法が一番の課題らしい.

Representation

Representationは大まかにJointとCoordinateの2つの種類に分けられる.

f:id:sheep96:20190329230313p:plain

Joint Representation

 {\bf x}_m = f({\bf x}_1,...,{\bf x}_n)

射影fとしてはDNNやボルツマンマシンなどが使われる.各モダリティの特徴表現(BoW,メル周波数ケプストラム係数など)への依存がある.

  • 一番シンプルな方法は,書くモダリティの特徴量をconcatinateするというもの.
  • 近年はニューラルネットやオートエンコーダを使うものが多い.事前学習が可能な点が有用だが,データが大量に必要だったり欠損データへの対応が難しい.
  • その問題に対応するためにDeep Boltz Machineが利用されたりする.DBMのような生成可能なモデルを使うことで一部モダリティの欠損に対応することができる.
  • シーケンス長が一定でないデータに対してはRNNなどの時系列モデルが用いられる.

Coordinate

 f({\bf x}_1) \sim g({\bf x}_2)

各モダリティに,共有空間へ射影するための関数を用意するタイプ.関数はモダリティ間で異なるが,射影後の空間は共有される.

  • Smilality models:モダリティ間の特徴ベクトルの距離を最小化するようなモデルを設計する.Web Scale Annotation by Image Embedding: WASABIEなどが有名なタスク.従来はモダリティ間の関係を表すラベルを元に射影行列を設計することなどがされていたが近年ではNNが用いられる.DeViSEなどはEnd-to-EndでWASABIEを行う.
  • Structured coordinate space: 距離以外の制約を加えてモデルを設計する.Cross-modal Hashingなどがある.

 

Translation

Translationは大まかにexample-basedとgenerativeの2つに分けられる.

Example-based

基本的に辞書データをベースにtranslationを行う.

Retrieval based modal

辞書データから最も近いデータを持ってくるというもの.モダリティの中で近いデータの検索を行うものと,共有空間内で近傍データを検索するものに別れる.テキストをスピーチに変換するタスクや画像のキャプション付などに用いられた.semantic空間を用いた検索は双方向の翻訳が容易などの利点があるが,その空間の設計のために辞書データ(学習データ)が大量に必要となるなどの問題がある.

Combination-based Model

検索ベースだが,その検索のクエリとして複数のモダリティのデータを結合したものを用いる.多くがhand-crafted特徴を用いている.

Generative-approaches

与えられたデータをもとに別モダリティのデータを生成するアプローチ.
ソースとターゲット両方のモダリティの性質を理解する必要があり難しい.

Grammar-based Model

事前にある程度Translationの規則(Grammar)を決めておくもの.

画像やビデオより物体や動作などの高レベルの概念を取得し,それをもとにTranslationを行うような感じ.

Encoder-decoder Model

ニューラルネットを用いたE2Eな手法.一度データを何らかのベクトルに落としてから別モダリティのデータへデコードする.機械翻訳のためのモデルがそのまま画像のキャプショニングに用いられたりしている.

Continuous generation Model

テキストから信号への変換のように,連続的なデータをオンラインに,タイムスタンプごとに変換していくタスク.graphicalモデル,continuous encoder-decoderモデルなどが用いられる.

Model evaluation and discussion

Translationの評価は難しい.現状人を集めて評価を行ってもらったり,別モダリティのデータのペアを互いにtranslateしあう,検索システムを使うなどの方法が取られている.

 

Alighment

異なるモダリティのデータの一部が与えられたとして,それらのなかで関連する部分を探すようなタスクと本論文では定義している.例えば画像とそれに対するキャプションが与えられ,画像中のキャプションに関連する箇所を特定するなど.

大まかにimplicitとexplicitに分割する.

Explicit alignment

あるインスタンスについてそのサブコンポーネントが2つ以上ある場合のalighnmentをここにカテゴライズする.

Explicit alighnmentにおいては類似度のはかり方が重要となる.学習ベースで類似度指標を設計する手法はUnsupervisedとsupervisedに分けられる.

Unsupervisedの多くは統計的機械翻訳の手法やgenome sequenceなどの手法からインスパイアされたもである.代表としてはDynamic time warping (DTW)などがある.

Supervisedの手法としては,CCAや混合ガウスモデルを用いるものなどが提案されてきたが,近年ではDeepLearning手法が台頭している.

Implicit alignment

explicitと違い,implicit alighnmentは他のタスクで利用するための潜在表現として利用される.これはVQAや機械翻訳などの厳密なalighnmentがないタスクに対する手法のパフォーマンスの向上に利用できる.手法は大まかにGraphical modelとNeural Networkに分けられる.

Graphical modelは機械翻訳やspeech phenemesのalignmentに利用されてきた.しかしそれらは人手での単語間のマッピングなどを必要とする.

Neural Networkは近年よく用いられており,encoder-decoderモデルはそれら従来の機械翻訳の問題に対応できる.しかし,implicit alignmentを利用しない場合,encoderへの要求が大きくなってしまう(うまく文書全体,画像全体を潜在表現に落とし込むことが求められる).この問題に解決する手法としてはattentionが有名である.

Discussion

Multimodal Aligmentの難しさとしては,1.厳密にaligmentがアノテーションされたデータセットが少ない,2.モダリティ間の類似度指標を定義するのが難しい,3.モダリティ間のデータに一対一の関係が成り立たない,などがある.

 

Fusion

fusionは,複数モダリティのデータを用いて予測を行うような分野である.マルチモーダル学習の中で最も歴史が長いものの一つ.

メリットとしては,複数モダリティのデータを用いることでよりロバストな予測を行うことができる,どこかのモダリティのデータが欠けていても予測ができる,などがある.

応用分野はAudio Visual Speech Recognition (AVSR),感情認識,医用画像解析,マルチメディアイベント検知など幅広い.

ここではFusionをmodel-agnostic,model-basedに分類する.

 

Model-agnostic approaches

model-agnosticは機械学習の手法と直接関係しないようなアプローチである(特徴量の組み合わせ方など).

歴史的にはこちらの方がより多く取り組まれてきた.大まかにearly, late, hybridに分けられる.

earlyは各モダリティの特徴量をそのままconcatしたりするというものが多い.各モダリティのlow levelでの関係を学習できる,基本的にモデルが一つしか必要ないなどの利点がある.

lateでは各モダリティの特徴を元にそれの平均をとる,結果のvotingを行う,分散やchannel noiseを元に重み付けをする,モデルを学習するなどの手法がとられた.これらの手法には各モダリティで別のモデルを利用し異なるアウトプットを得ることで,それらを柔軟に組み合わせられるという利点がある.また,どれかのモダリティのデータが欠けていても訓練や予測ができる.しかし,モダリティ間のlow levelでの関係を捉えられているとは言えない.

hybridではその両方を組み合わせており,話者特定やmultimediaイベント検出で成果をあげている.

 

Model-based approaches

単一モダリティに対して利用される機械学習手法を複数モダリティ用に拡張する.

Multiple kernel learning (MKL) はカーネルSVMを拡張したものである.モダリティごとに別々のカーネルを利用する.MKLベースの手法は様々な応用ができ,また,損失関数がconvexなのでスタンダードな最適化パッケージを利用できる.分類,回帰の両方に対応できるという利点もある.デメリットとしてはトレーニングデータへの依存の高さ,テスト時の推論の遅さ,メモリの使用料などがあげられる.

Graphical Modelsによる手法は大まかに,同時確率をモデリングするgenerativeと条件付き確率をモデリングするdiscriminativeに分けられる.初期ではcoupled and hidden Markov modelとdynamic Bayesian networkを組み合わせるgenerativeモデルが提案された.

Graphical Modelの利点として,データの局所的,一時的な構造を捉えるのが容易であるという部分がある.また,専門家の知見をモデルに組み込み,解釈可能なモデルを設計することができる. 

ニューラルネットは近年よく利用される.主な応用例としては,image captioning,audio-visual emotion classificationにCNN,RNNを用いるケースがあげられる.ニューラルネットの優位な点として,大規模なデータを有効に利用できるという点,RNN系列の手法がマルチモーダルでの表現学習とfusionの両方に利用できるという点がある.また,他に劣る点としては,結果の解釈性が低く,多くのデータセットが必要になるということがある.

全体として,現状,ニューラルネット系列の手法が多く利用されるが,以前graphical modelとmultiple kernelも利用される.現状の課題点として,1) 信号が稠密なのに対して,重要なイベントがスパースである,2) データの全体でなく,一部を利用するようなモデルを設計するのが難しい,3) モダリティごとに違うタイプのノイズが違うタイミングで現れてくる,などがある.

Co-learning

co-learningはあるモダリティにおいてモデルを設計する際,(データが豊富にあるような)別のモダリティのデータを利用するというものである.co-learningにおいては,訓練時のみに別モダリティのデータを利用し,テストの際には利用しない.co-learningはそれが訓練時に利用するデータのリソースに応じて,parallel,non-parallel,hybridに分けられる.

f:id:sheep96:20190921002825p:plain

Parallel data

parallel dataは,あるモダリティのデータと別モダリティのデータが完全にリンクしているようなケースである.例として,映像とその音声の組み合わせがあげられる.このケースにおいては,それぞれのモダリティにおいてモデルの性能の向上が期待できる.

Co-trainingはマルチモーダルなタスクに置いて少量しか訓練データが存在しない際に,ラベル付きのデータを生成するような処理である.例として,ラベルなしデータに対応するラベルありデータを別モダリティから取り出すために,弱学習機を各モダリティのために設置するなどが行われる.
このようにCo-trainingは学習データの増加に利用できるが,モデルのオーバーフィットなどが課題になる.

 
また,転移学習もparallelデータに大してco-learningを行う方法の一つである.
multi-modal Boltzmann machineやmulti-modal auto-encoderはあるモダリティの情報を別モダリティに転移させるのに利用される.これはマルチモーダルな表現の学習だけでなく,単一モダリティの表現力の向上も行うことができる.具体例として,スピーチ情報をリップリーディングに組み合わせ訓練を行いつつ,テストを行う際にはスピーチ情報を必要とせずリップリーディングの性能を向上させた研究が行われている.

Non-Parallel data

non-parallel dataは,モダリティ間でインスタンスが共有されていないが,カテゴリやコンセプトは共有されているようなケースである.このケースで利用される手法は,表現学習の際によりセマンティックな情報を取り込ませたり,未知オブジェクトへの対応などに利用できる.

転移学習はデータがnon-parallelな状況に対しても適用できる.転移学習によって,よりデータが多く綺麗なモダリティから良い表現を得るといったことが可能になる.代表として,タグの学習済みエンベディングを画像分類モデルの訓練に組み合わせるというものがある.このように訓練されたモデルは,たとえ分類が間違っていたとしても,元のカテゴリに近いクラスと判定したりと,エラーに関しても多くの情報量を含んでいる.

Conceptual groundingは言語上の意味情報だけでなく,視覚や音声など他のモダリティの追加情報を意味づけに利用するようなケースである.人間の知識は様々な知覚情報に大きく依存しているため,言語情報のみで意味づけを行うのは最適とは言えない.
初期の研究としては,各モダリティ間で共通の潜在表現を獲得するもの(Parallelなデータに対して),各モダリティそれぞれで表現学習を行なったのちそれを組み合わせる(Non-Parallelなデータに対して) などが行われていた.一度マルチモーダルな表現が得られれば,それはピュアなNLPタスクに利用できる.使用の一例として,それらの表現をメタファーやリテラル言語の分類に利用する研究が行われている.これらの表現はデータが意味的にどの程度似ているかを測る際などに有用である.
Conceptual groundingによって様々なタスクのパフォーマンス向上が期待できるが,groundingがタスクに関係ないようなケースでは,逆にモデルの性能を劣化させてしまうこともある(見た目が似ているような概念を結びつけるタスクなど).

Zero-shot Learning (ZSL)は見たことのないコンセプトを持つデータをモデルに知覚させるようなタスクである.例えば,猫という概念を猫とラベルがついたデータなしにモデルに認識させることを目標とする.これは画像認識など,ラベルを与えなければならないデータが際限なく存在するようなタスクで重要となる.

ZSLには大まかにunimodalとmultimodalの二つのタイプがある.unimodal ZSLでは,対象のパーツや特性を元に認識を行う.例として,音素を元に聞いたことのない単語認識したり,画像中の物体のパーツの一部から判定するといったものが存在する.multimodal ZSLでは,他のモダリティの情報を利用して本来のモダリティの認識を行う.multimodal ZSLは,Non-Parallelなデータに適用する際,適用するモダリティごとにオーバーラップしているクラスが多くの場合で異なってしまうという問題がある.
ZSLの例として,画像特徴を単語の意味空間に埋め込む研究が行われた.これによって,unseenなオブジェクトは視覚的に類似している単語に割り当てが行われる.他にも画像とコンセプトが協調しているような空間を設計する研究,いくつかの画像から人の想像している単語を予測する研究などが行われてきた.

Hybrid Data

Hybridのケースは,non-Parallelな2つのモダリティを,共有されている1つのモダリティによって橋渡しするようなものである.有名な手法として,Bridge Correlational Neural Networkというものがある.適用先として例えば多言語での画像キャプショニングがあり,その場合は画像が複数言語の間で共有される情報となる.この手法は別言語への翻訳を行う際,常に対応するコーパスが存在しないような場合に利用される.

 Co-Learningはfusion,translation,alignmentなど様々なタスクに適用でき,実際,画像認識,動作認識,ASVRなど様々な箇所に応用されている.

 

長くてめちゃめちゃ雑になってしまいました...

ProjE: Embedding Projection for Knowledge Graph Completionを実装,実験してみた

はじめに

ナレッジグラフ(KG)の埋め込み手法であるProjEを実装,実験してみました. 他の手法に比べると引用数が少ないですが,結構シンプルで精度も出て,リンクの予測ができたりベクトルの事前学習も必要ないなどいろいろな利点が存在するようです. 論文は以下になります.バージョンが2つあり,訓練のアルゴリズムは古い方のものにだけ書いてあります.

[1611.05425] ProjE: Embedding Projection for Knowledge Graph Completion

ナレッジグラフの埋め込みでは,Tramp is Presidentのような主語\boldsymbol{h},述語\boldsymbol{r}, 目的語\boldsymbol{t}のセット(RDFトリプル) に対して

 \boldsymbol{h}(Tramp) + \boldsymbol{r}(is) \approx \boldsymbol{t}(President)

を満たす空間を設計することを目的としています. 有名な手法としてはTransEなどがあります.

Translating Embeddings for Modeling Multi-relational Data

本論文ではそれらの従来手法よりパラメータ数を減らしつつ,高精度なエンティティ,リンク予測を 行える埋め込み空間を設計する手法を提案しています. ただ,エンティティ予測の際,TransEなどが埋め込みベクトルの類似度のみでそれを行なっている のに対して,本手法では予測のための重みを掛けてから類似度をはかるということをしています. なので単純なエンベディングとはちょっと違うかもしれません.

手法の概要

まず,エンティティベクトル\boldsymbol{e}とリレーションベクトル\boldsymbol{r}を足し合わせる演算として次を定義します.

 \boldsymbol{e} \oplus \boldsymbol{r} = \boldsymbol{D_e} \boldsymbol{e} + \boldsymbol{D_r}\boldsymbol{r} + \boldsymbol{b}_c

ここで \boldsymbol{D_e} \boldsymbol{D_r}は,ベクトルの次元 kを縦横のサイズとして持つ対角行列です. つまりベクトルの各要素を定数倍するだけのものになります.ここで普通のdenseな行列でええやろとしていたら,精度があまり出ませんでした.

次に,この関係を満たすエンティティを探すため, 候補エンティティのベクトルを列として持つ行列 \boldsymbol{W}^cに対して次の演算を行います.

 h(\boldsymbol{e}, \boldsymbol{r}) = g(\boldsymbol{W}^c f(\boldsymbol{e} \oplus \boldsymbol{r}) + b_p)

式中のf, gはsigmoid,tanhなどの活性化関数です.ここでは,\boldsymbol{e} \oplus \boldsymbol{r}で得られたベクトルと, 全ての候補エンティティとの類似度計算をしています. h(\boldsymbol{e}, \boldsymbol{r}) i番目の要素は 候補iとの類似度ということになります.

最後に損失関数の部分になります. 損失としては関係を満たすエンティティと満たさないエンティティを分類するpointwise lossと, 候補エンティティが正解となる確率について負の対数尤度を最小化するlistwise,listwiseに重みを追加したwlistwiseが提案されています. point wiseの式は以下になります.

f:id:sheep96:20190831163817p:plain

f:id:sheep96:20190901172503p:plain

ほぼBinary Cross Entropyです.第一項は正例を評価していて,第二項は負例を評価しています. 負例は,e+rを満たさないエンティティから二項分布でサンプリングしてきます.

そしてwlistwiseは以下になります.

f:id:sheep96:20190901172627p:plain

f:id:sheep96:20190901172641p:plain

softmaxで出力を確信度の形式にしてから,負の対数尤度を取ります. wlistwiseでは対数尤度の足し合わせの際,(e,r)の関係を満たすエンティティの総数の逆数を 重みとして掛け合わせます.通常のlistwiseの際は重みを利用しません.

実装・実験

実装はPytorchで行い,実験はQuadro P6000を用いました. 全然整理できていないのですが,コードは以下になります.

GitHub - sheepover96/ProjE.torch: pytorch implementation of ProjE: Embedding Projection for KGC

また,tensorflowによる公式実装は以下になります.

GitHub - bxshi/ProjE: Embedding Projection for Knowledge Graph Completion

公式実装は学習結果の表示が綺麗です.あと公式なので多分正確です. 私の実装の方は公式に比べ学習が数倍早いのですが,メモリをめちゃくちゃ食います. これはネガティブサンプリングのための候補をキャッシュしているかどうかの違いだと思います.

データセットはFB15KとWN18RRを利用しました.

Relation Prediction | NLP-progress

評価は,論文中で利用されているMean RankとHIT@10を用いました. Mean Rankは,目標のエンティティが現れるまでのランクの平均で, HIT@10は,目標のエンティティが10位以内にあらわれる割合です.

結果は次のようになりました.オレオレの方はtailの予測を行なった結果ですが, 公式の方はtail, head予測どちらをやってるのかわかりませんでした.また, 実験結果はwlistwiseのものだけを示しています.

実装 MeanRank HITS@10
論文 124 54.7
公式(tail) 182.9 49.4
公式(head) 275 41.5
オレオレ(tail) 153 57.8
オレオレ(head) 252 49.9

公式実装の方は,以前動かした時は論文ぐらいの精度だったのですが,なぜか今回は調子が悪いです. オレオレ実装は,部分的に論文に勝ったり負けたりしていますが,それっぽい結果は出ているんじゃないかと思います.

重松清さんの「疾走」を読んだ

重松清さんの「疾走」を読んだので感想を書きます.

概要

インターネッツ上では重松清さんの異作かつ名作として有名なようです. 直木賞を受賞した「ビタミンF」では家族の生々しい現実としかしそこに残る暖かい関係というのが書かれていた気がしますが, そういうものとはかけ離れていたように思えます. 私は悲壮を感じるのがとても好きなので,この作品もとても好きになれそうでした.読むのに熱中して朝日が上るのを目撃してしまいました.

なぜ読んだか

名前も顔も,そもそも存在するかもわからないけど間違いなくいい人に,おすすめの作家として重松清さんを教えてもらいました. 最初は有名な「ビタミンF」を読んでいたのですが,作中のテーマだと思われる家族の絆が私には非常に現実的かつ, 生々しく感じられてしまいました.私は母や父が苦労しつつも注いでくれる精一杯の愛というようなものに,なぜだか 私が好む悲壮とは全く反対の悲しさを見てしまいます.自分が親の人生を阻害してしまっているという申し訳なさが 浮かぶからかもしれません.なんにせよ,「ビタミンF」を読んでいると辛い気持ちになりました. なのでもっと絶望に近いような作品はないか,ということで「疾走」を読み始めました.

最初の印象

裏表紙のあらすじやネット上に書かれたストーリーの概要も見ずに読み始めたので,話に関する先入観は, 絶望の予感以外にはありませんでした.衝撃的な表紙からも,人生をなくし,行くあてもなく疾走する少年の絶望のようなものが 感じられました

内容の感想

期待を超える衝撃を常に与えてくれる小説でした.不穏な空気が漂う家族と,ビー玉越しの景色のように歪みつつも 綺麗だったりする町.それらが止まることなく壊れていく様子.素晴らしい人間になれただろうに,周りの崩壊に 合わせてどんどん壊れていく主人公.全てに期待せず別世界で生きているようなヒロイン. 心の救いであり,祈り続けてくれる神父様.それらを書く疾走感のある文章.読んでいる途中も,読み終わった後も, すげぇなぁと思いました.私はこの小説のように,人間の感情と,それによる出来事の連鎖からなる,人の人生そのもの を表すような作品がとても好きです.特にこの小説は,登場人物の環境,心情の絡みが空気感も合わせてそのまま表現 されているようで,自分が人生を追体験しているように思えました.

小説中にはよく聖書の引用が出てきます.私は,宗教というものがよくわかりませんでした. でも,どんな人生の先にも誰かの祈りと,一筋の救いがあると,信じるためにあるのかと何と無く納得しました. 人生には間違いなくどうしようもないことがたくさんあって,それは人間そのものを破壊しようとしてきます. 私が今普通に生きれているのも,そんなどうしようもないことに出会う機会がたまたま少なかったからです. 自分は運よく生きれていて,それがいつ壊れるかもわからない.もし壊れてしまってもどこかに希望や救いがある. 与えられたものを楽しみ,精一杯生きていこうと思いました.

西加奈子さんの「サラバ!」を読んだ

西加奈子さんの「サラバ!」を読んだので感想を書きます.

概要

西加奈子さんの直木賞受賞作です.出たのはもう4,5年前ですが,今でもよく本屋で見かけるので,そういうことだと思います. そういうことなので私は徹夜で読んでしまいました.

なぜ読んだか

数年前,塾でバイトをしていた時に,本好きの生徒さんがいて,おすすめとして紹介してもらいました. 私は普段,流行に乗っているようなものを読むことはほとんどないのですが,その勧めてくれた人とはいろんな趣味が あって,また人間的にもすごく立派な方で,尊敬していたので,その人の一押しなら間違いないと思いました. 残念なことに,これを勧めてもらった時の私はお金のためにバイトを詰め込んでいて,精神的余裕がありませんでした. 結局バイトを辞めて地元を出るまで,これに手をつけることはなかったのですが,それから数年が経ち,読み終わった今,すごく後悔しています.

最初の印象

父親の事情で海外移住を繰り返す主人公が,エジプトで大切な友達と出会う...

裏に書かれたあらすじを見た時,私はこの小説のテーマが私の精神と対局にあるように思えました. 家に引きこもって一人で楽しんでいるような人間が,見知らぬ土地で言葉も通じぬ人間と心を通わす などという事象に同調するのは非常に難しいのです.同調するような心が自分にあることを認めたくはないのです. 私にはない幸せな心の交流を燦々と見せつけられ,うんざりしそうだなとか思っていました.

内容の感想

とても予想を裏切られました.だいたい裏切られます.幸せな黄色で埋め尽くされていると思っていた小説内の空気は, 実はその多くがどんよりとしたもので覆われていました.汚い環境,不穏な家庭,格差,嫉妬とプライド, 後悔,そういった暗いものが常につきまとっていました.私が人生で感じた,どうしようもないネガティブな 感情というものが多く表れており,最高に同調しました. しかし,そういうものに心を魅了されたから こういうのを書いているわけではありません.私が,すごいとなったのは,そんな暗い空気の中を優しく,力強く 生きている主人公の周りの人間たちです.読んでいる時,私は,自分が生きているのが恥ずかしくなりました. 立場,能力,目標そんな自分ごとにばかり目を取られ,己のダメさばかり呪っていた自身が,周りの人間にいかに恵まれていたか, そしていかに多くのものを切り捨ててきたかを分かった気がします.

人間関係を軽視していた私が,もっと人と,心から温かく交流していきたいなぁと思える小説でした. 勧めてくれた人と会って話したいです.

青空文庫の全小説でword2vecしてみる

はじめに

word2vecの日本語学習済みベクトルというと,wikiやWebサイトのコメントなどの割と説明的な文章に対して作られたようなものが多い気がします.もう少し叙情的な分散表現を得られないかな(テキトー)と考えて青空文庫の文章全体を使ってやって見たのでその過程と得られた結果について書きます.

データセット作成

データセットは以下で作ったのを用いました.

serenard.hatenablog.com

分散表現の学習

gensim内のword2vecを使って分散表現の学習をします.

gesim.word2vec.Word2Vecには次のようなtokenizeした文を要素として持つリストを与えます.

[
 [...tokenizeした1文],
 ...,
 ['\u3000', '底本', 'Project Gutenberg', 'Canada', 'です', '。', 'この', '翻訳', '独自', '行く', '、', '先行', 'する', '訳', '類似', 'する', '部分', 'ある', '偶然', 'による', 'です', '。', 'しかし', '、', '大型', '宇宙船', '飛び交う', '時代', 'なる'],
 ['\u3000', 'この', '訳文', 'Creative Commons', 'CC', 'BY-SA', '公開', 'する'],
....,
]

データセット内の小説は1行に大体2文ぐらいが入っているのですが,分割が面倒なため複数文をまとめてtokenizeして利用します.tokenizerはMeCabを使いました.また,stop wordとしてSlothlibにひらがな1文字を付け加えたものを定義しています.最終的なコードは以下のような形です.

import MeCab as mecab

hiragana = [ chr(i) for i in range(12354, 12436)]
with open('stop_words.txt', 'r') as f:
    content = f.read()
    stopwords = [word for word in content.split('\n')]
stopwords.extend(hiragana)

def tokenizer(words, part_use=['名詞', '形容詞'], normalize_word=True):
    tagger = mecab.Tagger('-d /usr/local/mecab/lib/mecab/dic/mecab-ipadic-neologd ')
    tagger.parse('')
    mecab_word_nodes = tagger.parseToNode(words)
    tokenized = []
    while mecab_word_nodes:
        elements = mecab_word_nodes.feature
        word = mecab_word_nodes.surface
        element_list = elements.split(',')
        if normalize_word:
            word = element_list[6]
        part = element_list[0]

        if not word in stopwords:
            if part is None or part_use is None:
                tokenized.append(word)
            elif part in part_use and not word.encode('utf-8').isalnum():
                tokenized.append(word)

        mecab_word_nodes = mecab_word_nodes.next

    return tokenized


all_novel_lines = []
for author in os.listdir('./novel')
    for novel in os.listdir(os.oath.join('./novel', author)):
        with open(os.path.join(os.path.join('./novel', author, novel)), 'r') as f:
            novel_content = f.read()
            novel_text_lines = novel_content.split('\n')
            tokenized_novel_text_lines = [ tokenizer(text_line, part_use=None) for text_line in novel_text_lines]
            all_novel_lines.extend(tokenized_novel_text_lines)

model = word2vec.Word2Vec(all_novel_lines, iter=100)
model.save('aozora_model.model')

ハイパーパラメータは学習epoch数以外はデフォルトとしました(あまり詳しくない).

学習後のモデルの解析

学習後のモデルが普通のword2vecとどんな感じで違うのか見てみます.

比較対象としては http://www.cl.ecei.tohoku.ac.jp/~m-suzuki/jawiki_vector/ を用いました.

類義語

まずは自分が昔はまっていた江戸川乱歩さんにありがちな”怪人”に類似語を調べてみます.

通常のwikiベクトルでは,

#Wikipedia
wiki_model = KeyedVectors.load_word2vec_format('./pickle_objects/entity_vector.model.bin', binary=True)
print(wiki_model.wv.most_similar(positive=['怪人'], topn=10))
[('怪獣', 0.8180444240570068), ('ウルトラマン', 0.7956470251083374), ('魔人', 0.7853308916091919), ('[ウルトラセブン_(キャラクター)]', 0.7787925004959106), ('[ウルトラマン]', 0.7747620344161987), ('[バルタン星人]', 0.7678325176239014), ('[怪獣]', 0.7658182382583618), ('[ウルトラQの登場怪獣]', 0.7623452544212341), ('[ショッカー]', 0.759750247001648), ('宇宙人', 0.7564390897750854)]

完全にウルトラマンですね.

一方,今回学習した青空ベクトルでは,

#青空ベクトル
w2vmodel = word2vec.Word2Vec.load(MODEL_PATH2)
print(w2vmodel.wv.most_similar(positive=['走る'], topn=10))
>[('面相', 0.7113069891929626), ('怪物', 0.6929771900177002), ('透明人間', 0.6617274284362793), ('宇宙怪人', 0.6395553946495056), ('青銅の魔人', 0.6285749077796936), ('丸木', 0.6179652214050293), ('怪盗', 0.6169403195381165), ('恐怖王', 0.6136674880981445), ('骸骨', 0.6076086759567261), ('黄金仮面', 0.5900589227676392)]

懐かしいタイトルがたくさん並んでいます.

次に,走れメロスからとって,動詞の'走る'を比較して見ます.

wiki_model = KeyedVectors.load_word2vec_format('./pickle_objects/entity_vector.model.bin', binary=True)
print(wiki_model.wv.most_similar(positive=['走る'], topn=10))
>[('通る', 0.7714841365814209), ('横切る', 0.7033817172050476), ('駆け抜ける', 0.6959885358810425), ('走り抜ける', 0.6874959468841553), ('貫く', 0.6851664781570435), ('走り', 0.6783742904663086), ('歩く', 0.6761924624443054), ('通り抜ける', 0.6483071446418762), ('抜ける', 0.6402491331100464), ('走れる', 0.6378321647644043)]

類義語が多いような感じでしょうか.

w2vmodel = word2vec.Word2Vec.load(MODEL_PATH2)
print(w2vmodel.wv.most_similar(positive=['走る'], topn=10))
>[('走り出す', 0.7814075350761414), ('駆ける', 0.7684956192970276), ('駆け出す', 0.7316458225250244), ('走り去る', 0.7313833236694336), ('馳', 0.7097421884536743), ('横切る', 0.7063639163970947), ('駈ける', 0.7036117315292358), ('疾走', 0.6949977278709412), ('疾駆', 0.6659486293792725), ('はしる', 0.6658031940460205)]

類義語の中でも疾走感を付け足したような単語が並んでいる気がします.

最後に'メロス'を試して見ます.

#Wikipedia
print(wiki_model.wv.most_similar(positive=['メロス'], topn=10))
>[('[メリアドク・ブランディバック]', 0.6494805812835693), ('ニルス', 0.6397469639778137), ('クローディオ', 0.6338703632354736), ('[ベルガリオン]', 0.624444842338562), ('[エオウィン]', 0.6243359446525574), ('ローハン', 0.62108314037323), ('アベル', 0.6190121173858643), ('アンジェリカ', 0.6186339259147644), ('エリサ', 0.6166181564331055), ('エルザ', 0.6157675385475159)]

世界の児童文学作品などのキャラクターが多く出てきているようです.

#青空ベクトル
print(w2vmodel.wv.most_similar(positive=['メロス'], topn=10))
>[('素戔嗚', 0.4792671203613281), ('鞭', 0.4768710732460022), ('勇ましい', 0.47102075815200806), ('ふりほどく', 0.4657329320907593), ('力いっぱい', 0.45828065276145935), ('ハーキュリーズ', 0.4551655948162079), ('振り上げる', 0.45437756180763245), ('遮二無二', 0.452807754278183), ('セリヌンティウス', 0.45001697540283203), ('勇む', 0.4490892291069031)]

小説の情景が浮かぶような単語が出てきていそうです.セリヌンティウスもちゃんといます.

ベクトルの足し引き

'メロス'から'走る'を引いて見ます.

#青空ベクトル
merosu = w2vmodel.wv['メロス']
hashiru = w2vmodel.wv['走る']
sub = merosu - hashiru
print(w2vmodel.most_similar([sub]))
>[('黄道吉日', 0.4538549482822418), ('メロス', 0.4192773997783661), ('真心', 0.41565418243408203), ('こそ', 0.41269564628601074), ('総統', 0.4102414846420288), ('政恒', 0.4036811888217926), ('潔い', 0.403376042842865), ('心から', 0.4014996588230133), ('生殺与奪の権', 0.40138477087020874), ('得度式', 0.3940085172653198)]

メロスから走りを抜くと真心が残るようです.

まとめ

word2vecはおもしろい.gensimはべんり.

今度は作ったベクトルを画像と近づけたりしてみようと思います.

使ったコードは以下においてあります.

GitHub - sheepover96/aozora_analyzer

青空文庫内の小説を全ダウンロードして解析用にきれいにする

はじめに

青空文庫のWebページはありがたいことに小説を含む全ページのソースがgithub上に公開されています. しかしそのままデータを用いようとすると,ルビがあったりエンコーディングがshift-jisだったりで少し使いづらいです. そこで青空文庫のレポジトリ内部のデータをパースして以下のような形式で保存することを行なったのでその手順について備忘録として書こうと思います.

novels
├── 芥川龍之介
│   ├── 河童
│   ├── 蜘蛛の糸
├── 坂口安吾
│   ├── 白痴

使ったもの

小説データの保存手順

まず青空文庫のレポジトリをクローンしてきます.

github.com

git clone git@github.com:aozorabunko/aozorabunko.git

クローンしてきたレポジトリに移動すると色々入っていますが,小説データなどは./aozorabunko/cards の中に入っています.

cards内のファイルをパースする前にエンコーディングを全てshift-jisからutf-8に変更します.

find cards -name '*.html' -exec nkf -w --overwrite {} \;

自分は全て上書きしてしまいましたが,文字化けで見れなくなるので別のところに保存した方がいいかもしれません.

次にcards内の小説データを全てパースします.自分はpythonとbeautiful soupを用いました. 主に小説のhtmlデータから著者,タイトル,メインテキストを取り出し,ルビを取り除いて保存するということを行なっています. 自分以外の環境で動くかはわかりませんが使用したコードは以下です.

from bs4 import BeautifulSoup as bs
import numpy as np

import re, sys, os

def make_dir(dir_name):
    if not os.path.exists(dir_name):
        os.mkdir(dir_name)

def save_novel(file_path, text):
    if not os.path.exists(file_path):
        print('saved', file_path)
        with open(file_path, 'w') as f:
            f.write(text)

if __name__ == '__main__':
    pass

    cards = os.listdir('./cards')

    for card in cards:
        if card.isdecimal() and os.path.exists('./cards/' + card + '/files'):
            novels = os.listdir('./cards/' + card + '/files')
            for novel in novels:

                if novel[-4:] == 'html':
                    with open('./cards/' + card + '/files/' + novel, 'r') as f:
                        novel_page_html = f.read()
                        novel_parsed = bs(novel_page_html, 'html.parser')
                        try:
                            for rt in novel_parsed('rt'):
                                rt.decompose()
                            for rp in novel_parsed('rp'):
                                rp.decompose()
                        except AttributeError as err:
                            print(err)
                        novel_title = novel_parsed.find('h1', class_='title')
                        novel_author = novel_parsed.find('h2', class_='author')
                        novel_content = novel_parsed.find('div', class_='main_text')
                        if novel_author is not None and novel_author.string is not None:
                            make_dir('./novels/' + novel_author.string)
                            save_novel('./novels/' + novel_author.string + '/' + novel_title.string, novel_content.text)

これでおおよそ14000の小説データがnovels以下に保存されました.全部でおよそ500MB程度です. 校正を行なってくださっている方たちに頭があがらねぇ...

桜庭一樹さんの「私の男」を読んだ

桜庭一樹さんの「私の男」を読んだので印象と感想を書きます.

 

概要

「砂糖菓子の弾丸は打ち抜けない」,「GOSICK」などのライトノベルで人気を博した桜庭一樹さんが一般小説においてもすごいことを示す直木賞受賞作です.影のある親子の壊れた生活を振り返っていく話です.

なぜ読んだか

桜庭一樹さんのことは,少しでもサブカル系に足を踏み込んでる人なら誰でも聞いたことはある「砂糖菓子の弾丸は打ち抜けない」で知っていました.

その後,表紙がかわいい「GOSICK」を買いに行ったところ,コーナーにこの本が置いてあり,一般文芸でも描いてて直木賞まで取ってるんだ〜,読んでみよう.と思って同時に購入しました. 買ったのは4年前ぐらいなのですが,積ん読整理の過程で目についたので読み始めました.

最初の印象

「私の男」というタイトルから,ダメな夫に振り回されつつも世話を焼いてしまう妻の日常を書いたオムニバス小説と想像していました.表紙の目が虚と花で覆われて倒れ込んでいる人間は,共依存を続けることで同化し,枯れてしまった姿なのかなぁなど.

内容の感想

まさかここまでハードな内容だとは思いませんでした.あっさりとした表紙とタイトルの字体からは想像できなかったです.

結婚という一般的な人生最大の幸せを迎えているにも関わらず,心は別にあるような主人公.突然消えるように死んだ妖しい義父.苦労話が始まるのかと思ったら,実際はもっと恐ろしくて,壊れた親子生活が振り返られます.最後まで読み終わった後に,安定した人間との結婚式を迎えた主人公の気持ちを察すると,とても恐ろしいです.あれだけ人間として破滅した日常を送っていたのに,今更普通に幸せな生活をしていけるのかと.父親が死んだ今,誰にも言うことができないし,逃げることもできない.戻りたくても戻れない.父親は主人公を歪ませ,地獄を歩ませるために生きていたのかと思えるほどです.

現実的かどうかはわかりませんが,生々しく人間が書かれた小説を読むのは久しぶりでした.田舎の閉塞感や,それをけがらわしく見る主人公の視点の描写が生々しかったです.いろんな小説を読むようにしたいきたいです.