BigGorilla的官方幫助文檔

原文檔地址:https://www.biggorilla.org/walkt

幫助教程


本教程的主要目的是突出顯示BigGorilla提供的“實體匹配”問題的工具。這裏展示的工作流集成了從不同來源獲得的兩個電影數據集。實體匹配(entity-matching)這一步在本教程的最後一部分(第4部分)討論。但是我們建議讀者閱讀第1-3部分,在這裏我們將展示如何部署現有的python包,以便爲實體匹配任務準備數據。


第一部分:數據採集
第二部分:數據提取
第三部分:數據分析與清洗
第四部分:數據匹配與合併

第一部分:數據採集

我們首先使用一個流行的python包urllib,用於在web上獲取數據,以下載本教程所需要的數據集。

步驟一:下載"kaggle5000電影數據集"

需要的數據集是一個.csv格式的文件,需要用到下面代碼段中指定的url。

# Importing urlib
import urllib
import os

# Creating the data folder
if not os.path.exists('./data'):
    os.makedirs('./data')

# Obtaining the dataset using the url that hosts it
kaggle_url = 'https://github.com/sundeepblue/movie_rating_prediction/raw/master/movie_metadata.csv'
if not os.path.exists('./data/kaggle_dataset.csv'):     # avoid downloading if the file exists
    response = urllib.urlretrieve(kaggle_url, './data/kaggle_dataset.csv')

步驟二:下載"IMDB純文本數據"

IMDB純文本數據(請參閱這裏)是一個文件集合,其中每個文件描述一個或幾個屬性。我們關注一個電影屬性的子集意味着我們只對下面列出的幾個文件感興趣:

  • genres.list.gz
  • ratings.list.gz

**注意:上面提到的文件總大小大約爲30M,運行下面代碼可能會需要幾分鐘。

import gzip

# Obtaining IMDB's text files
imdb_url_prefix = 'ftp://ftp.funet.fi/pub/mirrors/ftp.imdb.com/pub/'
imdb_files_list = ['genres.list.gz', 'ratings.list.gz']
for name in imdb_files_list:
    if not os.path.exists('./data/' + name):
        response = urllib.urlretrieve(imdb_url_prefix + name, './data/' + name)
        urllib.urlcleanup()   # urllib fails to download two files from a ftp source. This fixes the bug!
        with gzip.open('./data/' + name) as comp_file, open('./data/' + name[:-3], 'w') as reg_file:
            file_content = comp_file.read()
            reg_file.write(file_content)

步驟三:下載"IMDB準備數據"

在本教程中,我們討論genres.list.gz和ratings.list.gz中的內容如何被整合。但是,爲了使教程更簡潔,我們避免對所有"IMDB純文本數據"文件包含同樣的處理。"IMDB準備數據"是我們通過整合來自"IMDB純文本數據"的大量文件所獲得的數據集,我們會在本教程的後續階段中使用這份數據。下面的代碼段下載這個數據集。

imdb_url = 'https://anaconda.org/BigGorilla/datasets/1/download/imdb_dataset.csv'
if not os.path.exists('./data/imdb_dataset.csv'):     # avoid downloading if the file exists
    response = urllib.urlretrieve(kaggle_url, './data/imdb_dataset.csv')


第二部分:數據提取

"Kaggle5000電影數據集"存儲在一個.csv格式文件中,該文件已經是結構化數據且可以直接使用。另一方面,"IMDB純文本數據"是一個半結構化文本文件的集合,需要處理這些文件來提取數據。快速瀏覽每個文件的前幾行,就會發現每個文件有不同的格式,需要分開處理。

ratings.list數據文件內容:
with open("./data/ratings.list") as myfile:
    head = [next(myfile) for x in range(38)]
print (''.join(head[28:38]))   # skipping the first 28 lines as they are descriptive headers


genres.list數據文件內容:
with open("./data/genres.list") as myfile:
    head = [next(myfile) for x in range(392)]
print (''.join(head[382:392]))   # skipping the first 382 lines as they are descriptive header


