preproc.py & config.py
os.path.expanduser
home = os.path.expanduser(".")
os.path.expanduser(path) 把path中包含的""和"user"转换成用户目录
路径文件
word_emb_file = config.fasttext_file if config.fasttext else config.glove_word_file
word_emb_file为glove_word所在的路径,而非wiki_news的路径
train_file = os.path.join(home, "data", "squad", "train-v1.1.json")
train_file:’.\data\squad\train-v1.1.json’
flags.DEFINE_integer("num_heads", 1, "Number of heads in multi-head attention")
flags.DEFINE_string("train_log", "log/train.log", "Log for each checkpoint")
flags.DEFINE_boolean("print_weight", False, "Print weights of some layers")
config = flags.FLAGS
将所有的路径都定义成flag的全局参数,并给它规定类型和注释
最后用config = flags.FLAGS来结束定义
device = torch.device("cuda" if torch.cuda.is_available() else "cpu")
自动判断是否使用GPU
cudnn.enabled = False
一般来讲,应该遵循以下准则:
- 如果网络的输入数据维度或类型上变化不大,设置 torch.backends.cudnn.benchmark = true 可以增加运行效率;
- 如果网络的输入数据在每次 iteration 都变化的话,会导致 cnDNN 每次都会去寻找一遍最优配置,这样反而会降低运行效率。
get_embedding
word_emb_mat, word2idx_dict = get_embedding(
word_counter, "word", emb_file=word_emb_file, vec_size=config.glove_dim)
char_emb_mat, char2idx_dict = get_embedding(
char_counter, "char", emb_file=char_emb_file, vec_size=char_emb_dim)
word_emb
if emb_file is not None:
with open(emb_file, "r", encoding="utf-8") as fh:
for line in tqdm(fh):
array = line.split()
word = "".join(array[0:-vec_size])
vector = list(map(float, array[-vec_size:]))
if word in counter and counter[word] > limit:
embedding_dict[word] = vector
将glove中的所有行遍历一遍,每行为string类型用line.split()分割成list,word取第一个词,vector变为float型的300维的向量,接着将word:vector添加到新的字典中embedding_dict
print("{} / {} tokens have corresponding {} embedding vector".format(
len(embedding_dict), len(filtered_elements), data_type))
91594 / 111135 tokens have corresponding word embedding vector
embedding_dict的长度是文章在glove中存在的词数目,而filtered_elements是文章中所有的词数目,意味着还有2w多个词并没有给予词向量,需要初始词向量和预训练。
NULL = "--NULL--"
OOV = "--OOV--"
token2idx_dict = {token: idx for idx, token in enumerate(embedding_dict.keys(), 2)}
token2idx_dict[NULL] = 0
token2idx_dict[OOV] = 1
embedding_dict[NULL] = [0. for _ in range(vec_size)]
embedding_dict[OOV] = [0. for _ in range(vec_size)]
idx2emb_dict = {idx: embedding_dict[token]
for token, idx in token2idx_dict.items()}
emb_mat = [idx2emb_dict[idx] for idx in range(len(idx2emb_dict))]
return emb_mat, token2idx_dict
token代表字符,index代表从0开始的下标,embedding代表300维的向量
创建了NULL和OOV,分别代表空字符和未记录到的字符
赋予embedding_dict 两个300维大小以0为值的初始向量
再通过for循环创建两个dict,
token2idx_dict: ‘,’ (2428012413424)=(int)2 ……
idx2emb_dict: 2 (1927635136) ={list}[-0.082752, 0.67204, -0.14987, -0.064983, …… 从2开始建dict是因为0和1是NULL和OOV
embedding_dict: ‘,’ (2428012413424)={list}[-0.082752, 0.67204, -0.14987, -0.064983, ……
最终把idx2emb_dict转化为emb_mat的列表使得list的index对应了dict的name,使顺序从0开始。
char_emb
else:
assert vec_size is not None
for token in filtered_elements:
embedding_dict[token] = [np.random.normal(
scale=0.1) for _ in range(vec_size)]
print("{} tokens have corresponding embedding vector".format(
len(filtered_elements)))
emb_file=None
vec_size = config.char_dim 64
embedding_dict 将文章中的每个char都遍历一遍,给其初始化的embedding值,方法用正态随机,方差是0.1.
build_features
build_features(config, train_examples, "train", config.train_record_file, word2idx_dict, char2idx_dict)
para_limit = config.para_limit #400
ques_limit = config.ques_limit #50
ans_limit = config.ans_limit #30
char_limit = config.char_limit #16
for n, example in tqdm(enumerate(examples)):
example包含着每个问题对应的信息,包含context_tokens context的词,context_chars context的字符,ques_tokens ques的词,ques_chars ques的字符,y1s 答案对应的开始,y2s 答案对应的结束。
def filter_func(example, is_test=False):
return len(example["context_tokens"]) > para_limit or \
len(example["ques_tokens"]) > ques_limit or \
(example["y2s"][0] - example["y1s"][0]) > ans_limit
为了判断每个context和question是否超出长度或者答案是否超出长度,若其中一个有超出,则返回True,在循环时则继续下一个循环,不对当前的example进行操作。
def _get_word(word):
for each in (word, word.lower(), word.capitalize(), word.upper()):
if each in word2idx_dict:
return word2idx_dict[each]
return 1
遍历word的四种形态,若存在于word2idx_dict,则返回对应的idx
for i, token in enumerate(example["context_chars"]):
for j, char in enumerate(token):
if j == char_limit:
break
context_char_idx[i, j] = _get_char(char)
context_char_idxs.append(context_char_idx)
通过_get_char得到char对应的idx,context_char_idx(400,16),400代表文章的词数上限。16代表每个词词长度上限。context_char_idxs列表仅有一个元素context_char_idx(array类型)。
for i, token in enumerate(example["ques_chars"]):
for j, char in enumerate(token):
if j == char_limit:
break
ques_char_idx[i, j] = _get_char(char)
ques_char_idxs.append(ques_char_idx)
ques_char_idx(50,16),50代表问题的词数上限。
start, end = example["y1s"][-1], example["y2s"][-1]
y1s.append(start)
y2s.append(end)
ids.append(example["id"])
y1s记录所有问题对应答案的开始,y2s记录所有问题对应答案的末尾
np.savez(out_file, context_idxs=np.array(context_idxs), context_char_idxs=np.array(context_char_idxs),
ques_idxs=np.array(ques_idxs), ques_char_idxs=np.array(ques_char_idxs), y1s=np.array(y1s),
y2s=np.array(y2s), ids=np.array(ids))
将所有的list都转换为array类型,存储到train.npz中。
build_features目的是将原来以问题为单位记录特征的examples,变为以特征为单位的多个list,并且把所有的字符或字符串变为idx,再以array类型存储。