【完结】囚生CYのMemo(20231118~20231230)


20231118

  • 日常八点醒,但天气转冷并不想早起(其实这周四周五都差点迟到,都是因为天冷赖床,本来每天赶8点53分的车,还有一班8点56分兜底,周四连后者都错过了,结果9点30分30秒险些没打到卡,周五是因为在18号线上发消息坐过站到迎春路,当时慌得一批,想想还是只能回头转9号线,好在9点29分多打到卡),吃点东西回去睡回笼觉,结果第二次醒来已是十二点,下午还要陪嘉伟拉长距离,赶紧收拾东西去学校。
  • 阳光明媚,6~12℃,最喜欢冬天这样的天气,感觉有用不完的力量。陪嘉伟做上马前最后一次30km拉练,我只跟随了前15km,配速4’44",心率155bpm,对我来说是很满意的数据,因为周四蛙跳没热身,大腿酸痛得厉害,并不想太苛求自己跑更多。事实上现在我压住75%的最大心率就可以达到4’45"左右的配速,远远优于之前的水平,跑得非常轻松,12km后依然可以轻松聊天,如果不是因为跑前已处战损状态,这样的天气我能一路跑到天黑。
  • 不熟悉马拉松的人可能不知道,半马和全马是两种完全不同的运动,前者对于我们这样的业余跑者是可以一口气不补给不停歇地冲下来,与普通跑个10km并没有太大区别,无非是配速慢10~15秒。但是全马过了30km,任何人,哪怕是最顶尖的职业运动员,都会极其痛苦,因为30km之后所有人体内储存的糖原都消耗殆尽,能量补充大多来源于脂肪,但此时距离终点还有10km以上的路程,这是最令人绝望的一段路程。都说马拉松从30km才刚刚开始,绝大多数业余跑者30km之后配速会显著下降。如果这段遇到长上坡、过大桥,很可能就会转为走走停停,因为意志一旦被冲垮,就很难重建。
  • 我也认为全马是反人类的项目,但是终有一天我也一定会完赛全马。后AK时代,嘉伟是第一个将要参加全马的学生,首马即上马,壮志凌云,剑指破三,当共勉之。

m3e-base的中文嵌入效果确实是非常好,最近直接拿来不作任何微调,就可以把很脏的招聘信息title直接匹配到职业大典里的标准职业字段,精确度非常高,远远超过之前用的许多基于规则以及应用序列距离的方法。另外还有两个近期的中文预训练模型也很不错,bgestella,但是模型比m3e大很多,m3e基本上只有半个标准BERT的大小,CPU上也能跑得很快。

最后,该死的textarea问题终于解决:

# -*- coding: utf-8 -*-
# @author: caoyang
# @email: caoyang@163.sufe.edu.cn

import os
import re
import time
import logging
import requests

from bs4 import BeautifulSoup
from selenium import webdriver
from selenium.webdriver.support.ui import WebDriverWait
from selenium.webdriver.support import expected_conditions

class BaseCrawler:
	tag_regex = re.compile(r"<[^>]+>|\n|\t")
	global_timeout = 60
	global_interval = 300
	chrome_user_data_path = r"C:\Users\caoyang\AppData\Local\Google\Chrome\User Data"
	# Convert request headers copied from Firefox to dictionary
	@classmethod
	def headers_to_dict(cls, headers: str) -> dict:
		lines = headers.splitlines()
		headers_dict = {}
		for line in lines:
			key, value = line.strip().split(':', 1)
			headers_dict[key.strip()] = value.strip()
		return headers_dict

	# Easy use of WebDriverWait
	@classmethod
	def check_element_by_xpath(cls, driver, xpath, timeout=30):
		WebDriverWait(driver, timeout).until(lambda _driver: _driver.find_element_by_xpath(xpath).is_displayed())
	
	# @param method: e.g. GET, POST
	def easy_requests(self, method, url, **kwargs):
		while True:
			try:
				response = requests.request(method, url, **kwargs)
				break
			except Exception as e:
				logging.warning(f"Error {method} {url}, exception information: {e}")
				logging.warning(f"Wait for {self.global_interval} seconds ...")
				time.sleep(self.global_interval)
		return response

	# Initialize driver
	def initialize_driver(self, browser="chrome", headless=True, timeout=60, **kwargs):
		browser = browser.lower()
		assert browser in ["chrome", "firefox"], f"Unknown browser name: {browser}"
		return eval(f"self._initialize_{browser}_driver")(headless, timeout, **kwargs)
	
	# Initialize Google Chrome driver
	def _initialize_chrome_driver(self, headless, timeout, **kwargs):
		chrome_options = webdriver.ChromeOptions()		
		chrome_options.add_argument(f"user-data-dir={self.chrome_user_data_path}")	# Import user data
		if headless:
			chrome_options.add_argument("--headless")
		driver = webdriver.Chrome(chrome_options=chrome_options)
		driver.set_page_load_timeout(timeout)
		if not headless:
			driver.maximize_window()
		return driver

	# Initialize Mozilla Firefox driver
	def _initialize_firefox_driver(self, headless, timeout, **kwargs):
		options = webdriver.FirefoxOptions()
		if headless:
			options.add_argument("--headless")
		driver = webdriver.Firefox(options=options)
		driver.set_page_load_timeout(timeout)
		if not headless:
			driver.maximize_window()
		return driver

	# Get cookies by driver
	def get_cookies(self, url, driver=None, browser="chrome"):
		quit_flag = False
		if driver is None:
			# If there is no driver passed
			quit_flag = True
			driver = self.initialize_driver(browser=browser, headless=True, timeout=30)
		driver.get(url)
		cookies = driver.get_cookies()
		def _cookie_to_string(_cookies):
			_string = str()
			for _cookie in _cookies:
				_name = _cookie["name"]
				_value = _cookie["value"].replace(' ', '%20') # %20 refers to space char in HTML
				_string += f"{_name}={_value};"
			return _string.strip()
		if quit_flag:
			driver.quit()
		return _cookie_to_string(cookies)


class ChatGLMCrawler(BaseCrawler):
	urls = {"home": "https://chatglm.cn/main/detail",	# Home URL
			}
	layout_xpaths = {"input-box"			: "//textarea[@class=\"scroll-display-none\"]",					# XPath of the input box for human
					 # "input-box"			: "//div[@class=\"input-box-inner\"]",							# XPath of the input box for human (div tag cannot be interacted)
					 "send-button-1"		: "//img[@class=\"enter_icon\"]",								# XPath of the button to send text of input box
					 "send-button-2"		: "//div[@class=\"enter\"]",									# XPath of the button to send text of input box
					 "chat-area"			: "//div[@id=\"session-container\"]",							# XPath of the chat area which contains all the talks (consist of several chat boxes)
					 "human-box"			: "//div[@class=\"pr\"]",										# XPath of human chat box
					 "ai-box"				: "//div[@class=\"answer-content-wrap\"]",						# XPath of AI chat box
					 "ai-box-text"			: "//div[@class=\"markdown-body\"]",							# XPath of the text contained in AI chat box
					 "create-new-button"	: "//div[@class=\"new-session-button\"]",						# XPath of create new talk
					 "like-or-dislike-area"	: "//div[@class=\"interact-operate\"]",							# XPath of div tag contains like and dislike icons
					 "delete-session"		: "//span[@class=\"btn delete\"]",								# XPath of button to delete old talk
					 }
	forbidden_strings = []
	def __init__(self):
		super(ChatGLMCrawler, self).__init__()

	# @param driver		: WebDriver object
	# @param prompt		: The question you would like to ask AI
	# @param model_name	: One of the key in `model_card_xpath`, e.g. "chatgpt3.5(16k)"
	def request(self, driver, prompt, first_trial=True):
		prompt = prompt.replace('\n', "\\n")
		if first_trial:
			driver.get(self.urls["home"])
		self.check_element_by_xpath(driver, xpath=self.layout_xpaths["input-box"], timeout=60)		# Check if input box is rendered
		self.check_element_by_xpath(driver, xpath=self.layout_xpaths["send-button-1"], timeout=60)	# Check if send button is rendered
		self.check_element_by_xpath(driver, xpath=self.layout_xpaths["send-button-2"], timeout=60)	# Check if send button is rendered
		# Delete old talk
		try:
			driver.find_element_by_xpath(self.layout_xpaths["delete-session"]).click()
			logging.info("Delete old talk ...")
		except:
			logging.info("No old talk found ...")
		# Request
		logging.info("Prompt ...")
		try:
			## Method 1: Use `element.send_keys`
			driver.find_element_by_xpath(self.layout_xpaths["input-box"]).send_keys(prompt)	# Input the given prompt
			logging.info("  - ok!")
		except:
			# ## Method 2: Use Javascript with one auguments (Fail)
			# js = """var txt = arguments[0]; document.getElementsByTagName("textarea")[0].value = txt;"""		
			# driver.execute_script(js, prompt)
			# logging.info("  - Use Javascript to input ...")

			# # Method 3: Use Javascript with event dispatch (Fail)
			# js = """var elm = arguments[0], txt = arguments[1]; elm.value += txt; elm.dispatchEvent(new Event('change'));"""
			# element = driver.find_element_by_xpath(self.layout_xpaths["input-box"])
			# driver.execute_script(js, element, prompt)
			# logging.info("  - Use Javascript to input ...")

			# # Method 4: Use keyboard operation (Success)
			# import pyperclip
			# pyperclip.copy(prompt)
			# time.sleep(1)
			# driver.find_element_by_xpath(self.layout_xpaths["input-box"]).send_keys(Keys.CONTROL, 'v')
			# logging.info("  - Use keyboard to input ...")

			# Method 5: Use Javascript with DispatchEvent (Success)
			js = """var txt = arguments[0];
			const textarea = $("textarea");
			var nativeTextAreaValueSetter = Object.getOwnPropertyDescriptor(window.HTMLTextAreaElement.prototype, "value").set;
			nativeTextAreaValueSetter.call(textarea, txt);
			const event = new Event("input", {bubbles: true});
			textarea.dispatchEvent(event);"""
			driver.execute_script(js, prompt)
			logging.info("  - Use Javascript to input ...")

		while True:
			# The button is dynamic and sometimes fail to click on
			try:
				driver.find_element_by_xpath(self.layout_xpaths["send-button-1"]).click()		# Click on the button to send the prompt
				logging.info("Use send button 1 ...")
				break
			except:
				try:
					driver.find_element_by_xpath(self.layout_xpaths["send-button-2"]).click()	# Click on the button to send the prompt
					logging.info("Use send button 2 ...")
					break
				except:
					logging.info("Use send button error ...")
					raise Exception("Use send button error ...")
		# Wait for response
		self.check_element_by_xpath(driver, xpath=self.layout_xpaths["chat-area"], timeout=30)	# Check if chat area is rendered
		self.check_element_by_xpath(driver, xpath=self.layout_xpaths["human-box"], timeout=30)	# Check if human chat box is rendered
		self.check_element_by_xpath(driver, xpath=self.layout_xpaths["ai-box"], timeout=30)		# Check if AI chat box is rendered
		finish_flag = True	# Indicating if AI generation is finished
		while finish_flag:
			try:
				# If like or dislike appear, then stop
				driver.find_element_by_xpath(self.layout_xpaths["like-or-dislike-area"])
				finish_flag = False
			except:
				ai_box_text = driver.find_element_by_xpath(self.layout_xpaths["ai-box-text"])						# Find AI response text element
				# ai_box_text = driver.find_element_by_xpath(self.layout_xpaths["ai-box"])							# Find AI response text element
				ai_box_text_inner_html = ai_box_text.get_attribute("innerHTML")										# Get inner HTML of the element
				response = self.tag_regex.sub(str(), ai_box_text_inner_html).strip("\n\t ").replace('\n', '\\n')	# Process response text
				forbidden_flags = [forbidden_string in response for forbidden_string in self.forbidden_strings]
				if sum(forbidden_flags) > 0:
					# It indicates that a forbidden string occurs
					finish_flag = False
		# Extract AI response text
		ai_box_text = driver.find_element_by_xpath(self.layout_xpaths["ai-box-text"])	# Find AI response text element
		ai_box_text_inner_html = ai_box_text.get_attribute("innerHTML")					# Get inner HTML of the element
		response = self.tag_regex.sub(str(), ai_box_text_inner_html).strip("\n\t ")		# Process response text
		return response

	# @param data_path: EXCEL file of job descriptions
	# @param save_path: file path for storing AI response
	# @param model_name: defined in model_card_xpaths
	def demo(self, model_name="chatgpt3.5(16k)"):
		driver = self.initialize_driver(browser="chrome", headless=False, timeout=60)
		driver.implicitly_wait(15) # 
		prompt = "给我讲述一下《基督山伯爵》的故事,500字左右。"
		response = self.request(driver, prompt)
		with open(f"d:/answer-chatglm.txt", 'w', encoding="utf8") as f:
			f.write(response)
		time.sleep(5)
		driver.quit()

if __name__ == "__main__":
	crawler = ChatGLMCrawler()
	crawler.demo()

20231119~20231120

  • 昨天擦眼镜把眼镜擦断了,从鼻梁架中间断成两截,去年暑期在丹阳眼镜城买的,戴了一年多,真是假冒伪劣,每次店家都说镜架是钛合金还是啥,绝对够硬,那家店叫司徒吧。还好当时一起买了副备用的,结果找到它带上觉得度数特别深,晕得很,真是纳了闷,一起配的眼镜怎么度数还有差别呢… 本来坏掉的这副是可以折叠镜腿,专门买来跑步戴的,后来嫌麻烦不想换来换去就一直带的这副,现在坏了以后就只好不带眼镜跑了。想搞个那种橙色的护目镜,看起来贼好看,看AK跑步经常戴,像超人一样。
  • 今日跑休,周末两天练到位,昨天也干了16圈,从4’50"加速到3’30",均配4’23",真的很舒服,冬天只要跑5圈热起来,后面就是怎么跑都不会累。(现在吊杆一分钟比较轻松了,在试着拉一拉,好想有一天能拉起一个引体向上)
  • Rebuttling… 9月投稿又到开奖时刻…
  • 半年没登微博,午休没忍住看了一眼,原来天池就在杭州,这么近,我还以为在东北。看起来真的很棒,年轻就应该多出去看一看,等我顺利毕业后,我一定要抽一段时间认真训练,去跑个越野赛,看看不同的风景,年轻时要是做不到,等老了只能望山兴叹了。

编辑距离与bleu-score计算(为什么Levenshtein包算编辑距离这么慢,还不如自己写个带cache的递归函数):

import gc
from functools import lru_cache
from nltk.translate.bleu_score import sentence_bleu, SmoothingFunction

# 编辑距离
def calc_edit_distance(a, b):
    @lru_cache()
    def _easy_distance(i, j):
        if min(i, j) == 0:
            return max(i, j)
        else:
            return min(_easy_distance(i - 1, j) + 1, _easy_distance(i, j - 1) + 1, _easy_distance(i - 1, j - 1) + (a[i - 1] != b[j - 1]))
    return _easy_distance(len(a), len(b))

def calc_bleu_score(reference, candidate):
    # return sentence_bleu(reference, candidate, weights=(.5, .5), smoothing_function=SmoothingFunction().method1)
    return sentence_bleu(reference, candidate, weights=(.25, .25, .25, .25), smoothing_function=SmoothingFunction().method1)

关于sentence_bleu里的平滑函数,没有细看,但是一般是用method1,用于平滑掉分母是零的情况。


20231121~20231123

  • 最近两天很疲劳,另一副眼镜度数应该是一样的,但就是带得很累,我走路和在地铁上都得把眼镜摘了才舒服些。
  • 诸事不利,前天晚上回去又找不到钥匙开门(这已经是搬完家两个月里第二次找不到钥匙了),想想回来在学校走了好多地方,可能在操场也可能在食堂,时间太晚只能第二天早上去找。第二天觉得还是先去最后接水的地方大活看了一下,结果一下子就在水箱旁边的洗手台上找到了。(因为我上一次找不到钥匙也是丢在大活厕所的洗手台上,不知道为什么总是丢在这个位置…)
  • 昨天升温,晚上短裤上了点强度,4’10"均配跑了5km(前2k是4’25",后面冲的4分配)。前天陪董祖浩摇了5km,穿的羊毛裤瞎跑跑,但是董祖浩可不是瞎跑啊,4’30"配速跑了10km,我是跟了他最后5km,而且看他嘴巴紧闭,只用鼻子呼吸,这是有bear而来啊,这小子以前练短跑的,高百填预计成绩填个55分钟,结果正赛跑了47分半,不过就前晚这情况看,跑进45分钟绰绰有余,能跑到四十二三分也不奇怪,小伙子高百肯定还是放水了。
  • 距离上马还有不到三天,起点很近在外滩,但终点在徐家汇体育公园,有些远,我要是起得来就去接一下嘉伟和吴安迪,他俩昨晚一起遛了会儿,没有碰到我,吴安迪看起来状态很好,小伙子又帅气跑得又稳,预计能到315,嘉伟自然还是奔着破三去跑,但是全马变数太多了,能匀速跑完全程就已经是一种奢求,大多数人30km后都会掉速很多,所以我也很想能在终点给他们单独拍点照片,毕竟首马对于每一个跑者都是很神圣的事情,何况首马是在上马。说实话还是挺想跟他们一起参加上马,可是时间、身体、精力都难以维持,而且中签率太低。

EXCEL统计单元格内某个字符出现次数的方法:LEN(A2)-LEN(SUBSTITUTE(A2,"-",""))

饼图绘制:

import matplotlib.pyplot as plt
plt.pie(x, explode=None, labels=None, colors=None, autopct=None, pctdistance=0.6, shadow=False, labeldistance=1.1,
 startangle=0, radius=1, counterclock=True, wedgeprops=None, textprops=None, center=0, 0, frame=False, 
 rotatelabels=False, *, normalize=None, data=None)
  1. x即每个扇形的占比的序列或数组
  2. explode如果不是None,则是一个len(x)长度的数组(float类型),指定每一块的突出程度(每个扇形会沿着离心方向突出);突出显示,设置每一块分割出来的间隙大小
  3. labels为每个扇形提供标签的字符串序列
  4. colors为每个扇形提供颜色的字符串序列
  5. autopct如果它是一个格式字符串,标签将是fmt % pct。如果它是一个函数,它将被调用。它的默认值是每个扇片的百分比数,你可以做一些修正
  6. shadow阴影
  7. startangle从x轴逆时针旋转,饼的旋转角度
  8. pctdistance, default: 0.6每个饼片的中心与由autopct生成的文本的开头之间距离与半径的比率,大于1的话会显示在圆外
  9. labeldistance, default: 1.1饼状图标签绘制时的径向距离(我认为这个也与8类似是个比率)。如果设置为None,则不绘制标签,而是存储在图例中使用。

