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で作りました感がすごい。結構手を抜いているように見える。