From 805ee41e4368c8bb3043b84d967e885855a14f4a Mon Sep 17 00:00:00 2001 From: MTRNord Date: Tue, 27 Sep 2022 20:30:38 +0200 Subject: [PATCH] Use a rust server to serve the model via a highlevel api --- .github/workflows/build_docker.yaml | 33 + .gitignore | 3 +- Cargo.lock | 1458 +++++++++++++++++ Cargo.toml | 2 + Dockerfile | 13 + crates/model_server/Cargo.toml | 19 + crates/model_server/src/main.rs | 165 ++ input/MatrixData | 3 +- model.png | Bin 18905 -> 0 bytes model.py | 286 ---- model_v2.py | 120 +- .../keras_metadata.pb | 3 + .../saved_model.pb | 3 + .../variables/variables.data-00000-of-00001 | 3 + .../variables/variables.index | 3 + .../keras_metadata.pb | 3 + .../saved_model.pb | 3 + .../variables/variables.data-00000-of-00001 | 3 + .../variables/variables.index | 3 + .../keras_metadata.pb | 3 + .../saved_model.pb | 3 + .../variables/variables.data-00000-of-00001 | 3 + .../variables/variables.index | 3 + .../keras_metadata.pb | 3 + .../saved_model.pb | 3 + .../variables/variables.data-00000-of-00001 | 3 + .../variables/variables.index | 3 + spam-keras.ipynb | 674 -------- 28 files changed, 1800 insertions(+), 1024 deletions(-) create mode 100644 .github/workflows/build_docker.yaml create mode 100644 Cargo.lock create mode 100644 Cargo.toml create mode 100644 Dockerfile create mode 100644 crates/model_server/Cargo.toml create mode 100644 crates/model_server/src/main.rs delete mode 100644 model.png delete mode 100644 model.py create mode 100644 models/spam_keras_1664296013.7868948/keras_metadata.pb create mode 100644 models/spam_keras_1664296013.7868948/saved_model.pb create mode 100644 models/spam_keras_1664296013.7868948/variables/variables.data-00000-of-00001 create mode 100644 models/spam_keras_1664296013.7868948/variables/variables.index create mode 100644 models/spam_keras_1664302378.8613324/keras_metadata.pb create mode 100644 models/spam_keras_1664302378.8613324/saved_model.pb create mode 100644 models/spam_keras_1664302378.8613324/variables/variables.data-00000-of-00001 create mode 100644 models/spam_keras_1664302378.8613324/variables/variables.index create mode 100644 models/spam_keras_1664302816.4003732/keras_metadata.pb create mode 100644 models/spam_keras_1664302816.4003732/saved_model.pb create mode 100644 models/spam_keras_1664302816.4003732/variables/variables.data-00000-of-00001 create mode 100644 models/spam_keras_1664302816.4003732/variables/variables.index create mode 100644 models/spam_keras_1664303305.1441052/keras_metadata.pb create mode 100644 models/spam_keras_1664303305.1441052/saved_model.pb create mode 100644 models/spam_keras_1664303305.1441052/variables/variables.data-00000-of-00001 create mode 100644 models/spam_keras_1664303305.1441052/variables/variables.index delete mode 100644 spam-keras.ipynb diff --git a/.github/workflows/build_docker.yaml b/.github/workflows/build_docker.yaml new file mode 100644 index 0000000..e345fe2 --- /dev/null +++ b/.github/workflows/build_docker.yaml @@ -0,0 +1,33 @@ +name: Build Docker + +on: + push: + branches: + - "main" + +env: + REGISTRY: ghcr.io + IMAGE_NAME: ${{ github.repository }} + +jobs: + docker: + runs-on: ubuntu-latest + permissions: + contents: read + packages: write + steps: + - name: Set up QEMU + uses: docker/setup-qemu-action@v2 + - name: Set up Docker Buildx + uses: docker/setup-buildx-action@v2 + - name: Login to Container registry + uses: docker/login-action@v2 + with: + registry: ${{ env.REGISTRY }} + username: ${{ github.actor }} + password: ${{ secrets.GITHUB_TOKEN }} + - name: Build and push + uses: docker/build-push-action@v3 + with: + push: true + tags: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:latest diff --git a/.gitignore b/.gitignore index 612488a..cee7e00 100644 --- a/.gitignore +++ b/.gitignore @@ -2,4 +2,5 @@ /hyper_tuning /hypertuner_logs /target -/logs \ No newline at end of file +/logs +/wandb \ No newline at end of file diff --git a/Cargo.lock b/Cargo.lock new file mode 100644 index 0000000..d15ef26 --- /dev/null +++ b/Cargo.lock @@ -0,0 +1,1458 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +version = 3 + +[[package]] +name = "addr2line" +version = "0.17.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b9ecd88a8c8378ca913a680cd98f0f13ac67383d35993f86c90a70e3f137816b" +dependencies = [ + "gimli", +] + +[[package]] +name = "adler" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f26201604c87b1e01bd3d98f8d5d9a8fcbb815e8cedb41ffccbeb4bf593a35fe" + +[[package]] +name = "aes" +version = "0.7.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9e8b47f52ea9bae42228d07ec09eb676433d7c4ed1ebdf0f1d1c29ed446f1ab8" +dependencies = [ + "cfg-if", + "cipher", + "cpufeatures", + "opaque-debug", +] + +[[package]] +name = "ansi_term" +version = "0.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d52a9bb7ec0cf484c551830a7ce27bd20d67eac647e1befb56b0be4ee39a55d2" +dependencies = [ + "winapi", +] + +[[package]] +name = "async-trait" +version = "0.1.57" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "76464446b8bc32758d7e88ee1a804d9914cd9b1cb264c029899680b0be29826f" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "autocfg" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d468802bab17cbc0cc575e9b053f41e72aa36bfa6b7f55e3529ffa43161b97fa" + +[[package]] +name = "axum" +version = "0.5.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c9e3356844c4d6a6d6467b8da2cffb4a2820be256f50a3a386c9d152bab31043" +dependencies = [ + "async-trait", + "axum-core", + "bitflags", + "bytes", + "futures-util", + "http", + "http-body", + "hyper", + "itoa", + "matchit", + "memchr", + "mime", + "percent-encoding", + "pin-project-lite", + "serde", + "serde_json", + "serde_urlencoded", + "sync_wrapper", + "tokio", + "tower", + "tower-http", + "tower-layer", + "tower-service", +] + +[[package]] +name = "axum-auth" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f9770f9a9147b2324066609acb5495538cb25f973129663fba2658ba7ed69407" +dependencies = [ + "async-trait", + "axum-core", + "base64", + "http", +] + +[[package]] +name = "axum-core" +version = "0.2.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d9f0c0a60006f2a293d82d571f635042a72edf927539b7685bd62d361963839b" +dependencies = [ + "async-trait", + "bytes", + "futures-util", + "http", + "http-body", + "mime", + "tower-layer", + "tower-service", +] + +[[package]] +name = "backtrace" +version = "0.3.66" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cab84319d616cfb654d03394f38ab7e6f0919e181b1b57e1fd15e7fb4077d9a7" +dependencies = [ + "addr2line", + "cc", + "cfg-if", + "libc", + "miniz_oxide", + "object", + "rustc-demangle", +] + +[[package]] +name = "base64" +version = "0.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "904dfeac50f3cdaba28fc6f57fdcddb75f49ed61346676a78c4ffe55877802fd" + +[[package]] +name = "base64ct" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8a32fd6af2b5827bce66c29053ba0e7c42b9dcab01835835058558c10851a46b" + +[[package]] +name = "bitflags" +version = "1.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" + +[[package]] +name = "block-buffer" +version = "0.10.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "69cce20737498f97b993470a6e536b8523f0af7892a4f928cceb1ac5e52ebe7e" +dependencies = [ + "generic-array", +] + +[[package]] +name = "byteorder" +version = "1.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "14c189c53d098945499cdfa7ecc63567cf3886b3332b312a5b4585d8d3a6a610" + +[[package]] +name = "bytes" +version = "1.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ec8a7b6a70fde80372154c65702f00a0f56f3e1c36abbc6c440484be248856db" + +[[package]] +name = "bzip2" +version = "0.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6afcd980b5f3a45017c57e57a2fcccbb351cc43a356ce117ef760ef8052b89b0" +dependencies = [ + "bzip2-sys", + "libc", +] + +[[package]] +name = "bzip2-sys" +version = "0.1.11+1.0.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "736a955f3fa7875102d57c82b8cac37ec45224a07fd32d58f9f7a186b6cd4cdc" +dependencies = [ + "cc", + "libc", + "pkg-config", +] + +[[package]] +name = "cc" +version = "1.0.73" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2fff2a6927b3bb87f9595d67196a70493f627687a71d87a0d692242c33f58c11" +dependencies = [ + "jobserver", +] + +[[package]] +name = "cfg-if" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" + +[[package]] +name = "cipher" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7ee52072ec15386f770805afd189a01c8841be8696bed250fa2f13c4c0d6dfb7" +dependencies = [ + "generic-array", +] + +[[package]] +name = "color-eyre" +version = "0.6.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5a667583cca8c4f8436db8de46ea8233c42a7d9ae424a82d338f2e4675229204" +dependencies = [ + "backtrace", + "color-spantrace", + "eyre", + "indenter", + "once_cell", + "owo-colors", + "tracing-error", +] + +[[package]] +name = "color-spantrace" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1ba75b3d9449ecdccb27ecbc479fdc0b87fa2dd43d2f8298f9bf0e59aacc8dce" +dependencies = [ + "once_cell", + "owo-colors", + "tracing-core", + "tracing-error", +] + +[[package]] +name = "constant_time_eq" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "245097e9a4535ee1e3e3931fcfcd55a796a44c643e8596ff6566d68f09b87bbc" + +[[package]] +name = "cpufeatures" +version = "0.2.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "28d997bd5e24a5928dd43e46dc529867e207907fe0b239c3477d924f7f2ca320" +dependencies = [ + "libc", +] + +[[package]] +name = "crc" +version = "2.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "49fc9a695bca7f35f5f4c15cddc84415f66a74ea78eef08e90c5024f2b540e23" +dependencies = [ + "crc-catalog", +] + +[[package]] +name = "crc-catalog" +version = "1.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ccaeedb56da03b09f598226e25e80088cb4cd25f316e6e4df7d695f0feeb1403" + +[[package]] +name = "crc32fast" +version = "1.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b540bd8bc810d3885c6ea91e2018302f68baba2129ab3e88f32389ee9370880d" +dependencies = [ + "cfg-if", +] + +[[package]] +name = "crossbeam-utils" +version = "0.8.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "51887d4adc7b564537b15adcfb307936f8075dfcd5f00dde9a9f1d29383682bc" +dependencies = [ + "cfg-if", + "once_cell", +] + +[[package]] +name = "crypto-common" +version = "0.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1bfb12502f3fc46cca1bb51ac28df9d618d813cdc3d2f25b9fe775a34af26bb3" +dependencies = [ + "generic-array", + "typenum", +] + +[[package]] +name = "curl" +version = "0.4.44" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "509bd11746c7ac09ebd19f0b17782eae80aadee26237658a6b4808afb5c11a22" +dependencies = [ + "curl-sys", + "libc", + "openssl-probe", + "openssl-sys", + "schannel", + "socket2", + "winapi", +] + +[[package]] +name = "curl-sys" +version = "0.4.56+curl-7.83.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6093e169dd4de29e468fa649fbae11cdcd5551c81fe5bf1b0677adad7ef3d26f" +dependencies = [ + "cc", + "libc", + "libz-sys", + "openssl-sys", + "pkg-config", + "vcpkg", + "winapi", +] + +[[package]] +name = "digest" +version = "0.10.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "adfbc57365a37acbd2ebf2b64d7e69bb766e2fea813521ed536f5d0520dcf86c" +dependencies = [ + "block-buffer", + "crypto-common", + "subtle", +] + +[[package]] +name = "eyre" +version = "0.6.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4c2b6b5a29c02cdc822728b7d7b8ae1bab3e3b05d44522770ddd49722eeac7eb" +dependencies = [ + "indenter", + "once_cell", +] + +[[package]] +name = "filetime" +version = "0.2.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e94a7bbaa59354bc20dd75b67f23e2797b4490e9d6928203fb105c79e448c86c" +dependencies = [ + "cfg-if", + "libc", + "redox_syscall", + "windows-sys", +] + +[[package]] +name = "flate2" +version = "1.0.24" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f82b0f4c27ad9f8bfd1f3208d882da2b09c301bc1c828fd3a00d0216d2fbbff6" +dependencies = [ + "crc32fast", + "miniz_oxide", +] + +[[package]] +name = "fnv" +version = "1.0.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1" + +[[package]] +name = "form_urlencoded" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a9c384f161156f5260c24a097c56119f9be8c798586aecc13afbcbe7b7e26bf8" +dependencies = [ + "percent-encoding", +] + +[[package]] +name = "futures-channel" +version = "0.3.24" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "30bdd20c28fadd505d0fd6712cdfcb0d4b5648baf45faef7f852afb2399bb050" +dependencies = [ + "futures-core", +] + +[[package]] +name = "futures-core" +version = "0.3.24" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4e5aa3de05362c3fb88de6531e6296e85cde7739cccad4b9dfeeb7f6ebce56bf" + +[[package]] +name = "futures-task" +version = "0.3.24" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a6508c467c73851293f390476d4491cf4d227dbabcd4170f3bb6044959b294f1" + +[[package]] +name = "futures-util" +version = "0.3.24" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "44fb6cb1be61cc1d2e43b262516aafcf63b241cffdb1d3fa115f91d9c7b09c90" +dependencies = [ + "futures-core", + "futures-task", + "pin-project-lite", + "pin-utils", +] + +[[package]] +name = "generic-array" +version = "0.14.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bff49e947297f3312447abdca79f45f4738097cc82b06e72054d2223f601f1b9" +dependencies = [ + "typenum", + "version_check", +] + +[[package]] +name = "gimli" +version = "0.26.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "22030e2c5a68ec659fde1e949a745124b48e6fa8b045b7ed5bd1fe4ccc5c4e5d" + +[[package]] +name = "half" +version = "1.8.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "eabb4a44450da02c90444cf74558da904edde8fb4e9035a9a6a4e15445af0bd7" + +[[package]] +name = "hermit-abi" +version = "0.1.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "62b467343b94ba476dcb2500d242dadbb39557df889310ac77c5d99100aaac33" +dependencies = [ + "libc", +] + +[[package]] +name = "hmac" +version = "0.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6c49c37c09c17a53d937dfbb742eb3a961d65a994e6bcdcf37e7399d0cc8ab5e" +dependencies = [ + "digest", +] + +[[package]] +name = "http" +version = "0.2.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "75f43d41e26995c17e71ee126451dd3941010b0514a81a9d11f3b341debc2399" +dependencies = [ + "bytes", + "fnv", + "itoa", +] + +[[package]] +name = "http-body" +version = "0.4.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d5f38f16d184e36f2408a55281cd658ecbd3ca05cce6d6510a176eca393e26d1" +dependencies = [ + "bytes", + "http", + "pin-project-lite", +] + +[[package]] +name = "http-range-header" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0bfe8eed0a9285ef776bb792479ea3834e8b94e13d615c2f66d03dd50a435a29" + +[[package]] +name = "httparse" +version = "1.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d897f394bad6a705d5f4104762e116a75639e470d80901eed05a860a95cb1904" + +[[package]] +name = "httpdate" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c4a1e36c821dbe04574f602848a19f742f4fb3c98d40449f11bcad18d6b17421" + +[[package]] +name = "hyper" +version = "0.14.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "02c929dc5c39e335a03c405292728118860721b10190d98c2a0f0efd5baafbac" +dependencies = [ + "bytes", + "futures-channel", + "futures-core", + "futures-util", + "http", + "http-body", + "httparse", + "httpdate", + "itoa", + "pin-project-lite", + "socket2", + "tokio", + "tower-service", + "tracing", + "want", +] + +[[package]] +name = "indenter" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ce23b50ad8242c51a442f3ff322d56b02f08852c77e4c0b4d3fd684abc89c683" + +[[package]] +name = "itoa" +version = "1.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6c8af84674fe1f223a982c933a0ee1086ac4d4052aa0fb8060c12c6ad838e754" + +[[package]] +name = "jobserver" +version = "0.1.25" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "068b1ee6743e4d11fb9c6a1e6064b3693a1b600e7f5f5988047d98b3dc9fb90b" +dependencies = [ + "libc", +] + +[[package]] +name = "lazy_static" +version = "1.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646" + +[[package]] +name = "libc" +version = "0.2.133" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c0f80d65747a3e43d1596c7c5492d95d5edddaabd45a7fcdb02b95f644164966" + +[[package]] +name = "libz-sys" +version = "1.1.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9702761c3935f8cc2f101793272e202c72b99da8f4224a19ddcf1279a6450bbf" +dependencies = [ + "cc", + "libc", + "pkg-config", + "vcpkg", +] + +[[package]] +name = "lock_api" +version = "0.4.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "435011366fe56583b16cf956f9df0095b405b82d76425bc8981c0e22e60ec4df" +dependencies = [ + "autocfg", + "scopeguard", +] + +[[package]] +name = "log" +version = "0.4.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "abb12e687cfb44aa40f41fc3978ef76448f9b6038cad6aef4259d3c095a2382e" +dependencies = [ + "cfg-if", +] + +[[package]] +name = "matchers" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8263075bb86c5a1b1427b5ae862e8889656f126e9f77c484496e8b47cf5c5558" +dependencies = [ + "regex-automata", +] + +[[package]] +name = "matchit" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "73cbba799671b762df5a175adf59ce145165747bb891505c43d09aefbbf38beb" + +[[package]] +name = "memchr" +version = "2.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2dffe52ecf27772e601905b7522cb4ef790d2cc203488bbd0e2fe85fcb74566d" + +[[package]] +name = "mime" +version = "0.3.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2a60c7ce501c71e03a9c9c0d35b861413ae925bd979cc7a4e30d060069aaac8d" + +[[package]] +name = "miniz_oxide" +version = "0.5.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "96590ba8f175222643a85693f33d26e9c8a015f599c216509b1a6894af675d34" +dependencies = [ + "adler", +] + +[[package]] +name = "mio" +version = "0.8.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "57ee1c23c7c63b0c9250c339ffdc69255f110b298b901b9f6c82547b7b87caaf" +dependencies = [ + "libc", + "log", + "wasi", + "windows-sys", +] + +[[package]] +name = "model_server" +version = "0.1.0" +dependencies = [ + "axum", + "axum-auth", + "color-eyre", + "once_cell", + "serde", + "serde_json", + "tensorflow", + "tokio", + "tracing", + "tracing-subscriber", +] + +[[package]] +name = "num-complex" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7ae39348c8bc5fbd7f40c727a9925f03517afd2ab27d46702108b6a7e5414c19" +dependencies = [ + "num-traits", +] + +[[package]] +name = "num-traits" +version = "0.2.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "578ede34cf02f8924ab9447f50c28075b4d3e5b269972345e7e0372b38c6cdcd" +dependencies = [ + "autocfg", +] + +[[package]] +name = "num_cpus" +version = "1.13.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "19e64526ebdee182341572e50e9ad03965aa510cd94427a4549448f285e957a1" +dependencies = [ + "hermit-abi", + "libc", +] + +[[package]] +name = "num_threads" +version = "0.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2819ce041d2ee131036f4fc9d6ae7ae125a3a40e97ba64d04fe799ad9dabbb44" +dependencies = [ + "libc", +] + +[[package]] +name = "object" +version = "0.29.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "21158b2c33aa6d4561f1c0a6ea283ca92bc54802a93b263e910746d679a7eb53" +dependencies = [ + "memchr", +] + +[[package]] +name = "once_cell" +version = "1.15.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e82dad04139b71a90c080c8463fe0dc7902db5192d939bd0950f074d014339e1" + +[[package]] +name = "opaque-debug" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "624a8340c38c1b80fd549087862da4ba43e08858af025b236e509b6649fc13d5" + +[[package]] +name = "openssl-probe" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ff011a302c396a5197692431fc1948019154afc178baf7d8e37367442a4601cf" + +[[package]] +name = "openssl-sys" +version = "0.9.75" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e5f9bd0c2710541a3cda73d6f9ac4f1b240de4ae261065d309dbe73d9dceb42f" +dependencies = [ + "autocfg", + "cc", + "libc", + "pkg-config", + "vcpkg", +] + +[[package]] +name = "owo-colors" +version = "3.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c1b04fb49957986fdce4d6ee7a65027d55d4b6d2265e5848bbb507b58ccfdb6f" + +[[package]] +name = "parking_lot" +version = "0.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3742b2c103b9f06bc9fff0a37ff4912935851bee6d36f3c02bcc755bcfec228f" +dependencies = [ + "lock_api", + "parking_lot_core", +] + +[[package]] +name = "parking_lot_core" +version = "0.9.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "09a279cbf25cb0757810394fbc1e359949b59e348145c643a939a525692e6929" +dependencies = [ + "cfg-if", + "libc", + "redox_syscall", + "smallvec", + "windows-sys", +] + +[[package]] +name = "password-hash" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1d791538a6dcc1e7cb7fe6f6b58aca40e7f79403c45b2bc274008b5e647af1d8" +dependencies = [ + "base64ct", + "rand_core", + "subtle", +] + +[[package]] +name = "pbkdf2" +version = "0.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "271779f35b581956db91a3e55737327a03aa051e90b1c47aeb189508533adfd7" +dependencies = [ + "digest", + "hmac", + "password-hash", + "sha2", +] + +[[package]] +name = "percent-encoding" +version = "2.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "478c572c3d73181ff3c2539045f6eb99e5491218eae919370993b890cdbdd98e" + +[[package]] +name = "pin-project" +version = "1.0.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ad29a609b6bcd67fee905812e544992d216af9d755757c05ed2d0e15a74c6ecc" +dependencies = [ + "pin-project-internal", +] + +[[package]] +name = "pin-project-internal" +version = "1.0.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "069bdb1e05adc7a8990dce9cc75370895fbe4e3d58b9b73bf1aee56359344a55" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "pin-project-lite" +version = "0.2.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e0a7ae3ac2f1173085d398531c705756c94a4c56843785df85a60c1a0afac116" + +[[package]] +name = "pin-utils" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184" + +[[package]] +name = "pkg-config" +version = "0.3.25" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1df8c4ec4b0627e53bdf214615ad287367e482558cf84b109250b37464dc03ae" + +[[package]] +name = "proc-macro2" +version = "1.0.44" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7bd7356a8122b6c4a24a82b278680c73357984ca2fc79a0f9fa6dea7dced7c58" +dependencies = [ + "unicode-ident", +] + +[[package]] +name = "protobuf" +version = "2.27.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cf7e6d18738ecd0902d30d1ad232c9125985a3422929b16c65517b38adc14f96" + +[[package]] +name = "quote" +version = "1.0.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bbe448f377a7d6961e30f5955f9b8d106c3f5e449d493ee1b125c1d43c2b5179" +dependencies = [ + "proc-macro2", +] + +[[package]] +name = "rand_core" +version = "0.6.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c" + +[[package]] +name = "redox_syscall" +version = "0.2.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fb5a58c1855b4b6819d59012155603f0b22ad30cad752600aadfcb695265519a" +dependencies = [ + "bitflags", +] + +[[package]] +name = "regex" +version = "1.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4c4eb3267174b8c6c2f654116623910a0fef09c4753f8dd83db29c48a0df988b" +dependencies = [ + "regex-syntax", +] + +[[package]] +name = "regex-automata" +version = "0.1.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6c230d73fb8d8c1b9c0b3135c5142a8acee3a0558fb8db5cf1cb65f8d7862132" +dependencies = [ + "regex-syntax", +] + +[[package]] +name = "regex-syntax" +version = "0.6.27" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a3f87b73ce11b1619a3c6332f45341e0047173771e8b8b73f87bfeefb7b56244" + +[[package]] +name = "rustc-demangle" +version = "0.1.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7ef03e0a2b150c7a90d01faf6254c9c48a41e95fb2a8c2ac1c6f0d2b9aefc342" + +[[package]] +name = "rustversion" +version = "1.0.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "97477e48b4cf8603ad5f7aaf897467cf42ab4218a38ef76fb14c2d6773a6d6a8" + +[[package]] +name = "ryu" +version = "1.0.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4501abdff3ae82a1c1b477a17252eb69cee9e66eb915c1abaa4f44d873df9f09" + +[[package]] +name = "schannel" +version = "0.1.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "88d6731146462ea25d9244b2ed5fd1d716d25c52e4d54aa4fb0f3c4e9854dbe2" +dependencies = [ + "lazy_static", + "windows-sys", +] + +[[package]] +name = "scopeguard" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d29ab0c6d3fc0ee92fe66e2d99f700eab17a8d57d1c1d3b748380fb20baa78cd" + +[[package]] +name = "semver" +version = "1.0.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e25dfac463d778e353db5be2449d1cce89bd6fd23c9f1ea21310ce6e5a1b29c4" + +[[package]] +name = "serde" +version = "1.0.145" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "728eb6351430bccb993660dfffc5a72f91ccc1295abaa8ce19b27ebe4f75568b" +dependencies = [ + "serde_derive", +] + +[[package]] +name = "serde_derive" +version = "1.0.145" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "81fa1584d3d1bcacd84c277a0dfe21f5b0f6accf4a23d04d4c6d61f1af522b4c" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "serde_json" +version = "1.0.85" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e55a28e3aaef9d5ce0506d0a14dbba8054ddc7e499ef522dd8b26859ec9d4a44" +dependencies = [ + "itoa", + "ryu", + "serde", +] + +[[package]] +name = "serde_urlencoded" +version = "0.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d3491c14715ca2294c4d6a88f15e84739788c1d030eed8c110436aafdaa2f3fd" +dependencies = [ + "form_urlencoded", + "itoa", + "ryu", + "serde", +] + +[[package]] +name = "sha1" +version = "0.10.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f04293dc80c3993519f2d7f6f511707ee7094fe0c6d3406feb330cdb3540eba3" +dependencies = [ + "cfg-if", + "cpufeatures", + "digest", +] + +[[package]] +name = "sha2" +version = "0.10.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "82e6b795fe2e3b1e845bafcb27aa35405c4d47cdfc92af5fc8d3002f76cebdc0" +dependencies = [ + "cfg-if", + "cpufeatures", + "digest", +] + +[[package]] +name = "sharded-slab" +version = "0.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "900fba806f70c630b0a382d0d825e17a0f19fcd059a2ade1ff237bcddf446b31" +dependencies = [ + "lazy_static", +] + +[[package]] +name = "signal-hook-registry" +version = "1.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e51e73328dc4ac0c7ccbda3a494dfa03df1de2f46018127f60c693f2648455b0" +dependencies = [ + "libc", +] + +[[package]] +name = "smallvec" +version = "1.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2fd0db749597d91ff862fd1d55ea87f7855a744a8425a64695b6fca237d1dad1" + +[[package]] +name = "socket2" +version = "0.4.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "02e2d2db9033d13a1567121ddd7a095ee144db4e1ca1b1bda3419bc0da294ebd" +dependencies = [ + "libc", + "winapi", +] + +[[package]] +name = "subtle" +version = "2.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6bdef32e8150c2a081110b42772ffe7d7c9032b606bc226c8260fd97e0976601" + +[[package]] +name = "syn" +version = "1.0.101" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e90cde112c4b9690b8cbe810cba9ddd8bc1d7472e2cae317b69e9438c1cba7d2" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "sync_wrapper" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "20518fe4a4c9acf048008599e464deb21beeae3d3578418951a189c235a7a9a8" + +[[package]] +name = "tar" +version = "0.4.38" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4b55807c0344e1e6c04d7c965f5289c39a8d94ae23ed5c0b57aabac549f871c6" +dependencies = [ + "filetime", + "libc", + "xattr", +] + +[[package]] +name = "tensorflow" +version = "0.19.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "813259d8b797c862effd4fb000d9ce9bdcbaa02c2d8cfcd94ec7dc742842daa7" +dependencies = [ + "byteorder", + "crc", + "half", + "libc", + "num-complex", + "protobuf", + "rustversion", + "tensorflow-internal-macros", + "tensorflow-sys", +] + +[[package]] +name = "tensorflow-internal-macros" +version = "0.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4975c975b6d9c05a7cbf007ebc8e01e92e7e0907be9efcc74074857fc92ceb54" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "tensorflow-sys" +version = "0.22.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ba8ac1e8d3e408e14b49e7164cda710c6c992eaa76750b126ad3c5225eb18cfd" +dependencies = [ + "curl", + "flate2", + "libc", + "pkg-config", + "semver", + "tar", + "zip", +] + +[[package]] +name = "thread_local" +version = "1.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5516c27b78311c50bf42c071425c560ac799b11c30b31f87e3081965fe5e0180" +dependencies = [ + "once_cell", +] + +[[package]] +name = "time" +version = "0.3.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3c3f9a28b618c3a6b9251b6908e9c99e04b9e5c02e6581ccbb67d59c34ef7f9b" +dependencies = [ + "itoa", + "libc", + "num_threads", + "time-macros", +] + +[[package]] +name = "time-macros" +version = "0.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "42657b1a6f4d817cda8e7a0ace261fe0cc946cf3a80314390b22cc61ae080792" + +[[package]] +name = "tokio" +version = "1.21.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0020c875007ad96677dcc890298f4b942882c5d4eb7cc8f439fc3bf813dc9c95" +dependencies = [ + "autocfg", + "bytes", + "libc", + "memchr", + "mio", + "num_cpus", + "once_cell", + "parking_lot", + "pin-project-lite", + "signal-hook-registry", + "socket2", + "tokio-macros", + "winapi", +] + +[[package]] +name = "tokio-macros" +version = "1.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9724f9a975fb987ef7a3cd9be0350edcbe130698af5b8f7a631e23d42d052484" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "tower" +version = "0.4.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b8fa9be0de6cf49e536ce1851f987bd21a43b771b09473c3549a6c853db37c1c" +dependencies = [ + "futures-core", + "futures-util", + "pin-project", + "pin-project-lite", + "tokio", + "tower-layer", + "tower-service", + "tracing", +] + +[[package]] +name = "tower-http" +version = "0.3.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3c530c8675c1dbf98facee631536fa116b5fb6382d7dd6dc1b118d970eafe3ba" +dependencies = [ + "bitflags", + "bytes", + "futures-core", + "futures-util", + "http", + "http-body", + "http-range-header", + "pin-project-lite", + "tower", + "tower-layer", + "tower-service", +] + +[[package]] +name = "tower-layer" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "343bc9466d3fe6b0f960ef45960509f84480bf4fd96f92901afe7ff3df9d3a62" + +[[package]] +name = "tower-service" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b6bc1c9ce2b5135ac7f93c72918fc37feb872bdc6a5533a8b85eb4b86bfdae52" + +[[package]] +name = "tracing" +version = "0.1.36" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2fce9567bd60a67d08a16488756721ba392f24f29006402881e43b19aac64307" +dependencies = [ + "cfg-if", + "log", + "pin-project-lite", + "tracing-attributes", + "tracing-core", +] + +[[package]] +name = "tracing-attributes" +version = "0.1.22" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "11c75893af559bc8e10716548bdef5cb2b983f8e637db9d0e15126b61b484ee2" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "tracing-core" +version = "0.1.29" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5aeea4303076558a00714b823f9ad67d58a3bbda1df83d8827d21193156e22f7" +dependencies = [ + "once_cell", + "valuable", +] + +[[package]] +name = "tracing-error" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d686ec1c0f384b1277f097b2f279a2ecc11afe8c133c1aabf036a27cb4cd206e" +dependencies = [ + "tracing", + "tracing-subscriber", +] + +[[package]] +name = "tracing-log" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "78ddad33d2d10b1ed7eb9d1f518a5674713876e97e5bb9b7345a7984fbb4f922" +dependencies = [ + "lazy_static", + "log", + "tracing-core", +] + +[[package]] +name = "tracing-subscriber" +version = "0.3.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "60db860322da191b40952ad9affe65ea23e7dd6a5c442c2c42865810c6ab8e6b" +dependencies = [ + "ansi_term", + "matchers", + "once_cell", + "regex", + "sharded-slab", + "smallvec", + "thread_local", + "tracing", + "tracing-core", + "tracing-log", +] + +[[package]] +name = "try-lock" +version = "0.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "59547bce71d9c38b83d9c0e92b6066c4253371f15005def0c30d9657f50c7642" + +[[package]] +name = "typenum" +version = "1.15.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dcf81ac59edc17cc8697ff311e8f5ef2d99fcbd9817b34cec66f90b6c3dfd987" + +[[package]] +name = "unicode-ident" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dcc811dc4066ac62f84f11307873c4850cb653bfa9b1719cee2bd2204a4bc5dd" + +[[package]] +name = "valuable" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "830b7e5d4d90034032940e4ace0d9a9a057e7a45cd94e6c007832e39edb82f6d" + +[[package]] +name = "vcpkg" +version = "0.2.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "accd4ea62f7bb7a82fe23066fb0957d48ef677f6eeb8215f372f52e48bb32426" + +[[package]] +name = "version_check" +version = "0.9.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "49874b5167b65d7193b8aba1567f5c7d93d001cafc34600cee003eda787e483f" + +[[package]] +name = "want" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1ce8a968cb1cd110d136ff8b819a556d6fb6d919363c61534f6860c7eb172ba0" +dependencies = [ + "log", + "try-lock", +] + +[[package]] +name = "wasi" +version = "0.11.0+wasi-snapshot-preview1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" + +[[package]] +name = "winapi" +version = "0.3.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419" +dependencies = [ + "winapi-i686-pc-windows-gnu", + "winapi-x86_64-pc-windows-gnu", +] + +[[package]] +name = "winapi-i686-pc-windows-gnu" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" + +[[package]] +name = "winapi-x86_64-pc-windows-gnu" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" + +[[package]] +name = "windows-sys" +version = "0.36.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ea04155a16a59f9eab786fe12a4a450e75cdb175f9e0d80da1e17db09f55b8d2" +dependencies = [ + "windows_aarch64_msvc", + "windows_i686_gnu", + "windows_i686_msvc", + "windows_x86_64_gnu", + "windows_x86_64_msvc", +] + +[[package]] +name = "windows_aarch64_msvc" +version = "0.36.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9bb8c3fd39ade2d67e9874ac4f3db21f0d710bee00fe7cab16949ec184eeaa47" + +[[package]] +name = "windows_i686_gnu" +version = "0.36.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "180e6ccf01daf4c426b846dfc66db1fc518f074baa793aa7d9b9aaeffad6a3b6" + +[[package]] +name = "windows_i686_msvc" +version = "0.36.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e2e7917148b2812d1eeafaeb22a97e4813dfa60a3f8f78ebe204bcc88f12f024" + +[[package]] +name = "windows_x86_64_gnu" +version = "0.36.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4dcd171b8776c41b97521e5da127a2d86ad280114807d0b2ab1e462bc764d9e1" + +[[package]] +name = "windows_x86_64_msvc" +version = "0.36.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c811ca4a8c853ef420abd8592ba53ddbbac90410fab6903b3e79972a631f7680" + +[[package]] +name = "xattr" +version = "0.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6d1526bbe5aaeb5eb06885f4d987bcdfa5e23187055de9b83fe00156a821fabc" +dependencies = [ + "libc", +] + +[[package]] +name = "zip" +version = "0.6.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bf225bcf73bb52cbb496e70475c7bd7a3f769df699c0020f6c7bd9a96dcf0b8d" +dependencies = [ + "aes", + "byteorder", + "bzip2", + "constant_time_eq", + "crc32fast", + "crossbeam-utils", + "flate2", + "hmac", + "pbkdf2", + "sha1", + "time", + "zstd", +] + +[[package]] +name = "zstd" +version = "0.10.2+zstd.1.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5f4a6bd64f22b5e3e94b4e238669ff9f10815c27a5180108b849d24174a83847" +dependencies = [ + "zstd-safe", +] + +[[package]] +name = "zstd-safe" +version = "4.1.6+zstd.1.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "94b61c51bb270702d6167b8ce67340d2754b088d0c091b06e593aa772c3ee9bb" +dependencies = [ + "libc", + "zstd-sys", +] + +[[package]] +name = "zstd-sys" +version = "1.6.3+zstd.1.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fc49afa5c8d634e75761feda8c592051e7eeb4683ba827211eb0d731d3402ea8" +dependencies = [ + "cc", + "libc", +] diff --git a/Cargo.toml b/Cargo.toml new file mode 100644 index 0000000..21d2337 --- /dev/null +++ b/Cargo.toml @@ -0,0 +1,2 @@ +[workspace] +members = ["crates/model_server"] diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 0000000..3bda22b --- /dev/null +++ b/Dockerfile @@ -0,0 +1,13 @@ +FROM rust:1.64 as builder + +WORKDIR /app +COPY ./crates /app +COPY ./Cargo.toml /app +COPY ./Cargo.lock /app +RUN cargo build --release + +ENV MODEL_PATH /app/models/matrix_spam +# Copy the model files to the image +COPY ./models/spam_keras_1664303305.1441052 /app/models/matrix_spam + +CMD ["./target/release/model_server"] \ No newline at end of file diff --git a/crates/model_server/Cargo.toml b/crates/model_server/Cargo.toml new file mode 100644 index 0000000..721b89c --- /dev/null +++ b/crates/model_server/Cargo.toml @@ -0,0 +1,19 @@ +[package] +edition = "2021" +name = "model_server" +publish = false +version = "0.1.0" + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[dependencies] +axum = "0.5.16" +axum-auth = {version = "0.3", default-features = false, features = ["auth-bearer"]} +color-eyre = "0.6.2" +once_cell = "1.15.0" +serde = {version = "1.0", features = ["derive"]} +serde_json = "1.0" +tensorflow = {version = "0.19.1", features = ["tensorflow_gpu"]} +tokio = {version = "1.0", features = ["full"]} +tracing = "0.1" +tracing-subscriber = {version = "0.3", features = ["env-filter"]} diff --git a/crates/model_server/src/main.rs b/crates/model_server/src/main.rs new file mode 100644 index 0000000..5ce2073 --- /dev/null +++ b/crates/model_server/src/main.rs @@ -0,0 +1,165 @@ +use axum::{http::StatusCode, response::IntoResponse, routing::post, Json, Router}; +use axum_auth::AuthBearer; +use color_eyre::eyre::{bail, Result}; +use once_cell::sync::OnceCell; +use serde::{Deserialize, Serialize}; +use std::io::Write; +use std::{fs::OpenOptions, net::SocketAddr}; +use tensorflow::{Graph, SavedModelBundle, SessionOptions, SessionRunArgs, Tensor}; +use tracing::{debug, error, info}; + +static GRAPH: OnceCell = OnceCell::new(); +static MODEL: OnceCell = OnceCell::new(); + +#[tokio::main] +async fn main() -> Result<()> { + color_eyre::install()?; + // initialize tracing + tracing_subscriber::fmt::init(); + info!("Starting up"); + + let model_path = match std::env::var("MODEL_PATH") { + Ok(val) => val, + Err(_) => bail!("Missing MODEL_PATH env var"), + }; + + let mut graph = Graph::new(); + let bundle = + SavedModelBundle::load(&SessionOptions::new(), &["serve"], &mut graph, model_path)?; + GRAPH.set(graph).unwrap(); + MODEL.set(bundle).unwrap(); + + // build our application with a route + let app = Router::new() + // `GET /test` goes to `test` + .route("/test", post(test)) + // `POST /submit` goes to `submit` + .route("/submit", post(submit)) + .route("/submit_review", post(submit_for_review)); + + let addr = SocketAddr::from(([127, 0, 0, 1], 3000)); + info!("listening on {}", addr); + axum::Server::bind(&addr) + .serve(app.into_make_service()) + .await + .unwrap(); + Ok(()) +} + +async fn test(Json(payload): Json) -> impl IntoResponse { + let bundle = MODEL.get().unwrap(); + let graph = GRAPH.get().unwrap(); + let session = &bundle.session; + let meta = bundle.meta_graph_def(); + debug!("Signatures: {:#?}", meta.signatures()); + let signature = meta + .get_signature(tensorflow::DEFAULT_SERVING_SIGNATURE_DEF_KEY) + .unwrap(); + debug!("Inputs: {:#?}", signature.inputs()); + debug!("Outputs: {:#?}", signature.outputs()); + let input_info = signature.get_input("input_1").unwrap(); + let output_info = signature.get_output("output_1").unwrap(); + + let input_op = graph + .operation_by_name_required(&input_info.name().name) + .unwrap(); + let output_op = graph + .operation_by_name_required(&output_info.name().name) + .unwrap(); + + let tensor: Tensor = Tensor::from(&[payload.input_data.clone()]); + let mut args = SessionRunArgs::new(); + args.add_feed(&input_op, 0, &tensor); + + let out = args.request_fetch(&output_op, 0); + + session + .run(&mut args) + .expect("Error occurred during calculations"); + let out_res: f32 = args.fetch(out).unwrap()[0]; + + let response = Prediction { + input_data: payload.input_data, + score: out_res, + }; + + (StatusCode::OK, Json(response)) +} + +async fn submit( + Json(payload): Json, + AuthBearer(token): AuthBearer, +) -> impl IntoResponse { + let access_token = match std::env::var("ACCESS_TOKEN") { + Ok(val) => val, + Err(_) => { + error!("Missing ACCESS_TOKEN env var"); + return StatusCode::INTERNAL_SERVER_ERROR; + } + }; + if token != access_token { + return StatusCode::UNAUTHORIZED; + } + + // TODO implement + StatusCode::NOT_IMPLEMENTED +} + +async fn submit_for_review( + Json(payload): Json, + AuthBearer(token): AuthBearer, +) -> impl IntoResponse { + let access_token = match std::env::var("ACCESS_TOKEN") { + Ok(val) => val, + Err(_) => { + error!("Missing ACCESS_TOKEN env var"); + return StatusCode::INTERNAL_SERVER_ERROR; + } + }; + if token != access_token { + return StatusCode::UNAUTHORIZED; + } + + std::fs::create_dir_all("./data/").unwrap(); + let file = OpenOptions::new() + .write(true) + .append(true) + .create(true) + .open("./data/review.txt"); + match file { + Ok(mut file) => { + if let Err(e) = writeln!(file, "{}", payload.input_data) { + eprintln!("Couldn't write to file: {}", e); + return StatusCode::INTERNAL_SERVER_ERROR; + } + } + Err(e) => { + eprintln!("Couldn't open file: {}", e); + return StatusCode::INTERNAL_SERVER_ERROR; + } + } + + StatusCode::OK +} + +#[derive(Deserialize, Serialize)] +struct TestData { + input_data: String, +} + +#[derive(Deserialize, Serialize)] +struct Prediction { + input_data: String, + score: f32, +} + +#[derive(Deserialize, Serialize)] +struct SubmitData { + input_data: String, + spam: bool, +} + +#[derive(Deserialize, Serialize)] +struct SubmitReview { + input_data: String, +} diff --git a/input/MatrixData b/input/MatrixData index b81bfab..8e73832 100644 --- a/input/MatrixData +++ b/input/MatrixData @@ -5596,4 +5596,5 @@ ham Greg, can you call me back once you get this? ham I'm writing a bot. I want to be able to provide the bot with the cross signing recovery key from Element, then have it grab all the room keys from the server. Is that currently possible and if so how? ham O thanks, i'll take a look ham Hello all you Element experts, I'm setting up a homelab with Synapse and Element-web. I have read that there are security downsides in hosting both on the same domain. Can someone confirm whether it is still true, even on different subdomains ? -ham Greg, can you call me back once you get this? \ No newline at end of file +ham Greg, can you call me back once you get this? +spam You have won a million dollars! Fill out your bank details here... \ No newline at end of file diff --git a/model.png b/model.png deleted file mode 100644 index ef6e3881f55a2cbb3ec8b399bf99afba58b50305..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 18905 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

FuWZzLYgjZvpzqV+9MKSL zAbfmI7yywEFpWVz^~b@gMo=W=xgfU}wXmG@tDY<-*99sRlG_0^3kQk;cg(fslsufz z&mQFCZeW1TG6>#FKB_7zFF>39x5o`r)esyLRd1@~{AK*~e*vB}QzQTY diff --git a/model.py b/model.py deleted file mode 100644 index a640fbf..0000000 --- a/model.py +++ /dev/null @@ -1,286 +0,0 @@ -import os -import time -from datetime import datetime - -import keras_tuner as kt -import matplotlib.pyplot as plt -import numpy as np -import pandas as pd -import tensorflow as tf -import tensorflow_addons as tfa -from nltk.corpus import stopwords -from tensorflow import keras -from tensorflow.keras.preprocessing.sequence import pad_sequences -from tensorflow.keras.preprocessing.text import Tokenizer - -print("[Step 1/9] Loading data") -# Read data -data = pd.read_csv('./input/MatrixData', sep='\t') - - -def remove_stopwords(input_text): - ''' - Function to remove English stopwords from a Pandas Series. - - Parameters: - input_text : text to clean - Output: - cleaned Pandas Series - ''' - stopwords_list = stopwords.words('english') - # Some words which might indicate a certain sentiment are kept via a whitelist - whitelist = ["n't", "not", "no"] - words = input_text.split() - clean_words = [word for word in words if ( - word not in stopwords_list or word in whitelist) and len(word) > 1] - return " ".join(clean_words) - - -# Remove unknown -data.dropna(inplace=True) - -# Convert label to something useful - - -def change_labels(x): return 1 if x == "spam" else 0 - - -data['label'] = data['label'].apply(change_labels) - -# Count by label -spam = 0 -ham = 0 - - -def count_labels(x): - if x == 1: - global spam - spam += 1 - else: - global ham - ham += 1 - return x -# .apply(count_labels) -#print("Spam: ", spam) -#print("Ham: ", ham) - - -# Remove stopwords -data['message'] = data['message'].apply( - remove_stopwords) - -# Print unbalanced -print(data.groupby('label').describe().T) - - -#ham_msg = data[data.label == 0] -#spam_msg = data[data.label == 1] - -# randomly taking data from ham_msg -#ham_msg = ham_msg.sample(n=len(spam_msg)*2, random_state=42) - -#data = pd.concat([ham_msg, spam_msg]).reset_index(drop=True) - -# Balanced -print(data.groupby('label').describe().T) - -# Shuffle data -data = data.sample(frac=1).reset_index(drop=True) - -# Split data into messages and label sets -sentences = data['message'].tolist() -labels = data['label'].tolist() - -# Separate out the sentences and labels into training and test sets -#training_size = int(len(sentences) * 0.8) -training_size = int(len(sentences) * 0.7) -training_sentences = sentences[0:training_size] -testing_sentences = sentences[training_size:] -training_labels = labels[0:training_size] -testing_labels = labels[training_size:] - -# Make labels into numpy arrays for use with the network later -training_labels_final = np.array(training_labels) -testing_labels_final = np.array(testing_labels) - -print("[Step 2/9] Tokenizing data") -vocab_size = 1000 -embedding_dim = 16 -#embedding_dim = 32 -#max_length = 120 -max_length = None -trunc_type = 'post' -padding_type = 'post' -oov_tok = "" - - -tokenizer = Tokenizer(num_words=vocab_size, oov_token=oov_tok) - -tokenizer.fit_on_texts(training_sentences) -word_index = tokenizer.word_index - -sequences = tokenizer.texts_to_sequences(training_sentences) -padded = pad_sequences(sequences, maxlen=max_length, padding=padding_type, - truncating=trunc_type) - -testing_sequences = tokenizer.texts_to_sequences(testing_sentences) -testing_padded = pad_sequences(testing_sequences, maxlen=max_length, - padding=padding_type, truncating=trunc_type) - - -print("[Step 3/9] Prepare callbacks") -logdir = "logs/scalars/" + datetime.now().strftime("%Y%m%d-%H%M%S") -tensorboard_callback = keras.callbacks.TensorBoard(log_dir=logdir) - -hypermodel_logdir = "logs/scalars/" + \ - datetime.now().strftime("%Y%m%d-%H%M%S") + "_hypermodel" -hypermodel_tensorboard_callback = keras.callbacks.TensorBoard( - log_dir=hypermodel_logdir) - -hypertuner_logdir = "hypertuner_logs/scalars/" + \ - datetime.now().strftime("%Y%m%d-%H%M%S") -hypertuner_tensorboard_callback = keras.callbacks.TensorBoard( - log_dir=hypertuner_logdir) -# Define the checkpoint directory to store the checkpoints. -checkpoint_dir = './training_checkpoints' -# Define the name of the checkpoint files. -checkpoint_prefix = os.path.join(checkpoint_dir, "ckpt_{epoch}") - -progress_bar = tf.keras.callbacks.ProgbarLogger() - -#es_callback = tf.keras.callbacks.EarlyStopping(monitor='val_loss', patience=3) - -print("[Step 4/9] Creating model") - - -class SpamDectionModel(kt.HyperModel): - def build(self, hp): - # Tune the number of units in the first Dense layer - # Choose an optimal value between 6-512 - hp_units = hp.Int('units', min_value=6, max_value=512, step=12) - hp_dropout = hp.Float('dropout', min_value=.1, max_value=.9, step=.01) - hp_l2 = hp.Float('l2', min_value=0.0001, max_value=0.001, step=0.0001) - model = tf.keras.Sequential([ - tf.keras.layers.Embedding( - vocab_size, embedding_dim, input_length=max_length), - tf.keras.layers.GlobalAveragePooling1D(), - tf.keras.layers.Dropout(hp_dropout,), - tf.keras.layers.Dense(units=hp_units, activation='relu', - kernel_regularizer=tf.keras.regularizers.l2(hp_l2)), - # tf.keras.layers.Dense(6, activation='relu', - # kernel_regularizer=tf.keras.regularizers.l2(0.0001)), - tf.keras.layers.Dropout(hp_dropout,), - tf.keras.layers.Dense(1, activation='sigmoid') - ]) - # Adam was best so far - # tf.keras.optimizers.Nadam() has similar results to Adam but a bit worse. second best - hp_learning_rate = hp.Choice('learning_rate', values=[ - 1e-2, 1e-3, 1e-4, 1e-5, ]) - opt = tf.keras.optimizers.Adam(learning_rate=hp_learning_rate) - # opt = tf.keras.optimizers.Nadam() - model.compile(loss=tf.keras.losses.BinaryCrossentropy(), - optimizer=opt, metrics=['accuracy']) - # print(model.summary()) - - return model - - -print("[Step 5/9] Tuning hypervalues") -tuner = kt.Hyperband(SpamDectionModel(), - objective='val_accuracy', - max_epochs=350, - factor=3, - directory='hyper_tuning', - project_name='spam-keras') - -stop_early = tf.keras.callbacks.EarlyStopping(monitor='val_loss', patience=5) -tuner.search(padded, training_labels_final, epochs=400, verbose=0, - validation_data=(testing_padded, testing_labels_final), callbacks=[hypertuner_tensorboard_callback, stop_early, progress_bar]) -# Get the optimal hyperparameters -best_hps = tuner.get_best_hyperparameters(num_trials=1)[0] - -print(f""" -The hyperparameter search is complete. The optimal number of units in the first densely-connected -layer is {best_hps.get('units')} and the optimal learning rate for the optimizer is {best_hps.get('learning_rate')}. -The optimal dropout rate is {best_hps.get('dropout')} and the optimal l2 rate is {best_hps.get('l2')}. -""") - - -print("[Step 6/9] Fitting initial model") -num_epochs = 200 -model = tuner.hypermodel.build(best_hps) -history = model.fit(padded, - training_labels_final, - epochs=num_epochs, - verbose=0, - callbacks=[tensorboard_callback, progress_bar], - validation_data=(testing_padded, testing_labels_final)) - - -val_acc_per_epoch = history.history['val_accuracy'] -best_epoch = val_acc_per_epoch.index(max(val_acc_per_epoch)) + 5 -print('Best epoch: %d' % (best_epoch,)) -print("Average train loss: ", np.average(history.history['loss'])) -print("Average test loss: ", np.average(history.history['val_loss'])) - -print("[Step 7/9] Building final model") -hypermodel = tuner.hypermodel.build(best_hps) -hypermodel_history = hypermodel.fit(padded, training_labels_final, verbose=0, - epochs=best_epoch, - callbacks=[hypermodel_tensorboard_callback, - tf.keras.callbacks.ModelCheckpoint(filepath=checkpoint_prefix, - save_weights_only=True), progress_bar, - # es_callback - ], validation_data=(testing_padded, testing_labels_final) - ) - -print("Average train loss(hypermodel_history): ", - np.average(hypermodel_history.history['loss'])) -print("Average test loss(hypermodel_history): ", - np.average(hypermodel_history.history['val_loss'])) - - -print("[Step 8/9] Saving final model") -# Save model -hypermodel.save(f"./models/spam_keras_{time.time()}") - -print("[Step 9/9] Testing final model") -# Use the model to predict whether a message is spam -text_messages = ['Greg, can you call me back once you get this?', - 'Congrats on your new iPhone! Click here to claim your prize...', - 'Really like that new photo of you', - 'Did you hear the news today? Terrible what has happened...', - 'Attend this free COVID webinar today: Book your session now...', - 'Are you coming to the party tonight?', - 'Your parcel has gone missing', - 'Do not forget to bring friends!', - 'You have won a million dollars! Fill out your bank details here...', - 'Looking forward to seeing you again', - 'oh wow https://github.com/MGCodesandStats/tensorflow-nlp/blob/master/spam%20detection%20tensorflow%20v2.ipynb works really good on spam detection. Guess I go with that as the base model then lol :D', - 'ayo', - 'Almost all my spam is coming to my non-gmail address actually', - 'Oh neat I think I found the sizing sweetspot for my data :D', - 'would never click on buttons in gmail :D always expecting there to be a bug in gmail that allows js to grab your google credentials :D XSS via email lol. I am too scared for touching spam in gmail', - 'back to cacophony ', - 'Room version 11 when', - 'skip 11 and go straight to 12', - '100 events should clear out any events that might be causing a request to fail lol'] - -# print(text_messages) - -# Create the sequences -padding_type = 'post' -sample_sequences = tokenizer.texts_to_sequences(text_messages) -fakes_padded = pad_sequences( - sample_sequences, padding=padding_type, maxlen=max_length) - -classes = hypermodel.predict(fakes_padded) - -# The closer the class is to 1, the more likely that the message is spam -for x in range(len(text_messages)): - print(f"Message: \"{text_messages[x]}\"") - print(f"Likeliness of spam in percentage: {classes[x][0]:.5f}") - print('\n') - - -#tf.keras.utils.plot_model(model, rankdir="LR", show_shapes=True) diff --git a/model_v2.py b/model_v2.py index 14e4290..54499dd 100644 --- a/model_v2.py +++ b/model_v2.py @@ -12,8 +12,9 @@ from nltk.corpus import stopwords from tensorflow import keras from tensorflow.keras.preprocessing.sequence import pad_sequences from tensorflow.keras.preprocessing.text import Tokenizer +from wandb.keras import WandbCallback -print("TensorFlow version:", tf.__version__) +import wandb vocab_size = 1000 embedding_dim = 16 @@ -46,10 +47,12 @@ progress_bar = tf.keras.callbacks.ProgbarLogger() class SpamDectionModel(tf.keras.Model): - def __init__(self, vocab_size, embedding_dim, max_length, hp_units, hp_dropout, hp_l2): + def __init__(self, vectorize_layer, vocab_size, embedding_dim, max_length, hp_units, hp_dropout, hp_l2): super(SpamDectionModel, self).__init__() + #self.input_layer = tf.keras.Input(shape=(1,), dtype=tf.string) + self.vectorize_layer = vectorize_layer self.embedding = tf.keras.layers.Embedding( - vocab_size, embedding_dim, input_length=max_length, name="text_input") + vocab_size + 1, embedding_dim, input_length=max_length, name="text_input") self.glob_average_pooling_1d = tf.keras.layers.GlobalAveragePooling1D() self.dropout = tf.keras.layers.Dropout(hp_dropout,) self.dense1 = tf.keras.layers.Dense(units=hp_units, activation='relu', @@ -61,6 +64,8 @@ class SpamDectionModel(tf.keras.Model): @tf.function def call(self, x, training=False): + #x = self.input_layer(x) + x = self.vectorize_layer(x) x = self.embedding(x) x = self.glob_average_pooling_1d(x) if training: @@ -72,14 +77,21 @@ class SpamDectionModel(tf.keras.Model): class SpamDectionHyperModel(kt.HyperModel): + def __init__(self, vectorize_layer, vocab_size, embedding_dim, max_length,): + super(SpamDectionHyperModel, self).__init__() + self.vectorize_layer = vectorize_layer + self.vocab_size = vocab_size + self.embedding_dim = embedding_dim + self.max_length = max_length + def build(self, hp): # Tune the number of units in the first Dense layer # Choose an optimal value between 6-512 hp_units = hp.Int('units', min_value=6, max_value=512, step=12) hp_dropout = hp.Float('dropout', min_value=.1, max_value=.9, step=.01) hp_l2 = hp.Float('l2', min_value=0.0001, max_value=0.001, step=0.0001) - model = SpamDectionModel( - vocab_size, embedding_dim, max_length, hp_units, hp_dropout, hp_l2) + model = SpamDectionModel(self.vectorize_layer, + self.vocab_size, self.embedding_dim, self.max_length, hp_units, hp_dropout, hp_l2) # Adam was best so far # tf.keras.optimizers.Nadam() has similar results to Adam but a bit worse. second best hp_learning_rate = hp.Choice('learning_rate', values=[ @@ -115,16 +127,16 @@ def remove_stopwords(input_text): def change_labels(x): return 1 if x == "spam" else 0 -def tokenize_data(data, training_sentences, testing_sentences): - #tokenizer = Tokenizer(num_words=vocab_size, oov_token=oov_tok) +def tokenize_data(data): + # tokenizer = Tokenizer(num_words=vocab_size, oov_token=oov_tok) # tokenizer.fit_on_texts(training_sentences) - #sequences = tokenizer.texts_to_sequences(training_sentences) + # sequences = tokenizer.texts_to_sequences(training_sentences) # padded = pad_sequences(sequences, maxlen=max_length, padding=padding_type, # truncating=trunc_type) - #testing_sequences = tokenizer.texts_to_sequences(testing_sentences) + # testing_sequences = tokenizer.texts_to_sequences(testing_sentences) # testing_padded = pad_sequences(testing_sequences, maxlen=max_length, # padding=padding_type, truncating=trunc_type) @@ -139,23 +151,23 @@ def tokenize_data(data, training_sentences, testing_sentences): vectorize_layer.adapt(data) # Create the model that uses the vectorize text layer - model = tf.keras.models.Sequential() + # model = tf.keras.models.Sequential() # Start by creating an explicit input layer. It needs to have a shape of # (1,) (because we need to guarantee that there is exactly one string # input per batch), and the dtype needs to be 'string'. - model.add(tf.keras.Input(shape=(1,), dtype=tf.string)) + # model.add(tf.keras.Input(shape=(1,), dtype=tf.string)) # The first layer in our model is the vectorization layer. After this # layer, we have a tensor of shape (batch_size, max_len) containing vocab # indices. - model.add(vectorize_layer) + # model.add(vectorize_layer) # Now, the model can map strings to integers, and you can add an embedding # layer to map these integers to learned embeddings. - padded = model.predict(training_sentences) - testing_padded = model.predict(testing_sentences) + # padded = model.predict(training_sentences) + # testing_padded = model.predict(testing_sentences) - return padded, testing_padded # , tokenizer + return vectorize_layer # , tokenizer def load_data(): @@ -175,27 +187,18 @@ def load_data(): sentences = data['message'].tolist() labels = data['label'].tolist() - # Separate out the sentences and labels into training and test sets - # training_size = int(len(sentences) * 0.8) - training_size = int(len(sentences) * 0.7) - training_sentences = sentences[0:training_size] - testing_sentences = sentences[training_size:] - training_labels = labels[0:training_size] - testing_labels = labels[training_size:] - # Make labels into numpy arrays for use with the network later - training_labels_final = np.array(training_labels) - testing_labels_final = np.array(testing_labels) - padded, testing_padded = tokenize_data( - sentences, training_sentences, testing_sentences) - return padded, testing_padded, training_labels_final, testing_labels_final, sentences + labels_final = np.array(labels) + sentences_final = np.array(sentences) + vectorize_layer = tokenize_data(sentences) + return vectorize_layer, sentences_final, labels_final -def train_hyperparamters(padded, training_labels_final, testing_padded, testing_labels_final, tuner): +def train_hyperparamters(data, labels_final, tuner): stop_early = tf.keras.callbacks.EarlyStopping( monitor='val_loss', patience=5) - tuner.search(padded, training_labels_final, epochs=5, verbose=1, - validation_data=(testing_padded, testing_labels_final), callbacks=[hypertuner_tensorboard_callback, stop_early, progress_bar]) + tuner.search(data, labels_final, epochs=5, verbose=1, validation_split=0.3, + callbacks=[hypertuner_tensorboard_callback, stop_early, progress_bar, WandbCallback()]) # Get the optimal hyperparameters best_hps = tuner.get_best_hyperparameters(num_trials=1)[0] @@ -209,15 +212,15 @@ def train_hyperparamters(padded, training_labels_final, testing_padded, testing_ return best_hps -def train_model(padded, training_labels_final, testing_padded, testing_labels_final, best_hps, tuner): +def train_model(data, labels_final, best_hps, tuner): num_epochs = 200 model = tuner.hypermodel.build(best_hps) - history = model.fit(padded, - training_labels_final, + history = model.fit(data, + labels_final, epochs=num_epochs, - verbose=0, - callbacks=[tensorboard_callback, progress_bar], - validation_data=(testing_padded, testing_labels_final)) + verbose=1, validation_split=0.3, + callbacks=[tensorboard_callback, + progress_bar, WandbCallback()],) val_acc_per_epoch = history.history['val_accuracy'] best_epoch = val_acc_per_epoch.index(max(val_acc_per_epoch)) + 5 print('Best epoch: %d' % (best_epoch,)) @@ -225,13 +228,13 @@ def train_model(padded, training_labels_final, testing_padded, testing_labels_fi print("Average test loss: ", np.average(history.history['val_loss'])) hypermodel = tuner.hypermodel.build(best_hps) - hypermodel_history = hypermodel.fit(padded, training_labels_final, verbose=0, - epochs=best_epoch, + hypermodel_history = hypermodel.fit(data, labels_final, verbose=1, + epochs=best_epoch, validation_split=0.3, callbacks=[hypermodel_tensorboard_callback, tf.keras.callbacks.ModelCheckpoint(filepath=checkpoint_prefix, - save_weights_only=True), progress_bar, + save_weights_only=True), progress_bar, WandbCallback(), # es_callback - ], validation_data=(testing_padded, testing_labels_final) + ] ) print("Average train loss(hypermodel_history): ", @@ -242,7 +245,7 @@ def train_model(padded, training_labels_final, testing_padded, testing_labels_fi return hypermodel -def test_model(sentences, model): +def test_model(vectorize_layer, model): # Use the model to predict whether a message is spam text_messages = ['Greg, can you call me back once you get this?', 'Congrats on your new iPhone! Click here to claim your prize...', @@ -268,19 +271,10 @@ def test_model(sentences, model): # Create the sequences padding_type = 'post' - #sample_sequences = tokenizer.texts_to_sequences(text_messages) + # sample_sequences = tokenizer.texts_to_sequences(text_messages) # fakes_padded = pad_sequences( # sample_sequences, padding=padding_type, maxlen=max_length) - - vectorize_layer = tf.keras.layers.TextVectorization( - output_mode='int', - output_sequence_length=max_length) - vectorize_layer.adapt(sentences) - vectorize_model = tf.keras.models.Sequential() - vectorize_model.add(tf.keras.Input(shape=(1,), dtype=tf.string)) - vectorize_model.add(vectorize_layer) - sequences = vectorize_model.predict(text_messages) - classes = model.predict(sequences) + classes = model.predict(text_messages) # The closer the class is to 1, the more likely that the message is spam for x in range(len(text_messages)): @@ -290,22 +284,24 @@ def test_model(sentences, model): def main(): + print("TensorFlow version:", tf.__version__) + # wandb.tensorboard.patch(root_logdir="logs/scalars/") + wandb.init(project="matrix-spam", entity="mtrnord") print("[Step 1/6] Loading data") - padded, testing_padded, training_labels_final, testing_labels_final, sentences = load_data() - model = SpamDectionHyperModel() - #print("[Step 2/6] Plotting model") - #tf.keras.utils.plot_model(model, rankdir="LR", show_shapes=True) + vectorize_layer, data, labels_final = load_data() + model = SpamDectionHyperModel( + vectorize_layer, vocab_size, embedding_dim, max_length) + # print("[Step 2/6] Plotting model") + # tf.keras.utils.plot_model(model, rankdir="LR", show_shapes=True) tuner = kt.Hyperband(model, hyperband_iterations=2, objective='val_accuracy', max_epochs=200, directory='hyper_tuning', project_name='spam-keras') print("[Step 3/6] Tuning hypervalues") - best_hps = train_hyperparamters( - padded, training_labels_final, testing_padded, testing_labels_final, tuner) + best_hps = train_hyperparamters(data, labels_final, tuner) print("[Step 4/6] Training model") - model = train_model(padded, training_labels_final, - testing_padded, testing_labels_final, best_hps, tuner) + model = train_model(data, labels_final, best_hps, tuner) print("[Step 5/6] Saving model") export_path = f"./models/spam_keras_{time.time()}" @@ -314,7 +310,7 @@ def main(): model.save(export_path) print("[Step 6/6] Testing model") - test_model(sentences, model) + test_model(vectorize_layer, model) if __name__ == "__main__": diff --git a/models/spam_keras_1664296013.7868948/keras_metadata.pb b/models/spam_keras_1664296013.7868948/keras_metadata.pb new file mode 100644 index 0000000..5acc4e0 --- /dev/null +++ b/models/spam_keras_1664296013.7868948/keras_metadata.pb @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:25c72070889b01d22f200a7341b3b1a468a8c09a1441d4ddda179328b4407cea +size 6338 diff --git a/models/spam_keras_1664296013.7868948/saved_model.pb b/models/spam_keras_1664296013.7868948/saved_model.pb new file mode 100644 index 0000000..12565f8 --- /dev/null +++ b/models/spam_keras_1664296013.7868948/saved_model.pb @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:de3f320d86418f990246e6077f3679cc72ce7914e0ea47038e64e91c92849cfc +size 248724 diff --git a/models/spam_keras_1664296013.7868948/variables/variables.data-00000-of-00001 b/models/spam_keras_1664296013.7868948/variables/variables.data-00000-of-00001 new file mode 100644 index 0000000..882a61f --- /dev/null +++ b/models/spam_keras_1664296013.7868948/variables/variables.data-00000-of-00001 @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:8e89306b74b3ea1d58b2b1368899ccb9b5dfc501ba15f76011c1c228b4ac0a46 +size 262040 diff --git a/models/spam_keras_1664296013.7868948/variables/variables.index b/models/spam_keras_1664296013.7868948/variables/variables.index new file mode 100644 index 0000000..564c28f --- /dev/null +++ b/models/spam_keras_1664296013.7868948/variables/variables.index @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:487f6aa1025c284ed111aefc8ec9ff93dd0c04e2d24007fcbfd284c63a1d8955 +size 1625 diff --git a/models/spam_keras_1664302378.8613324/keras_metadata.pb b/models/spam_keras_1664302378.8613324/keras_metadata.pb new file mode 100644 index 0000000..1c40a78 --- /dev/null +++ b/models/spam_keras_1664302378.8613324/keras_metadata.pb @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:dff883e7488707e6998153c9b2e04cabeadd2564a3626a23bf33b36265928222 +size 7976 diff --git a/models/spam_keras_1664302378.8613324/saved_model.pb b/models/spam_keras_1664302378.8613324/saved_model.pb new file mode 100644 index 0000000..b5521e0 --- /dev/null +++ b/models/spam_keras_1664302378.8613324/saved_model.pb @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:526c09bfacee767ce2cd7fc527eafffdf114be3b2f77f45e00250539e859e11d +size 278772 diff --git a/models/spam_keras_1664302378.8613324/variables/variables.data-00000-of-00001 b/models/spam_keras_1664302378.8613324/variables/variables.data-00000-of-00001 new file mode 100644 index 0000000..025cfaa --- /dev/null +++ b/models/spam_keras_1664302378.8613324/variables/variables.data-00000-of-00001 @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:65b73789b297e3343d54f17222f61749de3a8afecc8b635016dcedb2c634cc3d +size 263443 diff --git a/models/spam_keras_1664302378.8613324/variables/variables.index b/models/spam_keras_1664302378.8613324/variables/variables.index new file mode 100644 index 0000000..e215166 --- /dev/null +++ b/models/spam_keras_1664302378.8613324/variables/variables.index @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:146ae93e2ec7ceec263dcb5dba9d1f97c18aa3efe3d5d5f9f4e327f1c10d53ef +size 1733 diff --git a/models/spam_keras_1664302816.4003732/keras_metadata.pb b/models/spam_keras_1664302816.4003732/keras_metadata.pb new file mode 100644 index 0000000..1c40a78 --- /dev/null +++ b/models/spam_keras_1664302816.4003732/keras_metadata.pb @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:dff883e7488707e6998153c9b2e04cabeadd2564a3626a23bf33b36265928222 +size 7976 diff --git a/models/spam_keras_1664302816.4003732/saved_model.pb b/models/spam_keras_1664302816.4003732/saved_model.pb new file mode 100644 index 0000000..14c74a0 --- /dev/null +++ b/models/spam_keras_1664302816.4003732/saved_model.pb @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:524b845b4f7794dbbf77aa159be96dc4c78b13df0611e967ad58d8fc5c573d9d +size 278772 diff --git a/models/spam_keras_1664302816.4003732/variables/variables.data-00000-of-00001 b/models/spam_keras_1664302816.4003732/variables/variables.data-00000-of-00001 new file mode 100644 index 0000000..b4385da --- /dev/null +++ b/models/spam_keras_1664302816.4003732/variables/variables.data-00000-of-00001 @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:c18c6b667bf9e18f64496fd13f13114d62a39a21cabb69606ba5d49f32bca66b +size 263443 diff --git a/models/spam_keras_1664302816.4003732/variables/variables.index b/models/spam_keras_1664302816.4003732/variables/variables.index new file mode 100644 index 0000000..3b48fd6 --- /dev/null +++ b/models/spam_keras_1664302816.4003732/variables/variables.index @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:8b1ed8b62f43ac290d290889c4a036a85afe193246a63472dd6842b91d1220c7 +size 1733 diff --git a/models/spam_keras_1664303305.1441052/keras_metadata.pb b/models/spam_keras_1664303305.1441052/keras_metadata.pb new file mode 100644 index 0000000..1c40a78 --- /dev/null +++ b/models/spam_keras_1664303305.1441052/keras_metadata.pb @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:dff883e7488707e6998153c9b2e04cabeadd2564a3626a23bf33b36265928222 +size 7976 diff --git a/models/spam_keras_1664303305.1441052/saved_model.pb b/models/spam_keras_1664303305.1441052/saved_model.pb new file mode 100644 index 0000000..2281ca5 --- /dev/null +++ b/models/spam_keras_1664303305.1441052/saved_model.pb @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:855c80af1b87df2469cc77280d9632a4a313e891c8c419eccaead8b4fb946185 +size 278772 diff --git a/models/spam_keras_1664303305.1441052/variables/variables.data-00000-of-00001 b/models/spam_keras_1664303305.1441052/variables/variables.data-00000-of-00001 new file mode 100644 index 0000000..ab9c496 --- /dev/null +++ b/models/spam_keras_1664303305.1441052/variables/variables.data-00000-of-00001 @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:92261311d426ede421f32f0a05fad35cacd6809b0b0bba0ffb0629ef84530864 +size 263443 diff --git a/models/spam_keras_1664303305.1441052/variables/variables.index b/models/spam_keras_1664303305.1441052/variables/variables.index new file mode 100644 index 0000000..3598825 --- /dev/null +++ b/models/spam_keras_1664303305.1441052/variables/variables.index @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:f0a87ae580f8a44c2cb333e7ee476b26a04d21b1b27d9dcc96286c4182cb2309 +size 1733 diff --git a/spam-keras.ipynb b/spam-keras.ipynb deleted file mode 100644 index 33c54e3..0000000 --- a/spam-keras.ipynb +++ /dev/null @@ -1,674 +0,0 @@ -{ - "cells": [ - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "# Spam Model using Keras\n", - "## Imports" - ] - }, - { - "cell_type": "code", - "execution_count": 1, - "metadata": {}, - "outputs": [ - { - "name": "stderr", - "output_type": "stream", - "text": [ - "2022-09-26 11:49:22.756897: I tensorflow/core/platform/cpu_feature_guard.cc:193] This TensorFlow binary is optimized with oneAPI Deep Neural Network Library (oneDNN) to use the following CPU instructions in performance-critical operations: AVX2 FMA\n", - "To enable them in other operations, rebuild TensorFlow with the appropriate compiler flags.\n", - "2022-09-26 11:49:23.682758: E tensorflow/stream_executor/cuda/cuda_blas.cc:2981] Unable to register cuBLAS factory: Attempting to register factory for plugin cuBLAS when one has already been registered\n", - "2022-09-26 11:49:25.265522: W tensorflow/stream_executor/platform/default/dso_loader.cc:64] Could not load dynamic library 'libnvinfer.so.7'; dlerror: libnvinfer.so.7: cannot open shared object file: No such file or directory; LD_LIBRARY_PATH: /home/marcel/libtorch/lib:\n", - "2022-09-26 11:49:25.265770: W tensorflow/stream_executor/platform/default/dso_loader.cc:64] Could not load dynamic library 'libnvinfer_plugin.so.7'; dlerror: libnvinfer_plugin.so.7: cannot open shared object file: No such file or directory; LD_LIBRARY_PATH: /home/marcel/libtorch/lib:\n", - "2022-09-26 11:49:25.265778: W tensorflow/compiler/tf2tensorrt/utils/py_utils.cc:38] TF-TRT Warning: Cannot dlopen some TensorRT libraries. If you would like to use Nvidia GPU with TensorRT, please make sure the missing libraries mentioned above are installed properly.\n" - ] - } - ], - "source": [ - "import keras_tuner as kt\n", - "import matplotlib.pyplot as plt\n", - "import numpy as np\n", - "import pandas as pd\n", - "import tensorflow as tf\n", - "import tensorflow_addons as tfa\n", - "from nltk.corpus import stopwords\n", - "from tensorflow.keras.preprocessing.sequence import pad_sequences\n", - "from tensorflow.keras.preprocessing.text import Tokenizer" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "## Import dataset and normalize" - ] - }, - { - "cell_type": "code", - "execution_count": 2, - "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "label 0 \\\n", - "message count 4845 \n", - " unique 4533 \n", - " top Sorry, I'll call later \n", - " freq 30 \n", - "\n", - "label 1 \n", - "message count 750 \n", - " unique 644 \n", - " top Please call customer service representative FR... \n", - " freq 4 \n", - "label 0 \\\n", - "message count 4845 \n", - " unique 4533 \n", - " top Sorry, I'll call later \n", - " freq 30 \n", - "\n", - "label 1 \n", - "message count 750 \n", - " unique 644 \n", - " top Please call customer service representative FR... \n", - " freq 4 \n" - ] - } - ], - "source": [ - "# Read data\n", - "data = pd.read_csv('./input/MatrixData', sep='\\t')\n", - "\n", - "\n", - "def remove_stopwords(input_text):\n", - " '''\n", - " Function to remove English stopwords from a Pandas Series.\n", - " \n", - " Parameters:\n", - " input_text : text to clean\n", - " Output:\n", - " cleaned Pandas Series \n", - " '''\n", - " stopwords_list = stopwords.words('english')\n", - " # Some words which might indicate a certain sentiment are kept via a whitelist\n", - " whitelist = [\"n't\", \"not\", \"no\"]\n", - " words = input_text.split()\n", - " clean_words = [word for word in words if (\n", - " word not in stopwords_list or word in whitelist) and len(word) > 1]\n", - " return \" \".join(clean_words)\n", - "\n", - "# Remve unknown\n", - "data.dropna(inplace=True)\n", - "\n", - "# Convert label to something useful\n", - "def change_labels(x): return 1 if x == \"spam\" else 0\n", - "data['label'] = data['label'].apply(change_labels) \n", - "\n", - "# Count by label\n", - "spam = 0\n", - "ham = 0\n", - "\n", - "\n", - "def count_labels(x):\n", - " if x == 1:\n", - " global spam\n", - " spam += 1\n", - " else:\n", - " global ham\n", - " ham += 1\n", - " return x\n", - "#.apply(count_labels)\n", - "#print(\"Spam: \", spam)\n", - "#print(\"Ham: \", ham)\n", - "\n", - "# Remove stopwords\n", - "data['message'] = data['message'].apply(\n", - " remove_stopwords)\n", - "\n", - "# Print unbalanced\n", - "print(data.groupby('label').describe().T)\n", - "\n", - "\n", - "#ham_msg = data[data.label == 0]\n", - "#spam_msg = data[data.label == 1]\n", - "\n", - "#randomly taking data from ham_msg\n", - "#ham_msg = ham_msg.sample(n=len(spam_msg)*2, random_state=42)\n", - "\n", - "#data = pd.concat([ham_msg, spam_msg]).reset_index(drop=True)\n", - "\n", - "# Balanced\n", - "print(data.groupby('label').describe().T)\n", - "\n", - "# Shuffle data\n", - "data = data.sample(frac=1).reset_index(drop=True)\n", - "\n", - "# Split data into messages and label sets\n", - "sentences = data['message'].tolist()\n", - "labels = data['label'].tolist()\n", - "\n", - "# Separate out the sentences and labels into training and test sets\n", - "#training_size = int(len(sentences) * 0.8)\n", - "training_size = int(len(sentences) * 0.7)\n", - "training_sentences = sentences[0:training_size]\n", - "testing_sentences = sentences[training_size:]\n", - "training_labels = labels[0:training_size]\n", - "testing_labels = labels[training_size:]\n", - "\n", - "# Make labels into numpy arrays for use with the network later\n", - "training_labels_final = np.array(training_labels)\n", - "testing_labels_final = np.array(testing_labels)" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "## Tokenize" - ] - }, - { - "cell_type": "code", - "execution_count": 3, - "metadata": {}, - "outputs": [], - "source": [ - "vocab_size = 1000\n", - "embedding_dim = 16\n", - "#embedding_dim = 32\n", - "#max_length = 120\n", - "max_length = None\n", - "trunc_type = 'post'\n", - "padding_type = 'post'\n", - "oov_tok = \"\"\n" - ] - }, - { - "cell_type": "code", - "execution_count": 4, - "metadata": {}, - "outputs": [], - "source": [ - "tokenizer = Tokenizer(num_words=vocab_size, oov_token=oov_tok)\n", - "\n", - "tokenizer.fit_on_texts(training_sentences)\n", - "word_index = tokenizer.word_index\n", - "\n", - "sequences = tokenizer.texts_to_sequences(training_sentences)\n", - "padded = pad_sequences(sequences, maxlen=max_length, padding=padding_type,\n", - " truncating=trunc_type)\n", - "\n", - "testing_sequences = tokenizer.texts_to_sequences(testing_sentences)\n", - "testing_padded = pad_sequences(testing_sequences, maxlen=max_length,\n", - " padding=padding_type, truncating=trunc_type)\n" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "## Model" - ] - }, - { - "cell_type": "code", - "execution_count": 5, - "metadata": {}, - "outputs": [], - "source": [ - "from datetime import datetime\n", - "\n", - "from tensorflow import keras\n", - "\n", - "logdir = \"logs/scalars/\" + datetime.now().strftime(\"%Y%m%d-%H%M%S\")\n", - "tensorboard_callback = keras.callbacks.TensorBoard(log_dir=logdir)\n", - "\n", - "hypermodel_logdir = \"logs/scalars/\" + datetime.now().strftime(\"%Y%m%d-%H%M%S\") + \"_hypermodel\"\n", - "hypermodel_tensorboard_callback = keras.callbacks.TensorBoard(\n", - " log_dir=hypermodel_logdir)\n", - "\n", - "hypertuner_logdir = \"hypertuner_logs/scalars/\" + datetime.now().strftime(\"%Y%m%d-%H%M%S\")\n", - "hypertuner_tensorboard_callback = keras.callbacks.TensorBoard(\n", - " log_dir=hypertuner_logdir)\n", - "# Define the checkpoint directory to store the checkpoints.\n", - "checkpoint_dir = './training_checkpoints'\n", - "# Define the name of the checkpoint files.\n", - "checkpoint_prefix = os.path.join(checkpoint_dir, \"ckpt_{epoch}\")\n", - "\n", - "#es_callback = tf.keras.callbacks.EarlyStopping(monitor='val_loss', patience=3)" - ] - }, - { - "cell_type": "code", - "execution_count": 6, - "metadata": {}, - "outputs": [], - "source": [ - "def model_builder(hp):\n", - " # Tune the number of units in the first Dense layer\n", - " # Choose an optimal value between 6-512\n", - " hp_units = hp.Int('units', min_value=6, max_value=512, step=12)\n", - " hp_dropout = hp.Float('dropout', min_value=.1, max_value=.9, step=.01)\n", - " hp_l2 = hp.Float('l2', min_value=0.0001, max_value=0.001, step=0.0001)\n", - " model = tf.keras.Sequential([\n", - " tf.keras.layers.Embedding(\n", - " vocab_size, embedding_dim, input_length=max_length),\n", - " tf.keras.layers.GlobalAveragePooling1D(),\n", - " tf.keras.layers.Dropout(hp_dropout,),\n", - " tf.keras.layers.Dense(units=hp_units, activation='relu',\n", - " kernel_regularizer=tf.keras.regularizers.l2(hp_l2)),\n", - " #tf.keras.layers.Dense(6, activation='relu',\n", - " # kernel_regularizer=tf.keras.regularizers.l2(0.0001)),\n", - " tf.keras.layers.Dropout(hp_dropout,),\n", - " tf.keras.layers.Dense(1, activation='sigmoid')\n", - " ])\n", - " # Adam was best so far\n", - " # tf.keras.optimizers.Nadam() has similar results to Adam but a bit worse. second best\n", - " hp_learning_rate = hp.Choice('learning_rate', values=[1e-2, 1e-3, 1e-4, 1e-5,])\n", - " opt = tf.keras.optimizers.Adam(learning_rate=hp_learning_rate)\n", - " # opt = tf.keras.optimizers.Nadam()\n", - " model.compile(loss=tf.keras.losses.BinaryCrossentropy(),\n", - " optimizer=opt, metrics=['accuracy'])\n", - " #print(model.summary())\n", - "\n", - " return model\n" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "## Get best hypermodel values" - ] - }, - { - "cell_type": "code", - "execution_count": 7, - "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "INFO:tensorflow:Reloading Oracle from existing project hyper_tuning/spam-keras/oracle.json\n" - ] - }, - { - "name": "stderr", - "output_type": "stream", - "text": [ - "2022-09-26 11:49:32.695952: I tensorflow/stream_executor/cuda/cuda_gpu_executor.cc:966] could not open file to read NUMA node: /sys/bus/pci/devices/0000:09:00.0/numa_node\n", - "Your kernel may have been built without NUMA support.\n" - ] - }, - { - "name": "stdout", - "output_type": "stream", - "text": [ - "INFO:tensorflow:Reloading Tuner from hyper_tuning/spam-keras/tuner0.json\n" - ] - }, - { - "name": "stderr", - "output_type": "stream", - "text": [ - "2022-09-26 11:49:32.820214: I tensorflow/stream_executor/cuda/cuda_gpu_executor.cc:966] could not open file to read NUMA node: /sys/bus/pci/devices/0000:09:00.0/numa_node\n", - "Your kernel may have been built without NUMA support.\n", - "2022-09-26 11:49:32.820285: I tensorflow/stream_executor/cuda/cuda_gpu_executor.cc:966] could not open file to read NUMA node: /sys/bus/pci/devices/0000:09:00.0/numa_node\n", - "Your kernel may have been built without NUMA support.\n", - "2022-09-26 11:49:32.822176: I tensorflow/core/platform/cpu_feature_guard.cc:193] This TensorFlow binary is optimized with oneAPI Deep Neural Network Library (oneDNN) to use the following CPU instructions in performance-critical operations: AVX2 FMA\n", - "To enable them in other operations, rebuild TensorFlow with the appropriate compiler flags.\n", - "2022-09-26 11:49:32.822979: I tensorflow/stream_executor/cuda/cuda_gpu_executor.cc:966] could not open file to read NUMA node: /sys/bus/pci/devices/0000:09:00.0/numa_node\n", - "Your kernel may have been built without NUMA support.\n", - "2022-09-26 11:49:32.823077: I tensorflow/stream_executor/cuda/cuda_gpu_executor.cc:966] could not open file to read NUMA node: /sys/bus/pci/devices/0000:09:00.0/numa_node\n", - "Your kernel may have been built without NUMA support.\n", - "2022-09-26 11:49:32.823130: I tensorflow/stream_executor/cuda/cuda_gpu_executor.cc:966] could not open file to read NUMA node: /sys/bus/pci/devices/0000:09:00.0/numa_node\n", - "Your kernel may have been built without NUMA support.\n", - "2022-09-26 11:49:34.550694: I tensorflow/stream_executor/cuda/cuda_gpu_executor.cc:966] could not open file to read NUMA node: /sys/bus/pci/devices/0000:09:00.0/numa_node\n", - "Your kernel may have been built without NUMA support.\n", - "2022-09-26 11:49:34.550929: I tensorflow/stream_executor/cuda/cuda_gpu_executor.cc:966] could not open file to read NUMA node: /sys/bus/pci/devices/0000:09:00.0/numa_node\n", - "Your kernel may have been built without NUMA support.\n", - "2022-09-26 11:49:34.550941: I tensorflow/core/common_runtime/gpu/gpu_device.cc:1700] Could not identify NUMA node of platform GPU id 0, defaulting to 0. Your kernel may not have been built with NUMA support.\n", - "2022-09-26 11:49:34.551039: I tensorflow/stream_executor/cuda/cuda_gpu_executor.cc:966] could not open file to read NUMA node: /sys/bus/pci/devices/0000:09:00.0/numa_node\n", - "Your kernel may have been built without NUMA support.\n", - "2022-09-26 11:49:34.551613: I tensorflow/core/common_runtime/gpu/gpu_device.cc:1616] Created device /job:localhost/replica:0/task:0/device:GPU:0 with 5904 MB memory: -> device: 0, name: NVIDIA GeForce RTX 2080 SUPER, pci bus id: 0000:09:00.0, compute capability: 7.5\n" - ] - }, - { - "name": "stdout", - "output_type": "stream", - "text": [ - "INFO:tensorflow:Oracle triggered exit\n", - "\n", - "The hyperparameter search is complete. The optimal number of units in the first densely-connected\n", - "layer is 330 and the optimal learning rate for the optimizer is 0.01.\n", - "The optimal dropout rate is 0.5499999999999998 and the optimal l2 rate is 0.0001.\n", - "\n" - ] - } - ], - "source": [ - "tuner = kt.Hyperband(model_builder,\n", - " objective='val_accuracy',\n", - " max_epochs=750,\n", - " factor=3,\n", - " directory='hyper_tuning',\n", - " project_name='spam-keras')\n", - "stop_early = tf.keras.callbacks.EarlyStopping(monitor='val_loss', patience=5)\n", - "tuner.search(padded, training_labels_final, epochs=800, verbose=0,\n", - " validation_data=(testing_padded, testing_labels_final), callbacks=[hypertuner_tensorboard_callback, stop_early])\n", - "# Get the optimal hyperparameters\n", - "best_hps = tuner.get_best_hyperparameters(num_trials=1)[0]\n", - "\n", - "print(f\"\"\"\n", - "The hyperparameter search is complete. The optimal number of units in the first densely-connected\n", - "layer is {best_hps.get('units')} and the optimal learning rate for the optimizer is {best_hps.get('learning_rate')}.\n", - "The optimal dropout rate is {best_hps.get('dropout')} and the optimal l2 rate is {best_hps.get('l2')}.\n", - "\"\"\")" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "### Train" - ] - }, - { - "cell_type": "code", - "execution_count": 8, - "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "Best epoch: 6\n", - "Average train loss: 0.006002985829836689\n", - "Average test loss: 0.22046395140389602\n", - "Average train loss(hypermodel_history): 0.10212815863390763\n", - "Average test loss(hypermodel_history): 0.08288264522949855\n" - ] - } - ], - "source": [ - "num_epochs = 200\n", - "model = tuner.hypermodel.build(best_hps)\n", - "history = model.fit(padded, \n", - " training_labels_final, \n", - " epochs=num_epochs, \n", - " verbose=0, \n", - " callbacks=[tensorboard_callback,],\n", - " validation_data=(testing_padded, testing_labels_final))\n", - "\n", - "\n", - "val_acc_per_epoch = history.history['val_accuracy']\n", - "best_epoch = val_acc_per_epoch.index(max(val_acc_per_epoch)) + 1\n", - "print('Best epoch: %d' % (best_epoch,))\n", - "print(\"Average train loss: \", np.average(history.history['loss']))\n", - "print(\"Average test loss: \", np.average(history.history['val_loss']))\n", - "\n", - "hypermodel = tuner.hypermodel.build(best_hps)\n", - "hypermodel_history = hypermodel.fit(padded, training_labels_final, verbose=0,\n", - " epochs=best_epoch, validation_split=0.2,\n", - " callbacks=[hypermodel_tensorboard_callback,\n", - " tf.keras.callbacks.ModelCheckpoint(filepath=checkpoint_prefix,\n", - " save_weights_only=True),\n", - " #es_callback\n", - " ],\n", - " )\n", - "\n", - "print(\"Average train loss(hypermodel_history): \",\n", - " np.average(hypermodel_history.history['loss']))\n", - "print(\"Average test loss(hypermodel_history): \",\n", - " np.average(hypermodel_history.history['val_loss']))\n" - ] - }, - { - "cell_type": "code", - "execution_count": 9, - "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "INFO:tensorflow:Assets written to: ./models/spam_keras_1664186466.3541195/assets\n" - ] - }, - { - "data": { - "image/png": "iVBORw0KGgoAAAANSUhEUgAAAjcAAAHHCAYAAABDUnkqAAAAOXRFWHRTb2Z0d2FyZQBNYXRwbG90bGliIHZlcnNpb24zLjYuMCwgaHR0cHM6Ly9tYXRwbG90bGliLm9yZy89olMNAAAACXBIWXMAAA9hAAAPYQGoP6dpAACunUlEQVR4nO2dd3gUVdvG791NskkISQiBJEAgIF3pTRBsREERsaPiS1Hxs2DD8qK+gh1siAqCDbGLoqCCIhA60nvvEFo66X13vj8ms3tmdmZ2Zvtunt915drd2TMzZ2Ync+552jFwHMeBIAiCIAgiRDD6uwMEQRAEQRCehMQNQRAEQRAhBYkbgiAIgiBCChI3BEEQBEGEFCRuCIIgCIIIKUjcEARBEAQRUpC4IQiCIAgipCBxQxAEQRBESEHihiAIgiCIkILEDUEQAc+pU6dgMBgwb9483euuXr0aBoMBq1evVm03b948GAwGnDp1yqU+EgQROJC4IQiCIAgipCBxQxAEQRBESEHihiAIgiCIkILEDUEQTnnllVdgMBhw5MgR3HfffYiLi0OTJk3w8ssvg+M4nDlzBiNGjEBsbCySk5Px/vvvO2wjJycHDzzwAJKSkhAZGYlu3brh66+/dmhXWFiIsWPHIi4uDvHx8RgzZgwKCwtl+3Xo0CHccccdSEhIQGRkJHr37o0//vjDo8f+ySef4NJLL4XZbEazZs3w2GOPOfTn6NGjuP3225GcnIzIyEi0aNECd999N4qKimxtli9fjoEDByI+Ph4xMTHo0KEDXnzxRY/2lSAInjB/d4AgiOBh5MiR6NSpE6ZNm4YlS5bgjTfeQEJCAj799FNce+21ePvtt/H999/j2WefRZ8+fXDllVcCACoqKnD11Vfj2LFjmDBhAlq3bo1ffvkFY8eORWFhIZ588kkAAMdxGDFiBNavX4+HH34YnTp1wsKFCzFmzBiHvuzfvx9XXHEFmjdvjkmTJqFBgwb4+eefccstt+DXX3/Frbfe6vbxvvLKK3j11VeRnp6ORx55BIcPH8bs2bOxdetWbNiwAeHh4aiursaQIUNQVVWFxx9/HMnJyTh37hwWL16MwsJCxMXFYf/+/bjpppvQtWtXvPbaazCbzTh27Bg2bNjgdh8JgpCBIwiCcMKUKVM4ANxDDz1kW1ZbW8u1aNGCMxgM3LRp02zLL168yEVFRXFjxoyxLZsxYwYHgPvuu+9sy6qrq7n+/ftzMTExXHFxMcdxHLdo0SIOAPfOO++I9jNo0CAOAPfVV1/Zlg8ePJjr0qULV1lZaVtmtVq5AQMGcO3atbMtW7VqFQeAW7VqleoxfvXVVxwA7uTJkxzHcVxOTg4XERHBXX/99ZzFYrG1mzlzJgeAmzt3LsdxHLdz504OAPfLL78obvuDDz7gAHC5ubmqfSAIwjOQW4ogCM08+OCDtvcmkwm9e/cGx3F44IEHbMvj4+PRoUMHnDhxwrbsr7/+QnJyMu655x7bsvDwcDzxxBMoLS3FmjVrbO3CwsLwyCOPiPbz+OOPi/pRUFCAlStX4q677kJJSQny8vKQl5eH/Px8DBkyBEePHsW5c+fcOtYVK1aguroaTz31FIxG+61y/PjxiI2NxZIlSwAAcXFxAIB//vkH5eXlstuKj48HAPz++++wWq1u9YsgCOeQuCEIQjMtW7YUfY6Li0NkZCQSExMdll+8eNH2+fTp02jXrp1IJABAp06dbN8LrykpKYiJiRG169Chg+jzsWPHwHEcXn75ZTRp0kT0N2XKFAB8jI87CH2S7jsiIgJt2rSxfd+6dWtMnDgRX3zxBRITEzFkyBDMmjVLFG8zcuRIXHHFFXjwwQeRlJSEu+++Gz///DMJHYLwEhRzQxCEZkwmk6ZlAB8/4y0EUfDss89iyJAhsm3atm3rtf1Lef/99zF27Fj8/vvvWLZsGZ544glMnToVmzZtQosWLRAVFYW1a9di1apVWLJkCZYuXYr58+fj2muvxbJlyxTPIUEQrkGWG4IgvE6rVq1w9OhRB0vFoUOHbN8LrxcuXEBpaamo3eHDh0Wf27RpA4B3baWnp8v+NWzY0O0+y+27uroaJ0+etH0v0KVLF/zvf//D2rVrsW7dOpw7dw5z5syxfW80GjF48GBMnz4dBw4cwJtvvomVK1di1apVbvWTIAhHSNwQBOF1brzxRmRlZWH+/Pm2ZbW1tfj4448RExODq666ytautrYWs2fPtrWzWCz4+OOPRdtr2rQprr76anz66ae4cOGCw/5yc3Pd7nN6ejoiIiLw0UcfiaxQX375JYqKijBs2DAAQHFxMWpra0XrdunSBUajEVVVVQD4GCEp3bt3BwBbG4IgPAe5pQiC8DoPPfQQPv30U4wdOxbbt29HWloaFixYgA0bNmDGjBk2K8vw4cNxxRVXYNKkSTh16hQ6d+6M3377TRS/IjBr1iwMHDgQXbp0wfjx49GmTRtkZ2dj48aNOHv2LHbv3u1Wn5s0aYIXXngBr776KoYOHYqbb74Zhw8fxieffII+ffrgvvvuAwCsXLkSEyZMwJ133on27dujtrYW3377LUwmE26//XYAwGuvvYa1a9di2LBhaNWqFXJycvDJJ5+gRYsWGDhwoFv9JAjCERI3BEF4naioKKxevRqTJk3C119/jeLiYnTo0AFfffUVxo4da2tnNBrxxx9/4KmnnsJ3330Hg8GAm2++Ge+//z569Ogh2mbnzp2xbds2vPrqq5g3bx7y8/PRtGlT9OjRA5MnT/ZIv1955RU0adIEM2fOxNNPP42EhAQ89NBDeOuttxAeHg4A6NatG4YMGYI///wT586dQ3R0NLp164a///4bl19+OQDg5ptvxqlTpzB37lzk5eUhMTERV111FV599VVbthVBEJ7DwHkz6o8gCIIgCMLHUMwNQRAEQRAhBYkbgiAIgiBCChI3BEEQBEGEFCRuCIIgCIIIKUjcEARBEAQRUpC4IQiCIAgipKh3dW6sVivOnz+Phg0bwmAw+Ls7BEEQBEFogOM4lJSUoFmzZg6T8Eqpd+Lm/PnzSE1N9Xc3CIIgCIJwgTNnzqBFixaqbeqduBHKvJ85cwaxsbF+7g1BEARBEFooLi5Gamqqpklx6524EVxRsbGxJG4IgiAIIsjQElJCAcUEQRAEQYQUJG4IgiAIgggpSNwQBEEQBBFS1LuYG61YLBbU1NT4uxtBSXh4OEwmk7+7QRAEQdRTSNxI4DgOWVlZKCws9HdXgpr4+HgkJydTLSGCIAjC55C4kSAIm6ZNmyI6OpoGZ51wHIfy8nLk5OQAAFJSUvzcI4IgCKK+QeKGwWKx2IRN48aN/d2doCUqKgoAkJOTg6ZNm5KLiiAIgvApFFDMIMTYREdH+7knwY9wDiluiSAIgvA1JG5kIFeU+9A5JAiCIPwFiRuCIAiCIEIKEjeEA2lpaZgxY4a/u0EQBEEQLkEBxSHC1Vdfje7du3tElGzduhUNGjRwv1MEQRAE4QdI3NQTOI6DxWJBWJjzn7xJkyY+6BFB1FOqy4EISlogCG9CbqkQYOzYsVizZg0+/PBDGAwGGAwGzJs3DwaDAX///Td69eoFs9mM9evX4/jx4xgxYgSSkpIQExODPn36YMWKFaLtSd1SBoMBX3zxBW699VZER0ejXbt2+OOPP3x8lAQRAuxfCLyVAmya7e+eEERIQ+LGCRzHoby61i9/HMdp6uOHH36I/v37Y/z48bhw4QIuXLiA1NRUAMCkSZMwbdo0HDx4EF27dkVpaSluvPFGZGRkYOfOnRg6dCiGDx+OzMxM1X28+uqruOuuu7Bnzx7ceOONGDVqFAoKCtw+vwRRr1hwP/+6dJJ/+0EQIQ65pZxQUWNB58n/+GXfB14bgugI5z9RXFwcIiIiEB0djeTkZADAoUOHAACvvfYarrvuOlvbhIQEdOvWzfb59ddfx8KFC/HHH39gwoQJivsYO3Ys7rnnHgDAW2+9hY8++ghbtmzB0KFDXTo2giAIgvAWZLkJcXr37i36XFpaimeffRadOnVCfHw8YmJicPDgQaeWm65du9reN2jQALGxsbYpFgiC0ArVfyIIX0CWGydEhZtw4LUhftu3u0iznp599lksX74c7733Htq2bYuoqCjccccdqK6uVt1OeHi46LPBYIDVanW7fwRBEAThaUjcOMFgMGhyDfmbiIgIWCwWp+02bNiAsWPH4tZbbwXAW3JOnTrl5d4RBAEAMBgAbaF0BEG4gd/dUrNmzUJaWhoiIyPRr18/bNmyRbV9YWEhHnvsMaSkpMBsNqN9+/b466+/fNTbwCUtLQ2bN2/GqVOnkJeXp2hVadeuHX777Tfs2rULu3fvxr333ksWGIIgCCKk8Ku4mT9/PiZOnIgpU6Zgx44d6NatG4YMGaIYy1FdXY3rrrsOp06dwoIFC3D48GF8/vnnaN68uY97Hng8++yzMJlM6Ny5M5o0aaIYQzN9+nQ0atQIAwYMwPDhwzFkyBD07NnTx70lCIIgCO9h4LTmG3uBfv36oU+fPpg5cyYAwGq1IjU1FY8//jgmTXJMlZwzZw7effddHDp0yCEGRCvFxcWIi4tDUVERYmNjRd9VVlbi5MmTaN26NSIjI13aPsFD55IgZHgtEbDW8O9fKfJvXwgiyFAbv6X4zXJTXV2N7du3Iz093d4ZoxHp6enYuHGj7Dp//PEH+vfvj8ceewxJSUm47LLL8NZbb6nGmlRVVaG4uFj0RxAEQRBE6OI3cZOXlweLxYKkpCTR8qSkJGRlZcmuc+LECSxYsAAWiwV//fUXXn75Zbz//vt44403FPczdepUxMXF2f6E4nYEQRA+x0Cp4AThC/weUKwHq9WKpk2b4rPPPkOvXr0wcuRIvPTSS5gzZ47iOi+88AKKiopsf2fOnPFhjwmCIAiC8DV+y3FOTEyEyWRCdna2aHl2dratyq6UlJQUhIeHw2Sy13/p1KkTsrKyUF1djYiICId1zGYzzGazZztPEAThEmS5IQhf4DfLTUREBHr16oWMjAzbMqvVioyMDPTv3192nSuuuALHjh0TpS4fOXIEKSkpssKGIAiCIIj6h1/dUhMnTsTnn3+Or7/+GgcPHsQjjzyCsrIyjBs3DgAwevRovPDCC7b2jzzyCAoKCvDkk0/iyJEjWLJkCd566y089thj/joEgiAI7VDMDUH4BL+W3h05ciRyc3MxefJkZGVloXv37li6dKktyDgzMxNGo11/paam4p9//sHTTz+Nrl27onnz5njyySfx3//+11+HQBAEQRBEgOHXOjf+gOrc+AY6lwQhwxvJQG0F/57q3BD+proM2PgJ0Gk40LSjv3vjlKCoc0MQBEEQhB/JeB1Y9QbwST9/98TjkLghAPBzU82YMcPf3SCI0IZibkKDczuAzZ8CwT4v37lt/u6B1wj86a4JgiAIIpD4/Br+NSoB6Hqnf/tCyEKWG4IgCIJwhZwD/u4BoQCJmxDgs88+Q7NmzUT1fwBgxIgRuP/++3H8+HGMGDECSUlJiImJQZ8+fbBixQo/9ZYgCCJUqFf5OEEFiRtncBwfUe6PP42JbHfeeSfy8/OxatUq27KCggIsXboUo0aNQmlpKW688UZkZGRg586dGDp0KIYPH47MzExvnTWCIGShmJuQIuiTjUP3eqSYG2fUlANvNfPPvl88D0Q0cNqsUaNGuOGGG/DDDz9g8ODBAIAFCxYgMTER11xzDYxGI7p162Zr//rrr2PhwoX4448/MGHCBK91nyAIgiD8AVluQoRRo0bh119/RVVVFQDg+++/x9133w2j0YjS0lI8++yz6NSpE+Lj4xETE4ODBw+S5YYgfA1lS4UYwW65CV3IcuOM8GjeguKvfWtk+PDh4DgOS5YsQZ8+fbBu3Tp88MEHAIBnn30Wy5cvx3vvvYe2bdsiKioKd9xxB6qrq73Vc4IgiNAn6N1SoQuJG2cYDJpcQ/4mMjISt912G77//nscO3YMHTp0QM+ePQEAGzZswNixY3HrrbcCAEpLS3Hq1Ck/9pYg6itkuQktSNwEKiRuQohRo0bhpptuwv79+3HffffZlrdr1w6//fYbhg8fDoPBgJdfftkhs4ogCIKoZ4Swm5RibkKIa6+9FgkJCTh8+DDuvfde2/Lp06ejUaNGGDBgAIYPH44hQ4bYrDoEQfiQEB5M6iXklgpYyHITQhiNRpw/7xgflJaWhpUrV4qWPfbYY6LP5KYiCIIgQgWy3BAEQRCEK5DlJmAhcUMQBOEzyC0VWpC4CVRI3BAEQRAEEVKQuCEIgvAVZLgJLYLeLRW6FySJGxm4oL9g/Q+dQ4IgCMJfkLhhCA8PBwCUl5f7uSfBj3AOhXNKEAQQyk/K9RN6iAtUKBWcwWQyIT4+Hjk5OQCA6OhoGKguhS44jkN5eTlycnIQHx8Pk8nk7y4RBEF4B7JQBywkbiQkJycDgE3gEK4RHx9vO5cEQdRBD0shBombQIXEjQSDwYCUlBQ0bdoUNTU1/u5OUBIeHk4WG4IgQh+y3AQsJG4UMJlMNEATBOFhyHITWgS5uAlhSyIFFBMEQRAEEVKQuCEIgiAIIqQgcUMQBEEQrkAxNwELiRuCIAhfEcIxDvUTEjeBCokbgiAIgnAFstwELCRuCIIgfAZZbkKLYBc3oXs9krghCIIgCFcgy03AQuKGIAjCV1DMTYhB4iZQIXFDEARBEERIQeKGIAjCZ5DlhiB8AYkbgiAIgnCFYI+5CWE3KYkbgiAIXxHCg0n9JMjFTQhD4oYgCIIgXIG0TcBC4oYgCIIgXILUTaBC4oYgCMJnkFsqpAj2mJsQhsQNQRAEQXiKmkrg70nA8VX+7km9hsQNQRCEr6CA4hBDxnKz5VNg82zg21t83hvCDokbgiAIgvAUF0/5uwc6CF2xTeKGIAjCZ4TuYFIvkY25od84ECBxQxChSPF54MxWf/eCIEIPkaChgOJAJSDEzaxZs5CWlobIyEj069cPW7ZsUWw7b948GAwG0V9kZKQPe0sQQcD0TsCX6cD5nf7uCcFCMTfBDytu5Cw39BsHBH4XN/Pnz8fEiRMxZcoU7NixA926dcOQIUOQk5OjuE5sbCwuXLhg+zt9+rQPe0wQQcTpf/3dA4IIMchyEwz4XdxMnz4d48ePx7hx49C5c2fMmTMH0dHRmDt3ruI6BoMBycnJtr+kpCQf9pgggghrrb97QBChRSjVtglhK5NfxU11dTW2b9+O9PR02zKj0Yj09HRs3LhRcb3S0lK0atUKqampGDFiBPbv3++L7hJE8EHihiA8jBO3FAUUBwR+FTd5eXmwWCwOlpekpCRkZWXJrtOhQwfMnTsXv//+O7777jtYrVYMGDAAZ8+elW1fVVWF4uJi0R9B1BusFn/3gBBBA1/Q4yygOIStIcGE391Seunfvz9Gjx6N7t2746qrrsJvv/2GJk2a4NNPP5VtP3XqVMTFxdn+UlNTfdxjgvAjlhp/94AgQgxnlhsiEPCruElMTITJZEJ2drZoeXZ2NpKTkzVtIzw8HD169MCxY8dkv3/hhRdQVFRk+ztz5ozb/SaIoIHcUoEFPdUHP04FDf3GgYBfxU1ERAR69eqFjIwM2zKr1YqMjAz0799f0zYsFgv27t2LlJQU2e/NZjNiY2NFfwRRbyBxQxAeJkSzpULMChXm7w5MnDgRY8aMQe/evdG3b1/MmDEDZWVlGDduHABg9OjRaN68OaZOnQoAeO2113D55Zejbdu2KCwsxLvvvovTp0/jwQcf9OdhEERgQuImwKCn+qDHWZ2bYIXjQsqy6HdxM3LkSOTm5mLy5MnIyspC9+7dsXTpUluQcWZmJoxGu4Hp4sWLGD9+PLKystCoUSP06tUL//77Lzp37uyvQyCIwIXEDUF4Fs7KfnD8PlgFAmdFEIbhKuJ3cQMAEyZMwIQJE2S/W716tejzBx98gA8++MAHvSKIEIDETWARpOMewRJC1hoWkWgLfkJHphEE4QilghOEZ3HqlgpWBRtaoo3EDUGEMmS5CTCYgS+U4jXqFaEaUEyWG4IgggUSN4FLiA0m9YZQDigOIUjcEEQoQ+ImsDCQ5Sb4cfK7BXVAcehA4oYgQhmqUBzAkLgJSkK1iB+JG4IgggYKKA5cQmwwqZcEu/VNZGUK8mORQOKGIEIZcksFGOSWCnqc/W6sYLAGkYANMbFN4oYgQhkSN4FLiA0m9QZnRfxEbYPIchpiYpvEDUGEMlaKuQkoQtgNUH+QyZbKOwYUnJBpGkQCNsTETUBUKCYIwktQzE3gEkwDH2FHlApuBarLgJm9+M8v54vbBtX/X2iJG7LcEEQoQ26pwCXEnpTrDxJxU5Zn/2ypkjQNcAErFWohBIkbgghlKBU8cAmxwaTe4CAIJCJVVMsowC03JG4IgghKyHIDFF8Azm7zdy/qoJib4EdFEEjdUAHvlgrRasugmBuCCG0C/ubqA6Z35F//by2Q0s2/fWEJscGk3iC1dqhZPwL9NybLDUEQQQlZbuxkbvZ3D2j6hZBAOnGmmrgJ8IcLPWntQQaJG4IIZUjcBC4h9qRcb5BOnMl+tlocPwc0ZLkhCCIYIXETWHDSp34i6GBFAGeVfLY4fh/IiPoaWtcjiRuCCGUC/smxvhG6T8r1B8lvyD5AcFbx/1zAu6VC93okcUMQoQxZbgILqUuDCD6kgoAVM1aJ5SbgHy5I3BAEEYzQ9AsBRugOJvUHNctNELulQgwSNwQRypDlJrCgmJvgxyEVXMVy40vxwHHAyjeBvQv0rWN7H1pCh8QNQYQaQZWt4UsCQUyQWyqk4DhJjA3nmlvq/E7g0yuBE2tc70veEWDtO8CvDwDndmhbhwKKCYIIGtibFE2/EFiIDDeh9aRcb1CLueEsjgHGWvjuDuDCbuCbm13vV02F/f22LzWuFLqWG6pQTBAhB2u5IbdUYEFuqeBHJeZm6STg+Erme42Wm/I8522cdosRJ9XlGtcJXXFDlhuCCDVENyxySwUUITyY1Bukrhz2f4wVNoCP3cIuXFshHANG4oYgQo7QukmFFjIxN2X5QNFZ/3SH0I+aW8qhrY8DinXvN3TFNokbggg1QiwwMKSQq3Pzbhvgg0uBiov+6ROhk0AVNy5kabHtljwLWENH4JC4IYhQI8SewEILFTdA3jGf9oRwEbVUcCm+dEu58n/PHsuZTcChxZ7rj58hcUMQIQdZbgIWVdcB/W7BgUSgqgXtB5PlBgDK8z3XHz9D4oYgQg1ySwUwEreUNXTrjIQsumJu/GS5cSXmJsQgcUMQIYfkhkWDJk8gnAcHlwY7CAVA/wgNqKSCOzQN8IDiQPif8BIkbggi1JDesCgGJ4CQuDQ4SXVbIvBRFagS/BVzo/VakvbdYPBcf/wMiRuCCDlI3AQsUteBaPAjcRMc6LHckFvKX5C4IYhQgyw3gYs0FTyUiyxarUB5gb974XmkFhLVmBsfigdPBBSDLDcEQQQskhuqL0zjIVQfw7uozUsUYk/RP94NvNOanxQylHAIKFax3PjULeVKzI3kM7mlCIIIWHxtuVn5Bl+I7uIp7+4nFJCWuw/lgOKj//CvW77wbz88jg7rG7ml/AaJG4IIOXwsbta+y1fXXT3Nu/txhYCzhqgEo4as+zDQfgM3kc7sHigVil05z+SWIggiaPBXzE3ACQkEXp+kBYrZgdGnkyz6kED7DdxGR50bv2VLuZgKTm4pgiCChpC1CGgg4I5dLRU8RMVNyFludEy/EOhuKWm7EBKiJG4IItRwsNyEzg1LPypzOfkDteq2oWq5CTn0pIIHeLaUgws7dK5BEjcEEXKE7g1LN4FsueEkAcVqg2QwE2riWs/0C8FWxC+ErkESNwQRajiYmn01wAfgIBZoA6uaSyNkLTcB9hu4jXR+sEBxS3lg+gXhWLZ9BRwM7hnCSdwQRKhBRfwYAm1glc4oTZaboEPq/lGNuXHhf8/V86Xkljq7Hdi/SGkl8UdrLZB3FFj8FDB/lGv9CBDC/N0BgiA8DYkbG4F27GS5CX6ktYo8XcTPWguYwvWvpyRuvriWf01YB6R0BbbPA+JSgbaDHf8/LDVAWa7+fQcgAWG5mTVrFtLS0hAZGYl+/fphy5Ytmtb76aefYDAYcMstt3i3gwQRTJDlxk7AWQ1UXBpkuQkSdMTcHPwDOPinvs1balzslpPg+YLjwPldwJ9PAt/dBszsw9enYgmha9Dv4mb+/PmYOHEipkyZgh07dqBbt24YMmQIcnJyVNc7deoUnn32WQwaNMhHPSWIYIHq3NgINGGnNqN0yAZ+B+B14Q56pl84tgKYfx9QVaJtewBgdVXcOMuWMgDF5+wf8444Ngkh66Hfxc306dMxfvx4jBs3Dp07d8acOXMQHR2NuXPnKq5jsVgwatQovPrqq2jTpo0Pe0sQQYBSkGC9RGIp8TsqdW5C6Kk5tFERqErUVCh/J/3dLS5eB86ypbQU6JP2JYjnjPOruKmursb27duRnp5uW2Y0GpGeno6NGzcqrvfaa6+hadOmeOCBB5zuo6qqCsXFxaI/gghtqM6NjUC33NQHt1SoocdyowVLtfizq5YbqegCJA82BjidXkF6LEFsTfSruMnLy4PFYkFSUpJoeVJSErKysmTXWb9+Pb788kt8/vnnmvYxdepUxMXF2f5SU1Pd7jdBBDR+i7kJQBEVcMJOrc5NgAkxTxFwv4G76Ii5saEiKqQxNlKxoxX2Wso5AGS8Lt6WQcNw7yBugvea9LtbSg8lJSX4z3/+g88//xyJiYma1nnhhRdQVFRk+ztz5oyXe0kQ/oYCim0E2sDKScRNvbDcBNhv4C4uWW5UzoGDuPFAzA0ArHsPOLnW/lmTW8qi/jmI8GsqeGJiIkwmE7Kzs0XLs7OzkZyc7ND++PHjOHXqFIYPH25bZq172gkLC8Phw4dxySWXiNYxm80wm81e6D1BBCiULcUQaAOrNOaG6twEHy5UAFetYiwRM65eB3L/5yUXmA8uxNyQW8o1IiIi0KtXL2RkZNiWWa1WZGRkoH///g7tO3bsiL1792LXrl22v5tvvhnXXHMNdu3aRS4nggBAlhsGZ+mxvka1zk2IiptAOO+eRK42jNN1VESC1A3lKcuNdFtari9rLUQiiCw3rjNx4kSMGTMGvXv3Rt++fTFjxgyUlZVh3LhxAIDRo0ejefPmmDp1KiIjI3HZZZeJ1o+PjwcAh+UEUW9xsNz46AYViE/orsy341VU3FJB/JQc9BxdAZzfCVz5rHP3jfQyqi5zvn21BwxpdpQnUsEFaqvE23VWHNBaC9nA5CDE7+Jm5MiRyM3NxeTJk5GVlYXu3btj6dKltiDjzMxMGI1BFRpEEH6GLDd2AuhGLecurA8VigNCVDrh+9v51+TLgA43OGksOR61GjYCar+tg+XGVbeUzHmurRRvN8zJbyHtp7//Z9zA7+IGACZMmIAJEybIfrd69WrVdefNm+f5DhFEMEMxN3ZEx+7nQdZh8Kknc0v5+7zrgS1yp4T0d6wu1bCOyv+g1FLjcraUzHkWuaVqnFsHrbWSOLDgFdxkEiGIUMNB3PhqcAnAQcyVmZK9hszvEiIDiSrBYLkRMEVoaCS13GgQN6qWG2lAsSdjbhi3lKXGuYC21kpcpf7+n3EdEjcEEXKQ5caG05L0PsSpWypULTcBDhuXYtQwYaX0d9TillKNuZFabjyYLSWKual1vm1rjeR/JngFN4kbgggGFk8E5gwS36yUILcUQyBNvyDnlqKYG7/DWl6MdZEae34Gji5XWMEVt5SeVHAvBRRrstxYQiYOjMQNQQQD274EsvYAh//S0JjmlrIRSNlSZLkJTFhxYq0FCjOB38YD398h397BcqNhSh/VOjfSuaW85JayanVLkeWGIAhfo0WokOXGTkDVuZGJuakXqeD+Pu9OYMWNpQooy7V/lhXELhyPquVG8v/pibmlBGqZ4GStMTeB9EDgBiRuCCLkIHFjJ4ACimUtN/UgWyrQB0jWLSV1+8r9Jq5cR6rZUr6y3NRqFDfkliIIIhDxl+UmEAcx6VxOfqUeiZuAspg5oZoJCNYkblw4HrVJUb0pbljLjTQTSrYvlpCxJpK4IYiQgyw3NgIpFVw6KEoHklCaFTygRKUT2ArDUnEjKzQ87JaSfufVVHAn26Y6NwRBBCx+q3MTgEhTwa1WbXVJvNMZyUdpZkooWW4Uiiee2wGU5fu8O6qI3FKV4u88ZrnRE1Bc97noLJB3TPs+ZCsUSy03Tq6x6lLg7FZmm8EruAOiQjFBEBrRdGP109xSAel+kLhHvr4JOL0BmHgQiG3m4644yWILKXHDujbqjjtzEzB3CBAWBfwvyz/9kkMaUMz+Th6z3Ki5pRQsNx9cyr8+fxKITnBtH3qL+GXt5f9s2yTLDUEQPkHDjZWypexILTenN/Dv9y/0R2ckHy0hHHMjc80dW8G/1lb4ti/OqJYEFLO/g5wbxxXLjWq2lOQ7S7V4H4WZGvchZ7mRpoLrFCtB7Colyw1BhBwkbmwEUuyHnOWGnYE6lH4nObeUIUCfpaXZUqL5mOQEpw/cUuwyredNNqCYnThTg+VGyzaDhAC92giCkEXLAE2WGzuBNP2CnOgM1Ykz5WqlBKq4cbDcMOJGbroClyw3KteeXEAxO3mm0VRXE8nJ9Su3j7wjzHY1xNw461sQEaBXG0EQrkOp4HaUUpIN0obeR85yU58CigNW3LDZUpWSDDZXC+pJ0FvnhrUeGYx8teRP+okDhPXsQ9iu3muMsqUIgggYpDc5v1ss/EgguaXkYm5CdW4pWcuNHwSlFkTzL1WLhYVcQLEr/09WmQBrue8AmWkSDHy8Ut4R4Nx25X0465e1RnnizI43KWwzeK9JEjcEEVRocUtJPgfDoFleAKx8E8g/7tntBlIpeVnLTai6pWQsZoFquWFdQLWVYmuNpwOKj2UA77QBDi1h9iENKK4V90mrdc+p5UbFLTVwomvbDGAC9GojiADHlwOlbutDENa5+fNJYO07wGdXe3jDAVTET4pDnZsgEKFakbNUsOLmo55A0Tnf9kkJkbiplmRLyf0mbgQUf3cbUFEA/HQv853ULSWxHtVWKbfV0y+1iTNN4QrrBO81SeKGIPRyfKXj05c30StO/BZQ7IaIOv0v/6plhmU9BFJAsYPlxipxS4WS5UbmvBtM9mUFx4GVr/u2T0qw4qG2Uuy6kXVLuWCJ0hNzY5XE3LDiS01sOLtPqMXcmCIUthlgDwQ6IHFDEHr59lbHpy9volTtVXkFlfXrGZzCB3/EfzjEQkncUkEc3+CAXAl/qRiQTnXgL0RCQkOdG+E6Cm+gfR96sqUsteL9sunc7ril1OaWUlqXLDcEQXiU3T8BH/UAcg9Dt0UkKFPBveQ6CyTLjfQYHeaWClXLjYK4CZQYHLaKr55U8Iho7ftQrXMjlwrOihvJFApKaBI3CuubG7q2zQAmQK4ugiBELPw/oOAE8PtjLtxgKBXcTgBlSzmITmlAcfA+JTugxXITKNlTDm4pJwHFNstNlPz2+j4E3P2DZBU9FYql4oYtxOelVPD4VODuHx2zpjgLsOVzYN9v6tsuywfm3QTs+kG9nQ8hcUMQgUxNhX5xIjeI1leU3D6n1gNf3QjkHPRlZ8QfHerchNDvpMVy449aQ3JIrSTOrGmcE7dUeJSj8NEScxMWZe+PVSGgWM2VpyUVXK1uT8cbgZaXi5flHwf+ehZYME5925tmAafWAYseUW/nQ0jcEEQgw1ldSGd20XLDccDBxfqyWDxlDfGWVYXdLjtQHVrMzzP14z3e2a+zvgAydW5CyS3FZksJAcUSMaPHcnNmK3D4b/f7JYdFYrkRuaVULDdKbqn4VoBRMrORlukXwiPrPqtYbtTm5VL6H2p9Jf+afxw4+Kfy+oBjvysu2t8r1cgBxMdXcRGoLFLfjw8gcUMQgQzHydcMcbaO6LNGcbP7R2D+KGBmH83dc8nllXcM2PypjwJK2RmeZW7Opdk+6IOATLaUyHLjoWq4gYBIVCpdIxrFjdUKfJkO/Hi3d9LHWSFRU+F8binhmg9XEDctL3cUCWrWU2F7rOVGWnvH9t4Fy43QT3aaCSWk/TYyGW5sP6TENLW/fzsNmNbS75Nu0sSZBKEbA7wWAOsA54GYG419PfwX/1pTpt5Ocdsa9zOzF/9aWQxc9Zz2fblCIBXJk7XcMJ9lrQRBipxbSnodaw0oLrlgf1+t49osLwAiGgBhZvV2ogrFVUDhaeazSiq4krhp0gk4u1W8TJflRhL4y/avRs1yo3CfCItUXkcKK2YA8W9UWwmYY+TXkwtIrq3gz7+fIMsNQejFl1kenETceHPiTLUbp+K+3Hg6y9zIbsj17aghsiD4Wzw4iblx5fwHKnIBxdIBXqtbKv8Ys47G/72yPOCd1sCMrs7bCm4pY10hu/O77N+pzQouFQLGcKDTzYDRKGO50RJzUydC1AKK1awnziw3WnBwpzHHr3Z9yolAPz9MkOWGIPRiMPowSJeD/oHfh+LGZxYsF2GPXS1mwBfIZksxA3yg1H3xBLKWG+n/jEZxU8BMyaH1Wj69gX8tzXLeVhiYm/UAzm4BLuyyf6d1+oXwaGDiQcAcy3+WCh9VcVN3XsLYmBvGLaXkotLSL0A5q0sOqbhREllS5ISMny2RZLkhCL341HJj1R9s62xiPiXctdzoDgr2RZp2gFtu2N9GbfAINjRZbjRui51vTPNDhY5gZUFUNu/p+J2sIJaZCJSzAlHxvNUGcDGgWIi5kUwBwV4XNS5YbvS4hgwSUaZV3MgJGbW0dR9A4oYg9OLL+hxSt5SmgGIXZwVnxc2qt7RZOvxdO8YZgR5zI8oqsvj9addjWCXHxb4KaH1IKDgpv101tP6Psq7BhDYy36tZbiTihsWlgOK62CBLrXIRP7VsKaV7Q0JrlXUkSC1OrEBRE1Zy58nP4obcUgShF59WVpVkS2kSKq66pcrt79e8DTRMAXo7qW/hTsyNL4QRuw9Z4eDLWisy2VKQDHq1lcqTGAYTIlFpFb/a0HjuWYuB5utNw7Y5DvjjCftnwaXEoiY2WQElFV0uWW7qYmOkbil3s6Xkjkvg6f1iy46qW0ot5obcUgQR/Pg1oFjDzd1TAcVsIKfyzrRt2+m6vggo9nORPGcVigH1p+NgQkvMjVbrCrutb28FTq6Tb3csA9j6pcz6CtfW+R3Aru/snyNlRIDcNaPJciOxgBSfU67TIxdQ7MlsKWMYYFLIGItrAUQ1ErdlEYksFWFFlhuCCAV86ZaSFvHzpuVGcuN0RUi5iteMOAEec+MwmWSoiBsZUekwh5JGNyF7HZbnAV/fBLwiUyTuu9v412Y9HONhpLEkAFBdLv4cIZPmrDb9gkicSX5bqbjZ+gX/J4dDQLHULeVmtpQpHGiYLE5xV0JN3OjNlqKYG4IIMnztltIbeCttolWASM3OWiwdQe+W8iHOKhQDoZMxpaXOjdbsNb3XWPE5iB5AtIoouZRp2To3Qn9UHnKkIkENaZ0bS7WL2VJKlptw4LbPtfWlvmdLff3111iyZInt8/PPP4/4+HgMGDAAp09rUIcEEcz4NKAYHrDcaHTHSG9QmgYFT7mlvIS3J6asKAS2fy0uU68VaZ0bwEnAaBAhd96lx6rVkqZFBLO/rcEoiYdRuI6lBQHliv2pBRSrPeToEjcSy43D3FJuZkuZwoCW/YD/nhIv7z/Bsa1RckxaLTey4iYILTdvvfUWoqL4tLWNGzdi1qxZeOedd5CYmIinn37aox0kiIBDanL2Kj6MuXHYjg/dUl7Dy26pxU8Bfz4B/DxaQ1dkfpd6YbmxAoeWAOs/ELfR+mSvRZyzwfAwQJPlpqpE/Fmukq/WVHApesSNcHxCKri1Vrxf0cSZLswtJRQnjIy3L7vtC2DImzJtpYHQChN4SglAt5RLMTdnzpxB27ZtAQCLFi3C7bffjoceeghXXHEFrr76ak/2jyACED+mgnsz5sZhM1rcUp4SN94KKPZyKvj+hfzrybVaOiP+aLU4DpChUqVYOtv5T/c6tnEl5kYJNn6Gs4ofQJQsdtVScSNnuVGZFRwGwBTBD+KxzcVt9DwAyVYo9mC2lJB9x15r0Y3k27qaLSUbUByEbqmYmBjk5+cDAJYtW4brrrsOABAZGYmKihD55yQIJfxZxE9THIyHxI3emBt3iw16A2/H3DRo4lpfgLpsKcmykLTcKFxHmi03Gq5f1nJTWyn5n1Gy3EgmkpSr5OssoPiBZUC764FRC8RN3HFLWdXcUm5YbgDgphlAr7FAm2sV2qoFFKsV8Qs8t5RLlpvrrrsODz74IHr06IEjR47gxhtvBADs378faWlpnuwfQQQePq9z4wPLjdyNUfe+AtBF5W3LTYOmQFmu1s6IP1otgEGmzo23Wf02ENsM6Pkf7+2DPe9Kx6Q55sYFccMKFUXLjUTcyFlu5AZt1nLTrAcw6hfHNq6IGyGgmLNKXFGswJBkeIn6pRJzI+CsbpVaET+9lhs/C3WX7tKzZs1C//79kZubi19//RWNGzcGAGzfvh333HOPRztIEAGHTwOKJZYbb8XcyN009VqJAjJzyonlxt3fMoax3Dg7BmcVigFg/ih+0kdvkb0fWP0W8IdMMKkn0XIteDJbirVo1FSIr12tlhu5mBvNqeAS5FLPlbC5pRhBJhVrtuWu1LnRURRSdfoFvTE3/nVLuWS5iY+Px8yZMx2Wv/rqq253iCACHl8X8YNeAeHC3FJyJmdNMTeecksFacwN65aqLOLnF9LSF0DecgPwU1/cNN0j3XOADaLlOO8JdS3XqVbLjZbrl818qqmQxPwoZUtJYm5MEY5tZFPBZYr4SdETcyP0lbUcsSJGVMSvnK/0LM1qApzH3GjB1To3AVjEz6W79NKlS7F+/Xrb51mzZqF79+649957cfGiCymRBBFM+NUtpaXOjdRCoCWVVmYA0JsK7o7lxltoib1wB3ZALLngrDOSj1Z5AVmW43a3FGGfzL3pNtByzWmOuXGyreILwHzGxVZbKbHcKIgjabaUweBovZG9ZrRYbnSIRunEmYA4QFrq1lN0XSrF3OiwYahWKA6umBuX7tLPPfcciouLAQB79+7FM888gxtvvBEnT57ExIkTPdpBggg8QjBbSjYrRIsLTG/fRCvrbO8KXg4oZs+bM3EjN1u74Bphq+OqzQXkLqxFwZvxPVqsLed3ADu/d97O2XX19/NAFVOxuKZCm8VO6pYCtIkbYdvuWr1WTeWnkhD2YTLbxWdVsb2dVCQoxd14xXITvEX8XHJLnTx5Ep07dwYA/Prrr7jpppvw1ltvYceOHbbgYoIIWfwac+NC1WBXxY3DRIdy+3LDciNa1xdzS3nBcsNuU1oUzrEzko8W+yAWHmUPcDU39Fj3HGCvXa9abjReC78/CvQY5d62Ck6IP2uNuZEGFAOO4sZVt5QW1kwD1gBoein/2RTGz/VUeBrIO2pvJxUVesWNnpgbh4Bi5viDbFZwlyw3ERERKC/nT/CKFStw/fXXAwASEhJsFh2CCFkCPVvKlYBiJbfUwcXAoseUb2yibQditpSXi/ixg6izm7mc5aay2HE73nTvsfvxpuXGk8fgbFvSaRNqNcbcCJabhEuAB5bz76UZU7LnSINbSg9C/wwmIKEN/551TUqPXzonllI7AU9ZbuTEoK1d4FluXLpLDxw4EBMnTsTrr7+OLVu2YNiwYQCAI0eOoEWLFrq3N2vWLKSlpSEyMhL9+vXDli1bFNv+9ttv6N27N+Lj49GgQQN0794d3377rSuHQRCuEeizgrvklpKb/djKZ+/s+g7YPMf5vgIt5qaqBFjxinf3wQ6cTm/mMpabyiL7ewE5d4mn0OpmcBePihsn1kppfZqaSvHv4qyI3y2zgdS+/Hup5eaizHRCtp/RQ+JGOD5jGND4EuftaxQshIp1bvTE3KikgquVPAgVy83MmTMRFhaGBQsWYPbs2WjenK/O+Pfff2Po0KG6tjV//nxMnDgRU6ZMwY4dO9CtWzcMGTIEOTnyQXUJCQl46aWXsHHjRuzZswfjxo3DuHHj8M8//7hyKIQ/qCgEjq/yzlw/vkA0b423B3QXsqXkUo6dIRtbwKxXkqWwLzeypbxt6Vn5JlB81kkjNwcodyw3tVX22iHsd2pPyO7C/s5SceNJ16AeceM0hV6n5aamXJtbSki5b5DIbEsiborOyGQJeclyYwzjrUjOUMpa8na2lNI9AAid6RdatmyJxYsXOyz/4IMPZFqrM336dIwfPx7jxvHFhebMmYMlS5Zg7ty5mDRpkkN76fQOTz75JL7++musX78eQ4YM0b1/wg/MHQLkHgKGvg1c/rC/e6Mf1nLDWeDiM4I2PDJxpqtuKXYyQoUbucfiZrwgdLL2en6bUjgd4kZ6jBWF9vetrgCO/M2/96q4Uahb8s9L/PxPD61WT2fXih5xU1MBRMjMyK11W1JrQ22l84DiqhL7eY5JUtk4x8f0JF3KLPJQzI2tf4Llxmh3S6mh6JbSUKHYGVJxw17flYW8VUwqAIHQmX4BACwWC3799Ve88cYbeOONN7Bw4UJYLPqexKurq7F9+3akp6fbO2Q0Ij09HRs3bnS6PsdxyMjIwOHDh3HllVfKtqmqqkJxcbHoj/AzuYf4170ylT2DATad1htBqiyc1X8xN7oDhAMsoNgXgd963FLSYxRmEg9vAAz/EIitc+k7DUx2A6WYm40zgYsngV/GAp9dA1zYY//Old9Gz7Xg7HidbUsqBrUEFAtWiIiGgJnJVCvLt79v3pt/ZYN7+Q7xLx6z3DBuqUatnLfXHVCso+aOs7ZKZQpCJRX82LFj6NSpE0aPHo3ffvsNv/32G+677z5ceumlOH78uObt5OXlwWKxIClJrJyTkpKQlaVsAisqKkJMTAwiIiIwbNgwfPzxx7b5raRMnToVcXFxtr/U1FTN/SMIWURuKS+LG3DuiwBNdW5kHkx0zy0ViKngXkYkFpxlH0mO11LXPjIWaJgEjPiY/+xNceMs++XEKj5F++e6ujF/PQe83xEo1TrFRB26xE2J+vdy1++C+4FNdXFg0vNVW+k8oFgQNw2TxcvZwbsxPzk0Lp5U6I8XAoqlE3DKoVfc6BFhzuJzSrLll4dKzM0TTzyBSy65BGfOnMGOHTuwY8cOZGZmonXr1njiiSc83UcHGjZsiF27dmHr1q148803MXHiRKxevVq27QsvvICioiLb35kzZ7zeP0IrQTq4sW4pb5teq0vFgXzemjjTWcyNpn0F2O/pE8sN65ZyZrlRWC7UtRFq3UiLy3kStZgblvICPp5sy2dAaRZwyDEMQRW1a+6Kp8SfnYk5uWt+36/A+uny6ztYbmTWVxI37IAcGafQPy/G3ERqqHGkV9zowZm4KVUwOoTK9Atr1qzBpk2bkJCQYFvWuHFjTJs2DVdccYXm7SQmJsJkMiE7W6wGs7OzkZycrLAW77pq25ZX1d27d8fBgwcxdepUh3gcADCbzTCbZSZEI/yPL2aF9gqs5cYHQdE/3m1/76+YG8VduWG58bYw8kVWG3veTm8APl0MDJ0KtBog01jhGCMl4sarbimNcwWZIsT1Y6Ia6duP2rUgHcBddUsJafTS7DIt0y8IBRel4oZFSAt3CLwWivh56Ppis6W0oDcVXA/O5sQqVbLcyNwrgtFyYzabUVLi+HRRWlqKiAiZ+TkUiIiIQK9evZCRkWFbZrVakZGRgf79+2vejtVqRVWVf2cgJeoR3p6vSOu+Fdu4MLeUq5Ybt1LBvS1uNTxZV5cAfzwOnFgDZLwGbPhI3y7Yc3RiFXBhF/Dd7QptlcRNnYUgokFdn7wZUKyxzo0pgj8WAb1P4WrXgjTd2tnxKm2rtoKP9RDWv/XTuuXSVHAdbqk21/CvPcfY+1lbxf92xefr+uPFgGIt6M2W0oOzmJtShZibAHRLuWS5uemmm/DQQw/hyy+/RN++fH2AzZs34+GHH8bNN9+sa1sTJ07EmDFj0Lt3b/Tt2xczZsxAWVmZLXtq9OjRaN68OaZOnQqAj6Hp3bs3LrnkElRVVeGvv/7Ct99+i9mzZ7tyKIRfCVLLTaCLG0/VuREtU8qWcifmht2OHy03O77h/wQGPK7d5SD3+yu5DZSud8EtJVQmrq3kB22TS7dndeTq3Mid+7AIIIsJKlY8JgVUxY3Eku5OQHF1iX19YRLTqlJxiQa536g8X7yOwB1zgaPLgU7DgU2f8MvK8njhu346cNMH8GpAsRaU6tzIXV9d7tTXF2fHJJw3KaHilvroo48wZswY9O/fH+HhfJpZTU0NRowYgRkzZuja1siRI5Gbm4vJkycjKysL3bt3x9KlS21BxpmZmTAyirasrAyPPvoozp49i6ioKHTs2BHfffcdRo4c6cqhEP4kWN1SgS5ufJkt5U6ws7d/f1cHH6vFubDIPw6c3ymfJSKtuyKgaLkR3FIN7MuqSz2Tki1FLuZG7gnbFCFOVddb8E+P5cZZ0UK1bVVctAdmx7es216R+JjkhLtwPGGSAoDRCUC3keJ+7v/N/v3ip4GBwvyJTq6vuJZAUaZ6G8Bu9RDEzdBpwNJJ/GdZ8azRchPfCrj9C+f714OSuJGdWyoILTfx8fH4/fffcezYMRw8eBAA0KlTJ1scjF4mTJiACRMmyH4nDRQWUs8Jwm+IxI2PCxEGWsxNIFcodtVtYNVgNfm4p/J3ivEpTiw3YWa+Jom1xovihnmaXvEqcPBP4C6ZCu8ms/j312u5Ubt2HNxSziw3KttiC8uxmUbsICy3vjDwqhW4k1qY7BvkX5yJ50f/BdZNtwc+K8FmSwFAv4eBNlcDJ1bzIkeK1jo3now5i27Mn9OyPOD0RiCps92dCgR3ET9ns32vWrXK9n76dCc/JkEEM1rmrfHFvhXbeGriTA1mZbcqFIs25Ma6CrhsuXHzN42Ml1/uzHID8FafqiL1SQrdQZRFVAOc2cxnREkJixD//krWAiV0uaWcWW5Urg1B3Jgi+EKA5lh+MtLyPHsbud9TCKZWFDBwFGEO/XFyfZkbAsmXqbdhEeJdDAagaSc+QF0OJSua9Jx7MlswJokXN6fWAV8NBdoPBe6db/8+mGNudu7cqamdwZczJgcQ+88XYeL83UiOi8TX9/f1d3eCBHJL6d+3hnPm4JbSUudGzvytYYB1K+PJ224pF59c3RY3cQpfCE/8RvE1ZGbah0fViRudlhKtyD1hF8q4TkwRkpo4emNuVH5bYxgw4hN+RnDAvZib4nP8q+DSi2rEi5syJ+LGZrlRSYBREjd6Ym70XIPSvkgzl+Jb8r+VRSF5xpuWU2ls0pGlzH45+XMsmzHoOzSLG9YyQzhSVWvF4ewSVNQE6XxJ/oBibtzbt3IjyUct2VIybbQMaJ4KKPYKbsTcuIXCdS1c78YwST0V1nJTFwOi11KiFbnrVS691xjmmuWmqpSv+Kt2LRhMQI9RQP4x3mXjarYUABypm1MwsT3/Gp0AFJ6W1IZy1XKj8J2e+5az1GoWqbhhM5c6j+BdVYuflk/ht1qBonPSnWvftzPUpqiQO7+DngUGv+y5/buAD6c3Dm2MdSreGqwDNqEda4DH3LgUUCzzRK8piNSNST0DdfoFdwWrM7eBdK4f1tIjBCN7y3Ij9zvLpfdaasTB0lrEzY5vgKnNge3znIibumHHlvruhuXm1Dr+teNN/KsQ7+Ss8KVg/TC54pbSUedGz9QHapabAU/aY7Pkrq8/JjhWevaoW6qp+HMcU+nfz1lRSpC48RDGuuuItI0egvRkBZ3lxsWYG02WG50CRdQXJxYOt/GTuFFy5wnbDZMMYmZfWm5kBnq5+YIs1RLLjYZr4Y/H+dc/n1S/5oTsV5uQc3KsWh4g2g7mX6PqCstWFjHry1lu6ixn0t+CRc2qA2gUDxqvQWOYY50bNjU8zCyuuyNl1/fa9uMq0gxAtm+ysXn+v7eTuPEQBpDlRjfBeqpE4sbHTy2+LOKnqT96LTdKbbyQdRVwlpu630H6hC7rlvJhzI1ceq+lWhJzo5R+zAFFZ2WshSrXnGCR0CrktFwPgqiRy1RTjblxwXJjszRpuL7YWJXOI5TbycX+sFafsEjlisme5qr/2s+nXF8Afr6tf17irXty5RACABI3HsJAlpv6Q9BZblyMudGyX90zlmvIrvK1q89h/+6KG4WAT+G4pAOqyHJT94TsrQFM67HVVonbKomtde8DH1wKrP9AvFzVciOIG40uOC3XlSCUohMcv1MTN65YbioK+Fct4rlFb74w4BO7gL7/p9xOTtywbq8wMyNutFbjd1HcX/Mi8PQ+yaZMwN0/Ah1utC/bOBPYPMfu4tMTX+QDSNx4CIq5cYUgPVf+TAV3ZeJMT1huFL93x3Kj5JbylOXGxdubu/tXEiY2cSONuWHFTZ21wJcBxXJYqrRZbla+zr9mvCpe7iygGBBbbkpz+SrABXIzcGu4TwjxO9KsHkCcOSVQ60bMTXmduNEiHgwG4LLbgYTWQNoVwP+ttc82zuJU3EQybimNwtedmJuIBuIsPqMR6HgjP28ay5Gl2oKz/QCJGw8huEutQTpeEzoItiJ+WgY0p+JGwf2mNxVc6XzpdW9pwV+p4Iripm670oFMznLjtYBilWNL7sLPqQTwMSneqnMjZ7lZ9DBvBfpaMn2PlodFY5hdMF56m+P3mz6xZ1UJ2AKKXbHcFPKvroiHlG5AdKLjcrl+sL+VS5YbN2Hn3RIEqbSi84U98uImAB7ySdx4CMFywwXAjxo0BOu5csctVXwe+H0Cf1Nwad866twIWTkeETcK37vjlnKnjRb8GXMjDbS+eEo+oNgYJo5n8HZAsVpmS3g0cHld7RlLlf5sKRbh+BM7OH4nZ7k5Vjd5snS6Ai0u1XBm2ooGjYEe9zm2WfY/8WdNAcVKMTdC6rqL15c5RmZfcuKGOXZXLDfupoLHptjfC9eoVPBVFdmDt0Xny//3dhI3HkLIliK3lB6C9Fw5m5RPjYX/B+z8Fvh0kGv71jPwC0+DmsSNk0FE8Xs3sqWU2muajVwLfhI3nFW8jY0zgQ+72QdY9imdnU8K8K/lxhRhH2QtNZ6x3KQNBB5aDdz7s/07QXSKhJzCtbDja+f7CpdYE+REibSNplRwhe+E38ZV8RwhI25kLTfM+TeF+95yEyNnuZE5t+e28a9qVjA/QOLGQxhsMTd+7gjhfdxxS2Xv99y+FdvUXYTC/EjejLnRO/1CUGRLeUBcsU/XgqgRqumyA6rUReGPIn4CYWZ732qlMTcKYktpJmvhHBqMQLMe4lgYrQHFxzKAJc8o91cgQpKmLCtumDaWWvs15sr0C4KlQlqvSCtylhs5YcCef4NBbLnR8r/mbp0bYZZ6wP47y52vzI113zHnKwAe8knceAjhMiLLjQ6C9Vy5VZXXzRuOs/3VVNoHME+6pZTcGbpTwTXE6AR7nRtAfeoKNqA4uYv4Ozlxk38c+HemZwSP9NhEooOxDlhrtAUUKz2tSwvdscesNRX8wi755VKkNVjkBmDWcsNOX6B1+oVBzwCdhvPvBeEqFVVaYWOs1Poh/a2E45JaBhVx817DWhWFoFI5wXRaEDeBFVDs0qzghCNGWy64f/sRXATpyRJlS+l8ynd3pl41l011GfB2a8bkHuAxN4rVioM8oBhQj4tgB7KUruLv5ArbfX4Nby0ozwPSX3GvX1KRGp1or+ZrqRKLkBqmcrAQgyOdLd0UIW95sVVjrhMyrIVHarmplRE3HKfd/eIgbpxZbpipL9QGZLaonsHouB+pq0srmt1SUnHDHFdtpfqM5p6AFTdqad7CJKWic+n/eztZbjwEpYLXI9wJKNZTjt3ZvqWc2yF+KtVlufFEzI1ecVP3Xpru67EMNBf/F50KPQ3HKRqYJU+77EDfrKf4OzlrhuAGkWb8uIL02NjrsaZS7DKTTosgNweUkjgQ9iMITKOK5UbKqfXA26346Ry04BBz48RyIwQTw6DsVpMiK24ayLd1BuvuEZALKJaKc/a3qa1W/s5TsMer5b6lONGofyDLjYcw2AKK/dsPwgeIBmi9A7EX3VLSG5BPYm50poLLxStJj8lTlhtXRZLLNX8YWMuNwSA+T0YT0Gc8bzFpfaV4PSHVVs4a4mwOJi1I+86eo9pKsQVBan2qKgGi4sXLlNw6tiJ5dYMue20anYibH0byQoqdQkENqZiRHWSZ/zsLk7qsNS7FYPKc5UZrzE33e3mB1+56/rPRWDdbe7XjbxMe6ThbuLsxN6zbTUuBPvZ3kFY49gMkbjyE0UiWG90E67lyJ6DYbbeUyjmTbtsnqeDuWG7qzp30HHpK3LiadeW0WrOG7aq5VIwmYNh78t8JA+aJVcD8/wDDptu/0xtzU1sN7P2Fn006rjm/zEHcMJ9rq/gB1BguX9dIznKjJG6EwVcQGqKYm7pB12jiv5cO1M5mCZci/b3kLDfswC9YPfRk9xiMjjE20kw3rUTIWG7k+mJuCDyyQbwsLFJe3MjeF9wUN6xlSovlxmQGbpkDHF4C9H3IvX17AHJLeQiaONMVgvBkSV0SegdQt8WNhsqvAr6IufGIW8pL4sYflhthkBLFkai4paSw1oGDfwDLJ9s/600PX/8B8PujwGdX25dJY27Ycy/0WSoOhGt20yfAz2PEwk3JLVUrtdzIuKUA160fLNLrR85yw/ZZSwE/KY0vceyr1JKjFTm3lNa+COfz1wfEg43wu7a9zrU+yeGK5ab7PcDI71wPtvYgJG48BMXc1BOkA6+W+AsWLaZitWtIVdwozCqsNhhXFgPV5T5MBZcJxnZwS3ko5sZVkeQ0/kjlXMU241+lbikWtYFCOoBeZKYj0OuWOryEf2Vn/Zb2nT1HQoaXNFBVmIxyxzfAgUX2gnuAdssNK+jY8+GqQGDRYrlhfw890wWMWQwMngx0vsUxxsbbbik5hP/x8zuBrL325YKlbfiHTFt3LTcaYm5YKxTF3IQmlAruAsF4rtwdiDWJG5VBWVXcSLZts9wo9LG2CninDd+u083ybQQ0uaX0FvGzyvcv4C03kv4Zw4CxS/h+L5/MVyNWSwVXs9xIXR0iF01dBpHWlFs54e3glmLaCAJAGpwalSCeOVwUPyN3LAZm7qa6QZvNsmJ/X49YbiTH6dRyo8Mt1XoQ/wc49tVlt5TGCsVylLJCtU7QcBwztYcHM6giNLilohoB1SX8e63H4CPIcuMhhCJ+QThc+5EgPFtSMeONmBs1kXB+l/LAKV3PWcxNyQX+BllTDlRcVO+Tx1LBZdxYDoLRQ9eFryw3BhPQ8nKg1QD7gCCyskjdUirXQANJUT+ptaYkS71vav2UW8ZZgKad+ffCjM/SQUqw3Aiwri05cW8wqltuPC1u9FpupMHOWvFYQLEbbin2nllVJyrY31QkNj1ouVGyNkYz10aAWW5I3HgINuaG5pcKYdy23GjwXasNypYq4OfRCutJ+mJy4pZi+1JyXr1PWmJuXMmWKj7PB70qtXEHud9GixtEr4uOHVCEp3LW4qLHLSWtWFwlCa7VE3cjd/zSmBurBfjPImDY+/YZn6WDlFTcsMHGcufKYHB0/bDHzN4fXXFL3fUtcM98Znsqlpt+j/CvtW4GFAOOcSSeTAV3ZeoCIZuM/U1Zy01MU/3bZNFkuWGyoqiIX2hiZG5gHOe+u7NeEIwi0CHmRmedG00XhpPzclSh3on0CdaZ5YYdpJxZBCyestwwbUqzgOmdZPrloZgbue0kXQqc3epkPSe/qVQ0sDd+OXGjK6BYIiykmUOaJ02EguVG6gK0AA2TgD4P2pdJ03ijJZ/Za0HuHBuMTLp13fGERwIdb+IH5IQ29rZNOwFnNqsfh5TmvezZX4DjIM4OsoIgEVludMTcsDi4pVyMF5JzZ7kkbor5V/b/2BgO3PsLsGmWOP7GFbQU8WOvDW/U2nEDstx4CFbcUNyNVoLwPLkdUKzFLeWqO0UymAlPcZxVvp/sICVUqdW6bQF3UsHdaaMFue1IpzuQQ5oGfX4ncOB3PvAaUC+EJwSLsu4kqaDVU8hRaqnRM2minPCQHptcG6lrLDJeeRuy03IYGLcUM2jf/T0wdrH4fAyeAjTpBMS1lI9FkUM4f/f8BLS+CrjhbfH3rOXGVgWZtdxomDRTDpGlxuC6GyaiAdBTYn11y3LDXI+mcKD99cDo34H4VNf6J8Ba1eRKA4RHA9GN7Z8DzHJD4sZDsGMWFfILYeSefPXAXiiKs2K7eAE5WBTClL8D5G9YSngjFdydNlqQG7i1iJvFTwMn1vDvq0qBuTfwrsBFdS4OuYBiAeFpV+pOUmqv93s9lhu58yj8jl3v5gf3O79ybMMOWAaTzIzaTHVcWbeUkXFLOREA0QnAw+uAJ3aK57lSQ7AidLgBGPMHENdC/D27T+H3kI250Sko2PMQHu2eef7mj4FLb7V/1ipuRnxif18ltdwY3K+AzsJabtjzd9c3QEwSMOoX/lWAYm5CE7LcuEAwnidpn90JKNYSpKsHaV/YG6ZcP5Umw9SybQGPzQqus40W5ARdYgdt6353O/9amGmv/5J3hH+VCygWEFJjRTE3ktusM+udqrhx03Ij/OZ9HgRePMcX+JPCWm6MYY6CyuIk5gacY0CxGqZwPj5Ma/yNswFc5JYSxA1ruVHICnMG2z9PBEKztX+0Cq0eo4CBE/n30pgbT881xZ5n9vx1HgE8cxhIGwg0TLEvJ8tNaMJq+GAcs+sVlUV89deDf+pf1+2aLMyVojhQuWq5kfSNvdlpyZxRQ8nKE2xuqcZtta0rHG/xOfuy8oK679QCioVsKRdjbpx9r8tyI2etE1KGw5QHQzao2RTuGPfDHr/cNWSpcSzipwWtgsGZOGRFS7hMzI1gVZOrN6MGG2PjiSJ17O+sxy0VWTeruDTmxuhhccMivVcJD/MNk+3LSNyEJqKA4mCMJfELfjpPq9/mq7/Ov0//um6ngrNz3FTLt9EyuMvGU0gHXSfiRpflRktAsQWoKHSyHQ3ny5lgPPw3sOEj59uR25feDJKiM/b3FQV16ZBS9x9zG5WLuZHizPKg9r2rlhtBhNosKipCQmq5kWYFWZzE3HAWptqxDleFVnGjx3LDFrIUYlMEsaY1xkeAddcVZupbVw72OHSJmzj+VRpzI52x3RewlhsKKA5N2DGLYm404i8Tl7O0ZzXcttywJdPdEDdCjQsWB7dUmPJ3gIdibiR8Okj9e02uKydtfrwbWP4ycHqjk+0wx2wyAxMP6pgose7WWMRYbizVfICvFssNG3PjEFDsZBAaPFn5O1ctN8K1pqU6LzuIm8KBQc8Aqf3sy5ylggP249czaMc2d94GcF5OgRVUrIAQsqSE/x29lpsws2cqKgu4arkx11lupDE33rDcXPVffub6rnfJf89abgIsRZjEjYegmJt6grvZUmxmg9JTuJbrR07cSIWWwWgfpN223GiIuQGcP9F60i3FuozkYH+bRmn2qRE0YZDfR3mBTMo9K25kYm4cNu1kcO79APDQavnvXLXcCKLIZrlRETciy004ENMEeGAZ0Gscv8zixC0F2K9FPZYbNkVcDWeWG9EknTJxIzbLjUy9GWcMr7MYXnaH/nWluOyWiudfKwv5V2/F3ADANS8CD61SrsbM1kByZrX1MVTnxkMYGdHqqZABwku4Iz7dzZYSpdEqWW60iJtimW1L+2Lgb6CWag/E3GhIBdeCu+JGz3QPonmsVI41qpFjhWbhYaXorHh5xUXH8ywKKJaJuXGY88vJM6XBAKR0l5+dW4/lhr2+aqt4USKcBzXRwWYtiVwn4Y7bdXYN6YnDaHyJtnbOxCFrQeAs9vMozKpui7lxQdx0vRNo0h6Ib6V/XSmsuNGTueUQc1P3G3gz5kYJkbUmsB7qyXLjIchy4wr+Ok9u7NfdIn4WDeJGS/9kLTeSvhkM6pNn6rHcKLaV6WvFReWYE1fjieT64WxbopgTlbbSCryAXZCwcyoBfNyNmltKNuZGp1sK4H87ObeJVnFjtUomi6y0u2UAJ5YbJi6JFX22opAa3FK2/bhoubnjK+DmmfLt9KQ7Wy32Pgjnw1W3lEBKNyAq3rV1Wdhz1/RS7es5xNwIlhs/2SqGTAVaDQS63+uf/StA4sZDiGNuSNxoIhhPk4O40Wu5kbilhKdJtX3IUSlnuZEONE7EjSdibuT6+nYa/yfnQnHXcsMO2M62JQ12FpA+4cqJG0GQCAJUEDsVF2XEDXMbFYJU1ercaJmCAxAH8toGaI1uKbnif+y6asGf7CDJWqCE5YJbip2wUXFbOiwSrOWmSUeg53/k22mJ7eg+CmjcDmg/xC7kji4Dvr0NyN7Hf9YbUOxpzm23v09sp309NuaG43yTLaVG/0eBcUtcs4R5ERI3HsIgstz4sSOEc9wRn9J19bqlWMvDkaXAm8mOmT+ecksZmKJesjVP9Dx1c/LxRUp9tVQDhWccl2sRN0smKm+XFTd6pklgtye1WghPwizC/7OQ0iwUKysvcPzN2SBTdvoFYZ/SsViL5QYQxzkIcRZyYlgOB3FTaT93xjDXnvKllhtnwt4U4dwFxxIZx0/R0KwnkNhef/9YbvkEmLCVz8AS3DhLJwHHM+xxVP4ejJvU1VyKaqQvGFe4Xjkrf515M+YmiCFx40Fsk2cGpUnCHwSjW0qaCq4zwIodkNfUlY1f/rJkH1qypWTEjYPQMtitBK5Ybh5Yzpubbe1ltqHWV7mBTcux5R3hpzyQgxU3zgZ6dvBl30utCXKuE8FSY5GImwqZgGKRuBEECac8yaVWt4pI3NQNaFotN1K3IGu50eIqkrNmCedNGEydXT+uVKy9+3s+gNUTLhZBMChVPva35Sb9FeCKp4CH1uhbLzzKLpAri5mYGwqhZSFx40GEuBvySmkkGE+Uu6ngmmJ06s6LMYz3Z8shN7DLWm7qbni1Mu2dxdykdAVGL1KOP2H7Kofck73WaHurBfjnJeDbW8UWphpW3DiZIVtpUk+p5UZ2UJC4pYQMoqoSx+OKkIgbwcIhFP1zCCh2QdwIMR5aY26kIkgkbjQE+cYkOy6zzTIviBsPBhN7EyVx42rMjadomAxc9yrQSGdwssEgjrshy40sJG48iCBuKOZGhUA4N265pdyMudESxCvsw2BUvmHVlGtwkTHi5vNrgeIL4q+1xNyYwoH4lvz7guPKfZXD1ZgbgJ9FevvXwPGVQP4xZpuMSHMmbkQBxSqWG7lzLDz1C7+X8JRvqXEc1NnYGIPBPpiW5QgLJdt2xXITz79qsdxYreLgYUDsltJiUUlo7bhMEG3COXF2LdfoyOzyJoriJrBiRHTBxt34O+YmQCFx40GE+yHF3KggGpD9dKLYPugOCPZgKrgSrLhRevpd+YZ9/iOlvrExNwCw9h3x985ibgSEKQuy98v0VeU3lA6wgHZxw1ntVpOaMt5SVXFRPLg7tdww56Pv/9nfO1huwoFh08XLpG4pQdxs+gT4VxIjJa0BIlRBLlWYaV2r5YZ1d2m13JzfCbzdCvj3Y/Fy1nKjJcj3hneAhEuAocyM2yaJuHH2v1Mtk9HnD5SqUrtS5yZQsKWDF/m3QnEAQ+LGg9gsN6RuVNBRp8QXKKZjK+CO5UZLdonQDgBgcMxqEZ7gAT44UrUvBrHL5fwuSXuN2VIJdVksfz8P7PtV0lcvWW6stfb+VZcBXw/nM7DYIoHVGt1SN30ADJpoXy49p6YwoM8DwKBnmYUG/newiRtGwJzdKl5fOs+QMJgKlhu9FYpt23Uh5mbhw/zT/N5fxMv1Wm7iU4EndgCXP2xfZpS6pXRk2/mTQHVLuYPNLUWWGyVI3HgQW0BxAIzZAYu3KxyeXAfMGQTs/F6tE/a3bosbDxTCU9qH1HLT72E+xVVxPZWYGwC4sNu1/sSn2t9v+0q6U+X1tIqbO78GHpMIhtpqe9vqMrug2PmdvY3WgOKWA+SL0QnI1W8BV7d+3fGpDYTSuZeEOjGlOeJ+CLjilhICfNUsN8UXgNxD8t/pjbmRwxZQXHfdOLt+Ot/i2n48DVtxWcAYHjgxQa4guKUqCynmRgESNx7EQDE3zvGmuDm0BPj6JiBrD7D1c219qNUrbqRxLjqOR3PRPCGF2CC+ARuMjqZndptybimRpcwirrir1S2VxmRMCVlDtm3WbT9tEHCtJOtLTjjKna9GaXzVVxbW5cRm/rACrUZlckrALvakbiDpoGZzt0hqELH9Vyo/D8hYboSYmzq3lMN0DW7E3JxcA+xdIN9+Zm/lbem13MhhkohAZ1Wfb1P5H/QlDWTcUrK1jYII4XoQxdyQW4qFxI0HEazPJG1UEA1uHj5TK9/Q1o4dtHRbbtyYFVyz5UYQN9KYG4NjvISoqJ2MW0pqPWEnm9TqVmjWg7caAY6VkYW+Rje2x+bY+qbRciMXA8IelzCHDsCnYgs4c0tZGQuY2v7kLDeWKnHMkFp8hnQyRanlxmEWcTdibgDg1wcc23Kc+nxWtZXuW24cAopVrucWffRNKeBN5NxSDZMclwUTsjE3ZLlhIXHjQShbSgNycwNZrcCfT8m4PHQiV7VXjlp3xI2TVHC1316vuJHG3BhkxA2bkSJnuZEKjI0ztWe7sLQawL861NdhhJhwwxXQKm7kBlvW5VR+0fF7aRspq6fZA1qlYiL9FfFnW+VdyflgLUZS6wyLUkCxzXKjMl2DGmw7aaFB6VxYasIG4M+VRUedGzmk50nteu403LV9eAO5Io1yqe7BBBtzI/yualWn6yEBIW5mzZqFtLQ0REZGol+/ftiyZYti288//xyDBg1Co0aN0KhRI6Snp6u29yX2mBsSN4rIDW4n1wDbvwIWP+XetqVTGyjBPpF7MqD45Frg3bbA/kUK+1URE3JzJhkM4oHIYHQcGNnUaLmAYmnG0oVdwKbZde11iBvpfDZyfZUOIidWAUueFVtYZC03Mk+crFtKtr4O1N1Sq5n6QNIYl+Y9gReZtHhpoKyAYKUyRagPHFJxE92Yfy3Lq9uuRARojblhLU7SfZxaL/5cXgBVasq1zQiuhkOFYplA1rFLgBGfAD0Upk5wh8sfA+6Yq389uZTvYLfcCMdUVWL/n5QTcfUYv4ub+fPnY+LEiZgyZQp27NiBbt26YciQIcjJyZFtv3r1atxzzz1YtWoVNm7ciNTUVFx//fU4d+6cj3vuiN1y4+eOBDJybim1uBE9aBU37HdLnqmrXaKjuBwLa7n59jagPA/4ZQz/pLzvV6CikFlXRUyIKsqyMTeMpUbOcvP7Y8DMvsCZrcCaaeLvDAaxlarfI/yrkGWlNeYGUBE3jJXJLLm57vqej33KeI1pr9EtxVqkFMWNxqkI5NxArCXGKBNzA9jnhzJFqLtYpAHFQrBndSl/XUmPWatbim1nlljFzu0Qf65wIm6qy/RVKJbDoUJx3fmKSQKSu/ITSrYcAPQYpW86Aa0MfQu47Hbn7aSERzkui0pwvz/+RBC7NeX2e4wnJvMMIfwubqZPn47x48dj3Lhx6Ny5M+bMmYPo6GjMnSuv0L///ns8+uij6N69Ozp27IgvvvgCVqsVGRkZsu19CQUUa0Hm3LCZKNLBUw+seFC13DAD/ql1wPd38rVBjixzvg8Hyw3zmd3/0knAgvt5oWPbr4q4YQdqUbaUxHIjFQIn1wJ5h4Ev02U2ahDHrggTEZ7ZWifoZPojuGxGfCJe7tRyY1R+cjyximkv8/vLihsNlhulmBvpPqQxNw77V7Lc1LngTOHqtWGkLivheq4qka+DpFXcsBaeZj2AIW8BnW7mP0sz35xZbqrLGMuNi7EwtvNUJ2rYWI+HVgPjV+ubS8pXyAktf0+94C6CoK4utcekkeVGhF+vxOrqamzfvh3p6fYbs9FoRHp6OjZu3Kiypp3y8nLU1NQgIUFeiVdVVaG4uFj05y1sRfy8nO0c1IhK4jPxGgLObtJqsFYVtZRZqfA5sYofyH640/k+tE6/sH1e3bZXM/1TsZTUyLluJJYauWwpNQwGcf+adOKzRGrK+IJ8cmLr8keBF87xT98sglWmulRi4WCsTNKYG4GLp5jmWi03jNhTskrIza8FOB6XkhuoURr/KgiGlv0l22fdUmqWG4m4YSfPlPvNtbqlWBFkMAD9HwMGPsV/vrBbLOKkMThSasrdt9xIA4rZOY2MpsAUNgJSd65aDFUwIFhuqssZt1S837oTiPj1aszLy4PFYkFSktj/mZSUhKysLE3b+O9//4tmzZqJBBLL1KlTERcXZ/tLTU2VbecJhJgbstyoIFehmB0AlJ7StcBuRy2WRm+cjWhdycCpx42marlhxY2ObClVpMXjjEBsc/59RYH8wGsMk6/pwgoXVlRoqaZcqxL0DLhuuakslLf0SWNxlCwlj24Cnt5vn5251zhgxCz799WMW0qPuBHOn7VW3rqkNaA46TLHZU0v5cVReR5QUhc3VF5gz6BqP1R+WyLLjQdSwdnsrGDI0pH+frHN/NMPTyGIs+oyckspEMBS2znTpk3DTz/9hIULFyIyUv4f9oUXXkBRUZHt78yZM17rj9EbfuZQQ+7JnR1kncUOqCGKudFhudGDdOBUEghyqFpulNxSZvFyPeJG7noU4g9qKuTFlpILxxRuN4WzqdlszI0aQuyPWkDxo5vsy1jRombNy5eZ70oai6N0TOFRQFwLph9hQI/7gLi6ByCb5caJW0oq6liXB3uuBLS6pVr1B27/knf52Pocae9zYd29bD0zdUTDZPHx2uJ/yuzH44lU8D8eB364q265xuPxJ6wA63Yv0GmE//riCYRrrKaM3FIK+FXcJCYmwmQyITs7W7Q8OzsbycnqqXrvvfcepk2bhmXLlqFr166K7cxmM2JjY0V/3oJSwTUgmwrOWm5cFDfSqQ0s1cr+QXcsN9InccHtwwbuKhV8UwsoFs2TxLh62CwdS7XOQl1OxI1cf9QEOpt+6tBXJ7cSYSoCOXEj7LNpJ6DjTfz7HV/bv1dLcy444bhM+hvpHXyFwZ91S6nFqTjMVWWyW3PYgHLb9zp+wy538PE2LA1T+FfBcsOKOWutOMBZeJrP3Ahsq4tjdDVlmJ1baue39uXBUPafFae3zg7+eZjCGcsNuaVk8au4iYiIQK9evUTBwEJwcP/+/RXXe+edd/D6669j6dKl6N1bpSqnj6GJMzUgirmpEwasq8JVy43coKkkYtyy3NQNnEJRN6uVF1ZCTRNA+Wav5paSS5eWWm4sNR6w3NTdFGvK9dW5AeSDim19dbKuIBScVXTWW2VV1nIjETdaY1wEhKJvOQf5VzXLTbvrHas2A/YnaznLjTMh6IyGdQ9+JXWue1ZMG8PF2UFCJV72f8FV94UgbqRWUVfdXL4kGASYHmwxN+SWUsLvbqmJEyfi888/x9dff42DBw/ikUceQVlZGcaNGwcAGD16NF544QVb+7fffhsvv/wy5s6di7S0NGRlZSErKwulpU6KWPmAemG54Tjg4J/yg4qm9a2O7z0RcyM3UAs34W1fAfP/ww/KHCc/W7VWhJRtIQYley8wrSWwebZjG4G/nucDQNVcZXIxN5DM6m2pdi/mBmCe+Mr1zYsFKIgbieXmxvfk19UqbrTGbyTWxcnIWW4cxI3O21zaIP71aF32nFLMTauBwKhf5EWkEHdz+C/H79x140gtN2zV6KtfkBc3LF3vcm2/gkCQCjavDqoecvUHQ1yQHgRxU1tpt8CS5UaE38XNyJEj8d5772Hy5Mno3r07du3ahaVLl9qCjDMzM3Hhgr3g1uzZs1FdXY077rgDKSkptr/33lO4qfqQelHE7+hyYP59wMc9XdyAl9xScgO1YKFZ/BRw8A9g6Yv6rRVShIGTLQxWVQz8+zGzX0nMx5ZP+XOmNo+VUkAxi6VGnzmdFXGC9cItyw1T8l3aV2EQ6jseuO41OCAEITu13GgchJK78K9ylj6puNErJi65hn91FlDcUMV1Llhutn7h+J27cwDFCuKmznIjiJshb/HF6UTTNkiySHuNc31eJaVrz5uxHu5auQR0PRQEAVLXt8GkPv9ZPSQgHI8TJkzAhAkTZL9bvXq16POpU6e83yEXMdSHIn7C7Myuwg5ugjvKE5YbdhsGE+/yslSJ05Z3fQcMneq4rh4Eq4xc1VM1CjPVLUZVjOVRydVj1emWYp/oBdeJs5gbNWRr3TDxQQJybgrNbimNQqTxJeLtskhjbvS6pZr3En+WuqXaXQ80bsenZiuhdn3o7Y8Um+XmPP8qnANhn2yas9Sq4s4AqCQ8vWkxMBiVyy3oIdTETVgk+BtE3f9fVLx3CicGMX633IQS9jo3Iaxu3H2SknVLMTevEiclAE7/C/x4L3DxtHg5uw2bybYKKBUHq4tqrriCELypV9yw68pxZCnjYlGy3Oh0S1UWA/f+wqcU3/sTv8wmbsr1VSgGnMTcMH2V66MQhCxYetpczU8w2f0+cTut7oMEFXEjPc9666+Eme0CAqgLKGZin+JS+Wq5cc2Vt6FWJM5dy4005kYqbkSWG4mVRpq2rgela8+rlhsPDditr+RfQyX2xmAQX2PxrfzXlwAlICw3oYIQcxPC0sZL4oYZZKViRMpXN/CvVcXA2MX25cI2DEZ+AK8q5v3R0uJmnw7S3+fiCwA4vjaGnFtKK8Is0QajowXj5Brgox7AK0XiIn4slhp9A2NVCdD+ev5PwOaWcsNyI6pzI5MKLpdq/OcTQEIb+7FFNwaeOeRoqdF6fI3qbuay4sbJbOFaiEu1x7SYIsSiS8uAK1crSMDdYndCqvrF0/w1IfweNnGjEnPjjuXG3BAIi3J0uwaDW2rwy/yEpoE0oae7RETbJ4dtETiJNYECWW48SL0o4ufuk5QoFVzGLVWSpT6ztkCRpF4RWy1VSHWtrQaKzrreV4C3bkzvCEzvxAsCV91SgH2wdLauYsyNTsuNXAp1hIezpeSsTEp9/PomsaVHzgWl9cla6EvhaX5+MDbt3xPiJr6l/b2zOjdyVKkkOLhruYlvxWfrWaqAvKOM5aYuJooVN1Lh4Y64CY8EhrzhuDwYxE1EA2DQRCCxnWe2Fwiwv6XUlUqQuPEkNstNCGsb98WNE8uNtUZbULF0EGRnKBYsB7WVQPF51/sKiIv2FZ1zTAXXg+BGkE4wyVJdLu/qAVxwS8lU72UDivVmS5nlAoqZWcEF1FKDBUGrNGhpjblhBeLWL8RZSR4RN0wlc2lAsZYBN2uv8nfuxtwYjfaA6qw9jm4pYVZygP8t2Lo37rilAKDPg8BjW4BbP7Mv82rxOIoj0URzstxIIXHjQerHxJmetNzIxNwAQKmGqTekg7ywDWOYfXC1VAHFKrPFK5WqZ2GtG5VF9mBVl9xSdS43NZdFeT5kg3SFvujJlpJz2bABxXrr/UgtN0f+AQ4tqesrcytRq4CrJNwEtMbcSGNa8o/a3ytNqKkHkeUmQvJbaPgfuOZFlS89cH8QxM3qafYgfOGabMhMLWAKFwcYe2JOpSYd+KwsAW+mgnvKchOKsGUQhAB7wgZdOR7E7pbybz88gtXKVzTN3i9ezt5s9AakAvKzakstCM6CigHHQd7mljLZq8nWVkmq6TIkXQbcO995ATJWAJTn2S05SpNEqqHFLVWer2K50ZktJUwMycKKG6FqsFZYcVNdzpffzzng2FdN4kbBeqHmlmp9Jf97Xf+m4zksYWK1BMtNSnfg/9Yqb0+NhDb2967USOlxH/DkbqDzLY7fecLS0eZq/vXiSfsym7hhUtRNZnE2U7iH0oXNzPUfDAHFoUyTTnSeZKCAYg9iCKWYmz3zgcVP8+9fYdwQInFT5UIZc8m54ayOsR9axI2DW4qJuREES22lcvq1MMibzI4TO4oK5zHrl+a4Z7mxuaVUhNG/H/NxJAAcA4o1uqX6PcwLkGv/5/id4JYoz5d3W6khDJKVRY7uvgaJ9vdKmULmWHk3FotaPErrK4FRv8pPhZB3hH+9sAfY9An/vsONQEo35e2pkcxM6VIjKb6oZSAxGHhxGc3UmXliFy+WXbl2pHS8kZ/FPHOjfZlw3mOZTK+wCD6QVrBseWo2bFbQkLjxD3d9A2yaA9z2qb97EpCQ5caD2GNuQkDcnFovv5y92bgyjYHUcsPJWG6kFX4BPjj4s2vsn6VP0yJxwwQUC33sNU7cXhA30oGy4CSw4hWgNNe+DYGyXPeypYQAX3ZdqUjbt8BeS0jWciNjRbj0VuCBFfbPzXoCt84RTwopIIgbocK0nid51nJTLAnUjkl2bCelqhjIqCvwp+iWUhE3JrPyHE+5h/nXM5vtyxo0lm+rBVaU5B5yfTup/ezvE1oDTTu6vi0pKd3t7yNi7KJc5JaKsE8nAXiu0Btr8fSEWFOi11j+VagaTdjpPAK4/2+xC5WwQZYbD2LwVkCxpdb3E73JzYkDiA/OlWq/DuLG4hhzIzcnVOZG4PwO+2fpE75stlSlfVvNe/JPugsf4j8Lg7x0EsHFTwGn1gF7fwWe3iu26pTlMdlSCtaX8GjnAa3sYNC4LZB7UL6dICR7jgZ2fANcPUnebRMZJ3YFqQXlCsctWKQaJgMFGqfSEFxxVSX2GakFWFeImmVK2g8papYbNRdi8Tn+emQzxLqOdN4PLThknemwJnQdyVvJWvT1TF9Y2OuIPefsb8Fx4rmvPOWWim3Gu8akbi9Pc83/+GkuWinPNUgQcpDlxoN4Jebm/C5ganNg/Qce3KgGqhRiVdjaKGoVd0uy+DmVciRPvVLlJ2e5EfbBccCad4F9vzkO2FL3jBD/Y2IsNzUVdnFiMotdJ0qWm1Pr+NeiTN4dwQqtshznRfyiE+WXs7DrJrRWbieIm+EfAc8cATrfLO+W4qxicaMWhMmmCQP8IKWVqEZ1++fs50mALXrHxiPFJAHdR8n0Q0GoqMXcqLo/OP6aE1Kw+z7kvkXh7h94K8hNkv89Pa4Sg4GvZJzax72+yMEeH1vPhn1fcRGIYS03HnJLGQzA6N+BUT9713UUFsHXafKmdYgISUjceBCvTJy55Bl+gF7xiue2qQWlQFw2iFhtrqSFD/NzKn1+LR/V//2dfHVhaRCrnLgR9nFmC7DqDWDBOEcrkfSGKueWWvpfe2xIWITYWhDGxNwocXq92PVWkmUXdEo3WzlXSPNeQOur7J/Zp2xVk3LdMRoM9uwUObdUeYFY9KhabiTihhUlzjCF290se+ZLtsNYC1ihZa0Fhn/ouK2wKMdlgHrfG7dV71/xebtlTa1CsFY6DgOeOWivbmsjQOJAWBHJZiwZDMAl1/LzSrW5Wix23E0FJ4gggdxSHiToJ86sKgEWPABceov7lhshbqSmDFhwP3B+p32WZdH2LDLipk40sdWKpXE40ngfVtywZngh0NRkFj+1KlluWC7sEWejsDOhs+Km6aVATl1WmazlxsCnap5c47iumriRs8DIPSU7iBuVf2tpzEWsDnED8IOl1GoDKE/GWFslL8iULDdqmUmN2yh/B/BxQELFVm9MIpjUhZ8Fvuudnt+2KyhZbgDgvt/4cx8eKa7JRJMrEvUEstx4EO9MnOnGxrZ+Cez5RXv7fz8Gjv4DLHpExXLDWGvUAopZgZevEtOh5pZikcY9SONy2FTwq553XD/MLH5qDddgucl4lY91ERAmKoRBHLvQvIf9PRuIKmAwiq01rMhSm1laq7m/PF+7W6pBU3FfmugMcG3WXX65Ul9rK+WXK1puVMSNdAC/8T1+4BZS3ovP291S3nBjPLgCeGov0KyH87a+QJSOHS/+zmCwC0hW0ITaBJIEoQCJGw8i3N496pZydVtFZ4ElE4HfHtS+DXYeJrYAHMfZt8G6peQCf+0r2d+qVcLlOJmAYhlxI61aLB002SJ+5oZA6uXi7x3EjRBQ7MLNPqKBeH6gpC7293JixWAQuxDYwNhGrYGWA+T346yAmTBZ3g3TxMchPZ8sRiNf40dAb9l2uWBhuZgaW18UfnvFmBsdxuS+44FJmXzWCAAs+x9wYBH/3hsWivDIwMpMEVlu4pXbtaiL9zEYKbWaqDeQW8qDGL1iuXERVpxYatTdLwLSGjYCXw/nM4AeWCG2qmhNBVcbbDk5t5SwD+ZElueJ2wjxPtu+AjI38fERgP3JXzp4mszieBPB/eHKJIbh0eIidA0Sgf9bxwsuwR3HIrXcmCKAW+bwGT7Ne/ITgJ7bDnx5neN6alz9An/ckbHi38LZtArsuWmsc64daczGuL/5LDS9KFlulLIC298gv9xoBGJlUt49EXMT6GgVNw2T+IKCrkwZQhBBCokbDyKMk56NuXF1W8wTmqVKo7hRCOZkM4hYq4qa5UaUMq7mvmLcUgYTL3aE7bKDdFmueD1hm4ufqtsOY7kBHAfPMLP80zwrICIa2mM21IiIFge+RsYBKXVF32TnFDKIM33CIoHLbrN/NpoUspYUnrK73Amc3sjPcCxM5cC6c5yJmx73AcdX8hYnveJOeg4bNNFmDYhvxRQnhLLlRnoNpg0C0l8Fkjorb1vu3KlNcREqqMXcSJGrVk0QIQyJGw/ilYkzXd0YO+BorUfjbJA6uVYc+6JqudHYb1bchEfz4sJaA1zYDfz5lL2dg1uqSuwiE2ItBNEhzQoKM4tdN3Izb7ceZJ+AMekyIHuffJ/DG4gHYTbeQa4Wi9RyIyc0oxRideS47XP+vLECixUp0lpCUi67ne9zchf1dnJIxY3W7JsHlgMfdLb/1kqWG4c6SBzQwonrLK65TD/rmbjxVP0agggRKObGg3hn4kwXt8UOElrdR87cIH88Duz71f5Zq+VGDStTxE94mj+3E/j0SnHGVpnULVUltuYIA71guZGKG4fJD2XEDbvOgyuAdtfL9zkiWhwbwtYRkbNISGNu5IKY5eqPKIlNg0E9ZVrNDSjQdjBfll8vUnGjNbalYRJwKWOtUrLcyBV5dEZsfRU3zDWlJ1aJIOoBJG48SEBNnKnVfcSidwZerdtVQ2S5qRMXQlo1ixBz0+dB/rW2Sjx7uGBRsrmlJIOn0mSO7DGzpvvwKPu+pIRH87Eh6a8CV00Sr6dURdesEFCsiovBn87cUu4gtbgoiZuBdfOSdb/PvowVj4qWG8k/j5ZjkUu/rw8pz0qWO4IgyC3lSbxSxM9VrD4QN55wS32Rbp+uQGnAA+yWG8F9Y6kSzwQtTAKpZLlREhTsMV/xFD9HkRAPw7pcIuPtU1IIywc+5bg9ORFlMEqypTRmaLkqUrwpbqSDqFJdmmv+B7QfKk6bZn8TRcuNxFLjUEBPQ5+A+lPRtt31vAtXycpIEPUUEjceRHjO9mhAsavb0pyyzeBJy43WfrMVi5UGPMAuLKIb2/ddcoH5vk7cmFTcUgCfyluYyc8YDYiPOTIWuPt7+2fWVRTT1N4HtRL2cgJNGnOj1SJTW6GtncAlg/ng704361vPG5jCgJaSdHyR5Ubht2Zdaje+B/T4j2v7rw+WGwC492ft2ZAEUY8gceNBAqqIH/v07qmYGymesNywaAlOFcQNABQxkzdWFPKvztxSj2zkKx83voT/rDoPEzNANmhqr3as1k/ZgGKD2JKgJApv/xLY8CGQtYf/XKNQAE+J+361V6UNRFjhJxWfAqzlpu947dtu3hs4t43ZV4CeA09jMJCwIQgZyFHrQezTL/i3HwB845ZSTfF24SRoGZDYCsCFmfb3glVF1i1lsC83x9iFDaCeISay3LCTD6pYBWRje+oCgFtfBSS0AZK7yq/b5Q7gYWZqA62i1LYbQ+AKG0Bcw0bpt07s4Nq27/0ZGDzF/pmK1RFEvYYsNx7EKzE3rm5KyS1VVSq2XLDoHRDUJs50BS2WG7ZYGTv3lJBlI2e5CYtUyTzSaLlhLUZqIkzOIiHsY/TvvNtFqVCdFL1uqYCH+Q2ULDct+/EWLLnrU40GjYFBE3kBGe2k5gtBECEPWW48SEAV8RNVEmZEyK8PAB/3BPKOOq6jtR6Orb2CZYHjnNdaMcfy8/SwKA14LBEN7anU7HQRAnJ1blTN9hotN6y1Ru3YZAOKmdm9tQobQL9bKphQE4hd7nB9/qYWvXjrGEEQ9RoSNx7EOzE3LqKUCp5zgH+9eMpxHb1ZNkqWG0s1nIoyq8WxcJ0mcdPAPjCWy4kbYfoFdqoFlckx1Sw37ADM1hFRO09hMkGzrgbF+spyY66rntxUpQqwJ2CtZ+Q2IgjCi5BbyoPodktxHJB/jJ88UemJ3lUrkCjmhrGwVNRlFcnFc+gVN0qWGyG1Ww1rLS9UTGb7djSLmwigCgqWG5npF5Rq3ADq4oYdgNlzo2bhYvc1ZjF/TEmXKrdXw5sp3SwP/MPPCC83mzpBEEQQQpYbD2KfFVzjCju/A2b25l1FiihsjOOAzM3iCTJZLDKDsdUCVNWJG7kgY71uKSW3iXR5SnegcVvxMs7Ciwc2QFhLQHFEjL36bE2Z4/e2gGI25sZFccPCpihb1cSNZL/JlwW+laJpJ+CWT3ww/1CAnweCIEIGEjcexJ4tpVHdrP+Afz2wSP/Odv8EzL0emDtU/nu52buFWjCAvLjR7ZZScJtILTdGk2P9F0EssK4pZwHFYZG8hathinIbIeYmzANuKRZW3ChlOwFiC1yoxn4INXtMOlOQve32IgiCqIPcUh7EpxNn7v6RfxUmd7Ra+DosiR34yGarTLYU68aRdUvptdwoiJtaieXGYJJxOdUdF2u5cZbGLAT1ys6gXYdcKrhaQPFVz/PiUmmqBQFrLfB/6/jJQ3uOUW876QzfPlRnph79O7Dsf8B1r+lbr911wE0z7DOoEwRBeAkSNx7EpxNnSl0d66YDq94AbpkDdL9HPqBYKHTHLmPRMuEii9RCU5YHbJrNuzlYjCblMv0NmHmBnFluXBY3KqKp8SXAC2eV+9ekI5B7iJ+SIaWrtoGZnWohFGneExj3l/71DAag9zjP94cgCEICiRsPon/iTJ0iyGq155tL3SkFJ/jXrL0A7pGvUMxabrwRc/P7BODI347tDCY+nuPkWsfv2BmdncXcCLE2crNACwgihd2Ws3mGlIQNAIxfBRSfAxLbqW/DkxjDfBdMTBAEEYJQzI0H8U4RP2ZbbGl6qbgRxIowU7bIclP3XqjiCzi6pcrygFPr9fVNark5slS+XXwqMHQa8OBKx+/iWtjfe8RyUxdzE9scSBsEtOwPXPs/9e2q7jPat8IGqD9TBxAEQXgJEjceRHcRP03tmDaip3mJW0oQN8JM2XKp4GqWm5m9gZLzGvrD4BBzI3M8HW8Crn+DFyYtejl+LxI3MoP6rZ/a39ssNxJxE9fS/l4IcjUagbGLgfuXAindFA8hICFxQxAE4RYkbjyKl4v4seLGqeVGLqC40HGZgFzNGGcoBRQLtLmGn2WbjauRInJLydS5YVPI5Sw3xnAgoTXT3sdWFm8QneC8DUEQBKEIiRsPon/iTA0NOQXLjaK4yalrKzP9gjRbavOnwJfXi1PEWWKS1fvmrIKulqJ8can294JLiYUVMsL2YpL4OB6Ad2XFNLW3CTYrjRx3fAXEtwJu+9zfPSEIgghKKKDYg3gl5kbklmLmNJJmSwkCpqoYqC6Xz5ZiY242zrS//5d5zxKbYrcEycFabuQyrbS4V0RWHZnz1oARLsL+jCagYTIf6BsRDVQWM31WiccJFpIvA57a4+9eEARBBC1kufEguov4aWknqoyrwXID8IJEVOdGJuaGpVgh1oYVJ+mvOH5fU24/hrJcx++1zPJtMACDpwBd7uKDf6WwRfFYC5NQyC88GuhQV8gwtkXgVwMmCIIgvA5ZbjyIVybOVBQ30oBiJvupJFt+LiQ25oZFSfSwk0UqVfmtreIDgUsuOH7nrCifwKCJ/KvVyUziVRILzTnw4qbHaCC8AZA2UNv+CIIgiJCGLDcexCtuKVakKKWCWy1iN1RptvizXJ0bFk3ihqkFE97A/j5nP/+ad8xxfS0xN6L9SS7H698Uf2aDhYVA5Iho3rrTbSQQp1L/hiAIgqg3kLjxIPqL+GmADQxWSgWvrRLXrSnNlqSCy8TcsGgRN+zkk1GN7AG9n1/LT94piBwWuewnrdz6KTBgAv9+/Cqg52hg6FT790JsjV4BRRAEQYQ8JG48iEFvzI2WbClW0FgVLDeWKrGlpiRLIRVcQcTIiZ7/LFR2S5ljxFak0hwg+4DjNrS6peRgJ2Vs3hO4+WNxVlSrAbzAatHH9X0QBEEQIQmJGw9iNBgQgRq9kyqooxRzw1JbLY65kVpuaqv4TCPphJYC0licsUuAS64Vp2azk09KpzOoKQdy6sRNYnv7crmAYoNMurccbIq4HC16A5My3as+TBAEQYQkJG48SJuizdhnvh/ds37VtoIWFcRaZERCh1m+5VNe0AiUZElicHKUg4kBx3o1xrr4GjbOhnUxScVN0Tmg6Az/vtUAZh0Zy41cLRuWUb8CN7wLpGqwyITqrNsEQRCEW/hd3MyaNQtpaWmIjIxEv379sGXLFsW2+/fvx+233460tDQYDAbMmDHDdx3VwF2Hn0KEwYIbM9/13EZFbimZDCgAWPe+eJ3SHHHbwkx9FYgFdxTrlmrQxP4+Ml7c/sxm/jW2OdCIqRYsZ7kxOknQa5cO9HtIc1cJgiAIQopfxc38+fMxceJETJkyBTt27EC3bt0wZMgQ5OTkyLYvLy9HmzZtMG3aNCQnO6me6wcMuh1SCu3L8vhCfIByzI3aDN7Ze4FDi+2fayuAvCPauyXUlmFdSDFNgWte4ovqSWveZG7iX5Mu5YONBeRibrS6pQiCIAjCRfwqbqZPn47x48dj3Lhx6Ny5M+bMmYPo6GjMnTtXtn2fPn3w7rvv4u6774bZrFB3xV8wNVqs0kkt9VCWB7x7CTCjS52YkcwKXlEIHFwMVJfp2+6x5drbCm6pGmYfDZoAVz0PPHsEaNSKnx5AIPNf/rVpZ7G4kcuWcuaWIgiCIAg38Zu4qa6uxvbt25Genm7vjNGI9PR0bNy40WP7qaqqQnFxsejPKxRl2t5WmGK1rcPJTK0gWEHK8xwDiK21wE+jgPmjgDOb9PVv53f8a9t0+e/Z2jWC66iccWUJVhghJeyBZYA5TryNlK4Syw2JG4IgCML3+E3c5OXlwWKxICkpSbQ8KSkJWVkq8xnpZOrUqYiLi7P9paY6ycJxlZxDtreRllLn1Xal2IQMI3ikM3dba4HT69W3w4oLQBz3ktwVuGqS/HpsATzBLVWer7yfhsl84TyB+JZAx5s0iBsqik0QBEF4F78HFHubF154AUVFRba/M2fOeGdHHYZibh8+zsUEi3LBPBGs5UYmhua7O8SflVLBWeJaiD/3HMMLjrBI4KYPgIgGztcTBEhFgfq+2KypgRP5Qn8it5RMzM3wj/hXSuEmCIIgvITfHqMTExNhMpmQnZ0tWp6dne3RYGGz2eyz+JzyqGQUc1GINVQA5QVAdIL2leWEi9T1pMUaFJMEYK/98yXX8AHANeV8f/KPy68Xy1huhJibcmfihnG/CeKIFTeczEzhHYYCL55XFlkEQRAE4SZ+s9xERESgV69eyMjIsC2zWq3IyMhA//4ys0MHAQaDAQVc3YBfnqdvZaGiMDuNghQtlhup26eqhI+XEYQWW7uGhbXcCG2EiSgTO8ivw1puBHHEuqJiFeZ6ImFDEARBeBG/BkBMnDgRY8aMQe/evdG3b1/MmDEDZWVlGDduHABg9OjRaN68OaZO5ecUqq6uxoEDB2zvz507h127diEmJgZt27b123EIhBkNuIiGSEM2n/XkDGn14VMbgF8fUGmvQdxIkcbNGBT0bMNkxza3fgpsmwv0GiO/DiukhJgdgwF4fAefzdUgUX9/CYIgCMJN/CpuRo4cidzcXEyePBlZWVno3r07li5dagsyzszMhJGZKfr8+fPo0aOH7fN7772H9957D1dddRVWr17t6+47EBVhwkWurmqulqJ50kkx592o3l7OzePQhonjiU4ELr1N/L1cYT1AbGURxE3DJOCaF5T3VcVknkUymVONL3HeT4IgCILwEn5PXZkwYQImTJgg+51UsKSlpemYlNL3REeEoRx18T015c5XYCe3zD3ovL0Wy821L/E1bfqM52fRlqZeRycAt30B/PageHnjtsCAx3lxFBXvfD8A0KyH8zYEQRAE4WP8Lm5CiegIE0q5ugwhLUX2WMvN8VXK7eJbAYWngdXTnG8zpRvw31N8sK9BoZhg1zsdxU1UPHD9G863z5I2ELjnJ/FkmQRBEAThZ0I+FdyXREWYdFpuGHGjVFMmLpW3qgDap1CIjFMWNgKdR9jf93vEsT6OVjrcQG4ogiAIIqAgy40HiQ43oUIQN9Uy4sZSC/w8GmjSHhg8RWy5UZq1O6KBdwrfjfgE6DoSaHMNEKEQh0MQBEEQQQiJGw8SHRGGck4QN6WODbL3AoeX8H/SYntKRf+8JW7MMUDHYZ7fLkEQBEH4GXJLeRCnbqmqEvv7I/+Iv6sskt9oRAPASD8TQRAEQWiFRk0PEh1hQjmEgGIZccMGGUvdUIpuqRiaj4kgCIIgdEDixoNER5hsbimrXLZUFeOqkrqh3HVLmczAmMWa+kkQBEEQoQyJGw8SHRFmCyi2VsmIGzYOR1rkTym7KiIGaD9UubKwwMjvgNaDdPSWIAiCIEIT8nd4kIgwI6oMvFuKkwsoVhM3ihttAHS5A2h9FfBxT3FVYAC47zfg3A6g3XUu9pogCIIgQguy3HgYS930BmF5h8QBxIA45kbrPFERddM5xDSx17thaTsYuOo553VtCIIgCKKeQOLG04Tx4sYADvjyevF3UrGjBXYG7WHvA6YIYOBEoEUf4LLb3egoQRAEQYQm5JbyMFxEA0AwyuQcAPYvAorOAgMmaJuSYdzfAAzA3p/r3E2MQGreE5h0BgiP9ELPCYIgCCI0IHHjacIbiD//MoZ/bd5Lm+WmcVsgpinQqr/C9knYEARBEIQaJG48jEFpKoOvhmrbgBBjQxAEQRCES1DMjYcJi2zgvBELW8PGYATCozzbIYIgCIKoZ5C48TBNYnVOQpk20P4+IoayngiCIAjCTUjceJiU+CjcXPW69hWSu9jfxzb3fIcIgiAIop5B4sbDpMRFYg93CQ6au2lboUVf+/uWl3unUwRBEARRjyBx42FS4vhspnyrBvdUSjcglRE3KV291CuCIAiCqD9QtpSHaRbPBwRn10gCgxu1BtKnAMUX+FTvddOB2+cCDZP5gny5R4AOw/zQY4IgCIIILUjceBjBcpNb24A/u00vBe75AYhqBETG2Rt2ucP+/j+LgNoqoEFjn/aVIAiCIEIREjcepmFkOGIjw5BbE8svSOoMNEpTX8kcw/8RBEEQBOE2FHPjBdISG2ChZRBOtH8QuPJ5f3eHIAiCIOoVJG68QOvEBihALJY1fxRo0t7f3SEIgiCIegWJGy+Q1pivUnwyV8NEmQRBEARBeBQSN16gTZM6cZNP4oYgCIIgfA2JGy8gWG5O5ZG4IQiCIAhfQ+LGC6Ql8uImp6QKpVW1fu4NQRAEQdQvSNx4gbiocDRuEAGArDcEQRAE4WtI3HiJ1nXWm5MkbgiCIAjCp5C48RKCa4osNwRBEAThW0jceAnBcnMst9TPPSEIgiCI+gWJGy/RuRk//ULGwRwcJ4FDEARBED6DxI2X6NqcnySztKoWg99fg7RJS/DztjN+7hVBEIEGx3H+7gJBhBwkbrxE4xizw7LnF+xxWMZxHIrKa3zRJY9w8EIx9pwt9Hc3CCIk+GztcfR5MwMnQtC6e/ZiOf7aeyGgxVtZVS3eXnoI+84V+bsrhIchceNF7unb0mHZigPZos8frDiK7q8vw4ZjeS7to7rW6tJ6rlBrseKGD9fh5pkbgkqQEUSg8tZfh5BXWoW3/jrk7654nBs+XIdHv9+BP3af93dXFJm56hhmrz6Omz5e7++uEB6GxI0XmXxTZyx4uD9SE6Jsyx78Zhv2nrU/JXyUcRQcB7z6535YrBxySio1b//bTafRefJSrDqc47StxcrBanXvCSqnpMr2/hRNLUEEMcWVNfh77wVU1lj83RUAQHl16BX7LKnkjynjoPP7k79g78VEaEHixotERZjQOy0B656/VrR80a5zqK61oqTSbv3gOODdfw6j75sZyDiYLd2ULC8v2odaK4dxX21VbVdjsWLojLW4bfa/bpmILxRV2N6fK6xQaRn6XCyrxuYT+QFtcieUefbn3Xjk+x14f9lhf3cFAP/wEapU1QaGgJQjIsw+BLr78EcEFiRufERa42jb+993ncMtszagyyvLbMuO5pRizprjAID/LdoHgPcHH7xQ7Lbr6XR+GY7mlGLXmUJcdMOddL7QblXKLCh3q08sW08VYMmeCx7bni8YO28rRn62KaCfSgllltW5hz9fd9JvfWCFcaiJm4pqu6DxpetcL0aDwfY+r7RKpWXwcDy3FDNWHAkYq6S/IHHjIz4b3RuPXXMJmjQ0I6+0GgcuFCu2vVBUiTlrjmPUF5txw4fr0P5/f2PsV1scgg7NzFOHUgzM3rNFOJFrdyGdd8PiwlpuPCVuOI7DnXM24rEfdjgE9W0/XRCw/vrdZwoBAN9vPu2zfeaXOp+r7HR+mS7XpieorLH4zJLHcVzAWcssVg4T5+/Cs7/s1tU39res9aO42X66AOO+2oLTHnQ1s0KhuFJ8zW45WYDX/jwQEIPvxfJq2/szF0PDGn3jh+swY8VRTF9+RFP7FQeyMeyjdTiaXeLlnvkWEjc+on1SQzw3pCOeu76DpvbT/j6EXXUDKACsPpyLUV9stn22WjnR094nq485bGPVoRwMn7keD3273bbs7MUKvLH4gKw5/uzFcjwwb6tiyjpruflhcyaO5bj+z5BxMBs/bM7E+SL7Nvcw/m+O43D77I144sedNiEhpcZixbEc17NMNh7Px4crjuo2m9dY7E+iq4/kYsgHa7HuaK7L/dBCQVk1rnp3NUbMXK84KOSXVuGqd1fj2vfWgOM45JZUYdxXW/CnGwJxy8kCfLjiqKplYdKvezDo7ZX4e68269viPedx3xebsftMIcp0TCy7I/MiLnnxL3y53n/WFjl+2JKJ33aew4LtZ3FWxwBZyDyQ7DpTiG82nvJC79SxWvn/s1WHc/HeMm2DoRZYcXNBInzv+nQj5m44iU/XnPDY/lwll4kjDAVXO8dxqKqzlG08nm9bNmfNcSzdJ///+eA327D/fDEe+X6Hz/rpC0jc+JhhXVNEn3u3agQAiDGHYe1z1+D4WzdiZO9U2XUvFFVi7vqTyC6uREF5tehpb+6Gk6J/VAB4T0bAPPzddnyx/iQ+XnkMN364DoeyisFxHGotVoyYuQEZh3Lwv4X7YLFy+GXbGRxh1HxWkdgi8PFKR0HFwnEc7p+3FUNnrEUxE19UWWPBA19vw4sL9+IXRkgdZKxZrGXosMITxccrjyF9+hos2nnOtiynuBLjvtoiGmira624f95WTJy/S/Rk/cJve/DBiiP4Yt1JnCko1/zUzVq/OI7v36Rf92paVy/nCytQVF6D3WcKUVpVi+O5ZZj37ynZtnvqLF+lVbW4UFSJ7zadxqrDuZj48y5d6fvnCiswd/1JjPpiE+76dCM+WHEEP27JVGy/aNd5WDngke93OLUsAcCEH3Zi/bE8jJi1Abd+skFzMO1LC/fBygFvLDmo+VgEdmRetIlxaR+LK2uw8lA2ai363Se1Fis+WWX/Pzii4en387UncOOH67D/vNh6O/n3/Vi6L0t3H/TAcRze/eeQTUitZUS5J13NeaV2i8j5okqbVZb9H9ueedFj+3MF4QFAYL8H0sE5jsPriw9g6t8HwXEcJv68C0/9tNNn8TwXmHt0mIl3uW0+WYBpfx/Cw9/tUL3G3XlQDERI3PiYBuYwjOnfCgDwzf19seCRAdj/6hCsevZqtGwcDZPRgFduvtQ2fYOU1xYfwBXTVmL8N9sAAIkxZrRPikGNhUOfN1egx2vL8MHyI9iZeRGHstRvtAcuFGPojHXo8fpy3PrJv8gv429I1RYr3vrrIJ5bsAfXf7AWFdUWfJRxFEv38zfeLnUFCjefKMDR7BKRcGHZfLIAKw/l4FBWCZ75eTfeXnoIpVW12HKywNZmxoqjtvffbjqNN5ccQHFljciKs3DHOdkB8KMMft2nf95lW/by7/uw6nAuHvl+h81Vt3R/FlYeysFvO89h3zl+QCkoq8apfP5m/u4/hzHonVWYvvwIbvp4HT6ti30C+JvVK3/sx8uL9tluzGcKHJ/wzhVWaI6b+GP3edw1Z6PT9P8zBeUY/P4a3PXpRtGg+fPWM7JCjLUabD1VgM/X8U/GNRYOE37YqWolqayxILu4En/vvYCbP16P1xYfwIZj+bbv95+Xv/FLf5eft6oXqpT2+0h2qegaUKOasbBpFSJnL5Zj0c5zuO2Tf3HnnI2orLE4WBLu+WwT7p+3DXM36LcIrTqcKxpQlIS4wL/H8/DmXwdx4EIxPl7peNwzVhxxOEen88vwxboTHold2XmmELNWHcfk3/fjYlk1/j1u/43P6hD4zpDGrzz0zTZU1liQVWw/VxXVtcgtqVK8trRypqAcF8uqnTeUUFpViwrGCvrbznNuneNaixWv/nkAX67nrVLfbTqN33acw6Jd57Fbw8PFkj0X8Mh321HgwrEI7GUE2rm6+wErWo7lloLjOOw/X4Ts4kqH/98ShXt5MBIQ4mbWrFlIS0tDZGQk+vXrhy1btqi2/+WXX9CxY0dERkaiS5cu+Ouvv3zUU8/w8k2dsf6/1+DK9k0A8IKnSUN70b+oCBPmjeuDide1x+LHB6JNkwZ445bLMH5Qa7RJbIBaK4edmYUA+Lib6zsn29a9WF6DDzOO4q5PN2oebAvLa0T/FABEpv9Ok5fa/LcNzWH4fnw/hJsMyCquxHUfrMV109c4+Ot/3nYGd3+2yfZ5+YFszF59HB+uOKLqwvl83Ul0fWUZHv9xp23ZxhP56Dz5H9z3xWbMqxuA2Cch4X58sawa/+y3Z5p1e20Zdp8pFImVd/45hMoaC+bKuDY+XnkM+84VY+rfh2yun33nijHv31P4dtNpHLzAD1xnLtqfcDunxNref7XhpOzN4UxBuU1YcByH95cdxpZTBRj1xWbszLyI7zefxnv/HBb9Xp+tPY5B76xCRY0Fh7NL8M1Ge2zPibwyrD6ci7MXy0XngY3JevKnXSivtsBkNCAlLhKZBeX4dpNjfNCB88W4f95WdHx5Kfq9lYFHvt9hE7ksbNwWy6k88dP+l+tPYsWBbPyx+7zsQJlT4hi0+fO2M6issYDjOCzecx5fbTgpEmK5JVX4fdc5lFXZB6KOLy91sMRJWXkoGwPfXoWn5u8CwP9vvPfPYayXiErBgvL2Uv2ZU8v2iy0thy6oi5t5G0457Fe0fpb9t66uteLlRftw1bur8caSg/hO8vvllFRi4c6zuixOO07brSXrj+VhO/M5v6wag99fY7v2D14oxsT5u3DShcl/8+p+5/ROSWja0IzzRZV45ufd6D91pa3N1lMX0e+tFRj20XqH8yjHxbJqzFp1zJa+XVxZg9P5ZRg8fQ1un/2vbmEi3PMamsOQGGNGbkkV7v18EzYez9dsaamssdjaPr9gj8iq+vLv+23v/9h9XjYWbv/5Ikz+fR9eX3wAj/2wA3/vy8LNM9fbpuzZkXnRwWKuBuuCzimpwvMLdtvcUwDw77F83D77Xwz7aD0Gvb0Kn60VuwZ/28FbwfNL+XVdrb8WCBg4P0fnzZ8/H6NHj8acOXPQr18/zJgxA7/88gsOHz6Mpk2bOrT/999/ceWVV2Lq1Km46aab8MMPP+Dtt9/Gjh07cNlllzndX3FxMeLi4lBUVITY2Fin7QMNi5XDf77cbHviuq1Hc/z3ho54/MedIosIALRMiMa8cX1w7ftrbMtMRgPev7Mbnl+wB3HR4WiZEC26wf1vWCd8uvaEg4tL4LkhHfDYNW1x92cbsemEfX8pcZF4Or09iipqsOdcka44j8YNIvDE4HaY8sd+540BPHL1Jdh/vhhrj9hF0tPp7fH95tOyg6caHZMbylq4runQBLf0aI43lxy0bfPxa9ti4nXtMenXvZi/7QzGDkjDKzdfiucX7MbP284CAIwGvnhj1xZxMMCAAxd4cdS2aQy+GtsHr/55ACsUUv2n3dYFd/dtifLqWnSe/I+m/vdtnYAR3Zuhc0ospv59SHQNRIQZ8caIy2AyGvDML7sRGW7ES8M6Y1DbRLRqHI1lB7Lx1E+7RE+v7LrmMKOtVgnAW+z6pCVgcKem6JDcEPml1Viy9wI+yjiKzimxyC6udBBGt3RvhqfS26NV42hU1lixaNc5vPAb78L7c8JA/N+323C+qBKXNY/FxbIaW9zDJU0aYM59vdAuqSHu/XyTyMLA8uatl+Gu3qkIN4mf0z5YfgQfZqhbhHq2jMf5wkqRNaFzSixG92+FO3q1gMlowKdrT2DbqQL831WXoE9agsM2rn53FU7ll2PsgDTbwHZ3n1Q8P7QjEhpEwGrlwIH/vyssr0bfNzNQLSNGHrvmElissGVM9k1LwJZT4v/nlLhIfPtAX6Q1boAwkxH3z9uKlYf4bL1rOjTBF2P6wGQ0OGybZfw327C8LlOsUXS4LXuycYMI22/Xr3UCBrVLtMXghJsM+O/QjggzGjCie3M0ahBh296WkwV4b9lhPD+kA3oz5+ehb7Zh2YFsPDekA5o2NOM5mersLM3jo/DRPd3RtUW8w29Za7HiuQV7sLDO/Ww0AB2SY3E4qxisBhnWNQXpnZpiYNsmaNLQjO2nCxBjDkeH5IYO+6uqtWDs3K3YeCIf9/RNxZBLk/HQN9ttv01kuBED2zbBwLaNYTQacCqvHKfzy/Bkeju0T2qII9kl+GXbWfy4JRM3dknB0MuS8WhdzMqV7ZuI7k0sw7s1Q8uEKHRKicUNl6Xgug/WKD44sHRLjce9fVNxV+9UGAzi35jjOBzKKkG4yYjrP1gDNV0WZjSoBq+HGQ1IiY8UWaefH9oBzeKicEuP5qp9zCmpxNojebiuUxLiosOdHpMr6Bm//S5u+vXrhz59+mDmzJkAAKvVitTUVDz++OOYNGmSQ/uRI0eirKwMixcvti27/PLL0b17d8yZM8fp/oJd3AC8wKm1WrErsxCdmsUiNpK/kDiOQ62Vw9ZTBfh56xk8dk1btEtqiMm/70PGwRw8O6Q9BrVrgsQYM0qrahEZZkSYyYjP1h7HW38dwsjeqZh2exccuFCMYR/xFTv/78o26JQSiw7JDREbFY6U2EgYjQYcyynFM7/sRl5JFWqtVmQXO4qKVo2j8fE9PdA8PgpzN5zE33uzcKLuKbB9Ugw+uqcHGjcw26xWHMfh4e+226wv6Z2ScLbOSjK8WzOsOpSDbYwQkyMuKhzdUuNFN5eocBPm/KcXqmuteOqnnShj0lTnjeuDy9s0xocZRzF79XG5TYqICDPanhB//r/+6Ns6AZU1Fny36TS+WHdSNFCq0btVI+SWVuF0vt3yYTAA3VrEiwLJAX5gFKw6vzzcHz9tOYM/95xXfVJNjInAjJE9MLBdImotVoyeu0UkEIwGqN4EX735UvRrk4Bpfx/C5hMFsgKI5faeLdC2aQzeXipfaTc5NlJ0btI7NcUXY/rgj93n8fT8XYpWxsQYs4OLIz46HO2TGoqEXLcWcWjTJAYbjuWJBG64yYA1z10Di5XD+G+2IbOgHDUWK0b3T8MTg9vBaOCLzE1ffkQUc9IyIRrN46Ow8YT9nPVoGY+ocBMOXihGk4ZmlFdbbK7A3ZOvx7vLDuG7TXxsktEA9GzZCGcvVqDWyuHKdok4cKEYh7JK0CklFv8b1gnjvtqKaosVDwxsjZdv6gyO4zB7zXG8989h1d8mKtyEnq3iRW5D4Ry0S2qIWosV4SYjtpwqQFS4CTd3b4buLeJxvqgSz/6y22F7nVNiMW9cH3y76bTTOLoYcxhG9klFaqMoJMSY8dwvu20BrGMHpKG8uhZGgwF/7j6PsmoL/pwwEJc1j8Xriw+K3H79WicgIsyItk1j8OOWTFTW8NtIaBCBy9skoKE5HDGRYTCHGfH3vizd1qOG5jCU1Fn/bu3RHMlxkVh5MActGkWhRaMonMovx5q6e8Rvjw5Az5aNcDKvDLNXH8OvO86pWr3V/ncevfoSPD+0I9YeycU7//D/C+Emo83SrkabJg1QXmVRvYe0T4pBr1YJsFo5RIQZ0bxRFI5ml+LXHWdtbQa1S8TMe3pi7dFcvPrnAYf/H5PRgC/G9MZ/F+yx/a+8dWsXLNp5zkFQs3ROiUXnZrFoEGFCSVUtYsxhSKgTuvvOFWH9sTxU1lhhDjOiT1oCbu7eDHf0bAGjE8Gth6ARN9XV1YiOjsaCBQtwyy232JaPGTMGhYWF+P333x3WadmyJSZOnIinnnrKtmzKlClYtGgRdu92/MetqqpCVRWTllhcjNTU1KAWN57GauVwKr8MrRMb2J4Kdp8pBAege2q80/ULy6vx5fqTWHc0D1HhJhzKKkZ0RBh+euhypCbY6/vUWqz4cesZ7D9XhP/0b4VLm8XJbo/jOBSUVSOhQYToKaWq1oIv15/EbzvO2fzI/x3aEZU1Fiw7kI3DWcWYfld3DL0sGXvOFiE1IQrbTl1Ez1aN0DyerxJ98EIxfticiZYJ0XhwUGvb9q1WDnM3nESY0YBocxh+2JyJwvJq9L+kMdonNcSaI7n491i+7cmuZ8t4/PrIAIenqGX7s/DtptOotfA3Hw5A+6Yx+GvvBZwvqkRsZBgGd0rC49e2hTnchE9WHUNFjQXnLlZgs8Ty9s7tXXFTtxSUVVmweM95JMdG4oYuKbZzcehCCd5cchA7Mi/ansYEgSrtV43FirnrT2LhznM4nF1ic+Vd1b4J3r2zKxpFR9iWHc8tRYekhrabktXK4WBWMU7k8rEfJ3LLbAMHwAuIr+/vi8tbN8aC7WdxPLcU/+nfCqfzyzHt70MOLs8IkxFTb+uC23u1AMDfGNccyUVa4wa4ukMTnMwrw0sL92L/+WLbcfVq1QhXtE3E5W0SMOCSRNTUxYV9vylT1hIi8O0DfTGoXRPReaiosdgeCASKK2uw/1wx9p4rxOzVx20WDZPRgGs6NMXKQ9mKA1q31Hj8/tgVAPhYp5cX7VOMdws3GfDz//VHj5aNUFxZg2X7szG4Y1ORNeRodgmW7L3Ax1YBWPrklXh18X78vuu8R+rh9GwZj4/v7Ynftp+FwQCM6tfKtv8TuaV1WYwVqK61YsAliTiUVWyzTOqhcYMIbH0p3XYdFZXXwGQyoKC0Gs0bRdmsTNtPF+CztSew5WSBYh0uk9GAu3qn4rFrLsHKQznYcCwPxRW12HeuCFERJjwwsDV2ZhbiUFaxLZbOGWFGA96+vavtOhSoqrXgSFYpftqaiS0nC3BUY6DtoHaJ+GJMb5jDTKLlVitvWTmaU4K9Z4tQVFGDhTvP2a7tBwe2xoRr26JhZDgMADgA209fxOrDOaiqtaJDckNk5pfj07XHUWNx/vt/c39fW8jDsZxS/Ln7PPqkJaBbahxO5JahgTkMbZvG4ERuKb5cfxJtmsTggYGtYbVy2HA8DwVl1cjML8fus4XYeDxf9DCohw5JDbH0qUEO9yJ3CBpxc/78eTRv3hz//vsv+vfvb1v+/PPPY82aNdi8ebPDOhEREfj6669xzz332JZ98sknePXVV5Gd7Wjuf+WVV/Dqq686LCdx4z0EC5LUvOxJrFZO9ETAcRwqaiyIjgjz2j4rayzILChHUUUN2jdtqNv0WlljQbjJqOg6OJpdgsPZJVh1KBeXNY/FuCtaa972xbJqcIDtSUqNU3llWHs0FwMuSUTbpjGa98GSU1KJymor9p8vQtumMWiX5Gj6Fygoq8bCneeQW1KFR66+BA3NYZqe5oora5CZXw6OAzqmNJS9nmosVhzPLcVX608hs6AcVo5DbFQ4OiU3xAOD2iAuSr95vLSqFuuP5mL76Yu4pmNTDLgkEWcKyrHxRD5qLRwullejqKIGqY2iEGYy4vI2jUUJABzH4UxBBdYczYXVyqGsuhbVtbw1ZXCnpuiYrO2+IwgZ4XrhOA65pVXYeDwfRRU1OJRVglH9WqJzSizOFFRg88l85JRUoaruOr2mY1OUV1uQcTAHR3NKYDQYEGY04P27uqFri3hd5yQzvxwcOKw6lIPD2aXIL61CdkkVOiY1xH/6t8K/x/OQX1aN2MhwlFTWYu+5Qozo3hx3KWR+ylFrsWLTiQIczSlBebUFxRU1yC+rRueUWAzrmoKk2EjZdYwGg+h6yimpxD/7s2EOM6Jd0xj8sv0sDmeVIMYcht6tGiG7pBI7ThdizIBWGNnHcf4/KYKlvNbCYeWhHDRpaEb31HiYw4ywckBRRQ1qLVY0lemfEkeyS5Bfl1HWO62RpntldnEltp4qwN6zRbb765aT+TidX44bu6SgZUI0os0m3Nu3pUcFBcD//jvPXMTZixUor65FcUVtXUCyBYkxEbwVKT4aD13ZBoezS7DhWB6SYiNxh0Q4uguJGway3BAEQRBE8KNH3HjvMVcDiYmJMJlMDqIkOzsbycnJsuskJyfram82m2E2m2W/IwiCIAgi9PBrKnhERAR69eqFjIwM2zKr1YqMjAyRJYelf//+ovYAsHz5csX2BEEQBEHUL/xquQGAiRMnYsyYMejduzf69u2LGTNmoKysDOPGjQMAjB49Gs2bN8fUqVMBAE8++SSuuuoqvP/++xg2bBh++uknbNu2DZ999pk/D4MgCIIgiADB7+Jm5MiRyM3NxeTJk5GVlYXu3btj6dKlSEpKAgBkZmbCaLQbmAYMGIAffvgB//vf//Diiy+iXbt2WLRokaYaNwRBEARBhD5+r3Pja0Khzg1BEARB1Df0jN8BMf0CQRAEQRCEpyBxQxAEQRBESEHihiAIgiCIkILEDUEQBEEQIQWJG4IgCIIgQgoSNwRBEARBhBQkbgiCIAiCCClI3BAEQRAEEVKQuCEIgiAIIqTw+/QLvkYoyFxcXOznnhAEQRAEoRVh3NYysUK9EzclJSUAgNTUVD/3hCAIgiAIvZSUlCAuLk61Tb2bW8pqteL8+fNo2LAhDAaDR7ddXFyM1NRUnDlzhuatcgKdK+3QudIHnS/t0LnSDp0r7XjrXHEch5KSEjRr1kw0obYc9c5yYzQa0aJFC6/uIzY2li5+jdC50g6dK33Q+dIOnSvt0LnSjjfOlTOLjQAFFBMEQRAEEVKQuCEIgiAIIqQgceNBzGYzpkyZArPZ7O+uBDx0rrRD50ofdL60Q+dKO3SutBMI56reBRQTBEEQBBHakOWGIAiCIIiQgsQNQRAEQRAhBYkbgiAIgiBCChI3BEEQBEGEFCRuPMSsWbOQlpaGyMhI9OvXD1u2bPF3l3zO2rVrMXz4cDRr1gwGgwGLFi0Sfc9xHCZPnoyUlBRERUUhPT0dR48eFbUpKCjAqFGjEBsbi/j4eDzwwAMoLS314VH4hqlTp6JPnz5o2LAhmjZtiltuuQWHDx8WtamsrMRjjz2Gxo0bIyYmBrfffjuys7NFbTIzMzFs2DBER0ejadOmeO6551BbW+vLQ/EJs2fPRteuXW1Fwfr374+///7b9j2dK3mmTZsGg8GAp556yraMzpWdV155BQaDQfTXsWNH2/d0rsScO3cO9913Hxo3boyoqCh06dIF27Zts30fUPd4jnCbn376iYuIiODmzp3L7d+/nxs/fjwXHx/PZWdn+7trPuWvv/7iXnrpJe63337jAHALFy4UfT9t2jQuLi6OW7RoEbd7927u5ptv5lq3bs1VVFTY2gwdOpTr1q0bt2nTJm7dunVc27ZtuXvuucfHR+J9hgwZwn311Vfcvn37uF27dnE33ngj17JlS660tNTW5uGHH+ZSU1O5jIwMbtu2bdzll1/ODRgwwPZ9bW0td9lll3Hp6enczp07ub/++otLTEzkXnjhBX8cklf5448/uCVLlnBHjhzhDh8+zL344otceHg4t2/fPo7j6FzJsWXLFi4tLY3r2rUr9+STT9qW07myM2XKFO7SSy/lLly4YPvLzc21fU/nyk5BQQHXqlUrbuzYsdzmzZu5EydOcP/88w937NgxW5tAuseTuPEAffv25R577DHbZ4vFwjVr1oybOnWqH3vlX6Tixmq1csnJydy7775rW1ZYWMiZzWbuxx9/5DiO4w4cOMAB4LZu3Wpr8/fff3MGg4E7d+6cz/ruD3JycjgA3Jo1aziO489NeHg498svv9jaHDx4kAPAbdy4keM4XkwajUYuKyvL1mb27NlcbGwsV1VV5dsD8AONGjXivvjiCzpXMpSUlHDt2rXjli9fzl111VU2cUPnSsyUKVO4bt26yX5H50rMf//7X27gwIGK3wfaPZ7cUm5SXV2N7du3Iz093bbMaDQiPT0dGzdu9GPPAouTJ08iKytLdJ7i4uLQr18/23nauHEj4uPj0bt3b1ub9PR0GI1GbN682ed99iVFRUUAgISEBADA9u3bUVNTIzpfHTt2RMuWLUXnq0uXLkhKSrK1GTJkCIqLi7F//34f9t63WCwW/PTTTygrK0P//v3pXMnw2GOPYdiwYaJzAtB1JcfRo0fRrFkztGnTBqNGjUJmZiYAOldS/vjjD/Tu3Rt33nknmjZtih49euDzzz+3fR9o93gSN26Sl5cHi8UiurgBICkpCVlZWX7qVeAhnAu185SVlYWmTZuKvg8LC0NCQkJIn0ur1YqnnnoKV1xxBS677DIA/LmIiIhAfHy8qK30fMmdT+G7UGPv3r2IiYmB2WzGww8/jIULF6Jz5850riT89NNP2LFjB6ZOnerwHZ0rMf369cO8efOwdOlSzJ49GydPnsSgQYNQUlJC50rCiRMnMHv2bLRr1w7//PMPHnnkETzxxBP4+uuvAQTePb7ezQpOEIHGY489hn379mH9+vX+7kpA06FDB+zatQtFRUVYsGABxowZgzVr1vi7WwHFmTNn8OSTT2L58uWIjIz0d3cCnhtuuMH2vmvXrujXrx9atWqFn3/+GVFRUX7sWeBhtVrRu3dvvPXWWwCAHj16YN++fZgzZw7GjBnj5945QpYbN0lMTITJZHKIoM/OzkZycrKfehV4COdC7TwlJycjJydH9H1tbS0KCgpC9lxOmDABixcvxqpVq9CiRQvb8uTkZFRXV6OwsFDUXnq+5M6n8F2oERERgbZt26JXr16YOnUqunXrhg8//JDOFcP27duRk5ODnj17IiwsDGFhYVizZg0++ugjhIWFISkpic6VCvHx8Wjfvj2OHTtG15WElJQUdO7cWbSsU6dONjdeoN3jSdy4SUREBHr16oWMjAzbMqvVioyMDPTv39+PPQssWrdujeTkZNF5Ki4uxubNm23nqX///igsLMT27dttbVauXAmr1Yp+/fr5vM/ehOM4TJgwAQsXLsTKlSvRunVr0fe9evVCeHi46HwdPnwYmZmZovO1d+9e0c1i+fLliI2NdbgJhSJWqxVVVVV0rhgGDx6MvXv3YteuXba/3r17Y9SoUbb3dK6UKS0txfHjx5GSkkLXlYQrrrjCoVzFkSNH0KpVKwABeI/3aHhyPeWnn37izGYzN2/ePO7AgQPcQw89xMXHx4si6OsDJSUl3M6dO7mdO3dyALjp06dzO3fu5E6fPs1xHJ8mGB8fz/3+++/cnj17uBEjRsimCfbo0YPbvHkzt379eq5du3YhmQr+yCOPcHFxcdzq1atFaajl5eW2Ng8//DDXsmVLbuXKldy2bdu4/v37c/3797d9L6ShXn/99dyuXbu4pUuXck2aNAnJNNRJkyZxa9as4U6ePMnt2bOHmzRpEmcwGLhly5ZxHEfnSg02W4rj6FyxPPPMM9zq1au5kydPchs2bODS09O5xMRELicnh+M4OlcsW7Zs4cLCwrg333yTO3r0KPf9999z0dHR3HfffWdrE0j3eBI3HuLjjz/mWrZsyUVERHB9+/blNm3a5O8u+ZxVq1ZxABz+xowZw3Ecnyr48ssvc0lJSZzZbOYGDx7MHT58WLSN/Px87p577uFiYmK42NhYbty4cVxJSYkfjsa7yJ0nANxXX31la1NRUcE9+uijXKNGjbjo6Gju1ltv5S5cuCDazqlTp7gbbriBi4qK4hITE7lnnnmGq6mp8fHReJ/777+fa9WqFRcREcE1adKEGzx4sE3YcBydKzWk4obOlZ2RI0dyKSkpXEREBNe8eXNu5MiRorotdK7E/Pnnn9xll13Gmc1mrmPHjtxnn30m+j6Q7vEGjuM4z9qCCIIgCIIg/AfF3BAEQRAEEVKQuCEIgiAIIqQgcUMQBEEQREhB4oYgCIIgiJCCxA1BEARBECEFiRuCIAiCIEIKEjcEQRAEQYQUJG4Igqj3rF69GgaDwWEeIYIgghMSNwRBEARBhBQkbgiCIAiCCClI3BAE4XesViumTp2K1q1bIyoqCt26dcOCBQsA2F1GS5YsQdeuXREZGYnLL78c+/btE23j119/xaWXXgqz2Yy0tDS8//77ou+rqqrw3//+F6mpqTCbzWjbti2+/PJLUZvt27ejd+/eiI6OxoABAxxmQSYIIjggcUMQhN+ZOnUqvvnmG8yZMwf79+/H008/jfvuuw9r1qyxtXnuuefw/vvvY+vWrWjSpAmGDx+OmpoaALwoueuuu3D33Xdj7969eOWVV/Dyyy9j3rx5tvVHjx6NH3/8ER999BEOHjyITz/9FDExMaJ+vPTSS3j//fexbds2hIWF4f777/fJ8RME4Vlo4kyCIPxKVVUVEhISsGLFCvTv39+2/MEHH0R5eTkeeughXHPNNfjpp58wcuRIAEBBQQFatGiBefPm4a677sKoUaOQm5uLZcuW2dZ//vnnsWTJEuzfvx9HjhxBhw4dsHz5cqSnpzv0YfXq1bjmmmuwYsUKDB48GADw119/YdiwYaioqEBkZKSXzwJBEJ6ELDcEQfiVY8eOoby8HNdddx1iYmJsf9988w2OHz9ua8cKn4SEBHTo0AEHDx4EABw8eBBXXHGFaLtXXHEFjh49CovFgl27dsFkMuGqq65S7UvXrl1t71NSUgAAOTk5bh8jQRC+JczfHSAIon5TWloKAFiyZAmaN28u+s5sNosEjqtERUVpahceHm57bzAYAPDxQARBBBdkuSEIwq907twZZrMZmZmZaNu2regvNTXV1m7Tpk229xcvXsSRI0fQqVMnAECnTp2wYcMG0XY3bNiA9u3bw2QyoUuXLrBaraIYHoIgQhey3BAE4VcaNmyIZ599Fk8//TSsVisGDhyIoqIibNiwAbGxsWjVqhUA4LXXXkPjxo2RlJSEl156CYmJibjlllsAAM888wz69OmD119/HSNHjsTGjRsxc+ZMfPLJJwCAtLQ0jBkzBvfffz8++ugjdOvWDadPn0ZOTg7uuusufx06QRBegsQNQRB+5/XXX0eTJk0wdepUnDhxAvHx8ejZsydefPFFm1to2rRpePLJJ3H06FF0794df/75JyIiIgAAPXv2xM8//4zJkyfj9ddfR0pKCl577TWMHTvWto/Zs2fjxRdfxKOPPor8/Hy0bNkSL774oj8OlyAIL0PZUgRBBDRCJtPFixcRHx/v7+4QBBEEUMwNQRAEQRAhBYkbgiAIgiBCCnJLEQRBEAQRUpDlhiAIgiCIkILEDUEQBEEQIQWJG4IgCIIgQgoSNwRBEARBhBQkbgiCIAiCCClI3BAEQRAEEVKQuCEIgiAIIqQgcUMQBEEQREhB4oYgCIIgiJDi/wFSYbN/etABKgAAAABJRU5ErkJggg==", - "text/plain": [ - "

" - ] - }, - "metadata": {}, - "output_type": "display_data" - } - ], - "source": [ - "# Save model\n", - "import time\n", - "\n", - "hypermodel.save(f\"./models/spam_keras_{time.time()}\")\n", - "\n", - "# summarize history for accuracy\n", - "plt.plot(history.history['loss'])\n", - "plt.plot(history.history['val_loss'])\n", - "plt.title('model loss')\n", - "plt.ylabel('loss')\n", - "plt.xlabel('epoch')\n", - "plt.legend(['train', 'val'], loc='upper left')\n", - "plt.show()" - ] - }, - { - "cell_type": "code", - "execution_count": 10, - "metadata": {}, - "outputs": [ - { - "data": { - "image/png": "iVBORw0KGgoAAAANSUhEUgAAAkAAAAHHCAYAAABXx+fLAAAAOXRFWHRTb2Z0d2FyZQBNYXRwbG90bGliIHZlcnNpb24zLjYuMCwgaHR0cHM6Ly9tYXRwbG90bGliLm9yZy89olMNAAAACXBIWXMAAA9hAAAPYQGoP6dpAABcR0lEQVR4nO3dd3hUZd7/8ffMpHdCQgoEQ29CkCILWDEaEEFUEFh9ENfye1wbi6wL7goqrmBdVkH0cVfRVVcEEbGABSmKIE2qSJOWkEKAdNJm5vfHJBMiLWWSk5n5vK5rLoYzZ858J+uaj/d9n+9tstvtdkRERES8iNnoAkREREQamwKQiIiIeB0FIBEREfE6CkAiIiLidRSARERExOsoAImIiIjXUQASERERr6MAJCIiIl5HAUhERES8jgKQiLi9gwcPYjKZmDdvXq3fu3LlSkwmEytXrjzvefPmzcNkMnHw4ME61SgiTYsCkIiIiHgdBSARERHxOgpAIiIi4nUUgESk3p544glMJhN79uzh9ttvJzw8nOjoaB5//HHsdjtHjhzhxhtvJCwsjNjYWF588cUzrpGVlcVdd91FTEwMAQEBJCUl8fbbb59xXk5ODuPHjyc8PJyIiAjuuOMOcnJyzlrXL7/8wsiRI4mMjCQgIIA+ffqwZMkSl373V199lW7duuHv7098fDz333//GfXs3buXW265hdjYWAICAmjVqhVjxowhNzfXec7XX3/NZZddRkREBCEhIXTq1InHHnvMpbWKSBUfowsQEc8xevRounTpwsyZM/n88895+umniYyM5PXXX2fQoEE8++yzvPfee0yaNIm+fftyxRVXAHDq1Cmuuuoq9u3bxwMPPECbNm1YsGAB48ePJycnh4cffhgAu93OjTfeyPfff8///u//0qVLFz7++GPuuOOOM2rZuXMnAwcOpGXLlkyePJng4GA+/PBDRowYwUcffcRNN91U7+/7xBNP8OSTT5KcnMx9993H7t27mTt3Lhs2bGDNmjX4+vpSWlpKSkoKJSUlPPjgg8TGxpKWlsZnn31GTk4O4eHh7Ny5kxtuuIEePXrw1FNP4e/vz759+1izZk29axSRc7CLiNTTtGnT7ID93nvvdR4rLy+3t2rVym4ymewzZ850Hj958qQ9MDDQfscddziPzZo1yw7Y3333Xeex0tJSe//+/e0hISH2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8vPzef/991m5ciVffvml0aV5lNDQ0DPWrwUHB9O8eXOta3OhSZMmMWzYMC666CKOHj3KtGnTsFgsjB071pB6FIAayejRozl27BhTp04lIyODnj17smzZsjMWRkv9bNy4kauvvtr594kTJwJwxx13MG/ePIOq8jxz584F4Kqrrqp2/K233mL8+PGNX5CHysrKYty4caSnpxMeHk6PHj348ssvufbaa40uTaTWUlNTGTt2LMePHyc6OprLLruMdevWER0dbUg96gMkIiIiXkdrgERERMTrKACJiIiI11EAEhEREa+jACQiIiJeRwFIREREvI4CkIiIiHgdBSARERHxOgpAIiI1sHLlSkwmEzk5OUaXIiIuoAAkIiIiXkcBSERERLyOApCIuAWbzcaMGTNo06YNgYGBJCUlsXDhQqBqeurzzz+nR48eBAQE8Lvf/Y4dO3ZUu8ZHH31Et27d8Pf3JzExkRdffLHa6yUlJfzlL38hISEBf39/2rdvz7///e9q52zatIk+ffoQFBTEgAED2L17d8N+cRFpEApAIuIWZsyYwTvvvMNrr73Gzp07+dOf/sTtt9/OqlWrnOf8+c9/5sUXX2TDhg1ER0czbNgwysrKAEdwufXWWxkzZgzbt2/niSee4PHHH6+2Se64ceP473//y8svv8yuXbt4/fXXCQkJqVbHX//6V1588UU2btyIj48Pf/jDHxrl+4uIa2kzVBFp8kpKSoiMjOSbb76hf//+zuN33303RUVF3HvvvVx99dV88MEHjB49GoATJ07QqlUr5s2bx6233sptt93GsWPH+Oqrr5zvf/TRR/n888/ZuXMne/bsoVOnTnz99dckJyefUcPKlSu5+uqr+eabb7jmmmsA+OKLLxg6dCinTp0iICCggX8KIuJKGgESkSZv3759FBUVce211xISEuJ8vPPOO+zfv9953unhKDIykk6dOrFr1y4Adu3axcCBA6tdd+DAgezduxer1cqWLVuwWCxceeWV562lR48ezudxcXEAZGVl1fs7ikjj8jG6ABGRCykoKADg888/p2XLltVe8/f3rxaC6iowMLBG5/n6+jqfm0wmwLE+SUTci0aARKTJ69q1K/7+/hw+fJj27dtXeyQkJDjPW7dunfP5yZMn2bNnD126dAGgS5curFmzptp116xZQ8eOHbFYLHTv3h2bzVZtTZGIeC6NAIlIkxcaGsqkSZP405/+hM1m47LLLiM3N5c1a9YQFhbGRRddBMBTTz1F8+bNiYmJ4a9//StRUVGMGDECgEceeYS+ffsyffp0Ro8ezdq1a5k9ezavvvoqAImJidxxxx384Q9/4OWXXyYpKYlDhw6RlZXFrbfeatRXF5EGogAkIm5h+vTpREdHM2PGDH799VciIiLo1asXjz32mHMKaubMmTz88MPs3buXnj178umnn+Ln5wdAr169+PDDD5k6dSrTp08nLi6Op556ivHjxzs/Y+7cuTz22GP88Y9/5Pjx47Ru3ZrHHnvMiK8rIg1Md4GJiNurvEPr5MmTREREGF2OiLgBrQESERERr6MAJCIiIl5HU2AiIiLidTQCJCIiIl5HAUhERES8jgKQiIiIeB0FIBEREfE6CkAiIiLidRSARERExOsoAImIiIjXUQASERERr6MAJCIiIl7n/wMPWSpIFmYi3QAAAABJRU5ErkJggg==", - "text/plain": [ - "
" - ] - }, - "metadata": {}, - "output_type": "display_data" - } - ], - "source": [ - "# summarize history for accuracy\n", - "plt.plot(hypermodel_history.history['loss'])\n", - "plt.plot(hypermodel_history.history['val_loss'])\n", - "plt.title('model loss')\n", - "plt.ylabel('loss')\n", - "plt.xlabel('epoch')\n", - "plt.legend(['train', 'val'], loc='upper left')\n", - "plt.show()\n" - ] - }, - { - "cell_type": "code", - "execution_count": 11, - "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "1/1 [==============================] - 0s 73ms/step\n", - "Message: \"Greg, can you call me back once you get this?\"\n", - "Likeliness of spam in percentage: 0.7976732850074768\n", - "\n", - "\n", - "Message: \"Congrats on your new iPhone! Click here to claim your prize...\"\n", - "Likeliness of spam in percentage: 0.9998524188995361\n", - "\n", - "\n", - "Message: \"Really like that new photo of you\"\n", - "Likeliness of spam in percentage: 0.008655842393636703\n", - "\n", - "\n", - "Message: \"Did you hear the news today? Terrible what has happened...\"\n", - "Likeliness of spam in percentage: 0.0016309074126183987\n", - "\n", - "\n", - "Message: \"Attend this free COVID webinar today: Book your session now...\"\n", - "Likeliness of spam in percentage: 0.9975067973136902\n", - "\n", - "\n", - "Message: \"Are you coming to the party tonight?\"\n", - "Likeliness of spam in percentage: 0.00012246571714058518\n", - "\n", - "\n", - "Message: \"Your parcel has gone missing\"\n", - "Likeliness of spam in percentage: 0.0040820869617164135\n", - "\n", - "\n", - "Message: \"Do not forget to bring friends!\"\n", - "Likeliness of spam in percentage: 0.013192469254136086\n", - "\n", - "\n", - "Message: \"You have won a million dollars! Fill out your bank details here...\"\n", - "Likeliness of spam in percentage: 0.1410737931728363\n", - "\n", - "\n", - "Message: \"Looking forward to seeing you again\"\n", - "Likeliness of spam in percentage: 0.01070433109998703\n", - "\n", - "\n", - "Message: \"oh wow https://github.com/MGCodesandStats/tensorflow-nlp/blob/master/spam%20detection%20tensorflow%20v2.ipynb works really good on spam detection. Guess I go with that as the base model then lol :D\"\n", - "Likeliness of spam in percentage: 0.0005995486862957478\n", - "\n", - "\n", - "Message: \"ayo\"\n", - "Likeliness of spam in percentage: 0.002324814209714532\n", - "\n", - "\n", - "Message: \"Almost all my spam is coming to my non-gmail address actually\"\n", - "Likeliness of spam in percentage: 2.6484692625672324e-06\n", - "\n", - "\n", - "Message: \"Oh neat I think I found the sizing sweetspot for my data :D\"\n", - "Likeliness of spam in percentage: 5.796300683869049e-05\n", - "\n", - "\n", - "Message: \"would never click on buttons in gmail :D always expecting there to be a bug in gmail that allows js to grab your google credentials :D XSS via email lol. I am too scared for touching spam in gmail\"\n", - "Likeliness of spam in percentage: 0.41252583265304565\n", - "\n", - "\n", - "Message: \"back to cacophony \"\n", - "Likeliness of spam in percentage: 0.3257969617843628\n", - "\n", - "\n", - "Message: \"Room version 11 when\"\n", - "Likeliness of spam in percentage: 0.00024024260346777737\n", - "\n", - "\n", - "Message: \"skip 11 and go straight to 12\"\n", - "Likeliness of spam in percentage: 0.038723770529031754\n", - "\n", - "\n", - "Message: \"100 events should clear out any events that might be causing a request to fail lol\"\n", - "Likeliness of spam in percentage: 0.0003453373210504651\n", - "\n", - "\n" - ] - } - ], - "source": [ - "# Use the model to predict whether a message is spam\n", - "text_messages = ['Greg, can you call me back once you get this?',\n", - " 'Congrats on your new iPhone! Click here to claim your prize...',\n", - " 'Really like that new photo of you',\n", - " 'Did you hear the news today? Terrible what has happened...',\n", - " 'Attend this free COVID webinar today: Book your session now...',\n", - " 'Are you coming to the party tonight?',\n", - " 'Your parcel has gone missing',\n", - " 'Do not forget to bring friends!',\n", - " 'You have won a million dollars! Fill out your bank details here...',\n", - " 'Looking forward to seeing you again',\n", - " 'oh wow https://github.com/MGCodesandStats/tensorflow-nlp/blob/master/spam%20detection%20tensorflow%20v2.ipynb works really good on spam detection. Guess I go with that as the base model then lol :D',\n", - " 'ayo',\n", - " 'Almost all my spam is coming to my non-gmail address actually',\n", - " 'Oh neat I think I found the sizing sweetspot for my data :D',\n", - " 'would never click on buttons in gmail :D always expecting there to be a bug in gmail that allows js to grab your google credentials :D XSS via email lol. I am too scared for touching spam in gmail',\n", - " 'back to cacophony ',\n", - " 'Room version 11 when',\n", - " 'skip 11 and go straight to 12',\n", - " '100 events should clear out any events that might be causing a request to fail lol']\n", - "\n", - "#print(text_messages)\n", - "\n", - "# Create the sequences\n", - "padding_type = 'post'\n", - "sample_sequences = tokenizer.texts_to_sequences(text_messages)\n", - "fakes_padded = pad_sequences(\n", - " sample_sequences, padding=padding_type, maxlen=max_length)\n", - "\n", - "classes = hypermodel.predict(fakes_padded)\n", - "\n", - "# The closer the class is to 1, the more likely that the message is spam\n", - "for x in range(len(text_messages)):\n", - " print(f\"Message: \\\"{text_messages[x]}\\\"\")\n", - " print(f\"Likeliness of spam in percentage: {classes[x][0]}\")\n", - " print('\\n')\n" - ] - }, - { - "cell_type": "code", - "execution_count": 12, - "metadata": {}, - "outputs": [ - { - "data": { - "image/png": "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qW1f9zVw9gbsmjVrbLZnZmYKAJsbg5wd96effpLo6GjdfOAi4pvzFfm5z3fs2GGz3VGfi5jrZjyjvEZqb8a79dZb7bbfeeedEh4eLqWlpdZtn3zyiQCQBx980Kbt5s2bJSkpSS5dumTd5mlNWrduncuYneHizzXOFZwr/EGLm/Ec1bebb77Z5c14zGX957I7grHee/LaO7oJ4Z577hEA8u6779psv3jxorRs2VKioqLk5MmTNvtwN+fVrCmcxecONcfZsGGD07XLNddcI1VVVdZtvl67qIlTbf+qjdUdatavavLIk5zz5OaZQNQwV3OtJ7VZLa1uxmvevHm9N+PV5k/t39XztJHrUffu3WXRokU220aNGiUAZPbs2TbbWXOucPSfJxRFkYSEBBk5cqRs27bN2pb1xDl3rusvXLjg8IMmdwTjesoZb9bWzsakpzXO1/OwiPYf5hUVFUnXrl0lOztbqqurfbbfurS4Gc/Z+GNueEbrm9m8pXX8auuYp+sQd+bIlJQUuemmm+xibN++vc2HeP64pqkrELVHLa0+7K3L0/oWDDnjLT30rzcCET/XXLZ8Pa9WVlbKnj175He/+52EhobK008/7bN9uxKo/OcajTfj+YoW8XO9Zk/LulVLq/mbazLfqu9mPJ/8TO3q1asREhKC4cOH22xv3rw5rr/+emzfvh1Hjx61e16PHj1s/t2yZUuH25OSknD8+HGHx27QoAF69epls61z585o2bIldu7ciRMnTqiO8eOPPwYApKen28XXvn17uxjUtndHz549bf6dnJwMADavg7PjNm3aFB07dvTouFpx53xrxcTEoGvXrjbbHPW52ZjxNRo+fLjDn/RJTU1FVVUVdu/ebd02ZMgQdO7cGW+//TaKi4ut2//2t7/h4YcfRnh4uHWbpzXpxhtv9MVpkROcKzhXBDvmsnPMZX3z1Wu/atUqAMCwYcNstkdGRmLQoEGoqKjAJ598YvOYuzmvZk3hDTXHGTRoELp16+Zw7TJ16lSbn23w9dpFTZxq+9fTWF1xd/2qJo88yTlP6KGG+aM260VSUhIAWH+65WoFBQUQEeTn57u9TyPUo127dmH//v341a9+ZbO99qdqFy5caLOdNcdWRkYGRAQigpqaGhQVFeH999+3Ga+sJ96pzfnw8HCbnysidbyp387GpCf56o95WGvnz59Heno6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xoyxudPdFb3Er1ccO/7F/PM95qz/6CVfv/rqKxQXF+O2225T9U2neomfAos1gTzBekHuYo0htfRSX3bs2IEjR45g6NChqr5lSi/xe4tj9wqz9KenmAf6+jxk7ty5GD58OJKTk91+jp7i9yXmpr1gr1d1MT9s6eXztPfeew8pKSno3r27qufpJX5/Yb7Wz+z97ynmjXN6mQ/Pnj2L//znPxg9ejQaNWrk9vP0Er+3mKNXmKU/PcU8qPd6bEWYFgEREZF/hISE4OGHH9Y6DCIi8rNevXppHQIRERGRoXXr1g3dunXTOgwiIqsHHnhA6xCIiDz2q1/9SusQiIgCplGjRpgwYYLWYRCRjoVoHQARERERERERERERERERERERERGR0fFmPBVmz54NRVGwc+dOHDt2DIqi4Mknn9Q6LCKXmLtEnuHYIaNhzhJRXawJRORPrDFExsSxSwDzgPSLuUn1YX6QkTBfyRPMG9I75igBzAN38WdqVZg+fTqmT5+udRhEqjF3iTzDsUNGw5wlorpYE4jIn1hjiIyJY5cA5gHpF3OT6sP8ICNhvpInmDekd8xRApgH7uI34xERERERERERERERERERERERERF5iTfjEREREREREREREREREREREREREXmJN+MREREREREREREREREREREREREReYk34xERERERERERERERERERERERERF5KczZA8uXLw9kHER2cnNztQ6ByG1Hjx5Fq1attA7DK0ePHmXtJ00cPXoUANceZAys90QULDg/E5G/cD1FesL5jszyHjRz2PxYr8jMzFKLyTPsf1KD15OkJ1yfUX1zmCIiUnfD8uXLkZ2d7fegiIjMJjMzEytWrNA6DI9kZWVh5cqVWodBRGQIrPdERERE3uF6ioj05qqPSQyDn+cQEQDk5ORgzJgxWofhEUVRtA6BiAyG15NEpDcOridXOP1mPKNefJJ5ZWVlAYBhJ1cyt9r8NDIjL15JX1ivycxY74mItMH1BZF5cD1FRqEoiqFvbiD3mOVmNn6eY35cD5MzZriZjfMtXa12fub8Rlfj9SSZFa8/jam+68mQAMdCREREREREREREREREREREREREZDq8GY+IiIiIiIiIiIiIiIiIiIiIiIjIS7wZj4iIiIiIiIiIiIiIiIiIiIiIiMhLvBmPiIiIiIiIiIiIiIiIiIiIiIiIyEu8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", - "text/plain": [ - "" - ] - }, - "execution_count": 12, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "tf.keras.utils.plot_model(model, rankdir=\"LR\", show_shapes=True)\n" - ] - } - ], - "metadata": { - "kernelspec": { - "display_name": "Python 3.10.6 64-bit", - "language": "python", - "name": "python3" - }, - "language_info": { - "codemirror_mode": { - "name": "ipython", - "version": 3 - }, - "file_extension": ".py", - "mimetype": "text/x-python", - "name": "python", - "nbconvert_exporter": "python", - "pygments_lexer": "ipython3", - "version": "3.10.6" - }, - "orig_nbformat": 4, - "vscode": { - "interpreter": { - "hash": "916dbcbb3f70747c44a77c7bcd40155683ae19c65e1c03b4aa3499c5328201f1" - } - } - }, - "nbformat": 4, - "nbformat_minor": 2 -}