宇宙の渚を眺めるエンジニアのブログ

技術的な備忘録や、日々思ったこと、たまに宇宙関連について綴る

Multi-Head Self-Attentionを用いたSNLIタスク

勤め先のグループで、ここ一年間SNLI(Stanford Natural Language Inference)というタスクに取り組もうということになっていた。どういう手法でタスクに取り組もうかと調べていたときに、最近発表されたBERTモデルがその元となったTransformerというモデルのMulti-Head Self-Attentionを利用していると知り、Multi-Head Self-Attentionを自分で実装して、SNLIタスクに取り組んでみた。

SNLIタスクについて

SNLIは、英語で書かれた前提となる文と仮説となる文の2文を比較して、それが、entailment(含意)・contradiction(矛盾)・neutral(中立)のどれに当てはまるかという推論を行う3値分類のタスクである。より詳しくは、

などを参照。

SNLIのデータ件数
  • train: 549367/550152 (gold_label ありデータ/全データ)
  • validation: 9842/ 10000 (gold_label ありデータ/全データ)
  • test: 9824/ 10000 (gold_label ありデータ/全データ)

Multi-Head Self-Attention について

Scaled Dot-Product Attention

Transformerの論文では、まずScaled Dot-Product Attentionが導入されており、以下のような式で表される。


{\rm Attention}(Q, K, V) = {\rm softmax}\left(\frac{QK^T}{\sqrt{d_k}}\right)V

Self-Attentionの場合は、Q, K, V はすべて同じものが入力される。 また、Q, K^{T}内積\sqrt{d_k}でスケーリングしているのは、次元数\sqrt{d_k}が大きいと内積が大きくなり、逆伝搬のsoftmaxの勾配が小さくなることを防いでいるようだ。

Multi-Head Attention

続いて論文では、以下の式でMulti-Head Attentionが導入されている。


\begin{eqnarray}
{\rm MultiHead}(Q, K, V) &=& {\rm Concat}({\rm head_1, ..., head_h})W^O \\\
{\rm where}  \;\;\; {\rm head_i} &=& {\rm Attention}(QW^Q_i, KW^K_i, VW^V_i) \\\
\\\
W^Q_i \in \mathbb{R}^{d_{\rm model}\times d_k} , W^K_i &&\in \mathbb{R}^{d_{\rm model}\times d_k}, W^V_i \in \mathbb{R}^{d_{\rm model}\times d_v}, W^O_i \in \mathbb{R}^{hd_{v}\times d_{\rm model}}
\end{eqnarray}

 これは、オリジナルの単語分散表現の次元( d_{\rm model} )をh分割して、h個のScaled Dot-Product Attentionを実行し、それらを連結させてW^{O}との内積をとることで元の d_{\rm model} 次元に写像するものである。このh分割のAttentionを使用することをMulti-Head Attentionと呼んでおり、Q, K, Vが全て同じ入力の場合はMulti-Head Self-Attentionとなる。
 単語分散表現の次元をh分割することによって、一つ一つのAttentionの性能としては落ちるものの、分散表現次元の特定の部分空間のAttentionを、各Headが役割を分担させて実施させることが、性能向上につながるのだろうというのが、自分なりの理解である。

TransformerやMulti-Head Attentionについて、より詳しくは、

などの記事を参照。

実験

モデル

Keras(バックエンド: Tensorflow) を用いて、以下のようなSNLI用のモデルを作成した。コードはこちら

f:id:hiro-uchi:20190317213739p:plain
  1. InputLayer: 系列の入力
  2. Embedding: 系列を単語分散表現に変換
  3. MHA: Multi-Head Attention層
  4. PFFN: Position-wise Feed-Forward Networks層
  5. Flatten: 135x300次元
  6. Dense: 全結合層

である。

4 の Position-wise Feed-Forward Networks (PFFN)は、系列の各位置の分散表現 xに対して、


{\rm FFN}(x) = {\rm max}\left(0, xW_1+b_1\right)W_2 + b_2

という変換を施すものである。

実験設定と環境

