转自:http://www.131x.com/zhaosq/BBSShow.aspx?id=1720

词干提取算法Porter Stemming Algorithm解读 ClickNum:199|ReplyNum:2

Lucene里面的分词器里面有一个PorterStemFilter类,里就用到了著名的词干提取算法。所谓Stemming,就是词干,在英语中单词有多种变形。比如单复数加s,进行时加ing等等。在分词的时候,如果能够把这些变形单词的词根找出了,对搜索结果是很有帮助的。Stemming算法有很多了,三大主流算法是Porter stemming algorithm、Lovins stemming algorithm、Lancaster (Paice/Husk) stemming algorithm,还有一些改进的或其它的算法。这个PorterStemFilter里面调用的一个PorterStemmer就是Porter Stemming algorithm的一个实现。 其主页为http://tartarus.org/~martin/PorterStemmer/,也可查看其论文http://tartarus.org/~martin/PorterStemmer/def.txt。通过以下网页可以进行简单的测试:Porter's Stemming Algorithm Online[http://facweb.cs.depaul.edu/mobasher/classes/csc575/porter.html]。

网上找了好久,才找到一个对此算法解释的文章,它用的是Java版的代码,这里我改成用.net版的。主要是把里面的函数作了一下注释,个人没做什么分析,本身是想的,结果看着就头痛。下面的东西都是来自这篇博文波特词干算法,我只是把这里的代码改成了.net的。

接下来,是一系列工具函数。首先先介绍一下它们:

  • cons(i):参数i:int型;返回值bool型。当i为辅音时,返回真;否则为假。
/// <summary>
/// cons(i) 为真 <=> b[i] 是一个辅音
/// </summary>
private bool cons(int i)
{
switch (b[i])
{
case 'a':
case 'e':
case 'i':
case 'o':
case 'u':
return false;
case 'y':
return (i == k0) ? true : !cons(i - 1);//y开头,为辅;否则看i-1位,如果i-1位为辅,y为元,反之亦然。
default:
return true;
}
}

m() :返回值:int型。表示单词b介于0和j之间辅音序列的个度。现假设c代表辅音序列,而v代表元音序列。<..>表示任意存在。于是有如下定义;

  • <c><v>          结果为 0
  • <c>vc<v>       结果为 1
  • <c>vcvc<v>    结果为 2
  • <c>vcvcvc<v> 结果为 3
  • ....
/// <summary>
/// m() 用来计算在0和j之间辅音序列的个数
/// </summary>
/// <returns></returns>
private int m()
{
int n = 0;//辅音序列的个数,初始化
int i = k0;//偏移量
while (true)
{
if (i > j)//如果超出最大偏移量,直接返回n
return n;
if (!cons(i))//如果是元音,中断
break;
i++;//辅音移一位,直到元音的位置
}
i++;//移完辅音,从元音的第一个字符开始
while (true)//循环计算vc的个数
{
while (true)//循环判断v
{
if (i > j)
return n;
if (cons(i))
break;//出现辅音则终止循环
i++;
}
i++;
n++;
while (true)//循环判断c
{
if (i > j)
return n;
if (!cons(i))
break;
i++;
}
i++;
}
}

vowelinstem() :返回值:bool型。从名字就可以看得出来,表示单词b介于0到i之间是否存在元音。

/// <summary>
/// vowelinstem() 为真 <=> 0,...j 包含一个元音
/// </summary>
/// <returns>[To be supplied.]</returns>
private bool vowelinstem()
{
int i;
for (i = k0; i <= j; i++)
if (!cons(i))
return true;
return false;
}

doublec(j) :参数j:int型;返回值bool型。这个函数用来表示在j和j-1位置上的两个字符是否是相同的辅音。

/// <summary>
/// doublec(j) 为真 <=> j,(j-1) 包含两个一样的辅音
/// </summary>
/// <param name="j"></param>
/// <returns></returns>
private bool doublec(int j)
{
if (j < k0 + 1)
return false;
if (b[j] != b[j - 1])
return false;
return cons(j);
}

cvc(i) :参数i:int型;返回值bool型。对于i,i-1,i-2位置上的字符,它们是“辅音-元音-辅音”的形式,并且对于第二个辅音,它不能为w、x、y中的一个。这个函数用来处理以e结尾的短单词。比如说cav(e),lov(e),hop(e),crim(e)。但是像snow,box,tray就辅符合条件。

