亚马逊工程师分享:如何抓取、创建和构造高质量的数据集

AI 科技评论按,数据是所有机器学习问题的核心。如果不能访问相关数据,那么现在使用机器学习所取得的所有进展都是不可能的。尽管如此,如今大多数机器学习爱好者专注于获取方法论知识(这是一个很好的开始,但不是一直如此)。

当方法论达到一定程度时,仅解决数据集可用的问题就限制了其潜力。

幸运的是,我们生活在一个网络上有大量数据可用的时代,我们所需要的只是识别和提取有意义的数据集的技能。对此,亚马逊工程师 Rishabh Misra 分享了他关于如何识别、抓取和构建一个高质量的机器学习数据集的心得,雷锋网 AI 科技评论编译整理如下。

本文的重点是通过真实的案例和代码片段解释如何构建高质量的数据集。

本文将参考作者收集的三个高质量数据集,即服装尺寸推荐数据集、新闻类别数据集和讽刺检测数据集来解释不同的点。下面先简要地解释每个数据集是关于什么的。

  • 服装尺寸推荐数据集

服装尺寸推荐和合身度预测对于改善顾客的购物体验和降低产品返工率至关重要。从 ModCloth 收集的数据集包含客户对其购买的服装是否合适的反馈,以及诸如评级、评论、类别信息、客户度量等其他方面的信息。这个数据集在识别决定服装产品是否适合客户的关键特征方面很有用。

  • 新闻类别数据集

该数据集包含从 HuffPost 获得的 2012 至 2018 年约 20 万条新闻的标题。它包含诸如新闻类别、新闻标题、新闻故事的简短描述、出版日期等详细信息。数据集可以用于多种用途,如识别未跟踪的新闻文章的标签、识别不同新闻类别中使用的语言类型等。

  • 讽刺检测数据集

过去关于讽刺检测的研究大多是利用基于 hashtag 的监督收集的 twitter 数据集,但这些数据集在标签和语言方面存在噪音。为了克服这些限制,这个数据集是从两个新闻网站收集的:TheOnion 和 HuffPost。TheOnion 制作了当前事件的讽刺版本,而 HuffPost 则报道了真实和非讽刺的新闻。

一个有趣的事实是:这些数据集在 Kaggle 上共有超过 250 个赞、50k+次浏览、6000+次下载和 50+个提交者。

步骤1:搜索数据

这个阶段需要耐心,因为你可能需要广泛地进行网络搜索。但你不用担心。在这里,我将根据我的经验提供一些指导,使您的搜索更加系统和高效。

如果您希望收集和构建一个高质量的数据集,那么您可能处于以下两种情况之一:

  • 您正在寻找一个数据集去解决特定的问题 [已知问题]。
  • 您正在寻找可用于解决有趣问题的数据集 [未知问题]。

根据您所处的情况,以下指南将很有帮助。

已知问题

收集服装合身度和讽刺检测数据集,以解决特定的问题。

以下步骤可能有助于在这种情况下搜索数据集:

分解问题以识别解决问题所必需的数据信号:这是最重要的一步。在尺寸推荐问题的情况中,如果我们想向客户推荐服装尺寸,那么最重要的数据信息将是用户 ID、产品 ID、购买的尺寸以及客户对本次购买尺寸是否合适的反馈。其他信息,如产品类别、客户测量等,有了更好但也不是必须的。

在网络上搜索一个提供所有必要信息的来源:在这里,你的谷歌搜索技巧会派上用场。使用它可以浏览多个网站,并查看它们是否提供必要的数据信息。对于服装尺寸匹配数据集,像 Zappos 这样的网站似乎很有希望,但缺少基本的购买尺寸信息,而 ModCloth 确实提供了所有基本数据信息(尽管需要进行一些额外的调整,稍后将详细介绍)。

如果找不到单个数据源,请查看是否可以组合多个数据源的数据来构建数据集:讽刺检测数据集是将多个源的数据组合起来以构建完整且质量良好的数据集的完美示例。因为我们知道问题(发现讽刺)和我们想要的数据类型(讽刺和非讽刺文本),所以我们不必坚持用一个数据源来提供所有信息。我将 TheOnion 确定为获取讽刺文本的来源,而对于非讽刺文本,我选择了一个真正的新闻报道网站 HuffPost。

