ictclas4j如何自定义用户词典

ictclas4j如何自定义用户词典,第1张

下载 ictclas4j 看了下源码,正找示例,org.ictclas4j.run.SegMain 可以运行。分词的核心逻辑在org.ictclas4j.segment.Segment 的 split(String src) 方法中。运行 SegMain 的结果是一串字符串(带有词性标注),细看了 Segment 与 org.ictclas4j.bean.SegResult 没看到一个个分好的词。这样就比较难以扩展成为 lucene 的分词器。555,接下还是 hack 一下。

hack 的突破口的它的最终结果,在 SegResult 类里的 finalResult 字段记录。 在Segment.split(String src) 生成。慢慢看代码找到 outputResult(ArrayList<SegNode>wrList) 方法把一个个分好的词拼凑成 string。我们可以修改这个方法把一个个分好的词收集起来。下面是 hack 的过程。

1、修改 Segment:

1)把原来的outputResult(ArrayList<SegNode>wrList) 复制为 outputResult(ArrayList<SegNode>wrList, ArrayList<String>words) 方法,并添加收集词的内容,最后为:

// 根据分词路径生成分词结果

private String outputResult(ArrayList<SegNode>wrList, ArrayList<String>words) {

String result = null

String temp=null

char[] pos = new char[2]

if (wrList != null &amp&ampwrList.size() >0) {

result = ""

for (int i = 0i <wrList.size()i++) {

SegNode sn = wrList.get(i)

if (sn.getPos() != POSTag.SEN_BEGIN &amp&ampsn.getPos() != POSTag.SEN_END) {

int tag = Math.abs(sn.getPos())

pos[0] = (char) (tag / 256)

pos[1] = (char) (tag % 256)

temp=""+pos[0]

if(pos[1]>0)

temp+=""+pos[1]

result += sn.getSrcWord() + "/" + temp + " "

if(words != null) { //chenlb add

words.add(sn.getSrcWord())

}

}

}

}

return result

}

2)原来的outputResult(ArrayList<SegNode>wrList) 改为:

//chenlb move to outputResult(ArrayList<SegNode>wrList, ArrayList<String>words)

private String outputResult(ArrayList<SegNode>wrList) {

return outputResult(wrList, null)

}

3)修改调用outputResult(ArrayList<SegNode>wrList)的地方(注意不是所有的调用),大概在 Segment 的126行 String optResult = outputResult(optSegPath)改为 String optResult = outputResult(optSegPath, words)当然还要定义ArrayList<String>words了,最终 Segment.split(String src) 如下:

