如何从POS标记词列表中提取模式?使用NLTK。

4

我有一个文本文件,其中包含多个列表;每个列表都包含单词/词性标记对的元组,如下所示:

    [('reviewtext', 'IN'), ('this', 'DT'), ('movie', 'NN'), ('was', 'VBD'), ('great', 'JJ'), ('and', 'CC'), ('fun', 'NN'), ('i', 'PRP'), ('really', 'RB'), ('enjoyed', 'VBD'), ('this', 'DT'), ('awesome', 'NN'), ('movie', 'NN')]
    [('reviewtext', 'IN'), ('it', 'PRP'), ('was', 'VBD'), ('fun', 'VBN'), ('but', 'CC'), ('long', 'RB')]
    [('reviewtext', 'IN'), ('i', 'PRP'), ('loved', 'VBD'), ('the', 'DT'), ('new', 'JJ'), ('movie', 'NN'), ('my', 'PRP$'), ('brother', 'NN'), ('got', 'VBD'), ('sad', 'JJ'), ('and', 'CC'), ('unhappy', 'JJ'), ('at', 'IN'), ('the', 'DT'), ('end', 'NN')]

我需要提取所有形容词连词形容词对,或所有JJ-CC-JJ对(仅限单词,不包括pos标签)。对于这个例子,最终的输出应该是一个包含所有模式的列表:

    ['great and fun', 'sad and unhappy']

我使用了以下代码来标记文本:
```html

我使用了以下代码来标记文本:

```
    with open("C:\\Users\\M\\Desktop\\sample dataset.txt") as fileobject:
        for line in fileobject:
            line = line.lower() #lowercase
            line = re.sub(r'[^\w\s]','',line) #remove punctuation
            line = nltk.word_tokenize(line) #tokenize
            line = nltk.pos_tag(line) #POS tag

            fo = open("C:\\Users\\M\\Desktop\\movies1_complete.txt", "a")
            fo.write(str(line))
            fo.write("\n")
            fo.close()

但是如何提取上述模式中的单词呢?我在这里查看查看,但它们没有解释如何提取特定的pos模式。提前致谢。

1
除非您的文件是每行一个句子的格式,否则这将给您带来糟糕的结果。请阅读整个段落(或整个文件),使用nltk.sent_tokenize将它们分成句子,然后对每个结果句子使用nltk.word_tokenize进行处理。 - alexis
2个回答

3
from itertools import islice

for sub in l:
    for a, b, c in zip(islice(sub, 0, None), islice(sub, 1, None), islice(sub, 2, None)):
        if all((a[-1] == "JJ", b[-1] == "CC", c[-1] == "JJ")):
            print("{} {} {}".format(a[0], b[0], c[0]))

这段代码输出的结果是 sad and unhappy,没有包含 'great and fun',因为它不符合模式 JJ-CC-JJ

或者只需使用enumerate和生成器:

l = [[('reviewtext', 'IN'), ('this', 'DT'), ('movie', 'NN'), ('was', 'VBD'), ('great', 'JJ'), ('and', 'CC'),
      ('fun', 'NN'), ('i', 'PRP'), ('really', 'RB'), ('enjoyed', 'VBD'), ('this', 'DT'), ('awesome', 'NN'),
      ('movie', 'NN')],
     [('reviewtext', 'IN'), ('it', 'PRP'), ('was', 'VBD'), ('fun', 'VBN'), ('but', 'CC'), ('long', 'RB')],
     [('reviewtext', 'IN'), ('i', 'PRP'), ('loved', 'VBD'), ('the', 'DT'), ('new', 'JJ'), ('movie', 'NN'), ('my', 'PRP$'), ('brother', 'NN'), ('got', 'VBD'), ('sad', 'JJ'), ('and', 'CC'), ('unhappy', 'JJ'), ('at', 'IN'), ('the', 'DT'), ('end', 'NN')]]