步驟一:從"genres.list"中提取信息

這一步的目標是從movies.list中提取電影名稱和製作年份,然後將提取的數據存儲到一個dataframe中。Dataframe(來源於python包:pandas)是用於數據分析和清洗的主要工具之一。爲了從文本中提取想要的信息,我們依賴於正則表達式,正則表達式在python包"re"中實現。
import re
import pandas as pd

with open("./data/genres.list") as genres_file:
    raw_content = genres_file.readlines()
    genres_list = []
    content = raw_content[382:]
    for line in content:
        m = re.match(r'"?(.*[^"])"? \(((?:\d|\?){4})(?:/\w*)?\).*\s((?:\w|-)+)', line.strip())
        genres_list.append([m.group(1), m.group(2), m.group(3)])
    genres_data = pd.DataFrame(genres_list, columns=['movie', 'year', 'genre'])

步驟二:從"ratings.list"中提取信息

with open("./data/ratings.list") as ratings_file:
    raw_content = ratings_file.readlines()
    ratings_list = []
    content = raw_content[28:]
    for line in content:
        m = re.match(r'(?:\d|\.|\*){10}\s+\d+\s+(1?\d\.\d)\s"?(.*[^"])"? \(((?:\d|\?){4})(?:/\w*)?\)', line.strip())
        if m is None: continue
        ratings_list.append([m.group(2), m.group(3), m.group(1)])
    ratings_data = pd.DataFrame(ratings_list, columns=['movie', 'year', 'rating'])

注意,如果對其他數據文件感興趣,也必須對其他數據文件重複提取過程。現在(爲了保持教程的簡潔),我們假設我們只對電影的類型(genres)和評級(ratings)感興趣。上面的代碼段將從這兩個屬性中提取出的數據存儲到兩個dataframes中(即genres_list和ratings_list)


第三部分:數據分析與清洗

在此階段數據預處理的高級目標是查看我們迄今爲止採集和提取的數據。這有助於我們熟悉數據,瞭解數據需要清洗或轉換的方式,並最終使我們能夠爲下面進行的數據整合工作準備數據。

步驟一:加載"kaggle5000電影數據集"

對於這一步,我們依賴於dataframes(來自python包:pandas),因爲它們被設計用來幫助用戶進行數據挖掘和數據分析工作。在本教程的第二部分中,我們將從"IMDB純文本數據"中提取的數據存儲到dataframes中。也應該將"Kaggle5000電影數據集"加載到一個dataframe中,並遵循對所有數據集進行相同數據分析過程。

import pandas as pd

# Loading the Kaggle dataset from the .csv file (kaggle_dataset.csv)
kaggle_data = pd.read_csv('./data/kaggle_dataset.csv')

步驟二:計算一些基本的統計數據(數據分析過程)

讓我們先來看看每個dataframe中列出了多少部電影。

print ('Number of movies in kaggle_data: {}'.format(kaggle_data.shape[0]))
print ('Number of movies in genres_data: {}'.format(genres_data.shape[0]))
print ('Number of movies in ratings_data: {}'.format(ratings_data.shape[0]))


我們還可以查看數據中是否有重複的內容(例如一個電影出現不止一次)。如果我們能夠找到另一份具有相同的電影名稱和製作年份的作品,我們就認爲這是一條重複數據。

print ('Number of duplicates in kaggle_data: {}'.format(
    sum(kaggle_data.duplicated(subset=['movie_title', 'title_year'], keep=False))))
print ('Number of duplicates in genres_data: {}'.format(
    sum(genres_data.duplicated(subset=['movie', 'year'], keep=False))))
print ('Number of duplicates in ratings_data: {}'.format(
    sum(ratings_data.duplicated(subset=['movie', 'year'], keep=False))))


步驟三:處理重複數據(數據清洗過程)

有許多策略可以處理重複的問題。在這裏,我們使用一個簡單的方法來處理副本,那就是隻保留重複條目的第一條並刪除剩餘條目。

