【问题标题】:How to create a multi-column recommendation engine in postgresql?如何在 postgresql 中创建多列推荐引擎?
【发布时间】:2017-04-11 08:31:15
【问题描述】:

我在 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 时,我想在搜索结果之外显示其他一些更松散的结果,与其他汽车相似,但不一定完全符合所有搜索条件。

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

  • 如果您搜索“雷诺 Clio”,那么“雷诺 Twingo”也很接近
  • 如果您的最高价格为 8000,请按最接近的价格订购
  • 等。等

基于这段代码,我生成了一个带有 where 和 order by 子句的 SQL 查询。

但问题是,事情变得非常庞大,因为我有 20 个不同的列,我想根据初始标准选择性地考虑这些列。此外,我希望建议是向后兼容的,因为我不想只做一个简单的过滤 (WHERE) 查询,以防万一最终返回零匹配。相反,我希望它做一些类似于使用文本相似性算法时的事情,您可以将一个短语与所有短语进行比较,并获得所有短语的比较分数,然后您可以对其进行排序。

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

【问题讨论】:

  • 恐怕这在 StackOverflow 问题中解决起来太难(而且不清楚)。 SQL 通常擅长进行精确匹配,但搜索“类似”特定汽车型号的内容远远超出了它的核心功能。我必须实现一个(比较)简单的“近似”名称匹配,并且最终在触发器中生成了一个特殊用途的索引,以便能够获得有点可预测的查询结果(和速度)。恐怕您将不得不从小处着手并从那里成长。

标签: ruby postgresql recommendation-engine


【解决方案1】:

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

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;   

【讨论】:

    【解决方案2】:

    应用机器学习技术。

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

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

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

    【讨论】:

      【解决方案3】:

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

      例如,您可以赋予品牌名称更多的权重,这样结果就会根据这一点进行排序,显示同一品牌的所有结果。假设您有一个名为“全文”的 tsvector 列:'Clio:1A Renault:2B,4C Benzin:5D'::tsvector;,您可以使用 tsquery 搜索它,例如 'Renault &amp; Clio'::tsquery;,它会为每个 Renault 和每个可用的 Clio 提供结果,但它会放在首位 @首先是 987654325@,然后是 Renault。请注意,如果有任何可能存在 Mercedes Clio,它也会显示出来。

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

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

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

      【讨论】:

        猜你喜欢
        • 2012-10-16
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2012-01-09
        • 1970-01-01
        • 2019-09-25
        • 2011-02-11
        • 2017-10-26
        相关资源
        最近更新 更多