【Share 2021】caoyang.log(END)

序言

Welcome Back

上一篇博客囚生CYのPOST于2020年12月15日停更,原因是笔者担心再更新就永远过不了审了,CSDN审核现在对内容要求非常高,一定要求是与技术相关的,动辄给博客打上meaningless的标签,笔者心想年关将至,趁早收笔还能保住博客,免得再自找不痛快。

本来笔者在2020年度征文机器学习该怎么学活动上,趁有机会可以畅所欲言时,写点跟技术无关的内容就写些无趣的琐事,但是后来突然兴起决定就机器学习这个方向的学习写一些个人的想法与思考,毕竟也摸爬滚打几年,没吃过猪到底也看过猪跑了,一口气写完没收住笔,很爽快,却偏离了初衷。

停更后笔者是再也不想写这种博客,但是发现半个月不写实在是耐不住寂寞,而且有时候真的无处倾诉时写下来也是一种解脱,于是笔者趁跨年之际重开一篇博客。为了响应CSDN的号召,决定在记录日常的同时,分享一些与IT相关小知识,不管怎么说计算机这块的知识浩如烟海,而且很难理清层次,很多知识甚至在用途上是重复的,而我们却不得不面面俱到,样样精通,不积跬步无以至千里,也许这就是时代赋予我们这些打工人的命运。

决定重启这项工程的日期是2020年12月29日,冬日的上海下了第一场雪,对于笔者来说也有一场意外的邂逅,虽有些许徒劳,但对笔者来说偶尔能见到一次也便是小确幸。然而此后的机会就很渺茫了,几乎是没有任何交集,当此为新伊始也是一个极好的决定。

2020年虽然没有2019年那样令笔者满意,但是平淡地结束未免也不是件坏事,2019年就是年末时想得太多,致使现在徒增无谓的烦恼。

最后说明本文的结构,本文将于2021年1月1日开始更新,为期一年。与囚生CYのPOST的倒序排版方式不同,本文将采用正序排版,并通过内置目录的方式来弥补正序所带来的阅读不便。另外本文将采用markdown编辑,而非富文本编辑。另外希望CSDN能尽快推出类似博客园的代码折叠功能,否则排版出来的数百行代码在快速阅读时比较影响阅读体验。

不思八九,常想一二,序言的末尾将附上一段跨年Python小脚本,作为新年伊始的祷祝(试着运行一下,有小彩蛋哦~)。

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

import os
import time
import random
import datetime

SLEEP_INTERVAL = 1

def date_generator(start_date, end_date):
	today_date = start_date
	while today_date <= end_date:
		yield today_date
		today_date += datetime.timedelta(days=1)

class Year2020:
	
	def __init__(self):
		self.start_date = datetime.datetime.strptime('2020-01-01', '%Y-%m-%d')
		self.end_date = datetime.datetime.strptime('2020-12-31', '%Y-%m-%d')
		self.diary = []
		self.months = ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec']
		index = -1
		for today_date in date_generator(self.start_date, self.end_date):
			if today_date.day==1: 
				index += 1
				print('---- {} 2020 ----'.format(self.months[index]))
				time.sleep(SLEEP_INTERVAL)
			print(today_date.strftime('%Y/%m/%d'), self.keep_diary())

	def keep_diary(self):
		if random.random() < 0.1:
			content = b'\xe2\x98\xba'.decode('utf8') + ' Today is a happy day. I do ...' 
		else:
			content = b'\xe2\x98\xb9'.decode('utf8') + ' Today is a sad day. I do ...'
		self.diary.append(content)
		return content

	def happy_end(self):
		os.system('start https://caoyang.blog.csdn.net/article/details/111937367')
		
	def bad_end(self):
		print('This is a bad end, you can change it yourself.')

class Year2021(Year2020):
	
	def __init__(self):
		Year2020.__init__(self)
		self.rubbish_bin = {}
		self.memory_box = {}
		counter_sad = 0
		counter_happy = 0
		for date, content in zip(date_generator(self.start_date, self.end_date), self.diary):
			if content.find('sad') >= 0: 
				self.rubbish_bin[date] = content + ' Let it go away ' + b'\xe2\x98\xba'.decode('utf8') + ' .'
				counter_sad += 1
			elif content.find('happy') >= 0:
				self.memory_box[date] = content + ' Treasure it forever ' + b'\xe2\x98\xba'.decode('utf8') + ' .'
				counter_happy += 1
			else:
				assert False
		time.sleep(SLEEP_INTERVAL + 1)
		print('\n'*2)
		print('-'*32)
		print('#'*32)
		print('#'*9 + ' Summary 2020 ' + '#'*9)
		print('#'*32)
		print('You have totally {} sad days.'.format(counter_sad)) 
		print('You have totally {} happy days'.format(counter_happy))
		print('Oh, What a terrible year is !')
		print('-'*32)
		print('\n'*2)
		time.sleep(SLEEP_INTERVAL + 1)
		print('But you have treasured so many memory:')
		time.sleep(SLEEP_INTERVAL + 1)
		for date, memory in self.memory_box.items():
			print(date.strftime('%Y/%m/%d'), memory)
		print('\n'*2)
		time.sleep(SLEEP_INTERVAL + 1)
		print('Now everything has restarted in 2021: ')
		self.diary = []
		self.start_date = datetime.datetime.strptime('2021-01-01', '%Y-%m-%d')
		self.end_date = datetime.datetime.strptime('2021-12-31', '%Y-%m-%d')
		time.sleep(SLEEP_INTERVAL + 1)
		input('  - Are you ready ? (按回车键即可)')
		time.sleep(SLEEP_INTERVAL + 1)
		if random.random() < 0:
			self.bad_end()
		else:
			self.happy_end()
		print('\n'*2)
		print('HAPPY NEW YEAR !')
		print('\n'*2)
		content = input('Write something for {} : '.format(datetime.datetime.today().strftime('%Y/%m/%d')))
		self.diary.append(content)

if __name__ == '__main__':
	new_year = Year2021()


2021年1月

01-01

  • 零下五度完成15km跑,用时71’08’’,平均配速4’44’’,其中4km与8km处分别补给了一圈豆奶。我发现只有在极端寒冷的条件下体力消耗才是最少的,之前的两次雨夜12km也是这样跑出来的,这次如果不是10km之后感觉手冷肚子饿,导致选择提速跑到力竭,自信能把4’48’'的配速坚持到20km。
  • 倘若到今年12月份能保持住状态且不受伤(事实上这次又把右膝跑伤了),就去报名跑一次上马的半马比赛。
  • 标准误差: 指在给定样本: X = { x 1 , x 2 , . . . , x n } \bm{X}=\{x_1,x_2,...,x_n\} X={x1,x2,...,xn}的条件下, 样本统计量 T ( X ) T(\bm{X}) T(X)的标准差;
    • 提供统计推断的一组样本数据, 同样可以用来评估该推断结果的精确性;
  • 如样本均值 T ( X ) = x ˉ T(\bm{X})=\bar x T(X)=xˉ, 其标准误差的估计值 s e ^ \widehat{\rm se} se 计算公式为: [ ∑ i = 1 n 1 n 2 V a r ( x i ) ] 1 2 = [ n ⋅ 1 n 2 ⋅ ( x i − x ˉ ) 2 ( n − 1 ) ] 1 2 = [ ∑ i = 1 n ( x i − x ˉ ) 2 n ( n − 1 ) ] 1 2 \left[\sum_{i=1}^n\frac{1}{n^2}{\rm Var(x_i)}\right]^{\frac{1}{2}}=\left[n\cdot\frac{1}{n^2}\cdot\frac{(x_i-\bar x)^2}{(n-1)}\right]^{\frac{1}{2}}=\left[\sum_{i=1}^n\frac{(x_i-\bar x)^2}{n(n-1)}\right]^{\frac{1}{2}} [i=1nn21Var(xi)]21=[nn21(n1)(xixˉ)2]21=[i=1nn(n1)(xixˉ)2]21
  • 最小二乘法中参数 β ^ 0 {\hat\beta}_0 β^0 β ^ 1 {\hat \beta}_1 β^1的标准误差分别为: s e ( β ^ 0 ) = σ ^ 2 ( 1 n + x ˉ 2 S X X ) s e ( β ^ 1 ) = σ ^ 2 S X X {\rm se}({\hat\beta}_0)={\hat\sigma}^2\left(\frac{1}{n}+\frac{\bar x^2}{\rm SXX}\right)\\{\rm se}({\hat\beta}_1)=\frac{{\hat\sigma}^2}{\rm SXX} se(β^0)=σ^2(n1+SXXxˉ2)se(β^1)=SXXσ^2其中( σ ^ 2 {\hat\sigma}^2 σ^2是最小二乘方差的无偏估计): σ ^ 2 = 1 n − 2 ∑ i = 1 n ( y i − β ^ 0 − β ^ 1 x i ) 2 S X X = ∑ i = 1 n ( x i − x ˉ ) 2 = ∑ i = 1 n x i ( x i − x ˉ ) {\hat\sigma}^2=\frac{1}{n-2}\sum_{i=1}^n(y_i-{\hat\beta}_0-{\hat \beta}_1x_i)^2\\{\rm SXX}=\sum_{i=1}^n(x_i-\bar x)^2=\sum_{i=1}^nx_i(x_i-\bar x) σ^2=n21i=1n(yiβ^0β^1xi)2SXX=i=1n(xixˉ)2=i=1nxi(xixˉ)

01-02

  • 投石问路,石沉深海。唉… 新年手贱发了一封邮件,果然是多此一举,罢了。
  • 今天恢复了后程提速可以用4’15’'以内的配速完成最后2km的冲刺了。受过几次伤之后慢慢就知道怎么跑是适合的,可惜右膝的伤怕是成了隐疾,昨天又疼一天。
  • 假定一组独立同分布样本: x i ∼ N ( θ , 1 ) i = 1 , 2 , . . . , n (4.34) x_i\sim\mathcal{N}(\theta,1)\quad i=1,2,...,n\tag{4.34} xiN(θ,1)i=1,2,...,n(4.34) θ \theta θ的估计量 θ ^ = x ˉ \hat\theta=\bar x θ^=xˉ, 观察员通过抛硬币的方式来决定究竟进行多少次采样: n = 25 p r o b a b i l i t y   1 2 n = 100 p r o b a b i l i t y   1 2 (4.35) n=25\quad{\rm probability}\space\frac{1}{2}\\n=100\quad{\rm probability}\space\frac{1}{2}\tag{4.35} n=25probability 21n=100probability 21(4.35)最终采样结果是 n = 25 n=25 n=25, 那么问题来了, θ ^ = x ˉ \hat\theta=\bar x θ^=xˉ的标准差是多少?
    • 如果是 1 / 25 = 0.2 1/\sqrt{25}=0.2 1/25 =0.2, 那么费雪就会赞成你, 这是一个条件推断;
    • 如果是 [ ( 0.01 + 0.04 ) / 2 ] 1 / 2 = 0.158 [(0.01+0.04)/2]^{1/2}=0.158 [(0.01+0.04)/2]1/2=0.158, 这就是非条件的频率学派做出的回答;