/* cvc(i) is 为真 <=> i-2,i-1,i
* 有形式: 辅音 - 元音 - 辅音
* 并且第二个c不是 w,x 或者 y.
* 这个用来处理以e结尾的短单词。
* e.g. cav(e), lov(e), hop(e), crim(e),
* 但不是 snow, box, tray. */
private bool cvc(int i)
{
if (i < k0 + 2 || !cons(i) || cons(i - 1) || !cons(i - 2))
return false;
else
{
int ch = b[i];
if (ch == 'w' || ch == 'x' || ch == 'y') return false;
}
return true;
}

ends(s) :参数:String;返回值:bool型。顾名思义,判断b是否以s结尾。

private bool ends(string s)
{
int l = s.Length;
int o = k - l + 1;
if (o < k0)
return false;
for (int i = 0; i < l; i++)
if (b[o + i] != s[i])
return false;
j = k - l;
return true;
}

setto(s) :参数:String;void类型。把b在(j+1)...k位置上的字符设为s,同时,调整k的大小。

// setto(s) 设置 (j+1),...k 到s字符串上的字符, 并且调整k值
void setto(string s)
{
int l = s.Length;
int o = j + 1;
for (int i = 0; i < l; i++)
b[o + i] = s[i];
k = j + l;
dirty = true;
}

r(s) :参数:String;void类型。在m()>0的情况下,调用setto(s)。

void r(string s) { if (m() > 0) setto(s); }

接下来,就是分六步来进行处理的过程。

第一步,处理复数,以及ed和ing结束的单词。

private void step1()
{
if (b[k] == 's')
{
if (ends("sses")) k -= 2;//以“sses结尾”
else if (ends("ies")) setto("i");//以ies结尾,置为i
else if (b[k - 1] != 's') k--;//两个s结尾不处理
}
if (ends("eed"))//以“eed”结尾,当m>0时,左移一位
{
if (m() > 0)
k--;
}
else if ((ends("ed") || ends("ing")) && vowelinstem())
{
k = j;
if (ends("at")) setto("ate");
else if (ends("bl")) setto("ble");
else if (ends("iz")) setto("ize");
else if (doublec(k))//如果有两个相同辅音
{
int ch = b[k--];
if (ch == 'l' || ch == 's' || ch == 'z')
k++;
}
else if (m() == 1 && cvc(k))
setto("e");
}
}

第二步,如果单词中包含元音,并且以y结尾,将y改为i。代码很简单:

//如果单词中包含元音,并且以y结尾,将y改为i
private void step2()
{
if (ends("y") && vowelinstem())
{
b[k] = 'i';
dirty = true;
}
}

第三步,将双后缀的单词映射为单后缀。

/* step3() 将双后缀的单词映射为单后缀。
* 所以 -ization ( = -ize 加上 -ation) 被映射到 -ize 等等。
* 注意在去除后缀之前必须确保 m() > 0. */
private void step3()
{
if (k == k0) return; /* For Bug 1 */
switch (b[k - 1])
{
case 'a':
if (ends("ational")) { r("ate"); break; }
if (ends("tional")) { r("tion"); break; }
break;
case 'c':
if (ends("enci")) { r("ence"); break; }
if (ends("anci")) { r("ance"); break; }
break;
case 'e':
if (ends("izer")) { r("ize"); break; }
break;
case 'l':
if (ends("bli")) { r("ble"); break; }
if (ends("alli")) { r("al"); break; }
if (ends("entli")) { r("ent"); break; }
if (ends("eli")) { r("e"); break; }
if (ends("ousli")) { r("ous"); break; }
break;
case 'o':
if (ends("ization")) { r("ize"); break; }
if (ends("ation")) { r("ate"); break; }
if (ends("ator")) { r("ate"); break; }
break;
case 's':
if (ends("alism")) { r("al"); break; }
if (ends("iveness")) { r("ive"); break; }
if (ends("fulness")) { r("ful"); break; }
if (ends("ousness")) { r("ous"); break; }
break;
case 't':
if (ends("aliti")) { r("al"); break; }
if (ends("iviti")) { r("ive"); break; }
if (ends("biliti")) { r("ble"); break; }
break;
case 'g':
if (ends("logi")) { r("log"); break; }
break;
}
}