20231124

  • 昨晚均配4’45"慢摇20圈,平均心率150bpm,这是最舒适的区间,哪怕再累这样跑都是极其轻松愉悦的。如今核心稳定,上半身几乎不晃,视野保持静止。跑了四年,终于能看起来跑得轻松。今晚去小姨家补一下,明天陪嘉伟做最后的准备,虽然我不参赛,但真的很想跑一次全马。
  • 早上帮宝玺姐一起下去寄件,完事在小邮局她又收了一封明信片,我觉得现在寄明信片的人已经很少了,问她是谁寄来的,告诉我是她在武汉的时候寄给上海的自己,是个有趣的人呐。
  • 本来下午要给步执汇报,结果他儿子发烧,请假回家照顾,听步执说家里好几个都发烧了,又是流感季,我现在出门都会带口罩,但是晚上在宿舍都是赤膊短裤干到十二点,运气不错,没有感冒。
  • 看聊天记录上一次已是4月8日,其实每次都觉得熟悉又陌生,大半年没有见,但彼此似乎都摸得清对方的近况,却害怕失去而心照不宣,默而不语。年前或许能再见一面吗?再别无奢求。

numpy基于余弦相似度的快速计算与排序代码片段,比如两个矩阵作product的计算相似度,循环就太费事了:

sent_embeddings = model.encode(sents) 				# (n_sents, 768) 一般为 (buffer_size, 768) 最后一个少点
n_sents = sent_embeddings.shape[0]
sent_emb_norms = norm(sent_embeddings, axis=1)												# (n_sents, 768)
dot_res = np.dot(sent_embeddings, occupation_embeddings.T)									# (n_sents, n_occ)
norm_matrix = np.dot(sent_emb_norms.reshape(n_sents, 1), occ_emb_norms.reshape(1, n_occ))	# (n_sents, n_occ)
cosine_matrix = dot_res / norm_matrix														# (n_sents, n_occ)W
argsort_res = np.argsort(cosine_matrix)												# (n_sents, N_OCCUPATION)

20231125

  • 周六依然是好天气,今天只是稍微出力,就把5000米跑到20分钟整,其实并没有奔着跑成绩,只是想比平时养生快一点,但前4000米就跑到了16’12",并不累,但是想想可以试试能不能自己一个人跑进20分钟,所以第5个1000米就加速冲了一下,3’48",刚好20分钟整,平均心率171bpm。身体各方面素质上去之后,即便平时不上强度,也依然可以有提升。跑完补了10圈慢跑,4’45"配速,很舒畅。
  • 明日计划七点一刻出门,骑车赶往25k江滨路瑞宁路处的上财私补点等嘉伟和吴安迪,等他们38k折返回来陪着一起到终点前给他们拍照。宋某人纯懒怂,赖床上都不想来为嘉伟加油,他在田径队群里的昵称是陈嘉伟粉丝,我的是陈嘉伟老粉,他就一纯假粉。
  • 我是看着嘉伟从本科入学一步一步走到今天的,很高兴与自己一起训练的朋友即将冲击首马破三,将来有一天我也能做到的,一定。
2、expected_conditions模块常用类
title_is(title)
title_is(title)判断网页title是否是特定文本(英文区分大小写),若完全相同则返回True,否则返回False。
title_contains(title)
title_contains(title)判断网页title是否包含特定文本(英文区分大小写),若包含则返回True,不包含返回False。
presence_of_element_located(locator)(重点,常用)
presence_of_element_located(locator)判断一个元素存在于页面DOM树中,存在则返回元素本身,不存在则报错。
locator为一个(by, path)元祖,这个(by, path)的by是Selenium的一个类selenium.webdriver.common.by.By,包括CLASS_NAME,CSS_SELECTOR,ID,LINK_TEXT,NAME,PARTIAL_LINK_TEXT,TAG_NAME和XPATH,和我们8种基本元素定位中使用的方法相同。
presence_of_all_elements_located(locator)
presence_of_all_elements_located(locator)判断定位的元素范围内,至少有一个元素存在于页面当中,存在则以list形式返回元素本身,不存在则报错。
visibility_of_element_located(locator)
visibility_of_element_located(locator)判断特定元素是否存在于DOM树中并且可见,可见意为元素的高和宽都大于0,元素存在返回元素本身,否则返回False。
这个类和presence_of_element_located(locator)有点像,但后者只强调元素存在于DOM树中,可见不可见无所谓,而前者要求必须高和宽必须都大于0,因此后者在性能上会稍微快一点点。
visibility_of(element)
visibility_of(element)同上面visibility_of_element_located(locator),不过参数从locator的元组变为元素。
invisibility_of_element_located(locator)
invisibility_of_element_located(locator)判断元素是否隐藏。
text_to_be_present_in_element(locator,text)
text_to_be_present_in_element(locator,text)判断给定文本是否出现在特定元素中,若存在则返回True,不存在返回False。
text_to_be_present_in_element_value(locator,text)
text_to_be_present_in_element_value(locator,text) 判断某文本是否是存在于特定元素的value值中,存在则返回True,不存在则返回False,对于查看没有value值的元素,也会返回False。
frame_to_be_available_and_switch_to_it(locator)
frame_to_be_available_and_switch_to_it(locator) 判断某个frame是否可以切换过去,若可以则切换到该frame,否则返回False 。
element_to_be_clickable(locator)
element_to_be_clickable(locator)判断某元素是否可访问并且可启用,比如能够点击,若可以则返回元素本身,否则返回False。
staleness_of(element)
staleness_of(element)判断某个元素是否不再附加于于DOM树中,不再附加的话返回True,依旧存在返回False。可以用于判断页面是否刷新了。
alert_is_present
alert_is_present判断alert是否存在,若存在则切换到alert,若不存在则返回False。
element_to_be_selected(element)
element_to_be_selected(element) 判断某元素是否被选中。
element_located_to_be_selected(locator)
element_located_to_be_selected(locator) 判断某元素是否被选,locator为一个(by, path)元祖。
element_selection_state_to_be (element, is_selected)
element_selection_state_to_be (element, is_selected)判断某元素的选中状态是否与预期相同,相同则返回True,不同则返回False。
element_located_selection_state_to_be(locator, is_selected)
element_located_selection_state_to_be(locator, is_selected) 判断某元素是否与预期相同,相同则返回True,不同则返回False,locator为一个(by, path)元祖。
总结:这些方法与WebDriverWait类和 until()、until_not()方法组合能使用,够实现很多判断功能,如果能自己灵活封装,将会大大提高脚本的稳定性。

20231126

  • 2:59:15,首马破三,他做到了。全中国全马有破三能力的人大约有5000,许多人究其一生练不到破三水平,首马破三,可以算是地区一哥的水平了。
  • 今天差不多骑行了40km,跑了10km,突破重重封锁终于抵达了赛道38km处,陪跑了最后4km,见证了这个传奇的诞生。
  • 崔洲宸3:43:14(违规完赛,他报的是10km健康跑,但是偷偷跟着大部队跑到了全马终点),吴安迪3:45:50(小伙子还是没能顶住),两个人都是25km之后跑崩,只能走走停停,事实上两个人的5k和10k差距非常大,但是全马就是最好的有氧能力鉴别器。嘉伟是到35km后跑崩,但即便是跑崩也维持了4’30"以内的配速,天赋可见一斑。照目前的心率水平来说,我是有能力以4’45"的配速跑完全马的,对标的成绩是3小时20分,至少开3小时半是没有问题的。
  • 下一次,该轮到我了。

关于捕获元和findall函数返回结果的关系

  1. 如果不带捕获元,那么regex.findall(string)的结果将是一个纯列表,返回所有匹配的结果(str),但是注意这些结果在string中是不overlap,如果想要匹配所有overlap的结果,我不知道怎么做,match函数不是很能搞得明白,可能只能在正则上下功夫。
  2. 如果带捕获元,那么findall返回的结果仍是列表,但每个元素是tuple,包含每个捕获元的结果。
  3. 这里特别的是捕获元在findall结果中出现的次序,如果是不嵌套的,那么很简单,按顺序出现,如果嵌套,则按照层次遍历顺序出现,如((regex1)(regex2))(regex3),返回的结果是(regex1regex22, regex1, regex2, regex3)