查看数据源是否包含足够的历史数据,以允许您构造足够大的数据集:这也是在开始收集数据之前需要考虑的一个非常重要的点。如果一个网站没有足够的数据,例如,一个在线零售商没有大量的产品可提供,或者如果一个新闻网站不包含对旧故事的存档,那么即使你收集了这些数据,它也不会给你带来多大好处。所以,寻找一个提供足够数据的数据源来构造足够大的数据集。

如何改进数据集?你能把其他来源的数据结合起来使它更有趣吗?检查完上述所有点后,看看如何进一步改进数据集。思考一下,您是否可以通过不同的数据源组合有关某些属性的更多信息,这些信息可能会帮助人们为他们的模型构建特性。

未知问题

在解释这些类型情况的时候,新闻类别数据集是一个很好的选择。不知道要找的是什么会使情况稍微复杂一点,但是,当您在浏览网页时注意以下几点可以帮助您识别下一个有趣的数据集:

数据源是否包含任何值得估计/预测的数据信号?:分析网站时,请考虑网站是否提供了任何值得评估的有趣信息。它可以是一些直接的东西,或者与网站上的信息类型有关的东西。

一个直截了当的例子是,我在 HuffPost 上注意到,每个故事都被进行了分类(如体育、政治等),而我认为预测分类将是一个有趣的问题。对于信息类型的案例,我将 HuffPost 的新闻标题视为讽刺检测数据集中的非讽刺性句子(假设他们报道的是真实新闻),而将 TheOnion 的标题视为讽刺性句子。

数据源是否包含足够的元数据,这些元数据在预测结果时是否有用?一旦您选定了一个值得预测的数据信息,您就必须确保站点为您提供足够的可用于预测该数据信息的相关信息,如果不是,您是否可以使用其他数据源将该信息带到数据集中。例如,如果我们没有关于商品的元数据,那么在电子商务平台上预测产品价格的效果可能不会很好。为了使数据集成为一个好的数据集,需要足够的相关信息。

站点是否包含足够的历史数据,让你可以构建足够大的数据集?这与「已知问题」部分中的第 4 点相同。 预测结果有什么重要的意义或应用吗?高质量数据集的一个标志是,它还可以用于解决有趣的实际问题,或者能够对某些现象提供有趣的见解。例如,基于新闻类别数据集构建的分类器可以帮助识别任何散文的写作风格(无论是政治、幽默等),帮助标记未跟踪的新闻文章,提供对不同类型新闻的写作风格差异的洞察等等。

交叉检查以查看此类数据是否已经可用。如果是,数据集是否在现有数据集上添加了任何内容?这一步很重要,这样你就知道你在贡献一些独特的东西,而不是一些已经存在的东西。从这一步开始,在谷歌上简单搜索就足够了。

如何改进数据集?你能把其他来源的数据结合起来使它更有趣吗?这与「已知问题」部分中的第 5 点相同。

步骤 2:提取数据

一旦缩小了数据源范围,我们就可以开始提取数据了。

在抓取数据之前,请仔细阅读网站的条款,以确保您不会因为抓取和公开分发数据而违反法律规则。

由于在不举实际例子的情况下很难解释这一节,因此我将以我在从 ModCloth 获取数据时使用的脚本为例来阐述不同的观点。

了解网站的结构

首先要做的是熟悉站点的结构。

在 ModCloth 上,我们看到在网站顶部有各种服装类别:连衣裙、上衣、下装等等。如果我们单击其中一个类别(如上图中的顶部),就会看到产品以网格格式显示。图片中的页面显示 100 个产品,其余产品可通过滚动右上角附近的页面滚动器访问。

接下来,我们单击其中一个产品来观察每个产品的页面是什么样子的。在顶部,我们有与项目相关的元数据,在底部,我们有产品评论。

我们注意到每一页最多包含 10 条评论。如果评论超过 10 条,我们会在右下角看到「NEXT」按钮。

