连载囚生CYの备忘录(20220906-)

Posted 囚生CY

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了连载囚生CYの备忘录(20220906-)相关的知识,希望对你有一定的参考价值。

序言

下午田径队第一次训练。其实我有点不太想去,一来太阳特别晒,二来自七月下旬住院回来之后,整个八月只进行了五六次路跑,这学期回来已有一周,每天跑2km就足以折煞我,心理落差特别大。

可是转念又想昨天偶遇王炳杰相谈甚欢,暑期又给陈嘉伟的队伍做指导老师,不去队里露脸见个面有点说不过去,之前也注意到李婷玉也住在三门路13号楼,尽管以我的能力可能也不太能搞得好关系吧,反正难得队里有另一个住在同一栋楼的博士,说不定以后能多一点机会呢… 所以尽管时间卡得很紧,还是去训练。

4×15开合深蹲跳,4×10箭步深蹲跳,4×15俯卧挺身,2×60次的交替抬腿跨栏,最后3000米慢跑恢复,大半年没上过这种强度,标准不标准得都勉强坚持下来,差点就要交代在田径场。想到王炳杰暑期每天还能四五点起来跑步,依然保持着10km以上的体能,我真的是要羞愧得无地自容。

说到底运动是能改变一个人的精神面貌,正如前文收尾写得那样,八月份如同泥石流般的颓败,也不知道自己就突然想摆烂到底,找不到可以刺激自己神经的支撑点。但是这场训练之后,我又找回了去年那种做事的激情。

作为一个开端这是值得乐观的,尽管在未来无穷的变数里我仍未发现那束属于自己的光芒,它或有或无,或是注定错失,然而似乎我已经别无选择,大不了自己成为一束最弱的光去点亮下一代罢。


20220906

关于torch的RNN模块做一个详解记录,重点要注意输入输出张量,以及参数张量的形状,另外像LSTM的输出相比GRU以及RNN要多一个 c i c_i ci,GRU与RNN是相似的,RNN类的两个输出是有关联的,具体见下面代码中的注释:

  • LSTM网络由若干层的LSTM层构成,每个LSTM层中包含若干LSTM的单元(数量一般为序列长度),每一个LSTM层里面有两条线贯穿始终,即 h t , c t h_t,c_t ht,ct,因此需要设置初始的 h 0 h_0 h0 c 0 c_0 c0,默认值应该是零张量,需要自定义的话,形状是(num_layers, batch_size, hidden_size),如果是双向,则是2倍的num_layers,

    下面这个例子里说明的是,hidden_output其实是最后一个LSTM层所有 h t h_t ht的合并输出,final_states则是每一个LSTM层的最后一个单元格的输出,即每一层的(h_n, c_n)。

import torch
from torch.nn import Module, Embedding, Linear, Dropout, LSTM, GRU, Sigmoid, CrossEntropyLoss, functional as F

x = torch.FloatTensor(32, 4, 256)
lstm = LSTM(input_size=256,
			hidden_size=128,
			num_layers=2,
			batch_first=True,
			bidirectional=False)

# initial_states = (torch.autograd.Variable(torch.zeros(self.n_layers + int(self.is_bidirectional) * self.n_layers, batch_size, self.d_output)).to(DEVICE),
                  # torch.autograd.Variable(torch.zeros(self.n_layers + int(self.is_bidirectional) * self.n_layers, batch_size, self.d_output)).to(DEVICE))

hidden_output, final_states = lstm(x)
print(hidden_output.shape)		# (batchsize, seq_len, (1+bidirectional)*hidden_size)
print(final_states[0].shape)	# ((1+bidirectional)*num_layers, batchsize, hidden_size)
print(final_states[1].shape)	# ((1+bidirectional)*num_layers, batchsize, hidden_size)


