淺談信息檢索

按:本文淺談信息檢索是什么,為什么,怎么做等問題,主要內(nèi)容是Manning等人著的《信息檢索導論》前八張的讀書筆記

問曰:信息檢索的定義是什么?

答曰:

根據(jù)《信息檢索導論》(Manning, Raghavan & Schütze, 2008)第一章:

Information retrieval (IR) is finding material (usually documents) of an unstructured nature (usually text) that satisfies an information need from within large collections (usually stored on computers).

翻譯過來的大白話茄蚯,“信息檢索”是在一大堆非結構化的信息里面(通常是文本),找到符合需求的信息睦优。

問曰: 為什么會產(chǎn)生信息檢索?

  • 或問曰: 信息檢索這門技術用在什么地方?
  • 或問曰: 信息檢索的起源與演變過程是怎樣的?

答曰:

為什么需要信息檢索渗常?最本源的原因就是,信息太多汗盘,找不過來皱碘,需要有相應的技術和工具輔助。信息越多衡未,我們的需求越細致尸执,就越要使用高級的技術和工具。

信息檢索技術起源于圖書館書籍管理缓醋。這不難理解如失,在計算機和互聯(lián)網(wǎng)出現(xiàn)以前,書籍就是信息的基本載體送粱,圖書館就是信息的集中地褪贵。要在一本書籍中找到某個信息,比如“某個朝代發(fā)生的某件事情”抗俄,可以人工把書本翻個遍脆丁,找出來這個信息,這并不是難難事动雹。但要在整個圖書館找到符合“某個朝代發(fā)生的某件事情”這個需求的信息槽卫,顯然是不可能一本書一本書地去翻。因此胰蝠,對書籍進行分類歼培,就是必須的震蒋。分類以后,我們可以到“歷史”這個類別里找到這個朝代的書籍躲庄,再翻書查找查剖。“分類”就是最簡單的信息管理手段噪窘,對信息分類笋庄,然后根據(jù)需求到特定的類別里去找找信息,就是最簡單的信息檢索方法倔监。

進入互聯(lián)網(wǎng)時代直砂,計算機里的文檔成為了信息的主要載體,互聯(lián)網(wǎng)成為了新時代的圖書館(互聯(lián)網(wǎng)本質(zhì)上就是一個分布式文檔系統(tǒng))丐枉。自然而然地哆键,分類方法在一開始也被應用到了互聯(lián)網(wǎng)文檔(主要是HTML文檔)的檢索上掘托。而Yahoo瘦锹!的成功案例也證明了分類方法在互聯(lián)網(wǎng)初期也是非常有用的。但隨著互聯(lián)網(wǎng)內(nèi)容的爆炸性增長闪盔,分類方法也逐漸失效了弯院。沒辦法,即便設置幾百個類別泪掀,每個類別內(nèi)的內(nèi)容也太多了听绳。所以Yahoo!逐漸沒落异赫,而Google逐漸興起椅挣。畢竟Google代表的全文搜索方法無論在有效性還是快捷性都比分類方法有優(yōu)勢。

至此塔拳,信息檢索的發(fā)展主要脈絡就是從圖書館書籍管理開始產(chǎn)生分類方法鼠证,然后在互聯(lián)網(wǎng)時代分類方法不再適用,由此催生了全文檢索方法靠抑,更準確地說量九,是建立索引,用索引來做檢索的方法颂碧。

  • 什么是索引荠列?

索引,顧名思義载城,就是搜索的指引肌似。任何能夠指引我們盡快搜索到我們所求之物的東西,都是索引诉瓦。若我們把每一本書的書名川队、作者等信息記錄在案受楼,找書時只要提供書名、作者等部分或全部信息呼寸,就能找到一本書艳汽。這種情況下,書名对雪、作者信息的記錄表就是索引河狐。Google的全文檢索把每一篇文檔的每一個詞記錄在案,形成了一個全面而龐大的索引瑟捣,所以能比分類法更準確的找到信息馋艺。順帶一提,這種角度看迈套,分類法其實是索引法的特殊形式捐祠,因為類別也是一種索引。如此一來桑李,用全文每個詞做索引踱蛀,和根據(jù)書本要義用類別做索引,孰優(yōu)孰劣贵白,就不難分別了率拒。

問曰: 什么是信息檢索?

  • 或問曰: 信息檢索的目標是什么? 實現(xiàn)目標的基本流程如何?
  • 問曰: 信息檢索的基本任務是什么? 其基本手段又是什么?