public SegResult split(String src) {

SegResult sr = new SegResult(src)// 分词结果

String finalResult = null

if (src != null) {

finalResult = ""

int index = 0

String midResult = null

sr.setRawContent(src)

SentenceSeg ss = new SentenceSeg(src)

ArrayList<Sentence>sens = ss.getSens()

ArrayList<String>words = new ArrayList<String>() //chenlb add

for (Sentence sen : sens) {

logger.debug(sen)

long start=System.currentTimeMillis()

MidResult mr = new MidResult()

mr.setIndex(index++)

mr.setSource(sen.getContent())

if (sen.isSeg()) {

// 原子分词

AtomSeg as = new AtomSeg(sen.getContent())

ArrayList<Atom>atoms = as.getAtoms()

mr.setAtoms(atoms)

System.err.println("[atom time]:"+(System.currentTimeMillis()-start))

start=System.currentTimeMillis()

// 生成分词图表,先进行初步分词,然后进行优化,最后进行词性标记

SegGraph segGraph = GraphGenerate.generate(atoms, coreDict)

mr.setSegGraph(segGraph.getSnList())

// 生成二叉分词图表

SegGraph biSegGraph = GraphGenerate.biGenerate(segGraph, coreDict, bigramDict)

mr.setBiSegGraph(biSegGraph.getSnList())

System.err.println("[graph time]:"+(System.currentTimeMillis()-start))

start=System.currentTimeMillis()

// 求N最短路径

NShortPath nsp = new NShortPath(biSegGraph, segPathCount)

ArrayList<ArrayList<Integer>>bipath = nsp.getPaths()

mr.setBipath(bipath)

System.err.println("[NSP time]:"+(System.currentTimeMillis()-start))

start=System.currentTimeMillis()

for (ArrayList<Integer>onePath : bipath) {

// 得到初次分词路径

ArrayList<SegNode>segPath = getSegPath(segGraph, onePath)

ArrayList<SegNode>firstPath = AdjustSeg.firstAdjust(segPath)

String firstResult = outputResult(firstPath)

mr.addFirstResult(firstResult)

System.err.println("[first time]:"+(System.currentTimeMillis()-start))

start=System.currentTimeMillis()

// 处理未登陆词,进对初次分词结果进行优化

SegGraph optSegGraph = new SegGraph(firstPath)

ArrayList<SegNode>sns = clone(firstPath)

personTagger.recognition(optSegGraph, sns)

transPersonTagger.recognition(optSegGraph, sns)

placeTagger.recognition(optSegGraph, sns)

mr.setOptSegGraph(optSegGraph.getSnList())

System.err.println("[unknown time]:"+(System.currentTimeMillis()-start))

start=System.currentTimeMillis()

// 根据优化后的结果,重新进行生成二叉分词图表

SegGraph optBiSegGraph = GraphGenerate.biGenerate(optSegGraph, coreDict, bigramDict)

mr.setOptBiSegGraph(optBiSegGraph.getSnList())

// 重新求取N-最短路径

NShortPath optNsp = new NShortPath(optBiSegGraph, segPathCount)

ArrayList<ArrayList<Integer>>optBipath = optNsp.getPaths()

mr.setOptBipath(optBipath)

// 生成优化后的分词结果,并对结果进行词性标记和最后的优化调整处理

ArrayList<SegNode>adjResult = null

for (ArrayList<Integer>optOnePath : optBipath) {

ArrayList<SegNode>optSegPath = getSegPath(optSegGraph, optOnePath)

lexTagger.recognition(optSegPath)

String optResult = outputResult(optSegPath, words)//chenlb changed

mr.addOptResult(optResult)

adjResult = AdjustSeg.finaAdjust(optSegPath, personTagger, placeTagger)

String adjrs = outputResult(adjResult)

System.err.println("[last time]:"+(System.currentTimeMillis()-start))

start=System.currentTimeMillis()

if (midResult == null)

midResult = adjrs

break

}

}

sr.addMidResult(mr)

} else {

midResult = sen.getContent()

words.add(midResult) //chenlb add

}

finalResult += midResult

midResult = null

}

sr.setWords(words)//chenlb add

sr.setFinalResult(finalResult)

DebugUtil.output2html(sr)

logger.info(finalResult)

}

return sr

}

4)Segment中的构造方法,词典路径分隔可以改为"/"

5)同时修改了一个漏词的 bug,请看:ictclas4j的一个bug

2、修改 SegResult:

添加以下内容:

private ArrayList<String>words //记录分词后的词结果,chenlb add

/**

* 添加词条。

* @param word null 不添加

* @author chenlb 2009-1-21 下 午05:01:25

*/

public void addWord(String word) {

if(words == null) {

words = new ArrayList<String>()

}

if(word != null) {

words.add(word)

}

}

public ArrayList<String>getWords() {

return words

}

public void setWords(ArrayList<String>words) {

this.words = words

}

下面是创建 ictclas4j 的 lucene analyzer

1、新建一个ICTCLAS4jTokenizer类:

package com.chenlb.analysis.ictclas4j

import java.io.IOException

import java.io.Reader

import java.util.ArrayList

import org.apache.lucene.analysis.Token

import org.apache.lucene.analysis.Tokenizer

import org.ictclas4j.bean.SegResult

import org.ictclas4j.segment.Segment

/**

* ictclas4j 切词

*

* @author chenlb 2009-1-23 上午11:39:10

*/

