From f7ac40bce1ce2b2f5f9eb897fb3f3cba8150d35a Mon Sep 17 00:00:00 2001 From: GodK <38203359+gaohongkui@users.noreply.github.com> Date: Wed, 14 Jul 2021 02:30:15 -0500 Subject: [PATCH 1/3] =?UTF-8?q?=E4=BF=AE=E5=A4=8D=20#106=20=E4=BD=BF?= =?UTF-8?q?=E7=94=A8acc=E6=97=A0=E6=B3=95=E4=BF=9D=E5=AD=98=E6=A8=A1?= =?UTF-8?q?=E5=9E=8B=E7=9A=84bug=20(#196)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 解决了 #106 无法保存模型的bug。 原因是lambda表达式无法被pickled。 在`basemodel.py`中的`accuracy`使用了lambda表达式 https://github.com/shenweichen/DeepCTR-Torch/blob/b4d8181e86c2165722fa9331bc16185832596232/deepctr_torch/models/basemodel.py#L492-L494 https://github.com/shenweichen/DeepCTR-Torch/issues/106#issuecomment-862417549 给出了复现方法 --- deepctr_torch/models/basemodel.py | 8 ++++++-- tests/savemodel_with_acc_test.py | 34 +++++++++++++++++++++++++++++++ 2 files changed, 40 insertions(+), 2 deletions(-) create mode 100644 tests/savemodel_with_acc_test.py diff --git a/deepctr_torch/models/basemodel.py b/deepctr_torch/models/basemodel.py index 4235ad38..07d6cb9c 100644 --- a/deepctr_torch/models/basemodel.py +++ b/deepctr_torch/models/basemodel.py @@ -15,6 +15,7 @@ import torch.nn.functional as F import torch.utils.data as Data from sklearn.metrics import * +from sklearn.metrics import accuracy_score from torch.utils.data import DataLoader from tqdm import tqdm @@ -476,6 +477,10 @@ def _log_loss(self, y_true, y_pred, eps=1e-7, normalize=True, sample_weight=None sample_weight, labels) + @staticmethod + def _accuracy_score(y_true, y_pred): + return accuracy_score(y_true, np.where(y_pred > 0.5, 1, 0)) + def _get_metrics(self, metrics, set_eps=False): metrics_ = {} if metrics: @@ -490,8 +495,7 @@ def _get_metrics(self, metrics, set_eps=False): if metric == "mse": metrics_[metric] = mean_squared_error if metric == "accuracy" or metric == "acc": - metrics_[metric] = lambda y_true, y_pred: accuracy_score( - y_true, np.where(y_pred > 0.5, 1, 0)) + metrics_[metric] = self._accuracy_score self.metrics_names.append(metric) return metrics_ diff --git a/tests/savemodel_with_acc_test.py b/tests/savemodel_with_acc_test.py new file mode 100644 index 00000000..aa3a7d71 --- /dev/null +++ b/tests/savemodel_with_acc_test.py @@ -0,0 +1,34 @@ +""" +Date: 2021-07-13 11:59:44 +LastEditors: GodK +LastEditTime: 2021-07-13 12:26:11 +""" +import pytest +from deepctr_torch.models import DeepFM +from deepctr_torch.callbacks import EarlyStopping, ModelCheckpoint +from .utils import get_test_data, SAMPLE_SIZE,get_device + +@pytest.mark.parametrize( + 'use_fm,hidden_size,sparse_feature_num', + [(True, (32,), 3), + (False, (32,), 3), + (False, (32,), 2), (False, (32,), 1), (True, (), 1), (False, (), 2) + ] +) +def test_save(use_fm, hidden_size, sparse_feature_num): + sample_size = SAMPLE_SIZE + x, y, feature_columns = get_test_data( + sample_size, sparse_feature_num=sparse_feature_num, dense_feature_num=sparse_feature_num) + model = DeepFM(feature_columns, feature_columns, use_fm=use_fm, + dnn_hidden_units=hidden_size, dnn_dropout=0.5, device=get_device()) + + # test callbacks + early_stopping = EarlyStopping(monitor='val_acc', min_delta=0, verbose=1, patience=0, mode='max') + model_checkpoint = ModelCheckpoint(filepath='model.ckpt', monitor='val_acc', verbose=1, + save_best_only=True, + save_weights_only=False, mode='max', period=1) + model.compile('adam', 'binary_crossentropy', metrics=['binary_crossentropy','acc']) + model.fit(x, y, batch_size=64, epochs=3, validation_split=0.5, callbacks=[early_stopping, model_checkpoint]) + +if __name__ == "__main__": + pass \ No newline at end of file From 2d56269259197ad67360a21fce0477c57b454818 Mon Sep 17 00:00:00 2001 From: Jason Zan Date: Sat, 18 Jun 2022 16:47:56 +0800 Subject: [PATCH 2/3] 1. fix the bug #192 which is introduced in ac9ea22 (#246) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 2. 完善测试用例,补充无linear part、无dense feature、无sparse feature的用例 3. update doc examples --- deepctr_torch/models/basemodel.py | 3 ++- docs/source/FAQ.md | 4 ++-- tests/models/DeepFM_test.py | 28 ++++++++++++++++++++++------ 3 files changed, 26 insertions(+), 9 deletions(-) diff --git a/deepctr_torch/models/basemodel.py b/deepctr_torch/models/basemodel.py index 07d6cb9c..d8b8fb36 100644 --- a/deepctr_torch/models/basemodel.py +++ b/deepctr_torch/models/basemodel.py @@ -3,6 +3,7 @@ Author: Weichen Shen,weichenswc@163.com + zanshuxun, zanshuxun@aliyun.com """ from __future__ import print_function @@ -76,7 +77,7 @@ def forward(self, X, sparse_feat_refine_weight=None): sparse_embedding_list += varlen_embedding_list - linear_logit = torch.zeros([X.shape[0], 1]).to(sparse_embedding_list[0].device) + linear_logit = torch.zeros([X.shape[0], 1]).to(self.device) if len(sparse_embedding_list) > 0: sparse_embedding_cat = torch.cat(sparse_embedding_list, dim=-1) if sparse_feat_refine_weight is not None: diff --git a/docs/source/FAQ.md b/docs/source/FAQ.md index 102e35bc..1006bf42 100644 --- a/docs/source/FAQ.md +++ b/docs/source/FAQ.md @@ -6,7 +6,7 @@ To save/load weights: ```python import torch -model = DeepFM() +model = DeepFM(...) torch.save(model.state_dict(), 'DeepFM_weights.h5') model.load_state_dict(torch.load('DeepFM_weights.h5')) ``` @@ -15,7 +15,7 @@ To save/load models: ```python import torch -model = DeepFM() +model = DeepFM(...) torch.save(model, 'DeepFM.h5') model = torch.load('DeepFM.h5') ``` diff --git a/tests/models/DeepFM_test.py b/tests/models/DeepFM_test.py index b11b5cb4..5a333af4 100644 --- a/tests/models/DeepFM_test.py +++ b/tests/models/DeepFM_test.py @@ -6,21 +6,37 @@ @pytest.mark.parametrize( - 'use_fm,hidden_size,sparse_feature_num', - [(True, (32,), 3), - (False, (32,), 3), - (False, (32,), 2), (False, (32,), 1), (True, (), 1), (False, (), 2) + 'use_fm,hidden_size,sparse_feature_num,dense_feature_num', + [(True, (32,), 3, 3), + (False, (32,), 3, 3), + (False, (32,), 2, 2), + (False, (32,), 1, 1), + (True, (), 1, 1), + (False, (), 2, 2), + (True, (32,), 0, 3), + (True, (32,), 3, 0), + (False, (32,), 0, 3), + (False, (32,), 3, 0), + (True, (), 0, 1), + (True, (), 1, 0), + (False, (), 0, 2), + (False, (), 2, 0), ] ) -def test_DeepFM(use_fm, hidden_size, sparse_feature_num): +def test_DeepFM(use_fm, hidden_size, sparse_feature_num, dense_feature_num): model_name = "DeepFM" sample_size = SAMPLE_SIZE x, y, feature_columns = get_test_data( - sample_size, sparse_feature_num=sparse_feature_num, dense_feature_num=sparse_feature_num) + sample_size, sparse_feature_num=sparse_feature_num, dense_feature_num=dense_feature_num) model = DeepFM(feature_columns, feature_columns, use_fm=use_fm, dnn_hidden_units=hidden_size, dnn_dropout=0.5, device=get_device()) check_model(model, model_name, x, y) + # no linear part + model = DeepFM([], feature_columns, use_fm=use_fm, + dnn_hidden_units=hidden_size, dnn_dropout=0.5, device=get_device()) + check_model(model, model_name + '_no_linear', x, y) + if __name__ == "__main__": pass From 300f1158952c25ddec8ee1e454f06c1154421f82 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E6=B5=85=E6=A2=A6?= Date: Sun, 19 Jun 2022 17:19:46 +0800 Subject: [PATCH 3/3] update docs (#247) --- .github/workflows/ci.yml | 22 ++++++---- README.md | 58 +++++++++---------------- deepctr_torch/__init__.py | 2 +- deepctr_torch/models/basemodel.py | 1 - docs/pics/code2.jpg | Bin 0 -> 53499 bytes docs/pics/planet_github.png | Bin 0 -> 8309 bytes docs/requirements.readthedocs.txt | 3 +- docs/source/History.md | 3 +- docs/source/conf.py | 2 +- docs/source/index.rst | 9 ++-- setup.py | 5 ++- tests/models/DeepFM_test.py | 4 -- tests/savemodel_with_acc_test.py | 34 --------------- tests/utils.py | 68 ++++++++++++++++-------------- 14 files changed, 87 insertions(+), 124 deletions(-) create mode 100644 docs/pics/code2.jpg create mode 100644 docs/pics/planet_github.png delete mode 100644 tests/savemodel_with_acc_test.py diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index fc3cdae4..4dad4ba1 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -17,19 +17,25 @@ jobs: timeout-minutes: 120 strategy: matrix: - python-version: [3.6,3.7] - torch-version: [1.1.0,1.2.0,1.3.0,1.4.0,1.5.0,1.6.0,1.7.0,1.8.1] + python-version: [3.6,3.7,3.8] + torch-version: [1.1.0,1.2.0,1.3.0,1.4.0,1.5.0,1.6.0,1.7.1,1.8.1,1.9.0,1.10.2,1.11.0] -# exclude: -# - python-version: 3.5 -# tf-version: 1.1.0 + exclude: + - python-version: 3.6 + torch-version: 1.11.0 + - python-version: 3.8 + torch-version: 1.1.0 + - python-version: 3.8 + torch-version: 1.2.0 + - python-version: 3.8 + torch-version: 1.3.0 steps: - - uses: actions/checkout@v1 + - uses: actions/checkout@v3 - name: Setup python environment - uses: actions/setup-python@v1 + uses: actions/setup-python@v4 with: python-version: ${{ matrix.python-version }} @@ -47,7 +53,7 @@ jobs: pip install -q sklearn pytest --cov=deepctr_torch --cov-report=xml - name: Upload coverage to Codecov - uses: codecov/codecov-action@v1.0.2 + uses: codecov/codecov-action@v3.1.0 with: token: ${{secrets.CODECOV_TOKEN}} file: ./coverage.xml diff --git a/README.md b/README.md index 7c646933..6d02554e 100644 --- a/README.md +++ b/README.md @@ -47,34 +47,19 @@ Let's [**Get Started!**](https://deepctr-torch.readthedocs.io/en/latest/Quick-St ## DisscussionGroup & Related Projects - - - - - - - -
- 公众号:浅梦学习笔记

