1.概要
本モデルは、Convolutional Sequence to Sequence Learningのエンコーダ部を改良したものです。もともとは機械翻訳に使用されていたモデルを、質問応答におけるスロットフィルインと意図認識の統合モデリングに再利用しています。
主な改良点は以下の通りです:
1. 複数のサイズを持つ畳み込みカーネルを使用して多様な特徴抽出を行う。
2. 多頭注意機構を追加し、特徴抽出を強化。
3. プーリング層を導入。
手順:
1. 文のトークンとその位置情報を埋め込みベクトルに変換。
2. 異なるサイズの畳み込みカーネル(3種類)を適用し、出力特徴を連結。
3. 多頭注意層を通過させた特徴を取得。
4. 上記2と3の出力を連結。
5. プーリング層を通じて意図認識を実施。
6. スロットフィルインを直接実施。
元のモデルのエンコーダ部分の詳細は:https://www.cnblogs.com/little-horse/p/14462023.html
多頭注意の実装例:
</div></div></div></div><div><div><div><div>```
'''
畳み込み特徴と多頭注意特徴の統合処理
'''
class EnhancedConvAttention(nn.Module):
def __init__(self, vocab_size, intent_classes, slot_classes, hidden_dim, num_layers, kernel_sizes, dropout, pad_idx, num_heads, max_len=50):
super(EnhancedConvAttention, self).__init__()
for k in kernel_sizes:
assert k % 2 == 1, 'カーネルサイズは奇数でなければなりません'
self.pad_idx = pad_idx
self.scale_factor = torch.sqrt(torch.tensor([0.5])).to(device)
self.token_emb = nn.Embedding(vocab_size, hidden_dim)
self.position_emb = nn.Embedding(max_len, hidden_dim)
self.combine_proj = nn.Linear(hidden_dim * 2, hidden_dim)
# 複数のカーネルサイズの畳み込み層
self.conv_blocks = nn.ModuleList([
nn.Conv1d(hidden_dim, 2*hidden_dim, k, padding=(k-1)//2)
for k in kernel_sizes
])
self.conv_proj = nn.Linear(len(kernel_sizes)*hidden_dim, hidden_dim)
self.multihead_attn = MultiHeadSelfAttention(hidden_dim, num_heads, dropout)
self.dropout = nn.Dropout(dropout)
self.intent_classifier = nn.Linear(hidden_dim, intent_classes)
self.slot_classifier = nn.Linear(hidden_dim, slot_classes)
def create_mask(self, input_ids):
mask = (input_ids != self.pad_idx).unsqueeze(1).unsqueeze(2)
return mask
def forward(self, input_ids):
batch_size, seq_len = input_ids.size()
src_mask = self.create_mask(input_ids)
positions = torch.arange(seq_len).expand(batch_size, seq_len).to(device)
tok_emb = self.token_emb(input_ids)
pos_emb = self.position_emb(positions)
embedded = self.dropout(tok_emb + pos_emb)
conv_input = embedded.transpose(1, 2) # [batch, dim, seq]
conv_features = []
for conv in self.conv_blocks:
x = conv(self.dropout(conv_input))
x = F.glu(x, dim=1)
x = (x + conv_input) * self.scale_factor
conv_input = x
conv_features.append(x)
combined_conv = torch.cat(conv_features, dim=1)
conv_out = self.conv_proj(combined_conv.transpose(1, 2))
# 多頭注意処理
attn_out, _ = self.multihead_attn(embedded, embedded, embedded, src_mask)
# 特徴統合
combined = torch.cat([conv_out, attn_out], dim=2)
combined = self.combine_proj(combined)
final_output = (combined + embedded) * self.scale_factor
# 意図認識用の平均プーリング
intent_logits = self.intent_classifier(
self.dropout(F.max_pool1d(final_output.transpose(1,2), seq_len).squeeze())
)
# スロットフィルイン
slot_logits = self.slot_classifier(self.dropout(final_output))
return intent_logits, slot_logits
'''
多頭自己注意層の実装
'''
class MultiHeadSelfAttention(nn.Module):
def __init__(self, hidden_dim, num_heads, dropout):
super(MultiHeadSelfAttention, self).__init__()
assert hidden_dim % num_heads == 0
self.hidden_dim = hidden_dim
self.num_heads = num_heads
self.head_dim = hidden_dim // num_heads
self.q_linear = nn.Linear(hidden_dim, hidden_dim)
self.k_linear = nn.Linear(hidden_dim, hidden_dim)
self.v_linear = nn.Linear(hidden_dim, hidden_dim)
self.output_linear = nn.Linear(hidden_dim, hidden_dim)
self.dropout = nn.Dropout(dropout)
self.scale = torch.sqrt(torch.tensor([hidden_dim])).to(device)
def forward(self, query, key, value, mask=None):
batch_size = query.size(0)
q = self.q_linear(query)
k = self.k_linear(key)
v = self.v_linear(value)
q = q.view(batch_size, -1, self.num_heads, self.head_dim).permute(0, 2, 1, 3)
k = k.view(batch_size, -1, self.num_heads, self.head_dim).permute(0, 2, 1, 3)
v = v.view(batch_size, -1, self.num_heads, self.head_dim).permute(0, 2, 1, 3)
energy = torch.matmul(q, k.permute(0, 1, 3, 2)) / self.scale
if mask is not None:
energy = energy.masked_fill(mask == 0, -1e10)
attention = torch.softmax(energy, dim=-1)
attention = self.dropout(attention)
x = torch.matmul(attention, v)
x = x.permute(0, 2, 1, 3).contiguous()
x = x.view(batch_size, -1, self.hidden_dim)
return self.output_linear(x), attention