kaggle_data = kaggle_data.drop_duplicates(subset=['movie_title', 'title_year'], keep='first').copy()
genres_data = genres_data.drop_duplicates(subset=['movie', 'year'], keep='first').copy()
ratings_data = ratings_data.drop_duplicates(subset=['movie', 'year'], keep='first').copy()

步驟四:規範化文本(數據清洗過程)

我們用於整合電影數據集的關鍵屬性是電影名稱,因此,規範化這些名稱很重要。下面的代碼段的作用是:使所有電影名稱小寫,刪除特定字符,比如說""和"?",替換一些其他特殊字符,比如說"&"替換成"and"。
def preprocess_title(title):
    title = title.lower()
    title = title.replace(',', ' ')
    title = title.replace("'", '')    
    title = title.replace('&', 'and')
    title = title.replace('?', '')
    title = title.decode('utf-8', 'ignore')
    return title.strip()

kaggle_data['norm_movie_title'] = kaggle_data['movie_title'].map(preprocess_title)
genres_data['norm_movie'] = genres_data['movie'].map(preprocess_title)
ratings_data['norm_movie'] = ratings_data['movie'].map(preprocess_title)

步驟五:看幾個樣本

這一步的目標是查看每個數據集的幾個示例條目,以便快速檢查。爲了保持教程簡潔,我們只展示存儲在kaggle_data這個dataframe中的“kaggle5000電影數據集”這一步。
kaggle_data.sample(3, random_state=0)

顯示結果請參見原文檔

查看這些數據可以指導我們決定哪些方法可以清理數據,例如,上面所示的小樣例數據表明屬性title_year被存儲爲浮點數(即:有理數)。我們可以添加其他清洗步驟將title_year轉換成字符串類型,並用符號"?"替代缺失的電影名稱和製作年份。

def preprocess_year(year):
    if pd.isnull(year):
        return '?'
    else:
        return str(int(year))

kaggle_data['norm_title_year'] = kaggle_data['title_year'].map(preprocess_year)
kaggle_data.head()

顯示結果請參見原文檔

第四部分:數據匹配與合併

這部分的主要目標是將不同來源獲得的數據進行匹配創建出一個單一富數據集。回想在第三部分,我們將所有數據集轉換爲一個dataframe,我們用它來清洗數據。在這一部分,我們繼續對到目前爲止準備的數據使用相同的dataframes。

步驟一:整合"IMDB純文本數據"文件

請注意,ratings_data dataframe 和 genres_data dataframe都包含來自同一數據源的數據(例如"IMDB純文本數據")。因此,我們假設在這些dataframes中存儲的數據之間沒有不一致的地方,然後將它們組合到一起,我們需要做的就是匹配共享相同電影名稱和製作年份的條目。這個簡單的"精確匹配"可以由簡單的dataframes來完成。
brief_imdb_data = pd.merge(ratings_data, genres_data, how='inner', on=['norm_movie', 'year'])

brief_imdb_data.head()

顯示結果請參見原文檔

我們將上面創建的數據集稱爲brief_imdb_data,因爲它只包含兩個屬性(即類型和評級)。之後,我們將使用一個更豐富的IMDB數據集,我們通過整合大量"IMDB純文本數據"文件來創建它。如果您已經完成了本教程的第一部分,那麼這個數據集就已經被下載並存儲到data文件夾下的imdb_dataset.csv中。下面的代碼段加載了這個數據集,對電影名稱和製作年份進行預處理,刪除了之前的副本,並打印數據集的大小。

# reading the new IMDB dataset
imdb_data = pd.read_csv('./data/imdb_dataset.csv')
# let's normlize the title as we did in Part 3 of the tutorial
imdb_data['norm_title'] = imdb_data['title'].map(preprocess_title)
imdb_data['norm_year'] = imdb_data['year'].map(preprocess_year)
imdb_data = imdb_data.drop_duplicates(subset=['norm_title', 'norm_year'], keep='first').copy()
imdb_data.shape


步驟二:整合Kaggle和IMDB數據集

