import%20marimo%0A%0A__generated_with%20%3D%20%220.14.16%22%0Aapp%20%3D%20marimo.App(width%3D%22full%22)%0A%0A%0A%40app.cell(hide_code%3DTrue)%0Adef%20_()%3A%0A%20%20%20%20import%20marimo%20as%20mo%0A%0A%20%20%20%20mo.md(%0A%20%20%20%20%20%20%20%20r%22%22%22%0A%20%20%20%20%20%20%20%20%23%20N-gram%20Language%20Model%20Parameter%20Analysis%0A%0A%20%20%20%20%20%20%20%20This%20notebook%20explores%20how%20different%20parameters%20affect%20the%20performance%20of%20an%20n-gram%20language%20model.%0A%20%20%20%20%20%20%20%20We'll%20systematically%20vary%20each%20parameter%20and%20visualize%20its%20impact%20on%20training%20and%20test%20loss.%0A%0A%20%20%20%20%20%20%20%20Disclaimer%3A%20This%20was%20originally%20intended%20to%20be%20an%20interactive%20application%20to%20run%20in%20your%20browser.%20But%20due%20to%20limitations%20involving%20running%20torch%20in%20WASM%2C%20this%20has%20become%20a%20static%20notebook.%20I%20used%20Claude%20to%20rewrite%20the%20interactive%20code%20into%20a%20linear%20static%20notebook.%20The%20original%20interactive%20notebook%20can%20be%20found%20here%3A%20https%3A%2F%2Fgithub.com%2Fnelmaliki%2Fmachine-learning-notes%2Fblob%2Fmain%2Fmakemore%2Fngram.py%0A%0A%20%20%20%20%20%20%20%20Based%20on%20%5BAndrej%20Karpathy's%20makemore%20video%5D(https%3A%2F%2Fwww.youtube.com%2Fwatch%3Fv%3DPaCmpygFfXo%26t%3D382s)%0A%20%20%20%20%20%20%20%20%22%22%22%0A%20%20%20%20)%0A%20%20%20%20return%20(mo%2C)%0A%0A%0A%40app.cell(hide_code%3DTrue)%0Adef%20_()%3A%0A%20%20%20%20import%20matplotlib.pyplot%20as%20plt%0A%20%20%20%20import%20numpy%20as%20np%0A%20%20%20%20import%20torch%0A%20%20%20%20import%20torch.nn.functional%20as%20F%0A%0A%20%20%20%20%23%20Load%20data%0A%20%20%20%20names%20%3D%20open(%22names.txt%22%2C%20%22r%22).read().splitlines()%0A%20%20%20%20separator%20%3D%20%22.%22%0A%20%20%20%20chars%20%3D%20%5Bseparator%5D%20%2B%20sorted(list(set(%22%22.join(names))))%0A%20%20%20%20char_lookup%20%3D%20%7Bchar%3A%20index%20for%20index%2C%20char%20in%20enumerate(chars)%7D%0A%0A%20%20%20%20%23%20Get%20device%20for%20computation%0A%20%20%20%20def%20get_device()%3A%0A%20%20%20%20%20%20%20%20if%20torch.cuda.is_available()%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20return%20torch.device(%22cuda%22)%0A%20%20%20%20%20%20%20%20if%20hasattr(torch.backends%2C%20%22mps%22)%20and%20torch.backends.mps.is_available()%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20return%20torch.device(%22mps%22)%0A%20%20%20%20%20%20%20%20return%20torch.device(%22cpu%22)%0A%0A%20%20%20%20device%20%3D%20get_device()%0A%20%20%20%20print(f%22Using%20device%3A%20%7Bdevice%7D%22)%0A%0A%20%20%20%20%23%20Define%20ngram_sizes%20once%20for%20all%20analyses%0A%20%20%20%20ngram_sizes%20%3D%20range(1%2C%208)%0A%20%20%20%20return%20(%0A%20%20%20%20%20%20%20%20F%2C%0A%20%20%20%20%20%20%20%20char_lookup%2C%0A%20%20%20%20%20%20%20%20chars%2C%0A%20%20%20%20%20%20%20%20device%2C%0A%20%20%20%20%20%20%20%20names%2C%0A%20%20%20%20%20%20%20%20ngram_sizes%2C%0A%20%20%20%20%20%20%20%20np%2C%0A%20%20%20%20%20%20%20%20plt%2C%0A%20%20%20%20%20%20%20%20separator%2C%0A%20%20%20%20%20%20%20%20torch%2C%0A%20%20%20%20)%0A%0A%0A%40app.cell(hide_code%3DTrue)%0Adef%20_(F%2C%20char_lookup%2C%20chars%2C%20device%2C%20names%2C%20separator%2C%20torch)%3A%0A%20%20%20%20def%20create_dataset(name_list%2C%20num_preceding_chars)%3A%0A%20%20%20%20%20%20%20%20_input_labels%2C%20_output_labels%20%3D%20%5B%5D%2C%20%5B%5D%0A%20%20%20%20%20%20%20%20for%20_n%20in%20name_list%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20_characters%20%3D%20(%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20(%5Bseparator%5D%20*%20num_preceding_chars)%20%2B%20list(_n)%20%2B%20%5Bseparator%5D%0A%20%20%20%20%20%20%20%20%20%20%20%20)%0A%20%20%20%20%20%20%20%20%20%20%20%20for%20_i%20in%20range(num_preceding_chars%2C%20len(_characters))%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20_inputs%20%3D%20tuple(%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%5B%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20char_lookup%5B_x%5D%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20for%20_x%20in%20_characters%5B_i%20-%20num_preceding_chars%20%3A%20_i%5D%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%5D%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20)%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20_output%20%3D%20char_lookup%5B_characters%5B_i%5D%5D%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20_input_labels.append(_inputs)%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20_output_labels.append(_output)%0A%20%20%20%20%20%20%20%20return%20_input_labels%2C%20_output_labels%0A%0A%20%20%20%20def%20train_model(%0A%20%20%20%20%20%20%20%20num_preceding_chars%2C%0A%20%20%20%20%20%20%20%20iterations%3D100%2C%0A%20%20%20%20%20%20%20%20use_cross_entropy%3DFalse%2C%0A%20%20%20%20%20%20%20%20reg_strength%3D0.01%2C%0A%20%20%20%20%20%20%20%20test_split%3D0.2%2C%0A%20%20%20%20)%3A%0A%20%20%20%20%20%20%20%20%23%20Split%20data%0A%20%20%20%20%20%20%20%20_split_idx%20%3D%20int(len(names)%20*%20(1%20-%20test_split))%0A%20%20%20%20%20%20%20%20_train_names%20%3D%20names%5B%3A_split_idx%5D%0A%20%20%20%20%20%20%20%20_test_names%20%3D%20names%5B_split_idx%3A%5D%0A%0A%20%20%20%20%20%20%20%20%23%20Create%20datasets%0A%20%20%20%20%20%20%20%20_train_inputs%2C%20_train_outputs%20%3D%20create_dataset(%0A%20%20%20%20%20%20%20%20%20%20%20%20_train_names%2C%20num_preceding_chars%0A%20%20%20%20%20%20%20%20)%0A%20%20%20%20%20%20%20%20_test_inputs%2C%20_test_outputs%20%3D%20create_dataset(%0A%20%20%20%20%20%20%20%20%20%20%20%20_test_names%2C%20num_preceding_chars%0A%20%20%20%20%20%20%20%20)%0A%0A%20%20%20%20%20%20%20%20%23%20Create%20lookup%20for%20character%20combinations%0A%20%20%20%20%20%20%20%20_all_char_pairs%20%3D%20list(set(_train_inputs))%0A%20%20%20%20%20%20%20%20_char_pair_lookup%20%3D%20%7B%0A%20%20%20%20%20%20%20%20%20%20%20%20_t%3A%20_index%20for%20_index%2C%20_t%20in%20enumerate(_all_char_pairs)%0A%20%20%20%20%20%20%20%20%7D%0A%0A%20%20%20%20%20%20%20%20%23%20Convert%20to%20tensors%0A%20%20%20%20%20%20%20%20_train_input_tensor%20%3D%20torch.tensor(%0A%20%20%20%20%20%20%20%20%20%20%20%20%5B_char_pair_lookup.get(_t%2C%200)%20for%20_t%20in%20_train_inputs%5D%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20device%3Ddevice%2C%0A%20%20%20%20%20%20%20%20)%0A%20%20%20%20%20%20%20%20_train_output_tensor%20%3D%20torch.tensor(_train_outputs%2C%20device%3Ddevice)%0A%20%20%20%20%20%20%20%20_test_input_tensor%20%3D%20torch.tensor(%0A%20%20%20%20%20%20%20%20%20%20%20%20%5B%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20_char_pair_lookup.get(_t%2C%200)%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20for%20_t%20in%20_test_inputs%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20if%20_t%20in%20_char_pair_lookup%0A%20%20%20%20%20%20%20%20%20%20%20%20%5D%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20device%3Ddevice%2C%0A%20%20%20%20%20%20%20%20)%0A%20%20%20%20%20%20%20%20_test_output_tensor%20%3D%20torch.tensor(%0A%20%20%20%20%20%20%20%20%20%20%20%20%5B%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20_test_outputs%5B_i%5D%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20for%20_i%2C%20_t%20in%20enumerate(_test_inputs)%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20if%20_t%20in%20_char_pair_lookup%0A%20%20%20%20%20%20%20%20%20%20%20%20%5D%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20device%3Ddevice%2C%0A%20%20%20%20%20%20%20%20)%0A%0A%20%20%20%20%20%20%20%20%23%20Initialize%20weights%0A%20%20%20%20%20%20%20%20_num_classes%20%3D%20len(_all_char_pairs)%0A%20%20%20%20%20%20%20%20_g%20%3D%20torch.Generator(device%3Ddevice).manual_seed(2147483647)%0A%20%20%20%20%20%20%20%20_W%20%3D%20torch.randn(%0A%20%20%20%20%20%20%20%20%20%20%20%20(_num_classes%2C%20len(chars))%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20generator%3D_g%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20requires_grad%3DTrue%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20device%3Ddevice%2C%0A%20%20%20%20%20%20%20%20)%0A%0A%20%20%20%20%20%20%20%20%23%20Training%20loop%0A%20%20%20%20%20%20%20%20_train_losses%20%3D%20%5B%5D%0A%20%20%20%20%20%20%20%20for%20_%20in%20range(iterations)%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20_W.grad%20%3D%20None%0A%20%20%20%20%20%20%20%20%20%20%20%20_logits%20%3D%20_W%5B_train_input_tensor%5D%0A%0A%20%20%20%20%20%20%20%20%20%20%20%20if%20use_cross_entropy%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20_loss%20%3D%20F.cross_entropy(_logits%2C%20_train_output_tensor)%0A%20%20%20%20%20%20%20%20%20%20%20%20else%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20_logcount%20%3D%20_logits.exp()%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20_probs%20%3D%20_logcount%20%2F%20_logcount.sum(1%2C%20keepdim%3DTrue)%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20_loss%20%3D%20(%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20-_probs%5B%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20torch.arange(len(_train_input_tensor)%2C%20device%3Ddevice)%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20_train_output_tensor%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%5D%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20.log()%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20.mean()%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20)%0A%0A%20%20%20%20%20%20%20%20%20%20%20%20_loss%20%3D%20_loss%20%2B%20(_W**2).mean()%20*%20reg_strength%0A%20%20%20%20%20%20%20%20%20%20%20%20_train_losses.append(_loss.item())%0A%20%20%20%20%20%20%20%20%20%20%20%20_loss.backward()%0A%20%20%20%20%20%20%20%20%20%20%20%20_W.data%20%2B%3D%20-100%20*%20_W.grad%0A%0A%20%20%20%20%20%20%20%20%23%20Calculate%20test%20loss%0A%20%20%20%20%20%20%20%20with%20torch.no_grad()%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20_test_logits%20%3D%20_W%5B_test_input_tensor%5D%0A%20%20%20%20%20%20%20%20%20%20%20%20if%20use_cross_entropy%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20_test_loss%20%3D%20F.cross_entropy(%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20_test_logits%2C%20_test_output_tensor%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20).item()%0A%20%20%20%20%20%20%20%20%20%20%20%20else%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20_test_logcount%20%3D%20_test_logits.exp()%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20_test_probs%20%3D%20_test_logcount%20%2F%20_test_logcount.sum(%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%201%2C%20keepdim%3DTrue%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20)%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20_test_loss%20%3D%20(%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20-_test_probs%5B%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20torch.arange(len(_test_input_tensor)%2C%20device%3Ddevice)%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20_test_output_tensor%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%5D%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20.log()%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20.mean()%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20.item()%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20)%0A%0A%20%20%20%20%20%20%20%20return%20_train_losses%2C%20_test_loss%0A%0A%20%20%20%20return%20(train_model%2C)%0A%0A%0A%40app.cell(hide_code%3DTrue)%0Adef%20_(mo)%3A%0A%20%20%20%20mo.md(%0A%20%20%20%20%20%20%20%20%22%22%22%0A%20%20%20%20%23%23%201.%20Effect%20of%20N-gram%20Size%0A%0A%20%20%20%20It%20appears%20that%20looking%20ahead%202%20tokens%20does%20perform%20better%20than%201%2C%20but%20looking%20any%20further%20only%20has%20a%20negative%20impact.%0A%20%20%20%20%22%22%22%0A%20%20%20%20)%0A%20%20%20%20return%0A%0A%0A%40app.cell(hide_code%3DTrue)%0Adef%20_(ngram_sizes%2C%20np%2C%20plt%2C%20train_model)%3A%0A%20%20%20%20ngram_train_losses%20%3D%20%5B%5D%0A%20%20%20%20ngram_test_losses%20%3D%20%5B%5D%0A%0A%20%20%20%20for%20_n%20in%20ngram_sizes%3A%0A%20%20%20%20%20%20%20%20_train_loss%2C%20_test_loss%20%3D%20train_model(_n%2C%20iterations%3D200)%0A%20%20%20%20%20%20%20%20ngram_train_losses.append(_train_loss%5B-1%5D)%0A%20%20%20%20%20%20%20%20ngram_test_losses.append(_test_loss)%0A%20%20%20%20%20%20%20%20print(%0A%20%20%20%20%20%20%20%20%20%20%20%20f%22N-gram%20size%20%7B_n%7D%3A%20Train%20Loss%20%3D%20%7B_train_loss%5B-1%5D%3A.4f%7D%2C%20Test%20Loss%20%3D%20%7B_test_loss%3A.4f%7D%22%0A%20%20%20%20%20%20%20%20)%0A%0A%20%20%20%20_fig%2C%20(_ax1%2C%20_ax2)%20%3D%20plt.subplots(1%2C%202%2C%20figsize%3D(14%2C%205))%0A%0A%20%20%20%20_x%20%3D%20np.arange(len(ngram_sizes))%0A%20%20%20%20_width%20%3D%200.35%0A%0A%20%20%20%20_ax1.bar(%0A%20%20%20%20%20%20%20%20_x%20-%20_width%20%2F%202%2C%0A%20%20%20%20%20%20%20%20ngram_train_losses%2C%0A%20%20%20%20%20%20%20%20_width%2C%0A%20%20%20%20%20%20%20%20label%3D%22Train%20Loss%22%2C%0A%20%20%20%20%20%20%20%20color%3D%22%233498db%22%2C%0A%20%20%20%20%20%20%20%20alpha%3D0.8%2C%0A%20%20%20%20)%0A%20%20%20%20_ax1.bar(%0A%20%20%20%20%20%20%20%20_x%20%2B%20_width%20%2F%202%2C%0A%20%20%20%20%20%20%20%20ngram_test_losses%2C%0A%20%20%20%20%20%20%20%20_width%2C%0A%20%20%20%20%20%20%20%20label%3D%22Test%20Loss%22%2C%0A%20%20%20%20%20%20%20%20color%3D%22%23e74c3c%22%2C%0A%20%20%20%20%20%20%20%20alpha%3D0.8%2C%0A%20%20%20%20)%0A%20%20%20%20_ax1.set_xlabel(%22N-gram%20Size%22)%0A%20%20%20%20_ax1.set_ylabel(%22Loss%22)%0A%20%20%20%20_ax1.set_title(%22Training%20vs%20Test%20Loss%20by%20N-gram%20Size%22)%0A%20%20%20%20_ax1.set_xticks(_x)%0A%20%20%20%20_ax1.set_xticklabels(%5Bf%22%7B_n%7D-gram%22%20for%20_n%20in%20ngram_sizes%5D)%0A%20%20%20%20_ax1.legend()%0A%20%20%20%20_ax1.grid(True%2C%20alpha%3D0.3)%0A%0A%20%20%20%20_ax2.plot(%0A%20%20%20%20%20%20%20%20list(ngram_sizes)%2C%0A%20%20%20%20%20%20%20%20ngram_train_losses%2C%0A%20%20%20%20%20%20%20%20%22o-%22%2C%0A%20%20%20%20%20%20%20%20label%3D%22Train%20Loss%22%2C%0A%20%20%20%20%20%20%20%20linewidth%3D2%2C%0A%20%20%20%20%20%20%20%20markersize%3D8%2C%0A%20%20%20%20%20%20%20%20color%3D%22%233498db%22%2C%0A%20%20%20%20)%0A%20%20%20%20_ax2.plot(%0A%20%20%20%20%20%20%20%20list(ngram_sizes)%2C%0A%20%20%20%20%20%20%20%20ngram_test_losses%2C%0A%20%20%20%20%20%20%20%20%22s-%22%2C%0A%20%20%20%20%20%20%20%20label%3D%22Test%20Loss%22%2C%0A%20%20%20%20%20%20%20%20linewidth%3D2%2C%0A%20%20%20%20%20%20%20%20markersize%3D8%2C%0A%20%20%20%20%20%20%20%20color%3D%22%23e74c3c%22%2C%0A%20%20%20%20)%0A%20%20%20%20_ax2.set_xlabel(%22N-gram%20Size%22)%0A%20%20%20%20_ax2.set_ylabel(%22Loss%22)%0A%20%20%20%20_ax2.set_title(%22Loss%20Trends%20with%20N-gram%20Size%22)%0A%20%20%20%20_ax2.legend()%0A%20%20%20%20_ax2.grid(True%2C%20alpha%3D0.3)%0A%0A%20%20%20%20plt.tight_layout()%0A%20%20%20%20plt.show()%0A%20%20%20%20return%0A%0A%0A%40app.cell(hide_code%3DTrue)%0Adef%20_(mo)%3A%0A%20%20%20%20mo.md(%0A%20%20%20%20%20%20%20%20%22%22%22%0A%20%20%20%20%23%23%202.%20Loss%20Function%20Comparison%3A%20Negative%20Log%20Likelihood%20vs%20Cross-Entropy%0A%0A%20%20%20%20**Finding**%3A%20Cross-entropy%20loss%20and%20negative%20log%20likelihood%20perform%20similarly%20across%20different%20n-gram%20sizes%20due%20to%20the%20one-hot%20encoding%20approach%20used.%0A%20%20%20%20%22%22%22%0A%20%20%20%20)%0A%20%20%20%20return%0A%0A%0A%40app.cell(hide_code%3DTrue)%0Adef%20_(ngram_sizes%2C%20plt%2C%20train_model)%3A%0A%20%20%20%20%23%20Store%20results%20for%20each%20loss%20type%0A%20%20%20%20nll_train_losses%20%3D%20%5B%5D%0A%20%20%20%20nll_test_losses%20%3D%20%5B%5D%0A%20%20%20%20ce_train_losses%20%3D%20%5B%5D%0A%20%20%20%20ce_test_losses%20%3D%20%5B%5D%0A%0A%20%20%20%20for%20_n%20in%20ngram_sizes%3A%0A%20%20%20%20%20%20%20%20%23%20Negative%20Log%20Likelihood%0A%20%20%20%20%20%20%20%20_train_loss_nll%2C%20_test_loss_nll%20%3D%20train_model(%0A%20%20%20%20%20%20%20%20%20%20%20%20_n%2C%20iterations%3D200%2C%20use_cross_entropy%3DFalse%0A%20%20%20%20%20%20%20%20)%0A%20%20%20%20%20%20%20%20nll_train_losses.append(_train_loss_nll%5B-1%5D)%0A%20%20%20%20%20%20%20%20nll_test_losses.append(_test_loss_nll)%0A%0A%20%20%20%20%20%20%20%20%23%20Cross-Entropy%0A%20%20%20%20%20%20%20%20_train_loss_ce%2C%20_test_loss_ce%20%3D%20train_model(%0A%20%20%20%20%20%20%20%20%20%20%20%20_n%2C%20iterations%3D200%2C%20use_cross_entropy%3DTrue%0A%20%20%20%20%20%20%20%20)%0A%20%20%20%20%20%20%20%20ce_train_losses.append(_train_loss_ce%5B-1%5D)%0A%20%20%20%20%20%20%20%20ce_test_losses.append(_test_loss_ce)%0A%0A%20%20%20%20%20%20%20%20print(%0A%20%20%20%20%20%20%20%20%20%20%20%20f%22N-gram%20%7B_n%7D%20-%20NLL%3A%20Train%3D%7B_train_loss_nll%5B-1%5D%3A.4f%7D%2C%20Test%3D%7B_test_loss_nll%3A.4f%7D%20%7C%20%22%0A%20%20%20%20%20%20%20%20%20%20%20%20f%22CE%3A%20Train%3D%7B_train_loss_ce%5B-1%5D%3A.4f%7D%2C%20Test%3D%7B_test_loss_ce%3A.4f%7D%22%0A%20%20%20%20%20%20%20%20)%0A%0A%20%20%20%20%23%20Create%20line%20graph%0A%20%20%20%20_fig%2C%20(_ax1%2C%20_ax2)%20%3D%20plt.subplots(1%2C%202%2C%20figsize%3D(14%2C%205))%0A%0A%20%20%20%20%23%20Training%20loss%20comparison%0A%20%20%20%20_ax1.plot(%0A%20%20%20%20%20%20%20%20list(ngram_sizes)%2C%0A%20%20%20%20%20%20%20%20nll_train_losses%2C%0A%20%20%20%20%20%20%20%20%22o-%22%2C%0A%20%20%20%20%20%20%20%20label%3D%22Negative%20Log%20Likelihood%22%2C%0A%20%20%20%20%20%20%20%20linewidth%3D2%2C%0A%20%20%20%20%20%20%20%20markersize%3D8%2C%0A%20%20%20%20%20%20%20%20color%3D%22%239b59b6%22%2C%0A%20%20%20%20%20%20%20%20alpha%3D0.8%2C%0A%20%20%20%20)%0A%20%20%20%20_ax1.plot(%0A%20%20%20%20%20%20%20%20list(ngram_sizes)%2C%0A%20%20%20%20%20%20%20%20ce_train_losses%2C%0A%20%20%20%20%20%20%20%20%22s-%22%2C%0A%20%20%20%20%20%20%20%20label%3D%22Cross-Entropy%22%2C%0A%20%20%20%20%20%20%20%20linewidth%3D2%2C%0A%20%20%20%20%20%20%20%20markersize%3D8%2C%0A%20%20%20%20%20%20%20%20color%3D%22%231abc9c%22%2C%0A%20%20%20%20%20%20%20%20alpha%3D0.8%2C%0A%20%20%20%20)%0A%20%20%20%20_ax1.set_xlabel(%22N-gram%20Size%20(Characters%20of%20Context)%22)%0A%20%20%20%20_ax1.set_ylabel(%22Training%20Loss%22)%0A%20%20%20%20_ax1.set_title(%22Training%20Loss%3A%20NLL%20vs%20Cross-Entropy%22)%0A%20%20%20%20_ax1.legend()%0A%20%20%20%20_ax1.grid(True%2C%20alpha%3D0.3)%0A%0A%20%20%20%20%23%20Test%20loss%20comparison%0A%20%20%20%20_ax2.plot(%0A%20%20%20%20%20%20%20%20list(ngram_sizes)%2C%0A%20%20%20%20%20%20%20%20nll_test_losses%2C%0A%20%20%20%20%20%20%20%20%22o-%22%2C%0A%20%20%20%20%20%20%20%20label%3D%22Negative%20Log%20Likelihood%22%2C%0A%20%20%20%20%20%20%20%20linewidth%3D2%2C%0A%20%20%20%20%20%20%20%20markersize%3D8%2C%0A%20%20%20%20%20%20%20%20color%3D%22%239b59b6%22%2C%0A%20%20%20%20%20%20%20%20alpha%3D0.8%2C%0A%20%20%20%20)%0A%20%20%20%20_ax2.plot(%0A%20%20%20%20%20%20%20%20list(ngram_sizes)%2C%0A%20%20%20%20%20%20%20%20ce_test_losses%2C%0A%20%20%20%20%20%20%20%20%22s-%22%2C%0A%20%20%20%20%20%20%20%20label%3D%22Cross-Entropy%22%2C%0A%20%20%20%20%20%20%20%20linewidth%3D2%2C%0A%20%20%20%20%20%20%20%20markersize%3D8%2C%0A%20%20%20%20%20%20%20%20color%3D%22%231abc9c%22%2C%0A%20%20%20%20%20%20%20%20alpha%3D0.8%2C%0A%20%20%20%20)%0A%20%20%20%20_ax2.set_xlabel(%22N-gram%20Size%20(Characters%20of%20Context)%22)%0A%20%20%20%20_ax2.set_ylabel(%22Test%20Loss%22)%0A%20%20%20%20_ax2.set_title(%22Test%20Loss%3A%20NLL%20vs%20Cross-Entropy%22)%0A%20%20%20%20_ax2.legend()%0A%20%20%20%20_ax2.grid(True%2C%20alpha%3D0.3)%0A%0A%20%20%20%20plt.tight_layout()%0A%20%20%20%20plt.show()%0A%20%20%20%20return%0A%0A%0A%40app.cell%0Adef%20_(mo)%3A%0A%20%20%20%20mo.md(%0A%20%20%20%20%20%20%20%20%22%22%22%0A%20%20%20%20%23%23%203.%20Effect%20of%20Regularization%20Strength%20Across%20N-gram%20Sizes%0A%0A%20%20%20%20Regularization%20didn't%20seem%20to%20have%20a%20positive%20affect.%20I%20don't%20think%20overfitting%20was%20a%20issue.%0A%20%20%20%20%22%22%22%0A%20%20%20%20)%0A%20%20%20%20return%0A%0A%0A%40app.cell(hide_code%3DTrue)%0Adef%20_(ngram_sizes%2C%20np%2C%20plt%2C%20train_model)%3A%0A%20%20%20%20reg_strengths%20%3D%20%5B0.0%2C%200.001%2C%200.01%2C%200.05%2C%200.1%2C%200.5%5D%0A%0A%20%20%20%20%23%20Store%20results%20for%20each%20n-gram%20size%0A%20%20%20%20reg_results_by_ngram%20%3D%20%7B%7D%0A%0A%20%20%20%20for%20_n%20in%20ngram_sizes%3A%0A%20%20%20%20%20%20%20%20reg_train_losses%20%3D%20%5B%5D%0A%20%20%20%20%20%20%20%20reg_test_losses%20%3D%20%5B%5D%0A%0A%20%20%20%20%20%20%20%20for%20_reg%20in%20reg_strengths%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20_train_loss%2C%20_test_loss%20%3D%20train_model(%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20_n%2C%20iterations%3D200%2C%20reg_strength%3D_reg%0A%20%20%20%20%20%20%20%20%20%20%20%20)%0A%20%20%20%20%20%20%20%20%20%20%20%20reg_train_losses.append(_train_loss%5B-1%5D)%0A%20%20%20%20%20%20%20%20%20%20%20%20reg_test_losses.append(_test_loss)%0A%20%20%20%20%20%20%20%20%20%20%20%20%23%20print(f%22N-gram%20%7B_n%7D%2C%20Reg%20%7B_reg%7D%3A%20Train%3D%7B_train_loss%5B-1%5D%3A.4f%7D%2C%20Test%3D%7B_test_loss%3A.4f%7D%22)%0A%0A%20%20%20%20%20%20%20%20reg_results_by_ngram%5B_n%5D%20%3D%20%7B%0A%20%20%20%20%20%20%20%20%20%20%20%20%22train%22%3A%20reg_train_losses%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20%22test%22%3A%20reg_test_losses%2C%0A%20%20%20%20%20%20%20%20%7D%0A%0A%20%20%20%20%23%20Create%20visualization%0A%20%20%20%20_fig%2C%20_axes%20%3D%20plt.subplots(2%2C%204%2C%20figsize%3D(16%2C%208))%0A%20%20%20%20_axes%20%3D%20_axes.flatten()%0A%0A%20%20%20%20_colors%20%3D%20plt.cm.viridis(np.linspace(0%2C%201%2C%20len(reg_strengths)))%0A%0A%20%20%20%20for%20_idx%2C%20_n%20in%20enumerate(ngram_sizes)%3A%0A%20%20%20%20%20%20%20%20_ax%20%3D%20_axes%5B_idx%5D%0A%0A%20%20%20%20%20%20%20%20_ax.semilogx(%0A%20%20%20%20%20%20%20%20%20%20%20%20%5B_r%20%2B%201e-6%20for%20_r%20in%20reg_strengths%5D%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20reg_results_by_ngram%5B_n%5D%5B%22train%22%5D%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20%22o-%22%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20label%3D%22Train%22%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20linewidth%3D2%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20markersize%3D6%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20color%3D%22%23f39c12%22%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20alpha%3D0.8%2C%0A%20%20%20%20%20%20%20%20)%0A%20%20%20%20%20%20%20%20_ax.semilogx(%0A%20%20%20%20%20%20%20%20%20%20%20%20%5B_r%20%2B%201e-6%20for%20_r%20in%20reg_strengths%5D%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20reg_results_by_ngram%5B_n%5D%5B%22test%22%5D%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20%22s-%22%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20label%3D%22Test%22%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20linewidth%3D2%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20markersize%3D6%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20color%3D%22%238e44ad%22%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20alpha%3D0.8%2C%0A%20%20%20%20%20%20%20%20)%0A%0A%20%20%20%20%20%20%20%20_ax.set_xlabel(%22Regularization%20Strength%22)%0A%20%20%20%20%20%20%20%20_ax.set_ylabel(%22Loss%22)%0A%20%20%20%20%20%20%20%20_ax.set_title(f%22%7B_n%7D-gram%20Model%22)%0A%20%20%20%20%20%20%20%20_ax.legend(loc%3D%22best%22%2C%20fontsize%3D8)%0A%20%20%20%20%20%20%20%20_ax.grid(True%2C%20alpha%3D0.3)%0A%0A%20%20%20%20%23%20Hide%20the%208th%20subplot%0A%20%20%20%20_axes%5B7%5D.axis(%22off%22)%0A%0A%20%20%20%20plt.suptitle(%0A%20%20%20%20%20%20%20%20%22Regularization%20Effect%20Across%20N-gram%20Sizes%22%2C%20fontsize%3D14%2C%20y%3D1.02%0A%20%20%20%20)%0A%20%20%20%20plt.tight_layout()%0A%20%20%20%20plt.show()%0A%20%20%20%20return%0A%0A%0A%40app.cell%0Adef%20_(mo)%3A%0A%20%20%20%20mo.md(%0A%20%20%20%20%20%20%20%20%22%22%22%0A%20%20%20%20%23%23%204.%20Training%20Iterations%20Analysis%20Across%20N-gram%20Sizes%0A%0A%20%20%20%20Number%20of%20iterations%20obviously%20relies%20on%20how%20many%20characters%20ahead%20we%20look.%20It%20appears%20that%20even%20the%205%2B%20gram%20models%20taper%20off%20between%2010%5E3%20and%2010%5E4%20iterations.%20All%20models%20greater%20than%201-gram%20seem%20to%20converge%20to%20around%202.4%20or%202.5%20training%20loss.%0A%20%20%20%20%22%22%22%0A%20%20%20%20)%0A%20%20%20%20return%0A%0A%0A%40app.cell(hide_code%3DTrue)%0Adef%20_(ngram_sizes%2C%20plt%2C%20train_model)%3A%0A%20%20%20%20iteration_counts%20%3D%20%5B10%2C%2050%2C%20100%2C%20200%2C%20500%2C%201000%2C%202000%2C%205000%2C%2010000%5D%0A%0A%20%20%20%20%23%20Store%20results%20for%20each%20n-gram%20size%0A%20%20%20%20iter_results_by_ngram%20%3D%20%7B%7D%0A%0A%20%20%20%20for%20_n%20in%20ngram_sizes%3A%0A%20%20%20%20%20%20%20%20iter_train_losses%20%3D%20%5B%5D%0A%20%20%20%20%20%20%20%20iter_test_losses%20%3D%20%5B%5D%0A%0A%20%20%20%20%20%20%20%20for%20_iters%20in%20iteration_counts%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20_train_loss%2C%20_test_loss%20%3D%20train_model(_n%2C%20iterations%3D_iters)%0A%20%20%20%20%20%20%20%20%20%20%20%20iter_train_losses.append(_train_loss%5B-1%5D)%0A%20%20%20%20%20%20%20%20%20%20%20%20iter_test_losses.append(_test_loss)%0A%20%20%20%20%20%20%20%20%20%20%20%20%23%20print(f%22N-gram%20%7B_n%7D%2C%20Iters%20%7B_iters%7D%3A%20Train%3D%7B_train_loss%5B-1%5D%3A.4f%7D%2C%20Test%3D%7B_test_loss%3A.4f%7D%22)%0A%0A%20%20%20%20%20%20%20%20iter_results_by_ngram%5B_n%5D%20%3D%20%7B%0A%20%20%20%20%20%20%20%20%20%20%20%20%22train%22%3A%20iter_train_losses%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20%22test%22%3A%20iter_test_losses%2C%0A%20%20%20%20%20%20%20%20%7D%0A%0A%20%20%20%20%23%20Create%20visualization%0A%20%20%20%20_fig%2C%20_axes%20%3D%20plt.subplots(2%2C%204%2C%20figsize%3D(16%2C%208))%0A%20%20%20%20_axes%20%3D%20_axes.flatten()%0A%0A%20%20%20%20for%20_idx%2C%20_n%20in%20enumerate(ngram_sizes)%3A%0A%20%20%20%20%20%20%20%20_ax%20%3D%20_axes%5B_idx%5D%0A%0A%20%20%20%20%20%20%20%20_ax.plot(%0A%20%20%20%20%20%20%20%20%20%20%20%20iteration_counts%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20iter_results_by_ngram%5B_n%5D%5B%22train%22%5D%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20%22o-%22%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20label%3D%22Train%22%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20linewidth%3D2%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20markersize%3D6%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20color%3D%22%232ecc71%22%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20alpha%3D0.8%2C%0A%20%20%20%20%20%20%20%20)%0A%20%20%20%20%20%20%20%20_ax.plot(%0A%20%20%20%20%20%20%20%20%20%20%20%20iteration_counts%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20iter_results_by_ngram%5B_n%5D%5B%22test%22%5D%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20%22s-%22%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20label%3D%22Test%22%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20linewidth%3D2%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20markersize%3D6%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20color%3D%22%23e67e22%22%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20alpha%3D0.8%2C%0A%20%20%20%20%20%20%20%20)%0A%0A%20%20%20%20%20%20%20%20_ax.set_xlabel(%22Training%20Iterations%22)%0A%20%20%20%20%20%20%20%20_ax.set_ylabel(%22Loss%22)%0A%20%20%20%20%20%20%20%20_ax.set_title(f%22%7B_n%7D-gram%20Model%22)%0A%20%20%20%20%20%20%20%20_ax.legend(loc%3D%22best%22%2C%20fontsize%3D8)%0A%20%20%20%20%20%20%20%20_ax.grid(True%2C%20alpha%3D0.3)%0A%20%20%20%20%20%20%20%20_ax.set_xscale(%22log%22)%0A%0A%20%20%20%20%23%20Hide%20the%208th%20subplot%0A%20%20%20%20_axes%5B7%5D.axis(%22off%22)%0A%0A%20%20%20%20plt.suptitle(%0A%20%20%20%20%20%20%20%20%22Training%20Iterations%20Effect%20Across%20N-gram%20Sizes%22%2C%20fontsize%3D14%2C%20y%3D1.02%0A%20%20%20%20)%0A%20%20%20%20plt.tight_layout()%0A%20%20%20%20plt.show()%0A%20%20%20%20return%0A%0A%0A%40app.cell%0Adef%20_(mo)%3A%0A%20%20%20%20mo.md(%0A%20%20%20%20%20%20%20%20%22%22%22%0A%20%20%20%20%23%23%205.%20Train%2FTest%20Split%20Analysis%20Across%20N-gram%20Sizes%0A%0A%20%20%20%20**Finding**%3A%20A%2080%2F20%20or%2070%2F30%20split%20typically%20provides%20a%20good%20balance%20between%20having%20enough%20training%20data%20and%20reliable%20test%20metrics%20across%20all%20n-gram%20sizes.%0A%20%20%20%20%22%22%22%0A%20%20%20%20)%0A%20%20%20%20return%0A%0A%0A%40app.cell(hide_code%3DTrue)%0Adef%20_(ngram_sizes%2C%20plt%2C%20train_model)%3A%0A%20%20%20%20split_ratios%20%3D%20%5B0.05%2C%200.1%2C%200.2%2C%200.3%2C%200.4%2C%200.5%5D%0A%0A%20%20%20%20%23%20Store%20results%20for%20each%20n-gram%20size%0A%20%20%20%20split_results_by_ngram%20%3D%20%7B%7D%0A%0A%20%20%20%20for%20_n%20in%20ngram_sizes%3A%0A%20%20%20%20%20%20%20%20split_train_losses%20%3D%20%5B%5D%0A%20%20%20%20%20%20%20%20split_test_losses%20%3D%20%5B%5D%0A%0A%20%20%20%20%20%20%20%20for%20_split%20in%20split_ratios%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20_train_loss%2C%20_test_loss%20%3D%20train_model(%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20_n%2C%20iterations%3D5000%2C%20test_split%3D_split%0A%20%20%20%20%20%20%20%20%20%20%20%20)%0A%20%20%20%20%20%20%20%20%20%20%20%20split_train_losses.append(_train_loss%5B-1%5D)%0A%20%20%20%20%20%20%20%20%20%20%20%20split_test_losses.append(_test_loss)%0A%20%20%20%20%20%20%20%20%20%20%20%20%23%20print(f%22N-gram%20%7B_n%7D%2C%20Split%20%7B_split%3A.0%25%7D%3A%20Train%3D%7B_train_loss%5B-1%5D%3A.4f%7D%2C%20Test%3D%7B_test_loss%3A.4f%7D%22)%0A%0A%20%20%20%20%20%20%20%20split_results_by_ngram%5B_n%5D%20%3D%20%7B%0A%20%20%20%20%20%20%20%20%20%20%20%20%22train%22%3A%20split_train_losses%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20%22test%22%3A%20split_test_losses%2C%0A%20%20%20%20%20%20%20%20%7D%0A%0A%20%20%20%20%23%20Create%20visualization%0A%20%20%20%20_fig%2C%20_axes%20%3D%20plt.subplots(2%2C%204%2C%20figsize%3D(16%2C%208))%0A%20%20%20%20_axes%20%3D%20_axes.flatten()%0A%0A%20%20%20%20for%20_idx%2C%20_n%20in%20enumerate(ngram_sizes)%3A%0A%20%20%20%20%20%20%20%20_ax%20%3D%20_axes%5B_idx%5D%0A%0A%20%20%20%20%20%20%20%20_train_percentages%20%3D%20%5B(1%20-%20_s)%20*%20100%20for%20_s%20in%20split_ratios%5D%0A%0A%20%20%20%20%20%20%20%20_ax.plot(%0A%20%20%20%20%20%20%20%20%20%20%20%20_train_percentages%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20split_results_by_ngram%5B_n%5D%5B%22train%22%5D%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20%22o-%22%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20label%3D%22Train%22%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20linewidth%3D2%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20markersize%3D6%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20color%3D%22%2316a085%22%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20alpha%3D0.8%2C%0A%20%20%20%20%20%20%20%20)%0A%20%20%20%20%20%20%20%20_ax.plot(%0A%20%20%20%20%20%20%20%20%20%20%20%20_train_percentages%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20split_results_by_ngram%5B_n%5D%5B%22test%22%5D%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20%22s-%22%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20label%3D%22Test%22%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20linewidth%3D2%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20markersize%3D6%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20color%3D%22%23c0392b%22%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20alpha%3D0.8%2C%0A%20%20%20%20%20%20%20%20)%0A%0A%20%20%20%20%20%20%20%20_ax.set_xlabel(%22Training%20Data%20%25%22)%0A%20%20%20%20%20%20%20%20_ax.set_ylabel(%22Loss%22)%0A%20%20%20%20%20%20%20%20_ax.set_title(f%22%7B_n%7D-gram%20Model%22)%0A%20%20%20%20%20%20%20%20_ax.legend(loc%3D%22best%22%2C%20fontsize%3D8)%0A%20%20%20%20%20%20%20%20_ax.grid(True%2C%20alpha%3D0.3)%0A%20%20%20%20%20%20%20%20_ax.invert_xaxis()%20%20%23%20Show%20from%2095%25%20to%2050%25%20training%20data%0A%0A%20%20%20%20%23%20Hide%20the%208th%20subplot%0A%20%20%20%20_axes%5B7%5D.axis(%22off%22)%0A%0A%20%20%20%20plt.suptitle(%0A%20%20%20%20%20%20%20%20%22Train%2FTest%20Split%20Effect%20Across%20N-gram%20Sizes%22%2C%20fontsize%3D14%2C%20y%3D1.02%0A%20%20%20%20)%0A%20%20%20%20plt.tight_layout()%0A%20%20%20%20plt.show()%0A%20%20%20%20return%0A%0A%0Aif%20__name__%20%3D%3D%20%22__main__%22%3A%0A%20%20%20%20app.run()%0A
744a4bca7aa3b8a5f34950b3332d87b13b8ccb72a1a9ea2ed61d3baa5926e95e