public class ICTCLAS4jTokenizer extends Tokenizer {

private static Segment segment

private StringBuilder sb = new StringBuilder()

private ArrayList<String>words

private int startOffest = 0

private int length = 0

private int wordIdx = 0

public ICTCLAS4jTokenizer() {

words = new ArrayList<String>()

}

public ICTCLAS4jTokenizer(Reader input) {

super(input)

char[] buf = new char[8192]

int d = -1

try {

while((d=input.read(buf)) != -1) {

sb.append(buf, 0, d)

}

} catch (IOException e) {

e.printStackTrace()

}

SegResult sr = seg().split(sb.toString()) //分词

words = sr.getWords()

}

public Token next(Token reusableToken) throws IOException {

assert reusableToken != null

length = 0

Token token = null

if(wordIdx <words.size()) {

String word = words.get(wordIdx)

length = word.length()

token = reusableToken.reinit(word, startOffest, startOffest+length)

wordIdx++

startOffest += length

}

return token

}

private static Segment seg() {

if(segment == null) {

segment = new Segment(1)

}

return segment

}

}

2、新建一个ICTCLAS4jFilter类:

package com.chenlb.analysis.ictclas4j

import org.apache.lucene.analysis.Token

import org.apache.lucene.analysis.TokenFilter

import org.apache.lucene.analysis.TokenStream

/**

* 标点符等, 过虑.

*

* @author chenlb 2009-1-23 下午03:06:00

*/

public class ICTCLAS4jFilter extends TokenFilter {

protected ICTCLAS4jFilter(TokenStream input) {

super(input)

}

public final Token next(final Token reusableToken) throws java.io.IOException {

assert reusableToken != null

for (Token nextToken = input.next(reusableToken)nextToken != nullnextToken = input.next(reusableToken)) {

String text = nextToken.term()

switch (Character.getType(text.charAt(0))) {

case Character.LOWERCASE_LETTER:

case Character.UPPERCASE_LETTER:

// English word/token should larger than 1 character.

if (text.length()>1) {

return nextToken

}

break

case Character.DECIMAL_DIGIT_NUMBER:

case Character.OTHER_LETTER:

// One Chinese character as one Chinese word.

// Chinese word extraction to be added later here.

return nextToken

}

}

return null

}

}

3、新建一个ICTCLAS4jAnalyzer类:

package com.chenlb.analysis.ictclas4j

import java.io.Reader

import org.apache.lucene.analysis.Analyzer

import org.apache.lucene.analysis.LowerCaseFilter

import org.apache.lucene.analysis.StopFilter

import org.apache.lucene.analysis.TokenStream

/**

* ictclas4j 的 lucene 分析器

*

* @author chenlb 2009-1-23 上午 11:39:39

*/

public class ICTCLAS4jAnalyzer extends Analyzer {

private static final long serialVersionUID = 1L

// 可以自定义添加更多的过虑的词(高频无多太用处的词)

private static final String[] STOP_WORDS = {

"and", "are", "as", "at", "be", "but", "by",

"for", "if", "in", "into", "is", "it",

"no", "not", "of", "on", "or", "such",

"that", "the", "their", "then", "there", "these",

"they", "this", "to", "was", "will", "with",

"的"

}

public TokenStream tokenStream(String fieldName, Reader reader) {

TokenStream result = new ICTCLAS4jTokenizer(reader)

result = new ICTCLAS4jFilter(new StopFilter(new LowerCaseFilter(result), STOP_WORDS))

return result

}

}

下面来测试下分词效果:

文本内容:

京华时报1月23日报道 昨天,受一股来自中西伯利亚的强冷空气影响,本市出现大风降温天气,白天最高气温只有零下7摄氏度,同时伴有6到7级的偏北风。

原分词结果:

京华/nz 时/ng 报/v 1月/t 23日/t 报道/v 昨天/t ,/w 受/v 一/m 股/q 来自/v 中/f 西伯利亚/ns 的/u 强/a 冷空气/n 影响/vn ,/w 本市/r 出现/v 大风/n 降温/vn 天气/n ,/w 白天/t 最高/a 气温/n 只/d 有/v 零下/s 7/m 摄氏度/q ,/w 同时/c 伴/v 有/v 6/m 到/v 7/m 级/q 的/u 偏/a 北风/n 。/w