当我们单击「NEXT」按钮时,将显示接下来的 10 条评论。但是,您可能会注意到链接没有更改,这意味着除了单击「NEXT」按钮之外,没有其他方法可以访问后续评论。我们还可以看到,在随后的页面中,还会出现「PREVIOUS」按钮。稍后我们将知道为什么这些细节对于数据提取很重要。

我们现在对网站的结构有了相当好的了解。重申一下,我们的目标是从每个类别中提取每个产品的评论。

提取产品链接

由于类别数量有限,因此不需要编写脚本来提取链接,我们可以手动收集这些链接。在本节中,我们将重点从服装类别之一:上衣中提取产品链接:

https://medium.freecodecamp.org/how-to-scrape-websites-with-python-and-beautifulsoup-5946935d93fe

要了解数据提取的基础知识,请浏览以下博客:如何使用 pytho 和 BeautifulSoup 提取网站数据 我们还将利用浏览器自动化工具 Selenium 进行数据提取。

要了解 Selenium 的工作原理,请浏览以下博客:

使用 Selenium 提取网站数据: https://medium.com/the-andela-way/introduction-to-web-scraping-using-selenium-7ec377a8cf72

那么,让我们开始吧:

到目前为止,我们知道在每个类别中,产品以每组 100 个的形式呈现,我们可以滚动页面滚动器来访问所有产品。首先,我们需要了解不同页面的链接是如何变化的。通常情况下,以下图片建议使用遵循一个模式的链接。

页面 1

页面 2

页面 3

然后,对于每个页面,我们需要提取到单个项目的页面的链接。为此,请转到其中一个项目,右键单击该项目并转到「inspect」选项。滚动滚动条以识别包含 item 链接的<a>元素并注意其 css 类。在下面的图片中,我们看到在我们的例子中,类是虚链接。最有可能的是,所有其他产品链接也将使用相同的类进行样式设计(只需验证一次)。

有了这些信息,我们可以编写以下代码来提取 Tops 类别中所有产品的链接:

rom selenium import webdriver

from bs4 import BeautifulSoup

# download driver from http://chromedriver.chromium.org/downloads

path_to_chromedriver = './chromedriver2.exe'

browser = webdriver.Chrome(executable_path = path_to_chromedriver)

urls = []; counter = 0; tops_link = []

## Since Tops category has 7 pages, link to each following a specific pattern,

## we can create links to pages in following way.

for i in range(7):

urls.append('https://www.modcloth.com/shop/tops?sz=102&start='+str(counter))

counter += 102

## Extracting links for products in each page

for url in urls:

## open the url

browser.get(url)

## purposeful wait time to allow website to get fully loaded

time.sleep(4)

## get page content

content = browser.page_source

soup = BeautifulSoup(content, "lxml")

product_links = []

## extract all the "a" elements with "thumb-link" class from the page

data_links = soup.find_all("a", {"class":"thumb-link"})

## from each <a> element, extract the URL

for i in data_links:

product_links.append(i['href'])

tops_link.extend(product_links)

## purposeful wait time to avoid sending requests in quick succession

time.sleep(10)

正如您所注意到的,脚本有等待时间,以确保我们不会向站点太频繁地发送请求。通常,每秒一个请求是合适的,但是考虑到 ModCloth 是一个小站点(可能没有亚马逊那么大),我们可以将等待时间延长。在这方面你可以运用你的判断力。

提取评论

既然我们已经为每个产品建立了一个链接,那么我们就可以更深入地了解每个产品的评论。首先,我们将检查每个评论对应的 HTML。再次,右键单击查看并单击「inspect」。

我们注意到每个评论都包含在<article>元素中。让我们来探索一下<article>元素的构成。我们可以通过单击元素旁边的箭头来实现这一点。当我们将鼠标悬停在<article>标记内的各个元素上时,相应的视图将在网页上突出显示。

例如,在上面的图像中,具有名为「pr-rd-content-block pr-accordion pr-accordion-collapsed」的类的<section>元素折叠对应于尺寸反馈意见和与客户测量相关的数据。请参阅下面的脚本以了解如何提取<article>里面所有相关内容的详细信息。

from selenium.common.exceptions import NoSuchElementException, WebDriverException

import numpy as np

import random

## helper function to consolidate two dictionaries

