From 95a0d5efc00f6096fcb25d7c87d2e46e48be1458 Mon Sep 17 00:00:00 2001 From: Shawn-Shan Date: Sun, 28 Jun 2020 23:34:48 -0500 Subject: [PATCH] upload to pypi --- fawkes/LICENSE | 19 + fawkes/README.md | 57 +++ fawkes/{ => build/lib/fawkes}/__init__.py | 0 .../{ => build/lib/fawkes}/differentiator.py | 6 +- fawkes/{ => build/lib/fawkes}/protection.py | 10 +- fawkes/{ => build/lib/fawkes}/utils.py | 1 - fawkes/dist/fawkes-0.0.1-py3-none-any.whl | Bin 0 -> 13487 bytes fawkes/dist/fawkes-0.0.1.tar.gz | Bin 0 -> 13106 bytes fawkes/fawkes.egg-info/PKG-INFO | 71 +++ fawkes/fawkes.egg-info/SOURCES.txt | 10 + fawkes/fawkes.egg-info/dependency_links.txt | 1 + fawkes/fawkes.egg-info/top_level.txt | 1 + fawkes/fawkes/__init__.py | 0 fawkes/fawkes/differentiator.py | 421 ++++++++++++++++ fawkes/fawkes/protection.py | 110 +++++ fawkes/fawkes/utils.py | 459 ++++++++++++++++++ fawkes/setup.py | 23 + fawkes_dev/config.py | 8 +- fawkes_dev/protection.py | 2 +- 19 files changed, 1183 insertions(+), 16 deletions(-) create mode 100644 fawkes/LICENSE create mode 100644 fawkes/README.md rename fawkes/{ => build/lib/fawkes}/__init__.py (100%) rename fawkes/{ => build/lib/fawkes}/differentiator.py (99%) rename fawkes/{ => build/lib/fawkes}/protection.py (94%) rename fawkes/{ => build/lib/fawkes}/utils.py (99%) create mode 100644 fawkes/dist/fawkes-0.0.1-py3-none-any.whl create mode 100644 fawkes/dist/fawkes-0.0.1.tar.gz create mode 100644 fawkes/fawkes.egg-info/PKG-INFO create mode 100644 fawkes/fawkes.egg-info/SOURCES.txt create mode 100644 fawkes/fawkes.egg-info/dependency_links.txt create mode 100644 fawkes/fawkes.egg-info/top_level.txt create mode 100644 fawkes/fawkes/__init__.py create mode 100644 fawkes/fawkes/differentiator.py create mode 100644 fawkes/fawkes/protection.py create mode 100644 fawkes/fawkes/utils.py create mode 100644 fawkes/setup.py diff --git a/fawkes/LICENSE b/fawkes/LICENSE new file mode 100644 index 0000000..335ea9d --- /dev/null +++ b/fawkes/LICENSE @@ -0,0 +1,19 @@ +Copyright (c) 2018 The Python Packaging Authority + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. \ No newline at end of file diff --git a/fawkes/README.md b/fawkes/README.md new file mode 100644 index 0000000..42f90b4 --- /dev/null +++ b/fawkes/README.md @@ -0,0 +1,57 @@ +# Fawkes +Code implementation of the paper "[Fawkes: Protecting Personal Privacy against Unauthorized Deep Learning Models](https://arxiv.org/pdf/2002.08327.pdf)", at *USENIX Security 2020*. + +### BEFORE YOU RUN OUR CODE +We appreciate your interest in our work and for trying out our code. We've noticed several cases where incorrect configuration leads to poor performances of protection. If you also observe low detection performance far away from what we presented in the paper, please feel free to open an issue in this repo or contact any of the authors directly. We are more than happy to help you debug your experiment and find out the correct configuration. + +### ABOUT + +This repository contains code implementation of the paper "[Fawkes: Protecting Personal Privacy against Unauthorized Deep Learning Models](https://arxiv.org/pdf/2002.08327.pdf)", at *USENIX Security 2020*. + +### DEPENDENCIES + +Our code is implemented and tested on Keras with TensorFlow backend. Following packages are used by our code. + +- `keras==2.3.1` +- `numpy==1.18.4` +- `tensorflow-gpu==1.13.1` + +Our code is tested on `Python 3.6.8` + +### HOWTO + +#### Download and Config Datasets +The first step is to download several datasets for protection and target selection. +1. Download the following dataset to your local machine. After downloading the datasets, restructure it the same way as the FaceScrub dataset downloaded. + - FaceScrub -- used for protection evaluation (link) + - VGGFace1 -- used for target select (link) + - VGGFace2 -- used for target select (link) + - WebFace -- used for target select (link) + +2. Config datasets +open `fawkes/config.py` and update the `DATASETS` dictionary with the path to each dataset. Then run `python fawkes/config.py`. Every time the datasets are updated or moved, remember to rerun the command with the updated path. + +3. Calculate embeddings using feature extractor. +Run `python3 fawkes/prepare_feature_extractor.py --candidate-datasets scrub vggface1 vggface2 webface`. This will calculate and cache the embeddings using the default feature extractor we provide. To use a customized feature extractor, please look at the Advance section at the end. + +#### Generate Cloak for Images +To generate cloak, run +`python3 fawkes/protection.py --gpu 0 --dataset scrub --feature-extractor webface_dense_robust_extract` +For more information about the detailed parameters, please read `fawkes/protection.py`. +The code will output a directory in `results/` with `cloak_data.p` inside. You can check the cloaked images or inspect the changes in `this notebook`. + +#### Evaluate Cloak Effectiveness +To evaluate the cloak, run `python3 fawkes/eval_cloak.py --gpu 0 --cloak_data PATH-TO-RESULT-DIRECTORY --transfer_model vggface2_inception_extract`. + +The code will print out the tracker model accuracy on uncloaked/original test images of the protected user, which should be close to 0. + + +### Citation +``` +@inproceedings{shan2020fawkes, + title={Fawkes: Protecting Personal Privacy against Unauthorized Deep Learning Models}, + author={Shan, Shawn and Wenger, Emily and Zhang, Jiayun and Li, Huiying and Zheng, Haitao and Zhao, Ben Y}, + booktitle="Proc. of USENIX Security", + year={2020} +} +``` \ No newline at end of file diff --git a/fawkes/__init__.py b/fawkes/build/lib/fawkes/__init__.py similarity index 100% rename from fawkes/__init__.py rename to fawkes/build/lib/fawkes/__init__.py diff --git a/fawkes/differentiator.py b/fawkes/build/lib/fawkes/differentiator.py similarity index 99% rename from fawkes/differentiator.py rename to fawkes/build/lib/fawkes/differentiator.py index 4031f35..98b85d6 100644 --- a/fawkes/differentiator.py +++ b/fawkes/build/lib/fawkes/differentiator.py @@ -211,8 +211,8 @@ class FawkesMaskGeneration: # we're creating start_vars = set(x.name for x in tf.global_variables()) self.learning_rate_holder = tf.placeholder(tf.float32, shape=[]) - # optimizer = tf.train.AdadeltaOptimizer(self.learning_rate_holder) - optimizer = tf.train.AdamOptimizer(self.learning_rate_holder) + optimizer = tf.train.AdadeltaOptimizer(self.learning_rate_holder) + # optimizer = tf.train.AdamOptimizer(self.learning_rate_holder) self.train = optimizer.minimize(self.loss_sum, var_list=[self.modifier]) @@ -250,8 +250,6 @@ class FawkesMaskGeneration: imgs = reverse_preprocess(imgs, self.intensity_range) imgs = np.clip(imgs, 0, self.max_val) - imgs = np.rint(imgs) - imgs = preprocess(imgs, self.intensity_range) return imgs diff --git a/fawkes/protection.py b/fawkes/build/lib/fawkes/protection.py similarity index 94% rename from fawkes/protection.py rename to fawkes/build/lib/fawkes/protection.py index 228b93a..a7afd55 100644 --- a/fawkes/protection.py +++ b/fawkes/build/lib/fawkes/protection.py @@ -16,12 +16,13 @@ random.seed(12243) np.random.seed(122412) set_random_seed(12242) -BATCH_SIZE = 32 +BATCH_SIZE = 1 MAX_ITER = 1000 def generate_cloak_images(sess, feature_extractors, image_X, target_X=None, th=0.01): batch_size = BATCH_SIZE if len(image_X) > BATCH_SIZE else len(image_X) + differentiator = FawkesMaskGeneration(sess, feature_extractors, batch_size=batch_size, mimic_img=True, @@ -66,8 +67,6 @@ def fawkes(): tar_img = select_target_label(org_img, feature_extractors_ls, [args.feature_extractor]) target_images.append(tar_img) target_images = np.concatenate(target_images) - # import pdb - # pdb.set_trace() else: target_images = select_target_label(orginal_images, feature_extractors_ls, [args.feature_extractor]) @@ -80,7 +79,6 @@ def fawkes(): for p_img, path in zip(protected_images, image_paths): p_img = reverse_process_cloaked(p_img) - # img_type = path.split(".")[-1] file_name = "{}_cloaked.jpeg".format(".".join(path.split(".")[:-1])) dump_image(p_img, file_name, format="JPEG") @@ -97,9 +95,9 @@ def parse_arguments(argv): default="webface_dense_robust_extract") parser.add_argument('--th', type=float, default=0.005) - parser.add_argument('--sd', type=int, default=1e10) + parser.add_argument('--sd', type=int, default=1e9) parser.add_argument('--protect_class', type=str, default=None) - parser.add_argument('--lr', type=float, default=0.1) + parser.add_argument('--lr', type=float, default=1) parser.add_argument('--result_directory', type=str, default="../results") parser.add_argument('--seperate_target', action='store_true') diff --git a/fawkes/utils.py b/fawkes/build/lib/fawkes/utils.py similarity index 99% rename from fawkes/utils.py rename to fawkes/build/lib/fawkes/utils.py index f153913..c0c4577 100644 --- a/fawkes/utils.py +++ b/fawkes/build/lib/fawkes/utils.py @@ -297,7 +297,6 @@ def select_target_label(imgs, feature_extractors_ls, feature_extractors_names, m if cur_tot_sum > highest_num: highest_num = cur_tot_sum paired_target_X = cur_paired_target_X - final_target_class_path = target_class_path np.random.shuffle(paired_target_X) paired_target_X = list(paired_target_X) diff --git a/fawkes/dist/fawkes-0.0.1-py3-none-any.whl b/fawkes/dist/fawkes-0.0.1-py3-none-any.whl new file mode 100644 index 0000000000000000000000000000000000000000..a45d1ca22686a47adc867e3ab2c991d4c462db80 GIT binary patch literal 13487 zcmZ|0V|Z=L)~+4fwr$(CZQHh;%vdwFlNsB#of+G9X7Xj9^X+%-cb&cZxT;tG)9$PK z7&V@1tyL=pXP)Y%Z)s=gqOVWq;0X+% z@IMwUz?%w{ibZgb@Bjdxq5uFGe_5DVnwgn8ncBHn8oJm!ecN4PTRU&C-F5hbV#SS^ z5G~X%JmlwaJ+jCZlwZ0T$zSKsIS?S#bCyWp3ck(BeZ5}>5!@1#jgWS=!5@YXg1Do< z4;ukq`a9~}?s?>xjL>)!R36{a%{)Dg%}o#^9Xcj%#!9Ht#dh~0Vtnn=(qaahi^zqS z4ixTY7AF02{HOw&1RD8uePSH;Wj45fLoc6`$kXwMa8D3*lN7OQ3K&hD&q$YK%~$ra zhDP1T#pmPY1;e2zpiD5CKJNJ`@sNsM3Ta@`wBBT@GFj;m76Wr6}cLR%c<{ zbBw-{-&c-lRURn>_quTYlVm-?Aue$vjirk`QhJ#OSV|%j`Gm<&k&whhw3ez6&Qn_A zJOL>tHB?VAJPnnjPy!duwyX~fIlAp6#x_bSvhrXJXSH}A!9*pw$%Ka})aXu)#Eqa~ z$O!?*81Wl_+4Bqh-Gif#{k5rs<4S7+52g@tC7FbuK|HgKMmQI_fs4J%K~CualBEQk z`HCv2Ct#ras#;EgK8}7*w^mM%d?4e;5~;Y^QWa%EB+}y`G$1UVl7XcX4jBPj0$<5A zi9u-x%|cT|%Li6Y%$yw%{vc%b@mWPpQaKo_oWk#TReM{aZ zy9yMn#)pOz5Dq?RrHp`o;yDE63b5XZI$v*YKi&pr(Cq=8F<|tivqreVKF|+x>QdrO z5=;ohX}PdqMvh?8e}#jr%O4;2gj|!Uo1+U{;L3U$*OU4}33l|Gjp|q@>Vuw~X0jvQ zfSHdg-^f3tfoqYtz>SkP8Wio(klmZ#J-vB3aS{+Hf)YW338pb>&xXbM?y!k2+#awu z0t&+19&R20Wf64dd`M%Q4}$gRDzSgOPcVx>V2R;tI(e(#Wl;9EcJf&z)cEssaKL~0 z@p*pjg6UpT&1gmT6%Q>+aVJeQJRk2qhBxRt{O0EU<0LT1|I7DQx3wz0!)*ncz!AAS z8{(^MEW%L%08-ecmEnOy8a6 zVFyAJ2Op*Y$}ACZM+gR}@xVQ9tA#Uao-s23xU5MC5z}zkHg`l*4nEWjzP28GXJ~a= z9Bb*-F0_v$&89s84+Y8g&?{$&#iK@*cie`@E6VGlmq!>vPJDElXDV5bo1{sa?nd$>P^(L2>O z1pm-YcnkOFNG+dXJ<4!jvIzbi3D^yg6K5oahnzI1FQq_ZZ7~?3bS%S)uc=V}K%e+l zQNvSk&bampv*3k{l}D!Nl-yCtub;hPg@9GJc0p9`;2e)e3(T_2%|A?otYagE%Gr^t z#^U#f`KM;a#Q}x7(go&E0fau6ehP88@P@xE7^$wjctJzR0!O@T7tFVFBwFaxhy&J_ z!;k_FOUp;o`OhrBR#0i5)N2(_W@?dTCaCMKvk}sw`(ZVCdD^2W+pYtbhvvGY95aiXIQS+-tDoeLzUE)b7xnU zy3GS#Jo;m2?oYb~x4ph{kuCZ)x1f_jmYyr(qW9v=2q&v(u(vBV5Ey?wY6W#sweFf{ zs`Mh!9E=DWM~p)EDBnSzU-cN!q`y(|VT0LUOev!MPPZBDA{i@`Qfy|6Vs(V)Fq+yD zG_xj|(F4`hleT8naLnmmlD&0Rz=$)bdjH zkPG`2Y_IRn9RPc)-nXK^k6-#L_*eJOzJ(Aq>|$V`A!2;^4O0o|0oe%&u{0Z>2rls) zIA8Jp!MLX12Gqn+C4vYrqZTeMOuJ5^07jri&4;ZqU|y+YrEc)|H(Pv?gcJ3bbFJ`E<#2XR zz@&oRsOg0!SxSEUdyw}b&;XinJ`wTya^V~0WLaT?=l%ghR{YeR3)0j9V3j5S?lCY` zH3^EqyHEEP@kZ1(5Rx^+uTkZS9hWgZU#k7=WvSJJWDhwEaS5!ZZPzMxggc01$>GJlTjVZS}hpj<28HrMZtXw{mXEsc_3OVnp(elyN zzFMhI;3PtS2H6*mIwt;E!*3p0 z_>XB4$HLs{BxX99p|T;OY#~)KDfE7k0pip5`s>L>pr{%`#kzb=b+6@BWu5E*NNo$M zY^oZtm64#MX}EHIrw44R4W4X%R=+tik$ByY1ktTXVcFn`}=o?oPbEyRbJIIjgMK zipcc`ulJSa_D~_-x+c57s>Aadxo>TliC6X=Dzh^bx?EG%)t{vcRb{9jbf|E)1dUSa zR|CcvR55cPee?tJHWct;P!2ttVspscX$`S;H$L{=12I>c4sZc~nPZOn@*vqwH}m!y zhmlHE@gHPJKJJL&H|Qil8CCr363&$w0}J91OUSqF3&x zn-D$qkk_s!Hrj-j^V%$AtC$Ol{)D*O7Yge@>7Z-W>ZB!{c?Ao<(;|-72|7V>me1+~ zWII;Mx7_A3OCo4VJHnju-P#(`h32U(y^$0n_=J5k^mnRZR>K=d&aooj2#Bfj76Uee zn7EP&NH3bA*1e^!DSNv*DG+i*I(!j-m%L_`7)mcSOjzynP*!DnV7GhimAO`=I7#6J zLe!IJ&Pu#H-_M@hO_3 z$t${>(>pN9h}R2;iox_=SPy#977O{Y$@R4sTn%N49Gol&RNVr zT;3h4TMpm~Aul{u144&oFnsDIy*ev3=yqzgzU!KZfd6%Kf2Txq@y(zNr}pMe2t3gn z847DmHNm9V-Gj`oQjjU&0J-61R(-{4@v$WyO>u*F7@Dv}r>86MxO_gnO6ELm1V+k-NuJ|a%Z>))Z#xFk+C)yNq$Q}ttz7c* z9q7^oih)2!)B@nHL!4%t5{<{~!6U!qliqMv*0!-4{i5R^b>4;%T^9C!oZi@HRTZ4} z*;YxTY~v1W8!m1N+Yt_UR=aANaS)!yWst;$$_vuH!&gk@$|oCT&qVN`HDmJ*&9>dC zCal31y$fhQu8`K9u1Ozi7gefv{u4>6e!D~ZGTt}%bb14i_rW3P7C;i8wdH26MrZV3)opd4Gr}@Oih_6Qx2MOUa0?gawt4t)WQ7N8hP4 zd)A(cGt4??gyg}dv9#ngJ$rMcbkq+!9G^tgaW-;r(fesl&UKsjN3nk^!eI{qLueU- zWT$RVwHVb=o;VMHK3`sJXc%_jRF!U{)9W?*`&TjjN%hP@q|z;EGo2jDAuo20IPJ}w z>(=qH!5Q>jqs?SjJcRm zS07{~DL!8)M-8qubymAxTbE=rU6x-zvc5CgPMD|5gs9X8MS_lwjpW}3svB&4xaBs_ zX7@sYLTcq=NZMARpo zU!;JIRTW+u_FV^;JaP_W43$OMIcH znW4!3@b0k-QMU5*96GMRqNn1VxT7+eH3!q_dN=cO?(qf0}#zP;{luCdcuNLkt> zIyw`{n7Bg^q9-E&jyMaJ&C|ULlg`NEk&}c!Up~uxt~^E3Lurf2dh_>ytK5WGplWRy zxm`0peZn$i{rRf>7VtGE@{JRM>G%1|vlj3#aM-51MA7Xwm=yfkl0)7a+ITJy^z^CD zlQ9$*crUP0RKuANJDQbcs2&+DuV(5h#>#~{43?~|0fq5oa|jp zja@A5?Y`3l+3Fj1`)mllE9wYm!NqEd7Y{%z0Z>JvZXMBfh>es8iwqF!1tvA5iUg-5 zTSK4T;q01;^cpJyKvG$pSrb1oqn2pVp%4|DMI^8o-gVS`PFHl^QV3+Ht-naznQqR_ zv|G4ENUYZL5IV&;siCMW3NaLQ2*{tpI&I261P+d9ZOaIvZimP1cO*-SY^j@>ewn`M zq&DPsno(O&zeGtzYA)ER>Rb1WWXx07W+X34js5VHKwYJWrn90aE;Gr^iZA$aP*C;~ zy`7?Bc+Qxbe57VmsX^K$G9(#$O1)FlI|fo0Myb=h0pdd4uB0KT16WKXtwl-^PGD#( z1tiX|9Krr1R}Ekd91ij{sFI5@WoPI2!UvbZhTD_9IoLPKcaNtB-m}H~`NaMCzS;9k zA>6Ptu!-#V?D3W6mo8T-h;Yy-Q2c|x30%99@2@9*VO+G z>_g$)CPU8r);3_Lg~Y09n5)b|YX(j&FN!SzIC75ukF1s?*D4&$a)jWT;$2qrs)5g@ zy}fI*z8;W5Lq;`0nCTNmGrDEH*nMny_Oyv)Lu90&@X`Sj3YcQW4b-@pX*|Kt@eibc zD5N=oTLW20XobjVA^|-{Wjacx`>c*pu&U*6hfDA9rfqFIB;24Gu&NH<|` z1qc?1tz#dS(kpwaiIc2W24{cqktJV;F2`A$(V!iED?5p?R5DSetcwClogzBQ=xU6j z&Ms%vUXZ{hu+XX0F?I;l^!wX>D*BpRu`3daU>!Nc=aAz?JhU=?!T_<7(n9A#YMHB) z{!bMqrt+zudF>Kzw~UfxhF=1+5@$xi1}|*pp$5Cn+JG*x{rq{kcR_9=3a zhGOYwPtwUtJZ0;vfVip5H4lCFh6B8z^|cgiSL@`}mE>9#U!q%caapXy{KzPN*u%Go zk8OGw{*yth!M-Q<`S!bnR;}|uM+lI>utrX}!}pbI{hGs~<&>xGHDdS;LUGii%xXBu zgrn14OrHbq7EP!}7NgyaR-FGAsA=diXr^jJ39w7gLA;141ET?(J@Ejss<8lsNE zR=lm@tc(SdXLUS6ZorTLp1FWwx|!`MFln577n*b^>w^{?ye5+4hK=MqGxgkIxcZHM_`{hWcc z&^v4ntFx52yU1eH?nj!cTHq#sGH@$sB~MM{xi%;mTSA@8gamO1zt^r{PD^I3Ubne> z+QlDqQUJ(=aG=hVAvfRUzS4D&C}I3=ecY^P+RhT;ME6fArrTIwm3(LBRraVD2TpAAsLRH=lX}1_x&z+ zbeS*B4zkhP{#xVc@5+8$<6j(w{JE;mO`r$i+H(AExIv#WoY0Dq$YL|o+}u~D;_~nd z59?6lH=SPt<2>@`v@=3kuUf&Bv&!Fa(2o}j>oNTPnjgw6+f*fIehCKFa zVhNy^IBN`Ew&YoZx+pTy_U*Fz{QnZ+e5g$YzeNkp;O~5Gxc~sbclU1*c6G6|asJld zDQxSw?=$#8EOhfQ=B)h0BUR0o17Bq{^zs*7wlnnFLn)WZ;2F! zR%%O!iLA1TXiGJ$bxd9cu7w2pK)z@R#}jN_`^hr1ojP?g!Jh&4M=m3zv}{mRQ>S4+ z6*|~1EgDUMfEr{qgLe`&fV~P`5s#}v6=AjJ8oTAOQ>*4nia~4JT3a>hST)ToQ;J^6 zJkSxPLrz;up7(Fwf_m>`WhAGr49aOv^Sn6n!RRV5D2WG6eJ~To8q9A^*?kkmrq*?J zf^b6!HeI+cC^7cVEP!gP0Y8GN7HMfC%vtkg-cL{=yeAn$HHS37tArLV) z>BR`&`n7iU8e*@{8%_J((@~{sl@P*|qFmeM*_PH0x@s??1 zQ?#6?yTQm_8TnpvX{HWwZ9-_#zg)MtuB8U{Sy+9z-542Dm0;Q=nJA|{=QUBjHf=G2 zb5XO8MQs*K-$cgHX--F>5%d&l5(_L4*#_nym?xz`XOOx#%AFD!x2Dar6>GlftAPu} zgDq19_zUU-yhF(;Pc;NbpX(w@fEhi!w;I~cOUQhzIFaJSyG=^9OI^)o-i2I`0~ief zJ#1(B2v6Xw?k3eBDUqJL2mjHHF3+OFW zFo5ng))65gX$9_$+zGBpDD293d5r?Hkp>Rd>!i7dYF3R;k!8lk&ZpE|W&pWTOj`mZ znGY$sp(bKdlQlPP*SYE~h2L*8<(AXKirc`!NqKkCs#@g}Vy5|NnvMcMm=U329%P2_ z)j!R|0Sq<7i}_>!*;EUpp`<#P<)B%1t{F=k3RE%cm4YBA3^oO$5WT0%C+FkyEp27H zyG>GW>-%wNik`S=BNPe8L3Ktjc0Wo8k)=TfbjL0z03ToA1PoEVJ+^)1< zz|=54eXS1h7-f+z(701UWaNpGnH2h*^S0 zF;e8I6)hI1BpCE9jT8nckRjSNzPz<^))%8Zb>!FC7zK#;1UBMh8x+D!onnLW0R~0~N4941A3E2wytfCoKX}OBxc4 zFQ4ZPc@D3*D6=zjI2wGM9^0ohXX?n}Mo_04Oo3)}BqU=4kqCeW$sNeEdCwM4Bu^(& zt09PGKsp%*XK@T56Ss|h;;RNt=c|TD|72-r5alX?Q?4+^VgzkSF`^cEyDnBobh;a_ z1mNVnrTbVc;BNjLa?AP?eUa`S&0Ki21)1@w;7j5Gw>+EmewzLyAG#rrN-j54IE}Dw z%b$ycc^|e1DHLJAfg+C(BrYkAf#*T^X@r3n^O!ZbhV8GQ;~WhuWphQ}Gb?9x8sh9c zVJYg~F+}XrqRP|n_FhDP5TNF*2@Bp~dziHY?Pnx5;9+?IU0;6XG78ORr zAxvN1P}!lE-kekOfW3e^B_Ehe1FOq8g_L`#Z4;mD8bDgBk}w~=mK@F5I(tqLn_Gnm zx>rR!%kz4}xW$J(#1!G^#YJRiDXdNL`5TCjNW*m^3g=x0LDyT<4?OVJl4Rm(?k0a- zY4t?aYXqtQ6qg-@n0yz8fk)a4krG2Yp&6v>@OyXc674e?H33#8eYCGdGRhG{k0OEV zL?TYeGi0DZE%uHJEd`NXHMP0#WQ<}^RaaWU$7T0+Gy>8o3caQf-PAFBEkJ5NYsX&e z_T{KHdFo4Ye#sBd`Mzy&FKmm^PV>&QMF4++aOHO2%L}9Rk-^_^prYWCEuLOiHuxBl zV&0L-%$wpi!+>PNZgm4Ebl{WGE0d8x%!D2RmUfKYq^~y+v)o-YP}eMig?1lTBq)12 z{i7cP0t&%kIoCd?=VLdFyZS%}cP~Mvw|#DuySgkmz+H`&kkEH!f0m0Q0L91Fl29`1 z{Ft8fE>d;+ z@L~Asd(#1PB30<&XIAFSaA#Lq)w~x>W;g#thMPnE>S0CNapnq;qd<-ofZUs6ppY>* zYOMK+8W^$s%}Hu8=oA}ckfB|E-Y-g$Q|)0Y4tQ2&o3k*p=2BygzIcM?QX?QAV(FMbtO|iz^N}d)Y?@uI$z~3GYIf%Pz$aO!MzI@I07a6M8ixg zvUyIooA?BqX2bKD{1IJG^?O+ek0`op;63{Mh?>x_aEUfGI=mX?UGbt~)~gFJ7$#V< zB>Ei@rdOf|BVeL4$*k&y&fq@O9bJ^H!AB3-YvgwZIDu5*c6Aj%amZ?ubox~*(2j1% zw^Ld{Z5AcUw;WQ_SSD7WaPcO%1g3YnOE;j&+vV`>v!8SQxz^l@gqH<7kX{Ztr@B@X zh(bBl=)}TvM^MR3DLDCO`imOr00a{;Tg2urr~4`I$|`IV0jH2oZ_Lm zbWZMa*Dr*vtpwE2Jff&K!rZgZxc#JV$=u2k!RdTbH2OKQPLEV}xT30v^Ub$@Y+iM?Llv(%rS;+T zkjQ3`lJp0NnIOKR7SYj_?AtIkFKz1l^x_)OVQHKthZj-T`?1nFRxk47!QKa$R%ek> zDLVd{!zGvHg9*4Q;hum3LJC_)%K)L9jf%z=OQJ9^3&LDi)^q^NzQI}o5D@kl?)c{i zr}XhAZcHHi3Nw8FuIC+eamw#?zW`7`_je6mPKMC^$9F=#@Ec50!qA=c6y%TZxqOc%%az6_{h zQf7gWvIE8%>L@zW&KX4j8*F#x^pCBJ=d12SBDqT(c>UYpqSL*TX#-OV^jI&B@XeA; zTD;pq)}6zEmRmZG?E@Vt&Ve|;vyZvJylr6XSy}SY3oTT&mzSHiPjihujk*m4A^Zxj zlU;-;pdJPHf}L*hgc`XHgeK))*eN8D1a9=i zE3k$y`(_#8?grp_D%rKQG{fJ{p}V98V_6aS_a?S+Cy4<`&qZu>fzu@5?(SysocSdf z!Lc)h$D$RTYU6h9iZ#ickc=WDkQ|t?O%ud>XE~JbPjjMPT6CsYL-E0<#=$*c_U0YL z0&|^C;uEG;L;#^AT@9K7IUgY_M0!R47=|J0*ADBD0rC2y^ODvC<4o6jgzl9AE{&b` zXGL}S0Ko<&;r!Gdq>hcl;pzjH1RM|Wt*Xi!i&S(Z4R*Cf)&RY(4c9Uew!~Z-JyTBE z7^7=AjLm+vvbn9BT30Wf;Wpa>j@xl9fao7lhq%23M-zVGDI%!%ghv<6y3(hNiACVh z#lb|oLz5T>yGn3W_+usn#S6@nR~InDKd0lM&OB?-JF;S;Ak9}fmzQ-JI0csZ zd>R&P=-Y6b7jso<8++9ezR33CagL4WiKld+kuOyOK7bMS%f<5K;tV{OnTlZO!~->C zW=v`n;7(OIXo0*eqO~DQ6Lt4KejEZ{-?b z<(Qg$oDBYooFbcLY5y>ZgXqyB}?6Q*I939NqI2u z`DwU4+sSMwEG}jl*oH@zN(9TsDazopSB+f1WbVqxp|hl`hVj~)FMYcIM}&Vq;CS{$ zCs`Swml|;+JpU?8Q!&Rc!n5ZkWm!f3<=f%1UH{U|!tv{mLm82FuVDvjkVVZ!KFTez z132~h-ExrM{H&TLRqgv~!-|R?H@OlI)6*bW_?wE^>yHNVm|nx2blk-?It_%sBF2Fx z-H}geCqWWo!NeMrNiM{Kr3KL!<;IdhBPpB<3_+37lThX*dOLz!xN7`eRSy^wCVF zm-t%Y#;$n572K&ijQ5n#(58$PXyvPBlzCIVK#18TP>Rd_5t!UdY z@|63B$%xuyQ1e8!#FO*PApRf77$qa6VQ$y6gCB>z+j}vi3zFL@9_x*#k5ggy;3;H{ zu$7lQTfZTc_+sYHd?zg9mnjqG*cstU8zaQWX>a=TwG9k@ z3C|IWg})-p`%J~QD?DDGf<&z3L-z^(SG;~oys03?9>DGPU6REJ0sw&bKk+&(104e$ zBb|w*vkR@IotZtojHIxroU*8{qIBFk148$)dJM;<)ZCL}g%@+AW>px6)1*=?8RMXB zgrZH<%X2p5g0Pd+gP$sD}mK?ziW$E#Qt;VSVudkYeeV8H^3rJFS7C z+es#AI#LMTXSfY|a$(`F&Jkl4(JX7ouLbP~FKa#=EKBIcFn}uO1_N=^Vewla1Pm)# z&Vk8I0{MeaZ_>iC6%;whK1-0#!q=H^oiIbf%X?)503H!{`i$G?i6gRc`5_WJ!0Q)n zD~F43EW@qZ)%s4 zYeVyEXklHQU$5lT5_g1qyV<9^2P2Z#U+MB?JqHRCp<4_?f{O~2uS8Bc12Ic4ImNYz zUX&|X4nlQG_JVW)DcUcD<2<0B4N9Fgf)yfF6+#YGkxbOfZ4|kNv8_s06oXP0BuhvV zH*%yNvL@6u^`v}jIw(T+3nqjvl3CE@0fIxxXlv~TP!&xTCR2)h;_FJu(4ka5L(+dj zDl~vUgW${3swgC=46R*$jpCcsuT$N2F4iWCPSaYeP8-3o{zW-30~!APolqfd9`kR#sF+P()Bg za7lgJe!msbZ$+PR)%RqxFA=x2iw>x$wQApHeqvbgLZ_WJDm|4VvzDZS1s$`KJ3iwliS^O-`9+DMjr#)9ic2QMcN# z9o%$SEptaPA8=o4n@Tj&`BBXE8ue$)7aCv5>qV06RH)Y3qg9c1s#Mg`m3Y^F-I=_b zqR*iYZES~zrb-ajsx>56t?XX5BAOLevMmK#>*G8_N}& zf(vg&)Zq$l3#xasl5#f@M(vsZD2dK(zV@;Vk0Utc zHLqB$@s6*cRy3Gm7Biw%F~#Lo>j(bj7aE0A;CV6tgNNYdija6Ax-z{GCg%OBB9+DIzm8=2`wn_RoBZD~Vu zA^AIaZpjq49dcr7_q?J!6^+4*39#`zs-=3h)pd=>jQ7 zgkHB>xV{iOr{-7@L)KGNBmj1_w@{|FNFQ&kR}+cOh*YOS=~Vz9JprWOK#sMDHJ6&$ z2oRyw$svF`0JM&5SXtrDJ1>=oKYZ8~67~2M4-$;yrsG zxSTG-=k2%JdM~5v!LAcE57}h=WY?*E6X$CSMo0L&j8t;@q7GJjrzO%Xu+-V0Ll8=) z0zivPdUYjL{By^;%aQLsyhREDlIcx@j+);NeKtwBG$+{|uRu0Y_n^2%zH%8oD3(xY zRnMp*`P0fhAm7$DA3+UaiKOnpEB@YU!5U&IDaPJ2b;7u(RmRe1jb1j0D&j8vrx-;n zuzZiB%$3MoNb6^f!qU_G)eb#A-_2T5p|d!|N6p7ePl|scb?Vp5-WQy0Msrr)OPjx;*+lDO9vOIRx z^%W8p(b_z;{2iP*w8XycT*rLswlAi%VjA^4YD^%9&E>pwt6*Yy@Je~_2E}IU^vB<0{P+iovilsNkWtq^p5-*~ z={g>{og>5gm$m6atGom^4(f~!3C5?BOtYs%dHrTE;|yoSe(mVXk1)00-R^aC*$?&w zt8Nd@mNV+Vo%G5?G}~U_0S9v$nOZw{pKI~_X@7$ZP=eD2WIVx3 zxS-(YB~;=$J$&68@|ZR1=kLFXl7!S44P7KXc)8*%!z?q~9P|7E)c6ej+#}60ECsF9)R;`Y5(Ood z>>-q-Op6jl8O!q2#EkTk>}2J^9?*YO=>N-uH2$dSfczc}_;(}u=TW)XJLub(x|!P0 zxp=rpOViBA(H{LBRaLc6(+&s#Q1$JQ;{G#ANmN)~NyK%MGqpef0ATjJ-e(O(iJ%Vz zWoJ2RijIUwK)@u0N`dna2y>_T9)Islf%wnFYX+K(ps?KnZ^i>GM~Rn#=n6BJmL)W8 zM2Jh>OTb3;`96uMda<{<4evfP&!(`Zfm;2wo?|5w&Gk}PTB7wQ`;8opOA?elj>9KL zY(Mg(%4#r3)BSNH4u1_CSeTtS?M$Z@aTdni`vzsxQeP{Pd9QSB zF*&K6X>#S;p92I!0siMxA>Y6J|GA0+|JD7!XGH$f{=c0O`M=fx07ZdJf7$;(Cr190 z^54ow{-Wr97nJMlYy`w-_{XvR bU-?jw2K^2q001DrKcU}xgYvJr0`UI2+eyc^)3I&acE`4Dn@^mMZ5z+LQ~%UIHM?(h>aMdno3pwv z;usj14EuKjFyJ367gr{8V-FiMmtV|`%#19IuEx$F*8%}9Tbxbby7^#=S@9(rMU*`5 zA5VF_r<*bdHYPr{7z}_aPv#Q!3ddy*?)8jnZH-qcQpoCFeRhhFcA1T zpO1al58409_O-jUwfh;)(f$QISko1{4>vgA@V_s{fmtxND_7^eFY}LZ+MucAqM#^^ z8Fjsom`3vXUBAH(haQmFAaNz-i2lzf#O&OS`x)QG0YS+=mIj^-qho_8(~6P;v!9duFbpObB8fr zF2-41+)Gbu@PB0vbF;qBylH$dpX2E+D*MIs0jQ20*d4-&)t5-BX5 z&;%O_nWE2HMVQG?1UzLqqlaksKwN!5PDI)R3j@hr<(-=60v7}6@!x+S=-vd$Fy_8- zpRysHdhWd5Nz9XkA~e?>65-F{xO8PE_xR&Cm^C3!7w!tbhV7^xK zXDa!``ulNQnm!W(Wdp&+qX-9tq|HO^C=F1jk^Oo7JhB4ZXzvq3C}Pul;7RgFhJh$n zLKnHu3i0055#zlvzEP^A4FiCd{y>UkKG2|u{DtRy5l<0Y5$r};B|PINw9%Sd#G9{; z1!JaR*ng%DCwDMUnsRbb+nj&C&3I`ZX1Ug935Ranx8~{qU5kb@Sb+DYEjylC;=iY3 zIiZd?A~|vQz9JH_Q>#0ry`4O|U~J~LYa1T^9gV8j&*$|S<*S;)i+9GHumTDtVrW9{ z5X~Z2)Qwa+%$~t8naHzqutAB4X7ZKD;YE1oNv@VI4?c_Q5f?w=@g9&p$0gDQa`718 z5W=>xk_!|B!fdL@>_CK;r9qgdOi^%pBey7t+`{-6tdKuJA5jU-IOZJT*5DK%w}#%} zKogW-A636#=ZLLooHa_r@DUl2v*JHqcm52HYr3J$+*sp+^!Ip&G3!YBg|O0JwsO_J zR_~5`UuvofZN{gD8EarkJM7&3_KCQpx9IC3Wn?-L`)iii#3cot0gc@|9fPlwDNfXY z^Hy;f4IL0V18qLVNs!u%8-oQBFjvaE7CY=sVq0J;l7x445|X+}Tm+UM{`!MrW^|$W zNkX-taYSxeXFnJS>Wj(SL{?-55a17Mj=FCkwn_pd6w(%Qhr_fsJ z4SLuoyWX@C{>^l$1rqN(O$gi#?2LGbyNA3`Ag_o>VMTdjtZ4xRe&x62@+W_9T9ya| zl)!ZMoo4*I7J~I+X}AJJQ9Ymd`;FDn!y?Ptd@0|%aGVrk17A9(s2BU#M%JXPp zVBk_TW-VDm!m=gnU5`^{VZiTa^bpV+0%4yaw82iInGAd-kAX?g)9{#QMCGwjLEcAv z8dP-p0Ptc6h}a-Cgqt^`0cT5A)Fh`X;SC;ZXm5CTM8Pqv2~jZmgN99o5 zpl3>)0yWe`q1s6rnftiAgPzW~R|caIg>lE)_f5dcppq!4R9mfvXEOJZ!R^;d#rCK$ z=ldbqEUouG+24c;h{GhY%I)=Y*pf-I-nNnRS8%>6xCW-(g$g`0vGa)NDh>S-Sqg+5 z@Z{2;j)-a8-CkY?bQwLfxI}V$4Iqwk@6ZI(J+DR9#I&hvn)8j|-e)Z_OVd1R5%lr6 zrGc?4+N;w{h0xo`(nSdu=Tad}76k@QCFMUS`y(-jMi_6Xi*^X$e$knILG}!R=QZH- z?|-5C-QymHyhgFhvB$5B;=QpaU|$^)n2v3Vr_Kpli8&n`AyRCoE}ITF79!4ZThvu} zUxA$<-zZ$3&4e$e9%~x_!b2#zdm}%pxang8y;F#>L8P~54@RDY*gi9S=asPqXS8(I0d6RvpVm!a=Eaov`9luvgddMhBx%^}+|6MYqvL3b&#>^dD zA52-yWWox2?^?`oD`xa=IVM-Jyi5W{{Rq)aj1oYdX>J1nwyinyD?j-f-H<;(fB4q` zyKf*hKkQx%7SV*%N) z)2j?*)wIn>VQYtnS1)sm!QnYg3yt3|UU9_kq)0grdW__{W!ctRte=>(0OOkP_s@V= zgDbmN;E&ap$6x~z+S<2X`T+W4{}IsNwX5 zk=zvw`928gue54a)i4VeXi`&HoPD>|oh_H_^Gj-DaU&l|wig2<9teXjtBTcU6Ku%x z=!6R#Q(Q=N!@Uo_f$+z_ho+v{v41r!ZWZ4BFk|&JqxCl}Hw$)nZw*! z3+rC^ROc5Bc!(IaIu2=kL8{y~%v$c)N~uY^$X<;Kx*wys3c|=~-j{MldT8WN_Wu^M z<%Rk=9PIcg?>$rd%c=V9$p;6#E2oj8i5YtB!>2;-F*#ILZ^}*cEShLwBHon?6?dPG z6;b_gh{F=Y2p*_J`Xnr|9|!f)oka^Sfp0I8xyIn1Lxw+4>kxVCjK^bCG`=O-@sWPfs zJF-uc<(-h$<>)7(GF8Q#AnM+URh12s7+=k%j5Y!~<#ZfU6y)?u``xpR4Ews z!-uFTpIW8BJ0A9sg1%uI#~v5zJ@1t2HH=60!8Y53Do}%zVC9%Ku6L0iH8&pm%qbF1 zI_7|9`rWSWVq)H^rmU;K!RBXa&Tg~_!nVTK@XAR5S5&+( zue}j>%;Bner+A=RM|vq zebI!j24K*kWoK4hVbOPt?zG;&irv5F>*=DWhzLAV%7PQ2Ov|U;)YmddUeLgeH%=p$ zC*GyjAe(4D~GShS8g*q9=p_1 zeStaEybxiaTK=tl@#1nGRsNJ@h7qu9CVGl_j?OTcW}GBAh*m<$Hj#J$`5j~3p|pk` z*$VAX+Vz1#KcYo!=w8C6DjTimZts=7aBkU1l8b~1-*nDBNvI> z5gb~Lcc`lplu{BiSi=~zxW<@C_i9kiI9?(ERW96KNG=(+g|!2nk^WoPA_UTNpg75r zS68OfGu&p}VXT08VxvX|XK24hTcq}S`*^vsET*L8q*8$x4#D5(BjMuK7rs+u0Y>K z!CwdLSyj8^`q-p+lSJzs^Cn8>wO`#NJUc}hydn>>hd1Z_C((g@WIMuFQH9%rED)Bd zjAW~$6vKBi(d)g|?Kqoxf1wgnaWX-|g!|qXjbi0Np)WedX z;%naf@!T@oEKa7l#!U-&;o+9werC1@G#l zX`%kFL=bTkeXj(Nt_Vcc>*>(JGUkf4OSc()P!>Gw28uH*tVe9VN_C_$>&#eYq_=Hn z1h_cve>yjl9`Z52{m6HYuF%H7YC+=BE=99fJow@BCNujgV{qO@tTDBQlc3ZeCZk*&e>w(e#Q{_zV{|mS?o#$78Yep)WbDixA6^a5T7VN;{A3 zQA&Nw^mO-CDS6HlLMcN+`Tf)qQ-?8G#8k!}Z|u;AR*>wXYjt8dvx}9m1jC(Zk7o|9 zBW2s8|C8r-Z@`)RMYK>2pYoJ@J3mMi-Ho$cdrs;eoFKu%AAca%$kBo~{kVaHgBQ{_ zFTPo3iu-G?tSBec`Q)(ld^ek6CPEvlPr*Px41A?(f|pG=85dVAP_g$0VpvUcjgB`4 zG0F%({D<{UIu#ogQM7`3UxffAz)7@(l%1VS!Vp&As=US!duw?c2WP3aZEJpz%u1+U z+eS>H1iigB#7YOsC(uUOD60B2>2)uAj2nX$g0)**y_Al_lvRzdzWV)qR>EK;n8rGn z@{Vmp4YPtjxfLdr$V0|{Yk|A&W1%Xq?$?!zT{N9scVtHl+SSYo4aa@OK2oE8V)WKfKut(-moX)E2x5SJNDer|xF)B$!U3-jlP1l`Q36 z;k`RSO25rKiXMY*Dr)4bw*`*GX(b9jYRt8%DH(K~9Sb*)mtH-%v|WY;vPyhpBeEKA zx7GS)Ffy0R7Tv5CdaBODbcJ$`_2ZS;i)+Mc*~&390}m83i3j>V^)NjFJ85^(nnTY7 zd$}0gfjC-P5hSI%0xJygXO47hD}rHm9ZV)`%}PbH^p%@BUS>$O-c7f%WBU6?x(UGEtklm8d}X}Nk#tf5l)`HY zSjp&^uVcV!(;zWy?hLT)Q_Z_wPovbREia|t5)ppuk0}2Qn%-lqNp+<2!H;!V966j0 zoCUKgrr}0(9#ZKc1t@M3TcM`t3LL*Ds8+xVDFfvQ{~-*hr|e zdcVsA=v$0>tl>r)2ZvU}`_Ri0RzDnrPgt6`br{7>3yS_QYq40mDWlsbta`l^Gt!%{ z&tZ_&CM!>gcJT9bs)6mkjnPu~>BmZ<@7A3>pp1gFr}}kB*x;?Wzi@mSddqoBwtvg5 z?|NimuJ9veQTx`BoUP#=>lF;{lWm;rCKSGEA%6CUM5jwKp4oP8x4=#LsAcRlRQCm< z;|nclBslbT8ZUduRAp#wDThsrVE4=FU?j`H3ZaUvEy(EeQ@k$trf(+}3B?M0$>F#D zF(x40s^iMK8jR&cMMD1WaZ8S*VYz4j@*S2rN{Ldqx9xzq|h3s$%OIJ6|7fQX-!1Q(BMJ;>pQloYFRU7Q zDqs;yNpr>1+4|H5fa}b|>rA7258;2<5-n00ESBXdCwV-DMzoY=v<#_kc5^~6X741A ze*ZQ*hHXEe?ohDlQv4_0^sdyVeBbF;#dNJFaBOCGVpV%coCkXt$oP_0e;J`IP=s#3AQ@-(BP=xSy zn1iSij4p23Tp1={S={OjZ^tLR1OK&3tOw zD8mY%GAK?dMa=^m@lVY*JDocq( zN;$UY3pjto#*l;jz(5Dz(pL9RjAh*o{%AsbwAuJX-w|ePhSb{26g)))YV%fRg*PJQ z!hr)bp-h|{Gj@SC){4q&8qH_VJHqy>%+Pb6$-Q*Nm$h{4{$s;}^aG}_z7!2=$n@$L z6pP&Jw#OoIKp8C4Jg0AeDj@V=pTED5*!Hax@9ka7eEdS1fn&e0^!B!9}?L(fa zwFYydF?)KXSSLs;XSZLIZN(qlMQuxOt+l2)GcY44q4UWz$8BSdlk$6Q8pwhmNiOtY z@peu}N%r0JF0`5X7;*|+58LhDW#6jZKKlKzDNL+O{xuK(y*2vYn-KI6k ztj%($(|j&RJ;;21-1AtLkKCjOh?v0}9Li`*`_N4#inmnZhudHA7@eE3eA_#5*ll6vs$dG24vI5o6GtQ^5S z%>(!{WuCGM?qsrIg{6;!B1R8;Q(Cnvp`_qzaZJo%yIYoDItqkNH2WMFlGfe?Z;SFm z<`CY~$?-it3fSwx8}UeuM%$a`#m02Mw@)A@LDX3YajZj1COIU>jsd$`Y0Pj4m%!`V z&VBV-2KGlX=o5~3cD6#L%h$`E`h2gapJVv^3KepLXJ?QwnXH|QlOH$<<94xWGBQ$` zrQ&b75bjSuxP*!WZtx7NvG!^*V*6=j8fsU zp86R=5!et_&Ec^CTQht01ck`^@-=Ve$dU9zyi%lEz_PFy5 zwP@al)QYdhf%%)_1=H^=#3wsQH79hI%zw1O-M0I*WhgEppT*|S!s@2d&axz$vX>Lv z5L#+jxy*hwbID+{KZ!s2G+WZ$yg6lbO%AbX17=z2n{jC{<*wAcEwEDXn&^)KzPVF0 zexOtiGoqqr1)*K6&DzWv!F%now;{h;1W^X5)nu8l1d;~Bbq*|G?687 zbyqL+4?wl^KS4pk)df)sc7ZV`AF#PxO6BCegk)X-P+Z?XAlrQ?uq7G!pZ272L1g+<9+A zX`=V9`I)x}9B|YHlgOCiPEx64VN`Hc2R@K^nnpO4LQ`DdSwEzz7o1m-l3uYO)qN!O zG(2h>8II;X8duglKHstqIQEHTWQni(U1ghCilu)@%EsM|s%=|itpK)bk*X4Rje#r? zL;B+9WF_1^)_2&+qckKh;aHflW`lx)4k!HQnQjX>yvt!W|g8k1Ud-& zlj~_vA^cz&h$VNz!`5OWUsh%|Ei)nxS#fBK^6s)LTczoUS+*EloyQTkB8b6*Xf=FS zX%(6*qLFwKsak5TocWggitlL_OZLkUj++=}Kt*V7En%$&7&?$O^oKL4#+%zTqtrV)LXuM;-##G)^=|S};dYZMvthE%vj;EdNZS>~W*( zAB#8y(V@xOLKuz2N-9kIrK1fAHgeUrVzUMr|UdS2_6s@Ov?NYjB| zG~fdheIC?AUM8RztQlwKla`$@78c@v?j9 z6*d_WU)BHC54p&=;;Qijb8F%k=(*KSs#ZEB_0y_n6ftM`M{UghhE(h2_b07d7<&ykt@*Md5m^m3~)<>_EC$-uh{xabdXq5CfoKl%Sqt2a2GiV{u}vg)e=y4)+y#O>+?g6oxEXk#Fufk?$Fyha z1Sa?(TRS`UnJZl*TccWI2GymO_KxBKjo))+UHsWg(Z6HUU2Zh7VF}FMaSqY*wfwMT>u>a?t1$gNJM# zPZTRsGN#f{V17U5Jf_9j9My5p6dBO>d65^3Dwk>IQ>EX9(Jj@=xzJ8@h8pl&03HdoIv7`bSw+L~etm5yHI2ypJOLPiB3M1%a$aQ@ zj})U5ib;elX0L8kq0tsbQhkkY5;+b_k7|yW69~gUizey%gp&8{oDk*n$2)Sx&14XI zt-sKhIb*&~@s%5ebm^^(?j~^ph@4l26A{Wn~{!4MM(_ zKk>Bb>_C84#aIUyGJ7a$@5ak?zA+e{AB+tF^ zB`KXJr&(`<*GXU&XIEmQ?%ew4UlqW>eAoy#&{rPjqTq?FYkPnN^Ew@aPUIlM}n`9S5P)qToWAYWFTih^h#w78*ea z)2gx|!XXWtHO&l>|9DlXRH&3z6i7>o5FB68oZJ;L@hYpo7veuy;EAhRV`b@JiZtXc z|2-myx%M!Fi$w@dPaSyl_Ena#+%^xOgIho{*ch&43?1xf`8=GkgbTQkg)){T*VXJr z68;N8AsBTqUO+>4)oShOQzsshuCdm>rtfUXLt0&N=+x8bpT}nN%izf~r4pCCVU3?W z-OqR6g+g$9Jkj$Lw%xwsyqs1x!|&KUeP|iqhx4d343Yg+?KfX%8Nn!6ZQhQ<`KM6_ zE1rz>6?}GEFk3@fd5QkKxb6?Ca&OH`hq-R?*xzF#e0Ekoh!%OO1DnpCpg+S0u%r1k zplbsiT2}aczDsmg#K%ZJE{1AVxq+td*Ttn@?wY)CDrq&xRwJdk?P?9fwx%DpU@nk$ za03{(>}X=nhu*j*dTyadhg?k;`X4Q7ftAWk+RF0-fC@@=o4)ubw5Pe!#a^+TQj5igCRG$^qRF~BWdd4 z?1b#HAtt5R3ASw2%AeVA9V_Mr`aN4_d@D$KPi7~preC7=UA)tAnyrsr(>fgkhe$1zWgH-cmGC~zusu_=mA`WL(V?aEkcOA@CmXOYghsZGlM zvUbJNv*v^bdPIK>-Pb8~8pJn|K3BbVK8e3x;A0a6cWx52o^{IwK3;S;{o;s?`0q|z zfrF%A{Zg1QUqNQ%f(6xd;TRK@B^HYsA*mnLATYd`7%b55jax?6&s`lX9EHZ!Kj$h7*VuVTg}J>Zfr<2qLKdO*n`NRq1anvec0 zE37a?f$El#1T3eoc#d2wMa#ye`%y}0)8x7pN1??F8E=65kz68uWMXw~LUCH_fWqFf z>R76+w!~87tD*iX#nqD3F6)-?A3i?Y_V5>RK>P>WP1leRUya=_(s(W(@^A`1==;kC zXTpo+JmF@Xat#+u6~!E}{7Lo?IrAkQDnOTV&R?@uWr<-B-FEXih0htzt<&aRhBg8? ztk0MCJNPUYTz~@XE1q(P+w1_M+|caIFrxhE^Nb`Wask`T`utB5vG?K~&b)XW)+0sE z7H@pJ|D4603!HIg@#w|PlR`Z2!Q|@k-7=^5d)jk&VKbT9 zgcIf^jZctLwnuI_AKyKEF>8KtljGbSLC$@9{@a|VK16*f)tKhC(AU^ISFcd>9ru{2 z{)60vuq!kJZv`9l2Y5&0anXn$;$tz1@`uP_M%An1@V&@>dc|;U?xhBs&|05vk|J=y zq;4k?5_CqP2xonIwPW9-8>_?MPH(d*zKiuB1;-E23@|IUr|PI zuzBX~-k?^l7rXp{qFsN1@R!$JYaW;AFnF1~zcUJGWl()lZ;4XLj}Ajh7TR(voFdB@ zzHQcWa<`@7s~rf*ENRH4LC*3_naiScwk?E&B5BBEeXeH*EC^d6B50;KzVblcp*cB0 z%LvEhH5G?hd{%mTb+`t8Tq@^5R9=@Pj9yoa2vIcynq>4ViBDFlr^%z7y;7!>UbfN< zK+FlbL-EtY`hw<08K+KAUc>jEjc_f?9V(sQFnxY!CC%aq&8BrYmw_?_CuP7K{sHjj zsexa4;z{~OstAP<&uJ;4HbN|@VkvD4aa>+M4bZPJw_lb4AI~->D(=X7Jc2KX{3iQ* z!}`cQXW7EB%x^b&Ws#fKpAs>Xxl&IfMSrX_O6(~a`9d^rl0nzQ@EWf|eiC#A*35hW z9UM986_}R>CmXM16K=ETpUsy`uEFfc?i$me6tjPVKZtQgzQ*wLcb-7+jPE_oZG)EW zn+v4Wk0#T0J<#Dpu}MqwX9#)Q(O2W1uj^JK+w4A?No=*rFuU|q-x>NOGCK^KL2twC zqD!Oc#zo}om`Y*Mu{Mz;%SAeNttY)xJh#?xfjBNTf1#HBK4+fI-y@5Mij|CEYM3Ou zs0BJniH^TGv>K68g~#5LdX8&;Z5nxmA0N?o53b8Z(MfCXbfvvxw`B=_FFT zI~PRbF6mIkQJZ;)7y&2Ox@0-vs|tE>piscAZCvv2BgL!z;o^!AK3qwpMB+*#J1uQ5 z?aFRJ+8fC{Cg-#>t!=}>1f5(~~_)V}S!f|h+ z`p_Zgu?S%gr&?wZe+ka1+@K(6?*0d*JQtk#(==bHI@8WnnQ2NPJ5XkO(`=(6n&{SX ztVYv;yY$t#8`Ex^v@k7=UYL=;)dF2@+$@^AW;S{A;;EY|(luXq|GI%@N}*=kQ$p?C z;iwg*Qd$W)OIwpfv%nm(4M1ek5Y@@ja_MvsU65 zJK3kW0I83G?qByEs56rLa~9Si2mU@jkoQ7FjEDFA-`LljJr2E|KfbT{ZS8Tu@6Iow z&v*61x1-~8?06vO)+ZHkYqobAd+Y6Xtz+jSDq_5h-5)dujV+_O4M3MW@NWV2qPJux zF)@`93s8yui;s+m`j;a+4H*LH|7ESr0ZSt{ha{c)Pqf8+W|0m?;hv<Gh6(}Z~8*DogCvnDevFI=MC#Yqa@?nJg9;4RfgSv7Z zD2AGpusvri9UcQ4>siQBe0C_lfG`y`0{}$Cgzg}M1V9I>-JFMLk+9iQgWiIpcbZ!Nk<7OS|BtB_W`&g9 z(%>+SyhKIaB6>-Fpy(6lrRU9ohzHwD%UyYfuCq9xMKD~2P;8sF1wuEZh@Lq1-ikP7?3%#_y5meD!8+FC^8y4GEl7KyTW6YkLMzn zfasO+fl?uC;NUf~>VLabe8u?S|LszpMk56R8BH=;4j%twsR_hAbKyunl`1#$`=~%y z$?1-ID2cvzsP4j*3j6l!E8bfCZ^+tsx9(r%&moWhJX7j;3k_xee~DWB$h%(GeEa2S zTE2M+Fl17!tq&~PA4V{Z7XN=BY9DtVXm&0-7%Bc#whA$V1P9Tvl2}ekK6S;>J-uq1 zu0pi9$zLxL5EE1OV5)tP>@4qscZ-5FB$}KY@epd)M@+1a0jT&@6JY~~fpP6vB$g^f z{(JBn!(eZPGL$qRlP!e^Fg0%F6J-^|_BW*}b=7nnnqxX*}F zH8pEHEj#u8#Fw>(^52$xDp=v#+9bVRu%JnJ{#q*DXS z<>V>_wG#;=9?nKVa;S!3@io#c%rQ3`klquq@(co11QXFF8Su51By)hBE7sQTA?W!{ z50^seGg4+7kP661MKL!W{O1Stxk1F4)S>6y6UR-hPjR!L=PBqcZrbGaIl9T{*&w29T4 zMFbAZf6TOuS*I#q6m$RBxRn<+&O5)ny)7q9`74oQzn~WKx|D@KIHifnv4c@+U?59k^dKQI0zVytXdmfJZ>$M4Zchrw7ZIA6Rk;)MiJ@)%W3w%z9rjFy^GPA4tBb4Kpmx1GR)=6-pI(>=zI{qxhWpKsx`-wPEeoMR zjE9^>IvM&~9o#N<$HX7pYCxp-|6!z0a{m&b%;trA52O{pXT@R}V-%kXl2{O)XiYw# zx*7*4RtY`lPTAjDZBnu4==->Pxy_+nmb--&Is$xrX$|q!6=*SH5%m}bDRLgVt~@!P zoJi3WKmMnN)b6lz>y2p)$f&TJOZ>w4_lV`)`M%%W#lwRO9cXji)_yAFpLW^f;rP^c zDi=YSmuTI`5EZ3KR9Gf3MTnr&w88j5YL_N=`)GW;1m`u~o6=?1;JRoRRU4Mgwem!2yh6XKtZ1-f5K`0)Mo69L=Nf84)x$NJ?@{gdu@r(%KIy&5+EZN^#& z-?#4T$Aq{*p^NWN?G7jJf&9%syPLaqcBjC4XZ@fi59JNR3^a$U7i>_%c^NJ-{|~F? k7Im~O8y~y+>(<`3wU=3.5 +Description-Content-Type: text/markdown diff --git a/fawkes/fawkes.egg-info/SOURCES.txt b/fawkes/fawkes.egg-info/SOURCES.txt new file mode 100644 index 0000000..2868117 --- /dev/null +++ b/fawkes/fawkes.egg-info/SOURCES.txt @@ -0,0 +1,10 @@ +README.md +setup.py +fawkes/__init__.py +fawkes/differentiator.py +fawkes/protection.py +fawkes/utils.py +fawkes.egg-info/PKG-INFO +fawkes.egg-info/SOURCES.txt +fawkes.egg-info/dependency_links.txt +fawkes.egg-info/top_level.txt \ No newline at end of file diff --git a/fawkes/fawkes.egg-info/dependency_links.txt b/fawkes/fawkes.egg-info/dependency_links.txt new file mode 100644 index 0000000..8b13789 --- /dev/null +++ b/fawkes/fawkes.egg-info/dependency_links.txt @@ -0,0 +1 @@ + diff --git a/fawkes/fawkes.egg-info/top_level.txt b/fawkes/fawkes.egg-info/top_level.txt new file mode 100644 index 0000000..83397e2 --- /dev/null +++ b/fawkes/fawkes.egg-info/top_level.txt @@ -0,0 +1 @@ +fawkes diff --git a/fawkes/fawkes/__init__.py b/fawkes/fawkes/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/fawkes/fawkes/differentiator.py b/fawkes/fawkes/differentiator.py new file mode 100644 index 0000000..98b85d6 --- /dev/null +++ b/fawkes/fawkes/differentiator.py @@ -0,0 +1,421 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- +# @Date : 2020-05-17 +# @Author : Shawn Shan (shansixiong@cs.uchicago.edu) +# @Link : https://www.shawnshan.com/ + +import datetime +import time +from decimal import Decimal + +import numpy as np +import tensorflow as tf +from utils import preprocess, reverse_preprocess + + +class FawkesMaskGeneration: + # if the attack is trying to mimic a target image or a neuron vector + MIMIC_IMG = True + # number of iterations to perform gradient descent + MAX_ITERATIONS = 10000 + # larger values converge faster to less accurate results + LEARNING_RATE = 1e-2 + # the initial constant c to pick as a first guess + INITIAL_CONST = 1 + # pixel intensity range + INTENSITY_RANGE = 'imagenet' + # threshold for distance + L_THRESHOLD = 0.03 + # whether keep the final result or the best result + KEEP_FINAL = False + # max_val of image + MAX_VAL = 255 + # The following variables are used by DSSIM, should keep as default + # filter size in SSIM + FILTER_SIZE = 11 + # filter sigma in SSIM + FILTER_SIGMA = 1.5 + # weights used in MS-SSIM + SCALE_WEIGHTS = None + MAXIMIZE = False + IMAGE_SHAPE = (224, 224, 3) + RATIO = 1.0 + LIMIT_DIST = False + + def __init__(self, sess, bottleneck_model_ls, mimic_img=MIMIC_IMG, + batch_size=1, learning_rate=LEARNING_RATE, + max_iterations=MAX_ITERATIONS, initial_const=INITIAL_CONST, + intensity_range=INTENSITY_RANGE, l_threshold=L_THRESHOLD, + max_val=MAX_VAL, keep_final=KEEP_FINAL, maximize=MAXIMIZE, image_shape=IMAGE_SHAPE, + verbose=0, ratio=RATIO, limit_dist=LIMIT_DIST): + + assert intensity_range in {'raw', 'imagenet', 'inception', 'mnist'} + + # constant used for tanh transformation to avoid corner cases + self.tanh_constant = 2 - 1e-6 + self.sess = sess + self.MIMIC_IMG = mimic_img + self.LEARNING_RATE = learning_rate + self.MAX_ITERATIONS = max_iterations + self.initial_const = initial_const + self.batch_size = batch_size + self.intensity_range = intensity_range + self.l_threshold = l_threshold + self.max_val = max_val + self.keep_final = keep_final + self.verbose = verbose + self.maximize = maximize + self.learning_rate = learning_rate + self.ratio = ratio + self.limit_dist = limit_dist + self.single_shape = list(image_shape) + + self.input_shape = tuple([self.batch_size] + self.single_shape) + + self.bottleneck_shape = tuple([self.batch_size] + self.single_shape) + + # the variable we're going to optimize over + self.modifier = tf.Variable(np.zeros(self.input_shape, dtype=np.float32)) + + # target image in tanh space + if self.MIMIC_IMG: + self.timg_tanh = tf.Variable(np.zeros(self.input_shape), dtype=np.float32) + else: + self.bottleneck_t_raw = tf.Variable(np.zeros(self.bottleneck_shape), dtype=np.float32) + # source image in tanh space + self.simg_tanh = tf.Variable(np.zeros(self.input_shape), dtype=np.float32) + + self.const = tf.Variable(np.ones(batch_size), dtype=np.float32) + self.mask = tf.Variable(np.ones((batch_size), dtype=np.bool)) + self.weights = tf.Variable(np.ones(self.bottleneck_shape, + dtype=np.float32)) + + # and here's what we use to assign them + self.assign_modifier = tf.placeholder(tf.float32, self.input_shape) + if self.MIMIC_IMG: + self.assign_timg_tanh = tf.placeholder( + tf.float32, self.input_shape) + else: + self.assign_bottleneck_t_raw = tf.placeholder( + tf.float32, self.bottleneck_shape) + self.assign_simg_tanh = tf.placeholder(tf.float32, self.input_shape) + self.assign_const = tf.placeholder(tf.float32, (batch_size)) + self.assign_mask = tf.placeholder(tf.bool, (batch_size)) + self.assign_weights = tf.placeholder(tf.float32, self.bottleneck_shape) + + # the resulting image, tanh'd to keep bounded from -0.5 to 0.5 + # adversarial image in raw space + self.aimg_raw = (tf.tanh(self.modifier + self.simg_tanh) / + self.tanh_constant + + 0.5) * 255.0 + # source image in raw space + self.simg_raw = (tf.tanh(self.simg_tanh) / + self.tanh_constant + + 0.5) * 255.0 + if self.MIMIC_IMG: + # target image in raw space + self.timg_raw = (tf.tanh(self.timg_tanh) / + self.tanh_constant + + 0.5) * 255.0 + + # convert source and adversarial image into input space + if self.intensity_range == 'imagenet': + mean = tf.constant(np.repeat([[[[103.939, 116.779, 123.68]]]], self.batch_size, axis=0), dtype=tf.float32, + name='img_mean') + self.aimg_input = (self.aimg_raw[..., ::-1] - mean) + self.simg_input = (self.simg_raw[..., ::-1] - mean) + if self.MIMIC_IMG: + self.timg_input = (self.timg_raw[..., ::-1] - mean) + + elif self.intensity_range == 'raw': + self.aimg_input = self.aimg_raw + self.simg_input = self.simg_raw + if self.MIMIC_IMG: + self.timg_input = self.timg_raw + + def batch_gen_DSSIM(aimg_raw_split, simg_raw_split): + msssim_split = tf.image.ssim(aimg_raw_split, simg_raw_split, max_val=255.0) + dist = (1.0 - tf.stack(msssim_split)) / 2.0 + # dist = tf.square(aimg_raw_split - simg_raw_split) + return dist + + # raw value of DSSIM distance + self.dist_raw = batch_gen_DSSIM(self.aimg_raw, self.simg_raw) + # distance value after applying threshold + self.dist = tf.maximum(self.dist_raw - self.l_threshold, 0.0) + # self.dist = self.dist_raw + self.dist_raw_sum = tf.reduce_sum( + tf.where(self.mask, + self.dist_raw, + tf.zeros_like(self.dist_raw))) + self.dist_sum = tf.reduce_sum(tf.where(self.mask, self.dist, tf.zeros_like(self.dist))) + # self.dist_sum = 1e-5 * tf.reduce_sum(self.dist) + # self.dist_raw_sum = self.dist_sum + + def resize_tensor(input_tensor, model_input_shape): + if input_tensor.shape[1:] == model_input_shape or model_input_shape[1] is None: + return input_tensor + resized_tensor = tf.image.resize(input_tensor, model_input_shape[:2]) + return resized_tensor + + def calculate_direction(bottleneck_model, cur_timg_input, cur_simg_input): + target_features = bottleneck_model(cur_timg_input) + return target_features + + self.bottlesim = 0.0 + self.bottlesim_sum = 0.0 + self.bottlesim_push = 0.0 + for bottleneck_model in bottleneck_model_ls: + model_input_shape = bottleneck_model.input_shape[1:] + cur_aimg_input = resize_tensor(self.aimg_input, model_input_shape) + + self.bottleneck_a = bottleneck_model(cur_aimg_input) + if self.MIMIC_IMG: + # cur_timg_input = resize_tensor(self.timg_input, model_input_shape) + # cur_simg_input = resize_tensor(self.simg_input, model_input_shape) + cur_timg_input = self.timg_input + cur_simg_input = self.simg_input + self.bottleneck_t = calculate_direction(bottleneck_model, cur_timg_input, cur_simg_input) + # self.bottleneck_t = bottleneck_model(cur_timg_input) + else: + self.bottleneck_t = self.bottleneck_t_raw + + bottleneck_diff = self.bottleneck_t - self.bottleneck_a + scale_factor = tf.sqrt(tf.reduce_sum(tf.square(self.bottleneck_t), axis=1)) + + cur_bottlesim = tf.sqrt(tf.reduce_sum(tf.square(bottleneck_diff), axis=1)) + cur_bottlesim = cur_bottlesim / scale_factor + cur_bottlesim_sum = tf.reduce_sum(cur_bottlesim) + + self.bottlesim += cur_bottlesim + + # self.bottlesim_push += cur_bottlesim_push_sum + self.bottlesim_sum += cur_bottlesim_sum + + # sum up the losses + if self.maximize: + self.loss = self.const * tf.square(self.dist) - self.bottlesim + else: + self.loss = self.const * tf.square(self.dist) + self.bottlesim + + self.loss_sum = tf.reduce_sum(tf.where(self.mask, + self.loss, + tf.zeros_like(self.loss))) + + # self.loss_sum = self.dist_sum + tf.reduce_sum(self.bottlesim) + # import pdb + # pdb.set_trace() + # self.loss_sum = tf.reduce_sum(tf.where(self.mask, self.loss, tf.zeros_like(self.loss))) + + # Setup the Adadelta optimizer and keep track of variables + # we're creating + start_vars = set(x.name for x in tf.global_variables()) + self.learning_rate_holder = tf.placeholder(tf.float32, shape=[]) + optimizer = tf.train.AdadeltaOptimizer(self.learning_rate_holder) + # optimizer = tf.train.AdamOptimizer(self.learning_rate_holder) + + self.train = optimizer.minimize(self.loss_sum, + var_list=[self.modifier]) + end_vars = tf.global_variables() + new_vars = [x for x in end_vars if x.name not in start_vars] + + # these are the variables to initialize when we run + self.setup = [] + self.setup.append(self.modifier.assign(self.assign_modifier)) + if self.MIMIC_IMG: + self.setup.append(self.timg_tanh.assign(self.assign_timg_tanh)) + else: + self.setup.append(self.bottleneck_t_raw.assign( + self.assign_bottleneck_t_raw)) + self.setup.append(self.simg_tanh.assign(self.assign_simg_tanh)) + self.setup.append(self.const.assign(self.assign_const)) + self.setup.append(self.mask.assign(self.assign_mask)) + self.setup.append(self.weights.assign(self.assign_weights)) + + self.init = tf.variables_initializer(var_list=[self.modifier] + new_vars) + + print('Attacker loaded') + + def preprocess_arctanh(self, imgs): + + imgs = reverse_preprocess(imgs, self.intensity_range) + imgs /= 255.0 + imgs -= 0.5 + imgs *= self.tanh_constant + tanh_imgs = np.arctanh(imgs) + + return tanh_imgs + + def clipping(self, imgs): + + imgs = reverse_preprocess(imgs, self.intensity_range) + imgs = np.clip(imgs, 0, self.max_val) + imgs = preprocess(imgs, self.intensity_range) + + return imgs + + def attack(self, source_imgs, target_imgs, weights=None): + + if weights is None: + weights = np.ones([source_imgs.shape[0]] + + list(self.bottleneck_shape[1:])) + + assert weights.shape[1:] == self.bottleneck_shape[1:] + assert source_imgs.shape[1:] == self.input_shape[1:] + assert source_imgs.shape[0] == weights.shape[0] + if self.MIMIC_IMG: + assert target_imgs.shape[1:] == self.input_shape[1:] + assert source_imgs.shape[0] == target_imgs.shape[0] + else: + assert target_imgs.shape[1:] == self.bottleneck_shape[1:] + assert source_imgs.shape[0] == target_imgs.shape[0] + + start_time = time.time() + + adv_imgs = [] + print('%d batches in total' + % int(np.ceil(len(source_imgs) / self.batch_size))) + + for idx in range(0, len(source_imgs), self.batch_size): + print('processing batch %d at %s' % (idx, datetime.datetime.now())) + adv_img = self.attack_batch(source_imgs[idx:idx + self.batch_size], + target_imgs[idx:idx + self.batch_size], + weights[idx:idx + self.batch_size]) + adv_imgs.extend(adv_img) + + elapsed_time = time.time() - start_time + print('attack cost %f s' % (elapsed_time)) + + return np.array(adv_imgs) + + def attack_batch(self, source_imgs, target_imgs, weights): + + """ + Run the attack on a batch of images and labels. + """ + + LR = self.learning_rate + nb_imgs = source_imgs.shape[0] + mask = [True] * nb_imgs + [False] * (self.batch_size - nb_imgs) + mask = np.array(mask, dtype=np.bool) + + source_imgs = np.array(source_imgs) + target_imgs = np.array(target_imgs) + + # convert to tanh-space + simg_tanh = self.preprocess_arctanh(source_imgs) + if self.MIMIC_IMG: + timg_tanh = self.preprocess_arctanh(target_imgs) + else: + timg_tanh = target_imgs + + CONST = np.ones(self.batch_size) * self.initial_const + + self.sess.run(self.init) + simg_tanh_batch = np.zeros(self.input_shape) + if self.MIMIC_IMG: + timg_tanh_batch = np.zeros(self.input_shape) + else: + timg_tanh_batch = np.zeros(self.bottleneck_shape) + weights_batch = np.zeros(self.bottleneck_shape) + simg_tanh_batch[:nb_imgs] = simg_tanh[:nb_imgs] + timg_tanh_batch[:nb_imgs] = timg_tanh[:nb_imgs] + weights_batch[:nb_imgs] = weights[:nb_imgs] + modifier_batch = np.ones(self.input_shape) * 1e-6 + + self.sess.run(self.setup, + {self.assign_timg_tanh: timg_tanh_batch, + self.assign_simg_tanh: simg_tanh_batch, + self.assign_const: CONST, + self.assign_mask: mask, + self.assign_weights: weights_batch, + self.assign_modifier: modifier_batch}) + + best_bottlesim = [0] * nb_imgs if self.maximize else [np.inf] * nb_imgs + best_adv = np.zeros_like(source_imgs) + + if self.verbose == 1: + loss_sum = float(self.sess.run(self.loss_sum)) + dist_sum = float(self.sess.run(self.dist_sum)) + thresh_over = (dist_sum / self.batch_size / self.l_threshold * 100) + dist_raw_sum = float(self.sess.run(self.dist_raw_sum)) + bottlesim_sum = self.sess.run(self.bottlesim_sum) + print('START: Total loss: %.4E; perturb: %.6f (%.2f%% over, raw: %.6f); sim: %f' + % (Decimal(loss_sum), + dist_sum, + thresh_over, + dist_raw_sum, + bottlesim_sum / nb_imgs)) + + try: + total_distance = [0] * nb_imgs + + if self.limit_dist: + dist_raw_list, bottlesim_list, aimg_input_list = self.sess.run( + [self.dist_raw, + self.bottlesim, + self.aimg_input]) + for e, (dist_raw, bottlesim, aimg_input) in enumerate( + zip(dist_raw_list, bottlesim_list, aimg_input_list)): + if e >= nb_imgs: + break + total_distance[e] = bottlesim + + for iteration in range(self.MAX_ITERATIONS): + + self.sess.run([self.train], feed_dict={self.learning_rate_holder: LR}) + + dist_raw_list, bottlesim_list, aimg_input_list = self.sess.run( + [self.dist_raw, + self.bottlesim, + self.aimg_input]) + for e, (dist_raw, bottlesim, aimg_input) in enumerate( + zip(dist_raw_list, bottlesim_list, aimg_input_list)): + if e >= nb_imgs: + break + if (bottlesim < best_bottlesim[e] and bottlesim > total_distance[e] * 0.1 and ( + not self.maximize)) or ( + bottlesim > best_bottlesim[e] and self.maximize): + best_bottlesim[e] = bottlesim + best_adv[e] = aimg_input + + if iteration != 0 and iteration % (self.MAX_ITERATIONS // 3) == 0: + # LR = LR / 2 + print("Learning Rate: ", LR) + + if iteration % (self.MAX_ITERATIONS // 10) == 0: + if self.verbose == 1: + loss_sum = float(self.sess.run(self.loss_sum)) + dist_sum = float(self.sess.run(self.dist_sum)) + thresh_over = (dist_sum / + self.batch_size / + self.l_threshold * + 100) + dist_raw_sum = float(self.sess.run(self.dist_raw_sum)) + bottlesim_sum = self.sess.run(self.bottlesim_sum) + print('ITER %4d: Total loss: %.4E; perturb: %.6f (%.2f%% over, raw: %.6f); sim: %f' + % (iteration, + Decimal(loss_sum), + dist_sum, + thresh_over, + dist_raw_sum, + bottlesim_sum / nb_imgs)) + except KeyboardInterrupt: + pass + + if self.verbose == 1: + loss_sum = float(self.sess.run(self.loss_sum)) + dist_sum = float(self.sess.run(self.dist_sum)) + thresh_over = (dist_sum / self.batch_size / self.l_threshold * 100) + dist_raw_sum = float(self.sess.run(self.dist_raw_sum)) + bottlesim_sum = float(self.sess.run(self.bottlesim_sum)) + print('END: Total loss: %.4E; perturb: %.6f (%.2f%% over, raw: %.6f); sim: %f' + % (Decimal(loss_sum), + dist_sum, + thresh_over, + dist_raw_sum, + bottlesim_sum / nb_imgs)) + + best_adv = self.clipping(best_adv[:nb_imgs]) + + return best_adv diff --git a/fawkes/fawkes/protection.py b/fawkes/fawkes/protection.py new file mode 100644 index 0000000..a7afd55 --- /dev/null +++ b/fawkes/fawkes/protection.py @@ -0,0 +1,110 @@ +import argparse +import glob +import os +import random +import sys + +import numpy as np +from differentiator import FawkesMaskGeneration +from keras.applications.vgg16 import preprocess_input +from keras.preprocessing import image +from skimage.transform import resize +from tensorflow import set_random_seed +from utils import load_extractor, init_gpu, select_target_label, dump_image, reverse_process_cloaked + +random.seed(12243) +np.random.seed(122412) +set_random_seed(12242) + +BATCH_SIZE = 1 +MAX_ITER = 1000 + + +def generate_cloak_images(sess, feature_extractors, image_X, target_X=None, th=0.01): + batch_size = BATCH_SIZE if len(image_X) > BATCH_SIZE else len(image_X) + + differentiator = FawkesMaskGeneration(sess, feature_extractors, + batch_size=batch_size, + mimic_img=True, + intensity_range='imagenet', + initial_const=args.sd, + learning_rate=args.lr, + max_iterations=MAX_ITER, + l_threshold=th, + verbose=1, maximize=False, keep_final=False, image_shape=image_X.shape[1:]) + + cloaked_image_X = differentiator.attack(image_X, target_X) + return cloaked_image_X + + +def extract_faces(img): + # foo + return preprocess_input(resize(img, (224, 224))) + + +def fawkes(): + assert os.path.exists(args.directory) + assert os.path.isdir(args.directory) + + sess = init_gpu(args.gpu) + + print("Loading {} for optimization".format(args.feature_extractor)) + + feature_extractors_ls = [load_extractor(args.feature_extractor)] + + image_paths = glob.glob(os.path.join(args.directory, "*")) + image_paths = [path for path in image_paths if "_cloaked" not in path.split("/")[-1]] + + orginal_images = [extract_faces(image.img_to_array(image.load_img(cur_path))) for cur_path in + image_paths] + + orginal_images = np.array(orginal_images) + + if args.seperate_target: + target_images = [] + for org_img in orginal_images: + org_img = org_img.reshape([1] + list(org_img.shape)) + tar_img = select_target_label(org_img, feature_extractors_ls, [args.feature_extractor]) + target_images.append(tar_img) + target_images = np.concatenate(target_images) + else: + target_images = select_target_label(orginal_images, feature_extractors_ls, [args.feature_extractor]) + + # file_name = args.directory.split("/")[-1] + # os.makedirs(args.result_directory, exist_ok=True) + # os.makedirs(os.path.join(args.result_directory, file_name), exist_ok=True) + + protected_images = generate_cloak_images(sess, feature_extractors_ls, orginal_images, + target_X=target_images, th=args.th) + + for p_img, path in zip(protected_images, image_paths): + p_img = reverse_process_cloaked(p_img) + file_name = "{}_cloaked.jpeg".format(".".join(path.split(".")[:-1])) + dump_image(p_img, file_name, format="JPEG") + + +def parse_arguments(argv): + parser = argparse.ArgumentParser() + parser.add_argument('--gpu', type=str, + help='GPU id', default='0') + parser.add_argument('--directory', type=str, + help='directory that contain images for cloaking', default='imgs/') + + parser.add_argument('--feature-extractor', type=str, + help="name of the feature extractor used for optimization", + default="webface_dense_robust_extract") + + parser.add_argument('--th', type=float, default=0.005) + parser.add_argument('--sd', type=int, default=1e9) + parser.add_argument('--protect_class', type=str, default=None) + parser.add_argument('--lr', type=float, default=1) + + parser.add_argument('--result_directory', type=str, default="../results") + parser.add_argument('--seperate_target', action='store_true') + + return parser.parse_args(argv) + + +if __name__ == '__main__': + args = parse_arguments(sys.argv[1:]) + fawkes() diff --git a/fawkes/fawkes/utils.py b/fawkes/fawkes/utils.py new file mode 100644 index 0000000..c0c4577 --- /dev/null +++ b/fawkes/fawkes/utils.py @@ -0,0 +1,459 @@ +import json +import os +import pickle +import random + +import keras +import keras.backend as K +import numpy as np +import tensorflow as tf +from keras.applications.vgg16 import preprocess_input +from keras.layers import Dense, Activation +from keras.models import Model +from keras.preprocessing import image +from keras.utils import to_categorical +from sklearn.metrics import pairwise_distances + + +def clip_img(X, preprocessing='raw'): + X = reverse_preprocess(X, preprocessing) + X = np.clip(X, 0.0, 255.0) + X = preprocess(X, preprocessing) + return X + + +def dump_dictionary_as_json(dict, outfile): + j = json.dumps(dict) + with open(outfile, "wb") as f: + f.write(j.encode()) + + +def fix_gpu_memory(mem_fraction=1): + os.environ['TF_CPP_MIN_LOG_LEVEL'] = '3' + gpu_options = tf.GPUOptions(per_process_gpu_memory_fraction=mem_fraction) + tf_config = tf.ConfigProto(gpu_options=gpu_options) + tf_config.gpu_options.allow_growth = True + tf_config.log_device_placement = False + init_op = tf.global_variables_initializer() + sess = tf.Session(config=tf_config) + sess.run(init_op) + K.set_session(sess) + return sess + + +def load_victim_model(number_classes, teacher_model=None, end2end=False): + for l in teacher_model.layers: + l.trainable = end2end + x = teacher_model.layers[-1].output + + x = Dense(number_classes)(x) + x = Activation('softmax', name="act")(x) + model = Model(teacher_model.input, x) + opt = keras.optimizers.Adadelta() + model.compile(loss='categorical_crossentropy', optimizer=opt, metrics=['accuracy']) + return model + + +def init_gpu(gpu_index, force=False): + if isinstance(gpu_index, list): + gpu_num = ','.join([str(i) for i in gpu_index]) + else: + gpu_num = str(gpu_index) + if "CUDA_VISIBLE_DEVICES" in os.environ and os.environ["CUDA_VISIBLE_DEVICES"] and not force: + print('GPU already initiated') + return + os.environ["CUDA_VISIBLE_DEVICES"] = gpu_num + sess = fix_gpu_memory() + return sess + + +def preprocess(X, method): + assert method in {'raw', 'imagenet', 'inception', 'mnist'} + + if method is 'raw': + pass + elif method is 'imagenet': + X = imagenet_preprocessing(X) + else: + raise Exception('unknown method %s' % method) + + return X + + +def reverse_preprocess(X, method): + assert method in {'raw', 'imagenet', 'inception', 'mnist'} + + if method is 'raw': + pass + elif method is 'imagenet': + X = imagenet_reverse_preprocessing(X) + else: + raise Exception('unknown method %s' % method) + + return X + + +def imagenet_preprocessing(x, data_format=None): + if data_format is None: + data_format = K.image_data_format() + assert data_format in ('channels_last', 'channels_first') + + x = np.array(x) + if data_format == 'channels_first': + # 'RGB'->'BGR' + if x.ndim == 3: + x = x[::-1, ...] + else: + x = x[:, ::-1, ...] + else: + # 'RGB'->'BGR' + x = x[..., ::-1] + + mean = [103.939, 116.779, 123.68] + std = None + + # Zero-center by mean pixel + if data_format == 'channels_first': + if x.ndim == 3: + x[0, :, :] -= mean[0] + x[1, :, :] -= mean[1] + x[2, :, :] -= mean[2] + if std is not None: + x[0, :, :] /= std[0] + x[1, :, :] /= std[1] + x[2, :, :] /= std[2] + else: + x[:, 0, :, :] -= mean[0] + x[:, 1, :, :] -= mean[1] + x[:, 2, :, :] -= mean[2] + if std is not None: + x[:, 0, :, :] /= std[0] + x[:, 1, :, :] /= std[1] + x[:, 2, :, :] /= std[2] + else: + x[..., 0] -= mean[0] + x[..., 1] -= mean[1] + x[..., 2] -= mean[2] + if std is not None: + x[..., 0] /= std[0] + x[..., 1] /= std[1] + x[..., 2] /= std[2] + + return x + +def imagenet_reverse_preprocessing(x, data_format=None): + import keras.backend as K + x = np.array(x) + if data_format is None: + data_format = K.image_data_format() + assert data_format in ('channels_last', 'channels_first') + + if data_format == 'channels_first': + if x.ndim == 3: + # Zero-center by mean pixel + x[0, :, :] += 103.939 + x[1, :, :] += 116.779 + x[2, :, :] += 123.68 + # 'BGR'->'RGB' + x = x[::-1, :, :] + else: + x[:, 0, :, :] += 103.939 + x[:, 1, :, :] += 116.779 + x[:, 2, :, :] += 123.68 + x = x[:, ::-1, :, :] + else: + # Zero-center by mean pixel + x[..., 0] += 103.939 + x[..., 1] += 116.779 + x[..., 2] += 123.68 + # 'BGR'->'RGB' + x = x[..., ::-1] + return x + + +def reverse_process_cloaked(x, preprocess='imagenet'): + x = clip_img(x, preprocess) + return reverse_preprocess(x, preprocess) + + +def build_bottleneck_model(model, cut_off): + bottleneck_model = Model(model.input, model.get_layer(cut_off).output) + bottleneck_model.compile(loss='categorical_crossentropy', + optimizer='adam', + metrics=['accuracy']) + return bottleneck_model + + +def load_extractor(name): + model = keras.models.load_model("../feature_extractors/{}.h5".format(name)) + if hasattr(model.layers[-1], "activation") and model.layers[-1].activation == "softmax": + raise Exception( + "Given extractor's last layer is softmax, need to remove the top layers to make it into a feature extractor") + # if "extract" in name.split("/")[-1]: + # pass + # else: + # print("Convert a model to a feature extractor") + # model = build_bottleneck_model(model, model.layers[layer_idx].name) + # model.save(name + "extract") + # model = keras.models.load_model(name + "extract") + return model + + +def get_dataset_path(dataset): + if not os.path.exists("config.json"): + raise Exception("Please config the datasets before running protection code. See more in README and config.py.") + + config = json.load(open("config.json", 'r')) + if dataset not in config: + raise Exception( + "Dataset {} does not exist, please download to data/ and add the path to this function... Abort".format( + dataset)) + return config[dataset]['train_dir'], config[dataset]['test_dir'], config[dataset]['num_classes'], config[dataset][ + 'num_images'] + + +def normalize(x): + return x / np.linalg.norm(x, axis=1, keepdims=True) + + +def dump_image(x, filename, format="png", scale=False): + img = image.array_to_img(x, scale=scale) + img.save(filename, format) + return + + +def load_dir(path): + assert os.path.exists(path) + x_ls = [] + for file in os.listdir(path): + cur_path = os.path.join(path, file) + im = image.load_img(cur_path, target_size=(224, 224)) + im = image.img_to_array(im) + x_ls.append(im) + raw_x = np.array(x_ls) + return preprocess_input(raw_x) + + +def load_embeddings(feature_extractors_names): + dictionaries = [] + for extractor_name in feature_extractors_names: + path2emb = pickle.load(open("../feature_extractors/embeddings/{}_emb_norm.p".format(extractor_name), "rb")) + dictionaries.append(path2emb) + + merge_dict = {} + for k in dictionaries[0].keys(): + cur_emb = [dic[k] for dic in dictionaries] + merge_dict[k] = np.concatenate(cur_emb) + return merge_dict + + +def extractor_ls_predict(feature_extractors_ls, X): + feature_ls = [] + for extractor in feature_extractors_ls: + cur_features = extractor.predict(X) + feature_ls.append(cur_features) + concated_feature_ls = np.concatenate(feature_ls, axis=1) + concated_feature_ls = normalize(concated_feature_ls) + return concated_feature_ls + + +def calculate_dist_score(a, b, feature_extractors_ls, metric='l2'): + features1 = extractor_ls_predict(feature_extractors_ls, a) + features2 = extractor_ls_predict(feature_extractors_ls, b) + + pair_cos = pairwise_distances(features1, features2, metric) + max_sum = np.min(pair_cos, axis=0) + max_sum_arg = np.argsort(max_sum)[::-1] + max_sum_arg = max_sum_arg[:len(a)] + max_sum = [max_sum[i] for i in max_sum_arg] + paired_target_X = [b[j] for j in max_sum_arg] + paired_target_X = np.array(paired_target_X) + return np.min(max_sum), paired_target_X + + +def select_target_label(imgs, feature_extractors_ls, feature_extractors_names, metric='l2'): + original_feature_x = extractor_ls_predict(feature_extractors_ls, imgs) + + path2emb = load_embeddings(feature_extractors_names) + items = list(path2emb.items()) + paths = [p[0] for p in items] + embs = [p[1] for p in items] + embs = np.array(embs) + + pair_dist = pairwise_distances(original_feature_x, embs, metric) + max_sum = np.min(pair_dist, axis=0) + sorted_idx = np.argsort(max_sum)[::-1] + + highest_num = 0 + paired_target_X = None + final_target_class_path = None + for idx in sorted_idx[:1]: + target_class_path = paths[idx] + cur_target_X = load_dir(target_class_path) + cur_target_X = np.concatenate([cur_target_X, cur_target_X, cur_target_X]) + cur_tot_sum, cur_paired_target_X = calculate_dist_score(imgs, cur_target_X, + feature_extractors_ls, + metric=metric) + if cur_tot_sum > highest_num: + highest_num = cur_tot_sum + paired_target_X = cur_paired_target_X + + np.random.shuffle(paired_target_X) + paired_target_X = list(paired_target_X) + while len(paired_target_X) < len(imgs): + paired_target_X += paired_target_X + + paired_target_X = paired_target_X[:len(imgs)] + return np.array(paired_target_X) + + + +class CloakData(object): + def __init__(self, protect_directory=None, img_shape=(224, 224)): + + self.img_shape = img_shape + + # self.train_data_dir, self.test_data_dir, self.number_classes, self.number_samples = get_dataset_path(dataset) + # self.all_labels = sorted(list(os.listdir(self.train_data_dir))) + self.protect_directory = protect_directory + + self.protect_X = self.load_label_data(self.protect_directory) + + self.cloaked_protect_train_X = None + + self.label2path_train, self.label2path_test, self.path2idx = self.build_data_mapping() + self.all_training_path = self.get_all_data_path(self.label2path_train) + self.all_test_path = self.get_all_data_path(self.label2path_test) + self.protect_class_path = self.get_class_image_files(os.path.join(self.train_data_dir, self.protect_class)) + + def get_class_image_files(self, path): + return [os.path.join(path, f) for f in os.listdir(path)] + + def extractor_ls_predict(self, feature_extractors_ls, X): + feature_ls = [] + for extractor in feature_extractors_ls: + cur_features = extractor.predict(X) + feature_ls.append(cur_features) + concated_feature_ls = np.concatenate(feature_ls, axis=1) + concated_feature_ls = normalize(concated_feature_ls) + return concated_feature_ls + + def load_embeddings(self, feature_extractors_names): + dictionaries = [] + for extractor_name in feature_extractors_names: + path2emb = pickle.load(open("../feature_extractors/embeddings/{}_emb_norm.p".format(extractor_name), "rb")) + dictionaries.append(path2emb) + + merge_dict = {} + for k in dictionaries[0].keys(): + cur_emb = [dic[k] for dic in dictionaries] + merge_dict[k] = np.concatenate(cur_emb) + return merge_dict + + def select_target_label(self, feature_extractors_ls, feature_extractors_names, metric='l2'): + original_feature_x = self.extractor_ls_predict(feature_extractors_ls, self.protect_train_X) + + path2emb = self.load_embeddings(feature_extractors_names) + items = list(path2emb.items()) + paths = [p[0] for p in items] + embs = [p[1] for p in items] + embs = np.array(embs) + + pair_dist = pairwise_distances(original_feature_x, embs, metric) + max_sum = np.min(pair_dist, axis=0) + sorted_idx = np.argsort(max_sum)[::-1] + + highest_num = 0 + paired_target_X = None + final_target_class_path = None + for idx in sorted_idx[:5]: + target_class_path = paths[idx] + cur_target_X = self.load_dir(target_class_path) + cur_target_X = np.concatenate([cur_target_X, cur_target_X, cur_target_X]) + cur_tot_sum, cur_paired_target_X = self.calculate_dist_score(self.protect_train_X, cur_target_X, + feature_extractors_ls, + metric=metric) + if cur_tot_sum > highest_num: + highest_num = cur_tot_sum + paired_target_X = cur_paired_target_X + final_target_class_path = target_class_path + + np.random.shuffle(paired_target_X) + return final_target_class_path, paired_target_X + + def calculate_dist_score(self, a, b, feature_extractors_ls, metric='l2'): + features1 = self.extractor_ls_predict(feature_extractors_ls, a) + features2 = self.extractor_ls_predict(feature_extractors_ls, b) + + pair_cos = pairwise_distances(features1, features2, metric) + max_sum = np.min(pair_cos, axis=0) + max_sum_arg = np.argsort(max_sum)[::-1] + max_sum_arg = max_sum_arg[:len(a)] + max_sum = [max_sum[i] for i in max_sum_arg] + paired_target_X = [b[j] for j in max_sum_arg] + paired_target_X = np.array(paired_target_X) + return np.min(max_sum), paired_target_X + + def get_all_data_path(self, label2path): + all_paths = [] + for k, v in label2path.items(): + cur_all_paths = [os.path.join(k, cur_p) for cur_p in v] + all_paths.extend(cur_all_paths) + return all_paths + + def load_label_data(self, label): + train_label_path = os.path.join(self.train_data_dir, label) + test_label_path = os.path.join(self.test_data_dir, label) + train_X = self.load_dir(train_label_path) + test_X = self.load_dir(test_label_path) + return train_X, test_X + + def load_dir(self, path): + assert os.path.exists(path) + x_ls = [] + for file in os.listdir(path): + cur_path = os.path.join(path, file) + im = image.load_img(cur_path, target_size=self.img_shape) + im = image.img_to_array(im) + x_ls.append(im) + raw_x = np.array(x_ls) + return preprocess_input(raw_x) + + def build_data_mapping(self): + label2path_train = {} + label2path_test = {} + idx = 0 + path2idx = {} + for label_name in self.all_labels: + full_path_train = os.path.join(self.train_data_dir, label_name) + full_path_test = os.path.join(self.test_data_dir, label_name) + label2path_train[full_path_train] = list(os.listdir(full_path_train)) + label2path_test[full_path_test] = list(os.listdir(full_path_test)) + for img_file in os.listdir(full_path_train): + path2idx[os.path.join(full_path_train, img_file)] = idx + for img_file in os.listdir(full_path_test): + path2idx[os.path.join(full_path_test, img_file)] = idx + idx += 1 + return label2path_train, label2path_test, path2idx + + def generate_data_post_cloak(self, sybil=False): + assert self.cloaked_protect_train_X is not None + while True: + batch_X = [] + batch_Y = [] + cur_batch_path = random.sample(self.all_training_path, 32) + for p in cur_batch_path: + cur_y = self.path2idx[p] + if p in self.protect_class_path: + cur_x = random.choice(self.cloaked_protect_train_X) + elif sybil and (p in self.sybil_class): + cur_x = random.choice(self.cloaked_sybil_train_X) + else: + im = image.load_img(p, target_size=self.img_shape) + im = image.img_to_array(im) + cur_x = preprocess_input(im) + batch_X.append(cur_x) + batch_Y.append(cur_y) + batch_X = np.array(batch_X) + batch_Y = to_categorical(np.array(batch_Y), num_classes=self.number_classes) + yield batch_X, batch_Y diff --git a/fawkes/setup.py b/fawkes/setup.py new file mode 100644 index 0000000..6defe08 --- /dev/null +++ b/fawkes/setup.py @@ -0,0 +1,23 @@ +import setuptools + + +with open("README.md", "r") as fh: + long_description = fh.read() + +setuptools.setup( + name="fawkes", + version="0.0.1", + author="Shawn Shan", + author_email="shansixiong@cs.uchicago.edu", + description="Fawkes protect user privacy", + long_description=long_description, + long_description_content_type="text/markdown", + url="https://github.com/Shawn-Shan/fawkes", + packages=setuptools.find_packages(), + classifiers=[ + "Programming Language :: Python :: 3", + "License :: OSI Approved :: MIT License", + "Operating System :: OS Independent", + ], + python_requires='>=3.5', +) \ No newline at end of file diff --git a/fawkes_dev/config.py b/fawkes_dev/config.py index a145b13..64881c3 100644 --- a/fawkes_dev/config.py +++ b/fawkes_dev/config.py @@ -3,11 +3,11 @@ import json import os DATASETS = { - "scrub": "../data/scrub/", - "vggface1": "/mnt/data/sixiongshan/data/vggface/", - # "vggface2": "/mnt/data/sixiongshan/data/vggface2/", + "scrub": "/home/shansixioing/cloak/fawkes/data/scrub/", + # "vggface1": "/mnt/data/sixiongshan/data/vggface/", + "vggface2": "/mnt/data/sixiongshan/data/vggface2/", "webface": "/mnt/data/sixiongshan/data/webface/", - # "youtubeface": "/mnt/data/sixiongshan/data/youtubeface/keras_flow_data/", + "youtubeface": "/mnt/data/sixiongshan/data/youtubeface/keras_flow_data/", } diff --git a/fawkes_dev/protection.py b/fawkes_dev/protection.py index f114fb1..10cc0ee 100644 --- a/fawkes_dev/protection.py +++ b/fawkes_dev/protection.py @@ -13,7 +13,7 @@ random.seed(12243) np.random.seed(122412) set_random_seed(12242) -NUM_IMG_PROTECTED = 32 # Number of images used to optimize the target class +NUM_IMG_PROTECTED = 400 # Number of images used to optimize the target class BATCH_SIZE = 32 MAX_ITER = 1000