Skip to article frontmatterSkip to article content
Site not loading correctly?

This may be due to an incorrect BASE_URL configuration. See the MyST Documentation for reference.

Bigram language models

Original study notes by Satish Bajagain, following Andrej Karpathy’s Zero to Hero. Comments, intermediate attempts and saved outputs are retained. Section titles are navigation additions.

Characters and bigrams

# makemore is a character-level language model
# so it models the given sequence of characters, e.g. one example is given as: alyssa, there are 32k such examples
# then it can predict the next character in the sequence
# we will go from character-level(bigram), to word-level(bag of words), mlp, rnn, gru, finally modern transformers(on which gpt-2 is based on)
# first, let's load up the dataset "names.txt"
# open("names.txt", "r").readlines()[5:20]
# .read(5) reads the first 5 characters, not the first 5 names
['emma', 'olivia', 'ava', 'isabella', 'sophia', 'charlotte', 'mia', 'amelia', 'harper', 'evelyn', 'abigail', 'emily', 'elizabeth', 'mila', 'ella', 'avery', 'sofia', 'camila', 'aria', 'scarlett']
32033
2
an
15
muhammadibrahim
# a character level language model is predicting the next character in a sequence given already some concrete sequence of characters before it
# for eg. if there is a word named isabella, it says that the character 'i' is very likely to occur first in the new name
# the character 's' is likely to come after i
# the character 'a' is likely to come after is, and so on
# and the last piece of information: after the sequence becomes 'isabella', the word is likely to end
# in a bigram language model, we are always working with only 2 characters at a time
# we are looking only at the one character we are given, and are trying to predict the next character in the sequence
# eg. what characters are likely to follow 'r', or likely to follow 'a', and so on.
# we are just modeling this little structure
# atleast for now, we are ignoring the fact that we have more information through the entire word
# HERE: WE ARE JUST LOOKING AT THE PREVIOUS CHARACTER TO PREDICT THE NEXT ONE
# and this is a very simple and weak language model
# now, let's look at what bigrams in our dataset look like
# bigrams: just 2 characters in a row
emma
['emma']
e m
m m
m a
# continue from 8:14

