贪心(基于检索式问答系统)

    科技2022-07-11  116

    1.注意

    注意(attention)!开始做前必读项!

    所有的代码一定要在这个文件里面编写,不要自己创建一个新的文件对于提供的数据集,不要改存储地方,也不要修改文件名和内容确保到时候git pull之后我们可以直接运行这个 starter_code文件不要重新定义函数(如果我们已经定义好的),按照里面的思路来编写。当然,除了我们定义的部分,如有需要可以自行定义函数或者模块写完之后,重新看一下哪一部分比较慢,然后试图去优化。一个好的习惯是每写一部分就思考这部分代码的时间复杂度和空间复杂度,AI工程是的日常习惯!第一次作业很重要,一定要完成! 相信会有很多的收获!

    Part 2: 搭建一个简单的问答系统

    本次项目的目标是搭建一个基于检索式的简单的问答系统。至于什么是检索式的问答系统请参考课程直播内容/PPT介绍。

    通过此项目,你将会有机会掌握以下几个知识点:

    字符串操作 2. 文本预处理技术(词过滤,标准化) 3. 文本的表示(tf-idf, word2vec) 4. 文本相似度计算 5. 文本高效检索

    此项目需要的数据:

    dev-v2.0.json: 这个数据包含了问题和答案的pair, 但是以JSON格式存在,需要编写parser来提取出里面的问题和答案。glove.6B: 这个文件需要从网上下载,下载地址为:https://nlp.stanford.edu/projects/glove/, 请使用d=100的词向量
    检索式的问答系统

    问答系统所需要的数据已经提供,对于每一个问题都可以找得到相应的答案,所以可以理解为每一个样本数据是 <问题、答案>。 那系统的核心是当用户输入一个问题的时候,首先要找到跟这个问题最相近的已经存储在库里的问题,然后直接返回相应的答案即可。 举一个简单的例子:

    假设我们的库里面已有存在以下几个<问题,答案>: <"贪心学院主要做什么方面的业务?”, “他们主要做人工智能方面的教育”> <“国内有哪些做人工智能教育的公司?”, “贪心学院”> <“人工智能和机器学习的关系什么?”, “其实机器学习是人工智能的一个范畴,很多人工智能的应用要基于机器学习的技术”> <“人工智能最核心的语言是什么?”, ”Python“> …

    假设一个用户往系统中输入了问题 “贪心学院是做什么的?”, 那这时候系统先去匹配最相近的“已经存在库里的”问题。 那在这里很显然是 “贪心学院是做什么的”和“贪心学院主要做什么方面的业务?”是最相近的。 所以当我们定位到这个问题之后,直接返回它的答案 “他们主要做人工智能方面的教育”就可以了。 所以这里的核心问题可以归结为计算两个问句(query)之间的相似度。

    在本次项目中,你会频繁地使用到sklearn这个机器学习库。具体安装请见:http://scikit-learn.org/stable/install.html sklearn包含了各类机器学习算法和数据处理工具,包括本项目需要使用的词袋模型,均可以在sklearn工具包中找得到。 另外,本项目还需要用到分词工具jieba, 具体使用方法请见 https://github.com/fxsjy/jieba

    Part 2.1 第一部分: 读取文件,并把内容分别写到两个list里(一个list对应问题集,另一个list对应答案集)

    # 分数(5) import json import pandas as pd def read_corpus(): """ 读取给定的语料库,并把问题列表和答案列表分别写入到 qlist, alist 里面。 在此过程中,不用对字符换做任何的处理(这部分需要在 Part 2.3里处理) qlist = ["问题1", “问题2”, “问题3” ....] alist = ["答案1", "答案2", "答案3" ....] 务必要让每一个问题和答案对应起来(下标位置一致) """ path = r"E:/贪心学院/课件资料/课件资料/资料/资料/Project1-master-6969c54c279dbdb8196c1ae07e1b4a60b9c49436/Project1-master-6969c54c279dbdb8196c1ae07e1b4a60b9c49436/data/train-v2.0.json" df = pd.read_json(path) qlist = [] alist = [] for i in range(df.shape[0]): # i 样本索引 for j in range(len(df.data[i]["paragraphs"])): # j : qas 的个数 for k in range(len(df.data[i]["paragraphs"][j]["qas"])): # k : qa对的个数 qlist.append(df.data[i]["paragraphs"][j]["qas"][k]["question"]) if not df.data[i]["paragraphs"][j]["qas"][k]["answers"] : alist.append(df.data[i]["paragraphs"][j]["qas"][k]["plausible_answers"][0]["text"]) continue alist.append(df.data[i]["paragraphs"][j]["qas"][k]["answers"][0]["text"]) assert len(qlist) == len(alist)# 确保长度一样 return qlist, alist

    数据的样子:

    # qadata.data[0]

    Part 2.2 理解数据(可视化分析/统计信息)

    对数据的理解是任何AI工作的第一步,需要充分对手上的数据有个更直观的理解。

    # 分数(10) # TODO: 统计一下在qlist 总共出现了多少个单词? 总共出现了多少个不同的单词? # 这里需要做简单的分词,对于英文我们根据空格来分词即可,其他过滤暂不考虑(只需分词) # import jieba # q_token = jieba.lcut() # 英文分词不用这么复杂 qlist, alist = read_corpus() q_token = [] #list of list of word a_token = [] # list of list of word for line in qlist: q_token.append(line.split(" ")) for line in alist: a_token.append(line.split(" ")) word_total = 0 for item in q_token: word_total += len(item) word_dif = 0 for item in a_token: word_dif += len(item) print (word_total) # 1321965 print(word_dif) # 414111

    2.3 文本预处理

    这部分需要尝试做文本的处理。在这里我们面对的是英文文本,所以任何对英文适合的技术都可以考虑进来。

    # 分数(10) # TODO: 对于qlist, alist做文本预处理操作。 可以考虑以下几种操作: # 1. 停用词过滤 (去网上搜一下 "english stop words list",会出现很多包含停用词库的网页,或者直接使用*NLTK*自带的) # 2. 转换成 lower_case: 这是一个基本的操作 # 3. 去掉一些无用的符号: 比如连续的感叹号!!!, 或者一些奇怪的单词。 # 4. 去掉出现频率很低的词:比如出现次数少于10,20.... # 5. 对于数字的处理: 分词完有些单词可能就是数字比如44,415,把所有这些数字都看成是一个单词, # 这个新的单词我们可以定义为 "#number" # 6. stemming(利用porter stemming): 因为是英文,所以stemming也是可以做的工作 # 7. 其他(如果有的话) # 请注意,不一定要按照上面的顺序来处理,具体处理的顺序思考一下,然后选择一个合理的顺序 # hint: 停用词用什么数据结构来存储? 不一样的数据结构会带来完全不一样的效率! ########################## 1. 转换为小写和停用词过滤 import nltk from nltk.corpus import stopwords stopwords = stopwords.words("english") stop_qlist = [[w.lower() for w in item if w.lower() not in stopwords] for item in q_token ] stop_alist = [[w.lower() for w in item if w.lower() not in stopwords] for item in a_token ] print(len(stop_qlist), len(stop_alist)) #130319个问句, 一定要保证 q , a 个数相同 #130319 130319 ######################### 2. 去掉一些无用的符号: 比如连续的感叹号!!!, 或者一些奇怪的单词。 import re p= "[~!@#\$%\^\&\* \(\)_\+=\?/\|\}\{\n\.。\t,、\r\f\a<>,:;《》\[\]:;“”‘’'|——]+" pat = re.compile(p) qsymb = [] asymb = [] qsymb = [[re.sub(pat,"", w) for w in item if w] for item in stop_qlist] asymb = [[re.sub(pat,"", w) for w in item if w] for item in stop_alist] print(len(qsymb), len(asymb)) # 130319个 questions,130319个answers # TODO: 统计一下qlist中每个单词出现的频率,并把这些频率排一下序,然后画成plot. #比如总共出现了7个不同单词,而且每个单词出现的频率为 4, 5,10, 2, 1, 1,1 # 把频率排序之后就可以得到(从大到小) 10, 5, 4, 2, 1, 1, 1. 然后把这7个数plot即可(从大到小) # 需要使用matplotlib里的plot函数。y轴是词频 qcount = {} acount = {} for item in qsymb: for word in item: if word not in qcount: qcount[word] = 1 else: qcount[word] += 1 for item in asymb: for word in item: if word not in acount: acount[word] = 1 else: acount[word] += 1 qcount = dict(sorted(qcount.items(), key= lambda item:item[1], reverse=True)) acount = dict(sorted(acount.items(), key= lambda item:item[1], reverse=True)) print(len(qcount), len(acount)) # qlsit 中不同的单词个数49436,alist中不同的单词个数 48051 import matplotlib.pyplot as plt plt.plot(list(qcount.keys())[:15], list(qcount.values())[:15]) plt.show()

    # TODO: 从上面的图中能观察到什么样的现象? 这样的一个图的形状跟一个非常著名的函数形状很类似,能得出此定理吗? # hint: [XXX]'s law # ###### 齐夫定律 # 最简单的齐夫定律的例子:给出一组齐夫分布的频率,按照从最常见到非常见排列, #第二常见的频率是最常见频率的出现次数的1/2,第三常见的频率是最常见的频率的1/3,第n常见的频率是最常见频率出现次数的1/n。 #然而,这并不精确,因为所有的项必须出现一个整数次数,一个单词不可能出现2.5次。然而,在一个广域范围内并且做出适当的近似,许多自然现象都符合齐夫定律。 # TODO: 在qlist和alist里出现次数最多的TOP 10单词分别是什么? maxq = list(qcount.keys())[:10] maxa = list(acount.keys())[:10] print(maxq, maxa) #['many', 'year', 'first', 'name', 'used', 'type', 'what', 'new', 'people', 'city'] #['million', 'century', 'new', 'two', 'united', 'one', 'years', 'states', 'three', 'war'] ############### 3. 去掉出现频率很低的词:比如出现次数少于10,20.... qc = [[w for w in item if qcount[w] >= 2] for item in qsymb] ac = [[w for w in item if acount[w] >= 2] for item in asymb] ### 有个疑问:列表需要去重吗????? print(len(qc), len(ac)) # 130319 130319 # ################ 4. 对于数字的处理: 分词完有些单词可能就是数字比如44,415,把所有这些数字都看成是一个单词, # # 这个新的单词我们可以定义为 "#number" # pat = r"^\d+$" # qnum= [[re.sub(pat,"#number",w) for w in item] for item in qc] # anum =[[re.sub(pat,"#number",w) for w in item] for item in ac] # print(len(qnum), len(anum)) 130319 130319 ################# 5. stemming(利用porter stemming): 因为是英文,所以stemming也是可以做的工作 # from nltk.stem import PorterStemmer # stemmer = PorterStemmer() # qstem = [[stemmer.stem(w) for w in item] for item in qnum] # astem = [[stemmer.stem(w) for w in item] for item in anum] # print(len(qstem), len(astem)) 130319 130319 ################# 6. 去掉空字符和列表,空列表即空问题,不能去除空列表吧,始终保持 q,a 个数对应 qsymb = [[w for w in item if w ] for item in qc] #qsymb = [item for item in qsymb if item] asymb = [[w for w in item if w ] for item in ac] #asymb = [item for item in asymb if item] print(len(qsymb), len(asymb)) 130319 130319

    2.4 文本表示

    当我们做完关键的预处理过程之后,就需要把每一个文本转换成向量。

    # 分数(10) # TODO: 把qlist中的每一个问题字符串转换成tf-idf向量, 转换之后的结果存储在X矩阵里。 X的大小是: N* D的矩阵。 这里N是问题的个数(样本个数), # D是字典库的大小。 #*** 不能传入单词形式,否则一个单词就是一句话 from sklearn.feature_extraction.text import TfidfVectorizer vectorizer = TfidfVectorizer(norm=None, ngram_range=(1,1)) # 定义一个tf-idf的vectorizer # 将 list of list word 转化为 list of str qtext = [" ".join(item) for item in qsymb] X = vectorizer.fit_transform(qtext) # 结果存放在X矩阵 print(X.shape) # qlist的问题个数:130319, 词库大小:27080 # (130319, 27080) # TODO: 矩阵X有什么特点? 计算一下它的稀疏度 # X 是稀疏矩阵,稀疏矩阵是由大部分为零的矩阵组成的矩阵,这是和稠密矩阵有所区别的主要特点 #矩阵的稀疏性可以用一个分数来量化,即矩阵中 非零元素 的个数除以矩阵中元素的 总数 。 #sparsity = X.size / X.toarray().size print (sparsity) # 打印出稀疏度(sparsity)

    2.5 对于用户的输入问题,找到相似度最高的TOP5问题,并把5个潜在的答案做返回

    # 分数(10) from sklearn.metrics.pairwise import cosine_similarity def preprocess(s): ''' # 1. 停用词过滤 (去网上搜一下 "english stop words list",会出现很多包含停用词库的网页,或者直接使用*NLTK*自带的) # 2. 转换成 lower_case: 这是一个基本的操作 # 3. 去掉一些无用的符号: 比如连续的感叹号!!!, 或者一些奇怪的单词。 # 4. 去掉出现频率很低的词:比如出现次数少于10,20.... # 5. 对于数字的处理: 分词完有些单词可能就是数字比如44,415,把所有这些数字都看成是一个单词, # 这个新的单词我们可以定义为 "#number" # 6. stemming(利用porter stemming): 因为是英文,所以stemming也是可以做的工作 # 7. 其他(如果有的话) ''' sq = s.split(" ") # list of word sq = [w.lower() for w in sq if w.lower() not in stopwords] #停用词过滤+lower case sq = [re.sub(pat,"", w) for w in sq] # 去掉无用字符 # p = r"^\d+$" # sq = [re.sub(pat,"#number",w) for w in sq] # 纯数字的处理 sq = [w for w in sq if w] # 去掉空字符 #sq = [stemmer.stem(w) for w in sq] # 词干化 return [" ".join(sq)] def top5results(input_q): """ input_q: string 给定用户输入的问题 input_q, 返回最有可能的TOP 5问题。这里面需要做到以下几点: 1. 对于用户的输入 input_q 首先做一系列的*预处理*,然后再转换成tf-idf向量(利用上面的vectorizer) 2. 计算,跟每个库里的问题之间的相似度 3. 找出相似度最高的top5问题的答案 """ # 预处理 input_q = preprocess(input_q) # tf-idf转化 input_idf = vectorizer.transform(input_q) #print("input_idf", input_idf.shape) # X: N * V, input_idf: 1 * V # X, input_idf 都是稀疏矩阵 #Similarity = input_idf.dot(X.T) # 1 * N # 相似度(点积) Similarity = cosine_similarity(input_idf, X)[0] #print("similarity shape", Similarity.shape) top_idxs = np.argsort(Similarity)[-5:].tolist()[::-1] # top_idxs存放相似度最高的(存在qlist里的)问题的下表标 # hint: 利用 priority queue来找出 top results. 思考为什么可以这么做? #print("top_idxs", top_idxs) atext = np.array([" ".join(item) for item in asymb]) return atext[top_idxs] # 返回相似度最高的问题对应的答案,作为TOP5答案 # TODO: 编写几个测试用例,并输出结果 print (top5results("When did Beyonce start becoming popular?"))

    top5results("When did Beyonce leave Destiny's Child and become a solo singer?")

    top5results('What album made her a worldwide known artist?')

    返回的结果第一元素算是比较符和的…

    2.6 利用倒排表的优化。

    上面的算法,一个最大的缺点是每一个用户问题都需要跟库里的所有的问题都计算相似度。假设我们库里的问题非常多,这将是效率非常低的方法。 这里面一个方案是通过倒排表的方式,先从库里面找到跟当前的输入类似的问题描述。然后针对于这些candidates问题再做余弦相似度的计算。这样会节省大量的时间。

    参考:https://blog.csdn.net/weixin_46425692/article/details/108592068

    # 分数(10) # TODO: 基于倒排表的优化。在这里,我们可以定义一个类似于hash_map, 比如 inverted_index = {}, # 然后存放包含每一个关键词的文档出现在了什么位置, # 也就是,通过关键词的搜索首先来判断包含这些关键词的文档(比如出现至少一个),然后对于candidates问题做相似度比较。 ##== ======================== 1. 定一个一个简单的倒排表================================ from collections import defaultdict # 与 setdefault 区分 from functools import reduce inverted_idx = defaultdict(set) # qsymb为列表的列表 for i in range(len(qsymb)): for word in qsymb[i]: inverted_idx[word].add(i) ## ==========================2. 定义一个用于reduce的函数,求各个集合最后的交集 ============== def intersec(set1, set2): return set1 & set2 # &求交集 def union(set1, set2): return set1 | set2 # |求并集 def top5results_invidx(input_q): """ 给定用户输入的问题 input_q, 返回最有可能的TOP 5问题。这里面需要做到以下几点: 1. 利用倒排表来筛选 candidate 2. 对于用户的输入 input_q 首先做一系列的预处理,然后再转换成tf-idf向量(利用上面的vectorizer) 3. 计算跟每个库里的问题之间的相似度 4. 找出相似度最高的top5问题的答案 """ # 1.预处理input_q,返回是列表 input_q = preprocess(input_q)[0] # string # 2.分词 input_words = input_q.split(" ") #list # 3. 输入句子的inverted_index input_inverted_index = [] # 每个元素是 set for w in input_words: input_inverted_index.append(inverted_idx[w]) # 4.最后的 candidate index candidates = list(reduce(intersec, input_inverted_index)) # list if not candidates: candidates = list(reduce(union, input_inverted_index)) #print("候选索引:", candidates[:5]) # 5. 转化为 tf-idf 向量 input_idf = vectorizer.transform([input_q]) # 6 .相似度计算 sim = cosine_similarity(input_idf, X[candidates])[0] #print(sim.shape) # 7. 返回最大的 5个索引 top_idxs = np.argsort(sim)[-5:].tolist()[::-1] top_idxs = np.array(candidates)[top_idxs] #返回答案 #print("top_idxs", top_idxs) atext = np.array([" ".join(item) for item in asymb]) return atext[top_idxs] # 返回相似度最高的问题对应的答案,作为TOP5答案 # TODO: 编写几个测试用例,并输出结果 print (top5results_invidx("When did Beyonce start becoming popular?")) print (top5results_invidx("What album made her a worldwide known artist?"))

    2.7 基于词向量的文本表示

    上面所用到的方法论是基于词袋模型(bag-of-words model)。这样的方法论有两个主要的问题:1. 无法计算词语之间的相似度 2. 稀疏度很高。 在2.7里面我们 讲采用词向量作为文本的表示。词向量方面需要下载: https://nlp.stanford.edu/projects/glove/ (请下载glove.6B.zip),并使用d=100的词向量(100维)。

    # 分数(10) # TODO # ===============================1.加载glove.100词向量============================ from gensim.models import KeyedVectors from gensim.scripts.glove2word2vec import glove2word2vec # 将glove转为 word2vec 储存 path1 = r"H:/nlt数据/glove.6B/glove.6B.100d.txt" path2 = r"H:/nlt数据/glove.6B/glove2wordvec.6B.100d.txt" _ = glove2word2vec(path1, path2) # 模型 model = KeyedVectors.load_word2vec_format(path2) def sentence_vec(sent): # list of word: ''' input: list of word 将单词列表转化为句子的词向量 output: 句子的词向量,为句子单词词向量的均值, np.array, (100,) ''' emb = np.zeros((100,)) size = len(sent) for w in sent: try: emb += model[w] except: size -= 1 #print(emb.shape) ##(100,) if size != 0: return emb / size else: return emb / 0.00001 # 读取每一个单词的嵌入。这个是 D*H的矩阵,这里的D是词典库的大小, H是词向量的大小。 这里面我们给定的每个单词的词向量,那句子向量怎么表达? # 其中,最简单的方式 句子向量 = 词向量的平均(出现在问句里的), 如果给定的词没有出现在词典库里,则忽略掉这个词。 def top5results_emb(input_q): """ 给定用户输入的问题 input_q, 返回最有可能的TOP 5问题。这里面需要做到以下几点: 1. 利用倒排表来筛选 candidate 2. 对于用户的输入 input_q,转换成句子向量 3. 计算跟每个库里的问题之间的相似度 4. 找出相似度最高的top5问题的答案 """ # =================================2. N 个问题 的词向量 ================ X = np.zeros((len(qsymb), 100)) for k, item in enumerate(qsymb): X[k] = sentence_vec(item) # 1.预处理input_q,返回是列表 input_q = preprocess(input_q)[0] # string # 2.分词 input_words = input_q.split(" ") #list # 3. 输入句子的inverted_index input_inverted_index = [] # 每个元素是 set for w in input_words: input_inverted_index.append(inverted_idx[w]) # 4.最后的 candidate index candidates = list(reduce(intersec, input_inverted_index)) # list if not candidates: candidates = list(reduce(union, input_inverted_index)) # candidates仍然可能为空啊 #print("候选索引:", candidates[:5]) # 5. 转化为glove向量 input_vec = sentence_vec(input_words ) # (100, ) # 6 .相似度计算 sim = cosine_similarity(input_vec.reshape(1,100), X[candidates])[0] print("sim.shape: ", sim.shape) # 7. 返回最大的5个索引 top_idxs = np.argsort(sim)[-5:].tolist()[::-1] # 从大到小排 top_idxs = np.array(candidates)[top_idxs] #返回答案 print("top_idxs", top_idxs) atext = np.array([" ".join(item) for item in asymb]) return atext[top_idxs] # 返回相似度最高的问题对应的答案,作为TOP5答案 # TODO: 编写几个测试用例,并输出结果 print (top5results_emb("When did Beyonce start becoming popular?"))

    可以看看原来文本问题和对应的答案: ‘paragraphs’: [{‘qas’: [{‘question’: ‘When did Beyonce start becoming popular?’, ‘id’: ‘56be85543aeaaa14008c9063’, ‘answers’: [{‘text’: ‘in the late 1990s’, ‘answer_start’: 269}], ‘is_impossible’: False}, {‘question’: ‘What areas did Beyonce compete in when she was growing up?’, ‘id’: ‘56be85543aeaaa14008c9065’, ‘answers’: [{‘text’: ‘singing and dancing’, ‘answer_start’: 207}], ‘is_impossible’: False}, {‘question’: “When did Beyonce leave Destiny’s Child and become a solo singer?”, ‘id’: ‘56be85543aeaaa14008c9066’, ‘answers’: [{‘text’: '2003’, ‘answer_start’: 526}], ‘is_impossible’: False}, {‘question’: 'In what city and state did Beyonce grow up? ', ‘id’: ‘56bf6b0f3aeaaa14008c9601’, ‘answers’: [{‘text’: ‘Houston, Texas’, ‘answer_start’: 166}], ‘is_impossible’: False}, {‘question’: ‘In which decade did Beyonce become famous?’, ‘id’: ‘56bf6b0f3aeaaa14008c9602’, ‘answers’: [{‘text’: ‘late 1990s’, ‘answer_start’: 276}], ‘is_impossible’: False}, {‘question’: ‘In what R&B group was she the lead singer?’, ‘id’: ‘56bf6b0f3aeaaa14008c9603’, ‘answers’: [{‘text’: “Destiny’s Child”, ‘answer_start’: 320}], ‘is_impossible’: False}, {‘question’: ‘What album made her a worldwide known artist?’, ‘id’: ‘56bf6b0f3aeaaa14008c9604’, ‘answers’: [{‘text’: ‘Dangerously in Love’, ‘answer_start’: 505}], ‘is_impossible’: False}, {‘question’: “Who managed the Destiny’s Child group?”, ‘id’: ‘56bf6b0f3aeaaa14008c9605’, ‘answers’: [{‘text’: ‘Mathew Knowles’, ‘answer_start’: 360}], ‘is_impossible’: False}, {‘question’: ‘When did Beyoncé rise to fame?’, ‘id’: ‘56d43c5f2ccc5a1400d830a9’, ‘answers’: [{‘text’: ‘late 1990s’, ‘answer_start’: 276}], ‘is_impossible’: False}, {‘question’: “What role did Beyoncé have in Destiny’s Child?”, ‘id’: ‘56d43c5f2ccc5a1400d830aa’, ‘answers’: [{‘text’: ‘lead singer’, ‘answer_start’: 290}], ‘is_impossible’: False}, {‘question’: ‘What was the first album Beyoncé released as a solo artist?’, ‘id’: ‘56d43c5f2ccc5a1400d830ab’, ‘answers’: [{‘text’: ‘Dangerously in Love’, ‘answer_start’: 505}], ‘is_impossible’: False}, {‘question’: ‘When did Beyoncé release Dangerously in Love?’, ‘id’: ‘56d43c5f2ccc5a1400d830ac’, ‘answers’: [{‘text’: ‘2003’, ‘answer_start’: 526}], ‘is_impossible’: False}, {‘question’: ‘How many Grammy awards did Beyoncé win for her first solo album?’, ‘id’: ‘56d43c5f2ccc5a1400d830ad’, ‘answers’: [{‘text’: ‘five’, ‘answer_start’: 590}], ‘is_impossible’: False}, {‘question’: “What was Beyoncé’s role in Destiny’s Child?”, ‘id’: ‘56d43ce42ccc5a1400d830b4’, ‘answers’: [{‘text’: ‘lead singer’, ‘answer_start’: 290}], ‘is_impossible’: False}, {‘question’: “What was the name of Beyoncé’s first solo album?”, ‘id’: ‘56d43ce42ccc5a1400d830b5’, ‘answers’: [{‘text’: ‘Dangerously in Love’, ‘answer_start’: 505}], ‘is_impossible’: False}],

    Processed: 0.015, SQL: 8