test_imperative_deepcf.py 14.9 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.nn import Linear
X
Xin Pan 已提交
28 29


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

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

    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)
78
        return paddle.multiply(users, items)
X
Xin Pan 已提交
79 80


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

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


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

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

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

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

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


155 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
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]
184 185 186 187 188 189 190 191
        return (
            np.expand_dims(users_np, -1),
            np.expand_dims(items_np, -1),
            np.expand_dims(labels_np, -1),
            NUM_USERS,
            NUM_ITEMS,
            matrix,
        )
192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210

    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 已提交
211 212
            user_ids.append(uid)
            item_ids.append(iid)
213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230
            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]
231 232 233 234 235 236 237 238
        return (
            np.expand_dims(users_np, -1),
            np.expand_dims(items_np, -1),
            np.expand_dims(labels_np, -1),
            num_users,
            num_items,
            matrix,
        )
239

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

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

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

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

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

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

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

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

376
        with fluid.dygraph.guard():
377 378 379 380
            paddle.seed(seed)
            paddle.framework.random._manual_program_seed(seed)
            fluid.default_startup_program().random_seed = seed
            fluid.default_main_program().random_seed = seed
381

382 383 384 385 386 387 388 389 390 391 392 393 394 395 396 397 398 399 400
            deepcf = DeepCF(num_users, num_items, matrix)
            adam = fluid.optimizer.AdamOptimizer(
                0.01, parameter_list=deepcf.parameters()
            )

            for e in range(self.num_epoches):
                sys.stderr.write('epoch %d\n' % e)
                for slice in range(
                    0, self.batch_size * self.num_batches, self.batch_size
                ):
                    if slice + self.batch_size >= users_np.shape[0]:
                        break
                    prediction = deepcf(
                        to_variable(users_np[slice : slice + self.batch_size]),
                        to_variable(items_np[slice : slice + self.batch_size]),
                    )
                    loss = paddle.sum(
                        paddle.nn.functional.log_loss(
                            prediction,
401
                            to_variable(
402
                                labels_np[slice : slice + self.batch_size]
403 404
                            ),
                        )
405 406 407 408 409 410 411 412
                    )
                    loss.backward()
                    adam.minimize(loss)
                    deepcf.clear_gradients()
                    eager_loss = loss.numpy()
                    sys.stderr.write(
                        'eager loss: %s %s\n' % (slice, eager_loss)
                    )
413

X
Xin Pan 已提交
414
        self.assertEqual(static_loss, dy_loss)
415
        self.assertEqual(static_loss, dy_loss2)
416
        self.assertEqual(static_loss, eager_loss)
X
Xin Pan 已提交
417 418 419


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