This commit is contained in:
aholic 2014-02-08 17:56:02 +08:00
commit e6ce8e23f0
8 changed files with 92 additions and 106 deletions

View File

@ -13,7 +13,7 @@ ENDIF()
ADD_SUBDIRECTORY(src)
ADD_SUBDIRECTORY(dict)
if (!${APPLE})
IF("${CMAKE_SYSTEM}" MATCHES "Linux")
ADD_SUBDIRECTORY(script)
ADD_SUBDIRECTORY(conf)
endif()

23
ChangeLog.md Normal file
View File

@ -0,0 +1,23 @@
## v2.3.1
1. 修复安装时的服务启动问题不过安装切词服务只是linux下的一个附加功能不影响核心代码。
## v2.3.0
1. 增加`KeywordExtractor.hpp`来进行关键词抽取。
2. 使用`gtest`来做单元测试。
## v2.2.0
1. 性能优化提升切词速度约6倍。
2. 其他暂时也想不起来了。
## v2.1.1 (v2.1.1之前的统统一起写在 v2.1.1里面了)
1. 完成__最大概率分词算法__和__HMM分词算法__并且将他们结合起来成效果最好的`MixSegment`
2. 进行大量的代码重构将主要的功能性代码都写成了hpp文件。
3. 使用`cmake`工具来管理项目。
4. 使用`Limonp`作为工具函数库,比如日志,字符串操作等常用函数。
5. 使用`Husky` 搭简易分词服务的服务器框架。

View File

