Preprint
Essay

This version is not peer-reviewed.

Anatomy of a Transformer: An Essay on the Code and Concepts for Biological Sequence Analysis

Submitted:

08 June 2025

Posted:

09 June 2025

You are already at the latest version

Abstract
The application of advanced artificial intelligence, particularly Transformer architectures, to biological sequence analysis holds immense promise for predictive and generative genomics. However, for many life scientists, the inner workings of these powerful models remain an intimidating “black box”, creating a significant barrier to their adoption and effective use. This work addresses this gap not through the presentation of novel research findings, but through a unique pedagogical format designed to build foundational intuition.We present an interpretive essay that serves as a guided tour of a minimal, yet functional, Transformer model designed for a simple, biologically-informed task. The essay deconstructs the core concepts of the architecture and training process in accessible, analogical terms. This narrative is critically supported by fully annotated Python scripts and their outputs, provided in the Appendices, which serve as a transparent and reproducible “ground truth” for the reader.The result is a complete conceptual toolkit that demystifies the key components of the process. We guide the reader to a foundational understanding of: (1) the Materials of a model (tokens, vectors, and embeddings); (2) the architectural Blueprint (the modular layers of a Transformer); and (3) the Physics of learning (the interplay of loss, optimizers, and metrics).Ultimately, this paper provides a clear and accessible entry point into the world of deep learning for sequential data. By transforming the ”black box” into a transparent system, we aim to empower biologists with the confidence and foundational knowledge required to critically evaluate, adapt, and apply these powerful tools to their own research challenges.
Keywords: 
;  ;  ;  ;  ;  ;  

1. Introduction

The ongoing challenge of tracking and anticipating the evolution of viral pathogens requires an ever-more sophisticated and integrated scientific arsenal. To stay ahead in this “viral chase”, we must seamlessly blend foundational biotechnology, large-scale data analysis, and the predictive power of artificial intelligence. In a recent review, we surveyed this multidisciplinary landscape, charting a course from the molecular tools that decode viral genomes to the powerful USHER platform that organizes this information into pandemic-scale phylogenetic trees [1]. That work concluded by highlighting the immense potential of advanced AI—specifically Transformer architectures—to move beyond tracking the past and begin forecasting the future evolutionary trajectories of viruses.
While the conceptual promise of these models is compelling, for many researchers in the life sciences their inner workings remain an intimidating “black box”. A significant gap persists between understanding that a tool is powerful and knowing how to begin using it. The primary purpose of this essay is to bridge that gap. We aim to demystify the Transformer [2] by providing a transparent, step-by-step guide to its architecture and operation, using a simple, biologically-informed case study.
To achieve this, we adopt a unique pedagogical format. The main body of this paper is an interpretive essay, designed to build intuition and provide conceptual clarity. This essay serves as a guided tour of the Appendices, which contain the complete, fully annotated Python scripts and their outputs. Our goal is not to present novel biological findings, but to deconstruct the tool itself. By the end of this journey, the reader will have a foundational understanding of how to build, train, and interpret a simple Transformer model, empowering them to explore its application to their own research questions.

2. The Sandbox: Creating Our Learning Environment

Before we can dissect the intricate machinery of a Transformer, we must first build a clean, controlled environment in which to operate it. Attempting to learn the fundamentals of a complex model on raw, noisy, real-world data would be akin to learning to fly in a storm. Instead, we begin in a “digital sandbox”—a simulated dataset designed with one purpose in mind: to provide the clearest possible stage for observing and understanding the model’s behavior. This approach allows us to isolate the core learning process from the complexities of data cleaning and formatting, ensuring that what we observe is a direct result of the model’s architecture, not a quirk of the input data.
The specific task we simulate is a simplified, yet biologically-informed, evolutionary event. We generate a set of “ancestor” DNA sequences and a corresponding set of “descendant” sequences. The descendants are created by applying a consistent rule to the ancestors: every instance of the base Adenine (‘A’) undergoes a transition to become Guanine (‘G’). This `A → G` substitution is not arbitrary; it represents a common type of point mutation and provides a simple, predictable pattern for our model to learn.
However, a simple one-to-one replacement would fail to capture a crucial aspect of real evolution, which occurs not as a single event but across a population. To introduce this concept with elegance, our simulation incorporates the standard IUPAC ambiguity code [3]. Instead of every ‘A’ invariably becoming a ‘G’, we introduce a probabilistic outcome. In our descendant sequences, an ancestral ‘A’ has a chance to become either the fully transitioned ‘G’ or the IUPAC code ‘R’, which represents a polymorphic state (a site where both the purines ‘A’ and ‘G’ are present in the population). This ‘R’ is not merely a placeholder; it is an “evolutionary snapshot”, transforming our dataset from a simple substitution puzzle into a richer problem where the model must learn to handle ambiguity and recognize a process that is still in progress.
The complete, fully annotated Python script used to generate this foundational dataset is provided in Appendix A.1 (`simulate_data_v2.py`). We encourage the reader to examine this script, as its parameters—such as the number of samples (`NUM_SAMPLES`) and the probability of polymorphism (`POLYMORPHISM_PROB`)—are clearly defined at the top, inviting experimentation. With this clean and reproducible dataset in hand, we are now ready to construct the model that will learn its underlying rules.