01-03

  • 考虑到明天会下雨, 于是选择今天再进行一次长跑,但是下午风大,而且出了太阳,气温回升,25圈就完全力竭了,平均配速4’45"。可能还是起步太急,15km那次起手才5’15"(今天是4’55"),还是穿着厚大衣跑的,最后还跑回了4’44"的平均配速。总之离能稳定跑半马还有很长路要走的。
  • 结果前天的发的邮件最终还是收到回复了… SXY一直在实习,似乎并没有读博的打算。总是试图去忘记,却无奈地发现自己始终只是在躲避,何等荒唐
  • 逻辑回归(logistic regression)是一种专门用于频数或频率型数据的回归分析方法; 其中的logit参数 λ \lambda λ定义为: λ = log ⁡ { π 1 − π } (8.4) \lambda=\log\left\{\frac{\pi}{1-\pi}\right\}\tag{8.4} λ=log{1ππ}(8.4)其中 π \pi π为计数事件发生的频率, 即二项分布 B i ( n , p ) Bi(n,p) Bi(n,p)中的概率参数 p p p; 当 π \pi π 0 0 0变化到 1 1 1时, logit参数 λ \lambda λ − ∞ -\infty 变化到 + ∞ +\infty +;
  • 究其本质, 逻辑回归就是假设应变量 π \pi π与自变量 x x x间的关系并非线性, 因此构造 λ \lambda λ使得 λ \lambda λ x x x之间的存在线性关系, 然后通过拟合 λ \lambda λ x x x, 再根据 λ \lambda λ π \pi π之间的关系来解决 π \pi π x x x之间的回归方程;
  • 从结果上来看, 逻辑回归的目标函数是最小化Kullback–Leibler距离; 因此也是可以与Lasso回归等添加正则项的方法相结合; 本质上逻辑回归中的训练参数与线性回归中的训练参数是一致的, 正则项可以设为待定参数的某种模数;
  • 逻辑回归是广义线性模型的一种特殊情况;
  • 逻辑回归中的link function是: g ( y ) = e y e y + 1 g(y)=\frac{e^y}{e^y+1} g(y)=ey+1ey
    • link function的反函数称为logit function: g − 1 ( θ ) = log ⁡ { θ 1 − θ } g^{-1}(\theta)=\log\left\{\frac{\theta}{1-\theta}\right\} g1(θ)=log{1θθ}其中 θ 1 − θ \frac{\theta}{1-\theta} 1θθ称为odds;
  • 估计参数 θ \theta θ的表达式可以写作: θ ( X ) = g ( X β ) = e X β 1 + e X β = 1 1 + e − X β \theta(\bm{X})=g(\bm{X}\beta)=\frac{e^{\bm{X}\beta}}{1+e^{\bm{X}\beta}}=\frac{1}{1+e^{-\bm{X}\beta}} θ(X)=g(Xβ)=1+eXβeXβ=1+eXβ1

01-04

  • 王英林给我找了个离谱的合作课题,鼎捷软件那边要做一个APP的个性化UI设计,居然想用机器学习来学习各种不同的UI来做智能化的UI排版。我细想了一会儿感觉这活实在是太扯了,不过还是等周三周四去公司实地考察一下再说。
  • 不管怎么样,有活干总是件好事,等王明这辈博士学长毕业,下一个门面担当舍我其谁,始终还想在学生时代再狂几年。
  • 右脚跟腱似乎出了些问题,这两个月右下肢从大腿根到脚底都废了一遍,不过晚上还是冒雨10圈加速跑,感觉状态不太好,要休整一两日回口血了。
  • The plug-in principle:
    • 用原模型分布产生的数据分布来估算模型本身的分布, 然后将预估出来的参数插入到原分布中以做出最优预测;
    • 如可以将分布 F F F下的样本均值 X ˉ = ∑ X i n \bar X=\frac{\sum X_i}{n} Xˉ=nXi的标准误差与 v a r F ( X ) {\rm var}_F(X) varF(X)联系起来: s e ( X ˉ ) = [ v a r F ( X ) n ] 1 2 (2.8) {\rm se}(\bar X)=\left[\frac{{\rm var}_F(X)}{n}\right]^{\frac{1}{2}}\tag{2.8} se(Xˉ)=[nvarF(X)]21(2.8)我们只有观测到的样本数据 x = ( x 1 , x 2 , . . . , x n ) \bm{x}=(x_1,x_2,...,x_n) x=(x1,x2,...,xn), 于是只能进行估计: v a r ^ F = ∑ ( x i − x ˉ ) 2 n − 1 (2.9) {\widehat {\rm var}}_F=\sum\frac{(x_i-\bar x)^2}{n-1}\tag{2.9} var F=n1(xixˉ)2(2.9)Formula 2.9嵌入到Formula 2.8中就可以得到Formula 1.2中的估计值 s e ^ \widehat{\rm se} se ;
    • 拓展:
      • ① 如 θ = E ( X ) \theta=E(X) θ=E(X)的估计量为 ∑ i = 1 n X i n \frac{\sum_{i=1}^nX_i}{n} ni=1nXi, 通过R-S Integral可以知道 θ = E ( X 2 ) \theta=E(X^2) θ=E(X2)的估计量为 ∑ i = 1 n X i 2 n \frac{\sum_{i=1}^nX_i^2}{n} ni=1nXi2
      • ② 关于 v a r ( X ˉ ) {\rm var}(\bar X) var(Xˉ)的估计值, 可以使用 v a r ( X ˉ ) = σ 2 n = 1 n { ∫ x 2 d F ( x ) − [ x d F ( x ) ] 2 } {\rm var}(\bar X)=\frac{\sigma^2}{n}=\frac{1}{n}\left\{\int x^2{\rm d}F(x)-\left[x{\rm d}F(x)\right]^2\right\} var(Xˉ)=nσ2=n1{x2dF(x)[xdF(x)]2}, 则可以得到: v a r ^ ( X ˉ ) = 1 n { ∫ x 2 d F n ( x ) − [ x d F n ( x ) ] 2 } \widehat {\rm var}(\bar X)=\frac{1}{n}\left\{\int x^2{\rm d}F_n(x)-\left[x{\rm d}F_n(x)\right]^2\right\} var (Xˉ)=n1{x2dFn(x)[xdFn(x)]2}

01-05

  • 连肝五天,终于把这学期的统计课从头到尾细细过了一遍,【学习笔记】计算机时代的统计推断可能真的只能挑几个章节写一下了,我发现看李卫明的Slide到Chapter11和12讲到bootstrap还有交叉验证的时候完全已经看不懂里面的推导,结论也很难记住,大后天考到只能认栽。
  • 我计划是把前12章每个章节做一下重点汇总,主要还是应付考试。后面主要是17-19这三个章节可能会详细写一下,因为确实是很流行的三种方法(Random ForestsNeural NetworksSVMs),也很想细致得了解一下里面的统计原理,其他几个章节只能随缘,实在是太难了。
  • 右脚跟腱好像真的有点隐疼,但是跑起来也感觉不到疼痛,晚上前五圈很谨慎地以5分以上的配速跑,后五圈把平均配速跑回4’36",结果一点疼痛感也没有,这跟腱就很迷,不知道是怎么回事。
  • 这学期结束,转回管科的话,软工这儿的人以后也难碰面了… 人和事都无疾而终,大约还是要重新开始。
  1. 神经网络概述:
  • 20世纪80年代, 神经网络的提出震惊了应用统计学界;
  • 神经网络是一种高度参数化的(highly parametrized)模型;
  • Figuer 18.1中是最简单的前馈神经网络示意图:
    • 其中模型输入包含4个预测因子(predictors), 隐层节点共计5个, 最终输出为单个标量值; 具体计算公式如下:
      • 模型输入张量: ( x 1 , x 2 , x 3 , x 4 ) (x_1,x_2,x_3,x_4) (x1,x2,x3,x4)
      • 计算隐层状态: a l = g ( w l 0 ( 1 ) + ∑ j = 1 4 w l j ( 1 ) x j ) a_l=g(w_{l0}^{(1)}+\sum_{j=1}^4w_{lj}^{(1)}x_j) al=g(wl0(1)+j=14wlj(1)xj)
      • 计算输出标量: o = h ( w 0 ( 2 ) + ∑ l = 1 5 w l ( 2 ) a l ) o=h(w_0^{(2)}+\sum_{l=1}^5w_l^{(2)}a_l) o=h(w0(2)+l=15wl(2)al)
    • 上述公式中:
      • g g g h h h为激活函数, 通常为非线性函数; 常见的激活函数有Sigmoid, ReLU, Softmax;
      • a l a_l al称为神经元(neurons); 神经元从数据中学习新特征的过程称为监督学习(supervised learning);
      • 每个神经元通过训练参数 { w l j ( 1 ) } 1 p \{w_{lj}^{(1)}\}_1^p {wlj(1)}1p相互联系, 其中上标 ( 1 ) (1) (1)表示这是第 1 1 1层, 下标 l j lj lj表示这是第 j j j个变量的第 l l l个训练参数; 特别地, 截距项 w l 0 ( 1 ) w_{l0}^{(1)} wl0(1)称偏差(bias);
  • 本质上神经网络就是一种非线性模型, 与其他线性模型的推广(广义线性模型等)并没有什么区别, 但是它确实给学界注入新鲜能量;
  • 20世纪90年代, 随着boosting方法(Chapter 17)与支持向量机(Chapter 19)的兴起, 神经网络由于其解释性的缺乏逐渐没落;
  • 2010年之后, 神经网络又突然以深度学习(deep learning)的身份重生, 再次制霸各类分类预测领域, 如图像与音像数据分类, 自然语言处理等;

01-06

  • 考前焦虑,什么事情也做不成,颓废了一天。
  • 前天刷完了《小魔女学园》,一部非常好的番剧,很久不补番,这部还是半年前开始看的,但是确实是很出乎意料的精彩。好想就这样无忧无虑地补补番唉。
  • 各种奇怪的markdown公式括号写法:
    Figure

01-07

  • 昨晚到今早花了五六个小时终于把大一C++课的期末考卷改完,惨不忍睹,除了一个96分,两个85分,其他都在80以下,一半的人不及格,就看最后怎么调分了。我真的是一肚子火,学了半年,好多人连最基本的条件和循环都分不清,平时作业各种雷同,还都是计科班的,这届新生质量真的很差。
  • 下午去鼎捷调研,来回骑车10公里,大顶风不谈,关键是真的冻死了,在上海待了五年,第一次冬天这么冷,废了。不过接下来要搞智能用户界面,这不是一件容易的事情,如果只有我们硕士做可能问题会比较大。
  1. 费雪学派的方法论因为过度依赖正态样本的假设而被批判;
  • 以之前提到的白血病案例, 47个 A L L \rm ALL ALL与25个 A M L \rm AML AML患者(Figure 1.1), 两样本 t t t检验的结果为 t t t值3.13, 双侧显著水平为 0.0025 0.0025 0.0025, 这些都是基于高斯或者正态的假设;
  • 费雪提出使用置换方法处理这72个患者样本, 即每次随机分为47个和25个的不相交集合, 然后重复 B B B t t t值计算, 得到了 B B B个不同的 t t t值序列 t 1 ∗ , t 2 ∗ , . . . , t B ∗ t_1^*,t_2^*,...,t_B^* t1,t2,...,tB, 则双侧置换显著水平就是: c o u n t ( { ∣ t i ∗ ∣ ≥ t } ) B \frac{{\rm count}(\{|t_i^*|\ge t\})}{B} Bcount({tit})
    • 即是多次实验取均值, 这样得出的结果是 t t t值3.13, 置换显著水平为 0.0022 0.0022 0.0022
  1. 费雪对置换显著水平的可信度做出解释:
  • 假设我们零下架是72个独立同分布的样本: x i ∼ f μ ( x ) i = 1 , 2 , . . . , n x_i\sim f_\mu(x)\quad i=1,2,...,n xifμ(x)i=1,2,...,n这里没有正态假设, 但是我们还是默认 f μ ( x ) f_\mu(x) fμ(x)就是 N ( θ , σ 2 ) \mathcal{N}(\theta,\sigma^2) N(θ,σ2);
  • 假设 o \bm{o} o是观测样本集合 x \bm{x} x的次序统计量, 不妨设是从小到大排列的结果, 然后去除了各自的 A L L \rm ALL ALL A M L \rm AML AML标签; 那么就有 72 ! / ( 47 ! 25 ! ) 72!/(47!25!) 72!/(47!25!)中方法通过划分 o \bm{o} o成为不相交的子集(子集元素个数分别为47与25个), 来获得 x \bm{x} x, 且这些概率是相同的;

01-08

  • 统计考完,感觉写得还是比较顺的,结果应该不会太辣眼睛。
  • 恢复常态,这几天因为考试确实是颓废得很。重新开始从4km跑,不过确实是冷,阳台水池和拖把都冻住了,风也很大,不过按照之前得经验,明天会是个长跑的好天气,今晚养精蓄锐。
  • 这样一来就彻底结束了,接下来真的是和软工再无牵扯了,实话说自己还是有些遗憾的,多少还是认识了几个人,这么快就又要断了。
  1. 多进程模块: class multiprocessing.Process(group=None, target=None, name=None, args=(), kwargs={}, *, daemon=None):
  • 实例化对象: process = Process(target=f, args=(1, ));
  • 对象方法:
    • process.run(): 直接运行该进程, 即执行f(1);
    • process.start(): 进程准备就绪, 等待调度;
    • process.terminate(): 终止进程(发出SIGTERM信号);
    • process.kill(): 杀死进程(发出SIGKILL信号);
    • process.close(): 关闭进程;
      • 若该进程的当前进程存在处于运行状态的子进程时, 调用则会报错;
    • process.join(timeout=None):
      • timeoutNone, 则该方法将阻塞, 直到join()调用其方法的进程终止;
      • timeout是一个正数, 它最多会阻塞timeout秒;
      • 若方法的进程终止或方法超时, 则返回该方法;
      • 可以检查进程的process.exitcode以确定它是否终止;
      • 所谓阻塞, 就是需要等方法完全执行完毕才会让其他进程工作, 非阻塞即可以异步执行(效果上是同时执行)不同的进程;
    • process.name: 进程名称;
    • process.is_alive(): 进程是否存活;
      • start()方法返回到子进程终止的那一刻, 进程对象将处于活动状态;
    • process.daemon: 进程的守护进程标志;
      • 默认值False;
      • 必须在start()调用之前设置;
      • 若设置为True, 当进程退出时, 它会尝试终止其所有守护进程子进程;
    • process.pid: 进程ID;
    • process.exitcode: 进程的退出代码;
      • 如果进程尚未终止, 则返回None;
      • 否则返回-N, 表示进程被信号N终止;

01-09

  • 寒假开始,下午长跑,发现右脚跟腱更疼了,明明已经休息了两天,一踮脚就疼,本是奔着20km去跑的,最后只跑了10km就撤了,一旦提速就会有刺痛感,太不便了,得多泡脚看看能不能恢复得过来。
  • 从昨晚开始上手之前一直想写的三国杀单挑仿真项目,初始灵感是想要测试四血界孙权单挑四血新王异在不同状态下的优劣(如新王异零装备起手,单+1马起手,+1马和藤甲/仁王盾起手,单雌雄剑起手,单木牛流马起手),后续是想使用强化学习方法进行训练得出界孙权的最优打法,之前听说过四血标孙权可以和四血新王异五五开,以为界孙权的容错会相对高一些,但是看了去年老炮杯一局经典的界孙权内奸单挑主公新王异,界孙权实在是真的是太被动了(当然那时候新王异已经神装雌雄+木马了,界孙权几乎没有任何机会,但是最后还是界孙权竟然没有靠闪电就完成翻盘,虽然只是险胜。我个人的感觉是王异只要有雌雄剑和+1马基本上就可以一战了,如果摸到木牛流马王异几乎必胜),这个单挑对于界孙权来说要比想象中的困难得多,因为能针对新王异的卡牌就只有三张乐不思蜀,而且木马本质上对于界孙权来说只能存一张牌。
  • 目前已经把除出牌阶段的逻辑都写完了,预计明天可以基本完成。

摘自https://caoyang.blog.csdn.net/article/details/109139934

  1. 使用掩码蒙蔽(mask blinding)的特征加密:
  • 为保全特征值的隐私, 本文使用一个带有模加(modular addition)随机掩码来蒙蔽每个特征值, 从而将这个值隐藏起来让服务器无法获得;
  • 随机掩码使用一个PRF确定性地生成, 只对客户端可知;
  • 每个特征值 f f f在索引中以 [ f + R ] [f+R] [f+R]被存储起来:
    • 其中 [ f + R ] = ( f + R ) ( m o d   N ) [f+R]=(f+R)(mod\space N) [f+R]=(f+R)(mod N)
    • R R R是一个被计算成项目编号和文档编号的PRF值的特征掩码(feature mask), 取值范围为 { 0 , 1 , 2 , . . . , N − 1 } \{0,1,2,...,N-1\} {0,1,2,...,N1}
      • 备注: 理解为随机噪声
    • N N N在本文的实现中被设置为常数 2 32 2^{32} 232
    • 中括号表示的 [ f + R ] [f+R] [f+R]也强调服务器只能看到 ( f + R ) ( m o d   N ) (f+R)(mod\space N) (f+R)(mod N), 而不能知道 f f f R R R的具体值;
  • 如果折衷且明智地选择掩码, 上面的schema将允许服务器通过累和掩码特征值来计算排序得分, 从而可以进行部分比较(partial comparison);
  • 本文没有采用一个同态加密schema来进行特征蒙蔽(feature blinding), 因为这不能带来可见的优势但却更加低效, 且不能支持服务端的部分排序(partial ranking);
  • 具体来说, 对于文档 d d d, 索引中的特征权重 f i d f_i^d fid是一个整数, 并且使用随机噪声掩码 R i d R_i^d Rid处理后以 [ f i d + R i d ] [f_i^d+R_i^d] [fid+Rid]存储; 可以引入一个可选的权重 O t d O_t^d Otd即存储成 [ O t d + R O t d ] [O_t^d+RO_t^d] [Otd+ROtd], 这里的随机噪声掩码是 R O t d RO_t^d ROtd;
    • 给定一个带有 q q q个必选特征的查询和 m m m个可选的权重, 对于包含这些掩码的文档 d d d排序总分(total rank score) F F F加上总分掩码(total score mask)模 N N N的结果是: [ F + M ] = [ ∑ i = 1 q [ f i d + R i d ] + ∑ 1 ≤ t ≤ m , O t ∈ X [ O t d + R O t d ] ] [F+M]=[\sum_{i=1}^q[f_i^d+R_i^d]+\sum_{1\le t\le m, O_t\in X}[O_t^d+RO_t^d]] [F+M]=[i=1q[fid+Rid]+1tm,OtX[Otd+ROtd]]
    • 其中 M = ∑ 1 ≤ t ≤ m , O t ∈ X O t d + ∑ 1 ≤ t ≤ m , O t ∈ X R O t d M=\sum_{1\le t\le m, O_t\in X}O_t^d+\sum_{1\le t\le m, O_t\in X}RO_t^d M=1tm,OtXOtd+1tm,OtXROtd
    • O t ∈ X O_t\in X OtX表示对应的可选特征可以在索引的键值对存储中找到;
  • 虽然服务器可以计算上面的加密总和, 但是不同文档里的特征掩码是随机且相互独立的, 所以服务器是不能比较所有匹配到的文档中的相对排名次序;
    • 当服务器匹配到的文档数量适中时, 服务器可以将这些结果发送给客户端, 顺带发送一个用在每个文档里的可选特征的位图(bitmap), 这个位图可以用来协助客户端移除每个排序得分中的掩码总和;
      • 备注:
        • 位图: 就是用每一位来存放某种状态, 适用于大规模数据, 但数据状态又不是很多的情况, 通常是用来判断某个数据存不存在的; 这里应该就是表示文档是否被匹配;
    • 当服务器匹配到的文档数量非常大时或C/S带宽很小, 本文想要服务器进行部分排序从而使得有低分的结果先被过滤掉, 就可以不用发送给客户端; 本质上这变成了一个两阶段的排序, 是cascade ranking的一种类型;

01-10

  • 算是白忙活了个周末,我觉得一个人真的很难写完三国杀单挑测试脚本,我为了减少工作量已经没有继承BaseCard给每张卡牌写单独的子类,但是即便如此,规则定义还是过于费时,就单单无懈可击结算就足以让我抓狂。
  • 我已经把代码挂到博客里,其实我觉得已经完成了八成的工作量了,剩余很多东西主要是需要多次人机交互,导致写提示语非常繁琐,除了非延时类锦囊外其他卡牌的用法都已经定义好了,虽然最近似乎还挺闲,但也不太愿意花再多时间在非本业的工作了,希望以后能有机会回头把代码全部写完。但愿…
  • 唉,真是无奈。
  1. 三国杀牌堆类:
# -*- coding: UTF-8 -*-
# @author: caoyang
# @email: caoyang@163.sufe.edu.cn

import random

from example_config import CardConfig, DeckConfig
from example_base import BaseCard
from example_utils import *

class Deck(object):
	def __init__(self, cards: list=None, discards: list=None) -> None:
		if cards is None:												 # 没有给定
			self.load_default_deck()
		else:															 # 否则直接使用给定的卡牌创建新牌堆
			self.cards = cards
		# random.shuffle(self.cards)										 # 洗牌
		self.discards = [] if discards is None else discards			 # 初始化弃牌堆
		self.id2card = {card.id: card for card in self.cards}			 # 构建一个卡牌的索引, 便于查询
		
	def load_default_deck(self) -> None:
		"""默认军争牌堆(带木牛流马, 161张牌)"""
		self.cards = []
		cardconfig_dict = get_config_dict(CardConfig)
		index = -1
		with open(DeckConfig.default_deck_filepath, 'r', encoding='utf8') as f:
			lines = f.read().splitlines()
		# default_deck_filepath配置文件的数据处理
		for line in filter(None, lines):								 # 注意删除配置文件中的空行
			entry = line.strip()
			if entry.startswith('[') and entry.endswith(']'):			 # 花色名称包含在'['与']'之间
				suit = CardConfig.__getattribute__(CardConfig, cardconfig_dict.get(entry[1:-1]))
			else:														 # 其他每行都是点数与花色相同的数张卡牌名称
				cells = entry.split('|')								 # 每行的数据使用'|'分隔
				point = cells[0]										 # 点数位于行首
				for cell in cells[1:]:									 # 其他位置都是卡牌名称
					name = CardConfig.__getattribute__(CardConfig, cardconfig_dict.get(cell))
					first_class, second_class, attack_range = CardConfig.name2attr.get(name)
					index += 1
					kwargs = {											 # 新卡牌的参数
						'id': index,
						'name': name,
						'first_class': first_class,
						'second_class': second_class,
						'suit': suit,
						'point': point,
						'attack_range': attack_range,
					}
					card = BaseCard(**kwargs)							 # 创建新卡牌
					self.cards.append(card)								 # 将新卡牌添加到牌堆中
	
	def refresh(self) -> None:
		"""更新牌堆"""
		random.shuffle(self.discards)									 # 弃牌堆洗牌
		self.cards.extend(self.discards)								 # 将洗好的弃牌堆卡牌添加到牌堆下面
		self.discards = []												 # 置空弃牌堆
	
if __name__ == '__main__':
	
	deck = Deck()
	index = 0
	for card in deck.cards:
		index += 1
		print(index, card.suit, card.point, card.name, card.first_class, card.second_class, card.area, card.attack_range)

01-11

  • 颓废半天,上分不成,白白浪费一个下午。
  • 现在如果不是在做分内的事就特别罪恶,但是有时候还就是想去花很长时间做一件看起来确实没啥意义的事情,总结还是被生活毒打得不够。
  • 晚上随性跑了10圈,明天养个状态跑10km清醒一下,8号考完试后已经不务正业整整三天了,再这么下去怕是真的没了。
  1. 三国杀测试脚本的工具函数:
# -*- coding: UTF-8 -*-
# @author: caoyang
# @email: caoyang@163.sufe.edu.cn

from example_config import CardConfig

def get_config_dict(config: type) -> dict:
	"""获取配置类的参数字典(键值反转)"""
	config_dict = {}
	for key in dir(config):
		if key.startswith('__') and key.endswith('__'):
			continue
		try: 
			config_dict[config.__getattribute__(config, key)] = key
		except:
			 continue
	return config_dict

def display_card(card) -> None:
	if card is None:
		print(card)
	else:
		print(card.id, card.suit, card.point, card.name)

def display_character(character) -> None:
	print('  - 体力值: {}'.format(character.health_point))
	print('  - 体力上限: {}'.format(character.health_point_max))
	print('  - 手牌区: {}张'.format(len(character.hand_area)))
	for card in character.hand_area:
		print('    + ', end='')
		display_card(card)
	equipment_count = 0
	for card in character.equipment_area.values():
		if card is not None:
			equipment_count += 1
	print('  - 装备区: {}张'.format(equipment_count))
	for area, card in character.equipment_area.items():
		print('    + {}: '.format(area), end='')
		display_card(card)
	print('  - 判定区: {}张'.format(len(character.judgment_area)))
	for card in character.judgment_area:
		print('    + ', end='')
		display_card(card)

def fetch_cards(deck, number: int=1, from_top=True) -> list:
	"""从牌堆获取若干张卡牌"""
	if len(deck.cards) < number:
		print('  - 触发洗牌')
		deck.refresh()
		if len(deck.cards) < number:
			raise Exception('平局')
	cards = [deck.cards.pop(0 if from_top else -1) for _ in range(number)]
	return cards
	
def recast_card(deck, card):
	"""重铸卡牌"""
	new_card = fetch_cards(deck=deck, number=1)[0]
	deck.discards.append(card)
	return new_card

def hurt(deck, character, hurt_point: int=1):
	"""受到伤害"""
	armor = character.equipment_area[CardConfig.armor]
	if armor is not None and armor.name == CardConfig.bysz:				 # 白银狮子减伤
		hurt_point = 1
	character.health_point -= hurt_point
	if character.health_point <= 0:
		character.ask_for_peach(deck, mode='manual')
		
def calc_distance(source_character, target_character, base: int=1) -> int:
	"""计算距离"""
	return max(1, base - (source_character.equipment_area[CardConfig.attack_horse] is not None) + (target_character.equipment_area[CardConfig.defend_horse] is not None))

def calc_attack_range(character):
	"""计算攻击范围"""
	if character.equipment_area[CardConfig.arms] is None:
		return 1
	return character.equipment_area[CardConfig.arms].attack_range


if __name__ == '__main__':
	
	pass

01-12

  • 统计85分卡点3.7,刺激,拼了五天终究不负,状态振奋。
  • 考虑到论文不可能每篇都详读,从今天起新设一块专门放论文略读的笔录,以表格记录。
  • 下午大破10km成绩,去年十一月底的46’42",今天提升到了45’53"。上一个pb跑完废了左脚,后来又陆续废了右脚,右膝,到最近废了右跟腱,今天天气非常好,不冷不热,维持4’40"配速到20圈状态都保持得很好,然后决定提速跑10km,因为感觉再跑个15km太费时了,而且可能这个速度坚持不到15km。最后5圈把平均配速提到4’35"以内,历史最好成绩,很舒爽。也许是受今天统计考试成绩的鼓舞吧,哈哈!
  1. DGL库图节点分类示例代码:(我看THU数据派的推文好像清华那边也开发了一个开源的PythonAutoGL,跟DGL差不多也是做图神经网络这块的,延展性也很好,不过真的有必要学习这么多用途相近的package吗,人的精力还是有限的)。
# -*- coding: UTF-8 -*-

import dgl
import torch
import numpy as np
import dgl.nn as dglnn
import torch.nn as nn
import torch.nn.functional as F

# Load data.
dataset = dgl.data.CiteseerGraphDataset()
graph = dataset[0]														 # num_nodes: 3327 | num_edges: 9228								

# Contruct a two-layer GNN model.
class SAGE(nn.Module):
	def __init__(self, in_feats, hid_feats, out_feats):
		super().__init__()
		self.conv1 = dglnn.SAGEConv(in_feats=in_feats, out_feats=hid_feats, aggregator_type='mean')
		self.conv2 = dglnn.SAGEConv(in_feats=hid_feats, out_feats=out_feats, aggregator_type='mean')

	def forward(self, graph, inputs):									 # inputs are features of nodes
		h = self.conv1(graph, inputs)
		h = F.relu(h)
		h = self.conv2(graph, h)
		return h	

node_features = graph.ndata['feat']										 # Node feature: shape(3327, 3703)
node_labels = graph.ndata['label']										 # Node labels: shape(3327, )
train_mask = graph.ndata['train_mask']									 # Train mask: shape(3327, ), used to drop some nodes
valid_mask = graph.ndata['val_mask']									 # Valid mask: shape(3327, ), used to drop some nodes
test_mask = graph.ndata['test_mask']									 # Test mask: shape(3327, ), used to drop some nodes
n_features = node_features.shape[1]										 # Number of features: 3703
n_labels = int(node_labels.max().item() + 1)							 # Number of different classes: 6


# Define model metric.
def evaluate(model, graph, features, labels, mask):
	model.eval()														 # Enter the evaluation mode.
	with torch.no_grad():												 # When we do evaluation, gradient is not needed to be considered.
		logits = model(graph, features)									 # Get the output of the model.
		logits = logits[mask]											 # Predicted possibility.
		labels = labels[mask]											 # True labels.
		_, indices = torch.max(logits, dim=1)							 # Get the index of max possibility.
		correct = torch.sum(indices == labels)							 # Get the number of correct prediction.	
		return correct.item() * 1.0 / len(labels)						 # Calculate accuracy.

# Train model.
model = SAGE(in_feats=n_features, hid_feats=100, out_feats=n_labels)
opt = torch.optim.Adam(model.parameters())

for epoch in range(100):
	model.train()
	logits = model(graph, node_features)
	loss = F.cross_entropy(logits[train_mask], node_labels[train_mask])
	acc = evaluate(model, graph, node_features, node_labels, valid_mask)
	opt.zero_grad()
	loss.backward()
	opt.step()
	print(loss.item(), acc)
	
print('Accuracy on test: {}'.format(evaluate(model, graph, node_features, node_labels, test_mask)))

# Save model.
torch.save(model, 'node_sage.m')

01-13

  • 转硕博要补第一学期的课,于是我赶紧去问ss和yy要优化理论的课件资料,以及期中期末的考卷,怕过段时间他们就全忘了。然后这俩货笑我还要这玩意儿,说哪有什么课件,他们自己用的课件就是我给他们整理的本科高级运筹学课件,我直接打出三个问号???
  • 压力山大,每次开完会都觉得自己跟个废柴一样,但是总是躲不过间歇性踌躇满志综合征,只有在跑步中才能逐渐从迷失中寻回自我,跑步已经越来越化为我生命中无法割裂的一部分了。
  • 这学期总体来说不算太差,虽然没有实习或者做助管搞副业来充实口袋,但是坚持跑步并且达到了从前不曾企及的水平,绩点马马虎虎有3.79多(全靠期末统计力挽狂澜,英语实在是一生之敌),然后也忙里偷闲在导师主持的会议论文集中发了一篇水文。接下来两个学期课非常多,到时候还有转博考和博资考,还是要抓紧空闲时间,一切提早准备才能顺利毕业。研路漫漫,善始善终,且与诸君共勉。
  1. python带参数的装饰器写法:
# -*- coding: UTF-8 -*-
# @author: caoyang
# @email: caoyang@163.sufe.edu.cn

import time
from functools import wraps


def timer(func):
	@wraps(func)
	def wrapper(*args, **kwargs):
		start_time = time.time()
		func(*args, **kwargs)
		end_time = time.time()
		print('Function `{}` runtime is {} seconds.'.format(func.__name__, end_time - start_time))
	return wrapper

@timer
def mytest(n):
	for i in range(n):
		time.sleep(.01)
	
if __name__ == '__main__':
	mytest(1000)

01-14

  • 最近在重看transformer,准备用torch和tensorflow分别写个demo,以后总能用得到,正好也是回顾一下Transformer中的原理,感觉还是很有必要重看的,很多都忘了。
  • 午睡时不知为何心跳很快,特别紧张,完全睡不着,近期也没什么特别的事情。
  • 下午状态也不行,上场只跑了10圈,不过拼了半条命跑进了18分钟,基本持平了pb,有点热,明天再试试能不能到20km。
  1. PyTorch版本的Positional Encoding
# -*- coding: UTF-8 -*-
# @author: caoyang
# @email: caoyang@163.sufe.edu.cn
# Implementation for *Attention is all you need*
# Paper download: https://arxiv.org/abs/1706.03762.pdf

import torch
import numpy

class PositionalEncoding(torch.nn.Module):
	"""Section 3.5: Implementation for Positional Encoding"""
	def __init__(self, d_input: int, d_output: int, n_position: int) -> None:
		"""
		Transformer takes the position index of element in a sequence as input features.
		Positional Encoding can be formulated as below:
		$$
		{\rm PE}({\rm pos}, 2i) = \sin\left(\frac{\rm pos}{10000^{\frac{2i}{d_{\rm model}}}}\right) \\
		{\rm PE}({\rm pos}, 2i+1) = \cos\left(\frac{\rm pos}{10000^{\frac{2i}{d_{\rm model}}}}\right)
		$$
		where:
		  - $\rm pos$ is the position index of element.
		  - $i$ is the index of embedding vector of `PositionalEncoding`.
		
		:param d_input		: Input dimension of `PositionalEncoding` module.
		:param d_output		: Output dimension of `PositionalEncoding` module.
		:param n_position	: Total number of position, that is the length of sequence.
		"""
		super(PositionalEncoding, self).__init__()
		def _generate_static_positional_encoding():
			sinusoid_table = numpy.array([[pos / numpy.power(10000, (i - i % 2) / d_output) for i in range(d_output)] for pos in range(n_position)])
			sinusoid_table[:, 0::2] = numpy.sin(sinusoid_table[:, 0::2])
			sinusoid_table[:, 1::2] = numpy.cos(sinusoid_table[:, 1::2])	
			return torch.FloatTensor(sinusoid_table).unsqueeze(0)		 # Unsequeeze: wrap position encoding tensor with '[' and ']'.
		self.linear = torch.nn.Linear(in_features=d_input, out_features=d_output)
		self.register_buffer('pos_table', _generate_static_positional_encoding())
		# print(self.pos_table.shape)									 # (1, `n_position`, `d_output`)

	def forward(self, input: torch.FloatTensor) -> torch.FloatTensor:
		"""
		Add static postional encoding table to input tensor for output tensor.	
		:param input: 				The shape is (*, `n_position`, `d_input`)
		:return position_encoding: 	The shape is (*, `n_position`, `d_output`)
		"""
		x = self.linear(input)											 # (*, `n_position`, `d_output`)
		position_encoding = x + self.pos_table[:, :x.shape[1]].clone().detach()
		return position_encoding										 # (*, `n_position`, `d_output`)

01-15

  • CSDN年度之战榜单
  • 一共700多人参赛,一等奖5个,二等奖10个,前15里面除我之外全是博客专家,90%的粉丝过千,征文点赞和收藏都是至少两位数,而我只有4点赞1收藏,那个收藏还是我自己点的。什么叫以质取胜啊[战术后仰]
  • 可把我给牛逼的[哈哈]
  1. PyTorch版本的Position-wise Feed-Forward Networks
class PositionWiseFeedForwardNetworks(torch.nn.Module):
	"""Section 3.3: Implementation for Position-wise Feed-Forward Networks"""
	def __init__(self, d_input: int, d_hidden: int) -> None:
		"""
		The Position-wise Feed-Forward Networks can be formulated as below:
		$$
		{\rm FFN}(x) = \max(0, xW_1 + b_1)W_2 + b_2
		$$
		Note:
		  - Input dimension is the same as output dimension, which is set as $d_{\rm model}=512$ in paper.
		  - Hidden dimenstion is set as $\d_{ff}=2048$ in paper.
		  
		:param d_input	: Input dimension, default 512 in paper, which is the size of $d_{\rm model}$
		:param d_hidden	: Hidden dimenstion, default 2048 in paper.
		"""
		super(PositionWiseFeedForwardNetworks, self).__init__()
		self.linear_1 = torch.nn.Linear(in_features=d_input, out_features=d_hidden)
		self.linear_2 = torch.nn.Linear(in_features=d_hidden, out_features=d_input)

	def forward(self, input: torch.FloatTensor) -> torch.FloatTensor:
		"""
		:param input	: Shape is (*, `d_input`).
		:return output	: Shape is (*, `d_input`).
		"""
		x = self.linear_1(input)
		x = torch.nn.ReLU()(x)											 # Relu(x) = max(0, x)
		output = self.linear_2(x)
		return output

01-16

  • 临近回家综合征,啥事都做不成。唉… 这学期勉强算是逃过了周六的诅咒,却还是逃不过最终的亡语。
  • 简单跑了五圈,权当休整了。
  1. PyTorch版本的Scaled Dot-Product Attention
class ScaledDotProductAttention(torch.nn.Module):
	"""Section 3.2.1: Implementation for Scaled Dot-Product Attention"""
	def __init__(self) -> None:
		super(ScaledDotProductAttention, self).__init__()

	def forward(self, q: torch.FloatTensor, k: torch.FloatTensor, v: torch.FloatTensor, mask: torch.LongTensor=None) -> (torch.FloatTensor, torch.FloatTensor):
		"""
		The Scaled Dot-Product Attention can be formulated as below:
		$$
		{\rm Attention}(Q, K, V) = {\rm softmax}\left(\frac{QK^\top}{\sqrt{d_k}}\right)V
		$$
		
		:param q		: This is $Q$ above, whose shape is (batch_size, n_head, len_q, $d_q$)
		:param k		: This is $K$ above, whose shape is (batch_size, n_head, len_k, $d_k$)
		:param v		: This is $V$ above, whose shape is (batch_size, n_head, len_v, $d_v$)
		:param mask		: The value in `scores` will be replaced with 1e-9 if the corresponding value in mask, whose shape is (len_q, len_k), is 0.
		
		Note:
		  - $d_q$ = $d_k$ holds and let $d_q$ = $d_k$ = d_output.
		  - `len_k = len_v` holds.
		
		:return scores	: (batch_size, n_head, len_q, len_k)
		:return output	: (batch_size, n_head, len_q, $d_v$)
		"""
		# batch_size: batch size of training input.
		# n_head	: The number of multi-heads.
		# d_output	: The dimension of $Q$, $K$, $V$.
		d_q, d_k = q.shape[-1], k.shape[-1]								 # `d_k` is d_output.
		assert d_q == d_k												 # Assumption: $d_q$ = $d_k$
		scores = torch.matmul(q, k.transpose(2, 3)) / (d_k ** 0.5)		 # (batch_size, n_head, len_q, d_output) * (batch_size, n_head, d_output, len_k) -> (batch_size, n_head, len_q, len_k)
		if mask is not None:
			scores = scores.masked_fill(mask.unsqueeze(0).unsqueeze(0)==0, 1e-9)
		scores = torch.nn.Softmax(dim=-1)(scores)						 # (batch_size, n_head, len_q, len_k) -> (batch_size, n_head, len_q, len_k)
		output = torch.matmul(scores, v)								 # (batch_size, n_head, len_q, len_k) * (batch_size, n_head, len_k, $d_v$) -> (batch_size, n_head, len_q, $d_v$)
		return output, scores

01-17

  • 背了两台4kg的笔记本负重行军一小时,轻便与配置实在是不可兼得。也罢,愿明早一路顺风别堵车。
  1. PyTorch版本的Multi-head Attention
class MultiHeadAttention(torch.nn.Module):
	"""Section 3.2.2: Implementation for Multi-Head Attention"""
	def __init__(self, d_input: int, d_output: int, n_head: int) -> None:
		"""
		The Multi-Head Attention can be formulated as below:
		$$
		{\rm MultiHead}(Q, K, V) = {\rm Concat}({\rm head}_1, ... , {\rm head}_h)W^O \\
		{\rm head}_i = {\rm Attention}(QW_i^Q, KW_i^K, VW_i^V)
		$$
		where:
		  - $W_i^Q \in \mathcal{R}^{d_{\rm model} × d_q}$
		  - $W_i^K \in \mathcal{R}^{d_{\rm model} × d_k}$
		  - $W_i^V \in \mathcal{R}^{d_{\rm model} × d_v}$
		  - $W^O \in \mathcal{R}^{hd_v × d_{\rm model}}$
		  - $h$ is the total number of heads.
		  - Note that $d_q = d_k$ holds.
		  - As is mentioned in paper, $h = 8$ and $d_k = d_v = \frac{d_{\rm model}}{h} = 64$ is set as default.
		
		Below we set: 
		  - `d_input` = $d_{\rm model}$
		  - `d_output` = $d_q$ = $d_k$ = $d_v$
		  - Usually, `d_input` = `d_output` is assumed so that residual connection is easy to calculate.
		
		:param d_input	: Input dimension of `MultiHeadAttention` module.
		:param d_output	: Output dimension of `MultiHeadAttention` module.
		:param n_head	: Total number of heads.
		"""
		super(MultiHeadAttention, self).__init__()
		self.linear = torch.nn.Linear(in_features=n_head * d_output, out_features=d_input)		# $W^O \in \mathcal{R}^{hd_v × d_{\rm model}}$
		self.linear_q = torch.nn.Linear(in_features=d_input, out_features=n_head * d_output)	# $W^Q \in \mathcal{R}^{d_{\rm model} × hd_q}$
		self.linear_k = torch.nn.Linear(in_features=d_input, out_features=n_head * d_output)	# $W^K \in \mathcal{R}^{d_{\rm model} × hd_k}$
		self.linear_v = torch.nn.Linear(in_features=d_input, out_features=n_head * d_output)	# $W^V \in \mathcal{R}^{d_{\rm model} × hd_v}$
		self.n_head = n_head
		self.d_output = d_output
		self.scaled_dot_product_attention = ScaledDotProductAttention()

	def forward(self, q: torch.FloatTensor, k: torch.FloatTensor, v: torch.FloatTensor, mask: torch.LongTensor=None) -> (torch.FloatTensor, torch.FloatTensor):
		"""
		$$
		{\rm MultiHead}(Q, K, V) = {\rm Concat}({\rm head}_1, ... , {\rm head}_h)W^O \\
		{\rm head}_i = {\rm Attention}(QW_i^Q, KW_i^K, VW_i^V)
		$$
		where:
		  - $d_q = d_k$ holds.
		  - `d_input` = $d_q$ = $d_k$ = $d_v$

		:param q		: This is $Q$ above, whose shape is (`batch_size`, `len_q`, $d_q$)
		:param k		: This is $K$ above, whose shape is (`batch_size`, `len_k`, $d_k$)
		:param v		: This is $V$ above, whose shape is (`batch_size`, `len_v`, $d_v$)
		:param mask		: The value in `scores` will be replaced with 1e-9 if the corresponding value in mask, whose shape is (len_q, len_k), is 0.
		
		Note that `len_k` = `len_v` holds.
				
		:return scores	: (batch_size, n_head, len_q, len_k)
		:return output	: (batch_size, len_q, d_input)
		"""
		batch_size, len_q, len_k, len_v = q.shape[0], q.shape[1], k.shape[1], v.shape[1]
		assert len_k == len_v											 # Assumption: seq_len = `len_k` = `len_v`
		q = self.linear_q(q).contiguous().view(batch_size, len_q, self.n_head, self.d_output)	# (batch_size, len_q, d_input) -> (batch_size, len_q, n_head * d_output) -> (batch_size, len_q, n_head, d_output)
		k = self.linear_k(k).contiguous().view(batch_size, len_k, self.n_head, self.d_output)	# (batch_size, len_k, d_input) -> (batch_size, len_k, n_head * d_output) -> (batch_size, len_k, n_head, d_output)
		v = self.linear_v(v).contiguous().view(batch_size, len_v, self.n_head, self.d_output)	# (batch_size, len_v, d_input) -> (batch_size, len_v, n_head * d_output) -> (batch_size, len_v, n_head, d_output)
		q = q.transpose(1, 2)											 # (batch_size, len_q, n_head, d_output) -> (batch_size, n_head, len_q, d_output)
		k = k.transpose(1, 2)											 # (batch_size, len_k, n_head, d_output) -> (batch_size, n_head, len_k, d_output)
		v = v.transpose(1, 2)											 # (batch_size, len_v, n_head, d_output) -> (batch_size, n_head, len_v, d_output)
		output, scores = self.scaled_dot_product_attention(q=q, k=k, v=v, mask=mask)# (batch_size, n_head, len_q, d_output), (batch_size, n_head, len_q, len_k)
		output = output.transpose(1, 2).contiguous().view(batch_size, len_q, -1)	# (batch_size, n_head, len_q, d_output) -> (batch_size, len_q, n_head, d_output) -> (batch_size, len_q, n_head * d_output)
		output = self.linear(output)									 # (batch_size, len_q, n_head * d_output) -> (batch_size, len_q, d_input)
		return output, scores											 # (batch_size, len_q, d_input), (batch_size, n_head, len_q, len_k)

01-18

  • 养老期第一跑,选择路跑5.54km,配速4’42",看起来很一般,其实已经是在非常好的状态下的水平了。总之路跑确实挺难,上坡难跑,下坡难控制,这段时间可以好好练一练路跑,备战下半年的半马。
  • 希望假期生活有规律吧,应该是难得比较清闲的一个假期了,养养老也挺好。
  1. PyTorch版本的Encoder
class Encoder(torch.nn.Module):
	"""Section 3.1: Implementation for Encoder"""
	def __init__(self, d_input: int, d_output: int, d_hidden: int, n_head: int, n_position: int) -> None:
		"""
		This is an implementation for one-time Encoder, which is repeated for six times in paper.
		
		:param d_input		: Input dimension of `Encoder` module.
		:param d_output		: Output dimension of `Encoder` module.
		:param d_hidden		: Hidden dimension of `PositionWiseFeedForwardNetworks` module.
		:param n_head		: Total number of heads.
		:param n_position	: Total number of position, that is the length of sequence.
		"""
		super(Encoder, self).__init__()
		self.position_encoding = PositionalEncoding(d_input=d_input, d_output=d_output, n_position=n_position)
		self.multi_head_attention = MultiHeadAttention(d_input=d_output, d_output=d_output, n_head=n_head)
		self.layer_norm_1 = torch.nn.LayerNorm(d_output)				
		self.layer_norm_2 = torch.nn.LayerNorm(d_output)				
		self.position_wise_feed_forward_networks = PositionWiseFeedForwardNetworks(d_input=d_output, d_hidden=d_hidden)

	def forward(self, input: torch.FloatTensor, mask: torch.LongTensor=None) -> torch.FloatTensor:
		"""
		See https://img-blog.csdnimg.cn/20210114103418782.png
		
		:param input	: Shape is (batch_size, `n_position`, `d_input`)
		:param mask		: Shape is (`n_position`, `n_position`)
		:return output	: Shape is (batch_size, `n_position`, `d_output`)
		"""
		q = self.position_encoding(input=input)							 # (*, n_position, d_input) -> (*, n_position, d_output)
		k, v = q.clone(), q.clone()										 # $Q$, $K$, $V$ are just the same in Encoder, but it is a little different in Decoder.
		residual_1 = q.clone()											 # `.clone()` is used for safety.
		output_1, scores = self.multi_head_attention(q=q, k=k, v=v, mask=mask)
		x = self.layer_norm_1(output_1 + residual_1)					 # Add & Norm: residual connection.
		residual_2 = x.clone()											 # `.clone()` is used for safety.
		output_2 = self.position_wise_feed_forward_networks(input=x)	 # Feed Forward
		output = self.layer_norm_2(output_2 + residual_2)				 # Add & Norm: residual connection.
		return output

01-19

  • 路跑10km达成,配速4’45",最后基本是崩了,差点跑抽筋,是硬撑下来的。
  • 相对来说冬天更倾向于不午睡,因为一旦上了床,就很难再下得来了,所以对夜晚睡眠时长与质量要求颇高,尽量做到早睡自然醒,才能保持住状态去跑更长的里程。
  • 三天一篇paper,有灵感就写点代码,但愿寒假一个月能别太荒废罢。
  1. PyTorch版本的Decoder
class Decoder(torch.nn.Module):
	"""Section 3.1: Implementation for Decoder"""
	def __init__(self, d_input: int, d_output: int, d_hidden: int, n_head: int, n_position: int) -> None:
		super(Decoder, self).__init__()
		self.position_encoding = PositionalEncoding(d_input=d_input, d_output=d_output, n_position=n_position)
		self.multi_head_attention_1 = MultiHeadAttention(d_input=d_output, d_output=d_output, n_head=n_head)
		self.multi_head_attention_2 = MultiHeadAttention(d_input=d_output, d_output=d_output, n_head=n_head)
		self.layer_norm_1 = torch.nn.LayerNorm(d_output)
		self.layer_norm_2 = torch.nn.LayerNorm(d_output)
		self.layer_norm_3 = torch.nn.LayerNorm(d_output)
		self.position_wise_feed_forward_networks = PositionWiseFeedForwardNetworks(d_input=d_output, d_hidden=d_hidden)

	def generate_subsequence_mask(self, subsequence):
		"""
		The subsequence mask is defined as a lower-triangle matrix with value 1.
		"""
		seq_len = subsequence.shape[1]
		ones_tensor = torch.ones((seq_len, seq_len), dtype=torch.int, device=subsequence.device)
		subsequence_mask = 1 - torch.triu(ones_tensor, diagonal=1)		 # Note that the value on diagonal is 1.
		return subsequence_mask

	def forward(self, encoder_output: torch.FloatTensor, target: torch.FloatTensor, mask: torch.LongTensor=None) -> torch.FloatTensor:
		"""
		See https://img-blog.csdnimg.cn/20210114103418782.png
		
		:param encoder_output	: Output of Encoder, whose shape is (batch_size, `n_position`, `d_output_encoder`)
		:param target			: Target tensor in dataset, whose shape is (batch_size, `n_position`, `d_input_decoder`)
		:return output			: Shape is (batch_size, `n_position`, `d_output`)
		"""
		q = self.position_encoding(input=target)						 # (*, n_position, d_input) -> (*, n_position, d_output)
		k, v = q.clone(), q.clone()										 # $Q$, $K$, $V$ are just the same in Encoder, but it is a little different in Decoder.
		residual_1 = q.clone()											 # `.clone()` is used for safety.
		output_1, _ = self.multi_head_attention_1(q=q, k=k, v=v, mask=self.generate_subsequence_mask(target))
		x = self.layer_norm_1(output_1 + residual_1)					 # Add & Norm: residual connection.
		residual_2 = x.clone()											 # `.clone()` is used for safety.
		output_2, _ = self.multi_head_attention_2(q=x, k=encoder_output, v=encoder_output, mask=mask)
		x = self.layer_norm_2(output_2 + residual_2)					 # Add & Norm: residual connection.
		residual_3 = x.clone()											 # `.clone()` is used for safety.
		output_3 = self.position_wise_feed_forward_networks(input=x)	 # Feed Forward
		output = self.layer_norm_3(output_3 + residual_3)				 # Add & Norm: residual connection.
		return output

01-20

  • 路跑7.12km,配速4’48",中途补了一个枣子,因为带水在身上实在是不太切实际,连续三天状态都还不错,果然在家吃得好是不一样。寒假的终极目标是路跑半马,而且我也已经想好了这条半马路线。
  • ACL的论文里也有水平挺次的,感觉很多时候还是看学校和机构背景。
  1. PyTorch版本的Transformer
class Transformer(torch.nn.Module):
	"""Attention is all you need: Implementation for Transformer"""
	def __init__(self, d_input_encoder: int, d_input_decoder: int, d_output_encoder: int, d_output_decoder: int, d_output: int, d_hidden_encoder: int, d_hidden_decoder: int, n_head_encoder: int, n_head_decoder: int, n_position_encoder: int, n_position_decoder) -> None:
		"""
		:param d_input_encoder		: Input dimension of Encoder.
		:param d_input_decoder		: Input dimension of Decoder.
		:param d_output_encoder		: Output dimension of Encoder.
		:param d_output_decoder		: Output dimension of Decoder.
		:param d_output				: Final output dimension of Transformer.
		:param d_hidden_encoder		: Hidden dimension of linear layer in Encoder.
		:param d_hidden_decoder		: Hidden dimension of linear layer in Decoder.
		:param n_head_encoder=4		: Total number of heads in Encoder.
		:param n_head_decoder=4		: Total number of heads in Decoder.
		:param n_position_encoder	: Sequence Length of Encoder Input, e.g. max padding length of Chinese sentences.
		:param n_position_decoder	: Sequence Length of Encoder Input, e.g. max padding length of English sentences.
		"""
		super(Transformer, self).__init__()
		self.encoder = Encoder(d_input=d_input_encoder, d_output=d_output_encoder, d_hidden=d_hidden_encoder, n_head=n_head_encoder, n_position=n_position_encoder)
		self.decoder = Decoder(d_input=d_input_decoder, d_output=d_output_decoder, d_hidden=d_hidden_decoder, n_head=n_head_decoder, n_position=n_position_decoder)
		self.linear = torch.nn.Linear(in_features=d_output_decoder, out_features=d_output)

	def forward(self, source, target) -> torch.FloatTensor:
		"""
		See https://img-blog.csdnimg.cn/20210114103418782.png
		"""
		encoder_output = self.encoder(source)
		decoder_output = self.decoder(encoder_output, target)
		x = self.linear(decoder_output)
		output = torch.nn.Softmax(dim=-1)(x)
		return output

	def size(self):
		size = sum([p.numel() for p in self.parameters()])
		print('%.2fKB' % (size * 4 / 1024))

01-21

  • 路跑6.17km,配速4’48",这次跑的是一个环线,其实还是留有余力的,可是眼前就是一个长上坡,还是就此而止了罢。
  • 蓄力待时,我已经想好了一条15km的环线准备抽空去尝试,现在唯一的障碍是中途无法得到补给,以现在的水平零补给跑10km以上的里程难度很大。
  • 寒假差不多在家一个月,争取月跑200km以上。
  1. 昨天看完paper之后开始研究python实现磁力链接下载的问题,发现前人也已经做了很多尝试,github上很多repository都有关于如何将磁力链接解析为种子文件(.torrent)的方法,但是基本上都是用了第三方工具(如aria2, libtorrent库)。
  2. 之所以开始想搞磁力链接,倒不是为了开车,主要还是不想再用迅雷了,新机上我甚至连微信都不装,因为我除了必要的软件之外,只想安装开源的软件(确切地说最好是portable的版本,像pdf阅读器sumatra直接下个zip解压就能用,都不会污染注册表),一来是确保不是流氓软件,二来是尽可能少地写入C盘。至少到目前为止,除了远程连接工具,压缩软件,浏览器,OFFICE全家桶外,其他的音像播放器(potplayer),文本编辑器(notepad++ sumatra)都是开源的。
  3. 这里我正好推荐一个神器网站FOSSHUB,这上面所有软件都是开源的,绝无绑定和广告,而且一定都是足够精简的。部分还是portable的,sumutra就在上面,我昨天下了一个portable的备份软件,才几个4Mb大小,源码才几百k,真是相见恨晚。自从WIN10升级后,连联想自带的备份软件联想一键恢复都无法备份当前版本的系统了。
  4. 回到磁力链接的问题上,确实目前国内还是迅雷一家独大,而且因为磁力链接本身的特性,导致用户基数巨大的迅雷实然是最好的选择,但是我肯定是不可能给新机装迅雷的,那就必须自己解决了。首先我是先把多进程和多线程下载文件的python脚本写了一下,速度还不错,有空可以分享一下,然后就开始四处搜寻磁力链接下载的方法,事实上因为libtorrent不开源之后,python-libtorrent库的源码也无法下载了,现在连从磁力链接(40位的SHA码)解析出种子文件(.torrent)都极其困难(其实用迅雷偶尔也会难以解析很老的磁力链接,本身我理解解析磁力链接就是求哈希函数的逆,这从哈希函数的定义上来说就是难以求逆的,我挺好奇这个具体原理是怎么样的,难道就是穷举?迅雷可能是已经有足够大的种子文件库,可能直接去比对就能解析出种子文件了)。然后就是怎么连接tracker服务器。
  5. 关于具体了解磁力链接我推荐一篇英文blog:https://markuseliasson.se/article/bittorrent-in-python/,非常详细,其实原作者本身尝试写了一个磁力链接下载器的python脚本(在原文链接中有),我大致看了一下,因为他自己说还很不完善,我也没有深究,后来我是参考了GitHub@magnet-dht,作者也是同道中人了,按照README里的说法已经可以实现到下载了,至于解析磁力链接,他用的是aria2,可惜跟libtorrent一样,都是C++编译的,很难装… 我也没看懂他里面到底是怎么用aria2的。
  6. 四处碰壁后我去FOSSHUB上找了qBittorrent,在旧机上先测试了一下,算是比较简约的软件,评价也很好,但是用起来效果很一般,老旧的磁力链接还是下不下来,不过我倒是用它把http://amvnews.ru/index.php?go=Files&in=view&id=4470上的U神的AMV神作Our Tapes(中文名就是著名的秋山澪与折木奉太郎的爱情故事,B站有转载https://www.bilibili.com/video/BV1nx411F7Jf)给下载到本地,留作一个高清版本作为收藏。
  7. 总之我挺希望有大神能写出一个非常好用的开源磁力链接下载器,再不断推广,至少可以在Github这种社区里通用,让我们能享受资源快速共享,因为很多国外下载链接都很慢(包括git下载的速度),如果能都用上磁力链接,实在是再好不过了,哪怕只是局限在码农这个群体中,分享一些数据,exe,源码等等,现在回看像pandownload(基于aria2开发),SimDTU,这些GitHub项目都已经挂掉了,libtorrent都不开源了,不禁让我们这些后来的人唏嘘不已,更多时候还是得靠自己才行了唉。

01-22

  • 第一次看复联,刷了美队三部,我的老天爷,堕落的生活又开始了,实话说还真挺好看。
  • 昨天有个叫周红喜的人加我vx,我也不知道是谁,也只好接受了,然后上来他问我“贵姓,男生女生?”我就很纳闷,于是就晾着他,过了四五个小时又问“男孩?女生?”,我还是晾着他,感觉不是个好东西,我就想看看这家伙能有啥事,我也不问他是谁,找我干嘛,反正我一个山野闲人能有啥好事,你没事我也没事呗~
  • 雨天,跑步机伺候,5分配的10km,尚有余力,感觉至少能再坚持10分钟以上,目前的极限是跨年那晚的4’44"配速下的场地跑15.2km,感觉在跑步机上是可以达到半马水平的了。
  1. 记录今天搞路由器和网关(猫)的问题:其实我对这块还真是一窍不通;
  • 前几天连有线,把网关千兆口上原先连在路由器WAN口上的网线拔了,换了有线的网线连到笔记本,速度一流,然后把WAN口的网线绑到百兆口上,结果从此家里就没有WIFI了,原来百兆口只能给机顶盒用,且非中国电信天翼超级用户,是不能把百兆口改成可移动端连网的。
  • 后来把网关千兆口和百兆口的线对调,WIFI能上了,有线又不行了。
  • 最后知道是把有线要连在路由器的LAN口(一般有4个,WAN口只有1个)上才能联网,原因是WAN口是连外网,LAN口是连内网,有线算内网。
  • 网关的用户名(一般是useradmin)和密码在猫盒背面,访问192.168.1.1一般可以登陆进行管理,我今天才发现不知道是哪个缺德的在背面贴了个条形码,刚好把密码给挡住了,我小刀刮了半天才勉强看清楚密码。。。

01-23

  • 又刷了四部复联,心满意足,老美拍超级英雄确实有一手。
  • 雨天跑步机10km,左髋有点疼,应该问题不大。
  1. 拓扑排序python脚本
def toposort(graph):
	in_degrees = dict((u,0) for u in graph)   #初始化所有顶点入度为0
	vertex_num = len(in_degrees)
	for u in graph:
		for v in graph[u]:
			in_degrees[v] += 1       #计算每个顶点的入度
	Q = [u for u in in_degrees if in_degrees[u] == 0]   # 筛选入度为0的顶点
	Seq = []
	while Q:
		u = Q.pop()       #默认从最后一个删除
		Seq.append(u)
		for v in graph[u]:
			in_degrees[v] -= 1       #移除其所有指向
			if in_degrees[v] == 0:
				Q.append(v)          #再次筛选入度为0的顶点
	if len(Seq) == vertex_num:       #如果循环结束后存在非0入度的顶点说明图中有环,不存在拓扑排序
		return Seq
	else:
		print("there's a circle.")

01-24

  • 路跑了一次4km速耐,明显感觉不如场地跑,任重道远。第一周52km多一些,勉强说得过去。
  • 旧机搞崩了,真的是手贱,现在准备重装系统,过去的东西都不要了,Adobe全家桶近三年都没打开过,丢了就丢了吧,准备之后只装微信和迅雷,因为这两个不打算再在新机上装。推荐一个图片查看器https://www.fosshub.com/FastStone-Image-Viewer.html,用下来还不错,比WIN10自带的好多了。
  1. 关于python的异步代码(我还真是第一次看到可以在def前面加async,这玩意儿Python 3.5的时候就有了)
import asyncio
from random import randint

async def do_stuff(ip, port):
    print('About to open a connection to {ip}'.format(ip=ip))
    reader, writer = await asyncio.open_connection(ip, port)
    print('Connection open to {ip}'.format(ip=ip))
    await asyncio.sleep(randint(0, 5))
    writer.close()
    print('Closed connection to {ip}'.format(ip=ip))

if __name__ == '__main__':
    loop = asyncio.get_event_loop()
    work = [
        asyncio.ensure_future(do_stuff('8.8.8.8', '53')),
        asyncio.ensure_future(do_stuff('8.8.4.4', '53')),
    ]
    loop.run_until_complete(asyncio.gather(*work))

01-25

  • 出门暴饮暴食,回来跑步机10km,事实上跑得不是很尽兴,有余力,感能上20km的感觉,可惜时间不够了。果然多吃一些是不一样的。
  • 新手上路,一天吃一个罚单,烦得很,开得没自信了。
  1. 使用datetime.datetime.fromtimestamp():
  • 代码示例:
    from datetime import datetime
    timestamp = 1381419600
    print(datetime.fromtimestamp(timestamp))
    
    • 输出结果: 2013-10-10 23:40:00
  • 注意:
    • 该函数无keyword augments, 即不需要添加参数名称;
    • time.localtime()函数不同, datetime.datetime.fromtimestamp()接收的时间戳至少为86400, 前者则可以从0开始;

01-26

  • 研究了一天aria2,发现好东西是不配给草包用的。
  • 重置了老电脑,发现速度倒还可以,果然之前还是装的东西太多了,准备花一段时间好好整顿一下。
  • 试了一下跑步机12km,还是没啥压力,等天气好了再去路跑。
  1. 关于WIN10下aria2的使用:这玩意儿支持磁力链接和种子下载,以及各种传统下载方式,可以替代迅雷那垃圾玩意儿。参考自http://www.360doc.com/content/19/0830/16/20268466_858072587.shtml

01-27

  • 路跑15.21km,选的是一条大环线,挺极限的了,一口气跑完,中途有几处爬坡,终点500米冲刺,很久没有这种畅快了。
  • 这两天装机接触到很多有趣的东西,比如油猴(TamperMonkey)下的许多用户脚本,Motric下载器,ariac2,感叹前人真的为资源共享做出太多贡献,为了攻破百度云盘的防御,一代又一代的码工在前赴后继的写脚本,可惜现在真的连aria2也不管用了,被迫安装了百度云客户端,真的是屈辱至极。
  1. BT下载Tracker服务器爬取:由衷感谢开源贡献者们的工作
 # -*- coding: UTF-8 -*-
# @author: caoyang
# @email: caoyang@163.sufe.edu.cn

import re
import requests
from bs4 import BeautifulSoup

class Tracker(object):
	
	def __init__(self) -> None:
		self.mirrors_all = [
			'https://trackerslist.com/all.txt'
			'https://cdn.jsdelivr.net/gh/XIU2/TrackersListCollection@master/all.txt',
			'http://github.itzmx.com/1265578519/OpenTracker/master/tracker.txt',
		]		
		self.mirrors_best = [
			'https://trackerslist.com/best.txt',
			'https://cdn.jsdelivr.net/gh/XIU2/TrackersListCollection@master/best.txt',
		]
		self.mirrors_bestip = [	
			'https://cdn.jsdelivr.net/gh/ngosang/trackerslist@master/trackers_best_ip.txt',
		]
		self.mirrors_http = [
			'https://trackerslist.com/http.txt',
			'https://cdn.jsdelivr.net/gh/XIU2/TrackersListCollection@master/http.txt',
		]
		self.mirrors_https = [
			'https://cdn.jsdelivr.net/gh/ngosang/trackerslist/trackers_all_https.txt',
		]
		self.mirrors_ws = [
			'https://cdn.jsdelivr.net/gh/ngosang/trackerslist/trackers_all_ws.txt',
		]
		self.mirrors_udp = [
			'https://cdn.jsdelivr.net/gh/ngosang/trackerslist/trackers_all_udp.txt',
		]
		self.mirrors_ip = [
			'https://cdn.jsdelivr.net/gh/ngosang/trackerslist@master/trackers_all_ip.txt'
		]
		self.mirrors_blacklist = [
			'https://cdn.jsdelivr.net/gh/ngosang/trackerslist@master/blacklist.txt',
		]
		self.mirrors = {
			'all': self.mirrors_all,
			'best': self.mirrors_best,
			'bestip': self.mirrors_bestip,
			'http': self.mirrors_http,
			'https': self.mirrors_https,
			'ws': self.mirrors_ws,
			'udp': self.mirrors_udp,
			'ip': self.mirrors_ip,
		}
		
		self.label_compiler = re.compile(r'<[^>]+>', re.S)
		
	def get_trackers(self, category: str='best', filter_blacklist=False) -> list:
		
		def _parse(html):
			soup = BeautifulSoup(html, 'lxml')
			body_html = str(soup.find('body').string)
			trackers = self.label_compiler.sub('', body_html).split()
			return trackers
		mirrors = self.mirrors.get(category)
		assert category is not None
		flag = False
		for mirror in mirrors:
			try:
				print('Try mirror: {} ...'.format(mirror))
				response = requests.get(mirror)
				flag = True
				break
			except:
				print('  - Fail ...')
				continue
		if not flag:
			print('All {} mirrors for trackers are fail.'.format(category))
			return []
		html = response.text
		trackers = _parse(html)
		if filter_blacklist:
			print('Fetch blacklist ...')
			flag = False
			for mirror in self.mirrors_blacklist:
				try:
					print('Try mirror: {} ...'.format(mirror))
					response = requests.get(mirror)
					flag = True
					break
				except:
					print('  - Fail ...')
					continue
			if not flag:
				print('Fail to fetch blacklist.')
				blacklist = []
			else:
				html = response.text
				blacklist = _parse(html)
			for tracker in trackers[:]:
				if tracker in blacklist:
					trackers.remove(tracker)
		return trackers

if __name__ == '__main__':
	t = Tracker()
	trackers = t.get_trackers()
	from pprint import pprint
	pprint(trackers)
	print(','.join(trackers))

01-28

  • 4’33"配4km,休息一分多钟后4’47"配3.17km,感觉路跑的速耐很难,因为有很多爬坡要上,现在的水平还很难跑进4’30",目前场地pb是3km 12’58",6km 26’38",10km 45’53",15km 71’08",配速分别是4’19",4’25",4’35",4’45"
  • 装了matlab,因为忽然想起要补优化理论,到时候作业肯定得要,现在R2020a装完占了30多G,实在是太离谱了。
  • 之前搞IDEA也费了很大事,最后选了别人一个装好的portable的版本,2019.3版本的,勉强凑合,主要还是不太想丢掉Java,虽然以后可能用的很少。
  1. aria2:开源下载器中的巅峰之作
  • aria2c [资源链接URL]
  • aria2c [40位的种子哈希]
  • aria2c [.torrent文件路径]
  • 几个以aria2为基础,做成GUI界面的下载器:Motric(强推),PanDownload(虽然全网封禁,但是也是aria2的辉煌),IDM(这玩意儿似乎要破解,比较麻烦,虽然挺好),qBittorrent和比特彗星。

01-29

  • 路跑第12天,状态低迷,左右肩和左右大腿都疼得不行,慢跑4km就废了,休息完回程又跑了4km多一些,精疲力竭。前天强行15km一时爽,可能又要废一阵子了。目前这周5天的历程分别是:10km(跑步机) 12km(跑步机)15.21km(路跑)7.17km(间歇)8.5km,已经达到日均10km以上的跑量了。
  • 解析磁链很难,最终还是屈服于迅雷,虽然吸血雷,但是耐不住快,是香…
  1. Tensorflow自定义层:
# -*- coding: UTF-8 -*-
import os
import gc
import re
import time
import keras
import numpy as np
import pandas as pd

from tqdm import tqdm
import tensorflow as tf
import keras.backend as K
from keras.layers import *
from keras.models import *
from sklearn import metrics
from keras.callbacks import *
from keras.optimizers import *
from unidecode import unidecode
from keras.initializers import *
from keras.preprocessing.text import Tokenizer
from keras.losses import categorical_crossentropy
from keras.preprocessing.sequence import pad_sequences
from keras_bert import load_trained_model_from_checkpoint
from sklearn.model_selection import train_test_split,StratifiedKFold
from keras import initializers,regularizers,constraints,optimizers,layers

class MyLayer(Layer):

	def __init__(self, output_dim, **kwargs):
		self.output_dim = output_dim
		super(MyLayer, self).__init__(**kwargs)

	def build(self, input_shape):
		# Create a trainable weight variable for this layer.
		self.kernel = self.add_weight(name='kernel', 
									  shape=(input_shape[1], self.output_dim),
									  initializer='uniform',
									  trainable=True)
		super(MyLayer, self).build(input_shape)  # Be sure to call this somewhere!

	def call(self, x):
		return K.dot(x, self.kernel)

	def compute_output_shape(self, input_shape):
		return (input_shape[0], self.output_dim)

def model_1(
	max_features=32768,													 # 单词种类数
	embed_size=256,														 # 词向量维度
	d_input=(100,),														 # 原始句向量形状(#token,#emb)
	d_output=(100,),													 # 目标句向量形状(#token,#emb)
	dropout=0.12,														 # 残差连接概率
	lr=0.008,															 # 学习率(步长)
	is_summarized=True,													 # 是否输出网络概况
):													
	K.clear_session()
	input_tensor = Input(shape=d_input)
	x = Embedding(max_features,embed_size,mask_zero=False,trainable=False,input_length=d_input[0])(input_tensor)
	x = Flatten()(x)
	x = MyLayer(25600)(x)
	x = Dense(128,activation="relu")(x)
	model = Model(inputs=input_tensor,outputs=x)
	if is_summarized: model.summary()
	return model

if __name__ == "__main__":
	model = model_1()

01-30

  • 休整一日,连续12天日均8km以上的跑量,腿实在是挺不住了(主要是27日的15km路跑太伤了)。医生朋友提醒我跑步对膝盖损伤极大,现在不注意,年纪大了肯定会有后遗症的。
  • 油猴其实一般,我感觉那些脚本我也写过不少,只是油猴的用户脚本基本上都是内嵌浏览器的javascript脚本,用起来方便一些,但是无效的方法也很多,比如之前破不了QQ音乐的下载,油猴也拿它没办法,而且现在所有百度云盘直链下载的脚本都挂了,不过倒是看到不少视频网站的脚本,有空试试视频的下载,有些好mv还是很值得收藏的。
  1. opencv的一些脚本(其实这个库还能操控计算机的摄像头,还是挺有意思的):
# -*- coding: UTF-8 -*-
# @author: caoyang
# @email: CaoYang@163.sufe.edu.cn

import numpy as np
import cv2

'''
定义裁剪函数, 四个参数分别是:
左上角横坐标x0
左上角纵坐标y0
裁剪宽度w
裁剪高度h
'''
crop_image = lambda img,x0,y0,w,h: img[y0:y0+h,x0:x0+w]

'''
随机裁剪
area_ratio为裁剪画面占原画面的比例
hw_vari是扰动占原高宽比的比例范围
'''
def random_crop(img,area_ratio,hw_vari):
	h,w = img.shape[:2]
	hw_delta = np.random.uniform(-hw_vari,hw_vari)
	hw_mult = 1 + hw_delta
	
	# 下标进行裁剪, 宽高必须是正整数
	w_crop = int(round(w*np.sqrt(area_ratio*hw_mult)))
	
	# 裁剪宽度不可超过原图可裁剪宽度
	if w_crop > w:
		w_crop = w
		
	h_crop = int(round(h*np.sqrt(area_ratio/hw_mult)))
	if h_crop > h:
		h_crop = h
	
	# 随机生成左上角的位置
	x0 = np.random.randint(0,w-w_crop+1)
	y0 = np.random.randint(0,h-h_crop+1)
	
	return crop_image(img,x0,y0,w_crop,h_crop)

'''
定义旋转函数:
angle是逆时针旋转的角度
crop是个布尔值, 表明是否要裁剪去除黑边
'''
def rotate_image(img,angle,crop):
	h,w = img.shape[:2]
	
	# 旋转角度的周期是360°
	angle %= 360
	
	# 用OpenCV内置函数计算仿射矩阵
	M_rotate = cv2.getRotationMatrix2D((w/2,h/2),angle,1)
	
	# 得到旋转后的图像
	img_rotated = cv2.warpAffine(img,M_rotate,(w,h))

	# 如果需要裁剪去除黑边
	if crop:
		# 对于裁剪角度的等效周期是180°
		angle_crop = angle % 180
		
		# 并且关于90°对称
		if angle_crop > 90:
			angle_crop = 180 - angle_crop
			
		# 转化角度为弧度
		theta = angle_crop * np.pi / 180.0
		
		# 计算高宽比
		hw_ratio = float(h) / float(w)
		
		# 计算裁剪边长系数的分子项
		tan_theta = np.tan(theta)
		numerator = np.cos(theta) + np.sin(theta) * tan_theta
		
		# 计算分母项中和宽高比相关的项
		r = hw_ratio if h > w else 1 / hw_ratio
		
		# 计算分母项
		denominator = r * tan_theta + 1
		
		# 计算最终的边长系数
		crop_mult = numerator / denominator
		
		# 得到裁剪区域
		w_crop = int(round(crop_mult*w))
		h_crop = int(round(crop_mult*h))
		x0 = int((w-w_crop)/2)
		y0 = int((h-h_crop)/2)

		img_rotated = crop_image(img_rotated,x0,y0,w_crop,h_crop)

	return img_rotated

'''
随机旋转
angle_vari是旋转角度的范围[-angle_vari,angle_vari)
p_crop是要进行去黑边裁剪的比例
'''
def random_rotate(img,angle_vari,p_crop):
	angle = np.random.uniform(-angle_vari,angle_vari)
	crop = False if np.random.random() > p_crop else True
	return rotate_image(img,angle,crop)

'''
定义hsv变换函数:
hue_delta是色调变化比例
sat_delta是饱和度变化比例
val_delta是明度变化比例
'''
def hsv_transform(img,hue_delta,sat_mult,val_mult):
	img_hsv = cv2.cvtColor(img,cv2.COLOR_BGR2HSV).astype(np.float)
	img_hsv[:,:,0] = (img_hsv[:,:,0] + hue_delta) % 180
	img_hsv[:,:,1] *= sat_mult
	img_hsv[:,:,2] *= val_mult
	img_hsv[img_hsv > 255] = 255
	return cv2.cvtColor(np.round(img_hsv).astype(np.uint8),cv2.COLOR_HSV2BGR)

'''
随机hsv变换
hue_vari是色调变化比例的范围
sat_vari是饱和度变化比例的范围
val_vari是明度变化比例的范围
'''
def random_hsv_transform(img,hue_vari,sat_vari,val_vari):
	hue_delta = np.random.randint(-hue_vari,hue_vari)
	sat_mult = 1 + np.random.uniform(-sat_vari,sat_vari)
	val_mult = 1 + np.random.uniform(-val_vari,val_vari)
	return hsv_transform(img,hue_delta,sat_mult,val_mult)

'''
定义gamma变换函数:
gamma就是Gamma
'''
def gamma_transform(img,gamma):
	gamma_table = [np.power(x / 255.0,gamma) * 255.0 for x in range(256)]
	gamma_table = np.round(np.array(gamma_table)).astype(np.uint8)
	return cv2.LUT(img,gamma_table)

'''
随机gamma变换
gamma_vari是Gamma变化的范围[1/gamma_vari,gamma_vari)
'''
def random_gamma_transform(img,gamma_vari):
	log_gamma_vari = np.log(gamma_vari)
	alpha = np.random.uniform(-log_gamma_vari,log_gamma_vari)
	gamma = np.exp(alpha)
	return gamma_transform(img,gamma)

01-31

  • 早上起床发现左膝盖疼我就知道事情不太对了,又给老妈抓住把柄了,这几天先在跑步机上休整了,确实是伤了。
  • 最近补了不少番和剧,这两天迷上了逆转裁判,本来是部游戏,但是改编的番剧情不太全,在B站上找了个全程实况的云通关了一下,确实剧情挺不错的,算是推理游戏中做得相当好的video game了。
# -*- coding: UTF-8 -*-
# @author: caoyang
# @email: lzwcy110@163.com

import json
import requests
from urllib.parse import urlencode
from datetime import datetime,timedelta

from baiduindex_utils import *
from baiduindex_config import *

class Index(object):
	
	def __init__(self,keywords,start_date,end_date,cookies,area=0):
		# 1. 参数校验
		print("正在检查参数可行性...")
		assert is_cookies_valid(cookies),"cookies不可用!"				 # 检查cookie是否可用
		print("  - cookies检测可用!")
		assert is_keywords_valid(keywords),"keywords集合不合规范!"		 # 检查keywords集合是否符合规范
		print("  - keywords集合符合规范!")
		print("  - 检查每个keyword是否存在可查询的指数...")
		for i in range(len(keywords)):									 # 检查每个keyword是否有效
			for j in range(len(keywords[i])):
				flag = is_keyword_existed(keywords[i][j],cookies)
				print("    + 关键词'{}'有效性: {}".format(keywords[i][j],flag))
				if not flag: keywords[i][j] = str()						 # 置空无效关键词
		keywords = list(filter(None,map(lambda x: list(filter(None,x)),keywords))) # 删除无效关键词与无效关键词组
		print("  - 删除无效关键词后的keywords集合: {}".format(keywords))
		assert days_duration(start_date,end_date)+1<=MAX_DATE_LENGTH,"start_date与end_date间隔不在可行范围内: 0~{}".format(MAX_DATE_LENGTH)
		print("  - 日期间隔符合规范!")
		assert int(area)==0 or int(area) in CODE2PROVINCE,"未知的area!"	 # 检查area是否在CODE2PROVINCE中, 0为全国
		print("  - area编号可以查询!")
		print("参数校验通过!")
		
		# 2. 类构造参数
		self.keywords = keywords										 # 关键词: 二维列表: [["拳皇","街霸"],["英雄联盟","绝地求生"],["围棋","象棋"]]
		self.start_date = start_date									 # 起始日期: yyyy-MM-dd
		self.end_date = end_date										 # 终止日期: yyyy-MM-dd
		self.cookies = cookies											 # cookie信息
		self.area = area												 # 地区编号: 整型数, 默认值全国(0)

		# 3. 类常用参数
		self.index_names = ["search","news","feedsearch"]				 # 被加密的三种指数: 搜索指数, 媒体指数, 资讯指数
		
	def get_decrypt_key(self,uniqid):									 # 根据uniqid请求获得解密密钥
		response = request_with_cookies(API_BAIDU_INDEX_KEY(uniqid),self.cookies)
		json_response = json.loads(response.text)
		decrypt_key = json_response["data"]
		return decrypt_key

	def get_encrypt_data(self,api_name="search"):						 # 三个API接口(搜索指数, 媒体指数, 资讯指数)获取加密响应的方式: api_name取值范围{"search","news","feedsearch"}
		word_list = [[{"name":keyword,"wordType":1} for keyword in group] for group in self.keywords]
		assert api_name in self.index_names,"Expect param 'api_name' in {} but got {} .".format(self.index_names,api_name)
		if api_name=="search":											 # 搜索指数相关参数
			field_name = "userIndexes"
			api = API_SEARCH_INDEX
			kwargs = KWARGS_SEARCH_INDEX
		if api_name=="news":											 # 媒体指数相关参数
			field_name = "index"
			api = API_NEWS_INDEX
			kwargs = KWARGS_NEWS_INDEX
		if api_name=="feedsearch":										 # 资讯指数相关参数
			field_name = "index"
			api = API_FEEDSEARCH_INDEX
			kwargs = KWARGS_FEEDSEARCH_INDEX			

		query_string = kwargs.copy()
		query_string["word"] = json.dumps(word_list)
		query_string["startDate"] = self.start_date
		query_string["endDate"] = self.end_date
		query_string["area"] = self.area
		query_string = urlencode(query_string)
		
		response = request_with_cookies(api(query_string),self.cookies)
		json_response = json.loads(response.text)
		uniqid = json_response["data"]["uniqid"]
		encrypt_data = []
		for data in json_response["data"][field_name]: encrypt_data.append(data)
		return encrypt_data,uniqid

	def get_index(self,index_name="search"):							 # 获取三大指数: 搜索指数, 媒体指数, 资讯指数
		index_name = index_name.lower().strip()
		assert index_name in self.index_names,"Expect param 'index_name' in {} but got {} .".format(self.index_names,index_name)
		encrypt_data,uniqid = self.get_encrypt_data(api_name=index_name)# 获取加密数据与密钥请求参数
		decrypt_key = self.get_decrypt_key(uniqid)						 # 获取解密密钥

		if index_name=="search":										 # 搜索指数区别于媒体指数与资讯指数, 搜索指数分是分SEARCH_MODE的
			for data in encrypt_data:									 # 遍历每个数据
				for mode in SEARCH_MODE:								 # 遍历所有搜索方式解密: ["all","pc","wise"]
					data[mode]["data"] = decrypt_index(decrypt_key,data[mode]["data"])
				keyword = str(data["word"])
				for mode in SEARCH_MODE:								 # 遍历所有搜索方式格式化: ["all","pc","wise"]
					_start_date = datetime.strptime(data[mode]["startDate"],"%Y-%m-%d")
					_end_date = datetime.strptime(data[mode]["endDate"],"%Y-%m-%d")
					dates = []											 # _start_date到_end_date(包头包尾)的日期列表
					while _start_date<=_end_date:						 # 遍历_start_date到_end_date所有日期
						dates.append(_start_date)
						_start_date += timedelta(days=1)
					index_list = data[mode]["data"]
					if len(index_list)==len(dates): print("指数数据长度与日期长度相同!")
					else: print("警告: 指数数据长度与日期长度不一致, 指数一共有{}个, 日期一共有{}个, 可能是因为日期跨度太大!".format(len(index_list),len(dates)))
					for i in range(len(dates)):							 # 遍历所有日期
						try: index_data = index_list[i]					 # 存在可能日期数跟index数匹配不上的情况
						except IndexError: index_data = ""				 # 如果意外匹配不上就置空好了
						output_data = {
							"keyword": [keyword_info["name"] for keyword_info in json.loads(keyword.replace("'",'"'))],
							"type": mode,								 # 标注是哪种设备的搜索指数
							"date": dates[i].strftime('%Y-%m-%d'),
							"index": index_data if index_data else "0",	 # 指数信息
						}
						yield output_data
			
		if index_name in ["news","feedsearch"]:
			for data in encrypt_data:
				data["data"] = decrypt_index(decrypt_key,data["data"])	 # 将data字段替换成解密后的结果
				keyword = str(data["key"])
				_start_date = datetime.strptime(data["startDate"],"%Y-%m-%d")
				_end_date = datetime.strptime(data["endDate"],"%Y-%m-%d")
				dates = []												 # _start_date到_end_date(包头包尾)的日期列表
				while _start_date<=_end_date:							 # 遍历_start_date到_end_date所有日期
					dates.append(_start_date)
					_start_date += timedelta(days=1)
				index_list = data["data"]
				if len(index_list)==len(dates): print("指数数据长度与日期长度相同!")
				else: print("警告: 指数数据长度与日期长度不一致, 指数一共有{}个, 日期一共有{}个, 可能是因为日期跨度太大!".format(len(index_list),len(dates)))		
				for i in range(len(dates)):								 # 遍历所有日期
					try: index_data = index_list[i]						 # 存在可能日期数跟index数匹配不上的情况
					except IndexError: index_data = ""					 # 如果意外匹配不上就置空好了
					output_data = {
						"keyword": [keyword_info["name"] for keyword_info in json.loads(keyword.replace("'",'"'))],
						"date": dates[i].strftime('%Y-%m-%d'),
						"index": index_data if index_data else "0",		 # 没有指数对应默认为0
					}
					yield output_data

if __name__ == "__main__":
	
	pass

2021年2月

02-01

  • 跑步机12km,状态不是很行,跑完已经崩溃了,毕竟休整了两天,一下子上这种量不是很明智。现在全家都抵制我跑步,就怕我以后腿残走不了路,烦的很。
  • 逆转裁判看到复苏的逆转,录播全长有将近800分钟,非常精彩的剧情,没想到video game也能做出让人身临其境的效果,不得不说脚本作者太强了,这可比写一本悬疑小说难多了(得构思好多支线)。
  1. 最近的确没做啥事,简单分享一个python的装饰器numba,可以用来提高代码运行效率,不过看到最近十几天虽然没有写东西,还是有几位小伙伴关注了我,等我看完逆转裁判一定好好学习… [Facepalm]
  • 有兴趣的可以试试,这玩意儿好像还有点用处:
from numba import jit
import random
import time

@jit(nopython=True)
def monte_carlo_pi(nsamples):
	acc = 0
	for i in range(nsamples):
		x = random.random()
		y = random.random()
		if (x*x+y*y)<1.0: acc += 1
	return 4.0*acc/nsamples

@numba.jit(nopython=True,parallel=True)
def logistic_regression(Y,X,w,iterations):
	for i in range(iterations):
		w -= np.dot(((1.0/(1.0+np.exp(-Y*np.dot(X,w)))-1.0)*Y),X)
	return w

def logistic_regression_(Y,X,w,iterations):
	for i in range(iterations):
		w -= np.dot(((1.0/(1.0+np.exp(-Y*np.dot(X,w)))-1.0)*Y),X)
	return w

02-02

  • 禁足令
  1. 闲来无事试了一下b站视频下载,目前倒是可行的,简单说一下方法:
  • 到视频页后刷新监听XHR下面的MP4文件
    • 这里有个小问题,就是并非所有视频都可以监听到mp4文件,有些可能是m4s,有些可能是flv,如果类型那栏看不太出来就去看看文件那栏,总会有文件格式信息的,或者直接筛选媒体而非XHR
      在这里插入图片描述
  • 随意拉开一个看看响应头(为什么会有很多?因为视频总是分块传回前端,否则太大了…)
    • 注意看到下图中蓝色标识出来的content-range: bytes 933-1648/2375632,这意味着这个响应数据流一共有2375632字节,该请求是933-1648字节这块。所以其实只要改成content-range: bytes 933-2375632/2375632就可以得到完整的视频了。
    • 关于2375632的位置一般来说包含在响应头里的一个content-length字段中,不过这里没有,我看了一些其他的可能是会有的,这东西并不严格。

在这里插入图片描述

  • 直接邮件重新编辑发送即可,按照我刚才说的改请求头发送一次即可👇

在这里插入图片描述

  • 一些细节就不多说了,点开响应会看到一些大写英文字母的排列,那是base64编码的结果,可以用pythonbase64库中base64.bs64decode将它们解码为字节流再用wb模式写入mp4文件即可。不过有些大的视频可能会传输的很慢一般是很难下载得下来的,因为总会失败,不嫌烦可以分块下载,或者去试试自己写一个多线程的下载器,这玩意儿还挺有趣的。
  • 至于怎么拿到请求的URL,我看了一阵子js也没找到相关的脚本,如果想写爬虫的话可能还是比较困难,得彻底搞清楚这个复杂的请求url是怎么来的,不过倒是可以写点浏览器内嵌的js来协助,这应该会相对简单。油猴上面是有用户脚本的,不过也并非万能,毕竟b站数以亿计的视频,不可能用一种方法能全部搞定的,总有很多情况不适用于这种方法,我其实看油猴里有些人做了一些好玩的工作,比如跳过大会员限制看番之类的,确实还挺有趣~

02-03

  • 注意到某人换回了捣蛋鹅的头像,还有的同学似乎刚走。
  • 前两天每日12km,今天6km,大致如此,感觉左膝伤一直在,不敢路跑长距离,痛苦的休整期。
  • 吹爆逆转裁判,名侦探柯南到底还是逊色了一些。
  1. adb关键事件(一)
电话键
KEYCODE_CALL 		拨号键 		5
KEYCODE_ENDCALL 	挂机键 		6
KEYCODE_HOME 		按键Home 	3
KEYCODE_MENU 		菜单键 		82
KEYCODE_BACK 		返回键 		4
KEYCODE_SEARCH 		搜索键 		84
KEYCODE_CAMERA 		拍照键 		27
KEYCODE_FOCUS 		拍照对焦键 	80
KEYCODE_POWER 		电源键 		26
KEYCODE_NOTIFICATION 	通知键 		83
KEYCODE_MUTE 		话筒静音键 	91
KEYCODE_VOLUME_MUTE 	扬声器静音键 	164
KEYCODE_VOLUME_UP 	音量增加键 	24
KEYCODE_VOLUME_DOWN 	音量减小键 	25

02-04

  • 最近在做一些收集语料的工作,以备后用。
  • 感觉巧舟回归后,逆6没有前三部那个味道了,加了美贯,王泥喜,心音后太乱了,而且逆转前三部差不多是把坑都填了,是一个完整的故事链,后续再强行开篇章没有之前的感觉了。看完逆3最后一章华丽的逆转,除了震惊就是震惊。
  1. dot命令概述及使用方法:
  • 命令路径: E:\Graphviz\bin\dot.exe;
    • 注意没有添加到系统环境变量中, 只添加在用户caoyang的环境变量中, 所以调用pythonos.system('dot')方法将无效, 可以修改为os.system(r'E:\Graphviz\bin\dot.exe')即可;
  • 调用方法:
    • 方法一: 常规shell脚本调用;
      • 将下面的代码保存到test.dot文件中后, 在同级目录下执行指令dot -Tpng -o test.png test.dot即可在同级目录下生成test.png图片;
        digraph G {
        # 定义全局属性
        	fontname = "Courier New"
        	fontsize = 8
        	# 从下往上
        	rankdir = BT
        # 定义节点属性
        	node [
        			shape = "record" # 矩形,默认是椭圆
        			color = "blue" # 边框蓝色
        	]
        # 定义边的属性
        	edge [
        			fontsize = 9
        	]
        # 换行符是\l,而要新建一个新的单元格,则需要是用|。{}里面的是内容
        	Reportable [
        		label = "{Reportable | + getSummary() : Map\<String, Integer\> | + getDetail() : Map\<String, Integer\> | + isDetailVisible() : boolean}"
        	]
        # 特殊字符要转义
        	LineCounter [
        		label = "{LineCounter | + count(String line) : boolean | + getType() : String}"
        	]
        
        	CharCounter [
        		label = "{CharCounter | + count(Character c) : boolean | + getType() : String}"
        	]
        
           AbstractCharCounter [
        		label = "{AbstractCharCounter | characterMap : Map\<Character, Integer\> | + count(Character c) : boolean | + getSummary() : Map\<String, Integer\> | +getDetail() : Map\<String, Integer\> }"
        	]
           
           AbstractLineCounter [
        		label = "{AbstractLineCounter |  + count(String line) : boolean | + getSummary() : Map\<String, Integer\> | +getDetail() : Map\<String, Integer\> }"
        	]
        
           PredicateCharacter[label = "{Predicate\<Character\> | + apply(Character c) : boolean}"]
           PredicateString[label = "{Predicate\<String\> | + apply(String line) : boolean}"]
        
           BlankCharCounter[label = "{BlankCharCounter | + apply(Character c) : boolean | + getType() : String | + isDetailVisible() : boolean }"]
           ChineseCharCounter[label = "{ChineseCharCounter | - chinesePattern : Pattern | + apply(Character c) : boolean | + getType() : String | + isDetailVisible() : boolean }"]
           LetterCharCounter[label = "{LetterCharCounter | - chinesePattern : Pattern | + apply(Character c) : boolean | + getType() : String | + isDetailVisible() : boolean }"]
        	NumberCharCounter[label = "{NumberCharCounter | + apply(Character c) : boolean | + getType() : String | + isDetailVisible() : boolean }"]
        	LineNumberCounter[label = "{LineNumberCounter | + apply(Character c) : boolean | + getType() : String | + isDetailVisible() : boolean }"]
        	
        	parentInterface [label = "parent interface" color = "green" style=filled]
        	childInterface [label = "child interface" color = "green" style=filled]
        	abstractClass [ label = "abstract class : implement some methods using the abstract methods" color = "green" style=filled]
        	specificClass [ label = "specific class : implement all unimplemented methods" color = "green" style=filled]
        
        	LineProcessor [label = "{LineProcessor\<List\<Reportable\>\>}" ]
        	ReportableLineProcessor [ label = "{ReportableLineProcessor | + ReportableLineProcessor() | + processsLine(String line) : boolean | + getResult() : List\<Reportable\>}"]
        
        	# 定义在同一层
        	{rank = same; parentInterface; Reportable; LineProcessor}
        	{rank = same; childInterface; LineCounter; CharCounter; PredicateCharacter; PredicateString}
        	{rank = same; abstractClass; AbstractLineCounter; AbstractCharCounter;}
        	{rank = same; specificClass; LineNumberCounter; BlankCharCounter; ChineseCharCounter; LetterCharCounter; NumberCharCounter; ReportableLineProcessor}
        	
           # 箭头为空心,接口之间的继承
           LineCounter -> Reportable[arrowhead="empty"]
           CharCounter -> Reportable[arrowhead="empty"]
           AbstractCharCounter -> CharCounter[arrowhead="empty"]
           AbstractLineCounter -> LineCounter[arrowhead="empty"]
        
           AbstractCharCounter -> PredicateCharacter[arrowhead="empty"]
           AbstractLineCounter -> PredicateString[arrowhead="empty"]
        
           # 实现类的UML
           BlankCharCounter -> AbstractCharCounter[arrowhead="empty", style="dashed"]
           ChineseCharCounter -> AbstractCharCounter[arrowhead="empty", style="dashed"]
           LetterCharCounter -> AbstractCharCounter[arrowhead="empty", style="dashed"]
           NumberCharCounter -> AbstractCharCounter[arrowhead="empty", style="dashed"]
           LineNumberCounter -> AbstractLineCounter[arrowhead="empty", style="dashed"]
        
           ReportableLineProcessor -> LineProcessor[arrowhead="empty", style="dashed"]
        }
        
    • 方法二: python脚本中的调用;
      • 首先使用os.popen()函数打开一个管道: f = popen(r"E:\Graphviz\bin\dot -Tpng -o %s.png" % file, 'w');
      • 将方法一中test.dot文件中的所有代码以字符串的形式写入管道: f.write(dot_command);
      • 最后关闭文件: f.close()

02-05

  • GCX今天早上的飞机去的加拿大,大约gap了半年,最终还是出去了,听到这个消息都无感了。
  • 烦躁,腿伤未愈。
  1. 小脚本
# -*- coding: UTF-8 -*-
# Author: Cao Yang
# 酷我音乐爬虫模块

import os
import sys
import math
import time
import json
import random
import base64
import codecs

sys.path.append("../")													 # 导入上级目录

from requests import Session
from bs4 import BeautifulSoup
from Crypto.Cipher import AES											 # 这个库安装的话直接安装pycryptodome, 如果安装Crypto会有些不友好的问题
from selenium import webdriver
from selenium.webdriver.common.keys import Keys
from selenium.webdriver.support.ui import WebDriverWait
from selenium.webdriver.common.action_chains import ActionChains

from FC_crawl import Crawl
from FC_utils import *


class KuWo(Crawl):														 # 酷我音乐爬虫

	def __init__(self):
		
		Crawl.__init__(self)											 # 父类继承

		# 类常用参数
		self.url_main = "http://www.kuwo.cn/"							 # 网易云音乐首页
		self.headers = {"User-Agent": self.user_agent}					 # 请求头伪装信息
		self.url_api = self.url_main + "url"							 # 请求歌曲链接的接口
		self.api_params = {												 # 接口调用参数
			"format": "mp3",											 # 返回格式
			"rid": None,												 # 歌曲编号
			"response": "url",											 # 返回变量
			"type": "convert_url3",										 # 返回类型
			"br": "128kmp3",											 # 返回歌曲质量
			"from": "web",												 # 请求来源
			"t": None,													 # 时间戳
			"reqId": "",												 # 关于这个字段的生成我目前细究, 因为目前不带这个字段也是可行的
		}
		self.url_song = "http://www.kuwo.cn/play_detail/{}"				 # 歌曲页面链接
		
		# 类初始化操作
		self.renew_session()											 # 生成新的session对象

	def renew_session(self):											 # 重构
		self.session = Session()										 # 创建新的Session对象
		self.session.headers = self.headers.copy()						 # 伪装头部信息
		self.session.get(self.url_main)									 # 访问主页

	def search_for_song_id(self,song_name,driver,
		n_result=1,														 # 返回多少个查询结果
	):	
		pass

	def download_by_song_id(self,song_id,								 # 给定歌曲编号
		save_path=None,													 # 歌曲下载保存路径
		driver=None,
	):																	 # 通过歌曲编号下载歌曲
		song_url = self.request_for_song_url(song_id,driver=driver)		 # 获取歌曲链接
		r = self.session.get(song_url)									 # 访问歌曲链接
		if save_path is None: save_path = "kuwo_{}".format(song_id)		 # 默认的保存路径
		with open(save_path,"wb") as f: f.write(r.content)				 # 写入音乐文件

	def request_for_song_url(self,song_id,
		driver=None,
	):																	 # 请求歌曲链接
		params = self.api_params.copy()									 # 获取请求字符串
		params["rid"] = song_id											 # 设置歌曲编号
		params["t"] = int(time.time()*1000)								 # 设置时间戳							
		r = self.session.get(self.url_api,params=params)				 # 发出播放请求
		print(r.text)												
		song_url = json.loads(r.text)["url"]							 # 这里用eval不好使, 因为有python无法识别为缺失值的null
		return song_url

	def test(self):
		song_id = "90785562"
		r = self.download_by_song_id(
			song_id,
			save_path="kuwo_{}.mp3".format(song_id),
			driver=None,
		)

if __name__ == "__main__":
	kw = KuWo()
	kw.test()

02-06

  • 周跑54km,明天还有一天,目测回家一个月的月跑量能上到230km左右。
  • CSDN又犯病了。
  1. functools杂记
  • functools.reduce: 将列表中所有元素连乘
    • 代码示例:
      from functools import reduce
      lt = [1, 2, 3, 4, 5]
      ln = reduce(lambda x, y: x * y, lt)
      print(ln)
      
      • 输出结果: 120;

02-07

  • 挣扎了很多天终于回归了正轨。
  • 今天又有人跟我提GCX去加拿大的事情,可能是我墨守成规,我总是觉得女生没有必要在这种局势下去那么远的地方留学,别人不知道,我怎么会不清楚,gcx本科四年显然也没有尽全力,会院去加拿大的都是较次的一批学生了。本科四年,改变我们太多太多,有些从前再也回不去了。
  • 昨日10km后腿伤加剧,右膝有刺痛感,还想着年前能跑一次20km,还是太年轻了。
  1. 百度翻译2021最新的解密方法已经找到,近期更新思路,先给出核心脚本:
# -*- coding: UTF-8 -*-
# @author: caoyang
# @email: caoyang@163.sufe.edu.cn

import json
import execjs
import requests
from bs4 import BeautifulSoup
from urllib.parse import urlencode

class BaiduFanyi(object):
	""""""
	def __init__(self) -> None:
		javascript_code = '''function n(r,o){for(var t=0;t<o.length-2;t+=3){var a=o.charAt(t+2);a=a>="a"?a.charCodeAt(0)-87:Number(a),a="+"===o.charAt(t+1)?r>>>a:r<<a,r="+"===o.charAt(t)?r+a&4294967295:r^a}return r}var i=null;function e(r,gtk){var o=r.match(/[\uD800-\uDBFF][\uDC00-\uDFFF]/g);if(null===o){var t=r.length;t>30&&(r=""+r.substr(0,10)+r.substr(Math.floor(t/2)-5,10)+r.substr(-10,10))}else{for(var e=r.split(/[\uD800-\uDBFF][\uDC00-\uDFFF]/),C=0,h=e.length,f=[];h>C;C++)""!==e[C]&&f.push.apply(f,a(e[C].split(""))),C!==h-1&&f.push(o[C]);var g=f.length;g>30&&(r=f.slice(0,10).join("")+f.slice(Math.floor(g/2)-5,Math.floor(g/2)+5).join("")+f.slice(-10).join(""))}var u=void 0,l=""+String.fromCharCode(103)+String.fromCharCode(116)+String.fromCharCode(107);u=null!==i?i:(i=gtk||"")||"";for(var d=u.split("."),m=Number(d[0])||0,s=Number(d[1])||0,S=[],c=0,v=0;v<r.length;v++){var A=r.charCodeAt(v);128>A?S[c++]=A:(2048>A?S[c++]=A>>6|192:(55296===(64512&A)&&v+1<r.length&&56320===(64512&r.charCodeAt(v+1))?(A=65536+((1023&A)<<10)+(1023&r.charCodeAt(++v)),S[c++]=A>>18|240,S[c++]=A>>12&63|128):S[c++]=A>>12|224,S[c++]=A>>6&63|128),S[c++]=63&A|128)}for(var p=m,F=""+String.fromCharCode(43)+String.fromCharCode(45)+String.fromCharCode(97)+(""+String.fromCharCode(94)+String.fromCharCode(43)+String.fromCharCode(54)),D=""+String.fromCharCode(43)+String.fromCharCode(45)+String.fromCharCode(51)+(""+String.fromCharCode(94)+String.fromCharCode(43)+String.fromCharCode(98))+(""+String.fromCharCode(43)+String.fromCharCode(45)+String.fromCharCode(102)),b=0;b<S.length;b++)p+=S[b],p=n(p,F);return p=n(p,D),p^=s,0>p&&(p=(2147483647&p)+2147483648),p%=1e6,p.toString()+"."+(p^m)}'''
		self.javascript_lambda = execjs.compile(javascript_code)
		self.headers = {
			'Host'						: 'fanyi.baidu.com',
			'User-Agent'				: 'Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:82.0) Gecko/20100101 Firefox/82.0',
			'Accept'					: 'text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8',
			'Accept-Language'			: 'zh-CN,zh;q=0.8,zh-TW;q=0.7,zh-HK;q=0.5,en-US;q=0.3,en;q=0.2',
			'Accept-Encoding'			: 'gzip, deflate, br',
			'Connection'				: 'keep-alive',
			'Cookie'					: 'BAIDUID=57D8DECD1001EDF4A260905A983072A9:FG=1; BIDUPSID=57D8DECD1001EDF4A260905A983072A9; PSTM=1612680499; BDRCVFR[gltLrB7qNCt]=mk3SLVN4HKm; delPer=0; PSINO=5; H_PS_PSSID=33425_33355_33273_33585; BA_HECTOR=2g8g240g0h00ak24451g1v39k0r; BDORZ=FFFB88E999055A3F8A630C64834BD6D0; Hm_lvt_64ecd82404c51e03dc91cb9e8c025574=1612680504,1612680509; Hm_lpvt_64ecd82404c51e03dc91cb9e8c025574=1612680509; __yjs_duid=1_57795229af6fbff1bde0f88f5beda8381612680504436; ab_sr=1.0.0_ZGQxNTEyYzNmYmM3YzA3ODgxMTIzNzhkNTQ2MDg4ODU2ZDAxODNlODQxZjJlYzdkNDNhNjhlYjIyNWNlZjIxNmIzOTE2YzgxNjJjMTExMzlkMWY5NWQzOTUxMTkzYWZi; __yjsv5_shitong=1.0_7_f89862c9f80b86296408413c2a5c443713a1_300_1612680509895_49.95.205.54_60776bff; REALTIME_TRANS_SWITCH=1; FANYI_WORD_SWITCH=1; HISTORY_SWITCH=1; SOUND_SPD_SWITCH=1; SOUND_PREFER_SWITCH=1',
			'Upgrade-Insecure-Requests'	: '1',
		}
		self.mainpage_url = 'https://fanyi.baidu.com/'
		self.transapi_url = 'https://fanyi.baidu.com/v2transapi'
		self.result = None
		
	def v2transapi(self, keyword: str, source: str='en', target: str='zh') -> dict:		
		session = requests.Session()
		session.headers = self.headers.copy()
		'''
		response = session.get(self.mainpage_url, headers=self.headers)
		html = response.text
		with open('baidufanyi.html', 'w', encoding='utf8') as f:
			f.write(html)
		'''
		with open('baidufanyi.html', 'r', encoding='utf8') as f:
			html = f.read()
		def _find_token_and_gtk(html: str) -> (str, str):
			soup = BeautifulSoup(html, 'lxml')
			scripts = soup.find_all('script')
			script_code = '''var window={};try{'''
			for script in scripts:
				_script_code = str(script.string).strip('\n')
				if _script_code.startswith('window'):
					script_code += _script_code + ''';'''
			script_code += '''}catch(e){}'''
			window = execjs.compile(script_code).eval('window')
			token = window['common']['token']
			gtk = window['gtk']
			return token, gtk
		token, gtk = _find_token_and_gtk(html)
		formdata = {
			'from'				: source,
			'to'				: target,
			'query'				: keyword,
			'simple_means_flag'	: '3',
			'sign'				: self.javascript_lambda.call('e', keyword, gtk),
			'token'				: token,
			'domain'			: 'common',
		}
		# print(urlencode(formdata))
		response = session.post(self.transapi_url, data=formdata)
		result = response.json()
		with open('transapi_{}.json'.format(keyword), 'w', encoding='utf8') as f:
			json.dump(result, f)		
		return result

02-08

  • 发现某人很有趣,礼尚往来。
  • 精心写了一篇博客作为年关收尾,不过确实是一个挺不错的爬虫训练。
  • 20km跑,终会达成。
  1. Linux的一些杂记:
ssh root@101.132.103.34 				连接远程服务器
ls -a 									查看当前目录下的文件与文件夹
cd <文件夹> 							进入文件夹
cd .. 									返回上级目录
rm <文件名> 							删除文件
rm -f <文件名>							强制删除文件
rm -r <文件夹名>						删除文件夹

mkdir 目录名        (新建一个文件夹,文件夹在Linux系统中叫做“目录”)
touch 文件名        (新建一个空文件)
rmdir 目录名        (删除一个空文件夹,文件夹里有内容则不可用)
rm -rf 非空目录名 (删除一个包含文件的文件夹)
rm 文件名 文件名 (删除多个文件)
cp 文件名 目标路径(拷贝一个文件到目标路径,如cp hserver /opt/hqueue)
cp -i        (拷贝,同名文件存在时,输出 [yes/no] 询问是否执行)
cp -f        (强制复制文件,如有同名不询问)

dpkg -S 软件名		(用apt安装的软件目录)

ps aux	查看所有进程

crontab:
SHELL=/bin/bash
PATH=/sbin:/bin:/usr/sbin:/usr/bin
MAILTO=root
HOMR=/
0 18 * * 1-5 root /root/智能投顾小信/Update/Beginner.py

nohup python -u nndial_interact.py >nndial.log 2>&1 &tail -f nndial.log
nohup python -u Beginner.py >Beginner.log 2>&1 &tail -f Beginner.log
nohup python -u manage.py runserver 0.0.0.0:8000 >manage.log 2>&1 &tail -f manage.log
nohup python -u CSDN.py >CSDN.log 2>&1 &tail -f CSDN.log
nohup python3.6 CSDN.py >CSDN.log 2>&1 &

查看磁盘占用
du -sh
du -h
du -h <文件夹名>

查看内存占用
free -h

任务管理器
top

绝对路径
/root/..	在前面添加一个斜杠则默认为绝对路径

查看系统位数
sudo uname --m


anaconda  创建 环境:conda create -n py2 python=2.7

无法使用conda命令怎么办:输入上面两句即可
export PATH="/root/anaconda3/bin:PATH" >> ~/.bashrc
source ~/.bashrc
conda --version

改变python版本
alias python="/root/anaconda3/bin/python3.6"

查看文件管理系统: df

02-09

  • 王英林给发了PIC的会务费,出手还挺豪爽的,没想到会给这么多… 先礼后兵,然后就让我考虑一下IUI的研究,就很让人绝望,我怎么知道这玩意儿怎么搞,大过年的,不会是这时候要动工吧。
  • 现在有时候感觉膝盖头是肿的,有时候又什么感觉都没有,挤按左膝头还是会疼,真的是恢复不了了吗,天天跑步机实在是没有长进。
  1. ADB关键事件(二)
控制键
KEYCODE_ENTER 		回车键 			66
KEYCODE_ESCAPE 		ESC键 			111
KEYCODE_DPAD_CENTER 	导航键 确定键 		23
KEYCODE_DPAD_UP 	导航键 向上 		19
KEYCODE_DPAD_DOWN 	导航键 向下 		20
KEYCODE_DPAD_LEFT 	导航键 向左 		21
KEYCODE_DPAD_RIGHT 	导航键 向右 		22
KEYCODE_MOVE_HOME 	光标移动到开始键 	122
KEYCODE_MOVE_END 	光标移动到末尾键 	123
KEYCODE_PAGE_UP 	向上翻页键 		92
KEYCODE_PAGE_DOWN 	向下翻页键 		93
KEYCODE_DEL 		退格键 			67
KEYCODE_FORWARD_DEL 	删除键 			112
KEYCODE_INSERT 		插入键 			124
KEYCODE_TAB 		Tab键 			61
KEYCODE_NUM_LOCK 	小键盘锁 		143
KEYCODE_CAPS_LOCK 	大写锁定键 		115
KEYCODE_BREAK 		Break/Pause键 		121
KEYCODE_SCROLL_LOCK 	滚动锁定键 		116
KEYCODE_ZOOM_IN 	放大键 			168
KEYCODE_ZOOM_OUT 	缩小键 			169

02-10

  • 电灯泡逛了一天街,晚上回来以为酒饱饭足可以大跑一通,结果8km就跪了,可能还是白天逛街脚走得酸了,年前破20km的梦想就此幻灭,遗憾透顶。
  • 金钱会使人膨胀,金钱也会使人毁灭。
  • 老妈偷看了我的日记,知道我的左右膝盖都在痛,之前一直瞒着她,但是今晚她还是陪我一起出门跑,老妈问我为什么执着于要跑半马,也许没什么原因,就是想去跑,很朴素的愿望。
  1. java.lang.Thread类
  • 构造函数:
    • Thread(Runnable target)
    • Thread(String string)
  • 方法:
    • .sleep()——停止
    • .yield()——同级别线程上位
    • .join()——等待直到当前线程结束
    • .interrupt()
    • .currentThread()——返回当前线程的引用
    • .isAlive()——是否运行
    • 线程交互wait()与notify()、notifuAll()
    • .stop():强制结束
    • .suspend()与.resume()——暂停执行再次运行

02-11

  • 大年三十,一天奔波,疲累不堪。
  • 不久就快开学了,假期余额又不足了,唉。
  1. R语言中字符串处理小结
如果是连接字符串最快的方法用library(stringr)->str_c("A","B")->"AB"

    x <- c("Hellow", "World", "!")
    nchar(x)
    [1] 6 5 1
    length("")
    [1] 1
    nchar("")
    [1] 0
	
    DNA <- "AtGCtttACC"
    tolower(DNA)
    ## [1] "atgctttacc"
    toupper(DNA)
    ## [1] "ATGCTTTACC"
    chartr("Tt", "Uu", DNA)
    ## [1] "AuGCuuuACC"
    chartr("Tt", "UU", DNA)
    ## [1] "AUGCUUUACC"
	
paste("CK", 1:6, sep = "")
## [1] "CK1" "CK2" "CK3" "CK4" "CK5" "CK6"
x <- list(a = "aaa", b = "bbb", c = "ccc")
y <- list(d = 1, e = 2)
paste(x, y, sep = "-")  #较短的向量被循环使用
## [1] "aaa-1" "bbb-2" "ccc-1"
z <- list(x, y)
paste("T", z, sep = ":")
## [1] "T:list(a = \"aaa\", b = \"bbb\", c = \"ccc\")"
## [2] "T:list(d = 1, e = 2)"

    as.character(x)
    ## [1] "aaa" "bbb" "ccc"
    as.character(z)
    ## [1] "list(a = \"aaa\", b = \"bbb\", c = \"ccc\")"
    ## [2] "list(d = 1, e = 2)"
    paste函数还有一个用法,设置collapse参数,连成一个字符串:
     
    paste(x, y, sep = "-", collapse = "; ")
    ## [1] "aaa-1; bbb-2; ccc-1"
    paste(x, collapse = "; ")
    ## [1] "aaa; bbb; ccc"
	
text <- "Hello Adam!\nHello Ava!"
strsplit(text, " ")
## [[1]]
## [1] "Hello"        "Adam!\nHello" "Ava!"
R语言的字符串事实上也是正则表达式,上面文本中的\n在图形输出中是被解释为换行符的。
 
strsplit(text, "\\s")
## [[1]]
## [1] "Hello" "Adam!" "Hello" "Ava!"
strsplit得到的结果是列表,后面要怎么处理就得看情况而定了:
 
class(strsplit(text, "\\s"))
## [1] "list"
有一种情况很特殊:如果split参数的字符长度为0,得到的结果就是一个个的字符:
 
strsplit(text, "")
## [[1]]
##  [1] "H"  "e"  "l"  "l"  "o"  " "  "A"  "d"  "a"  "m"  "!"  "\n" "H"  "e" 
## [15] "l"  "l"  "o"  " "  "A"  "v"  "a"  "!"

02-12

  • 大年初一,与服役的表哥长聊了一次,有所感。
  1. 使用EXCEL的小TIP
    (一)交叉合并两列数据
    把需要交叉合并的数据分别放在A列和B列
    在任意非A, B列中的单元格写入公式:
    =OFFSET(A$1,INT((ROW(A1)-1)/2),MOD(ROW(A1)-1,2))
    再一路拉下去即可

02-13

  • 大年初二,迎财神。
  • wordnet用了一下,这玩意儿居然还有个exe的版本,还挺不错的,有空研究一下。
  1. matlab绘图:
例 用不同的线型和颜色在同一坐标内绘制曲线 及其包络线。
>> x=(0:pi/100:2*pi)';
>> y1=2*exp(-0.5*x)*[1,-1];
>> y2=2*exp(-0.5*x).*sin(2*pi*x);
>> x1=(0:12)/2;
>> y3=2*exp(-0.5*x1).*sin(2*pi*x1);
>> plot(x,y1,'k:',x,y2,'b--',x1,y3,'rp');

02-14

  • 回家后的第一天不跑步,腿伤的太重了,不敢再强行起跑了,连跑步机都不敢上,这样一来回家4个整周,跑量分别为52km,56km,62km,26km,累积勉强接近200km的目标。
  • 下午一家去凤凰岛玩了半天,好久没一家三口出来玩了,人很多,才发现是情人节。
  1. pywin32库一些测试:
import win32gui,win32ui,win32con
'''
def get_windows(windowsname,filename):
    # 获取窗口句柄
    handle = win32gui.FindWindow(None,windowsname)
    # 将窗口放在前台,并激活该窗口(窗口不能最小化)
    win32gui.SetForegroundWindow(handle)
    # 获取窗口DC
    hdDC = win32gui.GetWindowDC(handle)
    # 根据句柄创建一个DC
    newhdDC = win32ui.CreateDCFromHandle(hdDC)
    # 创建一个兼容设备内存的DC
    saveDC = newhdDC.CreateCompatibleDC()
    # 创建bitmap保存图片
    saveBitmap = win32ui.CreateBitmap()

    # 获取窗口的位置信息
    left, top, right, bottom = win32gui.GetWindowRect(handle)
    # 窗口长宽
    width = right - left
    height = bottom - top
    # bitmap初始化
    saveBitmap.CreateCompatibleBitmap(newhdDC, width, height)
    saveDC.SelectObject(saveBitmap)
    saveDC.BitBlt((0, 0), (width, height), newhdDC, (0, 0), win32con.SRCCOPY)
    saveBitmap.SaveBitmapFile(saveDC, filename)

get_windows("PyWin32","截图.png")
'''

'''
import time
import win32gui,win32con
import keyboardEmulation as ke

def get_windows(windowsname,filename):
    # 获取窗口句柄
    hwnd = win32gui.FindWindow(None,windowsname)
    # 将窗口放在前台,并激活该窗口
    win32gui.SetForegroundWindow(hwnd)

    # 输入helloworld

    scancodes = [0x23, 0x12, 0x26, 0x26, 0x18, 0x11, 0x18, 0x13, 0x26, 0x20, 0x2a]

    for code in  scancodes:
        ke.key_press(code)

    # 保存
    ke.key_down(0x1d)
    ke.key_down(0x1f)
    ke.key_up(0x1d)
    ke.key_up(0x1f)

    # 关闭窗口

    time.sleep(1);
    win32gui.PostMessage(hwnd, win32con.WM_CLOSE, 0, 0)

get_windows("新建文本文档 (2).txt - 记事本","截图.png")
'''


import win32gui
import win32process
import win32con
import win32api
import ctypes
 
PROCESS_ALL_ACCESS  =  ( 0x000F0000 | 0x00100000 | 0xFFF )
hwnd = win32gui.FindWindow(0, "Form1")
hid,pid = win32process.GetWindowThreadProcessId(hwnd)
phand = win32api.OpenProcess(PROCESS_ALL_ACCESS, False, pid)
date = ctypes.c_long()
mydll = ctypes.windll.LoadLibrary("C:\Windows\System32\kernel32.dll") 
mydll.ReadProcessMemory(int(phand) ,0x0039A778,ctypes.byref(date),4,None)
print (date.value)

02-15

  1. MBAlib
#-*- coding:UTF-8 -*-
import os
import re
import sys
import time
import numpy
import random
import pandas
import requests

from bs4 import BeautifulSoup,element
from selenium import webdriver
from multiprocessing import Process


class MBAlib():
	def __init__(self,
		userAgent="Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/71.0.3578.98 Safari/537.36",
		phantomPath="E:/Python/phantomjs-2.1.1-windows/bin/phantomjs.exe",
		driver=True,	):
		""" 设置类构造参数 """
		self.userAgent = userAgent										 # 设置浏览器用户代理
		self.phantomPath = phantomPath
		""" 设置类常用参数 """
		self.workspace = os.getcwd()									 # 获取当前工作目录
		self.date = time.strftime("%Y%m%d")								 # 获取类初始化的时间
		self.labelCompiler = re.compile(r"<[^>]+>",re.S)				 # 标签正则编译
		self.entryCount = 0												 # 记录已经爬取的条目数量
		self.mainURL = "https://www.mbalib.com/"						 # MBA首页URL
		self.wikiURL = "https://wiki.mbalib.com/"						 # MBA百科URL
		self.searchURL = self.mainURL + "s?q={}"						 # MBA搜索URL
		self.wikisearchURL = self.wikiURL + "wiki/Special:Search?search={}&fulltext=搜索"
		self.entryURL = self.wikiURL + "wiki/{}"						 # MBA条目URL
		self.indexURL = self.wikiURL + "wiki/MBA智库百科:分类索引"			 # MBA词条分类索引页面URL
		self.categoryURL = self.wikiURL + "wiki/Category:{}"			 # MBA词条类别页面URL
		self.session = requests.Session()								 # 类专用session
		self.myHeaders = {"User-Agent":self.userAgent}					 # 爬虫头信息
		self.imageFolder = "image"										 # 存放图片词条的子文件夹
		self.entryFolder = "entry"										 # 存放词条文本的子文件夹
		self.logFolder = "log"											 # 存放程序运行的子文件夹
		self.log = "{}/log.txt".format(self.logFolder)					 # 记录文件
		self.categoryWait = "{}/categoriyWait.txt".format(self.logFolder)# 记录队列中所有的类别文件
		self.categoryDone = "{}/categoriyDone.txt".format(self.logFolder)# 记录已经获取完毕的类别文件
		if driver: self.webdriver = webdriver.Firefox()					 # 初始化火狐浏览器

		""" 初始化操作 """
		self.session.headers = self.myHeaders							 # 设置session
		self.session.get(self.mainURL)									 # 定位主页
		if not os.path.exists("{}\\{}".format(self.workspace,self.date)):# 每个交易日使用一个单独的文件夹(用日期命名)存储金融数据
			print("正在新建文件夹以存储{}MBA智库的数据...".format(self.date))
			os.mkdir("{}\\{}".format(self.workspace,self.date))
			os.mkdir("{}\\{}\\{}".format(self.workspace,self.date,self.imageFolder))
			os.mkdir("{}\\{}\\{}".format(self.workspace,self.date,self.entryFolder))
			os.mkdir("{}\\{}\\{}".format(self.workspace,self.date,self.logFolder))

	def parse_entries(self,entries,
		driver=False,
		cycle=100,
		minInterval=60,
		maxInterval=90,
	):																	 # 给定词条列表获取它们所有的信息		
		for entry in entries:
			if driver:
				self.webdriver.get(self.entryURL.format(entry))
				html = self.webdriver.page_source
			else:
				html = self.session.get(self.entryURL.format(entry)).text
				self.entryCount += 1
				if self.entryCount%cycle==0:
					interval = random.uniform(minInterval,maxInterval)
					time.sleep(interval)
					print("获取到第{}个条目 - 暂停{}秒...".format(self.entryCount,interval))
			string = "正在获取条目{}...".format(entry)
			print(string)
			with open("{}/{}".format(self.date,self.log),"a") as f:
				f.write("{}\t{}\n".format(string,time.strftime("%Y-%m-%d %H:%M:%S")))
			soup = BeautifulSoup(html,"lxml")
			if entry[:6]=="Image:":										 # 图片词条保存为图片
				img = soup.find("img")
				imageURL = self.wikiURL + img.attrs["src"][1:]
				byte = self.session.get(imageURL).text
				with open("{}/{}/{}".format(self.date,self.imageFolder,entry[6:]),"wb") as f: f.write(byte)
			else:														 # 非图片词条处理相对麻烦
				for script in soup.find_all("script"): script.extract()	 # 删除脚本
				p = soup.find("p")										 # 从第一个<p>标签的前一个标签开始搞
				label = p if isinstance(p.previous_sibling,element.NavigableString) else p.previous_sibling  
				text = str()											 # 最终text被写入文本
				while True:
					if isinstance(label,element.NavigableString):		 # 下一个平行节点有可能是字符串, 那就扔了
						label = label.next_sibling
						continue
					if not label: break									 # 没有下一个平行节点, 那就再见
					if "class" in label.attrs.keys() and \
					   label.attrs["class"][0]=="printfooter": break	 # 页脚处与空标签处暂停
					string = self.labelCompiler.sub("",str(label))
					text += "{}\n".format(string.strip())
					label = label.next_sibling
				_nCompiler = re.compile(r"\n+",re.S)
				text = _nCompiler.sub("\n",text)
				with open("{}/{}/{}.txt".format(self.date,self.entryFolder,entry),"a",encoding="UTF-8") as f:
					f.write(text)
				
	def parse_categories(self,categories):								 # 以参数列表中的条目为根目录, 穿透根目录就完事了
		with open("{}/{}".format(self.date,self.categoryWait),"a") as f: # 将传入的所有类别记录到categoryWait.txt中
			for category in categories: f.write("{}\n".format(category)) 
		for category in categories:
			with open("{}/{}".format(self.date,self.categoryDone),"a") as f: f.write("{}\n".format(category)) 			
			subCategories = list()										 # 存储该category下的所有亚类
			entries = list()											 # 存储该category下的所有条目
			html = self.session.get(self.categoryURL.format(category)).text
			count = 0
			while True:
				flag = True												 # flag用于判断是否还存在下一页
				count += 1
				soup = BeautifulSoup(html,"lxml")
				divs1 = soup.find_all("div",class_="CategoryTreeSection")# 子类超链接
				divs2 = soup.find_all("div",class_="page_ul")			 # 条目超链接
				if divs1:
					for div in divs1:
						aLabel = div.find_all("a")
						for a in aLabel:
							if str(a.string)=="+": continue				 # "+"号是一个特殊的书签超链接
							subCategories.append(str(a.string))
				if divs2:
					for div in divs2:
						aLabel = div.find_all("a")
						for a in aLabel:
							if a.attrs["title"][:6]=="Image:": entries.append("Image:{}".format(a.string))
							else: entries.append(str(a.string))
				string = "正在处理{}类别第{}页的信息 - 共{}个子类{}个条目...".format(category,count,len(subCategories),len(entries))
				print(string)
				with open("{}/{}".format(self.date,self.log),"a") as f:
					f.write("{}\t{}\n".format(string,time.strftime("%Y-%m-%d %H:%M:%S")))	
				buttons = soup.find_all("a",title="Category:{}".format(category))
				if buttons:												 # 可能并没有这个按钮
					for button in buttons:
						if str(button.string)[0]=="后":
							nextURL = self.wikiURL + button.attrs["href"][1:]
							html = self.session.get(nextURL).text		 # 进入下一页
							flag = False								 # flag高高挂起
							break										 # 退出循环
				if flag: break											 # flag为True无非两种情况: 没有button, 或者只要前200页的button
			string = "开始获取{}类别下所有条目信息...".format(category)
			print(string)
			with open("{}/{}".format(self.date,self.log),"a") as f:
				f.write("{}\t{}\n".format(string,time.strftime("%Y-%m-%d %H:%M:%S")))
			self.parse_entries(entries,False)							 # 爬取当前类别的条目
			string = "开始获取{}类别下所有亚类信息...".format(category)
			print(string)
			with open("{}/{}".format(self.date,self.log),"a") as f:
				f.write("{}\t{}\n".format(string,time.strftime("%Y-%m-%d %H:%M:%S")))
			self.parse_categories(subCategories)						 # 爬取当前类别的亚类

	def parse_index_page(self,):										 # 爬取MBA词条分类索引页面
		print("正在获取MBA索引页面信息...")
		html = self.session.get(self.indexURL).text
		soup = BeautifulSoup(html,"lxml")
		h2s = soup.find_all("h2")										 # 由于<div>标签过于混乱, 采用<h2>标签来定位不同分类
		categories = dict()
		for h2 in h2s:
			tempList = list()
			div = h2.find_parent().next_sibling.next_sibling			 # 经验之谈, 没有为什么
			ps = div.find_all("p")										 # <p>标签索引每行第一个类别
			for p in ps:
				a = p.find("a")											 # 只要第一个<a>标签即可因为后面的都可以在第一个类别的亚类或者次亚类中寻找到
				if a: tempList.append(str(a.string))					 # 存在<a>标签则取第一个
				else:													 # 不存在<a>标签的情况比较复杂, 总的来说是MBAlib网页设计者实在是太垃圾了
					label = p.find_next_sibling("p")					 # 后续平行结点还有<p>标签定位
					label = label if label else p.find_next_sibling("dl")# 否则定位<dl>标签
					aLabel = label.find_all("a")						 # "其他/它"类别下的所有类别, 互相平行所以全都要
					for a in aLabel: tempList.append(str(a.string))
					break
			categories[str(h2.string)] = tempList						 # 考虑到未必需要全部词条,因此这里把根条目按照六类分别存入字典
		print("索引页面信息获取完毕!\n共有{}个大类,{}个根条目".format(len(categories.keys()),sum(len(value) for value in categories.values())))
		return categories

	def search(self,keyword):											 # 使用MBA智库搜索引擎, 返回搜索到的"百科"条目
		html = self.session.get(self.searchURL.format(keyword)).text
		soup = BeautifulSoup(html,"lxml")
		h3s = soup.find_all("h3")
		results = []													 # 获取搜索到的"百科"条目
		for h3 in h3s:
			s = h3.find("span",class_="pd-top")
			if str(s.string)=="百科":
				s.extract()
				string = self.labelCompiler.sub("",str(h3))
				results.append(string)
		for result in results:
			print(result)
		return results

	def search_wiki(self,keyword):										 # 搜索MBA智库百科词条, 返回至多前十个搜索词条
		html = self.session.get(self.wikisearchURL.format(keyword)).text
		soup = BeautifulSoup(html,"lxml")
		aLabel = soup.find_all("a")
		results = []
		for a in aLabel:
			try:
				span = a.find("span")
				if "searchmatch" in span.attrs["class"]:
					string = self.labelCompiler.sub("",str(a))
					results.append(string.strip())
			except:
				try:													 # 这个except里面的内容后来写的,当我搜索"大大大"的时候, 虽然没有<span>匹配项, 但是仍有返回结果因此我又硬代码了一回
					if a.attrs["href"][:5]=="/wiki" and len(a.find_parents("b")):
						string = self.labelCompiler.sub("",str(a))
						results.append(string.strip())
					else: continue
				except: continue
		return results[:10] if len(results)>10 else results
	
	def entry(self,entry,
		driver = False
	):																	 # 给定词条列表获取它们所有的信息(我打算涉及一下关键词提取的功能)
		nonsense = [
			"想问一下","想问","想了解一下","想了解","想知道一下","想知道",
			"想搞清楚","什么是","是什么意思","是什么","什么意思","何为",
			"的定义内容","的定义","我"
		]
		entry = entry.strip()
		for word in nonsense:
			entry = entry.replace(word,"")
		if driver:
			b = webdriver.Firefox()
			b.get(self.entryURL.format(entry))
			html = b.page_source
			b.quit()
		else: html = self.session.get(self.entryURL.format(entry)).text
		soup = BeautifulSoup(html,"lxml")
		title = soup.find("title")
		if entry not in str(title.string): return "你要访问的词条不存在",False
		for script in soup.find_all("script"): script.extract()			 # 删除脚本
		p = soup.find("p")												 # 从第一个<p>标签的前一个标签开始搞
		label = p if isinstance(p.previous_sibling,element.NavigableString) else p.previous_sibling  
		text = str()													 # 最终text被写入文本
		while True:
			if isinstance(label,element.NavigableString):				 # 下一个平行节点有可能是字符串, 那就扔了
				label = label.next_sibling
				continue
			if not label: break											 # 没有下一个平行节点,那就再见
			if "class" in label.attrs.keys() and \
			   label.attrs["class"][0]=="printfooter": break			 # 页脚处与空标签处暂停
			string = self.labelCompiler.sub("",str(label))
			text += "{}\n".format(string.strip())
			label = label.next_sibling
		_nCompiler = re.compile(r"\n+",re.S)
		text = _nCompiler.sub("\n",text)
		index1 = text.find("[编辑]")
		index2 = text.find("[编辑]",index1+1)
		return text[index1+4:index2],True

if __name__ == "__main__":
	print("开始测试...")
	mbalib = MBAlib(driver=False)
	#mbalib = MBAlib(driver=False)
	#categories = mbalib.parse_index_page()
	#mbalib.parse_categories(categories["金融百科"])
	#mbalib.parse_entries(["银行资产证券化"])
	#mbalib.search("资产证券")

02-16

  • 两腿的膝盖都有撕裂感,绑着护膝休息了一天,之后只能轻松跑了,唉,可能要到下半年再加量备战半马了。好好研究研究怎么才能安全地跑步了,没想到竟然会这么伤。
  1. nltk的corpus可以到网盘里下好用,现在nltk.download()已经不太行了。
'''
import nltk 
from pattern.en import lemma

print(nltk.__version__)

from nltk.corpus import wordnet as wn

# 词集
synsets = wn.synsets('dog')
print(synsets)
'''


from transformers import BertTokenizer, BertModel, BertForMaskedLM

'''
bert_tokenizer = BertTokenizer.from_pretrained('bert-base-chinese')
words = bert_tokenizer.tokenize('I love you.')
words = bert_tokenizer.tokenize('我爱你。')
print(words)

bert_tokenizer.add_tokens([chr(i) for i in range(ord("A"), ord("Z") + 1)])
'''
help()
bert_model = BertModel.from_pretrained('bert-base-chinese')

02-17

  • 终于是逃得过初一,逃不过十五,被导师给抓回来了。
  1. 发现一个非常厉害的开源项目:https://github.com/huggingface/transformers,csdn有加速。transformers库里面有很多预训练好的模型,跟tensorflow那些都挂在google上的不同,这里的都可以比较快的下载好,无需翻墙。
  2. 被迫搞IUI,我连自己到底在做什么都不知道,不知道为什么wyl一定要搞这个烂活。

02-18

  • 腿恢复了一些,这几天4~6km在跑步机上做康复性的练习。总之长跑确实对腿脚损伤太大了,只有经历过的才能有感同身受,很羡慕那些跑半马全马如喝水的大神。
  • 或许离家前会再跑一次长距离,但是一定会注意防护的了,能跑完半马且不受伤才是终极目标。
  • 关于transformers库下载预处理模型的路径,可以去考察transformers源码下的file_utils.py中的几个环境变量,自己设置一下环境变量就不会存储到C盘里的.cache/…/transformers目录下了,目前好像网络上没有相关的回答。
  • transformers库值得一看,从其支持的模型与框架来看,pytorch远远优于tensorflow,说起来最近试了一下tf2调用BERT,发现之前的代码改了半天也跑不通,tf2已经完全脱离tf1了,我已经决定不搞tensorflow了,以后能用pytorch就用pytorch,tensorflow真是越搞事越多,还难办。

02-19

  • 过几天回去了
  1. transformers小实例
import transformers
print(transformers.__version__)
from transformers import pipeline
classifier = pipeline('sentiment-analysis')
print(classifier('You are a son of bitch'))

02-20

  • 复制一段API文档的内容也算违规了?现在抓的这么紧
  1. transformers 4.3.x版本收录的模型列表:见https://huggingface.co/transformers/
  • 注意看这个链接里的平台列表,pytorch是全部模型都支持,但tensorflow则并不支持全部的模型。

02-21

  • 接下来会跟一个基金项目,首先得把30页的科研基金申请书给写完。总算是把自己拉回了现实。

DGL摘录(一)

  1. DGL定义图的方法并非常见的邻接矩阵或出入度链表形式, 而是直接将所有边的出节点和入节点用两个list存储, 这样的好处是对于稀疏图(即邻接矩阵系数)可以大大减少存储成本, 且无需额外记录图的节点, 直接将两个list拼接后去重就可以得到所有节点, 不过离群点(出度与入度都为零的节点)是不会被考虑进来的;
  • 建图代码如下所示:
    import dgl
    import numpy as np
    
    def build_karate_club_graph():
    	# All 78 edges are stored in two numpy arrays. One for source endpoints
    	# while the other for destination endpoints.
    	src = np.array([1, 2, 2, 3, 3, 3, 4, 5, 6, 6, 6, 7, 7, 7, 7, 8, 8, 9, 10, 10,
    		10, 11, 12, 12, 13, 13, 13, 13, 16, 16, 17, 17, 19, 19, 21, 21,
    		25, 25, 27, 27, 27, 28, 29, 29, 30, 30, 31, 31, 31, 31, 32, 32,
    		32, 32, 32, 32, 32, 32, 32, 32, 32, 33, 33, 33, 33, 33, 33, 33,
    		33, 33, 33, 33, 33, 33, 33, 33, 33, 33])
    	dst = np.array([0, 0, 1, 0, 1, 2, 0, 0, 0, 4, 5, 0, 1, 2, 3, 0, 2, 2, 0, 4,
    		5, 0, 0, 3, 0, 1, 2, 3, 5, 6, 0, 1, 0, 1, 0, 1, 23, 24, 2, 23,
    		24, 2, 23, 26, 1, 8, 0, 24, 25, 28, 2, 8, 14, 15, 18, 20, 22, 23,
    		29, 30, 31, 8, 9, 13, 14, 15, 18, 19, 20, 22, 23, 26, 27, 28, 29, 30,
    		31, 32])
    	# Edges are directional in DGL; Make them bi-directional.
    	u = np.concatenate([src, dst])
    	v = np.concatenate([dst, src])
    	# Construct a DGLGraph
    	return dgl.DGLGraph((u, v))
    
    G = build_karate_club_graph()
    print('We have %d nodes.' % G.number_of_nodes())
    print('We have %d edges.' % G.number_of_edges())
    
  • 输出结果:
    We have 34 nodes.
    We have 156 edges.
    

02-22

饯行

  1. tqdm代码示例:
from tqdm import tqdm

def train(total, epoch):
	pbar = tqdm(total=total, bar_format='{l_bar}{r_bar}', dynamic_ncols=True)
	pbar.set_description('Epoch {}'.format(epoch))
	for i in range(total):
		fields = {
			'loss': random.random(), 
			'accuracy': random.random(), 
			'recall': random.random(),
		}
		pbar.set_postfix(**fields)
		pbar.update()
if __name__ == '__main__':
	total = 1024	
	n_epoch = 16
	for epoch in range(1, 1 + n_epoch):
		train(total=total, epoch=epoch)
  • 注意这种写法要求外循环体for epoch in range(1, 1 + n_epoch)中不含有print的内容, 否则依然会输出很多行;

02-23

  • 两点到宿舍,花了两个小时打扫卫生,安置物品,yjz先我一步已经回来。
  • 总结下来这个寒假确实没做什么事情,完全没有动力,不过倒是一天也没惹老妈生气,陪家人做了很多事情,以后可能回来的机会越来越少了。
  • 坚持了每天跑步(前三周周均60km,前30天总量204km,后期虽然腿伤,但也是至少每天4km的量在跑),半马或许还是只能是存在于梦想中的东西,看了一些过来人的经验,很多人无外乎如此,一失足成万古恨,不管你之前成绩多么好,能40分钟跑10km,膝盖报销就在一瞬间,现在不管以什么样的配速跑,5km就都是极限了,我也不知道自己还能不能回到之前的水平,现在虽然感觉不到膝盖疼痛,但是跑上2km就明显感觉腿不对劲了,或许以后只能每天慢慢跑个5圈10圈了罢,唉。
  1. 使用datetime.datetime.fromtimestamp():
  • 代码示例:
    from datetime import datetime
    timestamp = 1381419600
    print(datetime.fromtimestamp(timestamp))
    
    • 输出结果: 2013-10-10 23:40:00
  • 注意:
    • 该函数无keyword augments, 即不需要添加参数名称;
    • time.localtime()函数不同, datetime.datetime.fromtimestamp()接收的时间戳至少为86400, 前者则可以从0开始;

02-24

  • 空想还是不如参考前人的做法,有时候还是不应该对自己胡扯的能力太过自信。
  1. pypdf2操作相关:合并pdf文件(详见本人blog,应无侵权行为)
# -*- coding: UTF-8 -*-
# @author: caoyang
# @email: caoyang@163.sufe.edu.cn

import cv2
import numpy
from PyPDF2 import PdfFileReader, PdfFileWriter

def parse_pdf_images(import_path: str) -> None:
	with open(import_path, 'rb') as pdf:
		reader = PdfFileReader(pdf)
		for number in range(reader.getNumPages()):
			page = reader.getPage(number)
			print('Page {}:'.format(number))
			x_object = page['/Resources'].get('/XObject')
			if x_object is None: 
				continue
			for key in x_object:
				if x_object[key]['/Subtype'] == '/Image':
					size = (x_object[key]['/Width'], x_object[key]['/Height'])
					data = x_object[key].getData()
					if x_object[key]['/ColorSpace'] == '/DeviceRGB':
						mode = 'RGB'
					else:
						mode = 'P'	
					image = numpy.frombuffer(data, numpy.uint8)	
					print(x_object[key]['/Filter'])	

					if x_object[key]['/Filter'] == '/FlateDecode':
						print(size)
						print(image.shape)
						image = image.reshape(size[1], size[0], 3)
						cv2.imwrite('page' + '%05ui' % number + 'key' + key[1:] + '.png', image)
					elif x_object[key]['/Filter'] == '/DCTDecode':
						cv2.imwrite(key[1:] + '.jpg', image)
					elif xObject[obj]['/Filter'] == '/JPXDecode':
						cv2.imwrite(key[1:] + '.jpg', image)
			'''
			try:
				xObject = page['/Resources']['/XObject'].getObject()
			except: 
				print('  - Fail ...')
				continue
			print('  - {}'.format(xObject))
			for obj in xObject:
				print('  - {}'.format(xObject[obj]['/Subtype']))
				if xObject[obj]['/Subtype'] == '/Image':
					size = (xObject[obj]['/Width'], xObject[obj]['/Height'])
					data = xObject[obj].getData()
					if xObject[obj]['/ColorSpace'] == '/DeviceRGB':
						mode = 'RGB'
					else:
						mode = 'P'			
					if xObject[obj]['/Filter'] == '/FlateDecode':
						img = np.fromstring(data, np.uint8)
						img = img.reshape(size[1],size[0])
						img = 255-img
						img = img+1
						img = img[:,220:-225]
						cv2.imwrite("page"+"%05ui"%i+"obj"+obj[1:]+".png",img)
						pic_list.append("page"+"%05ui"%i+"obj"+obj[1:]+".png")
					elif xObject[obj]['/Filter'] == '/DCTDecode':
						img = np.fromstring(data,np.uint8)
						cv2.imwrite(obj[1:]+".jpg",img)
					elif xObject[obj]['/Filter'] == '/JPXDecode':
						img = np.fromstring(data,np.uint8)
						cv2.imwrite(obj[1:]+".jp2",img)
			'''

if __name__ == '__main__':
	# format_reference_text(
	# 	import_path=r'D:\study\paper\1906.08340_reference.md',
	# 	export_path=r'D:\study\paper\1906.08340_reference_formatted.md',
	# 	index_start_char='[',
	# 	index_end_char=']',
	# )
	parse_pdf_images(
	# 	import_path='1906.08340.pdf',
		import_path=r'D:\code\python\project\paper\paper1_RE2RNN\emnlp20reg.pdf',
	)

02-25

  • 雨蒙蒙的一天,疯狂码字,但是真的感觉提不太起精神。下学期六门专业课,似乎是个巨大的挑战,但是转念一想每门课都和lth一起上,也是一件大幸事,高中同桌三年,本科四年未能在同一间教室里上过课,然而研究生阶段有这么一次同台竞技的机会,实然使人振奋得很。
  • 跑了两天4km,感觉鞋有点问题,脚有些扭到了,如今4km跑起来都费事,但也要坚持下去。曾经26’38"的6km和45‘53“的10km,以及完全跑不累的71’08" 15km可能再也回不去了。
  1. python数组一些常用的工具函数:排序并记录排序前的索引值
# -*- coding: UTF-8 -*-
# @author: caoyang
# @email: caoyang@163.sufe.edu.cn

from typing import List

def sorted_index(array: List[int]) -> (List[int], List[int]):
	sorted_index_array = sorted(enumerate(array), key=lambda k: k[1])
	sorted_index = [x[0] for x in sorted_index_array]
	sorted_array = [x[1] for x in sorted_index_array]
	return sorted_index, sorted_array
	

if __name__ == '__main__':
	array = [1,3,5,7,9,2,4,6,8,10]
	sorted_index, sorted_array = sorted_index(array)
	print(sorted_index)
	print(sorted_array)

02-26

  • 元宵,连日阴雨,还要下几天,不适。
  • 今晚换了日常穿的鞋去跑,结果4km配速4’38",前几天5分配都够呛。风停雨息,我觉得自己又行了,难道新买的跑鞋真的是坑,穿上就再也跑不快了,感觉是鞋底弹性太好每一步都变慢了。
  1. 最近在看自适应界面生成的问题,确实是个大坑,但是可能还非做不可。
  2. 这方面可能会涉及很久之前学过的一种叫做交互式机器学习的概念,印象中似乎和在线学习也有一定关联。
  3. 总之可以好好研究一下。

02-27

  • 又多了一些新的思路,扩充了一下内容,差不多一万字了。
  1. import tensorflow时触发警告: (tensorflow版本为2.3.0)
W tensorflow/stream_executor/platform/default/dso_loader.cc:59] Could not load dynamic library 'cudart64_101.dll'; dlerror: cudart64_101.dll not found
  1. 处理方案: 去路径%CUDA_PATH%\bin下将cudart64_xxx.dll改为cudart64_101.dll即可;
  • 原因: CUDA版本不匹配, 从警告内容可以看出tensorflow 2.3.0对应的CUDA版本应为10.1, 笔者使用的为CUDA 10.2, 所以%CUDA_PATH%\bin路径下只有cudart64_102.dll, 一般高版本会兼容低版本, 所以直接改名即可, 低版本(如CUDA 10.0, 注意从tensorflow 2.0.0之后, 对CUDA版本的最低要求就是CUDA 10.0);
  • 关于CUDA版本对应问题最新更新的结果在https://tensorflow.google.cn/install/source_windows ;

