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

时间:2017-04-11 08:31:15

标签: ruby postgresql recommendation-engine

我在postgresql中有一个包含一些汽车+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"   |
+----+--------+------+---------+-----------+-------------+------------+------------+

我想创建一个推荐引擎,它可以根据一些不同的标准返回20个最“相似”的匹配,例如:当用户搜索:brand = 'Renault' AND price < 60000 AND year > 2010时,我想在搜索结果之外的其他车辆中显示其他更松散的结果,这类似,但不一定完全符合所有搜索条件。< / p>

我已经尝试在ruby中制作一些基于规则的代码,这类似于:

  • 如果您使用'雷诺Clio'进行搜索,那么'Renen Twingo'也是一个非常接近的匹配
  • 如果您的最高价格为8000,则按最接近
  • 的订单排序
  • 等。等

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

然而问题是,事情变得很大,因为我想根据初始标准考虑20个不同的列,我可以选择考虑。此外,我希望建议向后兼容,因为我不想只进行简单的过滤(WHERE)查询,在这种情况下最终可能会返回零匹配。相反,我希望它做一些类似于使用文本相似度算法的东西,你可以将一个短语与所有短语进行比较,然后得到所有这些短语的比较分数,然后你可以对它们进行排序。

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

3 个答案:

答案 0 :(得分:6)

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

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子句中以获得更好的性能。例如,如果查询无法返回所需品牌以外的其他品牌,那么计算此属性的偏差是没有意义的:

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;   

答案 1 :(得分:2)

应用机器学习技巧。

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

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

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

答案 2 :(得分:0)

理想情况下,您可以在类型为tsvector的列中缓存“文本部分”(与行相关的每个文本),这样您就可以执行全文搜索甚至提供“权重”每个单词,以便在执行搜索时更重要。

例如,您可以对品牌名称给予更多权重,以便对结果进行排序,并显示同一品牌的所有品牌。假设您有一个名为“fulltext”的tsvector列:'Clio:1A Renault:2B,4C Benzin:5D'::tsvector;,您可以使用tsquery 'Renault & Clio'::tsquery;来搜索它,它会为每个雷诺和每个可用的Clio提供结果,但它会首先放在Clio上,然后放在Renault上。请注意,如果存在Mercedes Clio的任何可能性,它也会显示出来。

文档非常明确,有些例子,我建议深入研究。

话虽如此,在这种情况下,数据库不会为您完成工作。如果Clio是一个非常接近的比赛,只因为他们拥有相同的品牌(雷诺),是的,它会起作用。但是,如果您使用(精神上)其他参数,例如汽车的大小,如果它是城市汽车等等,那么您是唯一可以设计该算法的人。例如,价格范围部分不是任何全文搜索将为您执行的操作,您必须主动检查是否包含数字并最终排序(除非数字完全匹配)。

最后,您的工作就是这样,根据用户输入创建一个“智能”的功能,并定义一个可以对数据库运行的复杂查询。这是一个漫长的过程,但绝对可行。尽量聪明但不要太多,无论如何tsvector会覆盖所有文本列,这会减少你所拥有的列数。