如何在 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 中编写一些基于规则的代码,其作用类似于:
- 如果您按 'Renault Clio' 搜索,那么“雷诺 Twingo”也是一个接近的匹配项
- 如果您的最高价格为 8000,则按最接近该价格的人订购
- 等等等
基于此代码,我生成了一个包含 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/。里面很好地描述了构建推荐引擎的各种现代方法。
我在 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 中编写一些基于规则的代码,其作用类似于:
- 如果您按 'Renault Clio' 搜索,那么“雷诺 Twingo”也是一个接近的匹配项
- 如果您的最高价格为 8000,则按最接近该价格的人订购
- 等等等
基于此代码,我生成了一个包含 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/。里面很好地描述了构建推荐引擎的各种现代方法。