@ -4,6 +4,10 @@
之所以全写成hpp文件是因为这样在别的项目需要使用到中文分词功能的时候直接`#include"xx.hpp" `进来就可以使用,无需麻烦的链接。
实践证明写成hpp使用起来真的很爽在后面提到的在iOS应用中的使用和包装成`Node.js`的扩展[NodeJieba]都特别顺利。
如果对代码细节感兴趣的请见 [代码详解]
## 中文编码
现在支持utf8,gbk编码的分词。
@ -12,8 +16,8 @@
### 依赖
* g++ (version >= 4.6);
* cmake (version >= 2.8);
* g++ (version >= 4.6 recommended);
* cmake (version >= 2.8 recommended);
### 下载和安装
@ -38,6 +42,8 @@ make test
### 启动服务
因为服务的后台运行需要`start-stop-daemon`在ubuntu下是自带的。但是在CentOS下就需要自己安装了。
```
#Usage: /etc/init.d/cjserver {start|stop|restart|force-reload}
#启动
@ -147,58 +153,42 @@ Full方法切出所有字典里的词语。
Query方法先使用Mix方法切词对于切出来的较长的词再使用Full方法。
### 关键词抽取
## 模块详解
```
make && ./test/keyword.demo
```
本项目主要是如下目录组成:
you will see:
### src
```
我是蓝翔技工拖拉机学院手扶拖拉机专业的。不用多久我就会升职加薪当上总经理出任CEO迎娶白富美走上人生巅峰。
->
["CEO:11.7392", "蓝翔:11.7392", "白富美:11.7392", "升职:10.8562", "加薪:10.6426"]
```
核心目录,包含主要源代码。
关键词抽取的demo代码请见`test/keyword_demo.cpp`
#### TrieManager模块
TrieManager.hpp 提供一个单例TrieManager负责管理trie树。
通过该单例获取trie树时会先判断是否已经由该字典文件生成了一颗trie树如果已有则返回已有的trie树否则重新创建一颗trie树返回。
#### Trie树
Trie.hpp 负责载入词典的trie树主要供Segment模块使用。
## 相关应用
#### Segment模块
MPSegment.hpp
(Maximum Probability)最大概率法:负责根据Trie树构建有向无环图和进行动态规划算法是分词算法的核心。
HMMSegment.hpp
是根据HMM模型来进行分词主要算法思路是根据(B,E,M,S)四个状态来代表每个字的隐藏状态。
HMM模型由dict/下面的`hmm_model.utf8`提供。
分词算法即viterbi算法。
FullSegment.hpp
枚举句子中所有可能成词的情况,找出字典里存在的即可。
#### TransCode模块
TransCode.hpp 负责转换编码类型将utf8和gbk转换成`uint16_t`类型,也负责逆转换。
### src/Husky
提供服务的框架代码,
详见: https://github.com/aszxqw/husky
### src/Limonp
主要是一些工具函数,例如字符串操作等。
直接include就可以使用。
详见: https://github.com/aszxqw/limonp
## 关于CppJieba的跨语言包装使用
### 关于CppJieba的跨语言包装使用
收到邮件询问跨语言包装(ios应用开发)使用的问题这方面我没有相关的经验建议参考如下python使用cppjieba的项目
[jannson] 开发的供 python模块调用的项目 [cppjiebapy] , 和相关讨论 [cppjiebapy'discussion] .
[jannson] 开发的供 python模块调用的项目 [cppjiebapy] , 和相关讨论 [cppjiebapy_discussion] .
### NodeJieba
如果有需要在`node.js`中使用分词,不妨试一下[NodeJieba]。
### simhash
如果有需要在处理中文文档的的相似度计算,不妨试一下[simhash]。
## 演示
http://cppjieba-webdemo.herokuapp.com/
## 客服
@ -206,12 +196,15 @@ TransCode.hpp 负责转换编码类型将utf8和gbk转换成`uint16_t`类型
## 鸣谢
"结巴中文"分词作者: SunJunyi
"结巴"中文分词作者: SunJunyi
https://github.com/fxsjy/jieba
顾名思义之所以叫CppJieba是参照SunJunyi大神的Jieba分词Python程序写成的所以饮水思源再次感谢SunJunyi。
顾名思义之所以叫CppJieba是参照Jieba分词Python程序写成的所以饮水思源再次感谢SunJunyi。
[CppJieba]:https://github.com/aszxqw/cppjieba
[jannson]:https://github.com/jannson
[cppjiebapy]:https://github.com/jannson/cppjiebapy
[cppjiebapy'discussion]:https://github.com/aszxqw/cppjieba/issues/1
[cppjiebapy_discussion]:https://github.com/aszxqw/cppjieba/issues/1
[NodeJieba]:https://github.com/aszxqw/nodejieba
[simhash]:https://github.com/aszxqw/simhash
[代码详解]:http://aszxqw.github.io/jekyll/update/2014/02/10/cppjieba-dai-ma-xiang-jie.html

View File

@ -20,7 +20,7 @@ case "$1" in
mkdir -p $RUNDIR
touch $PIDFILE
chmod 755 $RUNDIR
if start-stop-daemon --start --quiet --umask 007 --pidfile $PIDFILE --exec /bin/bash -- -c "$DAEMON $DAEMON_ARGS >> $LOGFILE 2>&1"
if start-stop-daemon --start --quiet --pidfile $PIDFILE --exec /bin/bash -- -c "$DAEMON $DAEMON_ARGS >> $LOGFILE 2>&1"
then
echo "$NAME."
else

View File

@ -138,8 +138,11 @@ namespace CppJieba
itr ++;
}
keywords.resize(MIN(topN, wordmap.size()));
partial_sort_copy(wordmap.begin(), wordmap.end(), keywords.begin(), keywords.end(), _cmp);
keywords.clear();
std::copy(wordmap.begin(), wordmap.end(), std::inserter(keywords, keywords.begin()));
topN = MIN(topN, keywords.size());
partial_sort(keywords.begin(), keywords.begin() + topN, keywords.end(), _cmp);
keywords.resize(topN);
return true;
}
private:
@ -153,7 +156,7 @@ namespace CppJieba
}
private:
static bool _cmp(const pair<string, uint>& lhs, const pair<string, uint>& rhs)
static bool _cmp(const pair<string, double>& lhs, const pair<string, double>& rhs)
{
return lhs.second > rhs.second;
}

View File

@ -1,5 +1,6 @@
SET(EXECUTABLE_OUTPUT_PATH ${PROJECT_BINARY_DIR}/test)
ADD_EXECUTABLE(segment.demo segment.cpp)
ADD_EXECUTABLE(keyword.demo keyword_demo.cpp)
ADD_EXECUTABLE(load_test load_test.cpp)
ADD_SUBDIRECTORY(unittest)

13
test/keyword_demo.cpp Normal file
View File

@ -0,0 +1,13 @@
#include "../src/KeywordExtractor.hpp"
using namespace CppJieba;
int main(int argc, char ** argv)
{
KeywordExtractor extractor("../dict/jieba.dict.utf8", "../dict/hmm_model.utf8", "../dict/idf.utf8");
string s("我是蓝翔技工拖拉机学院手扶拖拉机专业的。不用多久我就会升职加薪当上总经理出任CEO迎娶白富美走上人生巅峰。");
vector<pair<string, double> > wordweights;
size_t topN = 5;
extractor.extract(s, wordweights, topN);
cout<< s << "\n -> \n" << wordweights << endl;
return EXIT_SUCCESS;
}

View File

@ -3,63 +3,16 @@
using namespace CppJieba;
const char* KEYWORD_EXT_TEST_SENTENCE = "我来自北京邮电大学。 学号123456";
TEST(KeywordExtractorTest, Test1)
{
KeywordExtractor extractor("../dict/jieba.dict.utf8", "../dict/hmm_model.utf8", "../dict/idf.utf8");
const char* res[] = {"学号", "北京邮电大学"};
vector<string> words;
ASSERT_TRUE(extractor);
ASSERT_TRUE(extractor.extract(KEYWORD_EXT_TEST_SENTENCE, words, 2));
ASSERT_EQ(words, vector<string>(res, res + sizeof(res)/sizeof(res[0])));
string s("我是蓝翔技工拖拉机学院手扶拖拉机专业的。不用多久我就会升职加薪当上总经理出任CEO迎娶白富美走上人生巅峰。");
string res;
vector<pair<string, double> > wordweights;
size_t topN = 5;
extractor.extract(s, wordweights, topN);
res << wordweights;
ASSERT_EQ(res, "[\"CEO:11.7392\", \"蓝翔:11.7392\", \"白富美:11.7392\", \"升职:10.8562\", \"加薪:10.6426\"]");
}
TEST(KeywordExtractorTest, Test2)
{
KeywordExtractor extractor("../dict/jieba.dict.utf8", "../dict/hmm_model.utf8", "../dict/idf.utf8");
const char* res[] = {"学号", "北京邮电大学", "123456", "来自"};
vector<string> words;
ASSERT_TRUE(extractor);
ASSERT_TRUE(extractor.extract(KEYWORD_EXT_TEST_SENTENCE, words, 9));
ASSERT_EQ(words, vector<string>(res, res + sizeof(res)/sizeof(res[0])));
}
TEST(KeywordExtractorTest, Test3)
{
ifstream ifs("../test/testdata/weicheng.utf8");
ASSERT_TRUE(!!ifs);
string str((istreambuf_iterator<char>(ifs)), (istreambuf_iterator<char>()));
KeywordExtractor extractor("../dict/jieba.dict.utf8", "../dict/hmm_model.utf8", "../dict/idf.utf8");
const char* res[] = {"柔嘉", "小姐", "孙小姐", "方鸿渐", "鸿渐"};
const char* res2 = "[\"柔嘉:5611.34\", \"小姐:4268.75\", \"孙小姐:3789.41\", \"方鸿渐:3030.35\", \"鸿渐:2552.93\"]";
vector<string> keywords;
string resStr;
vector<pair<string,double> > keywords2;
extractor.extract(str, keywords, 5);
extractor.extract(str, keywords2, 5);
ASSERT_EQ(keywords, vector<string>(res, res + sizeof(res)/sizeof(res[0])));
resStr << keywords2;
ASSERT_EQ(res2, resStr);
}
//TEST(KeywordExtractorTest, Test4)
//{
// ifstream ifs("../test/testdata/weicheng.utf8");
// ASSERT_TRUE(!!ifs);
// string str((istreambuf_iterator<char>(ifs)), (istreambuf_iterator<char>()));
// KeywordExtractor extractor("../dict/jieba.dict.utf8", "../dict/hmm_model.utf8", "../dict/idf.utf8");
// //const char* res[] = {"小姐", "孙小姐", "方鸿渐", "自己", "没有"};
// vector<pair<string,double> > keywords;
// extractor.extract(str, keywords, 5);
// //print(keywords);
// string res;
// res << keywords;
// print(keywords);
// print(__LINE__);
// exit(1);
// ASSERT_EQ(res, "[\"小姐:4268.75\", \"孙小姐:3789.41\", \"方鸿渐:3030.35\", \"自己:2300.54\", \"没有:2104.27\"]");
//
//}