print(hidden_output[:, -1, :].squeeze() == final_states[0][-1, :, :].squeeze())	# True
print(hidden_output[:, -1, :].squeeze() == final_states[1].squeeze())	# False
  • RNN网络类似,可以指定初始状态 h 0 h_0 h0,但是final_states只有一个值:

    rnn = RNN(input_size=256, hidden_size=128, num_layers=2, batch_first=True, bidirectional=False)
    
    hidden_output, final_states = rnn(x)
    
    print(hidden_output.shape) # [batchsize, seqlen, hidden_size]
    print(final_states.shape)	# [num_layers, batchsize, hiddensize]
    
    • input_size – 输入x的特征数量。
    • hidden_size – 隐层的特征数量。
    • num_layers – RNN的层数。
    • nonlinearity – 指定非线性函数使用tanh还是relu。默认是tanh
    • bias – 如果是False,那么RNN层就不会使用偏置权重 b i h b_ih bih b h h b_hh bhh,默认是True
    • batch_first – 如果True的话,那么输入Tensor的shape应该是[batch_size, time_step, feature],输出也是这样。
    • dropout – 如果值非零,那么除了最后一层外,其它层的输出都会套上一个dropout层。
    • bidirectional – 如果True,将会变成一个双向RNN,默认为False
  • GRU网络:

    gru = GRU(input_size=256, hidden_size=128, num_layers=2, batch_first=True, bidirectional=False)
    
    hidden_output, final_states = gru(x)
    
    print(hidden_output.shape) # [batchsize, seqlen, hidden_size]
    print(final_states.shape)	# [num_layers, batchsize, hiddensize]
    
    

    GRU的输出与RNN完全相同,但是也可以指定初始状态 h 0 h_0 h0,GRU是只有一条线贯穿始终,与LSTM有所区别。

  • LSTMCell,RNNCell,GRUCell

    这个就是每一个LSTM层中包含的若干LSTM的单元的一个,具体而言输入输出形状:

    from torch.nn import LSTMCell, RNNCell, GRUCell
    import torch
    
    x = torch.FloatTensor(32, 256)	# (batchsize, input_size)
    h_0 = torch.FloatTensor(32, 512) # (batchsize, hidden_size)
    rnncell = RNNCell(input_size=256, hidden_size=512, bias=True, nonlinearity='tanh')
    h_1 = rnncell(x, h_0)	# (batchsize, hidden_size)
    
    x = torch.FloatTensor(32, 256)	# (batchsize, input_size)
    h_0 = torch.FloatTensor(32, 512) # (batchsize, hidden_size)
    grucell = GRUCell(input_size=256, hidden_size=512, bias=True)
    h_1 = grucell(x, h_0)	# (batchsize, hidden_size)
    
    x = torch.FloatTensor(32, 256)	# (batchsize, input_size)
    h_0 = torch.FloatTensor(32, 512) # (batchsize, hidden_size)
    c_0 = torch.FloatTensor(32, 512) # (batchsize, hidden_size)
    
    lstmcell = LSTMCell(input_size=256, hidden_size=512, bias=True)
    
    h_1, c_1 = lstmcell(x, (h_0, c_0))	# both (batchsize, hidden_size)
    

20220907

  • 疼,大臂疼、肩膀疼、大腿前侧疼、大腿后侧疼、屁股疼。起不来床,下不了楼,蹲不了坑,但也得硬着头皮一大早去实验室,晚上临走前操场小跑了2圈,这种机械式的routine实在是累死,我决定至少九月份还是要坚持训练的,之后实在精力不够只能慢慢降低跑步占比,真的是被生活逼上绝路。

TreeRNN一个Cell的编码器:

