From aaf62e0b0361cafc6f130228a360af46741dadd0 Mon Sep 17 00:00:00 2001 From: taytroye <1582706091@qq.com> Date: Thu, 26 Oct 2023 23:05:55 +0800 Subject: [PATCH 1/4] add(fearec) --- docs/source/asset/fearec.png | Bin 0 -> 157784 bytes docs/source/index.rst | 2 +- .../user_guide/model/sequential/fearec.rst | 88 ++ docs/source/user_guide/model_intro.rst | 2 +- .../model/sequential_recommender/__init__.py | 1 + .../model/sequential_recommender/fearec.py | 783 ++++++++++++++++++ recbole/properties/model/FEARec.yaml | 20 + tests/model/test_model_auto.py | 7 + 8 files changed, 901 insertions(+), 2 deletions(-) create mode 100644 docs/source/asset/fearec.png create mode 100644 docs/source/user_guide/model/sequential/fearec.rst create mode 100644 recbole/model/sequential_recommender/fearec.py create mode 100644 recbole/properties/model/FEARec.yaml diff --git a/docs/source/asset/fearec.png b/docs/source/asset/fearec.png new file mode 100644 index 0000000000000000000000000000000000000000..980c51216b87461ac212dfa639bc1a430895e018 GIT binary patch literal 157784 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

rv38`KaJfVnON~wwg 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 =a>D97kA73N(N`ZN~mJ8gyfE^?zg&F--#0V5` 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 dvSVLj;d-ms`?3t8rEnTRA*Qs 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

