序言
首马备赛篇——伤后半年的第二次挑战,目标依然是破三。
昨日赛后,明显状态好转,之前还是心理作用大一些。默认自己不行,就真的不行了。
尽管事难如愿,但开始自我怀疑的时候,就已经失败了。
常想一二,不思八九。
老家伙们该退场了。
文章目录
- 序言
- 20241030
- 20241031
- 20241101
- 20241102
- 20241103
- 20241104
- 20241105
- 20241106
- 20241107
- 20241108
- 20241109
- 20241110
- 20241111
- 20241112
- 20241113
- 20241114
- 20241115
- 20241116(首马前夕)
- 20241117(完篇)
-
- 一:楔子
- 二:风暴
- 三:摆烂?拼命?
- 四:写在30KM后
- 五:行百里半九十
- 六:终点
- 后记
20241030
晚上九点下去摇了会儿,一共8K多,把月跑量补到180K,月均配4’10",明后台风登陆,这个月大概率是凑不到200K了。
逮到DGL,她是真的马不停蹄,练得太狠了,昨天参加800米、3000米、4×400米三项,今天又是5组800米间歇,我给她带了一组,前后还有一共3K多的慢跑,真佩服年轻人的精力。
PS:这次校运会照片真的不少,有5000多张,学校难得做了回人事。
DQN进阶之Nature DQN:
在NIPS2013中,DQN的目标Q值计算公式:
y j = { R j i s e n d j i s t r u e R j + γ max a ′ Q ( ϕ ( S j ′ ) , A j ′ , w ) i s e n d j i s f a l s e y_j=\left\{\begin{aligned} &R_j&& isend_j is true\\ &R_j+\gamma \max_{a'}Q(\phi(S_j'), A_j', w)&& isend_j is false \end{aligned}\right. yj=⎩
这里目标Q值的计算使用到了当前要训练的Q网络参数来计算 Q ( ϕ ( S j ′ ) , A j ′ , w ) Q(\phi(S_j'), A_j', w) Q(ϕ(Sj′),Aj′,w),而实际上,我们又希望通过 y j y_j yj来后续更新Q网络参数,这样两者循环依赖,迭代起来相关性太强,不利于算法收敛。因此,一个改进版的Nature DQN尝试用两个Q网络来减少目标Q值计算和要更新Q网络参数之间的依赖关系。
Nature DQN使用了两个Q网络,一个当前Q网络
Q Q Q用来选择动作,更新模型参数,另一个目标Q网络 Q ′ Q' Q′用于计算目标Q值。目标Q网络的网络参数不需要迭代更新,而是每隔一段时间从当前Q网络 Q Q Q,复制过来,即延时更新,这样可以减少目标Q值和当前的Q值相关性。
要注意的是,两个Q网络的结构是一模一样的。这样才可以复制网络参数。
首先是Q网络,上一篇的DQN是一个三层的神经网络,而这里我们有两个一样的三层神经网络,一个是当前Q网络,一个是目标Q网络,网络的定义部分如下:代码
def create_Q_network(self):
# input layer
self.state_input = tf.placeholder("float", [None, self.state_dim])
# network weights
with tf.variable_scope('current_net'):
W1 = self.weight_variable([self.state_dim,20])
b1 = self.bias_variable([20])
W2 = self.weight_variable([20,self.action_dim])
b2 = self.bias_variable([self.action_dim])
# hidden layers
h_layer = tf.nn.relu(tf.matmul(self.state_input,W1) + b1)
# Q Value layer
self.Q_value = tf.matmul(h_layer,W2) + b2
with tf.variable_scope('target_net'):
W1t = self.weight_variable([self.state_dim,20])
b1t = self.bias_variable([20])
W2t = self.weight_variable([20,self.action_dim])
b2t = self.bias_variable([self.action_dim])
# hidden layers
h_layer_t = tf.nn.relu(tf.matmul(self.state_input,W1t) + b1t)
# Q Value layer
self.target_Q_value = tf.matmul(h_layer,W2t) + b2t
对于定期将目标Q网络的参数更新的代码如下面两部分:
t_params = tf.get_collection(tf.GraphKeys.GLOBAL_VARIABLES, scope='target_net')
e_params = tf.get_collection(tf.GraphKeys.GLOBAL_VARIABLES, scope='current_net')
with tf.variable_scope('soft_replacement'):
self.target_replace_op = [tf.assign(t, e) for t, e in zip(t_params, e_params)]
def update_target_q_network(self, episode):
# update target Q netowrk
if episode % REPLACE_TARGET_FREQ == 0:
self.session.run(self.target_replace_op)
#print('episode '+str(episode) +', target Q network params replaced!')
此外,注意下我们计算目标Q值的部分,这里使用的目标Q网络的参数,而不是当前Q网络的参数:
# Step 2: calculate y
y_batch = []
Q_value_batch = self.target_Q_value.eval(feed_dict={
self.state_input:next_state_batch})
for i in range(0,BATCH_SIZE):
done = minibatch[i][4]
if done:
y_batch.append(reward_batch[i])
else :
y_batch.append(reward_batch[i] + GAMMA * np.max(Q_value_batch[i]))
20241031
南马号码簿公布,不幸分在F区,SABC第一枪7:30出发,DEFG第二枪7:45出发,虽然两枪相隔很长时间,但一共24000人实在是太多了,AK在A区,肯定是带不了我,想破三只能靠自己。
看windy上的预报从今晚开始,暴雨一直要下到明晚,于是晚上吃完饭就润回去了,食物饮水都备好,已经做好明天宅一天的准备。还没在上海经历过台风天,今年自从魔都结界破了之后,这已经是第三回台风来袭,前两回都是躲过了,这次不知道会是个啥子情况,emmm
解决git拉取文件字符不合法问题:如有些文件包含星号,在
忽略转义字符
git config --global core.protectNTFS false
禁用换行符转换
git config --global core.autocrlf false
中文文件名,乱码问题。设为false的话,就不会对0x80以上的字符进行quot
git config --global core.quotepath false
DQN进阶之DDQN
在DDQN之前,基本上所有的目标Q值都是通过贪婪法直接得到的,无论是Q-Learning,DQN(NIPS 2013)还是Nature DQN,都是如此。
使用max虽然可以快速让Q值向可能的优化目标靠拢,但是很容易过犹不及,导致过度估计(Over Estimation),所谓过度估计就是最终我们得到的算法模型有很大的偏差(bias)。为了解决这个问题, DDQN通过解耦目标Q值动作的选择和目标Q值的计算这两步,来达到消除过度估计的问题。
DDQN和Nature DQN一样,也有一样的两个Q网络结构。在Nature DQN的基础上,通过解耦目标Q值动作的选择和目标Q值的计算这两步,来消除过度估计的问题。
在Nature DQN中,目标Q值计算公式为:
y j = R j + γ max a ′ Q ′ ( ϕ ( S j ′ ) , A j ′ , w ′ ) y_j= R_j+\gamma \max_{a'}Q'(\phi(S_j'), A_j', w') yj=Rj+γa′maxQ′(ϕ(Sj′),Aj′,w′)
在DDQN中,不再是在目标Q网络中找各个动作的最大Q值,而是先在当前Q网络中找最大Q值对应的动作:
a max ( S j ′ , w ) = arg max a ′ Q ( ϕ ( S j ′ ) , a , w ) a^{\max}(S_j',w)=\arg\max_{a'}Q(\phi(S_j'), a, w) amax(Sj′,w)=arga′maxQ(ϕ(Sj′),a,w)
然后利用选出来的 a max ( S j ′ , w ) a^{\max}(S_j',w) amax(Sj′,w)在目标网络中计算目标Q值,即:
y j = R j + γ max a ′ Q ′ ( ϕ ( S j ′ ) , arg max a ′ Q ( ϕ ( S j ′ ) , a , w ) , w ′ ) y_j= R_j+\gamma \max_{a'}Q'(\phi(S_j'), \arg\max_{a'}Q(\phi(S_j'), a, w), w') yj=Rj+γa′maxQ′(ϕ(Sj′),arga′maxQ(ϕ(Sj′),a,w),w′)
其余与Nature DQN完全相同
代码只有一个地方不一样,就是计算目标Q值的时候,如下:
# Step 2: calculate y
y_batch = []
current_Q_batch = self.Q_value.eval(feed_dict={
self.state_input: next_state_batch})
max_action_next = np.argmax(current_Q_batch, axis=1)
target_Q_batch = self.target_Q_value.eval(feed_dict={
self.state_input: next_state_batch})
for i in range(0,BATCH_SIZE):
done = minibatch[i][4]
if done:
y_batch.append(reward_batch[i])
else :
target_Q_value = target_Q_batch[i, max_action_next[i]]
y_batch.append(reward_batch[i] + GAMMA * target_Q_value)
而之前的Nature DQN这里的目标Q值计算是如下这样的:
# Step 2: calculate y
y_batch = []
Q_value_batch = self.target_Q_value.eval(feed_dict={
self.state_input:next_state_batch})
for i in range(0,BATCH_SIZE):
done = minibatch[i][4]
if done:
y_batch.append(reward_batch[i])
else :
y_batch.append(reward_batch[i] + GAMMA * np.max(Q_value_batch[i]))
20241101
小风小雨,完全比不上上次的破坏力,还指望把田径场刚修好的围栏再吹倒,到晚上已经基本复原。
跑休两日,明日准备起早30K,本周末也是柴古越野和市运会,会是个好天气。
Prioritized Replay DQN
在Prioritized Replay DQN之前,在采样的时候,我们是一视同仁,在经验回放池里面的所有的样本都有相同的被采样到的概率。
注意到在经验回放池里面的不同的样本由于TD误差的不同,对我们反向传播的作用是不一样的。TD误差越大,那么对我们反向传播的作用越大。而TD误差小的样本,由于TD误差小,对反向梯度的计算影响不大。在Q网络中,TD误差就是目标Q网络计算的目标Q值和当前Q网络计算的Q值之间的差距。这样如果TD误差的绝对值 ∣ δ ∣ |\delta| ∣δ∣较大的样本更容易被采样,则我们的算法会比较容易收敛。下面我们看看Prioritized Replay DQN的算法思路。
Prioritized Replay DQN根据每个样本的TD误差绝对值 ∣ δ ∣ |\delta| ∣δ∣,给定该样本的优先级正比于 ∣ δ ∣ |\delta| ∣δ∣将这个优先级的值存入经验回放池。回忆下之前的DQN算法,我们仅仅只保存和环境交互得到的样本状态,动作,奖励等数据,没有优先级这个说法。
由于引入了经验回放的优先级,那么Prioritized Replay DQN的经验回放池和之前的其他DQN算法的经验回放池就不一样了。因为这个优先级大小会影响它被采样的概率。在实际使用中,我们通常使用SumTree这样的二叉树结构来做我们的带优先级的经验回放池样本的存储。
具体的SumTree树结构如下图:
所有的经验回放样本只保存在最下面的叶子节点上面,一个节点一个样本。内部节点不保存样本数据。而叶子节点除了保存数据以外,还要保存该样本的优先级,就是图中的显示的数字。对于内部节点每个节点只保存自己的儿子节点的优先级值之和,如图中内部节点上显示的数字。
这样保存有什么好处呢?主要是方便采样。以上面的树结构为例,根节点是42,如果要采样一个样本,那么我们可以在[0,42]之间做均匀采样,采样到哪个区间,就是哪个样本。比如我们采样到了26, 在(25-29)这个区间,那么就是第四个叶子节点被采样到。而注意到第三个叶子节点优先级最高,是12,它的区间13-25也是最长的,会比其他节点更容易被采样到。
如果要采样两个样本,我们可以在[0,21],[21,42]两个区间做均匀采样,方法和上面采样一个样本类似。
def sample(self, n):
b_idx, b_memory, ISWeights = np.empty((n,), dtype=np.int32), np.empty((n, self.tree.data[0].size)), np.empty((n, 1))
pri_seg = self.tree.total_p / n # priority segment
self.beta = np.min([1., self.beta + self.beta_increment_per_sampling]) # max = 1
min_prob = np.min(self.tree.tree[-self.tree.capacity:]) / self.tree.total_p # for later calculate ISweight
if min_prob == 0:
min_prob = 0.00001
for i in range(n):
a, b = pri_seg * i, pri_seg * (i + 1)
v = np.random.uniform(a, b)
idx, p, data = self.tree.get_leaf(v)
prob = p / self.tree.total_p
ISWeights[i, 0] = np.power(prob/min_prob, -self.beta)
b_idx[i], b_memory[i, :] = idx, data
return b_idx, b_memory, ISWeights
除了采样部分,要注意的就是当梯度更新完毕后,我们要去更新SumTree的权重,代码如下,注意叶子节点的权重更新后,要向上回溯,更新所有祖先节点的权重。
def get_leaf(self, v):
"""
Tree structure and array storage:
Tree index:
0 -> storing priority sum
/ \
1 2
/ \ / \
3 4 5 6 -> storing priority for transitions
Array type for storing:
[0,1,2,3,4,5,6]
"""
parent_idx = 0
while True: # the while loop is faster than the method in the reference code
cl_idx = 2 * parent_idx + 1 # this leaf's left and right kids
cr_idx = cl_idx + 1
if cl_idx >= len(self.tree): # reach bottom, end search
leaf_idx = parent_idx
break
else: # downward search, always search for a higher priority node
if v <= self.tree[cl_idx]:
parent_idx = cl_idx
else:
v -= self.tree[cl_idx]
parent_idx = cr_idx
data_idx = leaf_idx - self.capacity + 1
return leaf_idx, self.tree[leaf_idx], self.data[data_idx]
Prioritized Replay DQN和DDQN相比,收敛速度有了很大的提高,避免冗余的迭代。
20241102
30K挑战失败,10K多有点岔气,均配407,停下来休息了会儿,接着又跑了2K@404还是不太行,感觉肯定坚持不到30K,太阳也有点大,索性还是不折磨自己了。
节奏尚可,感觉410的配肯定是能坚持很久,但是也明显感觉得到目前状态不如三月,全程心率几乎都在170左右,破三的信心依然不是很足。
下午白辉龙带YY跑6000米+200米×20组间歇,配速3分整,间歇200米慢跑,量非常大,实际上200米都跑到了35秒以内,最后一组30秒,他校运会失利后还是很受刺激的,下定决心要狠狠练,上限注定很高。
战报:
AK柴古55K组6小时59分完赛,虽然不如去年的表现,但排名倒是上升了(去年6小时34分,排名49,今年47),喜提红马甲。
市运会5000米,小崔17分55秒,中途紧跟隔壁车浩然,虽然最后被拉爆了(车神16分38秒,发挥真的稳);AX18分33秒,AX又数错圈,少跑一圈停下来五六秒才反应过来,估计是被小崔传染了。两人表现皆不如校运会,不过也是正常发挥,没有人能一直PB的。
其余未知,不知道DGL的1500米和3000米怎么样,可能是明天,反正是没听到消息,之前听说可能会放掉保接力,不过我觉得她练这么狠肯定还是希望在个人项目上跑个好成绩。
对偶DQN
在Prioritized Replay DQN中,优化经验回放池按权重采样来优化算法。在Dueling DQN中,则通过优化神经网络的结构来优化算法。
Dueling DQN考虑将Q网络分成两部分,第一部分是仅与状态 S S S相关,与动作 A A A无关,这部分叫作价值函数,记作 V ( S , w , α ) V(S,w,\alpha) V(S,w,α),第二部分同时与状态 S S S和 A A A有关,这部分叫作优势函数(advantage function)部分,记作 A ( S , A , w , β ) A(S,A,w,\beta) A(S,A,w,β),做种的价值函数为:
Q ( S , A , w , α , β ) = V ( S , w , α ) + A ( S , A , w , β ) Q(S,A,w,\alpha,\beta)=V(S,w,\alpha)+A(S,A,w,\beta) Q(S,A,w,α,β)=V(S,w,α)+A(S,A,w,β)
由于Q网络的价值函数被分为两部分,因此Dueling DQN的网络结构也和之前的DQN不同。
在前面讲到的DDQN等DQN算法中,我使用了一个简单的三层神经网络:一个输入层,一个隐藏层和一个输出层。如下左图所示:
在Dueling DQN中,后面加了两个子网络结构,分别对应上面上到价格函数网络部分和优势函数网络部分。对应上面右图所示。最终Q网络的输出由价格函数网络的输出和优势函数网络的输出线性组合得到。
为了辨识两部分函数各自的作用,实际使用的组合公式如下:
Q ( S , A , w , α , β ) = V ( S , w , α ) + ( A ( S , A , w , β ) − 1 A ∑ a ′ ∈ A A ( S , a ′ , w , β ) ) Q(S,A,w,\alpha,\beta)=V(S,w,\alpha)+\left(A(S,A,w,\beta)-\frac1{\mathcal{A}}\sum_{a'\in\mathcal{A}}A(S,a',w,\beta)\right) Q(S,A,w,α,β)=V(S,w,α)+(A(S,A,w,β)−A1a′∈A∑A(S,a′,w,β))
Cartpole案例对偶DQN代码
这里我们重点关注Dueling DQN和Nature DQN的代码的不同之处。也就是网络结构定义部分,主要的代码如下,一共有两个相同结构的Q网络,每个Q网络都有状态函数和优势函数的定义,以及组合后的Q网络输出,主要是idden layer for state value
和hidden layer for action value
及Q Value layer
的部分
def create_Q_network(self):
# input layer
self.state_input = tf.placeholder("float", [None, self.state_dim])
# network weights
with tf.variable_scope('current_net'):
W1 = self.weight_variable([self.state_dim,20])
b1 = self.bias_variable([20])
# hidden layer 1
h_layer_1 = tf.nn.relu(tf.matmul(self.state_input,W1) + b1)
# hidden layer for state value
with tf.variable_scope('Value'):
W21= self.weight_variable([20,1])
b21 = self.bias_variable([1])
self.V = tf.matmul(h_layer_1, W21) + b21
# hidden layer for action value
with tf.variable_scope('Advantage'):
W22 = self.weight_variable([20,self.action_dim])
b22 = self.bias_variable([self.action_dim])
self.A = tf.matmul(h_layer_1, W22) + b22
# Q Value layer
self.Q_value = self.V + (self.A - tf.reduce_mean(self.A, axis=1, keep_dims=True))
with tf.variable_scope('target_net'):
W1t = self.weight_variable([self.state_dim,20])
b1t = self.bias_variable([20])
# hidden layer 1
h_layer_1t = tf.nn.relu(tf.matmul(self.state_input,W1t) + b1t)
# hidden layer for state value
with tf.variable_scope('Value'):
W2v = self.weight_variable([20,1])
b2v = self.bias_variable([1])
self.VT = tf.matmul(h_layer_1t, W2v) + b2v
# hidden layer for action value
with tf.variable_scope('Advantage'):
W2a = self.weight_variable([20,self.action_dim])
b2a = self.bias_variable([self.action_dim])
self.AT = tf.matmul(h_layer_1t, W2a) + b2a
# Q Value layer
self.target_Q_value = self.VT + (self.AT - tf.reduce_mean(self.AT, axis=1, keep_dims=True))
20241103
晚上仍然试跑30K,但还是力不能支,5K@349+1.2K@349+5K@354+2K@359,只能说差强人意。
现在感觉又是个瓶颈期,综合实力肯定不及上半年。一个是节奏不行,自从上半年伤后,350以外的配速我都不太适应用前脚掌跑,觉得垂直振幅大,很费劲,但是X3P这双鞋设计来就是用前掌跑,导致现在越跑越别扭。另一个是压不住速度,起手总是跑到350以内,然后自我感觉还很良好,心率蹭蹭得涨,三四公里之后就不太行了,坚持不了长距离。
第二个5K带的XR,本来前2K很稳定的410,感觉很轻松,结果这家伙很快就不行了,慢慢他就加到350以内冲掉了,后面我也是只能顶到5K。最后2K带的YY,起手420,本来还想补个10K的,但是他昨天被白辉龙榨干了,两圈人就没影了,后面我也感觉心肺扛不太住,也便是2K就冲掉了。
还剩两周,最后一周肯定是要减量,所以接下来一周是最后挣扎的时间了,感觉可能又是看临场发挥了。
战报:
首先,2009级经济的李朝松又双叒叕PB了!两周前刚突破232达标一级,今天北马又以2:30:48成功PB一分钟,简直太夸张了。
然后,2013级金融的孙大伟,北马净时间2:59:59极限破三,运气太好了,他成为了上财第十位破三的校友,而且看起来2004级财管的刘守征很可能会成为下一个破三的人选,而我还在苦苦挣扎,属实难绷。
小崔市运会1500米摔了一跤还跑了4分39秒,只比嘉伟校运会慢3秒,顶级天赋,伟大,无需多言,但这次市运会前八门槛是4分29秒。他跟白辉龙注定成为接下来的双子星,上财中长跑未来的希望。
HJY女子800米2分49秒,不如去年的2分47秒,但也进前八了;DGL的1500米不知道跑了多少,这也是她最后一次了,她之后也不准备再练了,我觉得6分钟以内应该问题不大吧,但是对手很强,同济的两个都能跑到5分10秒以内,太夸张了。
其余似乎没有很突出的成绩,不过今年北马确实很多人都PB了,可能有幸存者偏差的缘故,但估计确实是天时地利人和都在,陈龙、房博,都如愿跑进220达标健将了,大正也PB了,比柏林快十几秒大概。
关于位置编码的排列不变性与排列等变性
- input embedding + encoder + decoder
- position encoding is in input embedding
- rnn 天然编码了位置信息
- h t = f ( h t − 1 , x t ) h_{t}=f(h_{t-1},x_t) ht=f(ht−1,xt)
- f f f 是非线性激活函数, h t − 1 h_{t-1} ht−1 是前一时间步的隐藏状态, x t x_t xt 是当前时间步的输入。由于 h t h_t ht 依赖于 h t − 1 h_{t-1} ht−1,而 h t − 1 h_{t-1} ht−1 又依赖于 h t − 2 h_{t-2} ht−2,以此类推,隐藏状态包含了从初始时间步到当前时间步的所有历史信息。这种递归结构使得位置信息被隐式地编码在隐藏状态中。
- RNN 通过其递归结构隐式地编码位置信息,而 Transformer 需要通过显式添加位置编码来获取位置信息。
- 如果在 Transformer Encoder 中没有使用位置编码,那么模型将无法区分输入序列中各个词的顺序,这实际上等同于一个词袋(Bag of Words)模型。原因是 Transformer 的自注意力机制本质上是对输入的加权求和,而没有位置编码的情况下,模型无法获取任何位置信息。
- Permutation Equivariance(排列等变)
- Permutation Equivariance(排列等变):如果对输入序列进行某种排列,模型的输出将以相同的方式被排列。
- Permutation Invariance(排列不变):对输入序列的排列不会影响模型的输出,即输出与输入的排列无关。
- 没有位置编码的 Transformer Encoder 并不是排列不变的,而是排列等变的。这意味着如果我们改变输入序列中词的顺序,输出序列中的元素也会按照相同的方式重新排列,但输出本身的数值不会保持不变。
import torch
import torch.nn as nn
torch.manual_seed(42)
# 定义模型参数
vocab_size = 10000 # 词汇表大小
d_model = 512 # 嵌入维度
nhead = 8 # 注意力头数
num_layers = 1 # Transformer Encoder 层数
# RNN
# 定义输入序列
sequence_length = 5 # 序列长度
embedding_dim = 8 # 词嵌入维度
batch_size = 1 # 批大小
original_sequence = torch.randn(batch_size, sequence_length, embedding_dim)
original_sequence.shape # torch.Size([1, 5, 8])
original_sequence
"""
tensor([[[ 1.9269, 1.4873, 0.9007, -2.1055, 0.6784, -1.2345, -0.0431,
-1.6047],
[-0.7521, 1.6487, -0.3925, -1.4036, -0.7279, -0.5594, -0.7688,
0.7624],
[ 1.6423, -0.1596, -0.4974, 0.4396, -0.7581, 1.0783, 0.8008,
1.6806],
[ 0.0349, 0.3211, 1.5736, -0.8455, 1.3123, 0.6872, -1.0892,
-0.3553],
[-1.4181, 0.8963, 0.0499, 2.2667, 1.1790, -0.4345, -1.3864,
-1.2862]]])
"""
permuted_sequence = original_sequence.clone()
permutation = torch.randperm(sequence_length)
permuted_sequence = permuted_sequence[:, permutation, :]
permuted_sequence.shape # torch.Size([1, 5, 8])
permuted_sequence
"""
tensor([[[ 1.9269, 1.4873, 0.9007, -2.1055, 0.6784, -1.2345, -0.0431,
-1.6047],
[-0.7521, 1.6487, -0.3925, -1.4036, -0.7279, -0.5594, -0.7688,
0.7624],
[-1.4181, 0.8963, 0.0499, 2.2667, 1.1790, -0.4345, -1.3864,
-1.2862],
[ 0.0349, 0.3211, 1.5736, -0.8455, 1.3123, 0.6872, -1.0892,
-0.3553],
[ 1.6423, -0.1596, -0.4974, 0.4396, -0.7581, 1.0783, 0.8008,
1.6806]]])
"""
hidden_dim = 8
rnn = nn.RNN(input_size=embedding_dim, hidden_size=hidden_dim, batch_first=True)
ori_output, _ = rnn(original_sequence)
perm_output, _ = rnn(permuted_sequence)
ori_output.shape, perm_output.shape # (torch.Size([1, 5, 8]), torch.Size([1, 5, 8]))
# global mean pooling
ori_output.squeeze(0).mean(dim=0) # tensor([-0.2083, 0.0781, -0.1784, 0.0027, 0.2188, 0.4131, 0.1809, -0.0833], grad_fn=<MeanBackward1>)
perm_output.squeeze(0).mean(dim=0)
"""
tensor([-0.0615, 0.0666, -0.1055, 0.0008, 0.3712, 0.2399, 0.1625, 0.1277],
grad_fn=<MeanBackward1>)
"""
20241104
又是被wyl气晕的下午,周日答辩,不知道最后这活能给他整成啥样。
嘉伟给我找了个好活,周四下午带五月姐和她师弟跑30K,五月姐要求30K@4’55",她师弟则是30K@4’15"(看来这位也是奔着破三去练的,4’15"刚好是全马破三的配速),刚好他们两位也是在备赛南京马拉松,我也正愁没人一起跑30K,真是想睡觉就来枕头。
DGL很贴心地给我同步了市运会成绩,4×400接力第四(4’58",人均75秒以内),虽然3000米比校运会慢了10秒,但1500米5’40"可以说是非常超预期了,比去年要快17秒。两项都刚好是第八名,算是实力和运气都到位了。同济的黄芳1500米5’11",3000米11’07",人比人气死人。
有点被激励到,DGL上个月练得极其可怕,虽然我没去健身房,但是一周好像都要去练两三次负重深蹲,组数相当多,平时又是马不停蹄地间歇和以赛代练,能跑出这种成绩是情理之中了。
PS:本来今天想补下肢力量训练,最后还是十点去实验楼负一层跳绳和跳台阶了(国教不知道在田径场搞什么飞机,搭了一堆棚子不让进),500次台阶交替步+1700次单摇+200次双摇+200次台阶交替步,结束左右各100次提踵拉伸,全是练的小腿和脚踝。国庆之后就一直没做下肢力量的训练,已经断了快一个月,想想可能现在慢跑前掌总感觉支撑不了,原因还是力量不足。
w/o pe
self_attention(perm(x)) = perm(self_attention(x)).
- x: input sequence
- perm:permutation,置换
# 定义嵌入层和 Transformer Encoder
embedding = nn.Embedding(vocab_size, d_model)
# dropout == 0.
encoder_layer = nn.TransformerEncoderLayer(d_model=d_model, nhead=nhead, dropout=0.0)
transformer_encoder = nn.TransformerEncoder(encoder_layer, num_layers=num_layers)
# 生成随机输入序列
seq_len = 10 # 序列长度
input_ids = torch.randint(0, vocab_size, (seq_len,))
# 打乱输入序列
perm = torch.randperm(seq_len)
shuffled_input_ids = input_ids[perm]
perm, torch.argsort(perm)
"""
(tensor([8, 3, 6, 4, 9, 5, 1, 2, 7, 0]),
tensor([9, 6, 7, 1, 3, 5, 2, 8, 0, 4]))
"""
# 获取嵌入表示
embedded_input = embedding(input_ids) # [seq_len, d_model]
embedded_shuffled_input = embedding(shuffled_input_ids)
# Transformer 期望的输入形状为 [seq_len, batch_size, d_model],因此需要调整维度。
# 添加 batch 维度
# [seq_len, 1, d_model]
embedded_input = embedded_input.unsqueeze(1)
embedded_shuffled_input = embedded_shuffled_input.unsqueeze(1)
# 通过 Transformer Encoder
output = transformer_encoder(embedded_input) # [seq_len, 1, d_model]
output_shuffled = transformer_encoder(embedded_shuffled_input)
output.shape, output_shuffled.shape # (torch.Size([10, 1, 512]), torch.Size([10, 1, 512]))
torch.allclose(output, output_shuffled, atol=1e-6) # False
are_outputs_equal = torch.allclose(output.squeeze(1).mean(dim=0), output_shuffled.squeeze(1).mean(dim=0), atol=1e-6)
are_outputs_equal # True
inverse_perm = torch.argsort(perm)
output_shuffled_reordered = output_shuffled[inverse_perm]
torch.allclose(output, output_shuffled_reordered, atol=1e-6) # True
证明两者的结果相同,是不变的。
with pe
# 定义位置编码
class PositionalEncoding(nn.Module):
def __init__(self, d_model, max_len=5000):
super().__init__()
# 创建位置编码矩阵
position = torch.arange(0, max_len).unsqueeze(1)
div_term = torch.exp(torch.arange(0, d_model, 2) * (-torch.log(torch.tensor(10000.0)) / d_model))
pe = torch.zeros(max_len, 1, d_model)
pe[:, 0, 0::2] = torch.sin(position * div_term)
pe[:, 0, 1::2] = torch.cos(position * div_term)
self.pe = pe
def forward(self, x):
# 加性位置编码
x = x + self.pe[:x.size(0)]
return x
# 添加位置编码
pos_encoder = PositionalEncoding(d_model)
embedded_input_pos = pos_encoder(embedded_input)
embedded_shuffled_input_pos = pos_encoder(embedded_shuffled_input)
# 通过 Transformer Encoder
output_pe = transformer_encoder(embedded_input_pos)
output_shuffled_pe = transformer_encoder(embedded_shuffled_input_pos)
output_pe.shape, output_shuffled_pe.shape # (torch.Size([10, 1, 512]), torch.Size([10, 1, 512]))
# global mean pooling
torch.allclose(output_pe.squeeze(1).mean(dim=0), output_shuffled_pe.squeeze(1).mean(dim=0), atol=1e-6) # False
下面我们重点证明:self_attention(perm(x)) = perm(self_attention(x))
P = torch.tensor([[1, 0, 0, 0],
[0, 0, 1, 0],
[0, 1, 0, 0],
[0, 0, 0, 1]])
P @ P.T, P.T @ P
- 设 P P P 是排列矩阵,排列后的输入为: X perm = P X X_{\text{perm}}=PX Xperm=PX
- QKV
- Q perm = P Q , K perm = P K , V perm = P V Q_{\text{perm}}=PQ, K_{\text{perm}}=PK,V_{\text{perm}}=PV Qperm=PQ,Kperm=PK,Vperm=PV
- attention score matrix
- S perm = Q perm K perm T d k = P Q K T P T d k = P ( Q K T d k ) P T = P S P T S_{\text{perm}}=\frac{Q_{\text{perm}}K^T_{\text{perm}}}{\sqrt{d_k}}=\frac{PQK^TP^T}{\sqrt{d_k}}=P\left(\frac{QK^T}{\sqrt{d_k}}\right)P^T=PSP^T Sperm=dk
QpermKpermT=dk PQKTPT=P(dk QKT)PT=PSPT - S = Q K T d k S=\frac{QK^T}{\sqrt{d_k}} S=dk<
- S perm = Q perm K perm T d k = P Q K T P T d k = P ( Q K T d k ) P T = P S P T S_{\text{perm}}=\frac{Q_{\text{perm}}K^T_{\text{perm}}}{\sqrt{d_k}}=\frac{PQK^TP^T}{\sqrt{d_k}}=P\left(\frac{QK^T}{\sqrt{d_k}}\right)P^T=PSP^T Sperm=dk
发布者:admin,转转请注明出处:http://www.yc00.com/web/1754767574a5199717.html
评论列表(0条)