基於TF-IDF的特徵提取技術:基於內容的電影推薦(物品畫像、用戶畫像、爲用戶產生TOP-N推薦結果)

日萌社

人工智能AI:Keras PyTorch MXNet TensorFlow PaddlePaddle 深度學習實戰(不定時更新)


 

基於內容的電影推薦:物品畫像

物品畫像構建步驟:

  • 利用tags.csv中每部電影的標籤作爲電影的候選關鍵詞
  • 利用TF·IDF計算每部電影的標籤的tfidf值,選取TOP-N個關鍵詞作爲電影畫像標籤
  • 將電影的分類詞直接作爲每部電影的畫像標籤

基於TF-IDF的特徵提取技術

前面提到,物品畫像的特徵標籤主要都是指的如電影的導演、演員、圖書的作者、出版社等結構話的數據,也就是他們的特徵提取,尤其是體徵向量的計算是比較簡單的,如直接給作品的分類定義0或者1的狀態。

但另外一些特徵,比如電影的內容簡介、電影的影評、圖書的摘要等文本數據,這些被稱爲非結構化數據,首先他們本應該也屬於物品的一個特徵標籤,但是這樣的特徵標籤進行量化時,也就是計算它的特徵向量時是很難去定義的。

因此這時就需要藉助一些自然語言處理、信息檢索等技術,將如用戶的文本評論或其他文本內容信息的非結構化數據進行量化處理,從而實現更加完善的物品畫像/用戶畫像。

TF-IDF算法便是其中一種在自然語言處理領域中應用比較廣泛的一種算法。可用來提取目標文檔中,並得到關鍵詞用於計算對於目標文檔的權重,並將這些權重組合到一起得到特徵向量。

算法原理

TF-IDF自然語言處理領域中計算文檔中詞或短語的權值的方法,是詞頻(Term Frequency,TF)和逆轉文檔頻率(Inverse Document Frequency,IDF)的乘積。TF指的是某一個給定的詞語在該文件中出現的次數。這個數字通常會被正規化,以防止它偏向長的文件(同一個詞語在長文件裏可能會比短文件有更高的詞頻,而不管該詞語重要與否)。IDF是一個詞語普遍重要性的度量,某一特定詞語的IDF,可以由總文件數目除以包含該詞語之文件的數目,再將得到的商取對數得到。

TF-IDF算法基於一個這樣的假設:若一個詞語在目標文檔中出現的頻率高而在其他文檔中出現的頻率低,那麼這個詞語就可以用來區分出目標文檔。這個假設需要掌握的有兩點:

  • 在本文檔出現的頻率高;
  • 在其他文檔出現的頻率低。

因此,TF-IDF算法的計算可以分爲詞頻(Term Frequency,TF)和逆轉文檔頻率(Inverse Document Frequency,IDF)兩部分,由TF和IDF的乘積來設置文檔詞語的權重。

結論:TF-IDF與詞語在文檔中的出現次數成正比,與該詞在整個文檔集中的出現次數成反比。

用途:在目標文檔中,提取關鍵詞(特徵標籤)的方法就是將該文檔所有詞語的TF-IDF計算出來並進行對比,取其中TF-IDF值最大的k個數組成目標文檔的特徵向量用以表示文檔。

注意:文檔中存在的停用詞(Stop Words),如“是”、“的”之類的,對於文檔的中心思想表達沒有意義的詞,在分詞時需要先過濾掉再計算其他詞語的TF-IDF值。

算法舉例

對於計算影評的TF-IDF,以電影“加勒比海盜:黑珍珠號的詛咒”爲例,假設它總共有1000篇影評,其中一篇影評的總詞語數爲200,其中出現最頻繁的詞語爲“海盜”、“船長”、“自由”,分別是20、15、10次,並且這3個詞在所有影評中被提及的次數分別爲1000、500、100,就這3個詞語作爲關鍵詞的順序計算如下。

  1. 將影評中出現的停用詞過濾掉,計算其他詞語的詞頻。以出現最多的三個詞爲例進行計算如下:

    • “海盜”出現的詞頻爲20/200=0.1
    • “船長”出現的詞頻爲15/200=0.075
    • “自由”出現的詞頻爲10/200=0.05;
  2. 計算詞語的逆文檔頻率如下:

    • “海盜”的IDF爲:log(1000/1000)=0
    • “船長”的IDF爲:log(1000/500)=0.3 “自由”的IDF爲:log(1000/100)=1
  3. 由1和2計算的結果求出詞語的TF-IDF結果,“海盜”爲0,“船長”爲0.0225,“自由”爲0.05。