上記のようなモデルを用いて、以下のような設定・環境で実験を行った。

  • 前提と仮説の2文は以下のように、"|"を用いて結合して入力とした
    • 前提: a biker race
    • 仮説: the car is yellow
    • 結合文: a biker races [padding] | the car is yellow [padding]
    • 結合文の[padding]の部分は、それぞれ(仮説文/前提文)の最大文長まで調整することを意味
  • Embeddingには、事前学習モデルとしてglove.6B.300d を用いて、事後学習を実施
  • gold_labelありのデータについてのみ学習と評価に用いた
  • GPU: GeForce GTX 1080Ti

結果と考察

Head数ごとの精度

 8epoch 回した、 Head数に応じた評価セットの精度は以下のとおり

Head数 1 2 6 10 20
train 精度 [%] 79.1 81.0 83.8 84.3 85.6
test 精度 [%] 74.3 75.4 78.3 77.8 78.4

 確かに Head数を増やすことで、testの精度が上昇する結果となった。 8epoch より先まで実行すると、train精度のみが上昇する傾向であった。 Head数をさらに増やすとどうなるかというところが気になる点であったが、Head数を 20にした段階で、実行の速度の低下がみられたのでそれ以上の実験は行わなかった。 実行速度の低下は、Headの分割を増やしたことで、GPU計算での並列性が落ちたためと考えられる。

考察 ~HeadごとのAttention重みの可視化~

 最後に、HeadごとのAttentionがどのような役割を果たしているかを確認するため、 Attention重みの

 
  {\rm softmax}(QW_i(KW_i)^T/ \sqrt{d_k})

を可視化してみる。

entailment と正答した場合

前提: a land rover is being driven across a river
仮説: a vehicle is crossing a river
の2文に対して、2つのHead で推論した場合のAttenstion 重みを可視化したのが以下の2つの図である。

f:id:hiro-uchi:20190317204813p:plain
Head 1 のAttention重み

f:id:hiro-uchi:20190317204834p:plain
Head 2 のAttention重み

色が濃い部分が、より注意が向いていることを表している。Head 1 の方は、主に "driven"という単語に注目し、Head 2 の方は"crossing"という単語に最も注意が向いている。他にも"river"などにも注意が向いており、結果的に2つの文が合致するという判定するのに必要な単語に注意が向いていることが見て取れる。この場合、モデルはentailmentと正答している。

contradiction を entailmentと誤答した場合

前提: a woman with a green headscarf blue shirt and a very big grin
仮説: the woman has been shot
の2文に対して、2つのHead で推論した場合のAttenstion 重みを可視化したのが以下の2つの図である。

f:id:hiro-uchi:20190317210124p:plain
Head 1 のAttention重み

f:id:hiro-uchi:20190317210316p:plain
Head 2 のAttention重み

2つHeadのAttention重みを見てみると、"woman"や"grin"等に注意が向いており、最終的にentailmentという推論が得られている。 contradictionを導くのに重要と思われる"shot"等には注意が向いていないが、Headを増やすとどうなるか、というのがさらなる考察ポイントだろう。

まとめ

 今回は、Transformerモデルで導入されているMulti-Head Self-Attention層を、Kerasで自作してSNLIタスクに応用した。その結果、Multi-Head Attentionの有用性が確認された。ただし、今回はMulti-Head Self-Attentionを利用するというところも目的に置いていたので、SNLIタスクのように、2文の比較による推論の場合は、通常のSource-Target Attentionを用いたほうが良さそうな気がするのでやってみたいと思う。

Ubuntu 16.04のCUDA driver インストール

cupy使おうとしたら、cuda driver 関連でエラーが出たので cuda driverを新しくした。自分用の備忘録。

背景

cupy 使用時に以下のようなエラー(抜粋)が出た

> from chainer import cuda
> xp = cuda.cupy
> xp.arange(25)

cupy/cuda/memory.pyx in cupy.cuda.memory.MemoryPool.malloc()
cupy/cuda/memory.pyx in cupy.cuda.memory.MemoryPool.malloc()
cupy/cuda/device.pyx in cupy.cuda.device.get_device_id()
cupy/cuda/runtime.pyx in cupy.cuda.runtime.getDevice()
cupy/cuda/runtime.pyx in cupy.cuda.runtime.check_status()
CUDARuntimeError: cudaErrorInsufficientDriver: CUDA driver version is insufficient for CUDA runtime version