第四步,处理-ic-,-full,-ness等等后缀。和步骤3有着类似的处理。

/* step4() deals with -ic-, -full, -ness etc. similar strategy to step3. */
//处理-ic-,-full,-ness等等后缀。和步骤3有着类似的处理。
private void step4()
{
switch (b[k])
{
case 'e':
if (ends("icate")) { r("ic"); break; }
if (ends("ative")) { r(""); break; }
if (ends("alize")) { r("al"); break; }
break;
case 'i':
if (ends("iciti")) { r("ic"); break; }
break;
case 'l':
if (ends("ical")) { r("ic"); break; }
if (ends("ful")) { r(""); break; }
break;
case 's':
if (ends("ness")) { r(""); break; }
break;
}
}

第五步,在<c>vcvc<v>情形下,去除-ant,-ence等后缀。

//step5() takes off -ant, -ence etc., in context <c>vcvc<v>.
//在<c>vcvc<v>情形下,去除-ant,-ence等后缀。
private void step5()
{
if (k == k0) return; /* for Bug 1 */
switch (b[k - 1])
{
case 'a':
if (ends("al")) break;
return;
case 'c':
if (ends("ance")) break;
if (ends("ence")) break;
return;
case 'e':
if (ends("er")) break; return;
case 'i':
if (ends("ic")) break; return;
case 'l':
if (ends("able")) break;
if (ends("ible")) break; return;
case 'n':
if (ends("ant")) break;
if (ends("ement")) break;
if (ends("ment")) break;
/* element etc. not stripped before the m */
if (ends("ent")) break;
return;
case 'o':
if (ends("ion") && j >= 0 && (b[j] == 's' || b[j] == 't')) break;
/* j >= 0 fixes Bug 2 */
if (ends("ou")) break;
return;
/* takes care of -ous */
case 's':
if (ends("ism")) break;
return;
case 't':
if (ends("ate")) break;
if (ends("iti")) break;
return;
case 'u':
if (ends("ous")) break;
return;
case 'v':
if (ends("ive")) break;
return;
case 'z':
if (ends("ize")) break;
return;
default:
return;
}
if (m() > 1)
k = j;
}

第六步,也就是最后一步,在m()>1的情况下,移除末尾的“e”。

// step6() removes a final -e if m() > 1.
//也就是最后一步,在m()>1的情况下,移除末尾的“e”。
private void step6()
{
j = k;
if (b[k] == 'e')
{
int a = m();
if (a > 1 || a == 1 && !cvc(k - 1))
k--;
}
if (b[k] == 'l' && doublec(k) && m() > 1)
k--;
}

在了解了步骤之后,我们写一个stem()方法,来完成得到词干的工作。

public bool stem(int i0)
{
k = i - 1;
k0 = i0;
if (k > k0 + 1)
{
step1(); step2(); step3(); step4(); step5(); step6();
}
// Also, a word is considered dirty if we lopped off letters
// Thanks to Ifigenia Vairelles for pointing this out.
if (i != k + 1)
dirty = true;
i = k + 1;
return dirty;
}

最后要提醒的就是,传入的单词必须是小写。关于Porter Stemmer的实现就是这些.

需要测试数据这里是样本文件。而相应的输出文件在这里。更多内容请参考官方网站。

另外,波特词干算法有第二个版本,它的处理结果要比文中所介绍的算法准确度高,但是,相应地也就更复杂,消耗的时间也就更多。本文就不作解释,详细参考官方网站The Porter2 stemming algorithm。

这里有一个关于此算法的应用:WordCloud - A Squarified Treemap of Word Frequency

以上的解释转自前面所说的博客,你可以在本文最后的参考资料中找到链接.

这是整个PorterStemmer类的代码:

View Code

参考资料:

1.Porter stemming algorithm

2.波特词干算法

3.Lucene源码及自带的注释

