如何在 postgresql 中创建多列推荐引擎?

How to create a multi-column recommendation engine in postgresql?

我在 postgresql 中有一个 table 包含一些汽车 +1000000 条记录:

+----+--------+------+---------+-----------+-------------+------------+------------+
| id |  price | year | mileage | fuel_type |  body_type  |   brand    |   model    |
+----+--------+------+---------+-----------+-------------+------------+------------+
|  1 |   4894 | 2011 |  121842 | "Benzin"  | "Sedan"     | "Toyota"   | "Yaris"    |
|  2 |   4989 | 2012 |   33901 | "Benzin"  | "Hatchback" | "Renault"  | "Twingo"   |
|  3 |   4990 | 2013 |   55105 | "Benzin"  | "Hatchback" | "Renault"  | "Twingo"   |
|  3 |   5290 | 2013 |   20967 | "Benzin"  | "Hatchback" | "Renault"  | "Twingo"   |
|  5 |   5594 | 2008 |  121281 | "Benzin"  | "Hatchback" | "Mercedes" | "A170"     |
|  6 |   4690 | 2012 |   71303 | "Benzin"  | "Hatchback" | "Renault"  | "Twingo"   |
|  7 |   5290 | 2013 |   58300 | "Benzin"  | "Hatchback" | "Renault"  | "Twingo"   |
|  8 |   5890 | 2013 |   35732 | "Benzin"  | "Hatchback" | "Renault"  | "Twingo"   |
|  9 |   5990 | 2013 |   38777 | "Benzin"  | "Hatchback" | "Renault"  | "Twingo"   |
| 10 |   6180 | 2013 |   69491 | "Benzin"  | "Hatchback" | "VW"       | "up!"      |
| 11 |   6490 | 2012 |   72900 | "Benzin"  | "Sedan"     | "Renault"  | "Clio III" |
| 12 |   6790 | 2012 |   49541 | "Benzin"  | "Hatchback" | "Renault"  | "Clio III" |
| 13 |   6790 | 2012 |   46377 | "Benzin"  | "Hatchback" | "Renault"  | "Clio III" |
| 14 |   6790 | 2012 |   45200 | "Benzin"  | "Hatchback" | "Renault"  | "Clio III" |
| 15 |   6894 | 2007 |  108840 | "Benzin"  | "Sedan"     | "VW"       | "Golf V"   |
| 16 |   6990 | 2009 |   54200 | "Benzin"  | "Sedan"     | "Renault"  | "Mégane"   |
| 17 |   6990 | 2012 |   40652 | "Benzin"  | "Hatchback" | "Renault"  | "Clio III" |
| 18 |   6990 | 2012 |   38080 | "Benzin"  | "Sedan"     | "Renault"  | "Clio III" |
| 19 |   7290 | 2012 |   28600 | "Benzin"  | "Hatchback" | "Renault"  | "Clio III" |
| 20 |   7290 | 2013 |   52800 | "Benzin"  | "Hatchback" | "Renault"  | "Twingo"   |
+----+--------+------+---------+-----------+-------------+------------+------------+

我想创建一个推荐引擎,它可以 return 根据一些不同的标准 "similar" 匹配最多的 20 个,例如当用户搜索:brand = 'Renault' AND price < 60000 AND year > 2010 时,我想在搜索结果之外展示其他一些与其他汽车更宽松的结果,这些结果相似,但不一定完全匹配所有搜索条件。

我尝试在 ruby 中编写一些基于规则的代码,其作用类似于:

基于此代码,我生成了一个包含 where 和 order by 子句的 SQL 查询。

然而,问题是,事情变得很庞大,因为我有 20 个不同的列,我想根据初始标准有选择地考虑。此外,我希望建议能够向后兼容,因为我不想只进行简单的过滤 (WHERE) 查询,在某些情况下可能会以 returning 零匹配结束。相反,我希望它做一些类似于使用文本相似性算法的事情,您可以在其中将一个短语与所有短语进行比较,并获得所有短语的比较分数,然后您可以根据该分数进行排序。

我对如何实现这一点感到非常困惑,在一种方法中,这不是定义 1000 条规则和 if/then 语句来生成 SQL 查询。有没有我可以使用的其他技术,或者除了 postgresql 之外的其他技术?

理想情况下,您可以将 "text part"(与行相关的每个文本)缓存在类型为 tsvector 的列中,这将使您能够执行全文搜索,甚至提供"weight" 每个词,以便在执行搜索时更重要。

例如,您可以为品牌名称赋予更多权重,以便在结果排序时考虑到这一点,显示同一品牌的所有结果。假设您有一个名为 "fulltext": 'Clio:1A Renault:2B,4C Benzin:5D'::tsvector; 的 tsvector 列,您可以使用 tsquery 搜索它,例如 'Renault & Clio'::tsquery;,它会给出每个 Renault 和每个可用的 Clio 的结果, 但它会先放在 Clio 上,然后放在 Renault 上。请注意,如果 Mercedes Clio 存在,它也会显示。