整合兩個數據集的一種簡單的方法是單純的合併具有相同電影名稱和製作年份的條目。下面的代碼顯示了使用這種簡單的方法可以找到4248個匹配項。

data_attempt1 = pd.merge(imdb_data, kaggle_data, how='inner', left_on=['norm_title', 'norm_year'],
                         right_on=['norm_movie_title', 'norm_title_year'])
data_attempt1.shape


但是,鑑於IMDB和Kaggle的數據集來自於不同的數據源,那麼在這些數據集裏,電影的名稱可能略有差異(例如"Wall.E"和"WALLE")。爲了找到這樣的匹配,我們可以看看電影名稱的相似度,並考慮將高度相似的電影名稱看作相同的實體。BigGorilla提供了一個數據包叫py_stringsimjoin,用於對兩個數據集做相似度連接。下面的代碼段使用py_stringsimjoin來匹配所有具有編輯距離(edit distance)的電影名稱(即:至少由一個字符需要被修改/添加/刪除操作來使兩個電影名稱相同)。一旦相似度連接完成,只選擇了電影製作年份相同的數據對。
import py_stringsimjoin as ssj
import py_stringmatching as sm

imdb_data['id'] = range(imdb_data.shape[0])
kaggle_data['id'] = range(kaggle_data.shape[0])
similar_titles = ssj.edit_distance_join(imdb_data, kaggle_data, 'id', 'id', 'norm_title',
                                        'norm_movie_title', l_out_attrs=['norm_title', 'norm_year'],
                                         r_out_attrs=['norm_movie_title', 'norm_title_year'], threshold=1)
# selecting the entries that have the same production year
data_attempt2 = similar_titles[similar_titles.r_norm_title_year == similar_titles.l_norm_year]
data_attempt2.shape


我們可以看到,使用相似度連接,有4689個電影名稱被匹配上。讓我們來看一下這些電影名稱,它們通過相似度連接匹配上,但是並不完全相同。
data_attempt2[data_attempt2.l_norm_title != data_attempt2.r_norm_movie_title].head()

顯示結果請參見原文檔

步驟三:使用麥哲倫方法(Magellan)進行數據匹配

子步驟一:尋找候選集(Blocking)

這一步的目標是使用簡單的啓發式方法來限制我們考慮可以將其作爲潛在匹配項的數據對的數量。對於這個任務,我們可以在每個數據集中創建一個新列,將重要屬性的值合併到單個字符串中(我們稱之爲混合項(mixture))。然後,我們可以像以前一樣使用字符串相似度連接來找到一組實體,它們在重要列的值中有一些重疊。在此之前,我們需要將混合項(mixture)中的一部分列內容轉換爲字符串類型。py_stringsimjoin包允許我們很容易完成這項工作。
# transforming the "budget" column into string and creating a new **mixture** column
ssj.utils.converter.dataframe_column_to_str(imdb_data, 'budget', inplace=True)
imdb_data['mixture'] = imdb_data['norm_title'] + ' ' + imdb_data['norm_year'] + ' ' + imdb_data['budget']

# repeating the same thing for the Kaggle dataset
ssj.utils.converter.dataframe_column_to_str(kaggle_data, 'budget', inplace=True)
kaggle_data['mixture'] = kaggle_data['norm_movie_title'] + ' ' + kaggle_data['norm_title_year'] + \
                         ' ' + kaggle_data['budget']

現在,我們可以使用混合項這一列來創建一個需要的候選集,我們稱之爲C。

C = ssj.overlap_coefficient_join(kaggle_data, imdb_data, 'id', 'id', 'mixture', 'mixture', sm.WhitespaceTokenizer(), 
                                 l_out_attrs=['norm_movie_title', 'norm_title_year', 'duration',
                                              'budget', 'content_rating'],
                                 r_out_attrs=['norm_title', 'norm_year', 'length', 'budget', 'mpaa'],
                                 threshold=0.65)
C.shape


我們可以看到,通過相似度連接,已經將候選集縮減爲18317項。

子步驟二:指定關鍵字

