pai_flow_operator_v5_1.py 27 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700
  1. # -*- coding: utf-8 -*-
  2. """PAI 广告模型更新工作流 v5_1(piaoquan_ad_rank_dnn_v15_1):训练用 7 天数据,评估用 1 天数据。"""
  3. import functools
  4. import os
  5. import re
  6. import sys
  7. import time
  8. import json
  9. from alibabacloud_paistudio20210202.client import Client as PaiStudio20210202Client
  10. from alibabacloud_tea_openapi import models as open_api_models
  11. from alibabacloud_paistudio20210202 import models as pai_studio_20210202_models
  12. from alibabacloud_tea_util import models as util_models
  13. from alibabacloud_tea_util.client import Client as UtilClient
  14. from alibabacloud_eas20210701.client import Client as eas20210701Client
  15. from alibabacloud_paiflow20210202 import models as paiflow_20210202_models
  16. from alibabacloud_paiflow20210202.client import Client as PAIFlow20210202Client
  17. from datetime import datetime, timedelta
  18. from odps import ODPS
  19. from ad_monitor_util import _monitor
  20. import alibabacloud_oss_v2 as oss
  21. target_names = {
  22. '样本shuffle',
  23. '生成CID文件',
  24. '模型训练-样本shufle',
  25. '模型导出-2',
  26. '更新EAS服务(Beta)-1',
  27. '虚拟起始节点',
  28. '二分类评估-1',
  29. '二分类评估-2',
  30. '预测结果对比'
  31. }
  32. WORKFLOW_NAME = "piaoquan_ad_rank_dnn_v15_1"
  33. EXPERIMENT_ID = "draft-lg9zywreopsjmq78m4"
  34. ACCESS_KEY_ID = "LTAI5tFGqgC8f3mh1fRCrAEy"
  35. ACCESS_KEY_SECRET = "XhOjK9XmTYRhVAtf6yii4s4kZwWzvV"
  36. MAX_RETRIES = 3
  37. TRAIN_DAYS = 7
  38. EVAL_OFFSET_DAYS = 1
  39. ONLINE_SERVICE_NAME = 'ad_rank_dnn_v11_easyrec_v5'
  40. def retry(func):
  41. @functools.wraps(func)
  42. def wrapper(*args, **kwargs):
  43. retries = 0
  44. while retries < MAX_RETRIES:
  45. try:
  46. result = func(*args, **kwargs)
  47. if result is not False:
  48. return result
  49. except Exception as e:
  50. print(f"函数 {func.__name__} 执行时发生异常: {e},重试第 {retries + 1} 次")
  51. retries += 1
  52. print(f"函数 {func.__name__} 重试 {MAX_RETRIES} 次后仍失败。")
  53. return False
  54. return wrapper
  55. def get_odps_instance(project):
  56. odps = ODPS(
  57. access_id=ACCESS_KEY_ID,
  58. secret_access_key=ACCESS_KEY_SECRET,
  59. project=project,
  60. endpoint='http://service.cn.maxcompute.aliyun.com/api',
  61. )
  62. return odps
  63. def get_data_from_odps(project, table, num):
  64. odps = get_odps_instance(project)
  65. try:
  66. sql = f'select * from {table} limit {num}'
  67. with odps.execute_sql(sql).open_reader() as reader:
  68. df = reader.to_pandas()
  69. if len(df) < num:
  70. return None
  71. return df
  72. except Exception as e:
  73. print(f"发生错误: {e}")
  74. def get_dict_from_odps(project, table):
  75. odps = get_odps_instance(project)
  76. try:
  77. sql = f'select * from {table}'
  78. with odps.execute_sql(sql).open_reader() as reader:
  79. data = {}
  80. for record in reader:
  81. record_list = list(record)
  82. key = record_list[0][1]
  83. value = record_list[1][1]
  84. data[key] = value
  85. return data
  86. except Exception as e:
  87. print(f"发生错误: {e}")
  88. def load_holiday_dates():
  89. current_dir = os.getcwd()
  90. file_path = os.path.join(current_dir, 'ad', 'holidays.txt')
  91. try:
  92. with open(file_path, 'r', encoding='utf-8') as file:
  93. dates = set()
  94. for line in file:
  95. token = line.strip()
  96. if re.fullmatch(r'\d{8}', token):
  97. dates.add(token)
  98. return dates
  99. except FileNotFoundError:
  100. raise Exception(f"错误:未找到 {file_path} 文件。")
  101. except Exception as e:
  102. raise Exception(f"错误:读取节假日文件失败: {e}")
  103. def yyyymmdd_ago(days):
  104. return (datetime.now() - timedelta(days=days)).strftime('%Y%m%d')
  105. def get_eval_date():
  106. """评估日期:昨天 1 天。"""
  107. return yyyymmdd_ago(EVAL_OFFSET_DAYS)
  108. def get_train_dates():
  109. """训练日期:从昨天往前取 TRAIN_DAYS 个非节假日。"""
  110. holidays = load_holiday_dates()
  111. dates = []
  112. offset = EVAL_OFFSET_DAYS
  113. while len(dates) < TRAIN_DAYS:
  114. day = yyyymmdd_ago(offset)
  115. if day not in holidays:
  116. dates.append(day)
  117. offset += 1
  118. if offset > 90:
  119. raise Exception(f"无法凑齐 {TRAIN_DAYS} 天训练日期,请检查 holidays.txt")
  120. dates.sort()
  121. print(f"v5_1 训练日期({TRAIN_DAYS}天): {dates}")
  122. print(f"v5_1 评估日期(1天): {get_eval_date()}")
  123. return dates
  124. def replace_sql_dt_in(sql, dates):
  125. """用指定日期列表替换 SQL 中第一次出现的 where dt in (...)。"""
  126. quoted = ','.join(f"'{d}'" for d in dates)
  127. marker = 'where dt in ('
  128. start_index = sql.find(marker)
  129. if start_index == -1:
  130. return None
  131. value_start = start_index + len(marker)
  132. value_end = sql.find(')', value_start)
  133. if value_end == -1:
  134. return None
  135. return sql[:value_start] + quoted + sql[value_end:]
  136. def is_created_today(time_str):
  137. time_obj = datetime.fromisoformat(time_str)
  138. today_start = datetime.combine(datetime.now().date(), datetime.min.time())
  139. return time_obj.timestamp() > today_start.timestamp()
  140. def replace_train_tables(cmd, table):
  141. train_table = 'odps://pai_algo/tables/' + table
  142. marker = '-Dtrain_tables="'
  143. start_index = cmd.find(marker)
  144. if start_index == -1:
  145. return None
  146. value_start = start_index + len(marker)
  147. value_end = cmd.find('"', value_start)
  148. if value_end == -1:
  149. return None
  150. return cmd[:value_start] + train_table + cmd[value_end:]
  151. class PAIClient:
  152. def __init__(self):
  153. pass
  154. @staticmethod
  155. def create_client() -> PaiStudio20210202Client:
  156. config = open_api_models.Config(
  157. access_key_id=ACCESS_KEY_ID,
  158. access_key_secret=ACCESS_KEY_SECRET
  159. )
  160. config.endpoint = f'pai.cn-hangzhou.aliyuncs.com'
  161. return PaiStudio20210202Client(config)
  162. @staticmethod
  163. def create_eas_client() -> eas20210701Client:
  164. config = open_api_models.Config(
  165. access_key_id=ACCESS_KEY_ID,
  166. access_key_secret=ACCESS_KEY_SECRET
  167. )
  168. config.endpoint = f'pai-eas.cn-hangzhou.aliyuncs.com'
  169. return eas20210701Client(config)
  170. @staticmethod
  171. def create_flow_client() -> PAIFlow20210202Client:
  172. config = open_api_models.Config(
  173. access_key_id=ACCESS_KEY_ID,
  174. access_key_secret=ACCESS_KEY_SECRET
  175. )
  176. config.endpoint = f'paiflow.cn-hangzhou.aliyuncs.com'
  177. return PAIFlow20210202Client(config)
  178. @staticmethod
  179. def get_work_flow_draft(experiment_id: str):
  180. client = PAIClient.create_client()
  181. runtime = util_models.RuntimeOptions()
  182. headers = {}
  183. try:
  184. resp = client.get_experiment_with_options(experiment_id, headers, runtime)
  185. return resp.body.to_map()
  186. except Exception as error:
  187. raise Exception(f"get_work_flow_draft error {error}")
  188. @staticmethod
  189. def get_describe_service(service_name: str):
  190. client = PAIClient.create_eas_client()
  191. runtime = util_models.RuntimeOptions()
  192. headers = {}
  193. try:
  194. resp = client.describe_service_with_options('cn-hangzhou', service_name, headers, runtime)
  195. return resp.body.to_map()
  196. except Exception as error:
  197. raise Exception(f"get_describe_service error {error}")
  198. @staticmethod
  199. def update_experiment_content(experiment_id: str, content: str, version: int):
  200. client = PAIClient.create_client()
  201. update_experiment_content_request = pai_studio_20210202_models.UpdateExperimentContentRequest(
  202. content=content, version=version)
  203. runtime = util_models.RuntimeOptions()
  204. headers = {}
  205. try:
  206. resp = client.update_experiment_content_with_options(
  207. experiment_id, update_experiment_content_request, headers, runtime)
  208. print(resp.body.to_map())
  209. except Exception as error:
  210. raise Exception(f"update_experiment_content error {error}")
  211. @staticmethod
  212. def create_job(experiment_id: str, node_id: str, execute_type: str):
  213. client = PAIClient.create_client()
  214. create_job_request = pai_studio_20210202_models.CreateJobRequest()
  215. create_job_request.experiment_id = experiment_id
  216. create_job_request.node_id = node_id
  217. create_job_request.execute_type = execute_type
  218. runtime = util_models.RuntimeOptions()
  219. headers = {}
  220. try:
  221. resp = client.create_job_with_options(create_job_request, headers, runtime)
  222. return resp.body.to_map()
  223. except Exception as error:
  224. raise Exception(f"create_job error {error}")
  225. @staticmethod
  226. def get_jobs_list(experiment_id: str, order='DESC'):
  227. client = PAIClient.create_client()
  228. list_jobs_request = pai_studio_20210202_models.ListJobsRequest(
  229. experiment_id=experiment_id,
  230. order=order
  231. )
  232. runtime = util_models.RuntimeOptions()
  233. headers = {}
  234. try:
  235. resp = client.list_jobs_with_options(list_jobs_request, headers, runtime)
  236. return resp.body.to_map()
  237. except Exception as error:
  238. raise Exception(f"get_jobs_list error {error}")
  239. @staticmethod
  240. def get_job_detail(job_id: str, verbose=False):
  241. client = PAIClient.create_client()
  242. get_job_request = pai_studio_20210202_models.GetJobRequest(
  243. verbose=verbose
  244. )
  245. runtime = util_models.RuntimeOptions()
  246. headers = {}
  247. try:
  248. resp = client.get_job_with_options(job_id, get_job_request, headers, runtime)
  249. return resp.body.to_map()
  250. except Exception as error:
  251. print(error.message)
  252. print(error.data.get("Recommend"))
  253. UtilClient.assert_as_string(error.message)
  254. @staticmethod
  255. def get_flow_out_put(pipeline_run_id: str, node_id: str, depth: int):
  256. client = PAIClient.create_flow_client()
  257. list_pipeline_run_node_outputs_request = paiflow_20210202_models.ListPipelineRunNodeOutputsRequest(
  258. depth=depth
  259. )
  260. runtime = util_models.RuntimeOptions()
  261. headers = {}
  262. try:
  263. resp = client.list_pipeline_run_node_outputs_with_options(
  264. pipeline_run_id, node_id, list_pipeline_run_node_outputs_request, headers, runtime)
  265. return resp.body.to_map()
  266. except Exception as error:
  267. print(error.message)
  268. print(error.data.get("Recommend"))
  269. UtilClient.assert_as_string(error.message)
  270. def extract_date_yyyymmdd(input_string):
  271. pattern = r'\d{8}'
  272. matches = re.findall(pattern, input_string)
  273. if matches:
  274. return matches[0]
  275. return None
  276. def get_online_model_config(service_name: str):
  277. model_config = {}
  278. model_detail = PAIClient.get_describe_service(service_name)
  279. service_config_str = model_detail['ServiceConfig']
  280. service_config = json.loads(service_config_str)
  281. model_path = service_config['model_path']
  282. model_config['model_path'] = model_path
  283. model_config['online_date'] = extract_date_yyyymmdd(model_path)
  284. return model_config
  285. def bind_shuffle_train_table(table):
  286. draft = PAIClient.get_work_flow_draft(EXPERIMENT_ID)
  287. print(json.dumps(draft, ensure_ascii=False))
  288. content = draft['Content']
  289. version = draft['Version']
  290. content_json = json.loads(content)
  291. nodes = content_json.get('nodes')
  292. for node in nodes:
  293. if node['name'] != '模型训练-样本shufle':
  294. continue
  295. for property in node['properties']:
  296. if property['name'] != 'sql':
  297. continue
  298. new_value = replace_train_tables(property['value'], table)
  299. if new_value is None:
  300. print("error")
  301. property['value'] = new_value
  302. new_content = json.dumps(content_json, ensure_ascii=False)
  303. PAIClient.update_experiment_content(EXPERIMENT_ID, new_content, version)
  304. def wait_job_end(job_id: str, check_interval=300):
  305. while True:
  306. job_detail = PAIClient.get_job_detail(job_id)
  307. print(job_detail)
  308. statue = job_detail['Status']
  309. if (statue == 'Initialized' or statue == 'Starting' or statue == 'WorkflowServiceStarting'
  310. or statue == 'Running' or statue == 'ReadyToSchedule'):
  311. time.sleep(check_interval)
  312. continue
  313. if statue == 'Failed' or statue == 'Terminating' or statue == 'Unknown' or statue == 'Skipped' or statue == 'Succeeded':
  314. return job_detail
  315. def get_node_dict():
  316. draft = PAIClient.get_work_flow_draft(EXPERIMENT_ID)
  317. content = draft['Content']
  318. content_json = json.loads(content)
  319. nodes = content_json.get('nodes')
  320. node_dict = {}
  321. for node in nodes:
  322. name = node['name']
  323. if name in target_names:
  324. node_dict[name] = node['id']
  325. return node_dict
  326. def get_job_dict():
  327. job_dict = {}
  328. jobs_list = PAIClient.get_jobs_list(EXPERIMENT_ID)
  329. for job in jobs_list['Jobs']:
  330. if not is_created_today(job['GmtCreateTime']):
  331. break
  332. job_id = job['JobId']
  333. job_detail = PAIClient.get_job_detail(job_id, verbose=True)
  334. for name in target_names:
  335. if job_detail['Status'] != 'Succeeded':
  336. continue
  337. if name in job_dict:
  338. continue
  339. if name in job_detail['RunInfo']:
  340. job_dict[name] = job_detail['JobId']
  341. return job_dict
  342. @retry
  343. def update_online_flow():
  344. try:
  345. online_model_config = get_online_model_config(ONLINE_SERVICE_NAME)
  346. draft = PAIClient.get_work_flow_draft(EXPERIMENT_ID)
  347. print(json.dumps(draft, ensure_ascii=False))
  348. content = draft['Content']
  349. version = draft['Version']
  350. print(content)
  351. content_json = json.loads(content)
  352. nodes = content_json.get('nodes')
  353. global_params = content_json.get('globalParams')
  354. eval_date = get_eval_date()
  355. train_dates = get_train_dates()
  356. for global_param in global_params:
  357. try:
  358. if global_param['name'] == 'bizdate':
  359. global_param['value'] = eval_date
  360. if global_param['name'] == 'online_version_dt':
  361. global_param['value'] = online_model_config['online_date']
  362. if global_param['name'] == 'eval_date':
  363. global_param['value'] = eval_date
  364. if global_param['name'] == 'online_model_path':
  365. global_param['value'] = online_model_config['model_path']
  366. except KeyError:
  367. raise Exception("在处理全局参数时,字典中缺少必要的键")
  368. for node in nodes:
  369. try:
  370. if node['name'] != '样本shuffle':
  371. continue
  372. for property in node['properties']:
  373. if property['name'] != 'sql':
  374. continue
  375. new_value = replace_sql_dt_in(property['value'], train_dates)
  376. if new_value is None:
  377. print("error")
  378. property['value'] = new_value
  379. except KeyError:
  380. raise Exception("在处理节点属性时,字典中缺少必要的键")
  381. new_content = json.dumps(content_json, ensure_ascii=False)
  382. PAIClient.update_experiment_content(EXPERIMENT_ID, new_content, version)
  383. return True
  384. except json.JSONDecodeError:
  385. raise Exception("JSON 解析错误,可能是草稿内容格式不正确")
  386. except Exception as e:
  387. raise Exception(f"发生未知错误: {e}")
  388. @retry
  389. def shuffle_table():
  390. try:
  391. node_dict = get_node_dict()
  392. if '生成CID文件' not in node_dict:
  393. raise Exception("工作流中未找到节点 生成CID文件")
  394. train_node_id = node_dict['样本shuffle']
  395. execute_type = 'EXECUTE_FROM_HERE'
  396. validate_res = PAIClient.create_job(EXPERIMENT_ID, train_node_id, execute_type)
  397. validate_job_id = validate_res['JobId']
  398. validate_job_detail = wait_job_end(validate_job_id, 10)
  399. if validate_job_detail['Status'] != 'Succeeded':
  400. return False
  401. job_verbose = PAIClient.get_job_detail(validate_job_id, verbose=True)
  402. run_info = job_verbose.get('RunInfo') or ''
  403. if '生成CID文件' not in run_info:
  404. print(f"样本shuffle 未带上生成CID文件, RunInfo={run_info}")
  405. return False
  406. print("样本shuffle 已触发生成CID文件")
  407. return True
  408. except Exception as e:
  409. error_message = f"在执行 shuffle_table 函数时发生异常: {str(e)}"
  410. print(error_message)
  411. raise Exception(error_message)
  412. @retry
  413. def shuffle_train_model():
  414. try:
  415. node_dict = get_node_dict()
  416. job_dict = get_job_dict()
  417. job_id = job_dict['样本shuffle']
  418. validate_job_detail = wait_job_end(job_id)
  419. if validate_job_detail['Status'] == 'Succeeded':
  420. pipeline_run_id = validate_job_detail['RunId']
  421. node_id = validate_job_detail['PaiflowNodeId']
  422. flow_out_put_detail = PAIClient.get_flow_out_put(pipeline_run_id, node_id, 2)
  423. outputs = flow_out_put_detail['Outputs']
  424. table = None
  425. for output in outputs:
  426. if output["Producer"] == node_dict['样本shuffle'] and output["Name"] == "outputTable":
  427. value1 = json.loads(output["Info"]['value'])
  428. table = value1['location']['table']
  429. if table is not None:
  430. bind_shuffle_train_table(table)
  431. node_dict = get_node_dict()
  432. train_node_id = node_dict['模型训练-样本shufle']
  433. execute_type = 'EXECUTE_ONE'
  434. train_res = PAIClient.create_job(EXPERIMENT_ID, train_node_id, execute_type)
  435. train_job_id = train_res['JobId']
  436. train_job_detail = wait_job_end(train_job_id)
  437. if train_job_detail['Status'] == 'Succeeded':
  438. return True
  439. return False
  440. except Exception as e:
  441. error_message = f"在执行 shuffle_train_model 函数时发生异常: {str(e)}"
  442. print(error_message)
  443. raise Exception(error_message)
  444. @retry
  445. def export_model():
  446. try:
  447. node_dict = get_node_dict()
  448. export_node_id = node_dict['模型导出-2']
  449. execute_type = 'EXECUTE_ONE'
  450. export_res = PAIClient.create_job(EXPERIMENT_ID, export_node_id, execute_type)
  451. export_job_id = export_res['JobId']
  452. export_job_detail = wait_job_end(export_job_id)
  453. if export_job_detail['Status'] == 'Succeeded':
  454. return True
  455. return False
  456. except Exception as e:
  457. error_message = f"在执行 export_model 函数时发生异常: {str(e)}"
  458. print(error_message)
  459. raise Exception(error_message)
  460. def update_online_model():
  461. try:
  462. node_dict = get_node_dict()
  463. train_node_id = node_dict['更新EAS服务(Beta)-1']
  464. execute_type = 'EXECUTE_ONE'
  465. train_res = PAIClient.create_job(EXPERIMENT_ID, train_node_id, execute_type)
  466. train_job_id = train_res['JobId']
  467. train_job_detail = wait_job_end(train_job_id)
  468. if train_job_detail['Status'] == 'Succeeded':
  469. return True
  470. return False
  471. except Exception as e:
  472. error_message = f"在执行 update_online_model 函数时发生异常: {str(e)}"
  473. print(error_message)
  474. raise Exception(error_message)
  475. @retry
  476. def get_validate_model_data():
  477. try:
  478. node_dict = get_node_dict()
  479. train_node_id = node_dict['虚拟起始节点']
  480. execute_type = 'EXECUTE_FROM_HERE'
  481. validate_res = PAIClient.create_job(EXPERIMENT_ID, train_node_id, execute_type)
  482. validate_job_id = validate_res['JobId']
  483. validate_job_detail = wait_job_end(validate_job_id)
  484. if validate_job_detail['Status'] == 'Succeeded':
  485. return True
  486. return False
  487. except Exception as e:
  488. error_message = f"在执行 get_validate_model_data 函数时出现异常: {e}"
  489. print(error_message)
  490. raise Exception(error_message)
  491. def validate_model_data_accuracy():
  492. try:
  493. table_dict = {}
  494. node_dict = get_node_dict()
  495. job_dict = get_job_dict()
  496. job_id = job_dict['虚拟起始节点']
  497. validate_job_detail = wait_job_end(job_id)
  498. if validate_job_detail['Status'] == 'Succeeded':
  499. pipeline_run_id = validate_job_detail['RunId']
  500. node_id = validate_job_detail['PaiflowNodeId']
  501. flow_out_put_detail = PAIClient.get_flow_out_put(pipeline_run_id, node_id, 3)
  502. print(flow_out_put_detail)
  503. outputs = flow_out_put_detail['Outputs']
  504. for output in outputs:
  505. if output["Producer"] == node_dict['二分类评估-1'] and output["Name"] == "outputMetricTable":
  506. value1 = json.loads(output["Info"]['value'])
  507. table_dict['二分类评估-1'] = value1['location']['table']
  508. if output["Producer"] == node_dict['二分类评估-2'] and output["Name"] == "outputMetricTable":
  509. value2 = json.loads(output["Info"]['value'])
  510. table_dict['二分类评估-2'] = value2['location']['table']
  511. if output["Producer"] == node_dict['预测结果对比'] and output["Name"] == "outputTable":
  512. value3 = json.loads(output["Info"]['value'])
  513. table_dict['预测结果对比'] = value3['location']['table']
  514. num = 10
  515. df = get_data_from_odps('pai_algo', table_dict['预测结果对比'], 10)
  516. old_abs_avg = df['old_error'].abs().sum() / num
  517. new_abs_avg = df['new_error'].abs().sum() / num
  518. new_auc = get_dict_from_odps('pai_algo', table_dict['二分类评估-1'])['AUC']
  519. old_auc = get_dict_from_odps('pai_algo', table_dict['二分类评估-2'])['AUC']
  520. eval_date = get_eval_date()
  521. score_diff = abs(old_abs_avg - new_abs_avg)
  522. msg = ""
  523. result = False
  524. if new_abs_avg > 0.1:
  525. msg += f'{WORKFLOW_NAME}线上模型评估{eval_date}的数据,绝对误差大于0.1,请检查'
  526. level = 'error'
  527. elif score_diff > 0.02 and new_abs_avg - old_abs_avg > 0.02:
  528. msg += f'{WORKFLOW_NAME}两个模型评估{eval_date}的数据,两个模型分数差异为: {score_diff}, 大于0.02, 请检查'
  529. level = 'error'
  530. else:
  531. msg += f'{WORKFLOW_NAME}广告模型更新完成(训练7天/评估1天)'
  532. level = 'info'
  533. result = True
  534. top10_msg = "| CID | 老模型相对真实CTCVR的变化 | 新模型相对真实CTCVR的变化 |"
  535. top10_msg += "\n| ---- | --------- | -------- |"
  536. for index, row in df.iterrows():
  537. cid = row['cid']
  538. old_error = row['old_error']
  539. new_error = row['new_error']
  540. top10_msg += f"\n| {int(cid)} | {old_error} | {new_error} | "
  541. print(top10_msg)
  542. msg += f"\n\t - 老模型AUC: {old_auc}"
  543. msg += f"\n\t - 新模型AUC: {new_auc}"
  544. msg += f"\n\t - 老模型Top10差异平均值: {old_abs_avg}"
  545. msg += f"\n\t - 新模型Top10差异平均值: {new_abs_avg}"
  546. return result, msg, level, top10_msg
  547. except Exception as e:
  548. error_message = f"在执行 validate_model_data_accuracy 函数时出现异常: {str(e)}"
  549. print(error_message)
  550. raise Exception(error_message)
  551. def update_trained_cids_pointer(model_name=None, dt_version=None):
  552. if not model_name and not dt_version:
  553. draft = PAIClient.get_work_flow_draft(EXPERIMENT_ID)
  554. content = draft['Content']
  555. content_json = json.loads(content)
  556. global_params = content_json.get('globalParams', [])
  557. model_name = None
  558. dt_version = None
  559. for param in global_params:
  560. if param.get('name') == 'model_name':
  561. model_name = param.get('value')
  562. if param.get('name') == 'bizdate':
  563. dt_version = param.get('value')
  564. if not model_name or not dt_version:
  565. raise Exception("globalParams 中未找到 model_name 或 bizdate")
  566. elif not (model_name and dt_version):
  567. raise Exception("model_name 和 dt_version 必须同时提供")
  568. model_version = {}
  569. model_version['modelName'] = f"model_name={model_name}"
  570. model_version['dtVersion'] = f"dt_version={dt_version}"
  571. model_version['timestamp'] = int(time.time())
  572. print(json.dumps(model_version, ensure_ascii=False, indent=4).encode('utf-8'))
  573. bucket_name = "art-recommend"
  574. object_key = "fengzhoutian/pai_model_trained_cids/model_version_v5_1.json"
  575. oss_config = oss.config.load_default()
  576. oss_config.credentials_provider = oss.credentials.StaticCredentialsProvider(
  577. access_key_id=ACCESS_KEY_ID, access_key_secret=ACCESS_KEY_SECRET
  578. )
  579. oss_config.region = "cn-hangzhou"
  580. client = oss.Client(oss_config)
  581. ret = client.put_object(oss.PutObjectRequest(
  582. bucket=bucket_name,
  583. key=object_key,
  584. body=json.dumps(model_version, ensure_ascii=False, indent=4).encode('utf-8')
  585. ))
  586. print(f'oss put status code: {ret.status_code},'
  587. f' request id: {ret.request_id},'
  588. f' content md5: {ret.content_md5},'
  589. f' etag: {ret.etag},'
  590. f' hash crc64: {ret.hash_crc64},'
  591. f' version id: {ret.version_id},'
  592. f' content: {model_version}'
  593. )
  594. if __name__ == '__main__':
  595. start_time = int(time.time())
  596. functions = [update_online_flow, shuffle_table, shuffle_train_model, export_model, get_validate_model_data]
  597. function_names = [func.__name__ for func in functions]
  598. start_function = None
  599. if len(sys.argv) > 1:
  600. start_function = sys.argv[1]
  601. if start_function not in function_names:
  602. print(f"指定的起始函数 {start_function} 不存在,请选择以下函数之一:{', '.join(function_names)}")
  603. sys.exit(1)
  604. start_index = 0
  605. if start_function:
  606. start_index = function_names.index(start_function)
  607. for func in functions[start_index:]:
  608. if not func():
  609. print(f"{func.__name__} 执行失败,后续函数不再执行。")
  610. step_end_time = int(time.time())
  611. elapsed = step_end_time - start_time
  612. _monitor('error', f"{WORKFLOW_NAME}模型更新,{func.__name__} 执行失败,后续函数不再执行,请检查", start_time, elapsed, None)
  613. break
  614. else:
  615. print("所有函数都成功执行,可以继续下一步操作。")
  616. result, msg, level, top10_msg = validate_model_data_accuracy()
  617. if result:
  618. update_online_res = update_online_model()
  619. if update_online_res:
  620. update_trained_cids_pointer()
  621. print("success")
  622. step_end_time = int(time.time())
  623. elapsed = step_end_time - start_time
  624. print(level, msg, start_time, elapsed, top10_msg)
  625. _monitor(level, msg, start_time, elapsed, top10_msg)