答曰:

信息檢索的目標,或者說基本的任務禁荒,就是從一大堆信息中找到我們需要的某部分信息猬膨。進一步,我們縮小范圍呛伴,使之更加具體:信息檢索的目標是在一大堆文檔等非結構化信息中根據(jù)我們的需求挑選出我們需要的部分文檔勃痴。這其實就體現(xiàn)在Manning等人對信息檢索的定義中。

那么热康,進行信息檢索的基本流程有哪些沛申?或者說為了達成信息檢索的任務,我們要做哪些子任務褐隆?首先我們總得先對我們眼前的一大堆數(shù)據(jù)(在這里特指文檔集)污它,有一個清晰的認識。起碼要知道這個文檔集的規(guī)模如何庶弃,文檔由哪些語言寫成衫贬,如果是電子文檔,還要檢查一下編碼之類的歇攻,這樣我們就可以大概知道需要用什么方案固惯。然后就可以建立索引,無論是類別缴守,還是全文詞項葬毫,又或是其他的輔助指引工具镇辉,都是索引。最后還要實現(xiàn)檢索機制贴捡,比如制定一些圖書館借閱規(guī)定忽肛,或者開發(fā)一套計算機系統(tǒng)。

問曰: 目前最典型的信息檢索方案是怎樣的?

答曰(這一部分假定讀者掌握線性代數(shù)的基礎知識):

目前最常用最典型的信息檢索任務烂斋,恐怕就是對網(wǎng)聯(lián)網(wǎng)上的文檔做檢索了屹逛。而最典型的信息檢索方案,便是web搜索引擎汛骂。那么web搜索引擎的工作原理又是怎樣的呢罕模?

Google和百度等大公司都會有web采集器,不斷地帘瞭、動態(tài)地從網(wǎng)上獲取web文檔(基本就是HTML文檔)淑掌。采集回來的文檔就是信息集,要在這么一大堆文檔里(通常是百億級規(guī)模)找出用戶需要的文檔蝶念,就需要索引了抛腕。

不過搜索引擎用到的索引到底長什么樣?

我們要根據(jù)關鍵詞把文檔找出來祸轮,也就是說要針對文檔中出現(xiàn)的每一個詞給問你當建立索引兽埃。所以全文檢索用到的索引就是一個以詞為行,文檔為列的“詞-文檔表格”适袜,確切的說是“詞-文檔矩陣”。

詞-文檔矩陣

上圖就是一個“詞-文檔矩陣”舷夺,圖中的行就是文檔中出現(xiàn)的詞苦酱,列就是文檔的名字(都是莎士比亞的作品)。圖中某行某列中的0代表該行的詞不曾出現(xiàn)在該列的文檔中给猾,反之疫萤。因此“Antony”這個詞出現(xiàn)在了“Julius Caesar”這篇文檔中,而不曾出現(xiàn)在“Hamlet”中敢伸。 從這個矩陣可以清晰看出哪些詞存在于哪些文檔中扯饶,這個就是全文搜索會用到的索引,到信息檢索里的術語稱為“倒排索引”池颈。

要知道尾序,互聯(lián)網(wǎng)上的文檔數(shù)量是百億級的,而其中包含的詞可能也要數(shù)十萬甚至上百萬(瞎猜的)躯砰,總之比任何一步詞典收錄的詞都要多每币。這么大的矩陣,實在是太過占用空間了琢歇,哪怕是今天兰怠,計算機的內(nèi)存都是寶貴資源啊梦鉴。考慮到大規(guī)模文檔集和大規(guī)模詞表形成的“詞-文檔矩陣”中會有很多0存在揭保,我們就不難想到采用稀疏表示的方式來存儲這個矩陣肥橙。而上圖的索引也會變成下面的樣子。

倒排索引

圖中左邊是詞項秸侣,右邊則是文檔(編號)列表快骗。“Brutus”對應的列表里有1塔次、2方篮、4等號碼,意味著文檔1励负、文檔2藕溅、文檔4里包含著“Brutus”這個詞。

那么這個索引是怎么構建出來的呢继榆?