3. From Letters to Language: The Core Concepts of AI Models

Before we can assemble our Transformer, it is useful to first understand the fundamental materials from which it is built. The terminology of artificial intelligence can seem foreign, but the core concepts are often intuitive. This section will serve as a brief glossary, defining the essential ideas of tokens, vectors, and embeddings, and clarifying what we mean by “deep learning”.

3.1. The Language of a Model: Tokens, Vectors, and Embeddings

A computer does not understand letters like ‘A’, ‘C’, ‘G’, or ‘T’. Its native language is mathematics. Therefore, our first task is always translation.
  • A Token is a single, discrete unit of our input [4]. For our purposes, each DNA base (‘A’, ‘C’, etc.) is a token. In human language, a token might be a word or a punctuation mark. We first assign a simple numerical ID to each token (e.g., ‘A’ becomes 0, ‘C’ becomes 1).
  • A Vector is simply a list of numbers. While a single number (like ‘0’ for ‘A’) is a start, it is not very descriptive. A vector allows us to represent our token with a much richer “definition”. For example, we might represent ‘A’ not just as ‘0’, but as a list of 32 numbers, like `[0.12, -0.45, 0.89, ...]`. This list is a vector.
  • An Embedding is the process of creating these rich vector definitions. It is the crucial step where the model learns the best vector representation for each token based on how it is used in the data. A good embedding will learn that ‘A’ and ‘G’ (both purines) are more similar to each other than ‘A’ and ‘T’ are, and this similarity will be reflected in their vectors. In essence, an embedding is a learned, multi-dimensional dictionary that translates simple tokens into meaningful mathematical objects.

3.2. What is “Deep Learning”? Building with Layers

The term “deep learning” simply refers to a type of machine learning that uses neural networks with multiple layers stacked on top of one another. The “deep” refers to the depth of this stack of layers.
It is most helpful to think of these layers as Lego blocks. Each block performs a specific, well-defined task (like the embedding we just described, or the attention mechanism we will soon discuss). A model architecture is simply the specific way we choose to stack these blocks. The power of deep learning comes from the fact that we can combine these simple, modular blocks to create incredibly complex and powerful systems capable of learning sophisticated patterns—much like simple Lego bricks can be assembled into an elaborate castle. Our Transformer is one such elegant assembly of these blocks.

3.3. A Note on How Transformers Differ

While we will provide a more detailed comparison later, it is useful to understand one key distinction upfront. Many traditional computational methods and even other machine learning models are excellent at finding patterns based on the presence or frequency of certain features. A Transformer’s defining characteristic, however, is its exceptional ability to understand order, context, and long-range relationships. The self-attention mechanism, which we will explore next, is designed specifically to solve this problem, making Transformers uniquely suited for sequential data like language and biological genomes where order is not just important, but everything.

4. The Blueprint: Architecting the Model’s “Brain”

Having established our learning environment, we now turn to the task of building the machine that will learn from it: the Transformer model. The code that defines this architecture, found in Appendix A.2 (‘transformer_model.py’), may at first appear complex. However, its true elegance lies in its modularity. Much like a complex biological system is built from specialized cells, our Transformer is constructed from distinct, reusable components, each with a specific and understandable purpose. In this section, we will act as anatomists, dissecting this “digital brain” to understand its core components.
Our model is built using the Keras Application Programming Interface (API), a high-level framework that allows us to define complex architectures in a readable, structured manner [5]. We will explore two primary building blocks: the specialized input layer that processes the raw sequence, and the core Transformer block where the “thinking” happens.

4.1. The Input System: Understanding What and Where

Before a model can find patterns in a sequence, it must first be able to perceive it meaningfully. For sequential data like DNA, this requires understanding two fundamental things about each base: its identity (is it an ‘A’ or a ‘C’?) and its position (is it the first or the fifth base in the sequence?). Our first component, a custom layer named `TokenAndPositionEmbedding`, is designed to handle both.
First, it performs token embedding. Think of this as looking up each base in a rich, multi-dimensional dictionary. Instead of being a simple number, each base is transformed into a dense vector—a list of numbers that gives the model a much richer representation to work with. Second, it performs position embedding. This process gives each position in the sequence (1st, 2nd, 3rd, etc.) its own unique vector. The final step is to simply add these two vectors together. The result is a single, powerful signal for each base that tells the model both what it is and where it is, all at once.

4.2. The Core Engine: The Transformer Block

This component is the heart of the architecture, where the model moves beyond simple perception and begins to understand context. It achieves this through a powerful mechanism called self-attention [2].
Imagine reading the sentence: “The virus mutates, and it becomes more infectious”. To understand what “it” refers to, you instinctively look back at the word “virus”. Self-attention gives our model this same ability. For every single base in the sequence, the attention mechanism can look at all other bases and weigh their importance for understanding that specific base’s context. It learns which other parts of the sequence are most relevant. Our model uses Multi-Head Attention, which is like having a panel of experts read the sequence simultaneously, each looking for different kinds of relationships (some might look for short-range patterns, others for long-range dependencies).
After the attention mechanism has done its work, the resulting information is passed through a standard Feed-Forward Network. This can be thought of as a moment of processing or “thought” for each position in the sequence, refining the context-aware information.
You will also notice “helper” techniques layered throughout the block, such as `Dropout` and `LayerNormalization`. These are best understood as crucial engineering best practices. Dropout helps prevent the model from “memorizing” the data (a problem known as overfitting) by randomly ignoring some neurons during training, forcing it to learn more robust patterns. Layer Normalization acts as a stabilizer, ensuring that the flow of information through the model remains smooth and efficient, which is vital for successful training.

