graphaware / reco4php
基于Neo4j的PHP推荐引擎框架
Requires
- php: ^7.0
- graphaware/neo4j-php-client: ^4.0
- psr/log: ^1.0
- symfony/event-dispatcher: ^2.7 || ^3.0
- symfony/stopwatch: ^2.7 || ^3.0
Requires (Dev)
- phpunit/phpunit: ^5.1
This package is not auto-updated.
Last update: 2024-09-11 13:39:45 UTC
README
基于Neo4j的PHP推荐引擎框架
GraphAware Reco4PHP是一个用于在Neo4j之上构建复杂推荐引擎的库。
特性
- 干净灵活的设计
- 内置算法和函数
- 测量推荐质量的能力
- 内置Cypher事务管理
要求
- PHP7.0+
- Neo4j 2.2.6+(推荐使用Neo4j 3.0+)
该库强加了一种特定的推荐引擎架构,这种架构源于我们构建推荐引擎的经验,并解决了通过Cypher在远程运行推荐引擎的架构挑战。作为回报,它处理了所有管道,这样您只需编写特定于您用例的推荐业务逻辑。
推荐引擎架构
发现引擎和推荐
推荐引擎的目的是进行推荐
,比如推荐您应该关注的人、您应该购买的产品、您应该阅读的文章。
推荐过程的第一步是找到推荐的项目,这被称为发现
过程。
在Reco4PHP中,DiscoveryEngine
负责以某种可能的方式发现推荐的项目。
通常,推荐系统将包含多个发现引擎。如果您编写的是“您应该在GitHub上关注的用户”推荐引擎,您可能会得到一个不完整的Discovery Engines
列表。
- 找到与我共同贡献过相同仓库的人
- 找到与我关注的相同人关注的人
- 找到与我关注的相同仓库的人
- ...
每个Discovery Engine
将生成一组包含发现的Item
及其分数(下面将进一步说明)的Recommendations
。
过滤器和黑名单
Filters
的目的是将原始input
与发现的项进行比较,并决定此项是否应该被推荐给用户。一个非常简单的过滤器可能是ExcludeSelf
,它会排除与输入相同的节点,这在密集连接的图中可能会相对发生。
另一方面,BlackLists
是一组不应向用户推荐的预定义节点。一个例子可能是创建一个包含用户已购买物品的BlackList
,如果您想推荐他应该购买的产品。
后处理器
PostProcessors
提供在推荐通过过滤器和黑名单过程后的后处理能力。
例如,如果您会奖励一个住在您同一个城市的人,那么在发现阶段(以伦敦为例,这可能需要数百万)加载所有住在该城市的人就不合理了。
然后,您可以创建一个RewardSameCity
后处理器,如果输入节点和推荐的项目住在同一个城市,它会调整生成的推荐的分数。
总结
总结一下,一个典型的推荐引擎将是一组
- 一个或多个
Discovery Engines
- 零个或多个
Fitlers
和BlackLists
- 零个或多个
PostProcessors
让我们开始吧!
通过示例使用
我们将使用来自MovieLens的小数据集,其中包含电影、用户、评分以及流派。
数据集公开可用于此处:http://grouplens.org/datasets/movielens/。要下载的数据集在 MovieLens 最新数据集 部分中,名为 ml-latest-small.zip
。
下载并解压缩存档后,您可以运行以下 Cypher 语句来导入数据集,只需将文件 URL 调整为匹配您实际文件路径即可
CREATE CONSTRAINT ON (m:Movie) ASSERT m.id IS UNIQUE;
CREATE CONSTRAINT ON (g:Genre) ASSERT g.name IS UNIQUE;
CREATE CONSTRAINT ON (u:User) ASSERT u.id IS UNIQUE;
LOAD CSV WITH HEADERS FROM "file:///Users/ikwattro/dev/movielens/movies.csv" AS row
WITH row
MERGE (movie:Movie {id: toInt(row.movieId)})
ON CREATE SET movie.title = row.title
WITH movie, row
UNWIND split(row.genres, '|') as genre
MERGE (g:Genre {name: genre})
MERGE (movie)-[:HAS_GENRE]->(g)
USING PERIODIC COMMIT 500
LOAD CSV WITH HEADERS FROM "file:///Users/ikwattro/dev/movielens/ratings.csv" AS row
WITH row
MATCH (movie:Movie {id: toInt(row.movieId)})
MERGE (user:User {id: toInt(row.userId)})
MERGE (user)-[r:RATED]->(movie)
ON CREATE SET r.rating = toInt(row.rating), r.timestamp = toInt(row.timestamp)
为了举例说明,我们将假设我们正在为用户 ID 为 460 的用户推荐电影。
安装
需要使用 composer
安装依赖项
composer require graphaware/reco4php
用法
发现
为了推荐人们应该观看的电影,您决定以以下方式找到潜在推荐:
- 找到与我评分相同的电影的人评分的电影,但我尚未评分的电影
如前所述,reco4php
推荐引擎框架负责所有管道,因此您只需关注业务逻辑,这就是为什么它提供了一个基类,您应该扩展该类并仅实现上层接口的方法,以下是创建您的第一个发现引擎的方式:
<?php namespace GraphAware\Reco4PHP\Tests\Example\Discovery; use GraphAware\Common\Cypher\Statement; use GraphAware\Common\Type\Node; use GraphAware\Reco4PHP\Context\Context; use GraphAware\Reco4PHP\Engine\SingleDiscoveryEngine; class RatedByOthers extends SingleDiscoveryEngine { public function discoveryQuery(Node $input, Context $context) { $query = 'MATCH (input:User) WHERE id(input) = {id} MATCH (input)-[:RATED]->(m)<-[:RATED]-(o) WITH distinct o MATCH (o)-[:RATED]->(reco) RETURN distinct reco LIMIT 500'; return Statement::create($query, ['id' => $input->identity()]); } public function name() { return "rated_by_others"; } }
discoveryMethod
方法应返回一个包含查找推荐查询的 Statement
对象,name
方法应返回一个字符串,描述您引擎的名称(这主要用于日志记录目的)。
这里的查询有一些逻辑,我们不希望返回所有找到的电影作为候选人,因为在初始数据集中有 10k+,想象一下在 100M 数据集中的情况。因此,我们正在计算评分的总和,并返回评分最高的电影,将结果限制为 500 个潜在推荐。
基类假设推荐节点将具有标识符 reco
,产生的推荐评分的标识符为 score
。评分不是必需的,它将被赋予默认评分 1
。
所有这些默认值都可以通过覆盖基类的方法进行自定义(请参阅自定义部分)。
此发现引擎将生成一组 500 个评分的 Recommendation
对象,您可以使用这些对象在您的过滤器或后处理程序中。
过滤
作为过滤器的示例,我们将过滤在 1999 年之前制作的电影。年份写在电影标题中,因此我们将在过滤器中使用正则表达式提取年份。
<?php namespace GraphAware\Reco4PHP\Tests\Example\Filter; use GraphAware\Common\Type\Node; use GraphAware\Reco4PHP\Filter\Filter; class ExcludeOldMovies implements Filter { public function doInclude(Node $input, Node $item) { $title = $item->value("title"); preg_match('/(?:\()\d+(?:\))/', $title, $matches); if (isset($matches[0])) { $y = str_replace('(','',$matches[0]); $y = str_replace(')','', $y); $year = (int) $y; if ($year < 1999) { return false; } return true; } return false; } }
Filter
接口强制您实现 doInclude
方法,该方法应返回一个布尔值。您可以通过方法参数访问推荐节点以及输入。
黑名单
当然,我们不希望推荐当前用户已经评分的电影,为此我们将创建一个黑名单,构建一组这些已经评分的电影节点。
<?php namespace GraphAware\Reco4PHP\Tests\Example\Filter; use GraphAware\Common\Cypher\Statement; use GraphAware\Common\Type\Node; use GraphAware\Reco4PHP\Filter\BaseBlacklistBuilder; class AlreadyRatedBlackList extends BaseBlacklistBuilder { public function blacklistQuery(Node $input) { $query = 'MATCH (input) WHERE id(input) = {inputId} MATCH (input)-[:RATED]->(movie) RETURN movie as item'; return Statement::create($query, ['inputId' => $input->identity()]); } public function name() { return 'already_rated'; } }
您只需要添加匹配应被列入黑名单的节点的逻辑,框架会负责将推荐节点与提供的黑名单进行过滤。
后处理程序
Post Processors
用于向推荐项目添加额外的评分。在我们的示例中,如果产生的推荐有超过 10 个评分,我们可以奖励该推荐。
<?php namespace GraphAware\Reco4PHP\Tests\Example\PostProcessing; use GraphAware\Common\Cypher\Statement; use GraphAware\Common\Result\Record; use GraphAware\Common\Type\Node; use GraphAware\Reco4PHP\Post\RecommendationSetPostProcessor; use GraphAware\Reco4PHP\Result\Recommendation; use GraphAware\Reco4PHP\Result\Recommendations; use GraphAware\Reco4PHP\Result\SingleScore; class RewardWellRated extends RecommendationSetPostProcessor { public function buildQuery(Node $input, Recommendations $recommendations) { $query = 'UNWIND {ids} as id MATCH (n) WHERE id(n) = id MATCH (n)<-[r:RATED]-(u) RETURN id(n) as id, sum(r.rating) as score'; $ids = []; foreach ($recommendations->getItems() as $item) { $ids[] = $item->item()->identity(); } return Statement::create($query, ['ids' => $ids]); } public function postProcess(Node $input, Recommendation $recommendation, Record $record) { $recommendation->addScore($this->name(), new SingleScore($record->get('score'), 'total_ratings_relationships')); } public function name() { return "reward_well_rated"; } }
整合所有组件
现在,我们的组件已创建,我们需要有效地构建我们的推荐引擎
<?php namespace GraphAware\Reco4PHP\Tests\Example; use GraphAware\Reco4PHP\Engine\BaseRecommendationEngine; use GraphAware\Reco4PHP\Tests\Example\Filter\AlreadyRatedBlackList; use GraphAware\Reco4PHP\Tests\Example\Filter\ExcludeOldMovies; use GraphAware\Reco4PHP\Tests\Example\PostProcessing\RewardWellRated; use GraphAware\Reco4PHP\Tests\Example\Discovery\RatedByOthers; class ExampleRecommendationEngine extends BaseRecommendationEngine { public function name() { return "example"; } public function discoveryEngines() { return array( new RatedByOthers() ); } public function blacklistBuilders() { return array( new AlreadyRatedBlackList() ); } public function postProcessors() { return array( new RewardWellRated() ); } public function filters() { return array( new ExcludeOldMovies() ); } }
在您的推荐服务中,您可能有多多个推荐引擎提供不同的推荐,最后一步是创建此服务并注册您创建的每个 RecommendationEngine
。您还需要提供对您的 Neo4j 数据库的连接,在您的应用程序中,这可能看起来像这样:
<?php namespace GraphAware\Reco4PHP\Tests\Example; use GraphAware\Reco4PHP\Context\SimpleContext; use GraphAware\Reco4PHP\RecommenderService; class ExampleRecommenderService { /** * @var \GraphAware\Reco4PHP\RecommenderService */ protected $service; /** * ExampleRecommenderService constructor. * @param string $databaseUri */ public function __construct($databaseUri) { $this->service = RecommenderService::create($databaseUri); $this->service->registerRecommendationEngine(new ExampleRecommendationEngine()); } /** * @param int $id * @return \GraphAware\Reco4PHP\Result\Recommendations */ public function recommendMovieForUserWithId($id) { $input = $this->service->findInputBy('User', 'id', $id); $recommendationEngine = $this->service->getRecommender("user_movie_reco"); return $recommendationEngine->recommend($input, new SimpleContext()); } }
检查推荐
推荐引擎中的recommend()
方法会返回一个包含一系列Recommendation
的Recommendations
对象,每个Recommendation
包含推荐的物品及其评分。
每个评分都会被插入,这样您可以轻松地检查为什么会产生这样的推荐,例如
$recommender = new ExampleRecommendationService("http://localhost:7474"); $recommendation = $recommender->recommendMovieForUserWithId(460); print_r($recommendations->getItems(1)); Array ( [0] => GraphAware\Reco4PHP\Result\Recommendation Object ( [item:protected] => GraphAware\Bolt\Result\Type\Node Object ( [identity:protected] => 13248 [labels:protected] => Array ( [0] => Movie ) [properties:protected] => Array ( [id] => 2571 [title] => Matrix, The (1999) ) ) [scores:protected] => Array ( [rated_by_others] => GraphAware\Reco4PHP\Result\Score Object ( [score:protected] => 1067 [scores:protected] => Array ( [0] => GraphAware\Reco4PHP\Result\SingleScore Object ( [score:GraphAware\Reco4PHP\Result\SingleScore:private] => 1067 [reason:GraphAware\Reco4PHP\Result\SingleScore:private] => ) ) ) [reward_well_rated] => GraphAware\Reco4PHP\Result\Score Object ( [score:protected] => 261 [scores:protected] => Array ( [0] => GraphAware\Reco4PHP\Result\SingleScore Object ( [score:GraphAware\Reco4PHP\Result\SingleScore:private] => 261 [reason:GraphAware\Reco4PHP\Result\SingleScore:private] => ) ) ) ) [totalScore:protected] => 261 ) )
许可证
本库采用Apache v2许可证发布,请阅读附带的LICENSE
文件。
如有商业支持或定制开发/扩展需求,请发送邮件至info@graphaware.com。