在建索引前肯定要做一些預處理巾表。常見的預處理可能會有識別文檔編碼(UTF-8?GBK略吨?ASKII集币?),選用正確的解碼方式翠忠。然后就是分詞鞠苟。英文等拉丁語系的文字詞與詞之間有間隔,所以還不算難秽之,但也要注意不能把“United Kingdom”這樣的專有名詞切開当娱。而對于漢語等東亞的語言文字,就需要特殊的分詞手段(比如條件隨機場考榨、馬爾科夫鏈等)跨细。分完詞后還沒完,一般還會把一些的都好河质、句號之類的符號去掉冀惭。最后可能還會考慮一下要不要把所有詞換成小寫,甚至進行詞根還原(把詞的動詞掀鹅、名詞等各種形式統(tǒng)一映射到詞根)等詞項歸一化散休,使得用戶的查詢能夠匹配到更多可能相關結果。

經(jīng)過了編碼識別淫半、分詞溃槐、大小寫轉換、詞項歸一化等一系列預處理科吭,我們把一個文檔轉換成詞項流(可以看成由詞組成的數(shù)組)昏滴,因此文檔集也就轉換成了詞項數(shù)組的集合猴鲫。接下來我們就可以建立索引了。我們可以掃描一遍得到的詞項流谣殊,得到一個“詞-文檔ID”流拂共,然后把詞項相同的元組合并,就得到了下圖右面的倒排索引

系數(shù)矩陣形式倒排索引

Talk is cheap, show me your code!

? —— Torvalds · Linus

下面是一段很簡單的python代碼姻几,為一個簡單的文檔集構建倒排索引

"""
inverted_index.py

Build a basic (naive) inverted index for a simple (naive) documents set
Please Run this script using Python3.x 
Tested under Python3.6, Win7 and Python3.5 ubuntu16.04
Author: Richy Zhu
Email: rickyzhu@foxmail.com
"""

import json
import re
from pprint import pprint

def clear_symbols(text):
    """remove symbols like commas, semi-commas
    """
    simbols = re.compile("[\s+\.\!\/_,$%^*()+\"\']+|[+——宜狐!,蛇捌。抚恒?、~@#¥%……&*():]+")
    if type(text) is str:   
        processed_text = re.sub(simbols, ' ', text)
        return processed_text
    elif type(text) is list:
        return [re.sub(simbols, ' ', item) for item in text]
    else:
        raise TypeError("This function only accept str or list as argument")

def lowercase(text):
    """turn all the characters to be lowercase
    """
    if type(text) is str:
        return text.lower()
    elif type(text) is list:
        return [item.lower() for item in text]
    else:
        raise TypeError("This function only accept str or list as argument")

def tokenize(docs):
    token_stream = []
    for doc in docs:
        token_stream.append(doc.split())
    return token_stream

def preprocess(docs):
    """clear symbols, lowercase, tokenize, get clean tokenized docs
    """
    normalized_docs = lowercase(clear_symbols(docs))
    tokenized_docs = tokenize(normalized_docs)
    return tokenized_docs

def get_token_stream(tokenized_docs, docs_dict):
    """get (term-doc_id) stream
    """
    token_stream = []
    for doc_id in docs_dict:
        for term in tokenized_docs[doc_id]:
            token_stream.append((term, doc_id))
    return token_stream

def build_indices(tokenized_docs, docs_dict):
    """main function -- build invertex index
       assume that the documents set is small enough to be loaded into Memory
    """
    token_stream = get_token_stream(tokenized_docs, docs_dict)
    # pprint(token_stream)
    indices = {}

    for pair in token_stream:
        if pair[0] in indices:
            if pair[1] not in indices[pair[0]]:
                indices[pair[0]].append(pair[1])
        else:
            indices[pair[0]] = [pair[1]]
    return indices

if __name__ == "__main__":
    docs = [
        "hello world",
        "hello python", 
        "I love C, Java, Python, Typescript, and PHP",
        "use python to build inverted indices",
        "you and me are in one world"
        ]
    docs_dict = {
        0: "docs[0]",
        1: "docs[1]",
        2: "docs[3]",
        3: "docs[4]",
        4: "docs[5]"
    }
    tokenized_docs = preprocess(docs)
    # pprint(tokenized_docs)
    indices = build_indices(tokenized_docs, docs_dict)
    pprint(indices)
    

運行文件可以得到如下結果:

$ python3 inverted_index.py
{'and': [4],
 'are': [4],
 'build': [3],
 'hello': [0, 1],
 'i': [2],
 'in': [4],
 'indices': [3],
 'inverted': [3],
 'love': [2],
 'me': [4],
 'one': [4],
 'python': [1, 2, 3],
 'to': [3],
 'use': [3],
 'world': [0, 4],
 'you': [4]}

