我正在开发一个基于PHP的购物应用程序。我有一个字符串列表,我知道这些字符串代表相同的产品。这些字符串可能包含完整的产品名称或其一部分(完整的产品名通常是品牌+型号)。
我想知道执行这种产品名称提取的最佳方法是什么。
例如,这里有一个表示相同产品的字符串列表:
- Tkg BOUILLOIRE Tkg-JK 1008 RWD
- Tkg Jk 1008 Rwd
- Tkg Kalorik-JK 1008 RWD-BouilloireÉlectrique sans Fil 360°
- TKG Bouilloireélectrique san-fil 1.7升2000瓦Pois TKG Rouge et blanc
- Tkg Kalorik-JK 1008 RWD-BouilloireÉlectrique sans Fil 360°
- Tkg JK 1008 RWD BOUILLOIRES
我希望提取产品名称"Tkg JK 1008 RWD"。请注意,字符串4只包含部分信息。
我尝试过一种方法,当我计算所有字符串中重复的单词时;但从那时起,很难走得更远。
你有什么线索吗?
干杯Nicolas
您可以分析字符串的重叠程度(并生成出现在大多数字符串中的单词/子字符串列表),然后选择最相关的单词。
例如,如果这些单词出现在一定百分比的字符串中,则可以将它们识别为最有可能的产品名称候选者。(这与你所做的非常相似,但添加了阈值-例如,你可以看到88%的字符串中出现了5个单词,其他单词的百分比要低得多-然后选择前5个单词作为产品名称。恐怕这并不准确,需要手动调整。)这应该可以收集大部分信息,但永远不会完美。
此外,你可以有一个预定义的品牌列表,并过滤掉这些词。我也会考虑单词的部分匹配,因为它们可能是手动数据输入的产物,而且总是会有拼写错误。你可以看到这是多么重要,如果你通过简单地丢弃它们来获得足够强的"信号",那么就不必担心了。
更进一步,您可以指定另一个过滤器来标记要手动管理的项目,但这可能非常耗时。
恐怕没有简单的答案。您所做的基本上是文本挖掘。我刚刚提出了一些想法和出发点,可以帮助你开始。
假设您正在构建一些自动爬网程序,试图将来自多个来源的日期放在一起,则以上内容将起作用。如果你想让访问者能够搜索你的网站,并为所有查询返回正确的产品页面,那么我建议进行一些文本搜索(主要数据分析,有人吗?)。或者只是使用一些现成的解决方案。
在比较购物引擎中工作过(尽管不是专门研究这个问题),我想你所描述的问题非常困难。我的建议是放弃,只选择"最好的"字符串,而不是试图合成或提取"产品名称"(无论如何,这是一个模糊的概念)。你用来提取产品名称的大多数想法都会产生不一致和令人沮丧的结果。例如,只看你给出的例子,天真的算法可能会产生像"Jk 1008 Rwd"这样的神秘结果,或者像"BouilloireÉlectrique"这样极其模糊的结果。即使托马斯聪明漂亮的结果在很多产品中也会失败,或者产生令人尴尬的不符合语法的结果。我想到的很多想法都倾向于去掉"BouilloireÉlectrique"这样的类别词,这对用户体验和SEO来说是次优的。
如果我处于你的位置,我可能会这样建模解决方案:计算标题中每个单词的idf权重(将你的所有产品或该类别中的所有产品视为文档空间)。然后将每个乘积字符串转换为其idf权重向量,并计算该乘积的所有权重向量的质心。找到最接近质心的字符串,并称之为"最佳"。使用该字符串作为产品名称。它并不完美,但在大多数情况下可能效果良好。Lucene(或者你正在使用的任何搜索数据库)中可能有一个插件或查询可以为你做很多这方面的工作。
在你给出的字符串列表中,这种方法倾向于远离第四个不完整的字符串,因为它不包括高度加权的型号1008(可能在电热水壶中不常见)。如果你得到了很多信息不足、不完整的产品名称,这可能会成为一个问题。那么质心可能不会特别接近包含型号的名称。正如我所说,这是一个难题。
其他想法:
- 托马斯挑选前n个最常见单词的启发式方法可能比我猜的效果更好。或者,可能还有另一种启发式方法来检测何时效果不佳
- 查找大多数字符串通用的长子字符串,然后选择IDF权重和最高的子字符串
进一步阅读:
TF-IDF
质心
向量空间模型
<?php
// to lower case
$string = strtolower(
'Tkg BOUILLOIRE TKG - JK 10o8 RWD
Tkg Jk 10o8 Rwd
Tkg Kalorik - JK 10o8 RWD - Bouilloire Électrique sans Fil 360°
TKG Bouilloire électrique sans fil 1,7 litre 2000 watts Pois TKG Rouge et blanc
Tkg Kalorik - JK 10o8 RWD - Bouilloire Électrique sans Fil 360°
Tkg JK 10o8 RWD BOUILLOIRES'
);
// remove new lines and explode by spaces
$data = explode(' ', str_replace(array("'r'n", "'n", "'r"), ' ', $string));
// count most popular words
$count = array_count_values($data);
// sort
arsort($count);
// get first 6 most popular words
$product = array_slice($count, 0, 6);
// print product
var_dump(implode(' ', array_keys($product)));
?>
输出为:
tkg rwd 1008 jk - bouilloire
第一次尝试实现你们带来的一些想法。
class ProductNameExtraction {
private $brandName = NULL;
private $categoryName = NULL;
private $modelName = NULL;
/**
* @param $A Array of string discribing the same product
*/
public function __construct($A, $brandName, $categoryName) {
$this->brandName = $brandName;
$this->categoryName = $categoryName;
$res = array();
foreach ($A as $k => $title) {
$res[] = $this->cleanTitle($title);
}
$this->modelName = $this->computeProductName($res);
}
public function getModelName() {
return $this->modelName;
}
private function computeProductName($A) {
$s = NULL;
foreach ($A as $k => $title) {
$s .= $title . ' ';
}
$s = trim($s);
$data = explode(' ', $s);
// count most popular words
$count = array_count_values($data);
// Remove brand & category names
unset($count[$this->cleanTitle($this->brandName)]);
unset($count[$this->cleanTitle($this->categoryName)]);
$s = '';
$totalnb = sizeof($A);
foreach ($count as $k => $val) {
if ($val / $totalnb > 0.5) {
$s .= $k . ' ';
}
}
return $s;
}
private function cleanTitle($title) {
// Remove extra spaces
$title = trim($title);
$title = preg_replace('/'s's+/', ' ', $title);
// Remove noise
$title = str_replace(' - ', ' ', $title);
$title = str_replace(array("'r'n", "'n", "'r"), ' ', $title);
return strtoupper($title);
}
}