1% Better Everyday

  • Add momentum to the custom optimizer

import numpy as np
import pandas as pd
import seaborn as sns
import albumentations as A
import matplotlib.pyplot as plt
import os, gc, cv2, random, re, datetime
import warnings, math, sys, json
import subprocess, pprint, pdb
from functools import partial

import tensorflow as tf
from tensorflow.keras import backend as K
import tensorflow_hub as hub

from sklearn.model_selection import train_test_split
from sklearn.metrics import f1_score, precision_score, recall_score, confusion_matrix

warnings.simplefilter('ignore')
print(f"Using TensorFlow v{tf.__version__}")
Using TensorFlow v2.4.1

Tip: Adding seed helps reproduce results. Setting debug parameter wil run the model on smaller number of epochs to validate the architecture.

def seed_everything(seed=0):
    random.seed(seed)
    np.random.seed(seed)
    tf.random.set_seed(seed)
    os.environ['PYTHONHASHSEED'] = str(seed)
    os.environ['TF_DETERMINISTIC_OPS'] = '1'

GOOGLE = 'google.colab' in str(get_ipython())
KAGGLE = not GOOGLE

print("Running on {}!".format(
   "Google Colab" if GOOGLE else "Kaggle Kernel"
))
Running on Google Colab!

Hyperparameters

HEIGHT = 224#@param {type:"number"}
WIDTH = 224#@param {type:"number"}
CHANNELS = 3#@param {type:"number"}
IMG_SIZE = (HEIGHT, WIDTH, CHANNELS)
EPOCHS =  8#@param {type:"number"}
BATCH_SIZE = 8#@param {type:"raw"}
GLOBAL_BATCH_SIZE = BATCH_SIZE * strategy.num_replicas_in_sync

print("Input size: {}".format(IMG_SIZE))
print("Train on batch size of {} with {} replicas for {} epochs".format(
    BATCH_SIZE, strategy.num_replicas_in_sync, EPOCHS))
Input size: (224, 224, 3)
Train on batch size of 8 with 8 replicas for 8 epochs

Data

Loading data

%%run_if {GOOGLE}
#@title {run: "auto", display-mode: "form"}
# reference: https://www.kaggle.com/austinyhc/custom-training-with-tpu?scriptVersionId=51687595
GCS_DS_PATH = 'gs://kds-f7aaa241d2ceea308646ba649de83022e9089736f446906c81c4f8a0' #@param {type: "string"}
GCS_PATH_SELECT = {
    192: GCS_DS_PATH + '/tfrecords-jpeg-192x192',
    224: GCS_DS_PATH + '/tfrecords-jpeg-224x224',
    331: GCS_DS_PATH + '/tfrecords-jpeg-331x331',
    512: GCS_DS_PATH + '/tfrecords-jpeg-512x512'
}
print(f"Sourcing images from")
for v in GCS_PATH_SELECT.values(): print(f"\t{v}")
Sourcing images from
	gs://kds-f7aaa241d2ceea308646ba649de83022e9089736f446906c81c4f8a0/tfrecords-jpeg-192x192
	gs://kds-f7aaa241d2ceea308646ba649de83022e9089736f446906c81c4f8a0/tfrecords-jpeg-224x224
	gs://kds-f7aaa241d2ceea308646ba649de83022e9089736f446906c81c4f8a0/tfrecords-jpeg-331x331
	gs://kds-f7aaa241d2ceea308646ba649de83022e9089736f446906c81c4f8a0/tfrecords-jpeg-512x512

CLASSES = ['pink primrose',        'hard-leaved pocket orchid', 'canterbury bells', 'sweet pea',     'wild geranium',
           'tiger lily',           'moon orchid',               'bird of paradise', 'monkshood',     'globe thistle',        
           'snapdragon',           "colt's foot",               'king protea',      'spear thistle', 'yellow iris',
           'globe-flower',         'purple coneflower',         'peruvian lily',    'balloon flower','giant white arum lily',
           'fire lily',            'pincushion flower',         'fritillary',       'red ginger',    'grape hyacinth',
           'corn poppy',           'prince of wales feathers',  'stemless gentian', 'artichoke',     'sweet william',        
           'carnation',            'garden phlox',              'love in the mist', 'cosmos',        'alpine sea holly',
           'ruby-lipped cattleya', 'cape flower',               'great masterwort', 'siam tulip',    'lenten rose',          
           'barberton daisy',      'daffodil',                  'sword lily',       'poinsettia',    'bolero deep blue',
           'wallflower',           'marigold',                  'buttercup',        'daisy',         'common dandelion',     
           'petunia',              'wild pansy',                'primula',          'sunflower',     'lilac hibiscus',
           'bishop of llandaff',   'gaura',                     'geranium',         'orange dahlia', 'pink-yellow dahlia',   
           'cautleya spicata',     'japanese anemone',          'black-eyed susan', 'silverbush',    'californian poppy',
           'osteospermum',         'spring crocus',             'iris',             'windflower',    'tree poppy',           
           'gazania',              'azalea',                    'water lily',       'rose',          'thorn apple',
           'morning glory',        'passion flower',            'lotus',            'toad lily',     'anthurium',
           'frangipani',           'clematis',                  'hibiscus',         'columbine',     'desert-rose',
           'tree mallow',          'magnolia',                  'cyclamen ',        'watercress',    'canna lily',           
           'hippeastrum ',         'bee balm',                  'pink quill',       'foxglove',      'bougainvillea',
           'camellia',             'mallow',                    'mexican petunia',  'bromelia',      'blanket flower',       
           'trumpet creeper',      'blackberry lily',           'common tulip',     'wild rose']

with strategy.scope():
    NCLASSES = len(CLASSES)
print(f"Number of labels: {NCLASSES}")
Number of labels: 104
def count_data_items(filenames):
    n = [int(re.compile(r"-([0-9]*)\.").search(filename).group(1))
            for filename in filenames]
    return np.sum(n)
def inspect_tfrecord(tfrec):
    raw_dataset = tf.data.TFRecordDataset(tfrec)
    for raw_record in raw_dataset.take(1):
        example = tf.train.Example()
        example.ParseFromString(raw_record.numpy())
    print(example)
train_filenames = tf.io.gfile.glob(GCS_PATH_SELECT[HEIGHT] + '/train/*.tfrec')
valid_filenames = tf.io.gfile.glob(GCS_PATH_SELECT[HEIGHT] + '/val/*.tfrec')
test_filenames  = tf.io.gfile.glob(GCS_PATH_SELECT[HEIGHT] + '/test/*.tfrec') 
Number of train set: 12753
Number of valid set: 3712
Number of test set:  7382