當然络拌,上面的索引構建程序有一個假設:文檔規(guī)模不大俭驮,整個文檔集索引的構建過程都可以在內(nèi)存里完成。如果文檔集過大春贸,就要將其分割成較小的塊混萝,對每塊做構建索引的操作,然后把每一個塊操作的結果(這一個塊的索引文件)合并起來得到整個文檔集的索引萍恕。典型的算法有BSBI算法逸嘀、SPIMI算法等。

現(xiàn)在我們已經(jīng)有一個索引了允粤,但我們怎么去使用呢崭倘?

有了索引,就可以很方便地找出我們需要的文檔维哈,最典型的的應用是布爾查詢绳姨。布爾查詢使用布爾運算符對查詢 關鍵詞進行連接,比如:

“信息 AND 檢索 AND 導論”阔挠, 這個查詢就是查找文中既包含“信息”,又包含“檢索”脑蠕,還包含“導論”這三個關鍵詞的文檔购撼。

“(python OR Java) NOT Ruby”撩幽,這個查詢就是查找文中包含“python”或包含“Java”送漠,但不包含“Ruby”的文檔金度。

對于AND操作项郊,只需在索引中找到每個關鍵詞對應的的文檔ID列表椭迎,然后對文檔ID列表求交集劫樟,也就是找出所有列表中共有的文檔ID毙石,那么這些找出來的文檔就是用戶想要的文檔炼蹦。OR的NOT操作不再贅述掀虎。

直到現(xiàn)在凌盯,很多圖書館的檢索系統(tǒng)都還支持布爾查詢的操作(大多放在“高級查詢”功能里面)付枫。

但是下一個問題來了,布爾查詢使用到布爾操作符驰怎,而且其理論基礎是布爾代數(shù)阐滩。雖說他們已經(jīng)足夠簡單,但還是要求用戶學習一點知識县忌。而且布爾查詢的查詢表達式可以相當復雜掂榔。而且查找出來的文檔雖然都與用戶的查詢相關,但是并不能按照相似程度來排序症杏,只能根據(jù)發(fā)表日期等指標來排序装获。

那么有沒有更加簡單有效的方法讓用戶輸入自然語言查詢(而非布爾表達式等有一定規(guī)則的查詢),得到根據(jù)相關程度排序的文檔結果呢厉颤?

答案是向量空間模型穴豫。

我們的目標是把所有文檔和用戶查詢一起轉化成向量(詞袋模型、if-idf權重)走芋,然后使用線性代數(shù)的方法來求得用戶查詢向量與所有文檔向量之間的相似度(余弦相似度)绩郎,進而得到用戶查詢與所有文檔之間的相關程度。

要把向量空間模型應用于信息檢索翁逞,要關注三個重要概念:詞袋模型肋杖、TF-IDF、余弦相似度挖函。

關于向量空間模型的文章整個互聯(lián)網(wǎng)滿大街都是状植,隨便百度一下都能找到許多好的入門文章。在此我摘引一篇文章的部分來介紹詞袋模型怨喘,資料來自bag-of-words模型入門

Bag-of-words模型是信息檢索領域常用的文檔表示方法津畸。

在信息檢索中,BOW模型假定對于一個文檔必怜,忽略它的單詞順序和語法肉拓、句法等要素,將其僅僅看作是若干個詞匯的集合梳庆,文檔中每個單詞的出現(xiàn)都是獨立的暖途,不依賴于其它單詞是否出現(xiàn)。(是不關順序的)

也就是說膏执,文檔中任意一個位置出現(xiàn)的任何單詞驻售,都不受該文檔語意影響而獨立選擇的。那么到底是什么意思呢更米?那么給出具體的例子說明:

例子

Wikipedia[1]上給出了如下例子:

John likes to watch movies. Mary likes too.
John also likes to watch football games.

根據(jù)上述兩句話中出現(xiàn)的單詞, 我們能構建出一個字典 (dictionary):

{
  "John": 1, 
  "likes": 2, 
  "to": 3, 
  "watch": 4, 
  "movies": 5, 
  "also": 6, 
  "football": 7, 
  "games": 8, 
  "Mary": 9, 
  "too": 10
}

該字典中包含10個單詞, 每個單詞有唯一索引,**注意它們的順序和出現(xiàn)在句子中的順序沒有關聯(lián). 根據(jù)這個字典, **我們能將上述兩句話重新表達為下述兩個向量:

[1, 2, 1, 1, 1, 0, 0, 0, 1, 1]
[1, 1, 1, 1, 0, 1, 1, 1, 0, 0]

