From d7cf2a53988626d42cc240cd69445452f6fb6672 Mon Sep 17 00:00:00 2001 From: aprilvkuo Date: Fri, 8 Nov 2019 11:40:51 +0800 Subject: [PATCH] dialogue_domain_classification init (#3839) --- .../dialogue_domain_classification/README.MD | 223 +++++++++ .../imgs/function.png | Bin 0 -> 15574 bytes .../imgs/nets.png | Bin 0 -> 37395 bytes .../dialogue_domain_classification/nets.py | 96 ++++ .../dialogue_domain_classification/run.sh | 118 +++++ .../run_classifier.py | 459 ++++++++++++++++++ .../dialogue_domain_classification/utils.py | 354 ++++++++++++++ 7 files changed, 1250 insertions(+) create mode 100755 PaddleNLP/dialogue_domain_classification/README.MD create mode 100755 PaddleNLP/dialogue_domain_classification/imgs/function.png create mode 100755 PaddleNLP/dialogue_domain_classification/imgs/nets.png create mode 100755 PaddleNLP/dialogue_domain_classification/nets.py create mode 100755 PaddleNLP/dialogue_domain_classification/run.sh create mode 100755 PaddleNLP/dialogue_domain_classification/run_classifier.py create mode 100755 PaddleNLP/dialogue_domain_classification/utils.py diff --git a/PaddleNLP/dialogue_domain_classification/README.MD b/PaddleNLP/dialogue_domain_classification/README.MD new file mode 100755 index 00000000..664f858e --- /dev/null +++ b/PaddleNLP/dialogue_domain_classification/README.MD @@ -0,0 +1,223 @@ +# Paddle NLP(对话领域分类器) + + + +## 模型简介 + +​ 在对话业务场景中,完整的对话能力往往由多个领域的语义解析bot组成并提供,对话领域分类器能够根据业务场景需求,将流量分发到对应领域的语义解析bot。对话领域分类器不但能够节省机器资源,流量只分发到所属领域的bot,避免了无效流量调用bot; 同时,对话领域分类器的精准分发,过滤了无效的解析结果,也使得最终的解析结果更加精确。 + + + + +## 快速开始 +**目前模型要求使用PaddlePaddle 1.6及以上版本或适当的develop版本运行。** + +### 1. Paddle版本安装 + +本项目训练模块兼容Python2.7.x以及Python3.7.x, 依赖PaddlePaddle 1.6版本以及CentOS系统环境, 安装请参考官网 [快速安装](https://www.paddlepaddle.org.cn/documentation/docs/zh/beginners_guide/install/index_cn.html)。 + +注意:该模型同时支持cpu和gpu训练和预测,用户可以根据自身需求,选择安装对应的paddlepaddle-gpu或paddlepaddle版本。 + +> Warning: GPU 和 CPU 版本的 PaddlePaddle 分别是 paddlepaddle-gpu 和 paddlepaddle,请安装时注意区别。 + + +### 2. 代码安装 + +克隆工具集代码库到本地 + +```shell +git clone https://github.com/PaddlePaddle/models.git +cd models/PaddleNLP/dialogue_domain_classification +``` + + + +### 3. 数据准备 + +本项目提供了部分涉及的数据集,通过运行以下指令可以快速下载。运行指令后会生成`data/input`目录,`data/input`目录下有训练集数据(train.txt)、开发集数据(eval.txt)、测试集数据(test.txt),对应词典(char.dict),领域词表(domain.dict) 以及模型配置文件(model.conf) + +```shell +mkdir -p data/input +wget --no-check-certificate https://baidu-nlp.bj.bcebos.com/dialogue_domain_classification-dataset-1.0.0.tar.gz +tar -zxvf dialogue_domain_classification-dataset-1.0.0.tar.gz -C ./data/input +``` + +**数据格式说明** + + +1. 数据格式 + +输入和输出的数据格式相同。 + +数据格式为: query \t domain_1 \002 domain_2 (多个标签, 使用\002分隔开) + +指定输入数据的文件夹: 参数`data_dir` + +训练文件: train.txt +验证集: eval.txt +测试集: test.txt + +指定输出结果的文件夹: 参数`save_dir` + +测试集预测结果为: test.rst + +2. 模型配置 + +参数`config_path` 指定模型配置文件地址, 格式如下: +```shell +[model] +emb_dim = 128 +win_sizes = [5, 5, 5] +hid_dim = 160 +hid_dim2 = 160 +``` + + + +### 4. 模型下载 + +针对于"打电话, 天气, 火车票预订, 机票预订, 音乐"这5个领域数据,我们开源了一个使用CharCNN训练好的对话领域分类模型,使用以下指令可以对模型进行下载。 + +```model +mkdir -p model +wget --no-check-certificate https://baidu-nlp.bj.bcebos.com/dialogue_domain_classification-model-1.0.0.tar.gz +tar -zxvf dialogue_domain_classification-model-1.0.0.tar.gz -C ./model +``` + +### 5. 脚本参数说明 + +通过执行如下指令,可以查看入口脚本文件所需要的参数以及说明,指令如下: + `export PATH="/path/to/your/python:$PATH"; python run_classifier.py --help ` + +```shell +1. 模型参数 +--init_checkpoint # 指定热启动加载的checkpoint模型, Default: None. +--checkpoints # 指定保存checkpoints的地址,Default: ./checkpoints. +--config_path # 指定模型配置文件,Default: ./data/input/model.conf. +--build_dict # 是否根据训练数据建立char字典和domain字典,Default: False + +2. 训练参数 +--epoch # 训练的轮次,Default: 100. +--learning_rate # 学习率, Default: 0.1. +--save_steps # 保存模型的频率,每x个steps保存一次模型,Default: 1000. +--validation_steps # 模型评估的频率,每x个steps在验证集上验证模型的效果,Default: 100. +--random_seed # 随机数种子,Default: 7 +--threshold # 领域置信度阈值,当置信度超过阈值,预测结果出对应的领域标签。 Default: 0.1. +--cpu_num # 当使用cpu训练时的线程数(当use_cuda=False才起作用)。 Default: 3. + +3. logging +--skip_steps # 训练时打印loss的频率,每x个steps打印一次loss,Default: 10. + +4. 数据 +--data_dir # 数据集的目录,其中train.txt为训练集,eval.txt为验证集,test.txt为测试集。Default: ./data/input/ +--save_dir # 模型产出的目录, Default: ./data/output/ +--max_seq_len # 最大句子长度,超过会进行截断,Default: 50. +--batch_size # 批大小, Default: 64. + +5. 脚本运行配置 +--use_cuda # 是否使用GPU,Default: False +--do_train # 是否进行训练,Default: True +--do_eval # 是否进行验证,Default: True +--do_test # 是否进行测试,Default: True +``` + + + +### 6. 模型训练 + +用户可以基于示例数据构建训练集和开发集,可以运行下面的命令,进行模型训练和开发集验证。 + +``` +sh run.sh train +``` + +> Warning1: 可以参考`run.sh`脚本以及第5节的**脚本参数说明**, 对默认参数进行修改。 + +> Warning2: CPU多线程以及GPU多卡训练时,每个step训练分别给每一个CPU核或者GPU卡提供一个batch数据,实际上的batch_size为单核的线程数倍或者单卡的多卡数倍。 + + +### 7. 模型评估 + +基于已有的预训练模型和数据,可以运行下面的命令进行测试,查看训练的模型在验证集(test.tsv)上的评测结果 + +``` +sh run.sh eval +``` + +> Warning: 可以参考`run.sh`脚本以及第5节的**脚本参数说明**, 对默认参数进行修改。 + +### 8. 模型推断 + +``` +sh run.sh test +``` +> Warning: 可以参考`run.sh`脚本以及第5节的**脚本参数说明**, 对默认参数进行修改。 + + + +## 进阶使用 + + + +### 1. 任务定义与建模 + +在真实复杂业务场景中,语义解析服务往往由多个不同领域的语义解析bot组成,从而同时满足多个场景下的语义解析需求。例如:同时能查天气、播放音乐、查询股票等多种功能的对话bot。 + +与此同时用户输入的query句子形式各样,而且存在很多歧义。比如用户输入的query为`“下雨了”`, 这条query的语义解析既属于`天气`领域, 又属于`音乐`领域(薛之谦的歌曲)。针对这种多歧义的情况,业务上常见的方法是将query进行"广播",即同时请求每一个语义解析bot,再对返回的解析结果进行粗排,得到最终的语义解析结果。 + +对话领域分类器能够处理同一query同时命中多个领域的情况,根据对话领域分类器的解析结果,可以对query进行有效的分发到各个领域的bot。对话领域分类器对query进行有效的分发,可以避免"广播"式调用带来的资源浪费,大量的节省了机器资源;同时也提高了最终粗排后的语义解析结果的准确率。 + + +对话领域分类模型解决了一个多标签分类(Multilabel Classification)的问题, 将用户输入的文本作为模型的输入,分类器会预测出输入文本对应的每一个标签的置信度,从而得到多标签结果,并依次对query分发。 + + + +### 2. 模型原理介绍 + +对话领域分类器的大体结构如下图所示,用户输入通过`输入层`进行向量化后,作为`分类器模型`的输入,`分类器`最终的输出是一个多标签结果为`[label_1, label_2, ..., label_n]`,它的维度为`n`.(训练数据定义的训练领域总共有`n-1`个,每一个领域对应一个标签,还有额外一个标签表示背景,即不属于任何一个训练领域) + +其中每个`label_i`的概率为0到1之间,且所有label的概率之和不恒为1,它表示当前输入属于第`i`个领域的概率。最后可以人为对每一个label的概率设置阈值,从而可以得到多标签分类的结果。 + +![net](./imgs/nets.png) + +**评估指标说明** + +传统的二分类任务中,通常使用准确率、召回率和F1值对模型效果进行评估。 + +

+![fuction](./imgs/function.png) +

+ + +**该项目中对于正负样本的定义** + +在多标签分类任务中,我们将样本分为正样本(Pos)与负样本(Neg)两种。如果样本包含了领域标签,表示需要分发到至少1个bot进行解析,则为正样本;反之,样本不包含任何领域标签流量,表示不需要分发,则为负样本。 + +我们的对话领域分类器在保证了原有解析效果的基础之上,有效的降低机器资源的消耗。即在保证正样本召回率的情况下,尽可能提高准确率。 + + +**该项目中样本预测正确的定义** +1. 如果`正确结果`不包含领域标签, 则`预测结果`也不包含领域标签时,预测正确。 +2. 如果`正确结果`包含领域标签, 则`预测结果`包含`正确结果`的所有领域标签时(即`预测结果`的标签是`正确结果`的超集,预测正确。 + + + + +### 3. 代码结构说明 + +``` +├── run_classifier.py:该项目的主函数,封装包括训练、预测、评估的部分 +├── nets.py : 定义了模型所使用的网络结构 +├── utils.py:定义了其他常用的功能函数 +├── run.sh: 启动主函数的demo脚本 +``` + + +### 4. 如何组建自己的模型 +可以根据自己的需求,组建自定义的模型,具体方法如下所示: + +1. 定义自己的对话领域模型,可以在 ../models/classification/nets.py 中添加自己的网络结构。 + +2. 定义自己的领域对话数据,可以参考**第3节数据准备**的数据格式,准备自己的训练数据。 + +3. 模型训练、评估、预测的逻辑,需要在[run.sh](./run.sh)中修改对应的模型路径、数据路径和词典路径等参数,具体说明请参照**第5节的脚本参数说明**. diff --git a/PaddleNLP/dialogue_domain_classification/imgs/function.png b/PaddleNLP/dialogue_domain_classification/imgs/function.png new file mode 100755 index 0000000000000000000000000000000000000000..40a236d2dc681ec79ecad68303fbc4ff081db56b GIT binary patch literal 15574 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 literal 0 HcmV?d00001 diff --git a/PaddleNLP/dialogue_domain_classification/imgs/nets.png b/PaddleNLP/dialogue_domain_classification/imgs/nets.png new file mode 100755 index 0000000000000000000000000000000000000000..812003b1fc4b181b33395f18902742929e6f522d GIT binary patch literal 37395 zcmeFYRacxr*Cvb;oZt?@U4u0a!8H)v-JQlAg1fuBySqzh+}(pW4#9?JX5M$r%n$gE zzLQ?nRdrYGU9#=k6|STpiGoOg2mt|sA}#ep83F=Y2?7Fg=L_6tO{GGVEd&H&jis2F zlC+o@nUa&enWc>>1cX$0atiz}6-|QCD=(Jr8K``*zw&W<#mf*fwOpd22vots=yeo9 zh%vC(UzOlZlz#NrL$Krgz+yl|T>1Q~G}0fKS9XrXXlV9$e!1!gntID-*?zlp)87&} zMuRw{i=0*rD29+aG(`;T-o%UlIg9*C)(3?V2Q|?bF$~)2goFDB{mc1w`@S4kxZt^` zzHF)Y<6WO>Dtyua8UnU}XmZ@3w7?sV*w&_p3=@KqFxt9$;vljO%2X3_jckHOJd1Vm zf;=YsFqDHgzyFdp7D_m;P9qQoVl2@jD}Go(_)G~)iR;j@jX%jp$<>Iafrr*!#6!f& zDkE%`#NYQ^|CI8z+N`RLIx?B;XtnWA*iQe{PHCOf`}F8_Z*m5C*18xHang?fMOJ`p zSdO++MsAu%y)4vG+GP58?(UC##TBAPj*Y_Ow80xNX05#Dp3vyE4ogX!0yT$A46J4U z-akkDoNvy5F#cRo$)P%9UlERCE&q{6j!xTWw0lb61xcY^XHGlfj=~NYI8I+fv0US; zaJ)M)Gpl4xn@v3?rsJJ*Y?~Lg!7F5imhym*FpqCJHizz?=>qm%+pz2%`6(EFba7$& ztR5}HMdT`COmaKKh^D#2p*CPVeoX`!;>RfH-Q}7 zSonhqF)NVumMJu2eu?{|mzPxAkq82b@Vf>#sVoD;&VV`uW$X0lUF9s!C4|E>g*NS2 zMzrA%{el-J1d{-AC9!P^AA_g|a}*+fq_tv}FA(`;h*c0aabJ`JAgZ9O`y`YgBKj2S zaFYVWc362375r855VGJ?`#BwuHlPJ|5dOl8?V#X6=k{U2h`5I#&XS2s5zhrgkvWgy zI0f>5^P|O!2sFxr34=$-=bWJ~`$8Wwktb7z^A8pPuNz_~tUE(t0YMP3C8loxQ_|N^ zM_+}+5^QUzzJiAOJ9QAj0g#1sJh;BA+=eKK$~};}tL=ccfz}%^`qe+~E1)0%kD?`v z55xZl#dR2jW+)apl@zveY!eyrm`@QYzPO;V10F$00NV(Ufj6rm9v3xnBWfdxN(gU+ zJ3ygM=(b;c zH^HIOaoqvl0i{h%2iFdrJ7#nr`&z$$F_Q-B)fl25M+w|*EaiMvQ5PE3q%OhQVXOkqgvBwJRL{M+F|E%3W1 z$Twn3_Jh2Z{)(QIrdPE`uSeTgR7A61{F_3)QocyO$X#4K;jqRh4_S!^%tb?gBuCgu3%j zS>^RY*PpgOvP&+DK&sn?zS3`;VllfDQie(FYxu?l^aO$gqR}$Z3ej}(0#mG0{8MmK z=310Y+STsW0$R3O#%rf*kNusicnln-(Ycz~<-Rj=cUi*_Y7#m|qv8RX`k3pq`?Qss z^QO~`&aJXcb<^7w9#XipQ`?fP}ZWZo;buCAbuoGNvu<|NT=^R@2T`laMG(lx`a z*RK6v7O=f0GRuQ*m)G??6+qTqHUml+g2TYAuTi+W@2*cj$U!7Pl(myq$7e`6%Ue(Bm~;kW+V7FQkp?7vk0*-r zrt|<%kr#hUjw49)jG-BnJ!&p3FE%X&UPnK{zP`J>+;yKALkWqb9;DpjHdf>6Q`>PuPeG4pKv)dez`&j<)J!zn zew$splMSnS;K$O{@2r7L@e*+=asAf%*3H-1^U*od($-P?s|~~M{T+o3g)PvQE~qmG zM;7-O2kla?tJ4w9SaCNe)=T}e`=^nnlLe_|u7kc`xu4~&@5LM6qaTwj;~AYBgMFD& z`HB{sqrY?BOWtJvKe39?2vncv@R#{PnX8Er`pt@j!L~uoP<^#fb+=cW7iZgOW%-{| ztrT4pZ<0Z=Ez4^QOC&11ErqBxm`l+_`mV2A2(g$`_apboD0d+Ri91E$gEM_2-xGmD zw~C9Vi<5sG0CRfK90-R?G@3WsSUz0-ZR+Qg?y>iDb2Cp<*4yI2GQ}|`C{;hMvu>aK z#OQtaM7BM3MEkkAFcp&-t)<$=%Zch{BIqIS0aWR$UaNoG;aWV{ygvsVJv+8v+2(PZ ze=wV8I@6ABt+vbUHU=KslmgB7bu&AsKCPl=7pBvWm5U9Z=bq6wYqT^pyL-lylT(y4 zpOx0C+K+F?yWqkk$tx0bfgI0u&AS&{QY;~&XN%{0TYiVv=h6j570lm>5s8vQ>%Nyy7}tl33l+6bwZ+@WJd<5T=qa`ayjyT!fw3#&CnsnX5IfosPt<8SIRI9H zHXm!VuCwM1HwLo^K); z{95c50^28~%cYDJcSr%WH0qyE@Vr%IQmHc9(K+2~yx^2h0_{m~mF z(EjK)-F_?q9EVZB%7cKV6^2Aq@`w7LMbfxc{#B-)(gWtp0m3|3}CF6#HZ$ z;gs}59l8FW-pF}vX#X|f|NaE5><^igs+1M|zoz-`fuLzGq5mHmy08o&4ymxr3;%D* z`XC}+{_h%LNETQ;VGo4i)&G-&&!Im_75wiR{ug`yKdJv;u4;zEH`b=~Iv;g9*K^*O zi8Y*s1;QD-hG5x;5GK-p!5UFS`I4Y96CGAQG-tD@0A?;5*Gxlaltb)PRnEQ5*x0YkE;6&ruz86{*4)8h)_!IJwU>Jmos zx1hs>z)4)FrcTTCe6iL$nET_63SfF16CNJGr50L4%hb^a*V3;f8BRv#$6AG>0g(IK z}_-lS$w@^BQFAN%W1kLHqJ8Q?MGjo@6uEuECY z(HgVhBwYba#LCMAhBh@J!!|=nuX3-uH0l)G3{*T^aoS%U0?dLC9zRZUSN@&+Y$wBu zTp5!4i!p~j2*5@6s$Aez(Wa0KNpylN%my-(P4s75WX?u?c%Bm5cz*a3t$z=bj{~Ig zKxM21?`(n5Hv!hAFh46m%?nc5*2633-+4hyeMMb=;>LeU!6{KU!uD!*54RMKj?jn+ z86l%FinUFq%U4@ev;1CN2)WYp;<{`*fzta%D*BPHvp9=+|1@j=5fhO(B_%a3;ceC8 z!hlp=`9tjFa2YtRN?w?cQ;lLO6*a#TpQ{_~JpJeXj6O{RjF6O633!B?sLVuX0Wt>w z?fxQ;7lWvVhCjRMMc)>!14_X-WmT3V#Aw|Cd;85{iX>E{9YjB5?Wq+4aFs6lh(kPi zLBy#fCVdrrwSB|R)G)roz8Yd!=`OSbDC0u>?tame?Iq=Pvf4c9UyqKfCOHT!!ApHL9l-spDeSt3l;R~(t$P4CX5+qa%mdBzF;jV(!RJw z^Ff*)X#}B2@*)od7T<2S-&YxUer__3w)1OO&MQ@=o;l$DZAzQ*ZotdNp?LTHFv`M( z!bK9hRKC&Q+E~Qom!8B7qIt}ADBCJm3oD9uQqGQx;5-u=Ng)mmwOZ7fM*mWlX?pcO zm|13T6C~(N9#r~`^~xSQW5vBFDkD&#efnI|DY@yVLJjZlT>om&+q?XF9-}lxF^J8b zphZ+|vqf1#KE;bKfKTl$8)&y8Lte+AhI?@3an3|ko3M_-KFa2f$oNR75?OYlosKOlj9WKv3r-2zm}=PeZw3DfECm96r4 z<3ykkV8NR&OTY_Rp&AFY=Am-lb*6WBwn)r15QQ2j}Rfs%$(6K-nX}%?c{|Z=B0Z%GYxxn>G5^z%azJOv_wP zTlhVUyBR9eU<>8}`$l6^eGTJXu?*Kx@=*GyITJX zG(oDWj*PNdH2uvk+zRO|6$_-Umutfli3MuEAqiPR3dttqk~z5~hGMn$52a=`2SifH z&zYHI|MQdCZ>q#e)rP& z2E9??;pe^e^$QD|?!cE{%gsAVmeC&=rZR)%c_bud^W!t)bE+vS4hUs-;u#tO@9o_@ zZCp{>9JG+y*@@=opYkz+jSQ(0P};%P2!aiETftDpWimQw7puRKLRILeLdRyBc9xe4 zp{H0>#;Z~pgHa;A5D3BhJ(1Xvw~^`+Lzv@sEwxTUI&0`y&^87yS2%hjCBm%_xbU z9DY_ZG$CMF%`UnZkg(HDOUe%U3s^0Zb6=F4oh@Ho!>3u-6_b#;p}Gn#V%CruYQ3^UPLTCr$D44w)svCusWsioj#i-eOtHzV*lOL@M#S>#UwD4}U{UjuEwnP)dPfS!MS|2J#-v_)0O zN+o7}mSAdQI~?PeYM8BmmenoT_}TtLq&3bHE7;RU*gUCMApqWw5ux2!*I$-XYro7{ z#9QH{wwtCeDehat)J>O>5NDdH$$4~fYx4cKV6PlyF-&e5#FL_r88V!uZ&MmmREhz8hlU4_^oXva_6_2i9 z9}5JYv#j12`}kkSM|!*{y&aU*>H%kLj<2*j9mnW)ddIn!LRHS)VPdbh=fQxYC8y zjrL8UMa zo2s>=DvVWE!J#w-v@gtiZY{tMD@ZKjFU$d*>J5ux7GQ=4~7_rBis!#?dv^HHlS*SZiwI_i8kdtd8UI`ng( zolHwN(DLIY`LECMMWwy!nrFI&vP*Zzf`rLW_W5s==o|OU!DOKZDjrggVMNh^{2l}i z-sY!uc{QM58MH@#?WhL@*JqFLvDpfZg$Rn_>ax4ElDEP0Sx2eV&h~|0T85{$q1%F~ zLLZ#Qi-oy{##spBBR@5Y5?UV7?~Y~CaA3W$z$G=BhDsE!ljBOeV;Vz~lMiYXu4-8Y z&@~E?fp>UKu48|WP?NsH-e_W&RrwsU63~e6=dPb;2kEj!-M9r{!Ujr`@tO+H^tM=FG%B{b|)c312Y$WVp&zKJxHt5ypxw#bR`N~)c5cdYYYLrGw_$tdy zw@f;6>BlsN59q0z-?`@yby2ZDF&RTFPS))N^1q&*@k(7R%BTRrJeT?^N3K9#;6IqI z*Bwd|$~LY8%{sTUZ&e#BJ2?#-p?7yTj$^V;*~?KZZb>`GXVy?Jl3SGhiGY!!r|Ta2 z$Bk=ZSOx9AmyVJ_c_CiHZQ^fe@j%VU`W{q8K_En?4O10}rNHqmdj5KI;6U$|Ylmf; zxUUjB0KrV=L3Kag2W0E;(QQ!DO&CAB)7nX}sov*?1y&}@Iwd-2YuQu*Q{pV^Ct03t zEnEEc5*d`G2T4E*Aq?$q@gX|QuIN4Pr>}vJ{=K<-x1PcJ?+7j(<_f`Do$4j`UBflZ5^{T$D<9V1eWT&JETscHw|ENlM}mb@?j!%e1u5uKVX~ht4V@Q;ypE0 z?Q8oSDdrr`CY&9PmCz32;-f1^D;4n7&No-j3S`o7*&1<8pbT}73*wp7pN>HjNs_=n z1J(1Ye9f@y+Yi&U-XdxV8(HWWZErE@oBfGzX$KY_b5uL0nI*>YIx8*m`3_ai$ay9B z@pHudyprF4Jw6{{EA%-}k{|2oFC%q)Ym$XCQ>pD;^HrgjlXpLN6W^Q;?eIOZ*#K~m zl&i4+^ar$lJ(qDK>a#sF<#8T$d5h*agi(mC;x%@-{s-4ZF`eN7?(}GZ7$;Z%^YNkx z-;rzXd_7>q5S8xmm{;b$S9q`9Hw@*z>G3VnGWh z=40Yh1U62#8iYm<`C7!`{$JRTmB#Kwu;~)K@anN-!8qHnqq6VM&W3hPmvb?&A}5m z6=BPAd4`aK^-~8puF z$+W0| zUdK$YTaB<2#c~Xfs(U2Q>0TnJBwefs2sjLXZt6-2WDBZ=fLHyvQNU^vg$TJ7T_e$5 z7h+{)qqy7xw~fen|JrRH)LTvgwn|N-QjWub_AFX-OQ=%yWx!9*0{b(0)1SlyxTO@F zY-(_3;e%=4y>urs;(}i6{{**mT8%gSi@F5xQ%(!ZN{<%GN~}gStJMV8(qDGI#j73t zxdI~wJ7-sJ=tdA8NXo;`JA-q#$Hh_$jG3m`8(gyfBkTL z0b;U>#QZelt+lacC7*Qca?-c8AUZ=Xrm5fb88jW@gq|qACS&*p=g~grzT+4dwT1Ni z;pI{#Y6cYI*COcU$xQMF!ZYE?REC#XqQY~^TvT10HUD*wmaSD*llaKJDtUBvrR}Q_ z;DEt%(Q)Kxqdq$P<%DmGo)5-mmC&2AnX6}^C1=dOSU;jqmqNoqfPTPoJ1J(Mrf%5Na zm4?5v=Tn_N=B-DorCd=>8THcu=S;%Ui%fPDmVngjV|(B{m`EEpgsbX(e%HTtI(Hq^ zH1LZI`Ap~GFV-LFMuQ-J;nr!&P1i;6SF6`auu$|_qL(i^B@~h!<6qPqtJtLdWIZ;?N?ejb{IfHen}fMrPRD9?e^SUJof&T z$=~`IGQp%dP_&j27kuNl<`U~-MO=+d^t`pyE?RY+ZN-Y}fq^HMX|y8A1g$>?I2EwI zctPf#W`s88olDcTU{Xps6e;94#Vk=dH$tvI-fNLjeMO=k{-#>nfy2Yl`caCazoE{*0L1@t-9#AU3=n9N`C_(F0-zH>^U zARHQrgkjTOv*`iB-L)`}nP0RN#s^{2Pukpjja_Ly>=6&V|4Zo+uv8tw%)T3Ua+9)*6F zK3RZ))Qu@UFqe^J_g}rfK8vm(A@_QSBAlz;t4wWyE* zQc~egbPTg#Tn%ckZbmfxhhF4Ryr+)f6|xTd=CzW-DE8&0k9dfZ-gRUno0m5J`}1$e zagUQWLy?-d3U?oY_iyUM$nrE`Ut4D8gk55mjuV|IzFQtWk6yUwwI4d2tfVTomIUD6 z&hAH-L(hRwyL3CUx|2*HHC7 z4De?pas!$6#G`yvIC5F((S$7ZxhAS9FeTZKduL|5W|Bf~oLy%xoCq2&FUl8 z2OJ0wp89!?nUVIiJAMJ+N@ufIV|C0U>26TAw*O*=*wY{(uq-@sSXF;&Q2utYj(u*W z>sUk49^<2tT^un>xoV%cO(0@g*hrgynM zgZcb9k0_JgQx|esV*PfrlGN7k1ni86td=+3!-$ZhpzB<1RnA-Ja4Y{1f04DW+IdAw z;^4B(an3M&N-V9xCaG0|k>Gg02c>h7HT%#>tliqK6Y~Ie%Ttor>9TQ&>R_6UlqLXc zXMh({;0{b4Y%F!uxGuF)E(|>wB#2(WD6;$C7!1a~QlZlU?T(MgyW*H|nM-I43i#uE zr-HhsxnLx-kx=kbRpE<8=E)A1JIR%BY_N~ zJE!H+Gg=)pb|=E7($HL&^#<3At5R%SW5zC~6_#R})o|MS%pIVKQPtl$M#T#2SHxjJmmL+gPG#ufc8xp1XJ+R$VX>dWx$SudftIYFvekKO+?+Q&AKn zM~BiJ|9D8x%_F}?LHVMGB>_rF_8%)Qs?f>xb2f8q)qHR~+eqvi4;>6V+PkQJ@zvZzN zjdmb!gXmK25}mjIx#Q6<{x$EW;nmA!H7F{9WwCV@Vt&lw(Akm%j$3Qr zeZ~e6e^1@NaG6cc_HS7ona-JU$Ygjn3wyMe)iy5XK0zI0M?F(Q_U|lG;AM?#D>du` z`9E$t%K%dW^HHj~yT8r#`_0**u21B8n2$TBPHc#ei~S#=h<*%sF@U818%lg-G2r#L zPAD;Iq&S3kZQk$E+=}k*Jb^dgt5^)Rm5tGA!KcaT9;h76A##5wkbA2!jy`@RxyXH8 z=$!S9&U7$)P<5#KYM+$bH!!Q0rSPv~gUsetUr8<=$`!Ij81^j(J^DN;Z(GWa40b@+ zAIOA^7_a$-?lTH`xyR16XYok5)0iNU>Q?Hz>QL|!kW#i)kGsW1-JIi~X?$%C_Kc2B zcF?4FSnz0<&`%`{o+>{izt&#j^-Bgo)3Q+AmYVNxojKLt1%5y9YL5~!)9&!*TH5r3 zTw*2-67^6bqd^RA^+#=GoEXejj~NK;%HQ18TpsY6HoE+WX^?%UXeK4JoEwd+{sCWd zc(~hTFBn{kKhty5S#i83%E8}ThS`Z!5U;qnpNbN1-;5g1|;sIHR}8tFv2 zb#5c^Y>rautrjInf$CAOTV~BEMxwOIF+5DBLKyV*W)pCdC#TdP(}fn z3LGIj)2Fc&r97&CZq<{o%U=+lqEtYABu~~x?5nVY}MqV>P0z)%R7qS^YGUX zj6$oofO}G=l8>TQ)|*(~A^|<0!o)}Z2>}+0`ICHqOn3`eGt-^?w%h6)(a|CmJR@gI~(m{tOTAb?glg5@d4d5IuijT72qnDJd zO6`@hWE;F~%MZRoN@cyFVJQRE?SKwJ>pgg^FCqbJa70$nW|Vk!|6<`C_-(O#Jye+Q zsFAb|d-*P3#^ULiNPNLMSk!6t@4Z6(9VI;;rn+a=1B$dWlK#LNGz6hM;XGJ(1$+y` zkJ4#~)9>(B;sV{x5Bep?sGT@tj+9Mz|2`)Ow|I~ z;br8_8i}IJO)K11I%_u{JU%`s_h?$D;~37>vfd_``N8F0=*fQlFrTrY;h1H& z-5#_%_vd)JvA8BTJGv2pOhW%Io!6U$@9SfHj~m3%#X>?u5EXt7zL+jh0LBa!?CSBb zqu?Lf-28uC+FX691y?tzy*hkke)TrZZa(tk7au;Py5&$+EBNPEpeBQurDC36-i`j} z4v?$(1DV+;v)f4&AV2SCY0t;<6hwu-3i0JX5SCo?sQ9MyA?VLwH3yTsTV?mN`RRbD zjUGh%a=BN^C2mRueb=2hqnVsyNd5L`kejGoasnN}ZA#W}uXR_$MZp)kLSG|P-(C$^ zBenA;SVd~f=nSU*#Eho2EEcrY`QrWGq|xjb2Keiu6)r!si54ucF#ERFd4PtbL@O zM1COFfy7PfIpzoHKaaoRAzI&rKz1~Y0cU=?iK!}K#RQ>}&ieeYNHcvhN%=^?r@z98 zgT&~BmSB4tHnM@xVtvT-lhTdcbTzYrKBlaGm|tFEJKz)Gt11w)?=3&ngO+ zx&nUV9|L-%o7~mcx4Z9Mu5Ri+Pq<-6~RT;0Xo3t|5Ad9EPX^vAxTM7DpDwG*Vn-9 zvde0ZQH?e2USQp#|FC8?&apj6_sQDs!sAB=zVN-Z)j)-$+y0T{<31YceOCO%>!f*` zEs)MN?ZQRh?iCry@15=a(P7&3>VVSA`ImDwtTFpVkQh(rKMy@!q|2vG$X}~H3ws5% z6JLA}nRTD#ex|ALAgY*tXXjkJQ>h>4#*D8QvzM3Uj)3THzT6E`Rpwe7 zrpAVF~SgCtI80!AqQQsk5b@Um+i(zbuXtcH`X%%jf!G(PP4 zQW8ZVJ_Ee7S+JBAx|HLyt3r*$XAx-;2-0xmU}xbBAB1uQ>RqNtRx zkN}}d@oCxaE2s2$o9TD3&F|AHdKI^O$EG_O*`t0g`zpx=8um zftdtSpRz&tl-(B59b<>Lm?l~2H41V;xHu&t1|J_30$@<4i5>iRsP=lK@}iO{5lb4- zerSt#jAtU6nH3e@;w~GhP0FjD^JZcnol*a!Q`NhGEN_#PwGEb<5pR70p2lpKh@3G9%$AV zF_zCmna_c8$rJJx^`m`G$5H&J{KW^pNdh+RZ8x6~if_*E7W$4%Dd+CL9 zbdqZYXs6>n3oxjL7gs2zPm;$Fx4Ws}S64l=@|RNMn5X;G14Uc0KGkSGd0fb3Dc^(7 zs>w?pBVePe0sYp-Q5h&ZZ|{7%k;ul+vtwb6ZJ}krXO$LksLE z$2zEe8e8T zc+)mQG3H#$D=8b#|9rb<&?Cd~=b%*eW6dK?1$px;lOTx^sf$-?BPO#jj}-?5QYmFq z1sgB=JE`Pjym${fVVASR^`~05%}vYc2W`)+>UvkV#AjMCo@wlz05|N$wjx>iJ;ZQ} z)zT#cXC-7;^Mh@&0%$Vw6cUE_o9z#VQx~~leMFdcVe%f1=`C{Y*AwX%vahz$Pgmw> z`_)sSbz;qJEvox9Kx`@eRG99Cab0bC(>C8qCU@mRxj(-)Z5E}{5G>n(t0q~dFA5}G z+J>hjE~4K)1;=wu-xMMF=VkvEcw|L8!}NLN?IP;j`ync}jpPQhao2(}8nTmltm;*$ zEcn2uA?#h-m+rafK?!fwEri?qBRK(lx1JbT-0NEfCe}yR)_wL%(zhzLblM~xUdu$1 zZi2wx5z?4-g(rF;o{-j}cB}OVeT=pD$qHp-jxg+mqSRh$Fho+<-j#bdo)i9@V_ZN5jU_yF zsnlJru_^DDkCs-*c8}eU7jw>)6CTYC@SGi%{bE5`!SalM%&(_NC=nV~0AX*AeXqX( zk4Au-RpGT*b9@Yr8n8Fd*P7!va;|u&|pAP6PS>oU4$l zxs3Uc{*LhyA;>InoGjoUyU_T{I-yy@=$t;uPK?8VGcrof$ACJz#TO>&%BLu_xjf~D z^#w5?hRbGk)n69ze&t_F(@aT}6F+V1@)A(1=#B0xn&0~BP0`@1N4UnL9eawr`jjHO zQlySlSfYq_Q=R^SgM0vRN;RmKLhJL+7DVQz|JR)*TRwFVG8<(Z$aN> z$Awtz^>aGOUpqx!>{8=1zXW@IOE91u={9cGN4(n{)b@w+;IL@L*Hcj7zA|cjwaxRU z{MgaAQrZwrz7*CEzFgQAlf+N@ji+UL%X-Py*mhr=I%*(eJGLRZ>iCu2%S@0Y-H3E) zLs>Qaqmm?m3iOjPn}gT4vF09XcYO@|a|<>a%t4{@pIa~}L0X61nez;9L$#Y+cS?d4 zJ*@ATb;281+MO8*9{=muW?6tt%eKkpskey0U$+=`kK_e&H}CB5$U{N0q7j-98?%?( z6dz{GmfzEDt`wfrzQ|x1a2hroD86qqn64c|JW6`Fswj<6`X#1`z+_k(yrzc69oR+# z`do7>$osr$%a|o~PDWm>Q!x00Yo~K^l$?_emcxcZS*zaEhmM}bDMr2Kv<#rF#93CsSei2V$Z$L3ZGdktQ3PPj5T=MUYiSM!#TZ zD?QDaZ;+Cr2$b~I4dZ2x3(k13V{F*mVa6S}D2y0~KsT2zT+F(TF_*$$6d>MG z5Ew36T$5-4yNZA$1LCmbBzX*9LIm8+^gEf{wRXRJf>}<8B#ArYYV&JW4!HzP!(6Lp z<74$suoP7I6pPhA*_+2XHo9^(mb+T_vUaKa0JwHk6R$A65Yu!)y;1!3`HlF)x2^pT z<;VTaA!ZDnq>~3LVQW?!b#uOZuGh(l;C)&4L(A_EOEHfHy>$teFejkP5rsSZ#pSG%JoVTF<9zx-9`ZWVm!BdTg-2VKO35l62;NoyGQvT(gy=?I)M3Y9Cig;O>N7VzS-V!HE&%N!f~>PbXr@A_i% zcPp*#J==+LdS?Ivk>Nfn}F0dy@^34nOVT=PF7s-_Q_2Bh>3IBb=K;ITu%@5%bTuTV(_nW%`F7N=d1{fU7);om^Hhs+>CW_jTN9K41Z=F zo24lnX&l3CeXYv=8@Jv@6Cr+kBRV!QyQ4nq49Yl{24HPg>1VK)T{pUHO%^ZZ*?|lcg~K(I^}%J2 z`%p;?(jUaM(>xe0LWR8Qa*Qj1M}ZYU+?QP*$m}8m5w+~I>1v*f1oKB z&Me%zDT$LxzPA4)zCda5p3Y|aT(%Mwzxb=J?%;eeZP9gz!M?mL3Z4*kV*fn4 z2HGP1Ij|-iXjj&!=qzyhGy-tTun-Y^=^F5EBFHejXliwVc|MZZI7(k9jL)eUecIST z&iNG(EC&I6xZ{Y!s|}h}!gb_Ro-x-TV3Sv7wvrSwPC9iPrb~il=6B-<@bS&gs*z5t zewnmwb!jS?w;965l|!C<6}KwUI1X$*P|a2mVEj)h%6qjT@>nho3CBt72g5X-&o$Bk zd-j}s_uxt(!;!*39qJvMFtr%9|D2T(_@Z*Up;_DaLXZ%)^JpP0B+hIf95{`T%|2T z*)9W795Z?1=$IpV9(YE)mgwDxO_*QY_juu1c3r28tP8wVsdusj><#?5I(_U^nR*=$hiKEMeFd(`DC_<*!Diw%!?*uQ^SMeg5@v525f zMSpj=yG-sm$4z+U1{Ny4HeuTVYvO?*#i}5M5TM;mut=uA)#}sr)U82Fnqi!?j6(mn znb{?n#3n>NzeD7MC^^)a=u>kBQF?!wsMuk9Ux)PS=E8P~Xe(Tdef>Kz(to6tm0VLa zXp>T0y6#}fR0@D64`~JZznuQ0pxSH>5_M2!pldt8{QCmTe2Mr$OIk5sP<{>8{7CG; zTRFK1TQ-w3q0r@X473?)_jpLBct|^J2YLGnfD1dvz^lRDubXHchy*}rpt92Mb zTIf6&v8<+nsaY~EGdW_tR>;qtdnB@X^i_+RiUCrzs ziiyH5*lQP4-b^y(q=X-z;NOhqi#dM7VlXsd$z-T#|vro$+^bO58GK7nn?vcg4;Vmsrgkfl7Ruu06ruxB%;mk5L_|q7v=@{;+(NinXl#@pe(2Bx5=HrI{|sH& z!P&GxocvV^P=8v(a3CRs3QeGNYbc{bq5I4%;D>>lvalmH?&u;@S2ar|BTGR_!HOkb zAQ8&`JxQ8-0<(p=Vfyum5HGnO^EAJXvdR7aQY;pHNW~T60E56MROcx5_vqOAK2X`s#E~2Fv!D!c@#3 z@cficAeA@25fif{k!E7T#c42f&?WbJ6q;%z^DafIa%hxKKC@~$oN-UO-;TmJ`>pWy z1Y<5QQZc!5e>`jnJOs);Tv@aX&?Na{W7W9K*}jUY-|GPIb)EF49GYFrUFy$DSzP;F zs<&?ViHysZR+oy~7!ep1i$~D7zBBQv9s`QFWkZ_)z$PxK&^ra~?3a^<+VQbo{_uxd zS8z#*#DSOLzQXJCV(hHh`R5}M@=*X#fo`--cWx*O{kq zaG26uN065QP_xVyugCg1yhPu7}?xtss2d2-P! zU7S<3YuA?F-nFax;??5aWvr3O$JvSx{;aM;LCeh={J3GsUEi0_Vv8beMK`yaKaPyB zN!3e(A8$_Sb!8o_EKVq@DStmFpK6Sbnwq-_Q<|t=)^n$tmH2t<$z-2kmqGEWj)JCG z1+IQ7zu-6EfP-U3q-nq3CQT=Q*a|f2X6+`Vk>$LM{gGP>&wT!3oTb}V_IIFjsiYqw zjd;xaOfoh^zfZO4J6}2iqMXz;*dwnn;a49GU#XtIOBV|warK}vDY5eL!uGd(%rG#|#0%$=8xv^GEC z58c)f+6ZZ9`-`_$4c_m2o$3w0148Zg%|Jp~kUekX9vS}h$-|kH;RiaZ4}`dW9AiHh z@Yu((_$r%cvk~vkm(Z%KxM{qi@`#*DOkShBQsVRSgloK(QE;puYO!y;W!3_YDte(z zpIO^GwmsfXmK{vFeS(6A5nNuoKVNvodwuzKn{etrh7KB1eC%0!Pg10siVh}0_-6f) zyj!ZR@38lKRT6Llu?4=eQl<1@pFy3OOib08vF!X*HA=DS+8$Rt7#ej8g}zcPjaEK) zZBEHBbM3y#o=p{{Q6tc$I;n&lIRQFfEge3gnVt8gp@i_KWc9R1D=iHp1JaJ_1gLRj zjOpf4bT^sNU=oZ}9ko+)gwwL)c2W6>mxuS5eBvDwGOcvbk5ptfT8Z&qcY83-dtNVC zwPJ!M9&_+o2fjh0!hQ9GPidyKw@`P;uv-}Wk~dz*YtGh7wd&PskbsKp)zYN(p{tg^ zKP3)YLL605Juxh2kz7Csd8q`A7Pjk|37nWLbHPIxj<`hw2NFk7G7lpOdeCM__5KlG zErmL;ZK%3zq))ZDBo}n3`7xU_RVg*qm(&Lq-|c6C?E+AQ3KZk!PHIf_-X{0C?#wI5 zZps%#kob~Xwbe9dO zoi^&{?QaI~)G1QM#ZGqK+JR5q+m-5UKPb|+ zgnAU@SZm|fvYUqTv6_PwQOf*xQ#07hfDqLRMf!5MfOcnQ+nP4D4PE;Fd}&?! za?Tb$>Dlw#)|vXOd6T`4M+=@%e09#jecN+V#dqd&D@u=AuQ+_1*tU4E2a(8;ze4Yr z)YLzURI#0>7z9iG%v9%|?1PJYnjM^*w7kEd4`zO6-X)XV&iwdCm&Oyk!}ERf_Dj?w z1?`r3#PUn4cNSLjh^q;q@VWBZSqC+nsBFNwTCWOwv1?xsye=hpL2Wn5jJeH0R@)(Q zA=L=Q1OFXN=KgD34>?ch#aD6tiiP0?3sjj|013_s%{BB$*wqRdeoC8M$489tNim-J zwjSUg{0B|%Urwz4HdX~o@H{j(y`6YNDy4PG2rs?N1S20*QeSg~UWZGpiLquaf~LIB zJ_=*M#GTU$NAC$AT%eZURWNrvSd4Yo6|K9#b9gU^>-lg}!oXDZ;_$hrt0m)-96=n6 zW;6;7S(u1-{m#M?6N7|*$JA$MBh|^Qcd_fg@d1QZhng_(8?Y`!4VveMdNkM zZ;h-op25B#OS5?SZGwM=5L2VNMFdu{M$|Lwph0QKVC}aDz>Npi{m3uj!S#-P3c3q7 zewXngJkm(K4T63i-0}uTXUx37Y2g>hY~XU-zARtkv~4w;rSzQ*!|mT*`UF&9DKEKK zQQgZZefmr@UfH{JT+j2V8n7JV-@%eEH+XUk-fNGr@jF`_?Mo`dD(ec7q&53utT)Kq zU1e+{n(~tNX3oQ+_hYU<{G?cyhH$h zhJtW>%3kt?w9A!ip>2+DUp&R~Cy+hgrGYHeq{kxLNTLdR7!tIZ*9lZe(BH?T!zln0 z1KD@W{_CW(8!~Vr`Z*Rx`d_WUCLNT#UJR$L#D8lpAm^a32wI=@zGR{W2>D0n{Mmp+ zcT+oVB>Z&%`{)yJEE~y4{p(+?0pbA1!&MIYng1#tS@wDMoE(3+7VfWB;j6NVX&-u??0RJ)_vV8w9 zNVn%NfBo8kR2YsTkiWpg3O@h>#M8!RVdaF#UJHt~PX7FQPc=LNTbW2L9I*DGx22pf zQng{cW^j-eZ^_Ru(KOw&s5q}BG09KTQz$3B!a+?=ULG~r=6l@*27ZJ4AS-H`rw44| zTTmXsPHJk(nPrvP^EpKsSkXJ5k{EMU@ z#{ZFXKVeXnwz};h;@z6#CXr%$$_8J@;KUytiiPH4`1dk|f3KMM_cn}w+JC^7{Cmg9 z|KsaWWHcDUy#V%s*j`m)4qR=W>XP$l1|{4mjx)U^GtjkCnvs)%%`UFc{Ygu*F*q3d_o|jtS9tO%F<135_61HMC|D4| z+>O%i*m(FCiRgR>)$&hOi}~Us)U9%dHZ!wNLasy{vsfg#47wNNICoCl>iuz1xGC`l z#pEEbfVTjckm(%gmnCR~DIDFa+^7mW-kfp}riy%V+%pp(hDV`PKQoJ}Nis11wa~Lt zw5leF-&HOPMi%{U*{@`I(lV)UGWR=;v;`Y1c^ZIMta$<3{#iC{LGKq`>J{1IjP?8a zuS~@H!WijWao_t9SQDUEXtk?Uhh<&mRWlQ;wv|X%f8uKn-u`H78jk0M|5^s%H+oui zgAnKy$~4s!@QJ_2@|8o(b;jf# zQK)Wwg+?K11=FG^%=c5nw(%|JDLJxv=Zpx8bNc+0b3&!EMS+6;W?UB#8-(|GQ~G}B zZa)&P`(uSL6#Um8z`$123LD>`b*M{k`{22%9&|fMGe!Hxw&({bDg?cbP>1^jkQ6)~ zH`kHME~WLI3?k|PMBkV zb*5t1ue2RSj12wMq@dvWA@N!Om5o6hbUIb(!#-WwhzUw;6lQ(=atr_mS-h@(%Z$Qj zRdL@X5JSMlXAyul@@Vv$XlNAb;n_v6u*;YpKAW+tq#U?Kq_}hXx8FCbjMwCjfC~!f z6R35fJ{tG}QM4lfX;(Dc`tf1g__c3~WtBNN%~@0ZMPlOK!1FhH((1r)^rP=h0}4O` z2Vm2$p#W9G6jT-zu!O3RTMcL)RW)S!Iq6SP;mDxOlNMu=`MONnDb)HAV9Aj;00`XH z=o!xq|23$!=SHzki_mZ3vMmb~j|3k8lDB)Ekwv8?!AHdnfxTaYfv&$(P+E|~5~?lh zH9^UIEDH~5otGnc4V%HNnpDX6RcA1c1YVjz9a0`{+QXoQ3diWhg2_)vNlDpn)42!m zdh|7{&$#8&6;PiA0EXm7q1HtHv7N{<(2ewOV>)}qWe9>HJy@?Wm_j)BU(5|a832V( zMdr!;1-Esmf%OYUh*kOf_NF^3ASh``fdHO=bONUfFjPmqwjb+%`vrzr)&iOz&h|2-h` zF9`2zt>T&gH=Y1qYi$9V?{<5}{~FMX9;mEG`{mnL`1i~o(1x#A*|YzQw?dKDncSD7 zJ!piBb%Fac@ca!_Pd0eo2h=BwnUSKjZgwsLE_h^d?8m?|XcDKdkG`AL1 zalazjW*+9_5F*}6V^(!({;qVRvQ~PKZ8nZa-ML`eE3>DPSh{GGuR<$ zLOCG8CQsK|=gGpBS>sXr#{g62cBB2TGy3PM60grh-Zr|sJfmV^s>^JT zeIge)p}E(FVN5c32iRRyHeb*K>`ZqMl&md-^{K>|wSwqSzf`l#D>d=(?|xK*mu}#j zlGUS*FtQJp{0tUNj_AchTO#QvMhIBumJ-t!V6h>LdOI!(pbV6ObW>+PP=aIBzh-maKqzPD z>=oLRIPrTwQ%fUEkBS$1@)EE3b&r+CaBLHfH}HAe3yMQ%vLFY>5NUgdwSThO9{ql| zu9-FKNJ{kZAoDJJP+>#`+TwmD-l*;n!Hgk~J$dTTc6jkR ztc&;~3}!+RsW!m2ka}zTL6=_p6R{`RgR_L%45w_g?U{d)uGUuQQD1Yg-+k#Y`6WBI z=FaT81dT-}{!)H*);m^**NT#7_sA!MeuP|&O*73BAA4%Wpff+;Hl%n=6iL>j7J{u_ zWRp(w7SGzkO>tYJCQ>#wAenT9v&?z>)hq55(tmNyyQ8%bkP*O6;B-}7zsMi>IwTfS zeeDoa-EC8p%WzR`rRHtx27FmAjz0?2PGL;>9(cncGwnfToGWtGF-xurc`h1s-* zpLzAKR3r7{{a4+WY_X2`%@r#MjW!)9a3q;)Q&gi~q*QwvM1{klQMZ|R)UEu6N0q0~ zEHstYG*(0FrdJLsYuOl|7S8EIE3KO*fi*gWBRX^hMN&b(=5G5Z4k)Y->o;SZ8S$Ko za1!-1xg=ys@l;j)u_v7otLM>oHwP*T9CcSqhC0P<-+*gWvmy_+NDX~eR2$7KGs!fz zF9_#1+LzxLN=(j87LJguu71NE-kz}Mqe|fX5>CDClJupL&C%uUuTGORm#g^Kioq2$ zo7NU!zzG6$ppXKFtqneDCJV4S!%75LmtZm}!4^5-R%643XM9l@v8yFQf?vMDsADqb zEUzH&v~%N6TWIejR_;d{&6n?qs}rvAyjRt?`FCQH9KN;50_@u#>OfP3vXdeuljO* zOiHw@M2*ZxjXH6~cu;fqk_~}36Jiy;=rI5zwE^3|2N9$9!rDJW40~IVFF?A7zxbwk zhM`aHD~8b5y#2F3C~0EQ@*q%Q^L7&55>Fqqb$;1ELUq0ZPd&I$_e=Vg&3r|di=D$!#Y~pM$M)?PGhAIieSK9f z<0Dx2*fXsiAp@s>#uGm$JNNb8Ua@1hN?2?%BclT>6|MH!4ana@*h>PiHzP1H9>U&b zT4|?srg;S;OIHRws?Oe`91{^6mC12W&Ij3V=M0s#f)5Q}W@cJv7vA#MRnf7dZ*`;$ z708$#fFjG@`CX)d`sOXl7IrK3ifzwYQtXvyUteuSMzSeedPx>|-5s)Y{xbcJoWqx0 zjdpKONImQFe#+clt#B&Qi0f+kIxg}@dt>2?`O}J_(s#x{!M$&?Iw4`@S*ktKaBZNKJAIzCndnu28 z{X|jbQdywLtb&z^SG!nC87S6GE$HZXy2xtBlcT96Kx(mhm6G2(_dQzIGGvr^>!T$Y zhZL`%Ju$Jbw0OF2S<>(G)NUcY6EkpV6DLH=bqWJG^Pf%HSY7pkb+C^=e;$ch_W_kl z=JD0RRVyY=isTJm#Q@S%;*8)cEO5&m{L21jHUNfD2(3=JOO{)Y4KfX+!oK6YWJ$!J zOY(Ew;PfFY2%bRRKfUJ;soEdz^D#(P`W;-w>~yltH!VNsfmePydK`&Os_n-m*UO_) zX1iuFU%`yBiyyyQi`47xET)Fdb92O&vZ(GadRa2RxGvUOVUYe>aD?JI!nKe(`5t#e zz8UZ|%kwXS>{cS3_1WXr$w)Mz$%RHq-@Q)$INTw;oEzk~(yc?SYK}j;V7~5VI}7~= z?Uvql6^`q+l~u}Bg%)s)(%Nh=NkSnj%B)j=PMh&jr$NVZmLz(%QE#StFls)o@dP)o zQ}!MWUsQa>@cZGzE%FL_8 zgp!`sHz|!ouZ)kENix8R+n0>x@&(blCdO@yrh!K$zKv4W)gD!A@h=^8mNIb)YxiD@ z<9xt{efO6BFoNPl_g5@uU+IQrG`Wq$Vt7(nPjeIHvX-3e#J0cFlZi z67|<TSduL%xNRT5apEEqLd!PJ#W%fEy8J!VWuB zF++;8dtUpbQEOP^S>Tm;W!KpLcYv+ZfgRif)GC~&)M9{)&1D_6@GcM$J7wO-K7T?eyI1wcz3;vju7SZE z5|W^Am(PS!GunT@ z%BwwYl!@acfpv+U%%ZD7UkMG7AZH`PaMZGWVe>9?ost4msrwnWlDdJQu`##9Uf$0J z0UGi=`X4aGilJyH7=JJb1g8g3m>5I>2E$WB`*!2`bn~@5mdwJ!6s4HFYm~ppi|`}6 zXCI>`bu{vB@BBtZ=Z{4RCuv25dIsM)U0ANWLzCUU9jU!G#jLiY$wrJ);!F&68J%lB zC$F|FYHe~97jWp&G3MUA9()k>kEY7jcFEZJlJlvt5`7yWvYSt-`By$xM?r>-a4-d&46R5V{`^244A6ph6!lw(SJY&>>D;!)6M|n z?e5}IB++#IWbe*vtf*A8j)YC@kEFb4TG8kQZAn)L9ZaJo^nCYv-9ikx{EeG0N)N7q z)BF&E+N1!ye-%w(3sEO+X=tF1lGB^$-32}l!RIZr_RXC4=2GUuKkr^$O>|!P&tR?V zRJ+ebCnaM0m~{~#{*uIvS&~^Ui|%8uxhaYspc zUFPr`HFO93zQndJILS`^1Mh_hbjQ%{ky#P?qqFO$OAyweGxxoPSs4Mdh|lO{V3W&M zX%5pB=94UZErx`HG@KUf5C{DXj(@GZ0*7cN{`E8q$O;BCy#(x86vxe%l0$rn_Ti`g z?{DsEsQ31!Meo(oMy{@f!Wm7x@C#J<1=s@nUWa_DVjAwPw8tENv?s z(V10lf-xHO91PB2c();5ZK8V zyilUWdp^N9=+3YNUUS6erwuMIVU!~`{=let#?(KeMu;2| zy@a>+o~8u1q-EsyN%C!9hf~-0B?dOWj9Vr5Me`+D4s9jY`$Fs5pJjT$Sw<%fPIM~x zi6$XcW3<3uZ%nCfGhI3Gjr6aqojELq2(3f+53e&fqs96nPurF8Xp&LyAX{hztFV&R zFyEZ8p#ckw8bI!q)i>CNrgG8#d4KS&8Jhr7)%MyRpYVIrL}?~v66151n{2bLd&w7# zcY?Sqjc2fn(TY^e`r@gc=TtV~+yUot11CpXQ$1UCB>gN1_z}C5oqsmHhQ&Om#$CLQ zXdq%BjH+C3#$plU>T4!v`0imd%TJD;c-5P$YuZ(C=*L;|7oD0C+8F=6huT|A8AY?( zKCoFitc5|LY85s`3psy!aBeII`1K@roVQ^LcYu@yA-h>9%Za&sHk{!uJUp{oTh!`-akYXbfbbzMOkfl*&(v_?!OzI~0>nmqH zifpLNo2t~+VhhZxCM~Ab=5|%h#fm0cnnouz%`JaseHK7LK@0Rmo+SS6o-9HddD|}z z{pqc+pB2Mq($F!9_+XN4%F^LIGAVD0ZOUeIP1V-MJ7&u$V|pSXRyY0Z;{4pnKvvQ7 zJM4*Bhd7yX;WnSqFH{@o1Mh#R8Q4i39XA=bBUBCt2b6vAd{U-uAy)M76n4W0L@T9dm%xkf`hS6vS2dVoXW zbVKMN7AqgCvZ7LDt|rFhxZqra7Rxz4+xi+8-!?mhndFl`zfg4jVB2Mx97pqA*Nv#H zOJS`kbIsX$NVnX`!DqS|J{L&!_#S^Se|Jc+upoi3QI``Mhh4olYHo0Ie4iNadkD*j zkSZi%kPz6tG1kATte{i@_OSq;EUUezQ?W`wPw1ik?ComIt)%dXuizF=9!@gF&uwxj zb~7Kt>|j^k3~0FAWr;>#AZuMV_^6)Dzc_7?idC1+z0O%rl=%<_JttOqlyxB3=6y3` z$+A8AYTINMjqNJpCzktT&zqAfCSzy2q5?%4je3z?*>;!BeV?G5?r$FM;ADq5XGR|C z>uGz$W=gFmF{uR+g5dB! z+6d^8p;4uur%NXOvr$_N;JBIt_9vqMzD(fR3WAdcz4^*v4iTOIJnaeCsWh`0bC{6- zZF>g&`~wJz8F{Dj+dmtt0gO|wUxUK*-~hrlmXy;+vN>whTn?V34iKpTR~&y2j2UTYYcc ziwug6NGjBptMVx)M1z^9K{m^m$?y81GA3A$?Y##`rXoOU^$xAKKx-59!|PPZL=M@a z0p=H8J;d|3WM2KrgvfQctS9$wzeI?l4^BBqXiflXFaT=m(jrArpmN~U!4IgupJ_~r zmo}rKx}QT$!81)ADkR2(R!426?24ei5qWxY10u3FW62-lNd-HY!Y)Y`N+)f*k5}c- zSIk6U43k8F%<`@cW=GV1^w>9nQMdA)Ea6LFCgH$Lf;bZ8QA!&?vJqz!y9Pp!6ZOsI z9rN8?MQ#pn>p6ANUX4#`EwXP=;f(k-3=j=eBgE>R!PGkex|TYD z=3p-U^)N+Q*+2vmVN9zp3)hH*8BhH+nJ%W`RE8DfyrnC4PsI_0+EaSKODVg%e_o2NC;i{HuSS654) zRX25azgxO7o_K3CoCrIO&Du- z1z1YR1S+ya#`QOob3HipU;mIq+_fG3)H(nVYH{FhBLuk5kO3Oix@vwk2YC}M1)mE= zX3Fxg0Ul`PBIn+7#W^xC2O#~|NmbR3aR%0#SW(t?QaUKN+ST82U-8q)~-3Jq3uxC(R z@X{dmYEzNdfK4uqZH-w7ana?AF22{3VLI|i)+*#Jq@GyZNo#uhLG|f*Aq3$0dRGxd z$n#;X9yAO2Lt6B*n9bAP-F&BRH)ne&f%CmxeoqDylmTcMiKrCvOMsB&FJ4NM2XbQM zz>kk`>=F2Sus2BKALLHehnXPF4q^N;#IK_uX0&IF7BXLuJi+wa$u*DJVmkMm;VN&p zMLin&niB!RoHm_`X^j7I63?jt8{LWvI)yN04i;=m8W_jHq81rgQJ)ncuAh>;GLgDS zzI);B@WzT78^WL^5~eellU3mBd7!g zU=lD@dBEk!T43=(zHCvJ8pwt4*lLEq(9dyLaPQt7fyGQ-ylKU3-?8SNLQd%cqqZNLh0-`Pu%^V4=cc#L=n}8 z6GK+$KYsw8geRwpGJgKw7T|9tS#`9~W5RiS?k(jQCy{~5ap zpgmiDQesF8n%g7Gqc6Y)SHAsl!HP z_J_)^uR=f&SHbzpX%CgU?8%?vP#(3DX^r`X^+vCX$k_zSJ*YlPPxxo|ru;xQdc(p$ z^w2@^gCZrgEnE)bN^{&sMj~(G265O}K|O74`=+W$B?3JCyRXra^}&aE?!+ zkJhxfr8K5QL52AKf{fPy;FX!Cm*4QAVAQFOx+@Cg>q}@kPNJgvkvfa-eXPEAs6Drb zXOiCul#-mN{DwL!w7hB#X!ddd^n9BmKOrK-3%-?FZa2R@+r?Ovu~sikUggd&-|_(5l_J2gn5x0oB$W~kl)dvkcuugU ziNG`wLphE$j?0=-R4A@hGPDMQSQ4)kAi8j=Ucm*v5PX15aW>Yln`}BuTI(-#&3lDO z^yy6_C^t(b9NOffr<3FI?D#o8G75wph>&1TSSaEJFdko8VTLBJt4mgSMS1kjkfYG3 z-i)D_Lsh}zeEsG*>Fyw`ZFJQ5rkEA<6IYP{PtV<2iVZ*lVScYe?&JIfpAT7jA>tiJ z0TiELpwDkn8FqQx*Ao~OzNaP=$e;jY5G=n2aERdE|LCvJaek- zn8x>y6u*d&Eld^U1}r3~2d9`ZB*Qk}Ciy_{pw)XQ_}up8BM2n=P7VRgH;Y4B3W6^6 zvK4WG95Oc~C~HQ!v$Vc*X9K>Q6ePZ3*Zg}#Up_yBaz!{|Eoiz8+XA8$5@_E00nz;; zlkcnz-CRR$S_`z>7d$yLqw+trb*qY-jp|85aA!A9*`XjvXSo~N4IY0cfC$23C^RPz z+xV3GQ{bnsHmI1n4MDo;#~o-!VTIRvf~kIpC%a-wso}L=Z!rN1wQYfUCOQ(V0Fhq> z>}PYG1=C5l6PKnFgR+gt3fJ4Ma?`@aqH5`&1Nw>s0Lh0Pd`Wsr9t;CDI`UymjS&5M z-PkHe*Z4zXj_NmKC!ieyyr{R0SneInN{r{e*S__oR{<{(xiSz4;i{M@q?L+o4MN6L zazd>?{mO)dRIigjvJEpYik(#jAvR`9L|e(Zi1v1;Wc^&9-24cJB>pkwR*T5{s5| zJ$J$?Ro=I*P*_G{#AGBCqLe7#icBn38tv}gxp+W4`w@Y zWRI%j0J9DNBH?2)$Z86Mym(8db6F?NV|h!YDKvM11(E6<`<^RQf#<3n#IyBlqj$Hf zAh!?vSh;3@`Xi#oy=q!N4RF{)y!(Xjk!{d4qOB_5@p?P-$|CEI?ECQ$%$D2p`?xJP$Vlf z9C0DYoDan7bi%B!7pHbv?vKC57Rh`&{z>qX?=1~@H zUL$ASRaL=ihi!qLou#s3Cy)7NIg?_fWK)w@!xf|D2ie6L_k7EPbw#KYc0rvkk#B}n zYW;)Wc22>0WW*JGc`=IH*$wGJMg8Ga9X+>Njd(?|)jpQ3mgbaFOkDe%{bieNbdp&o zO^W-sP+f=#G=zkB6VdIl6Ij#3Oyo*&Lb1v2oD~Rz<`SDwyC3+c#n9dzcjp27Yblfp3cJ`xh zFKe}Rqg4}3&acHJN50CGS+_r!dlfvT9PNN;cp@48_DN&kDp%;##L#M$^e4GFkCoE| z5T;E{%2N%zwqw3JvHN_sdGuYBr%*B38IkgYI%+Diu(45{`|4aLyY^$({$hGT>Ru8P zVQ#~LtC-`GOT@yl!!A9ju=2so0_UVgafK zcesKn+J`bC`@U&Sd~mlHUZg9b#s13o1QQ1nesJ}}d7#R-_k1? zNbBUPeW!9t!OX0%&EBPg{!k%hIb4xlHL@u=s*CZPLSCl00iR07*bs)uZ+l7$H>Wac z8nLCk(&;C$oU%O*G6Qcvt6+*sV94whg8z1mK>OKsxk-_#TJdJA!i5Df1(}lWk9W!) z#hmAjpV12ET?j%$H*U@nG1IS4T_t+=);uGo)_`9Y6T)xOoXFWI_dXYxc)#9g%__USuzBN{;g;+3tWQ zy7a?G_8m89DlFki`umn+l6nu@CBgD9A{}6j(=Mbcr}GgB;gNHfZ$})cX41Cpni}+* zPRTsx@`PXcUqf7I0Zie;R|hoO9tgr{LYuB`5S6;SG||7YjoQGUpBuY5tVQ9o-yyJN zKW)FBYmjB4Vo2u;l#=5l4gT&Ny0y8aRbcqsIDwl%fO+CWXNsMutbqw}FzOrZjS;`9 zs(gsdKM(BDmXs7ELttrL{CMldnr42xL~@ey{{qY-gp zbVz>&%=@vq1SomkC{9on`f1LiZC#R3QT4r6n|P@(8?=U0=~SpyRgj*~uY$?5aC0q% zv-_(SfB--igB9?y3u&bYfB`F&B`Dt*bL>x$+BZn+jO85d&AzO$UvKo@I+b9?zKqN3 z3qp0y&;C8(XupOH=62FZQEV=iYY6Kb+57d~1Z%F}Mh0&^p*L@a`l2CwO(h4Z|FzqL z9GTI!!u7`9cSY{=(3nHZ6=6CXyUFQkcL!{x!^s~h;scCRBG?MKRJNk$X7i?enDVDw zGd?wmYRF-zpIHwr!Nwuzw2YDZ#NCtfY6g>TCX={YY*Icp_49{u1jm1T{tLhtvYs)p z6MBL^BiyY-pQ~1YV;O7)5~Na`!2P_vXEuJ@CtJdD1)9LmU{Mwa7oEr}O%ld$rdxp1 z)MKgR@+>9yHa=wpCf6UNt7_PSs~ya9M-P%h!=kH6o66EJYX&9Q3+St><<*iO$|uF; z(-&sl1e$*(%9KiJCFK~#%6CQU$?3%vvZOIE2FjbEzd-yd{%Im{KpYRnd844bT=!Gn zVv=oen6c@b{JLs+MpCq$Fp*=|1nk5>}zgX<@M-CW__R65%6}Kd%+%#B8tVb%&e2iH?YJ%5A|Hpx|O948M93FSCxT2w;aldf%4D?)$$%yT32CTfD5S_=d6idx(0|7+D z?R8$I176e4(YO1-;OKNyy4|?tFOLleE+|HY@?y3{7PWiwA+*8VR2#HV<&a$STOm)B z+>UJoLLh}1`>Ig2hL7&S{0F8p7G+pTg;^QahulNznT!%~k;z{Xq;mZtjGI#9^Pfvh zCM_q3O!;~(a+P+HD6lL^HxjK^h!@;HgV@`V66ol#?8uQZvqVD&rl)q98gG8(Abi=3 z-5+V+V9$T9R+OSX@)4d{x3Ks&wWcalYUr~s^KL?_LQSL!hKcU?+<-N#P&qZ0|CCbPayH(aGI$JV=ribhYOC6z5U@pH z-yaVI599rT(YnK?Vk2@0ReG}MdVB^vwS>(Qo0!*zw1+v0JgI)V9*R^@>1J-`w9{L` zxriT?!+y{9jKTHcI03CmQ@Ib^atlJnEm5QfehzyqCx4m?!T#Kvkmg#)Onm42^M-@1 z`jn`62krrk+m_uA4lWDQReh))MfJDYwoyC#%E5h-uaK#LRbQqDG|%6jd>b%}+%R~q z2NL@XjMMs?VT`6cBs)l*;V2}jgYik(g9&+N6M_r(qCS#r?u{1geO#AzOlsEY=*ege z?DVDX{^*Vki*ilPw_mdG5_y$VR*24rVHH%bWX+(@@e)~VH9C0aXV0cccF(@^DulfG z0IS+=K-$c*-`77yhmPxXP@p&CT7SGQcs(Y|yf9C3Wnr9nGCMw@DDRCGL7IGDfMMliWcy5P_ zz8E1}aZE&o3UrM*4VuibDw3fOx+}y3??k6N%{gMrujlxi;#HmMvK4<-M#)Ye0oP1v#xBr?Y1SFM|~tVYmQeb^CP=4 zR>Y>99@NZzV%<5-%?BOQgz0)Z0Xd;~Uwb-k0Xhww7_zKB>szU6(@XHyu9bmdL8imGv^ab1}+q*(_8 zic;mPP526(0vjV^i+Zv_L0s+j+Zo2Q;I4Y24%CMEvb(CfYlB;2=k!hY zW#XZx5Yxhf&Xynr!p@P{0yLnt74GhU?r^(FB1?3LLcTobhxhw9=`TeuBAI)Fr%{ol z4#`u#5J`lyIu_4l!d^CJogOF`)No8jyoulL!A|IZ{298|4;o({tV(r>_NPd(;GW?U zLc6`nQ)v;YDcbAYx2{^hxjDV%P!$F+B(O?78=6gg)zkwNq5Dg9bqBhK*~Z5bxIZ)% zvTkl{H4HMzXYyci84(iJJ?;DU-!a|j8me9EtC?0L0K029l`{3(gU5<25^7J%D+DC5 zIiQg(A+x$CDJ{vDdAuGM1yomSTc!PGw_M0MS@qsVAdAJLR*lzVhg+k3>&?BJdtqAM zqGpbH)yt)a>J^X4oj}W`B407xu@L|MfRK_?O~0)}9Y#!NBsIwtyt_$?1V*llk$04H zMTx0<)PXK+IeiD_RIJ-w1^rB3BLg|kQ$yoxQ?KWE_g(ZZgAoMdUy%`(c&k>`?^eia zE`WELBtl;ER55RLU79Nx|1PCf+uotMxhTLDP*QBhQ&t{c*VxspF3^xr;QW1_xfKd) zt8&`I;i3w}zA!E%!`Ed~1wQO>fcF9sfV~cRj{_4D)uQF-HNTz79iL=jqc!A}64kW3 zF*prWE`}F1pGu!Ljwk42+bx!d;t~b+^S?1*thv&6*o$ba*LyJ1@K6NrHFZDa+brC) zNALy4-RGPgt#)osQc&F!FVNFCU~3)+TdQ(3Nqc}Te9&8B^E`N2S;_=*3)RIqQZ!C5 zFt&ce>dKF^FUPJ<nZ9h`u0$!dvJv@x+92j6&cp-7`vC9ply{`a%Tx$e7qYJ+33rcAt0MuuStUY@u~}n zH`fVXA4l!EcF_N(EMfZ&(@ z7|zVy4%jmH6DPa2v!A#%J@G!D%M8=~g1bk?ftnMAG?ui%#CJP$mUlb>r{v=^+?nQ>U{rc;a- zH|yqPC+TJTEvdS9PvC=(B)HwW7u)yfBSq&Q#Do0Isitg#vOiiH7c1EEWj*MpI4hdA zwQ_mbzeibwC=&7(vaks&HrA8X=n~X!7jiwHvAk2v!>sFN2-+zB0F*@O4dmREpZE3( zj~wDRN?#a-=1etp`oHlEC*5`|1~R_F z$7;V2CKw;h1Zl)SY)5_M*klrJ^(qzAJoz~J9n?jPR zA{X$+D+MOOw3>=$;>VmN7iYLZ%^`*Q1;iE>V*qjKJwDVkWKdcQETL44puvKvvALY! zqA-v*nnvD|OsgxN7atr+qtmkW^F!*{IY9n1QQ|mYL~nbz6##VH!#>>nA}w+$(;p8d(pGCuor-?9DJXDESO9!y+JQQ~5~i`Z$rq$X%sR8!Q;A)TV3uB=q;YZYF=!v^5B>F4VyKBY?a>2K1rgw=QI;M0>sdSZ@Z!QHzu2sd&Muev8Ph)f48$gnMEKW49u}y4Gx&` z9wD;I={9E=t&*Br!<~=N42jt)&NHv>1gQalSdZJic&vz6_Gc)=#7{wk2e9vZ?oT)c znAs`#yX`8Et)fduN*cpjSuW#ZdxB%2CJ@NnCYW9t&2~U*I6Qpw=Z$lGQu?>5p3s!( z4gSjjkHcY2b`s8gwD>38M3n>)0qkj`^~TPO#$b{=kGpz9?yV`$8#|9-NWg2w1fBAO zJ>C}52P*2Lc9R+tPJpa!fV-g_R22pJJ}Vi9lhP3*vUZMYATEwSa(VoZMmHmwnx z_=ADmuoblH#bjP%h9*!h!CX&Ix3Wr<*6|joRGJ8h9TG203wZIg@U&i;4i%mDq-0MW zKvw2SvuWQgW0J?(vu*#`@Bh;bx&y%H@tpNv^;xyzz?k~DlJ!sT{m%z-3m_GD4oQF2 ze9^uG#9GBa@~JN2f5w69paVgnwcr~5a~#h;V53}pawYSruI)c09Rs@%ot9nk{~TwT n0YLWulKCGN{r{Vrk1tURXcju)ex5x8{)q@k@)v*B_Wr*B@+sKl literal 0 HcmV?d00001 diff --git a/PaddleNLP/dialogue_domain_classification/nets.py b/PaddleNLP/dialogue_domain_classification/nets.py new file mode 100755 index 00000000..77912b3b --- /dev/null +++ b/PaddleNLP/dialogue_domain_classification/nets.py @@ -0,0 +1,96 @@ +""" +# Copyright (c) 2019 PaddlePaddle Authors. All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +""" + +import paddle.fluid as fluid +import paddle + +def textcnn_net_multi_label(data, + label, + dict_dim, + emb_dim=128, + hid_dim=128, + hid_dim2=96, + class_dim=2, + win_sizes=None, + is_infer=False, + threshold=0.5, + max_seq_len=100): + """ + multi labels Textcnn_net + """ + init_bound = 0.1 + initializer = fluid.initializer.Uniform(low=-init_bound, high=init_bound) + #gradient_clip = fluid.clip.GradientClipByNorm(10.0) + gradient_clip = None + regularizer = fluid.regularizer.L2DecayRegularizer( + regularization_coeff=1e-4) + seg_param_attrs = fluid.ParamAttr(name="seg_weight", + learning_rate=640.0, + initializer=initializer, + gradient_clip=gradient_clip, + trainable=True) + fc_param_attrs_1 = fluid.ParamAttr(name="fc_weight_1", + learning_rate=1.0, + regularizer=regularizer, + initializer=initializer, + gradient_clip=gradient_clip, + trainable=True) + fc_param_attrs_2 = fluid.ParamAttr(name="fc_weight_2", + learning_rate=1.0, + regularizer=regularizer, + initializer=initializer, + gradient_clip=gradient_clip, + trainable=True) + + if win_sizes is None: + win_sizes = [1, 2, 3] + + # embedding layer + + emb = fluid.embedding(input=data, size=[dict_dim, emb_dim], param_attr=seg_param_attrs) + + # convolution layer + convs = [] + for cnt, win_size in enumerate(win_sizes): + emb = fluid.layers.reshape(x=emb, shape=[-1, 1, max_seq_len, emb_dim], inplace=True) + filter_size = (win_size, emb_dim) + cnn_param_attrs = fluid.ParamAttr(name="cnn_weight" + str(cnt), + learning_rate=1.0, + regularizer=regularizer, + initializer=initializer, + trainable=True) + conv_out = fluid.layers.conv2d(input=emb, num_filters=hid_dim, filter_size=filter_size, act="relu", \ + param_attr=cnn_param_attrs) + pool_out = fluid.layers.pool2d( + input=conv_out, + pool_type='max', + pool_stride=1, + global_pooling=True) + convs.append(pool_out) + convs_out = fluid.layers.concat(input=convs, axis=1) + + # full connect layer + fc_1 = fluid.layers.fc(input=[pool_out], size=hid_dim2, act=None, param_attr=fc_param_attrs_1) + # sigmoid layer + fc_2 = fluid.layers.fc(input=[fc_1], size=class_dim, act=None, param_attr=fc_param_attrs_2) + prediction = fluid.layers.sigmoid(fc_2) + if is_infer: + return prediction + + cost = fluid.layers.sigmoid_cross_entropy_with_logits(x=fc_2, label=label) + avg_cost = fluid.layers.mean(x=cost) + pred_label = fluid.layers.ceil(fluid.layers.thresholded_relu(prediction, threshold)) + return [avg_cost, prediction, pred_label, label] diff --git a/PaddleNLP/dialogue_domain_classification/run.sh b/PaddleNLP/dialogue_domain_classification/run.sh new file mode 100755 index 00000000..81efc2b6 --- /dev/null +++ b/PaddleNLP/dialogue_domain_classification/run.sh @@ -0,0 +1,118 @@ +export PATH="/home/guohongjie/tmp/paddle/paddle_release_home/python/bin/:$PATH" + + + + + +# CPU setting +:< 0: + pred_pos_num += 1 + if len(actual_labels) > 0: + pos_num += 1 + if set(actual_labels).issubset(set(pred_labels)): + tp += 1 + true_cnt += 1 + elif len(pred_labels) == 0 and len(actual_labels) == 0: + true_cnt += 1 + try: + precision = tp * 1.0 / pred_pos_num + recall = tp * 1.0 / pos_num + f1 = 2 * precision * recall / (recall + precision) + except Exception as e: + precision = 0 + recall = 0 + f1 = 0 + acc = true_cnt * 1.0 / total + logger.info("tp, pred_pos_num, pos_num, total") + logger.info("%d, %d, %d, %d" % (tp, pred_pos_num, pos_num, total)) + logger.info("%s result is : precision is %f, recall is %f, f1_score is %f, acc is %f" % (eval_phase, precision, \ + recall, f1, acc)) + + +def train(args, train_exe, compiled_prog, build_res, place): + """[train the net] + + Arguments: + args {[type]} -- [description] + train_exe {[type]} -- [description] + compiled_prog{[type]} -- [description] + build_res {[type]} -- [description] + place {[type]} -- [description] + """ + global DEV_COUNT + cost = build_res["cost"] + prediction = build_res["prediction"] + pred_label = build_res["pred_label"] + label = build_res["label"] + fetch_list = [cost.name, prediction.name, pred_label.name, label.name] + train_pyreader = build_res["train_pyreader"] + train_prog = build_res["train_prog"] + steps = 0 + time_begin = time.time() + test_exe = train_exe + logger.info("Begin training") + feed_data = [] + for i in range(args.epoch): + try: + for data in train_pyreader(): + feed_data.extend(data) + if len(feed_data) == DEV_COUNT: + avg_cost_np, avg_pred_np, pred_label, label = train_exe.run(feed=feed_data, program=compiled_prog, \ + fetch_list=fetch_list) + feed_data = [] + steps += 1 + if steps % int(args.skip_steps) == 0: + time_end = time.time() + used_time = time_end - time_begin + get_score(pred_label, label, eval_phase = "Train") + logger.info('loss is {}'.format(avg_cost_np)) + logger.info("epoch: %d, step: %d, speed: %f steps/s" % (i, steps, args.skip_steps / used_time)) + time_begin = time.time() + if steps % args.save_steps == 0: + save_path = os.path.join(args.checkpoints, + "step_" + str(steps)) + fluid.io.save_persistables(train_exe, save_path, train_prog) + logger.info("[save]step %d : save at %s" % (steps, save_path)) + if steps % args.validation_steps == 0: + if args.do_eval: + evaluate(args, test_exe, build_res["eval_prog"], build_res, place, "eval") + if args.do_test: + evaluate(args, test_exe, build_res["test_prog"], build_res, place, "test") + except Exception as e: + logger.exception(str(e)) + logger.error("Train error : %s" % str(e)) + exit(1) + save_path = os.path.join(args.checkpoints, "step_" + str(steps)) + fluid.io.save_persistables(train_exe, save_path, train_prog) + logger.info("[save]step %d : save at %s" % (steps, save_path)) + + +def evaluate(args, test_exe, test_prog, build_res, place, eval_phase, save_result=False, id2intent=None): + """[evaluate on dev/test dataset] + + Arguments: + args {[type]} -- [description] + test_exe {[type]} -- [description] + test_prog {[type]} -- [description] + build_res {[type]} -- [description] + place {[type]} -- [description] + eval_phase {[type]} -- [description] + + Keyword Arguments: + threshold {float} -- [description] (default: {0.5}) + save_result {bool} -- [description] (default: {False}) + id2intent {[type]} -- [description] (default: {None}) + """ + threshold = args.threshold + cost = build_res["cost"] + prediction = build_res["prediction"] + pred_label = build_res["pred_label"] + label = build_res["label"] + fetch_list = [cost.name, prediction.name, pred_label.name, label.name] + total_cost, total_acc, pred_prob_list, pred_label_list, label_list = [], [], [], [], [] + if eval_phase == "eval": + test_pyreader = build_res["eval_pyreader"] + elif eval_phase == "test": + test_pyreader = build_res["test_pyreader"] + else: + exit(1) + logger.info("-----------------------------------------------------------") + for data in test_pyreader(): + avg_cost_np, avg_pred_np, pred_label, label= test_exe.run(program=test_prog, fetch_list=fetch_list, feed=data, \ + return_numpy=True) + total_cost.append(avg_cost_np) + pred_prob_list.extend(avg_pred_np) + pred_label_list.extend(pred_label) + label_list.extend(label) + + if save_result: + logger.info("save result at : %s" % args.save_dir + "/" + eval_phase + ".rst") + save_dir = args.save_dir + if not os.path.exists(save_dir): + logger.warning("save dir not exists, and create it") + os.makedirs(save_dir) + fin = codecs.open(os.path.join(args.data_dir, eval_phase + ".txt"), "r", encoding="utf8") + fout = codecs.open(args.save_dir + "/" + eval_phase + ".rst", "w", encoding="utf8") + for line in pred_prob_list: + query = fin.readline().rsplit("\t", 1)[0] + res = [] + for i in range(1, len(line)): + if line[i] > threshold: + #res.append(id2intent[i]+":"+str(line[i])) + res.append(id2intent[i]) + if len(res) == 0: + res.append(id2intent[0]) + fout.write("%s\t%s\n" % (query, "\2".join(sorted(res)))) + fout.close() + fin.close() + + logger.info("[%s] result: " % eval_phase) + get_score(pred_label_list, label_list, eval_phase) + logger.info('loss is {}'.format(sum(total_cost) * 1.0 / len(total_cost))) + logger.info("-----------------------------------------------------------") + + + +def create_net(args, flow_data, class_dim, dict_dim, place, model_name="textcnn_net", is_infer=False): + """[create network and pyreader] + + Arguments: + flow_data {[type]} -- [description] + class_dim {[type]} -- [description] + dict_dim {[type]} -- [description] + place {[type]} -- [description] + + Keyword Arguments: + model_name {str} -- [description] (default: {"textcnn_net"}) + is_infer {bool} -- [description] (default: {False}) + + Returns: + [type] -- [description] + """ + if model_name == "textcnn_net": + model = textcnn_net_multi_label + else: + return + char_list = fluid.data(name="char", shape=[None, args.max_seq_len, 1], dtype="int64", lod_level=0) + label = fluid.data(name="label", shape=[None, class_dim], dtype="float32", lod_level=0) # label data + reader = fluid.io.PyReader(feed_list=[char_list, label], capacity=args.batch_size * 10, iterable=True, \ + return_list=False) + output = model(char_list, label, dict_dim, + emb_dim=flow_data["model"]["emb_dim"], + hid_dim=flow_data["model"]["hid_dim"], + hid_dim2=flow_data["model"]["hid_dim2"], + class_dim=class_dim, + win_sizes=flow_data["model"]["win_sizes"], + is_infer=is_infer, + threshold=args.threshold, + max_seq_len=args.max_seq_len) + if is_infer: + prediction = output + return [reader, prediction] + else: + avg_cost, prediction, pred_label, label = output[0], output[1], output[2], output[3] + return [reader, avg_cost, prediction, pred_label, label] + + +def build_data_reader(args, char_dict, intent_dict): + """[decorate samples for pyreader] + + Arguments: + args {[type]} -- [description] + char_dict {[type]} -- [description] + intent_dict {[type]} -- [description] + + Returns: + [type] -- [description] + """ + reader_res = {} + if args.do_train: + train_processor = DataReader(char_dict, intent_dict, args.max_seq_len) + train_data_generator = train_processor.prepare_data( + data_path=args.data_dir + "train.txt", + batch_size=args.batch_size, + mode='train') + reader_res["train_data_generator"] = train_data_generator + num_train_examples = train_processor._get_num_examples() + logger.info("Num train examples: %d" % num_train_examples) + logger.info("Num train steps: %d" % (math.ceil(num_train_examples * 1.0 / args.batch_size) * \ + args.epoch // DEV_COUNT)) + if math.ceil(num_train_examples * 1.0 / args.batch_size) // DEV_COUNT <= 0: + logger.error("Num of train steps is less than 0 or equals to 0, exit") + exit(1) + if args.do_eval: + eval_processor = DataReader(char_dict, intent_dict, args.max_seq_len) + eval_data_generator = eval_processor.prepare_data( + data_path=args.data_dir + "eval.txt", + batch_size=args.batch_size, + mode='eval') + reader_res["eval_data_generator"] = eval_data_generator + num_eval_examples = eval_processor._get_num_examples() + logger.info("Num eval examples: %d" % num_eval_examples) + if args.do_test: + test_processor = DataReader(char_dict, intent_dict, args.max_seq_len) + test_data_generator = test_processor.prepare_data( + data_path=args.data_dir + "test.txt", + batch_size=args.batch_size, + mode='test') + reader_res["test_data_generator"] = test_data_generator + return reader_res + + +def build_graph(args, model_config, num_labels, dict_dim, place, reader_res): + """[build paddle graph] + + Arguments: + args {[type]} -- [description] + model_config {[type]} -- [description] + num_labels {[type]} -- [description] + dict_dim {[type]} -- [description] + place {[type]} -- [description] + reader_res {[type]} -- [description] + + Returns: + [type] -- [description] + """ + res = {} + cost, prediction, pred_label, label = None, None, None, None + train_prog = fluid.default_main_program() + + startup_prog = fluid.default_startup_program() + eval_prog = train_prog.clone(for_test=True) + test_prog = train_prog.clone(for_test=True) + train_prog.random_seed = args.random_seed + startup_prog.random_seed = args.random_seed + if args.do_train: + with fluid.program_guard(train_prog, startup_prog): + with fluid.unique_name.guard(): + train_pyreader, cost, prediction, pred_label, label = create_net(args, model_config, num_labels, \ + dict_dim, place, model_name="textcnn_net") + train_pyreader.decorate_sample_list_generator(reader_res['train_data_generator'], places=place) + res["train_pyreader"] = train_pyreader + sgd_optimizer = fluid.optimizer.SGD(learning_rate=fluid.layers.exponential_decay( + learning_rate=args.learning_rate, decay_steps=1000, decay_rate=0.5, staircase=True)) + sgd_optimizer.minimize(cost) + if args.do_eval: + with fluid.program_guard(eval_prog, startup_prog): + with fluid.unique_name.guard(): + eval_pyreader, cost, prediction, pred_label, label = create_net(args, model_config, num_labels, \ + dict_dim, place, model_name="textcnn_net") + eval_pyreader.decorate_sample_list_generator(reader_res['eval_data_generator'], places=place) + res["eval_pyreader"] = eval_pyreader + if args.do_test: + with fluid.program_guard(test_prog, startup_prog): + with fluid.unique_name.guard(): + test_pyreader, cost, prediction, pred_label, label = create_net(args, model_config, num_labels, \ + dict_dim, place, model_name="textcnn_net") + test_pyreader.decorate_sample_list_generator(reader_res['test_data_generator'], places=place) + res["test_pyreader"] = test_pyreader + res["cost"] = cost + res["prediction"] = prediction + res["label"] = label + res["pred_label"] = pred_label + res["train_prog"] =train_prog + res["eval_prog"] = eval_prog + res["test_prog"] = test_prog + + + return res + + +def main(args): + """ + Main Function + """ + global DEV_COUNT + startup_prog = fluid.default_startup_program() + random.seed(args.random_seed) + model_config = ConfigReader.read_conf(args.config_path) + if args.use_cuda: + place = fluid.CUDAPlace(int(os.getenv('FLAGS_selected_gpus', '0'))) + DEV_COUNT = fluid.core.get_cuda_device_count() + else: + place = fluid.CPUPlace() + os.environ['CPU_NUM'] = str(args.cpu_num) + DEV_COUNT = args.cpu_num + logger.info("Dev Num is %s" % str(DEV_COUNT)) + exe = fluid.Executor(place) + if args.do_train and args.build_dict: + DataProcesser.build_dict(args.data_dir + "train.txt", args.data_dir) + # read dict + char_dict = DataProcesser.read_dict(args.data_dir + "char.dict") + dict_dim = len(char_dict) + intent_dict = DataProcesser.read_dict(args.data_dir + "domain.dict") + id2intent = {} + for key, value in intent_dict.items(): + id2intent[int(value)] = key + num_labels = len(intent_dict) + # build model + reader_res = build_data_reader(args, char_dict, intent_dict) + build_res = build_graph(args, model_config, num_labels, dict_dim, place, reader_res) + if not (args.do_train or args.do_eval or args.do_test): + raise ValueError("For args `do_train`, `do_eval` and `do_test`, at " + "least one of them must be True.") + + exe.run(startup_prog) + if args.init_checkpoint and args.init_checkpoint != "None": + try: + init_checkpoint(exe, args.init_checkpoint, main_program=startup_prog) + logger.info("Load model from %s" % args.init_checkpoint) + except Exception as e: + logger.exception(str(e)) + logger.error("Faild load model from %s [%s]" % (args.init_checkpoint, str(e))) + + if args.do_train: + build_strategy = fluid.compiler.BuildStrategy() + compiled_prog = fluid.compiler.CompiledProgram(build_res["train_prog"]).with_data_parallel( \ + loss_name=build_res["cost"].name, build_strategy=build_strategy) + build_res["compiled_prog"] = compiled_prog + train(args, exe, compiled_prog, build_res, place) + if args.do_eval: + + evaluate(args, exe, build_res["eval_prog"], build_res, place, "eval", \ + save_result=True, id2intent=id2intent) + if args.do_test: + + evaluate(args, exe, build_res["test_prog"], build_res, place, "test",\ + save_result=True, id2intent=id2intent) + + + + +if __name__ == "__main__": + logger.info("the paddle version is %s" % paddle.__version__) + check_version('1.6.0') + print_arguments(args) + main(args) diff --git a/PaddleNLP/dialogue_domain_classification/utils.py b/PaddleNLP/dialogue_domain_classification/utils.py new file mode 100755 index 00000000..2c839a2c --- /dev/null +++ b/PaddleNLP/dialogue_domain_classification/utils.py @@ -0,0 +1,354 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- +""" +# Copyright (c) 2019 PaddlePaddle Authors. All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +""" + +from __future__ import unicode_literals +import sys +import os +import random +import paddle +import logging +import paddle.fluid as fluid +import numpy as np +import collections +import six +import codecs +try: + import configparser as cp +except ImportError: + import ConfigParser as cp + + +random_seed = 7 +logger = logging.getLogger() +format = "%(asctime)s - %(name)s - %(levelname)s -%(filename)s-%(lineno)4d -%(message)s" +# format = "%(levelname)8s: %(asctime)s: %(filename)s:%(lineno)4d %(message)s" +logging.basicConfig(format=format) +logger.setLevel(logging.INFO) +logger = logging.getLogger('Paddle-DDC') + + +def str2bool(v): + """[ because argparse does not support to parse "true, False" as python + boolean directly] + Arguments: + v {[type]} -- [description] + Returns: + [type] -- [description] + """ + return v.lower() in ("true", "t", "1") + + +def to_lodtensor(data, place): + """ + convert ot LODtensor + """ + seq_lens = [len(seq) for seq in data] + cur_len = 0 + lod = [cur_len] + for l in seq_lens: + cur_len += l + lod.append(cur_len) + flattened_data = np.concatenate(data, axis=0).astype("int64") + flattened_data = flattened_data.reshape([len(flattened_data), 1]) + res = fluid.LoDTensor() + res.set(flattened_data, place) + res.set_lod([lod]) + return res + + +class ArgumentGroup(object): + """[ArgumentGroup] + + Arguments: + object {[type]} -- [description] + """ + def __init__(self, parser, title, des): + self._group = parser.add_argument_group(title=title, description=des) + + def add_arg(self, name, type, default, help, **kwargs): + """[add_arg] + + Arguments: + name {[type]} -- [description] + type {[type]} -- [description] + default {[type]} -- [description] + help {[type]} -- [description] + """ + type = str2bool if type == bool else type + self._group.add_argument( + "--" + name, + default=default, + type=type, + help=help + ' Default: %(default)s.', + **kwargs) + + +class DataReader(object): + """[get data generator for dataset] + + Arguments: + object {[type]} -- [description] + + Returns: + [type] -- [description] + """ + def __init__(self, char_vocab, intent_dict, max_len): + self._char_vocab = char_vocab + self._intent_dict = intent_dict + self._oov_id = 0 + self.intent_size = len(intent_dict) + self.all_data = [] + self.max_len = max_len + self.padding_id = 0 + + def _get_num_examples(self): + return len(self.all_data) + + def prepare_data(self, data_path, batch_size, mode): + """ + prepare data + """ + # print word_dict_path + # assert os.path.exists( + # word_dict_path), "The given word dictionary dose not exist." + assert os.path.exists(data_path), "The given data file does not exist." + if mode == "train": + train_reader = fluid.io.batch(paddle.reader.shuffle(self.data_reader(data_path, self.max_len, shuffle=True), + buf_size=batch_size * 100), batch_size) + # train_reader = fluid.io.batch(self.data_reader(data_path), batch_size) + return train_reader + else: + test_reader = fluid.io.batch(self.data_reader(data_path, self.max_len), batch_size) + return test_reader + + def data_reader(self, file_path, max_len, shuffle=False): + """ + Convert query into id list + use fixed voc + """ + + for line in codecs.open(file_path, "r", encoding="utf8"): + line = line.strip() + if isinstance(line, six.binary_type): + line = line.decode("utf8", errors="ignore") + query, intent = line.split("\t") + char_id_list = list(map(lambda x: 0 if x not in self._char_vocab else int(self._char_vocab[x]), \ + list(query))) + if len(char_id_list) < max_len: + char_id_list.extend([self.padding_id] * (max_len - len(char_id_list))) + char_id_list = char_id_list[:max_len] + intent_id_list = [self.padding_id] * self.intent_size + for item in intent.split('\2'): + intent_id_list[int(self._intent_dict[item])] = 1 + self.all_data.append([char_id_list, intent_id_list]) + if shuffle: + random.seed(random_seed) + random.shuffle(self.all_data) + def reader(): + """ + reader + """ + for char_id_list, intent_id_list in self.all_data: + # print char_id_list, intent_id + yield char_id_list, intent_id_list + return reader + + +class DataProcesser(object): + """[file process methods] + + Arguments: + object {[type]} -- [description] + + Returns: + [type] -- [description] + """ + @staticmethod + def read_dict(filename): + """ + read_dict: key\2value + """ + res_dict = {} + for line in codecs.open(filename, encoding="utf8"): + try: + if isinstance(line, six.binary_type): + line = line.strip().decode("utf8") + line = line.strip() + key, value = line.strip().split("\2") + res_dict[key] = value + except Exception as err: + logger.error(str(err)) + logger.error("read dict[%s] failed" % filename) + return res_dict + + @staticmethod + def build_dict(filename, save_dir, min_num_char=2, min_num_intent=2): + """[build_dict from file] + + Arguments: + filename {[type]} -- [description] + save_dir {[type]} -- [description] + + Keyword Arguments: + min_num_char {int} -- [description] (default: {2}) + min_num_intent {int} -- [description] (default: {2}) + """ + char_dict = {} + intent_dict = {} + # readfile + for line in codecs.open(filename): + line = line.strip() + if isinstance(line, six.binary_type): + line = line.strip().decode("utf8", errors="ignore") + query, intents = line.split("\t") + # read query + for char_item in list(query): + if char_item not in char_dict: + char_dict[char_item] = 0 + char_dict[char_item] += 1 + # read intents + for intent in intents.split('\002'): + if intent not in intent_dict: + intent_dict[intent] = 0 + intent_dict[intent] += 1 + # save char dict + with codecs.open("%s/char.dict" % save_dir, "w", encoding="utf8") as f_out: + f_out.write("PAD\0020\n") + f_out.write("OOV\0021\n") + char_id = 2 + for key, value in char_dict.items(): + if value >= min_num_char: + if isinstance(key, six.binary_type): + key = key.encode("utf8") + f_out.write("%s\002%d\n" % (key, char_id)) + char_id += 1 + # save intent dict + with codecs.open("%s/domain.dict" % save_dir, "w", encoding="utf8") as f_out: + f_out.write("SYS_OTHER\0020\n") + intent_id = 1 + for key, value in intent_dict.items(): + if value >= min_num_intent and key != u'SYS_OTHER': + if isinstance(key, six.binary_type): + key = key.encode("utf8") + f_out.write("%s\002%d\n" % (key, intent_id)) + intent_id += 1 + + + +class ConfigReader(object): + """[read model config file] + + Arguments: + object {[type]} -- [description] + + Returns: + [type] -- [description] + """ + + @staticmethod + def read_conf(conf_file): + """[read_conf] + + Arguments: + conf_file {[type]} -- [description] + + Returns: + [type] -- [description] + """ + flow_data = collections.defaultdict(lambda: {}) + class2key = set(["model"]) + param_conf = cp.ConfigParser() + param_conf.read(conf_file) + for section in param_conf.sections(): + if section not in class2key: + continue + for option in param_conf.items(section): + flow_data[section][option[0]] = eval(option[1]) + return flow_data + + +def init_pretraining_params(exe, + pretraining_params_path, + main_program, + use_fp16=False): + """load params of pretrained model, NOT including moment, learning_rate""" + assert os.path.exists(pretraining_params_path + ), "[%s] cann't be found." % pretraining_params_path + + def _existed_params(var): + if not isinstance(var, fluid.framework.Parameter): + return False + return os.path.exists(os.path.join(pretraining_params_path, var.name)) + + fluid.io.load_vars( + exe, + pretraining_params_path, + main_program=main_program, + predicate=_existed_params) + print("Load pretraining parameters from {}.".format( + pretraining_params_path)) + + +def init_checkpoint(exe, init_checkpoint_path, main_program): + """ + Init CheckPoint + """ + assert os.path.exists( + init_checkpoint_path), "[%s] cann't be found." % init_checkpoint_path + + def existed_persitables(var): + """ + If existed presitabels + """ + if not fluid.io.is_persistable(var): + return False + return os.path.exists(os.path.join(init_checkpoint_path, var.name)) + + fluid.io.load_vars( + exe, + init_checkpoint_path, + main_program=main_program, + predicate=existed_persitables) + print ("Load model from {}".format(init_checkpoint_path)) + +def print_arguments(args): + """ + Print Arguments + """ + print('----------- Configuration Arguments -----------') + for arg, value in sorted(six.iteritems(vars(args))): + print('%s: %s' % (arg, value)) + print('------------------------------------------------') + + +def check_version(version='1.6.0'): + """ + Log error and exit when the installed version of paddlepaddle is + not satisfied. + """ + err = "PaddlePaddle version 1.6 or higher is required, " \ + "or a suitable develop version is satisfied as well. \n" \ + "Please make sure the version is good with your code." \ + + try: + fluid.require_version(version) + except Exception as e: + logger.error(err) + sys.exit(1) + + -- GitLab