4.3. Final Assembly

The `build_model` function in our script acts as the final assembly line. It chains our components together in a logical sequence: the raw input is fed into our `TokenAndPositionEmbedding` layer, the output of which is then processed by the `TransformerBlock`. Finally, a simple output layer is added to make the ultimate prediction for each position in the sequence. By constructing our model from these simple, interpretable parts, we transform an intimidating architecture into a logical and understandable system.

5. The Engine of Learning: How a Model Improves

We have designed our data and architected our model. But how does the model go from a state of random guessing to one of competence? The answer lies in an elegant, iterative process powered by three core concepts: the loss function, the optimizer, and the metric. It is helpful to visualize this process as a journey: imagine a hiker trying to find the lowest point in a vast, foggy mountain range.

5.1. The Loss Function: Knowing How Wrong You Are

The hiker’s first need is an altimeter to know their current elevation. In machine learning, this “altimeter” is the loss function. After the model makes a prediction, the loss function compares this prediction to the true answer and calculates a single numerical score—the “loss”—that represents how wrong the model was. A high loss score means the model is high up on a mountain peak, far from the correct solution. A low loss score means it is down in a valley, close to the truth. The entire goal of training is to find the set of internal model parameters that results in the lowest possible loss. It is the single, guiding signal that drives all learning.

5.2. The Optimizer: Taking a Step in the Right Direction

Knowing your elevation is one thing; knowing which way to walk is another. This is the job of the optimizer. Our hiker, surrounded by fog, can only feel the slope of the ground directly under their feet. The optimizer is their strategy for taking a step in the steepest downward direction. It uses calculus to look at the loss score and determine how to make tiny adjustments to every one of the model’s internal parameters to best decrease the loss. The learning rate is a crucial setting that controls the size of each step. A learning rate that is too large is like taking giant leaps in the fog—you might overshoot the valley entirely. One that is too small is like taking tiny shuffles—you might take an eternity to get there.

5.3. The Metric: Reporting Your Progress to the World

Finally, while the loss score is the perfect signal for the optimizer to use, it is often not very intuitive for us humans. The hiker wouldn’t report their progress to base camp by saying, “My current loss is 0.0021”. They would say, “I am 99.9% of the way to the goal”. This is the role of a metric. In our case, we use accuracy—the percentage of bases the model gets correct. It is a human-readable report card that allows us to judge the model’s performance in a way that makes practical sense. It is important to remember this distinction: the model learns by minimizing the loss, and we evaluate its success by observing the metric.
Armed with this understanding of the core mechanics of learning, we can now examine the script that puts this engine into motion.

6. The Orchestra Conductor: The Training Pipeline in Action

With our data prepared, our model architected, and the engine of learning understood, the final step is to orchestrate the training process itself. This is where the abstract concepts become a tangible reality. The script that manages this entire workflow, found in Appendix A.3 (`train.py`), acts as the orchestra conductor, bringing together all our components to create a cohesive performance. This process can be understood as a clear, five-step recipe, moving from raw data to a fully trained and saved model.

6.1. The Recipe for Training

The training pipeline begins by loading our data from the `.csv` file we created. A CSV (Comma-Separated Values) file is a universal, plain-text format for storing tabular data (data organized in rows and columns). In this format, each line of the file represents a row, and commas are used to separate the values, or ‘fields’, in each column. Its simplicity and readability make it the perfect format for sharing data between different programs. Once this data is loaded, the script performs the crucial tokenization step, translating the character-based sequences into the numerical vectors our model understands, a concept we introduced in Section 3.
Next, the script builds the model from the architectural blueprint defined in our `transformer_model.py` file. Once assembled, we must compile it. This is a critical configuration step where we provide the model with its learning instructions, using the very concepts we just discussed in the previous section. We define three key things:
  • The Optimizer (`adam`): This is the engine that drives the learning. It’s an efficient algorithm for adjusting the model’s internal parameters to improve performance.
  • The Loss Function (`sparse_categorical_crossentropy`): This is the function the model uses to measure its own mistakes. After each prediction, it calculates a “loss” score that quantifies how far its prediction was from the true answer. The goal of training is to make this score as low as possible.
  • The Metrics (`accuracy`): This is the human-understandable score we use to judge the model’s performance—in this case, the percentage of bases it predicts correctly.

6.2. The Learning Loop

The heart of the script is a single command: `model.fit()`. This command initiates the training loop. The model is shown the ancestor sequences and tasked with predicting the descendant sequences. It repeats this process over the entire dataset for a set number of cycles, or epochs. After each pass, the optimizer uses the information from the loss function to make tiny adjustments to the model’s internal parameters, nudging it ever closer to the correct patterns.
This iterative process of predicting, measuring the error, and adjusting is the very essence of machine learning. In Appendix B.1, we provide the actual on-screen log from this training process. You can observe as the loss value steadily decreases and the accuracy score rises with each epoch, offering a transparent window into the model’s journey from ignorance to competence.

Interpreting the Numbers: From Random Guessing to Competence

As you observe the training log in Appendix B.1, you will see the `loss` decrease and the `accuracy` increase with each epoch. These numbers are not arbitrary; they tell a story about the model’s journey from ignorance to expertise. Understanding what to expect from these numbers is key to diagnosing the health of a training process.
At the very beginning of training, it is crucial to consider the baseline accuracy for our specific task. A naive model making random guesses among our five possible output characters (‘A’, ‘C’, ‘G’, ‘T’, ‘R’) would be correct about 20% of the time. However, our problem has a much higher effective baseline. Because our evolutionary rule only affects the base ‘A’, roughly 75% of the bases in any given sequence (‘C’, ‘G’, and ‘T’) do not change between the ancestor and descendant. A model that learns nothing more than to copy these characters will already be 75% accurate. Therefore, the true learning task for the model is to master the remaining 25% of cases.
As you observe the training log in Appendix B.1, the accuracy at the end of the first epoch is already ~73%. This high value is a healthy sign: it indicates that the model has very quickly learned the simple “no-change” rule for ‘C’, ‘G’, and ‘T’, and is already beginning to correctly learn the `A → G/R` transition. The journey from ~73% to 87.5% accuracy represents the model mastering this specific, nuanced rule. It does not reach 100% because our task is no longer deterministic. An ancestral ‘A’ can become either ‘G’ or ‘R’, with a 50/50 probability. The model cannot be 100% correct on these positions, because the outcome is random. The best a perfect model can do is learn this 50/50 probability.
As training proceeds, the optimizer works to minimize the loss. This has a direct effect on the model’s predictions. Initially, the model might assign roughly equal probability (~20%) to all five characters. As it learns, it begins to adjust its weights to make its predictions “spikier” and more confident. It learns that when it sees an ancestral ‘A’, it should assign a very high probability to the descendant being ‘G’ or ‘R’, and very low probability to the others. This increasing confidence in the correct prediction is what drives the accuracy metric upwards.
Eventually, the model’s performance will plateau, reaching a state of convergence. This means the optimizer has found the lowest point in the “loss valley” that it can, given the model’s architecture and the complexity of the data. For a simple task like ours, this can result in a final accuracy very close to 87.5%. Our final observed accuracy of 87.96% (Appendix B.1) aligns perfectly with this expectation; the minor difference is attributable to the specific statistical makeup of our finite, randomly generated dataset. For more complex, real-world data, the final accuracy might be lower, but the principle remains the same. The final accuracy score reported at the end of training is a statistical summary of the model’s competence on the entire dataset.
The link to the final predictions is therefore direct. The high aggregate accuracy score achieved during training is the reason why, when we test the model on individual sequences as shown in Appendix B.2, it produces the theoretically most probable output. The successful training journey is the prerequisite for reliable inference.
This example demonstrates that the model isn’t just applying one simple trick. It has learned to treat different characters differently, precisely according to the rules we embedded in the data. It correctly identifies the characters that need to be changed (‘A’) and, just as importantly, the characters that must be left alone.

6.3. The Payoff: From Trained Weights to Final Predictions

Once a model has been successfully trained, its purpose shifts. We no longer need to teach it; we need to use it. The process of using a trained model to make predictions on new, unseen data is called inference. This is the final and most important step of our recipe—the moment we get to taste the dish we have so carefully prepared.

6.3.1. The Mechanism of Prediction: A Look Inside the Trained Model

But how does a trained model physically generate a prediction? The answer lies in its weights. All the “knowledge” a neural network acquires during training is stored as a vast collection of numbers, called weights. It is helpful to think of the model as a complex machine with millions of tiny, adjustable numerical dials.
  • During Training: The `model.fit()` process is the act of meticulously turning each of these dials, guided by the optimizer, until the machine consistently transforms inputs into the correct outputs. The final positions of all these dials are the learned weights.
  • During Inference: When we use the model for prediction, all these dials are locked in place. The model is no longer learning or adjusting. When we provide a new input sequence (which has been converted into a numerical vector), it enters this fixed machine. At each layer, the input signal is transformed by a series of mathematical operations (primarily multiplications and additions) with the layer’s fixed weights. This transformed signal flows through the entire network, from one layer to the next, until it reaches the final output layer. This layer then produces the ultimate result: a list of probabilities for each possible character in our vocabulary. The character with the highest probability is the model’s final prediction.
In essence, inference is the simple act of passing a signal through the network of perfectly tuned weights that we discovered during training.

6.3.2. The Final Artifact and Its Use

The last act of our training script is to save the fully trained model using `model.save()`. This command creates a single file that encapsulates the entire model—its architecture and, most importantly, the final, optimized state of all its learned weights. This saved model is the final, valuable artifact, now ready for the inference stage.
To demonstrate this payoff in action, Appendix B.2 provides a complete, self-contained exhibit. It first presents a clear table showing the model’s final predictions on our simple task, allowing you to directly compare its output to the correct answers. Immediately following this table, we provide a concise Python code snippet that performs the three essential steps of inference: (1) loading the saved model (with all its weights), (2) preparing a new input sequence, and (3) using the model to generate a prediction. This completes the full cycle, showing not just how a model is trained, but how its stored knowledge is ultimately deployed to perform its designated task.

