File size: 96,612 Bytes
e93284c
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
{
 "cells": [
  {
   "cell_type": "markdown",
   "id": "356860c4",
   "metadata": {},
   "source": [
    "# Importing Necessary Libraries:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 1,
   "id": "d39bd257",
   "metadata": {},
   "outputs": [],
   "source": [
    "import torch\n",
    "import torch.nn as nn\n",
    "import numpy as np\n",
    "from torch.utils.data import Dataset, DataLoader\n",
    "from transformers import BertTokenizer\n",
    "import matplotlib.pyplot as plt\n",
    "import json\n",
    "import pickle\n",
    "from collections import defaultdict\n",
    "\n",
    "from transformers import AutoTokenizer \n",
    "from tokenizers.pre_tokenizers import Whitespace"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "2a6d4013",
   "metadata": {},
   "source": [
    "# Loading and Preprocessing the Dataset:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 2,
   "id": "e602ee96",
   "metadata": {},
   "outputs": [],
   "source": [
    "# Load the JSON data from a file\n",
    "with open('Amharic.json', 'r') as file:\n",
    "    data = json.load(file)\n",
    "\n",
    "dataset = data[:1000]\n",
    "# Extract English and Amharic sentences\n",
    "sentence_pairs = [{'en':example['input'], 'am':example['output']} for example in dataset]"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 3,
   "id": "763df0fa",
   "metadata": {},
   "outputs": [],
   "source": [
    "# Extract unique values for the key \"instruction\"\n",
    "unique_instructions = {entry['instruction'] for entry in data[:2000] if 'instruction' in entry}"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 4,
   "id": "f9bd0ee1",
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "[{'en': 'Give three tips for staying healthy.',\n",
       "  'am': ' ጤናማ ለመሆን ሶስት ምክሮችን ይስጡ.'},\n",
       " {'en': '1. Eat a balanced and nutritious diet: Make sure your meals are inclusive of a variety of fruits and vegetables, lean protein, whole grains, and healthy fats. This helps to provide your body with the essential nutrients to function at its best and can help prevent chronic diseases.\\n\\n2. Engage in regular physical activity: Exercise is crucial for maintaining strong bones, muscles, and cardiovascular health. Aim for at least 150 minutes of moderate aerobic exercise or 75 minutes of vigorous exercise each week.\\n\\n3. Get enough sleep: Getting enough quality sleep is crucial for physical and mental well-being. It helps to regulate mood, improve cognitive function, and supports healthy growth and immune function. Aim for 7-9 hours of sleep each night.',\n",
       "  'am': ' 1. የተመጣጠነ እና የተመጣጠነ ምግብ ይመገቡ፡- ምግብዎ የተለያዩ አትክልትና ፍራፍሬ፣ ዘንበል ያለ ፕሮቲን፣ ሙሉ እህል እና ጤናማ ቅባቶችን ያካተተ መሆኑን ያረጋግጡ። ይህ ለሰውነትዎ አስፈላጊ የሆኑትን ንጥረ ነገሮች በተሻለ ሁኔታ እንዲሰራ እና ሥር የሰደደ በሽታዎችን ለመከላከል ይረዳል. 2. መደበኛ የአካል ብቃት እንቅስቃሴ ማድረግ፡ የአካል ብቃት እንቅስቃሴ ለአጥንቶች፣ ጡንቻዎች እና የልብና የደም ሥር (cardiovascular) ጤንነት ለመጠበቅ ወሳኝ ነው። ቢያንስ ለ150 ደቂቃ መጠነኛ የኤሮቢክ የአካል ብቃት እንቅስቃሴ ወይም 75 ደቂቃ የጠንካራ የአካል ብቃት እንቅስቃሴን በየሣምንት አቅርብ። 3. በቂ እንቅልፍ መተኛት፡- ጥራት ያለው እንቅልፍ መተኛት ለአካልና አእምሮአዊ ደህንነት ወሳኝ ነው። ስሜትን ለመቆጣጠር, የእውቀት (ኮግኒቲቭ) ተግባርን ለማሻሻል እና ጤናማ እድገትን እና የበሽታ መከላከያ ተግባራትን ይደግፋል. በእያንዳንዱ ሌሊት ለ 7-9 ሰአታት እንቅልፍ ያጥፉ።'},\n",
       " {'en': 'What are the three primary colors?', 'am': ' ሦስቱ ዋና ቀለሞች ምንድ ናቸው?'}]"
      ]
     },
     "execution_count": 4,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "sentence_pairs[:3]"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 5,
   "id": "0ddd4c34",
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Using device: cuda\n"
     ]
    }
   ],
   "source": [
    "device = torch.device('cuda' if torch.cuda.is_available() else 'cpu')\n",
    "print(f\"Using device: {device}\")"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 6,
   "id": "1a2a7cf3",
   "metadata": {},
   "outputs": [],
   "source": [
    "class BPETokenizer:\n",
    "    def __init__(self, vocab_size=4000):\n",
    "        self.vocab_size = vocab_size\n",
    "        self.vocab = [\"<|endoftext|>\"]\n",
    "        self.word_freqs = defaultdict(int)\n",
    "        self.merges = {}\n",
    "        self.tokenizer = AutoTokenizer.from_pretrained(\"gpt2\")\n",
    "\n",
    "    def compute_pair_freqs(self,splits):\n",
    "        pair_freqs = defaultdict(int)\n",
    "        for word, freq in self.word_freqs.items():\n",
    "            split = splits[word]\n",
    "            if len(split) == 1:\n",
    "                continue\n",
    "            for i in range(len(split) - 1):\n",
    "                pair = (split[i], split[i + 1])\n",
    "                pair_freqs[pair] += freq\n",
    "        return pair_freqs\n",
    "    \n",
    "    def merge_pair(self,a, b, splits):\n",
    "        for word in self.word_freqs:\n",
    "            split = splits[word]\n",
    "            if len(split) == 1:\n",
    "                continue\n",
    "\n",
    "            i = 0\n",
    "            while i < len(split) - 1:\n",
    "                if split[i] == a and split[i + 1] == b:\n",
    "                    split = split[:i] + [a + b] + split[i + 2 :]\n",
    "                else:\n",
    "                    i += 1\n",
    "            splits[word] = split\n",
    "        return splits\n",
    "\n",
    "    def build_vocab(self, corpus):\n",
    "        for text in corpus:\n",
    "            self.tokenizer.backend_tokenizer.pre_tokenizer = Whitespace()\n",
    "            text= ' Ġ'.join(text.split())\n",
    "            words_with_offsets = self.tokenizer.backend_tokenizer.pre_tokenizer.pre_tokenize_str(text)\n",
    "            new_words = [word for word, offset in words_with_offsets]\n",
    "            for word in new_words:\n",
    "                self.word_freqs[word] += 1\n",
    "\n",
    "        alphabet = []\n",
    "\n",
    "        for word in self.word_freqs.keys():\n",
    "            for letter in word:\n",
    "                if letter not in alphabet:\n",
    "                    alphabet.append(letter)\n",
    "        alphabet.sort()\n",
    "\n",
    "\n",
    "        # Add every unique character to the vocab\n",
    "        for char in alphabet:\n",
    "            if char not in self.vocab:\n",
    "                self.vocab.append(char)\n",
    "\n",
    "        splits = {word: [c for c in word] for word in self.word_freqs.keys()}\n",
    "\n",
    "        while len(self.vocab) < self.vocab_size:\n",
    "            pair_freqs = self.compute_pair_freqs(splits)\n",
    "            best_pair = \"\"\n",
    "            max_freq = None\n",
    "            for pair, freq in pair_freqs.items():\n",
    "                if max_freq is None or max_freq < freq:\n",
    "                    best_pair = pair\n",
    "                    max_freq = freq\n",
    "            if len(best_pair) == 2:\n",
    "                splits = self.merge_pair(best_pair[0],best_pair[1], splits)\n",
    "                self.merges[best_pair] = best_pair[0] + best_pair[1]\n",
    "                self.vocab.append(best_pair[0] + best_pair[1])\n",
    "            else:\n",
    "                break\n",
    "\n",
    "\n",
    "    def tokenize(self,text):\n",
    "        self.tokenizer.backend_tokenizer.pre_tokenizer = Whitespace()\n",
    "        pre_tokenize_result = self.tokenizer._tokenizer.pre_tokenizer.pre_tokenize_str(text)\n",
    "        pre_tokenized_text = [word for word, offset in pre_tokenize_result]\n",
    "        splits = [[l for l in word] for word in pre_tokenized_text]\n",
    "\n",
    "\n",
    "        for word in pre_tokenized_text:\n",
    "            for char in word:\n",
    "                if char not in self.vocab:\n",
    "                    self.vocab.append(char)  \n",
    "\n",
    "        for pair, merge in self.merges.items():\n",
    "            for idx, split in enumerate(splits):\n",
    "                i = 0\n",
    "                while i < len(split) - 1:\n",
    "                    if split[i] == pair[0] and split[i + 1] == pair[1]:\n",
    "                        split = split[:i] + [merge] + split[i + 2 :]\n",
    "                    else:\n",
    "                        i += 1\n",
    "                splits[idx] = split\n",
    "\n",
    "        return sum(splits, [])\n",
    "\n",
    "    def save(self, file_path):\n",
    "        \"\"\"\n",
    "        Save the tokenizer's state to a file.\n",
    "        \"\"\"\n",
    "        state = {\n",
    "            'vocab_size': self.vocab_size,\n",
    "            'vocab': self.vocab,\n",
    "            'word_freqs': dict(self.word_freqs),\n",
    "            'merges': self.merges\n",
    "        }\n",
    "        with open(file_path, 'wb') as f:\n",
    "            pickle.dump(state, f)\n",
    "\n",
    "    @classmethod\n",
    "    def load(cls, file_path):\n",
    "        \"\"\"\n",
    "        Load a tokenizer's state from a file.\n",
    "        \"\"\"\n",
    "        with open(file_path, 'rb') as f:\n",
    "            state = pickle.load(f)\n",
    "        \n",
    "        tokenizer = cls(vocab_size=state['vocab_size'])\n",
    "        tokenizer.vocab = state['vocab']\n",
    "        tokenizer.word_freqs = defaultdict(int, state['word_freqs'])\n",
    "        tokenizer.merges = state['merges']\n",
    "        return tokenizer"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 7,
   "id": "f4a4b0fc",
   "metadata": {},
   "outputs": [],
   "source": [
    "tokenizer_file = \"tokenizer.pkl\"\n",
    "\n",
    "def encode(text):\n",
    "    # Step 1: Encode, decode, and normalize the text\n",
    "    text = text.encode('utf-8').decode('utf-8').lower()\n",
    "    text = 'Ġ'.join(text.split())\n",
    "\n",
    "    # Step 2: Load tokenizer\n",
    "    tokenizer_instance = BPETokenizer.load(tokenizer_file)\n",
    "\n",
    "    # Step 3: Create a dictionary for vocabulary for O(1) lookups\n",
    "    vocab_dict = {token: idx for idx, token in enumerate(tokenizer_instance.vocab)}\n",
    "\n",
    "    # Step 4: Tokenize the text\n",
    "    tokens = tokenizer_instance.tokenize(text)\n",
    "\n",
    "    # Step 5: Generate token IDs efficiently\n",
    "    unknown_token_id = len(tokenizer_instance.vocab) \n",
    "    token_ids = [vocab_dict.get(t, unknown_token_id) for t in tokens]\n",
    "\n",
    "    return token_ids\n",
    "\n",
    "def decode(token_ids):\n",
    "    tokenizer_instance = BPETokenizer.load(tokenizer_file)\n",
    "    tokens = []\n",
    "    for id in token_ids:\n",
    "        if 0 <= id < len(tokenizer_instance.vocab):\n",
    "            tokens.append(tokenizer_instance.vocab[id])\n",
    "        else:\n",
    "            # Handle out-of-vocabulary token IDs\n",
    "            tokens.append('<UNK>')\n",
    "    decoded_string = ''.join(tokens)\n",
    "    decoded_string = decoded_string.replace('Ġ', ' ').strip()\n",
    "    return decoded_string"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 8,
   "id": "47b7e47f",
   "metadata": {},
   "outputs": [],
   "source": [
    "def tokenize_pair(pair, tokenizer):\n",
    "    \"\"\"\n",
    "    Tokenize a pair of English and Amharic sentences using custom tokenizers.\n",
    "    \n",
    "    Args:\n",
    "        pair (dict): A dictionary with 'en' and 'am' keys for English and Amharic sentences.\n",
    "        tokenizer (function): Custom English + Amharic tokenizer function.\n",
    "\n",
    "    Returns:\n",
    "        dict: Tokenized inputs for both languages.\n",
    "    \"\"\"\n",
    "    en_tokens = tokenizer(pair['en'])\n",
    "    am_tokens = tokenizer(pair['am'])\n",
    "    \n",
    "    return {\n",
    "        'en_input_ids': torch.tensor(en_tokens, dtype=torch.long),\n",
    "        'am_input_ids': torch.tensor(am_tokens, dtype=torch.long),\n",
    "    }\n",
    "\n",
    "# Preprocess data\n",
    "tokenized_data = [tokenize_pair(pair, encode) for pair in sentence_pairs]"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 9,
   "id": "2499913c",
   "metadata": {},
   "outputs": [
    {
     "name": "stderr",
     "output_type": "stream",
     "text": [
      "/tmp/ipykernel_451450/2173940672.py:12: UserWarning: To copy construct from a tensor, it is recommended to use sourceTensor.clone().detach() or sourceTensor.clone().detach().requires_grad_(True), rather than torch.tensor(sourceTensor).\n",
      "  'en_input_ids': torch.tensor(pad_sequence(pair['en_input_ids'], max_length)),\n",
      "/tmp/ipykernel_451450/2173940672.py:13: UserWarning: To copy construct from a tensor, it is recommended to use sourceTensor.clone().detach() or sourceTensor.clone().detach().requires_grad_(True), rather than torch.tensor(sourceTensor).\n",
      "  'am_input_ids': torch.tensor(pad_sequence(pair['am_input_ids'], max_length)),\n"
     ]
    }
   ],
   "source": [
    "def pad_sequence(seq, max_length, pad_token=0):\n",
    "    if len(seq) < max_length:\n",
    "        padding = torch.full((max_length - len(seq),), pad_token, dtype=seq.dtype)\n",
    "        return torch.cat((seq, padding))\n",
    "    else:\n",
    "        return seq[:max_length]\n",
    "\n",
    "\n",
    "max_length = 64  # Example max length\n",
    "tokenized_data2 = [\n",
    "    {\n",
    "        'en_input_ids': torch.tensor(pad_sequence(pair['en_input_ids'], max_length)),\n",
    "        'am_input_ids': torch.tensor(pad_sequence(pair['am_input_ids'], max_length)),\n",
    "    }\n",
    "    for pair in tokenized_data\n",
    "]\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 10,
   "id": "d05b0318",
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "[{'en_input_ids': tensor([  14,  741,  430,  333,   12,  325,   16,   23,   26,  523,  493,  653,\n",
       "           371,  519,  394,  839,   32, 1001,    0,    0,    0,    0,    0,    0,\n",
       "             0,    0,    0,    0,    0,    0,    0,    0,    0,    0,    0,    0,\n",
       "             0,    0,    0,    0,    0,    0,    0,    0,    0,    0,    0,    0,\n",
       "             0,    0,    0,    0,    0,    0,    0,    0,    0,    0,    0,    0,\n",
       "             0,    0,    0,    0]),\n",
       "  'am_input_ids': tensor([ 284,  210,  134,  695,  842,   36,  157,  156,  193,  648,  231,  149,\n",
       "           506,  442,  156,  281, 1001,    0,    0,    0,    0,    0,    0,    0,\n",
       "             0,    0,    0,    0,    0,    0,    0,    0,    0,    0,    0,    0,\n",
       "             0,    0,    0,    0,    0,    0,    0,    0,    0,    0,    0,    0,\n",
       "             0,    0,    0,    0,    0,    0,    0,    0,    0,    0,    0,    0,\n",
       "             0,    0,    0,    0])},\n",
       " {'en_input_ids': tensor([1011, 1011,  416,  352,  328,  375,  394,  387,   10,  415,  370,  440,\n",
       "           488,   25,  374,   16,  771,  389,   16,  478, 1011,  705,  581,  680,\n",
       "           333,  591,   25,  749,  966,  597,  755,   19,  423,  741,  399,  328,\n",
       "           592,  395,   16,  478,   32,  399,  382,   25,   28,  374,   26,  370,\n",
       "           592,   12,   14,  478,    8,    9,   19,  365, 1011,  735,  387,  475,\n",
       "           886,  332, 1011,  689]),\n",
       "  'am_input_ids': tensor([1012, 1012,  683,  131,  283,  280,  207,  426,  683,  131,  283,  280,\n",
       "           207,  648,  276,  179,  442,  131,  272,  175,  316, 1012,  648,  276,\n",
       "           179,  239,  683,  119,  254,  253,  410,  193,  231,  124,  193,  210,\n",
       "            36,  307,  146,  307,  147,  318,   36,  243,  212,  174,  124,  509,\n",
       "           119,   36,  314,  149,  190,  212,  318,   36,  132,  120,  366,  117,\n",
       "           124,  426,   36,  284])},\n",
       " {'en_input_ids': tensor([  30,   15,  352,  597,  336,  430,  333,   12,  368,   25,  461,  395,\n",
       "            32,  341,  505,  384,   26, 1001,    0,    0,    0,    0,    0,    0,\n",
       "             0,    0,    0,    0,    0,    0,    0,    0,    0,    0,    0,    0,\n",
       "             0,    0,    0,    0,    0,    0,    0,    0,    0,    0,    0,    0,\n",
       "             0,    0,    0,    0,    0,    0,    0,    0,    0,    0,    0,    0,\n",
       "             0,    0,    0,    0]),\n",
       "  'am_input_ids': tensor([ 142,  156,  189,   36,  236,  210,  843,  119,  137,  201,  648,  212,\n",
       "           262,   36,  210,  599, 1001,    0,    0,    0,    0,    0,    0,    0,\n",
       "             0,    0,    0,    0,    0,    0,    0,    0,    0,    0,    0,    0,\n",
       "             0,    0,    0,    0,    0,    0,    0,    0,    0,    0,    0,    0,\n",
       "             0,    0,    0,    0,    0,    0,    0,    0,    0,    0,    0,    0,\n",
       "             0,    0,    0,    0])}]"
      ]
     },
     "execution_count": 10,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "tokenized_data2[:3]"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "d9d3a8c4",
   "metadata": {},
   "source": [
    "# Creating the LSTM Model:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 11,
   "id": "b81924aa",
   "metadata": {},
   "outputs": [],
   "source": [
    "# Define model\n",
    "class LSTM_model(nn.Module):\n",
    "    def __init__(self, input_dim, embedding_dim, hidden_dim, output_dim):\n",
    "        super(LSTM_model, self).__init__()\n",
    "        self.encoder_embedding = nn.Embedding(input_dim, embedding_dim)\n",
    "        self.encoder_lstm = nn.LSTM(embedding_dim, hidden_dim, batch_first=True)\n",
    "        self.decoder_embedding = nn.Embedding(output_dim, embedding_dim)\n",
    "        self.decoder_lstm = nn.LSTM(embedding_dim, hidden_dim, batch_first=True)\n",
    "        self.fc_out = nn.Linear(hidden_dim, output_dim)\n",
    "\n",
    "    def forward(self, en_input, am_input):\n",
    "        en_embedded = self.encoder_embedding(en_input)\n",
    "        _, (hidden, cell) = self.encoder_lstm(en_embedded)\n",
    "        am_embedded = self.decoder_embedding(am_input)\n",
    "        decoder_output, _ = self.decoder_lstm(am_embedded, (hidden, cell))\n",
    "        output = self.fc_out(decoder_output)\n",
    "        return output\n",
    "\n",
    "# Define dataset and dataloader\n",
    "class TranslationDataset(Dataset):\n",
    "    def __init__(self, data):\n",
    "        self.data = data\n",
    "\n",
    "    def __len__(self):\n",
    "        return len(self.data)\n",
    "\n",
    "    def __getitem__(self, idx):\n",
    "        return self.data[idx]\n",
    "\n",
    "dataset = TranslationDataset(tokenized_data2)\n",
    "dataloader = DataLoader(dataset, batch_size=32, shuffle=True)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 12,
   "id": "66ffd042",
   "metadata": {},
   "outputs": [],
   "source": [
    "# Initialize model, loss, and optimizer\n",
    "input_dim = BPETokenizer().vocab_size #len(en_tokenizer)\n",
    "output_dim = BPETokenizer().vocab_size #len(am_tokenizer)\n",
    "embedding_dim = 256\n",
    "hidden_dim = 512\n",
    "\n",
    "device = torch.device('cuda' if torch.cuda.is_available() else 'cpu')\n",
    "model = LSTM_model(input_dim, embedding_dim, hidden_dim, output_dim).to(device)\n",
    "loss_fn = nn.CrossEntropyLoss(ignore_index=0)  # Ignore padding tokens\n",
    "optimizer = torch.optim.Adam(model.parameters())"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "1ca0c7b7",
   "metadata": {},
   "source": [
    "## Training the Model:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 13,
   "id": "14f466a9",
   "metadata": {},
   "outputs": [
    {
     "name": "stderr",
     "output_type": "stream",
     "text": [
      "/tmp/ipykernel_451450/2781125501.py:22: FutureWarning: `torch.cuda.amp.GradScaler(args...)` is deprecated. Please use `torch.amp.GradScaler('cuda', args...)` instead.\n",
      "  scaler = torch.cuda.amp.GradScaler()\n",
      "/tmp/ipykernel_451450/2781125501.py:43: FutureWarning: `torch.cuda.amp.autocast(args...)` is deprecated. Please use `torch.amp.autocast('cuda', args...)` instead.\n",
      "  with torch.cuda.amp.autocast():\n"
     ]
    },
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Epoch 1/5, Loss: 8.1577, BLEU: 30.5696, CHRF: 23.1772\n",
      "Epoch 2/5, Loss: 7.7313, BLEU: 0.0000, CHRF: 5.2237\n",
      "Epoch 3/5, Loss: 6.1164, BLEU: 0.0000, CHRF: 2.3915\n",
      "Epoch 4/5, Loss: 5.9061, BLEU: 3.0982, CHRF: 2.1938\n",
      "Epoch 5/5, Loss: 4.6787, BLEU: 21.8002, CHRF: 11.3951\n"
     ]
    }
   ],
   "source": [
    "import torch\n",
    "import os\n",
    "import csv\n",
    "import sacrebleu\n",
    "import random\n",
    "\n",
    "# Training setup\n",
    "epochs = 5\n",
    "early_stopping_patience = 5\n",
    "best_loss = float('inf')\n",
    "patience_counter = 0\n",
    "save_dir = \"./BPE_Q1_checkpoints\"\n",
    "os.makedirs(save_dir, exist_ok=True)\n",
    "results_file = \"training_results.csv\"\n",
    "\n",
    "# Save training metadata (only once at the beginning)\n",
    "with open(results_file, mode='w', newline='') as file:\n",
    "    writer = csv.writer(file)\n",
    "    writer.writerow([\"Epoch\", \"Train Loss\", \"BLEU\", \"CHRF\"])\n",
    "\n",
    "# Mixed Precision Setup (if using compatible hardware)\n",
    "scaler = torch.cuda.amp.GradScaler()\n",
    "\n",
    "# Training loop\n",
    "losses, bleu_scores, chrf_scores = [], [], []\n",
    "for epoch in range(epochs):\n",
    "    model.train()\n",
    "    total_loss = 0\n",
    "    for batch in dataloader:\n",
    "        # Randomly select direction (0 for English to Amharic, 1 for Amharic to English)\n",
    "        direction = random.choice([0, 1])\n",
    "\n",
    "        if direction == 0:  # English to Amharic\n",
    "            en_input = batch['en_input_ids'].to(device)\n",
    "            am_input = batch['am_input_ids'].to(device)\n",
    "        else:  # Amharic to English\n",
    "            en_input = batch['am_input_ids'].to(device)  # Reverse the input\n",
    "            am_input = batch['en_input_ids'].to(device)  # Reverse the target\n",
    "\n",
    "        optimizer.zero_grad()\n",
    "\n",
    "        # Mixed Precision Forward and Backward Pass\n",
    "        with torch.cuda.amp.autocast():\n",
    "            output = model(en_input, am_input)\n",
    "            loss = loss_fn(output.view(-1, output_dim), am_input.view(-1))\n",
    "\n",
    "        # Backward pass with scaler\n",
    "        scaler.scale(loss).backward()\n",
    "        scaler.step(optimizer)\n",
    "        scaler.update()\n",
    "\n",
    "        total_loss += loss.item()\n",
    "\n",
    "    avg_loss = total_loss / len(dataloader)\n",
    "    losses.append(avg_loss)\n",
    "\n",
    "    # Validation metrics (only at the end of the epoch)\n",
    "    model.eval()\n",
    "    with torch.no_grad():\n",
    "        references, hypotheses = [], []\n",
    "        for batch in dataloader:\n",
    "            # Alternate between English-to-Amharic and Amharic-to-English for validation\n",
    "            direction = random.choice([0, 1])\n",
    "            \n",
    "            if direction == 0:  # English to Amharic\n",
    "                en_input = batch['en_input_ids'].to(device)\n",
    "                am_input = batch['am_input_ids'].to(device)\n",
    "            else:  # Amharic to English\n",
    "                en_input = batch['am_input_ids'].to(device)\n",
    "                am_input = batch['en_input_ids'].to(device)\n",
    "\n",
    "            output = model(en_input, am_input)\n",
    "            predicted = output.argmax(dim=-1).cpu().tolist()\n",
    "\n",
    "            references.extend(batch['am_input_ids'].tolist())\n",
    "            hypotheses.extend(predicted)\n",
    "\n",
    "        # Decode for BLEU/CHRF scoring (after full batch)\n",
    "        references = [[decode(ref)] for ref in references]\n",
    "        hypotheses = [decode(hyp) for hyp in hypotheses]\n",
    "\n",
    "        bleu = sacrebleu.corpus_bleu(hypotheses, references).score\n",
    "        chrf = sacrebleu.corpus_chrf(hypotheses, references).score\n",
    "        bleu_scores.append(bleu)\n",
    "        chrf_scores.append(chrf)\n",
    "\n",
    "    # Log results (only at the end of the epoch)\n",
    "    print(f\"Epoch {epoch + 1}/{epochs}, Loss: {avg_loss:.4f}, BLEU: {bleu:.4f}, CHRF: {chrf:.4f}\")\n",
    "    with open(results_file, mode='a', newline='') as file:\n",
    "        writer = csv.writer(file)\n",
    "        writer.writerow([epoch + 1, avg_loss, bleu, chrf])\n",
    "\n",
    "    # Save model if it improves\n",
    "    if avg_loss < best_loss:\n",
    "        best_loss = avg_loss\n",
    "        torch.save(model.state_dict(), os.path.join(save_dir, f\"model_epoch_{epoch + 1}.pt\"))\n",
    "        patience_counter = 0\n",
    "    else:\n",
    "        patience_counter += 1\n",
    "        if patience_counter >= early_stopping_patience:\n",
    "            print(\"Early stopping triggered.\")\n",
    "            break\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 14,
   "id": "9269057f",
   "metadata": {},
   "outputs": [
    {
     "data": {
      "image/png": "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\n",
      "text/plain": [
       "<Figure size 864x432 with 1 Axes>"
      ]
     },
     "metadata": {
      "needs_background": "light"
     },
     "output_type": "display_data"
    }
   ],
   "source": [
    "# Plot training results\n",
    "plt.figure(figsize=(12, 6))\n",
    "plt.plot(range(1, len(losses) + 1), losses, label=\"Loss\")\n",
    "plt.xlabel(\"Epoch\")\n",
    "plt.ylabel(\"Score\")\n",
    "plt.title(\"Training Progress\")\n",
    "plt.legend()\n",
    "plt.show()"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 15,
   "id": "a92c6c5e",
   "metadata": {},
   "outputs": [
    {
     "data": {
      "image/png": "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\n",
      "text/plain": [
       "<Figure size 864x432 with 1 Axes>"
      ]
     },
     "metadata": {
      "needs_background": "light"
     },
     "output_type": "display_data"
    }
   ],
   "source": [
    "# Plot training results\n",
    "plt.figure(figsize=(12, 6))\n",
    "plt.plot(range(1, len(bleu_scores) + 1), bleu_scores, label=\"BLEU\")\n",
    "plt.xlabel(\"Epoch\")\n",
    "plt.ylabel(\"Score\")\n",
    "plt.title(\"Training Progress\")\n",
    "plt.legend()\n",
    "plt.show()"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 16,
   "id": "1bb162ea",
   "metadata": {},
   "outputs": [
    {
     "data": {
      "image/png": "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\n",
      "text/plain": [
       "<Figure size 864x432 with 1 Axes>"
      ]
     },
     "metadata": {
      "needs_background": "light"
     },
     "output_type": "display_data"
    }
   ],
   "source": [
    "# Plot training results\n",
    "plt.figure(figsize=(12, 6))\n",
    "plt.plot(range(1, len(chrf_scores) + 1), chrf_scores, label=\"CHRF\")\n",
    "plt.xlabel(\"Epoch\")\n",
    "plt.ylabel(\"Score\")\n",
    "plt.title(\"Training Progress\")\n",
    "plt.legend()\n",
    "plt.show()"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 18,
   "id": "abad1ab4",
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Training data saved to training_results_LSTM2.csv\n"
     ]
    }
   ],
   "source": [
    "import pandas as pd\n",
    "training_data = {\n",
    "    \"Epoch\": list(range(1, len(losses) + 1)),\n",
    "    \"Loss\": losses,\n",
    "    \"BLEU\": bleu_scores,\n",
    "    \"CHRF\": chrf_scores\n",
    "}\n",
    "\n",
    "# Convert the dictionary to a DataFrame\n",
    "df = pd.DataFrame(training_data)\n",
    "\n",
    "# Save the DataFrame to a CSV file\n",
    "csv_file = \"training_results_LSTM.csv\"\n",
    "df.to_csv(csv_file, index=False)\n",
    "print(f\"Training data saved to {csv_file}\")"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 19,
   "id": "827d1fa9",
   "metadata": {},
   "outputs": [],
   "source": [
    "def translate_sentence(model, sentence, tokenizer, max_length=64, pad_token=0, sos_token=1, eos_token=2):\n",
    "    \"\"\"\n",
    "    Translate a sentence using the given LSTM model and custom tokenizer.\n",
    "\n",
    "    Args:\n",
    "    - model: The trained translation model.\n",
    "    - sentence: The input sentence to translate.\n",
    "    - tokenizer: The custom tokenizer (e.g., BPETokenizer).\n",
    "    - max_length: The maximum sequence length (default: 64).\n",
    "    - pad_token: The padding token ID (default: 0).\n",
    "    - sos_token: The start-of-sequence token ID (default: 1).\n",
    "    - eos_token: The end-of-sequence token ID (default: 2).\n",
    "\n",
    "    Returns:\n",
    "    - translation: The translated sentence as a string.\n",
    "    \"\"\"\n",
    "    model.eval()\n",
    "    with torch.no_grad():\n",
    "        # Tokenize the input sentence\n",
    "        token_ids = encode(sentence)\n",
    "\n",
    "        # Debugging: Check the tokenized output\n",
    "        print(f\"Token IDs: {token_ids}\")\n",
    "\n",
    "        # Pad the tokenized sequence to the max_length\n",
    "        token_ids = token_ids[:max_length]  # Truncate if necessary\n",
    "        padding_length = max_length - len(token_ids)\n",
    "        token_ids += [pad_token] * padding_length  # Pad to the max_length\n",
    "        \n",
    "        # Debugging: Check the padded sequence\n",
    "        print(f\"Token IDs after padding: {token_ids}\")\n",
    "\n",
    "        # Convert token IDs to tensor and move to the correct device\n",
    "        input_tensor = torch.tensor(token_ids, dtype=torch.long).unsqueeze(0).to(device)  # Add batch dimension\n",
    "\n",
    "        # Initialize the target sequence with the start-of-sequence token\n",
    "        target_ids = [sos_token]\n",
    "        target_tensor = torch.tensor(target_ids, dtype=torch.long).unsqueeze(0).to(device)  # Shape: (1, 1)\n",
    "\n",
    "        # Decode the sequence using greedy decoding\n",
    "        for _ in range(max_length):\n",
    "            # Forward pass\n",
    "            output = model(input_tensor, target_tensor)  # Shape: (1, target_len, vocab_size)\n",
    "            next_token_logits = output[:, -1, :]  # Get the logits of the last generated token\n",
    "            next_token_id = next_token_logits.argmax(dim=-1).item()  # Choose the token with the highest probability\n",
    "            \n",
    "            # Append the predicted token to the target sequence\n",
    "            target_ids.append(next_token_id)\n",
    "            target_tensor = torch.tensor(target_ids, dtype=torch.long).unsqueeze(0).to(device)\n",
    "            \n",
    "            # Stop decoding if the end-of-sequence token is generated\n",
    "            if next_token_id == eos_token:\n",
    "                break\n",
    "\n",
    "        # Decode the token IDs back to text\n",
    "        translation = decode(target_ids[1:])  # Exclude the start-of-sequence token\n",
    "        return translation"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "8f7d1786",
   "metadata": {},
   "outputs": [],
   "source": [
    "# Example usage\n",
    "example_sentence = \"What are the three primary colors?\"\n",
    "translation = translate_sentence(model, example_sentence, BPETokenizer.load(tokenizer_file))\n",
    "print(f\"English: {example_sentence}\")\n",
    "print(f\"Amharic Translation: {translation}\")"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "1400c072",
   "metadata": {},
   "outputs": [],
   "source": [
    "# Example usage\n",
    "example_sentence = \"ጤናማ ለመሆን ሶስት ምክሮችን ይስጡ.\"\n",
    "translation = translate_sentence(model, example_sentence, BPETokenizer.load(tokenizer_file))\n",
    "print(f\"Amharic: {example_sentence}\")\n",
    "print(f\"English Translation: {translation}\")"
   ]
  }
 ],
 "metadata": {
  "kernelspec": {
   "display_name": "Python 3 (ipykernel)",
   "language": "python",
   "name": "python3"
  },
  "language_info": {
   "codemirror_mode": {
    "name": "ipython",
    "version": 3
   },
   "file_extension": ".py",
   "mimetype": "text/x-python",
   "name": "python",
   "nbconvert_exporter": "python",
   "pygments_lexer": "ipython3",
   "version": "3.10.12"
  }
 },
 "nbformat": 4,
 "nbformat_minor": 5
}