02-28

  • 就是鞋子的问题,我发现这个鞋子真的毒,穿上5圈就能把人跑废,弹性好确实保护关节,但是跑得也太累了,好奇怪啊。不过今天也是有点闷,不是很适合户外跑。

1.1 pipeline()方法

transformers.pipeline(task: str, 
					  model: Optional = None, 
					  config: Optional[Union[str, transformers.configuration_utils.PretrainedConfig]] = None, 
					  tokenizer: Optional[Union[str, transformers.tokenization_utils.PreTrainedTokenizer]] = None, 
					  framework: Optional[str] = None, 
					  revision: Optional[str] = None, 
					  use_fast: bool = True, **kwargs) → transformers.pipelines.base.Pipeline
  • 参数说明:
    • task: 即管道适用的任务类型, 通过设置不同的task值将返回不同类型的管道对象;
      • 'feature-extraction': 返回FeatureExtractionPipeline;
      • 'sentiment-analysis': 返回TextClassificationPipeline;
      • 'ner': 返回TokenClassificationPipeline;
      • 'question-answering': 返回QuestionAnsweringPipeline;
      • 'fill-mask': 返回FillMaskPipeline;
      • 'summarization': 返回SummarizationPipeline;
      • 'translation_xx_to_yy': 返回TranslationPipeline;
      • 'text2text-generation': 返回Text2TextGenerationPipeline;
      • 'text-generation': 返回TextGenerationPipeline;
      • 'zero-shot-classification': 返回ZeroShotClassificationPipeline;
      • 'conversation': 返回ConversationalPipeline;
    • model: 即管道所使用的模型, 默认会从huggingface镜像中下载模型, 可以传入继承自PreTrainModel(PyTorch)或TFPreTrainModel(Tensorflow)的变量;
    • config: 配置信息, 见transformers.PretrainedConfig
  • 示例:
    >>> from transformers import pipeline, AutoModelForTokenClassification, AutoTokenizer
    
    >>> # Sentiment analysis pipeline
    >>> pipeline('sentiment-analysis')
    
    >>> # Question answering pipeline, specifying the checkpoint identifier
    >>> pipeline('question-answering', model='distilbert-base-cased-distilled-squad', tokenizer='bert-base-cased')
    
    >>> # Named entity recognition pipeline, passing in a specific model and tokenizer
    >>> model = AutoModelForTokenClassification.from_pretrained("dbmdz/bert-large-cased-finetuned-conll03-english")
    >>> tokenizer = AutoTokenizer.from_pretrained("bert-base-cased")
    >>> pipeline('ner', model=model, tokenizer=tokenizer)
    

