Position Rank代码解读(一)

2021SC@SDUSC

简介

项目github地址:GitHub - corinaflorescu/PositionRank: An Unsupervised Approach to Keyphrase Extraction from Scholarly Documents

项目原始python版本:2.7

目录结构

在这里插入图片描述

依赖库及版本号:

backports.functools-lru-cache1.5
decorator4.3.0
futures3.2.0
networkx2.2
nltk3.4
nose1.3.7
numpy1.15.4
Pillow5.3.0
PositionRank1.0
psutil5.4.8
pyparsing2.3.0
pytz2018.7
scipy1.1.0
singledispatch3.4.0.3
six1.12.0
subprocess323.5.3

实验室要求

为了方便实验结果复用,实验室的学长对实验环境做出如下要求:

1.	Python及其第三方库
(1)	Python3.7.6
(2)	第三方库如
①	Numpy1.18.1 支持python3.5-3.8
②	Matplotlib3.1.3 支持python3.6-3.8
③	Scipy1.4.1 支持python3.5-3.8
④	Keras2.3.1 支持python3.5-3.8
⑤	Scikit_learn0.22.1 支持python3.5-3.8
⑥	Scikit_image0.16.2 支持python3.6-3.8
2.	深度学习框架
(1)	推荐Pytorch 1.8.2支持cpu/gpu
(2)	或tensorflow 1.14 支持python3.5-3.7,python2.7;支持cpu/gpu
3.	Cuda (gpu版需要)
(1)	cuda 10.1 
(2)	Cudnn 7.6.5 for cuda 10.1(与cuda版本对应)

因此,本项目需要对代码中的内容进行一定的修改和调整来满足新环境的需要。

流程控制模块(_main_.py)

初始化

初始化评估指标,P(PR):精准率,R(RR):召回率,F1:由P、R计算得到(本质上是P与R调和平均倒数乘以2),来解决PR之间的冲突。

计算公式如下:在这里插入图片描述

注意在python中对列表使用“”*“表示将列表数据复制并拼接。如[0] * args.topK表示一个大小为1*topK,且数据均为0的列表。

    # initialize the evaluation metrics vectors
    P, R, F1 = [0] * args.topK, [0] * args.topK, [0] * args.topK
    Rprec = 0.0
    bpref = 0.0
    docs = 0
    files = [f for f in os.listdir(args.input_data) if isfile(join(args.input_data, f))]

运行流程控制分析

    for filename in files:
        print(filename)
        # if doc has passed the criteria then we save its text and gold
        # 借助之前生成的文件名列表生成文件的绝对路径列表
        # text指的是原文的绝对路径列表,gold指的是已标注关键字的文件列表
        # 如果路径存在,读取源文件
        # 如果相应路径不存在,返回值为None
        text = process_data.read_input_file(args.input_data + filename)
        # 如果路径存在,读取文件并返回关键短语(keyphrase)列表
        # 如果相应路径不存在,返回值为None
        gold = process_data.read_gold_file(args.input_gold + filename)

        # 如果文档路径和关键词路径同时存在
        if text and gold:
            gold_stemmed = []
            # 将短语中的每一个单词进行词形还原并拼接成新的短语
            for keyphrase in gold:
                keyphrase = [porter_stemmer.stem(w) for w in keyphrase.lower().split()]
                gold_stemmed.append(' '.join(keyphrase))
            # count the document
            docs += 1

            # 对position_rank算法进行初始化
            system = PositionRank.PositionRank(text, args.window, args.phrase_type)

            # 得到文本的表层形式、词性标签、stem以后的形式(词形还原)
            # 将结果存储在self.words中,其元素是一个Candidate对象,属性包括
            # 单词原形、词性、还原后的词形、单词位置、单词所在的句子序号
            system.get_doc_words()

            # 选择候选短语,一种方法是将组成词满足一定条件长度为1-n的短语都加入候选,另一种方法是将组成词满足条件并且长度最大的短语加入候选词列表
            system.candidate_selection()

            # 为候选词打分,核心算法,基于图的PageRank算法
            system.candidate_scoring(update_scoring_method=False)

            # 计算结果指标
            currentP, currentR, currentF1 = \
                evaluation.PRF_range(system.get_best_k(args.topK), gold_stemmed, k=args.topK)

            Rprec += evaluation.Rprecision(system.get_best_k(args.topK), gold_stemmed,
                                           k=len(gold_stemmed))

            bpref += evaluation.Bpref(system.get_best_k(args.topK), gold_stemmed)

            P = map(sum, zip(P, currentP))
            R = map(sum, zip(R, currentR))
            F1 = map(sum, zip(F1, currentF1))  