# so, e is likely to come first, and a is likely to come last
# we are going to create a special array of characters, and we're going to hallucinate a special start token here, then the word, then the special end
<S> e
e m
m m
m a
a <E>
<S> e
e m
m m
m a
a <E>
<S> o
o l
l i
i v
v i
i a
a <E>
<S> a
a v
v a
a <E>
# TO LEARN THE STATISTICS ABOUT WHICH CHARACTERS ARE MORE LIKELY TO FOLLOW OTHER CHARACTERS,
# THE SIMPLEST WAY IN THE BIGRAM LANGUAGE MODELS IS TO SIMPLY DO IT BY COUNTING
# SO WE'RE JUST GOING TO COUNT HOW OFTEN ANY ONE OF THESE COMBINATIONS OCCURS IN THE TRAINING SET
# so we need a dictionary to maintain some counts for every one of these bigrams, let's use a dictionary b
# bigram is a tuple of character 1, character 2
<S> e
e m
m m
m a
a <E>
<S> o
o l
l i
i v
v i
i a
a <E>
<S> a
a v
v a
a <E>
{('<S>', 'e'): 1, ('e', 'm'): 1, ('m', 'm'): 1, ('m', 'a'): 1, ('a', '<E>'): 3, ('<S>', 'o'): 1, ('o', 'l'): 1, ('l', 'i'): 1, ('i', 'v'): 1, ('v', 'i'): 1, ('i', 'a'): 1, ('<S>', 'a'): 1, ('a', 'v'): 1, ('v', 'a'): 1}
# note that for these 3 words, a was always the ending character everytime so the count is 3 which is what we want to see
{('<S>', 'e'): 1531, ('e', 'm'): 769, ('m', 'm'): 168, ('m', 'a'): 2590, ('a', '<E>'): 6640, ('<S>', 'o'): 394, ('o', 'l'): 619, ('l', 'i'): 2480, ('i', 'v'): 269, ('v', 'i'): 911, ('i', 'a'): 2445, ('<S>', 'a'): 4410, ('a', 'v'): 834, ('v', 'a'): 642, ('<S>', 'i'): 591, ('i', 's'): 1316, ('s', 'a'): 1201, ('a', 'b'): 541, ('b', 'e'): 655, ('e', 'l'): 3248, ('l', 'l'): 1345, ('l', 'a'): 2623, ('<S>', 's'): 2055, ('s', 'o'): 531, ('o', 'p'): 95, ('p', 'h'): 204, ('h', 'i'): 729, ('<S>', 'c'): 1542, ('c', 'h'): 664, ('h', 'a'): 2244, ('a', 'r'): 3264, ('r', 'l'): 413, ('l', 'o'): 692, ('o', 't'): 118, ('t', 't'): 374, ('t', 'e'): 716, ('e', '<E>'): 3983, ('<S>', 'm'): 2538, ('m', 'i'): 1256, ('a', 'm'): 1634, ('m', 'e'): 818, ('<S>', 'h'): 874, ('r', 'p'): 14, ('p', 'e'): 197, ('e', 'r'): 1958, ('r', '<E>'): 1377, ('e', 'v'): 463, ('v', 'e'): 568, ('l', 'y'): 1588, ('y', 'n'): 1826, ('n', '<E>'): 6763, ('b', 'i'): 217, ('i', 'g'): 428, ('g', 'a'): 330, ('a', 'i'): 1650, ('i', 'l'): 1345, ('l', '<E>'): 1314, ('y', '<E>'): 2007, ('i', 'z'): 277, ('z', 'a'): 860, ('e', 't'): 580, ('t', 'h'): 647, ('h', '<E>'): 2409, ('r', 'y'): 773, ('o', 'f'): 34, ('f', 'i'): 160, ('c', 'a'): 815, ('r', 'i'): 3033, ('s', 'c'): 60, ('l', 'e'): 2921, ('t', '<E>'): 483, ('<S>', 'v'): 376, ('i', 'c'): 509, ('c', 't'): 35, ('t', 'o'): 667, ('o', 'r'): 1059, ('a', 'd'): 1042, ('d', 'i'): 674, ('o', 'n'): 2411, ('<S>', 'l'): 1572, ('l', 'u'): 324, ('u', 'n'): 275, ('n', 'a'): 2977, ('<S>', 'g'): 669, ('g', 'r'): 201, ('r', 'a'): 2356, ('a', 'c'): 470, ('c', 'e'): 551, ('h', 'l'): 185, ('o', 'e'): 132, ('<S>', 'p'): 515, ('e', 'n'): 2675, ('n', 'e'): 1359, ('a', 'y'): 2050, ('y', 'l'): 1104, ('<S>', 'r'): 1639, ('e', 'y'): 1070, ('<S>', 'z'): 929, ('z', 'o'): 110, ('<S>', 'n'): 1146, ('n', 'o'): 496, ('e', 'a'): 679, ('a', 'n'): 5438, ('n', 'n'): 1906, ('a', 'h'): 2332, ('d', 'd'): 149, ('a', 'u'): 381, ('u', 'b'): 103, ('b', 'r'): 842, ('r', 'e'): 1697, ('i', 'e'): 1653, ('s', 't'): 765, ('a', 't'): 687, ('t', 'a'): 1027, ('a', 'l'): 2528, ('a', 'z'): 435, ('z', 'e'): 373, ('i', 'o'): 588, ('u', 'r'): 414, ('r', 'o'): 869, ('u', 'd'): 136, ('d', 'r'): 424, ('<S>', 'b'): 1306, ('o', 'o'): 115, ('o', 'k'): 68, ('k', 'l'): 139, ('c', 'l'): 116, ('i', 'r'): 849, ('s', 'k'): 82, ('k', 'y'): 379, ('u', 'c'): 103, ('c', 'y'): 104, ('p', 'a'): 209, ('s', 'l'): 279, ('i', 'n'): 2126, ('o', 'v'): 176, ('g', 'e'): 334, ('e', 's'): 861, ('s', 'i'): 684, ('s', '<E>'): 1169, ('<S>', 'k'): 2963, ('k', 'e'): 895, ('e', 'd'): 384, ('d', 'y'): 317, ('n', 't'): 443, ('y', 'a'): 2143, ('<S>', 'w'): 307, ('w', 'i'): 148, ('o', 'w'): 114, ('w', '<E>'): 51, ('k', 'i'): 509, ('n', 's'): 278, ('a', 'o'): 63, ('o', 'm'): 261, ('i', '<E>'): 2489, ('a', 'a'): 556, ('i', 'y'): 779, ('d', 'e'): 1283, ('c', 'o'): 380, ('r', 'u'): 252, ('b', 'y'): 83, ('s', 'e'): 884, ('n', 'i'): 1725, ('i', 't'): 541, ('t', 'y'): 341, ('u', 't'): 82, ('t', 'u'): 78, ('u', 'm'): 154, ('m', 'n'): 20, ('g', 'i'): 190, ('t', 'i'): 532, ('<S>', 'q'): 92, ('q', 'u'): 206, ('u', 'i'): 121, ('a', 'e'): 692, ('e', 'h'): 152, ('v', 'y'): 121, ('p', 'i'): 61, ('i', 'p'): 53, ('y', 'd'): 272, ('e', 'x'): 132, ('x', 'a'): 103, ('<S>', 'j'): 2422, ('j', 'o'): 479, ('o', 's'): 504, ('e', 'p'): 83, ('j', 'u'): 202, ('u', 'l'): 301, ('<S>', 'd'): 1690, ('k', 'a'): 1731, ('e', 'e'): 1271, ('y', 't'): 104, ('d', 'l'): 60, ('c', 'k'): 316, ('n', 'z'): 145, ('z', 'i'): 364, ('a', 'g'): 168, ('d', 'a'): 1303, ('j', 'a'): 1473, ('h', 'e'): 674, ('<S>', 'x'): 134, ('x', 'i'): 102, ('i', 'm'): 427, ('e', 'i'): 818, ('<S>', 't'): 1308, ('<S>', 'f'): 417, ('f', 'a'): 242, ('n', 'd'): 704, ('r', 'g'): 76, ('a', 's'): 1118, ('s', 'h'): 1285, ('b', 'a'): 321, ('k', 'h'): 307, ('s', 'm'): 90, ('o', 'd'): 190, ('r', 's'): 190, ('g', 'h'): 360, ('s', 'y'): 215, ('y', 's'): 401, ('s', 's'): 461, ('e', 'c'): 153, ('c', 'i'): 271, ('m', 'o'): 452, ('r', 'k'): 90, ('n', 'l'): 195, ('d', 'n'): 31, ('r', 'd'): 187, ('o', 'i'): 69, ('t', 'r'): 352, ('m', 'b'): 112, ('r', 'm'): 162, ('n', 'y'): 465, ('d', 'o'): 378, ('o', 'a'): 149, ('o', 'c'): 114, ('m', 'y'): 287, ('s', 'u'): 185, ('m', 'c'): 51, ('p', 'r'): 151, ('o', 'u'): 275, ('r', 'n'): 140, ('w', 'a'): 280, ('e', 'b'): 121, ('c', 'c'): 42, ('a', 'w'): 161, ('w', 'y'): 73, ('y', 'e'): 301, ('e', 'o'): 269, ('a', 'k'): 568, ('n', 'g'): 273, ('k', 'o'): 344, ('b', 'l'): 103, ('h', 'o'): 287, ('e', 'g'): 125, ('f', 'r'): 114, ('s', 'p'): 51, ('l', 's'): 94, ('y', 'z'): 78, ('g', 'g'): 25, ('z', 'u'): 73, ('i', 'd'): 440, ('m', '<E>'): 516, ('o', 'g'): 44, ('j', 'e'): 440, ('g', 'n'): 27, ('y', 'r'): 291, ('c', '<E>'): 97, ('c', 'q'): 11, ('u', 'e'): 169, ('i', 'f'): 101, ('f', 'e'): 123, ('i', 'x'): 89, ('x', '<E>'): 164, ('o', 'y'): 103, ('g', 'o'): 83, ('g', 't'): 31, ('l', 't'): 77, ('g', 'w'): 26, ('w', 'e'): 149, ('l', 'd'): 138, ('a', 'p'): 82, ('h', 'n'): 138, ('t', 'l'): 134, ('m', 'r'): 97, ('n', 'c'): 213, ('l', 'b'): 52, ('i', 'k'): 445, ('<S>', 'y'): 535, ('t', 'z'): 105, ('h', 'r'): 204, ('j', 'i'): 119, ('h', 't'): 71, ('r', 'r'): 425, ('z', 'l'): 123, ('w', 'r'): 22, ('b', 'b'): 38, ('r', 't'): 208, ('l', 'v'): 72, ('e', 'j'): 55, ('o', 'h'): 171, ('u', 's'): 474, ('i', 'b'): 110, ('g', 'l'): 32, ('h', 'y'): 213, ('p', 'o'): 59, ('p', 'p'): 39, ('p', 'y'): 12, ('n', 'r'): 44, ('z', 'm'): 35, ('v', 'o'): 153, ('l', 'm'): 60, ('o', 'x'): 45, ('d', '<E>'): 516, ('i', 'u'): 109, ('v', '<E>'): 88, ('f', 'f'): 44, ('b', 'o'): 105, ('e', 'k'): 178, ('c', 'r'): 76, ('d', 'g'): 25, ('r', 'c'): 99, ('r', 'h'): 121, ('n', 'k'): 58, ('h', 'u'): 166, ('d', 's'): 29, ('a', 'x'): 182, ('y', 'c'): 115, ('e', 'w'): 50, ('v', 'k'): 3, ('z', 'h'): 43, ('w', 'h'): 23, ('t', 'n'): 22, ('x', 'l'): 39, ('g', 'u'): 85, ('u', 'a'): 163, ('u', 'p'): 16, ('u', 'g'): 47, ('d', 'u'): 92, ('l', 'c'): 25, ('r', 'b'): 41, ('a', 'q'): 60, ('b', '<E>'): 114, ('g', 'y'): 31, ('y', 'p'): 15, ('p', 't'): 17, ('e', 'z'): 181, ('z', 'r'): 32, ('f', 'l'): 20, ('o', '<E>'): 855, ('o', 'b'): 140, ('u', 'z'): 45, ('z', '<E>'): 160, ('i', 'q'): 52, ('y', 'v'): 106, ('n', 'v'): 55, ('d', 'h'): 118, ('g', 'd'): 19, ('t', 's'): 35, ('n', 'h'): 26, ('y', 'j'): 23, ('k', 'r'): 109, ('z', 'b'): 4, ('g', '<E>'): 108, ('a', 'j'): 175, ('r', 'j'): 25, ('m', 'p'): 38, ('p', 'b'): 2, ('y', 'o'): 271, ('z', 'y'): 147, ('p', 'l'): 16, ('l', 'k'): 24, ('i', 'j'): 76, ('x', 'e'): 36, ('y', 'u'): 141, ('l', 'n'): 14, ('u', 'x'): 34, ('i', 'h'): 95, ('w', 's'): 20, ('k', 's'): 95, ('m', 'u'): 139, ('y', 'k'): 86, ('e', 'f'): 82, ('k', '<E>'): 363, ('y', 'm'): 148, ('z', 'z'): 45, ('m', 'd'): 24, ('s', 'r'): 55, ('e', 'u'): 69, ('l', 'h'): 19, ('a', 'f'): 134, ('r', 'w'): 21, ('n', 'u'): 96, ('v', 'r'): 48, ('m', 's'): 35, ('<S>', 'u'): 78, ('f', 's'): 6, ('y', 'b'): 27, ('x', 'o'): 41, ('g', 's'): 30, ('x', 'y'): 30, ('w', 'n'): 58, ('j', 'h'): 45, ('f', 'n'): 4, ('n', 'j'): 44, ('r', 'v'): 80, ('n', 'm'): 19, ('t', 'c'): 17, ('s', 'w'): 24, ('k', 't'): 17, ('f', 't'): 18, ('x', 't'): 70, ('u', 'v'): 37, ('k', 'k'): 20, ('s', 'n'): 24, ('u', '<E>'): 155, ('j', 'r'): 11, ('y', 'x'): 28, ('h', 'm'): 117, ('e', 'q'): 14, ('u', 'o'): 10, ('f', '<E>'): 80, ('h', 'z'): 20, ('h', 'k'): 29, ('y', 'g'): 30, ('q', 'r'): 1, ('v', 'n'): 8, ('s', 'd'): 9, ('y', 'i'): 192, ('n', 'w'): 11, ('d', 'v'): 17, ('h', 'v'): 39, ('x', 'w'): 3, ('o', 'z'): 54, ('k', 'u'): 50, ('u', 'h'): 58, ('k', 'n'): 26, ('s', 'b'): 21, ('i', 'i'): 82, ('y', 'y'): 23, ('r', 'z'): 23, ('l', 'g'): 6, ('l', 'p'): 15, ('p', '<E>'): 33, ('b', 'u'): 45, ('f', 'u'): 10, ('b', 'h'): 41, ('f', 'y'): 14, ('u', 'w'): 86, ('x', 'u'): 5, ('q', '<E>'): 28, ('l', 'r'): 18, ('m', 'h'): 5, ('l', 'w'): 16, ('j', '<E>'): 71, ('s', 'v'): 14, ('m', 'l'): 5, ('n', 'f'): 11, ('u', 'j'): 14, ('f', 'o'): 60, ('j', 'l'): 9, ('t', 'g'): 2, ('j', 'm'): 5, ('v', 'v'): 7, ('p', 's'): 16, ('t', 'w'): 11, ('x', 'c'): 4, ('u', 'k'): 93, ('v', 'l'): 14, ('h', 'd'): 24, ('l', 'z'): 10, ('k', 'w'): 34, ('n', 'b'): 8, ('q', 's'): 2, ('i', 'w'): 8, ('c', 's'): 5, ('h', 's'): 31, ('m', 't'): 4, ('h', 'w'): 10, ('x', 'x'): 38, ('t', 'x'): 2, ('d', 'z'): 1, ('x', 'z'): 19, ('t', 'm'): 4, ('t', 'j'): 3, ('u', 'q'): 10, ('q', 'a'): 13, ('f', 'k'): 2, ('z', 'n'): 4, ('l', 'j'): 6, ('j', 'w'): 6, ('v', 'u'): 7, ('c', 'j'): 3, ('h', 'b'): 8, ('z', 't'): 4, ('p', 'u'): 4, ('m', 'z'): 11, ('x', 's'): 31, ('b', 't'): 2, ('u', 'y'): 13, ('d', 'j'): 9, ('j', 's'): 7, ('w', 'u'): 25, ('o', 'j'): 16, ('b', 's'): 8, ('d', 'w'): 23, ('w', 'o'): 36, ('j', 'n'): 2, ('w', 't'): 8, ('l', 'f'): 22, ('d', 'm'): 30, ('p', 'j'): 1, ('j', 'y'): 10, ('y', 'f'): 12, ('q', 'i'): 13, ('j', 'v'): 5, ('q', 'l'): 1, ('s', 'z'): 10, ('k', 'm'): 9, ('w', 'l'): 13, ('p', 'f'): 1, ('q', 'w'): 3, ('n', 'x'): 6, ('k', 'c'): 2, ('t', 'v'): 15, ('c', 'u'): 35, ('z', 'k'): 2, ('c', 'z'): 4, ('y', 'q'): 6, ('y', 'h'): 22, ('r', 'f'): 9, ('s', 'j'): 2, ('h', 'j'): 9, ('g', 'b'): 3, ('u', 'f'): 19, ('s', 'f'): 2, ('q', 'e'): 1, ('b', 'c'): 1, ('c', 'd'): 1, ('z', 'j'): 2, ('n', 'q'): 2, ('m', 'f'): 1, ('p', 'n'): 1, ('f', 'z'): 2, ('b', 'n'): 4, ('w', 'd'): 8, ('w', 'b'): 1, ('b', 'd'): 65, ('z', 's'): 4, ('p', 'c'): 1, ('h', 'g'): 2, ('m', 'j'): 7, ('w', 'w'): 2, ('k', 'j'): 2, ('h', 'p'): 1, ('j', 'k'): 2, ('o', 'q'): 3, ('f', 'w'): 4, ('f', 'h'): 1, ('w', 'm'): 2, ('b', 'j'): 1, ('r', 'q'): 16, ('z', 'c'): 2, ('z', 'v'): 2, ('f', 'g'): 1, ('n', 'p'): 5, ('z', 'g'): 1, ('d', 't'): 4, ('w', 'f'): 2, ('d', 'f'): 5, ('w', 'k'): 6, ('q', 'm'): 2, ('k', 'z'): 2, ('j', 'j'): 2, ('c', 'p'): 1, ('p', 'k'): 1, ('p', 'm'): 1, ('j', 'd'): 4, ('r', 'x'): 3, ('x', 'n'): 1, ('d', 'c'): 3, ('g', 'j'): 3, ('x', 'f'): 3, ('j', 'c'): 4, ('s', 'q'): 1, ('k', 'f'): 1, ('z', 'p'): 2, ('j', 't'): 2, ('k', 'b'): 2, ('m', 'k'): 1, ('m', 'w'): 2, ('x', 'h'): 1, ('h', 'f'): 2, ('x', 'd'): 5, ('y', 'w'): 4, ('z', 'w'): 3, ('d', 'k'): 3, ('c', 'g'): 2, ('u', 'u'): 3, ('t', 'f'): 2, ('g', 'm'): 6, ('m', 'v'): 3, ('c', 'x'): 3, ('h', 'c'): 2, ('g', 'f'): 1, ('q', 'o'): 2, ('l', 'q'): 3, ('v', 'b'): 1, ('j', 'p'): 1, ('k', 'd'): 2, ('g', 'z'): 1, ('v', 'd'): 1, ('d', 'b'): 1, ('v', 'h'): 1, ('k', 'v'): 2, ('h', 'h'): 1, ('s', 'g'): 2, ('g', 'v'): 1, ('d', 'q'): 1, ('x', 'b'): 1, ('w', 'z'): 1, ('h', 'q'): 1, ('j', 'b'): 1, ('z', 'd'): 2, ('x', 'm'): 1, ('w', 'g'): 1, ('t', 'b'): 1, ('z', 'x'): 1}
# b.items() returns the tuples of key-value
# in this case, the keys are character bigrams and values are the counts
# we want to sort, but the default sort is on the first item of the tuple, but we want to sort by count here which is the second element of the tuple
# so we added key = lambda that takes the key value and returns keyvalue at [1] instead of default [0]
# and we want to do it in descending order by using -ve sign
dict_items([(('<S>', 'e'), 1531), (('e', 'm'), 769), (('m', 'm'), 168), (('m', 'a'), 2590), (('a', '<E>'), 6640), (('<S>', 'o'), 394), (('o', 'l'), 619), (('l', 'i'), 2480), (('i', 'v'), 269), (('v', 'i'), 911), (('i', 'a'), 2445), (('<S>', 'a'), 4410), (('a', 'v'), 834), (('v', 'a'), 642), (('<S>', 'i'), 591), (('i', 's'), 1316), (('s', 'a'), 1201), (('a', 'b'), 541), (('b', 'e'), 655), (('e', 'l'), 3248), (('l', 'l'), 1345), (('l', 'a'), 2623), (('<S>', 's'), 2055), (('s', 'o'), 531), (('o', 'p'), 95), (('p', 'h'), 204), (('h', 'i'), 729), (('<S>', 'c'), 1542), (('c', 'h'), 664), (('h', 'a'), 2244), (('a', 'r'), 3264), (('r', 'l'), 413), (('l', 'o'), 692), (('o', 't'), 118), (('t', 't'), 374), (('t', 'e'), 716), (('e', '<E>'), 3983), (('<S>', 'm'), 2538), (('m', 'i'), 1256), (('a', 'm'), 1634), (('m', 'e'), 818), (('<S>', 'h'), 874), (('r', 'p'), 14), (('p', 'e'), 197), (('e', 'r'), 1958), (('r', '<E>'), 1377), (('e', 'v'), 463), (('v', 'e'), 568), (('l', 'y'), 1588), (('y', 'n'), 1826), (('n', '<E>'), 6763), (('b', 'i'), 217), (('i', 'g'), 428), (('g', 'a'), 330), (('a', 'i'), 1650), (('i', 'l'), 1345), (('l', '<E>'), 1314), (('y', '<E>'), 2007), (('i', 'z'), 277), (('z', 'a'), 860), (('e', 't'), 580), (('t', 'h'), 647), (('h', '<E>'), 2409), (('r', 'y'), 773), (('o', 'f'), 34), (('f', 'i'), 160), (('c', 'a'), 815), (('r', 'i'), 3033), (('s', 'c'), 60), (('l', 'e'), 2921), (('t', '<E>'), 483), (('<S>', 'v'), 376), (('i', 'c'), 509), (('c', 't'), 35), (('t', 'o'), 667), (('o', 'r'), 1059), (('a', 'd'), 1042), (('d', 'i'), 674), (('o', 'n'), 2411), (('<S>', 'l'), 1572), (('l', 'u'), 324), (('u', 'n'), 275), (('n', 'a'), 2977), (('<S>', 'g'), 669), (('g', 'r'), 201), (('r', 'a'), 2356), (('a', 'c'), 470), (('c', 'e'), 551), (('h', 'l'), 185), (('o', 'e'), 132), (('<S>', 'p'), 515), (('e', 'n'), 2675), (('n', 'e'), 1359), (('a', 'y'), 2050), (('y', 'l'), 1104), (('<S>', 'r'), 1639), (('e', 'y'), 1070), (('<S>', 'z'), 929), (('z', 'o'), 110), (('<S>', 'n'), 1146), (('n', 'o'), 496), (('e', 'a'), 679), (('a', 'n'), 5438), (('n', 'n'), 1906), (('a', 'h'), 2332), (('d', 'd'), 149), (('a', 'u'), 381), (('u', 'b'), 103), (('b', 'r'), 842), (('r', 'e'), 1697), (('i', 'e'), 1653), (('s', 't'), 765), (('a', 't'), 687), (('t', 'a'), 1027), (('a', 'l'), 2528), (('a', 'z'), 435), (('z', 'e'), 373), (('i', 'o'), 588), (('u', 'r'), 414), (('r', 'o'), 869), (('u', 'd'), 136), (('d', 'r'), 424), (('<S>', 'b'), 1306), (('o', 'o'), 115), (('o', 'k'), 68), (('k', 'l'), 139), (('c', 'l'), 116), (('i', 'r'), 849), (('s', 'k'), 82), (('k', 'y'), 379), (('u', 'c'), 103), (('c', 'y'), 104), (('p', 'a'), 209), (('s', 'l'), 279), (('i', 'n'), 2126), (('o', 'v'), 176), (('g', 'e'), 334), (('e', 's'), 861), (('s', 'i'), 684), (('s', '<E>'), 1169), (('<S>', 'k'), 2963), (('k', 'e'), 895), (('e', 'd'), 384), (('d', 'y'), 317), (('n', 't'), 443), (('y', 'a'), 2143), (('<S>', 'w'), 307), (('w', 'i'), 148), (('o', 'w'), 114), (('w', '<E>'), 51), (('k', 'i'), 509), (('n', 's'), 278), (('a', 'o'), 63), (('o', 'm'), 261), (('i', '<E>'), 2489), (('a', 'a'), 556), (('i', 'y'), 779), (('d', 'e'), 1283), (('c', 'o'), 380), (('r', 'u'), 252), (('b', 'y'), 83), (('s', 'e'), 884), (('n', 'i'), 1725), (('i', 't'), 541), (('t', 'y'), 341), (('u', 't'), 82), (('t', 'u'), 78), (('u', 'm'), 154), (('m', 'n'), 20), (('g', 'i'), 190), (('t', 'i'), 532), (('<S>', 'q'), 92), (('q', 'u'), 206), (('u', 'i'), 121), (('a', 'e'), 692), (('e', 'h'), 152), (('v', 'y'), 121), (('p', 'i'), 61), (('i', 'p'), 53), (('y', 'd'), 272), (('e', 'x'), 132), (('x', 'a'), 103), (('<S>', 'j'), 2422), (('j', 'o'), 479), (('o', 's'), 504), (('e', 'p'), 83), (('j', 'u'), 202), (('u', 'l'), 301), (('<S>', 'd'), 1690), (('k', 'a'), 1731), (('e', 'e'), 1271), (('y', 't'), 104), (('d', 'l'), 60), (('c', 'k'), 316), (('n', 'z'), 145), (('z', 'i'), 364), (('a', 'g'), 168), (('d', 'a'), 1303), (('j', 'a'), 1473), (('h', 'e'), 674), (('<S>', 'x'), 134), (('x', 'i'), 102), (('i', 'm'), 427), (('e', 'i'), 818), (('<S>', 't'), 1308), (('<S>', 'f'), 417), (('f', 'a'), 242), (('n', 'd'), 704), (('r', 'g'), 76), (('a', 's'), 1118), (('s', 'h'), 1285), (('b', 'a'), 321), (('k', 'h'), 307), (('s', 'm'), 90), (('o', 'd'), 190), (('r', 's'), 190), (('g', 'h'), 360), (('s', 'y'), 215), (('y', 's'), 401), (('s', 's'), 461), (('e', 'c'), 153), (('c', 'i'), 271), (('m', 'o'), 452), (('r', 'k'), 90), (('n', 'l'), 195), (('d', 'n'), 31), (('r', 'd'), 187), (('o', 'i'), 69), (('t', 'r'), 352), (('m', 'b'), 112), (('r', 'm'), 162), (('n', 'y'), 465), (('d', 'o'), 378), (('o', 'a'), 149), (('o', 'c'), 114), (('m', 'y'), 287), (('s', 'u'), 185), (('m', 'c'), 51), (('p', 'r'), 151), (('o', 'u'), 275), (('r', 'n'), 140), (('w', 'a'), 280), (('e', 'b'), 121), (('c', 'c'), 42), (('a', 'w'), 161), (('w', 'y'), 73), (('y', 'e'), 301), (('e', 'o'), 269), (('a', 'k'), 568), (('n', 'g'), 273), (('k', 'o'), 344), (('b', 'l'), 103), (('h', 'o'), 287), (('e', 'g'), 125), (('f', 'r'), 114), (('s', 'p'), 51), (('l', 's'), 94), (('y', 'z'), 78), (('g', 'g'), 25), (('z', 'u'), 73), (('i', 'd'), 440), (('m', '<E>'), 516), (('o', 'g'), 44), (('j', 'e'), 440), (('g', 'n'), 27), (('y', 'r'), 291), (('c', '<E>'), 97), (('c', 'q'), 11), (('u', 'e'), 169), (('i', 'f'), 101), (('f', 'e'), 123), (('i', 'x'), 89), (('x', '<E>'), 164), (('o', 'y'), 103), (('g', 'o'), 83), (('g', 't'), 31), (('l', 't'), 77), (('g', 'w'), 26), (('w', 'e'), 149), (('l', 'd'), 138), (('a', 'p'), 82), (('h', 'n'), 138), (('t', 'l'), 134), (('m', 'r'), 97), (('n', 'c'), 213), (('l', 'b'), 52), (('i', 'k'), 445), (('<S>', 'y'), 535), (('t', 'z'), 105), (('h', 'r'), 204), (('j', 'i'), 119), (('h', 't'), 71), (('r', 'r'), 425), (('z', 'l'), 123), (('w', 'r'), 22), (('b', 'b'), 38), (('r', 't'), 208), (('l', 'v'), 72), (('e', 'j'), 55), (('o', 'h'), 171), (('u', 's'), 474), (('i', 'b'), 110), (('g', 'l'), 32), (('h', 'y'), 213), (('p', 'o'), 59), (('p', 'p'), 39), (('p', 'y'), 12), (('n', 'r'), 44), (('z', 'm'), 35), (('v', 'o'), 153), (('l', 'm'), 60), (('o', 'x'), 45), (('d', '<E>'), 516), (('i', 'u'), 109), (('v', '<E>'), 88), (('f', 'f'), 44), (('b', 'o'), 105), (('e', 'k'), 178), (('c', 'r'), 76), (('d', 'g'), 25), (('r', 'c'), 99), (('r', 'h'), 121), (('n', 'k'), 58), (('h', 'u'), 166), (('d', 's'), 29), (('a', 'x'), 182), (('y', 'c'), 115), (('e', 'w'), 50), (('v', 'k'), 3), (('z', 'h'), 43), (('w', 'h'), 23), (('t', 'n'), 22), (('x', 'l'), 39), (('g', 'u'), 85), (('u', 'a'), 163), (('u', 'p'), 16), (('u', 'g'), 47), (('d', 'u'), 92), (('l', 'c'), 25), (('r', 'b'), 41), (('a', 'q'), 60), (('b', '<E>'), 114), (('g', 'y'), 31), (('y', 'p'), 15), (('p', 't'), 17), (('e', 'z'), 181), (('z', 'r'), 32), (('f', 'l'), 20), (('o', '<E>'), 855), (('o', 'b'), 140), (('u', 'z'), 45), (('z', '<E>'), 160), (('i', 'q'), 52), (('y', 'v'), 106), (('n', 'v'), 55), (('d', 'h'), 118), (('g', 'd'), 19), (('t', 's'), 35), (('n', 'h'), 26), (('y', 'j'), 23), (('k', 'r'), 109), (('z', 'b'), 4), (('g', '<E>'), 108), (('a', 'j'), 175), (('r', 'j'), 25), (('m', 'p'), 38), (('p', 'b'), 2), (('y', 'o'), 271), (('z', 'y'), 147), (('p', 'l'), 16), (('l', 'k'), 24), (('i', 'j'), 76), (('x', 'e'), 36), (('y', 'u'), 141), (('l', 'n'), 14), (('u', 'x'), 34), (('i', 'h'), 95), (('w', 's'), 20), (('k', 's'), 95), (('m', 'u'), 139), (('y', 'k'), 86), (('e', 'f'), 82), (('k', '<E>'), 363), (('y', 'm'), 148), (('z', 'z'), 45), (('m', 'd'), 24), (('s', 'r'), 55), (('e', 'u'), 69), (('l', 'h'), 19), (('a', 'f'), 134), (('r', 'w'), 21), (('n', 'u'), 96), (('v', 'r'), 48), (('m', 's'), 35), (('<S>', 'u'), 78), (('f', 's'), 6), (('y', 'b'), 27), (('x', 'o'), 41), (('g', 's'), 30), (('x', 'y'), 30), (('w', 'n'), 58), (('j', 'h'), 45), (('f', 'n'), 4), (('n', 'j'), 44), (('r', 'v'), 80), (('n', 'm'), 19), (('t', 'c'), 17), (('s', 'w'), 24), (('k', 't'), 17), (('f', 't'), 18), (('x', 't'), 70), (('u', 'v'), 37), (('k', 'k'), 20), (('s', 'n'), 24), (('u', '<E>'), 155), (('j', 'r'), 11), (('y', 'x'), 28), (('h', 'm'), 117), (('e', 'q'), 14), (('u', 'o'), 10), (('f', '<E>'), 80), (('h', 'z'), 20), (('h', 'k'), 29), (('y', 'g'), 30), (('q', 'r'), 1), (('v', 'n'), 8), (('s', 'd'), 9), (('y', 'i'), 192), (('n', 'w'), 11), (('d', 'v'), 17), (('h', 'v'), 39), (('x', 'w'), 3), (('o', 'z'), 54), (('k', 'u'), 50), (('u', 'h'), 58), (('k', 'n'), 26), (('s', 'b'), 21), (('i', 'i'), 82), (('y', 'y'), 23), (('r', 'z'), 23), (('l', 'g'), 6), (('l', 'p'), 15), (('p', '<E>'), 33), (('b', 'u'), 45), (('f', 'u'), 10), (('b', 'h'), 41), (('f', 'y'), 14), (('u', 'w'), 86), (('x', 'u'), 5), (('q', '<E>'), 28), (('l', 'r'), 18), (('m', 'h'), 5), (('l', 'w'), 16), (('j', '<E>'), 71), (('s', 'v'), 14), (('m', 'l'), 5), (('n', 'f'), 11), (('u', 'j'), 14), (('f', 'o'), 60), (('j', 'l'), 9), (('t', 'g'), 2), (('j', 'm'), 5), (('v', 'v'), 7), (('p', 's'), 16), (('t', 'w'), 11), (('x', 'c'), 4), (('u', 'k'), 93), (('v', 'l'), 14), (('h', 'd'), 24), (('l', 'z'), 10), (('k', 'w'), 34), (('n', 'b'), 8), (('q', 's'), 2), (('i', 'w'), 8), (('c', 's'), 5), (('h', 's'), 31), (('m', 't'), 4), (('h', 'w'), 10), (('x', 'x'), 38), (('t', 'x'), 2), (('d', 'z'), 1), (('x', 'z'), 19), (('t', 'm'), 4), (('t', 'j'), 3), (('u', 'q'), 10), (('q', 'a'), 13), (('f', 'k'), 2), (('z', 'n'), 4), (('l', 'j'), 6), (('j', 'w'), 6), (('v', 'u'), 7), (('c', 'j'), 3), (('h', 'b'), 8), (('z', 't'), 4), (('p', 'u'), 4), (('m', 'z'), 11), (('x', 's'), 31), (('b', 't'), 2), (('u', 'y'), 13), (('d', 'j'), 9), (('j', 's'), 7), (('w', 'u'), 25), (('o', 'j'), 16), (('b', 's'), 8), (('d', 'w'), 23), (('w', 'o'), 36), (('j', 'n'), 2), (('w', 't'), 8), (('l', 'f'), 22), (('d', 'm'), 30), (('p', 'j'), 1), (('j', 'y'), 10), (('y', 'f'), 12), (('q', 'i'), 13), (('j', 'v'), 5), (('q', 'l'), 1), (('s', 'z'), 10), (('k', 'm'), 9), (('w', 'l'), 13), (('p', 'f'), 1), (('q', 'w'), 3), (('n', 'x'), 6), (('k', 'c'), 2), (('t', 'v'), 15), (('c', 'u'), 35), (('z', 'k'), 2), (('c', 'z'), 4), (('y', 'q'), 6), (('y', 'h'), 22), (('r', 'f'), 9), (('s', 'j'), 2), (('h', 'j'), 9), (('g', 'b'), 3), (('u', 'f'), 19), (('s', 'f'), 2), (('q', 'e'), 1), (('b', 'c'), 1), (('c', 'd'), 1), (('z', 'j'), 2), (('n', 'q'), 2), (('m', 'f'), 1), (('p', 'n'), 1), (('f', 'z'), 2), (('b', 'n'), 4), (('w', 'd'), 8), (('w', 'b'), 1), (('b', 'd'), 65), (('z', 's'), 4), (('p', 'c'), 1), (('h', 'g'), 2), (('m', 'j'), 7), (('w', 'w'), 2), (('k', 'j'), 2), (('h', 'p'), 1), (('j', 'k'), 2), (('o', 'q'), 3), (('f', 'w'), 4), (('f', 'h'), 1), (('w', 'm'), 2), (('b', 'j'), 1), (('r', 'q'), 16), (('z', 'c'), 2), (('z', 'v'), 2), (('f', 'g'), 1), (('n', 'p'), 5), (('z', 'g'), 1), (('d', 't'), 4), (('w', 'f'), 2), (('d', 'f'), 5), (('w', 'k'), 6), (('q', 'm'), 2), (('k', 'z'), 2), (('j', 'j'), 2), (('c', 'p'), 1), (('p', 'k'), 1), (('p', 'm'), 1), (('j', 'd'), 4), (('r', 'x'), 3), (('x', 'n'), 1), (('d', 'c'), 3), (('g', 'j'), 3), (('x', 'f'), 3), (('j', 'c'), 4), (('s', 'q'), 1), (('k', 'f'), 1), (('z', 'p'), 2), (('j', 't'), 2), (('k', 'b'), 2), (('m', 'k'), 1), (('m', 'w'), 2), (('x', 'h'), 1), (('h', 'f'), 2), (('x', 'd'), 5), (('y', 'w'), 4), (('z', 'w'), 3), (('d', 'k'), 3), (('c', 'g'), 2), (('u', 'u'), 3), (('t', 'f'), 2), (('g', 'm'), 6), (('m', 'v'), 3), (('c', 'x'), 3), (('h', 'c'), 2), (('g', 'f'), 1), (('q', 'o'), 2), (('l', 'q'), 3), (('v', 'b'), 1), (('j', 'p'), 1), (('k', 'd'), 2), (('g', 'z'), 1), (('v', 'd'), 1), (('d', 'b'), 1), (('v', 'h'), 1), (('k', 'v'), 2), (('h', 'h'), 1), (('s', 'g'), 2), (('g', 'v'), 1), (('d', 'q'), 1), (('x', 'b'), 1), (('w', 'z'), 1), (('h', 'q'), 1), (('j', 'b'), 1), (('z', 'd'), 2), (('x', 'm'), 1), (('w', 'g'), 1), (('t', 'b'), 1), (('z', 'x'), 1)])
[(('n', '<E>'), 6763), (('a', '<E>'), 6640), (('a', 'n'), 5438), (('<S>', 'a'), 4410), (('e', '<E>'), 3983), (('a', 'r'), 3264), (('e', 'l'), 3248), (('r', 'i'), 3033), (('n', 'a'), 2977), (('<S>', 'k'), 2963), (('l', 'e'), 2921), (('e', 'n'), 2675), (('l', 'a'), 2623), (('m', 'a'), 2590), (('<S>', 'm'), 2538), (('a', 'l'), 2528), (('i', '<E>'), 2489), (('l', 'i'), 2480), (('i', 'a'), 2445), (('<S>', 'j'), 2422), (('o', 'n'), 2411), (('h', '<E>'), 2409), (('r', 'a'), 2356), (('a', 'h'), 2332), (('h', 'a'), 2244), (('y', 'a'), 2143), (('i', 'n'), 2126), (('<S>', 's'), 2055), (('a', 'y'), 2050), (('y', '<E>'), 2007), (('e', 'r'), 1958), (('n', 'n'), 1906), (('y', 'n'), 1826), (('k', 'a'), 1731), (('n', 'i'), 1725), (('r', 'e'), 1697), (('<S>', 'd'), 1690), (('i', 'e'), 1653), (('a', 'i'), 1650), (('<S>', 'r'), 1639), (('a', 'm'), 1634), (('l', 'y'), 1588), (('<S>', 'l'), 1572), (('<S>', 'c'), 1542), (('<S>', 'e'), 1531), (('j', 'a'), 1473), (('r', '<E>'), 1377), (('n', 'e'), 1359), (('l', 'l'), 1345), (('i', 'l'), 1345), (('i', 's'), 1316), (('l', '<E>'), 1314), (('<S>', 't'), 1308), (('<S>', 'b'), 1306), (('d', 'a'), 1303), (('s', 'h'), 1285), (('d', 'e'), 1283), (('e', 'e'), 1271), (('m', 'i'), 1256), (('s', 'a'), 1201), (('s', '<E>'), 1169), (('<S>', 'n'), 1146), (('a', 's'), 1118), (('y', 'l'), 1104), (('e', 'y'), 1070), (('o', 'r'), 1059), (('a', 'd'), 1042), (('t', 'a'), 1027), (('<S>', 'z'), 929), (('v', 'i'), 911), (('k', 'e'), 895), (('s', 'e'), 884), (('<S>', 'h'), 874), (('r', 'o'), 869), (('e', 's'), 861), (('z', 'a'), 860), (('o', '<E>'), 855), (('i', 'r'), 849), (('b', 'r'), 842), (('a', 'v'), 834), (('m', 'e'), 818), (('e', 'i'), 818), (('c', 'a'), 815), (('i', 'y'), 779), (('r', 'y'), 773), (('e', 'm'), 769), (('s', 't'), 765), (('h', 'i'), 729), (('t', 'e'), 716), (('n', 'd'), 704), (('l', 'o'), 692), (('a', 'e'), 692), (('a', 't'), 687), (('s', 'i'), 684), (('e', 'a'), 679), (('d', 'i'), 674), (('h', 'e'), 674), (('<S>', 'g'), 669), (('t', 'o'), 667), (('c', 'h'), 664), (('b', 'e'), 655), (('t', 'h'), 647), (('v', 'a'), 642), (('o', 'l'), 619), (('<S>', 'i'), 591), (('i', 'o'), 588), (('e', 't'), 580), (('v', 'e'), 568), (('a', 'k'), 568), (('a', 'a'), 556), (('c', 'e'), 551), (('a', 'b'), 541), (('i', 't'), 541), (('<S>', 'y'), 535), (('t', 'i'), 532), (('s', 'o'), 531), (('m', '<E>'), 516), (('d', '<E>'), 516), (('<S>', 'p'), 515), (('i', 'c'), 509), (('k', 'i'), 509), (('o', 's'), 504), (('n', 'o'), 496), (('t', '<E>'), 483), (('j', 'o'), 479), (('u', 's'), 474), (('a', 'c'), 470), (('n', 'y'), 465), (('e', 'v'), 463), (('s', 's'), 461), (('m', 'o'), 452), (('i', 'k'), 445), (('n', 't'), 443), (('i', 'd'), 440), (('j', 'e'), 440), (('a', 'z'), 435), (('i', 'g'), 428), (('i', 'm'), 427), (('r', 'r'), 425), (('d', 'r'), 424), (('<S>', 'f'), 417), (('u', 'r'), 414), (('r', 'l'), 413), (('y', 's'), 401), (('<S>', 'o'), 394), (('e', 'd'), 384), (('a', 'u'), 381), (('c', 'o'), 380), (('k', 'y'), 379), (('d', 'o'), 378), (('<S>', 'v'), 376), (('t', 't'), 374), (('z', 'e'), 373), (('z', 'i'), 364), (('k', '<E>'), 363), (('g', 'h'), 360), (('t', 'r'), 352), (('k', 'o'), 344), (('t', 'y'), 341), (('g', 'e'), 334), (('g', 'a'), 330), (('l', 'u'), 324), (('b', 'a'), 321), (('d', 'y'), 317), (('c', 'k'), 316), (('<S>', 'w'), 307), (('k', 'h'), 307), (('u', 'l'), 301), (('y', 'e'), 301), (('y', 'r'), 291), (('m', 'y'), 287), (('h', 'o'), 287), (('w', 'a'), 280), (('s', 'l'), 279), (('n', 's'), 278), (('i', 'z'), 277), (('u', 'n'), 275), (('o', 'u'), 275), (('n', 'g'), 273), (('y', 'd'), 272), (('c', 'i'), 271), (('y', 'o'), 271), (('i', 'v'), 269), (('e', 'o'), 269), (('o', 'm'), 261), (('r', 'u'), 252), (('f', 'a'), 242), (('b', 'i'), 217), (('s', 'y'), 215), (('n', 'c'), 213), (('h', 'y'), 213), (('p', 'a'), 209), (('r', 't'), 208), (('q', 'u'), 206), (('p', 'h'), 204), (('h', 'r'), 204), (('j', 'u'), 202), (('g', 'r'), 201), (('p', 'e'), 197), (('n', 'l'), 195), (('y', 'i'), 192), (('g', 'i'), 190), (('o', 'd'), 190), (('r', 's'), 190), (('r', 'd'), 187), (('h', 'l'), 185), (('s', 'u'), 185), (('a', 'x'), 182), (('e', 'z'), 181), (('e', 'k'), 178), (('o', 'v'), 176), (('a', 'j'), 175), (('o', 'h'), 171), (('u', 'e'), 169), (('m', 'm'), 168), (('a', 'g'), 168), (('h', 'u'), 166), (('x', '<E>'), 164), (('u', 'a'), 163), (('r', 'm'), 162), (('a', 'w'), 161), (('f', 'i'), 160), (('z', '<E>'), 160), (('u', '<E>'), 155), (('u', 'm'), 154), (('e', 'c'), 153), (('v', 'o'), 153), (('e', 'h'), 152), (('p', 'r'), 151), (('d', 'd'), 149), (('o', 'a'), 149), (('w', 'e'), 149), (('w', 'i'), 148), (('y', 'm'), 148), (('z', 'y'), 147), (('n', 'z'), 145), (('y', 'u'), 141), (('r', 'n'), 140), (('o', 'b'), 140), (('k', 'l'), 139), (('m', 'u'), 139), (('l', 'd'), 138), (('h', 'n'), 138), (('u', 'd'), 136), (('<S>', 'x'), 134), (('t', 'l'), 134), (('a', 'f'), 134), (('o', 'e'), 132), (('e', 'x'), 132), (('e', 'g'), 125), (('f', 'e'), 123), (('z', 'l'), 123), (('u', 'i'), 121), (('v', 'y'), 121), (('e', 'b'), 121), (('r', 'h'), 121), (('j', 'i'), 119), (('o', 't'), 118), (('d', 'h'), 118), (('h', 'm'), 117), (('c', 'l'), 116), (('o', 'o'), 115), (('y', 'c'), 115), (('o', 'w'), 114), (('o', 'c'), 114), (('f', 'r'), 114), (('b', '<E>'), 114), (('m', 'b'), 112), (('z', 'o'), 110), (('i', 'b'), 110), (('i', 'u'), 109), (('k', 'r'), 109), (('g', '<E>'), 108), (('y', 'v'), 106), (('t', 'z'), 105), (('b', 'o'), 105), (('c', 'y'), 104), (('y', 't'), 104), (('u', 'b'), 103), (('u', 'c'), 103), (('x', 'a'), 103), (('b', 'l'), 103), (('o', 'y'), 103), (('x', 'i'), 102), (('i', 'f'), 101), (('r', 'c'), 99), (('c', '<E>'), 97), (('m', 'r'), 97), (('n', 'u'), 96), (('o', 'p'), 95), (('i', 'h'), 95), (('k', 's'), 95), (('l', 's'), 94), (('u', 'k'), 93), (('<S>', 'q'), 92), (('d', 'u'), 92), (('s', 'm'), 90), (('r', 'k'), 90), (('i', 'x'), 89), (('v', '<E>'), 88), (('y', 'k'), 86), (('u', 'w'), 86), (('g', 'u'), 85), (('b', 'y'), 83), (('e', 'p'), 83), (('g', 'o'), 83), (('s', 'k'), 82), (('u', 't'), 82), (('a', 'p'), 82), (('e', 'f'), 82), (('i', 'i'), 82), (('r', 'v'), 80), (('f', '<E>'), 80), (('t', 'u'), 78), (('y', 'z'), 78), (('<S>', 'u'), 78), (('l', 't'), 77), (('r', 'g'), 76), (('c', 'r'), 76), (('i', 'j'), 76), (('w', 'y'), 73), (('z', 'u'), 73), (('l', 'v'), 72), (('h', 't'), 71), (('j', '<E>'), 71), (('x', 't'), 70), (('o', 'i'), 69), (('e', 'u'), 69), (('o', 'k'), 68), (('b', 'd'), 65), (('a', 'o'), 63), (('p', 'i'), 61), (('s', 'c'), 60), (('d', 'l'), 60), (('l', 'm'), 60), (('a', 'q'), 60), (('f', 'o'), 60), (('p', 'o'), 59), (('n', 'k'), 58), (('w', 'n'), 58), (('u', 'h'), 58), (('e', 'j'), 55), (('n', 'v'), 55), (('s', 'r'), 55), (('o', 'z'), 54), (('i', 'p'), 53), (('l', 'b'), 52), (('i', 'q'), 52), (('w', '<E>'), 51), (('m', 'c'), 51), (('s', 'p'), 51), (('e', 'w'), 50), (('k', 'u'), 50), (('v', 'r'), 48), (('u', 'g'), 47), (('o', 'x'), 45), (('u', 'z'), 45), (('z', 'z'), 45), (('j', 'h'), 45), (('b', 'u'), 45), (('o', 'g'), 44), (('n', 'r'), 44), (('f', 'f'), 44), (('n', 'j'), 44), (('z', 'h'), 43), (('c', 'c'), 42), (('r', 'b'), 41), (('x', 'o'), 41), (('b', 'h'), 41), (('p', 'p'), 39), (('x', 'l'), 39), (('h', 'v'), 39), (('b', 'b'), 38), (('m', 'p'), 38), (('x', 'x'), 38), (('u', 'v'), 37), (('x', 'e'), 36), (('w', 'o'), 36), (('c', 't'), 35), (('z', 'm'), 35), (('t', 's'), 35), (('m', 's'), 35), (('c', 'u'), 35), (('o', 'f'), 34), (('u', 'x'), 34), (('k', 'w'), 34), (('p', '<E>'), 33), (('g', 'l'), 32), (('z', 'r'), 32), (('d', 'n'), 31), (('g', 't'), 31), (('g', 'y'), 31), (('h', 's'), 31), (('x', 's'), 31), (('g', 's'), 30), (('x', 'y'), 30), (('y', 'g'), 30), (('d', 'm'), 30), (('d', 's'), 29), (('h', 'k'), 29), (('y', 'x'), 28), (('q', '<E>'), 28), (('g', 'n'), 27), (('y', 'b'), 27), (('g', 'w'), 26), (('n', 'h'), 26), (('k', 'n'), 26), (('g', 'g'), 25), (('d', 'g'), 25), (('l', 'c'), 25), (('r', 'j'), 25), (('w', 'u'), 25), (('l', 'k'), 24), (('m', 'd'), 24), (('s', 'w'), 24), (('s', 'n'), 24), (('h', 'd'), 24), (('w', 'h'), 23), (('y', 'j'), 23), (('y', 'y'), 23), (('r', 'z'), 23), (('d', 'w'), 23), (('w', 'r'), 22), (('t', 'n'), 22), (('l', 'f'), 22), (('y', 'h'), 22), (('r', 'w'), 21), (('s', 'b'), 21), (('m', 'n'), 20), (('f', 'l'), 20), (('w', 's'), 20), (('k', 'k'), 20), (('h', 'z'), 20), (('g', 'd'), 19), (('l', 'h'), 19), (('n', 'm'), 19), (('x', 'z'), 19), (('u', 'f'), 19), (('f', 't'), 18), (('l', 'r'), 18), (('p', 't'), 17), (('t', 'c'), 17), (('k', 't'), 17), (('d', 'v'), 17), (('u', 'p'), 16), (('p', 'l'), 16), (('l', 'w'), 16), (('p', 's'), 16), (('o', 'j'), 16), (('r', 'q'), 16), (('y', 'p'), 15), (('l', 'p'), 15), (('t', 'v'), 15), (('r', 'p'), 14), (('l', 'n'), 14), (('e', 'q'), 14), (('f', 'y'), 14), (('s', 'v'), 14), (('u', 'j'), 14), (('v', 'l'), 14), (('q', 'a'), 13), (('u', 'y'), 13), (('q', 'i'), 13), (('w', 'l'), 13), (('p', 'y'), 12), (('y', 'f'), 12), (('c', 'q'), 11), (('j', 'r'), 11), (('n', 'w'), 11), (('n', 'f'), 11), (('t', 'w'), 11), (('m', 'z'), 11), (('u', 'o'), 10), (('f', 'u'), 10), (('l', 'z'), 10), (('h', 'w'), 10), (('u', 'q'), 10), (('j', 'y'), 10), (('s', 'z'), 10), (('s', 'd'), 9), (('j', 'l'), 9), (('d', 'j'), 9), (('k', 'm'), 9), (('r', 'f'), 9), (('h', 'j'), 9), (('v', 'n'), 8), (('n', 'b'), 8), (('i', 'w'), 8), (('h', 'b'), 8), (('b', 's'), 8), (('w', 't'), 8), (('w', 'd'), 8), (('v', 'v'), 7), (('v', 'u'), 7), (('j', 's'), 7), (('m', 'j'), 7), (('f', 's'), 6), (('l', 'g'), 6), (('l', 'j'), 6), (('j', 'w'), 6), (('n', 'x'), 6), (('y', 'q'), 6), (('w', 'k'), 6), (('g', 'm'), 6), (('x', 'u'), 5), (('m', 'h'), 5), (('m', 'l'), 5), (('j', 'm'), 5), (('c', 's'), 5), (('j', 'v'), 5), (('n', 'p'), 5), (('d', 'f'), 5), (('x', 'd'), 5), (('z', 'b'), 4), (('f', 'n'), 4), (('x', 'c'), 4), (('m', 't'), 4), (('t', 'm'), 4), (('z', 'n'), 4), (('z', 't'), 4), (('p', 'u'), 4), (('c', 'z'), 4), (('b', 'n'), 4), (('z', 's'), 4), (('f', 'w'), 4), (('d', 't'), 4), (('j', 'd'), 4), (('j', 'c'), 4), (('y', 'w'), 4), (('v', 'k'), 3), (('x', 'w'), 3), (('t', 'j'), 3), (('c', 'j'), 3), (('q', 'w'), 3), (('g', 'b'), 3), (('o', 'q'), 3), (('r', 'x'), 3), (('d', 'c'), 3), (('g', 'j'), 3), (('x', 'f'), 3), (('z', 'w'), 3), (('d', 'k'), 3), (('u', 'u'), 3), (('m', 'v'), 3), (('c', 'x'), 3), (('l', 'q'), 3), (('p', 'b'), 2), (('t', 'g'), 2), (('q', 's'), 2), (('t', 'x'), 2), (('f', 'k'), 2), (('b', 't'), 2), (('j', 'n'), 2), (('k', 'c'), 2), (('z', 'k'), 2), (('s', 'j'), 2), (('s', 'f'), 2), (('z', 'j'), 2), (('n', 'q'), 2), (('f', 'z'), 2), (('h', 'g'), 2), (('w', 'w'), 2), (('k', 'j'), 2), (('j', 'k'), 2), (('w', 'm'), 2), (('z', 'c'), 2), (('z', 'v'), 2), (('w', 'f'), 2), (('q', 'm'), 2), (('k', 'z'), 2), (('j', 'j'), 2), (('z', 'p'), 2), (('j', 't'), 2), (('k', 'b'), 2), (('m', 'w'), 2), (('h', 'f'), 2), (('c', 'g'), 2), (('t', 'f'), 2), (('h', 'c'), 2), (('q', 'o'), 2), (('k', 'd'), 2), (('k', 'v'), 2), (('s', 'g'), 2), (('z', 'd'), 2), (('q', 'r'), 1), (('d', 'z'), 1), (('p', 'j'), 1), (('q', 'l'), 1), (('p', 'f'), 1), (('q', 'e'), 1), (('b', 'c'), 1), (('c', 'd'), 1), (('m', 'f'), 1), (('p', 'n'), 1), (('w', 'b'), 1), (('p', 'c'), 1), (('h', 'p'), 1), (('f', 'h'), 1), (('b', 'j'), 1), (('f', 'g'), 1), (('z', 'g'), 1), (('c', 'p'), 1), (('p', 'k'), 1), (('p', 'm'), 1), (('x', 'n'), 1), (('s', 'q'), 1), (('k', 'f'), 1), (('m', 'k'), 1), (('x', 'h'), 1), (('g', 'f'), 1), (('v', 'b'), 1), (('j', 'p'), 1), (('g', 'z'), 1), (('v', 'd'), 1), (('d', 'b'), 1), (('v', 'h'), 1), (('h', 'h'), 1), (('g', 'v'), 1), (('d', 'q'), 1), (('x', 'b'), 1), (('w', 'z'), 1), (('h', 'q'), 1), (('j', 'b'), 1), (('x', 'm'), 1), (('w', 'g'), 1), (('t', 'b'), 1), (('z', 'x'), 1)]