class TreeRNNEncoder(Module):
	"""2022/08/26 11:50:47
	为每个句法树节点建立独立的RNN网络, 输入为句法树节点对应下面的若干分词或从句的表示, 维数为(n_words, input_size), 输出为(output_size)
	:param args: QAModelConfig类型的配置
		   args.tree_rnn_encoder_tag_name			: 节点名称, setting.py中STANFORD_SYNTACTIC_TAG中定义的名称
													  这里区分是否为STANFORD_POS_TAG(叶子节点)
													  叶子节点必然汇入非叶节点, 非叶节点也汇入非叶节点
													  因此叶子节点的输入输出维数可以不同, 非叶节点的输入输出维数必须相同
		   args.tree_rnn_encoder_rnn_type			: 使用的RNN编码器, 可选值为RNN, LSTM, GRU
		   args.tree_rnn_encoder_num_layers			: RNN编码器堆叠的层数
		   args.tree_rnn_encoder_bidirectional		: RNN编码器双向标志
		   args.tree_rnn_encoder_squeeze_strategy	: 压缩RNN输出的strategy, 可选值有mean(取均值), final(只取最后一层输出), fixed_weight(固定权重加权平均), variable_weight(可变权重加权平均, 即作为参数训练)"""
	def __init__(self, args, tag_name):	
		super(TreeRNNEncoder, self).__init__()
		self.args = deepcopy(args)
		self.tag_name = tag_name
		
		# 句法树根节点
		if self.tag_name == 'ROOT':											
			self.input_size = self.args.tree_rnn_encoder_leaf_hidden_size
			
		# 词性标注: 即叶子节点
		elif self.tag_name in STANFORD_POS_TAG:							
			if self.args.document_embedding is not None:
				# 文档嵌入: 禁用, 句法树模型只能使用词嵌入
				raise Exception('Please use word embedding rather than document model !')
			elif self.args.word_embedding is None:
				# 顺序编码值嵌入: 需要建立嵌入层
				self.embedding_layer = Embedding(num_embeddings=pandas.read_csv(REFERENCE_TOKEN2ID_PATH, sep='\\t', header=0).shape[0], 
												 embedding_dim=self.args.default_embedding_size)
				self.input_size = self.args.default_embedding_size
			elif self.args.word_embedding == 'word2vec':
				# word2vec词嵌入
				self.embedding_layer = None
				self.input_size = self.args.size_word2vec			
			elif self.args.word_embedding == 'fasttext':
				# fasttext词嵌入
				self.embedding_layer = None
				self.input_size = self.args.size_fasttext
			else:
				raise Exception(f'Unknown word embedding: self.args.word_embedding')

		# 句法结构标注: 即非叶节点
		elif self.tag_name in STANFORD_SYNTACTIC_TAG:						
			self.input_size = self.args.tree_rnn_encoder_leaf_hidden_size	
		else: 
			raise Exception(f'Unknown syntactic tag: tag_name')	
		
		self.output_size = self.args.tree_rnn_encoder_leaf_hidden_size
		self.sequence_encoder = eval(self.args.tree_rnn_encoder_rnn_type)(input_size	= self.input_size, 
																		  hidden_size	= int(self.output_size / (1 + self.args.tree_rnn_encoder_bidirectional)),	# 双向RNN的输出维数是hidden_size的2倍
																		  num_layers	= self.args.tree_rnn_encoder_num_layers,
																		  batch_first	= True, 
																		  bidirectional	= self.args.tree_rnn_encoder_bidirectional)
													
		# 2022/09/05 22:12:01 这里我想的是可以考虑在variable_weight的情况下引入一个Parameter类型的一维权重张量self.squeeze_weight
		# 2022/09/05 22:12:01 但是需要确保该张量的元素和为1, 这比较麻烦, 或许可以在损失函数中引入self.squeeze_weight的正则项, 但是这太麻烦了
		# 2022/09/05 22:12:01 于是我决定暂时不实现这种情况了
		if self.args.tree_rnn_encoder_squeeze_strategy in ['mean', 'final']:
			self.args.squeeze_weight = None
		elif self.args.tree_rnn_encoder_squeeze_strategy == 'fixed_weight':
			# 2022/09/06 14:21:45 默认的固定权重定义为等比数列1/2, 1/4, 1/8, ...
			self.squeeze_weight = torch.Parameter(torch.FloatTensor([2 ** (-i) for i in range(self.args.default_max_child)]), requires_grad=False)
		elif self.args.tree_rnn_encoder_squeeze_strategy == 'variable_weight':
			# 2022/09/06 14:21:45 即将上面默认的固定权重改为可修改(requires_grad=True)
			# self.squeeze_weight = torch.Parameter(torch.FloatTensor([2 ** (-i) for i in range(1, self.args.default_max_child)]), requires_grad=True)
			raise NotImplementedError
		else:
			raise Exception(f'Unknown squeeze_strategy: self.squeeze_strategy')
		
	def forward(self, x):
		"""2022/09/03 15:58:30
		前馈逻辑: 
		:param x: (batch_size, seq_len, input_size), 目前感觉batchsize或许只能取1"""
		# (batch_size, seq_len, input_size) -> (batch_size, seq_len, (1 + bidirectional) * hidden_size)
		y1, _ = self.sequence_encoder(x)		
		
		# (batch_size, seq_len, (1 + bidirectional) * hidden_size) -> (batch_size, (1 + bidirectional) * hidden_size)
		if self.args.tree_rnn_encoder_squeeze_strategy == 'mean':
			y2 = torch.mean(y1, axis=1)			
		elif self.args.tree_rnn_encoder_squeeze_strategy == 'final':
			y2 = y1[:, -1, :]
		elif self.args.tree_rnn_encoder_squeeze_strategy == 'fixed_weight':
			sequence_length = x.shape[1]
			for i in range(1, sequence_length):
				y2 = y1[: -i, :] * self.squeeze_weight[i - 1]
			y2 += y1[:, 0, :] * self.squeeze_weight[sequence_length - 2]# 初始位置的权重跟下一个位置的权重是相同的, 即1/2, 1/4, 1/8, 1/8
		elif self.args.tree_rnn_encoder_squeeze_strategy == 'variable_weight':
			# 2022/09/06 22:41:04 实现逻辑与fixed_weight的情况是一样的
			raise NotImplementedError
		else:
			raise Exception(f'Unknown squeeze_strategy: self.squeeze_strategy')
		return y2

眼下有两个问题,其一是每个样本句法树不同,因此需要即时地根据句法树生成大的模型架构,这似乎就不能批训练;其二是这种情况下,模型被划分为两部分,主模型部分是可以预先设定好的,但是句法树部分是随数据变化的,这就要同时训练多个模型,那么多个模型之间的梯度是否在torch中是可以传递的?其三,我看了一下句法树里的子女数,大部分都不超过5,有计划根据子女数再进一步细化模型。