這兩個向量共包含10個元素, 其中第i個元素表示字典中第i個單詞在句子中出現(xiàn)的次數(shù). 因此BoW模型可認為是一種統(tǒng)計直方圖 (histogram). 在文本檢索和處理應用中, 可以通過該模型很方便的計算詞頻.

但是從上面我們也能夠看出欺栗,在構造文檔向量的過程中可以看到,我們并沒有表達單詞在原來句子中出現(xiàn)的次序這也是bag of words的一個缺點,但是聽師兄說迟几,很多情況簡單的用bow特征產(chǎn)生的結果就比較好了

根據(jù)上面介紹到的詞袋模型消请,我們可以把一個文檔轉換成一個向量,向量的長度是詞典的大小瘤旨,也就是文檔集里詞項的數(shù)量梯啤,而向量中的每個元素都是都是對應詞項的詞頻。同理存哲,用戶的查詢也能轉換為一個向量因宇,比如在上述的情境下,如果用戶查詢是“football games”祟偷,那么對應的向量就是[0,0,0,0,0,0,1,1,0,0] 〔旎現(xiàn)在,我們就可以使用線性代數(shù)的方法來求得兩個向量的相似度修肠,從而得到用戶查詢和所有文檔對應的相似度贺辰。典型的方法就是求得條用戶查詢向量與文檔向量之間的余弦夾角,夾角越小嵌施,相似度越大饲化。具體的求法如下:

假定A和B是兩個n維向量,A是 [A1, A2, ..., An] 吗伤,B是 [B1, B2, ..., Bn] 吃靠,則A與B的夾角θ的余弦等于:

cos-similarity

這種方法雖然用到了形式化數(shù)學的方法,但是本質(zhì)上的思想很簡單:包含用戶查詢中關鍵詞的文檔才與用戶的查詢相關足淆。一篇文檔包含的關鍵詞越多巢块,關鍵詞出現(xiàn)的頻率越高,這篇文檔與用戶查詢的相關度就越高巧号。

但是現(xiàn)在問題又來了族奢,有一些詞比如“a”,“the”丹鸿, “of”越走,或者“啊”, “的”靠欢, “了”弥姻,它們基本上在每一篇文檔都出現(xiàn),而且在每一篇文檔中的出現(xiàn)頻率都很高掺涛,用戶查詢中也驚顫會包含這些詞。

顯然這些詞是沒什么價值的疼进,不能幫我們找出真正與用戶查詢相關的文檔的薪缆。那我們要怎么做來消除這些詞的影響呢?

一個簡單粗暴的方法是維護一個停用詞表,也就是把一些沒有價值的詞拣帽,也就是在絕大多數(shù)文檔都出現(xiàn)的詞記錄成一張表疼电,在建索引和解析用戶查詢時忽略這些詞。

一個更有技術含量的方法是减拭,在向量化文檔和用戶查詢時蔽豺,給每個詞賦予權重,使得重要的詞權重高拧粪,“a”修陡,“啊”之類的詞權重低。那么文檔向量和用戶查詢向量里的元素就不再是詞頻這樣簡單的指標可霎,而是詞在文檔中的權重這樣的指標魄鸦。這種權重指標中最常用的方法就是TF-IDF。其核心思想是癣朗,在絕大多數(shù)文檔的中都出現(xiàn)的詞重要性最低拾因,只在少數(shù)文檔中出現(xiàn)的詞重要性較高,這些詞的詞頻越高旷余,重要性越高绢记。因此,IF-iDF的計算方法如下:

一些符號: t--某個詞項正卧, d--某篇文檔蠢熄, n--文檔集包含的文檔總數(shù)

第一步,計算詞頻穗酥。**

tf = 詞項t在文章d中的出現(xiàn)次數(shù) / 文章d的總詞項數(shù)

第二步护赊,計算逆文檔頻率。

idf = lg(文檔集規(guī)模n / 包含詞項t的文檔數(shù) + 1)

第三步砾跃,計算TF-IDF骏啰。

tf-idf = tf * idf

Again, talk is cheap。下面是一段簡單的python代碼抽高,演示使用VSM來計算用戶查詢和文檔相似度判耕。

"""
vsm.py

Simple implementation of Vector Space Model

Note: Depend on Numpy, please install it ahead (`pip install numpy`)

Please Run this script using Python3.x 
Tested under Python3.6, Win7 and Python3.5 ubuntu16.04
Author: Richy Zhu
Email: rickyzhu@foxmail.com
"""