注意:该部分的核心是:

system = PositionRank.PositionRank(text, args.window, args.phrase_type)
system.get_doc_words()
system.candidate_selection()
system.candidate_scoring(update_scoring_method=False)

其中2、3、4行分别对应“候选词生成”、“候选短语生成”、“基于图的PageRank算法为候选列表打分”(下一篇关于基于图的PageRank算法博客详细介绍)。

补充:PorterStemmer模块介绍

PorterStremmer是一种用于词形还原的工具,它基于一定的规则对英文单词进行还原,虽然可能得不到正确的结果,但是总体正确率是可以接受的。而且这种方式相较于基于词典的词形还原具有时间和空间上的优势。而且,即便结果可能不是真实词形,但是可以确保不同变体还原成同一词形,如created和create还原后都可以得到creat。这种方法被搜索引擎、数据挖掘系统广泛使用。

候选词生成分析

    def get_doc_words(self):

        """
        生成候选词列表
        """
        # tokens对应一个列表
        # tokens是一个列表,列表中的每一项代表一个词,元素形如(word,序号)
        tokens = []
        # pos_tags对应一个列表,列表中的每一项代表一个词的词性
        pos_tags = []
        # 对应一个列表,元素是stem过的词
        stems = []

        # get a list of document's words and another one of its part-of-speech tags
        for i in range(0, len(self.sentences)):
            # tokens是一个列表,列表中的每一项代表一个词,元素形如(word,所属句子序号)
            # 注意使用的是extend直接拼接,而不是append结合
            tokens.extend([(w, i) for w, p in self.sentences[i]])
            pos_tags.extend([p for w, p in self.sentences[i]])

        # get the stems  of the words as another list
        # 相当于将stemmed_sentences降维
        for s in self.stemmed_sentences:
            stems.extend([w for w in s])

        # add the word in the words container
        for i in range(0, len(tokens)):
            # 参数为:第i个词,第i个词的词性,stem过的第i个词,第i个词的位置,第i个词出现的句子序号
            self.words.append(Candidate(tokens[i][0], pos_tags[i], stems[i], (i + 1), 									tokens[i][1]))

self.sentences的生成算法如下:

# 元素是列表,代表一个句子,句子用列表表示,元素是(word,序号)
        self.sentences = [pos_tag(word_tokenize(s)) for s in sent_tokenize(self.input_text.lower())]							

其中pos_tag算法是用于对单词进行词性标注的算法。其中输入为分词后的句子,输出为一个元素为元组的列表,元组形如(word,词性)。例如原句为:”John’s big idea isn’t all that bad“。输出结果为:

[('John', 'NNP'), ("'s", 'POS'), ('big', 'JJ'), ('idea', 'NN'), ('is', 'VBZ'),
("n't", 'RB'), ('all', 'PDT'), ('that', 'DT'), ('bad', 'JJ'), ('.', '.')]

”NNP“等是POS Tagging标签类型,可在POS Tagging 标签类型查询表(Penn Treebank Project) - creatures-of-habit - 博客园 (cnblogs.com)中查询。

候选短语生成分析