下一步是指定py_entitymatching包中的列對應每個dataframe中的鍵值。另外,我們需要指定哪些列對應於候選集中的兩個dataframes的外鍵。
import py_entitymatching as em
em.set_key(kaggle_data, 'id')   # specifying the key column in the kaggle dataset
em.set_key(imdb_data, 'id')     # specifying the key column in the imdb dataset
em.set_key(C, '_id')            # specifying the key in the candidate set
em.set_ltable(C, kaggle_data)   # specifying the left table 
em.set_rtable(C, imdb_data)     # specifying the right table
em.set_fk_rtable(C, 'r_id')     # specifying the column that matches the key in the right table 
em.set_fk_ltable(C, 'l_id')     # specifying the column that matches the key in the left table 


子步驟三:調試攔截器

現在,我們需要確保候選集足夠寬鬆,可以包含不太相似的幾對電影。如果不是這樣,我們就可能去掉了可能匹配成功的數據對。通過觀察候選集中的幾對數據,我們可以判斷數據攔截這一步是否過於苛刻。

注意:py_entitymatching包也提供了調試攔截器的一些工具。
C[['l_norm_movie_title', 'r_norm_title', 'l_norm_title_year', 'r_norm_year',
   'l_budget', 'r_budget', 'l_content_rating', 'r_mpaa']].head()

顯示結果請參見原文檔

根據上面的例子,我們可以看到攔截過程似乎是合理的。

子步驟四:從候選集抽樣

這一步的目標是從候選集中獲得一個樣本,並手工標記取樣的候選集。也就是說,指定候選對是否匹配正確。
# Sampling 500 pairs and writing this sample into a .csv file
sampled = C.sample(500, random_state=0)
sampled.to_csv('./data/sampled.csv', encoding='utf-8')

爲了標記被採樣的數據,我們在.csv文件中創建一個新的列(即label列),如果匹配正確,就將值置爲1,否則置爲0。爲了避免覆蓋原文件,我們將新文件重命名爲labeled.csv。
# If you would like to avoid labeling the pairs for now, you can download the labled.csv file from
# BigGorilla using the following command (if you prefer to do it yourself, command the next line)
response = urllib.urlretrieve('https://anaconda.org/BigGorilla/datasets/1/download/labeled.csv',
                              './data/labeled.csv')
labeled = em.read_csv_metadata('data/labeled.csv', ltable=kaggle_data, rtable=imdb_data,
                               fk_ltable='l_id', fk_rtable='r_id', key='_id')
labeled.head()

顯示結果請參見原文檔

子步驟五:訓練機器學習算法

現在,我們可以使用這個樣本數據集來爲我們的預測任務訓練各種機器學習算法。爲此,我們需要將我們的數據集分解爲一個訓練集和一個測試集,然後爲我們的預測任務選擇所需的機器學習技術。
split = em.split_train_test(labeled, train_proportion=0.5, random_state=0)
train_data = split['train']
test_data = split['test']

dt = em.DTMatcher(name='DecisionTree', random_state=0)
svm = em.SVMMatcher(name='SVM', random_state=0)
rf = em.RFMatcher(name='RF', random_state=0)
lg = em.LogRegMatcher(name='LogReg', random_state=0)
ln = em.LinRegMatcher(name='LinReg')
nb = em.NBMatcher(name='NaiveBayes')

在我們應用任何機器學習技術之前,我們需要提取一組特徵。幸運的是,一旦我們指定兩個數據集中的哪些列彼此對應,py_entitymatching包可以自動提取一組特徵。下面的代碼段首先指定兩個數據集的列之間的對應關係。然後,使用py_entitymatching包來確定每一列的類型。通過考慮每個數據集的列的類型(存儲在變量l_attr_types和r_attr_types中)以及使用py_entitymatching包中建議的分詞器和相似度函數,我們可以提取一組用於提取特徵的說明。注意,變量F不是提取的特徵集合,而是爲了計算特徵編碼了指令。
attr_corres = em.get_attr_corres(kaggle_data, imdb_data)
attr_corres['corres'] = [('norm_movie_title', 'norm_title'), 
                         ('norm_title_year', 'norm_year'),
                        ('content_rating', 'mpaa'),
                         ('budget', 'budget'),
]