CUDA driver version がおかしいと言っているので、ひとまず最新化を実施。

インストール手順

自分の環境は以下のとおり
- OS: Ubuntu 16.04 (Windows 10とのデュアルブート) - CUDA 10.0

  1. 既存のCUDA driverを削除
sudo apt-get purge nvidia-*
  1. インストールしたいパッケージの確認
> ubuntu-drivers devices

== /sys/devices/pci0000:00/0000:00:01.0/0000:01:00.0 ==
vendor   : NVIDIA Corporation
modalias : pci:v000010DEd00001B06sv000010DEsd00001B06bc03sc00i00
driver   : nvidia-390 - third-party free
driver   : nvidia-415 - third-party free recommended
driver   : nvidia-384 - third-party non-free
driver   : nvidia-410 - third-party non-free
driver   : nvidia-396 - third-party free
driver   : xserver-xorg-video-nouveau - distro free builtin

nvidia-415 をインストールすることに

  1. インストール
sudo apt install nvidia-415

インストール中に別のウィンドウ立ち上がり、セキュアブート機能OFFにするか聞いてくる(おそらくデュアルブートの場合)。

  • セキュアブート機能をオOFFにするに同意 → パスワード設定 → そのままインストール進む → インストール終了後reboot
  • reboot時の青色の画面で、MOKうんちゃらでセキュアブートをオフにできる(画像撮ればよかった)。上記で設定したパスワード使用

以上でエラーが解消された。

ハッカソン初参加の感想

 エンジニアとして働き始めて2年弱になり、Webまわりの知識もある程度ついてきたため、「自分の実力を試してみるため」と「仕事以外のつながりを広げるため」という意識のもと、"Geospatial Hackers Program 福井"*1に参加してみたので、感想を書いていく。自分としては、これがハッカソンイベント初参加*2だった。全参加者はおそらく50人ぐらい(学生さん多め?)だった。

イベント全体の流れ

 イベント全体の流れはおよそ以下の通り。ただし、書きやすさのため、実際の順番と若干入れ替えている。

1日目

セミナー

  • G空間技術の現状と産業事例の紹介
  • ハッカソン用に公開されたデータの説明

主に座学の時間。

イデアワーク & チームビルディング

 アイデアワークは大雑把に以下のような流れだった

  1. 同じ机にいた人たちと、G空間情報を利用したアイデアをたくさん出し合う。この段階ではとにかくアイデアを出していく段階
  2. そのアイデアの中から、良さそうなものを2つ選ぶ
  3. イデア2つを全体に発表して、そのアイデアをやりたいと思った人が集まってチームをつくる

自分は、元々組む予定だったメンバーの2人と同じものを選んだ。また、他にも3名メンバーが集まり、合計6人のメンバーに。

ハンズオン

 G空間情報を利用したアプリの開発をハンズオン形式で学ぼうという時間。オープンデータを利用したWeb地図アプリの開発を実施。ハンズオン用にgithub上で公開されているコードをフォークして、それをいじってみようという感じだった。githubが簡易的なwebサーバーを持っていて、そこにwebアプリを立てられるということを初めて知った。

 今回のハンズオンでは、地図表示用ライブラリとしてleaflet.js、オープンデータ利用のためにsparqlというrdf用クエリ言語を利用していた。ハッカソンの前に個人的に少しいじったりしてみたが、sparqlの書き方が非常に独特で慣れるのに若干時間かかりそうだなという印象。rdfのデータ構造も把握しきれていない。

 ハンズオンの時間では、メンバーの学生さんにアドバイスしながら自分でもちょこちょこいじるといった感じ。個人的にいじっていたのが生きたか。

ハッカソン本番に向けての事前準備

 2日目のハッカソン本番の時間に向けて、アイデアに対して、どのような形で作っていくかをチーム内で話し合う時間。30分ほど。結局この時間では明確には決まらず、2日目に持ち越しに。

2日目

ハッカソン本番

 いよいよハッカソン本番。10:00頃から開始して、17:00には完全終了、17:10から各チーム発表して投票、デモデイ*3チーム発表という流れだった。