analyzer:

[京华] [时] [报] [1月] [23日] [报道] [昨天] [受] [一] [股] [来自] [中] [西伯利亚] [强] [冷空气] [影响] [本市] [出现] [大风] [降温] [天气] [白天] [最高] [气温] [只] [有] [零下] [7] [摄氏度] [同时] [伴] [有] [6] [到] [7] [级] [偏] [北风]

谢谢您的回复!我是要解决一个语义理解的问题。例如:我输入一句话:“I

want

to

listen

to

westlife'

my

love”要求输出歌手以及歌曲的名字。我是利用倒排索引的原理,先列出一个单子,将每个歌手以及歌曲封装成一个短语,并为其添加词性,加入用户词典。这样如果可以用分词工具分出英文短语的话,就可以直接得到答案。

ICTCLAS分词系统是个NB的系统,这几天找到了仔细试了试,效率奇高,比自己搞字典,分词算法效率不知高了很多倍,用起来也是非常简单的,BOSS需要导出自定义词库,然后用文章训练词库,不管了,反正可以使用了。DT地用hash做了两个星期,进度很慢,分析一本《天龙八部》,统计出现的词语频率(词语只是仅仅基于频率,在并没有字典的情况下无法实现智能分词!)大概要花费十几分钟,可见效率底下,而且内存200M左右。使用ICTCLAS分词系统可以高效地实现分词。下面把过程贴出来。

使用方法,首先到网上下载ICTCLAS,因为我是windows下的C++实现, 下载ICTCLAS2011_Windows_32_c,解压,里面有很好的Demo,Doc,copy API目录下的所有文件(夹)到你所在的工程,在你的源文件加上

#include "ICTCLAS50.h"

#pragma comment(lib, "ICTCLAS50.lib") //ICTCLAS50.lib库加入到工程中

//

//your code here,可参考Demo里面的代码

//

即可。

大概的函数C++函数接口都在Doc文件下的文档中:

bool ICTCLAS_Init(const char* pszInitDir=NULL)初始化函数

返回值

如果初始化成功返回true, 否则返回false. 如初始化不成功,请查看ictclas.log文件了解详细错误原因.

参数

pszInitDir:初始化路径,应包含配置文件(Configure.xml)和词典目录(Data目录)以及授权文件(user.lic). 如果这些文件及目录在系统运行当前目录下,此参数可以为null。

bool ICTCLAS_Exit( )退出,释放内存

返回值

成功返回true;否则返回false。

unsigned int ICTCLAS_ImportUserDict(const char *sFilename,eCodeType eCT)

//导入用户自定义词典

返回值

导入成功的词的个数

参数

sFilename: 用户定义词典文件

eCT:编码格式

int ICTCLAS_ParagraphProcess(const char *sParagraph,int nPaLen,eCodeType eCt,int bPOStagged,char* sResult)//对一段文字进行分词

返回值

返回结果缓冲区的指针(sResult)以及结果的长度R

参数

sParagraph: 原始文字段

nPaLen: 文字段的长度

eCodeType: 文字段的编码格式

bPOStagged: 需不需要根据标注集做标记 0 = 做标记 1 = 不标记默认为1.

sResult:输出结果

t_pstRstVec ICTCLAS_ParagraphProcessA(const char *sParagraph,int PaLen,eCodeType eCodeType,int bPOStagged,int &nRstCnt)

//处理文字段

返回值

结果vector的指针,系统调用,用户无法分配以及释放

struct stResult{

int start//start position

int length//length

#ifdef POS_TAGGER

int iPOS//POS

char sPOS[POS_SIZE]//word type

#endif

int word_ID//word_ID

int word_type//Is the word of the user's dictionary?(0-no,1-yes)

int weight// word weight

}

参数

sParagraph: 原始文字段

nPaLen: 文字段长度

eCodeType: 编码格式

bPOStagged:

需不需要根据标注集做标记 0 = 做标记 1 = 不标记默认为1.

nRstcnt:处理结果的长度值。