通過對比可得,該篇影評的關鍵詞排序應爲:“自由”、“船長”、“海盜”。把這些詞語的TF-IDF值作爲它們的權重按照對應的順序依次排列,就得到這篇影評的特徵向量,我們就用這個向量來代表這篇影評,向量中每一個維度的分量大小對應這個屬性的重要性。

將總的影評集中所有的影評向量與特定的係數相乘求和,得到這部電影的綜合影評向量,與電影的基本屬性結合構建視頻的物品畫像,同理構建用戶畫像,可採用多種方法計算物品畫像和用戶畫像之間的相似度,爲用戶做出推薦。

加載數據集

import pandas as pd
import numpy as np
'''
- 利用tags.csv中每部電影的標籤作爲電影的候選關鍵詞
- 利用TF·IDF計算每部電影的標籤的tfidf值,選取TOP-N個關鍵詞作爲電影畫像標籤
- 並將電影的分類詞直接作爲每部電影的畫像標籤
'''

def get_movie_dataset():
    # 加載基於所有電影的標籤
    # all-tags.csv來自ml-latest數據集中
    # 由於ml-latest-small中標籤數據太多,因此藉助其來擴充
    _tags = pd.read_csv("datasets/ml-latest-small/all-tags.csv", usecols=range(1, 3)).dropna()
    tags = _tags.groupby("movieId").agg(list)

    # 加載電影列表數據集
    movies = pd.read_csv("datasets/ml-latest-small/movies.csv", index_col="movieId")
    # 將類別詞分開
    movies["genres"] = movies["genres"].apply(lambda x: x.split("|"))
    # 爲每部電影匹配對應的標籤數據,如果沒有將會是NAN
    movies_index = set(movies.index) & set(tags.index)
    new_tags = tags.loc[list(movies_index)]
    ret = movies.join(new_tags)

    # 構建電影數據集,包含電影Id、電影名稱、類別、標籤四個字段
    # 如果電影沒有標籤數據,那麼就替換爲空列表
    # map(fun,可迭代對象)
    movie_dataset = pd.DataFrame(
        map(
            lambda x: (x[0], x[1], x[2], x[2]+x[3]) if x[3] is not np.nan else (x[0], x[1], x[2], []), ret.itertuples())
        , columns=["movieId", "title", "genres","tags"]
    )

    movie_dataset.set_index("movieId", inplace=True)
    return movie_dataset

movie_dataset = get_movie_dataset()
print(movie_dataset)
  • map函數

    • 描述

      map() 會根據提供的函數對指定序列做映射。

      第一個參數 function 以參數序列中的每一個元素調用 function 函數,返回包含每次 function 函數返回值的新列表。

    • 語法

      map() 函數語法:

      map(function, iterable, ...)
      
    • 參數

      • function -- 函數
      • iterable -- 一個或多個序列
    • 返回值

      Python 2.x 返回列表。

      Python 3.x 返回迭代器。

    • 示例

      >>>def square(x) :            # 計算平方數
      ...     return x ** 2
      ... 
      >>> map(square, [1,2,3,4,5])   # 計算列表各個元素的平方
      [1, 4, 9, 16, 25]
      >>> map(lambda x: x ** 2, [1, 2, 3, 4, 5])  # 使用 lambda 匿名函數
      [1, 4, 9, 16, 25]
      
      # 提供了兩個列表,對相同位置的列表數據進行相加
      >>> map(lambda x, y: x + y, [1, 3, 5, 7, 9], [2, 4, 6, 8, 10])
      [3, 7, 11, 15, 19]
      

基於TF·IDF提取TOP-N關鍵詞,構建電影畫像

  • gensim介紹

    • python 三方庫 自然語言處理利器
    • 支持包括TF-IDF,word2vec在內的多種主題模型算法
    • 安裝 pip install gensim
  • gensim基本概念

    • 語料(Corpus):一組原始文本的集合,在Gensim中,Corpus通常是一個可迭代的對象(比如列表)。每一次迭代返回一個可用於表達文本對象的(稀疏)向量。
    • 向量(Vector):由一組文本特徵構成的列表。是一段文本在Gensim中的內部表達。
    • 模型(Model)
  • 詞袋模型(BOW bag of words)

    文本特徵提取有兩個非常重要的模型:

    • 詞集模型:單詞構成的集合,集合自然每個元素都只有一個,也即詞集中的每個單詞都只有一個。
    • 詞袋模型:在詞集的基礎上如果一個單詞在文檔中出現不止一次,統計其出現的次數(頻數)。

    兩者本質上的區別,詞袋是在詞集的基礎上增加了頻率的維度,詞集只關注有和沒有,詞袋還要關注有幾個。