def merge_two_dicts(x, y):

z = x.copy() # start with x's keys and values

z.update(y) # modifies z with y's keys and values & returns None

return z

scraped_data = []

## for each product in Tops category

for iterr in range(0,len(tops_link)):

init = 0

url = tops_link[iterr]

## open the URL in browser

try:

browser.get(url)

time.sleep(4)

except WebDriverException: ## when extracted URL is invalid

print('invalid url', iterr)

continue

## get the webpage content

content = browser.page_source

soup = BeautifulSoup(content, "lxml")

## repeat until we run of review pages

while(True):

## get the webpage content

content = browser.page_source

soup = BeautifulSoup(content, "lxml")

## extract reviewer details

reviewer_details = soup.find_all("div", {"class": "pr-rd-reviewer-details pr-rd-inner-side-cont ent-block"})

## extract reviewers' name

reviewers_name = []

for reviewer in reviewer_details:

## In ModCloth, reviewer name appears as "By REVIEWER_NAME"

## Splitting at the end is to remove "By" to get only the actual reviewer name

reviewer_name = reviewer.find("p", {"class":"pr-rd-details pr-rd-author-nickname"}).te xt.split('\n')[-1].strip()

reviewers_name.append(reviewer_name)

## extract "isVerified" information

isVerified = soup.find_all("span", {"class": "pr-rd-badging-text"})

## extract the fit feedback and customer measurements data (review_metadata)

review_data = soup.find_all("article", {"class": "pr-review"})

review_metadata_raw = []

for i in range(len(review_data)):

review_metadata_raw.append(review_data[i].find("div", {"class": "pr-accordion-conten t"}))

## extract HTML elements which contain review metadata

review_metadata_elements = [review_metadata_raw[i].find_all("dl", {"class", "pr-rd-def-list" })

if review_metadata_raw[i] is not None else None

for i in range(len(review_metadata_raw))]

## extract actual data from HTML elements

review_metadata = []

for element in review_metadata_elements:

if element is None:

review_metadata.append(None)

continue

## <dt> elements contain metadata field name like "fit", "length" etc

## <dd> elements contain reviewer's response for those metadata fields like "small", "just right" etc

review_metadata.append([(element[i].find("dt").text.lower(), element[i].find("dd").text. lower())

if element is not None else ""

for i in range(len(element))])

## extract review text

review_text = [txt.text for txt in soup.find_all("p", {"class": "pr-rd-description-text"})]

review_summary = [txt.text for txt in soup.find_all("h2", {"class": "pr-rd-review-headline"})]

## extract item id

item_id = soup.find("div", {"class": "product-number"}).find("span").text

## extract item category

try:

category = soup.find("a", {"class":"breadcrumb-element"}).text.lower()

except AttributeError: ## if category not present, item is not available

time.sleep(15 + random.randint(0,10))

break

## extract available product sizes

product_sizes = [i.text.strip().lower() for i in soup.find("ul", {"class": "swatches size"})

.find_all("li", {"class": "selectable variation-group-value"})]

item_info = {"category": category, "item_id": item_id, "product_sizes": product_sizes}

## consolidate all the extracted data

## ignore records which don't have any review metadata as fit feedback is an essential sig nal for us

scraped_data.extend([merge_two_dicts({"review_text": review_text[j], "review_summary":

review_summary[j]}, merge_two_dicts(merge_two_ dicts({

"user_name":reviewers_name[j]},

{data[0]:data[1] for data in review_metadata[j]})

,item_info))

for j in range(len(reviewer_details)) if review_metadata_raw[j] is not None])

## if current page is the initial one, it contains only NEXT button (PREVIOUS is missing)

if init == 0:

try:

init = 1

## execute click on NEXT by utilizing the xpath of NEXT

browser.execute_script("arguments[0].click();", browser.find_element_by_xpath ('//*[@id="pr-review-display"]/footer/div/aside/button')) time.sleep(10 + random.randint(0,5))

except NoSuchElementException: ## No NEXT button present, less than 10 reviews time.sleep(15 + random.randint(0,10))

break

else:

try:

## execute click on NEXT by utilizing the xpath of NEXT

## if you notice, the xpath of NEXT is different here since PREVIOUS button is also pre sent now