def candidate_selection(self, pos=None, phrase_type='n_grams'):
    if pos is None:
        # NN:名词,单数或不可数
        # NNS:复数名词
        # NNP:专有名词单数
        # NNPS:专有名词复数
        # JJ:形容词
        pos = ['NN', 'NNS', 'NNP', 'NNPS', 'JJ']

    # 选择使用n_grams方式或者longest方式提取短语(运行时可以选择)
    if phrase_type == 'n_grams':
        # 该方法将长度为1、2、3...n的满足要求的短语都作为候选
        self.get_ngrams(n=4, good_pos=pos)
    else:
        # 该方法只选择长度最长的满足要求的短语作为候选短语
        self.get_phrases(self, good_pos=pos)

get_ngrams方法分析:

def get_ngrams(self, n=3, good_pos=None):
    """
    compute all the ngrams or ngrams with patters found in the document
    该方法将长度为1、2、3...n的短语都作为候选
    :param n: the maximum length of the ngram (default is 3)
    :param good_pos: goodPOS if any word filter is applied, for eg. keep only nouns and adjectives
    :return:
    """

    jump = 0
    # 如果参数中提供词性限制
    if good_pos:
        # 将句子中满住条件的短语加入
        for i in range(0, len(self.sentences)):

            # jump helps to keep track of a word position; its leap is the sentence length
            if i == 0:
                jump = 0
            else:
                jump += len(self.sentences[i - 1])

            # if the sentence is very short then the maximum length of the phrase is the sentence's length
            max_length = min(n, len(self.sentences[i]))

            # 第i句中的单词
            tokens = [w for w, p in self.sentences[i]]
            # 第i句单词对应的词性
            pos_tags = [p for w, p in self.sentences[i]]
            # 第i句中的单词对应的stem形式
            stems = self.stemmed_sentences[i]
            # 选择一个单词,遍历该句中本单词后的所有内容,如果词性满足我们的要求并且之间的距离小于n(或单词长度-1)
            for j in range(0, len(tokens)):
                for k in range(j, len(tokens)):
                    # 将满住要求的n-grams加入候选列表candidates
                    if pos_tags[k] in good_pos and k - j < max_length and k < (len(tokens) - 1):
                        self.candidates.append(Candidate(' '.join(tokens[j:k + 1]), ' '.join(pos_tags[j:k + 1]),
                                                         ' '.join(stems[j:k + 1]), j + jump, i))
                    else:
                        break
    # 如果参数中没有提供词性限制
    else:
        for i in range(0, len(self.sentences)):

            # jump helps to keep track of a word position; its leap is the sentence length
            if i == 0:
                jump = 0
            else:
                jump += len(self.sentences[i - 1])

            # if the sentence is very short then the maximum length of the phrase is the sentence's length
            max_length = min(n, len(self.sentences[i]))

            tokens = [w for w, p in self.sentences[i]]
            pos_tags = [p for w, p in self.sentences[i]]
            stems = self.stemmed_sentences[i]
            for j in range(0, len(tokens)):
                for k in range(j, len(tokens)):
                    if k - j < max_length and k < (len(tokens) - 1):
                        self.candidates.append(Candidate(' '.join(tokens[j:k + 1]), ' '.join(pos_tags[j:k + 1]),
                                                         ' '.join(stems[j:k + 1]), j + jump, i))
                    else:
                        break

get_phrases方法分析:

该方法与get_ngrams方法不同之处在于只将满住条件的最长短语作为候选短语加入列表,

实现方式如下:

while j < len(tokens):
    for k in range(j, len(tokens)):
        if pos_tags[k] in good_pos and k - j < max_length and k < (len(tokens) - 1):
            continue
        else:
            if j < k:
                self.candidates.append(
                    Candidate(' '.join(tokens[j:k]), ' '.join(pos_tags[j:k]), ' '.join(stems[j:k]),j + jump, i))
            j = k + 1
            break

小结

本次我们配置了实验环境、分析了PositionRank算法的基本结构。

Guess you like

Origin blog.csdn.net/Simonsdu/article/details/120818254