TabNetを頑張って調べて見たりする遊び(2/2)
実装を少し端折る
前回に引き続き、今回は実装視点でみてみる
Kaggleにいい感じにシンプルな実装をみつけたので、それを参考にします。
https://www.kaggle.com/marcusgawronsky/tabnet-in-tensorflow-2-0
後述のとおり、若干掛けている部分があるので、そこだけピックアップします
その前に重要な機能を前回よりも細かく列挙

FeatureTransformer

- Skip構造
- ResNetなどに代表されるスキップ構造を導入
- 最後(square(0.5))は結果の変動を抑止(学習安定化)するための安全策らしい。理由は不明
- ShareとDecisionステップ
- 構造的な違い
- ShareStep:使用するFCは全て共通
- Decision Step:使用するFCは全て異なる
- スキップ可能
- 参考サイトではShareStepとDecisionは1つのみ、Googleの実装も1,2つずつくらいで決まっていない疑惑。なので可変変数にしてみた
- 構造的な違い
- GLU
- Gated Linier Unit;線形ゲート付きユニット
- 中身は非常に単純で下記式はA=Bで、入力をシグモイドに変換し、元の入力と積をとる
- これもアテンション機構+決定木の特徴量選定の一つだと推測
- 結局入力信号の篩を掛けているだけだと思います。でもこのブログで取り上げられている通り、かますことで性能が上がるらしいです。

AttentiveTransformer

- Sparsemax
- softmaxよりもとんがった分類を行うらしく、論文ではマルチ分類に有効らしい
- ablation testではRELUよりもよかったぞ
- 決定木はいわゆる「ぶった切っ」て「結果を2分」するものなので、より鋭い活性化が必要だったと推測
- Prior
- 各ステップごとに係数を決定される。[過去のPrior*(gamma-Attentionからのスカラー値)]として流用される
- これがBoostingの前回の反省を生かす仕組みの実装なのかは不明
- これに関する実験記述なし
- このgammaは全ステップ共通。論文中ではスパース性への対応力が変化すると記載あり。
- これがBoostingの前回の反省を生かす仕組みの実装なのかは不明
- 各ステップごとに係数を決定される。[過去のPrior*(gamma-Attentionからのスカラー値)]として流用される

