test_imperative_deepcf.py 15.3 KB
Newer Older
X
Xin Pan 已提交
1 2 3 4 5 6 7 8 9 10 11 12 13 14
# Copyright (c) 2018 PaddlePaddle Authors. All Rights Reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
#     http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.

X
Xin Pan 已提交
15
import os
16
import random
X
Xin Pan 已提交
17
import sys
18 19 20 21
import unittest

import numpy as np
from test_imperative_base import new_program_scope
X
Xin Pan 已提交
22 23 24 25

import paddle
import paddle.fluid as fluid
import paddle.fluid.core as core
26
from paddle.fluid.dygraph.base import to_variable
27
from paddle.fluid.framework import _test_eager_guard
28
from paddle.nn import Linear
X
Xin Pan 已提交
29 30


31
class DMF(fluid.Layer):
32
    def __init__(self):
33
        super().__init__()
34 35
        self._user_latent = Linear(1000, 256)
        self._item_latent = Linear(100, 256)
X
Xin Pan 已提交
36 37 38 39 40 41 42 43

        self._user_layers = []
        self._item_layers = []
        self._hid_sizes = [128, 64]
        for i in range(len(self._hid_sizes)):
            self._user_layers.append(
                self.add_sublayer(
                    'user_layer_%d' % i,
44 45 46 47 48 49
                    Linear(
                        256 if i == 0 else self._hid_sizes[i - 1],
                        self._hid_sizes[i],
                    ),
                )
            )
50 51 52 53 54 55
            self._user_layers.append(
                self.add_sublayer(
                    'user_layer_act_%d' % i,
                    paddle.nn.ReLU(),
                )
            )
X
Xin Pan 已提交
56 57 58
            self._item_layers.append(
                self.add_sublayer(
                    'item_layer_%d' % i,
59 60 61 62 63 64
                    Linear(
                        256 if i == 0 else self._hid_sizes[i - 1],
                        self._hid_sizes[i],
                    ),
                )
            )
65 66 67 68 69 70
            self._item_layers.append(
                self.add_sublayer(
                    'item_layer_act_%d' % i,
                    paddle.nn.ReLU(),
                )
            )
X
Xin Pan 已提交
71 72 73 74 75 76 77 78

    def forward(self, users, items):
        users = self._user_latent(users)
        items = self._item_latent(items)

        for ul, il in zip(self._user_layers, self._item_layers):
            users = ul(users)
            items = il(items)
79
        return paddle.multiply(users, items)
X
Xin Pan 已提交
80 81


82
class MLP(fluid.Layer):
83
    def __init__(self):
84
        super().__init__()
85 86
        self._user_latent = Linear(1000, 256)
        self._item_latent = Linear(100, 256)
X
Xin Pan 已提交
87 88 89 90 91 92
        self._match_layers = []
        self._hid_sizes = [128, 64]
        for i in range(len(self._hid_sizes)):
            self._match_layers.append(
                self.add_sublayer(
                    'match_layer_%d' % i,
93 94 95 96 97 98
                    Linear(
                        256 * 2 if i == 0 else self._hid_sizes[i - 1],
                        self._hid_sizes[i],
                    ),
                )
            )
99 100 101 102 103 104
            self._match_layers.append(
                self.add_sublayer(
                    'match_layer_act_%d' % i,
                    paddle.nn.ReLU(),
                )
            )
X
Xin Pan 已提交
105 106 107 108

    def forward(self, users, items):
        users = self._user_latent(users)
        items = self._item_latent(items)
109 110 111
        match_vec = fluid.layers.concat(
            [users, items], axis=len(users.shape) - 1
        )
X
Xin Pan 已提交
112 113 114 115 116
        for l in self._match_layers:
            match_vec = l(match_vec)
        return match_vec


117
class DeepCF(fluid.Layer):
118
    def __init__(self, num_users, num_items, matrix):
119
        super().__init__()
X
Xin Pan 已提交
120 121 122
        self._num_users = num_users
        self._num_items = num_items
        self._rating_matrix = self.create_parameter(
123 124 125
            attr=fluid.ParamAttr(trainable=False),
            shape=matrix.shape,
            dtype=matrix.dtype,
X
Xin Pan 已提交
126
            is_bias=False,
127 128
            default_initializer=fluid.initializer.NumpyArrayInitializer(matrix),
        )