from math import log10
from pprint import pprint
import numpy as np

def _tf(tokenized_doc):
    """calculate term frequency for each term in each document"""
    term_tf = {}
    for term in tokenized_doc:
        if term not in term_tf:
            term_tf[term]=1.0
        else:
            term_tf[term]+=1.0

    # pprint(term_tf)
    return term_tf

def _idf(indices, docs_num):
    """calculate inverse document frequency for every term"""
    term_df = {}
    for term in indices:
        # 一個term的df就是倒排索引中這個term的倒排記錄表(對應文檔列表)的長度 
        term_df.setdefault(term, len(indices[term]))
    
    term_idf = term_df
    for term in term_df:
        term_idf[term] = log10(docs_num /term_df[term])
    # pprint(term_idf)
    return term_idf

def tfidf(tokenized_docs, indices):
    """calcalate tfidf for each term in each document"""
    term_idf = _idf(indices, len(tokenized_docs))
        
    term_tfidf={}
    doc_id=0
    for tokenized_doc in tokenized_docs:
        term_tfidf[doc_id] = {}
        term_tf = _tf(tokenized_doc)
        
        doc_len=len(tokenized_doc)
        for term in tokenized_doc:
            tfidf = term_tf[term]/doc_len * term_idf[term]
            term_tfidf[doc_id][term] =tfidf
        doc_id+=1
    # pprint(term_tfidf)
    return term_tfidf

def build_terms_dictionary(tokenized_docs):
    """assign an ID for each term in the vocabulary"""
    vocabulary = set()
    for doc in tokenized_docs:
        for term in doc:
            vocabulary.add(term)
    vocabulary = list(vocabulary)
    dictionary = {}
    for i in range(len(vocabulary)):
        dictionary.setdefault(i, vocabulary[i])
    return dictionary

def vectorize_docs(docs_dict, terms_dict, tf_idf):
    """ transform documents to vectors
        using bag-of-words model and if-idf
    """
    docs_vectors = np.zeros([len(docs_dict), len(terms_dict)])

    for doc_id in docs_dict:
        for term_id in terms_dict:
            if terms_dict[term_id] in tf_idf[doc_id]:
                docs_vectors[doc_id][term_id] = tf_idf[doc_id][terms_dict[term_id]]
    return docs_vectors

def vectorize_query(tokenized_query, terms_dict):
    """ transform user query to vectors 
        using bag-of-words model and vector normalization
    """
    query_vector = np.zeros(len(terms_dict))
    for term_id in terms_dict:
        if terms_dict[term_id] in tokenized_query:
            query_vector[term_id] += 1
    return query_vector / np.linalg.norm(query_vector)

def cos_similarity(vector1, vector2):
    """compute cosine similarity of two vectors"""
    return np.dot(vector1,vector2)/(np.linalg.norm(vector1)*(np.linalg.norm(vector2))) 

def compute_simmilarity(docs_vectors, query_vector, docs_dict):
    """compute all similarites between user query and all documents"""
    similarities = {}
    for doc_id in docs_dict:
        similarities[doc_id] = cos_similarity(docs_vectors[doc_id], query_vector)
    return similarities


if __name__ == '__main__':
    tokenized_docs = [
        ['hello', 'world'],
        ['hello', 'python'],
        ['i', 'love', 'c', 'java', 'python', 'typescript', 'and', 'php'],
        ['use', 'python', 'to', 'build', 'inverted', 'indices'],
        ['you', 'and', 'me', 'are', 'in', 'one', 'world']
                    ]
    tokenized_query = ["python", "indices"]
    docs_dict = {
        0: "docs[0]",
        1: "docs[1]",
        2: "docs[2]",
        3: "docs[3]",
        4: "docs[4]"
    }
    indices = {'and': [2, 4], 'are': [4], 'build': [3], 'c': [2], 'hello': [0, 1], 'i': [2], 
            'in': [4], 'indices': [3], 'inverted': [3], 'java': [2], 'love': [2], 'me': [4],
            'one': [4], 'php': [2], 'python': [1, 2, 3], 'to': [3], 'typescript': [2], 'use'
            : [3], 'world': [0, 4], 'you': [4]}
    tf_idf = tfidf(tokenized_docs, indices)
    terms_dict = build_terms_dictionary(tokenized_docs);
    docs_vectors = vectorize_docs(docs_dict, terms_dict, tf_idf)
    query_vector = vectorize_query(tokenized_query, terms_dict)
    # pprint(docs_vectors)
    pprint(compute_simmilarity(docs_vectors, query_vector, docs_dict))

