解析自然语言

首先:我知道这个系统会有缺陷!

注意:我添加了一些其他语言,因为我没有发现这个问题特定于php ..一个JavaScript或jquery解决方案将工作…我可以改变语言…它的方法我追求!

什么:我试图解析一个字符串来确定用户的期望。

这个想法是字符串是从语音生成的

示例1:打开厨房灯,关闭卧室和客厅灯。

示例2:打开厨房灯,我的卧室灯亮,客厅灯关闭。

示例3:关闭我的厨房和卧室以及客厅的灯。

这是一个过于简化的例子,但请注意,我想扩展到这三个房间以外只是控制灯的例子:外面的吊扇……

方法:我目前正在使用一些while循环迭代数组并检查数组中是否有某些字符串。

更多如何:我的想法是首先拆分“和”上的字符串。 然后我检查每个arrays的开启或关闭。 如果它没有打开或关闭我加入数组与下一个。

帮助:我想清理这个概念以及看到别人的想法……我很想做任何事……

谢谢JT

码:

$input = 'kitchen lights on and bed and living lights off'; $output = preg_split( "/ (and) /", $input ); $num = (int)count($output); $i=0; while($i<$num){ if ((strpos($output[$i],'on') !== false)||(strpos($output[$i],'off') !== false)) {} elseif(((strpos($output[$i+1],'on') !== false)||(strpos($output[$i+1],'off') !== false))){ $output[$i+1] .= ' + '.$output[$i]; unset($output[$i]); } $i++; } $output = array_values($output); $i=0; $num = (int)count($output); echo '
'; while($i<$num){ if ((strpos($output[$i],'lights') !== false)&&(strpos($output[$i],'on') !== false)&&(strpos($output[$i],'kitchen') !== false)){ echo'kitchen lights on
'; } if ((strpos($output[$i],'lights') !== false)&&(strpos($output[$i],'off') !== false)&&(strpos($output[$i],'kitchen') !== false)){ echo'kitchen lights off
'; } if ((strpos($output[$i],'lights') !== false)&&(strpos($output[$i],'on') !== false)&&(strpos($output[$i],'living') !== false)){ echo'living lights on
'; } if ((strpos($output[$i],'lights') !== false)&&(strpos($output[$i],'off') !== false)&&(strpos($output[$i],'living') !== false)){ echo'living lights off
'; } if ((strpos($output[$i],'lights') !== false)&&(strpos($output[$i],'on') !== false)&&(strpos($output[$i],'bed') !== false)){ echo'bed lights on
'; } if ((strpos($output[$i],'lights') !== false)&&(strpos($output[$i],'off') !== false)&&(strpos($output[$i],'bed') !== false)){ echo'bed lights off
'; } $i++; }

代码试用2:注意:这处理所有上述示例!

 <?php //works list $inp[]='turn the lights in the bedroom on'; $inp[]='Turn on the bedroom light'; $inp[]='turn on the lights in the bedroom'; $inp[]='Turn my kitchen and my bedroom and living room lights off.'; $inp[]='Turn the light in the kitchen on and the fan in the bedroom off'; $inp[]='Turn my kitchen lights on and my bedroom and living room lights off'; $inp[]='Turn my kitchen fan and my bedroom lights on and living room lights off.'; $inp[]='Turn my kitchen lights on and my bedroom lights on and living room lights off'; $inp[] = 'kitchen lights on and bath and living lights off'; $inp[] = 'flip on the lights in the living room'; $inp[] = 'turn on all lights'; //does not work list //$inp[] = 'turn on all lights but living'; foreach ($inp as $input){ $input = trim($input); $input = rtrim($input, '.'); $input = trim($input); $input = rtrim($input, '.'); $words = explode(" ", $input); $state = array('and','but','on','off','all','living','bed','bedroom','bath','kitchen','dining','light','lights','fan','tv'); $result = array_intersect($words, $state); $result = implode(" ", $result); $result = trim($result); //$result = preg_split('/(and|but)/',$input,-1, PREG_SPLIT_DELIM_CAPTURE); $result = preg_split( "/ (and|but) /", $result ); //$result = explode("and", $result); $sep=array(); foreach($result as $string){ $word = explode(" ", $string); $sep[]=$word; } $test=array(); $num = (int)count($sep); $i=0; while($i<($num)){ $result = (int)count(array_intersect($sep[$i], $state)); $j=$i; while($result=3){$j++;break;} $result = (int)count(array_intersect($sep[++$j], $state)); } $i=$j; } print_r($test); echo '
