解析句子的数据结构

时间:2012-03-05 01:33:25

标签: java parsing tree nlp

请找一个例句:

(S1 (S (S (NP (NP (NP (NN Activation)) (PP (IN of) (NP (NN protein) (NN kinase) (NN C)))) (CC and) (NP (NP (NN elevation)) (PP (IN of) (NP (NN cAMP))))) (VP (VBP interact) (ADVP (RB synergistically)) (S (VP (TO to) (VP (VB raise) (NP (NP (NP (NP (NN c-Fos)) (CC and) (NP (NN AP-1))) (NN activity)) (PP (IN in) (NP (NN Jurkat) (NNS cells))))))))) (. .)))

目标是从这句话创建一棵树;叶子是单词,中间节点是词性标签,根是S1。括号表示句子中包含的短语的范围;它们不需要包含在树中。

实现上述目标的数据结构的良好组合是什么,您是否也能够共享支持您的建议的伪代码?

我想到了一个HashMap和ArrayList,但我很困惑如何实际开始实现。只是说这个逻辑在这一点上并不直观。您的建议将不胜感激。

感谢。

3 个答案:

答案 0 :(得分:3)

这种事情的基本方法是将 lex 字符串转换为标记的序列,然后解析该字符串成为什么的称为抽象语法树。这是一个很大的主题,但非常简短:

Lexing 意味着将您的字符串分解为不同的逻辑令牌。在您的情况下,您可能只想将序列分为开括号和闭括号以及标签。所以你的令牌是“(”,“)”之一,或者不是那个非空白字符序列。

解析意味着读取该字符序列并从中构建树结构。

首先你需要一个树结构:在你的情况下,它可能是一个由Sentence组成的数据结构,它由一个词性标签和一个可以是单词或子句的对象列表组成。 (我假设这里没有有趣的结构:如果你知道NN只能包含单词而NP只能包含子句或类似的东西,那么你可以在这里建立一个更丰富的树结构。)

接下来,您需要将令牌解析为此树。执行此操作的最简单方法非常简单:例如,它看起来像这里你可以编写一个函数`parse(List tokens),它希望第一个标记是一个开括号,第二个标记是一个标签,然后递归从序列中消耗标记,直到遇到一个近括号标记。

这些主题是大型书籍和许多图书馆等的主题,但希望这将使您初步了解如何解决问题。

答案 1 :(得分:1)

我最近在Python中做过类似的事情。我将仅讨论有关数据结构的部分:

每个词性都是词性列表(非终端),或者只是一个词(终端)。所以你可以创建类似的东西:

enum Type {
    Sentence,
    NounPhrase,
    VerbPhrase,
    Conjunction,
    ...
};

interface PartOfSpeech { };

class NonTerminal implements PartOfSpeech {
    Type type;
    List<PartOfSpeech> subparts;
};

class Terminal implements PartOfSpeech {
    String word;
};

这是相对无类型的:你的程序只需要构造这些的有效结构(例如,没有使用由句子列表组成的VerbPhrase - 你可以做到但是它毫无意义!)。

替代路线是定义更明确的类型系统。因此,您可以为每种类型的词性定义类,例如

class VerbPhrase {
    Verb verb;
    AdverbPhrase adverb; /* optional */
    ...
};

由于有多种方法可以制作动词短语,因此您可以为每种类型添加类:

interface VerbPhrase { };

class IntransitiveVerbPhrase implements VerbPhrase {
    Verb verb;
    AdverbPhrase adverb; /* optional */
};

class TransitiveVerbPhrase implements VerbPhrase {
    Verb verb;
    AdverbPhrase adverb; /* optional */
    NounPhrase obj;
};

等等。 Java级别的最佳类型显式性可能在一开始就不明显。首先写一些处理简单句子的东西,然后看看它的感受。

在我的情况下,我为每个词性类型创建了类,尽管每个类都继承自Terminal或Nonterminal。然后我有关于如何构建每种类型的规则。对于其中一些人来说,这有点混乱,例如。

add_rule(NounPhrase, [Noun])
add_rule(NounPhrase, [RelativeNounWord])
add_rule(NounPhrase, [Noun, AdjectiveClause])
add_rule(NounPhrase, [Article, Noun])
add_rule(NounPhrase, [Article, Adjectives, Noun])
add_rule(NounPhrase, [Article, Noun, AdjectiveClause])
add_rule(NounPhrase, [Article, Adjectives, Noun, AdjectiveClause])
add_rule(NounPhrase, [Adjectives, Noun])
add_rule(NounPhrase, [Adjectives, Noun, AdjectiveClause])
add_rule(NounPhrase, [Article, Adjectives, Noun])
...

这是代码,说“NounPhrase是一个名词。或者,它是一个RelativeNoun。或者,它是一个名词后跟一个AdjectiveClause。或者等等”。有一个通用的解析器,它试图将规则应用于单词列表,直到它得到一棵树。 (您可以在http://code.google.com/p/ejrh/source/browse/trunk/ircbot/nl.py看到凌乱且未记录的代码。)

这里有一定数量的组合爆炸。它可以通过引入新类型的词性来改进,或者只是将它的某些部分作为可选的:对于Article / Adjectives / AdjectiveClause / etc的每种可能组合都有规则。出现/缺席,您可以将它们作为可选项。

答案 2 :(得分:1)

这是一个非常天真的实现,可以帮助您开始使用一些想法。它没有基于词性的打字,也没有强制执行任何超出我从单个例子​​中推断的规则。

class Node {
    String partOfSpeech;

    Node(String partOfSpeech) {
        this.partOfSpeech = partOfSpeech;
    }

    String toString() {
        return "(" + partOfSpeech + " " + getInternalString() + ")";
    }

    abstract String getInternalString(); // could use a better name
    abstract String toSentence();
}

class Word extends Node {
    String word;

    Word(String partOfSpeech, String word) {
        super(partOfSpeech);
        this.word = word;
    }

    String getInternalString() {
        return word;
    }

    String toSentence() {
        return word;
    }
}

class Phrase extends Node {
    List<Node> children;

    Phrase(String partOfSpeech) {
        super(partOfSpeech);
        this.children = new ArrayList<Node>();
    }

    add(Node child) {
        children.add(child);
    }

    String getInternalString() {
        return combineChildren(false);
    }

    String toSentence() {
        return combineChildren(true);
    }

    private String combineChildren(boolean sentence) {
        StringBuilder result = new StringBuilder();
        for (Node child : children) {
            sentenct.append(sentence ? child.toSentence() : child.toString()).append(' ');
        }
        return result.substring(0, result.length() - 1);
    }
}