2021年3月

03-01

  • 新的一个月,新的学期第一天,真是一个非常好的开始。
  • 还是这种生活节奏畅快,上课,看代码,写报告。
  • 下午与lth碰头,老朋友虽然数年没太多交流,但是一见面还是非常亲切的,这学期所有的课都将和lth一起上,也算是一件幸事了,至少不会辣么枯燥,而且有这家伙在,我想必也会充满斗志的,彼此干掉对方都是件快乐的事情。你我知根知底,就无需藏着掖着了。
  1. Transformers笔记:我最近在系统地看Transformers库地API文档,可能会更新一篇blog
  • 任务类型:
    • (1) 情感分析(Sentiment analysis): 判断文本是积极的或是消极的;
    • (2) 文本生成(Text generation): 根据某种提示生成一段相关文本;
    • (3) 命名实体识别(Name entity recognition): 判断语句中的某个分词属于何种类型;
    • (4) 问答系统(Question answering): 根据上下文和问题生成答案;
    • (5) 缺失文本填充(Filling masked text): 还原被挖去某些单词的语句;
    • (6) 文本综述(Summarization): 根据长文本生成总结性的文字;
    • (7) 机器翻译(Translation): 将某种语言的文本翻译成另一种语言;
    • (8)特征挖掘(Feature extraction): 生成文本的张量表示;
  • 以情感分析为例, 给出一个快速上手的示例:
    from transformers import pipeline
    
    nlp = pipeline("sentiment-analysis")
    result = nlp("I hate you")[0]
    print(f"label: {result['label']}, with score: {round(result['score'], 4)}")
    result = nlp("I love you")[0]
    print(f"label: {result['label']}, with score: {round(result['score'], 4)}")
    
    • 该管道模型从distilbert-base-uncased-finetuned-sst-2-english 处下载获得, 如果需要指定使用哪种特定的模型, 可以设置model参数, 获取从model hub 上储存的模型, 如下面这个模型除了可以处理英文外, 还可以处理法语, 意大利语, 荷兰语:
    from transformers import pipeline
    
    classifier = pipeline('sentiment-analysis', model="nlptown/bert-base-multilingual-uncased-sentiment")
    
    • 关于这些模型的参数可以到huggingface页面上去查阅README文件;
    • 通常可以为管道模型添加tokenizer参数, 即指定好分词器, transformers库中已经提供了相应的模块AutoModelForSequenceClassificationTFAutoModelForSequenceClassification:
      # PyTorch
      from transformers import AutoTokenizer, AutoModelForSequenceClassification
      
      model_name = "nlptown/bert-base-multilingual-uncased-sentiment"
      model = AutoModelForSequenceClassification.from_pretrained(model_name)
      tokenizer = AutoTokenizer.from_pretrained(model_name)
      classifier = pipeline('sentiment-analysis', model=model, tokenizer=tokenizer)
      
      # TensorFlow
      from transformers import AutoTokenizer, TFAutoModelForSequenceClassification
      
      model_name = "nlptown/bert-base-multilingual-uncased-sentiment"
      # This model only exists in PyTorch, so we use the `from_pt` flag to import that model in TensorFlow.
      model = TFAutoModelForSequenceClassification.from_pretrained(model_name, from_pt=True)
      tokenizer = AutoTokenizer.from_pretrained(model_name)
      classifier = pipeline('sentiment-analysis', model=model, tokenizer=tokenizer)
      
    • 如果需要在特定的数据集上微调这些预训练管道模型, 可以参考Example ;