129
        self._rating_matrix.stop_gradient = True
X
Xin Pan 已提交
130

131 132
        self._mlp = MLP()
        self._dmf = DMF()
133
        self._match_fc = Linear(128, 1)
X
Xin Pan 已提交
134 135

    def forward(self, users, items):
X
Xin Pan 已提交
136 137
        # users_emb = self._user_emb(users)
        # items_emb = self._item_emb(items)
138

139 140
        users_emb = paddle.gather(self._rating_matrix, users)
        items_emb = paddle.gather(
141
            paddle.transpose(self._rating_matrix, [1, 0]), items
142
        )
X
Xin Pan 已提交
143 144
        users_emb.stop_gradient = True
        items_emb.stop_gradient = True
X
Xin Pan 已提交
145 146 147

        mlp_predictive = self._mlp(users_emb, items_emb)
        dmf_predictive = self._dmf(users_emb, items_emb)
148 149 150
        predictive = fluid.layers.concat(
            [mlp_predictive, dmf_predictive], axis=len(mlp_predictive.shape) - 1
        )
X
Xin Pan 已提交
151
        prediction = self._match_fc(predictive)
152
        prediction = paddle.nn.functional.sigmoid(prediction)
X
Xin Pan 已提交
153 154 155
        return prediction


156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184
class TestDygraphDeepCF(unittest.TestCase):
    def setUp(self):
        # Can use Amusic dataset as the DeepCF describes.
        self.data_path = os.environ.get('DATA_PATH', '')

        self.batch_size = int(os.environ.get('BATCH_SIZE', 128))
        self.num_batches = int(os.environ.get('NUM_BATCHES', 5))
        self.num_epoches = int(os.environ.get('NUM_EPOCHES', 1))

    def get_data(self):
        user_ids = []
        item_ids = []
        labels = []
        NUM_USERS = 100
        NUM_ITEMS = 1000
        matrix = np.zeros([NUM_USERS, NUM_ITEMS], dtype=np.float32)

        for uid in range(NUM_USERS):
            for iid in range(NUM_ITEMS):
                label = float(random.randint(1, 6) == 1)
                user_ids.append(uid)
                item_ids.append(iid)
                labels.append(label)
                matrix[uid, iid] = label
        indices = np.arange(len(user_ids))
        np.random.shuffle(indices)
        users_np = np.array(user_ids, dtype=np.int32)[indices]
        items_np = np.array(item_ids, dtype=np.int32)[indices]
        labels_np = np.array(labels, dtype=np.float32)[indices]
185 186 187 188 189 190 191 192
        return (
            np.expand_dims(users_np, -1),
            np.expand_dims(items_np, -1),
            np.expand_dims(labels_np, -1),
            NUM_USERS,
            NUM_ITEMS,
            matrix,
        )
193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211

    def load_data(self):
        sys.stderr.write('loading from %s\n' % self.data_path)
        likes = dict()
        num_users = -1
        num_items = -1
        with open(self.data_path, 'r') as f:
            for l in f.readlines():
                uid, iid, rating = [int(v) for v in l.split('\t')]
                num_users = max(num_users, uid + 1)
                num_items = max(num_items, iid + 1)
                if float(rating) > 0.0:
                    likes[(uid, iid)] = 1.0

        user_ids = []
        item_ids = []
        labels = []
        matrix = np.zeros([num_users, num_items], dtype=np.float32)
        for uid, iid in likes.keys():
X
Xin Pan 已提交
212 213
            user_ids.append(uid)
            item_ids.append(iid)
214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231
            labels.append(1.0)
            matrix[uid, iid] = 1.0

            negative = 0
            while negative < 3:
                nuid = random.randint(0, num_users - 1)
                niid = random.randint(0, num_items - 1)
                if (nuid, niid) not in likes:
                    negative += 1
                    user_ids.append(nuid)
                    item_ids.append(niid)
                    labels.append(0.0)

        indices = np.arange(len(user_ids))
        np.random.shuffle(indices)
        users_np = np.array(user_ids, dtype=np.int32)[indices]
        items_np = np.array(item_ids, dtype=np.int32)[indices]
        labels_np = np.array(labels, dtype=np.float32)[indices]