運行以上腳本可以得到下面的結果,字典做點是文檔id翘骂,右邊是對應的用戶查詢的相似度壁熄。可見文檔3與用戶查詢最為相關碳竟。

$ python3 vsm.py
{0: 0.0,
 1: 0.34431538823149532,
 2: 0.088542411007409116,
 3: 0.41246212572975449,
 4: 0.0}

關于向量空間模型的資料草丧,有很多更加詳細,更加通俗易懂的文章莹桅,比如吳軍博士《數(shù)學之美》的第11章: 確定網(wǎng)頁和查詢的相關性:TF-IDF昌执,Manning等人《信息檢索導論》的第6章: Scoring, term weighting, and the vector space model

小結

所以向量空間模型的要點是把文檔轉換成向量,把用戶的查詢也轉換成向量懂拾,然后求查詢向量和所有文檔向量的余弦夾角等相似度指標煤禽,根據(jù)相似度來排序。

但是對大規(guī)模文檔集岖赋,不可能把用戶查詢跟所有文檔向量的相似度都計算一遍檬果,這樣太耗費時間了。我們可以犧牲一點點搜索質(zhì)量唐断,來獲取時間性能上的大幅提高选脊。首先我們可以結合倒排索引,先根據(jù)用戶查詢的全部或部分關鍵詞栗涂,在索引中找出相關文檔列表知牌,然后只對搜索出來的文檔進行相似度計算。如果這一步后找出來的文檔依然太多斤程,我們可以更進一步角寸,先根據(jù)每個詞在每一篇文檔中TF-IDF值對文檔進行排序,排序高的放在這個詞對應的文檔列表前面忿墅。這樣扁藕,我們可以每次只選出關鍵詞對應的文檔列表的前K個文檔做相似度計算,便完全可以控制時間成本和搜索質(zhì)量之間的平衡了疚脐。

至此亿柑,一個基于倒排索引和向量空間模型的全文搜索引擎的核心工作機制就完備了。

信息檢索當下的應用和未來可能的發(fā)展方向有哪些棍弄?

答曰:

前文詳細介紹了一種主要使用倒排索引和向量空間模型的信息檢索方案望薄,主要用于檢索計算機里的文檔。早期的Google等互聯(lián)網(wǎng)搜索引擎主要采用的都是這種方案呼畸。甚至現(xiàn)在痕支,這種方案可能也還是Google的主要框架。這種搜索引擎也稱為“ad hoc search engine”蛮原, “ad hoc”在拉丁文中是“特殊的卧须、臨時的、針對特定目的”的意思儒陨。也就是說我們常用的搜索引擎針對得到用戶信息需求都是“特殊的花嘶、臨時的、針對特定目的”蹦漠。相應的椭员,也有長期的,穩(wěn)定的信息需求笛园,比如一個長期關注信息檢索技術的人拆撼,就想要每天閱讀一些信息檢索主題的文章容劳。更加常見的長期、穩(wěn)定的信息需求闸度,可能是“在一大堆郵件中找出垃圾郵件(然后丟掉)”。針對這些信息需求蚜印,我們需要文檔集進行分類等操作莺禁,找出文檔集里“信息檢索技術”主題的文章,或者對郵件集操作窄赋,找出“垃圾郵件”類別的郵件(然后丟掉)哟冬。典型的方案就是貝葉斯文本分類、支持向量機文本分類忆绰,K臨近文本聚類之類額機器學習習方法浩峡。Manning等人的《信息檢索導論》后面好幾章都是介紹這些機器學習方法在文本分類和聚類中的應用。

當下错敢,搜索引擎是信息檢索的主要工具和研究分支翰灾。但是讓我們重新檢視信息檢索的定義:

Information retrieval (IR) is finding material (usually documents) of an unstructured nature (usually text) that satisfies an information need from within large collections (usually stored on computers).

信息檢索的本質(zhì),應該是“給用戶找到他想要的信息”稚茅。那么如果用戶提出一個問題纸淮,然后我們不是返回一堆文檔,而是直接告訴他問題的答案亚享,豈不是更好咽块?是的,人們把自動問答系統(tǒng)看作是下一代的搜索引擎欺税,而Google侈沪、百度、搜狗等人工智能和信息檢索巨頭也正在大力開發(fā)自動問答系統(tǒng)晚凿。

