OneScience's picture
Upload folder using huggingface_hub
4516781 verified
Raw
History Blame Contribute Delete
10.6 kB
# Copyright 2026 Google LLC.
#
# 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.
"""AlphaGenome model."""
from collections.abc import Mapping
from flax_model.alphagenome._sdk import typing
from flax_model.alphagenome._sdk.models import dna_model
from flax_model.alphagenome.model import attention
from flax_model.alphagenome.model import convolutions
from flax_model.alphagenome.model import embeddings as embeddings_module
from flax_model.alphagenome.model import heads as heads_module
from flax_model.alphagenome.model import layers
from flax_model.alphagenome.model import schemas
from flax_model.alphagenome.model import splicing
from flax_model.alphagenome.model.metadata import metadata as metadata_lib
import haiku as hk
import jax
from jaxtyping import Array, Float, Int, PyTree, Shaped # pylint: disable=g-importing-member, g-multiple-import
DEFAULT_NUM_SPLICE_SITES = 512
DEFAULT_SPLICE_SITE_THRESHOLD = 0.1
class SequenceEncoder(hk.Module):
"""Encodes a sequence of DNA into embeddings."""
@typing.jaxtyped
def __call__(
self, dna_sequence: Float[Array, 'B S 4']
) -> tuple[Float[Array, 'B S//128 D'], dict[str, Array]]:
intermediates = {}
x = convolutions.DnaEmbedder()(dna_sequence)
intermediates['bin_size_1'] = x
x = layers.pool(x)
for block_idx, bin_size in enumerate([2, 4, 8, 16, 32, 64]):
x = convolutions.DownResBlock(f'downres_block_{block_idx}')(x)
intermediates[f'bin_size_{bin_size}'] = x
x = layers.pool(x)
return x, intermediates
class SequenceDecoder(hk.Module):
"""Decodes a sequence of embeddings."""
@typing.jaxtyped
def __call__(
self, x: Float[Array, 'B S D'], intermediates: dict[str, Array]
) -> Float[Array, 'B S_final D_final']:
for bin_size in [64, 32, 16, 8, 4, 2, 1]:
x = convolutions.UpResBlock()(x, intermediates[f'bin_size_{bin_size}'])
return x
class TransformerTower(hk.Module):
"""Transformer tower with interleaved pairwise updates."""
@typing.jaxtyped
def __call__(
self, x: Float[Array, 'B S C']
) -> tuple[Float[Array, 'B S C'], Float[Array, 'B S//16 S//16 F'] | None]:
pair_x = None
for i in range(9):
if i % 2 == 0:
pair_x = attention.PairUpdateBlock()(x, pair_x)
mha_bias = attention.AttentionBiasBlock()(pair_x)
x += attention.MHABlock()(x, mha_bias)
x += attention.MLPBlock()(x)
return x, pair_x
class AlphaGenome(hk.Module):
"""Main AlphaGenome model.
The model architecture consists of a sequence encoder, a transformer tower,
and a sequence decoder. The output of the decoder is used to generate
embeddings at 1bp resolution, while the output of the transformer tower
is used to generate embeddings at 128bp resolution and pair embeddings.
These embeddings are then passed to various heads to make predictions.
"""
def __init__(
self,
output_metadata: Mapping[
dna_model.Organism, metadata_lib.AlphaGenomeOutputMetadata
],
*,
num_splice_sites: int = DEFAULT_NUM_SPLICE_SITES,
splice_site_threshold: float = DEFAULT_SPLICE_SITE_THRESHOLD,
freeze_trunk_embeddings: bool = False,
num_organisms: int = 2,
name: str | None = None,
):
"""Initializes the AlphaGenome model.
Args:
output_metadata: Metadata for the output tracks for each organism.
num_splice_sites: The maximum number of splice sites that are extracted
from the splice site classification predictions.
splice_site_threshold: The threshold to use for splice site prediction.
freeze_trunk_embeddings: Whether to stop the gradient to the embeddings.
This is useful for training only the heads in fine-tuning.
num_organisms: The number of organisms. This is used to initialize the
organism embedding layer. Default is 2, for human and mouse. Leave at 2
to load pre-trained weights.
name: The name of the module.
"""
super().__init__(name=name or 'alphagenome')
self._output_metadata = output_metadata
self._num_splice_sites = num_splice_sites
self._splice_site_threshold = splice_site_threshold
self._freeze_trunk_embeddings = freeze_trunk_embeddings
self._num_organisms = num_organisms
self._heads: dict[heads_module.HeadName, heads_module.Head] = {}
for head in heads_module.HeadName:
output_type = heads_module.get_head_config(head).output_type
organisms_with_metadata = [
organism
for organism, metadata in output_metadata.items()
if metadata.get(output_type) is not None
]
if not organisms_with_metadata:
# None of the organisms have metadata for this output type. Skip.
continue
missing_organisms = set(self._output_metadata.keys()) - set(
organisms_with_metadata
)
if missing_organisms:
raise ValueError(
f'No metadata found for output type "{output_type.name}" for the'
f' following organisms: {missing_organisms}. We expect the same set'
' of output types for all organisms. Use padding to account for'
' missing tracks.'
)
self._heads[head] = heads_module.create_head(
heads_module.get_head_config(head),
self._output_metadata,
num_organisms=num_organisms,
)
@hk.name_like('__call__')
def predict_junctions(
self,
trunk_embeddings: Float[Array, 'B S D'],
splice_site_positions: Int[Array, 'B 4 K'],
organism_index: Int[Array, 'B'],
) -> PyTree[Float[Array, 'B ...'] | None]:
"""Predicts splice site junctions from embeddings and splice site positions.
Args:
trunk_embeddings: The trunk embeddings to use for predictions.
splice_site_positions: The splice site positions. Format: [batch, 4,
num_splice_sites] with order: [donor_pos_idx, accept_pos_idx,
donor_neg_idx, accept_neg_idx]
organism_index: The organism index.
Returns:
The predictions for splice site junctions.
"""
junction_head = self._heads.get(heads_module.HeadName.SPLICE_SITES_JUNCTION)
if junction_head is None:
raise ValueError('Junction head is not supported by this model.')
with hk.name_scope('head'):
return junction_head(
embeddings_module.Embeddings(embeddings_1bp=trunk_embeddings),
organism_index,
splice_site_positions=splice_site_positions,
)
@typing.jaxtyped
def __call__(
self,
dna_sequence: Float[Array, 'B S 4'],
organism_index: Int[Array, 'B'],
) -> tuple[PyTree[Shaped[Array, 'B ...']], embeddings_module.Embeddings]:
"""Encodes a sequence of DNA and makes predictions for various heads.
Args:
dna_sequence: The sequence of DNA to encode.
organism_index: The organism index.
Returns:
A tuple of (predictions, embeddings), where predictions is a dictionary
of predictions for various heads.
"""
trunk, intermediates = SequenceEncoder()(dna_sequence)
if self._num_organisms >= 1:
organism_embedding_trunk = hk.Embed(self._num_organisms, trunk.shape[-1])(
organism_index
)
trunk += organism_embedding_trunk[:, None, :]
trunk, pair_activations = TransformerTower()(trunk)
x = SequenceDecoder()(trunk, intermediates)
embeddings_128bp = embeddings_module.OutputEmbedder(self._num_organisms)(
trunk, organism_index
)
embeddings_1bp = embeddings_module.OutputEmbedder(self._num_organisms)(
x, organism_index, embeddings_128bp
)
embeddings_pair = embeddings_module.OutputPair(self._num_organisms)(
pair_activations, organism_index
)
embeddings = embeddings_module.Embeddings(
embeddings_1bp=embeddings_1bp,
embeddings_128bp=embeddings_128bp,
embeddings_pair=embeddings_pair,
)
if self._freeze_trunk_embeddings:
embeddings = jax.lax.stop_gradient(embeddings)
predictions = {
'embeddings_1bp': embeddings_1bp,
}
with hk.name_scope('head'):
for head_name, head_fn in self._heads.items():
if head_name == heads_module.HeadName.SPLICE_SITES_JUNCTION:
# This head is handled separately (see below).
continue
predictions[head_name.value] = head_fn(
embeddings,
organism_index,
)
# Handle the splice junction head separately. It requires splice site
# positions as input, which are derived from the splice site
# classification predictions.
if (
junction_head := heads_module.HeadName.SPLICE_SITES_JUNCTION
) in self._heads:
if heads_module.HeadName.SPLICE_SITES_CLASSIFICATION not in self._heads:
raise ValueError(
'SPLICE_SITES_CLASSIFICATION head is required for junctions'
' predictions.'
)
splice_sites_probabilities = predictions[
heads_module.HeadName.SPLICE_SITES_CLASSIFICATION.value
]['predictions']
splice_site_positions = splicing.generate_splice_site_positions(
splice_sites_probabilities,
alt=None,
splice_sites=None,
k=self._num_splice_sites,
pad_to_length=self._num_splice_sites,
threshold=self._splice_site_threshold,
)
predictions[junction_head.value] = self.predict_junctions(
embeddings.embeddings_1bp, splice_site_positions, organism_index
)
return predictions, embeddings
@typing.jaxtyped
def loss(
self, batch: schemas.DataBatch
) -> tuple[
Float[Array, ''], PyTree[Float[Array, '']], PyTree[Shaped[Array, 'B ...']]
]:
"""Returns the loss for the model."""
predictions, _ = self(batch.dna_sequence, batch.get_organism_index())
total_loss, all_scalars = 0.0, {}
for head_name, head_fn in self._heads.items():
scalars = head_fn.loss(predictions[head_name.value], batch)
all_scalars.update(
{f'{head_name.value}_{k}': v for k, v in scalars.items()}
)
total_loss += scalars['loss']
return total_loss, all_scalars, predictions