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()

显示结果请参见 原文档
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值