inspect_tfrecord(train_filenames)
features {
  feature {
    key: "class"
    value {
      int64_list {
        value: 57
      }
    }
  }
  feature {
    key: "id"
    value {
      bytes_list {
        value: "338ab7bac"
      }
    }
  }
  feature {
    key: "image"
    value {
      bytes_list {
        value: "\377\330\377\340\000\020JFIF\000\001\001\001\001,\001,\000\000\377\333\000C\000\002\001\001\001\001\001\002\001\001\001\002\002\002\002\002\004\003\002\002\002\002\005\004\004\003\004\006\005\006\006\006\005\006\006\006\007\t\010\006\007\t\007\006\006\010\013\010\t\n\n\n\n\n\006\010\013\014\013\n\014\t\n\n\n\377\333\000C\001\002\002\002\002\002\002\005\003\003\005\n\007\006\007\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\377\300\000\021\010\000\340\000\340\003\001\021\000\002\021\001\003\021\001\377\304\000\035\000\000\002\003\001\001\001\001\001\000\000\000\000\000\000\000\000\005\006\004\007\010\003\002\001\t\000\377\304\000A\020\000\001\003\003\003\002\005\002\003\006\003\007\003\005\000\000\001\002\003\004\005\006\021\000\022!\0071\010\023\"AQ\024a2q\201\025#B\221\241\261R\301\321\t\0263b\202\341\360\027$\2224CDc\361\377\304\000\034\001\000\001\005\001\001\001\000\000\000\000\000\000\000\000\000\000\003\000\002\004\005\006\001\007\010\377\304\0009\021\000\001\003\003\003\002\004\005\002\005\004\002\003\001\000\000\001\000\002\003\004\021!\005\0221\006A\023\"Qa\007\0242q\221\201\241\025#B\261\3013R\321\341\026\361r\202\262\360\377\332\000\014\003\001\000\002\021\003\021\000?\000\3742\254NB\036*\216O\007\202?=UF\313\233\225\301\302\'K\272\2475\017u:j\220\350\031%\'\235\006H\263\224\217\010\375\265\325\373\206\212\332\303\216\371\353X\300+9\320%\241\202L\224\005*OZ\257I)\014y\211m\263\376\016\372\020\323\351\301\272J\005Z\344\237[d\256T\345\253\217\302\245h\321\306\310\335`\236\302\001@\347%\3452\024\301\355\337\032;Q.\024)b\\\250\336J\233\357\361\247\001\346\334\272\270\265n\262\320\004\266\t\307\'R<bRR\231\243\260\262\022\246t3)\272\345\302\356m\324-;P\317~\332\341\224\204\256\023\365\217\321i\017R\215K\177*\031\010\007T\325\032\213\234\357\r\004\311qe\n}\242\252T\202\314\210\212\n\007\370\264\346\324\357e\220\307(MF\224\337\250\206\224=\360u!\222\222,\236\203I\210\343\213\362X=\207mJ\022\2002\222\372\033TF|\267\006\272\036\037\302JM\241Or\251r\260\210kJ\026\205\205nW\330\351\263\037\016\022Ot\357\351Z^\334\275\331\251Q\274\247\333\005\366\000K\215\214v\3062\007\306\262\022C\347%\005{[\360\227 <\341\332\223\330\221\330\350V=\222@.\310\021\334QQ\364\224\234\245y\306th\217\207\302I&\265Uq\024\323FJ6\254/!^\312\032\265\200o;\223\331\207\004\277N\216\204\324R\353\355o@\341c\344j\305\327\360\3549R.\027:\275R=\036\243\206\025\350Q\312G~>\372[$sr\232\374\214\"\324i*\270\325\364\321\"8\265\377\000\205)\317:\013\334\"\356\200\344\325\013\247\225*=1\333\222\344a,\261\031!M4\2622\342\275\201\032\203=X\276\326\233\335&\003t\235sW\335\253Ov[\353\345d\234\001\242AO\261\252RL\257HyjRJ\273\361\333Vp\2000\222\024\342rB7v\306\245\366I\027\246\272\210\311\013B\260@\340\3506)$YU\340\350(KG\363\325\230\207\t/\352%iQ\245a\343\351W})\"\273p\221Ov\255\255.\351!\024\206\224\263\236N8\032\253\232a\013|\3529\302{\246t9\3246\r^V\323\355\267\333\363\325d\225\356\'kF\022F\017I-f)\345\010**\037\305\215\014T\274\244\222\353\326*\350\322\034SaE\243\330c:\231\025M\360RB\205\035)\217\347) `\340j@\221\304\331\021\204\001\225\355T\222\022\024\226\311\007\276\007\032#\\\010\312v\366\372\251\220-\251\022}M0x\355\351\323\014\204\024\0278\022\214@\264\313\t\023jCch?\032\023\2442a\251+&\320\2515R\241\371\026\337\357|\276\025\252*\2269\262\222P\3169C\256\250\222\245\313J\252\254%\242\220px\347J\'\026\213\205\313\204\201u\206\243\272R\207\022RO\004j\326\236\347\314S\207)u\347\321\021%\326X*Q\373gR\300k\223\256\027\250TZ\365\302\327\324F\2458Z\007\033\302\017}0\313\024]\322\334\002`\265\372{^\240T\305\\\371EIA)B\225\375\364\t\252[+-t\203\201A\353\367\347P\250\227\002d\300\212\266\336d\344yi\312T>\017\316\215OKM3<\3058\006\220\256>\234_\256_\266\352j\223\355\307\"<\203\261\354~\025\250w)\325\005u7\311\324Y\246\341\r\303iR\356\370se\304\314X\353\344d\034h\020\220J\340\"\352\272\270)\265\026\322L\250\312\037\n\000\352\342\231\355\r\311E\261B\341<\226\267.@W\244z\0165,\235\303\312\221\004\013\225\322\334\262\337\276\356$\306\301K@\356Z\361\370@\327%\252\371x\356P\313\355\302\261%]6\217Nif\215lDC\222\3020\037\004`\037\225\177\246\251vMP\375\304\330\"0\027\362\253\213\206\362\271f?\365\022\252\217=\355\373\325dc\365\355\253\030\251\341`\343(\255hhA\247\324\274\326D\305`\034r\001\355\251\241\244\004\344\267Q\2546]\334\2421\251qE\213\224\220\323Uh\222\262\243\371\'G\330\344\227\217\367\201\334\204\241\'\036\331\367\321|0\222\367v\364\376\241H&LV\024Z\'\216\016\207KX\311\006J\345\302\001\026\2332\\\326\3404\302\274\307T\022\224\343\236u<\270\006\335+\205\257z/\323\0306\025\216\324\232\236\004\227[\013uK\366\367\307\333X}B\250\324\324\020;(\356p%\021v\357\262\326\333\215\2715#o\003\325\337Q\203e\345-\301D\2116\201Uy\021\243N\001*W\316\236\014\215\345+\205\322\365\264\351\351\2459\373\304\250\355\364\250\350\221\275\336 )\\*\242\363\241\312\244\333\037\264Z\031JT}\265q\010&K\244\r\312a\351T\032=\345Fm\345:\2375\t\365\':S\007F\354\256\271\216\t\202z\250\264\025\375$f\023\270\014\251G\034i\273\255\312`iw\n\253\352\307S\0227\323\341>9\316v\2365.\226\002\343tP\302\246\370|\353\204k]\227-\371iHrI8}g\204\352\026\253\247\272O8\354\273+\001\027\t\312\277uP\346L*\250\\\255:\245\037JR\241\316\252c\202m\227\332\243\206\270$\213\236r\014\364\226\343\251m\377\000\016\023\334\352\306\231\304\266\304\256\242\266e\237W\251\3113\225Gyg\031e\224\2439\320\352*\030\005\201H\233+\016\233I\251\322i\212i\332Z\232mI\312\301F6j\017\211s\222\233\365 \263a9 \226\343H\331\216\344\236\3720\341t\013$\373\316T\232z\013,\270\024\2620U\251\224\340\336\367D\034\"\335\034\352U:\335\214h\027\030PC\317\202\313\340d6\243\301\317\333\357\240WQ:\241\367iI\343p\302\272\336\256\3334:Zj\025\332\314d\265\214\267\352\004\250}\276uC\262bl\300P\266\271s\225K\264.xM?\nKn\267!\275\354\270\021\302\201\370\343\347:\354R>\'\330\234\204\355\316i\312\005#\303\365>\240\242\352AJI\356=\365`\315D\201\224\347?\tc\251we\231\320\230\206\203JdH\251HF\036-\250\002\322}\363\367\324\232x\344\324N~\225\326\260\270\252v\241\3253/s\261\251!9\367Y\311\325\2734\350\333\216\312H\000\014 So\232\254\340B\202\020\236\330\t\324\241H\306\234$\240*\2539\300ZC\245AZ?\207\020IDSN\251\334<\3418=\206\235p\005\202K\371m\251j\302\021\307n\332\355\302K\241\210[\306\343\330k\251+\242\023\220\347\025C\2524\235\247\201\271=\265\233-\330.\322\242\207\024\016-\237F\240]\302\344*J\320\301\313H#\215\332\222k$0l\356\226\342\217Wz\227pU\242)\205\311(l\366BOa\252\366\322\264;q\345si@\021\365O\240\004\347\324x\306\24605\243\001q8[\224YQ\331L\205(\244\343\214\037}2G5\330!$U\245\324\247?\344\316\224\262\312r\010\371\323\007\206;$\246\326)\324\232\315\256\375!\001$\371d\202O\330\350\321\311g]9\274\335V\2350\244UmJ\374\220\344\222\206\022\277@\007<jUC\204\220\2029Es\201\nGP\256\304\305\214\362\243J\365\253#\277\316\231K\033\337\310Lo\225S\025\t2\'\312Z\320\024\262\243\221\236ux\326\265\200_\010\367\t\212\307\351\035\327w-3\030dFcv<\327\216\334\376Z\205Y\251SS\202/t\302\361eq[\336\036\354{Q\014K\272k2d\313\341A-\237@?\007\347TRjR\324:\314\026j\t\225\304\355)\254\327\241Pe4\266)\221\327\034\021\265*d\034}\365\0301\305\2479L\"\341\036\215s\267Ut\311\247\307m\2059\370\313I\306t-\201\247)\226#\225\374\307\327C\250\241\371kS\215=\302\320\256A\032yh\262\341\272\351y\304\265k\321\333\246\301B\"\310\301\001\306\223\214\253\340\350`=\206\344\341=\267\262\244\356\310NB\220\344y\204\225\266\242\225\016\374\203\215Z\322\270=\242\310\243\204&4\030\362\010y\301\214v\030\324\263#\332l\272\207\\\325%2\311iO+h\374#q:,-m\376\224`\321t\337\341\333\304\235\026\302S\324\016\241?5\352j\020>\2100\200\242\322\263\310\347\330\352\016\253\244:\252\317\207\005q\354\016\3414\337\376:\333\363\234\247t\342\334H\217\263kR\347grI\367\t\037\347\250\224\335=\303\2469C\021\033\252>\265U\251\\\025\027j\225iJ\220\374\225\357uk<\222u\241c\031\013C\0320\021\300\000(\212\304pQ\263\214c\221\247\203t\224Gb\245i*G\000\236t\360l\222\362\206C`\014\003\215p\344\244\272\262\204\205\025(rs\301\327\022]P\302\244(!\000\360=\264\222_\035\216H\312\211\316{i\333\202\3416\n\337\272\240\376\310\234\262S\267\235f\242vr\242%\331\322\222\244\025\251_\226\244\213]=\254\334\242\303qs\034\330\236F\214\346\000\322\272Zl\234-\352PRP\227\023\334\214\350\003\2244\366\3259\ta\t@\000\004\373\373\352;\315\212J5r\n\242S\367 \221\270\367\306\206_d\222\365O\366\223\014\030\364\3472\245\'\222\2559\222\200\234\336\022\262j\257\260\353\210\222}`\220\256ud\033q\312r\367\323\370\335=\271+\217\307\276C\216\204\000Xi+\300\'\357\240\325\311Q\014c\302\\u\300\272\267\255~\202\364\346\274\205N\264^\211\035\345\243\367M\274\310?\324\352\236Z\312\307}f\350>#\275\020\206,\212\355\016\340z\225VB\023\364\336\244m\036\225\376Z\343vH\3370E\006\342\350\214\353~\243S\377\000\334;\030a\'\204\347\235\021\201\254\300A\376\253\250\222\350\221\235g\310\220\215\252I\354t]\373S\301\272#J\214\325::R\316\325\003\355\2409\331\005\".\2100\343\256>\323r\202\220\t\343\237\353\242^\351\244Y.\335\014\310\203Q[\221\337\013\t^P\261\256\021p\272\336\022\267Q\232v\267h;^LQ\365\020]\001\367\033O\342m^\347\362<\350\324G\303\230\002S\332nl\253\004\326\035A\00199\032\275-\027G\014K\267\035QN\344g\277}I\211\202\350\210;X\t\357\357\251$\333\204\227V\220\245\272\224\014\214\253\032\341 \266\353\2166\027Lp\342\2452\222\301\345X\000\001\334\235E\311\027L\361\024\365Y7\rI\345\3714\305\377\000\3241\241\032\230\030m\271p\313n\310]J\203V\242\273\364\263i\353l\236FG\007\357\2431\354\223\351)\315x!B\tPs9\034w\032{\205\212E\340\025\360\027\027(%\266\312\216;$g\\\273G%s\304\366S)\324\351\262\327\265\210\353R\2248JG8\323\035#\033\335/\021L]\026\243\025\220\344\230\371JV7\204\034\2353\307\210\340\034\256\027\334Y\\w\224\021>z\224\350\341G\337Tq4\024\0264\275\333{\245\251\226R\3458\033[\305)=\200\321\206\340\265\272GMTV\345\370Si6;P\210\362\320\342\310\367:\357\217\273\n\366~\211p\206\354u\312>\313mDq\246J\002\025\270w\030\327Am\360\260\332\226\231Q\246\313\262F\224\373O\244CDF\237}\340s\355\250\222\337\262\254&\312\027QYe0Z\372\\\004\361\300\324p\347\023\225\326\371\222\035FB\232i\331\nQ\375\333D\351\315i|\200\'\201e[\277PzB\035x\222T\265\234c\337WL\001\242\305\021\255\004]\021\265z]r\324]EY\203\345\225\034\244\036\347\\\226\256&4\262\327I\317h\362\225`ZW%\303g\324\177eM\334\332\321\302q\306\252\347c\010\273PJy\025\331\265 \212\215I!\314\016T\224\362\177=C\r\000Yp`YN\205X\206\343\352V\356;\224\023\337Km\270M \000\243]\223h\322c\2310\243\224\270\007\250\201\337]hq6+\215\345\010\247\324\022\344nU\2029\030\034\351\316g\027O]\325XQd%H%D\200\222{\350\301\242\313\204]x\226\313n\266D\206\312w\017~t\304\200\262\003%\271\rF\235F\215\020\272\211\321V\322Q\217\304H\340\351\273\257+O\242\350\301\272C\271:!T\240Xrny54\252TP\225;\021\264\347j\t\3019\373j\322-A\222T\010\302\220\307\227*~\262\341R\266\245Y\317}_\302\002\"\357n[\365+\202kT\330,\034\274\260\220\265$\355\037\256\2334\321\302\t%q\306\302\352\354\262\274:\322\305\021\0257\032\372\211l\253\0139;T\177/\2165\235\250\325\244t\233\033\302\001\220\220\246\016\227\307\20593\237\210\322\\B\3626\247\007B\371\271vX\024\315\30507Mi-\345\306\301\334\223\310\324b\375\316\271H\233\241j\241\261P\221\377\000\273m*JI\010\3348\324\221/\206\337)\\U\177V(\264\232\035\306\344*z\\J\266\356u+\030\001G\343\355\253\232)\0374!\305<p\245Z\224(\364:JjR\010T\231-\025\014\216\033N\242\324\314\347\315\261\274.\257\246\240\343A^Z\362{\002\007a\361\244\346<\244\272\321\231MJ[1\2260\235\345N\034\373ho\033xI[\364(T\372\365\330\304yk\001\255\371P:dM\003\n\327B\241\371\332\366\266\312\314z\312\262\035\220\333Q !K\310\033\224\2352\242b\314\005\364\036\235\245\304\330\201\262%;\2476\264f\322\343\241\244\235\271\325c\345{\\\265\361Q\322x \330$\273\357\247,K\202\252\205\016>\347\033\004\244 g\266\237\rK\214\201c:\313\246\"\324\250\335$M\271\tb\227.\260\206\307\325\245H\331\302\222\241\330\215Y<2\370_8TS>\236GF\361\220W\013\212\254\251\tK*s }\373j;\230\333]265&uR\244\212U\263\373\245\355q\343\264\017s\242\320\260>|\360\223\300\007\013\247K:{\016u%\252\225Y\255\353X\312PGmJ\250y\271\262\031\221\3156\n\313\246\320>\230\200\333@l\374\000{\rA9\345\010\222M\312\023\324[a\331\221\323]\210\205y\321\306\\\3439\032p\r\003\224H\300<\250\266\315\342\\\247\371d\250\251<)\000g#Q\034\321{\004\353\005=u\010\025V\317\323\035\216\003\351H\340\235\"\335\246\327\272B\'9\267\317\370\374\241\225*\215V\023\242+\310;T0A\037\353\251\021\266\343\t\255n\347m\034\375\356\274\322k\021\302v\227\023\271$\347\236\332\353\343\'\224\210\261\262\350\212\242\025-\001Na;\216u\300\307m\\D\327)R\343.@{ \020\000\317 j3\256\323d\221\n-:*\302$\272\2573=\2069\032\003\211\004\373\246\222n\202\365z\237Z\235\323J\254+V\234\271\223\244\276\224\276\323*\033\323\035 \251X\031\311$\201\333D\323\313\031\\\013\360\023\342q\272\312\216G}\371\302*\032QqK\332\021\216s\234c[\201\265\261\356\'\nQ\341h\233B\335\247\301\266!Fb*Z}\246\023\346\215\243;\261\311\374\365\221\253\231\357\234\347\n9\221\304Y7\332\025\311\024\304\270\3121\265c\003:\205 \026\272b\343&\245\021R\013\222\360\243\273$\237m8\003\267\t/\037\265\341z\223\345\002\235\247\n\007]\014q\345$\"t\350\373\025\345\271\312\270@O\261\316\210\340C2\222Q\3525*\231V\271 V%>\225\245\230\270\226\0179ZO\003\365\316\244\320\324\275\264\317g\356\2368Ku\032\272\237t\206\321\265\001\004\004\343\214jd0\026\024`\326\220\2407!\001[w\367\034\361\251\213\245\255L\326\375\031\324S\377\000h\207JK\235\222\0078\347U\323J\003\254\202\254\3305\227)\356\26790\222\225!X9\032\201\342\311{\202\245\351\372\224\332l\342X\273\'\326o\252!\200\312YmbB\3227\224\373\035Gt\357q\310^\243E\361\035\221\261\221\271\274\362\206\302\272\252\265\332\372\240\314\005\014\243\204-j\306tg@\327\303\270\025\270\321\272\211\232\251\362\034\'\272M\353f\331\314\375=ZKng\202\t\032\0254D\270\222\264u\232\305\r\034\026\226@\007p\025]\326k\246\336\225VK\266\242\206\327\177\030I\343\032\270c1\225\363\317T>\212\242\270\311O\301IH}\331n\215\374\363\241\270\014\254\271\001\246\341\014\275\250N\326\352\021r\202\246Y\374J\301)\004\017}:\226H\243q\004\330\244\372j\222\337\023\3036\365\001\304\017\302\261:qn\324\345A@\216\301\014\266=O\021\351Om\002j\2060\222\342\253\313\267\033\337\373\217\356\256^\231t\252\035\317\005u\007\'\356\303\245\260CY\031\000\023\216~\010\325%v\253\341\221\260/K\370\177\360\373\377\0001\022\316\371\2661\230\372o\237\272w\244t3\247q\3467\032\350~k\321\234y)\222\264\024\2404\236y\300I\343\266I\366\317c\202+[\252K$\266\013\327\351>\014t\3355\014\236$\217{\207\177\370\364N6\027\204\036\230\300\233Qa\213z;-\314N\326\245\271\373\345m)%*N@\t\317\007 {\215L}v\347Z\352S>\031\364\254\024Nlt\345\316 \330\275\3277El\037\016\226\325\265oBb\263A\244;3s\252q\ti\267}[\311\341X9\001! \343\214\203\21647\325J\323f\225\316\216\351:}+IlU1\2078\334\334\200q|.\367\177C\272gqA~5r\303\243\222\347\342_\321\264\225\362@\033T\000P\374^\307Q\345\326*`\036U\250\227\2464*\346l\226\235\226\356v\214{\254;\342\207\241\355\364\006\376LzD\207\237\243\325R\247i\353q\'sD\037S*>\344e8>\340\217pu\245\323k\205t\036\343\225\363g\304\016\214\377\000\305\265?\345\033\305!%\247\374$\253\n\235*\370\276\351\026t5\257\314\250\324Y\212\003H\334\241\275iI }\263\235O}\343aqX\3352\201\325\372\204t\277\357 _\321h*WA\354\024\301n\234\364I\n\220\230\350nD\364L^\\{\370\226\020}\000d\340\000\237\302\221\234\253r\216.\253W\225\263yF\027\3254?\005\272q\232ad\361\227JG\327\272\300\177\365\365\036\251f\245\323\373\232\315\2549Lb\2176tf\016\346\344\306\204\342\222\264\343<\355\004\003\362?\355\251\361\327C4a\337\325\350\274\037_\370i\255\350\372\277\313E\013\345\214\2348p/\353\366V\255\275\341\036\306\250\306b\245>\346\256\242c\361\320\271/D\226\312[K\212\000\220\022Z$\001\234`\222x\324\t5m\257-#\205\352:w\301\255&M9\216\231\316\0229\240\233v6\315\225=\326\177\366kW)\027\332z\247`^\221*\221]|\276\375\035\370\205\227\206\023\310h\240\251.\250\221\214\035\234\2518\316u\243\244\352\030_A\341?\016Y}o\341\016\245F\307>\201\341\340\002l\356q\350\226#\332\262\033Bc\322\2512eMu\302\224Ea\225-\325\037t\354\003vG\3063\306\243|\303\t\363\034/!\217L\254}[\251\304N/\034\201\312;M\265\345\320\322\246k4\2310e\266\274\310\211::\232q\277\214\241`\020>24\t$%\336S\204:\252\n\312\007m\252\214\306O\000\362\221n\267\020\252\373\314\266\177v\025\221\216\307VQ\201\341\335DC\035\224\272{^hh\245\245\034\002N\212\320\022_)\356\262\270.\276\372\260[\'\005G\267\301\320\246\315\232\023\207\t\n\346\252\266\271\356H\007%J\354\025\306\254\251\340\332\300\324v\264mC#J\223=\362\207R\224\244\003\221\363\253\020,\236\246\305\204\307\234\333 \014\251\\\361\3064\302\202\\U\205Bn\214\363(O\234\224\244$\000\t\340j\232F\312]\302i\341Y-YN\316+J\031\030Q%<j\254\311ce\035Df\332]\021\345&Vx\036\214\217}s\304IC\270!\324j0\231~\225%i\177\315\332\226\333\007\325\251TR\r\346\374+Z\035Z\256\2062!u\221*\'\207\373\276\354\214dV&-\242\007\001J<\352d\225\320G\345hA\250\324\353*\237\272Yo\354\207\326z\017q\323d\242\233\021F\\\265\203\345\262\312rR\221\214\251G\330\017rx\320]\251\306\301w)zV\237\250\353U?-I\031{\317\355\236W[g\241]D\211xS\342\312\266L\346>\251\225>\224\254\245\245#p\334\205/\215\274d\036\177,\3524\332\275\033\241/c\254W\240A\360\347\251\250\265\330)\252i7\003m\306\370\003\272\323\321(\221\243\303n\034\032j!\264\312\003i\216\310\tJ\000\030\300\030\373k\023-\\\316\224\270\276\367_W\320\351:u\014\"\236\030\300\210\001aa\351\337\365\\\234\261i\025J;\364\346\233D7\025\225!l\262\220\024\263\330\250\001\317\367\376\332\340\256\2208\007\033\205\210\352\317\206:\016\277\003\344\211\273%\354Z?\375\017E\037\242mT\354\306\352\266\225j\230P\344Y\302C\017\236R\340u;\010\007\261\003\312\317\375G8#R5\t\241\221\215p\354\251\376\025i\032\247N\313Y\247\3262\333\034\3075\303\207\007\\+\026=Ms\")N\177\037\370N\000\032\242|\363\003v\257f\216\033\002\n\351o\336\017\331\276D*B\022\2101\300B!7\351m(\370H\376\034{\021\251PVJ\371l\364)4\232I \273yDk\375k\215i\242-C\350\326\374\027\\)}D\360\312I\306\357\216\001\'\223\330c\337Sa\2512JZU{\264\255\220\223\350\212\324\357j|\345\251\320\244\245\021\226\002\312Nw\022\204\237\363\034\177\312F\207)&D\032zG\265\270\357\205F\370\206\350M\335\342\206\210\021k\255\270F\231)N\304\2335\262\226\035;JT\336\354\344rA*\tW j\377\000I\255m\023\313\217u\201\370\215\323G\250t\301M\013\255#\r\307\275\325S\341s\240\027\337J:\277T\270\372\213E~\2332\215H\222\3355*\010u\251N\311i\330\341hq;\220\264\245*Y\364\234\205m\316\3221\253\375KU\207\345\201g\365/;\370u\360\347P\203^3j\r\261\210\202=\017\272g\246\335)\250NS\305\3745\346\024\266\017l\036\007\367\3262h\366\017\276W\325\021\370n\033\233\3354I\270\233b\232\230-\025\007\\O\240\204\376\0369=\261\250\221\007x\205\376\210U!\205\273Ou\352\321\273gS&\256\220\251\205\310\214\200\nV\312\323\345\251^\241\265g\205$g\004\'\200=\262\t2\246`\236\"GuU\0211\324\271\235\200\262i\215p?_\205%\014+p`\002F\356F\340G\376~Z\014\021K+Jt\302(\2340\210\332H\240F\270\236\270[\243Gbt\264\241/LK@-\321\216r~rH?>\371\300\303\244\232`\301\031U1hZ\\un\251\212 \036\356M\202X\361\275M\267\344\364\2225\330V\226jp*\r3\t\344\250nZ\034\n\334\321\371\007\033\276F\317\202se\243K\276_\014\257+\370\277\246Q?Bml\203\371\215 \017\324\347\366X\312\261-\2770\310\330\255\303\205\0008?}k\333\035\232\276Y\356Pz\224\3712`\226\3262\333g$|i\354n\327\204F\202\202\326\253\316G\216\230\361\325\200\264\215\377\000}\0328\303\236W{\331,\270\322\346H\363\024\234\214\361\253&7kl\214\3344]\022\215K`!N\266\016\340\203\203\2354\234\241\270\235\312\\z$\245A\372\347\001\001\303\350\343\234h^.ST\332[o6\220\223\224#\261\316\233\'+\205j\213J\261B\247\245\3410\202\260\234#\215c\244\216c\302\005\302X\273\252\rU\246y1\231<\377\000\205<\235\035\200\264e$\327\323>\231;-\326j\323\230;Q\2052\237\363:\350\2245\245\241p\360\255\230\224\230\360\231>S``hxC \332\326\374\361\367\375\020\252\024\006|\3513\035a*/<T\245m\365$\016\000\311\366\356\177]Pj3\336K\005\366\017\301\355\007\370oN\374\344\200x\223\330\334\177\264q\372\036\311\22648\215\260\251HJI@\301\300\306A\376\272\246\271\'+\327lJ\234\323q\345\'hl\005d+\031\356\237\177\345\2468\200W@7Q&\244\304\222\000\340\005\343q>\307]k\201\n[X\034\020\373\262\244\334WX\222\311\001A[r\007$\021\377\000m\024\035\302\305\0328r\213\332\210\021-\031\025\312\253\301;\330+m*W\037\000\237\222N\235\362\355#\225\026J\207\032\237\t\210\004\212\274\212\233E\020\336(qD\345AX\300\320#g\207\236\352\317\035\327\364J\253,Br\327\213\033\316J\030\375\342|\302\246\326\341%X;\211\332\016G\330c\000\001\2533\362\3060\367\375J\255\361L\351KG\322W\247b\335\215\264\2210;=\267\036[\216\2462\202|\322NpJ\216R0~3\202\006F\001\014\022B\35454\302G\224\360\215\256\372\275\341R\tC\177@\304V\216P\324|%\244\001\273\337 \r\271>\332|q\276\344\226\225\026JjH\315\345#)v\241x?W\244\313\253\312\251)\361\031K\005\305\234\341A!X\t\343\034\020{\014\347N\224<=\254=\324\272Xib\273\2328\347\323\365Y\362\304\272\'&\357\253S\252t\301\031\210\025\02447<\0240\241\271\013\310\371\003?l\215_\327\3226\032f\226\363e\221\351\276\246\237V\325\253)%\217h\201\326\004q\236?R\254w\246\027\337CJ$/\225\002U\301\373~z\243c\013\n\333L<\315\nm\002\242\207*\tf\177\360,c$q\317$\377\0003\244\340y\354\202r\343e2\300\273\335\247\310\222\302\223\2377\313R\277$\250\202?<\250h\264\347\301\224\240\327E\342B\tO\002\261\016;&qt6\t\'`\340\203\355\256T\206\310nUt>;\017\220\245.\261\321*\275N\264Xf\220L\245\301\232\037\223\013a\336\246\303k\033\221\362Fy\035\361\235\023O,\204\223k/9\370\301\242\325\352\375<\326S\002|7\336\300r6\363\366Y\342\267\323\372\365^\342f\335\263\351J\227&b\302\020\302\022?S\223\300\000rI\340\001\2554\025Q\262\033\311\205\362\206\227\245W\352\265\302\216\231\233\244&\303\030\007\335hN\216\370\022\351\205\267I\217W\352cj\255U\034o{\361V\361\020\332Q\344\004\240`\271\214c\324JI\354\221\306\240K\252\271\316\263qe\364oN\374)\322\250\240d\272\2002L9\261\362\217a\353\357\350\234n\317\016}\004\254\302r\231\'\243\326\353@\266w\230\224\306\232X\007\377\000\330\330\n\007\267c\221\216\372\020\325*Xn\327-\244\275\031\323\325\021\230\337\0033\350\333\037\312\316\235]\360\017\026\227)u\336\223\270\260\301AZ\351\022\335\335\300\317\374\'\025\311\035\206\024I\340\372\217\003V\264\272\353\344!\222\362\274\263\252~\022\210 uN\222onX\343{\177\361\365\277*\230n\331f\025A\252qgb\212\366:\225\247\360\234\340\347\371\035Z>`\341\270pW\205M\004\324\323:)\001\016o \362>\350\235\301G\211\001i\202\323\251-\264\214\022\236\304\343\\\216BT[\203\220\205D\244\004Gr\244\342\222\032I\300\334y\317\376cF\r7IX\350\252\313\235Qf-=\245-np\000\325[\342\014n\347\014.\030\300\027)\346\326\262\247D}3\352\r\356R\207\247\214\201\252\271&\016\3411\\\026\243\2463)B\220\234\004\000\000\320A\027\\\270R.Z\224\032m\035\351\353\226\204\251)\300l\367Q<\014i\3176i(\320Dj&\021\016]\217\312\365mEu1\n\224\204\2254\336\343\346ps\360\017\346~\372\314\316]$\246\313\357\355\032\216=/G\202\010\370kZ\337\300\377\000\265-\232\252\312\271i8\333\215\240\177MDs\200Z8\341\016h+\314Y~L\317\247\3366+\224%C\270\307a\367\306\230Z\327\213\256I\031iSk\022\230\231\031\004c\177\361\234|\201\317\347\237\357\241\200\003\261\302tB\351\016\261T\025+\220S\234\n\"+YY\377\000\231G\003\371\014\235X1\203\302\277\252\234\317/*WP\356\231\261(\364\313q%\260\303\354\007\\B\323\222\002U\204`\373gi\324\226D\013B\201\014q\272gI\356\241\320j\303\311R^H\301I\347\034\376~\370\324i\030\320\345,\275\275\320\232\007]\250\226\315f\241K\026\221\232\317\324\022\324\226%yd\374\202\nU\236s\203\221\306\254\276Z7\3047*\311g>%\232\230)\236!g\326\246E\240[6B<\3712\002R\353\362\267c\'\234$$\022\000\347\270\340i2\216\026\233\201t\003$\255u\357\205\023\256\235Q\270\335\262\334\263z\223n\325aQ\3250\025H\241\272Xx\270\023\350V\356\352\030\347\361\024\022\220pH\325\3445U\355\213`-\333\214YQ\352\272v\2333L\2572po\260\215\331\356/\350\253^\217-\252\215\215]\266-\333\252Mn$\2658\350~vL\326w\266\020\020\261\222\034\302R\010Zx>\341=\264\315J\235\217\235\223\206\332\337\272\256\351V\370\024\323Q|\311\221\240\022\327?\3527\354\3421\204\n\320\351c\3358\265\323F\021\227-\351\025\005K\235$FRK\236\242B\202A*X\300JG=\263\245_P\372\311C\210\332-d\376\236\320c\320\250\036\032\360\367=\373\234\341\222}\255\316\023\204z\240\375\222\232\305R\"\331HB\210C\315)\267\016\007\341\332\256r1\252\266\321K,\373\031\221\335j_\250D`\361\237\213\336\327\007\326\312\035\271*\253Syq\002R\207\236\036xqJ\000\224(\340(\014\373g\034\373\352d\364\254\245\210\367U\364\272\224\265\325\241\215m\2309+\315\301\324\353b\327\270\030\264\355\232\213\017=\037\002t\204\362\002\371\036^}\361\334\340w\376A\221\322\274\307\275\303*\023\272\303\247\252\265wi\321\274\027\216\327\377\000<&\306\272\205\373e\224)e#n\001H\340\023\264s\306\240J\307\003\225\177J\300\322Su\217TC,\031X*[\216\204\241C\277\000\237\323%CH\207\354\302\035`\022\277on\367G,\013z\217\016\2672\371M-,\311\234@q{?\020I\345@c\323\222A8\374Dd\347\032\004\325\222m\021\236B\240\323z[C\323k\244\255\247\2046Ww\355\367MM\324\245JVZR}\000\002\254pO\0319\370\030\377\000\371\334\000NJ\2661\226\267i \367\374\2519zK\207\313\206\277%\316\002\370\302\271\306>O\3118\307\177\276\212\327<\233\250\222\200\323\205*]\254\364\210\373\037m|$\r\200\343?\323?\034\367\307\037}Hk\3107\034\240\207\214\203\220Vm\361U\341\366\233IZ\372\231nS\314w\013\311MQ\220\341)p+jP\350N8V\356\025\3179\007\003\004\233\372\n\262\3731\353\303\376*tU3\251N\261K\202\337\250\016\343\325P\225::\3430_x\022;\340\373\352\3562\001_<\202\010\270@&\207\335\364\276\332\222\320>\206\322?\027\337S\332\340R\034\253[\242\366\353\365\251F\256\204\377\000\302;[\311\343\'\276\252+\344cHj\354\216\034+\226\237I\225Ka\013HII<\203\355\252r\306z\241\"\014\314Ld)\304\274\022\022?\t\327\000m\360\231\264\245{\266\242\252\224u\025\205-\001aA)=\360s\242\006n6*]\014\215\247\255\216Wp\322\017\340\253\236\335~\302\352\r\212\302-\367X\246\326\222\312T\221\365*Sr\310\316P\275\371\330\243\236\343\000\021\310#\221QQ\014a\245\243\005}\315\241\353QVSGU\023\201\215\334~\330>\370K\355\255A\305\"K*miQJ\322\256\nO\301\370:\316L\013\r\212\337\307#\0347\003\202\271I\tN\325\223\205%yI\036\372c\034wY\035\321\2076\367S#<j\r\230\254\266\024\351l\226\322\023\3528\344\247\037?o\365\321\234\302\035`\024Qh\316P\270\026)\223=\352\375I{YqiZ\033\316\013\204\000\007#\370x\007V0\355dc\325E\251\324\034\035\262<\244\016\270\\\364\350\227\262#\311\220\206\223\016\013m\251JV=D\251x\037\242\307mM\2027\312\t\0016\n\230\251\342&W\000};\244\311}UzLU\322\255\267Kiq%.\276G\250\247\341?\037\237}\035\264A\216\005\352$\372\243&v\330\234\024\010O3\0059Z\222\0241\270\036\377\000\256\215\341\233\234a\001\225P\233\033\337\3256t\246\350\240\323W_\270*\223XC\361(\017\265JC\217\004)\311o\341\224m\367$\005\257\2608\306O\000\235K\245\205\273\356\340\2525\035Dl\264n\356\254\253K\375\346\253\275\3732\261\0023\314C@\3412\024\240\332\\\000\020\0241\270z\177\t\340w\035\365\312\312\235\215\260VT0\260\221\274\220\342?\277\374\252\353\251\236\030\353\026\234\351\367gE\353\342\013\322\236/9H\222\220\224\266\347\031\362\334H\030\316;\021\214\234\344{\252}Z9\010l\343\205M/NT\320:it\331C|L\226\221q\177c\356\252\325x\205\271my\351\205\325k\261\341Q\204\224\266bT\036Q\332\3368F\321\3748\030\037\323VS\321:\275\227\201\270Y\352~\252\243\320\034\177\212J\326=\274\362>\306\326\356\266O\203^\231\330=I\263 u\276\356\216\246\233\220|\332d7\231S\301M\340\341|\247\322\024G\277\360\343\347KN\323\035\004\216\337\331X\353\035E&\245\004rSe\256\315\370\272\256\374w\331\365zS\254\317\351\310M:\241:\233.<U\264\320IA\310)9\030\003*8\317\261\347N\230S\266\255\242Q\344L\235\232\254\335?1\242v\311\255\202\027\347\315\223R\255\322/\006\250\225\231\016\267\271\342\207\033q\314\224\271\367\347\276u\240\255\202\031i\356\320\276D\251}n\221\251\370\217$H3{\236FU\361gu%4\325*\231]\231\345\024)YR\262\177\010\003\267\306\0109\326>z2\343p\027\325\035#\327t\263h\321>\251\376b\000?\377\000]h\036\234V\033]\276\227\035s\320\022T\263\267\261\'\030\376\207T\023\227\306\373/Jc\233P\300\366\367\316S\355\221t\305\2555\373*S\273\026\300\310H\356\342~\334}\365\016x\311;\3021ac2\235 CK\310&<T\224\0360F\320\177<\363\200=\270\367\317r5\024\003~\024\'\223\331\032\215\344\301I\222\343\305G\340\236\n\2168\036\352<\017\3448\000q)\256\302\204\340\342\272;q\241\014\251\0027\2518\034\021\267\357\356?\227\276;\363\251P\310Z2\243\230}J\256\372\372\266%t\336\267V\256\024\026\243R\336u\010m m\302\t\037\327\037|\343\235Y\322\274I;AT=Y\265\2359U\270]\273\r\276\353\033Vf\323\247\265\346\231EI\'\320\235\270\003Zf7j\370\255,\326\251\317Iu/\211i\307\030J}\306\2465\326\t\'[^\343\250Z\326\342\244\300\226\246\320\362p\332Ry\335\356uKV\326L\353\246\023stZ\337\352\335\306\002\231\226\267T\310>\202\024x\032\204\350\230\325\335\251\210u\016t\270\373K\205`{+\2024\306\267)\253\323W\"\334HQie\262=D\214\200u!\2438K=\224\312-r|I\330\2438r\341\316\305\016\016\201QO\033\331w-\337Ju\356\263\322m1AgE{\355v\177\nS\0368i\326\365jM\251v\321L\017\246Xl\310DR\372\025\333\005@\222\254\376\207P\006\205-C7\260\205\356\372?\306\235\026\261\2155\261\230\375\301\004~0\236\255\376\252\331\267\304a2\215Y\247\313F7f\"\266)<s\224dl<\366#T\365\024\025\020;,\341z\366\215\324:>\261\026\372\031\332\361k\340\347\365\036\250\325\006\0332\037\025\327\212\233i$)\222IIY\036\370\366\037\337Cn\366r\255j$\361[\265\234(}O\352}6\316\243\n\244\267R\262s\344\306K\200)\303\216\303=\207l\237mK\2029*d\332\002\253\227\303\242\205\322\277\260\272\306\367\375\337q_\267%B\346\221)\206B\022^}R\036(@$\360\332I\030$\'h\031\307\003[\212*&E\020a\031^#]\257\324\324\326\3110}\203o\336\302\311z\037Q\256*c\315M\231L*\213$\022\322\320\214\002\214\224\205}\307\030\317\276\245\273N\211\316 r\252i:\267V\323\347\016\231\243\303\223\203\352=Qz2z\261\032\226ne\333\025\211\364\351\t+\022\232\212]\034\022\016\nJ\267\001\217\351\330i\222EIv\262\340\025;M\255\352JV\311<\221\270\304\353\221oEl\364n\235*\342\351\304\013\262\217\025\177S%\327V\371Q\332\353Km\327\033\333\2028 `\343\337q\316\240N[I-\201[}\026/\343zX\252\000\203\350{\020\256+J\355\256R\221\3473\000\207^e\"I^\006\345\'\202\177\236\3561\371j\216\255\321H\t\005n\350\342\225\321\265\322\214\332\337\204j-\334\232\312\037\2134\002\340\300R\200\374I=\217\337\007\215R=\273\274\301[lk@\262\241\374P\370r\227\325[\302\207\026\217\215\325\032\203\021$\251)\312\333B\210\036b\177 pA\3561\361\255oO\352\302\026\230\334\274\303\342\007F\267\250d\201\355\026\027\033\354;\014\217\356\267\005\207\262\307\351\355.\304\2676G\217Ja\2662v\250\257jB@\003\034\220\000\032\271\212\263sy\375Q\376Q\22184d\000\000\375\005\226~\361S{Qj\027[6\3533w\232K+L\247J\306\3371X;A\367\306\006~\372\240\324\344\222\246p\033\220\026\273K\217\345\251\336d\260\016\030\272\240\247xl\245uv\305\257\324\343\321Lk\256\233\004\315\267\347F\310T\326\333\316Yq\031\301$\024\200\254d\0222H\032\260\207Y0L\330\311\273I\000\376\270^g\325\237\017\351u}*i\274;M\227\003\366\027\265\275\327\037\005~\013\372\233\342\212k7U\311%\352E\266\307\022*\222\033\375\364\242?\201\224\223\317\003\005g\322>\347\215K\324\353i\251\235\262.\353\313\372G\2455\t\330\'\250alm6\027\356}\207\371Zr\341\247\330\335=\271dY6\014-\260\251\177\270q\345\271\346.K\300\376\361j\'\277<p\000\373k\021W/\2115\312\372\213I\246\224\322\202\356\300\001|%E\324%\306\255\376\320\206T\336\325e\275\274\020\177\323\333\365\323r\032\025\243\343qm\205\217\330\247\032\005Q\212\203I\224\311\362\037N\024\343i\354\223\330\024\234\366\376\332+<\'\362\253\'aja\245\334\265\030\353,L/\276\000=\224\222\240>\001Wo\235\031\324\221J02\253\234lQ\326+Q\334m\267b\357\030\347\326\240Js\375\217\344}\265^b\235\216\332B,f\'rm\367Y\247\306\307_j\262\241\273\322K\016\022d![\025[\236\303\241A\004+w\220\235\247\223\302J\216\177\345\306s\2156\217@\320\014\222\236\027\203|P\353h\366\035\"\215\327\004]\356\007\037o\373Y\2417T\272r\274\232\234GR\000\306\024\2025\241c\032\343\202\276x\301\341s\213tH\250\257j\034\332\224\236\344\363\215J\360\310\354\235\265h\n%\032\327\252RXE6\232d!m\204\225\'\377\000\266\257q\254\233\236X\363t\025)}?a\234)\210JJ~\nt\307L\034\235\271u\213d\306q*\006Kl\020G\016\203\241\357%5{q\206\2432\250\rNeiI\354\204\344\223\366\371\321\343\026\312sM\212\345p\313\266\272uJM\305]\257\000r6\266\201\214\375\277=p:j\207\230\332\324\367]\376Q\312\252\254\016\225\337\036#\356)\367U\266\224S\250nNR\025U\224\306\362\342\263\377\000\r\244\361\274\201\214\250\220\224\347\271#\032\271dq\321\300\321)\317\242\364\016\217\350mC\251_\342\302\003b\343s\206o\336\303\205oZ\336\037ln\235\315j\035B\2476\242\350y\n2R\033HQI9\034m\332\237\346O\353\252\352\231\341x;B\372;\246:\032\213\246\3111n/<\222\177\260\342\312\357j\326My\241-\212\323\356 \244\r\201~XG\302p\234cY\032\230%\214\227\014\205\351\314\250`h\016\t\033\257\226]\275D\351\215n\352\254D@\372*s\253nK\271Y\016\004\220\216s\237\304G\032\221\244\311+\352\330\306\205O\324\265T\320h\225\022\312l\320\323\371X\367\245H\246\365\022\217[\207\324;jd\305Gu\267Zz\237U\362\t;v\223\261^\225\244%#$r\234\3600N\275\"y!\243\266y_1t\354\264\032\3354\315\255\030\354o\331\032\211\027\245W\265a\236\234\320\024\247\234hyL\037%\304\253\311\031R\233\013V\3370\244\002r\240\220I$\014dj\275\356\254\200:\245\377\000I\030Z\366\036\226\327\352 \322)\237\347m\255\203\206\367\001kX\266\275=P#1\016*\032i1\232KQ\333H\tBv\r\251\003\267\035\265\213}C\3373\234\\n\276\205\245\201\224\264\254\2144Y\255\003\213\245ht\211v\005BKo\276\201\036L\262\245mG\r/\266O\347\2003\366\032\224gt\255\001F\206\212*g\271\3216\300\376\021\366g\274\372AI$\021\316\334s\360~\332\257\233p\301\302\231\031\027\366B\352\350\223\031\344T\243\245\302\331%/\006\273\220;\234}\265\310\354[b\216X-to\247u\250R\257\332\023\323d\245j\214\267]XQ\300\033\032X\317<s\301\324\230\030\326:\352\rX\221\324\356cM\256\275\365c\304\215JD\231\024>\236\255$\021\344.\240\331<)C\n\362\371\306@\'\325\374\276uf\351\\\031\205[MF\302\340\\8U\315*\302j\342\220\324\252\212\024\260\323\233\302\224\263\223\371\237ru\005\365\022D\016\336J\265\223O\247\250p2\014\016\311\322\303\224\232%\334\342\320\311J`\245\031l#hZ\024NF;\021\201\375\264\310\300\"\375\327+<\261\200x\377\000\n\334\267\372\214l{N\341\217Ne\226\325\006\"\325\t\265\000\020\024G\240`\016\333\224;h0\233L|B\252u\n\037\032&\272<\273\267`=\255\307\352\261\215\337\325?\025\026\304\202\344\276\234C\222\211/`T\351,\273).(\236I\001[\222r\177\211#Z(\364\355\036\251\267\3613\350l\027\233k\035S\361\023A\233\302\371!+/\202\3177\346\351\266\314\247x\207\277\234\212\354{}\370\255\270\007\236\231Q\320\337\226q\367\344\217\320\037\266\240\311MC\023\213c\261[}\033Z\3275\nf\311U\017\204}\300\037\246\025\206\353\225\233=LA\252\324aJ\250\016\036\217\014\225\006\307\302\225\214d\374\014\352\266F1\206\341j\030~a\273v\233\251\261\356\307\032\230\033x\266P\025\307\253\007\036\331\3755-\204\206\013,\364\340\211\034\3008Gg\277nW\251OEz\256\246\023%\2056\357\323\314(^\010\344\r\244js\035\034\226/\345R\325\302\372\232g\300\376\010\267\276Vb\271mI\326\215\301.\224\207\303\255y\207\351\337#\207\021\236\017\347\354~\343V\021\275\2566\007\013\344~\260\320*zwVt\022_k\311->\277t\271r\312\375\222\312\2236\003n\254{-\260u.\235\217\221\326j\314X\214\024\n\r\261o\325inV\006X|\254\204!+\300Q\324\302\351D\273FBv\345qXW\264\013A\243H\223\021n\224\250\2248\201\224\247\344\037\327Yy\203\245\030C\260E.n\257\321\340@\\\312\205ML\241\010*XC]\276\332\344TrK\206\256\354r^o\304\177DX\247\"mZ\350Z\335Y\301g\311Q(?|\rJn\227\\^Abw\204\221\272\271\343.\327\240\257\366\177J)\rKV2\354\351\r\224\247?a\334\376z\265\323\364I\344\027\230\330\'6,\345T\326s\235Y\361c\325Z]\200\355m\347\027Q\232\002\324\001\rB`\020]x\244\177\n\020\024\243\363\267\034\234\r_>\032M2\235\322\221\300\374\253\235\033H\237V\324\031K\010$\270\376\002\375\027\250O\265\372GaC\351\265\203Om\230\320`\246\0246@\005A\035\212\324@\345J$\222{\251J$\362I\327\232U\326\311\250\316f\340z/\265\364]\"\233F\242\212\2326\217(\037\236\351.\257C\253\266\205\311]I\265-#)B[=\375\275\370\374\364\326\221l-\036\362Be\351\375\311\365\220\033w\352U\271\033|\346\312\373\237~5\032\247u\356xD\001\256e\212\242\374hWo[\246\365\0350\256\327f?A\227\031\271\2648\224\320ZJ\226\214\207\033s\t%\325\202R\241\316\000\035\263\255\016\216\332xi\276f+n\006\306\377\000\341x\227\304\217\342\325\025B\207i1<yv\372\367\277\252\254mN\231\336\026\275\021\331\256S\352q\324\247\374\272{\236BS\265\322\000\004\340\356\3561\3029\311\347V\223\325R\324\273q+\t\246t^\261CC<\362D\346\264\375>\245O\253Y\235M1a\3652\317\242\210\223\351IQ\250IZ\033\334\025\205\005(#o\2558*\334\016x\371\307\000em\010&\031$\334\323\200\025\364\2757\324\364\260\301\251\321G\266H\207\233\034\375\370Z\212\r])\201\r\3011\324\270\304\006\304\367\335p\020\247\016\356R\007`S\267\037|\236\330\326N\251\201\271`\301+\350\375(\310i\207\215\350\337\320\221r\270T\350wM\371C\230\253F\315\227P\216\204\251*q\255\250J\270#hR\210\336O\3339$h\324\221\220\340\\T}B\242\010\330FU?\323.\264?\021S\251\027\034I,\307\243\270\204?6J\026\237\245+V\320\323\305@\024\220s\311\354\007=\265o]\244>F\207\264\344\254N\223\325\221x\262\307+H\014<\225e\304\276\030\253\206d\320f5,:@iQ\324\034J\362@\306S\236y\325+h\245d\241\216\006\353oO\251SUSx\26189\276\307\205\303\305/O\256~\207\337\261)2i\313M>\241\001\2652\376\342\020\\(\033\320O\001\'%Co\333\215h\035C\362\345\241\343\220\263\262kF}\316\210\\\003o\302R\351e\n\352\272\247\242\203\373=-\256;E\331\222\334RCa\004m\033s\3118\004`r>\331\032\207^\321L.\356\352v\225\\\372\223m\266#\225oG\246\323\355\312q\334\220\343\351\034\037\277\330\177\236\251w\202n\264\016\221\304Yv\351,V\25173\225\352\304?\254\n_\230\364u\237I\037\341\374\200\306\213\024\240L\032\242W\r\324\350wS\337\253\334\262\236\201g\004C\023\035@x>\177\002\023\222\023\201\337>\221\372i\3404\324\272\375\220\032\351E3l\206Sz\177\325\232[\"E6\251\005`\014\220\246T\234\377\000}?\302\2078(O\254\223\351\270\003\323\325Ie\376\254KX\240U\253.CC\251\033\333g\010J\316}\224\236O\363\323\303bh\300\262\346\350\234.\340\tQ^\214\305J\264\252\r\264\262\324H\353\331\"P\037\274\220\241\302\271\356\022\016@\003\2762s\250\357\221\254\006\310\221I(?Q\036\212\266\353\3052\340\351\335\334\337\320\335\323L\t\320\303\3555\347\0342\240v\251\003\337\034\003\377\000V\256h\355,C\031_>|B\327\365\316\223\326Xb~\346H\t\261\346\371\356\221\177\365\006\343\216\362\025\"\250\363\244\247rw\310W\371\347V?+\034\230\262\311\303\361V\255\221\267\306\200\223`NW:\217Y\035\271i\306\2152*P\353N\005y\205\314\251\037\221\343\276\237\026\234\370\235\270\034*n\252\353\321\324\324-\201\364\355m\270q9C!&\237]\224\206$T\226\245/\202^\340\003\371\352a\335\020\362\2579\262\376z\227\345T\r:\"\202\333m~\245$\361\367\306\232*\274\233\200\311MS\356~\271\332\366\323J\223\"\007\335\274\036N\253\250\350\245\231\330D\360\312\245:\217\327*\255\357-l\307_\221\024\036\033\007\225~z\324\321\351m\207%\036\301.\304\2556\370\362_\333\214c \366\324\267F\346\272\355]\\j\024\265\272\2172*\267\016\375\365\330\344;\254y\\&\331[\237\375\236\276\022z\203\323\273\016OY+T\250\261jW4F\032\243\267!\363\346F\204\263\274\272\260\220v\371\204 \204\367\001\000\237\305\201\231\352)\333P\326\306\303k\025\357\377\000\013:r}-\247R\252g\231\343\313~\302\340\253\306\243\323\247lI\n\254\316BkRT\322\224\206\311S)\n#\004\244\234\223\216y\366\3108\326I\354lC\'\225\356qT\03100\253\313\232\344}\366\336\r\323R\307\222\t\334\211;\310\306O\370Ft X\r\202\264h{\033w!\026\315z]\036C\023\335\334#NJ]e\355\230J\206\342\223\374\224\222\223\367\032SB\363\035\310\270J:\230wX\034\372&\353\202\233G\272 \2632\\6\226\3745\027#\272q\300Wt\347\340\340\177|\035F\212c\037\224`)\017\211\223=\2629\240\226\334\217k\362\227\347C\272i\327\275\032\237\036\336S\224\355\213\\\227\202\201\031;AP\300\343\000\234|\363\300\324\210\343i\204\276\371A\222\246FV\266\020\337\'\257d\370\327J*s\276\260\332Twd\027\343\027U\023\322\204\356JpO\230\242\023\317\034\036I?}2\2269j\234CG\010\323\325CI\036\327\0348\361\356\275Pz\033z\\l\320\340\277\364\321\243TY\017T%\316p\264\210\213NR\264\272q\351 \244\340|\021\215Oe\005d\216\021\265\277\251\341T;]\245\245\022I\311\344\217^\337\267\262\234\377\000\213>\230t\316\2336\332\261\212n\306\351;\343\310\223O\313QB\233\357\265j\031P\317r\022\177\257\006\376\032\312y\203d>o\331R\267W\207S\201\365T\346\355o*\212\265\256\032\207W\352WE\"\362\240\267\005w\204\247\034u\r\362\026\311\3327\2478\345(\332\234\373\355\316\254&\223\344\234\331\032\353\333\267\242\317\351\341\272\3043\322\313\036\322\360H<_\377\000I3\300\235\265-\317\022v?L*UGQ\026\233w\274\374\324\266\361\010W\220\225\251AC\335*-%<\374\375\316\256\034\366IP%p\362\233/&\320\237\250R\352\337#\023\215\3017\311\340\037E\372K\343K\247\024\016\243\364\306\245M\362\211\251\241\205Ia\347\210)m\304\r\351\340\366N@\007\363\324\315g\345]L\3274\257S\323\205k\245s\0302N\001\341b[F4\336\234\320\027S\270\252\251e\306P\0252G\004n\300N\026\276@\030\003\270O\3165\206\256\0377\264\306n\267:9\250\323`tu\237\352rO`>\377\000k&Kb\250\335\363\001\271Q\256\030\305\314\225\266\326}2\007\261J\263\337\372~Z\252\2260\317)\036e\240\212VT4K\t\334\323\234\'\313\002\232\352\236r+\201\033\302\017\230\022\241\224\247\364\376\372\014mx\224;\335r\261\315m>R\354\232\3444]\005o\024\205y\204\014c\n\332q\235X4\332B\345\036\356u+@O\364k\226\216\246R\240\013\212JyIP\003?\257\032\222\331A\031U3S\033\335tR\251\2273\013\211\031\240\227y\302\266\203\205{\037\276?\313I\377\000\314\026\0106\222\022\t*\270\265-:\245\025\305\tqK\207\314!KaY\347\'\365\376\232\257\222\027\023\200\254\005LOhi6U\277\214\305\320\304j,\030\225\022\272\262$-ND\n\031Dr\006J\307t\222\240\2223\334\003\371\352\353Gd\215\'p\302\360\037\215\225Zt\324\224\354c\232\351C\261c\330\252\036J\005Fs\356\227B\003(\033S\362u\240\211\241\246\300\257\236\017\227\312\016\002Q\220\352\341U\213\216\270P\240\256\343\370\201\371\325\225\204\214\001=\226o\000~\001\376\351\256\024V\333-K\016%iq\001G\036\303Us=\340\221t\313\222T\307\372\233mP\256\010\366\324\324\226\336z:\\\363\210\001#9\000\037\277\032`\245\250\222\237\304g\013\244z,\363w\335sn:\212\336uE(\n!\010\366\306\265t\264\254\201\203\031RG\0106O\316\246.\251P\301\337\234\361\240\275%jxS\351\233\235e\353\255\267\323\267c\207\342\312\250%\311\355(\340\030\315\002\353\374\374\371H^\007\271\300\325]t\246\010K\307*\353\247\264\303\253\353PR\216\356\037\266\177\302\375i\237Zf\236\312\"\304o\313(@CIC)\t\306\321\3318\343\216\330\355\355\257:\251\252\225\317.+\354\210h\304q\010\370\014\260\036\330K\327\rAU\310\377\000L\356\324\254$\245;\324\224\253?9#\343U\257\250s\316U\204Q6\"\035{\252\206\364\266e Hf\034P\342\324\222\224mX\333\372\234\347\035\375\264\242\224oV\316\221\262D\276\3320 \305\351d\3532\252\266\024a0\247a\027[%I\363\013iu)\030\311\345-(c\266\325\037s\253\330*[\340H\327\013\361e\234\252\245\221\265\261\310\323o_t\237A\274c\262\271\326uI\367V\270\355\204)\304(ehVFR}\310\343\365\324Ih\016\341 W0jQx\346\"{#V\357R\272\206j\346\237k9Fz\024\177CF6\327]P\' \254\034-\007\030\340\373\203\246I\020\r\307\010\324\262\262yv\310\354+)7]\356\355\033\n\217\035O\355%\034(\r\330\034\037V1\366\357\246SI<g\313\217\262\025\\td\202\363p\026|\361s\326\276\2601a?f\"\362DUT\010i\310\224\326\226\034u\2621\264z\316\001\355\300\311\373kI\244\314\367\311i\t#\334\257\036\353\375E\264\224A\224o\r\221\346\300\017\250\376\274\360\253\217\002\361\353U\252\365k\244\227\034u%R`\256T\006\344$\245\307\002\000\363\022\224\236O\243\327\366\010V\247\353\324\346f\265\360r\263\377\000\r5J\2355\223\321W\2335\371\271\365\364W\037^\256G|5USg\\f2V\'<(\315\255\275\257\2404\257-eDp\033$\224\205\021\312\270\004\340\342\252=.\252vY\313s]\325z>\214A\334\034r\007\262\t\376\314\332#\027\247\216\250\01730\277\373Y\211o\272\323\261\301Chq\315\347\271\311\364\250{{\235]\311\013\2041\305n\340\025\346\232\035d\022u,\265\221\236ZJ\375?\353g\206+e\313\"\177\322\310\227L\333/\377\000n\3632}2[\n\';N\0063\236\016{\r\036\267H\023\323\226\265\313\177\242\365+\337T\320\346\002=\326$\352\367H\245\304\203W\266\351\225g\346\244\222\314\270\212\000\207\332x\024\356\003\270\354~@)<\353\006\370\346\323\352v7\364^\257#\351\365Z\0271\343\312\341c\354\020\253j\203ot~\330\216\315N\321D\331L4\226\241\305q`\260\311\003\272\263\370\261\366\320]\262J\242^r\233OL(\264\366\301G\364\264\000-\350\005\202Y\215\326\036\256_w\330\262\255\345\304\211Hm\036eU\350L\204m\005Dl\310\036\343\260\357\337S\236\332X\251<KePGS_>\274ircc\001y=\235|~S\333\020m\212Qj\253=\240\353\354\244\245\005\325q\202rN;j\234\274\277\000\362\265\244\000\333\004\036\346\353\0359`\300\245!+t\344%A\036\224\236\332\221\014onIA\014\0176*oN/\231l\327\240\307\221!(q\302PJ}IW\007\222=\275\265-\256\r\272\201YN\034\313~\352\245\353\207Po\250w\335^\223N\271]\214\313r\324\022\230\244 \355P\n\000\253\277\2768>\332\260\247\214l\271_-\365\347X\365%\036\263-\0043\206\260ql\022\252\271\020\244\315\250\231\017HZ\337x\356u\307VIQ>\344\236\372\237\033\203\033e\344\265\023K<\242I\034\\\357RnW\323\016#\305m\003\260\266\177x\254\376#\243\266P\006\020\024;\326\306\363\351\010\252\261\035(;{\016\347F\245\252\376a\005+\224\217\036\340\253[\356\245\022\020V\224+(?\344u:Zx\246\030O\301Cn\031TK\236\270\345rxy\022\026\220\022R\277J\000\3541\216\332$BZh\004@au%T\351m:\200H\332\261\253H\345*B\024\354\007\2328\357\372j@\221\245%\3266\033s\0138\374\364\323\225\307p\256/\rW\354\316\222\334\364\256\250PZK\322\251\265$\255\366O\036c$\024\270\336\177\207z\024\244\347\333vuS\250\006\271\341\245O\321u)\264}N*\310\306Xo\372w\375\226\361k\306\357\206\033\316\0032\234\275\034\243\310Sy\\J\235=\344\255\245{\202\244%hW\350u\214\254\322*\233!,\027\005}A\246|P\351Z\230\274I&\332\347Z\341\303\373{\251\020z\325\323\212\354\004V-\213\331\352\204e\254\245\265\306\200\360\334A\301\332\\@\007\007 \234\343#\036\307TS\321M\023\355#@[\2557\\\3235Z\177\032\221\341\314\366\004/\024:\333\027}Qq\330\245N1\220\222\247\246\310(F\337\201\264n\356x\344\215\004Cn\025\233j\006\354\004>\243M~rU\n<6\343\255\304\372^\007\324\214cp\'vq\310\355\366\343E\214\210\310*C\235\024\255!\334\252\266\356\264*\366\355\302\335\347&\242\207!\322\335B\234\334\201\216r\006\\\306H<\014+\330`kQ\014\242\252\237\303\035\327\227\353\221V\320j\314\252o\372m\345)J\270l\333\352\354\201\006\316\274\342\323\347\031\271}\227\333p\245H_\342VR0\262\201\236\307<\002=\310tt3C\021\334\313\204y\372\273C\257\2328i\3526\313\337\320+\013\254\3369:]m\227\250\326k\315\313X\312\024\345;\nGm\273\2018\311\343\270\371\357\242\374\204\365_N\025n\245\327\335=\245\035\216y{\373\333#\366Y\'\251\335]\252Vn\323U\243\314~K{\010e\347x)*\003#\n\347\003\267\351\253\332\n\010\341f\327\362\274CY\327\232\355m\3654X\3341\354\274t\306\363\352\035\267~\300\352m\275R\220j0%y\315>\205\250\006\200\310X\310\376\025$\224\221\236A#\266\247I\341F\322\002\217AQ\251UK\343\027\023\372\'\336\253u\243\250]I\352eK\251\267\233\017T\345\\\264\317\2470\227\270\264a\237\302\332\0019\t\334\222\256?\213*\356u\t\337\314h\267egS%\236\\\356HV/\3732\256\n?L<]\322&^\220\352q\246\310\245\270\210\014>\330\rGa@\257*Q!K\316\314\0161\203\334\3505.\2241\2338\276T\376\217tL\326\010x\313\201\037\225\372\377\000\325\333\376\005\315a4\262\205)\227\030\310u\004\020\010\300\306s\224\236;j\312\246v\n{\273\205\351Z=\004\224\365\304\016\330Y\226\340\245[\364j\334\313\203\310Q~L$2V\342\267\004\241%G\037\236TN\274\343V\253\210\311\374\245\352\3240\314\332k{\254\365\342\n\353D\2511-z+\200?-\334pxJ;g\355\250\024l\336\363!WRK\263\371~\250}>\265C\262i\r\322\351\350\010N\n\226\340\306]s\335G\334\237\364\327$d\323\033\036\024{\301\0216\345+\334\267\215f\270\202\333/-\226\207\031Wu\017\362\321\343\205\260\205\036I\\xK\215<\350\226\226\333s\n$\004\2506U\223\36055\255\334\333\005\021\325\257\003m\325\235\321\310\023Z\270\274\311L\240\371m\025\r\311)!_\177\353\246\272?)C\252\250\226X6\203\202G\367\272\316\335c\352g\355?\022\267=\264\202\025\037\366\211e\222\017\341[hJ\n\177\371$\353I\r\006\3558L9_\036\374@\231\225=YS#\177\335o\302\225JW\324\035\316#r@\357\216\332\256\007\013\022\353\335M\203Be\325\371\362T\2242\342\212A*\344\250\014\366\376Z)s\330\313\204\341\033vn]\\\t\222\205A[\212[A8J\317a\241\202G\234\362P\371Iwm\236\313\252[l\240mH\316\357\203\253\032j\237T\222T\213Y\317?\312,\234\234\366:\262\022\356F\361/\204\243>\201X\212\371D\350\213ii\341Ai#V[\232\356\021\003\205\227\027\342\262\304|8\200I\367\327@$\256\356\010c\354\'qZ\000\003: r\352e\351\247\355\'\037\222\031x\210\255\266\026\362\177\305\250\225\354i\000\216S\036p\236,{U\376\241\336\324\213:\032J\rR\240\324e:\021\273\312J\326\022W\217\204\202U\372j\003\237\341D\\\246iT.\324\265\010\351[\313\310\000\372/\320*\027F\251\024\2123t\352e9\014\301\204\312Y\212\300 \204\245#\003\337\'\267\177~\376\372\301\326J\351d.+\354\355*\222\237M\242\216\010\005\232\320\001\373\216J\234\305\253Hct\t\025\024D\334\336\024\246\016\024\006{\343\375F\253\266H\354\203\205x\335\3476FZ\177\247\266\315$\320\350\nZ\244I\005)zB\367\275!\305\014r}\207\031\300\355\371\r\n]\343\262\222\306\356w\210\356US\342J\320\021-\330\36646\374\327\335\246\2771\3769Y9H\317\3118s\372|\352v\235Q,$\020UWPR\215GM\225\255\344\005\213:\355e\316\261/\345*<g#\211q\230\227Op6R\\mh\030R\017r;\2168\343[\375:c=)\336\276D\327\242\222\223Tqa\332@\001.G\263\345\326\320\374\247*,\266\266YS\257%iW\251\\a9H\340\222}\376\347S\315K\"`m\256U4\014\222\256@\306\362\206\324h\325\353zC\"\243CQi\306\322\343+q\n\302\207\334)<\217\345\306\212\323\034\202\367\312\231.\235SF\340di\267\252a\270\256\311_\356\313tkR\235\344\245i\303\205\007\326\001\356\006\006\177\\\366\323Z\t\222\357+I]Tc\323wR\267h\266Q\033j\301\254^\010\217T\233Uq\267\035\206\260\304}\344-\304\260\201\222\242;\014\35688\341\265|\214\205\362F\322Z\321\225\026*)\337\023&\224\340\214+/\301=\221q;\325\361q\312\204\264\323)KJ\225=\341\275M\214\341iH\'\370\2119\370I:\255\325\252\343\246\247kF\t[^\202\321\353u\rY\362[\310\301{\237^\313\364\306\351\276\2503-ZU&\336\232\362\311h\271Pm\335\252,\2722\222\200\240\006FA<\352\247V\324LTM\215\206\367\013\330\364->i*\035$\312\240\352\305\364\210\021\224\300|eCq\347\030\037\032\307\261\206G\371\226\316\001`\262\325\367u\267M\272\225qW\337PS\355/\351P9\334S\200\022\237\376C?\236u\240\245\244\022A\344Uz\246\247\026\231#]\'~\024z\025qW\023\255\266\231\276j\2222\265\202HI=\377\000\320\177=\022x\274!\205\036\206\265\2659\0054=O\224\354_*\225\004-I\030\n\'\t\037\256\240nh9\341X\324\316\377\000\014\010[\2252\301\351Ur\005~M\317U\257\te\364a\270\0176\224\241\261\236\002N\001\007\357\316q\251rW\304\030\003\032\262\360\351\032\203\265\027\324\325T\231/\303v\355\003\376U\261B\211)\226\214`\317\226\342\323\202\351#\322>G\316\202j\331k\333*\321\264\2622M\304\335\243\267\257e\202<Q\364z\356\350\227W&?P\225\"Lj\244\227\'R\352\313\377\000\362\002\227\271@\220\007\255*V\025\300\317\n\300\n\003[\2552\262\032\272P\326\2360B\371;\256\272~\263B\326d|\307s$$\265\335\276\337\245\321\336\230\335\222.\372x\222\265!/ \024<\332\025\334\343\361c\357\376z\251\324)M4\267\003\005`\345#u\223->\244\333S\222\037l\205\240\344\202u\000\202\020\321\300\363\0176\244\2562\222H\310(O\007N\270\001r\342\366KuZ\275\032;\310\217W\224\230\241\345\024\240\274v\356\307\276\215\003\036\377\000\244]t\002RwR:\201e\322\242~\315\242\004\314\225\264\376\361\265\372P\177=[\322Q\324<\202\354\004F\261\301\303\nT\252\300\234\024\325A\246%\247\220A\031:h\0233\222\222\\\256\331\266\345e\243\364\250TWG8\366\324\270\352\0349I&\327,\252\2353%\225\241\344\377\000\313\337S\230\366\273\204p\366\335H\351\314\227 \315\225G\226\205\241R\233\001\003\035\310\347\032eP\334\003\207d7\216\352\347\360\322\270\224N\263[\363\346J1\333faR\235\362\367s\261^\234s\370\277\017\333v\252\252<\360\233-\027G=\255\352JrO\365/\321\010\254)\310\345\325\002\204-#i \345C\344\017\323X\n\275\300\226\216W\330\364\214\273.R\365\307h\212\314\257)\224-\016\250\014\255*\332@\373\363\377\000}Di{[b\255w\260\004\"\327\351\252h7\202.\t\317\270\377\000\222\202\226\226\362\312\216NrG8\034\0228\371\321X\367\270\332\330M\361\232\3545q\257\322\256N\252u\242\241\002\334\247\261\276\226\322\031vZ\337!-\241)\031\366\366qJ\355\223\306\216#t\236P,\220\236:h\334$\356\250\216\265x\030\352l\356\243\310\257\322+\320V\325Bg\357\020\340}\323\030,z\226\220G\341\334\t\3329\033\270\355\255\r&\264\312X<\027\214\205\341\275]\360\322\257W\255}e\024\227c\263\263\214\363\312\256\257\037\014W\207I\252\276d\212\273\325X)qD\271\032\032\320\330\371Q\340\360;\344\366\007\235I\247\325i\253\257vm+\'C\321:\276\221^\331d\210\355\265\257\312\213{Z\360.\003\016\3325\0259!\342\220\266[%E\277\203\357\311\347\217\276\246\302\347F\322\373\255n\247A\005m;aq\317\331H\267\3748\277\002{Ti2\337J\\J\225)+F\026\002Fp\221\317\247\007\225\034v\355\330\021TW\312\363\344\031Q\364\376\206\236\246]\222\277d6\271\'\270\364\375Sk\366\265\235C\350\225\313s\332R&\307\251Fg\351\241\313ac\367\021\374\346\233t\240\017r\200\350*\310\300VA\310\301T\325\315\017\332\357\254\253.\247\320\033\026\216d\245\037\312\214X\001\333\337\365L^\006\305\273\026\207^\245\302\273\223T\221S&b\233y\302\247\300\t-\225\254\221\316U\201\311\317\037\007U\032\371\235\326{\233k.\374&\254\210\272j{\335\304]_\351\273]ZS(\264\224\311q\241\365N6\243\373\305\340\034\221\3339\317o\235R\325\317\342\300\326\367\262\366j\032O\227;\201\301\312H\276\335\221S\224\262\0348>\312\366\324\030\213Y\311W03y\341S}E\266k\310\251\267p\2402)\314\204\207\322[YX^\342\022R\020A\311\334@\301\356;kS\245O\020\213a9</4\353\315?T\236\276*\210\235\344g#\331\027~\327\254Qk\014\272\325\004Gi\340\220\035u\245\2459#9Q\306\001\3119\347C\255\221\207\202\245\3511T\3055\210\260\367M\256\255\332\024\006\027^\216P\227\237J\033Tt\205\r\307\341E@v\311\307\177I\343\215U\306\337\020\033vW\362\317$c-\345z\217R\226+\nTm\310R\207\247\325\203\234}\277\355\241\273ii\262~A\363r\235(\225YO\224\260\244\250\270\254\002r\006O\347\377\000}\004\033\213\256\277\312\333\250}e\351U\267\327\273\025V\r\333\031)zBK\224\231\355\272\204\256\034\204\245{^ \221\226\306\024\027\366P\034d(\\\351/\252\246\234K\037\036\236\253\027\325\372V\225\255irRT`\330\220m\305\277\345~l\307\237V\266\347:!Mq\207\220\242\333\212a\314g\344q\257Fv\311\2327\213\337+\344)\240\021Hc\364\305\375Q;g\251\225\213vs\222e:\251(w\227P\352\311$\374\347@\236\206\031\233kY\010\260\005c/\256W\025n\333\215\002\332\244\210\356\002\013\322^H8\003\331:\251f\227\024R\235\306\350\364\324\246I\205\302J\352\014\272\325\304\343s\252\363K\245<\014\'\214\352\322\006C\037\322\025\235u(d[\203l\022\244\212z\324\214\227N\2461\300\014\252\201\312|\270[BnYF\227\351l<v\204\237\347\371s\252\252g\270\302\335\336\2120\341\r\257]oRX\332\225\245N\343\004(h\254\214HxD\330PG/Vd\250}c$}\301\324\203\003A\273WC\010*M\002\347\241E\25451\307\321\264\034(+\270\007\337L\226\031K\010\013\262}\n\366\360\333E\240]}v\266`U$o\247H\224\247S\345\250\2448\246\332[\210FG \025\241)8\347\223\333\276\250j\034\346@\340yZ\036\212\244\206\263\251\251\343\220\333\315\217\272\337\322^}Q\367\204m\316\000\001X\007\340q\203\374\261\254\211n\341\270\257\262c\263b\362\360\t\037\205\311\207Xa\013X#\004aN\344\345J=\360?\363\030\371\320\235\030\272i\224\221e\311\311\206L\234R\240\031\013h\217\247\214\332yQ\311)N=\263\307=\206\232\3268;\010\260\020\321\271\306\310\317I\374>W\272t\353\335C\252\270\363\325I\213\3371-(\251+\013;\212\002A\347\222\016~ucOO=\267\355*\025M|\025$\306\316B\272+v5\026\271n\304\226\232z\023\346/v\337/\n\354\177\026\007\007\355\362tZ\272B#\006\331Ut\265\217\212r\333\341Q~ mJe)\022\232\\\026R\332c\371\177\203\331C\221\372\352\210\007\3078w\013QJ_Q\006\307>\377\000u\200n\331\357\364\322\361E\305\021lK\\\031\017GIu\321\270\000\242\221\271=\301\033}\373\360{\035m\251\\*\251\266\225\345\332\234\261\351\332\223\247 \035\216\265\217\034\177o\362\225z\203\324j\205\314\337\324\302\257I\337\260\360\036\303\201Dv\030\307\277\364\324\312jv\304,Un\257\324\022\326\305h_a\350=T\253;\246\276#/\256\225\324\033\267\250\322\025KK\215\241^I\000\234\r\304c9\t!Y\'\030\373\351\225\022i\224\263\211\036r\263S?\253+:~FFHi6\373\331E\350\343\335C\360\367\324$U\345\321\344\306XZ\243\312\216G.\260\276\351\367\344`\020~F\237[5-u>\326\020n\262\2359W\252\364\256\263\025k\330Z\016\0169ZZ\221\326\272e^\240\212\022\362\205\251\224\272\304\224\377\000\303P#;O<\036\177\246\261\322\321\2766^\313\352\332\035^\236\265\254xw\324\001\377\000\254&\nUJ-\307Wb\202\304\226U.J\366\266\225,\014\237\374\366\356u\021\2242\312\373\014+\337\235u,Ns\207\037e&\331\265\243\257\2517\207N+\310\022\026\314X\356\307AV\334\354\335\224\360r\016Nr9\367\321\244\205\364\345\241\247-9\262\253eY\254\016.\027\016\030\272\343\324\236\255t\362\310\255V:h\355\3034\305LTf-V2J\307\234\320R\022\227Z\364\257\205\247\013\340\237\216\372\263:c\274?\021\253%G\324L\250{\241\254!\256\030\373\372 }\000\257\323\357\253Z]\235t\244?\345\0209O\251\031\317\250+\276A\035\306\010?\241\325]|f\231\301\355\345i4\335I\272\203LM!\301\236\234\217\272/q\325\250\226\304\025\307\250\323\034\372\232l\260\324\247\n\202\274\306\326O\226\350G\033\277\300\240\236A\330@\301V\211\024?2\320\346\362\243W\326\311A!\231\377\000K\260?\355O\266j4\233\225\205L\211P\036I%-\371k mI<\036\307\371\200t\032\226\030]\264\204\372\t\376~\225\3224\3376T\327\216n\264\265et\375]8\212\022ju\307\222\363O\205~\361\250\305\003r\207?\205i! \366<\236p5\256\351\352B\370\367\273\205\344?\026u\250\351#l\024\322\021!\344\016\303\034\254^\344\262\226\371F>25\255\266W\316\305\304\035\304\223\177U:\331\241\312\253Nm\331\010>V\354\221\363\256K+@\260V\372u\023\252%\027\341?\304\206\210\2404\326BP8\000\352\r\311[\006R\305\021\303P\213\226\242\323l\252\017\033\267\003\333N\215W\352\2224S\354\266P\246\343\241\346\301X\340\350\373\202\305)N]\221\342\312y\311\'\367\201D\255*\035\225\236F\200\330]`\032.\020\204x\345+V\253.Uf*J\216\001\354=\26528\303\002*\0312J\220\235\240\347:3\030\tIC$\237YW:8\302J\342\360\275\324\032\345\241p\323\252m\342C\020\252-\276[Wv\322\225\002\254|dd~\272\240\325\351\332\366\270\201l)\372Ug\360\355J*\201\375.\037\336\313\364\376\035f\025B\003RZ\224\035ahJ\331q\265d)$gy?\030\326\010\260\206\200\276\317\246\231\262\3235\354\313H\004\037\\!\325z\352\035;ZPBYl\200\002\200\034\376}\273r~\337s\242\262\0078\341q\36261r\257O\r\335\"\264\255\372\033\327]\361vS\0272[\201N\037\256@CM\203\302\022I\034s\311\367\374\200\325\355\016\237\013X${\202\314\352\272\224\345\302&5\331\366N\027/\217\277\017\275\035\2501g\322m\330\265\232\262\242\274\250\263\2202\314\251\007\177\226\312U\217PIJw/\260\334\000\311\344Z|\325+/\033s`\262Z\206\227\252KT\306\313&\315\347\002\366+6\370u\377\000h}\313.\306\250\336\235cb\227W\227V\256Iy\252}*#\254\030q\262\002[\034m99\301\'\201\334\363\252\271kib\027\224]kt\315\036\256\242\"\313\220Zm{_\214z\2427WT\354\256\272\333\250\237F\2539Kt>\343n\323g\2602\352\316\305\'\016\216\001BA\343\370\202\376\332\242\257d\023E\2763\237E\254\243\216\242\210\271\222\214\016\353\030\365\253\302\277P\'u\n\241>\210\302$E\232\261 \305S\233T]\000%X\310\301\007\031\357\357\366\325\255\005|\020\322\206\277\004/?\352]\007U\251\326$\232\231\315|rX\355&\304\033[\336\377\000\262\227bx\034\271\3515\264Tkux\014\264@R\242\307\213\346(/\214\215\312\307\037\241\316\243\315\324m`;\0026\227\3205\342\255\262T\274\006\333\213_\367\272\276\350T\032\237J_\2156\225\025_HP\023-\214p\350\371\307`y\':\317\324\352\262VHK\254\2752\203A\240\240\246s\0309\365\343\360\232o~\233t\363\253\026\224\212\223\026\353*t\263\225\355\000\025\355\031\306G#\003$\037\266\234\312\271Z\320\032\252\3534j\n\226\370\025,\005\247\333\217\325c\276\247C\274:Z!\306\264CR\251\265\n\231m\247f4\\u\245\356\t\t\335\236\331\310\347\2765\247\243s\'\204\231y\262\362mjMw\243%d\032i\274o\2206\344^\333\261\217\262\320\375-\360\241P\250[1\353\265\233\225\364? \371\376\204m\005_#>\334\034q\3545\236\251\256,}\230\027\255\351\260:\206 \034\342\362y\277\272=i\364\271\265\324\337\253\324R\343\223\244\312\'\352V\263\346\004$\341>\241\3566\347\371\352\272J\211\235&\r\211W{c\021\356\n\250\361?\323\232{\322jU\205\312Xu0\322\266d\274\262\242\260\201\204\202O\335\000\375\201\032\272\323k\352d~\307\034,\276\275\247R\315E,\333@ss\177\266US\341\377\000\252T\253J\346TW\345\006\034u{?x\261\345\272\234\362\235\336\312\367\031\357\253-V\212I!\016`\272\362\037\206\335UE\246\352U4u\222Y\317q \362\017\262\321\025\373j/V\251\241\345\026\320\246\322K^h\356H \373\216q\214c\343T\220\312i\244\000\362\275\227R\205\272\215(\037\323\315\373\024\257iR\356K6\213Q\231X\211\364\264\3663\261\327\000\336>I\034\202\222x\3109\376z\227Y\266g44\335Uh\3651\351T\262:P\033\033|\327\277\037\260Y;\304\332\356;\352\375v\352L\007\177g\262\327\223\0108\356\345\006\367)D\253\000\000T\245)D\017\236\347[-*Zh\351\204`\345|\305\326}Dz\233[\222\244\001\264\033\013wU\035T)\'\0339\007\266\256\233\222\262|\270\\\242\264\013\221\330.6\246\3206\245 \024\350r@\034r\264\024\225\302+X&\005\337l3\034\273\260\025\250g\266\201\340<`+gj\270\272\\\237[L\245*b\323\225\270\257H?\032;\030\032\025E]X|E\334\225\351\212\306\306\202\022\2557aT)\233\253v\002\241\264\273\305\224\371En\004\311i_\304\243\214(\177\236\240i\225np\360\217\350\204\307\227*\361\304\'\214\235\\wEPd\253\016\036y\321\203l\222\342NNt\344\221\356\235\335\016Z\367\033\023V\342\276\237v\037H\367I\343Q\252\342\023DBk\333\270m\035\326\372\360\357\342\026\rR\324\215gW\346\267\031\310\261P\3347\335PJ\037d\'\t9=\224\006\001\036\370\317\316\2605\224Ol\227\037\252\372\033\341\377\000[\321U\351\261\351\365R\006I\030\r\027\357\317\357\352\232\257.\244\322c\303%Ufw\225\347\205\177,|\350Q\265\326\260\345z4\372\275\025+K\344\231\241\241V\222\272\347Z\205\365R\341\272\023\035\244\234>\254\202\265{\r=\324\317s\2009+\313u\377\000\2131\3229\320\3204<\372\221\307\332\312\227\255\365R\364\233|\306\275\323TZ\345\301\220\227a\251\316R\331I\310\030<m\371\036\372\320\323RC\014V\003$Yy\035WS\352\365\232\223kf\220\227\003p\017\013K\370~\353\207D+\261\351\202u\252\244\267\026{r\253\264\006\337\330\265\264V<\3040\245wG~3\220\016>\372\250\250\246\206\235\343\304\027j\372\'\244\372\352=v\207\345\332\360\312\213}=\317\276SW\373Az\355\320\033\252;]<\360\201g&\217\nT\237\256\255I\202\\\tL\202\204\215\210R\224q\200\224\214#\t\0308\344\350\365\222\351\306@\350[\200\215GK\257\212i \250\220\231\r\300=\255\352\262\370\361O\342?\246\025\030p\252\267j\252\r\306P[\037\264\242\241\335\351\007\360\225\024\356\307\310\316\212))+c\273FW\227\352\335M\326\235\'R)\346~\350\357|\201r>\374\376\026\231\350\007\213\231\375W\265\334\255T:v\334g\033w\312%\267w!e \022S\221\367\367\376z\311j\232l4u\033\001\301^\335\320\375K\'ViFr\315\273q\177_\312\272)5k&\372\206\013u&\242\277\263\377\000\245\230\255\204\234p\006x?\317T\256\245\261\273\026\314M4\'\"\341,3R\252t\312\354U\271&J?gT\243\253\313.\' \203\302\222?\231#R!c\200\271\nD\314\212\256\020x!\007\243\370\\\217}HT\367\356\325*<Z\232\037a\262\316P\360A\334\204\234\234\244\356\003\267\276\256\335ZZ\320\320\007\013#=\033\035!3\r\326p \036\326W\225zG\354*K\020Z\245}+1!%\244\222\275\303r\177\022\363\362I\'\035\206uT\360dz\223\013\367<\233\344\244JeV\004\0323\225S1\022\013\345K\214\246\326\n0\277\302\001\373\016N\272\370lC\224\347?\371{AY\323\306\247U-\353j\305]\035S\0335:\250Tx`\253%-\237\370\256c\343\036\221\367P\370\325\246\207C4\263\357\003\312\027\234|J\352Ht\255 \322\207\017\022Pl\007\247{\254W\\\274\021\036:\332\247\222\267q\200\346;ku\0247vx_,\264\271\206\3556*}\213\342\257\254\266K\"\005>\356vDd\247j\031\230\003\205\003\333\n>\241\217a\234}\264\351\364\2529\276\246\333\354\266:o^\365F\227\027\207\024\344\267\320\345:\300\361G\\\275\342\246\223t\327\337@\'\206\n\202[W\346\023\214\376\271\325T\372!\214n\214(:\277Xu\026\257L`\236s\264\362\000\001Mv\257\026\242\320m\017\245\304\253\376`F\240\370O\205\330\013*\016\323\216=\022\305~\337\242\2248\352b!\n\316w\0005cOY#\260\221$\233\245E\3331\344\307\022\330\010\004\223\355\214\352\303\346\037|\234\"6W5Ev\332RRJ\2338\354H\032\'\216=Rt\3229\017\250P\304d\225\244\2224vJ\035\312x%CS\n\010\310<\352F\344\375\241]>$f\273\032\321\215\010\307H\022d\341eC\n\033q\214}\265\231\321\374\323\334\366Q\"T4\243\345\243?\003ZpIr\220\206\255Ej*>\372\222\222\371\244\222\372\222Rx\322]\004\203ul\364k\2512\325\004\333\265\226\222\350e\030\214\352\225\205\001\376\037\357\252=B\225\245\333\233\312\003\255\030\033p}{\376U\233N]?\314D\257\332\215\006\226\236R\277\304\237\235S\020\373\355!q\365\0252\013>G\037\271*\005\325X57DH\316\253\351Z\341#\266\343\363\242\305\037\206o\335\r\256-\341\006j\217\347\271\267\333\333\355\251bcd\213\2117+\327\322O\241\313n\241N\222\266^iAM<\322\360\244\221\357\2355\256k\301k\205\324\272j\272\212I\204\320\272\317\365\034\376U\225d\370\214\267#R~\236\374\351\342d\311@\301\237\001\304\266\\\373\251\004`\037\222\017\351\252\252\215+k\257\021\302\366\235\013\342\361\247\244m>\241\016\3477\033\270H}s\352M\267\324\'\243C\266\355\365\304m\207\024\267\035}Cq\343\030\030\366\377\000\266\246PRINw8\254\377\000\\\365\275\027U\2062\231\233KG>\253Mx;\267)\024o\017\264\312\315Y\364G\022\020\364\235\313#\235\316\257\0079\355\264\016u\230\326X\371\265\007m\027\266\027\265|9a\241\3518/f\223\330\233_\335O\277o\212\215\331dV\342t\212s?S\035\206\322\325Y\325\000\215\352p#\010?\221 +\347@\245`\202F\272a\201\331l\253%\232zI\031H\357\346\021\201\333\362\252>\224\337\035Z\207Z\377\000\322\236\244\326$.SsS\364\351\230\341S\321\334Y#\320Ot\221\203\200q\306\255\253\340\247\2321$\\z*n\237\252\324\351dtU\307\315\376}\212\324>\023z\244\252\235\"\\)\225\006\335tIPR\220\2767\245[\010\376`\353;R\311\"9\n\355\322A\\\322\350\236\t\0306\365\n\330\352WQ\354\313\006\334z\275}\313Kq\312@Z\023\352\n\317\030\000\373\235*2\351\212\250\325+i\264z7U\324\235\221\267\027\365+\033\337\036(\203^}7\247\266\202\241\303l\221\001\016\035\255\264\217l$d\373\017\215\\\262\214?\352+\313u?\213T\220\304\177\207\307\271\343\273\360?E\235ov\036\277.G\253\327\304\271\017L\177\323\346\025\360\220;%#\260H\347\215hi\344\020\303\262.\002\360\275SW\256\326*]=S\256\343\373}\222\245{\245F<\306\323L\235\271\207A++O)\324\310\365\000\326\371\206T\000\363u\362\017D)\321\335\017\315\237 \205\034\240\000\006t\347\352\327n\002v\360\205\327\372wV\246\2474<HQ\317\n\300Rtzz\326H\357>\022\270~\022\345\006\346\270\250\025\221\022L\267[)^\327\032p\236\016\254$\206\031\243\270\013\273\032\230\252\375A\220R`LeH^1\273=\365\022*H\330\342@KcT\3335\311\0063\320%,\022\204\207RH\345 \236\332\211W\264\200Bk\232\000\3026\026\323i)\177b\222\257q\337Q\006\002\030B\352\021\031|)M \376\243R[&\333\'or\\\251S^\212\356\375\247i=\207\266\254\031.\356Q\356S\267Q\256\027:\2053\317\232\235\215\264\010a\264\236\303\347\363\325]\034\177*\003\200\312\214<\252\247\257\004\261%q\220\254\355V3\253\370A9*B\035\251\t/\355$\227FZ{\207P\202@=\361\256\022-\224\221J\034\332\203S\020\363\r\222\244+<\003\250\322\261\205\266\272\034\200+\212\225R\236\272Ks\336\210\244\205\016B\265K$%\256\265\356\200y]\2233\3169\030\307\270\316\200[\264\331qM\215)-\344\025\'\267\316\206yI\177I}\247@J\361\214i\300\000\273r\203\313Z\212\224\206\323\301\321\366\264\014\"\022H\261K\327\004\346\250\214}pa\267\334J\301\021\337\004\241\300\016JU\202\016\017c\2025\"&6K\007\024Xd,\231\256\2606<\036\023\207L\353\275G\361\t9\177\267j\311\207A\2412\333h\245@\036Lt\361\2047\260\036F\006I9\370\367\324\035B*M60Z/#\273\225\355=#&\255\326U{*e\021\323\303k5\270\037et\\r%\331~\027n3H\232\244\255\307CL\270\312\260S\265i\'\371\034\376Z\315S\336}E\215\224\\\022\275G^{\250\272ZwP\276\305\274\025U\305\361\207[6\374?\332\326\264Yw\0256)b\r}\305z\3221\200\265\017\342P\004\200s\216\332\321;Bc*L\215w\227\321x\3457\305J\361\243\nZ\210\257 \275\235\3357x\033\352\005^\335\257\271\026\2531Jj\245 \270\302\234^?|9\311\317\261?\327U\332\3554rGf`\330\005?\341\267YK\006\255%5Y\033d%\302\377\000\356?\372\013F\177\2646\332\274\272wyPh\025\tN=I\255\320c\325b8\261\301Z\206\327\022>\311P?\374\207\333Q\206\232(v\2229\001E\370\235\324\265\032\204-\244\002\315\0076\340\252&\211K\2059\234\345$\351\262\222\323\205\343F\373\262\225\257ku\"ii\t\030\034\247#S!\227h\302W(\023\264\251$\245jl\220\2021\367\321\013\356\225\312\230\3532g\224\215\277\203\330\r!\312\342\031&\224K\312%\275\252\317\177\276\211\272\303+\240\221\302F\352\255\221\"\262\332*\264\252z>\245\235\306B\207\033\322\006A\307\271\325\266\233Z\3268\261\347\010\215{\273\245\n2?\336\271\215Fy;]e9u_)\037\371\215Y\313\272 ]|\024V\336\312t\372\264\272=tL\212\254\224+\224\023\302\207\301\373j$Q6f\026\271t\200yV\025\247&\005\327Kjs\220\220\313\212*\033Pr\006\016\251\252\231$\023X\034(\316\303\210\013\335B\210\206T\2446\254\343\276\335(\334I\312\345\312\001*\200\304\222\265<\362\302\262G\330j{e\261\302(q\272\347Ne\016)N:2\203\220Nu\327\341\253\216\372\222\365\317k[\261\224\247\236\250ajW\341\335\3165.\232\242b-dt%\252\025\276\367\374)D\343\277\253RL\263\372$\273\177\272\264|nK\371\377\000\253\\3L\002Jm6\223\000(Dm\033\202\273\r\002I\346\262k\235\264+\363\243\236\026\351\206\226\335\337z\341\206\024\235\314\307X\345c\357\241\211\236~\244\007\277r\025\326\253\212\337\267\331]:\234\2044\3228i#\271\323\232\337\020\335pd*\242\r\373Pi%ji*J\217\243v\272\372h\334\353\224\353\004^\337\272\227 \024T\\\303\205\\g\2664\'\300\320,\022\260G\315J>\314\027\023\310\371\324S\021C<\2415+\232\014\007\013G\326\254q\203\333F\212\022pQ@\312O\270*\257V%\0059\302A;G\306\247D\301\030\262#\230\034\333/\264\013\212\347\264\304\227-j\333\320\325-\237-\377\000(\376!\234\216\017\031\037=\306t\351a\206r<Q{+\035?U\324t\262~Q\345\245\3309\354\254\232\027\\\351\257\370js\244U\031\217\376\335\\\325\2422|\265+\316K\216\205\344\253\260\374J\367\366\373\352\242]0\273SmC\007\224\177\302\364J~\263\241\035\023=\004\357.\234\234\177\355A\247t\222m1\204\310\235\010HqI\031J\371\000\375\264\307j,y\332\325\344\373\215\200<\216S\r\241J\251\265!\250\261\242\272\310aYl\000x9\3565\n\242F\273\224\233$\221\312\331\030|\315\341j\273\207\305\254>\257\364b\211\321\237\020\266[\265g\255\234\246\203tS$\245\231\361\332)\000\264\260\264\224\270\236\023\337\037\204{\363\256>\264K\000\211\343\216\352\352]a\265Qm\235\204\223\335Stg\342G\250\226\232x\371JQ\t+\343\217l\343\201\252\351@\354\250\034\000q\267\nM\335KfDV\3440\002\326\025\205\016\372d$\335q\014\251\300\210\305)\010z6\307T3\310\340\352D\177\352\024\220\030\017?\nAa\270\233\302\273\234~\035HIx\250G\r,\276\352\277\021\340cMrH#\212mR\266\2519HV\024>\337\032\353\234\032\002\353yU\025::i7\215Z+i\r\224:\240\332A\316\023\273?\333\032\324H\355\324\214>\310\3166Q\253iy\345\227\226\337\'\337:tI\355\372Q+\n\366\223A\234\325!\365\2451]s\324\2429A>\372ee3gf\346\362\023$\001Y)\203\035\370\177X\324\225-K\344(+9\3256\322\n\005\212\027%\000(\245I\003\003\007\357\242\016WA7A\250t\212\225N3\245\016\006\320\220}D\366\321f\232(\334\010\010\216\372\222\314\236\225\335\025j\302\233\213!.\241g\320\352\224\177\226\247\307\251\3232,\213#\\),t\236\253\006A\205P{b\322pq\330\351\216\324\242p$%p\272T\254\311tI\214\260\227\013\241\336\001\037?\032tS\3078\302W\n\327\360\353\321&\356\333\271\227\347\305+f\030\363\235I\355\307l\376\272\205W\\\330\030n\207!\004aZ\335w\276\332\351\355\001\306\210\005a\033#\260>~\303P\250\014\2257\220\360\204\320IY\n\362\270\352\027=YS\347:\245\022\254\224\347\266\264ln\326\251#\205\342\236\226d\255\014\036\000\366:\344\227\262\033\301\272,\210`\020@\355\333\003B\034&/kq\366\223\222\342\276\334\235u%\002f\034>a\'$\362N\210\244.Ia\267\t\334G\347\256\\$\275\026\232i9J\206u\303\224\223\017I\354\361t\336,\270\264\253de\371\212ZGb;j\rl\346\030m\352\230\361\205\1772\333m\263\265`mO\004c\261\326ov\374\250\203\222\213\333\214\322\366\025\226\322\225\023\335I\032\013\331\271\327NC\256(NH\234\265\303\005^\237a\2424mm\222JUI\263i\316\225:\263\351<\343\333F\031\030IL\246u>\235\t\2653UuJW\033\026G})!.\030J\305I\231z\323kN\264|\245\006\322;\254\016s\24627\264d\244\202\334\327,8\022P\272{\343\003\272@\357\251Lc\236-d\224\n\205^l\204\245\347RR\223\333]\360\205\354\222\0352raGz\254\246\034q\246\033.\255\r\247%@rt\337\005\257\221\255=\323\232\r\325cDy\272\213\365\013\216I\036|\271\007\003\035\201\311\377\000Mh\252\006\310\333\030\354\212\365\366td<\326w\017\347\256\260\006\233\'4\215\2504\350\251e`\203\251\027\272z\351\022\350\270\351\355\226\240\326\3442\331\030\010C\247]\360ay\310Iw\205\324\273\222\023\037J\353\355HH\341%\366\367\020>3\246IE\003\217\010^\030ON9\"$?!\221\205\016\344\036\343Te\241\356\363&\"\266\304\227\033RV\215\212!@\216{s\241T\306\324\227\256\242\326\333B\331Ld\241\307\311\005^_\360\343L\243\214\356!\343\013\240\023\302\215\002\273\016\264\204\245\332j\224\363D\024\356Omu\360\311\031$:\301\"\322\026\250\360\237C\246S\354Y\267D\204$.C\245##\260H\344\177=U\326\310\342\335\205qgO\022\327C\327u\3759\206\334\375\304\"\244\240g\337\334\353GA\030\206\231\240\362Q#\356\251\'\231R\337Q\377\000\233\347V\340\330\".\320I\215(+\031\322w\322\222c`\002\330X:\214y@w\324\275\242 \220\216\370\374\364\335\345qC\231\025\264\273\260\034\201\366\321\030\363\265\033sJ\342\030BIHO}ut\345\023\244\333)\236\023\277\007?\302\006\232d\014ZM;D3\301\273\224\347dZ\357\333\312~E=\302\227\026\001\030\370\370\325>\241#H\001\305;W\320\205\005\037\215t\347O\270^e\"%Q\222\254\376<\352\2444\034\265d\r\310\030Fi\262\232~HLi\005\010\003\261:\351\004.X\242\220k\321\340\324\txy\210\376=\274\351\217\210\274^\351X\2417X\240\\/\007i\321\024\0018u#I\221\271\203%+\024\203V\2433\022yR\033;R\274\200\257mYF72\341<\256\216\255\251R\032\215\023\323\204\376\363Q\334\034\002\032\361P\245\307\nCk\001JJ\267\034\351\320\310\343\301]\261^\345\334\360\026\241\031\370E;F8O\032!\205\316\310+\226\272\213R\255Sa\333\265iKd\371\177\263\334m\034\343\005C\003L\206)<v\216r\021c!\274\252\232\327\\W)\205\010\177*C\236\244\017\313ZZ\206\220\373\224\367\202xO\266\305\201\002\263O\375\241T|4\320F\364\244+\005\177\351\252\271\253\004N\332\321r\206plQ\212E\237\323x\220\222\206]D\331ks*K\214\347\034\344\014\235B\236\252\260\216,\227\210B\361|Y\264\n\317\225LE\025\270\356\r\277\274i)I\0039<\2156\232\262\242!rn\227\210PK\243\241\366\300\205\365T\320\374e\245\030\010\337\2708\254{\347\266\254i\265YK\303]\224\346\311w\013\360\277\377\331"
      }
    }
  }
}