browser.execute_script("arguments[0].click();", browser.find_element_by_xpath('//*[@ id="pr-review-display"]/footer/div/aside/button[2]'))

time.sleep(10 + random.randint(0,5))

except NoSuchElementException: ## No NEXT button, no more pages left

time.sleep(15 + random.randint(0,10))

break

## save the extracted data locally

np.save('./scraped_data_tops.npy',scraped_data)

需要注意的几点:

我们在许多地方做过异常处理。当我在运行脚本时遇到问题时,这些处理都会逐步添加进去。

第 30-97 行负责将感兴趣的数据提取出来并解析为字典格式。通常,人们更喜欢将提取的数据存储在本地并离线解析,但是,由于笔记本电脑的存储空间限制,我更喜欢在线进行分析。

Selenium 在第 99-119 行中很有用。由于不同页面的 URL 不会更改,所以导航的唯一方法是模拟按钮单击。我们已经使用「NEXT」按钮的 xpath 来完成同样的工作。

xpath 可用于浏览 XML 文档中的元素和属性。要标识元素的 xpath,请转到 inspect,右键单击 HTML 代码并复制 xpath,如下图所示。

获取 HTML 元素的 xpath 的方法;在本例中,为「NEXT」按钮

这就完成了数据提取和分析过程,之后我们数据中的记录将如下图所示:

看起来,我们的工作已经完成了。但是,仍然有几个步骤可以构建最终的数据集。

步骤 3:构建数据集

到目前为止,我们所拥有的数据质量在以下几个维度上有一些改进空间:

数据清洗

到目前为止提取的数据可能缺少一些基本的数据信息或者记录,谢谢数据可以被安全地处理掉。例如: 有很多关于 ModCloth 的评论,它们不包含是否合身的反馈或购买产品尺寸信息。尽管我们放弃了第 64-66 行中不包含任何相关信息的评论,但也存在一些包含相关信息但不包含某些基本数据信息的评论。

我们注意到,ModCloth 上评论人的信息并没有和任何特定的 ID 关联。这对我们来说是另一个挑战,因为用户 ID 是必不可少的数据。为了解决这个问题,我们可以将评论者的昵称与打分值连接起来,以形成一个唯一的字符串。我们可以字符串要求至少包含 3 个字段信息,以减少数据集中由于不同的评论者得到相同的 ID 而产生的干扰。然后就可以安全地删除不存在此类信息的所有记录。

此外,很少有记录显示产品目录尺寸中没有的采购尺寸(可能是报告错误),因此我们也抛弃了这些记录。

匿名处理

为了保护隐私,需要对用户和条目的详细信息进行匿名处理。在 ModCloth 数据集中有一些敏感的属性,比如身体尺寸数据,我们可以随机生成用户 ID 和条目 ID。如果我们提取的是非公开的数据信息,这一点就变得更加重要了。

标准化

数据中可能存在一些属性,这些属性在所有记录中的含义可能并不完全相同。在这种情况下,我们需要使用直觉(或一些基线)来标准化数据集中的属性。例如,ModCloth 上的不同产品可能有不同的尺码单位(比如牛仔裤在美国和中国有完全不同的尺码单位)。因此,在我们标准化所有记录的数据之前,数据集基本上是不可用的。

处理这一问题的一种方法是利用 ModCloth 上的尺寸表,它将不同单位的尺寸转换为统一的标准。然而,有些产品中仍然存在一些单位未知的尺寸。我们可以利用我们的直觉将它们转换为标准尺度,或者删除数据不确定的评论。

结构化

在我们完成了所有的预处理之后,我们要做的事情就是将数据结构转换成常见的格式,如 csv、json 等,以便让对数据集感兴趣的人能够很容易地读取和导入数据。

结束语

完成上述所有步骤后,数据中的记录可能如下图所示:

吸收知识的最好方法就是动手实践,所以同学们现在就可以找个数据集,开始练手啦!

原文发布于微信公众号 - AI科技评论(aitechtalk)

原文发表时间:2019-05-03

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

发表于

我来说两句

0 条评论
登录 后参与评论

扫码关注云+社区

领取腾讯云代金券