“中文信息抽取”课题之源码分析1

本文详细介绍了Stanford NLP库中parser包的结构和功能,包括其各个子包的作用,如charniak、common、dvparser等。重点分析了common子包中的ParserGrammar抽象类,解析了其实现的函数接口及主要方法,如tokenize、parse和lemmatize等,这些方法涉及词法分析、语法解析和词性归类。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

2021SC@SDUSC

书接上文,根据分工,我负责对parser包中的源码进行解析,因此我将首先对parser包进行一个基本的概述,然后再来进行其中源码的分析。

一、parser包基本概述

如图所示,parser包内又有诸多子包,分别是charniak, common, dvparser, eval, lexparser, metrics, nndep, server, shiftreduce, tools, ui和webapp。从名字上不难看出,parser包内的代码支撑起了整个程序的语法解析功能。而其中名字诸如dvparser, lexparser的子包,则是存放了具体解析功能分支的功能模块包。其余子包名如common, ui, eval, webapp, tools, server的,则不难理解其存储源码的作用分别是常规源码、用户界面源码、评估源码、网页程序源码、工具源码以及服务器端源码。至于最后剩下的一些子包名字多是缩写,需要进一步分析才能得知其中源码的意义与作用。

二、common子包基本概述

首先,来看所谓的常规源码——即common子包内的源码。打开common子包,可以看到其中有9个java语言编写的源文件,通过IDEA的源文件图标不难看出,ParserGrammar是其中的抽象类,ParserQuery是一个接口,ParserQueryFactory则显然是ParserQuery的抽象工厂,而剩下的均是实现类。根据自顶向下的原则,我将首先分析ParserGrammar这个抽象类(ParserQuery和它的抽象类被囊括在内),然后再来分析其中的剩余实现类。

 

三、ParserGrammar抽象类源码分析

根据ParserGrammar抽象类的类声明来看,ParserGrammar抽象类实现了两个接口。实现的第一个接口是Function<List<? extends HasWord>, Tree>,第二个则是ParserQueryFactory。

public abstract class ParserGrammar implements Function<List<? extends HasWord>, Tree>, ParserQueryFactory {

第一个接口的语法看着有些难懂,显而易见这是一个语法糖,这里我们稍作解释。

List<?>:是一个泛型,在没有赋值前,表示可以接受任何类型的集合赋值,但赋值之后不能往里面随便添加元素,但可以remove和clear,并非immutable(不可变)集合。List<?>一般作为参数来接收外部集合,或者返回一个具体元素类型的集合,也称为通配符集合

由此,我们可以知道实现的第一个接口是一个函数接口,他会接收一个List<? extends HasWord>类的对象作为输入,即输入一个元素均为继承自HasWord类的某一子类的对象的列表,然后输出一个Tree类的对象。那么实现了这一个函数接口的ParserGrammar抽象类自然也会实现这一方法。

第二个接口则是ParserQueryFactory接口,之前已经提到过这个接口是ParserQuery的一个抽象工厂,因此实现这个接口的ParserGrammar抽象类也自然会生产出这么一个ParserQuery的对象,这一点在后面可以看得出。

接下来分析该抽象类的具体定义,首先是两个成员变量logger和parserQuery。到这里,已经可以理解第二个接口是如何实现的了,通过这个接口对象作为一个公有抽象变量,这个抽象变量就可以在日后对ParserGrammar抽象类进行解析的时候具体实现这一个接口对象。而静态变量logger则是RedwoodChannels类的对象,这个类我目前还不了解,有待日后分析考证。

  private static Redwood.RedwoodChannels logger = Redwood.channels(ParserGrammar.class);

  public abstract ParserQuery parserQuery();

 该抽象类的成员方法偏多,我们具体地一个个看。众多成员函数中,最重要的就是这个tokenize方法,它将输入的字符串句子通过TokenizerFactory的getTokenizer()方法进行处理,对其进行词法分析之后切分成具体的单词装进token列表中返回。

public List<? extends HasWord> tokenize(String sentence) {
    TokenizerFactory<? extends HasWord> tf = treebankLanguagePack().getTokenizerFactory();
    Tokenizer<? extends HasWord> tokenizer = tf.getTokenizer(new StringReader(sentence));
    List<? extends HasWord> tokens = tokenizer.tokenize();
    return tokens;
  }

有了tokenize方法,parse方法便在此基础上对于生成的分词列表进行处理,使其变成树形结构。

public Tree parse(String sentence) {
    List<? extends HasWord> tokens = tokenize(sentence);
    if (getOp().testOptions.preTag) {
      Function<List<? extends HasWord>, List<TaggedWord>> tagger = loadTagger();
      tokens = tagger.apply(tokens);
    }
    return parse(tokens);
  }

apply方法则通过parse方法对于词语列表同样进行了处理。

public Tree apply(List<? extends HasWord> words) {
    return parse(words);
  }

这时候,类中又定义了两个临时变量,分别是函数接口对象tagger和字符串taggerPath。有了这两个临时变量,接下来的方法定义才有他的意义所在。

private transient Function<List<? extends HasWord>, List<TaggedWord>> tagger;
  private transient String taggerPath;

基于以上两个临时变量,我们可以分析以下的具体函数方法loadTagger以及重载方法lemmatize。由于对于Options类的不了解,暂时跳过对于loadTagger的分析,而对于重载函数lemmatize,则可以根据其注释得知,这是一个用于对词性进行归类分析的分析方法,输入字符串则是非英语分析,输入列表则是英语分析。

public Function<List<? extends HasWord>, List<TaggedWord>> loadTagger() {
    Options op = getOp();
    if (op.testOptions.preTag) {
      synchronized(this) { // TODO: rather coarse synchronization
        if (!op.testOptions.taggerSerializedFile.equals(taggerPath)) {
          taggerPath = op.testOptions.taggerSerializedFile;
          tagger = ReflectionLoading.loadByReflection("edu.stanford.nlp.tagger.maxent.MaxentTagger", taggerPath);
        }
        return tagger;
      }
    } else {
      return null;
    }
  }

  public List<CoreLabel> lemmatize(String sentence) {
    List<? extends HasWord> tokens = tokenize(sentence);
    return lemmatize(tokens);
  }

  /**
   * Only works on English, as it is hard coded for using the
   * Morphology class, which is English-only
   */
  public List<CoreLabel> lemmatize(List<? extends HasWord> tokens) {
    List<TaggedWord> tagged;
    if (getOp().testOptions.preTag) {
      Function<List<? extends HasWord>, List<TaggedWord>> tagger = loadTagger();
      tagged = tagger.apply(tokens);
    } else {
      Tree tree = parse(tokens);
      tagged = tree.taggedYield();
    }
    Morphology morpha = new Morphology();
    List<CoreLabel> lemmas = Generics.newArrayList();
    for (TaggedWord token : tagged) {
      CoreLabel label = new CoreLabel();
      label.setWord(token.word());
      label.setTag(token.tag());
      morpha.stem(label);
      lemmas.add(label);
    }
    return lemmas;
  }

剩下的都是抽象方法了,有待实现类定义。

public abstract Tree parse(List<? extends HasWord> words);

public abstract Tree parseTree(List<? extends HasWord> words);

public abstract List<Eval> getExtraEvals();

public abstract List<ParserQueryEval> getParserQueryEvals();

public abstract Options getOp();

public abstract TreebankLangParserParams getTLPParams();

public abstract TreebankLanguagePack treebankLanguagePack();

public abstract String[] defaultCoreNLPFlags();

public abstract void setOptionFlags(String ... flags);

public abstract boolean requiresTags();

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值