- - - -
- 微信:deepctrbot

- - - -
- -
- +- [Github Discussions](https://github.com/shenweichen/DeepCTR/discussions) +- Wechat Discussions +|公众号:浅梦学习笔记|微信:deepctrbot|学习小组 [加入](https://t.zsxq.com/026UJEuzv) [主题集合](https://mp.weixin.qq.com/mp/appmsgalbum?__biz=MjM5MzY4NzE3MA==&action=getalbum&album_id=1361647041096843265&scene=126#wechat_redirect)| +|:--:|:--:|:--:| +| [![公众号](./docs/pics/code.png)](https://github.com/shenweichen/AlgoNotes)| [![微信](./docs/pics/deepctrbot.png)](https://github.com/shenweichen/AlgoNotes)|[![学习小组](./docs/pics/planet_github.png)](https://t.zsxq.com/026UJEuzv)| +- Related Projects + + - [AlgoNotes](https://github.com/shenweichen/AlgoNotes) + - [DeepCTR](https://github.com/shenweichen/DeepCTR) + - [DeepMatch](https://github.com/shenweichen/DeepMatch) + - [GraphEmbedding](https://github.com/shenweichen/GraphEmbedding) ## Main Contributors([welcome to join us!](./CONTRIBUTING.md)) @@ -84,59 +69,58 @@ Let's [**Get Started!**](https://deepctr-torch.readthedocs.io/en/latest/Quick-St ​ pic
Shen Weichen ​ -

Core Dev
Zhejiang Unversity

​ +

Alibaba Group

​ ​ pic
Zan Shuxun -

Core Dev
Beijing University
of Posts and
Telecommunications

​ +

Alibaba Group

pic
Wang Ze ​ -

Core Dev
Beihang University

​ +

Meituan

​ ​ pic
Zhang Wutong -

Core Dev
Beijing University
of Posts and
Telecommunications

​ +

Tencent

​ ​ pic
Zhang Yuefeng -

Core Dev
- Peking University

​ +

Peking University

​ ​ pic
Huo Junyi -

Core Dev
+

University of Southampton

​ ​ pic
Zeng Kai ​ -

Dev
+

SenseTime

​ ​ pic
Chen K ​ -

Dev
+

NetEase

​ ​ pic
Cheng Weiyu ​ -

Dev
+

Shanghai Jiao Tong University

​ ​ pic
Tang -

Test
+

Tongji University

​ diff --git a/deepctr_torch/__init__.py b/deepctr_torch/__init__.py index 88508515..4be7a5bc 100644 --- a/deepctr_torch/__init__.py +++ b/deepctr_torch/__init__.py @@ -2,5 +2,5 @@ from . import models from .utils import check_version -__version__ = '0.2.7' +__version__ = '0.2.8' check_version(__version__) \ No newline at end of file diff --git a/deepctr_torch/models/basemodel.py b/deepctr_torch/models/basemodel.py index d8b8fb36..17e57b90 100644 --- a/deepctr_torch/models/basemodel.py +++ b/deepctr_torch/models/basemodel.py @@ -16,7 +16,6 @@ import torch.nn.functional as F import torch.utils.data as Data from sklearn.metrics import * -from sklearn.metrics import accuracy_score from torch.utils.data import DataLoader from tqdm import tqdm diff --git a/docs/pics/code2.jpg b/docs/pics/code2.jpg new file mode 100644 index 0000000000000000000000000000000000000000..e191f2971ebdfcc60981e4b680e3331735874744 GIT binary patch literal 53499 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 DQr=M~ literal 0 HcmV?d00001 diff --git a/docs/pics/planet_github.png b/docs/pics/planet_github.png new file mode 100644 index 0000000000000000000000000000000000000000..67efe9684c29f66878f45514e84a7e61e66b1284 GIT binary patch literal 8309 zcmZ{IRa6vg)b$KCq;z+8m%z|SNFzwMlnfvsG9%rfA`S>BDbga{HFS4(Hw-Xz3H-eO z#kanj@8UdXuXEOV&N^|kcZ{Bn8UZd1E&u=^&`?(~c=82L_6Hm7>8)8y*aZOaU0sxw z^)!@~ne@Co99f>lIWp(}_ zGDJG)w^e`Udi~x=Wvm)dQ4l?AO6F(HSHN{(r>JkZ9FFmR$r#~_@HqN+HTd)KyNvfS zJwVK_omYJ^ghDquKqKsVnBc~i;vzNx0q2BoqbnHh^5<=9l0gMJ8w{wJI$g=0YsUEU zzKbN0#GhsBEN4dG2h$`DBsv0rc@pmb#BAd0%|4;Fa}u^@!&30Z=mdh9lL^aNE{S2V z1oAq3J9B5j5UOtUCQ-!3;4N)XH<5gDs(5%Jj3C-9TAibBOXpEu^Q?tk&{%G_gGSV+>u_g2v4TSBFuD-{v9@jzl0}tA$($14wXjIQaW3B#eLr^OnL65O5V4Y zw$Gr$bXYXI9#y$k3$BG@m28znI?1B>@l-U%h+GwrYy&ZIhI3zd$SSfyZaJsVU;IlV z>cQz;YkL{^m+2J^MSg|ga00>hXE*Xs&&PQDg{*%NW&I_b9^81=N$A}sfYMp39Kc;S@;=G&uX~>O z51Pj&FQG`yY4u}>p1$Y{$Qt+?KpPI=_|}mGy*>PM80y;(7iz^c_=btms-H`p z1_ukVM9x6JTMIyr1dlVOx zAascQsyhJd?AlQm`qG&d%woY=Me43gGL-y4v_&=g?54x8y7wAEQ7686-Wui^)cfqG zCzd-lou}_^R0K$Agh6lMbysl#?iuK)#R0W6p*IIl@8g0|Nkv2MHna%*2;!J2UKF>K z#)(XS$@*9sr5boX@=$W?KdbiO9K+xJ%+{$qVHWlmScIvC#CRQ5ZPP;rb@q{^Hn9v_ zUdLMR3jzNQx~BE7`1G89Prz((uN8N_)qW$`z=~8uB@z}v?*_Jf;$}F-PHz7%vpq#;#_CQ)y8us zQ*imztt$n4;Aq!P;0NQB0cwO8jaPC0DG*^}W-H>W1GsHizF1+;)RSppinf9EFkZIe zmQg?e=B*#fXp!h3MSf=hXB+i9VhgO9Hp{w?(yf}K1j}J^7>erH1X-+D(cE?s&zVZ1 zB}szAS@hNE;zM&-O{1yctfFvCb({*^xnPJg58N7_-K(()9**bpN6_Wl^gs;E9%}~^G1BCwzr&BON*ZHx zt3%gD|CpC!gV;y0RWU%Jf+5OOYP~F4>}13*ab$4Ru=Cff_L-VENn-GS6O7>8&`aiOX+S=Lug#!hR1+kM0leJsONn<=N`ObvS%{1=`I0?RDS=)bAtyhihCXY2vd85s%owThoq%>qOq@0p0 z)>zeRHf;vsFPVlEdlhG!NmhTX+M5@c7oLaBYgS)5XU+tcD36y<<4mm`@J-H@c)#2c zrq4DXoqNv^01N=#i#01J7r15*j15imk1gdd{8=c@&)z9X&XLqEwK)D^C4+8nBKrJ` zMYmVCVmCo|zHN7hNe4zp5`GcG2ZveE{o1YKE!w@^{r&CCg*>$off9ii{%e{^nnlS@ zfc(;n~%{q;I?j^BS!#54x=iz&Jc?=0%gAPf;sXvz-RUL^Py(--tiyZ^DCAMR> z&xWp2S89KmK+WHn{VjK{*sT$(rn8rtsOUfaNnJd$d&fs&B2T5>gf@; zbGPRb5fVlcR}hz)keXKzUP$R2@nO@U7gA!*vPSJt78!B5b(u`32hs(>J2;|hh{K8H zSUXvHR2SdpNLSF|(G}4#N(-m`PTQ85l5VDPkrMIQ?1ww41^QgR8}?aV^PhRq<4`r= zpZqadvgEP^JmWRv>Mm9*R4e7~<6gQ%zleF1y5s>HmF!c| zyqe$jnJ}M`nf5EZl41N8Q$N$v(Dbm}HnmXsvVghD*;r;1 zb*Q?hx@&=sv)yjwD9Co(*3{O>c4>`aO|+es6`L4N%**QT^+gJ-!=CR~HM47)oyuAW zKY`P-Fus^m)5zZM`DL#fXDYjGRb^E5yg>r1^PS!6p2fK8m~@i61(WsJ+43#>%$OK5nL_>O12SNiC6n{hs}Nac=M z%NdqXE}Wg(vt7K=ya_s)LCIh~gc^l9{e1XYB$DZL<5WDnPlko78DYs%;~ci@^Fie@ z2}vj=EY;|B>z&|zwNtp2yF*WY5bfPLl%UO_o4Uy1`Hf6tKL1+BR3_J)hI@%y`3>nC zpPL3$KH`Nt@1w>;8Pl&{O}~8Kj!!C2ROQFyS8LT~BHr9qwwJYAJFbdCMN=G8hSDU@ z*7&CXx>dsGs4G*qO$JS$=i9qqFD{*Noq28F%dNm3{@ff>un87eJ2$;MYVVPccr-N1 z(P!gtsPFX{OX|9|8(W`dch{U2yfGUTMCh$s7?_>HxIOkFGA#oI?&^a~w=1T%G6p0| znM#OC>&ivmZ5zMf=CY#?6NcT(MG+b5`R5V;99T@+8 zY*MUS`v`k?^AM{_n+*9^7SpoVzx``y%KS_1cgW4tZZD>5P;TPn)8x4eF$;J#@=Nfl z4W!_5i{X*jqG`q1fWS$~<&tM(DsM9n?-kXdsa3@5z9Q+Xyu;=u!8g{H)%F)P_h0VW zL9M`>5XzRk$B!E}n{9c@%Ta#_&FPn+;ek1IScN~WCM! zlMmh-ybH!rkGQu*xqcSDJ+6BP50aWIG)+qv!}pf2bRi>QSFqzdIkGF7^P`8|`x9jT zrtZjXULZ$+z@6Py?t|;*jNd)%{+^`dy9|mqxGmDr5e?-?38*3i?4lM`sY}6l&#-@k zTb?nYGRsbzo-<_t3_EmAXh+;JRsoP{Z)QozC+q8Qrr?Hp0`bINktK^n3qvRg%lYM1 zi#S%WAz>?}OHj~C-ot~Sj7?cB&^hflIE*J8dj#M7o_x1KE=b^7@%zrivDd$H&?oH0 zwz&Hiz$e+bCHIv-giq|_xr2#@qqa7H=Lus2fKe_0j3)$qGMXm?0MLG+{ZFLe7yAF; zKmR4U!XaM(0MZZ*6-7fR@X*5b%bOk}#K_U!-VCzogVRp=vN5FRLz8WKmkP$Te(-8< z2$u-P3CX8xpi(#&u{zTR;FDe;HLnu3-WP0nwAbIm*(mx|IcPaeOA{TMV-5p44+4gc zhLKH-_Xt5mgRDhUKG@9O2 z;OU!a39+hq_ERP8i{eNf5*TI^n?lWgBpPBWT7#uDI2oh&^^#s|Ese0oCU?=!(kCcW zP|+a;b1FG5b}jfStef7b8oQt`sUKOrPuTMH;i`&;`qJJ>@+uOfzf)j_#R zBZv&8jrCGOSh#4XZ=XIINNuR@4g+RIx)U+M**M^h3+6`DEzKuLYB${LjF7i}1V*hM z9$-J!R5XOsuX!uw3x@ldA{@f?K6)HdIH#}vmUz`zgz-W(KhH)ZManSEQ%ey4#@CH# z2-mE=zqYv7!`-#6)ldEYmsB;C)pdw?UrUOnln;}k^(Ch1^WAAgiKM$TyW?uJoCm#9Air8@Kv$3#ChF0$291eG3CI{gcrqTq?QhUG1B;abBGD!_s5#+*NI9&Pkpr zzCS%x`PF8Uy2K(Kfg1y!teIzzH>Pf(=8CW7U1bMBMX$(EsV`JdKVUX(0z9@e&;l!a z)t7ZlWk+al04jsuT8uoHUp^gD>jAUnf6kSO-+V@aOyU)L7_J+~Db?>-fmnjiWPiSR z&~dpO^Q`7Z)Rf!fUU#3sFH6v@%$#XuRQoLL_iioQ6PP@;q5R(8v1l zh$2)Nd^ISof}K~3ln9D(QOe&ameoa6*aROAE!gI_BGeu>KbMV)?I$}@T~UX)r*Kgv zWj)S2A?$t@R1Am^EQo2ns^w?3&2r=*y>#9erj2+*!e78aGM#LlR<5U$5`Z%~Xf1ym zp041rR8{4KIC+z-rtF*nZWYz~5)3S*m2O1SoB@{FI$uys%-1|t>+PYTxx-OcAZ_@s- zq7;nQNWbhr2uk>D08SGGgIQ=}^GL5lwo~N}+8NN3hd|ANedu8HABv1jTf%$x^PpRN zuN*1#Ajv@@j8u#u^9O#}Z_)0a3QA1AJ4m1qRYymJn1Tez zIh4=w{5I#S)bbe$uqRG15nL!mOFd#m*Gu7 zFR3n@gZePQ3buW3nPbHH))Z*_1~}YWV;+UmqbTb#GAfGS7DH?_M@WK{v3!ACevYWV zB%B=Chkd^f4&wnIIUE*Eyru^j%o#pD#^XS1@Mi=kw1R)zsu@S61f=u^ev{Qg$9Ig< zp2hkQY@3bn<2r^7PdW!W!iRuHGsn8@mNNm@=`!#$(t3g%weGT?_IQt#r$F7hR&!}8 z;_zEb1Rn8fA~xo=Kzq`=p=WdS_8-)Zn#ww|gL4`2q~gB^%tykj-cu3+9Q{-e&}vA9 ziY>Zn`W=0r<$NG;v{(Xruich5IZbwcrY#tA-$8l&A?=CKIYBFaON6EPTqQ>_(zNIZ zpGx|Uuy6R680oLw2B$e7$A?XEjsU#UH~clRVfYN8u_NEwtTT8s>cwgePQ2!F$T8v= zoAHVwusDdiG#Wy|uVgEz=CD=THr)P+ceMEY5I&5uNN*mQD4S+oqQ0C>wSA2G$yoMV zt=Pi*w9wC&m4k#8ae_U74i@>Ljba|Z7DL@84c)9g%V=)5y+Uy$i;G-SZ`r?kQUI!3 z14i0n?yel&_C)+dqc(vI(RR(<=fhM@IL{p8p5E@X(%H6{h&yy?Pb>ce~afb zzJyk0T*$#UM>GTMd<$1gR}6!;b}M|U1~rJ*l^KI9Fc@vQ$Fb4YSC76bDzPElBVZlAG-HZ{X>(IG3D_gmV4;U0apuS_M zoK1)C-t0R4-F_wGz@_rv3U#0 zMmmSFCh*qgU1=YFd}Mq$M4F+7l0BL|i=ZQ&tzG7rqjy^d1V3hM&;8v#U>X%$oQ+;x z*GrWJLGFa_vjNl`yME2FFz05lp~Syai9ut;f~e%-It7a#->)h) z@^N(8pQ>T`VK(wrjX%I_OAgHLVuTKYi2<56`uFE^%DhH!k3+UHdZd z^~6&_kIL{aecallvO_NyawI-9$@Rf*u0^ai`rf*&EsICqf28L(&@5ONU>!HMiE8cq zC7v;nz_&5SpLE?EB&@qx7YJ_la{u6vYv%Wm=zcMo}*(f?k&AyrP`*nDxtS(e#`uCHf(5vsjk1!EA6c2Zq z5Tp(*4KAYURT$)VTlsY(JYV#ET23L<+1Kj1jIn2VRL52%Nc`J=uUFCvB#I%Urbr&h zw$Oe3+C!MXIVPQ#|sRxYI(Yz-nJNvh8BaOXO5M7fp|o6#_2erG*+V+ z&sAgi{a!pS5i-c#wh9GmhwRVjTK1UIs7{|0D6|V1*V7RdgFl+`bOrwg z950Lnt(IHM#SdPmWE|@DpU7D=tm;}ciH~L?D~k)qT1}?()ySFpyZ@ zhgByF4jcOK++Wj$U3hXWhKaiJBD{Lx1$&WR!)xp ztu_i$wLF$vkNf8fy3D*4xV*Ufy5*UIuqf0aaOAURb?z*6{KA%m*9f^If-B*I%l}6G z+1jF&2b=cqfc|8-&P%KcZlK5jk|+DaZN?R)=>c0PR?u*|9Bdy+WHq~w~Z62v|JRzg% zy;+ApFw900kY~!DG;N#|wojv;ehdq|{>-^@RbtV6JLoMadqbJos?z!aCKpIWx{55` za@AN;zv~LDjhrbr&U50x0vT^_UmXw9(J>a;O_vQ>|EupXtM89zJU;n6%X#^aUAH^c zRg&T}RDPxQb-^ZFu>Qk$>4cY&?&MD z&#yb*;*~jX=^+FC%`s_ySnI%i+vaSqjX;2B&BuSaXC^c#5kaAN*RHo8)JD)(hI!4` z#YXg4uoT%AN^qlsTgP3e2e&jKYtu__9Cst^d;CGxqc#t!48}`;@?p}?9R9U!Fa{3& z;7OjYLWDg0JNjiXFP@@%6-N5Qo43oL(7-b+EWtixC#|J^@Ixq4(z|VPcJ*S8z3Ha9 zeP!JL;4BEkI@(C7GzQEVtK-ZRrmEbVa57VxVa+{#OJ8YN=SVw6=>P8VCbeDU}Fva|;;wuD1y4I$^y1?^m8*qDA1GDBTh+KHs=pAWqpw1D3}`KbQjx+0LR9htE z-u)^D&9Ys1At^N2`4FQaXX;n-f?-v*d1bUHfZNrxeAjPEwA8|Dc4XDHS6wC$Qp9>=z;#qjL7?~_rO^PwTDafx^d~yP~hW*q+=9TLRb8U zb4j_VApNbu+J|`wek2~=*1oR*!|21gq~9|ayE1U=v6M)?nz8_*=*YI}2XR#P{^4iq zw@MwgNYL*_KZg2y%3j7Gwgz@J#f-x=I_RRXpmX<1251mp&L887=(HfBw za2d;%(vGS$1iMP#Z3-*P|4j9X8lekPH*l>wD3lX0lA+u%v?B#@q11Yi4$#XZ=xys6 zrd8flNNq_-?Voajc4AmWyMRjY%;+rJBD-a@l2x#AuP?JmDo(){&X7-@h(0T?y2un?O0=JP1K1#K4FwTm;=r?3KB zsfZlzizZy$Yv>w1tSC56iMTWrP0k9cw(Q=)y`VlLK@lz%^`$F;CJSr)1(&WWB>SZ* zL>0aK(~JT}xHKyP&qhRf{3`{ZAZLkEDH*DrsE_+sa5AZJmN72WHhPncsHLJAJy6Y1 zl;*QXjQbQ`_)t6#v1+0*7qXp~nP0_;7FUDv_c#0~9=ICbUOOJ@<^B^lmi+u>^AH!> z2V5SM#r)?8hc{UQ1*KY3>(%<`1=V)Z1xHmi&**YWnv7t}Y*|>#+P0J;FKKD61ypvX zi&6=)L;?@W4>bs$IC;3+fhzB6Ekj4fOb zKDz`;`9BSb1(tZ?FJwRVJ2(z=uqgo-D#uaotl!#$=D6G^a9<07DH{!3swz1AyX>xX ks^VVt!}@b5ejWr&sK+u{=a$9!Pn6Smp`%iwWE1-T0BSZF_5c6? literal 0 HcmV?d00001 diff --git a/docs/requirements.readthedocs.txt b/docs/requirements.readthedocs.txt index 80bc5e6d..793412bd 100644 --- a/docs/requirements.readthedocs.txt +++ b/docs/requirements.readthedocs.txt @@ -1,2 +1,3 @@ Cython>=0.28.5 -tensorflow==1.15.4 +tensorflow==2.7.2 +scikit-learn==1.0 diff --git a/docs/source/History.md b/docs/source/History.md index ec68a102..4984dfc4 100644 --- a/docs/source/History.md +++ b/docs/source/History.md @@ -1,5 +1,6 @@ # History -- 06/14/2021 : [v0.2.7](https://github.com/shenweichen/DeepCTR-Torch/releases/tag/v0.2.6) released.Add [AFN](./Features.html#afn-adaptive-factorization-network-learning-adaptive-order-feature-interactions) and fix some bugs. +- 06/19/2022 : [v0.2.8](https://github.com/shenweichen/DeepCTR-Torch/releases/tag/v0.2.8) released.Fix some bugs. +- 06/14/2021 : [v0.2.7](https://github.com/shenweichen/DeepCTR-Torch/releases/tag/v0.2.7) released.Add [AFN](./Features.html#afn-adaptive-factorization-network-learning-adaptive-order-feature-interactions) and fix some bugs. - 04/04/2021 : [v0.2.6](https://github.com/shenweichen/DeepCTR-Torch/releases/tag/v0.2.6) released.Add [IFM](./Features.html#ifm-input-aware-factorization-machine) and [DIFM](./Features.html#difm-dual-input-aware-factorization-machine);Support multi-gpus running([example](./FAQ.html#how-to-run-the-demo-with-multiple-gpus)). - 02/12/2021 : [v0.2.5](https://github.com/shenweichen/DeepCTR-Torch/releases/tag/v0.2.5) released.Fix bug in DCN-M. - 12/05/2020 : [v0.2.4](https://github.com/shenweichen/DeepCTR-Torch/releases/tag/v0.2.4) released.Imporve compatibility & fix issues.Add History callback.([example](https://deepctr-torch.readthedocs.io/en/latest/FAQ.html#set-learning-rate-and-use-earlystopping)). diff --git a/docs/source/conf.py b/docs/source/conf.py index e99b48ea..615f48b0 100644 --- a/docs/source/conf.py +++ b/docs/source/conf.py @@ -26,7 +26,7 @@ # The short X.Y version version = '' # The full version, including alpha/beta/rc tags -release = '0.2.7' +release = '0.2.8' # -- General configuration --------------------------------------------------- diff --git a/docs/source/index.rst b/docs/source/index.rst index 1701d403..497d232b 100644 --- a/docs/source/index.rst +++ b/docs/source/index.rst @@ -34,18 +34,21 @@ You can read the latest code at https://github.com/shenweichen/DeepCTR-Torch and News ----- +06/19/2022 : Fix some bugs. `Changelog `_ + 06/14/2021 : Add `AFN <./Features.html#afn-adaptive-factorization-network-learning-adaptive-order-feature-interactions>`_ and fix some bugs. `Changelog `_ 04/04/2021 : Add `IFM <./Features.html#ifm-input-aware-factorization-machine>`_ and `DIFM <./Features.html#difm-dual-input-aware-factorization-machine>`_ . Support multi-gpus running(`example <./FAQ.html#how-to-run-the-demo-with-multiple-gpus>`_). `Changelog `_ -02/12/2021 : Fix bug in DCN-M. `Changelog `_ DisscussionGroup ----------------------- -公众号:**浅梦学习笔记** wechat ID: **deepctrbot** + 公众号:**浅梦学习笔记** wechat ID: **deepctrbot** + + `Discussions `_ `学习小组主题集合 `_ -.. image:: ../pics/code.png +.. image:: ../pics/code2.jpg .. toctree:: :maxdepth: 2 diff --git a/setup.py b/setup.py index 4e44fe13..705a9905 100644 --- a/setup.py +++ b/setup.py @@ -4,12 +4,12 @@ long_description = fh.read() REQUIRED_PACKAGES = [ - 'torch>=1.1.0', 'tqdm', 'sklearn', 'tensorflow' + 'torch>=1.1.0', 'tqdm', 'scikit-learn', 'tensorflow' ] setuptools.setup( name="deepctr-torch", - version="0.2.7", + version="0.2.8", author="Weichen Shen", author_email="weichenswc@163.com", description="Easy-to-use,Modular and Extendible package of deep learning based CTR(Click Through Rate) prediction models with PyTorch", @@ -37,6 +37,7 @@ 'Programming Language :: Python :: 3.5', 'Programming Language :: Python :: 3.6', 'Programming Language :: Python :: 3.7', + 'Programming Language :: Python :: 3.8', 'Topic :: Scientific/Engineering', 'Topic :: Scientific/Engineering :: Artificial Intelligence', 'Topic :: Software Development', diff --git a/tests/models/DeepFM_test.py b/tests/models/DeepFM_test.py index 5a333af4..a11dc3bd 100644 --- a/tests/models/DeepFM_test.py +++ b/tests/models/DeepFM_test.py @@ -17,10 +17,6 @@ (True, (32,), 3, 0), (False, (32,), 0, 3), (False, (32,), 3, 0), - (True, (), 0, 1), - (True, (), 1, 0), - (False, (), 0, 2), - (False, (), 2, 0), ] ) def test_DeepFM(use_fm, hidden_size, sparse_feature_num, dense_feature_num): diff --git a/tests/savemodel_with_acc_test.py b/tests/savemodel_with_acc_test.py deleted file mode 100644 index aa3a7d71..00000000 --- a/tests/savemodel_with_acc_test.py +++ /dev/null @@ -1,34 +0,0 @@ -""" -Date: 2021-07-13 11:59:44 -LastEditors: GodK -LastEditTime: 2021-07-13 12:26:11 -""" -import pytest -from deepctr_torch.models import DeepFM -from deepctr_torch.callbacks import EarlyStopping, ModelCheckpoint -from .utils import get_test_data, SAMPLE_SIZE,get_device - -@pytest.mark.parametrize( - 'use_fm,hidden_size,sparse_feature_num', - [(True, (32,), 3), - (False, (32,), 3), - (False, (32,), 2), (False, (32,), 1), (True, (), 1), (False, (), 2) - ] -) -def test_save(use_fm, hidden_size, sparse_feature_num): - sample_size = SAMPLE_SIZE - x, y, feature_columns = get_test_data( - sample_size, sparse_feature_num=sparse_feature_num, dense_feature_num=sparse_feature_num) - model = DeepFM(feature_columns, feature_columns, use_fm=use_fm, - dnn_hidden_units=hidden_size, dnn_dropout=0.5, device=get_device()) - - # test callbacks - early_stopping = EarlyStopping(monitor='val_acc', min_delta=0, verbose=1, patience=0, mode='max') - model_checkpoint = ModelCheckpoint(filepath='model.ckpt', monitor='val_acc', verbose=1, - save_best_only=True, - save_weights_only=False, mode='max', period=1) - model.compile('adam', 'binary_crossentropy', metrics=['binary_crossentropy','acc']) - model.fit(x, y, batch_size=64, epochs=3, validation_split=0.5, callbacks=[early_stopping, model_checkpoint]) - -if __name__ == "__main__": - pass \ No newline at end of file diff --git a/tests/utils.py b/tests/utils.py index 10abcecb..28f3010b 100644 --- a/tests/utils.py +++ b/tests/utils.py @@ -4,6 +4,7 @@ import numpy as np import torch as torch +from deepctr_torch.callbacks import EarlyStopping, ModelCheckpoint from deepctr_torch.inputs import SparseFeat, DenseFeat, VarLenSparseFeat SAMPLE_SIZE = 64 @@ -17,46 +18,46 @@ def gen_sequence(dim, max_len, sample_size): def get_test_data(sample_size=1000, embedding_size=4, sparse_feature_num=1, dense_feature_num=1, sequence_feature=['sum', 'mean', 'max'], classification=True, include_length=False, hash_flag=False, prefix=''): - - feature_columns = [] model_input = {} - - if 'weight' in sequence_feature: - feature_columns.append(VarLenSparseFeat(SparseFeat(prefix+"weighted_seq",vocabulary_size=2,embedding_dim=embedding_size),maxlen=3,length_name=prefix+"weighted_seq"+"_seq_length",weight_name=prefix+"weight")) + if 'weight' in sequence_feature: + feature_columns.append( + VarLenSparseFeat(SparseFeat(prefix + "weighted_seq", vocabulary_size=2, embedding_dim=embedding_size), + maxlen=3, length_name=prefix + "weighted_seq" + "_seq_length", + weight_name=prefix + "weight")) s_input, s_len_input = gen_sequence( 2, 3, sample_size) - model_input[prefix+"weighted_seq"] = s_input - model_input[prefix+'weight'] = np.random.randn(sample_size,3,1) - model_input[prefix+"weighted_seq"+"_seq_length"] = s_len_input + model_input[prefix + "weighted_seq"] = s_input + model_input[prefix + 'weight'] = np.random.randn(sample_size, 3, 1) + model_input[prefix + "weighted_seq" + "_seq_length"] = s_len_input sequence_feature.pop(sequence_feature.index('weight')) - for i in range(sparse_feature_num): dim = np.random.randint(1, 10) - feature_columns.append(SparseFeat(prefix+'sparse_feature_'+str(i), dim,embedding_size,dtype=torch.int32)) + feature_columns.append(SparseFeat(prefix + 'sparse_feature_' + str(i), dim, embedding_size, dtype=torch.int32)) for i in range(dense_feature_num): - feature_columns.append(DenseFeat(prefix+'dense_feature_'+str(i), 1,dtype=torch.float32)) + feature_columns.append(DenseFeat(prefix + 'dense_feature_' + str(i), 1, dtype=torch.float32)) for i, mode in enumerate(sequence_feature): dim = np.random.randint(1, 10) maxlen = np.random.randint(1, 10) feature_columns.append( - VarLenSparseFeat(SparseFeat(prefix +'sequence_' + mode,vocabulary_size=dim, embedding_dim=embedding_size), maxlen=maxlen, combiner=mode)) + VarLenSparseFeat(SparseFeat(prefix + 'sequence_' + mode, vocabulary_size=dim, embedding_dim=embedding_size), + maxlen=maxlen, combiner=mode)) for fc in feature_columns: - if isinstance(fc,SparseFeat): - model_input[fc.name]= np.random.randint(0, fc.vocabulary_size, sample_size) - elif isinstance(fc,DenseFeat): + if isinstance(fc, SparseFeat): + model_input[fc.name] = np.random.randint(0, fc.vocabulary_size, sample_size) + elif isinstance(fc, DenseFeat): model_input[fc.name] = np.random.random(sample_size) else: s_input, s_len_input = gen_sequence( fc.vocabulary_size, fc.maxlen, sample_size) model_input[fc.name] = s_input if include_length: - fc.length_name = prefix+"sequence_"+str(i)+'_seq_length' - model_input[prefix+"sequence_"+str(i)+'_seq_length'] = s_len_input + fc.length_name = prefix + "sequence_" + str(i) + '_seq_length' + model_input[prefix + "sequence_" + str(i) + '_seq_length'] = s_len_input if classification: y = np.random.randint(0, 2, sample_size) @@ -66,7 +67,7 @@ def get_test_data(sample_size=1000, embedding_size=4, sparse_feature_num=1, dens return model_input, y, feature_columns -def layer_test(layer_cls, kwargs = {}, input_shape=None, +def layer_test(layer_cls, kwargs={}, input_shape=None, input_dtype=torch.float32, input_data=None, expected_output=None, expected_output_shape=None, expected_output_dtype=None, fixed_batch_size=False): '''check layer is valid or not @@ -90,7 +91,7 @@ def layer_test(layer_cls, kwargs = {}, input_shape=None, for i, e in enumerate(input_data_shape): if e is None: input_data_shape[i] = np.random.randint(1, 4) - + if all(isinstance(e, tuple) for e in input_data_shape): input_data = [] for e in input_data_shape: @@ -104,37 +105,37 @@ def layer_test(layer_cls, kwargs = {}, input_shape=None, # use input_data to update other parameters if input_shape is None: input_shape = input_data.shape - + if expected_output_dtype is None: expected_output_dtype = input_dtype - + # layer initialization layer = layer_cls(**kwargs) - + if fixed_batch_size: inputs = torch.tensor(input_data.unsqueeze(0), dtype=input_dtype) else: inputs = torch.tensor(input_data, dtype=input_dtype) - + # calculate layer's output output = layer(inputs) if not output.dtype == expected_output_dtype: raise AssertionError("layer output dtype does not match with the expected one") - + if not expected_output_shape: - raise ValueError("expected output shape should not be none") + raise ValueError("expected output shape should not be none") actual_output_shape = output.shape for expected_dim, actual_dim in zip(expected_output_shape, actual_output_shape): if expected_dim is not None: if not expected_dim == actual_dim: raise AssertionError(f"expected_dim:{expected_dim}, actual_dim:{actual_dim}") - + if expected_output is not None: # check whether output equals to expected output assert_allclose(output, expected_output, rtol=1e-3) - + return output @@ -148,10 +149,14 @@ def check_model(model, model_name, x, y, check_model_io=True): :param check_model_io: :return: ''' + early_stopping = EarlyStopping(monitor='val_acc', min_delta=0, verbose=1, patience=0, mode='max') + model_checkpoint = ModelCheckpoint(filepath='model.ckpt', monitor='val_acc', verbose=1, + save_best_only=True, + save_weights_only=False, mode='max', period=1) model.compile('adam', 'binary_crossentropy', - metrics=['binary_crossentropy']) - model.fit(x, y, batch_size=100, epochs=1, validation_split=0.5) + metrics=['binary_crossentropy', 'acc']) + model.fit(x, y, batch_size=100, epochs=1, validation_split=0.5, callbacks=[early_stopping, model_checkpoint]) print(model_name + 'test, train valid pass!') torch.save(model.state_dict(), model_name + '_weights.h5') @@ -165,9 +170,10 @@ def check_model(model, model_name, x, y, check_model_io=True): print(model_name + 'test save load model pass!') print(model_name + 'test pass!') -def get_device(use_cuda = True): + +def get_device(use_cuda=True): device = 'cpu' if use_cuda and torch.cuda.is_available(): print('cuda ready...') device = 'cuda:0' - return device \ No newline at end of file + return device