详细用法参见Doc文件。

bool ICTCLAS_FileProcess(const char *sSrcFilename,eCodeType eCt,const char *sDsnFilename,int bPOStagged)//处理txt文件

返回值

处理文本文件成功返回true, 否则返回false

参数

sSourceFilename: 原始处理文件

eCodeType: 原始文件编码格式

sDsnFilename:存储结果的文件名T

bPOStagged: 需不需要根据标注集做标记 0 = 做标记 1 = 不标记默认为1.

注意事项

调用此函数之前需要调用init函数成功,输出格式可以通过ICTCLAS 配置来更改,这个需要研究下配置文件。

int ICTCLAS_SetPOSmap(int nPOSmap)//设置标注集

返回值

成功为1,其他为0

参数

nPOSmap :

ICT_POS_MAP_FIRST 计算所一级标注集

ICT_POS_MAP_SECOND 计算所二级标注集 PKU_POS_MAP_SECOND 北大二级标注集 PKU_POS_MAP_FIRST 北大一级标注集

int ICTCLAS_GetWordId(const char *sWord,int nWrdLen,eCodeType eCT)

返回值

单词的ID(我觉得是词典里面的存储位置,不清楚词典的具体结构)

参数

sWord: 目标单词

nWrdLen: 单词长度

eCodeType: 编码格式

bool ICTCLAS_ResultFree ( t_pstRstVec pRetVec)

//释放调用ICTCLAS_ParagraphProcessAW得到的vector指针

返回值

成功为1,失败为0

参数

t_pstRstVec: ICTCLAS_ParagraphProcessAW得到的vector指针

总结:这些函数都很好用,我需要使用处理文件函数ICTCLAS_FileProcess我出现的问题是:单独调用这个函数没有问题,但是在MFC界面调用两个选择打开文件路径和保存结果文件路径的CFileDialog以后就会出现ICTCLAS_Init初始化失败!郁闷了半天,查看ICTCLAS.log文件,

Default Path : E:\test_ICTCLAS\test_ICTCLAS\test_ICTCLAS

start lic check.

License succeed!Cannot open user dictionary

E:\test_ICTCLAS\test_ICTCLAS\test_ICTCLAS\Data\UserDict.pdat.

Cannot open file E:\test_ICTCLAS\test_ICTCLAS\test_ICTCLAS\Data\UserDict.map.

Cannot open user dictionary E:\test_ICTCLAS\test_ICTCLAS\test_ICTCLAS\Data\UserDict.pos.

Load dictionary down!

并没有异常,加载失败是因为并没有自定义词典。

仔细排查,发现bool ICTCLAS_Init(const char* pszInitDir=NULL)有一个默认的工作路径,在没有打开CFileDialog的时候默认的路径是exe文件执行路径,但是打开以后若不进行设置,会改变工作路径!这就是为什么点击CFileDialog路径更改,找不到路径下的文件,当然无法初始化了!( pszInitDir:初始化路径,应包含配置文件(Configure.xml)和词典目录(Data目录)以及授权文件(user.lic). 如果这些文件及目录在系统运行当前目录下,此参数可以为null)

解决方案:

方案1. 在每次调用CFileDialog打开文件后重新设置工作路径

方案2. 在程序中使用绝对路径

方案3. CFileDialog的构造函数有8个参数,平时为了省事一般只是指定第一个。其实解决这个问题,只要在第四个参数dwFlags中加上OFN_NOCHANGEDIR即可

我使用第三个方法解决了^_^!后面我会贴出最终分词的代码。还有中间碰见的一些东西(关于cstring LPSCSTR char* const char * UNICODE 和 multibyte我已经被微软折磨得不行了,无力吐槽,有时间在说吧)!

MOVE ON!


欢迎分享,转载请注明来源:内存溢出

原文地址:https://54852.com/bake/11724306.html

(0)
打赏 微信扫一扫微信扫一扫 支付宝扫一扫支付宝扫一扫
上一篇 2023-05-18
下一篇2023-05-18

发表评论

登录后才能评论

评论列表(0条)

    保存