03-02

  • 阴雨连绵一周后,终于放晴,可惜后天又要开始降雨,烦得很。
  • 下午趁晴朗穿短裤去跑了一发,5.35km,均配4’39",第一个1km是4’56",后程速度倒还说得过去,感觉可能还是有机会能恢复到巅峰状态的,现在都是带着护膝跑了,虽然影响一些速度,但是至少要保住膝盖。
  1. 使用预训练模型: 经过分词器预处理后的数据可以直接输入到模型中, 正如上文所述, 分词器的输出包含了模型所需的所有信息:
  • 示例: 注意PyTorch版本需要打包字典输入;
    # PyTorch
    # import torch
    # pt_outputs = pt_model(**pt_batch, labels = torch.tensor([1, 0])) # add labels
    outputs = pt_model(**pt_batch)
    print(outputs)
    
    # TensorFlow
    # import tensorflow as tf
    # tf_outputs = tf_model(tf_batch, labels = tf.constant([1, 0])) # add labels
    outputs = tf_model(tf_batch)
    print(outputs)
    
    • 输出结果:
    (tensor([[-4.0833,  4.3364],
    		[ 0.0818, -0.0418]], grad_fn=<AddmmBackward>),)
    		
    (<tf.Tensor: shape=(2, 2), dtype=float32, numpy=
    array([[-4.0832963 ,  4.336414  ],
    	   [ 0.08181786, -0.04179301]], dtype=float32)>,)
    
    • 重点注意: 输出结果是去除了模型最后一个激活层(如softmax等激活函数)的输出, 这在所有transformers库中的模型都是通用的, 原因是最后一个激活层会和损失函数相融合(fused with loss)