232 233 234 235 236 237 238 239
        return (
            np.expand_dims(users_np, -1),
            np.expand_dims(items_np, -1),
            np.expand_dims(labels_np, -1),
            num_users,
            num_items,
            matrix,
        )
240

X
Xin Pan 已提交
241
    def test_deefcf(self):
X
Xin Pan 已提交
242
        seed = 90
243
        if self.data_path:
244 245 246 247 248 249 250 251
            (
                users_np,
                items_np,
                labels_np,
                num_users,
                num_items,
                matrix,
            ) = self.load_data()
X
Xin Pan 已提交
252
        else:
253 254 255 256 257 258 259 260
            (
                users_np,
                items_np,
                labels_np,
                num_users,
                num_items,
                matrix,
            ) = self.get_data()
C
cnn 已提交
261
        paddle.seed(seed)
L
Leo Chen 已提交
262
        paddle.framework.random._manual_program_seed(seed)
X
Xin Pan 已提交
263 264
        startup = fluid.Program()
        main = fluid.Program()
X
polish  
Xin Pan 已提交
265

X
Xin Pan 已提交
266 267
        scope = fluid.core.Scope()
        with new_program_scope(main=main, startup=startup, scope=scope):
X
Xin Pan 已提交
268 269
            users = fluid.layers.data('users', [1], dtype='int32')
            items = fluid.layers.data('items', [1], dtype='int32')
X
Xin Pan 已提交
270 271
            labels = fluid.layers.data('labels', [1], dtype='float32')

272
            deepcf = DeepCF(num_users, num_items, matrix)
X
Xin Pan 已提交
273
            prediction = deepcf(users, items)
274
            loss = paddle.sum(paddle.nn.functional.log_loss(prediction, labels))
X
Xin Pan 已提交
275 276 277
            adam = fluid.optimizer.AdamOptimizer(0.01)
            adam.minimize(loss)

278 279 280 281 282
            exe = fluid.Executor(
                fluid.CPUPlace()
                if not core.is_compiled_with_cuda()
                else fluid.CUDAPlace(0)
            )
X
Xin Pan 已提交
283
            exe.run(startup)
284
            for e in range(self.num_epoches):
X
Xin Pan 已提交
285
                sys.stderr.write('epoch %d\n' % e)
286 287 288
                for slice in range(
                    0, self.batch_size * self.num_batches, self.batch_size
                ):
289
                    if slice + self.batch_size >= users_np.shape[0]:
X
Xin Pan 已提交
290 291 292 293
                        break
                    static_loss = exe.run(
                        main,
                        feed={
294 295 296 297 298 299 300 301 302
                            users.name: users_np[
                                slice : slice + self.batch_size
                            ],
                            items.name: items_np[
                                slice : slice + self.batch_size
                            ],
                            labels.name: labels_np[
                                slice : slice + self.batch_size
                            ],
X
Xin Pan 已提交
303
                        },
304 305
                        fetch_list=[loss],
                    )[0]
X
Xin Pan 已提交
306
                    sys.stderr.write('static loss %s\n' % static_loss)
X
Xin Pan 已提交
307

L
lujun 已提交
308
        with fluid.dygraph.guard():
C
cnn 已提交
309
            paddle.seed(seed)
L
Leo Chen 已提交
310
            paddle.framework.random._manual_program_seed(seed)
X
Xin Pan 已提交
311

312 313
            deepcf = DeepCF(num_users, num_items, matrix)
            adam = fluid.optimizer.AdamOptimizer(
314 315
                0.01, parameter_list=deepcf.parameters()
            )
316
            for e in range(self.num_epoches):
X
Xin Pan 已提交
317
                sys.stderr.write('epoch %d\n' % e)
318 319 320
                for slice in range(
                    0, self.batch_size * self.num_batches, self.batch_size
                ):
321
                    if slice + self.batch_size >= users_np.shape[0]:
X
polish  
Xin Pan 已提交
322
                        break
X
Xin Pan 已提交
323
                    prediction = deepcf(
324 325 326
                        to_variable(users_np[slice : slice + self.batch_size]),
                        to_variable(items_np[slice : slice + self.batch_size]),
                    )
