如何设计好词袋模型的类类型
Bag of words,也叫做“词袋”,在信息检索中,Bag of words model假定对于一个文本,忽略其词序和语法,句法,将其仅仅看做是一个词集合,或者说是词的一个组合,文本中每个词的出现都是独立的,不依赖于其他词是否出现,或者说当这篇文章的作者在任意一个位置选择一个词汇都不受前面句子的影响而独立选择的。
这种假设虽然对自然语言进行了简化,便于模型化,但是其假定在有些情况下是不合理的,例如在新闻个性化推荐中,采用Bag of words的模型就会出现问题。例如用户甲对“南京醉酒驾车事故”这个短语很感兴趣,采用bag of words忽略了顺序和句法,则认为用户甲对“南京”、“醉酒”、“驾车”和“事故”感兴趣,因此可能推荐出和“南京”,“公交车”,“事故”相关的新闻,这显然是不合理的。
勤劳的人会有各种幸运,懒惰的人则只有一种不幸。
勤劳应以所做的工作为尺度。
回顾过去自己写过的一些词袋模型,比如 BoW图像检索Python实战 、 图像检索(CBIR)三剑客之BoF、VLAD、FV 以及Bag of Words cpp实现,这些写出来的要么只是助于自己理解词袋模型的有关理论,要么也只是面向实验的一些验证,或者更直接点可以说只是些小玩具摆了。
在我2016年的计划列表里,存放着一条由2015年拖过来的目标,就是写出一个可以面向商业级别的词袋模型,这条计划伴随着成功将VLfeat的一些c接口打通而变成了可能,并且在过去的大半年里,自己也一直留意在具体编写的时候选用哪些库比较合适的问题。机缘巧合,这一段时间又重新开始造起了轮子,并有了初步的成功,所以在此梳理总结一下。在谈怎样设计一个词袋模型的类类型之前,先谈谈库的选用问题。
选取合适的库
在具体编写一个面向应用级别的词袋模型的时候,大概会经历这么几个步骤:SIFT特征抽取,特征采样,聚类,构建KD树,统计词频,计算词频权重,计算词频直方图,保存数据。这8个步骤在具体实现的时候,会设计到一些库的选取问题,下面对其进行细谈。
1) SIFT特征抽取提取选用哪个库?
提取SIFT的库有很多,主要有以下几个大家用得比较多:
Lowe的 SIFT ,效果只提供SIFT的二进制可执行文件,弃用;
Robwhess的 OpenSIFT ,开源,效果也还不错,需要一些别的依赖库,不再更新,弃用;
OpenCV的SIFT,这个当然在使用上是最方便的,文档全,不依赖别的库,但SIFT的实现效果并不是很好,弃用;
VLfeat里的 SIFT ,SIFT的实现效果是比较好的,缺点是c接口文档不怎么全,网上提供的资料也比较少,但多读读它的C源码,还是可以搞定的,而且在不用依赖其他的库,所以选择这个库来提取SIFT还是很好的,在实际提取的时候,我选用的是 covdet 函数来提取SIFT,它是一个功能更强大的提取co-variant特征提取器。
在去年的时候,基本弄清了VLfeat中的一些函数的C接口调用方式,covdet这个函数通过阅读写给matlab的接口源码转成的C,对比matlab提取的结果和自己转成C之后提取的结果,两者完全一致。
2) 矩阵运算库的选取
虽然使用矩阵操作并不是必须的,除了OpenCV的矩阵,还有可能引入其他的矩阵运算库,这些矩阵的引入会给后面的实现带来巨大的方便,比如聚类,KD树的构建以及后面词频统计等。作为运算的基础库,在矩阵库的选择上主要有下面几个用得比较多:
Eigen ,使用的只需要把头文件包含进工程里即可,提供了多个平台的版本,比如可以运行于安卓上,矩阵运算操作还是比较方便的,更新得比较快,不过在PC平台上开发,我比较倾向于使用下面要说的Armadillo。
Armadillo ,这个库是我非常喜欢的矩阵运算库,此矩阵库在使用语法上Matlabjie借鉴了Matlab的语法使用习惯,所以熟悉Matlab的开发者在使用此库的时候会觉得非常的舒服,并且有名的MLPack是建立在它的基础之上,另外它的矩阵运算效率也是非常高的,使用的时候同Eigen一样只需要包含头文件目录即可,最新版本中添加了KMeans聚类。因而,基于以上这些优点,在实现词袋模型的时候,对于矩阵运算库的选取,选择这个无疑是最优的。
选用矩阵库虽然能极大的方便我们的程序编写,但是会涉及到数据类型的转换,比如STL的vector存储的数据要转成Armadillo的矩阵进行运算,如果数据频繁的进行类型的转换,必然会降低程序的运行效率,因而在程序的编写中,不必要转换的尽量避免转换。
3) 多线程并行处理
为了使程序的SIFT特征提取、KMeans聚类、统计词频等过程支持并行处理,在选择并行计算库的时候,有两种选择,一种是采用OpenMP,另一种是选择MPI。OpenMP是采用的是内存共享的方式,只需要对原程序进行小幅调整即可实现并行处理,并且语法易读已写;MPI需要对原来的程序进行大幅重构,写出来的代码也不是很好读。所以,在多线程并处计算库选择这块,选择OpenMP是比较好的。
词袋模型的类类型设计
终于可以讲核心的了,这一部分讲讲在编写程序的时候自己对词袋模型的类类型设计的一点心得。先上自己写的词袋模型的类类型,设计了两个类,一个是 SIFT特征提取的类类型 ,另一个是 词袋模型的类类型 。先谈谈 SIFT特征提取的类类型 :
class siftDesctor{
public:
siftDesctor(){};
std::string imageName;
std::vector<std::vector<float>> frame;
std::vector<std::vector<float>> desctor;
void covdet_keypoints_and_descriptors(cv::Mat &img, std::vector<std::vector<float>> &frames, std::vector<std::vector<float>> &desctor, bool rooSIFT, bool verbose);
std::vector<float> rootsift(std::vector<float> &dst);
void Serialize(std::ofstream &outfile) const {
std::string tmpImageName = imageName;
int strSize = (int)imageName.size();
outfile.write((char *)&strSize, sizeof(int));
outfile.write((char *)&tmpImageName[0], sizeof(char)*strSize); // 写入文件名
int descSize = (int)desctor.size();
outfile.write((char *)&descSize, sizeof(int));
// 写入sift特征
for(int i = 0; i < descSize; i++ ){
outfile.write((char *)&(desctor[i][0]), sizeof(float) * 128);
outfile.write((char *)&(frame[i][0]), sizeof(float) * 6);
}
}
static siftDesctor Deserialize(std::ifstream &ifs) {
siftDesctor siftDesc;
int strSize = 0;
ifs.read((char *)&strSize, sizeof(int)); // 写入文件名
siftDesc.imageName = "";
siftDesc.imageName.resize(strSize);
ifs.read((char *)&(siftDesc.imageName[0]), sizeof(char)*strSize); // 读入文件名
int descSize = 0;
ifs.read((char *)&descSize, sizeof(int));
// 读入sift特征和frame
for(int i = 0; i < descSize; i++ ){
std::vector<float> tmpDesc(128);
ifs.read((char *)&(tmpDesc[0]), sizeof(float) * 128);
siftDesc.desctor.push_back(tmpDesc);
std::vector<float> tmpFrame(6);
ifs.read((char *)&(tmpFrame[0]), sizeof(float) * 6);
siftDesc.frame.push_back(tmpFrame);
}
return siftDesc;
}
};
在设计SIFT特征提取的类类型的时候,对于每一幅图像,提取SIFT特征之后,由于需要保存图像名、128维的SIFT特征以及6维的frame,因为 imageName 、 desctor 和 frame 这三个成员是必须的,这里说一下对于 imageName 这个成员,在最后保存方式文件名的时候,更合理的方式是不应该带入文件所在目录路径的,因为最终写入的数据有可能转移到别的计算机上,带入路径不便于共享后使用者对数据的处理,这个地方我在刚开始设计的时候有欠考虑。另外,刚开始在设计 covdet_keypoints_and_descriptors() 方法的时候,是在方法里读入图片然后在提取特征的,当时想得是想让这样一个特征提取器更加的方便使用(只需要传入图像待路径的文件名就可以处理),但后来发现其实根本没必要这么设计这个方法,这种蹩脚的方式使得后面在显示结果的时候,你需要再次读入图片,降低了程序的执行效率,因而改成了现在的这种传入已读入数据的方式。
上面除了三个重要的成员变量,两个序列化和反序列化的方法是极其重要的,序列化的目的使得上面三个重要的成员变量得以保存,这样可以避免当我们想再次聚类时又要提取特征的尴尬;反序列化使得我们可以读取保存的数据,因而,三个成员变量加两个方法都是必不可少的。
再谈 词袋模型的类类型 ,先看类定义:
class bowModel {
public:
bowModel(){};
bowModel(int _numWords,std::vector<siftDesctor> _imgFeatures, std::vector<std::vector<int>> _words):numWords(_numWords),imgFeatures(_imgFeatures),words(_words){};
int numNeighbors = 1;
int numWords;
std::vector<siftDesctor> imgFeatures;
std::vector<std::vector<int>> words;
cv::Mat centroids_opencvMat;
cv::flann::Index opencv_buildKDTree(cv::Mat ¢roids_opencvMat);
void Serialize(std::ofstream &outfile) const {
int imgFeatsSize = (int)imgFeatures.size();
outfile.write((char *)&imgFeatsSize, sizeof(int));
// 写入imgFeatures和words
for(int i = 0; i < imgFeatsSize; i++ ){
imgFeatures[i].Serialize(outfile);
outfile.write((char *)&(words[i][0]), sizeof(int) * imgFeatures[i].desctor.size());
}
}
static bowModel Deserialize(std::ifstream &ifs) {
bowModel BoW;
int imgFeatsSize;
ifs.read((char *)&imgFeatsSize, sizeof(int));
BoW.words.resize(imgFeatsSize);
for (int i = 0; i < imgFeatsSize; i++) {
// 读入imgFeatures
auto siftDesc = siftDesctor::Deserialize(ifs);
BoW.imgFeatures.push_back(siftDesc);
// 读入words
BoW.words[i].resize(siftDesc.desctor.size());
ifs.read((char *)&(BoW.words[i][0]), sizeof(int) * siftDesc.desctor.size());
}
return BoW;
}
};
上面最重要的有三个东西,一是成员 std::vector<siftDesctor> imgFeatures ,另外是序列化和反序列化方法。对于每一个图片提取的特征,将 imageName 、 desctor 和 frame 通过实例化一个siftDesctor将其保存起来,这样我们将所有图片的siftDesctor实例用STL的vector保存下来,在序列化的时候,对每一个实例通过调用 SIFT特征提取的类类型 中定义的序列化方法将其保存下来,读取数据的时候,其过程基本就是原来的一个拟过程。通过这样设计这样两个 SIFT特征提取的类类型 和 词袋模型的类类型 ,在数据读写的时候,通过内外两重循环,内部循环完成一个实例的数据读写,外部循环完成所有图片实例的读写,使得我们可以比较优雅地完成图片的特征提取、数据保存以及读写。
对于数据读写,做过一番调研,一种是通过HDF5的方式,一种是通过BOOST库。HDF5很适合大数据量的保存,而且读写高效,但在C++里,写起来没有在Python里使用HDF5方便,BOOST也查阅过相应的资料,写起来也比较繁杂,所以最后选择了只用fstream来进行数据读写保存了,测了一下,数据读写还是比较高效的,所以暂且采用这种方案。
如何设计好词袋模型的类类型
通过这样设计这样两个 SIFT特征提取的类类型 和 词袋模型的类类型 ,在数据读写的时候,通过内外两重循环,内部循环完成一个实例的数据读写,外部循环完成所有图片实例的读写,使得我们可以比较优雅地完成图片的特征提取、数据保存以及读写。对于数据读写,做过一番调研,一种是通过HDF5的方式,一种是通过BOOST库。HDF5很...
CBOW(连续词袋模型)简介
CBOW,全称为连续词袋模型,犹如一座语言学的宝藏,是神经网络世界里一颗璀璨的明珠。由天才科学家Tomas Mikolov等人在2013年首次揭示,它旨在通过巧妙地捕捉单词间的语义与语法联系,将每个单词转化为一维度的实数向量,为理解自然语言提供了全新的视角。核心理念:上下文预测的智慧 CBOW的核心思想是基于上下文...
文本分类方法有哪些
1,基于词袋模型的特征表示:以词为单位(Unigram)构建的词袋可能就达到几万维,如果考虑二元词组(Bigram)、三元词组(Trigram)的话词袋大小可能会有几十万之多,因此基于词袋模型的特征表示通常是极其稀疏的。 (1)词袋特征的方法有三种: (2)优缺点: 2,基于embedding的特征表示: 通过词向量计算文本的特征。(主要针对短...
人工智能技术应用:情感分析概述
第四步根据分词统计词频构建词袋模型,形成特征词矩阵,如表所示。在这一步可以根据业务需要给每个特征词赋予权重,并通过词频乘以权重得到特征词分数。最后一步就是根据分类算法,将特征词矩阵作为输入数据,得到最终的分类模型。 当训练好分类模型之后,就可以对测试集进行分类了,具体的流程与建模流程类似,先对测试的文本数...
QA问句解析的七种方法及优化思路
传统的词集模型(SOW)和词袋模型(BOW)采用的即是 one-hot Representation。 我们可以采用深度学习的方法来得到词 Distributed Representation 的词向量。比如训练一个普通的神经概率语言模型,就可以得到词的词向量,或者参考 word2vec 中的方式,训练 CBOW 或者 Skip-gram 模型。神经概率语言模型、CBOW 以及 Skip-gram ...
NLP基础知识和综述
word2vec⼯具包含了两个模型,即跳字模型(skip-gram)和连续词袋模型(continuous bag of words,CBOW)。word2vec的input\/output都是将单词作为one-hot向量来表示,我们可以把word2vec认为是词的无监督学习的降维过程。 MaxEnt 模型(最大熵模型): 可以使用任意的复杂相关特征,在性能上最大熵分类器超过了 Byaes 分...
如何评价2024泰迪杯B题?
文本方面,词袋模型和词嵌入如Word2Vec、FastText是常用手段,它们能捕捉文本的语义信息。融合模型与算法:选手们需考虑如Concatenation、Bi-Encoder、Transformer、MLP和Attention等方法,以将图像和文本特征无缝融合,增强检索效果。优化策略:Triplet Loss和Contrastive Loss是常用的损失函数,它们能够帮助模型学习...
ai算法工程师要学什么
3、学习自然语言处理和计算机视觉:自然语言处理和计算机视觉是AI算法工程师需要掌握的两个重要领域。可以通过学习NLP和CV领域的经典算法和模型,如词袋模型、CNN、LSTM等,掌握相关技能。4、掌握AI算法工程实践:除了理论知识,A算法工程师还需要掌握AI算法工程实践的流程和工具如数据预处理、模型训练、模型...
向量数据库怎么构建
接下来是特征提取与向量化。在这一阶段,我们需要将清洗和格式化后的数据转换成向量形式,以便进行数学计算和比较。对于文本数据,可以使用词袋模型、TF-IDF或者更先进的词嵌入技术来将文本转换为向量。这些向量捕捉了文本的语义信息,使得相似的文本在向量空间中的距离也更近。构建索引是向量数据库构建的关键...
文本分类特征工程概述
其对应的词袋模型为:对应的2-gram模型为:其他的话本质上还是和词袋模型相同:N-gram模型的缺点是会造成更高的时空开销,维度也更加稀疏了。 关于N-gram在语言模型上的知识以后再表。 其他方法亦可另见《python自然语言处理实战:核心技术与算法》P85:tf-idf算法、TextRank算法、LSA\/LSI\/LDA算法...
柞毅紫龙: 如何设计好词袋模型的类类型 回顾过去自己写过的一些词袋模型,比如 BoW图像检索Python实战 、 图像检索(CBIR)三剑客之BoF、VLAD、FV 以及Bag of Words cpp实现,这些写出来的要么只是助于自己理解词袋模型的有关理论,要么也...
宣化县15896519226: 工业设计专业就业前景怎么样 - ?
柞毅紫龙: 工业设计专业学生主要学习工业设计的基础理论与知识,具有应用造型设计原理和法则处理各种产品的造型与色彩、形式与外观、结构与功能、结构与材料、外形与工艺、产品与人、产品与环境、市场的关系,并将这些关系统一表现在产品的造...
宣化县15896519226: 情感计算的“情感计算”的基本内容 - ?
柞毅紫龙: 人们期盼着能拥有并使用更为人性化和智能化的计算机.在人机交互中,从人操作计算机,变为计算机辅助人;从人围着计算机转,变为计算机围着人转;计算机从认知型,变为直觉型.显然,为实现这些转变,人机交互中的计算机应具有情感...
宣化县15896519226: 库存控制的模型分类 - ?
柞毅紫龙: 根据供应和需求规律确定生产和流通过程中经济合理的物资存储量的管理工作.库存管理应起缓冲作用,使物流均衡通畅,既保证正常生产和供应,又能合理压缩库存资金,以得到较好的经济效果. 1915年,美国的F·W·哈里斯发表关于经济...
宣化县15896519226: mongodb 如何设计一个有多级分类的 collection - ?
柞毅紫龙: 有效的数据模型是为应用服务的,设计构架的关键问题是文档模型适合使用嵌入式模型(embed)还是使用引用模型(references).嵌入式数据模型(Embedded Data Models) 在MongoDB中,你可能将相关数据嵌入到一个单一结构或文档,这些模式通常被称为“非正规”模型,但是它充分利用了MongoDB富文档模型的有点.嵌入式数据模型允许应用程序存储相关的信息在一条数据库记录中,这样应用程序可能需要更少的查询和更新来完成常规的操作.
宣化县15896519226: 怎样设计一个好的数据库 - ?
柞毅紫龙: 数据库设计(Database Design)是指对于一个给定的应用环境,构造最优的数据库模式,建立数据库及其应用系统,使之能够有效地存储数据,满足各种用户的应用需求(信息要求和处理要求).在数据库领域内,常常把使用数据库的各类...
宣化县15896519226: 如何进行高效的文章分类和标签的数据库设计 - ?
柞毅紫龙: 几乎在所有web项目中,都涉及文章分类和标签的设计,应该说这是一个比较常见、典型的案例.站长并不保证我的思路就是最好的,只是分享出来大家一起交流一下,互相促进与提高.我们假设的开发项目是一个博客系统,最核心的部分就是...
宣化县15896519226: 分类信息数据库设计 - ?
柞毅紫龙: ID ClassName ParentID 多级分类表如上建立 如果你还需要对信息类型进行区分就再增加一个 NewsFlag 字段1:表示图文介绍形式的信息 2:新闻列表形式 3:图片类型 等等
宣化县15896519226: 农产品品牌形象包装设计要怎么做? - ?
柞毅紫龙: 农产品品牌形象包装设计有三种风格类型 原生态形象 有时候“土”也是有道理的.现代很多的消费者追求亲近自然,喜欢那种与世无争的原生态意境.披上原生态外衣,在原生态外衣上加上点风格个性,便是设计的重点. 区域特色化 作为消费...