概述
hanLP探索——语义距离计算的实现
在翻看hanLP源码时,看见了计算词语语义距离的方法,不由得引起了我强烈的好奇心,是什么样的逻辑可以计算词语语义之间的距离。
探索
在jar包中有一个类com.hankcs.hanlp.dictionary.CoreSynonymDictionary ,根据注释可以知道这个类是与核心同义词词典相关的,其中有方法distance()可以计算词语距离,看代码。
/**
* 语义距离
* @param itemA
* @param itemB
* @return
*/
public static long distance(CommonSynonymDictionary.SynonymItem itemA, CommonSynonymDictionary.SynonymItem itemB)
{
return itemA.distance(itemB);
}
/**
* 判断两个单词之间的语义距离
* @param A
* @param B
* @return
*/
public static long distance(String A, String B)
{
CommonSynonymDictionary.SynonymItem itemA = get(A);
CommonSynonymDictionary.SynonymItem itemB = get(B);
if (itemA == null || itemB == null) return Long.MAX_VALUE;
return distance(itemA, itemB);
}
从中可以很明显的看出,这里调用的方法是CommonSynonymDictionary.SynonymItem.distance(),继续寻找。
/**
* 语义距离
* @param other
* @return
*/
public long distance(Synonym other)
{
return Math.abs(id - other.id);
}
一步步深入之后,找到了如上的代码。从代码中可以看出,计算两词语的语义距离只是取词语的id做差绝对值的计算就可以得到,并没有设想的特别算法逻辑。那么这里的id又是怎么得来的?
经过寻找,发现这些词语都是从核心同义词字典文件中得到的。如下是字典文件CoreSynonym.txt内容:
Aa01A01= 人 士 人物 人士 人氏 人选
Aa01A02= 人类 生人 全人类
Aa01A03= 人手 人员 人口 人丁 口 食指
Aa01A04= 劳力 劳动力 工作者
Aa01A05= 匹夫 个人
Aa01A06= 家伙 东西 货色 厮 崽子 兔崽子 狗崽子 小子 杂种 畜生 混蛋 王八蛋 竖子 鼠辈 小崽子
Aa01A07= 者 手 匠 客 主 子 家 夫 翁 汉 员 分子 鬼 货 棍 徒
Aa01A08= 每人 各人 每位
Aa01A09= 该人 此人
Aa01B01= 人民 民 国民 公民 平民 黎民 庶 庶民 老百姓 苍生 生灵 生人 布衣 白丁 赤子 氓 群氓 黔首 黎民百姓 庶人 百姓 全民 全员 萌
Aa01B02= 群众 大众 公众 民众 万众 众生 千夫
Aa01B03# 良民 顺民
Aa01B04# 遗民 贱民 流民 游民 顽民 刁民 愚民 不法分子 孑遗
Aa01C01= 众人 人人 人们
Aa01C02= 人丛 人群 人海 人流 人潮
Aa01C03= 大家 大伙儿 大家伙儿 大伙 一班人 众家 各户
Aa01C04= 们 辈 曹 等
Aa01C05@ 众学生
Aa01C06# 妇孺 父老兄弟 男女老少 男女老幼
Aa01C07# 党群 干群 军民 工农兵 劳资 主仆 宾主 僧俗 师徒 师生 师生员工 教职员工 群体 爱国志士 党外人士 民主人士 爱国人士 政群 党政群 非党人士 业内人士 工农分子 军警民 党政军民
Aa01D01@ 角色
Aa02A01= 我 咱 俺 余 吾 予 侬 咱家 本人 身 个人 人家 斯人
文件中将词语做了分类,同一行的词语语义相近或相同,位于行首的字符串应该就是id了。那这些字符串是如何参与计算的?继续寻找。
public boolean load(InputStream inputStream)
{
trie = new DoubleArrayTrie<SynonymItem>();
TreeMap<String, SynonymItem> treeMap = new TreeMap<String, SynonymItem>();
String line = null;
try
{
BufferedReader bw = new BufferedReader(new InputStreamReader(inputStream, "UTF-8"));
ArrayList<Synonym> synonymList = null;
//这里是从核心同义词字典文件中按照行读取内容
while ((line = bw.readLine()) != null)
{
//这里将读取到内容按照空格分割开了
String[] args = line.split(" ");
//这里调用了Synonym.create()方法,构建了词语的数组,这里应该就是关键
synonymList = Synonym.create(args);
char type = args[0].charAt(args[0].length() - 1);
for (Synonym synonym : synonymList)
{
treeMap.put(synonym.realWord, new SynonymItem(synonym, synonymList, type));
// 这里稍微做个test
//assert synonym.getIdString().startsWith(line.split(" ")[0].substring(0, line.split(" ")[0].length() - 1)) : "词典有问题" + line + synonym.toString();
}
}
bw.close();
// 获取最大语义id
if (synonymList != null && synonymList.size() > 0)
{
maxSynonymItemIdDistance = synonymList.get(synonymList.size() - 1).id - SynonymHelper.convertString2IdWithIndex("Aa01A01", 0) + 1;
}
//这里构造一个双数组trie树,便于词语的查找
int resultCode = trie.build(treeMap);
if (resultCode != 0)
{
logger.warning("构建" + inputStream + "失败,错误码" + resultCode);
return false;
}
}
catch (Exception e)
{
logger.warning("读取" + inputStream + "失败,可能由行" + line + "造成");
return false;
}
return true;
}
进入方法Synonym.create()查看具体逻辑。
/**
* @see com.hankcs.hanlp.corpus.synonym.Synonym#create(String)
* @param args
* @return
*/
public static ArrayList<Synonym> create(String[] args)
{
ArrayList<Synonym> synonymList = new ArrayList<Synonym>(args.length - 1);
//果然行首就是id,这里获取了行首的字符串
String idString = args[0];
Type type;
//这里获取了id字符串的最后一个字符,看来不同字符代表着不同的含义
switch (idString.charAt(idString.length() - 1))
{
case '=':
//这里表示同义词
type = Type.EQUAL;
break;
case '#':
//这里表示同类词
type = Type.LIKE;
break;
default:
//这里表示封闭词,没有同义词或者同类词
type = Type.SINGLE;
break;
}
/*
这里将id字符串转换为了数字,里面的规则是:
id =
(idString.charAt(0) - 'A') * 26L * 10 * 10 * 26 * 10 * 10 +
(idString.charAt(1) - 'a') * 10 * 10 * 26 * 10 * 10 +
(idString.charAt(2) - '0') * 10 * 26 * 10 * 10 +
(idString.charAt(3) - '0') * 26 * 10 * 10 +
(idString.charAt(4) - 'A') * 10 * 10 +
(idString.charAt(5) - '0') * 10 +
(idString.charAt(6) - '0') ;
// 编码等号前面的
*/
long startId = SynonymHelper.convertString2IdWithIndex(idString, 0);
// id从这里开始
for (int i = 1; i < args.length; ++i)
{
if (type == Type.LIKE)
{
synonymList.add(new Synonym(args[i], startId + i, type));
// 如果不同则id递增
}
else
{
synonymList.add(new Synonym(args[i], startId, type));
// 如果相同则不变
}
}
return synonymList;
}
结论
至此,弄清楚了hanLP中语义距离计算的整个逻辑,首先读取已有的核心同义词文件CoreSynonym.txt,按照特定的规则构建trie树,计算词语语义距离时,即从trie树中查找词语,计算两词语id的差绝对值即为词语距离,如果没有记录则直接返回Long.MAX_VALUE。其中并没有特定的算法计算逻辑,核心同义词文件是计算的基础。其中的tire树(又叫单词查找树)类似于数据结构中的哈夫曼树,可以高效的查找词语。
最后
以上就是彪壮导师为你收集整理的hanLP探索-语义距离计算的实现hanLP探索——语义距离计算的实现的全部内容,希望文章能够帮你解决hanLP探索-语义距离计算的实现hanLP探索——语义距离计算的实现所遇到的程序开发问题。
如果觉得靠谱客网站的内容还不错,欢迎将靠谱客网站推荐给程序员好友。
发表评论 取消回复