7. Placing Our Tool on the Map: A Comparative Analysis

We have successfully built, trained, and used a simple Transformer model. To truly appreciate the significance of this architecture, however, it is essential to place it on the map of existing computational tools. For researchers in the life sciences, this landscape is primarily defined by classical bioinformatics methods and, more recently, by other frameworks within machine learning. By understanding how the Transformer differs, we can understand the unique class of problems it is designed to solve.

7.1. In Contrast to Classical Bioinformatics

The bedrock of modern computational biology is a suite of powerful, algorithm-driven tools like BLAST, Clustal, and HMMER [6]. These methods are the pillars of the field for good reason: they are fast, interpretable, and exceptionally effective at their designed tasks. It is helpful to think of a tool like BLAST as a hyper-efficient library index, designed to find sequences with high similarity based on well-established principles of homology. The knowledge is, in a sense, pre-compiled into scoring matrices (like BLOSUM62) that represent the aggregate probabilities of substitutions observed over vast evolutionary time.
The fundamental difference lies in the nature of this knowledge. These classical methods apply a set of fixed, expertly-defined rules to the data. A Transformer, by contrast, is designed to learn the rules themselves, directly from the data. It does not begin with a pre-conceived notion of substitution probabilities. Instead, through training, it discovers the complex, context-dependent patterns inherent in the examples it is shown. It can, in theory, learn that a specific mutation is highly probable, but only when preceded by a particular three-base-pair motif fifty positions upstream—a type of nuanced, long-range dependency that is outside the scope of traditional similarity-search algorithms.

7.2. In Contrast to Other Machine Learning Models

The Transformer is not the only architecture in the machine learning toolkit. Other powerful models, such as Support Vector Machines (SVMs) or Random Forests, are also used in bioinformatics [7]. These models are excellent general-purpose classifiers and are often used for tasks like predicting whether a protein is an enzyme based on its amino acid composition.
However, a key distinction arises in how they perceive the data. To use a model like a Random Forest on a DNA sequence, a researcher must first perform feature engineering. This involves manually converting the sequence into a fixed set of numerical features—for example, calculating its GC content, or counting the frequency of all possible three-base-pair “k-mers”. While effective, this process often resembles putting the sequence through a shredder. The model may know the counts of all the k-mers, but it has lost the vital information of their order and their relationship to one another across the sequence.
This is the Transformer’s defining advantage. It is an end-to-end learning system for sequential data. Thanks to its positional embeddings and self-attention mechanism, it works on the ordered sequence directly. It does not require a human to pre-define the important features. Its entire purpose is to learn the features and their grammatical relationships automatically. It learns not just what letters are in the sequence, but the very language the sequence is written in.
In summary, while classical tools excel at applying known rules of similarity and other machine learning models excel at classifying based on engineered features, the Transformer’s unique strength is its ability to learn the complex, long-range, context-dependent grammar directly from raw sequential data. This makes it a uniquely powerful tool for the new frontier of predictive and generative genomics.

8. Conclusion: A Foundational Toolkit for the Modern Biologist

Our journey began with a simple but ambitious goal: to open the “black box” of the Transformer and render its complex machinery intelligible to researchers in the life sciences. We sought to replace intimidation with intuition, and complexity with clarity. By walking through the construction of a simple, working model from the ground up, we have provided not just a technical guide, but a conceptual toolkit for understanding this revolutionary technology.
The reader who has completed this journey is now equipped with a new mental model, built upon three foundational pillars. They understand:
  • The Materials: How the raw language of biology—the sequences of ‘A’, ‘C’, ‘G’, and ‘T’—is translated into the native language of a neural network through the essential concepts of tokens, vectors, and learned embeddings.
  • The Blueprint: How a sophisticated architecture like the Transformer is not a monolith, but an elegant assembly of modular layers, with the self-attention mechanism at its core, enabling the model to understand the crucial element of context within a sequence.
  • The Physics: How a model truly learns, demystifying the dynamic process where the guiding signal of a loss function and the methodical work of an optimizer iteratively adjust the model’s internal weights, transforming it from a random guesser into a competent predictor.
Ultimately, the most valuable artifact from this essay is not the code provided in the Appendices, but the foundational understanding built in the mind of the reader. Armed with this intuition, the concepts of deep learning are no longer an inaccessible frontier. Instead, they become a powerful and approachable new set of tools, ready to be applied to the countless fascinating and urgent questions that drive biological discovery. The chase to outsmart evolution continues, but the modern biologist now has a new and powerful lens through which to view the path ahead.

Funding

This research received no external funding.

Acknowledgments

The conceptual development and drafting of this essay benefited significantly from discussions and iterative refinement with an AI language model, Gemini 2.5 Pro, (Google, 6/5/2025).

Conflicts of Interest

The author declares no conflict of interest.

Appendix A.1: ‘simulate_data_v2.py’