03-03

  • 4km破18’00",近一个月来第一次跑回伤残前的水平。天晴了,雨停了,老子觉得自己又行了(半月板警告
  • 我感觉王英林还是靠谱的,但是毕竟年纪大了,行动稍显迟缓,让我们这些年轻人实在是有些焦虑。

问答挖掘

  1. 关于SQuAD任务的模型微调可以参考run_squad.pyrun_tf_squad.py , 前者的PyTorch脚本似乎已经挂掉了, 只有后者TensorFlow的脚本仍然是有效的了;
  • 简单示例一:
    from transformers import pipeline
    
    nlp = pipeline("question-answering")
    context = r"""
    Extractive Question Answering is the task of extracting an answer from a text given a question. An example of a
    question answering dataset is the SQuAD dataset, which is entirely based on that task. If you would like to fine-tune
    a model on a SQuAD task, you may leverage the examples/question-answering/run_squad.py script.
    """
    
    result = nlp(question="What is extractive question answering?", context=context)
    print(f"Answer: '{result['answer']}', score: {round(result['score'], 4)}, start: {result['start']}, end: {result['end']}") # Answer: 'the task of extracting an answer from a text given a question.', score: 0.6226, start: 34, end: 96
    result = nlp(question="What is a good example of a question answering dataset?", context=context)
    print(f"Answer: '{result['answer']}', score: {round(result['score'], 4)}, start: {result['start']}, end: {result['end']}") # Answer: 'SQuAD dataset,', score: 0.5053, start: 147, end: 161
    