プロダクト確定〜作成体制決め

 1日目の時点では、プロダクトを確定させられていなかったため、まずはそれを確定させることに。自分は、1日目の夜に使えそうな外部APIを試しておいて、それも使っていけるように準備しておいた。この時点でもいくつか案が出たが、それなりに意味がありそう&現在の技術で動かせる方向で落ち着いた。

 開発体制としては、自分がバックエンド側の開発、もう一人の方がフロントエンド側開発、アイデア出した方が発表資料作成&発表者といった感じだった*4。昨日いたうちのメンバーの1人は残念ながら来れなくなってしまったが、自分は学生さん*52人にもプログラミングに触れて欲しいと思い、python教えつつ協力してもらうことに。

本作業

 自分は、主にFlaskでサーバーを立てて、フロントエンドと連携するためのAPIを作成。そのAPIを叩くと、外部APIを実行して最終的な処理を実施するという部分を作った。合わせて、学生さんにやって欲しい作業を伝えて、自身で調べてもらいながら*6、プロダクトに必要なパーツを作ってもらうことに。

 途中で、フロントエンド側のほうが作業が多そうだと思ったので、フロントエンド側の作業も少し分担することになった。仕事のほうではフロントエンドの人間なので、役に立てたような気がする*7。とはいえ、仕事のフロントエンド開発とは違う方式の部分もあったりして、内心はヒヤヒヤした部分もあった。

それでも、当初の予定通り動かせるものは準備できて、スマホでデモもできるようになったので、かなり頑張ったのではないかと思う。プロダクトづくりに短時間でこれだけ集中したのは初めてかもしれない。

発表〜投票・デモデイチーム発表

 各チームのプロダクトの発表。どのチームも面白いアイデアで、本当に実現したら良さそうと思えるものが多かった。また、発表に力を入れているチームが多く、発表者の力量が高い・劇を採用している・デモがしっかりしている、など工夫がなされていた。個人的に感じたのは、どのチームも自信を持って発表しているということだった。もちろん、限られた時間で作ったものなので、プロダクトとしては不十分なのだが、「自分たちのプロダクトではこれができる!」とアピールしており、すぐ自信がなくなる自分は見習おうと思った。

 自分のチームからは、アイデア出しの方が発表。ただし、デモは自分のPC&スマホでしかできない状況だったので、それを貸して発表してもらった。その方の発表が非常に上手だった。また、デモも滞りなく動いたので一安心した。

 いよいよ投票&デモデイチームの発表となったが、なんと自分のチームがデモデイチームに選ばれることとなった。他のチームのプロダクトも良かっただけに、望外の結果であった。外部APIと連携したのを見せられたのが良かったのかな、と思っている。その後は、写真撮影などして終了。

その他

全体の感想

 初めてハッカソンに参加したが、今回のハッカソンは、どうやらガチガチのものというわけではなく、どちらかというと学び重視で初心者の方も歓迎、という雰囲気があった。初参加だったので不安があったが、その雰囲気は非常に安心した。ただし、今回の場合の運営側の視点に立ってみると、初心者の方をできるだけフォローしたいと思いつつも、すべてをフォローするのには限界がありそうだった。そういう場合は、チームメンバー間のフォローや協調が大切だが、メンバーも本番中に決定するので、ギャンブル的なところはあるな、と思った。それでも、初心者が一人で行っても安心して取り組めるイベントがあるのは非常に重要だと思う。逆に、初心者歓迎と書いていないハッカソンに行くときには、それなりに準備しておいたほうが良いかもしれない。

 ハッカソンの内容としては、自分の興味がある分野とあって楽しく取り組めた。メンバーとも、短い時間ながらうまく連携できたと思う。なにより、自分の役割のなかである程度の成果を出せたことが嬉しかった。興味のあるハッカソンがあれば、また参加してみてもいいかも。

ハッカソンに向けて事前に自分が準備したこと

ハッカソンに向けて事前に準備しておいたことで、役に立ったことを記しておく。これがなかったらここまでうまくいかなかったかも。

  • 事前資料はひととおり試しておいた
    特にハンズオンのときに役に立った。本番ではあまり使わなかったものの、勉強になることが多かった。
  • 最小限の労力でAPIを立てられるように準備 PythonのFlaskでAPIを立てられるように練習していた。本番でも実際に利用した。
  • コピペしてすぐ使えるような汎用的なコードを用意 とくにAjaxまわり、htmlの基本的な要素を準備しておいた。これも本番で結構利用。

