weak-and算法原理演示(wand)

推薦一個在信息檢索中用到的weak-and算法,這個算法在廣告系統中有成熟的應用。
 
簡單來說,一般我們在計算文本相關性的時候,會通過倒排索引的方式進行查詢,通過倒排索引已經要比全量遍歷節約大量時間,但是有時候仍然很慢。
原因是很多時候我們其實只是想要top n個結果,一些結果明顯較差的也進行了複雜的相關性計算,而weak-and算法通過計算每個詞的貢獻上限來估計文檔的相關性上限,從而建立一個閾值對倒排中的結果進行減枝,從而得到提速的效果。
 
從我實際測試的結果看,對於短文本的效果不如長文本的明顯,但是在視頻的電影數據上面看,仍然減少了50%的耗時(top 100),並且該算法可以通過犧牲精度來進一步提升速度,非常不錯。
 
以下代碼是一個算法原理演示,實現了主要的算法邏輯以驗證算法的有效性,供大家參考,該實現優化了原始算法的一些邏輯儘量減少了無謂的循環

#!/usr/bin/python
#wangben updated 20130108

class WAND:
	'''implement wand algorithm'''
	def __init__(self, InvertIndex, last_docid):
		self.invert_index = InvertIndex #InvertIndex: term -> docid1, docid2, docid3 ...
		self.current_doc = 0
		self.current_invert_index = {}
		self.query_terms = []
		self.threshold = 2

		self.sort_terms = []
		self.LastID = 2000000000 #big num
		self.debug_count = 0
		self.last_docid = last_docid

	def __InitQuery(self, query_terms):
		'''check terms len > 0'''
		self.current_doc = -1
		self.current_invert_index.clear()
		self.query_terms = query_terms
		self.sort_terms[:] = []
		self.debug_count = 0

		for term in query_terms:
			#initial start pos from the first position of term's invert_index
			self.current_invert_index[term] = [ self.invert_index[term][0], 0 ] #[ docid, index ]
	
	def __SortTerms(self):
		if len(self.sort_terms) == 0:
			for term in self.query_terms:
				if term in self.current_invert_index:
					doc_id = self.current_invert_index[term][0]
					self.sort_terms.append([ int(doc_id), term ])
		self.sort_terms.sort()
			

	def __PickTerm(self, pivot_index):
		return 0

	def __FindPivotTerm(self):
		score = 0
		for i in range(0, len(self.sort_terms)):
			score += 1
			if score >= self.threshold:
				return [ self.sort_terms[i][1], i]

		return [ None, len(self.sort_terms) ]

	def __IteratorInvertIndex(self, change_term, docid, pos):
		'''move to doc id > docid'''
		doc_list = self.invert_index[change_term]
		i = 0
		for i in range(pos, len(doc_list)):
			if doc_list[i] >= docid:
				pos = i
				docid = doc_list[i]
				break

		return [ docid, pos ]

	def __AdvanceTerm(self, change_index, docid ):
		change_term = self.sort_terms[change_index][1]
		pos = self.current_invert_index[change_term][1]
		(new_doc, new_pos) = \
			self.__IteratorInvertIndex(change_term, docid, pos)
		
		self.current_invert_index[change_term] = \
			[ new_doc , new_pos ]
		self.sort_terms[change_index][0] = new_doc
		
		
	def __Next(self):
		if self.last_docid == self.current_doc:
			return None
			
		while True:
			self.debug_count += 1
			#sort terms by doc id
			self.__SortTerms()
			
			#find pivot term > threshold
			(pivot_term, pivot_index) = self.__FindPivotTerm()
			if pivot_term == None:
				#no more candidate
				return None
			
			#debug_info:
			for i in range(0, pivot_index + 1):
				print self.sort_terms[i][0],self.sort_terms[i][1],"|",
			print ""
				

			pivot_doc_id = self.current_invert_index[pivot_term][0]
			if pivot_doc_id == self.LastID: #!!
				return None

			if pivot_doc_id <= self.current_doc:
				change_index = self.__PickTerm(pivot_index)
				self.__AdvanceTerm( change_index, self.current_doc + 1 )
			else:
				first_docid = self.sort_terms[0][0]
				if pivot_doc_id == first_docid:
					self.current_doc = pivot_doc_id
					return self.current_doc
				else:
					#pick all preceding term
					for i in range(0, pivot_index):
						change_index = i
						self.__AdvanceTerm( change_index, pivot_doc_id )
	
	def DoQuery(self, query_terms):
		self.__InitQuery(query_terms)
		
		while True:
			candidate_docid = self.__Next()
			if candidate_docid == None:
				break
			print "candidate_docid:",candidate_docid
			#insert candidate_docid to heap
			#update threshold
		print "debug_count:",self.debug_count
		

if __name__ == "__main__":
	testIndex = {}
	testIndex["t1"] = [ 0, 1, 2, 3, 6 , 2000000000]
	testIndex["t2"] = [ 3, 4, 5, 6, 2000000000 ]
	testIndex["t3"] = [ 2, 5, 2000000000 ]
	testIndex["t4"] = [ 4, 6, 2000000000 ]

	w = WAND(testIndex, 6)
	w.DoQuery(["t1", "t2", "t3", "t4"])

輸出結果中會展示next中循環的次數,以及最後被選爲candidate的docid
這裏省略了建立堆的過程,使用了一個默認閾值2作爲doc的刪選條件,候選doc和query doc採用重複詞的個數計算UB,這裏只是一個算法演示,實際使用的時候需要根據自己的相關性公式進行調整(關於Upper Bound需要注意的是,需要在預處理階段,把每個詞可能會共現的最大相關性的分值計算出來作爲該詞的UB)

輸出結果如下,展示了docid的相關變化過程,以及最後的循環次數14次

0 t1 | 2 t3 | 
2 t1 | 2 t3 | 
candidate_docid: 2
2 t1 | 2 t3 | 
2 t3 | 3 t1 | 
3 t1 | 3 t2 | 
candidate_docid: 3
3 t1 | 3 t2 | 
3 t2 | 4 t4 | 
4 t2 | 4 t4 | 
candidate_docid: 4
4 t2 | 4 t4 | 
4 t4 | 5 t2 | 
5 t2 | 5 t3 | 
candidate_docid: 5
5 t2 | 5 t3 | 
5 t3 | 6 t1 | 
6 t1 | 6 t2 | 
candidate_docid: 6
debug_count: 14


發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章