项目文件夹

文件
wehub-resource-sync 593b94c120
pytest / Unit Tests (push) Has been cancelled
pytest / Integration (integration_tests_a) (push) Has been cancelled
pytest / Integration (integration_tests_b) (push) Has been cancelled
pytest / Integration (integration_tests_c) (push) Has been cancelled
pytest / Integration (integration_tests_d) (push) Has been cancelled
pytest / Integration (integration_tests_e) (push) Has been cancelled
pytest / Integration (integration_tests_f) (push) Has been cancelled
pytest / Integration (integration_tests_g) (push) Has been cancelled
pytest / Integration (integration_tests_h) (push) Has been cancelled
pytest / Integration (integration_tests_i) (push) Has been cancelled
pytest / Integration (integration_tests_j) (push) Has been cancelled
pytest / Distributed (distributed_a) (push) Has been cancelled
pytest / Distributed (distributed_b) (push) Has been cancelled
pytest / Distributed (distributed_c) (push) Has been cancelled
pytest / Distributed (distributed_d) (push) Has been cancelled
pytest / Distributed (distributed_e) (push) Has been cancelled
pytest / Distributed (distributed_f) (push) Has been cancelled
pytest / Minimal Install (push) Has been cancelled
pytest / Event File (push) Has been cancelled
pytest (slow) / py-slow (push) Has been cancelled
Publish JSON Schema / publish-schema (push) Has been cancelled
chore: import upstream snapshot with attribution
2026-07-13 12:49:20 +08:00

174 行
6.7 KiB
Python

# Copyright (c) 2023 Predibase, Inc., 2019 Uber Technologies, Inc.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
# ==============================================================================
import logging
import torch
from torch import nn
from ludwig.modules.attention_modules import FeedForwardAttentionReducer
from ludwig.utils.misc_utils import get_from_registry
from ludwig.utils.torch_utils import LudwigModule, sequence_length_3D
logger = logging.getLogger(__name__)
class AttentionPooling(nn.Module):
"""Learnable attention-weighted pooling over sequence positions.
Uses a learnable query vector that attends to all positions via scaled dot-product
attention. Better than mean/max pooling when different positions have different
importance, as the model learns which positions to attend to.
Unlike FeedForwardAttentionReducer (which uses a two-layer feedforward network to
compute attention scores), this module uses a single learnable query vector with
scaled dot-product attention, making it more parameter-efficient.
Input shape: [batch, seq_len, hidden_size]
Output shape: [batch, hidden_size]
"""
def __init__(self, input_size: int, **kwargs):
super().__init__()
self.query = nn.Parameter(torch.randn(1, 1, input_size))
self.scale = input_size**-0.5
def forward(self, x: torch.Tensor, mask: torch.Tensor | None = None) -> torch.Tensor:
# x: [batch, seq_len, hidden]
attn = (self.query * self.scale) @ x.transpose(-2, -1) # [batch, 1, seq_len]
if mask is not None:
attn = attn.masked_fill(~mask.unsqueeze(1).bool(), float("-inf"))
attn = torch.softmax(attn, dim=-1)
return (attn @ x).squeeze(1) # [batch, hidden]
class SequenceReducer(LudwigModule):
"""Reduces the sequence dimension of an input tensor according to the specified reduce_mode. Any additional
kwargs are passed on to the reduce mode's constructor. If using reduce_mode=="attention", the input_size kwarg
must also be specified.
A sequence is a tensor of 2 or more dimensions, where the shape is [batch size x sequence length x ...].
Args:
reduce_mode: The reduction mode, one of {"last", "sum", "mean", "max", "concat", "attention",
"attention_pooling", "none"}.
max_sequence_length: The maximum sequence length. Only used for computation of shapes - inputs passed
at runtime may have a smaller sequence length.
encoding_size: The size of each sequence element/embedding vector, or None if input is a sequence of
scalars.
"""
def __init__(
self, reduce_mode: str | None = None, max_sequence_length: int = 256, encoding_size: int | None = None, **kwargs
):
super().__init__()
# save as private variable for debugging
self._reduce_mode = reduce_mode
self._max_sequence_length = max_sequence_length
self._encoding_size = encoding_size
# If embedding size specified and mode is attention/attention_pooling, use embedding size as
# attention module input size unless the input_size kwarg is provided.
if reduce_mode in ("attention", "attention_pooling") and encoding_size and "input_size" not in kwargs:
kwargs["input_size"] = encoding_size
# use registry to find required reduction function
self._reduce_obj = get_from_registry(reduce_mode, reduce_mode_registry)(**kwargs)
def forward(self, inputs, mask=None):
"""Forward pass of reducer.
Args:
inputs: A tensor of 2 or more dimensions, where the shape is [batch size x sequence length x ...].
mask: A mask tensor of 2 dimensions [batch size x sequence length]. Not yet implemented.
Returns:
The input after applying the reduction operation to sequence dimension.
"""
return self._reduce_obj(inputs, mask=mask)
@property
def input_shape(self) -> torch.Size:
"""Returns size of the input tensor without the batch dimension."""
if self._encoding_size is None:
return torch.Size([self._max_sequence_length])
else:
return torch.Size([self._max_sequence_length, self._encoding_size])
@property
def output_shape(self) -> torch.Size:
"""Returns size of the output tensor without the batch dimension."""
input_shape = self.input_shape
if self._reduce_mode in {None, "none", "None"}:
return input_shape
elif self._reduce_mode == "concat":
if len(input_shape) > 1:
return input_shape[:-2] + (input_shape[-1] * input_shape[-2],)
return input_shape
else:
return input_shape[1:] # Reduce sequence dimension.
class ReduceLast(torch.nn.Module):
def forward(self, inputs, mask=None):
# inputs: [batch_size, seq_size, hidden_size]
batch_size = inputs.shape[0]
# gather the correct outputs from the the RNN outputs (the outputs after sequence_length are all 0s)
# todo: review for generality
sequence_length = sequence_length_3D(inputs) - 1
sequence_length[sequence_length < 0] = 0
gathered = inputs[torch.arange(batch_size), sequence_length.type(torch.int64)]
return gathered
class ReduceSum(torch.nn.Module):
def forward(self, inputs, mask=None):
return torch.sum(inputs, dim=1)
class ReduceMean(torch.nn.Module):
def forward(self, inputs, mask=None):
return torch.mean(inputs, dim=1)
class ReduceMax(torch.nn.Module):
def forward(self, inputs, mask=None):
return torch.amax(inputs, dim=1)
class ReduceConcat(torch.nn.Module):
def forward(self, inputs, mask=None):
if inputs.dim() > 2:
return inputs.reshape(-1, inputs.shape[-1] * inputs.shape[-2])
return inputs
class ReduceNone(torch.nn.Module):
def forward(self, inputs, mask=None):
return inputs
reduce_mode_registry = {
"last": ReduceLast,
"sum": ReduceSum,
"mean": ReduceMean,
"avg": ReduceMean,
"max": ReduceMax,
"concat": ReduceConcat,
"attention": FeedForwardAttentionReducer,
"attention_pooling": AttentionPooling,
# TODO: Simplify this.
"none": ReduceNone,
"None": ReduceNone,
None: ReduceNone,
}