From 18f646fbd8918147b12e37824a283bfc148ccf98 Mon Sep 17 00:00:00 2001
From: lewwang
-
-
AI6k&~UNd|1sQGzMs*YiB|6HBH7J)`8Gc%sA%;C2V1$$GkePREL!Q> zRTGQ^^{%zXdI}4o$#EFiOPyZsrh}*TfYF*t^irdkSq^Iby&C&Y@67wph>_BDs|OI% z4`)23f;wPs@GONug~ouVfR9>sopq$U-pR^|=|*rXtl*{hO~5@Ym9L`Z^qM4fP}8*P z_y9BU%aL|GTL+ZIw{Dem%`Dm<{+0PJ2*IT*tW#N+C>N?%LBDY)EyjQ%0Wk0V=a+TV za{DP8##0U#;)|R&y{@JdZ|IW00+679C>l&X4zt!XiH8m9 zt$8zns~Xr8j{{eFb=7b|z+DaEUJbbY!gH*TPgS}=as4$k1-h+R)fz8Ae^pCO50ZOK zcO8|Q_Kw<%niC)YNp-Q<48EMT^7(cb-g5yACHscm_fg=}PsDc4l1~Z(7Vnz{_?jNI zixRsypL_ Iq&5>ZnNRS zSSRJi9M|xoqPpy23;4%=d#b0#&(%cNFvr16q7JbzA!MzU84eYQoua>~xwA{PAJAw# z|GMW8^H57KrE8Z(e`cxERw=W~Fgy8Fa^?K)-W}e$dvcG_vO1OTKlBfq(K!3UHiKJH zs$;InMMoa@`Q@W+*9$kEPPKJ6xp!?1iotgZDrU67r7WF97P7>M$pWC)&jJN>pgPg7 z+AOTyHdW6NC(`dD+@EtxnL@Ae59U42wgEX(KNLqqC46z;xvvtYg78J6zODaf&Sq{P z=G(y<42i~4q;ycb RkD16U4YzIXNUr%P~J_pq&~p z@B~Dr3w71~NoITz0LPRl<{+Zw_Wj$cx;LzHhs>wXyHw}W&p!fZ*Y<(2JJ22K4X(o= z>*!~ecWuSNbgkw5S6^T(+==*)1MNNhuRVaIy8xy;1U0w2^AD-u;_u _N{`GKnB3egh%~c7dK^DxD?TFeG~jZeSbrfjD#i)oY+_BBJBG z2M89wI}rn-lo$P5h5*<18gP5gMo# wKQHy3m*#TbbZs^5U>`9)_xy*J9QHMr~y ~WH&V6Wmn+l9Nl=C) zRAFze6?)Py(7k^V9;h3AWNDN%9*~T5rf50O-aQ3m>`AKP%(b@%(cA4GGFXJ45oTG8 zSMz1jbD|-el6TgD;yxu*ri+D&O%i%HS_c?o%Qnok#^%Z*S{t?2JAk!QKC_Q!M~_Rf zmRKOuu|8bOk}+E6QNh&JM4toO&{|E*I7Y?$sv*BbiRg?(W8)P@dUjv7j7wi{wF1im zoFeZ Is~kRrJixyei6;@%*Z&V+W5w7PYK5-9@ogw!|70+=rxH zy`w`jj|m(Dhj$lS#Z^@0(@FgaZeHw 5hStB|56{Ng=KG6HjKlY z#0jk gN^PuqCQXWR)lq>4F2vg ZWV8$_xBswv{E{+)nIqG*FG%TNM82f zYY&60I!S0#5bdkj+W3}$x6$si62)zA4yOfc&na8KN0 pfh(ZhOSDesYgZ9&Y(bjSi~i! zncmFR5lfJm^kaw2FllRZcMzQnSfH0CBF-%)W|_{sW8UPS_L222lgWk!B1pDT3kjJJ zTyGurrN6W$ttC4YZx?2;x$Rw3k$7GdR?560PAgoeUSCpe=L$)i`TdP3!M_3Py0Zb` z54Hwe4n->Gzow;w_Wpg_9_`Bq-j`LvS_ Iy4WhU>i-btPMh1%+Vg-K_ank# z=*ab;!^NH2ZN%FdlE!|Ll~ho1P+`I d&b;OTfv6#pIQ4 z>Oz7%#Vx$W#Z-X)(*CyBR;}1JrWW`9m2zL)R*6tM11`w2xlO5G+8)vo-tI(NWSu{S zuOAy7u`-|^FL5FbToB81%y duSO&!g1`$Tb^AWq;EP~r6TuQ$r_{UDAnzAeYbWFP) zo^05vp~CaCsXPt736rckf$z1p7(}Sad(3dVh2ev^?5`AvOxq$(1VvsnCGwjPlTKNf zYriFTVyOM-k#`!)`r9dt?JFS@?qC0Q`V0pHbqiKL+TWAaeYg5w6Ms*N3M*vt;>vUa zo-Q?++iuQ+9%2FJX9c+%;8VW6@8up$I5u(#?VVLA)&!tvue_q#l~VDv3X|qZeqn{U zsyJy15vMUhI?wpl5nl37R+ce?m)GD*CDgBq%?E(V)c#@mihkrOh`z1oCJdskfJHd4 z5~dp06u%biuUNO?o!^@u FrFjzgW&Zc?34*?I-f1-?b@MaNh$NRThU%c}Fd zZ*hXdfr6eS-J+xRJ} 6&z1@K%^&Wh*tZBzb@ b5qMKH-F9<+eTw#`25ck9 W8@bVG$Dtn3Du`S*w4 z18fJi@iq47=sY$Yyd`SCV7+eA$C-O^C^kwyCPZh8jgV%DS73}98FsG5&Oncd3vb`G z77+C;XkYk(;!qWTT(dY34I=JU% QySv{?pfa2MO=Yl65=*klo8@fmLir$3;Wy?XZjE#HNzABtz- zfY22yE_9L(m!jzp1=10v?2WfpdYc?9AOj@c1OKJ=#YH1<`;CnQen4tAhNvHey{?3_ zz+=eQI_HSK8Eyqs%@oiB!P+9PnFM63PG9ohiSx6&64>TS5?1-W9%pPP?ak%Vh^4XO z$> pOKAv0U2dP2;NvR?3F?}E@w?>T(n=I&(z4d2uuWZifTv&yz?7S=+SFLsa zQoY3s`g66t4}f_ATi@6f`;`K#J)C((0ZAA6rFruE1S=-OxFnr#%8->$`G;4vF{2VO zM)cw}mK^v&4(1LYFl-i<7atClm`9(i$D5VoW9qDeM 6>3qyxK|PEc8JAM4jT) zzy92On^dkf BVhAZK^MFw`G+i~}7~1#^QMbJ^7)r(2ANt?Bt_@py0?tB-Cy$;oY7f=2 z5H<|P;au}K+dMDWbRPc|x3a6jgC6(n4c)&j$kcE=EYIHw1`)Y*vGaiR0HSwHqj1|( zM3UC|l<;Prrwq!creo=aCW3bC`udkwI=+WtX3kE59%^DEN2_x6MZ5=Lu_1nMrBl~` zC0YqW71mPhbZd|5MzN#)tF;6y2+&>vq)+ZNd4*j!?i3v5yg!-}6HL5_s|hEQ5}fpR zus1s`*{wGBGoGD$Ov82OqVjHtHtT!I%6MBU<*TcOu^|DI;rupnd3r_k*N?;M{k7M& z%q+FEm!q`57IuWUAk&4iqtbss=^;Pwj=rvBote|dP(e2X@eHHBsV)78y?O<@^6ZIQ zkjQ?sFei#rKJJhCBTHoU4C&%`rpxpKymkln`Vhl0TPKD4{|;64{KueU0MHe^!JCeS znASYIuy*K@9iBeiFhdA|{AO*?iVNvA$E6ruBk7%{?3K$84PSmvr|$4zy7TyP2ALsB zxJy&NY+t2OzZcTG<5?VpQs_R=HK@AX2dHcR7?ifj`-;edMZfni?s3v?T$~R$Z-_Y2 zqI9N*QO)A3{`9tH;S{TTY4!fc_N1GSq>I9N59y!dtT6A;g7)9-o?^_nMJ(1UbX4hb zd#`G9JeELp_>7fAwoKQfOBYOX0$s(j^WyPf0t-zagZ(+e;?+^s&4F@0hxuc@vPUgd zr;bVmCYe=|;H`0frJC}*Bbi>FkX()PJePPO_nW#wlB^FPlwnh8#;@2-7Jm*dK?1x1 z&ko_H()Uw!+(lxdAbPHZ0*|;gf4rGqq3*ITnDVC#8nD$=xA>4;cF-gWntH%?I`4f- zj4IA~uzfI%KNA-DTyWELXpfx)IujQuJ3zd<`RNLa&P+YRBSzGHIolF1bSnM>E0zB6 zrqTA6H(LMqC;ewSyUu1kKlgk(&a+<~xUwT*a!fn8VnTK49=aEHL{J3PT-1Fl`by1Z zv#fT+_U@8g--6ONgX?lDnVG!@xpuFBd+e;Qz9v%&2ec|TZx}bl{4KSIzRa`hZIm7x zs}FgrTkANWj DN%ghs0opGP7dYf1s*d6 z+sft}*(NkR4c>`4+TBpoq6_V$CXmT@dLqR$CyJ*_%9~%;PZT07E~BKUJIrb&%iUC( zEL)t&B-C#02p_9|Pv5xEAwBf8y1414=fn={Cap3erbYR=+ivU5 >?lU4eo6hHh= z-wQ9>Ft^GG<~Y=QBK3K%F)@*{eHJm*w8uWISfL3Z9wu+by<&<$Go>FE)`1AOWQz=; z{QuM1x&Jfy`2RmbXbDN=oY#=^Ea&q%%^@8fYs}f?d-<%vgI0;`sIpUNX4=Ow3&DD06h zhxKD^Eq_w_$)`u8uX^jg-;4#E^R<;U}{gYZpBXNg=owuE_ii)_Du1+ay)-XU%k2I*GO%q zPhlSExDLgc(s?%tM0k^`a@z#IiXoi=;-`RoEd>+jYo+v52ndCc?{BBvh9;dz!Rihg zEgZi;>54QTOEeoZKaC{+AUuk(l=6o=lFuKndAuZbTiYiy5*rNmPWqfwD6_jCM4D2d zR?>w#zzQ^?U=2MFYkvLi_~}Xe`1S?ISSM#=(q$Px>l`FOF721#i~rocpySG$oIhEl zVdg6TAqP4;`|5Im=i8$dH}n(*6B2@604VE&k;(0wZE|DrS=^n?r9UonIpe(43?LzK z?qTi^)*@6S_+oX`&Bo;kOOj92AHDzfs-CoRy-8b%)dtZ0JUCNfMOnTSban1- QbO>%(do87|v}P^Tk$TE^$i zwsa)CB$1k=e&zPnws}Erut#_+q06x`93;RPDDIA^WHHdg>U4C%#hG5a^Ew4`YpPfI z)+%c|toBHECMbgL^NaLUj3O(QX2k8Q9UIlg#2jRu*|*asWU5mzS?ENcixQS7pSvU) z+YCMX zQ|aU{1#Qq^ji|L1#Zg<&SrO}G4CwFJ!>|a(2o%{3noS2FPyF*;?9J=m7b2b*L2;vO zTIO3kr~Zx(o{Q@c1CLI8coxc;GhMqo6CpX5^burB7QYq69)x_j)}M?o4$K@dqv#h4 znJ1j!i+o{BFUOmSe|6e u9FT}( zpXfs@2)r*xgfZ3@2ZB^qWT+txe*}MhK;Z7V?{Bk+D}i5W>bjh#=db5G BBr-fN;KXZQ4Lp}P3NvO+TTT!cD2E~s zy~CZ(*_9myoRArvGGDK0#n}&_J~2gpX+dbAw570BE^94JIhJDo=A(0%Q)6CD#id5o zo{RDo_AUQIMS6$CNeaGF#&F7={joo%io;1)-qZ*{+Zt^Zl8NROgB5 >co)XY zb#ni9oY;c#wtA%|riaOyY%w#F822vY9AKZOnMhLTHqidPh_>RL1I|628fnhZlYpv{ zlULYBvIQ5GJ7FA^v1`HDWne)pUw%#gI&+lz-J8tS4 7aPWEPsm%1I 6dvB?f2C)lDc6tSnONFE*|p~mFThUUFzKQUoy7k!6yqfO#P$z-W<~Hb&&kD& zofp93$A3J5 -E3TA;$e}&8h4m_IphqlqS$m`YF6u+_g+o=Fm(%dBdZj zQ?lY)dwePk)W2=iwNSPq&)r>jvFcgeZ{u5Y`|G^W&sM3aNgK`IpZ1xobwRN3z2ESO z%8+xkJ?&d$ylKlutL$NCW-CK1f+#wkTcq~XWHY)z2!sE>OZozNv_xK{a>7oufwA}h z{99mGU(SP^RJ(E2iNk1R=BJw%%;~4Mtjop)lfv8f)eE?oQR51rN7iq06rM&730P8w zTV*xVNE^M>?7^f0wMB>j4Kvq(U3Py4Gf%g1nk4&MEKCgbQ0TEs7i)xAQ)DQMZrQ^t znu(z^yoP0G_M7l2%thaFZz@OxWbawVg}xBTIK30Z->5QxHj9Z)6}0)JX(MCLS%*rj z7+93od|9HkK2AmJw5;4<7PWUX#8DZum2SaKdUog0@9&fbo4D_tZbSYInuu+o(Zmru z^FNf|WGblY40U=z!+q2C&w1dCWH;|WhDU`(4?w16t)$&zQP*!~@HwyIB|MTDUJc4_ zFYw~^rwjjETb3ufFe^ >nvRQ1<)7`$NuJ8tbp{L0Mg1+1Kdh~8_k zN_CHb4`k?h4pqIj`}#I6SU0|fK9ZU8`t}2ee@pWS-Y)qmbSNEtd*$f0PWTWKDSB?# z8JI5fbc =3f3D*>e58-)iM+7whqa$uOzOR(2g?v{L+Y<*i`ccoh75cYTjdVl^Rn*w zgvleY_foffV_Emd%yDDbJwKSd!}}fQ88;mt-(&4^=fyS?w%!%|-y0b7RRH>UxQKEt zY^M|mJ41g4R8a+EsLl`#g#p|2zpOx5@wkR(mSdzVT(EV~>F4a+n)|J*#Gr24yj@w% zqFvbqGV?K_f_J7p`w;eWPw?mf1`L6FU*Ay#egtDq&q7-E D1=n_}!yy z-kC}TeQdv7=*9)&YUf@G>HWOx=|GICl#?WJknNkh`k|K+37@7P&>3^u1B{-MsH4K! zv@K#cH3ls_b2lIKPTRRQyHy?2?%O1!nd@ciPsVoL`(q1C!5A=Dm-&h}Mv?d#Q=486 z36Y^{@WScs-nxGQsVI>PJiycKOlgK~q(2P(Q6T0kR9_OX{V@CA)1mTP>$~rO!u_5( zNSVpXZa+&I-m{Be-WL3^=vlrEUF%``m?#-kh#HR*>MYPt-1;1=`1FYPN25uU#$(jf z8S>WEAIjUT;>_pF3^|V%Au%m(H@eww^ufdu>{0o~&0C=ENht)tEIP@Sew?YU$kW*w zIi&w6Y8)D>lQ?;>cX7a@1DNRIIA+61sMjkqf&RkC)owZcygDDcDm+TNYM4WX-Bvhc zoNUj3ev9V^$e!JZ?MdnYQV5mtm<1{xI2ON5Vo@Oci%i}9JDyq3m2{$Vt=Q@3Tw#`8 zbD_bN1d*c#o0-8OJEOh1`4H~)^I^_aEp8d)y)85{8D$;NPah8pDO%tNDV;}Z3pzHZ zpvin}M+GRtv|jf+c9h6NakpZ+@1>A@RDelCUx B&l;_=HkAm8%u=C2pGe2!M*;5#oIi7 zK%>&Rv4yXI;?I;igX58&A7MfVa|7xeG5%aPX5101_d?8Wo3kKr5$C>w7p|=6RP^sS z?{2*wToIC90FP-Hk#5JHIHVp}Ik4o$Mr<_4eOs|ISZ*!}UGamEQA-w4Z&vDDnDgpu zzxyMsEU3u!v>5lZ>dXJu56U6Ji*E_Oi0j*GlzK6y&eZoDl{3sC8t3R+NAtC~^qBi~ z{{QBR u&7uDjTDQ~rzU zUd7)nuCEQr6GK1KVu|cOK#?R0l(j6YRGc8z=pe9{R___FM3<$zK3~;6>qfQ#;!IXT zx=ca?Abw62lY(n&`qOBEd!mNo0mK_w3s+vetKM1wLT0qC83bZIV8XJV1|vu(aMgtM z_ipZzAZYe|p`lgf34lu9-#K6!Y aj{C9so@*+^6wx$a}HrOw;Pw6Yq8$G+vDbd7ov=;t^ZECqf;7 z;I_s(eNxX@Y)cOh7 t z{KE~%fQ5zs|0LES^~Bo^Fw0E% q-JeeI>%r{ifk9{+jci+`e@X(As6y71 zRG})}xr^4sm;n$2U Sazt)AI$iBcC38dt6Ai5xq##YbKQ*Q z9Z_BALo##JZ*6kL2L`?34ELsbmW#2(722wC@Rg*sI1%fear=sU0gTxu_yXD@9`t2= zB6jUa3Mds`l8zhsQOj&_JeQ@i)r{f9Xa%nykDah{v%&&SR9rB3#bepmkG&+VFI{5t z>wlDfVxbJYzO?nco1iN6lK79n8=c0nF}t~(RBvhEGAfT&hOB)UF*1YoR0jmaGT7O> zGMc75!pPjA^s~7x9v`}*7-E=H^ngH(arvJtBXm#qjl05u^9hAq;ytg1RzS4I1_v%Y zKjtOL7bMf9ZR`^;s6$xOIieBnywW!QbyVKCR9zS>gjrRE$fxBAx-Om>o~(x{D5Q1s zE($}3?kICISBITR$i_G;8&~QxFED!iFnMs)S{oO^Ao9AiUfE}-90dD~y%h21AVEem zyckv>D*&%`kMT(SlG}#k%6Oyw+U_UiLq>OIdE{cI9%}zv%D%!N+%29)%{7fHvGu57 z04gffW|0(C|KVl!PBO{Pn2}w8+Zo=$^A|jaCOSHv2uaDSfah1C((T7}d{BzNp_7t! znt-<6CBTHL;WKPmj^uh8wn3Q40B`1Z53yV+R^;_uV8sd6;#Uh|2t7W-c}zlFQeR`s zy0OJ^GpD3nPt5P84N9BrWhw3+OGiNF8;o%y8fIF)O2qoKP$vF-B}Yu`YTRA#1Z>u` zB8mkDX>Bv*&grMGuhMg<-2#|?&hcSmVB`=()@Ia8g<0%XJ&bjx{o@%yFfF`4!h=et zW7B(}LSZ6}4^e>+!kpwKN)rosI4iln?8s+5UlAI6&U>?O%ue=OJUZR%OE#wJLz+En zN;i|rHMYF&WC=~Ba5aG@$p-G 4mo1BUAaXj}r?wY=E&gamZ@b(5%>Yb-*y{;IRLIWQ+<%}?q zvzMNo^?Z&aajz}ep)Z*^t^L?Ue%>u6W}M9wR54B|A-;v_pn&HQB)j0ax~Z-O?GU6; zj!Jle?0|}qJ|#ii+j(?ejm+-|efl-17ziIu@3;n&jFREL4F7DNI%Lths5P=ckc2dV z8(M_3b_OK^8|Xd@RWbO7XbVrvdu}DPalJ?`zmNa*vh*gUCQeC(AZ8Gwk>WL5NA&m7 zW}|v*1DM?YvG(qI&V2-%VM9wrP5CD0Kk|m+a2L^Tau3s(>7EZ_av3N87OoyUa%lKG zV2YmjoirpWQV80?V?&ZZsw_9s7MXS|#KkMd;n#JA0!O12oMSKeuQQ1a<8&Bi!`0mF z!G)brTqhNLm@B=NlbHpmdy5j32CoaU^;v!&!2u#o0-7+1Zf7(U8UX|u*63_#@*M$# z7tgyo`$tmx-dyHr5Jbni#S|zpuEC)>_6HWOP%t^;!fZ0tgXUZ{(koxcq?5|mB!~+8 zjl|o#w)h;zE6Oa~e>hMd`W{O(jZ$nrUmhkSQqZ~^AaCYR(Qvy$`jLm#yrfLB^C-i8 zo2+-+v%Yk!RQ_{oJsT=t4rWdfpS`iE=@$?46bUyxMibNqp!Q$yT=dlU?C@+q&Upd= zaap<2-!$pFS%g2!y79y B=>K%jjE{S9e0oK*s&};WQYOlcywI5W1j@l{{Gz6y=3HM zZT$PLK=v;01(S{h;JX-nZAmp)AkBAT$b;>gdvUNHLV6I{g#GFNb+fZ#Q9-ESi(ds_ zRxn yiF^!RGF0q2b?=i1F~-g;L0}-M}k3;)O+n9|GLQDiXzt z( ZusHGFTKbt3k;e3h`r>RhyE{QjrMrbIj{~w)U1;o~w$gjpqCG`qA-M_Aht;4L@%J zIravn%P0S0fut39NXqhC2D0C&@$gOHdG#kVi0%}*2q%P3 OBtk97o2xpu-9X z&zPjxGoQ}=a!Gta5Sf{`#(X>)iz*1Z@*|&MEb?t~gsA3r3DJt`n8axt3*Y6peIF|# zU2!*!j@mS5>J;LaFQk!l1+GC-(05p7VbfN{U%f;lt>^CJGWxPMvKa=c7J!~&t48G; zuEu|U%0+VzY maX*>UZ z{d1c~qX`B1f*lT0&DtHmi!D?Br08*ZN=Lknf_)_F1%$05k-78=V@kev!J u9ic$C+L{q3H z71|b<3ay?vl}rg_Y(}_rhp>;#B2t2`=N5kZ`8jE6tQIG}U!)y&HStFjn^4H4mP|t4 zje%BbPznHbRT}?SjYKfrWWx(&q%NJdyxkg192sf97G66)^t0XVj(jQ&?7|{gcuif7 zJ1*eooZ|!2xgYRZ-sfB3xp}D~&@Wwxf7`PbAI zOI0RAU8xMU?WES~tW?5PwrIHLv7DX1`O2mVo5{3c@r1FeW5WIz`^WN^KUmw!i*mis zPYn>?Yu>m1s>;6qotJ!;X*R;VkR!zp{hJ5Ril-YxS1Xwjrsi~ETGbLUEJ1|sXDI*% zpj%SZxL3$k1a1xuoP$y^aXL`&9-{fWIFH@z^hgvyhSH^$e}3=#jK< 3Df#qujTb-&?1 ;TD!A0TgY>sw%A4pP?1{0_yYlQ{cF%#=r|e-BDZiNcpt@zU*A4pYobUVAojF zeL#ofhV}&ae|Dm-a5M8+{Te6{*B!2xaM4~R(RWk)tJG_M!mGZ8sxhN&ukMl7NF@e~ zu!~;JLAZ7cJZ3T{y@{G|d=sd Due@ji|l7=-S`J@cE(<;ySRN<`}`gwPD26c*GDpyzrQxm=pE(7J+} zU3Vz<0Y`3Z;_(t=WF;-g8ci@?lBn)c%Gyrr))bUxG#U0s0j?o)v$wZ?D%OB9;Oj(< z2C+3?Be3-Xi+n<0@%bd-9BcYw6y0k1sHOK2$x*veE eS0&oVi66_&AdAlXc^iTD4*#@)(cC Ou(~H`0_dG)e #`r9aC#~m!@UfbU+n3&AJN0Oq=VO){ekb{hFJ7^ciuk=*IgP7;8g+9YVA3H>8 z_> <(F84@a9H~vKwE`%=u@yaISWCWg{M~HK@xk*a=K;v9 z&^lG@%*;E4NX_26oi>DQ9Y0H2ZnpqLiZvnPr!YE$!(%0CBy^(~@hO>Stt@SI{oVe| zMe2k)eeTn@%11&qA^~utIAH$L6Wx7Wn~#2E#rOm)bnKd7t8NjCAg!a+m-d!;-;v_m zLa>`!g&doBHbqenK7b(;DWIsC2}xMy1p${C8UY#D6R-UcmRl%tbU#2xw67W8+EzK% zEC*43gxhG{BbBmSvdT3$`EcaGDVR@T@o%wf{^rqA5mV~_R1rlR6z}d6A^(JX8{T_w z5qK@~8!R}c_J-E*(nK7D;`n0tW*|E(G6U}+kBIFTUz1m@8^7K^iA0x!hs$nABJ}b- zVgOuc<@0gu!d1%AB5cxUZ(! kkcN6{} zD_%vpNst#nKHES4vVUfYGff{#F+KPMU`W7c?UuPCLIQr(x=(BRhU2%v)| c }wX?$@6E>PGDkVUZtfeo< zyzIIx#oip?Bh$g|?EM_L8VZ@d!Wv_fis}t(9`M0WjlVnl=wU*j_owxDgFtzg_pQGj zSX#s7_U~5z2p212@!@vsH`%@xx@R~&_5Gqf3gG!N?(4j+N80a~Q-17qQ_SxbIF_%K z@EPgWXj^vtNH}>fqIKPo-}*|p)caresw%lVO+oFhM{StxYs)G0%gu5-Jj(6-&9gqE zin @M?X{Z0D33*oP?Ej~; hQm=PV--S~#V@Ssl9}NGJ@i&Px7N*uF7$a!H{{yKCe^dYf diff --git a/examples/benchmarks/TCTS/workflow_config_tcts_Alpha360.yaml b/examples/benchmarks/TCTS/workflow_config_tcts_Alpha360.yaml index cd3bbf59ca5..484ed45b17a 100644 --- a/examples/benchmarks/TCTS/workflow_config_tcts_Alpha360.yaml +++ b/examples/benchmarks/TCTS/workflow_config_tcts_Alpha360.yaml @@ -22,9 +22,9 @@ data_handler_config: &data_handler_config - class: CSRankNorm kwargs: fields_group: label - label: ["Ref($close, -1) / $close - 1", - "Ref($close, -2) / Ref($close, -1) - 1", - "Ref($close, -3) / Ref($close, -2) - 1"] + label: ["Ref($close, -2) / Ref($close, -1) - 1", + "Ref($close, -3) / Ref($close, -1) - 1", + "Ref($close, -4) / Ref($close, -1) - 1"] port_analysis_config: &port_analysis_config strategy: class: TopkDropoutStrategy @@ -53,9 +53,8 @@ task: d_feat: 6 hidden_size: 64 num_layers: 2 - dropout: 0.0 + dropout: 0.3 n_epochs: 200 - lr: 1e-3 early_stop: 20 batch_size: 800 metric: loss @@ -64,12 +63,11 @@ task: fore_optimizer: adam weight_optimizer: adam output_dim: 3 - fore_lr: 5e-4 - weight_lr: 5e-4 + fore_lr: 2e-3 + weight_lr: 2e-3 steps: 3 - target_label: 1 + target_label: 0 lowest_valid_performance: 0.993 - seed: 0 dataset: class: DatasetH module_path: qlib.data.dataset @@ -93,8 +91,7 @@ task: kwargs: ana_long_short: False ann_scaler: 252 - label_col: 1 - class: PortAnaRecord module_path: qlib.workflow.record_temp kwargs: - config: *port_analysis_config + config: *port_analysis_config \ No newline at end of file diff --git a/qlib/contrib/model/pytorch_tcts.py b/qlib/contrib/model/pytorch_tcts.py index da7fda5f5ed..598a357a2e4 100644 --- a/qlib/contrib/model/pytorch_tcts.py +++ b/qlib/contrib/model/pytorch_tcts.py @@ -61,8 +61,9 @@ def __init__( weight_lr=5e-7, steps=3, GPU=0, - seed=None, target_label=0, + mode="soft", + seed=None, lowest_valid_performance=0.993, **kwargs ): @@ -87,6 +88,7 @@ def __init__( self.weight_lr = weight_lr self.steps = steps self.target_label = target_label + self.mode = mode self.lowest_valid_performance = lowest_valid_performance self._fore_optimizer = fore_optimizer self._weight_optimizer = weight_optimizer @@ -100,6 +102,8 @@ def __init__( "\nn_epochs : {}" "\nbatch_size : {}" "\nearly_stop : {}" + "\ntarget_label : {}" + "\nmode : {}" "\nloss_type : {}" "\nvisible_GPU : {}" "\nuse_GPU : {}" @@ -111,6 +115,8 @@ def __init__( n_epochs, batch_size, early_stop, + target_label, + mode, loss, GPU, self.use_gpu, @@ -120,9 +126,17 @@ def __init__( def loss_fn(self, pred, label, weight): - loc = torch.argmax(weight, 1) - loss = (pred - label[np.arange(weight.shape[0]), loc]) ** 2 - return torch.mean(loss) + if self.mode == "hard": + loc = torch.argmax(weight, 1) + loss = (pred - label[np.arange(weight.shape[0]), loc]) ** 2 + return torch.mean(loss) + + elif self.mode == "soft": + loss = (pred - label.transpose(0, 1)) ** 2 + return torch.mean(loss * weight.transpose(0, 1)) + + else: + raise NotImplementedError("mode {} is not supported!".format(self.mode)) def train_epoch(self, x_train, y_train, x_valid, y_valid): @@ -132,6 +146,10 @@ def train_epoch(self, x_train, y_train, x_valid, y_valid): indices = np.arange(len(x_train_values)) np.random.shuffle(indices) + task_embedding = torch.zeros([self.batch_size, self.output_dim]) + task_embedding[:, self.target_label] = 1 + task_embedding = task_embedding.to(self.device) + init_fore_model = copy.deepcopy(self.fore_model) for p in init_fore_model.parameters(): p.init_fore_model = False @@ -155,12 +173,13 @@ def train_epoch(self, x_train, y_train, x_valid, y_valid): init_pred = init_fore_model(feature) pred = self.fore_model(feature) - dis = init_pred - label.transpose(0, 1) - weight_feature = torch.cat((feature, dis.transpose(0, 1), label, init_pred.view(-1, 1)), 1) + weight_feature = torch.cat( + (feature, dis.transpose(0, 1), label, init_pred.view(-1, 1), task_embedding), 1 + ) weight = self.weight_model(weight_feature) - loss = self.loss_fn(pred, label, weight) # hard + loss = self.loss_fn(pred, label, weight) self.fore_optimizer.zero_grad() loss.backward() @@ -188,11 +207,11 @@ def train_epoch(self, x_train, y_train, x_valid, y_valid): pred = self.fore_model(feature) dis = pred - label.transpose(0, 1) - weight_feature = torch.cat((feature, dis.transpose(0, 1), label, pred.view(-1, 1)), 1) + weight_feature = torch.cat((feature, dis.transpose(0, 1), label, pred.view(-1, 1), task_embedding), 1) weight = self.weight_model(weight_feature) loc = torch.argmax(weight, 1) - valid_loss = torch.mean((pred - label[:, 0]) ** 2) - loss = torch.mean(-valid_loss * torch.log(weight[np.arange(weight.shape[0]), loc])) + valid_loss = torch.mean((pred - label[:, abs(self.target_label)]) ** 2) + loss = torch.mean(valid_loss * torch.log(weight[np.arange(weight.shape[0]), loc])) self.weight_optimizer.zero_grad() loss.backward() @@ -207,7 +226,6 @@ def test_epoch(self, data_x, data_y): self.fore_model.eval() - scores = [] losses = [] indices = np.arange(len(x_values)) @@ -242,6 +260,7 @@ def fit( x_valid, y_valid = df_valid["feature"], df_valid["label"] x_test, y_test = df_test["feature"], df_test["label"] + if save_path == None: save_path = get_or_create_path(save_path) best_loss = np.inf @@ -277,7 +296,7 @@ def training( dropout=self.dropout, ) self.weight_model = MLPModel( - d_feat=360 + 2 * self.output_dim + 1, + d_feat=360 + 3 * self.output_dim + 1, hidden_size=self.hidden_size, num_layers=self.num_layers, dropout=self.dropout, @@ -303,8 +322,6 @@ def training( best_loss = np.inf best_epoch = 0 stop_round = 0 - fore_best_param = copy.deepcopy(self.fore_optimizer.state_dict()) - weight_best_param = copy.deepcopy(self.weight_optimizer.state_dict()) for epoch in range(self.n_epochs): print("Epoch:", epoch) From caeacd09c53ee9fce378e3d439be41411d22525c Mon Sep 17 00:00:00 2001 From: lewwang Date: Tue, 12 Oct 2021 00:01:27 +0800 Subject: [PATCH 2/8] Update TCTS README. --- examples/benchmarks/TCTS/README.md | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/examples/benchmarks/TCTS/README.md b/examples/benchmarks/TCTS/README.md index 8a26ff91533..ca53afa17c3 100644 --- a/examples/benchmarks/TCTS/README.md +++ b/examples/benchmarks/TCTS/README.md @@ -12,10 +12,9 @@ Given that there are usually multiple temporally correlated tasks, the key chall At step , with training data
, the scheduler
chooses a suitable task
(green solid lines) to update the model
(blue solid lines). After
steps, we evaluate the model
on the validation set and update the scheduler
(green dashed lines). ### Experiments -Due to different data versions and different Qlib versions, the original data and data preprocessing methods of the experimental settings in the paper are different from those experimental settings in the existing Qlib version. Therefore, we provide two versions of the code according to the two kind of settings, 1) the code[https://github.com/lwwang1995/tcts] that can be used to reproduce the experimental results and 2) the code[https://github.com/microsoft/qlib/blob/main/qlib/contrib/model/pytorch_tcts.py] in the current Qlib baseline. +Due to different data versions and different Qlib versions, the original data and data preprocessing methods of the experimental settings in the paper are different from those experimental settings in the existing Qlib version. Therefore, we provide two versions of the code according to the two kind of settings, 1) the [code](https://github.com/lwwang1995/tcts) that can be used to reproduce the experimental results and 2) the [code](https://github.com/microsoft/qlib/blob/main/qlib/contrib/model/pytorch_tcts.py) in the current Qlib baseline. -#### Task Description -Setting1 +#### Setting1 * Dataset: We use the historical transaction data for 300 stocks on [CSI300](http://www.csindex.com.cn/en/indices/index-detail/000300) from 01/01/2008 to 08/01/2020. We split the data into training (01/01/2008-12/31/2013), validation (01/01/2014-12/31/2015), and test sets (01/01/2016-08/01/2020) based on the transaction time. * The main tasks
refers to forecasting return of stock
as following, @@ -25,7 +24,7 @@ Setting1 * Temporally correlated task sets
, in this paper,
,
and
are used in
,
, and
. -Setting2 +#### Setting2 * Dataset: We use the historical transaction data for 300 stocks on [CSI300](http://www.csindex.com.cn/en/indices/index-detail/000300) from 01/01/2008 to 08/01/2020. We split the data into training (01/01/2008-12/31/2014), validation (01/01/2015-12/31/2016), and test sets (01/01/2017-08/01/2020) based on the transaction time. * The main tasks
refers to forecasting return of stock
as following, @@ -35,5 +34,5 @@ Setting2 * In Qlib baseline,
, is used in
. -Experimental Result -You can find the experimental result of setting1 in paper[http://proceedings.mlr.press/v139/wu21e/wu21e.pdf] and the experimental result of setting2 in this page[https://github.com/microsoft/qlib/tree/main/examples/benchmarks]. \ No newline at end of file +### Experimental Result +You can find the experimental result of setting1 in [paper](http://proceedings.mlr.press/v139/wu21e/wu21e.pdf) and the experimental result of setting2 in this [page](https://github.com/microsoft/qlib/tree/main/examples/benchmarks). \ No newline at end of file From 5c6e0ada0d950759dd1353032e6088db3f418270 Mon Sep 17 00:00:00 2001 From: lewwang
Date: Tue, 12 Oct 2021 00:04:19 +0800 Subject: [PATCH 3/8] Update TCTS README. --- examples/benchmarks/TCTS/README.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/examples/benchmarks/TCTS/README.md b/examples/benchmarks/TCTS/README.md index ca53afa17c3..0b405c6bee3 100644 --- a/examples/benchmarks/TCTS/README.md +++ b/examples/benchmarks/TCTS/README.md @@ -3,7 +3,7 @@ Sequence learning has attracted much research attention from the machine learning community in recent years. In many applications, a sequence learning task is usually associated with multiple temporally correlated auxiliary tasks, which are different in terms of how much input information to use or which future step to predict. In stock trend forecasting, as demonstrated in Figure1, one can predict the price of a stock in different future days (e.g., tomorrow, the day after tomorrow). In this paper, we propose a framework to make use of those temporally correlated tasks to help each other. ### Method -Given that there are usually multiple temporally correlated tasks, the key challenge lies in which tasks to use and when to use them in the training process. In this work, we introduce a learnable task scheduler for sequence learning, which adaptively selects temporally correlated tasks during the training process. The scheduler accesses the model status and the current training data (e.g., in current minibatch), and selects the best auxiliary task to help the training of the main task. The scheduler and the model for the main task are jointly trained through bi-level optimization: the scheduler is trained to maximize the validation performance of the model, and the model is trained to minimize the training loss guided by the scheduler. The process is demonstrated in Figure2. +Given that there are usually multiple temporally correlated tasks, the key challenge lies in which tasks to use and when to use them in the training process. This work introduces a learnable task scheduler for sequence learning, which adaptively selects temporally correlated tasks during the training process. The scheduler accesses the model status and the current training data (e.g., in the current minibatch) and selects the best auxiliary task to help the training of the main task. The scheduler and the model for the main task are jointly trained through bi-level optimization: the scheduler is trained to maximize the validation performance of the model, and the model is trained to minimize the training loss guided by the scheduler. The process is demonstrated in Figure2.
@@ -12,7 +12,7 @@ Given that there are usually multiple temporally correlated tasks, the key chall At step
, with training data
, the scheduler
chooses a suitable task
(green solid lines) to update the model
(blue solid lines). After
steps, we evaluate the model
on the validation set and update the scheduler
(green dashed lines). ### Experiments -Due to different data versions and different Qlib versions, the original data and data preprocessing methods of the experimental settings in the paper are different from those experimental settings in the existing Qlib version. Therefore, we provide two versions of the code according to the two kind of settings, 1) the [code](https://github.com/lwwang1995/tcts) that can be used to reproduce the experimental results and 2) the [code](https://github.com/microsoft/qlib/blob/main/qlib/contrib/model/pytorch_tcts.py) in the current Qlib baseline. +Due to different data versions and different Qlib versions, the original data and data preprocessing methods of the experimental settings in the paper are different from those experimental settings in the existing Qlib version. Therefore, we provide two versions of the code according to the two kinds of settings, 1) the [code](https://github.com/lwwang1995/tcts) that can be used to reproduce the experimental results and 2) the [code](https://github.com/microsoft/qlib/blob/main/qlib/contrib/model/pytorch_tcts.py) in the current Qlib baseline. #### Setting1 * Dataset: We use the historical transaction data for 300 stocks on [CSI300](http://www.csindex.com.cn/en/indices/index-detail/000300) from 01/01/2008 to 08/01/2020. We split the data into training (01/01/2008-12/31/2013), validation (01/01/2014-12/31/2015), and test sets (01/01/2016-08/01/2020) based on the transaction time. @@ -35,4 +35,4 @@ Due to different data versions and different Qlib versions, the original data an * In Qlib baseline,
, is used in
. ### Experimental Result -You can find the experimental result of setting1 in [paper](http://proceedings.mlr.press/v139/wu21e/wu21e.pdf) and the experimental result of setting2 in this [page](https://github.com/microsoft/qlib/tree/main/examples/benchmarks). \ No newline at end of file +You can find the experimental result of setting1 in the [paper](http://proceedings.mlr.press/v139/wu21e/wu21e.pdf) and the experimental result of setting2 in this [page](https://github.com/microsoft/qlib/tree/main/examples/benchmarks). \ No newline at end of file From ac1153849ac81266a704f2c77485a430523544ed Mon Sep 17 00:00:00 2001 From: lewwang
Date: Tue, 12 Oct 2021 00:33:47 +0800 Subject: [PATCH 4/8] Update TCTS. --- qlib/contrib/model/pytorch_tcts.py | 1 - 1 file changed, 1 deletion(-) diff --git a/qlib/contrib/model/pytorch_tcts.py b/qlib/contrib/model/pytorch_tcts.py index 598a357a2e4..c0dae98e418 100644 --- a/qlib/contrib/model/pytorch_tcts.py +++ b/qlib/contrib/model/pytorch_tcts.py @@ -260,7 +260,6 @@ def fit( x_valid, y_valid = df_valid["feature"], df_valid["label"] x_test, y_test = df_test["feature"], df_test["label"] - if save_path == None: save_path = get_or_create_path(save_path) best_loss = np.inf From 44bb149503a2bf26f4704364065da7538bd8fc13 Mon Sep 17 00:00:00 2001 From: lwwang1995 Date: Sun, 14 Nov 2021 22:25:17 +0800 Subject: [PATCH 5/8] Add ADARNN. --- README.md | 2 + examples/benchmarks/ADARNN/requirements.txt | 4 + .../workflow_config_adarnn_Alpha360.yaml | 88 ++ examples/benchmarks/README.md | 3 +- qlib/contrib/model/pytorch_adarnn.py | 804 ++++++++++++++++++ 5 files changed, 900 insertions(+), 1 deletion(-) create mode 100644 examples/benchmarks/ADARNN/requirements.txt create mode 100644 examples/benchmarks/ADARNN/workflow_config_adarnn_Alpha360.yaml create mode 100644 qlib/contrib/model/pytorch_adarnn.py diff --git a/README.md b/README.md index 261d6cf75b7..7125ee8fe69 100644 --- a/README.md +++ b/README.md @@ -11,6 +11,7 @@ Recent released features | Feature | Status | | -- | ------ | +| ADARNN model | [Released](https://github.com/microsoft/qlib/pull/689) on Nov 14, 2021 | | TCN model | [Released](https://github.com/microsoft/qlib/pull/668) on Nov 4, 2021 | |Temporal Routing Adaptor (TRA) | [Released](https://github.com/microsoft/qlib/pull/531) on July 30, 2021 | | Transformer & Localformer | [Released](https://github.com/microsoft/qlib/pull/508) on July 22, 2021 | @@ -296,6 +297,7 @@ Here is a list of models built on `Qlib`. - [Localformer based on pytorch (Juyong Jiang, et al.)](examples/benchmarks/Localformer/) - [TRA based on pytorch (Hengxu, Dong, et al. KDD 2021)](examples/benchmarks/TRA/) - [TCN based on pytorch (Shaojie Bai, et al. 2018)](examples/benchmarks/TCN/) +- [ADARNN based on pytorch (YunTao Du, et al. 2021)](examples/benchmarks/ADARNN/) Your PR of new Quant models is highly welcomed. diff --git a/examples/benchmarks/ADARNN/requirements.txt b/examples/benchmarks/ADARNN/requirements.txt new file mode 100644 index 00000000000..16de0a43842 --- /dev/null +++ b/examples/benchmarks/ADARNN/requirements.txt @@ -0,0 +1,4 @@ +pandas==1.1.2 +numpy==1.17.4 +scikit_learn==0.23.2 +torch==1.7.0 diff --git a/examples/benchmarks/ADARNN/workflow_config_adarnn_Alpha360.yaml b/examples/benchmarks/ADARNN/workflow_config_adarnn_Alpha360.yaml new file mode 100644 index 00000000000..ac49d014570 --- /dev/null +++ b/examples/benchmarks/ADARNN/workflow_config_adarnn_Alpha360.yaml @@ -0,0 +1,88 @@ +qlib_init: + provider_uri: "~/.qlib/qlib_data/cn_data" + region: cn +market: &market csi300 +benchmark: &benchmark SH000300 +data_handler_config: &data_handler_config + start_time: 2008-01-01 + end_time: 2020-08-01 + fit_start_time: 2008-01-01 + fit_end_time: 2014-12-31 + instruments: *market + infer_processors: + - class: RobustZScoreNorm + kwargs: + fields_group: feature + clip_outlier: true + - class: Fillna + kwargs: + fields_group: feature + learn_processors: + - class: DropnaLabel + - class: CSRankNorm + kwargs: + fields_group: label + label: ["Ref($close, -2) / Ref($close, -1) - 1"] +port_analysis_config: &port_analysis_config + strategy: + class: TopkDropoutStrategy + module_path: qlib.contrib.strategy + kwargs: + model: + dataset: + topk: 50 + n_drop: 5 + backtest: + start_time: 2017-01-01 + end_time: 2020-08-01 + account: 100000000 + benchmark: *benchmark + exchange_kwargs: + limit_threshold: 0.095 + deal_price: close + open_cost: 0.0005 + close_cost: 0.0015 + min_cost: 5 +task: + model: + class: ADARNN + module_path: qlib.contrib.model.pytorch_adarnn + kwargs: + d_feat: 6 + hidden_size: 64 + num_layers: 2 + dropout: 0.0 + n_epochs: 200 + lr: 1e-3 + early_stop: 20 + batch_size: 800 + metric: loss + loss: mse + GPU: 0 + dataset: + class: DatasetH + module_path: qlib.data.dataset + kwargs: + handler: + class: Alpha360 + module_path: qlib.contrib.data.handler + kwargs: *data_handler_config + segments: + train: [2008-01-01, 2014-12-31] + valid: [2015-01-01, 2016-12-31] + test: [2017-01-01, 2020-08-01] + record: + - class: SignalRecord + module_path: qlib.workflow.record_temp + kwargs: + model: + dataset: + - class: SigAnaRecord + module_path: qlib.workflow.record_temp + kwargs: + ana_long_short: False + ann_scaler: 252 + - class: PortAnaRecord + module_path: qlib.workflow.record_temp + kwargs: + config: *port_analysis_config diff --git a/examples/benchmarks/README.md b/examples/benchmarks/README.md index 1a7d2fc269c..85456ca4be8 100644 --- a/examples/benchmarks/README.md +++ b/examples/benchmarks/README.md @@ -54,13 +54,14 @@ The numbers shown below demonstrate the performance of the entire `workflow` of | XGBoost(Tianqi Chen, et al.) | Alpha360 | 0.0394±0.00 | 0.2909±0.00 | 0.0448±0.00 | 0.3679±0.00 | 0.0344±0.00 | 0.4527±0.02 | -0.1004±0.00 | | DoubleEnsemble(Chuheng Zhang, et al.) | Alpha360 | 0.0404±0.00 | 0.3023±0.00 | 0.0495±0.00 | 0.3898±0.00 | 0.0468±0.01 | 0.6302±0.20 | -0.0860±0.01 | | LightGBM(Guolin Ke, et al.) | Alpha360 | 0.0400±0.00 | 0.3037±0.00 | 0.0499±0.00 | 0.4042±0.00 | 0.0558±0.00 | 0.7632±0.00 | -0.0659±0.00 | +| TCN(Shaojie Bai, et al.) | Alpha360 | 0.0441±0.00 | 0.3301±0.02 | 0.0519±0.00 | 0.4130±0.01 | 0.0604±0.02 | 0.8295±0.34 | -0.1018±0.03 | | ALSTM (Yao Qin, et al.) | Alpha360 | 0.0497±0.00 | 0.3829±0.04 | 0.0599±0.00 | 0.4736±0.03 | 0.0626±0.02 | 0.8651±0.31 | -0.0994±0.03 | | LSTM(Sepp Hochreiter, et al.) | Alpha360 | 0.0448±0.00 | 0.3474±0.04 | 0.0549±0.00 | 0.4366±0.03 | 0.0647±0.03 | 0.8963±0.39 | -0.0875±0.02 | | GRU(Kyunghyun Cho, et al.) | Alpha360 | 0.0493±0.00 | 0.3772±0.04 | 0.0584±0.00 | 0.4638±0.03 | 0.0720±0.02 | 0.9730±0.33 | -0.0821±0.02 | +| AdaRNN(Yuntao Du, et al.) | Alpha360 | 0.0464±0.01 | 0.3619±0.08 | 0.0539±0.01 | 0.4287±0.06 | 0.0753±0.03 | 1.0200±0.40 | -0.0936±0.03 | | GATs (Petar Velickovic, et al.) | Alpha360 | 0.0476±0.00 | 0.3508±0.02 | 0.0598±0.00 | 0.4604±0.01 | 0.0824±0.02 | 1.1079±0.26 | -0.0894±0.03 | | TCTS(Xueqing Wu, et al.) | Alpha360 | 0.0508±0.00 | 0.3931±0.04 | 0.0599±0.00 | 0.4756±0.03 | 0.0893±0.03 | 1.2256±0.36 | -0.0857±0.02 | | TRA(Hengxu Lin, et al.) | Alpha360 | 0.0485±0.00 | 0.3787±0.03 | 0.0587±0.00 | 0.4756±0.03 | 0.0920±0.03 | 1.2789±0.42 | -0.0834±0.02 | -| TCN(Shaojie Bai, et al.) | Alpha360 | 0.0441±0.00 | 0.3301±0.02 | 0.0519±0.00 | 0.4130±0.01 | 0.0604±0.02 | 0.8295±0.34 | -0.1018±0.03 | - The selected 20 features are based on the feature importance of a lightgbm-based model. - The base model of DoubleEnsemble is LGBM. diff --git a/qlib/contrib/model/pytorch_adarnn.py b/qlib/contrib/model/pytorch_adarnn.py new file mode 100644 index 00000000000..398b4617819 --- /dev/null +++ b/qlib/contrib/model/pytorch_adarnn.py @@ -0,0 +1,804 @@ +# Copyright (c) Microsoft Corporation. +import os +from pdb import set_trace +from torch.utils.data import Dataset, DataLoader + +import copy +from typing import Text, Union + +import math +import numpy as np +import pandas as pd +import torch +import torch.nn as nn +import torch.nn.functional as F +import torch.optim as optim +from torch.autograd import Function +from qlib.contrib.model.pytorch_utils import count_parameters +from qlib.data.dataset import DatasetH +from qlib.data.dataset.handler import DataHandlerLP +from qlib.log import get_module_logger +from qlib.model.base import Model +from qlib.utils import get_or_create_path + + + + +class ADARNN(Model): + """ADARNN Model + + Parameters + ---------- + d_feat : int + input dimension for each time step + metric: str + the evaluate metric used in early stop + optimizer : str + optimizer name + GPU : str + the GPU ID(s) used for training + """ + + def __init__( + self, + d_feat=6, + hidden_size=64, + num_layers=2, + dropout=0.0, + n_epochs=200, + pre_epoch=40, + dw=0.5, + loss_type='cosine', + len_seq=60, + len_win=0, + lr=0.001, + metric="mse", + batch_size=2000, + early_stop=20, + loss="mse", + optimizer="adam", + n_splits=2, + GPU=0, + seed=None, + **kwargs + ): + # Set logger. + self.logger = get_module_logger("ADARNN") + self.logger.info("ADARNN pytorch version...") + os.environ["CUDA_VISIBLE_DEVICES"] = str(GPU) + + # set hyper-parameters. + self.d_feat = d_feat + self.hidden_size = hidden_size + self.num_layers = num_layers + self.dropout = dropout + self.n_epochs = n_epochs + self.pre_epoch = pre_epoch + self.dw = dw + self.loss_type = loss_type + self.len_seq = len_seq + self.len_win = len_win + self.lr = lr + self.metric = metric + self.batch_size = batch_size + self.early_stop = early_stop + self.optimizer = optimizer.lower() + self.loss = loss + self.n_splits = n_splits + self.device = torch.device("cuda:%d" % ( + GPU) if torch.cuda.is_available() and GPU >= 0 else "cpu") + self.seed = seed + + self.logger.info( + "ADARNN parameters setting:" + "\nd_feat : {}" + "\nhidden_size : {}" + "\nnum_layers : {}" + "\ndropout : {}" + "\nn_epochs : {}" + "\nlr : {}" + "\nmetric : {}" + "\nbatch_size : {}" + "\nearly_stop : {}" + "\noptimizer : {}" + "\nloss_type : {}" + "\nvisible_GPU : {}" + "\nuse_GPU : {}" + "\nseed : {}".format( + d_feat, + hidden_size, + num_layers, + dropout, + n_epochs, + lr, + metric, + batch_size, + early_stop, + optimizer.lower(), + loss, + GPU, + self.use_gpu, + seed, + ) + ) + + if self.seed is not None: + np.random.seed(self.seed) + torch.manual_seed(self.seed) + + n_hiddens = [hidden_size for _ in range(num_layers)] + self.model = AdaRNN( + use_bottleneck=False, + bottleneck_width=64, + n_input=d_feat, + n_hiddens=n_hiddens, + n_output=1, + dropout=dropout, + model_type='AdaRNN', + len_seq=len_seq, + trans_loss=loss_type + ) + self.logger.info("model:\n{:}".format(self.model)) + self.logger.info("model size: {:.4f} MB".format( + count_parameters(self.model))) + + if optimizer.lower() == "adam": + self.train_optimizer = optim.Adam( + self.model.parameters(), lr=self.lr) + elif optimizer.lower() == "gd": + self.train_optimizer = optim.SGD( + self.model.parameters(), lr=self.lr) + else: + raise NotImplementedError( + "optimizer {} is not supported!".format(optimizer)) + + self.fitted = False + self.model.cuda() + + @property + def use_gpu(self): + return self.device != torch.device("cpu") + + def train_AdaRNN(self, train_loader_list, epoch, dist_old=None, weight_mat=None): + self.model.train() + criterion = nn.MSELoss() + dist_mat = torch.zeros(self.num_layers, self.len_seq).cuda() + len_loader = np.inf + for loader in train_loader_list: + if len(loader) < len_loader: + len_loader = len(loader) + for data_all in zip(*train_loader_list): + # for data_all in zip(*train_loader_list): + self.train_optimizer.zero_grad() + list_feat = [] + list_label = [] + for data in data_all: + # feature :[36, 24, 6] + feature, label_reg = data[0].cuda( + ).float(), data[1].cuda().float() + list_feat.append(feature) + list_label.append(label_reg) + flag = False + index = get_index(len(data_all) - 1) + for temp_index in index: + s1 = temp_index[0] + s2 = temp_index[1] + if list_feat[s1].shape[0] != list_feat[s2].shape[0]: + flag = True + break + if flag: + continue + + total_loss = torch.zeros(1).cuda() + for i in range(len(index)): + feature_s = list_feat[index[i][0]] + feature_t = list_feat[index[i][1]] + label_reg_s = list_label[index[i][0]] + label_reg_t = list_label[index[i][1]] + feature_all = torch.cat((feature_s, feature_t), 0) + + if epoch < self.pre_epoch: + pred_all, loss_transfer, out_weight_list = self.model.forward_pre_train( + feature_all, len_win=self.len_win) + else: + pred_all, loss_transfer, dist, weight_mat = self.model.forward_Boosting( + feature_all, weight_mat) + dist_mat = dist_mat + dist + pred_s = pred_all[0:feature_s.size(0)] + pred_t = pred_all[feature_s.size(0):] + + loss_s = criterion(pred_s, label_reg_s) + loss_t = criterion(pred_t, label_reg_t) + + total_loss = total_loss + loss_s + loss_t + self.dw * loss_transfer + self.train_optimizer.zero_grad() + total_loss.backward() + torch.nn.utils.clip_grad_value_(self.model.parameters(), 3.) + self.train_optimizer.step() + if epoch >= self.pre_epoch: + if epoch > self.pre_epoch: + weight_mat = self.model.update_weight_Boosting( + weight_mat, dist_old, dist_mat) + return weight_mat, dist_mat + else: + weight_mat = self.transform_type(out_weight_list) + return weight_mat, None + + def calc_all_metrics(self, pred): + """pred is a pandas dataframe that has two attributes: score (pred) and label (real)""" + res = {} + ic = pred.groupby(level='datetime').apply( + lambda x: x.label.corr(x.score)) + rank_ic = pred.groupby(level='datetime').apply( + lambda x: x.label.corr(x.score, method='spearman')) + res['ic'] = ic.mean() + res['icir'] = ic.mean() / ic.std() + res['ric'] = rank_ic.mean() + res['ricir'] = rank_ic.mean() / rank_ic.std() + res['mse'] = -(pred['label']-pred['score']).mean() + res['loss'] = res['mse'] + return res + + def test_epoch(self, df): + self.model.eval() + preds = self.infer(df['feature']) + label = df['label'].squeeze() + preds = pd.DataFrame({'label': label, 'score': preds}, index=df.index) + metrics = self.calc_all_metrics(preds) + return metrics + + def log_metrics(self, mode, metrics): + metrics = ['{}/{}: {:.6f}'.format(k, mode, v) + for k, v in metrics.items()] + metrics = ', '.join(metrics) + self.logger.info(metrics) + + def fit( + self, + dataset: DatasetH, + evals_result=dict(), + save_path=None, + ): + + df_train, df_valid = dataset.prepare( + ["train", "valid"], + col_set=["feature", "label"], + data_key=DataHandlerLP.DK_L, + ) + # splits = ['2011-06-30'] + days = df_train.index.get_level_values(level=0).unique() + train_splits = np.array_split(days, self.n_splits) + train_splits = [df_train[s[0]:s[-1]] for s in train_splits] + train_loader_list = [get_stock_loader( + df, self.batch_size) for df in train_splits] + + save_path = get_or_create_path(save_path) + stop_steps = 0 + best_score = -np.inf + best_epoch = 0 + evals_result["train"] = [] + evals_result["valid"] = [] + + # train + self.logger.info("training...") + self.fitted = True + best_score = -np.inf + best_epoch = 0 + weight_mat, dist_mat = None, None + + for step in range(self.n_epochs): + self.logger.info("Epoch%d:", step) + self.logger.info("training...") + weight_mat, dist_mat = self.train_AdaRNN( + train_loader_list, step, dist_mat, weight_mat) + self.logger.info("evaluating...") + train_metrics = self.test_epoch(df_train) + valid_metrics = self.test_epoch(df_valid) + self.log_metrics('train: ', train_metrics) + self.log_metrics('valid: ', valid_metrics) + + valid_score = valid_metrics[self.metric] + train_score = train_metrics[self.metric] + evals_result["train"].append(train_score) + evals_result["valid"].append(valid_score) + if valid_score > best_score: + best_score = valid_score + stop_steps = 0 + best_epoch = step + best_param = copy.deepcopy(self.model.state_dict()) + else: + stop_steps += 1 + if stop_steps >= self.early_stop: + self.logger.info("early stop") + break + + self.logger.info("best score: %.6lf @ %d" % (best_score, best_epoch)) + self.model.load_state_dict(best_param) + torch.save(best_param, save_path) + + if self.use_gpu: + torch.cuda.empty_cache() + return best_score + + def predict(self, dataset: DatasetH, segment: Union[Text, slice] = "test"): + if not self.fitted: + raise ValueError("model is not fitted yet!") + x_test = dataset.prepare( + segment, col_set="feature", data_key=DataHandlerLP.DK_I) + return self.infer(x_test) + + def infer(self, x_test): + index = x_test.index + self.model.eval() + x_values = x_test.values + sample_num = x_values.shape[0] + x_values = x_values.reshape( + sample_num, self.d_feat, -1).transpose(0, 2, 1) + preds = [] + + for begin in range(sample_num)[:: self.batch_size]: + + if sample_num - begin < self.batch_size: + end = sample_num + else: + end = begin + self.batch_size + + x_batch = torch.from_numpy( + x_values[begin:end]).float().cuda() + + with torch.no_grad(): + pred = self.model.predict(x_batch).detach().cpu().numpy() + + preds.append(pred) + + return pd.Series(np.concatenate(preds), index=index) + + def transform_type(self, init_weight): + weight = torch.ones(self.num_layers, self.len_seq).cuda() + for i in range(self.num_layers): + for j in range(self.len_seq): + weight[i, j] = init_weight[i][j].item() + return weight + + +class data_loader(Dataset): + def __init__(self, df): + self.df_feature = df['feature'] + self.df_label_reg = df['label'] + self.df_index = df.index + self.df_feature = torch.tensor( + self.df_feature.values.reshape(-1, 6, 60).transpose(0, 2, 1), dtype=torch.float32) + self.df_label_reg = torch.tensor( + self.df_label_reg.values.reshape(-1), dtype=torch.float32) + + def __getitem__(self, index): + sample, label_reg = self.df_feature[index], self.df_label_reg[index] + return sample, label_reg + + def __len__(self): + return len(self.df_feature) + + +def get_stock_loader(df, batch_size, shuffle=True): + train_loader = DataLoader(data_loader( + df), batch_size=batch_size, shuffle=shuffle) + return train_loader + + +def get_index(num_domain=2): + index = [] + for i in range(num_domain): + for j in range(i+1, num_domain+1): + index.append((i, j)) + return index + + +class AdaRNN(nn.Module): + """ + model_type: 'Boosting', 'AdaRNN' + """ + + def __init__(self, use_bottleneck=False, bottleneck_width=256, n_input=128, n_hiddens=[64, 64], n_output=6, dropout=0.0, len_seq=9, model_type='AdaRNN', trans_loss='mmd'): + super(AdaRNN, self).__init__() + self.use_bottleneck = use_bottleneck + self.n_input = n_input + self.num_layers = len(n_hiddens) + self.hiddens = n_hiddens + self.n_output = n_output + self.model_type = model_type + self.trans_loss = trans_loss + self.len_seq = len_seq + in_size = self.n_input + + features = nn.ModuleList() + for hidden in n_hiddens: + rnn = nn.GRU( + input_size=in_size, + num_layers=1, + hidden_size=hidden, + batch_first=True, + dropout=dropout + ) + features.append(rnn) + in_size = hidden + self.features = nn.Sequential(*features) + + if use_bottleneck == True: # finance + self.bottleneck = nn.Sequential( + nn.Linear(n_hiddens[-1], bottleneck_width), + nn.Linear(bottleneck_width, bottleneck_width), + nn.BatchNorm1d(bottleneck_width), + nn.ReLU(), + nn.Dropout(), + ) + self.bottleneck[0].weight.data.normal_(0, 0.005) + self.bottleneck[0].bias.data.fill_(0.1) + self.bottleneck[1].weight.data.normal_(0, 0.005) + self.bottleneck[1].bias.data.fill_(0.1) + self.fc = nn.Linear(bottleneck_width, n_output) + torch.nn.init.xavier_normal_(self.fc.weight) + else: + self.fc_out = nn.Linear(n_hiddens[-1], self.n_output) + + if self.model_type == 'AdaRNN': + gate = nn.ModuleList() + for i in range(len(n_hiddens)): + gate_weight = nn.Linear( + len_seq * self.hiddens[i]*2, len_seq) + gate.append(gate_weight) + self.gate = gate + + bnlst = nn.ModuleList() + for i in range(len(n_hiddens)): + bnlst.append(nn.BatchNorm1d(len_seq)) + self.bn_lst = bnlst + self.softmax = torch.nn.Softmax(dim=0) + self.init_layers() + + def init_layers(self): + for i in range(len(self.hiddens)): + self.gate[i].weight.data.normal_(0, 0.05) + self.gate[i].bias.data.fill_(0.0) + + def forward_pre_train(self, x, len_win=0): + out = self.gru_features(x) + fea = out[0] # [2N,L,H] + if self.use_bottleneck == True: + fea_bottleneck = self.bottleneck(fea[:, -1, :]) + fc_out = self.fc(fea_bottleneck).squeeze() + else: + fc_out = self.fc_out(fea[:, -1, :]).squeeze() #[N,] + + out_list_all, out_weight_list = out[1], out[2] + out_list_s, out_list_t = self.get_features(out_list_all) + loss_transfer = torch.zeros((1,)).cuda() + for i in range(len(out_list_s)): + criterion_transder = TransferLoss( + loss_type=self.trans_loss, input_dim=out_list_s[i].shape[2]) + h_start = 0 + for j in range(h_start, self.len_seq, 1): + i_start = j - len_win if j - len_win >= 0 else 0 + i_end = j + len_win if j + len_win < self.len_seq else self.len_seq - 1 + for k in range(i_start, i_end + 1): + weight = out_weight_list[i][j] if self.model_type == 'AdaRNN' else 1 / ( + self.len_seq - h_start) * (2 * len_win + 1) + loss_transfer = loss_transfer + weight * criterion_transder.compute( + out_list_s[i][:, j, :], out_list_t[i][:, k, :]) + return fc_out, loss_transfer, out_weight_list + + def gru_features(self, x, predict=False): + x_input = x + out = None + out_lis = [] + out_weight_list = [] if ( + self.model_type == 'AdaRNN') else None + for i in range(self.num_layers): + out, _ = self.features[i](x_input.float()) + x_input = out + out_lis.append(out) + if self.model_type == 'AdaRNN' and predict == False: + out_gate = self.process_gate_weight(x_input, i) + out_weight_list.append(out_gate) + return out, out_lis, out_weight_list + + def process_gate_weight(self, out, index): + x_s = out[0: int(out.shape[0]//2)] + x_t = out[out.shape[0]//2: out.shape[0]] + x_all = torch.cat((x_s, x_t), 2) + x_all = x_all.view(x_all.shape[0], -1) + weight = torch.sigmoid(self.bn_lst[index]( + self.gate[index](x_all.float()))) + weight = torch.mean(weight, dim=0) + res = self.softmax(weight).squeeze() + return res + + def get_features(self, output_list): + fea_list_src, fea_list_tar = [], [] + for fea in output_list: + fea_list_src.append(fea[0: fea.size(0) // 2]) + fea_list_tar.append(fea[fea.size(0) // 2:]) + return fea_list_src, fea_list_tar + + # For Boosting-based + def forward_Boosting(self, x, weight_mat=None): + out = self.gru_features(x) + fea = out[0] + if self.use_bottleneck: + fea_bottleneck = self.bottleneck(fea[:, -1, :]) + fc_out = self.fc(fea_bottleneck).squeeze() + else: + fc_out = self.fc_out(fea[:, -1, :]).squeeze() + + out_list_all = out[1] + out_list_s, out_list_t = self.get_features(out_list_all) + loss_transfer = torch.zeros((1,)).cuda() + if weight_mat is None: + weight = (1.0 / self.len_seq * + torch.ones(self.num_layers, self.len_seq)).cuda() + else: + weight = weight_mat + dist_mat = torch.zeros(self.num_layers, self.len_seq).cuda() + for i in range(len(out_list_s)): + criterion_transder = TransferLoss( + loss_type=self.trans_loss, input_dim=out_list_s[i].shape[2]) + for j in range(self.len_seq): + loss_trans = criterion_transder.compute( + out_list_s[i][:, j, :], out_list_t[i][:, j, :]) + loss_transfer = loss_transfer + weight[i, j] * loss_trans + dist_mat[i, j] = loss_trans + return fc_out, loss_transfer, dist_mat, weight + + # For Boosting-based + def update_weight_Boosting(self, weight_mat, dist_old, dist_new): + epsilon = 1e-5 + dist_old = dist_old.detach() + dist_new = dist_new.detach() + ind = dist_new > dist_old + epsilon + weight_mat[ind] = weight_mat[ind] * \ + (1 + torch.sigmoid(dist_new[ind] - dist_old[ind])) + weight_norm = torch.norm(weight_mat, dim=1, p=1) + weight_mat = weight_mat / weight_norm.t().unsqueeze(1).repeat(1, self.len_seq) + return weight_mat + + def predict(self, x): + out = self.gru_features(x, predict=True) + fea = out[0] + if self.use_bottleneck == True: + fea_bottleneck = self.bottleneck(fea[:, -1, :]) + fc_out = self.fc(fea_bottleneck).squeeze() + else: + fc_out = self.fc_out(fea[:, -1, :]).squeeze() + return fc_out + + +class TransferLoss(object): + def __init__(self, loss_type='cosine', input_dim=512): + """ + Supported loss_type: mmd(mmd_lin), mmd_rbf, coral, cosine, kl, js, mine, adv + """ + self.loss_type = loss_type + self.input_dim = input_dim + + def compute(self, X, Y): + """Compute adaptation loss + + Arguments: + X {tensor} -- source matrix + Y {tensor} -- target matrix + + Returns: + [tensor] -- transfer loss + """ + if self.loss_type == 'mmd_lin' or self.loss_type =='mmd': + mmdloss = MMD_loss(kernel_type='linear') + loss = mmdloss(X, Y) + elif self.loss_type == 'coral': + loss = CORAL(X, Y) + elif self.loss_type == 'cosine' or self.loss_type == 'cos': + loss = 1 - cosine(X, Y) + elif self.loss_type == 'kl': + loss = kl_div(X, Y) + elif self.loss_type == 'js': + loss = js(X, Y) + elif self.loss_type == 'mine': + mine_model = Mine_estimator( + input_dim=self.input_dim, hidden_dim=60).cuda() + loss = mine_model(X, Y) + elif self.loss_type == 'adv': + loss = adv(X, Y, input_dim=self.input_dim, hidden_dim=32) + elif self.loss_type == 'mmd_rbf': + mmdloss = MMD_loss(kernel_type='rbf') + loss = mmdloss(X, Y) + elif self.loss_type == 'pairwise': + pair_mat = pairwise_dist(X, Y) + loss = torch.norm(pair_mat) + + return loss + +def cosine(source, target): + source, target = source.mean(), target.mean() + cos = nn.CosineSimilarity(dim=0) + loss = cos(source, target) + return loss.mean() + +class ReverseLayerF(Function): + + @staticmethod + def forward(ctx, x, alpha): + ctx.alpha = alpha + return x.view_as(x) + + @staticmethod + def backward(ctx, grad_output): + output = grad_output.neg() * ctx.alpha + return output, None + + +class Discriminator(nn.Module): + def __init__(self, input_dim=256, hidden_dim=256): + super(Discriminator, self).__init__() + self.input_dim = input_dim + self.hidden_dim = hidden_dim + self.dis1 = nn.Linear(input_dim, hidden_dim) + self.dis2 = nn.Linear(hidden_dim, 1) + + def forward(self, x): + x = F.relu(self.dis1(x)) + x = self.dis2(x) + x = torch.sigmoid(x) + return x + + +def adv(source, target, input_dim=256, hidden_dim=512): + domain_loss = nn.BCELoss() + # !!! Pay attention to .cuda !!! + adv_net = Discriminator(input_dim, hidden_dim).cuda() + domain_src = torch.ones(len(source)).cuda() + domain_tar = torch.zeros(len(target)).cuda() + domain_src, domain_tar = domain_src.view(domain_src.shape[0], 1), domain_tar.view(domain_tar.shape[0], 1) + reverse_src = ReverseLayerF.apply(source, 1) + reverse_tar = ReverseLayerF.apply(target, 1) + pred_src = adv_net(reverse_src) + pred_tar = adv_net(reverse_tar) + loss_s, loss_t = domain_loss(pred_src, domain_src), domain_loss(pred_tar, domain_tar) + loss = loss_s + loss_t + return loss + +def CORAL(source, target): + d = source.size(1) + ns, nt = source.size(0), target.size(0) + + # source covariance + tmp_s = torch.ones((1, ns)).cuda() @ source + cs = (source.t() @ source - (tmp_s.t() @ tmp_s) / ns) / (ns - 1) + + # target covariance + tmp_t = torch.ones((1, nt)).cuda() @ target + ct = (target.t() @ target - (tmp_t.t() @ tmp_t) / nt) / (nt - 1) + + # frobenius norm + loss = (cs - ct).pow(2).sum() + loss = loss / (4 * d * d) + + return loss + +class MMD_loss(nn.Module): + def __init__(self, kernel_type='linear', kernel_mul=2.0, kernel_num=5): + super(MMD_loss, self).__init__() + self.kernel_num = kernel_num + self.kernel_mul = kernel_mul + self.fix_sigma = None + self.kernel_type = kernel_type + + def guassian_kernel(self, source, target, kernel_mul=2.0, kernel_num=5, fix_sigma=None): + n_samples = int(source.size()[0]) + int(target.size()[0]) + total = torch.cat([source, target], dim=0) + total0 = total.unsqueeze(0).expand( + int(total.size(0)), int(total.size(0)), int(total.size(1))) + total1 = total.unsqueeze(1).expand( + int(total.size(0)), int(total.size(0)), int(total.size(1))) + L2_distance = ((total0-total1)**2).sum(2) + if fix_sigma: + bandwidth = fix_sigma + else: + bandwidth = torch.sum(L2_distance.data) / (n_samples**2-n_samples) + bandwidth /= kernel_mul ** (kernel_num // 2) + bandwidth_list = [bandwidth * (kernel_mul**i) + for i in range(kernel_num)] + kernel_val = [torch.exp(-L2_distance / bandwidth_temp) + for bandwidth_temp in bandwidth_list] + return sum(kernel_val) + + def linear_mmd(self, X, Y): + delta = X.mean(axis=0) - Y.mean(axis=0) + loss = delta.dot(delta.T) + return loss + + def forward(self, source, target): + if self.kernel_type == 'linear': + return self.linear_mmd(source, target) + elif self.kernel_type == 'rbf': + batch_size = int(source.size()[0]) + kernels = self.guassian_kernel( + source, target, kernel_mul=self.kernel_mul, kernel_num=self.kernel_num, fix_sigma=self.fix_sigma) + with torch.no_grad(): + XX = torch.mean(kernels[:batch_size, :batch_size]) + YY = torch.mean(kernels[batch_size:, batch_size:]) + XY = torch.mean(kernels[:batch_size, batch_size:]) + YX = torch.mean(kernels[batch_size:, :batch_size]) + loss = torch.mean(XX + YY - XY - YX) + return loss + +class Mine_estimator(nn.Module): + def __init__(self, input_dim=2048, hidden_dim=512): + super(Mine_estimator, self).__init__() + self.mine_model = Mine(input_dim, hidden_dim) + + def forward(self, X, Y): + Y_shffle = Y[torch.randperm(len(Y))] + loss_joint = self.mine_model(X, Y) + loss_marginal = self.mine_model(X, Y_shffle) + ret = torch.mean(loss_joint) - \ + torch.log(torch.mean(torch.exp(loss_marginal))) + loss = -ret + return loss + + +class Mine(nn.Module): + def __init__(self, input_dim=2048, hidden_dim=512): + super(Mine, self).__init__() + self.fc1_x = nn.Linear(input_dim, hidden_dim) + self.fc1_y = nn.Linear(input_dim, hidden_dim) + self.fc2 = nn.Linear(hidden_dim, 1) + + def forward(self, x, y): + h1 = F.leaky_relu(self.fc1_x(x)+self.fc1_y(y)) + h2 = self.fc2(h1) + return h2 + +def pairwise_dist(X, Y): + n, d = X.shape + m, _ = Y.shape + assert d == Y.shape[1] + a = X.unsqueeze(1).expand(n, m, d) + b = Y.unsqueeze(0).expand(n, m, d) + return torch.pow(a - b, 2).sum(2) + + +def pairwise_dist_np(X, Y): + n, d = X.shape + m, _ = Y.shape + assert d == Y.shape[1] + a = np.expand_dims(X, 1) + b = np.expand_dims(Y, 0) + a = np.tile(a, (1, m, 1)) + b = np.tile(b, (n, 1, 1)) + return np.power(a - b, 2).sum(2) + +def pa(X, Y): + XY = np.dot(X, Y.T) + XX = np.sum(np.square(X), axis=1) + XX = np.transpose([XX]) + YY = np.sum(np.square(Y), axis=1) + dist = XX + YY - 2 * XY + + return dist + +def kl_div(source, target): + if len(source) < len(target): + target = target[:len(source)] + elif len(source) > len(target): + source = source[:len(target)] + criterion = nn.KLDivLoss(reduction='batchmean') + loss = criterion(source.log(), target) + return loss + + +def js(source, target): + if len(source) < len(target): + target = target[:len(source)] + elif len(source) > len(target): + source = source[:len(target)] + M = .5 * (source + target) + loss_1, loss_2 = kl_div(source, M), kl_div(target, M) + return .5 * (loss_1 + loss_2) \ No newline at end of file From a6772d33e83a4f564b8f0f9fee681257a963108f Mon Sep 17 00:00:00 2001 From: lwwang1995 Date: Sun, 14 Nov 2021 22:35:24 +0800 Subject: [PATCH 6/8] Update README. --- examples/benchmarks/README.md | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/examples/benchmarks/README.md b/examples/benchmarks/README.md index 85456ca4be8..779a2bc127a 100644 --- a/examples/benchmarks/README.md +++ b/examples/benchmarks/README.md @@ -21,6 +21,7 @@ The numbers shown below demonstrate the performance of the entire `workflow` of | Model Name | Dataset | IC | ICIR | Rank IC | Rank ICIR | Annualized Return | Information Ratio | Max Drawdown | |------------------------------------------|-------------------------------------|-------------|-------------|-------------|-------------|-------------------|-------------------|--------------| +| TCN(Shaojie Bai, et al.) | Alpha158 | 0.0275±0.00 | 0.2157±0.01 | 0.0411±0.00 | 0.3379±0.01 | 0.0190±0.02 | 0.2887±0.27 | -0.1202±0.03 | | TabNet(Sercan O. Arik, et al.) | Alpha158 | 0.0204±0.01 | 0.1554±0.07 | 0.0333±0.00 | 0.2552±0.05 | 0.0227±0.04 | 0.3676±0.54 | -0.1089±0.08 | | Transformer(Ashish Vaswani, et al.) | Alpha158 | 0.0264±0.00 | 0.2053±0.02 | 0.0407±0.00 | 0.3273±0.02 | 0.0273±0.02 | 0.3970±0.26 | -0.1101±0.02 | | GRU(Kyunghyun Cho, et al.) | Alpha158(with selected 20 features) | 0.0315±0.00 | 0.2450±0.04 | 0.0428±0.00 | 0.3440±0.03 | 0.0344±0.02 | 0.5160±0.25 | -0.1017±0.02 | @@ -38,8 +39,6 @@ The numbers shown below demonstrate the performance of the entire `workflow` of | MLP | Alpha158 | 0.0376±0.00 | 0.2846±0.02 | 0.0429±0.00 | 0.3220±0.01 | 0.0895±0.02 | 1.1408±0.23 | -0.1103±0.02 | | LightGBM(Guolin Ke, et al.) | Alpha158 | 0.0448±0.00 | 0.3660±0.00 | 0.0469±0.00 | 0.3877±0.00 | 0.0901±0.00 | 1.0164±0.00 | -0.1038±0.00 | | DoubleEnsemble(Chuheng Zhang, et al.) | Alpha158 | 0.0544±0.00 | 0.4340±0.00 | 0.0523±0.00 | 0.4284±0.01 | 0.1168±0.01 | 1.3384±0.12 | -0.1036±0.01 | -| TCN | Alpha158 | 0.0275±0.00 | 0.2157±0.01 | 0.0411±0.00 | 0.3379±0.01 | 0.0190±0.02 | 0.2887±0.27 | -0.1202±0.03 | - ## Alpha360 dataset From 66094ec8ef6ac824c2fbbf967781bf6626da8848 Mon Sep 17 00:00:00 2001 From: lwwang1995 Date: Sun, 14 Nov 2021 22:38:14 +0800 Subject: [PATCH 7/8] Reformat ADARNN. --- qlib/contrib/model/pytorch_adarnn.py | 259 +++++++++++++-------------- 1 file changed, 122 insertions(+), 137 deletions(-) diff --git a/qlib/contrib/model/pytorch_adarnn.py b/qlib/contrib/model/pytorch_adarnn.py index 398b4617819..aad01011cc7 100644 --- a/qlib/contrib/model/pytorch_adarnn.py +++ b/qlib/contrib/model/pytorch_adarnn.py @@ -22,8 +22,6 @@ from qlib.utils import get_or_create_path - - class ADARNN(Model): """ADARNN Model @@ -48,7 +46,7 @@ def __init__( n_epochs=200, pre_epoch=40, dw=0.5, - loss_type='cosine', + loss_type="cosine", len_seq=60, len_win=0, lr=0.001, @@ -85,8 +83,7 @@ def __init__( self.optimizer = optimizer.lower() self.loss = loss self.n_splits = n_splits - self.device = torch.device("cuda:%d" % ( - GPU) if torch.cuda.is_available() and GPU >= 0 else "cpu") + self.device = torch.device("cuda:%d" % (GPU) if torch.cuda.is_available() and GPU >= 0 else "cpu") self.seed = seed self.logger.info( @@ -134,23 +131,19 @@ def __init__( n_hiddens=n_hiddens, n_output=1, dropout=dropout, - model_type='AdaRNN', + model_type="AdaRNN", len_seq=len_seq, - trans_loss=loss_type + trans_loss=loss_type, ) self.logger.info("model:\n{:}".format(self.model)) - self.logger.info("model size: {:.4f} MB".format( - count_parameters(self.model))) + self.logger.info("model size: {:.4f} MB".format(count_parameters(self.model))) if optimizer.lower() == "adam": - self.train_optimizer = optim.Adam( - self.model.parameters(), lr=self.lr) + self.train_optimizer = optim.Adam(self.model.parameters(), lr=self.lr) elif optimizer.lower() == "gd": - self.train_optimizer = optim.SGD( - self.model.parameters(), lr=self.lr) + self.train_optimizer = optim.SGD(self.model.parameters(), lr=self.lr) else: - raise NotImplementedError( - "optimizer {} is not supported!".format(optimizer)) + raise NotImplementedError("optimizer {} is not supported!".format(optimizer)) self.fitted = False self.model.cuda() @@ -174,8 +167,7 @@ def train_AdaRNN(self, train_loader_list, epoch, dist_old=None, weight_mat=None) list_label = [] for data in data_all: # feature :[36, 24, 6] - feature, label_reg = data[0].cuda( - ).float(), data[1].cuda().float() + feature, label_reg = data[0].cuda().float(), data[1].cuda().float() list_feat.append(feature) list_label.append(label_reg) flag = False @@ -199,13 +191,13 @@ def train_AdaRNN(self, train_loader_list, epoch, dist_old=None, weight_mat=None) if epoch < self.pre_epoch: pred_all, loss_transfer, out_weight_list = self.model.forward_pre_train( - feature_all, len_win=self.len_win) + feature_all, len_win=self.len_win + ) else: - pred_all, loss_transfer, dist, weight_mat = self.model.forward_Boosting( - feature_all, weight_mat) + pred_all, loss_transfer, dist, weight_mat = self.model.forward_Boosting(feature_all, weight_mat) dist_mat = dist_mat + dist - pred_s = pred_all[0:feature_s.size(0)] - pred_t = pred_all[feature_s.size(0):] + pred_s = pred_all[0 : feature_s.size(0)] + pred_t = pred_all[feature_s.size(0) :] loss_s = criterion(pred_s, label_reg_s) loss_t = criterion(pred_t, label_reg_t) @@ -213,12 +205,11 @@ def train_AdaRNN(self, train_loader_list, epoch, dist_old=None, weight_mat=None) total_loss = total_loss + loss_s + loss_t + self.dw * loss_transfer self.train_optimizer.zero_grad() total_loss.backward() - torch.nn.utils.clip_grad_value_(self.model.parameters(), 3.) + torch.nn.utils.clip_grad_value_(self.model.parameters(), 3.0) self.train_optimizer.step() if epoch >= self.pre_epoch: if epoch > self.pre_epoch: - weight_mat = self.model.update_weight_Boosting( - weight_mat, dist_old, dist_mat) + weight_mat = self.model.update_weight_Boosting(weight_mat, dist_old, dist_mat) return weight_mat, dist_mat else: weight_mat = self.transform_type(out_weight_list) @@ -227,30 +218,27 @@ def train_AdaRNN(self, train_loader_list, epoch, dist_old=None, weight_mat=None) def calc_all_metrics(self, pred): """pred is a pandas dataframe that has two attributes: score (pred) and label (real)""" res = {} - ic = pred.groupby(level='datetime').apply( - lambda x: x.label.corr(x.score)) - rank_ic = pred.groupby(level='datetime').apply( - lambda x: x.label.corr(x.score, method='spearman')) - res['ic'] = ic.mean() - res['icir'] = ic.mean() / ic.std() - res['ric'] = rank_ic.mean() - res['ricir'] = rank_ic.mean() / rank_ic.std() - res['mse'] = -(pred['label']-pred['score']).mean() - res['loss'] = res['mse'] + ic = pred.groupby(level="datetime").apply(lambda x: x.label.corr(x.score)) + rank_ic = pred.groupby(level="datetime").apply(lambda x: x.label.corr(x.score, method="spearman")) + res["ic"] = ic.mean() + res["icir"] = ic.mean() / ic.std() + res["ric"] = rank_ic.mean() + res["ricir"] = rank_ic.mean() / rank_ic.std() + res["mse"] = -(pred["label"] - pred["score"]).mean() + res["loss"] = res["mse"] return res def test_epoch(self, df): self.model.eval() - preds = self.infer(df['feature']) - label = df['label'].squeeze() - preds = pd.DataFrame({'label': label, 'score': preds}, index=df.index) + preds = self.infer(df["feature"]) + label = df["label"].squeeze() + preds = pd.DataFrame({"label": label, "score": preds}, index=df.index) metrics = self.calc_all_metrics(preds) return metrics def log_metrics(self, mode, metrics): - metrics = ['{}/{}: {:.6f}'.format(k, mode, v) - for k, v in metrics.items()] - metrics = ', '.join(metrics) + metrics = ["{}/{}: {:.6f}".format(k, mode, v) for k, v in metrics.items()] + metrics = ", ".join(metrics) self.logger.info(metrics) def fit( @@ -268,9 +256,8 @@ def fit( # splits = ['2011-06-30'] days = df_train.index.get_level_values(level=0).unique() train_splits = np.array_split(days, self.n_splits) - train_splits = [df_train[s[0]:s[-1]] for s in train_splits] - train_loader_list = [get_stock_loader( - df, self.batch_size) for df in train_splits] + train_splits = [df_train[s[0] : s[-1]] for s in train_splits] + train_loader_list = [get_stock_loader(df, self.batch_size) for df in train_splits] save_path = get_or_create_path(save_path) stop_steps = 0 @@ -289,13 +276,12 @@ def fit( for step in range(self.n_epochs): self.logger.info("Epoch%d:", step) self.logger.info("training...") - weight_mat, dist_mat = self.train_AdaRNN( - train_loader_list, step, dist_mat, weight_mat) + weight_mat, dist_mat = self.train_AdaRNN(train_loader_list, step, dist_mat, weight_mat) self.logger.info("evaluating...") train_metrics = self.test_epoch(df_train) valid_metrics = self.test_epoch(df_valid) - self.log_metrics('train: ', train_metrics) - self.log_metrics('valid: ', valid_metrics) + self.log_metrics("train: ", train_metrics) + self.log_metrics("valid: ", valid_metrics) valid_score = valid_metrics[self.metric] train_score = train_metrics[self.metric] @@ -323,8 +309,7 @@ def fit( def predict(self, dataset: DatasetH, segment: Union[Text, slice] = "test"): if not self.fitted: raise ValueError("model is not fitted yet!") - x_test = dataset.prepare( - segment, col_set="feature", data_key=DataHandlerLP.DK_I) + x_test = dataset.prepare(segment, col_set="feature", data_key=DataHandlerLP.DK_I) return self.infer(x_test) def infer(self, x_test): @@ -332,8 +317,7 @@ def infer(self, x_test): self.model.eval() x_values = x_test.values sample_num = x_values.shape[0] - x_values = x_values.reshape( - sample_num, self.d_feat, -1).transpose(0, 2, 1) + x_values = x_values.reshape(sample_num, self.d_feat, -1).transpose(0, 2, 1) preds = [] for begin in range(sample_num)[:: self.batch_size]: @@ -343,8 +327,7 @@ def infer(self, x_test): else: end = begin + self.batch_size - x_batch = torch.from_numpy( - x_values[begin:end]).float().cuda() + x_batch = torch.from_numpy(x_values[begin:end]).float().cuda() with torch.no_grad(): pred = self.model.predict(x_batch).detach().cpu().numpy() @@ -363,13 +346,13 @@ def transform_type(self, init_weight): class data_loader(Dataset): def __init__(self, df): - self.df_feature = df['feature'] - self.df_label_reg = df['label'] + self.df_feature = df["feature"] + self.df_label_reg = df["label"] self.df_index = df.index self.df_feature = torch.tensor( - self.df_feature.values.reshape(-1, 6, 60).transpose(0, 2, 1), dtype=torch.float32) - self.df_label_reg = torch.tensor( - self.df_label_reg.values.reshape(-1), dtype=torch.float32) + self.df_feature.values.reshape(-1, 6, 60).transpose(0, 2, 1), dtype=torch.float32 + ) + self.df_label_reg = torch.tensor(self.df_label_reg.values.reshape(-1), dtype=torch.float32) def __getitem__(self, index): sample, label_reg = self.df_feature[index], self.df_label_reg[index] @@ -380,15 +363,14 @@ def __len__(self): def get_stock_loader(df, batch_size, shuffle=True): - train_loader = DataLoader(data_loader( - df), batch_size=batch_size, shuffle=shuffle) + train_loader = DataLoader(data_loader(df), batch_size=batch_size, shuffle=shuffle) return train_loader def get_index(num_domain=2): index = [] for i in range(num_domain): - for j in range(i+1, num_domain+1): + for j in range(i + 1, num_domain + 1): index.append((i, j)) return index @@ -398,7 +380,18 @@ class AdaRNN(nn.Module): model_type: 'Boosting', 'AdaRNN' """ - def __init__(self, use_bottleneck=False, bottleneck_width=256, n_input=128, n_hiddens=[64, 64], n_output=6, dropout=0.0, len_seq=9, model_type='AdaRNN', trans_loss='mmd'): + def __init__( + self, + use_bottleneck=False, + bottleneck_width=256, + n_input=128, + n_hiddens=[64, 64], + n_output=6, + dropout=0.0, + len_seq=9, + model_type="AdaRNN", + trans_loss="mmd", + ): super(AdaRNN, self).__init__() self.use_bottleneck = use_bottleneck self.n_input = n_input @@ -412,13 +405,7 @@ def __init__(self, use_bottleneck=False, bottleneck_width=256, n_input=128, n_hi features = nn.ModuleList() for hidden in n_hiddens: - rnn = nn.GRU( - input_size=in_size, - num_layers=1, - hidden_size=hidden, - batch_first=True, - dropout=dropout - ) + rnn = nn.GRU(input_size=in_size, num_layers=1, hidden_size=hidden, batch_first=True, dropout=dropout) features.append(rnn) in_size = hidden self.features = nn.Sequential(*features) @@ -440,11 +427,10 @@ def __init__(self, use_bottleneck=False, bottleneck_width=256, n_input=128, n_hi else: self.fc_out = nn.Linear(n_hiddens[-1], self.n_output) - if self.model_type == 'AdaRNN': + if self.model_type == "AdaRNN": gate = nn.ModuleList() for i in range(len(n_hiddens)): - gate_weight = nn.Linear( - len_seq * self.hiddens[i]*2, len_seq) + gate_weight = nn.Linear(len_seq * self.hiddens[i] * 2, len_seq) gate.append(gate_weight) self.gate = gate @@ -462,52 +448,53 @@ def init_layers(self): def forward_pre_train(self, x, len_win=0): out = self.gru_features(x) - fea = out[0] # [2N,L,H] + fea = out[0] # [2N,L,H] if self.use_bottleneck == True: fea_bottleneck = self.bottleneck(fea[:, -1, :]) fc_out = self.fc(fea_bottleneck).squeeze() else: - fc_out = self.fc_out(fea[:, -1, :]).squeeze() #[N,] + fc_out = self.fc_out(fea[:, -1, :]).squeeze() # [N,] out_list_all, out_weight_list = out[1], out[2] out_list_s, out_list_t = self.get_features(out_list_all) loss_transfer = torch.zeros((1,)).cuda() for i in range(len(out_list_s)): - criterion_transder = TransferLoss( - loss_type=self.trans_loss, input_dim=out_list_s[i].shape[2]) - h_start = 0 + criterion_transder = TransferLoss(loss_type=self.trans_loss, input_dim=out_list_s[i].shape[2]) + h_start = 0 for j in range(h_start, self.len_seq, 1): i_start = j - len_win if j - len_win >= 0 else 0 i_end = j + len_win if j + len_win < self.len_seq else self.len_seq - 1 for k in range(i_start, i_end + 1): - weight = out_weight_list[i][j] if self.model_type == 'AdaRNN' else 1 / ( - self.len_seq - h_start) * (2 * len_win + 1) + weight = ( + out_weight_list[i][j] + if self.model_type == "AdaRNN" + else 1 / (self.len_seq - h_start) * (2 * len_win + 1) + ) loss_transfer = loss_transfer + weight * criterion_transder.compute( - out_list_s[i][:, j, :], out_list_t[i][:, k, :]) + out_list_s[i][:, j, :], out_list_t[i][:, k, :] + ) return fc_out, loss_transfer, out_weight_list def gru_features(self, x, predict=False): x_input = x out = None out_lis = [] - out_weight_list = [] if ( - self.model_type == 'AdaRNN') else None + out_weight_list = [] if (self.model_type == "AdaRNN") else None for i in range(self.num_layers): out, _ = self.features[i](x_input.float()) x_input = out out_lis.append(out) - if self.model_type == 'AdaRNN' and predict == False: + if self.model_type == "AdaRNN" and predict == False: out_gate = self.process_gate_weight(x_input, i) out_weight_list.append(out_gate) return out, out_lis, out_weight_list def process_gate_weight(self, out, index): - x_s = out[0: int(out.shape[0]//2)] - x_t = out[out.shape[0]//2: out.shape[0]] + x_s = out[0 : int(out.shape[0] // 2)] + x_t = out[out.shape[0] // 2 : out.shape[0]] x_all = torch.cat((x_s, x_t), 2) x_all = x_all.view(x_all.shape[0], -1) - weight = torch.sigmoid(self.bn_lst[index]( - self.gate[index](x_all.float()))) + weight = torch.sigmoid(self.bn_lst[index](self.gate[index](x_all.float()))) weight = torch.mean(weight, dim=0) res = self.softmax(weight).squeeze() return res @@ -515,8 +502,8 @@ def process_gate_weight(self, out, index): def get_features(self, output_list): fea_list_src, fea_list_tar = [], [] for fea in output_list: - fea_list_src.append(fea[0: fea.size(0) // 2]) - fea_list_tar.append(fea[fea.size(0) // 2:]) + fea_list_src.append(fea[0 : fea.size(0) // 2]) + fea_list_tar.append(fea[fea.size(0) // 2 :]) return fea_list_src, fea_list_tar # For Boosting-based @@ -533,17 +520,14 @@ def forward_Boosting(self, x, weight_mat=None): out_list_s, out_list_t = self.get_features(out_list_all) loss_transfer = torch.zeros((1,)).cuda() if weight_mat is None: - weight = (1.0 / self.len_seq * - torch.ones(self.num_layers, self.len_seq)).cuda() + weight = (1.0 / self.len_seq * torch.ones(self.num_layers, self.len_seq)).cuda() else: weight = weight_mat dist_mat = torch.zeros(self.num_layers, self.len_seq).cuda() for i in range(len(out_list_s)): - criterion_transder = TransferLoss( - loss_type=self.trans_loss, input_dim=out_list_s[i].shape[2]) + criterion_transder = TransferLoss(loss_type=self.trans_loss, input_dim=out_list_s[i].shape[2]) for j in range(self.len_seq): - loss_trans = criterion_transder.compute( - out_list_s[i][:, j, :], out_list_t[i][:, j, :]) + loss_trans = criterion_transder.compute(out_list_s[i][:, j, :], out_list_t[i][:, j, :]) loss_transfer = loss_transfer + weight[i, j] * loss_trans dist_mat[i, j] = loss_trans return fc_out, loss_transfer, dist_mat, weight @@ -554,8 +538,7 @@ def update_weight_Boosting(self, weight_mat, dist_old, dist_new): dist_old = dist_old.detach() dist_new = dist_new.detach() ind = dist_new > dist_old + epsilon - weight_mat[ind] = weight_mat[ind] * \ - (1 + torch.sigmoid(dist_new[ind] - dist_old[ind])) + weight_mat[ind] = weight_mat[ind] * (1 + torch.sigmoid(dist_new[ind] - dist_old[ind])) weight_norm = torch.norm(weight_mat, dim=1, p=1) weight_mat = weight_mat / weight_norm.t().unsqueeze(1).repeat(1, self.len_seq) return weight_mat @@ -572,7 +555,7 @@ def predict(self, x): class TransferLoss(object): - def __init__(self, loss_type='cosine', input_dim=512): + def __init__(self, loss_type="cosine", input_dim=512): """ Supported loss_type: mmd(mmd_lin), mmd_rbf, coral, cosine, kl, js, mine, adv """ @@ -589,40 +572,40 @@ def compute(self, X, Y): Returns: [tensor] -- transfer loss """ - if self.loss_type == 'mmd_lin' or self.loss_type =='mmd': - mmdloss = MMD_loss(kernel_type='linear') + if self.loss_type == "mmd_lin" or self.loss_type == "mmd": + mmdloss = MMD_loss(kernel_type="linear") loss = mmdloss(X, Y) - elif self.loss_type == 'coral': + elif self.loss_type == "coral": loss = CORAL(X, Y) - elif self.loss_type == 'cosine' or self.loss_type == 'cos': + elif self.loss_type == "cosine" or self.loss_type == "cos": loss = 1 - cosine(X, Y) - elif self.loss_type == 'kl': + elif self.loss_type == "kl": loss = kl_div(X, Y) - elif self.loss_type == 'js': + elif self.loss_type == "js": loss = js(X, Y) - elif self.loss_type == 'mine': - mine_model = Mine_estimator( - input_dim=self.input_dim, hidden_dim=60).cuda() + elif self.loss_type == "mine": + mine_model = Mine_estimator(input_dim=self.input_dim, hidden_dim=60).cuda() loss = mine_model(X, Y) - elif self.loss_type == 'adv': + elif self.loss_type == "adv": loss = adv(X, Y, input_dim=self.input_dim, hidden_dim=32) - elif self.loss_type == 'mmd_rbf': - mmdloss = MMD_loss(kernel_type='rbf') + elif self.loss_type == "mmd_rbf": + mmdloss = MMD_loss(kernel_type="rbf") loss = mmdloss(X, Y) - elif self.loss_type == 'pairwise': + elif self.loss_type == "pairwise": pair_mat = pairwise_dist(X, Y) loss = torch.norm(pair_mat) return loss + def cosine(source, target): source, target = source.mean(), target.mean() cos = nn.CosineSimilarity(dim=0) loss = cos(source, target) return loss.mean() -class ReverseLayerF(Function): +class ReverseLayerF(Function): @staticmethod def forward(ctx, x, alpha): ctx.alpha = alpha @@ -664,6 +647,7 @@ def adv(source, target, input_dim=256, hidden_dim=512): loss = loss_s + loss_t return loss + def CORAL(source, target): d = source.size(1) ns, nt = source.size(0), target.size(0) @@ -682,8 +666,9 @@ def CORAL(source, target): return loss + class MMD_loss(nn.Module): - def __init__(self, kernel_type='linear', kernel_mul=2.0, kernel_num=5): + def __init__(self, kernel_type="linear", kernel_mul=2.0, kernel_num=5): super(MMD_loss, self).__init__() self.kernel_num = kernel_num self.kernel_mul = kernel_mul @@ -693,20 +678,16 @@ def __init__(self, kernel_type='linear', kernel_mul=2.0, kernel_num=5): def guassian_kernel(self, source, target, kernel_mul=2.0, kernel_num=5, fix_sigma=None): n_samples = int(source.size()[0]) + int(target.size()[0]) total = torch.cat([source, target], dim=0) - total0 = total.unsqueeze(0).expand( - int(total.size(0)), int(total.size(0)), int(total.size(1))) - total1 = total.unsqueeze(1).expand( - int(total.size(0)), int(total.size(0)), int(total.size(1))) - L2_distance = ((total0-total1)**2).sum(2) + total0 = total.unsqueeze(0).expand(int(total.size(0)), int(total.size(0)), int(total.size(1))) + total1 = total.unsqueeze(1).expand(int(total.size(0)), int(total.size(0)), int(total.size(1))) + L2_distance = ((total0 - total1) ** 2).sum(2) if fix_sigma: bandwidth = fix_sigma else: - bandwidth = torch.sum(L2_distance.data) / (n_samples**2-n_samples) + bandwidth = torch.sum(L2_distance.data) / (n_samples ** 2 - n_samples) bandwidth /= kernel_mul ** (kernel_num // 2) - bandwidth_list = [bandwidth * (kernel_mul**i) - for i in range(kernel_num)] - kernel_val = [torch.exp(-L2_distance / bandwidth_temp) - for bandwidth_temp in bandwidth_list] + bandwidth_list = [bandwidth * (kernel_mul ** i) for i in range(kernel_num)] + kernel_val = [torch.exp(-L2_distance / bandwidth_temp) for bandwidth_temp in bandwidth_list] return sum(kernel_val) def linear_mmd(self, X, Y): @@ -715,12 +696,13 @@ def linear_mmd(self, X, Y): return loss def forward(self, source, target): - if self.kernel_type == 'linear': + if self.kernel_type == "linear": return self.linear_mmd(source, target) - elif self.kernel_type == 'rbf': + elif self.kernel_type == "rbf": batch_size = int(source.size()[0]) kernels = self.guassian_kernel( - source, target, kernel_mul=self.kernel_mul, kernel_num=self.kernel_num, fix_sigma=self.fix_sigma) + source, target, kernel_mul=self.kernel_mul, kernel_num=self.kernel_num, fix_sigma=self.fix_sigma + ) with torch.no_grad(): XX = torch.mean(kernels[:batch_size, :batch_size]) YY = torch.mean(kernels[batch_size:, batch_size:]) @@ -729,6 +711,7 @@ def forward(self, source, target): loss = torch.mean(XX + YY - XY - YX) return loss + class Mine_estimator(nn.Module): def __init__(self, input_dim=2048, hidden_dim=512): super(Mine_estimator, self).__init__() @@ -738,8 +721,7 @@ def forward(self, X, Y): Y_shffle = Y[torch.randperm(len(Y))] loss_joint = self.mine_model(X, Y) loss_marginal = self.mine_model(X, Y_shffle) - ret = torch.mean(loss_joint) - \ - torch.log(torch.mean(torch.exp(loss_marginal))) + ret = torch.mean(loss_joint) - torch.log(torch.mean(torch.exp(loss_marginal))) loss = -ret return loss @@ -752,10 +734,11 @@ def __init__(self, input_dim=2048, hidden_dim=512): self.fc2 = nn.Linear(hidden_dim, 1) def forward(self, x, y): - h1 = F.leaky_relu(self.fc1_x(x)+self.fc1_y(y)) + h1 = F.leaky_relu(self.fc1_x(x) + self.fc1_y(y)) h2 = self.fc2(h1) return h2 + def pairwise_dist(X, Y): n, d = X.shape m, _ = Y.shape @@ -775,6 +758,7 @@ def pairwise_dist_np(X, Y): b = np.tile(b, (n, 1, 1)) return np.power(a - b, 2).sum(2) + def pa(X, Y): XY = np.dot(X, Y.T) XX = np.sum(np.square(X), axis=1) @@ -784,21 +768,22 @@ def pa(X, Y): return dist + def kl_div(source, target): if len(source) < len(target): - target = target[:len(source)] + target = target[: len(source)] elif len(source) > len(target): - source = source[:len(target)] - criterion = nn.KLDivLoss(reduction='batchmean') + source = source[: len(target)] + criterion = nn.KLDivLoss(reduction="batchmean") loss = criterion(source.log(), target) return loss def js(source, target): if len(source) < len(target): - target = target[:len(source)] + target = target[: len(source)] elif len(source) > len(target): - source = source[:len(target)] - M = .5 * (source + target) + source = source[: len(target)] + M = 0.5 * (source + target) loss_1, loss_2 = kl_div(source, M), kl_div(target, M) - return .5 * (loss_1 + loss_2) \ No newline at end of file + return 0.5 * (loss_1 + loss_2) From 55184da96ce093471fc31edf04c689891e298366 Mon Sep 17 00:00:00 2001 From: lwwang1995 Date: Tue, 16 Nov 2021 11:18:08 +0800 Subject: [PATCH 8/8] Add README for adarnn. --- examples/benchmarks/ADARNN/README.md | 4 ++++ 1 file changed, 4 insertions(+) create mode 100644 examples/benchmarks/ADARNN/README.md diff --git a/examples/benchmarks/ADARNN/README.md b/examples/benchmarks/ADARNN/README.md new file mode 100644 index 00000000000..2296af92388 --- /dev/null +++ b/examples/benchmarks/ADARNN/README.md @@ -0,0 +1,4 @@ +# AdaRNN +* Code: [https://github.com/jindongwang/transferlearning/tree/master/code/deep/adarnn](https://github.com/jindongwang/transferlearning/tree/master/code/deep/adarnn) +* Paper: [AdaRNN: Adaptive Learning and Forecasting for Time Series](https://arxiv.org/pdf/2108.04443.pdf). +