20231127~20231129

  • 这周前三日太累,状态不好,周一13圈(@455),周二15圈(@444),下班没时间换衣服,穿牛仔裤厚底鞋跑,平均心率都快到160bpm了,尤其是周一,跑的很慢但都觉得累。昨天本来约了AK下班8点半去跑,结果我一直拖到8点半才走,最后AK只是在小区跑了会儿,我确实也累得不行,但还是从9.25跑到操场关门。今天带了换的裤子,准备认真摇一会儿。
  • 昨晚本来跟AK约了下班回来跑的,结果都加到好晚,我9点20分才到操场,疲劳得不行,但还是跑到操场关门,拉了会儿引体,现在勉强能提一些上去了。
  • 高百全国总决赛拉开序幕(12.16,地点位于上海徐汇滨江),今年因为上海站成绩特别好(其实我们的总时间在上海站只能排20名,到其他站依然是能进前8的,上海站今年主要是高手来的太多,别的站40分台稳进前100,上海站只能排到250名开外),所以我们还是可以报名参加全国总决赛,总决赛的形式是16km×10人的接力(分两组各5人同时出发,关门时间为6个小时,意味着平均每人至少跑进1小时12分,均配4’30"),其中至少包含2名女生,如果只是8个男生的话,马协还是可以凑出来的,全马sub250的人就有不少(现役第一人是09级经济的李朝松,上个月合肥全马232PB,历史第二人应该就是AK了,PB237,但目前处于复健状态,此外须任荣,郑俊荣,俞国峰,葉凯浩,信欣都是250左右的选手),破三的人就更多了,女子全马接近国一水平的还有3个人。但是日期和杭马(12.17)略有冲突,而且和在校生期末考试周也有冲突,之前听吴安迪说,上海站决赛时浙大好几个大一大二的学生就是因为期中考试来不了。
  • 如果最强的一批阵容真的凑齐,必然豪华无比,嘉伟都排不进前五,万米40分台选手只能担任替补,值得期待。

关于teacher forcing

这个点之前被忽略了。简而言之,对于自回归的生成任务,比如训练样本为:

X = ( x 1 , x 2 , x 3 , . . . , x n ) X = (x_1, x_2, x_3, ...,x_n) X=(x1,x2,x3,...,xn)

f θ f_\theta fθ是模型,常规的RNN自回归训练流程相当于:

x ^ 1 = x 1 x ^ 2 = f θ ( x ^ 1 ) x ^ 3 = f θ ( x ^ 1 , x ^ 2 ) x ^ 4 = f θ ( x ^ 1 , x ^ 2 , x ^ 3 ) . . . x ^ n = f θ ( x ^ 1 , x ^ 2 , x ^ 3 , . . . , x ^ n − 1 ) \begin{aligned} \hat x_1 &= x_1\\ \hat x_2 &= f_\theta(\hat x_1)\\ \hat x_3 &= f_\theta(\hat x_1, \hat x_2)\\ \hat x_4 &= f_\theta(\hat x_1, \hat x_2, \hat x_3)\\ & ...\\ \hat x_n &= f_\theta(\hat x_1, \hat x_2, \hat x_3, ..., \hat x_{n -1}) \end{aligned} x^1x^2x^3x^4x^n=x1=fθ(x^1)=fθ(x^1,x^2)=fθ(x^1,x^2,x^3)...=fθ(x^1,x^2,x^3,...,x^n1)

这样的问题是因为每一步用的都是预测出来的token(即 x ^ i \hat x_i x^i)进行下一步预测,很容易使得偏差大,所以一般会改为

x ^ 1 = x 1 x ^ 2 = f θ ( x 1 ) x ^ 3 = f θ ( x 1 , x 2 ) x ^ 4 = f θ ( x 1 , x 2 , x 3 ) . . . x ^ n = f θ ( x 1 , x 2 , x 3 , . . . , x n − 1 ) \begin{aligned} \hat x_1 &= x_1\\ \hat x_2 &= f_\theta(x_1)\\ \hat x_3 &= f_\theta(x_1, x_2)\\ \hat x_4 &= f_\theta(x_1, x_2, x_3)\\ & ...\\ \hat x_n &= f_\theta(x_1, x_2, x_3, ...,x_{n -1}) \end{aligned} x^1x^2x^3x^4x^n=x1=fθ(x1)=fθ(x1,x2)=fθ(x1,x2,x3)...=fθ(x1,x2,x3,...,xn1)

这样的好处是让模型更好的学习数据固有的模式,能够更好的收敛与控制模型的输出,但缺少泛化性能。


20231130~20231201

  • 国定三个食堂的砂锅全一个味道,怀念六七年前盛环的肥牛砂锅,好吃不贵,可惜只吃了不到两年就连人带锅搬到隔壁。
  • 昨天下午嘉伟给他们带了7组1200米间歇,宋某也去练了,3组跑到330配速,4组350左右,慢的组大概是为了照顾女生,他们几个女生确实也很长时间不上强度了,明天就要比赛属于是临时抱佛脚,去年他们花了两三周系统训练的,今年纯赶鸭子上架。
  • 大降温,昨晚6000米,4’09"配速,心率162bpm,很是舒适,最大摄氧量重回60,很舒适的天气,周末准备带宋某上长距离。

BGE模型的效果卓著,非常好,建议使用。

可使用FlipEmbedding或SentenceBert使用

from FlagEmbedding import FlagModel
sentences_1 = ["样例数据-1", "样例数据-2"]
sentences_2 = ["样例数据-3", "样例数据-4"]
model = FlagModel('BAAI/bge-large-zh-v1.5', 
                  query_instruction_for_retrieval="为这个句子生成表示以用于检索相关文章:",
                  use_fp16=True) # Setting use_fp16 to True speeds up computation with a slight performance degradation
embeddings_1 = model.encode(sentences_1)
embeddings_2 = model.encode(sentences_2)
similarity = embeddings_1 @ embeddings_2.T
print(similarity)

# for s2p(short query to long passage) retrieval task, suggest to use encode_queries() which will automatically add the instruction to each query
# corpus in retrieval task can still use encode() or encode_corpus(), since they don't need instruction
queries = ['query_1', 'query_2']
passages = ["样例文档-1", "样例文档-2"]
q_embeddings = model.encode_queries(queries)
p_embeddings = model.encode(passages)
scores = q_embeddings @ p_embeddings.T

from sentence_transformers import SentenceTransformer
sentences_1 = ["样例数据-1", "样例数据-2"]
sentences_2 = ["样例数据-3", "样例数据-4"]
model = SentenceTransformer('BAAI/bge-large-zh-v1.5')
embeddings_1 = model.encode(sentences_1, normalize_embeddings=True)
embeddings_2 = model.encode(sentences_2, normalize_embeddings=True)
similarity = embeddings_1 @ embeddings_2.T
print(similarity)

20231202

  • 太阳打西边出来,宋镇均居然在这么冷的天,六点起床出去跑了16km,均配4’16",一反常态。吓得我一机灵,七点醒来就没敢再睡,赶紧起床去学校吃早饭干活。
  • 为了两周后的高百全国总决赛,下午我按计划跑了一组15km(我本来是准备带宋镇均跑的),数据非常满意,用时1小时02分40秒,均配4’11",平均心率166bpm,最大心率177bpm,从手表数据来看,配速和心率都极其稳定,甚至在10km以后配速一度上升(因为10km处有个不错的小伙子能跟得住我,我就提了一点给他上些强度),最后1000米冲到3’50",心率和配速都没有顶到极限,跑到最后依然很轻松。
  • 现在我的水平绝对要比21年12月的巅峰期的配速要高到5秒左右,完全有自信能在明年上半年把半马破开1小时30分,当然我更想能把首马跑到3小时15分。
  • 市运会Day1,嘉伟和崔洲宸不出意外1500米(乙组)被血虐,前八全部达标二级(4’15"),崔洲宸4’45"跑了倒数第二(给小伙子来点打击也是好的,他为人处事太高调,需要来些挫败),嘉伟4’39"倒数第四,他俩为了能跑5000米,被迫报了乙组(因为甲组没有5000米),不过两人都算是PB,嘉伟也要比去年市运会快1秒。女生那边黄嘉懿拿到了800米第七(2’46"),卢星雨2’54"再次错失前八,但也很强,她去年3’01",跑赢自己就好,这成绩体测已经远远超过体测满分(3’16"),然而我现在1000米体测估计差满分(3’15")5秒左右,我自己800米最快也就勉强能开2’40"。

2023.acl-long.70,这篇很好,难得见到一篇比较靠谱的。

在开放领域的对话生成任务中,大多数训练语料集都是1-to-1映射,但是实际对话场景中更多出现的N-to-N的映射。

  • 1-to-1映射:即指模型生成与对话上下文中指定的某一语句对应,且回答语句是唯一的。对于早期的1-to-1对话生成任务,可以直接套用机器阅读理解模型的架构。

    举个例子,对于给定的对话样本 X = { x 0 , x 1 , . . . , x T − 1 } , y = x T X=\{x_0,x_1,...,x_{T-1}\},y=x_T X={x0,x1,...,xT1},y=xT,一种简单的思路是转化为机器阅读理解任务处理,即 C = { x 0 , x 1 , . . . , x T − 2 } , Q = x T − 1 , A = x T C=\{x_0,x_1,...,x_{T-2}\},Q=x_{T-1},A=x_T C={x0,x1,...,xT2},Q=xT1,A=xT,其中 C C C表示上下文, Q Q Q表示问题, A A A表示回答。

  • N-to-N映射:即指模型既可以生成多样化的回答,且回答可以与多条上下文语句关联(或者也可以理解为与不同的上下文语句关联,即可以选择对上下文中某一句话做出回答,这同时印证回答的多样化)。

    一些直觉性的解决思路,比如在回答语句的解码过程中引入扰动或松弛来获得多样化的生成结果,然而这样往往是行不通的,作者指出

    Discretely replacing each turn with semantic similarity breaks fragile context coherence

    即如果只是将生成的多样化的回答语句进行离散地拼接,往往不能得到合乎逻辑的对话结果,这正如Figure1(a)所示(Figure1(b)其实是说如果我用一种合理的方法进行对话路径采样,可以得到合乎逻辑的对话样本用于数据增强)。

目前的研究对于多轮的1-to-N以及单轮的N-to-N(这个似乎难以理解,其实确实就是个噱头,单轮怎么会有N-to-N呢,其实论文提到的相关工作是对1-to-1做数据增强的工作),而针对多轮的N-to-N对话生成缺少研究(你说是就是吧)。


20231203

  • 市运会Day2,嘉伟5000米17’02",成功PB,虽然没有拿到名次(前二甚至套了嘉伟一圈多,前八全部跑进16分30秒,但也只有前二是体育生,其他都是文化生),排名11/17,崔洲宸18’34"排名16/17,小家伙又拿了个倒数第二(甚至嘉伟都套了他一圈),两天两个倒二对他打击肯定不小,但他还年轻,未来是属于他的。
  • 男子4×400米(谢震宇,范贯一,徐瀚韬,刘星宇)拿下冠军,力克隔壁复旦,3’39"05,只比复旦快了不到0.5秒,看视频真的是特别精彩的纠缠。
  • 有点百感交集,早上后来跟AK聊了会儿,说到崔洲宸时,AK说不理解他朋友圈每天发四五条,还那么幼稚的文辞,我说他都05年的了,应当理解他的一些作为,AK说我们这一辈真的是老了,AK其实也就比我大一岁,但是女生的生命力却顽强很多,卢星雨(比我小一岁)和李婷玉(同岁)依然奋战在第一线,多想自己也能有一天能这样拼搏在更高的赛场上。但是我可能也很难有更高的提升了。
  • 几张拍得不错的图(嘉伟5000米途中跑 + 冲刺时的痛苦面具,李婷玉跳远,卢星雨1500米(其实是丑照哈哈,全是痛苦面具笑死),还有队里四朵金花的合照,以及全队合照)。

在这里插入图片描述
在这里插入图片描述

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述


20231204~20231206

  • 前两天听说队里周振凯在追李婷玉,市运会时有人就发现不对劲了,都觉得lty多了一个小尾巴。zzk是新生,lty比我还大半年,level差的不是一点点,其实就是一厢情愿,zzk偷偷把wx头像改成和lty能配对的,lty得知后连夜把头像给换了。搞不懂这些新生的脑回路…
  • 周一晚陪胡鑫宇跑了10km(前天下班后觉得自己身体特别轻,非常轻松,前面是4’50"的配速闲聊,最后冲了一个12分整的3000米,不累),很久不陪胡哥跑步,毕竟是老乡,又是套间室友。跟胡哥聊了许多,说到明年上半年的比赛(一场全马,一场半马,从此毕业前不再备赛),再到对未来的期望。胡哥说他爸跟他讲不要找大学同学谈恋爱,因为你们在一起别人会看到,分手后别人更会看到(所以胡哥现在都是异地恋,我也觉得这样是好的,双方都应该有一些私人空间,工作生活经常在一起的谈恋爱太难,会腻的),大学同学以后多少都有交集,容易社死,很符合我们扬州人好面子。我跟他讲lty去年也是跟队里的本科生姐弟恋,谈了半年就分了,撕得特别惨,虽然我觉得男生应该大气一点,在pyq指名道姓的抹黑实在有失风度,但是能把男生搞得这么疯狂,lty确实也不是省油的灯,男女在一些事情上观念确实还是差挺多,求同存异不容易。
  • 周二晚新宇哥毕业请同门在海底捞吃饭,可惜下班太晚没有赶上。便装5000米慢摇,24分半,最近操场跑步的人比较少,跑完做了20个单杠提腿卷腹,试着拉引体,能上去一点点了,有一点点能拉起来的感觉。大家一个个都离开学校了,终有一天也将轮到我。
  • 今晚是20’05"的5000米+30×10组的箭步走(负重15kg),说实话,练完感觉跟没练一样,真的太轻松了,难以置信的轻松,如果时间充裕,我再重复一遍这个量都可以。在经历了夏训和秋季的以赛代练后,我终于看到了自己的水平有了显著的提升,约了宋镇均周六测万米,有信心这次彻底破开40分钟。

查看是否支持BF16:

import transformers 
transformers.utils.import_utils.is_torch_bf16_gpu_available()

BF16

  • Sign(符号位): 1 位,0表示整数;1表示负数
  • Exponent(指数位):8位,表示整数部分,偏置值是 127
  • Fraction(尾数位):7位,表示小数部分,也是隐含了首位的1,实际的尾数精度为8位

FP16

  • Sign(符号位): 1 位,0表示整数;1表示负数。
  • Exponent(指数位):5位,简单地来说就是表示整数部分,范围为00001(1)到11110(30),正常来说整数范围就是 ,但其实为了指数位能够表示负数,引入了一个偏置值,偏置值是一个固定的数,它被加到实际的指数上,在二进制16位浮点数中,偏置值是 15。这个偏置值确保了指数位可以表示从-14到+15的范围,而不是1到30,注:当指数位都为00000和11111时,它表示的是一种特殊情况,在IEEE 754标准中叫做非规范化情况,后面可以看到这种特殊情况怎么表示的。
  • Fraction(尾数位):10位,简单地来说就是表示小数部分,存储的尾数位数为10位,但其隐含了首位的1,实际的尾数精度为11位,这里的隐含位可能有点难以理解,简单通俗来说,假设尾数部分为1001000000,为默认在其前面加一个1,最后变成1.1001000000然后换成10进制就是:

FP32

  • Sign(符号位): 1 位,0表示整数;1表示负数
  • Exponent(指数位):8位,表示整数部分,偏置值是 127
  • Fraction(尾数位):23位,表示小数部分,也是隐含了首位的1,实际的尾数精度为24位

20231207~20231209

  • 下午按计划场地10000米测试,并没有能够如愿跑进40分钟,在7000米处跑崩,最后3000米是隔了90秒分3次跑完,最终用时是39分35秒,说实话有些懊丧,但人生之不如意十之八九,凡事都不可能如想象般那样顺利,并不是同样的努力都能得到相同的成果的。而且还有一个遗憾,就是今天天气太热,十二月居然能有20℃,并不适合出成绩。
  • 这次是有不少人来捧场(我终于也算是有点号召力了),除了约好一起跑的宋镇均,还有卢星雨、AK(感冒初愈,就能3’50"以内跑20多圈)、嘉伟、小崔(发烧刚好)、叶赵志(已经是跑不动的模样了),周俊呈,王总(活的王总,不容易)在观赛助威。宋某带得很稳,前5000米用时19分48秒,平均配速3’57",这已经打破了我两年前的5000米的PB(这两年都没能测出5000米的真实水平),此时并没有感觉特别吃力,但是过了6000米心肺压力骤升,我明显觉得心脏已经支撑不了当前的配速,示意需要适当减速缓一下,到7000米用时28分整,均配已经掉到了4’00"的临界值,但心肺情况没能好转,实时配速已经掉出4’10",我深感绝望,越来越跟不住的宋某的节奏,完全失去了坚持下去的意志。然而宋某却越战越勇,拉爆我之后以3分45秒的配速跑完了最后3000米,甚至跑完万米之后又跟了AK十几圈(3’50"配速),他真的天赋太好了,只是懒,不然绝不是现在这个水平。
  • 虽然这次挑战失败,但至少证明了我基本适应4分整的配速,并且能够一点点地跨过这道大坎,想要彻底破开4分配,仍需中距离冲刺提升心肺耐受,腿脚并没有负担,以前跑崩有时候是因为短暂长胖致使大腿不能耐受,但这次跑完腿脚是完全没有感觉的(近期体重稳定在70~71kg),我能感觉到途中跑上半身前倾得非常稳定,而且戴了新买的墨镜,非常拉风。
  • 今天有点觉得lxy表现不太寻常,亲近了许多,以前她都是嚯嚯学弟。而且剪了短发,认识两年多,但交集很少,偶尔碰到能跟她摇几圈聊聊天,事实上现在冬天下班也碰不到。队里都知道她一直单身,但过于优秀,难以企及,虽然在男女生堆里都吃得开,但其实行事风格跟lty完全不同,lty是我见过同龄人里最成熟的女性了,段位太高,大一新生想追她实在离谱。但lxy不一样,我感觉她就是性格吃得开,但行事依然是学生风格,如果真的有大一新生追她我觉得也挺正常… 其实单身有啥不好呢,多个朋友比多个npy好。

使用newaxis(numpy没有broadcast函数,一般用newaxis实现张量的传播)

a = np.array([[1,2,3], [4,5,6]])
a_norm = norm(a, axis=1)
a = a / a_norm[:, np.newaxis]

关键词抽取:

# 看起来Gensim的关键词总结能力并没有很好

import gensim
for cluster in clusters:
    gensim_kw = gensim.summarization.keywords(cluster, words=10, split=True, scores=True)
    print(gensim_kw)
    print('-' * 64)
import jieba.analyse
#准备语料
corpus = "《知否知否应是绿肥红瘦》是由东阳正午阳光影视有限公司出品,侯鸿亮担任制片人,张开宙执导,曾璐、吴桐编剧,赵丽颖、冯绍峰领衔主演,朱一龙、施诗、张佳宁、曹翠芬、刘钧、刘琳、高露、王仁君、李依晓、王鹤润、张晓谦、李洪涛主演,王一楠、陈瑾特别出演的古代社会家庭题材电视剧"

#textrank
keywords_textrank = jieba.analyse.textrank(corpus)
print(keywords_textrank)
>>> ['有限公司', '出品', '社会', '家庭', '制片人', '担任', '影视', '题材', '电视剧', '知否', '东阳', '出演', '执导']

#tf-idf
keywords_tfidf = jieba.analyse.extract_tags(corpus)
print(keywords_tfidf)
>>> ['知否', '领衔主演', '刘钧', '刘琳', '侯鸿亮', '张晓谦', '王一楠', '张佳宁', '李依晓', '冯绍峰', '王鹤润', '施诗', '陈瑾', '赵丽颖', '吴桐', '朱一龙', '曹翠芬', '王仁君', '曾璐', '高露']
%%html
<style id=hide>div.input{display:none;}</style>
<button type="button" onclick="var myStyle = document.getElementById('hide').sheet;myStyle.insertRule('div.input{display:inherit !important;}', 0);">Show inputs</button>

kmeans.score(X)的结果是距离的相反数,即绝对值越大,越不匹配。


20231210~20231211

  • 周日去小姨家老鸭汤、清蒸鱼、糖醋虾彻底补满血,我现在每周就指着周末能吃点儿好的活着了。晚上卫衣西裤补了5000米慢跑,降雨湿闷,难受得很,很难想象12月的上海会有这种类梅雨天气。嘉伟和AK晚上六点在操场21.1km的拉练,用时1:20:45,平均配速3’49",令人咋舌。AK自从柴古唐斯越野赛回来之后,已经一个多月没有规律性训练,而且前天感冒刚好,竟然不需要恢复期就能达到这么高的训练水平,即便如今不再巅峰,一哥地位也无法撼动。
  • 周一冬雨渐深,有一点点要感冒的前兆。晚上下班寒风凛冽,干了个均配4’13"的10km节奏跑(平均心率162bpm),跑得极稳,不管是心率还是配速,最后1000米用时跑进4分钟,非常满意的高质量训练。
  • 今年还剩三个周末,万米跑进40分钟,一定会做到的。去年没有PB,今年至少要留下一个PB,这是我2023最后的疯狂。

在这里插入图片描述

确定KMeans模型距离质心最近的样本点:

def check_if_sample_label_match_cluster_center():
    for i in range(emb.shape[0]):
        sample_i = emb[i, :]
        distances_i = norm(sample_i[np.newaxis, :] - model.cluster_centers_, axis=1) # 计算距离
        index = np.argmin(distances_i)
        label = model.labels_[i]
        # print(i, index, label)
        assert index == label, f"{index}, {label}"
        
# 相反地,去检查每个质心距离它最近的那个样本作为名称
def check_which_sample_is_the_nearest_to_cluster_center():
    cluster_names = []
    for i in range(model.cluster_centers_.shape[0]):
        cluster_i = model.cluster_centers_[i, :]
        distances_i = norm(cluster_i[np.newaxis, :] - emb, axis=1) # 计算距离
        index = np.argmin(distances_i) # 0~9719中的而某个数
        print(f"第{i}类的名称: {samples[index]}")
        print('-' * 64)
        print(f"""第{i}类的样本:
{' - '.join(clusters[i].splitlines())}""")
        print("#" * 64)
        cluster_names.append(samples[index])
    return cluster_names

20231212~20231214

  • 周二组会开到八点半才走,讲了两个多小时,大家对方法上的创新似乎并不太感冒,我觉得这是很危险的事情,之前2~5年,许多创新还是集中在模型架构上,给我印象深刻的比如text-to-SQL的IRNet,当时看觉得很惊艳,现在较于大模型的代码生成能力就是依托答辩。
  • 周三晚按计划跑黑马课表(12组400米变速跑,每组400米快+400米慢,中间不休息,A组快圈80-82秒,慢圈92-95秒,后面BCDE组依次加3~5秒),最终快圈1’25",慢圈前面还能顶到1’40",后半程掉到2分开外(总体均配4’19")。本来AK、嘉伟、宋某和我是前天就约好一起跑,昨天还说不见不散,然后今天是嘉伟先说要跑休(他必是去约妹子了,啥原因也不说,除了女人,我想不到有什么事情会阻止嘉伟跑步)、然后AK也犹豫不决,觉得昨天没休息好(其实我下班也觉得特别累,但是我中午炫了三碗饭,装备也带了,不跑实在说不过去),而且晚上要赶个公司汇报的稿子。最后只有我和宋某,中途周俊呈来跟了我三组就吃不消,我却觉得越来越有力气,根本不觉得累,跑完12组后舒畅无比,一扫颓势,甚至还想再来两组。
  • 身体不适,估计是躲不过感冒,真的很无奈,想吃点好的。虽然昨天下班就有些不适,但是跑起来并没有觉得累,今天这种疲劳感增加了,晚上九点半回学校,还是跑了10圈,4’48"的配速,喘得不行,累到极限,回宿舍就洗澡睡了。(宋某今天补了12×400米间歇,间歇2分半,非常高质量,1’09~1’14",高质量,没有人比他更懂间歇)
    在这里插入图片描述
  • 如今大家似乎已经接受了这些主流的几个基座,认为只要有算力就能解决一切,Gemini给我带来无比的震撼,仅一年,从chatgpt到gemini,一个拥有听觉视觉和阅读能力的模型,这意味着文本、图像和语音可以被映射到统一的语义空间,这是真实存在的吗?学界之前觉得图像和文本的语义统一都是极其困难(如果需要统一语义,则必须要有充分多的图像文本的平行数据集,虽然vqa系列已经有成亿的图像文本对,但这相较于文本和图像单独的数据规模根本不值一提,而且图像文本语义根本就不是1-to-1的存在),更遑论语音语义的表征。如果Gemini是真的,那么我觉得世间一切的规律都可以被用潜在的数学公式(黑盒模型)表达出来,就像用物理学解释物体的运转规律一样。我还是倾向于gemini是造假的。

关于python加载JSON字符串的小问题

比如字符串是:

import json

json_string = """{"name": "sadsad\\asdasd"}"""
data = json.loads(json_string)

就会报错:

json.decoder.JSONDecodeError: Invalid \escape: line 1 column 17 (char 16)

就是两个反斜杠是不行的,但是如果事先转义:

import json

json_string = r"""{"name": "sadsad\\asdasd"}"""
data = json.loads(json_string)

又是不报错的。

实际上如果是把{"name": "sadsad\\asdasd"}直接复制到外部文件d:/1.json去读:

data = json.load(open("d:/1.json", 'r'))

又是可以的。

所以现在还是建议直接用ast来load,更加灵活一些:

json_string = """{"name": "sadsad\\asdasd"}"""
import ast
data = ast.literal_eval(json_string)

这样是可行的。


20231215~20231216

  • 周五早起发现喉咙疼得很,鼻子不通,心想坏了,还是躲不掉,于是在公司喝了十瓶保温杯(550ml)的热水,到晚上就感觉全好了,甚至身轻如燕,猛得不行,回学校虽然下雨操场封闭,于是在实验大楼上下4次,完全不累,感冒好的最快的一次,事实证明喝水包治百病。
  • 今天-1~2℃,还没太阳,冷得离谱。卢星雨一大早摇人去健身房跑,她每天六点多就起床,无论冬夏,确是个猛女。下午我试图继续追梦万米40分钟,本以为低温是buff,结果差点没被冻死。第一段是均配3’58"的4000米,手都快冻掉了,冷气吸得特别难受,实在无法坚持下去,戴上手套又补了8000米加速(从4’32"加速到3’35"),均配4’18",这段很轻松,身体热起来,越跑越舒适。今年万米想破40分真的很难,接下来几乎都是这种气温,除非有特别好的太阳,跑起来会舒服些,否则像今天这样没太阳,西北风呼呼的天,没有点意志力真的坚持不下去。
  • 但是今早高百总决赛是在徐汇滨江进行,来自全国50多所学校进行10人×16km的接力,因为浙大参加,吴安迪去观赛了,听他有些人跑完就倒地了,有的直接眼球上翻,很恐怖,我看直播好多人居然是短袖短裤,这跑完16km还能有知觉?那些从南方(尤其是两广)过来的学校,不得直接冻傻了。清华是亚军,他们派了一个全马239的女生来参加都没拿下冠军,冠军是重庆大学,总用时9小时33分,相当于平均每人57分,均配3’34"/km跑完了16km(这10个人里面还有2个女生),远远超过了往年的夺冠成绩(往年一般在9小时50分左右),足以证明全国大众跑者的水平这两年突飞猛进,马拉松人群基础愈发坚实,国家记录今年也是屡次被打破,一个辉煌的时代即将到来。

pandas datetime

data4["订单生成时间"] = pd.to_datetime(data4["订单生成时间"])
data4["时间"] = data4["订单生成时间"].dt.hour   #提取时间
data4["日期"] = data4["订单生成时间"].dt.date   #提取日期

df.truncate(before='2014')
df.truncate(after='2013-11')

import datetime
from pandas.tseries.offsets import Day
now_time =datetime.datetime.now()#获取当前时间
yes_time = (now_time -1*Day()).strftime('%Y-%m-%d')#格式化
print(yes_time)

   ##提取3日前日期和7日前日期
now_time =datetime.datetime.now()#获取当前时间
yes_time_7 = (now_time -7*Day()).strftime('%Y-%m-%d')#格式化
yes_time_3= (now_time-3*Day()).strftime("%Y-%m-%d")
print(yes_time_7)
print('='*50)
print(yes_time_3)
df_189['订单生成日期']=pd.to_datetime(df_189['订单生成日期'])  #修改“订单生成时间”的数据类型
df_189_7= df_189[df_189['订单生成日期']==yes_time_7]
df_189_3= df_189[df_189['订单生成日期']==yes_time_3]
print(df_189_7.info())
 #选取该错误数据对应的时间范围外的数据,多个条件时 '|'代表'或','&'代表'且'
  或者
 a= df_189[df_189['订单生成日期']=='2019-06-23']   #筛选2019-06-23的数据

import pandas as pd
#方法一:
#先利用to_datetime转换为时间格式,tm列的数据形式为'yyyy-MM-dd HH:mm:ss'
df['tm_1'] = pd.to_datetime(df['tm_1'])
df['tm_2'] = pd.to_datetime(df['tm_2'])
#利用".dt.seconds"转换为秒,除以相对于的间隔数得到分钟、小时等
df['diff_time'] = (df['tm_1'] - df['tm_2']).dt.seconds/60
#利用round函数可进行四舍五入
df['diff_time'] = round(df['diff_time'])

#方法二,日期相减变为小时;变为天的话将h替换为D即可:
df['diff_time'] = (df['tm_1'] - df['tm_2']).values/np.timedelta64(1, 'h')
#Dataframe中的时间是不能直接进行相加减的,所以需要先用pandas的to_datetime()方法,转化成时间格式进行加减,然后再转换成df格式
#delta=df1['Time_end']-df1['Time_start'] #直接报错TypeError: unsupported operand type(s) for -: 'str' and 'str'
#日期相减变为小时;变为天的话将h替换为D即可:
sf_df['交易周期']=pd.DataFrame((pd.to_datetime(sf_df['交易完成时间'])-pd.to_datetime(sf_df['订单生成时间'])).values/np.timedelta64(1,'h'))

import numpy as np
np.datetime64('2017-08-06')  #精确到日
>>> 
numpy.datetime64('2017-08-06')
np.datetime64('2018-08')   #精确到月
>>>
numpy.datetime64('2018-08')
# 通过参数,强制将数据格式转为我们想要的粒度
np.datetime64('2017-08','D')  #转化到日
>>>
numpy.datetime64('2017-08-01')
np.datetime64('2017-08','Y')  #转化到年
>>>
numpy.datetime64('2017')
a = np.array(['2017-07-01','2017-07-15','2017-08-01'],dtype = np.datetime64)  #列表日期
a
>>>
array(['2017-07-01', '2017-07-15', '2017-08-01'], dtype='datetime64[D]')
# 我们也可以使用arange函数初始化数组
b = np.arange('2017-08-01','2017-09-01',dtype = np.datetime64)
b
>>>
array(['2017-08-01', '2017-08-02', '2017-08-03', '2017-08-04',
       '2017-08-05', '2017-08-06', '2017-08-07', '2017-08-08',
       '2017-08-09', '2017-08-10', '2017-08-11', '2017-08-12',
       '2017-08-13', '2017-08-14', '2017-08-15', '2017-08-16',
       '2017-08-17', '2017-08-18', '2017-08-19', '2017-08-20',
       '2017-08-21', '2017-08-22', '2017-08-23', '2017-08-24',
       '2017-08-25', '2017-08-26', '2017-08-27', '2017-08-28',
       '2017-08-29', '2017-08-30', '2017-08-31'], dtype='datetime64[D]')

# 两个日期相减,会得到相差的天数
np.datetime64('2017-08-03') - np.datetime64('2017-07-15')
>>>
numpy.timedelta64(19,'D')
# 这里日期可以直接减去对应的天数
np.datetime64('2017-08-03') - np.timedelta64(20,'D')
>>>
#这里日期的粒度必须保证一样,一个是D,一个是M,是不可以相减的
np.datetime64('2017-08-03') - np.timedelta64(1,'M')
>>>
TypeError: Cannot get a common metadata divisor for NumPy datetime metadata [D] and [M] because they have incompatible nonlinear base time units
np.datetime64('2017-08') - np.timedelta64(1,'M')
>>>
numpy.datetime64('2017-07')

(np.datetime64('2014-10-30 23:00:00') - np.datetime64('2014-10-21 00:00:00'))  #按秒计时

(np.datetime64('2014-10-30 23:00:00') - np.datetime64('2014-10-21 00:00:00'))/np.timedelta64(1, 'h')   #计算时间差,并转为小时
>>>
239.0
#计算终止时间-初试时间,转为小时格式,最后格式设置为整型。np.floor((x - start_hour) / np.timedelta64(1, 'h')).astype(np.uint16)
np.floor((np.datetime64('2014-10-30 23:00:00') - 
          np.datetime64('2014-10-21 00:00:00'))/np.timedelta64(1, 'h')).astype(np.uint16)
 >>>
 239

20231217~20231218

  • 昨天周日真的是大好天气,但是我所有精力都耗费在了周六,其实我并不想跑,但这么好的晴天(虽然-2~2℃)不忍咸鱼,下午准备去慢跑一会儿,很幸运碰到吴安迪,跟安迪遛了10km,5’07"的均配,平均心率141bpm。
  • 每次跟安迪跑都很舒服,虽然跑得不多,但这小伙子真的健谈,从头聊到尾。恰好昨天是杭州马拉松,说到杭马就说起了杭马代言人施一公,施一公真的勤快,上个月底刚跑完上马(3小时42分),今天又跑杭马,安迪说他在浙大时有个师兄A(生物专业),在青山湖骑车偶然碰到施一公,直接大型粉丝见面会,就施老结识为朋友,经常一起骑车跑步,后来博士申到西湖大学,施老直接给A推荐了导师,A本身也很争气,跟他说,以后找不到工作就来西湖大学做老师。而就在周六上海这么阴间的天气下,A跑出了59分(16km,均配3’42")的成绩,不仅如此,A周六晚还赶回杭州西湖大学继续挑灯夜战读文献做实验,然后周日一大早就参加杭马,我都听傻了,人和人的差距真的不可同日而语。
  • 说到施一公,我其实也亲眼见过他一面,那还是在八年前,我高二升高三的暑假,当年幸运地以综合年纪排名第一拿到了清华暑校的资格,那时施老还在清华任职,给我们进行了一场匆匆忙忙的讲座,现在想想那会儿还真是幼稚得很呐,那时的施老对我来说就是神一般的存在,瘦削刚毅,深沉且充满了智慧,给我留下了极深的印象,八年后似乎我已经完全褪去了当年的年轻气盛,施老依然如此惊人地活跃在各个领域,以他那看似弱不禁风的瘦削身体,值得尊敬。
  • 今年高百最大黑马是兰州的两所大学(兰交大No.3,兰大No.7),上海最好的名次仅仅是同济No.10(上体大No.13,交大沦落到No.24,要知道交大可是夺冠热门),冠军重大直接刷新赛会纪录13分钟,清北分列No.2和No.5,北体No.4,国防科技大No.6,中山No.8,川大No.9,这些前十队伍派出几乎都是人均全马2小时50分以内的选手,而这样的人在财大在校生里连一个都没有(连嘉伟都做不到)。听吴安迪说他们浙大10人只有一个是毕业三年的老学长,其他都是在校生,就这样都跑到了No.12(No.9川大9:55:06,No.12浙大9:55:30,差距极小,即使是嘉伟上场也只能勉强跑到这些队伍的平均水平,但10人队伍中还包含2位女生)。
  • 今晚下班去小姨家补老母鸡汤,最近巨能吃(肉),周六晚去蜀地源点了425g肉+328g菜(¥59),昨晚在食堂点了三大荤(基围虾×8、烧鹅、毛豆肉丝,¥19),主要天气太冷,游走在感冒边缘,始终有些不适,多练多吃,养好身体才是王道。活得越久,越觉得纯粹的事情越少,无论学术还是业界,只有跑步,这是我觉得唯一纯粹且无争议的项目,现在资本也还彻底入侵大众跑步领域,跑团多为民间自发,团长用爱发电。跑步比之任何健身方式、乃至任何能想得到的事情都更加纯粹,同样的距离,无论你用何种方式,以更少的时间到达终点,就这么简单。

结构化Pipeline备份:

# -*- coding: utf-8 -*-
# @author: caoyang
# @email: caoyang@163.sufe.edu.cn
# 20231204: 将目前的一系列操作流程表达为一个完整的pipeline出来

import os
import re
import gc
import sys
import time
import joblib
import random
import logging
import numpy as np
import pandas as pd

from functools import wraps
from numpy.linalg import norm
from collections import Counter
from matplotlib import pyplot as plt
from sentence_transformers import SentenceTransformer

from src.util import timer

class BasePipeline:

	def __init__(self, **kwargs):
		for key, value in kwargs.items():
			self.__setattr__(key, value)
	
	##################################################################################################################################
	# 这一部分是和预训练模型及嵌入相关的函数
	##################################################################################################################################
	def load_pretrained_model(model_path):
		model = SentenceTransformer(model_path)
		return model
		
	def load_embedding(emb_path):
		emb = np.load(emb_path)
		return emb
		

class JobAdPipeline(BasePipeline):
	# Pipeline的参数
	data_type = "1m-full-dropdp"
	matched_occupation_type = "pt2-1991" # 可选的有 t2-1991, pt2-1991, pt24-1991, pt24i-1991 详见服务器上的jobad.py里的相关描述(p表示以prompt形式查询,t表示title, 2,4表示2级和4级行业标题,i表示招聘信息原文),目前认为pt2是最好的
	segment_library = "pkuseg" # 目前使用的中文分词包

	model_name_classify_title = "bge-base-zh" # 用于分类招聘信息的title到职业大典职业的中文嵌入模型(可选为m3e-base,bge-base-zh)
	model_name_cluster_skill = "bge-base-zh" # 用于聚类软硬技能的中文嵌入模型(可选为m3e-base,bge-base-zh)
	model_name_classify_dwa = "bge-base-zh" # 用于将任务映射到DWA的模型(可选为m3e-base,bge-base-zh)

	n_clusters_kmeans_soft = 1024    # 用于聚类的Kmeans模型的簇数(软技能)
	n_clusters_kmeans_solid = 1024   # 用于聚类的Kmeans模型的簇数(硬技能)
	random_state_kmeans_soft = 25 # Kmeans模型训练时用的随机种子,确保之后训练结果都是可以等价复现的(软技能)
	random_state_kmeans_solid = 25 # Kmeans模型训练时用的随机种子,确保之后训练结果都是可以等价复现的(硬技能)
	freq_threshold_for_nv_phrase_soft = 5 # 用于筛选高频短语的词频阈值(软技能,不推荐!)
	freq_threshold_for_nv_phrase_solid = 3 # 用于筛选高频短语的词频阈值(硬技能,不推荐!)
	sample_ratio_for_nv_phrase_soft = .2 # 用于筛选高频短语的采样率(软技能,推荐!)
	sample_ratio_for_nv_phrase_solid = .2 # 用于筛选高频短语的采样率(软技能,推荐!)

	# 关于软硬技能的区分方法,目前是根据动词来区分的:
	solid_skill_verbs = ["了解", "熟悉", "掌握", "熟练", "精通", "会"]
	soft_skill_verbs = ["具备", "具有", "富有", "拥有", "有"]
	task_verbs = ["完成", "负责", "制定", "指导", "实现",
				  "协调", "组织", "审查", "撰写", "分析", 
				  "理解", "阅读", "维护", "评估", "研究",
				  "参与", "推广", "接受", "挖掘", "审核",
				  "跟踪", "落实", "协助", "沟通", "制作",
				  "策划", "监督", "运用", "反馈", "采购",
				  "建立", "确保", "收集", "控制", "推进",
				  "设计", "开展", "处理", "配合", "对接",
				  "编写", "搭建", "排查", "考核",
				  ]
	# 关键的数据路径
	data_path = "data" # 原始数据
	result_path = "result" # 基本上都是用run.py跑出来的数据
	jupyter_path = "jupyter" # Jupyter跑出来的数据
	model_path = r"D:\model\huggingface" # 预训练模型(huggingface)存放的路径

	# 最终结构化数据的所有字段
	selected_columns = ("mid",					# 招聘广告的字段
						# 岗位预测信息
						"occupation_predict",	# 岗位预测结果
						"zydd_lv1",				# 职业大典lv1
						"zydd_lv2_1",			# 职业大典lv2(第一种方法,好)
						"zydd_lv2_2",			# 职业大典lv2(第二种方法,差)
						# 学历要求相关
						"education",			# 学历字段
						"education_upper",		# 学历上限
						"education_lower",		# 学历下限
						# 工作经验相关
						"experience",			# 工作经验字段
						"experience_number",	# 工作经验具体数值
						# 年龄要求相关
						"age",
						"age_number",
						# 专业相关
						"major",
						# 任务和技能相关
						"soft_skills",			# 软技能: JSON格式(Dict{<匹配到的原始字符串>: <软技能聚类簇的名称>})
						"solid_skills",			# 硬技能: JSON格式(Dict{<匹配到的原始字符串>: <硬技能聚类簇的名称>})
						"task_to_dwa", 			# 识别到的任务匹配的DWA的结果 
						)


	def __init__(self, **kwargs):
		super(JobAdPipeline, self).__init__(**kwargs)
		self.update_file_paths()
	
	def update_file_paths(self):
		# 这是一部分预先的数据路径
		self.file_paths = {
			"job_description": os.path.join(self.data_path, f"job-descriptions-{self.data_type}.csv"), # 原始数据: ODPS导出的原始招聘数据
			"zydd": os.path.join(self.data_path, "raw", "zydd.txt"), # 原始数据: 从网站上复制下来的职业大典表
			"zydd_clean": os.path.join(self.data_path, "zydd_all.txt"), # 通过src/nlp/jobad.py中的_generate_occupations_by_zydd函数(或者run.py中的run_generate_occupations函数)生成title_norm和level两个字段后的职业大典表(目前一二三级职业共计2071个条目)
			"occupation": os.path.join(self.data_path, f"occupations.txt"), # 这个目前是只包含1991个职业的表(筛除职业大典中的一级标题后只保留code和title_norm两个字段)
			"occupation_emb": os.path.join(self.data_path, f"occupations.txt.{self.model_name_classify_title}.npy"), # run.py中的run_generate_occupations函数运行,1991个职业的表的嵌入,这个是用来划分原始jd中的title的,所以用的是`self.model_name_classify_title`
			"dwa_en": os.path.join(self.data_path, f"dwa-onet-en.txt"), # 原始数据: ONet的原始DWA数据(231206,目前是2087个),在document/onet文件夹里有excel
			"dwa_zh": os.path.join(self.data_path, f"dwa-onet-zh.txt"), # 原始数据: ONet的原始DWA数据中文翻译(231206,目前是2087个)
			"dwa_zh_emb": os.path.join(self.data_path, f"dwa-onet-zh.txt.{self.model_name_classify_dwa}.npy"), # 数据来源:DWA及Skill映射考察(使用中文ONET).ipynb,中的“1.1 首次运行先把嵌入计算好存储到外部文件(不到1分钟)”
			# ----------------------
			"matched_occupation": os.path.join(self.result_path, f"job-occupations-{self.matched_occupation_type}-{self.data_type}-{self.model_name_classify_title}.csv"), # 这个数据来自run.py(请使用JobSkillRecognition-part中的版本)的run_classify_title.py(本地跑太慢,需要在服务器上运行)
			"metadata": os.path.join(self.result_path, f"job-metadata-{self.data_type}.csv"), # 这个数据来自jobad.py里的extract_metadata,或者run.py中的run_metadata函数
			"metadata_detail": os.path.join(self.result_path, f"job-metadata-detailed-{self.data_type}.csv"), # 专门写了一个**metadata转化为detail的脚本.ipynb**的脚本
			"job_phrase": os.path.join(self.result_path, f"job-phrases-{self.segment_library}-{self.data_type}.csv"), # run.py中run_nlp_tokenization,会筛除招聘信息中的无用分局,一共生成四个字段(不含id): raw(即单纯的分句), detailed(详细的分句结果),simplified(只包含名词和动词块的结果),brief(这个最常用,即更精简的结果,如筛除介词短语)
			"nv_phrase": os.path.join(self.result_path, f"job-nv-{self.data_type}.csv"), # 数据来自run.py中run_nv_extracter函数,包含每个招聘信息的动宾短语抽取,非常重要!!!目前的逻辑是按照job-phrase文件的raw字段进行二次分词处理,有人会说为什么不直接用detailed或者simplified,我不想解释,反正这样更精确
			# ----------------------
			"soft_skills_all": os.path.join(self.jupyter_path, "-软技能.csv"), # 数据来源:20231121-岗位字段匹配后的数据分析.ipynb,运行`display_pipeline(df, {}, True, 20)`</b></font>
			"solid_skills_all": os.path.join(self.jupyter_path, "-硬技能.csv"), # 数据来源:20231121-岗位字段匹配后的数据分析.ipynb,运行`display_pipeline(df, {}, True, 20)`</b></font>
		}
		# --------------------------------------
		# 这是一部分和模型参数、样本参数相关的文件路径
		# 读取对应的软技能文件确定样本数
		df_soft = pd.read_csv(open(self.file_paths["soft_skills_all"], encoding="utf8"), sep='\t', header=0)
		# view = df_soft[df_soft.freq >= self.freq_threshold_for_nv_phrase_soft]
		view = df_soft[: int(df_soft.shape[0] * self.sample_ratio_for_nv_phrase_soft)]
		samples_soft = view["n"].drop_duplicates(keep="first").tolist()
		self.n_samples_soft = len(samples_soft)  # 目前软技能是9720
		logging.info(f"软技能: {self.n_samples_soft}")

		# 读取对应的硬技能文件确定样本数
		df_solid = pd.read_csv(open(self.file_paths["solid_skills_all"], encoding="utf8"), sep='\t', header=0)

		# view = df_solid[df_solid.freq >= self.freq_threshold_for_nv_phrase_solid]
		view = df_solid[: int(df_solid.shape[0] * self.sample_ratio_for_nv_phrase_solid)]	
		samples_solid = view["n"].drop_duplicates(keep="first").tolist()
		self.n_samples_solid = len(samples_solid)  # 目前硬技能是11118
		logging.info(f"硬技能: {self.n_samples_solid}")

		# 这三个数据(模型、嵌入、聚类日志)的来源:20231129-动宾短语异质性探究.ipynb - 2.1 测试预训练模型在软技能聚类上的情况
		self.file_paths["soft_kmeans_model"] = os.path.join(self.jupyter_path, f"Kmeans{self.random_state_kmeans_soft}-soft-cluster{self.n_clusters_kmeans_soft}-sample{self.n_samples_soft}-{self.model_name_cluster_skill}.m")
		self.file_paths["soft_embedding"] = os.path.join(self.jupyter_path, f"soft-{self.n_samples_soft}-{self.model_name_cluster_skill}.emb.npy")
		self.file_paths["soft_cluster_detail"] = os.path.join(self.jupyter_path, f"Kmeans{self.random_state_kmeans_soft}-soft-cluster{self.n_clusters_kmeans_soft}-sample{self.n_samples_solid}-{self.model_name_cluster_skill}.txt")
		# 231206: 这个数据目前来自 20231129-动宾短语异质性探究.ipynb - 4.1 软技能簇取名 - 直接运行最后一个“直接找那个距离簇心最近的样本”的结果(不同的模型是分下来的,要运行多次)
		self.file_paths["soft_cluster_name"] = os.path.join(self.jupyter_path, f"clusterName-Kmeans{self.random_state_kmeans_soft}-soft-cluster{self.n_clusters_kmeans_soft}-sample{self.n_samples_soft}-{self.model_name_cluster_skill}.txt")

		# 这三个数据(模型、嵌入、聚类日志)的来源:20231129-动宾短语异质性探究.ipynb - 2.2 测试预训练模型在硬技能聚类上的情况
		self.file_paths["solid_kmeans_model"] = os.path.join(self.jupyter_path, f"Kmeans{self.random_state_kmeans_solid}-solid-cluster{self.n_clusters_kmeans_solid}-sample{self.n_samples_solid}-{self.model_name_cluster_skill}.m")
		self.file_paths["solid_embedding"] = os.path.join(self.jupyter_path, f"solid-{self.n_samples_solid}-{self.model_name_cluster_skill}.emb.npy")
		self.file_paths["solid_cluster_detail"] = os.path.join(self.jupyter_path, f"Kmeans{self.random_state_kmeans_solid}-solid-clusterself.{self.n_clusters_kmeans_solid}-sample{self.n_samples_solid}-{self.model_name_cluster_skill}.txt")
		# 231206: 这个数据目前来自 20231129-动宾短语异质性探究.ipynb - 4.2 硬技能簇取名 - 直接运行最后一个“直接找那个距离簇心最近的样本”的结果(不同的模型是分下来的,要运行多次)
		self.file_paths["solid_cluster_name"] = os.path.join(self.jupyter_path, f"clusterName-Kmeans{self.random_state_kmeans_solid}-solid-cluster{self.n_clusters_kmeans_solid}-sample{self.n_samples_solid}-{self.model_name_cluster_skill}.txt")

	def load_full_data(self, nrows=None):
		df_jd_with_occ = self._load_job_description_with_occupation(nrows=nrows)
		logging.info(f"df_jd_with_occ: {df_jd_with_occ.shape}")
		logging.info('#' * 64)

		df_nv = self._load_nv_phrases(nrows=nrows)
		logging.info(f"df_nv: {df_nv.shape}")
		logging.info('#' * 64)

		df_phrase = self._load_job_phrases(usecols=[0, 4], nrows=nrows)
		logging.info(f"df_phrase: {df_phrase.shape}")
		logging.info('#' * 64)

		df_metadata = self._load_detailed_metadata(nrows=nrows)
		logging.info(f"df_metadata: {df_metadata.shape}")
		logging.info('#' * 64)

		df = df_jd_with_occ.merge(df_nv, on="mid", how="left") \
						.merge(df_phrase, on="mid", how="left") \
						.merge(df_metadata, on="mid", how="left")
		logging.info(f"拼接后的总表: {df.shape}")

		return df

	def structurize(self, df, save_path):
		with open(save_path, 'w', encoding="utf8") as f:
			f.write('\t'.join(self.selected_columns) + '\n')
		
		# 原始招聘信息数据里有的字段(除了mid)都不会被包含再其中

		# 用于生成dataframe的字典
		structured_datadict = {column: list() for column in self.selected_columns}
		logging.info(f"加载模型{self.model_name_classify_dwa}")
		dwa_encoding_model = BasePipeline.load_pretrained_model(model_path=os.path.join(self.model_path, self.model_name_classify_dwa))
		
		logging.info(f"加载嵌入{self.file_paths['dwa_zh_emb']}") # 注意DWA嵌入所使用的编码模型与model_name_classify_dwa匹配,这意味着下面也是
		dwa_embeddings = BasePipeline.load_embedding(emb_path=self.file_paths["dwa_zh_emb"])
		norm_dwa_embeddings = norm(dwa_embeddings, axis=1)  
		
		logging.info(f"读取DWA文件: {self.file_paths['dwa_zh']}")
		dwas = pd.read_csv(self.file_paths["dwa_zh"], sep='\t', header=0)["dwa"].tolist()
		
		logging.info(f"读取软技能聚类模型: {self.file_paths['soft_kmeans_model']}")
		soft_kmeans_model = joblib.load(self.file_paths["soft_kmeans_model"])
		logging.info(f"读取软技能聚类簇名称: {self.file_paths['soft_cluster_name']}")
		soft_cluster_names = open(self.file_paths["soft_cluster_name"], 'r', encoding="utf8").read().splitlines()
		
		logging.info(f"读取硬技能聚类模型: {self.file_paths['solid_kmeans_model']}")
		solid_kmeans_model = joblib.load(self.file_paths["solid_kmeans_model"])
		logging.info(f"读取硬技能聚类簇名称: {self.file_paths['solid_cluster_name']}")
		
		solid_cluster_names = open(self.file_paths["solid_cluster_name"], 'r', encoding="utf8").read().splitlines()

		kwargs = {"dwas": dwas,                                   # 所有的DWA字符串构成的列表
				  "dwa_embeddings": dwa_embeddings,               # DWA嵌入绝阵
				  "norm_dwa_embeddings": norm_dwa_embeddings,     # 行标准化的DWA嵌入矩阵(便于计算相似度)
				  "soft_skill_cluster_model": soft_kmeans_model,  # 训练好的软技能聚类模型
				  "solid_skill_cluster_model": solid_kmeans_model,# 训练好的硬技能聚类模型
				  "soft_skill_names": soft_cluster_names,         # 标准化的软技能名称(簇命名)
				  "solid_skill_names": solid_cluster_names,       # 标准化的硬技能名称(簇命名)
				  "topk_dwa": 3,								  # 有时间自己手动改,目前输出前三看一看就完事了!!!
				  }
		if self.model_name_cluster_skill == self.model_name_classify_dwa:
			logging.info("任务和技能的编码使用同一个模型")
			# 如果任务技能使用的编码模型是一样的,那就只要传一个模型进去即可
			encoding_model = dwa_encoding_model
			kwargs["encoding_model"] = dwa_encoding_model
			kwargs["task_encoding_model"] = None
			kwargs["skill_encoding_model"] = None
		else:
			logging.info("任务和技能的编码使用不同模型")
			# 如果不同就要分别传入
			kwargs["encoding_model"] = None
			kwargs["task_encoding_model"] = dwa_encoding_model
			kwargs["skill_encoding_model"] = load_pretrained_model(model_path=os.path.join(model_path, model_name_cluster_skill))

		# 遍历每一条数据并进行结构化处理
		write_string = str()
		for i in range(df.shape[0]):
			if (i + 1) % 100000 == 0:
				with open(save_path, 'a', encoding="utf8") as f:
					f.write(write_string)
				write_string = str()
			logging.info(i)
			kwargs["entry"] = df.loc[i, :]
			results = self._easy_structurize(**kwargs)
			string_list = []
			for column in self.selected_columns:
				string_list.append(str(results[column]))
				structured_datadict[column].append(results[column])
		if write_string:
			with open(save_path, 'a', encoding="utf8") as f:
				f.write(write_string)
			write_string = str()
		logging.info("Over!!!!!!!")
		structured_dataframe = pd.DataFrame(structured_datadict, columns=structured_datadict.keys())
		structured_dataframe.to_csv(save_path.replace('txt', 'csv'), sep='\t', header=True, index=False)
		return structured_dataframe

	# @param entry: pd.series, 招聘信息DataFrame完整的一列内容
	# @param dwas: List[Str], 长度为N_DWA的列表
	# @param dwa_embeddings: np.darray, 形状为(N_DWA, 768)的嵌入矩阵
	# @param norm_dwa_embeddings: np.darray, 行标准化后的dwa_embeddings矩阵
	# @param soft_skill_cluster_model: 训练好的软技能聚类模型(sklearn)
	# @param solid_skill_cluster_model: 训练好的硬技能聚类模型(sklearn)
	# @param soft_skill_names: List[Str], 标准的软技能名称集合(软技能聚类簇名称)
	# @param solid_skill_names: List[Str], 标准的硬技能名称集合(软技能聚类簇名称)
	# @param encoding_model: 缺省的编码嵌入模型(BertModel)
	# @param task_encoding_model: 用于编码任务的嵌入模型(BertModel)
	# @param skill_encoding_model: 用于编码技能的嵌入模型(BertModel)
	# @param topk_dwa: Int, 预测的排名前topk个的DWA结果
	def _easy_structurize(self,
						  entry, 
						  dwas, 
						  dwa_embeddings, 
						  norm_dwa_embeddings, 
						  soft_skill_cluster_model,
						  solid_skill_cluster_model,
						  soft_skill_names,
						  solid_skill_names,
						  encoding_model = None, 
						  task_encoding_model = None, 
						  skill_encoding_model = None,
						  topk_dwa = 3,
						  ):
		results = {column: None for column in self.selected_columns}

		
		# 1. 首先把entry里自带的字段填到最终的result中(其实除了软技能、硬技能、DWA,其他都是可以直接用原始的数据拿到的)
		results["mid"] = entry["mid"]
		results["occupation_predict"] = entry["occupation_predict"]
		results["zydd_lv1"] = entry["zydd_lv1"]
		results["zydd_lv2_1"] = entry["zydd_lv2_1"]
		results["zydd_lv2_2"] = entry["zydd_lv2_2"]
		results["education"] = entry["education"]
		results["education_upper"] = entry["education_upper"]
		results["education_lower"] = entry["education_lower"]
		results["experience"] = entry["experience"]
		results["experience_number"] = entry["experience_number"]
		results["age"] = entry["age"]
		results["age_number"] = entry["age_number"]
		results["major"] = entry["major"]


		# 2. 非原始字段
		# 目前只有三个字段:soft_skills、solid_skills、task_to_dwa
		# soft_skills: 软技能: JSON格式(Dict{<匹配到的原始字符串>: <软技能聚类簇的名称>})
		# solid_skills: 硬技能: JSON格式(Dict{<匹配到的原始字符串>: <硬技能聚类簇的名称>})
		# task_to_dwa: 识别到的任务匹配的DWA的结果 
		## 首先识别软技能短语、硬技能短语、任务短语
		phrases = entry["brief"] # 选取brief系列的短语用于识别任务
		nv_phrases = entry["nv"] # 选取nv动宾短语用于识别软硬技能
		soft_skill_phrases = []
		solid_skill_phrases = []
		task_phrases = []
		
		### 动宾短语用于抽取软硬技能
		for verb, noun in nv_phrases:
			if verb in self.soft_skill_verbs:
				# 软技能:只录入名词成分,因为软技能聚类只用名词做的聚类
				soft_skill_phrases.append(noun)
			elif verb in self.solid_skill_verbs:
				# 硬技能:只录入名词成分,因为硬技能聚类只用名词做的聚类
				solid_skill_phrases.append(noun)
			else:
				# 任务短语的识别不用动宾短语识别,而是直接使用原始的分句结果(brief版本)
				continue
				
		### brief短语识别任务短语
		for phrase in phrases:          
			n_suffix = min(len(phrase), 6) # 这里我定向取前6个字符用于判定是否属于任务短语
			is_task_flags = sum([verb in phrase[: n_suffix] for verb in self.task_verbs])
			if is_task_flags == 0:
				# 不是任务字段   
				continue
			else:
				# logging.debug(f"添加{phrase}")
				task_phrases.append(phrase)
		#### 1. 软技能
		soft_skills_json = dict()	# 软技能结果JSON
		if soft_skill_phrases:
			emb = encoding_model.encode(soft_skill_phrases) # (N, 768)
			predict = soft_skill_cluster_model.predict(emb) # (n_test, 1)
			
			for index, soft_skill_string in zip(predict, soft_skill_phrases):
				soft_skills_json[soft_skill_string] = soft_skill_names[index]
		else:
			# logging.info("1. 没有识别到软技能!")
			pass

		#### 2. 硬技能
		solid_skills_json = dict()	# 硬技能结果JSON
		if solid_skill_phrases:
			emb = encoding_model.encode(solid_skill_phrases) # (N, 768)
			predict = solid_skill_cluster_model.predict(emb) # (n_test, 1)
			
			for index, solid_skill_string in zip(predict, solid_skill_phrases):
				solid_skills_json[solid_skill_string] = solid_skill_names[index]
		else:
			# logging.info("1. 没有识别到硬技能!")
			pass

		#### 3. 任务短语的识别
		task_to_dwa_json = dict()
		if task_phrases:
			if encoding_model is None:
				# 没有传入ncoding_model参数,说明必然传入task_encoding_model
				assert task_encoding_model is not None
				# 即区分任务和技能的嵌入模型,此时需要事先确定每个phrase所属的
				task_phrase_embeddings = task_encoding_model.encode(task_phrases)   # (N_TASK_PHRASE, 768)
			else:
				# 传入了encoding_model参数,说明任务技能用的同一个模型,直接调用
				task_phrase_embeddings = encoding_model.encode(task_phrases)        # (N_TASK_PHRASE, 768)
			norm_task_phrase_embeddings = norm(task_phrase_embeddings, axis=1)
			dot_res = np.dot(task_phrase_embeddings, dwa_embeddings.T)  # (N_TASK_PHRASE, N_DWA)
			norm_matrix = np.dot(norm_task_phrase_embeddings.reshape(-1, 1), norm_dwa_embeddings.reshape(1, -1)) # (N_TASK_PHRASE, N_DWA)
			cosine_matrix = dot_res / norm_matrix          # (N_TASK_PHRASE, N_DWA)
			argsort_res_dwa = np.argsort(cosine_matrix)    # (N_TASK_PHRASE, N_DWA)
			
			for j in range(argsort_res_dwa.shape[0]):
				cands_dwa = []
				for k in range(1, topk_dwa + 1):
					cands_dwa.append(dwas[argsort_res_dwa[j, -k]])

				task_to_dwa_json[task_phrases[j]] = list()
				for cand in cands_dwa:
					task_to_dwa_json[task_phrases[j]].append(cand)
		else:
			# logging.info("3. 没有识别到和任务相关的短语!")
			pass

		## 填补对应结果
		results["soft_skills"] = soft_skills_json
		results["solid_skills"] = solid_skills_json
		results["task_to_dwa"] = task_to_dwa_json

		
		# 3. 做结果的最后校验
		## 3.1 results的所有字段和
		assert len(results.keys()) == len(self.selected_columns), f"{list(results.keys())}\nv.s.\n{self.selected_columns}"
		for key in results.keys():
			assert key in self.selected_columns, key
			assert results[key] is not None, results
		return results
		
	# 就是上面_easy_structure函数的翻版,只用于展示
	def _easy_display(self,
					  entry, 
					  dwas, 
					  dwa_embeddings, 
					  norm_dwa_embeddings, 
					  soft_skill_cluster_model,
					  solid_skill_cluster_model,
					  soft_skill_names,
					  solid_skill_names,
					  encoding_model = None, 
					  task_encoding_model = None, 
					  skill_encoding_model = None,
					  topk_dwa = 3,
					  ):
		# with open(save_path, 'r', encoding="utf8") as f:
			# f.write('\t'.join(selected_columns) + '\n')
		logging.info("一、原文:")
		logging.info(entry["intro"])
		logging.info('-*' * 32)
		
		logging.info("二、已标注的信息:")
		logging.info(f"1. 标题: {entry['title']}")
		logging.info(f"2. lv2行业名称: {entry['industry_name_lv2']}")
		logging.info(f"3. lv4行业名称: {entry['industry_name_lv4']}")
		logging.info(f"4. 薪酬范围: {entry['salary_range']} (单位: {entry['salary_unit']})")
		logging.info('-*' * 32)
		
		logging.info("三、抽取结果:")
		logging.info(f"1. 抽取的专业: {entry['major']}")
		logging.info(f"2. 抽取的工作经验: {entry['experience']}")
		logging.info(f"3. 抽取的工作年龄要求: {entry['age']}")
		logging.info(f"4. 抽取的专业下限: {entry['education_lower']}")
		logging.info(f"5. 抽取的专业上限: {entry['education_upper']}")
		
		logging.info('-*' * 32)
		logging.info("四、岗位预测结果:")
		logging.info(f"1. 预测的lv1职业分类(79类): {entry['zydd_lv1']}")
		logging.info(f"2. 预测的lv2职业分类(443类): {entry['zydd_lv2_1']}") # 上面,lv2_1比lv2_2好
		logging.info(f"3. 预测的lv3职业岗位(1548类): {entry['occupation_predict']}")
		logging.info(f"4. 预测的lv3排序结果: {entry['candidates'][:5]}")
		
		logging.info('-*' * 32)
		logging.info("五、抽取的短语及匹配的DWA:")
		
		# 计算每个短语匹配的结果
		
		## 首先识别软技能短语、硬技能短语、任务短语
		phrases = entry["brief"] # 选取brief系列的短语用于识别任务
		nv_phrases = entry["nv"] # 选取nv动宾短语用于识别软硬技能
		soft_skill_phrases = []
		solid_skill_phrases = []
		task_phrases = []
		
		### 动宾短语用于抽取软硬技能
		for verb, noun in nv_phrases:
			if verb in self.soft_skill_verbs:
				# 软技能:只录入名词成分,因为软技能聚类只用名词做的聚类
				soft_skill_phrases.append(noun)
			elif verb in self.solid_skill_verbs:
				# 硬技能:只录入名词成分,因为硬技能聚类只用名词做的聚类
				solid_skill_phrases.append(noun)
			else:
				# 任务短语的识别不用动宾短语识别,而是直接使用原始的分句结果(brief版本)
				continue
		### brief短语识别任务短语
		for phrase in phrases:          
			n_suffix = min(len(phrase), 6) # 这里我定向取前6个字符用于判定是否属于任务短语
			is_task_flags = sum([verb in phrase[: n_suffix] for verb in self.task_verbs])
			if is_task_flags == 0:
				# 不是任务字段   
				continue
			else:
				logging.info(f"添加{phrase}")
				task_phrases.append(phrase)
		#### 1. 软技能
		if soft_skill_phrases:
			logging.info("1. 软技能:")
			emb = encoding_model.encode(soft_skill_phrases) # (N, 768)
			predict = soft_skill_cluster_model.predict(emb) # (n_test, 1)
			display_dict = {"软技能": [], "预测的分类结果(聚类簇的名称)": []}
			for index, soft_skill_string in zip(predict, soft_skill_phrases):
				display_dict["软技能"].append(soft_skill_string)
				display_dict["预测的分类结果(聚类簇的名称)"].append(soft_skill_names[index])
			print(pd.DataFrame(display_dict, columns=list(display_dict.keys())))
		else:
			logging.info("1. 没有识别到软技能!")
		
		#### 2. 硬技能
		if solid_skill_phrases:
			logging.info("1. 硬技能:")
			emb = encoding_model.encode(solid_skill_phrases) # (N, 768)
			predict = solid_skill_cluster_model.predict(emb) # (n_test, 1)
			display_dict = {"硬技能": [], "预测的分类结果(聚类簇的名称)": []}
			for index, solid_skill_string in zip(predict, solid_skill_phrases):
				display_dict["硬技能"].append(solid_skill_string)
				display_dict["预测的分类结果(聚类簇的名称)"].append(solid_skill_names[index])
			print(pd.DataFrame(display_dict, columns=list(display_dict.keys())))
		else:
			logging.info("1. 没有识别到硬技能!")
		
		#### 3. 首先是任务短语的识别
		if task_phrases:
			logging.info("3. 任务短语:")
			if encoding_model is None:
				# 没有传入ncoding_model参数,说明必然传入task_encoding_model
				assert task_encoding_model is not None
				# 即区分任务和技能的嵌入模型,此时需要事先确定每个phrase所属的
				task_phrase_embeddings = task_encoding_model.encode(task_phrases)   # (N_TASK_PHRASE, 768)
			else:
				# 传入了encoding_model参数,说明任务技能用的同一个模型,直接调用
				task_phrase_embeddings = encoding_model.encode(task_phrases)        # (N_TASK_PHRASE, 768)
			norm_task_phrase_embeddings = norm(task_phrase_embeddings, axis=1)
			dot_res = np.dot(task_phrase_embeddings, dwa_embeddings.T)  # (N_TASK_PHRASE, N_DWA)
			norm_matrix = np.dot(norm_task_phrase_embeddings.reshape(-1, 1), norm_dwa_embeddings.reshape(1, -1)) # (N_TASK_PHRASE, N_DWA)
			cosine_matrix = dot_res / norm_matrix          # (N_TASK_PHRASE, N_DWA)
			argsort_res_dwa = np.argsort(cosine_matrix)    # (N_TASK_PHRASE, N_DWA)
			for j in range(argsort_res_dwa.shape[0]):
				cands_dwa = []
				for k in range(1, topk_dwa + 1):
					cands_dwa.append(dwas[argsort_res_dwa[j, -k]])
				logging.info(f"{j + 1}. {task_phrases[j]}")
				logging.info(f"  - 匹配的排名前{topk_dwa}的DWAs:")
				for cand in cands_dwa:
					logging.info(f"    + {cand}")
				logging.info('·' * 32)        
		else:
			logging.info("3. 没有识别到和任务相关的短语!")

	##################################################################################################################################
	# 这一部分全部都是数据读取的函数
	##################################################################################################################################
	# 招聘信息表添加标准岗位信息,构成带标准岗位信息的招聘信息表
	@timer
	def _load_job_description_with_occupation(self, nrows=None):
		# 一、读取招聘广告表(原始数据)
		df_jd = pd.read_csv(self.file_paths["job_description"], sep='\t', header=0, nrows=nrows)
		logging.info(f"原始招聘信息表: {df_jd.shape}")    
		logging.info('-' * 64)
		# -----------------------------------------------------------------------------------------------------
		# 二、读取职业大典(目前还都是从职业大典中拿到的,其实可以自己添加一些,但是要记得更新对应的occupation嵌入)
		# 这里要注意,做匹配的时候用的是occupations表,那个只筛取了职业大典里的二三级职业,1991个
		# 但是这里我要用全量的2070个职业,因为后面数据处理需要用到一级分类的结果
		df_zydd_clean = pd.read_csv(self.file_paths["zydd_clean"], sep='\t', header=0)
		logging.info(f"职业大典表: {df_zydd_clean.shape}")
		# 确认一下岗位表中的重复岗位
		logging.info(f"重复项: {Counter(df_zydd_clean['title_norm']).most_common(5)}") 
		# 去重,并保留靠后的结果(即级别更低、类别更细的结果)
		df_zydd_clean_dropdp = df_zydd_clean.drop_duplicates(["title_norm"], keep="last").reset_index(drop=True)
		logging.info(f"去重后的职业大典表: {df_zydd_clean_dropdp.shape}")
		# 制作岗位索引以备后用
		## 注意code_to_name是一对一没有问题,code没有重复,用的是df_zydd
		## 但是name_to_code可能存在一个name对应多个code的情况,因此用的是df_zydd_dropdp
		zydd_code_to_name = {df_zydd_clean.loc[i, "code"]: df_zydd_clean.loc[i, "title_norm"] for i in range(df_zydd_clean.shape[0])}
		zydd_name_to_code = {df_zydd_clean_dropdp.loc[i, "title_norm"]: df_zydd_clean_dropdp.loc[i, "code"] for i in range(df_zydd_clean_dropdp.shape[0])}
		logging.info(f"字典zydd-code-to-name: {len(zydd_code_to_name)}")
		logging.info(f"字典zydd-name-to-code: {len(zydd_name_to_code)}")
		logging.info('-' * 64)
		# -----------------------------------------------------------------------------------------------------
		# 三、读取招聘广告表中title匹配岗位的运行结果文件(预训练模型匹配,目前需要在服务器上跑)
		df_matched_occ = pd.read_csv(self.file_paths["matched_occupation"], sep='\t', header=0, nrows=nrows)
		df_matched_occ["candidates"] = df_matched_occ["candidates"].map(lambda x: x.split('|')) # 将`|`分隔的记录转化为列表格式
		logging.info(f"title匹配岗位的结果表: {df_matched_occ.shape}")
		logging.info('-' * 64)
		# -----------------------------------------------------------------------------------------------------
		# 四、拼接df_jd和df_matched_occ,并进行具体的职业及行业预测
		# 选取给定的前三名预测结果,确定对应的一级分类(79个,目前occupations全体是1991个的二级和三级分类结果的全体)
		def _genr_zydd_lv1(_cands, _top=3):
			_cands_used = _cands[:_top] # 选取前三的结果(太多不好,少一点比较保守,因为靠后的预测结果可能是错误的)
			_codes_used = list(map(lambda _x: zydd_name_to_code[_x], _cands_used)) # 转化为codes
			_codes_used_1 = list(map(lambda _x: '-'.join(_x.split('-')[:2]), _codes_used)) # 只取codes的前三位(用-分割,即取2级别分类)
			_counter_1 = Counter(_codes_used_1).most_common()
			if _counter_1[0][1] == 1:
				# 说明_cands_used里的每个结果分属不同的一级分类,那么取第一个即可
				return zydd_code_to_name[_codes_used_1[0]]
			else:
				# 在_top=3的情况下说明_cands_used_1里有唯一最多的结果,可以直接取频数靠前的结果
				# 但_top>3的话,结果并不唯一,此时不太好处理,我也懒得费事了,考虑_top=3即可
				return zydd_code_to_name[_counter_1[0][0]]
		
		# 选取给定的前三名预测结果,确定对应的二级分类(443个二级分类,目前标准岗位表全体是1991个的二级和三级分类结果的全体)
		# 方法一:先确定前三名所属的一级分类的众数
		#   - 如果有,则以靠前的匹配该众数的岗位进行匹配
		#   - 否则,取排靠前的结果
		# 我认为_genr_zydd_lv2_1要比_genr_zydd_lv2_2好,因为如果直接看二级分类的众数,很多时候可能前三所属的都不同,结果就只能取靠前的结果了,其实不准
		def _genr_zydd_lv2_1(_cands, _top=3):
			_cands_used = _cands[:_top] # 选取前三的结果(太多不好,少一点比较保守,因为靠后的预测结果可能是错误的)
			_codes_used = list(map(lambda _x: zydd_name_to_code[_x], _cands_used)) # 转化为codes
			_codes_used_1 = list(map(lambda _x: '-'.join(_x.split('-')[:2]), _codes_used)) # 只取codes的前2位(用-分割,即取1级分类)
			_codes_used_2 = list(map(lambda _x: '-'.join(_x.split('-')[:3]), _codes_used)) # 只取codes的前3位(用-分割,即取2级分类)
			_counter_1 = Counter(_codes_used_1).most_common()
			if _counter_1[0][1] == 1:
				# 说明_cands_used里的每个结果分属不同的一级分类,那么取第一个即可
				return zydd_code_to_name[_codes_used_2[0]]
			else:
				# 在_top=3的情况下说明_cands_used_1里有唯一最多的结果,可以直接取频数靠前的结果
				# 但_top>3的话,结果并不唯一,此时不太好处理,我也懒得费事了,考虑_top=3即可
				for _code in _codes_used_2[:_top]:
					if _code.startswith(_counter_1[0][0]):   
						return zydd_code_to_name[_code]
				assert False, f"必然有`_code.startswith(_counter_1[0][0])`成功,但是{_codes_used_2[:_top]} v.s. {_counter_1[0][0]}"

		# 选取给定的前三名预测结果,确定对应的二级分类(443个二级分类,目前标准岗位表全体是1991个的二级和三级分类结果的全体)
		# 方法二:先确定前三名所属的二级分类的众数
		#   - 如果有,则以靠前的匹配该众数的岗位进行匹配
		#   - 否则,取排靠前的结果
		# 我认为_genr_zydd_lv2_1要比_genr_zydd_lv2_2好,因为如果直接看二级分类的众数,很多时候可能前三所属的都不同,结果就只能取靠前的结果了,其实不准
		def _genr_zydd_lv2_2(_cands, _top=3):
			_cands_used = _cands[:_top] # 选取前三的结果(太多不好,少一点比较保守,因为靠后的预测结果可能是错误的)
			_codes_used = list(map(lambda _x: zydd_name_to_code[_x], _cands_used)) # 转化为codes
			_codes_used_2 = list(map(lambda _x: '-'.join(_x.split('-')[:3]), _codes_used)) # 只取codes的前三位(用-分割,即取2级别分类)
			_counter_2 = Counter(_codes_used_2).most_common()
			if _counter_2[0][1] == 1:
				# 说明_cands_used里的每个结果分属不同的二级分类,那么取第一个即可
				return zydd_code_to_name[_codes_used_2[0]]
			else:
				# 在_top=3的情况下说明_cands_used里有唯一最多的结果,可以直接取频数靠前的结果
				# 但_top>3的话,结果并不唯一,此时不太好处理,我也懒得费事了,考虑_top=3即可
				return zydd_code_to_name[_counter_2[0][0]]
			
		# 注意这里只取了招聘信息原文的主键(mid),title,2/4级的行业名称,信息原文,薪酬范围及单位(用于薪酬分析),日期(我要看一下日期情况)
		df = df_matched_occ.merge(df_jd[["mid", "title", "industry_name_lv2", "industry_name_lv4", 
										 "intro", "salary_range", "salary_unit", "pdate"]], on="mid", how="left")
		df["occupation_predict"] = df["candidates"].map(lambda x: x[0]) # 选取top1作为直接预测的岗位结果(有2级,也有3级,看下来这个准确率至少有90%以上,但是有一些准确率还不是那么好)
		df["zydd_lv1"] = df["candidates"].map(_genr_zydd_lv1)     # 1级岗位分类
		df["zydd_lv2_1"] = df["candidates"].map(_genr_zydd_lv2_1) # 2级岗位分类(方法一):这个更好!
		df["zydd_lv2_2"] = df["candidates"].map(_genr_zydd_lv2_2) # 2级岗位分类(方法二):这个不如上面的好!
		
		df["salary_range"] = df["salary_range"].map(eval)
		df["pdate"] = pd.to_datetime(df["pdate"], format="%Y-%m-%d")
		return df

	# 读取动宾短语表:这个表只有两列,即mid和nv,nv是一个列表形式的字符串
	@timer
	def _load_nv_phrases(self, nrows=None):
		df_nv = pd.read_csv(self.file_paths["nv_phrase"], sep='\t', header=0, nrows=nrows)
		df_nv.columns = ["mid", "nv"]
		df_nv["nv"] = df_nv["nv"].map(eval)
		logging.info(f"动宾短语表: {df_nv.shape}")
		return df_nv

	# 读取招聘信息完整的分句、分词信息表(除了mid字段外,还包含四个字段:raw, detailed(这个则包含完整的), simplified(这个只有v, n, o三种类型), brief)
	# 一般调用mid和brief用于匹配DWA,即usecols=[0, 4]
	# 这个文件太大了,有2G,所以考虑引入usecols参数,而不是全读进来再取列
	@timer
	def _load_job_phrases(self, usecols=[0, 4], nrows=None):
		df_phrase = pd.read_csv(self.file_paths["job_phrase"], sep='\t', header=0, usecols=usecols, nrows=nrows)
		for column in df_phrase.columns:
			if column in ["raw", "detailed", "simplified", "brief"]:
				df_phrase[column] = df_phrase[column].map(eval)
		logging.info(f"招聘信息初步分词短语表: {df_phrase.shape}")
		return df_phrase

	# 读取详细的metadata表,目前包含的字段有:
	# - mid: 主键,Str
	# - salary: 列表形式的薪酬整型数(List[Int]),这个不关键了,因为原始招聘信息中已经包含薪酬
	# - education: 用`|`分隔的若干和学历相关的字符串(正则匹配到的),有缺失
	# - major: 列表形式的专业字符串(List[Str]正则匹配到的)
	# - experience: 用`|`分隔的若干和工作经验相关的字符串(正则匹配到的)
	# - age: 用`|`分隔的若干和年龄要求相关的字符串(正则匹配到的)
	# - salary_upper: Int,这个不关键了,因为原始招聘信息中已经包含薪酬
	# - salary_lower: Int,这个不关键了,因为原始招聘信息中已经包含薪酬
	# - education_upper: Str,最低学历要求(空字符串为缺失)
	# - education_lower: Str,最高学历要求(空字符串为缺失)
	# - experience_number: List[Int or Tuple(2)],如果是Int,则表示这么多年的工作经验,如果是Tuple,将是一个二元组,表示工作年限的范围,取最小值即可
	# - age_number: List[Int],建议取最小值
	@timer
	def _load_detailed_metadata(self, nrows=None):
		# 读取元信息文件(经过预处理后的详细版本,见“metadata转化为detail的脚本.ipynb”)
		
		_split_by_vert = lambda x: x.split('|') if x == x else list()
		# 对抽取到的工作经验取平均值
		
		# # 这种情况,如果是1~3年工作经验,会被处理成2年
		# _average_experience_number = lambda x: np.mean(list(map(lambda _x: np.mean(_x) if isinstance(_x, tuple) else _x, x)))
		# 这种情况,如果是1~3年工作经验,会被处理成1年(其实我觉得最小工作经验似乎更有说服力)
		_average_experience_number = lambda x: np.mean(list(map(lambda _x: np.min(_x) if isinstance(_x, tuple) else _x, eval(x))))
		
		# 处理age_number
		def _min_age_number(x):
			x = eval(x)
			if x:
				return np.min(x)
			else:
				return np.nan
		# 这个表字段太多,做一个字段处理的映射字典方便写
		selected_columns_and_mapping_function = {
			"mid": None,
			# "salary": eval,
			"education": _split_by_vert,
			"major": eval,
			"experience": _split_by_vert,
			"age": _split_by_vert,
			# "salary_upper": int,
			# "salary_lower": int,
			"education_upper": None,
			"education_lower": None,
			"experience_number": _average_experience_number, # 平均工作年限(这个平均指的是有多个工作经验时,如果只有一个,如1~3年,这种目前处理成1年)
			"age_number": _min_age_number, # 最小年龄有意义
		}
		selected_columns = list(selected_columns_and_mapping_function.keys())
		df_metadata = pd.read_csv(self.file_paths["metadata_detail"], sep='\t', header=0, nrows=nrows)
		for column, func in selected_columns_and_mapping_function.items():
			if func is not None:
				df_metadata[column] = df_metadata[column].map(func)
			logging.info(f"{column}字段缺失: {df_metadata[column].isna().sum()}")
		df_metadata = df_metadata[selected_columns]
		return df_metadata

20231219~20231220

  • 昨晚雨停,冷得要死,本来我计划是今天大晴天,下班回去跑强度,但组会时间临时调整,改约AK昨晚下班跑课表(12×600米间歇,间歇为慢走100米+慢跑100米)。AK水平比我高出一个嘉伟,尽管他近两个月没有规律训练。
  • 接近冰点的寒夜,又是刚下完雨,操场只有寥寥几人。计划是跑3’30"/km的配速,顶着寒风,前三组硬跟住AK,600米平均用时2’05",心肺爆炸,大势已去,已生退意。后面掉到2’10"开外,每组跑完话都说不出口。但还是坚持完了12组间歇,最后4组我是数着步子才坚持下来(差不多400步,就一步一步的数,减少痛苦),AK差不多200米就能把我甩开十多米,完全无法跟住,AK到最后都是平均每组2’05"。往年一般我过了元旦就开始摆烂了,状态下滑很多,一直到过完年才重新恢复训练。如果今年能把冬训坚持下来,开春可期。嘉伟和AK明年3月都是可以直通锡马(今年锡马直通条件是全马3小时05分,半马1小时30分),我还需要抽签,当然我也是计划3.24去锡马跑全马,然后4月抽扬马和上半马刷自己的半马PB(我现在绝对有把握跑进1小时30分,但实战变数很大)
  • 今晚下组会外道慢跑5圈热身,力量训练箭步30个×8组(负重20kg,之前觉得15kg很轻松,20也一样),最后陪吴安迪慢跑4km,穿的牛仔裤,纯养生,表都没开。最近我从进公司都出公司能喝到7瓶保温杯水,加上早饭1瓶水,晚上回去2-3瓶水,一天能喝到10-11瓶水,寒冬太难熬,多喝热水舒服些。
    在这里插入图片描述
    在这里插入图片描述

国务院政策网,政策文件爬取脚本:

# -*- coding: utf-8 -*-
# @author: caoyang
# @email: caoyang@163.sufe.edu.cn

import os
import re
import json
import time
import random
import pandas
import requests

from bs4 import BeautifulSoup

from src.base import BaseCrawler

from urllib.parse import urlencode

class GovZhengceCrawler(BaseCrawler):

	host_url = "https://www.gov.cn/zhengce/"

	query_url = "https://sousuo.www.gov.cn/search-gov/data?"

	wrap_regex = re.compile("<br>|</br>|</p>")

	# 20231212:关于目录导航的说明
	# 目录导航分成三个类型:按发布机构分类、按主题分类、政策解读,经过观察,前两个分类的结果大致是相等的,有很小的偏差,但是第二种的返回结果会多个主题分类的值,这很重要
	# 除了政策解读点一下就能出筛选结果(它没有任何下级分类),另外两个都是还有两个级别的子分类,两个级别的子分类都可以点击进行不同级别的筛选

	# 关键在于查询字符串里的t字段,这个比较特殊:
	#   - 如果不做任何分类,即全部,是zhengcelibrary
	#   - 按发布机构分类是zhengcelibrary_gw(国务院文件)或zhengcelibrary_bm(国务院部门文件);
	#   - 按主题分类是zhengcelibrary_gw_bm(因为这里面每一级的筛选结果都可能包含国务院文件或国务院部门文件,所以他们前端这么取名吧)
	#   - 政策解读是zhengcelibrary_or
	# 总结一些各种不同查询的查询字符串组合:t、bmfl、pcodeJiguan、childtype、subchildtype
	#   - 如果什么都不查询,t=zhengcelibrary
	#   - 如果是“按发布机构分类”: 
	#     + lv1: t=zhengcelibrary_gw(国务院文件)、t=zhengcelibrary_bm(国务院部门文件)
	#     + lv2: t=zhengcelibrary_gw&pcodeJiguan=<具体lv2名称>(国务院文件)、t=zhengcelibrary_bm&bmfl=<具体lv2名称>(国务院部门文件)
	#   - 如果是“按主题分类”: 
	#     + lv1: t=zhengcelibrary_gw_bm&childtype=<lv1对应的四位数字代码>
	#     + lv2: t=zhengcelibrary_gw_bm&subchildtype=<lv2对应的四位数代码>
	#   - 如果是“政策解读”: t=zhengcelibrary_or


	# 响应的结果可在 JSON_RESPONSE["searchVO"]["listVO"](按主题分类的结果在JSON_RESPONSE["searchVO"]["catMap"]["gongwen"]["listVO"]或JSON_RESPONSE["searchVO"]["catMap"]["bumen"]["listVO"])中找到(n个字典),每个字典中:
	#   - 我现在发现直接访问两个url就完事了:先把国务院文件全爬下来(找里面["searchVO"]["catMap"]["gongwen"]["listVO"])
	#   - 再把国务院部门文件弄下来,(找里面["searchVO"]["catMap"]["bumenfile"]["listVO"])
	#   - 然后把对应结果的文件中的字典解析成结构化(重点有个分类),需要

	# 然后运行parse_json_file


	# 查询

	# 这个是不做任何筛选,直接刷新页面的默认查询字符串
	query_dict = {'t': "zhengcelibrary",	# t的取值见上文说明
				  'q': str(),				# 查询内容,如果不做查询,置空
				  "timetype": "timeqb",		# 如果不筛选就是timeqb(即“全部”),否则是timeyn(一年内),timeyy(一月内),timeyz(一周内)
				  "mintime": str(),			# 大概是最早的时间筛选条件,在页面上没有这种筛选
				  "maxtime": str(),			# 大概是最晚的时间筛选条件,在页面上没有这种筛选
				  "sort": "score",			# score或pubtime,表明按相关性排序,或是按时间排序
				  "sortType": 1,			# 可能是降序或者升序,页面上没有对应的按钮可以改,通常为1
				  "searchfield": "title",	# title则只搜索标题,空表示搜索全文
				  "pcodeJiguan": str(),		# 发布机构分类->国务院文件->lv2的名称(如“国令”)
				  "childtype": str(),		# 按主题分类->lv1的分类的代码(如1078~1099,如“综合政务”对应的代码是1079,注意如果有其他筛选条件,未必1078~1099都有,比如一年内就没有1091)
				  "subchildtype": str(),	# 按主题分类->lv1分类->lv2的分类的代码(这个要根据lv1的响应JSON来找代码)
				  "tsbq": str(),			# 这个看JS是一些专题性的标题(如中国制造、减税降费、知识产权等,但是现在页面上不显示了),通常为空
				  "pubtimeyear": str(),		# 发布年份,但是现在页面上没有这种筛选
				  "puborg": str(),			# 如“国务院办公厅”,但是现在页面上没有这种筛选
				  "pcodeYear": str(),		# 不知道是啥,在页面上没有这种筛选
				  "pcodeNum": str(),		# 不知道是啥,在页面上没有这种筛选
				  "filetype": str(),		# 不知道是啥
				  'p': 1,					# 分页的页码
				  'n': 5,					# 每页显示的搜索结果数量数量,这个可以取很大,实测取到1500都可以
				  "inpro": str(),			# 不知道是啥
				  "bmfl": str(),			# 如果是国务院部门文件下的lv2分类,则会填充此字段,如“国家卫生健康委员会”
				  "dup": str(),				# 不知道是啥
				  "orpro": str(),			# 不知道是啥
				  "type": "gwyzcwjk",		# 目前都是gwyzcwjk,没看到别的结果
				  }
	
	headers = """Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7
Accept-Encoding: gzip, deflate
Accept-Language: zh-CN,zh;q=0.9,en;q=0.8,en-GB;q=0.7,en-US;q=0.6
Cache-Control: max-age=0
Connection: keep-alive
Cookie: wdcid=1e411507ad5f49f3; wdlast=1702432918; wdses=1021c06f04a1478f
Host: www.gov.cn
Sec-Fetch-Dest: document
Sec-Fetch-Mode: navigate
Sec-Fetch-Site: none
Sec-Fetch-User: ?1
Upgrade-Insecure-Requests: 1
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36 Edg/120.0.0.0
sec-ch-ua: "Not_A Brand";v="8", "Chromium";v="120", "Microsoft Edge";v="120"
sec-ch-ua-mobile: ?0
sec-ch-ua-platform: Windows"""

	
	def __init__(self):
		super(GovZhengceCrawler, self).__init__()

	def demo(self):

		self.parse_json_file()
	
	# 国务院文件和国务院部门文件的完整JSON文件抽取
	def parse_json_file(self,
						gongwen_filepath="国务院文件.json",
						bumen_filepath="国务院部门文件.json",
						):

		res = {"id": list(),
			   "pcode": list(),
			   "title": list(),
			   "pubtimeStr": list(),
			   "summary": list(),
			   "url": list(),
			   "childtype": list(),
			   "puborg": list(),
			   }
		
		gongwen_data = json.load(open(gongwen_filepath, 'r', encoding="utf8"))
		bumen_data = json.load(open(bumen_filepath, 'r', encoding="utf8"))

		gonewen_dicts = gongwen_data["searchVO"]["catMap"]["gongwen"]["listVO"]
		bumen_dicts = bumen_data["searchVO"]["catMap"]["bumenfile"]["listVO"]

		i = 0
		for data in gonewen_dicts + bumen_dicts:
			i += 1
			if i < 84:
				continue
			print(i, data["title"])
			for column in res:
				if column in data:
					res[column].append(data[column])
				else:
					res[column].append(None)
			self.parse_page_content(data)
			interval = 15
			print(f"  - Waiting {interval} seconds")
			time.sleep(interval)


		res["type"] = ["国务院文件"] * len(gonewen_dicts) + ["国务院部门文件"] * len(bumen_dicts)

		for k in res:
			print(k, len(res[k]))
		
		df = pandas.DataFrame(res, columns=list(res.keys()))
		df["ct_lv1"] = df["childtype"].map(lambda x: x.split('\\')[0] if x is not None else None)
		df["ct_lv2"] = df["childtype"].map(lambda x: x.split('\\')[1] if x is not None else None)
		df.to_csv("result.csv", sep='\t', header=True, index=False)
		
		
	# 政策具体内容爬取
	def parse_page_content(self, data):

		url = data["url"]

		print(f"  - URL: {url}")
		
		childtype = data.get('childtype')
		title = data["title"]

		title = title.replace('/', '-')
		
		if childtype is not None:
			fp_dir = os.path.join('政策文件', childtype)

		else:
			fp_dir = '政策文件'
		os.makedirs(fp_dir, exist_ok=True)
		r = requests.get(url, headers=BaseCrawler.headers_to_dict(self.headers))

		html = r.text.encode("iso8859-1").decode("utf8")
		while True:
			soup = BeautifulSoup(html, "html.parser")
			table = soup.find("table", class_="border-table noneBorder pages_content")

			if table is None:
				print("  - 没有Table内容!!!!")
				table = soup.find("div", class_="pages_content mhide")
				if table is None:	
					time.sleep(600)
					continue
			content = self.tag_regex.sub('', self.wrap_regex.sub('\n', str(table)))
			fp = os.path.join(fp_dir, title + '.txt')
			j = 1
			while True:
				if os.path.exists(fp):
					print(f'  - 已经存在{fp}!!!')
					fp = os.path.join(fp_dir, title + f'({j}).txt')
					
				else:
					break
			print(f"  - 写入: {fp}")
			with open(fp, 'x', encoding="utf8") as f:
				f.write(content)
			break

20231221~20231222

  • 昨晚是近期最冷的一个晚上,我刚到操场的时候瑟瑟发抖,穿着大衣兜了5圈才热起来,然后脱了大衣跑了5000米,没有耳罩,耳朵差点冻没了。今天虽然气温更低,但状态不错,直接穿羊毛衫牛仔裤(里面还有秋衣秋裤)在跑了10km,均配4’25",心率159bpm,很是轻松,套了一个不认识的女生5圈,小丫头还挺能跑,虽然套女生圈很常见,但能被我套5圈的可不多,换算下来至少也是5’30"配速跑了20圈以上,又出现了一个没有见过的女性高手,看起来很矮,像是本科生。
  • 冬至日,发饺子吃,为了防止前台那么多饺子被浪费,我晚上下班前一口气干了5盒(一盒5个,应该是袁记云饺,有红油可以浇,香喷喷),下午还吃了一盒,吃到撑,正好晚上回去跑了10km全消化掉,舒服。
  • AK周二陪我干了12×600米间歇之后,回去咳嗽发烧了,他之前感冒刚好,然后回云南给他95岁的外婆做生日,云南那边四季如春,他离沪的时候上海正好刚降温,刚回来估计是没适应这么冷的天,老难受了,搞得我有些内疚。

之前有发现无法创建空的dataframe

另外发现不知为何最新版pandas不接受直接创建空dataFrame了:

import pandas as pd

df = pd.DataFrame(columns=range(5)) # 会报错:object has no attribute dtype

报错原因大概是此时dataframe里的数据都是object类型,没有属性dtype

现在只能手动创建全部都是缺失值的dataframe了

import pandas as pd
import numpy as np
df = pd.DataFrame(np.full((10, 5), np.nan), columns=range(5), index=range(10))

其实可以这样:

df = pd.DataFrame(columns=["name", "age"], dtype=object)

就完事了,一定要带个dtype,肯定是构造函数里没给默认参数,否则会报错

设置整体区块的背景色

import matplotlib.pyplot as plt
import numpy as np

x = np.arange(0, 5, 0.1)
y = np.sin(x)
fig, ax = plt.subplots()
ax.plot(x, y)
ax.set_facecolor('lightgray')  # 将坐标轴背景色设置为灰色
plt.show()

设置柱状图数据标签:

p_1 = plt.bar(x, y, label='value') # 先设置label="value"
plt.bar_label(p_1, label_type='edge', size=20)  # 然后定义bar label的位置
plt.xlabel("营业收入")
plt.ylabel("频数")
plt.title("营业收入选项分布")

20231223

  • 今天-1~3℃,下午万米测试,39’45",成功破四。前6000米宋镇均跟住我,以适应我的配速(他目前要比我强一个档次,周四自测万米已经跑到38分整的水平,他这样造作身体,竟然仍然有如此大的提升,真乃天赋异禀),过6000米时,宋镇均上去帮我顶住配速。从最后的全程配速表来看,速度跑得极稳,而且心率维持得很低(平均心率172bpm),整体跑得很保守的,前5000米用时19’52",第9个1000米稍许觉得有些吃力,示意宋镇均降速缓冲(分段用时4’03"),最后1000米仍有余力冲刺,跑出3’45"的1000米分段。跑完后极其舒适,完全不累,如释重负。
  • 非常完美以及满意的一场万米测试,终于有一天,我也能踏入万米破4的业余精英队列,万米破4是我从刚开始跑步就一直以来的梦想,要知道本科体测1000米最快也没跑进过4分10秒,四年前刚开始跑步的时候,我觉得万米破4是极其遥远的事情,但现在,我真的做到了。
  • 真的很高兴,各种意义上。
    在这里插入图片描述

设置折线图数据标签:

from matplotlib import pyplot as plt  
plt.rcParams['font.sans-serif'] = ['SimHei']  # 用来正常显示中文标签
plt.figure(figsize=(20,8) )
plt.rcParams['axes.unicode_minus'] = False  # 用来正常显示负号
x = ['7月1日', '7月2日', '7月3日', '7月4日','7月5日', '7月6日', '7月7日', '7月8日','7月9日','7月10日', '7月11日', '7月12日', '7月13日', '7月14日','7月15日']  #数据在x轴的位置,是一个可迭代对象
y1 = [3370.48, 3895.69, 5109.03, 4353.68, 3954.21, 5346.81, 5289.11, 8275.16, 5686.16,7503.41, 5360.36, 5758.59, 4962.14, 9401.22, 5543.89]
y2 = [3097.55, 3401.15, 5525.05, 4202.49, 3797., 4972.03, 4941.97, 7440.79, 5561.71, 5888.88, 3776.57, 3990.54, 4114.41, 7612.38, 4539.72]

#数据在y轴的位置,是一个可迭代对象
#plt.plot(x, y, label='利润金额', linewidth=3, color='snow', marker='o',
         #markerfacecolor='blue', markersize=11 )
#for a, b in zip(x, y):
    #plt.text(a, b, b, ha='center', va='bottom', fontsize=18)
plt.title('7月利润变化图')
plt.xlabel('时间')
plt.ylabel('利润 单位(元)')
#plt.legend(prop =my_font)
plt.plot(x,y1)     #传入x和y1,通过plot绘制出折线图
plt.plot(x,y2)
plt.grid()
plt.show()   #展示图形

#plt.savefig("7月数据利润变化.png")

for a, b in zip(x, y1):
    plt.text(a, b+0.05, b, ha='center', va='bottom', fontsize=18)
    #第二个b代表加的是y轴的数据  ,这里的a,b是坐标轴
for c, d in zip(x, y2):
    plt.text(c, d+0.05, d, ha='center', va='bottom', fontsize=18,color='' )

20231224~20231225

  • 天气转暖,昨天已经可以穿风衣出门,下午阳光明媚,陪吴安迪遛了会儿,宋某拉伤,躺床上歇着,陪我破4那天,跟我谈了很多,以前我只知道他是上海人,原来竟是单亲家庭,早年也是多舛,中考在最不擅长的数学上超常发挥,却在擅长的英语上严重失利,以至于高中未能进四校,虽然最后还是考到财大的王牌专业会计,但也很卷不过会院那帮卷王…
  • 平安夜,圣诞节,没有特殊活动,圈里的朋友也很安静,买了10个苹果送人。熟悉的朋友里除了zjc秀恩爱,其他人都很安静。嘉伟很久不露面,我觉得他一定是有心事,一直觉得他跟cml很暧昧,不知道是不是那么一回事儿,前两天嘉伟问我实习事宜,大概也要开始为将来工作作打算。
  • 万米破4后,我也没有计划再进行高强度训练,冬训大致以堆跑量为主,AK大概会陪我到很晚才回老家,至少一月份还是会有个伴一起训练的,最近的比赛是备赛3月24日的锡马,希望能抽中签,以目前万米破4的水平来看,有望全马冲击3小时10分以内,破三仍然任重道远。
  • 周一早上过来公司发了苹果,新疆的冰糖苹果,很大很甜很好吃,这周预计会很忙,学校公司都有汇报要准备,慢慢熬过年底了。
  • 噢,这么快就年底了,可是… 大概会有机会再见吧。

一个完整的demo(包括坐标轴刻度、标签的字号、以及倾斜)

counter = Counter(df["营业收入"].dropna())
revenue_value_mapping_rev = {v: k for k, v in revenue_value_mapping.items()}
x = sorted(list(revenue_value_mapping.values()))
x = list(map(lambda _x: revenue_value_mapping_rev[_x], x))
y = []
for value in x:
    y.append(counter[value])
fig, ax = plt.subplots()
fig.set_size_inches(20, 10)  # 宽度为8英寸,高度为4英寸
p_1 = plt.bar(x, y, label='value')
plt.bar_label(p_1, label_type='edge', size=20)
plt.xlabel("营业收入", size=20)
plt.ylabel("频数", size=20)
plt.title("营业收入选项分布", size=20)

# 设置图形的大小,单位为英寸
for label in ax.get_yticklabels():
    label.set_fontsize(20)
for label in ax.get_xticklabels():
    label.set_fontsize(15)
plt.xticks(rotation=30)
plt.savefig("d:/营业收入.png", transparent=False, bbox_inches='tight', pad_inches=0.0, dpi = 200)
plt.show()
plt.close()

在这里插入图片描述

柱状图的数据标签改百分数

import matplotlib.pyplot as plt
y_data = [ 10, 21, 13, 7, ]
x_data = ('a', 'b', 'c', 'd')
bar = plt.bar(x_data, y_data)
plt.bar_label(bar,fmt='%g%%')

20231226

  • 昨晚熬夜,极度疲劳的一日,而且这周又特别忙。今晚回去只想直接躺床上睡觉了,真的太累了。
  • 很久没有感觉这么疲劳了,最近有半个多月精力保持充沛,每天都能进行高质量的训练,日均活动消耗基本在700卡以上(很想很想万米破4,只差临门一脚,就下定决心认真练),昨晚下班去小姨家补了点好的,回来太晚也没练,活动消耗只有300卡,回宿舍在瑜伽毯上做了十几分钟核心腰腹训练。
  • 今晚陪胡哥跑了会儿,他是100分钟LSD,大概20km左右,我陪了7km左右,然后20kg负重做30×8组的箭步练力量。跟胡哥又是好久不跑,联络一下房友感情。发现胡哥很有我们扬州老爷们儿的那种气质,就是对我dang野史张嘴就来的风范,从八九十年代谈起,给我好好科普了一下,还是挺有趣的。
  • 万米破四意味着半马可以稳进1小时30分,但距离全马进3小时还差点意思。全马破三的人万米水平普遍在38分半以内,而我的万米水平从41分半到成功破开40分用了两年多时间,想要进一步提升,只会更难,但相信总有一天能做到。
  • 没有什么事情是不可能的,但也没必要非得做成每一件事。躺,被一些人烦死,不想理。

LoRA的torch DEMO

import os  
import torch  
from torch import optim, nn  
from PIL import Image  
from torch.utils import data  
from torchvision import models  
from torchvision.transforms import transforms

transform = transforms.Compose([  
    transforms.Resize(256),  
    transforms.CenterCrop(224),  
    transforms.ToTensor(),  
    transforms.Normalize(mean=[0.485, 0.456, 0.406], std=[0.229, 0.224, 0.225]),  
])  
  
  
class LoraDataset(data.Dataset):  
    def __init__(self, data_path="datas"):  
        categories = models.VGG19_Weights.IMAGENET1K_V1.value.meta["categories"]  
        self.files = []  
        self.labels = []  
        for dir in os.listdir(data_path):  
            dirname = os.path.join(data_path, dir)  
            for file in os.listdir(dirname):  
                self.files.append(os.path.join(dirname, file))  
                self.labels.append(categories.index(dir))  
  
    def __getitem__(self, item):  
        image = Image.open(self.files[item]).convert("RGB")  
        label = torch.zeros(1000, dtype=torch.float64)  
        label[self.labels[item]] = 1.  
        return transform(image), label  
  
    def __len__(self):  
        return len(self.files)

class Lora(nn.Module):  
    def __init__(self, m, n, rank=10):  
        super().__init__()  
        self.m = m  
        self.A = nn.Parameter(torch.randn(m, rank))  
        self.B = nn.Parameter(torch.zeros(rank, n))  
  
    def forward(self, inputs):  
        inputs = inputs.view(-1, self.m)  
        return torch.mm(torch.mm(inputs, self.A), self.B)

# 加载底模和lora  
vgg19 = models.vgg19(models.VGG19_Weights.IMAGENET1K_V1)  
for params in vgg19.parameters():  
    params.requires_grad = False  
vgg19.eval()  
lora = Lora(224 * 224 * 3, 1000)  
# 加载数据  
lora_loader = data.DataLoader(LoraDataset(), batch_size=batch_size, shuffle=True)  
# 加载优化器  
optimizer = optim.Adam(lora.parameters(), lr=lr)  
# 定义损失  
loss_fn = nn.CrossEntropyLoss()  
# 训练  
for epoch in range(epochs):  
    for image, label in lora_loader:  
        # 正向传播  
        pred = vgg19(image) + lora(image)  
        loss = loss_fn(pred, label)  
        # 反向传播  
        loss.backward()  
        # 更新参数  
        optimizer.step()  
        optimizer.zero_grad()  
        print(f"loss: {loss.item()}")

pred = vgg19(image) + lora(image)

# 测试  
for image, _ in lora_loader:  
    pred = vgg19(image) + lora(image)  
    idx = torch.argmax(pred, dim=1).item()  
    category = models.VGG19_Weights.IMAGENET1K_V1.value.meta["categories"][idx]  
    print(category)
torch.save(lora.state_dict(), 'lora.pth')

# 转自:https://juejin.cn/post/7280436307914407976

20231227

  • 转暖,下午卢星雨摇人跑步,她从15号降温(14号晚20℃)冬眠到现在,AK重感冒渐愈,约晚上八点一起慢摇。我本来只是惯例性地一个人跑会儿,鞋子裤子都没换。但还是跟他俩一起跑完了10km(均配4’43",稍许累,状态一般),lxy确实很猛,两周没跑还是能以5分配以内把10km跟完,对于女生来说这个水平真心很不错。她之前忙着投稿,也想接下来每天不管多忙都要跑会儿,我一般七点到学校,约了工作日七点半一起,能有搭子多好,人总是不可能永远一个人跑到终点。
  • 昨晚陪胡哥跑的时候,胡哥跟我提到lxy就比你小1岁,可以试试嘛。我说你对lxy太不了解,我跟lxy认识两年多,她可比你在群里看到的样子要猛得多,以前就学习运动全能,本科均绩3.9,乒乓、羽球、篮球、排球样样上手,只不过随着年岁增长,精力变少,现在基本以跑步为主,交际圈非常广泛,但是行事风格感觉并不是特别成熟,在大一大二的小朋友里吃得很开,但就从我认识她开始,她就是一直单身。胡哥说这就没意思,女生太优秀要么找个比她还优秀的,要么就找个主夫,我发现胡哥就是典型的扬州老爷们儿的思维,他的家庭肯定是父亲主导,父亲对他的影响大,比较武断和大男子主义。我是典型母系家庭,父亲大致属于吉祥物,家庭弟位,没有给我灌输过什么理念,我始终认为在没有完全了解一个人之前,对TA的事当持保守和包容的态度。
  • AK跑完还是咳得厉害,说上周晚上咳得睡不着,周末那么好的天气在床上度过,一个人在外面生病确实很难受,去年这个时候我也是新冠发烧,今年运气好,在感冒的边缘游荡了很久,也没有成功感冒,每天十杯水+水果,再怎么造作也扛得过来。AK、嘉伟、我、宋某、安迪都已报名明年3月24日的无锡马拉松(如果抽中,锡马将成为我的首马赛场,我的及格线是进330,320是良好,310是优秀,300虽然不太可能,但如果进了就是满分答卷)
  • 宋某小腿拉伤,目前处于休眠期。年关将至,人越来越少,有多少能一起走到最后呢?一人,足矣。

P-tuning v2:https://arxiv.org/pdf/2110.07602.pdf

相对来说,现在还是Lora更主流一些,v2版本的Ptuning需要在每个网络层上都加入soft prompt张量微调,有点费事,本质上微调属于迁移学习的范畴,其实这里面是有很多学问探讨的,很多方法并不是拍脑子的结果。

from torch import nn
def transpose_for_scores(x: torch.Tensor) -> torch.Tensor:
    new_x_shape = x.size()[:-1] + (12, 64)
    x = x.view(new_x_shape)
    return x.permute(0, 2, 1, 3)

prompt = torch.rand(32,128,48,12,64) # batch_size, seq_len, num_layer*2, num_head, head_size
prompt = prompt.permute([2,0,3,1,4])
print(f"P-tuningV2构造的trainable continuous embeddings形状:{prompt.shape}")
past_key_values = prompt.split(2)
num_layers = 24
hidden_dim = 768
n_head = 12
head_dim = hidden_dim // n_head
all_head_size = n_head * head_dim
hidden_states = torch.randn(32,128,768) # batch_size, seq_len, hidden_size
print(f"输入的向量形状:{hidden_states.shape}")
for i in range(num_layers):
    past_key_value = past_key_values[i]
    print(f"每一层BertLayer需要加入的prompt形状: {past_key_value.shape}")
    self_attn_past_key_value = past_key_value[:2] if past_key_value is not None else None
    # BertSelfAttention
    query = nn.Linear(hidden_dim, all_head_size)
    key = nn.Linear(hidden_dim, all_head_size)
    value = nn.Linear(hidden_dim, all_head_size)

    key_layer = transpose_for_scores(key(hidden_states))
    print(f"经过transpose_for_scores后的key形状:{key_layer.shape}")
    value_layer = transpose_for_scores(value(hidden_states))
    print(f"经过transpose_for_scores后的value形状:{value_layer.shape}")
    key_layer = torch.cat([past_key_value[0], key_layer], dim=2)
    print(f"past_key_value[0]的形状:{past_key_value[0].shape} key_layer的形状:{key_layer.shape} 经过cat后的key_layer形状:{key_layer.shape}")
    value_layer = torch.cat([past_key_value[1], value_layer], dim=2)
    print(f"past_key_value[1]的形状:{past_key_value[1].shape} value_layer的形状:{value_layer.shape} 经过cat后的value_layer形状:{value_layer.shape}")

    mixed_query_layer = query(hidden_states)
    print(f"hidden_states经过query层后输出的形状:{mixed_query_layer.size()}") #batch seq len embed
    query_layer = transpose_for_scores(mixed_query_layer)
    print(f"经过transpose_for_scores后的query形状{query_layer.size()}") #batch

    print("注意力分数开始计算")
    attention_scores = torch.matmul(query_layer, key_layer.transpose(-1, -2))
    print(f"attention_scores的形状:{attention_scores.size()}") #batch head seq_len seq_len
    print("开始注意力汇聚计算")
    context_layer = torch.matmul(attention_scores, value_layer)
    print(f"注意力汇聚后输出矩阵context_layer的形状:{context_layer.size()}") #batch head seq_len embed/12
    print("最后,将context_layer的形状恢复成输入hidden_states的形状")
    context_layer = context_layer.permute(0, 2, 1, 3).contiguous()
    new_context_layer_shape = context_layer.size()[:-2] + (768,)
    context_layer = context_layer.view(new_context_layer_shape)
    print(f"context_layer的形状恢复完成,其形状为:{context_layer.size()}")
    print("一次P-tuningV2的BertLayer计算仿真结束")
    break

转自:https://blog.csdn.net/as949179700/article/details/130900814

20231228

  • 晚上胡哥一个人跑25km,一问才知道明天就要去苏州找对象,没时间跑步,年底跑量凑个整。
  • 我下班按约定陪卢星雨跑10km,AK团建烤肉来不了(他已经要开始圆滚滚),跟昨天差不多的速度,她确实游刃有余,8k之后可以从容地聊天。前天陪胡哥跑的时候他说这场上就没看到过好看的妹子,今天立即改口说lxy头发放下来还挺好看,求生欲极强。
  • AK其实比我要大2岁多,长跑世家,他嫂子和贾俄仁加(中国现役马拉松Top10)的未婚妻居然是同学,前一阵子他回老家,贾俄仁加恰好订婚还跟他合了照,重点是他俩长得还挺像,看照片我以为是AK要结婚了。四舍五入一下我们也算是跟中国最顶尖的一批马拉松选手有五毛钱关系了哈哈。
    在这里插入图片描述

pandas.Series.map方法有一个na_action=None的参数,设置为ignore可以自动忽略np.nan所在的行,不仅可以提速,也可以便于处理非数值(但有缺失的)其他列,比如一个字符串列,想转为整型,如果有缺失,直接map(int)会出错,不得不写成map(lambda x: int(x) if x == x else x),又臭又长又慢,可以写成map(int, na_action="ignore")即可。

df.applybroadcast参数,布尔型的可选参数。当为NoneFalse时,会返回一个与原dataframecolumnindex长度相同的Series。 当为True时,会返回一个indexcolumn跟原dataframe相同的dataframe

还有一个很重要的参数raw: 布尔型。默认为False。当为False时,会将dataframe中的每一个column或每一个index上以Series的形式传到函数中(这也是常规的做法,此时逻辑是循环,通常很慢)。当为True时,则会把数据以np.array的形式传到函数中,如果这个时候func用的就是numpy的函数,则执行效率会非常高。

在0.23版本以后,这个参数会被替换成 result_type=‘broadcast’,可选值包括以下几种:expand、reduce、broardcast、None。确定返回形式。(0.23.0以后版本才有并且只有当axis=1时这个参数才能发挥作用)

此外,apply支持位置参数agrs,和关键词参数**kwds

另外,假定X是dataframe的一列,比如每行的数值是一个列表或者np.darray,可以使用np.array(X)是无法将它变成一个矩阵的,得用np.stack或者np.vstack才行。


20231229~20231230(完篇)

很久不看番,以前我是那样的喜欢补番,最近却很难看得下去,觉得幼稚。但是晚上回来找了一部很老的《这个美术社大有问题》重刷了一遍。

昨晚卢星雨依然坚持跑步,这次她没那么好运,扛了6k,说喉咙疼痛,怕真是要感冒,今天一天都没冒泡。女生何必每天坚持10km呢,少跑一天又不会死。一个个跨年还要全勤,问就是跨年不跑步还能做啥,无力反驳。

今天下午按计划约AK进行跨年跑20.24km,跟AK说好按4’10"跑,结果前2000米AK带了不到8分钟,而我的心率只有不到160,并不吃力,错以为自己能用4分配跑完20km,10km用时41’55",虽然掉了一些,但依然在预期内,没有太吃力,心率也很低。15km之后彻底跑崩,大腿跟灌铅似的抬不动,配速逐渐掉出4’30",但好歹还是一口气跑完20.24km,均配4’19",用时1小时27分钟(AK最后是1小时24分跑完,均配4’08"),差强人意(毕竟雨后,天气不好,可以接受)。我本以为应该可以轻松跑完半马的,事不遂人,明年如果要跑全马,依然任重道远。

在这里插入图片描述

跑完回去洗了个澡,就赶去跟颜烨、王凯聚餐。很久不见,先交流一下MBTI测试结果,颜烨是ESTJ(执政官),王凯是INFJ(精神引路人),颜烨E人无疑,王凯偏爱哲学书籍。之前在宝玺姐的撺掇下也去测了MBTI,我是ISFJ(守护者)。我也认可测试结果。

已是年底,虽然很忙,但我们仨每年至少还是要聚一两次,见老朋友总是很高兴。颜烨选了芳甸路天物空间里的醋糟粕火锅,味道还不错,不是很辣(包括辣锅),汤底应该是高汤然后加了醋,很香可以直接喝。

其实很高兴跟他俩吃饭,最近有些心事,说了不少,以前失恋也是颜烨给我开导。后来八点多胡哥给我发消息说发烧,让我帮拿点药,才急匆匆赶回去。

年底怎能无心事,人总是如此,渴望且惧怕着些事,下半年我的状态达到前所未有的巅峰(我今年总跑量只有1011km,平均每月仅84km,但12月份跑量接近200km,并成功跑出39’45"的万米PB)。得益于一天10杯水+水果,下班回来坚持跑步,加上力量训练。上班没有让我疲惫不堪,反而使我的生活更加规律,让我看到了生活的希冀。这半年接触的人越来越多,明显感觉到自己也开朗了许多,我很高兴,却变得愈加谨慎。在认识lxy的两年多里,我们只是一起训练(这半年我因为工作就很少去例训),并无其他交集,我很清楚她一直单身,有意无意,这么久除了训练都是下意识地在躲避,即便会在场上偶遇,不想产生误会。但最近也是感觉她跟以前的态度不一样,让我有想迈出那一步的错觉。

其实一个人单身很久不需要什么理由,我也单了快3年,难道不快乐吗,只用管好自己,自由自在,无忧无虑,何必贪染爱情这种毒品?但世事就是这般滑稽,不经历过爱情的荼毒,又如何在婚姻的坟墓里熬过余生?有些事情不论你是否愿意,你只能被动去接受,囚徒困境,等待其实不太可能带来什么改变,总是需要一方去承担扯破蒙布的风险。

我不知道,我真的不知道。但是,我还是很想知道…

有些难受,却又很释然,因为这就是人生,我的人生,与其他人都不一样的人生。

花絮:本来以为这次轮到我买单了,去的路上在想着要放开大吃一通(本来我每周六上完大强度,都是要出去大吃一顿的,说实话,不如我去蜀地源,不到60元就能吃到撑,肉也不少),我来晚了,他们让我再点两个菜,我就挑了贵的点,结果凯爹说他刚转正,庆祝一下就他来买单,就没再多点坑凯爹了哈哈。


今年就更到这里了,什么?还有12月31日,谁知道呢?反正我的2023年结束了。

(END)

  • 3
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 3
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值