讓我們更激進一點亭罪,如果信息檢索的本質(zhì)是“給用戶找到他想要的信息”,那么更加高端的信息檢索形式可能是在用戶提出問題之前就給他提供他想要的信息晃虫,比如用戶剛想換一部手機皆撩,就把所有手機相關的商品信息呈現(xiàn)給用戶;或者說用戶剛想了解一下娛樂圈某明星最近發(fā)生的一件事哲银,系統(tǒng)就他推送相關新聞給這位用戶扛吞。是的,推薦系統(tǒng)可能才是信息檢索最高級的形態(tài)荆责,這可能也是很多人看好今日頭條的緣故吧滥比。


相關鏈接:

一些參考代碼:https://coding.net/u/qige96/p/IR-exe

本文直接或間接地使用了以下著作的內(nèi)容:

  1. 《信息檢索導論》
  2. 知乎文章:bag-of-words模型入門
  3. 阮一峰:TF-IDF與余弦相似性的應用(一):自動提取關鍵詞
  4. 阮一峰:TF-IDF與余弦相似性的應用(二):找出相似文章
  5. 吳軍:《數(shù)學之美》

本作品首發(fā)于簡書博客園平臺,采用知識共享署名 4.0 國際許可協(xié)議進行許可做院。

最后編輯于
?著作權歸作者所有,轉載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末盲泛,一起剝皮案震驚了整個濱河市濒持,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌寺滚,老刑警劉巖柑营,帶你破解...
    沈念sama閱讀 222,000評論 6 515
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異村视,居然都是意外死亡官套,警方通過查閱死者的電腦和手機,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 94,745評論 3 399
  • 文/潘曉璐 我一進店門蚁孔,熙熙樓的掌柜王于貴愁眉苦臉地迎上來奶赔,“玉大人,你說我怎么就攤上這事杠氢≌拘蹋” “怎么了?”我有些...
    開封第一講書人閱讀 168,561評論 0 360
  • 文/不壞的土叔 我叫張陵鼻百,是天一觀的道長绞旅。 經(jīng)常有香客問我,道長愕宋,這世上最難降的妖魔是什么玻靡? 我笑而不...
    開封第一講書人閱讀 59,782評論 1 298
  • 正文 為了忘掉前任,我火速辦了婚禮中贝,結果婚禮上囤捻,老公的妹妹穿的比我還像新娘。我一直安慰自己邻寿,他們只是感情好蝎土,可當我...
    茶點故事閱讀 68,798評論 6 397
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著绣否,像睡著了一般誊涯。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上蒜撮,一...
    開封第一講書人閱讀 52,394評論 1 310
  • 那天暴构,我揣著相機與錄音,去河邊找鬼段磨。 笑死取逾,一個胖子當著我的面吹牛,可吹牛的內(nèi)容都是我干的苹支。 我是一名探鬼主播砾隅,決...
    沈念sama閱讀 40,952評論 3 421
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼债蜜!你這毒婦竟也來了晴埂?” 一聲冷哼從身側響起究反,我...
    開封第一講書人閱讀 39,852評論 0 276
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎儒洛,沒想到半個月后精耐,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 46,409評論 1 318
  • 正文 獨居荒郊野嶺守林人離奇死亡晶丘,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 38,483評論 3 341
  • 正文 我和宋清朗相戀三年黍氮,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片浅浮。...
    茶點故事閱讀 40,615評論 1 352
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖捷枯,靈堂內(nèi)的尸體忽然破棺而出滚秩,到底是詐尸還是另有隱情,我是刑警寧澤淮捆,帶...
    沈念sama閱讀 36,303評論 5 350
  • 正文 年R本政府宣布郁油,位于F島的核電站,受9級特大地震影響攀痊,放射性物質(zhì)發(fā)生泄漏桐腌。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 41,979評論 3 334
  • 文/蒙蒙 一苟径、第九天 我趴在偏房一處隱蔽的房頂上張望案站。 院中可真熱鬧,春花似錦棘街、人聲如沸蟆盐。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,470評論 0 24
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽石挂。三九已至,卻和暖如春险污,著一層夾襖步出監(jiān)牢的瞬間痹愚,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 33,571評論 1 272
  • 我被黑心中介騙來泰國打工蛔糯, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留拯腮,地道東北人。 一個月前我還...
    沈念sama閱讀 49,041評論 3 377
  • 正文 我出身青樓渤闷,卻偏偏與公主長得像疾瓮,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子飒箭,可洞房花燭夜當晚...
    茶點故事閱讀 45,630評論 2 359

推薦閱讀更多精彩內(nèi)容