文档非常明确,并附有一些示例,我建议深入研究。

也就是说,在这种情况下,数据库不会为您完成这项工作。如果 Clio 只是因为它们具有相同的品牌(雷诺)而成为势均力敌的比赛,那么它就可以了。但是,如果您(在心理上)使用其他参数,例如汽车的大小,如果它是城市汽车之类的东西,那么您是唯一可以设计该算法的人。例如,价格范围部分不是任何全文搜索都会为您做的事情,您必须主动检查是否包含数字并最终排序(除非数字完全匹配)。

最后,您的工作就是这样,根据用户输入创建一个 "smart" 函数,并将定义一个您可以 运行 对数据库进行的复杂查询。这是一个漫长的过程,但绝对可行。尽量聪明但不要太多,在任何情况下 tsvector 都会涵盖所有文本列,这将大大减少您拥有的列数量。

计算每个数值的加权偏差属性:

deviation = abs(actual_value- expected_value)* property_weight

对文本属性应用简化计算:

deviation = (actual_value <> expected_value)::int* property_weight

按偏差总和升序推荐位置。

例子。我们正在寻找 2012 年的雷诺 Twingo 两厢车,里程数 50000,价格 6000:

select *, 
    abs(price- 6000)* 100+ 
    abs(year- 2012)* 10000+ 
    abs(mileage- 50000)* 1+
    (body_type <> 'Hatchback')::int* 40000+
    (brand <> 'Renault')::int* 100000+
    (model <> 'Twingo')::int* 50000
    as recommendation
from cars
order by recommendation
limit 10;

 id | price | year | mileage | fuel_type | body_type |  brand  |  model   | recommendation 
----+-------+------+---------+-----------+-----------+---------+----------+----------------
  9 |  5990 | 2013 |   38777 | Benzin    | Hatchback | Renault | Twingo   |          22223
  8 |  5890 | 2013 |   35732 | Benzin    | Hatchback | Renault | Twingo   |          35268
  7 |  5290 | 2013 |   58300 | Benzin    | Hatchback | Renault | Twingo   |          89300
  4 |  5290 | 2013 |   20967 | Benzin    | Hatchback | Renault | Twingo   |         110033
  3 |  4990 | 2013 |   55105 | Benzin    | Hatchback | Renault | Twingo   |         116105
  2 |  4989 | 2012 |   33901 | Benzin    | Hatchback | Renault | Twingo   |         117199
 12 |  6790 | 2012 |   49541 | Benzin    | Hatchback | Renault | Clio III |         129459
 13 |  6790 | 2012 |   46377 | Benzin    | Hatchback | Renault | Clio III |         132623
 14 |  6790 | 2012 |   45200 | Benzin    | Hatchback | Renault | Clio III |         133800
 20 |  7290 | 2013 |   52800 | Benzin    | Hatchback | Renault | Twingo   |         141800
(10 rows)

您可以通过更改属性的权重轻松校准算法。

要获得更复杂的文本属性近似值,您可以将数值分配给辅助表中的属性,如下所示:

create table models(id serial primary key, model text, value integer);
insert into models (model, value) values
('Twingo', 10),
('Clio III', 11), -- Clio is more similar to Twingo than to Laguna
('Laguna', 18)
--- etc

并在主查询中将这些值用作数字属性,例如:

select cars.*, 
    abs(price- 6000)* 100+ 
    abs(year- 2012)* 10000+ 
    abs(mileage- 50000)* 1+
    (body_type <> 'Hatchback')::int* 40000+
    (brand <> 'Renault')::int* 100000+
    abs(models.value- 10)* 50000    -- 10 is a numeric value for Twingo
    as recommendation
from cars
join models using(model)
order by recommendation
limit 10;

关于优化的说明。如果您可以严格定义任何 属性 的范围,则将其放在 WHERE 子句中以获得更好的性能。例如,如果查询不能 return 所需品牌以外的品牌,那么计算此 属性:

的偏差就没有意义
select *, 
    abs(price- 6000)* 100+ 
    abs(year- 2012)* 10000+ 
    abs(mileage- 50000)* 1+
    (body_type <> 'Hatchback')::int* 40000+
    (model <> 'Twingo')::int* 50000
    as recommendation
from cars
where brand = 'Renault' -- !
order by recommendation
limit 10;   

应用机器学习技术。

MADlib http://madlib.incubator.apache.org/ 是 Postgres 的扩展,使您能够在数据库中使用各种机器学习算法。值得学习和尝试。

从向量的线性回归开始,然后尝试随机森林和其他算法,比较哪种算法更适合您的情况(评估算法质量的技巧很简单:获取您拥有的所有数据,使用其中的 70-80% 用于训练,然后使用剩余部分从经过训练的引擎中获得估计——然后使用一些函数来计算偏差误差,通常人们使用均方误差方法)。

此外,我可以推荐一个很棒的斯坦福在线课程,在线书籍和讲座在 Youtube 上发布(全部免费!):http://mmds.org/。里面很好地描述了构建推荐引擎的各种现代方法。