03-04

  • 汝大逆不道,当死无赦。
  • 以后博客更新频率恐怕会越来越低了,我总归也要逐渐放弃一些不再该做的事情了。
  • 真的很想再回到以前的水平,我到现在都很难相信自己两个月前能跑38圈操场都不带喘,现在跑个5圈就逼近极限了。
  1. jupyter中调用parser.parse_args()报错
  • 报错内容: ipykernel_launcher.py: error: unrecognized arguments: -f /Users/apple/Library/Jupyter/runtime/kernel;
  • 解决方案: 使用parser.parse_known_args()[0]替代parser.parse_args()即可;

03-05

  • 下午午睡起来跑出4km的个人最好成绩,17’20",平均配速跑进4’20",最后1km用时3’58",爷的青春回来了。
  • 这个月不打算跑15圈以上的里程,仍然以膝盖恢复为主线任务,我一定要恢复到巅峰状态。
  • 至此,各项pb:12’58"(3km),17’20"(4km),26’38"(6km),45’53"(10km),71’08"(15km),腿废也值了。
  1. 查看pip安装库的时间及详细信息:
# -*- coding: UTF-8 -*-
# @author: caoyang
# @email: caoyang@163.sufe.edu.cn

import os
import pandas
from datetime import datetime
from pip._internal.utils.misc import get_installed_distributions