from gensim.models import TfidfModel

import pandas as pd
import numpy as np

from pprint import pprint

# ......

def create_movie_profile(movie_dataset):
    '''
    使用tfidf,分析提取topn關鍵詞
    :param movie_dataset: 
    :return: 
    '''
    dataset = movie_dataset["tags"].values

    from gensim.corpora import Dictionary
    # 根據數據集建立詞袋,並統計詞頻,將所有詞放入一個詞典,使用索引進行獲取
    dct = Dictionary(dataset)
    # 根據將每條數據,返回對應的詞索引和詞頻
    corpus = [dct.doc2bow(line) for line in dataset]
    # 訓練TF-IDF模型,即計算TF-IDF值
    model = TfidfModel(corpus)

    movie_profile = {}
    for i, mid in enumerate(movie_dataset.index):
        # 根據每條數據返回,向量
        vector = model[corpus[i]]
        # 按照TF-IDF值得到top-n的關鍵詞
        movie_tags = sorted(vector, key=lambda x: x[1], reverse=True)[:30]
        # 根據關鍵詞提取對應的名稱
        movie_profile[mid] = dict(map(lambda x:(dct[x[0]], x[1]), movie_tags))

    return movie_profile

movie_dataset = get_movie_dataset()
pprint(create_movie_profile(movie_dataset))

完善畫像關鍵詞

from gensim.models import TfidfModel

import pandas as pd
import numpy as np

from pprint import pprint

# ......

def create_movie_profile(movie_dataset):
    '''
    使用tfidf,分析提取topn關鍵詞
    :param movie_dataset:
    :return:
    '''
    dataset = movie_dataset["tags"].values

    from gensim.corpora import Dictionary
    # 根據數據集建立詞袋,並統計詞頻,將所有詞放入一個詞典,使用索引進行獲取
    dct = Dictionary(dataset)
    # 根據將每條數據,返回對應的詞索引和詞頻
    corpus = [dct.doc2bow(line) for line in dataset]
    # 訓練TF-IDF模型,即計算TF-IDF值
    model = TfidfModel(corpus)

    _movie_profile = []
    for i, data in enumerate(movie_dataset.itertuples()):
        mid = data[0]
        title = data[1]
        genres = data[2]
        vector = model[corpus[i]]
        movie_tags = sorted(vector, key=lambda x: x[1], reverse=True)[:30]
        topN_tags_weights = dict(map(lambda x: (dct[x[0]], x[1]), movie_tags))
        # 將類別詞的添加進去,並設置權重值爲1.0
        for g in genres:
            topN_tags_weights[g] = 1.0
        topN_tags = [i[0] for i in topN_tags_weights.items()]
        _movie_profile.append((mid, title, topN_tags, topN_tags_weights))

    movie_profile = pd.DataFrame(_movie_profile, columns=["movieId", "title", "profile", "weights"])
    movie_profile.set_index("movieId", inplace=True)
    return movie_profile

movie_dataset = get_movie_dataset()
pprint(create_movie_profile(movie_dataset))

爲了根據指定關鍵詞迅速匹配到對應的電影,因此需要對物品畫像的標籤詞,建立倒排索引

倒排索引介紹

通常數據存儲數據,都是以物品的ID作爲索引,去提取物品的其他信息數據

而倒排索引就是用物品的其他數據作爲索引,去提取它們對應的物品的ID列表

# ......

'''
建立tag-物品的倒排索引
'''

def create_inverted_table(movie_profile):
    inverted_table = {}
    for mid, weights in movie_profile["weights"].iteritems():
        for tag, weight in weights.items():
            #到inverted_table dict 用tag作爲Key去取值 如果取不到就返回[]
            _ = inverted_table.get(tag, [])
            #將電影的id 和 權重 放到一個tuple中 添加到list中
            _.append((mid, weight))
            #將修改後的值設置回去 
            inverted_table.setdefault(tag, _)
    return inverted_table

inverted_table = create_inverted_table(movie_profile)
pprint(inverted_table)

基於內容的電影推薦:用戶畫像

