Skip to content

Instantly share code, notes, and snippets.

@AhmedCoolProjects
Created November 23, 2025 02:37
Show Gist options
  • Select an option

  • Save AhmedCoolProjects/7253fafbc96315bcbbb07366e614f689 to your computer and use it in GitHub Desktop.

Select an option

Save AhmedCoolProjects/7253fafbc96315bcbbb07366e614f689 to your computer and use it in GitHub Desktop.
Bi-LSTM Application.ipynb
Display the source blob
Display the rendered blob
Raw
{
"nbformat": 4,
"nbformat_minor": 0,
"metadata": {
"colab": {
"provenance": [],
"authorship_tag": "ABX9TyPU/y966iE2TqAf9sRVlmPX",
"include_colab_link": true
},
"kernelspec": {
"name": "python3",
"display_name": "Python 3"
},
"language_info": {
"name": "python"
}
},
"cells": [
{
"cell_type": "markdown",
"metadata": {
"id": "view-in-github",
"colab_type": "text"
},
"source": [
"<a href=\"https://colab.research.google.com/gist/AhmedCoolProjects/7253fafbc96315bcbbb07366e614f689/bi-lstm-application.ipynb\" target=\"_parent\"><img src=\"https://colab.research.google.com/assets/colab-badge.svg\" alt=\"Open In Colab\"/></a>"
]
},
{
"cell_type": "code",
"execution_count": 1,
"metadata": {
"id": "BInhqD3gb85U"
},
"outputs": [],
"source": [
"import numpy as np\n",
"import matplotlib.pyplot as plt"
]
},
{
"cell_type": "code",
"source": [
"class LSTM:\n",
" def __init__(self, hidden_size, vocab_size, learning_rate=0.01):\n",
" self.H = hidden_size\n",
" self.V = vocab_size\n",
" self.lr = learning_rate\n",
"\n",
" # Initialize weights (4 sets)\n",
" # f: Forget, i: Input, c: Candidate, o: Output\n",
" self._W_size = (self.H, self.H + self.V) # weights shape for all gates\n",
" # Forget Gate\n",
" self.Wf = np.random.randn(*self._W_size) * 0.01\n",
" self.bf = np.zeros((self.H, 1))\n",
"\n",
" # Input Gate\n",
" self.Wi = np.random.randn(*self._W_size) * 0.01\n",
" self.bi = np.zeros((self.H, 1))\n",
"\n",
" # Candidate Layer\n",
" self.Wc = np.random.randn(*self._W_size) * 0.01\n",
" self.bc = np.zeros((self.H, 1))\n",
"\n",
" # Output Gate\n",
" self.Wo = np.random.randn(*self._W_size) * 0.01\n",
" self.bo = np.zeros((self.H, 1))\n",
"\n",
" # Final Output Layer\n",
" self.Why = np.random.randn(self.V, self.H) * 0.01\n",
" self.by = np.zeros((self.V, 1))\n",
"\n",
" def _sigmoid(self, z):\n",
" return 1 / (1 + np.exp(-z))\n",
"\n",
" def _tanh(self, z):\n",
" return np.tanh(z)\n",
"\n",
" def _softmax(self, z):\n",
" e_z = np.exp(z - np.max(z))\n",
" return e_z / np.sum(e_z, axis=0)\n",
"\n",
" def _sigmoid_derivative(self, z):\n",
" return z * (1 - z)\n",
"\n",
" def _tanh_derivative(self, z):\n",
" return 1 - np.tanh(z) ** 2\n",
"\n",
" def forward(self, inputs, h_prev, c_prev):\n",
" '''\n",
" inputs: list of input indices\n",
" h_prev: previous hidden state (H x 1)\n",
" c_prev: previous cell state (H x 1)\n",
" '''\n",
"\n",
" xs, hs, cs, zs, ys = {}, {}, {}, {}, {} # store values for each time step\n",
" fs, is_, cs_tilde, os = {}, {}, {}, {} # gate activations\n",
" concat_inputs = {}\n",
"\n",
" hs[-1] = np.copy(h_prev)\n",
" cs[-1] = np.copy(c_prev)\n",
"\n",
" total_cost = 0\n",
"\n",
" for t in range(len(inputs)):\n",
" # 1. One-hot encode the input character\n",
" xs[t] = np.zeros((self.V, 1))\n",
" xs[t][inputs[t]] = 1\n",
"\n",
" # 2. Concatenate h_prev and x_t\n",
" concat_inputs[t] = np.vstack((hs[t-1], xs[t])) # (H + V) x 1\n",
"\n",
" # 3. Forget Gate\n",
" fs[t] = self._sigmoid(np.dot(self.Wf, concat_inputs[t]) + self.bf)\n",
"\n",
" # 4. Input Gate\n",
" is_[t] = self._sigmoid(np.dot(self.Wi, concat_inputs[t]) + self.bi)\n",
"\n",
" # 5. Candidate Layer\n",
" cs_tilde[t] = self._tanh(np.dot(self.Wc, concat_inputs[t]) + self.bc)\n",
"\n",
" # 6. Output Gate\n",
" os[t] = self._sigmoid(np.dot(self.Wo, concat_inputs[t]) + self.bo)\n",
"\n",
" # 7. Update Cell State\n",
" cs[t] = fs[t] * cs[t-1] + is_[t] * cs_tilde[t] # (H x 1)\n",
"\n",
" # 8. Compute Hidden State\n",
" hs[t] = os[t] * self._tanh(cs[t]) # (H x 1)\n",
"\n",
" # 9. Compute Output\n",
" zs[t] = np.dot(self.Why, hs[t]) + self.by\n",
" ys[t] = self._softmax(zs[t]) # (V x 1)\n",
"\n",
" cache = (xs, hs, cs, fs, is_, cs_tilde, os, zs, ys, concat_inputs)\n",
"\n",
" return ys, hs[len(inputs)-1], cs[len(inputs)-1], cache\n",
"\n",
" def compute_cost(self, y_preds, targets):\n",
" total_cost = 0\n",
" for t in range(len(targets)):\n",
" prob_of_target = y_preds[t][targets[t], 0] # [target_index, 0] gives the prob of that target and the 0 is to get the scalar from the (1,1) array\n",
" total_cost += -np.log(prob_of_target + 1e-9) # add small value to avoid log(0)\n",
" return total_cost / len(targets)\n",
"\n",
" def backpropagation(self, targets, cache):\n",
" xs, hs, cs, fs, is_, cs_tilde, os, zs, ys, concat_inputs = cache\n",
" # Initialize gradients\n",
" self.dWf = np.zeros_like(self.Wf)\n",
" self.dbf = np.zeros_like(self.bf)\n",
" self.dWi = np.zeros_like(self.Wi)\n",
" self.dbi = np.zeros_like(self.bi)\n",
" self.dWc = np.zeros_like(self.Wc)\n",
" self.dbc = np.zeros_like(self.bc)\n",
" self.dWo = np.zeros_like(self.Wo)\n",
" self.dbo = np.zeros_like(self.bo)\n",
" self.dWhy = np.zeros_like(self.Why)\n",
" self.dby = np.zeros_like(self.by)\n",
"\n",
" dh_next = np.zeros_like(hs[0])\n",
" dc_next = np.zeros_like(cs[0])\n",
"\n",
" for t in reversed(range(len(targets))):\n",
" # 1. Output layer\n",
" dy = np.copy(ys[t])\n",
" dy[targets[t]] -= 1 # y_pred - y_true\n",
" self.dWhy += np.dot(dy, hs[t].T)\n",
" self.dby += dy\n",
"\n",
" # 2. Gradient for hidden state dh_t\n",
" dh = np.dot(self.Why.T, dy) + dh_next\n",
"\n",
" # 3. Gradient for output gate\n",
" do = dh * self._tanh(cs[t]) * os[t] * (1 - os[t])\n",
" self.dWo += np.dot(do, concat_inputs[t].T)\n",
" self.dbo += do\n",
"\n",
" # 4. Gradient for cell state\n",
" dc = dh * os[t] * (1 - self._tanh(cs[t])**2) + dc_next\n",
"\n",
" # 5. Gradient for forget gate\n",
" df = dc * cs[t-1] * fs[t] * (1 - fs[t])\n",
" self.dWf += np.dot(df, concat_inputs[t].T)\n",
" self.dbf += df\n",
"\n",
" # 6. Gradient for input gate\n",
" di = dc * cs_tilde[t] * is_[t] * (1 - is_[t])\n",
" self.dWi += np.dot(di, concat_inputs[t].T)\n",
" self.dbi += di\n",
"\n",
" # 7. Gradient for candidate layer\n",
" dc_tilde = dc * is_[t] * (1 - cs_tilde[t]**2)\n",
" self.dWc += np.dot(dc_tilde, concat_inputs[t].T)\n",
" self.dbc += dc_tilde\n",
"\n",
" # 8. Gradient for concatenated input\n",
" dconcat = (np.dot(self.Wf.T, df) +\n",
" np.dot(self.Wi.T, di) +\n",
" np.dot(self.Wc.T, dc_tilde) +\n",
" np.dot(self.Wo.T, do))\n",
" dh_next = dconcat[:self.H, :] # Gradient for h_(t-1)\n",
" dc_next = dc * fs[t] # Gradient for C_(t-1)\n",
"\n",
" # Gradient clipping to prevent exploding gradients\n",
" for grad in [self.dWf, self.dbf, self.dWi, self.dbi,\n",
" self.dWc, self.dbc, self.dWo, self.dbo,\n",
" self.dWhy, self.dby]:\n",
" np.clip(grad, -5, 5, out=grad)\n",
"\n",
" def update_parameters(self, learning_rate=0.01):\n",
" self.Wf -= learning_rate * self.dWf\n",
" self.bf -= learning_rate * self.dbf\n",
" self.Wi -= learning_rate * self.dWi\n",
" self.bi -= learning_rate * self.dbi\n",
" self.Wc -= learning_rate * self.dWc\n",
" self.bc -= learning_rate * self.dbc\n",
" self.Wo -= learning_rate * self.dWo\n",
" self.bo -= learning_rate * self.dbo\n",
" self.Why -= learning_rate * self.dWhy\n",
" self.by -= learning_rate * self.dby\n",
"\n",
" def sample(self, seed_idx, h_prev, c_prev, length=20):\n",
" x = np.zeros((self.V, 1))\n",
" x[seed_idx] = 1\n",
" indices = []\n",
"\n",
" for t in range(length):\n",
" concat_input = np.vstack((h_prev, x))\n",
"\n",
" # Gates\n",
" f = self._sigmoid(np.dot(self.Wf, concat_input) + self.bf)\n",
" i = self._sigmoid(np.dot(self.Wi, concat_input) + self.bi)\n",
" c_tilde = self._tanh(np.dot(self.Wc, concat_input) + self.bc)\n",
" o = self._sigmoid(np.dot(self.Wo, concat_input) + self.bo)\n",
"\n",
" # Update cell state\n",
" c = f * c_prev + i * c_tilde\n",
"\n",
" # Compute hidden state\n",
" h = o * self._tanh(c)\n",
"\n",
" z = np.dot(self.Why, h) + self.by\n",
" y = self._softmax(z)\n",
"\n",
" # Sample from the probability distribution\n",
" idx = np.random.choice(range(self.V), p=y.ravel()) # this gives us a scalar index meaning the index of the predicted character. the .ravel() is to convert the (V,1) shape to (V,) shape which is required by np.random.choice, this .choice samples according to the probabilities in y\n",
" x = np.zeros((self.V, 1))\n",
" x[idx] = 1\n",
" indices.append(idx)\n",
" h_prev = h\n",
" c_prev = c\n",
"\n",
" return indices"
],
"metadata": {
"id": "O-l2bzQRcBmj"
},
"execution_count": 8,
"outputs": []
},
{
"cell_type": "code",
"source": [
"class BiLSTM:\n",
" def __init__(self, hidden_size, vocab_size, learning_rate=0.01):\n",
" self.H = hidden_size\n",
" self.V = vocab_size\n",
" self.lr = learning_rate\n",
"\n",
" # Two LSTM instances\n",
" self.f_lstm = LSTM(self.H, self.V, self.lr) # Forward LSTM\n",
" self.b_lstm = LSTM(self.H, self.V, self.lr) # Backward L\n",
"\n",
" # Combined output layer\n",
" # The input to this layer is (H_fwd + H_bwd) = 2H\n",
" self.Wy = np.random.randn(self.V, 2 * self.H) * 0.01\n",
" self.by = np.zeros((self.V, 1))\n",
"\n",
" # Gradients for output layer\n",
" self.dWy = np.zeros_like(self.Wy)\n",
" self.dby = np.zeros_like(self.by)\n",
"\n",
" def _softmax(self, z):\n",
" exp_z = np.exp(z - np.max(z, axis=0))\n",
" return exp_z / exp_z.sum(axis=0)\n",
"\n",
" def forward(self, inputs, h_prev_f, c_prev_f, h_prev_b, c_prev_b):\n",
" # 1. Run forward LSTM\n",
" _, _, _, cache_f = self.f_lstm.forward(inputs, h_prev_f, c_prev_f)\n",
" _, hs_f, _, _, _, _, _, _, _, _ = cache_f\n",
"\n",
" # 2. Run backward LSTM\n",
" rev_inputs = inputs[::-1]\n",
" _, _, _, cache_b = self.b_lstm.forward(rev_inputs, h_prev_b, c_prev_b)\n",
" _, rev_hs_b, _, _, _, _, _, _, _, _ = cache_b\n",
"\n",
" # 3. Combine outputs\n",
" ys, concat_hs = {}, {}\n",
"\n",
" # We need to match time steps.\n",
" # hs_f[0] corresponds to inputs[0]\n",
" # rev_hs_b[0] corresponds to inputs[T-1] (last time step)\n",
"\n",
" T = len(inputs)\n",
" for t in range(T):\n",
" h_f = hs_f[t] # forward hidden state for input t\n",
" h_b = rev_hs_b[T - 1 - t] # backward hidden state for input t\n",
"\n",
" concat_hs[t] = np.vstack((h_f, h_b)) # Concatenate\n",
" z = np.dot(self.Wy, concat_hs[t]) + self.by\n",
" ys[t] = self._softmax(z)\n",
"\n",
" cache = (cache_f, cache_b, concat_hs, ys)\n",
" return ys, cache\n",
"\n",
" def compute_cost(self, ys, targets):\n",
" total_cost = 0\n",
" for t in range(len(targets)):\n",
" prob = ys[t][targets[t], 0]\n",
" total_cost += -np.log(prob + 1e-9)\n",
" return total_cost / len(targets)\n",
"\n",
" def backpropagation(self, targets, cache):\n",
" cache_f, cache_b, concat_hs, ys = cache\n",
"\n",
" T = len(targets)\n",
"\n",
" # We need to collect gradients for the hidden states of both LSTMs\n",
" # to run their specific backprop logic.\n",
" dh_f_seq = {}\n",
" dh_b_seq = {}\n",
"\n",
" # 1. Backprop through the Combined output layer\n",
" for t in reversed(range(T)):\n",
" dy = np.copy(ys[t])\n",
" dy[targets[t]] -= 1\n",
"\n",
" self.dWy += np.dot(dy, concat_hs[t].T)\n",
" self.dby += dy\n",
"\n",
" # Calculate gradient for the concatenated hidden state\n",
" # dh_concat = Wy.T @ dy\n",
" dh_concat = np.dot(self.Wy.T, dy)\n",
"\n",
" # Split gradients back to forward and backward LSTM\n",
" dh_f_seq[t] = dh_concat[:self.H, :]\n",
" dh_b_seq[T - 1 - t] = dh_concat[self.H:, :]\n",
"\n",
" # 2. Backprop through Forward LSTM\n",
" self._lstm_backprop_manual(self.f_lstm, dh_f_seq, cache_f, T)\n",
" # 3. Backprop through Backward LSTM\n",
" self._lstm_backprop_manual(self.b_lstm, dh_b_seq, cache_b, T)\n",
"\n",
" def _lstm_backprop_manual(self, lstm, dh_seq, cache, T):\n",
" '''\n",
" Runs BPTT for a single LSTM given a sequence of hidden states gradients.\n",
" '''\n",
" # Correctly unpack the cache from LSTM.forward.\n",
" # The cache elements are: xs, hs, cs, fs_gate, is_gate, cs_tilde, os_gate, zs, ys, concat_inputs\n",
" xs_cache, hs_cache, cs_cache, fs_gate_cache, is_gate_cache, cs_tilde_cache, os_gate_cache, _, _, concat_inputs_cache = cache\n",
"\n",
" # Initialize LSTM gradients\n",
" lstm.dWf = np.zeros_like(lstm.Wf)\n",
" lstm.dWi = np.zeros_like(lstm.Wi)\n",
" lstm.dWc = np.zeros_like(lstm.Wc)\n",
" lstm.dWo = np.zeros_like(lstm.Wo)\n",
" lstm.dbf = np.zeros_like(lstm.bf)\n",
" lstm.dbi = np.zeros_like(lstm.bi)\n",
" lstm.dbc = np.zeros_like(lstm.bc)\n",
" lstm.dbo = np.zeros_like(lstm.bo)\n",
"\n",
" dh_next = np.zeros_like(hs_cache[0])\n",
" dc_next = np.zeros_like(cs_cache[0])\n",
"\n",
" for t in reversed(range(T)):\n",
" # Get the gradient from the combined layer above\n",
" dh_from_above = dh_seq[t]\n",
" # Total gradient for h_t\n",
" dh = dh_from_above + dh_next\n",
"\n",
" # ---- Standard BPTT ----\n",
" # Gradient for output gate (do)\n",
" do = dh * lstm._tanh(cs_cache[t])\n",
" do_raw = do * lstm._sigmoid_derivative(os_gate_cache[t])\n",
" lstm.dWo += np.dot(do_raw, concat_inputs_cache[t].T)\n",
" lstm.dbo += do_raw\n",
"\n",
" # Gradient for cell state (dc)\n",
" dc = dh * os_gate_cache[t] * lstm._tanh_derivative(cs_cache[t]) + dc_next\n",
"\n",
" # Gradient for candidate cell state (dc_cand)\n",
" dc_cand = dc * is_gate_cache[t]\n",
" dc_cand_raw = dc_cand * lstm._tanh_derivative(cs_tilde_cache[t])\n",
" lstm.dWc += np.dot(dc_cand_raw, concat_inputs_cache[t].T)\n",
" lstm.dbc += dc_cand_raw\n",
"\n",
" # Gradient for input gate (di)\n",
" di = dc * cs_tilde_cache[t]\n",
" di_raw = di * lstm._sigmoid_derivative(is_gate_cache[t])\n",
" lstm.dWi += np.dot(di_raw, concat_inputs_cache[t].T)\n",
" lstm.dbi += di_raw\n",
"\n",
" # Gradient for forget gate (df)\n",
" df = dc * cs_cache[t-1]\n",
" df_raw = df * lstm._sigmoid_derivative(fs_gate_cache[t])\n",
" lstm.dWf += np.dot(df_raw, concat_inputs_cache[t].T)\n",
" lstm.dbf += df_raw\n",
"\n",
" # Gradient for concatenated input\n",
" dconcat = (np.dot(lstm.Wf.T, df_raw) +\n",
" np.dot(lstm.Wi.T, di_raw) +\n",
" np.dot(lstm.Wc.T, dc_cand_raw) +\n",
" np.dot(lstm.Wo.T, do_raw))\n",
"\n",
" dh_next = dconcat[:lstm.H, :] # Gradient for h_(t-1)\n",
" dc_next = dc * fs_gate_cache[t] # Gradient for C_(t-1)\n",
"\n",
" # Clip gradients\n",
" for grad in [lstm.dWf, lstm.dWi, lstm.dWc, lstm.dWo, lstm.dbf, lstm.dbi, lstm.dbc, lstm.dbo]:\n",
" np.clip(grad, -5, 5, out=grad)\n",
"\n",
" def update_parameters(self):\n",
" # Combined Layer\n",
" self.Wy -= self.lr * self.dWy\n",
" self.by -= self.lr * self.dby\n",
"\n",
" # Inner LSTMs\n",
" self._lstm_update_manual(self.f_lstm)\n",
" self._lstm_update_manual(self.b_lstm)\n",
"\n",
" def _lstm_update_manual(self, lstm):\n",
" lstm.Wf -= self.lr * lstm.dWf\n",
" lstm.Wi -= self.lr * lstm.dWi\n",
" lstm.Wc -= self.lr * lstm.dWc\n",
" lstm.Wo -= self.lr * lstm.dWo\n",
" lstm.bf -= self.lr * lstm.dbf\n",
" lstm.bi -= self.lr * lstm.dbi\n",
" lstm.bc -= self.lr * lstm.dbc\n",
" lstm.bo -= self.lr * lstm.dbo\n",
"\n",
" def sample(self, seed_idx, h_prev_fwd, c_prev_fwd, length=20):\n",
" \"\"\"\n",
" Generates text using ONLY the Forward LSTM.\n",
" Ideally, Bi-LSTMs are not used for generation, but this is a demonstration.\n",
" We feed zeros for the backward state.\n",
" \"\"\"\n",
" x = np.zeros((self.V, 1))\n",
" x[seed_idx] = 1\n",
"\n",
" generated_indices = []\n",
"\n",
" # Placeholder for backward state (zeros)\n",
" h_fake_bwd = np.zeros((self.H, 1))\n",
"\n",
" for t in range(length):\n",
" # --- 1. Run One Step of Forward LSTM ---\n",
" # (We manually run the step logic here since lstm.forward_pass does a loop)\n",
"\n",
" # Concatenate input [h_prev, x]\n",
" concat_input = np.vstack((h_prev_fwd, x))\n",
"\n",
""
],
"metadata": {
"id": "6_VfzGIscIpE"
},
"execution_count": 33,
"outputs": []
},
{
"cell_type": "code",
"source": [
"# 1. prepare data\n",
"data = \"helloahmed\"\n",
"chars = list(set(data))\n",
"vocab_size = len(chars)\n",
"char_to_idx = { ch:i for i,ch in enumerate(chars)}\n",
"idx_to_char = { i:ch for i,ch in enumerate(chars)}\n",
"\n",
"print(f\"Data: {data}\")\n",
"print(f\"Vocabulary: {chars}\")\n",
"print(f\"Vocab Size: {vocab_size}\")\n"
],
"metadata": {
"colab": {
"base_uri": "https://localhost:8080/"
},
"id": "aBw74HwQcNZO",
"outputId": "c7990f0a-a17a-45ef-aacd-e5992485d4d2"
},
"execution_count": 45,
"outputs": [
{
"output_type": "stream",
"name": "stdout",
"text": [
"Data: helloahmed\n",
"Vocabulary: ['h', 'e', 'a', 'm', 'o', 'l', 'd']\n",
"Vocab Size: 7\n"
]
}
]
},
{
"cell_type": "code",
"source": [
"# 2. Create Model\n",
"hidden_size = 25\n",
"learning_rate = 0.01\n",
"epochs = 500 # Fewer epochs needed because Bi-LSTM learns very fast"
],
"metadata": {
"id": "3yfCJF4kcQll"
},
"execution_count": 51,
"outputs": []
},
{
"cell_type": "code",
"source": [
"bilstm = BiLSTM(hidden_size, vocab_size, learning_rate)"
],
"metadata": {
"id": "fY1OZ3oRcS9L"
},
"execution_count": 52,
"outputs": []
},
{
"cell_type": "code",
"source": [
"# 3. Training Loop\n",
"print(\"Training Bi-LSTM...\")\n",
"costs = []\n",
"\n",
"inputs = [char_to_idx[ch] for ch in data[:-1]]\n",
"targets = [char_to_idx[ch] for ch in data[1:]]\n",
"\n",
"for epoch in range(epochs):\n",
" # Initialize states for BOTH LSTMs\n",
" h_prev_fwd = np.zeros((hidden_size, 1))\n",
" c_prev_fwd = np.zeros((hidden_size, 1))\n",
" h_prev_bwd = np.zeros((hidden_size, 1))\n",
" c_prev_bwd = np.zeros((hidden_size, 1))\n",
"\n",
" # Forward pass\n",
" y_preds, cache = bilstm.forward(inputs, h_prev_fwd, c_prev_fwd, h_prev_bwd, c_prev_bwd)\n",
"\n",
" # Compute cost\n",
" cost = bilstm.compute_cost(y_preds, targets)\n",
"\n",
" # Backpropagation\n",
" bilstm.backpropagation(targets, cache)\n",
"\n",
" # Update parameters\n",
" bilstm.update_parameters()\n",
"\n",
" if epoch % 100 == 0:\n",
" print(f\"Epoch {epoch}, Cost: {cost:.4f}\")\n",
" costs.append(cost)\n",
"\n",
"print(\"Training complete.\")"
],
"metadata": {
"colab": {
"base_uri": "https://localhost:8080/"
},
"id": "R7Fhomt3cVip",
"outputId": "1272c289-a8ea-4982-f96f-ba6cc749fbb5"
},
"execution_count": 53,
"outputs": [
{
"output_type": "stream",
"name": "stdout",
"text": [
"Training Bi-LSTM...\n",
"Epoch 0, Cost: 1.9458\n",
"Epoch 100, Cost: 1.8416\n",
"Epoch 200, Cost: 0.2916\n",
"Epoch 300, Cost: 0.0082\n",
"Epoch 400, Cost: 0.0003\n",
"Training complete.\n"
]
}
]
},
{
"cell_type": "code",
"source": [
"plt.plot(np.squeeze(costs))\n",
"plt.ylabel('Cost')\n",
"plt.xlabel('Epochs (per 100)')\n",
"plt.title(f\"Bi-LSTM Training\")\n",
"plt.show()"
],
"metadata": {
"colab": {
"base_uri": "https://localhost:8080/",
"height": 472
},
"id": "2uvoGm27cx3G",
"outputId": "0b1f4e38-2ae4-4878-a8c4-10add990b543"
},
"execution_count": 54,
"outputs": [
{
"output_type": "display_data",
"data": {
"text/plain": [
"<Figure size 640x480 with 1 Axes>"
],
"image/png": "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\n"
},
"metadata": {}
}
]
},
{
"cell_type": "code",
"source": [
"# Test the model (sampling)\n",
"print(\"\\nSampling from the model:\")\n",
"# Get the index for our seed character 'h'\n",
"seed_char_idx = char_to_idx['h']\n",
"\n",
"# For sampling, we only need the initial forward states\n",
"h_sample_fwd = np.zeros((hidden_size, 1))\n",
"c_sample_fwd = np.zeros((hidden_size, 1))\n",
"\n",
"generated_indices = bilstm.sample(seed_char_idx, h_sample_fwd, c_sample_fwd, length=10)\n",
"\n",
"generated_text = 'h' + ''.join(idx_to_char[idx] for idx in generated_indices)\n",
"\n",
"print(f\"Generated text: '{generated_text}'\")"
],
"metadata": {
"colab": {
"base_uri": "https://localhost:8080/"
},
"id": "56gClwK1fF8a",
"outputId": "7646ba2f-bedd-45af-9197-accdcf8437c5"
},
"execution_count": 57,
"outputs": [
{
"output_type": "stream",
"name": "stdout",
"text": [
"\n",
"Sampling from the model:\n",
"Generated text: 'heddddddddd'\n"
]
}
]
},
{
"cell_type": "code",
"source": [],
"metadata": {
"id": "sM4fD2LmePff"
},
"execution_count": null,
"outputs": []
}
]
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment