【问题标题】:parse natural language解析自然语言
【发布时间】:2013-09-12 11:03:51
【问题描述】:

开始:我知道这个系统会有缺陷!

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

什么:我正在尝试解析一个字符串以确定用户想要什么。

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

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

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

示例 3: 关掉我厨房、卧室和客厅的灯。

这是一个过于简化的示例,但请注意,我想在这三个房间之外进行扩展,并且只控制灯光 例如:外部吊扇在...

如何:我目前正在使用一些 while 循环来遍历数组并检查某些字符串是否在数组中。

更多方法:我的想法是首先拆分“and”上的字符串。然后我检查每个阵列的开或关。如果它没有开或关,我将与下一个加入阵列。

帮助:我很想清理这个概念,也想看看其他人的想法...我愿意做任何事情..

谢谢 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 '<br>';
while($i<$num){
if ((strpos($output[$i],'lights') !== false)&&(strpos($output[$i],'on') !== false)&&(strpos($output[$i],'kitchen') !== false)){
echo'kitchen lights on<br>';
}
if ((strpos($output[$i],'lights') !== false)&&(strpos($output[$i],'off') !== false)&&(strpos($output[$i],'kitchen') !== false)){
echo'kitchen lights off<br>';
}
if ((strpos($output[$i],'lights') !== false)&&(strpos($output[$i],'on') !== false)&&(strpos($output[$i],'living') !== false)){
echo'living lights on<br>';
}
if ((strpos($output[$i],'lights') !== false)&&(strpos($output[$i],'off') !== false)&&(strpos($output[$i],'living') !== false)){
echo'living lights off<br>';
}
if ((strpos($output[$i],'lights') !== false)&&(strpos($output[$i],'on') !== false)&&(strpos($output[$i],'bed') !== false)){
echo'bed lights on<br>';
}
if ((strpos($output[$i],'lights') !== false)&&(strpos($output[$i],'off') !== false)&&(strpos($output[$i],'bed') !== false)){
echo'bed lights off<br>';
}   
$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)
    {
        $imp = implode(" ", $sep[$j]);
        if(isset($test[$i])){$test[$i]=$imp.' '.$test[$i];}
        else{$test[$i]=$imp;}

        if ($result>=3){$j++;break;}        
        $result = (int)count(array_intersect($sep[++$j], $state));      
    }
$i=$j;
}

print_r($test);
    echo '<br>';
}


?>

【问题讨论】:

  • @ILA_JT 我也是...我认为这是一个有趣的练习!
  • 我不会查找and,而只是查找预定义目标关键字kitchenliving roombedroom 和最近的on/off 的列表。一个简单的正则表达式和preg_match_all() 将涵盖。
  • @mario 你能定义最近的...字符吗?...我不确定正则表达式如何处理所有三个示例,但我喜欢看一个示例!
  • 我会用preg_split('/(on|off)/', $input, PREG_SPLIT_DELIM_CAPTURE) 进一步简化它。您在 [2*n+1] 中获取状态并使用 preg_match_all('/(kitchen|living|bedroom|garage/' 在结果数组中找到 [2*n+0]` 中的房间关键字。
  • @mario 好的,你在 2*n+1 部分失去了我...为什么不把这个作为答案...只是一个想法:P

标签: php javascript jquery regex


【解决方案1】:

解析自然语言并非易事,如果您想要一个真正的自然语言解析器,我建议您尝试使用现有的项目或库。这是基于Stanford Parserweb based parser。或者wikipedia 是一个很好的起点。

话虽如此,如果您愿意限制所涉及的语法和关键字,则可以简化它。首先你需要知道什么是重要的——你在“地方”(卧室、厨房)有需要进入特定状态(“开”、“关”)的“东西”(灯、风扇)。

我会将字符串放入一个单词数组中,或者使用str_tok,或者直接在' ' 上展开。

现在你有一个从 end 开始的单词数组,然后向后寻找“状态”——开或关。然后向后寻找一个“东西”,最后是一个“地方”。如果你达到另一个状态,那么你可以重新开始。

让我试着用伪代码来做:

// 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
}

而且,写完之后,很明显它应该使用状态机和 switch 语句——这表明我应该首先在纸上设计它。如果您变得更复杂,您想使用状态机来实现逻辑 - 状态将是“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

好的,我对其进行了测试,但由于英语的结构方式存在一些问题。问题是,如果您想支持“打开...”和“打开...打开”,那么您需要使用我的第二个伪代码,但这并不容易,因为句子中的“和”。例如:

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

第一个和连接两个语句,第二个和连接到地方。执行此操作的正确方法是diagram the sentence 找出适用于什么的内容。

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

打开我的厨房灯然后我的卧室客厅的灯。 打开我的厨房灯我的卧室客厅的灯。

或者,这可能更容易,您可以坚持只使用“关闭...关闭/打开”形式的命令。这适用于我上面的第一个伪代码。

JavaScript Example 的第一个伪代码。

注意,如果有任何标点符号等的机会,您可能需要对字符串进行大量预处理。您可能还想看看用“livingroom”替换“livingroom”(和类似的两个词组)而不是而不是像我一样只匹配一个词并希望得到最好的结果。此外,代码可以简化一点,但我想保持它接近伪代码示例。

编辑 3

New Javascript Example

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

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

你必须做一些更复杂的事情来理解“厨房”和“风扇”以及“卧室”和“灯”之间的关系。

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

【讨论】:

  • 现在阅读...抱歉耽搁了...哇我喜欢你的方法。啊,尽管如此,获得体面结果的逻辑对我来说仍然很棘手。我认为您在给出可以说的多个示例时很好地强调了这一点
  • 我真的想说我完全理解你的方法,但它仍然对我有用。好的,所以我会尽力总结,你告诉我我在哪里。我们有地方、事物和状态。我们将字符串分解为单个单词。从末端向前遍历数组,寻找地点、事物或状态。一旦我们找到一个,我们就会继续寻找地方、事物或状态,以刮掉我们刚刚找到的东西。这是我有点困惑的地方......现在怎么办......我想要一个半工作示例......我可以用一些代表点来吸引你吗哈哈......谢谢你的意见
  • 让我再想一想,看看我能不能拼凑出一个具体的例子,不能很快承诺任何事情,但我会看看我能做些什么。
  • 好的,这是一个很好的谜题,我添加了一个具体的例子——它不适用于我描述的所有英语句子,但它适用于你的例子,它应该可扩展。应该很容易移植到任何其他语言。
  • 啊谢谢您的时间先生...我会花一些时间来挑选这个...谢谢
【解决方案2】:

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

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 建议的解析器。只要有足够的时间,我会尽力想出一些东西。

【讨论】:

  • 啊,我喜欢这个......请注意解释你使用的逻辑......我的意思是我正在阅读代码,但我只是好奇你能不能说得更好,哈哈
  • 我创建了一个更紧凑的原始示例代码版本。到目前为止,它似乎可以处理所有事情,除了一些困难的事情。请关掉我所有的灯,但我的厨房......如果你愿意,我可以分享......虽然它在 php 中......我喜欢一些关于如何处理它的输入。
  • @tman,很高兴您找到了解决方案。发布一个答案并接受你自己的答案是个好主意;)我认为一旦你有一个灵活的解析器设置,处理这些就不会太难了。
【解决方案3】:

我一直在研究parsing menus and recipes(未完成),这是我的方法:

  • 查找句子分隔符(我使用 AND 以及其他)
  • 解析每个句子以找到您需要的key 字词(灯/灯泡/等..,开/关)
  • 如果您的位置有限(厨房、浴室等...)
    • 搜索这些关键字,删除其他关键字
    • 否则
    • 删除某些人可能使用的extra words(明亮、多彩等...)
  • 将其存储到一个数组中,如下所示:
    • 什么
    • 在哪里
  • 如果您没有其中一个字段,请将其留空
  • 为每个结果检查你有什么,如果你有一个空白字段,用之前的解析填充它

I.E.:打开卧室和厨房的灯

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

what_2 为空,则what_2lights on

请记住,有时需要用下一个结果填充数组(取决于句子的结构,但很少见),我会添加一个“+”或“-”,这样我就知道我是否解析时必须前进或后退才能找到丢失的部分

【讨论】: