I have been assigned one target image. I am not allowed to show you the image, but you can ask me questions about it. When ready, submit one of the images on the left as your best guess. We can only make 3 guesses. I will try to answer your questions, but I am not perfect. But I hope we can work together to find the image as quickly as possible!
30 |
31 |
32 |
33 |
52 |
53 |
54 |
55 |
60 |
61 | {% endblock %}
62 |
--------------------------------------------------------------------------------
/amt/admin.py:
--------------------------------------------------------------------------------
1 | from django.contrib import admin
2 |
3 | from .models import GameRound, ImageRanking, Feedback
4 | from import_export import resources
5 | from import_export.admin import ImportExportModelAdmin
6 |
7 |
8 | class GameRoundResource(resources.ModelResource):
9 |
10 | class Meta:
11 | model = GameRound
12 |
13 |
14 | class GameRoundAdmin(ImportExportModelAdmin):
15 | list_display = ('socket_id', 'user_picked_image', 'worker_id', 'assignment_id', 'level', 'task',
16 | 'hit_id', 'game_id', 'round_id', 'question', 'answer', 'target_image', 'created_at', 'bot',)
17 | list_filter = ('bot', 'worker_id', 'task', )
18 | search_fields = ['socket_id', 'user_picked_image', 'worker_id', 'assignment_id', 'level',
19 | 'hit_id', 'game_id', 'round_id', 'question', 'answer', 'target_image', 'created_at', 'bot', ]
20 | resource_class = GameRoundResource
21 |
22 |
23 | class ImageRankingResource(resources.ModelResource):
24 |
25 | class Meta:
26 | model = ImageRanking
27 |
28 |
29 | class ImageRankingAdmin(ImportExportModelAdmin):
30 | list_display = ('socket_id', 'final_image_list', 'worker_id', 'assignment_id', 'level',
31 | 'task', 'hit_id', 'game_id', 'target_image', 'created_at', 'bot', 'score', )
32 | list_filter = ('bot', 'worker_id', 'task', )
33 | search_fields = ['socket_id', 'final_image_list', 'worker_id', 'assignment_id',
34 | 'level', 'hit_id', 'game_id', 'target_image', 'created_at', 'bot', 'score', ]
35 | resource_class = ImageRankingResource
36 |
37 |
38 | class FeedbackResource(resources.ModelResource):
39 |
40 | class Meta:
41 | model = Feedback
42 |
43 |
44 | class FeedbackAdmin(ImportExportModelAdmin):
45 | list_display = ('hit_id', 'assignment_id', 'worker_id', 'understand_question', 'task', 'understand_image',
46 | 'fluency', 'detail', 'accurate', 'consistent', 'comments', 'level', 'game_id', 'bot',)
47 | list_filter = ('bot', 'worker_id', 'assignment_id', 'task', )
48 | resource_class = FeedbackResource
49 |
50 | admin.site.register(GameRound, GameRoundAdmin)
51 | admin.site.register(ImageRanking, ImageRankingAdmin)
52 | admin.site.register(Feedback, FeedbackAdmin)
53 |
--------------------------------------------------------------------------------
/chatbot/rl_worker.py:
--------------------------------------------------------------------------------
1 | from __future__ import absolute_import
2 |
3 | import os
4 | import sys
5 | sys.path.append('..')
6 |
7 | os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'demo.settings')
8 |
9 | import django
10 | django.setup()
11 |
12 | from django.conf import settings
13 | from amt.utils import log_to_terminal
14 |
15 | import amt.constants as constants
16 | import PyTorch
17 | import PyTorchHelpers
18 | import pika
19 | import time
20 | import yaml
21 | import json
22 | import traceback
23 |
24 | RLVisDialModel = PyTorchHelpers.load_lua_class(
25 | constants.RL_VISDIAL_LUA_PATH, 'RLConversationModel')
26 |
27 | RLVisDialATorchModel = RLVisDialModel(
28 | constants.RL_VISDIAL_CONFIG['inputJson'],
29 | constants.RL_VISDIAL_CONFIG['qBotpath'],
30 | constants.RL_VISDIAL_CONFIG['aBotpath'],
31 | constants.RL_VISDIAL_CONFIG['gpuid'],
32 | constants.RL_VISDIAL_CONFIG['backend'],
33 | constants.RL_VISDIAL_CONFIG['imfeatpath'],
34 | )
35 |
36 | connection = pika.BlockingConnection(pika.ConnectionParameters(
37 | host='localhost'))
38 |
39 | channel = connection.channel()
40 |
41 | channel.queue_declare(queue='rl_chatbot_queue', durable=True)
42 |
43 |
44 | def callback(ch, method, properties, body):
45 | try:
46 | body = yaml.safe_load(body)
47 | body['history'] = body['history'].split("||||")
48 |
49 | # Get the imageid here so that use the extracted features in lua script
50 | image_id = body['image_path'].split("/")[-1].replace(".jpg", "")
51 |
52 | result = RLVisDialATorchModel.abot(
53 | image_id,
54 | body['history'],
55 | body['input_question'])
56 |
57 | result['question'] = str(result['question'])
58 | result['answer'] = str(result['answer'])
59 | result['history'] = result['history']
60 | result['history'] = result['history'].replace("", "")
61 | result['history'] = result['history'].replace("", "")
62 |
63 | log_to_terminal(body['socketid'], {"result": json.dumps(result)})
64 | ch.basic_ack(delivery_tag=method.delivery_tag)
65 |
66 | except Exception, err:
67 | print str(traceback.print_exc())
68 |
69 | channel.basic_consume(callback,
70 | queue='rl_chatbot_queue')
71 |
72 | channel.start_consuming()
73 |
--------------------------------------------------------------------------------
/chatbot/sl_worker.py:
--------------------------------------------------------------------------------
1 | from __future__ import absolute_import
2 |
3 | import os
4 | import sys
5 | sys.path.append('..')
6 |
7 | os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'demo.settings')
8 |
9 | import django
10 | django.setup()
11 |
12 | from django.conf import settings
13 |
14 | from amt.utils import log_to_terminal
15 |
16 | import amt.constants as constants
17 | import PyTorch
18 | import PyTorchHelpers
19 | import pika
20 | import time
21 | import yaml
22 | import json
23 | import traceback
24 | import signal
25 | import requests
26 | import atexit
27 |
28 | VisDialModel = PyTorchHelpers.load_lua_class(
29 | constants.SL_VISDIAL_LUA_PATH, 'SLConversationModel')
30 |
31 | VisDialATorchModel = VisDialModel(
32 | constants.SL_VISDIAL_CONFIG['inputJson'],
33 | constants.SL_VISDIAL_CONFIG['qBotpath'],
34 | constants.SL_VISDIAL_CONFIG['aBotpath'],
35 | constants.SL_VISDIAL_CONFIG['gpuid'],
36 | constants.SL_VISDIAL_CONFIG['backend'],
37 | constants.SL_VISDIAL_CONFIG['imfeatpath'],
38 | )
39 |
40 | connection = pika.BlockingConnection(pika.ConnectionParameters(
41 | host='localhost'))
42 |
43 | channel = connection.channel()
44 |
45 | channel.queue_declare(queue='sl_chatbot_queue', durable=True)
46 |
47 |
48 | def callback(ch, method, properties, body):
49 | try:
50 | body = yaml.safe_load(body)
51 | body['history'] = body['history'].split("||||")
52 |
53 | # get the imageid here so that use the extracted features in lua script
54 | image_id = body['image_path'].split("/")[-1].replace(".jpg", "")
55 |
56 | print image_id
57 | print type(image_id)
58 |
59 | result = VisDialATorchModel.abot(
60 | image_id, body['history'], body['input_question'])
61 | result['input_image'] = body['image_path']
62 | result['question'] = str(result['question'])
63 | result['answer'] = str(result['answer'])
64 | result['history'] = result['history'].replace("", "")
65 | result['history'] = result['history'].replace("", "")
66 | # Store the result['predicted_fc7'] in the database after each round
67 | log_to_terminal(body['socketid'], {"result": json.dumps(result)})
68 | ch.basic_ack(delivery_tag=method.delivery_tag)
69 |
70 | except Exception, err:
71 | print str(traceback.print_exc())
72 |
73 | channel.basic_consume(callback,
74 | queue='sl_chatbot_queue')
75 |
76 | channel.start_consuming()
77 |
--------------------------------------------------------------------------------
/amt/constants.py:
--------------------------------------------------------------------------------
1 | from django.conf import settings
2 |
3 | import os
4 | import sys
5 | sys.path.append(os.path.join(settings.BASE_DIR, 'chatbot'))
6 |
7 | POOL_IMAGES_URL = os.path.join(settings.MEDIA_URL, 'val2014/')
8 |
9 | BOT_INTORDUCTION_MESSAGE = [
10 | "Hi, my name is Abot. I am an Artificial Intelligence." \
11 | "I have been assigned one of these images as the target image." \
12 | "I am not allowed to show you the image, but as a start," \
13 | "I will describe the image to you in a sentence." \
14 | "You can then ask me follow up questions about it. " \
15 | "When ready, submit one of the images on the left as your best guess. " \
16 | "I will try to describe the image and answer your questions, but I am not perfect." \
17 | "I make quite a few mistakes. I hope we can work together to find the image! " \
18 | "Let's do this! Note: My knowledge of English is limited." \
19 | "Sometimes if I don't know the right word, I say UNK. " \
20 | "You will win points based on how accurately you are able to guess.",
21 | ]
22 |
23 | SL_VISDIAL_CONFIG = {
24 | 'inputJson': os.path.join(settings.BASE_DIR, 'chatbot/data/chat_processed_params.json'),
25 | 'qBotpath': os.path.join(settings.BASE_DIR, 'chatbot/data/qbot_hre_qih_sl.t7'),
26 | 'aBotpath': os.path.join(settings.BASE_DIR, 'chatbot/data/abot_hre_qih_sl.t7'),
27 | 'gpuid': 0,
28 | 'backend': 'cudnn',
29 | 'imfeatpath': os.path.join(settings.BASE_DIR, 'chatbot/data/all_pools_vgg16_features.t7'),
30 | }
31 |
32 | SL_VISDIAL_LUA_PATH = "sl_evaluate.lua"
33 |
34 |
35 | RL_VISDIAL_CONFIG = {
36 | 'inputJson': os.path.join(settings.BASE_DIR, 'chatbot/data/chat_processed_params.json'),
37 | 'qBotpath': os.path.join(settings.BASE_DIR, 'chatbot/data/qbot_rl.t7'),
38 | 'aBotpath': os.path.join(settings.BASE_DIR, 'chatbot/data/abot_rl.t7'),
39 | 'gpuid': 0,
40 | 'backend': 'cudnn',
41 | 'imfeatpath': os.path.join(settings.BASE_DIR, 'chatbot/data/all_pools_vgg16_features.t7'),
42 | }
43 |
44 | RL_VISDIAL_LUA_PATH = "rl_evaluate.lua"
45 |
46 | NUMBER_OF_ROUNDS_IN_A_GAME = 9
47 |
48 | NUMBER_OF_GAMES_IN_A_HIT = 1
49 |
50 | AWS_ACCESS_KEY_ID = ""
51 |
52 | AWS_SECRET_ACCESS_KEY = ""
53 |
54 | QUALIFICATION_TYPE_ID = ""
55 |
56 | AMT_HOSTNAME = 'mechanicalturk.amazonaws.com'
57 |
58 | MAX_BONUS_IN_A_GAME = 200
59 |
60 | BONUS_DEDUCTION_FOR_EACH_CLICK = 10
61 |
62 | BONUS_FOR_CORRECT_IMAGE_AFTER_EACH_ROUND = 10
63 |
--------------------------------------------------------------------------------
/amt/utils.py:
--------------------------------------------------------------------------------
1 | from django.conf import settings
2 | from channels import Group
3 |
4 | import h5py
5 | import time
6 | import cPickle
7 | import pdb
8 |
9 | from .constants import (
10 | POOL_IMAGES_URL,
11 | AWS_ACCESS_KEY_ID,
12 | AWS_SECRET_ACCESS_KEY,
13 | QUALIFICATION_TYPE_ID,
14 | AMT_HOSTNAME,
15 | )
16 |
17 | import json
18 | import os
19 | import random
20 | import traceback
21 | import numpy as np
22 | import boto.mturk.connection
23 |
24 |
25 | def log_to_terminal(socketid, message):
26 | Group(socketid).send({"text": json.dumps(message)})
27 |
28 |
29 | def get_pool_images(pool_id=1):
30 | with open('data/pools.json', 'r') as f:
31 | pool_data = json.load(f)
32 | return pool_data[pool_id]
33 |
34 |
35 | def get_url_of_image(image_id):
36 | return POOL_IMAGES_URL + str(image_id) + ".jpg"
37 |
38 |
39 | def fc7_sort(imfeats, prev_sort, chosen_imID):
40 | target_f = imfeats[chosen_imID]
41 | dist_vec = np.zeros(len(prev_sort), dtype='float32')
42 |
43 | for i in range(len(prev_sort)):
44 | dist_vec[i] = np.linalg.norm(imfeats[prev_sort[i]] - target_f)
45 |
46 | sort_ind = np.argsort(dist_vec).tolist()
47 | new_sort = []
48 | for i in range(len(sort_ind)):
49 | new_sort.append(prev_sort[sort_ind[i]])
50 | return new_sort
51 |
52 |
53 | def create_qualifications():
54 | mtc = boto.mturk.connection.MTurkConnection(
55 | aws_access_key_id=AWS_ACCESS_KEY_ID,
56 | aws_secret_access_key=AWS_SECRET_ACCESS_KEY,
57 | host=AMT_HOSTNAME,
58 | debug=2
59 | )
60 |
61 | qualification = mtc.create_qualification_type(
62 | name='Some Qualification Name',
63 | description='Qualification to avoid bias in responses by preventing workers who have already completed a HIT from doing subsequent HITs.',
64 | status='Active',
65 | auto_granted=True,
66 | auto_granted_value=0
67 | )
68 |
69 |
70 | def set_qualification_to_worker(worker_id=None, qualification_value=0):
71 | mtc = boto.mturk.connection.MTurkConnection(
72 | aws_access_key_id=AWS_ACCESS_KEY_ID,
73 | aws_secret_access_key=AWS_SECRET_ACCESS_KEY,
74 | host=AMT_HOSTNAME,
75 | debug=2
76 | )
77 |
78 | mtc.assign_qualification(QUALIFICATION_TYPE_ID, worker_id,
79 | value=qualification_value,
80 | send_notification=False)
81 |
82 |
83 | def updated_qualification_to_worker(worker_id=None, qualification_value=1):
84 | mtc = boto.mturk.connection.MTurkConnection(
85 | aws_access_key_id=AWS_ACCESS_KEY_ID,
86 | aws_secret_access_key=AWS_SECRET_ACCESS_KEY,
87 | host=AMT_HOSTNAME,
88 | debug=2
89 | )
90 |
91 | mtc.update_qualification_score(
92 | QUALIFICATION_TYPE_ID, worker_id, qualification_value)
93 |
--------------------------------------------------------------------------------
/chatbot/optim_updates.lua:
--------------------------------------------------------------------------------
1 | --Author: Andrej Karpathy https://github.com/karpathy
2 | --Project: neuraltalk2 https://github.com/karpathy/neuraltalk2
3 | --Slightly modified by Xiao Lin for initial values of rmsprop.
4 |
5 | -- optim, simple as it should be, written from scratch. That's how I roll
6 |
7 | function sgd(x, dx, lr)
8 | x:add(-lr, dx)
9 | end
10 |
11 | function sgdm(x, dx, lr, alpha, state)
12 | -- sgd with momentum, standard update
13 | if not state.v then
14 | state.v = x.new(#x):zero()
15 | end
16 | state.v:mul(alpha)
17 | state.v:add(lr, dx)
18 | x:add(-1, state.v)
19 | end
20 |
21 | function sgdmom(x, dx, lr, alpha, state)
22 | -- sgd momentum, uses nesterov update (reference: http://cs231n.github.io/neural-networks-3/#sgd)
23 | if not state.m then
24 | state.m = x.new(#x):zero()
25 | state.tmp = x.new(#x)
26 | end
27 | state.tmp:copy(state.m)
28 | state.m:mul(alpha):add(-lr, dx)
29 | x:add(-alpha, state.tmp)
30 | x:add(1+alpha, state.m)
31 | end
32 |
33 | function adagrad(x, dx, lr, epsilon, state)
34 | if not state.m then
35 | state.m = x.new(#x):zero()
36 | state.tmp = x.new(#x)
37 | end
38 | -- calculate new mean squared values
39 | state.m:addcmul(1.0, dx, dx)
40 | -- perform update
41 | state.tmp:sqrt(state.m):add(epsilon)
42 | x:addcdiv(-lr, dx, state.tmp)
43 | end
44 |
45 | -- rmsprop implementation, simple as it should be
46 | function rmsprop(x, dx, state)
47 | local alpha = state.alpha or 0.99;
48 | local learningRate = state.learningRate or 1e-2;
49 | local epsilon = state.epsilon or 1e-8;
50 | if not state.m then
51 | state.m = x.new(#x):zero()
52 | state.tmp = x.new(#x)
53 | end
54 | -- calculate new (leaky) mean squared values
55 | state.m:mul(alpha)
56 | state.m:addcmul(1.0-alpha, dx, dx)
57 | -- perform update
58 | state.tmp:sqrt(state.m):add(epsilon)
59 | x:addcdiv(-learningRate, dx, state.tmp)
60 | end
61 |
62 | function adam(x, dx, state)
63 | local beta1 = state.beta1 or 0.9
64 | local beta2 = state.beta2 or 0.999
65 | local epsilon = state.epsilon or 1e-8
66 | local lr = state.learningRate or 1e-2;
67 |
68 | if not state.m then
69 | -- Initialization
70 | state.t = 0
71 | -- Exponential moving average of gradient values
72 | state.m = x.new(#dx):zero()
73 | -- Exponential moving average of squared gradient values
74 | state.v = x.new(#dx):zero()
75 | -- A tmp tensor to hold the sqrt(v) + epsilon
76 | state.tmp = x.new(#dx):zero()
77 | end
78 |
79 | -- Decay the first and second moment running average coefficient
80 | state.m:mul(beta1):add(1-beta1, dx)
81 | state.v:mul(beta2):addcmul(1-beta2, dx, dx)
82 | state.tmp:copy(state.v):sqrt():add(epsilon)
83 |
84 | state.t = state.t + 1
85 | local biasCorrection1 = 1 - beta1^state.t
86 | local biasCorrection2 = 1 - beta2^state.t
87 | local stepSize = lr * math.sqrt(biasCorrection2)/biasCorrection1
88 |
89 | -- perform update
90 | x:addcdiv(-stepSize, state.m, state.tmp)
91 | end
92 |
--------------------------------------------------------------------------------
/amt/templates/amt/plot.html:
--------------------------------------------------------------------------------
1 |
23 |
24 |
106 |
--------------------------------------------------------------------------------
/amt/consumers.py:
--------------------------------------------------------------------------------
1 | from django.utils import timezone
2 | from django.conf import settings
3 |
4 | from .utils import log_to_terminal, fc7_sort
5 | from .sender import chatbot
6 | import constants as constants
7 | from .models import GameRound, ImageRanking
8 |
9 | from channels import Group
10 |
11 | import json
12 | import redis
13 | import datetime
14 | import os
15 | import shutil
16 | import pdb
17 |
18 |
19 | r = redis.StrictRedis(host='localhost', port=6379, db=0)
20 |
21 |
22 | def ws_connect(message):
23 | "Method called when a user is connected through SocketIO"
24 | pass
25 |
26 |
27 | def ws_message(message):
28 | "Method called when there is message from the SocketIO client"
29 |
30 | body = json.loads(message.content['text'])
31 |
32 | if body["event"] == "ConnectionEstablished":
33 | # Event when the user is connected to the socketio client
34 | Group(body["socketid"]).add(message.reply_channel)
35 | log_to_terminal(body["socketid"], {
36 | "info": "User added to the Channel Group"})
37 |
38 | elif body["event"] == "start":
39 | # Event when the user starts to play the game
40 | current_datetime = timezone.now()
41 | r.set("start_time_{}".format(
42 | body["socketid"]),
43 | current_datetime.strftime("%I:%M%p on %B %d, %Y"))
44 |
45 | elif body["event"] == "questionSubmitted":
46 | # Event when the user submits a question to the backend
47 | body['question'] = body['question'].lower()
48 | bot = body['bot']
49 | chatbot(body['question'],
50 | body['prev_history'],
51 | os.path.join(settings.BASE_DIR, body['target_image'][1:]),
52 | body["socketid"],
53 | bot)
54 |
55 | elif body['event'] == "imageSubmitted":
56 | # Event when the user selects an image after each round of a game
57 | GameRound.objects.create(
58 | socket_id=body['socketid'],
59 | user_picked_image=body['user_picked_image'],
60 | worker_id=body['worker_id'],
61 | assignment_id=body['assignment_id'],
62 | level=body['level'],
63 | hit_id=body['hit_id'],
64 | game_id=body['game_id'],
65 | round_id=body['round_id'],
66 | question=body['question'],
67 | answer=body['answer'].replace("", "").replace("", ""),
68 | history=body['history'],
69 | target_image=body['target_image'],
70 | bot=body['bot'],
71 | task=body['task'],
72 | )
73 | log_to_terminal(body["socketid"], {"image_selection_result": True})
74 |
75 | elif body['event'] == 'finalImagesSelected':
76 | # Event when the user submit the ranking of after completing all rounds
77 | ImageRanking.objects.create(
78 | socket_id=body['socketid'],
79 | final_image_list=body['final_image_list'],
80 | worker_id=body['worker_id'],
81 | assignment_id=body['assignment_id'],
82 | level=body['level'],
83 | hit_id=body['hit_id'],
84 | game_id=body['game_id'],
85 | bot=body['bot'],
86 | target_image=body['target_image'],
87 | score=body['bonus'],
88 | task=body['task'],
89 | )
90 |
91 |
92 | def ws_disconnect(message):
93 | "Method invoked when the client disconnects the socket connection"
94 | pass
95 |
--------------------------------------------------------------------------------
/chatbot/opts.lua:
--------------------------------------------------------------------------------
1 | cmd = torch.CmdLine()
2 | cmd:text()
3 | cmd:text('Options')
4 | -- Data input settings
5 | cmd:option('-input_img_h5','data/visdial_0.5/data_img.h5','h5file path with image feature')
6 | cmd:option('-input_ques_h5','data/visdial_0.5/chat_processed_data.h5','h5file file with preprocessed questions')
7 | cmd:option('-input_json','data/visdial_0.5/chat_processed_params.json','json path with info and vocab')
8 |
9 | cmd:option('-save_path', 'models/', 'path to save the model and checkpoints')
10 | cmd:option('-model_name', 'im-hist-enc-dec-answerer', 'Name of the model to use for answering')
11 |
12 | cmd:option('-img_norm', 1, 'normalize the image feature. 1=yes, 0=no')
13 | cmd:option('-load_path_a', '', 'path to saved answerer model')
14 |
15 | -- model params
16 | cmd:option('-metaHiddenSize', 100, 'Size of the hidden layer for meta-rnn');
17 | cmd:option('-multiEmbedSize', 1024, 'Size of multimodal embedding for q+i')
18 | cmd:option('-imgEmbedSize', 300, 'Size of the multimodal embeddings');
19 | cmd:option('-imgFeatureSize', 4096, 'Size of the deep image feature');
20 | cmd:option('-embedSize', 300, 'Size of input word embeddings')
21 | cmd:option('-rnnHiddenSize', 512, 'Size of the hidden language rnn in each layer')
22 | cmd:option('-ansHiddenSize', 0, 'Size of the hidden language rnn in each layer for answers')
23 | cmd:option('-maxHistoryLen', 60, 'Maximum history to consider when using appended qa pairs');
24 | cmd:option('-numLayers', 2, 'number of the rnn layer')
25 | cmd:option('-languageModel', 'lstm', 'rnn to use for language model, lstm | gru')
26 | cmd:option('-bidirectional', 0, 'Bi-directional language model')
27 | cmd:option('-metric', 'llh', 'Metric to use for retrieval, llh | mi')
28 | cmd:option('-lambda', '1.0', 'Factor for marginalized probability for mi metric')
29 |
30 | -- optimization params
31 | cmd:option('-batchSize', 30, 'Batch size (number of threads) (Adjust base on GRAM)');
32 | cmd:option('-probSampleSize', 50, 'Number of samples for computing probability');
33 | cmd:option('-learningRate', 1e-3, 'Learning rate');
34 | cmd:option('-dropout', 0, 'Dropout for language model');
35 | cmd:option('-numEpochs', 400, 'Epochs');
36 | cmd:option('-LRateDecay', 10, 'After lr_decay epochs lr reduces to 0.1*lr');
37 | cmd:option('-lrDecayRate', 0.9997592083, 'Decay for learning rate')
38 | cmd:option('-minLRate', 5e-5, 'Minimum learning rate');
39 | cmd:option('-gpuid', 0, 'GPU id to use')
40 | cmd:option('-backend', 'cudnn', 'nn|cudnn')
41 |
42 | local opts = cmd:parse(arg);
43 |
44 | -- if save path is not given, use default..time
45 | -- get the current time
46 | local curTime = os.date('*t', os.time());
47 | -- create another folder to avoid clutter
48 | local modelPath = string.format('models/model-%d-%d-%d-%d:%d:%d-%s/',
49 | curTime.month, curTime.day, curTime.year,
50 | curTime.hour, curTime.min, curTime.sec, opts.model_name)
51 | if opts.save_path == 'models/' then opts.save_path = modelPath end;
52 | -- add useMI flag if the metric is mutual information
53 | if opts.metric == 'mi' then opts.useMI = true; end
54 | if opts.bidirectional == 0 then opts.useBi = nil; else opts.useBi = true; end
55 | -- additionally check if its imitation of discriminative model
56 | if string.match(opts.model_name, 'hist') then
57 | opts.useHistory = true;
58 | if string.match(opts.model_name, 'disc') then
59 | opts.separateCaption = true;
60 | end
61 | end
62 | if string.match(opts.model_name, 'im') then opts.useIm = true; end
63 |
64 | return opts;
65 |
--------------------------------------------------------------------------------
/chatbot/testAnswerer.lua:
--------------------------------------------------------------------------------
1 | require 'nn'
2 | require 'nngraph'
3 | require 'io'
4 | require 'rnn'
5 | utils = dofile('utils.lua');
6 |
7 | -------------------------------------------------------------------------------
8 | -- Input arguments and options
9 | -------------------------------------------------------------------------------
10 | cmd = torch.CmdLine()
11 | cmd:text()
12 | cmd:text('Options')
13 | -- Data input settings
14 | cmd:option('-input_img_h5','data/visdial_0.5/data_img.h5','h5file path with image feature')
15 | cmd:option('-input_ques_h5','data/visdial_0.5/chat_processed_data.h5','h5file file with preprocessed questions')
16 | cmd:option('-input_json','data/visdial_0.5/chat_processed_params.json','json path with info and vocab')
17 |
18 | cmd:option('-load_path', 'models/model-2-14-2017-22:43:51-im-hist-enc-dec/model_epoch_20.t7', 'path to saved model')
19 | cmd:option('-result_path', 'results', 'path to save generated results')
20 |
21 | -- optimization params
22 | cmd:option('-batchSize', 200, 'Batch size (number of threads) (Adjust base on GRAM)');
23 | cmd:option('-gpuid', 0, 'GPU id to use')
24 | cmd:option('-backend', 'cudnn', 'nn|cudnn')
25 |
26 | local opt = cmd:parse(arg);
27 | print(opt)
28 |
29 | -- seed for reproducibility
30 | torch.manualSeed(1234);
31 |
32 | -- set default tensor based on gpu usage
33 | if opt.gpuid >= 0 then
34 | require 'cutorch'
35 | require 'cunn'
36 | --if opt.backend == 'cudnn' then require 'cudnn' end
37 | torch.setdefaulttensortype('torch.CudaTensor');
38 | else
39 | torch.setdefaulttensortype('torch.DoubleTensor');
40 | end
41 |
42 | ------------------------------------------------------------------------
43 | -- Read saved model and parameters
44 | ------------------------------------------------------------------------
45 | local savedModel = torch.load(opt.load_path);
46 |
47 | -- transfer all options to model
48 | local modelParams = savedModel.modelParams;
49 | opt.img_norm = modelParams.img_norm;
50 | opt.model_name = modelParams.model_name;
51 | print(opt.model_name)
52 |
53 | -- add flags for various configurations
54 | -- additionally check if its imitation of discriminative model
55 | if string.match(opt.model_name, 'hist') then
56 | opt.useHistory = true;
57 | if string.match(opt.model_name, 'disc') then
58 | opt.separateCaption = true;
59 | end
60 | end
61 | if string.match(opt.model_name, 'im') then opt.useIm = true; end
62 | ------------------------------------------------------------------------
63 | -- Loading dataset
64 | ------------------------------------------------------------------------
65 | local dataloader = dofile('dataloader.lua')
66 | dataloader:initialize(opt, {'test'});
67 | collectgarbage();
68 |
69 | ------------------------------------------------------------------------
70 | -- Setup the model
71 | ------------------------------------------------------------------------
72 | require 'modelAnswerer'
73 | print('Using models from '..modelParams.model_name)
74 | local svqaModel = VisDialAModel(modelParams);
75 |
76 | -- copy the weights from loaded model
77 | svqaModel.wrapperW:copy(savedModel.modelW);
78 |
79 | ------------------------------------------------------------------------
80 | -- Training
81 | ------------------------------------------------------------------------
82 | -- validation accuracy
83 | print('Evaluation..')
84 | svqaModel:retrieve(dataloader, 'test');
85 | os.exit()
86 |
87 | ---[[
88 | print('Generating answers...')
89 | -- local answers = svqaModel:generateAnswers(dataloader, 'test', {sample = false});
90 | local answers = svqaModel:generateAnswersBeamSearch(dataloader, 'test', {});
91 |
92 | --save the file to json
93 | local savePath = string.format('%s/%s-results.json', opt.result_path, modelParams.model_name);
94 | utils.writeJSON(savePath, answers);
95 | print('Writing the results to '..savePath);
96 | -- --]]
97 |
98 | --svqaModel:visualizeAttention(dataloader, 'val', genParams);
99 |
--------------------------------------------------------------------------------
/amt/migrations/0002_auto_20170501_1309.py:
--------------------------------------------------------------------------------
1 | # -*- coding: utf-8 -*-
2 | # Generated by Django 1.10.1 on 2017-05-01 13:09
3 | from __future__ import unicode_literals
4 |
5 | import django.contrib.postgres.fields
6 | from django.db import migrations, models
7 | import django.utils.timezone
8 |
9 |
10 | class Migration(migrations.Migration):
11 |
12 | dependencies = [
13 | ('amt', '0001_initial'),
14 | ]
15 |
16 | operations = [
17 | migrations.CreateModel(
18 | name='ImagePool',
19 | fields=[
20 | ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
21 | ('pool_id', models.CharField(blank=True, max_length=200, null=True)),
22 | ('caption', models.CharField(blank=True, max_length=1000, null=True)),
23 | ('easy_pool', django.contrib.postgres.fields.ArrayField(base_field=models.CharField(max_length=200), blank=True, size=None)),
24 | ('medium_pool', django.contrib.postgres.fields.ArrayField(base_field=models.CharField(max_length=200), blank=True, size=None)),
25 | ('hard_pool', django.contrib.postgres.fields.ArrayField(base_field=models.CharField(max_length=200), blank=True, size=None)),
26 | ('obj', models.CharField(blank=True, max_length=200, null=True)),
27 | ('target_image', models.CharField(blank=True, max_length=200, null=True)),
28 | ('is_active', models.BooleanField(default=False)),
29 | ],
30 | ),
31 | migrations.CreateModel(
32 | name='ImageRanking',
33 | fields=[
34 | ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
35 | ('socket_id', models.CharField(blank=True, max_length=100, null=True)),
36 | ('worker_id', models.CharField(blank=True, max_length=100, null=True)),
37 | ('assignment_id', models.CharField(blank=True, max_length=100, null=True)),
38 | ('level', models.CharField(blank=True, max_length=100, null=True)),
39 | ('hit_id', models.CharField(blank=True, max_length=100, null=True)),
40 | ('game_id', models.CharField(blank=True, max_length=100, null=True)),
41 | ('target_image', models.CharField(blank=True, max_length=100, null=True)),
42 | ('final_image_list', django.contrib.postgres.fields.ArrayField(base_field=models.CharField(max_length=200), blank=True, size=None)),
43 | ('created_at', models.DateTimeField(auto_now_add=True)),
44 | ('updated_at', models.DateTimeField(auto_now=True)),
45 | ],
46 | ),
47 | migrations.RemoveField(
48 | model_name='gameround',
49 | name='fc7_sorted',
50 | ),
51 | migrations.RemoveField(
52 | model_name='gameround',
53 | name='human_sorted',
54 | ),
55 | migrations.AddField(
56 | model_name='gameround',
57 | name='created_at',
58 | field=models.DateTimeField(auto_now_add=True, default=django.utils.timezone.now),
59 | preserve_default=False,
60 | ),
61 | migrations.AddField(
62 | model_name='gameround',
63 | name='history',
64 | field=models.CharField(blank=True, max_length=10000, null=True),
65 | ),
66 | migrations.AddField(
67 | model_name='gameround',
68 | name='level',
69 | field=models.CharField(blank=True, max_length=100, null=True),
70 | ),
71 | migrations.AddField(
72 | model_name='gameround',
73 | name='socket_id',
74 | field=models.CharField(blank=True, max_length=100, null=True),
75 | ),
76 | migrations.AddField(
77 | model_name='gameround',
78 | name='updated_at',
79 | field=models.DateTimeField(auto_now=True),
80 | ),
81 | ]
82 |
--------------------------------------------------------------------------------
/demo/settings.py:
--------------------------------------------------------------------------------
1 | import os
2 | import sys
3 |
4 | import numpy as np
5 |
6 | # Build paths inside the project like this: os.path.join(BASE_DIR, ...)
7 | BASE_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
8 |
9 | # Set the path of the Lua Script for both SL and RL bots
10 | os.environ['LUA_PATH'] = os.environ['LUA_PATH'] + ":" + os.path.join(BASE_DIR, 'chatbot', 'sl_evaluate.lua')
11 | os.environ['LUA_PATH'] = os.environ['LUA_PATH'] + ":" + os.path.join(BASE_DIR, 'chatbot', 'rl_evaluate.lua')
12 |
13 | # SECURITY WARNING: keep the secret key used in production secret!
14 | SECRET_KEY = '3$zc7zn#v==*r2ukiezqv39g2im4zf!2%53f+h0rga&*=&(7l5'
15 |
16 | # SECURITY WARNING: don't run with debug turned on in production!
17 | DEBUG = True
18 |
19 | ALLOWED_HOSTS = []
20 |
21 |
22 | # Application definition
23 |
24 | INSTALLED_APPS = [
25 | 'django.contrib.admin',
26 | 'django.contrib.auth',
27 | 'django.contrib.contenttypes',
28 | 'django.contrib.sessions',
29 | 'django.contrib.messages',
30 | 'django.contrib.staticfiles',
31 | 'channels',
32 | 'amt',
33 | 'import_export',
34 | ]
35 |
36 | MIDDLEWARE = [
37 | 'django.middleware.security.SecurityMiddleware',
38 | 'django.contrib.sessions.middleware.SessionMiddleware',
39 | 'django.middleware.common.CommonMiddleware',
40 | 'django.middleware.csrf.CsrfViewMiddleware',
41 | 'django.contrib.auth.middleware.AuthenticationMiddleware',
42 | 'django.contrib.messages.middleware.MessageMiddleware',
43 | # 'django.middleware.clickjacking.XFrameOptionsMiddleware',
44 | ]
45 |
46 | ROOT_URLCONF = 'demo.urls'
47 |
48 | TEMPLATES = [
49 | {
50 | 'BACKEND': 'django.template.backends.django.DjangoTemplates',
51 | 'DIRS': [],
52 | 'APP_DIRS': True,
53 | 'OPTIONS': {
54 | 'context_processors': [
55 | 'django.template.context_processors.debug',
56 | 'django.template.context_processors.request',
57 | 'django.contrib.auth.context_processors.auth',
58 | 'django.contrib.messages.context_processors.messages',
59 | ],
60 | },
61 | },
62 | ]
63 |
64 | WSGI_APPLICATION = 'demo.wsgi.application'
65 |
66 |
67 | # Database
68 | # https://docs.djangoproject.com/en/1.10/ref/settings/#databases
69 |
70 | DATABASES = {
71 | 'default': {
72 | 'ENGINE': 'django.db.backends.sqlite3',
73 | 'NAME': 'test.db',
74 | }
75 | }
76 |
77 | # Password validation
78 | # https://docs.djangoproject.com/en/1.10/ref/settings/#auth-password-validators
79 |
80 | AUTH_PASSWORD_VALIDATORS = [
81 | {
82 | 'NAME': 'django.contrib.auth.password_validation.UserAttributeSimilarityValidator',
83 | },
84 | {
85 | 'NAME': 'django.contrib.auth.password_validation.MinimumLengthValidator',
86 | },
87 | {
88 | 'NAME': 'django.contrib.auth.password_validation.CommonPasswordValidator',
89 | },
90 | {
91 | 'NAME': 'django.contrib.auth.password_validation.NumericPasswordValidator',
92 | },
93 | ]
94 |
95 |
96 | # Internationalization
97 | # https://docs.djangoproject.com/en/1.10/topics/i18n/
98 |
99 | LANGUAGE_CODE = 'en-us'
100 |
101 | TIME_ZONE = 'UTC'
102 |
103 | USE_I18N = True
104 |
105 | USE_L10N = True
106 |
107 | USE_TZ = True
108 |
109 |
110 | # Static files (CSS, JavaScript, Images)
111 | # https://docs.djangoproject.com/en/1.10/howto/static-files/
112 |
113 | STATIC_URL = '/static/'
114 |
115 | STATIC_ROOT = os.path.join(BASE_DIR, 'static')
116 |
117 | MEDIA_ROOT = os.path.join(BASE_DIR, 'media')
118 |
119 | MEDIA_URL= "https://vision.ece.vt.edu/mscoco/images/"
120 |
121 | CHANNEL_LAYERS = {
122 | "default": {
123 | "BACKEND": "asgi_redis.RedisChannelLayer",
124 | "CONFIG": {
125 | "hosts": [("localhost", 6379)],
126 | "prefix": u'vicki_redis:',
127 | },
128 | "ROUTING": "amt.routing.channel_routing",
129 | },
130 | }
131 |
--------------------------------------------------------------------------------
/amt/static/css/scrollbar.css:
--------------------------------------------------------------------------------
1 | /*************** SCROLLBAR BASE CSS ***************/
2 |
3 | .scroll-wrapper {
4 | overflow: hidden !important;
5 | padding: 0 !important;
6 | position: relative;
7 | }
8 |
9 | .scroll-wrapper > .scroll-content {
10 | border: none !important;
11 | box-sizing: content-box !important;
12 | height: auto;
13 | left: 0;
14 | margin: 0;
15 | max-height: none;
16 | max-width: none !important;
17 | overflow: scroll !important;
18 | padding: 0;
19 | position: relative !important;
20 | top: 0;
21 | width: auto !important;
22 | }
23 |
24 | .scroll-wrapper > .scroll-content::-webkit-scrollbar {
25 | height: 0;
26 | width: 0;
27 | }
28 |
29 | .scroll-element {
30 | display: none;
31 | }
32 | .scroll-element, .scroll-element div {
33 | box-sizing: content-box;
34 | }
35 |
36 | .scroll-element.scroll-x.scroll-scrollx_visible,
37 | .scroll-element.scroll-y.scroll-scrolly_visible {
38 | display: block;
39 | }
40 |
41 | .scroll-element .scroll-bar,
42 | .scroll-element .scroll-arrow {
43 | cursor: default;
44 | }
45 |
46 | .scroll-textarea {
47 | border: 1px solid #cccccc;
48 | border-top-color: #999999;
49 | }
50 | .scroll-textarea > .scroll-content {
51 | overflow: hidden !important;
52 | }
53 | .scroll-textarea > .scroll-content > textarea {
54 | border: none !important;
55 | box-sizing: border-box;
56 | height: 100% !important;
57 | margin: 0;
58 | max-height: none !important;
59 | max-width: none !important;
60 | overflow: scroll !important;
61 | outline: none;
62 | padding: 2px;
63 | position: relative !important;
64 | top: 0;
65 | width: 100% !important;
66 | }
67 | .scroll-textarea > .scroll-content > textarea::-webkit-scrollbar {
68 | height: 0;
69 | width: 0;
70 | }
71 |
72 |
73 |
74 |
75 | /*************** SIMPLE INNER SCROLLBAR ***************/
76 |
77 | .scrollbar-inner > .scroll-element,
78 | .scrollbar-inner > .scroll-element div
79 | {
80 | border: none;
81 | margin: 0;
82 | padding: 0;
83 | position: absolute;
84 | z-index: 10;
85 | }
86 |
87 | .scrollbar-inner > .scroll-element div {
88 | display: block;
89 | height: 100%;
90 | left: 0;
91 | top: 0;
92 | width: 100%;
93 | }
94 |
95 | .scrollbar-inner > .scroll-element.scroll-x {
96 | bottom: 2px;
97 | height: 8px;
98 | left: 0;
99 | width: 100%;
100 | }
101 |
102 | .scrollbar-inner > .scroll-element.scroll-y {
103 | height: 100%;
104 | right: 2px;
105 | top: 0;
106 | width: 8px;
107 | }
108 |
109 | .scrollbar-inner > .scroll-element .scroll-element_outer {
110 | overflow: hidden;
111 | }
112 |
113 | .scrollbar-inner > .scroll-element .scroll-element_outer,
114 | .scrollbar-inner > .scroll-element .scroll-element_track,
115 | .scrollbar-inner > .scroll-element .scroll-bar {
116 | -webkit-border-radius: 8px;
117 | -moz-border-radius: 8px;
118 | border-radius: 8px;
119 | }
120 |
121 | .scrollbar-inner > .scroll-element .scroll-element_track,
122 | .scrollbar-inner > .scroll-element .scroll-bar {
123 | -ms-filter:"progid:DXImageTransform.Microsoft.Alpha(Opacity=40)";
124 | filter: alpha(opacity=40);
125 | opacity: 0.4;
126 | }
127 |
128 | .scrollbar-inner > .scroll-element .scroll-element_track { background-color: #e0e0e0; }
129 | .scrollbar-inner > .scroll-element .scroll-bar { background-color: #c2c2c2; }
130 | .scrollbar-inner > .scroll-element:hover .scroll-bar { background-color: #919191; }
131 | .scrollbar-inner > .scroll-element.scroll-draggable .scroll-bar { background-color: #919191; }
132 |
133 |
134 | /* update scrollbar offset if both scrolls are visible */
135 |
136 | .scrollbar-inner > .scroll-element.scroll-x.scroll-scrolly_visible .scroll-element_track { left: -12px; }
137 | .scrollbar-inner > .scroll-element.scroll-y.scroll-scrollx_visible .scroll-element_track { top: -12px; }
138 |
139 |
140 | .scrollbar-inner > .scroll-element.scroll-x.scroll-scrolly_visible .scroll-element_size { left: -12px; }
141 | .scrollbar-inner > .scroll-element.scroll-y.scroll-scrollx_visible .scroll-element_size { top: -12px; }
142 |
--------------------------------------------------------------------------------
/amt/models.py:
--------------------------------------------------------------------------------
1 | # from __future__ import unicode_literals
2 |
3 | from django.db import models
4 | from django.core.urlresolvers import reverse
5 | from django.contrib.postgres.fields import ArrayField
6 |
7 |
8 | class GameRound(models.Model):
9 | """
10 | Model depicts the game and each round of the Game
11 | """
12 | socket_id = models.CharField(max_length=100, blank=True, null=True)
13 | worker_id = models.CharField(max_length=100, blank=True, null=True)
14 | assignment_id = models.CharField(max_length=100, blank=True, null=True)
15 | level = models.CharField(max_length=100, blank=True, null=True)
16 | task = models.CharField(max_length=100, blank=True, null=True)
17 | hit_id = models.CharField(max_length=100, blank=True, null=True)
18 | game_id = models.CharField(max_length=100, blank=True, null=True)
19 | round_id = models.CharField(max_length=100, blank=True, null=True)
20 | question = models.CharField(max_length=100, blank=True, null=True)
21 | answer = models.CharField(max_length=100, blank=True, null=True)
22 | history = models.CharField(max_length=10000, blank=True, null=True)
23 | target_image = models.CharField(max_length=100, blank=True, null=True)
24 | bot = models.CharField(max_length=100, blank=True, null=True)
25 | user_picked_image = models.CharField(max_length=100, blank=True, null=True)
26 | created_at = models.DateTimeField(auto_now_add=True)
27 | updated_at = models.DateTimeField(auto_now=True)
28 | is_active = models.NullBooleanField(default=True, blank=True, null=True)
29 |
30 | def __unicode__(self):
31 | return "%s : %s : %s" % (self.assignment_id, self.game_id, self.round_id)
32 |
33 |
34 | class ImageRanking(models.Model):
35 | socket_id = models.CharField(max_length=100, blank=True, null=True)
36 | worker_id = models.CharField(max_length=100, blank=True, null=True)
37 | assignment_id = models.CharField(max_length=100, blank=True, null=True)
38 | level = models.CharField(max_length=100, blank=True, null=True)
39 | task = models.CharField(max_length=100, blank=True, null=True)
40 | hit_id = models.CharField(max_length=100, blank=True, null=True)
41 | game_id = models.CharField(max_length=100, blank=True, null=True)
42 | target_image = models.CharField(max_length=100, blank=True, null=True)
43 | final_image_list = ArrayField(models.CharField(max_length=200), blank=True)
44 | created_at = models.DateTimeField(auto_now_add=True)
45 | updated_at = models.DateTimeField(auto_now=True)
46 | bot = models.CharField(max_length=100, blank=True, null=True)
47 | score = models.FloatField(default=0)
48 | is_active = models.NullBooleanField(default=True, blank=True, null=True)
49 |
50 | def __unicode__(self):
51 | return "%s : %s : %s" % (self.assignment_id, self.game_id, self.level)
52 |
53 |
54 | class ImagePool(models.Model):
55 | pool_id = models.CharField(max_length=200, blank=True, null=True)
56 | caption = models.CharField(max_length=1000, blank=True, null=True)
57 | easy_pool = ArrayField(models.CharField(max_length=200), blank=True)
58 | medium_pool = ArrayField(models.CharField(max_length=200), blank=True)
59 | hard_pool = ArrayField(models.CharField(max_length=200), blank=True)
60 | obj = models.CharField(max_length=200, blank=True, null=True)
61 | target_image = models.CharField(max_length=200, blank=True, null=True)
62 | is_active = models.NullBooleanField(default=False, blank=True, null=True)
63 |
64 |
65 | class Feedback(models.Model):
66 | understand_question = models.CharField(
67 | max_length=200, blank=True, null=True)
68 | understand_image = models.CharField(max_length=200, blank=True, null=True)
69 | fluency = models.CharField(max_length=200, blank=True, null=True)
70 | detail = models.CharField(max_length=200, blank=True, null=True)
71 | accurate = models.CharField(max_length=200, blank=True, null=True)
72 | consistent = models.CharField(max_length=200, blank=True, null=True)
73 | comments = models.CharField(max_length=200, blank=True, null=True)
74 | worker_id = models.CharField(max_length=100, blank=True, null=True)
75 | assignment_id = models.CharField(max_length=100, blank=True, null=True)
76 | level = models.CharField(max_length=100, blank=True, null=True)
77 | task = models.CharField(max_length=100, blank=True, null=True)
78 | hit_id = models.CharField(max_length=100, blank=True, null=True)
79 | game_id = models.CharField(max_length=100, blank=True, null=True)
80 | created_at = models.DateTimeField(auto_now_add=True)
81 | updated_at = models.DateTimeField(auto_now=True)
82 | bot = models.CharField(max_length=100, blank=True, null=True)
83 | is_active = models.NullBooleanField(default=True, blank=True, null=True)
84 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # GuessWhich
2 |
3 | ## Introduction
4 |
5 | **Evaluating Visual Conversational Agents via Cooperative Human-AI Games**
6 | Prithvijit Chattopadhyay*, Deshraj Yadav*, Viraj Prabhu, Arjun Chandrashekharan, Abhishek Das, Stefan Lee, Dhruv Batra, Devi Parikh
7 | [HCOMP 2017][4]
8 |
9 | This repository contains code for setting up the **GuessWhich Game** along with Amazon Mechinical Turk (AMT) integration for real time data collection. The data collection settings can be changed easily by modifying certain configurations defined [here](https://github.com/VT-vision-lab/GuessWhich/blob/master/amt/constants.py).
10 |
11 | ## Abstract
12 |
13 | As AI continues to advance, human-AI teams are inevitable. However, progress in AI is routinely measured in isolation, without a human in the loop. It is important to measure how progress in AI translates to humans being able to accomplish tasks better; i.e., the performance of human-AI teams. In this work, we design a cooperative game – GuessWhich to measure human-AI team performance in the specific context of the AI being a visual conversational agent. The AI, which we call ALICE, is provided an image which is unseen by the human. The human then asks ALICE questions aboutthis secret image to identify it from a fixed pool of images.
14 |
15 | We measure performance of the human-ALICE team by the number of guesses it takes the human to correctly identify the secret image after a fixed number of dialog rounds with ALICE. We compare performance of the human-ALICE teams for two versions of ALICE. While AI literature shows that one version outperforms the other when paired with another AI, we find that this improvement in AI-AI performance does not translate to improved human-AI performance.
16 |
17 |
18 | ## Installation Instructions
19 |
20 | ### Installing the essential requirements
21 |
22 | ```shell
23 | sudo apt-get install -y git python-pip python-dev
24 | sudo apt-get install -y python-dev
25 | sudo apt-get install -y autoconf automake libtool curl make g++ unzip
26 | sudo apt-get install -y libgflags-dev libgoogle-glog-dev liblmdb-dev
27 | sudo apt-get install libprotobuf-dev libleveldb-dev libsnappy-dev libopencv-dev libhdf5-serial-dev protobuf-compiler
28 | ```
29 |
30 | ### Install Torch
31 |
32 | ```shell
33 | git clone https://github.com/torch/distro.git ~/torch --recursive
34 | cd ~/torch; bash install-deps;
35 | ./install.sh
36 | source ~/.bashrc
37 | ```
38 |
39 | ### Install PyTorch(Python Lua Wrapper)
40 |
41 | ```shell
42 | git clone https://github.com/hughperkins/pytorch.git
43 | cd pytorch
44 | source ~/torch/install/bin/torch-activate
45 | ./build.sh
46 | ```
47 |
48 | ### Install RabbitMQ and Redis Server
49 |
50 | ```shell
51 | sudo apt-get install -y redis-server rabbitmq-server
52 | sudo rabbitmq-plugins enable rabbitmq_management
53 | sudo service rabbitmq-server restart
54 | sudo service redis-server restart
55 | ```
56 |
57 | ### Lua dependencies
58 |
59 | ```shell
60 | luarocks install loadcaffe
61 | ```
62 |
63 | The below two dependencies are only required if you are going to use GPU
64 |
65 | ```shell
66 | luarocks install cudnn
67 | luarocks install cunn
68 | ```
69 |
70 | ### Cuda Installation
71 |
72 | Note: CUDA and cuDNN is only required if you are going to use GPU
73 |
74 | Download and install CUDA and cuDNN from [nvidia website](https://developer.nvidia.com/cuda-downloads)
75 |
76 | ### Install dependencies
77 |
78 | ```shell
79 | git clone https://github.com/Cloud-CV/GuessWhich.git
80 | cd GuessWhich
81 | sh download_models.sh
82 | pip install -r requirements.txt
83 | ```
84 |
85 | ### Create the database
86 |
87 | ```shell
88 | python manage.py makemigrations amt
89 | python manage.py migrate
90 | ```
91 |
92 | ### Running the RabbitMQ workers and Development Server
93 |
94 | Open 3 different terminal sessions and run the following commands:
95 |
96 | ```shell
97 | cd chatbot && python sl_worker.py
98 | cd chatbot && python rl_worker.py
99 | python manage.py runserver
100 | ```
101 |
102 | You are all set now. Visit http://127.0.0.1:8000 and you will have your demo running successfully.
103 |
104 |
105 | ## Cite this work
106 |
107 | If you find this code useful, consider citing our work:
108 |
109 | ```
110 | @inproceedings{visdial_eval,
111 | title={Evaluating Visual Conversational Agents via Cooperative Human-AI Games},
112 | author={Prithvijit Chattopadhyay and Deshraj Yadav and Viraj Prabhu and Arjun Chandrasekaran and Abhishek Das and Stefan Lee and Dhruv Batra and Devi Parikh},
113 | booktitle={Proceedings of the Fifth AAAI Conference on Human Computation and Crowdsourcing (HCOMP)},
114 | year={2017}
115 | }
116 | ```
117 |
118 | ## Contributors
119 |
120 | * [Deshraj Yadav][2] (deshraj@gatech.edu)
121 |
122 | ## License
123 |
124 | BSD
125 |
126 | ## Credits
127 |
128 | - Vicki Image: "[Robot-clip-art-book-covers-feJCV3-clipart](https://commons.wikimedia.org/wiki/File:Robot-clip-art-book-covers-feJCV3-clipart.png)" by [Wikimedia Commons](https://commons.wikimedia.org) is licensed under [CC BY-SA 4.0](https://creativecommons.org/licenses/by-sa/4.0/deed.en)
129 |
130 | [1]: https://arxiv.org/abs/1611.08669
131 | [2]: http://deshraj.github.io
132 | [4]: http://www.humancomputation.com/2017/
133 |
--------------------------------------------------------------------------------
/chatbot/im-hist-enc-dec-answerer/lstm.lua:
--------------------------------------------------------------------------------
1 | -- lstm based models
2 | local lstm = {};
3 |
4 | function lstm.buildModel(params)
5 | -- return encoder, nil, decoder
6 | return lstm:EncoderNet(params), lstm:DecoderNet(params);
7 | end
8 |
9 | function lstm.EncoderNet(self, params)
10 | local dropout = params.dropout or 0.2;
11 | -- Use `nngraph`
12 | nn.FastLSTM.usenngraph = true;
13 |
14 | -- encoder network
15 | local enc = nn.Sequential();
16 |
17 | -- create the two branches
18 | local concat = nn.ConcatTable();
19 |
20 | -- word branch, along with embedding layer
21 | self.wordEmbed = nn.LookupTableMaskZero(params.vocabSize, params.embedSize);
22 | local wordBranch = nn.Sequential():add(nn.SelectTable(1)):add(self.wordEmbed);
23 |
24 | -- language model
25 | enc.rnnLayers = {};
26 | for layer = 1, params.numLayers do
27 | local inputSize = (layer==1) and (params.embedSize)
28 | or params.rnnHiddenSize;
29 | enc.rnnLayers[layer] = nn.SeqLSTM(inputSize, params.rnnHiddenSize);
30 | enc.rnnLayers[layer]:maskZero();
31 |
32 | wordBranch:add(enc.rnnLayers[layer]);
33 | end
34 | wordBranch:add(nn.Select(1, -1));
35 |
36 | -- make clones for embed layer
37 | local qEmbedNet = self.wordEmbed:clone('weight', 'bias', 'gradWeight', 'gradBias');
38 | local hEmbedNet = self.wordEmbed:clone('weight', 'bias', 'gradWeight', 'gradBias');
39 |
40 | -- create two branches
41 | local histBranch = nn.Sequential()
42 | :add(nn.SelectTable(2))
43 | :add(hEmbedNet);
44 | enc.histLayers = {};
45 | -- number of layers to read the history
46 | for layer = 1, params.numLayers do
47 | local inputSize = (layer == 1) and params.embedSize
48 | or params.rnnHiddenSize;
49 | enc.histLayers[layer] = nn.SeqLSTM(inputSize, params.rnnHiddenSize);
50 | enc.histLayers[layer]:maskZero();
51 |
52 | histBranch:add(enc.histLayers[layer]);
53 | end
54 | histBranch:add(nn.Select(1, -1));
55 |
56 | -- select words and image only
57 | local imageBranch = nn.Sequential()
58 | :add(nn.SelectTable(3))
59 | :add(nn.Dropout(0.5))
60 | :add(nn.Linear(params.imgFeatureSize, params.imgEmbedSize))
61 |
62 | -- add concatTable and join
63 | concat:add(wordBranch)
64 | concat:add(histBranch)
65 | concat:add(imageBranch)
66 | enc:add(concat);
67 |
68 | -- another concat table
69 | local concat2 = nn.ConcatTable();
70 |
71 | enc:add(nn.JoinTable(1, 1))
72 | -- change the view of the data
73 | -- always split it back wrt batch size and then do transpose
74 | enc:add(nn.View(-1, params.maxQuesCount, 2*params.rnnHiddenSize + params.imgEmbedSize));
75 | enc:add(nn.Transpose({1, 2}));
76 | enc:add(nn.View(params.maxQuesCount, -1, 2*params.rnnHiddenSize + params.imgEmbedSize))
77 | enc:add(nn.SeqLSTM(2*params.rnnHiddenSize + params.imgEmbedSize, params.rnnHiddenSize))
78 | enc:add(nn.Transpose({1, 2}));
79 | enc:add(nn.View(-1, params.rnnHiddenSize))
80 |
81 | return enc;
82 | end
83 |
84 | function lstm.DecoderNet(self, params)
85 | local dropout = params.dropout or 0.2;
86 | -- Use `nngraph`
87 | nn.FastLSTM.usenngraph = true;
88 |
89 | -- decoder network
90 | local dec = nn.Sequential();
91 | -- use the same embedding for both encoder and decoder lstm
92 | local embedNet = self.wordEmbed:clone('weight', 'bias', 'gradWeight', 'gradBias');
93 | dec:add(embedNet);
94 |
95 | dec.rnnLayers = {};
96 | -- check if decoder has different hidden size
97 | local hiddenSize = (params.ansHiddenSize ~= 0) and params.ansHiddenSize
98 | or params.rnnHiddenSize;
99 | for layer = 1, params.numLayers do
100 | local inputSize = (layer == 1) and params.embedSize or hiddenSize;
101 | dec.rnnLayers[layer] = nn.SeqLSTM(inputSize, hiddenSize);
102 | dec.rnnLayers[layer]:maskZero();
103 |
104 | dec:add(dec.rnnLayers[layer]);
105 | end
106 | dec:add(nn.Sequencer(nn.MaskZero(
107 | nn.Linear(hiddenSize, params.vocabSize), 1)))
108 | dec:add(nn.Sequencer(nn.MaskZero(nn.LogSoftMax(), 1)))
109 |
110 | return dec;
111 | end
112 | -------------------------------------------------------------------------------
113 | -- transfer the hidden state from encoder to decoder
114 | function lstm.forwardConnect(encOut, enc, dec, seqLen)
115 | for ii = 1, #enc.rnnLayers do
116 | dec.rnnLayers[ii].userPrevOutput = enc.rnnLayers[ii].output[seqLen];
117 | dec.rnnLayers[ii].userPrevCell = enc.rnnLayers[ii].cell[seqLen];
118 | end
119 |
120 | -- last layer gets output gradients
121 | dec.rnnLayers[#enc.rnnLayers].userPrevOutput = encOut;
122 | end
123 |
124 | -- transfer gradients from decoder to encoder
125 | function lstm.backwardConnect(enc, dec)
126 | -- borrow gradients from decoder
127 | for ii = 1, #dec.rnnLayers do
128 | enc.rnnLayers[ii].userNextGradCell = dec.rnnLayers[ii].userGradPrevCell;
129 | enc.rnnLayers[ii].gradPrevOutput = dec.rnnLayers[ii].userGradPrevOutput;
130 | end
131 |
132 | -- return the gradients for the last layer
133 | return dec.rnnLayers[#enc.rnnLayers].userGradPrevOutput;
134 | end
135 | return lstm;
136 |
--------------------------------------------------------------------------------
/amt/migrations/0001_initial.py:
--------------------------------------------------------------------------------
1 | # -*- coding: utf-8 -*-
2 | # Generated by Django 1.10.1 on 2017-05-03 08:27
3 | from __future__ import unicode_literals
4 |
5 | import django.contrib.postgres.fields
6 | from django.db import migrations, models
7 |
8 |
9 | class Migration(migrations.Migration):
10 |
11 | initial = True
12 |
13 | dependencies = [
14 | ]
15 |
16 | operations = [
17 | migrations.CreateModel(
18 | name='Feedback',
19 | fields=[
20 | ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
21 | ('understand_question', models.CharField(blank=True, max_length=200, null=True)),
22 | ('understand_image', models.CharField(blank=True, max_length=200, null=True)),
23 | ('fluency', models.CharField(blank=True, max_length=200, null=True)),
24 | ('detail', models.CharField(blank=True, max_length=200, null=True)),
25 | ('accurate', models.CharField(blank=True, max_length=200, null=True)),
26 | ('consistent', models.CharField(blank=True, max_length=200, null=True)),
27 | ('comments', models.CharField(blank=True, max_length=200, null=True)),
28 | ('worker_id', models.CharField(blank=True, max_length=100, null=True)),
29 | ('assignment_id', models.CharField(blank=True, max_length=100, null=True)),
30 | ('level', models.CharField(blank=True, max_length=100, null=True)),
31 | ('hit_id', models.CharField(blank=True, max_length=100, null=True)),
32 | ('game_id', models.CharField(blank=True, max_length=100, null=True)),
33 | ('created_at', models.DateTimeField(auto_now_add=True)),
34 | ('updated_at', models.DateTimeField(auto_now=True)),
35 | ('bot', models.CharField(blank=True, max_length=100, null=True)),
36 | ('is_active', models.NullBooleanField(default=True)),
37 | ],
38 | ),
39 | migrations.CreateModel(
40 | name='GameRound',
41 | fields=[
42 | ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
43 | ('socket_id', models.CharField(blank=True, max_length=100, null=True)),
44 | ('worker_id', models.CharField(blank=True, max_length=100, null=True)),
45 | ('assignment_id', models.CharField(blank=True, max_length=100, null=True)),
46 | ('level', models.CharField(blank=True, max_length=100, null=True)),
47 | ('hit_id', models.CharField(blank=True, max_length=100, null=True)),
48 | ('game_id', models.CharField(blank=True, max_length=100, null=True)),
49 | ('round_id', models.CharField(blank=True, max_length=100, null=True)),
50 | ('question', models.CharField(blank=True, max_length=100, null=True)),
51 | ('answer', models.CharField(blank=True, max_length=100, null=True)),
52 | ('history', models.CharField(blank=True, max_length=10000, null=True)),
53 | ('target_image', models.CharField(blank=True, max_length=100, null=True)),
54 | ('bot', models.CharField(blank=True, max_length=100, null=True)),
55 | ('user_picked_image', models.CharField(blank=True, max_length=100, null=True)),
56 | ('created_at', models.DateTimeField(auto_now_add=True)),
57 | ('updated_at', models.DateTimeField(auto_now=True)),
58 | ('is_active', models.NullBooleanField(default=True)),
59 | ],
60 | ),
61 | migrations.CreateModel(
62 | name='ImagePool',
63 | fields=[
64 | ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
65 | ('pool_id', models.CharField(blank=True, max_length=200, null=True)),
66 | ('caption', models.CharField(blank=True, max_length=1000, null=True)),
67 | ('easy_pool', django.contrib.postgres.fields.ArrayField(base_field=models.CharField(max_length=200), blank=True, size=None)),
68 | ('medium_pool', django.contrib.postgres.fields.ArrayField(base_field=models.CharField(max_length=200), blank=True, size=None)),
69 | ('hard_pool', django.contrib.postgres.fields.ArrayField(base_field=models.CharField(max_length=200), blank=True, size=None)),
70 | ('obj', models.CharField(blank=True, max_length=200, null=True)),
71 | ('target_image', models.CharField(blank=True, max_length=200, null=True)),
72 | ('is_active', models.NullBooleanField(default=False)),
73 | ],
74 | ),
75 | migrations.CreateModel(
76 | name='ImageRanking',
77 | fields=[
78 | ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
79 | ('socket_id', models.CharField(blank=True, max_length=100, null=True)),
80 | ('worker_id', models.CharField(blank=True, max_length=100, null=True)),
81 | ('assignment_id', models.CharField(blank=True, max_length=100, null=True)),
82 | ('level', models.CharField(blank=True, max_length=100, null=True)),
83 | ('hit_id', models.CharField(blank=True, max_length=100, null=True)),
84 | ('game_id', models.CharField(blank=True, max_length=100, null=True)),
85 | ('target_image', models.CharField(blank=True, max_length=100, null=True)),
86 | ('final_image_list', django.contrib.postgres.fields.ArrayField(base_field=models.CharField(max_length=200), blank=True, size=None)),
87 | ('created_at', models.DateTimeField(auto_now_add=True)),
88 | ('updated_at', models.DateTimeField(auto_now=True)),
89 | ('bot', models.CharField(blank=True, max_length=100, null=True)),
90 | ('score', models.FloatField(default=0)),
91 | ('is_active', models.NullBooleanField(default=True)),
92 | ],
93 | ),
94 | ]
95 |
--------------------------------------------------------------------------------
/chatbot/sl_evaluate.lua:
--------------------------------------------------------------------------------
1 | --[[
2 | History will not have , only
3 | ]]
4 | require 'nn'
5 | require 'nngraph'
6 | require 'cjson'
7 | require 'rnn'
8 | require 'modelAnswerer'
9 | utils = dofile('utils.lua')
10 |
11 | local TorchModel = torch.class('SLConversationModel')
12 |
13 | function TorchModel:__init(inputJson, qBotpath, aBotpath, gpuid, backend, imfeatpath)
14 | -- Load the image features
15 | print(imfeatpath)
16 | self.imfeats = torch.load(imfeatpath)
17 | print(self.imfeats)
18 | print(#self.imfeats)
19 |
20 | -- Model paths
21 | self.qBotpath = qBotpath
22 | self.aBotpath = aBotpath
23 | self.gpuid = gpuid
24 | self.backend = backend
25 |
26 | -- Create options table to initialize dataloader
27 | self.opt = {}
28 | self.opt['input_json'] = inputJson
29 | self.dataloader = dofile('dataloader.lua')
30 | self.dataloader:initialize(self.opt)
31 |
32 | -- Initial seeds
33 | torch.manualSeed(1234)
34 | if self.gpuid >= 0 then
35 | require 'cutorch'
36 | require 'cunn'
37 | if self.backend == 'cudnn' then require 'cudnn' end
38 | cutorch.setDevice(1)
39 | cutorch.manualSeed(1234)
40 | torch.setdefaulttensortype('torch.CudaTensor')
41 | else
42 | torch.setdefaulttensortype('torch.DoubleTensor')
43 | end
44 |
45 | -- Load Questioner and Answerer model
46 | self.questionerModel = torch.load(qBotpath)
47 | self.answererModel = torch.load(aBotpath)
48 |
49 | -- transfer all options to model
50 | self.questionerModelParams = self.questionerModel.modelParams
51 | self.answererModelParams = self.answererModel.modelParams
52 |
53 | -- changing savepath in checkpoints
54 | self.questionerModelParams['model_name'] = 'im-hist-enc-dec-questioner'
55 | self.answererModelParams['model_name'] = 'im-hist-enc-dec-answerer'
56 |
57 | -- Print Questioner and Answerer
58 | print('Questioner', self.questionerModelParams.model_name)
59 | print('Answerer', self.answererModelParams.model_name)
60 |
61 | -- Add flags for various configurations
62 | if string.match(self.questionerModelParams.model_name, 'hist') then self.questionerModelParams.useHistory = true; end
63 | if string.match(self.answererModelParams.model_name, 'hist') then self.answererModelParams.useHistory = true; end
64 | if string.match(self.answererModelParams.model_name, 'im') then self.answererModelParams.useIm = true; end
65 |
66 | -- Setup both Qbot and Abot
67 | print('Using models from'.. self.questionerModelParams.model_name)
68 | print('Using models from'.. self.answererModelParams.model_name)
69 | self.qModel = VisDialQModel(self.questionerModelParams)
70 | self.aModel = VisDialAModel(self.answererModelParams)
71 |
72 | -- copy weights from loaded model
73 | self.qModel.wrapperW:copy(self.questionerModel.modelW)
74 | self.aModel.wrapperW:copy(self.answererModel.modelW)
75 |
76 | -- set models to evaluate mode
77 | self.qModel.wrapper:evaluate()
78 | self.aModel.wrapper:evaluate()
79 | end
80 |
81 | --[[
82 | ABot method implementation is exactly similar to the Visual Dialog Model
83 | Need to clarify the left/right alignment of questions/etc
84 | ]]
85 |
86 | function TorchModel:abot(imgId, history, question)
87 | -- Get image-feature
88 | local imgFeat = self.imfeats[imgId]
89 | imgFeat = torch.repeatTensor(imgFeat, 10, 1)
90 | -- Concatenate history
91 | local history_concat = ''
92 | for i=1, #history do
93 | history_concat = history_concat .. history[i] .. ' |||| '
94 | end
95 | -- -- Remove from history
96 | -- history_concat = history_concat:gsub('','')
97 | -- if history_concat ~= '' then
98 | -- history_concat = history_concat .. ' '
99 | -- end
100 | -- get pre-processed QA+Hist
101 | local cmd = 'python prepro_ques.py -question "' .. question .. '" -history "' .. history_concat .. '"'
102 | os.execute(cmd)
103 | local file = io.open('ques_feat.json', 'r')
104 | if file then
105 | json_f = file:read('*a')
106 | qh_feats = cjson.decode(json_f)
107 | file:close()
108 | end
109 | -- Get question vector
110 | local ques_vector = utils.wordsToId(qh_feats.question, self.dataloader.word2ind, 20)
111 | -- Get history Tensor and hist_len vector
112 | local hist_tensor = torch.LongTensor(10, 40):zero()
113 | local hist_len = torch.zeros(10)
114 | for i=1, #qh_feats.history do
115 | hist_tensor[i] = utils.wordsToId(qh_feats.history[i], self.dataloader.word2ind, 40)
116 | hist_len[i] = hist_tensor[i][hist_tensor[i]:ne(0)]:size(1)
117 | end
118 | -- Get question Tensor
119 | local ques_tensor = torch.LongTensor(10, 20):zero()
120 | local ques_len = torch.zeros(10)
121 | for i=1, #qh_feats.questions do
122 | ques_tensor[i] = utils.wordsToId(qh_feats.questions[i], self.dataloader.word2ind, 20)
123 | ques_len[i] = ques_tensor[i][ques_tensor[i]:ne(0)]:size(1)
124 | end
125 | -- Parameter for generating answers
126 | local iter = #qh_feats.questions + 1
127 | ques_tensor[iter] = ques_vector
128 | -- Right align the questions
129 | -- ques_tensor = utils.rightAlign(ques_tensor, ques_len)
130 | -- Right align the history
131 | -- hist_tensor = utils.rightAlign(hist_tensor, hist_len)
132 | -- Transpose the question and history
133 | ques_tensor = ques_tensor:t()
134 | hist_tensor = hist_tensor:t()
135 | -- Shift to GPU
136 | if self.gpuid >= 0 then
137 | ques_tensor = ques_tensor:cuda()
138 | hist_tensor = hist_tensor:cuda()
139 | imgFeat = imgFeat:cuda()
140 | end
141 | -- Generate answer; returns a table :-> {ansWords, aLen, ansText}
142 | local ans_struct = self.aModel:generateSingleAnswer(self.dataloader, {hist_tensor, imgFeat, ques_tensor}, {beamSize = 5}, iter)
143 | -- Use answer-text to concatenate things to show at subject's end
144 | local answer = ans_struct[3]
145 | local result = {}
146 | result['answer'] = answer
147 | result['question'] = question
148 | if history_concat == '||||' then
149 | history_concat = ''
150 | end
151 | result['history'] = history_concat .. question .. ' ' .. answer
152 | result['history'] = string.gsub(result['history'], '', '')
153 | result['input_img'] = imgId
154 | return result
155 | end
--------------------------------------------------------------------------------
/chatbot/rl_evaluate.lua:
--------------------------------------------------------------------------------
1 | --[[
2 | History will not have , only
3 | ]]
4 | require 'nn'
5 | require 'nngraph'
6 | require 'cjson'
7 | require 'rnn'
8 | require 'modelAnswerer'
9 | utils = dofile('utils.lua')
10 |
11 | local RLTorchModel = torch.class('RLConversationModel')
12 |
13 | function RLTorchModel:__init(inputJson, qBotpath, aBotpath, gpuid, backend, imfeatpath)
14 |
15 | -- Load the image features
16 | self.imfeats = torch.load(imfeatpath)
17 |
18 | -- Model paths
19 | self.qBotpath = qBotpath
20 | self.aBotpath = aBotpath
21 | self.gpuid = gpuid
22 | self.backend = backend
23 |
24 | -- Create options table to initialize dataloader
25 | self.opt = {}
26 | self.opt['input_json'] = inputJson
27 | self.dataloader = dofile('dataloader.lua')
28 | self.dataloader:initialize(self.opt)
29 |
30 | -- Initial seeds
31 | torch.manualSeed(1234)
32 | if self.gpuid >= 0 then
33 | require 'cutorch'
34 | require 'cunn'
35 | if self.backend == 'cudnn' then require 'cudnn' end
36 | cutorch.setDevice(1)
37 | cutorch.manualSeed(1234)
38 | torch.setdefaulttensortype('torch.CudaTensor')
39 | else
40 | torch.setdefaulttensortype('torch.DoubleTensor')
41 | end
42 |
43 | -- Load Questioner and Answerer model
44 | self.questionerModel = torch.load(qBotpath)
45 | self.answererModel = torch.load(aBotpath)
46 |
47 | -- transfer all options to model
48 | self.questionerModelParams = self.questionerModel.modelParams
49 | self.answererModelParams = self.answererModel.modelParams
50 |
51 | -- changing savepath in checkpoints
52 | self.questionerModelParams['model_name'] = 'im-hist-enc-dec-questioner'
53 | self.answererModelParams['model_name'] = 'im-hist-enc-dec-answerer'
54 |
55 | -- Print Questioner and Answerer
56 | -- print('Questioner', self.questionerModelParams.model_name)
57 | -- print('Answerer', self.answererModelParams.model_name)
58 |
59 | -- Add flags for various configurations
60 | if string.match(self.questionerModelParams.model_name, 'hist') then self.questionerModelParams.useHistory = true; end
61 | if string.match(self.answererModelParams.model_name, 'hist') then self.answererModelParams.useHistory = true; end
62 | if string.match(self.answererModelParams.model_name, 'im') then self.answererModelParams.useIm = true; end
63 |
64 | -- Setup both Qbot and Abot
65 | -- print('Using models from'.. self.questionerModelParams.model_name)
66 | -- print('Using models from'.. self.answererModelParams.model_name)
67 | self.qModel = VisDialQModel(self.questionerModelParams)
68 | self.aModel = VisDialAModel(self.answererModelParams)
69 |
70 | -- copy weights from loaded model
71 | self.qModel.wrapperW:copy(self.questionerModel.modelW)
72 | self.aModel.wrapperW:copy(self.answererModel.modelW)
73 |
74 | -- set models to evaluate mode
75 | self.qModel.wrapper:evaluate()
76 | self.aModel.wrapper:evaluate()
77 | end
78 |
79 | --[[
80 | ABot method implementation is exactly similar to the Visual Dialog Model
81 | Need to clarify the left/right alignment of questions/etc
82 | ]]
83 |
84 | function RLTorchModel:abot(imgId, history, question)
85 | -- Get image-feature
86 | print(imgId)
87 | print(history)
88 | print(question)
89 | print(self.imfeats)
90 | local imgFeat = self.imfeats[imgId]
91 | imgFeat = torch.repeatTensor(imgFeat, 10, 1)
92 | -- Concatenate history
93 | local history_concat = ''
94 | for i=1, #history do
95 | history_concat = history_concat .. history[i] .. ' |||| '
96 | end
97 | -- -- Remove from history
98 | -- history_concat = history_concat:gsub('','')
99 | -- if history_concat ~= '' then
100 | -- history_concat = history_concat .. ' '
101 | -- end
102 | -- get pre-processed QA+Hist
103 | local cmd = 'python prepro_ques.py -question "' .. question .. '" -history "' .. history_concat .. '"'
104 | os.execute(cmd)
105 | local file = io.open('ques_feat.json', 'r')
106 | if file then
107 | json_f = file:read('*a')
108 | qh_feats = cjson.decode(json_f)
109 | file:close()
110 | end
111 | -- Get question vector
112 | local ques_vector = utils.wordsToId(qh_feats.question, self.dataloader.word2ind, 20)
113 | -- Get history Tensor and hist_len vector
114 | local hist_tensor = torch.LongTensor(10, 40):zero()
115 | local hist_len = torch.zeros(10)
116 | for i=1, #qh_feats.history do
117 | hist_tensor[i] = utils.wordsToId(qh_feats.history[i], self.dataloader.word2ind, 40)
118 | hist_len[i] = hist_tensor[i][hist_tensor[i]:ne(0)]:size(1)
119 | end
120 | -- Get question Tensor
121 | local ques_tensor = torch.LongTensor(10, 20):zero()
122 | local ques_len = torch.zeros(10)
123 | for i=1, #qh_feats.questions do
124 | ques_tensor[i] = utils.wordsToId(qh_feats.questions[i], self.dataloader.word2ind, 20)
125 | ques_len[i] = ques_tensor[i][ques_tensor[i]:ne(0)]:size(1)
126 | end
127 | -- Parameter for generating answers
128 | local iter = #qh_feats.questions + 1
129 | ques_tensor[iter] = ques_vector
130 | -- Right align the questions
131 | -- ques_tensor = utils.rightAlign(ques_tensor, ques_len)
132 | -- Right align the history
133 | -- hist_tensor = utils.rightAlign(hist_tensor, hist_len)
134 | -- Transpose the question and history
135 | ques_tensor = ques_tensor:t()
136 | hist_tensor = hist_tensor:t()
137 | -- Shift to GPU
138 | if self.gpuid >= 0 then
139 | ques_tensor = ques_tensor:cuda()
140 | hist_tensor = hist_tensor:cuda()
141 | imgFeat = imgFeat:cuda()
142 | end
143 | -- Generate answer; returns a table :-> {ansWords, aLen, ansText}
144 | local ans_struct = self.aModel:generateSingleAnswer(self.dataloader, {hist_tensor, imgFeat, ques_tensor}, {beamSize = 5}, iter)
145 | -- Use answer-text to concatenate things to show at subject's end
146 | local answer = ans_struct[3]
147 | local result = {}
148 | result['answer'] = answer
149 | result['question'] = question
150 | if history_concat == '||||' then
151 | history_concat = ''
152 | end
153 | result['history'] = history_concat .. question .. ' ' .. answer
154 | result['history'] = string.gsub(result['history'], '', '')
155 | result['input_img'] = imgId
156 | return result
157 | end
--------------------------------------------------------------------------------
/amt/views.py:
--------------------------------------------------------------------------------
1 | from django.conf import settings
2 | from django.shortcuts import render
3 | from django.http import JsonResponse
4 | from django.db.models import Sum
5 |
6 | from .utils import (
7 | log_to_terminal,
8 | get_pool_images,
9 | get_url_of_image,
10 | set_qualification_to_worker
11 | )
12 |
13 | from .models import Feedback, ImageRanking
14 |
15 | import constants as constants
16 |
17 | import sys
18 | import uuid
19 | import os
20 | import traceback
21 | import random
22 | import urllib2
23 | import redis
24 | import json
25 |
26 |
27 | r = redis.StrictRedis(host='localhost', port=6379, db=0)
28 |
29 |
30 | class PoolImage:
31 | """
32 | Class to store the details related to a particular pool
33 | """
34 |
35 | def __init__(self, image_path, score, img_id, rank):
36 | self.image_path = image_path
37 | self.score = score
38 | self.img_id = img_id
39 | self.rank = rank
40 |
41 |
42 | def home(request, template_name="amt/index.html"):
43 | """
44 | Method called when the game starts
45 | """
46 | worker_id = request.GET.get('workerId', "default")
47 |
48 | if worker_id == "default":
49 | # default is used for the debug mode
50 | disabled = True
51 | if worker_id != "default":
52 | disabled = False
53 | try:
54 | # Set the qualification so that worker cannot do the HIT again
55 | set_qualification_to_worker(
56 | worker_id=worker_id, qualification_value=1)
57 | print "Success: Setting Qualification for worker ", worker_id
58 | except Exception as e:
59 | print "Error: Cannot Set Qualification for worker ", worker_id
60 |
61 | '''
62 | Possible values of level:
63 | - easy
64 | - medium
65 | - hard
66 | '''
67 | level = request.GET.get("level", "medium")
68 | hitId = request.GET.get('hitId')
69 | assignmentId = request.GET.get('assignmentId')
70 | turkSubmitTo = request.GET.get('turkSubmitTo')
71 | bot = request.GET.get('bot')
72 |
73 | socketid = uuid.uuid4()
74 |
75 | # Fetch previous games played by this user
76 | prev_games_of_this_hit = ImageRanking.objects.filter(
77 | assignment_id=assignmentId, worker_id=worker_id, hit_id=hitId, bot=bot)
78 | prev_game_ids = prev_games_of_this_hit.values_list('game_id', flat=True)
79 | prev_game_ids = [int(i) for i in prev_game_ids]
80 |
81 | try:
82 | # Compute the next GameID to show the new pool of images to play with
83 | next_game_id = max(prev_game_ids)
84 | except:
85 | # If exception, start from the very beginning i.e game_id=0
86 | next_game_id = 0
87 |
88 | if next_game_id == 10:
89 | next_game_id = 9
90 | # If this is the last game, show the modal to fill feedback after this
91 | # game
92 | show_feedback_modal = True
93 | else:
94 | show_feedback_modal = False
95 |
96 | # Get the pool details for the particular game_id
97 | image_pool = get_pool_images(pool_id=int(next_game_id))
98 |
99 | # Fetch the images of particular difficulty from the pool json data
100 | image_list = image_pool['pools'][level][:20]
101 | image_list = sorted(image_list)
102 | img_list = []
103 | for i in xrange(len(image_list)):
104 | img_path = constants.POOL_IMAGES_URL + str(image_list[i]) + ".jpg"
105 | img = PoolImage(img_path, 0, image_list[i], i+1)
106 | img_list.append(img)
107 | image_path_list = [constants.POOL_IMAGES_URL +
108 | str(s) + ".jpg" for s in image_list][:20]
109 | target_image = image_pool['target']
110 | target_image_url = get_url_of_image(target_image)
111 | # Assign 0 rank to all of the images
112 | scores = [0] * 20
113 | caption = image_pool['gen_caption']
114 |
115 | r.set("target_{}".format(str(socketid)), target_image)
116 |
117 | intro_message = random.choice(constants.BOT_INTORDUCTION_MESSAGE)
118 |
119 | # Compute the comulative bonus for previous games that he has played before
120 | total_bonus_so_far = ImageRanking.objects.filter(
121 | assignment_id=assignmentId, worker_id=worker_id, hit_id=hitId, bot=bot).aggregate(score=Sum('score'))
122 |
123 | # If this is the first game for the user, set the total bonus to 0
124 | if total_bonus_so_far['score'] is None:
125 | total_bonus_so_far = 0
126 | else:
127 | total_bonus_so_far = total_bonus_so_far['score']
128 |
129 | return render(request, template_name, {
130 | "socketid": socketid,
131 | "bot_intro_message": intro_message,
132 | "img_list": img_list,
133 | "target_image": target_image_url,
134 | "target_image_id": target_image,
135 | "scores": scores,
136 | "img_id_list": json.dumps(image_list),
137 | "caption": caption,
138 | "max_rounds": constants.NUMBER_OF_ROUNDS_IN_A_GAME,
139 | "num_of_games_in_a_hit": constants.NUMBER_OF_GAMES_IN_A_HIT,
140 | "disabled": disabled,
141 | "total_bonus_so_far": total_bonus_so_far,
142 | "max_game_bonus": constants.MAX_BONUS_IN_A_GAME,
143 | "bonus_deduction_on_each_click": constants.BONUS_DEDUCTION_FOR_EACH_CLICK,
144 | "next_game_id": next_game_id,
145 | "bonus_for_correct_image_after_each_round": constants.BONUS_FOR_CORRECT_IMAGE_AFTER_EACH_ROUND,
146 | "show_feedback_modal": show_feedback_modal,
147 | })
148 |
149 |
150 | def feedback(request):
151 | """
152 | View to collect the feedback provided by the Mechanical Turk Workers
153 | """
154 | hitId = request.POST.get('hitId')
155 | assignmentId = request.POST.get('assignmentId')
156 | workerId = request.POST.get('workerId')
157 | understand_question = request.POST.get('understand_question')
158 | understand_image = request.POST.get('understand_image')
159 | fluency = request.POST.get('fluency')
160 | detail = request.POST.get('detail')
161 | accurate = request.POST.get('accurate')
162 | consistent = request.POST.get('consistent')
163 | comments = request.POST.get('comments')
164 | game_id = request.POST.get('game_id')
165 | level = request.POST.get('level')
166 | bot = request.POST.get('bot')
167 | task = request.POST.get('task')
168 |
169 | Feedback.objects.create(
170 | hit_id=hitId,
171 | assignment_id=assignmentId,
172 | worker_id=workerId,
173 | understand_question=understand_question,
174 | understand_image=understand_image,
175 | fluency=fluency,
176 | detail=detail,
177 | accurate=accurate,
178 | consistent=consistent,
179 | comments=comments,
180 | level=level,
181 | game_id=game_id,
182 | bot=bot,
183 | task=task
184 | )
185 | return JsonResponse({'success': True})
186 |
--------------------------------------------------------------------------------
/chatbot/utils.lua:
--------------------------------------------------------------------------------
1 | -- script containing supporting code/methods
2 | local utils = {};
3 | cjson = require 'cjson'
4 |
5 | -- right align the question tokens in 3d volume
6 | function utils.rightAlign(sequences, lengths)
7 | -- clone the sequences
8 | local rAligned = sequences:clone():fill(0);
9 | local numDims = sequences:dim();
10 |
11 | if numDims == 3 then
12 | local M = sequences:size(3); -- maximum length of question
13 | local numImgs = sequences:size(1); -- number of images
14 | local maxCount = sequences:size(2); -- number of questions / image
15 |
16 | for imId = 1, numImgs do
17 | for quesId = 1, maxCount do
18 | -- do only for non zero sequence counts
19 | if lengths[imId][quesId] == 0 then
20 | break;
21 | end
22 |
23 | -- copy based on the sequence length
24 | rAligned[imId][quesId][{{M - lengths[imId][quesId] + 1, M}}] =
25 | sequences[imId][quesId][{{1, lengths[imId][quesId]}}];
26 | end
27 | end
28 | else if numDims == 2 then
29 | -- handle 2 dimensional matrices as well
30 | local M = sequences:size(2); -- maximum length of question
31 | local numImgs = sequences:size(1); -- number of images
32 |
33 | for imId = 1, numImgs do
34 | -- do only for non zero sequence counts
35 | if lengths[imId] > 0 then
36 | -- copy based on the sequence length
37 | rAligned[imId][{{M - lengths[imId] + 1, M}}] =
38 | sequences[imId][{{1, lengths[imId]}}];
39 | end
40 | end
41 | end
42 | end
43 |
44 | return rAligned;
45 | end
46 |
47 | -- translate a table of words to index tensor
48 | function utils.wordsToId(words, word2ind, max_len)
49 | local len = max_len or 15
50 | local vector = torch.LongTensor(len):zero()
51 | for i = 1, #words do
52 | if word2ind[words[i]] ~= nil then
53 | vector[len - #words + i] = word2ind[words[i]]
54 | else
55 | vector[len - #words + i] = word2ind['UNK']
56 | end
57 | end
58 | return vector
59 | end
60 |
61 | -- translate a given tensor/table to sentence
62 | function utils.idToWords(vector, ind2word)
63 | local sentence = '';
64 |
65 | local nextWord;
66 | for wordId = 1, vector:size(1) do
67 | if vector[wordId] > 0 then
68 | nextWord = ind2word[vector[wordId]];
69 | sentence = sentence..' '..nextWord;
70 | end
71 |
72 | -- stop if end of token is attained
73 | if nextWord == '' then break; end
74 | end
75 |
76 | return sentence;
77 | end
78 |
79 | -- read a json file and lua table
80 | function utils.readJSON(fileName)
81 | local file = io.open(fileName, 'r');
82 | local text = file:read();
83 | file:close();
84 |
85 | -- convert and save information
86 | return cjson.decode(text);
87 | end
88 |
89 | -- save a lua table to the json
90 | function utils.writeJSON(fileName, luaTable)
91 | -- serialize lua table
92 | local text = cjson.encode(luaTable)
93 |
94 | local file = io.open(fileName, 'w');
95 | file:write(text);
96 | file:close();
97 | end
98 |
99 | -- compute the likelihood given the gt words and predicted probabilities
100 | function utils.computeLhood(words, predProbs)
101 | -- compute the probabilities for each answer, based on its tokens
102 | -- convert to 2d matrix
103 | local predVec = predProbs:view(-1, predProbs:size(3));
104 | local indices = words:contiguous():view(-1, 1);
105 | local mask = indices:eq(0);
106 | -- assign proxy values to avoid 0 index errors
107 | indices[mask] = 1;
108 | local logProbs = predVec:gather(2, indices);
109 | -- neutralize other values
110 | logProbs[mask] = 0;
111 | logProbs = logProbs:viewAs(words);
112 | -- sum up for each sentence
113 | logProbs = logProbs:sum(1):squeeze();
114 |
115 | return logProbs;
116 | end
117 |
118 | -- process the scores and obtain the ranks
119 | -- input: scores for all options, ground truth positions
120 | function utils.computeRanks(scores, gtPos)
121 | local gtScore = scores:gather(2, gtPos);
122 | local ranks = scores:gt(gtScore:expandAs(scores));
123 | ranks = ranks:sum(2) + 1;
124 |
125 | -- convert into double
126 | return ranks:double();
127 | end
128 |
129 | -- process the ranks and print metrics
130 | function utils.processRanks(ranks)
131 | -- print the results
132 | local numQues = ranks:size(1) * ranks:size(2);
133 |
134 | local numOptions = 100;
135 |
136 | -- convert ranks to double, vector and remove zeros
137 | ranks = ranks:double():view(-1);
138 | -- non of the values should be 0, there is gt in options
139 | if torch.sum(ranks:le(0)) > 0 then
140 | numZero = torch.sum(ranks:le(0));
141 | print(string.format('Warning: some of ranks are zero : %d', numZero))
142 | ranks = ranks[ranks:gt(0)];
143 | end
144 |
145 | if torch.sum(ranks:ge(numOptions + 1)) > 0 then
146 | numGreater = torch.sum(ranks:ge(numOptions + 1));
147 | print(string.format('Warning: some of ranks >100 : %d', numGreater))
148 | ranks = ranks[ranks:le(numOptions + 1)];
149 | end
150 |
151 | ------------------------------------------------
152 | print(string.format('\tNo. questions: %d', numQues))
153 | print(string.format('\tr@1: %f', torch.sum(torch.le(ranks, 1))/numQues))
154 | print(string.format('\tr@5: %f', torch.sum(torch.le(ranks, 5))/numQues))
155 | print(string.format('\tr@10: %f', torch.sum(torch.le(ranks, 10))/numQues))
156 | print(string.format('\tmedianR: %f', torch.median(ranks:view(-1))[1]))
157 | print(string.format('\tmeanR: %f', torch.mean(ranks)))
158 | print(string.format('\tmeanRR: %f', torch.mean(ranks:cinv())))
159 | end
160 |
161 | function utils.preprocess(path, width, height)
162 | local width = width or 224
163 | local height = height or 224
164 |
165 | -- load image
166 | local orig_image = image.load(path)
167 |
168 | -- handle greyscale and rgba images
169 | if orig_image:size(1) == 1 then
170 | orig_image = orig_image:repeatTensor(3, 1, 1)
171 | elseif orig_image:size(1) == 4 then
172 | orig_image = orig_image[{{1,3},{},{}}]
173 | end
174 |
175 | -- get the dimensions of the original image
176 | local im_height = orig_image:size(2)
177 | local im_width = orig_image:size(3)
178 |
179 | -- scale and subtract mean
180 | local img = image.scale(orig_image, width, height):double()
181 | local mean_pixel = torch.DoubleTensor({103.939, 116.779, 123.68})
182 | img = img:index(1, torch.LongTensor{3, 2, 1}):mul(255.0)
183 | mean_pixel = mean_pixel:view(3, 1, 1):expandAs(img)
184 | img:add(-1, mean_pixel)
185 | return img, im_height, im_width
186 | end
187 |
188 | return utils;
189 |
--------------------------------------------------------------------------------
/amt/templates/amt/feedback.html:
--------------------------------------------------------------------------------
1 |
123 |
124 |
--------------------------------------------------------------------------------
/amt/templates/amt/base.html:
--------------------------------------------------------------------------------
1 | {% load static %}
2 |
3 |
4 |
5 | Guess Which
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
28 |
29 |
42 |
43 |
153 |
233 |
234 |
235 |
236 |
237 | {% block header%}
238 |
239 |