def decode_image(image_string):
    image = tf.image.decode_jpeg(image_string, channels=3)
    image = tf.cast(image, tf.float32) / 255.0
    image = tf.reshape(image, IMG_SIZE)
    return image

def read_tfrecord(example, labeled=True):
    TFREC_FORMAT = {
        "image" : tf.io.FixedLenFeature([],tf.string),
        "class" : tf.io.FixedLenFeature([],tf.int64),
        "id"    : tf.io.FixedLenFeature([],tf.string),
    } if labeled else {
        "image" : tf.io.FixedLenFeature([],tf.string),
        "id"    : tf.io.FixedLenFeature([],tf.string),
    }
    example = tf.io.parse_single_example(example, TFREC_FORMAT)
    image = decode_image(example['image'])
    label = tf.cast(example['class'], tf.int32) if labeled else example['id']
    return image, label

Data augmentation

def transform_shear(image, height, shear):
    # input image - is one image of size [dim,dim,3] not a batch of [b,dim,dim,3]
    # output - image randomly sheared
    DIM = height
    XDIM = DIM%2 #fix for size 331
    
    shear = shear * tf.random.uniform([1],dtype='float32')
    shear = math.pi * shear / 180.
        
    # SHEAR MATRIX
    one = tf.constant([1],dtype='float32')
    zero = tf.constant([0],dtype='float32')
    c2 = tf.math.cos(shear)
    s2 = tf.math.sin(shear)
    shear_matrix = tf.reshape(tf.concat([one,s2,zero, zero,c2,zero, zero,zero,one],axis=0),[3,3])    

    # LIST DESTINATION PIXEL INDICES
    x = tf.repeat( tf.range(DIM//2,-DIM//2,-1), DIM )
    y = tf.tile( tf.range(-DIM//2,DIM//2),[DIM] )
    z = tf.ones([DIM*DIM],dtype='int32')
    idx = tf.stack( [x,y,z] )
    
    # ROTATE DESTINATION PIXELS ONTO ORIGIN PIXELS
    idx2 = K.dot(shear_matrix,tf.cast(idx,dtype='float32'))
    idx2 = K.cast(idx2,dtype='int32')
    idx2 = K.clip(idx2,-DIM//2+XDIM+1,DIM//2)
    
    # FIND ORIGIN PIXEL VALUES 
    idx3 = tf.stack( [DIM//2-idx2[0,], DIM//2-1+idx2[1,]] )
    d = tf.gather_nd(image, tf.transpose(idx3))
        
    return tf.reshape(d,[DIM,DIM,3])

def transform_shift(image, height, h_shift, w_shift):
    # input image - is one image of size [dim,dim,3] not a batch of [b,dim,dim,3]
    # output - image randomly shifted
    DIM = height
    XDIM = DIM%2 #fix for size 331
    
    height_shift = h_shift * tf.random.uniform([1],dtype='float32') 
    width_shift = w_shift * tf.random.uniform([1],dtype='float32') 
    one = tf.constant([1],dtype='float32')
    zero = tf.constant([0],dtype='float32')
        
    # SHIFT MATRIX
    shift_matrix = tf.reshape(tf.concat([one,zero,height_shift, zero,one,width_shift, zero,zero,one],axis=0),[3,3])

    # LIST DESTINATION PIXEL INDICES
    x = tf.repeat( tf.range(DIM//2,-DIM//2,-1), DIM )
    y = tf.tile( tf.range(-DIM//2,DIM//2),[DIM] )
    z = tf.ones([DIM*DIM],dtype='int32')
    idx = tf.stack( [x,y,z] )
    
    # ROTATE DESTINATION PIXELS ONTO ORIGIN PIXELS
    idx2 = K.dot(shift_matrix,tf.cast(idx,dtype='float32'))
    idx2 = K.cast(idx2,dtype='int32')
    idx2 = K.clip(idx2,-DIM//2+XDIM+1,DIM//2)
    
    # FIND ORIGIN PIXEL VALUES 
    idx3 = tf.stack( [DIM//2-idx2[0,], DIM//2-1+idx2[1,]] )
    d = tf.gather_nd(image, tf.transpose(idx3))
        
    return tf.reshape(d,[DIM,DIM,3])

def transform_rotation(image, height, rotation):
    # input image - is one image of size [dim,dim,3] not a batch of [b,dim,dim,3]
    # output - image randomly rotated
    DIM = height
    XDIM = DIM%2 #fix for size 331
    
    rotation = rotation * tf.random.uniform([1],dtype='float32')
    # CONVERT DEGREES TO RADIANS
    rotation = math.pi * rotation / 180.
    
    # ROTATION MATRIX
    c1 = tf.math.cos(rotation)
    s1 = tf.math.sin(rotation)
    one = tf.constant([1],dtype='float32')
    zero = tf.constant([0],dtype='float32')
    rotation_matrix = tf.reshape(tf.concat([c1,s1,zero, -s1,c1,zero, zero,zero,one],axis=0),[3,3])

    # LIST DESTINATION PIXEL INDICES
    x = tf.repeat( tf.range(DIM//2,-DIM//2,-1), DIM )
    y = tf.tile( tf.range(-DIM//2,DIM//2),[DIM] )
    z = tf.ones([DIM*DIM],dtype='int32')
    idx = tf.stack( [x,y,z] )
    
    # ROTATE DESTINATION PIXELS ONTO ORIGIN PIXELS
    idx2 = K.dot(rotation_matrix,tf.cast(idx,dtype='float32'))
    idx2 = K.cast(idx2,dtype='int32')
    idx2 = K.clip(idx2,-DIM//2+XDIM+1,DIM//2)
    
    # FIND ORIGIN PIXEL VALUES 
    idx3 = tf.stack( [DIM//2-idx2[0,], DIM//2-1+idx2[1,]] )
    d = tf.gather_nd(image, tf.transpose(idx3))
        
    return tf.reshape(d,[DIM,DIM,3])

def data_augment(image, label):
    p_rotation = tf.random.uniform([], 0, 1.0, dtype=tf.float32)
    p_spatial = tf.random.uniform([], 0, 1.0, dtype=tf.float32)
    p_rotate = tf.random.uniform([], 0, 1.0, dtype=tf.float32)
    p_pixel = tf.random.uniform([], 0, 1.0, dtype=tf.float32)    
    p_shear = tf.random.uniform([], 0, 1.0, dtype=tf.float32)
    p_shift = tf.random.uniform([], 0, 1.0, dtype=tf.float32)
    p_crop = tf.random.uniform([], 0, 1.0, dtype=tf.float32)
    
    # Flips
    if p_spatial >= .2:
        image = tf.image.random_flip_left_right(image)
        image = tf.image.random_flip_up_down(image)
        
    # Rotates
    if p_rotate > .75:
        image = tf.image.rot90(image, k=3) # rotate 270º
    elif p_rotate > .5:
        image = tf.image.rot90(image, k=2) # rotate 180º
    elif p_rotate > .25:
        image = tf.image.rot90(image, k=1) # rotate 90º
    
    if p_rotation >= .3: # Rotation
        image = transform_rotation(image, height=HEIGHT, rotation=45.)
    if p_shift >= .3: # Shift
        image = transform_shift(image, height=HEIGHT, h_shift=15., w_shift=15.)
    if p_shear >= .3: # Shear
        image = transform_shear(image, height=HEIGHT, shear=20.)
        
    # Crops
    if p_crop > .4:
        crop_size = tf.random.uniform([], int(HEIGHT*.7), HEIGHT, dtype=tf.int32)
        image = tf.image.random_crop(image, size=[crop_size, crop_size, CHANNELS])
    elif p_crop > .7:
        if p_crop > .9:
            image = tf.image.central_crop(image, central_fraction=.7)
        elif p_crop > .8:
            image = tf.image.central_crop(image, central_fraction=.8)
        else:
            image = tf.image.central_crop(image, central_fraction=.9)
            
    image = tf.image.resize(image, size=[HEIGHT, WIDTH])
        
    # Pixel-level transforms
    if p_pixel >= .2:
        if p_pixel >= .8:
            image = tf.image.random_saturation(image, lower=0, upper=2)
        elif p_pixel >= .6:
            image = tf.image.random_contrast(image, lower=.8, upper=2)
        elif p_pixel >= .4:
            image = tf.image.random_brightness(image, max_delta=.2)
        else:
            image = tf.image.adjust_gamma(image, gamma=.6)

    return image, label
def load_dataset(filenames, labeled=True, ordered=False):
    ignore_order = tf.data.Options()
    if not ordered: ignore_order.experimental_deterministic = False
    ds = tf.data.TFRecordDataset(filenames, num_parallel_reads=AUTOTUNE)
    ds = ds.with_options(ignore_order)
    ds = ds.map(partial(read_tfrecord, labeled=labeled), num_parallel_calls=AUTOTUNE)
    return ds

def get_train_dataset(filenames, bs=GLOBAL_BATCH_SIZE):
    ds = load_dataset(filenames, labeled=True)
    ds = ds.map(data_augment, num_parallel_calls=AUTOTUNE)
    ds = ds.shuffle(2048)
    ds = ds.batch(bs, drop_remainder=True)
    # prefetch the next batch while training
    ds = ds.prefetch(AUTOTUNE)
    return ds
 
def get_valid_dataset(filenames, bs=GLOBAL_BATCH_SIZE, ordered=False):
    ds = load_dataset(filenames, labeled=True, ordered=ordered)
    ds = ds.batch(bs, drop_remainder=True)
    ds= ds.cache()
    # prefetch the next batch while training
    ds = ds.prefetch(AUTOTUNE)
    return ds

def get_test_dataset(filenames, bs=GLOBAL_BATCH_SIZE, ordered=False): 
    ds = load_dataset(filenames, labeled=False, ordered=ordered)
    ds = ds.batch(bs)
    # prefetch the next batch while training
    ds = ds.prefetch(AUTOTUNE)
    return ds
train_ds = get_train_dataset(train_filenames)
valid_ds = get_valid_dataset(valid_filenames)
test_ds  = get_test_dataset(test_filenames)
def show_images(ds):
    _,axs = plt.subplots(3,3,figsize=(16,16))
    for ((x, y), ax) in zip(ds.take(9), axs.flatten()):
        ax.imshow((x.numpy()*255).astype(np.uint8))
        ax.set_title(CLASSES[y])
        ax.axis('off')

Custom Model

from tensorflow.keras.applications import Xception
from tensorflow.keras.applications import DenseNet121, DenseNet169, DenseNet201
from tensorflow.keras.applications import ResNet50V2, ResNet101V2, ResNet152V2
from tensorflow.keras.applications import InceptionV3
from tensorflow.keras.applications import InceptionResNetV2
class Flower_Classifier(tf.keras.models.Model):
    def __init__(self):
        super().__init__()
        self.image_embedding_layers = []
        self.image_embedding_layers.append(
            Xception(weights='imagenet',
                     include_top=False,
                     input_shape=IMG_SIZE))
        self.image_embedding_layers.append(
            ResNet152V2(weights='imagenet',
                        include_top=False,
                        input_shape=IMG_SIZE))
        self.image_embedding_layers.append(
            InceptionResNetV2(weights='imagenet',
                              include_top=False,
                              input_shape=IMG_SIZE))
        self.pooling_layer = tf.keras.layers.GlobalAveragePooling2D()

        self.layer_normalization_layers = []
        self.prob_dist_layers = []
        for model_idx, image_embedded_layer in enumerate(self.image_embedding_layers):
            self.layer_normalization_layers.append(
                tf.keras.layers.LayerNormalization(epsilon=1E-6))
            self.prob_dist_layers.append(
                tf.keras.layers.Dense(NCLASSES, activation='softmax',
                                      name=f'prob_dist_{model_idx}'))

        kernel_init = tf.constant_initializer(
            np.array([0.86690587, 1.0948032, 1.1121726])) 
        bias_init = tf.constant_initializer(
            np.array([-0.13309559, 0.09480964, 0.11218266]))

        self.prob_dist_weight = tf.keras.layers.Dense(
            len(self.image_embedding_layers), activation="softmax",
            kernel_initializer=kernel_init,
            bias_initializer=bias_init,
            name='prob_dist_weight')

    def call(self, inputs, training=False):
        all_model_outputs=[]
        for i in range(len(self.image_embedding_layers)):
            embedding = self.image_embedding_layers[i](inputs, training=training)
            pooling = self.pooling_layer(embedding, training=training)
            pooling_normalized = self.layer_normalization_layers[i](pooling, training=training)
            model_output = self.prob_dist_layers[i](pooling_normalized, training=training)
            all_model_outputs.append(model_output)

        all_model_outputs = tf.stack(all_model_outputs, axis=1)
        prob_dist_weight = self.prob_dist_weight(tf.constant(1, shape=(1,1)), training=training)
        prob_dist = tf.linalg.matmul(prob_dist_weight, all_model_outputs)
        prob_dist = prob_dist[:, 0, :]
        return prob_dist

    def model(self):
        x = tf.keras.Input(shape=IMG_SIZE)
        return tf.keras.Model(inputs=[x], outputs=self.call(x))
with strategy.scope():
    model = Flower_Classifier()
Downloading data from https://storage.googleapis.com/tensorflow/keras-applications/xception/xception_weights_tf_dim_ordering_tf_kernels_notop.h5
83689472/83683744 [==============================] - 1s 0us/step
Downloading data from https://storage.googleapis.com/tensorflow/keras-applications/resnet/resnet152v2_weights_tf_dim_ordering_tf_kernels_notop.h5
234553344/234545216 [==============================] - 2s 0us/step
Downloading data from https://storage.googleapis.com/tensorflow/keras-applications/inception_resnet_v2/inception_resnet_v2_weights_tf_dim_ordering_tf_kernels_notop.h5
219062272/219055592 [==============================] - 2s 0us/step

Note: There is a workaround to show summary() of a subclassing model which is introduced in this video.

Warning: Using this might casue a problem that the model goes outside of the scope. Will figure out a workaround for this.
#model.model().summary()

Custom Optimizer Schedule

class CustomCyclicSchedule(tf.keras.optimizers.schedules.LearningRateSchedule):
    def __init__(self, n_step, lr_max, div=25.0, div_final=1e5,
                 pct_start=0.25, staircase=False, cycle=False):
        self.lr_start = lr_max / div
        self.lr_max = lr_max
        self.lr_min = lr_max / div_final
        self.rising_steps = int(n_step * pct_start)
        self.rising_a = (lr_max-self.lr_min) / (self.rising_steps-1)
        self.rising_b = self.lr_min

        self.falling_steps = int(n_step - self.rising_steps)
        self.falling_rate = self.lr_min / lr_max
        self.cycle = tf.constant(cycle, dtype=tf.bool)
        self.decay_fn = tf.keras.experimental.CosineDecay(
            initial_learning_rate = lr_max,
            decay_steps = self.falling_steps,
            alpha = self.lr_min)

    def __call__(self, step):
        """ `step` is actually the step index, starting at 0. """
        lr = tf.cond(step < self.rising_steps,
                     lambda : self.rising_a*tf.cast(step, tf.float32) + self.rising_b,
                     lambda : self.decay_fn(tf.cast(step-self.rising_steps, tf.int32)))
        return tf.cast(lr, tf.float32)
NSTEPS = math.ceil(count_data_items(train_filenames) / GLOBAL_BATCH_SIZE) * EPOCHS
LR_MAX = 1e-4 #@param {type: "number"}
LR_MAX *= strategy.num_replicas_in_sync
DIV = 25.0 #@param {type: "number"}
DIV_FINAL = 1e5 #@param {type: "number"}
PCT_START = 0.25#@param {type: "number"}

with strategy.scope():
    schedule = CustomCyclicSchedule(
        n_step=NSTEPS,
        lr_max=LR_MAX,
        div=DIV,
        div_final=DIV_FINAL,
        cycle=False)

xps = tf.range(NSTEPS)
yps = [schedule(x) for x in xps]
fig,ax = plt.subplots(1,1,figsize=(8,5),facecolor='#F0F0F0')
ax.plot(xps, yps)
ax.set_facecolor('#F8F8F8')
ax.set_xlabel('iteration')
ax.set_ylabel('learning rate')

print('{:d} total epochs and {:d} steps per epoch'
        .format(EPOCHS, NSTEPS // EPOCHS))
8 total epochs and 200 steps per epoch
with strategy.scope():
    opt = tf.keras.optimizers.Adam(schedule)

Custom Loss Function

Note: About why we set reduction to &#8217;none&#8217;, please check this tutorial. In particular, read the paragraph. If using tf.keras.losses classes (as in the example below), the loss reduction needs to be explicitly specified to be one of NONE or SUM. AUTO and SUM_OVER_BATCH_SIZE are disallowed when used with tf.distribute.Strategy.

Note: About why we use tf.nn.compute_average_loss, please check this tutorial

Warning: While trained with BATCH_SIZE = 8 * strategy.num_replicas_in_sync, I got nan values. Since we pass probability distribution to CategoricalCrossentropy with from_logits = False, which has numerical unstability issue, we use the same trick in the source code to avoid such unstabiltiy
from tensorflow.python.ops import clip_ops
from tensorflow.python.framework import constant_op

While training with GLOBAL_BATCH_SIZE = 8 * replicas I got nan values. Since we pass probability distribution to CategoricalCrossentropy with from_logits = False, which has numerical unstability issue, we use the same trick in the source code to avoid such unstabiity.

def _constant_to_tensor(x, dtype):
    return constant_op.constant(x, dtype=dtype)

with strategy.scope():
    loss_object = tf.keras.losses.CategoricalCrossentropy(
                from_logits=False, reduction='none', label_smoothing=0.1)

    def loss_function(labels, prob_dists, sample_weights=1.0):
        epsilon_ = _constant_to_tensor(tf.keras.backend.epsilon(), prob_dists.dtype.base_dtype)
        prob_dists = clip_ops.clip_by_value(prob_dists, epsilon_, 1 - epsilon_)
        labels = tf.keras.backend.one_hot(labels, NCLASSES)
        loss = loss_object(labels, prob_dists)
        loss = tf.nn.compute_average_loss(loss, global_batch_size=GLOBAL_BATCH_SIZE)
        return loss

Custom Metric Function

def get_metrics(name):
    loss = tf.keras.metrics.Mean(name=f'{name}_loss', dtype=tf.float32)
    acc = tf.keras.metrics.SparseCategoricalAccuracy(name=f'{name}_acc', dtype=tf.float32)
    return loss, acc

with strategy.scope():
    train_loss_obj, train_acc_obj = get_metrics('train')
    valid_loss_obj, valid_acc_obj = get_metrics('valid')

Distributed datasets

There are two APIs to create a tf.distribute.DistributedDataset object: tf.distribute.Strategy.experimental_distribute_dataset(dataset) and tf.distribute.Strategy.distribute_datasets_from_function(dataset_fn).

When to use which?

When you have a tf.data.Dataset instance, and the regular batch splitting (i.e. re-batch the input tf.data.Dataset instance with a new batch size that is equal to the global batch size divided by the number of replicas in sync) and autosharding (i.e. the tf.data.experimental.AutoShardPolicy options) work for you, use the former API. Otherwise, if you are not using a canonical tf.data.Dataset instance, or you would like to customize the batch splitting or sharding, you can wrap these logic in a dataset_fn and use the latter API. Both API handles prefetch to device for the user. For more details and examples, follow the links to the APIs.

train_dist_ds = strategy.experimental_distribute_dataset(train_ds)

valid_ds = get_valid_dataset(valid_filenames, ordered=True)
valid_dist_ds = strategy.experimental_distribute_dataset(valid_ds)

test_ds = get_test_dataset(test_filenames, ordered=True).map(lambda img,lbl: img)
test_dist_ds = strategy.experimental_distribute_dataset(test_ds)
train_dist_ds = strategy.distribute_datasets_from_function(
    dataset_fn = lambda _: (get_train_dataset(train_filenames, bs=BATCH_SIZE)
                                .take(10*strategy.num_replicas_in_sync)))
valid_dist_ds = strategy.distribute_datasets_from_function(
    dataset_fn = lambda _: (get_valid_dataset(valid_filenames, ordered=True, bs=BATCH_SIZE)
                                .take(10*strategy.num_replicas_in_sync)))

Custom Training Loop

Train Step

train_input_signature = [
    tf.TensorSpec(shape=(None, None, None, None), dtype=tf.float32),
    tf.TensorSpec(shape=(None,), dtype=tf.int32)
]

@tf.function(input_signature=train_input_signature)
def train_step(images, labels):
    with tf.GradientTape() as tape:
        prob_dists = model(images, training=True)
        loss = loss_function(labels, prob_dists)

    grads = tape.gradient(loss, model.trainable_variables)
    grads, global_norm = tf.clip_by_global_norm(grads, clip_norm=1.0)
    opt.apply_gradients(zip(grads, model.trainable_variables))
    train_acc_obj(labels, prob_dists)
    return loss

@tf.function
def distributed_train_step(inputs):
    (images, labels) = inputs
    per_replica_losses = strategy.run(train_step, args=(images, labels))
    return strategy.reduce(tf.distribute.ReduceOp.SUM, per_replica_losses, axis=None)
metrics_names = ['train_loss','train_acc'] 
pbar = tf.keras.utils.Progbar(10, width=15, stateful_metrics=metrics_names)

train_loss_obj.reset_states()
train_acc_obj.reset_states()
train_iter = iter(train_dist_ds)

for i,train_inputs in enumerate(train_dist_ds):
    if i == 10: break
    train_loss = distributed_train_step(train_inputs)
    train_loss_obj(train_loss)
    values=[('train_loss', train_loss_obj.result()),
            ('train_acc', train_acc_obj.result())]
    pbar.add(1, values=values)
10/10 [===============] - 395s 345ms/step - train_loss: 5.0140 - train_acc: 0.0172

Valid step

valid_input_signature = train_input_signature

@tf.function(input_signature=valid_input_signature)
def valid_step(images, labels):
    prob_dists = model(images, training=False)
    loss = loss_function(labels, prob_dists, sample_weights=None)
    valid_acc_obj(labels, prob_dists)
    return loss, prob_dists

@tf.function
def distributed_valid_step(inputs):
    (images, labels) = inputs
    losses, prob_dists = strategy.run(valid_step, args=(images, labels))
    return strategy.reduce(tf.distribute.ReduceOp.SUM, losses, axis=None), prob_dists
metrics_names = ['train_loss','train_acc','valid_loss','valid_acc'] 
pbar = tf.keras.utils.Progbar(20, width=15, stateful_metrics=metrics_names)
    
train_loss_obj.reset_states()
train_acc_obj.reset_states()
valid_loss_obj.reset_states()
valid_acc_obj.reset_states()

for i,train_inputs in enumerate(train_dist_ds):
    if i == 10: break
    train_loss = distributed_train_step(train_inputs)
    train_loss_obj(train_loss)
    values=[('train_loss', train_loss_obj.result()),
            ('train_acc', train_acc_obj.result())]
    pbar.add(1, values=values)

for i,valid_inputs in enumerate(valid_dist_ds):
    if i == 10: break
    valid_loss = distributed_valid_step(valid_inputs)
    valid_loss_obj(valid_loss)
    values=[('valid_loss', valid_loss_obj.result()),
            ('valid_acc', valid_acc_obj.result())]
    pbar.add(1, values=values)
20/20 [===============] - 38s 2s/step - train_loss: 4.4410 - train_acc: 0.1094 - valid_loss: 4.2611 - valid_acc: 0.1344

Test step

test_input_signature = [
    tf.TensorSpec(shape=(None, None, None, None), dtype=tf.float32)]
    
@tf.function(input_signature=test_input_signature)
def test_step(images):
     prob_dists = model(images, training=False)
     return prob_dists

@tf.function
def distributed_test_step(inputs):
    images = inputs
    prob_dists = strategy.run(test_step, args=(images,))
    return prob_dist

Training loop

N_TRAIN_STEPS = count_data_items(train_filenames) // GLOBAL_BATCH_SIZE
N_VALID_STEPS = count_data_items(valid_filenames) // GLOBAL_BATCH_SIZE
N_TOTAL_STEPS = N_TRAIN_STEPS + N_VALID_STEPS
history = {
    "train_loss": [], "valid_loss": [],
    "train_acc": [],  "valid_acc": []
}

valid_ds = get_valid_dataset(valid_filenames, ordered=True)
valid_dist_ds = strategy.experimental_distribute_dataset(valid_ds)

for epoch in range(EPOCHS):

    train_ds = get_valid_dataset(train_filenames)
    train_dist_ds = strategy.experimental_distribute_dataset(train_ds)
    
    metrics_names = ['train_loss','train_acc','valid_loss','valid_acc']
    pbar = tf.keras.utils.Progbar(N_TOTAL_STEPS, width=15, stateful_metrics=metrics_names)

    train_loss_obj.reset_states()
    train_acc_obj.reset_states()
    valid_loss_obj.reset_states()
    valid_acc_obj.reset_states()

    for train_inputs in train_dist_ds:
        train_loss = distributed_train_step(train_inputs)
        train_loss_obj(train_loss)
        values=[('train_loss', train_loss_obj.result()),
                ('train_acc', train_acc_obj.result())]
        pbar.add(1, values=values)

    history['train_loss'].append(train_loss_obj.result())
    history['train_acc'].append(train_acc_obj.result())

    for valid_inputs in valid_dist_ds:
        valid_loss, _ = distributed_valid_step(valid_inputs)
        valid_loss_obj(valid_loss)
        values=[('valid_loss', valid_loss_obj.result()),
                ('valid_acc', valid_acc_obj.result())]
        pbar.add(1, values=values)

    history['valid_loss'].append(valid_loss_obj.result())
    history['valid_acc'].append(valid_acc_obj.result())
257/257 [===============] - 76s 295ms/step - train_loss: 2.3747 - train_acc: 0.5747 - valid_loss: 4.3283 - valid_acc: 0.1188
257/257 [===============] - 76s 294ms/step - train_loss: 2.2650 - train_acc: 0.5910 - valid_loss: 5.1784 - valid_acc: 0.0474
257/257 [===============] - 77s 295ms/step - train_loss: 2.0854 - train_acc: 0.6501 - valid_loss: 2.7212 - valid_acc: 0.5191
257/257 [===============] - 77s 295ms/step - train_loss: 1.6544 - train_acc: 0.7584 - valid_loss: 2.1080 - valid_acc: 0.6239
257/257 [===============] - 78s 300ms/step - train_loss: 1.3253 - train_acc: 0.8485 - valid_loss: 1.4403 - valid_acc: 0.8144
257/257 [===============] - 78s 301ms/step - train_loss: 1.0962 - train_acc: 0.9121 - valid_loss: 1.3059 - valid_acc: 0.8508
257/257 [===============] - 79s 303ms/step - train_loss: 0.9971 - train_acc: 0.9414 - valid_loss: 1.2708 - valid_acc: 0.8672
257/257 [===============] - 79s 302ms/step - train_loss: 0.9615 - train_acc: 0.9516 - valid_loss: 1.2731 - valid_acc: 0.8615
def show_history(history):
    topics = ['loss', 'acc']
    groups = [{k:v for (k,v) in history.items() if topic in k} for topic in topics]
    _,axs = plt.subplots(1,2,figsize=(15,6),facecolor='#F0F0F0')
    for topic,group,ax in zip(topics,groups,axs.flatten()):
        for (_,v) in group.items(): ax.plot(v)
        ax.set_facecolor('#F8F8F8')
        ax.set_title(f'{topic} over epochs')
        ax.set_xlabel('epoch')
        ax.set_ylabel(topic)
        ax.legend(['train', 'valid'], loc='best')
show_history(history)
all_valid_preds = []
pbar = tf.keras.utils.Progbar(N_VALID_STEPS, width=15)
for valid_inputs in valid_dist_ds:
    _, valid_preds = distributed_valid_step(valid_inputs)
    all_valid_preds.append(tf.concat(valid_preds.values, axis=0).numpy())
    pbar.add(1)
all_valid_preds = np.concatenate(all_valid_preds, axis=0, out=None)
58/58 [===============] - 7s 108ms/step
cm_trues = (valid_ds.map(lambda im,lbl: lbl)
                    .unbatch()
                    .batch(count_data_items(valid_filenames))
                    .as_numpy_iterator()
                    .next())
cm_preds = np.argmax(all_valid_preds, axis=-1)
cmat = confusion_matrix(cm_trues, cm_preds, labels=range(len(CLASSES)))
f1 = f1_score(cm_trues, cm_preds,
              labels=range(NCLASSES), average='macro')
precision = precision_score(cm_trues, cm_preds,
              labels=range(NCLASSES), average='macro')
recall = recall_score(cm_trues, cm_preds,
              labels=range(NCLASSES), average='macro')    
def show_confusion_matrix(cmat, f1, precision, recall):
    plt.figure(figsize=(15,15))
    ax = plt.gca()
    ax.matshow(cmat, cmap='Blues')
    ax.set_xticks(range(len(CLASSES)))
    ax.set_xticklabels(CLASSES, fontdict={'fontsize': 7})
    plt.setp(ax.get_xticklabels(), rotation=45, ha="left", rotation_mode="anchor")
    ax.set_yticks(range(len(CLASSES)))
    ax.set_yticklabels(CLASSES, fontdict={'fontsize': 7})
    plt.setp(ax.get_yticklabels(), rotation=45, ha="right", rotation_mode="anchor")
    titlestring = ""
    if precision: titlestring += 'precision = {:.3f} '.format(precision)
    if recall: titlestring += '\nrecall = {:.3f} '.format(recall)
    if f1: titlestring += '\nf1 = {:.3f} '.format(f1)
    if len(titlestring) > 0:
        ax.text(101, 1, titlestring,fontdict={'fontsize': 18, 'horizontalalignment':'right',
                                              'verticalalignment':'top', 'color':'#804040'})
    plt.show()
show_confusion_matrix(cmat, f1, precision, recall)