def match(l,p1,p2,p3):
    for sub in l:
        # avoid index error and catch last three elements
        end = len(sub) - 1
        for ind, (a, b) in enumerate(sub, 1):
            if ind == end:
                break
            if b == p1 and sub[ind][1] == p2 and sub[ind + 1][1] == p3:
                yield ("{} {} {}".format(a, sub[ind][0], sub[ind + 1][0]))

print(list(match(l,"JJ","CC","JJ")))        

输出(以示例为基础):

['sad and unhappy']

谢谢。但是你在最后一行所说的“不符合JJ-CC-JJ”的意思是什么?@padraic cunningham - modarwish
2
@modarwish,你期望的输出显示为“great and fun”,这与JJ-CC-JJ不匹配,它应该是JJ-CC-NN。 - Padraic Cunningham

2
即使已经有一个很好的答案被接受了,我认为你会发现这个工具很有用。你可以使用下面的来检查对象流中的正则表达式。请注意保留HTML标签。
from refo import finditer, Predicate, Plus

class Word(object):
    def __init__(self, token, pos):
        self.token = token
        self.pos = pos

class W(Predicate):
    def __init__(self, token=".*", pos=".*"):
        self.token = re.compile(token + "$")
        self.pos = re.compile(pos + "$")
        super(W, self).__init__(self.match)

    def match(self, word):
        m1 = self.token.match(word.token)
        m2 = self.pos.match(word.pos)
        return m1 and m2


originals = [
    [('reviewtext', 'IN'), ('this', 'DT'), ('movie', 'NN'), ('was', 'VBD'), 
     ('great', 'JJ'), ('and', 'CC'), ('fun', 'NN'), ('i', 'PRP'), 
     ('really', 'RB'), ('enjoyed', 'VBD'), ('this', 'DT'), 
     ('awesome', 'NN'), ('movie', 'NN')],
    [('reviewtext', 'IN'), ('it', 'PRP'), 
     ('was', 'VBD'), ('fun', 'VBN'), ('but', 'CC'), ('long', 'RB')],
    [('reviewtext', 'IN'), ('i', 'PRP'), ('loved', 'VBD'), ('the', 'DT'), 
     ('new', 'JJ'), ('movie', 'NN'), ('my', 'PRP$'), ('brother', 'NN'), 
     ('got', 'VBD'), ('sad', 'JJ'), ('and', 'CC'), ('unhappy', 'JJ'), 
     ('at', 'IN'), ('the', 'DT'), ('end', 'NN')]]


sentences = [[Word(*x) for x in original] for original in originals]

这里有一个有趣的部分,它说要寻找对象序列,其中 pos 属性为 JJ,后跟 CC,再后跟 JJNN

pred = W(pos="JJ") + W(pos="CC") + (W(pos="JJ") | W(pos="NN"))
for k, s in enumerate(sentences):
    for match in finditer(pred, s):
        x, y = match.span()   # the match spans x to y inside the sentence s
        print originals[k][x:y]

输出:

[('great', 'JJ'), ('and', 'CC'), ('fun', 'NN')]
[('sad', 'JJ'), ('and', 'CC'), ('unhappy', 'JJ')]

非常有趣!我也会尝试一下。如何仅输出单词,而不是POS标签?例如:['great and fun','sad and unhappy'] @Ale - modarwish
3
" ".join([w for w, tag in originals[k][x:y]]) 的作用是从每个元组中提取单词组件,将它们放入一个列表中,并用空格连接起来(没有测试过)。 - Ale
spaCy 有一个类似的模块,可以实现对象(POS和/或单词)模式匹配,以提取短语。它就像用于单词或POS标记而不是字符的正则表达式。另外,spaCy还有一个POS标记模式匹配器 - hobs
第二个POS标签匹配器的链接应为:https://dev59.com/7aDha4cB1Zd3GeqP9Aw1#42887388 - hobs

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接