Purpose: To generate a biologically-informed toy dataset representing a common evolutionary transition (`A → G`), and to introduce the concept of polymorphism using an IUPAC ambiguity code (`R`).
```python
# Appendix A.1: simulate_data_v2.py
# This script generates a biologically-informed toy dataset.
import numpy as np
import pandas as pd
# --- Configuration ---
NUM_SAMPLES = 1000
SEQ_LENGTH = 50
RANDOM_SEED = 42
VOCABULARY = [‘A’, ‘C’, ‘G’, ‘T’]
FULL_VOCABULARY = VOCABULARY + [‘R’]
ANCESTRAL_BASE = ‘A’
DESCENDANT_BASE = ‘G’
IUPAC_CODE = ‘R’
POLYMORPHISM_PROB = 0.5
OUTPUT_FILE = "transitional_toy_data.csv"
def generate_sequences_v2(num_samples, seq_length, vocabulary, ancestral, descendant, iupac, poly_prob):
  """
  Generates ancestor and descendant sequences simulating a biological transition.
  """
  ancestors = np.random.choice(vocabulary, size=(num_samples, seq_length))
  descendants = ancestors.copy()
  mutation_sites = (ancestors == ancestral)
  num_mutation_sites = np.sum(mutation_sites)
  # 1. Determine the outcome for each potential mutation site.
  outcomes = np.random.choice(
    [descendant, iupac],
    size=num_mutation_sites,
    p=[1 - poly_prob, poly_prob]
  )
  # 2. Directly assign these outcomes to the descendant array where
  # the condition is met. This avoids the "chained indexing" bug.
  descendants[mutation_sites] = outcomes
  return ancestors, descendants
def main():
  """Main function to orchestrate and save the data."""
  print("Starting biologically-informed data generation...")
  np.random.seed(RANDOM_SEED)
  ancestors_np, descendants_np = generate_sequences_v2(
    NUM_SAMPLES, SEQ_LENGTH, VOCABULARY,
    ANCESTRAL_BASE, DESCENDANT_BASE, IUPAC_CODE, POLYMORPHISM_PROB
  )
  # Verification step
  r_count = np.sum(descendants_np == ‘R’)
  print(f"Verification: Found {r_count} instances of ‘R’ in the generated data.")
  ancestor_strings = [‘‘.join(seq) for seq in ancestors_np]
  descendant_strings = [‘‘.join(seq) for seq in descendants_np]
  df = pd.DataFrame({
    ‘ancestor’: ancestor_strings,
    ‘descendant’: descendant_strings
  })
  df.to_csv(OUTPUT_FILE, index=False)
  print(f"Success! Data saved to ‘{OUTPUT_FILE}’.")
  print("\nData preview (with actual mutations):")
  print(df.head())
if __name__ == "__main__":
  main()
```

Appendix A.2: `transformer_model.py`

Purpose: To define the neural network architecture. This script contains the "blueprint" for a minimal, yet functional, Transformer model. It is broken down into two main building blocks: a specialized input layer and the core Transformer block itself.
```python
# Appendix A.2: transformer_model.py
# This script defines the architecture of our Transformer model using TensorFlow and Keras.
# - Custom layers are fully serializable (accept **kwargs).
# - Implements the build() method, adhering to Keras best practices.
import tensorflow as tf
from tensorflow import keras
from tensorflow.keras import layers
class TokenAndPositionEmbedding(layers.Layer):
  def __init__(self, maxlen, vocab_size, embed_dim, **kwargs):
    super(TokenAndPositionEmbedding, self).__init__(**kwargs)
    # Store parameters in __init__
    self.maxlen = maxlen
    self.vocab_size = vocab_size
    self.embed_dim = embed_dim
  def build(self, input_shape):
    # Create the sub-layers in build(), which is best practice.
    self.token_emb = layers.Embedding(input_dim=self.vocab_size, output_dim=self.embed_dim)
    self.pos_emb = layers.Embedding(input_dim=self.maxlen, output_dim=self.embed_dim)
    # Ensure the parent class’s build method is called.
    super(TokenAndPositionEmbedding, self).build(input_shape)
  def call(self, x):
    maxlen = tf.shape(x)[-1]
    positions = tf.range(start=0, limit=maxlen, delta=1)
    positions = self.pos_emb(positions)
    x = self.token_emb(x)
    return x + positions
  def get_config(self):
    config = super().get_config()
    config.update({
      "maxlen": self.maxlen,
      "vocab_size": self.vocab_size,
      "embed_dim": self.embed_dim,
    })
    return config
class TransformerBlock(layers.Layer):
  def __init__(self, embed_dim, num_heads, ff_dim, rate=0.1, **kwargs):
    super(TransformerBlock, self).__init__(**kwargs)
    # Store parameters in __init__
    self.embed_dim = embed_dim
    self.num_heads = num_heads
    self.ff_dim = ff_dim
    self.rate = rate
  def build(self, input_shape):
    # Create the sub-layers in build()
    self.att = layers.MultiHeadAttention(num_heads=self.num_heads, key_dim=self.embed_dim)
    self.ffn = keras.Sequential(
      [layers.Dense(self.ff_dim, activation="relu"), layers.Dense(self.embed_dim),]
    )
    self.layernorm1 = layers.LayerNormalization(epsilon=1e-6)
    self.layernorm2 = layers.LayerNormalization(epsilon=1e-6)
    self.dropout1 = layers.Dropout(self.rate)
    self.dropout2 = layers.Dropout(self.rate)
    super(TransformerBlock, self).build(input_shape)
  def call(self, inputs):
    attention_output = self.att(inputs, inputs)
    attention_output = self.dropout1(attention_output)
    out1 = self.layernorm1(inputs + attention_output)
    ffn_output = self.ffn(out1)
    ffn_output = self.dropout2(ffn_output)
    out2 = self.layernorm2(out1 + ffn_output)
    return out2
  def get_config(self):
    config = super().get_config()
    config.update({
      "embed_dim": self.embed_dim,
      "num_heads": self.num_heads,
      "ff_dim": self.ff_dim,
      "rate": self.rate,
    })
    return config
def build_model(maxlen, vocab_size, embed_dim, num_heads, ff_dim):
  """
  A function to build the complete Transformer model.
  It chains together the input layer, the transformer block, and a final output layer.
  """
  inputs = layers.Input(shape=(maxlen,))
  embedding_layer = TokenAndPositionEmbedding(maxlen, vocab_size, embed_dim)
  x = embedding_layer(inputs)
  transformer_block = TransformerBlock(embed_dim, num_heads, ff_dim)
  x = transformer_block(x)
  outputs = layers.Dense(vocab_size, activation="softmax")(x)
  model = keras.Model(inputs=inputs, outputs=outputs)
  return model
```

