图片搜索实验

基本任务

查找资料,跑通流程

【部署方法】

  • 使用MyEclipse导入该工程,运行ImageIndexer建立索引生成forIndex文件夹
  • 将forIndex文件夹拷贝到Tomcat的安装目录下(取决于具体运行环境,有的可能需要拷贝到Tomcat_ROOT/bin/目录下) 此外,由于我在ImageIndexer.java中修改了图片存放位置,因此一定要重新运行ImageIndexer.java生成forIndex文件并拷贝过去才行(也可直接拷贝提交的forIndex文件)
  • 将图片解压至/Tomcat_ROOT/webapps/ROOT/pictures/下,
  • 访问 http://localhost:8080/ImageSearch/imagesearch.jsp 即可进行搜索显示

根据ImageIndexer中对Sogou图片分类目录的索引,对Sogou图片搜索热门查询对应图片建立索引

首先运行 Encode_gbk_to_utf8.java将gbk编码的threeMonth.xml转换为utf-8编码格式,

1
<pic id="278535" query="西藏" total_click="6977" click="14" pic_url="http://travel.bjhotel.cn/tour/Article/UploadFiles/200803/2008030608595904.jpg" page_url="http://travel.bjhotel.cn/tour/Article/fsyj/xz" locate="pictures/threeMonth/西藏/274863.jpg" />

索引结果

threeMonth.xml中对每个搜索词的描述有query,click,total_click,page_url等, 本次索引的field采取query(点击频度暂未找到合适的方法考虑到评分系统内,因此并未考虑该参数)

由于threeMonth图片集太大,而索引文件中本身有pic_url,因此,我直接采用的是将pic_url作为其picPath,同时将imageshow.jsp中的图片路径改为相对路径,确保图片正确显示(sougou图片集的ImageIndexer.java中需要在路径前加’/‘)

1
2
3
4
5
6
7
8
9
10
11
NamedNodeMap map=node.getAttributes();
Node locate=map.getNamedItem("locate");
Node url = map.getNamedItem("pic_url");
// Node bigClass=map.getNamedItem("bigClass");
// Node smallClass=map.getNamedItem("smallClass");
Node query=map.getNamedItem("query");
String absString=query.getNodeValue();
Document document = new Document();
// 采用网页链接作为图片地址
Field PicPathField = new Field( "picPath" ,url.getNodeValue(),Field.Store.YES, Field.Index.NO);
Field abstractField = new Field( "abstract" ,absString,Field.Store.YES, Field.Index.ANALYZED);

运行结果

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
process 0
process 10000
process 20000
process 30000
process 40000
process 50000
process 60000
process 70000
process 80000
process 90000
process 100000
process 110000
process 120000
process 130000
process 140000
process 150000
process 160000
process 170000
process 180000
process 190000
process 200000
process 210000
process 220000
process 230000
process 240000
process 250000
process 260000
process 270000
process 280000
process 290000
process 300000
process 310000
process 320000
process 330000
process 340000
average length = 4.73518
total 340518 documents

根据Simple实现的Lucene评分核心类,了解Lucene评分架构,在SimpleScorer.java中实现BM25模型算法(实现很简单)

给定一个查询 Q,包含了关键词 $q_1, …, q_n$, 利用BM25算法计算出的文档D的得分为:

$${\text{score}}(D,Q)=\sum {i=1}^{n}{\text{IDF}}(q{i})\cdot {\frac {f(q_{i},D)\cdot (k_{1}+1)}{f(q_{i},D)+k_{1}\cdot \left(1-b+b\cdot {\frac {|D|}{\text{avgdl}}}\right)}}$$