词干提取算法Porter Stemming Algorithm解读相关推荐

  1. Porter Algorithm ---------词干提取算法

    在英语中,一个单词常常是另一个单词的"变种",如:happy=>happiness,这里happy叫做happiness的词干(stem).在信息检索系统中,我们常常做的一件 ...

  2. (1)英文分词——波特词干提取算法

    英文分词相比中文分词要简单得多,可以根据空格和标点符号来分词,然后对每一个单词进行词干还原和词形还原,去掉停用词和非英文内容.词干还原算法最经典的就是波特算法(Porter Algorithm官网ht ...

  3. Porter Stemming Algorithm

    所谓Stemming,可以称为词根化,这里有个overview.在英语这样的拉丁语系里面,单词有多种变形.比如加上-ed.-ing.-ly等等.在分词的时候,如果能够把这些变形单词的词根找出了,对搜索 ...

  4. [搜索]波特词干(Porter Streamming)提取算法详解(1)

    英语词汇由两部分构成,词干和词缀,词缀又分前缀和后缀,这里的词干提取仅只去除后缀的操作. 波特词干提取算法的原文在这里 http://tartarus.org/~martin/PorterStemme ...

  5. 词形变换和词干提取工具(英文)

    转载自: http://www.cnblogs.com/kaituorensheng/p/3437807.html 词形变换和词干提取工具(英文) 在信息检索和文本挖掘中,需要对一个词的不同形态进行归 ...

  6. java lucene词干提取_词形变换和词干提取工具(英文)

    在信息检索和文本挖掘中,需要对一个词的不同形态进行归并,即词形规范化,从而提高文本处理的效率.例如:词根run有不同的形式running.ran另外runner也和run有关.这里涉及到两个概念: 词 ...

  7. python 英语分词_英文分词算法(Porter stemmer)

    python金融风控评分卡模型和数据分析微专业课(博主亲自录制视频):http://dwz.date/b9vv 最近需要对英文进行分词处理,希望能够实现还原英文单词原型,比如 boys 变为 boy ...

  8. NLTK(3)处理文本、分词、词干提取与词形还原

    文章目录 访问文本 @字符串处理 @编码 @正则表达式 分词 @正则表达式分词(不好) Tokenize命令 @自定义函数 规范化文本 将文本转换为小写 查找词干 @自定义函数(不好) NLTK词干提 ...

  9. [搜索]波特词干(Porter Streamming)提取算法详解(2)

     接[搜索]波特词干(Porter Streamming)提取算法详解(1), http://blog.csdn.net/zhanghaiyang9999/article/details/4162 ...

  10. [搜索]波特词干(Porter Streamming)提取算法详解(3)

     接上 [搜索]波特词干(Porter Streamming)提取算法详解(2) 下面分为5大步骤来使用前面提到的替换条件来进行词干提取. 左边是规则,右边是提取成功或者失败的例子(用小写字母表示 ...

最新文章

  1. 深度整合英特尔傲腾,SmartX首发100us级超低延迟超融合解决方案
  2. 虚拟化之安装Xen实例
  3. [deviceone开发]-日程日历示例
  4. 【Spark篇】---Spark初始
  5. SQL语句中各个部分的执行顺序(转)
  6. PHP代码为什么不能直接保存HTML文件——PHP生成静态页面教程
  7. c#2.0的新特性--泛型
  8. oracle单行子查询返回多个行 order by,单行子查询返回多个行 Issue分析求助
  9. 如何彻底卸载AutoCAD 2018版
  10. 什么是M1、M2以及与资本市场的关系
  11. Python.习题八 文件与与异常(上)
  12. 通过线程八锁问题融会贯通synchronized关键字的使用
  13. 使用刻录机时的注意事项
  14. 河南省历年高考人数(2004-2021)
  15. Media Queries之Respond.js
  16. Hall Schematic
  17. golang随机数生成——关于rand.Seed的一点记录
  18. Mybatis——自定义映射ResultMap
  19. 输入x,根据以下函数关系,对输入的x值,计算出相应的y值。
  20. excel合并多个工作表_excel汇总多个工作表数据的神器——合并计算

热门文章

  1. 高阶常微分方程的求解
  2. 【Python百日基础系列】Day73 - dash实例:系统发育树
  3. 什么是双线服务器?只是双线路接入?
  4. Win11 2022 Edge浏览器解决教资报名(浏览器不兼容)问题
  5. Win 10 + Ubuntu 18.04双系统 卸载Ubuntu
  6. 【EverydaySport】健身笔记——人体肌肉分解图
  7. 遥感辐射亮度单位转换
  8. 概率论的学习和整理--番外4:学习期望之前,先学习平均数(包括算术平均数,几何平均数,调和平均数等),众数,中位数等概念差别。
  9. 校友故事|我在科大感受理工科“严谨的浪漫主义”
  10. 金融衍生物英语名词的对应中文解析