Foundations
Categorical and numeric molecular feature encoding
Free account with email, Google or GitHub. Your code runs in an isolated PyTorch sandbox.
Your task
Encode categorical fields with explicit unknown-value handling and combine them with transformed numeric fields. Return categorical indicators, combined features, and the supplied projection.
Function signature
categorical_feature_encoding(
categories: torch.Tensor,
choices: torch.Tensor,
continuous: torch.Tensor,
continuous_transform: torch.Tensor,
continuous_scale: torch.Tensor,
parameters: dict,
masks: dict,
unknown_policy: str = 'bucket',
) -> dict[str, torch.Tensor]Worked example
Known, unknown, and numerical features
For choices [6, 7, 8], category 6 uses the first channel, category 7 the second, and category 9 the extra unknown channel. In the second field, -1 is an explicitly listed category, so it is known. A missing field would instead have all-zero channels. Numerical values [12, 20, 14] with scale 0.01 become [0.12, 0.20, 0.14]; the identity projection preserves the assembled features.
import torch
categories = torch.tensor([[[6, -1], [9, 0], [7, 4]]], dtype=torch.int64)
choices = torch.tensor([[6, 7, 8], [-1, 0, 1]], dtype=torch.int64)
continuous = torch.tensor([[[12.0, 0.0], [20.0, 1.0], [14.0, 0.0]]], dtype=torch.float64, requires_grad=True)
continuous_transform = torch.tensor([0, 0], dtype=torch.int64)
continuous_scale = torch.tensor([0.01, 1.0], dtype=torch.float64, requires_grad=True)
parameters = {
'weight': torch.tensor([[1.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0], [0.0, 1.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0], [0.0, 0.0, 1.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0], [0.0, 0.0, 0.0, 1.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0], [0.0, 0.0, 0.0, 0.0, 1.0, 0.0, 0.0, 0.0, 0.0, 0.0], [0.0, 0.0, 0.0, 0.0, 0.0, 1.0, 0.0, 0.0, 0.0, 0.0], [0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 1.0, 0.0, 0.0, 0.0], [0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 1.0, 0.0, 0.0], [0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 1.0, 0.0], [0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 1.0]], dtype=torch.float64, requires_grad=True),
'bias': torch.full((10,), 0.0, dtype=torch.float64, requires_grad=True),
}
masks = {
'choice_mask': torch.tensor([[True, True, True], [True, True, True]], dtype=torch.bool),
'field_mask': torch.tensor([[[True, True], [True, True], [True, True]]], dtype=torch.bool),
'mask': torch.tensor([[True, True, True]], dtype=torch.bool),
}
unknown_policy = 'bucket'
result = categorical_feature_encoding(categories=categories, choices=choices, continuous=continuous, continuous_transform=continuous_transform, continuous_scale=continuous_scale, parameters=parameters, masks=masks, unknown_policy=unknown_policy)Expected return
{
'one_hot': torch.tensor([[[[1.0, 0.0, 0.0, 0.0], [1.0, 0.0, 0.0, 0.0]], [[0.0, 0.0, 0.0, 1.0], [0.0, 1.0, 0.0, 0.0]], [[0.0, 1.0, 0.0, 0.0], [0.0, 0.0, 0.0, 1.0]]]], dtype=torch.float64),
'features': torch.tensor([[[1.0, 0.0, 0.0, 0.0, 1.0, 0.0, 0.0, 0.0, 0.12, 0.0], [0.0, 0.0, 0.0, 1.0, 0.0, 1.0, 0.0, 0.0, 0.2, 1.0], [0.0, 1.0, 0.0, 0.0, 0.0, 0.0, 0.0, 1.0, 0.14, 0.0]]], dtype=torch.float64),
'embedding': torch.tensor([[[1.0, 0.0, 0.0, 0.0, 1.0, 0.0, 0.0, 0.0, 0.12, 0.0], [0.0, 0.0, 0.0, 1.0, 0.0, 1.0, 0.0, 0.0, 0.2, 1.0], [0.0, 1.0, 0.0, 0.0, 0.0, 0.0, 0.0, 1.0, 0.14, 0.0]]], dtype=torch.float64),
}Parameters
| Parameter | Shape and meaning |
|---|---|
categoriestorch.Tensor | [\ldots, F]Prepared raw category value for each field. |
choicestorch.Tensor | [F, K]Allowed category values in each field’s vocabulary order. |
continuoustorch.Tensor | [\ldots, D]float [\ldots,D], prepared raw numeric descriptors, D\ge 0. |
continuous_transformtorch.Tensor | [D]int64 [D], code 0=\mathrm{identity} or 1=\operatorname{asinh} per numeric field. |
continuous_scaletorch.Tensor | [D]float [D], per-field multiplier after identity/asinh. |
parametersdict | Supplied model parameters, keyed as shown below. |
masksdict | Supplied masks. Each field below defines what True includes or permits. |
unknown_policystrDefault 'bucket' | "bucket" assigns an unmatched valid category to its field’s extra unknown bucket; "zero" leaves that field’s categorical block all zero. |
Return
dict[str, torch.Tensor]
Return exactly the named fields applicable to this operation.
Required behavior
- Map multiple integer descriptor fields to explicit one-hots, concatenate numeric descriptors, and project the assembled feature.
categories[\ldots,F]isint64.choices[F,K]lists allowed raw values (including legitimate negative values such as formal charge);choice_mask[F,K]marks real vocabulary slots. - Valid
choiceswithin each field must be unique; unused slots remain zero feature columns.F,K\ge 1.field_mask[\ldots,F]marks present fields;mask[\ldots]marks valid observations. A missing field has ALL zero category channels, distinct from an unknown but present value. - For each valid/present field, compare its raw value with all valid
choices. Matched slotksets category channelkto1. If no match,unknown_policy='bucket'(default) sets the last channelKto1; 'zero' leaves every channel zero. Returnone_hot[\ldots,F,K+1]in continuous.dtype. - Each numeric descriptor
continuous[\ldots,D](Dmay be0) has a suppliedcontinuous_transform[D]: code0keepsx; code1uses\operatorname{asinh}(x). Multiply this transformed value bycontinuous_scale[D]. - This covers Chemprop's mass factor
0.01and AF3's asinh reference charge (ESMFold2 uses raw charge), without importing a ready-made asinh. Flatten one_hot's last two axes in field-major order and concatenate the transformed/scaled numeric descriptors, calling thisfeatures[\ldots,F\cdot (K+1)+D]. - Then
\mathrm{embedding}=\mathrm{features}\mathbin{@}\mathrm{weight}+\mathrm{bias}, withweight[F\cdot (K+1)+D,O],bias[O],O\ge 1. Masked rows have zerofeaturesand zeroembeddingEVEN with nonzerobias. Maskedcontinuouspayloads may beNaNand must be sanitized before arithmetic. - Unknown unknown_policy values raise ValueError. Floating tensors use one float32/float64 dtype. Observed descriptors and parameters are finite, continuous_transform codes are 0 or 1, and numeric magnitudes are below 10^10. Scaling and projection intermediates must remain finite in that dtype.
- Preserve the smooth derivative
1/\sqrt{1+x^2}, including derivative 1 at x=0. The supplied fields and vocabularies are ready to use; no chemical parsing, bond discovery, stereochemistry or atom-name lookup is part of this operation. - For real x,
asinh(x)=\log(x+\sqrt{1+x^2}). A stable value formula usessign(x)\log(|x|+\sqrt{1+x^2}); handle zero so its derivative remains 1.
Why this matters
A field’s vocabulary defines its meaning: an unknown observed value needs an explicit category, while a missing field contributes no observation.
Starter code
You write the body of this function in the BioTorch workspace. Your code runs in an isolated PyTorch sandbox; Run checks the public examples and Submit grades the full test suite, including gradients.
import torch
def categorical_feature_encoding(
categories: torch.Tensor, # (..., F)
choices: torch.Tensor, # (F, K)
continuous: torch.Tensor, # (..., D)
continuous_transform: torch.Tensor, # (D,)
continuous_scale: torch.Tensor, # (D,)
parameters: dict,
masks: dict,
unknown_policy: str = 'bucket',
) -> dict[str, torch.Tensor]:
"""Return dict[str, torch.Tensor]:
one_hot: (..., F, K+1).
features: (..., F* (K+1)+D).
embedding: (..., O)."""
# Implement the operation. Inputs and test setup are supplied by BioTorch.
raise NotImplementedError("Implement categorical_feature_encoding")
Ready to implement it?
3 progressive hints, a solution walkthrough, and grading that checks values, shapes and gradients are waiting in the workspace.
Free account with email, Google or GitHub. Your code runs in an isolated PyTorch sandbox.
Sources and further reading
A primitive exercise, not an end-to-end reproduction of the named models. No affiliation or endorsement is implied.