其中:

  • $f(q_i, D)$ 表示关键词$q_i$在文档D中的频率
  • |D| 是文档D的长度
  • avgdl 是平均文档长度
  • $k_1$ 和 b 是调节因子, 通常$k_1 \in [1.2,2.0]$ , b = 0.75 (本实验中$k_i = 2.0$)
  • $IDF(q_i)$ 是查询词$q_i$的IDF (inverse document frequency) 权重,是根据每个关键词在所有文档中出现的次数多少对该关键词的一个调整,其计算方式为:$$\text{IDF}(q_i) = \log \frac{N - n(q_i) + 0.5}{n(q_i) + 0.5}$$ 其中N代表文档总数,$n(q_i)$是包含该关键词的文档个数

查阅Lucene可知,norm的计算为lengthNorm = 1.0 / Math.sqrt(numTerms)

具体实现为:

1
2
3
4
5
6
7
8
9
// 文档termDocs中关键词qi的频率
float f_qi_D = this.termDocs.freq();
// doc对应的文档长度计算, norms[i] = 1/sqrt(length[i])
float norm = Similarity.decodeNorm(this.norms[doc]);
// 文档termDocs的长度
float length = 1 / (norm * norm);

// BM25算法
return idf * f_qi_D * (this.K1 + 1)/ (f_qi_D + this.K1 *(1 - this.b + this.b * length/this.avgLength));

利用Lucene提供的评分核心类实现VSM模型,并与BM25进行对比

原始评分算法即为VSM。

BM25和VSM算法的搜索结果没有明显差异,score值是明显不同的(这里的score值是开启了多关键词查询之后的结果)

BM25算法

1
2
3
4
5
6
7
8
9
10
11
=========下面是普通merge的结果==========
doc=15007 score=8.551983 picPath= pictures/sogou/精美壁纸/人物壁纸/港台美女/0.jpg tag= 精美壁纸 人物壁纸 港台美女
doc=15008 score=8.551983 picPath= pictures/sogou/精美壁纸/人物壁纸/港台美女/10.jpg tag= 精美壁纸 人物壁纸 港台美女
doc=15009 score=8.551983 picPath= pictures/sogou/精美壁纸/人物壁纸/港台美女/11.jpg tag= 精美壁纸 人物壁纸 港台美女
doc=15010 score=8.551983 picPath= pictures/sogou/精美壁纸/人物壁纸/港台美女/12.jpg tag= 精美壁纸 人物壁纸 港台美女
doc=15011 score=8.551983 picPath= pictures/sogou/精美壁纸/人物壁纸/港台美女/13.jpg tag= 精美壁纸 人物壁纸 港台美女
doc=15012 score=8.551983 picPath= pictures/sogou/精美壁纸/人物壁纸/港台美女/14.jpg tag= 精美壁纸 人物壁纸 港台美女
doc=15013 score=8.551983 picPath= pictures/sogou/精美壁纸/人物壁纸/港台美女/15.jpg tag= 精美壁纸 人物壁纸 港台美女
doc=15014 score=8.551983 picPath= pictures/sogou/精美壁纸/人物壁纸/港台美女/16.jpg tag= 精美壁纸 人物壁纸 港台美女
doc=15015 score=8.551983 picPath= pictures/sogou/精美壁纸/人物壁纸/港台美女/17.jpg tag= 精美壁纸 人物壁纸 港台美女
doc=15016 score=8.551983 picPath= pictures/sogou/精美壁纸/人物壁纸/港台美女/18.jpg tag= 精美壁纸 人物壁纸 港台美女

VSM算法

