From babe1a89342da3255a35797d6876dafc1b3c55b0 Mon Sep 17 00:00:00 2001 From: EC2 Default User Date: Tue, 7 Jan 2020 09:50:58 +0000 Subject: [PATCH] final commit --- .../1_Data_Exploration-checkpoint.ipynb | 746 ++++++ ...arism_Feature_Engineering-checkpoint.ipynb | 2362 +++++++++++++++++ .../3_Training_a_Model-checkpoint.ipynb | 789 ++++++ 1_Data_Exploration.ipynb | 746 ++++++ 2_Plagiarism_Feature_Engineering.ipynb | 2362 +++++++++++++++++ 3_Training_a_Model.ipynb | 789 ++++++ README.md | 33 + __MACOSX/._data | Bin 0 -> 222 bytes __MACOSX/data/._.DS_Store | Bin 0 -> 120 bytes __MACOSX/data/._file_information.csv | Bin 0 -> 177 bytes __MACOSX/data/._g0pA_taska.txt | Bin 0 -> 222 bytes __MACOSX/data/._g0pA_taskb.txt | Bin 0 -> 222 bytes __MACOSX/data/._g0pA_taskc.txt | Bin 0 -> 222 bytes __MACOSX/data/._g0pA_taskd.txt | Bin 0 -> 222 bytes __MACOSX/data/._g0pA_taske.txt | Bin 0 -> 222 bytes __MACOSX/data/._g0pB_taska.txt | Bin 0 -> 222 bytes __MACOSX/data/._g0pB_taskb.txt | Bin 0 -> 222 bytes __MACOSX/data/._g0pB_taskc.txt | Bin 0 -> 222 bytes __MACOSX/data/._g0pB_taskd.txt | Bin 0 -> 222 bytes __MACOSX/data/._g0pB_taske.txt | Bin 0 -> 222 bytes __MACOSX/data/._g0pC_taska.txt | Bin 0 -> 222 bytes __MACOSX/data/._g0pC_taskb.txt | Bin 0 -> 222 bytes __MACOSX/data/._g0pC_taskc.txt | Bin 0 -> 222 bytes __MACOSX/data/._g0pC_taskd.txt | Bin 0 -> 222 bytes __MACOSX/data/._g0pC_taske.txt | Bin 0 -> 222 bytes __MACOSX/data/._g0pD_taska.txt | Bin 0 -> 222 bytes __MACOSX/data/._g0pD_taskb.txt | Bin 0 -> 222 bytes __MACOSX/data/._g0pD_taskc.txt | Bin 0 -> 222 bytes __MACOSX/data/._g0pD_taskd.txt | Bin 0 -> 222 bytes __MACOSX/data/._g0pD_taske.txt | Bin 0 -> 222 bytes __MACOSX/data/._g0pE_taska.txt | Bin 0 -> 222 bytes __MACOSX/data/._g0pE_taskb.txt | Bin 0 -> 222 bytes __MACOSX/data/._g0pE_taskc.txt | Bin 0 -> 222 bytes __MACOSX/data/._g0pE_taskd.txt | Bin 0 -> 222 bytes __MACOSX/data/._g0pE_taske.txt | Bin 0 -> 222 bytes __MACOSX/data/._g1pA_taska.txt | Bin 0 -> 222 bytes __MACOSX/data/._g1pA_taskb.txt | Bin 0 -> 222 bytes __MACOSX/data/._g1pA_taskc.txt | Bin 0 -> 222 bytes __MACOSX/data/._g1pA_taskd.txt | Bin 0 -> 222 bytes __MACOSX/data/._g1pA_taske.txt | Bin 0 -> 222 bytes __MACOSX/data/._g1pB_taska.txt | Bin 0 -> 222 bytes __MACOSX/data/._g1pB_taskb.txt | Bin 0 -> 222 bytes __MACOSX/data/._g1pB_taskc.txt | Bin 0 -> 222 bytes __MACOSX/data/._g1pB_taskd.txt | Bin 0 -> 222 bytes __MACOSX/data/._g1pB_taske.txt | Bin 0 -> 222 bytes __MACOSX/data/._g1pD_taska.txt | Bin 0 -> 222 bytes __MACOSX/data/._g1pD_taskb.txt | Bin 0 -> 222 bytes __MACOSX/data/._g1pD_taskc.txt | Bin 0 -> 222 bytes __MACOSX/data/._g1pD_taskd.txt | Bin 0 -> 222 bytes __MACOSX/data/._g1pD_taske.txt | Bin 0 -> 222 bytes __MACOSX/data/._g2pA_taska.txt | Bin 0 -> 222 bytes __MACOSX/data/._g2pA_taskb.txt | Bin 0 -> 222 bytes __MACOSX/data/._g2pA_taskc.txt | Bin 0 -> 222 bytes __MACOSX/data/._g2pA_taskd.txt | Bin 0 -> 222 bytes __MACOSX/data/._g2pA_taske.txt | Bin 0 -> 222 bytes __MACOSX/data/._g2pB_taska.txt | Bin 0 -> 222 bytes __MACOSX/data/._g2pB_taskb.txt | Bin 0 -> 222 bytes __MACOSX/data/._g2pB_taskc.txt | Bin 0 -> 222 bytes __MACOSX/data/._g2pB_taskd.txt | Bin 0 -> 222 bytes __MACOSX/data/._g2pB_taske.txt | Bin 0 -> 222 bytes __MACOSX/data/._g2pC_taska.txt | Bin 0 -> 222 bytes __MACOSX/data/._g2pC_taskb.txt | Bin 0 -> 222 bytes __MACOSX/data/._g2pC_taskc.txt | Bin 0 -> 222 bytes __MACOSX/data/._g2pC_taskd.txt | Bin 0 -> 222 bytes __MACOSX/data/._g2pC_taske.txt | Bin 0 -> 222 bytes __MACOSX/data/._g2pE_taska.txt | Bin 0 -> 222 bytes __MACOSX/data/._g2pE_taskb.txt | Bin 0 -> 222 bytes __MACOSX/data/._g2pE_taskc.txt | Bin 0 -> 222 bytes __MACOSX/data/._g2pE_taskd.txt | Bin 0 -> 222 bytes __MACOSX/data/._g2pE_taske.txt | Bin 0 -> 222 bytes __MACOSX/data/._g3pA_taska.txt | Bin 0 -> 222 bytes __MACOSX/data/._g3pA_taskb.txt | Bin 0 -> 222 bytes __MACOSX/data/._g3pA_taskc.txt | Bin 0 -> 222 bytes __MACOSX/data/._g3pA_taskd.txt | Bin 0 -> 222 bytes __MACOSX/data/._g3pA_taske.txt | Bin 0 -> 222 bytes __MACOSX/data/._g3pB_taska.txt | Bin 0 -> 222 bytes __MACOSX/data/._g3pB_taskb.txt | Bin 0 -> 222 bytes __MACOSX/data/._g3pB_taskc.txt | Bin 0 -> 222 bytes __MACOSX/data/._g3pB_taskd.txt | Bin 0 -> 222 bytes __MACOSX/data/._g3pB_taske.txt | Bin 0 -> 222 bytes __MACOSX/data/._g3pC_taska.txt | Bin 0 -> 222 bytes __MACOSX/data/._g3pC_taskb.txt | Bin 0 -> 222 bytes __MACOSX/data/._g3pC_taskc.txt | Bin 0 -> 222 bytes __MACOSX/data/._g3pC_taskd.txt | Bin 0 -> 222 bytes __MACOSX/data/._g3pC_taske.txt | Bin 0 -> 222 bytes __MACOSX/data/._g4pB_taska.txt | Bin 0 -> 222 bytes __MACOSX/data/._g4pB_taskb.txt | Bin 0 -> 222 bytes __MACOSX/data/._g4pB_taskc.txt | Bin 0 -> 222 bytes __MACOSX/data/._g4pB_taskd.txt | Bin 0 -> 222 bytes __MACOSX/data/._g4pB_taske.txt | Bin 0 -> 222 bytes __MACOSX/data/._g4pC_taska.txt | Bin 0 -> 222 bytes __MACOSX/data/._g4pC_taskb.txt | Bin 0 -> 222 bytes __MACOSX/data/._g4pC_taskc.txt | Bin 0 -> 222 bytes __MACOSX/data/._g4pC_taskd.txt | Bin 0 -> 222 bytes __MACOSX/data/._g4pC_taske.txt | Bin 0 -> 222 bytes __MACOSX/data/._g4pD_taska.txt | Bin 0 -> 222 bytes __MACOSX/data/._g4pD_taskb.txt | Bin 0 -> 222 bytes __MACOSX/data/._g4pD_taskc.txt | Bin 0 -> 222 bytes __MACOSX/data/._g4pD_taskd.txt | Bin 0 -> 222 bytes __MACOSX/data/._g4pD_taske.txt | Bin 0 -> 222 bytes __MACOSX/data/._g4pE_taska.txt | Bin 0 -> 222 bytes __MACOSX/data/._g4pE_taskb.txt | Bin 0 -> 222 bytes __MACOSX/data/._g4pE_taskc.txt | Bin 0 -> 222 bytes __MACOSX/data/._g4pE_taskd.txt | Bin 0 -> 222 bytes __MACOSX/data/._g4pE_taske.txt | Bin 0 -> 222 bytes __MACOSX/data/._orig_taska.txt | Bin 0 -> 278 bytes __MACOSX/data/._orig_taskc.txt | Bin 0 -> 222 bytes __MACOSX/data/._orig_taskd.txt | Bin 0 -> 222 bytes __MACOSX/data/._orig_taske.txt | Bin 0 -> 222 bytes __MACOSX/data/._test_info.csv | Bin 0 -> 177 bytes __pycache__/helpers.cpython-36.pyc | Bin 0 -> 2432 bytes __pycache__/problem_unittests.cpython-36.pyc | Bin 0 -> 5060 bytes data.zip | Bin 0 -> 113826 bytes data/.DS_Store | Bin 0 -> 6148 bytes data/file_information.csv | 101 + data/g0pA_taska.txt | 22 + data/g0pA_taskb.txt | 5 + data/g0pA_taskc.txt | 7 + data/g0pA_taskd.txt | 21 + data/g0pA_taske.txt | 15 + data/g0pB_taska.txt | 33 + data/g0pB_taskb.txt | 26 + data/g0pB_taskc.txt | 14 + data/g0pB_taskd.txt | 26 + data/g0pB_taske.txt | 11 + data/g0pC_taska.txt | 7 + data/g0pC_taskb.txt | 5 + data/g0pC_taskc.txt | 6 + data/g0pC_taskd.txt | 17 + data/g0pC_taske.txt | 5 + data/g0pD_taska.txt | 5 + data/g0pD_taskb.txt | 3 + data/g0pD_taskc.txt | 5 + data/g0pD_taskd.txt | 1 + data/g0pD_taske.txt | 3 + data/g0pE_taska.txt | 1 + data/g0pE_taskb.txt | 1 + data/g0pE_taskc.txt | 1 + data/g0pE_taskd.txt | 1 + data/g0pE_taske.txt | 1 + data/g1pA_taska.txt | 1 + data/g1pA_taskb.txt | 3 + data/g1pA_taskc.txt | 6 + data/g1pA_taskd.txt | 3 + data/g1pA_taske.txt | 4 + data/g1pB_taska.txt | 2 + data/g1pB_taskb.txt | 1 + data/g1pB_taskc.txt | 4 + data/g1pB_taskd.txt | 3 + data/g1pB_taske.txt | 4 + data/g1pD_taska.txt | 1 + data/g1pD_taskb.txt | 1 + data/g1pD_taskc.txt | 1 + data/g1pD_taskd.txt | 1 + data/g1pD_taske.txt | 5 + data/g2pA_taska.txt | 10 + data/g2pA_taskb.txt | 5 + data/g2pA_taskc.txt | 8 + data/g2pA_taskd.txt | 8 + data/g2pA_taske.txt | 9 + data/g2pB_taska.txt | 9 + data/g2pB_taskb.txt | 9 + data/g2pB_taskc.txt | 4 + data/g2pB_taskd.txt | 10 + data/g2pB_taske.txt | 11 + data/g2pC_taska.txt | 7 + data/g2pC_taskb.txt | 5 + data/g2pC_taskc.txt | 9 + data/g2pC_taskd.txt | 16 + data/g2pC_taske.txt | 5 + data/g2pE_taska.txt | 6 + data/g2pE_taskb.txt | 2 + data/g2pE_taskc.txt | 2 + data/g2pE_taskd.txt | 9 + data/g2pE_taske.txt | 5 + data/g3pA_taska.txt | 1 + data/g3pA_taskb.txt | 4 + data/g3pA_taskc.txt | 11 + data/g3pA_taskd.txt | 15 + data/g3pA_taske.txt | 4 + data/g3pB_taska.txt | 7 + data/g3pB_taskb.txt | 7 + data/g3pB_taskc.txt | 7 + data/g3pB_taskd.txt | 5 + data/g3pB_taske.txt | 7 + data/g3pC_taska.txt | 15 + data/g3pC_taskb.txt | 20 + data/g3pC_taskc.txt | 10 + data/g3pC_taskd.txt | 13 + data/g3pC_taske.txt | 13 + data/g4pB_taska.txt | 3 + data/g4pB_taskb.txt | 1 + data/g4pB_taskc.txt | 12 + data/g4pB_taskd.txt | 15 + data/g4pB_taske.txt | 28 + data/g4pC_taska.txt | 7 + data/g4pC_taskb.txt | 6 + data/g4pC_taskc.txt | 6 + data/g4pC_taskd.txt | 9 + data/g4pC_taske.txt | 4 + data/g4pD_taska.txt | 5 + data/g4pD_taskb.txt | 6 + data/g4pD_taskc.txt | 10 + data/g4pD_taskd.txt | 1 + data/g4pD_taske.txt | 4 + data/g4pE_taska.txt | 4 + data/g4pE_taskb.txt | 6 + data/g4pE_taskc.txt | 9 + data/g4pE_taskd.txt | 23 + data/g4pE_taske.txt | 9 + data/orig_taska.txt | 12 + data/orig_taskb.txt | 8 + data/orig_taskc.txt | 8 + data/orig_taskd.txt | 10 + data/orig_taske.txt | 11 + data/test_info.csv | 43 + helpers.py | 112 + notebook_ims/common_subseq_words.png | Bin 0 -> 134996 bytes notebook_ims/matrix_1.png | Bin 0 -> 35050 bytes notebook_ims/matrix_2.png | Bin 0 -> 26600 bytes notebook_ims/matrix_3_match.png | Bin 0 -> 24983 bytes notebook_ims/matrix_6_complete.png | Bin 0 -> 24956 bytes notebook_ims/matrix_rules.png | Bin 0 -> 61292 bytes plagiarism_data/test.csv | 25 + plagiarism_data/train.csv | 70 + problem_unittests.py | 153 ++ source_pytorch/model.py | 44 + source_pytorch/predict.py | 80 + source_pytorch/train.py | 164 ++ source_sklearn/train.py | 70 + 230 files changed, 9476 insertions(+) create mode 100644 .ipynb_checkpoints/1_Data_Exploration-checkpoint.ipynb create mode 100644 .ipynb_checkpoints/2_Plagiarism_Feature_Engineering-checkpoint.ipynb create mode 100644 .ipynb_checkpoints/3_Training_a_Model-checkpoint.ipynb create mode 100644 1_Data_Exploration.ipynb create mode 100644 2_Plagiarism_Feature_Engineering.ipynb create mode 100644 3_Training_a_Model.ipynb create mode 100644 README.md create mode 100644 __MACOSX/._data create mode 100644 __MACOSX/data/._.DS_Store create mode 100644 __MACOSX/data/._file_information.csv create mode 100644 __MACOSX/data/._g0pA_taska.txt create mode 100644 __MACOSX/data/._g0pA_taskb.txt create mode 100644 __MACOSX/data/._g0pA_taskc.txt create mode 100644 __MACOSX/data/._g0pA_taskd.txt create mode 100644 __MACOSX/data/._g0pA_taske.txt create mode 100644 __MACOSX/data/._g0pB_taska.txt create mode 100644 __MACOSX/data/._g0pB_taskb.txt create mode 100644 __MACOSX/data/._g0pB_taskc.txt create mode 100644 __MACOSX/data/._g0pB_taskd.txt create mode 100644 __MACOSX/data/._g0pB_taske.txt create mode 100644 __MACOSX/data/._g0pC_taska.txt create mode 100644 __MACOSX/data/._g0pC_taskb.txt create mode 100644 __MACOSX/data/._g0pC_taskc.txt create mode 100644 __MACOSX/data/._g0pC_taskd.txt create mode 100644 __MACOSX/data/._g0pC_taske.txt create mode 100644 __MACOSX/data/._g0pD_taska.txt create mode 100644 __MACOSX/data/._g0pD_taskb.txt create mode 100644 __MACOSX/data/._g0pD_taskc.txt create mode 100644 __MACOSX/data/._g0pD_taskd.txt create mode 100644 __MACOSX/data/._g0pD_taske.txt create mode 100644 __MACOSX/data/._g0pE_taska.txt create mode 100644 __MACOSX/data/._g0pE_taskb.txt create mode 100644 __MACOSX/data/._g0pE_taskc.txt create mode 100644 __MACOSX/data/._g0pE_taskd.txt create mode 100644 __MACOSX/data/._g0pE_taske.txt create mode 100644 __MACOSX/data/._g1pA_taska.txt create mode 100644 __MACOSX/data/._g1pA_taskb.txt create mode 100644 __MACOSX/data/._g1pA_taskc.txt create mode 100644 __MACOSX/data/._g1pA_taskd.txt create mode 100644 __MACOSX/data/._g1pA_taske.txt create mode 100644 __MACOSX/data/._g1pB_taska.txt create mode 100644 __MACOSX/data/._g1pB_taskb.txt create mode 100644 __MACOSX/data/._g1pB_taskc.txt create mode 100644 __MACOSX/data/._g1pB_taskd.txt create mode 100644 __MACOSX/data/._g1pB_taske.txt create mode 100644 __MACOSX/data/._g1pD_taska.txt create mode 100644 __MACOSX/data/._g1pD_taskb.txt create mode 100644 __MACOSX/data/._g1pD_taskc.txt create mode 100644 __MACOSX/data/._g1pD_taskd.txt create mode 100644 __MACOSX/data/._g1pD_taske.txt create mode 100644 __MACOSX/data/._g2pA_taska.txt create mode 100644 __MACOSX/data/._g2pA_taskb.txt create mode 100644 __MACOSX/data/._g2pA_taskc.txt create mode 100644 __MACOSX/data/._g2pA_taskd.txt create mode 100644 __MACOSX/data/._g2pA_taske.txt create mode 100644 __MACOSX/data/._g2pB_taska.txt create mode 100644 __MACOSX/data/._g2pB_taskb.txt create mode 100644 __MACOSX/data/._g2pB_taskc.txt create mode 100644 __MACOSX/data/._g2pB_taskd.txt create mode 100644 __MACOSX/data/._g2pB_taske.txt create mode 100644 __MACOSX/data/._g2pC_taska.txt create mode 100644 __MACOSX/data/._g2pC_taskb.txt create mode 100644 __MACOSX/data/._g2pC_taskc.txt create mode 100644 __MACOSX/data/._g2pC_taskd.txt create mode 100644 __MACOSX/data/._g2pC_taske.txt create mode 100644 __MACOSX/data/._g2pE_taska.txt create mode 100644 __MACOSX/data/._g2pE_taskb.txt create mode 100644 __MACOSX/data/._g2pE_taskc.txt create mode 100644 __MACOSX/data/._g2pE_taskd.txt create mode 100644 __MACOSX/data/._g2pE_taske.txt create mode 100644 __MACOSX/data/._g3pA_taska.txt create mode 100644 __MACOSX/data/._g3pA_taskb.txt create mode 100644 __MACOSX/data/._g3pA_taskc.txt create mode 100644 __MACOSX/data/._g3pA_taskd.txt create mode 100644 __MACOSX/data/._g3pA_taske.txt create mode 100644 __MACOSX/data/._g3pB_taska.txt create mode 100644 __MACOSX/data/._g3pB_taskb.txt create mode 100644 __MACOSX/data/._g3pB_taskc.txt create mode 100644 __MACOSX/data/._g3pB_taskd.txt create mode 100644 __MACOSX/data/._g3pB_taske.txt create mode 100644 __MACOSX/data/._g3pC_taska.txt create mode 100644 __MACOSX/data/._g3pC_taskb.txt create mode 100644 __MACOSX/data/._g3pC_taskc.txt create mode 100644 __MACOSX/data/._g3pC_taskd.txt create mode 100644 __MACOSX/data/._g3pC_taske.txt create mode 100644 __MACOSX/data/._g4pB_taska.txt create mode 100644 __MACOSX/data/._g4pB_taskb.txt create mode 100644 __MACOSX/data/._g4pB_taskc.txt create mode 100644 __MACOSX/data/._g4pB_taskd.txt create mode 100644 __MACOSX/data/._g4pB_taske.txt create mode 100644 __MACOSX/data/._g4pC_taska.txt create mode 100644 __MACOSX/data/._g4pC_taskb.txt create mode 100644 __MACOSX/data/._g4pC_taskc.txt create mode 100644 __MACOSX/data/._g4pC_taskd.txt create mode 100644 __MACOSX/data/._g4pC_taske.txt create mode 100644 __MACOSX/data/._g4pD_taska.txt create mode 100644 __MACOSX/data/._g4pD_taskb.txt create mode 100644 __MACOSX/data/._g4pD_taskc.txt create mode 100644 __MACOSX/data/._g4pD_taskd.txt create mode 100644 __MACOSX/data/._g4pD_taske.txt create mode 100644 __MACOSX/data/._g4pE_taska.txt create mode 100644 __MACOSX/data/._g4pE_taskb.txt create mode 100644 __MACOSX/data/._g4pE_taskc.txt create mode 100644 __MACOSX/data/._g4pE_taskd.txt create mode 100644 __MACOSX/data/._g4pE_taske.txt create mode 100644 __MACOSX/data/._orig_taska.txt create mode 100644 __MACOSX/data/._orig_taskc.txt create mode 100644 __MACOSX/data/._orig_taskd.txt create mode 100644 __MACOSX/data/._orig_taske.txt create mode 100644 __MACOSX/data/._test_info.csv create mode 100644 __pycache__/helpers.cpython-36.pyc create mode 100644 __pycache__/problem_unittests.cpython-36.pyc create mode 100644 data.zip create mode 100644 data/.DS_Store create mode 100644 data/file_information.csv create mode 100755 data/g0pA_taska.txt create mode 100755 data/g0pA_taskb.txt create mode 100755 data/g0pA_taskc.txt create mode 100755 data/g0pA_taskd.txt create mode 100755 data/g0pA_taske.txt create mode 100755 data/g0pB_taska.txt create mode 100755 data/g0pB_taskb.txt create mode 100755 data/g0pB_taskc.txt create mode 100755 data/g0pB_taskd.txt create mode 100755 data/g0pB_taske.txt create mode 100755 data/g0pC_taska.txt create mode 100755 data/g0pC_taskb.txt create mode 100755 data/g0pC_taskc.txt create mode 100755 data/g0pC_taskd.txt create mode 100755 data/g0pC_taske.txt create mode 100755 data/g0pD_taska.txt create mode 100755 data/g0pD_taskb.txt create mode 100755 data/g0pD_taskc.txt create mode 100755 data/g0pD_taskd.txt create mode 100755 data/g0pD_taske.txt create mode 100755 data/g0pE_taska.txt create mode 100755 data/g0pE_taskb.txt create mode 100755 data/g0pE_taskc.txt create mode 100755 data/g0pE_taskd.txt create mode 100755 data/g0pE_taske.txt create mode 100755 data/g1pA_taska.txt create mode 100755 data/g1pA_taskb.txt create mode 100755 data/g1pA_taskc.txt create mode 100755 data/g1pA_taskd.txt create mode 100755 data/g1pA_taske.txt create mode 100755 data/g1pB_taska.txt create mode 100755 data/g1pB_taskb.txt create mode 100755 data/g1pB_taskc.txt create mode 100755 data/g1pB_taskd.txt create mode 100755 data/g1pB_taske.txt create mode 100755 data/g1pD_taska.txt create mode 100755 data/g1pD_taskb.txt create mode 100755 data/g1pD_taskc.txt create mode 100755 data/g1pD_taskd.txt create mode 100755 data/g1pD_taske.txt create mode 100755 data/g2pA_taska.txt create mode 100755 data/g2pA_taskb.txt create mode 100755 data/g2pA_taskc.txt create mode 100755 data/g2pA_taskd.txt create mode 100755 data/g2pA_taske.txt create mode 100755 data/g2pB_taska.txt create mode 100755 data/g2pB_taskb.txt create mode 100755 data/g2pB_taskc.txt create mode 100755 data/g2pB_taskd.txt create mode 100755 data/g2pB_taske.txt create mode 100755 data/g2pC_taska.txt create mode 100755 data/g2pC_taskb.txt create mode 100755 data/g2pC_taskc.txt create mode 100755 data/g2pC_taskd.txt create mode 100755 data/g2pC_taske.txt create mode 100755 data/g2pE_taska.txt create mode 100755 data/g2pE_taskb.txt create mode 100755 data/g2pE_taskc.txt create mode 100755 data/g2pE_taskd.txt create mode 100755 data/g2pE_taske.txt create mode 100755 data/g3pA_taska.txt create mode 100755 data/g3pA_taskb.txt create mode 100755 data/g3pA_taskc.txt create mode 100755 data/g3pA_taskd.txt create mode 100755 data/g3pA_taske.txt create mode 100755 data/g3pB_taska.txt create mode 100755 data/g3pB_taskb.txt create mode 100755 data/g3pB_taskc.txt create mode 100755 data/g3pB_taskd.txt create mode 100755 data/g3pB_taske.txt create mode 100755 data/g3pC_taska.txt create mode 100755 data/g3pC_taskb.txt create mode 100755 data/g3pC_taskc.txt create mode 100755 data/g3pC_taskd.txt create mode 100755 data/g3pC_taske.txt create mode 100755 data/g4pB_taska.txt create mode 100755 data/g4pB_taskb.txt create mode 100755 data/g4pB_taskc.txt create mode 100755 data/g4pB_taskd.txt create mode 100755 data/g4pB_taske.txt create mode 100755 data/g4pC_taska.txt create mode 100755 data/g4pC_taskb.txt create mode 100755 data/g4pC_taskc.txt create mode 100755 data/g4pC_taskd.txt create mode 100755 data/g4pC_taske.txt create mode 100755 data/g4pD_taska.txt create mode 100755 data/g4pD_taskb.txt create mode 100755 data/g4pD_taskc.txt create mode 100755 data/g4pD_taskd.txt create mode 100755 data/g4pD_taske.txt create mode 100755 data/g4pE_taska.txt create mode 100755 data/g4pE_taskb.txt create mode 100755 data/g4pE_taskc.txt create mode 100755 data/g4pE_taskd.txt create mode 100755 data/g4pE_taske.txt create mode 100755 data/orig_taska.txt create mode 100755 data/orig_taskb.txt create mode 100755 data/orig_taskc.txt create mode 100755 data/orig_taskd.txt create mode 100755 data/orig_taske.txt create mode 100644 data/test_info.csv create mode 100644 helpers.py create mode 100644 notebook_ims/common_subseq_words.png create mode 100644 notebook_ims/matrix_1.png create mode 100644 notebook_ims/matrix_2.png create mode 100644 notebook_ims/matrix_3_match.png create mode 100644 notebook_ims/matrix_6_complete.png create mode 100644 notebook_ims/matrix_rules.png create mode 100644 plagiarism_data/test.csv create mode 100644 plagiarism_data/train.csv create mode 100644 problem_unittests.py create mode 100644 source_pytorch/model.py create mode 100644 source_pytorch/predict.py create mode 100644 source_pytorch/train.py create mode 100644 source_sklearn/train.py diff --git a/.ipynb_checkpoints/1_Data_Exploration-checkpoint.ipynb b/.ipynb_checkpoints/1_Data_Exploration-checkpoint.ipynb new file mode 100644 index 0000000..154d7e1 --- /dev/null +++ b/.ipynb_checkpoints/1_Data_Exploration-checkpoint.ipynb @@ -0,0 +1,746 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "# Plagiarism Text Data\n", + "\n", + "In this project, you will be tasked with building a plagiarism detector that examines a text file and performs binary classification; labeling that file as either plagiarized or not, depending on how similar the text file is when compared to a provided source text. \n", + "\n", + "The first step in working with any dataset is loading the data in and noting what information is included in the dataset. This is an important step in eventually working with this data, and knowing what kinds of features you have to work with as you transform and group the data!\n", + "\n", + "So, this notebook is all about exploring the data and noting patterns about the features you are given and the distribution of data. \n", + "\n", + "> There are not any exercises or questions in this notebook, it is only meant for exploration. This notebook will note be required in your final project submission.\n", + "\n", + "---" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Read in the Data\n", + "\n", + "The cell below will download the necessary data and extract the files into the folder `data/`.\n", + "\n", + "This data is a slightly modified version of a dataset created by Paul Clough (Information Studies) and Mark Stevenson (Computer Science), at the University of Sheffield. You can read all about the data collection and corpus, at [their university webpage](https://ir.shef.ac.uk/cloughie/resources/plagiarism_corpus.html). \n", + "\n", + "> **Citation for data**: Clough, P. and Stevenson, M. Developing A Corpus of Plagiarised Short Answers, Language Resources and Evaluation: Special Issue on Plagiarism and Authorship Analysis, In Press. [Download]" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "collapsed": true + }, + "outputs": [], + "source": [ + "!wget https://s3.amazonaws.com/video.udacity-data.com/topher/2019/January/5c4147f9_data/data.zip\n", + "!unzip data" + ] + }, + { + "cell_type": "code", + "execution_count": 1, + "metadata": {}, + "outputs": [], + "source": [ + "# import libraries\n", + "import pandas as pd\n", + "import numpy as np\n", + "import os" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "This plagiarism dataset is made of multiple text files; each of these files has characteristics that are is summarized in a `.csv` file named `file_information.csv`, which we can read in using `pandas`." + ] + }, + { + "cell_type": "code", + "execution_count": 2, + "metadata": {}, + "outputs": [ + { + "data": { + "text/html": [ + "
\n", + "\n", + "\n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + "
FileTaskCategory
0g0pA_taska.txtanon
1g0pA_taskb.txtbcut
2g0pA_taskc.txtclight
3g0pA_taskd.txtdheavy
4g0pA_taske.txtenon
5g0pB_taska.txtanon
6g0pB_taskb.txtbnon
7g0pB_taskc.txtccut
8g0pB_taskd.txtdlight
9g0pB_taske.txteheavy
\n", + "
" + ], + "text/plain": [ + " File Task Category\n", + "0 g0pA_taska.txt a non\n", + "1 g0pA_taskb.txt b cut\n", + "2 g0pA_taskc.txt c light\n", + "3 g0pA_taskd.txt d heavy\n", + "4 g0pA_taske.txt e non\n", + "5 g0pB_taska.txt a non\n", + "6 g0pB_taskb.txt b non\n", + "7 g0pB_taskc.txt c cut\n", + "8 g0pB_taskd.txt d light\n", + "9 g0pB_taske.txt e heavy" + ] + }, + "execution_count": 2, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "csv_file = 'data/file_information.csv'\n", + "plagiarism_df = pd.read_csv(csv_file)\n", + "\n", + "# print out the first few rows of data info\n", + "plagiarism_df.head(10)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Types of Plagiarism\n", + "\n", + "Each text file is associated with one **Task** (task A-E) and one **Category** of plagiarism, which you can see in the above DataFrame.\n", + "\n", + "### Five task types, A-E\n", + "\n", + "Each text file contains an answer to one short question; these questions are labeled as tasks A-E.\n", + "* Each task, A-E, is about a topic that might be included in the Computer Science curriculum that was created by the authors of this dataset. \n", + " * For example, Task A asks the question: \"What is inheritance in object oriented programming?\"\n", + "\n", + "### Four categories of plagiarism \n", + "\n", + "Each text file has an associated plagiarism label/category:\n", + "\n", + "1. `cut`: An answer is plagiarized; it is copy-pasted directly from the relevant Wikipedia source text.\n", + "2. `light`: An answer is plagiarized; it is based on the Wikipedia source text and includes some copying and paraphrasing.\n", + "3. `heavy`: An answer is plagiarized; it is based on the Wikipedia source text but expressed using different words and structure. Since this doesn't copy directly from a source text, this will likely be the most challenging kind of plagiarism to detect.\n", + "4. `non`: An answer is not plagiarized; the Wikipedia source text is not used to create this answer.\n", + "5. `orig`: This is a specific category for the original, Wikipedia source text. We will use these files only for comparison purposes.\n", + "\n", + "> So, out of the submitted files, the only category that does not contain any plagiarism is `non`.\n", + "\n", + "In the next cell, print out some statistics about the data." + ] + }, + { + "cell_type": "code", + "execution_count": 3, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Number of files: 100\n", + "Number of unique tasks/question types (A-E): 5\n", + "Unique plagiarism categories: ['non' 'cut' 'light' 'heavy' 'orig']\n" + ] + } + ], + "source": [ + "# print out some stats about the data\n", + "print('Number of files: ', plagiarism_df.shape[0]) # .shape[0] gives the rows \n", + "# .unique() gives unique items in a specified column\n", + "print('Number of unique tasks/question types (A-E): ', (len(plagiarism_df['Task'].unique())))\n", + "print('Unique plagiarism categories: ', (plagiarism_df['Category'].unique()))" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "You should see the number of text files in the dataset as well as some characteristics about the `Task` and `Category` columns. **Note that the file count of 100 *includes* the 5 _original_ wikipedia files for tasks A-E.** If you take a look at the files in the `data` directory, you'll notice that the original, source texts start with the filename `orig_` as opposed to `g` for \"group.\" \n", + "\n", + "> So, in total there are 100 files, 95 of which are answers (submitted by people) and 5 of which are the original, Wikipedia source texts.\n", + "\n", + "Your end goal will be to use this information to classify any given answer text into one of two categories, plagiarized or not-plagiarized." + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Distribution of Data\n", + "\n", + "Next, let's look at the distribution of data. In this course, we've talked about traits like class imbalance that can inform how you develop an algorithm. So, here, we'll ask: **How evenly is our data distributed among different tasks and plagiarism levels?**\n", + "\n", + "Below, you should notice two things:\n", + "* Our dataset is quite small, especially with respect to examples of varying plagiarism levels.\n", + "* The data is distributed fairly evenly across task and plagiarism types." + ] + }, + { + "cell_type": "code", + "execution_count": 4, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "\n", + "Task:\n" + ] + }, + { + "data": { + "text/html": [ + "
\n", + "\n", + "\n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + "
TaskCounts
0a20
1b20
2c20
3d20
4e20
\n", + "
" + ], + "text/plain": [ + " Task Counts\n", + "0 a 20\n", + "1 b 20\n", + "2 c 20\n", + "3 d 20\n", + "4 e 20" + ] + }, + "metadata": {}, + "output_type": "display_data" + }, + { + "name": "stdout", + "output_type": "stream", + "text": [ + "\n", + "Plagiarism Levels:\n" + ] + }, + { + "data": { + "text/html": [ + "
\n", + "\n", + "\n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + "
CategoryCounts
0cut19
1heavy19
2light19
3non38
4orig5
\n", + "
" + ], + "text/plain": [ + " Category Counts\n", + "0 cut 19\n", + "1 heavy 19\n", + "2 light 19\n", + "3 non 38\n", + "4 orig 5" + ] + }, + "metadata": {}, + "output_type": "display_data" + }, + { + "name": "stdout", + "output_type": "stream", + "text": [ + "\n", + "Task & Plagiarism Level Combos :\n" + ] + }, + { + "data": { + "text/html": [ + "
\n", + "\n", + "\n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + "
TaskCategoryCounts
0acut4
1aheavy3
2alight3
3anon9
4aorig1
5bcut3
6bheavy4
7blight3
8bnon9
9borig1
10ccut3
11cheavy5
12clight4
13cnon7
14corig1
15dcut4
16dheavy4
17dlight5
18dnon6
19dorig1
20ecut5
21eheavy3
22elight4
23enon7
24eorig1
\n", + "
" + ], + "text/plain": [ + " Task Category Counts\n", + "0 a cut 4\n", + "1 a heavy 3\n", + "2 a light 3\n", + "3 a non 9\n", + "4 a orig 1\n", + "5 b cut 3\n", + "6 b heavy 4\n", + "7 b light 3\n", + "8 b non 9\n", + "9 b orig 1\n", + "10 c cut 3\n", + "11 c heavy 5\n", + "12 c light 4\n", + "13 c non 7\n", + "14 c orig 1\n", + "15 d cut 4\n", + "16 d heavy 4\n", + "17 d light 5\n", + "18 d non 6\n", + "19 d orig 1\n", + "20 e cut 5\n", + "21 e heavy 3\n", + "22 e light 4\n", + "23 e non 7\n", + "24 e orig 1" + ] + }, + "metadata": {}, + "output_type": "display_data" + } + ], + "source": [ + "# Show counts by different tasks and amounts of plagiarism\n", + "\n", + "# group and count by task\n", + "counts_per_task=plagiarism_df.groupby(['Task']).size().reset_index(name=\"Counts\")\n", + "print(\"\\nTask:\")\n", + "display(counts_per_task)\n", + "\n", + "# group by plagiarism level\n", + "counts_per_category=plagiarism_df.groupby(['Category']).size().reset_index(name=\"Counts\")\n", + "print(\"\\nPlagiarism Levels:\")\n", + "display(counts_per_category)\n", + "\n", + "# group by task AND plagiarism level\n", + "counts_task_and_plagiarism=plagiarism_df.groupby(['Task', 'Category']).size().reset_index(name=\"Counts\")\n", + "print(\"\\nTask & Plagiarism Level Combos :\")\n", + "display(counts_task_and_plagiarism)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "It may also be helpful to look at this last DataFrame, graphically.\n", + "\n", + "Below, you can see that the counts follow a pattern broken down by task. Each task has one source text (original) and the highest number on `non` plagiarized cases." + ] + }, + { + "cell_type": "code", + "execution_count": 5, + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "" + ] + }, + "execution_count": 5, + "metadata": {}, + "output_type": "execute_result" + }, + { + "data": { + "image/png": "iVBORw0KGgoAAAANSUhEUgAAAd0AAAEyCAYAAAC/Lwo5AAAABHNCSVQICAgIfAhkiAAAAAlwSFlzAAALEgAACxIB0t1+/AAAADl0RVh0U29mdHdhcmUAbWF0cGxvdGxpYiB2ZXJzaW9uIDMuMC4zLCBodHRwOi8vbWF0cGxvdGxpYi5vcmcvnQurowAADCFJREFUeJzt3V+MpXddx/HP1w5EW4jWdEKwf1w0xoRwIWRiVAhpQI2iEU0MgQQD3qwXosWYKHoDNybGIMELQ7ICBmOFmFKVGKKQCFFvGnZLI21XlGD5UwtdQiLUm4r9ejGHuK67O2fa53xnz9nXK9nszJnnnPnOb57Je5/nnHm2ujsAwOZ9y0kPAADXC9EFgCGiCwBDRBcAhoguAAwRXQAYIroAMER0AWCI6ALAkL1NPOgtt9zSp06d2sRDA8A159y5c1/p7v2jtttIdE+dOpWzZ89u4qEB4JpTVZ9bZzunlwFgiOgCwBDRBYAhogsAQ0QXAIaILgAMEV0AGCK6ADBEdAFgiOgCwBDRBYAhG7n2Mv9f1fHv0738HNvOOgLbzJEuAAwRXQAYIroAMER0AWCI6ALAENEFgCGiCwBDRBcAhoguAAwRXQAYIroAMER0AWCI6ALAENEFgCGiCwBDRBcAhoguAAwRXQAYIroAMER0AWCI6ALAENEFgCGiCwBDRBcAhqwV3ar6tap6qKoerKr3V9W3bnowANg1R0a3qm5N8qtJDrr7RUluSPLaTQ8GALtm3dPLe0m+rar2ktyY5N83NxIA7KYjo9vdjyZ5e5LPJ3ksyX9090c2PRgA7Jp1Ti/fnOTVSV6Q5LuS3FRVr7/Mdqer6mxVnb1w4cLykwLAllvn9PKPJvm37r7Q3f+V5N4kP3LpRt19prsPuvtgf39/6TkBYOutE93PJ/mhqrqxqirJK5Oc3+xYALB71nlO974k9yS5P8mnVvc5s+G5AGDn7K2zUXe/NclbNzwLAOw0V6QCgCGiCwBDRBcAhoguAAwRXQAYIroAMER0AWCI6ALAENEFgCGiCwBDRBcAhoguAAwRXQAYIroAMER0AWCI6ALAENEFgCGiCwBDRBcAhoguAAwRXQAYIroAMER0AWDI3kkPAMyrOv59upefA663fdGRLgAMEV0AGCK6ADBEdAFgiOgCwBDRBYAhogsAQ0QXAIaILgAMEV0AGCK6ADBEdAFgiOgCwBDRBYAhogsAQ0QXAIaILgAMEV0AGCK6ADBEdAFgiOgCwBDRBYAhogsAQ0QXAIaILgAMWSu6VfUdVXVPVf1zVZ2vqh/e9GAAsGv21tzuD5L8TXf/fFU9O8mNG5wJAHbSkdGtqm9P8vIkb0yS7n4yyZObHQsAds86p5dfkORCkj+uqk9W1bur6qZLN6qq01V1tqrOXrhwYfFBAa4lVU/vD9e3daK7l+QlSd7V3S9O8p9J3nLpRt19prsPuvtgf39/4TEBYPutE90vJvlid9+3ev+eHEYYADiGI6Pb3V9K8oWq+v7VTa9M8vBGpwKAHbTuq5d/Jcndq1cufzbJL25uJADYTWtFt7sfSHKw4VkAYKe5IhUADBFdABgiugAwRHQBYIjoAsAQ0QWAIaILAENEFwCGiC4ADBFdABgiugAwRHQBYIjoAsAQ0QWAIaILAENEFwCGiC4ADBFdABgiugAwRHQBYIjoAsAQ0QWAIaILAEP2TnoAuN5UHf8+3cvPcb3bhe/DLnwN1xtHugAwRHQBYIjoAsAQ0QWAIaILAENEFwCGiC4ADBFdABgiugAwRHQBYIjoAsAQ0QWAIaILAENEFwCGiC4ADBFdABgiugAwRHQBYIjoAsAQ0QWAIaILAENEFwCGiC4ADBFdABiydnSr6oaq+mRV/fUmBwKAXXWcI927kpzf1CAAsOvWim5V3Zbkp5K8e7PjAMDuWvdI951JfiPJUxucBQB22t5RG1TVTyd5vLvPVdWdV9nudJLTSXLHHXcsNuDhYx//Pt2LjkB8H64Vu/B9WOJr2IV12AW+D8ezzpHuS5P8TFU9kuQDSV5RVX966Ubdfaa7D7r7YH9/f+ExAWD7HRnd7v6t7r6tu08leW2Sv+vu1298MgDYMX5PFwCGHPmc7sW6++NJPr6RSQBgxznSBYAhogsAQ0QXAIaILgAMEV0AGCK6ADBEdAFgiOgCwBDRBYAhogsAQ0QXAIaILgAMEV0AGCK6ADBEdAFgiOgCwBDRBYAhogsAQ0QXAIaILgAMEV0AGCK6ADBEdAFgiOgCwJC9kx5gQtXx79O9/GOctJP+Gp7O5196hiWc9DrCkuzPsxzpAsAQ0QWAIaILAENEFwCGiC4ADBFdABgiugAwRHQBYIjoAsAQ0QWAIaILAENEFwCGiC4ADBFdABgiugAwRHQBYIjoAsAQ0QWAIaILAENEFwCGiC4ADBFdABgiugAwRHQBYMiR0a2q26vqY1X1cFU9VFV3TQwGALtmb41tvpHk17v7/qp6bpJzVfXR7n54w7MBwE458ki3ux/r7vtXb389yfkkt256MADYNcd6TreqTiV5cZL7LvOx01V1tqrOXrhwYZnpAGCHrB3dqnpOkg8meXN3f+3Sj3f3me4+6O6D/f39JWcEgJ2wVnSr6lk5DO7d3X3vZkcCgN20zquXK8l7kpzv7ndsfiQA2E3rHOm+NMkvJHlFVT2w+vOqDc8FADvnyF8Z6u5/TFIDswDATnNFKgAYIroAMER0AWCI6ALAENEFgCGiCwBDRBcAhoguAAwRXQAYIroAMER0AWCI6ALAENEFgCGiCwBDRBcAhoguAAwRXQAYIroAMER0AWCI6ALAENEFgCGiCwBDRBcAhuyd9ACsp+r49+lefo5tZx2XYR2XYR2XsU3r6EgXAIaILgAMEV0AGCK6ADBEdAFgiOgCwBDRBYAhogsAQ0QXAIaILgAMEV0AGCK6ADBEdAFgiOgCwBDRBYAhogsAQ0QXAIaILgAMEV0AGCK6ADBEdAFgiOgCwBDRBYAhogsAQ9aKblX9RFV9uqo+U1Vv2fRQALCLjoxuVd2Q5A+T/GSSFyZ5XVW9cNODAcCuWedI9weTfKa7P9vdTyb5QJJXb3YsANg960T31iRfuOj9L65uAwCOYW+pB6qq00lOr959oqo+vdRjX8UtSb5y+Xme2QM/0/tfCzMc4/7WcZn7X7PruGXfh8uu45Z9DdfCDDu7jsNfwxV/ri/x3es82DrRfTTJ7Re9f9vqtv+ju88kObPOJ11KVZ3t7oPJz7mLrOMyrOMyrOMyrOMyll7HdU4vfyLJ91XVC6rq2Ulem+RDSw0AANeLI490u/sbVfWmJH+b5IYk7+3uhzY+GQDsmLWe0+3uDyf58IZneTpGT2fvMOu4DOu4DOu4DOu4jEXXsbp7yccDAK7AZSABYIjoAsCQrY2u60Evo6oeqapPVdUDVXX2pOfZFlX13qp6vKoevOi276yqj1bVv67+vvkkZ9wGV1jHt1XVo6t98oGqetVJznitq6rbq+pjVfVwVT1UVXetbrc/HsNV1nHR/XErn9NdXQ/6X5L8WA6vkPWJJK/r7odPdLAtVFWPJDno7nV++ZuVqnp5kieS/El3v2h12+8l+Wp3/+7qH4I3d/dvnuSc17orrOPbkjzR3W8/ydm2RVU9P8nzu/v+qnpuknNJfjbJG2N/XNtV1vE1WXB/3NYjXdeD5kR1998n+eolN786yftWb78vhz+wXMUV1pFj6O7Huvv+1dtfT3I+h5fqtT8ew1XWcVHbGl3Xg15OJ/lIVZ1bXcqTp+953f3Y6u0vJXneSQ6z5d5UVf+0Ov3stOiaqupUkhcnuS/2x6ftknVMFtwftzW6LOdl3f2SHP7Xjb+8Ot3HM9SHz9ts33M314Z3JfneJD+Q5LEkv3+y42yHqnpOkg8meXN3f+3ij9kf13eZdVx0f9zW6K51PWiO1t2Prv5+PMlf5PDUPU/Pl1fPC33z+aHHT3ierdTdX+7u/+7up5L8UeyTR6qqZ+UwFHd3972rm+2Px3S5dVx6f9zW6Loe9AKq6qbVCwZSVTcl+fEkD179XlzFh5K8YfX2G5L81QnOsrW+GYqVn4t98qqqqpK8J8n57n7HRR+yPx7DldZx6f1xK1+9nCSrl22/M/97PejfOeGRtk5VfU8Oj26Tw0uC/pl1XE9VvT/JnTn8b7++nOStSf4yyZ8nuSPJ55K8pru9SOgqrrCOd+bwVF4neSTJL1303CSXqKqXJfmHJJ9K8tTq5t/O4fOR9sc1XWUdX5cF98etjS4AbJttPb0MAFtHdAFgiOgCwBDRBYAhogsAQ0QXAIaILgAM+R8ehKbWpEhdRgAAAABJRU5ErkJggg==\n", + "text/plain": [ + "
" + ] + }, + "metadata": {}, + "output_type": "display_data" + } + ], + "source": [ + "import matplotlib.pyplot as plt\n", + "% matplotlib inline\n", + "\n", + "# counts\n", + "group = ['Task', 'Category']\n", + "counts = plagiarism_df.groupby(group).size().reset_index(name=\"Counts\")\n", + "\n", + "plt.figure(figsize=(8,5))\n", + "plt.bar(range(len(counts)), counts['Counts'], color = 'blue')" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Up Next\n", + "\n", + "This notebook is just about data loading and exploration, and you do not need to include it in your final project submission. \n", + "\n", + "In the next few notebooks, you'll use this data to train a complete plagiarism classifier. You'll be tasked with extracting meaningful features from the text data, reading in answers to different tasks and comparing them to the original Wikipedia source text. You'll engineer similarity features that will help identify cases of plagiarism. Then, you'll use these features to train and deploy a classification model in a SageMaker notebook instance. " + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [] + } + ], + "metadata": { + "kernelspec": { + "display_name": "conda_amazonei_mxnet_p36", + "language": "python", + "name": "conda_amazonei_mxnet_p36" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.6.5" + } + }, + "nbformat": 4, + "nbformat_minor": 2 +} diff --git a/.ipynb_checkpoints/2_Plagiarism_Feature_Engineering-checkpoint.ipynb b/.ipynb_checkpoints/2_Plagiarism_Feature_Engineering-checkpoint.ipynb new file mode 100644 index 0000000..9a32e37 --- /dev/null +++ b/.ipynb_checkpoints/2_Plagiarism_Feature_Engineering-checkpoint.ipynb @@ -0,0 +1,2362 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "# Plagiarism Detection, Feature Engineering\n", + "\n", + "In this project, you will be tasked with building a plagiarism detector that examines an answer text file and performs binary classification; labeling that file as either plagiarized or not, depending on how similar that text file is to a provided, source text. \n", + "\n", + "Your first task will be to create some features that can then be used to train a classification model. This task will be broken down into a few discrete steps:\n", + "\n", + "* Clean and pre-process the data.\n", + "* Define features for comparing the similarity of an answer text and a source text, and extract similarity features.\n", + "* Select \"good\" features, by analyzing the correlations between different features.\n", + "* Create train/test `.csv` files that hold the relevant features and class labels for train/test data points.\n", + "\n", + "In the _next_ notebook, Notebook 3, you'll use the features and `.csv` files you create in _this_ notebook to train a binary classification model in a SageMaker notebook instance.\n", + "\n", + "You'll be defining a few different similarity features, as outlined in [this paper](https://s3.amazonaws.com/video.udacity-data.com/topher/2019/January/5c412841_developing-a-corpus-of-plagiarised-short-answers/developing-a-corpus-of-plagiarised-short-answers.pdf), which should help you build a robust plagiarism detector!\n", + "\n", + "To complete this notebook, you'll have to complete all given exercises and answer all the questions in this notebook.\n", + "> All your tasks will be clearly labeled **EXERCISE** and questions as **QUESTION**.\n", + "\n", + "It will be up to you to decide on the features to include in your final training and test data.\n", + "\n", + "---" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Read in the Data\n", + "\n", + "The cell below will download the necessary, project data and extract the files into the folder `data/`.\n", + "\n", + "This data is a slightly modified version of a dataset created by Paul Clough (Information Studies) and Mark Stevenson (Computer Science), at the University of Sheffield. You can read all about the data collection and corpus, at [their university webpage](https://ir.shef.ac.uk/cloughie/resources/plagiarism_corpus.html). \n", + "\n", + "> **Citation for data**: Clough, P. and Stevenson, M. Developing A Corpus of Plagiarised Short Answers, Language Resources and Evaluation: Special Issue on Plagiarism and Authorship Analysis, In Press. [Download]" + ] + }, + { + "cell_type": "code", + "execution_count": 1, + "metadata": {}, + "outputs": [], + "source": [ + "# NOTE:\n", + "# you only need to run this cell if you have not yet downloaded the data\n", + "# otherwise you may skip this cell or comment it out\n", + "\n", + "#!wget https://s3.amazonaws.com/video.udacity-data.com/topher/2019/January/5c4147f9_data/data.zip\n", + "#!unzip data" + ] + }, + { + "cell_type": "code", + "execution_count": 2, + "metadata": {}, + "outputs": [], + "source": [ + "# import libraries\n", + "import pandas as pd\n", + "import numpy as np\n", + "import os" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "This plagiarism dataset is made of multiple text files; each of these files has characteristics that are is summarized in a `.csv` file named `file_information.csv`, which we can read in using `pandas`." + ] + }, + { + "cell_type": "code", + "execution_count": 3, + "metadata": {}, + "outputs": [ + { + "data": { + "text/html": [ + "
\n", + "\n", + "\n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + "
FileTaskCategory
0g0pA_taska.txtanon
1g0pA_taskb.txtbcut
2g0pA_taskc.txtclight
3g0pA_taskd.txtdheavy
4g0pA_taske.txtenon
\n", + "
" + ], + "text/plain": [ + " File Task Category\n", + "0 g0pA_taska.txt a non\n", + "1 g0pA_taskb.txt b cut\n", + "2 g0pA_taskc.txt c light\n", + "3 g0pA_taskd.txt d heavy\n", + "4 g0pA_taske.txt e non" + ] + }, + "execution_count": 3, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "csv_file = 'data/file_information.csv'\n", + "plagiarism_df = pd.read_csv(csv_file)\n", + "\n", + "# print out the first few rows of data info\n", + "plagiarism_df.head()" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Types of Plagiarism\n", + "\n", + "Each text file is associated with one **Task** (task A-E) and one **Category** of plagiarism, which you can see in the above DataFrame.\n", + "\n", + "### Tasks, A-E\n", + "\n", + "Each text file contains an answer to one short question; these questions are labeled as tasks A-E. For example, Task A asks the question: \"What is inheritance in object oriented programming?\"\n", + "\n", + "### Categories of plagiarism \n", + "\n", + "Each text file has an associated plagiarism label/category:\n", + "\n", + "**1. Plagiarized categories: `cut`, `light`, and `heavy`.**\n", + "* These categories represent different levels of plagiarized answer texts. `cut` answers copy directly from a source text, `light` answers are based on the source text but include some light rephrasing, and `heavy` answers are based on the source text, but *heavily* rephrased (and will likely be the most challenging kind of plagiarism to detect).\n", + " \n", + "**2. Non-plagiarized category: `non`.** \n", + "* `non` indicates that an answer is not plagiarized; the Wikipedia source text is not used to create this answer.\n", + " \n", + "**3. Special, source text category: `orig`.**\n", + "* This is a specific category for the original, Wikipedia source text. We will use these files only for comparison purposes." + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "---\n", + "## Pre-Process the Data\n", + "\n", + "In the next few cells, you'll be tasked with creating a new DataFrame of desired information about all of the files in the `data/` directory. This will prepare the data for feature extraction and for training a binary, plagiarism classifier." + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### EXERCISE: Convert categorical to numerical data\n", + "\n", + "You'll notice that the `Category` column in the data, contains string or categorical values, and to prepare these for feature extraction, we'll want to convert these into numerical values. Additionally, our goal is to create a binary classifier and so we'll need a binary class label that indicates whether an answer text is plagiarized (1) or not (0). Complete the below function `numerical_dataframe` that reads in a `file_information.csv` file by name, and returns a *new* DataFrame with a numerical `Category` column and a new `Class` column that labels each answer as plagiarized or not. \n", + "\n", + "Your function should return a new DataFrame with the following properties:\n", + "\n", + "* 4 columns: `File`, `Task`, `Category`, `Class`. The `File` and `Task` columns can remain unchanged from the original `.csv` file.\n", + "* Convert all `Category` labels to numerical labels according to the following rules (a higher value indicates a higher degree of plagiarism):\n", + " * 0 = `non`\n", + " * 1 = `heavy`\n", + " * 2 = `light`\n", + " * 3 = `cut`\n", + " * -1 = `orig`, this is a special value that indicates an original file.\n", + "* For the new `Class` column\n", + " * Any answer text that is not plagiarized (`non`) should have the class label `0`. \n", + " * Any plagiarized answer texts should have the class label `1`. \n", + " * And any `orig` texts will have a special label `-1`. \n", + "\n", + "### Expected output\n", + "\n", + "After running your function, you should get a DataFrame with rows that looks like the following: \n", + "```\n", + "\n", + " File\t Task Category Class\n", + "0\tg0pA_taska.txt\ta\t 0 \t0\n", + "1\tg0pA_taskb.txt\tb\t 3 \t1\n", + "2\tg0pA_taskc.txt\tc\t 2 \t1\n", + "3\tg0pA_taskd.txt\td\t 1 \t1\n", + "4\tg0pA_taske.txt\te\t 0\t 0\n", + "...\n", + "...\n", + "99 orig_taske.txt e -1 -1\n", + "\n", + "```" + ] + }, + { + "cell_type": "code", + "execution_count": 4, + "metadata": {}, + "outputs": [], + "source": [ + "# Read in a csv file and return a transformed dataframe\n", + "def numerical_dataframe(csv_file='data/file_information.csv'):\n", + " '''Reads in a csv file which is assumed to have `File`, `Category` and `Task` columns.\n", + " This function does two things: \n", + " 1) converts `Category` column values to numerical values \n", + " 2) Adds a new, numerical `Class` label column.\n", + " The `Class` column will label plagiarized answers as 1 and non-plagiarized as 0.\n", + " Source texts have a special label, -1.\n", + " :param csv_file: The directory for the file_information.csv file\n", + " :return: A dataframe with numerical categories and a new `Class` label column'''\n", + " \n", + " # your code here\n", + " category_to_numerical = {'non': 0, 'heavy': 1, 'light': 2, 'cut': 3, 'orig': -1 }\n", + " numerical_to_class = {'non': 0, 'heavy': 1, 'light': 1, 'cut': 1, 'orig': -1}\n", + " df = pd.read_csv(csv_file)\n", + " class_list = []\n", + " category_list = []\n", + " for index, row in df.iterrows():\n", + " category_list.append(category_to_numerical[row['Category']])\n", + " class_list.append(numerical_to_class[row['Category']])\n", + " df['Category'] = category_list\n", + " df['Class'] = class_list\n", + " return df\n" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Test cells\n", + "\n", + "Below are a couple of test cells. The first is an informal test where you can check that your code is working as expected by calling your function and printing out the returned result.\n", + "\n", + "The **second** cell below is a more rigorous test cell. The goal of a cell like this is to ensure that your code is working as expected, and to form any variables that might be used in _later_ tests/code, in this case, the data frame, `transformed_df`.\n", + "\n", + "> The cells in this notebook should be run in chronological order (the order they appear in the notebook). This is especially important for test cells.\n", + "\n", + "Often, later cells rely on the functions, imports, or variables defined in earlier cells. For example, some tests rely on previous tests to work.\n", + "\n", + "These tests do not test all cases, but they are a great way to check that you are on the right track!" + ] + }, + { + "cell_type": "code", + "execution_count": 5, + "metadata": {}, + "outputs": [ + { + "data": { + "text/html": [ + "
\n", + "\n", + "\n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + "
FileTaskCategoryClass
0g0pA_taska.txta00
1g0pA_taskb.txtb31
2g0pA_taskc.txtc21
3g0pA_taskd.txtd11
4g0pA_taske.txte00
5g0pB_taska.txta00
6g0pB_taskb.txtb00
7g0pB_taskc.txtc31
8g0pB_taskd.txtd21
9g0pB_taske.txte11
\n", + "
" + ], + "text/plain": [ + " File Task Category Class\n", + "0 g0pA_taska.txt a 0 0\n", + "1 g0pA_taskb.txt b 3 1\n", + "2 g0pA_taskc.txt c 2 1\n", + "3 g0pA_taskd.txt d 1 1\n", + "4 g0pA_taske.txt e 0 0\n", + "5 g0pB_taska.txt a 0 0\n", + "6 g0pB_taskb.txt b 0 0\n", + "7 g0pB_taskc.txt c 3 1\n", + "8 g0pB_taskd.txt d 2 1\n", + "9 g0pB_taske.txt e 1 1" + ] + }, + "execution_count": 5, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "# informal testing, print out the results of a called function\n", + "# create new `transformed_df`\n", + "transformed_df = numerical_dataframe(csv_file ='data/file_information.csv')\n", + "\n", + "# check work\n", + "# check that all categories of plagiarism have a class label = 1\n", + "transformed_df.head(10)" + ] + }, + { + "cell_type": "code", + "execution_count": 6, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Tests Passed!\n", + "\n", + "Example data: \n" + ] + }, + { + "data": { + "text/html": [ + "
\n", + "\n", + "\n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + "
FileTaskCategoryClass
0g0pA_taska.txta00
1g0pA_taskb.txtb31
2g0pA_taskc.txtc21
3g0pA_taskd.txtd11
4g0pA_taske.txte00
\n", + "
" + ], + "text/plain": [ + " File Task Category Class\n", + "0 g0pA_taska.txt a 0 0\n", + "1 g0pA_taskb.txt b 3 1\n", + "2 g0pA_taskc.txt c 2 1\n", + "3 g0pA_taskd.txt d 1 1\n", + "4 g0pA_taske.txt e 0 0" + ] + }, + "execution_count": 6, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "# test cell that creates `transformed_df`, if tests are passed\n", + "\n", + "\"\"\"\n", + "DON'T MODIFY ANYTHING IN THIS CELL THAT IS BELOW THIS LINE\n", + "\"\"\"\n", + "\n", + "# importing tests\n", + "import problem_unittests as tests\n", + "\n", + "# test numerical_dataframe function\n", + "tests.test_numerical_df(numerical_dataframe)\n", + "\n", + "# if above test is passed, create NEW `transformed_df`\n", + "transformed_df = numerical_dataframe(csv_file ='data/file_information.csv')\n", + "\n", + "# check work\n", + "print('\\nExample data: ')\n", + "transformed_df.head()" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Text Processing & Splitting Data\n", + "\n", + "Recall that the goal of this project is to build a plagiarism classifier. At it's heart, this task is a comparison text; one that looks at a given answer and a source text, compares them and predicts whether an answer has plagiarized from the source. To effectively do this comparison, and train a classifier we'll need to do a few more things: pre-process all of our text data and prepare the text files (in this case, the 95 answer files and 5 original source files) to be easily compared, and split our data into a `train` and `test` set that can be used to train a classifier and evaluate it, respectively. \n", + "\n", + "To this end, you've been provided code that adds additional information to your `transformed_df` from above. The next two cells need not be changed; they add two additional columns to the `transformed_df`:\n", + "\n", + "1. A `Text` column; this holds all the lowercase text for a `File`, with extraneous punctuation removed.\n", + "2. A `Datatype` column; this is a string value `train`, `test`, or `orig` that labels a data point as part of our train or test set\n", + "\n", + "The details of how these additional columns are created can be found in the `helpers.py` file in the project directory. You're encouraged to read through that file to see exactly how text is processed and how data is split.\n", + "\n", + "Run the cells below to get a `complete_df` that has all the information you need to proceed with plagiarism detection and feature engineering." + ] + }, + { + "cell_type": "code", + "execution_count": 7, + "metadata": {}, + "outputs": [ + { + "data": { + "text/html": [ + "
\n", + "\n", + "\n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + "
FileTaskCategoryClassText
0g0pA_taska.txta00inheritance is a basic concept of object orien...
1g0pA_taskb.txtb31pagerank is a link analysis algorithm used by ...
2g0pA_taskc.txtc21the vector space model also called term vector...
3g0pA_taskd.txtd11bayes theorem was names after rev thomas bayes...
4g0pA_taske.txte00dynamic programming is an algorithm design tec...
\n", + "
" + ], + "text/plain": [ + " File Task Category Class \\\n", + "0 g0pA_taska.txt a 0 0 \n", + "1 g0pA_taskb.txt b 3 1 \n", + "2 g0pA_taskc.txt c 2 1 \n", + "3 g0pA_taskd.txt d 1 1 \n", + "4 g0pA_taske.txt e 0 0 \n", + "\n", + " Text \n", + "0 inheritance is a basic concept of object orien... \n", + "1 pagerank is a link analysis algorithm used by ... \n", + "2 the vector space model also called term vector... \n", + "3 bayes theorem was names after rev thomas bayes... \n", + "4 dynamic programming is an algorithm design tec... " + ] + }, + "execution_count": 7, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "\"\"\"\n", + "DON'T MODIFY ANYTHING IN THIS CELL THAT IS BELOW THIS LINE\n", + "\"\"\"\n", + "import helpers \n", + "\n", + "# create a text column \n", + "text_df = helpers.create_text_column(transformed_df)\n", + "text_df.head()" + ] + }, + { + "cell_type": "code", + "execution_count": 8, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Sample processed text:\n", + "\n", + " inheritance is a basic concept of object oriented programming where the basic idea is to create new classes that add extra detail to existing classes this is done by allowing the new classes to reuse the methods and variables of the existing classes and new methods and classes are added to specialise the new class inheritance models the is kind of relationship between entities or objects for example postgraduates and undergraduates are both kinds of student this kind of relationship can be visualised as a tree structure where student would be the more general root node and both postgraduate and undergraduate would be more specialised extensions of the student node or the child nodes in this relationship student would be known as the superclass or parent class whereas postgraduate would be known as the subclass or child class because the postgraduate class extends the student class inheritance can occur on several layers where if visualised would display a larger tree structure for example we could further extend the postgraduate node by adding two extra extended classes to it called msc student and phd student as both these types of student are kinds of postgraduate student this would mean that both the msc student and phd student classes would inherit methods and variables from both the postgraduate and student classes \n" + ] + } + ], + "source": [ + "# after running the cell above\n", + "# check out the processed text for a single file, by row index\n", + "row_idx = 0 # feel free to change this index\n", + "\n", + "sample_text = text_df.iloc[0]['Text']\n", + "\n", + "print('Sample processed text:\\n\\n', sample_text)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Split data into training and test sets\n", + "\n", + "The next cell will add a `Datatype` column to a given DataFrame to indicate if the record is: \n", + "* `train` - Training data, for model training.\n", + "* `test` - Testing data, for model evaluation.\n", + "* `orig` - The task's original answer from wikipedia.\n", + "\n", + "### Stratified sampling\n", + "\n", + "The given code uses a helper function which you can view in the `helpers.py` file in the main project directory. This implements [stratified random sampling](https://en.wikipedia.org/wiki/Stratified_sampling) to randomly split data by task & plagiarism amount. Stratified sampling ensures that we get training and test data that is fairly evenly distributed across task & plagiarism combinations. Approximately 26% of the data is held out for testing and 74% of the data is used for training.\n", + "\n", + "The function **train_test_dataframe** takes in a DataFrame that it assumes has `Task` and `Category` columns, and, returns a modified frame that indicates which `Datatype` (train, test, or orig) a file falls into. This sampling will change slightly based on a passed in *random_seed*. Due to a small sample size, this stratified random sampling will provide more stable results for a binary plagiarism classifier. Stability here is smaller *variance* in the accuracy of classifier, given a random seed." + ] + }, + { + "cell_type": "code", + "execution_count": 9, + "metadata": {}, + "outputs": [ + { + "data": { + "text/html": [ + "
\n", + "\n", + "\n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + "
FileTaskCategoryClassTextDatatype
0g0pA_taska.txta00inheritance is a basic concept of object orien...train
1g0pA_taskb.txtb31pagerank is a link analysis algorithm used by ...test
2g0pA_taskc.txtc21the vector space model also called term vector...train
3g0pA_taskd.txtd11bayes theorem was names after rev thomas bayes...train
4g0pA_taske.txte00dynamic programming is an algorithm design tec...train
5g0pB_taska.txta00inheritance is a basic concept in object orien...train
6g0pB_taskb.txtb00pagerank pr refers to both the concept and the...train
7g0pB_taskc.txtc31vector space model is an algebraic model for r...test
8g0pB_taskd.txtd21bayes theorem relates the conditional and marg...train
9g0pB_taske.txte11dynamic programming is a method for solving ma...test
\n", + "
" + ], + "text/plain": [ + " File Task Category Class \\\n", + "0 g0pA_taska.txt a 0 0 \n", + "1 g0pA_taskb.txt b 3 1 \n", + "2 g0pA_taskc.txt c 2 1 \n", + "3 g0pA_taskd.txt d 1 1 \n", + "4 g0pA_taske.txt e 0 0 \n", + "5 g0pB_taska.txt a 0 0 \n", + "6 g0pB_taskb.txt b 0 0 \n", + "7 g0pB_taskc.txt c 3 1 \n", + "8 g0pB_taskd.txt d 2 1 \n", + "9 g0pB_taske.txt e 1 1 \n", + "\n", + " Text Datatype \n", + "0 inheritance is a basic concept of object orien... train \n", + "1 pagerank is a link analysis algorithm used by ... test \n", + "2 the vector space model also called term vector... train \n", + "3 bayes theorem was names after rev thomas bayes... train \n", + "4 dynamic programming is an algorithm design tec... train \n", + "5 inheritance is a basic concept in object orien... train \n", + "6 pagerank pr refers to both the concept and the... train \n", + "7 vector space model is an algebraic model for r... test \n", + "8 bayes theorem relates the conditional and marg... train \n", + "9 dynamic programming is a method for solving ma... test " + ] + }, + "execution_count": 9, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "random_seed = 1 # can change; set for reproducibility\n", + "\n", + "\"\"\"\n", + "DON'T MODIFY ANYTHING IN THIS CELL THAT IS BELOW THIS LINE\n", + "\"\"\"\n", + "import helpers\n", + "\n", + "# create new df with Datatype (train, test, orig) column\n", + "# pass in `text_df` from above to create a complete dataframe, with all the information you need\n", + "complete_df = helpers.train_test_dataframe(text_df, random_seed=random_seed)\n", + "\n", + "# check results\n", + "complete_df.head(10)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "# Determining Plagiarism\n", + "\n", + "Now that you've prepared this data and created a `complete_df` of information, including the text and class associated with each file, you can move on to the task of extracting similarity features that will be useful for plagiarism classification. \n", + "\n", + "> Note: The following code exercises, assume that the `complete_df` as it exists now, will **not** have its existing columns modified. \n", + "\n", + "The `complete_df` should always include the columns: `['File', 'Task', 'Category', 'Class', 'Text', 'Datatype']`. You can add additional columns, and you can create any new DataFrames you need by copying the parts of the `complete_df` as long as you do not modify the existing values, directly.\n", + "\n", + "---" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "\n", + "# Similarity Features \n", + "\n", + "One of the ways we might go about detecting plagiarism, is by computing **similarity features** that measure how similar a given answer text is as compared to the original wikipedia source text (for a specific task, a-e). The similarity features you will use are informed by [this paper on plagiarism detection](https://s3.amazonaws.com/video.udacity-data.com/topher/2019/January/5c412841_developing-a-corpus-of-plagiarised-short-answers/developing-a-corpus-of-plagiarised-short-answers.pdf). \n", + "> In this paper, researchers created features called **containment** and **longest common subsequence**. \n", + "\n", + "Using these features as input, you will train a model to distinguish between plagiarized and not-plagiarized text files.\n", + "\n", + "## Feature Engineering\n", + "\n", + "Let's talk a bit more about the features we want to include in a plagiarism detection model and how to calculate such features. In the following explanations, I'll refer to a submitted text file as a **Student Answer Text (A)** and the original, wikipedia source file (that we want to compare that answer to) as the **Wikipedia Source Text (S)**.\n", + "\n", + "### Containment\n", + "\n", + "Your first task will be to create **containment features**. To understand containment, let's first revisit a definition of [n-grams](https://en.wikipedia.org/wiki/N-gram). An *n-gram* is a sequential word grouping. For example, in a line like \"bayes rule gives us a way to combine prior knowledge with new information,\" a 1-gram is just one word, like \"bayes.\" A 2-gram might be \"bayes rule\" and a 3-gram might be \"combine prior knowledge.\"\n", + "\n", + "> Containment is defined as the **intersection** of the n-gram word count of the Wikipedia Source Text (S) with the n-gram word count of the Student Answer Text (S) *divided* by the n-gram word count of the Student Answer Text.\n", + "\n", + "$$ \\frac{\\sum{count(\\text{ngram}_{A}) \\cap count(\\text{ngram}_{S})}}{\\sum{count(\\text{ngram}_{A})}} $$\n", + "\n", + "If the two texts have no n-grams in common, the containment will be 0, but if _all_ their n-grams intersect then the containment will be 1. Intuitively, you can see how having longer n-gram's in common, might be an indication of cut-and-paste plagiarism. In this project, it will be up to you to decide on the appropriate `n` or several `n`'s to use in your final model.\n", + "\n", + "### EXERCISE: Create containment features\n", + "\n", + "Given the `complete_df` that you've created, you should have all the information you need to compare any Student Answer Text (A) with its appropriate Wikipedia Source Text (S). An answer for task A should be compared to the source text for task A, just as answers to tasks B, C, D, and E should be compared to the corresponding original source text.\n", + "\n", + "In this exercise, you'll complete the function, `calculate_containment` which calculates containment based upon the following parameters:\n", + "* A given DataFrame, `df` (which is assumed to be the `complete_df` from above)\n", + "* An `answer_filename`, such as 'g0pB_taskd.txt' \n", + "* An n-gram length, `n`\n", + "\n", + "### Containment calculation\n", + "\n", + "The general steps to complete this function are as follows:\n", + "1. From *all* of the text files in a given `df`, create an array of n-gram counts; it is suggested that you use a [CountVectorizer](https://scikit-learn.org/stable/modules/generated/sklearn.feature_extraction.text.CountVectorizer.html) for this purpose.\n", + "2. Get the processed answer and source texts for the given `answer_filename`.\n", + "3. Calculate the containment between an answer and source text according to the following equation.\n", + "\n", + " >$$ \\frac{\\sum{count(\\text{ngram}_{A}) \\cap count(\\text{ngram}_{S})}}{\\sum{count(\\text{ngram}_{A})}} $$\n", + " \n", + "4. Return that containment value.\n", + "\n", + "You are encouraged to write any helper functions that you need to complete the function below." + ] + }, + { + "cell_type": "code", + "execution_count": 10, + "metadata": {}, + "outputs": [], + "source": [ + "from sklearn.feature_extraction.text import CountVectorizer\n", + "\n", + "# Calculate the ngram containment for one answer file/source file pair in a df\n", + "def calculate_containment(df, n, answer_filename):\n", + " '''Calculates the containment between a given answer text and its associated source text.\n", + " This function creates a count of ngrams (of a size, n) for each text file in our data.\n", + " Then calculates the containment by finding the ngram count for a given answer text, \n", + " and its associated source text, and calculating the normalized intersection of those counts.\n", + " :param df: A dataframe with columns,\n", + " 'File', 'Task', 'Category', 'Class', 'Text', and 'Datatype'\n", + " :param n: An integer that defines the ngram size\n", + " :param answer_filename: A filename for an answer text in the df, ex. 'g0pB_taskd.txt'\n", + " :return: A single containment value that represents the similarity\n", + " between an answer text and its source text.\n", + " '''\n", + " \n", + " # your code here\n", + " row = df.loc[df['File'] == answer_filename]\n", + " answer = row['File']\n", + " answer_location = answer.index.item()\n", + " \n", + " source = df[(df['Task'] == df.iloc[answer_location]['Task']) & (df['Category'] == -1)]\n", + " source_location = source.index.item()\n", + " \n", + " counts = CountVectorizer(analyzer='word', ngram_range=(n,n))\n", + " ngrams = counts.fit_transform(df['Text'])\n", + " \n", + " ngram_array = ngrams.toarray()\n", + " answer_and_source = ngram_array[(answer_location, source_location),]\n", + " \n", + " sum_intersection_ngrams = np.sum(np.min(answer_and_source, axis=0))\n", + " containment = sum_intersection_ngrams / np.sum(answer_and_source[0])\n", + " \n", + " return containment\n" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Test cells\n", + "\n", + "After you've implemented the containment function, you can test out its behavior. \n", + "\n", + "The cell below iterates through the first few files, and calculates the original category _and_ containment values for a specified n and file.\n", + "\n", + ">If you've implemented this correctly, you should see that the non-plagiarized have low or close to 0 containment values and that plagiarized examples have higher containment values, closer to 1.\n", + "\n", + "Note what happens when you change the value of n. I recommend applying your code to multiple files and comparing the resultant containment values. You should see that the highest containment values correspond to files with the highest category (`cut`) of plagiarism level." + ] + }, + { + "cell_type": "code", + "execution_count": 11, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Original category values: \n", + " [0, 3, 2, 1, 0]\n", + "\n", + "3-gram containment values: \n", + " [0.009345794392523364, 0.9641025641025641, 0.6136363636363636, 0.15675675675675677, 0.031746031746031744]\n" + ] + } + ], + "source": [ + "# select a value for n\n", + "n = 3\n", + "\n", + "# indices for first few files\n", + "test_indices = range(5)\n", + "\n", + "# iterate through files and calculate containment\n", + "category_vals = []\n", + "containment_vals = []\n", + "for i in test_indices:\n", + " # get level of plagiarism for a given file index\n", + " category_vals.append(complete_df.loc[i, 'Category'])\n", + " # calculate containment for given file and n\n", + " filename = complete_df.loc[i, 'File']\n", + " c = calculate_containment(complete_df, n, filename)\n", + " containment_vals.append(c)\n", + "\n", + "# print out result, does it make sense?\n", + "print('Original category values: \\n', category_vals)\n", + "print()\n", + "print(str(n)+'-gram containment values: \\n', containment_vals)" + ] + }, + { + "cell_type": "code", + "execution_count": 12, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Tests Passed!\n" + ] + } + ], + "source": [ + "# run this test cell\n", + "\"\"\"\n", + "DON'T MODIFY ANYTHING IN THIS CELL THAT IS BELOW THIS LINE\n", + "\"\"\"\n", + "# test containment calculation\n", + "# params: complete_df from before, and containment function\n", + "tests.test_containment(complete_df, calculate_containment)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### QUESTION 1: Why can we calculate containment features across *all* data (training & test), prior to splitting the DataFrame for modeling? That is, what about the containment calculation means that the test and training data do not influence each other?" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "**Answer:** To make the model as accurate as possible, we train it on both training and test datasets. This also eliminates the condition of missing out important words that might signify plagiarism.\n" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "---\n", + "## Longest Common Subsequence\n", + "\n", + "Containment a good way to find overlap in word usage between two documents; it may help identify cases of cut-and-paste as well as paraphrased levels of plagiarism. Since plagiarism is a fairly complex task with varying levels, it's often useful to include other measures of similarity. The paper also discusses a feature called **longest common subsequence**.\n", + "\n", + "> The longest common subsequence is the longest string of words (or letters) that are *the same* between the Wikipedia Source Text (S) and the Student Answer Text (A). This value is also normalized by dividing by the total number of words (or letters) in the Student Answer Text. \n", + "\n", + "In this exercise, we'll ask you to calculate the longest common subsequence of words between two texts.\n", + "\n", + "### EXERCISE: Calculate the longest common subsequence\n", + "\n", + "Complete the function `lcs_norm_word`; this should calculate the *longest common subsequence* of words between a Student Answer Text and corresponding Wikipedia Source Text. \n", + "\n", + "It may be helpful to think of this in a concrete example. A Longest Common Subsequence (LCS) problem may look as follows:\n", + "* Given two texts: text A (answer text) of length n, and string S (original source text) of length m. Our goal is to produce their longest common subsequence of words: the longest sequence of words that appear left-to-right in both texts (though the words don't have to be in continuous order).\n", + "* Consider:\n", + " * A = \"i think pagerank is a link analysis algorithm used by google that uses a system of weights attached to each element of a hyperlinked set of documents\"\n", + " * S = \"pagerank is a link analysis algorithm used by the google internet search engine that assigns a numerical weighting to each element of a hyperlinked set of documents\"\n", + "\n", + "* In this case, we can see that the start of each sentence of fairly similar, having overlap in the sequence of words, \"pagerank is a link analysis algorithm used by\" before diverging slightly. Then we **continue moving left -to-right along both texts** until we see the next common sequence; in this case it is only one word, \"google\". Next we find \"that\" and \"a\" and finally the same ending \"to each element of a hyperlinked set of documents\".\n", + "* Below, is a clear visual of how these sequences were found, sequentially, in each text.\n", + "\n", + "\n", + "\n", + "* Now, those words appear in left-to-right order in each document, sequentially, and even though there are some words in between, we count this as the longest common subsequence between the two texts. \n", + "* If I count up each word that I found in common I get the value 20. **So, LCS has length 20**. \n", + "* Next, to normalize this value, divide by the total length of the student answer; in this example that length is only 27. **So, the function `lcs_norm_word` should return the value `20/27` or about `0.7408`.**\n", + "\n", + "In this way, LCS is a great indicator of cut-and-paste plagiarism or if someone has referenced the same source text multiple times in an answer." + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### LCS, dynamic programming\n", + "\n", + "If you read through the scenario above, you can see that this algorithm depends on looking at two texts and comparing them word by word. You can solve this problem in multiple ways. First, it may be useful to `.split()` each text into lists of comma separated words to compare. Then, you can iterate through each word in the texts and compare them, adding to your value for LCS as you go. \n", + "\n", + "The method I recommend for implementing an efficient LCS algorithm is: using a matrix and dynamic programming. **Dynamic programming** is all about breaking a larger problem into a smaller set of subproblems, and building up a complete result without having to repeat any subproblems. \n", + "\n", + "This approach assumes that you can split up a large LCS task into a combination of smaller LCS tasks. Let's look at a simple example that compares letters:\n", + "\n", + "* A = \"ABCD\"\n", + "* S = \"BD\"\n", + "\n", + "We can see right away that the longest subsequence of _letters_ here is 2 (B and D are in sequence in both strings). And we can calculate this by looking at relationships between each letter in the two strings, A and S.\n", + "\n", + "Here, I have a matrix with the letters of A on top and the letters of S on the left side:\n", + "\n", + "\n", + "\n", + "This starts out as a matrix that has as many columns and rows as letters in the strings S and O **+1** additional row and column, filled with zeros on the top and left sides. So, in this case, instead of a 2x4 matrix it is a 3x5.\n", + "\n", + "Now, we can fill this matrix up by breaking it into smaller LCS problems. For example, let's first look at the shortest substrings: the starting letter of A and S. We'll first ask, what is the Longest Common Subsequence between these two letters \"A\" and \"B\"? \n", + "\n", + "**Here, the answer is zero and we fill in the corresponding grid cell with that value.**\n", + "\n", + "\n", + "\n", + "Then, we ask the next question, what is the LCS between \"AB\" and \"B\"?\n", + "\n", + "**Here, we have a match, and can fill in the appropriate value 1**.\n", + "\n", + "\n", + "\n", + "If we continue, we get to a final matrix that looks as follows, with a **2** in the bottom right corner.\n", + "\n", + "\n", + "\n", + "The final LCS will be that value **2** *normalized* by the number of n-grams in A. So, our normalized value is 2/4 = **0.5**.\n", + "\n", + "### The matrix rules\n", + "\n", + "One thing to notice here is that, you can efficiently fill up this matrix one cell at a time. Each grid cell only depends on the values in the grid cells that are directly on top and to the left of it, or on the diagonal/top-left. The rules are as follows:\n", + "* Start with a matrix that has one extra row and column of zeros.\n", + "* As you traverse your string:\n", + " * If there is a match, fill that grid cell with the value to the top-left of that cell *plus* one. So, in our case, when we found a matching B-B, we added +1 to the value in the top-left of the matching cell, 0.\n", + " * If there is not a match, take the *maximum* value from either directly to the left or the top cell, and carry that value over to the non-match cell.\n", + "\n", + "\n", + "\n", + "After completely filling the matrix, **the bottom-right cell will hold the non-normalized LCS value**.\n", + "\n", + "This matrix treatment can be applied to a set of words instead of letters. Your function should apply this to the words in two texts and return the normalized LCS value." + ] + }, + { + "cell_type": "code", + "execution_count": 13, + "metadata": {}, + "outputs": [], + "source": [ + "# Compute the normalized LCS given an answer text and a source text\n", + "def lcs_norm_word(answer_text, source_text):\n", + " '''Computes the longest common subsequence of words in two texts; returns a normalized value.\n", + " :param answer_text: The pre-processed text for an answer text\n", + " :param source_text: The pre-processed text for an answer's associated source text\n", + " :return: A normalized LCS value'''\n", + " \n", + " answer = answer_text.split()\n", + " source = source_text.split()\n", + " \n", + " lcs_matrix = np.zeros((len(answer) + 1, len(source) + 1))\n", + " row_index= 0\n", + " col_index = 0\n", + " for row_index in range(0, len(answer)):\n", + " answer_word = answer[row_index]\n", + " for col_index in range(0, len(source)):\n", + " source_word = source[col_index]\n", + " if source_word == answer_word:\n", + " lcs_matrix[row_index + 1][col_index + 1] = (lcs_matrix[row_index][col_index]) + 1\n", + " else: \n", + " lcs_matrix[row_index + 1][col_index + 1] = max(lcs_matrix[row_index][col_index + 1], \n", + " lcs_matrix[row_index + 1][col_index])\n", + "\n", + " normalized_lcs = lcs_matrix[len(answer)][len(source)] / len(answer)\n", + " print(normalized_lcs)\n", + " return normalized_lcs\n" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Test cells\n", + "\n", + "Let's start by testing out your code on the example given in the initial description.\n", + "\n", + "In the below cell, we have specified strings A (answer text) and S (original source text). We know that these texts have 20 words in common and the submitted answer is 27 words long, so the normalized, longest common subsequence should be 20/27.\n" + ] + }, + { + "cell_type": "code", + "execution_count": 14, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "0.7407407407407407\n", + "LCS = 0.7407407407407407\n", + "Test passed!\n" + ] + } + ], + "source": [ + "# Run the test scenario from above\n", + "# does your function return the expected value?\n", + "\n", + "A = \"i think pagerank is a link analysis algorithm used by google that uses a system of weights attached to each element of a hyperlinked set of documents\"\n", + "S = \"pagerank is a link analysis algorithm used by the google internet search engine that assigns a numerical weighting to each element of a hyperlinked set of documents\"\n", + "\n", + "# calculate LCS\n", + "lcs = lcs_norm_word(A, S)\n", + "print('LCS = ', lcs)\n", + "\n", + "\n", + "# expected value test\n", + "assert lcs==20/27., \"Incorrect LCS value, expected about 0.7408, got \"+str(lcs)\n", + "\n", + "print('Test passed!')" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "This next cell runs a more rigorous test." + ] + }, + { + "cell_type": "code", + "execution_count": 15, + "metadata": {}, + "outputs": [ + { + "data": { + "text/html": [ + "
\n", + "\n", + "\n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + "
FileTaskCategoryClassTextDatatype
0g0pA_taska.txta00inheritance is a basic concept of object orien...train
1g0pA_taskb.txtb31pagerank is a link analysis algorithm used by ...test
2g0pA_taskc.txtc21the vector space model also called term vector...train
3g0pA_taskd.txtd11bayes theorem was names after rev thomas bayes...train
4g0pA_taske.txte00dynamic programming is an algorithm design tec...train
\n", + "
" + ], + "text/plain": [ + " File Task Category Class \\\n", + "0 g0pA_taska.txt a 0 0 \n", + "1 g0pA_taskb.txt b 3 1 \n", + "2 g0pA_taskc.txt c 2 1 \n", + "3 g0pA_taskd.txt d 1 1 \n", + "4 g0pA_taske.txt e 0 0 \n", + "\n", + " Text Datatype \n", + "0 inheritance is a basic concept of object orien... train \n", + "1 pagerank is a link analysis algorithm used by ... test \n", + "2 the vector space model also called term vector... train \n", + "3 bayes theorem was names after rev thomas bayes... train \n", + "4 dynamic programming is an algorithm design tec... train " + ] + }, + "execution_count": 15, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "complete_df.head()" + ] + }, + { + "cell_type": "code", + "execution_count": 16, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "0.42783505154639173\n", + "0.1917808219178082\n", + "0.8207547169811321\n", + "0.8464912280701754\n", + "0.3160621761658031\n", + "0.24257425742574257\n", + "Tests Passed!\n" + ] + } + ], + "source": [ + "# run test cell\n", + "\"\"\"\n", + "DON'T MODIFY ANYTHING IN THIS CELL THAT IS BELOW THIS LINE\n", + "\"\"\"\n", + "# test lcs implementation\n", + "# params: complete_df from before, and lcs_norm_word function\n", + "tests.test_lcs(complete_df, lcs_norm_word)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Finally, take a look at a few resultant values for `lcs_norm_word`. Just like before, you should see that higher values correspond to higher levels of plagiarism." + ] + }, + { + "cell_type": "code", + "execution_count": 17, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "0.1917808219178082\n", + "0.8207547169811321\n", + "0.8464912280701754\n", + "0.3160621761658031\n", + "0.24257425742574257\n", + "Original category values: \n", + " [0, 3, 2, 1, 0]\n", + "\n", + "Normalized LCS values: \n", + " [0.1917808219178082, 0.8207547169811321, 0.8464912280701754, 0.3160621761658031, 0.24257425742574257]\n" + ] + } + ], + "source": [ + "# test on your own\n", + "test_indices = range(5) # look at first few files\n", + "\n", + "category_vals = []\n", + "lcs_norm_vals = []\n", + "# iterate through first few docs and calculate LCS\n", + "for i in test_indices:\n", + " category_vals.append(complete_df.loc[i, 'Category'])\n", + " # get texts to compare\n", + " answer_text = complete_df.loc[i, 'Text'] \n", + " task = complete_df.loc[i, 'Task']\n", + " # we know that source texts have Class = -1\n", + " orig_rows = complete_df[(complete_df['Class'] == -1)]\n", + " orig_row = orig_rows[(orig_rows['Task'] == task)]\n", + " source_text = orig_row['Text'].values[0]\n", + " # calculate lcs\n", + " lcs_val = lcs_norm_word(answer_text, source_text)\n", + " lcs_norm_vals.append(lcs_val)\n", + "\n", + "# print out result, does it make sense?\n", + "print('Original category values: \\n', category_vals)\n", + "print()\n", + "print('Normalized LCS values: \\n', lcs_norm_vals)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "---\n", + "# Create All Features\n", + "\n", + "Now that you've completed the feature calculation functions, it's time to actually create multiple features and decide on which ones to use in your final model! In the below cells, you're provided two helper functions to help you create multiple features and store those in a DataFrame, `features_df`.\n", + "\n", + "### Creating multiple containment features\n", + "\n", + "Your completed `calculate_containment` function will be called in the next cell, which defines the helper function `create_containment_features`. \n", + "\n", + "> This function returns a list of containment features, calculated for a given `n` and for *all* files in a df (assumed to the the `complete_df`).\n", + "\n", + "For our original files, the containment value is set to a special value, -1.\n", + "\n", + "This function gives you the ability to easily create several containment features, of different n-gram lengths, for each of our text files." + ] + }, + { + "cell_type": "code", + "execution_count": 18, + "metadata": {}, + "outputs": [], + "source": [ + "\"\"\"\n", + "DON'T MODIFY ANYTHING IN THIS CELL THAT IS BELOW THIS LINE\n", + "\"\"\"\n", + "# Function returns a list of containment features, calculated for a given n \n", + "# Should return a list of length 100 for all files in a complete_df\n", + "def create_containment_features(df, n, column_name=None):\n", + " \n", + " containment_values = []\n", + " \n", + " if(column_name==None):\n", + " column_name = 'c_'+str(n) # c_1, c_2, .. c_n\n", + " \n", + " # iterates through dataframe rows\n", + " for i in df.index:\n", + " file = df.loc[i, 'File']\n", + " # Computes features using calculate_containment function\n", + " if df.loc[i,'Category'] > -1:\n", + " c = calculate_containment(df, n, file)\n", + " containment_values.append(c)\n", + " # Sets value to -1 for original tasks \n", + " else:\n", + " containment_values.append(-1)\n", + " \n", + " print(str(n)+'-gram containment features created!')\n", + " return containment_values\n" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Creating LCS features\n", + "\n", + "Below, your complete `lcs_norm_word` function is used to create a list of LCS features for all the answer files in a given DataFrame (again, this assumes you are passing in the `complete_df`. It assigns a special value for our original, source files, -1.\n" + ] + }, + { + "cell_type": "code", + "execution_count": 19, + "metadata": {}, + "outputs": [], + "source": [ + "\"\"\"\n", + "DON'T MODIFY ANYTHING IN THIS CELL THAT IS BELOW THIS LINE\n", + "\"\"\"\n", + "# Function creates lcs feature and add it to the dataframe\n", + "def create_lcs_features(df, column_name='lcs_word'):\n", + " \n", + " lcs_values = []\n", + " \n", + " # iterate through files in dataframe\n", + " for i in df.index:\n", + " # Computes LCS_norm words feature using function above for answer tasks\n", + " if df.loc[i,'Category'] > -1:\n", + " # get texts to compare\n", + " answer_text = df.loc[i, 'Text'] \n", + " task = df.loc[i, 'Task']\n", + " # we know that source texts have Class = -1\n", + " orig_rows = df[(df['Class'] == -1)]\n", + " orig_row = orig_rows[(orig_rows['Task'] == task)]\n", + " source_text = orig_row['Text'].values[0]\n", + "\n", + " # calculate lcs\n", + " lcs = lcs_norm_word(answer_text, source_text)\n", + " lcs_values.append(lcs)\n", + " # Sets to -1 for original tasks \n", + " else:\n", + " lcs_values.append(-1)\n", + "\n", + " print('LCS features created!')\n", + " return lcs_values\n", + " " + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## EXERCISE: Create a features DataFrame by selecting an `ngram_range`\n", + "\n", + "The paper suggests calculating the following features: containment *1-gram to 5-gram* and *longest common subsequence*. \n", + "> In this exercise, you can choose to create even more features, for example from *1-gram to 7-gram* containment features and *longest common subsequence*. \n", + "\n", + "You'll want to create at least 6 features to choose from as you think about which to give to your final, classification model. Defining and comparing at least 6 different features allows you to discard any features that seem redundant, and choose to use the best features for your final model!\n", + "\n", + "In the below cell **define an n-gram range**; these will be the n's you use to create n-gram containment features. The rest of the feature creation code is provided." + ] + }, + { + "cell_type": "code", + "execution_count": 20, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "1-gram containment features created!\n", + "2-gram containment features created!\n", + "3-gram containment features created!\n", + "4-gram containment features created!\n", + "5-gram containment features created!\n", + "6-gram containment features created!\n", + "0.1917808219178082\n", + "0.8207547169811321\n", + "0.8464912280701754\n", + "0.3160621761658031\n", + "0.24257425742574257\n", + "0.16117216117216118\n", + "0.30165289256198347\n", + "0.6217105263157895\n", + "0.484304932735426\n", + "0.597457627118644\n", + "0.42783505154639173\n", + "0.2708333333333333\n", + "0.22395833333333334\n", + "0.9\n", + "0.8940397350993378\n", + "0.8232044198895028\n", + "0.775\n", + "0.45977011494252873\n", + "0.3055555555555556\n", + "0.2826086956521739\n", + "0.9930555555555556\n", + "0.7888888888888889\n", + "0.3246753246753247\n", + "0.3466666666666667\n", + "1.0\n", + "0.18932038834951456\n", + "0.36893203883495146\n", + "0.4166666666666667\n", + "0.4898785425101215\n", + "0.24742268041237114\n", + "0.21875\n", + "0.29441624365482233\n", + "0.5163934426229508\n", + "0.4725274725274725\n", + "0.6064516129032258\n", + "0.536697247706422\n", + "0.39436619718309857\n", + "0.25833333333333336\n", + "0.2789115646258503\n", + "0.3431372549019608\n", + "0.15302491103202848\n", + "0.4559386973180077\n", + "0.82\n", + "0.45\n", + "0.22935779816513763\n", + "0.16535433070866143\n", + "0.26046511627906976\n", + "0.3415841584158416\n", + "0.9294117647058824\n", + "1.0\n", + "0.6699029126213593\n", + "0.3551912568306011\n", + "0.23376623376623376\n", + "0.3492647058823529\n", + "0.3476190476190476\n", + "0.5677233429394812\n", + "0.774390243902439\n", + "0.19298245614035087\n", + "0.21818181818181817\n", + "0.26666666666666666\n", + "0.22110552763819097\n", + "0.5047169811320755\n", + "0.5585585585585585\n", + "0.9966996699669967\n", + "0.2289156626506024\n", + "0.1722488038277512\n", + "0.23684210526315788\n", + "0.29493087557603687\n", + "0.5037593984962406\n", + "0.9117647058823529\n", + "0.9923076923076923\n", + "0.2833333333333333\n", + "0.2616822429906542\n", + "0.6470588235294118\n", + "0.85\n", + "0.178743961352657\n", + "0.2350230414746544\n", + "0.6619718309859155\n", + "0.7911111111111111\n", + "0.9298245614035088\n", + "0.8546712802768166\n", + "0.2983425414364641\n", + "0.2230769230769231\n", + "0.9270833333333334\n", + "0.9098039215686274\n", + "0.4900990099009901\n", + "0.25203252032520324\n", + "0.1774193548387097\n", + "0.22767857142857142\n", + "0.6437246963562753\n", + "0.24271844660194175\n", + "0.8395061728395061\n", + "0.2830188679245283\n", + "0.16176470588235295\n", + "0.24583333333333332\n", + "LCS features created!\n", + "\n", + "Features: ['c_1', 'c_2', 'c_3', 'c_4', 'c_5', 'c_6', 'lcs_word']\n", + "\n" + ] + } + ], + "source": [ + "# Define an ngram range\n", + "ngram_range = range(1,7)\n", + "\n", + "\n", + "# The following code may take a minute to run, depending on your ngram_range\n", + "\"\"\"\n", + "DON'T MODIFY ANYTHING IN THIS CELL THAT IS BELOW THIS LINE\n", + "\"\"\"\n", + "features_list = []\n", + "\n", + "# Create features in a features_df\n", + "all_features = np.zeros((len(ngram_range)+1, len(complete_df)))\n", + "\n", + "# Calculate features for containment for ngrams in range\n", + "i=0\n", + "for n in ngram_range:\n", + " column_name = 'c_'+str(n)\n", + " features_list.append(column_name)\n", + " # create containment features\n", + " all_features[i]=np.squeeze(create_containment_features(complete_df, n))\n", + " i+=1\n", + "\n", + "# Calculate features for LCS_Norm Words \n", + "features_list.append('lcs_word')\n", + "all_features[i]= np.squeeze(create_lcs_features(complete_df))\n", + "\n", + "# create a features dataframe\n", + "features_df = pd.DataFrame(np.transpose(all_features), columns=features_list)\n", + "\n", + "# Print all features/columns\n", + "print()\n", + "print('Features: ', features_list)\n", + "print()" + ] + }, + { + "cell_type": "code", + "execution_count": 21, + "metadata": {}, + "outputs": [ + { + "data": { + "text/html": [ + "
\n", + "\n", + "\n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + "
c_1c_2c_3c_4c_5c_6lcs_word
00.3981480.0790700.0093460.0000000.0000000.0000000.191781
11.0000000.9846940.9641030.9432990.9222800.9010420.820755
20.8693690.7194570.6136360.5159820.4495410.3824880.846491
30.5935830.2688170.1567570.1086960.0819670.0604400.316062
40.5445030.1157890.0317460.0053190.0000000.0000000.242574
50.3295020.0538460.0077220.0038760.0000000.0000000.161172
60.5903080.1504420.0355560.0044640.0000000.0000000.301653
70.7653060.7098980.6643840.6254300.5896550.5536330.621711
80.7597770.5056180.3954800.3068180.2457140.1954020.484305
90.8844440.5267860.3408070.2477480.1809950.1500000.597458
\n", + "
" + ], + "text/plain": [ + " c_1 c_2 c_3 c_4 c_5 c_6 lcs_word\n", + "0 0.398148 0.079070 0.009346 0.000000 0.000000 0.000000 0.191781\n", + "1 1.000000 0.984694 0.964103 0.943299 0.922280 0.901042 0.820755\n", + "2 0.869369 0.719457 0.613636 0.515982 0.449541 0.382488 0.846491\n", + "3 0.593583 0.268817 0.156757 0.108696 0.081967 0.060440 0.316062\n", + "4 0.544503 0.115789 0.031746 0.005319 0.000000 0.000000 0.242574\n", + "5 0.329502 0.053846 0.007722 0.003876 0.000000 0.000000 0.161172\n", + "6 0.590308 0.150442 0.035556 0.004464 0.000000 0.000000 0.301653\n", + "7 0.765306 0.709898 0.664384 0.625430 0.589655 0.553633 0.621711\n", + "8 0.759777 0.505618 0.395480 0.306818 0.245714 0.195402 0.484305\n", + "9 0.884444 0.526786 0.340807 0.247748 0.180995 0.150000 0.597458" + ] + }, + "execution_count": 21, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "# print some results \n", + "features_df.head(10)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Correlated Features\n", + "\n", + "You should use feature correlation across the *entire* dataset to determine which features are ***too*** **highly-correlated** with each other to include both features in a single model. For this analysis, you can use the *entire* dataset due to the small sample size we have. \n", + "\n", + "All of our features try to measure the similarity between two texts. Since our features are designed to measure similarity, it is expected that these features will be highly-correlated. Many classification models, for example a Naive Bayes classifier, rely on the assumption that features are *not* highly correlated; highly-correlated features may over-inflate the importance of a single feature. \n", + "\n", + "So, you'll want to choose your features based on which pairings have the lowest correlation. These correlation values range between 0 and 1; from low to high correlation, and are displayed in a [correlation matrix](https://www.displayr.com/what-is-a-correlation-matrix/), below." + ] + }, + { + "cell_type": "code", + "execution_count": 22, + "metadata": {}, + "outputs": [ + { + "data": { + "text/html": [ + "
\n", + "\n", + "\n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + "
c_1c_2c_3c_4c_5c_6lcs_word
c_11.000.940.900.890.880.870.97
c_20.941.000.990.980.970.960.98
c_30.900.991.001.000.990.980.97
c_40.890.981.001.001.000.990.95
c_50.880.970.991.001.001.000.95
c_60.870.960.980.991.001.000.94
lcs_word0.970.980.970.950.950.941.00
\n", + "
" + ], + "text/plain": [ + " c_1 c_2 c_3 c_4 c_5 c_6 lcs_word\n", + "c_1 1.00 0.94 0.90 0.89 0.88 0.87 0.97\n", + "c_2 0.94 1.00 0.99 0.98 0.97 0.96 0.98\n", + "c_3 0.90 0.99 1.00 1.00 0.99 0.98 0.97\n", + "c_4 0.89 0.98 1.00 1.00 1.00 0.99 0.95\n", + "c_5 0.88 0.97 0.99 1.00 1.00 1.00 0.95\n", + "c_6 0.87 0.96 0.98 0.99 1.00 1.00 0.94\n", + "lcs_word 0.97 0.98 0.97 0.95 0.95 0.94 1.00" + ] + }, + "metadata": {}, + "output_type": "display_data" + } + ], + "source": [ + "\"\"\"\n", + "DON'T MODIFY ANYTHING IN THIS CELL THAT IS BELOW THIS LINE\n", + "\"\"\"\n", + "# Create correlation matrix for just Features to determine different models to test\n", + "corr_matrix = features_df.corr().abs().round(2)\n", + "\n", + "# display shows all of a dataframe\n", + "display(corr_matrix)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## EXERCISE: Create selected train/test data\n", + "\n", + "Complete the `train_test_data` function below. This function should take in the following parameters:\n", + "* `complete_df`: A DataFrame that contains all of our processed text data, file info, datatypes, and class labels\n", + "* `features_df`: A DataFrame of all calculated features, such as containment for ngrams, n= 1-5, and lcs values for each text file listed in the `complete_df` (this was created in the above cells)\n", + "* `selected_features`: A list of feature column names, ex. `['c_1', 'lcs_word']`, which will be used to select the final features in creating train/test sets of data.\n", + "\n", + "It should return two tuples:\n", + "* `(train_x, train_y)`, selected training features and their corresponding class labels (0/1)\n", + "* `(test_x, test_y)`, selected training features and their corresponding class labels (0/1)\n", + "\n", + "** Note: x and y should be arrays of feature values and numerical class labels, respectively; not DataFrames.**\n", + "\n", + "Looking at the above correlation matrix, you should decide on a **cutoff** correlation value, less than 1.0, to determine which sets of features are *too* highly-correlated to be included in the final training and test data. If you cannot find features that are less correlated than some cutoff value, it is suggested that you increase the number of features (longer n-grams) to choose from or use *only one or two* features in your final model to avoid introducing highly-correlated features.\n", + "\n", + "Recall that the `complete_df` has a `Datatype` column that indicates whether data should be `train` or `test` data; this should help you split the data appropriately." + ] + }, + { + "cell_type": "code", + "execution_count": 23, + "metadata": {}, + "outputs": [], + "source": [ + "# Takes in dataframes and a list of selected features (column names) \n", + "# and returns (train_x, train_y), (test_x, test_y)\n", + "def train_test_data(complete_df, features_df, selected_features):\n", + " '''Gets selected training and test features from given dataframes, and \n", + " returns tuples for training and test features and their corresponding class labels.\n", + " :param complete_df: A dataframe with all of our processed text data, datatypes, and labels\n", + " :param features_df: A dataframe of all computed, similarity features\n", + " :param selected_features: An array of selected features that correspond to certain columns in `features_df`\n", + " :return: training and test features and labels: (train_x, train_y), (test_x, test_y)'''\n", + " \n", + " merged_df = complete_df.merge(features_df, left_index=True, right_index=True)\n", + " \n", + " # get the training features\n", + " train_x = merged_df.loc[merged_df.Datatype == 'train', selected_features].values\n", + " # And training class labels (0 or 1)\n", + " train_y = merged_df.loc[merged_df.Datatype == 'train', 'Class'].values\n", + " \n", + " # get the test features and labels\n", + " test_x = merged_df.loc[merged_df.Datatype == 'test', selected_features].values\n", + " test_y = merged_df.loc[merged_df.Datatype == 'test', 'Class'].values\n", + " \n", + " return (train_x, train_y), (test_x, test_y)\n", + " " + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Test cells\n", + "\n", + "Below, test out your implementation and create the final train/test data." + ] + }, + { + "cell_type": "code", + "execution_count": 24, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Tests Passed!\n" + ] + } + ], + "source": [ + "\"\"\"\n", + "DON'T MODIFY ANYTHING IN THIS CELL THAT IS BELOW THIS LINE\n", + "\"\"\"\n", + "test_selection = list(features_df)[:2] # first couple columns as a test\n", + "# test that the correct train/test data is created\n", + "(train_x, train_y), (test_x, test_y) = train_test_data(complete_df, features_df, test_selection)\n", + "\n", + "# params: generated train/test data\n", + "tests.test_data_split(train_x, train_y, test_x, test_y)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## EXERCISE: Select \"good\" features\n", + "\n", + "If you passed the test above, you can create your own train/test data, below. \n", + "\n", + "Define a list of features you'd like to include in your final mode, `selected_features`; this is a list of the features names you want to include." + ] + }, + { + "cell_type": "code", + "execution_count": 25, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Training size: 70\n", + "Test size: 25\n", + "\n", + "Training df sample: \n", + " [[0.39814815 0. 0.19178082]\n", + " [0.86936937 0.44954128 0.84649123]\n", + " [0.59358289 0.08196721 0.31606218]\n", + " [0.54450262 0. 0.24257426]\n", + " [0.32950192 0. 0.16117216]\n", + " [0.59030837 0. 0.30165289]\n", + " [0.75977654 0.24571429 0.48430493]\n", + " [0.51612903 0. 0.27083333]\n", + " [0.44086022 0. 0.22395833]\n", + " [0.97945205 0.78873239 0.9 ]]\n" + ] + } + ], + "source": [ + "# Select your list of features, this should be column names from features_df\n", + "# ex. ['c_1', 'lcs_word']\n", + "selected_features = ['c_1', 'c_5', 'lcs_word']\n", + "\n", + "\n", + "\"\"\"\n", + "DON'T MODIFY ANYTHING IN THIS CELL THAT IS BELOW THIS LINE\n", + "\"\"\"\n", + "\n", + "(train_x, train_y), (test_x, test_y) = train_test_data(complete_df, features_df, selected_features)\n", + "\n", + "# check that division of samples seems correct\n", + "# these should add up to 95 (100 - 5 original files)\n", + "print('Training size: ', len(train_x))\n", + "print('Test size: ', len(test_x))\n", + "print()\n", + "print('Training df sample: \\n', train_x[:10])" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Question 2: How did you decide on which features to include in your final model? " + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "**Answer:** We decide features based on uniqueness and their correlation to other features. \n", + "For example, in this model, c_1 and lcs_word represent single words common and longest common subsequence respectively, which are totally unique and hence must be selected.\n", + "Now we see c_2, c_3, c_4, c_5 are highly correlated to one another. So selecting one of them should do the work. We finally settle on selecting c_5 as it is least correlated to c_1 and lcs_word, and hence more unique than other features.\n" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "---\n", + "## Creating Final Data Files\n", + "\n", + "Now, you are almost ready to move on to training a model in SageMaker!\n", + "\n", + "You'll want to access your train and test data in SageMaker and upload it to S3. In this project, SageMaker will expect the following format for your train/test data:\n", + "* Training and test data should be saved in one `.csv` file each, ex `train.csv` and `test.csv`\n", + "* These files should have class labels in the first column and features in the rest of the columns\n", + "\n", + "This format follows the practice, outlined in the [SageMaker documentation](https://docs.aws.amazon.com/sagemaker/latest/dg/cdf-training.html), which reads: \"Amazon SageMaker requires that a CSV file doesn't have a header record and that the target variable [class label] is in the first column.\"\n", + "\n", + "## EXERCISE: Create csv files\n", + "\n", + "Define a function that takes in x (features) and y (labels) and saves them to one `.csv` file at the path `data_dir/filename`.\n", + "\n", + "It may be useful to use pandas to merge your features and labels into one DataFrame and then convert that into a csv file. You can make sure to get rid of any incomplete rows, in a DataFrame, by using `dropna`." + ] + }, + { + "cell_type": "code", + "execution_count": 26, + "metadata": {}, + "outputs": [], + "source": [ + "def make_csv(x, y, filename, data_dir):\n", + " '''Merges features and labels and converts them into one csv file with labels in the first column.\n", + " :param x: Data features\n", + " :param y: Data labels\n", + " :param file_name: Name of csv file, ex. 'train.csv'\n", + " :param data_dir: The directory where files will be saved\n", + " '''\n", + " # make data dir, if it does not exist\n", + " if not os.path.exists(data_dir):\n", + " os.makedirs(data_dir)\n", + " \n", + " # your code here\n", + " df = pd.concat([pd.DataFrame(y), pd.DataFrame(x)], axis=1).dropna()\n", + " df.to_csv(os.path.join(data_dir, filename), header=False, index=False)\n", + " \n", + " # nothing is returned, but a print statement indicates that the function has run\n", + " print('Path created: '+str(data_dir)+'/'+str(filename))" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Test cells\n", + "\n", + "Test that your code produces the correct format for a `.csv` file, given some text features and labels." + ] + }, + { + "cell_type": "code", + "execution_count": 27, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Path created: test_csv/to_delete.csv\n", + "Tests passed!\n" + ] + } + ], + "source": [ + "\"\"\"\n", + "DON'T MODIFY ANYTHING IN THIS CELL THAT IS BELOW THIS LINE\n", + "\"\"\"\n", + "fake_x = [ [0.39814815, 0.0001, 0.19178082], \n", + " [0.86936937, 0.44954128, 0.84649123], \n", + " [0.44086022, 0., 0.22395833] ]\n", + "\n", + "fake_y = [0, 1, 1]\n", + "\n", + "make_csv(fake_x, fake_y, filename='to_delete.csv', data_dir='test_csv')\n", + "\n", + "# read in and test dimensions\n", + "fake_df = pd.read_csv('test_csv/to_delete.csv', header=None)\n", + "\n", + "# check shape\n", + "assert fake_df.shape==(3, 4), \\\n", + " 'The file should have as many rows as data_points and as many columns as features+1 (for indices).'\n", + "# check that first column = labels\n", + "assert np.all(fake_df.iloc[:,0].values==fake_y), 'First column is not equal to the labels, fake_y.'\n", + "print('Tests passed!')" + ] + }, + { + "cell_type": "code", + "execution_count": 28, + "metadata": {}, + "outputs": [], + "source": [ + "# delete the test csv file, generated above\n", + "! rm -rf test_csv" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "If you've passed the tests above, run the following cell to create `train.csv` and `test.csv` files in a directory that you specify! This will save the data in a local directory. Remember the name of this directory because you will reference it again when uploading this data to S3." + ] + }, + { + "cell_type": "code", + "execution_count": 29, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Path created: plagiarism_data/train.csv\n", + "Path created: plagiarism_data/test.csv\n" + ] + } + ], + "source": [ + "# can change directory, if you want\n", + "data_dir = 'plagiarism_data'\n", + "\n", + "\"\"\"\n", + "DON'T MODIFY ANYTHING IN THIS CELL THAT IS BELOW THIS LINE\n", + "\"\"\"\n", + "\n", + "make_csv(train_x, train_y, filename='train.csv', data_dir=data_dir)\n", + "make_csv(test_x, test_y, filename='test.csv', data_dir=data_dir)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Up Next\n", + "\n", + "Now that you've done some feature engineering and created some training and test data, you are ready to train and deploy a plagiarism classification model. The next notebook will utilize SageMaker resources to train and test a model that you design." + ] + } + ], + "metadata": { + "kernelspec": { + "display_name": "conda_amazonei_mxnet_p36", + "language": "python", + "name": "conda_amazonei_mxnet_p36" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.6.5" + } + }, + "nbformat": 4, + "nbformat_minor": 2 +} diff --git a/.ipynb_checkpoints/3_Training_a_Model-checkpoint.ipynb b/.ipynb_checkpoints/3_Training_a_Model-checkpoint.ipynb new file mode 100644 index 0000000..769f066 --- /dev/null +++ b/.ipynb_checkpoints/3_Training_a_Model-checkpoint.ipynb @@ -0,0 +1,789 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "# Plagiarism Detection Model\n", + "\n", + "Now that you've created training and test data, you are ready to define and train a model. Your goal in this notebook, will be to train a binary classification model that learns to label an answer file as either plagiarized or not, based on the features you provide the model.\n", + "\n", + "This task will be broken down into a few discrete steps:\n", + "\n", + "* Upload your data to S3.\n", + "* Define a binary classification model and a training script.\n", + "* Train your model and deploy it.\n", + "* Evaluate your deployed classifier and answer some questions about your approach.\n", + "\n", + "To complete this notebook, you'll have to complete all given exercises and answer all the questions in this notebook.\n", + "> All your tasks will be clearly labeled **EXERCISE** and questions as **QUESTION**.\n", + "\n", + "It will be up to you to explore different classification models and decide on a model that gives you the best performance for this dataset.\n", + "\n", + "---" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Load Data to S3\n", + "\n", + "In the last notebook, you should have created two files: a `training.csv` and `test.csv` file with the features and class labels for the given corpus of plagiarized/non-plagiarized text data. \n", + "\n", + ">The below cells load in some AWS SageMaker libraries and creates a default bucket. After creating this bucket, you can upload your locally stored data to S3.\n", + "\n", + "Save your train and test `.csv` feature files, locally. To do this you can run the second notebook \"2_Plagiarism_Feature_Engineering\" in SageMaker or you can manually upload your files to this notebook using the upload icon in Jupyter Lab. Then you can upload local files to S3 by using `sagemaker_session.upload_data` and pointing directly to where the training data is saved." + ] + }, + { + "cell_type": "code", + "execution_count": 1, + "metadata": {}, + "outputs": [], + "source": [ + "import pandas as pd\n", + "import boto3\n", + "import sagemaker" + ] + }, + { + "cell_type": "code", + "execution_count": 2, + "metadata": {}, + "outputs": [], + "source": [ + "\"\"\"\n", + "DON'T MODIFY ANYTHING IN THIS CELL THAT IS BELOW THIS LINE\n", + "\"\"\"\n", + "# session and role\n", + "sagemaker_session = sagemaker.Session()\n", + "role = sagemaker.get_execution_role()\n", + "\n", + "# create an S3 bucket\n", + "bucket = sagemaker_session.default_bucket()" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## EXERCISE: Upload your training data to S3\n", + "\n", + "Specify the `data_dir` where you've saved your `train.csv` file. Decide on a descriptive `prefix` that defines where your data will be uploaded in the default S3 bucket. Finally, create a pointer to your training data by calling `sagemaker_session.upload_data` and passing in the required parameters. It may help to look at the [Session documentation](https://sagemaker.readthedocs.io/en/stable/session.html#sagemaker.session.Session.upload_data) or previous SageMaker code examples.\n", + "\n", + "You are expected to upload your entire directory. Later, the training script will only access the `train.csv` file." + ] + }, + { + "cell_type": "code", + "execution_count": 3, + "metadata": {}, + "outputs": [], + "source": [ + "# should be the name of directory you created to save your features data\n", + "data_dir = 'plagiarism_data'\n", + "\n", + "# set prefix, a descriptive name for a directory \n", + "prefix = 'sagemaker/plagiarism_detector'\n", + "\n", + "# upload all data to S3\n", + "input_data = sagemaker_session.upload_data(path=data_dir, bucket=bucket, key_prefix=prefix)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Test cell\n", + "\n", + "Test that your data has been successfully uploaded. The below cell prints out the items in your S3 bucket and will throw an error if it is empty. You should see the contents of your `data_dir` and perhaps some checkpoints. If you see any other files listed, then you may have some old model files that you can delete via the S3 console (though, additional files shouldn't affect the performance of model developed in this notebook)." + ] + }, + { + "cell_type": "code", + "execution_count": 4, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "sagemaker-scikit-learn-2020-01-07-09-01-41-326/debug-output/training_job_end.ts\n", + "sagemaker-scikit-learn-2020-01-07-09-01-41-326/output/model.tar.gz\n", + "sagemaker-scikit-learn-2020-01-07-09-01-41-326/source/sourcedir.tar.gz\n", + "sagemaker/plagiarism_detector/test.csv\n", + "sagemaker/plagiarism_detector/train.csv\n", + "Test passed!\n" + ] + } + ], + "source": [ + "\"\"\"\n", + "DON'T MODIFY ANYTHING IN THIS CELL THAT IS BELOW THIS LINE\n", + "\"\"\"\n", + "# confirm that data is in S3 bucket\n", + "empty_check = []\n", + "for obj in boto3.resource('s3').Bucket(bucket).objects.all():\n", + " empty_check.append(obj.key)\n", + " print(obj.key)\n", + "\n", + "assert len(empty_check) !=0, 'S3 bucket is empty.'\n", + "print('Test passed!')" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "---\n", + "\n", + "# Modeling\n", + "\n", + "Now that you've uploaded your training data, it's time to define and train a model!\n", + "\n", + "The type of model you create is up to you. For a binary classification task, you can choose to go one of three routes:\n", + "* Use a built-in classification algorithm, like LinearLearner.\n", + "* Define a custom Scikit-learn classifier, a comparison of models can be found [here](https://scikit-learn.org/stable/auto_examples/classification/plot_classifier_comparison.html).\n", + "* Define a custom PyTorch neural network classifier. \n", + "\n", + "It will be up to you to test out a variety of models and choose the best one. Your project will be graded on the accuracy of your final model. \n", + " \n", + "---\n", + "\n", + "## EXERCISE: Complete a training script \n", + "\n", + "To implement a custom classifier, you'll need to complete a `train.py` script. You've been given the folders `source_sklearn` and `source_pytorch` which hold starting code for a custom Scikit-learn model and a PyTorch model, respectively. Each directory has a `train.py` training script. To complete this project **you only need to complete one of these scripts**; the script that is responsible for training your final model.\n", + "\n", + "A typical training script:\n", + "* Loads training data from a specified directory\n", + "* Parses any training & model hyperparameters (ex. nodes in a neural network, training epochs, etc.)\n", + "* Instantiates a model of your design, with any specified hyperparams\n", + "* Trains that model \n", + "* Finally, saves the model so that it can be hosted/deployed, later\n", + "\n", + "### Defining and training a model\n", + "Much of the training script code is provided for you. Almost all of your work will be done in the `if __name__ == '__main__':` section. To complete a `train.py` file, you will:\n", + "1. Import any extra libraries you need\n", + "2. Define any additional model training hyperparameters using `parser.add_argument`\n", + "2. Define a model in the `if __name__ == '__main__':` section\n", + "3. Train the model in that same section\n", + "\n", + "Below, you can use `!pygmentize` to display an existing `train.py` file. Read through the code; all of your tasks are marked with `TODO` comments. \n", + "\n", + "**Note: If you choose to create a custom PyTorch model, you will be responsible for defining the model in the `model.py` file,** and a `predict.py` file is provided. If you choose to use Scikit-learn, you only need a `train.py` file; you may import a classifier from the `sklearn` library." + ] + }, + { + "cell_type": "code", + "execution_count": 5, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "\u001b[34mfrom\u001b[39;49;00m \u001b[04m\u001b[36m__future__\u001b[39;49;00m \u001b[34mimport\u001b[39;49;00m print_function\r\n", + "\r\n", + "\u001b[34mimport\u001b[39;49;00m \u001b[04m\u001b[36margparse\u001b[39;49;00m\r\n", + "\u001b[34mimport\u001b[39;49;00m \u001b[04m\u001b[36mos\u001b[39;49;00m\r\n", + "\u001b[34mimport\u001b[39;49;00m \u001b[04m\u001b[36mpandas\u001b[39;49;00m \u001b[34mas\u001b[39;49;00m \u001b[04m\u001b[36mpd\u001b[39;49;00m\r\n", + "\r\n", + "\u001b[34mfrom\u001b[39;49;00m \u001b[04m\u001b[36msklearn.externals\u001b[39;49;00m \u001b[34mimport\u001b[39;49;00m joblib\r\n", + "\r\n", + "\u001b[37m## TODO: Import any additional libraries you need to define a model\u001b[39;49;00m\r\n", + "\u001b[34mfrom\u001b[39;49;00m \u001b[04m\u001b[36msklearn.linear_model\u001b[39;49;00m \u001b[34mimport\u001b[39;49;00m LogisticRegression\r\n", + "\r\n", + "\u001b[37m# Provided model load function\u001b[39;49;00m\r\n", + "\u001b[34mdef\u001b[39;49;00m \u001b[32mmodel_fn\u001b[39;49;00m(model_dir):\r\n", + " \u001b[33m\"\"\"Load model from the model_dir. This is the same model that is saved\u001b[39;49;00m\r\n", + "\u001b[33m in the main if statement.\u001b[39;49;00m\r\n", + "\u001b[33m \"\"\"\u001b[39;49;00m\r\n", + " \u001b[34mprint\u001b[39;49;00m(\u001b[33m\"\u001b[39;49;00m\u001b[33mLoading model.\u001b[39;49;00m\u001b[33m\"\u001b[39;49;00m)\r\n", + " \r\n", + " \u001b[37m# load using joblib\u001b[39;49;00m\r\n", + " model = joblib.load(os.path.join(model_dir, \u001b[33m\"\u001b[39;49;00m\u001b[33mmodel.joblib\u001b[39;49;00m\u001b[33m\"\u001b[39;49;00m))\r\n", + " \u001b[34mprint\u001b[39;49;00m(\u001b[33m\"\u001b[39;49;00m\u001b[33mDone loading model.\u001b[39;49;00m\u001b[33m\"\u001b[39;49;00m)\r\n", + " \r\n", + " \u001b[34mreturn\u001b[39;49;00m model\r\n", + "\r\n", + "\r\n", + "\u001b[37m## TODO: Complete the main code\u001b[39;49;00m\r\n", + "\u001b[34mif\u001b[39;49;00m \u001b[31m__name__\u001b[39;49;00m == \u001b[33m'\u001b[39;49;00m\u001b[33m__main__\u001b[39;49;00m\u001b[33m'\u001b[39;49;00m:\r\n", + " \r\n", + " \u001b[37m# All of the model parameters and training parameters are sent as arguments\u001b[39;49;00m\r\n", + " \u001b[37m# when this script is executed, during a training job\u001b[39;49;00m\r\n", + " \r\n", + " \u001b[37m# Here we set up an argument parser to easily access the parameters\u001b[39;49;00m\r\n", + " parser = argparse.ArgumentParser()\r\n", + "\r\n", + " \u001b[37m# SageMaker parameters, like the directories for training data and saving models; set automatically\u001b[39;49;00m\r\n", + " \u001b[37m# Do not need to change\u001b[39;49;00m\r\n", + " parser.add_argument(\u001b[33m'\u001b[39;49;00m\u001b[33m--output-data-dir\u001b[39;49;00m\u001b[33m'\u001b[39;49;00m, \u001b[36mtype\u001b[39;49;00m=\u001b[36mstr\u001b[39;49;00m, default=os.environ[\u001b[33m'\u001b[39;49;00m\u001b[33mSM_OUTPUT_DATA_DIR\u001b[39;49;00m\u001b[33m'\u001b[39;49;00m])\r\n", + " parser.add_argument(\u001b[33m'\u001b[39;49;00m\u001b[33m--model-dir\u001b[39;49;00m\u001b[33m'\u001b[39;49;00m, \u001b[36mtype\u001b[39;49;00m=\u001b[36mstr\u001b[39;49;00m, default=os.environ[\u001b[33m'\u001b[39;49;00m\u001b[33mSM_MODEL_DIR\u001b[39;49;00m\u001b[33m'\u001b[39;49;00m])\r\n", + " parser.add_argument(\u001b[33m'\u001b[39;49;00m\u001b[33m--data-dir\u001b[39;49;00m\u001b[33m'\u001b[39;49;00m, \u001b[36mtype\u001b[39;49;00m=\u001b[36mstr\u001b[39;49;00m, default=os.environ[\u001b[33m'\u001b[39;49;00m\u001b[33mSM_CHANNEL_TRAIN\u001b[39;49;00m\u001b[33m'\u001b[39;49;00m])\r\n", + " \r\n", + " \u001b[37m## TODO: Add any additional arguments that you will need to pass into your model\u001b[39;49;00m\r\n", + " \r\n", + " \u001b[37m# args holds all passed-in arguments\u001b[39;49;00m\r\n", + " args = parser.parse_args()\r\n", + "\r\n", + " \u001b[37m# Read in csv training file\u001b[39;49;00m\r\n", + " training_dir = args.data_dir\r\n", + " train_data = pd.read_csv(os.path.join(training_dir, \u001b[33m\"\u001b[39;49;00m\u001b[33mtrain.csv\u001b[39;49;00m\u001b[33m\"\u001b[39;49;00m), header=\u001b[36mNone\u001b[39;49;00m, names=\u001b[36mNone\u001b[39;49;00m)\r\n", + "\r\n", + " \u001b[37m# Labels are in the first column\u001b[39;49;00m\r\n", + " train_y = train_data.iloc[:,\u001b[34m0\u001b[39;49;00m]\r\n", + " train_x = train_data.iloc[:,\u001b[34m1\u001b[39;49;00m:]\r\n", + " \r\n", + " \r\n", + " \u001b[37m## --- Your code here --- ##\u001b[39;49;00m\r\n", + " \r\n", + "\r\n", + " \u001b[37m## TODO: Define a model \u001b[39;49;00m\r\n", + " model = LogisticRegression()\r\n", + " \r\n", + " \r\n", + " \u001b[37m## TODO: Train the model\u001b[39;49;00m\r\n", + " model.fit(train_x, train_y)\r\n", + " \r\n", + " \r\n", + " \u001b[37m## --- End of your code --- ##\u001b[39;49;00m\r\n", + " \r\n", + "\r\n", + " \u001b[37m# Save the trained model\u001b[39;49;00m\r\n", + " joblib.dump(model, os.path.join(args.model_dir, \u001b[33m\"\u001b[39;49;00m\u001b[33mmodel.joblib\u001b[39;49;00m\u001b[33m\"\u001b[39;49;00m))\r\n" + ] + } + ], + "source": [ + "# directory can be changed to: source_sklearn or source_pytorch\n", + "!pygmentize source_sklearn/train.py" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Provided code\n", + "\n", + "If you read the code above, you can see that the starter code includes a few things:\n", + "* Model loading (`model_fn`) and saving code\n", + "* Getting SageMaker's default hyperparameters\n", + "* Loading the training data by name, `train.csv` and extracting the features and labels, `train_x`, and `train_y`\n", + "\n", + "If you'd like to read more about model saving with [joblib for sklearn](https://scikit-learn.org/stable/modules/model_persistence.html) or with [torch.save](https://pytorch.org/tutorials/beginner/saving_loading_models.html), click on the provided links." + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "---\n", + "# Create an Estimator\n", + "\n", + "When a custom model is constructed in SageMaker, an entry point must be specified. This is the Python file which will be executed when the model is trained; the `train.py` function you specified above. To run a custom training script in SageMaker, construct an estimator, and fill in the appropriate constructor arguments:\n", + "\n", + "* **entry_point**: The path to the Python script SageMaker runs for training and prediction.\n", + "* **source_dir**: The path to the training script directory `source_sklearn` OR `source_pytorch`.\n", + "* **entry_point**: The path to the Python script SageMaker runs for training and prediction.\n", + "* **source_dir**: The path to the training script directory `train_sklearn` OR `train_pytorch`.\n", + "* **entry_point**: The path to the Python script SageMaker runs for training.\n", + "* **source_dir**: The path to the training script directory `train_sklearn` OR `train_pytorch`.\n", + "* **role**: Role ARN, which was specified, above.\n", + "* **train_instance_count**: The number of training instances (should be left at 1).\n", + "* **train_instance_type**: The type of SageMaker instance for training. Note: Because Scikit-learn does not natively support GPU training, Sagemaker Scikit-learn does not currently support training on GPU instance types.\n", + "* **sagemaker_session**: The session used to train on Sagemaker.\n", + "* **hyperparameters** (optional): A dictionary `{'name':value, ..}` passed to the train function as hyperparameters.\n", + "\n", + "Note: For a PyTorch model, there is another optional argument **framework_version**, which you can set to the latest version of PyTorch, `1.0`.\n", + "\n", + "## EXERCISE: Define a Scikit-learn or PyTorch estimator\n", + "\n", + "To import your desired estimator, use one of the following lines:\n", + "```\n", + "from sagemaker.sklearn.estimator import SKLearn\n", + "```\n", + "```\n", + "from sagemaker.pytorch import PyTorch\n", + "```" + ] + }, + { + "cell_type": "code", + "execution_count": 6, + "metadata": {}, + "outputs": [], + "source": [ + "# your import and estimator code, here\n", + "from sagemaker.sklearn.estimator import SKLearn" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## EXERCISE: Train the estimator\n", + "\n", + "Train your estimator on the training data stored in S3. This should create a training job that you can monitor in your SageMaker console." + ] + }, + { + "cell_type": "code", + "execution_count": 7, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "CPU times: user 100 µs, sys: 8 µs, total: 108 µs\n", + "Wall time: 113 µs\n" + ] + } + ], + "source": [ + "%%time\n", + "\n", + "# Train your estimator on S3 training data\n", + "estimator = SKLearn(role=role,\n", + " sagemaker_session=sagemaker_session,\n", + " train_instance_count=1,\n", + " train_instance_type='ml.m4.xlarge',\n", + " entry_point='train.py',\n", + " source_dir='source_sklearn'\n", + " )\n" + ] + }, + { + "cell_type": "code", + "execution_count": 8, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "2020-01-07 09:09:49 Starting - Starting the training job...\n", + "2020-01-07 09:09:52 Starting - Launching requested ML instances......\n", + "2020-01-07 09:11:00 Starting - Preparing the instances for training......\n", + "2020-01-07 09:11:59 Downloading - Downloading input data...\n", + "2020-01-07 09:12:45 Training - Training image download completed. Training in progress..\u001b[34m2020-01-07 09:12:46,565 sagemaker-containers INFO Imported framework sagemaker_sklearn_container.training\u001b[0m\n", + "\u001b[34m2020-01-07 09:12:46,567 sagemaker-containers INFO No GPUs detected (normal if no gpus installed)\u001b[0m\n", + "\u001b[34m2020-01-07 09:12:46,579 sagemaker_sklearn_container.training INFO Invoking user training script.\u001b[0m\n", + "\u001b[34m2020-01-07 09:12:46,879 sagemaker-containers INFO Module train does not provide a setup.py. \u001b[0m\n", + "\u001b[34mGenerating setup.py\u001b[0m\n", + "\u001b[34m2020-01-07 09:12:46,879 sagemaker-containers INFO Generating setup.cfg\u001b[0m\n", + "\u001b[34m2020-01-07 09:12:46,879 sagemaker-containers INFO Generating MANIFEST.in\u001b[0m\n", + "\u001b[34m2020-01-07 09:12:46,879 sagemaker-containers INFO Installing module with the following command:\u001b[0m\n", + "\u001b[34m/miniconda3/bin/python -m pip install . \u001b[0m\n", + "\u001b[34mProcessing /opt/ml/code\u001b[0m\n", + "\u001b[34mBuilding wheels for collected packages: train\n", + " Building wheel for train (setup.py): started\n", + " Building wheel for train (setup.py): finished with status 'done'\n", + " Created wheel for train: filename=train-1.0.0-py2.py3-none-any.whl size=5830 sha256=89f0f979d7c997c9fa98f05ad318aa496237540267e8f3bbaa7891a289b94c0c\n", + " Stored in directory: /tmp/pip-ephem-wheel-cache-tkej929b/wheels/35/24/16/37574d11bf9bde50616c67372a334f94fa8356bc7164af8ca3\u001b[0m\n", + "\u001b[34mSuccessfully built train\u001b[0m\n", + "\u001b[34mInstalling collected packages: train\u001b[0m\n", + "\u001b[34mSuccessfully installed train-1.0.0\u001b[0m\n", + "\u001b[34m2020-01-07 09:12:48,410 sagemaker-containers INFO No GPUs detected (normal if no gpus installed)\u001b[0m\n", + "\u001b[34m2020-01-07 09:12:48,423 sagemaker-containers INFO Invoking user script\n", + "\u001b[0m\n", + "\u001b[34mTraining Env:\n", + "\u001b[0m\n", + "\u001b[34m{\n", + " \"additional_framework_parameters\": {},\n", + " \"channel_input_dirs\": {\n", + " \"train\": \"/opt/ml/input/data/train\"\n", + " },\n", + " \"current_host\": \"algo-1\",\n", + " \"framework_module\": \"sagemaker_sklearn_container.training:main\",\n", + " \"hosts\": [\n", + " \"algo-1\"\n", + " ],\n", + " \"hyperparameters\": {},\n", + " \"input_config_dir\": \"/opt/ml/input/config\",\n", + " \"input_data_config\": {\n", + " \"train\": {\n", + " \"TrainingInputMode\": \"File\",\n", + " \"S3DistributionType\": \"FullyReplicated\",\n", + " \"RecordWrapperType\": \"None\"\n", + " }\n", + " },\n", + " \"input_dir\": \"/opt/ml/input\",\n", + " \"is_master\": true,\n", + " \"job_name\": \"sagemaker-scikit-learn-2020-01-07-09-09-49-538\",\n", + " \"log_level\": 20,\n", + " \"master_hostname\": \"algo-1\",\n", + " \"model_dir\": \"/opt/ml/model\",\n", + " \"module_dir\": \"s3://sagemaker-us-east-1-309164732448/sagemaker-scikit-learn-2020-01-07-09-09-49-538/source/sourcedir.tar.gz\",\n", + " \"module_name\": \"train\",\n", + " \"network_interface_name\": \"eth0\",\n", + " \"num_cpus\": 4,\n", + " \"num_gpus\": 0,\n", + " \"output_data_dir\": \"/opt/ml/output/data\",\n", + " \"output_dir\": \"/opt/ml/output\",\n", + " \"output_intermediate_dir\": \"/opt/ml/output/intermediate\",\n", + " \"resource_config\": {\n", + " \"current_host\": \"algo-1\",\n", + " \"hosts\": [\n", + " \"algo-1\"\n", + " ],\n", + " \"network_interface_name\": \"eth0\"\n", + " },\n", + " \"user_entry_point\": \"train.py\"\u001b[0m\n", + "\u001b[34m}\n", + "\u001b[0m\n", + "\u001b[34mEnvironment variables:\n", + "\u001b[0m\n", + "\u001b[34mSM_HOSTS=[\"algo-1\"]\u001b[0m\n", + "\u001b[34mSM_NETWORK_INTERFACE_NAME=eth0\u001b[0m\n", + "\u001b[34mSM_HPS={}\u001b[0m\n", + "\u001b[34mSM_USER_ENTRY_POINT=train.py\u001b[0m\n", + "\u001b[34mSM_FRAMEWORK_PARAMS={}\u001b[0m\n", + "\u001b[34mSM_RESOURCE_CONFIG={\"current_host\":\"algo-1\",\"hosts\":[\"algo-1\"],\"network_interface_name\":\"eth0\"}\u001b[0m\n", + "\u001b[34mSM_INPUT_DATA_CONFIG={\"train\":{\"RecordWrapperType\":\"None\",\"S3DistributionType\":\"FullyReplicated\",\"TrainingInputMode\":\"File\"}}\u001b[0m\n", + "\u001b[34mSM_OUTPUT_DATA_DIR=/opt/ml/output/data\u001b[0m\n", + "\u001b[34mSM_CHANNELS=[\"train\"]\u001b[0m\n", + "\u001b[34mSM_CURRENT_HOST=algo-1\u001b[0m\n", + "\u001b[34mSM_MODULE_NAME=train\u001b[0m\n", + "\u001b[34mSM_LOG_LEVEL=20\u001b[0m\n", + "\u001b[34mSM_FRAMEWORK_MODULE=sagemaker_sklearn_container.training:main\u001b[0m\n", + "\u001b[34mSM_INPUT_DIR=/opt/ml/input\u001b[0m\n", + "\u001b[34mSM_INPUT_CONFIG_DIR=/opt/ml/input/config\u001b[0m\n", + "\u001b[34mSM_OUTPUT_DIR=/opt/ml/output\u001b[0m\n", + "\u001b[34mSM_NUM_CPUS=4\u001b[0m\n", + "\u001b[34mSM_NUM_GPUS=0\u001b[0m\n", + "\u001b[34mSM_MODEL_DIR=/opt/ml/model\u001b[0m\n", + "\u001b[34mSM_MODULE_DIR=s3://sagemaker-us-east-1-309164732448/sagemaker-scikit-learn-2020-01-07-09-09-49-538/source/sourcedir.tar.gz\u001b[0m\n", + "\u001b[34mSM_TRAINING_ENV={\"additional_framework_parameters\":{},\"channel_input_dirs\":{\"train\":\"/opt/ml/input/data/train\"},\"current_host\":\"algo-1\",\"framework_module\":\"sagemaker_sklearn_container.training:main\",\"hosts\":[\"algo-1\"],\"hyperparameters\":{},\"input_config_dir\":\"/opt/ml/input/config\",\"input_data_config\":{\"train\":{\"RecordWrapperType\":\"None\",\"S3DistributionType\":\"FullyReplicated\",\"TrainingInputMode\":\"File\"}},\"input_dir\":\"/opt/ml/input\",\"is_master\":true,\"job_name\":\"sagemaker-scikit-learn-2020-01-07-09-09-49-538\",\"log_level\":20,\"master_hostname\":\"algo-1\",\"model_dir\":\"/opt/ml/model\",\"module_dir\":\"s3://sagemaker-us-east-1-309164732448/sagemaker-scikit-learn-2020-01-07-09-09-49-538/source/sourcedir.tar.gz\",\"module_name\":\"train\",\"network_interface_name\":\"eth0\",\"num_cpus\":4,\"num_gpus\":0,\"output_data_dir\":\"/opt/ml/output/data\",\"output_dir\":\"/opt/ml/output\",\"output_intermediate_dir\":\"/opt/ml/output/intermediate\",\"resource_config\":{\"current_host\":\"algo-1\",\"hosts\":[\"algo-1\"],\"network_interface_name\":\"eth0\"},\"user_entry_point\":\"train.py\"}\u001b[0m\n", + "\u001b[34mSM_USER_ARGS=[]\u001b[0m\n", + "\u001b[34mSM_OUTPUT_INTERMEDIATE_DIR=/opt/ml/output/intermediate\u001b[0m\n", + "\u001b[34mSM_CHANNEL_TRAIN=/opt/ml/input/data/train\u001b[0m\n", + "\u001b[34mPYTHONPATH=/miniconda3/bin:/miniconda3/lib/python37.zip:/miniconda3/lib/python3.7:/miniconda3/lib/python3.7/lib-dynload:/miniconda3/lib/python3.7/site-packages\n", + "\u001b[0m\n", + "\u001b[34mInvoking script with the following command:\n", + "\u001b[0m\n", + "\u001b[34m/miniconda3/bin/python -m train\n", + "\n", + "\u001b[0m\n", + "\u001b[34m/miniconda3/lib/python3.7/site-packages/sklearn/externals/joblib/externals/cloudpickle/cloudpickle.py:47: DeprecationWarning: the imp module is deprecated in favour of importlib; see the module's documentation for alternative uses\n", + " import imp\u001b[0m\n", + "\u001b[34m/miniconda3/lib/python3.7/site-packages/sklearn/linear_model/logistic.py:432: FutureWarning: Default solver will be changed to 'lbfgs' in 0.22. Specify a solver to silence this warning.\n", + " FutureWarning)\u001b[0m\n", + "\u001b[34m2020-01-07 09:12:49,705 sagemaker-containers INFO Reporting training SUCCESS\u001b[0m\n", + "\n", + "2020-01-07 09:13:10 Uploading - Uploading generated training model\n", + "2020-01-07 09:13:10 Completed - Training job completed\n", + "Training seconds: 71\n", + "Billable seconds: 71\n" + ] + } + ], + "source": [ + "estimator.fit({'train': input_data})" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## EXERCISE: Deploy the trained model\n", + "\n", + "After training, deploy your model to create a `predictor`. If you're using a PyTorch model, you'll need to create a trained `PyTorchModel` that accepts the trained `.model_data` as an input parameter and points to the provided `source_pytorch/predict.py` file as an entry point. \n", + "\n", + "To deploy a trained model, you'll use `.deploy`, which takes in two arguments:\n", + "* **initial_instance_count**: The number of deployed instances (1).\n", + "* **instance_type**: The type of SageMaker instance for deployment.\n", + "\n", + "Note: If you run into an instance error, it may be because you chose the wrong training or deployment instance_type. It may help to refer to your previous exercise code to see which types of instances we used." + ] + }, + { + "cell_type": "code", + "execution_count": 9, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "---------------------------------------------------------------------------------------------------!CPU times: user 520 ms, sys: 19.4 ms, total: 540 ms\n", + "Wall time: 8min 20s\n" + ] + } + ], + "source": [ + "%%time\n", + "\n", + "# uncomment, if needed\n", + "# from sagemaker.pytorch import PyTorchModel\n", + "\n", + "\n", + "# deploy your model to create a predictor\n", + "predictor = estimator.deploy(initial_instance_count=1, instance_type='ml.m4.xlarge')" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "---\n", + "# Evaluating Your Model\n", + "\n", + "Once your model is deployed, you can see how it performs when applied to our test data.\n", + "\n", + "The provided cell below, reads in the test data, assuming it is stored locally in `data_dir` and named `test.csv`. The labels and features are extracted from the `.csv` file." + ] + }, + { + "cell_type": "code", + "execution_count": 10, + "metadata": {}, + "outputs": [], + "source": [ + "\"\"\"\n", + "DON'T MODIFY ANYTHING IN THIS CELL THAT IS BELOW THIS LINE\n", + "\"\"\"\n", + "import os\n", + "\n", + "# read in test data, assuming it is stored locally\n", + "test_data = pd.read_csv(os.path.join(data_dir, \"test.csv\"), header=None, names=None)\n", + "\n", + "# labels are in the first column\n", + "test_y = test_data.iloc[:,0]\n", + "test_x = test_data.iloc[:,1:]" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## EXERCISE: Determine the accuracy of your model\n", + "\n", + "Use your deployed `predictor` to generate predicted, class labels for the test data. Compare those to the *true* labels, `test_y`, and calculate the accuracy as a value between 0 and 1.0 that indicates the fraction of test data that your model classified correctly. You may use [sklearn.metrics](https://scikit-learn.org/stable/modules/classes.html#module-sklearn.metrics) for this calculation.\n", + "\n", + "**To pass this project, your model should get at least 90% test accuracy.**" + ] + }, + { + "cell_type": "code", + "execution_count": 11, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Test passed!\n" + ] + } + ], + "source": [ + "# First: generate predicted, class labels\n", + "test_y_preds = predictor.predict(test_x)\n", + "\n", + "\n", + "\"\"\"\n", + "DON'T MODIFY ANYTHING IN THIS CELL THAT IS BELOW THIS LINE\n", + "\"\"\"\n", + "# test that your model generates the correct number of labels\n", + "assert len(test_y_preds)==len(test_y), 'Unexpected number of predictions.'\n", + "print('Test passed!')" + ] + }, + { + "cell_type": "code", + "execution_count": 12, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Recall: 1.0666666666666667\n", + "Precision: 1.0\n", + "Accuracy: 0.96\n", + "\n", + "Predicted class labels: \n", + "[1 1 1 1 1 1 0 0 0 0 0 0 1 1 1 1 1 1 0 1 1 1 1 0 0]\n", + "\n", + "True class labels: \n", + "[1 1 1 1 1 1 0 0 0 0 0 0 1 1 1 1 1 1 0 1 0 1 1 0 0]\n" + ] + } + ], + "source": [ + "# Second: calculate the test accuracy\n", + "accuracy_array = test_y_preds == test_y\n", + "count = 0\n", + "for element in accuracy_array:\n", + " if element == True:\n", + " count = count + 1\n", + "\n", + "false_positives = test_y_preds - accuracy_array\n", + "false_positive_count = false_positives.where(false_positives > 0, 0 )\n", + "accuracy = count/ len(accuracy_array)\n", + " \n", + "recall = test_y_preds.sum() / test_y.sum()\n", + "print('Recall: ', recall)\n", + "precision = test_y_preds.sum() / (test_y_preds.sum() + 0)\n", + "print('Precision: ', precision)\n", + "\n", + "print('Accuracy:', accuracy)\n", + "\n", + "\n", + "## print out the array of predicted and true labels, if you want\n", + "print('\\nPredicted class labels: ')\n", + "print(test_y_preds)\n", + "print('\\nTrue class labels: ')\n", + "print(test_y.values)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Question 1: How many false positives and false negatives did your model produce, if any? And why do you think this is?" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "** Answer**: My model didn't produce any false positives, and only 1 false negative. With an astounding accuracy of 94%, I think it happened because the dataset is small and hence we don't have many outliers.\n" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Question 2: How did you decide on the type of model to use? " + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "** Answer**: The problem being a binary classification problem, with multiple numerical features, Linear Logistic Regression is one of the best models. It accepts n features and returns classification." + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "----\n", + "## EXERCISE: Clean up Resources\n", + "\n", + "After you're done evaluating your model, **delete your model endpoint**. You can do this with a call to `.delete_endpoint()`. You need to show, in this notebook, that the endpoint was deleted. Any other resources, you may delete from the AWS console, and you will find more instructions on cleaning up all your resources, below." + ] + }, + { + "cell_type": "code", + "execution_count": 13, + "metadata": {}, + "outputs": [], + "source": [ + "# uncomment and fill in the line below!\n", + "predictor.delete_endpoint()\n" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Deleting S3 bucket\n", + "\n", + "When you are *completely* done with training and testing models, you can also delete your entire S3 bucket. If you do this before you are done training your model, you'll have to recreate your S3 bucket and upload your training data again." + ] + }, + { + "cell_type": "code", + "execution_count": 14, + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "[{'ResponseMetadata': {'RequestId': 'FF46362CF1FCE014',\n", + " 'HostId': 'CpqWvNa0rhrelGsFOAYQFukvUQ8xvAhiB9EB24l472MTvPhr1ykHsAX/Gr8a+lSIjlL1Jfoz9fg=',\n", + " 'HTTPStatusCode': 200,\n", + " 'HTTPHeaders': {'x-amz-id-2': 'CpqWvNa0rhrelGsFOAYQFukvUQ8xvAhiB9EB24l472MTvPhr1ykHsAX/Gr8a+lSIjlL1Jfoz9fg=',\n", + " 'x-amz-request-id': 'FF46362CF1FCE014',\n", + " 'date': 'Tue, 07 Jan 2020 09:21:53 GMT',\n", + " 'connection': 'close',\n", + " 'content-type': 'application/xml',\n", + " 'transfer-encoding': 'chunked',\n", + " 'server': 'AmazonS3'},\n", + " 'RetryAttempts': 0},\n", + " 'Deleted': [{'Key': 'sagemaker-scikit-learn-2020-01-07-09-01-41-326/output/model.tar.gz'},\n", + " {'Key': 'sagemaker/plagiarism_detector/test.csv'},\n", + " {'Key': 'sagemaker/plagiarism_detector/train.csv'},\n", + " {'Key': 'sagemaker-scikit-learn-2020-01-07-09-09-49-538/debug-output/training_job_end.ts'},\n", + " {'Key': 'sagemaker-scikit-learn-2020-01-07-09-09-49-538/output/model.tar.gz'},\n", + " {'Key': 'sagemaker-scikit-learn-2020-01-07-09-09-49-538/source/sourcedir.tar.gz'},\n", + " {'Key': 'sagemaker-scikit-learn-2020-01-07-09-01-41-326/source/sourcedir.tar.gz'},\n", + " {'Key': 'sagemaker-scikit-learn-2020-01-07-09-01-41-326/debug-output/training_job_end.ts'}]}]" + ] + }, + "execution_count": 14, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "#deleting bucket, uncomment lines below\n", + "\n", + "bucket_to_delete = boto3.resource('s3').Bucket(bucket)\n", + "bucket_to_delete.objects.all().delete()" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Deleting all your models and instances\n", + "\n", + "When you are _completely_ done with this project and do **not** ever want to revisit this notebook, you can choose to delete all of your SageMaker notebook instances and models by following [these instructions](https://docs.aws.amazon.com/sagemaker/latest/dg/ex1-cleanup.html). Before you delete this notebook instance, I recommend at least downloading a copy and saving it, locally." + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "---\n", + "## Further Directions\n", + "\n", + "There are many ways to improve or add on to this project to expand your learning or make this more of a unique project for you. A few ideas are listed below:\n", + "* Train a classifier to predict the *category* (1-3) of plagiarism and not just plagiarized (1) or not (0).\n", + "* Utilize a different and larger dataset to see if this model can be extended to other types of plagiarism.\n", + "* Use language or character-level analysis to find different (and more) similarity features.\n", + "* Write a complete pipeline function that accepts a source text and submitted text file, and classifies the submitted text as plagiarized or not.\n", + "* Use API Gateway and a lambda function to deploy your model to a web application.\n", + "\n", + "These are all just options for extending your work. If you've completed all the exercises in this notebook, you've completed a real-world application, and can proceed to submit your project. Great job!" + ] + } + ], + "metadata": { + "kernelspec": { + "display_name": "conda_pytorch_p36", + "language": "python", + "name": "conda_pytorch_p36" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.6.5" + } + }, + "nbformat": 4, + "nbformat_minor": 2 +} diff --git a/1_Data_Exploration.ipynb b/1_Data_Exploration.ipynb new file mode 100644 index 0000000..154d7e1 --- /dev/null +++ b/1_Data_Exploration.ipynb @@ -0,0 +1,746 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "# Plagiarism Text Data\n", + "\n", + "In this project, you will be tasked with building a plagiarism detector that examines a text file and performs binary classification; labeling that file as either plagiarized or not, depending on how similar the text file is when compared to a provided source text. \n", + "\n", + "The first step in working with any dataset is loading the data in and noting what information is included in the dataset. This is an important step in eventually working with this data, and knowing what kinds of features you have to work with as you transform and group the data!\n", + "\n", + "So, this notebook is all about exploring the data and noting patterns about the features you are given and the distribution of data. \n", + "\n", + "> There are not any exercises or questions in this notebook, it is only meant for exploration. This notebook will note be required in your final project submission.\n", + "\n", + "---" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Read in the Data\n", + "\n", + "The cell below will download the necessary data and extract the files into the folder `data/`.\n", + "\n", + "This data is a slightly modified version of a dataset created by Paul Clough (Information Studies) and Mark Stevenson (Computer Science), at the University of Sheffield. You can read all about the data collection and corpus, at [their university webpage](https://ir.shef.ac.uk/cloughie/resources/plagiarism_corpus.html). \n", + "\n", + "> **Citation for data**: Clough, P. and Stevenson, M. Developing A Corpus of Plagiarised Short Answers, Language Resources and Evaluation: Special Issue on Plagiarism and Authorship Analysis, In Press. [Download]" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "collapsed": true + }, + "outputs": [], + "source": [ + "!wget https://s3.amazonaws.com/video.udacity-data.com/topher/2019/January/5c4147f9_data/data.zip\n", + "!unzip data" + ] + }, + { + "cell_type": "code", + "execution_count": 1, + "metadata": {}, + "outputs": [], + "source": [ + "# import libraries\n", + "import pandas as pd\n", + "import numpy as np\n", + "import os" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "This plagiarism dataset is made of multiple text files; each of these files has characteristics that are is summarized in a `.csv` file named `file_information.csv`, which we can read in using `pandas`." + ] + }, + { + "cell_type": "code", + "execution_count": 2, + "metadata": {}, + "outputs": [ + { + "data": { + "text/html": [ + "
\n", + "\n", + "\n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + "
FileTaskCategory
0g0pA_taska.txtanon
1g0pA_taskb.txtbcut
2g0pA_taskc.txtclight
3g0pA_taskd.txtdheavy
4g0pA_taske.txtenon
5g0pB_taska.txtanon
6g0pB_taskb.txtbnon
7g0pB_taskc.txtccut
8g0pB_taskd.txtdlight
9g0pB_taske.txteheavy
\n", + "
" + ], + "text/plain": [ + " File Task Category\n", + "0 g0pA_taska.txt a non\n", + "1 g0pA_taskb.txt b cut\n", + "2 g0pA_taskc.txt c light\n", + "3 g0pA_taskd.txt d heavy\n", + "4 g0pA_taske.txt e non\n", + "5 g0pB_taska.txt a non\n", + "6 g0pB_taskb.txt b non\n", + "7 g0pB_taskc.txt c cut\n", + "8 g0pB_taskd.txt d light\n", + "9 g0pB_taske.txt e heavy" + ] + }, + "execution_count": 2, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "csv_file = 'data/file_information.csv'\n", + "plagiarism_df = pd.read_csv(csv_file)\n", + "\n", + "# print out the first few rows of data info\n", + "plagiarism_df.head(10)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Types of Plagiarism\n", + "\n", + "Each text file is associated with one **Task** (task A-E) and one **Category** of plagiarism, which you can see in the above DataFrame.\n", + "\n", + "### Five task types, A-E\n", + "\n", + "Each text file contains an answer to one short question; these questions are labeled as tasks A-E.\n", + "* Each task, A-E, is about a topic that might be included in the Computer Science curriculum that was created by the authors of this dataset. \n", + " * For example, Task A asks the question: \"What is inheritance in object oriented programming?\"\n", + "\n", + "### Four categories of plagiarism \n", + "\n", + "Each text file has an associated plagiarism label/category:\n", + "\n", + "1. `cut`: An answer is plagiarized; it is copy-pasted directly from the relevant Wikipedia source text.\n", + "2. `light`: An answer is plagiarized; it is based on the Wikipedia source text and includes some copying and paraphrasing.\n", + "3. `heavy`: An answer is plagiarized; it is based on the Wikipedia source text but expressed using different words and structure. Since this doesn't copy directly from a source text, this will likely be the most challenging kind of plagiarism to detect.\n", + "4. `non`: An answer is not plagiarized; the Wikipedia source text is not used to create this answer.\n", + "5. `orig`: This is a specific category for the original, Wikipedia source text. We will use these files only for comparison purposes.\n", + "\n", + "> So, out of the submitted files, the only category that does not contain any plagiarism is `non`.\n", + "\n", + "In the next cell, print out some statistics about the data." + ] + }, + { + "cell_type": "code", + "execution_count": 3, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Number of files: 100\n", + "Number of unique tasks/question types (A-E): 5\n", + "Unique plagiarism categories: ['non' 'cut' 'light' 'heavy' 'orig']\n" + ] + } + ], + "source": [ + "# print out some stats about the data\n", + "print('Number of files: ', plagiarism_df.shape[0]) # .shape[0] gives the rows \n", + "# .unique() gives unique items in a specified column\n", + "print('Number of unique tasks/question types (A-E): ', (len(plagiarism_df['Task'].unique())))\n", + "print('Unique plagiarism categories: ', (plagiarism_df['Category'].unique()))" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "You should see the number of text files in the dataset as well as some characteristics about the `Task` and `Category` columns. **Note that the file count of 100 *includes* the 5 _original_ wikipedia files for tasks A-E.** If you take a look at the files in the `data` directory, you'll notice that the original, source texts start with the filename `orig_` as opposed to `g` for \"group.\" \n", + "\n", + "> So, in total there are 100 files, 95 of which are answers (submitted by people) and 5 of which are the original, Wikipedia source texts.\n", + "\n", + "Your end goal will be to use this information to classify any given answer text into one of two categories, plagiarized or not-plagiarized." + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Distribution of Data\n", + "\n", + "Next, let's look at the distribution of data. In this course, we've talked about traits like class imbalance that can inform how you develop an algorithm. So, here, we'll ask: **How evenly is our data distributed among different tasks and plagiarism levels?**\n", + "\n", + "Below, you should notice two things:\n", + "* Our dataset is quite small, especially with respect to examples of varying plagiarism levels.\n", + "* The data is distributed fairly evenly across task and plagiarism types." + ] + }, + { + "cell_type": "code", + "execution_count": 4, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "\n", + "Task:\n" + ] + }, + { + "data": { + "text/html": [ + "
\n", + "\n", + "\n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + "
TaskCounts
0a20
1b20
2c20
3d20
4e20
\n", + "
" + ], + "text/plain": [ + " Task Counts\n", + "0 a 20\n", + "1 b 20\n", + "2 c 20\n", + "3 d 20\n", + "4 e 20" + ] + }, + "metadata": {}, + "output_type": "display_data" + }, + { + "name": "stdout", + "output_type": "stream", + "text": [ + "\n", + "Plagiarism Levels:\n" + ] + }, + { + "data": { + "text/html": [ + "
\n", + "\n", + "\n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + "
CategoryCounts
0cut19
1heavy19
2light19
3non38
4orig5
\n", + "
" + ], + "text/plain": [ + " Category Counts\n", + "0 cut 19\n", + "1 heavy 19\n", + "2 light 19\n", + "3 non 38\n", + "4 orig 5" + ] + }, + "metadata": {}, + "output_type": "display_data" + }, + { + "name": "stdout", + "output_type": "stream", + "text": [ + "\n", + "Task & Plagiarism Level Combos :\n" + ] + }, + { + "data": { + "text/html": [ + "
\n", + "\n", + "\n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + "
TaskCategoryCounts
0acut4
1aheavy3
2alight3
3anon9
4aorig1
5bcut3
6bheavy4
7blight3
8bnon9
9borig1
10ccut3
11cheavy5
12clight4
13cnon7
14corig1
15dcut4
16dheavy4
17dlight5
18dnon6
19dorig1
20ecut5
21eheavy3
22elight4
23enon7
24eorig1
\n", + "
" + ], + "text/plain": [ + " Task Category Counts\n", + "0 a cut 4\n", + "1 a heavy 3\n", + "2 a light 3\n", + "3 a non 9\n", + "4 a orig 1\n", + "5 b cut 3\n", + "6 b heavy 4\n", + "7 b light 3\n", + "8 b non 9\n", + "9 b orig 1\n", + "10 c cut 3\n", + "11 c heavy 5\n", + "12 c light 4\n", + "13 c non 7\n", + "14 c orig 1\n", + "15 d cut 4\n", + "16 d heavy 4\n", + "17 d light 5\n", + "18 d non 6\n", + "19 d orig 1\n", + "20 e cut 5\n", + "21 e heavy 3\n", + "22 e light 4\n", + "23 e non 7\n", + "24 e orig 1" + ] + }, + "metadata": {}, + "output_type": "display_data" + } + ], + "source": [ + "# Show counts by different tasks and amounts of plagiarism\n", + "\n", + "# group and count by task\n", + "counts_per_task=plagiarism_df.groupby(['Task']).size().reset_index(name=\"Counts\")\n", + "print(\"\\nTask:\")\n", + "display(counts_per_task)\n", + "\n", + "# group by plagiarism level\n", + "counts_per_category=plagiarism_df.groupby(['Category']).size().reset_index(name=\"Counts\")\n", + "print(\"\\nPlagiarism Levels:\")\n", + "display(counts_per_category)\n", + "\n", + "# group by task AND plagiarism level\n", + "counts_task_and_plagiarism=plagiarism_df.groupby(['Task', 'Category']).size().reset_index(name=\"Counts\")\n", + "print(\"\\nTask & Plagiarism Level Combos :\")\n", + "display(counts_task_and_plagiarism)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "It may also be helpful to look at this last DataFrame, graphically.\n", + "\n", + "Below, you can see that the counts follow a pattern broken down by task. Each task has one source text (original) and the highest number on `non` plagiarized cases." + ] + }, + { + "cell_type": "code", + "execution_count": 5, + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "" + ] + }, + "execution_count": 5, + "metadata": {}, + "output_type": "execute_result" + }, + { + "data": { + "image/png": "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\n", + "text/plain": [ + "
" + ] + }, + "metadata": {}, + "output_type": "display_data" + } + ], + "source": [ + "import matplotlib.pyplot as plt\n", + "% matplotlib inline\n", + "\n", + "# counts\n", + "group = ['Task', 'Category']\n", + "counts = plagiarism_df.groupby(group).size().reset_index(name=\"Counts\")\n", + "\n", + "plt.figure(figsize=(8,5))\n", + "plt.bar(range(len(counts)), counts['Counts'], color = 'blue')" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Up Next\n", + "\n", + "This notebook is just about data loading and exploration, and you do not need to include it in your final project submission. \n", + "\n", + "In the next few notebooks, you'll use this data to train a complete plagiarism classifier. You'll be tasked with extracting meaningful features from the text data, reading in answers to different tasks and comparing them to the original Wikipedia source text. You'll engineer similarity features that will help identify cases of plagiarism. Then, you'll use these features to train and deploy a classification model in a SageMaker notebook instance. " + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [] + } + ], + "metadata": { + "kernelspec": { + "display_name": "conda_amazonei_mxnet_p36", + "language": "python", + "name": "conda_amazonei_mxnet_p36" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.6.5" + } + }, + "nbformat": 4, + "nbformat_minor": 2 +} diff --git a/2_Plagiarism_Feature_Engineering.ipynb b/2_Plagiarism_Feature_Engineering.ipynb new file mode 100644 index 0000000..9a32e37 --- /dev/null +++ b/2_Plagiarism_Feature_Engineering.ipynb @@ -0,0 +1,2362 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "# Plagiarism Detection, Feature Engineering\n", + "\n", + "In this project, you will be tasked with building a plagiarism detector that examines an answer text file and performs binary classification; labeling that file as either plagiarized or not, depending on how similar that text file is to a provided, source text. \n", + "\n", + "Your first task will be to create some features that can then be used to train a classification model. This task will be broken down into a few discrete steps:\n", + "\n", + "* Clean and pre-process the data.\n", + "* Define features for comparing the similarity of an answer text and a source text, and extract similarity features.\n", + "* Select \"good\" features, by analyzing the correlations between different features.\n", + "* Create train/test `.csv` files that hold the relevant features and class labels for train/test data points.\n", + "\n", + "In the _next_ notebook, Notebook 3, you'll use the features and `.csv` files you create in _this_ notebook to train a binary classification model in a SageMaker notebook instance.\n", + "\n", + "You'll be defining a few different similarity features, as outlined in [this paper](https://s3.amazonaws.com/video.udacity-data.com/topher/2019/January/5c412841_developing-a-corpus-of-plagiarised-short-answers/developing-a-corpus-of-plagiarised-short-answers.pdf), which should help you build a robust plagiarism detector!\n", + "\n", + "To complete this notebook, you'll have to complete all given exercises and answer all the questions in this notebook.\n", + "> All your tasks will be clearly labeled **EXERCISE** and questions as **QUESTION**.\n", + "\n", + "It will be up to you to decide on the features to include in your final training and test data.\n", + "\n", + "---" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Read in the Data\n", + "\n", + "The cell below will download the necessary, project data and extract the files into the folder `data/`.\n", + "\n", + "This data is a slightly modified version of a dataset created by Paul Clough (Information Studies) and Mark Stevenson (Computer Science), at the University of Sheffield. You can read all about the data collection and corpus, at [their university webpage](https://ir.shef.ac.uk/cloughie/resources/plagiarism_corpus.html). \n", + "\n", + "> **Citation for data**: Clough, P. and Stevenson, M. Developing A Corpus of Plagiarised Short Answers, Language Resources and Evaluation: Special Issue on Plagiarism and Authorship Analysis, In Press. [Download]" + ] + }, + { + "cell_type": "code", + "execution_count": 1, + "metadata": {}, + "outputs": [], + "source": [ + "# NOTE:\n", + "# you only need to run this cell if you have not yet downloaded the data\n", + "# otherwise you may skip this cell or comment it out\n", + "\n", + "#!wget https://s3.amazonaws.com/video.udacity-data.com/topher/2019/January/5c4147f9_data/data.zip\n", + "#!unzip data" + ] + }, + { + "cell_type": "code", + "execution_count": 2, + "metadata": {}, + "outputs": [], + "source": [ + "# import libraries\n", + "import pandas as pd\n", + "import numpy as np\n", + "import os" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "This plagiarism dataset is made of multiple text files; each of these files has characteristics that are is summarized in a `.csv` file named `file_information.csv`, which we can read in using `pandas`." + ] + }, + { + "cell_type": "code", + "execution_count": 3, + "metadata": {}, + "outputs": [ + { + "data": { + "text/html": [ + "
\n", + "\n", + "\n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + "
FileTaskCategory
0g0pA_taska.txtanon
1g0pA_taskb.txtbcut
2g0pA_taskc.txtclight
3g0pA_taskd.txtdheavy
4g0pA_taske.txtenon
\n", + "
" + ], + "text/plain": [ + " File Task Category\n", + "0 g0pA_taska.txt a non\n", + "1 g0pA_taskb.txt b cut\n", + "2 g0pA_taskc.txt c light\n", + "3 g0pA_taskd.txt d heavy\n", + "4 g0pA_taske.txt e non" + ] + }, + "execution_count": 3, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "csv_file = 'data/file_information.csv'\n", + "plagiarism_df = pd.read_csv(csv_file)\n", + "\n", + "# print out the first few rows of data info\n", + "plagiarism_df.head()" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Types of Plagiarism\n", + "\n", + "Each text file is associated with one **Task** (task A-E) and one **Category** of plagiarism, which you can see in the above DataFrame.\n", + "\n", + "### Tasks, A-E\n", + "\n", + "Each text file contains an answer to one short question; these questions are labeled as tasks A-E. For example, Task A asks the question: \"What is inheritance in object oriented programming?\"\n", + "\n", + "### Categories of plagiarism \n", + "\n", + "Each text file has an associated plagiarism label/category:\n", + "\n", + "**1. Plagiarized categories: `cut`, `light`, and `heavy`.**\n", + "* These categories represent different levels of plagiarized answer texts. `cut` answers copy directly from a source text, `light` answers are based on the source text but include some light rephrasing, and `heavy` answers are based on the source text, but *heavily* rephrased (and will likely be the most challenging kind of plagiarism to detect).\n", + " \n", + "**2. Non-plagiarized category: `non`.** \n", + "* `non` indicates that an answer is not plagiarized; the Wikipedia source text is not used to create this answer.\n", + " \n", + "**3. Special, source text category: `orig`.**\n", + "* This is a specific category for the original, Wikipedia source text. We will use these files only for comparison purposes." + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "---\n", + "## Pre-Process the Data\n", + "\n", + "In the next few cells, you'll be tasked with creating a new DataFrame of desired information about all of the files in the `data/` directory. This will prepare the data for feature extraction and for training a binary, plagiarism classifier." + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### EXERCISE: Convert categorical to numerical data\n", + "\n", + "You'll notice that the `Category` column in the data, contains string or categorical values, and to prepare these for feature extraction, we'll want to convert these into numerical values. Additionally, our goal is to create a binary classifier and so we'll need a binary class label that indicates whether an answer text is plagiarized (1) or not (0). Complete the below function `numerical_dataframe` that reads in a `file_information.csv` file by name, and returns a *new* DataFrame with a numerical `Category` column and a new `Class` column that labels each answer as plagiarized or not. \n", + "\n", + "Your function should return a new DataFrame with the following properties:\n", + "\n", + "* 4 columns: `File`, `Task`, `Category`, `Class`. The `File` and `Task` columns can remain unchanged from the original `.csv` file.\n", + "* Convert all `Category` labels to numerical labels according to the following rules (a higher value indicates a higher degree of plagiarism):\n", + " * 0 = `non`\n", + " * 1 = `heavy`\n", + " * 2 = `light`\n", + " * 3 = `cut`\n", + " * -1 = `orig`, this is a special value that indicates an original file.\n", + "* For the new `Class` column\n", + " * Any answer text that is not plagiarized (`non`) should have the class label `0`. \n", + " * Any plagiarized answer texts should have the class label `1`. \n", + " * And any `orig` texts will have a special label `-1`. \n", + "\n", + "### Expected output\n", + "\n", + "After running your function, you should get a DataFrame with rows that looks like the following: \n", + "```\n", + "\n", + " File\t Task Category Class\n", + "0\tg0pA_taska.txt\ta\t 0 \t0\n", + "1\tg0pA_taskb.txt\tb\t 3 \t1\n", + "2\tg0pA_taskc.txt\tc\t 2 \t1\n", + "3\tg0pA_taskd.txt\td\t 1 \t1\n", + "4\tg0pA_taske.txt\te\t 0\t 0\n", + "...\n", + "...\n", + "99 orig_taske.txt e -1 -1\n", + "\n", + "```" + ] + }, + { + "cell_type": "code", + "execution_count": 4, + "metadata": {}, + "outputs": [], + "source": [ + "# Read in a csv file and return a transformed dataframe\n", + "def numerical_dataframe(csv_file='data/file_information.csv'):\n", + " '''Reads in a csv file which is assumed to have `File`, `Category` and `Task` columns.\n", + " This function does two things: \n", + " 1) converts `Category` column values to numerical values \n", + " 2) Adds a new, numerical `Class` label column.\n", + " The `Class` column will label plagiarized answers as 1 and non-plagiarized as 0.\n", + " Source texts have a special label, -1.\n", + " :param csv_file: The directory for the file_information.csv file\n", + " :return: A dataframe with numerical categories and a new `Class` label column'''\n", + " \n", + " # your code here\n", + " category_to_numerical = {'non': 0, 'heavy': 1, 'light': 2, 'cut': 3, 'orig': -1 }\n", + " numerical_to_class = {'non': 0, 'heavy': 1, 'light': 1, 'cut': 1, 'orig': -1}\n", + " df = pd.read_csv(csv_file)\n", + " class_list = []\n", + " category_list = []\n", + " for index, row in df.iterrows():\n", + " category_list.append(category_to_numerical[row['Category']])\n", + " class_list.append(numerical_to_class[row['Category']])\n", + " df['Category'] = category_list\n", + " df['Class'] = class_list\n", + " return df\n" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Test cells\n", + "\n", + "Below are a couple of test cells. The first is an informal test where you can check that your code is working as expected by calling your function and printing out the returned result.\n", + "\n", + "The **second** cell below is a more rigorous test cell. The goal of a cell like this is to ensure that your code is working as expected, and to form any variables that might be used in _later_ tests/code, in this case, the data frame, `transformed_df`.\n", + "\n", + "> The cells in this notebook should be run in chronological order (the order they appear in the notebook). This is especially important for test cells.\n", + "\n", + "Often, later cells rely on the functions, imports, or variables defined in earlier cells. For example, some tests rely on previous tests to work.\n", + "\n", + "These tests do not test all cases, but they are a great way to check that you are on the right track!" + ] + }, + { + "cell_type": "code", + "execution_count": 5, + "metadata": {}, + "outputs": [ + { + "data": { + "text/html": [ + "
\n", + "\n", + "\n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + "
FileTaskCategoryClass
0g0pA_taska.txta00
1g0pA_taskb.txtb31
2g0pA_taskc.txtc21
3g0pA_taskd.txtd11
4g0pA_taske.txte00
5g0pB_taska.txta00
6g0pB_taskb.txtb00
7g0pB_taskc.txtc31
8g0pB_taskd.txtd21
9g0pB_taske.txte11
\n", + "
" + ], + "text/plain": [ + " File Task Category Class\n", + "0 g0pA_taska.txt a 0 0\n", + "1 g0pA_taskb.txt b 3 1\n", + "2 g0pA_taskc.txt c 2 1\n", + "3 g0pA_taskd.txt d 1 1\n", + "4 g0pA_taske.txt e 0 0\n", + "5 g0pB_taska.txt a 0 0\n", + "6 g0pB_taskb.txt b 0 0\n", + "7 g0pB_taskc.txt c 3 1\n", + "8 g0pB_taskd.txt d 2 1\n", + "9 g0pB_taske.txt e 1 1" + ] + }, + "execution_count": 5, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "# informal testing, print out the results of a called function\n", + "# create new `transformed_df`\n", + "transformed_df = numerical_dataframe(csv_file ='data/file_information.csv')\n", + "\n", + "# check work\n", + "# check that all categories of plagiarism have a class label = 1\n", + "transformed_df.head(10)" + ] + }, + { + "cell_type": "code", + "execution_count": 6, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Tests Passed!\n", + "\n", + "Example data: \n" + ] + }, + { + "data": { + "text/html": [ + "
\n", + "\n", + "\n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + "
FileTaskCategoryClass
0g0pA_taska.txta00
1g0pA_taskb.txtb31
2g0pA_taskc.txtc21
3g0pA_taskd.txtd11
4g0pA_taske.txte00
\n", + "
" + ], + "text/plain": [ + " File Task Category Class\n", + "0 g0pA_taska.txt a 0 0\n", + "1 g0pA_taskb.txt b 3 1\n", + "2 g0pA_taskc.txt c 2 1\n", + "3 g0pA_taskd.txt d 1 1\n", + "4 g0pA_taske.txt e 0 0" + ] + }, + "execution_count": 6, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "# test cell that creates `transformed_df`, if tests are passed\n", + "\n", + "\"\"\"\n", + "DON'T MODIFY ANYTHING IN THIS CELL THAT IS BELOW THIS LINE\n", + "\"\"\"\n", + "\n", + "# importing tests\n", + "import problem_unittests as tests\n", + "\n", + "# test numerical_dataframe function\n", + "tests.test_numerical_df(numerical_dataframe)\n", + "\n", + "# if above test is passed, create NEW `transformed_df`\n", + "transformed_df = numerical_dataframe(csv_file ='data/file_information.csv')\n", + "\n", + "# check work\n", + "print('\\nExample data: ')\n", + "transformed_df.head()" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Text Processing & Splitting Data\n", + "\n", + "Recall that the goal of this project is to build a plagiarism classifier. At it's heart, this task is a comparison text; one that looks at a given answer and a source text, compares them and predicts whether an answer has plagiarized from the source. To effectively do this comparison, and train a classifier we'll need to do a few more things: pre-process all of our text data and prepare the text files (in this case, the 95 answer files and 5 original source files) to be easily compared, and split our data into a `train` and `test` set that can be used to train a classifier and evaluate it, respectively. \n", + "\n", + "To this end, you've been provided code that adds additional information to your `transformed_df` from above. The next two cells need not be changed; they add two additional columns to the `transformed_df`:\n", + "\n", + "1. A `Text` column; this holds all the lowercase text for a `File`, with extraneous punctuation removed.\n", + "2. A `Datatype` column; this is a string value `train`, `test`, or `orig` that labels a data point as part of our train or test set\n", + "\n", + "The details of how these additional columns are created can be found in the `helpers.py` file in the project directory. You're encouraged to read through that file to see exactly how text is processed and how data is split.\n", + "\n", + "Run the cells below to get a `complete_df` that has all the information you need to proceed with plagiarism detection and feature engineering." + ] + }, + { + "cell_type": "code", + "execution_count": 7, + "metadata": {}, + "outputs": [ + { + "data": { + "text/html": [ + "
\n", + "\n", + "\n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + "
FileTaskCategoryClassText
0g0pA_taska.txta00inheritance is a basic concept of object orien...
1g0pA_taskb.txtb31pagerank is a link analysis algorithm used by ...
2g0pA_taskc.txtc21the vector space model also called term vector...
3g0pA_taskd.txtd11bayes theorem was names after rev thomas bayes...
4g0pA_taske.txte00dynamic programming is an algorithm design tec...
\n", + "
" + ], + "text/plain": [ + " File Task Category Class \\\n", + "0 g0pA_taska.txt a 0 0 \n", + "1 g0pA_taskb.txt b 3 1 \n", + "2 g0pA_taskc.txt c 2 1 \n", + "3 g0pA_taskd.txt d 1 1 \n", + "4 g0pA_taske.txt e 0 0 \n", + "\n", + " Text \n", + "0 inheritance is a basic concept of object orien... \n", + "1 pagerank is a link analysis algorithm used by ... \n", + "2 the vector space model also called term vector... \n", + "3 bayes theorem was names after rev thomas bayes... \n", + "4 dynamic programming is an algorithm design tec... " + ] + }, + "execution_count": 7, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "\"\"\"\n", + "DON'T MODIFY ANYTHING IN THIS CELL THAT IS BELOW THIS LINE\n", + "\"\"\"\n", + "import helpers \n", + "\n", + "# create a text column \n", + "text_df = helpers.create_text_column(transformed_df)\n", + "text_df.head()" + ] + }, + { + "cell_type": "code", + "execution_count": 8, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Sample processed text:\n", + "\n", + " inheritance is a basic concept of object oriented programming where the basic idea is to create new classes that add extra detail to existing classes this is done by allowing the new classes to reuse the methods and variables of the existing classes and new methods and classes are added to specialise the new class inheritance models the is kind of relationship between entities or objects for example postgraduates and undergraduates are both kinds of student this kind of relationship can be visualised as a tree structure where student would be the more general root node and both postgraduate and undergraduate would be more specialised extensions of the student node or the child nodes in this relationship student would be known as the superclass or parent class whereas postgraduate would be known as the subclass or child class because the postgraduate class extends the student class inheritance can occur on several layers where if visualised would display a larger tree structure for example we could further extend the postgraduate node by adding two extra extended classes to it called msc student and phd student as both these types of student are kinds of postgraduate student this would mean that both the msc student and phd student classes would inherit methods and variables from both the postgraduate and student classes \n" + ] + } + ], + "source": [ + "# after running the cell above\n", + "# check out the processed text for a single file, by row index\n", + "row_idx = 0 # feel free to change this index\n", + "\n", + "sample_text = text_df.iloc[0]['Text']\n", + "\n", + "print('Sample processed text:\\n\\n', sample_text)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Split data into training and test sets\n", + "\n", + "The next cell will add a `Datatype` column to a given DataFrame to indicate if the record is: \n", + "* `train` - Training data, for model training.\n", + "* `test` - Testing data, for model evaluation.\n", + "* `orig` - The task's original answer from wikipedia.\n", + "\n", + "### Stratified sampling\n", + "\n", + "The given code uses a helper function which you can view in the `helpers.py` file in the main project directory. This implements [stratified random sampling](https://en.wikipedia.org/wiki/Stratified_sampling) to randomly split data by task & plagiarism amount. Stratified sampling ensures that we get training and test data that is fairly evenly distributed across task & plagiarism combinations. Approximately 26% of the data is held out for testing and 74% of the data is used for training.\n", + "\n", + "The function **train_test_dataframe** takes in a DataFrame that it assumes has `Task` and `Category` columns, and, returns a modified frame that indicates which `Datatype` (train, test, or orig) a file falls into. This sampling will change slightly based on a passed in *random_seed*. Due to a small sample size, this stratified random sampling will provide more stable results for a binary plagiarism classifier. Stability here is smaller *variance* in the accuracy of classifier, given a random seed." + ] + }, + { + "cell_type": "code", + "execution_count": 9, + "metadata": {}, + "outputs": [ + { + "data": { + "text/html": [ + "
\n", + "\n", + "\n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + "
FileTaskCategoryClassTextDatatype
0g0pA_taska.txta00inheritance is a basic concept of object orien...train
1g0pA_taskb.txtb31pagerank is a link analysis algorithm used by ...test
2g0pA_taskc.txtc21the vector space model also called term vector...train
3g0pA_taskd.txtd11bayes theorem was names after rev thomas bayes...train
4g0pA_taske.txte00dynamic programming is an algorithm design tec...train
5g0pB_taska.txta00inheritance is a basic concept in object orien...train
6g0pB_taskb.txtb00pagerank pr refers to both the concept and the...train
7g0pB_taskc.txtc31vector space model is an algebraic model for r...test
8g0pB_taskd.txtd21bayes theorem relates the conditional and marg...train
9g0pB_taske.txte11dynamic programming is a method for solving ma...test
\n", + "
" + ], + "text/plain": [ + " File Task Category Class \\\n", + "0 g0pA_taska.txt a 0 0 \n", + "1 g0pA_taskb.txt b 3 1 \n", + "2 g0pA_taskc.txt c 2 1 \n", + "3 g0pA_taskd.txt d 1 1 \n", + "4 g0pA_taske.txt e 0 0 \n", + "5 g0pB_taska.txt a 0 0 \n", + "6 g0pB_taskb.txt b 0 0 \n", + "7 g0pB_taskc.txt c 3 1 \n", + "8 g0pB_taskd.txt d 2 1 \n", + "9 g0pB_taske.txt e 1 1 \n", + "\n", + " Text Datatype \n", + "0 inheritance is a basic concept of object orien... train \n", + "1 pagerank is a link analysis algorithm used by ... test \n", + "2 the vector space model also called term vector... train \n", + "3 bayes theorem was names after rev thomas bayes... train \n", + "4 dynamic programming is an algorithm design tec... train \n", + "5 inheritance is a basic concept in object orien... train \n", + "6 pagerank pr refers to both the concept and the... train \n", + "7 vector space model is an algebraic model for r... test \n", + "8 bayes theorem relates the conditional and marg... train \n", + "9 dynamic programming is a method for solving ma... test " + ] + }, + "execution_count": 9, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "random_seed = 1 # can change; set for reproducibility\n", + "\n", + "\"\"\"\n", + "DON'T MODIFY ANYTHING IN THIS CELL THAT IS BELOW THIS LINE\n", + "\"\"\"\n", + "import helpers\n", + "\n", + "# create new df with Datatype (train, test, orig) column\n", + "# pass in `text_df` from above to create a complete dataframe, with all the information you need\n", + "complete_df = helpers.train_test_dataframe(text_df, random_seed=random_seed)\n", + "\n", + "# check results\n", + "complete_df.head(10)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "# Determining Plagiarism\n", + "\n", + "Now that you've prepared this data and created a `complete_df` of information, including the text and class associated with each file, you can move on to the task of extracting similarity features that will be useful for plagiarism classification. \n", + "\n", + "> Note: The following code exercises, assume that the `complete_df` as it exists now, will **not** have its existing columns modified. \n", + "\n", + "The `complete_df` should always include the columns: `['File', 'Task', 'Category', 'Class', 'Text', 'Datatype']`. You can add additional columns, and you can create any new DataFrames you need by copying the parts of the `complete_df` as long as you do not modify the existing values, directly.\n", + "\n", + "---" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "\n", + "# Similarity Features \n", + "\n", + "One of the ways we might go about detecting plagiarism, is by computing **similarity features** that measure how similar a given answer text is as compared to the original wikipedia source text (for a specific task, a-e). The similarity features you will use are informed by [this paper on plagiarism detection](https://s3.amazonaws.com/video.udacity-data.com/topher/2019/January/5c412841_developing-a-corpus-of-plagiarised-short-answers/developing-a-corpus-of-plagiarised-short-answers.pdf). \n", + "> In this paper, researchers created features called **containment** and **longest common subsequence**. \n", + "\n", + "Using these features as input, you will train a model to distinguish between plagiarized and not-plagiarized text files.\n", + "\n", + "## Feature Engineering\n", + "\n", + "Let's talk a bit more about the features we want to include in a plagiarism detection model and how to calculate such features. In the following explanations, I'll refer to a submitted text file as a **Student Answer Text (A)** and the original, wikipedia source file (that we want to compare that answer to) as the **Wikipedia Source Text (S)**.\n", + "\n", + "### Containment\n", + "\n", + "Your first task will be to create **containment features**. To understand containment, let's first revisit a definition of [n-grams](https://en.wikipedia.org/wiki/N-gram). An *n-gram* is a sequential word grouping. For example, in a line like \"bayes rule gives us a way to combine prior knowledge with new information,\" a 1-gram is just one word, like \"bayes.\" A 2-gram might be \"bayes rule\" and a 3-gram might be \"combine prior knowledge.\"\n", + "\n", + "> Containment is defined as the **intersection** of the n-gram word count of the Wikipedia Source Text (S) with the n-gram word count of the Student Answer Text (S) *divided* by the n-gram word count of the Student Answer Text.\n", + "\n", + "$$ \\frac{\\sum{count(\\text{ngram}_{A}) \\cap count(\\text{ngram}_{S})}}{\\sum{count(\\text{ngram}_{A})}} $$\n", + "\n", + "If the two texts have no n-grams in common, the containment will be 0, but if _all_ their n-grams intersect then the containment will be 1. Intuitively, you can see how having longer n-gram's in common, might be an indication of cut-and-paste plagiarism. In this project, it will be up to you to decide on the appropriate `n` or several `n`'s to use in your final model.\n", + "\n", + "### EXERCISE: Create containment features\n", + "\n", + "Given the `complete_df` that you've created, you should have all the information you need to compare any Student Answer Text (A) with its appropriate Wikipedia Source Text (S). An answer for task A should be compared to the source text for task A, just as answers to tasks B, C, D, and E should be compared to the corresponding original source text.\n", + "\n", + "In this exercise, you'll complete the function, `calculate_containment` which calculates containment based upon the following parameters:\n", + "* A given DataFrame, `df` (which is assumed to be the `complete_df` from above)\n", + "* An `answer_filename`, such as 'g0pB_taskd.txt' \n", + "* An n-gram length, `n`\n", + "\n", + "### Containment calculation\n", + "\n", + "The general steps to complete this function are as follows:\n", + "1. From *all* of the text files in a given `df`, create an array of n-gram counts; it is suggested that you use a [CountVectorizer](https://scikit-learn.org/stable/modules/generated/sklearn.feature_extraction.text.CountVectorizer.html) for this purpose.\n", + "2. Get the processed answer and source texts for the given `answer_filename`.\n", + "3. Calculate the containment between an answer and source text according to the following equation.\n", + "\n", + " >$$ \\frac{\\sum{count(\\text{ngram}_{A}) \\cap count(\\text{ngram}_{S})}}{\\sum{count(\\text{ngram}_{A})}} $$\n", + " \n", + "4. Return that containment value.\n", + "\n", + "You are encouraged to write any helper functions that you need to complete the function below." + ] + }, + { + "cell_type": "code", + "execution_count": 10, + "metadata": {}, + "outputs": [], + "source": [ + "from sklearn.feature_extraction.text import CountVectorizer\n", + "\n", + "# Calculate the ngram containment for one answer file/source file pair in a df\n", + "def calculate_containment(df, n, answer_filename):\n", + " '''Calculates the containment between a given answer text and its associated source text.\n", + " This function creates a count of ngrams (of a size, n) for each text file in our data.\n", + " Then calculates the containment by finding the ngram count for a given answer text, \n", + " and its associated source text, and calculating the normalized intersection of those counts.\n", + " :param df: A dataframe with columns,\n", + " 'File', 'Task', 'Category', 'Class', 'Text', and 'Datatype'\n", + " :param n: An integer that defines the ngram size\n", + " :param answer_filename: A filename for an answer text in the df, ex. 'g0pB_taskd.txt'\n", + " :return: A single containment value that represents the similarity\n", + " between an answer text and its source text.\n", + " '''\n", + " \n", + " # your code here\n", + " row = df.loc[df['File'] == answer_filename]\n", + " answer = row['File']\n", + " answer_location = answer.index.item()\n", + " \n", + " source = df[(df['Task'] == df.iloc[answer_location]['Task']) & (df['Category'] == -1)]\n", + " source_location = source.index.item()\n", + " \n", + " counts = CountVectorizer(analyzer='word', ngram_range=(n,n))\n", + " ngrams = counts.fit_transform(df['Text'])\n", + " \n", + " ngram_array = ngrams.toarray()\n", + " answer_and_source = ngram_array[(answer_location, source_location),]\n", + " \n", + " sum_intersection_ngrams = np.sum(np.min(answer_and_source, axis=0))\n", + " containment = sum_intersection_ngrams / np.sum(answer_and_source[0])\n", + " \n", + " return containment\n" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Test cells\n", + "\n", + "After you've implemented the containment function, you can test out its behavior. \n", + "\n", + "The cell below iterates through the first few files, and calculates the original category _and_ containment values for a specified n and file.\n", + "\n", + ">If you've implemented this correctly, you should see that the non-plagiarized have low or close to 0 containment values and that plagiarized examples have higher containment values, closer to 1.\n", + "\n", + "Note what happens when you change the value of n. I recommend applying your code to multiple files and comparing the resultant containment values. You should see that the highest containment values correspond to files with the highest category (`cut`) of plagiarism level." + ] + }, + { + "cell_type": "code", + "execution_count": 11, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Original category values: \n", + " [0, 3, 2, 1, 0]\n", + "\n", + "3-gram containment values: \n", + " [0.009345794392523364, 0.9641025641025641, 0.6136363636363636, 0.15675675675675677, 0.031746031746031744]\n" + ] + } + ], + "source": [ + "# select a value for n\n", + "n = 3\n", + "\n", + "# indices for first few files\n", + "test_indices = range(5)\n", + "\n", + "# iterate through files and calculate containment\n", + "category_vals = []\n", + "containment_vals = []\n", + "for i in test_indices:\n", + " # get level of plagiarism for a given file index\n", + " category_vals.append(complete_df.loc[i, 'Category'])\n", + " # calculate containment for given file and n\n", + " filename = complete_df.loc[i, 'File']\n", + " c = calculate_containment(complete_df, n, filename)\n", + " containment_vals.append(c)\n", + "\n", + "# print out result, does it make sense?\n", + "print('Original category values: \\n', category_vals)\n", + "print()\n", + "print(str(n)+'-gram containment values: \\n', containment_vals)" + ] + }, + { + "cell_type": "code", + "execution_count": 12, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Tests Passed!\n" + ] + } + ], + "source": [ + "# run this test cell\n", + "\"\"\"\n", + "DON'T MODIFY ANYTHING IN THIS CELL THAT IS BELOW THIS LINE\n", + "\"\"\"\n", + "# test containment calculation\n", + "# params: complete_df from before, and containment function\n", + "tests.test_containment(complete_df, calculate_containment)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### QUESTION 1: Why can we calculate containment features across *all* data (training & test), prior to splitting the DataFrame for modeling? That is, what about the containment calculation means that the test and training data do not influence each other?" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "**Answer:** To make the model as accurate as possible, we train it on both training and test datasets. This also eliminates the condition of missing out important words that might signify plagiarism.\n" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "---\n", + "## Longest Common Subsequence\n", + "\n", + "Containment a good way to find overlap in word usage between two documents; it may help identify cases of cut-and-paste as well as paraphrased levels of plagiarism. Since plagiarism is a fairly complex task with varying levels, it's often useful to include other measures of similarity. The paper also discusses a feature called **longest common subsequence**.\n", + "\n", + "> The longest common subsequence is the longest string of words (or letters) that are *the same* between the Wikipedia Source Text (S) and the Student Answer Text (A). This value is also normalized by dividing by the total number of words (or letters) in the Student Answer Text. \n", + "\n", + "In this exercise, we'll ask you to calculate the longest common subsequence of words between two texts.\n", + "\n", + "### EXERCISE: Calculate the longest common subsequence\n", + "\n", + "Complete the function `lcs_norm_word`; this should calculate the *longest common subsequence* of words between a Student Answer Text and corresponding Wikipedia Source Text. \n", + "\n", + "It may be helpful to think of this in a concrete example. A Longest Common Subsequence (LCS) problem may look as follows:\n", + "* Given two texts: text A (answer text) of length n, and string S (original source text) of length m. Our goal is to produce their longest common subsequence of words: the longest sequence of words that appear left-to-right in both texts (though the words don't have to be in continuous order).\n", + "* Consider:\n", + " * A = \"i think pagerank is a link analysis algorithm used by google that uses a system of weights attached to each element of a hyperlinked set of documents\"\n", + " * S = \"pagerank is a link analysis algorithm used by the google internet search engine that assigns a numerical weighting to each element of a hyperlinked set of documents\"\n", + "\n", + "* In this case, we can see that the start of each sentence of fairly similar, having overlap in the sequence of words, \"pagerank is a link analysis algorithm used by\" before diverging slightly. Then we **continue moving left -to-right along both texts** until we see the next common sequence; in this case it is only one word, \"google\". Next we find \"that\" and \"a\" and finally the same ending \"to each element of a hyperlinked set of documents\".\n", + "* Below, is a clear visual of how these sequences were found, sequentially, in each text.\n", + "\n", + "\n", + "\n", + "* Now, those words appear in left-to-right order in each document, sequentially, and even though there are some words in between, we count this as the longest common subsequence between the two texts. \n", + "* If I count up each word that I found in common I get the value 20. **So, LCS has length 20**. \n", + "* Next, to normalize this value, divide by the total length of the student answer; in this example that length is only 27. **So, the function `lcs_norm_word` should return the value `20/27` or about `0.7408`.**\n", + "\n", + "In this way, LCS is a great indicator of cut-and-paste plagiarism or if someone has referenced the same source text multiple times in an answer." + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### LCS, dynamic programming\n", + "\n", + "If you read through the scenario above, you can see that this algorithm depends on looking at two texts and comparing them word by word. You can solve this problem in multiple ways. First, it may be useful to `.split()` each text into lists of comma separated words to compare. Then, you can iterate through each word in the texts and compare them, adding to your value for LCS as you go. \n", + "\n", + "The method I recommend for implementing an efficient LCS algorithm is: using a matrix and dynamic programming. **Dynamic programming** is all about breaking a larger problem into a smaller set of subproblems, and building up a complete result without having to repeat any subproblems. \n", + "\n", + "This approach assumes that you can split up a large LCS task into a combination of smaller LCS tasks. Let's look at a simple example that compares letters:\n", + "\n", + "* A = \"ABCD\"\n", + "* S = \"BD\"\n", + "\n", + "We can see right away that the longest subsequence of _letters_ here is 2 (B and D are in sequence in both strings). And we can calculate this by looking at relationships between each letter in the two strings, A and S.\n", + "\n", + "Here, I have a matrix with the letters of A on top and the letters of S on the left side:\n", + "\n", + "\n", + "\n", + "This starts out as a matrix that has as many columns and rows as letters in the strings S and O **+1** additional row and column, filled with zeros on the top and left sides. So, in this case, instead of a 2x4 matrix it is a 3x5.\n", + "\n", + "Now, we can fill this matrix up by breaking it into smaller LCS problems. For example, let's first look at the shortest substrings: the starting letter of A and S. We'll first ask, what is the Longest Common Subsequence between these two letters \"A\" and \"B\"? \n", + "\n", + "**Here, the answer is zero and we fill in the corresponding grid cell with that value.**\n", + "\n", + "\n", + "\n", + "Then, we ask the next question, what is the LCS between \"AB\" and \"B\"?\n", + "\n", + "**Here, we have a match, and can fill in the appropriate value 1**.\n", + "\n", + "\n", + "\n", + "If we continue, we get to a final matrix that looks as follows, with a **2** in the bottom right corner.\n", + "\n", + "\n", + "\n", + "The final LCS will be that value **2** *normalized* by the number of n-grams in A. So, our normalized value is 2/4 = **0.5**.\n", + "\n", + "### The matrix rules\n", + "\n", + "One thing to notice here is that, you can efficiently fill up this matrix one cell at a time. Each grid cell only depends on the values in the grid cells that are directly on top and to the left of it, or on the diagonal/top-left. The rules are as follows:\n", + "* Start with a matrix that has one extra row and column of zeros.\n", + "* As you traverse your string:\n", + " * If there is a match, fill that grid cell with the value to the top-left of that cell *plus* one. So, in our case, when we found a matching B-B, we added +1 to the value in the top-left of the matching cell, 0.\n", + " * If there is not a match, take the *maximum* value from either directly to the left or the top cell, and carry that value over to the non-match cell.\n", + "\n", + "\n", + "\n", + "After completely filling the matrix, **the bottom-right cell will hold the non-normalized LCS value**.\n", + "\n", + "This matrix treatment can be applied to a set of words instead of letters. Your function should apply this to the words in two texts and return the normalized LCS value." + ] + }, + { + "cell_type": "code", + "execution_count": 13, + "metadata": {}, + "outputs": [], + "source": [ + "# Compute the normalized LCS given an answer text and a source text\n", + "def lcs_norm_word(answer_text, source_text):\n", + " '''Computes the longest common subsequence of words in two texts; returns a normalized value.\n", + " :param answer_text: The pre-processed text for an answer text\n", + " :param source_text: The pre-processed text for an answer's associated source text\n", + " :return: A normalized LCS value'''\n", + " \n", + " answer = answer_text.split()\n", + " source = source_text.split()\n", + " \n", + " lcs_matrix = np.zeros((len(answer) + 1, len(source) + 1))\n", + " row_index= 0\n", + " col_index = 0\n", + " for row_index in range(0, len(answer)):\n", + " answer_word = answer[row_index]\n", + " for col_index in range(0, len(source)):\n", + " source_word = source[col_index]\n", + " if source_word == answer_word:\n", + " lcs_matrix[row_index + 1][col_index + 1] = (lcs_matrix[row_index][col_index]) + 1\n", + " else: \n", + " lcs_matrix[row_index + 1][col_index + 1] = max(lcs_matrix[row_index][col_index + 1], \n", + " lcs_matrix[row_index + 1][col_index])\n", + "\n", + " normalized_lcs = lcs_matrix[len(answer)][len(source)] / len(answer)\n", + " print(normalized_lcs)\n", + " return normalized_lcs\n" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Test cells\n", + "\n", + "Let's start by testing out your code on the example given in the initial description.\n", + "\n", + "In the below cell, we have specified strings A (answer text) and S (original source text). We know that these texts have 20 words in common and the submitted answer is 27 words long, so the normalized, longest common subsequence should be 20/27.\n" + ] + }, + { + "cell_type": "code", + "execution_count": 14, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "0.7407407407407407\n", + "LCS = 0.7407407407407407\n", + "Test passed!\n" + ] + } + ], + "source": [ + "# Run the test scenario from above\n", + "# does your function return the expected value?\n", + "\n", + "A = \"i think pagerank is a link analysis algorithm used by google that uses a system of weights attached to each element of a hyperlinked set of documents\"\n", + "S = \"pagerank is a link analysis algorithm used by the google internet search engine that assigns a numerical weighting to each element of a hyperlinked set of documents\"\n", + "\n", + "# calculate LCS\n", + "lcs = lcs_norm_word(A, S)\n", + "print('LCS = ', lcs)\n", + "\n", + "\n", + "# expected value test\n", + "assert lcs==20/27., \"Incorrect LCS value, expected about 0.7408, got \"+str(lcs)\n", + "\n", + "print('Test passed!')" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "This next cell runs a more rigorous test." + ] + }, + { + "cell_type": "code", + "execution_count": 15, + "metadata": {}, + "outputs": [ + { + "data": { + "text/html": [ + "
\n", + "\n", + "\n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + "
FileTaskCategoryClassTextDatatype
0g0pA_taska.txta00inheritance is a basic concept of object orien...train
1g0pA_taskb.txtb31pagerank is a link analysis algorithm used by ...test
2g0pA_taskc.txtc21the vector space model also called term vector...train
3g0pA_taskd.txtd11bayes theorem was names after rev thomas bayes...train
4g0pA_taske.txte00dynamic programming is an algorithm design tec...train
\n", + "
" + ], + "text/plain": [ + " File Task Category Class \\\n", + "0 g0pA_taska.txt a 0 0 \n", + "1 g0pA_taskb.txt b 3 1 \n", + "2 g0pA_taskc.txt c 2 1 \n", + "3 g0pA_taskd.txt d 1 1 \n", + "4 g0pA_taske.txt e 0 0 \n", + "\n", + " Text Datatype \n", + "0 inheritance is a basic concept of object orien... train \n", + "1 pagerank is a link analysis algorithm used by ... test \n", + "2 the vector space model also called term vector... train \n", + "3 bayes theorem was names after rev thomas bayes... train \n", + "4 dynamic programming is an algorithm design tec... train " + ] + }, + "execution_count": 15, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "complete_df.head()" + ] + }, + { + "cell_type": "code", + "execution_count": 16, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "0.42783505154639173\n", + "0.1917808219178082\n", + "0.8207547169811321\n", + "0.8464912280701754\n", + "0.3160621761658031\n", + "0.24257425742574257\n", + "Tests Passed!\n" + ] + } + ], + "source": [ + "# run test cell\n", + "\"\"\"\n", + "DON'T MODIFY ANYTHING IN THIS CELL THAT IS BELOW THIS LINE\n", + "\"\"\"\n", + "# test lcs implementation\n", + "# params: complete_df from before, and lcs_norm_word function\n", + "tests.test_lcs(complete_df, lcs_norm_word)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Finally, take a look at a few resultant values for `lcs_norm_word`. Just like before, you should see that higher values correspond to higher levels of plagiarism." + ] + }, + { + "cell_type": "code", + "execution_count": 17, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "0.1917808219178082\n", + "0.8207547169811321\n", + "0.8464912280701754\n", + "0.3160621761658031\n", + "0.24257425742574257\n", + "Original category values: \n", + " [0, 3, 2, 1, 0]\n", + "\n", + "Normalized LCS values: \n", + " [0.1917808219178082, 0.8207547169811321, 0.8464912280701754, 0.3160621761658031, 0.24257425742574257]\n" + ] + } + ], + "source": [ + "# test on your own\n", + "test_indices = range(5) # look at first few files\n", + "\n", + "category_vals = []\n", + "lcs_norm_vals = []\n", + "# iterate through first few docs and calculate LCS\n", + "for i in test_indices:\n", + " category_vals.append(complete_df.loc[i, 'Category'])\n", + " # get texts to compare\n", + " answer_text = complete_df.loc[i, 'Text'] \n", + " task = complete_df.loc[i, 'Task']\n", + " # we know that source texts have Class = -1\n", + " orig_rows = complete_df[(complete_df['Class'] == -1)]\n", + " orig_row = orig_rows[(orig_rows['Task'] == task)]\n", + " source_text = orig_row['Text'].values[0]\n", + " # calculate lcs\n", + " lcs_val = lcs_norm_word(answer_text, source_text)\n", + " lcs_norm_vals.append(lcs_val)\n", + "\n", + "# print out result, does it make sense?\n", + "print('Original category values: \\n', category_vals)\n", + "print()\n", + "print('Normalized LCS values: \\n', lcs_norm_vals)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "---\n", + "# Create All Features\n", + "\n", + "Now that you've completed the feature calculation functions, it's time to actually create multiple features and decide on which ones to use in your final model! In the below cells, you're provided two helper functions to help you create multiple features and store those in a DataFrame, `features_df`.\n", + "\n", + "### Creating multiple containment features\n", + "\n", + "Your completed `calculate_containment` function will be called in the next cell, which defines the helper function `create_containment_features`. \n", + "\n", + "> This function returns a list of containment features, calculated for a given `n` and for *all* files in a df (assumed to the the `complete_df`).\n", + "\n", + "For our original files, the containment value is set to a special value, -1.\n", + "\n", + "This function gives you the ability to easily create several containment features, of different n-gram lengths, for each of our text files." + ] + }, + { + "cell_type": "code", + "execution_count": 18, + "metadata": {}, + "outputs": [], + "source": [ + "\"\"\"\n", + "DON'T MODIFY ANYTHING IN THIS CELL THAT IS BELOW THIS LINE\n", + "\"\"\"\n", + "# Function returns a list of containment features, calculated for a given n \n", + "# Should return a list of length 100 for all files in a complete_df\n", + "def create_containment_features(df, n, column_name=None):\n", + " \n", + " containment_values = []\n", + " \n", + " if(column_name==None):\n", + " column_name = 'c_'+str(n) # c_1, c_2, .. c_n\n", + " \n", + " # iterates through dataframe rows\n", + " for i in df.index:\n", + " file = df.loc[i, 'File']\n", + " # Computes features using calculate_containment function\n", + " if df.loc[i,'Category'] > -1:\n", + " c = calculate_containment(df, n, file)\n", + " containment_values.append(c)\n", + " # Sets value to -1 for original tasks \n", + " else:\n", + " containment_values.append(-1)\n", + " \n", + " print(str(n)+'-gram containment features created!')\n", + " return containment_values\n" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Creating LCS features\n", + "\n", + "Below, your complete `lcs_norm_word` function is used to create a list of LCS features for all the answer files in a given DataFrame (again, this assumes you are passing in the `complete_df`. It assigns a special value for our original, source files, -1.\n" + ] + }, + { + "cell_type": "code", + "execution_count": 19, + "metadata": {}, + "outputs": [], + "source": [ + "\"\"\"\n", + "DON'T MODIFY ANYTHING IN THIS CELL THAT IS BELOW THIS LINE\n", + "\"\"\"\n", + "# Function creates lcs feature and add it to the dataframe\n", + "def create_lcs_features(df, column_name='lcs_word'):\n", + " \n", + " lcs_values = []\n", + " \n", + " # iterate through files in dataframe\n", + " for i in df.index:\n", + " # Computes LCS_norm words feature using function above for answer tasks\n", + " if df.loc[i,'Category'] > -1:\n", + " # get texts to compare\n", + " answer_text = df.loc[i, 'Text'] \n", + " task = df.loc[i, 'Task']\n", + " # we know that source texts have Class = -1\n", + " orig_rows = df[(df['Class'] == -1)]\n", + " orig_row = orig_rows[(orig_rows['Task'] == task)]\n", + " source_text = orig_row['Text'].values[0]\n", + "\n", + " # calculate lcs\n", + " lcs = lcs_norm_word(answer_text, source_text)\n", + " lcs_values.append(lcs)\n", + " # Sets to -1 for original tasks \n", + " else:\n", + " lcs_values.append(-1)\n", + "\n", + " print('LCS features created!')\n", + " return lcs_values\n", + " " + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## EXERCISE: Create a features DataFrame by selecting an `ngram_range`\n", + "\n", + "The paper suggests calculating the following features: containment *1-gram to 5-gram* and *longest common subsequence*. \n", + "> In this exercise, you can choose to create even more features, for example from *1-gram to 7-gram* containment features and *longest common subsequence*. \n", + "\n", + "You'll want to create at least 6 features to choose from as you think about which to give to your final, classification model. Defining and comparing at least 6 different features allows you to discard any features that seem redundant, and choose to use the best features for your final model!\n", + "\n", + "In the below cell **define an n-gram range**; these will be the n's you use to create n-gram containment features. The rest of the feature creation code is provided." + ] + }, + { + "cell_type": "code", + "execution_count": 20, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "1-gram containment features created!\n", + "2-gram containment features created!\n", + "3-gram containment features created!\n", + "4-gram containment features created!\n", + "5-gram containment features created!\n", + "6-gram containment features created!\n", + "0.1917808219178082\n", + "0.8207547169811321\n", + "0.8464912280701754\n", + "0.3160621761658031\n", + "0.24257425742574257\n", + "0.16117216117216118\n", + "0.30165289256198347\n", + "0.6217105263157895\n", + "0.484304932735426\n", + "0.597457627118644\n", + "0.42783505154639173\n", + "0.2708333333333333\n", + "0.22395833333333334\n", + "0.9\n", + "0.8940397350993378\n", + "0.8232044198895028\n", + "0.775\n", + "0.45977011494252873\n", + "0.3055555555555556\n", + "0.2826086956521739\n", + "0.9930555555555556\n", + "0.7888888888888889\n", + "0.3246753246753247\n", + "0.3466666666666667\n", + "1.0\n", + "0.18932038834951456\n", + "0.36893203883495146\n", + "0.4166666666666667\n", + "0.4898785425101215\n", + "0.24742268041237114\n", + "0.21875\n", + "0.29441624365482233\n", + "0.5163934426229508\n", + "0.4725274725274725\n", + "0.6064516129032258\n", + "0.536697247706422\n", + "0.39436619718309857\n", + "0.25833333333333336\n", + "0.2789115646258503\n", + "0.3431372549019608\n", + "0.15302491103202848\n", + "0.4559386973180077\n", + "0.82\n", + "0.45\n", + "0.22935779816513763\n", + "0.16535433070866143\n", + "0.26046511627906976\n", + "0.3415841584158416\n", + "0.9294117647058824\n", + "1.0\n", + "0.6699029126213593\n", + "0.3551912568306011\n", + "0.23376623376623376\n", + "0.3492647058823529\n", + "0.3476190476190476\n", + "0.5677233429394812\n", + "0.774390243902439\n", + "0.19298245614035087\n", + "0.21818181818181817\n", + "0.26666666666666666\n", + "0.22110552763819097\n", + "0.5047169811320755\n", + "0.5585585585585585\n", + "0.9966996699669967\n", + "0.2289156626506024\n", + "0.1722488038277512\n", + "0.23684210526315788\n", + "0.29493087557603687\n", + "0.5037593984962406\n", + "0.9117647058823529\n", + "0.9923076923076923\n", + "0.2833333333333333\n", + "0.2616822429906542\n", + "0.6470588235294118\n", + "0.85\n", + "0.178743961352657\n", + "0.2350230414746544\n", + "0.6619718309859155\n", + "0.7911111111111111\n", + "0.9298245614035088\n", + "0.8546712802768166\n", + "0.2983425414364641\n", + "0.2230769230769231\n", + "0.9270833333333334\n", + "0.9098039215686274\n", + "0.4900990099009901\n", + "0.25203252032520324\n", + "0.1774193548387097\n", + "0.22767857142857142\n", + "0.6437246963562753\n", + "0.24271844660194175\n", + "0.8395061728395061\n", + "0.2830188679245283\n", + "0.16176470588235295\n", + "0.24583333333333332\n", + "LCS features created!\n", + "\n", + "Features: ['c_1', 'c_2', 'c_3', 'c_4', 'c_5', 'c_6', 'lcs_word']\n", + "\n" + ] + } + ], + "source": [ + "# Define an ngram range\n", + "ngram_range = range(1,7)\n", + "\n", + "\n", + "# The following code may take a minute to run, depending on your ngram_range\n", + "\"\"\"\n", + "DON'T MODIFY ANYTHING IN THIS CELL THAT IS BELOW THIS LINE\n", + "\"\"\"\n", + "features_list = []\n", + "\n", + "# Create features in a features_df\n", + "all_features = np.zeros((len(ngram_range)+1, len(complete_df)))\n", + "\n", + "# Calculate features for containment for ngrams in range\n", + "i=0\n", + "for n in ngram_range:\n", + " column_name = 'c_'+str(n)\n", + " features_list.append(column_name)\n", + " # create containment features\n", + " all_features[i]=np.squeeze(create_containment_features(complete_df, n))\n", + " i+=1\n", + "\n", + "# Calculate features for LCS_Norm Words \n", + "features_list.append('lcs_word')\n", + "all_features[i]= np.squeeze(create_lcs_features(complete_df))\n", + "\n", + "# create a features dataframe\n", + "features_df = pd.DataFrame(np.transpose(all_features), columns=features_list)\n", + "\n", + "# Print all features/columns\n", + "print()\n", + "print('Features: ', features_list)\n", + "print()" + ] + }, + { + "cell_type": "code", + "execution_count": 21, + "metadata": {}, + "outputs": [ + { + "data": { + "text/html": [ + "
\n", + "\n", + "\n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + "
c_1c_2c_3c_4c_5c_6lcs_word
00.3981480.0790700.0093460.0000000.0000000.0000000.191781
11.0000000.9846940.9641030.9432990.9222800.9010420.820755
20.8693690.7194570.6136360.5159820.4495410.3824880.846491
30.5935830.2688170.1567570.1086960.0819670.0604400.316062
40.5445030.1157890.0317460.0053190.0000000.0000000.242574
50.3295020.0538460.0077220.0038760.0000000.0000000.161172
60.5903080.1504420.0355560.0044640.0000000.0000000.301653
70.7653060.7098980.6643840.6254300.5896550.5536330.621711
80.7597770.5056180.3954800.3068180.2457140.1954020.484305
90.8844440.5267860.3408070.2477480.1809950.1500000.597458
\n", + "
" + ], + "text/plain": [ + " c_1 c_2 c_3 c_4 c_5 c_6 lcs_word\n", + "0 0.398148 0.079070 0.009346 0.000000 0.000000 0.000000 0.191781\n", + "1 1.000000 0.984694 0.964103 0.943299 0.922280 0.901042 0.820755\n", + "2 0.869369 0.719457 0.613636 0.515982 0.449541 0.382488 0.846491\n", + "3 0.593583 0.268817 0.156757 0.108696 0.081967 0.060440 0.316062\n", + "4 0.544503 0.115789 0.031746 0.005319 0.000000 0.000000 0.242574\n", + "5 0.329502 0.053846 0.007722 0.003876 0.000000 0.000000 0.161172\n", + "6 0.590308 0.150442 0.035556 0.004464 0.000000 0.000000 0.301653\n", + "7 0.765306 0.709898 0.664384 0.625430 0.589655 0.553633 0.621711\n", + "8 0.759777 0.505618 0.395480 0.306818 0.245714 0.195402 0.484305\n", + "9 0.884444 0.526786 0.340807 0.247748 0.180995 0.150000 0.597458" + ] + }, + "execution_count": 21, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "# print some results \n", + "features_df.head(10)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Correlated Features\n", + "\n", + "You should use feature correlation across the *entire* dataset to determine which features are ***too*** **highly-correlated** with each other to include both features in a single model. For this analysis, you can use the *entire* dataset due to the small sample size we have. \n", + "\n", + "All of our features try to measure the similarity between two texts. Since our features are designed to measure similarity, it is expected that these features will be highly-correlated. Many classification models, for example a Naive Bayes classifier, rely on the assumption that features are *not* highly correlated; highly-correlated features may over-inflate the importance of a single feature. \n", + "\n", + "So, you'll want to choose your features based on which pairings have the lowest correlation. These correlation values range between 0 and 1; from low to high correlation, and are displayed in a [correlation matrix](https://www.displayr.com/what-is-a-correlation-matrix/), below." + ] + }, + { + "cell_type": "code", + "execution_count": 22, + "metadata": {}, + "outputs": [ + { + "data": { + "text/html": [ + "
\n", + "\n", + "\n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + "
c_1c_2c_3c_4c_5c_6lcs_word
c_11.000.940.900.890.880.870.97
c_20.941.000.990.980.970.960.98
c_30.900.991.001.000.990.980.97
c_40.890.981.001.001.000.990.95
c_50.880.970.991.001.001.000.95
c_60.870.960.980.991.001.000.94
lcs_word0.970.980.970.950.950.941.00
\n", + "
" + ], + "text/plain": [ + " c_1 c_2 c_3 c_4 c_5 c_6 lcs_word\n", + "c_1 1.00 0.94 0.90 0.89 0.88 0.87 0.97\n", + "c_2 0.94 1.00 0.99 0.98 0.97 0.96 0.98\n", + "c_3 0.90 0.99 1.00 1.00 0.99 0.98 0.97\n", + "c_4 0.89 0.98 1.00 1.00 1.00 0.99 0.95\n", + "c_5 0.88 0.97 0.99 1.00 1.00 1.00 0.95\n", + "c_6 0.87 0.96 0.98 0.99 1.00 1.00 0.94\n", + "lcs_word 0.97 0.98 0.97 0.95 0.95 0.94 1.00" + ] + }, + "metadata": {}, + "output_type": "display_data" + } + ], + "source": [ + "\"\"\"\n", + "DON'T MODIFY ANYTHING IN THIS CELL THAT IS BELOW THIS LINE\n", + "\"\"\"\n", + "# Create correlation matrix for just Features to determine different models to test\n", + "corr_matrix = features_df.corr().abs().round(2)\n", + "\n", + "# display shows all of a dataframe\n", + "display(corr_matrix)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## EXERCISE: Create selected train/test data\n", + "\n", + "Complete the `train_test_data` function below. This function should take in the following parameters:\n", + "* `complete_df`: A DataFrame that contains all of our processed text data, file info, datatypes, and class labels\n", + "* `features_df`: A DataFrame of all calculated features, such as containment for ngrams, n= 1-5, and lcs values for each text file listed in the `complete_df` (this was created in the above cells)\n", + "* `selected_features`: A list of feature column names, ex. `['c_1', 'lcs_word']`, which will be used to select the final features in creating train/test sets of data.\n", + "\n", + "It should return two tuples:\n", + "* `(train_x, train_y)`, selected training features and their corresponding class labels (0/1)\n", + "* `(test_x, test_y)`, selected training features and their corresponding class labels (0/1)\n", + "\n", + "** Note: x and y should be arrays of feature values and numerical class labels, respectively; not DataFrames.**\n", + "\n", + "Looking at the above correlation matrix, you should decide on a **cutoff** correlation value, less than 1.0, to determine which sets of features are *too* highly-correlated to be included in the final training and test data. If you cannot find features that are less correlated than some cutoff value, it is suggested that you increase the number of features (longer n-grams) to choose from or use *only one or two* features in your final model to avoid introducing highly-correlated features.\n", + "\n", + "Recall that the `complete_df` has a `Datatype` column that indicates whether data should be `train` or `test` data; this should help you split the data appropriately." + ] + }, + { + "cell_type": "code", + "execution_count": 23, + "metadata": {}, + "outputs": [], + "source": [ + "# Takes in dataframes and a list of selected features (column names) \n", + "# and returns (train_x, train_y), (test_x, test_y)\n", + "def train_test_data(complete_df, features_df, selected_features):\n", + " '''Gets selected training and test features from given dataframes, and \n", + " returns tuples for training and test features and their corresponding class labels.\n", + " :param complete_df: A dataframe with all of our processed text data, datatypes, and labels\n", + " :param features_df: A dataframe of all computed, similarity features\n", + " :param selected_features: An array of selected features that correspond to certain columns in `features_df`\n", + " :return: training and test features and labels: (train_x, train_y), (test_x, test_y)'''\n", + " \n", + " merged_df = complete_df.merge(features_df, left_index=True, right_index=True)\n", + " \n", + " # get the training features\n", + " train_x = merged_df.loc[merged_df.Datatype == 'train', selected_features].values\n", + " # And training class labels (0 or 1)\n", + " train_y = merged_df.loc[merged_df.Datatype == 'train', 'Class'].values\n", + " \n", + " # get the test features and labels\n", + " test_x = merged_df.loc[merged_df.Datatype == 'test', selected_features].values\n", + " test_y = merged_df.loc[merged_df.Datatype == 'test', 'Class'].values\n", + " \n", + " return (train_x, train_y), (test_x, test_y)\n", + " " + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Test cells\n", + "\n", + "Below, test out your implementation and create the final train/test data." + ] + }, + { + "cell_type": "code", + "execution_count": 24, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Tests Passed!\n" + ] + } + ], + "source": [ + "\"\"\"\n", + "DON'T MODIFY ANYTHING IN THIS CELL THAT IS BELOW THIS LINE\n", + "\"\"\"\n", + "test_selection = list(features_df)[:2] # first couple columns as a test\n", + "# test that the correct train/test data is created\n", + "(train_x, train_y), (test_x, test_y) = train_test_data(complete_df, features_df, test_selection)\n", + "\n", + "# params: generated train/test data\n", + "tests.test_data_split(train_x, train_y, test_x, test_y)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## EXERCISE: Select \"good\" features\n", + "\n", + "If you passed the test above, you can create your own train/test data, below. \n", + "\n", + "Define a list of features you'd like to include in your final mode, `selected_features`; this is a list of the features names you want to include." + ] + }, + { + "cell_type": "code", + "execution_count": 25, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Training size: 70\n", + "Test size: 25\n", + "\n", + "Training df sample: \n", + " [[0.39814815 0. 0.19178082]\n", + " [0.86936937 0.44954128 0.84649123]\n", + " [0.59358289 0.08196721 0.31606218]\n", + " [0.54450262 0. 0.24257426]\n", + " [0.32950192 0. 0.16117216]\n", + " [0.59030837 0. 0.30165289]\n", + " [0.75977654 0.24571429 0.48430493]\n", + " [0.51612903 0. 0.27083333]\n", + " [0.44086022 0. 0.22395833]\n", + " [0.97945205 0.78873239 0.9 ]]\n" + ] + } + ], + "source": [ + "# Select your list of features, this should be column names from features_df\n", + "# ex. ['c_1', 'lcs_word']\n", + "selected_features = ['c_1', 'c_5', 'lcs_word']\n", + "\n", + "\n", + "\"\"\"\n", + "DON'T MODIFY ANYTHING IN THIS CELL THAT IS BELOW THIS LINE\n", + "\"\"\"\n", + "\n", + "(train_x, train_y), (test_x, test_y) = train_test_data(complete_df, features_df, selected_features)\n", + "\n", + "# check that division of samples seems correct\n", + "# these should add up to 95 (100 - 5 original files)\n", + "print('Training size: ', len(train_x))\n", + "print('Test size: ', len(test_x))\n", + "print()\n", + "print('Training df sample: \\n', train_x[:10])" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Question 2: How did you decide on which features to include in your final model? " + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "**Answer:** We decide features based on uniqueness and their correlation to other features. \n", + "For example, in this model, c_1 and lcs_word represent single words common and longest common subsequence respectively, which are totally unique and hence must be selected.\n", + "Now we see c_2, c_3, c_4, c_5 are highly correlated to one another. So selecting one of them should do the work. We finally settle on selecting c_5 as it is least correlated to c_1 and lcs_word, and hence more unique than other features.\n" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "---\n", + "## Creating Final Data Files\n", + "\n", + "Now, you are almost ready to move on to training a model in SageMaker!\n", + "\n", + "You'll want to access your train and test data in SageMaker and upload it to S3. In this project, SageMaker will expect the following format for your train/test data:\n", + "* Training and test data should be saved in one `.csv` file each, ex `train.csv` and `test.csv`\n", + "* These files should have class labels in the first column and features in the rest of the columns\n", + "\n", + "This format follows the practice, outlined in the [SageMaker documentation](https://docs.aws.amazon.com/sagemaker/latest/dg/cdf-training.html), which reads: \"Amazon SageMaker requires that a CSV file doesn't have a header record and that the target variable [class label] is in the first column.\"\n", + "\n", + "## EXERCISE: Create csv files\n", + "\n", + "Define a function that takes in x (features) and y (labels) and saves them to one `.csv` file at the path `data_dir/filename`.\n", + "\n", + "It may be useful to use pandas to merge your features and labels into one DataFrame and then convert that into a csv file. You can make sure to get rid of any incomplete rows, in a DataFrame, by using `dropna`." + ] + }, + { + "cell_type": "code", + "execution_count": 26, + "metadata": {}, + "outputs": [], + "source": [ + "def make_csv(x, y, filename, data_dir):\n", + " '''Merges features and labels and converts them into one csv file with labels in the first column.\n", + " :param x: Data features\n", + " :param y: Data labels\n", + " :param file_name: Name of csv file, ex. 'train.csv'\n", + " :param data_dir: The directory where files will be saved\n", + " '''\n", + " # make data dir, if it does not exist\n", + " if not os.path.exists(data_dir):\n", + " os.makedirs(data_dir)\n", + " \n", + " # your code here\n", + " df = pd.concat([pd.DataFrame(y), pd.DataFrame(x)], axis=1).dropna()\n", + " df.to_csv(os.path.join(data_dir, filename), header=False, index=False)\n", + " \n", + " # nothing is returned, but a print statement indicates that the function has run\n", + " print('Path created: '+str(data_dir)+'/'+str(filename))" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Test cells\n", + "\n", + "Test that your code produces the correct format for a `.csv` file, given some text features and labels." + ] + }, + { + "cell_type": "code", + "execution_count": 27, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Path created: test_csv/to_delete.csv\n", + "Tests passed!\n" + ] + } + ], + "source": [ + "\"\"\"\n", + "DON'T MODIFY ANYTHING IN THIS CELL THAT IS BELOW THIS LINE\n", + "\"\"\"\n", + "fake_x = [ [0.39814815, 0.0001, 0.19178082], \n", + " [0.86936937, 0.44954128, 0.84649123], \n", + " [0.44086022, 0., 0.22395833] ]\n", + "\n", + "fake_y = [0, 1, 1]\n", + "\n", + "make_csv(fake_x, fake_y, filename='to_delete.csv', data_dir='test_csv')\n", + "\n", + "# read in and test dimensions\n", + "fake_df = pd.read_csv('test_csv/to_delete.csv', header=None)\n", + "\n", + "# check shape\n", + "assert fake_df.shape==(3, 4), \\\n", + " 'The file should have as many rows as data_points and as many columns as features+1 (for indices).'\n", + "# check that first column = labels\n", + "assert np.all(fake_df.iloc[:,0].values==fake_y), 'First column is not equal to the labels, fake_y.'\n", + "print('Tests passed!')" + ] + }, + { + "cell_type": "code", + "execution_count": 28, + "metadata": {}, + "outputs": [], + "source": [ + "# delete the test csv file, generated above\n", + "! rm -rf test_csv" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "If you've passed the tests above, run the following cell to create `train.csv` and `test.csv` files in a directory that you specify! This will save the data in a local directory. Remember the name of this directory because you will reference it again when uploading this data to S3." + ] + }, + { + "cell_type": "code", + "execution_count": 29, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Path created: plagiarism_data/train.csv\n", + "Path created: plagiarism_data/test.csv\n" + ] + } + ], + "source": [ + "# can change directory, if you want\n", + "data_dir = 'plagiarism_data'\n", + "\n", + "\"\"\"\n", + "DON'T MODIFY ANYTHING IN THIS CELL THAT IS BELOW THIS LINE\n", + "\"\"\"\n", + "\n", + "make_csv(train_x, train_y, filename='train.csv', data_dir=data_dir)\n", + "make_csv(test_x, test_y, filename='test.csv', data_dir=data_dir)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Up Next\n", + "\n", + "Now that you've done some feature engineering and created some training and test data, you are ready to train and deploy a plagiarism classification model. The next notebook will utilize SageMaker resources to train and test a model that you design." + ] + } + ], + "metadata": { + "kernelspec": { + "display_name": "conda_amazonei_mxnet_p36", + "language": "python", + "name": "conda_amazonei_mxnet_p36" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.6.5" + } + }, + "nbformat": 4, + "nbformat_minor": 2 +} diff --git a/3_Training_a_Model.ipynb b/3_Training_a_Model.ipynb new file mode 100644 index 0000000..769f066 --- /dev/null +++ b/3_Training_a_Model.ipynb @@ -0,0 +1,789 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "# Plagiarism Detection Model\n", + "\n", + "Now that you've created training and test data, you are ready to define and train a model. Your goal in this notebook, will be to train a binary classification model that learns to label an answer file as either plagiarized or not, based on the features you provide the model.\n", + "\n", + "This task will be broken down into a few discrete steps:\n", + "\n", + "* Upload your data to S3.\n", + "* Define a binary classification model and a training script.\n", + "* Train your model and deploy it.\n", + "* Evaluate your deployed classifier and answer some questions about your approach.\n", + "\n", + "To complete this notebook, you'll have to complete all given exercises and answer all the questions in this notebook.\n", + "> All your tasks will be clearly labeled **EXERCISE** and questions as **QUESTION**.\n", + "\n", + "It will be up to you to explore different classification models and decide on a model that gives you the best performance for this dataset.\n", + "\n", + "---" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Load Data to S3\n", + "\n", + "In the last notebook, you should have created two files: a `training.csv` and `test.csv` file with the features and class labels for the given corpus of plagiarized/non-plagiarized text data. \n", + "\n", + ">The below cells load in some AWS SageMaker libraries and creates a default bucket. After creating this bucket, you can upload your locally stored data to S3.\n", + "\n", + "Save your train and test `.csv` feature files, locally. To do this you can run the second notebook \"2_Plagiarism_Feature_Engineering\" in SageMaker or you can manually upload your files to this notebook using the upload icon in Jupyter Lab. Then you can upload local files to S3 by using `sagemaker_session.upload_data` and pointing directly to where the training data is saved." + ] + }, + { + "cell_type": "code", + "execution_count": 1, + "metadata": {}, + "outputs": [], + "source": [ + "import pandas as pd\n", + "import boto3\n", + "import sagemaker" + ] + }, + { + "cell_type": "code", + "execution_count": 2, + "metadata": {}, + "outputs": [], + "source": [ + "\"\"\"\n", + "DON'T MODIFY ANYTHING IN THIS CELL THAT IS BELOW THIS LINE\n", + "\"\"\"\n", + "# session and role\n", + "sagemaker_session = sagemaker.Session()\n", + "role = sagemaker.get_execution_role()\n", + "\n", + "# create an S3 bucket\n", + "bucket = sagemaker_session.default_bucket()" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## EXERCISE: Upload your training data to S3\n", + "\n", + "Specify the `data_dir` where you've saved your `train.csv` file. Decide on a descriptive `prefix` that defines where your data will be uploaded in the default S3 bucket. Finally, create a pointer to your training data by calling `sagemaker_session.upload_data` and passing in the required parameters. It may help to look at the [Session documentation](https://sagemaker.readthedocs.io/en/stable/session.html#sagemaker.session.Session.upload_data) or previous SageMaker code examples.\n", + "\n", + "You are expected to upload your entire directory. Later, the training script will only access the `train.csv` file." + ] + }, + { + "cell_type": "code", + "execution_count": 3, + "metadata": {}, + "outputs": [], + "source": [ + "# should be the name of directory you created to save your features data\n", + "data_dir = 'plagiarism_data'\n", + "\n", + "# set prefix, a descriptive name for a directory \n", + "prefix = 'sagemaker/plagiarism_detector'\n", + "\n", + "# upload all data to S3\n", + "input_data = sagemaker_session.upload_data(path=data_dir, bucket=bucket, key_prefix=prefix)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Test cell\n", + "\n", + "Test that your data has been successfully uploaded. The below cell prints out the items in your S3 bucket and will throw an error if it is empty. You should see the contents of your `data_dir` and perhaps some checkpoints. If you see any other files listed, then you may have some old model files that you can delete via the S3 console (though, additional files shouldn't affect the performance of model developed in this notebook)." + ] + }, + { + "cell_type": "code", + "execution_count": 4, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "sagemaker-scikit-learn-2020-01-07-09-01-41-326/debug-output/training_job_end.ts\n", + "sagemaker-scikit-learn-2020-01-07-09-01-41-326/output/model.tar.gz\n", + "sagemaker-scikit-learn-2020-01-07-09-01-41-326/source/sourcedir.tar.gz\n", + "sagemaker/plagiarism_detector/test.csv\n", + "sagemaker/plagiarism_detector/train.csv\n", + "Test passed!\n" + ] + } + ], + "source": [ + "\"\"\"\n", + "DON'T MODIFY ANYTHING IN THIS CELL THAT IS BELOW THIS LINE\n", + "\"\"\"\n", + "# confirm that data is in S3 bucket\n", + "empty_check = []\n", + "for obj in boto3.resource('s3').Bucket(bucket).objects.all():\n", + " empty_check.append(obj.key)\n", + " print(obj.key)\n", + "\n", + "assert len(empty_check) !=0, 'S3 bucket is empty.'\n", + "print('Test passed!')" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "---\n", + "\n", + "# Modeling\n", + "\n", + "Now that you've uploaded your training data, it's time to define and train a model!\n", + "\n", + "The type of model you create is up to you. For a binary classification task, you can choose to go one of three routes:\n", + "* Use a built-in classification algorithm, like LinearLearner.\n", + "* Define a custom Scikit-learn classifier, a comparison of models can be found [here](https://scikit-learn.org/stable/auto_examples/classification/plot_classifier_comparison.html).\n", + "* Define a custom PyTorch neural network classifier. \n", + "\n", + "It will be up to you to test out a variety of models and choose the best one. Your project will be graded on the accuracy of your final model. \n", + " \n", + "---\n", + "\n", + "## EXERCISE: Complete a training script \n", + "\n", + "To implement a custom classifier, you'll need to complete a `train.py` script. You've been given the folders `source_sklearn` and `source_pytorch` which hold starting code for a custom Scikit-learn model and a PyTorch model, respectively. Each directory has a `train.py` training script. To complete this project **you only need to complete one of these scripts**; the script that is responsible for training your final model.\n", + "\n", + "A typical training script:\n", + "* Loads training data from a specified directory\n", + "* Parses any training & model hyperparameters (ex. nodes in a neural network, training epochs, etc.)\n", + "* Instantiates a model of your design, with any specified hyperparams\n", + "* Trains that model \n", + "* Finally, saves the model so that it can be hosted/deployed, later\n", + "\n", + "### Defining and training a model\n", + "Much of the training script code is provided for you. Almost all of your work will be done in the `if __name__ == '__main__':` section. To complete a `train.py` file, you will:\n", + "1. Import any extra libraries you need\n", + "2. Define any additional model training hyperparameters using `parser.add_argument`\n", + "2. Define a model in the `if __name__ == '__main__':` section\n", + "3. Train the model in that same section\n", + "\n", + "Below, you can use `!pygmentize` to display an existing `train.py` file. Read through the code; all of your tasks are marked with `TODO` comments. \n", + "\n", + "**Note: If you choose to create a custom PyTorch model, you will be responsible for defining the model in the `model.py` file,** and a `predict.py` file is provided. If you choose to use Scikit-learn, you only need a `train.py` file; you may import a classifier from the `sklearn` library." + ] + }, + { + "cell_type": "code", + "execution_count": 5, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "\u001b[34mfrom\u001b[39;49;00m \u001b[04m\u001b[36m__future__\u001b[39;49;00m \u001b[34mimport\u001b[39;49;00m print_function\r\n", + "\r\n", + "\u001b[34mimport\u001b[39;49;00m \u001b[04m\u001b[36margparse\u001b[39;49;00m\r\n", + "\u001b[34mimport\u001b[39;49;00m \u001b[04m\u001b[36mos\u001b[39;49;00m\r\n", + "\u001b[34mimport\u001b[39;49;00m \u001b[04m\u001b[36mpandas\u001b[39;49;00m \u001b[34mas\u001b[39;49;00m \u001b[04m\u001b[36mpd\u001b[39;49;00m\r\n", + "\r\n", + "\u001b[34mfrom\u001b[39;49;00m \u001b[04m\u001b[36msklearn.externals\u001b[39;49;00m \u001b[34mimport\u001b[39;49;00m joblib\r\n", + "\r\n", + "\u001b[37m## TODO: Import any additional libraries you need to define a model\u001b[39;49;00m\r\n", + "\u001b[34mfrom\u001b[39;49;00m \u001b[04m\u001b[36msklearn.linear_model\u001b[39;49;00m \u001b[34mimport\u001b[39;49;00m LogisticRegression\r\n", + "\r\n", + "\u001b[37m# Provided model load function\u001b[39;49;00m\r\n", + "\u001b[34mdef\u001b[39;49;00m \u001b[32mmodel_fn\u001b[39;49;00m(model_dir):\r\n", + " \u001b[33m\"\"\"Load model from the model_dir. This is the same model that is saved\u001b[39;49;00m\r\n", + "\u001b[33m in the main if statement.\u001b[39;49;00m\r\n", + "\u001b[33m \"\"\"\u001b[39;49;00m\r\n", + " \u001b[34mprint\u001b[39;49;00m(\u001b[33m\"\u001b[39;49;00m\u001b[33mLoading model.\u001b[39;49;00m\u001b[33m\"\u001b[39;49;00m)\r\n", + " \r\n", + " \u001b[37m# load using joblib\u001b[39;49;00m\r\n", + " model = joblib.load(os.path.join(model_dir, \u001b[33m\"\u001b[39;49;00m\u001b[33mmodel.joblib\u001b[39;49;00m\u001b[33m\"\u001b[39;49;00m))\r\n", + " \u001b[34mprint\u001b[39;49;00m(\u001b[33m\"\u001b[39;49;00m\u001b[33mDone loading model.\u001b[39;49;00m\u001b[33m\"\u001b[39;49;00m)\r\n", + " \r\n", + " \u001b[34mreturn\u001b[39;49;00m model\r\n", + "\r\n", + "\r\n", + "\u001b[37m## TODO: Complete the main code\u001b[39;49;00m\r\n", + "\u001b[34mif\u001b[39;49;00m \u001b[31m__name__\u001b[39;49;00m == \u001b[33m'\u001b[39;49;00m\u001b[33m__main__\u001b[39;49;00m\u001b[33m'\u001b[39;49;00m:\r\n", + " \r\n", + " \u001b[37m# All of the model parameters and training parameters are sent as arguments\u001b[39;49;00m\r\n", + " \u001b[37m# when this script is executed, during a training job\u001b[39;49;00m\r\n", + " \r\n", + " \u001b[37m# Here we set up an argument parser to easily access the parameters\u001b[39;49;00m\r\n", + " parser = argparse.ArgumentParser()\r\n", + "\r\n", + " \u001b[37m# SageMaker parameters, like the directories for training data and saving models; set automatically\u001b[39;49;00m\r\n", + " \u001b[37m# Do not need to change\u001b[39;49;00m\r\n", + " parser.add_argument(\u001b[33m'\u001b[39;49;00m\u001b[33m--output-data-dir\u001b[39;49;00m\u001b[33m'\u001b[39;49;00m, \u001b[36mtype\u001b[39;49;00m=\u001b[36mstr\u001b[39;49;00m, default=os.environ[\u001b[33m'\u001b[39;49;00m\u001b[33mSM_OUTPUT_DATA_DIR\u001b[39;49;00m\u001b[33m'\u001b[39;49;00m])\r\n", + " parser.add_argument(\u001b[33m'\u001b[39;49;00m\u001b[33m--model-dir\u001b[39;49;00m\u001b[33m'\u001b[39;49;00m, \u001b[36mtype\u001b[39;49;00m=\u001b[36mstr\u001b[39;49;00m, default=os.environ[\u001b[33m'\u001b[39;49;00m\u001b[33mSM_MODEL_DIR\u001b[39;49;00m\u001b[33m'\u001b[39;49;00m])\r\n", + " parser.add_argument(\u001b[33m'\u001b[39;49;00m\u001b[33m--data-dir\u001b[39;49;00m\u001b[33m'\u001b[39;49;00m, \u001b[36mtype\u001b[39;49;00m=\u001b[36mstr\u001b[39;49;00m, default=os.environ[\u001b[33m'\u001b[39;49;00m\u001b[33mSM_CHANNEL_TRAIN\u001b[39;49;00m\u001b[33m'\u001b[39;49;00m])\r\n", + " \r\n", + " \u001b[37m## TODO: Add any additional arguments that you will need to pass into your model\u001b[39;49;00m\r\n", + " \r\n", + " \u001b[37m# args holds all passed-in arguments\u001b[39;49;00m\r\n", + " args = parser.parse_args()\r\n", + "\r\n", + " \u001b[37m# Read in csv training file\u001b[39;49;00m\r\n", + " training_dir = args.data_dir\r\n", + " train_data = pd.read_csv(os.path.join(training_dir, \u001b[33m\"\u001b[39;49;00m\u001b[33mtrain.csv\u001b[39;49;00m\u001b[33m\"\u001b[39;49;00m), header=\u001b[36mNone\u001b[39;49;00m, names=\u001b[36mNone\u001b[39;49;00m)\r\n", + "\r\n", + " \u001b[37m# Labels are in the first column\u001b[39;49;00m\r\n", + " train_y = train_data.iloc[:,\u001b[34m0\u001b[39;49;00m]\r\n", + " train_x = train_data.iloc[:,\u001b[34m1\u001b[39;49;00m:]\r\n", + " \r\n", + " \r\n", + " \u001b[37m## --- Your code here --- ##\u001b[39;49;00m\r\n", + " \r\n", + "\r\n", + " \u001b[37m## TODO: Define a model \u001b[39;49;00m\r\n", + " model = LogisticRegression()\r\n", + " \r\n", + " \r\n", + " \u001b[37m## TODO: Train the model\u001b[39;49;00m\r\n", + " model.fit(train_x, train_y)\r\n", + " \r\n", + " \r\n", + " \u001b[37m## --- End of your code --- ##\u001b[39;49;00m\r\n", + " \r\n", + "\r\n", + " \u001b[37m# Save the trained model\u001b[39;49;00m\r\n", + " joblib.dump(model, os.path.join(args.model_dir, \u001b[33m\"\u001b[39;49;00m\u001b[33mmodel.joblib\u001b[39;49;00m\u001b[33m\"\u001b[39;49;00m))\r\n" + ] + } + ], + "source": [ + "# directory can be changed to: source_sklearn or source_pytorch\n", + "!pygmentize source_sklearn/train.py" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Provided code\n", + "\n", + "If you read the code above, you can see that the starter code includes a few things:\n", + "* Model loading (`model_fn`) and saving code\n", + "* Getting SageMaker's default hyperparameters\n", + "* Loading the training data by name, `train.csv` and extracting the features and labels, `train_x`, and `train_y`\n", + "\n", + "If you'd like to read more about model saving with [joblib for sklearn](https://scikit-learn.org/stable/modules/model_persistence.html) or with [torch.save](https://pytorch.org/tutorials/beginner/saving_loading_models.html), click on the provided links." + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "---\n", + "# Create an Estimator\n", + "\n", + "When a custom model is constructed in SageMaker, an entry point must be specified. This is the Python file which will be executed when the model is trained; the `train.py` function you specified above. To run a custom training script in SageMaker, construct an estimator, and fill in the appropriate constructor arguments:\n", + "\n", + "* **entry_point**: The path to the Python script SageMaker runs for training and prediction.\n", + "* **source_dir**: The path to the training script directory `source_sklearn` OR `source_pytorch`.\n", + "* **entry_point**: The path to the Python script SageMaker runs for training and prediction.\n", + "* **source_dir**: The path to the training script directory `train_sklearn` OR `train_pytorch`.\n", + "* **entry_point**: The path to the Python script SageMaker runs for training.\n", + "* **source_dir**: The path to the training script directory `train_sklearn` OR `train_pytorch`.\n", + "* **role**: Role ARN, which was specified, above.\n", + "* **train_instance_count**: The number of training instances (should be left at 1).\n", + "* **train_instance_type**: The type of SageMaker instance for training. Note: Because Scikit-learn does not natively support GPU training, Sagemaker Scikit-learn does not currently support training on GPU instance types.\n", + "* **sagemaker_session**: The session used to train on Sagemaker.\n", + "* **hyperparameters** (optional): A dictionary `{'name':value, ..}` passed to the train function as hyperparameters.\n", + "\n", + "Note: For a PyTorch model, there is another optional argument **framework_version**, which you can set to the latest version of PyTorch, `1.0`.\n", + "\n", + "## EXERCISE: Define a Scikit-learn or PyTorch estimator\n", + "\n", + "To import your desired estimator, use one of the following lines:\n", + "```\n", + "from sagemaker.sklearn.estimator import SKLearn\n", + "```\n", + "```\n", + "from sagemaker.pytorch import PyTorch\n", + "```" + ] + }, + { + "cell_type": "code", + "execution_count": 6, + "metadata": {}, + "outputs": [], + "source": [ + "# your import and estimator code, here\n", + "from sagemaker.sklearn.estimator import SKLearn" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## EXERCISE: Train the estimator\n", + "\n", + "Train your estimator on the training data stored in S3. This should create a training job that you can monitor in your SageMaker console." + ] + }, + { + "cell_type": "code", + "execution_count": 7, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "CPU times: user 100 µs, sys: 8 µs, total: 108 µs\n", + "Wall time: 113 µs\n" + ] + } + ], + "source": [ + "%%time\n", + "\n", + "# Train your estimator on S3 training data\n", + "estimator = SKLearn(role=role,\n", + " sagemaker_session=sagemaker_session,\n", + " train_instance_count=1,\n", + " train_instance_type='ml.m4.xlarge',\n", + " entry_point='train.py',\n", + " source_dir='source_sklearn'\n", + " )\n" + ] + }, + { + "cell_type": "code", + "execution_count": 8, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "2020-01-07 09:09:49 Starting - Starting the training job...\n", + "2020-01-07 09:09:52 Starting - Launching requested ML instances......\n", + "2020-01-07 09:11:00 Starting - Preparing the instances for training......\n", + "2020-01-07 09:11:59 Downloading - Downloading input data...\n", + "2020-01-07 09:12:45 Training - Training image download completed. Training in progress..\u001b[34m2020-01-07 09:12:46,565 sagemaker-containers INFO Imported framework sagemaker_sklearn_container.training\u001b[0m\n", + "\u001b[34m2020-01-07 09:12:46,567 sagemaker-containers INFO No GPUs detected (normal if no gpus installed)\u001b[0m\n", + "\u001b[34m2020-01-07 09:12:46,579 sagemaker_sklearn_container.training INFO Invoking user training script.\u001b[0m\n", + "\u001b[34m2020-01-07 09:12:46,879 sagemaker-containers INFO Module train does not provide a setup.py. \u001b[0m\n", + "\u001b[34mGenerating setup.py\u001b[0m\n", + "\u001b[34m2020-01-07 09:12:46,879 sagemaker-containers INFO Generating setup.cfg\u001b[0m\n", + "\u001b[34m2020-01-07 09:12:46,879 sagemaker-containers INFO Generating MANIFEST.in\u001b[0m\n", + "\u001b[34m2020-01-07 09:12:46,879 sagemaker-containers INFO Installing module with the following command:\u001b[0m\n", + "\u001b[34m/miniconda3/bin/python -m pip install . \u001b[0m\n", + "\u001b[34mProcessing /opt/ml/code\u001b[0m\n", + "\u001b[34mBuilding wheels for collected packages: train\n", + " Building wheel for train (setup.py): started\n", + " Building wheel for train (setup.py): finished with status 'done'\n", + " Created wheel for train: filename=train-1.0.0-py2.py3-none-any.whl size=5830 sha256=89f0f979d7c997c9fa98f05ad318aa496237540267e8f3bbaa7891a289b94c0c\n", + " Stored in directory: /tmp/pip-ephem-wheel-cache-tkej929b/wheels/35/24/16/37574d11bf9bde50616c67372a334f94fa8356bc7164af8ca3\u001b[0m\n", + "\u001b[34mSuccessfully built train\u001b[0m\n", + "\u001b[34mInstalling collected packages: train\u001b[0m\n", + "\u001b[34mSuccessfully installed train-1.0.0\u001b[0m\n", + "\u001b[34m2020-01-07 09:12:48,410 sagemaker-containers INFO No GPUs detected (normal if no gpus installed)\u001b[0m\n", + "\u001b[34m2020-01-07 09:12:48,423 sagemaker-containers INFO Invoking user script\n", + "\u001b[0m\n", + "\u001b[34mTraining Env:\n", + "\u001b[0m\n", + "\u001b[34m{\n", + " \"additional_framework_parameters\": {},\n", + " \"channel_input_dirs\": {\n", + " \"train\": \"/opt/ml/input/data/train\"\n", + " },\n", + " \"current_host\": \"algo-1\",\n", + " \"framework_module\": \"sagemaker_sklearn_container.training:main\",\n", + " \"hosts\": [\n", + " \"algo-1\"\n", + " ],\n", + " \"hyperparameters\": {},\n", + " \"input_config_dir\": \"/opt/ml/input/config\",\n", + " \"input_data_config\": {\n", + " \"train\": {\n", + " \"TrainingInputMode\": \"File\",\n", + " \"S3DistributionType\": \"FullyReplicated\",\n", + " \"RecordWrapperType\": \"None\"\n", + " }\n", + " },\n", + " \"input_dir\": \"/opt/ml/input\",\n", + " \"is_master\": true,\n", + " \"job_name\": \"sagemaker-scikit-learn-2020-01-07-09-09-49-538\",\n", + " \"log_level\": 20,\n", + " \"master_hostname\": \"algo-1\",\n", + " \"model_dir\": \"/opt/ml/model\",\n", + " \"module_dir\": \"s3://sagemaker-us-east-1-309164732448/sagemaker-scikit-learn-2020-01-07-09-09-49-538/source/sourcedir.tar.gz\",\n", + " \"module_name\": \"train\",\n", + " \"network_interface_name\": \"eth0\",\n", + " \"num_cpus\": 4,\n", + " \"num_gpus\": 0,\n", + " \"output_data_dir\": \"/opt/ml/output/data\",\n", + " \"output_dir\": \"/opt/ml/output\",\n", + " \"output_intermediate_dir\": \"/opt/ml/output/intermediate\",\n", + " \"resource_config\": {\n", + " \"current_host\": \"algo-1\",\n", + " \"hosts\": [\n", + " \"algo-1\"\n", + " ],\n", + " \"network_interface_name\": \"eth0\"\n", + " },\n", + " \"user_entry_point\": \"train.py\"\u001b[0m\n", + "\u001b[34m}\n", + "\u001b[0m\n", + "\u001b[34mEnvironment variables:\n", + "\u001b[0m\n", + "\u001b[34mSM_HOSTS=[\"algo-1\"]\u001b[0m\n", + "\u001b[34mSM_NETWORK_INTERFACE_NAME=eth0\u001b[0m\n", + "\u001b[34mSM_HPS={}\u001b[0m\n", + "\u001b[34mSM_USER_ENTRY_POINT=train.py\u001b[0m\n", + "\u001b[34mSM_FRAMEWORK_PARAMS={}\u001b[0m\n", + "\u001b[34mSM_RESOURCE_CONFIG={\"current_host\":\"algo-1\",\"hosts\":[\"algo-1\"],\"network_interface_name\":\"eth0\"}\u001b[0m\n", + "\u001b[34mSM_INPUT_DATA_CONFIG={\"train\":{\"RecordWrapperType\":\"None\",\"S3DistributionType\":\"FullyReplicated\",\"TrainingInputMode\":\"File\"}}\u001b[0m\n", + "\u001b[34mSM_OUTPUT_DATA_DIR=/opt/ml/output/data\u001b[0m\n", + "\u001b[34mSM_CHANNELS=[\"train\"]\u001b[0m\n", + "\u001b[34mSM_CURRENT_HOST=algo-1\u001b[0m\n", + "\u001b[34mSM_MODULE_NAME=train\u001b[0m\n", + "\u001b[34mSM_LOG_LEVEL=20\u001b[0m\n", + "\u001b[34mSM_FRAMEWORK_MODULE=sagemaker_sklearn_container.training:main\u001b[0m\n", + "\u001b[34mSM_INPUT_DIR=/opt/ml/input\u001b[0m\n", + "\u001b[34mSM_INPUT_CONFIG_DIR=/opt/ml/input/config\u001b[0m\n", + "\u001b[34mSM_OUTPUT_DIR=/opt/ml/output\u001b[0m\n", + "\u001b[34mSM_NUM_CPUS=4\u001b[0m\n", + "\u001b[34mSM_NUM_GPUS=0\u001b[0m\n", + "\u001b[34mSM_MODEL_DIR=/opt/ml/model\u001b[0m\n", + "\u001b[34mSM_MODULE_DIR=s3://sagemaker-us-east-1-309164732448/sagemaker-scikit-learn-2020-01-07-09-09-49-538/source/sourcedir.tar.gz\u001b[0m\n", + "\u001b[34mSM_TRAINING_ENV={\"additional_framework_parameters\":{},\"channel_input_dirs\":{\"train\":\"/opt/ml/input/data/train\"},\"current_host\":\"algo-1\",\"framework_module\":\"sagemaker_sklearn_container.training:main\",\"hosts\":[\"algo-1\"],\"hyperparameters\":{},\"input_config_dir\":\"/opt/ml/input/config\",\"input_data_config\":{\"train\":{\"RecordWrapperType\":\"None\",\"S3DistributionType\":\"FullyReplicated\",\"TrainingInputMode\":\"File\"}},\"input_dir\":\"/opt/ml/input\",\"is_master\":true,\"job_name\":\"sagemaker-scikit-learn-2020-01-07-09-09-49-538\",\"log_level\":20,\"master_hostname\":\"algo-1\",\"model_dir\":\"/opt/ml/model\",\"module_dir\":\"s3://sagemaker-us-east-1-309164732448/sagemaker-scikit-learn-2020-01-07-09-09-49-538/source/sourcedir.tar.gz\",\"module_name\":\"train\",\"network_interface_name\":\"eth0\",\"num_cpus\":4,\"num_gpus\":0,\"output_data_dir\":\"/opt/ml/output/data\",\"output_dir\":\"/opt/ml/output\",\"output_intermediate_dir\":\"/opt/ml/output/intermediate\",\"resource_config\":{\"current_host\":\"algo-1\",\"hosts\":[\"algo-1\"],\"network_interface_name\":\"eth0\"},\"user_entry_point\":\"train.py\"}\u001b[0m\n", + "\u001b[34mSM_USER_ARGS=[]\u001b[0m\n", + "\u001b[34mSM_OUTPUT_INTERMEDIATE_DIR=/opt/ml/output/intermediate\u001b[0m\n", + "\u001b[34mSM_CHANNEL_TRAIN=/opt/ml/input/data/train\u001b[0m\n", + "\u001b[34mPYTHONPATH=/miniconda3/bin:/miniconda3/lib/python37.zip:/miniconda3/lib/python3.7:/miniconda3/lib/python3.7/lib-dynload:/miniconda3/lib/python3.7/site-packages\n", + "\u001b[0m\n", + "\u001b[34mInvoking script with the following command:\n", + "\u001b[0m\n", + "\u001b[34m/miniconda3/bin/python -m train\n", + "\n", + "\u001b[0m\n", + "\u001b[34m/miniconda3/lib/python3.7/site-packages/sklearn/externals/joblib/externals/cloudpickle/cloudpickle.py:47: DeprecationWarning: the imp module is deprecated in favour of importlib; see the module's documentation for alternative uses\n", + " import imp\u001b[0m\n", + "\u001b[34m/miniconda3/lib/python3.7/site-packages/sklearn/linear_model/logistic.py:432: FutureWarning: Default solver will be changed to 'lbfgs' in 0.22. Specify a solver to silence this warning.\n", + " FutureWarning)\u001b[0m\n", + "\u001b[34m2020-01-07 09:12:49,705 sagemaker-containers INFO Reporting training SUCCESS\u001b[0m\n", + "\n", + "2020-01-07 09:13:10 Uploading - Uploading generated training model\n", + "2020-01-07 09:13:10 Completed - Training job completed\n", + "Training seconds: 71\n", + "Billable seconds: 71\n" + ] + } + ], + "source": [ + "estimator.fit({'train': input_data})" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## EXERCISE: Deploy the trained model\n", + "\n", + "After training, deploy your model to create a `predictor`. If you're using a PyTorch model, you'll need to create a trained `PyTorchModel` that accepts the trained `.model_data` as an input parameter and points to the provided `source_pytorch/predict.py` file as an entry point. \n", + "\n", + "To deploy a trained model, you'll use `.deploy`, which takes in two arguments:\n", + "* **initial_instance_count**: The number of deployed instances (1).\n", + "* **instance_type**: The type of SageMaker instance for deployment.\n", + "\n", + "Note: If you run into an instance error, it may be because you chose the wrong training or deployment instance_type. It may help to refer to your previous exercise code to see which types of instances we used." + ] + }, + { + "cell_type": "code", + "execution_count": 9, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "---------------------------------------------------------------------------------------------------!CPU times: user 520 ms, sys: 19.4 ms, total: 540 ms\n", + "Wall time: 8min 20s\n" + ] + } + ], + "source": [ + "%%time\n", + "\n", + "# uncomment, if needed\n", + "# from sagemaker.pytorch import PyTorchModel\n", + "\n", + "\n", + "# deploy your model to create a predictor\n", + "predictor = estimator.deploy(initial_instance_count=1, instance_type='ml.m4.xlarge')" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "---\n", + "# Evaluating Your Model\n", + "\n", + "Once your model is deployed, you can see how it performs when applied to our test data.\n", + "\n", + "The provided cell below, reads in the test data, assuming it is stored locally in `data_dir` and named `test.csv`. The labels and features are extracted from the `.csv` file." + ] + }, + { + "cell_type": "code", + "execution_count": 10, + "metadata": {}, + "outputs": [], + "source": [ + "\"\"\"\n", + "DON'T MODIFY ANYTHING IN THIS CELL THAT IS BELOW THIS LINE\n", + "\"\"\"\n", + "import os\n", + "\n", + "# read in test data, assuming it is stored locally\n", + "test_data = pd.read_csv(os.path.join(data_dir, \"test.csv\"), header=None, names=None)\n", + "\n", + "# labels are in the first column\n", + "test_y = test_data.iloc[:,0]\n", + "test_x = test_data.iloc[:,1:]" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## EXERCISE: Determine the accuracy of your model\n", + "\n", + "Use your deployed `predictor` to generate predicted, class labels for the test data. Compare those to the *true* labels, `test_y`, and calculate the accuracy as a value between 0 and 1.0 that indicates the fraction of test data that your model classified correctly. You may use [sklearn.metrics](https://scikit-learn.org/stable/modules/classes.html#module-sklearn.metrics) for this calculation.\n", + "\n", + "**To pass this project, your model should get at least 90% test accuracy.**" + ] + }, + { + "cell_type": "code", + "execution_count": 11, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Test passed!\n" + ] + } + ], + "source": [ + "# First: generate predicted, class labels\n", + "test_y_preds = predictor.predict(test_x)\n", + "\n", + "\n", + "\"\"\"\n", + "DON'T MODIFY ANYTHING IN THIS CELL THAT IS BELOW THIS LINE\n", + "\"\"\"\n", + "# test that your model generates the correct number of labels\n", + "assert len(test_y_preds)==len(test_y), 'Unexpected number of predictions.'\n", + "print('Test passed!')" + ] + }, + { + "cell_type": "code", + "execution_count": 12, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Recall: 1.0666666666666667\n", + "Precision: 1.0\n", + "Accuracy: 0.96\n", + "\n", + "Predicted class labels: \n", + "[1 1 1 1 1 1 0 0 0 0 0 0 1 1 1 1 1 1 0 1 1 1 1 0 0]\n", + "\n", + "True class labels: \n", + "[1 1 1 1 1 1 0 0 0 0 0 0 1 1 1 1 1 1 0 1 0 1 1 0 0]\n" + ] + } + ], + "source": [ + "# Second: calculate the test accuracy\n", + "accuracy_array = test_y_preds == test_y\n", + "count = 0\n", + "for element in accuracy_array:\n", + " if element == True:\n", + " count = count + 1\n", + "\n", + "false_positives = test_y_preds - accuracy_array\n", + "false_positive_count = false_positives.where(false_positives > 0, 0 )\n", + "accuracy = count/ len(accuracy_array)\n", + " \n", + "recall = test_y_preds.sum() / test_y.sum()\n", + "print('Recall: ', recall)\n", + "precision = test_y_preds.sum() / (test_y_preds.sum() + 0)\n", + "print('Precision: ', precision)\n", + "\n", + "print('Accuracy:', accuracy)\n", + "\n", + "\n", + "## print out the array of predicted and true labels, if you want\n", + "print('\\nPredicted class labels: ')\n", + "print(test_y_preds)\n", + "print('\\nTrue class labels: ')\n", + "print(test_y.values)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Question 1: How many false positives and false negatives did your model produce, if any? And why do you think this is?" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "** Answer**: My model didn't produce any false positives, and only 1 false negative. With an astounding accuracy of 94%, I think it happened because the dataset is small and hence we don't have many outliers.\n" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Question 2: How did you decide on the type of model to use? " + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "** Answer**: The problem being a binary classification problem, with multiple numerical features, Linear Logistic Regression is one of the best models. It accepts n features and returns classification." + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "----\n", + "## EXERCISE: Clean up Resources\n", + "\n", + "After you're done evaluating your model, **delete your model endpoint**. You can do this with a call to `.delete_endpoint()`. You need to show, in this notebook, that the endpoint was deleted. Any other resources, you may delete from the AWS console, and you will find more instructions on cleaning up all your resources, below." + ] + }, + { + "cell_type": "code", + "execution_count": 13, + "metadata": {}, + "outputs": [], + "source": [ + "# uncomment and fill in the line below!\n", + "predictor.delete_endpoint()\n" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Deleting S3 bucket\n", + "\n", + "When you are *completely* done with training and testing models, you can also delete your entire S3 bucket. If you do this before you are done training your model, you'll have to recreate your S3 bucket and upload your training data again." + ] + }, + { + "cell_type": "code", + "execution_count": 14, + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "[{'ResponseMetadata': {'RequestId': 'FF46362CF1FCE014',\n", + " 'HostId': 'CpqWvNa0rhrelGsFOAYQFukvUQ8xvAhiB9EB24l472MTvPhr1ykHsAX/Gr8a+lSIjlL1Jfoz9fg=',\n", + " 'HTTPStatusCode': 200,\n", + " 'HTTPHeaders': {'x-amz-id-2': 'CpqWvNa0rhrelGsFOAYQFukvUQ8xvAhiB9EB24l472MTvPhr1ykHsAX/Gr8a+lSIjlL1Jfoz9fg=',\n", + " 'x-amz-request-id': 'FF46362CF1FCE014',\n", + " 'date': 'Tue, 07 Jan 2020 09:21:53 GMT',\n", + " 'connection': 'close',\n", + " 'content-type': 'application/xml',\n", + " 'transfer-encoding': 'chunked',\n", + " 'server': 'AmazonS3'},\n", + " 'RetryAttempts': 0},\n", + " 'Deleted': [{'Key': 'sagemaker-scikit-learn-2020-01-07-09-01-41-326/output/model.tar.gz'},\n", + " {'Key': 'sagemaker/plagiarism_detector/test.csv'},\n", + " {'Key': 'sagemaker/plagiarism_detector/train.csv'},\n", + " {'Key': 'sagemaker-scikit-learn-2020-01-07-09-09-49-538/debug-output/training_job_end.ts'},\n", + " {'Key': 'sagemaker-scikit-learn-2020-01-07-09-09-49-538/output/model.tar.gz'},\n", + " {'Key': 'sagemaker-scikit-learn-2020-01-07-09-09-49-538/source/sourcedir.tar.gz'},\n", + " {'Key': 'sagemaker-scikit-learn-2020-01-07-09-01-41-326/source/sourcedir.tar.gz'},\n", + " {'Key': 'sagemaker-scikit-learn-2020-01-07-09-01-41-326/debug-output/training_job_end.ts'}]}]" + ] + }, + "execution_count": 14, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "#deleting bucket, uncomment lines below\n", + "\n", + "bucket_to_delete = boto3.resource('s3').Bucket(bucket)\n", + "bucket_to_delete.objects.all().delete()" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Deleting all your models and instances\n", + "\n", + "When you are _completely_ done with this project and do **not** ever want to revisit this notebook, you can choose to delete all of your SageMaker notebook instances and models by following [these instructions](https://docs.aws.amazon.com/sagemaker/latest/dg/ex1-cleanup.html). Before you delete this notebook instance, I recommend at least downloading a copy and saving it, locally." + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "---\n", + "## Further Directions\n", + "\n", + "There are many ways to improve or add on to this project to expand your learning or make this more of a unique project for you. A few ideas are listed below:\n", + "* Train a classifier to predict the *category* (1-3) of plagiarism and not just plagiarized (1) or not (0).\n", + "* Utilize a different and larger dataset to see if this model can be extended to other types of plagiarism.\n", + "* Use language or character-level analysis to find different (and more) similarity features.\n", + "* Write a complete pipeline function that accepts a source text and submitted text file, and classifies the submitted text as plagiarized or not.\n", + "* Use API Gateway and a lambda function to deploy your model to a web application.\n", + "\n", + "These are all just options for extending your work. If you've completed all the exercises in this notebook, you've completed a real-world application, and can proceed to submit your project. Great job!" + ] + } + ], + "metadata": { + "kernelspec": { + "display_name": "conda_pytorch_p36", + "language": "python", + "name": "conda_pytorch_p36" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.6.5" + } + }, + "nbformat": 4, + "nbformat_minor": 2 +} diff --git a/README.md b/README.md new file mode 100644 index 0000000..c7d406f --- /dev/null +++ b/README.md @@ -0,0 +1,33 @@ +# Plagiarism Project, Machine Learning Deployment + +This repository contains code and associated files for deploying a plagiarism detector using AWS SageMaker. + +## Project Overview + +In this project, you will be tasked with building a plagiarism detector that examines a text file and performs binary classification; labeling that file as either *plagiarized* or *not*, depending on how similar that text file is to a provided source text. Detecting plagiarism is an active area of research; the task is non-trivial and the differences between paraphrased answers and original work are often not so obvious. + +This project will be broken down into three main notebooks: + +**Notebook 1: Data Exploration** +* Load in the corpus of plagiarism text data. +* Explore the existing data features and the data distribution. +* This first notebook is **not** required in your final project submission. + +**Notebook 2: Feature Engineering** + +* Clean and pre-process the text data. +* Define features for comparing the similarity of an answer text and a source text, and extract similarity features. +* Select "good" features, by analyzing the correlations between different features. +* Create train/test `.csv` files that hold the relevant features and class labels for train/test data points. + +**Notebook 3: Train and Deploy Your Model in SageMaker** + +* Upload your train/test feature data to S3. +* Define a binary classification model and a training script. +* Train your model and deploy it using SageMaker. +* Evaluate your deployed classifier. + +--- + +Please see the [README](https://github.com/udacity/ML_SageMaker_Studies/tree/master/README.md) in the root directory for instructions on setting up a SageMaker notebook and downloading the project files (as well as the other notebooks). + diff --git a/__MACOSX/._data b/__MACOSX/._data new file mode 100644 index 0000000000000000000000000000000000000000..2b46a146714c9a90e81ce92f82e0f602d4863685 GIT binary patch literal 222 zcmZQz6=P>$Vqox1Ojhs@R)|o50+1L3ClDI}@fsio@$UgK5x_AdBnYYuq+J^qI7A5ADWagzZ6zUro7#LccCL0>3C0Q6*yXWVp=cL9|7#TQc v6y@ipTDuy%7+G2xnCO}rSQ_Y>n7A0~TAI0->pD7`7#bRyyBV4|nJ@qV9epC{ literal 0 HcmV?d00001 diff --git a/__MACOSX/data/._.DS_Store b/__MACOSX/data/._.DS_Store new file mode 100644 index 0000000000000000000000000000000000000000..09fa6bdda3a49951cf3fb7aa68796ee7d5c71310 GIT binary patch literal 120 zcmZQz6=P>$Vqox1Ojhs@R)|o50+1L3ClDI}u^SMB_!U6R09PRZ;$Z`hAt6CfagYwM HE_8JOM2!ci literal 0 HcmV?d00001 diff --git a/__MACOSX/data/._file_information.csv b/__MACOSX/data/._file_information.csv new file mode 100644 index 0000000000000000000000000000000000000000..bd2b377f3aad15125a6630a268c3d6d4276ac678 GIT binary patch literal 177 zcmZQz6=P>$Vqox1Ojhs@R)|o50+1L3ClDI}aXk=&_#1&30R#|CRF-2%NDx#6q+$Vqox1Ojhs@R)|o50+1L3ClDI}@fsio@$UgK5x_AdBnYYuq+J^qI7A5ADWagzZ6zUro7#LccCL0>3C0Q6*yXWVp=cL9|7#TQc v6y@ipTDuy%7+G2xnCO}rSQ_Y>n7A0~TAI0->pD7`7#bRyyBV4|nJ@qV9epC{ literal 0 HcmV?d00001 diff --git a/__MACOSX/data/._g0pA_taskb.txt b/__MACOSX/data/._g0pA_taskb.txt new file mode 100644 index 0000000000000000000000000000000000000000..2b46a146714c9a90e81ce92f82e0f602d4863685 GIT binary patch literal 222 zcmZQz6=P>$Vqox1Ojhs@R)|o50+1L3ClDI}@fsio@$UgK5x_AdBnYYuq+J^qI7A5ADWagzZ6zUro7#LccCL0>3C0Q6*yXWVp=cL9|7#TQc v6y@ipTDuy%7+G2xnCO}rSQ_Y>n7A0~TAI0->pD7`7#bRyyBV4|nJ@qV9epC{ literal 0 HcmV?d00001 diff --git a/__MACOSX/data/._g0pA_taskc.txt b/__MACOSX/data/._g0pA_taskc.txt new file mode 100644 index 0000000000000000000000000000000000000000..2b46a146714c9a90e81ce92f82e0f602d4863685 GIT binary patch literal 222 zcmZQz6=P>$Vqox1Ojhs@R)|o50+1L3ClDI}@fsio@$UgK5x_AdBnYYuq+J^qI7A5ADWagzZ6zUro7#LccCL0>3C0Q6*yXWVp=cL9|7#TQc v6y@ipTDuy%7+G2xnCO}rSQ_Y>n7A0~TAI0->pD7`7#bRyyBV4|nJ@qV9epC{ literal 0 HcmV?d00001 diff --git a/__MACOSX/data/._g0pA_taskd.txt b/__MACOSX/data/._g0pA_taskd.txt new file mode 100644 index 0000000000000000000000000000000000000000..2b46a146714c9a90e81ce92f82e0f602d4863685 GIT binary patch literal 222 zcmZQz6=P>$Vqox1Ojhs@R)|o50+1L3ClDI}@fsio@$UgK5x_AdBnYYuq+J^qI7A5ADWagzZ6zUro7#LccCL0>3C0Q6*yXWVp=cL9|7#TQc v6y@ipTDuy%7+G2xnCO}rSQ_Y>n7A0~TAI0->pD7`7#bRyyBV4|nJ@qV9epC{ literal 0 HcmV?d00001 diff --git a/__MACOSX/data/._g0pA_taske.txt b/__MACOSX/data/._g0pA_taske.txt new file mode 100644 index 0000000000000000000000000000000000000000..2b46a146714c9a90e81ce92f82e0f602d4863685 GIT binary patch literal 222 zcmZQz6=P>$Vqox1Ojhs@R)|o50+1L3ClDI}@fsio@$UgK5x_AdBnYYuq+J^qI7A5ADWagzZ6zUro7#LccCL0>3C0Q6*yXWVp=cL9|7#TQc v6y@ipTDuy%7+G2xnCO}rSQ_Y>n7A0~TAI0->pD7`7#bRyyBV4|nJ@qV9epC{ literal 0 HcmV?d00001 diff --git a/__MACOSX/data/._g0pB_taska.txt b/__MACOSX/data/._g0pB_taska.txt new file mode 100644 index 0000000000000000000000000000000000000000..2b46a146714c9a90e81ce92f82e0f602d4863685 GIT binary patch literal 222 zcmZQz6=P>$Vqox1Ojhs@R)|o50+1L3ClDI}@fsio@$UgK5x_AdBnYYuq+J^qI7A5ADWagzZ6zUro7#LccCL0>3C0Q6*yXWVp=cL9|7#TQc v6y@ipTDuy%7+G2xnCO}rSQ_Y>n7A0~TAI0->pD7`7#bRyyBV4|nJ@qV9epC{ literal 0 HcmV?d00001 diff --git a/__MACOSX/data/._g0pB_taskb.txt b/__MACOSX/data/._g0pB_taskb.txt new file mode 100644 index 0000000000000000000000000000000000000000..2b46a146714c9a90e81ce92f82e0f602d4863685 GIT binary patch literal 222 zcmZQz6=P>$Vqox1Ojhs@R)|o50+1L3ClDI}@fsio@$UgK5x_AdBnYYuq+J^qI7A5ADWagzZ6zUro7#LccCL0>3C0Q6*yXWVp=cL9|7#TQc v6y@ipTDuy%7+G2xnCO}rSQ_Y>n7A0~TAI0->pD7`7#bRyyBV4|nJ@qV9epC{ literal 0 HcmV?d00001 diff --git a/__MACOSX/data/._g0pB_taskc.txt b/__MACOSX/data/._g0pB_taskc.txt new file mode 100644 index 0000000000000000000000000000000000000000..2b46a146714c9a90e81ce92f82e0f602d4863685 GIT binary patch literal 222 zcmZQz6=P>$Vqox1Ojhs@R)|o50+1L3ClDI}@fsio@$UgK5x_AdBnYYuq+J^qI7A5ADWagzZ6zUro7#LccCL0>3C0Q6*yXWVp=cL9|7#TQc v6y@ipTDuy%7+G2xnCO}rSQ_Y>n7A0~TAI0->pD7`7#bRyyBV4|nJ@qV9epC{ literal 0 HcmV?d00001 diff --git a/__MACOSX/data/._g0pB_taskd.txt b/__MACOSX/data/._g0pB_taskd.txt new file mode 100644 index 0000000000000000000000000000000000000000..2b46a146714c9a90e81ce92f82e0f602d4863685 GIT binary patch literal 222 zcmZQz6=P>$Vqox1Ojhs@R)|o50+1L3ClDI}@fsio@$UgK5x_AdBnYYuq+J^qI7A5ADWagzZ6zUro7#LccCL0>3C0Q6*yXWVp=cL9|7#TQc v6y@ipTDuy%7+G2xnCO}rSQ_Y>n7A0~TAI0->pD7`7#bRyyBV4|nJ@qV9epC{ literal 0 HcmV?d00001 diff --git a/__MACOSX/data/._g0pB_taske.txt b/__MACOSX/data/._g0pB_taske.txt new file mode 100644 index 0000000000000000000000000000000000000000..2b46a146714c9a90e81ce92f82e0f602d4863685 GIT binary patch literal 222 zcmZQz6=P>$Vqox1Ojhs@R)|o50+1L3ClDI}@fsio@$UgK5x_AdBnYYuq+J^qI7A5ADWagzZ6zUro7#LccCL0>3C0Q6*yXWVp=cL9|7#TQc v6y@ipTDuy%7+G2xnCO}rSQ_Y>n7A0~TAI0->pD7`7#bRyyBV4|nJ@qV9epC{ literal 0 HcmV?d00001 diff --git a/__MACOSX/data/._g0pC_taska.txt b/__MACOSX/data/._g0pC_taska.txt new file mode 100644 index 0000000000000000000000000000000000000000..2b46a146714c9a90e81ce92f82e0f602d4863685 GIT binary patch literal 222 zcmZQz6=P>$Vqox1Ojhs@R)|o50+1L3ClDI}@fsio@$UgK5x_AdBnYYuq+J^qI7A5ADWagzZ6zUro7#LccCL0>3C0Q6*yXWVp=cL9|7#TQc v6y@ipTDuy%7+G2xnCO}rSQ_Y>n7A0~TAI0->pD7`7#bRyyBV4|nJ@qV9epC{ literal 0 HcmV?d00001 diff --git a/__MACOSX/data/._g0pC_taskb.txt b/__MACOSX/data/._g0pC_taskb.txt new file mode 100644 index 0000000000000000000000000000000000000000..2b46a146714c9a90e81ce92f82e0f602d4863685 GIT binary patch literal 222 zcmZQz6=P>$Vqox1Ojhs@R)|o50+1L3ClDI}@fsio@$UgK5x_AdBnYYuq+J^qI7A5ADWagzZ6zUro7#LccCL0>3C0Q6*yXWVp=cL9|7#TQc v6y@ipTDuy%7+G2xnCO}rSQ_Y>n7A0~TAI0->pD7`7#bRyyBV4|nJ@qV9epC{ literal 0 HcmV?d00001 diff --git a/__MACOSX/data/._g0pC_taskc.txt b/__MACOSX/data/._g0pC_taskc.txt new file mode 100644 index 0000000000000000000000000000000000000000..2b46a146714c9a90e81ce92f82e0f602d4863685 GIT binary patch literal 222 zcmZQz6=P>$Vqox1Ojhs@R)|o50+1L3ClDI}@fsio@$UgK5x_AdBnYYuq+J^qI7A5ADWagzZ6zUro7#LccCL0>3C0Q6*yXWVp=cL9|7#TQc v6y@ipTDuy%7+G2xnCO}rSQ_Y>n7A0~TAI0->pD7`7#bRyyBV4|nJ@qV9epC{ literal 0 HcmV?d00001 diff --git a/__MACOSX/data/._g0pC_taskd.txt b/__MACOSX/data/._g0pC_taskd.txt new file mode 100644 index 0000000000000000000000000000000000000000..2b46a146714c9a90e81ce92f82e0f602d4863685 GIT binary patch literal 222 zcmZQz6=P>$Vqox1Ojhs@R)|o50+1L3ClDI}@fsio@$UgK5x_AdBnYYuq+J^qI7A5ADWagzZ6zUro7#LccCL0>3C0Q6*yXWVp=cL9|7#TQc v6y@ipTDuy%7+G2xnCO}rSQ_Y>n7A0~TAI0->pD7`7#bRyyBV4|nJ@qV9epC{ literal 0 HcmV?d00001 diff --git a/__MACOSX/data/._g0pC_taske.txt b/__MACOSX/data/._g0pC_taske.txt new file mode 100644 index 0000000000000000000000000000000000000000..2b46a146714c9a90e81ce92f82e0f602d4863685 GIT binary patch literal 222 zcmZQz6=P>$Vqox1Ojhs@R)|o50+1L3ClDI}@fsio@$UgK5x_AdBnYYuq+J^qI7A5ADWagzZ6zUro7#LccCL0>3C0Q6*yXWVp=cL9|7#TQc v6y@ipTDuy%7+G2xnCO}rSQ_Y>n7A0~TAI0->pD7`7#bRyyBV4|nJ@qV9epC{ literal 0 HcmV?d00001 diff --git a/__MACOSX/data/._g0pD_taska.txt b/__MACOSX/data/._g0pD_taska.txt new file mode 100644 index 0000000000000000000000000000000000000000..2b46a146714c9a90e81ce92f82e0f602d4863685 GIT binary patch literal 222 zcmZQz6=P>$Vqox1Ojhs@R)|o50+1L3ClDI}@fsio@$UgK5x_AdBnYYuq+J^qI7A5ADWagzZ6zUro7#LccCL0>3C0Q6*yXWVp=cL9|7#TQc v6y@ipTDuy%7+G2xnCO}rSQ_Y>n7A0~TAI0->pD7`7#bRyyBV4|nJ@qV9epC{ literal 0 HcmV?d00001 diff --git a/__MACOSX/data/._g0pD_taskb.txt b/__MACOSX/data/._g0pD_taskb.txt new file mode 100644 index 0000000000000000000000000000000000000000..2b46a146714c9a90e81ce92f82e0f602d4863685 GIT binary patch literal 222 zcmZQz6=P>$Vqox1Ojhs@R)|o50+1L3ClDI}@fsio@$UgK5x_AdBnYYuq+J^qI7A5ADWagzZ6zUro7#LccCL0>3C0Q6*yXWVp=cL9|7#TQc v6y@ipTDuy%7+G2xnCO}rSQ_Y>n7A0~TAI0->pD7`7#bRyyBV4|nJ@qV9epC{ literal 0 HcmV?d00001 diff --git a/__MACOSX/data/._g0pD_taskc.txt b/__MACOSX/data/._g0pD_taskc.txt new file mode 100644 index 0000000000000000000000000000000000000000..2b46a146714c9a90e81ce92f82e0f602d4863685 GIT binary patch literal 222 zcmZQz6=P>$Vqox1Ojhs@R)|o50+1L3ClDI}@fsio@$UgK5x_AdBnYYuq+J^qI7A5ADWagzZ6zUro7#LccCL0>3C0Q6*yXWVp=cL9|7#TQc v6y@ipTDuy%7+G2xnCO}rSQ_Y>n7A0~TAI0->pD7`7#bRyyBV4|nJ@qV9epC{ literal 0 HcmV?d00001 diff --git a/__MACOSX/data/._g0pD_taskd.txt b/__MACOSX/data/._g0pD_taskd.txt new file mode 100644 index 0000000000000000000000000000000000000000..2b46a146714c9a90e81ce92f82e0f602d4863685 GIT binary patch literal 222 zcmZQz6=P>$Vqox1Ojhs@R)|o50+1L3ClDI}@fsio@$UgK5x_AdBnYYuq+J^qI7A5ADWagzZ6zUro7#LccCL0>3C0Q6*yXWVp=cL9|7#TQc v6y@ipTDuy%7+G2xnCO}rSQ_Y>n7A0~TAI0->pD7`7#bRyyBV4|nJ@qV9epC{ literal 0 HcmV?d00001 diff --git a/__MACOSX/data/._g0pD_taske.txt b/__MACOSX/data/._g0pD_taske.txt new file mode 100644 index 0000000000000000000000000000000000000000..2b46a146714c9a90e81ce92f82e0f602d4863685 GIT binary patch literal 222 zcmZQz6=P>$Vqox1Ojhs@R)|o50+1L3ClDI}@fsio@$UgK5x_AdBnYYuq+J^qI7A5ADWagzZ6zUro7#LccCL0>3C0Q6*yXWVp=cL9|7#TQc v6y@ipTDuy%7+G2xnCO}rSQ_Y>n7A0~TAI0->pD7`7#bRyyBV4|nJ@qV9epC{ literal 0 HcmV?d00001 diff --git a/__MACOSX/data/._g0pE_taska.txt b/__MACOSX/data/._g0pE_taska.txt new file mode 100644 index 0000000000000000000000000000000000000000..2b46a146714c9a90e81ce92f82e0f602d4863685 GIT binary patch literal 222 zcmZQz6=P>$Vqox1Ojhs@R)|o50+1L3ClDI}@fsio@$UgK5x_AdBnYYuq+J^qI7A5ADWagzZ6zUro7#LccCL0>3C0Q6*yXWVp=cL9|7#TQc v6y@ipTDuy%7+G2xnCO}rSQ_Y>n7A0~TAI0->pD7`7#bRyyBV4|nJ@qV9epC{ literal 0 HcmV?d00001 diff --git a/__MACOSX/data/._g0pE_taskb.txt b/__MACOSX/data/._g0pE_taskb.txt new file mode 100644 index 0000000000000000000000000000000000000000..2b46a146714c9a90e81ce92f82e0f602d4863685 GIT binary patch literal 222 zcmZQz6=P>$Vqox1Ojhs@R)|o50+1L3ClDI}@fsio@$UgK5x_AdBnYYuq+J^qI7A5ADWagzZ6zUro7#LccCL0>3C0Q6*yXWVp=cL9|7#TQc v6y@ipTDuy%7+G2xnCO}rSQ_Y>n7A0~TAI0->pD7`7#bRyyBV4|nJ@qV9epC{ literal 0 HcmV?d00001 diff --git a/__MACOSX/data/._g0pE_taskc.txt b/__MACOSX/data/._g0pE_taskc.txt new file mode 100644 index 0000000000000000000000000000000000000000..2b46a146714c9a90e81ce92f82e0f602d4863685 GIT binary patch literal 222 zcmZQz6=P>$Vqox1Ojhs@R)|o50+1L3ClDI}@fsio@$UgK5x_AdBnYYuq+J^qI7A5ADWagzZ6zUro7#LccCL0>3C0Q6*yXWVp=cL9|7#TQc v6y@ipTDuy%7+G2xnCO}rSQ_Y>n7A0~TAI0->pD7`7#bRyyBV4|nJ@qV9epC{ literal 0 HcmV?d00001 diff --git a/__MACOSX/data/._g0pE_taskd.txt b/__MACOSX/data/._g0pE_taskd.txt new file mode 100644 index 0000000000000000000000000000000000000000..2b46a146714c9a90e81ce92f82e0f602d4863685 GIT binary patch literal 222 zcmZQz6=P>$Vqox1Ojhs@R)|o50+1L3ClDI}@fsio@$UgK5x_AdBnYYuq+J^qI7A5ADWagzZ6zUro7#LccCL0>3C0Q6*yXWVp=cL9|7#TQc v6y@ipTDuy%7+G2xnCO}rSQ_Y>n7A0~TAI0->pD7`7#bRyyBV4|nJ@qV9epC{ literal 0 HcmV?d00001 diff --git a/__MACOSX/data/._g0pE_taske.txt b/__MACOSX/data/._g0pE_taske.txt new file mode 100644 index 0000000000000000000000000000000000000000..2b46a146714c9a90e81ce92f82e0f602d4863685 GIT binary patch literal 222 zcmZQz6=P>$Vqox1Ojhs@R)|o50+1L3ClDI}@fsio@$UgK5x_AdBnYYuq+J^qI7A5ADWagzZ6zUro7#LccCL0>3C0Q6*yXWVp=cL9|7#TQc v6y@ipTDuy%7+G2xnCO}rSQ_Y>n7A0~TAI0->pD7`7#bRyyBV4|nJ@qV9epC{ literal 0 HcmV?d00001 diff --git a/__MACOSX/data/._g1pA_taska.txt b/__MACOSX/data/._g1pA_taska.txt new file mode 100644 index 0000000000000000000000000000000000000000..2b46a146714c9a90e81ce92f82e0f602d4863685 GIT binary patch literal 222 zcmZQz6=P>$Vqox1Ojhs@R)|o50+1L3ClDI}@fsio@$UgK5x_AdBnYYuq+J^qI7A5ADWagzZ6zUro7#LccCL0>3C0Q6*yXWVp=cL9|7#TQc v6y@ipTDuy%7+G2xnCO}rSQ_Y>n7A0~TAI0->pD7`7#bRyyBV4|nJ@qV9epC{ literal 0 HcmV?d00001 diff --git a/__MACOSX/data/._g1pA_taskb.txt b/__MACOSX/data/._g1pA_taskb.txt new file mode 100644 index 0000000000000000000000000000000000000000..2b46a146714c9a90e81ce92f82e0f602d4863685 GIT binary patch literal 222 zcmZQz6=P>$Vqox1Ojhs@R)|o50+1L3ClDI}@fsio@$UgK5x_AdBnYYuq+J^qI7A5ADWagzZ6zUro7#LccCL0>3C0Q6*yXWVp=cL9|7#TQc v6y@ipTDuy%7+G2xnCO}rSQ_Y>n7A0~TAI0->pD7`7#bRyyBV4|nJ@qV9epC{ literal 0 HcmV?d00001 diff --git a/__MACOSX/data/._g1pA_taskc.txt b/__MACOSX/data/._g1pA_taskc.txt new file mode 100644 index 0000000000000000000000000000000000000000..2b46a146714c9a90e81ce92f82e0f602d4863685 GIT binary patch literal 222 zcmZQz6=P>$Vqox1Ojhs@R)|o50+1L3ClDI}@fsio@$UgK5x_AdBnYYuq+J^qI7A5ADWagzZ6zUro7#LccCL0>3C0Q6*yXWVp=cL9|7#TQc v6y@ipTDuy%7+G2xnCO}rSQ_Y>n7A0~TAI0->pD7`7#bRyyBV4|nJ@qV9epC{ literal 0 HcmV?d00001 diff --git a/__MACOSX/data/._g1pA_taskd.txt b/__MACOSX/data/._g1pA_taskd.txt new file mode 100644 index 0000000000000000000000000000000000000000..2b46a146714c9a90e81ce92f82e0f602d4863685 GIT binary patch literal 222 zcmZQz6=P>$Vqox1Ojhs@R)|o50+1L3ClDI}@fsio@$UgK5x_AdBnYYuq+J^qI7A5ADWagzZ6zUro7#LccCL0>3C0Q6*yXWVp=cL9|7#TQc v6y@ipTDuy%7+G2xnCO}rSQ_Y>n7A0~TAI0->pD7`7#bRyyBV4|nJ@qV9epC{ literal 0 HcmV?d00001 diff --git a/__MACOSX/data/._g1pA_taske.txt b/__MACOSX/data/._g1pA_taske.txt new file mode 100644 index 0000000000000000000000000000000000000000..2b46a146714c9a90e81ce92f82e0f602d4863685 GIT binary patch literal 222 zcmZQz6=P>$Vqox1Ojhs@R)|o50+1L3ClDI}@fsio@$UgK5x_AdBnYYuq+J^qI7A5ADWagzZ6zUro7#LccCL0>3C0Q6*yXWVp=cL9|7#TQc v6y@ipTDuy%7+G2xnCO}rSQ_Y>n7A0~TAI0->pD7`7#bRyyBV4|nJ@qV9epC{ literal 0 HcmV?d00001 diff --git a/__MACOSX/data/._g1pB_taska.txt b/__MACOSX/data/._g1pB_taska.txt new file mode 100644 index 0000000000000000000000000000000000000000..2b46a146714c9a90e81ce92f82e0f602d4863685 GIT binary patch literal 222 zcmZQz6=P>$Vqox1Ojhs@R)|o50+1L3ClDI}@fsio@$UgK5x_AdBnYYuq+J^qI7A5ADWagzZ6zUro7#LccCL0>3C0Q6*yXWVp=cL9|7#TQc v6y@ipTDuy%7+G2xnCO}rSQ_Y>n7A0~TAI0->pD7`7#bRyyBV4|nJ@qV9epC{ literal 0 HcmV?d00001 diff --git a/__MACOSX/data/._g1pB_taskb.txt b/__MACOSX/data/._g1pB_taskb.txt new file mode 100644 index 0000000000000000000000000000000000000000..2b46a146714c9a90e81ce92f82e0f602d4863685 GIT binary patch literal 222 zcmZQz6=P>$Vqox1Ojhs@R)|o50+1L3ClDI}@fsio@$UgK5x_AdBnYYuq+J^qI7A5ADWagzZ6zUro7#LccCL0>3C0Q6*yXWVp=cL9|7#TQc v6y@ipTDuy%7+G2xnCO}rSQ_Y>n7A0~TAI0->pD7`7#bRyyBV4|nJ@qV9epC{ literal 0 HcmV?d00001 diff --git a/__MACOSX/data/._g1pB_taskc.txt b/__MACOSX/data/._g1pB_taskc.txt new file mode 100644 index 0000000000000000000000000000000000000000..2b46a146714c9a90e81ce92f82e0f602d4863685 GIT binary patch literal 222 zcmZQz6=P>$Vqox1Ojhs@R)|o50+1L3ClDI}@fsio@$UgK5x_AdBnYYuq+J^qI7A5ADWagzZ6zUro7#LccCL0>3C0Q6*yXWVp=cL9|7#TQc v6y@ipTDuy%7+G2xnCO}rSQ_Y>n7A0~TAI0->pD7`7#bRyyBV4|nJ@qV9epC{ literal 0 HcmV?d00001 diff --git a/__MACOSX/data/._g1pB_taskd.txt b/__MACOSX/data/._g1pB_taskd.txt new file mode 100644 index 0000000000000000000000000000000000000000..2b46a146714c9a90e81ce92f82e0f602d4863685 GIT binary patch literal 222 zcmZQz6=P>$Vqox1Ojhs@R)|o50+1L3ClDI}@fsio@$UgK5x_AdBnYYuq+J^qI7A5ADWagzZ6zUro7#LccCL0>3C0Q6*yXWVp=cL9|7#TQc v6y@ipTDuy%7+G2xnCO}rSQ_Y>n7A0~TAI0->pD7`7#bRyyBV4|nJ@qV9epC{ literal 0 HcmV?d00001 diff --git a/__MACOSX/data/._g1pB_taske.txt b/__MACOSX/data/._g1pB_taske.txt new file mode 100644 index 0000000000000000000000000000000000000000..2b46a146714c9a90e81ce92f82e0f602d4863685 GIT binary patch literal 222 zcmZQz6=P>$Vqox1Ojhs@R)|o50+1L3ClDI}@fsio@$UgK5x_AdBnYYuq+J^qI7A5ADWagzZ6zUro7#LccCL0>3C0Q6*yXWVp=cL9|7#TQc v6y@ipTDuy%7+G2xnCO}rSQ_Y>n7A0~TAI0->pD7`7#bRyyBV4|nJ@qV9epC{ literal 0 HcmV?d00001 diff --git a/__MACOSX/data/._g1pD_taska.txt b/__MACOSX/data/._g1pD_taska.txt new file mode 100644 index 0000000000000000000000000000000000000000..2b46a146714c9a90e81ce92f82e0f602d4863685 GIT binary patch literal 222 zcmZQz6=P>$Vqox1Ojhs@R)|o50+1L3ClDI}@fsio@$UgK5x_AdBnYYuq+J^qI7A5ADWagzZ6zUro7#LccCL0>3C0Q6*yXWVp=cL9|7#TQc v6y@ipTDuy%7+G2xnCO}rSQ_Y>n7A0~TAI0->pD7`7#bRyyBV4|nJ@qV9epC{ literal 0 HcmV?d00001 diff --git a/__MACOSX/data/._g1pD_taskb.txt b/__MACOSX/data/._g1pD_taskb.txt new file mode 100644 index 0000000000000000000000000000000000000000..2b46a146714c9a90e81ce92f82e0f602d4863685 GIT binary patch literal 222 zcmZQz6=P>$Vqox1Ojhs@R)|o50+1L3ClDI}@fsio@$UgK5x_AdBnYYuq+J^qI7A5ADWagzZ6zUro7#LccCL0>3C0Q6*yXWVp=cL9|7#TQc v6y@ipTDuy%7+G2xnCO}rSQ_Y>n7A0~TAI0->pD7`7#bRyyBV4|nJ@qV9epC{ literal 0 HcmV?d00001 diff --git a/__MACOSX/data/._g1pD_taskc.txt b/__MACOSX/data/._g1pD_taskc.txt new file mode 100644 index 0000000000000000000000000000000000000000..2b46a146714c9a90e81ce92f82e0f602d4863685 GIT binary patch literal 222 zcmZQz6=P>$Vqox1Ojhs@R)|o50+1L3ClDI}@fsio@$UgK5x_AdBnYYuq+J^qI7A5ADWagzZ6zUro7#LccCL0>3C0Q6*yXWVp=cL9|7#TQc v6y@ipTDuy%7+G2xnCO}rSQ_Y>n7A0~TAI0->pD7`7#bRyyBV4|nJ@qV9epC{ literal 0 HcmV?d00001 diff --git a/__MACOSX/data/._g1pD_taskd.txt b/__MACOSX/data/._g1pD_taskd.txt new file mode 100644 index 0000000000000000000000000000000000000000..2b46a146714c9a90e81ce92f82e0f602d4863685 GIT binary patch literal 222 zcmZQz6=P>$Vqox1Ojhs@R)|o50+1L3ClDI}@fsio@$UgK5x_AdBnYYuq+J^qI7A5ADWagzZ6zUro7#LccCL0>3C0Q6*yXWVp=cL9|7#TQc v6y@ipTDuy%7+G2xnCO}rSQ_Y>n7A0~TAI0->pD7`7#bRyyBV4|nJ@qV9epC{ literal 0 HcmV?d00001 diff --git a/__MACOSX/data/._g1pD_taske.txt b/__MACOSX/data/._g1pD_taske.txt new file mode 100644 index 0000000000000000000000000000000000000000..2b46a146714c9a90e81ce92f82e0f602d4863685 GIT binary patch literal 222 zcmZQz6=P>$Vqox1Ojhs@R)|o50+1L3ClDI}@fsio@$UgK5x_AdBnYYuq+J^qI7A5ADWagzZ6zUro7#LccCL0>3C0Q6*yXWVp=cL9|7#TQc v6y@ipTDuy%7+G2xnCO}rSQ_Y>n7A0~TAI0->pD7`7#bRyyBV4|nJ@qV9epC{ literal 0 HcmV?d00001 diff --git a/__MACOSX/data/._g2pA_taska.txt b/__MACOSX/data/._g2pA_taska.txt new file mode 100644 index 0000000000000000000000000000000000000000..2b46a146714c9a90e81ce92f82e0f602d4863685 GIT binary patch literal 222 zcmZQz6=P>$Vqox1Ojhs@R)|o50+1L3ClDI}@fsio@$UgK5x_AdBnYYuq+J^qI7A5ADWagzZ6zUro7#LccCL0>3C0Q6*yXWVp=cL9|7#TQc v6y@ipTDuy%7+G2xnCO}rSQ_Y>n7A0~TAI0->pD7`7#bRyyBV4|nJ@qV9epC{ literal 0 HcmV?d00001 diff --git a/__MACOSX/data/._g2pA_taskb.txt b/__MACOSX/data/._g2pA_taskb.txt new file mode 100644 index 0000000000000000000000000000000000000000..2b46a146714c9a90e81ce92f82e0f602d4863685 GIT binary patch literal 222 zcmZQz6=P>$Vqox1Ojhs@R)|o50+1L3ClDI}@fsio@$UgK5x_AdBnYYuq+J^qI7A5ADWagzZ6zUro7#LccCL0>3C0Q6*yXWVp=cL9|7#TQc v6y@ipTDuy%7+G2xnCO}rSQ_Y>n7A0~TAI0->pD7`7#bRyyBV4|nJ@qV9epC{ literal 0 HcmV?d00001 diff --git a/__MACOSX/data/._g2pA_taskc.txt b/__MACOSX/data/._g2pA_taskc.txt new file mode 100644 index 0000000000000000000000000000000000000000..2b46a146714c9a90e81ce92f82e0f602d4863685 GIT binary patch literal 222 zcmZQz6=P>$Vqox1Ojhs@R)|o50+1L3ClDI}@fsio@$UgK5x_AdBnYYuq+J^qI7A5ADWagzZ6zUro7#LccCL0>3C0Q6*yXWVp=cL9|7#TQc v6y@ipTDuy%7+G2xnCO}rSQ_Y>n7A0~TAI0->pD7`7#bRyyBV4|nJ@qV9epC{ literal 0 HcmV?d00001 diff --git a/__MACOSX/data/._g2pA_taskd.txt b/__MACOSX/data/._g2pA_taskd.txt new file mode 100644 index 0000000000000000000000000000000000000000..2b46a146714c9a90e81ce92f82e0f602d4863685 GIT binary patch literal 222 zcmZQz6=P>$Vqox1Ojhs@R)|o50+1L3ClDI}@fsio@$UgK5x_AdBnYYuq+J^qI7A5ADWagzZ6zUro7#LccCL0>3C0Q6*yXWVp=cL9|7#TQc v6y@ipTDuy%7+G2xnCO}rSQ_Y>n7A0~TAI0->pD7`7#bRyyBV4|nJ@qV9epC{ literal 0 HcmV?d00001 diff --git a/__MACOSX/data/._g2pA_taske.txt b/__MACOSX/data/._g2pA_taske.txt new file mode 100644 index 0000000000000000000000000000000000000000..2b46a146714c9a90e81ce92f82e0f602d4863685 GIT binary patch literal 222 zcmZQz6=P>$Vqox1Ojhs@R)|o50+1L3ClDI}@fsio@$UgK5x_AdBnYYuq+J^qI7A5ADWagzZ6zUro7#LccCL0>3C0Q6*yXWVp=cL9|7#TQc v6y@ipTDuy%7+G2xnCO}rSQ_Y>n7A0~TAI0->pD7`7#bRyyBV4|nJ@qV9epC{ literal 0 HcmV?d00001 diff --git a/__MACOSX/data/._g2pB_taska.txt b/__MACOSX/data/._g2pB_taska.txt new file mode 100644 index 0000000000000000000000000000000000000000..2b46a146714c9a90e81ce92f82e0f602d4863685 GIT binary patch literal 222 zcmZQz6=P>$Vqox1Ojhs@R)|o50+1L3ClDI}@fsio@$UgK5x_AdBnYYuq+J^qI7A5ADWagzZ6zUro7#LccCL0>3C0Q6*yXWVp=cL9|7#TQc v6y@ipTDuy%7+G2xnCO}rSQ_Y>n7A0~TAI0->pD7`7#bRyyBV4|nJ@qV9epC{ literal 0 HcmV?d00001 diff --git a/__MACOSX/data/._g2pB_taskb.txt b/__MACOSX/data/._g2pB_taskb.txt new file mode 100644 index 0000000000000000000000000000000000000000..2b46a146714c9a90e81ce92f82e0f602d4863685 GIT binary patch literal 222 zcmZQz6=P>$Vqox1Ojhs@R)|o50+1L3ClDI}@fsio@$UgK5x_AdBnYYuq+J^qI7A5ADWagzZ6zUro7#LccCL0>3C0Q6*yXWVp=cL9|7#TQc v6y@ipTDuy%7+G2xnCO}rSQ_Y>n7A0~TAI0->pD7`7#bRyyBV4|nJ@qV9epC{ literal 0 HcmV?d00001 diff --git a/__MACOSX/data/._g2pB_taskc.txt b/__MACOSX/data/._g2pB_taskc.txt new file mode 100644 index 0000000000000000000000000000000000000000..2b46a146714c9a90e81ce92f82e0f602d4863685 GIT binary patch literal 222 zcmZQz6=P>$Vqox1Ojhs@R)|o50+1L3ClDI}@fsio@$UgK5x_AdBnYYuq+J^qI7A5ADWagzZ6zUro7#LccCL0>3C0Q6*yXWVp=cL9|7#TQc v6y@ipTDuy%7+G2xnCO}rSQ_Y>n7A0~TAI0->pD7`7#bRyyBV4|nJ@qV9epC{ literal 0 HcmV?d00001 diff --git a/__MACOSX/data/._g2pB_taskd.txt b/__MACOSX/data/._g2pB_taskd.txt new file mode 100644 index 0000000000000000000000000000000000000000..2b46a146714c9a90e81ce92f82e0f602d4863685 GIT binary patch literal 222 zcmZQz6=P>$Vqox1Ojhs@R)|o50+1L3ClDI}@fsio@$UgK5x_AdBnYYuq+J^qI7A5ADWagzZ6zUro7#LccCL0>3C0Q6*yXWVp=cL9|7#TQc v6y@ipTDuy%7+G2xnCO}rSQ_Y>n7A0~TAI0->pD7`7#bRyyBV4|nJ@qV9epC{ literal 0 HcmV?d00001 diff --git a/__MACOSX/data/._g2pB_taske.txt b/__MACOSX/data/._g2pB_taske.txt new file mode 100644 index 0000000000000000000000000000000000000000..2b46a146714c9a90e81ce92f82e0f602d4863685 GIT binary patch literal 222 zcmZQz6=P>$Vqox1Ojhs@R)|o50+1L3ClDI}@fsio@$UgK5x_AdBnYYuq+J^qI7A5ADWagzZ6zUro7#LccCL0>3C0Q6*yXWVp=cL9|7#TQc v6y@ipTDuy%7+G2xnCO}rSQ_Y>n7A0~TAI0->pD7`7#bRyyBV4|nJ@qV9epC{ literal 0 HcmV?d00001 diff --git a/__MACOSX/data/._g2pC_taska.txt b/__MACOSX/data/._g2pC_taska.txt new file mode 100644 index 0000000000000000000000000000000000000000..2b46a146714c9a90e81ce92f82e0f602d4863685 GIT binary patch literal 222 zcmZQz6=P>$Vqox1Ojhs@R)|o50+1L3ClDI}@fsio@$UgK5x_AdBnYYuq+J^qI7A5ADWagzZ6zUro7#LccCL0>3C0Q6*yXWVp=cL9|7#TQc v6y@ipTDuy%7+G2xnCO}rSQ_Y>n7A0~TAI0->pD7`7#bRyyBV4|nJ@qV9epC{ literal 0 HcmV?d00001 diff --git a/__MACOSX/data/._g2pC_taskb.txt b/__MACOSX/data/._g2pC_taskb.txt new file mode 100644 index 0000000000000000000000000000000000000000..2b46a146714c9a90e81ce92f82e0f602d4863685 GIT binary patch literal 222 zcmZQz6=P>$Vqox1Ojhs@R)|o50+1L3ClDI}@fsio@$UgK5x_AdBnYYuq+J^qI7A5ADWagzZ6zUro7#LccCL0>3C0Q6*yXWVp=cL9|7#TQc v6y@ipTDuy%7+G2xnCO}rSQ_Y>n7A0~TAI0->pD7`7#bRyyBV4|nJ@qV9epC{ literal 0 HcmV?d00001 diff --git a/__MACOSX/data/._g2pC_taskc.txt b/__MACOSX/data/._g2pC_taskc.txt new file mode 100644 index 0000000000000000000000000000000000000000..2b46a146714c9a90e81ce92f82e0f602d4863685 GIT binary patch literal 222 zcmZQz6=P>$Vqox1Ojhs@R)|o50+1L3ClDI}@fsio@$UgK5x_AdBnYYuq+J^qI7A5ADWagzZ6zUro7#LccCL0>3C0Q6*yXWVp=cL9|7#TQc v6y@ipTDuy%7+G2xnCO}rSQ_Y>n7A0~TAI0->pD7`7#bRyyBV4|nJ@qV9epC{ literal 0 HcmV?d00001 diff --git a/__MACOSX/data/._g2pC_taskd.txt b/__MACOSX/data/._g2pC_taskd.txt new file mode 100644 index 0000000000000000000000000000000000000000..2b46a146714c9a90e81ce92f82e0f602d4863685 GIT binary patch literal 222 zcmZQz6=P>$Vqox1Ojhs@R)|o50+1L3ClDI}@fsio@$UgK5x_AdBnYYuq+J^qI7A5ADWagzZ6zUro7#LccCL0>3C0Q6*yXWVp=cL9|7#TQc v6y@ipTDuy%7+G2xnCO}rSQ_Y>n7A0~TAI0->pD7`7#bRyyBV4|nJ@qV9epC{ literal 0 HcmV?d00001 diff --git a/__MACOSX/data/._g2pC_taske.txt b/__MACOSX/data/._g2pC_taske.txt new file mode 100644 index 0000000000000000000000000000000000000000..2b46a146714c9a90e81ce92f82e0f602d4863685 GIT binary patch literal 222 zcmZQz6=P>$Vqox1Ojhs@R)|o50+1L3ClDI}@fsio@$UgK5x_AdBnYYuq+J^qI7A5ADWagzZ6zUro7#LccCL0>3C0Q6*yXWVp=cL9|7#TQc v6y@ipTDuy%7+G2xnCO}rSQ_Y>n7A0~TAI0->pD7`7#bRyyBV4|nJ@qV9epC{ literal 0 HcmV?d00001 diff --git a/__MACOSX/data/._g2pE_taska.txt b/__MACOSX/data/._g2pE_taska.txt new file mode 100644 index 0000000000000000000000000000000000000000..2b46a146714c9a90e81ce92f82e0f602d4863685 GIT binary patch literal 222 zcmZQz6=P>$Vqox1Ojhs@R)|o50+1L3ClDI}@fsio@$UgK5x_AdBnYYuq+J^qI7A5ADWagzZ6zUro7#LccCL0>3C0Q6*yXWVp=cL9|7#TQc v6y@ipTDuy%7+G2xnCO}rSQ_Y>n7A0~TAI0->pD7`7#bRyyBV4|nJ@qV9epC{ literal 0 HcmV?d00001 diff --git a/__MACOSX/data/._g2pE_taskb.txt b/__MACOSX/data/._g2pE_taskb.txt new file mode 100644 index 0000000000000000000000000000000000000000..2b46a146714c9a90e81ce92f82e0f602d4863685 GIT binary patch literal 222 zcmZQz6=P>$Vqox1Ojhs@R)|o50+1L3ClDI}@fsio@$UgK5x_AdBnYYuq+J^qI7A5ADWagzZ6zUro7#LccCL0>3C0Q6*yXWVp=cL9|7#TQc v6y@ipTDuy%7+G2xnCO}rSQ_Y>n7A0~TAI0->pD7`7#bRyyBV4|nJ@qV9epC{ literal 0 HcmV?d00001 diff --git a/__MACOSX/data/._g2pE_taskc.txt b/__MACOSX/data/._g2pE_taskc.txt new file mode 100644 index 0000000000000000000000000000000000000000..2b46a146714c9a90e81ce92f82e0f602d4863685 GIT binary patch literal 222 zcmZQz6=P>$Vqox1Ojhs@R)|o50+1L3ClDI}@fsio@$UgK5x_AdBnYYuq+J^qI7A5ADWagzZ6zUro7#LccCL0>3C0Q6*yXWVp=cL9|7#TQc v6y@ipTDuy%7+G2xnCO}rSQ_Y>n7A0~TAI0->pD7`7#bRyyBV4|nJ@qV9epC{ literal 0 HcmV?d00001 diff --git a/__MACOSX/data/._g2pE_taskd.txt b/__MACOSX/data/._g2pE_taskd.txt new file mode 100644 index 0000000000000000000000000000000000000000..2b46a146714c9a90e81ce92f82e0f602d4863685 GIT binary patch literal 222 zcmZQz6=P>$Vqox1Ojhs@R)|o50+1L3ClDI}@fsio@$UgK5x_AdBnYYuq+J^qI7A5ADWagzZ6zUro7#LccCL0>3C0Q6*yXWVp=cL9|7#TQc v6y@ipTDuy%7+G2xnCO}rSQ_Y>n7A0~TAI0->pD7`7#bRyyBV4|nJ@qV9epC{ literal 0 HcmV?d00001 diff --git a/__MACOSX/data/._g2pE_taske.txt b/__MACOSX/data/._g2pE_taske.txt new file mode 100644 index 0000000000000000000000000000000000000000..2b46a146714c9a90e81ce92f82e0f602d4863685 GIT binary patch literal 222 zcmZQz6=P>$Vqox1Ojhs@R)|o50+1L3ClDI}@fsio@$UgK5x_AdBnYYuq+J^qI7A5ADWagzZ6zUro7#LccCL0>3C0Q6*yXWVp=cL9|7#TQc v6y@ipTDuy%7+G2xnCO}rSQ_Y>n7A0~TAI0->pD7`7#bRyyBV4|nJ@qV9epC{ literal 0 HcmV?d00001 diff --git a/__MACOSX/data/._g3pA_taska.txt b/__MACOSX/data/._g3pA_taska.txt new file mode 100644 index 0000000000000000000000000000000000000000..2b46a146714c9a90e81ce92f82e0f602d4863685 GIT binary patch literal 222 zcmZQz6=P>$Vqox1Ojhs@R)|o50+1L3ClDI}@fsio@$UgK5x_AdBnYYuq+J^qI7A5ADWagzZ6zUro7#LccCL0>3C0Q6*yXWVp=cL9|7#TQc v6y@ipTDuy%7+G2xnCO}rSQ_Y>n7A0~TAI0->pD7`7#bRyyBV4|nJ@qV9epC{ literal 0 HcmV?d00001 diff --git a/__MACOSX/data/._g3pA_taskb.txt b/__MACOSX/data/._g3pA_taskb.txt new file mode 100644 index 0000000000000000000000000000000000000000..2b46a146714c9a90e81ce92f82e0f602d4863685 GIT binary patch literal 222 zcmZQz6=P>$Vqox1Ojhs@R)|o50+1L3ClDI}@fsio@$UgK5x_AdBnYYuq+J^qI7A5ADWagzZ6zUro7#LccCL0>3C0Q6*yXWVp=cL9|7#TQc v6y@ipTDuy%7+G2xnCO}rSQ_Y>n7A0~TAI0->pD7`7#bRyyBV4|nJ@qV9epC{ literal 0 HcmV?d00001 diff --git a/__MACOSX/data/._g3pA_taskc.txt b/__MACOSX/data/._g3pA_taskc.txt new file mode 100644 index 0000000000000000000000000000000000000000..2b46a146714c9a90e81ce92f82e0f602d4863685 GIT binary patch literal 222 zcmZQz6=P>$Vqox1Ojhs@R)|o50+1L3ClDI}@fsio@$UgK5x_AdBnYYuq+J^qI7A5ADWagzZ6zUro7#LccCL0>3C0Q6*yXWVp=cL9|7#TQc v6y@ipTDuy%7+G2xnCO}rSQ_Y>n7A0~TAI0->pD7`7#bRyyBV4|nJ@qV9epC{ literal 0 HcmV?d00001 diff --git a/__MACOSX/data/._g3pA_taskd.txt b/__MACOSX/data/._g3pA_taskd.txt new file mode 100644 index 0000000000000000000000000000000000000000..2b46a146714c9a90e81ce92f82e0f602d4863685 GIT binary patch literal 222 zcmZQz6=P>$Vqox1Ojhs@R)|o50+1L3ClDI}@fsio@$UgK5x_AdBnYYuq+J^qI7A5ADWagzZ6zUro7#LccCL0>3C0Q6*yXWVp=cL9|7#TQc v6y@ipTDuy%7+G2xnCO}rSQ_Y>n7A0~TAI0->pD7`7#bRyyBV4|nJ@qV9epC{ literal 0 HcmV?d00001 diff --git a/__MACOSX/data/._g3pA_taske.txt b/__MACOSX/data/._g3pA_taske.txt new file mode 100644 index 0000000000000000000000000000000000000000..2b46a146714c9a90e81ce92f82e0f602d4863685 GIT binary patch literal 222 zcmZQz6=P>$Vqox1Ojhs@R)|o50+1L3ClDI}@fsio@$UgK5x_AdBnYYuq+J^qI7A5ADWagzZ6zUro7#LccCL0>3C0Q6*yXWVp=cL9|7#TQc v6y@ipTDuy%7+G2xnCO}rSQ_Y>n7A0~TAI0->pD7`7#bRyyBV4|nJ@qV9epC{ literal 0 HcmV?d00001 diff --git a/__MACOSX/data/._g3pB_taska.txt b/__MACOSX/data/._g3pB_taska.txt new file mode 100644 index 0000000000000000000000000000000000000000..2b46a146714c9a90e81ce92f82e0f602d4863685 GIT binary patch literal 222 zcmZQz6=P>$Vqox1Ojhs@R)|o50+1L3ClDI}@fsio@$UgK5x_AdBnYYuq+J^qI7A5ADWagzZ6zUro7#LccCL0>3C0Q6*yXWVp=cL9|7#TQc v6y@ipTDuy%7+G2xnCO}rSQ_Y>n7A0~TAI0->pD7`7#bRyyBV4|nJ@qV9epC{ literal 0 HcmV?d00001 diff --git a/__MACOSX/data/._g3pB_taskb.txt b/__MACOSX/data/._g3pB_taskb.txt new file mode 100644 index 0000000000000000000000000000000000000000..2b46a146714c9a90e81ce92f82e0f602d4863685 GIT binary patch literal 222 zcmZQz6=P>$Vqox1Ojhs@R)|o50+1L3ClDI}@fsio@$UgK5x_AdBnYYuq+J^qI7A5ADWagzZ6zUro7#LccCL0>3C0Q6*yXWVp=cL9|7#TQc v6y@ipTDuy%7+G2xnCO}rSQ_Y>n7A0~TAI0->pD7`7#bRyyBV4|nJ@qV9epC{ literal 0 HcmV?d00001 diff --git a/__MACOSX/data/._g3pB_taskc.txt b/__MACOSX/data/._g3pB_taskc.txt new file mode 100644 index 0000000000000000000000000000000000000000..2b46a146714c9a90e81ce92f82e0f602d4863685 GIT binary patch literal 222 zcmZQz6=P>$Vqox1Ojhs@R)|o50+1L3ClDI}@fsio@$UgK5x_AdBnYYuq+J^qI7A5ADWagzZ6zUro7#LccCL0>3C0Q6*yXWVp=cL9|7#TQc v6y@ipTDuy%7+G2xnCO}rSQ_Y>n7A0~TAI0->pD7`7#bRyyBV4|nJ@qV9epC{ literal 0 HcmV?d00001 diff --git a/__MACOSX/data/._g3pB_taskd.txt b/__MACOSX/data/._g3pB_taskd.txt new file mode 100644 index 0000000000000000000000000000000000000000..2b46a146714c9a90e81ce92f82e0f602d4863685 GIT binary patch literal 222 zcmZQz6=P>$Vqox1Ojhs@R)|o50+1L3ClDI}@fsio@$UgK5x_AdBnYYuq+J^qI7A5ADWagzZ6zUro7#LccCL0>3C0Q6*yXWVp=cL9|7#TQc v6y@ipTDuy%7+G2xnCO}rSQ_Y>n7A0~TAI0->pD7`7#bRyyBV4|nJ@qV9epC{ literal 0 HcmV?d00001 diff --git a/__MACOSX/data/._g3pB_taske.txt b/__MACOSX/data/._g3pB_taske.txt new file mode 100644 index 0000000000000000000000000000000000000000..2b46a146714c9a90e81ce92f82e0f602d4863685 GIT binary patch literal 222 zcmZQz6=P>$Vqox1Ojhs@R)|o50+1L3ClDI}@fsio@$UgK5x_AdBnYYuq+J^qI7A5ADWagzZ6zUro7#LccCL0>3C0Q6*yXWVp=cL9|7#TQc v6y@ipTDuy%7+G2xnCO}rSQ_Y>n7A0~TAI0->pD7`7#bRyyBV4|nJ@qV9epC{ literal 0 HcmV?d00001 diff --git a/__MACOSX/data/._g3pC_taska.txt b/__MACOSX/data/._g3pC_taska.txt new file mode 100644 index 0000000000000000000000000000000000000000..2b46a146714c9a90e81ce92f82e0f602d4863685 GIT binary patch literal 222 zcmZQz6=P>$Vqox1Ojhs@R)|o50+1L3ClDI}@fsio@$UgK5x_AdBnYYuq+J^qI7A5ADWagzZ6zUro7#LccCL0>3C0Q6*yXWVp=cL9|7#TQc v6y@ipTDuy%7+G2xnCO}rSQ_Y>n7A0~TAI0->pD7`7#bRyyBV4|nJ@qV9epC{ literal 0 HcmV?d00001 diff --git a/__MACOSX/data/._g3pC_taskb.txt b/__MACOSX/data/._g3pC_taskb.txt new file mode 100644 index 0000000000000000000000000000000000000000..2b46a146714c9a90e81ce92f82e0f602d4863685 GIT binary patch literal 222 zcmZQz6=P>$Vqox1Ojhs@R)|o50+1L3ClDI}@fsio@$UgK5x_AdBnYYuq+J^qI7A5ADWagzZ6zUro7#LccCL0>3C0Q6*yXWVp=cL9|7#TQc v6y@ipTDuy%7+G2xnCO}rSQ_Y>n7A0~TAI0->pD7`7#bRyyBV4|nJ@qV9epC{ literal 0 HcmV?d00001 diff --git a/__MACOSX/data/._g3pC_taskc.txt b/__MACOSX/data/._g3pC_taskc.txt new file mode 100644 index 0000000000000000000000000000000000000000..2b46a146714c9a90e81ce92f82e0f602d4863685 GIT binary patch literal 222 zcmZQz6=P>$Vqox1Ojhs@R)|o50+1L3ClDI}@fsio@$UgK5x_AdBnYYuq+J^qI7A5ADWagzZ6zUro7#LccCL0>3C0Q6*yXWVp=cL9|7#TQc v6y@ipTDuy%7+G2xnCO}rSQ_Y>n7A0~TAI0->pD7`7#bRyyBV4|nJ@qV9epC{ literal 0 HcmV?d00001 diff --git a/__MACOSX/data/._g3pC_taskd.txt b/__MACOSX/data/._g3pC_taskd.txt new file mode 100644 index 0000000000000000000000000000000000000000..2b46a146714c9a90e81ce92f82e0f602d4863685 GIT binary patch literal 222 zcmZQz6=P>$Vqox1Ojhs@R)|o50+1L3ClDI}@fsio@$UgK5x_AdBnYYuq+J^qI7A5ADWagzZ6zUro7#LccCL0>3C0Q6*yXWVp=cL9|7#TQc v6y@ipTDuy%7+G2xnCO}rSQ_Y>n7A0~TAI0->pD7`7#bRyyBV4|nJ@qV9epC{ literal 0 HcmV?d00001 diff --git a/__MACOSX/data/._g3pC_taske.txt b/__MACOSX/data/._g3pC_taske.txt new file mode 100644 index 0000000000000000000000000000000000000000..2b46a146714c9a90e81ce92f82e0f602d4863685 GIT binary patch literal 222 zcmZQz6=P>$Vqox1Ojhs@R)|o50+1L3ClDI}@fsio@$UgK5x_AdBnYYuq+J^qI7A5ADWagzZ6zUro7#LccCL0>3C0Q6*yXWVp=cL9|7#TQc v6y@ipTDuy%7+G2xnCO}rSQ_Y>n7A0~TAI0->pD7`7#bRyyBV4|nJ@qV9epC{ literal 0 HcmV?d00001 diff --git a/__MACOSX/data/._g4pB_taska.txt b/__MACOSX/data/._g4pB_taska.txt new file mode 100644 index 0000000000000000000000000000000000000000..2b46a146714c9a90e81ce92f82e0f602d4863685 GIT binary patch literal 222 zcmZQz6=P>$Vqox1Ojhs@R)|o50+1L3ClDI}@fsio@$UgK5x_AdBnYYuq+J^qI7A5ADWagzZ6zUro7#LccCL0>3C0Q6*yXWVp=cL9|7#TQc v6y@ipTDuy%7+G2xnCO}rSQ_Y>n7A0~TAI0->pD7`7#bRyyBV4|nJ@qV9epC{ literal 0 HcmV?d00001 diff --git a/__MACOSX/data/._g4pB_taskb.txt b/__MACOSX/data/._g4pB_taskb.txt new file mode 100644 index 0000000000000000000000000000000000000000..2b46a146714c9a90e81ce92f82e0f602d4863685 GIT binary patch literal 222 zcmZQz6=P>$Vqox1Ojhs@R)|o50+1L3ClDI}@fsio@$UgK5x_AdBnYYuq+J^qI7A5ADWagzZ6zUro7#LccCL0>3C0Q6*yXWVp=cL9|7#TQc v6y@ipTDuy%7+G2xnCO}rSQ_Y>n7A0~TAI0->pD7`7#bRyyBV4|nJ@qV9epC{ literal 0 HcmV?d00001 diff --git a/__MACOSX/data/._g4pB_taskc.txt b/__MACOSX/data/._g4pB_taskc.txt new file mode 100644 index 0000000000000000000000000000000000000000..2b46a146714c9a90e81ce92f82e0f602d4863685 GIT binary patch literal 222 zcmZQz6=P>$Vqox1Ojhs@R)|o50+1L3ClDI}@fsio@$UgK5x_AdBnYYuq+J^qI7A5ADWagzZ6zUro7#LccCL0>3C0Q6*yXWVp=cL9|7#TQc v6y@ipTDuy%7+G2xnCO}rSQ_Y>n7A0~TAI0->pD7`7#bRyyBV4|nJ@qV9epC{ literal 0 HcmV?d00001 diff --git a/__MACOSX/data/._g4pB_taskd.txt b/__MACOSX/data/._g4pB_taskd.txt new file mode 100644 index 0000000000000000000000000000000000000000..2b46a146714c9a90e81ce92f82e0f602d4863685 GIT binary patch literal 222 zcmZQz6=P>$Vqox1Ojhs@R)|o50+1L3ClDI}@fsio@$UgK5x_AdBnYYuq+J^qI7A5ADWagzZ6zUro7#LccCL0>3C0Q6*yXWVp=cL9|7#TQc v6y@ipTDuy%7+G2xnCO}rSQ_Y>n7A0~TAI0->pD7`7#bRyyBV4|nJ@qV9epC{ literal 0 HcmV?d00001 diff --git a/__MACOSX/data/._g4pB_taske.txt b/__MACOSX/data/._g4pB_taske.txt new file mode 100644 index 0000000000000000000000000000000000000000..2b46a146714c9a90e81ce92f82e0f602d4863685 GIT binary patch literal 222 zcmZQz6=P>$Vqox1Ojhs@R)|o50+1L3ClDI}@fsio@$UgK5x_AdBnYYuq+J^qI7A5ADWagzZ6zUro7#LccCL0>3C0Q6*yXWVp=cL9|7#TQc v6y@ipTDuy%7+G2xnCO}rSQ_Y>n7A0~TAI0->pD7`7#bRyyBV4|nJ@qV9epC{ literal 0 HcmV?d00001 diff --git a/__MACOSX/data/._g4pC_taska.txt b/__MACOSX/data/._g4pC_taska.txt new file mode 100644 index 0000000000000000000000000000000000000000..2b46a146714c9a90e81ce92f82e0f602d4863685 GIT binary patch literal 222 zcmZQz6=P>$Vqox1Ojhs@R)|o50+1L3ClDI}@fsio@$UgK5x_AdBnYYuq+J^qI7A5ADWagzZ6zUro7#LccCL0>3C0Q6*yXWVp=cL9|7#TQc v6y@ipTDuy%7+G2xnCO}rSQ_Y>n7A0~TAI0->pD7`7#bRyyBV4|nJ@qV9epC{ literal 0 HcmV?d00001 diff --git a/__MACOSX/data/._g4pC_taskb.txt b/__MACOSX/data/._g4pC_taskb.txt new file mode 100644 index 0000000000000000000000000000000000000000..2b46a146714c9a90e81ce92f82e0f602d4863685 GIT binary patch literal 222 zcmZQz6=P>$Vqox1Ojhs@R)|o50+1L3ClDI}@fsio@$UgK5x_AdBnYYuq+J^qI7A5ADWagzZ6zUro7#LccCL0>3C0Q6*yXWVp=cL9|7#TQc v6y@ipTDuy%7+G2xnCO}rSQ_Y>n7A0~TAI0->pD7`7#bRyyBV4|nJ@qV9epC{ literal 0 HcmV?d00001 diff --git a/__MACOSX/data/._g4pC_taskc.txt b/__MACOSX/data/._g4pC_taskc.txt new file mode 100644 index 0000000000000000000000000000000000000000..2b46a146714c9a90e81ce92f82e0f602d4863685 GIT binary patch literal 222 zcmZQz6=P>$Vqox1Ojhs@R)|o50+1L3ClDI}@fsio@$UgK5x_AdBnYYuq+J^qI7A5ADWagzZ6zUro7#LccCL0>3C0Q6*yXWVp=cL9|7#TQc v6y@ipTDuy%7+G2xnCO}rSQ_Y>n7A0~TAI0->pD7`7#bRyyBV4|nJ@qV9epC{ literal 0 HcmV?d00001 diff --git a/__MACOSX/data/._g4pC_taskd.txt b/__MACOSX/data/._g4pC_taskd.txt new file mode 100644 index 0000000000000000000000000000000000000000..2b46a146714c9a90e81ce92f82e0f602d4863685 GIT binary patch literal 222 zcmZQz6=P>$Vqox1Ojhs@R)|o50+1L3ClDI}@fsio@$UgK5x_AdBnYYuq+J^qI7A5ADWagzZ6zUro7#LccCL0>3C0Q6*yXWVp=cL9|7#TQc v6y@ipTDuy%7+G2xnCO}rSQ_Y>n7A0~TAI0->pD7`7#bRyyBV4|nJ@qV9epC{ literal 0 HcmV?d00001 diff --git a/__MACOSX/data/._g4pC_taske.txt b/__MACOSX/data/._g4pC_taske.txt new file mode 100644 index 0000000000000000000000000000000000000000..2b46a146714c9a90e81ce92f82e0f602d4863685 GIT binary patch literal 222 zcmZQz6=P>$Vqox1Ojhs@R)|o50+1L3ClDI}@fsio@$UgK5x_AdBnYYuq+J^qI7A5ADWagzZ6zUro7#LccCL0>3C0Q6*yXWVp=cL9|7#TQc v6y@ipTDuy%7+G2xnCO}rSQ_Y>n7A0~TAI0->pD7`7#bRyyBV4|nJ@qV9epC{ literal 0 HcmV?d00001 diff --git a/__MACOSX/data/._g4pD_taska.txt b/__MACOSX/data/._g4pD_taska.txt new file mode 100644 index 0000000000000000000000000000000000000000..2b46a146714c9a90e81ce92f82e0f602d4863685 GIT binary patch literal 222 zcmZQz6=P>$Vqox1Ojhs@R)|o50+1L3ClDI}@fsio@$UgK5x_AdBnYYuq+J^qI7A5ADWagzZ6zUro7#LccCL0>3C0Q6*yXWVp=cL9|7#TQc v6y@ipTDuy%7+G2xnCO}rSQ_Y>n7A0~TAI0->pD7`7#bRyyBV4|nJ@qV9epC{ literal 0 HcmV?d00001 diff --git a/__MACOSX/data/._g4pD_taskb.txt b/__MACOSX/data/._g4pD_taskb.txt new file mode 100644 index 0000000000000000000000000000000000000000..2b46a146714c9a90e81ce92f82e0f602d4863685 GIT binary patch literal 222 zcmZQz6=P>$Vqox1Ojhs@R)|o50+1L3ClDI}@fsio@$UgK5x_AdBnYYuq+J^qI7A5ADWagzZ6zUro7#LccCL0>3C0Q6*yXWVp=cL9|7#TQc v6y@ipTDuy%7+G2xnCO}rSQ_Y>n7A0~TAI0->pD7`7#bRyyBV4|nJ@qV9epC{ literal 0 HcmV?d00001 diff --git a/__MACOSX/data/._g4pD_taskc.txt b/__MACOSX/data/._g4pD_taskc.txt new file mode 100644 index 0000000000000000000000000000000000000000..2b46a146714c9a90e81ce92f82e0f602d4863685 GIT binary patch literal 222 zcmZQz6=P>$Vqox1Ojhs@R)|o50+1L3ClDI}@fsio@$UgK5x_AdBnYYuq+J^qI7A5ADWagzZ6zUro7#LccCL0>3C0Q6*yXWVp=cL9|7#TQc v6y@ipTDuy%7+G2xnCO}rSQ_Y>n7A0~TAI0->pD7`7#bRyyBV4|nJ@qV9epC{ literal 0 HcmV?d00001 diff --git a/__MACOSX/data/._g4pD_taskd.txt b/__MACOSX/data/._g4pD_taskd.txt new file mode 100644 index 0000000000000000000000000000000000000000..2b46a146714c9a90e81ce92f82e0f602d4863685 GIT binary patch literal 222 zcmZQz6=P>$Vqox1Ojhs@R)|o50+1L3ClDI}@fsio@$UgK5x_AdBnYYuq+J^qI7A5ADWagzZ6zUro7#LccCL0>3C0Q6*yXWVp=cL9|7#TQc v6y@ipTDuy%7+G2xnCO}rSQ_Y>n7A0~TAI0->pD7`7#bRyyBV4|nJ@qV9epC{ literal 0 HcmV?d00001 diff --git a/__MACOSX/data/._g4pD_taske.txt b/__MACOSX/data/._g4pD_taske.txt new file mode 100644 index 0000000000000000000000000000000000000000..2b46a146714c9a90e81ce92f82e0f602d4863685 GIT binary patch literal 222 zcmZQz6=P>$Vqox1Ojhs@R)|o50+1L3ClDI}@fsio@$UgK5x_AdBnYYuq+J^qI7A5ADWagzZ6zUro7#LccCL0>3C0Q6*yXWVp=cL9|7#TQc v6y@ipTDuy%7+G2xnCO}rSQ_Y>n7A0~TAI0->pD7`7#bRyyBV4|nJ@qV9epC{ literal 0 HcmV?d00001 diff --git a/__MACOSX/data/._g4pE_taska.txt b/__MACOSX/data/._g4pE_taska.txt new file mode 100644 index 0000000000000000000000000000000000000000..2b46a146714c9a90e81ce92f82e0f602d4863685 GIT binary patch literal 222 zcmZQz6=P>$Vqox1Ojhs@R)|o50+1L3ClDI}@fsio@$UgK5x_AdBnYYuq+J^qI7A5ADWagzZ6zUro7#LccCL0>3C0Q6*yXWVp=cL9|7#TQc v6y@ipTDuy%7+G2xnCO}rSQ_Y>n7A0~TAI0->pD7`7#bRyyBV4|nJ@qV9epC{ literal 0 HcmV?d00001 diff --git a/__MACOSX/data/._g4pE_taskb.txt b/__MACOSX/data/._g4pE_taskb.txt new file mode 100644 index 0000000000000000000000000000000000000000..2b46a146714c9a90e81ce92f82e0f602d4863685 GIT binary patch literal 222 zcmZQz6=P>$Vqox1Ojhs@R)|o50+1L3ClDI}@fsio@$UgK5x_AdBnYYuq+J^qI7A5ADWagzZ6zUro7#LccCL0>3C0Q6*yXWVp=cL9|7#TQc v6y@ipTDuy%7+G2xnCO}rSQ_Y>n7A0~TAI0->pD7`7#bRyyBV4|nJ@qV9epC{ literal 0 HcmV?d00001 diff --git a/__MACOSX/data/._g4pE_taskc.txt b/__MACOSX/data/._g4pE_taskc.txt new file mode 100644 index 0000000000000000000000000000000000000000..2b46a146714c9a90e81ce92f82e0f602d4863685 GIT binary patch literal 222 zcmZQz6=P>$Vqox1Ojhs@R)|o50+1L3ClDI}@fsio@$UgK5x_AdBnYYuq+J^qI7A5ADWagzZ6zUro7#LccCL0>3C0Q6*yXWVp=cL9|7#TQc v6y@ipTDuy%7+G2xnCO}rSQ_Y>n7A0~TAI0->pD7`7#bRyyBV4|nJ@qV9epC{ literal 0 HcmV?d00001 diff --git a/__MACOSX/data/._g4pE_taskd.txt b/__MACOSX/data/._g4pE_taskd.txt new file mode 100644 index 0000000000000000000000000000000000000000..2b46a146714c9a90e81ce92f82e0f602d4863685 GIT binary patch literal 222 zcmZQz6=P>$Vqox1Ojhs@R)|o50+1L3ClDI}@fsio@$UgK5x_AdBnYYuq+J^qI7A5ADWagzZ6zUro7#LccCL0>3C0Q6*yXWVp=cL9|7#TQc v6y@ipTDuy%7+G2xnCO}rSQ_Y>n7A0~TAI0->pD7`7#bRyyBV4|nJ@qV9epC{ literal 0 HcmV?d00001 diff --git a/__MACOSX/data/._g4pE_taske.txt b/__MACOSX/data/._g4pE_taske.txt new file mode 100644 index 0000000000000000000000000000000000000000..2b46a146714c9a90e81ce92f82e0f602d4863685 GIT binary patch literal 222 zcmZQz6=P>$Vqox1Ojhs@R)|o50+1L3ClDI}@fsio@$UgK5x_AdBnYYuq+J^qI7A5ADWagzZ6zUro7#LccCL0>3C0Q6*yXWVp=cL9|7#TQc v6y@ipTDuy%7+G2xnCO}rSQ_Y>n7A0~TAI0->pD7`7#bRyyBV4|nJ@qV9epC{ literal 0 HcmV?d00001 diff --git a/__MACOSX/data/._orig_taska.txt b/__MACOSX/data/._orig_taska.txt new file mode 100644 index 0000000000000000000000000000000000000000..409fb1ce3f09e00f9bf2e58122f65e7df693fb38 GIT binary patch literal 278 zcmZQz6=P>$Vqox1Ojhs@R)|o50+1L3ClDI}@e?2h@)^Z|G!eitBqRu;46Ne-kR67m z4P-hKhms7+RYq8ycr2Sr}Qn=jW&Aq{dVj88~MY<>#hayBfO~ mSy~#H=$aW=8t9ssxEShMnz@+kIy#yd8XB6r8JakmFaQAaD=y3c literal 0 HcmV?d00001 diff --git a/__MACOSX/data/._orig_taskc.txt b/__MACOSX/data/._orig_taskc.txt new file mode 100644 index 0000000000000000000000000000000000000000..2b46a146714c9a90e81ce92f82e0f602d4863685 GIT binary patch literal 222 zcmZQz6=P>$Vqox1Ojhs@R)|o50+1L3ClDI}@fsio@$UgK5x_AdBnYYuq+J^qI7A5ADWagzZ6zUro7#LccCL0>3C0Q6*yXWVp=cL9|7#TQc v6y@ipTDuy%7+G2xnCO}rSQ_Y>n7A0~TAI0->pD7`7#bRyyBV4|nJ@qV9epC{ literal 0 HcmV?d00001 diff --git a/__MACOSX/data/._orig_taskd.txt b/__MACOSX/data/._orig_taskd.txt new file mode 100644 index 0000000000000000000000000000000000000000..2b46a146714c9a90e81ce92f82e0f602d4863685 GIT binary patch literal 222 zcmZQz6=P>$Vqox1Ojhs@R)|o50+1L3ClDI}@fsio@$UgK5x_AdBnYYuq+J^qI7A5ADWagzZ6zUro7#LccCL0>3C0Q6*yXWVp=cL9|7#TQc v6y@ipTDuy%7+G2xnCO}rSQ_Y>n7A0~TAI0->pD7`7#bRyyBV4|nJ@qV9epC{ literal 0 HcmV?d00001 diff --git a/__MACOSX/data/._orig_taske.txt b/__MACOSX/data/._orig_taske.txt new file mode 100644 index 0000000000000000000000000000000000000000..2b46a146714c9a90e81ce92f82e0f602d4863685 GIT binary patch literal 222 zcmZQz6=P>$Vqox1Ojhs@R)|o50+1L3ClDI}@fsio@$UgK5x_AdBnYYuq+J^qI7A5ADWagzZ6zUro7#LccCL0>3C0Q6*yXWVp=cL9|7#TQc v6y@ipTDuy%7+G2xnCO}rSQ_Y>n7A0~TAI0->pD7`7#bRyyBV4|nJ@qV9epC{ literal 0 HcmV?d00001 diff --git a/__MACOSX/data/._test_info.csv b/__MACOSX/data/._test_info.csv new file mode 100644 index 0000000000000000000000000000000000000000..c9407ddf01897aa89f1820f45263d79bf6ca806f GIT binary patch literal 177 zcmZQz6=P>$Vqox1Ojhs@R)|o50+1L3ClDI}aXk=&_#1&30R#|CRF-2%NDx#6q+ zh6`u;Rdi6u3SNkk9q>2oK)fNB1(XU>+SXAjvq6{_QK34Par0;}Oq8((acZnYrC7sv zJHlM31aaCA)6rlAMzP7>$SzK-L~|5 z;2%nKT|rCm5&r4?Dh~=w2n+{oEF>?*30kEfyaON=(vfpcVQtxxORMAuO@3bDKw{St z=~6z*<{#EC>^1B0DknW?c2@zmQiN_vKx?mQLZ$ImkK=Vnv?n&kki%)$mu@%@a%be41kw>p8Y1a?Z@?<9$%~xi?@s&7is`J zHwJ(KkX*^Ci#}>CCvjd-5LfO%T}}tMZ2yxQ8x<%B4LuzV&YS6`eShZb4~YX@ptIz@NaQI02N`&LMR17X&qB2xTh%l4CS1m+!Ly#^4nP3 zZ~yQjTG+q45Zqe$^7#?>_r;F|TDmWMf8X+bzhfI4Su2f`WIHGyLZiqa8&F#8UYw}F zi;^TP)NWCSg%YCnzdh7hSLJy~%kGmawcl-PM$GW)7y2gnM-IhkX@Y*r7D9jJeuBvf z`ul8b?LGpswVX>BfH3#pWu$x7yTA4mA>1kO3?tDZu@ur2}u2{ zR~?5_dYK+Xqy+PBg5sO@ad559htOZY54+aS=_6Z18EX_|WasV5{s9W5KjHa1n@S%$ z<)bqCIaVq;(Np_$)~d>{<9Ol3qZWzB4L%3tt#F?x)ZMWOyp=W@NB&mDZWg z*YNmZPsi2Q5znf0BCV;$+eo*xf`M_7-YS)yrl!&m%$#+_H0V@h8Wow{B5-uJ6UN4) zRVmWjG^_Q)sMyq>(*ByJwdavJdt9C?h4qC##g4i{H$2O>+NjSSpS3)36-|EyX@~2} z^{_yLr#l#oI|K}(+=!vnAA(WUkSd!ipQ@dwj(?XO-tr)mqeQJxP0fk#ar~}#qjkl* F@Gnc4ceVfk literal 0 HcmV?d00001 diff --git a/__pycache__/problem_unittests.cpython-36.pyc b/__pycache__/problem_unittests.cpython-36.pyc new file mode 100644 index 0000000000000000000000000000000000000000..0045b88c8dabf5a3ec79c637511509bc3c00cf58 GIT binary patch literal 5060 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 z zWv{~vG3#!_smQU>V72Dxm>sk%^eNNI?c1Kk>|4qn%T)4!|?{a^9gnt?H@bhlb&p zX233uCzguaanbxCv?Q`@(di&&6mxNPB_riKsq)cL0f%MNG9q#M!#Rl1D9+Tg#4M4I zg?1Cw{9B^M?t*e$(;_)~aN1l5ClPsZF4x!;ZWqLarQmIms;oZVk?CzYwm9|Kw1>kp z$Zb2@a`JBW+UHcz0=lrW7r@i*oY))wwQ;9=qgzdP-+<7&r~s!96^F4j5kSWsHNo5t zi9@YBqx8KX?9X>nA=yUE-HNO+OR^_U!Hzi*=QcHlg;QluXP@bd#nYOF)626-V>5cF zNWe84049^RduWG(+xn=JV{C03ws^%V>@Zy7YKNd_J7j6=E=w3MRf_+GU4RI3{3uEc|*1Y^cduz^gNS61|M_f>~WDW?SL-qu0iYR6z98ZjDko1>O4%}bU=Nv&C( z9IfYtSTr@kTdTJSv7L%-+qTV$Q$giT_ga14 zQ~RpD&ei+d?oZDj@QmL*=eXy9%^ZxE(%D}d{*h=#**VsK`?P^TM_;lBwhK{YY1YY0=!7Bgr z6ZW_9{f{E}mz?r1f}KBxea!JAbbb_?ickwv3j0eO2*HcBBtqE>;w(P&>zH3Ka<{>@ zT*I{!%J#Bb7TYH{M6}gyd3|DD^K3Hp7A!RkTUXrQMk@DKq!rvtNa+D}5ESf)_|_D| zC~-iAZx^btqOb}noG2#y+Alb(aFqnMu#StA@XPbE^bF$ewcQA3W=KAH! zdrQlkonu3uHhBRhJDw`{iae6s$xy~u99A90J7qSV`XopfC&Z`<$YGAMSAq`k8RP3T zwh;>6mhB#E0(AeKhwf|O%jmd-x>2H&5_?^n=$;G02#ete-)N*|xeD*TBxVPBrh*mS z#)mVABCv{)B@fZH;ZMx`-?g=<(A->QfcCYqF`JY2mGoQ(0i|6ttJ)GU>JxCV%H*m} z*Zj!i_AtD72`f)-YnmpF$tg}~hFg=DpK=?B?Y>{< zXim5Vccm-SrpExzO!UYDi)0o$x_sd`X9we{_!DQwfI{wawswUwCJb+(84Qz`dt$~# zKvzOzF2xUhp2!l8;p%3leYWfHsV{18qT>0?L#6w3H@vJ#eN}lVY#c zO=s7w8gl~yBz!tw1L_6jJn_IN^=WmY>S%scuiu`s;ZJP23!!ou{9p$dPU_J&s!g7{ zANVuBDg>sXkM^nBPlPgZnPa_l1-28m79R7CTrg8W&Ubmk)x$agp zymzFXW$GJ!TR9V~_KVnhOTkKID@<{wjdPY>IV~t>x8OeF;t{w*3}4r!s5^5;H}>{& zWB0kE8Eu_h?kS)fjj# z(eMPV-$#iNoof-j4)A34i|_5R@(}3r;+_bayD)yYZ7c8;8(2cdVu#Tf8`0FH7YXa4 zk!Nvx0c{vj@eubUH+~Xn>R! zg$W7CT!9R!Iv2uyTwu~Zo<-b94qWMJ=nKqn=WsJes2%5|koo0zztjIyvPr$pDojUj4HS$F=Z9IVC<&;C*qMyMrbndgU&9RcGJ_oa`ak~>au z*S6b{WV=*NTAhTLJtNf} znezvda7j4RV6SoH^u$==1P*hPS-;W#dC(Lw`RiJgs+HnP?xD}JWEs$pcLPrK3{&w# z+<8Mc(8}M0qG2m2f;x-Uqe_pENEYS|+K$AnKb)6K#%7Qmqll8BBmnKcvoAp?V?hTv|3%ov(9+ zcywURzynH?V`Nr>U7qii;TS3ba``&4El3^4x{;}qt+T^l7sty~UV7!hV9HZfzi>r! zF&B6ZS4lvKNs((S>UNxM$K-Dtz5y}S`7NdfOUN+c(2y5mK*fYZf5+7qxi1MDO?yoF zqPHHxv2hO3j(ET|uw=6}ChtLEn-g_o)=KC6)CcO&jJ3IHc@$q@#Va^W`s6N}kXCvz z>|_(IL>4*;i)xo$Rnh2UTjch&B~w^%&ctp;I4=PI>mC^yP6?Jsq*jbqDuM`yqU$&* z(94{Pys<&sp=#c{B#pm4^rFfY$A}+xSXEM;t!JC;4Y5=6UDJ3S*5VD0Rht+1L}bDf z^dGMUvQI!@Zy%m))h~S?|23Na8+YhG1lB(aonP|nztH(`hr&1bVb;MNz-f^gfE@$< z_=l;~_kn6P8wbu}<}5z#8IE(?7uaBT8M(MfQi&@UvM-mQ)7>Gc*%1w6*y2`qXRx^) zRTpS6B!k=4ETQC%8wt|`d^$xAlcR~;yz;FsFuwoVa*j|Uw^*;cq&RB#8O~^6(g8@+ zVfqRcOHjkmkH!DU4S9IK#x5hL0IN#zRCIA(YU3W#P=lERPt;p3u$G>L&G;O3#u)wGO7fO>ym`zTT60G3mQ*rk&0RA7K2P0OJ*rnUvk3)&p1Oqzm)2CDiV#Kl<`4e zcOjQDfKc*xU!UmU*Im;_nfIH`hqJp;3=Fk8&nM3q*&76thNDr!f zu`i=qm!K}5CM%Q8kF?wpN+nMu`m$cMYBn=P%bK)2jt$u~RH!nI zJO080t6vdexn9}w)v0L-h9)Z1334tqq1)Is8nCf!yTxN0*gm&rG z5RA*8V&S6C(eRMolhQuOL#1@c*!)g@N5Fv2h^|;rlfcE)4`TEIaG+e$ z_GMWKE}GBWrZ1?W83+dgWRNPiLqSNg39dz9ZfZ}GSN@iH{<4<0}Twe%@F&88_ea$K=}FH(6i@R-ohBvZilS18Z$& z^1Ay02TpyHu1hTQ#qbX%eigfV99;=N8BYuPLz$ zgpI6$78;4oaye7_L^70s%xk58*ut7CE8Vk(7UhwEI5Uu-CwH4h@<3hXRqmyR|KyBa zF*JMra*XE4gG`evt*Y*WY41=wbyV;jq*;u(V}pZjp7&Sw;QK${=mW-kkS9K*;PMy8 z=x<5EZ$bG-Nx?6<@Ly8k{O{3<;KTS&zIyMm0JPJdep%*WjX|UF)Fao=*e|xj5WT7QnLM<=Ud{@! zjpig;`i57titw$#6RNDPV&rorIg+l{)Y=^%N;4F=bw3DFY05c4v$O z!jy4LPxQOdLW)JVa}z%$3*g!hX;rrw=e6=m7TQjZoB*+=4yq2KFktdC(xb()S#?*} zhc4cx;;geu(De8kjzs9#td21Wj2rzC)uK`X>0BlQLf23hfe_}#TE)_hyyG%vnB>s`-b><3SBA>m#3Xz}A{t-@%DsZM+jM*4^$h#RMOKxqJstG}Df9pN>H6D%`$r-5OP2c= zQq~`Z?dxm^?bzQxu^h&OOhZA&<9Im(0RuWP?7jjB-;4E!7mSmc(tPnQ6F0}}(Lv)D ztZgf6Ejy~>-`r2V9#SVn7O9=7B6TFHi4Hi39;C&%qWMNCGvapDD+HG^;+VB3b}faJ z65$g?-Pfx_F-3;_C4FO?iLW}VBBR!D=HZ(qrZF3j^8 zT^L|&ZXrj=ApzP%osFuOxJ54g^=79jJ198nr@afCc|lA&&E_`4?65%s_7lIM`o8q> zQYx|{jFg`{inphWZ{ZbKa-_s!ScJeSsiR7bR49imulKs|?xQYu@5M@FB1Vk5ZkvWg z`4fkO|BV$8*CY5SKFDqeOe|@ByG6!w9lr082x7io(6@K|^CwHj=e#~CntY&sNgCr3 zkK1q8+NKZ%uC*f-W$ZH>j~YkQpFPPMNyFM0G7WF6VU{4wW#R8-JZ|qq@{w5(pyE^l zR@Q327T^G{t%7^3pFcprZ}wQVor0m0)RlIE%sPg5I#j7}Y)n9p#&|13t$c<_SKXGq zOB9N$Oy*R}P_lm8W!P%vNAWgyDX}nH?|G|H!}EL&6a+Q35Q|Lfr@7=7A$2*dp>dY+ z>ftY7f)XWo*nz^x#Z&`)^LIVWnVvi1PN|I2!M8)GSJ)D}H(HvfG+9e8uU;c(ap-WmVV&T@5CV4+;_k{;j^#}@qm2IIuR=qqA z3S{{kEH*4{ti3_h`L}BX$LODh74WYFGbCKu`{qpSfu!VLJ znj)XyxP`qU#H1#EHuTBt`pS9NnthTo+pZ87fN!4cjIS`cLJo&24B$?ghAGeT|GrGR ze^a+nUG5|TD${PaW~(@pscLb#ih-wo#ls3W)H=_NoVAqUyHY&+aP6&`Y6|8QN}$v!xGk-ke0&AiSpUU!~OxSb%ogQDesa0rYQlH_L$kH!RNFrej@PL1h*0N%h+&H zbp0nD4B)bj(~rHHpTMdehbuwp2!{xQ>1_&fcVArJ|Mav17vp^5Ka%p~U#i*v))nEm zwEr*Cv$g-=Y5k_fUFP4Oma&p#a{5PNeuTp1y9aHQPE$Kdh8~Lzm=DgMJc0xlq%K>a`QdFp$6VSMF*WKbo;a+e5=X{|io%A2QYw=NAQES7G*)=3 zVGv7I%1wonMnm2d0or%opE;hh?tmuwVPn-d1ozn00ZSqmdFg6hGTR(cS`L|pO%ddg)_?W`X5vXdPuw&u`V}_N3h~@SztTo&nr>RD42D z5eO!6UULaM9g#Zyd^bPrJpgFc=NtGxt}QYcp%RB_ARi5w9zyUSce<(*qEC&t zI|oDB(xeaFmdc2~=m2_VlBmb!<_f);yDB(T z*JYNk=6t+|e?-0Yh9SP#Y~PsS>u(9muSX^+U?9#^c8}4O`@U0AiHKiIVNt98^YuA% zQ3(yp;WNncloZypzbSnA4~%I}{%nZ=R#Kr6x zFeE;$$M=jcd(lHm$Wr{U5*>CcRpm`!u?Q??prb+3h^|%jg=%cnjJ8voFhA}~(p0K6 zy${9;TC!wWyP?^HLuR^DnRA`kpaogQ53ukFXlPmMD6*L@Tq((*jLg$qpYv@LQnoox z*uF=bq2C}>h}foh@(`TXZ}aIV6Jc8oP<^WqmF7q`qZdvvPzb_iAc1s3A20 z&ua^#*pGe&n~RPEc#%3RfdxsISd>+8fH_lTBX9;N@5!2A}!f0V%d zVy65hFwP2+GQb~8=NzH%eO0PWM#5=78(1UBA`g=Yo}ZL79g)t#f16j%dwR1vV%7*v z-Mo=I9Y&KZa;?EKo(c=pCeU5ykzfOg6dxG2FQU(-QbZLN=BdeJOKR@@ydD%4qelrs zSdVU67%y|aAGckC{$LS~K13-n>aVq9PNOsXAk)94ZWrK*x?6C-woq0;tBkCISGAzK zS|@R|mu#+r;O9d6WopH_4OH;-?v_TTR>drIrH_>m4vIX`<^&i-a};xpMhlZXBQ*If zDX)@j-IVJ7efE)4AJu|I36NWptD-=j21RyxGIOKhpmEf4=Ia_^AA)vZKQS%g zZjAoDjf_<-5a(4qK_V^D4>oc94W)=|?KN%<$y?CZ`LQWYkEcK9jnzGS9H@W1JFR}4 zegA(S`riu6-~P0JR9OCENc-%7qz_cjArG~)rJX%Um^b}+0^2gdl`;*Bg!6`HX0p%x}F5#5=*mSP-2Ex3XF9O zg-%G|c-ke+vyd@}x(8%DivIZo9}O~^5&%8ohwL2tK^@QvK|UB!@8+2jb(4}i91xCRf#-4qWp)sK-e#JF52v69Qxg&ua3EJ66$Ep z-ni-L*ZS4vX|e|VirV9S`#Zhl!QvdN=gP_K}Y11_+Ibs4O{0YV2?BcvMgi|S$(ik z-|yX8RzS96y%Pn71@4*VV37=_ng8-fJvcT+peJl z+Lm`0u(Ok9y0Xy^$c~9gLDjsCp@zF38Zlf$T`B1%@FtN30oJHCMAyW3ivAfKRFJcl zZl*tQq}ODP=7)HYQ_7(Pg%t^rI0g>5@e3;UMU}jUy@t_U0P@!_wnhNv$mNQ3rbZ%kLA>*B4bYzCONvJa zb|iLj5H?RJj>Dp+RqbYYLhz}3R}L6c5vN0LtP=~*Swh?ed6-;tjHD3*K9Qw;^QK1R z-4jiFgYbAk#2A}8RNalSUTa~vg%de#ziFlw6=O0DPD>QY zKfQQ8NaIRox&%sPazvtL^~@%%(nJvLQ<41)gY31@g9Um_%-P!}7~ zF)coZVwoG3%^Fz%8WEwoG&zsZFFu(upDpf{j7(7x;;Trug6Ra0kH^O&Y`{)gq9=a4 zHBA73>8K5|O!Itl%F1SRB)+=yP+fY43v z?d}18dzFCBJueyyx?pDRqGDAa0BG(s!d&}i3dcTa(d>-r>tf(rK6}aq+fb>!;Tre^ zbGEea4^kFY_S7A^NkxdIL-N>c-jaOB}B zV!*z0Rsk9-POIt4qCxjC^@~sIKM9AwO({k63PnuP6lX=)!#SYnPf>v7;kti zvh-#)M-MF)Iq3BJH*)cAx-&!X8-m}=`qc(`Bag5-0ceU=`Yi}6+@5$`mmvmLh**o) z26@%IqO^1^DW1B+;Zh|bor4M z_FnEOE&jkyafY-u{1onJ(%!pk4UGn z)Ul_}P%7zZ=HQucq@&mTUb`Z>pM%P0(eSG2mRj2Hk7Q=~3I|O+5jIiv#q4!}tu{8X z*Z(z+ZMu6&3hLvC4*w-9`mbpH)^_=il>IN#k)8jij{K7Msr*|nR9AmAL6cz#v>dE8lGyKPPfo~A&xi^q!ZHaO1#%pHd4*@Tqm)sag?tRNf@=3V zkpFsU2O?I9rx$`^im;kt5GVafIEO?i=IN8m_>de4X!QZ%;%7ykt@sqW{xrYk#L-RN z;R9S=vs|jk-5sPm5Fq3A`owRYS}9-+`Q%2ei1oE%)$CwZ{p!XCdX)3-0CPnpkJS34 z%#8Eqr45vD)9{O}y9Q?Sjr(x65Z^kwWBQCiXXqHDk9UUpHI4XVp~-To)8r~M_zN<( zV`c~w{4XxAHj|QIU(gUV@RB^=Pi{MxenRybf$Zkxt0sRaA=SBWee!87)UDPGuqqwx zIA}e1@7*lnjXD90YkUL=adnh6p--`EY7mWc+wpK*;kLsg<9L5zAb^y_~ z&}KjIV&BCB|Kioe;6AjkFpZrCY2x3?a7XQvF_a*%O_hw>nMJ?xRoqR%ttU)QdUEn% zfcmo~G-X(`WtB_H!T4xbjV2o4z9^+0cVHBT?Cz|TTW-t6AnbzPzuGgWuw%-cpFJlq zqBaBdio?N5=(n9{GK${4Aa0KU!2rf?&lmC0^GsP}I~(eSwtXSc>RAaAlwx+Z=%_Ot zL;>ZJl_71O@x!iUjd&v%Jo8AK#@+Jfb!?W)eiuj^90jvIy{%+JV(e5f(g{xvKd`qO zyw`^dNxpa^seDjBDu!$+M1Y3J;tz-+2FZ4=J87SccdWZb;8bsh{_MSS)s@%SMTj?@ z=H`>zOH3u|+fY7`n7k_SY~}Uk`Nv+PI@m^M38_H*3u`+H>&BDr?#8G$d8ha0 z%%8>lfK|TG`yV%r`(KRN{|e22REXd5^N&)9UsCYD6v9}E*Y@M4k!jfdzB66OwheJS zG%tn81!Bg{BJ7&RrEW!`ur05$uDj)0aL#dJqB!JwwHN((mu?*$X2g(cUGg<64_$K)t&BUQlO@6pNDN*uJ) zcJ~Ot^zf_z%1}f245ti5!pOt(Mo@A#G^Y76A$5p&voZ#H>sPP@ZJ2q^7%YV44lbFD zguVRhHmCxaBzc3FIX&4#DYss8K=f{s5u?EwZcW6)UO$JayQjCGNAw=e{_pO=#X|{3 zcxm^iKZ&wv##e76c=ccz>cni0&*J97G(rqYjXFkX5{bh#mYg$CL>dCLL;B(&mutk) zRgPTRZmKYZ^;oW3^hOW{gD`B~9l~L~m&9~5U@~5olMyPeU4UZZjo~*@rI5oYfYo!0 z5sq>+*j8;MncD=bRvr$AW5@k8_-bQ_n_v$v7aKMd3aa&xfGwE|*@Zax>K>shma}FG za-*vKe{djwO4q+@59;btVsfQxj^}S*p1m0sU{zXB#bmn^M85s$RJkBP28cezGBkgA z9431Yp274{>1CH!BP-JERu!w^8xc10faqr-H~eVeDZL$Ed`*`r8a~vZp*?aHU8^pA*vF+GebdAF)_osr6 zx8E$h)zwkmc5-x7`ABK40FjbHqYzeMAkQjg3UKSBrb zHN0af0A*Ckyo0YGDx7jluK)b)aux^mV%y~6Yj1;lyla zKHHN&w4wAVrFDOZk3iG9Bo8)TVXkAB%x;O1Wnmhh#wOz4#}@C}!l5Wz4!A8kiqHcR zdBBppN~RIFq2y)ddApJ*KAcckiMO=bNk#rJTd~Gn1+7_aqWrDI&IkfoJYVTG(%ePS zYHa-ISc`zZ{Rj&EF$tIq+miUL^?DVw{`p;Ah@8UW&x5Og-hG;8$+62E;ofs(TL9a` zaq*dWJjn%bop%Wdv!L)b6yIQ$CPnK5bx#CinP^3NT3m`e+&yZ;Iocm`{WOVh%U1x* z#uk-8TE50en78*Kx0?qP^Z+)vl39swy}{vyfTjw4JJ3m$P($KJZAT57v!SA75Z~SA z9+%mwxZeU~r4p1Xid?FPyD0~T$hs}5Q|hZ)P$F}{1!)etQV8BC1&h68D_X~qX0h=y zrDn}AWNApqU2AM8${_RdxS4J^t<3|K*PV`g4_#HWX>a0E-?M0KedP~Y3eNq`jAwyN zlf|FVY7x3Y403YzM!gCNE@20FE3oCan*d2kEi%P0;VkVZ%ifX-h(G6MZzWz%nX;Tl zk%u%D(yuF}#DMt2w1J|A`haW?pnfX1kJ6epgw%Rmr@m8(>Ee6$qvDv97jX)`ti$Dl z<)vpN>3JCUFirv=S^MYxq+MeOL}lL8lw=iV?^^Pes^u)}IPlnqBb#@ROXn{!Aq#Dv zba}UQXyk+}krDXG`)v3)yW048TRwGp>tyBv)NwZoUS(k(F;002967h3MrzzLRouyi zQg~}n?VVb;Hu*Vh`p@G~Q8QklahuI?5S#qK8h!-g#n>kTmeLPbOdC9N1ksTrK6jx0 zK~!7Ef8xZO?>N0*Wf?z)*e z*v9uB!40xZd5r~24NJgrXE=%wx-g)xr%Hu_MVF#F#z_4BiRcu?4&ctNU1=W;38U6swAwGjwm<0`dP0+1n^ykGRHs`A-6NS$ePO`L?$PnZ4)oer#E0)3J4w&Asq@rGBHaBX*nHY?d^?UV!Y`lW~PF$Yj z86Ct>4Y+H_0;`4^uy)27t}O*PB>aUPv)f>w0)WTC_p1@<;luo7da{QyN;jsv4@Lri zx#d_Hug+A8Fc!j)wrRb7aWS^t`=FYzB8!7UTpFby3&QthARq;ih}5aCX{H@t45f5* z;-I7^C53q#VJ%hlqcrd^@yS`=Gy+Yi46~5|s=G@{`~t^%-KnmUgG%mkHg?2^YrveT zFvLn);godlG92xZb?PyLj&|C%LpTLsy>_>8^MIPf(MKjhkgd8?YZ4ffh4l!8Lxu(; zo_5%x)Sf!hES}k@VG70w7aaOu>zDyw^vRG3zNY#fIzWPBwhFFwk_xS**74a>XW6ZG39_%B$52KYsa$} z)=>x6v2?iWBwSW`<AK%6kHBE6A63$JpeELhdctdhCKY*|ULQ@W$0k6QY{0S5fHa1MC z!Dn?MxLVBA8zg(YNT=*|Q{owi;Y4=^$3#hzE9>Tri7Lp3C#Utfc)2)OPI5~oKh-&I ziLitbAm3}2>Jen)KHby+m)(z9BpddDf_n5oCSIO?!G6r@u54BGRrJ`JvWOmF#FA_q z-@? zJN7aMDVcgrJ5a@#pR6We=?qa@kE{gRSK=g~y78fL@N_NnUk2)yWO62-)^T0u|Ip5{ zknlO&9M)4|fC`d47-)C8{9^c(G2He5dn-NF$k+_{E;w_A+Q5!Jq`o7~DaT4+UeDS3 z%7PrFNF0sb48`9pK3s*VO$zd|F-Q=|cJmrjysb-=^I}eaB%Ej2(8tCBzPQjUk6Lw! zQQyq@Tg<9m;;eX9RteSq@ZHRMN~f+tnfAhAV>iLhTAgl;3p&~2Jq(r+?TtSGo?;FC z8>hj;HB<^+xy5i3=EPWYhg*zT)VyifMX+=vN5vZ5A?ecDL$ix?eIu9933n6bTDS$V z(kugy&Z6r;Z1zUhw9lSqyiQrj0YD$XSd_aWM!jt+L6w859E~HKV|0Y$%5!)&CDEN5 zkmIVR^5`r{>`}sLZ|TNPh{)Sg-j@=v4J0J~dE$bYp`;uuoo{V`U({ufC9Yux@f zj=_Hj;eQmQzjz{lLHbd#`A2`(2KX?pn{QZLp75hOs&<1J_;FgJnI?uh6A5Q<)eQ1u z9Nv`Fw01Ds4?eGPca&3ax}?+NJ$_dC6oYFSrf>N|WjJ)terxqg&&jh0`?k5+Jj3+S z=(`LB=Vu(5!{{(O5?=4&@rgt=9&q~kjNkT)IS)o_E@M>9%?BONWJ-3b)JR23Vc%q) zYGjY)s-8p5Wp0r$a?%~X0o{Dmd2~O-)Q4-Q9TguHfEIJ>CbXvM7pgF*O-Vp=rMZ!7k130^R`<6S-BYiqCDr^jznxmv8xMf({dufr< zOL2f|Sm{T6nd@g(jX{q&mkzyS!USHM11gTG*NF|>Bi zJh?>SA*I+8=ewA@=sVfK-{=OvK(}8fo8p+fK*N% znchmH+{K&nT<)_Fsdn9({jsk+t7y)|LYL$+O+^g3Ve^HH6|f+=zF9)dPKai>p{H_~ z*Rq>d-`QBPDtD^FD}w6~jV1?I-4H*L4yAn_aKCvWqJm zGb0_yuAU&KrokuTHh1crUIUE%Toxcn zdp?E90dZ{+q-+la1@9~b=Q1Y%@-qKQ+OvCV`#nCmeg5T5@o#ba+o=9War=uc@fWw= ziM$SL{4hZ;edjS|hZvmpc**rdL#ZffGjiRY@c8_^ zv+VY)##GSUxv?VG1wkB0FA*@Q3%Ii6O2+E7C}GNbL1pbqSP7PZ@Y4#%s|`?vVNUgB z;-o3eytDBVGd<7^MA~67yIoPPDQU`;=ZJ;{;@(MC!DQI-2+bDZrq3FtBen&PKOaZM zQa~s2+rP$+)Y6ApIB8o<4cw3tMpIG-+`<_ygxQr0Hx$kCm~V4}x&Xq; zc@52L*Dg-s5s@-jT@U5!g@M7AIl4b(OMg#3T7}EaQ7r9Eb1r0$e)Mc$!AzHRr zC+}BSty zxAr{Tpe=lVN9v0lT|E;B1w7e1nsjf#NGZO72;8X*Ow+j?c`N;HW{&!jPpg|M2&LRQ z346Kjli0icN&;p_u2#Msix)y%xYjQA6V(_)OT#Y0y0cWOPZf0@0A4Z6*tvYcQWKi= z1yYxFQT&oJ%L*K5GZIym=YU&qHW0P&PPbQFcoA)Vh*$GG-1yOQ=b?J}a zn@u|~Dn4C3kE@dO4yf6|LQ!G(e8R!H?uolt(j#5&zlZ^je|T76-5A;N?zydZq{FHB z(t|&hwp*1IDJd?q(u!DF^}`4Etpeu4_KzYOe7@c@r4J&f{^vygHeUZxME;V-|3zfx zzmnWPdbwyE%%Piq`mS5$2sl!Q1MpTACN)3^mysB&^7D1pB_>i#7E){n2wxhym?W=F z<54yt?KbBV$HenVXU`(Gp_CXL73fsr=!T`!#QBT{Wvx!zr(KpnX-^@3 ziH(NWwOrg@L3~N$iZ*SCX=)6go|fwb3x|Nj9TE1D@$?uka7h`Mi=E~{$5`0rfW9Zp z?Fx2NJgX2Zwv#}jxB4c30w{kI(zZDg{naBQA9Om>DW21%Z0iQJgw#Yu*8Kb#hYO`y zzresY3}0YnTqb+K92@9TV{(xmEJv8e@QzWR;9REJ#*-h`3w;OLV1S`1)I!=T+P}4!VPE0F zf@At_)nZ|@Bz={kd3E?G@$TFhtMw{VrEYZ%mbcwm)^S@p(`%0{fw*7eZKcxp^B$8R zD|fvx`ZXFLJ0S%u0<~;L@a7#ous&k==5LuUaM)u2246h+E% z(jD?Ti8K| zlfEK~!$RmX0AKel<^^cy;0ek=dI2k>M=dLG&)@V72%W{GBX6&5JwYjweFULQow}^U zLOO6PfD+;%obvl@P(JPD{pJKIdo1jfN|`D9M$&zh_IJ@m@sB}< zcjmHqJt&1y?m6xGX?+5ph>fsG7FiqOut(00n>u%5j^{`U-AmJA;T3>n6UY|a+x9X2 zd06o|j~`b@&sVjK1V72`tyf<|m-Vkip%i?)-?Z(vKA!Ap&JVMQIS<$x`<>N4;5s1RxE`qNWczGS1lm1izPeNcgq79jr0>FHbxe(iij$tC9<-|mTyB%750{5DCZJG0NI_e<;6b6>BL=Gic^q(BFSk7X zNS)%`lt3m{W`-J++x6UQk_0d_GLM0FT0PU4ud`;k+?Q<~X?M}Y4Q*Fx3PKMZ-?!d6 z;qt{gPEg=!SPmbAo>PP&6vK#F#>eu~0aftj#tqmnM2>+cb`{WWlS)GKen-5@?4u}!G`I_+Aj-+LjF z7!`0@J4oK@i}Eozv@K68s7$#Go?UIHTHy2D5Z!_EWyjo~UZ1|(+!jJ~(TMg*`~cx$ zEz=$S#sF~RGxtK&K6+n}@v>^(8vk6CP4}ur6|KdQyBsnx)tdOMtGTu|Z(cI+Gpc%k z@qQ}gn&`_Xf-f5fv)%~h0K7sBXk_Qx@ID;Wt#a8-wl=kGMyv@Q-ZB&53jm6JfJ^sz zrwe9s1Iu@)i(rj3-xFu_zVf3Z9{+3DZs_z$Gu(%L{%beH-_nxblJ>tyOAh~ok@l~( z_xf~BZcfT-FaU+Z0$O_ir+%=_;Uh;se?vXqO>bETZ4Ox0mg##^C)cY-qyx=XarA#$A< z@@2Q;(yTNsx`_tb3gwBUzXswZ2`o-XM?`uT{*HXuFp1Z@z)Pkdl~7m~pmPJ`3&cSJ zipNBd(_RNL8#NbAut5gTw!AbVKs?y@mSNc+^A_+G5Ab&pkqS_!Ii^auAG@xNyYHf5 z@l*OX{5HQ)7LJhopk)-x@T(_y)S>pwt<%UUF+rS%E|gMy;3_km%YdV&@0u?EWY5GW zkzX;jcYH%=9F{ZJbIL@dO{%U{dRh=Cd88m>Q)i!161<41IKIe$oxpqiV}R8_8a1Vu z)4%MSYvxVO_rOa$jBTK2BVSOPn?o0V@}yh5?>;-tU6WrX?K2%x0~Sxps!~oFu&dKJ zzOj^Rtzx%BP9dG-Zu{-?L7*=4(k;r!FFqK{acu}fKc6MJr(^yPYh z#LjN7gsHnu_!wVof#~Jo1P}fCP#DwP>NE;=N$JHo+qbJA% z$TIP+B>rp@#of$nW6;kMc+`9|f$Y;olYRWZaFb*?LH%6~u%JhCZJ1!lUMJNB;&Oaw)1G5-cxqe&#ew|?dU*q_{G1mS=NdKdt z{l$X#3tDeQ#y^%7pI@P_6>_cv$B#h z?TmSZ!f%dA4LY4k;)u?!P8q1J$Nu5c;?*S7`+ep}+0J-0Dg0$;uAyeV532x&DDJ03 zEn6P~7?RWZ#$n5;l2JdX{By!Gr)2(MOF44dxF`2&kNzDIx%7E>;cJ6&JsGx}T-Cw3 zz8-iO(G^z!5(OVdtkI6No<9Wi&)0{NI{&yi^1*GhdEDzJA?gw}eOY^GU=u(yO-!qR zrfBoI%aKLx#lih{(2=){7a$G{l!rGS1%Pc*)72GQ9=DSNH(zOo`4jkNY+k^4lttQy zd+%xtkhMU_$(wf~Ku`L~N_EMu&7Ttr42KS-J6%XzVuIIhW^bR+tUOUQb3IV11hs9n zFEo$3Lw-PcLnc*{hoO@0Jse4PLU+!{avP;M2@6}0w3IwHvtt)o;4Cnv`G%8>_Cj_f za_rSKG*~CqcFv=TO&}19+OMvHMl2{)M_N~y&1fi=SCQM|`QUpD-A!`}Zv*Qeh0ZTt!C&Y^{d>^R6*WY5BY)bL^W2a;VNVkyGBhel9rx;4I=lxTP8Vb~(#FE#H2XV7;t-#sy zc!!Ce7su3$<~z%dmm{LoA4B5{Xh=Od9SO3EGL~Uk;PJZDi)dPNw%pZbU?^DN4Itp6 z1{VN%RaGsLaW!z&ymS?Oh&>C-3|2qI z=_1`5DDjN~Rr=id-Ky}CKh0{tT;D%bLs;ON!(o|RluzUlPIji@rKLdc`i`&MWtJw_z z0~TCbO_@lOt2=^1wdml4j(4Z`ZF?dX&fAAY(C z;p8W1Dh2c_e#l#}a|gA}Ae6_dmv;Ra?54|{b?f8Z8XKW$i*C*a8`}TJ-8pw>zNTqA z727r|c2aRFw(SZls@S$|+qP}nsMxmgX7BDj^VY1IUaR}R+0R$_oyYau=fx#_P!CKh z@&z2T_@ak-5pf}f^$}()FemUsL>r2mECM_<^VHy)lT9mPX24krmZ=`sTz=rS8{?|^E@db_EHVs>&tpDu|=21HZq z&?`Uj;t%g^6Fn9h0=3nkC>6`=_)bRK>3t#z-%%bSh6!hf-s$sSP8I?&fU)gYj$wqrNHE*8DY|6q&ngDtZZ}>VxIQ+$}FKKZ6D5cyyj#!nkbQT z)-7%UW~-0eHG3`}?{M%2^v!{y;&^X8xmE9mbBo5tz z2W`)13cZ>6%0B+>@c<@Fua>PAuX7E$w(K(w3SF{?=DPZrntp%n1CqUw9#jgBr^KVE zFdyqQT3v!ma8f?7w*qSdEBknmoz{)X3a=3cpYk0(cw(w9`WbYmJ42W=KLK>ia;9Q> ziV(XRhgASyp-0dBeDs1*9oV#aU32>|P>N)`Rd@$JO8%Kb3x8C#aa$?1s7_rf34ce* z96{_?dg%EArMWTzs|IVKrnw0)x{>P7Y<{RcVR%G&8X%)h+!popGcHaSr8)P}X;t}a zm;UoUBK4chk|kHb2{3-rLn=Xa!vtnW!RltEgV@{KQ&D>g2mQHgSk2#>7{3ymOc1r` zaXt)&Sv=N!CnP_x;;5Lvg}SboP$E!OsfcNf*WUt=DMyz}r#IOF(c{|cQkejLP*&eB z+`~k2m88jJK!bUfz3(!Kce=y+Ba!5CndT?*LE*}OPT_Ag`nRI+7km2`g?0*};aK$0 ztrL_F04s2V)SWx%JJE2JB+csR)G*XvI5UQcuBwn-9O&B7D~VA{g8SS(1YpM}6Xi45 zfYDs6{Z!!CX*V^ZI}UXs*@?yPyOO+aWFQUbsnHx_Zta>T&v^^QV1so@wx_3=3wXSa z@gJ=GU*%gc5GN!d2?XB(&J?YMbR>KIrd2d8LGsAcjUtVFPoUPU+`~|zHf{410iP@c zE)g$YKA~h~wI#@b`{92DAii4v8p*83GqOsRu1biWnmzrrfg!KUr$9U z#xj&XAPZ%6EkGf=vCWA$qq2{n=G$v2KF&PAz=5J+pluY<}U8!D}2W4pO+rhKFE!b5cvkMgW=cN7s_6Yyac-3>ALi^9^f)#}wIgIHQ56RpR4^I}sx{o^^uoR)&iGDC zgtC6lQGEM|;%<7}9Nem~Jcd9cSDKq^x)C|~`0A#*W_CJ-O0Z%@?re0OA9h>(fEcIrX06{TLmNcPuA-n05uir3{^nr~P4 zuKbka!2$P(-3Oq)JDnecWE(MQ;xpv!<_I0>UdcvQC2^n`=O%C+KuHwOw+IPV_=44x zHhzV`x0+7K=tth@UUsP+GuebH3x3P1h={c(IgF!c$*!c$A_ z=HVh3kPDfsmS>_`aOG$?T3l3grs6!ZKISBSekyp!k`?=v*(MuH-tIC@BSSqe@_W3D zCMNoN>~n`xp@k}7!En0LP)wr%gmL!4^z3SHUd{@(e-a;oFu%}&TG4p<^caeMlX;cM z{j2oX7ZE+wCn759W~mSk9h1B!^m9-Fc?%WnEvtBD-K<^~%xp?O+0T#r4P?{0kx;u< zU^xRo(b*UCy{I5eY+7~M^K91Wi%XlUf;#)>vy6q8oc_IUd<7Z2!*Lmc_r-BB1LPkI zl9Un6{es?M^3?85lFYNlk@?G_Ai@eH#P4*VeM$&Od-R8nM)A~`MDTZTbd*t_V7s0z z+apU9`PPr51SdLiEkFGz44bl}~L&|mEd{`r#`tml?K;fh$JF{I(Z_CIz5jfHP#~<&C){XWsYs-cdW|}n$ zNTGDe6!6oi3+wc&$|7h_c@WfPEB&tORdl4~n)6>p&%8TJW}jAWawY-lg1KPlBc!gZ65UJ*lz^%0RE-(y0lMQk|n z;kP;fSw7VxWAv6>bP^kM-c_EygISp^85Doy(VwVi>G?{1k|X#%E&DU`1k$21N6TjStkx#50Rvc5r(iI$ zeNQrsJ&q>90a?6du#KPOp82>lUKuDj`SXh8Dg*PC#aRlU3|v~~&4kq}aIA1>Zm4_S zaxH-u(20|4)Kkn<%cbgIA�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 literal 0 HcmV?d00001 diff --git a/data/.DS_Store b/data/.DS_Store new file mode 100644 index 0000000000000000000000000000000000000000..5008ddfcf53c02e82d7eee2e57c38e5672ef89f6 GIT binary patch literal 6148 zcmeH~Jr2S!425mzP>H1@V-^m;4Wg<&0T*E43hX&L&p$$qDprKhvt+--jT7}7np#A3 zem<@ulZcFPQ@L2!n>{z**++&mCkOWA81W14cNZlEfg7;MkzE(HCqgga^y>{tEnwC%0;vJ&^%eQ zLs35+`xjp>T0ErS`7*5DHsXq$vQeJ;!e>J zDjW$3o6J^9O69$j6rGBjvz4ubB@)uRsN_^M4fXe=eXwLANdh@KWHm|WkaM#fF^lvY$`}6;!=WNTB+uxb@geaLP|jIA6Pa}BH7GETBD%KW_gYz2{_*y8zhvw;; znZl1~pOTloKDUw3^T<$O_T|i?r9}OT+bAl?$Y$~5pey8cR>cI_XCbwm1*Fp@qR&A; z*iqGo2C1n@tp-fxZ`DHgzowEl^)zC2PCD_@-No$J?G?J0Gh;4E_C4Y-p>O}jihT7DC0s^)=>PPV0|=w zkGeodeY|wDcMIjxjTJXR!GHG*Xu`8j?ugG^ivP=+hy^J~ay@vt(ra~6kETEJfy>-z zVG8-jJNvUd4&Gmc{kjdPhjGp8*~dE=F*t`|fcQFd)=!sm=ZrT5C{Yj6Q#{mEg(8F` z_-l`p#_0ZMYrkLUJlD9`>q8k&Iz`v!_p{-C;p;q(knPjHhZn`|v|}H$ zoZi;cs(uDuj$M6{xlikXH4CqIdiTeFp6A_1pAE_E;51d#5j8O_s6Z_t2GBz*laF

%qRERSU%> zCzFw6YskN0Qv!+TJCb*V$T;fFucvVXs6t5EYZ!NLxY5xj=$;IJs0O1}ec#nJq0zyP zmSEMvnGIDmp`6FK3_)4rE<{sY!)`z-`{3n*vK(rkOWJ~jvPMlNi5C_&j)$5P_nR)_ zqp(@15dGmtT7qb428?$EU!u|RczB=dh9ogq_A^=mt@XSDkP-{d7C>mq464x<4w$XvduHS@I%?XalYTg=ctR}ZEw(cJ`9*E zD3DhF%HXDDr;_hhmXfw zb*y-0)PgG>Y24L(^5pJS@Z0ehX@myo5@8@oHdB)9G!>qBWM#Y~Nx&M|+6>rC+*CSMKTXKU$;i%#F=(LDc8XhOYczC=b<^JR-HKq5BQlSs zeEIE*lCDD0cd8#51pf}A*3U}D24Wpn zVNqex7Rqta%r;h4ns_%M`?<8?O{LmN!#YQg#LI{;wQ3D&DU+Ca0dlEHVAjN>*rfZU z#zC1uK=K$HoSQpBtShp^l-+dSEW`S|7*F$)W{Ae5rko~jVNDTsA=}TzqTb?Btx^rq z?8Tn$w70`_R!r{1XWVB@XH!C@u0|W6jg)wRP?`I(U+sw<*bZ1rP^(utM;csurK=2Pe=tWWlzj?%{!fC_F3#R_HV1d5podH}$7=JoI@vq&ToR_Bf1{UZZUvm-3c+ z>8@03)#280);QG!I+|N`RTkAWI>}dG)En9?ncNPJF2`Bd&m7i?b_G`5tKC}Na^c3| zGDL>r2E?Jpy^l+$b`~xYk`XF(Y5J|;a_MFCdwKJ2qNn5K*jB}maT!S&dD*Q&mLe~> zJH5tUEj}Ggx#SqBp9&Zf9SCUlON}f;Tgn~W_*~G&Jm+2yQ|D4I_b9(N;%Y5zky-6) zf!sD+4a0>GVy;oIQm*Q*S#Nu;98djzm&3AljW@*cOlY|U*F5OxxnH;_u9ec2(kZEU zj=!leKfYUYr=Lzhm_zz_$q`AJ?AXMKfsVvEr8ZhCGA8t&&(WX~Q2Q=gX)@VO={tdN zp_gWITS?5HXFs1Y=;x5B^5tI2w8mV<84(9?^}cvZHAVFk55Q3;av6}}Y@lnPeb;?; zeWFe9n&1ipC+bJ{eD_LJsQP_=S=J(8Bv9|9CVeWlU<@2+8`1k|n4H;)ZP;QA_(l3% zwESY~`B&=EGPCDQXfK_WXx6S=1YC?u=S0)T=>5ng@UIuoxws%qgO7?9(b>&lW- zES0a`2YlGlbJhg&8-Y|J=_a&%_sM?f%n5IeK_)pcRM5?+yn;*q?5ygnmtRkLO=3&T z!M1zNx>oSb%(t@eYF2G7Zz%;jbTzb?vcw5#xUbCS*$Ew@XZ;s)xUz&1y@P5WdCHS z$uZ6C=$)Yyca~f7$enDi@~Yn6-b#l!L!EY(cN;I(1WFzAmc$%k&h4#?CbEXfs+!kI z>!Wv>%3eK(cGo>Ssk&T z__s7p)Sk>cr?el~PC6C?vwh8G-VYAsBz6^GD&c@hz=z_AzWD_{R%fl%C)VXz>275Q z{{7c6q<35yjwg1)o)ZUVQ!i8H`?vsxW$o)Od1W(g7xPD#?X>ppHb0wpLABW{jjfgD zYJCGr@uPfm{^f=ZG#>YZu@#img62-EZTtsXbB>1x*515FElue`-oICKZo_V0J}070 z5})?hxrp4Mtu@3oWNr#*)~A&bZJO)a@i`HKpTJggm}{7~ls&;(;?+L0(=f=~ga7W? zDrqOF)O}A7+kNA8-oQ|9kk7-62%h-(t=zfY)kU=6uJdI}t=|%mjOXguaJD%c{*rcD zeEKxY@3*YMkKI*(Z_`vr^BWj%h8d6~q2!jY*dr2(jyZ$_W&Mz+XT3^3={QA~ z#{BBt!TJva2pBv09=y~sYHQVg#b0vi zyl`-Mo+<5!2o*3~-s!p{A(6cN^L_GO?Zq)7iU8Yc=y>QTDu|dnJ93&@IGb5=f*f5C zqmht+AQ8l+qos!_9mvta$z22_%J9z!5ybVM+guEE{|xc47iG{BQyIFJb2n!2yar1KV@^T{-Jf+$&73_wL>U7XNR>uBlZj(CR{504Np@Sh3)k4OLg z%Kv4m?tf2x{lCxrU!MHuOd!`E2>us{{sUeA+(qC^3=_!pU$_^;toeSZiXaD-t+cWR z;urZ3Z4jRm#Fynie-YO=DuizFB1lMYklss6Xn>yVXFf}#lDX+^U%;c6j>3{gr+*r` zcIXteNhkDF$N<+Hohs5@Xc_9|(of$Zb&My8j<-&;ekeKr_3`%7;o$PpYwUb@|E(9q z7i?ke+w}8ikm1d&_Qgvz^n5yGhJQJd73k=pMPH)WMDX#R{Pl?c5XNlz^skTb@q*dt z>7Ye&6>Mt%GT;e@@*v8;y;^sh&8*oUwJwvOU=GXFB* z1(5am#}DX#Jv1;78`BKY{2cu+1KxquX#NEu_=N<>1mNPQKN*Psjxqd%;~&6(!8e1- z%cn1ZniyI#Z2v|R=zE`kfk^th9Qr#@F+oYR#@}%w{iZYV^j{$Q_$C=I;aKCP4sp@n zaqHP>ugviOC80|$I@K9I7ZPy)8?oQfCCgz*gAwesg`t!h^*6FlV2U!3r_+~34BIQr=_+bdgK;)QNe1}G~hYbNvGcvB@8!F+6Yp7J2JjBK;N z;-fGEu^ZwCwUoLijt4 zixDSsHq^fYW%mxDT%cT}HCIZ<=xcssuHR{~=U8zaEHc#HN5C|Md^f%>HX!H-uiP@JN*Ldux+0PUwa~QP< zz3Q1W4qAx=95$4|cK&L;f_J6Qh$a6QIwX1&*jAZR%8ZyZ{&37)JSJto| zjoIh$_!so0k+f3_e<+n*}Q4mv8hF-o4k*Vpdc}{ zhMJ?#vNQv*$#Hv{pgULhaE9}z)PLp0kBYxyE7glM_U%2)e31F&KoqZS5-xsE8cNjPtrC$RD1X72!j*>V?P7D~X)d1V(afIUlIs+^URQ{Yf0 z?{6exP!U3KUh*%z;uSrPrL8X*oXz{Ze#aeXuT*Y#3bGs!)MEAaf4I8_dp9S)(oO-a z7O68+3OX4ynqLQALnCP=Q}4GZA!xMzbWm1fP?OSM6d&?aw+Slb)LnawU<&gSX?r@~ zAL(KNzKyAnMtAFthz(Qd2Nc{2W%2u(PGv{{Jo68u{ySs-@ez-`buDI;kz2#s@MgE`md%%=inbX$F%!&?6GKDuzy^hx@8(2YP(QnIa{pxO25WN71}LlwiYOdK!i(P zPr=8(O8G4NKU$EMUlQbdP&>CDMySM+k-`;`s3bD*`?@Tl!mI}~ozGf{UNxz?&T*-3 ztLca)>SG1Hr zH|ockj){&AiM%lU3xTBRp8qlJA4nR0EMh6wu-HxI$Ces2RxRgFiTs8vMLUieCsZB8Q)szz!<9aCOHfF#f7r zP7qXx?qi+OoiPF}{!x-k&h4eYKJ+0JO)6$z7p_9Oz{p~)y>*dB@$y)${oG~C62J3m z%d%_0->G{0kLIM-vWMEW9mxVf4jPT2lkON{-Y2WoxbfaQ{uh~IIsMs0nBdZ`fxPCxLt=R~$A3N}KqUZ}C`@vRG~#rku|w!(=XZf4rQvZS~y$eDF>C-C>n@qSwARU+V_5r;Ul|iL+){d_G5Qq%2p> z+gifQ-O*C59uCiV0%T`JBQxcAeT&k)nW_SBn@TO$YtW6FpUt!+*GN3v#))#)*t%3M zs4R-Q8I)&uH$`<+Wqn+x2hE(BcIo)OAYeL>Rp&V+;?+m8M;ZoQG=?pfpFLJ|EzQ=q zd)aEJ7&J9(<25+`22yfchaW)f44XX?d!LD3O>K}Y>L;e^)xWJhXe&)8CQ9Wj^LhGZ zOp%+icAfz6y0%;OU{(m}PbBq{|1C)5dNc7LR&ccfgOEvQZ?;s_O+JOa66HB?oaJ2M zEAPuBt5)9&hn{F+FK9SvO0+;Wv(|ctX;;L8Rl4JLkC5BuyFG;B&Fci0e*;+r4 zW9K+aL?%FQ?-%u!Jb%JXUZ=u+ApO^u%GlG*x$d_UU`IBCrl*}#`Ls%xamKT?m4;d- z)=ix_47e@4u9b$N9mZ24GM7Wa2aKbW&|G^Q4+lp(j&DltsWum;MYx!W@S?&9n zgF)kM--*kWpRtb16<(GQv)JgP`e#A&^u~(t$#d#e&09Op8q1KNwbUtxtIp&){WIUo z!BGyr%x@RMZUzmFRo+B z2l5gaZZ;u-ufu`R^ra;GwQ|R@YZ9$3as@zUd{3@Oul(9S2wwf>THf zOnj1z2HjQmZoE{|Z#r0tH=EfJIWzl`cXL)y+g}-5IwDQ;exvp#x(iK47j;fa#~LYl zN8~(^eD>R^CV>)jm@Iod;HYn57HtR=|BIlhBCm&207E%0>|y&)){wKt!%6c`Y@X~f z_o)Ga{_gv)5xCTL?e~}Z!n+2eb=TYJsnI-Jfh8eaKlMSyA+piLd0_=QX&#dlaweV_ z%JwXA{0zDaVFfeE?=X~q#;xO2Kb-O_wedI5Y7Dn3=nr_>PnvwB?38aiV2 zPZf(YbbnUQ`+r}Z06fbJjq+2QkXo*>ohosMjn>U*>FdG|S4t5@%!^c z#Y~ZzC8A9DmYU2XSXUK8hcI<Ug>0Aro zgDme>BQH4mo`Q+%Rh@Xnn8K2$y)Gq)aqr1<+PyOMq?4OMT5US;!#s)wg8}E*L+(7q zG+R}i3uC)|jPESnn9x1^8+N=SgzyEKt{G={G>6Rd zbe9Nh721amVlrPyue(#WkGP*-4CZT{@-!$v6`iiks4S^gOpEfXE?Y%v`Q4dupr{#< z{PKVb{_Fs#2{`FhKv;)=Uc31D?CA04sM~CMIYKl!_V>u=B zW8U_IsSOcMc#qmz^7n!%D>9hlYCi#s{dE`-8la1moQ*+##Mm}!k)`1`gf3Uttm_Wm zp+FzB;QVfHpEyMlkoKlc#%jZ)lR$XZ^BP@w)g7nj{@MWRvF0g6N*1Wx?8$+jD>@jS z*Rr@J`RZ;Np#d`8M};pD!L;www(D)mz47c&xX+*D%AZ8XItE8}XD33-JKP-{HXqf! zDsSQ4Pb%rtt9Nqbvl{*gjL(^fCVnLs^nA%6qw~3-)FZ-Gk{ffUZ7`^?9s?Uu=+}Mn z>bu?jS?&v)P={1m4lQrx1u-k6nPiH1p0+(lWdG_`eeIPucV~X4;!wOUJO{!}Gd*Wd zQdn2r$3zi1q?+4Z1B7~RyXr(llid1M7E!RM3TH^$Y+FE}(*UHScbN44H5xTX^+cr8 z@0aP+7eUxr&a}$LyS0Rey35915u6JRm6;2|550R><2mivg1cjJ`WAfS9avbgJYPie zq)-5Uxe>ozdMGo93)H;EK0dp=yOODUnt5xYQ?Jz-=Tr|iY_&D#8nykcad@?$aa05M za@Ndt$8Q0%mt9%L;k=O*V^?WtFs)|tdU5!fRY-fyzn31Zw*JLrfH&O=%yGKE>|phW zG$x-UGhkzzUt z-MQO)TnTuf&YWqklsEvWk7HnB^fQ4XFDhLf%^ghi8$;Kw@fwWMD(1mbG?}p-(mEzz zMTieu$9AV#U{ADl{DCC0pvAi`h$Fp7DBP*Gjk?`56jL=*#3Vl;xW#)tVsF7vpPw(< z+T+YbQJhM49;nLu58sMB5_}mg^pl~6t=-v9 z%Pg1p@&LN%gHc}2x)zw=8KjcGV91ycLPgzp)dKehELW>r{Ba`vsEz76jl4-cgq)Fc zTlL=ZokF5L$J={3r2XYv8t&U0$A}jE=u1UAtTM4BU6YQxPb=*q9<3k_fkQDaGZbrY z@khwcOHVe|)ixP*7ZwO7_oFz_H(ONPRF&m{!_uh)~qM)PXlhr*$N2fJBb!1b-} zY9&5eeLRt~P9Fr|7To&8$kaK-tv7rA!#DU|T(G?*Dkh%;3@o7wdn3SJfg|+wfNFK_ zhFrSeAiUrtE#Ycu%za-LyR#GdY{mFwxX(F(6Xu;A8{{!8sf_Lu1Rv&Hxi%2Q{%RkP z&sm#ns^Z#}$wTicxcd>eKjv$vsj)JgDXag{Zj*o8IsX<7`@!(a3sMLEzT1`+>|4mS zo4o-Yd{`|_Z|u4pFMPJz%qfa@3Z>OAa~ADb3SZk^XD_d>YRTb5_p6JQAKU|z5AI!< zoH6WPt$drS)*c6|dbFAL18>m-3Qg+6_y_Q9?0ucv0&NW+nZpKeKU*8WAvX=6!X_#) zTCNQD?Cdtq4!)Qq79XpPuvw0zX_46fefeniEmoQqdRi8HFsVopX(a1<2c7sGZ^^Rf zXU3aw6v$i=P1wIpYtw9u49Sy7`}mz^cbica?7Y-st3;TZ@vE6v%EvTwkaw_ZFxW#A zOZiVc5}ysG3ik*PAE6Jd@Sm_Z*GUTz@h6>z4vT7g+ka{^xdLD|dxkABvt8z0u{H92uw|;e*@_V%(kkJy*V7fiE|3OR3<87Z? z0c!gXt2aO&Hv>I7%!mG?{f5KoVFGK9j~s6*vmBj6A!t+Bp-~k&^h7h&-8 ztVvPRScEvyRpDCPd!{AT)>!gXXe4;#>gFW+yS>?~_k{6I<`@ccs8XDriEx7>rRr%v z8%P&8rlii+EXrM^pr>MOe9!ZFVz=ZLZp>j1BwwDe3;`mG0Q=sqscgGfZpT7eXOLm zPQNCILOieqHZT_Tro}migyR$3k|OBw-up-Z;j*0_Hg}*xaRLmWJBi9-mAP>ugM>W| zJGa(Z0b0Ayxc7aksIf4WX}_Dl&khge(U4uG^S2)!ACcJ_h&z65Y<*+n0Y2<}!=0Xo z3NV!o;Ok<_F}1M4NVb*_BRRmKd3&`)AF&%OkU}nt?8K+womGmRZH0Zj3bJsev^_Lc z?hO-OyAKpZV9sk>ftJc-QiHdaR{?pv`g?ns00w@b7Br)7=?F z|Mb}JobOki`YHa}MIIL+iA+5R0??YV0QBOh$i3yy$ewu?zrP_fenZ0g~LW25?k9UVfGNFoaM*pJJ`AvI0$LWBM3C` z2{RBHB{}H|8Tr-hn#T~JX#kD7{+h#tB4bzg%D+4?rYfC>$HO6NzW3~|lMoGNo!xRu zJ`X<4o@-VyQvSqJ9tbTG?4BcROW(P$o;UI}TZ$dnuo`n+9dxzI95Q^%}TZY2Oh8A4=tKrw(W51iFA3F!q3wk+EOK7i^xTs$D ziAgMDvl}a5ElijtYE2fhwf+L0C?}PNG6KiJ(?R(GI9LF^;ESPb+#qDnD~SBwu~%W& zhT>hmy5>nxG*yu{&!t4$1#(Yv9B8hTUf@9qiv`4BWf*Q+xw}469|+_mQX65TC0g zX2Rw>&nx_=pSQa!4|o`tW(i*maTZ>&@?*^^!sF!%qQuX9=#6~XuL6kJe_Ev~5OJ5L zIy(3-B)ELAaHRthZqg8y*o1$7yJu+2o5|w^t9wq;)SpMs~tTq<-<4fSy zC_5-Ttbp$GFZduH*dXb8|HK1P++9M{i7klAY5b2R0Bavm3_MmL>+5EIYr$yye-amA zta`2&dvoQ>!iVylom20bGcC$nPNVB-Nsjz}XGb>Kw6qUcOw#yl;{Pdm0r!3&s-qpK z*kme)h&Uzg`26AaXbF*w-_s2|@sCy+Ntb>8(O~)O!%a9dCP`n;#<0jO=CDm>aIm?Q zNAF!Q4m8i^!@?S$LqzXMP;+e@kwHo}9|%YxDbk4I4g^hL-Vz(yW0 zq{B9kXg)94yYa!R=}q075ec%cA(@AyUJkag$k(sf4;A{VFqDUbBF!QKup3m)eTXtw zDuS5+IW;#qK7i8sRY-2FcVNDs^=<4Bi!OM6!D=s)aI zKS)gQkWqz)D|9B>V}T6wn|R2MR`2tDFGisiA|Rrp0a~R#z>iucv`<7YaC;_^=U6>q zp9efE$V@3a&Q_QC4X#F*fR#AH1eTdwv66&S1eZ>?<|e&WN>4t(CKe-y9I-GQu$0~a z;DpkXs1C5>vNYpRWZu-Z35reJ>75d^c>8z>_J*+VhE76`lCpcxGSX+ofz4%r(ou?} zpVl^}@;v7Z3e$ISy#7SHft*vrOu&Gs0n$26B8zJSc#h&K<*e<)%M&6cmO`VDz$YAL zWKJK#_k}CgaIg>>)$>_uuG)_)tW$QhW%lP1So16~%t3dJoVB^(vqDMQn3ANOYFFj& zj(me(GM5DD7+w3bm(w0DEb52A+PTC5(5QzV&-r6%PF=@aHV2|+jvM2WQIcO{FlFf~mR=siE?2wc^ zEpkQTe?|RFy0g2n;zzJdtTEl~RLo0j}gVv100sH%!&$AHaAk}quvfJ<3UcBZ)O zy8$H%^9$i5I@GUph<#4P1nD_&JDONsy_n@! z_$On2;>8pYr3mCZb=?=3Ia?*wc1KaBQO{Gzqn-4Ss^PV(J&%8nWwMsm;HobFi=s4P zZBTTmKr#@KBD93oHH9-X#izW@3VLYdwsF>pH z$|v*bwP>q8KYy{2J%?)gK$G-gEyJ31Ved7Eaz-gE@b?d0KZKyRqCjnX4{lbMab$Z4 zw)_{8T$JV>*~Hc-n1D*S#aBzp;IFo$m+C_PWXSUKr{e9$-5#zHNBkcxzgjiWd}z2t zN_oxg-=(Onw4NFQ{Vk#?O zMOSDGQpLu(YJ49bdZ}+ffNwyuQ8vH?RDHK|eXpRL0bSdCKP+&Rn4Qr8+PK?7%0%(R z#C??N11_yf7SK@}o0%xQ8iq5ZFbYGX;MS>e44z3dxMR>acJQ$R-wUi%R}~dZEYdeG zK+jPK^xm|f;B&DQ-u&v!#`r@jxnh&F||xk!g>`H^=L|i` zGuoF_lCFiVdvZF4Wjjde&J-Xg5#Wuj!57|44+J8=LfJlo>Kty;Dm3q{qbt{(E?~&_ zHNtShu~q?7YNjd5dK{C{37xN&B=x)_IBGY#yTo*idb}m=je9d~Z5cR-9x;Q%NJxn= z+g9rpZNQ$Lgh%qGk$wQNSa`CnPb(pR&Mpz}kS1uI-`_Z%uH16>s6rf35V8d$bqr(y zBnQI@!DP%*Owic&tWUn-A(TBpJy>O+4=Q#Jd$hPLz)NJ2C*OPg;eeNO!s70$q$XmE z1S^deMNUli+W9e-_wSM}PWrV?&1&UubwR7jC(9Y!W-F5%p?RdKw_skA6lQ;`Diuqb zmFsF&a30~%kbM7C4P7i|31mKKjlic$mRPr_GchJHB}CZ?!l1$8ZEGGdc=^$c^_B>7 zxK!4;Nyc31n=}or>1=1raddS(J>r_zjwOL&k1;+!G<>5!bf2L%hkdf%>F|DWk%eyU zN07o}*0->;qlo^H)}-&qX5=VLHH;~XKFAZ6^KZlRZ8GPoyJylh@FE9$Mp{1|QhYYr zog{1Ep(wvt64e9cCVaVh&fZwz5!+FnzbMv{sUY@xu_Q?E zhg=5QiDNuTFd#L6gOm$y^i7u`gt%gw^LiLmtvo@)ki}fKYZv8294?oNJhr~qrW$cC zz|CS~BO~bPExvUz7u$pAG70Ajz+Wq!d7>QrMJ{Hw7{jp1<7B;$M)?^8=@#hY%F~J{ z@;F-f>$?wS> z#dIcj5{SdqH;wm+lS3@OAQ~Z34M}`yE~8HaBe}Jp-uW2vxUCTQym|a|XE@fYjKrhh zM@6HI#kG~Fyhj?!Ryr+>SJ8<@qBlw>WlPsn14YSKG$f-prup8ZMUXM{+s4kyD0!`v ze9QArakjfr6!(FmJJl;INyI%Fwi($g$(PF_<|qTn5R;bjQCLY?AF6J+Ab7=2^Gm8sVT}!KoqYG1$-mNNc;gS`~FYOtt^0 zj9p1mnC11d{^>qiAP+xp@rc;F)ZDoQ@vcr*mU>P_?d-d53~#!l6N;3H3SX$@CfY;W zmTbHBM<=Gi%eE}#i$Dn3T#zWApKuZ0`v{cgl+7tXWH@YNEsr)ise|)Kxl`H>^dJmv z_V~KNXZX06VM3G*aVGy_Rl|A}xg}4-NZWgD^X@BV%=yK8+cN!d7f+bDuvIWAvvAn6 z{4BVIk12H)hXGvv=8h&yL5Udcszp4n#6d~_o2Ynm%}HaZD-X5tsKIVt?2bNx8khJJ zrMuW8Hg?FftgYTJ)}ky@*XTxs7x&A-J6>@Y`Wf8MfN5F}=9yF^5yM=Fe?o{ad``vJ zdxpQ;DP+1DEiS+3Zsh%28|pawvrS;Hpb_e5c^v5Q${>?KlLRCV-@k>YM_6UjPT=CYKG&vg;jwkqAuyx$EByifO>@GoihmI4-2?M8>Au4(@L#i!{90cf9u%vX zsyWMNYIz?iJS2aHzH}u71)LY9wJF?xi+no*A2khnp#+J9TN=lnGzIN55>fc3)_mq1 zg{x@yV&KiIQMP6&4VAWs!%qxa-_{M8Cvj-Q;8!3w3rbREM_|EKhicI}VVg2>7LYDE zgQomIDNZM?En@Bj!nZ-L8Lo0S-Ee+njjiy~Jc;ye0#$v(>%+}d;#<=JR(bUv=KP~{ zf94j&k_0>|mP1i?>Z9cLuzHAkYI3js-F#XFi)1|UqdCadcYr~dxy6yCt$Gv4je#{_ z+wk}vrKRBuZN%JB$KGHi8HF(8+Qjs1jV%49!3Ka-XgPt(V`n^lo)gD(N7;4){F0z5P}csI_aaOy>w?o^mriJFVtmZWL9f$59*CE{oaQg zWgST%40;4x*?8yeMY^g09{L`(>-XMK9LhOZUk~As(3%_0UbA{9K!-@2%$nI_KBv2g zn`VRb5$Y*tU{1>fj19+|!aq5s^1|If^{O*y5`Ke;+J5ZuW;i%*Z=dGWvLzxE-cn~l zN5+qIilq$8&ZQBgy0dJWBA+>lCjf)H{JdLu?5Te;5(z2-5x&DFYOg9v#9C`x$ha#z zDiaam!$+QDT%)5l_i-g?*x0ByEptVSkQS}fG0SU>x?>{#E1di@>Z~+T($Hm-j|5;A zrF~rnD{qwMBkR7s2GouCn?urCwC+#op@eXyoL!3c zhN}!BG=mIP$y!3ToG|9cfLjZ4Y*&>ofl&CJ?8k&Y5lEi3-KP$gJ?j+F2Q12ui1Ii~ z-C3X^H+Kvp(A8zgPqpMZVO5BKZkzksz7SAUzzERSU~qrzB@NgJA~m&)_rzwFy&6`% z2+;U;j{@O_M!6n+#CX{o;L&X%r?IRA%Ky$QEX{p}rOfpyzV9cZCBD#&z+ZrQ_lvBK z(FvBaYxilem}&TdS;ntGq$65yH*U7kVmw3z^sB%FmLII6rh?N85lo4ZrNPLw%dWq{ z&YEJ-H20T|TZK>Ju31xmMX|vBlR1E@<5=nbALSsH;(5%%x{)zU10gUER^lA~lQVEOCTfg?7|&ppbY6d#p7I~X;9h*Gw{qoJXQ?pXFD<&2h83_o+j(Ls$BSO-q!6XtD}0U)Uq`epZ~}hc zCLuT)(VKKrHhg!2=q$fD{FBOv0FyBRMIWvtYW;m$bo<6BrK!#ly?^1@ zGz^@tL|a6ynilAc@p-Yjdp$a6dsf?pP@cSP!htYo=BB-j6|Q_An1}Q zf@tl_YOwXPb)lBRy$bE%0;+zBBAi!cXwRdszEz#eQs+H;2*M`;1VVRu(lP^AW2zfA zJFmJV(-5rN*b=vlh40pRIAFdI#%%qp0~AL}Lb#1SQbo-q{sj zKGBRpi)jw_P_(@L^x4z+AdyxF2091szB9wG!{=hQ^nEp!lo_rn8{puO0PZVhro*_3 zBy6L^9X|o+fcRBj@>VhCLlsT^h{?*~d?hd}9O09vD5my)cKks8DJvu<-2{UW94KSt zy)(r026e+K&};62be6wkEsPmAV_~%qLz5nOkSOJIa8&J!L719(EL|BJdhO7`W%Ecm zEAARXsEJ98UMvZ^=@i5uXOism5%b19%O4U~O^iQWUNgpQZ%NU*>R2m}?_@#?h26H_ zW|dck7qCDVf`T5cgBCu0qw*vSU(lkcZ~)Az625xyv%bl=Gqd_i#>ZD*DR)}HOV2#K z&U!s??y-zl_ocB{)I>?Qi77RUh*Ru7^4xjs)7ZHUaiDpovH#B6d2PIQjSbuVLG}c3 z*@)Not9;1dWO`%|VK|dsh7XDQk*t1mWFF&=PKO?w6D=z{4TiF*h8uth%8FG!!vVC^ z`$DR4qidb1$+r14XakNY#UfgzU$aM#o0A%PeD;R4!dC(3@bAZABZIAHhEIC)H?!x6 zeaE?gTjrLo3@J^O&~=C;N@;{m_y$EM3)HsY%U2ye_$I~juNXKwA=qZ33*x5R37d2g zQ@Hav*h>5SM{N?#h0v49<{uff^(wXLPa+DE6iHocs~i$B-!gG9l}CT9v<~58ZH(Pm z_>QhQBY13^RqqWqbxp#CTO~Ko3$ml)2PJ3^hrFfU+-qo&_V8B7W-o4u-;rtN0;1CL zxm#EJSY-5f8Ah-??F!x3ok2t%=B^K?%U|B;6{9$>814^hxMWy?wLE(=i-OCJxrR`w z#r2dbJ_%3F$J2K0?IV^5RroRO7$W3q@RSev>b0HAEip_B+r3gSjTq`!ZXVPI?d}m$ zM9Q+y4W0Wd{l*~#3$1zYFwPmEr23uSRJRxF>JcEe2@gZuJf@G<{nhf%4%#nMB@`zEnq2{7Da#S?q9~pkh0Wt z+jE_M$CaY!RihJ?#95)C#ir0%VM-$$7Z>y=p6J;Mx+7 zU9E@NOL%8=_a>^AsHFC;gD@}S_{>V?$#js@Jj_hTBROA?7K$NHC0|afu6p&nxd4!o z(ePt`vGE$du+lKo^RvB?aD02cU*>D)?yA9Es&U|n0O?rLTeyC?QR_aU;r|jiJ|S3}}z-_ev$ojHx#0|T13zuC8_+s&07II@gF zg07%wT}H|s@>n;A0;JY2vO3M>%gTJ6!vq<819P;$m0z;9>pE(5HoRvr{5F+7^ui&j zA4h2?HWqRpyb6hfRJ7PD56A1JinaTt11A+vb?L#Pcdk#xZv}I4mkg*vV(U-XTGK2R zU0#!MwNGJT3NqlHJ!}h*!hAxX+mPwVdbtTP>{sc$2BHV+~@Ek)9*2vA+H-ebBx7?4Tl;eTuMF*Ppws0djxj zJ_)6aFuD|59hx~-*j(mxmr{26r4X;ZybpGYc?B@c#^{i6vICmopNc*3w)^?e9>sz4 z@96E^L65=yLA7Vge-bs~%HXDLfz=C%Iv~I`Y;a_mlbr=DV`Kui9ireGzbg zrK@y;SarqY1od&tb;TX%0)KVjX;J}?3l9*5lIIph&}2KJzhg~+G=2l=m*LRO(J!t7 zKjZedtqV^stm{6Mwbc6@l7al(t|KYM`3{!vW@+0N3lx9%?tB#>^`4u`HHlYd_Ty}E z4x1~jmTE~H=|Xon8*bwoO^KmcjT-wQ|-`t5*k-?;sYNd8UZYp(SA3Pgfh5 z&h~vU&9Sc;wjah!Kog3l^n9MB@v&8RWNH9X)bDt5lTMzosXh8^k8KN1+K{nB^LkEKGjWy#=<4s_*@Ny*lSK}M%<3?lkDJkp~a23v0C z1&v-5*t4`$Qe*c~V?)f;A8?S3@K(0hRS(LRNB|_WH#Nwd>G}v_s~EWYI(? zm~#Y2{iQS=I7e%+d*3d5eB@r3OL5(}TG20w@e&Bs^`^X-!a9vj=O5YODcD$rrwU14 zo3_`vY^rIMi$uOY*8|`MR2ZOKR-UyCYUXPLL`Y2*Mrt1`#TT`G6MmopeetkSeJw40 z7nnc&54PSqDvsdW7ETDkf@^R{aF^f~LXa>x2~Kc#AKW28@L&lH4k5U^y9ak0Tn8U~ zzRvyK`>p%ldjB*G^wjFAI(7QgseSfd+=HxA>?;J37pz4H@8!h_Gre{wfDQIneu8Vx z=QT_DvflL*rzv4QvU~2tnvaOk{)%FR_4dtOdd*>x8?2=-0lzw@2)ohDMrVlyz*~Ac zOIz=~SC{R><^)1|?0#t*Z$`RRz)gDm2T2%z`UVJRGvCF=+Ua^}V8oEC?c!XwF>>r1 zM`WJyr|epiCpL%S{8NFK{1Ac1E%H`44av?Sf7+PzgLx z?Kn>4wh)?K>cs8<+>uPkInV8w?09xE^8pAKP;jVhD?XpgQr`-dolaMp$o}Fo3L+Wz z+|96%rSi<9bp0#23I~cYRw|giw#3eFfxsm?fJCUBd`|F83}sx@6-$AWJa;0X34;;L zzQWUvy0Nmp!9O75+--3d@&L zyj#sLxX^@uyZJ5=1y47;*f11TUE9}&l_s=M6Y?(eDZD@(xHRsIS^vo(y3HdO#VYEg zGTix6nqcSG?uOBx52oIHwmIpYYx(SJh3cNq_E`ZJ3@vPaf_7<|)Rl=;#EZ0wOYe$o zV9L4&kLilw$V2p)_+BE2p`2kkEzBoK_)K;>LrD~?Af;t5`#g7lxFs`Z$Q^h@Ji%D6 z?-!e5OUMou98^s-g(;%PZ63Vwh3eBy zy<|s*?|#G$E^u0N%lo%Y#qx>bgEZGWNnv<@D|T4DTBLzn(FaXqLo$QRANIQi(lsuM zpv6Yz)3UukVg?)gN?!4jx zaq5*`ghT!1`|-zPZ_rqD%j&sqNd-8k_>*ObL6 z;jcl0>?j*W5=g32GOz)h)ow=__VP`>O189EGUYg|?=(4U2Z<6lTa53*mQttZvEKl7 zUdqln`?+z#qZuSua4j&_<8wDhAVW)@>vJ`l>mxKF_$Ra9q5&-kA)=n#)u?AzlTUQ$ z=;d`1EgW)uN`vMU%`?5Bs_@XC*cwj$!(A%ZN?OwnieR4I-I_TKL*?|y?wU$!ClgLi zmA15(-gO{k6-Rs;yzuAwg*xtuYIJ^46SJyZRBo<)aOSkS+q1c(lk=&@L&{(8#N|K= z?IY)pZ|}w(9MGq`F^eXm*xb}3vfOMuK(2Os91&Obp9Q-XJkgbX3$zdNKUJP2?dSP? ztPk+3`^^0K@ZA>j|I$&u&ENgBl|1X^IAY*1tliML#%N_u; zZDfAp7^`Ua+pwCZJ$9kmd-Rvy13Zu+@eY z>opCpJ1H5x{1z(LK{WgWN=#4UUIsM$%5^_xw!DXgY>|Y6ajmcC70(40*9vk3!3il zXS=gTM-QakdIEMm+nLdP%QR z%5uvCp9#6r3TLAovG7RPf4{So`ti0cb`~tF%U@=ap)!_guvZ2#0EF&P-_sJ4yun^G}; ztP9Lv7O6;m^=d7^e;j7vB@wDULwU3PP9dPA=B-iEfLF!exQBbRYT8$rl%H9HY8A?8 zJy#seH&a4hDQilBe>bOfCEDv(H?+KoPOMxBBln19Y%5%C^-hWM*ysu^!lo1k z!yNl5JrsyY`j_IW_2}>n9=t`jtKo4M6_Vo=o$mz(T~kK*wk~gE;N?cEox33FTi3KATZ8NKn&wd7J~nsS=qV-EWJY?O;835lsh z2eS~Ue=G=wY{|53Nqm`MEW#sE~HJWBTwLB_=IkNytaT_5WnY@D>+pS%)d%nSx_2RRC5 z8$S}T%p228x&DG6hp%^`ZvA$+~XXb^--8+jd^C^VLNf;2(1_i+_c0Ah~VopFLvG2Wa;ACSX?VQ+@NQ zXR?$YR8b4%=W`aSyZ5m!Zw7%}03qbHMXIJcqP1JRT7SLP!|(&EIv6*xS~T`eJtEB^ zP;hM1^(ij)l*oO2t66B_E7tfoX< z{sSgEQC?dN>~+sd`ORwl{vnLw1Uu=8U7VLahaN#PzVLoO^hSlna+_5&TIch%o{&Aq ziLxwhX0eV76P=ySa4isCv@oM9Yd_oAZRna(6j&i7T>uL6VA^7(0Mx2zBG<)Zij=q# zfFKzPrBt9rSN=-EX`XdEa=~*FG&Bw#@K^03P$+0!6LLv9>p@}?7NihgKWGd#!+@f%n#{X!j#&($b+v1XXoKAG2 z_O`xaV7h0 zbGE%8g3NO?2nJ~sEKEv*#4S95(CGH%-1s^(TgrO<&Z}P|38yt)p^QpMJZoIAej>^T zVjiQeMAkzmw#Bp*t^TVUL3W=>=mhlxagxEVWpwqkS@x3qn{mz>F2DA|K0U^yHYw_^ zq*@?v6x8)AQ+j@P;di~B&R#2r`e}*_<4$7Kg|MeGeNE}f)8KQLA>>%Rw5jbfC;vo= zANu4hqP_ynY$u*)494=CCnC8=ivcMO&{ux)h->Ia53iE;Pc>h9cd_LS?w4;c<_HZ$ z=pJ=DHuXaZq_7DMG@b3*x)RrP*GSIpGZip;8|Ye~o`~~`9JkoF8-wu!h;kquegr^K zo`+wBe?Zo~gN6Uo!c2y!z!Ksr(;qWC4?Jr!i}~IRfSIx?FT?LG+}Vq0ott-pL_^)rd(_PctD)Z=01U0Oz`eLb*VEYJEH=IV;o}O zb0)o?(EN{NXc@>0KuI9;uX{6Y&2rmB*kZtnpZlk=kmm=Z3FbG}1G@Pl0sn$}ZJ+j@ zWA^hdCR?>TRIDq6aV))gTVolu=Kb>qyCu6GN2iRR;kggWZ@N#AP)C?b2%@Nkl@Lf^Y|S!02JH*&+xT$oW` z00Ighuz%lRYm^}K2AfRofsY3zqA7otuzOHK$R0M!vZcV=V}}JR5`WpUY2oq-^(ONJS+J2AcIL z`h7$P;X^dadlT1;!-z8$kcL~Ao&s)xBOB-Yb8Z8tAZ8`U;}vP9`p)$O=G9nY6(^*W z5MiT;L}H`B9K6jfPp4AI1F=?1T~#P!AE`$sy^fIZu>Q3j6xm|G*J7u6mktT`>F{O< zyBCr6gZQJ@2gfwh@-aE=pSnu6z*$V{7_`4XZ@gQrtszIw*)c6ns`!tgoRUrgi;Rv~ z{O<8!XQos;j@OXQnmU3<-kz0|+pE^Okr;>s${IbR@M>XQz%{9LQ@ z#18u#+VuCfVnY0Eo|Unmz>(1`=N~<5r8tHNS*D6t(uG_~JTGd(LxJMr~z)?~J;T52u-rrJwzZS^`LYjuArVE_&teTGkfzK<%5U=tF1kOqS#e;4pd(lRVZ! zRAMUDuiPCy9WC5;*88Hs-MQbABn$MUuqgDxBE{>_%d9ZDj$E%3HTa@M5=TB0fL+dx zuCtKq6N!*GO%lYs@~~_4%hOCGVPva13?I2rdVJNA^6+9c&bm`wMeF@wU9${ZMgKZw z3?y5gqDMPbQ{*cQ{==Q8<DT*yzUVu4SzY@E7meX6&z#PZ$o;X+alpvN-;} z8$0#%EfU-04>0S=4I(j-R&lFJ{pXzKm~%9CyB42*Nf@+N>#{DPP<5Z9TXYsr6%Gu$ z%06vS*{!ql2qO$BtS!188`ds1gO}b_BW&8giq~_r?zF{U9B1YNn4OE9Wm%JT%6-WM zyo^YnQP+%y4KMdOjZyi{u}hDE`Y&R2E{(52!~&Fk(1^g%io>61{fENiz@~#S`Xq zxuhY%WW3HaS{wUnRM;hUf6Tp7ebI=`cJ?05*FyGEOw1lWXgdCC$;j?t$wZ80mhtJ` z$X8WcS%09KrmOnrD}$qzKH+Xw1FoJc>)o)Qj{0d*X*|NxVriL@Kv~y?8-a*-R8PDD$)H)?$@arZ4zET^DNpda6Ky6 z?#t6qaN-!dqh>9CRPHb>8Fbnh2jdcl`--hZRadnx>IZlrsLneiOXscxZ>Dw-4=FJ7 zj$$Z=2<7%wUFx>mUXa9*;j|Ry8nkim(=2saxNJ9IuVGWAUe_3+uI8^ciOXyLWzgkz zNWCORh*^eJ9K@Um4E<|tAM^lN! zA~^JCyITlM@F(4mMP|&q4^2pFvd6P5$!?$4 z2By;PsBMSG-g~BfZ&4iop}rAO&C|L#5q01*U3z&Ae+S`C6j-@6?7TZBpH9Sdx@Yvy z@}JmDWM1{v;uH_`<62VpA`%q`QJ$x_Uz}{$a}=Vr zOpESi(z3@+^+p)ZhM$*~?0t_p`7zo4bvXrLT$eX*dm)qDU}^%QarhWI&D7&7(9(VO zph&JWp}4Wyk;tj^?*5rsvwb{0$MOKa*lcX%q#9Tg!r1bBU)&67^&bBeNrVE_eUV73u+Lvi{cTRzlhWrGG+!d1d6@ zC$2uidcCs>MZt**%Z-ini<*~$gAJR=p0!a@%ojrV5T1)lU`H_@-$f-;2k=GxS1AR+ z8i3X6tw;<>=Fqp+wX02vCgttJ7F>UJ43ljcOICzk z+pd`R1yy}+WCwX=fRcpZ!Vj0X_$QX6?pp0-b8@I6;ztw{lvgj_l4RL|JSp=$)`gpl zB6PpNpa}?PA_&vTgxRgBtLuEc7gF%J{zMTLiue!ekKQ=Y40L)*d=owT z_I06@8)f`9itT&5Fq*PYOt=htg|}*-_2x^z28$L+vhr6bBsj5|VUmpcji{)7Y#xMt z5UTb8a?|vQqChbbzeL6W)-qV5l@**fPz#uo%opI6b`jfV-SnQ`zG2OCZrVrih?W5HeDi%DB=bQF5 zqo_G+`#2+0nZXllafKu~dX-}4o{yMjtw84U_~$J5b#{3-9SdWj;DQ(0Bz{TM&G!_9 zXuo-u4>v_VtllH^lFh?GklJ-sGB!p>*>(tHvNteX{(wb%WwUU%%K+a zAsVyVAo=vFQ7x6N61JmtK$_m4_Wk(R&(ZEwW6Z^Jw2cTWxvNmETiDry)#t|sSe#$=|{ld_}&XoY*(_Cthav#bk#dv z3;5nOrgq*N(-2mrX2i?j0gDQ2vjfIb7Y zuNNCtjH zMJWY^PJBwedLpY9KC(RuSHin{mL1I|f+~>H>Gv`ijbyf8(Tk=POJxH4qwKxMI~L3q zxZ=t$FJiWEP*Kj{SBgbMJ z@NBOcOW(}z@2-CftWI1ZpQ&`45R6};dmxW><#J4h+iT@HI_@CHw$fgo4IR=B*iBZ% zc&q|Gj+@pms;r+YYxg~KCUB5vY;laNmH&4!H2eAa3^DHBVvhO<_G@-&nE<|CrLdFr z2Vs<)r@&uusTjm9>I$K|N0{S4+PU-qj02`Fx_*CzRg5|&J~_05iV0=nKZ#2FpmXL_ zk)r&Y1K59!%H~jDN_uXxT`l3#=ai81IvF$mCJAxW$>EMWi@{9-NIrN;V1D>0I_Vob z&!WsSf`n1I#_qlmvlC`7ZBpKqS^*d=C~qe6As%CiOF)mex*jA{=^h4(GSaS^OnzHx z=XkVX_si35Dw$gG`r|3K+$R6)X;&%Vjr#LpS@5~8cbslPr-VNoRS7p8Wn#TK0UZhB zZ4H^p&tL0lf^n2DcS?p|>ttoMG};ua9EJ&YO|?iw9&=XsI3`=O>FUys*Yq}T(nM{? zBr@7R>%WeM+mPWFK8)k;R3%jd@h$rJA+AK3s3-v884T;p=+?eIC=EsFRf6LGe;-f z9U{G=lOZK9^qXV$j);GhD$L$}rMrnW7F)8I%P=#j7J;synFU*!LOA!afm)1v6!lW>fUw%e?A;A zqDkPya_d6;93Y1Op)# zOYh&I@zO$WLi)OGDWDl>*^K&>`O-yE&89U?(C7;BckGr}^2#mV2cF(}Nme_)GDhE3 z)exS#LC$;|=AWOg=6OTTy>Au<*cm(z_FTIaY)!LoST9Z^E=|nTi=Y_T7%7=iNK9 z{k2m9Y0gNKP?Qt_pwd-Y?H@7%K(RVStN^4f_BBUm;Q#}jZhs7As}!F;_)W+#XZvzB zhRtMN1Qw-m&F3E%&w3v?nMBWc#;@xMe>B>yf_lCHkx_LeP|t0k zLHeH)rI6bOjUm5C(Mu0gsv*#uAAEc^oRrgbvG=iJ!aaik;aOq+Au6&EaNFzl&NNRGikr4bJ!yTqr-#E8a#`roXPzLT@ zsjfh90g&1;9jlx-NH_`&?yrMI&255{xt=bdeg&cHhp*=+CK=ceG&YM$uP>eI7TAq8 z*6&KF%~P10*|y$j!cv?U%iJGP?z% z5G&M(X5->w9$l&*?IZ|UWMA{JW!VStDGfHXekg1Oz<32w(1&&|^wZ0R(A5%eu2PB+ zC--zvV8)m6#DPu!@OjwXN=b@+ABb~{uBnl1c>tt)q}*G>tv zY>v2I_-u7b`+qBfwnIfEfL^*VFN$Fer`)Ta=#fLAj{XZ_87TaEW17D^Mg$keTKPPS z=CGw}U6yh)nkkPdP)9K(vNG~#A_zj7vVHH6hs*Q^q=BVFxKh?t1y!EV2vIHgJx4$w zLgH?9RQtuA*(cpBju00nD{=|#k=|y=)YD#kltd}iO-G~&8L(Mk^{x6HEHOp`PcP*4!^kc=pIu1IYiXuF&T^hHR&^4ddZqn>v z`Fl%3hs+qwUjSkPzZVm0Z5}-jlY=N!7pO}1&g9=)jjocE*Av`0q)l%)4^AmvO1fQI zEe_!M$zIOs3tHbKjk4{;Kor=;53M@jb_Px|00msrV@1j4q_QI(TYq%2fDSQJFY?-J zmzDa)_f5b_lh-C(?G+TYObv+?6rjJN;9s_|nJy_Z|f$8g8PBIn%{pR=Bn z`U0)U;ae_rRoaTFN_}Vlcbs9Y!RdpAE_q+SXH7^{<2lpf0y)-x;) z{FyF2O&w~~e&&PZ4)s0g2n(2fd#rv-Mf{AS@lK=nPy!>wD&n}Vu4m1mZm=I-6)5Ij zYt=edMWifh!5)>eR3HHCyfPev=y%4dbDsOMdrmHJ#Zp>GQ@0rv>7LX~2S{pMPO2w+ z-MYcVrEyk5O(YgG%iGK()?BNj`!Q!=64MYd)tlZHQ$|9+qQnL zd~{zTO>THV8hO{jvj>A~LoXr8^Eu7*2=`$67{PgbS!(k^UXF?$dy$2TSDaO}9uj8N zB5sw_@!?wG&9KU`BBI&t{ri%!)$ptj$~vVm@k;1h<({SkD~)NGFG%0HHs6oZTH2xQ z+d?K|>pjWiA+6)>w3r z5xi9Gg}FXG@51Os`1@ZC@U}XrgoAc*-<91IZ)`PtjPN2pc}=yBL%VISsd`}@k?%x* zSh`>1sQz`h-NW?C>|M^VkvQLEp~3F3eP55@kbqT4%a!j z)hyWyV`Zx~B{ly<6&i7=ooqQ(@UEQBeQhRznD zv24Pbn)t3t$9Jt3`od8(yd`1Yte=HF8^oQu%T`ofrA3{LUDIY6uC0xcUe;MM^06VG zi6YLq{9D)_nkrY`mjD8R2l%GiM{ME4j;es_(N21eCzmTB2_!wWetJ5B*{*C<@!>fWLRCE0u$+Ng@{<ov#&|7dZPy!uZ*sM{iXd70MBX9&9>&)|$wojF|&FP^AkU zUq`4%yDgR7w+8u^n1F4HTe}MF?BYq^aIo^lM|=OTj>b?=T&METRB}c~++MHm!$13= z`p#Sm$r(mM8bqR-d4vAgy?z)DgmPLkjYlyAx}9dfo@}h|Eb5duRd8a$%uv^uml&10 zRG6@q_-U}qE2>sew#(Ru-nW@$-2Nn1scEyw7^oVd&s`qoD4~o_RDyKheY>Bij49lv zDov=jSDWLHsf1}Pdv7^YEz#8}CaQ#I-JL?B-CiJw@V$(vKPefWulOZdP(sEZ)tAR( zh^4>`$~PSlshWmQt}Fx&4Wb-w*E9|b`cR&um74CmO8caoI3Ch7<$wl0)U&A^j57w6 zHDY|V3!~5;K`XJ)y4h-<#Vo&2Gwa6X;Cm_0;r)gh?5o0h38JaN$3H$Aip-Ip5urCT z1ypxCy4%iC-**e*GF$G0)iFH9pBp8?*V~a7!-g=+sE;G;lSrMgB0*j1SY8W!8>;FO zIF-|f4%)`Mlt&qLD@V|xT1?^Q=tU}|5h>*?XjqOvv=Z0kgwJN7E0RNOEn#lOdfsO^ z(!OOV!DIaMW&$SRu5LwzCbDM3P+ctKY}9&b6lRy+Er`E;KcC_Cb4> zN5fo{5KO1T56U6c8*d$t_szE?9C4BBHb)AK#C+PzX`bUjdF9LMSI=`c( zvg=t!=$3bvOjUCgWtgLkOMBCKu)?IwKhyx{=BI!jBBV{nvP@>T2OGRvWdyyT^6A4^9dB z50%8e(Ho(q@gIG?AJ~OJw`!%h`~ufsvgYNG^N)%J@vUS0lS@JVk6elkQ(#5DyN#t9 zD6x0jA^jGLDMUWez*}15Jb$n`xayptXAXFp5}bcL*(%*=4cnevDpl0i4i=+Q{77X+ zaV}60cYu$I!`kOI~)TA#w;$0@G=ARnBC=GAwQP@nb zO>1gZg+C@)&ioK8@IA`QuOLa>CBoIDQ|g_p59Z|*`c8SqBCZPPb9DSiOQs|nc~)j) zUkdzN1m3?uHKE5};J%tRf8&RmGGMftH2Zfh&-9-JP6rEPN4^Pa#|!$Dk}qKbgu5ldcwQUY5a4V~JI(K&ZX16ddll_RX|ALHw1Z4C3GxEE8N9j z{KV+X7uwr%x@vf#=LF+euLdbSdARHfC!g!{Yd%V880V__B4j>D?Dy>Ty60ds254sw zul((GSd$k4$dJ1GcU)I$-;zpt4uKT$xZi%_>@q+iMD%12Nd5q7KmVDb2&VnxXpY(a z+Hdv~<14>uMSo3m)(OTbx>oO0vrp2Jr#@d_YO; zl$F0}M`q^`*eSiW#kjxyUK3uEWl_P>*!X5N>RHN?*h>H;C}NyAufERx9nvt*!Sfet z#KK@D9w2w+01n6qjP|)5PFf*bdPy#DJvQMFnD|Wavkan#&uZcR--TNoK2ff|TBUS2 z_w*=AOwzGDt>?eJ58xl~Lmndc43SC@|7g1vMjq6;g|e1Lo& z+eGfjy{T|eEgUh;rRBWBO>y52CH>juG-`CJHMASN+oWcMlH9|O3?*mFv!&xVv82~==)lU*B>B5bbtXOt$dK`O%FMkaNMMRO6_Ob@vsacPPM0tqqe7M8)` zAUN>Es$ka;|Jq?IGR(!ow-rHKx)m@xKOrX#H1&rF3C5+Q(UAukFxY0XWu$Nby2Hecrbl#>XibST*t2(n7rQ5o&K-D7(k5s=vnY}SRCT5XssozGOL-)H7ZvVT+qNwM zRUG~f4D+>A88mo-3}-H>k;yZ|DHoHwHanCB<9+rQFmC-cWM6Gfa{`U*FXibd$k9WpsAYglzY0hNndRm4 zCL*?FEll;!hD>4f$=Ii2v@xn~E1S6;vGTpbg`>*PI5H+n*OAcd?%Z@=F5K z;Es#j62zX*9ZME$xnb|{W%&=Ix>j`YhCJA6IwS#!&H0bzvoq62O(-*|@I)E&86g&< zV*Y;(&HuFipMmp9@}g!E>%b*8JNbY8BQoHB-#vr-{NM9`{-5Uu2I&1iHH>rY!rvhcZm;8^_ApgIf>tB<2J=edMKT9G1 z01x@8B!P#hq}cxrH}H5gSje-0f)KD(P2L_(0RiNaTY&xn@qI8vhWqE||6?2sQDw5` zFX$XDP#)}l=m-f_>2@*!Bc$=&ta1MjiN_9nXLFh zb58hw_i*7&2lXL9#Q(*<`I!F<;YdPEED!JkSFyLr?~IR+r%2(5P1igofH>j-F5r5# z`%1%3Bq4x9NHN2q^(xsFw%V$8I5+f%1PLhSuL7t*(v-xm=S?a%0gSqZeo1GJ?`wjM6V1$yHw|7pa8%qllFHmWtdAKONiC)@)1SM2P%b?@xj zfx3#VyTkJGX}^)5d?2suBdde-cUB3ciSl*;hiNJ9e2ZuGbh*LW{wztwzs?zd@bY}#QD$u2mVQneLlBZar!Gc zvujkz5`Dbh8EgjV*-WFE!EAXpjh zgL}^h1OG=iQeN}#n6J6sj{&#jDYk4U>xO7JFlPExQ8;!y|1_l$8U+l$xICOKl~&h? z$m_%KbsrJ^MZ!A^>3^$8I;YsI@WZ;c))G4>%a7onbv*{fp9kv?uRJt35)OBi`0T(r zOYepLNG}~PLTp(9-=T{v?T!saKXF;Dum3jfz|1#BO&Y25BTo7bBVHqE2As-!V zk8zyx_1_wa=g@B&Xz{$*0Jjj3MGyV&BN*u+JZWEm?*-{>@2WzJ&bjBv z>S27ah$1k6s)wLJE{JSO7t{l{T!&MJ&Ma zCm}wnKtDwkDwrqXE2m{?+;1T@Pm!0Uw1^q?#L6T|TJftb47R zD}(M~ssk{a*Vrv-yzT+7cLYvmNd}v0XUx4m@__b46hvR_j;9c_>!5v#0!lji+4$2* zhxfO3%pioNL0B|+4aX_QX z(iY!l@Mr`O?l2qr!ECMoUM|xY;dN+WCEHadG&10cKKm$8>CkrD*JHflLk=juVUqIn z)V!5nF|sO9{KfEAHij^HE$l>0(SzA?LS^eb1;@!DBIkg@UUw5>?a==}j8 z;*#bi#3@M9AjAF5Ls$fv;Vonj{}xzbscuP+8!d(TWFBw1*o}*!3Fz zUPi&~+x;F#9`W*56{ddt_%SeFX<~M=+_*!vsb_)7(DdyDpeUFG)~lSfCdye)s+9!d zp;~*Yfy#EdfqQn=7rvrNCeVi?i_+!%PjO@85l|UaySVRP+dWxnmaS4#Al{59{-nvv{ zmDrgC=X1IOdBUtyn9n|cU(2fm-dH80o)|Re&xY8g zd8!PP*jCh-LK|Z5rXA=WRsF-8CLk92SSD#x0;P%4p})+~>x|1|xwp%-G7kCUo@4!L zsQM_}?<`xY%MMcALFu75(yE6XsTHu?9JF)H;X3VyqU3%?y zM2yOU#b@k})Wu8Kn6&hLV`=5oDo2OYTHk2CJr=w*Bm5RIeCMP6-6_AOQ;TXo&rHUp z0=Ky`IwXIrrAx7jSft9WOw-^l+d%Aq?;TvT;L*5kkQ~EwvJXi+%sc5K4Q^F_hb47S zR8q|It?=_)`77A>izELff!SquIn2eqQ?v%uJ)>G#EZvNn2rTSj>% z(4bmf(m%YW=}qn8_s2BseJwp?M9CfYC@#bz7Mn~aD@p;{d;@8TD363s5rDh22|y$w zzvmR3uRp42A;ZVewb4whoYskm4SPM+fxEuhYMr##95--K;k%0{I=f3~Iv+5KZzcQq z7Y>A^)aQFpY*SnzyElp~JJH`30l^aaWLS|Oz(TM9yo(bon3}y-+~x)W+he^ys*ZgC zpipC?m`o#v#fY1JiSOqp^3%ndoQ-z}#oI^K<1rPWmC%~#PC6v>=3&s)GnqNC#eY41VP3QlIvDp7S&oQ*vJpBdZT#bKB&BK?NX}frW z1g6KS&vjq@Zq0c4IViN>)a%xp5PTN?M0akKc@C6sf;bx1kz6RYB@#aQWEHjm;xf($ zMTLE4x%mGyT7Ehg;l2!&Kw?aH3|a_UCF1?oY~>3u>2}&Wtu6SmOmk!-4e1xjH8uEI z0{V2EfSG5ed+wx_9aXy6k|*`F15%h>8EwA<9szMyez-Q9>caD+ieZjsg_DNOnAZ#S z=A&edmy@9F43E|LoHw{L&q;+{fBOL1nem^p-e3PM|GYOQf$+%nD70s@_)es%m{z8du;l#A-%m;X1ZKayT#3LSd zCC2jT`~<^bZTDri3u;YYDkNw7K0v3|deEvc!+cgA zze7@JKQD0$m>x7p%>9^mASc@`E*)B zZHdns8+OenO7Y36t;oIplYAqoWcqF} zofG;Ck+r$nc3MHbx#LFHh(c<~k!3-LpId?Hj-MR9Pl!2|%ggvB6>xe_05A;D%$ z?pF7(pJvW$U1?#L=*dec$kTioUOmzGm?J5_cDIL=59g2*f59y37o_bM^F8R!W9d%b zMp?Xq6=Tt42B`@YhEdG4lrGOU#=cYdP!i9!W#Bd=oHo9vhT+%iJ;aMmHnzCZZI1t>h{Sa z&Y}KzQ5L|$mmS9P_7BdbCJt^jDT!^zHZWGj_ol))voajYwYDf&lULBSAW`=*J(`}& z-aw4RYn<;{%z0L0n**lKG*|@b5KgE?sLjEX^X~D~HseI($2a{Y&VINYgwLbHG~$b3 z?>EY^4k*s#eg?NN1qMkD7k-&CN>HntzZTYT#%`C`&)KolVP6$#CtzlJD@WFmlZ{1q z5>{jTETY2R$to>5&YJhVvyz{_vvps)FEi;?7HzpgZWMpkgx2c?@yw(jR-lw@hEKwX zFLY<7)PLuxbZ{r+gN-ZYJ3izhA?Yhp5nPp%>f4F)yWr-IW`wzWJp(v9nRjhpwN z?Q+o`)-%)2X8I~4B`YhT*JCIdhAY!d{g}teZWRGK^lCLtmtKl3?~lBcu!>e{dt z>0C-A7nF{7{Qlm*;2h>GXYbs(bMGfxTQ_M*oCvk4I0+4i(-UIaDCLaJSdfUrxD+3T& zq+$?g-u*;}9nn)>i4A(qaeqV^y)y_L@LbmSS}aE=IbE5A#V! zUK08ex;>yocOLik>xpKc6$oO_;9U$JEssLXtF{~>84yF0X#*ouo2m$EwAU$6w3-Q= z#&W@h{Zuv$luRTbd(R+t;B3-C-!xRWn9Bk{3?LxXDJsh(;fFG9aYJ=)MQD!-;~S2A%6orqi6*9(X1RE=%`2AJh>U`X7n2XKrS0 zhMM0sDSPG(-GHqZ`&Ri_6&`KYm^6-0agjTPwoA^e^-o71M*01LLRMU?zrcG5)31C7 z+^D|81$&dHt1Ly5$^PLlQc8ysyCLhV?+V^Luz#IXmU{6(%{0zR=TN5we;pxu&*a21 zfHG_xa(3J4;zz;}*cPN={0-GrtwIW#4uE z$zf?b3(HXa7*1^c#u{v$CUl_vHM}*dP3ifVu(431`N^Y7=NB8i8^PEzT>&&wmeBmR z`ue|NNXynX&dguYT9%G&HXueqt(hxwE)2`=4>{Ly)ovIxY<}FiR>VH(7!Q;~A1cim zk!LCM0x-*9if|8dKL0OwpFCRzHoK5Bl>z79%a%UZ>vj#jPPEZN4T_zMD4X3Vl_0+| z@jd3QmVb_y;T>DOpEI2iDEEpa=jSTg-&av2uj6oxFB(rXmj}+#Q)NP*-E61RAz=Yv zMC2@?SM)`vQoY1TeB#5B;m6$}2W)kh*4{iLqD$yU|?=oEY`NgWEu-$I_ ztF>LOI#3?=>HeH&Xz~=sv}`gF|8AjiYygCl4P0E5i0sGfj<)B7er+HlTFUO7+rjYH zGv?DWb<4OcwM877Be#2d5c?rnQfm-Mc2ywK^6Pc)95_Yxsn#I9pN`%F{*lC-zah`_ ztuOpKhiG<@h*^oU7tyZCqj5Kh|J8P++8o4BgNvfwOd?y^FLg>H`=eUuxRDZt9nnwP z{}eOpy|GRjO8?DG>+xkaj7U*yNBa#;Zjjb^QNSmu(eY@sFpR6{C~v-lt2aBa*{$c& z^hwgshrdTAK)8-e9{;d9ojzHQK&b`1r2Pv6V1rI~|lk|QPD)ZlUU@*kRLjU8> zIL3W6=uL8EHz4LE8jlS%3B|$$hS|ml?&o-~I~F=zadCAzw&z&0FUvNCbn< z+vXvnEfs@#aXWaQjBTDqRT>YwqlPlRyB)Li9%^qiKC6$mx5Am3aD(EUWr64W{r2aH5&JI{vOUWq}i8Z&=gdb9M1bEtHg(#X{h&#-Nhh2R7&bkSo)Vi0(Uv2WHyON(s{P3KY zuLnS<)-$ov$0FOZFc>)KCFgpt5eZEmtbK4)SP^@MT{R)TAImXtRk0BJ=&Pt25cu8- ze%LvZ;dKfG$^5a*dp{eoqiM{uFQ)30k!=&YVCEMX2`Y;0gaZ3`VSnmp?_0Pj@q zU;UT74V84j)QWjzqQMkSIXLPK{VsoqKvHyK+03F0~(o=pE&658kFyXK~QrEeaH!o;t~!u^44&^(jGC z+PMFM5v+f{hwTeh?Mnm@{cdw%9$AWiaG8J>7Kw*?j#OdO5!n$|Xh@}1AS|%uV-l2* z1+1A>!MkHxt!gZN#`$~j5Oa9M+tP!hXavDkR@Ku!oz&-OSteZ)1eP1a)HjONIcG8j z*%|SDsti(6RIK=hDO(bd4AaGyWZqdRF)2=!y3GA~sTi9-C#{b4);viM)m70r<9b;B zxn(q=A9n^XWlkncwK_}oV@ex$!A=HqjR<#=fv-!xw~UL$XH~SUeM@nFM%P=7UD4~t zl=dZlX2~pTw+UuPt5XY(tdK#I!$n~ZE_jq}qTae{YIK~Qp*C;=m8*Hy;^O}Ts4PI8 zh_OpmNsl-%8<&_fLBu(J?r1!d}I2S+ysB2zZz_w)Tc|h= z8^0rW)uoukQNXMjkAUp~q^{*VLMRT8Wd*amO=DfkRbSLJoX`Ehee+GEI~H3O2@_(f z)lL$>8*04=D?eN6${>I=+Q(<18LZi^2^>cZr-5cR;+>~;cd2vrw1EJQ^o!ge+Fo5+Afk=OMw2; zmuhqQb$+HI>0H`bbhrIG3G>ZsL-{19H@Wzf*`uB}3`|Qw_7pwSGW@+}SDXd@U4DZv z%hlh?F=3^v6-N^~W_?{K)pFp}Jqtzi1ft;SJPBwXB|?naM<@-Ie`%HC5oHexfWDBt zaa3n1_<_z*q#i09Tg117*}O`K7DFeUHE%0VSi7IAU3PTUX;TQ|M%wcC+E4Ff&OoX) zC~cP25D8JL&~poXa%f0;AUx`CTA}TS zc%goUgAWY6$m0jZ)jDCS2poo(`D24UAH`CSi8v5QwW-UgSgEcZ3SC)bo338j&q{hJ!!1 zPt450&d~HFi>!3TF`eeg-p?dY`-oW~S_{h-<_9i#Nq_z1&n-M>y>Neo0yS+=K+Acl zbk=niU2Y#M)4qwS6SD#XQ~Y<1XFOO{xb87(V*)?~y;qWp`rq&HNv7`H?D?02?PXZe z)nalutc0LeTQ#No#j+|{jqiB`r;mLioam#||Huhi?Ud?qu{VAsX!$gN zJ7dZSM3VBli}D|A?U8bg8zwZ;aES8MCR?JzTKMhW{=g-ICfHRm6#wI6GLpwJS1pI) z6D0B4c{5@Zlfd^d9RZY~y~@9N!(HGU9?hYFs$Ev=&#>9 zv*^Nuq7^xX={k(a+AhzxYcIt`h}&D$ng|SY;V;o8&6r|0X62Wzw}`L?xn;}YjIqgs zNwZ_Zj!ORm|Nh;v?fwe{rc%v(ML4BC7wttgL$QDVU?qp$jp>48=coX|9k)<0F~CTf zHHlO)^cWJ_B!Xw5x%Jf)9AA8+$d5_wQ zW>7EJBUYMge@A&iO$)vX#IbU;&U5^F#TNQ@m?Qfx{Q6UKNz(WCAF)~MV@Z{@?hO~V z8~<3*7-xO&Vq-gOkNA(bCGkV3U74L5pLD;h-h8qxDFd{=_pONaP)9sS?23;Iw5U%Q z$Vojf6@qYN!;EIrw*^wS1_f@&>`T+Wr=}8_c=Uh&kZAYdG18H$i>z+nWEO$r?FG=m zZpR2|twr_bv(otnsN8jq?FaoDqaf#irR_gHof}X);F>%vHo0L=UGY0vQzCl!J& zbgH1UK^hcuR;KoGmMj_=8mNQDcSy!Gy%n3oLa3zOMX|7~k->W3h<9bQ|1;i~rpfTg zPPfc-RUBE(AbcFAq~NK*Hldl>C#4ypi6hBR-RZ~e38@#%Nz~fxmi=aKBlQV{6ZZh2 zY!hIL!6Z~inLhD4LJfZ+PEE>Vb&=;WdMbepMzk?$GMvss_cj{4@Hl1SH=Gc{WaUVm zt)`m1)D%~SKRXm{$Uk$o^-YI__CpbBc5niuebF1%_56p~$73utVBBIWiND1j`(hzh za)q)}D^}?EvAxz>QC~N`G;X~Ho%TN*yEXhS+7YIVx_0B66>I+;2&bbW3 zm{l7~mCv+~0)hL}y0}Z;HCAjm9R4D`Xp+btWWTl|{$%P}8IG{F0JS!nc0I=c7SbO; zMa7w4iQR1U6!Sdw#lQCt*ScRq&-ppG;FMk9WM!Mu zk9)aj{E*tn&_qZKi;C~GesJ;a(9`9kH5*mcZK?+VJn|mrx=F6=hCU|S0W4$pC8@V? zTmx~bE0($uLsB#90|pX38S^&SPC3}1@7r&75~L!T);ze!!A0Gh^Q9Ap8FgNYqTP4y z8Hf}$nl@+r_wg}nif9-muBcYQx)Pyr0=e;OC1WUum$;Xa^i5o~pgh;UsgF?^3q`5n zIxzM{+rb(rVqX^TQWirF-05w_p|ZQX?^XQtC9~g-NkOf;X4H7{gsJDM2pg{Mb>LLt z94Y3RpwzfpY8x>ng03NwZ_(5E;7(d*TlzTN=FU%lW(XG8>0fAO5;v?U+iL_YWWfq+ zv0+3uHhiQnM&ri)1pG?BC*SfIF7U}LRx^g|a*$h~hg;j=rFuhZ)UBkd>kHlpxyHGE zkUm_}MmQq(`1T!x$ zZ@snQSCfCT6M z&-0p9+V>9Jh{w~X$C={zyVfhKT;>>R6XT&u#vay#$9F5o`E!qL{4Z4uikJ!wc~Ek? zc?!JV#Fzd&~d zpoS7d*AXIaz^rX;EJb8-K;vt9S-<^^3ZMvN)wy};DyEOLx&P!HHP1d7h_?D6LY|W? zU63iE40{dT#Uuh5Y>005@yN%xt%A|phk-)5QkX3yfz`mY+VaEQ8lmr3TMDPWqvj6n zeS{!2GQ%R#izE|xh?$LliR31ynlhA*HIdV}Pn;7$coNtQq(fDtShJN7Q%X!|7n5fM zcyfq@i>c`}KTC?NWJ8qHsP|gHn#qx)7;3)KZcCoSid4j*>slaq(PdM`IO$ZMGQ`z7 zUf|C#=sb^AM-tQ*$H2sTOx|5LSC(ltwl`MCBqfsI|Iy| zamgB@YE>()&ocdW%lMF^h9q1VWwtB-BFWVDi=eI{rIC?!t7m84QFw>*>DvH7)~Zpz3$GsAn^x(LQ$27mF^Zawm&J&Z@Rtb#&tg{>clZV&u&=l|0J|NQ{!lyje1x*1a`upbcjFp zAb`yGx{dLEi&V1xyG97{4p3LAOMeWC&sD4$mulp~^lg5#Wo2hrE0`l`!+|&uwR?X2 z4JA6}C;S3BC5OW=izF6)%*D-}xl|-Tr*DTTq_X`nL786Zp;z;05@I@a?o1TlBa=l6 zy+pOw?d;|vB?BS&*dd5G{R}8xt2)?#hYg1g5&>u4)sX7^t?HW$YuXS7xPVFx^UNbb^&wk(`#!BS`0qb?*Dn>N{38b8X^yooqC!ol^6>u4 zA(@|O>0V<+>2s&V8(C)xU!R$1e=X$EZhRwk4|)tr6JC_h+z$q>Fk`#0-N%j4Vv+xsuld#xsU5>7XP)M2& zHwEG!YOQ0-*1m*Sfu>#IWr5iuchT-m9aCv*Pg@1foyK%>o+{?ViEDoT;b zs)BfO(MO+nDi0XJkcg*=V~6%|gWk^Id{VKqIZLx$604FEvXiuAajngrvfbFqXQe|aa*SZ@?3ElP zk|Y@i!U1V}s1EM1SPSVI-*{Zo>pj;3Rn?9g=a0St~_c6HlArPoP|Z>1FN}k9aaZ9>_5a&qj(7HGm5pR#aqtU0$b?ked7bN1`WEpN%oG${J?=w>7V>#mYsk! zO?jQm@D))&tZ8_m*~N(B6==%LVP6X;#TMm=doIV5v}j+1d9d1{h&WkxvRV&SQ9aD&eJkT6vy!q5SQ z3>4m7&IvlALeax9^_g0gx4c`tVZ(5@BZKY4J8`aH3Gnks#_B!?QTiq4eruj7j7+E* z;O05`P-S8HldF+jcuDqp^Tk#^p;9Bbm`=aMh^#~AdblBmiwucL1UJ zoN`PqFFKU+xR?^fsbfGx9-*ST@i~p3x_1j1M*0(`uCUKlt9^Gn;u~BaIp>2u$h=w! z#jrcsOY;1+Lpyok5O~BBBjQA@p8n@<4t&1zsMIn!zo!#Jl~0-s-Q?U?&&D(0O?$rB z{0L~Sjt*2y$`|VS10?5bST}IFiUQ#R4Cj^VD;?(NjcZcvE&KBq|1>L;8i!MtCyzZk zAE^l2%_P4rK~LsCH3j|WW)XT`mu&I>C69o7liS9Z_H}373)T?cganZZ-0nZ=7t6lM zg)=4Yg3#)XTdAVtEOqHi}pAF730Yc@u6i> z5ppByCiQjS?x{H+iC1d&E8ja;F8QzDpoPyOIagqu`0A(Y9jN^Su8mS?$NMb3@#9!YLV>ip-_dzl(spct6k=P>y4W}!Ji zpuJ0r>awB1bGW#c$MPGVhW7oreWEb@DKfBv_aw`EhxVch?!-wt z-r#dMarPX8$yS=uF69liDW~gc;e()sJet_x#%`V=qrD44qUlE+(Z!iK&c41gd;CN? zi1q%Dd&tW27ADI9I3zXHHEX0|EaYH1EG87O>)2B}Z#R4T%1pskt0jS;>12WWNiAa8w(h-ARqDGISWxeEh(Cvtb zZE_?B#$Bq5}!kbWMd$saz#-@1;Vou-xfytRNN-}v!giSl<#OS{r&e=XWDIa zDjbm4Y`iNS5a7|@9iIoCY0-R*Enb(rK9!4+9+duKF!K0dkrl4m zu`=A}@L?eErKG}$9Yk4b@WbS!mpvv2fOkg)g|FBrKGRf^O4~tD#WL)Iq?%-sx0rKG z834Ur6@9~l3myL(fx;^Z>qAW6kAEztZ*#YCuPnkVYw5>L(VB4^>xf{uf=;kYhGNS> z#>Q)57iPKNgoF)DziygiE~b>E1_cC^wJvtGBzVb9eU`mK5Ln^EXx&T8V2~_;-`!6o zH@mXbKO?(&DQ8-DHjKVtOjse;JY5C4{6}Qt+!-3d=Gt#KcYE@-)iY!w(-$Q*mpf^1y$*I@^)lO0hk zR?eTO^bLY>p1HTC|EETY!bS5`Y!NxvO_6J`7Lag=tG=Xrr;Li3Z;STtUYM=z@IKWL+7LAZ9 zOWX+lEQFqypKK`5&4StO7)Zq-{Nl=6i2&l>oN=Ykoy|MXl>-R<)a07(&|FySJYKv( z1ou__=`@e4_Ev7Up8Y4iZ7Yjq{c4%!zp=_7(4YArq9-!6Qb#JSlz+k?o^*!ZNB?-; z>mz$X`enzc@mlHd3jovs`X+2skUV!WfCHc$=eMu8`w8NO>ppW#vy4{6z{2kXeRL|6 zc9D6&CSSc`FaiyeXkDT;`S0vxtm)gT4~dq1`v_oQX{6{u+s9-abU;Y8zUkvd9#sx; zK(liPtJ7#8zf)EIU!~uO_IAw)sv5M1_)s6?9o5KW26168+eee(=I8wx|K{E?AfM=3 zYT7FJv$>d>%jlRhdHd`{{o}LEg6GD#-pDk)){k3$eKpl+SXmEj@&d88q$|o!421e2 z;kC&F_21Y&K3+W9%!8G<^M;AwT*@lcrz8$zB|R0iej#SwJkkuOQ}T!k}CI*z3v`SgAelR%R~ep6SydIIaZ0n^?) zCEMtE?bd;Ek`x!t{!?Jx8rsnAn8ZeU5kP_l0p)`H>{rLRD(GUJ4Db!g+NijdKw?9~ zg4>rrz9eruL@Nr1ejOUKFW@SMx)Hk}Rqf`&S(pNLNN%J7QOX5?#%i(`nzHNtc zac&&PXAhF8n)}%V9z2KxF?=DNH~&$s_0&tP2Y>4SQkUG$iWIU6?kmi^%W!=1pz~Oc zxF1*iFz?31+Ws3qN0QCPhIGJq`6={4$I3=Tp-~9>3}AE1t*Y zA6wDl4NM70-DbNMgZta2O8$Zloi#td0Op&xZd=p@?hyNw=8m_LA?`@PF|v^OJd(4T zGw}s#`lyZNTdC`OPqE@vZq|<4|90RRHVdV6RVI2P2NXDw-3t8r@kvC(BIki3$kfG8 zN+r$-ORbxc)^6K~{|3+ZdW}K`hcm)az?3bL)UgAL*?*2iX=bHI@Ue){Q%6_41ogEh z5$QKCgjAN~_;^}QaWI+ivI%Hx6vHQ1oOA1F2tY`MBlu` zFo$-hgm=U_jRTel>sbY(l%oA)^Hq=3U3UkCoJB)W;nOZR+*)dI9NwDX5=mmPtam|U zYyMankIdAhYC8vhd{J=kzaTG9Xev3nN3d?E*)b;0p@Bh>I1_;5;zH_iq=K+pnaRXy zT(U!Kzbz?Ybc4>YAa4y*r;X&~S5+uoF=6_7U*iP|Zx~^N!-pZ-ZLIk>X$c_GZTN_R z&Bnu(|IUyC;d0m>-LbqjldOv$92J=J!v4#|NDzuP0&bO8l`*wVtX3{q5ee~LEn5Q2gT53?_}VTvXj1%b56+-qR3w>9vY(0WLGBEe&@<^ zt?LJj`XgS8k^f7XkjDS7$YZ)=&R$?^It7~YdYhd>JIjk(+(t*-W$X*--DYN8>M=-) zBqhk*8DQsRFpQ#Y=4XO?&m@nowc?Zh7ySWv* zo|io8XfyG+1JtCeOI6_g3^k&5gLW@Qt~Z_lOg7Fv5&jn}p`n@w{elmP4Y0l~iDTGf zRW-HIdJ7|Z@#EY0WGf|UygiqHi6>QOIm-fWzdMf!N?y0uTl|#K3cuseb{ETSSo3O^ zx}Cv*9^W$VoiBxLXorG9m-VUm-q(0oMt}b*cUpmLBk#QzTGLfE#CNi7CHo)J$^?Gx zhuaX#VgUeD|Lj+F^#{&019!Q>-UN`xJ7G$y?Sa7@fV}7kET*mn1^{Da|LN7$lVPVR z*(`ypn)O1_I0Mk2kglv-R%{_4TC|tn>N{GuU6rUN`E|aHa$EA`P?e*iQ7qXd5$XNWQrnf4=H1P z_&DNVVdv14w-+6^LtdADY)4(ikpJ-liPqiAS>?kz$d&3$LI~|h23y~IqF3Pg$u(tt zjT)&0R^yJv0&E@)tYd7+=YZmL2H1A>ju=kS`8yh3MqTK?Efd&d<<7+q57#iWS}A2E z*7uEY13AJ6WKpk@z$4UNpBq0KNx+y#$gqXrax?>-KoyKD|UO6|uo1lbz#OA%#)kv^M|Y~PK_%63=$WaaRoxOIp4 zqA~&3PqF1Nr4}X7f^I$Wu-RdsXC*J${nbj*?&1k`nm^)ISS3)^sIPUcnG| zER8IwKV8A$o9#h38m7MCOjXR392x$I>2NC91vh8I0&$hRm{8z;dxh(l^=M9%ct>rc z<)~bcwG!51BI2UTkHKB~q4#S|S2Xnk59fg7XJmZW>y1RjoBO7oERYnn?5;|=qM0~r zubKHx;^5lR=P)x7N#N88?)oktrO$UT7GE~CzN$efWlVQ%7e2$VbxcwtjpA(H?L>XI z!%%E)CL&wqzE`^Ly^}AVAx+4w>m6y$zeqz0ah`aap^_hBmbNG6`3gVuv3^~f{3tso z-e1Dw{lRKEvr(7zp&42NcfYe6!f}4Yr2_UeyBM4tY}oDtehIn!#tXbltqZtIuQ!MC z9(gLH_M}It83Ufyx-CBn1_uyAO!!0V_1+lz|kS_a@7q@LnfLtx_-LwjT}TfT)~`ieL;pAiPH2~56FUM=vc+&$I__P&`O`jw)(R6+ITZ}W_992&1#AxDK|81ZIlPg2;Pb?I~~f;c7I&++em8rsU*e! z0%;R^9r(KSr8?VpzlesA$(W9SzLv@uK-7S=!}HA(@~OIO9{q@7N!#RQYZ`bH6rQA@ z-brTeZ?)Q{YoRH=OPrA+Rw3w@k>!;2h0j28ugz7tcPkr8?#lXh*lmembyns*g}SJV zf}DC>;0F)$ojTpu^|edU&kr$Ow_UjjJ>Q1tHmSLgBX|6oy6o0lKM#K>DAh8lW$0o> zw+keKw3qQ7dC_**^Jv(-3fgWtq1dA{0t6*j;ii_o-f=I}W8@O=H&n>#l9PBr&;Cuw zxDy{H{H3pgUgiB!cC@OR0<>oR5ikcmhT=LB0#JoFu<>R*l6@LlWjdBt%jt0*h7eZS z6!~~9Jq-B*vfyJj+Y$@FT=^yx_vko0%B#5oau86y))jU@&EsE;^?b1?GwKxc9Jw9KNkr z61Lxe?I-g?42e6K(80e&{6)Xsi{3^50+w->!{Qunq#~FVF)7RO`E3*_y?V;CL-uVG zBT@9KN^Ki6fpS9^x?lA*7=wL9`mY)%|D4qBoRa6 ziV@6gmwn4WY^0jj^xu}FQP|Kb5sY{?0f=Qvkr79_|M1~GqFfJV0qgEccTYaad)(OLLtu6H z4BvkHD-oYs*F@^jAZ7y0v?jB~?~L>M#&9KqzRk}DUyI>n$`uz%&-V#SILnNmJh@wD z=qF9`0G6)s53R{-oZliHy-Fk;KXrTF{mpdYDoA)k)l?@vs$`)1l#r`C`K_Pe2I<6V zv$^QR#9lVGgL}h&h_Q#u@d!=qpUk%$r+KzesM}=eT)YsBExhI((cBxynywK%jd z5lR|z5Htyb-#kR%3D=%!wq0podh1}GXIQevTReASPKREtM!K+#sTBigx! zpKiPoy~}>;U@gqBt54z>SVtC1rFbqpDCg9lQu54D$ zscA6l&c_(sPL`Zr3UQix!x_!TyioGP7^gAmwEwB^DP4R|p~9zR;Bt%|j*dPIRim?U zv*qAa$w^baa{h~Lz$$}^I$?(VCNdkhGgk&_Y4NwV^zLh7;b}$3HqJpitkz5MS)tdg ziL!B|y?uC|T=T1%u$`lWRv zj!e9aXF4l50BUAl^_K7jVO^~Os{v{wku_Ivt`jAHjVdB;7mrHpd+U-hpJz)%p~=Z{VLzAw*;)H>wCHBySmbeQpaU0;AR$Wss!AScFd zo-@IvAS>Bi^lpNW^KQJ08>6;1<36#wKhSddr_!-3T2nurw`XOKTFHYgs&*d+G=$Qz zCZNxk0zy4PFe(9zWZKN>uj0f=jZQ|t-2TL?6;Z-rtL%uNCEL&^itVXgj^k6tk_3NV zuUE?sS?9p&l%>JpCpdChilSUwts7okL$|!BIr*(ab!nWqE%U$82r=UdUQ7b#8kMFM(cCdA3x_Z-p^BW2` z_HchykSv`&3t}mbVJ*YCN!~Hp6_bvL6{^IN2 z(VKjA5ya9>`Oz zlexf-TFpL`q_+EdTlNF~h3^1isK6ZPs*AYW;uHW_97Qa$BsY-)DUb0--aU=sRYEz{M zgcXy-a3p6FWgtd2-L4_VgCownyb&k*@VJiq{-pafxw2f+g8V?)fUB0R)oFp}kJWs3 z!BK{-&PinDiEm5s(r*{zJ(iOB{Vac9)^2fL*T3iH>{Ehxaw9DyU24ms?My1liR4>c z>DV=T-)qK{*XqX*ZhkKr?XAZB8m*lIz9GxAF#O0~V%Ou7SC5{=jt%r%xkmcBT7i!( z`LQ-q#P@O+gDhpbKA}aAOV{sy?r2U*yxYx>nKa0XWrZ$4ICS7PQbZg@A@tLUAX|_q zhacRRqwXEz;D7Ewic!YI4ruV1Nfh4$Z+}Hds+D=y62J75s(mt*xIlQ_UQY!{sTt?H z&i#A^C^x|N8%rB8#Q0=r`3=vOb$@C*V-k0>VBv!RWb0)+eU`Orz2>5Fmq+KSzxb0m zkZ>^56**TaNW%Ue=@aa!X)+Y$2UWQ|MLJ){m5T{^Yh z{UHEvm~)OyP^5>CuRR?tdW}NB&={le=Kg!>z1|5C_oAwm_k6Dy=Fmhv{P&Ce{3YZ# zfG1e#sDxS&ny3F?gZLDAc+k9M$#voY84()!vu@cE}M`BgU!XII1eB4KRMWBPeUlr?pE;sE^>U~%8@jF zTM8a{>8j48bjiaQr%L+6dV)Do(ryJlaByU!Mb7y9IjpqQiKt$qZiW70N>!fpJMiY$ zNbHQH!4mKPjuM-#nN7Dn{eee4&p`!{N&U4oBU=)^ObaiIluy9xz964_aJj6oOkceE z$7u(X8?GY)g4c<8$=l=5(*2uw!qvgG)tmR0UBKgJE!m;)iCGrJ@`E`wN>?j{%W$_y zi}W!5p{(FD7<8PK>TLU9rQNx_+cvUf_(Tz>ZF<9*8NQbe;47 zbyEsgA&EhIf;$U=<^*?o+))A`|F9L+l0`Q?y{S$YM-iXI#;>j0EFEfGt+))dkvk#U^g;X1L~A zBPZVlnbZ!>ssU`9Dfx;%5-;!0pGb;qK)L~Rga!M)hzuGqBFIeX?h9=X&bZjX`* zs#2FSEci3{w8#T<#y8Z;1FMfVe-gu9#YPP;M|-Y`HeTL2-4_Z}-Jmct8R{BuELc_l zi9XG-9!mTx8ULS4R-~SWEw~S0s1>24nWRhBGgOWjhg3#4k*u65+3Qe^+r6>uzr{x% z-s9H1t^H%JTKilzXe0Ov%X9#Bycr(vFJ#{%E~0hxG8^+1EhQyvf{+cA_9eU^Z9sr2 zo77d<6YyG%_OVJnYoc%LAV3WkhMy^~Ct_ua3tsI_7je?TP~GaLe0qXO@f{+H3JZsa ztmQT}tsBA(3EAvW4rv4z9$aiurZL!#JOP&il5Aw>`4T&(8h4`vKJ1SF3T3r^vJHp_ z!K`aV-O0G2xyKv-RmhL8FH={S)HUz?xw|7T=IA!9N~Z=D|G zR)2#+o|sF3ofr0o=rZfVYA6b;N&MRdraDh!EKYso)vGaQFY0;hy%1cD!a+2AEc=kf zI;~G=Q6C@j&)`kbLyps4JYmk7szkmhw$%dwL|nJX^!(=C?>!0l&99Oi7%p}*9nV`s z5Bown+k9c{d5+S9H_rJ!DNYR}hMeOecgL)aJS2vASX^XN%+natjD{DOs-|+z!gJ<# z1&$Y9$9CQ9&Y}x@Y(t&lhGA6}{-DiC6i;=0@U&?pxWMQlU}nJXZKI?a7v30lxKC;r zrs)2n{$uf(Ico@=F=|x)&hBpBp=rUJT+F-TBs$DqI%o=egp-Ul4Fy*$^WhNs%Vt|R z;Z|FLTi?9Q;K4Je*AWg+%YP0v-1}nCy-&EHia@UO=WplLBE`aXClz6Jr zkWb>_tP+_SQzKns<*wV2&w`mlUt|#ff`5OLDKzVfY_#~;+Sqz#qZRN(Lhx7+G-DT!f$&OUr6?9<=|UWX!|?63*}8(i&4(}}(Q{>fstlCMTA_smZkEh4iGnr23Q zm8v}N7nIP2oCyuQALqF}SMCqaw|+A>U99~UP7TM+AFoQcz6p3RHfn1dGp!AO;ywIs z#^bu7_ukTZ0{ljItO&;e^FJLO+l>4+Tdi+Z%}#5#&5iCE>MN}@$Gk!d|5$R!)uP$u zyZTT(Z&F+%ETUQQj+cEjY2I_)yZ-r`eZa zZM@cK^m|aAeX)P#HY=~`lC*yqHZZR8FL21Rz^*|k>{X-W@-kZP6z?jVpr}i#ot?|E zZgsKLQgG@K>A))K4dLxn^K$tkLFB9EYMl@oF=U`oV&7^bm{-qbNNBuQ;Q(9rv18r}%k4PM;kWLc4Z z<9?1zpy+UZi|8KNLc-61fd(SVCUoc*Jr$bY(1N3dj_9K#pNIuX#QHjs%@2wzxE=Mk z5`%BA9ZE{-%<;1|XISN)ZR$D7EnIV^H{%U6o~+mh=#qAHUngyEpX)^p6LHdkO$Gi2 zf4$y_VSx@pcYEi0)3a9UIMhg!FXNxY-7lGm}O;)QbpE? zv^bHWTNei14MJzqG8u82M zlbqFuiLUJBkQGSZL-hpV>9DT$4^l{>lebc{3wSsF+ciXld;4W2XX}X?wD^ZW>WR@? zYdNJR|K{WJU9=TS=*f?9ESTnG@FcqVDFPF9N`Q0rXL2@p{^xh=DTsl(crvXY|T; zi6bCR)mRv_Ns*p6e7E$7Asav)l{ZCrkU^QU=x;oDeqGd`_GtEZd<78LN&4onIlUd4 zd3#eUzXD0X=|_u~wZPkvwm(;OQ?@77bGPCG6r>dH4#-{ix!Y&Y#IHe$a=Pu| z4VHd0B_||N9YZ&@6}yu~s!nEW_q%7g4Y94Q=O)&Cy|Qb9;PZQ>5$d^F_zC9zj7bM!GLDFn*h`rildA6#zubpy__5haDRiA`B*Wrk8q0v+aayBYHigSSV1 z#Kx?2skuG--m3VEcPn-Smi8Z!K|s@nc}`q6@ccUKNxryd5n*;V!o@T zAv+>=FC^=S-XRqys@b&HNg;I^SF&a*W=_m2W5O}6&7485ZQeM4Qx1F@|C>-eI+bS$ z5Qea$NJbA6vBVR&!Nt-luU8`4^;^Z>1EEm)KF}P_`#H|%Iz8S}T!nja?XGD%|DhkQ zf38;@?PadEVgMfxegI`}Pf?SbIsPO&pk!+O!MV-=NNFQ9&1~y+nz{F!)TP2?nfw>?S^AnmfjFMuT0O>6>6_X+4>Bp3kM}Bz zi5t#saS}JK$6NFn4d3{T@snx|-kZm<7MzvMeLGti{;%dI-CcX`|6-=fhYOBHM?Yp; zLH^)dJ_#wSUpiTccBH^W|_#)r<{ zx1N_P(bTKFqZsKNmYr_V4aguhRpL2c-!J?$%(#GRJdE#WjCyh;e<*6a9R8A@l)1GS z(X&ar@2G@T&-?C1aYEL3%>5V@^q%XbBfA5dtNw_@3!d_-qRSw=;MwtyS5(~Nu^6Y~ z4OmuJxC_+Ai)W%Sq$=(;@oR^zG-V{<0GtIGWBq2CWY&64M{&@kSh3vJ2Spt62DH|A zk0il~C?{wW!~bLNEraS>y1&675uBjG-CcqnJS4aV_uy{9J-CP9!AY>-?(Xic2Pe48 zL1vTZx%cMIoBxOT@>b2fQ+4XF>#%q4?$yhBb+6z0)`{W8J8P{j^UPmaw1XNYPEs=- z6|`6&Ic3+AMhKs#_Rch~>kQw#Dt1a;6Wv$%*3`0hS*R-3nG(Gy>P3?^{UukiU`Qg% zTFtTZJ2Z8!(I0VGBQ_}P%_@#s*@kA>ZP(q)v`ga^T`;ER^wU?os-wdF{>4*_@c`UY z7I%T4YwN0aoPdKOsC(q*5vqyxsn@w~xS)AT(>b1`M_{bD=otx09I^iNR=&Tct z*3*iXpYY~kUG4$WBFe3O=Bokjs-%{mVufE8r2fRE`{AKCyHYll5!=2nh?gauQa6Th zu9ojtLOVO9Goq}XRZ^%mIz`x$xL=6<0SQBSD#FeaVah*ks+fhV0M-+U<)9=##y&f9(7>Gg?NS=Wz9moTuo?9qEUZfIKf@Ba~^@USeFw7M8a%f~Kqhyro#g6Bv4XUPmkIK~>g@3f(9Cb#r%j zhhDO&YQ?kwsTE-Pfs>}?8+?a(0E8M|Jn!CuVL{>*bH^!S)d0g@?IZ+%Xp?ryNpbwj z+@!9`eE2=~r|7@HOnqj$p5khPPJ4l2ZO)j=0RKE9IRb!4;j3IOQ(o4td_q{CHBJj> zn6kw_4+XGHzxzMwrBY|K4dkNxn6_4Ac71)_M!;QYb6E{!u7qo@%6=)8!zVQ9JNvY%}A^wEkD`Jj$?2<2eSSi$uFE$ z-Fi|vRaNTK&B5rK^W7vav)lLb|Drp!z#}>iE2zDs_vKps4IcL&ffXDscYzvSZG{Mc z|AbWtdI@zO#MfjH0jhJBS?J6YL5`|>ZPt~NzsbKL1B#{OZ+bak>vJ<+bQ zADm+gz!1{Z>LV%$AoGZ+PZuTenW+oEeIW${cjcw%#qQFKW8=N2Q0*zqZS3>pCIiUz zC|VZd-+jpU+r_l~fK(uPf&WlQb2GT))~8*`EA*@SbGCtb_LX}l4698h7#b<0u2vx<*w}MraSQ-|n zAxgVHUmgF5Bw1$IFLluB`@H|^c+GHcrj*s7=QF$H#QJZVkL}lpz7_vR^Syk;M<$iS z{A^)Nmb!^;6%!xNz$|uOsE=YQS8i7rFhaB*C0nvdJ@yM<`uN&tpzsd9C30eE-ZxESx>nAR4YzqEqu zf3!kSkRZk?%?J67uTsG|cK-H()CIs6kR)*K{T{uUv!-#{*m|~6etbV915I`w^WIFY z7EN~2PPJ0;F9Np6_yXE}7Nzn(DAsQt!K#Ab0Kssr8ed%kWZx4(zBNB;}8TI_J5WBgS|azcc&FkLl<_?GK=7JyqjhScg|u5!Yt z&>LW@Z(ACsS=|R~q!qaq7o;=}cN}?PK1P~vIi+Gn6B9MVXC*G5VR0<+UDo&QuZ8;O z4P<413JPkcSb9H*oO#70kysUJa8BWN`-`(ciQ75=5VKsHY&>Vk0Z$BR4=qlLwtdnH zz3bq4*xIKy=`WFseRY%OPZ}h*G=5xAm_tCYdv`dzoQ&PhSWvzfmAm3PF}&$|(KQyB zqM!}yMbg@&sb2Rpb9u0lsX{*1rm@FP$o=`iXF-}aeB4T_%EVK1IesUBy-5D^++{hl zjW_e)!ZP~QZAwnu-5%DJX2R1V?oXeb1Rq?yIEvKhtPtCp-eVoV(R>FzeVxEYR~oHf z{?){M(Y4G=zhV-50H_NXZ2QAgyZNn=<(%%6;=w>S!5b}m7R2Y%E?!A08G{UFlTlhM zL_GSQwuMWh2Ddlvu)$2sm->MW%F&9KX%L<1iVgx+AY76UOQZOE?|>9b{ox>(K^)-|5V}Gk$#tmDmz=i033!} zoc*@*7gGQ})+6LPg$UJLWy%-l8#t6L&cP(g@*{7;m^rxw;E1xsp5Np^PT1^6m4QTZ zqGE0Mr@2*O_|9_bC}-1~(os$-3>@o2)iB6GFG&8d3H-@>N+rPbV$rQ3U*#I?br8mTRJT| zj@h1h+|zO!Z!&H6t+xigdb(blWxtUox!E_j%CLEB%P`m5zWA)e?j1=g{o7`%If>L= zZmzg#!iLHZ8Mg%b@3LI>Rx&ov-nDEm`Ll(eSdY&R2fQ}x1E9g95RS2{7(D7a#vc=3 zJj0MSdNwWBql#dJx1eHMAy|VkoFfO)T*wl9VQa?>bNvDU&ei$IR4ywX{Jq1gZXJ$w z-tv!7U`NmTp%b?OL&g{&VJ!;vZn~`YWMRyuY?MV{HkS|Yb+iD~r4Fb0zJ>nL_va_8 z;<}K9GM4}lqIR*bC;nVP_`+l@(~F`Gtz|K;1AZ%KW{rhQZMimqWy|Io`$Ttek<0X8 zikTRdif7e1_1=;L-P}*obSz&RE}O9zDpNJ~K&s9c%+`Unpk4Hq^QSB7Pr^R6RS`bZ zEZ(0`S=}qJvm)C?7(JX^t&&~Kvq;;BrmbUPs8>bng_1WyWS1I z?$7uWRgE|%9Yz2rI(Dj1L(*@n71| zcUEQIfsl0S6|BArNLLA$W}x*|SB2|ku~?r{mM7sdKv7qSxjgsr^j$|U^t)%)FAzUv zC;WhD5SSo}Krc!qSf{U5-0k5C>TVDmTTNu$t*KFKq zQZp#$y7SF?q*t^hoxEp-vR-3K#VoJOeRPFnL3v)vza6{syO~z-r+8jx* zoUwsjq1ByW3j1|aR9R1M0MSzfQw$V7d#CI21a^ciH{Ox?ZZtl%BqWc7`x#kN78Q;V zH;@T&jU7WXo%8@;uD!z^jO!jzfrmH=U<`ki+tVzA=6W!^c%vWHu}hw4;?u&h+4>VW zrZ7K^clvp4=JhAc=cQY>B`$fYWry_ols6zaENqRwE?WUrS&+Y7aZeI`yAa1c;25wK z6p{rfgjhetpBbVCJ^OTv9L!~=-`x$wwY9AkRz@r-IUKQRT z!|FH1>Y?Xf^Ww6VJjNJK8VyHwWeGvuVRUu7rHpv7qNUW2A()6)xZ~b(VS2V_+FYe) z**m8*ZLQ9&JzKbp3H$0)JM$dJe=Ep~*=ii^Y-gM1n-uPnZB@ZrVxl`N8-Wk+nwZ-r z3}lXp81{?aXf&(VgW}4Nrb75itUhnz!_Z-=o6C!hEy3AkvP~tSvoyuLgh65FmHNu$ z^T6@M`ipg1p&C|w8A3s005O*<$&J8EeWPh+%99F7ZuH=A~Y8t9|#1NC|Em@G{;#K3pw%4v-QH~KO$cTNJ@&l-^1 zQp%uCKVxY%6yL$}2J;HAXuUL-RI@)5`F$Z9_UL2jZ-2mX0#bEhRpZ=iT;T`@@Nf{& z>jba-r)bD!#WFu9@Kl(ZyHsr*+>>{?>Qd8VZc8|FVff|<8NG=@(cSroi2)l@X;yX( zI0n0om!BmCmF3j9aI71PcP0?NE8J?B)%9wj_#c|1!o^g{JzLtZua>8`NNgQ`5a#|~ zi4{yi`!doS6&HGAoasGCTa#m1Rb5`3IO^E8*(2F{@vZ}X-u>oGfi=Q%39kl-E_jE& zb5RG$3oTgiKVtGX^bWG1NW_qxk_ zjq!-^vrt8un)}+`{?ovYPlDBFRL>_Nr^KqGR}6|Z5hF^U*D((DBOU~VV*JmWfg8CV zxSxyLD42+f^e07_RO^^b+O;p3b|FPx>5P}=EGSKNGp`0;4wwR%VK34{b!QzJ#W3qa zuKmp?eTmJvb*LfiR*X!SxM+d#0(`w+1auilu7wo>;=C)85!GwLpIp1v54UX}4Bf3e z74qJMvUX!igYKY|rHUDJbbDeTwoHUj{VWjOwr4h0I;ot!s$G*lN!#yKWrq`wLrEYJ zKBD-ApK+gRvg&lT(Y{O{ zarl@S$;W~$bc`-*f!9l4$DthwiLyMp40X9qdYezFu6E36$W`F%yX##@_9I`*rI?}0 z^X~m`>uh2?*gbpHbI#ZS)S0{>D&aR2$IH$~jb0b#~CI7xS@}JnOp}cG*_UNl{T>f7C3x@ zy%9E*f(W-FA!|CM`{gMz6vx*vpuGL@7P+S)&E36GNPjG=klw}J_A{GCtg!{ufhaHbF(p~@_e zy;4zbw})m3L#`0yDx#l;WtT}C#wg>H9~{6Wg3;7Jjqrs|u~cYZarQ}LZ3P?~0UJfg zYfYWh$K>ec6saacY&Hwi0ataePNNy~^n{_q)?o`7#kd-lct;I`T?)%)Vd1$J=}g1X zDe?0Wj3YQ11D<<3uQ%>}0S+9!8s3?X_GF%tsyK3O+WHKuFtd!+wakNZ>g|Wikk;fE znr5D=jB&hPJ@l=AZIT$Z&5vcw>eDVZ94Q)qGF=Sw>JDQ;)Np)Z6zAdGJ7-yP^kz4+ znXhfPDn12}Cq?DtSVX=KGOVFvlf>FGLwCIe@fBw{K5o2XV=C+b=2dk&pUj6!lubX$ znShK0&W@O7-UhFSxr`)R^p9Cv-0BjZ5%Et0!5dv{But!EiD`7j-e;t64@?uy)xJ_? z8_|BZ=mlChkpt&=9Up-?T)r0ey|gHERwU9dFv$~h!P*C5>^q2KtmctUVX0`GCI4Pf zXNOexoS!0fOw>x_CMCo<(~cF9CFYa$ZFQ@>YXug{4y27C;y}V3;w2AZv=!ervq9B# z&Hbu&8C(PF!{oWqo%QiLn93;2aLRXaVO=DBTXm1HN&3NR1qu%9xzw;KO{6!q`IDy` zbkxls3neK~+Wo_A^|pCVo1q{Ss?=A(0OUr061qk}w2YJ@ki&r4RQ!A|jrAEa+$-8# z47~adcNz#2kipgIUzBKFKwUX=MYiBycznpg1l z6Ipv|ZP9+z-7Fy*sSMLou?)xTSX1cD{RH|UkSEHHFyQ+bDSjakySm26*oVCX5%dCH z6C8*2n5clBI)W+19A80cijNtfXI5qn4@=L2cS(QH0%?1iTy@B!tov>?=@ewvmPFf* zP8MpnRc_$ApQJ`WQe2r;VAu}EN0&-VdyjkM%U;z5CS@S=vy2rwV(KGy)h;4J<@%*B zL;n{Va_L&+=L~a3dYX~+c+f^K00WtF} z>gf2yny_Z@QY{{fwMYvmGc}76oJLLDA|2qVZ@#rPG$b4sHg`J@zw!nF^%p|nh zTd90Aewwhm29AD*i||@qobOq+!XsE)pPQv^rO5E_f!EJ)k3p$V>h7t)-19u`=xHPt z%k0T;7Lk_pFF&SyAaedjgkdf!5?>Yx*~bGA0`J681yFph^aPJagJ#E=SJYpUr{0G$ zO!`{s+djQ%l7q@vmBHqTM-$6`qRz{*B#l8wtHF*r?MoHG%&5!gKO_sP{KVUZ;)^UL z7h$1xQQi=ogO`4k`n;1f)+VRX(@@BMhtNq~FarrFosru0Wa$uG$iJZgtJU0~fBAlr zfo*O%sNq6 zrWf)D_(cpf#h$9+2N%kPjJK_0ex-QLQ#{X2#Ztc!<0FnfZu~8wvV(x0G1s(* z{3vkT`;h1v!Md690(V_^lhUSDkE+%EBL5m%oCis*nZEZiCz(mFC~N$8#x)F2=*xwb zWV_g2`ziv)d@6zC32dvWht@+mj9SDgwFe&utTiOosc6YeRN0Njs+zfst$!kCf-ieu z0mq+Jh=G3?$ibc^2wEkv@#4iRm48e8nQJYR+54m1E0I8`RGKs&XZU9*7dh4BaiMa$ zJOan-3aPh-wGj+(Z#mm8Ga4QQ+>D_uMIDU5=y0Tch_o-B$+dLTuljxBO7EC5#OE<{ z%5+2z57rMu(3r@I3R2wrIP!hcM2?NVGCWwEuJ8qE4>@gg4{5MAx5JW4 z&Ou?Fl1l2rUGYZ!;i8Je&j}R=$sx~&W-rbKg`h;$mI@g;ER)M-)XXP*F~Xlx=s>Zw zYo%)&3?M$Z1NL+oG?-19h}$@7PfhPKGEU((bjir=-p)<}vOJUSvRa>~zPscIQci@; zzFw$}4kib;_*iFFQXLLlMAN=Pk;!CW^H_GujP*zgov?zoaZ-b$E>%V^T@YmVB*cd4jgnaUixN=c1#p~%2+(5b zNI)qe^u%#oSm-dfE^}#xHhpO#ZRkh~iRU{`g%uENx60EK$sYbs%uDuD_VQTs6x85` zOfk3|0shWQ%$`kCKPh&B(44V0#f1l=TkbKFc@CWf-+|_AQHhjVCOmZf2xBaL&0(^E zd*;q+A)({~6I~+L)Y*OE;tTEXCN-#Ocq-ct{yBPA^xC1r2470H-paO=vV2fN1Jeu} zOMY=N$9oCXqx{cDOhT|tQ93-` zbVbjNIFu2;c~17NnxdnR6s%^9!bZ|xYv7(0TksAIe8V2y84TBHow;O8$U2N{4jb2} zlvXXymop8b1BDAI53jshGr4qjftSU25_d+VU=Cgk2jqx4v_^`1b5;3ZZ~dUSpNp7qNy)!p=M|rjxRVGa!p88ru;0qi{T5NH`cl*8PCR z?XzJ-MnRTI4yjI&o4wHcsy?Zd2m91_RsqA`jxga9b+BsvH5;1cWnBNPz&|TjK zQpy7HYWTMCwI2G9yrV3T2(^MRa<#yNtG%yIAHZy!m>Zc^RbS~qchH6zIoQX$_T3ft zS}9Mt*3UHCPG?MK`mOT_-?BgY6;&$!mCg0b;3mlhmsNJ0SmcEn|os^yuAFJ6 z%J}ATk2WHiA&pSk&4cnXE)K$nc=ZD1fZHe5YN{(Lx=2wFku(!+RetJM~b1EcR$PXTsh2*r!G!zntD5})-=8r9eEJ3W;&dez>{@O%AfX~$E8t8OW_re@15|D5mcg-7q{>3UM? z=|Fq|C$Fnv@{(G`j*Cfd@4h_qs5OxoI_d_bh<)~}3!H4SqV_OWMAqM8gX+`pV&-*& zU(QLea_kX1OsGrqq7!}{d0RByEB`Rn*~!oiw7m}<9Gmf((el&_wzthzfmN5w_WdPY zZrS7PdfKoV#FFf7ZAL;5a~o1cN9;&m+{?T^dM>GLilNVt2I=l1WZc!o{i&*+>O6%C z`tSm2iF?a!y5Xfb=@}Coj#_hUFEzFyx!MRgWVsqEQyHufPrty9_-!Hhl zw5oY%^)`kNPG^=7GSHaot+|V1u~NUpS!;lU2?W!;x0Lg!DFF?zxkzoWVi-Q!vksK@ zY~;`c)0{o9)u%j|6>Cj}QSn+44rB4gX^RTN0_BV!Kg1}|XsKn2F-I8f%)v(;T7|~F z!Q0!X=EdJ4&ojV|OvLkMX$e66JLk++J8J`~EI22Y?k^=CDQk#6;y1g@;>E4dwsP<~ z#1B_znzn3tnqX7UW_|S73=aZ7s#H#1@kM`gZrC=s@LpM;oKaST%f0IPgUwz-7&F@= z?pZ`mBT&k0cH_fthd?ctWhtZ7Foprx{m)Hjw5&#QXBk9Sxd2;1CE~390-T4hLpHSa;)Sd^ z3CKUJ_Y^C2Gm~y{$Dc7GlF;Tw-;}xyd~o|J9tPdLHb^NH@_|;Mw91$BsfCA0``3Mril7kF$sKGP=dJ2G;W!bc{!)z{Eg`Ru) zsvZeLm$qQ0_R&<8A6@gPJcPCT(M)eG;A6cQh}qv0u-y<5|E0+cX?`=ujy zNu0}N!zE`T8`e;mREZ=`-n`WZY2umzQM|K;PgybadEc+j>D5Pb!bRj=E0HNw0lM6S zW<+f{oJ+$Fg3lj<>RN2^T(d?wvq`SzobgFJl%Br)pxvzh^P^kEeC+Ah>LUYQr*q$j z>9l&HuVxaOBiN^+eHzYo<%YKfapdUL!|YbO2(m@ZBOX`D_@QA{?0QeLvr&z_Lje#_7JwXc^WO`9oq)*CY7d!a7zQg=%jm84lNE)W#-V|QGe5*j>n@eG1 zK_kH6DX`%&k%;`<>tuQ-ko_9FTEC-llL53?>spJ`=qftWB4CAl;o+#o>v%u0-7WBO zS10FGeW?u{<&7+jBVRq@@R$B$DRK0wyeF5({!~+y<==4AoTk6+&brX>ytHe05X|Wj z__i=~*m8Xm7rt50$z+SZ7N7VQIH;8c(9o!C>K!;(_{tQu4aan_GgPnOuq~SRQeha& zNyCHbFUv&?tp+FN+$S;e3Qm>U*r6*@Wc;I=P%Fsxo&g|RAX}GNB zpYv`+e)n@Wg9nB@M~jo3-Mb<77e;~foNnC*D{!0{8t_mhv>T(+s%F2nYGUEUj)d82L>=61 z=DB3esj!Maiy43Oh2%mka!k3?waN-{P#A6vJ{^rC!{XV>KFRFV(T!u(0cBI9?4GUb0j>9GE)MT}zCb zy+~D}?*>&W)%S=D9-Iwz6D2~OQoa+2*dCcoUapG1Q|eXGPD(2@u=lr4$Z;jq?(ZJn zS54eJok7up;#?yH_!^zf3ptUOX4TF-hp^{^l=fIX((tL%k*4up_;5Jn!Iyvmv~g__ zEF-7$U#0t~cc~uruJ13s8xB}D1jr#K*2S7Dq27gcLtv@Vl(C+?4I9?;P5{=bM(b*Bo*QTY`udOP8JrAbOq)UbOHV-DH4x6xcYS@e3DASE2O zqp!9{tA)4sN9exK2XDC*#%fGvHBSe+oDg}ZrGK)OC@Z70aYh8_U`KlJyKB8o=T#r$ zLb=?;Soj7w=+O*95@ufp^wpR19l45Wn6IkA@GqZd`h_PNh_`>O85c3b?K0Sj!vhqH zHL>|ljqsF$`u$WwGEVSthSb=7WdBe#T3fVX)AOsaZkEeWJ)g;qglujY{iyIrz&mC! z@Av~)#Y1MJT~Q=J7k=S~Ux^r#(2@3?eAgP8F`zf>Uc|6p8ElS@t_=fXi z4ttV&1p$og!l{L6Yc&F%WF}D7u|5yc+h%mVt9nkXbIpQU0Bl8-9VVN`29KVV+N8#7 zqYCh{ta??c5pr?;saS6?5latgUwwE$Lf|(NxYjFQyt|CZ&57!Q%DOhITI>_ct62+sm`b8}=S<8>@9mwePdx=*n~w4#r>?XdS9 zDCL~n?~*$zbBq0s-el}Kzc5VGC-iibU(md6@<^Sr6mA=zM-`$eC|`)!oSG)b5PC2i z?Kj$-LdvFN-AEOF2hvf!;XT-=gXfoRUMh~{E}dL*#4RmO?jM=X7Ja*{Qh8{`Xfd5P zRHgj=n*Sgc59TtYx!s9`aafoe?^_eRtktun#qFQY!u^f61wo>dc2>74(FNxBpuCVjA5dz9c_AFyZATjy2JW}kW91Gf0t};6j)sx&G=#B8IZOayF6eB}NZvyaj99 zQbc=@*ABjje5m1T_d!}v;hV$>xgcbswR>K)tQ3@we7Qr{^{3#*k8_`}$)Z-NP$wAA zVP7wGW3Z@tcAg01)?ZSSPL%c&cSf0NOeMYTnbS2@%3U8GTu7WVM(sOxw9iqRx}=}y zL54o1)MN@V*>j&+O+wP|Ud889vGNj&#@dYfD)MPwS=brCG!cX=IiId0nB~tH`s65# zF?^_6Ik4dS_UXWYI-F48OSIIhCXXeS-5snj53?v%aczAg-O>5r<0r6P-=Lw_wq_zo z$KZpE=;b#XZMY6fYtrTHC{q=SJxgZy{D+}D+JsZhsv8?SZXE}cdPe3trMy-9AoPrc zn)PG1w}qWL_jLdVG-wZe*N3BfNV7*ted=)}6@l277ck#nj_Co1E@`AYMQ(sJyNvJ35paKT&g@b1it94mk6K2sNx^97Bb#gx>N^xlT z&I5pr0wnyCE0dlnJDL!+`i$L0=^lyeoH)x}gG2eAS|p>5DX-iFCJZ*cXU#kmU&roHnIpt! zJq87^A*bI^U5i_}S*S-!%<-R!UE05SpoxnOceWRb32wKm4m3yR;49UlrlkNCdQ%JO z1sJ9w-te)kbz}EWt==ru*xA0{6o0vf^#Z1yIDuu=k17NM0G=TXezR}@9ketsb}C7D zT~LQJTn?91C6F&{Sii8`^~R=sxEAj6Qf@(Cxy@h>$~yj3Cm+O#C9xA--pX4VbmQ8? znKv40?M1PSjbKtGt`ii7=gPyP{*69ze+)vp zMuRa)4~h!hx9fVkJd;q6lvjg2bFsj9o0u_f9+{1L|F!KEgsdC`N2-`?Jg`-iqrNG{ z-!5IDwE&TEbKhb8Lo-#GACJf_IN@|fv@^m8F-~~2;2N4g`2e$6T3ba-etVYOW4@M?CcNUj z%vewnixl3O~(GHws_r zP1{rj2zfz?`82Ii7-n_@yJ%{%AS zPcfm!UAiB0@S;n2Qecc$}#^n0(S>Zu~bsN5QC@s@)90$=+x zs$FbS+OCIt)K;CM_Q zK*4Q>LQqPRV(#jYq4&jt7}s>44NEZysIQ*7JgaNc3brU$$h4vGryANNdojf+lTOV=3cNLbdEgG z)eJuQ_;Yr#(wbag?r)kIXp`p9vzSwKx80q~<0|Wbcy4sl z7?TM{nN)&Hdna|b0cD404>UC;7X^XN{3NB|&^QTJspYLv0)#a*eCf^lBIU}~d$+@@ zv^e4ZJbm~nRem^i6?itm|8Da5G#D>8;RvHTatZry+4}7wmmZk9*=q^sz9D8@ zpAGuJj85LcWYY>ahc>LW1+_Ls8Em2U{{~S0j-&t=f)S6wlt3CV4R5;%>`hKd=qu?c zsutn~j}QesI9YBjSR}H4*290Z3OCy4qZop0znncpvb68k%^Z9uQ5jJuNX!k==xC{9 zBV$#6-}=wR(IhJHR7$yD4l53S$%XVdw6+YpvM1uKjJY1+Afxu08v#`L_S?L-u}3lR z@f=vc4e!5UOrK&uZ{VE?6rkANqZ01u0k>^L!*9KeNCSuU=bRo%?A6v3p1{+uVmUDW z^mN$c)47jN+W-ssZ&hHV0nULZ6=VX94!nxK7vPN!_=)ryB>37InT(JW0xx2^t z{~5vGL2Y8_EFd&MJD59x1_puLi z2(lY#br*ji^{@Wtk^?*$`#rz(kHmSC9s&&P$7*P?;vYF0)kos-HR5%+8h@f3K-~nO zkF-r!JPv;vG1SZd(_ioN@chY>hX)R%9P7X8LGPtJPsR|P>sp3-8I$g9@R?XBiuhG) z^7szUP%^q}8n-T&RKjWEkc|si_Rx`}>JIiL;ohsW-vn z^BHxJ<;h7U*~UW+IF#O)Kl0_{kLh7>Nc%rLP7D?|{7n}A)sb|~7l4#v7xcaVGYh>DY>V#uT^;^@Em#YvtM`nmv3Y+I zg2y}ksA7K;)PJN6_|eA>2)^cNme4BxnK@Ab(V&I-*GPY(z=kleEUVR3%Ko2jKudl9 z>|Z1OjRJlCfLz$M{*d~!*a-sk%tQP~ntva%_lp>WM;G9eQsO`H_J7|X=B$A8uaW*n z0cz~W5W~}aR?Odi{QYKNqFH|%=D$Ywf9OQ(`eLV|1;45Fmj53nTM#;R3+~OMncjFI z6BzvO+4#>iTHrta_db~}?C87jXE&0OsDDxRFN$ZuaWT=EB4^EZXpwP!8(RBt2vR-bPx6-R~3p_jRuKW0xM}C_o#-a@>F9w)cVn zx14z80$;|t_>-#5R76huKfN35AX~8sJ0eye8)alTVF3*DqV5$H2kOi;ssKRbTB8lDfi*z#A z=mV$Ai9xDq%8Pz~i8UHQ*s!F#m5*8)}HGR|Ll9Dbbtob1>3yf!=q zF;NI%6+J=?xLzJu0(oDuwP_rGo}R~e;fYrfUieXk#Vl+6e%1V5Mj&CCQoeF0rAnRx z1@CbO`aXa>R+TNC)Wt8u`$vsoG4qQLZaHwlJGtK?%9=bNn^FZLfyh(EIs*A>+;yGnCAi{}zBmz%%g-@s;i!Z`hCK+ z@Na4O{UTTO7ezpH4M!gVlv9l~eh-PyJOJSSW5H&|QpTT=?I;2b6P~ejDpmdn*tAoM z7|ezLtf&9htUmC7^#a(sybVDX5uV>{A{XuZ{(N^x94hsMo zJzVMkV~4nYb%-A5&_uR09-e@wJDfsJvRz2y*_b57l;|Cc*S)*j?QyrHCC_%cuSL9; zBTc0?-Jb;(`4`*u!!vupiq?z(zesdrJSv~mtM&!@N7$0~v;>|%b zrnYk(P=0HF&b&CBe4^Ig_!pxA_|_Ew(35JGL@ep$xWpe>COq=jEV%%)lDgRRq_Zm&ExR1!bpn|Arf>Fqy+#Y)#9uFOxPSung=lW zB@3tF|JdhSz*FjrB#K-z@uLd-k(=AobhU~AS&x?81G2O$N58$RmoD|xsJ60%d5+8; zB3GMzx$Lz}=(bm?iNa)y&rqW6e%y(+dlvXd^?DCh33IxN(=cK>l09)VYYhN z(;QKF3D5(0KGq<3{e+VzG)HP;b2!af z$MZUiiQeO=^?4L2MqvsTqozsG6Qe%G`9C^993beTI?xP3>-_3sE>Iv8u$zo5Bn z-aKchrj$)h-Uai>{7GJkp911a(_&CsCyXGwK2oT`xx3u#MMJKl<59Wjx?5PalbsYd zx2bGXRqu7XM#N#g1gh?Pu$(SR3CCf}?x?f42h1rF!#>>~sIwdJD95!b&fwrFi3;@) z2?Acnv+7FA`KhZ&D|19#e%sYI-?q<|AMU3!NvCz379AYB37p;-;+QW2)x0eFVrWV) z1}hXNe<+bH!m$zL?EjIOb^uEuF-$cy1t_bRZ9ttfUF(M9xA=rOmpe<1^&UVqu3u^b zV)kb>ffWBs*(?I~i6!CEs(#;y>|Mk_sA{9@xdj00GWA_Ny5VHB^^dRK75Jqlzl94s zlV$_fXTJIVoavGC3rb3u&F!8OI;Zm;wZKeehy)fyPD1^$4ZO!--L*h@_*-YY+^z@$ z^Xdh=(8ime`O5F#uPf)=E&^b&zsxgGp-Etaf9@}b>0gb>9sgp(M5~W&(IOX+!)83u zT=8hiSwK_VEmKIwsfJ=P6aW_A{Jzq3yh~HazWb-jpSU4?72U>FaH2yW^WmU>6PZ&T zoM&-F^V=rEKNip@CiB2bdaB^W~;S zzWb}SivhOb8K737H2}e?eGdz`_kJ(VaGP2J7;oBdQZgRWqsN_L5i8i7DIlV{1BctM|Fg{$=z0Q)!QVcsvM`H z_-Q22)%*vD`;f$ZmW)z;p2&W~$yh5MiqC_L>IVWT8Z_UYgzh(-4mq|Xan3yYCyj2GRY0{k7Ozjha!43f=6=Ts z0+lj1u0ByR&pQQY$>^t%b&3Yb8cn3zflkg@RF*RTyV{e$(6-@VE@ zL{b>!z1HqH?lC)nm8~ogSO2BMVu*oY4{XHwDhizJZl}I#F(YvJ!}XkNOgqdkr_y=# zg>1#q9;bEX>&1q$lyTb1$wJ^~%}R_kz=RAGP)a4}M)Kd}Hm~hhFIxe4oG$A^08L6w z3?G+ZA3o83TLjk9C<=eStfa^<=cUb|o6#8BgF2&Cxxc02H;LfgxrB!xGSH}~or~Vf z1(_eSy@Ter6g39NXYslbpxyv_P`JiT&Ykh?hleItK~)o-)rQ;JgkfuTdhj)O*sAmpQYa{d0om z^G5Q4Er?5JAi&)%>1Oz&8Opay(J#9s_WJW{p+XxHdF-w74aFRy^vb>I)4`sm|v z$j!`3($8#w0p?t9vZQ*LRlREk{5QAwF#NR@W>{3LUCv;y;K&5(rS zo3)>&n{zhx65Et7k@n9uh8p zQ9n^+R{6tVGkFKUb$^2fWc%aN+EX@eUcgpo9;`J(c=+bqAZs(qZgl?Qk2V93j;&Uw zA1!QNeHWB9kp#z5KQ>f{kGbhP3k;UAn&;gw_FHrLUcC-$rM|*nJb`C6UgY-Ocs?CM ze^)jhGaZr#&VS#hNmVQfHf3=R>pGWxy1P^38Rw+!){mQ%o=OAR`K)Ffsdkd%)ufg? z{&NK~iE)U#+lO_<5o3IEWD~HaowLTfM~UkI_!336OeoHmlg%rv6NbDkE&t;Qe;H(I0cT!i1m z52-k>LT&~ZV(;1XdHo&K-P~cW7wn|zPnYi_K*vM(!Pb1uEb|%2GgvO~vY_|c9lOZg z(|Vi`8ps$%H|O$~jJT_7GlvwHc2yl`Qxn>p2p-HbliAxFPqpW=YA&Uw-+11I%nm$! z8~K`XFhgj&bhRI$c$w9gb_i!YVBqGxY;f6BBK3SZzFETgYs0N9Ud0MZQZNrfygt$o zrHjMz@wib#%1MvH(DQ3}*<|nmfZ16Ftl#kT-&g`coT_$YUezQHOJFrR;9xYtH=M}U z-E?~*nZ%+WP^ezn2P_^9wCWuzjRrG~Z^HY?(i{l;V#D9yD@-I2Hob#$Ka|T$aF}UB zJeGJ!P#o&$5|4 z;ade}Uj<7fku0wzDOGz_75O78-F7W0qQ}jUt;Jo1@7Xjd*|-F<&n*-!T1hs8+xHpx z6U>rZ&W{PISvhRRDavY|K_v*JHI4~THXO#8ywaY?jys>BJg4UYQF38k1&MsULFzL` zWsaS0T!~!&SQfa(L$qv~e?)WJ?3LKuhmw55ogE_+>nPH_`p{yPc9WNblcQU*NHu;( zuf0^*w69kDRr-_awEYZp^C;0!l0pwuLe*^-a^<)qY}w2Ii>9lLit7EkD&5^J-6dVp zN=ir%ozgwDbc0CO(4{m8LpMlw$I#tK*Z=<3yWUTHV%FR{=Xp--efEZgN&5;Fy`J5v z)beUA=CHJWjg#ymak{BKvwTcl#hSJgP^O`*!F!lk=)1X0vS^25x2wMRQJ-E(i4Ip{ zlMnHzaTzZLcyFqA;E^Za?lL_x+Jkz=V8xbW-7-r~)Jxq0~o7wtu$qY#g)@MztgdPSO9~jhUFcy(j@$ERM zcwlSNpx&fIVd>O_i}BEX=y^M2pnmQu8rE?x={~_ES|F<|((+aL)epnbGvK!O-*MH} z{b@L}Uk>a~d(HGir(^U9iRjr5&b1Y7lGmKPe6U)UOij}KB9cYwnUd!F+d8LgtAx2p7y`(+VXq|n%*)Xmkn9OZE6<{|=9$15sAfMH~7&bw$v z^(2zrCfavWb!!r(CWjAc6t!*b89PSZRz3nsi(W<{d-s2#eK+0PMJbz;OKYXK9KH7* z%fIYOOcvhWrwVksQ^(*IL_k27P(ukC}ii<&bM*Ak9X7a z@oN&J|MDFMS)8MKbMM=qA^{-tDkTu%BExzO1@+X*Y+0qnA@!^4y6B_z9I6N|Y)$?G zV^`pY#W$e6w{diqsmk-W|Sec3s3SkBo6#pa_ME4Yo%o&@n%w@}7Obo1s- zoV$W(0OFSb^81lk!Cd~s{5_8{Z3b9zX~U-jR<^d?W24qkD)H>vtH%xN2;gjU#|=O%S5nKACdV;`u%2C^##{6#hGOFa}&_t z+oWzSfb9(Xn7i$1#Z@U=q zaH#84?rXBqgt{k7lW$*FU+{gxFvxaa>Py!*0YRBR!yGLmz{H}}QlhbQ@q(lM&a`DC zjI7-I8Vnn4a@|{M+Jo3q`Ta{EAe4GSDQb2?^z9h*z7j=Xx`v;Hr9!({m(NjUuAQqg1tuZ z$+1bmC)rC?MJY;GjS&XVsejv?B3LJ@^)5-kqs;3yI|=`9{v~jva0mEndNBvRoA(22 zEa3<cQt!}M{kb5(Us&56QvcT zEPiaN*g9l91e=dgOLRK7oR=>+|7F7v!)#|5@d$}GFb68vM7?s5&5JmMP@Lm!|vX=U%ooDW^ozNA>Eiy{3w%yizV=pYp&L+y;5rCPoCm{GYn^$&r zK>mkU9F{2F+I&lkK=mB?eZ(i{(2_1}Vb$RMUODFRTNYuQdmYj~9LR!mNheR$oynj6 zi0Ovv>IGEV>-sn{q>U4S6;?bi(P+e%7wf+%vhU9Vo&*Qv@>;oS68BR5xGICjs48f5 zXj_4e-|`S7#h9hKA)LLiGx5KxrLS_!tvqSt9i?G$Oy2C5ssHGvT6?I+uh#aGlp zE7g8(W*;igdvQ*qk;d!dt?2vaY^mLhBGS>WlEvE!IF+u38jqvy9Z{ zn=Yjk6SQfj9Da7^Gck$Q(E8!5l~hek#32#3vkxyZJ06qk=6kQh zex%9Ot>)po`&3q2%L#0+{?9Q-R#uSgzs$xwbQ6;gM8bDOxdlYYKo`wiN%5H#&mS#7UL+7knJOUud9Fp8qGOB9UDbdHAL4EHaSQ72>dVpf4EuhL zkB^{%qDr;796-^_%Me95WA}aO8+@QRxuGAj7;@qreU18Y3Mwhu zTHo{xUape!^H&Rd$byZtQWyKHKVcX*Sw71jE$NnacyVaofGJaKYloBM^8ELnxv&^+ zvxK+2Z7{@$?=SrFUif#nFmZI*Osq`dk7?I)K@o>Ba(udx~WIa_@y zH9YTyp0!Oo+|u8AwnBntJQ>{v$9~UibmYHk2N@<;C$nY4&&^irlFDZK$CqLc626UT z;;<4EEyB`hBF=^scKwF|Lj2BVSDRYb4nWnm@*dNDf1=A8V@F*FytxjpGHiPqt#?`i zyeEKK?;F4W6PS$KG{o__H+Hqz{i-6Bf0@ni(XR9RTaOX$frWnugOXZNN-f8=(R}tx zGgL`bz@Wau5aWob@IM3*;&-F_`zz~1%hp-a&a1WVmKfLA`8&q=ZE5=uNld*3F3AW< zINGKa?01tE_M>0swtzM`_p&eVWzc(kyik0|SARr7DbQAQX!2>ZwdvwqE=8~`on8k# zMd!FKbw!zJOsxCqH1vj>6wK8my{0iakO4TEEA_Y0aX6YqTBi4zoz)5Lwh8tbU*?Pu z!Nl}P@@SLs^QoZ~IJ=m(){z238-T4-8 zv>WUle1Aw0Y-^%d|F<=@{(BB=q~t0KemNV4wGHH-IY(tpJTQ5 zEmp8Ir@VhA*GXp(`CzwRjnkz3*Cj006IB39YTKk>hvEb3YuuL3cp;v(cxGESM{jr< zhOP%TrQBLeXCGJE0(UWecQ)~XxnC;=eC+s*1HR1-h2639hg2!@X>Km=lQ>@+o-xeB z;=D0-ktqBy9Sf>CYu=dFxiA8I8{2Bw#JxFV5QYeU?qXgfZe&n5o_vov-I&P{JoBh*QVyjopv0Tdapm`~1#?q>XNkrs2o@ zF-}yWDh|NAnZFYzjTQ*xC2M&!`|u>BGq#;Mqz*QfPCFq)tpuvh)y6o~jw?I5I=uZu zM2~kbgeGdl^5A^qE|+l}kK)WP;^Q{S0X;t`hRh~~Oxq+`gIc&*e5qx&s&^Q2dNfJ} zPATdZBmhT*eOQQ!i>3?k+|qy7`tx1ANZH~!G!E^6d)djH&xF#badw7{M=zWXi7cCJpzD)$K%^}xY`5L%L9y*`baeCl1o zS9Q?h>6yz#QVUiA4#3s;+-w}7Ub$7jB)VwZogYT@_l?K=S+$v5nJ?hYPQ+nqc4{ga zk`Rphv8f~rHTeXqfMGGDs z38tIlLL5k&Iw6@!_^JzIW@J3a@8ltEG|(Sd(;CFqiWc`Wih@x;eZ|4be!uzVdNy1} z9R)%~Q1#ggs`5cKwxt!jIpuc&u&(S&(fZFeqvN-)n7(kBvdfH2TDA5AXEPSqZA^#L zVn6d|Z0}S`e5``^J~xu%L*it`HV#jRjlD+B48EWj&T{66AwW@#yOIul@a@K}Xn*Ad zdM93UE>BX09iF@0;;f}a|6XM-$ui|^`}gw%@R`4vkjA=qaO(0+Ovq|0HAxym7?Cuma}$S#%#BroNRoKYb%CcI|ToM(2;&GPgkn127rT6wi;W(ge+%* zGC%Wb1BbJ5j6`dC^-Amg{GXIhtA{$9$0?tQS~@Q%SD4#~7&4#V4MT)8_44HvuTCFE zVYJD8iQ&R4M`GR;I}rh}7x%ld8xw$Uz5n zwr%PdW|TlN@IV<0bwwPx4oz;rMq*|O3ys#?0|5QRa~a9gPRB&5b|jBdvsdes41s%! z`yM-3_lY^QZ8bE`U;F?s!n+>dSmEnj(EYSU(kY43q8oCM7d)_K19rK;>1Dd^vym$k zI&8bu=JkulYxZx);pWNu49u21IfM~aI`fC$fRKjXPa=d!af^^wZ7uOMeHGpac|Thn zoGE7*ga=va2Vmy(xodz*(=PKI6h{zRC|+G0rQl}oO>U8o=&e!DKY~vdDkW6Gz;x)J zneUpR0^a01zP(n0J@htV$bqz-D7LyzZtj(g{$kDhv7s&diH=f@6r$Nw{XjG1qC)j! z?9#Kw4smT>ZHw-TaT=l%<(B)>$Z7Quu11|F0{$g4%+#DVoxhD7QSci0DMQutS?<=P z`0HYro;IcZA=2 z@mHerW4$oMu@&wP8V;Hq-f6~a#J!JSE14Gv2X{phf%ii_?2)5%g>(o$vx_fMz<<1} z}`Yi*=$C8!HU|Ab?}F~nuD1LjelAOh)KChq15WD z%Y(RiZ(S*mM!Wn|X^pg;A_S75sP75dPs#xIW3n?!WXF-zi$ZIhl%u zD|pD8GA&4o-8=tFd9}4a$BP}g}88=-Z#YjuboLuHRJ4Ec*gP!&<2=93#N$!oLw z$Xp&Xg~HQ{SsaeDwI_Zz(UPQqo?GgX3KLRZn6EznOcdU;vvXPe0=$1?_x|3kK4cvQ630P)4FXW>C9bId=o9JibldwQ=fH zcD-9SUf;kK_j}bNZV!I&SS|A1nC%#yXxrP#<6oweY$~*ml1cGMUsUeMAE_qjC{kjU zt-nfMdBv3nv}zZ`Jo0kf18gXbCh3W2@yl?2&CL zo}c)g&%#vDJ>|_R8%#Df1&%yH*IVtO4rT%Xc;>MN&w5AWzdX&RqTqm)b^C{gCh(>@sjgUe=qnexHrT zTY(lvu|ik$30st5_L8E5Lk9hKk*(=d;nH|T2?1P(YdPKlDG9+cOWgTC=H8R5sJ;`W zpb`-Q*`PM+Lc!ZL;JSwsn$}A?z|q+(Ji>2jT}&_?fKXg(QW!CcUZhn_Y`vhK1fAD; z+&954aUs^a@@ljiF2V178^iL;Ly+=;f_K$cBBZa}wp@Nk>`guzJ15Bdx6iXW;jBaE zqZm0yVZD~3)#Uz-@-3NNbniVwbL$j}D*2~^XjY8{MeJU>c8qvZ)dw(*a3vS@Y4FI`xpDM6P`$WJ zn0nUx2@!j7vAO32p&1Kux3Aa}*BgsHxH_-Qor`yMfU;FDY}W&ej#k&((^Fh=AjDMP zHhUxG=3CutR-n#v4fgs0sQ`~`p{xX=_w5$o7p+!yym%XBk@8x`oat$QE18J;&&uyV z3Lv{)eRYl5fL=07nQZDrUeU`xGdhqOTSFnXvI0d2fh@j37#$Ktlb8bdlD&|FEW=GP zxJaWYu`zDw*G%*K=8ESS$+!yRuh(Z|Dae1&qFzL}ZAe1-s{+o%)?`GHF_}Hd+)Sh>lymmjS7t(IzAL+_dg#~E*>kB$N;SEt%Kx7Bo#|sl6;d30;X==r|IWl z9FcG;@8)C!Orm%vVQfOd7@Hq9y&_<8b_PM*(eDuSV`7ymv%();)#-+T5j9BVh?{i$ z+n$f6{TaD`)w=PvOpY&h&|!brG7%|7M(nV;cGbb#IrD^e$CKVcfh#?mns<~2W~nt< z3qs0#-lU>qk(Omjm%-f{&6EAc5QAOo_ws{1U7%I}N@-OhT0NJh-Z&gHr0TbywaN87 z;Ya_j`Zv8;;zA4t((bY1_ea9U717F$qS+59Pnwgd>?B%MS2}KGymDo`=p!&(zD6xj zZ?x)&lW4>^lM~Mf=V;D#Y zD!vK7a4s&6n?LVf7GbYF6l_P(uw&jvJH16iRjpTw!ZQnTC>gj`^f)YdXTf{twWn%z zm)aHQoOG1lB6mA%+$8cI$~NtT{i~q72{_K^U)p%(I&@10aCmOQL-9c-2D#Rr>`xea zE&T^Nbq@Lae~U-?z(yXZC|}bZWn+;Y!waY+14&>$L*$##gehMQ0j?%*4)Y{!4IlQUaJv~|dQiwomw)cb# zaYpn6Lvqi_uf>76PC)n6zOW9Wg~ATKXdlIdb3R8EFAJi|7jP7Bsf=P5$e8)BjKJ1=USj zgO2it-h=9O!(Gq$<8pTn^J=S~Mr6VHU;g8QB06&K@Kj`3XS)Hl{374HfrhEXSkU~h zudMpD@PRL9(4KucB1Ucog7{1UN5eHORomW_Z^_giAGbf7ZQ!-?OWfTkJB_odr~&!4 zF7do`((>aL(*;*he2*BX%4I)|^?hu?69~_$vAQ?Vw&JZ{-mwC*g)a`4qjj>1Jtd=J zKu-P|)NrgcocPO28)7-cH{lpJ!rbl?`BW!M_T+p2c1au#`HjKGp=K5@BR4XoV_m)6 z*yh~a>(p|{9PZEa`V`qpMId)>2X$+3P*EbP6c4%yKxZf1>9ZEVmZk=oN>XUI1@6j3 zZjgQLoNtJ%^T_Kn3(vLn3eL{d;7X>AgM!U8g_ReyAaP6HBFqU#(C3Pi5{x%^0n#Hvj+HJ#Nt{o1V|9y%UX~fQx`}1Y=|JauzkaxpuPAV1Ch|)U8o{H7qDU z-&?+13k^wXQU99xXAQ~554?r`l z_qJy1^{3asK`Y|;@KOGoArv~|y4|P=yYy3$L9_PdDmAvQD$x+eW~U3k*eeI;+SpU# zPsZLzn=U%|7=T3K!Bi6l57wn4T(m&3=7slDY~ZVq8hxsV9(5*eu(1Wu(8ET3tK!cP zhxr(Ped4<#Zp~S*tt;#<_;V31z^X>Xo3bKa9bpl4S?D0b^Jux-^*$#PPdqe=^d{hvF+$ zwcTV;Ek_>hf1A&hJb3x!^t#Rim%z(ElWqmE85g@XaMI8kmaX>)V$Gnb)F@J1GWTTL zl6p=-AE9Ix1&Puf_=oB4u+V6!@t2j(e+njHDT)81;N;~xi1qZmVxC5~(GgB2MQrQi(;!wTu5qG`JPNQ?{J{0>S+Y4AT`i93pzLLf&nMKV>`si<4Gpv1 zOs7!P^`hsEe|aUGz+lA!Ck4pjU|vlV0T=IFhiM=!93i6VYrFrn@)*8}Lpq!~iy;6E=;KHX`m z&Ane-vjV>=b>oHd>rpn54DAPjQG}(JO&UtBnJ4vjU`|)Ayvydx#Ien(wfGQXgJePi zXLSbFylOz&W{B0yAG+9|zm_Ao$A08Td=}@tZSegHh)qqW6U}*|og=lAczr{C`~LTTjO*W+bM`bWe1(~Qu)vya*k zMFP}-B6H*n4vkHqU{$hOrG@y+dtozfzMTUvn8EcM7!#XoheZ@2RIHyyRM2!P#O?Or z@EFi)V`3?;g(#*r`kbl-a{+yp+jad@Y$DLcZ64>J$@bz#H$!rz}|QcDJIdJo@1IFr_aKefrBJuELlNw z#cEmu4zFZt;S%w}S!eG-g9V39jgDC`1K^5aDT?mF7v+Te!OJn7tZis>yXtA=lz7G= z-`M1FvP80aT>H5m(!2-pu>*MfS5~9Ly9?{>vL&yZE#35S_NI{mG6Ny#r&ITBuuG-S~SweYvYKC?BoyMww=@W@b+>1M&^#N;IsiVL9*;Wj?07mCf5XW*D&T%)$@vV9B7=**2jcRg`L5D+$kFh~{K+NCLx3IHGUp5Y%=#yKcCoHn z!=!1EZWTx6!#U$=TI|O^N-`}W^NM+0qvAtbBzv*Eo&e>Lg#lVd+M@Con1wj{j}x1V z-5u+A+{%2hgTy;uZtAS@RQcldZ6{PSox`ptJmcI86q`_0CDAzh-=mN3A6}|nA0fu( zhm;tzkw-}%r&%U)eAg$ox{Jp3%sR&xxS<%=r|h1Fls+1T?UOO~5kS=aJbU(#=Mq%A zn-lAPSttM}M~0TODD4Ne2IUH+U35~>xy(go-I?>QM(WZPh|~X;NiNjN%k2>%Ab2OE ztFq3^NGM{%V5i4$M_&;NRwHWt<|SQ!Vx8*T3#DTMzc;TLJiVgY9u|?SuXs+qnVV^ShMa)bFBzEHL8_GY+~vKcy6ap}kIQ)f7KNEOyO&Amt9*1*frE8}&v>;)<(Qza^m;ZAv5A*s8tHq_EELLXw37wND(g|G96B z$n{7e{mZ}h53$9O?t&XhFX)W3T$>90Wu%>w+AT~1rB3aqXV7j9Jg9_lG+f3C?=DJk z>C`1NQQkzh#H=IvM&y8t9C3fZM!C_3CLUIdhB?){=(mWyv1GP1S*pJ}NIu;V^k&~7 zbQfl-akp|>Eto8p^r4ak9c7*;#g^h8Yg*t!n5NX}j8`}_9(eC0tm?L4M%1UD8z|Pe z<~0MR>9;@;y>Nu(m#f+NH)iW*aSOb2$VN>-c3A1$6xHSj=BjT}^iyb?)_Wg}GpfcCA#+W{V}i?iKd=~roPgZJH$SKe6%>dpZN96z6cIEO&Ct5k-Fv;AmA zfJ$3rj(J{c-HH@0wZ`tK;j#puqDiD=2@n~wQ|un95LK7*sWXR z*ZP*)eR4Q(Y^nvT7#AEqSzX}Is|#yTkn|b*5kbJCtyPcbl@f5a?M@=07LmbX_=}jm zEku}1r>FP2HYatVbH{L^bl9_ub(z$3Zt(k$-%)f(qpl2|6dqKx1BR~&<_P&QH=f= z+gsCqK8F^pz}n1mciSs%%e$OgF;hly{z(|?1(-+?e>_a5_fCa(gA__s?(>q~=B_%4 zfW5@^<6Ah5_>9fyyn!r^RXACMCs;p1K){00nmh45Bl*s^iy1S(x@8gu<`!r5fRl7C5gT8 z5pm?0m3Iu@v-#Q<1*<9i)Ey706BUAgJ1q2#kY2QAotOXJqY)8Jao?`DcRr~3mK7zfpVoT59#t}jBaOLj5H zh#F!^qXC{}`YeXZPC3wF^qCPW9Q+p2^=Iqo+SzyG@xvGw$9h{{JQE^ja3^TmGv&Qn?O2?a%{n#(3isuKZiXPqh*HYrDhQ$MCW>b7;_rzI%5PZ6!Mb zuxPHk*ZMBLK!RlZyJ(`(1vV)Odgq-nUN0Z~L#{U(JE|@NY|2QK%9kA^jZZb}{Q7~Z zF9bjHzVgcVnfgIka>d>%Jf@BzMV5^l6LTeKtQPI#h-)>6324-j?8r=C%J05J*P@-KZUxvV$MwP0a1JPF@OPv&GF?Q^f1Yy#i;9>O-a&*s?vWBKGN z4pP{2o?;Dyl~B|6zJqTi6x}l%gE7wP;{NphYvpdVO}GXwlrmtn#e0 zu8rHU%U5I8F_P1r%|>hN8DIB@(y~dxVr@pr-68AE?#!Zp9ARs6@s#%2!tS-fSmJ_U zU0rt9u$EE&VU>jql-AG0`*%d9%rx}a^2~u?g+)5YUel}7PtC|FxR9XynYM`Zkj0|9 z&ugC85K2yf<+J|{W#nJK<_w;e=Ct-Mji}_hZp;~&iR(Ti{hw5&pL12y{@f%oOEaBk zbV;3YIA*oBIfc8)-3 zZ^3c*t2x#4E#!?cVCo82uJn&hm@tLoi!!-np=j&rZS#eE&*Z2;+lUN$?n(=c>}HbR zAD3Jd0!Wz4EOf_6RV{3xjJrn+amM9^C2$&J)1h{$o9SnQWGg64%T07^v{uSnBdKSd z!h^b>xGVr{Tvk&qKO-LDzI_y=ux8K z0}W||QOD|UHXs-k8W&6+&8~=zO~$jpTx5bYsK$uY0xTbN@3M#>P)x8!miJkn4v={d z<{3>Pv23mkEm3j~?Vcxl$~+TBaMR0}!kr{>n3_sR#H<$gV78W(fDcZ0pPunLO%v>R z&93fscyv$TES?%-0*SD&ND%e>0v-#(NTl5oy>4+XepsXAi~tgysdhJV+HGm!{s>kN zc|+VsJ^f!aQuyC(+*}M$Et`Z>SOVlhwPHo~s}o95p;lMwUTS*p`ug7Xixu!{XrcR` z;PoSr9=U26qfAAmZj|0x=j^F6bjjXukwvU7QKYB>C&D=UD~guhA{`cIji+4A@6{$K z?0J68agtSIRQzM>FTG5iB1X><2K@k7E9j&$Fqfa;^U-Z%!UKy@3^hqoyaIdyjUd;+3g&jv4|>&EKl&+lJqkbK4JW@Mxy84 zU}>R6m)lX(kK-4OX7C&?KEQBtM5lc>L%i2Ta8=Yau){B&(=tS0=i0NQiFH1Ui1vcy zN)OuHxqP}+_QD+=ATX?dVYO?Pb`WrZKIpJFUMvqWBe`@Qe{6wi%bnu=xrCV!-Vex% zS)B%`cWm*p>8Otcq8s9R|vc*H9$J z3=?6%4fv@gpgDG2#h{iUXRn4_pl0RbUX%w=Q&OCSY}_(=R-&uuJjj=AHEqUhdbVD@7JFT36Udw-3ZM zEN~CG0j%z>xQ5B}+*x^|u@a1AIe7Aqvx@4uP@}HgL!5Cm|A{sN{(Qp>q0}(tt za3d>Mar%(p+x^4lxEv+4%A3@IJPtRn0rr3{rIj|2h@HRFpAO%y@8*l`PU|b`%jy-- zc4&5&txB>3S@M&|>+_>-na9Pvk=j$c<+T60!1Bd^{Q1~uiV*7yY}I7y!C&^J5z7|H#cMP;3!UFHp?h36BLm%4yT452XlRwH za^-7B7yaD8eSvj<)SvlB+d)t0{?+23J!2rkQt)Eo`+Bp#ov*k1cYmd$894RpR422h zuAMjhI_Mi=54PJEdg&B_wKL?kBCdUl`h==XvK$EP5X}DlX$@UyL++;qL!;44Gk)2o zKCZv4t1)_=p*tUcLr zEc4LjU?0)?o0U@O+UQJ4#W#9iC)fikxw?`n`ZTyYEoa=0x>|#WIRqb?UH*t*Xwfx# z#$fJxzB)!9=hY~3OOSd+SxQrJflFH8jO`OUnu z-k`Pl&|1bg@2T7#jVx%4z9V*LD^cCU-?bC7XCqYYc+an_nMHwUBJrluQ1J_n`PNUo zzS#;hf3pp#6lF1@`X5iMpc9V=yJo6a#!Q}1^Mrbf+&f3#21K=TIn3T%?0^Fuf)KWZ z`gkFf@Yrlc zPMHT={Op(17+7dH$L+h8R(wwpSL*hmL4V`p!FO<7{e^Jjt2t?L%($PH$mKWvV-HBh zEMZ4o3-uk>Q4>>i$vXq8t6fj1fIpQR{8x`o|I47bV7IUw3k9KuyWX6v;8;Jb3zR%&aIN~nq@dj4Hvd9^sx;Ty0- zJv0Pz7|RlB0F+dI8#LAav7UT?IA>bHdWfLi5;;7&H2WQ4@}?%vS%hHqyV<~&PGR+L zig^N%NcY4d&vFUmt8fQ_cAo+p=NdtEN=EGYyS%i|Yw=ejK5p++B3>ztj)I67&9LgW zy;M~%Ju&)YH=}SJf^`w4Z{@mo&Tk5bP42u`-Muu{=fLb>8hXk@yT~s(NqlwJvvSao zSL$Md2Xp7df!y8FVZmi(u1=qoE!Iu{II7{F>JET%0|FEwl(F)~Hz$k)w6U_$Wz$IY zu{LQ>*8BKt>Nf@q_gjKaGnc zf{UDP``4bf`PHd;FBSxTKi*iv-Z&gSc00MdiAe6taJ=8xR|s#dVorZ3H2-z?S{p_6 z7<%@mE1daM^JwK3hCK3eRukQ%!vK_WO^A|qMzYxYV=}jcYq3v@pRoEw`tS+N7hzwX zB#2^CYih;&YMiI8)7CP(nhJu{t#BbK`vL==_82Y6g(6k131%^Wu+}S66orYoQFKcv z!oKgQ9&2)H5u%HaeI`nCiM`mSieB%e>bCy@G*ZYKc+S5!>2Cct+5s?Qo(e$bM{*xZ zIJdd)oL15S`B;)xDvJc>Z!Dg`rl}Swib3RdpoR&cWZaI*oOh-n8L0>Uut5oxsmuL_+Ivf1if1u5LM%nH?AA(7Z!}OVbEh zBrs*&vXH|_O8Lw5Po8r|(eDc<_kSQ+!xC0ng21sJd2n572x+!TolVL-QPpeVCu^ zz=gPQA_6ekaW}B*N!!7gpEpk3Ms5=KEv*^TcV*F5Fvv%obSh^|rGM!&z39c|>P!QA z?K6ompEGHngBG6WBksvi+D2a5zA@y2t zW^7#0O%g4S01p!F%c7bD9l}r5TL5%+BYhIn(>bh-Gx)G7Apt?%G&`4IEK7p-^ z%F`Wl6i1WyLB{`m>CfoD)^`OH^|ER=dz0-#4%Ydc@A1)-eB<9HLZ$e~9EmU>9hOsp zzyuaCSE3Z0OINuAFS1VlJ$?LLSds!5Rc}=sdAPyMcq4^*GAag+%hlwxS6BJ9&djF9 zsC&AZ0CI=E+?5ea$m=|WepJxbu7LULVgbM!h*Qs8XYfvVdIikHiO{obsmAqeK$C&~ zocJqCvl=U$EL{Ba;dsN>4op7fTk+DmZ4%w2)1z~L&4i*4*U~n4G#)5wHauQ=Y`O;g z0NlV7;=ILnky($`if%o-6GBIs!+{}7;cxCt@q!{F+P`4B9)PI3hdz7&utx9Y?DbQ- z@v8Rz6RN_Jc$y&nU~Qbppd4j!{cktZaS7-p&R}B#FkdrZgW7YZqEBznqNF;))fCj%L;V=&4 z6`*Yd%{iCk$E+ntenQow*uh&^LNPp^@oO9~=u;35qs}`6Ux8iCn2z#GFZr!15D46U7hIKu0)iS4=U^Y1Gc2KCutua%2 z1)~|m4E#+hV&N$L{WB4ADg}pLq)R}my8ADK@HkIOd9bPfV~>kFIh!^~O!ixQqFhKc zE$R4oa0}KU*WBk-Z1aq3<>t3R7p3x81AcYV-rytJ?2O4lne_uaYN?jX#^R|1M= z{Hh7#S_>%NhUZkNmP~bNCM#0hc>6ajgUwk`|7KbZCmb+u_u+#}-zGTa~(b&v{2Uxw-w@k4(Sr$FNX5 zJLkqh*8i`F6WB-Ar|ZW}*Vx1^}fa<@Fa zKFc8RxgbOvlA5yTcCeG%Gu>hrnr@_#D%S9sSuY^;A^QY%?o6%g`JTL`i!{B`%2$i? zqp!jq;>g+f;$Kg}LoLi|71?YpU~+}jdq-jFwf@msm?kElV|mpfY+n;bdz%&1;n1)< ztri{-BAk_fWnpi-NN|$BJl|zjr~3k&Zu@w#n{%vmA=hHQZ}`uY?N?(W?d~{_#1TgL zkN6{*dmm#M0h0Km0)>F}1og5_N%qsVJe6O4+udfgR)Jis`ePFT@Ta!zW@(s^O~)8m z%c=8!erQq_di8dsG3Z|Y9KEBYB||yXaSITuswPu6()75am^LDbw%gF6e+f)D*nimHH@$}06Ldkt~G7pSH5yix7;sGDm{|T)^pXu+p^kTkua_8bR@_}4x8zUpz zHCg#_?w3xJVA{mj`@Y~kT^Pxi`UGOUZHHdCT&`|nMUF}P9U3K!=as^3>vyYrr!sH} z%*lOtjn~~XeSTc-+!|y$Unj937<#wwT*Syk$i(fCLRZymp;3*ONleO+GyaAlx7M7KIRwSb6s)lNJOsuDskQwb6XBtb;dG8}i zGhrdFX3%Z+H=(rgJg4YpxSGG+ybLvPozIY7-n@lwLAiQ5^YSvy%Elc)G7&59soyj& ztKe3^&S<&8To(J&aYSn%=0A;65u1Zvl|Dr$B$H*nS!8dH?!-)tp+5*-DE08Mc=F6( zs`?k&p3=Cr+dD`#@KCwf-NJeF@ZJAqbZ6Ld0BAoz5jvu)${7wO(A#;F(t0dY!!lk#6s!xxGt6x0Hm`#=TY*DaTce?{-Rv2qMs8X&zxC- z6BWMO-Kymwieg)|hR$Jf>iPC2uVt-6@;Uq#swiU=&YkGJMM9lM7)(G*Ea2!UstaJP#{m8Fa(p>Y z8tG`dB;NfL{V4fk$NIR$Ydw)6xa&VgsF`mGuok`483Wl+yPj`fM&Rd{#pVFT{s%{oMw^DMHde4};xM*_=O= z&q=-O5FqpF_|Rwfr8dh7S3QXLP`Bq-3Y0isaroym2I^=2Hx53k%1TkZ^*@@++UPS% za1z_)(aRW()%xT%ESi1gq8=n?G7(7~kXaJezIC!a=$$(4n zX}bYTP?Sm@$L8(FMug%NuE3%%(%j|@!q(GVQrwtN23r?6$Ub&-Li5D@D#Q|j)`fLId&wy(LF<^hEj7{>R=~Ma2~~?HYF{KyYVBut0)OkYK?H1b26b zFgOViT!QOBaDrQKcXtTx5OjdS_3V8A_g|jNb9dHWi>sNv+1=GO)zwwc3pDZK#Q9*c z+X=V}6SsQ`8(q|L5AkX9II&W0PTx+7Z{B$rn-k3%UwVYzwj24-CBYnY)h~lJ6&5#Q zGL*hq4Oq(mh)wc+mRvAX$n_|w5R(u#4&h%NvHD3cAI{>VO(F%^^vw+UWZAUN696Z3 zTEgNV>Ar>@I9i7F9h)svj|U-P3E#4#zj$N?J4A zbr`7Rxzxup&NN{q7M@-)I_s()6oOEcCu;nVrw^?%Xu_xN52ROsAt%4Y!mfoZBY&_s zew}#CGBe4!!vN;yTjj)18Ae25!Q*S0Yt5t7bCKVFuKDRgzWJl!-st^^P$_xOZBs*^ zU{M8xwp>E$r1oS1(H$ZHG={TMLBo`&%D7UkPBuT}~0qOY(|mC-&+O<;p$Y z9hfvJtOjl2Usj@DEh;RBl;7gCudixqUcHRXfb~lzj2cGh|Gah3Pw10_-Zu7pIoT;n zwsP*3MEv!BQ?{<8vH02EqB+HcjTls)o#M{Fns`nCMWdSOG3ccOp;NF{SCxtJ3(dvX zd;+Qbv7lUF)=6B$sbbHRN?n?NbV2xHIkREs$bJ0XV=F4f9daHDc`nSBAPgau{Ks1T z2S@#-S>Z5UpjZRYe#-~*z`$|K_CH!K^u0I57C-lFx&G3Z6?aYBh3b4oD0tyJ9<@!4 z#prqcBuC7A-?4cj`1FbCQd-x-_9N@|vZ9DWqZ>V=nl=D;+R(Lz; zlgyI(iM0zOT>E`boJofp8Xp&@=uLz6x2kvwvoBKcz+h1UPHXyJ_=bKBDM1OVmZG6G zAEGB&yX|j%MR~nRKI>b_`EF#oa_BUVQ{rH+0ylISv2B)&qvvkStz_ayAe*u6GTADZ zsa1q*1J|2N;-1bP_oXDF!a152XHEbm_1p+F#l6A7R(2!4vEQeC!XPS3)AJ<|=)r#& z`U7?j^qMwVM?|4?#|<(v(6d-yOrI2V5$6>A=51GXNKs#7%8T@NBwvoIwLpt6S4#i` z){pj=_wOII7g%FT=j0}YlBuTXF|jokS4jpp?Lpg>F} zX}B$HM2qH@{SUbacIPbD_l0*W&VMF_@tvU}-K_cY%Ev7v7Uy!&a;03rR`!@sE2nC! zdr|?77Ec$m;ZXUS5>W9uqw4e_k7{lL;7^zr{5nff-F+_S~PPa zpbZuA{8bXqr&!ayL3bigMZ1*UxYuz&#Zn-r#|!<<2G@ekUM-zSwVrA+b;C;d2k!8k zc(xd~!~D~o#iti;foCB@@{pX+57-6$eO#QAXHM6BS4*n|$&fO|a2fEL%==Ef#6zQ? zz`wf;;4`21PS4M5(XC|st1-<=Pkzyk2*MhlMjA7}^`2Gc2@-Smj!xc2$vFpsJg9=R zm)>;IQn5NO-N;AKw(S-EJQ>-hAOmx*oF^B#3c7;IQgVodFAn^@kmPwXUVC~0o!e(YRnEI7vS81sS{8)17+oMxTy$)>YGu?ZlVXS@Fa=7`Hxg zZO1fyuF|%C3CXv1sO^%lEDI>Q0$;HmNs!s|$8G^VCOECJKJ&IcL_W71d8l{&1QDt} zO}_NOu(_)nXrJPXx$>@7eB1_B^qBlh@CrgqpK$l~x1TldZ!MrXxDukv0Uo?JzLw4R zG@;(5GIK);wiTg)n3|@s(MK+_ogR!fJlT5ZkwUvbrEV1fk6y#Vjz{)fE(9pDzirfA z-URq3*Pr&dPB6#Ji+eN`_1HEYRS%bHRv2j1u%P_pQEGD^XbyF~;6~wW$`2-1|J)-j zm(fz^h`WO=iXU^WzAndgM77#Hvqn=EWB|oFV-loOF+t}uqdQ_6b1)9$JA;jSG<*A1 zcMc(NQWG9;r`_f|qe$ktwii{x=!pkE}!0GJP-eS?v z$wTi6&6ThB2g*ZEKGU4^dpn04=gpVBzLh_Nv1aVOQ7{W|%vF^IsCshe13MK_29R^o z&I_jwBi04og^BMRN3b_pW)zkFj3v)o_%ENn7H`n3*sUgu{!tEgdbXaOOHxd;xx~_3 z;?u9M(Feu+9*W>r{eACHxgaei=t1*(g7f7UDT2j)l_X)0fX!$u3nGJyC4m;JHG{sDz{$CE-!xS^+#=?r!wz~WQYtDxnDyRP6v072T7YQ#OIQrEZtJ`RQ z3b~r|8{qQG6|90mcYW9JIqW{u+{19;3d`N98J6P?yHPZ@f&PXL?YxfmuDcw+ZM4=J z%uSaXtr~fuUk|Nf4p~`iX{E<2)t(gkj9wbpSKC+dzHTmC2fc@|e*b2yKAY31{fubg zZp1uM)w)nGnl@YH5HIwlN?a7DzA^FFpM%}evfJ9QxnEBC_BCq`8pwV-zh4|Y7_5v+ zNAu|-K$|zZxyko3ADgx3$1LF7=iKIL+UPeD@Vow8B8M7W5uTT*vjc!QrjIneL1g@gO-2cV55>xxV9!!8mDsTrUQicryqQ- zi+Aal_1fj2TDM0u)Uj*f`nde)G2NK2`y`X8h-O2hF_Zy<0p&AWSeBEF{aQx@ib! zd>9S??Ql*hQ<_C|%#UGtShO(?R#Qavq#$Mzy&cLl#kV9TDk(+F*1hwAt7KdPoso&I zFbx~3eRIp?Gu0d+>mxCj9`X=vZ#}j06Tbz6y}nveDeCYcD%mW=;jT0*$w{7XISBf4 zU16ED;Tf|=xMK1#mCZ7c=jw;_ov(9VYAOc%;j;8l^Cai`KlC`DXa}`z%Y_1Sxh5@* z4O6wpWAJlJqW%nf2(|54u8LQKTqH9 zk@^+NkbCgrE370=0HGjq03dJnKqkN=VGJmI^5s=%ln0EDik z{vwv{Or6JV+NbsW^yr%`6mD3iTmKuV+`fGtl5oOW%L(BzccaX;rAA{WVy%EwO^}I! z^EVM0X=;F=OmF4&qp%2D45`1{VLHw*{jc5(*_a$PNnsWr`{E~yqgQJbVV+|lg<>_M zMvo|LpwRMLrWjJ`u|;gyTj>CtCumZ3orW_5R|Wdrmp4ow^#TMmyysoh&z~+?HvAyU zR8(F2f{a=B$@mp5Tz2or0lFkPn9dD_|1S#41I>;PzLY5LDV7c28CJCL3gtVOxcQ^U zS5NH=a8l5`V|`7Qhj^=f%eOKe=H&Xd*)@$?NSJ0NY%Okhiv@z(pY=oUpQpCZWwJbj=hqIn z$V8s2HE$+}4Jsl|u3>eGYqEov?;TIW+aCBDefm%hCQp9=Px|?M8g4Nz%L%KE2|)-Q z+}T|MRpd&^Ji~InAypXd`Nj{wwe&;{q0Q2x;hSOuA3nzoXZatK7`Vo_>)o7F8f^ID zgOZ;gQ#5ZjO9pog>LF5DK2SXo7*U1Qgo=OTIt=6~rB(<^Zl?ZAQl^ur=di-;Nv-(% z63d2Hk<(+Ldrjk=W1FVa`lrDi!+r>AVMemv(-nHf>G|QqP8a_g91G?{*Z7cC*wR&K zsKB}AR;YRL)IYfM+&qPyl$e7lYk}%byGz2MeE?Ha=`-(bCwv5yR*hQeE1N0@4lmlR z{aP1nZyqjpo80IdiN(acDpq9F9xc7#p{R|Pd5i#&e$E@oKN}CGrx)^Un{se^dG?j$ z$u{U;;lo9ybXiRDb+W$NWTliWo>8ZHzPmD=;i&P-T4z%`k~a7e5!HLur)X=|E>yiH z_pTEQEsPs_z!hMwV2<+DR%NY4yXNsSOd5AIw--mzvg$||_9o^|C5Nng(~9@=QQ1M- zkTM?@)4^3s6c0jR{&o4u!n0nDQ;FMk>~4A{X(&+uOhRpe59w$xdeps-Y9 zT?a$w)Y9!tFf+Q-1+k-tFxoVIsN2~&D`TDm)JaQi%I#7rDWZ&vYcUwHTJW@-7kF3~ z*_=5#5C6Exubtb}(~|uu6(wk(EXa)E3*N?kb}d08$s(0)iB3~lweCcHt5*_b+BJ_j zoGsanw(IAL&p-qvXyo3~D16V=a!oD?hZ;2cT607T3ArDucXix#}|TxH>kz}p5($-}oE)U}eE zZ#P>zkw)61*Wdp7dS6IF8iOywn2^Xj-)Y(7ZF1{0|4G`M`4jcd2igD3Y6pZhvZkwN zzoEw2QrpoX=bOQR|2of|FgvdmhdaYJqJBlURFid!f+Fq3&E4EbfW|-PkCc^zCtA^f z_l!aOYGPBZ_y`lSHqJXXmK4xShaBFoNW;F|67l`68Ke|=NWC(~x4zx~)63iI+^=c2 z4#iZrS3zoAf#)9SQBmvrt@s?gayi^n?l|dd06UZiA-u{nmkjzXGRFjrT3-<}oUsuo zUY}Zxsqo0OskUqY&Rx03V5KDK_~P4Qs2~52*j>toRLJn=*~4jNGcvbgk9^5Ye?>y- zJ&d<|_nz2!;{8D(GDW{jRL;`h!4yb2Kgb)%y2za!Wo%GJo4i+TfH8*K#hQvox9mRS(6}?eiw-9YA%U@&uzL zMCc)IQ|N(uW~11`z`#RO%X#dGqe~%H6F&rm2*!Wos=ImcB}vS#{Bu}U0ytdB;i|ct zQafL*Ua9ULt!~xZ^1G)asqXvC)n%d(J_YZ`)dnDyeygkM_JOLphf!GTLz1U;>Z3a4 zKUTC3P+Yz7ofSAOCJ#7sTpPWzG&Kgd!gXe=yWboi|HkP0izMla#y_TYzBpq(R}ax~ zLCf*Mdb?cP=l5St8e2@K0^YG-pDD$M+ofYInilAzD}pW2biIR;=6Ju7fw+3vO`M{n zH%B}zO)WYeDeQL9nRkW8KFWBsjB~Tm7Y1gjfZZ?ld#9_LVa(U+M2LicDlwgi!pHYY zD}{=>1sT~akD*b(ucklI9CD_|eT&y&WK0@LmxO%hAwPaott)^v(`N z9~RPCro7%;jfG0mhY|)S{M;hkRnJ;RhBB9+3s(DYdT&W37_{028A+M)p82pAB;X|f zgagUIbVFr(5);Cie!W&Nq2GWfQLGBQ0iY|uDxOVi5hXyA^tENt;ZlwgY!C$LY0c>2JZuX4RGw#H_4r8t^BlZy2*Tnv?Ag4X@!?5B_qNNc5~ zR@ZlS!++BC>bu0}gFx-FLdLmL`I!uY`#}x*->-#PIgNccQ&=o+k^_d_q~vH$X6wDa zVVi_`e{V2Tcd8ifWJsZ4_yQxbXP;E4G6>X~yR81FVFa+K5xPHx!ktb4&Amx{0=GBw z`>x2^PdgE!9UhPQuLghUDTy~zOj|YAl2a^rn}lC<8{@jWO&>Va`AMxmF9_p9io*|M z@QYYYI)VM|kv_3^#F=Ac7e^$Kr#xez`#~zeYJHGzAUZM-!J!B|@h6cw`y-NjL z_vnGHtB$*uxr-ahg2iJr$zwm@-DcEO9Q~h~YpZ*37xYVM@buk3)qJVEan~kpL&tM_ ziI*MwzS$bju2qA7|n**E7GU_05i@M8Irv#{^f2r3H%bXjJ zM%@7m&ih97$(~+-WGLZ@RCXv_X{8SKQXPwPM;1c%oEpa7oR&fy>^_(?# zJ62*{?EikDZ;So==*<8B=>MA$@I-phIjL@d z{^OBy1vBX|mELZnOJ3HGo&92!2`0OC&-M`VFsLq_(K^o`eH2d^X<6*h`Oe}x%?@h zG?8MSq+Nh3ul87iFZ7r0E#%b5^&cm({lGP&*%bot0Tckjq8+nC(>_bhtcGr#P4GWP zqAP&iNkJvkn`pV+O)tB1@Ld2@(;NU)t=-kCx1;MuFxkP*=|UeBMu*xZ?i*zUWgzL& z{^|wRE}*`|2k7azU+Q5CqTr$6%>*O=ll^(`eO2p;PbDx>TEFV$?x{TRE*%*_{ZuX3 z)r{J8`9I&UF%!JLAM^xCU2Gh)cpdVXlIkwFi9Gar7H-0w6c-3C_NV*pn)VBJb7Mr( zLIeqa(FjcNQ8nzQ*^d0fof$p>o2hgXO}#36EY;k4hw4F=0>gH{T|n9B&0dzjUzAIx zcMamrC#m@et~RSLfS#2`8BPa_Tz$EIa~h!RRCmyPX%6%TmXh;3WC%W-c8}_NtmG$m zTT;5x0jrn)--6jQHo>!!O6?%kv7&d~&rfD4z~rTU9c-^vs+^9?Pdfu$a4-%a*#DHhVCrzqLNGPNq47*mlEA#%Hl z4a5Qy6#vH4)~WG$b@J0!kXR`oK(@ql)kCcO&bG8lnjs$f)NCZ5vHeXjD^I(gmuF39 zR|NCYj9yAJ?^kC@93=xLWa@{(I zYsApp`g$JXf}<#Shb~}iYdGXQJwAZU)bHc4DlL_LD1_XKj_Yfg&9%Ib>y8blT)p1oLc9Q}92~?7YxMKH_Vs(iW5|R@p>H&m z;MA2mM9k2f#t5u2rRjUy&9(dTj_fOlf7wYQo&VDI6)q*;NXuCtfjx6S4Khqno;S!s_-E37p+a-I z*iEHdj7L1Wqf$8vH`yD1bIT7~M@5v)+ZW0-rh8RV?0!Yt_*>Am}+Zx9Ka-Dr!ATl)91 zi~QR)AGms4X4H7k-AK>G-MM32reA|0sT|gRP&^&NAs;H{@Z0OO0 zJY9KZ>#QGVga+uUtjn7YUOP4g;t36`CM(vpas~3b}K9Bi{w8IPJUX#rd)uKCme?<77y|p`ep-fI3HrOr_lbxAoy6O z-B#Ve?`?p@yLiuIoLmn0?BgGHmFe#bFA2rp#~GFUu)~wvuW1YIpbhJw9z?j>ebr&$ z25Y@$v0&^U^~##&Ir9YaiA*9{d*9hpCMRWRsRs-C?m4y1815nvAfDAV)@I_N(Lx7B zRsan2Vz=>+NraSThO@Nf^RWD>s#5>QqmomTvbwo&hlY*sANc}`7gTAd-qO$s4)HDm zqLK^;zgkG%bgo7`gT$11~p z61!!=h{%|>qgFy&4UtI@E~{7tv@KIB;^}&RKU6!v%Ks-SN!;Ivk{y|XzHV!9cT_W; z1Jvj&4uc>pUG7Xs>aT?FaOaD&V)|le4Tso@S0vY~t)I?f{Qs~^zKp;VSP$rNmbhI7 z`gYb1i7HlrU->ZjIAFcBgP(?f*eMZZjeF+`?A8=HJqqpnE7PvAH=C?2QB1GZ>F#@- zP2cBs*)QF}Z|9Rfopmqen$WMki;bSZ{ynVgm|lvCDA36CSVene^Z5<+vQ+mZHnMol>?<$n4j9$S^CS=m-vzbFvDedNnsz2@;hZhwCI5dcnW zd6x_tWvec(_w1DU*zr~vorkoR^91BtXxArqTj)!ODshoZ`#6w}?^(V2yu;-mCP|)@ zq!@8BnxPUYEvgoZX0S?t+c5Kdy~BY!*%O1s;}_21CG$YPkezh!3r+<83zY7tHJzLj z(bOnejPkn_Mf&;y4n3-!=j=KNlUNRRQAg%NYEBqCu>1As>VEB6gov*6B12bWTM|(s ztrcHeh3|Rw;^NbS9-n;Hl?30~Brx`SqY*9x()?aPkUL#nc^#|xF=iRPY8R02vd(wz zcyA_>Jdk`iz~YIqz+bF5D3icA>A9ClR^_)t|0zfkh$H-u7YE^OqGo8@AjBq~niozT zyUu{AniX6!XM;~;T;9-H5LRany+ax@W3In;FKa*T!X}=^t|U9I?8Y+aG_u(zs+kwsk7gswK#-ol=Z;UG4Uo$3gCEhy!{hSYZUM#M zaFhVfM0xmbeCwv*UIua4d1b=tIji!)f8AA2~#JmSuv-P;A_S`orX8`l|7X~jL z25=UB4*_G!_Wec>fK44f-kroTfNut#{J-P^ZvXL zeM0#6Ni4GGYyW4mFtp+e2bLdsquCZGNXj{_HRcm7mv3<)9}oog^Wu+$2^&((qQU0_ z7@fLGI#zt0^hP@iOcCxfOcZ}q-)pgZl8T`rE|5TXm^fhHkq{MbF=L9bk8|d46p+gk zL}jxAaJPSgO}fV{?qd8O01>dzq+GX{YtUD2hbKGs0U7vLU}lccN~bookmlPZ0Z)7jTmR+w}T1RAd@1e?SEmjy74 z^i@0hC0AmgRKn;acRv+zn3PWKs@GxS$q#(WWTtRr<^ObYJa|U?urgi}9G8-q)auzO z^}2ArS|Zga<8VPi$b%vHC?1r|sJ{~rgE@Z%A&7}7p69PsPu{P<{4DunObCg%VSj~F z*$7yxF@6sCMn6d!Bt-oF*=`L*a8Ib4>ma5iL;s?M~J-rhGr2TRYpU#sq(W;0p38RQzvVW4tkYc33pS)5@K{@p-zi z&~E4;Hd5W#!KTP+HhwHRU4Qzt(OZ+#h3y}Wg-`vG#9`G=3HmJjX;J;c<<;j55y98X z@M*#E<*VGH(;4nhKgSo&q3|4lF(wl%{3XTpSyVJ8yk-b>ZPS~7q?l|6$*##bP;efh z8%;D`HSS2)Ze2eBLp+@?=kX?<*gU}Ccx zwH(zm05tb{<>ec+6f$zxQsnK5botfXThQ#`m@tezG0{Ec64{{JRSbs=Y?`r$h#$X7 z(*D$aYVvNK*0rO6zMfB#k=8!6&9j^!%Ig<_?u?BOB8 z@S#g2FtWrqnzyIEumA8C*tP0ZO0RW8ETM(%bbAe0!e#^S@{_T_+~3vlCR=VJk$Dmk zl=EiqevqAmT!|lYPRb)u45t>=Z=Hr-6(&H!a6XFs1K#SZrAEAXx9(edX7aT(#!IvW z6*`b`ciQ%^H2NOzdeirMMf0_&seWnT@{;*l??HP4jmm&bB=xTKPzSB|i@TS;-|iZ# z-&eaWJ{V%2UI5$ock%Ox5eVcP=v#9f#J(@G+lbb~L}Z^$In7I}K&A$kcxymCcG|J! z&9U0IH!KTk4d%ws$@kc<9vQXxJBUyf8#Vr$F=)VLlsnlip+N_+Jy`;q=iek<2kYN! zs2TZwM`fl2 zG6Ea>jKZ6L3?BW;Eju$)9F^=$(_Pd}?2{Cm$d}k7e=4Kr4Jf@~=BdG1ZbcC@%tdXc6zqnopXNMDtvE z=guyyTHHG6kBODR#vp5c>TGICT#{G&&0`DIOsyW4Po=bzFVLI*n{Ai+D{@ zp))zkzovIkVs|u=0#HW7g43>MnTF}1YU`L$BgP_L30Gi`ifFs zY3fYo`F6hjNqw%J0Ba(r=8T#8SXX{MOc04a{927B>vXnIIqRby<J04rYE?r_J@(*d43@62*(I~2D?|5#ylS8ds0Wf z6+W3=LLr|2)8n1wbQ@<=z5JTxvr1%}Uye%k_dADdQT_d|REB!%`|E-DzkP`tF|&D; z?8JT;zetg*PFfdCj5shp3wl7ukf(eDyF(D0^Rx%?1xE@`G5X(_Nzc*up;@a4=SdCS ztfZxb*z>hfgwHilligEd{*^u8-T7_|@2jxCwrOLQvwuYjZEZ+TCH>W75IXp>rh?9* zR6nd`Zdc{PWLBqYN67PHVkqYz+g>N}q|jf>I=H8K=IgGl_y(0mlkvoHB;tAL@4Pi? z_X-`80`lciKU2J|S$=JkfuLcDduS-Ou+?YNwcf{kl1u112t+xeU=Zfz>1~AFyb;KjRQn;|jetpY&n%5$xOPJ-f2}6El zd7rk~5Rh%1ibFy#!??X9X`>^8DD>HO7n%Bi)UW z;Pb=lCBW`fa?}}t?BfdqKa>!a=yhqSl z1cZ7BJEB*UU((&BT*sf4JiZ-#&4qu8BGxT@i@dEkeWDf%H1o_v)9%XLfyd1emGi#t zSv^dL3X1O)=J86IykGP%+1OY6*m!Fi{L6i~dfO>W{!V0bLwdscGzyn+U6o?Pgum9q z^o|gpH;;MLeH;~9MC@v%LEGuhQ}T3)0=-z#;$#}s{cJ#@gg_Xe!s_P~EXN>5KDt!j zxhuF$c1N`zEAF#3I+}%B5?#o&lY*pt@%{y|7M=NE2M|AW;3v}mNUoW4eo?LS%;br8 ztKAQ(b|$5dVMVxs`9BMDdvX9lhInFCs`AuFbA=9pgABB!SbQC@zIXKqZ9u%->exQ+)>gP@HlY;e`>n8ZmR2Hk*+=U0%&rqWVmBg_dk zfXO}>Ng3xw0D2bb;wS)WJnh-e;9WS!i3q{#1Ag>U<;{Z{MXW2c2YC)>L%7X%$oc4H zKijU$9>zHRVE+7x#e`5rT8x?YXVsQS7nx2nc>5|nPceYAL0iM(P9%tC=<64Tuon;p z>f8Hw#V0jA^{PL4tmC%}_Jk=(&iC+-Ei&}h!-CoIV}riL^g_YoLWiZ0B$DU3D0cAa zSJO6}*^&2{7#b0vA&iuy-^LRUK-&I{s3OiJ%s8sxAwjJ4&guZP9`8T{`K{3r-{&dk z+qnE0k;8g~F2mXzX<(0DPl6<1c!_NKoC8tRlLLwXJy}pQ88>tM9%0xzhe0&>ws+FE zwU;$?C(b?0`$7fwc+`U5(=&f#op|&6eeZ^ZveW znGwY8WPXQ4hvhMmdppA~jTMMYxJ}iBAJa|^TuYMj6j=QJSY2N9iq`b&sM2`7#{*ku zfLdYqk}RtHhU^ADpB>l3+*z<=QU5yMtI@=&+-tcj1 zzzC83AF^?wUcWN-;w5Ihu->o@Psy!)4MBqSGLOgrYr(`U4&7hdq2~^7TQHFW!ij$x zbZ?&21%1$8CvCecGU!lCvJ6c^mG?VP^kzYn8?nwqtWA`7e=#|uyeNNF94Y$u;Mefiv}Go8GqNMShe?u@KxhbrZ;F}aYK6)S?gAjZv@siFlL_Ylw5 zBQ8A=o9eC|{>E*~9g&8={3 zDFdX~Xw~MP_TG>WlZs< z>5@`gVjcW2gbH<{A7)_xJ3#!E5x=PTwiuOWu}Yv=MY7F$F6eCUGh>L69xiv1damq9 zd`pm1LArU?Y&l#kSt@ZvV|DdecG|ZXeueRC1=T*z(*z4#Du06&jktg&FJxtFPPmT7 zoabUfdmirZf?4InLZ56lmyl@PfUFjjhEmcOA|)U)h@2Wv6Kog>8fyKE(%5XVCFoq* zbGIh@ldU2`Q%hbryGcLGr?LPL9#YOj0JH26N8;JSb!A^S=qU~lhfYh-^R7c+{uhq? zKueH%)R4AFNg=SCXt=odnz7{wI2fM^a9?+hx|YJ)B00RW?|c-FnW;B;8;mThymU5rJ!=*9KcEUcpg21wAGmiM;SockxLCp>)fI*31fOp_HECkirzGq!zXmTY;$XT}9| z;OnhpCVy|q^SepxnK|7V{T}YMfu)6sp4U-ZI^z~Wzuy10gVXTw zf}*Oi_R$6rTtzr6Z{yp~(kEksrvwg_8`zOCpaJcyF|6-5yYX{g>jxLC@AQt-IB_Mn zky-lCF0{84t`QQ;EuXV%F>`0d6#;n%y3{c#Pkk!q%-OcR!}$dLx+$wozzmpXwd)Qs zW@K&Mr)t^`wL9V|X~(a97J1oI68N<{h#h!52kW30XT%+0*GLR3W%!atBH65L4O zUnpV>zvI8D*R)kopC)(&%06C9Ab0T7&Qe0cvo!YDki!v<gqpYX{ z43IH@-Dt|%s|PjLkhNlVhyzn#N~=O7g2UbP`?%+x{|<3B{tg|~KBGDSw5@i$By0N- zto(@EQGoD?1~_qxNz1wqbEn>sTO6K6hM4FNvz4fN?4*x>fZo)2(VqS$GKt9@zuS$G zJD;lcGyxy~dGRizV>5Az3;i85+v9@l_VAZ`>4iT1Bh|mXDdcB|+7y}{Uh}SxE3dh1 z_6M5vFxwsaMOMfYGt(GBG| zW_cj?d?(hl-#L?jFdsnAL5~}|MAV_EXi_y-k~Fbgj7rj?u&7lO5Y+BN+L->JW4C+Z z3#?&~L8M3_lB-UKoMQS_F=e>iF4CxS;7>R?2o?Iqu_B1z-1;rTq=2zOxhuM>cs>f< zC|uUPtY*Vpyv(cVC@dIxb)--g8|YZErSw)YtOpx9aDTtz@X%xPFdEqv%KP(*@>+BqtT=D9jC~-KnEF;Q+^@4_z0v! z*h`vhfBrg?Rv(mI>c^Z=5~rX=$SqIi_|ZH=GPQ>riwbT`vetrY%6Y8=lUYm@`)Q>% zUN80#5zIW>*-wn{I+GX!hX%CrOdqJO>UeJKJJ+_V(=pBdX{?;tIWrXIri`i~uLE;! zr!dNT3`1cVmmg|x*_UW?!E@V;d*f2hOiuUv$aKf6wr6%Ml(e<9ygHjAjNjBu zoOg3d^~;)vI07T1-_unRg7UxPP7ZmNh-gB@o{vX_jKq)yU(V3|D#EhVR*=ZBG&@Jj zz7Ltyp_yc}jboUTJ<1>JB!%Uc$dp6LK zM?Di+RD1{L6DD+sOxe(VI)4=r@dJ|&u5OZVF3D*2*T0&;yugEaAk1Hy6p@jXOWGvp zl6}v& z{`2l+mi;u8i&|Jgdwhs|jv~Lws1Xxi_)q*l9K!X%kLXa&*%AjX2lv!&nqI|QiSnVxcbi9=H#J9HS+Q5h*jTvt8!5?8ME zV@K;UZ5u&d;e`B=syv9YQ5dRM^*xb1Cc!#Q$$JqEp)U!`BkdF-QK-h@q5O*PPCpS} z3J#}mMx>1OGsOh|?Osy6m^1Hl)17k@Id^w7Xp86-{>^W(C@^hZ2v;bK!aHMr)l8~L zZa;D(-si(VCAk)pFizN~Kr?r%$@99?9LE?|8Mm=C*pgDadd0 z5ak~7d#3T@vOSJz`XMKG%*R-h>$peG?2ydBl}oJzN@PWc3jfn;61jg=Ig#YE1 zciPsDH;;}LZ5yX@R512}6P3Duu&n9GiQ+MW3*Y)Rx=@{?J_4a1jOUprWaxX#5g{Td zDj;7Pfq-$&!u;Bo3`KzJ(s~Ymjxad|A64Pariqz3$2Alt>=HN>KkyOh=_Zc1`8)U( zT_PbH8JkBVKXYDSB6e(iu_kkn4@Y6zA~%R~)gordSme7WfaYZSF-eS->jJN-ivGl;^;^@`Au(9fs6+Cq7O1 zA>p$I$~UbM3{y2bxZ-;zH%@@kKRQ8NI}p)dev+oBLU`M>HU(a|4kelzBhFEo7;Y39 z)crP~t1aKcpY~p&QA*pUB20B9^xmiDen0|AYr%Fi$8Ha9fyv`n8BGW3^1!T6P15P2 z|KpC?xHR8BL}S&IZ5q`0B9%)?+jLoRTtuQY8s{G46G}t>EOiG<)6=M<4z1NnAkem#hqrXy>e*=94;92+wJy&cVX9)LZ$vK-;-L14lJxG1W)k zM^kLRJeN8@wc3{{j&iOfzmkvtvQgmpJ(W$`*P8TbX5nJZXTJLB*juAtjAD1)n^VtK zfNmtcMD`caS+caJDcJbzN3xCinIssqOoBH{B+I`JGDI1W(8RVmx-XQdlGaNsBciFs z4`tSfi}~zr0EBhE=t)*Rj;D!~QEw;|M^t?|qtim5wL8PPakjGJ2;BB81(=B3v$K=Q zTwje=ss{=_M*KNa~|AzFjHS&3;nU}n@ArC*l7 zkUt^y((C=%zMtVmsHu~I4RM*<{sEs zhBK~(V~uB$l6b*BW6_i+Ddvys0=lf@883i?liFGR<7GMHGRE|T&_8--R}*Vm3&w&! zc0U~K4n7o+7NwzQRnv!tu1JcVw9NTg_08_|k4C^E>EAu0@Qqco3|2$B7=poU&MQ+w z&7}U?q{@kjEDgy*p{iYE$#BVTXQxl031;EIvvwb5xG{HgPDC|LZC6|yM!J`B>Th_j|QVv)r89=XC%!sJ&Im)o_D~aGax*eWSBrjf{`Q#0`QP)?2{<&7M??@`d`WyqgIV ztjhmf)jMs%mOd8Jo^tOF*u*5hslvrUa39X$ThO?}FnLXal^>bmtlMU@mUb%hJdec! zuB6)h!VB*Rsl}P9f;tZ9#Q9sYpRL~~FnIPR=O=UC;&ao=wy=~K@A?7i1d##kEQ6cY zO8Jy!o0YGP@kq(A8D&~27xa?iVrdW4dt$gtiM4xLJ{XJ~ZVxYR6D6@EL^Q6*0RM;< zmT{was+Kn!{ky3P&(i2>cBmQLoJmBk{0OcEZX`GMMU1ghD`luEa>G0{XZJ!W^5Clf zdp}db06%sGd`$9>F%zqXAa3mNsvV>V4<0xdLzW53w0p49)%ArV1AJCLN<+J@79tn7 z<&*d;q?b6U+r~J};w;D=+9P{GQUYjAZDN*?mZns*4sM1$g_?7a25S9f)T|Avd>5(P z_p6tf2E}C)XvUdlhq5x7V#t%QBURy)k__Svl^<)N9i)i0Nu7Mq_z|daq!)to{UP9z z6h;=XJoKW>_)Jp2il7UL?c^(+Giww=|6Db71b6;%{o1OIX&BPdn|AS2!g2I%`Yc|$ z7J}Fn9d~2gWBA>~!?egCvOHHZFcOsrIkZ)VCSxO8U)OhIG|^~Kff2i4oGWgbAthCY zG_9Y-g#aImePo_XJ{~TOPXy3_PTGair~1yAktTifs-ghk zG$u;WUTcfyAXD+nJD`Bb3O6Z9suCi%r8MSkt}>(dMf{gF;asiBFoV%FlXa)KWftiM)gZjt>p5x zwcDmO+ietSgckF5R4`uWQq87wgH9!T1gKAfv@yx62eF7`&_t60-)CIDt{Or#FLLi^ zxcs%n{4Bo&xP3qmv%wB7=YEo+sd#q&`6pd1u=Bx>v5@N9OOg`x@;X2Tu*z8DGh|&e zid2cel$OMx@y%-`8mKm`i+ZWP32@JJX4B?Nk!sZchrPE7iYsdNc!$9uxCVE3mq9{s zf`x?O?yiBsAp{97!7ahvonXN=NFZ34AVYxQKG@xS-#HJb>b~B$Tk}8-HG5{)+G};M z)!qMJ*Her&#+ScM1VJ({KC!}b!A0^YC&P$0w=r!6P-O6MzEAKtT!@g4bAt|96qhC~ zLIvp-utr|ECxd>T>j$@oQ^C&WCtI9Eq)Y=Rj6sa7iX1)q{wQ=OQpvZ=vF?L*uD=;jkwRtdQz6rn9az#1n8TwkPeVuuWA|yXC)Kq2 zN_wr5Cedz6ti~p*TmFbGOKq-_W=$V$O>D_B`AOc|3dzjE)oDpHZ1haysz|ZQxsG_7 zmu6FgJ8Iyd{ah}tFnk#78Iq9b4Arc@$+12e==gGm{~Pl&p$Qm5%axbqsiApiY3z)- znl)4!|q{+#QkR;lN%ZZ%a z925v6`{CnoWucet4c0`p+f}H?Oj*aTgUv8f;l;9 zk-y6LS!bt!K-j7ej3rDm&W_$WD92^#^;aXduwNh@5fLM4Eh%i@S#hR=>+>Rkj!$Km zU`V^QH8oM?F(GUi&6LY=m5C&fcDa+l3vss`VFw@Z>QxcULwFCPp*V_YssOYB;s+h-Vw#dixIi5LQ z7^ZrfgAx@tld-03GD*n3-OWJC;2)6Mw(BdD?7rtH4Bu{@rdn8+?sR?h8FOVZ`Vw@9 z>L88|_dDF)MLBrxE9YP7UAA@Yo0a2>Z=vD%CjkuoytN@A{8`<1{r8JTQLH7i5o z@T(8fQ5u?o3r*i?eVrv2kHw)DlpHL`9u zZO#@037kfUxHph^x-r*>3yI(?lx{G*=D^D4Io;spmE8b$44lO1kRBJE z1V=@uSVvP#-^L0~v7MAW4IJOL_V39iM|T*q%Era%y-S8Px-7+`M$q81`U0Ut9d6C$ z7|!?r>jlTaKG)MmsJ~dBT^S&wKdVJ-|vc+K3h71g1r81WSYeqmL;6wB#E01>1{6|TIophuJMyAf@zvWEoe^Yr#iz81GWdete* z7j%GkfC+4FD)iHRpOZLKcx^_s)VwYbovIW;&BGwYjXF1Izjf)#L3CaZ&c#iScn<1< z^D>ZyqFkAI7(B+_lpo!Mu!>_&{YDR&z+MH-+6BKy-XcWNtKBj7Cm%;VJFIda1Lqkz z2PFFkLo|UdCVUJSb{2XbGrzvgLl>ETXn)7)t>TRo9)&4nD zw!90apisaJSo^7X?@Y%k=|VVQDMMnUSw-n{&DD-80yjRD%}O0&8aUt(XY-GHMg zfq-=M`EkmXDx`09!4@)X61H=V-AhE3`I1VxfVAg{UzkHF-fjOFTD!dC0ZGeZ8vhv| zIqlG3z~KBF^arwo#?s^ZPZWRQCC|+|G>pfcX|BY141ZI%^Vnfp0GG$#KND1mKHY(qyycw@jz= zmh$dyk;+v6SW{+pezgH@ASjg1vISC?zrWbDZ*jhu->kiO7@AcSt;7&>cW?wI<=;`J z5D93g8Rn_;jhgAjNh?h+k+pUAkN0udW2;vL&ak|Jx!)nIm^r2uMA^v@6pXJ0^+)n0 z5NMnIGO;h>N|W+M5!smVoXhhI|9W$qH@;hmy6AbnJvA>>J|S@z23h>!MAB0`*hkJY zYJ~wBmi@x=ds({62v=FTO)JV`w!;2BT-41H}WJ&MHCT~zEiK}|uK@asL)+CR6} zeUd`NNsnFTJ{jHD!bSHt_!FF-_#Dgk#0iu));<)ZeUr5qmo#T~@mv~6LQpWf=PkH; zwBGJVz1SvEZSy!TxTeknmr#ZXf30aii360i~(9wKq|Rv9+3Klt7dbj_cOP7wiB`S`wi;^W zt2AV%9UTINq-ITra*?nk5{#sCCtVl`eB6$1Bb*uIY8P2Qobn^0k@GQSL$1$ucFS1E z?&aG2m*mLS*?io<_T@(gtS-MP$wp(VLlqND!q z+Ew)EKQ+;7&Ds9qXa0XK5B_-$25t8>WAoRRJ*X5Lt`5HCS?Q$_HL$symSX57P{Why0S*8Vm|Q>~I; z_t(}|{e188=Tjt#Ya@~7+F~TlnsKo8>GtL++B^LrM(ZNHvj@P;uthFl?%QvCvq~C! zC0)R~um4SRo!ZNrG3xvxWhv~jiha=TIL=d+6&s>Hg|4?NxtV{?eGu~*WT#*AK1AJ~ zw9Z9bOSyczBCS{ouqT-x-ZsUu{cHe{r_xv0No+(ht~#W5FCzOgc#3v`#+JA;bwVlj zSWBDI#!5|B+tQ$L0&!?9@mU09TV8y!;-Aj;S5+>hae-)KvnlQ0jOkhtMa_9i?#?VY zjyxV%>wz^x^^3nUT#L5x#viJrXMp0YWXW%o6RVS=c*pX7Z!P#10#w1Gnnt&vEVj}y zX<$;D_f>X{y(Zo`kX;5ucf-|+yPX~4x`dKs`nv1+MO-XSF+-0`5Y+{GUfQ5*bEeV- zkxj?nQu5^jn5M*9e!i<^;ly$2u2fR&aC?@0* z1)`1TGebGVNVwGLJm={@Xn_cCezk9B3WKyl0Q+l$#Oba^#h)d6reh)Q#Vf?u5q=^%hHQ_uKyBlXnRb5QQ3)!P2y zqqXI7X+jK^&*AL#{e>FQxS{Xv>X6Xm4bHZ2nP$6=AJ%Z2j*6jxpP{@rafFAwTj(p2TdVn~}bUuJz}-|T2YhKTg*L>3(1 z4e_eTkw_U4f;N@mMYf@gTa7AQAz4Sj0}d?DO(JLGrrLDlFm1mzi}$;!3J8R#bzDw* zdalBUx>;D4Cwb75DN0BR-Ccz7>938r@?u?ii`Fl?Rt$j z7O`L0{>~Ie9xxuNw0A$$=rV+KK{4p`ZcS&iXs4Adi<$M~W`n)El0^BdB5=9$X`g$h zzOyz6A;~xDz1>HJ&|A{ma^NLq2kwiV5*-xQqXNo4&7;*3M4^B%ESAcg);9k>ZZ#sC z@*tiv!(dymJMk<4DB_5|YV$|EP9zm22;%O(-e3+QDCAMg?3FA_;rQf>PEQ=QM$EcQ zOZMiJnL6!tw{Y$bP$|H+`&L61*Lt1!o!x8RRPB-euJmTvmzqr?$MS1$bYGUJ6t)1? zfykq4?BbYAbnN;e;wILOSYjv>n_Tj^O}BS$>fWR^?wwwv>2Hu+*=m904eq~aRltT`E)LfO zgw7%vE&`@SM5(7bxRjG&lgWv~d(f%eWJitT{OuG1yJuXwJCs|{-${;r{L$~8TQO5i zqvuQC%eb}4SVd`hsQpl-$L++RJWk6`2-5pSj;~ZyJkVd9B7M`K^^XaOfq(^rVhodp z!Z+!*?b<4H&hmLNHqZ1iHB$UTFFXIT?y99{Dx7y(Zja~Oi5TCdOfrz1JCO$sLlUFj ziQ!EdofunI$u-M2=3XKQn*6Lf_IBy|Mok31TKhaC-#?Xe)GxAY85=k*D3E*Ka`1e1 zF@0N&e_;`S(*8@EwJfif=muH2l9_7Td=9s5Am@W@K|k(QhECK>e@_2Vk$kwOCnL^HIlxNYJYl3$L7mF?P^0DgUDVg=eOJj2dEPMUmvw8LwUV|M4!qg(UdU^rcYzCXfXeL4ph z6nggpDlTn8!d(=Kr$T8zDY)PGODi^FO=|cPrknj1BbW!1PWW^t!tO6M&P?w(<&ouq zkaMcu^(04HO(0b5<@p8CN4vYuB~qR4hIt}i2AlK_=kL4-ss+pmopZ||&f4dkF8a%y z$0&G7%o>P+ZrSrqmlqa2f%NUIczGAgKhRyN>0Ier z1EHaydfIoy_s^Sp`EE**m%l6z^t2r(6?*Q_Jn5=RwIc6|IWagRw1j!E|*)AMp$KoIfF*~ zQ~GQTDzz|iRRvmm3}j;H=frp)t6`?u>uPfHy7(EHT~2q=H)|i7S_V_nD97I!cmCn@ z*SF#s!U?GXH?LY!(ev(gXk;1S_u|XWW}OaSVMpS{#y_5J{%tH@|7fSYrWJ*BPynG9eH?9$Gr zU7vw!wzr7f*rV&-$l|{v(!UbFpDNJwsP#O1%UAdfq-h{B;nVD7z&d{sv?)3QD{nL8DDu%@F zy?<8vPq+%C?Dmp-`s~0__2vx%HIbB0+&<)0w_wQ0v%{4=Y3g8BYjdf0S0eBr^%SoP({CZH zW2$s^S1=^*nA=g74~0wiR|fc{D{;_fd>kNK4k|8PIKey?+S`?rmne33ovQa^n9N8w z)FmbRH#!%2e%~=_Bc;g*H>gTZfm>PWaFx=BaBO?}qvbVq>q%P76k5jzfBh?Wd9F|9 zKohV)D3@ z2QPX@z)mPld<+8yd0I-ojM>SnKVkYvjj>Knv7q;g05*|DjnOXf5RyzdCTETEtR6bf z79BAUo4Nne?={H2$}Mg?n!9vHz-*mo}a0kTH2@!+th!K)psG9vycAi zc%L2rFJEh+u_Bj zspG_FV@GSrg2Qjq%`@a>I+;PzB9u$IXOh;Ej168OuMcYSBsgHoeztOj;wA9)o@ZQD zBcah!Rn~)X0b0Hpb(gW8*(pzNzC!{xcV8E@7=4rGvy2L7o{YKCsuFez`ub6HqVIwM zZokw-@mnP~lM>ad;jFn^$4Kd)aFAfopp?98^B$`JdRGLXV@O2~65NTdR5c!Zok5Vf zTWDRT{^`|=j*|ceFxQW7DZ4G7_;Wjj#-N8Ww!yTSnMARfrnB5^#swyxA-7X$LAErC z@gb!izapv0j*~B4qo2*pddzQ%Yll%RC?PQAznyCmqh0t~$DCRvgmCdJD7I9clYLhI zI|nv+lUfTxRSa>LT*pkb*dahljMcO7yJa}#+57}3gX$THJftp)H8I$<4JvoZF8&b_ zl73f?B9ioJ=;m`h(WoD=7n%C?fuA1pV$&>A$6I#D=(rU z-njN)^rx3W3^E&BYY2&oZ!r%LqgG$+;oj4v=CXCsi`$Jx8QMoyxjx}>7XA+$rqUCH zlQQtJoqSPY(m_usl3 zifnv+7icbr`(K(5bt1CIaY7#>phtSow_%gs8A15?Q#qE^@iWUwUX{X|!$?VAxvBv` z9tSa0!~CY*&^@W+EWSM(wUT>*)mQnlUuz5MRHH`pL>1zCq6+bC{TCeZgN3RW@+71b zZo~W|vePlkOoEZF{c4&&TN*gyPUQOh&P)|D!UIO8ED^b_bku$8l0EvnEfa=s4HXkl zY?5rFNc8|x{ozJC&9mtL4ilUoS2A95RSWCgi6s_^?%f8cU`F3I z*E)Fo4@gm?j%)2KklrOLhsN%SVzX6;hi%aO)=AFv#vza!7)ei||1+F6RJ~4AXYO`D zXOucQ@_ZDZh%F|{mu@!V#Y0A(lfoimqF@R+RQOEj)6F=HBY|P#_AxeFBS{OnpXplK zKH$q6iz=Z^u<{A=FC2E?x)_3t4ru)U@iPvGF1EV$ga7+OW7{ zNcPgp;{U^HK>fxl%;6;MzKYp(R z(4ot2f2n|{3H9`1+X1n{-8raXL>}>kr4fXdY&^Ga4W|Iz!0{3|AgWVHOHs+PdjRt7 zUm5CD9$yA9r$#;>|CIWP^AQsUl;e4OVF43`n+llMP;!s|ey5!$u9>y_l6AViDD{Ta z?#Tji>>%NnkT$en08Ha-6fVN+#wH8vh4dnA2(JOszX;hDyXaE&Y~2dsFvuaT-pi*( zxaBSl#gYB#{~hk$P(pN&oY@(7LFQE zdr_a?6Sk#Ndaj)-5_n$gX27rIMbbz9@6G?e{ly&#efntG|Nqhd&qn}vD3tvp+S%Ya z?L^y>;N->`w^@sc7p98fe#Whk#nUC1a znE$g@GD-nG5MaM$o(xXi+RhJ0Wf{Q$xRB=$0nSY1?b^a0>@Q`6U)sJB?$P&C@EmF2 z^htI2=-0%L?($%h(QYxH&f+n4GE*X%JqS1LZ5Qwf2z)&||=w(rF6L)`Hp z?X-AwbE3I0GNUGUNARk)^>b@lWbn*lz#`wuzpm?9cq+-_X-WCTV31Ca;0`XWUE}?9 zhhdh>*`(?IyC!H*59M7F(!5y5u!BuLvwss5Du%EE z(Yq`Wn%Nc_dO_Y~Az_;T%L4Fp;B=lnP2(=gmJ~lubqdfRl_knD<;q#mw<8d2{$-Z; zw>?hZiw7m|kXmv!N7N!w-{FA={!QH{OOmqqLn2tB*u%BQm zOhjRm8Hh^N?hv=Igx__4FYj3fGWjtKuA7RxUmbt7vvHZl&AA5TrRD z4mjnN=D{vBNFbkeGW7kd9umQhC~LE)^_-A<<;rEsJ5(5NhVkI+QAx81!H*u#Z0|JS zy65|n|6*#1vG;aor>{&r#2c3w)(ngDkbE6TL?Y8CET7nA7dQYqYY+PC&=Yvm>G~Dd zuy?v7$n}!?x~6)4pVHY?SFg^ovt}O=c$<-_J!8~1<_G_Q3+sOV#2;Hb1!?ZYj8mRC zx9Bx>ino1U{rX{0=XRH|nPYU#QZWNtDaa+w(Co$80*mAV$4bYRpmJ5(73sb;#`rAG zidc;fK-!v}IP)qo3^@OkX_Vp2qu*RAUho-y0jTJfRsnzx{I~y+aLpi5&y7C-9D7yg zn^oFrT^4}-glQz@ePlS38had? zd8o1@AE)-`=yrcb_jTB2SXxAdm2W8? z8G7_sc6R~^KQVESE#<~Bj!HeyURhy02mjTQZN4zTx=M!vO{OOo3|);oXu6-!c|}iL zvfaB;Pf?I2v`r|z6tj2E=y!;x`^gRq%)Y4&_3+1$_xQoEpNq4|Vav7A#LduGbO1jm zWDd$(uHl9Yzw2zr^hK->@WThb+&{C82x{M^ab8r(`VIJ55!W!>cOgL3#Kdoxxe=IP znLx`nVQhw=HSfht!6~utuQK@hxVnz}spHqb$b}+bZ2Sj-2V~GHcFa4c0S?+hceI2P z(vB`{*WLZ*FNPBI>9?O%12e_g5SW$+zWEn6sCjUrzq>KqKGUiuSGT~@mW@DTt>KsZh>TwGlyR#tGnp$q<2pVsAIRI4 zIq(-$)acL;=tiAD!{&AH8>3bF+mb4su7~>FRztMVMqM;zTlt?gb9eu#F=+#omo=Z8 zIlhyI4XU%iL+Is6f*H`&FfgXyy3bIehTC|~1dHySZqqm0t*^_WYYbXcq;Wg<`xY%5 z?vGOLqd+c9}TuOT5pjb(4`T_2RD-|i>H13%5A z*BQ$f?Y{dk_wYB`WPOc(rD;EoLuxX?wPM{jzti9{#YH-~zn&oa-S(B}T)5A<_|T~8 zY`!;GW4y3Aq_qCE>-8x4jmfeF=I(b+<>$d%haOPmtWHu*Ky%xjEJ7<`Spo3m?~^?? zsLoKjjNc+Gx!Ey&v$zisLsvu-b(wQaC0ZZ!4KeDY6E?RRRuxF7u4 z_p9cP_l^4)`!9djFBy<(nrb}u1u&grZhA#DabB1zEPEMd`kalhG-*2`Y4Vsxw>CCS zbXJZ8+%mFP?*Co{02&>F@rkxWYNn=o3vRxBkdLu5ue16C81^Xw|46cX;_Vu8`s_L^ zBuXdQI<=mEwgzNgGYPJ!qqx3mW0rUKi$`IJOp6D+>vdg_FEoGR2> z#pu!hNgO`;gk<~&5TD?ul4iBMiw-_FeHEmd7D}j$@{d?SfctN zOb&Wd*7HpxX@(&0B#_%7G0=|tjTHqg?NPFT(N1jjoE}B(W7`i_YA0+Pq7WFN8FO6m z#zEel?dQcO#*CMJrp&TzW@!m&WPNb4(X&Oh+6<1Z!BAGx?K7sRgW$n|Z*+7DIeJyo zDVC+JULxuzLfrS05myb`A0`7gyVG!#>$$Q&TG6ljjBZatE2|ty$3SKrNO^Xpm0MOx z{#(9Kjp^vQF!0|QMY5XNxiGhrTSWE_Z{p@I63Kd6K{ZBB@S|Vd3%%Sw<HahRQobnpvyrI>e2@Zo`^%7MqL^^3QKH=8{fnLz!;rO*ndd(Wgnbc;U z`sFyGP`-5V$q4TR@}}Yv?40TD?2}`*3|EiAE&$e;<3;OolqUH&;x45t8f;)+J==)b zmdo@%OmlCqo|wF<=`8+fvd-o?T5TDzGMQE~rwK3Dd1o~Dk9X4)mE~J$K7hWNC@jUh zoq+J47~WoEHaz5XSU|;vkB+Pk#grDZc39#rYJ?|zdXDNVs$E~-2=QJKnZfV=eBg51 zGg&+e31S^(&*~IvwgiIPA%~9EE&*Eb`_s1*dad>OBVC?VO~EGV#;N>dqkfNVv61?D z((CF&@E3#`*Iv71C*tXTJ*GkJFW0Me^O9FkKTGD#fcq^8NnEd*&}%x z8^je5d3avF?x%WZk-Rb|QU)C@9UwGbmnP#~AGMx3MN^>-&$D0QuHm)Y)e8E5#`BA4 zc{$EhnS1A|SfCN^2^K?2Z09^C)wYL0vNSp8!+OCU4AwmPkIH9r%xTS@bXYM=9ybu* zm^DviK6D&JZ`Tz73|z5($G`K53qH$df3{{#9g?>MQ$nLdfgX0uy!_^f6|yVNS*t@4 z15tYXQhRkZG9iC0p}q`;isOBGWpx;+w;+s*7EMG4t4%lQ%W0pLDEAP+`fWN^-tvQz z^2qGPKfIDxX+hGxE7M8bmnqQCCvD*)`tB0)LmQHq9+@S9_TLY(Mk9=ro?Gvvxfg!( zWN}!6cw(n%tabW`N*;9yE~Pzze)s`kQ{nDXqwTK2H6cJ5O-w-MCvvC!T}c^{97X|oIG^{2E}5G47SZg7aiHQOwc{e0oDgg3hen9wK2KLv^OkvG z=@F{$pKp$=7@j=G=T#WS7>MI%LG&TcT&$;bLSHX*Ouixo%P&*oVCIJ zHHvxtRZ&+d1z28Me~5P35u;l>xM~MP{#TPD%HoUn_pI#H+zk2kUk8n%2WPRu2k0Qa znzVCaB(h(rO{qLYi5sLyoOB^}LVw0%gelC(4d#;sE{WUV55T$4rG?~2gMmkAsSgSp z>O{M`ELOPxvNyH;qQQb}%l*hPq* z_rO^&qz`5R<9BX4gdJnz+_d7Fwd7CO1`}!Vmz#E4`zwZA%8Xynbz{5XvHei5lFEFH z9Lf0UJWh+^i(Gf&d^|dbnui#H|G^`qx?x5lHI}d}Zsv|EU39>KthFrIz=e=P^HS6y zfwYTSRefdaZo8TvO76Ax9+k$>PIG>ty&nGzu*8h=1G6E5GhaS5x>tVNR zu!Mii*Pj@|Pri3mo(pEyK62cZMfi>1x$lnjF+cF~+_>_bExm4ds7u35llUtJF6efd zjH|jxxSe`uUB&0~5;QKsvdgHdhb6_rGYv>f|M2&}8!JY^4|_{hU}ySg$wyrP06q%f zIY$b7Xb5Op;z8&wv7WABGE4_>1>$<_aKOC~UpbsRMkM@dK+8~#iXvXV{Ia`Vif4(R zP#M!+7!6Cj+dZnRcKw`ySh;t?f$ie%;^ATbMdmM^CF@Xqq!#h4$bE-2RrdYhtCJZO z2tSwG4EJEFYcMC|pL}lJzr~w-U!*)x!E98$Z_WgN$9u2Rrtxb$C(8~F;uOTqo3Xo>U;3+wSKazHvzf~ogZ!EKM;!XI zh8`_HRV%v1JtCTdf|+)O_3_oP*!YiM4p}n{5ApL{cy&+z6J0fkMDAF`?9~~+k(vf+ zev_BIb|sQM4+fVQHSPIX%kh*{JH7gyh>AW&Sj9^kH%aZ`#P9VL1E%yzcE7Qr*JIad z26qE(pd@3EWi%^tDpvN=OGiJLenEycCWLk!MxSHEVL&57jjv1X^w&%4SB&~2`*FBi zLJ94=$dmyCSnGJTQH#0d7pu8p#Z=T&h5DdT%Z2^!>8rpfjOoarnMH>Iy?}Z!;^wtf zmh-=|+i`}(Je~zSt}cvqP1m|=Z|?K$OmyFV)Q!aHTZ1o(NQ3tZx7#jkz{z2X#U(5w zsxj#W>>^Z?=NQEo^fyX!QTVHZ35N~ML#k83bYcf6sKEUt9ry>*yzG9L;T2q$1fv0D z6&QRyl{JI>{(70nWR;g<$g^0J9>$n+9E75i{sZOf$F6qFW97!z+Az#Mf~yZc#k2+5 z3Tos>{ur`kDpFpMFOv|D_P1;AfV#KwpSYF$T+_eI!2gWdVPx5wi|4s<0vBeDuR3P| zUgJi-`^G6()BE05%{GE#cR{aH#^u%*B}}&NO$wA+5^HxMEm^R5ZPJp zQUlU3NguO_MLJelQ8or#2u_m@3JzL567IAwpqXHVD(|RrZPyWdt+^S&%CDBQvp=9t zpuXt^|IwZ;$V! zxaiTWA}jc2oon{th!HRw9|jaZPi~uokL&tQ+I{B1G;oFN9P{ z)?&-z7;}1}^uEjiLJfNF@j(a${qcfEYZI}G$lwW2+I?w}@DN(i{=4e#q&6Y$87FDW zfZx1bC9ZYigIK3aIKioHBFRV04e&J{O5>>J^V6RqO`tf(w;wt;jRj0~9EAq5Okg`#Sh$Gbdz!q|8|E)4yXSr?Tabv^^fc@DAXqkjMIEG#ej% zN+TF0C||dE zjoWqB1*pnJZF(j3;Y8%va>6QVE|(`sZ$hU=<0Y~|j>JEm{YzX^l`~E4JD|QYq#`mJ zBKQKad?#>NkN?;nTixaXzS-mz5G|YC#~-EKUtvNhhtw4+@JHRf>}fYb^y7lL+D;h) zk@mS$F{&IxB=lqidWOq^>V=T^NA-+cFt0*3j*W#tHY(6gBJ1<@qMM(+J_o+GfqfRU z#*OX=wz!H@VR`A&G^V9Z@gb^80ViMDE+$(9eirvvN-5a5?|^+T{uR{=kfQ4q6VmNA zCYSP}gknSJePnn7$x+$NE)pzM>A0>QnLU19Sg>o*DrqCvrO88$4z&JJi~S8e`!d1Z zB;|>HGc?-VKbB>d1Z`V& zX`Y~vGa4lA?r0_$oZa-TBfo8z#CO&)QuQ0pD}uyKFMc(plpHvrHc*a*IJ(>2UjUm) z?J9+kNWXZ!-;Z|3wi|rTD$Bw3#>o8L901k;->sY%zN@lErz9G(TlTeSY-+WG)axnF zSvh7lN(0~Qm5P)&wKl&ebq{$Z;@E~PVDo#`Dzc$FuPj9nj zCDJXoIL0BE)_i!8J`46 zv_%d+Ezl!mJEf9W6q_S~4$*UKd0kPBlRGmBk@dj#bJ;Em@cANasEX(8)ghwsXXc5!ZBK_N1q969I z3CXhSG0B8C*giK!2)pIu=fK%QOA@!N3uv>DcGmPAS<{Z-at{dNLxwD4nCj!=2q{UL zga!=7je9G^b|Y~2GrF@6lQ}|m8#}S|_>4sQRQh?XQIF|i=Poh4mJiHf-lSjX5h`;- zVEhdh0?-CPngSvcBC4e91;zZY8fHt^lt5B?B8~XjiA*s>qIjr#zxcZ7WJ5K}M!_AhX>Xguaj#`p5yT1nPBF)O7liMiN`OWZ`+-uQxA!e*wvMpbFtp2 z1mdaDj@75y*_smn-inqA9~DcZ!q7>mH9hQxfi?AXv}%Ri!aObszR-xGv8iO#pdS~R z(bynrW9kkZ>YU9XkLF*yODB-EW{28odgmxNaDBr=GVw{BpjGGz#7H;7Ar4Ec`LxLP zPKqRVrDwzm_&re%$u0U!)D%CFrCq6zyC1L;p;6%^&GtxLUD(LoMs!XAg2B! zCN(lqJnuhN-|s78Q)+1Fs70N*US(dHEZIfAsWU!X2)OW>U9P-pl&)_YNJe-2x(@FY zYLq{bs^n==fpvxPKw>&^T~zhYi}X5k7}AtRXlk)&1I|pPLP||X70J|i7Y0fPDFht@ z3uxNj{xQL2jw9H9!5zxFI+N&M&^BKA?e9%OcX!H&ogTppmEIK2X(smDjip8v^ZP!q z3wpiSHyu!%nEA~6*D1Gu3JE35iQLgA7~+%^I#>r({a+%58dZoMhc}bys<`iKw>}G4 z-`jkr>Jr&Qggwv`_1TN!UWe;VEoqDbmr@|lP0c^|BMHMT54B)jIBr#~R(Cf8R!W<2 zJ@%az??Q?Ye#R-Xp3B*&Bk8KfS>gyXWY-{WV#T%5_?u_JxFG5`y+y=49cfm!&-GLL z4%iM5uD%N>-*SjV&+{@L!T}SlRH44kjBII2$3U`c!Vn_&`Jz)T!*XKRarR@z!aKAQ z+rS6qTC82pAP*;3x;DE&5#MP?;7S}d?^jt*&#s2ZL#lN4xk}x+nnLz>1}V3gwdCaO zj_&*n-OY`3>v*c^SKPeGRe1oT2j?(KtvA@}Lr!`S?}2<5>3?8o=5g=k6}#|JQYPj9 zhMk7_wh@@a)$g*#Ef-8O4v*-UWj7e#SP*;=UYufhvw6AG=m#7fyZVI;S`V1sV|g{& z1zsKNmCaN5Zsh9u|H%TflLM~$>)kTb!4BR^&qTm-!$=u`V&@URH8fboh)H(LHao1v z_Ve4&W}(HAD#&>^R^#2&oT=%`f^?o--Hnc7X%~0B^ zIgrpMu(sPakm8HeO8O96R&F7bUBgo%T*Ivr!=Ks>GIPm0(#U3qFE5?l`x1RskDfsVF(O zdFb-1>UrO(r))1!++O*cVP=v1Se=x?WqE@QG3G?)Gh#gycEZIBKkATeKl*ueOmih0 z)fTvhDx~8*%n95H0oZIUTkspQqEn($cW{ zSMyDWtaocBM)}WOrT5PPu$zkWURu(TpB;F;K6%>;F63v_`&N$DOdB!9A}Or)Nr6~^ zyFNaMC2e=nk83Vl3U+s0Jn`YauI^m#cZcI3uQhca2PfW)i=IwtyEYstPi$n{0BIF; zjg;50AZx`LR`F7_rrEvXqEwyu0UJn7mt{TWU$Z`z0ESyB`U1oQRxv1HB4zM$J)If9 zyzEgZB)mFN?6pCX_s?Uk<9^N#N@>#DaFMN|*LVR{wV>Q)9z%3zM7_+Bj}&hN96fWp zL7V+Wr$gkml-t>whd$=t#zWRav=z}%qLYGt7m0Nr5-@*pkV;6H7_c=>QNUmMih11E z996IfRT16y^T@r;!G;hRK}M~o0&S`R=qzOkGIRIzpv7svLNJkI=x+<*y2|UTb&42D zSvt*d98g~Z^RYb@L}lDRct+3jyxH~TU$CaDYnNrkepdH~hn|PXK&Mz_3+;b9_2Fg0 z$6FmQiU)z2D5G!1U!p4vwOkf{0->kp&w0o%bpX_`Hi&F0D8Sta9V<$;+^eqimq zotCuv?>2C!nOC&i(d*uWi>6uG-PAT~N$a2EUc6o-k6alm4;|=J{)7q#YXX&9y3p1n z*-IyBO->2pf$BZ_!xz+^&yg1pEFg6Pj-AKNWhe_7ThW!_mFj-&o$ee zB`v6>vFSPb7HbQ2{Q`OrBdx~Kio-($WtkM#Ios4BF=(qd<>v8_9z%w({~9`j-t76m zYa@NyQOG%`mg~nDt*C>q-lq?bm#GP zHec#6u)FKbNC%*QEmC`XGd>1^-P}}7)kY?Fq?QU0tKj6*x1J8+2}lfedm)SG_Ps=` zcXu75)`#9pWmSo1=iH|Y0)WHcXtkHINd{m|r(fvMA+6Ul?{Cl5_Z{wbNEStc z^@5rktOh22HSY@b3Yon#PVtoqVJSTu7)8wsSnxeLKUqg%WR1Fu-{_x}g!lKWnDa)B z8yOTF`vxHlNsyGH-!k_7GWGzJi{ZEm_m0#u)A#2@(c@^1}4%bW=RLZy}_fI23Tju{?yULT$Y)q%v^ zfAHp+1k*Ahov+`TC*M?~;K_)-r(kg(dQZkl8>RCzpzlJM_WliOoooDp&>?Fi5!F%= zliZX?sK$cwhP(O4=u_@-fsx58%%orq67i_-lG0J5;s9tDNp`<~Dc-u?t>cARVp5ojn^hS#O_S0nko3*YxW9utD8tdY9Q zKwP)seX)&WeBI_H?XQhE3VeH&0ow9(B*Xa@J!;;p$X^#};M+t3_N3c#a~e(?nP=Bb zRv|YJT`1v0Kj+eG0aoh^)lKN`5m)FK$vhCGC{Ns3$#KX`mZlW`6Ihj}DPoP?7w$@!T8g76U~QP!M2U!W+$w^5fYrs&!e z0yy!{2K6UhVf84sLDKLkqPK}IA&3xtMi6ag^yqi;-S2zf_pWvS-hcO5XFY5Gc+Nh1@3YT7`|RhO zeFAdxqhdAQ&^av++!eUeR+3GVQ|PpC$~9A-I*rP27zqBr zcJ&n%kg1eHI7wl>U;dzPr9v79J3Nk@uzyoB6Mf`@4EI=AB`%aAAx`NOim1o>Pr$r( zR)Nl57hoF#K=~)TMA<#jEV35h|0nfu4gQYQ_pz;0EoZ$?QiaPf75KbGVx`ej$^9? zK#w5$Li6Q{w3m+f#1`$orGlRLGj28F??`a2lV99Zd)ay*Y|DG{wI7D-wvENpj?L6| z!#=#TAU3JdapY>jE?JxsmW59dLAjRzcdrxI`FdF8FSA{DJ6G=>Wm|2iFOf6sAc6fx zM9@s7VZTPDVp*Q9xhkqvgn z3W{Xl^KCcRjEvZ1$9VuiyGm<}8AAEDSOrdh*7)IQ1Ulb;5#3!!V*hOSoPF}=_R0NP zHDWMEHR=J#Wkd9Bqk1qa_4}bU+@4YN&W2-aE&fjR^kBw%c3Va%5()knXvVsnN0Ozp zcQ|lAJ+5KtBE!!zR)R#T)vSrY;j~Qg*LyXH773OcGnSQi@Y&mO2mjfxCgDb9=fZbf zo6rel_2{_r!RozPD<77czivcEm`qeh#>*W^%={xR)X1nOx_*#9s{Atnu`6gn#T9mTBz^^>dQqX{P)0V=i9>WjdBOBW^sNdPPq=sYLzfs+1Gg~@rHd1X<}+7JVHE4MM1~05^wRE z+RYFN6lrkXxXO~+?c=W$%a*~Pin1C9-?lvfnzqg9t)~OReu7!HkiZi|8rAi!=3&(w zwR4Sw*2nL5M9e+ZpsL-Dcc%@eT8JL)gUK3461GI?YLGZ?zG;eBE%G46Rta+5Tg9*9 zzNtbznjR9&^~2gg^C7skC(7DUC1^{o8jUX?D%P;k)YJ><7( zFVLw>9e5@Dn11oscQT0wmCqi(o0sFQ7o!OFT35h@*u4o3=z^8~9#!@zs!7cvf(Qce zzXF3ilH?7t;X)l;WfEIzj|gros^h+wS@$ zSMP74ZC+=$FCk4}Ty-f6uq@pFS10lF_mVkjYdvKNlh&fI|l zRr_NBBA}gWscZEaHE$s03vOg!t_fQFg@=%)kBIi;3HY;(5C^H4GPy#tP>OvD@*Xcf zux=h8yBp2*5LFR43bcain9p_=mxTA~9gh^3DDFsbDr-mAep#I|b$y03vAWo%fPZVu z5)L>V(KLac#`4eG8`~AO4w*F#SPFn_-}Edz(4fQY!QL-~t-DcxeLwG*0=IrL4!0Wve}Mgt&UGBVFh^~^C7)83qM%6F z%!KqwCcJDrp9G`gZJ~(0mIXuuVFCY^%UlB-O%18`p7iP|SwaF)IvggBAveT2LVDoP zcWYo6<~0DECDQe^XIKpO40w;7L23W|C6{(JZ|Y?%70ASs>K(Mw8YQHfF;I;2&xMN? zRl`RPAkHUWtRyO)hgSi>Ct*s(ovZwZqCYFRoryG;R9Rkqs_QI;PKN7slbjlG_G3nO zzbVcFL_v|xJk?P1>cPN7@AtxEJC)@A7CGzckhWSlWNo)s##GnAxg~Gd0J1c+^-c(M zq4$7^+GJ0|>vdXU79Z1{#ceeBg}C!-BaejqmjKE8RkH=-jD?QNv-*C!a3}QPE;p#p z!@o>EQLbNV!^@IxD~tl%WnL&tiZk_p2VV=?^0L`D{B#);YBZEHW|X#8i`nhHd>?)2 zZ=4gW(_qB_jwySbMyQ2exhvKz7`lkZD#ziR0)#-}beB|y#rFIPysHJfN8ZAT>M*xI zjouyIwSv|Ar*&@- zhu3W=XliG_-DN4toaIOn(l@Uv|Hg~|AyG8npHb2Yw1dt<9EhG~3Q8lmC&<}6__^d~ z_V9cA2OkR2sRS5L1=4Bto|DIv6%D|z&WTePjk|3g`We7j0RK9-Hd^OX6j)LcsmOX-Yy(spwm%z8d z4^+S{R`WEk=2SBCNe9q89LA)oj>?X6Nn%0?G0-E-8j;fX%FUP|C2|PoA+AT5vdcb{`ef(GOmJuDOS>iNWMWjJ1Zh&npt!;FvGXvuf26gB+Ul9d zililBGQ|Yg;xz>7>S@#R?SXwvkHVW}eWu%$_ThErI)n0tQB>b6T&Z@9Ka=p-2(FL& zpoB;?kimo0Gx6GtsJD=lucvrExjt%DDS4}Bdl6QnsgDPCo^@*9vgZe(hT;X)x}MDE zZpj?kJ9}v8^4RwihtFAu+0wrnRAbytJ}Yx!X!5DJD=<_t*C=U9w%P_(GKFD{HtzCk zdKIm0R-JmUFR67kb*N6%)(V132mFUft%YIFPtIwCufF)iE>ZQ*aCIbWSwmYyh+GU6y*_Bp?0s42vmN|RVk~kso7j@(>`yKpn9s`JhVie9;rE)uTfKvgLuMAt( zR`dgRd<^EaPlPX9Js4YV&gPi*RXperg~Q6UK|?ZE)qXYZkKekLZA++fJ!$K(bL`EN zmea%Qk-%QJSNI%)b?g_vrTGuxg(WCEyJNGdeFrLlr(I(cR%R|0F>e$tfb_!2w1|t; zvo9GPF{eZ5BJ3*ZaPC4&cr!3T=u!D0>tJThQ&nrmXo}Ohoeca!jhDq`Z`wVk)WTcS z|DBb?y58DF{BhDz7mymZ9h)&;s>h0tUB# zEms=y@u9HwLrJ8DyvH8;UULk$xoO3BuCyIMd5;`G%BEp0?=oDe z&cLgp#d{VCni~2wl61l*@*6-)$fN_X@F8RAV9+IHP+B{pH=QKq)iGltC%KqO>BmoI zK8Q{FXgQq*Wlk{_;easRb>YTJX^E?Wwcc}G&yJ4W9)_i~PTQ0G-WjY}HglIZUe$zD zU-_kE4zqn_9moEZuhZskkH)YQ#Lp#4sBB zQUev=Ku>muo?=FS8s*-~KG$gRjq5twP@fas(9t_l&`$9smi@3OSh#3I$R{+|I*xw& zq|N_J{&bJ@q}j^lEBu5`Ml|kf0cDANzr)1ej_cJCB_c_GV_a*Nu7L;FSUIJJpcvwn3{ zfuqVH;*<35S(X=i;OH3=h}9A#9vk5=Z7*b9k09iHyyhI_plJ(eR=&Goz(!MMe45CoGr%u4gC^jDYC-5*tk5tkCh~Cc*)Z;zWAl6 zRhnl)D7nydaB}Q#GaxOXw4wn;Dm-Bg0xVy966?3Y<>z~Q=FyU3iP^tp8wP&}w{Vl|9dN@v}Ci6hSZbB86OsDo~GUPTnc-!Y-O!wEx z>ghr|#ObYBz|x4A%%T87f4e^6#_#O3y)v0u4r;{1}6nG+=`x_%QL?1YBqb z%3qouHe100#VtM5{&|Z7>HDMsx>SWNnAx3~yvT7TB z*pN(WvMxAHIdut7GHeQ68@iKV*P@h~W*{iUl zr^>6CTpn8LvBaA!=d0{~PAbq=9bgx6E*DExa={zVdz9Bdr+6Mwj0ykP)bAU9o;m&F z4Xn&R&0ixgdL!g&KfE4&!d{K0qf$xxUjH-HC?K>q;D#W;>7jVX_|H{VWjmil4YPYI zPcIk?pA;;)E+TE7xVjhDCi35Urpe5OH-26p>K^@|SaQF@qH$11AMRsj`G+)x+8=C~ z(lXgq$iRBdANZsmDEe+bZ%pSsB?ECIfKF9+l4N%ffK{IJ_Xbs~pj*WsX*(*@gs3ZV zF!8SaooU&^i;*Ic4ob{4Trr9>-L_qSEQ8rnKnayuo5cve6hu&ipceUp3d$AJl#pcu zBa8Kk&9cfQ-pQiotMBN}F_L_nln!#R`_i02OoxBb2#GY5TYLFXeaZi5mUTK7w;N=k zSpKAZinsZxPyRt`>J4-t*r4VIdC+!TD(`u8;DTYkdAsMOzSFzbAqjRrEu|+~MwD15 z7PU~bBC&crQ&pN+Vc+Ce!SEMoO5-eI*T|Ze;EE@G&N}R)QQYo~aF{?c7kS?@!^`8P zHR+RQzbbKQWt&`%=VJb&6CNHtB-C>19PNeQLq73BUe7m3G-?_K8BZf?=$wisu(85T zOd~6j$LP|XLCeNiMxz!xct1Ir4>Z^)M$W5>91I&lr6x>I`inj)RPae?>c#m%Z_yPA#v2Dgr5GK%Gcw|`kHZBiy1XT((<{}76(?z0ad^>p%#8tv# z^hIi)gA9ntkpbEdPpBwap5|3YlAb_ozN*LG;(@w|8#zXtq#rhYR<=9Oi{ukYtVQUB z;0Ok<(U7t=t6iB!D{S9iWs%d7zdr1R*ZiaSOjonSoFq>E0{D}7{H2)gz1^7%AhS!% zL_{jYSoJ3y>0|`}hlmA*CjVYw>*J-905Gi;n64>sgxDhH*>I0EvH;=_l9wRHR3W#? zz_+F&>5OZ<%w!zC2IWH6L`rVF2e8TQr=BI29KP^r{+*>ybe#{B+T=XqBD90(@cz_) zW?s%@FZVGZ32vlEFWyeK+YqTXPY-3|O6{3%T!pd6NYLkO^zoV49 z6#3(Ag6%VZH>kynfByLk6eDX>tbPvb^{!gU-rWXjSO$Y)hxt*?XZA-{diSs2^cXnq zf0MIB)#Y2ILuUUQV}BKkYe-^FMeIn$Z!m|>DkF}*Yn-}+#4s5H*Vi-VLoF)FQ=p1F zqFk&9Locm(E&=yL<>NaJQG?t)maW&9xe^C{^lf?H0XL=s&m0&Emjf$mOwvM~AnS5S zy^|XkR4B`g1aNqfp1+vUrD!{oMdNxW&SZh9l7B8gn32?Fvois;d$L-*pb}~#_mqDnX(-7=7sdanVLUaxTN0_5$#9 zSc9JbjHh46(giY|FUoew&K;$bbl0 zuFcYXJ&mCSfo%uU`p%d72h1Xn^!+ivqJERqU7>YP_XUv(M>(??0jO=O&18$NNcSy)QRaDAyhb z@5VSHd#*RD@FXTaG; zD5^1mq@MkQp^=UL78~N89rhO0D}#A)QnD(lsg~F#9?&?WgISefU2*cb3_u8Q;*^<6 zIMi{<;Y1UZL`NSk_4L!7#cxHm+#reai(#H)>CW#50ZvzD-!`{2S>zDK>Vs7?cU7pr z+bB1q5sDS_q2c&o)>&gFOhEMpjlD%Ck9zD989TuVXX;p!C(Pv5S+^~sz?^jX#Wg*c zT)?F(I=;HB=SL1*Cp@`m?7$jYtj{mqrmO7|6(yl>Wix=Q43|e^KJ6_?YVhuau>{mQmzxGmI z*o@|Ojvl(h|3fd_Ow)p$_|U?1d4HQ{=PuX#u1?!&3gVz7EbkCj_;exThd2co_u@Pl zx?sZ(?yvhG3kRoKOhXg5u8jl6`l_FGOy4Zj74US33X@Cvpd7}U zKQ_n5zU(|ykZNdyX*+YSVY!e?#oGwg#!y+S{-fhw^#i3N9 zwKLZ3ZZCRWs%YPT(;ZE$#=B(b?Z>9{KBm5HDn*@)nMm|Pz2G{Z-6Mq|5&Au7tCv76 zKECBBQI^HqdpWyZ^k0^h`;Jqc!`O!^qnS4<>O^-$PCTzzvj6pw?m!t|`FA(oJhn@so}Q1bR^m z6+=B}g2?>xN?;_F4w0DLsMWjE>nSy2GSY2BH;oDKu^trAN z`|1oUx`)3H8J2qX!twU4tC#ylHz)B*kyzeKWO^`1=8{DC@DM8TKHvWKInHuX&Ht<| z;rvlFeW9_+hT^II2aEpfUn?xGk6-`f`zd#no+ZUC*x!PlmkfJ%^|O7FN!+Xlm-NoZ zz3i(>ZRAyU1mHlu8)*Xmo$xYF6N+}bDOH;r+DU5Q`)-(aB9)_Y?OtPa>&zNIrB(Tp zz|g1HWb9G~Dp;PRRX&*!GtT`h?wJcT1Os zo|2Mi>H5P*@U+6pW&%}}lc(X&^>omO&_`*0FWNN+#f1(` z`zyGgOS7_I!e4!uorMQrgWKGs5-o2f+J(kAlwGR|vvZFRZd-C)E zSOHK(SeUj@BRu80XO4vB)nMLqhQRQgM_+u1wXkO4DNeEPr)YReyO^njqUtnyjUU0(OakR@J)EhvJMaGWJLMFUNHKhZR4eC7n}vH?+&?g#gxY9TuO%QFMJg6gAKJC%4QRA0w5d_Stme2Jz_QNe4q!&`&Pd&fH zQ{c#PZ2Ilm2BGmMR>|Wk*Fw5b-4`hvG9YY&&rs8Gmwq=%D-S+qrto;fOpOoJ7g_Hy ztba(jaN~(Y|5?Pmax*49xZFhShMBC8z-4e_TMEupZ>w^9Q#g~o+d*g)>|q?|LAx2dFD zJo#jh;gAa@3x+!s_-y*`Fp6R3(CP&t((vDtQT_M!wsUK+ZhUs%8-HMrfBCElAE0}# ziB(2)+p~}vRDE9KirVdfdeMHYdp+vR&O%#sQ#Zw3qugFNPO~5)4-=S!qsOi=^#4rE zyi^|gM1)}Mh1D6x&%B@FN!KttS>Levu-OiC@%7vYL}Ft?Yk+RnjXTF90P$VNx1@`( zHwKo^jeA18ckK-9SQnVrQ*jSK1iy~6lfXv7u!uvEhN`JB-`g<*RWRnTr4iAVN4{b! zVVWG%8x~PmHG-nP4x{5iCI*@2Q{(w}jCMsWf&P^jJY7rqOhvZYvf305dBc@uce)%a zkM@q`FI&XE_{_fz%jyGx;^iVz} z_9Q~oeS;BU@WD(CCN+#YWv7{u2GQK)tC$gV^Lt}oFpTZ#Rf}uSxhjZPnVBLx?^o-u zgISM5+r+brfxZNg)d#fI&qa=l=ReRG^rA9|&(-bit6&!Ph3ckE{0=dCZT-;j&C*9WG-6_Y{ba_Wip=)@tI7G1j#g z|GKdd^#|DvQNrP8SouIRBNZGy-}r*b0}llW3UI;KJy-mI`9sXV3Zv|wbv3XF_f0{q z>0hL)sXgqZYf973S6|{y>QskNJXq72!0O7*z*k_yjD@jl6T6YQ|DiX#yO|fye>417cl{d>Cj?kPH!n)8pqmA6t^XGCU(zN7W=D|R z1|5bOi-HbEzl#3Vd;bLV?*FSNEB@}u|A_oAlK46Q$iCqbt}6oqiK{_hBGq(#J(sZ2bZ`)8%UVewu(jg-4X$8Yog+3H{L|2O}CEBs52 YJ-f-&1sN(A0{rtp!$7@8^5xWRy1PL-1%yQ?AaN$X@9X#N zZ=e11{5jX=LV@)>>zQ+oIma0HxW~Q1loh2Pq7b3Lz`#6wE+e4=1M`3l1_m}92?4w^ z+4F%C1_q75T3lTDxwtrmva^GQwXHb}j7(T!GNPL5bNt?AP-vJ?;%F^AV@j4k-E zdq&hWuwuxRm}&=oxvy=WNj_p~WXxUHfzh_r`t%8DZvS;?cohqtZJ|#*+?ZRNbL*>- z=AVO^0v@-Vsa}={Fz^C($|+%tkXYJ297zPv>UA7!?GI!DFp&2TnBI46Z=$qNQHjCU zjQ?CY{Hj3k@G$Ky&8*1&T?vDe*Jl!#SfsxGB|SyF%{-V@B9ffP7%*Z2XZxcAII*&$nt9UXiTK+%8>W9!o(KY);-3BX`~Th%8o}l9eW#F2B+5wbK>6j zJ%ft}-x+@sSzCKP7Pl}~;a%Hc{8fEMzbW7R!4!K^l;le@L%(vic%hxUjOaT%?bP*g z;WMM0{4fYLt!eOS-BW4-i4J9f2^tx6)R0oNOR8+9sO00XTuw$qGCm^ovxp;vMvwpq z%i>cwjtsd3hLKPxkuh=k;4&|bDdc|({as^r}t*bCMQNnMgJ`Hl$z1(!(r!ZuIx|a1aZ77x$`h5i;v>` zKRiKD9ULGf#kc4;miwg=bdZ{iNBY<%qWjc&LtL8M(W}*u;~_yZeJJ60@R#W=Qoe~g ztrS9b>=?T(a|DTjZ9;*BbaL*6I!+80ws}FLn2Gr>SR6NV0>%asHS)yKLCUKSd|**S zjh`dTQ;;4nUhV%{W7UolF?xU{121UAzD{U|MPG*1Zu#gjjKA1=;8Lag@`MgqU&t-1 zslogt{eouos^`I z9|sR>^^VvO$>(%+mqrIJ&-5ZG=J?InGf#yrse1Akae_ZaFCryxQo47V_|`i-`eNedI1Z3zUT1z`F;>tLGBPgsgiwkrK)F_# zc&yckE%hoQ;pJd}*F_$?2DgTg0)*!EwZp zDLgW75{?#+#>@zeOzTnAXfJX8SyL1ntN-a%215I1IDXZ(%egBU5`PHH#@DCWw>Awe z8V?+EGpX^m27MYfpTRtO591NcKZbDC4j0{zzzd_fihM1~hJ-jy0Xq~>orO^K{+qTD znHF03GX|}PGeHVQ#B<0$UO!l6D@0USMQea54{&#UuoPsIhu;kIV3m|W4D-#KF-(Nq z=v|87P(G6&Udr#GWKScp&bS}JdHT#XBnSu|oS(F$JY?RV+@G0t7wGlYe=t)htR z0sW@(^7z&58EoWFNaVUy*s$tgn7#~DG9FOOAl>cTXyMPq_;!WyKCoQFbM!@6XXiy6 z{er*BbgXb;(2On;V%XUPg}r79lp3Q+!O=r{_Qq3;(HJi$MOkr9fmezbVI7rF_CQ>+A4QT)(z`wx!T=?T(|bapg{^qEv{@4TOr z_nnxLRy~~0&XB>2MeX0(^xcf#R6J2ViOtB($jOM%Yar3Ik6vQ@tnVDCiD{By~RDm|EGj~6u4SrXv(x8$wf%4H;HaQ`SAwD50 z;U?kpfaHKc;wa-!Hnw2l&X6z0PmB*tGAz%EG1Vj0U#m^1%c`Rn))b)?GL|e9^%Re2 zl&J~jEOd9JNDWa~(7E8AvYpbMPV$yH8EklNB*h5umb)yys)fGHdMQ|oU8_?|{4#q= z`&naJ@QkR}%@x#K}BGE!~ z_=O9iOY0@xX*BX1(?%H3gHR{moSZeHR{On9EIxCB6KHJMxpV#YKEgJnA7+H$8te^f~C)DXzb))jj z;ukA=G&)sC5V~(PLiF?KbW#VtB3?<}GRMYUdB-2_2D?j}*W=w^Xh*j`4H}l?l@pf# z(#uxh$m&Y3u~CUh&mvy53(-v$7!>OFZF-d)QjWNoH?R>`&`Lk+Qop0hs#@V%abv*R zQr0ZF+}phJtKnkkC*NVjCBjA0McpOCukH)GlUKVHJK5h1H$*Uv$l0E*x>8WG(Ksrs zmQj>ZC@Q;+iGDIYzFu{qoQehMkTOPWSWGe}Dt^5GOZ=>2E4c*$9a0Gf;@Yz{n@(~G z0_jbOYo52fv?j7!3G{I@ai>(exdbmb^L|LSMEr<0!1ZPAp^+k)Bq_lZV6Nl;;hW*0 zr>&=X-F0yZ)x_q)zCeB$_MvO8>uXq$>P>!m_JY8$pANJpeKM+GG|SIAxF>RmkluoE z$ZS;borFxd+(PnMD(OhM2?iY^t%D-j>V+eZqk*H)=lup>!KVgoE@A$%+M@*%x5g(s^3*bqb9%;zIK^kn6IuDc(dJM zVyqc0H*%E$`y$lJgXNuoP(7s*JeiwLH{E)6*VoJMmx-6;7TqKK9JKzxPaAh$4X;$fXva3c_sY5L$bH)q0l_=em)Fjyy!a_r(oxk> zFSnk=g=dY+%=qn+VYML5#JjTaVn${5<6_d<;KkrV(jv=B!?g`^{ibt~y0zEd4sHf+ zvVYy}=k=YnLOYtAX5s>wCZ`_eN_EaD)$=$bdtROMd1y}gyb;;?4FxAg7v`6^GD-N!{?yB&wN z7HT7D{lpjQmx}8n*O^N0-QQnccJC#B`%2U#=ixt3JvUkV)cNT1%ieF_eA9kuOuy$| zPiwuI{83MSEHvggw!RvzV_~;P=_uo~ds*?kZCQ}Sx#2Q&GA>B^VT`?S53hzw$kcEV zaiLPl(MuVtU0yPu=Fj7`&^d>swnOU)yJEo{Z`Yt2zuURwfpfCB{#t%&6GHt=xwivv%JPEj>7nnj6#o zJ$9FKf4%udi}8p&L1fBD>pWzSyjCAwpT5zzNtawwsByM)&kM@?6S}jUOJ763rR0{S zAyVx%Gqtlad+YP0QS!S9AD;$|LF41srDRRU>KBrtOu$f843Fk)+j=1fn5V~dWN z`TV3`L8fNhOCz=H!^@(c;NvcvAdv@$tXRQ{zJyziu=4b@8azHu)>l3tg;Ct;`|gWP z-NAY5u8Le+tI{6xgH?;h))pgE!VZ)Q$c{4FE-)~7wDiv$c2O_Y|V~eFs1I{r+WEDvIB?xY`I& zX(=dEh&wo&Q*g6zv9M7Iqfks)ct@T*8j{rz_Emk^betE(eFE31cx2a5+Mi-WTz zD?1+_A1fOND+dQNxP#fn%ih)4liA*d`mcxl^Bf6t7gJ|zM^|eHdy4z#8k;z{xe8HH z-4FEV>#up5ds_c%Bzu>?mjxEcdVhtLorR6{&$Gc>1@B+wSGM*vx7CucwllYP0b>aB zvhxW3zW={n`PYd5_D=18y_1uV>)+q`Z&&_)ry%S70{(45e_8AItH4~sD1xkiOfQTg zSA)$8c7w!PLP-t$gxqfp`057#9{>Fl{4SyQ5KjCj42&qubBSkap0Ed*$V>9mcfDRtZ?r>s9WPCYP4O4zpKlpl}mp-A0KL#%)Ds&>ezGX z?sB=Y{*~qTp`}#5osp%~QLmPUpINVxj=Zu@hcZSp4!IO~Uq67wg!wPu?|67W`0BN1>x{s}RGYE&RxIN1?F z5He+Bz7!a{TiIA^?LFo|3V^x){s0S$L=eM72rdQ|INe{=yL`<5&qcQ17xDl1Vhq#& zBO^RrR@^$-8r+-Fb#SX`luKseGV8whshuDVW_m0Wn(aBX z-AGNBXcknN4^G_P{IWaJt+!WL@%vRajDfKydH)21H_AGHo*?r32{YgcJT^0tcu%y9 zs%sQ8`Oo$%+P?YuUY+G2 zUOSsJjoDky_P^zG+nbuyU<s0&q z^~ikB#wnQmFSqddkD5%JprktE8fcp84rFxqnm=xtYR?Cf6v_k*u|uML&t@4k{>(A{F3C zDvWkA@V{~6@Z?1Zu+)P?t4qu8PDH2r<23=I99Z`c=E>btW6^I+AmXtlFk7RJ5foDr zI!_qByZO0Nu-x)$@%qAecP!^@GhPYdvUmN2H);5xaWw3p_uTfb(U6S)UimpUGp{MWNHmrTAzVOL7 zXqa^-L^1-8Pth`hyM>U`LbSaLWmK}+Y9TFR6BF(qPlADa_xk>TPp<-d=HWC45s=GR zyG<9XQya`3Z31VPbJ-q3_CDW;5&5>$4c8JNEsNv0IHN)YK|%b-v0|Nnd!tHyrYs)S z6B!68>rmUb;u1*9`OZ<>-7@qbIq(6ZtbWsa3ZGznbiiU=l521$#hhv#rClPN2lc^fV89cf_ud()A-AP(K*_s0ZJ?Mc4+{W^SB!S8+` zGnub|dg8GkmzXGNGh6OPENIfu6GbeI76em?6FN0xqD094$Gt=;obGqig1ks|K35X; zYAuBPb*V|4#c&$@rGVGLTxAfWda*=W{=9*unb~erTq2A90*GBxG?+zYA1#VrF#faJ zKs8{h4X1}8`)c+}4RIIm2Sj@jb?oP>-bOYA9%W{wb6Ja_;e_8B@^ktBtlu3Xu>T&_Qkxc3|SEJzhhn66r9kS^#DX$ps<|Lq72 z^1pGB?jUoi-=@;ZKt}!2qI}p;I^RRLd)mg&J!dp|dgZ#`d-9oksV=mvC}(=Mk)}?*R~){Qk9x zdZE3k_C&GFd@yPHt#$}HX`%hsMyw6WfS4wtf6XqbYmn_U=jpKC4564Cp~K~tW@~Ns4rMv zmD(S#emUzvW$hx;*J)PXkN9e#T%y6;5@i6guYDdx2IBpY0k*@COj)LfOC>Y`b@}gD zCO{f?r26ExuW`d05Kphf--3Pu+&aZiSbMGGZO}4p1fmk?F+Qp{4>$W6uO$3jJOn-J z(G%mNouO3mSv{91;nx=2uM6HnAFFf*KMa#*^c1HME~gX_SR@*1Zab`748_<{tl6I_ zBlbP%r*}&Kl4k`*$)8jb5&B*eD|!v)v~EeDjpe&IvpS1mA|Zuk`1k@M!tZ7E%ef#U6URLF(yuguzqvjg^)B!OS+Z+i za1P4FOTQRHE~2TcHzU1N9B{^ z4kDAhf9rg;B}Nn>XYd676z^a~~uQZibKne)8@VUL*Nz4Ha|bxokJ9F9DA=s}EZNP6@Y)Iq@YK(@hY*xM4C@MpGqGe9C1C-cZ6M5*Bp})IuxrCFm}UXDA#~fYNnwWfnyi& z1xWXkIkS5(#|m^OQESNIRdy*xB}$5AmRFC)GKTO41w%N&TqXX_jFpLHWr{6MI(LdW z37@l>ymevtBdrk`8zaaeW{Bs)3{oPKCJP%a%rY!Lkq+|B3>!;@%{PLL-lnOIcgvl!8oMF z^+J6~ZecGIe?iB4Rzwy95=Q6v)opeU@vf58rw)l6LacyGR_rKmUEZt017_K!6Pn)>rx1vPEZ%mM2#nO{R97S z=G#ZJ$!lFQ@cORfl0Bvadge$e6|Gm{U7?Bnn*jRcCmZ_B`}9P4fOFYwTDJ4%KXZ`0 z=#%Yy07buPBrxkH-Cmz64J5N>=c!K?DAk7%SEj5$U0?O~As{tM|iZv#-W#w1DFz)rLD(4RuqX! zP9^;e#*P_93kd~6L6Hc<+Gkj07M5l4!LZ2dia?2aW6$gWAeq7#m0gVI7uo9V#}Ipi zKJwjY5EpZWCo>535U0>~2xGsC`q<-8R%L%%jxAo>-@&PBT#{2x`X_VHG(z+S& zg;e`eNY5dQ*)=J*aR(f>E^7L|Z-abu-gKg-LF(AL_F!jlGqi`;b&$E9W{M6sbr6&7 z?sC+BH$UB;V|e3S38Yfaf197=WYMEnU7)X|mpe+$Gqh<~?Af}t`X%ULMPWHW=#RrP zjn8-iA5cJ_?ZZ)gLE^ar)%(_R%&G|ux7^H;g+HAje0%aj(#cMTQMJtjcb+nKEfTzp^Ppys)k1<#e%RjL?siR?n$bGLBE4>|TS$-VT#%j?d0KycDSiSZ|p! zj<{ab`KZ-83cpn6i63E36EV@FhEb}(Y)Zyt)jC{hYik{=Hf-~A1=(o-R;t0XTq?Cp z^}+gc45xeO-2M>OshvD52=go4LqfV#{8L?lYkT;}7h{?RUT-}m1{}}#XX){5dUk5p zj<#yGt1W7bzCbt!K^ls^hp(r6>h@+x1b9sMRbtqiw%)SWJwAcNTi_%oP}f-+`N&9q zQ(JcIe8^=!@aP_zC9)bCD7^{!uvlwTa3nH%8kXlq(kAv6Wn0;)G=kQHbnv5;OYUg) zA+E1qW1TY=k*gwmF^z-*SG3oCg70c-A}9w=rS{p`92-$~PyAdE{!1f4TqmFeVhp_Z zX!X&Epc&<+v*zP2HdPr0pHE8Y-V8n9Rw{_bMU_Nz@IfLHNVz`l#JFnS%~wcY7(otA zQBHxeU(`vVIYzm!(Q>KUcQTLX>6+XR!x;^XjBfqbPff!gF<@UXu8l&G2zYKwt4BBr z2BD{oZu@824}7wbezrfr7&{#maj+P+%sVDzDB3ndN+A>S9+M{bHOtj*^AmD<6>yq$ zU#t#&b!EKljNF+%A@8r9HL%gS036HU8J4!s9HTbrA^ zA?i@78mkQTb}m-^fQ1Amf=8LN56Jkv!mFGW#{+Q+tUBZ3Yu4M5IrV}uImo%0q#w%R zvE6ihJ*L$4nM+!`5Z?r(*)#?MXA02~I=OJEgKmHBTkT35OU$vQ&n`ty8$D8=-j}Gi zqm>#&%HsCnJlalpJR28JM2crF@OYq(V;`oowo0C^x@>@uq$ zLd43#dDgR=%06OUAm@Im*4O2|IIlleIh^i=mWIr%0IfO-ltN5}{Ut z{)sN6C@AAD|MpLRJ6-=>iLqbGWCB$#H9wz{PJA0)dYM!&O8re9R8KKoFC}gaX;zSC%V-f?O3Hstyb8=NCRt@I^j%rzzxVi7ia?q2gBfE_z zx4u|PgOn&iG0fGn(^WwlBd%*l?eG`)3Xz}S;EVK|>Uxe$oJ?pst{t%#VP0nYT@@Yj zrEvkMBt>>qDq`h+maSJidhFip+-qFE%x^G2o zetvbYw)J4X8T7<;RK&g*7eL@FtL>`LLT$kCN#6JMDr z>%wTvih#8vk380P&F@K_H^2O%pMX=^)64i$hkMqHiSdJ0K!2m zZH`wau}EeX9)x}TSTh86BL4=iiqG3b3J32$hE3{oS3b-mR72DM)?v{Vij|+eNW3AJ z<8`|2{m7{ybgDB8+(xhG+j765(G{>{YrlJf?0X#gV>D}`DREk zj!;^BiN26S2!jdUQEf~vA)$CX0gWT5YGiOBCP9R!E*hoA;}^*{R+)45bMiqVzq-}d ztm2i)gvnq#Gp(|o8~Id(aSNx-b!dGaj^n_zkscNfG$tOsz_jd;2!Hvi&A1bx4CKG7 zA;hc~6g*^AElQi>r4Wmao+zT^?$r*>%JFAZ5wDx?7aEfu2t~wX(Zyqv;@uBHL?!sa zs{CHGVfWdQC!!$1y?eBEB8v;IlRW&*hdA2B>lwLD6=Z?7P`Jgh8> zdV$s*Zn|s4oG3<~!0D24CR8GFUp>GIeh45E*PISt7MoPhm!_+a{3`|md~RuBU{6_5 zmA*%cymn0O$72(W9K8f&h4O}oOlO#Q&ya}h_thph4!9wq!5_Pj{DG-q9mUXEDnb2` zRoWj09yVE~ym(e7rw}2bf;}&i=Scx;H3+PCO+HU1GG-=4+u3M&0WZ>qvuZeZsp(PK zA9?9DoV~d2DB_EPtz@|Tne$pkfOB<^mn5lx85$$sV<}?TT~}K-j}&Iqv+vS{Ah0kh za#sqC@eB4%Ht;U8=UlYA-mrGKey{{GbuKg}7(?Xx6Ws_M=LUgOx4oTCl|n5?YdRY7 z<-laW=htZ74pH4olecs{FcT%Bwg?}>NV&N2jGu6oIbE~@&N8f!u~*RRri9n;iv@K> z0WRtZH<>TCK+`vKFTkL3JJm(FpAJ8@`6!oM_jZRPky%$`1Bo-q?9w~C{IFC-i zCc1&ab4y?Mf+wi%<+SWifnNvJ3L;ORUc(Mv0xE*@F)s$zsQ{jWeI^?0pDEyVvgnCa>|KpJ?A>F&=bmC^O0D0Kc(_7PRuBxYZeJC86ZLwZz@&{vi zZ5xKc7up%aIFEQVFaY5+{Rm>sT;1ima%W)dWdIvyOGbDzBlfR<>#Eh zqv9`X(T`<$W_i(AK-aq&hokxIzPE=k5#JFoMzbLi=bQFMH?oBSdt+%Ey=977^jJ4i zw>#Y)#%2~*fnoxr8DfCu6eW^CklXXimYdGXUPla(3(7JEYm*i z5ds9`H=Nb+WEoQJ4VT^K(-Bl;36eQmzh&aJ%yPj@2CQi57a0d((=mg@_R>QV3G8g6 z&*SN=*L`XW2bs}Lqphnm+po=qPh(muXK;H>(XaEUV>-UjV%)p!e#Zs%38OAquI?lwJ{)4C?}OKUw0!^ykfI;dbQPk)lBzhaFlo=^Z|9Pa(Z83jmS)mmp-Zz z!$xD6ngz?s-R;k|4>~mt3F*Huo7q)9a2&r{wvOyP$@RU3CM@Rj-gV2M%+gQ=1U-?A zIG+f^@DdUjM@X<7q^CM%I5)Xvzh}|7oHu291-q45hf~;tx?DL_?qeQv7Ws}(_?c-_+L)|Ot3mxJXdmv&V4)MVeZ@$UAQ8!I$N2B`b%S~Z*< zg!)dJh7?`s`ZfM{evN>f+dZE(#2`#E)Pk3v3qWU;+Z+|YDZ%~dzJ>ZCDmO=(w<}k` z`>=NQ?9m(xyW|&2?#~8Q@Iy@|XIt#-D4pL2leUT!q1Vn2qhr&O?U-LPcs~56*8u~W z0L@An@jVvG3jRdUHKVQ66bf@=ZNzK<6>uj}-%)QAaRCPE5g^LDSg0vGb>W9nP1JH; z8t=rxXVG{(-g!hS2tcP|My}9$_Ox5MR;WRtVK~R>tx(y>m14#Vap3vWEnR|o*t%Rg zhD@K6nT>iJ1SLBCZ*<$`n;HZ&{rTY{--V0k{;`X-0hevOG;p3SKW=zFQ9-qiEGJQ${_Jc&Lh&YH zd+V`3SyL!hQ(heJo(2ZBqD6ihw@q-3^bG1eWSf@#?ZHR?{8(RqYxrUn-m}p7QV=kp z#UNmGfq?N3UtdTE8YF3sqE`m4jNOWcYGpdy*uN50>+Qepf*un3r;f`w$&Q6uo9DJr znH8VYC>M$~;*tP%Y{8_G7kAzM<3IgLCtI@kE8x!2wJ@mNZ{LFwVE3r)>KoI`$^0lR z{dx7e058`q;j7X7&c+s|1@&C)%TvbcuMEbl3u&sY$N#2xN(AMT+Ju|TVua!rAWCIO zLJN0T>ORrj?({%eNCdYc?eyCGT7=<;84LinnG|G^7@}RVFR+{ctVYSn%G})ij=2OL z6;HX^V%W6}bR1&37~1Q8GLo60rV#rrltt966M9*h|Ke0jA>mPAUC_~baQvr34FmTG zMClhW+7%Q)5_{4uuUPd0t3MFl6XO=*T! z55T)Gs+Aw!tGh|l7$J-1@XG-kYfm|A_pQO?0+a6W`H6S;k(+ex+t8BKyjWrFY>?_X)quc^osKA+F%?xoq3hZU>& z+zuSfSGz$!Sx?eoBRau%C({}&&pXCSmGZwY5c#OB{nBnE{k3YpwgtcUrxOT zjgnnz@n<~}YsR)}?JqLw5|bE=k`6@_HAh!I>UtE+aa=u1w%n3DJ-Ahi?s#EF(Vx3xx7m0RY|FkY)AO`oSA*JD);>SwNy9qhg zkAUps|26^bQvh^}HE6?KttFBBT^K79RlyBSmi(M-IQP$1yK`BTRjX=mQ|;qbC8I6U zt!IH+e{O#74Ke{J*`u^m0WSDEYB-}@d#RA?>jl61cu(RzZ(?4~`tx@>@X&Xi4h zVl5|~6tKsP_jwdoATAN7!@LrcO|cw=4ldIkwEON-nNDqZv1Y~hM^8$DlHyn4vmk`4 za@*9@)GGw|Cle3=dR)XxefOGH5vz2|NPeO%2DtqR_U4lTrmhfK9q{OC$0PQ2{l@5s zYUag+;SW%JK33 zxK-8Fd)w&_JhRbVI&VOj!6Byr)WL5d0CTD*{E(7~Yi>3#;`Z3{D2~%CDOKHsJeST= zEOsg5xlomlvg4q`QDod1%#)r$R#jECyZq|tBXpy8zk)h545uW3&*$XP@O-L5-VDiD z{oC!K5ter`tyYxo{5ZOg=?98l+JrG;oPcWRAH^kzg&R%+%F{(Y=hK>dBLYWLa-FLE zIyF{Un*CDIB+tcz(Z(lN;g6b+zdCBbcIKhYW`o+!yeXXYO_;z>={L%2`woaMyCwSb zHK$tTgx^12eJr%u>e#NH9FC)w-mMyB$piK2bN^v#dB70EoqwB-kWEG>TU)KR1&8g` z_s_jcJgFWWx?n(DR)Zx5)UTtAMh2hXKT>~eWfFGU{0*tY%y1W)H4LA?RdXu&M31+#PWVe^Ea$vQEz?L}qLvAbhd<;1sta!g}IE zSgT!4CA57Yk?FjaqxpuTDKZj`NYIlF`kAMh*@f2%I}aXGGvw)hOEKTS?`Rr%13$bLGt z2KxYEH~a3nae^83)sMpxa?aJ7@#xJ`>~|zeKgqaXK2pdQ&h(U$DgF%V3hYr9(4Mut z=PhMx*S%E8kO-96?$X1;6+gNSqr}Lg5%V0Zplvj4yueuQkHm+bn%qL*QFejZ(NX!oL-C(91VBSO@kklmaN zvUX+q*yjnl2~Lw_&-dOC-ogg{dd%8(?Ocre!prL~!ZAScW2SVe5;2eMd(cXrm?_hB zbLyTP@=PP}#{}I$xi&l|jVCjks#IhPIU{P<{ zi|(85&0-jaO`EWt7}^y>CRCh#-W|fPS)@XXMfZ3#-jetjEetfZ7)F_}2yKBh;DXK0 zK2I{dA-yOLNx*dY6mY?fq8om4KuUI2+ftdISPs1xetB&S#(MMuZF3^r*o@kQSllZRSh-S$5lg_Q;;g1CAX}lo118oD*)TdpecMC7~%hUr!&?|f*F_8VWv~{qZhvs zVfTsbunN!?)cU8AxH$%ZyroY7jVEJktJe*)@d*vase->j{G&*;3C`hW6DATp#G%A zEU<8qFFRoO@>B_7;9>@l-j^|mN4!zZ064gfi2%~Ga^5>5-hR=BuAU6@KTH(dgo_6I zd!Cd=*M=d*a)0BqT{Q%lFL@lGK{_x-wf5*hTkVl4|p(a)9su$62aYU&Npn)_Ch+6SYiG|SkdFcNSK8*jv5(I z@QQ+)UV~xv{cVNdBJcK<-H-t!!$FrJ&m4fE@Klq6u~Q(_p~+YWNDbKY-cllUWRU;Y z<|GEAI|#lG;DDs%GsqDm2Z=J?3*0=)#QtxKf|I}B&wJeu2|I@j`|tf!{_p!4C;U4t z1yq3gps$GK7HZZTOA)ys4eLE2`Zhq4uU(i%mRisz!$u@fM7TLJk_*Vbm$edcVw!qTV>=m*+LKm?Bizbv|^ z@juGqGJ>Q61M3z8G@Ty-P@CW!6%>E^)iu5`QiabP3j5#>;yK0$1YK%fpaaTGijU7r z=m!p7BXW1G`U~1iUP~*NWCi#)^|b7`3(%Z+pXqnCw{U%V`jE9qbG&lKz;g}$@Su8h z`?93YALtLKg|Bw0i!R~met;IL6##})c1S+-8(ttD>9qOX)VzjAT^Q&YJs3SwZxZ&s zvYOTR$b4t}=J7;B7GytqKYl-PU$gcV$c@~Tj5X>d8qbHr?|R6*HnGY;FsM7(>W`Pk zc`fz}H1|Gi<1@0~zGBrZ&-elU3`T+6A;CTFc-8#3+A^?)>b*`oO=D1nN_(hbZ7t9@ zE%Jv1k{bK9t_2g%^O=_%>keYSr7Rn6+d@5?}v= zlTe8(Bis|Ema+xAno%yRZ62S{;c=tjui$iHG7iQPJ}1 zXI)~Hz>vwqu!HrzJwPk6wqE28yJszPnRFqwEH~WtM3a^PSnXBE_O;1FQ-w!SEkA(I zY5k1`z)PD30(n?!yuEw6kLPd%bZj@S3c>7baG5l?I@v=E{cpIJb9sA` z8I0(ZvTUm_fl_FyVJPr>%g^QNw>AKg8(~_H-p}jJf1MX2oIHmcm{-cHqn6o&Yv}7; zht^9vvk0W?+It8=+1Yaqns;Y%)j ze#~2nn*f65s1}e|X=_J~`{l958(x&E?RtKfTlp;}z3d7HD)-=LD63(P};J+?Ro zdS*w3e?2Kx5G)%a%JLd?=YStFPu90s(9M@8RIp_ynbF(VA;hzqpPx7G>^)IQ3YZ6~ zP`j@a;uu1YW=P1)OIjZGr-hR*-Mo!LyeFNP{?zCwvt9ezT&nv76#V>p z$0^JG*ldBM*TfFZ?72cfbzIjUw0{glUNHpp3oxLf0=UGk?gUANrdPE$vQ9f!0+|-_ z5JIQIEa8U%i<62a_V@Y6f}MxZ&EFeF9%!kD(8)cnm!9bDygld`_OHXWEbKu2n~rn%k_jw%9a%MM$7Ex_Cm^ zsewmrKjQAa9!5XJOX4w*z+gY@PKplc;VzDar8E-D{et=%`3AbuQ2b+&BK4A_`LzZchPM_;frMlU0D#TYCDkUbj{3fMAzzC3Tb`-vQU86(CAg$@5{fs6M(o`O>Rky z0BEoC1TBK*5>NXF;0QG5rH%KC{9JPRYEki!fUP~U7m7i`yUJgl9sqh6K#^q26HUZD zpfE$^z+tT4w^FA2R1=zu3oxP`&=}N8{mTk_Ibe%fgO&^^=3}^NJsX}qWC^|EhmV+70+v3tE47ix zO}zz17@cqM0Usn_Pwr;O1;aP3Y4495o{7{I00p|mQn7joe=0#TIh?%S#TihUKshB) z{;I_pBt-KAp7)@)%ERI`NWBrrveyS$c1V%RgR7I2ZI?#@9ZW!LHuO`JB7K9ve9vTT z>AL6-?WY`Y@~cE~KzA;H+dX~+6oysPr1cD`VE+k)eHw;)fc~?Q2bvqf!m)9ZG*@+> z<|O_j&B?>YN0R!aLbgHUnM=TX=}U}7SuY@29U_`+W?2}@Wby;{7HUg|rRr^jE}jtV zSKI-rI!T3x8jBD_E0XF3f?=JQ_S$ELKG3(uX$d7@*)|5HTj_i*`8JECHbEDTO7rLt zI=|fiVZEn78L-G779)5Px}Vg(6GA{b1S&;eXw80~F=XHo8^~D&*3^pwh$4+nKpa`! z-2_OkuM#e#Qo!^>rHJ-Y)i@;Ci>6bghWM-o2Mg{`Va<^UgpDR&nsNzz$#m)=v{(X# z$Y*xGVm3^+7i-RF05pff|K?|6ecNw6u@^>K3Z7NTx9u;1UoNaJ2orO}tComZG#E?5I^qFUH z+h}|^IRAP7K@a9-kdF2kvld0G;N(GLNkJeFE3U=+oMQu;)JdYztMV~S{) zAFxKW8eK6?2WaxcMitL}GwS7OO~XTegAV~<%BXmqQmrR7G*9hMHv8*50m^m38FnMk zh6KYGybud{%V_{w-(Mxd7$ebO;}a9j!9q6o0_>NPn00NT!4HYI>2%bL zff~MJivW~FSg7~?AW@1-Bsdg;yp1oGd`1#d2EKp{v{kQiJ2pop*pF6ti;6!3P%>U6 z2S~>Fy7W+>pARMIwS!58Sb!bm5pZZa)m&(>jzxd6a%VgE|-0rYTU$OIdM zcw$nD+!l}No5njnmSg1?7ID9(~;0Eo6-7@of?+CP!00FLK8ka3T&~*{03XvtNpG35*Xb` z5;?7M;~=1ZU#-xsSG0hh)tDg@C`Q(|@*HfzUxJv9Y|WbO&`Cc@yq2eZ`iSF22!0hD zngfe+rHSOE0UpiT%;m8TEy$g2P22rBNX9J`Mjh);=h%=QTsk-ikdn)_U#-Rb>OvwQ z3nT;z)j7ovi&yf!jsU1}%IBlEqQG)mPtfT09`z7AxZ3hJ3kT226J$SJ`eGSKF$sXS zt!_GxoqW2s50s0M{;9*VAT#)Ei^ZHA%jYfzttCGI8?Mgx?WnDqGdYexu?POJOp4l% zU3`ZEEd0q1NRw;luR-SEOKS$SN>srwLc40S{&$poymXjv>EYxbeG-Fv8-NUpu>3nt z*upK91_nBYwaus@rI>wNEx}}aB0rGCY~z00fWzy3X0`aEQ97Dr9085M2z3&CM(={@ z2?abZWE)fwb@RiMK~$B{u)cYf`?elVLkatIN)f%Sj7L1aT;cZFCxjVhuc zD8YzUyMGD7_b(uCmbE&3-olHj-`}$HE<5rp`?5WpzNL{RqwqGsn7X3rJAe9(UJ@#6 zv+_nXi(#4h#xIa#Wm(hX-rK@fgDystG)bxX>eaWIvUCIg!1aWEFVO7|=9{Ph5r_CE zAo%A8dbT!;9Ixv~bj5Vx(c4N?3!)X;`&lUdYu0PWfdoPjIwT*u<3fU&|4)106%|$Y z;um-JO31^m=(lWND>In-nt{oeSg5hL8k)i{UiTsH%MPz0gUv*f|}dG^8UA z`Ma|XD`TPR)nqrvBS%mSgkAu8foAV=Eaxtq7et>q|8)2VtA*vaM+StK`9%yOt87ia zLWv5lbfDwY8-0ik5d_idij&O>!`eO(tx3i&o`u2`xWg?bnE(?RvJ0!UepwMvVU0q9 zzj3Fq^Zi00&`m!U2a1HH1zWi_zQohEczMj=5FJ)f^4_!t|g zvHyFU>$TnR1?SIixZJQz11}Bxe)%_~rHE9s3Z$Rmc`PdSe#g?!Y!^TTyMsMEdoGt| z5Rxb#oTB};9=9Dv%5tn24AU$%#lzNRuD`5)@#ewtE|tNAj{q+!FW*#sqpzx8;)KVsB1(Omi4c z{_#6D`~G}Q_7;i(aH6bwn)Fy&N3`cDGiPk5qsSPj?EV2az&pJW1x&DfvNZXKPOy&5I#z_zknG0@gH#9 zABzQSw>wYZh?2td?FMKp3wzT#z*na6J}!VB&b3Fg{>|Di0#c_uI~wLhk5aW(>|jDd z1?9;7YfBEt-gL?W@%JDK@GB~3c0PVXg~p!A;4p+g*o&UAUp|iHBtB2X`L`Bij|AV^ z^l(=@9a8aFYfCNy5o>S|(9Vc#J6(yQ|2rCzK*fBcbKszp!*Rb^w1-44ZFA3;)}SBWPmIaOJt?f2gt zXU+!YH7dt<)IKjd*Iw=*h^gc355RD;y{G)2Gx^T5iqEBZAiGcbst>xKd30>F+Hc#% z_ebsAJb}q0=p)gJL0>=6N;lAnx}*f0S%lSa-_GAUmSFOMgro297-&$a#DaU|oyKc4 zU9+ToH!{HXsR_KFzg}=lGTf*en{&Q9wSbX=*%SP#-2gN4lpi@yq-?CS0w7_+mr<+QQ3zNx2|(1k_YlI;W{pmoUMVd{Kl*pVY59)vit zl1f}dHIUdWGa`5GnJ(M02j?mzWeB>#f-fC>r84@!!_1`U?3;Izg-nHF0RXZ^^ zbi2S*(i5#@iSin;dET;^c@Axd+zabF#X^>fgNL30E-MZcgdNO>J4ywhi{AJ^9petA zv-_BHHU0qX)Q$2lBVA_ShLFwx(;N+srY;CmWKvc{ISmWB$<|#7{O%w~(0MlSS&dO9 zph0qXKI=`oho_4Gp%X~>-tuefDiw)=!rj4t54>U|;<(!<|F<+pouMr_|#{xkImz`ziv|cb%YNwuaQZpuGE$989cgK{F6duus%^mTOuUc4DRd>r$XVa0ks< z7fc{68{?ca4=~DCwC?g!9y4nl_&iv6Wv9mF8hCylmMkZd3)!3wdJSN{)x(dpu(+(WoCL$|A3CL9`o}zuwTgvxb8g zYRS}ZrP3^xQ|-xarhfBvUzvknQH?-0mffTt_fC=RubRDbZ2SgmQ29skqWqr94F21G zx>*;}4jL+a;*4-kqZJ)LO&rPf#_~W4Ge|Gt^-|4yF)jpC@F06yfW?AWz?$wDo5jUD zE#kLG5_h+*uRRtoo`cjPf#4UZviE%wva74%1tDqJ=Gp5q;%m;1@hcpI`saecPSy#d z%k9u2M_B5VTihBXwr!|VS5^H_oP*Y3;Ow}vYz)t zN1U`ENxUGxP5PL_ubnCx~(xMreW@tTq+)%)rru0ii3=^txLpTT?29Iz|65J&EumON0<bW@7E4I%Yv0D9#1{zVCpCN_eQz!!X5HKIOi z)<41HusPJ2-&$UtqpV~Hyx_jWqGyFFlMvFo^VIy98xB!sW8llxfE@W|)c}l5Hz}F{V?$V|~=1Pt` z4?MwK;0nWBxNV?A{D#)xmFja7cjcW{>TKPykXe;9w?^1uJPB)Ns;`gcN@0}jr+~s9 z56M^TNH?O0EjK7)PRp$K`^q@FJeS9rfa&-}v2S@i=$?7<8UuIx(%)9>{xm2y>_`;( zcoOXW4hx;IJLpS3sXT_z=6hYYKrZB&7`LJFLOmx}UjJDq&$jL1^Dxp7@(Rn(`A?)LXeUDuL^ zAv3>_1?Sz|^NP6dt>)ySOOty>;{m3l=l$-4d}`-t zxTNPUuqF2hI&e>8?oZChkkO>E$#14PWRKY_fJFKNQZnx4FH6E~byqExO8G6I~=A|;E37IS99v2zw{(_wdG=WzM1vFuS_#E5vs4U2Ro z!aF>NOrLK~vW7C8liB?o^}SzPBmUhf3Dthd#g_{;XoOnEP2&d?s9xDGrvrB$m;~-` zzThD^ttvpiDmj!MU!-3`Q~6~g@YIJIw{O;6kU|=rFdknzTSa))TY<Mqf{SgIS@TTVe&i^_Wf;AnYo;u*b7#@ z&UUIx_fDy*-WiDFkNLnFEsQL5#y1zv@ZOZVfAO1sHH-hskkr=jyS^SDI`9SOc^89x z?Z&HfwPrb}U=i({AK$z}$nvF-loez7CBE5jw{H2Na%XLi`d;Q-)bwCfgmn4t&jSd0W`AD1QsFO{8(HFd2zTub-2P0 z%~H&NjTccM<;Dsqyxq#>iLoSD(B1h4?tNn}b`f^c#=SIemj1SG;w_oQ1hm`fb+46C zVTY<=iA0hZhFiH&8Fn|p!Oi3rSRmG4hCLOkdgl4KA@);E<2MyNW3mSpEz%O^_>qsrB zK&Jy9u`p|B2EUa7pZ|mCo zrBov=AjB7CD>0&_6mxBQ2=5D29@}Sv3L>t|l*N!^O{_D`Z=)19*J31}?CY9HotDj2 zO*{vb-%HgrNih@j*#X=AuJqlj3S;WVPi{BSroH!nhgLOlU+8#`R`sP z>IPYJP9jN~55tYdp`Szg&w^At$xWhpM&J1tX#trYH*H@H0dOPa;`zPE0x9R0Z$T^6 zNP=#m3n~c9*-CojSkhQEyJGOuITDx8oPJR{3lJ*0kz6P--qZPj&XD3?j}v*|T|GUm z(H}aCq56fJJd+?Mjo1D#Utqo4@sVLb0q130B;b25nF^u(2bsTpcWuBf^!0)WQG`)R z?5(MifX$-zt^F^@Wzg%3wPRTh?p0m)RPrCky)#BW(&SW$qmNsfXIcHg? z3Dm%8lhIuHS+hk+z7|t-DyqUkEC&T56yFNdFa?-`#`>Uh@UJ zY|*-4r*`s#H7IJ+R!If(Z!}9E?0l1>Fe`c}OLoZXMk{(g4z7rY0x0EIV&34>GqI&S z7906tOj5)LOyP8_i-3k;OO&FR+5(7(SS*kf@xPy73dV=+yU2xRe_4??@9HV9wO5z! zW<^QITs0P( z&5hFHw?9Gr-O=%pLM$V45RK)Vo@($ZPq{Z8wg$;c=+-!7%9*sy$qXX2zZ@liEoa6< zMDUN@7;N`-+~p3PysxlQGs`6f1|0>dic2b0!S)6%=46_Sl%o%QK3uRfEb zn(`M=Iy!si{be(sjk${Em>)LR5ZOcavg6cIZl>LHGe6ec)4Gu$;e|8Pjwp(e+ zk814ly*r0qW+(GT*+OtlTkOAmf3nhHptJ9Ed%&s;(!LFeqAdzMoX=hI~rf`fonP-Cv+%9Bg~vo@jyKw$s=j#a6$9 zWz*^i*h1>wafUmxllMbh&RLL# zeKI*^ao0HFC-0)M8*Q9Vz!c9Nx0Xl9huc)wxGxtgg%N3*rx~g(PBzvDmViDe^KfCo zV7K-Nfr>k+T2Vj6CPJQ=%K1F{G39v_Nu}`l z_?u{yCE3k-M|@A^lW5`FG?n$w^FODgY=_t0@*_z+NYOjZaY`nA^Y$JHIMRl)p}A5A z7%+e25lrPWL~3DG4L+HZ)+B~HS_^9LsmlJ`u_|l0e1UAz-tMFND&qV*JFqvN_r_rm zbj=M!KGO|M-s5hQP-v~`zL!!WMy<4JZr>@AC!Sx>9#PZ?-ZEM)n)LYvK_XQ5Y?YEl zg&U|`PRR^yMp}X>wab1Ta%9QPqqU`RnUf-wp5aEWGG%~0MeU!pDs`F zP9xlvFTe!r8>gilH2stVGI&D%=1k3y9oi4h+Xs0=Z;(*?&9jR9cKLcz^b1 z%ngB#9cqDg`WamrQn?<%xqLIEoJ4^c^4_VSdngrFiwYswLru9=H>Tzoy~n?T1dRW( zmaD7~02&dO^U?!+?@bNTqayfYX3if+XiIO2I4BY(>&b{+8s#-^FVnZ6Zk_>xf@*Uq zHGhtn>hk7ew#aA3u8qQ52^~b|S>#&DxT}hFBdpGl1W{0yHXh>?+Q!eNKL_cS_5FmR zXm>F{+pNI?bP>1?P?KDJW@1$B!oP@UJrMcuRe{vi;HVYCGBMoZq-x?(3j--fixY^# zW%L=3M+)&|)!GGVvwTJ$;2xCvAMo$%@88Ej54n!*rtQYnbTjU={hD#N*u5fUKb`9T z$?y^X=qedEN01ir8Rv4d2ATUit9RrA1!SO@6HOGl6`WsrsWzsiDH0R*R6v-HT&RFp zf$u7r(CbngYk8dY7w&!H)Xv^ZxsP1Yp9bxS$sQ|Z#32=pH$GhI**QICQemg5cl5Nx zve4&qSy1E}{!BKzhQm_u1qPs0@m3a=2xQyCNl_mNkSD@at3M=li4DC4|8 z;XTFArWmp6J*(a0UwA*%!3TpWAYyOhL2@3u9Lsl?S8YDJ`V6w7($X%P1}OnrO|P>2 z7H>3GCBi7T8;wSWKO{T?&~fU$2QRWssyYjPwgBqy0wto z3ty@-1ta=%5IUF$G_1EO>cTh3rjC>fb+Fx-Ej0N$SJ^_dx z0iqlZ8;l?{b2$&T;}}S1^Z+K6JY=o1uM{r!0+JvSaLb25`fi>TmAt23+{DrKT<|$`Cs8^p>f)qlK;q##TA^I=$p!N6DRmkA_gE^UM3-1H zA>#L3I~O-31-~X0yc&h|QOG~NOIc5ek{lc zWpGrR^m<5Y*|FTW$b?ptL7a8a+nf_fH1-5Tj?4>fL*KUk)su+Pv-}9;FEpTMZZ~cS zpj3^{kx!jMsowV*h@nKp-pO9j;XKjsq$sOc*KO5p;xV{b(B>nT*ep7kkXA>@gxb)l zf=A%E$N$~qNIiK4Yrj(@Ua)#338JZ9kSXE2%nzmoH_nRR%|AhRk^SAM0)2JI7w?4I zCS9JrWw)g8>gBs--hIc`7&m{`h&;8?*gQLfZMgSfT+Bo5O?LhAx!#&df!>M(finHR z-Cp~lNAB{;oV|@US;0PzoCHYf`|I~i>w*i;s?CS{KS5-8==-Zc+RSH$+J3U3_Au?R zjDXIR%}w*7SYT-`ix7U>v0s8E&-_ly4};+m{%SNs3lPf$cdkad0AwbyS&`zMQ$>f+ zHMMaZnu@HYAL7&La=J~Mb$uHvxW5_o$0|Blr82MdnH)Gb)AYW;Jfpi5m};{AwKHE~ zNO_x)!}isWvig)EH~oSjLj_+==(=DP%51X55q_N;N=woEm7UaS{?w-ftw>*iqqVGw0;~ zU$&v6#Xc)ljAg!Er|`Z@r|^~&B6z#a1R+x^4(qh}5!RpF%4 rP>}Nw}A(Z>pl3UNo@SdxO+G z1m6?j9}OS9Fs+Lqyw$W(`iX7hr?`i$k!LQvm6*j^?JYT=I2%4DFGJQGIB+lICZZ-$9d9Ta&qL zfgpM3VwY2|WRD<|2*WytAdGEZu@X)v;KYp;_^JP)W4w93c49W>b1viF^7E$Bn(xIN zm3$LE4!jn3m5r-@t_o;-qSzFB_>G#&gZX}fUtXXD>ix;x&BTR%+hYfUj)rA-3TOyd z(sNg5Jrd*!yA*Fd5Bw$(Fbdn@%=^-QCKd}7wHiHqh+|I$VG@9X9RWq0Qa zP9m~yB$wpA@=V+5j;k*3*C{gzWl9y>?APAsVG!`*&C&F{Q2)G6r;o z4G2{-|3;nT_0Qs;I9;46BQ=Dtvl)`Yk1?<4ZP;K-%<5LpDZm((+1E3O+5|c2c1`yj zs9&mGUllR5AEpwjLz>R%(^ub!k~-UEB}L9=_`EnIJ9jL=8evK2&igIDR&w@unJz;h z9i4`S2&Cc0@<^XAenx-{q5T7m9g2dKZT>!S4%76DJym3(9KPPp#_Xv#jW@lG>6lmL z$M3U2Q)B8`{BA z^Fh}k!--M5@u3H&#M`w~2aIx((-VA2Mt~#Q;}4MV{$UdO4ubs>bQ&HU50m65ed^A1 zh1xybIqp3BO|cKw^fL?=Pij7(BX(z1*Gln4_fp8u42XQs$w~e$SU%WZ5ufZonC0y8p0HCw#MN$M-o|XVlbnerb(hxczo7SD*@&e^C$=% z#V;W?`}2}Q)3mk#y4zi{b})DCS}N$bQ#V_pnCi_Z6sczK3z8 z<@=B9Fd(cVN|ymMfpmfGC}Hy?bhd8(=mXMCMH^j5Ta@bC=<1Pzmpm3vJhRlYd@p8u zkct&w)m|8HxHY*96}vX+U{`Zv_1lv5=&Z5Mu$AS*x7eWQUe&1(kex3vdjLZ{yPfVYA}Q*l%?sUzQv?73lacT1r@De43f$Mne?1+@4Yr3$ z$*p0l%s?MAXh)7!lxooLAN=*T_NgfP3_Vqs@iAm>!=9R>64W(RayaZIzCzTW@y?A+ zdX%H2Q^vUK-6(AAy>qBnG(>HDG0!5Udi#}o7pvd(EZ=(TE`b~>p}>{Tdg?Awm=2Q3 z)y(cHTRRfyuof?Cg2*E6>+7jFG7Jh+gDt|+C)C_M-DKeHMRm4pU zMHS%dppy3^fJ2J60~dO8B+fs^f2_iMu8%B;j{Br8ye(B?xvE!>5nd zkCH$oZR9B7vIRL?4S+m;q_tMMSJk+fW?jUAy8 z0<+Y|Qq5wI4pV%!cmE}qw~`Lx`2-+<_5-Cb;QhP>8@Zs_5f#zoyNbK^B2ESj8#2sG z-p*dU&ge5MU%uGict;*BY+N<;YorRZ@M}*$z(234p;X<}q*MtOYx!+z4mk@h1U+*0 zMJN-A#BN=85z)_oABKgY67xq-9gOFL6P_~5NVE002L2xVS16Yp?)#k8{wvr)_z@mM zA5yjJq>Xg9gAxGOH4DtjD_RI_-m|FnY@(`(#dKFymFrB(;g|^5h19fXe*QE#5r$>Q zkh9Qw?u4@rDV}X4ugYZW>}5MLsE|X#;sjNoD+86vf;td--uFoiLoqzp@>l&)s<#yN zw1<~{#n^TjUCi8RQ8F470&dkCZ=&VJVHpoJ5q{ea>ME>)o!1z@qms|T9(MGD<|LX5~ z2r6QBC|@({CQJTtg|lpxJQn?WpId!|Y{j&0R0RjZUdr@_yd zesa?n)7bCwa?&R^t9A~3IW^m1w7J`{$iLPbqgH0Uz9ob=kF;aEy*$7tS%SCyu;=OC z9eRHMMzcgs{9WhIrL)e5jV=mx%fiM|hK-(~4fXEj4K;VOociMf{TL<-qsIevjP1v2 z0`7zjQBahQ464|1D(GK8R7zhnlEVe+t`k1iAOy0C z8`)q+`sy<-TK553E#MC}p&ggYg@W$9hiUoU6a;lvCd%1iNaW!%AuzM z-SnN%YVg^m=2+9cd3Q$=?3K+5{ot7x^==!xIhx*@15u=ay4ZJ~NQe-6Cf(VOWG@qz zjS<&5hol7a7c}&Ls7EvQB!rmO2$c}@0(rpzgw`#!=G}pk@6aHCq-VYH^IIiS7XQmm z!#Cjr7<_N0Je%%^fP@!t|3iotyH;^M@_!0X_`6KEvIO3pwbwxCw&L10uZdr_XYms* zE;$fhRWebxAIknhYpH|ileLdErSI21^omyRKHDx=FB-$PZO3<5;;(IQaQZ6+hoVT| zu#=<6N+ur~g;FJoIM^f@A5T1qB}nbGHKeG4rnAz<@fla~S@m0kkj}kFYWv#7VjzNm z2i&m-7K$5SuBf<@n-JqzEHFK^baG%`*=`Ot3g(eEkRpZRb#*Xu0-;nkzxQuxnsV%I}nr}4oO&1 z3mm4@qh!n%+LsjLZM1QAS^aSKC1tKusuA@@Pxn=Cg7Z@8qdqq)GMDVd#uV(0GKzRU zh?rTFD@;hvvs{h^!Gdg#L|SAhcfe6+j=Y=cC*#(`fw#EzH}GZ4zyR?s4=;JWQnI8G zG;`_}gsoscE9%q=^i4LEs@$o|C}svN4_4{-J?OST^Iz|N3pz$hDnbA87`$k^H!t$; z6Rqepn|TiCLX+NgNvXG)UgK+!?avZ2{CdE4BrCmIio|kw+lQc=w1$jh>+CIKoQd~@ zCtZMgnq+N8t%G-Fge0N)^BYM`P(ftIsJM!wRDG=|Tfx&srGoEcu~=Jw9U!{XTWKal zB%eWp7&YQ&Ny&YL89gVQ<-t@jU}+8rp{s^Wd+!#U`vV{a+Q|+NrI*%78M#vK$YYxz zO`9An-bzakfz3^3f16xrAxcx4lw1fuf2}?g6V}#YRQQE-)F_Aw6~utDa}>kfsa-~W z1lM{goWIY~Y@pn#0f>@O<{X}oOI1V>Rj;9G&5It8kfcdJU-}fB2SDpBU#VcLb%?v! zhG}IGcfYz6=>cA?{1>xEH)V%+Ub95E0Gb?*c*++>c5infuMyaR5XOy+Vp~7m9z>w-y3v}hxb@}1Y2WkuulNd5bIAT=E2{)yfA``8GiHy!d`tUNn#&@mQ4wlMGd!e- zKV**r4jcPjOlXn*Qpbba9_-5XbxWDWy54)|2mC5;P6fWL32+AI-=Uzrxs&v9&QepE zhwkbAThZV;KKA@OU|P>0R)w8){#D{UTq<{WVtXnu5IYO%y3@?IbY_?Dzw-dHBbP;@ zCrDY2?1btUF`;9tP2He8BB+l3SL7dV`|Lb8+5|y?K)&gOedY?DwV4!;kI`U)@5ok; zy%vuBVh38L26%Z*tHiHH?kW6ldlveydg@aF9|>*PNmxE7zC>3R6340F?DDIp0_smN zOPgp+eeAkdJ>eA#ALw~(BoQlWnv#shUg__RKaPlfb&tyNBr?~)(zB1vV(}u+0=0NN zN9&ER!f-AJ+!ub-6PBAQh^+2=5bdp@tS*BL=A|AZAP$)diax1=wS>rg+Ib%Sd=7NKos$)s7fILz~nAa`P%##ukD2b zLX)2UI`lKi(JwLMn2Eyh@JwDKQNpnY*ILK3SbX^WFHS~*%uT_N3(aJ^=DDCE7$awR zi4a!mObDxwrTG-Wz}O^SKY=)k44ECJr&7~HUr`32dE{2AlwZ}x(vZfZ6LixhqJ>Vb zWyVn5e9X~$1(#t9`TMfaSi*fHq4fsQV=7HuXzLNr=z>zUd9Ps{2wJc%@bc}Y;-PP` z9{$w88l0E(dW1Gg^@N>u&=C~0aq=c0QW8~YY-eP86%#@me}S(O957mxx%bfkjgjSZ z1N`}fM4u75_MYuFiS1?A|P*%$hJd`1FZ`l7F!p#ha;D^WZ=@3>AEPc9U zadFQBIr<|p1rNDzu@P<+&8`Ue_nE~Q9jGXX2d|MTfM&4%-1)0G9qVDCvq%71i`8@ zC-fIGtoJG=U^>c(OMwUX34KHT4*_e7Ju!@OG^}<>CA29o1GGW~+B8tS6ss*Hv<)}M zF?f8q(4m`Tp=M+q2-^!mczz?Ch98bdECh;WrdpG~RKjzS9r_O5$h8c0ki%V}QZ@Pe z;ysY6te0*Ze5An2>#6U0E!vVKGDZqK55~BO)$JTZ{COvYyF7w;50nK1Dff>*!}sTJ zplOH=5`E@*R=De*CxLfE_{TdDz*)S;OrQGmw*LKa696IqIpv?p`OjGXGl>Xs?4Q;1 lPuTb;UjE&yFti9rcekW84BaRwAjp7pBOx6ULx<8`(#_D_UEdzh zdCxgt{rmoW*Y$E;48t???7jBhEADl#^?Xr~6Myu8_yHOk+9OE`Q6)6A+i*0rTfXlN~&9LFv)RLHrvyDErrGK>8t6|kXmS(RvJHl-XGiW4Gbz_BCyK#jJVtH)L>ul z(OY}cmBiCp5w7r3Q#0JmHB_;@3(+w_bzpvuT5txX>7sU_m#Tlehx<&uk^L^_^ z&pgU(!9nwRhx9H{A;+Pau$E(@%u9Es6w>GDaoAM?+>~_28Cy-h94S8s2){v<(#FyD zC-J}eWH6vBcXg7GJTvVukiJmz+l-4LAbDjO+;U*QAR^9b=T`5{_J}a%WgyXj|L@@? z5}v_wjaVX9yin^U6LiteWg@=F1X9k4a`wkeEaUv|LI=lx#6A9ZtVkkj+?NHozux1&Li2fp{5VzYJTs{E zqCMc6*+_5v&+QBe%Yz?`Y`>ngYgVFf!)uVKyK9)ikG4PYg_ax9em#~tq&&yF^W|Ff z4=V{IoA(I;?%d6D{rjE=bE{NZchikdqeFK;^$WX-Rtm3DOx-G;E9U5u7?SZ$UBy^p zU?Fn+ZFNnVF73~A@z%(WGJE31N$d1??`)FyNaA0&EBq+;8U^OYHd9YLJ|kJJXP5We zuBmbxNc#d)Y~EIoDt_ZX?mPV|b`{&QQp1C6bZHO$HpaGjf5bQGH1V>tQeQ-NXXI0}=^HNK>8Q}8}{U~wn+digQZ0DUsZQ`l^f2+&e0$~x6u}+ zxl^t!E9=zm*rg>=5-fFjRxS#o;eSVS@#pPFKmTojI0l4B5Gdm1u<@obB#R`B|nvt3|srM?&}p z=hLTt9P~8!Dw%&EkD(uwd^?a7FG!REQvxqM=spfB+ha}NND7m78e5{z{5ZRj@eUyiAkJen#y3HvP|l3I_(`>)`$fx5YR5*;k() zqECFTe~rQVp~FZ<=2_{lL>5v8h;*|O3vM|Y{qN3Vx=pf?`!^ew8qY>Ty_&y3@4TNU zu=7GkvO+QXem|R|-<3VptHl-y&~K{QyLCzb@ohg<>=T{)!k=8<&>0Y<#wy5-$wJ>k zAD_rE&y#$UFvbh{0=KiJ!TIyW;R}7Bb|9kpPcydRV_29dMNUMwq)mn$UP7ce9gQ{B z*2^Rcr*L;k()N90l9ET`DTxvUVOSkYi(ZQni*ozQ`(cS`iK&UfI+YN08~7Amm9Bk= zz50^-lK(3ye50V})bZhRnld@xAsLBTQ(3j&Vq!)kT%uDb`eO8{^vQiBuG2g#F2!@% zhQF?hB)xsAtgdvTAg{cmFxzj&K^gy&@msXGwq#yr(hq?QukY-u(T8NbG4)}3l11q* zgZgysw1jl7wA~7?6ke%A)9h4J^%dm?BnHI46;!@1R@$pXGPVd)`-dZxDwU!KAN=r= ziiu37jff11yo#*q6zk-R>Z3bhVeuDi3ixfnV6bVJ`2H{#NA;_!ugajRlqz<1S;*4$pT0u_POiI(Qrx7}o>G!9P>SPp0o{y+<0dJC=#(V=|MLWe02#GXa61wR50 zp@n#Ekus#Y?!8X5ZaK6uOlmeqY)Y)m0V2mRv}iy`&!l$4wf8i9FQ9F9Sd_DegOYQ_ zVtdGH7&0_C@}M8j!g`1|Z@8#u=y*tOR4>a*sYX5^b)m3nR5Qy#YK2ESYN@5uSoLRb zF}fqC(;cUlYCgwF$AH7HM}~(Fjx3H|tSTQS9V#D=u5oWXCR`=wO-Oz|X&s;)!`CIy;Z@@i6HtgTnclhZEvx?Js6)kyGP81# zW6_l!b6r8L*i2jP>_z2i_X*Ed@EQ7P^lAAS?M2I}^}fey(Mrm?{(=zBJ5mpk^iZl!t)`2)$r;bSc4 zJ@!hp)zQ>Zzid7|+f&Emz&pi!^d+NttaAH>k_`^jdf}|&64&z9A3k@IBU{Kr2y_h?-x(w!_}Yer){keXGfc>9^AG5;#`mJtAL+@rm)$ zQoc{ijfMv5aOvLj#9NAiW-d(Od;%5Z^7mdiX|>QGQ<|sGw$Gx@NKIRMdD*nh%ilR~ zKuUSKR`?lrnNLla1Y_7M+>*CzTMnvMJbGq}(KN$qzq_SgH>Q2A3%*UTr7NSESbTc& zCaAzctp9|SaO**Cz)|5DxE-x=7%-YV|tWB*~9nEnSSpS$5 zv|h1osH1!*t{bJOdM1bLy-bpKZrQdtYgvm~pC+!6cJUdf9Q%Xdw%@6;Xj@dASYEuXhzAz`)(ec@SKHGtR~vyE=pG9R?g>>z+N(yJR7{|Hv}w(44|=k}I=a$*+^&atafJFWqz)}kkAk?@ZI5da z9+Uiptf#xWBQ>cf)TBc~n^&uc#Y@UlSy>{bf)Rr1o~&1cr<{u$SM&Mv2ISh!(J$_? zX!J+oqv`G3`VfuQhrRnkJhf|EUrEH+Uo8}U}_#p_|*aGJ3ZVI$F^F$`}4E|v` zyNo>E;vTn$Mx67%YS;u7gfKjNI%9aB)IVU>?AB|GyK}*2uC8WXySp*E3Y#Qoax3lI zUU-y^?AOl9m*1Y z3Q%asDv*iT+MAGZGI21mPzXLCBO~LtH#X%}5`Fv6+rfVV6lRW&cD&5YE-o%iF6>OU z_V1Zld3bo3S=gA_*cib#7#-Yf91UCME~-xvPnlhN!u!4esrm0a*`ZwjdFOw8^3OZ@nNbV)j|Kf>xBgxQ+a>sbpZQ-~ zFZiI8Pqh@B2E<%cUIqNUjXE0e^BerW`sZKpo>`R^5yubyl1qvTtGM3UOu`t#A13k_ z!p90qqfmQoMW~4LyV*Brnk?|CkvhJW23k!ZwX>L??xwB+OW^l0iZLuh;dI{zhIcl{ z(8Lz_Ub^&No4BmZ)|Il?TueE1_x8?I9rQT73|MJE43PTJPzs~n#`%DDmkjOJ{nuoV zKfVn#dhzD+A^7K?*Ku%OL)>uxe*BLs6u7S;lm-+}{&CCSYshv!)b0Ou#}_mtA?A~~ z|F{Q7eCFSuqkWXPi_<0%+kW@o*FHe^vH$CSFqA082k6^IQJQ~^iFT{`o6A4P|0^lp zt!7p{8n*wCo(x`l=U*#9yZ!Ax8T=c|H_U&p4h^LKv4SZalza(%Q_|5J71U@rUuC%T$OhZI3@?~+E&o{>%432B z4)B2&MU$AbbPBkM-p5amZ{1g60D>Ckm2dlz!=2*nXp+E`jw4u#C_nxhlDA3%Ub0KN zE){Xx*os~^2K+xdjsc%K&y#7FM8+v}R(=Zu`OL_ZutdLLrnuk{oY@xOM zd_8Zn{$krQn6X^LOY?!?iZT*Ryj9<_KOnFxvXMk6QKj%S4X4n3sD@~C0$G-dFspwtVaGx?&~=pHD)|^>dHs; zJcd1FY2^hE`$e3WqSPl6c^vj<)OL_T^o3^h1!YQP&C}IRrH{C$SazI@f$V7@R;R;q z19bw8#8iwnAMb6~>`ZWu>O46U+x4(WxPGRz`$eNkRDpon?L5C5uTX{>Erg%Gx-V>>Uj3vM%O^+WxLO-O)l_zCRwCW?DX1 zM_Y|p%w^LqvvvIxmDsm&PK8ifsdw+MY6gm59Z%YB<(KvbO|exhhD%R~J=4W6Q+NuS zB#t>J5O?ayJ=(39_0EH(ylF`Exjy4E>;L|SIrOUfWwLAI@sxx7#aC^?fOi<+%4&$D8B7i>qOr*u6jQ zU_*bEj~QZ}AXepPk=Y++JP%|&7DBjh$MIflmNuR8Ij+9*IzP;LQd+$xbakklz-1jT za6D#IZa3e^5~@W?>bd({rTkrvwz_U_-1MMj0YZ)JLvDJeY=VDZs$Wt4d1qn$xkb&K zFO~&=y7$IpMbWhDLI|%6$69Dy8>!C}jcmNJi2p-OBcA10qkPNJ!jE??A7)jLAI+@) zAqj}P@%i~1{rTAG-h$se2Eu27?=D$b3UDVWiY<%4Wn|JxUz1sU+buoURr5Tv@Uxwx5hm5p&OD-KC2JCzAS9d)9~? zexx|BgMYhE;3N?!l9%uZ0i)I!&q|`vxwy7&8dH+0EBG^6j5kA_I$4lj-d49jg*_qtek{NYRNd7?-}IN4t)a7b00DMQ_Y|sTUnTD zHcRHKJTbxzWvO72A~-`@anUM)(CV=i&wa^1gyJzn6R!m0Ipu-VOnU;%wUq86G04K+e1$z!{UhE{QK3_5yy#UWfqt3^?I((LlPv=(v&n7*CZ>EN`|g z!)1uUlF!W0cd(diaRhH(rg(-270NLNIJOeK{{(x=$F-M}mHdF_`fzoiDO%Gs?&nm- zUl&Gq1QP&@#xblCl5n?$@a67ypa2)$~?h>;tGK%*-iXhPC2#)>J! zh?nx%Y>w+g9cBE|TZcW|vv;xlFA}MgrC>uDFTSXmDs3QR^u383jV!L(FkZO+@;+b7 zVhPsBzL*I)dgvpuO6H)G_NHI$BXOU)c}D=$Vx-bEv?gP(#t-AMv;Ns9w7dHtq8^~- zW~AV@N#*vOrii6wY|B?n23;5Tl&=)Ew$uTKEUX|%(-Elzc`jw209K&Cz^s|X@v(>h=IDU>O9 zgQ(~$!PzV8ab?j}eIA~{>R?%I__8BI6LNH^ZBu2^FICmUH8~WJzPO^oT-c({J^E%Y zzFp<7^sSXzX1a9F{MqyY^7mD+K;Y1_IPY5#MZp09{&ngY9Ygl_y$_`(oF}biFYdjU z4ky^M-O{Q95nOl3YDy_6o#w6CN$i9qW8s9F;*B|8cg;?{Hf6A}>;}}-*<++#p^;fD zPjP`oS1E4-1frdKweGCPFwf)h_tqd1W5+>!F85F|)qj=;1WtQki4F%j+4{I`LAkve zq;L7G%ytZ8${K9z_dK<(#k!IzRl2F-l!A;R88MDz;=Mdpl3dWGhs?ODq~lX3OW(_% zaDMCSX2gH`>!(4}W*{Y8f+`wnyDkE;ZMZrnjxeBeqEJjcdObcYi$YcB;;3~7dk$fc zdiIx5Nwem}CK(UDg2P)>)pbpXpRFjh!AbluMHMTK97fMBrwoG`WhP0hGrXFH4R!wR zpIc71P*AL@uDLB{-xF>CKy4an=}2KRiY6IQd_<wCq-sY+f9w>ww%oekj86S zNdn)<0gj$S?&&uySbm_z*DLZ&nK(nO5>d&8QxK;6@*@l_FwzSxLdYF*`X$-SpS4W{ zO9+90n2ErTkkS%Yz6hu(J4FU#XbTrsEyr)GhMnxWxv+hL7x!DI93u>gfonPF-*jF z4-WsL^%EQ{=?P*n1<5!P-@Xv;d^Qjr7!?`xv~6N~2+ZQ%Pp2pO)s^6WGdO^+33fr9 z;t*vOBHBkwAm%=7ww-VMEc|Ig(I`Te%Uq$1QXF1}LUOg4VW<^}7rf8Wb<&Y)s{Rtc zm>RdmWZwOQz;=|d`^_LgWL7_-)aVum#|mlRLJ$w-FOIjYfBW7O_LOEsME1n8Hffca z;cD07Kl~_#@(YFqrfxLNtXPC{0e7i9@q~W7dA6S{;8AYQjU7|AY#`#oDp?AZ(&Hd% z>B+a5C^KhC&`FWZ1X?sQqU$nETmZ+L8b}xCHj`~z`;&zsO}i#AW>wm=dA5>LLG|+5 zn2k{dLLF%FvA`cwG_Drv{~7`iH#9r5Os1Lj$J2jXCb%dW(G`@j;cpU}Oio9$jE-EKN z=g%eB8EKrxdmCcwn4?~#_glt5Hf*4m^_*E?PRR zj~MK}CEh?02q!BNAP=nL#1T=v0@a){05rw{5Y(fDunv?~-uoqVzMB4>T8*#7I5V12 zd&07)E;^TKdSc-ezewxhk@#zXT>Vlpwk8mzV04VWtRq@P1!5#SUm!xyDVk@T_F!Dv zD_?BvrCg%MLTFrGQ9Z9lt?qPhu0Dbxw`J&x+q9eT0>BxIPr2JJm}UU>StPDt-6g{b z0rXdhve@Dq2z?rHosj6Vajec~>GEoq9X;FVZl-N__RAaNmENSSpH0|ZQPihB6;P>D zj(Y2vS~s^H5>7t?QBomqPrtSgZy6h6Jwdus|L6rj)Dl-Vo`bcgRxMbkJfpgPqp-&6 zY$>KM`iJpKsKBvC4G7i2^|P(dZ6S`+mK3;v_j>gepen+OlG+C_hAxX>PBTQl`J>l} zkwxjeg7WxB#2nc(R_hOj7X3UW8`+H8!#u~kE1)yLyGRtmMecW_+;ri zMl9muW-Fk%3CbIaa`7Cr2V8EUN1MdyIX3Q2))t{gj4kOVL7n6|8=8FWW(fjLe^#Ut zY^u%4zUHo!$@s(eEadHEE6?}4n5T2{Q!FH{NAfjUy5IDboh>I|Ebys@Qi06a75J6C zhh3R>n~SY-bJy=2F~U_JAIhT<2j^gtgVgYW7CE(g{O7G}`ZiXda}n_fy~C6doZ)Wg zyWc8EZtns8U06!U9mH)@QN{SZ2r}K}4^8r@WFHS0G|l~np^CQ)g%SHbxQ5 z5T5J4i`xy%)PnNu*Zaw~1&EP-MdRGg)e^h%BLEA}kGCvA;wqzRRqx9K5@?jY0U&Ir zFP52`2+XM$0Cl$K+%|~_%TXrP;lGl9IJhhxW4ANU5K^v_H zfZKAq8o_Bk*ew1nht8-&{Y7y{#7ph2XvPBB1g@N*9(5MwOqt?YpPOl}2{X#Sn-ItT ze&Q<;`|Ukj5NC1?4{*O;3%z2snJh0Ts6Sul^1gH>ZBatMt0WIp$m{7D)=#Zw#T&v< zV8UT-l%f2C1iFrjJ~+xsgBg+$o7WdR73px^PP*be_uyI6dyJ|eNiM#=y2$!eJCY8|w1lw2>t~Y(quU7E2KV6$HeX?1{pd{IkhuXp*>_SXxsTHp zo(jKuD+B*R0^8(%XD_oEXjFGA!pN{idY+aBP-2z#o&o;QbDDS^w?HHNO|NMp*r6C( zoIOsPaizoPB2ey*QQ;1X*AQuc-s9)w@M22%6?&l2-(ir1^m-5ES;29%L@n@fG!JkO zEOv@OB8+x6dIQs$!#g7VpfpWMH8iLltH+iixP{bU8!Dc?5BrLe<~77f9J28Ur%hu| z$u$VfL|X`%O(lodf1y!-fsBQC=)1gyczu(}gfz1fZBKFjuZ#LCMZ+y5AsG2AxyJI$XQBsVdq<}+5o|D0gkw13v!A#0JJoO?X5U(e)h5XH|JjeChU!ms| zaWXuCMr>16miyo^7dO5etpe>?e*o^F^av(*DSS)QdKMF9MNcs)-e1swjHBsN87A;#AVd_ z@Q=g#xSFn$s^w6&qA6i9iN4FUlk>Pq%y=biAz!z~d1j~7ZvM^1;UD=efPjlld*U@g zG`ET~iQe;0vARbAA8)s-9|Y$UrPoB%7XUT=wd)ugsnNm+2UtD<#oHyjEff940{vPJ zJ)G76K$prT@eV_G4y31$P^Wd38nA#ZpzR2K?=$L$b&?S@(k}lx1jq3YMqp*MCPNtShAMXYOAE3sJtaZg8!ESt& z<%wYLmUKKG0j;+p`^hl=hfEFjWueyXU^85rDQ6&@^5J=#MnN9p&)etL_<>Se2QA*A z(h!jHSPFO?=N~W}38eIf5V6y&vN0dKt=9-*!y;9#E@p2E9cs>#;3RzLy44nYiYJIxfodwvaqo+*X?5llSl~i;9J_2< z0__V7h~4Xci|V5hZHzun5K3x57-@X3VNWrkFf3jKuSY@Hh?So%gP{3anWfIs7f0kZ{3tsLvS&!VNuXWY| zYbe?2`7#Ar`R=#x_sQ`PaaCQJSeD<_j%!Mko6HTDY^RiSAPalk2kc?89MF-UX1z#G z@x>+Q1ur(A_>WXMy!V*@{LBjI_2a&^xn91VG7)SZD*lRb(}ZE76{2g6Mc^5!UNbRk z1IGhwwdsyH&9Vm=dy129;681GGziJq)wt6qhC*J! zM%KmaAOzO|Gt2B?K}496*%r4D7<6qspwEAY$2ePB;1!gv_2_0V)aO%Z+B|yvoU*<< zD)*nXu6Wdlc-!nL!<|5l^K$dVE1pBSk^w8c1G7BUa zkBE&ytn~^LUs#fZ&VR~3dw%X`3mals@H(01ekihl2FT8nU5~9hPB7=483BGTkTTuX zotVdbj3L+pYa?+f16Ix!m@5`c@O%GFyh$RX3H);;p$}L?IUv(byY%|q^pQ}nbH_hc zcHFJUL(1|9K#4*vWh_*O2c;Tru06t50Do1QS6EGFdHu%cA82t&4p_novDNF#K%+bF zhCAcAiuW1e=i)4-QE$V46o*uW8%s@pma<44Vo;L|4qeoSxQrQKc&z7S`SuUhogIzp zCz#5;myS=$=cJ!j5}33q4G{oT^LWWK$C()D2tl6TW5F>LZ_7DCv=yia~~l&A{QXG-{Ib?1rZ z&I+i7OiF8YRYoz~;!9@~iixk;Ykl6tV39g$5~JI=(*ilgZ)2`Mr69an(aIm+XFzLE=v&UU(f2bTf#N zUf_urJkNz8q{3epOvl>tZ-I2$77%OLg#$Ncn3;9Q6ZDtc3uv%I;csl6-vES$Bh=n? z2O52xyRK!hHeAw%PzMn= zJd&bMJkTE!hH*!O1a$-sDhDhnGzq6V8WS zMWwyGT-Fn1o-rJ&E`Xtl_D10Ft;OI-e?6d7#8WtE^AZ7KT~g5& zYYl=DV6*%~x(|igT(hUGGC{=8)>?TAL5ltRQ+)RS5S`WzKL@dOv zu|NT7;qkD<@!MI{GT6MU-z7c`$*xB`4^)4gC~gu5AFAtnIXy}p&PM_kmCZJ`sCGZd zJm6`J|3e}}t-&_+eQLN?f)aZ`6xGg7-HBcX4x;I+^L=~C4Q-3olA=Ws$Wd}0^(6I{ zSHTVQvb062ic5I{SvtvwvW(EaPtfxWNzElxc*vTdV^eD`X8B-_PoIL!M%-`|w@`11 zkT~&=^{0w7G0Dja_P=Np43~%T1BJKjZu9~SAPP;mHC+PVZZ+*R`sm;h1lm7&kfWfF z%*K?oxq2)60QMxh@mDD%`$(?qxbkrJ=6ZBQ-^UA8TOviCg5tz*>{X$pzQAO09NM9PWh%#E(_#QlZ$ z>AVDa?h%tifp`z^DCwiWACDO_wr?v98K8F-jg00fXQ9Vf`1XjCdMyU+?Sp{xTY}sK zWRna{QuG|gV5jW=u~XJ}ZR^jLao_~uBLtp`O3UH=oj%)DEAd+HL9&7^tPT&qBL_2* zU)j?Wq0pTO?Vq%9n~#TvDe|KiIBgR~Xq?St%Ha+zIlMcQ6}GW0Ct6X0&3lOsy_hq! z&la1%JfUnzAhzEF89qjgMXF21(^UGrF?m!R*U>KoXwtr*>NfH%1na}`3iUq(Vv_11 zbIu|6ke>zmo2j8QxH)VRt=a0uc={&`YT>E9%*3tOwr^e%MiWw*5k1Cg)LL02=5vlC zE)X1@4a*@nIo{QE%&y};RGM3Y8qD5))|qdJ@lj0!K0$vdrB5|O50 z!VV=PN;*Lkl!hEa)i^)U$N=rnD>cCu#TtLJBJ-HT#||nV!|Z}W!?jk*oRSFcFms*Z zi*G^fdpJiwk>_+N>BgLeR~i>ix-Nbbp8&Pou$_|jh6^0=X&L(s)8@RqaYn~vW*h`| zo>(7sopPM?SmnWIZy+wh7a)X>2KOJujTerOS$tCs7d6GsxsSzw1c!9{MNBj&P@Vj| zB7@(Q@J96It2euU#OPU&`dq?3NHOYbxsIc(O{-q43%v?o zHH<@9vvEFLv}-gU7PTe~L6D?8Zy3c99>n9caB`H~IkU(sSgt3yS`&dFwB?s8rCSoJ z%1sN&*gscmSJ-4)Q+d%e54@D^q55!Y7G#Vt6+Bv{1hQK*$MP^St=D&JoiICW)Pvele7Wjj6bNvJj+XejQL;X(qC`w& zG&UP8s%n}oWt{1Kzy|^PPus{^62WC!9&Q?}J}FXeGtGOcxl=)~ck+LC+X#-}$;TS( z@FRg)?_XM{+E^cI{;>d*AX~qu>%A^m@(=TBt$)3uCJ#W&r=5^jAlu_$?wgLdk zdw@=TVr7dVAUw{ykYWU;KMsmigrlr2A933VSliu`6%+F(^s~rpB1GYlWaLmu#JHFS zDbLcV6H^X?)j{SvpMHCmJS>tb2Wxbzr6BqhHxs+M)kMP?C>lR3F`E`g7&3w?dli#; zzioYU@iE1?Vzq;%OOvHQTX)N^=Z9pPY#=E2n5tVANh`J*nmRXaRHH!gM7i}w(C+`i z9Vyro+*gYKiy0ErOvkD}XKDp->d-NQs;uxvdTZyI1hC9KGkclk7j1wM zVcqITVlhfn zs9&zM;QAbbxO1D zQWSy}fC_hvaEf#rP0UZ>rV^Iuu=39(Eh3ECHq7s2xjHV}1YTtX-s~~LEqEdcd02Z# z8@XI}=j{9|o24jn2qaQEVxV-H!hYzVrRgMR5TZ(%r>3n1KRmd`-rY$M^m4azPp~d$ z?a@0XnGUA>+eiZYE z>n#`kXg8O4z09&a0Rvv(47AZaE;)U zHGB&4)X2lfhmVCWaRj|UmgUPhqw}mR+KPzB-njBIn9YL0cnERS=z9QZCWi-U_bm+O z<`oi`>6nyv15S()N36qNI=(ouXmV!B8l|j5UY526+0=m?cid1_heoBYX^^qy>o<5C z#I6qx?+ZvX6pOm{j((JfscdAozT%A=ZDw>bu=IKx10rC;_hdG0dCm=epT*{HsG5tC zDG%UAyHcb1TLPg37;Jwf4(bJU%I8)>t6GJQa-~|sLFskgoFbb0tKk`oWq;(@5u2wn zu)uWWB*+h}n*(t<-wZ!AM(|re5{=2&&B-0N)bPZC2F35Dj}PFziYepo12iVyP~Yl6 zx}{Z3mJoW`XH>V*YKh0yAuarY6(AA0-u?PO%xZx_wq>7jqIMC;SFA$g)Mi;^@v@hb z>2UWR>=5l6MQ1pOU(v&A8vP)CO|cb81#7fW!`;;oV`jE1GH%_*bMWD-EP!trowq9t zg#bHI0LwnY(gzu3ExGgBU`v^Xs}6D=v_ZX|u(1ObitGUkomoFAV$LebnzrUBkS8IB z%0smR1@s3Q+#iYU3PkW8)CU^TJc8$;LZ=ua1bUvc$q}fUhL1c#t&YX`YtM@aG(_EOzbeDF?F-l7gdbre3QEKb;vv!37-gmUJPTGJk{e zx;R9^cT<`?YPMed04SZ_R%oH-z80jmK4W`>Z@Uszl2e@Ro9QKo&nPG<#G&Fqeq#_O zNbCIddMqOd@+w;t z#mkUmj=1*tVe__gT(fTD$3UwqK^2KG-zWR?^h#fft@ZU3o;~0g9%xY=Fm7*mzFKBy zf~m+@@QhH*hI7jfT8!k!nfKW+zD}`>`(c=zb&Lo!vZnAXA^@QiF?5*H_XT=huu0XD zi;rB5Q3LqvGn9*g;-+eWK>P-mJHHv)Qf%<8IpR6&YXJR^OjI|}HSazFIdb)p3GyB} z{ENEqz%4Sk7^O@ZS~IKSu)3-eMrYcBy=+5oQ= z3xv9h#JLIw8X;8`9MJ-e2JSy$1f(Or5V!n)68C$j+X2wlwsI9}K>kyZ^m&0oMx~O) zIiP~piYRSyDZp0K=;0t^U7zBUfCJ}f6vYD7f|kCXC;wSR;jh0Shwl|Ls&)h# zlN_-EogzgX&#Yvog5yIX`AN6FtOCJAxbj*Ex6#ly{Lj2WZ@$ok#HZ|3#kdq4&um@s zZ@7)rja2Wv*WWmqELeMSeR4E=Rju_$m*$(!rynXfrx!s5KMWHs!a)C4E+8M@g6xCB zq$|3o3f7mvEl(q#y!mx!%qX;{Y7a4e5#hKtD2D!sm@uF195_0)g0!7R4c7epKH%G&}{Yfg@D%i$xe#Hg3)W7S6^NeH5XHHRJlN4 z-ARcQ@Z%LcSQ%eHVEXhK<@qMqSn%{zAz-uG+Eg-bY_SIU=QXfzpRLjkQQ<~ zpd+l^+br$t1f>v@v7ZL$;&EPPwsl9*G6`I=HZ@yZJjg)G65ICvcz~}94*1+bS+Z;V zeF|fp`w<(5Qlsw&P_#+cOMY9S2I>{d3AWLI)N2P&CByS<|LqFZoho}C zH+t{3gtHQ_3+IkQ}EHGV)l(}u}O1is?)p-++TbPRdAt5rB@5|B>;e|s7>KkD(Mw`8)1Mfj~pgTFqi4#|RLrKl>SD?`svwWV9 zP_2ku!#jXMZE5_igj(A@N#_92H%7IoPYI45=YKzX8l;(Sr_@727z1ty=Sv)MOC?pS z?}0|e8c>Y~0CfBy>)6J@6_5FJW!XG%4WWQL{~g3fG(`Z=#U9Qh!=jJ^Y0WRUDB;v; z#mje*X0o|KkMLm(1Xvi-x4UpNFpCz4QuT;h5i z11PyUuBrY6R>ovPjSd`7U~Bx7z;wemq-TiJBz{x^ZFD zl5EQhkjm?g!RIY>z<>=&vL}}(d!L&Vv73ICNfFeI=P<{U$00usg8JW&$qWGnFyf>a zb-&l}+g6f2eaq=vBTli%Q_y?d4jKW;=+*LHarme46=)&CXX-qHt7VHkFr^_bR+Ds;HObVL z^}1@WjE^KfR#$5|@)Q`USIv)KrUl&Dp@e_LZJhxHd5;J8(Qc;#R3`GjEw`PaIxxLdHocmC zX8dO&rIxL3+gpK&5ZXOCTKo~Z{+-bFIjC@7u94g}04Su1!tz~BW?*{oGN>omCHvT) zOqQ8bfKioq?D|(S*tF zGq6*lkfoRuZ^zD8ziTOd+`lZb3w(r{N_E%n2d2C6kW<`W1rX zzwl(078Auy*Iki4uWnHkh{-90=f->-8qC*ZuL9ksQV$is41*45#%<}>ylt7h!;El; zta`e>ss7pLeX1uR5!hZB?iVfAi_@^WTJE!kL}jTAQt$VvLjvbx$RJbChcJK_U{7(x z7nI(-#_4~91{?AnfWMV_(gxBWh5dt*hHsOg?6NkL6G0FQ1n??lWM=LQ-U7UtOS1JL zHhw893@LgBHY$-_D*r7ULW^p9$TVIKa136kyd_{fKY6otZ+~@@Ib#G@+zm+5?c)&7 z&srJdl6$dRvxt0tGFyKvXAG3q^j6PLy1;Y;OmfJ5Gho>kx6MV^X}+YNqi47ei0XDI z2cNr^S6h1xDdw28Fu$Kho`N1<{PV>y_mSeaA;h#zilrO;Ho9IU1)ix1w&{yrGD*}!kpZLo{FSCE_^N zv=tg|od!uPX|(q|evXR{rQ52{au(h81q8YJJgWE}uDFr1$UB_-fUI8T?tqv3u6Rkscla%5-bIYLW^eBug4)jk`+06TXL>wt9%1GJ4!rGY)QkMv2wXX zF&@7FPSfVZlg2U$nZ2B*##~+*93OBFxD_WC<-(i&n1*(cWo<&O50{0y#71Y=x3VT&_p1f>JA8IhI9Jb@y~7^_K*Do_iX1@ah0NP$Zy+IZkADl3 zSpw`evbvc^7zNEtuvhq|tbCI5%y?*%6`;POceyK&VfJw0XJ87eaW*2*{qsx-^aU>Y zVlnL=D_dvt6g*?nS$(Rwg&$?HV~qYB?2jzv=}_2}xo`%FqkXyLP72#IU?EH1qh-GCX2nCX(6ky@9u*hkcfQvAdA{lAxt z8+}HC&rMDG@4+5Qv0R@A8eQ^`-M;?`w4y!q%T2Y8r-KJSVeLBPQf@jUEDCsQrvERp|IyBsK0?{Qdr1-! zqAa5*}A82C>GIkNOnaanpoX=CUWbx})g5*07UZdS8)-G9Dja!@?U*@+;Bx-Lx(PmnZ zqnrA#8N9&n!VI2P7*l1a7Y8(0-a%SO)E|0t1rl`_2U17JZ7{EAA;i$iaS&A%f9<&e zz=x+AUC6eTWzj0O9z^}<4X3nbm%;nP^fZ5wlt9eYsq}Qk`JCVV;orXek?!tMKjm3U ztS8RMozlE7UlR76zQF1TRo!2LmVy{%@x3fNcFePIqvj2}11#XwGQoaa^p92EogitM z+3W0*R&G<4QTtSNu=O>^HKr}l3}zIWWF>3ds&d-ska?OpV6OA|b9dwuCj^vIVOZ#w z8zM-J+mULZkZAZ#Q?83bve1VM#o_=09%vybzbXFB0<6;zRZuH?U^}V_Q@{DupC=r zQddOMvyT2g;`w-DB$MbC4)xoNS+s3F`BCFYLUqIATIZG7%VV!!ReFCgBQN&rv?Vt; zcH;=IM>g6t3XC2QZedbbT>p<6ypCd-W|s63EH_fGuFQA%JfoA}giLi9)%Z~#yEa7j zKle281$1mBttnN=*c!=+0g3tR$*C!O+MQZI{ki~AVa>YJ_*3CU#<(bO^n$P%O>5Mwn|Kx(n5u$!r`EN#{ zKR;5NlDn&9+;+I*u1u#$_^23YP}#$+tPZ? zGmZxI{J_tBf=5eFeJTP<(jTjwu_pmNa_?8E2%uP+pBdcU=@vC#v+7}6S-A4K1=R81 z=3eT`xHE!J)#TmgdJPgdlU%JIe)xSKbxO;DQJ>RAKKT4Oo#uoi7-TD`Bp_Q@@&m=V z6`>@!_~s~hE$N*t2i>lU?LTIg2{Dgn`*c=n=7=-zGL;|t%dN$ff0xFp+fFfaTkNS6r@42MrIFptHrZ^ zDHacaE?V;AW|_fxhZebFjzbUMXbNfsjK3!|6W}@a#wvkG5NgJlWpU#GLJ-(-(Yj6- zVcM?u7m~YmoO2>@2PRcx=i5#Df*JZhh}|db3CZ)91`}hnAOuB)@FT4#3MbE?g>(^V zM*2gsqId^_QlU7_M^A`NoE54Q&i4~^UQB9A{q{8;)bDeIKpsqL?#r=lZd8M&Z}%Yp z&$P(w2x&%btFTuT-||@en=W^2MW3K0&)>0*XD7U6)>%wg60$|#^?D1DK;Ejo_U)kb4RlG8-piQ$S?9<1Qa<(uTlHK$t-yCf-Y%quV62THpDf;7cv=grem$^ zQ1G>bU-B4;c06VeQ*P~in49I=%@y`*zP1x2JxBKntEPfH@P_Url+$hL1Mb5txvBxj zFAY_sgVrJ`gXqkMQlUN)A_#o}l`yYrf7^&X`4@fH~4IGsM4C4;aq{hc>N^f6Ak5-~@GTh--C1xTs>o%rm{})V@^F$*wu2j^6>aK?$ z6-FgO%vD2y3T_u5s(dpg6Gk7hx+Hs!oM7z6O{31F)4K_@XixiQ_9 zBLM^ko)g3%>E+JClSJF{i{OT3K&-wWpBI(Nw4LCV+6&Lue|yRb+48mNW@m<^$wd%h z_aOSoiK37$6S+j_sZT*FN>b*scU<9I{qRMLTab9RYdav41A(F;S;`%(~g2A z_2YY~?@9R0$0s+~0)8$}#UVGDJcD_riK?$imHNrSM@C0Ck~lG|XWPFk z;s_sqim9_*gbu&!9%3!^Z*(ZX`mGq=mq*Zz38Sp7&%zd40a4JwoscmHt9w8xQ(c>) zpmJEaM;xzuF-bt|wi+zP%U*-eV6XU`&ak?HLl(!N+odUe8G1ZNSrj4Rd9kI^u0Sm` z-)3!ODUO#+ube?LRu+zrIR_3aJ<@l$1lJgPOdQtbY4xZ)9|l}?lkKje|FFaEt#Kvzi(PnS$FFFCDF~@t)#J`H;^<&PzOhGzkE1yY> z*#{eGpCMl*e9FhIhut6*dEl}8j4I8?dkiPUJO@{tW5bLY!>8{0uEZH2wD^Vs;Vz2t zZ^Ui`8J5kv@0f}BU!Xti1UI?-7to#=>DNyH=6@Gfni1HDrf%#vyA80E1^g{Zk2udk z6FBhE=aW8y@D<@Dk3(mN`U*2E@EShT73E;oid;23ay_7Go8fw*<#^{CeIIMkNEsWj zCj)A*AAlv!C()plU0cN@!jHAQuz|x zh-Ai}c0)~YJz)~KBO4j*YazG^9>Fif4@X@JdUtHP1j3tme(V(0;DLJBBw$^)!CL#|{gSAL`xw#8 z)jiWhj;uF0ziDFv((fq{<7FA&JzjMD9+eFEyr6n$DT9juDZqVO;iL96yPEcoO9zE( ziN7(0d@L0b7N5@LEYT&O!2_SMAI<31D*k?9iPoG1yNHi?XUpV{MJ)*i4$NeRDX9q# z@wWm%a7;+WFI0ESvm;Y&JX^?qec`jxo{?||5^~eP)BiLHDRf`REf&+d8R;KK5(q}#``LM6o1rm+IPKW)j+ZK zT$M}r?ztz3;f*<|wtDNRJv2DNg$rL8B_~0(gq>nAh!(wibZ2hV1LX-{4qyUbkz9FD zE=PP@Rbhz-2r8Wz6Qz|V76t~q5-%RABdI5f;oH5{8qVJ-;5DuX9j~O<1ORp`9Yl;| zfzgRdf7N3QJR`?;H%3|JaE?JdAYiG>WB{JJ?w)H=#y_OJygbvR2kirg(LKDkNLUJ? z&ZCrssLKExY9^CH^^T-}c)EBu2!6qi<(zyygp#C}E0)q%pLda$hnBrZwqu$x)G6z^ zVAJ`yWCHt7c#%I?Op-o=F7iOck-Fw3XiVk!(93Ck)e2k?OUog({RFY}z|fKtNHI-1 zMniA}GI?pgAZ_+0uV)D8GP6@0(t^*aQy9yB70xxm7~FA77eJWk{hB|M9#t!tXm%_lH$OAlNjkMwe!A!1E?*hwJnaA6q2KK4ELA}_1W7CIr! zHJ9|{TVOoH273^6@<|M-YkAfdiSQIaBQg9nsT>zpjIxH#T;W;701xdd33~6#Dpfpb zCO47W(8=aIm&xFv!Vesu4NIADzbP`^TuRd3bb;w1!;jAHZlw0#gD;9JzJ`ExHQ9<4 zA}OV*9RX2!Iyi(NkjsU!!5sC@U7}RL-j6-|K^hNO`ih6ONBQ>K^;djbc15{S4jNfS<(^jf#@!0}m5*0R#C{q&kmsJA?9CN4JjhcGNy?H&#E^w zrftacxz+jdbFdcV4b)kmZ&nO4PNGo_y{VB31TY+G472S$o6LDJ{Yv7>%o+~N1`oYwe8*@&-6%P=R2==(N zF|etMh~6-G7Lqq|atWS=0l0(|X}d9^-y2H+FDo$spbop9{q|ra zP=~M4TR_Y%Qni-bB%(i<*uxE>+O}43gr920{9?51WUiTz99X!WAJZ=QpQd0|rn{4X zn!*@#G&pguwnmI%TCr=8a;9Fn*2Tf7qBGM4C`5O!C6Nsb zilNA?PDu97J%KFfPCEoxc?&F*C@&R@hak}QAE*S-;dEJfLSV&A-evqEl6ov+G!p9c z%Eibzc;|C$IoA(ilyy*L;(7?{zOT-53|qX~E1pzYYs@G-oX6-(OcI@19(AUOV(fx- zuJTHIfrG^iZ+oHH31~G6xG4HtHX&k1g$)YaQJRM#4B%OLEM@IrGsOTIEUee(Hc9S%3f~T( zsIPO``$Wnkr!7Ah%lkrm{{HlCNO}Aw4>$lD^wJCfp+oQostm0(<;eUnNU^GA(TNld zAnEm*68CB;gd`AO)@-Vx(}YxbOY%aJFTa8TjPj4BOSmpW2cB|GeHO5b6Ia;iKU;y8 zi|`-3Yb=?eevo2$tUKb<84z)h%IztZ2aP^efz&bpNDNCnJ`kanP>6#pl8PQ0S*`U(Di*o?X^Mc{ch1~cN;HM zNhSO*Dbjut+ z)V{o^25kC*<^0GfaR{%x%u2QuQ44t@5>oCcV$`(do(!swg$q#Q)Z470 z7mh&X-&W0S!|+I3#4Qb>z={n+x+#{s^*_bLVByLuA!nem7R3vRwhXh-1SGDSx=7%2 zb^AG4IH4~Py2t%)j~NKyb|{7~IDf46y^|L{hP8U(|8YD`Xef@i90CY(qTHz#_Ux{G z-VlA4LHlR~OSQCqSN&EP82HY==$HUUDbf>A|U>dRq_O4jT7N1!m<~{(D2xwDt9^P@gup7_L&L%ri#dOnN z8U@;-IP(@nxp|=U5`Q1M^M9ro`U5cXDgQJLMzAsb+TiBqfpbVOo1S7g%>O?0<_bje zdBG#s82|nI=)b4=_uc%vZvNd2|4#zNpTk^XQg_78?NEXN&P+!7W_qME7vla49Qqb= literal 0 HcmV?d00001 diff --git a/notebook_ims/matrix_3_match.png b/notebook_ims/matrix_3_match.png new file mode 100644 index 0000000000000000000000000000000000000000..0a7feb93381bd48635230da7c49899da39924ae7 GIT binary patch literal 24983 zcmeFYbyU>v*EWiX5{e9+0yA`{O2;sCgS3Ejhjb%2z(`7mbSnbV(uy=lhafE_sC3tR z&-eFxp7(j*bN)H&oU_(hXFV)fGtB4nxpUupU)Qzwyw*^Y$H$|>!@$76S5!b~VPIe) zFfeYZ;oJqE*d$`+VPM>+u#=I|P?V9O*Kl*OwsW+?z)*OdoQnNaTamnHBbfwFq(qOY z1$PNOWN#(E_3JqU<1IKY6!P?UZ+@tQv@8in6MO!OA%=mY{>P6v-?u|ik=5K}j->&K zSRAp61G46=gYoxtq$9%)oODucWr)Gti$e@t+B?e~hZI0ZI z^;Nu97z}=^cJyTN&%7!H{-2C6#%amzn=-Z+zKt{(Z*Y41=ANmMtrlP`QPJeF5@5hZ z54VT=iIq;dEJ+DTa4_OIm!DEPCyD*uD$w<|VZ3?gSkFp|(Zndpk&}pbFcS8r z9P3#d#=ckY-%P>BPMvL$zuP>sw=K6X|9;7+<(9)oX6 z(O#&WF9**wkG&vs*bW^QWrg6Ot+b&>rV5K6cMcu(-Sh{I;!haZEerpA4HeA!I7$&O zq?JE|vHyc4KDdzcuJ%Cx!-wS7eP+rRTCaYmr;>mJHX@$L-$!`&a}+ZW3`1~HP)Zxbrq5qr+JLg_@vQcn2UhJ+O(7`_rR zSLL-ZW=Pu`am{04GBb`@sNir=#Kr%axYyv*-M_KTD|x%WaHH`5B1PlsE!d)ul@1xs ziEh8XMvU`4q$w34CL>(Bp*F<{I9S?bG{h>fI8BM$MUF`OAU;WN!sc#OEma8)C`_mX z<4lESec?7!)fazm zR`T)g2U1b!a#ixKeiM0Bdh!BPI;8$V`zs35(cl*eO z2=}zd_w`M7qXsSR;0mP>>p#|=8p2R$+o>Mzp)Njbj^X7AK3L1V`?(QIM7#a$`#A

|gRw+x=s>feu)X4A)?dZqfD`aAA%=UV6FSo_pm`K;D9J zd+8ws9D+oSKAL63nWX*%DhT<}#8- zeuzHx&4bkkV=;%&%6N6wa4k*Jr}rMgS@P9N$*Dr+3dTpD3Sx6fMU5oe-Z2dOE5AJ= zut|D&cQ&j|3Y+g`pM{DFdClicUOG-1sjFa1^Qzv-uNZnhot$*1B1J6{Xk zwpk`~4ZOR;Cxkuxg?x!)SM}7S<$+{`>DT7HTbCRmawCjs#LsY~kv?#CGqSui4Ylv8 zLUKX`Cu%&)591XqiDF*Iy1Fn!#$UU?=0F*t>N>|eAD9!oc!PkwOB_^mE_5Y&n8<&#g|jM=SVU4YWhAH>sJzRiDar^p*AmKA3ei5CXu3T&nv(u<_Fy)jX& zEbtyPW$$I9VE17g)L_+M)f38h)yXr}R2x+om5(p~Bvqxg_i2TzOPVP>p-$_QR>~M& zQJ_+4QZ`#+QcTiSQe(etzi9F>`w1^^xWw0pFJ_!(zs)mk4&Os`qIE)_j_D}rJSeSw zcfXXqZ1!FE`yt)(r{a0DU7cxigY?$S?xY922h0cKLgg<^e){}Oi4zs7aG&$9+q2KM z7po(xGpwVw&zUgT3fiLFa+uhjq_bV3vZm4&1d(G>sB%=&#HwZ6XXrFxFQR9067lGp zAj6{#`@a*8lQa`!Q+Ojp_D&NbWs{ZPCXOf6rcFu$wVKr<@_tr)oi-?OSK1I(PG0Nk zx77JKRCU+mk>_pCt|n2BA082h(MRToct`d}w42(8S%=z()4!f<6HshYs60-y(Xeox z))*S;o%zO%>N;FO$u=)yvVY zFHFO09|ue;$SNo+E}rG6@@IFxt##0fdz(%D!zscjRdhhSFRwl3 zY4?T=Z657PkIE|(p4Rdf*@d2##fwj;gD1j&qR#G~rkvKFv0ZeXI_>*!R&L~Mnf{c7 zJg4J*vgAPz}ujF-njiKQ0$2b z<&IsXk&;1w&C`a7x_5zgw03KDR{?a_%-3aypAVgW?Fe)T99!PE>9(4(j;~3mj=h(3 zhceWCW_G5gMijZ;VQ!`ut2}g`c}o*z>&=}YD&7E9zeDS3*u}h((>Z_k_bmC0&bn(z zgx|=n{<+sSO^xuth8Wi_u+M5;i8Z}#7ou7jox|8I*`7zS@za((lzJMzO?qcF+- z7^@g$R-K-}%Q~t%8kAR31j+13x!AYP*p^B%%>AlLPp7n|i+-dm51bCnru^Vu{B-Gn z+py~PPRGvo*9K`OX=-r&b>qs$axxpo4_)C8vrQSF3_qT)Q^h^-U>dGcjt2 zt*fw{|8XQRZOY!ju~;8cE!~n=2CL0CEBMkqNiFH3^uwcIthw5&W_^9_?V`)F^RKzB z#-pXjYS;q3|(fadv*P_KkNFHx;0PLtn3{;!|;8)?upw@ zqkYfTR$#`3?qu$xm5jEl@#6-%UGWjuk(H%bLu;pBP*;V3&9h3y_60E-w@+uN@%UHr z_;Jn>-9oxr5fk6uQJ1Qh?bs{WZVJ%`v^0*^?R|GiY5!w4=JZ}H&(C~PvA-`r@oNd5 z8h$oe_Fu_Fzv2>K>x0&sJ)6p$w{8`G0(#G)$S-*^o%Za7JxBkTk29qz_wa}sSG2FV z7FA5P9ev+9Zl`l_w=HYl@~O+4Z)~ly(CX<^iyIP{4yZK#MC)o4Cu zp_9Fhg?yG;mNj+HY+cD3->Hd>#p&ySt%C*f4sw~Ru3+}7#q!xa*0kqJN@Qvz5+(Ek_^!rIAFXa* zEq_=xgBo?F(B9$IA4wv?Fxk2FG6iG!!7i_~BcMCT=R}VWE zXL@wIX67!Q9^x<IiWp(m#Q1I%lh>(8j zbL)2&?j^p?O;7tVr-2qpTtcD7F)T@*){ir;&`06zW4NqRkq#57I3X#!ED;=+=_#XK ze6@Hz;%N#elR-gn&Y7f*iF;-9VCz9^JEgb8#?e?-x~Tj5;6?P`AHs|7Y88?MJalbZP7xBq`+WrVp9?U(yeocbT7g3f=j z%v4)Ntq$jys)-*x75-B@UMLqsrB$i{y?E{Q`#ZGJeItd4N$GpfCuvN^C7xS&@vva< zH^@1()f>;iZ`Oge9dYnEcf5`j%0OxsP4 zcNTZvKl9yRjW_R#G|eau^JM~Drxlb?L&el?UT9{b~)Urf$!tYbUe$+cD`x&)Fv1I$?C$a1F z6Sb~?dpjV!k(qcXk}ZWP%_0TW`T+gcknPv{lldLL^G>V#^f9F>Y)_RN=j`;n&I#Cm z-LltBo$X>=s6c2pQ5t_#;=0_Cy+2S3;WDaMY4YCTKG+ymZ_f0ddl4aabvSNkTIp$) z%&vpAW7YJ|ZLNR*^kCMHR^MD63=c$-a!f?JU+(n`h@Z?cbVZS?iw>*cjsBjgDRTNzUu`p- zjoQzn{{5k5C_%(?OP|YlG4LcS;QDN%+GVi~bug$lU17WyMsC8}+!ko^qKlN@ci(dO z#AUYjol2(AsCKI_Hh#;;8Y7C2fn;%b;lUyj^pt_KLsIw3(u;SJE(KqKMiN&A`rw~S4=FY|3J52L`J5+E9n^H%P)_&8L zY2<}6JuYRabrT*x{`13Xb0lx9Jvdl6;Pm4GN8_*Jx0b8D@q|5>-$M$AGaf0EaTzkc zx{F^OHBxT$q^$0N=SvDUkCWxru9BI0_3K5s$@e-|>x1e04eeK4=Nx|%rERGrjc+Qh&>Ct~?`YSxP?%`L zjNJU;(w;n9=P>YhzWEOuS6@oqlwrGobW3?9jMcQ@m zD}f~EXgT*LoL#TtS#Dph&b|4NQQQ~Osm5OEffQU!o6eyM%QLCm#`eJ*b5f2dGjI_~k* z%4Oc6{04Ry+5omvQ`T|rQx&}$lWxa>G*JqVY0$*z*Y&sphn@ZY#Wi1^sBr7aI6&hYWCk{moFgKh^a zrW4G0^4ou2el`S(wNfB=wtp6y(ti@u4$>Kc$DR zV@kH%xknc5<2Q(L1&+u}Pbkf^hw7;JdX?Pd#vbuk_tU;n!D{WsnWZ_tyR=_f2irUz z2}Jq&jb+_2&_-oEavY?6e)DtKW+W_e_8#F=KUVRw zpWAs9)=PQvirTynzjMISXbb|w>a`HeQbfi1FNI(aa!V@GfBeU1$^f598Tw%a9_mb? zfJGF9Sg8xA|Jxbef#>F0mjW?ET}xi?5vaR_6holB56$hzY;ppbJ-4SGH5(kfgCa%? zzVU9k?k;^X_=+M(Xhk>asr;`dc?BMk`O3m+aHm2$#t5p2EPER}Wi(!u)geozLNKT9 z_iet9{0oonVwvJog~D6Y$VBGu5evdFBz`1>L(H%q8cwL}Q@L2}FSFoUW&nH>&x6J8@}Jeo2p(yuaKf3vNw z3hj6%GbF@P8wz4xr>8+|GVvoMPSm`}&o1ZYKaVN-Eqkj%U#{KfL)+DwHn1yeU){7( zatEVyz#fm5Rkl$PBC^VFZ5n9*3V^)D2`&WrUeA9&J-JNIcm(r*G2GKYPEl|HhcK%E)4yvl$hmv8Qo zbJx0rd9ICRN&gsPd`V3ON7^xFRu5}h-}+!n#>RXLPg8~S*TX_cQ4g1+o|!WgOIx)0 z`Iyl?PZ1+oUflk{rlG-Kv zO8DXWTNgZCjX1TEoS>%XL2uE`o6(knaP+-lI7hO*t`M(3S+%bW^5;jxa0YTOf-dOe zM=2ydLWR+l6N2_>MCk1%vw7sUs$fuU`T7WnddNQ@^vQQsCgOoXxiYF<8qd(RP7mr< z{bFC}Y&B6kdq7Sb^_lvE_wVm!2g@L1GF#lAGY=g;kC16!8bAzY6aQ1Fn79fKXLTj6mTMh3v;)?#$uGkpHMlE`#5)ke88U}0Mn<|yLqcNb>7>wcWRO{ik>{wZ4xHAvu`S5q}4 z>`!~Qt~>oa{@1ysSl@{c8KEx{{Q~Q}RdbVpoXLE}@8Bi^M{YEFfXy5FIXNgN}E zbq3~jao!KvuzSA7)PsYzY*|gTW!vcj%k0!!>e<~Yt5n-o&PrU)y7b6F|^`_ zh7HbvPbo98KjS1i9B)q#`*|H0zX^@iCWUb-MSl(&6!2L3eCt3#w`_wYCxEE*WpK0W z9DRroa278<7rLjxlOELlZoey;ou)wuLsC#C0{p;2%As3}`JPYPvs!0(biUsI#5qLj z`9e0OfOXo34|Eo_B&6KWzDuMeQt!0iw6y@dK-p*|QZB=)z>LH>lk4eQqA>KKZ-}9K zRBf57l+2#+#KolS5pG$l#h)5Ji*A}p%y1F+yL}8i?L=g=S>PhFkl>-n(~r@3P3C4an1MJix`uLQ}19;OBU2Sj&7uI!5EtHX{~iCdmS` z+}i zO#S~nV$;k=v}n8i7Fwyv_Wvt9d{SmmQ#=`l!th`w5}HDjK8I*D)=;j7Z$s8Uz9wa* zxk6d>C&gdl{`n&AOPs%gS`hbVIq5^l=N)nbor$JsZ;q z?MFADDR%_eOQ-^9veZQ`Y;mPiAx%$rGv5`8`R*&U1)O2MkUKRi*C|WME&8&JOiU+( zi7~8%@>)DfDLdWkp(B>?ns4&tB7}>VjISi~y2qL3hY^IFOWs_%k7bVRht;joYx)%5 z>2yhQb;tMEyeue_|HBdWy*l00I$m& zDU8ihMnLx$dU3Loy7#jOyF??BN%{((Mo7bYFb%5n{;B4mFlgv#Qg5uP+v7-HYV zJ8dqzh+TYIF(HaO7Hrii=#K3{$6v$oiE+Mud4*kb+~aZbcm5v)do-b4^!!U`q>CbN zwTQ>&8^8rLm6BMYESauwUc91bk#vg`?DDZR@c?vkY|upi)(ED4?Dz%@T`?Xe8>-3L zrM0v}kE{fE6ixQih24wI{|-iC#R3E_lA$;&-vL9M#;1$~p)wh)MsXH7596&T;FvkHHj1%u!m((gp>AhdScih$ zkUO>JTC8LiD6N-W+W(u+xICbE`tVXjNf$m2@66q@EDSvpfA#*A0nf}cb`X~SXo0YF zo=FF>FFXFQA@Cr9gQ4(MhRZh# z8UGQ*C2VA&`;C|+&gxLDmzx%3Brv+=h{Gv>RU%6ORyh>rz>6Oy&2{z1i^oLJ)#O6X za3l*GiXm*eVgO_m4;yHr0xB@Vet`by&(rRN^~#?@TOWZvT|rA~fkTn-Rrforb6_YK zqdlBpC>;d9%E2X4&>!-byDkYR#F1J)CV|~SNmxrm_X&d(TvUNa@j-i(oSJ;d*|S@C z4PYozhkW<&;^TH@{2aj4yUwE%L9;g%8sUK*E}5UNa#2mOEc|> zOLhPdUND7U6pyP*8UtH2e+PoV!N)DH;xJpFEb;pvJKpL65O2n6IF%~)5sEp-uM;M{3bJp0?U&5f3tgfpwu@ieLyO^k7)hw4!BSDFk6#0#u@D(_k)(?&$be*aY2Nk1dx0j7kA?u3Nlkn>-zoA9!)x z_wC6dCtCQ>Zvnu$f3+{M$Z@6`clco63V;*OX#kgzJIsckfkq|myO|fkKT9PC-QV}x zo%H~bek_k{g9=ZRhL?@@K`5`K5(>z8Jn9l>mYwY_A=r}qw)jzMlVy4hK`#s9{O5<0 zAY7(x&wz9hG*Q#(Cqc74TUVCpJ|t{H$FhEu4uSP{7`e21PF$Ibn-(c0e<0)cAhm|s z-5UiXL+eeU?ZHDN+cUdYS0{@+aT4dH2Tv!R@Z#Cktk**UIl05wP@;jFONb0M(Lh4A z;fMPJJOe9TQBD{r;l|yLdqqIG44uNhy!UY47S+gyE7_r%A;{Ovo&$RJs1pMgF$M5l zzW@DTcHIxGKnW@51WpM!pnC^(B%Jyh=f{5>y2xg*lONZHZ31i?FZ%b>S8n59V@2hE zs{oi%1YQWh95#OQP8@@J$MnDlZEuBMp4UE0`qT6RN!&o@ttVCNqKEwf+p=1l#9>Ki^@JhSThS~!>-K2z$32I^U;lB*$@ z95KAsY9Iv_tXZ&z!4G*@+O%9rKYw$%AsL#h%ZOzlBe)M#&X>>ZO|9T4dMvc2Vf+S& zEXpNnS-%5o_yDp>mke&3N$!rJ9w60wX13$tWQ#}GbTe{w$628?3aGkDAjH8UFMvH6 zxm^JJoaq_0*R8YH@i%pl0aA=80Gka_x`R+RsrS+{4&!ibQQrDK3v?r3@YZ*}6hu>KhU*UEk+OGk@*BngTd)dl z0P$4#WX}ChU^!T7GR5_uAUO7Nv5{|RqMk`9xhlOuN*kf*Z`tCX1C!&zM7?cXOEFp* zlM*Au`U%cf-B5_oe9*<87sKjD_ne>|aj6ILL?Jz>;K)?n5Bg^KV6LX#^PalX88g$T z6C|+c$hQJEu{s|T)kGBOcPKnbd4w%ucqcytr^edz(EC}-__#;~6JmS1k~}(AfX~r; zF|a{wuao%208;m3qPm1ZERC>vmiu;jLx0V%1og?KM|&=$NI7=8Nckl^iMx?vS`%e1 z^}?6cwj-AdG1@VbCxgx5#Y&hB#yY=M|5?~pv!j&ulEQ%z43+@4qVi@W>u^F_or%UP zHjT8FJ{@{ylkHo4_MMdK}6{WaeZD5W*_2zji(82#y^0IU3p5z6> zsi+O%$feEetBW5@QunD>W{HXb%9WD1_${!V1kwUHO*nO7aqy(lyQjv?XkzeA3pEx& zDQGrA2E!_VrF1ez4@nP5=hs^H5m5@e6$UB+0$C&*MRweliVt)a4l=_Um3v=jY;w44 zA1S2dr!UD*uO_&6hnrog(_RqSZ@WUq= zn4;+1R;1_RUo(hof2jE&<+yS3%h>^suo&<2hi+!iUt_ZGb;_*0eg?_)5ZYnKI^Yr- zdM^au1a1qfiR~ez@5j8w;_K`cErf^R&Fon?S9rR|0vXFL76i?ihGrnMT7G_c>o}+- z2EZ}dT32hBYbKl|P9n}G5`f2e&M?-%G-=|yK&yK5$E4M7#+COeWsP97;${V(%qPAb zd~2L31+@0FK~e8Sw7w<%#bEXPK3HE^N*C z1eciB94l29$bT&xw!VV6MK3DFnlC_CsFUCXQsSK3*^f0-u-Oy-?k%?j*S}lKgktCH)@^YJQvNi9+T>L4$_0 zL8oRHf+`ZY|7;Xe%4-)6=jB`9c#$|V8@hbPd2cx${L!t$n@a( zp}$dNcZnlHx8v0hEq$36pGrIm!$~{}1Sy*1KZNQ;;xmO;MHaUOXe|oU6;X=P4e975 z=oLXsbbhb3pHu>W6QU29GS$u<&g-{ z^Bh-wGEs$Y1A0RgWtw!fql?Yej;@)M<6Rc)idT2^~z5{HL#( zgmU0X7;2%Fp{)0Tfm9>_QyXG_qx4}Ah>iAK5PNQ~ALr&naVPKC5ToV#X<<~7Z~BPV z>D`9Jku$MX-^&LBnP1`>Fm z*xw4&R8*$3A~FQiQ>lT`JV|<%EdzDKW#6i$$9n~Iz7+EhveraFu5;JC@WRj7 zZ0gx!Q$&!o!cm7Y64?DL`GHWyII(?H5~sn~@9rz4wQ^8waXLdIiYFKm!o#J(lVhm z!p&ZP?u$EWpu|4^EV3g6jy#O(Se7LJ`?G|H`1!mb9#5UP4UH!B6G&PITwh=8a3O8q zeMb6Ffsn?_h)!=*MTOaj1H$&_%7=RNs6u#;@$Wk(1O=bXiiOtMa*u{bEm1mhg9{MG znWIaff71mXY9gKy_x~u6C(yHUk2F76|5l#7V{E*+&xjYVtRbcv1I!qo>DBvjSxU5_ zz&d-MKjg3ZZUd4ISobWO6e+Uq`bx8#wgn(g!ryNH%qjrdamRAqv`LLi7~Rt+1@Cw@ zp>m_?$kU9?$)Df6e+Ag%5@Jn7=fcZ+0n{)i#pdF%4Uz6Z#^Q{~apa&kQw6&8fuq*G zF@4qy&^N;Qe2VmNLi_*S0XqJd4O@P}NAfUn23l0@tOt@@cO)egY_mSkJw~q!L?h<2 zec~MVL?r2CmSn#EZ?Z&PdnPVu$M0{}u@7q_rIzMBjPe~ORODZFX*_XR_;6&aD?tUX z^*{NGYK*6^0rkEG9Q^S!^U(~A`iZtO3UV3~@6HU8*9!L26;56j zLFS70qwfmVL>K5lBzH5w8Pmvy$wq#!v7Ii}l;Q1#8u>U z^};q^fc55BrzWI8WYtYJi;Zyer$4>0XnxO27w?`RcX5vZ9npQO(85`2r|O5q8c_&& zg-NR+uT!XXKE!U!(!?3W@i`4Q-v?p6Or;bhcT_2A!*DP|3FWPa9$xC3<9O#I6)E`S ziM2-aWx33xI3s}%h9zN3Pg=L~{FZq`bO&OAs&~JBYyB;wF&hFoYgPn3PSIk$e#>16 za6aBjF%c6Egpm$+-T`v=TbM(9WeOzkZ)PH&IaL_7OABEGDxLl_Ui6{@2(zvjdL}wj&6CQ1FE>P5RWRy~0L*`9NC4 zb$=F4VjQgc_Hm3p5$5o3m9#&U?hEwN6eEVnMn}O!(5(tYOu!SfkVG5$ym(z7SLU!x zx11fIvxc_oi^Usmif=_(wMd*V*AR&sYg#PqwHR`iM;6`pzeLY7$2=6(1^IYOuSp3u zggt9|yfW}Tskw8Rav-F$T?NUp)whaH+qEH#p9~Py(|}o@Jgm^97scQC(l2CH`dz zV>Nbb;Wh>y+M}=QQ!Je1wh~@MDC2#=i%<`Od;q|G@>U6Ws}Ds)DtZ***6JC8eYEQ_ z#C)8yf980-x)mP-I6o>7YhmTB@(CylU?Q|KeLq}E~aLUwQ5b-F>vUtBvU!= zUiZxBA4T`FKl}7i$GWyQnt~4w>L;8fZ#SjzsBmiC|ngcOgkKSDUq?`Vru^C{<2U-*BSW*L|NF?!|+HybQ%Bb(qw9y`sNJ==5Ia| zJHXw{nV-y~Yd|l96Di+dR-8}|$;(7*Y4Hs5%v}7%@@ar%ocHo?#IPaMsOE-n(NL+M z1okjZ8WOI8N61>PU)7P(^?gvASoMLV&}PC5uP3rOKdmIS;Yj|xA{R7;4ZaQ#Wt}t7 zyCPuysq86A4DOJcBkM zJ(=AcEtq_@RInohjb!e)QbDKviHae=z8o4+@N*&P!_pF&lv8j*qtV%~zFTCnUGH~r zmcCi`7ednAWRB64TbiDGrI>dbV(lma%3v6**VB| zjyJ^cLS>G+IX5^;izEJe259CMPt4Yv3NL1uYKPZ9HgqsQvZJ}{6)y3Kql0ZYYUZ*s&ciec-9!3kFprL zqY@{w9E=_6M?1A*_{lZzC_ukT%m8o8JY&9ptJRSe*nF+f|<+Ly=g?|W(fcgaq_ z|C92l{D`9o0)F|?dq9I>>4s_ARfc2zcr}(5sucle`?zFVgY{Sj;-F-y?itP$*+BQ< zcCFrr9(MhY&mT)<>jam61$4)1O*FFo6^QqwoNm$W5qnct1O5GLGG~AV7NW#C;mEp% z^`R^dwD|bgY1VS>CR(rD(5ligwo9ajYq@8wykVhwO0^8|qI&V{BQewaO+5+WL09Ki zYX^DP0OPQrOHQUV2kIl9wffafo=+AjlRMgve)a1_tvex&--87H*5` z+IbHh5kCPop;d4EFLj)BJS$qIcM4E#sCMKgooqaGIX|i3b!TJ+TiN z?4$bWA^0I4kbK*ZcU*2m@rJry%R>=rgNDxsA^B3YYCm2h8QX)-wR^eR!! zp%8;ijF4q00u#dZ8xw8`4M=@}4<%9&Ln=UuyNsn#oN6yW??Pa=P-I@Wpw*Bz<%n+A{BV|ATa4PGVeRk zYN1=0UqMs!hTrcXd*P?t52?Vk!C3!qOxr{Yg)Zm>sT$Iv;^KsbwRKPhA_s)>{JBPV z2T35kDC6Sdh6=Pykxub9cX#xXZ5&U;lZbddm$dqHX5Ft>$ZGX1`G-Pk)2v}#%FJk> z|K@I+WT|D*RuY>snQ@hz5iTf|685is1({(WaE`IIJHi~L%`@Fk~2u5}{rGuD7ZAA6SJi_;2y$BF?6S~;bwi!JXw`q&i z&6y87*VSR5NVT`O)TOuXdnNpwU(z~n>+yuR8;LofBJTTGPR4ba^#Sb@7dFCKLWd=f zV~nb;N^rNmw7QK36Ja>Ky2>wqT?{$-YvqGVdw{LdVm0T1+=z^M6?5jrJwI5?r3CKkON%hIc>v zna5K46NQ_^cfbYRmUSOffr=b&NSr%Q$=CXJY0cJHG5A)P;8U#(^hN!uu&)cvP87m{ zCgj>0(?MX{-i}qKnwZD%0VIK*1Zm*lAY>wT-xx z4xkY79mwr@)ST!hdhjWuhH}JZWHP3UljcQl!`I7vzc|O}I)BgfYZVp9t>}z3fD3xh zQ40p|Q#7rR&bk(Dtg3HjcN2t_ksbNsXijW8jzZv;QG%b?alkm5ijXK7B{j8Tx}eh# zZj%;PGN`2KVK=o4>d+e%oB;Y==B{xH15tlsZgTTD$6_G*R4?ib6>_`8A#ovg>N77OLFa`Y~UsI75E7fZC?*(9%Y zHrzfU!HK6;vYQ6UL4HuFkwM1&OabWWip}{l!%`@k3?b|Y^x@7fguKw_lo>BkF9F#P zCi?zpN`bLb%>v9=@J)I(|Ech8Zln5Wea_J2<*yMA1jg8+8)NHlKDA(El0_3jaO=8*>fM-eO+0+t3z7Q zbswsvsF-K=HT>O(+iI^gkmB<}y>FRC%a?|+1yF3;v~qQ4FpRD`=-}HEHkt7Ivu&NG zU?~TedBzj$ZpJ#4QzgXlPdy}(+ZxnHTpMYpPm(vfSu~Hx$^)CjgEOEA8WKs-!?n|R zkF_ybc9W{e$L+`t?-8W4lQRm;{dNNCffNeX5e7VR3I>vu6Lv-V8-}=)a%&H|O5!~o zUW%8e9i>$`mL&UDPYD;a8KU{KMb`7Rq4zYsq_eX#cX>~Tw_=9_C?tjKIu*u&dk^D| zOKKr|PK67T#FLYWct=!P?RGgOl(drS6B`R!httb4h7ZX~LIlW*4J5~it#LI!3O(4F zk>!~z_xZ|(Gm5VO6TyH+;*hhylF|q3_z`ue_c02xS6)PJ8o;+H(-e4qn3uq$yp{8m z>QPY`iYd18ZkD*eE_KzjMz_N`_u(Qw5j%HkF(2M65l^MEuPYPH-aCTZ@_ukb)YQ;T zX7S-xX@{+(Bh944w80`Kigl^4y2fQLze#{%W%Bd>P8nDW89cbekVJNIThC@UxP1=^Fj4@gi}dLXe~tiLb8Fd zJ!PoKyj5<(d)86h%y#(USvCW^_D=~aUW=R0m=8jW168l{*qAOBMq}5}sx} zO^`)>^y1Cs%e-1h)%V>R^=BR8lB+rVL4SKsXf)qx71d%_T@A}ObFm@FZzSZ6`$;tN zCj3?vxA@nM>*(w)Tnn}iH)6{TlS7$^>{f%}!Sfjg+%Ag&Y& z%iTf`y26&iZJYseM{=@ZQ-X;ku=|0|EZj)i{|?Xc)B2$&S9+yE&5&)Y@*`OPozC)6`@;O?@H3gZQTGS% z!ia>{Z7J06EIP!77$(3EnKK3`pNtkE_C>At&l;@C*?tb+-elWp zIe2gd862i+3q_$LHip6I>r zpD2%z)dk>%tuPjphzURT#F;C`q6c{9buu_dmCo}`?|RQDsjm4S1(%;6{u-}!`tf)c zB&tWI|DHKvJd0@-{)@cj`HP(_X}u<9A-cewfD>z={`1w2BYp1m*FbfwFn zWc|f^ZI)B=Or4?|=hJq-;RqkiIV2LfT_)SY!-jaPRmGKz2NVgt|V7%Uv0B-@RnOxEDT7MC?3clw=CCU9A6h*sf|{y8QNJQ$6mu-788X z3#a-EtP9??R($`-cT+pQehE zUl(FjE;DN~hM|7`GAa;6!#lEB<4K3{fgG+e()F6Pd!vgsY7U+pPi*B{7cNxvnq8CW z5z9C;H)6=%YhgHY=%1cbA0knb&b=<_V0w}DE{n7tUYzg%rm#h!sCr;kJ)awPeSQ&g zx14hFz{^T@levSw#zw7h0`bOk+hXg{e2>;e>4)`D8DSx~-cn9)Jb9;K284-06X^(BJ_ z{mGVeqo_da8~Pgt0-uc*PN*C&$sX<;nAhgv1;2@}^f>#E*6pAf;`TUPX>O|4CYPtm z?0U^Xy&8=Y4F}04J41#2_-`+~@UM^%$_|^TZV1e%@RoEZA$kn_MM&01kCJ^9@!FCZ zjlr^mS`(r@`TH>>pv5OHw1FU1$zBLbi|tLPb+JPoT?3srH~_<)tROl&j=4+%!R*0i zQ4{G?HENIFYU*w0y1kGyi`Yv|d2ZnY(%csg_5O4;1YED7qKBfm(EV4-BdR#@GQ<%d zos`j@n~Dv34!+BQ24JD_SK~TEnZk88p8Ao|WOmRM(-BZUqtl_$;B4%7I4Um_-k0-W zDNe+#FPxaW*c5*k2>{#|5T(!6jjzU847zECVh$_02-4G;W*$=!*1e znsL7>JyNc`Q${t(hokxQyW=Dd+_KDrK23-oZSeU?kiF^RGrV{gQt#Ig5EskJ2~x!U zyI(n3RA0<-r~2Hdzt-v*DzhSo1daf3Q}&%W25<)fX(0 zpe6ODfM2uaM%lx8lGg?%U6Q-VAI>F4)f*ZSKIQfKYdggee(fp-?s7Mu+bh3X$@tq3 zr!ujyTs-KSRL9YLRz#dwbql?pM1=sM8`jzq?ztntDkK=$Tn;3r9xoZ0qp4G8Ct{^b z3w$AsM@XAr7WEkLY(+&y_96Sp_oJYYqx_!{n-+t@;t`Ozuagn8X=`HK3$tIOu*M;b zy4CKK;&)lJ$n67P$NnJ^1lpQlY28$w% zUp5x97#+;=YcJ$$sp6ABg2@Q;TTndNp6iX%8-k3-PD%`N(Ydh?E3YDuR}AtZ;%73U26J7HRDcSfWd8L)wd)(13H{g%f{&5J=PGnZoWJ z_gwtasP2C2&)O0F@V?Y5MPOhrse zSwn?N36ZStf9k&P_kBM+-<}W8hvzu14|8zLd7g7#|MS26{=eVp3=`$wT2ap)y*E=h zx0adlEDM*C$$4RHAnc_ub?mw{QJ|>l0KYV`#ynmzItt+_6er-hFJ^5Tj`g*;sw+~} z1#Kpu4iQp=nwTG^GM3Wn>HFrkr~Y!yS^iCn!R2%Lk6A^*zYrA7ye0wG?!2qIH zEO#R~^n&OK4zvQ>kQ5&afXZeTpwcIz^5Mar)QR5eq?FADWf8^H&B7|K_jV10ja{vX zae3{mL>{k0J?i+-MEL+4u1C&Zg>EKA%D-Zt{{I7P?*0ZNn;1OudY61lBe|E`kB2;m!#WgyR>c}OwHwY|gP3=eD8OQA-oGfHz z-xVD-Wnh04<-RyQ*|;03V(n$)>jyCJPw<6hEV6NMb%Hd+94Ix_xV7g$;vm%#+v0q* z1U%aH^mJi=s$FJms4(u3I(dvS^DJ#klCW;y_u#1!livI13?ObL&3}G7<*-g_7s%ozqE$_}{Putz2)A1z-Zb`hlVYBt?bgK{~ z-J;OXNC0VtSk{blr5FDcfdOe}Hyz|aVIa={k0Jpz46#0~=l;3X&>Y;ThXzcTv!z*n z7*f}Gi0%9NM3p+bHw2sahrfgKB|AILG|q8tzdWj3RiZ8XB)mcYV5L`k@t!PYpcxxH zDf92Lk6*WmQm&5Qy0uSM_KD%`%xwI=@0{#J1h=F}NrW<3!LP=54O+2Jfwpc9L3q2a z=9;KHNLh{U47uq*O&Ua=h>#+9PA-inu=0ArPmz&lVNuIBExS@NWXAe94sDdUZg1}i z)RkuV&aS%n7$!R_=iIk%TJ$%K;-!Si*Sv~rOkWcS;HMO*nhc(*k^ zF{21h63>PV>nGE17U~e&`Y|? ztCNjT>0JYJwP`gPlOB>WyvM&(B^HGm#}T&~ui)%}@t*qQXyp9+Hc{N9!iQd!&%lD~ zZ(`|^XWKyFd}ja3CIV&2S0`AK@V>3#CoK;7NgDFLi5<$!UPFfdwA%(mHkbq-==n@m4d;6CFg;TBL=la*(<}n*80gZjxt?2o zHM8)!{B%I$Y!d3v2RF|(-25`*B3=R0+H!OrCJS}M${2+eBYZ!Kp+ z1GjQA9)Tj)^9R$GB6rhM=l+()2bD22ba z1>mB447vzNYA8dk_UMa zTqAwLc|F1NZU1<}+Fy=}1TMqug=W4A4|*A{V-VyPr9nqaJjK?DQg%+``J#N(Z8aE1 z+wBnK>im7Cft*QnO5!&*5IYsxWbMMNTj3ybn5aBtlQ z3BNj9iD#A|p?NCwJ}`p|KvfrKoGwhO^zJBm-A)Y z?wp{EF$FJ2OC@0e{>(pOk_29XY$Q6WnDq?<5)#Jk>q?F`KU{a4DMg9*=x zg?->nV$6v^re$Hpz@S8QYKYDQ9$eNiW5Sw1=`MFIr$=2kcION-L!Iupn;4!M&fGdR z_St1dDR0|MnjC`TN$j43236*LCKDx^^3Wpa@O7$S((wo$v~DHiWPRfiEkOP;H?jeN z2zUx0Ca=$Dmnz#+eU>69F@Id=-9|D)Jj`6t9Bbv|8Pu-?n^?)|j zK(Q+iT4Ga_lE!s*-!iz8xPptqG(ZP#%+F^I<{8C=w6^Y{&R^3TnQfov>lTDFWW?;% zhch&-t*c9B$Qf0o0;VEo?XFf9XiF)(@6z267EBdq;W%4HE=sKQz-12uYJFs4l87FY@pCU?^yS=#>)`F(sb;(a+%5K}&fEdE>~h_^_x4{RzDR|SqXS!!hOq-k<4UIs!+lgjDbe&RwU=xwA{ zR0~3=qPP{O6VSAKdF9X5pz9DzN>-o){^?I3&fk@-gBSG6y<@5VHU)~RSgP{gVmxFu zc&trDDd-qWT<56f$m@>GZ8`B-O&1oFJ7$9~isHWJW(iNVXXMI{^u_v$h!*Dr)S0~+ zQ{4Nn-LJyq+0l;NNqMKL;)U2rp;$MApM3KGWd>o{vmQE8HGoM7cSvyj_7zO2jWP0iB>{r6+%Kxf^7(cy|Ok z)Orop4I)r!x-*}E>|1bC=6z80omO_ece9W&^97=a;Z^A5m&{lLSSX*?JqtBZN@FvF zb%4iZ1R?g2eDce1ZgI%*u_e_L0Hbn{w?cMv4I)kW{36j2Lc2~D2zbAs!6KJBxyU3e zJ$8WATUJqp_AdrkN1?z;!5u7~ka5dIj;}8DU{<+r#tg{5_voqg{-NYj_fjlv+yYCL z+rLLQ262pb@w3EIH<*XeFG4ujDrd`wcPm7uNT9iI7*_0CaS!=1aOyN@ctkg(4@t3# zEE85&oLxmz3FgMvG51Mtdi{NcX2cdn!Tz7jS$fiVK%n}SbI+oLUi;OZSqI7vnt}hx zhjfej!zg&Vtkrao^VsZ3A-l$G4R33>u%znetDBA*CHfQnK5*g8OZYskpK1bJfy&@w ztJZE{bWH1KnLc^)#OlZ4vA98t;7%xbo-A%&Z(F!md}q9bogV<5c>p7){W$IM@{UJq z@q(cYLLtL;2s#y)Vw(w+mu{kduvd}FyYOVv2A}iwF-Xe>oj4hw(FqWtM<6Vs)u;JT z1lLE$eKHJ+l{*R@cXlHPOVRER9t44v10c8vfZ*UyrqHHe$Bd0_8UDQBm|w`k>NcD8^Mu{orV$mlsFdRpkXDfmdPqrnlTw+bqQ!Ptolw}(oWT78_9353kkz}y| z)~;KRs$)C3+EJnwiJd@E1S`60?mZ#PfhKM`rePY5WE*X-i6BtipGg+97(o8MH1;N4 zoIu(8K%#O#N>nrU?=?hlU7a+KEQsTOYEGFJ4nQI0RDW_C#EC?faBI43e;|FQbIp;% z4wYWXTKCyGdEcm;$`}C+;00)si~(<@0rg+}`~vpM#KR2a_ZuN)WI)Tk}Gb{NW?)LPw9EHfpHb){MuGO?D#G-`!G54 zWTdlK=7^a1#@aTbDoqHngn#pLY8hhI{C@h)F@*QsZ61Tt81~-}6n3QiCN69w=GPPH z4EEB0Ijl|cuWuL{LJ*V}euH5{em_Bq@NS(oQo~|?e*=S+gBTFYF8R-p0j&3XY*a1q zuWw-3z&PoaZXACP*>nq*70|wGl>e_qNauky3G@2>wFnIMZUz|Mt?bqYqrX=!46_oV z`2QKQmi4Z;&s@^}XXOBKTg%;M`+LZt1F)=GFR2->zgPa>&G_$Tu#EIS#xlOG`W`Qd Uv&@^;1C|@c%w#{YjNlgiKYw?`nE(I) literal 0 HcmV?d00001 diff --git a/notebook_ims/matrix_6_complete.png b/notebook_ims/matrix_6_complete.png new file mode 100644 index 0000000000000000000000000000000000000000..8fe9ac44f5bc98691a53c548684de90321a6ac36 GIT binary patch literal 24956 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

A6L<#^@WYR|- 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 literal 0 HcmV?d00001 diff --git a/notebook_ims/matrix_rules.png b/notebook_ims/matrix_rules.png new file mode 100644 index 0000000000000000000000000000000000000000..778a5897a5583c5ab041c3927b4e7b099c9115a6 GIT binary patch literal 61292 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%0Dj7~vMrYCFd|mlP8&+hZ0=mmoa8t5c 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 1, \ + 'Unexpected amount of test data. There should be multiple test files.' + + # check shape + assert train_x.shape[1]==2, \ + 'train_x should have as many columns as selected features, got: {}'.format(train_x.shape[1]) + assert len(train_y.shape)==1, \ + 'train_y should be a 1D array, got shape: {}'.format(train_y.shape) + + _print_success_message() + + + \ No newline at end of file diff --git a/source_pytorch/model.py b/source_pytorch/model.py new file mode 100644 index 0000000..21e1db7 --- /dev/null +++ b/source_pytorch/model.py @@ -0,0 +1,44 @@ +# torch imports +import torch.nn.functional as F +import torch.nn as nn + + +## TODO: Complete this classifier +class BinaryClassifier(nn.Module): + """ + Define a neural network that performs binary classification. + The network should accept your number of features as input, and produce + a single sigmoid value, that can be rounded to a label: 0 or 1, as output. + + Notes on training: + To train a binary classifier in PyTorch, use BCELoss. + BCELoss is binary cross entropy loss, documentation: https://pytorch.org/docs/stable/nn.html#torch.nn.BCELoss + """ + + ## TODO: Define the init function, the input params are required (for loading code in train.py to work) + def __init__(self, input_features, hidden_dim, output_dim): + """ + Initialize the model by setting up linear layers. + Use the input parameters to help define the layers of your model. + :param input_features: the number of input features in your training/test data + :param hidden_dim: helps define the number of nodes in the hidden layer(s) + :param output_dim: the number of outputs you want to produce + """ + super(BinaryClassifier, self).__init__() + + # define any initial layers, here + + + + ## TODO: Define the feedforward behavior of the network + def forward(self, x): + """ + Perform a forward pass of our model on input features, x. + :param x: A batch of input features of size (batch_size, input_features) + :return: A single, sigmoid-activated value as output + """ + + # define the feedforward behavior + + return x + \ No newline at end of file diff --git a/source_pytorch/predict.py b/source_pytorch/predict.py new file mode 100644 index 0000000..faf758e --- /dev/null +++ b/source_pytorch/predict.py @@ -0,0 +1,80 @@ +# import libraries +import os +import numpy as np +import torch +from six import BytesIO + +# import model from model.py, by name +from model import BinaryClassifier + +# default content type is numpy array +NP_CONTENT_TYPE = 'application/x-npy' + + +# Provided model load function +def model_fn(model_dir): + """Load the PyTorch model from the `model_dir` directory.""" + print("Loading model.") + + # First, load the parameters used to create the model. + model_info = {} + model_info_path = os.path.join(model_dir, 'model_info.pth') + with open(model_info_path, 'rb') as f: + model_info = torch.load(f) + + print("model_info: {}".format(model_info)) + + # Determine the device and construct the model. + device = torch.device("cuda" if torch.cuda.is_available() else "cpu") + model = BinaryClassifier(model_info['input_features'], model_info['hidden_dim'], model_info['output_dim']) + + # Load the store model parameters. + model_path = os.path.join(model_dir, 'model.pth') + with open(model_path, 'rb') as f: + model.load_state_dict(torch.load(f)) + + # Prep for testing + model.to(device).eval() + + print("Done loading model.") + return model + + +# Provided input data loading +def input_fn(serialized_input_data, content_type): + print('Deserializing the input data.') + if content_type == NP_CONTENT_TYPE: + stream = BytesIO(serialized_input_data) + return np.load(stream) + raise Exception('Requested unsupported ContentType in content_type: ' + content_type) + +# Provided output data handling +def output_fn(prediction_output, accept): + print('Serializing the generated output.') + if accept == NP_CONTENT_TYPE: + stream = BytesIO() + np.save(stream, prediction_output) + return stream.getvalue(), accept + raise Exception('Requested unsupported ContentType in Accept: ' + accept) + + +# Provided predict function +def predict_fn(input_data, model): + print('Predicting class labels for the input data...') + + device = torch.device("cuda" if torch.cuda.is_available() else "cpu") + + # Process input_data so that it is ready to be sent to our model. + data = torch.from_numpy(input_data.astype('float32')) + data = data.to(device) + + # Put the model into evaluation mode + model.eval() + + # Compute the result of applying the model to the input data + # The variable `out_label` should be a rounded value, either 1 or 0 + out = model(data) + out_np = out.cpu().detach().numpy() + out_label = out_np.round() + + return out_label \ No newline at end of file diff --git a/source_pytorch/train.py b/source_pytorch/train.py new file mode 100644 index 0000000..f0ff6ea --- /dev/null +++ b/source_pytorch/train.py @@ -0,0 +1,164 @@ +import argparse +import json +import os +import pandas as pd +import torch +import torch.optim as optim +import torch.utils.data + +# imports the model in model.py by name +from model import BinaryClassifier + +def model_fn(model_dir): + """Load the PyTorch model from the `model_dir` directory.""" + print("Loading model.") + + # First, load the parameters used to create the model. + model_info = {} + model_info_path = os.path.join(model_dir, 'model_info.pth') + with open(model_info_path, 'rb') as f: + model_info = torch.load(f) + + print("model_info: {}".format(model_info)) + + # Determine the device and construct the model. + device = torch.device("cuda" if torch.cuda.is_available() else "cpu") + model = BinaryClassifier(model_info['input_features'], model_info['hidden_dim'], model_info['output_dim']) + + # Load the stored model parameters. + model_path = os.path.join(model_dir, 'model.pth') + with open(model_path, 'rb') as f: + model.load_state_dict(torch.load(f)) + + # set to eval mode, could use no_grad + model.to(device).eval() + + print("Done loading model.") + return model + +# Gets training data in batches from the train.csv file +def _get_train_data_loader(batch_size, training_dir): + print("Get train data loader.") + + train_data = pd.read_csv(os.path.join(training_dir, "train.csv"), header=None, names=None) + + train_y = torch.from_numpy(train_data[[0]].values).float().squeeze() + train_x = torch.from_numpy(train_data.drop([0], axis=1).values).float() + + train_ds = torch.utils.data.TensorDataset(train_x, train_y) + + return torch.utils.data.DataLoader(train_ds, batch_size=batch_size) + + +# Provided training function +def train(model, train_loader, epochs, criterion, optimizer, device): + """ + This is the training method that is called by the PyTorch training script. The parameters + passed are as follows: + model - The PyTorch model that we wish to train. + train_loader - The PyTorch DataLoader that should be used during training. + epochs - The total number of epochs to train for. + criterion - The loss function used for training. + optimizer - The optimizer to use during training. + device - Where the model and data should be loaded (gpu or cpu). + """ + + # training loop is provided + for epoch in range(1, epochs + 1): + model.train() # Make sure that the model is in training mode. + + total_loss = 0 + + for batch in train_loader: + # get data + batch_x, batch_y = batch + + batch_x = batch_x.to(device) + batch_y = batch_y.to(device) + + optimizer.zero_grad() + + # get predictions from model + y_pred = model(batch_x) + + # perform backprop + loss = criterion(y_pred, batch_y) + loss.backward() + optimizer.step() + + total_loss += loss.data.item() + + print("Epoch: {}, Loss: {}".format(epoch, total_loss / len(train_loader))) + + +## TODO: Complete the main code +if __name__ == '__main__': + + # All of the model parameters and training parameters are sent as arguments + # when this script is executed, during a training job + + # Here we set up an argument parser to easily access the parameters + parser = argparse.ArgumentParser() + + # SageMaker parameters, like the directories for training data and saving models; set automatically + # Do not need to change + parser.add_argument('--output-data-dir', type=str, default=os.environ['SM_OUTPUT_DATA_DIR']) + parser.add_argument('--model-dir', type=str, default=os.environ['SM_MODEL_DIR']) + parser.add_argument('--data-dir', type=str, default=os.environ['SM_CHANNEL_TRAIN']) + + # Training Parameters, given + parser.add_argument('--batch-size', type=int, default=10, metavar='N', + help='input batch size for training (default: 10)') + parser.add_argument('--epochs', type=int, default=10, metavar='N', + help='number of epochs to train (default: 10)') + parser.add_argument('--seed', type=int, default=1, metavar='S', + help='random seed (default: 1)') + + ## TODO: Add args for the three model parameters: input_features, hidden_dim, output_dim + # Model Parameters + + + # args holds all passed-in arguments + args = parser.parse_args() + + device = torch.device("cuda" if torch.cuda.is_available() else "cpu") + print("Using device {}.".format(device)) + + torch.manual_seed(args.seed) + + # Load the training data. + train_loader = _get_train_data_loader(args.batch_size, args.data_dir) + + + ## --- Your code here --- ## + + ## TODO: Build the model by passing in the input params + # To get params from the parser, call args.argument_name, ex. args.epochs or ards.hidden_dim + # Don't forget to move your model .to(device) to move to GPU , if appropriate + model = None + + ## TODO: Define an optimizer and loss function for training + optimizer = None + criterion = None + + # Trains the model (given line of code, which calls the above training function) + train(model, train_loader, args.epochs, criterion, optimizer, device) + + ## TODO: complete in the model_info by adding three argument names, the first is given + # Keep the keys of this dictionary as they are + model_info_path = os.path.join(args.model_dir, 'model_info.pth') + with open(model_info_path, 'wb') as f: + model_info = { + 'input_features': args.input_features, + 'hidden_dim': , + 'output_dim': , + } + torch.save(model_info, f) + + ## --- End of your code --- ## + + + # Save the model parameters + model_path = os.path.join(args.model_dir, 'model.pth') + with open(model_path, 'wb') as f: + torch.save(model.cpu().state_dict(), f) diff --git a/source_sklearn/train.py b/source_sklearn/train.py new file mode 100644 index 0000000..f38e268 --- /dev/null +++ b/source_sklearn/train.py @@ -0,0 +1,70 @@ +from __future__ import print_function + +import argparse +import os +import pandas as pd + +from sklearn.externals import joblib + +## TODO: Import any additional libraries you need to define a model +from sklearn.linear_model import LogisticRegression + +# Provided model load function +def model_fn(model_dir): + """Load model from the model_dir. This is the same model that is saved + in the main if statement. + """ + print("Loading model.") + + # load using joblib + model = joblib.load(os.path.join(model_dir, "model.joblib")) + print("Done loading model.") + + return model + + +## TODO: Complete the main code +if __name__ == '__main__': + + # All of the model parameters and training parameters are sent as arguments + # when this script is executed, during a training job + + # Here we set up an argument parser to easily access the parameters + parser = argparse.ArgumentParser() + + # SageMaker parameters, like the directories for training data and saving models; set automatically + # Do not need to change + parser.add_argument('--output-data-dir', type=str, default=os.environ['SM_OUTPUT_DATA_DIR']) + parser.add_argument('--model-dir', type=str, default=os.environ['SM_MODEL_DIR']) + parser.add_argument('--data-dir', type=str, default=os.environ['SM_CHANNEL_TRAIN']) + + ## TODO: Add any additional arguments that you will need to pass into your model + + # args holds all passed-in arguments + args = parser.parse_args() + + # Read in csv training file + training_dir = args.data_dir + train_data = pd.read_csv(os.path.join(training_dir, "train.csv"), header=None, names=None) + + # Labels are in the first column + train_y = train_data.iloc[:,0] + train_x = train_data.iloc[:,1:] + + + ## --- Your code here --- ## + + + ## TODO: Define a model + model = LogisticRegression() + + + ## TODO: Train the model + model.fit(train_x, train_y) + + + ## --- End of your code --- ## + + + # Save the trained model + joblib.dump(model, os.path.join(args.model_dir, "model.joblib")) \ No newline at end of file