327
                    loss = paddle.sum(
328
                        paddle.nn.functional.log_loss(
329
                            prediction,
330 331 332 333 334
                            to_variable(
                                labels_np[slice : slice + self.batch_size]
                            ),
                        )
                    )
L
lujun 已提交
335
                    loss.backward()
X
Xin Pan 已提交
336 337
                    adam.minimize(loss)
                    deepcf.clear_gradients()
338
                    dy_loss = loss.numpy()
X
polish  
Xin Pan 已提交
339
                    sys.stderr.write('dynamic loss: %s %s\n' % (slice, dy_loss))
X
Xin Pan 已提交
340

341
        with fluid.dygraph.guard():
C
cnn 已提交
342
            paddle.seed(seed)
L
Leo Chen 已提交
343
            paddle.framework.random._manual_program_seed(seed)
344

345 346
            deepcf2 = DeepCF(num_users, num_items, matrix)
            adam2 = fluid.optimizer.AdamOptimizer(
347 348
                0.01, parameter_list=deepcf2.parameters()
            )
349
            fluid.set_flags({'FLAGS_sort_sum_gradient': True})
350
            for e in range(self.num_epoches):
351
                sys.stderr.write('epoch %d\n' % e)
352 353 354
                for slice in range(
                    0, self.batch_size * self.num_batches, self.batch_size
                ):
355
                    if slice + self.batch_size >= users_np.shape[0]:
356 357
                        break
                    prediction2 = deepcf2(
358 359 360
                        to_variable(users_np[slice : slice + self.batch_size]),
                        to_variable(items_np[slice : slice + self.batch_size]),
                    )
361
                    loss2 = paddle.sum(
362
                        paddle.nn.functional.log_loss(
363
                            prediction2,
364 365 366 367 368
                            to_variable(
                                labels_np[slice : slice + self.batch_size]
                            ),
                        )
                    )
369
                    loss2.backward()
370 371 372
                    adam2.minimize(loss2)
                    deepcf2.clear_gradients()
                    dy_loss2 = loss2.numpy()
373 374 375
                    sys.stderr.write(
                        'dynamic loss: %s %s\n' % (slice, dy_loss2)
                    )
376

377 378 379 380 381 382 383 384 385
        with fluid.dygraph.guard():
            with _test_eager_guard():
                paddle.seed(seed)
                paddle.framework.random._manual_program_seed(seed)
                fluid.default_startup_program().random_seed = seed
                fluid.default_main_program().random_seed = seed

                deepcf = DeepCF(num_users, num_items, matrix)
                adam = fluid.optimizer.AdamOptimizer(
386 387
                    0.01, parameter_list=deepcf.parameters()
                )
388

389
                for e in range(self.num_epoches):
390
                    sys.stderr.write('epoch %d\n' % e)
391 392 393
                    for slice in range(
                        0, self.batch_size * self.num_batches, self.batch_size
                    ):
394
                        if slice + self.batch_size >= users_np.shape[0]:
395 396
                            break
                        prediction = deepcf(
397 398 399 400 401 402 403
                            to_variable(
                                users_np[slice : slice + self.batch_size]
                            ),
                            to_variable(
                                items_np[slice : slice + self.batch_size]
                            ),
                        )
404
                        loss = paddle.sum(
405
                            paddle.nn.functional.log_loss(
406
                                prediction,
407 408 409 410 411
                                to_variable(
                                    labels_np[slice : slice + self.batch_size]
                                ),
                            )
                        )
412 413 414 415
                        loss.backward()
                        adam.minimize(loss)
                        deepcf.clear_gradients()
                        eager_loss = loss.numpy()
416 417 418
                        sys.stderr.write(
                            'eager loss: %s %s\n' % (slice, eager_loss)
                        )
419

X
Xin Pan 已提交
420
        self.assertEqual(static_loss, dy_loss)
421
        self.assertEqual(static_loss, dy_loss2)
422
        self.assertEqual(static_loss, eager_loss)
X
Xin Pan 已提交
423 424 425


if __name__ == '__main__':
426
    paddle.enable_static()
X
Xin Pan 已提交
427
    unittest.main()