1
2
3
4
5
6
7
8
9
10
11
=========下面是普通merge的结果==========
doc=15007 score=5.85986 picPath= pictures/sogou/精美壁纸/人物壁纸/港台美女/0.jpg tag= 精美壁纸 人物壁纸 港台美女
doc=15008 score=5.85986 picPath= pictures/sogou/精美壁纸/人物壁纸/港台美女/10.jpg tag= 精美壁纸 人物壁纸 港台美女
doc=15009 score=5.85986 picPath= pictures/sogou/精美壁纸/人物壁纸/港台美女/11.jpg tag= 精美壁纸 人物壁纸 港台美女
doc=15010 score=5.85986 picPath= pictures/sogou/精美壁纸/人物壁纸/港台美女/12.jpg tag= 精美壁纸 人物壁纸 港台美女
doc=15011 score=5.85986 picPath= pictures/sogou/精美壁纸/人物壁纸/港台美女/13.jpg tag= 精美壁纸 人物壁纸 港台美女
doc=15012 score=5.85986 picPath= pictures/sogou/精美壁纸/人物壁纸/港台美女/14.jpg tag= 精美壁纸 人物壁纸 港台美女
doc=15013 score=5.85986 picPath= pictures/sogou/精美壁纸/人物壁纸/港台美女/15.jpg tag= 精美壁纸 人物壁纸 港台美女
doc=15014 score=5.85986 picPath= pictures/sogou/精美壁纸/人物壁纸/港台美女/16.jpg tag= 精美壁纸 人物壁纸 港台美女
doc=15015 score=5.85986 picPath= pictures/sogou/精美壁纸/人物壁纸/港台美女/17.jpg tag= 精美壁纸 人物壁纸 港台美女
doc=15016 score=5.85986 picPath= pictures/sogou/精美壁纸/人物壁纸/港台美女/18.jpg tag= 精美壁纸 人物壁纸 港台美女

扩展任务

基于语料库所提供的图片所在网页内容,对图片描述文本进行扩充,实现基于图片描述文本的图像检索

基于BM25模型实现对于查询词的分词检索(参考Lucene的MultiTermQuery对应的核心评分类实现,也可参考网上的开源代码,最终大作业时需要)