Appendix A.3: `train.py`

Purpose: To orchestrate the complete model training pipeline. This script loads the simulated data, preprocesses it for the model, builds the model from our architectural blueprint, executes the training loop, and saves the final, trained artifact.
```python
# Appendix A.3: train.py
# This script is the "conductor" of our machine learning orchestra. It brings
# together the data (from simulate_data_v2.py) and the model architecture
# (from transformer_model.py) to train a model and save the result.
import pandas as pd
import numpy as np
import tensorflow as tf
from tensorflow.keras.preprocessing.sequence import pad_sequences
# Import the building blocks we created in our other scripts.
from transformer_model import build_model
from simulate_data_v2 import FULL_VOCABULARY
# --- Configuration: The Recipe for our Training Session ---
# 1. File Paths
DATA_FILE = "transitional_toy_data.csv"
SAVE_MODEL_PATH = "simple_transformer_model.keras"
# 2. Model Hyperparameters (The architectural details)
VOCAB_SIZE = len(FULL_VOCABULARY)
MAX_LEN = 50   # Must match SEQ_LENGTH from the simulation script.
EMBED_DIM = 32   # The "richness" of the vector for each token.
NUM_HEADS = 2   # Number of attention "perspectives".
FF_DIM = 32    # Hidden layer size in the feed-forward network.
# 3. Training Parameters (The learning process details)
EPOCHS = 10    # How many times to show the entire dataset to the model.
BATCH_SIZE = 64   # How many samples to process at once.
def tokenize_and_pad(sequences, vocab_map, maxlen):
  """
  Translates sequences of characters into padded sequences of integers.
  Args:
    sequences (pd.Series): A pandas Series of sequence strings.
    vocab_map (dict): A dictionary mapping each character to an integer.
    maxlen (int): The maximum length to pad sequences to.
  Returns:
    np.array: A numpy array of tokenized and padded sequences.
  """
  # Create a mapping from character to integer.
  tokenized = [[vocab_map[char] for char in seq] for seq in sequences]
  # Use Keras’s utility to pad all sequences to the same length.
  # ‘post’ means padding is added at the end of the sequence.
  return pad_sequences(tokenized, maxlen=maxlen, padding=‘post’)
def main():
  """The main function to orchestrate the entire training pipeline."""
  print("--- Starting Model Training Pipeline ---")
  # --- Step 1: Load and Prepare the Data ("Mise en Place") ---
  print("\n[1/5] Loading and preparing data...")
  df = pd.read_csv(DATA_FILE)
  # Create the vocabulary mapping from character to integer ID.
  vocab_map = {char: i for i, char in enumerate(FULL_VOCABULARY)}
  print(f"Vocabulary mapping created: {vocab_map}")
  # Tokenize and pad the ancestor sequences (our model’s input, X).
  X_train = tokenize_and_pad(df[‘ancestor’], vocab_map, MAX_LEN)
  # Tokenize and pad the descendant sequences (our model’s target, y).
  y_train = tokenize_and_pad(df[‘descendant’], vocab_map, MAX_LEN)
  print("Data successfully tokenized and padded.")
  # --- Step 2: Build the Model ("Assembling the Tools") ---
  print("\n[2/5] Building the Transformer model...")
  model = build_model(
    maxlen=MAX_LEN,
    vocab_size=VOCAB_SIZE,
    embed_dim=EMBED_DIM,
    num_heads=NUM_HEADS,
    ff_dim=FF_DIM
  )
  print("Model built successfully.")
  # --- Step 3: Compile the Model ("Setting the Rules for Learning") ---
  print("\n[3/5] Compiling the model...")
  model.compile(
    optimizer="adam",
    loss="sparse_categorical_crossentropy",
    metrics=["accuracy"]
  )
  model.summary() # Print a summary of the model architecture.
  print("Model compiled successfully.")
  # --- Step 4: Train the Model ("The Learning Process") ---
  print("\n[4/5] Starting model training...")
  # This is the core step where the model learns from the data.
  # The output from this step will be shown in Appendix B.1.
  model.fit(
    X_train,
    y_train,
    batch_size=BATCH_SIZE,
    epochs=EPOCHS
  )
  print("Training complete.")
  # --- Step 5: Save the Final Model ("Preserving the Result") ---
  print(f"\n[5/5] Saving the trained model to ‘{SAVE_MODEL_PATH}’...")
  model.save(SAVE_MODEL_PATH)
  print("Model saved successfully.")
  print("\n--- Pipeline Complete ---")
if __name__ == "__main__":
  main()
```

