百度网盘可以做网站吗?,网站建设手机软件,自己做网站投放有流量么,建设部网站预应力资质代码地址
1、create_dictionary.py
建立词典和使用预训练的glove向量
#xff08;1#xff09;create_dictionary()
遍历每个question文件取出所关注的question部分#xff0c;qs 遍历qs#xff0c;对每个问题的文本内容进行分词#xff0c;并将分词结果添加到字典中1create_dictionary()
遍历每个question文件取出所关注的question部分qs 遍历qs对每个问题的文本内容进行分词并将分词结果添加到字典中True表示添加新词而非索引
#创建词典
#词典用于将文本数据中的单词映射到唯一的整数标识符
def create_dictionary(dataroot):dictionary Dictionary()questions []files [v2_OpenEnded_mscoco_train2014_questions.json,v2_OpenEnded_mscoco_val2014_questions.json,v2_OpenEnded_mscoco_test2015_questions.json,v2_OpenEnded_mscoco_test-dev2015_questions.json]#遍历files列表中的每个文件for path in files:#将根目录dataroot与当前文件名path连接起来得到每个问题文件的完整路径question_path os.path.join(dataroot, path)#打开当前问题文件加载其中的JSON数据然后选择其中的question键对应的值qs json.load(open(question_path))[questions]#遍历qsfor q in qs:#对每个问题的文本内容进行分词并将分词结果添加到字典中True表示将新词添加到词典中而非词的索引dictionary.tokenize(q[question], True)return dictionary
2create_glove_embedding_init()
从预训练Glove词嵌入文件glove.6B.300d.txt中创建一个初始化权重矩阵
word2emb存储从单词到词嵌入向量的映射读取Glove文件中的所有行每一行包含一个单词及其对应的嵌入向量entries从第一行获取嵌入向量维度emb_dim创建一个与词汇表大小相同的零矩阵用于存储嵌入向量的权重weights遍历Glove文件的每一行entries-entry。获取当前单词word及嵌入向量的值字符串-浮点数。存储单词及其对应嵌入向量word2emb[word]遍历词汇表中每个单词在word2emb中的将对应嵌入向量赋值给初始化的权重矩阵返回weights、word2emb
#从预训练的Glove词嵌入文件中创建一个初始化权重矩阵
def create_glove_embedding_init(idx2word, glove_file):word2emb {} #用于存储从单词到嵌入向量的映射with open(glove_file, r,encodingutf-8) as f:#读取Glove文件的所有行每一行包含一个单词及其对应的嵌入向量entries f.readlines()#从第一行获取嵌入向量的维度emb_dim len(entries[0].split( )) - 1print(embedding dim is %d % emb_dim)#创建一个与词汇表大小相同的零矩阵该矩阵用于存储嵌入向量的权重weights np.zeros((len(idx2word), emb_dim), dtypenp.float32)#遍历Glove文件的每一行for entry in entries:vals entry.split( )word vals[0]#获取当前行的单词vals list(map(float, vals[1:]))#获取嵌入向量的值从字符串值转换为浮点数word2emb[word] np.array(vals)#将单词及其对应的嵌入向量存储在word2emb字典中#enumerate组合为一个索引序列同时返回索引和元素#遍历词汇表中的每个单词for idx, word in enumerate(idx2word):#如果单词不在word2emb中跳过if word not in word2emb:continue#否则将词汇表中每个单词的嵌入向量赋值给初始化的权重矩阵weights[idx] word2emb[word]#返回初始化的权重矩阵和单词到嵌入向量的映射return weights, word2emb 3main
创建词典并保存到指定路径dump_to_file加载词典对象(load_from_file)设置嵌入维度为300获取初始化权重和单词到嵌入向量的映射(create_glove_embedding_init())保存至glove6b_init_300d.npy
#创建词典d create_dictionary(D:/bottom-up-attention-vqa-master/data)#将创建的词典对象保存到指定路径下d.dump_to_file(D:/bottom-up-attention-vqa-master/data/dictionary.pkl)#加载词典对象d Dictionary.load_from_file(D:/bottom-up-attention-vqa-master/data/dictionary.pkl)emb_dim 300 #设置嵌入维度为300#data/glove/glove.6B.300.txtglove_file D:/bottom-up-attention-vqa-master/data/glove/glove.6B.%dd.txt % emb_dim#获取初始化权重矩阵和单词到嵌入向量的映射weights, word2emb create_glove_embedding_init(d.idx2word, glove_file)#将初始化的权重矩阵保存到下列文件中np.save(D:/bottom-up-attention-vqa-master/data/glove6b_init_%dd.npy % emb_dim, weights)2、compute_softscore.py
annotation部分的处理。
1main
打开并加载JSON数据中的annotation部分训练集答案信息、验证集答案信息、question部分训练集问题信息、验证集问题信息answers train_answers val_answersfilter_answers() :对答案数据集进行预处理和筛选返回occurencecreate_ans2label()创建答案到标签的映射ans2label标签到答案的映射label2ans并保存为2个文件返回答案到标签的映射ans2labelcompute_target()为数据集中的答案计算目标标签并保存用于训练和评估模型
if __name__ __main__:train_answer_file D:/bottom-up-attention-vqa-master/data/v2_mscoco_train2014_annotations.json#打开并加载JSON数据中的annotations部分其中包含训练集的答案信息train_answers json.load(open(train_answer_file))[annotations]val_answer_file D:/bottom-up-attention-vqa-master/data/v2_mscoco_val2014_annotations.json#验证集的答案信息val_answers json.load(open(val_answer_file))[annotations]#训练集的问题信息train_question_file D:/bottom-up-attention-vqa-master/data/v2_OpenEnded_mscoco_train2014_questions.jsontrain_questions json.load(open(train_question_file))[questions]#验证集的问题信息val_question_file D:/bottom-up-attention-vqa-master/data/v2_OpenEnded_mscoco_val2014_questions.jsonval_questions json.load(open(val_question_file))[questions]answers train_answers val_answers#对答案数据集进行预处理及筛选并返回一个字典occurenceoccurence filter_answers(answers, 9)# 创建答案到标签的映射、标签到答案的映射并保存为两个文件返回答案到标签的映射ans2label create_ans2label(occurence, trainval)compute_target(train_answers, ans2label, train)compute_target(val_answers, ans2label, val)2filter_answersanswers_destmin_occurence
occurence 字典键预处理过的答案值包含该答案的问题ID的set集合遍历answers_dest中的每个答案 ans_entry answers ans_entry[‘answer’] 最常见的基准真值gtruth ans_entry[multiple_choice_answer] preprocess_answer(gtruth)对gtruth进行预处理 若gtruth不在occurence中第一次遇到这个答案将gtruth作为字典的键对应是一个空set集合 将当前问题id ans_entry[‘question_id’]添加到occurence字典的键的set集合中遍历occurence字典的键答案移除出现次数小于min_occurence的答案及对应ID打印经过筛选后的occurence的长度返回occurence
#对答案数据集进行筛选并返回一个字典occurence
def filter_answers(answers_dset, min_occurence):This will change the answer to preprocessed version#字典键预处理过的答案preprocess_answer()值包含该答案的问题ID的set集合occurence {}#遍历answers_dest中的每个答案for ans_entry in answers_dset:answers ans_entry[answers]gtruth ans_entry[multiple_choice_answer]gtruth preprocess_answer(gtruth)#对答案进行预处理#若gtrush不在occurence中说明第一次遇到这个答案将gtrush作为字典的键对应的值是一个空的集合if gtruth not in occurence:occurence[gtruth] set()occurence[gtruth].add(ans_entry[question_id])#将当前问题id添加到occurence中gtruth为键的值set集合当中#遍历occurence字典的键答案移除出现次数小于min_occurence的答案及对应的问题IDfor answer in list(occurence.keys()):if len(occurence[answer]) min_occurence:occurence.pop(answer)#打印经过筛选后的occurence的长度print(Num of answers that appear %d times: %d % (min_occurence, len(occurence)))return occurence
a、preprocess_answer(answer)
对答案进行预处理
process_digit_article(process_punctuation(answer))移除答案中的逗号返回answer
b、process_punctuation(inText)
处理输入文本中的标点符号
遍历punct中定义好的每个符号若标点在inText的两侧或是千位分割符删除该标点否则将标点替换成空格去除文本中的句号返回经过处理的文本
#处理输入文本中的标点符号
def process_punctuation(inText):outText inText#遍历punct中定义好的每一个标点符号for p in punct:#若该标点在inText的两侧或inText匹配定义好的comma_strip则删除该标点if (p in inText or p in inText) \or (re.search(comma_strip, inText) ! None):outText outText.replace(p, )#否则将标点替换为一个空格else:outText outText.replace(p, )#使用period_strip去除文本中的句号outText period_strip.sub(, outText, re.UNICODE)#返回经过处理的文本return outTextc、process_digit_article(inText)
处理输入文本中的数字及冠词
将inText转换为小写并拆分成单词 tempText遍历tempText中的每个单词若word在manual_map中将数字单词映射为相应数字否则保持不变若word不是冠词则添加到outText中否则跳过不处理遍历outText并获取wordId及word。若单词为缩写词则替换为规范化文本将处理后的单词列表通过空格连成字符串返回outText
def process_digit_article(inText):outText []#存储处理后的文本tempText inText.lower().split()#将inText转换为小写并拆分成单词#遍历tempText中的每个单词for word in tempText:#如果word在manual_map中将其映射为相应的数字否则保持不变word manual_map.setdefault(word, word)#若word不是冠词则添加到outText列表中否则跳过不处理if word not in articles:outText.append(word)else:pass#遍历outText并获取wordId及wordfor wordId, word in enumerate(outText):#如果单词为缩写词则替换为规范化文本if word in contractions:outText[wordId] contractions[word]#将处理后的单词列表通过空格连接成一个字符串outText .join(outText)return outText
3create_ans2labeloccurencenamecache_root
ans2label存储答案到标签的映射的字典label2ans存储标签到答案的列表。label0 标签计数器遍历occurence字典中每个答案 将ans添加到label2ans列表中 将ans映射到当前label存储在ans2label字典中 标签计数器label递增使用pickle序列化将ans2label、label2ans存储到对应文件中返回ans2label
#创建答案到标签的映射并保存为两个文件
def create_ans2label(occurence, name, cache_rootD:/bottom-up-attention-vqa-master/data/cache):Note that this will also create label2ans.pkl at the same timeoccurence: dict {answer - whatever}name: prefix of the output filecache_root: strans2label {}#存储答案到标签的映射label2ans []#存储标签到答案的映射label 0#初始化一个标签计数器#遍历occurence字典中的每个答案for answer in occurence:label2ans.append(answer)#将答案添加到label2ans列表中ans2label[answer] label#将答案映射到当前标签存储在ans2label字典中label 1#标签计数器递增utils.create_dir(cache_root)#创建一个目录确保存储文件的目录存在cache_file os.path.join(cache_root, name_ans2label.pkl)#构建存储映射的文件路径pickle.dump(ans2label, open(cache_file, wb))#使用pickle序列化将ans2label存储到对应文件中cache_file os.path.join(cache_root, name_label2ans.pkl)pickle.dump(label2ans, open(cache_file, wb))#返回存储答案到标签的映射字典return ans2label4compute_target(answers_dest,ans2label,name,cache_root)
为数据集中的答案计算目标标签并将计算得到的标签信息保存到文件中标签信息用于训练和评估模型 taeget空列表存储目标标签 遍历answers_dest中每个答案ans_entry answers ans_entry[‘answers’]是一个包含10个元素的list answer_count字典记录每个答案出现次数 遍历当前答案answers中的所有答案answer获取具体答案answer_将答案出现的次数记录在answer_count字典中 列表:标签labels 分数scores 遍历answer_count中的每个答案若不在已创建的ans2label中跳过当前答案否则将答案映射到标签并添加到labels中根据答案出现的次数计算分数score将分数添加到scores列表中 将问题id、图像id、labels、scores组成一个字典并添加到target列表中 将target保存到文件当中返回target
#出现次数越多的答案分数越高
def get_score(occurences):if occurences 0:return 0elif occurences 1:return 0.3elif occurences 2:return 0.6elif occurences 3:return 0.9else:return 1#为数据集中的答案计算目标标签并将计算得到的标签信息保存到文件中标签信息用于训练和评估模型
def compute_target(answers_dset, ans2label, name, cache_rootE:/bottom-up-attention-vqa-master/data/cache):Augment answers_dset with soft score as label***answers_dset should be preprocessed***Write result into a cache file 结果写入缓存文件target []#创建一个空列表用于存储计算得到的目标标签#遍历answers_dest中的每个答案数据项ans_entryfor ans_entry in answers_dset:answers ans_entry[answers]answer_count {} #创建一个空字典用于记录每个答案的出现次数#遍历当前答案中的所有答案for answer in answers:answer_ answer[answer]#获取answer的具体内容#将答案出现的次数记录在answer_count字典中answer_count[answer_] answer_count.get(answer_, 0) 1#创建两个空列表用于存储标签和对应的分数labels []scores []#遍历answer_count中的每个答案for answer in answer_count:#如果答案不在已经创建的答案到标签的映射中跳过当前答案if answer not in ans2label:continue#否则将答案映射到标签并添加到labels列表中labels.append(ans2label[answer])score get_score(answer_count[answer])#根据答案出现的次数计算一个分数scores.append(score)#将分数添加到scores列表中#将整个过程得到的信息问题id、图像id、标签列表、分数列表组成一个字典并添加到target列表中target.append({question_id: ans_entry[question_id],image_id: ans_entry[image_id],labels: labels,scores: scores})utils.create_dir(cache_root)cache_file os.path.join(cache_root, name_target.pkl)pickle.dump(target, open(cache_file, wb))return target
结合一段实际案例理解上述这段代码
# 定义答案数据集answers_dset答案到标签的映射ans2label
answers_dset [{question_id: 1, image_id: 101, answers: [{answer: yes}, {answer: yes}, {answer: no}]},{question_id: 2, image_id: 102, answers: [{answer: dog}, {answer: cat}, {answer: dog}]},# ... 其他答案数据项
]ans2label {yes: 0, no: 1, dog: 2, cat: 3}# 调用 compute_target 函数计算目标标签和分数
target compute_target(answers_dset, ans2label, example)# 打印结果
print(Target Information:)
for entry in target:print(fQuestion ID: {entry[question_id]}, Image ID: {entry[image_id]})print(Labels: , entry[labels])print(Scores: , entry[scores])print(--------------------------)
输入结果如下图所示
3、detection_features_converter.py
创建2个HDF5文件对象h_train、h_val以写入模式打开从train_ids_file、val_ids_file文件图像id数据打开并加载其中保存的数据到train_imgids、val_imgids字典 train_indices 、val_indices存储图像ID到特征索引的映射关系创建训练集、验证集的图像特征、图像边框信息、空间特征信息的数据集train_counter、val_counter初始化为0打开tsv文件进行读取 从tsv文件中读取每一行并将其解析为字典FIELDNAMES a、进行一些数据类型转换 b、bboxes每行代表一个边界框每列左上角x坐标、左上角y坐标、右下角x坐标、右下角y坐标 box_width、box_height分别表示边界框宽、高度归一化后为scaled_width、scaled_height 左上角坐标归一化后为scaled_x、scaled_y c、将上述数据进行维度扩展便于拼接 d、生成空间特征归一化后的左上角x、y坐标 右下角x、y坐标、边界框宽度、高度 e、根据图像ID的归属是否在训练集或验证集中将处理后的数据存储到相应的HDF5中
以train_imgids为例
从train_imgids中移除当前的image_id将image_id映射到train_counter训练集当前图像索引train_indicetrain_img_bb的第train_counter行存储当前的边界框信息train_img_features存储图像特征信息train_spatial_img_features存储空间特征信息这些信息存储在HDF5文件中train_counter加一
最后将图像ID到索引的映射关系保存成文件train36_imgid2idx.pkl即可以通过该文件确定图像ID对应的特征信息存储在HDF5文件的第train_counter行。 读取一个预训练的bottom-up attention 特征的TSV文件并将其存储为HDF5格式。
同时将{图像ID特征索引}映射存储为一个pickle文件
HDF5文件的层次结构如下
{image_features:num_images * num_boxes *2048 的特征数组image_bb:num_images * num_boxes *4 的边界框数组
}
FIELDNAMES [image_id, image_w, image_h, num_boxes, boxes, features]
infile D:/bottom-up-attention-vqa-master/data/trainval_36/trainval_resnet101_faster_rcnn_genome_36.tsv
train_data_file D:/bottom-up-attention-vqa-master/data/train36.hdf5
val_data_file D:/bottom-up-attention-vqa-master/data/val36.hdf5
train_indices_file D:/bottom-up-attention-vqa-master/data/train36_imgid2idx.pkl
val_indices_file D:/bottom-up-attention-vqa-master/data/val36_imgid2idx.pkl
train_ids_file D:/bottom-up-attention-vqa-master/data/train_ids.pkl
val_ids_file D:/bottom-up-attention-vqa-master/data/val_ids.pklfeature_length 2048
num_fixed_boxes 36if __name__ __main__:#使用h5py库创建两个HDF5文件对象h_train、h_val并以写入模式打开这两个文件h_train h5py.File(train_data_file, w)h_val h5py.File(val_data_file, w)#检查文件系统中是否存在train_ids_file和val_ids_file此时已存在if os.path.exists(train_ids_file) and os.path.exists(val_ids_file):#若存在打开这两个文件并加载其中保存的数据到train_imgids、val_imgidstrain_imgids pickle.load(open(train_ids_file,rb))val_imgids pickle.load(open(val_ids_file,rb))else:#否则从相应目录中加载图像ID数据并保存到对应文件中train_imgids utils.load_imageid(data/train2014)val_imgids utils.load_imageid(data/val2014)pickle.dump(train_imgids, open(train_ids_file, wb))pickle.dump(val_imgids, open(val_ids_file, wb))#创建两个空字典用于存储图像ID到特征索引的映射关系train_indices {}val_indices {}#训练集图像特征、图像边框信息、空间特征信息的数据集train_img_features h_train.create_dataset(image_features, (len(train_imgids), num_fixed_boxes, feature_length), f)train_img_bb h_train.create_dataset(image_bb, (len(train_imgids), num_fixed_boxes, 4), f)train_spatial_img_features h_train.create_dataset(spatial_features, (len(train_imgids), num_fixed_boxes, 6), f)#验证集图像特征、图像边框信息、空间特征信息的数据集val_img_bb h_val.create_dataset(image_bb, (len(val_imgids), num_fixed_boxes, 4), f)val_img_features h_val.create_dataset(image_features, (len(val_imgids), num_fixed_boxes, feature_length), f)val_spatial_img_features h_val.create_dataset(spatial_features, (len(val_imgids), num_fixed_boxes, 6), f)train_counter 0val_counter 0print(reading tsv...)#打开tsv文件infile以进行读写with open(infile, r) as tsv_in_file:#从tsv文件中读取每一行并将其解析为一个字典字段名由FIELDNAMES定义reader csv.DictReader(tsv_in_file, delimiter\t, fieldnamesFIELDNAMES)#对每一行数据进行处理for item in reader:#转换数据类型item[num_boxes] int(item[num_boxes])image_id int(item[image_id])image_w float(item[image_w])image_h float(item[image_h])#从tsv文件中读取包含边界框信息的base64编码字符串中解析出一个二维float32类型的numpy数组bboxes# 行数为item[num_boxes]列数根据数据长度自动计算bboxes np.frombuffer(base64.decodebytes(item[boxes].encode()),dtypenp.float32).reshape((item[num_boxes], -1))#boxes是一个二维数组每行代表一个边界框每列包含该边界框的不同属性#列左上角x坐标、左上角y坐标、右下角x坐标、右下角y坐标#切片bboxes[:,2]表示选取所有行中的第3列数据即右下角x坐标box_width bboxes[:, 2] - bboxes[:, 0]#边界框宽度box_height bboxes[:, 3] - bboxes[:, 1]#边界框高度#宽度和高度的归一化scaled_width box_width / image_wscaled_height box_height / image_h#左上角点的x和y坐标想对于图像宽度和高度的归一化值scaled_x bboxes[:, 0] / image_wscaled_y bboxes[:, 1] / image_h#扩展维度(n)的一维数组转换为形状为(n,1)的二维数组box_width box_width[..., np.newaxis]box_height box_height[..., np.newaxis]scaled_width scaled_width[..., np.newaxis]scaled_height scaled_height[..., np.newaxis]scaled_x scaled_x[..., np.newaxis]scaled_y scaled_y[..., np.newaxis]#生成空间特征将下列数组沿着第二个轴列轴进行拼接#包含归一化的x坐标、y坐标、右下角x坐标、右下角y坐标、宽度和高度皆为用于描述边界框的空间特征spatial_features np.concatenate((scaled_x,scaled_y,scaled_x scaled_width,scaled_y scaled_height,scaled_width,scaled_height),axis1)#根据图像ID的归属是否在训练集或验证集中将处理后的数据存储到相应的HDF5数据集中if image_id in train_imgids:train_imgids.remove(image_id)#从train_imgids中移除当前的image_idtrain_indices[image_id] train_counter#将image_id映射到train_counter即训练集中当前图像的索引train_img_bb[train_counter, :, :] bboxes #将边界框信息存储到train_img_bb数据集的第train_counter行train_img_features[train_counter, :, :] np.frombuffer(base64.decodebytes(item[features].encode()),dtypenp.float32).reshape((item[num_boxes], -1))train_spatial_img_features[train_counter, :, :] spatial_featurestrain_counter 1elif image_id in val_imgids:val_imgids.remove(image_id)val_indices[image_id] val_counterval_img_bb[val_counter, :, :] bboxesval_img_features[val_counter, :, :] np.frombuffer(base64.decodebytes(item[features].encode()),dtypenp.float32).reshape((item[num_boxes], -1))val_spatial_img_features[val_counter, :, :] spatial_featuresval_counter 1else:assert False, Unknown image id: %d % image_idif len(train_imgids) ! 0:print(Warning: train_image_ids is not empty)if len(val_imgids) ! 0:print(Warning: val_image_ids is not empty)#将图像ID到索引的额映射关系保存到文件系统中pickle.dump(train_indices, open(train_indices_file, wb))pickle.dump(val_indices, open(val_indices_file, wb))h_train.close()h_val.close()print(done!)
4、main.py
parse_args()解析命令行参数epochs、num_hid、model、output、batch_size、seed设置pytorch的随机种子生成随机数、pytorch在cuda设备上的随机种子启动cudnn库的benchmark模式根据输入数据的大小、硬件性能等自动选择最优算法提高速度加载词典 dictionary构造VQAFeatureDataset并初始化train_dest、eval_deat使用给定数据集和隐藏层维度获取并实例化模型对象base_model-build_baseline0_newatt加载预训练词嵌入权重初始化文本嵌入器w_emb数据并行处理GPU加速创建用于训练、评估的数据加载器开始训练
#解析命令行参数
def parse_args():parser argparse.ArgumentParser()#创建命令行参数#添加命令行参数的定义parser.add_argument(--epochs, typeint, default30)parser.add_argument(--num_hid, typeint, default1024)parser.add_argument(--model, typestr, defaultbaseline0_newatt)parser.add_argument(--output, typestr, defaultD:/bottom-up-attention-vqa-master/saved_models/exp0)parser.add_argument(--batch_size, typeint, default512)parser.add_argument(--seed, typeint, default1111, helprandom seed)args parser.parse_args()return argsif __name__ __main__:args parse_args()#设置pytorch的随机种子用于生成随机数torch.manual_seed(args.seed)#设置pytorch在cuda设备上的随机种子torch.cuda.manual_seed(args.seed)#启动cudnn库的benchmark模式会根据输入数据的大小和硬件性能自动选择最优的算法来提高速度以加速深度学习计算torch.backends.cudnn.benchmark True#从文件中加载词典dictionary Dictionary.load_from_file(D:/bottom-up-attention-vqa-master/data/dictionary.pkl)#构造VQAFeatureDataset对象并初始化train_dset VQAFeatureDataset(train, dictionary)eval_dset VQAFeatureDataset(val, dictionary)batch_size args.batch_sizeconstructor build_%s % args.model#动态获取类并实例化得到的模型对象使用给定的数据集和隐藏层维度model getattr(base_model, constructor)(train_dset, args.num_hid).cuda()model.w_emb.init_embedding(D:/bottom-up-attention-vqa-master/data/glove6b_init_300d.npy)model nn.DataParallel(model).cuda()train_loader DataLoader(train_dset, batch_size, shuffleTrue, num_workers0)eval_loader DataLoader(eval_dset, batch_size, shuffleTrue, num_workers0)train(model, train_loader, eval_loader, args.epochs, args.output)
5、dataset.py
1Dictionary
构造函数 word2idx将词映射到索引字典idx2word将索引映射回词列表ntoken()返回词典中词的数量padding_idx()返回词典中词的数量用作特殊标记的索引表示填充索引从该位置开始tokenize() 将句子分词 小写 去除。将’s’替换为‘ ‘s’便于更好分离 使用空格分割句子得到单词列表 sentence -words tokens 空列表存储分词 根据add_wordTrue则将新词添加到词典中False将返回词的索引dump_to_file()将包含word2idx和idx2word的列表保存到文件中dictionary.pklload_from_file()从文件中加载词典add_word()将新单词添加到idx2word、word2idx
class Dictionary(object):#类的构造函数用于初始化词典def __init__(self, word2idxNone, idx2wordNone):#word2idx:将词映射到索引的字典#idx2word将索引映射回词的列表#如果没有提供这些参数默认是空字典和空列表if word2idx is None:word2idx {}if idx2word is None:idx2word []self.word2idx word2idxself.idx2word idx2wordproperty#返回词典中词的数量def ntoken(self):return len(self.word2idx)property#返回词典中特殊标记padding标记的索引#返回词典中词的数量用作特殊标记的索引表示新词的索引将从该位置开始def padding_idx(self):return len(self.word2idx)#将句子分词def tokenize(self, sentence, add_word):sentence sentence.lower()#将句子转换为小写#去除逗号、问号、将\s替换为 \s以便更好地分割s与其他单词sentence sentence.replace(,, ).replace(?, ).replace(\s, \s)words sentence.split()#使用空格分割句子得到一个单词的列表tokens [] #空列表存储分词#根据add_word选择将新词添加到词典中还是返回词的索引if add_word:for w in words:tokens.append(self.add_word(w))else:for w in words:tokens.append(self.word2idx[w])return tokens#将包含word2idx和idx2word的列表保存到文件中def dump_to_file(self, path):pickle.dump([self.word2idx, self.idx2word], open(path, wb))print(dictionary dumped to %s % path)classmethod#从文件中加载词典def load_from_file(cls, path):print(loading dictionary from %s % path)word2idx, idx2word pickle.load(open(path, rb))#创建新的词典示例并使用加载的映射关系进行初始化d cls(word2idx, idx2word)#cls特殊变量表示当前的类return d#返回新创建的词典实例#将新单词添加到idx2word、word2idx中def add_word(self, word):if word not in self.word2idx:self.idx2word.append(word)self.word2idx[word] len(self.idx2word) - 1return self.word2idx[word]def __len__(self):return len(self.idx2word)
2VQAFeatureDataset
a、构造函数
初始化ans2label、label2ans、num_ans_candidates答案候选数量、dictionary、img_id2idx、features、spatials、v_dim、s_dim调用load_dataset函数加载问题-答案的条目列表调用**tokenize()**分词调用**tensorize()**转换为tensor格式
#类的初始化方法def __init__(self, name, dictionary, datarootD:/bottom-up-attention-vqa-master/data):#通过super()调用父类的构造函数super(VQAFeatureDataset, self).__init__()#断言确保name的值在指定列表[train,val]中若不在将引发AssertionError异常assert name in [train, val]#构建文件路径ans2label_path os.path.join(dataroot, cache, trainval_ans2label.pkl)label2ans_path os.path.join(dataroot, cache, trainval_label2ans.pkl)#从文件中加载ans2label和label2ans的映射关系self.ans2label pickle.load(open(ans2label_path, rb))self.label2ans pickle.load(open(label2ans_path, rb))self.num_ans_candidates len(self.ans2label)#初始化答案候选数量即答案到标签映射关系中的标签数量self.dictionary dictionary#初始化img_id到idx的映射关系self.img_id2idx pickle.load(open(os.path.join(dataroot, %s36_imgid2idx.pkl % name),rb))print(loading features from h5 file)#构建HDF5文件路径h5_path os.path.join(dataroot, %s36.hdf5 % name)#使用h5py库打开HDF5文件并读取图像特征和空间特征with h5py.File(h5_path, r) as hf:self.features np.array(hf.get(image_features))self.spatials np.array(hf.get(spatial_features))#调用_load_dataset()函数加载问题-答案的条目列表self.entries _load_dataset(dataroot, name, self.img_id2idx)self.tokenize()#分词self.tensorize()#转换为tensor格式#获取图像特征的维度和空间特征的维度#索引2可以获取第三大维度的大小self.v_dim self.features.size(2)#2048self.s_dim self.spatials.size(2)#6b、load_dataset()
加载数据集中的问题和答案信息并生成一个包含特定字段的条目列表
加载问题JSON数据并按照question_id排序questions加载目标标签target并按question_id排序answers检查问题-答案数量是否相等entries 条目列表遍历questions、answers构建条目列表create_entry()
#加载数据集中的问题和答案信息并生成一个包含特定字段的条目列表
def _load_dataset(dataroot, name, img_id2val):加载条目img_id2val字典{img_id -val} val可用于检索图像或特征dataroot 数据集的根路径name: ’train,val#构建问题文件路径question_path os.path.join(dataroot, v2_OpenEnded_mscoco_%s2014_questions.json % name)#加载问题JSON数据并按照question_id进行排序questions sorted(json.load(open(question_path))[questions],keylambda x: x[question_id])#构建目标标签文件路径answer_path os.path.join(dataroot, cache, %s_target.pkl % name)#加载目标标签并按照question_id进行排序answers pickle.load(open(answer_path, rb))answers sorted(answers, keylambda x: x[question_id])#检查问题和答案的数量是否相等utils.assert_eq(len(questions), len(answers))#创建一个空的条目列表entries []#遍历问题和答案构建条目列表for question, answer in zip(questions, answers):#检查问题和答案的标识符是否匹配utils.assert_eq(question[question_id], answer[question_id])utils.assert_eq(question[image_id], answer[image_id])img_id question[image_id]#获取img_id#使用_create_entry函数创建一个条目并添加到条目列表中entries.append(_create_entry(img_id2val[img_id], question, answer))return entries
构建条目列表其中answer字段是target去除question_id、image_id即包含labels、scores
#创建包含特定字段的字典entry用于表示一个问题-回答对应的条目
def _create_entry(img, question, answer):#移除answer字典中image_id和question_id字段answer.pop(image_id)answer.pop(question_id)entry {question_id : question[question_id],image_id : question[image_id],image : img,question : question[question],answer : answer}return entry
c、tokenize()
#分词def tokenize(self, max_length14):Tokenizes the questions.This will add q_token in each entry of the dataset.-1 represent nil, and should be treated as padding_idx in embeddingfor entry in self.entries:#将问题进程分词不添加新词汇到词典中返回词的索引tokens self.dictionary.tokenize(entry[question], False)#根据指定最大长度max_length截断或填充分词列表tokens tokens[:max_length]if len(tokens) max_length:# Note here we pad in front of the sentencepadding [self.dictionary.padding_idx] * (max_length - len(tokens))tokens padding tokensutils.assert_eq(len(tokens), max_length)entry[q_token] tokens6、base_model.py #基础模型
class BaseModel(nn.Module):构造函数初始化接收6个参数w_emb文本嵌入器q_emb问题嵌入器v_att视觉注意力模块q_net问题网络v_net视觉网络classifier分类器def __init__(self, w_emb, q_emb, v_att, q_net, v_net, classifier):super(BaseModel, self).__init__()self.w_emb w_embself.q_emb q_embself.v_att v_attself.q_net q_netself.v_net v_netself.classifier classifierdef forward(self, v, b, q, labels):Forwardv: [batch, num_objs, obj_dim]视觉特征b: [batch, num_objs, b_dim]边界框特征q: [batch_size, seq_length]问题特征return: logits, not probsq torch.tensor(q).to(torch.int64)w_emb self.w_emb(q)#将问题特征q转换为文本嵌入q_emb self.q_emb(w_emb) # [batch, q_dim]通过问题嵌入去将文本嵌入转换为问题嵌入att self.v_att(v, q_emb)#计算视觉特征v关于问题嵌入q_emb的注意力权重att#将注意力权重与视觉特征逐元素相乘每个对象的加权视觉特征进行求和得到视觉嵌入v_emb (att * v).sum(1) # [batch, v_dim]q_repr self.q_net(q_emb)#使用问题网络对问题嵌入转换为问题表示v_repr self.v_net(v_emb)joint_repr q_repr * v_repr#对问题表示和视觉表示逐元素相乘得到联合表示logits self.classifier(joint_repr)return logitsdef build_baseline0(dataset, num_hid):w_emb WordEmbedding(dataset.dictionary.ntoken, 300, 0.0)q_emb QuestionEmbedding(300, num_hid, 1, False, 0.0)v_att Attention(dataset.v_dim, q_emb.num_hid, num_hid)q_net FCNet([num_hid, num_hid])v_net FCNet([dataset.v_dim, num_hid])classifier SimpleClassifier(num_hid, 2 * num_hid, dataset.num_ans_candidates, 0.5)return BaseModel(w_emb, q_emb, v_att, q_net, v_net, classifier)#基准模型
def build_baseline0_newatt(dataset, num_hid):#词嵌入层初始化将单词映射到300维的嵌入空间w_emb WordEmbedding(dataset.dictionary.ntoken, 300, 0.0)#问题嵌入层初始化q_emb QuestionEmbedding(300, num_hid, 1, False, 0.0)#注意力机制初始化v_att NewAttention(dataset.v_dim, q_emb.num_hid, num_hid)#问题网络初始化将问题特征映射到num_hid维隐藏空间q_net FCNet([q_emb.num_hid, num_hid])# 视觉网络初始化将视觉特征映射到num_hid维隐藏空间v_net FCNet([dataset.v_dim, num_hid])#分类器初始化classifier SimpleClassifier(num_hid, num_hid * 2, dataset.num_ans_candidates, 0.5)return BaseModel(w_emb, q_emb, v_att, q_net, v_net, classifier)
7、language_model.py #词嵌入层WoedEmbedding类继承自nn.Module
class WordEmbedding(nn.Module):Word EmbeddingThe ntoken-th dim is used for padding_idx, which agrees *implicitly*with the definition in Dictionary.词嵌入第ntoken维度用于填充索引(padding_idx),这与字典Dictionary中的定义隐含一致#构造函数ntoken:词汇表大小、emb_dim:词嵌入维度、dropout:dropout层的概率def __init__(self, ntoken, emb_dim, dropout):super(WordEmbedding, self).__init__()#调用父类nn.Module的构造函数#创建Embedding层将输入整数序列转换为词嵌入表示#标记为padding_idx的位置被视为填充标记self.emb nn.Embedding(ntoken1, emb_dim, padding_idxntoken)#创建dropout层用于在训练过程中随机丢弃部分神经元以防止过拟合self.dropout nn.Dropout(dropout)self.ntoken ntoken#将输入词汇表的大小存储为类的属性self.emb_dim emb_dim#将输入的词嵌入维度存储为类的属性#初始化词嵌入层的权重def init_embedding(self, np_file):#从numpy文件加载预训练的词嵌入权重并将其转换为pytorch张量weight_init torch.from_numpy(np.load(np_file))#断言确保加载的权重与词汇表大小和词嵌入维度匹配assert weight_init.shape (self.ntoken, self.emb_dim)#将加载的权重赋值给词嵌入层的权重只覆盖词汇表大小范围内的部分#data[:self.notoken]:切片操作取列表data的前self.ntoken个元素self.emb.weight.data[:self.ntoken] weight_init#定义前向传播方法接收输入x并返回词嵌入表示def forward(self, x):#x torch.tensor(x).to(torch.int64)emb self.emb(x)#将输入x传递给词嵌入层获取词嵌入表示emb self.dropout(emb)#对词嵌入表示应用dropout操作随机丢弃部分神经元return emb#QuestionEmbedding类继续自nn.Module
#将输入的问题序列进行嵌入可以选择性地返回整个序列或仅仅返回最后一个时间步的嵌入捕捉序列信息
class QuestionEmbedding(nn.Module):#构造函数初始化接收系列参数如下in_dim输入维度num_hid隐藏单元数量nlayers层数bidirect是否双向dropoutDropout层的概率rnn_typeRNN类型默认为GRUdef __init__(self, in_dim, num_hid, nlayers, bidirect, dropout, rnn_typeGRU):Module for question embeddingsuper(QuestionEmbedding, self).__init__()#调用父类构造函数#断言确保RNN类型为LSTM或GRU避免输入错误的RNN类型assert rnn_type LSTM or rnn_type GRUrnn_cls nn.LSTM if rnn_type LSTM else nn.GRU#根据指定的RNN类型选择相应的Pytorch RNN类#创建RNN层根据构造函数中的参数配置#batch_firstTrue表示输入的第一个维度是批次大小self.rnn rnn_cls(in_dim, num_hid, nlayers,bidirectionalbidirect,dropoutdropout,batch_firstTrue)self.in_dim in_dimself.num_hid num_hidself.nlayers nlayersself.rnn_type rnn_typeself.ndirections 1 int(bidirect)#是否双方设置方向的数量#初始化RNN的隐藏状态接收一个参数batch表示批次大小def init_hidden(self, batch):# just to get the type of tensor#self.parameters()返回模型中所有参数的迭代器#next()获取这个迭代器的下一个元素即模型的第一个参数#.data属性用于访问参数的底层数据即包含实际权重值的张量#weight next(self.parameters()).data#获取模型的第一个参数权重并访问其底层数据tensor返回模型参数的数据部分weight 0weight torch.tensor(weight,dtypetorch.float32)weight weight.cuda()#计算RNN中隐藏状态的形状#三元组层数*方向数批次大小隐藏单元数量hid_shape (self.nlayers * self.ndirections, batch, self.num_hid)if self.rnn_type LSTM:#若是LSTM模型返回包含LSTM的隐藏状态和细胞状态零初始化#Variable包装张量#weight.new(*hid_shape)使用模型参数权重通过new创建与模型参数相同类型和设备的新张量形状为hid_shape,_zero()表示将所有元素都设置为零return (Variable(weight.new(*hid_shape).zero_()),Variable(weight.new(*hid_shape).zero_()))else:#GRU模型返回GRU的隐藏状态return Variable(weight.new(*hid_shape).zero_())#前向传播def forward(self, x):# x: [batch, sequence, in_dim]batch x.size(0)#获取输入的批次大小hidden self.init_hidden(batch)#初始化RNN的隐藏状态self.rnn.flatten_parameters()#将参数展平#将输入序列x和隐藏状态传递给RNN获取输出和更新后的隐藏状态output, hidden self.rnn(x, hidden)#单向RNN返回最后一个时间步的输出if self.ndirections 1:return output[:, -1]#双向RNNforward_ output[:, -1, :self.num_hid]#获取最后一个时间步的前向部分的输出backward output[:, 0, self.num_hid:]#获取第一个时间步的后向部分的输出return torch.cat((forward_, backward), dim1)#将前向和后向的输出在维度1上进行连接并返回结果#返回所有时间步的输出def forward_all(self, x):# x: [batch, sequence, in_dim]batch x.size(0)hidden self.init_hidden(batch)self.rnn.flatten_parameters()output, hidden self.rnn(x, hidden)return output8、attention.py
#注意力机制
class Attention(nn.Module):构造函数初始化,接收以下三个参数v_dim视觉特征维度q_dim问题特征维度num_hid隐藏层维度def __init__(self, v_dim, q_dim, num_hid):super(Attention, self).__init__()#调用父类nn.Module的构造函数#创建全连接网络FCNet用于处理拼接视觉特征和问题特征的输入self.nonlinear FCNet([v_dim q_dim, num_hid])#指定输入和输出的维度#创建一个带有权重标准化的线性层用于将处理后的输入映射为注意力权重#nn.Linear(num_hid,1)指定输入和输出的维度#dimNone表示对所有权重进行标准化self.linear weight_norm(nn.Linear(num_hid, 1), dimNone)#前向传播方法接收视觉特征v和问题特征q并返回注意力权重def forward(self, v, q):v: [batch, k, vdim]q: [batch, qdim]logits self.logits(v, q)#调用logits方法计算未经softmax处理的注意力得分w nn.functional.softmax(logits, 1)#对得分进行归一化得到最终的注意力权重return w#计算注意力得分def logits(self, v, q):num_objs v.size(1)#获取视觉特征中对象的数量#q.unsqueeze(1)在张量q的第一维上增加一个维度[batch,1,q_dim]便于与v进行拼接操作#repeat(1,num_objs,1)对张量进行复制变为[batch,num_objs,q_dim]q q.unsqueeze(1).repeat(1, num_objs, 1)vq torch.cat((v, q), 2)#在第3维上进行拼接将视觉特征和问题特征合并在一起[batch,num_objs,q_dimv_dim]joint_repr self.nonlinear(vq)#将拼接后的特征输入到全连接网络中进行处理得到联合表示logits self.linear(joint_repr)#将联合表示输入到线性层中得到注意力得分未经softmax处理return logits#新的注意力机制
class NewAttention(nn.Module):构造函数初始化接收4个参数v_dim视觉特征维度q_dim问题特征维度num_hid隐藏层的维度dropoutDropout层的概率默认为0.2def __init__(self, v_dim, q_dim, num_hid, dropout0.2):super(NewAttention, self).__init__()#调用父类nn.Module的构造函数self.v_proj FCNet([v_dim, num_hid])#全连接层处理视觉特征投影到隐藏层的维度self.q_proj FCNet([q_dim, num_hid])#全连接层处理问题特征投影到隐藏层的维度self.dropout nn.Dropout(dropout)#dropout层在训练时随机丢失部分神经元以防止过拟合#创建一个带有权重标准化的线性层用于将处理后的问题特征映射为注意力得分标量self.linear weight_norm(nn.Linear(q_dim, 1), dimNone)def forward(self, v, q):v: [batch, k, vdim]q: [batch, qdim]logits self.logits(v, q) #计算注意力得分w nn.functional.softmax(logits, 1) #使用softmax函数将注意力得分转换为注意力权重得到归一化的权重分布return w #返回注意力权重#计算注意力得分的函数接受视觉特征v和问题特征qdef logits(self, v, q):batch, k, _ v.size()v_proj self.v_proj(v) # v_dim -num_hid -[batch,k,num_hid] #将视觉特征投影到隐藏层的维度q_proj self.q_proj(q).unsqueeze(1).repeat(1, k, 1) #q_dim - num_dim -[batch,k,num_hid]便于和视觉特征对齐joint_repr v_proj * q_proj #逐元素乘法点乘joint_repr self.dropout(joint_repr)logits self.linear(joint_repr) #得注意力得分return logits
8、fc.py
#非线性全连接网络FCNet用于处理输入数据
class FCNet(nn.Module):Simple class for non-linear fully connect network#构造函数接受参数dimsdef __init__(self, dims):#调用父类构造函数super(FCNet, self).__init__()layers []#创建一个空列表用于存储网络的层#遍历输入维度和输出维度列表创建线性层和激活函数的序列for i in range(len(dims)-2):in_dim dims[i]#获取当前层的输入维度out_dim dims[i1]#获取当前层的输出维度#添加带有权重归一化的线性层layers.append(weight_norm(nn.Linear(in_dim, out_dim), dimNone))#添加ReLU激活函数layers.append(nn.ReLU(inplaceFalse))#添加输出层的线性层同样使用权重归一化layers.append(weight_norm(nn.Linear(dims[-2], dims[-1]), dimNone))layers.append(nn.ReLU(inplaceFalse))#添加输出层的ReLU激活函数#将层序列封装成nn.Sequential对象并赋值给类的main属性main序列中定义了整个网络的前向传播过程self.main nn.Sequential(*layers)#定义前向传播函数接收x并返回网络的输出def forward(self, x):#将输入x通过网络的前向传播得到输出return self.main(x)if __name__ __main__:fc1 FCNet([10, 20, 10])print(fc1)print()fc2 FCNet([10, 20])print(fc2) 10、classifier.py #简单分类器模型
class SimpleClassifier(nn.Module):def __init__(self, in_dim, hid_dim, out_dim, dropout):super(SimpleClassifier, self).__init__()layers [weight_norm(nn.Linear(in_dim, hid_dim), dimNone),#线性层全连接层权重标准化nn.ReLU(inplaceFalse),#ReLU激活函数nn.Dropout(dropout, inplaceFalse),#inplace表示原地操作修改原始输入张量weight_norm(nn.Linear(hid_dim, out_dim), dimNone)]#创建一个序列容器将之前定义的层按顺序组合起来形成整个模型self.main nn.Sequential(*layers)def forward(self, x):logits self.main(x)return logits
11、train.py
def instance_bce_with_logits(logits, labels):#断言确保输入的logits张量是二维的#若logits的维度是2则程序继续执行否则触发AssertionError异常中断程序执行assert logits.dim() 2#计算二分类交叉熵损失loss nn.functional.binary_cross_entropy_with_logits(logits, labels)loss loss * labels.size(1)#将损失乘以真实标签的维度通常是类别的数量将损失值按照每个样本的平均损失进行缩放return loss#计算分类模型得分
def compute_score_with_logits(logits, labels):#torch.max(logits,1)选择每行的最大值返回的元组中的第一个元素是最大值第二个元素的最大值对应的索引#[1]取得索引.data取得数据的张量部分logits torch.max(logits, 1)[1].data # argmax 找到预测的类别#创建一个与labels大小相同的全零张量移动到GPU若可用用于存储独热编码one_hots torch.zeros(*labels.size()).cuda()#logits.view(-1,1)将logits张量变形为一个列向量列数为1-1表示自动推断该维度大小#维度索引为1的指定位置赋值为1#创建一个独热编码只有预测类别对应的位置上的值为1其他位置都为0new_one_hots torch.scatter(one_hots, 1, logits.view(-1, 1), 1)scores (new_one_hots * labels)#按元素相乘只有对应正确类别的位置上的值保留其他位置都是0return scoresdef train(model, train_loader, eval_loader, num_epochs, output):utils.create_dir(output)#创建一个由output指定的目录optim torch.optim.Adamax(model.parameters())#创建Adamax优化器logger utils.Logger(os.path.join(output, log.txt))#创建日志记录器对象best_eval_score 0 #模型在验证集上的某个性能的最佳值#开始训练循环循环次数为num_epochsfor epoch in range(num_epochs):#初始化训练过程中的总损失和总分数total_loss 0train_score 0t time.time()#获取当前的时间戳#遍历训练数据集中的每个batchfor i, (v, b, q, a) in enumerate(train_loader):v Variable(v).cuda()b Variable(b).cuda()q Variable(q).cuda()a Variable(a).cuda()#v v.float()#q q.float()pred model(v, b, q, a)#得到模型的预测loss instance_bce_with_logits(pred, a)#计算二分类交叉熵损失loss.backward() # 反向传播计算梯度nn.utils.clip_grad_norm(model.parameters(), 0.25) # 对梯度进行裁剪防止梯度爆炸所有参数的梯度的L2范数指定阈值0.25optim.step() # 执行一步优化更新模型参数optim.zero_grad() # 清零梯度为下一个batch的梯度计算做准备batch_score compute_score_with_logits(pred, a.data).sum()#计算当前batch的得分#loss.data[0]获取当前batch损失值v.size(0)获取当前batch样本数total_loss total_loss loss.item() * v.size(0)train_score train_score batch_scoretotal_loss total_loss / len(train_loader.dataset)#整个训练集上的平均损失train_score 100 * train_score / len(train_loader.dataset)model.train(False)#评估模式eval_score, bound evaluate(model, eval_loader)model.train(True)#训练模式#记录训练和评估过程中的一些信息写入日志文件中logger.write(epoch %d, time: %.2f % (epoch, time.time()-t))logger.write(\ttrain_loss: %.2f, score: %.2f % (total_loss, train_score))logger.write(\teval score: %.2f (%.2f) % (100 * eval_score, 100 * bound))#更新最佳得分并保存模型参数if eval_score best_eval_score:model_path os.path.join(output, model.pth)torch.save(model.state_dict(), model_path)best_eval_score eval_scoredef evaluate(model, dataloader):score 0 #评分upper_bound 0 #最大可能评分num_data 0 #处理的数据样本总数#遍历数据加载器中的每个batchfor v, b, q, a in iter(dataloader):#将v、b、q数据转换为pytorch变量并移动到GPU上v Variable(v, volatileTrue).cuda()b Variable(b, volatileTrue).cuda()q Variable(q, volatileTrue).cuda()pred model(v, b, q, None)#使用模型进行预测无需提供真实标签batch_score compute_score_with_logits(pred, a.cuda()).sum()#计算当前batch的得分score score batch_score #累计分数#a.max(1)计算第一维度上的最大值返回元组包含两个张量第一个是每个样本的最大值第二个是每个最大值的索引# [0].sum()表示对最大值进行求和upper_bound upper_bound (a.max(1)[0]).sum() #累计最大可能评分num_data num_data pred.size(0)#累计当前的样本数score score / len(dataloader.dataset) #平均值upper_bound upper_bound / len(dataloader.dataset) #最大可能得分平均值return score, upper_bound
12、最终复现结果
epoch 0, time: 269.13train_loss: 9.98, score: 39.04eval score: 50.29 (92.66)
epoch 1, time: 265.49train_loss: 3.96, score: 52.18eval score: 55.46 (92.66)
epoch 2, time: 267.10train_loss: 3.61, score: 56.99eval score: 58.44 (92.66)
epoch 3, time: 266.44train_loss: 3.39, score: 60.05eval score: 59.83 (92.66)
epoch 4, time: 256.31train_loss: 3.24, score: 62.45eval score: 60.87 (92.66)
epoch 5, time: 255.20train_loss: 3.11, score: 64.41eval score: 61.67 (92.66)
epoch 6, time: 254.13train_loss: 3.00, score: 66.10eval score: 62.00 (92.66)
epoch 7, time: 255.00train_loss: 2.91, score: 67.69eval score: 62.59 (92.66)
epoch 8, time: 254.41train_loss: 2.82, score: 69.16eval score: 62.96 (92.66)
epoch 9, time: 255.63train_loss: 2.74, score: 70.57eval score: 62.94 (92.66)
epoch 10, time: 254.48train_loss: 2.67, score: 71.77eval score: 63.12 (92.66)
epoch 11, time: 255.29train_loss: 2.60, score: 73.00eval score: 63.30 (92.66)
epoch 12, time: 256.36train_loss: 2.54, score: 74.11eval score: 63.26 (92.66)
epoch 13, time: 255.14train_loss: 2.48, score: 75.12eval score: 63.37 (92.66)
epoch 14, time: 255.62train_loss: 2.42, score: 76.04eval score: 63.38 (92.66)
epoch 15, time: 255.27train_loss: 2.37, score: 76.91eval score: 63.45 (92.66)
epoch 16, time: 255.21train_loss: 2.32, score: 77.71eval score: 63.40 (92.66)
epoch 17, time: 255.82train_loss: 2.28, score: 78.40eval score: 63.38 (92.66)
epoch 18, time: 255.40train_loss: 2.24, score: 79.02eval score: 63.34 (92.66)
epoch 19, time: 254.87train_loss: 2.20, score: 79.57eval score: 63.29 (92.66)
epoch 20, time: 255.41train_loss: 2.16, score: 80.13eval score: 63.23 (92.66)
epoch 21, time: 255.24train_loss: 2.13, score: 80.62eval score: 63.29 (92.66)
epoch 22, time: 255.93train_loss: 2.09, score: 81.14eval score: 63.19 (92.66)
epoch 23, time: 255.59train_loss: 2.06, score: 81.58eval score: 63.12 (92.66)
epoch 24, time: 254.89train_loss: 2.03, score: 81.94eval score: 63.28 (92.66)
epoch 25, time: 256.18train_loss: 2.00, score: 82.31eval score: 63.25 (92.66)
epoch 26, time: 256.01train_loss: 1.98, score: 82.73eval score: 63.20 (92.66)
epoch 27, time: 255.60train_loss: 1.95, score: 83.09eval score: 63.09 (92.66)
epoch 28, time: 255.98train_loss: 1.93, score: 83.35eval score: 63.19 (92.66)
epoch 29, time: 255.50train_loss: 1.90, score: 83.69eval score: 63.16 (92.66)