from utils import *

class PipLogger(object):
	
	def __init__(self) -> None:	
		self.logging_dict = {
			'index': [],
			'createdTime': [],
			'modifiedTime': [],
			'package': [],
		}

	def logging_pip_install_packages(self, export_path_csv:str=None, export_path_markdown: str=None) -> None:
		packages = []
		for package in get_installed_distributions():
			package_metadata_path = os.path.dirname(package._get_metadata_path_for_display('RECORD'))
			created_time = os.path.getctime(package_metadata_path)
			modified_time = os.path.getmtime(package_metadata_path)
			created_time = datetime.fromtimestamp(created_time)
			modified_time = datetime.fromtimestamp(modified_time)
			packages.append([created_time, modified_time, str(package)])
		index = 0
		for created_time, modified_time, package_name_version in sorted(packages):
			index += 1
			self.logging_dict['index'].append(index)
			self.logging_dict['createdTime'].append(created_time)
			self.logging_dict['modifiedTime'].append(modified_time)
			self.logging_dict['package'].append(package_name_version)
		logging_df = pandas.DataFrame(self.logging_dict, columns=list(self.logging_dict.keys()))
		if export_path_csv is not None:
			logging_df.to_csv(export_path_csv, header=True, index=False, sep='\t')
		if export_path_csv is not None:
			with open(export_path_markdown, 'w', encoding='UTF-8') as f: 
				f.write(pandas_dataframe_to_markdown_table(logging_df))

if __name__ == '__main__':
	pip_logger = PipLogger()
	pip_logger.logging_pip_install_packages(export_path_csv='pip.csv', export_path_markdown='pip.md')

03-06

  • 抗衡周六魔咒失败,不过懒觉睡得是挺舒服的。
  • 双腿感觉已经恢复了。不过阴沉的天气实在是让人打不起精神。
  • 论文标题: Integrating Adaptive User Interface Capabilities in Enterprise Applications
  • 中文标题: 在企业应用中集成用户界面自适应功能
  • 免费下载链接: citeseerx

03-07

  • 阴雨连绵,4’34"配4km
  • 令人苦恼的任务,江郎才尽,黔驴技穷了
  1. paper参考文献修改
# -*- coding: UTF-8 -*-
# @author: caoyang
# @email: caoyang@163.sufe.edu.cn

def format_reference_text(import_path: str, export_path: str=None, index_start_char: str='[', index_end_char: str=']') -> None:
	"""
	Format reference text copied from PDF file.
	
	:param import_path		: Import path of reference text file.
	:param export_path		: Export path of Formatted reference text file.
	:param index_start_char	: Starting char of reference entries' index.
	:param index_end_char	: Ending char of reference entries' index.
	"""
	if export_path is None:
		export_path = 'formatted_{}'.format(import_path)
	with open(import_path, 'r', encoding='utf8') as f:
		lines = f.read().splitlines()
	reference_index = 0
	references = []
	entry = None
	for line in filter(None, lines):
		line = line.strip()
		if line.startswith(index_start_char):
			reference_index += 1
			if entry is not None:
				references.append(''.join(entry))
			entry = []
			index = line.find(index_end_char)
			prefix = '[{}] '.format(str(reference_index).zfill(2))
			suffix = line[index + 1:]
		else:
			prefix = ''
			suffix = line[:]
		if suffix.endswith('-'):
			entry.append(prefix + suffix[:-1].strip())
		else:
			entry.append(prefix + suffix + ' ')		
	with open(export_path, 'w', encoding='utf8') as f:
		f.write('\n'.join(references))

			
if __name__ == '__main__':
	'''
	format_reference_text(
		import_path=r'1.txt',
		export_path=r'1.md',
		index_start_char='[',
		index_end_char=']',
	)'''
	parse_pdf_images(
		import_path='1.pdf',
	)

03-08

  • 感觉状态不错,晚上去操场再试试,可惜碰上下雨,还是只跑了10圈,不过明显体力仍然很充沛,慢慢地状态开始起来了,稳一个月,别浪,下个月开始突破。
  • 发现人的自律性就很奇怪,要始终保持自律是一件很难得事情,但是一旦自律起来真的很可怕;
  1. dgl的GCN demo
# -*- coding: UTF-8 -*-
# @author: caoyang
# @email: caoyang@163.sufe.edu.cn

import numpy as np
import torch
import torch.nn as nn
import torch.nn.functional as F
from dgl import DGLGraph
from dgl.data import register_data_args, load_data

def gcn_msg(edges):
	return {'m' : edges.src['h']}

def gcn_reduce(nodes):
	return {'h' : torch.sum(nodes.mailbox['m'], 1)}

class NodeApplyModule(nn.Module):  # define the GCN layer.
	def __init__(self, in_feats, out_feats, activation=None):
		super(NodeApplyModule, self).__init__()
		self.linear = nn.Linear(in_feats, out_feats)
		self.activation = activation

	def forward(self, nodes):
		# normalization by square root of dst degree
		h = nodes.data['h'] 
		h = self.linear(h)
		if self.activation:
			h = self.activation(h)
		return {'h' : h}

03-09

  • 无事发生,我回校快三个星期了,就出了三天太阳,我真的是吐了
  • 稍微起了一些状态,有点等不及到月底了,准备中旬开始冲刺10km。

Greedy algorithm Max 算法证明(作业,感觉证的有点复杂,不知道对不对)

  • 充分性证明

    命题:若 ( U , I ) (U,I) (U,I)构成一个拟阵,则对于任意成本函数 c ( ⋅ ) c(\cdot) c(),通过Greedy Algorithm Max算法可以得出最优解。

    不妨设成本函数值的排序结果为: c ( x 1 ) ≥ c ( x 2 ) ≥ . . . ≥ c ( x n ) > 0 x i ∈ U i = 1 , 2 , . . . , n c(x_1 )\ge c(x_2 )\ge...\ge c(x_n)>0\quad x_i\in U\quad i=1,2,...,n c(x1)c(x2)...c(xn)>0xiUi=1,2,...,n

    • 假设通过Greedy Algorithm Max输出的解 A A A非最优解,那么一定存在一个最优解 A ∗ A^* A与集合 A A A的元素构成是不完全相同的,显然最优解满足 A ∗ ∈ I A^*\in I AI,我们分以下两种情况讨论:

      • 情况一: A ∗ A^* A中的每个元素都属于集合 A A A,即 A ∗ ⊂ A A^*\subset A AA ∣ A ∗ ∣ < ∣ A ∣ |A^*|\lt|A| A<A

        显然此时 ( 1 ) (1) (1)式成立:
        ∑ x ∈ A ∗ c ( x ) < ∑ x ∈ A c ( x ) (1) \sum_{x\in A^*}c(x)<\sum_{x\in A}c(x)\tag{1} xAc(x)<xAc(x)(1)
        A ∗ A^* A是最优解矛盾;

      • 情况二: A ∗ A^* A中至少存在一个不属于 A A A的元素,即 ∃ x ∈ A ∗ \exists x\in A^* xA满足 x ∉ A x\notin A x/A,不妨设 x k x_k xk是满足这种性质的 x x x中下标值最小的,即 k k k的取值满足 ( 2 ) (2) (2)式的约束:
        x i ∈ A ∩ A ∗ ∀ i = 1 , 2 , . . . , k − 1 x k ∈ A ∗ x k ∉ A (2) x_i\in A\cap A^*\quad\forall i=1,2,...,k-1\quad\\ x_k\in A^*\\ x_k\notin A\tag{2} xiAAi=1,2,...,k1xkAxk/A(2)
        考察在Greedy algorithm max算法流程中 x k x_k xk没有添加到集合 A A A中的原因:

        显然是因为在判断是否应当将 x k x_k xk添加到集合 A k A_k Ak中时,发现此时满足 A k ∪ { x k } ∉ I A_k\cup\{x_k\}\notin I Ak{xk}/I A k A_k Ak表示算法遍历到 x k x_k xk时集合 A A A的构成情况)

        注意到算法遍历到 x k x_k xk时, A k A_k Ak中所包含的元素都在 A ∗ A^* A中(由 k k k是最小下标值的假设得出),即此时满足 A k ∈ A ∗ A_k\in A^* AkA

        又由 ( 2 ) (2) (2)式知 x k ∈ A ∗ x_k\in A^* xkA,则有 A k ∪ { x k } ⊂ A ∗ A_k\cup\{x_k\}\subset A^* Ak{xk}A

        又已知 A ∗ ∈ I A^*\in I AI,根据拟阵性质,则有 A k ∪ { x k } ∈ I A_k\cup\{x_k\}\in I Ak{xk}I

        这与 A k ∪ { x k } ∉ I A_k\cup\{x_k\}\notin I Ak{xk}/I矛盾;

    • 综上所述,不存在一个最优解 A ∗ A^* A与集合 A A A的元素构成是不完全相同的,因此通过Greedy Algorithm Max算法输出的解 A A A为最优解,充分性得证。

  • 必要性证明

    命题:若对于任意成本函数 c ( ⋅ ) c(\cdot) c(),通过Greedy Algorithm Max算法可以得出最优解,则 ( U , I ) (U,I) (U,I)构成一个拟阵。

    逆否命题:若 ( U , I ) (U,I) (U,I)不构成一个拟阵,则存在一个成本函数 c ( ⋅ ) c(\cdot) c(),通过Greedy Algorithm Max算法无法得出最优解。

    考察 ( U , I ) (U,I) (U,I)是否满足拟阵的三个性质:

    ( 1 ) (1)\quad (1) ∅ ∈ I \emptyset\in I I

    ( 2 ) (2)\quad (2) A ∈ I , B ⊂ A A\in I,B\subset A AI,BA,则 B ∈ I B\in I BI

    ( 3 ) (3)\quad (3) A ∈ I , B ∈ I , ∣ A ∣ > ∣ B ∣ A\in I,B\in I,|A|>|B| AI,BI,A>B,则 ∃ x ∈ A \exists x\in A xA,使得 B ∪ { x } ∈ I B\cup\{x\}\in I B{x}I成立;

    • 性质 ( 1 ) (1) (1):平凡,显然成立;

    • 性质 ( 2 ) (2) (2)

      考察必要性命题的逆否命题,即:

      证明:若 ( U , I ) (U,I) (U,I)不满足性质 ( 2 ) (2) (2),则存在一个成本函数 c ( ⋅ ) c(\cdot) c(),通过Greedy Algorithm Max算法无法得出最优解。

      已知 ( U , I ) (U,I) (U,I)不满足性质 ( 2 ) (2) (2),即集合族 I I I中可以找到一个集合 A A A,至少存在一个 B ⊂ A B\subset A BA,满足 B ∉ I B\notin I B/I

      令集合 S A S_A SA是集合族 I I I中包含元素最多的 A A A的超集(目的是使得集合族 I I I中不存在以 S A S_A SA为真子集的集合),即:
      S A = a r g m a x S ∈ I ∣ S ∣ : A ⊆ S S A ⊂ S ⇒ S ∉ I (3) S_A = {\rm argmax}_{S\in I}|S|:A\subseteq S\tag{3}\\ S_A\subset S\Rightarrow S\notin I SA=argmaxSIS:ASSASS/I(3)
      则有 B ⊂ S A B\subset S_A BSA,我们说明可以构造出成本函数 c ( ⋅ ) c(\cdot) c(),使得 S A S_A SA是唯一的最优解且通过Greedy Algorithm Max算法无法得出 S A S_A SA

      • 首先,我们说明可以构造出非负成本函数 c ( ⋅ ) c(\cdot) c(),使得 S A S_A SA是唯一的最优解:

        S A = { x a 1 , x a 2 , . . . , x a p } S_A=\{x_{a_1},x_{a_2},...,x_{a_p}\} SA={xa1,xa2,...,xap},设在全集 U U U S A S_A SA的补集为 S A c = { x b 1 , x b 2 , . . . , x b q } S_A^c=\{x_{b_1},x_{b_2},...,x_{b_q}\} SAc={xb1,xb2,...,xbq}

        不妨设 S A S_A SA中元素的成本函数呈降序排列: c ( x a 1 ) ≥ c ( x a 2 ) ≥ . . . ≥ c ( x a p ) c(x_{a_1})\ge c(x_{a_2})\ge...\ge c(x_{a_p}) c(xa1)c(xa2)...c(xap)

        则除 S A S_A SA外,集合族 I I I可能的成本函数之和最大的集合是 A ∗ = S A c ∪ { x a 1 , x a 2 , . . . , x a p − 1 } A^*=S_A^c\cup\{x_{a_1},x_{a_2},...,x_{a_{p-1}}\} A=SAc{xa1,xa2,...,xap1}

        注意到:
        ∑ x ∈ S A c ( x ) > ∑ x ∈ A ∗ c ( x ) ⇔ c ( x a p ) > ∑ x ∈ S A c c ( x ) (4) \sum_{x\in S_A}c(x)>\sum_{x\in A^*}c(x)\Leftrightarrow c(x_{a_p})>\sum_{x\in S_A^c}c(x)\tag{4} xSAc(x)>xAc(x)c(xap)>xSAcc(x)(4)
        即只需要构造 c ( x a p ) > ∑ x ∈ S A c c ( x ) c(x_{a_p})>\sum_{x\in S_A^c}c(x) c(xap)>xSAcc(x),即可使得 S A S_A SA是集合族 I I I中唯一的最优解;

      • 然后,我们说明在上述使得 S A S_A SA是唯一最优解的成本函数构造情况下(即 S A S_A SA中最小元素的成本函数值大于 S A S_A SA补集中所有元素的成本函数值之和),通过Greedy Algorithm Max算法无法得出 S A S_A SA

        同上我们假设 S A = { x a 1 , x a 2 , . . . , x a p } S_A=\{x_{a_1},x_{a_2},...,x_{a_p}\} SA={xa1,xa2,...,xap},全集 U U U S A S_A SA的补集为 S A c = { x b 1 , x b 2 , . . . , x b q } S_A^c=\{x_{b_1},x_{b_2},...,x_{b_q}\} SAc={xb1,xb2,...,xbq}

        显然 ∀ 1 ≤ i ≤ q , 1 ≤ j ≤ p \forall 1\le i\le q,1\le j\le p 1iq,1jp,满足 c ( x a i ) > c ( x b j ) c(x_{a_i})>c(x_{b_j}) c(xai)>c(xbj),因此算法会先遍历 S A S_A SA中的元素;

        已知 S A S_A SA中存在一个真子集 B B B,满足 B ∉ I B\notin I B/I,设 B = { x a b 1 , x a b 2 , . . . , x a b m } B=\{x_{a_{b_1}},x_{a_{b_2}},...,x_{a_{b_m}}\} B={xab1,xab2,...,xabm}

        则只需要在构造 S A S_A SA中元素的成本函数时满足 c ( x a b 1 ) ≥ c ( x a b 2 ) ≥ . . . ≥ c ( x a b m ) c(x_{a_{b_1}})\ge c(x_{a_{b_2}})\ge...\ge c(x_{a_{b_m}}) c(xab1)c(xab2)...c(xabm),此时算法依次运行 x a b 1 , x a b 2 , . . . x a b m x_{a_{b_1}},x_{a_{b_2}},...x_{a_{b_m}} xab1,xab2,...xabm

        1. 若算法运行到 x a b m x_{a_{b_m}} xabm时,候选最优解集包含了前面每一个元素,即为 { x a b 1 , x a b 2 , . . . , x a b m − 1 } \{x_{a_{b_1}},x_{a_{b_2}},...,x_{a_{b_m-1}}\} {xab1,xab2,...,xabm1},那么 x a b m x_{a_{b_m}} xabm将无法添加到候选最优解集中,从而算法无法得到唯一的最优解 S A S_A SA
        2. 若算法运行到 x a b m x_{a_{b_m}} xabm时,候选最优解集未能包含到前面的每一个元素,显然算法也无法得到唯一的最优解 S A S_A SA
      • 最后,综上所述, ( U , I ) (U,I) (U,I)满足性质 ( 2 ) (2) (2)

    • 性质 ( 3 ) (3) (3)

      考察必要性命题的逆否命题,即:

      证明:若 ( U , I ) (U,I) (U,I)不满足性质 ( 3 ) (3) (3),则存在一个成本函数 c ( ⋅ ) c(\cdot) c(),通过Greedy Algorithm Max算法无法得出最优解。

      注意:此时可以假定 ( U , I ) (U,I) (U,I)已经满足性质 ( 2 ) (2) (2)

      已知 ( U , I ) (U,I) (U,I)不满足性质 ( 3 ) (3) (3),即可以找到集合族 I I I中的一对集合 A A A B B B,满足 ∣ A ∣ > ∣ B ∣ |A|>|B| A>B,则 ∀ x ∈ A \forall x\in A xA,有 B ∪ { x } ∉ I B\cup\{x\}\notin I B{x}/I

      我们可以观察到集合 A A A和集合 B B B具有如下的性质:

      1. 显然集合 B B B非空,否则容易说明从集合 A A A中任取一个元素添加到集合 B B B中可以得到集合 A A A的一个子集,根据性质 ( 2 ) (2) (2),这与 B ∪ { x } ∉ I B\cup\{x\}\notin I B{x}/I的前提矛盾;

      2. 显然 B ∩ A = ∅ B\cap A=\emptyset BA=,否则从集合 A A A中取 x ∈ B ∩ A x\in B\cap A xBA,有 B ∪ { x } = B ∈ I B\cup\{x\}=B\in I B{x}=BI,从而推出与前提矛盾;

      3. 由集合 B B B非空与 ∣ A ∣ > ∣ B ∣ |A|>|B| A>B,可知集合 A A A中至少包含两个元素,即 ∣ B ∣ ≥ 1 , ∣ A ∣ ≥ 2 |B|\ge 1,|A|\ge 2 B1,A2

      令集合 S B S_B SB是集合族 I I I中包含元素最多的 B B B的超集(目的是使得集合族 I I I中不存在以 S B S_B SB为真子集的集合),即:
      S B = a r g m a x S ∈ I ∣ S ∣ : B ⊆ S S B ⊂ S ⇒ S ∉ I (5) S_B = {\rm argmax}_{S\in I}|S|:B\subseteq S\tag{5}\\ S_B\subset S\Rightarrow S\notin I SB=argmaxSIS:BSSBSS/I(5)
      显然 S B ∩ A = ∅ S_B\cap A=\emptyset SBA=,否则从集合 A A A中取 x ∈ S B ∩ A x\in S_B\cap A xSBA添加到集合 B B B中,有 B ∪ { x } ⊂ S B ⇒ B ∪ { x } ∈ I B\cup\{x\}\subset S_B\Rightarrow B\cup\{x\}\in I B{x}SBB∪</