Appendix B.1 - The Learning Process: Training Log

This is a snippet of the output that shows the model learning during the training phase.
```
Epoch 1/10 - loss: 0.6578 - accuracy: 0.7257
...
Epoch 10/10 - loss: 0.1764 - accuracy: 0.8796
```

Appendix B.2 - The Final Result: Generating and Viewing Predictions

Part 1: The Prediction Table
This is the final output that shows the model’s adaptation to our simple task.
--- Model Inference Examples ---
Input Sequence: G A T C A A
Model’s Prediction: G G T C G G
Input Sequence: C A C G A T
Model’s Prediction: C G C G G T
Part 2: The Code Snippet to Generate This Table
Right below the table, we place this beautifully simple "exhibit plaque":
```python
# --- Code to Generate Predictions (Proof of Concept) ---
# This snippet shows how to load the saved model and use it to predict.
# It would typically be in a separate script for real-world use.
from tensorflow import keras
import numpy as np
# We must import our custom layer from the model definition script.
from transformer_model import TokenAndPositionEmbedding
# Define our vocabulary and mappings (as in train.py)
FULL_VOCABULARY = [‘A’, ‘C’, ‘G’, ‘T’, ‘R’]
vocab_map = {char: i for i, char in enumerate(FULL_VOCABULARY)}
id_to_char = {i: char for i, char in enumerate(FULL_VOCABULARY)}
# 1. Load the fully trained model from the file.
# We use a ‘custom_object_scope’ to tell Keras how to recognize
# our custom-made layer when loading the model.
with keras.utils.custom_object_scope({‘TokenAndPositionEmbedding’: TokenAndPositionEmbedding}):
  model = keras.models.load_model("simple_transformer_model.keras")
# 2. Prepare a sample input sequence.
input_text = "GATCAA"
tokenized_input = [[vocab_map[char] for char in input_text]]
padded_input = keras.preprocessing.sequence.pad_sequences(tokenized_input, maxlen=50, padding=‘post’)
# 3. Use the model to make a prediction.
prediction_probabilities = model.predict(padded_input)
# Find the character with the highest probability for each position.
predicted_ids = np.argmax(prediction_probabilities, axis=-1)
# 4. Decode the result back into human-readable text.
# We slice the result to the length of our original input for a clean display.
predicted_sequence = predicted_ids[0][:len(input_text)]
predicted_text = "".join([id_to_char[id] for id in predicted_sequence])
print(f"Input: {input_text}")
print(f"Prediction: {predicted_text}")
```

References

  1. Friedman, R. The Viral Chase: Outsmarting Evolution with Data Trees and AI Predictions. Preprints 2025. [CrossRef]
  2. Vaswani, A.; Shazeer, N.; Parmar, N.; Uszkoreit, J.; Jones, L.; Gomez, A.N.; Polosukhin, I. Attention Is All You Need. Advances in Neural Information Processing Systems 2017, 30. [Google Scholar] [CrossRef]
  3. IUPAC codes. Available online: https://genome.ucsc.edu/goldenPath/help/iupac.html (accessed on 7 June 2025).
  4. Friedman, R. Tokenization in the Theory of Knowledge. Encyclopedia 2023, 3, 380–386. [Google Scholar] [CrossRef]
  5. Chollet, F. Deep Learning with Python, 2nd ed.; Manning Publications Co.: Shelter Island, NY, USA, 2021. [Google Scholar]
  6. Gauthier, J.; Vincent, A.T.; Charette, S.J.; Derome, N. A brief history of bioinformatics. Briefings in Bioinformatics 2019, 20, 1981–1996. [Google Scholar] [CrossRef] [PubMed]
  7. Ben-Hur, A.; Ong, C.S.; Sonnenburg, S.; Schölkopf, B.; Rätsch, G. Support Vector Machines and Kernels for Computational Biology. PLoS Computational Biology 2008, 4, e1000173. [Google Scholar] [CrossRef] [PubMed]
Disclaimer/Publisher’s Note: The statements, opinions and data contained in all publications are solely those of the individual author(s) and contributor(s) and not of MDPI and/or the editor(s). MDPI and/or the editor(s) disclaim responsibility for any injury to people or property resulting from any ideas, methods, instructions or products referred to in the content.
Copyright: This open access article is published under a Creative Commons CC BY 4.0 license, which permit the free download, distribution, and reuse, provided that the author and preprint are cited in any reuse.
Prerpints.org logo

Preprints.org is a free preprint server supported by MDPI in Basel, Switzerland.

Subscribe

Disclaimer

Terms of Use

Privacy Policy

Privacy Settings

© 2025 MDPI (Basel, Switzerland) unless otherwise stated