20220907-20220908

  • 这两天在搬工位,从之前流动工位搬到现在博士的固定工位,重装工位上的电脑,把一些不想装在PC机上的软件都弄在工位上。有一说一实验室的电脑配置是真的好,院里这个钱还真是舍得花。
  • 昨天实在疼得难受,已经从四肢疼到前胸后背都发麻,偷个懒晚上没去训练,而且特别累,回宿舍11点就睡了。今天再偷懒就实在是说不过去了,尽管依然很疼,但是还是可以凭借意志克服酸痛,权且只跑了五圈,大腿和心肺都不太能耐受得住,想要在10月之前恢复到七八成,任重道远。

关于WIN10重置系统后会卡在开机要求设定账户的交互界面上的解决方案:
其实在每一个位置都有可能卡住,只不过这里最容易卡,就是键盘输不了字,点左下角那个按钮调出软键盘也不行,然后就无限重启。

整了半天解决方案是这样的:

  • shift+F10可以调出cmd命令行
  • 输入taskmgr调出任务管理器,结束掉Microsoft IME任务
  • 此时就可以通过WIN+U调出设置界面(不结束Microsoft IME是不行的)
  • 这里就比较迷了,其实现在WIN+E也可以调出资源管理器,然后就可以正常使用了,但是你跳不过这个界面,所以还是需要去设置-账户 里设置新账户,一般来说这样就可以了,但是设置新账户的时候可能还是会卡,我的建议就是卡住就用任务管理器结束掉设置任务,然后多试几次,总是可以搞定的。

20220909-20220911

  • 股四头肌恢复了九成,至少走路不踉跄(其实这三天每天都还是5分配左右的慢跑在过渡),于是下午兴致冲冲去跑,然鹅又是不到5圈就熄火了,均配4’00",想去年连跑53圈不补给不休息,这么多天实力恢复了不到一成… 汗,倒是看到去年经常碰到一起跑的那个小伙子实力大增,也开始光着上半身裸奔,看步伐竟然是很稳定的内旋前脚掌,虽然腾空有点高不是很经济,但是的确是比去年强了好多,至少在这个学校里能前脚掌的人就已经是凤毛麟角了,敢裸奔总归是对自己的实力有点信心的。
  • 昨天中秋出去玩了大半天,错过了队内训练(以及东哥发的月饼),我说这帮人怎么这么卷,中秋节还顶着大太阳训练。发现不少博士(不只是我们院)平时是打羽毛球的,或许以后要转行咯(笑),老年人要有老年人的亚子。话虽如此,但是计划这个月还是抓紧恢复训练,15号争取有4~5km的体能,月底冲击10km。

提一个小问题,假如我想得到下面这棵树的一个层次遍历结果:


具体而言输入输出是这样的:

input: (ROOT (IP (NP (CP (IP (NP (DNP (ADJP (ADVP (AD 将)) (ADJP (JJ 传统))) (DEG 的)) (NP (NN 篇目))) (VP (ADVP (AD 首次)) (VP (VV 改为) (NP (NP (NN 名例) (PU 、) (NN 吏) (PU 、) (NN 户) (PU 、) (NN 礼) (PU 、) (NN 兵)) (PU 、) (NP (NR 刑)) (PU 、) (NP (NN 工各律)))))) (DEC 的))) (VP (VC 是))))
output: [
	[(ROOT)],
	[(IP)],
	[(PP, PU, NP, VP, PU)],
	[(P, NP), (,), (QP, NP), (ADVP, VP), (。)],
	[(根据), (NN, NN), (), (CD), (NN), (AD), (VV), ()],
	[(), (新闻), (报导), (大部分), (中学生), (都), (近视)]
]

这是一个简单的树层次遍历,但是输入并不是一棵树,而是字符串,你当然可以先把字符串变成树,但是我们希望最好只遍历input字符串一次即可得到output,注意output里的层次遍历结果必然满足后一层的()对数量恰好等于上一层非空节点的数量(比如最后一层有7个()对,倒数第二层的非空节点为[根据, NN, NN, CD, NN, AD, VV]共计7个)

以上是关于连载囚生CYの备忘录(20220906-)的主要内容,如果未能解决你的问题,请参考以下文章

继续更新囚生CYの备忘录(20220906-)

完结囚生CYの备忘录(20220525-20220813)

完结囚生CYの备忘录(20220525-20220813)

更新囚生CYの备忘录(20230216~)

更新囚生CYの备忘录(20230216~)

更新囚生CYの备忘录(20221121-)