文章目录

  • 1. 例子
  • 2. 对文章进行分词
  • 3. 确定文章相关属性
  • 4. 构造example
  • 5. 得到query_tokens
  • 6. 将doc_tokens进行更细粒度地划分all_doc_tokens
  • 7. 获取答案在all_doc_tokens中的起始位置
  • 8. 构造doc_spans
  • 9. 构造tokens,并转化为input_ids
  • 10. 更新start_position和end_position
  • 11. 构造features
  • 12. 保存features并作为model的输入
  • 13. 构造model
  • 14. 训练
  • 15. 预测

1. 例子

假如有一篇文章paragraph_text= "Lucy, (1964-2000), from Xian."
问题:question_text = 'When was Lucy born?'
答案:answer = '1964'
本例子一篇文章只有一个问题,有的文章,存在多个问题的情况。下述所操作对象均指一篇文章的一个问题及其答案

2. 对文章进行分词

对上例中的paragraph_text按照\s,\t,\r,\n进行分词,最终得到结果:
doc_tokens = ['Lucy,', '(1964-2000),', 'from', 'xian.']
注意一个词后的标点符号如逗号会和该词分到一起
与此同时得到char_to_word_offset = [0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 2, 2, 2, 2, 3, 3, 3, 3, 3]
在解释char_to_word_offset含义之前先明确两个概念:token表示一个单词如Lucy,字符表示token的一个字母,如Lucy中的L是一个字符。下文均采用此概念。
doc_tokens中"Lucy,"是第0个字符,而他由5个字符构成,因此这5个字符均用0表示,对应char_to_word_offset前5个元素,以此类推。

3. 确定文章相关属性

对于一篇文章的一个问题,可能存在没有答案的情况,因此用变量is_is_impossible表示,如果为True表示该文章的该问题缺少答案;反之则表示该文章的该问题有答案。

  • 对于训练集且答案没有丢失:

    • qas_id:表示该篇文章问题的编号。(对于一篇文章可能有多个问题,因此会对一篇的文章的多个问题进行编号),一般原始文本会标注,直接取即可。
    • orig_answer_text表示答案的原始文本,该例即’1964’
    • start_position:答案开头的token(本例为1964)在doc_tokens中的index(本例为1,1964包含在’(1964-2000),'中,因此为1)
    • end_position:答案结束的token(本例仍为1964)在doc_tokens中的index(本例为1)
  • 对于训练集答案有所丢失:
    • qas_id:同上
    • orig_answer_text:""空字符串
    • start_position:-1
    • end_position:-1
  • 对于预测集:
    • qas_id:同上
    • orig_answer_text:None
    • start_position:None
    • end_position:None

4. 构造example

example是一个列表,该列表每个元素是一个SquadExample类,该类主要记录了上述一篇文章一个问题的相关属性:qas_id, question_text, doc_tokens, orig_answer_text, start_positions, end_position, is_impossible。
对于该例:

  • qas_id:0
  • question_text:‘When was Lucy born?’
  • doc_tokens: [‘Lucy,’, ‘(1964-2000),’, ‘from’, ‘xian.’]
  • orig_answer_text: ‘1964’
  • start_positions: 1
  • end_positions: 1
  • is_impossible: False

5. 得到query_tokens

从现在开始,所做操纵均指一个example中的一个元素(SquadExample)
将问题(question_text)进行tokenize后按照max_query_length(问题的最大长度)进行截断。
tokenize指tokenizer.tokenize(example.question_text)操作,
此外还需要限制问题最大的长度,即问题最多有几个token,因此对于token超过max_query_length的,需要进行截断。
假设max_query_length = 3
对于本例query_tokens = ['When', 'was', 'Lucy']

6. 将doc_tokens进行更细粒度地划分all_doc_tokens

上文doc_tokens只是对文章按照\s,\t,\r,\n进行分词,现在需要将doc_tokens中每个token再进行更细粒度划分,如’normans’可能会划分成’norman’和’##s’, ‘Lucy,‘划分成’Lucy’和’,’。
对于本例,用all_doc_tokens表示细粒度划分后的结果,all_doc_tokens = ['lucy', ',', '(', '1964', '-', '2000', ')', ',', 'from', 'xi', '##an', '.']
在构造all_doc_tokens的同时,还会得到另两个变量:

  • tok_to_orig_index:list,对于每个元素,表示all_doc_tokens中的元素在doc_tokens中的index。比如all_doc_tokens中的lucy对应doc_tokens第0个元素,','对应doc_tokens第0个元素。因此对于本例,tok_to_orig_index = [0, 0, 1, 1, 1, 1, 1, 1, 2, 3, 3, 3]
  • orig_to_tok_index:list与tok_to_orig_index含义相反,表示doc_tokens中的元素在all_doc_tokens中的index(取最近的那个index)。对于本例orig_to_tok_index = [0, 2, 8, 9]

7. 获取答案在all_doc_tokens中的起始位置

  • tok_start_position:答案的第一个词是all_doc_tokens第几个元素
  • tok_end_position:答案最后一个词是all_doc_tokens第几个元素
  • 对于训练集且答案没有丢失:
    • tok_start_position:如上述解释,本例为3,得到该结果可以参考bert中tok_start_position = orig_to_tok_index[example.start_position]和_improve_answer_span函数
    • tok_end_position:如上解释,本例为3
  • 对于训练集且答案丢失:
    • tok_start_position:-1
    • tok_end_position:-1
  • 对于预测集:
    • tok_start_position:None
    • tok_end_position:None

8. 构造doc_spans

由于每篇文章的token个数是不一样的,如果简单地按照截断的思想去做,会存在一个问题:截断后的文章没有完全包含答案,而阅读理解最终就想得到答案在文章的开始和结束位置,假如预测的时候把答案截断了,就不能够得到结果。所以不能简单粗暴得进行截断。较为合理的一个方法是设定一个文章的最大长度,假如用x表示,先取文章的前x个元素作为batch_size中的一个样本,然后向后走doc_stride步,再取x个元素作为batch_size的另一个样本,以此类推。因此一篇文章可以切分为好几个样本,有的样本包含答案,有的样本只包含部分答案,有的样本完全不包含答案。
在得到本例的结果之前,先剧透下输入到bert中的tokens构成’[CLS]’ + query + ‘[SEP]’ + 文章 + ‘[SEP]’
假如用max_seq_length表示tokens的最大长度,那么文章的最大长度max_tokens_for_doc = max_seq_length - len(query_tokens) -3。3表示’[CLS]’ 、‘[SEP]’ 、‘[SEP]’
假设max_seq_length = 9, doc_stride = 2,前述假设max_query_length=3,因此len(qeury_tokens)=3,故max_tokens_for_doc=3
对于本例:
doc_spans是一个列表,每个元素是_DocSpan = collections.namedtuple("DocSpan", ["start", "length"]), start表示开头那个token取的是all_doc_tokens第几个,length表示3个token占all_doc_tokens几个元素,一般等于max_tokens_for_doc,只有构造最后一个时可能小于max_tokens_for_doc。
all_doc_tokens = [‘lucy’, ‘,’, ‘(’, ‘1964’, ‘-’, ‘2000’, ‘)’, ‘,’, ‘from’, ‘xi’, ‘##an’, ‘.’]
doc_spans第0个元素start:0, length:3,对应[‘lucy’, ‘,’, ‘(’]
doc_spans第1个元素start:2, length:3,对应[‘(’, ‘1964’, ‘-’]
doc_spans第2个元素start:4, length:3,对应[‘-’, ‘2000’, ‘)’]
doc_spans第3个元素start:6, length:3,对应[‘)’, ‘,’, ‘from’]
doc_spans第4个元素start:8, length:3,对应[ ‘from’, ‘xi’, ‘##an’]
doc_spans第5个元素start:10, length:2,对应[ ‘##an’, ‘.’]

9. 构造tokens,并转化为input_ids

第8章中每个doc_span都会构成一个tokens,且上文也提前剧透了tokens的构成:‘[CLS]’ + query + ‘[SEP]’ + 文章 + ‘[SEP]’。因此对于doc_spans第0个元素tokens = ['[CLS]', 'When', 'was', 'Lucy', '[SEP]', 'lucy', ',', '(', '[SEP]'],其对应的segment_ids = [0, 0, 0, 0, 0, 1, 1, 1, 1],0表示tokens的该元素位于上半句(query),1表示tokens的元素位于下半句(部分文章片段);其对应的input_mask = [1, 1, 1, 1, 1, 1, 1, 1, 1],1表示tokens的该元素为非填充值,0表示填充值(填充值接下来会讲)。
得到了tokens,需要将每个元素转化为id(词汇字典,每个字都对应唯一的id号),input_ids = tokenizer.convert_tokens_to_ids(tokens),对于本例input_ids=[101, 2043, 2001, 7004, 102, 7004, 1010, 1006, 102]。
对于doc_spans第5个元素,其tokens = [‘[CLS]’, ‘When’, ‘was’, ‘Lucy’, ‘[SEP]’, ‘##an’, ‘.’, ‘[SEP]’]长度只有8,小于max_seq_length,因此还需要进行填充操作,即在tokens后面加上max_seq_length-len(tokens)个[‘PAD’],相对应的input_ids需要再加max_seq_length-len(tokens)个0(词汇表中’PAD’对应的id号为0),segment_ids后面加上max_seq_length-len(tokens)个0,input_mask后面加上max_seq_length-len(tokens)个0(表示填充值)。

10. 更新start_position和end_position

  • 对于训练集且答案未丢失:

    • start_position:更新为答案的开头在tokens中的index,对于tokens = [‘[CLS]’, ‘When’, ‘was’, ‘Lucy’, ‘[SEP]’, ‘(’, ‘1964’, ‘-’, ‘[SEP]’],start_position = 6
    • end_position:更新为答案的结尾在tokens中的index,对于tokens = [‘[CLS]’, ‘When’, ‘was’, ‘Lucy’, ‘[SEP]’, ‘(’, ‘1964’, ‘-’, ‘[SEP]’],end_position = 6
    • 如果token中并没有完全包含答案,那么start_position和end_position都为0
  • 对于训练集且答案丢失:
    • start_position和end_position都为0
  • 对于预测集:
    • 任务就是预测start_position和end_position

11. 构造features

features是一个列表,每个列表是一个InputFeatures类的实例,其主要记录了一些属性,这些属性针对一篇文章一个问题的一个doc_span。

  • unique_id:该文章该问题的该doc_span的唯一标识号
  • example_index:examples中第几个元素(含义为第几篇文章的第几个问题编号)
  • doc_span_index:doc_spans的第几个元素(一篇文章一个问题的第几个子样本(tokens),一篇文章一个问题可以构造多个tokens)
  • tokens:即上述的tokens(不包括最后填充的’PAD’)
  • token_to_orig_map:字典,key为tokens中每个元素的index(只有文章,不包含query),value为当前词在doc_tokens中的索引,以tokens = [‘[CLS]’, ‘When’, ‘was’, ‘Lucy’, ‘[SEP]’, ‘(’, ‘1964’, ‘-’, ‘[SEP]’]为例,该例子的doc_span.start = 2,因此token_to_orig_map为{5: 1, 6: 1, 7: 1}
  • token_is_max_context:字典,key为tokens中每个元素的index(只有文章,不包含query),value为该词是否是含有该词的doc_span中得分最高的。比如doc_spans第0个元素start:0, length:3,对应[‘lucy’, ‘,’, ‘(’]和doc_spans第1个元素start:2, length:3,对应[‘(’, ‘1964’, ‘-’],两者都含有’(‘,因此会计算这两个doc_span中’(‘各自的得分。得分计算方式:对于doc_spans第0个元素start:0, length:3,对应[‘lucy’, ‘,’, ‘(’],’(‘左边有2个词,用num_left_context表示,右边有0个词,用num_right_context表示,因此score = min(num_left_context, num_right_context) + 0.01 * doc_span.length = 0.03;同理,对于doc_spans第1个元素start:2, length:3,对应[’(‘, ‘1964’, ‘-’],’(‘左边有0个元素,右边有两个元素,因此score = 0.03。由于doc_spans第1个元素在doc_spans第0个元素之后,所以比较顺序为doc_spans第1个元素的’(‘得分不大于doc_spans第0个元素的’(‘得分,故对于doc_spans第0个元素中’(‘的value为Ture,对于doc_spans第1个元素中’('的value为False
  • input_ids:同上,包括填充
  • segment_ids:同上,包括填充
  • start_position:同上,更新后的
  • end_position:同上,更新后的
  • is_impossible:同上,是否缺失答案(预测集为False)

12. 保存features并作为model的输入

将上述features保存成TFRecord,读取后即可作为model的输入了

13. 构造model

model有了输入,通过bert模型,得到结果final_hidden = model.get_sequence_output(),shape:[batch_size, seq_length, hidden_size],将其reshape为[batch_size * max_seq_length, hidden_size]再经过一个全连接层,得到final_hidden_matrix,shap:[batch_size*max_seq_length, 2]。再将final_hidden_matrix reshape和transpose,得到logits,shape:[2, batch_size, max_seq_length]。logits第0个元素就是start对应的结果start_logits,logits第1个元素就是end对应的结果end_logits。start,shape:[batch_size, max_seq_length],每个样本每个token的结果,再softmax就是每个样本每个token的概率值

14. 训练

正常训练即可,无更多说明

15. 预测

预测的时候主要对一些变量进行说明。

  • start_logits:列表。其对象为一篇文章一个问题的一个doc_span。列表长度为len(tokens),表示tokens中每个token做为开始位置的概率。通过操作[float(x) for x in result[“start_logits”].flat]得到,其中result就是一篇文章一个问题一个doc_span的结果,结构如下:{“unique_ids”: unique_ids, “start_logits”: start_logits, “end_logits”: end_logits},因此最终得到的start_logits是没有softmax的。
  • end_logits:和start_logits类似
  • start_indexes:列表,其对象为一篇文章一个问题的一个doc_span。上述start_logits表示每个token做为开始位置的概率值,因此start_indexes记录了概率前n_best_size大的index(也即最有可能的前n_best_size大token的位置)
  • end_indexes:和start_indexes类似
  • min_null_feature_index:int。其对象为一篇文章一个问题
    • 对于version_2_with_negative=True(样本中存在有些问题没有答案):一篇文章一个问题有多个doc_span,计算每个doc_span的start_logits[0] + end_logits[0](开始位置选择0,结束位置选择0,作为无答案的代表)。start_logits[0] + end_logits[0]最小的那个doc_span对应的index(一篇文章一个问题的每个doc_span的feature构成features列表,这里的index是针对features而言的)即min_null_feature_index
    • 对于version_2_with_negative=True,min_null_feature_index = 0
  • null_start_logit:int。其对象为一篇文章一个问题
    • 对于version_2_with_negative=True,承上,start_logits[0] + end_logits[0]最小的那个doc_span的start_logits[0]
    • 对于version_2_with_negative=True,null_start_logit= 0
  • null_end_logit:与null_start_logit类似
  • score_null:float。其对象为一篇文章的一个问题
    • 对于version_2_with_negative=True,承上,score_null = min(start_logits[0] + end_logits[0])
    • 对于version_2_with_negative=False,score_null = 1000000
  • prelim_predictions:列表。其对象为一篇文章的一个问题。其元素都是 _PrelimPrediction = collections.namedtuple( “PrelimPrediction”, [“feature_index”, “start_index”, “end_index”, “start_logit”, “end_logit”])。一篇文章一个问题一个doc_span得到了start_indexes和end_indexes,遍历每个doc_span的start_indexes与end_indexes组合(首先要遍历doc_span,然后要遍历组合,组合指:如start_indexes=[0,1], end_indexes=[3, 4],组合类型有start_index=0,end_index=3;start_index=0,end_index=4;start_index=1,end_index=3;start_index=1,end_index=4;),一旦发现有一组合的start_index对应那个token的token_is_max_context[start_index]为True,且end_index大于等于start_index,则成为一个 _PrelimPrediction元素,该元素的feature_index, start_index, end_index, start_logit, end_logit顾名思义。此外,对于version_2_with_negative=True,prelim_predictions中还有一个特殊的_PrelimPrediction 元素,该元素的feature_index = min_null_feature_index,start_index = 0, end_index = 0, start_logit = null_start_logit, end_logit = null_end_logit。最后还需要将所有的_PrelimPrediction元素按照start_logit + end_logit进行从大到小排序。这里注意的是在version_2_with_negative=False,prelim_predictions有可能为空(没有一个doc_span的start_index/end_index组合是满足要求的),当version_2_with_negative=True时,prelim_predictions有可能只有一个特殊元素
  • n_best:列表。其对象为一篇文章的一个问题。其元素都是 _NbestPrediction = collections.namedtuple( “NbestPrediction”, [“text”, “start_logit”, “end_logit”])。n_best长度小于等于n_best_size(也即上述prelim_predictions前n_best_size元素经过一些操作得到n_best)。遍历上述prelim_predictions每个元素,记每个元素用pred表示(直到len(n_best) > =n_best_size停止循环)。
    • 如果pred.start_index > 0(pred.start_index=0,从上面可以看到是version_2_with_negative=True时那个特殊的_PrelimPrediction元素。正常得到的_PrelimPrediction.start_index不可能是0,因为0对应的token属于query):final_text为最终预测的结果字符串
    • 如果pred.start_index = 0:final_text = “”

故n_best的一个_NbestPrediction的text,start_logit,end_logit都可以得到了。在得到最多n_best_size个 _NbestPrediction元素后,如果version_2_with_negative=True,且final_text=““不在n_best中,n_best还需要添加一个特殊的_NbestPrediction,其 text=””,start_logit=null_start_logit, end_logit = null_end_logit(前n_best_size个_PrelimPrediction 有可能不包含那个特殊的_PrelimPrediction )。因此最终n_best的长度有可能是n_best_size+1。此外n_best在version_2_with_negative=False时,有可能为空,此时需要添加另一个特殊的_NbestPrediction,final_text = ‘empty’, start_logit = 0.0, end_logits = 0.0。n_best在version_2_with_negative=True时有可能只有那个特殊的_NbestPrediction

  • total_scores:列表。其对象为一篇文章的一个问题。得到上述n_best后,每个元素都可以得到start_logit+end_logit,该值即total_socores的每个元素值
  • best_non_null_entry:一个_NbestPrediction 元素。其对象为一篇文章的一个问题。得到上述n_best后(按照start_logit+end_logit从大到小排过序了),best_non_null_entry即n_best首个text有值的_NbestPrediction 元素。如果n_best只有一个text为""的元素,则best_non_null_entry=None
  • probs:列表。其对象为一篇文章的一个问题。相对total_scores而言的。probs就是将total_scores的每个元素按照一定公式转化得到的。公式为:probi=exp(scorei−max(scores))∑iexp(scorei−max(scores))prob_i = \frac{exp(score_i-max(scores))}{\sum_{i}exp(score_i-max(scores))}probi​=∑i​exp(scorei​−max(scores))exp(scorei​−max(scores))​
  • nbest_json:列表。其对象为一篇文章的一个问题。得到上述n_best后,将每个元素转换为一个字典,keys为text、probability、start_logit、end_logit。key的含义顾名思义。该字典就是nbest_json的一个元素。
  • all_predictions:字典。其对象为所有文章的所有问题。单个元素为一篇文章一个问题的答案。对于单个元素其值如下:
    • FLAGS.version_2_with_negative = False:key为example.qas_id(一篇文章的一个问题编号),value为nbest_json[0][“text”]。答案有可能是empty
    • FLAGS.version_2_with_negative = True:score_diff = score_null - best_non_null_entry.start_logit - best_non_null_entry.end_logit
      • 如果score_diff 大于null_score_diff_threshold(指定的超参数),key为example.qas_id, value为""
      • 如果score_diff 小于等于null_score_diff_threshold,key为example.qas_id,value为best_non_null_entry.text
  • all_nbest_json:字典。其对象为所有文章的所有问题。单个元素为一篇文章一个问题的n个可能答案。对于单元素,其值key为example.qas_id,value为nbest_json。
  • scores_diff_json其对象为所有文章的所有问题
    • FLAGS.version_2_with_negative = False,score_diff_json就是一个空字典
    • FLAGS.version_2_with_negative = True,对于单个元素,承上all_predictions,key为example.qas_id,value为score_diff

基于bert的阅读理解脚本(run_squad)原理梳理(从举例的角度说明)相关推荐

  1. “非自回归”也不差:基于MLM的阅读理解问答

    作者丨苏剑林 单位丨追一科技 研究方向丨NLP,神经网络 个人主页丨kexue.fm 前段时间写了万能的Seq2Seq:基于Seq2Seq的阅读理解问答,探索了以最通用的 Seq2Seq 的方式来做阅 ...

  2. 万能的Seq2Seq:基于Seq2Seq的阅读理解问答

    作者丨苏剑林 单位丨追一科技 研究方向丨NLP,神经网络 个人主页丨kexue.fm 今天给 bert4keras [1] 新增加了一个例子:阅读理解式问答(task_reading_comprehe ...

  3. 【NLP】完全解析!Bert Transformer 阅读理解源码详解

    接上一篇: 你所不知道的 Transformer! 超详细的 Bert 文本分类源码解读 | 附源码 中文情感分类单标签 参考论文: https://arxiv.org/abs/1706.03762 ...

  4. 完全解析!Bert Transformer 阅读理解源码详解

    接上一篇: 你所不知道的 Transformer! 超详细的 Bert 文本分类源码解读 | 附源码 中文情感分类单标签 参考论文: https://arxiv.org/abs/1706.03762 ...

  5. 基于CNN的阅读理解式问答模型:DGCNN

    作者丨苏剑林 单位丨广州火焰信息科技有限公司 研究方向丨NLP,神经网络 个人主页丨kexue.fm 早在年初的一文读懂「Attention is All You Need」| 附代码实现中就已经承诺 ...

  6. NLP之BERT英文阅读理解问答SQuAD 2.0超详细教程

    环境 linux python 3.6 tensorflow 1.12.0 文件准备工作 下载bert源代码 : https://github.com/google-research/bert 下载b ...

  7. 【逻辑书单】①《一本小小的蓝色逻辑书》之阅读理解及其解题技巧梳理

        阅读理解这类题型实在是普遍,从小到大也是在各类学科中去学去练,那么,流畅阅读文字材料并理解掌握其内容是一项非常重要的技能,它要求我们具备一定水平的推理能力. 本文内容覆盖本书<一本小小的 ...

  8. 手写 RPC(一)基于 BIO,深入理解远程调用原理

                                                                                  [诗119:130]你的言语一解开,就发出亮 ...

  9. SIGIR 2019 | 基于人类阅读行为模式的机器阅读理解

    作者丨张琨 学校丨中国科学技术大学博士生 研究方向丨自然语言处理 论文动机 机器阅读理解一直是自然语言处理领域的一个非常重要的研究方向,目前虽然在一些给定条件下,机器学习的方法可以取得和人类类似甚至好 ...

  10. 博士学位论文 | 机器阅读理解与文本问答技术研究

    作者丨胡明昊 学校丨国防科技大学博士生 研究方向丨机器阅读理解 引言 文本问答是自然语言处理中的一个重要领域,随着一系列大规模高质量数据集的发布和深度学习技术的快速发展,文本问答技术在近年来引起了学术 ...

最新文章

  1. 一位中学计算机老师的英语作文,我的老师英语作文(精选14篇)
  2. 【Spring注解系列09】Spring初始化和销毁接口-InitializingBean与DisposableBean
  3. 【Python教程】 print 和return 的区别
  4. luogu3830 [SHOI2012]随机树
  5. 巨人网络第三季度营收5.06亿元 净利润3亿元
  6. 拿不出双十一成绩单,垂直电商何以安身立命?
  7. java报错空指针异常_分析使用Spring Boot进行单元测试时,报出空指针异常
  8. 触动精灵怎么设置虚拟服务器,如何调试脚本及解决问题的方法
  9. Java集合干货——HashMap源码分析
  10. tftp64工具使用
  11. 学生党什么价位蓝牙耳机性价比高?2021这五款蓝牙耳机发烧友都爱了
  12. 代码 马佳义_武汉大学电子信息学院
  13. 原子结构示意图全部_原子结构示意图的分类和详细知识点
  14. 请教苹果虚拟机自动配置序列号ID脚本
  15. 基于vue-router的matched实现面包屑功能
  16. php doctrine 使用,php – Doctrine 2 – 多数据库配置和使用
  17. 学猫叫用计算机歌词,抖音学猫叫是什么歌 学猫叫歌曲歌词
  18. pta Mysql题目集 (81-100)
  19. [量子计算]一种金融衍生品的蒙特卡洛定价量子算法。(Quantum algorithm for the Monte Carlo pricing of financial derivatives.)
  20. 贵阳哪里有计算机二级培训机构,贵阳省计算机二级考试培训

热门文章

  1. 斗鱼VS虎牙,谁才是直播之王?一文带你看看两家平台 的竞品报告
  2. Allegro PCB 封装库
  3. 乐高mindstormsev3_乐高MINDSTORMSEV3软件程序模块开发-2019年精选文档
  4. 身份证前6位地区编码sql
  5. Java疯狂讲义读书笔记第一章
  6. Nginx工作原理和优化
  7. 编程工具使用-Procexp
  8. JSONArray.fromObject(); 引入问题
  9. 几种常见的JS混淆工具比较。
  10. Filter过滤器|敏感词汇过滤