Counting transitions

# it will be convenient for us to keep this information in a 2d-array(table with proper organization of proper rows and columns and a value in a cell) instead of a python dictionary
# rows: first char of bigram, columns: second char of bigram
# each entry tells us how often the second character follows the first character in the dataset
# the array representation / library we are going to use: pytorch
# pytorch: deep learning neural network framework
# but part of it is also torch.tensor that allows us to create multidimensional arrays and manipulate them efficiently
tensor([[0., 0., 0., 0., 0.], [0., 0., 0., 0., 0.], [0., 0., 0., 0., 0.]])
torch.float32
torch.int32
tensor([[0, 0, 0, 0, 0],
        [0, 0, 0, 0, 0],
        [0, 0, 0, 0, 0]], dtype=torch.int32)
tensor([[2, 0, 0, 0, 0], [0, 0, 0, 5, 0], [0, 0, 0, 0, 0]], dtype=torch.int32)
tensor([[8, 0, 0, 0, 0], [0, 0, 0, 5, 0], [0, 0, 0, 0, 0]], dtype=torch.int32)
# the problem now is that we have characters a-z, <S> and <E> and they need to be indexed using integers
# so we need some lookup table from characters to integers
['a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o', 'p', 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z']
{'a': 0, 'b': 1, 'c': 2, 'd': 3, 'e': 4, 'f': 5, 'g': 6, 'h': 7, 'i': 8, 'j': 9, 'k': 10, 'l': 11, 'm': 12, 'n': 13, 'o': 14, 'p': 15, 'q': 16, 'r': 17, 's': 18, 't': 19, 'u': 20, 'v': 21, 'w': 22, 'x': 23, 'y': 24, 'z': 25, '<S>': 26, '<E>': 27}
{0: 'a', 1: 'b', 2: 'c', 3: 'd', 4: 'e', 5: 'f', 6: 'g', 7: 'h', 8: 'i', 9: 'j', 10: 'k', 11: 'l', 12: 'm', 13: 'n', 14: 'o', 15: 'p', 16: 'q', 17: 'r', 18: 's', 19: 't', 20: 'u', 21: 'v', 22: 'w', 23: 'x', 24: 'y', 25: 'z', 26: '<S>', 27: '<E>'}
['a', 'p', 'p', 'l', 'e']
# N
# let's visualize it nicely
<matplotlib.image.AxesImage at 0x756a1ed47890>
<Figure size 640x480 with 1 Axes>
# NumPy:      N[row, column] -> N[i, j]
# Matplotlib: (x, y)         -> (column, row) -> (j, i)
tensor(149, dtype=torch.int32)
<class 'torch.Tensor'>
149
<class 'int'>
(np.float64(-0.5), np.float64(27.5), np.float64(27.5), np.float64(-0.5))
<Figure size 2400x2400 with 1 Axes>
# upon careful scrutinization, we are not being very clever
# look at the last row and second last column, it is completely filled with zero's, and a 2x2 matrix at the bottom right too(only <S><E> is valid there for empty word)
# that's cuz <E> will never be the first character of a bigram, and <S> will never be the second character of a bigram
# we are using brackets <> cuz it is a convention to do so in NLP
# now, we won't use 2 special tokens. We're only going to use one special token, so N array would be 27x27 instead
{1: 'a', 2: 'b', 3: 'c', 4: 'd', 5: 'e', 6: 'f', 7: 'g', 8: 'h', 9: 'i', 10: 'j', 11: 'k', 12: 'l', 13: 'm', 14: 'n', 15: 'o', 16: 'p', 17: 'q', 18: 'r', 19: 's', 20: 't', 21: 'u', 22: 'v', 23: 'w', 24: 'x', 25: 'y', 26: 'z', 0: '.'}
(np.float64(-0.5), np.float64(26.5), np.float64(26.5), np.float64(-0.5))
<Figure size 2400x2400 with 1 Axes>
# note: first row is the start letter one, i.e. .a, .b, it is equivalent to <S>a, <S>b, etc
# note: first column is the end letter one, i.e. a., b., it is equivalent to a<E>, b<E>, etc
# to sample the first character of the name, we look at the first row, .a, .b.
# don't get confused .a doesn't mean it ends at a.
# here .a means <S>a, so it means, how likely is a to be the first character of a word
tensor([ 0, 4410, 1306, 1542, 1690, 1531, 417, 669, 874, 591, 2422, 2963, 1572, 2538, 1146, 394, 515, 92, 1639, 2055, 1308, 78, 376, 307, 134, 535, 929], dtype=torch.int32)
torch.Size([27])
# now, we want to sample from these counts
# since we are given raw counts, we have to convert them into probabilities
tensor([0.0000, 0.1377, 0.0408, 0.0481, 0.0528, 0.0478, 0.0130, 0.0209, 0.0273, 0.0184, 0.0756, 0.0925, 0.0491, 0.0792, 0.0358, 0.0123, 0.0161, 0.0029, 0.0512, 0.0642, 0.0408, 0.0024, 0.0117, 0.0096, 0.0042, 0.0167, 0.0290])
tensor(1.)
# now we can try to sample from this distribution
# to sample from this distribution, we are going to use torch.multinomial
# it means: you can give me probabilities and i will give you integers which are sampled according to the probability distribution

Sampling from probabilities

# suppose: P = torch.tensor([0.50, 0.30, 0.15, 0.05])
# here, a is most likely the next character, d is the least likely, but any of them can still happen
# A probability distribution tells you how likely each outcome is.
# sampling means: Randomly pick one outcome, but make the randomness follow the given probabilities.
# it's not choosing the largest probability, it's not choosing uniformly
# we let the probabilities themselves determine how likely each outcome is to be picked
# torch.multinomial
# P = torch.tensor([0.50, 0.30, 0.15, 0.05])
# torch.multinomial(P, num_samples=1)
# Here are the probabilities. Randomly choose one index according to them.
# it might return tensor[0] or tensor[1] or tensor[2] or even tensor[3]
# For one sample, anything can happen.
# but if we sample many times
# for _ in range(10):
    # print(torch.multinomial(P, 1).item())
# we will notice that in one run, [0] appears most and [3] appears least
# and why're we doing this? cuz we want it to feel natural
# we don't want every single word to start with the same letter with the highest probability
tensor([0.7081, 0.3542, 0.1054])
tensor([0.6064, 0.3033, 0.0903])
tensor([1, 1, 2, 0, 0, 2, 1, 1, 0, 0, 0, 1, 1, 0, 0, 1, 1, 0, 0, 1, 0, 2, 0, 0, 1, 0, 0, 1, 0, 0, 0, 1, 1, 1, 0, 1, 1, 0, 0, 1, 1, 1, 0, 1, 1, 0, 1, 1, 0, 2, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 1, 2, 0, 0, 0, 0, 0, 0, 1, 0, 0, 2, 0, 1, 0, 0, 1, 1, 1])
# prob for first sample, i.e. 0: 61%, for second sample: 30%, for third sample: 9%
# the more we sample, the more accurate it should be
# getting back to topic
tensor([0.0000, 0.1377, 0.0408, 0.0481, 0.0528, 0.0478, 0.0130, 0.0209, 0.0273, 0.0184, 0.0756, 0.0925, 0.0491, 0.0792, 0.0358, 0.0123, 0.0161, 0.0029, 0.0512, 0.0642, 0.0408, 0.0024, 0.0117, 0.0096, 0.0042, 0.0167, 0.0290])
# note: we are working on N[0], so we are working to find which character might be the first character to our word
tensor([3])
5
e
# we got the first character, now we have to draw the next one
# we drew e, so now look at the row e and find the character that is likely to occur after e
# now we write a loop
cexze.
momasurailezitynn.
konimittain.
llayn.
ka.
da.
staiyaubrtthrigotai.
moliellavo.
ke.
teda.
ka.
emimmsade.
enkaviyny.
ftlspihinivenvorhlasu.
dsor.
br.
jol.
pen.
aisan.
ja.
feniee.
zem.
deru.
firit.
gaikajahahbevare.
kiysthelenaririenah.
keen.
x.
al.
kal.
# the reason these samples are so terrible is that bigram language model is just terrible
# but they are name like a little bit but not name like enough
# but think from the model's perspective, eg. you see x., the model doesn't know that x is the first x of the word. it is just thinking about one character at a time
# so it directly also looks for how likely is x the last character
# to convince yourself that this is not so terrible is by : instead of relying on p(that came from our dataset of 32k words),
# if p was just a uniform probability distribution of all 27 characters making any character to appear equally likely
cexzm.
zoglkurkicqzktyhwmvmzimjttainrlkfukzkktda.
sfcxvpubjtbhrmgotzx.
iczixqctvujkwptedogkkjemkmmsidguenkbvgynywftbspmhwcivgbvtahlvsu.
dsdxxblnwglhpyiw.
igwnjwrpfdwipkwzkm.
desu.
firmt.
gbiksjbquabsvoth.
kuysxqevhcmrbxmcwyhrrjenvxmvpfkmwmghfvjzxobomysox.
gbptjapxweegpfwhccfyzfvksiiqmvwbhmiwqmdgzqsamjhgamcxwmmk.
iswcxfmbalcslhy.
fpycvasvz.
bqzazeunschck.
wnkojuoxyvtvfiwksddugnkul.
# saw the output from a completely untrained model? saw how random it could have actually been?

Normalization and broadcasting

# now, let's fix an efficiency issue
# we are always fetching a row of N from the count matrix up, then converting to float, and then dividing
# this happens in every iteration of the loop
# and we're renormalizing these rows again and again
# so let's prepare a new matrix P based on existing matrix N, but P will have probabilities 
# i.e. same as N, but instead of values, we set probabilities (value/sum of that row) normalized to one
# so it indicates the probability distribution directly for the next character(column) given the character before(row)
# we're doing this not just for efficiency, but also to practice n-dimensional tensors and practice their manipulation
# practice broadcasting
# if we're going to build up to transformers, we have to be good in tensor manipulations, complicated array operations for efficiency
# P.sum()
# We want to divide all the rows value's by the respective sum of the row
# looking documentation for torch.sum
# so, we provide the array we want to sum, and it's dimensions
# but not just that, be careful of keep dimension attribute
# if keepdim = True, output tensor is of the same size as input, except in the dimension along you summed which will become 1
# but if keepdim = False, this dimension is squeezed out, so torch.sum does the sum and also collapses dimension to be of size one, 
# but addition it also does the squeeze, it squeezes out that dimension
torch.Size([27, 27])
<built-in method sum of Tensor object at 0x73944a735050>
tensor([[32033., 33885.,  2645.,  3532.,  5496., 20423.,   905.,  1927.,  7616.,
         17701.,  2900.,  5040., 13958.,  6642., 18327.,  7934.,  1026.,   272.,
         12700.,  8106.,  5570.,  3135.,  2573.,   929.,   697.,  9776.,  2398.]])
torch.Size([1, 27])
tensor([32033., 33885.,  2645.,  3532.,  5496., 20423.,   905.,  1927.,  7616.,
        17701.,  2900.,  5040., 13958.,  6642., 18327.,  7934.,  1026.,   272.,
        12700.,  8106.,  5570.,  3135.,  2573.,   929.,   697.,  9776.,  2398.])
torch.Size([27])
# we don't want 1x27 row vector, we don't want the sum of counts across a column
# we want to sum of counts across a row, so we want a 27x1 row vector, and each entry gives corresponding sum of that row
tensor([[32033.],
        [33885.],
        [ 2645.],
        [ 3532.],
        [ 5496.],
        [20423.],
        [  905.],
        [ 1927.],
        [ 7616.],
        [17701.],
        [ 2900.],
        [ 5040.],
        [13958.],
        [ 6642.],
        [18327.],
        [ 7934.],
        [ 1026.],
        [  272.],
        [12700.],
        [ 8106.],
        [ 5570.],
        [ 3135.],
        [ 2573.],
        [  929.],
        [  697.],
        [ 9776.],
        [ 2398.]])
torch.Size([27, 1])
tensor([32033., 33885.,  2645.,  3532.,  5496., 20423.,   905.,  1927.,  7616.,
        17701.,  2900.,  5040., 13958.,  6642., 18327.,  7934.,  1026.,   272.,
        12700.,  8106.,  5570.,  3135.,  2573.,   929.,   697.,  9776.,  2398.])
torch.Size([27])
# note that shape is 27 x 1, so it's a column vector, we're going horizontally
# buut, notice that these counts are identical no matter whether we sum across rows or columns
# that's cuz this array of counts comes from bigram statistics and by coincidence or by the way this array is constructed, the sums along rows and columns are identical
# is it possible to take a 27x27 array and divide by 27 x 1 array?
# P / P.sum(1, keepdim=True)
# 27 x 27
# -------
# 27 x  1
# whether or not we can perform this operation is determined by broadcasting rules (explore this in pytorch)
# from documentation, there are a few conditions:
# 1. each tensor has atleast 1 dimension
# 2. when iterating over the dimension sizes, starting at the trailing(later) dimension, the dimension sizes must either be equal, one of them is 1, or one of them doesn't exist.
# so we align them like this
# 27 x 27
# -------
# 27 x  1
# move from right to left
# from the right, they are not equal, but one of them is a 1, so it is fine, and in the other dimension, both are equal (27)
# since they are fine, the operation P = P / P.sum(1, keepdim=True) is broadcastable/allowed
# so what happens internally is that, the dimension 1 is stretched out, it is copied to match 27
# so our column vector(that has the sum) is copied 27 times for each one of those 27 columns(. - z), and internally it becomes 27/27
# finally element wise division is done
# thus this action shall normalize every single row
tensor(1.)
cexze.
momasurailezitynn.
konimittain.
llayn.
ka.
da.
staiyaubrtthrigotai.
moliellavo.
ke.
teda.
ka.
emimmsade.
enkaviyny.
ftlspihinivenvorhlasu.
dsor.
# now, let's understand where broadcasting could go wrong
# also lookup some tutorials on broadcasting and practice it, cuz you can run into bugs very quickly
torch.Size([27, 1])
tensor([[32033.], [33885.], [ 2645.], [ 3532.], [ 5496.], [20423.], [ 905.], [ 1927.], [ 7616.], [17701.], [ 2900.], [ 5040.], [13958.], [ 6642.], [18327.], [ 7934.], [ 1026.], [ 272.], [12700.], [ 8106.], [ 5570.], [ 3135.], [ 2573.], [ 929.], [ 697.], [ 9776.], [ 2398.]])
# now suppose we erase keepdim=True, and by default it's false
# what happens then? well it just squeezes the dimension 1 out turning it into 1 d array
tensor([32033., 33885., 2645., 3532., 5496., 20423., 905., 1927., 7616., 17701., 2900., 5040., 13958., 6642., 18327., 7934., 1026., 272., 12700., 8106., 5570., 3135., 2573., 929., 697., 9776., 2398.])
torch.Size([27])
# you might think that P / P.sum(1) might work as expected, but it doesn't
# let's check if it is broadcastable
# P / P.sum(1)
# 27 x 27
# -------
#      27
# rule 1: align all dimensions on the right
# move from right to left
# either equal, one of them should be 1, or one of them shouldn't exist
# equal, and doesn't exist
# so internally, broadcasting will create a 1 in front, and it will get copied and will broadcast
# 27 x 27
# -------
#  1 x 27
# this won't work and you'll get garbage result
cexze.
zoghkurkicqukwydwevizimittain.
juffk.
kavig.
jomiviubrtthrugotzi.
mozixxluxuggwytedowekoumimmstheuwnkaviynyzytlsphiguwdebdorhlusuzusprddinwil.
pynwuigwn.
wuwffwippppem.
deru.
fixxt.
gbikajahudoshath.
kucoxxevicyabruqubh.
keengwmalosilmudavazeobomysoxxxsptjappheviphisphiyzzelyillevebhmiffidezhakamckamcoworucishaxfubalckahy.
foronazzz.
bezazexxzahay.
# so why was the keepdim necessary?
# earlier when keepdim was used, the 1-d vector(column) was copied across each column properly, 
# so eg. for a value in fourth row and first column, row[3][0], it was divided by sum of the fourth row[3]
# but now, when keepdim is absent, the 1-d vector(column) was copied across each row instead
# so eg. for a value in fourth row and first column, row[3][0], it was divided by sum of the first row[0]

# so first column was divided by sum of first row, instead of first row being divided by sum of first row and so on
# so first row was divided as: first entry divided by sum of first row ok, but second entry of first row divided by sum of second row, 3rd entry of first row divided by sum of third row and so on.
tensor(7.0225)
tensor(1.)
# in short, from broadcasting rules, a dimension is silently added to the left which caused this issue, so work with it carefully
# what was supposed to be a column vector becomes a row vector instead and it works in opposite manner instead

Likelihood and model quality

# we trained a bigram language model just by counting how frequently any pairing occurs and then normalizing so that we can get a nice probability distribution
# the elements of array P are the parameters of our bigram language model giving and summarizing the statistics of these bigrams
# so we trained the model and now we know how to sample from the model
# we just iteratively sample the next character and feed it in each time and get the next character
# now, we want to evaluate the quality of this model
# we want to somehow summarize the model's quality into a single number
# how good is it at predicting the training set, eg. we can evaluate the training loss from the training set
# this training loss tells us the quality of the model in a single number
0.037037037037037035
# side note: if we were to sum all probability values in the matrix P, would we get 27? cuz sum for one row would be 1, and we have 27 rows
.e: 0.0107 -4.5399
em: 0.0504 -2.9876
mm: 0.0522 -2.9517
ma: 0.1579 -1.8459
a.: 0.0470 -3.0585
.o: 0.0071 -4.9517
ol: 0.0389 -3.2464
li: 0.1333 -2.0153
iv: 0.0444 -3.1136
vi: 0.3572 -1.0295
ia: 0.0307 -3.4845
a.: 0.0470 -3.0585
.a: 0.0185 -3.9882
av: 0.0734 -2.6114
va: 0.1315 -2.0288
a.: 0.0470 -3.0585
log_likelihood=tensor(-47.9700)
nll=tensor(47.9700)
nll/n=tensor(2.9981)
graph for log(x): focus on x = 0 to 1
# from this graph, we can see the log is a monotonic transformation of probability
# if we pass in probability as x = 1, we get 0
# and as we go lower, i.e. probability or x < 1, we get the log's value as more and more negative until -infinity is reached at x = 0
# the only reason we are doing this stuff with log is for convenience
# keep in mind: we are trying to compute product of all the probabilities, i.e. ..*.a*...*aa*ab*ac...*zz
# note: log(a*b*c) = log(a) + log(b) + log(c)
# since likelihood = product of probabilities, 
# log likelihood = log(product of probabilities) = sum of logs of individual probabilities
# so log likelihood starts at 0, and it accumulates overtime by logs of all individual probabilities
# so how high can log likelihood get? it can go to zero
# this happens when all probabilities are 1, and since log(1) = 0, log(1) + log(1) + ...(for all probabilities) = 0
# but when all the probabilities are very low instead of 1, our log probability gets very very negative, and we don't like this
# but why don't we like this? cuz we'd like to implement a loss function: and loss function has the semantics that low is good (which is opposite of our case) because we're always trying to minimize the loss
# so we'd like to invert this from current situation where 0(desired) and -infinity(undesired)
# to invert it, we do the negative log likelihood: which is simply the negative of log likelihood
# and one final modification people like to do for convenience is instead of just the sum of nll, people compute the average of nll
# so the loss function assigned by our model: the average negative log likelihood is: 2.9981: the quality of our model
# lower the anll better our model and vice versa
# and now, the job of our training is to find the parameters that minimize the negative log likelihood
# SUMMARY:
# GOAL: MAXIMIZE LIKELIHOOD(product of all probabilities) OF DATA W.R.T MODEL PARAMETERS (statistical modeling) (defined in the matrix)
# equivalent to maximizing the log likelihood (because log is monotonic)
# equivalent to minimizing the negative log likelihood
# equivalent to minimizing the average negative log likelihood (guess we can't say normalized since it isn't in range 0-1.)
# currently we are storing all probabilities in a table format, but what's coming up later is that these numbers won't be kept explicitly but be calculated by a neural network
log_likelihood=tensor(-650247.6250)
nll=tensor(650247.6250)
nll/n=tensor(2.8501)
log_likelihood=tensor(-inf)
nll=tensor(inf)
nll/n=tensor(inf)

Smoothing

# acc to our model, hx has a 0% probability, so log(0) = -inf, and our anll = inf
# this is unreasonable, cuz we plugged a string that would be somewhat reasonable but it shouldn't be 0% likely
# there's a very simple fix to smooth out our model: model smoothing
# we will add fake counts: eg. add a count of 1 to everything
# the more we add, the more smoother it gets, and we get a more smoother uniform model
# 1 is a decent count to add which will ensure that there will be no 0s in our probability matrix P, this can change our generations by a little bit
# so it is unlikely to get an infinity
.s: 0.0641 -2.7468
sa: 0.1478 -1.9119
at: 0.0203 -3.8977
ti: 0.0952 -2.3515
is: 0.0743 -2.5998
sh: 0.1581 -1.8444
hx: 0.0001 -8.9415
x.: 0.2279 -1.4788
log_likelihood=tensor(-25.7726)
nll=tensor(25.7726)
nll/n=tensor(3.2216)
# so until now, we've trained a respectable bigram character level language model
# and we've trained the model by looking at the counts of all the bigrams and normalizing the rows to get probability distributions
# we can also use the parameters(probability values inside matrix P) of this model to perform sampling of new words according to the probability distribution
# and we can evaluate the quality of this model: summarized in a single number: negative log likelihood, and the lower this loss, the better our model is 
# because it is giving high probabilities to the actual next characters in all bigrams in our training set
# but what we did was just counts and normalizing these counts
# but now we want to take an alternative approach and end up in a very similar position
# the approach is very different cuz we would like to cast the problem of bigram character level language modeling into the neural network framework
# and in the neural network framework, we're going to approach things differently but end up in a similar spot

The neural network approach

PART 2: THE NEURAL NETWORK APPROACH
# our neural network is still going to be a bigram character level language model
# so it receives a single character as input
# and there's a neural network with some parameters
# and it will output the probability distribution over the next character in a sequence, 
# it will make guesses as to what is likely to follow the char that was input to this model
# ADDITIONALLY, we will be able to evaluate any setting of the parameters of the neural net cuz we have the loss function: the negative log likelihood
# so we'll look at the probability distributions and then look at the labels (identity of next character in that bigram)
# knowing the second character in the bigram allows us to look at how high of a probaility the model assigns to that character
# ofcourse, we want the probability to be very high, ie. loss is low
# then we're going to use gradient-based optimization to tune the parameters of this network cuz we have the loss function and we'll minimize it
# so we'll tune the weights so that neural net correctly predicts the probabilities for the next character
['emma', 'olivia', 'ava', 'isabella', 'sophia', 'charlotte', 'mia', 'amelia', 'harper', 'evelyn', 'abigail', 'emily', 'elizabeth', 'mila', 'ella', 'avery', 'sofia', 'camila', 'aria', 'scarlett']
{1: 'a', 2: 'b', 3: 'c', 4: 'd', 5: 'e', 6: 'f', 7: 'g', 8: 'h', 9: 'i', 10: 'j', 11: 'k', 12: 'l', 13: 'm', 14: 'n', 15: 'o', 16: 'p', 17: 'q', 18: 'r', 19: 's', 20: 't', 21: 'u', 22: 'v', 23: 'w', 24: 'x', 25: 'y', 26: 'z', 0: '.'}
. e
e m
m m
m a
a .
tensor([ 0, 5, 13, 13, 1])
tensor([ 5, 13, 13, 1, 0])
# so, when input is 0, we want the weights to be arranged so that 5 gets a high probability, similar for 5-13, 13-13, 13-1, and 1-0
# caution: be careful of APIs of some frameworks, eg. we used torch.tensor, but there is also a torch.Tensor which is very very similar too
# the official documentation doesn't distinguish much, but in stackoverflow there was a thread that distinguishes the difference
# difference:
# torch.tensor: infers the dtype automatically, also has argument dtype if we want to change it, so stick with this instead(recommended)
# torch.Tensor: returns torch.FloatTensor
torch.int64
torch.int64
# now, how will we feed these examples xs, ys into neural network?
# right now, it's not straightforward, cuz these examples are integers that are index of characters(., a, ..., z), and we can't just plug integer index into neural network
# neural networks has neurons, neurons have weights and they act multiplicatively on the input: wx + b, tanh, so it doesn't make sense to use these 0-26 int directly
# instead, a common way of encoding integers: one-hot encoding: 
# we take an integer eg.13 and create a vector with all zeros except for the 13th dimension(which will be 1), and we feed it into a neural net
# pytorch has a one hot function inside torch.nn.functional that takes a tensor made up of integers
# and it also takes input as num of classes: how long you want your vector to be. in our case 27 = 26(a-z) + 1(.)
# not specifying num_classes might give incorrect result: might take largest value in xs, eg. 13 instead
tensor([[1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], [0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], [0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]])
torch.Size([5, 27])
<matplotlib.image.AxesImage at 0x7db4875d3230>
<Figure size 640x480 with 1 Axes>
# understanding this graph: try to read ., e, m, m, a
torch.int64
tensor([[1., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0.], [0., 0., 0., 0., 0., 1., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0.], [0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 1., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0.], [0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 1., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0.], [0., 1., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0.]])
torch.float32
<matplotlib.image.AxesImage at 0x7db487412990>
<Figure size 640x480 with 1 Axes>
tensor([[-0.3689], [ 0.3017], [ 0.7983], [-0.2436], [-0.7199], [-0.7178], [-0.5994], [ 0.4572], [ 0.5109], [ 0.1610], [ 0.4179], [ 0.9262], [-0.1166], [-1.6169], [-1.2806], [ 0.6441], [ 0.6572], [ 0.0416], [ 0.2845], [ 0.2733], [ 0.4007], [ 0.7402], [ 0.2278], [-0.6630], [-0.4101], [-0.6030], [ 1.0632]])
tensor([[-0.3689], [-0.7178], [-1.6169], [-1.6169], [ 0.3017]])
# we evaluate 5 activations of this neuron on these 5 inputs all in parallel
# we fed 5 inputs into the same neuron simultaneously, and in parallel, pytorch has evaluated wx for all of them
# now instead of a single neuron, we want to have 27 neurons
tensor([[-0.2381, 0.3721, -0.6063, 0.2695, -0.4948, -0.2142, 0.3754, -0.9684, -0.2667, -0.6459, 0.9267, 1.3762, -0.4314, 0.4009, -0.6887, -0.6417, 1.3215, -0.0305, 1.0770, 0.9236, 0.7579, -0.2886, -0.5679, 0.2053, -0.9550, -0.1571, 0.0972], [ 1.2477, -0.6483, 2.9427, -1.1470, -0.5661, 0.2784, -0.9179, 1.8721, -1.0616, -0.4578, 1.5357, 0.1272, 0.1167, -0.0767, 0.7872, 0.3312, -0.0364, 0.4641, -0.3681, 0.0905, 0.4137, -0.6679, 0.1785, -0.6744, -0.1722, 0.8944, 0.7667], [-0.2772, -0.9649, -1.8149, 0.9409, 0.9290, -0.9330, 0.4190, 0.2508, 0.9838, 1.1341, -0.0316, -0.9327, 0.3265, -1.1368, -0.9668, 1.5915, 0.7876, 0.2264, 0.8547, -2.7509, 2.0192, -1.1206, -1.7251, -0.1866, 0.1177, -0.7251, 0.4731], [-0.2772, -0.9649, -1.8149, 0.9409, 0.9290, -0.9330, 0.4190, 0.2508, 0.9838, 1.1341, -0.0316, -0.9327, 0.3265, -1.1368, -0.9668, 1.5915, 0.7876, 0.2264, 0.8547, -2.7509, 2.0192, -1.1206, -1.7251, -0.1866, 0.1177, -0.7251, 0.4731], [-0.2046, -0.1169, -0.5736, -0.6125, -1.1879, 1.5528, 0.3456, -0.9457, -0.1947, 0.7187, -1.1427, 0.6083, 0.6176, 1.4568, -0.4730, -1.2182, 0.6352, 0.4920, 1.1291, -2.0718, -0.0313, 1.7058, -1.3478, 0.2128, -1.1291, -1.1662, -1.7359]])
torch.Size([5, 27])
tensor(0.3265)
tensor(-0.2772)
# so by using matrix multiplication, we can very efficiently evaluate the dot product between lots of input examples in a batch
tensor([0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 1., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0.])
tensor([-0.5477, -1.1185, -0.6815, 0.2531, 0.3081, -1.1757, 0.2530, 0.7343, 0.3226, 0.9534, 0.0457, 0.8043, -0.5917, -0.8857, 0.0232, -0.3316, -0.3378, 1.1293, 0.8111, -1.2503, -0.5216, -0.4478, -1.6201, -1.3961, -1.1951, -0.0884, 1.7745])
tensor(0.3265)
# so what we did until now was, we fed our 27-dimensional inputs into a first layer of a neural net that has 27 neurons
# i.e. 27 inputs and 27 neurons, these neurons perform wx, they don't have a bias and they don't have a non-linearity like tanh
# we'll leave this layer to be linear
# additionally, we're not going to have any other layers. This is going to be it, the dumbest, smallest, simplest neural net: just a single linear layer
# now we want to explain what we want those 27 outputs to be
# for every input example, we're trying to produce a probability distribution for the next character in the sequence
# we see that some nums are -ve, some are +ve as they are coming out of torch.randn() : normal distribution parameters
# but what we want is something like in previous matrix, where each row told us the counts which we normalized to get probability distributions
# we want something similar to come out of a neural net: but what we have now is some +ve and -ve nums
# probabilities have a structure: are +ve, and sum to 1
# the output of our neural net can't be counts: they are integers and positive, not a good thing to output from a neural net
# so what to output? and how to interpret the 27 numbers: the 27 nums are giving us the log counts instead of giving counts directly like in previous matrix
# to get the counts from the log counts, we have to exponentiate them
# for exponentiation: if x = -ve, fx is always < 1. 
# if x = +ve, fx > 1
tensor([[ 0.7881, 1.4508, 0.5454, 1.3093, 0.6097, 0.8072, 1.4556, 0.3797, 0.7659, 0.5242, 2.5262, 3.9600, 0.6496, 1.4932, 0.5022, 0.5264, 3.7490, 0.9699, 2.9359, 2.5183, 2.1339, 0.7493, 0.5667, 1.2279, 0.3848, 0.8546, 1.1020], [ 3.4824, 0.5229, 18.9670, 0.3176, 0.5677, 1.3210, 0.3993, 6.5021, 0.3459, 0.6327, 4.6447, 1.1357, 1.1237, 0.9261, 2.1973, 1.3926, 0.9642, 1.5906, 0.6921, 1.0947, 1.5124, 0.5128, 1.1955, 0.5095, 0.8418, 2.4459, 2.1527], [ 0.7579, 0.3810, 0.1629, 2.5622, 2.5319, 0.3934, 1.5204, 1.2851, 2.6746, 3.1084, 0.9689, 0.3935, 1.3860, 0.3209, 0.3803, 4.9112, 2.1981, 1.2541, 2.3506, 0.0639, 7.5321, 0.3261, 0.1782, 0.8298, 1.1250, 0.4843, 1.6050], [ 0.7579, 0.3810, 0.1629, 2.5622, 2.5319, 0.3934, 1.5204, 1.2851, 2.6746, 3.1084, 0.9689, 0.3935, 1.3860, 0.3209, 0.3803, 4.9112, 2.1981, 1.2541, 2.3506, 0.0639, 7.5321, 0.3261, 0.1782, 0.8298, 1.1250, 0.4843, 1.6050], [ 0.8150, 0.8897, 0.5635, 0.5420, 0.3049, 4.7245, 1.4128, 0.3884, 0.8230, 2.0519, 0.3190, 1.8373, 1.8544, 4.2920, 0.6231, 0.2958, 1.8875, 1.6357, 3.0929, 0.1260, 0.9692, 5.5058, 0.2598, 1.2371, 0.3233, 0.3116, 0.1762]])
# note that all -ve nums turned to 0-1 and all +ve nums are >1.
# these exponentiated outputs can be interpreted (unlike prev -ve, +ve nums) like counts in our earlier matrix example
# so these values are >0 and take values based on settings of W. hmm nice
tensor([[0.0222, 0.0409, 0.0154, 0.0369, 0.0172, 0.0227, 0.0410, 0.0107, 0.0216, 0.0148, 0.0712, 0.1116, 0.0183, 0.0421, 0.0142, 0.0148, 0.1056, 0.0273, 0.0827, 0.0710, 0.0601, 0.0211, 0.0160, 0.0346, 0.0108, 0.0241, 0.0311], [0.0601, 0.0090, 0.3271, 0.0055, 0.0098, 0.0228, 0.0069, 0.1121, 0.0060, 0.0109, 0.0801, 0.0196, 0.0194, 0.0160, 0.0379, 0.0240, 0.0166, 0.0274, 0.0119, 0.0189, 0.0261, 0.0088, 0.0206, 0.0088, 0.0145, 0.0422, 0.0371], [0.0182, 0.0091, 0.0039, 0.0615, 0.0607, 0.0094, 0.0365, 0.0308, 0.0642, 0.0746, 0.0232, 0.0094, 0.0333, 0.0077, 0.0091, 0.1178, 0.0527, 0.0301, 0.0564, 0.0015, 0.1807, 0.0078, 0.0043, 0.0199, 0.0270, 0.0116, 0.0385], [0.0182, 0.0091, 0.0039, 0.0615, 0.0607, 0.0094, 0.0365, 0.0308, 0.0642, 0.0746, 0.0232, 0.0094, 0.0333, 0.0077, 0.0091, 0.1178, 0.0527, 0.0301, 0.0564, 0.0015, 0.1807, 0.0078, 0.0043, 0.0199, 0.0270, 0.0116, 0.0385], [0.0219, 0.0239, 0.0151, 0.0145, 0.0082, 0.1268, 0.0379, 0.0104, 0.0221, 0.0551, 0.0086, 0.0493, 0.0498, 0.1152, 0.0167, 0.0079, 0.0507, 0.0439, 0.0830, 0.0034, 0.0260, 0.1478, 0.0070, 0.0332, 0.0087, 0.0084, 0.0047]])
tensor(1.)
torch.Size([5, 27])
# what did we achieve?
# note: by 5 examples: we mean 1 word emma: eg 1: .e, eg2: em, eg3: mm, eg4: ma, eg5: a.
# for every single one of our 5 examples, we have a row that came out of a neural net, and because of the transformations, we ensure the outputs are probabilities
# logits = xenc @ W #wx
# counts = logits.exp()
# probs = counts / counts.sum(1, keepdims=True)

# all three operations above are differentiable, and we can backpropagate through them, and we get probability distributions
# for the zeroth example, where we fed in .e of emma. we input a . and expect a e as output with high probability
# we first had one_hot vector of zeroth example, i.e. we fed the dot into a neural net
# it went into the neural net and produced a probability distribution: probs[0]
tensor([0.0222, 0.0409, 0.0154, 0.0369, 0.0172, 0.0227, 0.0410, 0.0107, 0.0216, 0.0148, 0.0712, 0.1116, 0.0183, 0.0421, 0.0142, 0.0148, 0.1056, 0.0273, 0.0827, 0.0710, 0.0601, 0.0211, 0.0160, 0.0346, 0.0108, 0.0241, 0.0311])
tensor(1.)
torch.Size([27])
# continue from 1:26:24
# SUMMARY---------------------------------------------------------------------------------------------------------------------------------------
tensor([ 0,  5, 13, 13,  1])
tensor([ 5, 13, 13,  1,  0])
# FORWARD PASS
torch.Size([5, 27])

Softmax and loss

# softmax: often used layer in neural net that takes input z(a vector of logits, i.e. raw neural network outputs) into a probability distribution
# It exponentiates each logit and then normalizes by dividing by the sum
# of all exponentiated values.
# Input: any real numbers (positive, negative, or zero).
# Before normalization:
#   - negative logits become numbers between 0 and 1 after exponentiation
#   - zero becomes 1
#   - positive logits become numbers greater than 1
# Output:
#   - all values are positive
#   - all values sum to 1
#   - each value represents the probability of that class

# Softmax is commonly used as the final layer of a classifier to convert
# logits into class probabilities.
# Note: that in the above forward pass, everything is made up of differentiable layers
# we can backpropagate through everything there, eg. matrix multiplication is just multiplication and addition, exponentiation(can be backpropagated through)
# also sum and division are backpropagatable
--------
bigram example 1: .e (indexes 0,5)
input to the neural net: 0
output probabilities from the neural net: tensor([0.0607, 0.0100, 0.0123, 0.0042, 0.0168, 0.0123, 0.0027, 0.0232, 0.0137,
        0.0313, 0.0079, 0.0278, 0.0091, 0.0082, 0.0500, 0.2378, 0.0603, 0.0025,
        0.0249, 0.0055, 0.0339, 0.0109, 0.0029, 0.0198, 0.0118, 0.1537, 0.1459])
label (actual next character): 5
probability assigned by the neural net to the correct character: 0.01228625513613224
log likelihood: -4.399273872375488
negative log likelihood: 4.399273872375488
--------
bigram example 2: em (indexes 5,13)
input to the neural net: 5
output probabilities from the neural net: tensor([0.0290, 0.0796, 0.0248, 0.0521, 0.1989, 0.0289, 0.0094, 0.0335, 0.0097,
        0.0301, 0.0702, 0.0228, 0.0115, 0.0181, 0.0108, 0.0315, 0.0291, 0.0045,
        0.0916, 0.0215, 0.0486, 0.0300, 0.0501, 0.0027, 0.0118, 0.0022, 0.0472])
label (actual next character): 13
probability assigned by the neural net to the correct character: 0.018050700426101685
log likelihood: -4.014570713043213
negative log likelihood: 4.014570713043213
--------
bigram example 3: mm (indexes 13,13)
input to the neural net: 13
output probabilities from the neural net: tensor([0.0312, 0.0737, 0.0484, 0.0333, 0.0674, 0.0200, 0.0263, 0.0249, 0.1226,
        0.0164, 0.0075, 0.0789, 0.0131, 0.0267, 0.0147, 0.0112, 0.0585, 0.0121,
        0.0650, 0.0058, 0.0208, 0.0078, 0.0133, 0.0203, 0.1204, 0.0469, 0.0126])
label (actual next character): 13
probability assigned by the neural net to the correct character: 0.026691533625125885
log likelihood: -3.623408794403076
negative log likelihood: 3.623408794403076
--------
bigram example 4: ma (indexes 13,1)
input to the neural net: 13
output probabilities from the neural net: tensor([0.0312, 0.0737, 0.0484, 0.0333, 0.0674, 0.0200, 0.0263, 0.0249, 0.1226,
        0.0164, 0.0075, 0.0789, 0.0131, 0.0267, 0.0147, 0.0112, 0.0585, 0.0121,
        0.0650, 0.0058, 0.0208, 0.0078, 0.0133, 0.0203, 0.1204, 0.0469, 0.0126])
label (actual next character): 1
probability assigned by the neural net to the correct character: 0.07367686182260513
log likelihood: -2.6080665588378906
negative log likelihood: 2.6080665588378906
--------
bigram example 5: a. (indexes 1,0)
input to the neural net: 1
output probabilities from the neural net: tensor([0.0150, 0.0086, 0.0396, 0.0100, 0.0606, 0.0308, 0.1084, 0.0131, 0.0125,
        0.0048, 0.1024, 0.0086, 0.0988, 0.0112, 0.0232, 0.0207, 0.0408, 0.0078,
        0.0899, 0.0531, 0.0463, 0.0309, 0.0051, 0.0329, 0.0654, 0.0503, 0.0091])
label (actual next character): 0
probability assigned by the neural net to the correct character: 0.014977526850998402
log likelihood: -4.201204299926758
negative log likelihood: 4.201204299926758
======
average negative log likelihood, i.e. loss= 3.7693049907684326
# we have a loss function: nlls.mean().item()
# and this is made up of only differentiable operations
# and we can minimize loss but tuning the W(weights) by computing the gradients of the loss w.r.t W matrices, i.e. gradient-based optimization
# what we'll do is very similar to what we did in micrograd
# in micrograd we had four input examples and their targets
# xs = [
#     [2.0, 3.0, -1.0],
#     [3.0, -1.0, 0.5],
#     [0.5, 1.0, 1.0],
#     [1.0, 1.0, -1.0]
# ]
# ys = [1.0, -1.0, -1.0, 1.0] # desired targets,
# but our input has xs as tensor([0, 5, 13, 13, 1]) which can be converted to one_hot and be 5x27 just like xs above
# but we won't have a desired ys, 
# instead we will try to minimize our loss upto the point that it is similar to the loss computed from earlier method 1: table approach
# in micrograd, our neural net was n(x) which was a multi layer perceptron
# say we want input layer with 3 neurons, 2 hidden layers each having four neurons and an output unit

# x = [2.0, 3.0, -1.0]
# n = MLP(3, [4, 4, 1])# we want 3 inputs into 2 layers of 4 and one output
# n(x)
# but our neural net is just a single linear layer of matrix multiplication(wx) which is followed by a softmax
# our neural network: logits = xenc @ W, followed by 2 lines of softmax
# in micrograd, loss was mean square error, we just subtracted prediction from ground truth, then squared it and summed it all up
# ypred = [n(x) for x in xs]
# loss = sum([(ygt - yout)**2 for ygt, yout in zip(ys, ypred)])
# here, loss=single num that represented the quality of neural net, and when loss is low, it showed that the neural net was predicting correctly
# then we iterated over all parameters, and set gradient to zero
# and called loss.backward
# backward pass
    # for p in n.parameters():
    #     p.grad = 0.0
    # loss.backward() #initiated backpropagation at final output node of loss, i.e. just filled the gradients that were recently reset to 0
# and during backpropagation, we updated/nudged all the parameters slightly in the negative direction of gradient, i.e. tuning weights, i.e. training
# update: gradient descent
    # for p in n.parameters():
    #     p.data += (-0.05) * p.grad
# we're going to do the same thing here too
# here, probs is our ypred
# to evaluate loss: we don't use MSE, we have negative log likelihood cuz in this case we're doing classification(not regression like in micrograd)
torch.Size([5, 27])
tensor([ 0,  5, 13, 13,  1])
tensor([ 5, 13, 13,  1,  0])
tensor([[0.0607, 0.0100, 0.0123, 0.0042, 0.0168, 0.0123, 0.0027, 0.0232, 0.0137, 0.0313, 0.0079, 0.0278, 0.0091, 0.0082, 0.0500, 0.2378, 0.0603, 0.0025, 0.0249, 0.0055, 0.0339, 0.0109, 0.0029, 0.0198, 0.0118, 0.1537, 0.1459], [0.0290, 0.0796, 0.0248, 0.0521, 0.1989, 0.0289, 0.0094, 0.0335, 0.0097, 0.0301, 0.0702, 0.0228, 0.0115, 0.0181, 0.0108, 0.0315, 0.0291, 0.0045, 0.0916, 0.0215, 0.0486, 0.0300, 0.0501, 0.0027, 0.0118, 0.0022, 0.0472], [0.0312, 0.0737, 0.0484, 0.0333, 0.0674, 0.0200, 0.0263, 0.0249, 0.1226, 0.0164, 0.0075, 0.0789, 0.0131, 0.0267, 0.0147, 0.0112, 0.0585, 0.0121, 0.0650, 0.0058, 0.0208, 0.0078, 0.0133, 0.0203, 0.1204, 0.0469, 0.0126], [0.0312, 0.0737, 0.0484, 0.0333, 0.0674, 0.0200, 0.0263, 0.0249, 0.1226, 0.0164, 0.0075, 0.0789, 0.0131, 0.0267, 0.0147, 0.0112, 0.0585, 0.0121, 0.0650, 0.0058, 0.0208, 0.0078, 0.0133, 0.0203, 0.1204, 0.0469, 0.0126], [0.0150, 0.0086, 0.0396, 0.0100, 0.0606, 0.0308, 0.1084, 0.0131, 0.0125, 0.0048, 0.1024, 0.0086, 0.0988, 0.0112, 0.0232, 0.0207, 0.0408, 0.0078, 0.0899, 0.0531, 0.0463, 0.0309, 0.0051, 0.0329, 0.0654, 0.0503, 0.0091]])
(tensor(0.0123), tensor(0.0181), tensor(0.0267), tensor(0.0737), tensor(0.0150))
tensor([0, 1, 2, 3, 4])
tensor([ 5, 13, 13,  1,  0])
tensor([0.0123, 0.0181, 0.0267, 0.0737, 0.0150])
# so we plucked out the probabilities assigned by the neural network to the correct next characters for 5 bigrams we're working with
tensor([-4.3993, -4.0146, -3.6234, -2.6081, -4.2012])
tensor(-3.7693)
tensor(3.7693)
# note: this loss matches the loss we obtained above(but this is a vectorized form)
# average negative log likelihood, i.e. loss= 3.7693049907684326
# so, we defined our forward pass: we have our probs(ypred) and loss
# now, we do backward pass

Gradient-based learning

# backward pass
# 1. make sure all gradients are reset: 0
# in pytorch, we can set all gradients to zero or None
# setting gradients to None: more efficient as it is interpreted as lack of gradient, and same as zeros too
# then we do loss.backward, but remember that pytorch requires we pass in requires_grad=True while generating the random weights initially
# in our forward pass above, pytorch actually keeps track of all the operations we did under the hood and builds a full computational graph
# those graphs exist inside pytorch, it knows all the dependencies and all the mathematical operations
# this loss.backward() just fills in the gradients of all the intermediates all the way back to W(parameters of our neural net)
torch.Size([27, 27])
torch.Size([27, 27])
# so, W[0,0] =  0.0121 is +ve
# it tells us that slightly increasing it will increase the loss: eg. W[0,0] += h
tensor([[ 0.0121, 0.0020, 0.0025, 0.0008, 0.0034, -0.1975, 0.0005, 0.0046, 0.0027, 0.0063, 0.0016, 0.0056, 0.0018, 0.0016, 0.0100, 0.0476, 0.0121, 0.0005, 0.0050, 0.0011, 0.0068, 0.0022, 0.0006, 0.0040, 0.0024, 0.0307, 0.0292], [-0.1970, 0.0017, 0.0079, 0.0020, 0.0121, 0.0062, 0.0217, 0.0026, 0.0025, 0.0010, 0.0205, 0.0017, 0.0198, 0.0022, 0.0046, 0.0041, 0.0082, 0.0016, 0.0180, 0.0106, 0.0093, 0.0062, 0.0010, 0.0066, 0.0131, 0.0101, 0.0018], [ 0.0000, 0.0000, 0.0000, 0.0000, 0.0000, 0.0000, 0.0000, 0.0000, 0.0000, 0.0000, 0.0000, 0.0000, 0.0000, 0.0000, 0.0000, 0.0000, 0.0000, 0.0000, 0.0000, 0.0000, 0.0000, 0.0000, 0.0000, 0.0000, 0.0000, 0.0000, 0.0000], [ 0.0000, 0.0000, 0.0000, 0.0000, 0.0000, 0.0000, 0.0000, 0.0000, 0.0000, 0.0000, 0.0000, 0.0000, 0.0000, 0.0000, 0.0000, 0.0000, 0.0000, 0.0000, 0.0000, 0.0000, 0.0000, 0.0000, 0.0000, 0.0000, 0.0000, 0.0000, 0.0000], [ 0.0000, 0.0000, 0.0000, 0.0000, 0.0000, 0.0000, 0.0000, 0.0000, 0.0000, 0.0000, 0.0000, 0.0000, 0.0000, 0.0000, 0.0000, 0.0000, 0.0000, 0.0000, 0.0000, 0.0000, 0.0000, 0.0000, 0.0000, 0.0000, 0.0000, 0.0000, 0.0000], [ 0.0058, 0.0159, 0.0050, 0.0104, 0.0398, 0.0058, 0.0019, 0.0067, 0.0019, 0.0060, 0.0140, 0.0046, 0.0023, -0.1964, 0.0022, 0.0063, 0.0058, 0.0009, 0.0183, 0.0043, 0.0097, 0.0060, 0.0100, 0.0005, 0.0024, 0.0004, 0.0094], [ 0.0000, 0.0000, 0.0000, 0.0000, 0.0000, 0.0000, 0.0000, 0.0000, 0.0000, 0.0000, 0.0000, 0.0000, 0.0000, 0.0000, 0.0000, 0.0000, 0.0000, 0.0000, 0.0000, 0.0000, 0.0000, 0.0000, 0.0000, 0.0000, 0.0000, 0.0000, 0.0000], [ 0.0000, 0.0000, 0.0000, 0.0000, 0.0000, 0.0000, 0.0000, 0.0000, 0.0000, 0.0000, 0.0000, 0.0000, 0.0000, 0.0000, 0.0000, 0.0000, 0.0000, 0.0000, 0.0000, 0.0000, 0.0000, 0.0000, 0.0000, 0.0000, 0.0000, 0.0000, 0.0000], [ 0.0000, 0.0000, 0.0000, 0.0000, 0.0000, 0.0000, 0.0000, 0.0000, 0.0000, 0.0000, 0.0000, 0.0000, 0.0000, 0.0000, 0.0000, 0.0000, 0.0000, 0.0000, 0.0000, 0.0000, 0.0000, 0.0000, 0.0000, 0.0000, 0.0000, 0.0000, 0.0000], [ 0.0000, 0.0000, 0.0000, 0.0000, 0.0000, 0.0000, 0.0000, 0.0000, 0.0000, 0.0000, 0.0000, 0.0000, 0.0000, 0.0000, 0.0000, 0.0000, 0.0000, 0.0000, 0.0000, 0.0000, 0.0000, 0.0000, 0.0000, 0.0000, 0.0000, 0.0000, 0.0000], [ 0.0000, 0.0000, 0.0000, 0.0000, 0.0000, 0.0000, 0.0000, 0.0000, 0.0000, 0.0000, 0.0000, 0.0000, 0.0000, 0.0000, 0.0000, 0.0000, 0.0000, 0.0000, 0.0000, 0.0000, 0.0000, 0.0000, 0.0000, 0.0000, 0.0000, 0.0000, 0.0000], [ 0.0000, 0.0000, 0.0000, 0.0000, 0.0000, 0.0000, 0.0000, 0.0000, 0.0000, 0.0000, 0.0000, 0.0000, 0.0000, 0.0000, 0.0000, 0.0000, 0.0000, 0.0000, 0.0000, 0.0000, 0.0000, 0.0000, 0.0000, 0.0000, 0.0000, 0.0000, 0.0000], [ 0.0000, 0.0000, 0.0000, 0.0000, 0.0000, 0.0000, 0.0000, 0.0000, 0.0000, 0.0000, 0.0000, 0.0000, 0.0000, 0.0000, 0.0000, 0.0000, 0.0000, 0.0000, 0.0000, 0.0000, 0.0000, 0.0000, 0.0000, 0.0000, 0.0000, 0.0000, 0.0000], [ 0.0125, -0.1705, 0.0194, 0.0133, 0.0270, 0.0080, 0.0105, 0.0100, 0.0490, 0.0066, 0.0030, 0.0316, 0.0052, -0.1893, 0.0059, 0.0045, 0.0234, 0.0049, 0.0260, 0.0023, 0.0083, 0.0031, 0.0053, 0.0081, 0.0482, 0.0187, 0.0051], [ 0.0000, 0.0000, 0.0000, 0.0000, 0.0000, 0.0000, 0.0000, 0.0000, 0.0000, 0.0000, 0.0000, 0.0000, 0.0000, 0.0000, 0.0000, 0.0000, 0.0000, 0.0000, 0.0000, 0.0000, 0.0000, 0.0000, 0.0000, 0.0000, 0.0000, 0.0000, 0.0000], [ 0.0000, 0.0000, 0.0000, 0.0000, 0.0000, 0.0000, 0.0000, 0.0000, 0.0000, 0.0000, 0.0000, 0.0000, 0.0000, 0.0000, 0.0000, 0.0000, 0.0000, 0.0000, 0.0000, 0.0000, 0.0000, 0.0000, 0.0000, 0.0000, 0.0000, 0.0000, 0.0000], [ 0.0000, 0.0000, 0.0000, 0.0000, 0.0000, 0.0000, 0.0000, 0.0000, 0.0000, 0.0000, 0.0000, 0.0000, 0.0000, 0.0000, 0.0000, 0.0000, 0.0000, 0.0000, 0.0000, 0.0000, 0.0000, 0.0000, 0.0000, 0.0000, 0.0000, 0.0000, 0.0000], [ 0.0000, 0.0000, 0.0000, 0.0000, 0.0000, 0.0000, 0.0000, 0.0000, 0.0000, 0.0000, 0.0000, 0.0000, 0.0000, 0.0000, 0.0000, 0.0000, 0.0000, 0.0000, 0.0000, 0.0000, 0.0000, 0.0000, 0.0000, 0.0000, 0.0000, 0.0000, 0.0000], [ 0.0000, 0.0000, 0.0000, 0.0000, 0.0000, 0.0000, 0.0000, 0.0000, 0.0000, 0.0000, 0.0000, 0.0000, 0.0000, 0.0000, 0.0000, 0.0000, 0.0000, 0.0000, 0.0000, 0.0000, 0.0000, 0.0000, 0.0000, 0.0000, 0.0000, 0.0000, 0.0000], [ 0.0000, 0.0000, 0.0000, 0.0000, 0.0000, 0.0000, 0.0000, 0.0000, 0.0000, 0.0000, 0.0000, 0.0000, 0.0000, 0.0000, 0.0000, 0.0000, 0.0000, 0.0000, 0.0000, 0.0000, 0.0000, 0.0000, 0.0000, 0.0000, 0.0000, 0.0000, 0.0000], [ 0.0000, 0.0000, 0.0000, 0.0000, 0.0000, 0.0000, 0.0000, 0.0000, 0.0000, 0.0000, 0.0000, 0.0000, 0.0000, 0.0000, 0.0000, 0.0000, 0.0000, 0.0000, 0.0000, 0.0000, 0.0000, 0.0000, 0.0000, 0.0000, 0.0000, 0.0000, 0.0000], [ 0.0000, 0.0000, 0.0000, 0.0000, 0.0000, 0.0000, 0.0000, 0.0000, 0.0000, 0.0000, 0.0000, 0.0000, 0.0000, 0.0000, 0.0000, 0.0000, 0.0000, 0.0000, 0.0000, 0.0000, 0.0000, 0.0000, 0.0000, 0.0000, 0.0000, 0.0000, 0.0000], [ 0.0000, 0.0000, 0.0000, 0.0000, 0.0000, 0.0000, 0.0000, 0.0000, 0.0000, 0.0000, 0.0000, 0.0000, 0.0000, 0.0000, 0.0000, 0.0000, 0.0000, 0.0000, 0.0000, 0.0000, 0.0000, 0.0000, 0.0000, 0.0000, 0.0000, 0.0000, 0.0000], [ 0.0000, 0.0000, 0.0000, 0.0000, 0.0000, 0.0000, 0.0000, 0.0000, 0.0000, 0.0000, 0.0000, 0.0000, 0.0000, 0.0000, 0.0000, 0.0000, 0.0000, 0.0000, 0.0000, 0.0000, 0.0000, 0.0000, 0.0000, 0.0000, 0.0000, 0.0000, 0.0000], [ 0.0000, 0.0000, 0.0000, 0.0000, 0.0000, 0.0000, 0.0000, 0.0000, 0.0000, 0.0000, 0.0000, 0.0000, 0.0000, 0.0000, 0.0000, 0.0000, 0.0000, 0.0000, 0.0000, 0.0000, 0.0000, 0.0000, 0.0000, 0.0000, 0.0000, 0.0000, 0.0000], [ 0.0000, 0.0000, 0.0000, 0.0000, 0.0000, 0.0000, 0.0000, 0.0000, 0.0000, 0.0000, 0.0000, 0.0000, 0.0000, 0.0000, 0.0000, 0.0000, 0.0000, 0.0000, 0.0000, 0.0000, 0.0000, 0.0000, 0.0000, 0.0000, 0.0000, 0.0000, 0.0000], [ 0.0000, 0.0000, 0.0000, 0.0000, 0.0000, 0.0000, 0.0000, 0.0000, 0.0000, 0.0000, 0.0000, 0.0000, 0.0000, 0.0000, 0.0000, 0.0000, 0.0000, 0.0000, 0.0000, 0.0000, 0.0000, 0.0000, 0.0000, 0.0000, 0.0000, 0.0000, 0.0000]])
3.7693049907684326
# run cell of forward pass then backward pass again and again manually to see loss decreasing, or you could just use loop
3.6690573692321777
3.649179458618164
3.629345655441284
3.609556198120117
3.589811325073242
3.5701117515563965
3.5504581928253174
3.530850648880005
3.5112900733947754
3.49177622795105
3.4723105430603027
3.452892303466797
3.433523178100586
3.4142024517059326
3.3949317932128906
3.3757102489471436
# getting lower loss: equivalent to: neural network is assigning high probabilities to the correct next characters
# rearranged everything and put together------------------------FROM START-------------------------------------------------------
total number of examples/bigrams: 228146

Comparing counts with learning

# what number do we expect to be our desired loss?
# we expect something we got originally when we had optimized just by counting, our loss was roughly 2.47 after smoothing
# getting approximately 2.46 is the goal, and not down to 0.0003, but why?
# because we're not taking any additional information, we're just taking the previous character and trying to predict the next one
# but instead of trying to do it by explicit counting and normalizing, we're doing it with gradient based learning
# this gradient based approach is way more flexible and scalable, we can now expand this approach and complexify the neural net
# currently we're just taking 1 char and predicting next char, but we can substantially/largely iterate on this
# eg. we take multiple previous characters instead of just 1, and feed it into very complex neural nets too
# but fundamentally, the output of the neural nets will just be logits, and the logits will go through the exact same transformation
# we'll take it through softmax, calculate the loss function and the negative log likelihood, and do gradient-based optimization
# as we complexify the neural nets and work all the way upto transformers, the structure is the same fundamentally
# the only thing that will change/be more complex is the way we,
# the way we do the forward pass where we take the previous chars to calculate the logits for the next character
# in our initial table based approach, it is not scalable, there'll be way too many combinations of what previous characters could be
# it works for 1char input, 1 char output, but doesn't work well for 10char input 1 char output, it was an unscalable approach
# but our neural network approach is very scalable and flexible too
3.758953332901001
3.371100664138794
3.1540427207946777
3.020374059677124
2.927711248397827
2.8604023456573486
2.8097290992736816
2.7701022624969482
2.7380728721618652
2.711496353149414
2.6890032291412354
2.6696884632110596
2.6529300212860107
2.638277292251587
2.6253879070281982
2.613990545272827
2.60386323928833
2.5948219299316406
2.5867116451263428
2.5794036388397217
2.572789192199707
2.5667760372161865
2.5612878799438477
2.5562589168548584
2.551633596420288
2.547365665435791
2.5434155464172363
2.5397486686706543
2.5363364219665527
2.533154249191284
2.5301806926727295
2.5273969173431396
2.5247862339019775
2.522334575653076
2.520028829574585
2.5178580284118652
2.515810489654541
2.513878107070923
2.512052059173584
2.510324001312256
2.5086872577667236
2.5071349143981934
2.5056610107421875
2.504261016845703
2.502929210662842
2.5016608238220215
2.5004520416259766
2.4992988109588623
2.498197317123413
2.497144937515259
2.496137857437134
2.495173692703247
2.4942493438720703
2.49336314201355
2.4925124645233154
2.4916954040527344
2.4909095764160156
2.4901537895202637
2.4894261360168457
2.488725423812866
2.488049268722534
2.4873974323272705
2.4867680072784424
2.4861602783203125
2.4855728149414062
2.4850046634674072
2.484455108642578
2.4839231967926025
2.483408212661743
2.4829084873199463
2.482424259185791
2.48195481300354
2.481499195098877
2.4810571670532227
2.4806275367736816
2.480210065841675
2.479804515838623
2.479410409927368
2.4790267944335938
2.4786536693573
2.478290557861328
2.4779367446899414
2.4775924682617188
2.477257251739502
2.4769303798675537
2.476611852645874
2.4763007164001465
2.4759979248046875
2.4757025241851807
2.475414276123047
2.475132703781128
2.474858045578003
2.4745898246765137
2.474327564239502
2.474071979522705
2.4738216400146484
2.4735772609710693
2.4733383655548096
2.47310471534729
2.4728758335113525
2.4726529121398926
2.4724340438842773
2.4722201824188232
2.472010850906372
2.4718058109283447
2.4716053009033203
2.471409320831299
2.471216917037964
2.4710283279418945
2.470843553543091
2.4706625938415527
2.4704854488372803
2.4703118801116943
2.4701414108276367
2.4699742794036865
2.4698104858398438
2.4696500301361084
2.469492197036743
2.4693377017974854
2.4691858291625977
2.4690372943878174
2.468891143798828
2.468747615814209
2.46860671043396
2.468468427658081
2.468332529067993
2.4681990146636963
2.4680681228637695
2.4679393768310547
2.4678127765655518
2.46768856048584
2.4675662517547607
2.4674463272094727
2.467327833175659
2.467211961746216
2.467097759246826
2.4669857025146484
2.4668753147125244
2.466766357421875
2.4666597843170166
2.466554641723633
2.4664509296417236
2.4663491249084473
2.4662492275238037
2.4661505222320557
2.4660532474517822
2.4659576416015625
2.4658634662628174
2.4657704830169678
2.465679407119751
2.4655895233154297
2.465500593185425
2.4654135704040527
2.465327501296997
2.465242624282837
2.4651591777801514
2.4650766849517822
2.4649956226348877
2.4649155139923096
2.464836597442627
2.46475887298584
2.464682102203369
2.464606285095215
2.464531660079956
2.4644579887390137
2.464385509490967
2.4643139839172363
2.464243173599243
2.4641737937927246
2.464104652404785
2.464036703109741
2.4639699459075928
2.4639039039611816
2.4638383388519287
2.463773727416992
2.463710308074951
2.4636473655700684
2.463585376739502
2.4635238647460938
2.463463306427002
2.4634037017822266
2.4633448123931885
2.4632863998413086
2.463228702545166
2.4631717205047607
2.463115692138672
2.463059902191162
2.4630050659179688
2.4629504680633545
2.4628970623016357
2.462843894958496
2.4627914428710938
2.462739944458008
2.462688684463501
2.4626379013061523
2.462587594985962
2.462538242340088
2.462489128112793
2.4624407291412354
2.462393045425415
tensor([[1., 0., 0.,  ..., 0., 0., 0.],
        [0., 0., 0.,  ..., 0., 0., 0.],
        [0., 0., 0.,  ..., 0., 0., 0.],
        ...,
        [0., 0., 0.,  ..., 0., 1., 0.],
        [0., 0., 0.,  ..., 0., 0., 1.],
        [0., 0., 0.,  ..., 1., 0., 0.]])
# ahh ahh, remember that in the table method case where table=N, we used the first character to index the row of the array
# that row gave us the probability distribution for the next character
# i.e. first character was used as a lookup into matrix N to get probability distribution
# but what happens in neural network case is very similar too
# we take the index by encoding xs as one-hot and multiplying it by W
# then logits becomes the row of W  
# logits then gets exponentiated to create the counts then gets normalized and becomes probability
# so, W is almost the same as N, but W is the log counts, not the counts itself
# thus we can say W.exp = N table matrix
# N was filled by populating the counts of bigram
# but in gradient based framework, we initialized W randomly, then we let the loss guide us to arrive at the exact same array
# qn: in this simple case we got loss from table case and used that to get loss for neural net.
# but what about other complex cases where we might not construct the table based on counts?
# remember the smoothing in N case where we added fake count 1 to every entry to prevent probability from being 0 to any bigram
# but if we increase the count, probability becomes more and more uniform

Regularization and sampling

# and interestingly, our gradient-based optimization of neural network has an equivalent to smoothing
# We initialized W randomly right? but what if we initialized W to be 0?
# if all entries of W are zero, then logits will be all 0 since logits = xenc @ W
# then counts = exponentiating logits becomes all 1
# finally probability will become exactly uniform
# thus when values in W are all equal to each other or all 0, then probabilities come out completely uniform
# IMPORTANT NOTE:
# trying to incentivize W's entries to be near zero's = equivalent to = label smoothing, more the smooth distribution
# SO, THIS BRINGS US TO

Regularization: we can augment the loss function to have a small component called regularization loss

tensor(1536.7557, grad_fn=<SumBackward0>)
tensor(2.1080, grad_fn=<MeanBackward0>)
# we can like actually add this to our actual loss computation
# regularization strength * regularization loss
# this optimization has 2 components now
# 1. it's trying to work out all the probabilities
# 2. the additional component + 0.01 * ((W)**2).mean() tries to make all W's be zero
# cuz if w's are non-zero, there is high loss, minimizing the loss is trying to make W zero
# adding this: + 0.01 * ((W)**2).mean(), is like pushing or adding a gravity force to make W 0 to reduce the loss
# so W want's to be 0 and the probabilities want to be uniform but simultaneously they also want to match up the probabilities as indicated by the data
# this strength of regularization controls the amount of counts we add eg.1 or 100, in P = (P+1).float()
# IMPORTANT
# adding more counts in count based prediction corresponds to increasing regularization strength in neural nets approach
2.4834256172180176
2.4833858013153076
2.4833483695983887
2.4833130836486816
2.4832799434661865
2.4832475185394287
2.4832167625427246
2.483186960220337
2.4831581115722656
2.4831299781799316
2.483102798461914
2.483076333999634
2.48305082321167
2.4830257892608643
2.483001232147217
2.4829773902893066
2.482954263687134
2.48293137550354
2.4829089641571045
2.4828872680664062
2.482866048812866
2.4828453063964844
2.4828240871429443
2.482804298400879
2.4827845096588135
2.482764959335327
2.48274564743042
2.482727289199829
2.48270845413208
2.4826903343200684
2.4826724529266357
2.4826548099517822
2.482637643814087
2.4826207160949707
2.4826037883758545
2.4825873374938965
2.4825706481933594
2.4825544357299805
2.482538938522339
2.482523202896118
2.4825077056884766
2.482492446899414
2.4824774265289307
2.4824624061584473
2.482448101043701
2.482433557510376
2.48241925239563
2.4824047088623047
2.482390880584717
2.482377290725708
2.48236346244812
2.4823503494262695
2.4823367595672607
2.48232364654541
2.4823105335235596
2.482297897338867
2.482285261154175
2.482272148132324
2.482259750366211
2.4822475910186768
2.4822356700897217
2.4822235107421875
2.4822115898132324
2.4821999073028564
2.4821882247924805
2.4821765422821045
2.4821650981903076
2.48215389251709
2.482142686843872
2.4821319580078125
2.4821207523345947
2.482109308242798
2.4820988178253174
2.482088327407837
2.4820778369903564
2.4820668697357178
2.4820566177368164
2.482046365737915
2.4820363521575928
2.4820261001586914
2.482016086578369
2.482006311416626
2.481996536254883
2.4819867610931396
2.4819769859313965
2.4819676876068115
2.4819583892822266
2.4819488525390625
2.4819395542144775
2.4819300174713135
2.4819211959838867
2.4819116592407227
2.481903314590454
2.4818944931030273
2.4818851947784424
2.4818766117095947
2.481868028640747
2.4818594455718994
2.4818508625030518
2.481842279434204
2.4818339347839355
2.481825351715088
2.4818174839019775
2.481809139251709
2.4818010330200195
2.481793165206909
2.4817850589752197
2.4817771911621094
2.481769561767578
2.4817614555358887
2.4817538261413574
2.481745719909668
2.481738567352295
2.4817306995391846
2.4817233085632324
2.4817159175872803
2.481708526611328
2.481701135635376
2.4816946983337402
2.48168683052063
2.481680154800415
2.481672763824463
2.48166561126709
2.481658697128296
2.481651782989502
2.481645107269287
2.481637954711914
2.481631278991699
2.4816246032714844
2.4816179275512695
2.481611490249634
2.48160457611084
2.481598377227783
2.4815919399261475
2.4815855026245117
2.481579303741455
2.4815728664398193
2.4815661907196045
2.481560468673706
2.4815540313720703
2.4815478324890137
2.481541395187378
2.4815359115600586
2.481529712677002
2.4815235137939453
2.481517791748047
2.4815118312835693
2.481506109237671
2.4815001487731934
2.481494665145874
2.4814887046813965
2.481482982635498
2.481477737426758
2.4814720153808594
2.481466293334961
2.4814608097076416
2.4814553260803223
2.481450080871582
2.4814443588256836
2.4814391136169434
2.481433629989624
2.481428384780884
2.4814231395721436
2.4814178943634033
2.481412887573242
2.481407403945923
2.4814023971557617
2.4813976287841797
2.4813923835754395
2.4813873767852783
2.481382369995117
2.481377124786377
2.481372594833374
2.481367826461792
2.481362819671631
2.481358051300049
2.481353282928467
2.4813485145568848
2.4813437461853027
2.4813392162323
2.4813344478607178
2.4813294410705566
2.481325149536133
2.481320381164551
2.481316328048706
2.481311559677124
2.481306791305542
2.4813027381896973
2.4812979698181152
2.4812936782836914
2.4812893867492676
2.481285333633423
2.481280565261841
2.481276512145996
2.4812722206115723
2.4812681674957275
2.4812636375427246
2.48125958442688
2.481255292892456
2.4812514781951904
cava.
ayamaijowy.
aianeta.
rl.
miryndardyrioma.
kiinninaswila.
en.
ahlanyanlen.
tav.
sissth.
eygerinan.
sullyukrbrilahubrnidrianelkheyneligalah.
ka.
f.
elah.
cexze.
momasurailezityha.
konimittain.
llayn.
ka.
# result kinda similar: earlier we used to get cexze for table based too, but now after 1 day getting cava idk
# but no worry its due to randomness and sampling and the results don't have to be same too, just similar
# both the models are identical
# SUMMARY:
# WE INTRODUCED THE BIGRAM CHARACTER-LEVEL LANGUAGE MODEL
# WE LEARNT HOW TO TRAIN THE MODEL(based on count/sum and for neural net too)
# WE LEARNT HOW TO SAMPLE FROM THE MODEL
# WE LEARNT HOW TO EVALUATE THE QUALITY OF THE MODEL USING NEGATIVE LOG LIKELIHOOD LOSS
# WE TRAINED THE MODEL IN 2 DIFFERENT WAYS THAT GIVE THE SAME RESULT AND THE SAME MODEL

# WAY 1:
# COUNTED THE FREQUENCY OF ALL BIGRAMS AND JUST NORMALIZED
# WAY 2:
# USE NLL LOSS AS GUIDE TO OPTIMIZE COUNT ARRAY TO MINIMIZE LOSS IN GRADIENT BASED FRAMEWORK

# OBSERVED: BOTH GAVE SAME RESULT
# WAY 2: VERY FLEXIBLE, currently: our neural network is very simple
# we take 1 char, we take 1 char through single linear layer to calculate LOGITS
# we will COMPLEXIFY, take more inputs than just 1 and feed them into the neural net
# the neural net's output will be the same logits which will be normalized in the same way, 
# the process of loss computation, gradient based framework will all be identical
# but more optimized and better output will be received
# and the neural net will complexify to transformers