l_attr_types = em.get_attr_types(kaggle_data)
r_attr_types = em.get_attr_types(imdb_data)

tok = em.get_tokenizers_for_matching()
sim = em.get_sim_funs_for_matching()

F = em.get_features(kaggle_data, imdb_data, l_attr_types, r_attr_types, attr_corres, tok, sim)

給定了一組需要的特徵F,現在我們可以計算訓練數據的特徵值,並估算數據中缺失的值。這種情況下,我們選擇列的平均值來替換缺失的值。
train_features = em.extract_feature_vecs(train_data, feature_table=F, attrs_after='label', show_progress=False) 
train_features = em.impute_table(train_features,  exclude_attrs=['_id', 'l_id', 'r_id', 'label'], strategy='mean')

利用計算的特徵,我們可以評價不同機器學習算法的性能,並選擇對於我們匹配任務最好的那個。
result = em.select_matcher([dt, rf, svm, ln, lg, nb], table=train_features, 
                           exclude_attrs=['_id', 'l_id', 'r_id', 'label'], k=5,
                           target_attr='label', metric='f1', random_state=0)
result['cv_stats']

顯示結果請參見原文檔

我們基於不同技術報告的精確度來觀察發現"隨機森林(RF)"算法性能最佳,因此,最好使用這種技術進行我們的匹配任務。

子步驟六:評估匹配質量

評估匹配的質量是很重要的,現在我們可以使用訓練集來達到這個目的並測量隨機森林算法預測的匹配項有多好。我們可以看到,我們在測試集上獲得了很高的準確性和回調率。
best_model = result['selected_matcher']
best_model.fit(table=train_features, exclude_attrs=['_id', 'l_id', 'r_id', 'label'], target_attr='label')

test_features = em.extract_feature_vecs(test_data, feature_table=F, attrs_after='label', show_progress=False)
test_features = em.impute_table(test_features, exclude_attrs=['_id', 'l_id', 'r_id', 'label'], strategy='mean')

# Predict on the test data
predictions = best_model.predict(table=test_features, exclude_attrs=['_id', 'l_id', 'r_id', 'label'], 
                                 append=True, target_attr='predicted', inplace=False)

# Evaluate the predictions
eval_result = em.eval_matches(predictions, 'label', 'predicted')
em.print_eval_summary(eval_result)


子步驟七:使用訓練模型匹配數據集

現在,我們可以使用經過訓練的模型來匹配這兩個表,如下:
candset_features = em.extract_feature_vecs(C, feature_table=F, show_progress=True)
candset_features = em.impute_table(candset_features, exclude_attrs=['_id', 'l_id', 'r_id'], strategy='mean')
predictions = best_model.predict(table=candset_features, exclude_attrs=['_id', 'l_id', 'r_id'],
                                 append=True, target_attr='predicted', inplace=False)
matches = predictions[predictions.predicted == 1]

請注意:匹配的dataframe包含許多列,這些列存儲了兩個數據集提取的特徵。下面的代碼段刪除了所有不必要的列,並創建了一個漂亮的格式化的dataframe,它擁有數據集整合後的最終結果。

from py_entitymatching.catalog import catalog_manager as cm
matches = matches[['_id', 'l_id', 'r_id', 'predicted']]
matches.reset_index(drop=True, inplace=True)
cm.set_candset_properties(matches, '_id', 'l_id', 'r_id', kaggle_data, imdb_data)
matches = em.add_output_attributes(matches, l_output_attrs=['norm_movie_title', 'norm_title_year', 'budget', 'content_rating'],
                                   r_output_attrs=['norm_title', 'norm_year', 'budget', 'mpaa'],
                                   l_output_prefix='l_', r_output_prefix='r_',
                                   delete_from_catalog=False)
matches.drop('predicted', axis=1, inplace=True)
matches.head()

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