2017-04-11 22 views
6

我有PostgreSQL中包含表中的一些汽車百萬記錄:如何在postgresql中創建多列推薦引擎?

+----+--------+------+---------+-----------+-------------+------------+------------+ 
| 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時,我想在搜索結果之外展示其他一些與其他汽車更加鬆散的結果,即類似的搜索結果,但並不一定完全符合所有搜索條件。

我試圖做的紅寶石一些基於規則的代碼,這不一樣的東西:

  • 如果通過了「雷諾Clio」的搜索,我們再'Renault Twingo'是一場勢均力敵的比賽太
  • 如果你有8000最高價,然後以通過那些最接近基於此代碼,
  • 等等,等等

,我生成一個SQL查詢,其中與order by子句。

但問題是,事情變得非常龐大,因爲我有20個不同的列,我想根據最初的標準選擇考慮。此外,我希望建議是向後兼容的,因爲我不想只做一個簡單的過濾(WHERE)查詢,在情況下可能最終返回零匹配。相反,我希望它可以做類似於使用文本相似性算法的方法,您可以在其中比較一個短語和所有這些短語,然後根據它們進行排序。

我對我如何實現這一點感到非常困惑,在這種方法中,這不是定義1000條規則,如果/然後生成SQL查詢語句。有沒有其他技術可以使用,或者可能是另一種技術而不是postgresql?

+0

恐怕這是太困難(和不清楚)解決StackOverflow問題。 SQL通常擅長做精確的匹配,但是搜索「喜歡」特定汽車模型的方式遠遠超出了其核心功能。我必須實現(相對)簡單的「近似」名稱匹配,並最終在觸發器中生成特殊用途索引,以便能夠*有些*可預測的查詢結果(和速度)。恐怕你不得不從小處着手併成長。 – Patru

回答

1

應用機器學習技術。

MADlib http://madlib.incubator.apache.org/是Postgres的擴展,它使您能夠在數據庫內部使用各種機器學習算法。值得學習和嘗試。

從你的向量開始進行線性迴歸,然後嘗試隨機森林和其他算法,並比較在你的情況下什麼效果更好(評估算法質量的技巧很簡單:你可以獲取所有數據,使用70 -80%要訓練,然後用餘數從訓練好的引擎中獲得估計值 - 然後用一些函數來計算偏差誤差,通常人們用均方誤差法)。

此外,我可以推薦一個很棒的斯坦福大學在線課程,在線發佈Youtube上的書和講座(全部免費!):http://mmds.org/。各種現代化的建立推薦引擎的方法在其中都有很好的描述。

0

理想情況下,您可以在類型爲tsvector的列中緩存「文本部分」(與該行相關的每個文本),這將使您能夠執行全文搜索,甚至爲每個單詞賦予「權重」所以在執行搜索時更重要。

例如,您可以對品牌名稱給予更多的重視,以便將結果記錄在案,並顯示同一品牌的所有品牌。假設您有一個名爲「fulltext」的tsvector列:'Clio:1A Renault:2B,4C Benzin:5D'::tsvector;,您可以使用tsquery(如'Renault & Clio'::tsquery;)搜索它,並且它會爲每個可用的雷諾和每個Clio提供結果,但它將首先放在Clio之上,然後放在Renault之後。請注意,如果有一個Mercedes Clio存在,它也會顯示。

該文檔非常明確,並有一些示例,我建議您深入研究。

這就是說,在這種情況下,數據庫不會爲你工作。如果克里歐只是因爲他們擁有相同的品牌(雷諾)而親密配對,那麼它是可以做到的。但是,如果你使用(精神上)其他論據,如汽車的大小,如果它是一輛城市車等等,你是唯一可以設計該算法的人。例如,價格範圍部分不是任何全文搜索會爲您做的事情,您必須檢查是否包含數字並最終進行排序(除非數字完全匹配)。

最後,您的工作就是如此,根據用戶輸入創建一個「智能」功能,並定義一個可以針對數據庫運行的複雜查詢。這是一個漫長的過程,但絕對可行。儘量聰明但不要過多,無論如何,tsvector會覆蓋你所有的文本列,這將減少大量的列。

6

計算每個數值屬性加權偏差:

deviation = abs(actual_value- expected_value)* property_weight 

應用簡化的計算爲文本屬性:

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

推薦偏差的總和的升序位置。

例子。我們從2012年行駛里程爲50000和6000的價格尋找雷諾TWINGO兩廂:

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) 

您可以輕鬆地更改屬性的權重校準算法。

要獲得文本的屬性更sophistcated近似你可以指定數值在輔助表的性質在這樣的:

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;