Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
0.00% covered (danger)
0.00%
0 / 29
0.00% covered (danger)
0.00%
0 / 4
CRAP
0.00% covered (danger)
0.00%
0 / 1
BayesianPersonalizedRanking
0.00% covered (danger)
0.00%
0 / 29
0.00% covered (danger)
0.00%
0 / 4
110
0.00% covered (danger)
0.00%
0 / 1
 __construct
0.00% covered (danger)
0.00%
0 / 3
0.00% covered (danger)
0.00%
0 / 1
2
 generateRandomFactors
0.00% covered (danger)
0.00%
0 / 4
0.00% covered (danger)
0.00%
0 / 1
6
 predict
0.00% covered (danger)
0.00%
0 / 6
0.00% covered (danger)
0.00%
0 / 1
6
 updateFactors
0.00% covered (danger)
0.00%
0 / 16
0.00% covered (danger)
0.00%
0 / 1
30
1<?php
2/**
3 * Jingga
4 *
5 * PHP Version 8.1
6 *
7 * @package   phpOMS\Business\Recommendation
8 * @copyright Dennis Eichhorn
9 * @license   OMS License 2.0
10 * @version   1.0.0
11 * @link      https://jingga.app
12 */
13declare(strict_types=1);
14
15namespace phpOMS\Business\Recommendation;
16
17/**
18 * Bayesian Personalized Ranking (BPR)
19 *
20 * @package phpOMS\Business\Recommendation
21 * @license OMS License 2.0
22 * @link    https://jingga.app
23 * @see     https://arxiv.org/ftp/arxiv/papers/1205/1205.2618.pdf
24 * @since   1.0.0
25 *
26 * @todo Implement, current implementation probably wrong
27 */
28final class BayesianPersonalizedRanking
29{
30    private int $numFactors;
31
32    private float $learningRate;
33
34    private float $regularization;
35
36    private array $userFactors = [];
37
38    private array $itemFactors = [];
39
40    // num_factors determines the dimensionality of the latent factor space.
41    // learning_rate controls the step size for updating the latent factors during optimization.
42    // regularization prevents overfitting by adding a penalty for large parameter values.
43    public function __construct(int $numFactors, float $learningRate, float $regularization)
44    {
45        $this->numFactors     = $numFactors;
46        $this->learningRate   = $learningRate;
47        $this->regularization = $regularization;
48    }
49
50    private function generateRandomFactors()
51    {
52        $factors = [];
53        for ($i = 0; $i < $this->numFactors; ++$i) {
54            $factors[$i] = \mt_rand() / \mt_getrandmax();
55        }
56
57        return $factors;
58    }
59
60    public function predict($userId, $itemId) {
61        $userFactor = $this->userFactors[$userId];
62        $itemFactor = $this->itemFactors[$itemId];
63        $score      = 0;
64
65        for ($i = 0; $i < $this->numFactors; ++$i) {
66            $score += $userFactor[$i] * $itemFactor[$i];
67        }
68
69        return $score;
70    }
71
72    public function updateFactors($userId, $posItemId, $negItemId) : void
73    {
74        if (!isset($this->userFactors[$userId])) {
75            $this->userFactors[$userId] = $this->generateRandomFactors();
76        }
77
78        if (!isset($this->itemFactors[$posItemId])) {
79            $this->itemFactors[$posItemId] = $this->generateRandomFactors();
80        }
81
82        if (!isset($this->itemFactors[$negItemId])) {
83            $this->itemFactors[$negItemId] = $this->generateRandomFactors();
84        }
85
86        $userFactor    = $this->userFactors[$userId];
87        $posItemFactor = $this->itemFactors[$posItemId];
88        $negItemFactor = $this->itemFactors[$negItemId];
89
90        for ($i = 0; $i < $this->numFactors; ++$i) {
91            $userFactor[$i]    += $this->learningRate * ($posItemFactor[$i] - $negItemFactor[$i]) - $this->regularization * $userFactor[$i];
92            $posItemFactor[$i] += $this->learningRate * $userFactor[$i] - $this->regularization * $posItemFactor[$i];
93            $negItemFactor[$i] += $this->learningRate * (-$userFactor[$i]) - $this->regularization * $negItemFactor[$i];
94        }
95
96        $this->userFactors[$userId]    = $userFactor;
97        $this->itemFactors[$posItemId] = $posItemFactor;
98        $this->itemFactors[$negItemId] = $negItemFactor;
99    }
100}