分词工具采用IKAnalyzer, 具体实现参考了IKAnalyzer中文分词器 [http://blog.sina.com.cn/s/blog_7663527601012vdg.html]一文。

一开始的实现是直接将查询词使用IKAnalyzer进行分词,然后将每个关键词分别作为搜索词去查询,然后将查询结果merge起来,按照得分高低排序。如下所示:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44

//基于Lucene实现
Analyzer analyzer = new IKAnalyzer(true);//true智能切分
StringReader reader = new StringReader(queryString);
TokenStream ts = analyzer.tokenStream("", reader);
CharTermAttribute term = ts.getAttribute(CharTermAttribute.class);
List<String> key_word = new ArrayList<String>();
while(ts.incrementToken()){
// System.out.print(term.toString()+"|");
key_word.add(term.toString());
}
System.out.println(key_word);

TopDocs[] resultsArray = new TopDocs[key_word.size()];

for(int j = 0; j < key_word.size(); ++j) {
// 针对每个搜索词去查询
resultsArray[j] = search.searchQuery(key_word.get(j), "abstract", 100);
}

// TopDocs results=search.searchQuery(queryString, "abstract", 100);
TopDocs results = TopDocs.merge(null, 100, resultsArray);

System.out.println("=========下面是普通merge的结果==========");
if (results != null) {
ScoreDoc[] hits = showList(results.scoreDocs, page);
if (hits != null) {
// tags = new String[hits.length];
// paths = new String[hits.length];
for (int i = 0; i < hits.length && i < PAGE_RESULT; i++) {
Document doc = search.getDoc(hits[i].doc);
System.out.println("doc=" + hits[i].doc + " score="
+ hits[i].score + " picPath= "
+ doc.get("picPath")+ " tag= "+doc.get("abstract"));
// tags[i] = doc.get("abstract");
// paths[i] = picDir + doc.get("picPath");
}

} else {
System.out.println("page null");
}
}else{
System.out.println("result null");
}

但是在实际操作中发现,这样的查询操作是由问题的,表现极差,无法达到搜索者预期的多次查询需求,于是通过进一步的搜索和查询相关资料,最后决定采用 MultiFieldQueryParser的方式进行多关键词查询。实现参考博客- 关键词高亮(lucene笔记) http://www.jianshu.com/p/055ddb99819d

该函数可以实现多个关键词在不同的field中进行查询的功能,同时可以设定多个关键词之间的关系:and, not, or, 这里我们采用或的逻辑关系,同时设置field为‘abstract’,具体实现如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
//待查找字符串对应的字段
String [] to_query = {queryString};
String [] fields = {"abstract"};
//Occur.MUST表示对应字段必须有查询值, Occur.MUST_NOT 表示对应字段必须没有查询值, Occur.SHOULD(结果“或”)
Occur[] occ={Occur.SHOULD};
Query query = null;
try {
query = MultiFieldQueryParser.parse(Version.LUCENE_35, to_query,fields,occ,analyzer);
System.out.println(query);
} catch (ParseException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
results = search.searchQuery(query, "abstract", 100);


System.out.println("============\n=========下面是MultiFieldQueryParser的结果==========");
if (results != null) {
ScoreDoc[] hits = showList(results.scoreDocs, page);
if (hits != null) {
tags = new String[hits.length];
paths = new String[hits.length];
for (int i = 0; i < hits.length && i < PAGE_RESULT; i++) {
Document doc = search.getDoc(hits[i].doc);
System.out.println("doc=" + hits[i].doc + " score="
+ hits[i].score + " picPath= "
+ doc.get("picPath")+ " tag= "+doc.get("abstract"));
tags[i] = doc.get("abstract");
paths[i] = picDir + doc.get("picPath");
}

} else {
System.out.println("page null");
}
}else{
System.out.println("result null");
}

实现结果对比:

搜索关键词为 港台女星

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
[港台, 女星]
org.apache.lucene.search.TopDocs@23506dfb
org.apache.lucene.search.TopDocs@75648bd9
=========下面是普通merge的结果==========
doc=15007 score=8.551983 picPath= pictures/sogou/精美壁纸/人物壁纸/港台美女/0.jpg tag= 精美壁纸 人物壁纸 港台美女
doc=15008 score=8.551983 picPath= pictures/sogou/精美壁纸/人物壁纸/港台美女/10.jpg tag= 精美壁纸 人物壁纸 港台美女
doc=15009 score=8.551983 picPath= pictures/sogou/精美壁纸/人物壁纸/港台美女/11.jpg tag= 精美壁纸 人物壁纸 港台美女
doc=15010 score=8.551983 picPath= pictures/sogou/精美壁纸/人物壁纸/港台美女/12.jpg tag= 精美壁纸 人物壁纸 港台美女
doc=15011 score=8.551983 picPath= pictures/sogou/精美壁纸/人物壁纸/港台美女/13.jpg tag= 精美壁纸 人物壁纸 港台美女
doc=15012 score=8.551983 picPath= pictures/sogou/精美壁纸/人物壁纸/港台美女/14.jpg tag= 精美壁纸 人物壁纸 港台美女
doc=15013 score=8.551983 picPath= pictures/sogou/精美壁纸/人物壁纸/港台美女/15.jpg tag= 精美壁纸 人物壁纸 港台美女
doc=15014 score=8.551983 picPath= pictures/sogou/精美壁纸/人物壁纸/港台美女/16.jpg tag= 精美壁纸 人物壁纸 港台美女
doc=15015 score=8.551983 picPath= pictures/sogou/精美壁纸/人物壁纸/港台美女/17.jpg tag= 精美壁纸 人物壁纸 港台美女
doc=15016 score=8.551983 picPath= pictures/sogou/精美壁纸/人物壁纸/港台美女/18.jpg tag= 精美壁纸 人物壁纸 港台美女
(abstract:港台 abstract:女星)
org.apache.lucene.search.TopDocs@292b2c95
============
=========下面是MultiFieldQueryParser的结果==========
doc=42133 score=20.643898 picPath= pictures/sogou/性感女星/港台女星/阿雅/0.jpg tag= 性感女星 港台女星 阿雅
doc=42134 score=20.643898 picPath= pictures/sogou/性感女星/港台女星/阿雅/10.jpg tag= 性感女星 港台女星 阿雅
doc=42135 score=20.643898 picPath= pictures/sogou/性感女星/港台女星/阿雅/11.jpg tag= 性感女星 港台女星 阿雅
doc=42136 score=20.643898 picPath= pictures/sogou/性感女星/港台女星/阿雅/12.jpg tag= 性感女星 港台女星 阿雅
doc=42137 score=20.643898 picPath= pictures/sogou/性感女星/港台女星/阿雅/13.jpg tag= 性感女星 港台女星 阿雅
doc=42138 score=20.643898 picPath= pictures/sogou/性感女星/港台女星/阿雅/14.jpg tag= 性感女星 港台女星 阿雅
doc=42139 score=20.643898 picPath= pictures/sogou/性感女星/港台女星/阿雅/15.jpg tag= 性感女星 港台女星 阿雅
doc=42140 score=20.643898 picPath= pictures/sogou/性感女星/港台女星/阿雅/16.jpg tag= 性感女星 港台女星 阿雅
doc=42141 score=20.643898 picPath= pictures/sogou/性感女星/港台女星/阿雅/17.jpg tag= 性感女星 港台女星 阿雅
doc=42142 score=20.643898 picPath= pictures/sogou/性感女星/港台女星/阿雅/18.jpg tag= 性感女星 港台女星 阿雅

MultiFieldQueryParser查询方式的搜索结果如下:

多关键词查询的靠前结果同时满足两个关键词

港台 女星  第一页

从第6页开始出现了港台美女等相关搜索结果,能够实现关键词或的查询

1
2
3
4
5
6
7
8
9
10
11
=========下面是MultiFieldQueryParser的结果==========
doc=42163 score=17.694769 picPath= pictures/sogou/性感女星/港台女星/安雅/2.jpg tag= 性感女星 港台女星 安雅
doc=42164 score=17.694769 picPath= pictures/sogou/性感女星/港台女星/安雅/3.jpg tag= 性感女星 港台女星 安雅
doc=42165 score=17.694769 picPath= pictures/sogou/性感女星/港台女星/安雅/4.jpg tag= 性感女星 港台女星 安雅
doc=42166 score=17.694769 picPath= pictures/sogou/性感女星/港台女星/安雅/5.jpg tag= 性感女星 港台女星 安雅
doc=42167 score=17.694769 picPath= pictures/sogou/性感女星/港台女星/安雅/6.jpg tag= 性感女星 港台女星 安雅
doc=42168 score=17.694769 picPath= pictures/sogou/性感女星/港台女星/安雅/7.jpg tag= 性感女星 港台女星 安雅
doc=42169 score=17.694769 picPath= pictures/sogou/性感女星/港台女星/安雅/8.jpg tag= 性感女星 港台女星 安雅
doc=42170 score=17.694769 picPath= pictures/sogou/性感女星/港台女星/安雅/9.jpg tag= 性感女星 港台女星 安雅
doc=15007 score=15.022858 picPath= pictures/sogou/精美壁纸/人物壁纸/港台美女/0.jpg tag= 精美壁纸 人物壁纸 港台美女
doc=15008 score=15.022858 picPath= pictures/sogou/精美壁纸/人物壁纸/港台美女/10.jpg tag= 精美壁纸 人物壁纸 港台美女

港台 女星 第6页

由此可以验证该多关键词查询实现是合理有效的。

注意

注意:在进行多关键词查询时需要修改ImageIndexer中的analyzer,将其设置为智能分词,不然会出现一些奇怪的查询结果:

搜索港台美女时第一页出现的是港台男星

将ImageIndexer的analyzer设置为智能分词即 analyzer = new IKAnalyzer(true);之后的搜索结果

修改后港台美女搜索结果第一页正常

第三页开始出现相关搜索结果 港台男星

打赏