pai_flow_operator.py 32 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796
  1. # -*- coding: utf-8 -*-
  2. import functools
  3. import os
  4. import re
  5. import sys
  6. import time
  7. import json
  8. import pandas as pd
  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. '模型训练-样本shufle',
  24. '模型训练-自定义',
  25. '模型增量训练',
  26. '模型导出-2',
  27. '更新EAS服务(Beta)-1',
  28. '虚拟起始节点',
  29. '二分类评估-1',
  30. '二分类评估-2',
  31. '预测结果对比'
  32. }
  33. EXPERIMENT_ID = "draft-wqgkag89sbh9v1zvut"
  34. ACCESS_KEY_ID = "LTAI5tFGqgC8f3mh1fRCrAEy"
  35. ACCESS_KEY_SECRET = "XhOjK9XmTYRhVAtf6yii4s4kZwWzvV"
  36. MAX_RETRIES = 3
  37. def retry(func):
  38. @functools.wraps(func)
  39. def wrapper(*args, **kwargs):
  40. retries = 0
  41. while retries < MAX_RETRIES:
  42. try:
  43. result = func(*args, **kwargs)
  44. if result is not False:
  45. return result
  46. except Exception as e:
  47. print(f"函数 {func.__name__} 执行时发生异常: {e},重试第 {retries + 1} 次")
  48. retries += 1
  49. print(f"函数 {func.__name__} 重试 {MAX_RETRIES} 次后仍失败。")
  50. return False
  51. return wrapper
  52. def get_odps_instance(project):
  53. odps = ODPS(
  54. access_id=ACCESS_KEY_ID,
  55. secret_access_key=ACCESS_KEY_SECRET,
  56. project=project,
  57. endpoint='http://service.cn.maxcompute.aliyun.com/api',
  58. )
  59. return odps
  60. def get_data_from_odps(project, table, num):
  61. odps = get_odps_instance(project)
  62. try:
  63. # 要查询的 SQL 语句
  64. sql = f'select * from {table} limit {num}'
  65. # 执行 SQL 查询
  66. with odps.execute_sql(sql).open_reader() as reader:
  67. df = reader.to_pandas()
  68. # 查询数量小于目标数量时 返回空
  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 语句
  78. sql = f'select * from {table}'
  79. # 执行 SQL 查询
  80. with odps.execute_sql(sql).open_reader() as reader:
  81. data = {}
  82. for record in reader:
  83. record_list = list(record)
  84. key = record_list[0][1]
  85. value = record_list[1][1]
  86. data[key] = value
  87. return data
  88. except Exception as e:
  89. print(f"发生错误: {e}")
  90. def get_dates_between(start_date_str, end_date_str):
  91. start_date = datetime.strptime(start_date_str, '%Y%m%d')
  92. end_date = datetime.strptime(end_date_str, '%Y%m%d')
  93. dates = []
  94. current_date = start_date
  95. while current_date <= end_date:
  96. dates.append(current_date.strftime('%Y%m%d'))
  97. current_date += timedelta(days=1)
  98. return dates
  99. def read_file_to_list():
  100. try:
  101. current_dir = os.getcwd()
  102. file_path = os.path.join(current_dir, 'ad', 'holidays.txt')
  103. with open(file_path, 'r', encoding='utf-8') as file:
  104. content = file.read()
  105. return content.split('\n')
  106. except FileNotFoundError:
  107. raise Exception(f"错误:未找到 {file_path} 文件。")
  108. except Exception as e:
  109. raise Exception(f"错误:发生了一个未知错误: {e}")
  110. return []
  111. def get_previous_days_date(days):
  112. current_date = datetime.now()
  113. previous_date = current_date - timedelta(days=days)
  114. return previous_date.strftime('%Y%m%d')
  115. def remove_elements(lst1, lst2):
  116. return [element for element in lst1 if element not in lst2]
  117. def process_list(lst, append_str):
  118. # 给列表中每个元素拼接相同的字符串
  119. appended_list = [append_str + element for element in lst]
  120. # 将拼接后的列表元素用逗号拼接成一个字符串
  121. result_str = ','.join(appended_list)
  122. return result_str
  123. def get_train_data_list(date_begin):
  124. end_date = get_previous_days_date(2)
  125. date_list = get_dates_between(date_begin, end_date)
  126. filter_date_list = read_file_to_list()
  127. date_list = remove_elements(date_list, filter_date_list)
  128. return date_list
  129. # 只替换第一次匹配的'where dt in ()'中的日期
  130. def update_data_date_range(old_str, date_begin='20250605'):
  131. date_list = get_train_data_list(date_begin)
  132. train_list = ["'" + item + "'" for item in date_list]
  133. result = ','.join(train_list)
  134. start_index = old_str.find('where dt in (')
  135. if start_index != -1:
  136. equal_sign_index = start_index + len('where dt in (')
  137. # 找到下一个双引号的位置
  138. next_quote_index = old_str.find(')', equal_sign_index)
  139. if next_quote_index != -1:
  140. # 进行替换
  141. new_value = old_str[:equal_sign_index] + result + old_str[next_quote_index:]
  142. return new_value
  143. return None
  144. def compare_timestamp_with_today_start(time_str):
  145. # 解析时间字符串为 datetime 对象
  146. time_obj = datetime.fromisoformat(time_str)
  147. # 将其转换为时间戳
  148. target_timestamp = time_obj.timestamp()
  149. # 获取今天开始的时间
  150. today_start = datetime.combine(datetime.now().date(), datetime.min.time())
  151. # 将今天开始时间转换为时间戳
  152. today_start_timestamp = today_start.timestamp()
  153. return target_timestamp > today_start_timestamp
  154. def update_train_table(old_str, table):
  155. address = 'odps://pai_algo/tables/'
  156. train_table = address + table
  157. start_index = old_str.find('-Dtrain_tables="')
  158. if start_index != -1:
  159. # 确定等号的位置
  160. equal_sign_index = start_index + len('-Dtrain_tables="')
  161. # 找到下一个双引号的位置
  162. next_quote_index = old_str.find('"', equal_sign_index)
  163. if next_quote_index != -1:
  164. # 进行替换
  165. new_value = old_str[:equal_sign_index] + train_table + old_str[next_quote_index:]
  166. return new_value
  167. return None
  168. class PAIClient:
  169. def __init__(self):
  170. pass
  171. @staticmethod
  172. def create_client() -> PaiStudio20210202Client:
  173. """
  174. 使用AK&SK初始化账号Client
  175. @return: Client
  176. @throws Exception
  177. """
  178. # 工程代码泄露可能会导致 AccessKey 泄露,并威胁账号下所有资源的安全性。以下代码示例仅供参考。
  179. # 建议使用更安全的 STS 方式,更多鉴权访问方式请参见:https://help.aliyun.com/document_detail/378659.html。
  180. config = open_api_models.Config(
  181. access_key_id=ACCESS_KEY_ID,
  182. access_key_secret=ACCESS_KEY_SECRET
  183. )
  184. # Endpoint 请参考 https://api.aliyun.com/product/PaiStudio
  185. config.endpoint = f'pai.cn-hangzhou.aliyuncs.com'
  186. return PaiStudio20210202Client(config)
  187. @staticmethod
  188. def create_eas_client() -> eas20210701Client:
  189. """
  190. 使用AK&SK初始化账号Client
  191. @return: Client
  192. @throws Exception
  193. """
  194. # 工程代码泄露可能会导致 AccessKey 泄露,并威胁账号下所有资源的安全性。以下代码示例仅供参考。
  195. # 建议使用更安全的 STS 方式,更多鉴权访问方式请参见:https://help.aliyun.com/document_detail/378659.html。
  196. config = open_api_models.Config(
  197. access_key_id=ACCESS_KEY_ID,
  198. access_key_secret=ACCESS_KEY_SECRET
  199. )
  200. # Endpoint 请参考 https://api.aliyun.com/product/PaiStudio
  201. config.endpoint = f'pai-eas.cn-hangzhou.aliyuncs.com'
  202. return eas20210701Client(config)
  203. @staticmethod
  204. def create_flow_client() -> PAIFlow20210202Client:
  205. """
  206. 使用AK&SK初始化账号Client
  207. @return: Client
  208. @throws Exception
  209. """
  210. # 工程代码泄露可能会导致 AccessKey 泄露,并威胁账号下所有资源的安全性。以下代码示例仅供参考。
  211. # 建议使用更安全的 STS 方式,更多鉴权访问方式请参见:https://help.aliyun.com/document_detail/378659.html。
  212. config = open_api_models.Config(
  213. # 必填,请确保代码运行环境设置了环境变量 ALIBABA_CLOUD_ACCESS_KEY_ID。,
  214. access_key_id=ACCESS_KEY_ID,
  215. # 必填,请确保代码运行环境设置了环境变量 ALIBABA_CLOUD_ACCESS_KEY_SECRET。,
  216. access_key_secret=ACCESS_KEY_SECRET
  217. )
  218. # Endpoint 请参考 https://api.aliyun.com/product/PAIFlow
  219. config.endpoint = f'paiflow.cn-hangzhou.aliyuncs.com'
  220. return PAIFlow20210202Client(config)
  221. @staticmethod
  222. def get_work_flow_draft_list(workspace_id: str):
  223. client = PAIClient.create_client()
  224. list_experiments_request = pai_studio_20210202_models.ListExperimentsRequest(
  225. workspace_id=workspace_id
  226. )
  227. runtime = util_models.RuntimeOptions()
  228. headers = {}
  229. try:
  230. resp = client.list_experiments_with_options(list_experiments_request, headers, runtime)
  231. return resp.body.to_map()
  232. except Exception as error:
  233. raise Exception(f"get_work_flow_draft_list error {error}")
  234. @staticmethod
  235. def get_work_flow_draft(experiment_id: str):
  236. client = PAIClient.create_client()
  237. runtime = util_models.RuntimeOptions()
  238. headers = {}
  239. try:
  240. # 复制代码运行请自行打印 API 的返回值
  241. resp = client.get_experiment_with_options(experiment_id, headers, runtime)
  242. return resp.body.to_map()
  243. except Exception as error:
  244. raise Exception(f"get_work_flow_draft error {error}")
  245. @staticmethod
  246. def get_describe_service(service_name: str):
  247. client = PAIClient.create_eas_client()
  248. runtime = util_models.RuntimeOptions()
  249. headers = {}
  250. try:
  251. # 复制代码运行请自行打印 API 的返回值
  252. resp = client.describe_service_with_options('cn-hangzhou', service_name, headers, runtime)
  253. return resp.body.to_map()
  254. except Exception as error:
  255. raise Exception(f"get_describe_service error {error}")
  256. @staticmethod
  257. def update_experiment_content(experiment_id: str, content: str, version: int):
  258. client = PAIClient.create_client()
  259. update_experiment_content_request = pai_studio_20210202_models.UpdateExperimentContentRequest(content=content,
  260. version=version)
  261. runtime = util_models.RuntimeOptions()
  262. headers = {}
  263. try:
  264. # 复制代码运行请自行打印 API 的返回值
  265. resp = client.update_experiment_content_with_options(experiment_id, update_experiment_content_request,
  266. headers, runtime)
  267. print(resp.body.to_map())
  268. except Exception as error:
  269. raise Exception(f"update_experiment_content error {error}")
  270. @staticmethod
  271. def create_job(experiment_id: str, node_id: str, execute_type: str):
  272. client = PAIClient.create_client()
  273. create_job_request = pai_studio_20210202_models.CreateJobRequest()
  274. create_job_request.EXPERIMENT_ID = experiment_id
  275. create_job_request.node_id = node_id
  276. create_job_request.execute_type = execute_type
  277. runtime = util_models.RuntimeOptions()
  278. headers = {}
  279. try:
  280. # 复制代码运行请自行打印 API 的返回值
  281. resp = client.create_job_with_options(create_job_request, headers, runtime)
  282. return resp.body.to_map()
  283. except Exception as error:
  284. raise Exception(f"create_job error {error}")
  285. @staticmethod
  286. def get_jobs_list(experiment_id: str, order='DESC'):
  287. client = PAIClient.create_client()
  288. list_jobs_request = pai_studio_20210202_models.ListJobsRequest(
  289. experiment_id=experiment_id,
  290. order=order
  291. )
  292. runtime = util_models.RuntimeOptions()
  293. headers = {}
  294. try:
  295. # 复制代码运行请自行打印 API 的返回值
  296. resp = client.list_jobs_with_options(list_jobs_request, headers, runtime)
  297. return resp.body.to_map()
  298. except Exception as error:
  299. raise Exception(f"get_jobs_list error {error}")
  300. @staticmethod
  301. def get_job_detail(job_id: str, verbose=False):
  302. client = PAIClient.create_client()
  303. get_job_request = pai_studio_20210202_models.GetJobRequest(
  304. verbose=verbose
  305. )
  306. runtime = util_models.RuntimeOptions()
  307. headers = {}
  308. try:
  309. # 复制代码运行请自行打印 API 的返回值
  310. resp = client.get_job_with_options(job_id, get_job_request, headers, runtime)
  311. return resp.body.to_map()
  312. except Exception as error:
  313. # 此处仅做打印展示,请谨慎对待异常处理,在工程项目中切勿直接忽略异常。
  314. # 错误 message
  315. print(error.message)
  316. # 诊断地址
  317. print(error.data.get("Recommend"))
  318. UtilClient.assert_as_string(error.message)
  319. @staticmethod
  320. def get_flow_out_put(pipeline_run_id: str, node_id: str, depth: int):
  321. client = PAIClient.create_flow_client()
  322. list_pipeline_run_node_outputs_request = paiflow_20210202_models.ListPipelineRunNodeOutputsRequest(
  323. depth=depth
  324. )
  325. runtime = util_models.RuntimeOptions()
  326. headers = {}
  327. try:
  328. # 复制代码运行请自行打印 API 的返回值
  329. resp = client.list_pipeline_run_node_outputs_with_options(pipeline_run_id, node_id,
  330. list_pipeline_run_node_outputs_request, headers,
  331. runtime)
  332. return resp.body.to_map()
  333. except Exception as error:
  334. # 此处仅做打印展示,请谨慎对待异常处理,在工程项目中切勿直接忽略异常。
  335. # 错误 message
  336. print(error.message)
  337. # 诊断地址
  338. print(error.data.get("Recommend"))
  339. UtilClient.assert_as_string(error.message)
  340. def extract_date_yyyymmdd(input_string):
  341. pattern = r'\d{8}'
  342. matches = re.findall(pattern, input_string)
  343. if matches:
  344. return matches[0]
  345. return None
  346. def get_online_model_config(service_name: str):
  347. model_config = {}
  348. model_detail = PAIClient.get_describe_service(service_name)
  349. service_config_str = model_detail['ServiceConfig']
  350. service_config = json.loads(service_config_str)
  351. model_path = service_config['model_path']
  352. model_config['model_path'] = model_path
  353. online_date = extract_date_yyyymmdd(model_path)
  354. model_config['online_date'] = online_date
  355. return model_config
  356. def update_shuffle_flow(table):
  357. draft = PAIClient.get_work_flow_draft(EXPERIMENT_ID)
  358. print(json.dumps(draft, ensure_ascii=False))
  359. content = draft['Content']
  360. version = draft['Version']
  361. content_json = json.loads(content)
  362. nodes = content_json.get('nodes')
  363. for node in nodes:
  364. name = node['name']
  365. if name == '模型训练-样本shufle':
  366. properties = node['properties']
  367. for property in properties:
  368. if property['name'] == 'sql':
  369. value = property['value']
  370. new_value = update_train_table(value, table)
  371. if new_value is None:
  372. print("error")
  373. property['value'] = new_value
  374. new_content = json.dumps(content_json, ensure_ascii=False)
  375. PAIClient.update_experiment_content(EXPERIMENT_ID, new_content, version)
  376. def update_shuffle_flow_1():
  377. draft = PAIClient.get_work_flow_draft(EXPERIMENT_ID)
  378. print(json.dumps(draft, ensure_ascii=False))
  379. content = draft['Content']
  380. version = draft['Version']
  381. print(content)
  382. content_json = json.loads(content)
  383. nodes = content_json.get('nodes')
  384. for node in nodes:
  385. name = node['name']
  386. if name == '模型训练-样本shufle':
  387. properties = node['properties']
  388. for property in properties:
  389. if property['name'] == 'sql':
  390. value = property['value']
  391. new_value = update_data_date_range(value)
  392. if new_value is None:
  393. print("error")
  394. property['value'] = new_value
  395. new_content = json.dumps(content_json, ensure_ascii=False)
  396. PAIClient.update_experiment_content(EXPERIMENT_ID, new_content, version)
  397. def wait_job_end(job_id: str, check_interval=300):
  398. while True:
  399. job_detail = PAIClient.get_job_detail(job_id)
  400. print(job_detail)
  401. statue = job_detail['Status']
  402. # Initialized: 初始化完成 Starting:开始 WorkflowServiceStarting:准备提交 Running:运行中 ReadyToSchedule:准备运行(前序节点未完成导致)
  403. if (statue == 'Initialized' or statue == 'Starting' or statue == 'WorkflowServiceStarting'
  404. or statue == 'Running' or statue == 'ReadyToSchedule'):
  405. time.sleep(check_interval)
  406. continue
  407. # Failed:运行失败 Terminating:终止中 Terminated:已终止 Unknown:未知 Skipped:跳过(前序节点失败导致) Succeeded:运行成功
  408. if statue == 'Failed' or statue == 'Terminating' or statue == 'Unknown' or statue == 'Skipped' or statue == 'Succeeded':
  409. return job_detail
  410. def get_node_dict():
  411. draft = PAIClient.get_work_flow_draft(EXPERIMENT_ID)
  412. content = draft['Content']
  413. content_json = json.loads(content)
  414. nodes = content_json.get('nodes')
  415. node_dict = {}
  416. for node in nodes:
  417. name = node['name']
  418. # 检查名称是否在目标名称集合中
  419. if name in target_names:
  420. node_dict[name] = node['id']
  421. return node_dict
  422. def get_job_dict():
  423. job_dict = {}
  424. jobs_list = PAIClient.get_jobs_list(EXPERIMENT_ID)
  425. for job in jobs_list['Jobs']:
  426. # 解析时间字符串为 datetime 对象
  427. if not compare_timestamp_with_today_start(job['GmtCreateTime']):
  428. break
  429. job_id = job['JobId']
  430. job_detail = PAIClient.get_job_detail(job_id, verbose=True)
  431. for name in target_names:
  432. if job_detail['Status'] != 'Succeeded':
  433. continue
  434. if name in job_dict:
  435. continue
  436. if name in job_detail['RunInfo']:
  437. job_dict[name] = job_detail['JobId']
  438. return job_dict
  439. @retry
  440. def update_online_flow():
  441. try:
  442. online_model_config = get_online_model_config('ad_rank_dnn_v11_easyrec')
  443. draft = PAIClient.get_work_flow_draft(EXPERIMENT_ID)
  444. print(json.dumps(draft, ensure_ascii=False))
  445. content = draft['Content']
  446. version = draft['Version']
  447. print(content)
  448. content_json = json.loads(content)
  449. nodes = content_json.get('nodes')
  450. global_params = content_json.get('globalParams')
  451. bizdate = get_previous_days_date(1)
  452. for global_param in global_params:
  453. try:
  454. if global_param['name'] == 'bizdate':
  455. global_param['value'] = bizdate
  456. if global_param['name'] == 'online_version_dt':
  457. global_param['value'] = online_model_config['online_date']
  458. if global_param['name'] == 'eval_date':
  459. global_param['value'] = bizdate
  460. if global_param['name'] == 'online_model_path':
  461. global_param['value'] = online_model_config['model_path']
  462. except KeyError:
  463. raise Exception("在处理全局参数时,字典中缺少必要的键")
  464. for node in nodes:
  465. try:
  466. name = node['name']
  467. if name in ('样本shuffle',):
  468. date_begin = '20250605' if name == '样本shuffle' else get_previous_days_date(10)
  469. properties = node['properties']
  470. for property in properties:
  471. if property['name'] == 'sql':
  472. value = property['value']
  473. new_value = update_data_date_range(value, date_begin)
  474. if new_value is None:
  475. print("error")
  476. property['value'] = new_value
  477. except KeyError:
  478. raise Exception("在处理节点属性时,字典中缺少必要的键")
  479. new_content = json.dumps(content_json, ensure_ascii=False)
  480. PAIClient.update_experiment_content(EXPERIMENT_ID, new_content, version)
  481. return True
  482. except json.JSONDecodeError:
  483. raise Exception("JSON 解析错误,可能是草稿内容格式不正确")
  484. except Exception as e:
  485. raise Exception(f"发生未知错误: {e}")
  486. @retry
  487. def shuffle_table():
  488. try:
  489. node_dict = get_node_dict()
  490. train_node_id = node_dict['样本shuffle']
  491. execute_type = 'EXECUTE_FROM_HERE'
  492. validate_res = PAIClient.create_job(EXPERIMENT_ID, train_node_id, execute_type)
  493. validate_job_id = validate_res['JobId']
  494. validate_job_detail = wait_job_end(validate_job_id, 10)
  495. if validate_job_detail['Status'] == 'Succeeded':
  496. return True
  497. return False
  498. except Exception as e:
  499. error_message = f"在执行 shuffle_table 函数时发生异常: {str(e)}"
  500. print(error_message)
  501. raise Exception(error_message)
  502. @retry
  503. def shuffle_train_model():
  504. try:
  505. node_dict = get_node_dict()
  506. job_dict = get_job_dict()
  507. job_id = job_dict['样本shuffle']
  508. validate_job_detail = wait_job_end(job_id)
  509. if validate_job_detail['Status'] == 'Succeeded':
  510. pipeline_run_id = validate_job_detail['RunId']
  511. node_id = validate_job_detail['PaiflowNodeId']
  512. flow_out_put_detail = PAIClient.get_flow_out_put(pipeline_run_id, node_id, 2)
  513. outputs = flow_out_put_detail['Outputs']
  514. table = None
  515. for output in outputs:
  516. if output["Producer"] == node_dict['样本shuffle'] and output["Name"] == "outputTable":
  517. value1 = json.loads(output["Info"]['value'])
  518. table = value1['location']['table']
  519. if table is not None:
  520. update_shuffle_flow(table)
  521. node_dict = get_node_dict()
  522. train_node_id = node_dict['模型训练-样本shufle']
  523. execute_type = 'EXECUTE_ONE'
  524. train_res = PAIClient.create_job(EXPERIMENT_ID, train_node_id, execute_type)
  525. train_job_id = train_res['JobId']
  526. train_job_detail = wait_job_end(train_job_id)
  527. if train_job_detail['Status'] == 'Succeeded':
  528. return True
  529. return False
  530. except Exception as e:
  531. error_message = f"在执行 shuffle_train_model 函数时发生异常: {str(e)}"
  532. print(error_message)
  533. raise Exception(error_message)
  534. @retry
  535. def export_model():
  536. try:
  537. node_dict = get_node_dict()
  538. export_node_id = node_dict['模型导出-2']
  539. execute_type = 'EXECUTE_ONE'
  540. export_res = PAIClient.create_job(EXPERIMENT_ID, export_node_id, execute_type)
  541. export_job_id = export_res['JobId']
  542. export_job_detail = wait_job_end(export_job_id)
  543. if export_job_detail['Status'] == 'Succeeded':
  544. return True
  545. return False
  546. except Exception as e:
  547. error_message = f"在执行 export_model 函数时发生异常: {str(e)}"
  548. print(error_message)
  549. raise Exception(error_message)
  550. def update_online_model():
  551. try:
  552. node_dict = get_node_dict()
  553. train_node_id = node_dict['更新EAS服务(Beta)-1']
  554. execute_type = 'EXECUTE_ONE'
  555. train_res = PAIClient.create_job(EXPERIMENT_ID, train_node_id, execute_type)
  556. train_job_id = train_res['JobId']
  557. train_job_detail = wait_job_end(train_job_id)
  558. if train_job_detail['Status'] == 'Succeeded':
  559. return True
  560. return False
  561. except Exception as e:
  562. error_message = f"在执行 update_online_model 函数时发生异常: {str(e)}"
  563. print(error_message)
  564. raise Exception(error_message)
  565. @retry
  566. def get_validate_model_data():
  567. try:
  568. node_dict = get_node_dict()
  569. train_node_id = node_dict['虚拟起始节点']
  570. execute_type = 'EXECUTE_FROM_HERE'
  571. validate_res = PAIClient.create_job(EXPERIMENT_ID, train_node_id, execute_type)
  572. validate_job_id = validate_res['JobId']
  573. validate_job_detail = wait_job_end(validate_job_id)
  574. if validate_job_detail['Status'] == 'Succeeded':
  575. return True
  576. return False
  577. except Exception as e:
  578. error_message = f"在执行 get_validate_model_data 函数时出现异常: {e}"
  579. print(error_message)
  580. raise Exception(error_message)
  581. def validate_model_data_accuracy():
  582. try:
  583. table_dict = {}
  584. node_dict = get_node_dict()
  585. job_dict = get_job_dict()
  586. job_id = job_dict['虚拟起始节点']
  587. validate_job_detail = wait_job_end(job_id)
  588. if validate_job_detail['Status'] == 'Succeeded':
  589. pipeline_run_id = validate_job_detail['RunId']
  590. node_id = validate_job_detail['PaiflowNodeId']
  591. flow_out_put_detail = PAIClient.get_flow_out_put(pipeline_run_id, node_id, 3)
  592. print(flow_out_put_detail)
  593. outputs = flow_out_put_detail['Outputs']
  594. for output in outputs:
  595. if output["Producer"] == node_dict['二分类评估-1'] and output["Name"] == "outputMetricTable":
  596. value1 = json.loads(output["Info"]['value'])
  597. table_dict['二分类评估-1'] = value1['location']['table']
  598. if output["Producer"] == node_dict['二分类评估-2'] and output["Name"] == "outputMetricTable":
  599. value2 = json.loads(output["Info"]['value'])
  600. table_dict['二分类评估-2'] = value2['location']['table']
  601. if output["Producer"] == node_dict['预测结果对比'] and output["Name"] == "outputTable":
  602. value3 = json.loads(output["Info"]['value'])
  603. table_dict['预测结果对比'] = value3['location']['table']
  604. num = 10
  605. df = get_data_from_odps('pai_algo', table_dict['预测结果对比'], 10)
  606. # 对指定列取绝对值再求和
  607. old_abs_avg = df['old_error'].abs().sum() / num
  608. new_abs_avg = df['new_error'].abs().sum() / num
  609. new_auc = get_dict_from_odps('pai_algo', table_dict['二分类评估-1'])['AUC']
  610. old_auc = get_dict_from_odps('pai_algo', table_dict['二分类评估-2'])['AUC']
  611. bizdate = get_previous_days_date(1)
  612. score_diff = abs(old_abs_avg - new_abs_avg)
  613. msg = ""
  614. result = False
  615. if new_abs_avg > 0.1:
  616. msg += f'线上模型评估{bizdate}的数据,绝对误差大于0.1,请检查'
  617. level = 'error'
  618. elif score_diff > 0.05 and new_abs_avg - old_abs_avg > 0.05:
  619. msg += f'两个模型评估${bizdate}的数据,两个模型分数差异为: ${score_diff}, 大于0.05, 请检查'
  620. level = 'error'
  621. else:
  622. msg += 'DNN广告模型更新完成'
  623. level = 'info'
  624. result = True
  625. # 初始化表格头部
  626. top10_msg = "| CID | 老模型相对真实CTCVR的变化 | 新模型相对真实CTCVR的变化 |"
  627. top10_msg += "\n| ---- | --------- | -------- |"
  628. for index, row in df.iterrows():
  629. # 获取指定列的元素
  630. cid = row['cid']
  631. old_error = row['old_error']
  632. new_error = row['new_error']
  633. top10_msg += f"\n| {int(cid)} | {old_error} | {new_error} | "
  634. print(top10_msg)
  635. msg += f"\n\t - 老模型AUC: {old_auc}"
  636. msg += f"\n\t - 新模型AUC: {new_auc}"
  637. msg += f"\n\t - 老模型Top10差异平均值: {old_abs_avg}"
  638. msg += f"\n\t - 新模型Top10差异平均值: {new_abs_avg}"
  639. return result, msg, level, top10_msg
  640. except Exception as e:
  641. error_message = f"在执行 validate_model_data_accuracy 函数时出现异常: {str(e)}"
  642. print(error_message)
  643. raise Exception(error_message)
  644. def update_trained_cids_pointer(model_name=None, dt_version=None):
  645. # 如均为空,则从工作流中获取
  646. if not model_name and not dt_version:
  647. draft = PAIClient.get_work_flow_draft(EXPERIMENT_ID)
  648. content = draft['Content']
  649. content_json = json.loads(content)
  650. global_params = content_json.get('globalParams', [])
  651. model_name = None
  652. dt_version = None
  653. for param in global_params:
  654. if param.get('name') == 'model_name':
  655. model_name = param.get('value')
  656. if param.get('name') == 'bizdate':
  657. dt_version = param.get('value')
  658. if not model_name or not dt_version:
  659. raise Exception("globalParams 中未找到 model_name 或 bizdate")
  660. elif not (model_name and dt_version):
  661. # 不允许其中一个为空
  662. raise Exception("model_name 和 dt_version 必须同时提供")
  663. model_version = {}
  664. model_version['modelName'] = f"model_name={model_name}"
  665. model_version['dtVersion'] = f"dt_version={dt_version}"
  666. model_version['timestamp'] = int(time.time())
  667. print(json.dumps(model_version, ensure_ascii=False, indent=4).encode('utf-8'))
  668. bucket_name = "art-recommend"
  669. object_key = "fengzhoutian/pai_model_trained_cids/model_version.json"
  670. oss_config = oss.config.load_default()
  671. oss_config.credentials_provider = oss.credentials.StaticCredentialsProvider(
  672. access_key_id=ACCESS_KEY_ID, access_key_secret=ACCESS_KEY_SECRET
  673. )
  674. oss_config.region = "cn-hangzhou"
  675. client = oss.Client(oss_config)
  676. ret = client.put_object(oss.PutObjectRequest(
  677. bucket=bucket_name,
  678. key=object_key,
  679. body=json.dumps(model_version, ensure_ascii=False, indent=4).encode('utf-8')
  680. ))
  681. print(f'status code: {ret.status_code},'
  682. f' request id: {ret.request_id},'
  683. f' content md5: {ret.content_md5},'
  684. f' etag: {ret.etag},'
  685. f' hash crc64: {ret.hash_crc64},'
  686. f' version id: {ret.version_id},'
  687. )
  688. if __name__ == '__main__':
  689. start_time = int(time.time())
  690. functions = [update_online_flow, shuffle_table, shuffle_train_model, export_model, get_validate_model_data]
  691. function_names = [func.__name__ for func in functions]
  692. start_function = None
  693. if len(sys.argv) > 1:
  694. start_function = sys.argv[1]
  695. if start_function not in function_names:
  696. print(f"指定的起始函数 {start_function} 不存在,请选择以下函数之一:{', '.join(function_names)}")
  697. sys.exit(1)
  698. start_index = 0
  699. if start_function:
  700. start_index = function_names.index(start_function)
  701. for func in functions[start_index:]:
  702. if not func():
  703. print(f"{func.__name__} 执行失败,后续函数不再执行。")
  704. step_end_time = int(time.time())
  705. elapsed = step_end_time - start_time
  706. _monitor('error', f"DNN模型更新,{func.__name__} 执行失败,后续函数不再执行,请检查", start_time, elapsed, None)
  707. break
  708. else:
  709. print("所有函数都成功执行,可以继续下一步操作。")
  710. result, msg, level, top10_msg = validate_model_data_accuracy()
  711. if result:
  712. update_online_res = update_online_model()
  713. if update_online_res:
  714. update_trained_cids_pointer()
  715. print("success")
  716. step_end_time = int(time.time())
  717. elapsed = step_end_time - start_time
  718. print(level, msg, start_time, elapsed, top10_msg)
  719. _monitor(level, msg, start_time, elapsed, top10_msg)