>-$&{gt=_Y;hg&`^6U=O7r$!6Jer9tm``+vr#PK;B zXGP&Y-TVzl<^2wx#p%>6Hl6dQrWj7^W&3$8Bde^*eB*o1m#v=H?YnNQZ4P}E=YtEO z^pVUi(T|?%8J zbz8S}*yHIZLI!?fbRHocof>mr5KJ;!Y8>qMdUaaUcz+L5mtT=|Ej0|&6J<)k2a*5= zkNYbLQfEX9S@}00=Yz1IJzx|*-1$#Yc@$8mUkH6SF@~)iQt-oL(D%zB;84)#vxrPC z2#wR@p3+6QHxlrWqS9U|1Ic8`x5dR-6Y2xu5HVJp#l6e(Z;skr%-+|2dC#rFGAbDv z)HTmG#Wl$(39o%$PJ-H9rC=pGS2dzwW-`f04x}-x(4Mw)+6v5D$gi)i_W4KR| zga?$W(0BamQDijypX!MJB}wKSaHd)pz=+^MA;P*^5oJz}zdGX&^!Jy?)wFKJ67p^| z8Fg@*GcY?cupS5_hNL_nWy>8P@evcths;iv84c45rD1ckv#F(^KvlXi?B$b)LBipQ zBT!!l^bQ|x4OEQqbsvxPfaGp}{xa`ha;AX4kJ9b&<#g_=^-U#Qm_y6E#b@&pmDWaU zIk3pnD~FSil=Rry2P-i@;3Y#O%_BkF8>hFf-v;YJ(NG}{95DLxr{}xZcVh#A-9X%@ zp@|In>VuczX9pTJTD>L@nV!#&K^CU#y^9U2TV;H@$=1K5LOfl-C?+p#Wai{#sLWz( zTCXyjQ3tC``Qzs}$lvtLf5+C=UewTDr9K+n;!OW8*CX|SW~T|am)oMOEDh59QsP>- z#IKj*Tw%G1laY85^ghKR?>zUI*jPwtK(hDv?_U{mf62^ZK1rwVt@a_-QC?@!u+a;#^!ft>x`7qh%lKP_F8-;9l91_ ze97M}408asdKRL$GZPbirh#369$C_w1SdPzN^B>cqd>|NJVX&QSX{^PnOo@updT&@ve(4e{1W~8Dl z{#@)5J{(QIZ;8Wlyc!GU$iO+&=0#DLg}9KU9NoiCP2k@6GbZA*qqryFN(*J|JfIeZ zB?#^EcrY5_jJa7+@uoj7i#N)Aa=uKnEET$w!*np6saDeurNlxlp@lrsTct!H;W>)u zv#WWKszktS{o33khx#f9H(T!B!6Wv?<>4~i_%HHVOj<%lv&CK4XgEa}mFbVEadC}S zXD0iD?-xP7M|AYWs|TErjHcySIxM7n930l=sFG05)LU0np{wDr0Z;@$p+OD{<+{6n z2_H!*STDlVJnio<^c~)MvHhXF=X0puKL|~AW^t~))0x@PjfsUt^x-lwD`PF+L`n)3 zbveGr^Y?|m!csp{HCq_T4!brPsy0%McH$H&JC*zWfByxHjss%m4=uIATM$Fa^b`?vD6S`v4n^rYt< z9#$kksPsGS#A?&+6WbSbRx|S~c!<=F^9ia-J@ysy<6>hWrmTz#NquhYcZ|3OwTkJH zz}8ecgCOH;3me7lBY0^cG~a((I|x z?c!chQX|jEiO3O__|8T^)Fa15k$S}7@YioW@#ZPqZHpy#*W1QhSoMUXW6ZEEvApIH zvMzdxshq^d$mbir{)BL^9Iz0$^d@_hRb~l9YF1Mv2%8Me602n zoomtnj_Jk-Bs)|Ap9g>?3`}4UT3FKoLvW}>Y;eOkm7#})2Y2^VGaRckXXpzwtqX0W&7iyqS}6_Kf^Y?^A`0)hH{Y*)UXCX5~ zf`h1=LtlxN55daJ_*bd=;vfFN>FW(&?k|{}KlTq%S*3ba$_CZRM#u=b-~K(0lN-yr z4cmv@PnF4Rk;hgykKB%LMsELZrv|Jh(XZZ0W z+@CFu0}d#hU+QkaEq>302Gx__-)OXEZe#>XN-`+FuA$+4yNAy>1*UK+h0uLZKO~V3 z!EiR#MR7)kRPRmY&$8X?^@Cl36J4PR%bpnrsgr&~$)~Tp#di$_5r83!NYx9lf*BK=P zN%P-i@Bduc7??PncY*eDS;xxe%eCwddj}xKEMDn2y$<;bUN0dF;YI!I%Hx9ndA!oL zdEI67PXHSFt^H{wM1=WMRTbmGq=l&kLS5Bm>D|Ig@9k}HNbKp1DU8qw!W0=B$H8!U zS2yP)PCdA!BR?Ej4&DJq$v}#x!gMloA1#5x2bb0Mz2^ls#LZ*RAm~U*|L4_MXpJt9 z&&O+QU!X!+K^ced#!7Ku=*Cvl_(qzmaHqXylLLt9)z$yG5hXbMjHypRS-+9WVR;#L zNGifDf)o727S#Vwa+pf5Qx2`N7z(GY;jXQ%uuVZl0k{%-Y&SWrjfoRsz-7}m`?bX+ zvrwGP=FsOI$JrL*p(sEm;sE|?(VgU-86*X<8!s{;9o<>Bu^*nR|P@RU-f zcTe+Bf@~re(U71Xw;f34EmIrKh+K6D%`uwg>Zgmv3hfRs+j8E}pd9TlS7mK(jnOd! zK4AC-tODvosQ)(v>V&{TIi`01Z(j^R^3zFbSb|0l!t27?l-Kflp7{aGFz}or?ki_Z z66rf{?5d?*tme=hVbSWzepS(hiU!r@D}l_886=rRN9*9%b4dCkj9XkT#_mJAC z5}0x%L_u4~^1}QA@^$B{p4R{t{Ww2eZT3WK#<8AlcHQ-~b-gg(;EJZ~k-8=E@@e1@ zO#}^1%CG+z>frJS=nKC#jgKci?+6q@l7H%T$LYi!Q6uGb+1#}cl$ndrTOflJ+D~98 zIH;y^$IM09XkI9YRT}mu8WAV}3NI#&^P(Zfv z)F3WMCRby1#7zg6@Z!z&UlxMpEb8DTJn_Nse0@)0i+sO6*Y?Y~?*w+=_-&H`tZ~+> zly2(YVit|IZ9q-8YGy-8}?ppO@efcxWm zrZ14w^}>F4&ICVrHID%B7Dh!UU#=tm?BucXX!R&EDX3w| z#zGvqG2!-bX(_A2UKDl~;^2jmi-;HH0UjUbT5VurXmjKVCrrta+)o)R!C7d95qqGc7?AV4}Vz)SK`BN}@EAt3DwC{eXny4=Rrz2z9>P zIK`XuERBa1)*8U$aa)TTWwMP6Ifs))5<*8ma_}VwfdVpq!cbO zEiEz4!p6cdTGtP)G*{mcq{>}wAX}=HPs+ng(*fl-mr7qNH$6EiqgP1gNG-gRIjOXJ zn4m|cLqZ&yL&U%O)JVqi0XGi^iHJ=+F-5?0R&u4&q2KT)=E%UEkcZ3ajR$vXa7cuL ze3*+vxwiuBlyh=Y8RFesy#pKQSnu*WIIVzOS@+ptbt(i^O3@h48n>QqJ;GdW)!c zb5O#MJWziabnQm0C&Dtl%Ztmyc3fUZQrcY9q1f})uK$>_MuJ1}e9bWBtm$oTNtwjz zvyc$TqN0)^3NP##qrxof`VuwUT#NO}^CU*tQb`uyx80&tF;)$nU!f+ZRL*?NI~EKQ zNNG5BJ->Sw@VYy;p=pq%s&m^&$Mqr)mmbc0prLweU^XrE)mz1B%L}I!Mon#C@T17*=LHT zrF7myIcgb$VC5m2t6dg`CK4qSMy7dX2e+^??{cKJ)Hbm;*Rn7%vav9;MxjNXZb{@H z{II`RVWfudUHOL^m)~{(rFxN&hOJpgTWEp){C!S4(lM0DPOCM`j9Q|ZYB*oNB+Vwy z%NruF8m+P4c7pF$W$L0F3^ygXb&XOB{hy}eLk5ovb#BwdFE(fpyI2Cge5)Ft((KJp zHUzjAa|2V6!Sk^ZX~G3hY2CikjG3?>;A9@p_k>RoZL^;{fDt{V)9^S;D6@MVX8;pR zxthF2lg3DDdD&WhcuK*cLRDv-X5v_)h0cEWvLqH!uiKKrKCUbJUJR=RYt~k-Mo&#Y zq>xA;kA=u@BeNYBA&V={>KoT7C@ARt6e0Ys(@t6&AVs93mpnr_oBOLlkwF0j&&a%C z>G<~{TrI5+isYO%#Y6oSM?uzKBPn99w7bwyk*6@Btnu;iv$ELn0!zbgPHt)ogWOP= z@7jUPlI?6p;{I($s!T|l0@fsI!l0Q+NqFI_ak)I{4(^SNTk|h+^sEUkj*N`7IJz)* zA_~^mAV1_~INveS;)ZfBJ4^#8Y3aSaZhg%fSg?9=Qo%JwNHGc%#WD>OV!#O8p==~tef-huY4t)*j|8DdP+ zDfVgXPe|Nb#{nb$>-M{y3_NT)OGU^2`iF*Es^8PIJtvZ#Z$pUL`IXQz#j->*Ux`eU z%m+?!@!_fUYUP|0$+OCLH~MvHUF*&Hf)w9Cw7MqgORb{{bAHGhA_MLA05hmnr*gYc zrqsAC5T2ZZ0T(!HB-Rw zp`oK(BDxQ$+pw{*sivmJ9UMIDbhQ1968JCMHDep2E}u(H^FWBVx+&x44$7moiIm~Xe@!uV(;ZI0BAhUbw-($tyW@{ zQ%Xp*(`J{d$Tkm#rq2;vJt~!ukZpH5{Noy(p*hW^x7F7;yTgf5>_yK;7^1`_b~X;wzlwu(_F%2v)*^D>Ch)G8cw#M#D+dMICfq znYW=( zC>l4N$a*;Vw|`)4fgvA_P^Ngi2b!&vUA8!}+K}%>uiJ>Ed3DwG_WNKm_##`>t1l^- z-=0;zTywM@R;eKEl%At&MEQG?ym%`5Qif#n8GTvC4L zDyYUwQ?i`a_cDczfS23m1PLVfI;-L3@v%V6>gmc#v*dB6T8BD`rew1liY>s;hv=IU zY4SoXFb~qz)on-e0an4lnHOkf{{;N|wKnw(I{a+58?FBeB@)p@p+YgbfK6E_BB5gY z?>^4{Y2r0DQ^#901oorwLJ#Dv97nj%Xh4i?psN_Nt4*Cw+>09DF znK*sCl1{bqo}z~0kiyRVYw~Z`#HwI)bab1j^fG6LJ3b`eGy~gDr)j6H;bPJSQnR5@ zvR5G#Dxo&J$;s($ci-1BRAtrQ4z_Dyv=VbSas94PX<9cNH|l*j zq2cW=9JKFj0Bp%c-_DWF)cd-T11%p{B%qN1g3H#?N4re2!1 zna3v5GIBJhTjKGsH`VmTzeV|f@w^RB7BQ)Wv*~zwT@-uw5UxAovyS1+;VEO{jis); zIfnLMKg_?yD!0(xs2HnoxE!8!J017!K3OPHz@7BmEu7|Wwtuda?I)>JEKg=B=uZK! zY`-6Q2%z@o>^|?$z&P$-nSDhzr!*xF3s4}y6krZy^uVBRjQSEFx2%)*j%HnFs%ipkZ=;=#l; zH{amI?SE$jftw+_pG}FrV?}P8BW8p91|=G?;G$fNQ1%lqlBjlOVs8A1sqOn52PKCj zJLr9TY=p%XTSjNdEDA5THjp4bF&BAB!q07tJUu^q`IS=Tb^ImysN9^DoZa<4t@r6;|8cXr`MV1wl*{MD zwM_m*Dg)z;^-Ew?y4s^FLz9q~Y^!bE^(Y#fr_<9FA+*bIcxs}uvh}YpGpf;VNJ7uQ zZO#s)rgAdZr1=z3<{{rwq73*JBB1}B%oYHPKtD~lqY3QSCva5973+K%?e^c$AkM6) zG4hybAe1lcFl(9?T-oM13@79Cp87^`i@pk?Sc;r`cu$(>Y9>Et=k-=4tCCm0!zuh4 z&(C1rBO6RSR8EY~?$dYr3!QIGqvqg+lt>fDFqUb0k*fEPz(%CtivUXmUUvsmCJpW8 zxvi$Tp9Y}de(!cPg$jtD8?_*?mcH+8jMrV1r1AvlU8**kJEvFkbtcny%dwuV9{H6~ zC7~6^Q`>UYo6W{+X~ARWFiJIsVihs((L^?yqC*QrfmHfdYE>CY85ue021yMCxO^l; z3h}~sv=cmJHqf9VQQugk`S|#PvUq$yy@11K+1Bo`C*UUq6yhjN4Pkbn^zHNEM;!J$ z{G?}-G}T$B+&=}77k(^3@%*C;xJq*FW2rN`ogB3ICoN=DTt_EFn-mtbXU<1HoiuvL z3@;-LO(Y$YPf9P34XK!m=`S=~$OSg4(2ty#?2F!D|0-2Z!X*LnBj8AWDt`m_OO2I! znU>$Fjr`|_-o4d=UUdR*)rsc6>@Tg?Muy-P!mVC|Vg@`($Mu7ts3c(vT5J+jf@I(N zLW#&wugNv%n~&9c3seH4Bo*$*^5D=Hz8c70IcASxGA~Aq-oL{a3+2Zvt6=1CnWsvF zYS=WCq4UG^NgWYYIjrSN0w$vxC-I5$gBEl@r;+b4S*lfN&c;(oQh)x$VWX#|UW*$Z zup43K>&by5%@M*4|K6iGRW`hB`cDz(2@1TvmVbahO58R*!o ztEQ#OcPpp4*@k3ESbw*0MxG0kSgeY** zq!teAd&skq16;mu#C`zjsC;grfGfJ#5uJIeNk0DEOKV5RF_+?E3-9((P+lAq<#;)0 zbNJ|~VE%$AW@;L4EGY*o4;dZmwhF-@+MLA0Xy$ON?uTpb zIhaA-IctVJo?T%^>CoGIa zJlqT*pz9pzDbkau^}Fs{H^aH|yj0`I5y=&e52$GPxEgJWi$%n9HT zL4YBSRjX1iD}RnHxHXBgi3z{K$ru=PUmxsW>?$wW*OIN)bc~B1~p{% zvssc;3~!zpZmA=gfF@LTCGAZfoF>b+okw`#nn5dx^ipkt8qG@&-9<~NV8PBQ2g(9v zm6|A?%mMM_YGMYPjX_32(sZh*31gGSv6qUutmGEzNi~LCnw@Ow$uosJfhEMKa}X`& z>FV50wPCo6m&f6Aq7n!AO{r0C2?Lks<#i9qN?(ms3dTjw*vHAhLbx#1T#`1&(HLM* zmuitM3u7RqQSma|n04}e%wEAjAy{ejq2ZXSPa^BtsBpG=S8!Y^Rcw=6|3&%M+Oevo zar_Za^)f(&f&zS{1HZ#(03CQh8)v@z9%{Qeul3e-xf>?({*G9)r*rvvi7URAo<%wN8|bLmj~)FR0JANwuQoN?qB}4|rzI2*l&^Y9 zDkgNOsUZHeE6iWE9t<8;u5%z zJj^gNc3T-t1c=U;pNzZ+~V{p7TAbIOCCb>lET0^bDEj=5Ia+tX3xa z`^Scsy0a4N!X>;mQpZc=-n}0TO+|-%UG3IaIFxIYYTP?wG7dBtjTz9)q7z9&(62B8 zm}j|RA&D$>q@iZ2zb~*TsKPL#@+M7_;ZBXH23UU<+SaoUst=7L<|OviXyD?Y1WZz7 zNE#YgSeTd;$A>g1PBgdzdJJ1VDS7l{ovuaT(iO~uwL4n(Ixm;}t9l!kj z<0m?S_PO`PqCc0sZkErf`*!RVxF++ZUO(K_xrYx~8>ZXp;CNj}H@(H(FFI4iyzGmB zkj7|>Y|MW^^gzbQ%q*pv)Trc*P@g364_cp6tH1*L`f6XT#)iWK?uFa+=S!3Kz9=vW zwcV`k^Fz2ywcc#@N|;+H&TtVvk%9;H;_dOA*1a({qIy?M&Tu$;;a_XCXfU58Ze0@< z5y`JSE+E?T70OGp%JKYMyZL;8^$376D|u_F76}esu-K^7oLs9lAh!8nV{nxG5kmiZ z4|Jn+IS+hoTCoVQ@&aX8oWvLVZ?0Y65ud75WvY`Cl-}#1vEZW3q9t2r7iqtvT8}DB zKuPCtYtTNZ8JC8gs80}MC_QN-Y%j=X1SL)@)QU@JaiDeIQy^G%85}PbCfS)z=UiHO zs?jSuT2>8w>#8=454!aK3@_5F8kfVuzyy7+T5;X^L7RJbH08g3nPf4)kP5z?+Np=l zO>~**I!HKptkZiQ6~)q>ub|u9kox7dx93lI#=_TdCktP*X5Q*JFBm~&(^c|p(uD|=?a8)abIF>MWfZTD$_;Ri3 zxo|AQ@y9(Gs=s2j`)ikve^++>Ok|MYm#ZBp-|Kd}+tgw~M08+adRuUKI)`>Xu+!M_ z!o@{LKl$Qi;zlc$^@spAUy3+4SCd;kzn*Qk=*N{*(k-&~?K7!AlEi!7y_m;)gQQht zZnYa24O_krFB;o}4Z4voxM+b(+-k&Zy9Yvi|esNnU8iX*swdBq- zmzv;s)sYhN_TVm=S?9_I-rZ_<=&uLm&m(57IqZ;T`81p<+oh!*X~Cl&{eaMuLsI^C zZ#`dpd3GvgTCLGL&{38r)}Q)(sDv@~d6+6l9*h_pzWQ)(cs5K*#X&|-L`1?IIaUrj zy9Eh>GTD&V64poq4|U@P@s)sa{S3$-;6ZjQBZ-s&LeVXlsLKlZzvr5fkv=e;l5?uq zyURku%uLJ9z)cDTLlFs2p$*pA64Y;fk}{(#kY_I&brj-7lAAI2Gw^$8-1LY(V~~Yn zmqLH(89BUe&A<4hc~Av=cnCNI1uoX1SQbhSsM4F}qM-Nfn`Ymu_LMB@$&}d+Bb}eI zf`hJf9AFd)NmSq{sHkc+n(08C)^X9Y&EJUF`C#QA!DhQ``a2(GuF+6*B8j|9h8iXu zG-A2xV5)}!k-f6DHeBxTw3I00?~0we9bXzn#l#pL_S)lT3}LpB32C;2e;>TvWr`A< zbxKR}vN=`j&0p;Hy(2DQn!hcKZ0rgyoT-*0cttE5YNz|yrgZUnz5Xs(%%8pnpl^(& z>UDX_-L*RxbpNH_Y{y4vnf>R0VLDee3(`k4BpS>*fgw#j7x9B1iINl)s{i=l01XG{ z?DRBzdsNVoWKs$Nv`G8zg4cdz64p#8fOlMj*8$8?Z*eZH#c-@BrJDw3yP&kO&eeRb z7zlc0sOvKYM=6i>kE&tWP?C{7dji4vXi>{$UDJkXDx6((aX4}pWm*Y4SKaUd3uc^6aH=fGl`#3SV z*=4uw<9xmt=r$ZfV6$3|?s8}NwwE{@o9|AYgq@Egm&Li+f(=(NA&00ZuDx83=1pc_}G{>?>VBz-ok)6}RUP<@8gR-9MjW)M`f{ng~)ZUgS@9g|(Gp%J%#WKq#s;LFuIK6eDh zkx8Oi9J)#Wp9Odh(ZZw!iuC@pg^y;7#medS{G*~p<4{v>Buj+?Eh6v-A{s%Vp+;$! z>n*NMPV+VY1oh2xlErOFKiX|xB(%m@r;DrB+f*JqJ@3{kC}RJR3>chEAR<95BO_xP zgqA-qm@m{jlf_cwfM5FAc~`HM`O%dlSsETMWzOab(>7y8PU@jn08>U)<>y5ZuyyJy z8nQ%#sp)^6DibLSDBuy8{woVJaY3$eeQPVDi}meU4ELOXGwOhTwj@!kdA;@6WBC^= zjaowP!yHQ?Khn-Wc5+jwmLY}%^nr-yi0zc+(%tm}F*)pPY zd>`*mM-&Q@HpzoHIGm5Hk7MwMq6mUpCU%H~k`&5IQ&qSG$QhV;f1A1nX2>YBQF+?$ zU{sgad48YnJ)Vl~$O_A^bAOvi01e71@&hPh1DTtJAkX5w6vv#v$16nrrq1i!mvu9N z3F6PMZ&CK)eG7cG|9Xy;1xV!*k!Utv-+x}7BiiXg`oe^Lp=73H1iUPDZlN~CI&p)k6Wu_Pl{O?j1=K(sU4UN*+DRh$wonYd|9^W zIVMx;TN>YdJ2qf;%q%^%tq9Oz)Jc$uMMlDl!e#s;_GFk4{96iGrJ6&6oEQvQ(R0#Pb5O|G;$WE?%%}l?L1_u0Pok7OSV4J)CX68}aL# zhAa?7J{7ekzs%%v|9W>hypAMlvh?@|f0&J4#4U2m*$@#kLKeU;!T*Dto@2)ejl2~x z$Gh;r{g&zGw1S`ej(jM@mj0GLAd>t^zabe%M5;gRUhO;Buj!@2fX^{KLDJy@SP4Nv z!OxZ2&#B9GY;0^^Ufz{faR{p}-)n;ECA%5^rpA$Zr>p+KN1Jsxd~T>Qvzo0g^m&?D zXd1O!OYIg2GU&)rLCIulTb$56dlyPFL@?DRTRsmKr_1(DsT9hPc;LSa3iLIQ|LSL^ z;}hivHcb3IE@+YJ9gF;Z4lq>8ApPX#?&>zkb077C7*-d-FP)@jWxae|Mbs8Lic^?; z-}1IMQe{MgD%Egtvp&I;)lLQ35^L#NYdSodQJ{-@c2APKmqQ5f(2oc+vS5`VYt_R@|-ky|{;j!}`JSi6Y;{93KAKfE7Djh^c53X+^ zE-O0}1^dDcn=aej8X_>pC#;T+Usabu7uK^6;iWTmch(JaaZo>JS7R#lcnAyx{ z^sjSxj;FHv9;X18!E&VrsusW1;lcNjb?xS%{{FqWpD~lE|)V>PZbRg zIv(e*vTT?dQxh{BY#f;viCc7zxhDu0Hxf~lL~H}HLjsC&&jT3u^4n++_W2HkfJgU%^c zcE=tzUOrq-xV6jsd}%nAaB-GfAhuOlty(8^6m5-`7_rG>9U~ig6Py0H!xD*Y}x}&CE)0;=44p=*6B-cGy3p2w}cP-`Hv-Zz5|nr_#evNv4yD0SLIf03)CapppQ1 z2nLBD?4pT3P!Z83w@8g)B#n6O=JH~XG0@kzp9Bt48DdTnfa_rL}f)K9#*Mz{NKD=y1Kv~Ffv+VkTi z?t(mW2+a5JuC9+JTsC048k8o478(`?Mcu@&8=?)C(^eL&&66Z2d3Rq;n>!;OqgW${ z`|`TA-Gljhd^K-&#qGmXt|r$-fPkC>oM1;HzctSkB)m>|CNG>&ffniAO?Vpp=gYoYaa|6nRS5Vzet*7mkT&Lx8pE!@ z`|iHKq@9?XxTAsb`G$o2SO+(uFf*fuEYRlRAcLY5`aw@0Gd3P)xnWh#cgSgjAM9kd7Gv_=Rg_CCL?zbG3?)p0Knu8w%N+ zF6WM%Sl15G=s$)=bx~UR+8i?2C>{a)r$YaAZ?al0^tRqq{BbQP09`j{>x|y=mwyEh zsaHCR5eWPaxKB$}=|swyBJtp)V4XrEL{IxGD=!wJDi+9=9uXB4|Miikhg;YygsSaB@itgT#vppy&-Ao zM7bLp7(MyUSU^ayksFuAWd8W`!)!uQCe^l;3=&vo?Oa`5Wjdv0W}f5Y2;VJqXcfow zn#>+actbA=l$e|6eK3=4TzMDeDD+#;e)8!m`4>hOyUxK1@Sj65q|^QqIxSCW2t3@; zji%r9V669T=+qf7R&KIfyt%PYjX-xmheql2_R@#AeW;SK$rWP3;Zb617nIj>IZi9x4{ag>R|9j!tO##E4mM7p?m_wf@ZO0jT8iBdlte0a4&7yDCQ3T0RrLlL6C z#y;obZMFW0(ws{$a0u4G5C97%iQ45Q2!UkD>a&OIfMu_?gG$%Nu}4# zT~9k^pMYVZN;zNv7*dC;zTzmS$OoHNF#N9IGFEl;l85N*a{u#Ql2BXN^!O#37HDc{YNM%Z#QMobnug27UEuOz~x1P7kxJ(7bkSG_Se$aep z;xhG+m&Z$dRLON%Qfa0NzTVpg`vxTaP_%*6^4h;78-!XD5qQb%&6 z$bAD#ZQ2mf7_=G_IhSvn-d@LZ*-rH=IH^vgBU5N1p=7&1e@VXLoh=)T9KtkaJZblY z$Q6=7jS%0wKAd}U45$9oGd_R#b8&L=yf+AoVj%u7DacEQS8gG>uvl+?=k;=H!q3nD z@%j+y+T(1jywIofgd)h%O_#M<@h_U=f;RqnKx{k&-;hxE*vl)fiRQ!W%fBk>*`s!{ zSeR#CI1R<`8?zGS=as^?;dQ+5w$k`pqy4mQK>cC z{C3!EKK+MMnE4a$P42M27Zz$V0MS34xf8v|>{y>EwmK|cgD^H;$~&)C)y+>P^24mj zOZ>^1Y>Wh2Oyv^*Y9IK9aOcK)T|R?8oyCQyM3L5a@m*{q{Oe6m0Nd9plq|}Qf~lx9 zEld~xRgQqsYC8N?zr* zhRluV-zdDFYW0^j<2-MRVo=(wt0+wW-D&;CQ?4d^<8yTQ1EB+m`JD^XcJ5~S>~LF* z1MT{rSUISm!9k6To}p&OeQvai;ACb<^MmKSjA5;ot3q@9O!UWc!lj_#_OGt|HF%i- z>5IqU(iB1l*qLuSGiK)fr{^=0x4>m#8oxy$OuZ(EPPfOQq|9~(Ls!7jNZmdFaEba) z?y?`BkYc3Fk#;e}^jX5>N?Qtp`Mx9jmWJL0h9cP!qq6Hatm$l` zqoXWH%+-xYcAY#Soe356g<&@2RjC1N774gJgZnG`eR~9-T@Pqv&#EtV|7Ev@7lO>j z#zwbSid^A|NTA1ipFYfX#?wr|Oft1u@coFMgKL#kLnB=^MfJ!uJICRwktMprYs&sf z&owmaqi=Oop@1HWQ6LZNf7p7*;L5_SYc#fP+wP=;j%`~V8y(wDx;wUQ+g8Wv*tU(k z`h4g8s_wn-uU)%p*IsKq>%p93jyVP@%2k0G4MA#OgN;%Vs1DwUgbsA&dH_JI9tZ6D z0!%O^;)qh44M8yjC7Z~lKNBi{(SP?d+oh5#k=FX-kdKK2+va*It{0dDV~WPq;xw6< zX;x57t|C*uSb2OvGyQf9wj?egCS+c;7n}#yOqNhAmws`+&trbk5tl8{hgM#Jt!yBS z(xYlSq76k!h_M}kseipQx~*u}?C=qKplRO^Jg;ZZ=_SA!Z7W`psx+i*cIirQPPzIGez^p0hLLu{WPTuzuT#Q6U{7V5nP`7KgJ;ge^ z9iTi4tFa(`w^vog$ivEoGJ&BIZR}&ia{aEHs7P2kO$FoH2Kcpdi1I%a>9@K6BFjme z2AeN8AXf55CgK)cxLj_GQkLE(0t~jYv-sPZ?KcD33I?Zalonr8K6mEGYIzi+Y@$Nc zR4r!5KZUQciBc39z)Ftp`a*7(3Sl2DF*)$`lr4VREYzGw&p7LRxlk97pC9e?hl<@1UlV|^trUr=Ek{aD zhX2Qv_7=TTAzkdb?^Q!b+Ry|Xwi@fLV>RjxEw?n>%ffr6F@9Vjfn$4?Upy?t~NFN{xb_cz*rIz|{jI9En@qd6{yRiHK+bkP7=_*D za)|`Jeju0b?%5&&8B*fV5`vY6LW~Sgoi-pUv5O8aWK&QJ2tVO)-K{ONPiy<(ai(#F z{Yl{|_gUpq)DtXS2fxlnoBMucZe^i1sBYbrvx2ECXSXb@B(BXBuB2EmHS;r*+m~Sb z*YN_1tqWbTHx&h?>Z#)K)6i|dSIn$BHf1Do7m~KF6K2G~-D9~ZVmyq>0F0sICXl=^ z+}7`PDTy&aAIFj?l0dNZ^LX48WM>m&b?NB>)Td)%NNgGucC{E5S~%q_0X;RD#J}MO zrD#Nz`g}eou3OF#ndKgTA2!5w(3X~`wJ0){aEy%2TGL`(g`q>MJt9eNp6ipTgjF6J z#(sMlWngs46b)|$*ng~zdHrA<{j+V_saM4KfrPtYn?S` zp=(7E!d8#=NO;k)Ion%(&+#TwNV=R~xo%vAlY{>5bY(#J(6qF+_Y*xG8l-3O`9|AN z`n=o?A}QE(Lud&b_XS_?{#?$p)+mi)V5=5P&G_O6IyMp=h~kj|QW7TZaC4X1(<6A+ zTyXj;&Gi1#S};hnB^)8RGV7Dy*J*E>6h{@8$wf&oFqMI-b0 z-tKx{OAW}>sVj=oW<7B<*Yy1EI4e?rQ8t1Lr^86iz(4!DHr>QhA*q82LxOkS?B76l z1m^~OeTcVUr`%s(#E^b?nKiaBqQ6kh_8EJ>q*;Mh1ld(kKb+|y8tzXnyoEaYX*$@m!j}q@5F&g27L(LlVzWeILti zZ(<;uL8J_UU%d(~*BIredJ~%G7ElB!!TTYBnIrpfvYt){G&{_R6le0e(D_6fkY;q|O_ky6c=_E-G!PV`xU59LSmjfIx$gG?|Q_Ui(Y88 zlNzlM?d*1a^{$Vt=4avbwqRdEF5OWC#3P;eOV<(L&=_@5b6@OPc?iy6nJI{8GXTJ3 z8Wqg_5zvqT8tI!gGsI%;MAbr)3iG?}lFtF3*=@+hB02n&ud7YAQL2PMqx)R-c7oWX z^YLuqAlmBUOs~kP? zDQfM&Z+&6|3ZGUt6+_>M+)~B-Qa4it^f1A+1jLN5e~l*>SLr*S&0Dr;Vf_`9?1l(176K_9so{sv zJ0+7Wb(Nq%>hwTwkYMJwld7!D$MJkdMTxgr3Y7*HqRcDu;fKrCah z%931~t}`9)Fk*??I?&S6M!?hYn>uLs(pV$l=qeS-Kz(&g<7hw$qQzp<6lM)#{Nsws zGB>NWY-|+O^Y}Z3-W)MzyinoE0QqZ_2DPH+7XY~C86@{WL_P_w$iXAHyj;zEe-cd^=@Suk z_Fr{Y>`PDCGb|e#8v*sZQ&XOXhcNt^P%pK^^KAQ)roG`}vhRm~B_1pLP?lyYLLMZo zPuYjt9$9=i$?KXmn< z4B^9}=X8L1jm3P)Oe7}#P&97-@uQxBxHb-t-`n*tU}Xj1lK}U<6`M)71yJH>xa(tK zcxeKHY=U%W4E{ycxRJQoM!&}0)pz+zpA|1rLLJtidydRe6jn+G*f7#jRF9V3 za(51;Yl^6#jUvJvU}0k z>sC^wJEGmo`v+af$vT6kY+21}#QKN7G}qR>jQ#vD*llKB{|z)}iNy&zndzGR=BSZ# z=xZO@n`#=`uY`!@i_K!3$K;f*aJr7;zn9N;rURoLetKN|*9Rf(N%{Z5I2Q zku%-1P|p=Uke82*JVZUDqoe6&v6yOK(#VWKl-Xcnxj}*zWobj@vOxa;j$iDE(T*bu z@{f7%&t0O5#8N=WG&;meBR`@O>XIju%p{k^=K-X?)71dQv|K)%XTCrR9UmWmy2I=7 z&+2Ns<{MVj`{LzlGcwZXcrx?lOg?m=Zq_$HqHRplg;aKPLohLd6`;1@4tbCK)}$`r4b@%yW9}?QPW_o9c|LG}&*nJ4AET1}MGI6Bnf@tW1X4Id=O=jw z-C6dN2nLx*ENh0-zH3qZy0-fNZ~>O_G+5XhpiFJ7G`&3w`iLN)%=RbN9B9dT6Qwuv zeojobQ8j*^tE|Z=$(v+$0H9aZ5Fh+}KQ1RJI~6mim26AWLlLHuokY{b8Jdq2};WYU6z6d5YC zw+EFB`0#`o^WBB;LSGvW=MXrJ;uDq?(U1jg-d2qfvGaSojZW6@}ApM!JVxb|G zSrWC=3>N}Qjn)5^-p z#&Pb-q_!TEl&Z!n-&z`(2HOtcAZ?b^XnnbZN9UqA&|)Jz92Vu`h^{m6n#37I$@La-!STfYB>4vl3`jBqu58W- zQ>?zb5`8rQUusL z1*@$07vndWCl&Vs-*Pb(O+8I-sO;ept=&)8h=fR3P=@i&Q4cSm09s}D{xV1r zlI0S0iAsjBtq@Gns3qk9H^~~{`?$cet)-k_$IQ zrq=o7k`JSG1b@wNo?rvO%{vnerBKcis-%>BkqOOngm@%jc^(D`JSg|g0}Qm1YM*`> z7+dw*PwHpa4Jy+bD58;(&`2rTvwpAukRe7<*x2Z3QBX6qg7*v#%UK}xtmSN2>ZRRJ zqbAsxW!r6RJVD0h2Ax7%o#kk?6^3Gt=tXC%OSFCcZJv~w`Od@5$HB%Wz@tWihL7t+ zXj{p*u7?ogj)3&`$ns@c62#+UPge(Knz?c>VeM~96L&JCz?EDGJX2$@^0GTBo(9*~ zDq%JotimQyx(;a#ZFqv?x;iipeeGj4ad18P%nO_7%tXhWQ#t2{y4hkl_-S|BXy-)z z5QviCq4HSCv9wbcA7folW#O-(faQsWQKkDY;cDm-*{9L|BIGkRO{RSi1Ta`BSmyUE zdTX(he>e`w&x8cgZNcsVSO!YmfF;e+?<5a20Hg*DnZcyfbe3%3OFPPj-D$Hl3}1hE zy*sQ?rORW~2l2F1rPuxdbo*$gSE-iH0@`uFv3GHK8HXcwG9Fr)3v&$d0)>cIvPow! zHe1oZ#hLLNHQmiQIXnAvS3;qlt$rXcUi2r@7E^qBHbtp;})Dx?Cwg{j983=(`nonFQKy&gvnl$HjZ<4%ywICI%3s zEb5+QsLU;_H?yG|I5`uwztvVsb@B5sJ3(xj$lT-NqAKlW#v}cEXmKZHaZv{{{~G7t z36PWVo*K0{6S?bQ$;=u;+vtXtX4mJ5MLz>o(ICdSz?76)x4}#JolNl&_;KEWUeg?b zUU)Yk^8+evDXZj%kwhbU4P?gP)a#_W$x1_ZQySI!La3F>g<}9Ja#VhTZ73lYOg)W0 zsiHT{7DDT>@aO3%Sn9U9DeHwg!@k-!?e=91mB*E!YbJ$1uEjfl4th~sT>SoIPA*B1 z0jtoIGOfg@V#G2;GOM*$)g*N!k)6HXm7tr|_VJvK3(c8ao%zcj3j-0RsBL0uTJs5( z?b;lqV$+Cb{+b0BZRuIl)zx%A4Bp$ZOhKQa(6}@n~A1t0_{{7my(9BTLgbwF0V7CkWs;jhk-XcvC2Xiw8SjY|NUD9>d*R^ zRNV4>l1^{J=pjorw3MAMR;2}fs4Yhtg$0(V_)aEq*xi#6-5$X!2m_32+rz(Rr+ z8y;+_Ipz`Tw_@&N&9q{k2iCYANhTVEzklE6$!u736``KR2Lo69_^ydL>@kjBA+ju6 zUR{LXh(-|gVr=VLU1ChB&4we}$?s4>%SArSKVoDe2CRIim=9buCY7*r)Xba6pXY+r z(mO;h?%C)R)ZfCz)ek4^N3vWc>T(j@y+y7Ebov(LmN8|W-NO3Dv1dZLWyQtNl_X7c zeXQz>srb>f^+-Sr2&HckU=gn?)nu^sP_h8T*Pji7`)tiX(#ZAW6)%BxCf)NWq`|6$ zJh%Ji3){h>#a1+SreulLyQ+jTN)rU=Iw=YkmNY9fc~z507}5e^N-(=n4hYO%R}=iS zvO#){(;Oy7{@a%e0ZTrY!>1F;n;z*26Zch?S9K$2&50YT3Ruo18N0eQ23&9-vHB0J zA~P9sqTVq&jL55q_#dQmii>F4(fuTP>^OJj^jrPVE$e8K|KR#O(VpoM{}U!oo^qO3 z#+NPxZm8wu)P#%D_9+GG^HJQ9Sgj5p(5On!`lVYaBAacpKHe(&&ZH~UR21PDnZ}J8V!{!$kGvs@dsn)){-!*%|D~UtJW0DHVOy6X1dTDJQ;hqn}Xx0^MM{zTO zCQ`m*QU!ogoc_03Ce~0CEl^NPP|$EJKQuKpEAv-q#%SR~$)5UgO!pr@BYki8i;Hbg z6mk(XcJ>iv?NXnNyB~%CPeTK61a^BQ90}QR!;Lf*E}>8r#t0c zWX6aeHiNADY=ux$Hb&x}R1MP<1DI@0B|F=BV!vR3=3;+6GN4JkfSb?v&LCE+jpa>J z=U;6K90CDhX~?X#b}_Hd+4;_LTHjC7>+ZPm@{S0V!1RY}foTCz_m2vRxU5lF++N~c zeLe(OcXY=422yAtV1t>)<=n@irlu};kHNbseBPt;@=Qxh8{<^Hoxjw(K&a7fw0a}3 zv*MaJr~_sdX*E0T<8>X7D95wFuw1;slGOam z#)Hxmog7O!X)?o(rWl49e{udR0W@fOf09*JDXk?ME4j#k=56HT>dz$Ygs>VqKCngr zfz|`h4!?3J(4gfL=?nOmEfa(CN~k=owYBK*s5K~dCvGS`T+9tC8a}N)n0|F?Obj24 z*O0Zk66we5>JlREmb~sAD;E_eLpWZ~SH6Lajf;9cjYDM4`j(82NxI`^vwm~3FVpJz zk|3RVex{pMHPbNN=oJUFd#hDw*4nMNr8O<6coy^qD_|i(z|YK)qSQvggE~7qbK`AI zSAi7r^!*erNa4c|xrd_<(RwZxQ(96q+MieRvQD9Bbmp{Bl%Kr>%TMdHaQc4zyS+~g zkw;&hfomeu1F7SQeU|NK1NfMwB;BoTS%XPew)wqt zY8k@uU9ezv?qIfoZ)&al`o8B&I+EdHJiJM zX=vCJ6PvD+hVn7ZY-1+;DlL&<(p8wEdr}b+`A^Fy2H3Yf0&R)>3Cpa!JB!Mo5~Ti| zpW!kJ-_EUKWEs({$q%v_x#y~6k{Zz!Y$&oG<2b@1J8R!;U=yzD<)q}ru##J356Y55 zsj-pvZl&-PT&|du(*SXcD|bHFAgMe-EZU0#_CS9Q+RZN*dHuhQ^OWgcmy*X?xFw4} zHsg!gixtXeCRfMv%Ys~ZlFqiedvk=OD6qmrq5KfwaIWVR!mtQqqW(m~$QP`v@}}da z@7v`s_>+?Yg`EJwPAJ4J`X3i(xYNSV1@rO%nWp{6Dbl_oE_iSuCl?nNC%cOHPy5b^ zYO$_{U;unZnFeGTh_a z(u{dkq(tfZH#GI(EV86vM;(5Sps`Ou;;a6s)GUV5Dv6gUrN~eb3cZq!zfNa@bwb4j z&U_fqlFWb)$2Ud#)iWLh1lM2gb4`SwWo2bW6M}Cr>3T5LLslYiQEjpS ztf}Oj5%N?o9@enelcW;hj6#EQTlOEL?bg;zADATpYc?`v^R4d2=j)h;!+5edVPHc; zwExHeQ_Qk1SA4(mRx|Rq%=__LEF1?eKJ@lf)-uhp>H#C#uMAuuz zXQR&_M@?gpgSA9a5J|A^D6}z+!3l$|R|f=! z`K`L1!+nF0-5)fDg8UN|mK%;NdySEDMW<2!*CMg$st*Op6eVEA<&>(JVzbp}e4{s4 zCoYR_Ufv)5aUZ^k;p^A0m^d`Y^W{A;QX+wZ%OYDq?xv8%*V_W1h9!eD$vl(wc0p91 z1JWGf2oi(J>_fCvy#% zup8wm{+5(b??@HN@TFG$E~J*=3P$Encs0o&=}qD>@@Xcx9Hpeq!o09XIUe-sVDH#C z7-_|kv9+<0{$ZqK<=XHZQ3s~ufj<7=$DOV;T95@1k zJ(RrA@mmBCHfJ&8?OLS04Z@zvQYh18V5~DUQ`rJ8G#W^`LHeEUO1suM?jd?W7@5=6 z^Q_7;Lzh0q!GAa~piElRR_+bZ=<+4vN>mRkwHwP6bHUdC0&1tX+x^fPX{o84wkz}E zN`R1cMFmjVD=sc3_t68egX1RE?dAG^jtJ?T)=8D;Zs{AFfMdc|FF23!3E)ruAuj&> z+D8Jk0Q83?-pkZ_zQ1s)vRPTwKy%qFO>b=I8x^mViE)6kDfvhRP)Kx-2!a9!>=I(C7Ub^$cz9W@T;1~T zgCw$q79ed6(#{qbxB%#m7`9kW(;diwrGIBBr`;9#ePrX0GJZXFL1xGF5x7&o8CJOU z8oGolljSsgZ?oK~H&f|g!NMY31_MfyCHkleflx{gg8>PqD}rR>vv9)8+oqY4hF`Zl z>z1*YY!hWnAvOzb%Pb~KRh_mky??Mo2KxI)9xm97l2W+Isdgz^V>7y4Gr({g%u=9< zAHy+=aRVcbwj(|73qdMyFW_X>4qa?Ak+X7WV0MJ;xLK29ySXX$bh&X;$%YoRdq$0-8i& zkpT#tb%5a%A+IY?`7I?Vj8uWBQ+7;j6P1dss5wD79A`8L1Wt7!4}HMwwJSlJVtR&M z<)Fj1(OqWV&6EG#sn%BZZs4i61V6t{a67)>%;K;qDa zNgn}F%kad|rGzR8F%&qDh^>gS!IQO3aqt|Rnq}Rellsx}gxRDKhYFqcC(klvF=wYg zn8DDiA}=gTJ5Fg3gCyKEx_%e-IS4J?50UCO!r6nD&QpSo9Ku=`r-w-t|Is_@%!J_% zW{0zO|YfWcb;=uSY@b;b=s zzfm&$VrvWJm6T7fw!;4hQDfJJ9Y=3HXrps)Aml>1#%7I?2(T)EN=_JR0OL;8=mUJR zc1W3Fx9>YQLFw4-+w(18Ob!7F>EY?wM;0lCN44qqe*gDyDmyoyEZ7|=rCIh0`n}v8 zZ*3Xgfgz39i9L$Vj4010D~5-MF9Bq$gMd!2$EsiPUtzeE5zYZj@95$%+t1b6Sp+e` zBqfA(3GlhpYj^GS!13+)36cI>u3B2>^ZEdE?8pIDQ!fuGKrw41PN@B6)wjP)w0l{Gy5Z^X7s5)6ym7 zb+eAYLX164T74aLvSjkA(bSP=lS6@ziMx)i{`XFNnNIjF8_uczt+#i_Vf1n3SuIlz zM}gKalC8&w$w7GREr|sC`Cu~Cuph!GH0>~gpz*1UG(!P{W(P9C*~V7X#l~0$)(cWc zd9jjKuEnvECqEw5yl=h<2_C|bFv8t<7;6---4*%vhr(c7?wv7EgBBe~e!w>Ne!jm> z3JIk!m=+X&M<>aISB=g33b{N8e`usTcnnn2a~5cBPgIvW=2giy-LGRIWnjM#MQZ`f zf8Y=;mQ4j~gd>}wM@q1nCe6(i0_{HpDkHjLPr|_|Mj2Sm))PCv2cSf&wyFN<3;8-KY<1Yiysb)0Q}!9 z3>t?BGo4(qLM!vLnE(q5DxY4}y2K9=kE16^6=tnODPO??a1=pb&A0&MB&C{0Oh}r3mN#Q|#*V<; zVZdA%t7S5S2R*V=kfcPXnF;Nq_msQZ-D*)(OiYLviuK$g$E@FbH2ffySu}+5DoPSZ zHNCNn*7?lky3X~tRLsDwJzJb>;6ds*Zg-rfw&2O1;1*GaDQYuTt`5#RXGeo?UZO^? zROeCPpl>Jy-&_&+yM1n(P&Qky5GyDe#iG&87*s?q=%z%IkLJ%T6|8^z8 zBfU_ZqHeT%6lFd|!&bs!s&bwn1{*3$FowadC0&`E>(M8NVK(G^_@~qTddKhcm6#!^ zu2WDYPXQAORG5;%D<@!9Obq|Gt#I>Xv;Rh|S-=J%Vc`ZD_uwH=6x)V<+l~7sJQ*~p zk|ZT536sPaOT;g|EMv-CN+U8HI*D#YF&+8`;U0(1O;8YIUN6Nqm(@54rW46WDJTT& z?e2Ka@ADn|h0YVge2LwBA|=3n3@B5+y}c=t^Xiu&Sabap^<+eV=9uRDV!wePjl)9= zR+A^FLRuCnbGg}FT&1j8nlpc=oa*B97o#L_j+&~NJoINht+OqAkkp~(BY3KE;%#|n zyfW{#Gs-8W5Y2B2GgfSV%!J3Qjaqlv-gXMcC``~N$>|G3d;M+!=MxHbtVlJo+5a#^ z{xhOu5MT)_5hbnj7Dh%QIO%MxPLx5>adF+iSOIg-_x?nUlA^6Lv=>*P`3Rh?M+L7* zfbqc%&_}u|M$!aRa}I?H-)f$|qoIsROYhAKUqCELo}M!?g-1jjj0b0@&CpY;FRV8H zPH`4|s?DqzWiJ|=kAxrbg9~2#5g;dbShM{Uq9zHj}PdWgk_3ndVtV@~)lIAos zFSlYC1F?_`+-OW)D`f$TrQoJiP<6uc7}TmzkVB!erz8?Kc^vidjLnJFobb$W%V=VG zEtGsqlCaKkYga{W8o}SdRHrmgqCXvRj0t z#D5{SLPSv6a>MfkHC`Y(EC1D7)<7DlTH@&V`Tj_3*_Vj9|}wx!7LD zN`i4G4a$<>j~S~ZIWiFh#X+$j>pVqH<_A;m1A&o!Bo1u46< z0|CT=2?Gfg6*jrS&!oS29wQ8|Yh1zG8x@1-Noi+b3>o3Zs6_{m`2~!g2dEv}8D}1& zVg6?*UZO#Y)F>s+gL|bZ(5&E!6)6fB^wPLgg)UD{BDXkL8hhshL;)E}RTS$-G|vIh zEP&S1|3`#7nT!}#-m$nu0fR+f`o1p#Cvk~_Rz@(H7M-|3=&8D|Lc-Z!zd(^Q54I;j z0EGyPsHuj(4<9QL0}+GksDCLYVj|B$zwp#ZICvc`+-Q#0HLQNeN1WJc*e94GiZ zXS$YE^9);zL(d(%|1CTw*#Rw-3+9^Tafv~st=+9V5q5sYsh=AE35UM+%Erf<= zQC@+$+FXIyGMIV3OoKU#tglSG@`f_h3R*9mEa)2DF8i5l1`hOxUXpj>)#E==Z^B8cVDSr7juccu>=D53LW1Ul9atvyCn7kJ{dcCm&g8x3!u%x$(%~N zx#(Xxgy%1cr^-miR`f8kS^Z#~iZ9KnP$6YvW@2V0ne4h1jGxfAcHPS&qIfY7$x1a8 z*x^H4%4)GkW+- z!zL9^N>=vn;G23e74=$|j||gYIf52w)U=@Ca{fewq!_q?N+o{XjWiUq0gZ1-SSflL zHbP>`v!N2vt9iq9sHWR3Tunjkvuwj``?l zXf#MH)U&JPmRUdksv<5xjwyPeh+!eMh4+GUfWZGyf_MeJg+?YKk%FD~offPuz!SEi z=}BNs!h=*p?mY^NDa#dL9nR0N?8Oc6V*w975RZU1MZ5L;0WFKtjh)}4HZ~C1)DVmU zC726^mS-KwH~?ayimM0$EnS#U2#U1yJF+4BC?s!ynydPt{V|cM2o4ADciqw5HO=Hk z69bkG!yj{<8p)IxY9%Z;tYH#^O8-ICIrV66VKiv3^sht@I@wUjj^jD#r8!e62t$ny z7uO^oE%GkKu}4hwA|IwMkR#dXxzn^@3H#{XW%FOLe7jK&31w6ef7C=5%mttv{Fl@X zC~&000FDS`k_d$ef?h)Uk;a>=%fPk_kn`Se#+m1+Vw9mdEKq^Ae$7s+>O~+6M9N~J zCaXYdz&o;$B#)^1>Ve7SvZtC2wUWTV>LZaOvHi0%nC-_mirWIZV~`FfCAC~sr9W*w zaA5?{%POr^$1*7!b6FM~&NLY$7BhK}Vo-Q5XjVB6vy;bQ4aRL1H6T5xUnC#UG8Hh< zg{?4Bh{s$-QC0dBO_zT())|A)r`0ncJ|vKNO#)6ktPmDuOSfVk53>W$SUI)iXv}GD z=PQ_IQ{Kn$;)-dU3Pi&+{PbE#*cEUl!4$KJ_V$_bvAG*~7Vw~5q{xdzPp-n7@iJE>l}vCw&~t7CcSWKL1UV^2L)B?TEE=^7=wq0e8xH`J zq9&AdF;?D-A-bW!78J$b`4ZT4fWQe8%C~hXC9+56lO!WRMqKQr&Gh8ewbPru}RtP0tuMcpP2 za@&tE2rB!f;X9WR zCP-_HI1t!Foo5JoX3VUa)R~8qKqUa|MCKC@lnbJO`U<8%4-%=vFLuDWAeysx8t|G-X$^B+f;n-_c=L&YxDzg^tm z@jeO=XaS-msFDy^C;`TDLw_6sl5VhTHhhy~2x;*R+aWCXfPlX{@BP$`a1`n!1!sgB=qV&-?O3alHxaa@e`Vkzcm)aN-#~g{D%)^*X zNxAFYs{7s?+|$)|e~{}6llwShbIqvqzInG#0l#g_Jzkc(t#eGEXS!>dsXw~{KS#6Q z!Sd&u+XcI`LD^eIwfVjj1E<9mn@AFI0|-7t_U9#9P?qk_GuB4)K`|n;w_!4lXm!iP z+g{pb1C|WH5d7wR5QQ~j8>#6$X|y{O<1A^H&Vjo?6hQac@!quhrlV(l-ZSg(&Y+w{F(8? z(s>#TI}!Jl9Bi&gZa2B4#oG90(u%(+_d(xH`|SH!ClHg4PM9SV)ve9SOeE*k}0!(Nxm3nubqmcFy+uu*a75-1>Me)U(`A)RL~K z9jf6BMbz=gV!zOhs|aKH{@-~4g#>oz#8u4q)%;KA;~Km3!8+ji>230GHruIV*XFnv zj=IkU4j&EjrOfeeMDLc#*M}f_^pd{$3l|)dLv3np9JwJ-zglsy!yqco>!q+Te>yl{ zU_$UdST4UZ-Hs5HK&f%{np$^*K3C28Z27QDahs0U8UOi)#`t=Z41d=6C?G=ru~XH3 z(NyMvoa3$`S+=XiR26l_{q~}^admC# z>+W6MhI$gyWXU6=pzQ;2^LB|S>MELA4*K4nbgmmj9)f;vV zGebIdoi1w1ef8#(U%mRY_M4b$rdA)yU-HYT+MoL1ixs-_=`4||`MsYWc+Zv?sB1-D z8&ni5an&#{x`wl=U$Q?-rKO)DX;thx9JO0*aWzdB)ZFmrUfvw%2GD)oyvKFbwqtqE zZKyqfi-f+YX>>W*>7{1V6SM9UCuf-ViBd5)$Y3P~}1M2_q z<&TRLOT-dzI^7+GMmw%f8l7zlt=q|VZQk!(`+cqkx0qrEyUAx~P1GZ0Jpaaz>0liU z_{l~_;V531XcwvhP3-OPHswTOzD^M^PA03iF&a-j%#$&R=%8-y?&0!5@%d#ER+2!R z&Gl?4d}nXyGa+fzb=9_cL0v~KL4_a4g=g3h>36t0=0_=KG7n5VvCU&xIQFHBd4we0 z!{CTp3v~os(B~F97MdlsF*mT9}V(cWsOYjn$4#|&RM8){Go7gMbtJrCkt>@j1Q$_@D zqSCWY9k_;F({gwlT9*6UH50y}j=Y}h{%g*+yR9I`Q^r1*zk1bG!Nu90W#dpZ5)W-? z^BbnH1et$LKQHU2TvPSH54!Gp6RTh+viQr#(GcR=plDjh&#XKSj*CyFQ`irGG09vh zlry#WwNEfj0#42)mzr*P_XKM@~53o_fIlbG=2aW~2Z+r01?KBjeqrZ{si1L7NgVKksoKAQo)+$LX4K?2kwJlMx4&7 zs#V-oJ8GIbYruhW$(7O5m6UdTkk1#=QvWI`VTwW9!BGGm+io*4ZO0urnN2X$Q7Dzk zPpVrnR^B7C?26nxkRbMHs46igI}BJXyzbcgu5UyOw|ifm`%cR_$#@(=k!r3mJ>C%^ zIE>PROxTsjZD3C9Ssvs|$iH?qNL)AnyTW;VLGBQ4fq~Ng5!|YjKKBr@Iult4AHs9P|H5$T z7u3AbQYMt7N^<8b3@||bQ1h6pSN^MpOZ`wBs;X|QMWE}$HC0y^BVLW5>%BGPh$UWY zLG-T(*JS9%_zIzu?N|pBExgFaJq4gQSzZ^d<4o+m5*;8GQhQ~r^V8a`j z=6QtGx|2z_=aCCK+sucM>3(4PjmR(HkAdfcEokpzdNpxC{-@hxr>m{)R_%1pSR`6N zNE`@dF>>%e2gdwMMobGWlIH4imKY^TF+ z%CUxQ5L$AeuSUZQcC-$7J4m;;19oI9za=}h5L7d*J3|f6OHe>meY=g3gP(W^15(e* z=k}o)>^91V~rk!W7ZZm$9m&VFiLfbLrtj^34eP(6|-}a#A=gSozWLQ zvKyI2G;`CH=52|Le8rxtY0$gPURz4mvDsvE(y6IGVmw>-|=C^`6U|Y9xf9DQ?h09PPAv@kFfP89Hp*c$V;MXd5jfwKa5pe!Rxp%tyBA728V$K%t!UG^jjR?%fNUj z!Uf6g55@a%RC0pDms5k!whP54$e~(Z+|ReeZ^Z6f0q9h+?z{M#OPP16RR7B}2LuQj ztR3QxAn94QBF)O1A`$UD?L;vXef@Dnm2Si4axx$C`S!4Ep3Us{JPt(9JU1ic&dJAp z!{4lB|0mx6Z+M3mAQmFHX_`eS5v-A`(RGtZo#6lm^%O+>2J-m&@!0+O$E|DbLB{1B zxU=rJn{i%!%f=o+dB$gtoVax-lHT{BKKp%__%kP{(1-uBO4Z2%ANerT*_Y@Ao_{Oixa^S_K0 z@OZ@cEc1@s4jp=K>-YV{A6Mi04|TwXb@`q66^FX{Gs~^AaZ%g-xI(L*YsG^6e#7&$ zlJ~q}?&I}R0sH0O#cz_k3(C(tyjb+bNZWwU2=yl*@+e-Kljmim>pZI)$KLl{z} z75371N&3GF;yfyLE3bN4zw~t*#MH1EX#r`z%yQ#25oJEsgQb?w&@|0b;J=@b^LyJ(x9@5t z_1U2Mmr?w`uQ^SOs;Qe|K5p(jC-;RBcx%{tKTxadIkGXLFTFNj&mJh=HXgga*se5G z``(um=4LWlQ#TwscfUWR11#^&kB2WWnril~`$>D7PeWe>t|JsW4*4&8eR=a7E3E~` zfR~!X54IuK1pCM2_e6j!R#}$s%dvU3&mv=(QMz55)bX-VZ5Lmsk?%dBz;`{+>I?`{@;=zwd+9V*`F-4G zdzxE=Zvz8G<+>a5Mc^>Q`QMumWn%)_0H#=V-5A}1F6EiX`&{TYoE z&voj08f12+;WLRe2 zwxGX_oGVrbevcelV2mjrg@&{Go!fR=)oyoM+5GW->DN7kXUzxfQ=KdR4F9s+Hc|gf zZ*MCesCb<70h%++&)3b*4O1XR-wG#>4gWJVJX{36ZVnab*K`RVi-!wXkgo}t+7EXD z3uEDTwcq>cBxdg7jO50;^cj#6!?MPcmBMT0B%sw-%1o2Q*}grvag40{9e$pmlIdZJKMmpAHwbGd%IJ1xZq{g*QFiQLR!hfN+nfP;ZdkL?yk!-9UH0Ih731lKk()L`HRYY~}c%XHf69u4*mfoH=rWW$7(+ zGBIxlR*%5L()Ug{Nm&8a+Q$tq39%1ibZw+&78`*9;>&XV`!ao75v(j=i#|uaqAj7y zF*U{~KB+GAHVpRhC?hm__5(KinHjYZ#(vj7%?ZrjM(Ifj5>h^TcvyY=Njg=MCtwLK zE=r91#Gg;YF-c!KkL4z7ZSl0w$Ugxh%_9s`>)!7ZR|HrhpSu~ij$J}%0tZ1U2Fkx6 z-N3tzH1P#rJInMqNG*iOK;hxr`zdnJoRM^l|5E;rrCE9YaPm#70sSMl!G5_6>x@=g{dJ>-XW{_F{vSkhF!unq2JBmT zEi|{4hGQ)sFQ?@t9BSvo#D09U#?-B+p}@HYly~#tvM~nh$Xs9B{&vdC2orS|0;!wG zI8nhg@+1tol-<%F?%ik|yVj7&GIceXaD^i^4ZZut<}S`AkMmZTiSqt zp-3+=1f(9ip^FNVgwRC@y$G1lo6K&wAF6y?@MlX6BvuojuFY;xXyy;Apnf9=cB+tJ(I;3)!f^#a}q+IvZ~d z8CWJyQv^^J#=tsc!i~u*8!rVyH<8TB?oUs?#b_LaHwwG2!&fLXY$@2T)*q9&D_z_E zox(sFb&korRwUsFY~#3d#25(`E3uvPPth2LcMiuJlqsT50{2Z5LCR8W3GKTpyf=L? zWu=`$?mM0g3RW3sx`?A0ddYqI!;c4=p#ZzfxebkK`! zfuKcd=U0lMklmM@+c$MVHBy5FH5lJ@ssoDSjZ~v48Sw^nZRGFp=R4W6?Q3>hA(lgQ zs5==J)M_E(^|MjNXTrf-bcG`c?pw?Qn0agcSczJWWsfa>PIUi_z1SFw;;r9-9c6=c zWsd9GN52eDDK9yilhu_+f5R5z7^B)P_7TLgV58r2*t-H)ycJQz4)1|zE9Tuv#%o|o*S*f4d6z@H^t9*?*ci+lP9i1`$beW~u@~WK>L=sX~V5qvSXA4}ZM9_gRE!RLO>B^h_`PXjsFeNCK%ztlzJ^Db|1}*FO^lhMHVdQQI&I30p z+aj4n$Ks)FtD8CU0iQZMNWLA9oO(>3@k3RVXQVougQ|v>nKPIPSnZ%&AU{YHii)xY zL9fjnr90j$lwYgPE4l5KzHNj&XDM@~JfgY!{D~vCNR_vGTF3r8(;AFuF51x_1O){iEX53f zhAA0?Z*T3+X@nhMw<0WyxnTan0?eYcj621n3}FVS{O5Xa+~+rDcaO`9k`>-7_vt{a zM-r4xdHDk5V+SU6E*J`An;>C#f_OOtQTYuzs+r=#OFWioOv@ZBQ_NhJ-_{ViSIov+&3;Jv%RbsW zpX=gSf5HO2OgFRS4Y4!)!R2ee7~3Msw&_K|ZNieW(P?@>v{hO;|nRXS8q4iD(lpuLY8zk{hQ<;FR0sKQirRn_Vt zPqzVr*l)HpvGbifd0P_Vnc`$5OiEP~9?}Cf<6HX_xBRhC|4};Jq{c6rX5aSxk%cZi zD{f<;`eMOjt6bYF>T^lgmw*NmVjFate!M-F$ohTuqj3q^6Le%Sp_cS4tK4U>>F3ur z9R+Jd{q$WvFZ|UfvBVReHHMF>22~(wuqT_2Kya}Sn|miI^WmqFln1*acb9fnte*NL zsxJZtYG%hND4Db%q8FKfJzIJ!fBTA+KYIxzeKMD6KoC*ZVZFfE-=>{!`R`c*HlyM$1Jz|Kj2>R^ofN$e<>I`FP42;-D~c#W3Ne`521$J|dQ|MCnn)n3U}oyb zi+01JtZ&Rp>X{Rr3colZB};-^9JE`x0!k-IKzm~(bbkv|e-L5)QAu;&}vF{%&F*$VkuZy=9FwOYt zLGz@+GfP1NV=u36f%yyX_x!k-_N;4_=diG%e0T3Dgzk`tDxSF+Pg%sV*& zrwaS7CVGsYWi;vTs^H8K$Gq8R8C*xzoZd@~-}hVLS3!mfD#pf4pcbZA}ppsQl8 zt&a*M-G5idr98yK)Z-y^4kcS0@L@$}g^@WBX~?r@AtrXyn`8fH2_%d`x3#!8sMCYfwdp+mVJK!Aqz9147uYvgT=ST+7Yf!7z3jM0IHPNUJkaNbKFkzQK!^9yFCL>w7+N)b~nViJ3x8SPP9xMn75q|sY{(mU|f6> zwHseUp61O}oDQF9Cq>g!5UI9%Qyvoc9uFd>%kSr9S4{PBqiDUnG`WFC4VEkRB|Yg_y3>oaidA)^=2W%R_8@S zm}^#7pT*_!TCJD@TQO@p!G7ju0T*uQUaCbjqr8oP6e6-!|ILPti8w?Y_kDeeUTMxz zlsxlEsw9{?vPwIH1|Kv6XVSl5%kdc6Maae1n*4OZ!4*!cwh2z!^x%Ekj?q8mp)(gS zaBkr)n+D=B6UyAz=?Ti%^o@eylwri-A9SWc-3;(pQ*(?X)%mAElA2QhoveM~=gwb& z;KWCc>&zl54=TN(iH@K7Ul;S3#rz(Zn4s_cX4P}j(!Fqr<3Kj=Tm-DGoUx53a#rhZ zy18d9*1)V;4;21Yzdu~1g`dDULyLr)!^QXpyc&F5Aj9R4`i+(_SVJPN-cLTZ*-&1I zpkk%)VdnjWtjk8+F|Uy#XWqi2o4 zd=}nDpY8w*FpR7Q(;ny>=poG|aHqw^h@a*e&9pFW%Zi#rX}G%cDA-p1^YHfTs9F(F zXk;p3=C(PLgfEwMvmT26LZnbKYYE7CsftjpL_akA;RmOueXH-N*0h$%rmm){T>R(Uy)ZELsW&u6=wBtM)vf%vKZddrZyNE-$SC7;#u7bzTA09_PvD&X~ zZF}%6cXd3@ctrD3f-ee@25nT&^Eg{!-@~1xu=jEq>S&A;Tms~G6vcWDMMz_dvV{w! z{n=t|iV=+S0F5v+hXg7S?zl z0fU-vWFZ-Kyl0V&5O>o@H%uhN;qB8~>@N}yT`AY6GKqZ)Cpd70%_kNHYcF~7Iu^qr z`d%?}x+!^B7w)OGt>H|DFHMu{3`F5vR_U7~N#==`7l$GmXkJh4B9u5Rn^uHcZocLx zh2g9U&I|Rcv{@Uefc1l6a3S|DHz^3hWPZ-mOrrPNJGVqUCw-0U=QyBrL)`4y2eUGt3t(g-5WB8 z2llnhV&W=rX{t(g$Wp%vETG5(b~qt`)BX|!uTX#GvrR&|M^$zzX_VWTo1Dv8Fjd;7 z^LSvCtSHg4%P&HswfW`BCdegOSRPRP`Xh%g6$v z0fb~>oh5K~OM&n$qoLNMUL_4Ja&SifI%PJbUNcjsYq_*$EGL7#~=gfgnTb#DSJVr*3wd zARE}c3(5$ArIjW^S($QPwXgrtdF`b!ipTbP_ua7}rT${}a;0|ZrtaZ-83GS%gK*8#gmR^5Stb4nv|?0p^^M$#ko* z*r!7PC60*e`d+)wf^s z&FMTnFn%4uEY|^{F`B;^$8{R&T%r59JZp1W*nBNV_C#*}7~Ln>79`tVRXZj(#~Kp9 z3TF4X;_Ah`QseUeUwSe+0eaM8_bkZO`>)TMC36?X*#Lsbc0Jq>yPPg7E4%RzSC zwbsE4FqJ=BLjJYO>HE~lP=#^olkmpSQ>*{yBI8GU!rJwtx1*-Q;1Lz&(bKwtsDa;# F_&<)N$t(Z> literal 0 HcmV?d00001 diff --git a/docs/source/index.rst b/docs/source/index.rst index c2dc7ab57..4424279ce 100644 --- a/docs/source/index.rst +++ b/docs/source/index.rst @@ -11,7 +11,7 @@ Introduction RecBole is a unified, comprehensive and efficient framework developed based on PyTorch. It aims to help the researchers to reproduce and develop recommendation models. -In the lastest release, our library includes 89 recommendation algorithms `[Model List]`_, covering four major categories: +In the lastest release, our library includes 90 recommendation algorithms `[Model List]`_, covering four major categories: - General Recommendation - Sequential Recommendation diff --git a/docs/source/user_guide/model/sequential/fearec.rst b/docs/source/user_guide/model/sequential/fearec.rst new file mode 100644 index 000000000..40219d2e4 --- /dev/null +++ b/docs/source/user_guide/model/sequential/fearec.rst @@ -0,0 +1,88 @@ +FEARec +=========== + +Introduction +--------------------- + +`[paper] `_ + +**Title:** FEARec: Frequency Enhanced Hybrid Attention Network for Sequential Recommendation + +**Authors:** Xinyu Du, Huanhuan Yuan, Pengpeng Zhao, Jianfeng Qu, Fuzhen Zhuang, Guanfeng Liu, Victor S. Sheng + +**Abstract:** The self-attention mechanism, which equips with a strong capability of modeling long-range dependencies, is one of the extensively used techniques in the sequential recommendation field. However, many recent studies represent that current self-attention based models are low-pass filters and are inadequate to capture high-frequency information. Furthermore, since the items in the user behaviors are intertwined with each other, these models are incomplete to distinguish the inherent periodicity obscured in the time domain. In this work, we shift the perspective to the frequency domain, and propose a novel Frequency Enhanced Hybrid Attention Network for Sequential Recommendation, namely FEARec. In this model, we firstly improve the original time domain self-attention in the frequency domain with a ramp structure to make both low-frequency and high-frequency information could be explicitly learned in our approach. Moreover, we additionally design a similar attention mechanism via auto-correlation in the frequency domain to capture the periodic characteristics and fuse the time and frequency level attention in a union model. Finally, both contrastive learning and frequency regularization are utilized to ensure that multiple views are aligned in both the time domain and frequency domain. Extensive experiments conducted on four widely used benchmark + +.. image:: ../../../asset/fearec.png + :width: 500 + :align: center + +Running with RecBole +------------------------- + +**Model Hyper-Parameters:** + +- ``hidden_size (int)`` : The number of features in the hidden state. It is also the initial embedding size of items. Defaults to ``64``. +- ``inner_size (int)`` : The inner hidden size in feed-forward layer. Defaults to ``256``. +- ``n_layers (int)`` : The number of transformer layers in transformer encoder. Defaults to ``2``. +- ``n_heads (int)`` : The number of attention heads for multi-head attention layer. Defaults to ``2``. +- ``hidden_dropout_prob (float)`` : The probability of an element to be zeroed. Defaults to ``0.5``. +- ``attn_dropout_prob (float)`` : The probability of an attention score to be zeroed. Defaults to ``0.5``. +- ``hidden_act (str)`` : The activation function in feed-forward layer. Defaults to ``'gelu'``. Range in ``['gelu', 'relu', 'swish', 'tanh', 'sigmoid']``. +- ``layer_norm_eps (float)`` : A value added to the denominator for numerical stability. Defaults to ``1e-12``. +- ``initializer_range (float)`` : The standard deviation for normal initialization. Defaults to ``0.02``. +- ``loss_type (str)`` : The type of loss function. If it is set to ``'CE'``, the training task is regarded as a multi-classification task and the target item is the ground truth. In this way, negative sampling is not needed. If it is set to ``'BPR'``, the training task will be optimized in the pair-wise way, which maximizes the difference between the positive item and the negative one. In this way, negative sampling is necessary, such as setting ``--train_neg_sample_args="{'distribution': 'uniform', 'sample_num': 1}"``. Defaults to ``'CE'``. Range in ``['BPR', 'CE']``. +- ``lmd (int) `` : The weight of unsupervised normalized CE loss.Defaults to ``0.1``. +- ``lmd_sem (int) `` : The weight of supervised normalized CE loss.Defaults to ``0.1``. +- ``global_ratio (float)`` : The ratio of frequency components. Defaults to ``1``. +- ``dual_domain (bool)`` : Frequency domain processing or not. Defaults to ``False``. +- ``std (bool)`` : Use the specific time index or not. Defaults to ``False``. +- ``fredom (bool)`` : Regularization in the frequency domain or not. Defaults to ``False``. +- ``spatial_ratio (float)`` : The ratio of the spatial domain and frequency domain. Defaults to ``0``. +- ``topk_factor (int)`` : To aggregate time delayed sequences with high autocorrelation. Defaults to ``1``. +- ``fredom_type (str)`` : The type of loss in different scenarios. Defaults to ``None``. Range in ``['un', 'su', 'us', 'us_x']``. + + +**A Running Example:** + +Write the following code to a python file, such as `run.py` + +.. code:: python + + from recbole.quick_start import run_recbole + + run_recbole(model='FEARec', dataset='ml-100k') + +And then: + +.. code:: bash + + python run.py + +Tuning Hyper Parameters +------------------------- + +If you want to use ``HyperTuning`` to tune hyper parameters of this model, you can copy the following settings and name it as ``hyper.test``. + +.. code:: bash + + global_ratio choice [0.6,0.8,1.0] + topk_factor choice [1,3,5] + spatial_ratio choice [0.1,0.9] + +Note that we just provide these hyper parameter ranges for reference only, and we can not guarantee that they are the optimal range of this model. + +Then, with the source code of RecBole (you can download it from GitHub), you can run the ``run_hyper.py`` to tuning: + +.. code:: bash + + python run_hyper.py --model=[model_name] --dataset=[dataset_name] --config_files=[config_files_path] --params_file=hyper.test + +For more details about Parameter Tuning, refer to :doc:`../../../user_guide/usage/parameter_tuning`. + + +If you want to change parameters, dataset or evaluation settings, take a look at + +- :doc:`../../../user_guide/config_settings` +- :doc:`../../../user_guide/data_intro` +- :doc:`../../../user_guide/train_eval_intro` +- :doc:`../../../user_guide/usage` \ No newline at end of file diff --git a/docs/source/user_guide/model_intro.rst b/docs/source/user_guide/model_intro.rst index c4b31315e..54fb813b7 100644 --- a/docs/source/user_guide/model_intro.rst +++ b/docs/source/user_guide/model_intro.rst @@ -1,6 +1,6 @@ Model Introduction ===================== -We implement 88 recommendation models covering general recommendation, sequential recommendation, +We implement 90 recommendation models covering general recommendation, sequential recommendation, context-aware recommendation and knowledge-based recommendation. A brief introduction to these models are as follows: diff --git a/recbole/model/sequential_recommender/__init__.py b/recbole/model/sequential_recommender/__init__.py index ceba1dcac..81788524d 100644 --- a/recbole/model/sequential_recommender/__init__.py +++ b/recbole/model/sequential_recommender/__init__.py @@ -26,3 +26,4 @@ from recbole.model.sequential_recommender.srgnn import SRGNN from recbole.model.sequential_recommender.stamp import STAMP from recbole.model.sequential_recommender.transrec import TransRec +from recbole.model.sequential_recommender.fearec import FEARec diff --git a/recbole/model/sequential_recommender/fearec.py b/recbole/model/sequential_recommender/fearec.py new file mode 100644 index 000000000..cc3297de5 --- /dev/null +++ b/recbole/model/sequential_recommender/fearec.py @@ -0,0 +1,783 @@ +import pickle +import random + +import numpy as np +import torch +from torch import nn +import torch.nn.functional as F +import math + +import torch.nn.functional as fn + + +from recbole.model.abstract_recommender import SequentialRecommender +# from recbole.model.layers import FEAEncoder #考虑把encoder放进来 +from recbole.model.loss import BPRLoss +from recbole.data.interaction import Interaction + + +class FEARec(SequentialRecommender): + def __init__(self, config, dataset): + super(FEARec, self).__init__(config, dataset) + + # load parameters info + self.dataset = dataset + self.config = config + self.n_layers = config['n_layers'] + self.n_heads = config['n_heads'] + self.hidden_size = config['hidden_size'] # same as embedding_size + self.inner_size = config['inner_size'] # the dimensionality in feed-forward layer + self.hidden_dropout_prob = config['hidden_dropout_prob'] + self.attn_dropout_prob = config['attn_dropout_prob'] + self.hidden_act = config['hidden_act'] + self.layer_norm_eps = config['layer_norm_eps'] + + self.lmd = config['lmd'] + self.lmd_sem = config['lmd_sem'] + + self.initializer_range = config['initializer_range'] + self.loss_type = config['loss_type'] + self.same_item_index = self.get_same_item_index(dataset) + + # define layers and loss + self.item_embedding = nn.Embedding(self.n_items, self.hidden_size, padding_idx=0) + self.position_embedding = nn.Embedding(self.max_seq_length, self.hidden_size) + self.item_encoder = FEAEncoder( + n_layers=self.n_layers, + n_heads=self.n_heads, + hidden_size=self.hidden_size, + inner_size=self.inner_size, + hidden_dropout_prob=self.hidden_dropout_prob, + attn_dropout_prob=self.attn_dropout_prob, + hidden_act=self.hidden_act, + layer_norm_eps=self.layer_norm_eps, + config=self.config, + ) + + self.LayerNorm = nn.LayerNorm(self.hidden_size, eps=self.layer_norm_eps) + self.dropout = nn.Dropout(self.hidden_dropout_prob) + + if self.loss_type == 'BPR': + self.loss_fct = BPRLoss() + elif self.loss_type == 'CE': + self.loss_fct = nn.CrossEntropyLoss() + else: + raise NotImplementedError("Make sure 'loss_type' in ['BPR', 'CE']!") + + self.ssl = config['contrast'] + self.tau = config['tau'] + self.sim = config['sim'] + self.fredom = config['fredom'] + self.fredom_type = config['fredom_type'] + self.batch_size = config['train_batch_size'] + self.mask_default = self.mask_correlated_samples(batch_size=self.batch_size) + self.aug_nce_fct = nn.CrossEntropyLoss() + self.sem_aug_nce_fct = nn.CrossEntropyLoss() + + # parameters initialization + self.apply(self._init_weights) + + + def get_same_item_index(self,dataset): + same_target_index = {} + aug_path = dataset.config['data_path'] + '/semantic_augmentationindex.pkl' + import os + if os.path.exists(aug_path): + with open(aug_path, 'rb') as file: + same_target_index = pickle.load(file) + file.close() + else: + target_item = dataset.inter_feat[self.ITEM_ID].numpy() + + count = 0 + # print(dataset.inter_feat[self.ITEM_ID][2911]) + # print("is 2895 fou") + # print(dataset.inter_feat[self.ITEM_ID][9867]) + for index, item_id in enumerate(target_item): + print("{:1}".format(count / len(target_item) * 100) + "%") + all_index_same_id = np.where(target_item == item_id)[0] + same_target_index[item_id] = all_index_same_id + count += 1 + # print(item_id,"index is",same_target_index[item_id]) + + with open(aug_path, 'wb') as file: + pickle.dump(same_target_index,file) + file.close() + + return same_target_index + + def _init_weights(self, module): + """ Initialize the weights """ + if isinstance(module, (nn.Linear, nn.Embedding)): + # Slightly different from the TF version which uses truncated_normal for initialization + # cf https://github.com/pytorch/pytorch/pull/5617 + module.weight.data.normal_(mean=0.0, std=self.initializer_range) + # module.weight.data = self.truncated_normal_(tensor=module.weight.data, mean=0, std=self.initializer_range) + elif isinstance(module, nn.LayerNorm): + module.bias.data.zero_() + module.weight.data.fill_(1.0) + if isinstance(module, nn.Linear) and module.bias is not None: + module.bias.data.zero_() + + def truncated_normal_(self, tensor, mean=0, std=0.09): + with torch.no_grad(): + size = tensor.shape + tmp = tensor.new_empty(size + (4,)).normal_() + valid = (tmp < 2) & (tmp > -2) + ind = valid.max(-1, keepdim=True)[1] + tensor.data.copy_(tmp.gather(-1, ind).squeeze(-1)) + tensor.data.mul_(std).add_(mean) + return tensor + + def get_attention_mask(self, item_seq): + """Generate left-to-right uni-directional attention mask for multi-head attention.""" + """ 最终,这个方法返回一个用于多头注意力的 attention mask,确保了在每个位置上, + 只能注意到该位置之前的位置。这有助于模型按照时间顺序逐步生成输出,过滤掉未来的信息 """ + attention_mask = (item_seq > 0).long() + extended_attention_mask = attention_mask.unsqueeze(1).unsqueeze(2) # torch.int64 + # mask for left-to-right unidirectional + max_len = attention_mask.size(-1) + attn_shape = (1, max_len, max_len) + subsequent_mask = torch.triu(torch.ones(attn_shape), diagonal=1) # torch.uint8 + subsequent_mask = (subsequent_mask == 0).unsqueeze(1) + subsequent_mask = subsequent_mask.long().to(item_seq.device) + + extended_attention_mask = extended_attention_mask * subsequent_mask + extended_attention_mask = extended_attention_mask.to(dtype=next(self.parameters()).dtype) # fp16 compatibility + extended_attention_mask = (1.0 - extended_attention_mask) * -10000.0 + return extended_attention_mask + + def get_bi_attention_mask(self, item_seq): + """Generate bidirectional attention mask for multi-head attention.""" + attention_mask = (item_seq > 0).long() + extended_attention_mask = attention_mask.unsqueeze(1).unsqueeze(2) # torch.int64 + # bidirectional mask + extended_attention_mask = extended_attention_mask.to(dtype=next(self.parameters()).dtype) # fp16 compatibility + extended_attention_mask = (1.0 - extended_attention_mask) * -10000.0 + return extended_attention_mask + + def forward(self, item_seq, item_seq_len): + position_ids = torch.arange(item_seq.size(1), dtype=torch.long, device=item_seq.device) + position_ids = position_ids.unsqueeze(0).expand_as(item_seq) + position_embedding = self.position_embedding(position_ids) + + + item_emb = self.item_embedding(item_seq) + input_emb = item_emb + position_embedding + input_emb = self.LayerNorm(input_emb) + input_emb = self.dropout(input_emb) + + extended_attention_mask = self.get_attention_mask(item_seq) + # extended_attention_mask = self.get_bi_attention_mask(item_seq) + + + + trm_output = self.item_encoder(input_emb, extended_attention_mask, output_all_encoded_layers=True) + output = trm_output[-1] + output = self.gather_indexes(output, item_seq_len - 1) + + return output # [B H] + + @staticmethod + def alignment(x, y): + x, y = F.normalize(x, dim=-1), F.normalize(y, dim=-1) + return (x - y).norm(p=2, dim=1).pow(2).mean() + + @staticmethod + def uniformity(x): + x = F.normalize(x, dim=-1) + x = abs(x) + return torch.pdist(x, p=2).pow(2).mul(-2).exp().mean().log() + + + + def calculate_loss(self, interaction): + + same_item_index = self.same_item_index + sem_pos_lengths = [] + sem_pos_seqs = [] + dataset = self.dataset + target_items = interaction[self.ITEM_ID] + for i, item_id in enumerate(target_items): + item_id = item_id.item() + targets_index = same_item_index[item_id] + lens = len(targets_index) + if lens == 0: + print("error") + while True: + sample_index = random.choice(targets_index) + cur_item_list = interaction[self.ITEM_SEQ][i].to('cpu') + sample_item_list = dataset.inter_feat['item_id_list'][sample_index] + are_equal = torch.equal(cur_item_list,sample_item_list) + sample_item_length = dataset.inter_feat['item_length'][sample_index] + if not are_equal or lens == 1: + sem_pos_lengths.append(sample_item_length) + sem_pos_seqs.append(sample_item_list) + break; + + sem_pos_lengths = torch.stack(sem_pos_lengths).to(self.device) + sem_pos_seqs = torch.stack(sem_pos_seqs).to(self.device) + + interaction.update(Interaction({'sem_aug': sem_pos_seqs, 'sem_aug_lengths': sem_pos_lengths})) + + + item_seq = interaction[self.ITEM_SEQ] + item_seq_len = interaction[self.ITEM_SEQ_LEN] + seq_output = self.forward(item_seq, item_seq_len) + pos_items = interaction[self.POS_ITEM_ID] + if self.loss_type == 'BPR': + neg_items = interaction[self.NEG_ITEM_ID] + pos_items_emb = self.item_embedding(pos_items) + neg_items_emb = self.item_embedding(neg_items) + pos_score = torch.sum(seq_output * pos_items_emb, dim=-1) # [B] + neg_score = torch.sum(seq_output * neg_items_emb, dim=-1) # [B] + loss = self.loss_fct(pos_score, neg_score) + else: # self.loss_type = 'CE' + test_item_emb = self.item_embedding.weight + logits = torch.matmul(seq_output, test_item_emb.transpose(0, 1)) + loss = self.loss_fct(logits, pos_items) + + + # Unsupervised NCE + if self.ssl in ['us', 'un']: + aug_seq_output = self.forward(item_seq, item_seq_len) + nce_logits, nce_labels = self.info_nce(seq_output, aug_seq_output, temp=self.tau, + batch_size=item_seq_len.shape[0], sim=self.sim) + + # nce_logits = torch.mm(seq_output, aug_seq_output.T) + # nce_labels = torch.tensor(list(range(nce_logits.shape[0])), dtype=torch.long, device=item_seq.device) + + # if self.ssl == 'un': + # with torch.no_grad(): + # alignment, uniformity = self.decompose(seq_output, aug_seq_output, seq_output, + # batch_size=item_seq_len.shape[0]) + + loss += self.lmd * self.aug_nce_fct(nce_logits, nce_labels) + + # Supervised NCE + if self.ssl in ['us', 'su']: + sem_aug, sem_aug_lengths = interaction['sem_aug'], interaction['sem_aug_lengths'] + sem_aug_seq_output = self.forward(sem_aug, sem_aug_lengths) + + sem_nce_logits, sem_nce_labels = self.info_nce(seq_output, sem_aug_seq_output, temp=self.tau, + batch_size=item_seq_len.shape[0], sim=self.sim) + + # sem_nce_logits = torch.mm(seq_output, sem_aug_seq_output.T) / self.tau + # sem_nce_labels = torch.tensor(list(range(sem_nce_logits.shape[0])), dtype=torch.long, device=item_seq.device) + + # with torch.no_grad(): + # alignment, uniformity = self.decompose(seq_output, sem_aug_seq_output, seq_output, + # batch_size=item_seq_len.shape[0]) + + loss += self.lmd_sem * self.aug_nce_fct(sem_nce_logits, sem_nce_labels) + + if self.ssl == 'us_x': + aug_seq_output = self.forward(item_seq, item_seq_len) + sem_aug, sem_aug_lengths = interaction['sem_aug'], interaction['sem_aug_lengths'] + + sem_aug_seq_output = self.forward(sem_aug, sem_aug_lengths) + sem_nce_logits, sem_nce_labels = self.info_nce(aug_seq_output, sem_aug_seq_output, temp=self.tau, + batch_size=item_seq_len.shape[0], sim=self.sim) + + loss += self.lmd_sem * self.aug_nce_fct(sem_nce_logits, sem_nce_labels) + # with torch.no_grad(): + # alignment, uniformity = self.decompose(aug_seq_output, sem_aug_seq_output, seq_output, + # batch_size=item_seq_len.shape[0]) + + # frequency domain loss + if self.fredom: + seq_output_f = torch.fft.rfft(seq_output, dim=1, norm='ortho') + aug_seq_output_f = torch.fft.rfft(aug_seq_output, dim=1, norm='ortho') + sem_aug_seq_output_f = torch.fft.rfft(sem_aug_seq_output, dim=1, norm='ortho') + if self.fredom_type in ['us', 'un']: + loss += 0.1 * abs(seq_output_f - aug_seq_output_f).flatten().mean() + if self.fredom_type in ['us', 'su']: + loss += 0.1 * abs(seq_output_f - sem_aug_seq_output_f).flatten().mean() + if self.fredom_type == 'us_x': + loss += 0.1 * abs(aug_seq_output_f - sem_aug_seq_output_f).flatten().mean() + + return loss + + def mask_correlated_samples(self, batch_size): + N = 2 * batch_size + mask = torch.ones((N, N), dtype=bool) + mask = mask.fill_diagonal_(0) + for i in range(batch_size): + mask[i, batch_size + i] = 0 + mask[batch_size + i, i] = 0 + return mask + + def info_nce(self, z_i, z_j, temp, batch_size, sim='dot'): + """ + We do not sample negative examples explicitly. + Instead, given a positive pair, similar to (Chen et al., 2017), we treat the other 2(N − 1) augmented examples within a minibatch as negative examples. + """ + N = 2 * batch_size + + z = torch.cat((z_i, z_j), dim=0) + + if sim == 'cos': + sim = nn.functional.cosine_similarity(z.unsqueeze(1), z.unsqueeze(0), dim=2) / temp + elif sim == 'dot': + sim = torch.mm(z, z.T) / temp + + sim_i_j = torch.diag(sim, batch_size) + sim_j_i = torch.diag(sim, -batch_size) + + positive_samples = torch.cat((sim_i_j, sim_j_i), dim=0).reshape(N, 1) + if batch_size != self.batch_size: + mask = self.mask_correlated_samples(batch_size) + else: + mask = self.mask_default + negative_samples = sim[mask].reshape(N, -1) + + labels = torch.zeros(N).to(positive_samples.device).long() + logits = torch.cat((positive_samples, negative_samples), dim=1) + return logits, labels + + def decompose(self, z_i, z_j, origin_z, batch_size): + """ + We do not sample negative examples explicitly. + Instead, given a positive pair, similar to (Chen et al., 2017), we treat the other 2(N − 1) augmented examples within a minibatch as negative examples. + """ + N = 2 * batch_size + + z = torch.cat((z_i, z_j), dim=0) + + # pairwise l2 distace + sim = torch.cdist(z, z, p=2) + + sim_i_j = torch.diag(sim, batch_size) + sim_j_i = torch.diag(sim, -batch_size) + + positive_samples = torch.cat((sim_i_j, sim_j_i), dim=0).reshape(N, 1) + alignment = positive_samples.mean() + + # pairwise l2 distace + sim = torch.cdist(origin_z, origin_z, p=2) + mask = torch.ones((batch_size, batch_size), dtype=bool) + mask = mask.fill_diagonal_(0) + negative_samples = sim[mask].reshape(batch_size, -1) + uniformity = torch.log(torch.exp(-2 * negative_samples).mean()) + + return alignment, uniformity + + def predict(self, interaction): + item_seq = interaction[self.ITEM_SEQ] + item_seq_len = interaction[self.ITEM_SEQ_LEN] + test_item = interaction[self.ITEM_ID] + seq_output = self.forward(item_seq, item_seq_len) + test_item_emb = self.item_embedding(test_item) + scores = torch.mul(seq_output, test_item_emb).sum(dim=1) # [B] + return scores + + def full_sort_predict(self, interaction): + item_seq = interaction[self.ITEM_SEQ] + item_seq_len = interaction[self.ITEM_SEQ_LEN] + seq_output = self.forward(item_seq, item_seq_len) + test_items_emb = self.item_embedding.weight + scores = torch.matmul(seq_output, test_items_emb.transpose(0, 1)) # [B n_items] + return scores + + + +class HybridAttention(nn.Module): + """ + Hybrid Attention layer: combine time domain self-attention layer and frequency domain attention layer. + + Args: + input_tensor (torch.Tensor): the input of the multi-head Hybrid Attention layer + attention_mask (torch.Tensor): the attention mask for input tensor + + Returns: + hidden_states (torch.Tensor): the output of the multi-head Hybrid Attention layer + + """ + + def __init__(self, n_heads, hidden_size, hidden_dropout_prob, attn_dropout_prob, layer_norm_eps, i, config): + super(HybridAttention, self).__init__() + if hidden_size % n_heads != 0: + raise ValueError( + "The hidden size (%d) is not a multiple of the number of attention " + "heads (%d)" % (hidden_size, n_heads) + ) + + self.factor = config['topk_factor'] + self.scale = None + self.mask_flag = True + self.output_attention = False + self.dropout = nn.Dropout(0.1) + self.config = config + self.num_attention_heads = n_heads + self.attention_head_size = int(hidden_size / n_heads) + self.all_head_size = self.num_attention_heads * self.attention_head_size + self.query_layer = nn.Linear(hidden_size, self.all_head_size) + self.key_layer = nn.Linear(hidden_size, self.all_head_size) + self.value_layer = nn.Linear(hidden_size, self.all_head_size) + self.attn_dropout = nn.Dropout(attn_dropout_prob) + self.dense = nn.Linear(hidden_size, hidden_size) + self.LayerNorm = nn.LayerNorm(hidden_size, eps=layer_norm_eps) + self.out_dropout = nn.Dropout(hidden_dropout_prob) + self.filter_mixer = None + self.global_ratio = config['global_ratio'] + self.n_layers = config['n_layers'] + if self.global_ratio > (1 / self.n_layers): + print("{}>{}:{}".format(self.global_ratio, 1 / self.n_layers, self.global_ratio > (1 / self.n_layers))) + self.filter_mixer = 'G' + else: + print("{}>{}:{}".format(self.global_ratio, 1 / self.n_layers, self.global_ratio > (1 / self.n_layers))) + self.filter_mixer = 'L' + self.max_item_list_length = config['MAX_ITEM_LIST_LENGTH'] + self.dual_domain = config['dual_domain'] + self.slide_step = ((self.max_item_list_length // 2 + 1) * (1 - self.global_ratio)) // (self.n_layers - 1) + self.local_ratio = 1 / self.n_layers + self.filter_size = self.local_ratio * (self.max_item_list_length // 2 + 1) + + if self.filter_mixer == 'G': + self.w = self.global_ratio + self.s = self.slide_step + + if self.filter_mixer == 'L': + self.w = self.local_ratio + self.s = self.filter_size + + self.left = int(((self.max_item_list_length // 2 + 1) * (1 - self.w)) - (i * self.s)) + self.right = int((self.max_item_list_length // 2 + 1) - i * self.s) + + # random: + # self.q_index = get_frequency_modes(50, 2, 'random') + # self.k_index = get_frequency_modes(50, 2, 'random') + # self.v_index = get_frequency_modes(50, 2, 'random') + + self.q_index = list(range(self.left, self.right)) + self.k_index = list(range(self.left, self.right)) + self.v_index = list(range(self.left, self.right)) + # if sample in time domain + self.std = config['std'] + if self.std: + self.time_q_index = self.q_index + self.time_k_index = self.k_index + self.time_v_index = self.v_index + else: + self.time_q_index = list(range(self.max_item_list_length // 2 + 1)) + self.time_k_index = list(range(self.max_item_list_length // 2 + 1)) + self.time_v_index = list(range(self.max_item_list_length // 2 + 1)) + + print('modes_q={}, index_q={}'.format(len(self.q_index), self.q_index)) + print('modes_k={}, index_k={}'.format(len(self.k_index), self.k_index)) + print('modes_v={}, index_v={}'.format(len(self.v_index), self.v_index)) + + if self.config['dual_domain']: + self.spatial_ratio = self.config['spatial_ratio'] + + def transpose_for_scores(self, x): + new_x_shape = x.size()[:-1] + (self.num_attention_heads, self.attention_head_size) + x = x.view(*new_x_shape) # [256, 50, 2, 32] + # return x.permute(0, 2, 1, 3) # [256, 2, 50, 32] + return x + + def time_delay_agg_training(self, values, corr): + """ + SpeedUp version of Autocorrelation (a batch-normalization style design) + This is for the training phase. + """ + head = values.shape[1] + channel = values.shape[2] + length = values.shape[3] + # find top k + top_k = int(self.factor * math.log(length)) + mean_value = torch.mean(torch.mean(corr, dim=1), dim=1) + index = torch.topk(torch.mean(mean_value, dim=0), top_k, dim=-1)[1] + weights = torch.stack([mean_value[:, index[i]] for i in range(top_k)], dim=-1) + # update corr + tmp_corr = torch.softmax(weights, dim=-1) + # aggregation + tmp_values = values + delays_agg = torch.zeros_like(values).float() + for i in range(top_k): + pattern = torch.roll(tmp_values, -int(index[i]), -1) + delays_agg = delays_agg + pattern * \ + (tmp_corr[:, i].unsqueeze(1).unsqueeze(1).unsqueeze(1).repeat(1, head, channel, length)) + return delays_agg + + def time_delay_agg_inference(self, values, corr): + """ + SpeedUp version of Autocorrelation (a batch-normalization style design) + This is for the inference phase. + """ + batch = values.shape[0] + head = values.shape[1] + channel = values.shape[2] + length = values.shape[3] + # index init + init_index = torch.arange(length).unsqueeze(0).unsqueeze(0).unsqueeze(0) \ + .repeat(batch, head, channel, 1).to(values.device) + # find top k + top_k = int(self.factor * math.log(length)) + mean_value = torch.mean(torch.mean(corr, dim=1), dim=1) + weights, delay = torch.topk(mean_value, top_k, dim=-1) + # update corr + tmp_corr = torch.softmax(weights, dim=-1) + # aggregation + tmp_values = values.repeat(1, 1, 1, 2) + delays_agg = torch.zeros_like(values).float() + for i in range(top_k): + tmp_delay = init_index + delay[:, i].unsqueeze(1).unsqueeze(1).unsqueeze(1).repeat(1, head, channel, length) + pattern = torch.gather(tmp_values, dim=-1, index=tmp_delay) + delays_agg = delays_agg + pattern * \ + (tmp_corr[:, i].unsqueeze(1).unsqueeze(1).unsqueeze(1).repeat(1, head, channel, length)) + return delays_agg + + def forward(self, input_tensor, attention_mask): + mixed_query_layer = self.query_layer(input_tensor) + mixed_key_layer = self.key_layer(input_tensor) + mixed_value_layer = self.value_layer(input_tensor) + + + #trans挺快,query + queries = self.transpose_for_scores(mixed_query_layer) + keys = self.transpose_for_scores(mixed_key_layer) + values = self.transpose_for_scores(mixed_value_layer) + + #这段代码是注意力机制中的一部分,涉及到对查询(queries)和键(keys)进行频域变换(FFT),以应用频域注意力 + # B, H, L, E = query_layer.shape + # AutoFormer + B, L, H, E = queries.shape + # print("qqqq", queries.shape) [256,50,2,32,] + _, S, _, D = values.shape + if L > S: + zeros = torch.zeros_like(queries[:, :(L - S), :]).float() + values = torch.cat([values, zeros], dim=1) + keys = torch.cat([keys, zeros], dim=1) + else: + values = values[:, :L, :, :] + keys = keys[:, :L, :, :] + + # period-based dependencies + q_fft = torch.fft.rfft(queries.permute(0, 2, 3, 1).contiguous(), dim=-1) + k_fft = torch.fft.rfft(keys.permute(0, 2, 3, 1).contiguous(), dim=-1) + + + # 装到采样的空盒子里 + q_fft_box = torch.zeros(B, H, E, len(self.q_index), device=q_fft.device, dtype=torch.cfloat) + q_fft_box = q_fft[:, :, :, self.q_index] + + k_fft_box = torch.zeros(B, H, E, len(self.k_index), device=q_fft.device, dtype=torch.cfloat) + k_fft_box = k_fft[:, :, :, self.q_index] + res = q_fft_box * torch.conj(k_fft_box) + + if self.config['use_filter']: + # filter + weight = torch.view_as_complex(self.complex_weight) + res = res * weight + + box_res = torch.zeros(B, H, E, L // 2 + 1, device=q_fft.device, dtype=torch.cfloat) + box_res[:, :, :, self.q_index] = res + + corr = torch.fft.irfft(box_res, dim=-1) + + + # time delay agg + if self.training: + V = self.time_delay_agg_training(values.permute(0, 2, 3, 1).contiguous(), corr).permute(0, 3, 1, 2) + else: + V = self.time_delay_agg_inference(values.permute(0, 2, 3, 1).contiguous(), corr).permute(0, 3, 1, 2) + + + # print(V.shape) + new_context_layer_shape = V.size()[:-2] + (self.all_head_size,) + context_layer = V.view(*new_context_layer_shape) + + if self.dual_domain: + # 装到采样的空盒子里 + # q + q_fft_box = torch.zeros(B, H, E, len(self.time_q_index), device=q_fft.device, dtype=torch.cfloat) + q_fft_box = q_fft[:, :, :, self.time_q_index] + spatial_q = torch.zeros(B, H, E, L // 2 + 1, device=q_fft.device, dtype=torch.cfloat) + spatial_q[:, :, :, self.time_q_index] = q_fft_box + + # k + k_fft_box = torch.zeros(B, H, E, len(self.time_k_index), device=q_fft.device, dtype=torch.cfloat) + k_fft_box = k_fft[:, :, :, self.time_k_index] + spatial_k = torch.zeros(B, H, E, L // 2 + 1, device=k_fft.device, dtype=torch.cfloat) + spatial_k[:, :, :, self.time_k_index] = k_fft_box + + + # v + v_fft = torch.fft.rfft(values.permute(0, 2, 3, 1).contiguous(), dim=-1) + # 装到采样的空盒子里 + v_fft_box = torch.zeros(B, H, E, len(self.time_v_index), device=v_fft.device, dtype=torch.cfloat) + v_fft_box = v_fft[:, :, :, self.time_v_index] + spatial_v = torch.zeros(B, H, E, L // 2 + 1, device=v_fft.device, dtype=torch.cfloat) + spatial_v[:, :, :, self.time_v_index] = v_fft_box + + + + queries = torch.fft.irfft(spatial_q, dim=-1) + keys = torch.fft.irfft(spatial_k, dim=-1) + values = torch.fft.irfft(spatial_v, dim=-1) + + queries = queries.permute(0, 1, 3, 2) + keys = keys.permute(0, 1, 3, 2) + values = values.permute(0, 1, 3, 2) + + attention_scores = torch.matmul(queries, keys.transpose(-1, -2)) + attention_scores = attention_scores / math.sqrt(self.attention_head_size) + + attention_scores = attention_scores + attention_mask + attention_probs = nn.Softmax(dim=-1)(attention_scores) + attention_probs = self.attn_dropout(attention_probs) + qkv = torch.matmul(attention_probs, values) # [256, 2, index, 32] + context_layer_spatial = qkv.permute(0, 2, 1, 3).contiguous() + new_context_layer_shape = context_layer_spatial.size()[:-2] + (self.all_head_size,) + context_layer_spatial = context_layer_spatial.view(*new_context_layer_shape) + context_layer = (1 - self.spatial_ratio) * context_layer + self.spatial_ratio * context_layer_spatial + + + hidden_states = self.dense(context_layer) + hidden_states = self.out_dropout(hidden_states) + hidden_states = self.LayerNorm(hidden_states + input_tensor) + return hidden_states + +class FeedForward(nn.Module): + """ + Point-wise feed-forward layer is implemented by two dense layers. + + Args: + input_tensor (torch.Tensor): the input of the point-wise feed-forward layer + + Returns: + hidden_states (torch.Tensor): the output of the point-wise feed-forward layer + + """ + + def __init__(self, hidden_size, inner_size, hidden_dropout_prob, hidden_act, layer_norm_eps): + super(FeedForward, self).__init__() + self.dense_1 = nn.Linear(hidden_size, inner_size) + self.intermediate_act_fn = self.get_hidden_act(hidden_act) + + self.dense_2 = nn.Linear(inner_size, hidden_size) + self.LayerNorm = nn.LayerNorm(hidden_size, eps=layer_norm_eps) + self.dropout = nn.Dropout(hidden_dropout_prob) + + def get_hidden_act(self, act): + ACT2FN = { + "gelu": self.gelu, + "relu": fn.relu, + "swish": self.swish, + "tanh": torch.tanh, + "sigmoid": torch.sigmoid, + } + return ACT2FN[act] + + def gelu(self, x): + """Implementation of the gelu activation function. + + For information: OpenAI GPT's gelu is slightly different (and gives slightly different results):: + + 0.5 * x * (1 + torch.tanh(math.sqrt(2 / math.pi) * (x + 0.044715 * torch.pow(x, 3)))) + + Also see https://arxiv.org/abs/1606.08415 + """ + return x * 0.5 * (1.0 + torch.erf(x / math.sqrt(2.0))) + + def swish(self, x): + return x * torch.sigmoid(x) + + def forward(self, input_tensor): + hidden_states = self.dense_1(input_tensor) + hidden_states = self.intermediate_act_fn(hidden_states) + + hidden_states = self.dense_2(hidden_states) + hidden_states = self.dropout(hidden_states) + hidden_states = self.LayerNorm(hidden_states + input_tensor) + + return hidden_states + +class FEABlock(nn.Module): + """ + One transformer layer consists of a multi-head self-attention layer and a point-wise feed-forward layer. + + Args: + hidden_states (torch.Tensor): the input of the multi-head self-attention sublayer + attention_mask (torch.Tensor): the attention mask for the multi-head self-attention sublayer + + Returns: + feedforward_output (torch.Tensor): The output of the point-wise feed-forward sublayer, + is the output of the transformer layer. + + """ + + def __init__( + self, n_heads, hidden_size, intermediate_size, hidden_dropout_prob, attn_dropout_prob, hidden_act, + layer_norm_eps, n, config + ): + super(FEABlock, self).__init__() + self.hybrid_attention = HybridAttention( + n_heads, hidden_size, hidden_dropout_prob, attn_dropout_prob, layer_norm_eps, n, config + ) + self.feed_forward = FeedForward(hidden_size, intermediate_size, hidden_dropout_prob, hidden_act, layer_norm_eps) + + def forward(self, hidden_states, attention_mask): + + attention_output = self.hybrid_attention(hidden_states, attention_mask) + feedforward_output = self.feed_forward(attention_output) + + return feedforward_output + +class FEAEncoder(nn.Module): + r""" One TransformerEncoder consists of several TransformerLayers. + + - n_layers(num): num of transformer layers in transformer encoder. Default: 2 + - n_heads(num): num of attention heads for multi-head attention layer. Default: 2 + - hidden_size(num): the input and output hidden size. Default: 64 + - inner_size(num): the dimensionality in feed-forward layer. Default: 256 + - hidden_dropout_prob(float): probability of an element to be zeroed. Default: 0.5 + - attn_dropout_prob(float): probability of an attention score to be zeroed. Default: 0.5 + - hidden_act(str): activation function in feed-forward layer. Default: 'gelu' + candidates: 'gelu', 'relu', 'swish', 'tanh', 'sigmoid' + - layer_norm_eps(float): a value added to the denominator for numerical stability. Default: 1e-12 + + """ + + def __init__( + self, + n_layers=2, + n_heads=2, + hidden_size=64, + inner_size=256, + hidden_dropout_prob=0.5, + attn_dropout_prob=0.5, + hidden_act='gelu', + layer_norm_eps=1e-12, + config=None, + ): + + super(FEAEncoder, self).__init__() + self.n_layers = n_layers + self.layer = nn.ModuleList() + for n in range(self.n_layers): + self.layer_ramp = FEABlock(n_heads, hidden_size, inner_size, hidden_dropout_prob, attn_dropout_prob, hidden_act, layer_norm_eps, n, config) + self.layer.append(self.layer_ramp) + + def forward(self, hidden_states, attention_mask, output_all_encoded_layers=True): + """ + Args: + hidden_states (torch.Tensor): the input of the TransformerEncoder + attention_mask (torch.Tensor): the attention mask for the input hidden_states + output_all_encoded_layers (Bool): whether output all transformer layers' output + + Returns: + all_encoder_layers (list): if output_all_encoded_layers is True, return a list consists of all transformer + layers' output, otherwise return a list only consists of the output of last transformer layer. + + """ + all_encoder_layers = [] + + for layer_module in self.layer: + hidden_states = layer_module(hidden_states, attention_mask) + if output_all_encoded_layers: + all_encoder_layers.append(hidden_states) + if not output_all_encoded_layers: + all_encoder_layers.append(hidden_states) + return all_encoder_layers diff --git a/recbole/properties/model/FEARec.yaml b/recbole/properties/model/FEARec.yaml new file mode 100644 index 000000000..29813bb9e --- /dev/null +++ b/recbole/properties/model/FEARec.yaml @@ -0,0 +1,20 @@ +n_layers: 2 # (int) The number of transformer layers in transformer encoder. +n_heads: 2 # (int) The number of attention heads for multi-head attention layer. +hidden_size: 64 # (int) The number of features in the hidden state +inner_size: 256 # (int) The inner hidden size in feed-forward layer. +hidden_dropout_prob: 0.5 # (float) The probability of an element to be zeroed. +attn_dropout_prob: 0.5 # (float) The probability of an attention score to be zeroed. +hidden_act: 'gelu' # (str) The activation function in feed-forward layer. +layer_norm_eps: 1e-12 # (float) A value added to the denominator for numerical stability. +initializer_range: 0.02 # (float) The standard deviation for normal initialization. +loss_type: 'CE' # (str) The type of loss function. +lmd: 0.1 # (float) The weight of unsupervised normalized CE loss. +lmd_sem: 0.1 # (float) The weight of supervised normalized CE loss. + +global_ratio: 1 # (float) The ratio of frequency components +dual_domain: False # (bool) Frequency domain processing or not +std: False # (bool) Use the specific time index or not +spatial_ratio: 0 # (float) The ratio of the spatial domain and frequency domain +fredom: False # (bool) Regularization in the frequency domain or not +fredom_type: None # (str) The type of loss in different scenarios +topk_factor: 1 # (int) To aggregate time delayed sequences with high autocorrelation diff --git a/tests/model/test_model_auto.py b/tests/model/test_model_auto.py index 672d8b659..5ea569e15 100644 --- a/tests/model/test_model_auto.py +++ b/tests/model/test_model_auto.py @@ -754,6 +754,13 @@ def test_core_ave(self): "dnn_type": "ave", } quick_test(config_dict) + + def test_fea_rec(self): + config_dict = { + "model": "FEARec", + "train_neg_sample_args": None, + } + quick_test(config_dict) # def test_gru4reckg(self): # config_dict = { From 0575c2e8ce8bce9bf63e1b58bbc0f4a37e41b1a0 Mon Sep 17 00:00:00 2001 From: TayTroye Date: Thu, 26 Oct 2023 15:32:15 +0000 Subject: [PATCH 2/4] Format Python code according to PEP8 --- .../model/sequential_recommender/fearec.py | 434 ++++++++++++------ tests/model/test_model_auto.py | 2 +- 2 files changed, 287 insertions(+), 149 deletions(-) diff --git a/recbole/model/sequential_recommender/fearec.py b/recbole/model/sequential_recommender/fearec.py index cc3297de5..6c0885ccd 100644 --- a/recbole/model/sequential_recommender/fearec.py +++ b/recbole/model/sequential_recommender/fearec.py @@ -11,6 +11,7 @@ from recbole.model.abstract_recommender import SequentialRecommender + # from recbole.model.layers import FEAEncoder #考虑把encoder放进来 from recbole.model.loss import BPRLoss from recbole.data.interaction import Interaction @@ -23,24 +24,28 @@ def __init__(self, config, dataset): # load parameters info self.dataset = dataset self.config = config - self.n_layers = config['n_layers'] - self.n_heads = config['n_heads'] - self.hidden_size = config['hidden_size'] # same as embedding_size - self.inner_size = config['inner_size'] # the dimensionality in feed-forward layer - self.hidden_dropout_prob = config['hidden_dropout_prob'] - self.attn_dropout_prob = config['attn_dropout_prob'] - self.hidden_act = config['hidden_act'] - self.layer_norm_eps = config['layer_norm_eps'] - - self.lmd = config['lmd'] - self.lmd_sem = config['lmd_sem'] - - self.initializer_range = config['initializer_range'] - self.loss_type = config['loss_type'] + self.n_layers = config["n_layers"] + self.n_heads = config["n_heads"] + self.hidden_size = config["hidden_size"] # same as embedding_size + self.inner_size = config[ + "inner_size" + ] # the dimensionality in feed-forward layer + self.hidden_dropout_prob = config["hidden_dropout_prob"] + self.attn_dropout_prob = config["attn_dropout_prob"] + self.hidden_act = config["hidden_act"] + self.layer_norm_eps = config["layer_norm_eps"] + + self.lmd = config["lmd"] + self.lmd_sem = config["lmd_sem"] + + self.initializer_range = config["initializer_range"] + self.loss_type = config["loss_type"] self.same_item_index = self.get_same_item_index(dataset) # define layers and loss - self.item_embedding = nn.Embedding(self.n_items, self.hidden_size, padding_idx=0) + self.item_embedding = nn.Embedding( + self.n_items, self.hidden_size, padding_idx=0 + ) self.position_embedding = nn.Embedding(self.max_seq_length, self.hidden_size) self.item_encoder = FEAEncoder( n_layers=self.n_layers, @@ -57,19 +62,19 @@ def __init__(self, config, dataset): self.LayerNorm = nn.LayerNorm(self.hidden_size, eps=self.layer_norm_eps) self.dropout = nn.Dropout(self.hidden_dropout_prob) - if self.loss_type == 'BPR': + if self.loss_type == "BPR": self.loss_fct = BPRLoss() - elif self.loss_type == 'CE': + elif self.loss_type == "CE": self.loss_fct = nn.CrossEntropyLoss() else: raise NotImplementedError("Make sure 'loss_type' in ['BPR', 'CE']!") - self.ssl = config['contrast'] - self.tau = config['tau'] - self.sim = config['sim'] - self.fredom = config['fredom'] - self.fredom_type = config['fredom_type'] - self.batch_size = config['train_batch_size'] + self.ssl = config["contrast"] + self.tau = config["tau"] + self.sim = config["sim"] + self.fredom = config["fredom"] + self.fredom_type = config["fredom_type"] + self.batch_size = config["train_batch_size"] self.mask_default = self.mask_correlated_samples(batch_size=self.batch_size) self.aug_nce_fct = nn.CrossEntropyLoss() self.sem_aug_nce_fct = nn.CrossEntropyLoss() @@ -77,13 +82,13 @@ def __init__(self, config, dataset): # parameters initialization self.apply(self._init_weights) - - def get_same_item_index(self,dataset): + def get_same_item_index(self, dataset): same_target_index = {} - aug_path = dataset.config['data_path'] + '/semantic_augmentationindex.pkl' + aug_path = dataset.config["data_path"] + "/semantic_augmentationindex.pkl" import os + if os.path.exists(aug_path): - with open(aug_path, 'rb') as file: + with open(aug_path, "rb") as file: same_target_index = pickle.load(file) file.close() else: @@ -100,14 +105,14 @@ def get_same_item_index(self,dataset): count += 1 # print(item_id,"index is",same_target_index[item_id]) - with open(aug_path, 'wb') as file: - pickle.dump(same_target_index,file) + with open(aug_path, "wb") as file: + pickle.dump(same_target_index, file) file.close() return same_target_index def _init_weights(self, module): - """ Initialize the weights """ + """Initialize the weights""" if isinstance(module, (nn.Linear, nn.Embedding)): # Slightly different from the TF version which uses truncated_normal for initialization # cf https://github.com/pytorch/pytorch/pull/5617 @@ -134,7 +139,9 @@ def get_attention_mask(self, item_seq): """ 最终,这个方法返回一个用于多头注意力的 attention mask,确保了在每个位置上, 只能注意到该位置之前的位置。这有助于模型按照时间顺序逐步生成输出,过滤掉未来的信息 """ attention_mask = (item_seq > 0).long() - extended_attention_mask = attention_mask.unsqueeze(1).unsqueeze(2) # torch.int64 + extended_attention_mask = attention_mask.unsqueeze(1).unsqueeze( + 2 + ) # torch.int64 # mask for left-to-right unidirectional max_len = attention_mask.size(-1) attn_shape = (1, max_len, max_len) @@ -143,25 +150,32 @@ def get_attention_mask(self, item_seq): subsequent_mask = subsequent_mask.long().to(item_seq.device) extended_attention_mask = extended_attention_mask * subsequent_mask - extended_attention_mask = extended_attention_mask.to(dtype=next(self.parameters()).dtype) # fp16 compatibility + extended_attention_mask = extended_attention_mask.to( + dtype=next(self.parameters()).dtype + ) # fp16 compatibility extended_attention_mask = (1.0 - extended_attention_mask) * -10000.0 return extended_attention_mask def get_bi_attention_mask(self, item_seq): """Generate bidirectional attention mask for multi-head attention.""" attention_mask = (item_seq > 0).long() - extended_attention_mask = attention_mask.unsqueeze(1).unsqueeze(2) # torch.int64 + extended_attention_mask = attention_mask.unsqueeze(1).unsqueeze( + 2 + ) # torch.int64 # bidirectional mask - extended_attention_mask = extended_attention_mask.to(dtype=next(self.parameters()).dtype) # fp16 compatibility + extended_attention_mask = extended_attention_mask.to( + dtype=next(self.parameters()).dtype + ) # fp16 compatibility extended_attention_mask = (1.0 - extended_attention_mask) * -10000.0 return extended_attention_mask def forward(self, item_seq, item_seq_len): - position_ids = torch.arange(item_seq.size(1), dtype=torch.long, device=item_seq.device) + position_ids = torch.arange( + item_seq.size(1), dtype=torch.long, device=item_seq.device + ) position_ids = position_ids.unsqueeze(0).expand_as(item_seq) position_embedding = self.position_embedding(position_ids) - item_emb = self.item_embedding(item_seq) input_emb = item_emb + position_embedding input_emb = self.LayerNorm(input_emb) @@ -170,9 +184,9 @@ def forward(self, item_seq, item_seq_len): extended_attention_mask = self.get_attention_mask(item_seq) # extended_attention_mask = self.get_bi_attention_mask(item_seq) - - - trm_output = self.item_encoder(input_emb, extended_attention_mask, output_all_encoded_layers=True) + trm_output = self.item_encoder( + input_emb, extended_attention_mask, output_all_encoded_layers=True + ) output = trm_output[-1] output = self.gather_indexes(output, item_seq_len - 1) @@ -189,10 +203,7 @@ def uniformity(x): x = abs(x) return torch.pdist(x, p=2).pow(2).mul(-2).exp().mean().log() - - def calculate_loss(self, interaction): - same_item_index = self.same_item_index sem_pos_lengths = [] sem_pos_seqs = [] @@ -206,26 +217,27 @@ def calculate_loss(self, interaction): print("error") while True: sample_index = random.choice(targets_index) - cur_item_list = interaction[self.ITEM_SEQ][i].to('cpu') - sample_item_list = dataset.inter_feat['item_id_list'][sample_index] - are_equal = torch.equal(cur_item_list,sample_item_list) - sample_item_length = dataset.inter_feat['item_length'][sample_index] + cur_item_list = interaction[self.ITEM_SEQ][i].to("cpu") + sample_item_list = dataset.inter_feat["item_id_list"][sample_index] + are_equal = torch.equal(cur_item_list, sample_item_list) + sample_item_length = dataset.inter_feat["item_length"][sample_index] if not are_equal or lens == 1: sem_pos_lengths.append(sample_item_length) sem_pos_seqs.append(sample_item_list) - break; + break sem_pos_lengths = torch.stack(sem_pos_lengths).to(self.device) sem_pos_seqs = torch.stack(sem_pos_seqs).to(self.device) - interaction.update(Interaction({'sem_aug': sem_pos_seqs, 'sem_aug_lengths': sem_pos_lengths})) - + interaction.update( + Interaction({"sem_aug": sem_pos_seqs, "sem_aug_lengths": sem_pos_lengths}) + ) item_seq = interaction[self.ITEM_SEQ] item_seq_len = interaction[self.ITEM_SEQ_LEN] seq_output = self.forward(item_seq, item_seq_len) pos_items = interaction[self.POS_ITEM_ID] - if self.loss_type == 'BPR': + if self.loss_type == "BPR": neg_items = interaction[self.NEG_ITEM_ID] pos_items_emb = self.item_embedding(pos_items) neg_items_emb = self.item_embedding(neg_items) @@ -237,12 +249,16 @@ def calculate_loss(self, interaction): logits = torch.matmul(seq_output, test_item_emb.transpose(0, 1)) loss = self.loss_fct(logits, pos_items) - # Unsupervised NCE - if self.ssl in ['us', 'un']: + if self.ssl in ["us", "un"]: aug_seq_output = self.forward(item_seq, item_seq_len) - nce_logits, nce_labels = self.info_nce(seq_output, aug_seq_output, temp=self.tau, - batch_size=item_seq_len.shape[0], sim=self.sim) + nce_logits, nce_labels = self.info_nce( + seq_output, + aug_seq_output, + temp=self.tau, + batch_size=item_seq_len.shape[0], + sim=self.sim, + ) # nce_logits = torch.mm(seq_output, aug_seq_output.T) # nce_labels = torch.tensor(list(range(nce_logits.shape[0])), dtype=torch.long, device=item_seq.device) @@ -255,12 +271,20 @@ def calculate_loss(self, interaction): loss += self.lmd * self.aug_nce_fct(nce_logits, nce_labels) # Supervised NCE - if self.ssl in ['us', 'su']: - sem_aug, sem_aug_lengths = interaction['sem_aug'], interaction['sem_aug_lengths'] + if self.ssl in ["us", "su"]: + sem_aug, sem_aug_lengths = ( + interaction["sem_aug"], + interaction["sem_aug_lengths"], + ) sem_aug_seq_output = self.forward(sem_aug, sem_aug_lengths) - sem_nce_logits, sem_nce_labels = self.info_nce(seq_output, sem_aug_seq_output, temp=self.tau, - batch_size=item_seq_len.shape[0], sim=self.sim) + sem_nce_logits, sem_nce_labels = self.info_nce( + seq_output, + sem_aug_seq_output, + temp=self.tau, + batch_size=item_seq_len.shape[0], + sim=self.sim, + ) # sem_nce_logits = torch.mm(seq_output, sem_aug_seq_output.T) / self.tau # sem_nce_labels = torch.tensor(list(range(sem_nce_logits.shape[0])), dtype=torch.long, device=item_seq.device) @@ -271,13 +295,21 @@ def calculate_loss(self, interaction): loss += self.lmd_sem * self.aug_nce_fct(sem_nce_logits, sem_nce_labels) - if self.ssl == 'us_x': + if self.ssl == "us_x": aug_seq_output = self.forward(item_seq, item_seq_len) - sem_aug, sem_aug_lengths = interaction['sem_aug'], interaction['sem_aug_lengths'] + sem_aug, sem_aug_lengths = ( + interaction["sem_aug"], + interaction["sem_aug_lengths"], + ) sem_aug_seq_output = self.forward(sem_aug, sem_aug_lengths) - sem_nce_logits, sem_nce_labels = self.info_nce(aug_seq_output, sem_aug_seq_output, temp=self.tau, - batch_size=item_seq_len.shape[0], sim=self.sim) + sem_nce_logits, sem_nce_labels = self.info_nce( + aug_seq_output, + sem_aug_seq_output, + temp=self.tau, + batch_size=item_seq_len.shape[0], + sim=self.sim, + ) loss += self.lmd_sem * self.aug_nce_fct(sem_nce_logits, sem_nce_labels) # with torch.no_grad(): @@ -286,15 +318,22 @@ def calculate_loss(self, interaction): # frequency domain loss if self.fredom: - seq_output_f = torch.fft.rfft(seq_output, dim=1, norm='ortho') - aug_seq_output_f = torch.fft.rfft(aug_seq_output, dim=1, norm='ortho') - sem_aug_seq_output_f = torch.fft.rfft(sem_aug_seq_output, dim=1, norm='ortho') - if self.fredom_type in ['us', 'un']: + seq_output_f = torch.fft.rfft(seq_output, dim=1, norm="ortho") + aug_seq_output_f = torch.fft.rfft(aug_seq_output, dim=1, norm="ortho") + sem_aug_seq_output_f = torch.fft.rfft( + sem_aug_seq_output, dim=1, norm="ortho" + ) + if self.fredom_type in ["us", "un"]: loss += 0.1 * abs(seq_output_f - aug_seq_output_f).flatten().mean() - if self.fredom_type in ['us', 'su']: - loss += 0.1 * abs(seq_output_f - sem_aug_seq_output_f).flatten().mean() - if self.fredom_type == 'us_x': - loss += 0.1 * abs(aug_seq_output_f - sem_aug_seq_output_f).flatten().mean() + if self.fredom_type in ["us", "su"]: + loss += ( + 0.1 * abs(seq_output_f - sem_aug_seq_output_f).flatten().mean() + ) + if self.fredom_type == "us_x": + loss += ( + 0.1 + * abs(aug_seq_output_f - sem_aug_seq_output_f).flatten().mean() + ) return loss @@ -307,7 +346,7 @@ def mask_correlated_samples(self, batch_size): mask[batch_size + i, i] = 0 return mask - def info_nce(self, z_i, z_j, temp, batch_size, sim='dot'): + def info_nce(self, z_i, z_j, temp, batch_size, sim="dot"): """ We do not sample negative examples explicitly. Instead, given a positive pair, similar to (Chen et al., 2017), we treat the other 2(N − 1) augmented examples within a minibatch as negative examples. @@ -316,9 +355,12 @@ def info_nce(self, z_i, z_j, temp, batch_size, sim='dot'): z = torch.cat((z_i, z_j), dim=0) - if sim == 'cos': - sim = nn.functional.cosine_similarity(z.unsqueeze(1), z.unsqueeze(0), dim=2) / temp - elif sim == 'dot': + if sim == "cos": + sim = ( + nn.functional.cosine_similarity(z.unsqueeze(1), z.unsqueeze(0), dim=2) + / temp + ) + elif sim == "dot": sim = torch.mm(z, z.T) / temp sim_i_j = torch.diag(sim, batch_size) @@ -380,7 +422,6 @@ def full_sort_predict(self, interaction): return scores - class HybridAttention(nn.Module): """ Hybrid Attention layer: combine time domain self-attention layer and frequency domain attention layer. @@ -394,7 +435,16 @@ class HybridAttention(nn.Module): """ - def __init__(self, n_heads, hidden_size, hidden_dropout_prob, attn_dropout_prob, layer_norm_eps, i, config): + def __init__( + self, + n_heads, + hidden_size, + hidden_dropout_prob, + attn_dropout_prob, + layer_norm_eps, + i, + config, + ): super(HybridAttention, self).__init__() if hidden_size % n_heads != 0: raise ValueError( @@ -402,7 +452,7 @@ def __init__(self, n_heads, hidden_size, hidden_dropout_prob, attn_dropout_prob, "heads (%d)" % (hidden_size, n_heads) ) - self.factor = config['topk_factor'] + self.factor = config["topk_factor"] self.scale = None self.mask_flag = True self.output_attention = False @@ -419,29 +469,45 @@ def __init__(self, n_heads, hidden_size, hidden_dropout_prob, attn_dropout_prob, self.LayerNorm = nn.LayerNorm(hidden_size, eps=layer_norm_eps) self.out_dropout = nn.Dropout(hidden_dropout_prob) self.filter_mixer = None - self.global_ratio = config['global_ratio'] - self.n_layers = config['n_layers'] + self.global_ratio = config["global_ratio"] + self.n_layers = config["n_layers"] if self.global_ratio > (1 / self.n_layers): - print("{}>{}:{}".format(self.global_ratio, 1 / self.n_layers, self.global_ratio > (1 / self.n_layers))) - self.filter_mixer = 'G' + print( + "{}>{}:{}".format( + self.global_ratio, + 1 / self.n_layers, + self.global_ratio > (1 / self.n_layers), + ) + ) + self.filter_mixer = "G" else: - print("{}>{}:{}".format(self.global_ratio, 1 / self.n_layers, self.global_ratio > (1 / self.n_layers))) - self.filter_mixer = 'L' - self.max_item_list_length = config['MAX_ITEM_LIST_LENGTH'] - self.dual_domain = config['dual_domain'] - self.slide_step = ((self.max_item_list_length // 2 + 1) * (1 - self.global_ratio)) // (self.n_layers - 1) + print( + "{}>{}:{}".format( + self.global_ratio, + 1 / self.n_layers, + self.global_ratio > (1 / self.n_layers), + ) + ) + self.filter_mixer = "L" + self.max_item_list_length = config["MAX_ITEM_LIST_LENGTH"] + self.dual_domain = config["dual_domain"] + self.slide_step = ( + (self.max_item_list_length // 2 + 1) * (1 - self.global_ratio) + ) // (self.n_layers - 1) self.local_ratio = 1 / self.n_layers self.filter_size = self.local_ratio * (self.max_item_list_length // 2 + 1) - if self.filter_mixer == 'G': + if self.filter_mixer == "G": self.w = self.global_ratio self.s = self.slide_step - if self.filter_mixer == 'L': + if self.filter_mixer == "L": self.w = self.local_ratio self.s = self.filter_size - self.left = int(((self.max_item_list_length // 2 + 1) * (1 - self.w)) - (i * self.s)) + self.left = int( + ((self.max_item_list_length // 2 + 1) * (1 - self.w)) - (i * self.s) + ) self.right = int((self.max_item_list_length // 2 + 1) - i * self.s) # random: @@ -453,7 +519,7 @@ def __init__(self, n_heads, hidden_size, hidden_dropout_prob, attn_dropout_prob, self.k_index = list(range(self.left, self.right)) self.v_index = list(range(self.left, self.right)) # if sample in time domain - self.std = config['std'] + self.std = config["std"] if self.std: self.time_q_index = self.q_index self.time_k_index = self.k_index @@ -463,15 +529,18 @@ def __init__(self, n_heads, hidden_size, hidden_dropout_prob, attn_dropout_prob, self.time_k_index = list(range(self.max_item_list_length // 2 + 1)) self.time_v_index = list(range(self.max_item_list_length // 2 + 1)) - print('modes_q={}, index_q={}'.format(len(self.q_index), self.q_index)) - print('modes_k={}, index_k={}'.format(len(self.k_index), self.k_index)) - print('modes_v={}, index_v={}'.format(len(self.v_index), self.v_index)) + print("modes_q={}, index_q={}".format(len(self.q_index), self.q_index)) + print("modes_k={}, index_k={}".format(len(self.k_index), self.k_index)) + print("modes_v={}, index_v={}".format(len(self.v_index), self.v_index)) - if self.config['dual_domain']: - self.spatial_ratio = self.config['spatial_ratio'] + if self.config["dual_domain"]: + self.spatial_ratio = self.config["spatial_ratio"] def transpose_for_scores(self, x): - new_x_shape = x.size()[:-1] + (self.num_attention_heads, self.attention_head_size) + new_x_shape = x.size()[:-1] + ( + self.num_attention_heads, + self.attention_head_size, + ) x = x.view(*new_x_shape) # [256, 50, 2, 32] # return x.permute(0, 2, 1, 3) # [256, 2, 50, 32] return x @@ -496,8 +565,13 @@ def time_delay_agg_training(self, values, corr): delays_agg = torch.zeros_like(values).float() for i in range(top_k): pattern = torch.roll(tmp_values, -int(index[i]), -1) - delays_agg = delays_agg + pattern * \ - (tmp_corr[:, i].unsqueeze(1).unsqueeze(1).unsqueeze(1).repeat(1, head, channel, length)) + delays_agg = delays_agg + pattern * ( + tmp_corr[:, i] + .unsqueeze(1) + .unsqueeze(1) + .unsqueeze(1) + .repeat(1, head, channel, length) + ) return delays_agg def time_delay_agg_inference(self, values, corr): @@ -510,8 +584,14 @@ def time_delay_agg_inference(self, values, corr): channel = values.shape[2] length = values.shape[3] # index init - init_index = torch.arange(length).unsqueeze(0).unsqueeze(0).unsqueeze(0) \ - .repeat(batch, head, channel, 1).to(values.device) + init_index = ( + torch.arange(length) + .unsqueeze(0) + .unsqueeze(0) + .unsqueeze(0) + .repeat(batch, head, channel, 1) + .to(values.device) + ) # find top k top_k = int(self.factor * math.log(length)) mean_value = torch.mean(torch.mean(corr, dim=1), dim=1) @@ -522,10 +602,17 @@ def time_delay_agg_inference(self, values, corr): tmp_values = values.repeat(1, 1, 1, 2) delays_agg = torch.zeros_like(values).float() for i in range(top_k): - tmp_delay = init_index + delay[:, i].unsqueeze(1).unsqueeze(1).unsqueeze(1).repeat(1, head, channel, length) + tmp_delay = init_index + delay[:, i].unsqueeze(1).unsqueeze(1).unsqueeze( + 1 + ).repeat(1, head, channel, length) pattern = torch.gather(tmp_values, dim=-1, index=tmp_delay) - delays_agg = delays_agg + pattern * \ - (tmp_corr[:, i].unsqueeze(1).unsqueeze(1).unsqueeze(1).repeat(1, head, channel, length)) + delays_agg = delays_agg + pattern * ( + tmp_corr[:, i] + .unsqueeze(1) + .unsqueeze(1) + .unsqueeze(1) + .repeat(1, head, channel, length) + ) return delays_agg def forward(self, input_tensor, attention_mask): @@ -533,20 +620,19 @@ def forward(self, input_tensor, attention_mask): mixed_key_layer = self.key_layer(input_tensor) mixed_value_layer = self.value_layer(input_tensor) - - #trans挺快,query + # trans挺快,query queries = self.transpose_for_scores(mixed_query_layer) keys = self.transpose_for_scores(mixed_key_layer) values = self.transpose_for_scores(mixed_value_layer) - #这段代码是注意力机制中的一部分,涉及到对查询(queries)和键(keys)进行频域变换(FFT),以应用频域注意力 + # 这段代码是注意力机制中的一部分,涉及到对查询(queries)和键(keys)进行频域变换(FFT),以应用频域注意力 # B, H, L, E = query_layer.shape # AutoFormer B, L, H, E = queries.shape # print("qqqq", queries.shape) [256,50,2,32,] _, S, _, D = values.shape if L > S: - zeros = torch.zeros_like(queries[:, :(L - S), :]).float() + zeros = torch.zeros_like(queries[:, : (L - S), :]).float() values = torch.cat([values, zeros], dim=1) keys = torch.cat([keys, zeros], dim=1) else: @@ -557,32 +643,39 @@ def forward(self, input_tensor, attention_mask): q_fft = torch.fft.rfft(queries.permute(0, 2, 3, 1).contiguous(), dim=-1) k_fft = torch.fft.rfft(keys.permute(0, 2, 3, 1).contiguous(), dim=-1) - # 装到采样的空盒子里 - q_fft_box = torch.zeros(B, H, E, len(self.q_index), device=q_fft.device, dtype=torch.cfloat) + q_fft_box = torch.zeros( + B, H, E, len(self.q_index), device=q_fft.device, dtype=torch.cfloat + ) q_fft_box = q_fft[:, :, :, self.q_index] - k_fft_box = torch.zeros(B, H, E, len(self.k_index), device=q_fft.device, dtype=torch.cfloat) + k_fft_box = torch.zeros( + B, H, E, len(self.k_index), device=q_fft.device, dtype=torch.cfloat + ) k_fft_box = k_fft[:, :, :, self.q_index] res = q_fft_box * torch.conj(k_fft_box) - if self.config['use_filter']: + if self.config["use_filter"]: # filter weight = torch.view_as_complex(self.complex_weight) res = res * weight - box_res = torch.zeros(B, H, E, L // 2 + 1, device=q_fft.device, dtype=torch.cfloat) + box_res = torch.zeros( + B, H, E, L // 2 + 1, device=q_fft.device, dtype=torch.cfloat + ) box_res[:, :, :, self.q_index] = res corr = torch.fft.irfft(box_res, dim=-1) - # time delay agg if self.training: - V = self.time_delay_agg_training(values.permute(0, 2, 3, 1).contiguous(), corr).permute(0, 3, 1, 2) + V = self.time_delay_agg_training( + values.permute(0, 2, 3, 1).contiguous(), corr + ).permute(0, 3, 1, 2) else: - V = self.time_delay_agg_inference(values.permute(0, 2, 3, 1).contiguous(), corr).permute(0, 3, 1, 2) - + V = self.time_delay_agg_inference( + values.permute(0, 2, 3, 1).contiguous(), corr + ).permute(0, 3, 1, 2) # print(V.shape) new_context_layer_shape = V.size()[:-2] + (self.all_head_size,) @@ -591,28 +684,37 @@ def forward(self, input_tensor, attention_mask): if self.dual_domain: # 装到采样的空盒子里 # q - q_fft_box = torch.zeros(B, H, E, len(self.time_q_index), device=q_fft.device, dtype=torch.cfloat) + q_fft_box = torch.zeros( + B, H, E, len(self.time_q_index), device=q_fft.device, dtype=torch.cfloat + ) q_fft_box = q_fft[:, :, :, self.time_q_index] - spatial_q = torch.zeros(B, H, E, L // 2 + 1, device=q_fft.device, dtype=torch.cfloat) + spatial_q = torch.zeros( + B, H, E, L // 2 + 1, device=q_fft.device, dtype=torch.cfloat + ) spatial_q[:, :, :, self.time_q_index] = q_fft_box # k - k_fft_box = torch.zeros(B, H, E, len(self.time_k_index), device=q_fft.device, dtype=torch.cfloat) + k_fft_box = torch.zeros( + B, H, E, len(self.time_k_index), device=q_fft.device, dtype=torch.cfloat + ) k_fft_box = k_fft[:, :, :, self.time_k_index] - spatial_k = torch.zeros(B, H, E, L // 2 + 1, device=k_fft.device, dtype=torch.cfloat) + spatial_k = torch.zeros( + B, H, E, L // 2 + 1, device=k_fft.device, dtype=torch.cfloat + ) spatial_k[:, :, :, self.time_k_index] = k_fft_box - # v v_fft = torch.fft.rfft(values.permute(0, 2, 3, 1).contiguous(), dim=-1) # 装到采样的空盒子里 - v_fft_box = torch.zeros(B, H, E, len(self.time_v_index), device=v_fft.device, dtype=torch.cfloat) + v_fft_box = torch.zeros( + B, H, E, len(self.time_v_index), device=v_fft.device, dtype=torch.cfloat + ) v_fft_box = v_fft[:, :, :, self.time_v_index] - spatial_v = torch.zeros(B, H, E, L // 2 + 1, device=v_fft.device, dtype=torch.cfloat) + spatial_v = torch.zeros( + B, H, E, L // 2 + 1, device=v_fft.device, dtype=torch.cfloat + ) spatial_v[:, :, :, self.time_v_index] = v_fft_box - - queries = torch.fft.irfft(spatial_q, dim=-1) keys = torch.fft.irfft(spatial_k, dim=-1) values = torch.fft.irfft(spatial_v, dim=-1) @@ -629,16 +731,20 @@ def forward(self, input_tensor, attention_mask): attention_probs = self.attn_dropout(attention_probs) qkv = torch.matmul(attention_probs, values) # [256, 2, index, 32] context_layer_spatial = qkv.permute(0, 2, 1, 3).contiguous() - new_context_layer_shape = context_layer_spatial.size()[:-2] + (self.all_head_size,) + new_context_layer_shape = context_layer_spatial.size()[:-2] + ( + self.all_head_size, + ) context_layer_spatial = context_layer_spatial.view(*new_context_layer_shape) - context_layer = (1 - self.spatial_ratio) * context_layer + self.spatial_ratio * context_layer_spatial - + context_layer = ( + 1 - self.spatial_ratio + ) * context_layer + self.spatial_ratio * context_layer_spatial hidden_states = self.dense(context_layer) hidden_states = self.out_dropout(hidden_states) hidden_states = self.LayerNorm(hidden_states + input_tensor) return hidden_states + class FeedForward(nn.Module): """ Point-wise feed-forward layer is implemented by two dense layers. @@ -651,7 +757,9 @@ class FeedForward(nn.Module): """ - def __init__(self, hidden_size, inner_size, hidden_dropout_prob, hidden_act, layer_norm_eps): + def __init__( + self, hidden_size, inner_size, hidden_dropout_prob, hidden_act, layer_norm_eps + ): super(FeedForward, self).__init__() self.dense_1 = nn.Linear(hidden_size, inner_size) self.intermediate_act_fn = self.get_hidden_act(hidden_act) @@ -694,6 +802,7 @@ def forward(self, input_tensor): return hidden_states + class FEABlock(nn.Module): """ One transformer layer consists of a multi-head self-attention layer and a point-wise feed-forward layer. @@ -709,34 +818,54 @@ class FEABlock(nn.Module): """ def __init__( - self, n_heads, hidden_size, intermediate_size, hidden_dropout_prob, attn_dropout_prob, hidden_act, - layer_norm_eps, n, config + self, + n_heads, + hidden_size, + intermediate_size, + hidden_dropout_prob, + attn_dropout_prob, + hidden_act, + layer_norm_eps, + n, + config, ): super(FEABlock, self).__init__() self.hybrid_attention = HybridAttention( - n_heads, hidden_size, hidden_dropout_prob, attn_dropout_prob, layer_norm_eps, n, config + n_heads, + hidden_size, + hidden_dropout_prob, + attn_dropout_prob, + layer_norm_eps, + n, + config, + ) + self.feed_forward = FeedForward( + hidden_size, + intermediate_size, + hidden_dropout_prob, + hidden_act, + layer_norm_eps, ) - self.feed_forward = FeedForward(hidden_size, intermediate_size, hidden_dropout_prob, hidden_act, layer_norm_eps) def forward(self, hidden_states, attention_mask): - attention_output = self.hybrid_attention(hidden_states, attention_mask) feedforward_output = self.feed_forward(attention_output) return feedforward_output + class FEAEncoder(nn.Module): - r""" One TransformerEncoder consists of several TransformerLayers. - - - n_layers(num): num of transformer layers in transformer encoder. Default: 2 - - n_heads(num): num of attention heads for multi-head attention layer. Default: 2 - - hidden_size(num): the input and output hidden size. Default: 64 - - inner_size(num): the dimensionality in feed-forward layer. Default: 256 - - hidden_dropout_prob(float): probability of an element to be zeroed. Default: 0.5 - - attn_dropout_prob(float): probability of an attention score to be zeroed. Default: 0.5 - - hidden_act(str): activation function in feed-forward layer. Default: 'gelu' - candidates: 'gelu', 'relu', 'swish', 'tanh', 'sigmoid' - - layer_norm_eps(float): a value added to the denominator for numerical stability. Default: 1e-12 + r"""One TransformerEncoder consists of several TransformerLayers. + + - n_layers(num): num of transformer layers in transformer encoder. Default: 2 + - n_heads(num): num of attention heads for multi-head attention layer. Default: 2 + - hidden_size(num): the input and output hidden size. Default: 64 + - inner_size(num): the dimensionality in feed-forward layer. Default: 256 + - hidden_dropout_prob(float): probability of an element to be zeroed. Default: 0.5 + - attn_dropout_prob(float): probability of an attention score to be zeroed. Default: 0.5 + - hidden_act(str): activation function in feed-forward layer. Default: 'gelu' + candidates: 'gelu', 'relu', 'swish', 'tanh', 'sigmoid' + - layer_norm_eps(float): a value added to the denominator for numerical stability. Default: 1e-12 """ @@ -748,16 +877,25 @@ def __init__( inner_size=256, hidden_dropout_prob=0.5, attn_dropout_prob=0.5, - hidden_act='gelu', + hidden_act="gelu", layer_norm_eps=1e-12, config=None, ): - super(FEAEncoder, self).__init__() self.n_layers = n_layers self.layer = nn.ModuleList() for n in range(self.n_layers): - self.layer_ramp = FEABlock(n_heads, hidden_size, inner_size, hidden_dropout_prob, attn_dropout_prob, hidden_act, layer_norm_eps, n, config) + self.layer_ramp = FEABlock( + n_heads, + hidden_size, + inner_size, + hidden_dropout_prob, + attn_dropout_prob, + hidden_act, + layer_norm_eps, + n, + config, + ) self.layer.append(self.layer_ramp) def forward(self, hidden_states, attention_mask, output_all_encoded_layers=True): diff --git a/tests/model/test_model_auto.py b/tests/model/test_model_auto.py index 5ea569e15..310f7c158 100644 --- a/tests/model/test_model_auto.py +++ b/tests/model/test_model_auto.py @@ -754,7 +754,7 @@ def test_core_ave(self): "dnn_type": "ave", } quick_test(config_dict) - + def test_fea_rec(self): config_dict = { "model": "FEARec", From 23d8d0b569dc5ff98a08f9560b3e4949df7ecc15 Mon Sep 17 00:00:00 2001 From: taytroye <1582706091@qq.com> Date: Fri, 27 Oct 2023 11:01:27 +0800 Subject: [PATCH 3/4] FEA: Add FEARec in sequential models --- .../model/sequential_recommender/fearec.py | 103 ++++++------------ 1 file changed, 35 insertions(+), 68 deletions(-) diff --git a/recbole/model/sequential_recommender/fearec.py b/recbole/model/sequential_recommender/fearec.py index cc3297de5..0af6a527f 100644 --- a/recbole/model/sequential_recommender/fearec.py +++ b/recbole/model/sequential_recommender/fearec.py @@ -1,4 +1,22 @@ -import pickle +# -*- coding: utf-8 -*- +# @Time : 2023/10/27 +# @Author : Kesha Ou +# @Email : keishaou@gmail.com + +r""" +FEARec +################################################ + +Reference: + Xinyu Du et al. "Frequency Enhanced Hybrid Attention Network for Sequential Recommendation." + In SIGIR 2023. + +Reference code: + https://github.com/sudaada/FEARec + +""" + + import random import numpy as np @@ -11,7 +29,6 @@ from recbole.model.abstract_recommender import SequentialRecommender -# from recbole.model.layers import FEAEncoder #考虑把encoder放进来 from recbole.model.loss import BPRLoss from recbole.data.interaction import Interaction @@ -80,29 +97,13 @@ def __init__(self, config, dataset): def get_same_item_index(self,dataset): same_target_index = {} - aug_path = dataset.config['data_path'] + '/semantic_augmentationindex.pkl' - import os - if os.path.exists(aug_path): - with open(aug_path, 'rb') as file: - same_target_index = pickle.load(file) - file.close() - else: - target_item = dataset.inter_feat[self.ITEM_ID].numpy() - - count = 0 - # print(dataset.inter_feat[self.ITEM_ID][2911]) - # print("is 2895 fou") - # print(dataset.inter_feat[self.ITEM_ID][9867]) - for index, item_id in enumerate(target_item): - print("{:1}".format(count / len(target_item) * 100) + "%") - all_index_same_id = np.where(target_item == item_id)[0] - same_target_index[item_id] = all_index_same_id - count += 1 - # print(item_id,"index is",same_target_index[item_id]) - - with open(aug_path, 'wb') as file: - pickle.dump(same_target_index,file) - file.close() + target_item = dataset.inter_feat[self.ITEM_ID].numpy() + count = 0 + + for index, item_id in enumerate(target_item): + all_index_same_id = np.where(target_item == item_id)[0] + same_target_index[item_id] = all_index_same_id + count += 1 return same_target_index @@ -131,8 +132,6 @@ def truncated_normal_(self, tensor, mean=0, std=0.09): def get_attention_mask(self, item_seq): """Generate left-to-right uni-directional attention mask for multi-head attention.""" - """ 最终,这个方法返回一个用于多头注意力的 attention mask,确保了在每个位置上, - 只能注意到该位置之前的位置。这有助于模型按照时间顺序逐步生成输出,过滤掉未来的信息 """ attention_mask = (item_seq > 0).long() extended_attention_mask = attention_mask.unsqueeze(1).unsqueeze(2) # torch.int64 # mask for left-to-right unidirectional @@ -170,8 +169,6 @@ def forward(self, item_seq, item_seq_len): extended_attention_mask = self.get_attention_mask(item_seq) # extended_attention_mask = self.get_bi_attention_mask(item_seq) - - trm_output = self.item_encoder(input_emb, extended_attention_mask, output_all_encoded_layers=True) output = trm_output[-1] output = self.gather_indexes(output, item_seq_len - 1) @@ -190,7 +187,6 @@ def uniformity(x): return torch.pdist(x, p=2).pow(2).mul(-2).exp().mean().log() - def calculate_loss(self, interaction): same_item_index = self.same_item_index @@ -207,9 +203,9 @@ def calculate_loss(self, interaction): while True: sample_index = random.choice(targets_index) cur_item_list = interaction[self.ITEM_SEQ][i].to('cpu') - sample_item_list = dataset.inter_feat['item_id_list'][sample_index] + sample_item_list = dataset.inter_feat[self.ITEM_SEQ][sample_index] are_equal = torch.equal(cur_item_list,sample_item_list) - sample_item_length = dataset.inter_feat['item_length'][sample_index] + sample_item_length = dataset.inter_feat[self.ITEM_SEQ_LEN][sample_index] if not are_equal or lens == 1: sem_pos_lengths.append(sample_item_length) sem_pos_seqs.append(sample_item_list) @@ -244,14 +240,6 @@ def calculate_loss(self, interaction): nce_logits, nce_labels = self.info_nce(seq_output, aug_seq_output, temp=self.tau, batch_size=item_seq_len.shape[0], sim=self.sim) - # nce_logits = torch.mm(seq_output, aug_seq_output.T) - # nce_labels = torch.tensor(list(range(nce_logits.shape[0])), dtype=torch.long, device=item_seq.device) - - # if self.ssl == 'un': - # with torch.no_grad(): - # alignment, uniformity = self.decompose(seq_output, aug_seq_output, seq_output, - # batch_size=item_seq_len.shape[0]) - loss += self.lmd * self.aug_nce_fct(nce_logits, nce_labels) # Supervised NCE @@ -262,13 +250,6 @@ def calculate_loss(self, interaction): sem_nce_logits, sem_nce_labels = self.info_nce(seq_output, sem_aug_seq_output, temp=self.tau, batch_size=item_seq_len.shape[0], sim=self.sim) - # sem_nce_logits = torch.mm(seq_output, sem_aug_seq_output.T) / self.tau - # sem_nce_labels = torch.tensor(list(range(sem_nce_logits.shape[0])), dtype=torch.long, device=item_seq.device) - - # with torch.no_grad(): - # alignment, uniformity = self.decompose(seq_output, sem_aug_seq_output, seq_output, - # batch_size=item_seq_len.shape[0]) - loss += self.lmd_sem * self.aug_nce_fct(sem_nce_logits, sem_nce_labels) if self.ssl == 'us_x': @@ -280,10 +261,7 @@ def calculate_loss(self, interaction): batch_size=item_seq_len.shape[0], sim=self.sim) loss += self.lmd_sem * self.aug_nce_fct(sem_nce_logits, sem_nce_labels) - # with torch.no_grad(): - # alignment, uniformity = self.decompose(aug_seq_output, sem_aug_seq_output, seq_output, - # batch_size=item_seq_len.shape[0]) - + # frequency domain loss if self.fredom: seq_output_f = torch.fft.rfft(seq_output, dim=1, norm='ortho') @@ -444,11 +422,6 @@ def __init__(self, n_heads, hidden_size, hidden_dropout_prob, attn_dropout_prob, self.left = int(((self.max_item_list_length // 2 + 1) * (1 - self.w)) - (i * self.s)) self.right = int((self.max_item_list_length // 2 + 1) - i * self.s) - # random: - # self.q_index = get_frequency_modes(50, 2, 'random') - # self.k_index = get_frequency_modes(50, 2, 'random') - # self.v_index = get_frequency_modes(50, 2, 'random') - self.q_index = list(range(self.left, self.right)) self.k_index = list(range(self.left, self.right)) self.v_index = list(range(self.left, self.right)) @@ -472,8 +445,8 @@ def __init__(self, n_heads, hidden_size, hidden_dropout_prob, attn_dropout_prob, def transpose_for_scores(self, x): new_x_shape = x.size()[:-1] + (self.num_attention_heads, self.attention_head_size) - x = x.view(*new_x_shape) # [256, 50, 2, 32] - # return x.permute(0, 2, 1, 3) # [256, 2, 50, 32] + x = x.view(*new_x_shape) + # return x.permute(0, 2, 1, 3) return x def time_delay_agg_training(self, values, corr): @@ -533,17 +506,13 @@ def forward(self, input_tensor, attention_mask): mixed_key_layer = self.key_layer(input_tensor) mixed_value_layer = self.value_layer(input_tensor) - - #trans挺快,query queries = self.transpose_for_scores(mixed_query_layer) keys = self.transpose_for_scores(mixed_key_layer) values = self.transpose_for_scores(mixed_value_layer) - #这段代码是注意力机制中的一部分,涉及到对查询(queries)和键(keys)进行频域变换(FFT),以应用频域注意力 # B, H, L, E = query_layer.shape # AutoFormer B, L, H, E = queries.shape - # print("qqqq", queries.shape) [256,50,2,32,] _, S, _, D = values.shape if L > S: zeros = torch.zeros_like(queries[:, :(L - S), :]).float() @@ -558,7 +527,7 @@ def forward(self, input_tensor, attention_mask): k_fft = torch.fft.rfft(keys.permute(0, 2, 3, 1).contiguous(), dim=-1) - # 装到采样的空盒子里 + # put into an empty box for sampling q_fft_box = torch.zeros(B, H, E, len(self.q_index), device=q_fft.device, dtype=torch.cfloat) q_fft_box = q_fft[:, :, :, self.q_index] @@ -584,12 +553,11 @@ def forward(self, input_tensor, attention_mask): V = self.time_delay_agg_inference(values.permute(0, 2, 3, 1).contiguous(), corr).permute(0, 3, 1, 2) - # print(V.shape) new_context_layer_shape = V.size()[:-2] + (self.all_head_size,) context_layer = V.view(*new_context_layer_shape) if self.dual_domain: - # 装到采样的空盒子里 + # put into an empty box for sampling # q q_fft_box = torch.zeros(B, H, E, len(self.time_q_index), device=q_fft.device, dtype=torch.cfloat) q_fft_box = q_fft[:, :, :, self.time_q_index] @@ -605,14 +573,13 @@ def forward(self, input_tensor, attention_mask): # v v_fft = torch.fft.rfft(values.permute(0, 2, 3, 1).contiguous(), dim=-1) - # 装到采样的空盒子里 + # put into an empty box for sampling v_fft_box = torch.zeros(B, H, E, len(self.time_v_index), device=v_fft.device, dtype=torch.cfloat) v_fft_box = v_fft[:, :, :, self.time_v_index] spatial_v = torch.zeros(B, H, E, L // 2 + 1, device=v_fft.device, dtype=torch.cfloat) spatial_v[:, :, :, self.time_v_index] = v_fft_box - queries = torch.fft.irfft(spatial_q, dim=-1) keys = torch.fft.irfft(spatial_k, dim=-1) values = torch.fft.irfft(spatial_v, dim=-1) @@ -627,7 +594,7 @@ def forward(self, input_tensor, attention_mask): attention_scores = attention_scores + attention_mask attention_probs = nn.Softmax(dim=-1)(attention_scores) attention_probs = self.attn_dropout(attention_probs) - qkv = torch.matmul(attention_probs, values) # [256, 2, index, 32] + qkv = torch.matmul(attention_probs, values) context_layer_spatial = qkv.permute(0, 2, 1, 3).contiguous() new_context_layer_shape = context_layer_spatial.size()[:-2] + (self.all_head_size,) context_layer_spatial = context_layer_spatial.view(*new_context_layer_shape) From c7893a4514990aea8f16e34d4d9759f63dff0161 Mon Sep 17 00:00:00 2001 From: TayTroye Date: Fri, 27 Oct 2023 03:19:13 +0000 Subject: [PATCH 4/4] Format Python code according to PEP8 --- .../model/sequential_recommender/fearec.py | 32 ++++++++++++------- 1 file changed, 20 insertions(+), 12 deletions(-) diff --git a/recbole/model/sequential_recommender/fearec.py b/recbole/model/sequential_recommender/fearec.py index 8fc9e13e1..5f2854117 100644 --- a/recbole/model/sequential_recommender/fearec.py +++ b/recbole/model/sequential_recommender/fearec.py @@ -1,5 +1,5 @@ # -*- coding: utf-8 -*- -# @Time : 2023/10/27 +# @Time : 2023/10/27 # @Author : Kesha Ou # @Email : keishaou@gmail.com @@ -181,7 +181,9 @@ def forward(self, item_seq, item_seq_len): extended_attention_mask = self.get_attention_mask(item_seq) # extended_attention_mask = self.get_bi_attention_mask(item_seq) - trm_output = self.item_encoder(input_emb, extended_attention_mask, output_all_encoded_layers=True) + trm_output = self.item_encoder( + input_emb, extended_attention_mask, output_all_encoded_layers=True + ) output = trm_output[-1] output = self.gather_indexes(output, item_seq_len - 1) @@ -212,9 +214,9 @@ def calculate_loss(self, interaction): print("error") while True: sample_index = random.choice(targets_index) - cur_item_list = interaction[self.ITEM_SEQ][i].to('cpu') + cur_item_list = interaction[self.ITEM_SEQ][i].to("cpu") sample_item_list = dataset.inter_feat[self.ITEM_SEQ][sample_index] - are_equal = torch.equal(cur_item_list,sample_item_list) + are_equal = torch.equal(cur_item_list, sample_item_list) sample_item_length = dataset.inter_feat[self.ITEM_SEQ_LEN][sample_index] if not are_equal or lens == 1: sem_pos_lengths.append(sample_item_length) @@ -292,7 +294,7 @@ def calculate_loss(self, interaction): ) loss += self.lmd_sem * self.aug_nce_fct(sem_nce_logits, sem_nce_labels) - + # frequency domain loss if self.fredom: seq_output_f = torch.fft.rfft(seq_output, dim=1, norm="ortho") @@ -509,9 +511,12 @@ def __init__( self.spatial_ratio = self.config["spatial_ratio"] def transpose_for_scores(self, x): - new_x_shape = x.size()[:-1] + (self.num_attention_heads, self.attention_head_size) - x = x.view(*new_x_shape) - # return x.permute(0, 2, 1, 3) + new_x_shape = x.size()[:-1] + ( + self.num_attention_heads, + self.attention_head_size, + ) + x = x.view(*new_x_shape) + # return x.permute(0, 2, 1, 3) return x def time_delay_agg_training(self, values, corr): @@ -609,9 +614,10 @@ def forward(self, input_tensor, attention_mask): q_fft = torch.fft.rfft(queries.permute(0, 2, 3, 1).contiguous(), dim=-1) k_fft = torch.fft.rfft(keys.permute(0, 2, 3, 1).contiguous(), dim=-1) - # put into an empty box for sampling - q_fft_box = torch.zeros(B, H, E, len(self.q_index), device=q_fft.device, dtype=torch.cfloat) + q_fft_box = torch.zeros( + B, H, E, len(self.q_index), device=q_fft.device, dtype=torch.cfloat + ) q_fft_box = q_fft[:, :, :, self.q_index] k_fft_box = torch.zeros( @@ -670,7 +676,9 @@ def forward(self, input_tensor, attention_mask): # v v_fft = torch.fft.rfft(values.permute(0, 2, 3, 1).contiguous(), dim=-1) # put into an empty box for sampling - v_fft_box = torch.zeros(B, H, E, len(self.time_v_index), device=v_fft.device, dtype=torch.cfloat) + v_fft_box = torch.zeros( + B, H, E, len(self.time_v_index), device=v_fft.device, dtype=torch.cfloat + ) v_fft_box = v_fft[:, :, :, self.time_v_index] spatial_v = torch.zeros( B, H, E, L // 2 + 1, device=v_fft.device, dtype=torch.cfloat @@ -691,7 +699,7 @@ def forward(self, input_tensor, attention_mask): attention_scores = attention_scores + attention_mask attention_probs = nn.Softmax(dim=-1)(attention_scores) attention_probs = self.attn_dropout(attention_probs) - qkv = torch.matmul(attention_probs, values) + qkv = torch.matmul(attention_probs, values) context_layer_spatial = qkv.permute(0, 2, 1, 3).contiguous() new_context_layer_shape = context_layer_spatial.size()[:-2] + ( self.all_head_size,