Check this Py code: import tensorflow as tf
import numpy as np

block_size = 20

with open('data/universe.txt', 'r', encoding='utf-8') as f:
    text = f.read()

data = np.array([ord(c) for c in text], dtype=np.int32)
vocab_size = len(set(data))


class TransformerBlock(tf.keras.layers.Layer):
    def init(self, n_embd, n_head, **kwargs):
        # Make sure to forward the ‘kwargs’ to the superclass initializer
        super().init(**kwargs)

        self.ln1 = tf.keras.layers.LayerNormalization(epsilon=1e-6)
        self.attn = tf.keras.layers.MultiHeadAttention(num_heads=n_head, key_dim=n_embd // n_head)
        self.ln2 = tf.keras.layers.LayerNormalization(epsilon=1e-6)
        self.proj = tf.keras.Sequential([tf.keras.layers.Dense(4 * n_embd, activation="relu"), tf.keras.layers.Dense(n_embd)])
        
    def call(self, x, training):
        a = self.ln1(x)
        a = self.attn(query=a, key=a, value=a, training=training)
        x = x + a
        
        a = self.ln2(x)
        a = self.proj(a, training=training)
        x = x + a
        return x
    
    def get_config(self):
        config = super().get_config().copy()
        config.update({
            "n_embd": self.attn.key_dim * self.attn.num_heads,
            "n_head": self.attn.num_heads,
        })
        return config


class GPTLanguageModel(tf.keras.Model):
    def __init__(self, vocab_size, n_embd, block_size, n_head, n_layer, dropout_rate=0.1):
        super(GPTLanguageModel, self).__init__()
        self.token_embedding_table = tf.keras.layers.Embedding(vocab_size, n_embd)
        self.position_embedding_table = tf.keras.layers.Embedding(block_size, n_embd)
        self.blocks = [TransformerBlock(n_embd, n_head) for _ in range(n_layer)]
        self.ln_f = tf.keras.layers.LayerNormalization(epsilon=1e-6)
        self.lm_head = tf.keras.layers.Dense(vocab_size)

    def get_config(self):
        config = super().get_config().copy()
        config.update({
            "vocab_size": self.vocab_size,
            "n_embd": self.n_embd,
            "block_size": self.block_size,
            "n_head": self.n_head,
            "n_layer": self.n_layer,
            "dropout_rate": self.dropout_rate,
        })
        return config

    @classmethod
    def from_config(cls, config, custom_objects=None):
        # This ensures custom_objects are taken into account when reconstructing the model
        if custom_objects is not None:
            with tf.keras.utils.custom_object_scope(custom_objects):
                return cls(**config)
        return cls(**config)

    def call(self, idx, targets=None, training=False):
        B, T = tf.shape(idx)[0], tf.shape(idx)[1]
        tok_emb = self.token_embedding_table(idx)
        pos_emb = self.position_embedding_table(tf.range(T))
        x = tok_emb + tf.cast(pos_emb[None, :, :], tok_emb.dtype)  # Explicit casting to match dtype

        for block in self.blocks:
            x = block(x, training=training)

        x = self.ln_f(x)
        logits = self.lm_head(x)

        loss = None
        if targets is not None:
            loss = self.compute_loss(logits, targets)

        return logits, loss

    def compute_loss(self, logits, targets):
        loss = tf.keras.losses.sparse_categorical_crossentropy(targets, logits, from_logits=True)
        return tf.reduce_mean(loss)

    def generate(self, idx, max_new_tokens):
        for _ in range(max_new_tokens):
            logits, _ = self(idx[:, -block_size:], training=False)
            logits = logits[:, -1, :]
            idx_next = tf.random.categorical(logits, num_samples=1, dtype=tf.int32)
            idx = tf.concat([idx, idx_next], axis=1)
        return idx


# Load the saved model
model = tf.keras.models.load_model("Universe-GPT-tf.keras",
    custom_objects={
    "GPTLanguageModel": GPTLanguageModel,
    "TransformerBlock": TransformerBlock,  # Possible that you require this as well
})

# Set the maximum number of new tokens to generate
max_new_tokens = 100

# Initialize the input sequence with a random starting point
input_seq = np.random.randint(0, vocab_size, size=(1, block_size))

# Generate new text
generated_text = model.generate(input_seq, max_new_tokens)

# Convert the generated text back to characters
generated_text = ''.join([chr(c) for c in generated_text[0]])

print("Generated Text:")
print(generated_text)