教你如何用 Amazon Mechanical Turk + Python 蒐集資料
從 MTurk 上蒐集資料的基礎教學
Mechanical Turk 是 Amazon 旗下一個群眾外包的網站。最廣為人知的功能就是請人幫我們標記那些要用在機器學習的資料,不過只要是你的資料是可以透過網頁的形式來蒐集,像是做問卷之類的,都可以利用 MTurk。當然!這是要收錢的!
這篇文章先說一些 MTurk 基礎的部分,以這篇文章的內容為基礎做調整。因為是使用 AWS 的 Python SDK Boto3,所以會需要了解一些基礎的 Python 語法,另外我們會使用靜態的網頁來向 Worker 們蒐集資料,所以懂一點點 HTML, CSS 也是好的,JS 的部分我們沒用到那麼複雜所以就不必了,這篇文章會談到:
- 建立 Python 環境
- MTurk 相關前置作業
- 發布 Task (HIT)
- 取回回答結果
光寫這幾個就會佔很多篇幅了,後續再寫一篇進階一點點的操作,像是設置條件阿、approve 或 reject 回答結果等等。
一、前置作業
工具
- Python 3 - https://www.python.org/downloads/
- Pip - 裝 Python 時應該是預設會安裝的東西,用來裝各式 Python 的應用
- Virtualenv - 虛擬環境,讓我們控管個別專案要使用的特定套件或是版本,還沒裝過 virtualenv 的話可以透過下面這行指令來安裝
$ pip install virtualenv
安裝完 virtualenv 就來建立我們要存放專案的資料夾跟啟動虛擬環境
$ mkdir work
$ cd work
$ virtualenv .
$ source bin/activate
接下來在虛擬環境中安裝 AWS 的 SDK Boto3,這個 Boto3 就是我們用來和 MTurk 互動用的,除了 Boto3 ,還要裝一個叫 xmltodict 的包,基本上就是用來解析 XML 格式用的,一樣透過 pip 安裝:
$ pip install boto3
$ pip install xmltodict
帳號
這邊開始就有點麻煩了,若是要透過 Python 來使用 MTurk,我們就總共需要兩個帳號,一個叫做 MTurk Requester Account,另一個則是 AWS 的帳號。Requester 帳號讓我們在 MTurk 上以一個發布工作者的帳號發布工作,對應的有一個 Worker 帳號,是以回答工作者的帳號來進行問題的回答賺取費用。AWS 帳號則是確保我們在和 AWS API 互動有安全認證,以下依序為步驟:
- 建立 AWS 帳號: aws.amazon.com
- 建立 MTurk Requester 帳號:requester.mturk.com
- 在 MTurk 頁面上選擇 「Developer」的頁面(https://requester.mturk.com/developer),然後照頁面上的步驟 2 把 AWS 帳號和 MTurk 帳號連結起來
- MTurk 還有一個沙盒(Sandbox)模式,能夠讓我們來測試一下我們要發布的東西或者一些功能等等,在這邊發布工作什麼的都是不會收費的。要用沙盒模式的話,還要再申請一個沙盒模式的帳號(requestersandbox.mturk.com.),然後也要再跟 AWS 帳號連結。
幫 MTurk 設定 IAM 使用者
在呼叫 API 的時候,需要進行一些安全的認證,官方是建議在 AWS 上撿立一個叫做「IAM」的使用者,具體可以透過 following these steps 來照著做,有點麻煩。等弄完了記得把 access key & secret key 收好,等等會用到。
連到 MTurk Sandbox
用 MTurk 的第一件事呢!就是看一下我們的帳號有多少錢,差不多是 MTurk 的 HelloWorld 的感覺。
- 到我們前面建立的資料夾建立一個 .py 檔,內容是:
import boto3MTURK_SANDBOX = 'https://mturk-requester-sandbox.us-east-1.amazonaws.com'mturk = boto3.client('mturk',
aws_access_key_id = "PASTE_YOUR_IAM_USER_ACCESS_KEY",
aws_secret_access_key = "PASTE_YOUR_IAM_USER_SECRET_KEY",
region_name='us-east-1',
endpoint_url = MTURK_SANDBOX
)print("I have $" + mturk.get_account_balance()['AvailableBalance'] + " in my Sandbox account")
首先不管我們要執行什麼動作,都會先建立一個 MTurk ‘client’,在這邊我們會先看一下我們帳戶裡面還有多少錢,用的是 client.get_account_balance(),其他的操作可以看這裡。
其次呢我們要把 client function 的 access_key & secret_access_key 給填上,就是我們前面說的 IAM 使用者會給的資訊。這樣就可以認證我們對 MTurk 的呼叫,不過在實務上,還是把兩個 key 存放在另一個檔案上比較好,以免不小心分享出去造成資訊外漏,這邊有說明該如何處理,不過若是防護都有做好,也確定不會分享,直接內嵌在同個 file 是比較快的處理方式。
最後,使用 MTurk 的話,region_name 的部分只會是 ‘us-east-1’。
2. 將檔案命名成 create_tasks.py,不過實務上我偏好將在 Sandbox 操作的 script 做個 suffix 來區分,如 create_tasks_sandbox.py,這樣之後在測試東西的時候才不用做太多的調整。
3. 在 terminal 跑剛剛的檔案,輸入 'python create_tasks_sandbox.py',應該會看到如下的輸出
$ I have $10000.00 in my Sandbox account
在 Sandbox 中,get_account_balance() 永遠會回傳 $10000.00 (我一般帳號也有這麼多錢就好了),若是要連到一般的 MTurk,只要把 endpoint_url 拿掉就好了:
mturk = boto3.client('mturk',
aws_access_key_id = "PASTE_YOUR_IAM_USER_ACCESS_KEY",
aws_secret_access_key = "PASTE_YOUR_IAM_USER_SECRET_KEY",
region_name='us-east-1'
)
購買預付(Prepaid)HITs
用 Sandbox 的話我們不用去擔心購買預付 HITs,但是如果我們要發布工作到一般的市場上的話,就要先「預付」這個款項,假設我請一個人做一項工作要付他 5 塊美金,那麼加上要付給 Amazon 的費用(20%),在發布工作的時候我們的帳戶就得有 5+1 = 6 塊美金才行。當有人做完我們的任務,我們也同意他的回答結果,那再進行 approve() 來付款即可,若是對方回答的品質很糟,完全文不對題等等,我們就可以 reject() 他的回答結果,這樣這 6 塊就回會到我們的 MTurk 帳戶(不是我們的信用卡)。
二、建立任務(Task)
會連上 MTurk 之後呢,我們就要來發佈 task 了,在這之前需要先解釋一些名詞:
Worker: 任何以 Worker 身分登入 MTurk 的人,他們會瀏覽發佈在平台上的 task,然後決定要做哪些 task 來賺取報酬。
HIT: 「Human Intelligence Task」的簡稱,在 MTurk 中用來計算我們的工作需要多少分量的單位。例如我們手上有 100 張照片,我們可以把他切成 100 個 HIT,這樣等於是 1 個 HIT 要標註 1 張照片,或是我們切成 10 個 HIT,每個 HIT 的工作量就是標註 10 張照片,以此類推。
Assignment: 我們可以請不同的 Worker 來完成同一個 HIT,以便蒐集比較 general、比較公正的資料。每一個 Worker 他的回答結果就稱作一個 Assignment,所以如果我們把前述的 100 章照片切成 100 個 HITs 給 2 個 Worker 進行標註,這樣我們就會有 200 個 Assignments。
這三者的概念要釐得清楚一些,因為在透過 SDK 互動的時候,要搞懂這三者的差異才能清楚知道我們正在找的是什麼東西。
定義 HIT
接下來我們來建立一個 HIT,首先我們把下列程式碼加入到“create_tasks_sandbox.py”,:
question = open(name='questions.xml',mode='r').read()new_hit = mturk.create_hit(
Title = 'Is this Tweet happy, angry, excited, scared, annoyed or upset?',
Description = 'Read this tweet and type out one word to describe the emotion of the person posting it: happy, angry, scared, annoyed or upset',
Keywords = 'text, quick, labeling',
Reward = '0.15',
MaxAssignments = 1,
LifetimeInSeconds = 172800,
AssignmentDurationInSeconds = 600,
AutoApprovalDelayInSeconds = 14400,
Question = question,
)
print("A new HIT has been created. You can preview it here:"
print("https://workersandbox.mturk.com/mturk/preview?groupId=" + new_hit['HIT']['HITGroupId'])
print("HITID = " + new_hit['HIT']['HITId'] + " (Use to Get Results)")# Remember to modify the URL above when you're publishing
# HITs to the live marketplace.
# Use: https://worker.mturk.com/mturk/preview?groupId=
Question: 首先第一行的 question 是 task 的形式,可以用 HTML, CSS, JS 等來建立,後面會有範例。
Title, Description & Keywords: 讓 Mturk 上的 Worker 知道你發布的 HIT 是在做什麼。Keywords 則是拿來改善搜尋結果用的。
Reward: 要付給 Worker 的價錢(approve 他們 Assignment 的話),不包括要給 MTurk 的抽成費用。
MaxAssignments: 這個 HIT 要給幾個 Worker 來做。
LifetimeInSeconds & AssignmentDurationInSeconds: 前者可以指定這個 HIT 可以在 marketplace 上刊登多久,後者則是 Worker 可以花多少時間來完成。通常這兩個值會設高一點,除非我們有什麼特別的考量。
AutoApprovalDelayInSeconds: 如果沒有 approve 或是 reject 這份 Assignment 的話,過多少時間他會自動 approve,預設時間是 2 天,這時間是蠻安全的,千萬不要很有自信的設置很短的時間像是半天一天之類的,假如剛好有段時間忙到沒辦法查核,又遇上不認真做答的 Worker 可是會賠錢的~
接來我們要來建立前面說到的 question 所要用的檔案,我們可以用 HTML 或 XML 檔來定義我們的 task 呈現方式。
定義 task 呈現方式
在 MTurk 中,一個 HIT 就是用 HTML 檔來呈現的,所以對 HTML, CSS, JS 比較熟的讀者就可以好好設計一下要給 Worker 做答的介面。一些基本的互動元件像是文字區域、按鈕或是 check box 等都可以加入,也可以另外使用一些外部的資源像是 Bootsrap, jQuery 或 React 等,我自己是用 Bootstrap 的 google material design 版,讀者可以自己看看喜歡哪種風格,不過最重要的還是實驗/蒐集的過程還有介面佈置。
接下來用使用了 bootstrap material design 的版本來做簡單的呈現,建立一個叫 “questions.xml” 的新檔案,加入以下內容:
<HTMLQuestion xmlns="http://mechanicalturk.amazonaws.com/AWSMechanicalTurkDataSchemas/2011-11-11/HTMLQuestion.xsd"><HTMLContent><![CDATA[<!-- YOUR HTML BEGINS --><!DOCTYPE html><html>
<head>
<meta http-equiv='Content-Type' content='text/html; charset=UTF-8'/>
<script type='text/javascript' src='<https://s3.amazonaws.com/mturk-public/externalHIT_v1.js>'></script>
<script src="<https://sdk.amazonaws.com/js/aws-sdk-2.142.0.min.js>"></script>
<link rel="stylesheet" href="<https://stackpath.bootstrapcdn.com/bootstrap/4.2.1/css/bootstrap.min.css>" integrity="sha384-GJzZqFGwb1QTTN6wy59ffF1BuGJpLSa9DkKMp0DgiMDm4iYMj70gZWKYbI706tWS" crossorigin="anonymous">
<link rel="stylesheet" href="<https://www.w3schools.com/w3css/4/w3.css>">
<link rel="stylesheet" href="<https://unpkg.com/bootstrap-material-design@4.1.1/dist/css/bootstrap-material-design.min.css>" integrity="sha384-wXznGJNEXNG1NFsbm0ugrLFMQPWswR3lds2VeinahP8N0zJw9VWSopbjv2x7WCvX" crossorigin="anonymous">
</head>
<body>
<section class="container mb-3 mt-3">
<fieldset class="shadow bg-white mb-3">
<div class="w3-container">
<h2 class="text-info" style="font-weight: bolder">Instructions and Examples</h2>
<div id="Instructions" class="w3-container ins w3-animate-opacity" style="display:block">
<h2>Instructions</h2>
<p>Read the premise, hypothesis and realation, select a segment from premise that can determine the relation between premise and hypothesis.</p>
</div>
</div>
</fieldset>
<fieldset class="shadow bg-white mb-3">
<div id="Entailment" class="w3-container ins w3-animate-opacity">
<h2>Entailment Example</h2>
<p>Here is a pair of premise and hypothesis with entailment relation. The following is the Segment you need to respond, which we also emphasize in the premise.</p>
<ul>
<dt style="font-size: medium;">Premise</dt>
<dd style='margin-bottom: 1rem; margin-left: 0;'>Test Paragraph</dd>
<dt>Hypothesis</dt>
<dd>Test Sentence</dd>
<dt>Relation</dt>
<dd>Entailment</dd>
<dt>Segment (Your response)</dt>
<dd>Test Sentence</dd>
</ul>
</div>
</fieldset>
</section>
<!-- Questions Section -->
<section class="container">
<form name='mturk_form' method='post' id='mturk_form' action='<https://www.mturk.com/mturk/externalSubmit>'><input type='hidden' value='' name='assignmentId' id='assignmentId'/>
<div class="card shadow mb-3 border-info text-center">
<div class="card-header">Question 1</div>
<div class="card-body">
<h5 class="card-title">Premise</h5>
<p class="card-text">Test Paragraph</p>
<h5 class="card-title">Hypoythesis</h5>
<p class="card-text">Test Sentence</p>
<h5 class="card-title">Relation</h5>
<p class="card-text">Neutral</p>
<h5 class="card-title">Segment (Your response)</h5>
<div class="form-group">
<label for="question1"></label>
<textarea type='text' class="form-control" name="question1" id="question1" rows="3"></textarea>
</div>
</div>
</div>
<button type="submit" class="btn btn-info btn-raised btn-lg btn-block" id='submitButton'>Submit</button>
</form>
</section>
<script language='Javascript'>turkSetAssignmentID();</script>
</body>
</html>
<!-- YOUR HTML ENDS -->]]></HTMLContent><FrameHeight>600</FrameHeight></HTMLQuestion>
在 MTturk 我們會把 HTML 的內容用 XML 檔包一包後丟去 MTurk 的 API,剩下的 MTurk 會幫我們處理,所以在實務上需要另外寫一個 HTML 檔,之後把內容丟去 XML 上就好了。有一個比較重要的是在程式碼倒數 15 行的部分,我們的有一個 name=”question1”,這是我們之後取出 Worker 回答用來識別是來自於哪個問題的標籤,當一份 task 的問題很多時,需要仔細設計一下識別名,在後續的取出答案過程會鬆很多。
接下來我們 run 一下 create_tasks_sandbox.py 後就可以到對應的連結找到我們發布的 HIT 了,也可以直接到 MTurk sandbox 上去找。點進去我們的 HIT 之後可以看到以下畫面:
畫面上可以看到,我們會先有一段 instructions ,instructions 是 Worker 能不能清楚知道這份 task 在做什麼關鍵之一,這裡有完整的 guideline。
三、取回結果
在檔案夾中建立一個叫 “get_results.py” 的檔案,像在建立 create_tasks_sandbox.py 時差不多的操作:
import boto3mturk = boto3.client('mturk',
aws_access_key_id = "PASTE_YOUR_IAM_USER_ACCESS_KEY",
aws_secret_access_key = "PASTE_YOUR_IAM_USER_SECRET_KEY",
region_name='us-east-1',
endpoint_url = MTURK_SANDBOX
)# You will need the following library
# to help parse the XML answers supplied from MTurk
# Install it in your local environment with
# pip install xmltodict
import xmltodict# Use the hit_id previously created
hit_id = 'PASTE_IN_YOUR_HIT_ID'# We are only publishing this task to one Worker
# So we will get back an array with one item if it has been completedworker_results = mturk.list_assignments_for_hit(HITId=hit_id, AssignmentStatuses=['Submitted'])
如果有 Worker 完成這份 task 的話,就會回傳一個 python dictionary,每個 Assignment 的結構會以下面的 dict 方式來呈現:
{
'AssignmentId': 'string',
'WorkerId': 'string',
'HITId': 'string',
'AssignmentStatus': 'Submitted'|'Approved'|'Rejected',
'AutoApprovalTime': datetime(2015, 1, 1),
'AcceptTime': datetime(2015, 1, 1),
'SubmitTime': datetime(2015, 1, 1),
'ApprovalTime': datetime(2015, 1, 1),
'RejectionTime': datetime(2015, 1, 1),
'Deadline': datetime(2015, 1, 1),
'Answer': 'string',
'RequesterFeedback': 'string'
}
Worker 的結果會存放在 ["Answer"] 中,並以 XML 字串的方式來存放,MTurk 官方是推薦以一個叫 xmltodict 的 module 來取出結果(我們在前面有安裝過)。
在我們的 get_results.py 加入以下內容:
if worker_results['NumResults'] > 0:
for assignment in worker_results['Assignments']:
xml_doc = xmltodict.parse(assignment['Answer'])
print("Worker's answer was:")
if type(xml_doc['QuestionFormAnswers']['Answer']) is list:
# Multiple fields in HIT layout
for answer_field in xml_doc['QuestionFormAnswers']['Answer']:
print("For input field: " + answer_field['QuestionIdentifier'])
print("Submitted answer: " + answer_field['FreeText'])
else:
# One field found in HIT layout
print("For input field: " + xml_doc['QuestionFormAnswers']['Answer']['QuestionIdentifier'])
print("Submitted answer: " + xml_doc['QuestionFormAnswers']['Answer']['FreeText'])
else:
print("No results ready yet")
上面的程式碼可以讓我們取出 Answer 中的結果並以 python dict 存放。每個輸入區域(input fields)都會有一的對應的問題識別器(QuestionIdentifier),在我們的例子中就是剛剛的 “question1”,Worker 的輸入內容則會存放在 ["FreeText"]。如果有多個值的會就會以 array 的方式來存放。
在有了回答結果後,我們有三個選擇!一個是接受(approve)這個回答結果,一個是拒絕(reject)這個結果,或是也可以什麼都不做(過了過了設定時間後就會自動接受)。在接受之後 Worker 就會拿到我們一開始設定的 reward,這份 Assignment 的狀態也會被標註成 approved。在接受之後,如果對特定的結果很滿意之類的,也可以給特定的 Worker 一個 bonus。
如果拒絕這個 Assignment 的話,Assignment 狀態就會變成 rejected,也不會付錢給 Worker。而在拒絕的時候,需要附上拒絕的理由,Worker 也會看到被拒絕的理由(然後你就會收到一堆 email 來 argue 說他們有認真做,但事實上它們並沒有)。不過是否要 reject 這份 Assignment 也需要審慎的考量,除非情節嚴重像是回答空白答案或是一直跳針之類的才會直接拒絕。
結語
以上就是 MTurk 基礎的介紹,這次我們只做基礎的介紹,像是如何建立帳號、建立任務、呈現任務和取得回答結果。但事實上在實務上還需要許多操作才能活用 MTurk,之後會再來寫一篇比較進階一點的操作方式。
詳細的操作指令可以參考這裡。