def generate_trained_model_path(trained_model_dir: str, trained_model_name: str): """ 传入模型保存文件夹,和训练后模型名称,按照日期生成模型保存文件夹,并生成模型保存路径,返回模型保存路径 :param trained_model_dir: (str)模型保存文件夹 :param trained_model_name: (str)模型名称 :return: trained_model_path(str)模型保存路径 :raise:ValueError 文件夹不存在或者模型命名不以.h5结尾 """ # 判断保存训练后模型的文主文件夹是否存在,不存在则报错 if not os.path.exists(trained_model_dir) or not os.path.isdir( trained_model_dir): logger.error(f"{trained_model_dir} is wrong!") raise ValueError # 模型不以.h5结尾则报错 if not trained_model_name.endswith(".h5"): logger.error(f"{trained_model_name} must end with .h5") raise ValueError # 按照当前时间年月日生成文件夹,构建训练后模型的保存位置 trained_model_dir = cat_path( trained_model_dir, time.strftime('%Y-%m-%d', time.localtime(time.time()))) trained_model_path = cat_path(trained_model_dir, trained_model_name) # 如果文件夹不存在则生成 if not os.path.exists(trained_model_dir): os.mkdir(trained_model_dir) return trained_model_path
def load_rnn_models(model_dir): """ 加载模型文件目录下的模型 Args: model_dir: 模型文件所在目录 Returns: 模型的 encoder, decoder """ encoder = load_model(cat_path(model_dir, 'encoder.h5'), compile=False) decoder = load_model(cat_path(model_dir, 'decoder.h5'), compile=False) return encoder, decoder
def on_epoch_end(self, epoch, logs=None): """ 选择所有训练次数中,f1最大时的模型 :param epoch: 训练次数 return None """ score_summary = self.evaluate() if score_summary > self.best: logger.info(f'{score_summary} better than old: {self.best}') self.best = score_summary encoder_path = cat_path(self.model_dir, 'encoder.h5') decoder_path = cat_path(self.model_dir, 'decoder.h5') self.encoder.save(encoder_path) self.decoder.save(decoder_path)
def test_extract_model_train(): """ 调用事件抽取模块训练函数,测试训练流程是否成功 :return: status """ # 训练后模型路径 trained_model_path = cat_path(extract_train_config.trained_model_dir, f"extract_model_{0}_{0}") # 模型训练 model_train(version="0", model_id="0", all_steps=10, trained_model_dir=extract_train_config.trained_model_dir, data_dir=extract_train_config.supplement_data_dir, maxlen=extract_train_config.maxlen, epoch=1, batch_size=extract_train_config.batch_size, max_learning_rate=extract_train_config.learning_rate, min_learning_rate=extract_train_config.min_learning_rate, model_type="roberta") # 判断是否有模型生成 assert os.path.exists(trained_model_path) == True # 将生成的模型删除 os.remove(trained_model_path) logger.info(f"event_extract_train demo is OK!") return {"status": "success"}
def get_data(train_data_path: str, dev_data_path: str, supplement_data_dir: str): """ 传入训练集、验证集、补充数据路径,读取并解析json数据,将补充数据补充到训练集中,返回解析后的数据 :param train_data_path:(str)训练集路径 :param dev_data_path:(str)验证集路径 :param supplement_data_dir:(str)补充数据保存路径 :return:train_data(list), dev_data(list) """ if not valid_file(train_data_path): logger.error(f"训练数据{train_data_path} 路径错误!") elif not valid_file(dev_data_path): logger.error(f"验证数据{dev_data_path} 路径错误!") elif not os.path.exists(supplement_data_dir) or os.path.isfile( supplement_data_dir): logger.error(f"补充数据集{supplement_data_dir} 文件夹路径错误!") # 加载训练数据集 train_data = read_json(train_data_path) # 加载验证集 dev_data = read_json(dev_data_path) # 加载补充数据 file_list = os.listdir(supplement_data_dir) supplement_data = [] for file in file_list: supplement_data_path = cat_path(supplement_data_dir, file) supplement_data.extend(read_json(supplement_data_path)) train_data.extend(supplement_data) return train_data, dev_data
def load_vec_data(cameo: str): """ 传入事件cameo号,到cameo号对应的列表中加载所有事件短句向量 :param cameo:(str)事件cameo号 :return:data(dict){事件id:向量} :raise:TypeError FileNotFoundError """ # 需要读取向量的文件路径 read_file_path = cat_path(pre_config.vec_data_dir, f"{cameo}.npy") data = {} # 判断文件是否存在,不存在则返回空值 if not os.path.exists(read_file_path): return data # 如果字典文件缺失则报错 elif not os.path.exists(pre_config.cameo2id_path): logger.error("cameo映射事件向量的字典文件缺失!") raise FileNotFoundError else: # cameo2id 字典 {cameo:[]} cameo2id = comm.read_cameo2id(pre_config.cameo2id_path) # 读取文件中的向量 x = comm.read_np(read_file_path) for key, value in zip(cameo2id[cameo], x): data[key] = value return data
def __init__(self, mode, data, vec_data, vector_id_dict_dir, batch_size, shuffle=True): """ 构造方法,传入类的实例变量 :param mode: (str) 数据模式 train dev test :param data: (list) 传入的数据列表 :param vec_data: (dict) 所有的{id:[vec]} :param vector_id_dict_dir: (str) 向量id保存字典文件夹 :param batch_size: (int)批量大小 :param shuffle: (bool)是否打乱 """ self.mode = mode self.data = data self.vec_data = vec_data self.vector_id_dict_dir = vector_id_dict_dir self.batch_size = batch_size self.shuffle = shuffle # 数据的步数 self.steps = len(self.data) // self.batch_size if len(self.data) % self.batch_size != 0: self.steps += 1 # 保存数据对应id字典的文件名称 dict_name = f"{self.mode}_dict.json" # 字典保存路径 dict_path = cat_path(vector_id_dict_dir, dict_name) # 获取字典 with open(dict_path, "r", encoding="utf-8") as f: self.data_dict = f.read() self.data_dict = json.loads(self.data_dict)
def test_model(version, model_id, trained_model_dir="", data_dir=extract_train_config.supplement_data_dir, maxlen=160): """ 进行模型训练的主函数,搭建模型,加载模型数据,根据传入的参数进行模型测试,测试模型是否达标 :param data_dir: 补充数据的文件夹路径 :param trained_model_dir: 训练后模型存放路径 :param maxlen: 最大长度 :param version: 模型版本号 :param model_id: 模型id :return: status, F1, precision, recall, corpus_num """ # 训练后模型路径 if version and model_id: trained_model_path = cat_path(trained_model_dir, f"extract_model_{version}_{model_id}") else: trained_model_path = extract_pred_config.event_extract_model_path # 获取训练集、验证集 train_data, dev_data = get_data(extract_train_config.train_data_path, extract_train_config.dev_data_path, data_dir) # 搭建模型 trigger_model, object_model, subject_model, loc_model, time_model, negative_model, train_model = build_model( ) with SESS.as_default(): with SESS.graph.as_default(): # 构造callback模块的评估类 evaluator = Evaluate(dev_data, maxlen, trained_model_path, trigger_model, object_model, subject_model, loc_model, time_model, negative_model, train_model) # 重载模型参数 train_model.load_weights(trained_model_path) # 将验证集预测结果保存到文件中,暂时注释掉 f1, precision, recall = evaluator.evaluate() assert f1 >= 0.8 assert precision >= 0.8 assert recall >= 0.8 logger.info(f"f1:{f1}, precision:{precision}, recall:{recall}") logger.info(f"model is OK!") return { "status": "success", "version": version, "model_id": model_id, "results": { "f1": f1, "precison": precision, "recall": recall } }
def gen_relation(relation, articles_dir, use_db=False): articles_content = get_articles_content(articles_dir, use_db) rst_articles_pos, rst_articles_neg = extract_articles_relation( articles_content, relation, 10000) relation_name = relation.__name__.split('.')[-1] ner_pos_fp = cat_path(ALGOR_PRETRAIN_ROOT, 'relation_key_extract', f'ner_{relation_name}_pos.txt') ner_neg_fp = cat_path(ALGOR_PRETRAIN_ROOT, 'relation_key_extract', f'ner_{relation_name}_neg.txt') save_ner_data(rst_articles_pos, rst_articles_neg, 1000, ner_pos_fp, ner_neg_fp) classify_pos_fp = cat_path(ALGOR_PRETRAIN_ROOT, 'relation_extract', f'classify_{relation_name}_pos.txt') classify_neg_fp = cat_path(ALGOR_PRETRAIN_ROOT, 'relation_extract', f'classify_{relation_name}_neg.txt') save_classify_data(rst_articles_pos, rst_articles_neg, 1000, classify_pos_fp, classify_neg_fp)
def execute(raw_dir, target_dir): """ 传入原始标注数据文件夹路径和解析后文件存放的路径,按照时间生成json文件名称,将解析好的数据保存到目标文件夹 :param raw_dir: 存放原始标注数据的文件夹 :param target_dir: 存放解析后数据的文件夹 :return: status--解析状态, corpus_num--数据量 """ # 存放所有解析后的数据 all_datas = [] # 语料中的句子数量 all_sentence_num = 0 # 语料中的事件数量 all_event_num = 0 try: # 判断数据路径是否正确 if valid_dir(raw_dir): # 判断目标文件夹路径是否存在,不存在则创建 if not valid_dir(target_dir): os.makedirs(target_dir) file_name = f"{time.strftime('%Y-%m-%d', time.localtime(time.time()))}.json" target_file_path = cat_path(target_dir, file_name) # 获取文件夹下所有文件的名称 file_names = os.listdir(raw_dir) file_names = list( set(file_name.split(".")[0] for file_name in file_names)) # 遍历文件进行解析 for file_name in tqdm(file_names): file_path = os.path.join(raw_dir, file_name) # 判断两个文件是否都同时存在 if valid_file(f"{file_path}.ann") and valid_file( f"{file_path}.txt"): # 解析文件获取事件和文件中的句子以及事件数量 data, sentence_num, event_num = data_process(file_path) all_datas.extend(data) all_sentence_num += sentence_num all_event_num += event_num logger.info(f"总共有句子:{all_sentence_num},总共有事件:{all_event_num}") # 将解析后的数据保存到目标文件 save_json(all_datas, target_file_path) return { "status": "success", "results": { "sentences": all_sentence_num, "events": all_event_num } } else: logger.error(f"存放原始标注数据的文件夹:{raw_dir}没有找到") raise FileNotFoundError except: trace = traceback.format_exc() logger.error(trace) return {"status": "failed", "results": trace}
def load_cnn_model(model_dir): """ 加载模型文件目录下的模型 Args: model_dir: 模型文件所在目录 Returns: 模型的 encoder, decoder """ model = load_model(cat_path(model_dir, 'cnn_model.h5'), compile=False) return model
def on_epoch_end(self, epoch, logs=None): """ 选择所有训练次数中,f1最大时的模型 :param epoch: 训练次数 return None """ score_summary = self.evaluate() if score_summary > self.best: logger.info(f'{score_summary} better than old: {self.best}') self.best = score_summary model_path = cat_path(self.model_dir, 'cnn_model.h5') self.model.save(model_path, include_optimizer=True)
def train(batch_size, epochs, latent_dim, array_x, array_y, array_yin, model_sub_dir): """ 训练模型并保存模件 Args: array_x: encoder 的输入序列 array_y: deocder 的输出序列 array_yin: decoder 的 inference 序列 model_sub_dir: 存放此次训练所生成的模型的目录 """ # x的shape是[样本数, encoder输入长度(即滞后期), 特征数] # y的shape是[样本数, decoder输出长度(即预测天数), 输出事件类别个数] n_input = array_x.shape[2] n_output = array_y.shape[2] model, encoder, decoder = build_models(latent_dim, n_input, n_output) model.fit([array_x, array_yin], array_y, batch_size=batch_size, epochs=epochs, verbose=2) encoder_path = cat_path(model_sub_dir, 'encoder.h5') decoder_path = cat_path(model_sub_dir, 'decoder.h5') encoder.save(encoder_path) decoder.save(decoder_path)
def load_vec_data(vector_data_dir: str): """ 传入向量数据保存的文件夹,加载所有的向量数据到内存中。 :param vector_data_dir: (str)向量数据保存的文件夹 :return: vec_data(dict)向量数据字典 :raise:ValueError 保存向量的文件夹路径不是文件夹或者不存在 """ if not os.path.exists(vector_data_dir) or os.path.isfile(vector_data_dir): logger.error(f"{vector_data_dir}向量保存文件夹错误!") raise ValueError # 将所有向量加载到内存中 vec_data = {} file_list = os.listdir(vector_data_dir) for file in file_list: vec_data["{}".format(file)] = np.load(cat_path(vector_data_dir, file)) return vec_data
def apply_pca(pca_n, pca_dir, data, reload=False): """ 对输入数据进行pca降维 Args: pca_n: 降维后维度 pca_dir: pca模型所在目录 data: 数据表数据 reload: 是否加载已存在的pca模型文件 Returns: 降维操作后的数据 """ pca_fp = cat_path(pca_dir, f"{pca_n}fit_pca") if reload: data_pca = joblib.load(pca_fp).transform(data) else: pca = PCA(n_components=pca_n) data_pca = pca.fit_transform(data) joblib.dump(pca, pca_fp) return data_pca
def measure(): """ 调用相似度模型,对测试集数据进行相似度计算,返回测试集相似度计算结果。 :return: test_model_pred(lsit)测试集相似度列表 """ # 测试集模式 mode = "dev" # 测试集数据保存字典 dict_name = f"{mode}_dict.json" dict_path = cat_path(search_config.vector_id_dict_dir, dict_name) # 加载字典 with open(dict_path, "r", encoding="utf-8") as f: dev_dict = f.read() dev_dict = json.loads(dev_dict) test_model_pred = [] for i in range(len(DEV_DATA)): x1 = DEV_DATA[i].split("\t")[0] x2 = DEV_DATA[i].split("\t")[1] # 测试数据在字典中的键,与保存是规则相同 key = str(i // 10000) # 获取测试数据在字典列表中的下标,用于到向量列表中去索引对应的句子向量 x1_id = dev_dict[key].index(x1) x2_id = dev_dict[key].index(x2) # 验证集向量保存位置 file_name = f"{mode}_{key}.npy" # 获取句子向量 x1 = VECTOR_DATA[file_name][x1_id] x2 = VECTOR_DATA[file_name][x2_id] # 计算两个句子的相似度[[0.1,0.9]] x = MATCH_MODEL.predict([np.array([x1]), np.array([x2])]) # 将相似度保存到列表中 test_model_pred.append(np.argmax(x)) return test_model_pred
def generate_vec(mode: str, data: list, tokenizer, bert_model, vector_data_dir: str, vector_id_dict_dir: str): """ 传入保存模式、数据、分词器、模型对象、向量保存文件夹路径、向量保存索引文件夹 :param mode: (str)数据模式 train dev test :param data: (list) 数据列表 sentence sentence2 label :param tokenizer: 分字器 :param bert_model: bert模型对象 :param vector_data_dir: (str)向量保存文件夹 :param vector_id_dict_dir: (str)事件{cameo:[id]}保存文件夹 :return: None """ # 下标列表 idxs = list(range(len(data))) # 数据字典 data_dict = {} # 保存向量的列表 X = [] for j, i in tqdm(enumerate(idxs)): d = data[i] # 匹配样本的两个句子 text_01 = d.split(" ")[0] text_02 = d.split(" ")[1] # 按照句子对的数量保存索引,每10000个句子对保存成一个文件 # 每个向量文件对应一个索引列表,以j//10000为键,以句子列表为值 data_dict.setdefault(j // 10000, []) # 如果短句不在字典中,则将短句保存到字典中,并将向量化的短句保存到向量文件中 if text_01 not in data_dict[j // 10000]: # 将短句保存到字典中 data_dict[j // 10000].append(text_01) # 将短句向量化并保存到向量列表中 x1_1, x1_2 = tokenizer.encode(first_text=text_01) x1 = bert_model.model.predict([np.array([x1_1]), np.array([x1_2])])[0][0] X.append(x1) # 同上 if text_02 not in data_dict[j // 10000]: data_dict[j // 10000].append(text_02) x2_1, x2_2 = tokenizer.encode(first_text=text_02) x2 = bert_model.model.predict([np.array([x2_1]), np.array([x2_2])])[0][0] X.append(x2) # 如果达到10000或者最后一个批次则将向量保存,置空变量再次循环 if (j + 1) % 10000 == 0 or (j + 1) == len(data): X = np.array(X) file_name = "{}_{}.npy".format(mode, j // 10000) file_path = cat_path(vector_data_dir, file_name) np.save(file_path, X) X = [] # 最后将字典文件保存 dict_name = "{}_dict.json".format(mode) dict_path = cat_path(vector_id_dict_dir, dict_name) with open(dict_path, "w", encoding="utf-8") as f: content = json.dumps(data_dict, ensure_ascii=False, indent=4) f.write(content)
def __train_over_hyperparameters_RNN(data, dates, events_set, events_p_oh, event_model: EventModel): """ 提供训练模型服务, 遍历不同超参数的组合来训练模型. 先对降维维度遍历, 再对encoder输入长度遍历, 产生的子模型数量为降维维度遍历个数与encoder输入长度遍历个数之积. 随encoder输入长度, 降维维度的增加, 模型训练时间会变长. epoch越大, 模型训练时间越长 Args: data: 模型目录 dates: 模型目录 events_set: 模型目录 events_p_oh: 模型目录 event_model: EventModel实体类 Returns: 训练完成的模型文件所在目录列表 模型名称列表 每个模型对应的 decoder 输出序列列表. 不同的模型由于输入序列长度不同导致输出序列不同 每个模型对应的超参数列表 """ logger.info('<RNN>开始训练模型') # 每次训练生成一个对应的目录 new_model_dir = cat_path(base_model_dir, guid()) if not os.path.exists(new_model_dir): os.makedirs(new_model_dir) pca_dir = new_model_dir sub_model_dirs = [] sub_model_names = [] lag_dates = [] pcas = [] outputs_list = [] params_list = [] # 先进行降维,对于PCA降维来说它始终只有固定的降维组合,若先对滞后期的选择的话,会导致出现N个重复的降维组合 for i in range(event_model.dr_min, event_model.dr_max, event_model.size): # 各种降维选择 values_pca = preprocess.apply_pca(i, pca_dir, data) # 基于页面选择的开始日期、结束日期的整个范围中每一天作为一个基准日期,在该基准日期往前推max_input_len至min_input_len天 # 的范围内每次间隔5天(10、15、20天)拉取数据训练模型。 for j in range(event_model.delay_min_day, event_model.delay_max_day, 5): # 滞后期的选择 logger.info(f"<RNN> Current value: 滞后期={j}, pca={i}") lag_dates.append(j) pcas.append(i) sub_model_name = f'{event_model.model_name}-{j}-{event_model.days}-{i}' sub_model_names.append(sub_model_name) sub_model_path = cat_path(new_model_dir, sub_model_name) if not os.path.exists(sub_model_path): os.makedirs(sub_model_path) sub_model_dirs.append(sub_model_path) # flag 表示样本是否可用 array_x, array_y, array_yin = gen_samples( values_pca, events_p_oh, j, event_model.days, dates, event_model.tran_start_date, event_model.tran_end_date) outputs_list.append(array_y) params_list.append([j, event_model.days, i]) train_model_rnn.train(event_model.train_batch_no, event_model.epoch, event_model.neure_num, array_x, array_y, array_yin, sub_model_path, values_pca, events_p_oh, j, event_model.days, dates, event_model.evaluation_start_date, event_model.evaluation_end_date, events_set) logger.info('<RNN>训练完成, 模型存入数据库') detail_ids = pgsql.model_train_done_rnn(event_model.model_id, lag_dates, pcas, sub_model_dirs, sub_model_names, outputs_list, events_set, new_model_dir) return sub_model_dirs, params_list, detail_ids, new_model_dir
def __predict_by_sub_models(data, dates, event_predict_array: list, pred_start_date, num_classes, task_id, model_type): """ 使用各个子模型进行预测。 Args: data: 预测输入数据 dates: 数据表日期列表 event_predict_array: array. EventPredict实体类,封装预测时需要用到的信息 pred_start_date: 开始预测日期, 即此日期后均有预测结果 num_classes: 事件类别数量 task_id: 由页面传入 Returns: preds_one: 所有子模型预测结果, 每天为输入数据预测的后一天结果, 如果最后一个样本没有历史预测结果, 则最后一个样本的预测结果全部保留. shape(最后样本有历史预测结果): (子模型数, 未预测的样本数), 或shape(最后样本无历史预测结果): (子模型数, 未预测的样本数 + output_len - 1) preds_all: 所有子模型预测结果, 每天为输入数据预测的多天结果. shape: (子模型数, 未预测的样本数, 预测天数) dates_pred_one: preds_one 对应的预测日期, shape 与 preds_one 相同 dates_pred_all: preds_all 对应的预测日期, shape 与 preds_all 相同 dates_pred_data: preds_one 对应的数据结束日期, shape 与 preds_one 相同 pred_detail_ids: 预测的子模型 detail_id 列表, 去掉了已预测的子模型 last_date_data_pred: 预测所用数据最后一天日期 model_type: string.模型类型,对应代码项为ModelType """ preds_one = [] preds_all = [] dates_pred_one = [] dates_pred_all = [] dates_pred_data = [] pred_detail_ids = [] last_date_data_pred = None for event_predict in event_predict_array: sub_model = event_predict.model_name logger.info(f'正在使用模型{sub_model}进行预测') input_len = event_predict.lag_date detail_id = event_predict.detail_id if ModelType.CNN.value == model_type: output_len = cnn_day inputs_data, output_dates = pp.gen_inputs_by_pred_start_date( data, input_len, dates, pred_start_date) elif ModelType.RNN.value == model_type: # 预测天数指的是模型可以预测n天,而不是预测开始日期+n天。假设预测开始日期为6月1日,则模型从6月1日起,每次预测n天 # 直到今天的日期,且对重复预测的处理是使用最新的预测。 output_len = event_predict.days values_pca = pp.apply_pca(event_predict.pca, event_predict.model_dir, data, True) inputs_data, output_dates = pp.gen_inputs_by_pred_start_date( values_pca, input_len, dates, pred_start_date) else: raise RuntimeError(f"Unsupport model type <{model_type}>") # 取样本数据中最大的日期,再往后推1天 TODO dates[-1]要求日期必须升序排序 max_output_date = datetime.strptime( dates[-1], date_formatter).date() + timedelta(1) output_dates.append(max_output_date) # 此时output_dates不包含预测第一天后日期 dates_data = [ datetime.strptime(output_dates[0], date_formatter).date() - timedelta(1) ] dates_data.extend([ datetime.strptime(out_put_date, date_formatter).date() for out_put_date in output_dates[:-1] ]) last_date_data_pred = dates_data[-1] predicted_detail_id_dates = pgsql.query_predicted_rsts( detail_id, pred_start_date, task_id) predicted_dates = predicted_detail_id_dates.get( detail_id) # type of list of str if predicted_dates is None: latest_date_predicted = False predicted_dates_to_delete = [] else: predicted_dates = sorted( [pp.parse_date_str(d) for d in predicted_dates]) predicted_dates_to_delete = predicted_dates[-output_len + 1:] predicted_dates = predicted_dates[:-output_len + 1] # 截取只预测一天的预测结果 max_predicted_date = predicted_dates[-1] zipped_unpredicted = [[ d, i, dd ] for d, i, dd in zip(output_dates, inputs_data, dates_data) if d not in predicted_dates] if not zipped_unpredicted: logger.info(f'{sub_model}所有日期已预测, 跳过') continue output_dates, inputs_data, dates_data, = zip(*zipped_unpredicted) output_dates = list(output_dates) inputs_data = list(inputs_data) dates_data = list(dates_data) if max_predicted_date == max_output_date: latest_date_predicted = True else: latest_date_predicted = False # 预测日期, 包含第一天以后日期 dates_pred_all_model = [[(dd + timedelta(t)) for t in range(1, output_len + 1)] for dd in dates_data] sub_model_dir = cat_path(event_predict.model_dir, sub_model) if ModelType.RNN.value == model_type: encoder, decoder = load_rnn_models(sub_model_dir) pred = predict_sample_rnn(encoder, decoder, inputs_data, output_len, num_classes) elif ModelType.CNN.value == model_type: model = load_cnn_model(sub_model_dir) pred = predict_sample_cnn(model, inputs_data) pred_one = [p[0] for p in pred] # 在预测到最后一天之前的每一天预测的结果都只有第一天可用 if not latest_date_predicted: pred_one.extend(pred[-1][1:]) # 此时output_dates添加第一天以后日期 output_dates.extend( [max_output_date + timedelta(d) for d in range(1, output_len)]) dates_data.extend([dates_data[-1]] * (output_len - 1)) if predicted_dates_to_delete: pgsql.delete_predicted_dates(detail_id, predicted_dates_to_delete) pred_detail_ids.append(detail_id) preds_one.append(pred_one) preds_all.append(pred) dates_pred_one.append(output_dates) dates_pred_data.append(dates_data) dates_pred_all.append(dates_pred_all_model) return preds_one, preds_all, dates_pred_one, dates_pred_all, dates_pred_data, pred_detail_ids, last_date_data_pred
:return: """ rst_articles_pos = [] rst_articles_neg = [] neg_num = 0 for content in tqdm(articles_content): content = remove_white_space(content) rsts, rsts_neg, neg_num = extract_article_relation(content, relation, neg_num, neg_capacity) rst_articles_pos.extend(rsts) rst_articles_neg.extend(rsts_neg) return rst_articles_pos, rst_articles_neg articles_db_path = cat_path(ALGOR_PRETRAIN_ROOT, 'articles.txt') def get_db_articles(): """ 获取数据库中爬取的文章内容. 如果有本地的内容文件, 则从本地读取, 否则从数据库读取, 并将过滤后的结果存到本地文件 :return: """ if os.path.exists(articles_db_path): articles_zh = json.loads(readfile(articles_db_path)) return articles_zh articles = get_articles_from_db() articles_zh = filter_db_articles(articles) save_content(json.dumps(articles_zh, ensure_ascii=False), articles_db_path)
def model_train(version, model_id, trained_model_dir="", data_dir="", all_steps=0, maxlen=160, epoch=40, batch_size=8, max_learning_rate=5e-5, min_learning_rate=1e-5, model_type="roberta"): """ 进行模型训练的主函数,搭建模型,加载模型数据,根据传入的参数进行模型训练 :param version: 模型版本 :param model_id: 模型编号 :param data_dir: 补充数据的文件夹路径 :param trained_model_dir: 训练后模型存放路径 :param all_steps: 模型训练步数 :param maxlen: 最大长度 :param epoch: 循环次数 :param batch_size: 批次大小 :param max_learning_rate: 最大学习率 :param min_learning_rate: 最小学习率 :param version: 模型版本号 :param model_id: 模型id :param model_type: 预训练模型类型,现在不用,后期会用到, :return: status, F1, precision, recall, corpus_num """ try: # 补充数据文件夹更换 if data_dir: extract_train_config.supplement_data_dir = data_dir # 判断训练后模型路径是否存在 if not os.path.exists(trained_model_dir): os.makedirs(trained_model_dir) # 训练后模型路径 trained_model_path = cat_path(trained_model_dir, f"extract_model_{version}_{model_id}") # 获取训练集、验证集 train_data, dev_data = get_data(extract_train_config.train_data_path, extract_train_config.dev_data_path, data_dir) # 搭建模型 trigger_model, object_model, subject_model, loc_model, time_model, negative_model, train_model = build_model( ) # 构造训练数据生成器 train_d = DataGenerator(TOKENIZER, maxlen, train_data, batch_size) with SESS.as_default(): with SESS.graph.as_default(): # 构造callback模块的评估类 evaluator = Evaluate(dev_data, maxlen, trained_model_path, trigger_model, object_model, subject_model, loc_model, time_model, negative_model, train_model, max_learning_rate, min_learning_rate) # 如果训练数据小于5000,则设置内部循环步数1000次为一个循环,否则集使用训练数据个数/batch_size计算得到的步数 if all_steps: pass elif len(train_data) < 5000: all_steps = 1000 else: all_steps = train_d.__len__() # 构造对抗攻击训练 adversarial_training(train_model, 'Embedding-Token', 0.1) # 模型训练 train_model.fit_generator(train_d.__iter__(), steps_per_epoch=all_steps, epochs=epoch, callbacks=[evaluator]) # 重载模型参数 train_model.load_weights(trained_model_path) # 将验证集预测结果保存到文件中,暂时注释掉 f1, precision, recall = evaluator.evaluate() logger.info(f"f1:{f1}, precision:{precision}, recall:{recall}") return { "status": "success", "version": version, "model_id": model_id, "results": { "f1": f1, "precison": precision, "recall": recall }, "corpus_num": { "train_data": len(train_data), "dev_data": len(dev_data) } } except: trace = traceback.format_exc() logger.error(trace) return {"status": "failed", "results": trace}
#!/usr/bin/env python # -*- coding:utf-8 -*- # @Author: Mr Fan # @Time: 2020年06月09 """ 事件状态模型训练需要的参数以就文件保存路径 """ import feedwork.AppinfoConf as appconf from feedwork.utils.FileHelper import cat_path from jdqd.common.event_emm.model_utils import generate_trained_model_path # 训练后模型保存路径 trained_model_dir = "event_extract/event_state_trained_model" trained_model_dir = cat_path(appconf.ALGOR_MODULE_ROOT, trained_model_dir) # 训练后模型名称 trained_model_name = "state_model.h5" # 训练后模型路径 trained_model_path = generate_trained_model_path(trained_model_dir, trained_model_name) # 训练集数据保存路径 train_data_path = "event_extract/data/event_state_data/raw_data/train_data.json" train_data_path = cat_path(appconf.ALGOR_PRETRAIN_ROOT, train_data_path) # 测试集数据保存路径 dev_data_path = "event_extract/data/event_state_data/raw_data/dev_data.json" dev_data_path = cat_path(appconf.ALGOR_PRETRAIN_ROOT, dev_data_path) # 补充数据保存文件夹 supplement_data_dir = "event_extract/data/event_state_data/supplement" supplement_data_dir = cat_path(appconf.ALGOR_PRETRAIN_ROOT, supplement_data_dir) # 模型状态 下标 字典
#!/usr/bin/env python # -*- coding:utf-8 -*- # @Author: Mr Fan # @Time: 2020年06月11 from feedwork.utils.FileHelper import cat_path import feedwork.AppinfoConf as appconf # 模型类型 model_type = "bert" # 模型参数 config_path = 'chinese_roberta_wwm_ext_L-12_H-768_A-12/bert_config.json' config_path = cat_path(appconf.ALGOR_PRETRAIN_ROOT, config_path) # 初始化模型路径 checkpoint_path = 'chinese_roberta_wwm_ext_L-12_H-768_A-12/bert_model.ckpt' checkpoint_path = cat_path(appconf.ALGOR_PRETRAIN_ROOT, checkpoint_path) # 模型字典路径 dict_path = 'chinese_roberta_wwm_ext_L-12_H-768_A-12/vocab.txt' dict_path = cat_path(appconf.ALGOR_PRETRAIN_ROOT, dict_path)
# -*- coding:utf-8 -*- # @Author: Mr Fan # @Time: 2020年06月09 """ 传递事件抽取预测模块需要的所有参数 """ import feedwork.AppinfoConf as appconf from feedwork.utils.FileHelper import cat_path # 模型类型 model_type = "bert" # 字符串最大长度 maxlen = 160 # 事件抽取模型路径 event_extract_model_path = "event_extract/model/extract_model.h5" event_extract_model_path = cat_path(appconf.ALGOR_PRETRAIN_ROOT, event_extract_model_path) # 事件状态判断模型路径 event_state_model_path = "event_extract/model/state_model.h5" event_state_model_path = cat_path(appconf.ALGOR_PRETRAIN_ROOT, event_state_model_path) # 事件类别模型路径 event_cameo_model_path = "event_extract/model/cameo_model.h5" event_cameo_model_path = cat_path(appconf.ALGOR_PRETRAIN_ROOT, event_cameo_model_path) # bert模型参数json文件路径 bert_config_path = "chinese_roberta_wwm_ext_L-12_H-768_A-12/bert_config.json" bert_config_path = cat_path(appconf.ALGOR_PRETRAIN_ROOT, bert_config_path) # bert模型字典保存的路径 dict_path = "chinese_roberta_wwm_ext_L-12_H-768_A-12/vocab.txt" dict_path = cat_path(appconf.ALGOR_PRETRAIN_ROOT, dict_path) # 小牛翻译的key user_key = "b3d33c84a6291b89524e1a759064032a" # 小牛翻译的网址
# @Time: 2020年06月09 """ 事件归并模型训练所有的参数和文件路径 """ import feedwork.AppinfoConf as appconf from feedwork.utils.FileHelper import cat_path # 批量大小 batch_size = 8 # 最大最小学习率 learning_rate = 5e-5 min_learning_rate = 1e-5 # 训练集路径 train_data_path = "event_search/train/data/datav06/train.txt" train_data_path = cat_path(appconf.ALGOR_PRETRAIN_ROOT, train_data_path) # 验证集路径 dev_data_path = "event_search/train/data/datav06/dev.txt" dev_data_path = cat_path(appconf.ALGOR_PRETRAIN_ROOT, dev_data_path) # 测试集路径 test_data_path = "event_search/train/data/datav06/test.txt" test_data_path = cat_path(appconf.ALGOR_PRETRAIN_ROOT, test_data_path) # 训练后的模型路径 trained_model_path = "event_search/trained_model/match_model.h5" trained_model_path = cat_path(appconf.ALGOR_MODULE_ROOT, trained_model_path) # 转化后的向量路径 vector_data_path = "event_search/train/data/vector_data" vector_data_path = cat_path(appconf.ALGOR_MODULE_ROOT, vector_data_path) # 存储转化样本的字典 vector_id_dict_dir = "event_search/train/data/" vector_id_dict_dir = cat_path(appconf.ALGOR_MODULE_ROOT, vector_id_dict_dir)
# @Time: 2020年06月09 """ 匹配模型训练需要的所有参数和模型路径 """ import feedwork.AppinfoConf as appconf from feedwork.utils.FileHelper import cat_path # 训练批次大小 batch_size = 8 # 循环 epoch = 5 # dropout drop_out_rate = 0.1 # 字符串最大长度 maxlen = 512 # 学习率 learning_rate = 5e-5 # 最小学习率 min_learning_rate = 1e-5 # 训练数据集路径 train_data_path = "event_match/data/second_train/train.txt" train_data_path = cat_path(appconf.ALGOR_PRETRAIN_ROOT, train_data_path) dev_data_path = "event_match/data/second_train/dev.txt" dev_data_path = cat_path(appconf.ALGOR_PRETRAIN_ROOT, dev_data_path) test_data_path = "event_match/data/second_train/test.txt" test_data_path = cat_path(appconf.ALGOR_PRETRAIN_ROOT, test_data_path) # 训练后模型保存路径 trained_model_path = "event_match/model/trained_model/match_model.h5" trained_model_path = cat_path(appconf.ALGOR_MODULE_ROOT, trained_model_path)
#!/usr/bin/env python # -*- coding:utf-8 -*- # @Author: Mr Fan # @Time: 2020年06月09 """ 事件提取中匹配模型预测模块需要的所有参数以及文件路径 """ import feedwork.AppinfoConf as appconf from feedwork.utils.FileHelper import cat_path # 模型批量大小 batch_size = 1 # 字符串最大长度 maxlen = 512 # 事件类别模型路径 match_model_path = "event_match/model/trained_model/match_model_05.h5" match_model_path = cat_path(appconf.ALGOR_PRETRAIN_ROOT, match_model_path) # 事件列表地址 allevent_path = "event_match/data/allevent" allevent_path = cat_path(appconf.ALGOR_PRETRAIN_ROOT, allevent_path) # 摘要句子数量 abstract_n = 3
def execute_delete(event_id: str): """ 删除模块的主控程序,读取cameo2id,然后查看事件id是否存在字典中,并进行删除。 :return: None :raise: FileNotFoundError """ if not os.path.exists(pre_config.cameo2id_path) or not os.path.isfile( pre_config.cameo2id_path): logger.error(f"{pre_config.cameo2id_path} miss, can not exec delete!") raise FileNotFoundError # 读取保存事件{cameo:[event_id]}字典 cameo2id = comm.read_cameo2id(pre_config.cameo2id_path) # 判断事件是否在向量库中,如果存在则设置为1,并读取对应的向量并将其删除 status = 0 for cameo in list(cameo2id.keys()): if event_id in cameo2id[cameo]: status += 1 # 读取向量的文件地址 read_file_path = cat_path(pre_config.vec_data_dir, f"{cameo}.npy") # 保存向量时的路径,只是比上边缺少了.npy,函数会自动补齐 save_file_path = cat_path(pre_config.vec_data_dir, cameo) # 读取cameo保存的向量文件 x = comm.read_np(read_file_path) # 删除向量 temp = np.delete(x, list(cameo2id[cameo]).index(event_id), axis=0) # 删除列表中的事件id cameo2id[cameo].remove(event_id) # 将更新后的文件重新保存 # cameo对应的向量没有删除完,cameo2id[cameo]也没有删完 if temp.shape[0] and cameo2id[cameo]: comm.save_cameo2id(cameo2id, pre_config.cameo2id_path) # 将向量保存到文件中 comm.save_np(save_file_path, temp) # 跳出循环 break # cameo对应的向量已经置空了,且cameo对应的列表也空了 elif not temp.shape[0] and not cameo2id[cameo]: # 将向量文件删除 os.remove(read_file_path) # 删除cameo在字典中的键 del cameo2id[cameo] # 判断字典是否为空 if len(cameo2id): # 字典不为空则重新保存 comm.save_cameo2id(cameo2id, pre_config.cameo2id_path) else: # 字典空了,则删除字典文件 os.remove(pre_config.cameo2id_path) # 跳出循环 break # 说明向量表和字典不匹配,一个已经清空,而另一个没有清空,此时需要手动将对应的cameo删除 else: logger.error( "vector file does not match cameo2id, please delete file manually!" ) raise ValueError return status
# -*- coding: utf-8 -*- """ Created on Tue Jun 9 16:18:41 2020 @author: 12894 """ """ 关系触发词抽取模型训练需要的所有参数以及文件路径 """ import feedwork.AppinfoConf as appconf from feedwork.utils.FileHelper import cat_path # 关系触发词抽取模型路径 relation_key_extract_model_path = "relation_key_extract/relation_key_extract.h5" relation_key_extract_model_path = cat_path(appconf.ALGOR_PRETRAIN_ROOT, relation_key_extract_model_path) # 模型参数 config_path = 'chinese_L-12_H-768_A-12/bert_config.json' config_path = cat_path(appconf.ALGOR_PRETRAIN_ROOT, config_path) # 初始化模型路径 checkpoint_path = 'chinese_L-12_H-768_A-12/bert_model.ckpt' checkpoint_path = cat_path(appconf.ALGOR_PRETRAIN_ROOT, checkpoint_path) # 模型字典路径 dict_path = 'chinese_L-12_H-768_A-12/vocab.txt' dict_path = cat_path(appconf.ALGOR_PRETRAIN_ROOT, dict_path) # 训练后模型保存路径 trained_model_dir = "relation_key_extract_model" trained_model_dir = cat_path(appconf.ALGOR_MODULE_ROOT, trained_model_dir) # 训练后模型名称 trained_model_name = "relation_key_extract.h5" # 训练集数据保存路径
def save_vec_data(cameo: str, event_id: str, main_vec): """ 传入事件cameo、事件id、事件短句向量,将向量保存到文件中,临时保存,后期改写为保存到数据库中, 将事件id存放到以cameo为键的字典中{cameo:[event_id], } :param cameo: (str)事件cameo号 :param event_id: (str)事件编号 :param main_vec: (ndarray)事件向量 :return: None :raise:字典文件缺失/ 向量文件文件缺失 FileNotFoundError 事件id重复 ValueError 传入类型错误 TypeError """ if not isinstance(cameo, str): logger.error("cameo编号格式错误!") raise TypeError if not isinstance(event_id, str): logger.error("事件编号格式错误!") raise TypeError # 读取向量时的路径 read_file_path = cat_path(pre_config.vec_data_dir, f"{cameo}.npy") # 保存向量时的路径,只是比上边缺少了.npy,函数会自动补齐 save_file_path = cat_path(pre_config.vec_data_dir, cameo) # 判断字典文件是否存在, 不存在这就是第一个向量 if not os.path.exists(pre_config.cameo2id_path): cameo2id = {cameo: [event_id]} # 将字典保存 comm.save_cameo2id(cameo2id, pre_config.cameo2id_path) # 将向量保存到文件中 comm.save_np(save_file_path, np.array([main_vec])) # 如果字典文件存在,而向量文件不存在,则说明这是这个cameo的第一个向量 elif not os.path.exists(read_file_path): # cameo2id 字典 {cameo:[]} cameo2id = comm.read_cameo2id(pre_config.cameo2id_path) # 将事件id添加到字典中 cameo2id[cameo] = [event_id] # 将向量保存到文件中 comm.save_np(save_file_path, np.array([main_vec])) # 写入文件中 comm.save_cameo2id(cameo2id, pre_config.cameo2id_path) # 如果向量文件存在,而字典文件不存在,则报错,字典文件缺失 elif not os.path.exists(pre_config.cameo2id_path): logger.error("字典文件缺失!") raise FileNotFoundError # 如果字典和向量都存在,则正常添加向量和事件id,并保存 else: # cameo2id 字典 {cameo:[]} cameo2id = comm.read_cameo2id(pre_config.cameo2id_path) # 如果事件id不在字典中,则进行保存向量和添加id索引的操作 if event_id not in cameo2id.setdefault(cameo, []): # 将事件id保存到cameo2id字典中 cameo2id[cameo].append(event_id) # 读取向量文件 x = comm.read_np(read_file_path) # 将向量拼接进去 temp = np.vstack([x, np.array([main_vec])]) # 将向量保存到文件中 comm.save_np(save_file_path, temp) # 写入文件中 comm.save_cameo2id(cameo2id, pre_config.cameo2id_path) # 事件id已经存在了,说明输入的id重复了,则报错,不进行保存操作。 else: logger.error("事件id重复!") raise ValueError