mr4-lc/recommendation

0.0.1 2023-12-21 02:50 UTC

This package is auto-updated.

Last update: 2024-09-21 04:26:54 UTC


README

相似度汉明(Hamming)是衡量两个二进制字符串之间距离的一种方法,通过计算它们之间不同位的数量。例如,1011和1101之间的汉明相似度为2,因为它们在第二位和第三位上有两个不同的位。计算汉明相似度的算法非常简单,只需比较两个二进制字符串的每个位,如果不同则增加计数器。=> 用于计算特征的相似度。

欧几里得相似度(Euclidean similarity)是衡量多维空间中两点之间距离的一种方法,通过计算它们坐标差的平方和的平方根。例如,点A(1, 2)和B(4, 6)之间的欧几里得相似度为√[(4−1)²+(6−2)²]=√25=5。计算欧几里得相似度的算法非常简单,只需应用以下公式:d(A,B)=∑i=1n(Ai−Bi)²,其中d(A,B)是点A和B之间的欧几里得相似度,n是空间的维度,Ai和Bi是点A和B在第i维上的坐标。=> 用于计算价格的相似度。

Jaccard相似度(Jaccard similarity)是衡量两个集合之间相似度的一种方法,通过将共同元素的数量除以不同元素的总数。例如,集合A = {1, 2, 3}和B = {2, 3, 4}之间的Jaccard相似度为2/4 = 0.5,因为它们有两个共同元素2和3,以及四个不同元素1, 2, 3, 4。计算Jaccard相似度的算法非常简单,只需应用以下公式:J(A,B)=|A∪B|/|A∩B|,其中J(A,B)是集合A和B之间的Jaccard相似度,|A∩B|是A和B的共同元素数量,|A∪B|是A和B的不同元素数量。=> 用于计算类别的相似度。

安装

composer require mr4-lc/recommendation
php artisan vendor:publish --tag=mr4-lc-recommendation --force

配置

return [
    'featureWeight' => 1,
    'categoryWeight' => 1,
    'priceWeight' => 1,
    'priceHighRange' => 1000,

    'chunkSize' => 1000,
    'perPage' => 5,

    'output' => storage_path('app/private/recommendation/data'),

    'mapping' => [
        'tables' => [
            // Sample config
            'table_name' => [
                'data_version' => '0.0.1',
                'output' => 'table_name.json',
                // SQL WHERE condition
                'status' => [
                    'status' => 1,
                ],
                // SQL ORDER BY
                'order' => 'id',
                // Customize model response
                'model' => \App\Models\SomeModel::class,
                // Load relatíonhip
                'model_with' => ['tags'],
                // Customize similarity
                'class' => \App\Similarities\PostSimilarity::class,
                'map' => [
                    'id' => 'id',
                    'price' => 'price',
                    'categories' => ['categories', 'group'], // Array or string column name,
                    'features' => ['color', 'type'], // Array or string column name,
                ],
                'map_type' => [
                    'categories' => 'merge', // String data
                    'features' => 'object', // Object data
                ],
            ],
        ],
    ],
];
namespace App\Similarities;

use Mr4Lc\Recommendation\Similarity\Similarity;

class PostSimilarity extends \Mr4Lc\Recommendation\Similarity\ItemSimilarity
{
    protected function calculateSimilarityScore($productA, $productB)
    {
        $productAFeatures = implode('', get_object_vars($productA->features));
        $productBFeatures = implode('', get_object_vars($productB->features));

        return array_sum([
            (Similarity::hamming($productAFeatures, $productBFeatures) * $this->featureWeight),
            (Similarity::jaccard($productA->categories, $productB->categories) * $this->categoryWeight)
        ]) / ($this->featureWeight + $this->categoryWeight);
    }

    public static function GetSimilarityPosts($product_id, $page = null, $perPage = null, $pagePrefix = 'page')
    {
        $products = static::GetSimilarityItems('posts', $product_id, $page, $perPage, $pagePrefix);
        return $products;
    }
}

使用

创建矩阵

php artisan recommendation:export {tableName} {--chunkSize=}

创建相似度(运行时间较长,运行时请注意)

php artisan recommendation:create {tableName} {--id=}
<script>
    function buildView (response, container, ctrl, perPage, pagePrefix) {
        const items = document.createElement('div')
        items.className = 'items'
        response.data.forEach(element => {
            const div = document.createElement('div')
            div.className = 'item'
            if (element.name) {
                const name = document.createElement('div')
                name.className = 'name'
                name.innerHTML = element.name
                div.appendChild(name)
            }
            if (element.images) {
                const images = JSON.parse(element.images)
                if (images && images.length > 0) {
                    const img = document.createElement('img')
                    img.src = location.origin + '/public/' + images[0]
                    img.className = 'thumbnail'
                    div.appendChild(img)
                }
            }
            items.appendChild(div)
        });
        container.appendChild(items)
        const pagination = document.createElement('div')
        pagination.className = 'pagination'
        response.links.forEach(element => {
            const button = document.createElement('button')
            button.innerHTML = element.label
            button.className = element.active ? '' : 'inactive'
            pagination.appendChild(button)
            const urlParams = new URLSearchParams(element.url)
            const selectPage = urlParams.get(pagePrefix)
            button.onclick = () => {
                LoadRecommendation(ctrl, selectPage, perPage, pagePrefix)
            }
        })
        container.appendChild(pagination)
    }
</script>
<x-mr4-lc.recommendation itemName='wines' itemId='1' apiUrl='http://127.0.0.1:8000/api/recommendation' builder="buildView" />
<x-mr4-lc.recommendation itemName='wines' itemId='1' apiUrl='http://127.0.0.1:8000/api/recommendation' />

控制器

$fields = request()->validate([
    'item_name' => ['required'],
    'item_id' => ['required'],
]);
$result = ItemSimilarity::GetSimilarityItems($fields['item_name'], $fields['item_id']);
return new JsonResponse($result, 200);