'; } ?>

解析自然语言并非易事,如果你想要一个真正的自然语言解析器,我建议你尝试使用现有的项目或库。 这是一个基于Web的解析器 ,基于Stanford Parser 。 或维基百科是一个很好的起点。

话虽如此,如果您愿意限制语法和涉及的关键字,您可能能够简化它。 首先,您需要知道什么是重要的 – 您在“地方”(卧室,厨房)中需要进入特定状态(“开启”,“关闭”)的“事物”(灯光,风扇)。

我会把字符串放到一个单词数组中,或者使用str_tok ,或者只是在' '上爆炸。

现在你有一系列单词从最后开始,然后向后寻找’状态’ – 开启或关闭。 然后跟着那个向后寻找一个’东西’,最后一个’地方’。 如果你打到另一个状态,那么你可以重新开始。

让我尝试用伪代码做到这一点:

 // array of words is inArray currentPlace = null; currentThing = null; currentState = null; for (i = (inArray.length - 1); i >= 0; i--) { word = inArray[i]; if (isState(word)) { currentState = word; currentPlace = null; currentThing = null; } else if (currentState) { if (isThing(word)) { currentThing = word; currentPlace = null; } else if (currentThing) { if (isPlace(word)) { currentPlace = word // Apply currentState to currentThing in currentPlace } // skip non-place, thing or state word. } // Skip when we don't have a thing to go with our state } // Skip when we don't have a current state and we haven't found a state } 

并且,写完之后,很明显它本应该使用状态机和切换语句 – 这表明我应该首先在纸上设计它。 如果你变得复杂,你想使用状态机来实现逻辑 – 状态将是’lookingForState’,’lookingForThing’等

你也不需要currentPlace作为一个变量,但我会留下它,因为它使逻辑更清晰。

编辑

如果你想支持’在卧室里打开灯’,你需要调整逻辑(如果你没有东西,你需要保存’地方’)。 如果你还想支持“打开卧室的灯”,你还需要更进一步。

考虑一下,我想知道你是否可以这样做:

 have a currentState variable and arrays for currentPlace and currentThing for each word if it's a state: store it in currentState if it's a thing, or place: add it to the approriate array if currentState is set and there is content in currentPlaces and currentThings: apply currentState to all currentThings in all currentPlaces 

那不是那里,但其中一个实现可能会给你一个起点。

编辑2

好吧,我测试了它,并且由于英语的结构方式存在一些问题。 问题是如果你想支持’打开…’和’打开……’然后你需要使用我的第二个伪代码,但由于句子中的’和’而无法轻松实现。 例如:

打开我的厨房灯我的卧室客厅灯关闭。

第一个和连接两个语句,第二个和连接到地方。 这样做的正确方法是绘制句子以找出适用于什么的句子 。

有两个快速选项,首先您可以坚持使用不同的单词或短语来连接两个命令:

把我的厨房灯打开然后我的卧室客厅灯关闭。 打开我的厨房灯,我的卧室客厅灯也关闭。

或者,这可能更容易,你可以坚持只有’Turn … off / on’forms的命令。 这适用于我上面的第一个伪代码。

JavaScript第一个伪代码的示例。