*1:"G空間情報"と呼ばれる、地理的な空間情報を活用する人材の裾野を広げることがイベントの趣旨だった。自分が地理情報に興味があるというのも参加した理由の1つ

*2:初参加だったものの、チームメンバーを探していた人に初対面ながら連絡をとって、3人のメンバーとして参加した。自分以外の2人はハッカソン複数回参加したことがある。

*3:得票数上位2チームが後日東京で行われるデモデイに参加してプロダクトを発表できる。もちろん交通費つき

*4:実はこの3人が、ハッカソン前に連絡して集まっていたメンバー

*5:プログラムはほんの少し触れたことがあるといった感じだった

*6:自分で調べていけるのが大事だと思うので、調べ方も教えた

*7:ハッカソンでは、敢えていつもと違う領域を担当したが、これもハッカソンの面白さだと思う

KH3をちょっとやった感想

これまで、KH1・KH2と、KH BBSをやってきたので、KH3にも手を出し始めた。もちろん、このためにPS4も買った。前にやったBBSから十年近く、KH2からは13年ほど経っている(よく考えたら、KH2は高1のときにやってた)。

まだ始めてから5時間ほどしか経っていないが、微妙になにか違う。ストーリーがわからないのは別に気にならない。アクション性が自分に合わない。△ボタンで逐一出てくる謎のアクション攻撃で、なにやってるのかわからないまま勝ってしまうのだ。アクション的には過去のKH1やKH2のほうが駆け引きがあったような気がするが、思い出補正のせいなのか...。世界観的には好きなゲームなので、ムービーを楽しみにやっていくか。

蛇足だが、PS4買ったついでにダウンロードしてKH3の直前にやった Horizon Zero Dawn のクオリティが高すぎたのも原因か。

SpacePy利用時の HDF5 関連のエラーに対処する

SpacePyという、宇宙関連のpythonライブラリ(人工衛星のデータ取得や解析等)を利用時に、モジュールをimportしようとして、HDF5 関連のエラーが出た。

↓ エラーメッセージ抜粋

pWarning! ***HDF5 library version mismatched error***
The HDF5 header files used to compile this application do not match
the version used by the HDF5 library to which this application is linked.
Data corruption or segmentation faults may occur if the application continues.
This can happen when an application was compiled by one version of HDF5 but
linked with a different version of static or shared HDF5 library.
You should recompile the application or check your shared library related
settings such as 'LD_LIBRARY_PATH'.
You can, at your own risk, disable this warning by setting the environment
variable 'HDF5_DISABLE_VERSION_CHECK' to a value of '1'.
Setting it to 2 or higher will suppress the warning messages totally.
Headers are 1.10.1, library is 1.10.2
︙
Bye...
Abort trap: 6

どうやらh5py の バージョンが悪かったらしく、h5py をインストールし直したらうまくいった

pip uninstall h5py
pip install h5py

Kerasで自作レイヤーを保存&ロードするときにはget_configが必要

Kerasで自作レイヤーを作るときには、最低限build, call, compute_output_shapeの3つのメソッドを定義していればよいとある。 例えば、コンストラクタに、以下のような定義をしてあるPFNNという自作レイヤーを作ったときに、

def __init__(self, ff_dim, **kwargs):
    self.ff_dim = ff_dim
    super(PFNN, self).__init__(**kwargs)

その自作レイヤーを使ったモデルを保存&ロードしようとすると、

TypeError: __init__() missing 1 required positional argument: 'ff_dim'

のようなエラーが出てしまう。これは、ロード時にget_configというメソッドが必要なためで、自作レイヤーに

def get_config(self):
    config = {'ff_dim': self.ff_dim}
    base_config = super(PFNN, self).get_config()
    return dict(list(base_config.items()) + list(config.items()))

のように定義しておけばよい。

ブログ開始にあたって

平成もそろそろ終わるので、平成生まれの自分も日々を綴っていこうと思った。 基本的には、技術的な内容や自分の開発について、日常的に想ったことのメモのつもり、たまには宇宙関連についても書いてみよう。