用戶畫像構建步驟:

  • 根據用戶的評分歷史,結合物品畫像,將有觀影記錄的電影的畫像標籤作爲初始標籤反打到用戶身上
  • 通過對用戶觀影標籤的次數進行統計,計算用戶的每個初始標籤的權重值,排序後選取TOP-N作爲用戶最終的畫像標籤

用戶畫像建立

import pandas as pd
import numpy as np
from gensim.models import TfidfModel

from functools import reduce
import collections

from pprint import pprint

# ......

'''
user profile畫像建立:
1. 提取用戶觀看列表
2. 根據觀看列表和物品畫像爲用戶匹配關鍵詞,並統計詞頻
3. 根據詞頻排序,最多保留TOP-k個詞,這裏K設爲100,作爲用戶的標籤
'''

def create_user_profile():
    watch_record = pd.read_csv("datasets/ml-latest-small/ratings.csv", usecols=range(2), dtype={"userId":np.int32, "movieId": np.int32})

    watch_record = watch_record.groupby("userId").agg(list)
    # print(watch_record)

    movie_dataset = get_movie_dataset()
    movie_profile = create_movie_profile(movie_dataset)

    user_profile = {}
    for uid, mids in watch_record.itertuples():
        record_movie_prifole = movie_profile.loc[list(mids)]
        counter = collections.Counter(reduce(lambda x, y: list(x)+list(y), record_movie_prifole["profile"].values))
        # 取出出現次數最多的前50個詞
        interest_words = counter.most_common(50)
        # 取出出現次數最多的詞 出現的次數
        maxcount = interest_words[0][1]
        # 利用次數計算權重 出現次數最多的詞權重爲1
        interest_words = [(w,round(c/maxcount, 4)) for w,c in interest_words]
        user_profile[uid] = interest_words

    return user_profile

user_profile = create_user_profile()
pprint(user_profile)
  • reduce函數

    • 描述

      reduce() 函數會對參數序列中元素進行累積。

      函數將一個數據集合(鏈表,元組等)中的所有數據進行下列操作:用傳給 reduce 中的函數 function(有兩個參數)先對集合中的第 1、2 個元素進行操作,得到的結果再與第三個數據用 function 函數運算,最後得到一個結果。

    • 語法

      reduce() 函數語法:

      reduce(function, iterable[, initializer])
      
    • 參數

      • function -- 函數,有兩個參數
      • iterable -- 可迭代對象
      • initializer -- 可選,初始參數
    • 返回值

      返回函數計算結果。

    • 示例

      >>>def add(x, y) :            # 兩數相加
      ...     return x + y
      ... 
      >>> reduce(add, [1,2,3,4,5])   # 計算列表和:1+2+3+4+5
      15
      >>> reduce(lambda x, y: x+y, [1,2,3,4,5])  # 使用 lambda 匿名函數
      15
      
  • 使用collections.Counter類統計列表元素出現次數

    from collections import Counter
    names = ["Stanley", "Lily", "Bob", "Well", "Peter", "Bob", "Well", "Peter", "Well", "Peter", "Bob","Stanley", "Lily", "Bob", "Well", "Peter", "Bob", "Bob", "Well", "Peter", "Bob", "Well"]
    names_counts = Counter(names)

基於內容的電影推薦:爲用戶產生TOP-N推薦結果

# ......

user_profile = create_user_profile()

watch_record = pd.read_csv("datasets/ml-latest-small/ratings.csv", usecols=range(2),dtype={"userId": np.int32, "movieId": np.int32})

watch_record = watch_record.groupby("userId").agg(list)

for uid, interest_words in user_profile.items():
    result_table = {} # 電影id:[0.2,0.5,0.7]
    for interest_word, interest_weight in interest_words:
        related_movies = inverted_table[interest_word]
        for mid, related_weight in related_movies:
            _ = result_table.get(mid, [])
            _.append(interest_weight)    # 只考慮用戶的興趣程度
            # _.append(related_weight)    # 只考慮興趣詞與電影的關聯程度
            # _.append(interest_weight*related_weight)    # 二者都考慮
            result_table.setdefault(mid, _)

    rs_result = map(lambda x: (x[0], sum(x[1])), result_table.items())
    rs_result = sorted(rs_result, key=lambda x:x[1], reverse=True)[:100]
    print(uid)
    pprint(rs_result)
    break

    # 歷史數據  ==>  歷史興趣程度 ==>  歷史推薦結果       離線推薦    離線計算
    # 在線推薦 ===>    娛樂(王思聰)   ===>   我 ==>  王思聰 100%  
    # 近線:最近1天、3天、7天           實時計算

 

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