请注意,如果有任何标点符号等,您可能需要对字符串进行大量预处理。您可能还希望将“起居室”(以及类似的两个单词短语)替换为“livingroom”而不仅仅是匹配一句话,希望我能做到最好。 此外,代码可以简化一点,但我想保持它接近psuedocode示例。

编辑3

新的Javascript示例

这会处理一些额外的句子,并且会更好地清理它,它仍然依赖于每个子句末尾的“状态”,因为它用作应用操作的触发器(此版本可能会向前读取而不是向后)。 此外,它不会处理如下:

 Turn my kitchen fan and my bedroom lights on and living room lights off. 

你必须做一些更复杂的事情来理解’厨房’和’扇子’和’卧室’和’灯’之间的关系。

这些技术的某种组合可能足以做一些令人印象深刻的事情,只要输入/说出命令的人遵循一些基本规则。

这当然不是最有效的解决方案,但这是一个。 你可以肯定地改进它,比如缓存正则表达式,但你明白了。 每个子数组中的最后一项是操作。

DEMO

 var s = 'Turn my kitchen lights on and my bedroom lights on and living room lights off and my test and another test off', r = s.replace(/^Turn|\s*my/g, '').match(/.+? (on|off)/g).map(function(item) { var items = item.trim().replace(/^and\s*/, '').split(/\s*and\s*/), last = items.pop().split(' '), op = last.pop(); return items.concat([last.join(' '), op]); }); console.log(r); 

介意解释你使用的逻辑…我的意思是我正在阅读代码,但我只是好奇,如果你能说得更好

实际上逻辑很简单,也许太简单了:

 var s = 'Turn my kitchen lights on and my bedroom lights on and living room lights off and my test and another test off', r = s .replace(/^Turn|\s*my/g, '') //remove noisy words .match(/.+? (on|off)/g) //capture all groups of [some things][on|off] //for each of those groups, generate a new array from the returned results .map(function(item) { var items = item.trim() .replace(/^and\s*/, '') //remove and[space] at the beginning of string //split on and to get all things, for instance if we have //test and another test off, we want ['test', 'another test off'] .split(/\s*and\s*/), //split the last item on spaces, with previous example we would get //['another', 'test', 'off'] last = items.pop().split(' '), op = last.pop(); //on/off will always be the last item in the array, pop it //items now contains ['test'], concatenate with the array passed as argument return items.concat( [ //last is ['another', 'test'], rejoin it together to give 'another test' last.join(' '), op //this is the operation ] ); }); 

编辑:当我发布答案时, 我还没有意识到你需要这么复杂和灵活。 我提供的解决方案仅适用于我的示例中构造的句子,具有可识别的嘈杂单词和特定的命令顺序。 对于更复杂的东西,你将别无选择,只能创建像@SpaceDog建议的解析器。 一旦我有足够的时间,我会尽力提出一些建议。

我一直致力于解析菜单和食谱 (未完成),这是我的方法:

  • 找到句子分隔符(我使用AND以及其他)
  • 解析每个句子,找到你需要的关键词(灯/灯泡/等..,开/关)
  • 如果你有一个有限的地方(厨房,浴室等……)
    • 搜索这些关键字,删除其他关键字
    • 其他
    • 删除一些人可能会使用的extra words (明亮,多彩等…)
  • 将它存储到一个数组中,可能看起来像这样:
    • 什么
    • 哪里
  • 如果您没有其中一个字段,请将其留空
  • 对于每个结果检查你有什么,如果你有一个空白字段填写以前的解析

IE:打开卧室和厨房的灯

  • 1:
    • 打开卧室的灯
    • 什么:点亮
    • 哪里:卧室
  • 2:
    • 在厨房
    • 什么:
    • 哪里:厨房

what_2是空的,那么what_2lights on

请记住,有时需要用下一个结果填充数组(取决于句子的结构,但很少见),我添加一个“+”或“ – ”,所以我知道我是否必须去在解析它时向前或向后找到缺失的部分