こんにちは.高山です.
今回の記事は,実践手話認識-モデル開発編第六回の補足になります.
第六回からは,Pydanticを用いた NNモデルの実装 に即してコードを実装しています.
Pydantic の BaseModel
を継承したクラスにモデルのハイパーパラメータをまとめることで,下記のようにコードを改善することができます.
- モデル,レイヤに冗長な引数が並ぶことを避けることができます.
- Pydantic のデータ検証機能によって,不正な値がモデルに渡されることを防ぐことができます.
- Pydantic のデータ変換機能によって,ハイパーパラメータの保存,読み込みが簡単に実装できます.
- 一貫したスタイルでモデル,レイヤが実装できます.
第六回の記事で実験に使用したコードでは,ローカル特徴抽出の実装と同時に Transformer encoder のリファクタリング (動作を保ったまま,コードの品質を改善することを指します) も行っています.
Transformer encoder リファクタリングは第六回の記事の主要点では無いので,本記事で紹介したいと思います.
今回解説するスクリプトはGitHub上に公開しています.
本記事の紹介箇所は第4節 "Rafactor Transformer ISLR model" です.
1. Pydantic モデルの設定
まず最初に,下記のコードで Pydantic モデルの設定を行います.
| class ConfiguredModel(BaseModel):
model_config = ConfigDict(extra="forbid")
|
上記の設定は,クラスで未定義の変数が入力された場合にエラーとする設定です.
(デフォルトでは無視する設定になっています)
ConfiguredModel
を継承したクラスにパラメータを実装することで,Pydantic の検証機能を備えたパラメータクラスが実装できます.
2. Positional encoding
では,Transformer を構成するレイヤを実装していきます.
次のコードで Positional encoding の設定パラメータを実装します.
| class PositionalEncodingSettings(ConfiguredModel):
dim_model: int = 64
dropout: float = 0.1
max_len: int = 5000
def build_layer(self):
return PositionalEncoding(self)
|
【コード解説】
- 引数
- dim_model: 入力特徴量の次元数
- dropout: Dropout層の欠落率
- max_len: 位置信号を生成する長さ (入力可能な最大長)
パラメータクラスには build_layer()
メソッドを実装して,自身のパラメータからレイヤを生成できるようにしています.
この処理によって,呼び出し側でレイヤクラスを読み込む必要が無くなります.
パラメータクラスを用いると,レイヤクラスは下記のように実装できます.
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 | class PositionalEncoding(nn.Module):
def __init__(self,
settings):
super().__init__()
assert isinstance(settings, PositionalEncodingSettings)
self.settings = settings
self.dim_model = settings.dim_model
# Compute the positional encodings once in log space.
pose = torch.zeros(settings.max_len, settings.dim_model,
dtype=torch.float32)
position = torch.arange(0, settings.max_len,
dtype=torch.float32).unsqueeze(1)
div_term = torch.exp(torch.arange(0, settings.dim_model, 2).float()
* -(math.log(10000.0) / settings.dim_model))
pose[:, 0::2] = torch.sin(position * div_term)
pose[:, 1::2] = torch.cos(position * div_term)
self.register_buffer("pose", pose)
self.dropout = nn.Dropout(p=settings.dropout)
def forward(self,
feature):
feature = feature + self.pose[None, :feature.shape[1], :]
feature = self.dropout(feature)
return feature
|
__init__()
の引数がパラメータクラスになっていることが分かります.
細かな処理については手話認識入門第九回 で説明していますので割愛させていただきます.
3. Multi-head attention
次のコードで Multi-head attention の設定パラメータを実装します.
| class MultiheadAttentionSettings(ConfiguredModel):
key_dim: int = 64
query_dim: int = 64
att_dim: int = 64
out_dim: int = 64
num_heads: int = 1
dropout: float = 0.1
add_bias: bool = True
def build_layer(self):
return MultiheadAttention(self)
|
【コード解説】
- 引数:
- key_dim: Key値の入力次元数.Self-attentionでは `query_dim` と同じ値にします.
Cross-attentionでは主要入力 (Decoder側の入力) の次元数を設定します.
- query_dim: Query値の入力次元数.Self-attentionでは `key_dim` と同じ値にします.
Cross-attentionでは補助入力 (Encoder側の出力) の次元数を設定します.
- att_dim: 内部処理の特徴量次元数.`num_heads` で割り切れる値なければなりません.
- out_dim: 出力次元数
- num_heads: 異なるアテンション重みを計算するためのヘッド数
- dropout: Dropout層の欠落率
- add_bias: Trueの場合,線形変換層にバイアス項を導入する
パラメータクラスを用いると,レイヤクラスは下記のように実装できます.
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
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92 | class MultiheadAttention(nn.Module):
def __init__(self,
settings):
super().__init__()
assert isinstance(settings, MultiheadAttentionSettings)
self.settings = settings
assert settings.att_dim % settings.num_heads == 0
self.head_dim = settings.att_dim // settings.num_heads
self.num_heads = settings.num_heads
self.scale = math.sqrt(self.head_dim)
self.w_key = nn.Linear(settings.key_dim, settings.att_dim,
bias=settings.add_bias)
self.w_value = nn.Linear(settings.key_dim, settings.att_dim,
bias=settings.add_bias)
self.w_query = nn.Linear(settings.query_dim, settings.att_dim,
bias=settings.add_bias)
self.w_out = nn.Linear(settings.att_dim, settings.out_dim, bias=settings.add_bias)
self.dropout_attn = nn.Dropout(p=settings.dropout)
self.neg_inf = None
self.qkv_same_dim = settings.key_dim == settings.query_dim
self.reset_parameters(settings.add_bias)
def reset_parameters(self, add_bias):
"""Initialize parameters with Xavier uniform distribution.
# NOTE: For this initialization, please refer
https://github.com/pytorch/fairseq/blob/master/fairseq/modules/multihead_attention.py # pylint: disable=line-too-long
"""
if self.qkv_same_dim:
nn.init.xavier_uniform_(self.w_key.weight, gain=1 / math.sqrt(2))
nn.init.xavier_uniform_(self.w_value.weight, gain=1 / math.sqrt(2))
nn.init.xavier_uniform_(self.w_query.weight, gain=1 / math.sqrt(2))
else:
nn.init.xavier_uniform_(self.w_key.weight)
nn.init.xavier_uniform_(self.w_value.weight)
nn.init.xavier_uniform_(self.w_query.weight)
nn.init.xavier_uniform_(self.w_out.weight)
if add_bias:
nn.init.constant_(self.w_key.bias, 0.)
nn.init.constant_(self.w_value.bias, 0.)
nn.init.constant_(self.w_query.bias, 0.)
nn.init.constant_(self.w_out.bias, 0.)
def forward(self,
key: torch.Tensor,
value: torch.Tensor,
query: torch.Tensor,
mask: torch.Tensor):
if self.neg_inf is None:
self.neg_inf = float(np.finfo(
torch.tensor(0, dtype=key.dtype).numpy().dtype).min)
bsize, klen = key.size()[: 2]
qlen = query.size(1)
# key: `[N, klen, kdim] -> [N, klen, adim] -> [N, klen, H, adim/H(=hdim)]`
# value: `[N, klen, vdim] -> [N, klen, adim] -> [N, klen, H, adim/H(=hdim)]`
# query: `[N, qlen, qdim] -> [N, qlen, adim] -> [N, qlen, H, adim/H(=hdim)]`
key = self.w_key(key).reshape([bsize, -1, self.num_heads, self.head_dim])
value = self.w_value(value).reshape([bsize, -1, self.num_heads, self.head_dim])
query = self.w_query(query).reshape([bsize, -1, self.num_heads, self.head_dim])
# qk_score: `[N, qlen, H, hdim] x [N, klen, H, hdim] -> [N, qlen, klen, H]`
qk_score = torch.einsum("bihd,bjhd->bijh", (query, key)) / self.scale
# Apply mask.
if mask is not None:
# `[N, qlen, klen] -> [N, qlen, klen, H]`
mask = mask.unsqueeze(3).repeat([1, 1, 1, self.num_heads])
mask_size = (bsize, qlen, klen, self.num_heads)
assert mask.size() == mask_size, f"{mask.size()}:{mask_size}"
# Negative infinity should be 0 in softmax.
qk_score = qk_score.masked_fill_(mask == 0, self.neg_inf)
# Compute attention weight.
attw = torch.softmax(qk_score, dim=2)
attw = self.dropout_attn(attw)
# cvec: `[N, qlen, klen, H] x [N, qlen, h, hdim] -> [N, qlen, H, hdim]
# -> [N, qlen, H * hdim]`
cvec = torch.einsum("bijh,bjhd->bihd", (attw, value))
cvec = cvec.reshape([bsize, -1, self.num_heads * self.head_dim])
cvec = self.w_out(cvec)
# attw: `[N, qlen, klen, H]` -> `[N, H, qlen, klen]`
attw = attw.permute(0, 3, 1, 2)
return cvec, attw
|
4. Position-wise feed-forward
次のコードで Position-wise feed-foward の設定パラメータを実装します.
| class PositionwiseFeedForwardSettings(ConfiguredModel):
dim_model: int = 64
dim_pffn: int = 256
dropout: float = 0.1
activation: str = Field(default="relu",
pattern=r"relu|gelu|swish|silu|mish|geluacc|tanhexp")
add_bias: bool = True
def build_layer(self):
return PositionwiseFeedForward(self)
|
【コード解説】
- 引数
- dim_model: 入力特徴量の次元数
- dim_pffn: 1層目の線形変換後の特徴量次元数
- dropout: Dropout層の欠落率
- activation: 活性化関数の種別を指定 [relu/gelu/swish/silu/mish/geluacc/tanhexp]
- add_bias: Trueの場合,線形変換層にバイアス項を導入する
activation
は入力値を実装済みの活性化関数名に制限したいので,Field
を使って型を定義しています.
pattern
に入力可能な文字列を正規表現で与えることで,値を制限できます.
パラメータクラスを用いると,レイヤクラスは下記のように実装できます.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21 | class PositionwiseFeedForward(nn.Module):
def __init__(self,
settings):
super().__init__()
assert isinstance(settings, PositionwiseFeedForwardSettings)
self.w_1 = nn.Linear(settings.dim_model, settings.dim_pffn,
bias=settings.add_bias)
self.w_2 = nn.Linear(settings.dim_pffn, settings.dim_model,
bias=settings.add_bias)
self.dropout = nn.Dropout(p=settings.dropout)
self.activation = select_reluwise_activation(settings.activation)
def forward(self, feature):
feature = self.w_1(feature)
feature = self.activation(feature)
feature = self.dropout(feature)
feature = self.w_2(feature)
return feature
|
5. Transformer encoder layer
次のコードで Transformer encoder layer の設定パラメータを実装します.
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 | class TransformerEncoderLayerSettings(ConfiguredModel):
dim_model: int = 64
dim_pffn: int = 256
activation: str = Field(default="relu",
pattern=r"relu|gelu|swish|silu|mish|geluacc|tanhexp")
norm_type_sattn: str = Field(default="layer", pattern=r"layer|batch")
norm_type_pffn: str = Field(default="layer", pattern=r"layer|batch")
norm_eps: float = 1e-5
norm_first: bool = True
dropout: float = 0.1
mhsa_settings: MultiheadAttentionSettings = Field(
default_factory=lambda: MultiheadAttentionSettings())
pffn_settings: PositionwiseFeedForwardSettings = Field(
default_factory=lambda: PositionwiseFeedForwardSettings())
def model_post_init(self, __context):
# Adjust mhsa_settings.
self.mhsa_settings.key_dim = self.dim_model
self.mhsa_settings.query_dim = self.dim_model
self.mhsa_settings.att_dim = self.dim_model
self.mhsa_settings.out_dim = self.dim_model
# Adjust pffn_settings.
self.pffn_settings.dim_model = self.dim_model
self.pffn_settings.dim_pffn = self.dim_pffn
self.pffn_settings.activation = self.activation
# Propagate.
self.mhsa_settings.model_post_init(__context)
self.pffn_settings.model_post_init(__context)
def build_layer(self):
if self.norm_first:
layer = PreNormTransformerEncoderLayer(self)
else:
layer = PostNormTransformerEncoderLayer(self)
return layer
|
【コード解説】
- 引数
- dim_model: 入力特徴量の次元数
- dim_pffn: PFFNの内部特徴次元数
- activation: 活性化関数の種別を指定 [relu/gelu/swish/silu/mish/geluacc/tanhexp]
- norm_type_sattn: MHSAの正規化層種別を指定
- norm_type_PFFN: PFFNの正規化層種別を指定
- norm_eps: LN層内で0除算を避けるための定数
- norm_first: Trueの場合,Pre-LN構成を用いる
- dropout: Dropout層の欠落率
- mhsa_settings: MHSAのパラメータクラス
- pffn_settings: PFFNのパラメータクラス
MHSA と PFFN の設定パラメータを,それぞれ mhsa_settings
と pffn_settings
として保持しています.
これら子レイヤのパラメータのうち,dim_model
は親レイヤと共通の値を設定する必要があります.
また,dim_pffn
や activation
はパラメータ探索の過程で頻繁に変更されます.
そこで親レイヤのパラメータにも同名の変数を用意し,model_post_init()
内で親から子へ値を受け渡して設定が連動するようにしています.
少し実装の手間が増えますが,この処理によってアプリケーション側ではいくつかのよく使う値を設定するだけで,他の値は自動的に連動して変化するような処理を行うことができます.
以前の実装では,Pre-LN 構成と標準構成の Transformer を一つのクラスに実装していました.
この実装だとコードが冗長になるのと,print()
などでモデルを表示した場合に違いが分からなかったので,実装を分けています.
そのため,build_layer()
内で norm_first
の値に応じてインスタンス化するレイヤを切り替えるようにしています.
では,レイヤクラスを実装していきます.
まず,Pre-LN 構成と標準構成の Transformer で共通して行う,マスク作成処理を下記の関数にまとめます.
| def create_encoder_mask(src_key_padding_mask,
causal_mask):
if src_key_padding_mask is not None:
san_mask = make_san_mask(src_key_padding_mask, causal_mask)
elif causal_mask is not None:
san_mask = causal_mask
else:
san_mask = None
return san_mask
|
パラメータクラスを用いると,Pre-LN 構成の Transformer は下記のように実装できます.
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
60
61
62
63 | class PreNormTransformerEncoderLayer(nn.Module):
"""Pre-normalization structure.
For the details, please refer
https://arxiv.org/pdf/2002.04745v1.pdf
"""
def __init__(self,
settings):
super().__init__()
assert isinstance(settings, TransformerEncoderLayerSettings)
assert settings.norm_first is True
self.settings = settings
#################################################
# MHSA.
#################################################
self.norm_sattn = create_norm(
settings.norm_type_sattn, settings.dim_model, settings.norm_eps,
settings.mhsa_settings.add_bias)
self.self_attn = settings.mhsa_settings.build_layer()
#################################################
# PFFN.
#################################################
self.norm_pffn = create_norm(settings.norm_type_pffn, settings.dim_model,
settings.norm_eps, settings.pffn_settings.add_bias)
self.pffn = settings.pffn_settings.build_layer()
self.dropout = nn.Dropout(p=settings.dropout)
# To store attention weights.
self.attw = None
def forward(self,
feature,
causal_mask=None,
src_key_padding_mask=None):
bsize, qlen = feature.shape[:2]
san_mask = create_encoder_mask(src_key_padding_mask, causal_mask)
#################################################
# MHSA
#################################################
# `[N, qlen, dim_model]`
residual = feature
feature = apply_norm(self.norm_sattn, feature)
feature, self.attw = self.self_attn(
key=feature,
value=feature,
query=feature,
mask=san_mask)
feature = self.dropout(feature) + residual
#################################################
# PFFN
#################################################
residual = feature
# `[N, qlen, dim_model]`
feature = apply_norm(self.norm_pffn, feature)
feature = self.pffn(feature)
feature = self.dropout(feature) + residual
return feature
|
MHSA と PFFN のインスタンス化処理は,各レイヤのパラメータクラスから build_layer()
を呼び出すことで行っています (20, 27行目).
パラメータクラス側にビルド処理を実装しておくことで,レイヤクラスの実装を簡略化することができます.
同様に,パラメータクラスを用いると標準構成の Transformer は下記のように実装できます.
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
60
61
62
63 | class PostNormTransformerEncoderLayer(nn.Module):
"""Post-normalization structure (Standard).
For the details, please refer
https://arxiv.org/pdf/2002.04745v1.pdf
"""
def __init__(self,
settings):
super().__init__()
assert isinstance(settings, TransformerEncoderLayerSettings)
assert settings.norm_first is False
self.settings = settings
#################################################
# MHSA.
#################################################
self.self_attn = settings.mhsa_settings.build_layer()
self.norm_sattn = create_norm(
settings.norm_type_sattn, settings.dim_model, settings.norm_eps,
settings.mhsa_settings.add_bias)
#################################################
# PFFN.
#################################################
self.pffn = settings.pffn_settings.build_layer()
self.norm_pffn = create_norm(settings.norm_type_pffn, settings.dim_model,
settings.norm_eps, settings.pffn_settings.add_bias)
self.dropout = nn.Dropout(p=settings.dropout)
# To store attention weights.
self.attw = None
def forward(self,
feature,
causal_mask=None,
src_key_padding_mask=None):
bsize, qlen = feature.shape[:2]
san_mask = create_encoder_mask(src_key_padding_mask, causal_mask)
#################################################
# MHSA
#################################################
# `[N, qlen, dim_model]`
residual = feature
feature, self.attw = self.self_attn(
key=feature,
value=feature,
query=feature,
mask=san_mask)
feature = self.dropout(feature) + residual
feature = apply_norm(self.norm_sattn, feature)
#################################################
# PFFN
#################################################
residual = feature
# `[N, qlen, dim_model]`
feature = self.pffn(feature)
feature = self.dropout(feature) + residual
feature = apply_norm(self.norm_pffn, feature)
return feature
|
6. Transformer encoder block
次のコードで Transformer encoder block の設定パラメータを実装します.
1
2
3
4
5
6
7
8
9
10
11
12 | class TransformerEncoderSettings(ConfiguredModel):
num_layers: int = 1
norm_type_tail: str = Field(default="layer", pattern=r"layer|batch")
norm_eps: float = 1e-5
add_bias: bool = True
add_tailnorm: bool = True
pe_settings: PositionalEncodingSettings = Field(
default_factory=lambda: PositionalEncodingSettings())
def build_layer(self, encoder_layer):
return TransformerEncoder(self, encoder_layer)
|
【コード解説】
- 引数
- num_layers: encoder層の数を指定
- norm_type_tail: ブロック末尾の正規化層種別を指定.[layer/batch]
- norm_eps: LN層内で0除算を避けるための定数
- add_bias: Trueの場合,末尾のLN層にバイアス項を適用する
- add_tailnorm: この変数がTrueで,かつ,Pre-LN 構成の Transformer の場合は,
ブロック末尾に LN層を追加
- pe_settings: PE層のパラメータクラス
build_layer()
には Encoder layer のインスタンスを渡して自身を作成するようにしています.
(今までの実装と同様の形態です)
パラメータクラスを用いると,レイヤクラスは下記のように実装できます.
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 | class TransformerEncoder(nn.Module):
def __init__(self,
settings,
encoder_layer):
super().__init__()
assert isinstance(settings, TransformerEncoderSettings)
dim_model = settings.pe_settings.dim_model
assert dim_model == encoder_layer.settings.dim_model
self.settings = settings
self.pos_encoder = settings.pe_settings.build_layer()
self.layers = nn.ModuleList([copy.deepcopy(encoder_layer) for _
in range(settings.num_layers)])
# Add LayerNorm at tail position.
# This is applied only for pre-normalization structure because
# post-normalization structure includes tail-normalization in encoder
# layers.
add_tailnorm0 = settings.add_tailnorm
add_tailnorm1 = not isinstance(encoder_layer, PostNormTransformerEncoderLayer)
if add_tailnorm0 and add_tailnorm1:
self.norm_tail = create_norm(settings.norm_type_tail, dim_model,
settings.norm_eps, settings.add_bias)
else:
self.norm_tail = Identity()
def forward(self,
feature,
causal_mask,
src_key_padding_mask):
feature = self.pos_encoder(feature)
for layer in self.layers:
feature = layer(feature,
causal_mask,
src_key_padding_mask)
feature = apply_norm(self.norm_tail, feature)
return feature
|
7. 認識モデル
次のコードで認識モデル全体の設定パラメータを実装します.
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 | class TransformerEnISLRSettings(ConfiguredModel):
in_channels: int = 64
inter_channels: int = 64
out_channels: int = 64
activation: str = Field(default="relu",
pattern=r"relu|gelu|swish|silu|mish|geluacc|tanhexp")
pooling_type: str = Field(default="none", pattern=r"none|average|max")
enlayer_settings: TransformerEncoderLayerSettings = Field(
default_factory=lambda: TransformerEncoderLayerSettings())
encoder_settings: TransformerEncoderSettings = Field(
default_factory=lambda: TransformerEncoderSettings())
head_settings: GPoolRecognitionHeadSettings = Field(
default_factory=lambda: GPoolRecognitionHeadSettings())
def model_post_init(self, __context):
# Adjust enlayer_settings.
self.enlayer_settings.dim_model = self.inter_channels
self.enlayer_settings.activation = self.activation
# Adjust head_settings.
self.head_settings.in_channels = self.inter_channels
self.head_settings.out_channels = self.out_channels
# Propagate.
self.enlayer_settings.model_post_init(__context)
self.encoder_settings.model_post_init(__context)
self.head_settings.model_post_init(__context)
def build_layer(self, fext_settings=None):
return TransformerEnISLR(self, fext_settings)
|
【コード解説】
- 引数
- in_channels: 入力特徴量の次元数
- inter_channels: 内部特徴量の次元数
- out_channels: 出力特徴量の次元数.単語応答値を出力したいので,全単語数と同じにします.
- activation: 活性化関数の種別を指定 [relu/gelu/swish/silu/mish/geluacc/tanhexp]
- pooling_type: 特徴抽出をPoolingで縮小するかを指定.[none/average/max]
- enlayer_settings: Encoder layer のパラメータクラス
- encoder_settiongs: Encoder block のパラメータクラス
- head_settings: 出力レイヤのパラメータクラス
基本的にはここまでに説明してきた処理と同じです.
第六回の記事で紹介したように,ここではローカル特徴抽出レイヤを切り替えられるようにしたいので,build_layer()
に fext_settings
を渡すように実装しています.
パラメータクラスを用いると,認識モデルは下記のように実装できます.
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 | class TransformerEnISLR(nn.Module):
def __init__(self,
settings,
fext_settings=None):
super().__init__()
assert isinstance(settings, TransformerEnISLRSettings)
self.settings = settings
self.fext_settings = fext_settings
# Feature extraction.
self.fext_module = create_fext_module(fext_settings)
# Transformer-Encoder.
enlayer = settings.enlayer_settings.build_layer()
self.tr_encoder = settings.encoder_settings.build_layer(enlayer)
self.head = settings.head_settings.build_layer()
def _apply_fext(self,
feature):
for layer in self.fext_module:
feature = layer(feature)
return feature
def forward(self,
feature,
feature_causal_mask=None,
feature_pad_mask=None):
# Feature extraction.
feature = self._apply_fext(feature)
# `[N, C, T] -> [N, T, C]`
feature = feature.permute([0, 2, 1])
# Adjust mask size.
if feature_pad_mask is not None:
if feature_pad_mask.shape[-1] != feature.shape[1]:
feature_pad_mask = F.interpolate(
feature_pad_mask.unsqueeze(1).float(),
feature.shape[1],
mode="nearest")
feature_pad_mask = feature_pad_mask.squeeze(1) > 0.5
if feature_causal_mask is not None:
feature_causal_mask = make_causal_mask(feature_pad_mask)
feature = self.tr_encoder(
feature=feature,
causal_mask=feature_causal_mask,
src_key_padding_mask=feature_pad_mask)
if torch.isnan(feature).any():
raise ValueError()
# `[N, T, C] -> [N, C, T]`
logit = self.head(feature.permute([0, 2, 1]), feature_pad_mask)
if torch.isnan(feature).any():
raise ValueError()
return logit
|
インスタンス化処理がかなりスッキリしました (^^).
今回は Pydantic を用いて Transformer ベースの孤立手話単語認識をリファクタリングしてみましたが,如何でしたでしょうか?
今回紹介したコードだけではピンと来ないかもしれませんが,細かくパラメータを変えていく実験用アプリケーションを組んだりすると便利さが実感できるかもしれません.
今回紹介した話が,同じようなことで悩んでいる方に何か参考になれば幸いです.