Split
- Relu行きor Attention行きかで若干ことなる
- Relu行き:次元数n_d
- Attention行き:次元数n_a
- →つまり今更だけれど、FeatureTransの段階でn_d+n_aに変換する必要が実はある。最初の層は無理矢理変えても問題なさそう
- →実はこの部分はGoogleAIの実装ではスキップされている。。。
上記をまとめて、(1)shre stepとdecision steps数可変化と(2)n_d, n_aの設定部分が不足しているので、その部分だけ切り抜いて実装した
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 |
class FeatureTransformer(tf.keras.layers.Layer): def __init__(self,
units:
Optional[int]
=
None, n_total:
int
=
4, n_shared:
int
=
2, virtual_batch_size:
Optional[int] =
128, momentum:
Optional[float]=0.02, skip=
False): super(FeatureTransformer).__init__() self.units
=
units self.virtual_batch_size
=
virtual_batch_size self.momentum
=
momentum self.n_a
=
n_a self.n_d
=
n_d self.n_share
=
n_share self.n_decision
=
n_decision self.share_fcs=List[tf.keras.layers.Layer]
=
[]
#
Share phase dence self.skip
=
skip self.blocks
=
[] #
n_a
->
to
Attention #
n_d
->
to
final Dicision def build(self,
input_shape:
tf.TensorShape): if
self.units is
None: self.units
=
input_shape[-1] for
n
in
range(n_share+n_decision): #
shared blocks if
self.share_fcs and
n
<
len(fcs): self.blocks.append( GLU(n_a=n_a,
n_d=n_d,fc=self.share_fcs[n] virtual_batch_size=self.virtual_batch_size,
momentum=self.momentum) #
build new
blocks else: self.blocks.append( GLU(n_a=n_a,
n_d=n_d virtual_batch_size=self.virtual_batch_size,
momentum=self.momentum) def call(self,
inputs:
Union[tf.Tensor,
np.ndarray],
training:
Optional[bool]
=
None): initial
=
self.initial(inputs,
training=training) # 直前入力ある場合(true)は加算 # 何も無い場合(false)は初期化状態のママ if
self.skip
==
True: initial
+=
inputs residual
=
self.residual(initial,
training=training)
return
(initial
+
residual)
*
np.sqrt(0.5) def call(self, x:
tf.Tensor,
training:
Optional[bool]
=
None): x
=
self.blocks[0](x,
training=training) for
n
in
range(1,
self.n_total): x
=
x
*
tf.sqrt(0.5)
+
self.blocks[n](x,
training=training) return
x |
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 |
class
GLU(tf.keras.layers.Layer): """ GLU: Ghost batch normalizationを適用 GBNは汎化誤差の抑制する https://www.tensorflow.org/api_docs/python/tf/keras/layers/BatchNormalization """ def
__init__(self, n_a:
Optional[int]=None, n_d:
Optional[int]=None, virtual_batch_size:
Optional[int]=128, momentum:
Optional[float]=0.01, fc:
tf.keras.layers.Layer
=
None, apply_glu:
bool
=
True, ): super(GLU,
self).__init__() self.units
=
2*(n_a
+
n_d)
self.virtual_batch_size
=
virtual_batch_size self.momentum
=
momentum def
build(self,
input_shape:
tf.TensorShape): if
self.units is
None: self.units
=
input_shape[-1] self.fc_out
=
tf.keras.layers.Dense(self.units,
use_bias=False)
if
fc is
None
else
fc self.bn_out
=
tf.keras.layers.BatchNormalization( virtual_batch_size=self.virtual_batch_size, momentum=self.momentum) def
call(self, inputs:
Union[tf.Tensor,
np.ndarray],
training:
Optional[bool]=None): # Pass GBN output=self.bn_out(self.fc_out(inputs),
training=training) if
self.apply_glu: # ゲート付き線形ユニット return
output[:,:(n_a
+
n_d)]
*
tf.keras.activations.sigmoid(output[:,(n_a
+
n_d):]) else # そのまま通過 return
output[:,:(n_a
+
n_d)] |
もちろん、stepのClassをつくるけれども、その際にもコレらの変数は何らかの方法で受け渡すので
その実装は必要。
事前学習機構

今回のおもしろい部分。FeatureTranとAttentionをコアとするEncoderは、単純に入力と正解与えて学習することも可能だが、AutoEncoderの仕組みを構築して事前学習することで更に精度向上するとか、、、
Unsupervised pre-traininng中の表中「?」に対して、下段で学習機が当てに行っているように見えるけど、
仕組みとしては下記だと推測する
- ?の部分は各ステップのマスキングを意味する。よって各ステップ内で精錬される。
- 結局各ステップでマスキングが行われるため、毎回ことなる入力と出力になる
- 学習時もエントロピでの評価
Decoderの仕組みも、Encoderと同一。逆につないだだけ。

一旦ここでおしまい
実装はこちらです。前述のとおり,kagleのコードを流用しているのでdata取得にはkaggleコマンドを通過させるようにする必要ありです
所感としては、なんだろう。内容としてはいつもとは違うモヤモヤが残る気がします
DNNだと結構コジツケ感があっても、まぁいいやというふうになるが
GBDTと似てます!っていうと決定木やBoostingの構造があるように見えるが、その周辺解説が少ないように見える。まぁシッカリ読んでいないからかもしれない
次回もゆるく何かを纏める
補足:すでにまとまっているニホンゴ資料
- https://zenn.dev/sinchir0/articles/9228eccebfbf579bfdf4
- https://www.guruguru.science/competitions/16/discussions/70f25f95-4dcc-4733-9f9e-f7bc6472d7c0/
- Pytorchの実装の解説だが普通に参考になる。
- Pytorchのライブラリが最も論文に則している?
- 実装はこっちのほうが自然に感じられる
- https://github.com/google-research/google-research/blob/master/tabnet/tabnet_model.py
- GoogleAIによる実装。
- Nottebookで作りました感がすごい。結構手を抜いているように見える。









