
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] [级] [偏] [北风]
谢谢您的回复!我是要解决一个语义理解的问题。例如:我输入一句话:“Iwant
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!
欢迎分享,转载请注明来源:内存溢出
微信扫一扫
支付宝扫一扫
评论列表(0条)