【完结】cyのMemo(20240422~20240608)

序言

胡哥首马在淮安325完赛,他的本硕都在淮安度过,七年的跑步生涯画上句号,真的是很圆满。七年,从180斤瘦到120斤,历经种种,胡哥理解的跑步,不是快,而是稳,他在比赛中从来不追求顶到极限,一旦感觉吃力,就会减速以重回舒适的区间。

现在,看到一些刚开始跑起来的人呐,就像看到从前的自己,有些高兴,却难起波澜。

不管出于什么初心,这总不是件坏事。

放弃不简单,坚持也没有多酷。

但是,我会等,直到那天到来。

(^_^)



20240422

  • 右脚的伤痛缓解,下午试着跑得快些。第一个5000米@4’14",全程能用前掌跑下来。但是,之后就连全掌跑都无法坚持很久,疼得只能用后跟跑,很艰难地顶完了第二个5000米@4’30",心率很高,水平倒退到2021年底。
  • 中途超越一个白衣小伙,他很快就提速反超,我却再也提不起速度,年轻人真是活力四射。名义上,目前手握学校现役最佳半马PB,至少还能保持半年,如今连4分以外的配速都跑得这么吃力,怎么会没有落差呢?补25个弹力带引体,菜就多练。
  • 近期NRC的训练只能鸽了,手头事情也很紧。作为最终考核指标之一,5月24日,NRC会进行场地5000米测试,届时必然是要去,我也两年多没有正式测一回场地5000米了,希望一个月里,能再回巅峰。
    在这里插入图片描述在这里插入图片描述在这里插入图片描述
  1. torchvision.transforms.ToTensor
  • 把一个取值范围是 [ 0 , 255 ] [0,255] [0,255]PIL.Image或者shape ( H , W , C ) (H,W,C) (H,W,C)numpy.ndarray,转换成shape ( C , H , W ) (C,H,W) (C,H,W),取值范围是 [ 0 , 1.0 ] [0,1.0] [0,1.0]torch.FloadTensor;

    • 注意会把channel(大部分图片的channel都是在第三个维度, channel维度值一般为 3 3 3 4 4 4, 即 R G B \rm RGB RGB R G B A \rm RGBA RGBA对应的维度提到了shape的最前面;
    • 注意该变换并不是直接转为张量, 对于 R G B \rm RGB RGB值的图片型的张量, 观察源码可发现会作除以 255 255 255的归一标准化;
    • 不符合上述图片型张量的形式的张量(如输入二维矩阵), 将直接不作任何数值处理直接转为 t o r c h \rm torch torch中的张量;
  • 可以使用torchvision.transforms.ToPILImage作逆变换, 这两个函数互为反函数;

    • 这是一个只针对PIL.Image输入的反函数, 即必然乘以 255 255 255再返回成图片数据类型;
  • 代码示例:

    import cv2
    import torchvision as tv
    
    # torchvision.transforms.ToTensor
    f = tv.transforms.ToTensor()
    numpy2tensor = f(np.array([[0, 1, 2, 3], [4, 5, 6, 7], [8, 9, 10, 11]]))
    image = cv2.imread(r'D:\media\image\wallpaper\1.jpg')
    image2tensor = f(image)
    print(numpy2tensor)
    print(image.shape)
    print(type(image2tensor))
    print(image2tensor.shape)
    
    # torchvision.transforms.ToPILImage
    f1 = tv.transforms.ToTensor()
    f2 = tv.transforms.ToPILImage()
    image = cv2.imread(r'D:\media\image\wallpaper\1.jpg')
    image2tensor = f1(image)
    tensor2image = f2(image2tensor)
    print(type(tensor2image))
    print(np.asarray(tensor2image))
    
  1. torchvision.transforms.Normalize
  • 这也是个很诡异的函数, 目前没有看出到底是怎么进行标准化的, 两个参数分别为meanstd;

    • 我现在看明白了,其实是 x : = ( x − mean ) / std x:=(x-\text{mean})/\text{std} x:=(xmean)/std,这里的meanstd是可以对应 c h a n n e l \rm channel channel数来写的
  • 也只能对图片型张量进行处理

  • 代码示例:

    import torchvision as tv
    
    f1 = tv.transforms.ToTensor()
    f2 = tv.transforms.Normalize([.5], [.5])
    image = cv2.imread(r'D:\media\image\wallpaper\1.jpg')
    image2tensor = f1(image)
    normal_tensor = f2(image2tensor)
    print(image.shape)
    print(image2tensor.shape)
    print(normal_tensor.shape)
    

20240423

  • 下午训练,宋某独自进行万米测试,跑出37’45"(上周四晚,他独自测试5000米,18’22",这是他目前的两项PB),虽是意料之中,但也是让我震惊不已。他重回巅峰后,已直逼我三月巅峰期水平(我目前万米PB37’40",5000米PB18’42",但是我没有正式测过5000米,这个PB只是万米PB的后5000米成绩)。事实上他这个37’45"并不比我差,今天下午太阳很晒,而且我的37’40"是嘉伟带着我跑出来的,而且是3月13日晚上,很凉快。
  • 三点半去练了会儿,5000米@4’12" + 弹力带引体×20个 + 30个正向箭步×4组(+20kg壶铃) + 20个反向箭步×6组(+20kg壶铃)
    • 5000米没什么好说的,下楼才发现鞋子没换,也懒得回实验室换,反正也跑不好,爱咋咋地。已经20多天没有穿碳板跑鞋训练,现在跑这种配速心率都能飙到180bpm以上,弃疗。
    • 引体不能省,每天我都会练20个,早晚把引体拉起来。
    • 试了试队里刚买的壶铃,东哥跟我讲,箭步倒过来走更有用。我也觉得20kg负重箭步做得太轻松,就试了试,果然,倒着走箭步很困难,平衡难以把握,而且起身时发力肌群不太一样,感觉有些支撑不住,一组只能坚持20个,走得歪歪扭扭。以后就正着走过去,再倒着走回来,挺好。
  • 晚上下会去操场遛了5000米@4’30"(最近重新用Keep,各位真的很卷啊,LXY天天10km,DGL也是天天跑,今晚也看到了),XR跟了我一段,有人陪着还是能干到3’50"以内的配速,虽然也顶不了太久。
  • 嘉伟也在练间歇,400米,600米,1000米都有。他的伤还是没好,需要两只跟腱都打肌贴。他上半年两场重要的比赛跑崩,水平倒退,一定很低落,我以前状态不好时,也喜欢没头脑地去跑乱七八糟的间歇,短时间内达到力竭,暗示自己真的已经尽力了。我问他要不要去参加联盟杯,他摇了摇头,我说那五月份就好好休整,现在状态不好没必要硬顶着强度训练,反正也没啥比赛,等六月底我的事情忙完,跟AK再组织一个月夏训,等到下半年,再把失去的东西,重新拿回来。
    在这里插入图片描述在这里插入图片描述在这里插入图片描述

numpy 提取 子数组
在NumPy中,可以使用多种方法来提取子数组。以下是一些常用的方法:

使用索引:

import numpy as np
 
arr = np.array([1, 2, 3, 4, 5, 6])
sub_arr = arr[1:4]  # 提取从索引1到索引4的元素
print(sub_arr)  # 输出: [2 3 4]

使用布尔索引:

import numpy as np
 
arr = np.array([1, 2, 3, 4, 5, 6])
bool_idx = (arr > 2) & (arr < 6)
sub_arr = arr[bool_idx]  # 提取大于2且小于6的元素
print(sub_arr)  # 输出: [3 4 5]

使用np.where:

import numpy as np
 
arr = np.array([1, 2, 3, 4, 5, 6])
sub_arr = arr[np.where(arr > 2)]  # 提取大于2的元素
print(sub_arr)  # 输出: [3 4 5 6]

使用np.take:

import numpy as np
 
arr = np.array([1, 2, 3, 4, 5, 6])
sub_arr = np.take(arr, [1, 3, 5])  # 提取索引为1、3、5的元素
print(sub_arr)  # 输出: [2 4 6]

这里有个很tricky的事情,比如:

import numpy as np
arr = np.array([[1, 2, 3], [4, 5, 6], [7, 8, 9]])

arr[1:3, 1:3] # 输出就是2×2的矩阵
arr[(0,2), (0,2)] # 输出就是[1,9],原因是它等于只取[0,0],[2,2],而不是你想要的[0,0],[0,2],[2,0],[2,2]
arr[[0,1,2], [0,1,2]] # 输出是[1,5,9],对角线元素

所以用布尔索引最保险。


20240424

  • 气温回升,夏天即将到来,接下来尽量晚上训练。时隔二十多日,到底耐不住慢跑的寂寞,我拿出了XTEP160x3.0pro,我想认真跑一次。但是,说实话,这几日差劲的表现,我觉得自己连5000米跑进20分钟都够呛。
  • 晚上8点,逮到嘉伟黑练(我真的搞不懂,为什么嘉伟这学期一直黑练,到底是受啥刺激,太奇怪了)。我到场时,他已经跑了3组800米间歇,非常快,目测圈速1’15"上下。我提议没必要这么用力,又没比赛,一起慢跑个10km呗。嘉伟问多少配速,我说最近水平倒退得厉害,最多到4分配。
  • 众所周知,我和嘉伟之间最大的谎言就是4分配慢跑,嘉伟让我先带,我很兴奋,一改颓势,起手加到3’45",一路顶完5000米。嘉伟在1500米后上来给我拉住速度,虽然几近全力,但节奏非常好。过3000米时看了眼手表,发现心率都不到150bpm,难以置信,最终平均心率只有146bpm。其实3000米时我就想放弃了,但是顶完全程也没觉得很累。
  • 换平底鞋摇了5000米@4’47"收尾。群成员几乎倾巢出动,安迪、XR、LZR、YY都在场,特别是YY,晚上跑了个19’23"的5000米,这小家伙深藏不露,不可小觑。晚饭后消食时也看到ZJC和LHR在慢跑,感觉ZJC从安阳回来后可能是受伤了,明显慢了许多,LHR确是稀客了。
  • 最近都是两个5000米分开跑,不想给脚踝太大压力。这个恢复期很关键,看起来,今天我好像真的又行了,希望这次不是假的,可惜晚了不到一周,或许不去江阴的话,真的能赶在4月21日前恢复,但是,没有如果
  • 总觉得嘉伟是有些颓废,结束不辞而别,下次得跟他谈点心才好,低谷期确是很难熬。
    在这里插入图片描述在这里插入图片描述在这里插入图片描述

关于seaborn的热力图:

seaborn.heatmap(data, *, vmin=None, vmax=None, cmap=None, center=None, robust=False, annot=None, fmt='.2g', annot_kws=None, linewidths=0, linecolor='white', cbar=True, cbar_kws=None, cbar_ax=None, square=False, xticklabels='auto', yticklabels='auto', mask=None, ax=None, **kwargs)

Parameters:
datarectangular dataset
2D dataset that can be coerced into an ndarray. If a Pandas DataFrame is provided, the index/column information will be used to label the columns and rows.

vmin, vmaxfloats, optional
Values to anchor the colormap, otherwise they are inferred from the data and other keyword arguments.

cmapmatplotlib colormap name or object, or list of colors, optional
The mapping from data values to color space. If not provided, the default will depend on whether center is set.

centerfloat, optional
The value at which to center the colormap when plotting divergent data. Using this parameter will change the default cmap if none is specified.

robustbool, optional
If True and vmin or vmax are absent, the colormap range is computed with robust quantiles instead of the extreme values.

annotbool or rectangular dataset, optional
If True, write the data value in each cell. If an array-like with the same shape as data, then use this to annotate the heatmap instead of the data. Note that DataFrames will match on position, not index.

fmtstr, optional
String formatting code to use when adding annotations.

annot_kwsdict of key, value mappings, optional
Keyword arguments for matplotlib.axes.Axes.text() when annot is True.

linewidthsfloat, optional
Width of the lines that will divide each cell.

linecolorcolor, optional
Color of the lines that will divide each cell.

cbarbool, optional
Whether to draw a colorbar.

cbar_kwsdict of key, value mappings, optional
Keyword arguments for matplotlib.figure.Figure.colorbar().

cbar_axmatplotlib Axes, optional
Axes in which to draw the colorbar, otherwise take space from the main Axes.

squarebool, optional
If True, set the Axes aspect to “equal” so each cell will be square-shaped.

xticklabels, yticklabels“auto”, bool, list-like, or int, optional
If True, plot the column names of the dataframe. If False, don’t plot the column names. If list-like, plot these alternate labels as the xticklabels. If an integer, use the column names but plot only every n label. If “auto”, try to densely plot non-overlapping labels.

maskbool array or DataFrame, optional
If passed, data will not be shown in cells where mask is True. Cells with missing values are automatically masked.

axmatplotlib Axes, optional
Axes in which to draw the plot, otherwise use the currently-active Axes.

kwargsother keyword arguments
All other keyword arguments are passed to matplotlib.axes.Axes.pcolormesh().

Returns:
axmatplotlib Axes
Axes object with the heatmap.

一些典型参数示例:

# 内置数据集
glue = sns.load_dataset("glue").pivot(index="Model", columns="Task", values="Score")
sns.heatmap(glue)
# --- 
sns.heatmap(glue, annot=True) # 热力图每个格子添加数据标注
sns.heatmap(glue, annot=True, fmt=".1f") # 数据标注保留一位小数
sns.heatmap(glue, annot=glue.rank(axis="columns")) # 使用其他列的数据来标注格子
sns.heatmap(glue, annot=True, linewidth=.5) # 格子之间添加行列的分割线
sns.heatmap(glue, cmap="crest") # 格子颜色转换(默认红黑,crest是蓝白)
sns.heatmap(glue, cmap=sns.cubehelix_palette(as_cmap=True)) # 这个是紫黑
sns.heatmap(glue, vmin=50, vmax=100) # 这个是红黑差不多
# ---
# 这个效果是把底部的axistick(x轴)调到顶部来
ax = sns.heatmap(glue, annot=True)
ax.set(xlabel="", ylabel="")
ax.xaxis.tick_top()

20240425

  • 联盟杯在即,队里难得认真训练,东哥问我要不要参加,你看我这脚像是能比赛的样子嘛。感觉这次队里都抓不到男生去跑1500和5000了,嘉伟也不想参加,韬哥肯定也不在状态,只剩一个佛系宋某,估计上去也是丢人,算了算了。
  • 不得不说,各位箭步走得真好,我练了一个冬天箭步,倒过来走还是歪歪扭扭,而且发现自己左右大腿后侧力量差距很大,臀桥收腿,右腿可以连续做十几个,左腿两三个就要抽。
  • 右脚情况并不好,晚上简单跑了两段,第一段带XR,本想带他410~420遛个弯,小家伙不讲武德,6圈多居然想拉爆我,那我不得给他点color see see,我是变弱了,不代表你就变强了xs;第二段跟着AK不到2000米,右脚踝就疼得受不了,最后AK是36’57"跑完的万米,均配3’41",老大哥确实稳,轻轻松松跑进37分,好想好想能跟下来,却找不到昨天那种兴奋感。
  • 总觉得自己是可以5K破18分,10K破37分。脚伤未愈,状态也没有完全恢复到巅峰,好不甘心呐。这个夏天过去,希望能与宋某一起达标万米36分台,从今晚来看,宋某现在力量水平确实要比之前强,黑练三个月是有点东西的,最近先忍他一手。
    在这里插入图片描述在这里插入图片描述在这里插入图片描述

huggingface模型隐层表示
以BERT为例

import os
# 模型路径
model_roots = [
    r"D:\resource\model\huggingface\common",
]
model_summary = {
    "bert-base-uncased": os.path.join(model_roots[0], "bert-base-uncased"),
    "albert-base-v2": os.path.join(model_roots[0], "albert-base-v2"),
}
device = "cuda"
from transformers import BertTokenizer, BertModel, BertConfig
tokenizer = BertTokenizer.from_pretrained(model_summary["bert-base-uncased"])
model = BertModel.from_pretrained(model_summary["bert-base-uncased"]).to(device)
inputs = tokenizer(text="a photo of cat in white", return_tensors='pt', padding='max_length', truncation=True, max_length=8).to(device)
outputs = model(**inputs, output_hidden_states = True) # 设置output_hidden_states = True

这样就可以用:

for hidden_state in outputs.hidden_states:
    print(hidden_state)

来查看每个隐层表示,比如bert-base-uncased有13个隐层,大小都是(batch_size, maxlen, 768),最后一个就是outputs.last_hidden_state

纠正几个错误:

  1. 首先[CLS]位置肯定不是pooler_output,尽管它确实包含最丰富的语义信息
  2. 每一层hidden_state(不仅是last_hidden_state),关于CLS位置(即句首)的相似度计算,极少出现负相似度的情况,即便是颜色位置,这个是之前其他编码器出现的问题,我们希望通过相似度的权重来)
  • 负相似性似乎是危险的事情,因为一旦出现负相似度,理论上这种情况在position维度上应该是交替出现才对,不应该是只有一个position出现这种特例。

20240426~20240427

  • 伤痛还是没有好,周三跟嘉伟拼了命之后,周四还是应该休息一天的。昨晚去一路小跑到火车头打卡,来回差不多15km,本来还很惬意,刚好晚饭后消食,去那边还能白嫖小零食和饮料,结果不到10km就已经不行了,走回纪念路时已经瘸成麻瓜,感觉跟刚从江阴回来一样,欢腾了没几天又回到解放前。真的是扎心了。
  • 周末到小姨家弄些好的吃,好好养伤,本还想五一要不要去报苏州的那场越野赛,真是活在梦里,什么都有。
    在这里插入图片描述在这里插入图片描述

position encoding:

class PositionalEncoding(nn.Module):
    def __init__(self, d_model, dropout, max_len=5000):
        super(PositionalEncoding, self).__init__()
        self.dropout = nn.Dropout(p=dropout)
        pe = torch.zeros(max_len, d_model)
        position = torch.arange(0, max_len).unsqueeze(1)
        div_term = torch.exp(torch.arange(0, d_model, 2) *
                             -(math.log(10000.0) / d_model))
        pe[:, 0::2] = torch.sin(position * div_term)
        pe[:, 1::2] = torch.cos(position * div_term)
        pe = pe.unsqueeze(0)
        self.register_buffer('pe', pe)

    def forward(self, x):
        x = x + Variable(self.pe[:, :x.size(1)], requires_grad=False)
        return self.dropout(x)

PositionwiseFeedForward

class PositionwiseFeedForward(nn.Module):
    def __init__(self, d_model, d_ff, dropout=0.1):
        super(PositionwiseFeedForward, self).__init__()
        self.w_1 = nn.Linear(d_model, d_ff)
        self.w_2 = nn.Linear(d_ff, d_model)
        self.dropout = nn.Dropout(dropout)

    def forward(self, x):
        return self.w_2(self.dropout(F.relu(self.w_1(x))))

反向传播,runtime error: Trying to backward through the graph a second time

a = Variable(torch.rand(1, 4), requires_grad=True)
b = a ** 2
c = b * 2
d = c.mean()
e = c.sum()
d.backward()
e.backward() # runtime error: Trying to backward through the graph a second time

20240428

  • 断断续续地,伤痛竟然持续快一个月,有些不能接受。
  • 烟雨朦胧,是今年第一次出这样的天气。晚饭后,去操场兜圈,虽然伤痛,但是一步不跑是不太可能的。5K,配速5分半,不能再多了,再多就要疼了,一天5K也足够了,真没必要天天跑太多,LXY月底疯狂刷卡,4月打卡榜从第5一下子跃升到第2,一天打4次,真特么离谱。
  • 嘉伟又在黑练,4000米+2000米×3组的间歇,跑得很快,到场时刚跑完,感觉是一脸迷茫地坐在长凳,两只小腿都拉着肌贴。遂拿了瓶沁葡水给他,让他不要急躁,多来参与一下集体活动,与民同乐,别老一个人黑练,这样真不好。
  • 宋某近期处于绝对上升期,昨天场地5000米测试继续PB,跑出18’09",最后1000米分段3’22",冲刺能力依然顶级,根据过往经验,正式比赛他必然轻松破开18分。不过莫得关系,这阵子先让他得瑟一下,以我对他的了解,不出两个月就要萎,到时候夏训开始,我应该也恢复得差不多。
  • 如果可以的话,还是想参加下半年的校运会,很想跑个好看点的正式成绩出来,也很想在比赛中跑赢一次宋某,乃至嘉伟(虽然不太可能…)。近几年校运会5000米成绩真的惨不忍睹,2021年第一是WXY(19’09"),2022年是嘉伟18’33",去年是小崔18’10",以今年的势头,可能前三都要跑进18分钟。
    在这里插入图片描述在这里插入图片描述
a = Variable(torch.rand(1, 4), requires_grad=True)
b = a ** 2
c = b * 2
d = c.mean()
e = c.sum()
d.backward(retain_graph=True)
e.backward() # Success

b.is_leaf查看变量是否属于叶子节点。输出False,即非叶节点

b.grad

则会抛出警告

The .grad attribute of a Tensor that is not a leaf tensor is being accessed

原因是在反向传播中,非叶节点的梯度是不会被populated,即不被传播,因为链式法则全部抵消掉了。因此,在计算图中,将不会保留他们的梯度。

如果确实需要非叶节点的梯度,需要添加

b.retained_graph()

事实上,多层nn那些中间层的Linear,Convlution层的weights都是叶子节点,因为它们只承载了输入,并没有输出,如果需要获取非叶节点的grad,比如:

class MLP(nn.Module):
    def __init__(self):
        super(MLP, self).__init__()
        self.fc1 = nn.Linear(28*28, 512)
        self.fc2 = nn.Linear(512, 512)
        self.fc3 = nn.Linear(512, 10)

    def forward(self, x):
        x = nn.Flatten(x)
        x = self.fc3(nn.ReLU(self.fc2(nn.ReLU(self.fc1(x)))))
        return x

mlp.fc3.weight.is_leaf # True
mlp.fc2.weight.is_leaf # True
mlp.fc1.weight.is_leaf # True

原因很简单,假定输入为 X ∈ R n × 576 X\in\R^{n×576} XRn×576

Y 1 = X W 1 + b Y 2 = Y 1 W 2 + b Y 3 = Y 2 W 3 + b Y_1 = XW_1 + b\\ Y_2 = Y_1W_2 + b\\ Y_3 = Y_2W_3 + b Y1=XW1+bY2=Y1W2+bY3=Y2W3+b

这个计算图就形如(Mermaid语法绘制):

X
Y1
W1
Y2
W2
Y3
W3

显然 W 1 , W 2 , W 3 W_1,W_2,W_3 W1,W2,W3都是叶子节点

总结:

  • 反向传播,链式法则,内部非叶子节点(non-leaf node,哪怕 requires_grad 为 true,且其存在 grad_fn)也是会算梯度的,只是用完就置空了,
  • 因此如果相查看内部非叶子节点的 grad,需要 retain_graph 保留在计算图中;
  • 深度神经网络中的中间层 layer 的参数(weights & bias)它们是内部节点呢,还是叶子节点呢?
    • 是叶子节点;
  • 不要轻易地关闭 warnings,有助于排查/定位问题;
    • warnings 不会导致程序 dump,但不推荐,因为有可能导致程序的运行不符合预期;
    • 对于自己写的代码,出于健壮性或者可快速定位问题的考虑,也可以尝试多写 warnings

20240429

  • 晚上慢跑5000米,26分20秒,比昨天快一些,中间陪LY跑了一段,有人一起会轻松一些,但也已是右脚的极限。宋某在自测5000米,不停地套我圈(试着跟了他两步,还是不敢勉强,算了算了),最终是18分03秒,他目前大腿后侧的拉伤还有些,但是不太碍事,很期待他联盟杯的表现,不出意外的话,跑到三级线(17分40秒)估计问题不大,真的比不过了。
  • 完事陪宋某做了力量,正向箭步30个×4组+反向箭步30个×4组,今天弹力带引体已经可以一口气拉上去10个。场上很多人都在,XR今晚跑出40分29秒的10K,拉爆了后面的YY和LZR,感觉他们有机会赶在黄梅季前破开40分,之后夏训就不太可能再PB了。
  • 掐指一算,群里已经能有快十个人都能万米破40分了,感觉10K破40分变得好轻松,一抓一大把,真的是时代变了,年轻人都开始发力,我们是不是该退出历史舞台咯。
  • 跑完还是有点疼唉。
    在这里插入图片描述在这里插入图片描述

Mermaid语法是一种基于JavaScript的绘图和制图工具,它允许用户通过Markdown-like的语法来定义和创建图表。1

在Markdown文件中,使用mermaid标记来开始一段用于绘制流程图、序列图等可视化的代码。其图形标记包括方形文本框[]、菱形文本框{}、圆角方形文本框()、圆形文本框(())。连线标记方面,实线不带箭头可以用–表示,实线带箭头则是->,虚线不带箭头用-.-表示,虚线带箭头则是-.->,粗线不带箭头是===,粗线带箭头则是==>。另外,流程图的方向可以用TB(top-bottom)、BT(bottom-top)、RL(right-left)、LR(left-right)来定义。

转载自:https://www.cnblogs.com/chehy/p/17477947.html

官方文档:https://mermaid.js.org/syntax/flowchart.html#graph

Mermaid是什么
​ Mermaid是一个开源的、基于JavaScript的绘图库,它可以用简洁的、人类可读的文本描述和绘制流程图、序列图、甘特图、类图、实例图、状态图和部署图等不同类型的图表。Mermaid的语法简单易懂,支持针对不同类型图表的定制化设置,可以轻松绘制高质量的图表并嵌入到网页、Markdown文档和其它支持HTML的场景之中。此外,它还支持插件和编辑器的集成,使得用户可以更加高效地使用和管理Mermaid图表。Mermaid是一个接近理想状态的图表和流程图的设计工具,是众多开发者和作者的首选。

Mermaid能做什么
Mermaid是一个功能强大的绘图库,可以创建多种类型和样式的图表,包括但不限于:

流程图:可以绘制各种类型的流程图,包括标准流程图、过程流程图、判断流程图和循环流程图等。

序列图:可以绘制对象之间交互的图,包括控制流和消息等,尤其适用于描述同步和异步操作。

实例图:可以绘制类和对象之间实例化和运行时关系的图表,包括属性和方法等,比较适用于系统建模和设计。

甘特图:可以绘制项目排期和进度等的图表,包括时间轴、任务和比例等,用于管理大规模项目非常重要。

类图:可以绘制类和对象之间的继承、实现、属性、方法和注释等关系,用于表达软件系统的结构和继承体系。

状态图:可以绘制状态和条件之间的转换的图表,包括状态、条件、事件和行为等,适用于描述状态机。

部署图:可以绘制组件和节点之间的关系和通信的图表,适用于描述系统的分布式和物理结构。

通过Mermaid你还可以定制图表颜色、字体、大小、尺寸等属性,并以HTML、SVG、PNG格式等多种格式输出图表,满足多种输出和展示需求。因此,Mermaid广泛应用于软件开发、项目管理、系统架构、数据可视化等领域,帮助用户绘制高质量的图表,提高工作效率和沟通效果。

Mermaid的优缺点
Mermaid是一个功能强大、易用性较高的绘图库,但是它也有一些优缺点,下面分别列举:

优点:

简单易学:Mermaid的语法结构简单易懂,每种图表均有对应的语法,并且支持样式设置。

支持多种图表类型:Mermaid可以绘制多种类型的图表,如流程图、序列图、甘特图等,满足不同用户的需求。

轻便灵活:Mermaid是一个基于JavaScript的绘图库,可以通过HTML文本直接生成图表,易于嵌入各种场景。

可定制性高:Mermaid支持各种样式的配置,可以调整节点、链接、标签、颜色等属性,使图表更具可视化效果。

免费:Mermaid是一个免费开源的绘图库,用户无需购买或支付任何费用,即可使用它进行图表绘制。

缺点:

格式限制:Mermaid对语法格式有严格的要求,不同类型图表的格式不尽相同,需要用户按照规则书写。

功能相对简单:Mermaid虽然支持多种类型图表的绘制,但功能相对于一些商业绘图软件而言还比较简单。

引入库的一定量工作:在使用Mermaid绘制图表时,需要先引入其对应的库,有时可能需要一定量的工作。

以上是Mermaid的优缺点,虽然存在一些缺点,但是对于需要用于制作简单的、具有动态感的图表的用户而言,Mermaid是一个非常适用的工具,可以提高工作效率,节约时间和金钱成本。

Mermaid的安装和使用
Mermaid的安装和使用非常简单,以下是一些常用方式:

浏览器使用
Mermaid作为一个JavaScript库,可以在浏览器中直接引入,使用的时候可以在HTML页面中添加以下代码:

<!-- 引入mermaid库-->
<script src="https://cdn.jsdelivr.net/npm/mermaid/dist/mermaid.min.js"></script>
<!-- 以后需要的时候再解析文本-->
<script>mermaid.initialize({startOnLoad:true});</script>

在Node.js中使用
如果需要在Node.js中使用Mermaid,可以在命令行中运行以下命令来安装:

npm install mermaid --save

安装成功后,可以使用以下代码来生成一个流程图的HTML:

const mermaid = require('mermaid');
mermaid.initialize({
startOnLoad: true
});
const graphDefinition = 'graph TD\nA-->B';
const html = mermaid.render('graphDiv', graphDefinition);

使用上面的代码,在终端中运行,就会输出一个HTML文档。其中,Mermaid库提供了render函数用于将文本转换为定义好的图表格式。

在markdown中使用
如果需要在markdown中使用Mermaid,可以在markdown页面里添加以下代码:

graph LR
A[开始] --> B(流程)
B --> C{判断}
C -->|结果1| D[结束]
C -->|结果2| E[结束]

20240430

  • 好吧,不是有点疼,是特别特别疼,现在哪怕只是用一点点力气,都疼得不行,就像有一道箍套在跟腱一圈上,压一下就会勒紧,疼得很难受。我不确信是否跟腱断裂,没断过,也不晓得是啥感觉。
  • 外敷药物已经无法缓解疼痛,可能需要消炎药,但愿不会更坏。明后看天气,可能的话得跟小姨开车回一趟扬州,顺便挨骂。我承认真的做错了,自作自受,活该,说啥我都认了,但是也得把先伤养好。
  • 塞翁失马,焉知非福,罢了,闭关潜修两月,也不是啥坏事吧。

Mermaid杂记(二)

定义一个简单流程图:

graph LR
  A[开始] --> B[流程1]
  B --> C[流程2]
  C --> D[结束]

graph:定义流程图,表示图表的类型。可以是graph、pie、classDiagram、stateDiagram等。
LR:定义图表的布局,可以是LR、RL、TB、BT。
用[ ]中括号来定义节点和它们的标签。
–>:连接两个节点,表示它们的流程顺序。
//:使用//`可以在节点间添加注释信息。

graph TD
    A[开始]-->B(步骤1)
    B---->C(步骤2)
    B--描述文本-->D(步骤3)
    C==>E(步骤4)
    D==>E
    E--选项1-->F(步骤5)
    E-.选项2.->G(步骤6)
    G==>H(步骤7)
    G-.选项3.->I(步骤8)
    I-.确定.->H
    subgraph 一组节点
        B
        C
        D
    end

这个流程图是一个从 A(开始)到 H(结束)的流程,其中包括以下箭头和括号的定义:

–>:定义一个简单的向右箭头,表示从节点 A 到节点 B。
---->:定义一个直线箭头,表示从节点 B 到节点 C。
– 描述文本 -->:定义一个带描述文本的双向箭头,表示从节点 B 到节点 D。
==>:定义一个粗箭头,表示从节点 C 到节点 E。
-.->:定义一个带有点的线箭头,表示从节点 E 到节点 G。
-. 选项3 .->:定义一个带有描述的线箭头,表示从节点 G 到节点 I,并使用花括号来表示选项描述。
-. 确定 .->:定义一个带有描述的直线箭头,表示从节点 I 到节点 H,并使用括号来表示箭头的标签。

graph TD
    A[开始] -->|初始化| B(设置参数)
    B --> C{条件}
    C -->|| D[步骤1]
    C -->|| E[步骤2]
    B --> F(调整参数)
    E --> F
    D --> G((输出结果))
    F --> G
    G -->|完成| H[结束]

在这个示例中,我们定义了以下几种形状:

A[开始]:定义一个圆角矩形节点,表示流程的开始。
B(设置参数):定义一个菱形节点,表示决策点。
C{条件}:定义一个菱形节点,表示具有条件的决策点,并使用花括号来表示节点的形状。
D[步骤1] 和 E[步骤2]:定义一个矩形节点,表示流程中的步骤。
F(调整参数):定义一个矩形节点,表示一个调整参数的步骤。
G((输出结果)):定义一个圆形节点,表示流程中的结束点。
–>:定义一个简单的向右箭头,表示单向箭头。
–>|描述文本|:定义一个带有标签的箭头,表示从一个节点到另一个节点,同时附带关于箭头的描述信息。

复杂流程图
定义一个步骤比较多的流程图:

graph LR
  A[开始]-->B[流程1]
  B-->C[判断条件]
  C-->|条件成立|D[流程2]
  D-->E[判断条件]
  E--> |条件成立| F[流程3]
  F-->|否则|G[流程4]
  E-->|条件不成立|G[流程4]
  G-->H[结束]

graph LR:定义流程图的类型和布局。
–>:定义节点间的流程顺序。
|文本|:表示条件分支的判断条件和文本。
==>:定义流程,表示在两个节点间连接一个带箭头的圆圈,圆圈中可以填写任何文本信息。
–文本–>:定义带有文本的流程,用于表示带有文本标签的流程。
点击事件:点击A–>B:执行的函数:定义点击事件,表示点击A节点时触发B节点的函数执行。
子图
定义一个包含子图的流程图:

flowchart LR
  subgraph A
  物品-->B[计算1]
  B-->C(判断1)
  C-->|条件1| D[输出结果1]
  C-->|条件2| E[计算2]
  E-->D
  end

  subgraph B
  客户-->F[计算3]
  F-->G(判断2)
  G-->|条件3|H[输出结果2]
  G-->|条件4|I[计算4]
  I-->H
  end

  A-->B
  F-->C
A
B

subgraph:定义一个子图,可以用来分组和组织节点。
end:标记子图的结束位置。
可以在子图中使用标准的流程图语法定义节点、流程和条件分支。
子图中的节点可以连接到主流程图中的其他节点。

类图
定义一个简单的类图:

classDiagram
  class Animal {
    +name: String
    +eat()
  }
Animal
+name: String
+eat()

classDiagram:定义一个类图。
class:定义一个类。
+ 或 -:表示类的公共或私有成员。
::定义成员的类型。
():定义一个方法。


20240501

  • 天气好转,学校里居然有不少游客,这儿有个鸡毛掸子好玩的。
  • 今天感觉好了很多,说实话,昨天的情况真把我吓死了,从宿舍走到学校都极其艰难,真以为是跟腱断了。今天至少走路已无大碍,虽然走得太久还是会有点疼。
  • AK九点问我明天要不要来家里吃饭,他亲自买菜下厨,还要把DGL和LXY都拐到家去一起,因为DGL是正统回族人,一点不吃猪肉,AK还很纠结买些啥菜回来吃。可惜晚了一步,八点半就启程回家,我好像是错过了很重要的事。
  • 路上堵了两三段,虽然没有堵太久,也是到了十二点之后才到家。回来称了个毛重,68.5kg,至少比之前肯定是重了些(最近消耗很低,但是依然很想吃东西,虽然前几天还是控制了饮食,但是今晚吃了很多),我倒不是很担心体重,安心度过这段恢复期,心态比任何事情都更重要。

状态图

stateDiagram-v2
  [*] --> Off
  Off --> Starting : Power on
  Starting --> On : Start success
  On --> Off : Power off
  On --> Suspended : Sleep
  Suspended --> Off : Power off
  On --> Starting : Restart

stateDiagram-v2:定义状态图。
[*]:表示默认起始状态。
–>:表示状态变化。
使用 : 标记每个状态的名称。
可以使用—定义状态的展示文字。
: 表示状态名称的展示文字。
extend 可以用于表示某个状态的 do 动作。
note 可以用于添加注释,例如在某个状态上添加状态详情信息等。

时序图

sequenceDiagram
  participant A
  participant B
  A ->> B : 请求数据
  B -->> A : 返回数据

sequenceDiagram:定义时序图。
participant:定义参与者的角色,可以在后面的流程步骤中使用它们。
->>:表示请求操作。
–>>:表示返回操作。
alt 和 else 可以用于表示可选的分支。

甘特图

gantt
  title 项目计划
  dateFormat  YYYY-MM-DD
  section 研发
  设计 :a1, 2018-01-01, 30d
  开发 :a2, 2018-01-03, 60d
  测试 :a3, after a2, 20d
  section 上线
  发布 :b1, after a3, 2d
  测试 :b2, after b1, 5d

gantt:定义甘特图。
title:设置标题。
dateFormat:定义日期格式。
section:定义项目的不同阶段,可以在不同的阶段中设置相应的任务。
使用 : 分隔任务名称和 ID。
使用 , 分隔任务 ID、开始日期和持续天数。
after:定义某个任务在另一个任务之后开始。
根据起始时间和持续时间,可以计算出任务的结束时间。

树状图

graph TD
  A[开始] --> B[分类1]
  B --> C[子分类1]
  B --> D[子分类2]
  C --> E[项目1]
  C --> F[项目2]
  D --> G[项目3]
  D --> H[项目4]

graph TD:定义树状图。
使用–>连接父级和子级节点。
树状图中的每个节点都代表一个分类或项目。
树形结构可以包含任意数量的子树。


20240502

  • 基本是宅一天,昨晚到两点多才睡,一直到傍晚才出去走了两三公里,确实一直不动是不行的,坐久膝盖都有点疼,顺便拉了几组单杠和双杠臂屈伸。
  • 消耗很低,但是吃得不少,估计过不了多久就要突破70kg,其实也无所谓,养一些起来也好,回学校还是要控制饮食,肉可以多吃,乱七八糟的饼干零食还是少吃。
  • 今天拍了个片子,骨头确实是没问题,我也没觉得骨头会出问题,我关心的是肌肉损伤,而且今天感觉又是跟四月初那时候差不多,单脚踮不起来,我也是醉了,每天都换一个地方疼,疼了一个月,疼痛都开始轮回了。后天等老妈上班去做个超声检查,正好她师傅要来会诊,弄清楚到底是什么问题,心里踏实些。

矩形图
定义一个简单的矩形图:

graph TD
  subgraph 系统
    A(用户) -- 输入 --> B(系统)
    B -- 处理 --> C{结果正确?}
    C ----> D(输出结果)
    C ----> E(返回错误信息)
  end

graph TD:定义矩形图。
使用–连接节点。
使用()定义节点类型。
使用{}定义判断条件。
使用subgraph和end来定义一个子集。

异步流程图
定义一个简单的异步流程图:

sequenceDiagram
  A->B: 请求A
  B->>C: 请求B
  B->>D: 请求C
  C-->>B: 返回C
  D-->>B: 返回D
  B-->>A: 返回B

sequenceDiagram:定义异步流程图。
->>:表示异步请求。
–>>:表示异步返回。
使用 : 分隔消息名称和消息 ID。


20240503

  • 好天气爬山真也挺好,但雨战属实花钱受罪,凡事还得找跟自己节奏差不多的人一起,不然效率太低。好喜欢这种一路水泥铺好的上坡,联想到箱根五区,好想能一路跑下来,却怎么也不敢迈开步伐。
  • 晚上回来去把肌骨看了一下,脚上韧带是没有问题,足底筋膜的影像也是均匀厚实,应该不是足底筋膜炎。目前走路并不碍事,但是只要连续走到3-5km,还是有些疼的,往好里说右脚后跟外侧有气泡,踩下去有气泡破裂的感觉,往坏里说就像是经脉都断了。
  • 为了还能跑得起来,现在就只能是静养,用活血化瘀的药物(比如艾草)泡脚,直到没有感觉为止。尽管大约伤痛结束又是从头再来,但至少还没那么坏,不幸中之万幸。
    在这里插入图片描述在这里插入图片描述

流程图

flowchart TD
  A[开始] -->|条件1| B[流程1]
  B -->|条件2| C[流程2]
  C -->|条件3| D[流程3]
  D -->|条件4| E[结束]

flowchart TD:定义流程图。
使用 -->|文本| 标记带有文本的流程,表示条件分支操作。
使用细长菱形表示条件判断。

状态转换图

stateDiagram
  [*] --> 状态1
  状态1 --> 状态2 : 事件1
  状态1 --> 状态3 : 事件2
  状态2 --> 状态3 : 事件3
  状态3 --> 状态1 : 事件4
  state 状态1 {
    [*] --> 子状态1
    子状态1 --> 子状态2 : 子事件1
    subgraph 子状态组
      子状态2 --> 子状态3 : 子事件2
    end
    子状态3 --> 子状态1 : 子事件3
  }

stateDiagram:定义状态转换图。
[*]:表示默认起始状态。
–>:表示状态转换,用于表示状态之间的关系。
state:可以用于定义一个新的状态域。
subgraph 和 end:可以用来定义一个子状态组。
使用 , 分隔状态名称和状态 ID。
使用 : 分隔状态 ID 和展示名称。
使用 —> 定义箭头的方向。

强调样式
定义一个具有强调样式的流程图:

graph TD
  A[开始] -->|条件1| B[流程1]
  B -->|条件2| C[流程2]
  C -->|条件3| D[流程3]
  D -->|条件4| E[结束]
  style A fill:#BEE7E9,stroke:#333,stroke-width:4px
  style B fill:#BEE7E9,stroke:#333,stroke-width:4px
  style D fill:#BEE7E9,stroke:#333,stroke-width:4px

style:用于修改节点样式。
fill:填充颜色。
stroke:边框颜色。
stroke-width:边框宽度。


20240504

  • 下午嘉伟跟LXY打球,嘉伟不止一次跟我讲他一定能打得过,结果应该是被虐了。
  • 但是,嘉伟最近确实不在状态,他前两天去了趟嘉兴,估计是一个人去的,说是很羡慕那里的生活。他或许是有些疑惑自己前两年的训练究竟是为了什么,不断地追求和否定,总是要走到看山非山,看水非水的地步。人还是不要太惧怕去否定自己。
  • 相较之下,几个备赛的人最近都猛得不行,宋某自不必提,LXY前天都能随意跑个20K,估计DGL也跟了至少10K向上。说实话,现在是有点羡慕他们的,不管是水平还是状态,而且一个个都比之前活跃不少。
  • 值得一提地,SXY今天完成了人生第一场越野赛,这就是时间的魅力,几年以前,真的很难想象这样一个人会去参加越野赛。到了下半年,兴许还真能完成虞山那场35K爬升2000米的越野。
  • 捎了一堆粽子和艾草回上海。临行前称了个空腹净重,67.6kg,比之前是重不少。这次跑休大约持续很久(其实脚已经没啥感觉了,但是真的怕,不敢去跑,我要把20包艾草泡完再重新进操场),必须减碳水,否则体重必然失控,这对于已经碳水成瘾的人来说,是有些艰难的(尤其寝室还有去年买的10斤三牛饼干,前一阵子天天晚上回去睡前吃,一吃就是三四包,还剩不少,蚌)。

字体样式
定义一个具有自定义字体样式的流程图:

graph TD
  A[开始] -->|条件1| B[流程1]
  B -->|条件2| C[流程2]
  C -->|条件3| D[流程3]
  D -->|条件4| E[结束]
  style A font-size:14px,fill:#BEE7E9,stroke:#333,stroke-width:4px
  style B font-size:14px,fill:#BEE79,stroke:#333,stroke-width:4px

样式继承
定义一个具有样式继承的流程图:

graph TD
  A[开始] -->|条件1| B[流程1]
  B -->|条件2| C[流程2]
  C -->|条件3| D[流程3]
  D -->|条件4| E[结束]
  style B extends A,fill:#BEE7E9,stroke:#333,stroke-width:4px
  style C extends B,fill:#E5D7D7
  style D extends B,fill:#E5D7D7

extends:用于定义节点的继承关系,使得一些节点可以继承其他节点的样式。
fill:定义填充颜色。
stroke:定义边框颜色。
stroke-width:定义边框宽度。

自定义节点类型
定义一个自定义节点类型的流程图:

classDiagram
  class MyNode{
    -name : String
    -level : Integer
    +getName() : String
  }
MyNode
-name : String
-level : Integer
+getName() : String

classDiagram:定义一个类图。
class:定义一个新的类或类型。
-:定义一个私有属性。
+:定义一个公共方法。
使用 : 分隔属性和类型。
使用 () 分隔方法名称和参数。


20240505

  • 晚上力量训练,(正向箭步30个×2组+反向箭步30个×2组)×3大组,弹力带引体10个×3组,双杠臂屈伸20个×3组,我试着想跑两步,哪怕只是用走的方式(即双脚不同时离地),连100米都坚持不下来。
  • 经过这几天的休养,感觉上恢复得已经很好了,至少,怎么扭动脚都不会疼,也不影响走路,单脚也能踮得起来,但就是不能持续地压迫脚踝,否则有明显的抽筋感。当然,我已经下定决心停跑至少半个月,只是晚上看到场上能有六七个人,成群结队地在训练,这种景象在从前的操场上是很难见到的(以前就是AK、嘉伟、宋某和我),心里还是有点不甘,甚至连LXY的速度都跟不上,唉。
  • 三月份,日均消耗在1000C以上,即便是四月,也能有700C上下的水平,最近一周,已经只剩不到300C。反正,也不是第一次从低谷恢复,以前还有更艰难的情景,只是这次飞得太高,摔得太狠。不过,我很高兴嘉伟终于愿意慢些,下午他跑了4组4000米的间歇,均配4分整,以前他是决计不愿跑这么慢。慢些,总会有慢些的风景。

点击事件
定义一个节点点击事件的流程图:

graph TD
  A[开始] -->|条件1| B[流程1]
  B -->|条件2| C[流程2]
  click C "alert('This is C node')" "点击C节点"
  C -->|条件3| D[流程3]
  D -->|条件4| E[结束]

click:定义节点的点击事件。
“alert(…)”:执行的 JavaScript 代码。
“点击C节点”:节点鼠标悬停标签。

容器
定义一个具有容器的流程图:

graph TB
  subgraph A
  B
  end

subgraph:定义一个容器。
end:标记容器的结束位置。
容器可以用来分组和组织节点,使它们易于管理。

Git 图
定义一个 Git 图:

gantt
  title Git 进化史
  dateFormat YYYY-MM-DD
  section 远古时代
  创建 Git :a1, 2005-04-12, 15d
  section 现代化时代
  GitHub 购买 Git :a2, 2018-06-04, 1d
  section 近期时 
  Microsoft 收购 GitHub :a3, 2018-06-04, 1d

title:定义 Git 图的标题。
dateFormat:定义日期格式。
section:定义不同时间段。
使用 : 分隔任务名称和 ID。
使用 , 分隔任务 ID、开始日期和持续天数。

用户定义样式
定义具有用户定义样式文件的流程图:

flowchart TD
  A[项目] -->|使用存储过程| B((数据库))
  B -->|更新数据表| C{数据表}
  C -->|检查结果| D[是否满足要求]
  style A fill:#BEE7E9,stroke:#333,stroke-width:4px
  style B fill:#BEE7E9,stroke:#333,stroke-width:4px,stroke-dasharray: 5, 5
  style C fill:#BEE7E9,stroke:#333,stroke-width:4px,stroke-dasharray: 5, 5
  style D fill:#BEE7E9,stroke:#333,stroke-width:4px,stroke-dasharray: 5, 5,font-size:14px

style:用于定义节点的样式。
fill:表示填充颜色。
stroke:指定边框颜色。
stroke-width:指定边框宽度。
stroke-dasharray:用于定义边框样式,该样式使用一组数字表示边框的线长度和间隔。
font-size:用于定义节点标签的字体大小。


20240506

  • 这两天,晚上睡前和早上起床,艾草泡两次脚,泡脚属实舒服,有效改善睡眠质量。
  • 晚上弹力带引体10个×5组,间隔操场慢跑2圈×4次,感觉上可以慢跑一点点,但是不能太多,差不多6圈时会有些感觉,赶紧停下来走路,问题应该不是太大。虽然引体确实比之前强些,去年的话用弹力带也是一个都拉不上去,两个月前也就最多做四五个就力竭了,但是脱离弹力带还是一个都拉不上去,无奈。
  • 想抽空去游个泳,去年夏天学完游泳之后,就一直没下过水,估计已经不行了。这段时间要能把游泳练起来也挺不错。

数字属性
定义具有数字属性的流程图:

graph TD
  A[顶部] ==>|50| B[中部]
  B <==>|25| C[底部]

==> |数字|:定义一个带数字属性的箭头。
<==>:定义双向箭头。
使用数字属性来定义箭头长度。

50
25
顶部
中部
底部

饼图
定义一个饼图表:

pie title 人员构成
  "工程师" : 43
  "销售" : 20
  "市场" : 17
  "其他" : 5
  "管理" : 15

pie:定义饼图。
title:定义饼图的标题。
使用冒号分隔项目名称和数量。

43% 20% 17% 5% 15% 人员构成 工程师 销售 市场 其他 管理

这里图例显示不全,检查了一下发现mermaid都是用的<g>标签,而且这个饼图的每个块其实都是一个长方形的<path>区域,图例的每一项也是一个<g>标签,改一改大小就能显示完整了。


20240507

  • 肩背酸痛,已经完全拉不上引体了,晚饭后双杠臂屈伸20个×5组,间歇慢跑了8圈,今天跟昨天的耐受差不多,只是改成三圈三圈地跑,到第三组感觉有点疼,没有继续勉强,有些灰心。
  • 真的好想能跑得起来,结束后冲了4个百米,跟上个月感觉一样,跑得快并不会觉得疼,但是我也不敢跑得太狠,点到为止。
  • 宋某今天四组400米间歇,第三组跑出61秒,刷新400米PB(之前应该是65秒),他目前1000米应该有3分以内的水平,换算到5000米是17分半,眼下嘉伟也是伤痛期,宋某已是一枝独秀,不错不错。

雷达图绘制示例:

# 构建数据(或者https://url11.ctfile.com/d/45455611-56628303-894706?p=6872下载)
import pandas as pd
import numpy as np
import matplotlib.pyplot as plt

plt.rcParams["font.family"] = "SimHei"

data_string = """百兽	18.1	13.7	26.3	13.1	65.2
梦岚	24.6	30.1	16.3	30.4	73.3
久酷	14.2	9	21.8	1.8	79.9
鹏鹏鸟	23.6	17.2	22.1	20.5	68.7
阿改	14.7	8.8	20.8	5.1	79.5
青枫	17.7	28.9	13.1	3.4	69.7"""
data = pd.DataFrame(list(map(lambda x: x.split('\t'), data_string.splitlines())))
data.columns = "选手	经济占比	伤害占比	承伤占比	推塔占比	参团率".split('\t')
for column in data.columns[1:]:
	data[column] = data[column].astype(float)

N = 5 # 雷达图属性个数
angles = np.linspace(0, 2 * np.pi, N, endpoint=False)
angles = np.concatenate((angles, [angles[0]]))
fig = plt.figure(figsize=[10, 6])
for i in range(len(data)):
    values = data.iloc[i, 1:].tolist()
    values.append(values[0])
    position = "23" + str(i + 1)
    ax = fig.add_subplot(int(position), polar=True)
    ax.plot(angles, values, "o-")
    ax.fill(angles, values, alpha=0.4)
    ax.set_thetagrids(angles[:-1] * 180 / np.pi,
                      data.columns[1:].tolist())
    ax.set_title(data.iloc[i, 0], color="b")
    ax.set_ylim(0, 100)
plt.subplots_adjust(hspace=0.5)
plt.show()

在这里插入图片描述


20240508~20240509

  • 昨天一天除了上下楼,安心宅在室内,几乎没怎么走路,因为感觉跟腱还是有点问题,有点像4月30日那天的疼痛,但相对缓和。
  • 今晚却感觉明显好了很多,间隔50个弹力带引体,中间穿插跑了四五圈,每段不到一圈,感觉很轻盈,差不多是接近4分的配速,用的前掌跑,一点都没有感觉疼痛(之前,前掌跑疼在内侧脚踝上方,后跟跑是疼在外侧脚踝下方,前者像是脚和小腿的连接处要断,后者是踩下去像是踩)。感觉上,要比前两天好很多,昨晚用两瓶水+艾草连续泡了差不多一个小时脚,还是要认真养,总归有点效果。

Fabric进行远程系统操作以及任务自动化,适合在其他服务器执行命令、文件上传下载和进行各种复杂的系统管理任务。

与同类库(Ansible、Paramiko)相比,Fabric属于轻量级,容易上手。

Ansible更适合配置管理和多机器协调。

项目地址:https://github.com/fabric/fabric

pip install fabric

执行远程 shell 命令和文件上传/下载。

from fabric import Connection
c = Connection('web@example.com')
c.run('hostname')

在上面的代码中,我们首先导入了 Fabric 的 Connection 类,然后创建了一个连接对象 c,它表示一个 SSH 连接。

我们简单地调用 run 方法在远程服务器上执行 hostname 命令。

Fabric 还提供了一系列的高级功能以满足更复杂的使用场景。

任务自动化。

对于需要重复执行的任务,比如定期备份数据库,检查服务状态等,你可以通过装饰器 task 定义任务函数,并通过命令行接口(CLI)快速执行。

以下是一个简单的例子,定义了一个备份数据库的任务:

from fabric import task

@task
def backup(c):
    c.run('pg_dump dbname > /var/backups/dbname.bak')

20240510~20240511

  • 昨天在游泳馆被救生员无情嘲讽,在深水区差点溺水,第一次是游到深水区时换气失误,想抓着泳道旁边的浮标,救生员说不要抓着,不会游就不要来深水区了,第二次是出去上了个厕所,回来下水忘记把泳镜带上了,一下去就是深水区,眼睛还睁不开,woc,那时真的觉得要溺水了,救生员看我好不容易探出头吸了口气浮上来,问我到底行不行,蚌。
  • 其实,拨水、踩水、抬头换气的节奏最后是找到了,能把25米泳池从头游到尾,就是气憋久了全身都很僵硬,探头出来就吸不进气。
  • 昨天遛了两圈,还是不太行,久坐,膝盖都感觉僵硬。但是,今晚,1000个单摇+100个双摇+100次台阶交替步,感觉很轻松,而且基本没有疼痛感(内侧脚踝正中稍有感觉,很快就好了)。说实话,现在就是既想知道自己到底有没有完全好,又怕会疼起来而不敢全力,难受。
  • 明日走个定向,下周正式恢复训练,我不想再等了。

Loguru 是一个第三方的日志库,它旨在简化日志记录的过程。

它的核心理念是提供一个预配置的、即插即用的 logger,无需编写冗长的配置代码。

更厉害的是 Loguru 的性能非常出色,它比 Python 内置的日志模块快10倍。

安装
Loguru 的安装非常简单,只需通过 pip安装即可:

pip install loguru

基本功能
Loguru 的 logger 对象是其核心,它负责将日志消息分派到不同的处理器。

下面是一个基本的日志记录示例:

from loguru import logger

logger.debug("Hello, this is a debug message!")

默认情况下,日志会输出到标准错误输出(stderr),但这是完全可配置的。

强大的add()函数
在 Loguru 中,添加处理器、设置日志格式、过滤消息和设置日志级别都可以通过一个函数——add()来实现:

logger.add(sys.stderr, format="{time} {level} {message}", filter="my_module", level="INFO")

这个函数允许你注册 sink,即负责管理带有记录字典的日志消息的处理器。Sink可以是多种形式,包括函数、文件路径、文件类对象、协程函数或内置处理器。

文件日志记录与自动轮换
Loguru 使得文件日志记录变得异常简单。只需提供一个字符串路径作为 sink,Loguru 会自动处理文件轮换、保留和压缩:

logger.add("file_{time}.log")

如果需要,还可以轻松配置轮换策略:

logger.add("file_1.log", rotation="500 MB")  # 文件过大时自动轮换
logger.add("file_2.log", rotation="12:00")   # 每天中午创建新文件
logger.add("file_3.log", retention="10 days")  # 保留10天后清理

优雅的字符串格式化
Loguru 支持使用花括号 {} 进行字符串格式化,这比传统的 % 格式化更为优雅和强大:

logger.info("If you're using Python {}, prefer {feature} of course!", 3.6, feature="f-strings")

捕获线程中的异常
Loguru 通过 catch() 装饰器/上下文管理器确保任何错误都能被正确地传递到 logger,解决了线程中异常未被捕获的问题:

@logger.catch
def my_function(x, y, z):
    return 1 / (x + y + z)

彩色日志输出
Loguru 会自动为你的日志添加颜色,如果你的终端支持的话:

logger.add(sys.stdout, colorize=True, format="<green>{time}</green> <level>{message}</level>")

高级功能
异步、线程安全、多进程安全
Loguru 的所有 sink 默认是线程安全的。虽然它们不是多进程安全的,但可以通过 enqueue 参数确保日志的完整性:

logger.add("somefile.log", enqueue=True)

完整的异常堆栈跟踪
Loguru 允许你记录完整的异常堆栈跟踪,包括变量的值,这对于调试是非常有用的:

logger.add("out.log", backtrace=True, diagnose=True)

结构化日志
如果你需要将日志序列化以便于解析或传递,可以使用 serialize 参数:

logger.add(custom_sink_function, serialize=True)

延迟评估
Loguru 允许你以不牺牲性能的方式记录详细信息:

logger.opt(lazy=True).debug("If sink level <= DEBUG: {x}", x=lambda: expensive_function(2**64))

自定义日志级别
Loguru 允许你添加自定义的日志级别:

new_level = logger.level("SNAKY", no=38, color="<yellow>", icon="🐍")
logger.log("SNAKY", "Here we go!")

日志处理
Loguru 提供了更好的 datetime处理方式,使得格式化更加直观:

logger.add("file.log", format="{time:YYYY-MM-DD at HH:mm:ss} | {level} | {message}")

与标准日志的兼容性
Loguru 完全兼容 Python 的标准日志库:

handler = logging.handlers.SysLogHandler(address=('localhost', 514))
logger.add(handler)

便捷的解析器
Loguru 提供了一个 parse() 方法,可以帮助你从日志中提取特定信息:

pattern = r"(?P<time>.*) - (?P<level>[0-9]+) - (?P<message>.*)"
caster_dict = dict(time=dateutil.parser.parse, level=int)

for groups in logger.parse("file.log", pattern, cast=caster_dict):
    print("Parsed:", groups)

通知器集成
Loguru 可以与 notifiers 库结合使用,以便在程序意外失败时发送电子邮件或其他类型的通知:

import notifiers

params = {
    "username": "you@gmail.com",
    "password": "abc123",
    "to": "dest@gmail.com"
}

notifier = notifiers.get_notifier("gmail")
notifier.notify(message="The application is running!", **params)

20240512(学校发生了很不好的事)

  • 今日首蚌,以为昨天是母亲节(我一直记得六月第三个周日是父亲节,那五月的母亲节应该就是周六,不能还是周日吧),昨天零点给老妈发了个红包(老妈节日快乐),结果今天零点一看,怎么今天还是母亲节?噢,怪不得老妈问我昨天过啥节。
  • 然后,定向也是一言难尽,虽然主旨团建,我也没指望去冲成绩,但刚出发时,一个个还热情澎湃,想一路小跑,地铁站上个扶梯都要跑。然而,越野赛里,差距从来不是在上坡拉开的,平地和下坡才是分胜负的地方,结果,出地铁站,他们几个六分配开外跑了不到一公里,就不行了,老老实实养生拉倒。最后看到拿冠军队,清一色的alpha3,你说这拿啥比,人家要装备有装备,要实力有装备。
  • 傍晚回来,刚到操场就逮到嘉伟黑练,他在2000米×4组的间歇,我上去就不自量力地想跟,不到两圈就被拉爆。补弹力带引体10个×3组,正向箭步30个×4组+反向箭步30个×4组(+20kg),完事陪嘉伟慢跑五圈,状态良好,目前,纯用前掌跑确实没什么问题,但是伤还是没好,今天定向结束时,明显走路已经不适。我跟崔教头把NRC给退了,已经翘了太多,这期不可能再坚持到最后,有缘再续,人还是什么时间做什么事吧,PACER,就当一个未尽的梦想吧。
  • PS:值得一提的是,下午DGL5000米跑进22分半,均配4’28",这个成绩是相当惊艳,她第一个1000米跑到4’15",后面崩得很厉害,有人带她的话,是有很大提升空间的。就22分半换算成同水平男性,至少是20分以内。感觉DGL和宋某一样,都爆发了,突飞猛进。我记得LXY的3000米PB是12’50",DGL的5000米换算下来并不比这个成绩差,而且前者还是在正式比赛中跑出来,给我的震撼不亚于宋某最近的表现,这帮人真的太可怕。
    在这里插入图片描述在这里插入图片描述
    在这里插入图片描述

kivy quick start

from kivy.app import App
from kivy.uix.button import Button

class TestApp(App):
    def build(self):
        return Button(text='Hello, Kivy')
TestApp().run()

kivy触控响应

from kivy.app import App
from kivy.uix.widget import Widget

class MultiTouch(Widget):
    def on_touch_down(self, touch):
        if touch.is_double_tap:
            print('双次点击!')
        else:
            print('触控点:', touch.pos)

class TouchApp(App):
    def build(self):
        return MultiTouch()

TouchApp().run()

自定义组件(画一个黄色的圆圈)

from kivy.uix.widget import Widget
from kivy.graphics import Color, Ellipse

class MyPaintWidget(Widget):

    def on_touch_down(self, touch):
        color = (1, 1, 0)  # 黄色 
        with self.canvas:
            Color(*color)
            d = 30.
            Ellipse(pos=(touch.x - d / 2, touch.y - d / 2), size=(d, d))

class PaintApp(App):
    def build(self):
        return MyPaintWidget()

PaintApp().run()

20240513

  • 白天巨晒,夏天真的要来了。晚饭前去称了个毛重,69.5kg,最近没管住嘴,长得太多。
  • 停跑两周后,开始恢复训练(黑练),晚上1000米热身+5000米节奏跑@4’05",好消息是一点没疼(跑完感觉也还行,至少比四月底那段时间要好很多,本想再慢跑会儿,保守点儿算了,怕再出事),坏消息是有氧暴跌,尽管预料之中。
  • 没有穿碳板,起手能轻松加到4分整,心率不过150bpm出头,但是2000米之后迅速攀升,平均心率174bpm,最大心率195bpm,很难受,跑完胸腔闷了很久,要知道以前这个配速也就160bpm出头的心率。估测有氧阈速下降20秒左右,手表给的训练效果是极差这个月还是第一次开表跑步,总是要面对现实的
  • 之后是黄梅,再就是大蒸笼的两个月了,几乎无法PB,不适合长距离拉练。夏训,大部分时间都是和朋友一起间歇变速,想起去年、前年的这个时期,也都是恢复期,水平暴跌,但都坚持了一个月的夏训,秋冬才能快速看到提升。
    在这里插入图片描述在这里插入图片描述

之前joblib都是用来加载numpy的数据和模型,原来这个还能用来做并行计算:

from joblib import Parallel, delayed
import math

results = Parallel(n_jobs=-1)(delayed(math.sqrt)(i ** 2) for i in range(10))

以及缓存机制:

from joblib import Memory
cachedir = 'your_cache_dir_goes_here'
mem = Memory(cachedir)
import numpy as np
a = np.vander(np.arange(3)).astype(float)
square = mem.cache(np.square)
b = square(a)                                   
______________________________________________________________________...
[Memory] Calling square...
square(array([[0., 0., 1.],
       [1., 1., 1.],
       [4., 2., 1.]]))
_________________________________________________...square - ...s, 0.0min

c = square(a)
# The above call did not trigger an evaluation

几个主要的函数:

Module reference
Memory([location, backend, mmap_mode, ...])

A context object for caching a function's return value each time it is called with the same input arguments.

Parallel([n_jobs, backend, return_as, ...])

Helper class for readable parallel mapping.

parallel_config([backend, n_jobs, verbose, ...])

Set the default backend or configuration for Parallel.

dump(value, filename[, compress, protocol, ...])

Persist an arbitrary Python object into one file.

load(filename[, mmap_mode])

Reconstruct a Python object from a file persisted with joblib.dump.

hash(obj[, hash_name, coerce_mmap])

Quick calculation of a hash to identify uniquely Python objects containing numpy arrays.

register_compressor(compressor_name, compressor)

Register a new compressor.

Deprecated functionalities
parallel_backend(backend[, n_jobs, ...])

Change the default backend used by Parallel inside a with block.

20240514

  • 难绷,竟然发烧了,昨天就觉得很累,特别困惑,直到晚上十点才意识到是要发烧,虽然不咳嗽,但头疼得不行,也没睡好。午饭吃得很清淡,但也撑了大半天,肚子一直没有消下去,有点不是很能绷得住。
  • 老妈嘲讽我之前说自己身体好,不生病。最近虽然缺少训练,但是每天早上都会吃水果,饮食虽然比不上三月份规律,但是也说得过去。前天定向坐车回来时就觉得喉咙有点不对头,而且最近亦童也是发烧刚好,估计就是被传染的。
  • 晚上去搞了点布洛芬,消炎药,枇杷露和冲剂。22年12月新冠发烧后,应该就没有感冒发烧过,之前就觉得盛久必衰,一旦病,必是大病,这次是真难顶。
  1. DGL提供了两个脚本来协助进行分布式训练:
  • tools/copy_files.py用于将图分区复制到图;
  • tools/launch.py​​用于在机器集群中启动分布式训练工作;
  • copy_files.py将机器(在其中对图形进行分区的机器)中的分区数据和相关文件(例如, 训练脚本)复制到机器集群(在其中进行分布式训练); 该脚本将分区复制到机器上, 在该计算机上, 分布式训练作业将需要该分区; 该脚本包含四个参数:
    • --part_config指定分区配置文件, 该文件包含本地计算机中分区数据的信息;
    • --ip_config指定集群的IP配置文件;
    • --workspace指定训练机中存储与分布式训练有关的所有数据的目录;
    • --rel_data_path指定工作空间目录下将存储分区数据的相对路径;
    • --script_folder指定工作空间目录下存储用户的训练脚本的相对路径;
    • 注意: copy_files.py根据IP配置文件找到合适的机器来存储分区; 因此, copy_files.pylaunch.py​​应该使用相同的IP配置文件;
  1. DGL提供了用于启动群集中的分布式训练作业的tools/launch.py​​; 该脚本进行以下假设:
  • 分区数据和训练脚本已复制到群集或群集中所有计算机均可访问的全局存储(例如NFS);

  • 主计算机(在其中执行启动脚本的计算机)具有对所有其他计算机的无密码ssh访问权限;

  • 注意: 必须在集群中的一台计算机上调用启动脚本;

  • 下面显示了在集群中启动分布式训练作业的示例:

    python3 tools/launch.py \
    --workspace ~graphsage/ \
    --num_trainers 2 \
    --num_samplers 4 \
    --num_servers 1 \
    --part_config data/ogb-product.json \
    --ip_config ip_config.txt \
    "python3 code/train_dist.py --graph-name ogb-product --ip_config ip_config.txt --num-epochs 5 --batch-size 1000 --lr 0.1 --num_workers 4"
    
  • 配置文件ip_config.txt包含集群中计算机的IP地址; ip_config.txt的典型示例如下:

    172.31.19.1
    172.31.23.205
    172.31.29.175
    172.31.16.98
    
    • 每行是计算机的IP地址; IP地址后面还可以有一个端口, 该端口指定训练人员之间的网络通信使用的端口; 如果未提供端口, 则默认值为30050;
    • 启动脚本中指定的工作空间是计算机中的工作目录, 其中包含训练脚本, IP配置文件, 分区配置文件以及图形分区; 文件的所有路径都应指定为工作空间的相对路径;
    • 启动脚本会在每台计算机上创建指定数量的训练作业(--num_trainers);
    • 另外, 用户需要为每个训练者指定采样器处理的数量(--num_samplers); 采样器进程的数量必须与initialize() 中指定的辅助进程的数量匹配;

20240515

  • 到昨晚发烧基本好了,这次就是单纯发烧,没有鼻涕也不咳嗽,但是肠胃和消化紊乱,吃啥拉啥,从昨晚开始,到今天下午依然如此,晚上差不多是好了。
  • 前天晚上刚开始烧的时候,手表判定我一晚上没睡觉,心率最低57bpm(正常在40bpm以下)。然后,昨天到晚,手表的身体电量第一次归零警告,不停地震动,我真的是无语了。
  • 晚上就喝了一碗红豆粥,两个韭菜盒子,根本不敢吃荤的。完事跑了3000米,4’26"的配速,补30个弹力带引体,非常的吃力。连ZJC都跟不上,虽然可能病还没完全好。就很怀念之前三月份吃嘛嘛香的时候,能吃真的是福,现在啥也不敢吃。
    在这里插入图片描述
  1. ogb库, 全称开源图基准(Open Graph Benchmark), 是一个图深度学习的基准数据集, 其中内置了用于下载和处理ogb数据集转为dgl.data.DGLGraph对象的接口函数; 简单pip安装即可;
  • 加载数据集Graph Property Prediction示例代码: 这是整图分类;

    # Load Graph Property Prediction datasets in OGB
    import dgl
    import torch
    from ogb.graphproppred import DglGraphPropPredDataset
    from torch.utils.data import DataLoader
    
    def _collate_fn(batch):
    	# batch is a list of tuple (graph, label)
    	graphs = [e[0] for e in batch]
    	g = dgl.batch(graphs)
    	labels = [e[1] for e in batch]
    	labels = torch.stack(labels, 0)
    	return g, labels
    
    # load dataset
    dataset = DglGraphPropPredDataset(name='ogbg-molhiv')
    split_idx = dataset.get_idx_split()
    # dataloader
    train_loader = DataLoader(dataset[split_idx["train"]], batch_size=32, shuffle=True, collate_fn=_collate_fn)
    valid_loader = DataLoader(dataset[split_idx["valid"]], batch_size=32, shuffle=False, collate_fn=_collate_fn)
    test_loader = DataLoader(dataset[split_idx["test"]], batch_size=32, shuffle=False, collate_fn=_collate_fn)
    
  • 加载数据集Node Property Prediction示例代码, 该数据集只有一个图: 这是节点分类;

    # Load Node Property Prediction datasets in OGB
    from ogb.nodeproppred import DglNodePropPredDataset
    
    dataset = DglNodePropPredDataset(name='ogbn-proteins')
    split_idx = dataset.get_idx_split()
    
    # there is only one graph in Node Property Prediction datasets
    g, labels = dataset[0]
    # get split labels
    train_label = dataset.labels[split_idx['train']]
    valid_label = dataset.labels[split_idx['valid']]
    test_label = dataset.labels[split_idx['test']]
    
  • 加载数据集Link Property Prediction示例代码: 这是链接预测;

    # Load Link Property Prediction datasets in OGB
    from ogb.linkproppred import DglLinkPropPredDataset
    
    dataset = DglLinkPropPredDataset(name='ogbl-ppa')
    split_edge = dataset.get_edge_split()
    
    graph = dataset[0]
    print(split_edge['train'].keys())
    print(split_edge['valid'].keys())
    print(split_edge['test'].keys())
    

20240516~20240517

  • 我大概是进入了一段厌跑期,周一还觉得自己没有掉太多,今天已经连4分半跑个10圈都喘得不行了,之前开玩笑说跑不过DGL,是真的跑不过。现在就是明显心肺支撑不住,加上最近路走得少,左膝有点感觉,难以想象,现在竟然跑得如此痛苦。
  • 游泳是更加熟练了,换气得在水下提前把头抬好,这样就能抬出头的时间更长,有更充裕的时间换气。但是今天看到一个大妈,全程都是狗刨式,全程头都能在水面之上,居然能不沉下去,可能体脂高比较容易浮起来。
  • 明天要看宋某表演,但是天气太热,想PB还是有难度的。下周WXY来沪,应该是要来一趟学校,以前的老大哥,现在也敌不过新人了。
    在这里插入图片描述

线下推断

class StochasticTwoLayerGCN(nn.Module):
	def __init__(self, in_features, hidden_features, out_features):
		super().__init__()
		self.hidden_features = hidden_features
		self.out_features = out_features
		self.conv1 = dgl.nn.GraphConv(in_features, hidden_features)
		self.conv2 = dgl.nn.GraphConv(hidden_features, out_features)
		self.n_layers = 2

	def forward(self, blocks, x):
		x_dst = x[:blocks[0].number_of_dst_nodes()]
		x = F.relu(self.conv1(blocks[0], (x, x_dst)))
		x_dst = x[:blocks[1].number_of_dst_nodes()]
		x = F.relu(self.conv2(blocks[1], (x, x_dst)))
		return x

	def inference(self, g, x, batch_size, device):
		"""
		Offline inference with this module
		"""
		# Compute representations layer by layer
		for l, layer in enumerate([self.conv1, self.conv2]):
			y = torch.zeros(g.number_of_nodes(),
							self.hidden_features
							if l != self.n_layers - 1
							else self.out_features)
			sampler = dgl.dataloading.MultiLayerFullNeighborSampler(1)
			dataloader = dgl.dataloading.NodeDataLoader(
				g, torch.arange(g.number_of_nodes()), sampler,
				batch_size=batch_size,
				shuffle=True,
				drop_last=False)

			# Within a layer, iterate over nodes in batches
			for input_nodes, output_nodes, blocks in dataloader:
				block = blocks[0]

				# Copy the features of necessary input nodes to GPU
				h = x[input_nodes].to(device)
				# Compute output.  Note that this computation is the same
				# but only for a single layer.
				h_dst = h[:block.number_of_dst_nodes()]
				h = F.relu(layer(block, (h, h_dst)))
				# Copy to output back to CPU.
				y[output_nodes] = h.cpu()
			x = y
		return y

20240518

  • 宋某发挥了他一贯的鸽子精神,赛前大腿又拉伤,成功错过联盟杯。
  • 昨天游完泳后,3000米把跟腱又跑疼了,一言难尽,我要开始六分配养老了。
  • 到小姨家吃点好的,鸡汤、炒鳝丝、红烧梅花肉,开始养膘,爱咋咋滴。

最近他们发现gpt-4o在中文token的压缩上做了一些操作,文本充分长的情况下,压缩率较于gpt-4差不多是1.4,即同样一段文本gpt-4解析的token数是gpt-4o的1.4倍左右。

原因是两者使用了不同分词编码:

import tiktoken
import langdetect
import regex as re

# https://platform.openai.com/tokenizer
T1 = tiktoken.get_encoding("cl100k_base")
T2 = tiktoken.get_encoding("o200k_base")

print(T1.n_vocab) # 100277
print(T2.n_vocab) # 200019

T1.encode("text ...")
T2.encode("text ...")

但是天下没有免费的午餐,token的减少,带来的是鲁棒性变差。

一个经典的例子:

prompt: "给主人留下些什么吧"这句话翻译成中文

gpt-4o对这个的回答错误很多,而且每次都不一样,原因是给主人留下些什么吧cl100k_base的一个token:

T1.encode("给主人留下些什么吧") # [177431]
T2.encode("给主人留下些什么吧") # [90112, 36668, 17792, 98184, 6271, 222, 82696, 7305, 100]

这种情况还有很多,但是找出来的这些长token都是很不好的短语,经常是些不可描述的网站上广告词。

现在这个问题的出现,猜测是因为tokenizer是在全语料上训练,但transformer是在高质量语料上训练,导致transformer在这些低质量token上的表现很差。


20240519~20240520

  • 果然,到头来,还是不想放弃。
  • 总之,最近五年的四五月份都不太顺心,像是一种诅咒,20年无声的毕业季,21年在断舍离,22年在坐牢,23年活该,今年又是历史重演。
  • 总是好了伤疤忘了痛,不疼的时候还是想去跑两步,寻常的5000米,却几近全力,鞋子磨脚得不行,一个多月没有穿碳板跑,对我来说已经无所谓了。LXY和DGL晚上都在凑5’20"的13.14km,说实话,我很难想象自己现在能跑10km,其实每次都这样。
  • 补力量训练,一周一次还是要有的,不然真的要废了。弹力带引体10个×5组,箭步45个×6组(+15kg,正反交替),20kg的片和锤铃都被拿去压遮阳棚,15kg的片真的做起来一点感觉都没有,所以每组多加了15个箭步,大腿无碍,反而是小臂最后酸得不行。这个月能把中期搞完,六月份开始规律性地恢复训练。
  • 最大摄氧量终于掉到了61,大概还会更低吧,现在应该是连60都不到了。最近走路明显还是有感觉的,虽然今晚没有疼,希望伤能彻底恢复,真的好难受。
    在这里插入图片描述
  • DGL中消息传递通过两种接口表达:
    • send(edges, message_func): 根据给定的边计算消息;
    • recv(nodes, reduce_func): 收集输入的消息, 进行消息聚合等其他操作;
  • 以上两种阶段的接口可以覆盖所有在消息传递框架下定义出的模型, 但是这种方式是低效的, 因为它需要存储显式消息(explicit message), DGL Blog Post 中给出了一些性能评估的详细情况;
  • 解决方案也在上述链接中给出了说明, 即将两阶段融合进同一个kernel中, 于是就无需生成并存储显式消息, 因此DGL推荐使用内置的消息传递函数, 它们已经进行了这种融合优化;
  • 代码示例:
    import dgl
    import dgl.function as fn
    import torch as th
    import numpy as np
    
    # 1 create a DGLGraph
    src = np.random.randint(0, 100, 500)
    dst = np.random.randint(0, 100, 500)
    g = dgl.graph((np.concatenate([src, dst]), np.concatenate([dst, src])))
    # 2 set feature for nodes and edges
    g.ndata['h'] = th.randn((g.number_of_nodes(), 10)) # each node has feature size 10
    g.edata['w'] = th.randn((g.number_of_edges(), 1))  # each edge has feature size 1
    # 3 collect features from source nodes and aggregate them in destination nodes
    g.update_all(fn.copy_u('h', 'm'), fn.sum('m', 'h_sum'))
    print(g.ndata['m']) # error
    print(g.edata['m']) # error
    # 4 multiply source node features with edge weights and aggregate them in destination nodes
    g.update_all(fn.u_mul_e('h', 'w', 'm'), fn.max('m', 'h_max'))
    # 5 compute edge embedding by multiplying source and destination node embeddings
    g.apply_edges(fn.u_mul_v('h', 'h', 'w_new'))
    
    • ① 首先随机生成一个带有至多101个节点(因为随机值randn未必会取得到100), 500条边的随图, 将图设置为无向图(取逆向图再合并);
    • ② 接着为边和节点分别设置随即特征值;
    • 重点就是第三步: update_all函数可以参考本文 Chapter 2中的具体说明, 它接收一个消息生成函数, 消息聚合函数和更新函数(optional), 这里先复制了一份节点h特征然后再将所有节点的h_sum特征值更新为其近邻节点的m特征值之和;
      • 'm'显然是一个中间值, 可以发现update_all函数执行结束后根本没有名为m的特征值, 这就是上面所说的融合后而无需存储显示消息;
      • 为了看出具体的传播逻辑可以创建一个小一些的简单图, 特征值用常数, 这样输出更新后的特征值会更加清晰;
    • ④ 与第③步大同小异, 仍是节点特征的更新, 不再赘述;
    • ⑤ 最后通过将源节点和目标节点的h特征值相乘达到更新边特征的目的;’
  • 自定义消息传递函数的写法参考(不推荐自定义, 尽量使用内置的进行拼积木);
    • fn.u_mul_e('h', 'w', 'm')等价于:
    def udf_u_mul_e(edges):
    	return {'m' : edges.src['h'] * edges.data['w']}
    
    • fn.max('m', 'h_max')等价于:
    def udf_max(nodes):
    	return {'h_max' : th.max(nodes.mailbox['m'], 1)[0]}
    

20240521

  • 晚饭后去遛了会儿,身体极其沉重,没有开表,大概是530-600的区间跑了11圈,心率最高都过了150bpm,感觉整个人都废了。
  • 开完会,八点半去操场拉弹力带引体,10个×5组,之前一天拉完50个,第二天就完全拉不动了,但现在50个确实还挺轻松,昨天也拉了50个。
  • 其实我不想跑,但是晚上操场人还挺多,除了AK,嘉伟和DGL,其他人都在,XR在跟LXY跑,他俩属于是卷王遇卷王了,两个人月跑量估计都得有250K以上。YY,LZR,安迪,胡哥也在。我跟着胡哥冲了三圈,也没有开表,但至少是4分以内的配速,最后冲刺应该有345左右。我还是好想跑,渴望重回巅峰。
  • 今天高百五月资格赛成绩出炉,有280个院校参加,场地3000米×20人(2名女生),真特么恐怖。冠军解放军大平均配速有321,我们这边最强的嘉伟也就勉强跑进10分整(配速320)。就算是一些中流的学校(华农,扬大)也有平均345的水平,而我自己,虽然巅峰期3000米多次跑到11分以内的成绩(最好的一次跑到10分50秒),但估摸着就算是认真跑,也不太可能达到10分半(配速330)。
  • 正好最近关注了B站的华农狮山长跑队,他们的水平分布跟我们差不多,但是厚度比我们强太多,像嘉伟这个段位的,他们能有三四个人,而且那边有两个女生(倩姐和菊姐,还都是博士),万米41分台,半马130台,这对标男子已经是嘉伟这个档次的了,我们这边可一个都找不出来。

pdfplumber关于page.debug_table_finder函数的用法

这个是用于debug的方法,table_debug = page.debug_tablefinder(table_settings={}),其中table_debug是一个TableFinder的对象,主要有以下几个属性:

  • table_debug.cells:由一系列四元组(x0, y0, x1, y1)构成的列表,用于表示tables中所有非None(注意,空字符串也会被考虑在内)元素对应的单元格的左上角(x0, y0)和右下角(x1, y1)的坐标。

    • 如果将table_debug.cells中所有的x0, x1统计到一个集合xs中,y0, y1统计到一个集合ys中,并假设numpy.array(tables)是一个形状为(X, Y)的矩阵,那么xs中不重复元素的个数为X+3ys中不重复的元素的个数为Y+3,而且xsys中最小的元素一定是0

    • 那为什么是X+3Y+3呢,假定xs=(x_1, x_2, ..., x_{n+3}),那么必然x_1=0,接下来x_2x_{n+2}共计X+1个坐标表示高度为X的表格每一行的上下边界,x_1x_{n+3}可能是页面边缘的坐标(但是如果有多个表格的话,每个表格x_{n+3}未必相同),可以不用管。ys同理

      这个规则仅适用于一个页面中只有一个表格的情况,如果有多个表格,因为可能存在两个表格的行列位置重合而不满足上述规则,但是如果行列位置并不重合的话,除了x_1位置是0是一样的之外,右边缘x_{n+3}如果不一样,那么就是如果有K个表格,形状为(X_1, Y_1),...,(X_K, Y_K)的话,xs中会有X_1+...+X_K+2K+1个元素,ys同理

    • 然后接下来是table_debug.cells中的元素数量:len(table_debug.cells)恰好等于tables中非None元素的个数加一,为什么是加一,是因为table_debug.cells中的第一个元素一定是(0, 0, x_{n+3}, y_{n+3}),即表示整个表格的左上角和右下角,接下来所有的位置就是按照行优先顺序(即一行一行的遍历)遍历所有非None单元格的左上和右下边缘。

      这个规则对于页面中有多个表格的情况依然成立,此时第一个元素一定是(0, 0, X^K_{n+3}, Y^K_{n+3}),即表示整个页面所有表格区域的左上角和右下角。

  • table_debug.intersections:这是一个字典,键是一系列的二元组(x, y)坐标(可能是表示每个分界线的起始位置),值是一个很复杂的字典,里面会表示表格的每个分界线的起止位置(x0, y0, x1, y1),注意表格的分界线其实是可以很复杂的,而且看起来这个分界线是


20240522

  • WXY重回校园,有些发福,但也不至于认不出来,他之前休学两年,22年本科毕业,和现任都在重庆工行工作,这次是520旅游,昨天在迪士尼,也挺幸福。说实话,随大流卷学历,纯吃饱撑着。
  • 晚上跟嘉伟和AK进行倒金字塔间歇训练,1600米×1+1200米×2+800米×3+400米×4,组间休息150-180秒,D组圈速77-79秒,E组全速82-85秒(ABCF不考虑)。我最终只跑了1600米×1+800米×2+400米×4,1600米和800米的圈速81-83秒,400米的圈速能跑进80秒,最快能到75秒,差强人意。
  • 嘉伟有如天神下凡,圈速都在75-77秒,最后两组400米甚至跑进70秒,把我和AK都给干废了。嘉伟目前状态极好,伤痛已经痊愈,破而后立,目测已经超越巅峰期,很可怕的状态。
  • 我的第一个1600米圈速81秒(5分25秒,这个其实跑得还行,相当于5分上下的1500米,不算太差),力竭,我知道自己已经不行了。第二个1200米跑了两圈,胸腔闷得不行,提前结束,结果AK也提前停下,我是真的不行了,他指定是偷懒。
  • 跑完右脚内侧脚踝上方有阵痛,这是旧伤,但感觉不是很碍事,明天看看情况怎样。停跑半个多月,虽然水平暴跌,但好在力量训练、核心训练、引体向上都没有断,身体素质保持得还行。昨晚称毛重66kg,似乎是因为上周发烧,体重又掉下来了。目前啥都缺,既要慢跑恢复有氧,也需要间歇恢复速度。虽然今天的强度过大,但机会难得,AK和嘉伟都在,我知道自己现在完全跟他俩水平不匹配,以前至少是能掰掰手腕,现在跟他俩一起训练就是纯吊车尾,但我不是一向如此吗?我总会重回巅峰的。(左图是我,右图是嘉伟,没有对比就没有伤害
    在这里插入图片描述在这里插入图片描述

散点图动态效果

单图动态:

import numpy as np
import matplotlib.pyplot as plt
from matplotlib.animation import FuncAnimation

fig, ax = plt.subplots()
xdata, ydata = [], []
ln, = ax.plot([], [], 'ro')

ax.set_xlim(0, 2*np.pi)
ax.set_ylim(-1, 1)


def init():
    # print('init')
    ax.set_xlim(0, 2*np.pi)
    ax.set_ylim(-1, 1)
    return ln,


def animate(i):
    # print('animate', i)
    xdata.append(i)
    ydata.append(np.sin(i))
    ln.set_data(xdata, ydata)
    return ln,


ani = FuncAnimation(fig,
                    animate,
                    frames=np.linspace(0, 2*np.pi, 128),
                    # init_func=init,
                    blit=True
                    )
plt.show()

多图动态效果:

import numpy as np
import matplotlib.pyplot as plt
from matplotlib.animation import FuncAnimation

fig, axes = plt.subplots(1, 2)

xdata, ydata1, ydata2 = [], [], []

ln1, = axes[0].plot([], [], 'ro')
ln2, = axes[1].plot([], [], 'go')


def init():
    # print('init')
    axes[0].set_xlim(0, 2*np.pi)
    axes[0].set_ylim(-1, 1)
    axes[1].set_xlim(0, 2*np.pi)
    axes[1].set_ylim(-1, 1)
    return ln1, ln2,

def animate(i):
    # print('animate', i)
    xdata.append(i)
    ydata1.append(np.sin(i))
    ln1.set_data(xdata, ydata1)

    ydata2.append(np.cos(i))
    ln2.set_data(xdata, ydata2)
    return ln1, ln2,

ani = FuncAnimation(fig,
                    animate,
                    frames=np.linspace(0, 2*np.pi, 128),
                    init_func=init,
                    blit=True
                    )
plt.show()

20240523~20240524

  • 昨天院里在面教职,wyl自是不能缺席,但老爷子墨水堪忧,自是要拉些人撑场面,就被硬薅大半天。面教职分两步,先做研究报告,再做课堂试讲,说句实话,nlp和cv这块,门槛真的挺低,其实除了top的那批,下面的都只小打小闹。面了两个复旦,一个交大,这几天正好也听了一些本科硕士的答辩,做的东西都差不了太多,尤其是复旦的CWT,做的是跨域语义分割方向,讲完CWY就问这东西大模型是不是已经做的很好了,扯了堆有的没的,去年segment anything出来之后,已经可以把清明上河图上每个人、每棵树都划分得很完美,可能有些东西能用就行了,何必整那么多花里胡哨的。有种激光剑打原始人的无奈。
  • 昨天休一天,今晚继续恢复训练,说实话这两天还是疼,但是相对来说,可能比之前要好一点。其实昨天脚是有点疼的,今天断断续续凑了10km出头,只有中间6km是开表的,差不多4分配上下在跑,对我来说很艰难。四分配这个好不容易跨过去的槛再次压在我的头上,让我喘不过气。完事补了60个弹力带引体,以及50个正向箭步×4组(+15kg)
  • XR和ZYY跑了15组400米间歇,圈速85秒左右,LXY又在卷,她最近的跑量有点恐怖,我看到KEEP上已经是五月打卡榜霸主了,远远领先第二位,LY也慢跑了好久,目测六分配左右。
  • 这个月记录的跑量刚刚30km出头,实际上应该有50km左右,因为有很多慢跑都没开表。伤或许真的好不了了,有点像是慢病,找不到可以彻底治愈的方法。可能还是该休得更久些,但是总耐不住寂寞,自讨苦吃。
    在这里插入图片描述

PPO(Proximal Policy Optimization)
工作原理
目标函数:PPO旨在通过最大化特定的目标函数来改进策略。这个目标函数通常包括一个期望回报的项,以及可能的正则化项(如熵)来鼓励探索。
概率比率剪切:PPO使用了一种称为概率比率剪切的技术,这涉及到计算新策略和旧策略对动作概率的比率。如果这个比率偏离1太远,PPO会通过剪切这个比率来限制更新的幅度,从而避免过大的策略变动。
目标函数的优化:PPO对目标函数进行优化,通常使用随机梯度上升方法。这个过程涉及到在策略网络参数上应用梯度更新,以增加高回报动作的概率,同时减少低回报动作的概率。
多次迭代更新:PPO算法通常在一次策略更新中使用多个迭代,这意味着它会重复利用同一批数据多次,以进行有效的学习。
实现步骤
收集数据:首先,使用当前策略在环境中执行多个动作,收集状态、动作和回报的数据。
计算优势函数:然后,计算每个时间步的优势函数,这通常涉及到对回报的估计和基线(比如状态价值函数)的使用。
优化策略:接着,通过优化目标函数来更新策略参数。这个过程包括计算目标函数的梯度,并使用梯度上升来更新参数。
重复迭代:重复上述过程多次,直到策略收敛或达到预定的迭代次数。
加载4个模型,2个推理,2个训练

Actor Model:演员模型,想要训练的目标语言模型

Critic Model:评论家模型,它的作用是预估总收益

Reward Model:奖励模型,它的作用是计算即时收益

Reference Model:参考模型,它的作用是在RLHF阶段给语言模型增加一些“约束”,防止语言模型训歪(朝不受控制的方向更新,效果可能越来越差)

其中:

Actor/Critic Model在RLHF阶段是需要训练的;而Reward/Reference Model是参数冻结的。

Critic/Reward/Reference Model共同组成了一个“奖励-loss”计算体系,我们综合它们的结果计算loss,用于更新Actor和Critic Model

2、DPO (Direct Preference Optimization)
DPO是一种相对较新的方法,它直接优化用户或专家的偏好,而非传统的累积奖励。在DPO中,通过对比不同的决策序列或策略,并根据用户或专家的偏好来优化模型,使得最终的策略能够更好地符合预期的行为。DPO通常用于那些难以明确定义奖励函数的场景,或者在用户偏好需要直接编码到决策过程中的应用中。

DPO的实现需要构建一个偏好模型,该模型能够从用户或专家的反馈中学习。在实际应用中,可能需要设计一种机制来收集用户的偏好数据,例如通过对比查询或者排名反馈。然后使用这些数据来训练一个或多个模型,这些模型能够预测给定决策序列的偏好得分,并据此来优化策略。

只需要加载2个模型,其中一个推理,另外一个训练,直接在偏好数据上进行训练。

3、DPO(Distributed Proximal Policy Optimization)
工作原理
分布式架构:DPO在多个计算节点上并行运行,每个节点都有自己的一份策略副本,并在各自的环境实例中收集数据。
数据同步:在一定时间步或者周期之后,各个节点会将它们收集到的数据(如梯度信息或者策略更新)发送给中心节点。
中心化更新:中心节点会聚合这些数据,并进行策略更新。更新后的策略参数随后会被分发回各个计算节点。
并行化探索:由于每个节点可以独立探索不同的状态空间区域,DPO能够更加高效地进行探索和利用,从而加快学习过程。
实现步骤
分配任务:将并行任务分配到多个计算节点。
局部数据收集:各个节点独立执行策略,收集状态、动作和回报等数据。
局部梯度计算:每个节点计算其收集数据的梯度或参数更新。
全局同步:所有节点将梯度或参数更新发送到中心节点进行聚合。
策略更新和分发:中心节点更新策略参数后,将新的策略分发给所有节点。
重复迭代:重复上述过程,直到策略收敛或达到预定的迭代次数。
总结来说,PPO专注于通过剪切概率比率来稳定策略更新,而DPO在此基础上引入分布式计算,以提高数据收集和处理的效率,加快学习速度。


20240525

  • 下午看了个老中医,其实我是不太信中医的,但据说是老多年交情,挺靠谱。
  • 本来是给表哥看失眠,就顺便也把我的伤看了一下。任使怎么折腾右脚,我都感觉不疼(其实只有在跑动的时候才会有感觉,其他不管怎么搞都找不到那个疼的点)。但是一扎针,立刻就有感觉了,有些地方扎得不疼,有些地方扎得确实很有感觉,从胫骨一端扎到另一端,扎了六七根针,说是我是气血不足,扎一次也不会那么快见效,或许有用吧。
  • 昨晚刚好安迪带他的浙大老学长YLS来财大跑步,YLS很强,是浙大夸父跑步联盟的前队长,全马245,刚来上海工作,在宝山,离学校也不远,以后倒是可以周末来一起训练(他说正愁来上海没人一起跑强度的,宝山那边全是养老选手),而且跟AK还是老乡,身材也有几分神似,果然大西南出高手。

强化学习中的PPO和DPO的异同

  1. 相同点
  • 基于策略梯度:PPO(Proximal Policy Optimization)和DPO(Distributed Proximal Policy Optimization)都是基于策略梯度的强化学习算法,它们通过优化策略函数来直接学习一个策略,该策略能够映射观察到的状态到动作的概率分布。
  • 目标函数:两者都使用了类似的目标函数来优化策略,即通过最大化期望的回报来进行学习。
    剪切或限制策略更新:PPO和DPO都采用了一种机制来避免在策略更新时做出过大的改变,从而保持学习的稳定性。PPO通过剪切概率比率或使用KL散度限制,而DPO也可能采用类似的策略来限制分布式训练中的策略更新。
  1. 不同点
  • 分布式训练:DPO的“Distributed”指的是它被设计为在分布式计算环境中运行,可以在多个处理器或机器上并行执行,而PPO通常指单机版本的算法。
  • 扩展性和并行化:由于DPO是为分布式环境设计的,它在处理大规模并行化训练任务时具有更好的扩展性,而PPO则在这方面可能受到限制。
    通信和同步:在分布式设置中,DPO需要有效的通信和同步机制来保证多个训练节点之间的协调,这是PPO在单机设置中不需要考虑的问题。
  • 资源利用和效率:DPO通常能更有效地利用多核处理器或多台机器的计算资源,从而在实际应用中可能获得更快的训练速度和更好的性能。
  • 总结来说,PPO和DPO在算法框架和目标函数上有共同之处,但在实现方式、并行化程度以及适用的计算环境上存在差异,DPO特别适用于需要大规模并行处理的场景。

20240526~20240527

  • 周六针灸似乎有点效果,遂连扎了三天,耐心走个小疗程看看情况。感觉上,确实是不怎么疼了,但是,前两天也没怎么动,不疼似乎也是情理之中的事情。昨晚回来淋着大雨跑了一路,感觉high得不行,雨是热的,人也是热的,却依然掩盖不了内心的无力,这次已经持续了近两个月,黄道益、云南白药、消炎药都试过,我感觉应该是胫骨末端的炎症,以及跟腱炎,但始终无法根治。
  • 今天有一针扎在像是昆仑穴的地方(就是内踝跟脚跟之间凹陷的那个位置),一扎下去,整个右脚的右半边突然就整片麻木,有的地方扎下去一点感觉都没有,特别神奇。因为每次针扎的地方都不同,所以拍照记录了一下,想之后买点针灸针自己试试。老中医跟我讲针灸这东西可有门道,且不说扎不准穴位会有血光之灾,光一个穴位就有深浅不同的4层,针灸之法向来秘不外传,他做了30年中医,现在还在拜师一名复姓欧阳的大师(据说这个欧阳大师的太爷还是乾隆的御医),都是酒桌上喝得高兴,人家才会露两手给你看。
  • 今晚非常凉快,应该是黄梅前最后的甘甜。慢跑半小时(6.22km,@4’49"/km,其实我不该跑,但是这天气不跑太可惜了)。是近一个月以来跑得最久的一次(这段恢复期,最多一次也就5k,再多会有疼痛感,之前基本上3k左右右脚就感觉要开始疼的了),坦然地说,今晚跑完真的没有疼,穿的还是代步鞋,平底没弹性,是因为足底有一块死皮磨得太难受才停下来的,否则无痛地跑完10K应该不是问题。
  • 15个弹力带引体×4组,引体可以一次性拉上去15个,但是脱离弹力带还是一个都拉不上去,真实。
  • PS:都说中医已经不行了,老祖宗传下来的东西,总有起作用的时候。说实话,我本来已经对自己完全痊愈不抱希望了,其实很多马拉松运动员都会做抗疼痛的训练,毕竟腿脚都会有伤痛的。但这三天的针灸确实让我看到些曙光,我只想要最后一次完全的状态,再最后疯狂一回,就足够了。
    在这里插入图片描述

pygame使用:

初始化窗口

方法说明
pygame.display.set_mode()初始化游戏显示窗口
pygame.display.update()刷新屏幕内容显示,稍后使用
screen2 = pygame.display.set_mode((args.window_width, args.window_height), pygame.DOUBLEBUF, 32)

一般来说只要设定set_mode(size=(500, 500))即可,后面几个参数不是很明白


convert_alpha与convert

其中 convert_alpha相对于convert,保留了图像的Alpha 通道信息,可以认为是保留了透明的部分,实现了透明转换

Alpha 通道:
阿尔法通道是一个8位的灰度通道,该通道用256级灰度来记录图像中的透明度信息,定义透明、不透明和半透明区域,其中白表示不透明,黑表示透明,灰表示半透明

  • 对整个窗口进行alpha转换:

    self.screen2 = pygame.display.set_mode((w, h), pygame.DOUBLEBUF, 32)
    self.screen = self.screen2.convert_alpha()
    pygame.display.set_caption(title)
    

    这样在进行绘图时,color参数可以使用RGBA形式(带透明度)

  • 也有对图像进行转换的:

    self.image = pygame.image.load(image).convert_alpha()
    

20240528

  • wyl给我们透露了一下院里这次扩招教职的待遇,发现我们学院真的是待遇好,考核难度还低,据说这届毕业的博士有一个都有机会留院,属实有点离谱了。
  • 今天会前先去慢摇了10圈多,没有开表,但是全程记录心率,520-530的配速,全程心率没有超过148bpm,脚也没有疼(其实对我来说,5分多和3分多的配速对脚的压力区别不算太大,前者对我来说,也跑得挺难受)。
  • 下会已经快九点半,去操场补了50个弹力带引体,看到LXY竟然还在跑,看样子是又跑了10K,我感觉她这个月每天都至少跑了10K,月跑量得有300K向上,真是个无可救药的疯子。
  • 我也跑了四五圈,其中冲了个800米,2分55秒,似乎伤痛真的痊愈了。感觉上身体素质已经恢复到去年11月-12月中上旬(即万米勉强能叩开40分大门的水平),但是目前天气闷热,实际表现不会很好。计划明天要试一次10K,争取跑进40分钟。
  • 偶尔回顾一下《强风吹拂》,大结局10个区的10个人,12345区的故事都太玄幻,9区和10区是藏原走和灰二两个主角的故事,太过理想,反而是678三个区的故事最令人感动,6区阿雪和母亲的和解、7区烟鬼学长的救赎、8区普通人King的呐喊。最后一年,我也好想带队跑进一次总决赛。去年总决赛前12的队伍,16K平均用时都进了60分钟(均配345,8男2女),当然我们只要能进总决赛就是胜利,全队平均万米37分台,基本足够,但是目前也只有AK,嘉伟有这样的实力,宋某和我尽管也行,但都在伤痛恢复期。总之,男生先全员都能跑进40分钟,女生能有两个跑进45分钟,这个夏天,再最后好好练一回,不留遗憾。

看到py37的transformers源码里出现:=运算符(最低版本支持是py38),导致运行报错,版本是4.26.1,不知道是怎么回事,莫名其妙,现在装库的版本问题越来越多。

:=python3.8 的新特性,该符号又称为"海象运算符"。

官方例子:https://docs.python.org/3/whatsnew/3.8.html

好处就是,:=可将值分配给变量,又作为表达式的一部分,使赋值和判断,两步合成一步,让代码变得更简洁。

常常用于判断语句中,也非常适合运用在"while/do while"循环语句当中。

比方说,len(a) 表示输出列表a的长度(元素个数)。

你想要判断 len(a) > 5 的情况下,输出 len(a) 的值。

你用":="时,是如下所写的。 ​​​​​​​

a = [1,2,3,4,5,6,7,8,9,10]

if (n:=len(a)) > 5:
    print(n) # 结果应该是10

假如不用":=",则会这么写:​​​​​​​

a = [1,2,3,4,5,6,7,8,9,10]
n = len(a)

​​​​​​​if n > 5:
    print(n)

或者​​​​​​​

a = [1,2,3,4,5,6,7,8,9,10]

​​​​​​​if len(a) > 5:
    print(len(a))

看看,要么引入变量 n 来获取 len(a) 的值,要么就是 len(a) 运算两次。


20240529

  • 最后一天针灸,明天中医就要回杭州了,医者真的是再生父母,没有病的时候感觉不到,被伤痛折磨了这么久,终于找到一剂良药的感觉,真是人生第一回。
  • 白天走了有8km路,太阳晒得不行,晚上还放纵地吃了一把,喝了一整瓶快乐水。然后回学校想想还是得跑会儿,但是肚子太撑,根本跑不动。本想就慢跑10圈,也没换鞋,但是中途XR和YY两个好大儿非要跟,我也不好意思太慢,均配4’27"跑了5000米,体感很轻松,全程是后跟跑(平底鞋前掌跑太磨脚了),而且该疼的地方一点儿没疼。
  • 晚上称毛重是67kg不到一些,已经可以不依赖弹力带,一口气硬拉三个引体向上,这也是人生首次完成引体向上这个动作。总之,谨慎一些,再观察两天,如果确定没有问题,下个月正式开始规律性地恢复训练。虽然各项身体素质都不如三月份,但背部力量一定比三月强,生命总不会是一无是处的。
    在这里插入图片描述在这里插入图片描述

gesim.similarities.Similarity的一些问题

注意到:

import gensim
from gensim.corpora import MmCorpus, Dictionary
from gensim.similarities import Similarity

model = gensim.models.TfidfModel.load(GENSIM_RETRIEVAL_MODEL_SUBJECT_SUMMARY['宪法']['summary']['tfidf']['model'])
corpus = MmCorpus(GENSIM_RETRIEVAL_MODEL_SUBJECT_SUMMARY['宪法']['summary']['tfidf']['corpus'])
dictionary = Dictionary.load(GENSIM_RETRIEVAL_MODEL_SUBJECT_SUMMARY['宪法']['summary']['tfidf']['dictionary'])

similarity = Similarity('gensim_temp', corpus, num_features=len(dictionary), num_best=19)

query_tokens = ['下列', '有关', '国体', '和', '政体', '说法', '正确', '的', '是', '?']
query_tokens = ['公民', '行使', '集会', '、', '游行', '、', '示威', '权利', '时应', '向', '主管机关', '提出申请', '并', '获得', '许可', '。', '下列', '选项', '中', '哪些', '属于', '依法', '不予', '许可', '的', '情形', '?']

query_corpus = dictionary.doc2bow(query_tokens)
query_corpus = model[query_corpus]
result = similarity[query_corpus]

注意虽然设定num_best=19,但是实际上result可能不足19个,只有9个,形如:

[(116, 0.09448226541280746),
 (8, 0.09299059957265854),
 (180, 0.07245595753192902),
 (137, 0.06240452080965042),
 (876, 0.06101685389876366),
 (604, 0.052845943719148636),
 (697, 0.05002279579639435),
 (40, 0.049057234078645706),
 (581, 0.04327135533094406)]

而如果设定num_best=None,结果将为991维向量,其中只有9个元素是非零元,因此如果设定num_best>9,也只能得到9个,


20240530~20240531

  • 黄梅的序幕,持续近一天的雨,从昨天中午一直下到今天上午,却没有带来清凉,晚上依然沉闷。
  • 接下来几天都是这种阴天,说实话不如室内空调吹的舒服,但晚上还是去遛了会儿,5000米@4’09",节奏很好,但极其吃力,心率爆炸。因为气压低,大口喘气却吸不进多少氧气,跑得很难受。全程是前掌跑,穿的代步鞋,跑完内踝上方旧伤稍有不适,但耐受比之前好很多,总之还是不能太托大了。补50个正向箭步×6组(+15kg),50个弹力带引体,感觉状态比较一般,明天或许还是想试试10K。
  • 感觉院里要有大动作了,因为下一届管科学硕已经没有考研点了,像是要分家,像是新旧派系的争斗。
  • 反正跟我也没啥关系,我只关系明天开奖能不能中,praying…
    在这里插入图片描述在这里插入图片描述
    介绍 jieba + whoosh 实现搜索之前,你可以先看下文 whoosh 的简单介绍。

简单的搜索引擎的例子:

import os
import shutil

from whoosh.fields import *
from whoosh.index import create_in
from whoosh.qparser import QueryParser
from jieba.analyse import ChineseAnalyzer


analyzer = ChineseAnalyzer()

schema = Schema(title=TEXT(stored=True),
                path=ID(stored=True),
                content=TEXT(stored=True,
                             analyzer=analyzer))
if not os.path.exists("test"):
    os.mkdir("test")
else:
    # 递归删除目录
    shutil.rmtree("test")
    os.mkdir("test")

idx = create_in("test", schema)
writer = idx.writer()

writer.add_document(
    title=u"document1",
    path="/tmp1",
    content=u"Tracy McGrady is a famous basketball player, the elegant basketball style of him attract me")
writer.add_document(
    title=u"document2",
    path="/tmp2",
    content=u"Kobe Bryant is a famous basketball player too , the tenacious spirit of him also attract me")
writer.add_document(
    title=u"document3",
    path="/tmp3",
    content=u"LeBron James is the player i do not like")

writer.commit()
searcher = idx.searcher()
parser = QueryParser("content", schema=idx.schema)

for keyword in ("basketball", "elegant"):
    print("searched keyword ",keyword)
    query= parser.parse(keyword)
    results = searcher.search(query)
    for hit in results:
        print(hit.highlights("content"))
    print("="*50)

上面代码中,使用 add_document() 把一个文档添加到了 index 中。在这些文档中,搜索含有 “basketball”和 “elegant” 的文档。

打印结果如下:

Building prefix dict from the default dictionary ...
Loading model from cache C:\Users\wyzane\AppData\Local\Temp\jieba.cache
Loading model cost 0.754 seconds.
Prefix dict has been built successfully.
searched keyword  basketball
McGrady is a famous <b class="match term0">basketball</b> player, the elegant...<b class="match term0">basketball</b> style of him attract me
Bryant is a famous <b class="match term0">basketball</b> player too , the tenacious
==================================================
searched keyword  elegant
basketball player, the <b class="match term0">elegant</b> basketball style
==================================================

更换搜索词时:

for keyword in ("LeBron", "Kobe"):
    print("searched keyword ",keyword)
    query= parser.parse(keyword)
    results = searcher.search(query)
    for hit in results:
        print(hit.highlights("content"))
    print("="*50)

搜索结果如下:

Building prefix dict from the default dictionary ...
Loading model from cache C:\Users\wyzane\AppData\Local\Temp\jieba.cache
Loading model cost 0.801 seconds.
Prefix dict has been built successfully.
searched keyword  LeBron
<b class="match term0">LeBron</b> James is the player i do not like
==================================================
searched keyword  Kobe
<b class="match term0">Kobe</b> Bryant is a famous basketball player too , the tenacious
==================================================

上面是搜索英文,下面展示下搜索中文。

添加下面的文档数据:

writer.add_document(
    title=u"document1",
    path="/tmp1",
    content=u"麦迪是一位著名的篮球运动员,他飘逸的打法深深吸引着我")
writer.add_document(
    title=u"document2",
    path="/tmp2",
    content=u"科比是一位著名的篮球运动员,他坚韧的精神深深的感染着我")
writer.add_document(
    title=u"document3",
    path="/tmp3",
    content=u"詹姆斯是我不喜欢的运动员")

执行搜索:

for keyword in ("篮球", "麦迪"):
    print("searched keyword ",keyword)
    query= parser.parse(keyword)
    results = searcher.search(query)
    for hit in results:
        print(hit.highlights("content"))
    print("="*50)

结果如下:

Building prefix dict from the default dictionary ...
Loading model from cache C:\Users\wyzane\AppData\Local\Temp\jieba.cache
Loading model cost 0.780 seconds.
Prefix dict has been built successfully.
searched keyword  篮球
麦迪是一位著名的<b class="match term0">篮球</b>运动员,他飘逸的打法深深吸引着我
科比是一位著名的<b class="match term0">篮球</b>运动员,他坚韧的精神深深的感染着我
==================================================
searched keyword  麦迪
<b class="match term0">麦迪</b>是一位著名的篮球运动员,他飘逸的打法深深吸引着我
==================================================

20240601~20240602

  • 又被鸽了两天,投稿群里诸位已经骂娘声一片,这两年ccf,除了几个主要的顶会,明显比之前两年搞得差太多,无论买方抑或卖方,买方质量差,卖方也就懒得认真搞,越来越形式主义,突出一个难绷。
  • 今年天气有点反常,已经是黄梅季,但雨水不多,天气也相对比较凉快。按计划从六月开始恢复训练,不过就近两日的情况来看,还是有些勉强,并没有完全康复。有一种说法,针灸只是短期内缓解,长期并不是那么有效,总之目前依然不支持长时间的前掌跑,内踝上方依然会疼,一般会在半小时以上开始有感觉,可以忍耐,没有之前那么剧烈。
  • 昨天有AK带着用4分出头的配速跑,7K后AK跑去拍火烧云的晚霞,我仅仅顶了1K就坚持不下去了,最终是4’03"的8000米,整体渐加速,后程可以顶进4分配,至少也要比去年同期要好,依然是持平冬训前的水平。
  • 今天10K@4’07",我不太敢用前掌跑,结果就是很别扭——左脚是前掌,右脚是全掌偏后的跑法,但是这样也行得通,至少不太疼。因为没有人带,就慢慢从五分配往上加,本来其实想的就是慢点跑个长距离,但还是压不住节奏。后5K用时19’56",最后2K提速,用时3’54"和3’46"。以前,经常是前两圈猛冲,然后回调节奏,现在只敢慢慢加,避免受伤,短期内不可能PB,不如在舒适区活动,至少这个心率170bpm并不算高,体感也是有余力的。
  • 下周末可能会进行高百资格赛,其实跑不跑也无所谓,校友都能路子走后门,但是摸个底其实也不错,看看大家现在水平到底怎么样。目前,XR的水平提升很快,昨天他用差不多340的配速跑了4K,休息10分钟后,又跟着我跑了5K才下场(这段他明显是力竭了,后面想继续跟,不到2圈就又下了),仅仅开始不到半年就达到这种水平,天赋很强,但他跑得确实也很多,估计跟LXY的跑量不相上下。嘉伟最近似乎没怎么练,宋某也是拉伤恢复,水平不比五月初的巅峰。
  • 最离谱的还是LXY,昨天15K,今天又是10K出头,最近一个月可能一周最就跑休一天,其余天天10K以上,疯狂得可怕,不过也挺让人羡慕,我要是还能有这种身体素质就好了,永远都是失去才知道要珍惜,也许真的无法回到巅峰了。
    在这里插入图片描述在这里插入图片描述在这里插入图片描述在这里插入图片描述

musique数据集,标注了每个问题的子问题分解及对应答案:

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

import os
import json

from src.datasets.base import BaseDataset

class MusiqueDataset(BaseDataset):

    def __init__(self,
                 data_path,
                 ):
        super(MusiqueDataset, self).__init__()
        self.data_path = data_path

    # @param batch_size: Int
    # @param type_: Str, e.g. "run_race", "dev", "test"
    # @yield batch: List of `{"full": [json_full_1, json_full_2], "ans": json_ans}`, where `json_full_1, json_full_2, json_ans` have the same key-value pairs
    # JSON structure in @yield batch is as below:
    # - id: Str, e.g. "2hop__55254_176500"
    # - paragraphs: List[Dict{idx: Int, title: Str, paragraph_text: Str, is_supporting: Boolean}]
    # - question: Str
    # - question_decomposition: List[Dict{id: Int, question: Str, answer: Str, paragraph_support_idx: Int[None]}]
    # - answer: Str, ground truth
    # - answer_aliases: List[Str], other possible answers
    # - answerable: Boolean
    def yield_batch(self,
                    batch_size,
                    type_,
                    ):
        """Tool function for loading .jsonl file"""
        def _easy_load_jsonl(_file_path):
            _jsonl = list()
            with open(_file_path, 'r', encoding="utf8") as f:
                while True:
                    _jsonl_string = f.readline()
                    if not _jsonl_string:
                        break
                    _jsonl.append(json.loads(_jsonl_string))
            return _jsonl
        file_path_full = os.path.join(self.data_path, f"musique_full_v1.0_{type_}.jsonl")
        file_path_ans = os.path.join(self.data_path, f"musique_ans_v1.0_{type_}.jsonl")
        jsonl_full = _easy_load_jsonl(file_path_full)
        jsonl_ans = _easy_load_jsonl(file_path_ans)
        sorted_jsonl_full = sorted(jsonl_full, key = lambda _json: _json["id"])
        sorted_jsonl_ans = sorted(jsonl_ans, key = lambda _json: _json["id"])
        del jsonl_full, jsonl_ans

        batch, current_batch_size = list(), 0
        for i in range(len(sorted_jsonl_ans)):
            # Sampling and sanity check
            json_full_1 = sorted_jsonl_full[2 * i]
            json_full_2 = sorted_jsonl_full[2 * i + 1]
            json_ans = sorted_jsonl_ans[i]
            id_full_1 = json_full_1["id"]
            id_full_2 = json_full_2["id"]
            id_ans = json_ans["id"]
            assert id_full_1 == id_full_2 == id_ans, f"Mismatch: {id_full_1} v.s. {id_full_2} v.s. {id_ans}"
            batch.append({"full": [json_full_1, json_full_2], "ans": json_ans})
            current_batch_size += 1
            if current_batch_size == batch_size:
                yield batch
                batch, current_batch_size = list(), 0
        if current_batch_size > 0:
            yield batch

类似地几个封装抽取式的QA:

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

import os
import json

from src.datasets.base import BaseDataset


class SquadDataset(BaseDataset):

    def __init__(self,
                 data_path,
                 ):
        super(SquadDataset, self).__init__()
        self.data_path = data_path

    # @param batch_size: Int
    # @param filename: Str, e.g. "run_race-v1.1.json", "run_race-v1.1.json", "run_race-v1.1.json"
    # @yield batch: List[Dict]
    # - article_id: "run_race-1.1-00000"
    # - question_id: "5733be284776f41900661182"
    # - title: "University_of_Notre_Dame"
    # - article: "Architecturally, the school has a Catholic character. Atop ..."
    # - question: "To whom did the Virgin Mary allegedly appear in 1858 in Lourdes France?"
    # - answers: ["Saint Bernadette Soubirous"]
    # - answer_starts: [515]
    # - answer_ends: [541]
    def yield_batch(self,
                    batch_size,
                    filename,
                    ):
        batch, current_batch_size, = list(), 0
        with open(os.path.join(self.data_path, filename), 'r', encoding="utf8") as f:
            data = json.load(f)
        count = -1
        for sample in data["data"]:
            title = sample["title"]
            paragraphs = sample["paragraphs"]
            for paragraph in paragraphs:
                count += 1
                article_id = f"{filename[: -5]}-{str(count).zfill(5)}"
                article = paragraph["context"]
                for qas in paragraph["qas"]:
                    question_id = qas["id"]
                    question = qas["question"]
                    candidate_answers = qas["answers"]
                    answer_starts, answer_ends, answers = list(), list(), list()
                    for candidate_answer in candidate_answers:
                        answer_start = int(candidate_answer["answer_start"])
                        answer = candidate_answer["text"]
                        answer_end = answer_start + len(answer)
                        assert answer == article[answer_start: answer_end]
                        answer_starts.append(answer_start)
                        answer_ends.append(answer_end)
                        answers.append(answer)
                    batch.append({"article_id": article_id,
                                  "question_id": question_id,
                                  "title": title,
                                  "article": article,
                                  "question": question,
                                  "answers": answers,
                                  "answer_starts": answer_starts,
                                  "answer_ends": answer_ends,
                                  })
                    current_batch_size += 1
                    if current_batch_size == batch_size:
                        yield batch
                        batch, current_batch_size, = list(), 0
        if current_batch_size > 0:
            yield batch
# -*- coding: utf-8 -*- 
# @author : caoyang
# @email: caoyang@stu.sufe.edu.cn

import os
import json

from src.datasets.base import BaseDataset


class HotpotqaDataset(BaseDataset):
    pipeline_type = "mutiple-choice"
    def __init__(self,
                 data_path,
                 ):
        super(HotpotqaDataset, self).__init__()
        self.data_path = data_path

    # @param batch_size: Int
    # @param filename: Str, e.g. "train_v1.1.json", "dev_distractor_v1.json", "dev_fullwiki_v1.json", "test_fullwiki_v1.json"
    # @yield batch: List[Dict]
    # - id: "5adf9ba1554299025d62a2db"
    # - question: "What position on the Billboard Top 100 did Alison Moyet's late summer hit achieve?"
    # - context: List[Tuple[Str, List[Str]]]: In HotpotQA, The first Str in Tuple is title, and the List is tokenized sentences
    #   e.g. [["The Other Side of Love", ["\"The Other Side of Love\" is a song ...", ]], [..., [...]], ...]
    # - answer: "Yes" (may be None in test file)
    # - type: "comparison"
    # - level: "hard"
    def yield_batch(self,
                    batch_size,
                    filename,
                    ):
        batch, current_batch_size, = list(), 0
        with open(os.path.join(self.data_path, filename), 'r', encoding="utf8") as f:
            data = json.load(f)
        for sample in data:
            id_ = sample["_id"]
            question = sample["question"]
            context = sample["context"]
            answer = sample.get("answer")  # Maynot in test file
            type_ = sample.get("type")  # Maynot in test file
            level = sample.get("level")  # Maynot in test file
            batch.append({"id": id_,
                          "question": question,
                          "context": context,
                          "answer": answer,
                          "type": type_,
                          "level": level,
                          })
            current_batch_size += 1
            if current_batch_size == batch_size:
                yield batch
                batch, current_batch_size, = list(), 0
        if current_batch_size > 0:
            yield batch

抽取式和生成式,目前看下来还是后者要更通用一些,而且发现在F1的metric下,LLM反而后者做得更好,也可能是没有给抽取式找到比较好的prompt,LLM在这种字符级别的数数上做得还是不行。


20240603

  • 一大早王总跟我说想转码走数分商分,说觉得金融不行了,我说怎么就不行了。他本来指望中金能留,想翘了光华的保研,现在又觉得很难进,于是投了个京东的TET,结果二面又被高管后台挂了(估计是卡学历),我说你想转码,还不如去买方或者投行金工组,这么好的金融背景不要,非要来互联网卷,卷尼玛卷。我觉得王总对自己太苛刻了,我要是四年前有这条件,本科毕业就去工作,不读这书受鸟气。(晚上十点半,终于收到接收的邮件了,时隔两年,上天还是眷顾了我一回)
  • 原计划是跑休一天,给身体恢复的时间。但是晚饭后还是去操场逛了会儿,看到LXY还在跑,想想别人能坚持每天10K+(今天是11K,均配5’30",是比之前慢了不少,但这跑量还是让人害怕),我每天跑个10K也不过分吧,遂起步5’30",均配4’23",渐加速跑了10K,最后200米冲刺到3分配,平均心率168bpm,感觉不算太差,不过天气确实是热了。
  • 中间遇到一个粉衣小哥,起手4’10"的配速,我以为遇到了不认识的高手,跟着他跑了会儿,结果不到两圈他就掉到4’30",5圈就停了,些许扫兴。不过,今天穿的代步鞋,全程前掌跑,一点儿没疼,很欣慰。这次,或许真的要走出伤痛的阴影了,吗?不想再被打击了,好想能立刻恢复到三月的水平,跟嘉伟和AK痛快地赤膊大战一场。
    在这里插入图片描述在这里插入图片描述

近期,Nishant Aklecha 发布了一个从零开始实现 llama3 的存储库,包括跨多个头的注意力矩阵乘法、位置编码和每个层在内都有非常详细的解释。

项目地址:

https://github.com/naklecha/llama3-from-scratch

首先从 Meta 提供的 llama3 模型文件中加载张量。

下载地址:

https://llama.meta.com/llama-downloads/

接着是分词器(tokenizer),作者表示没打算自己实现分词器,因而借用了 Andrej Karpathy 的实现方式:

分词器的实现链接:
https://github.com/karpathy/minbpe


from pathlib import Path
import tiktoken
from tiktoken.load import load_tiktoken_bpe
import torch
import json
import matplotlib.pyplot as plt
tokenizer_path = "Meta-Llama-3-8B/tokenizer.model"
special_tokens = [
            "<|begin_of_text|>",
            "<|end_of_text|>",
            "<|reserved_special_token_0|>",
            "<|reserved_special_token_1|>",
            "<|reserved_special_token_2|>",
            "<|reserved_special_token_3|>",
            "<|start_header_id|>",
            "<|end_header_id|>",
            "<|reserved_special_token_4|>",
            "<|eot_id|>",  # end of turn
        ] + [f"<|reserved_special_token_{i}|>" for i in range (5, 256 - 5)] mergeable_ranks = load_tiktoken_bpe (tokenizer_path) tokenizer = tiktoken.Encoding (
    name=Path (tokenizer_path).name,
    pat_str=r"(?i:'s|'t|'re|'ve|'m|'ll|'d)|[^\r\n\p {L}\p {N}]?\p {L}+|\p {N}{1,3}| ?[^\s\p {L}\p {N}]+[\r\n]*|\s*[\r\n]+|\s+(?!\S)|\s+",
    mergeable_ranks=mergeable_ranks,
    special_tokens={token: len (mergeable_ranks) + i for i, token in enumerate (special_tokens)},
)
tokenizer.decode (tokenizer.encode ("hello world!"))

20240604

  • 极大地重振精神状态,七八月的计划可能要变更了,想去再水一份实习。
  • 晚饭后慢摇5K,均配4’59",平均心率148bpm,后程心率涨得有点厉害。路上遇到LXY,很难得地互相打了个招呼。其实经常遇见,总不愿意再去直面隔阂。
  • 开完会九点多,补5K,均配4’12",平均心率163bpm,尚可,便装跑步,没有追求速度的需求,重在有氧和节奏。感觉上,已经恢复到今年1月的水平,有巅峰期的八九成实力,明后等嘉伟带我上点强度,看看自己能顶到多少。
  • 跑完看到吴征还在操场(他们这年纪的都睡得很早),上去寒暄了几句,吴征有些苦恼高百资格赛一个女生都没有,因为今年要求必须全日制学生,那些MBA的佬们原则上都不让参加了,人手就捉襟见肘。我问吴征为什么没拉LXY,她很强的,不比几个女性校友差。吴征说是她周末不在学校,怪不得太阳打西边出来,今天居然没来跑。不过周末本也是端午,也可能是参会,有事也正常吧。但是,我从未见过LXY全力跑的样子,尽管她最近都跑得很慢,但甘心养老的人,真的会这么拼命地每天跑这么多的吗?
    在这里插入图片描述在这里插入图片描述

上述步骤完成后,就是读取模型文件了。源码是从头开始实现 llama3,因此代码一次只读取一个张量文件。

model = torch.load ("Meta-Llama-3-8B/consolidated.00.pth")
print(json.dumps (list (model.keys ())[:20], indent=4))

输出结果:


[
    "tok_embeddings.weight",
    "layers.0.attention.wq.weight",
    "layers.0.attention.wk.weight",
    "layers.0.attention.wv.weight",
    "layers.0.attention.wo.weight",
    "layers.0.feed_forward.w1.weight",
    "layers.0.feed_forward.w3.weight",
    "layers.0.feed_forward.w2.weight",
    "layers.0.attention_norm.weight",
    "layers.0.ffn_norm.weight",
    "layers.1.attention.wq.weight",
    "layers.1.attention.wk.weight",
    "layers.1.attention.wv.weight",
    "layers.1.attention.wo.weight",
    "layers.1.feed_forward.w1.weight",
    "layers.1.feed_forward.w3.weight",
    "layers.1.feed_forward.w2.weight",
    "layers.1.attention_norm.weight",
    "layers.1.ffn_norm.weight",
    "layers.2.attention.wq.weight"
]
with open ("Meta-Llama-3-8B/params.json", "r") as f:
    config = json.load (f)
config

"""

{'dim': 4096,
 'n_layers': 32,
 'n_heads': 32,
 'n_kv_heads': 8,
 'vocab_size': 128256,
 'multiple_of': 1024,
 'ffn_dim_multiplier': 1.3,
 'norm_eps': 1e-05,
 'rope_theta': 500000.0}
"""

项目作者使用以下配置来推断模型细节:

  • 模型有 32 个 transformer 层;
  • 每个多头注意力块有 32 个头。

dim = config ["dim"]
n_layers = config ["n_layers"]
n_heads = config ["n_heads"]
n_kv_heads = config ["n_kv_heads"]
vocab_size = config ["vocab_size"]
multiple_of = config ["multiple_of"]
ffn_dim_multiplier = config ["ffn_dim_multiplier"]
norm_eps = config ["norm_eps"]
rope_theta = torch.tensor (config ["rope_theta"])

接下来的操作是将文本装换为 token,这里作者使用的是 tiktoken 库(一个用于 OpenAI 模型的 BPE tokeniser)。

在这里插入图片描述

prompt = "the answer to the ultimate question of life, the universe, and everything is"
tokens = [128000] + tokenizer.encode (prompt)
print (tokens)
tokens = torch.tensor (tokens)
prompt_split_as_tokens = [tokenizer.decode ([token.item ()]) for token in tokens]
print (prompt_split_as_tokens)
"""
[128000, 1820, 4320, 311, 279, 17139, 3488, 315, 2324, 11, 279, 15861, 11, 323, 4395, 374, 220]
['<|begin_of_text|>', 'the', ' answer', ' to', ' the', ' ultimate', ' question', ' of', ' life', ',', ' the', ' universe', ',', ' and', ' everything', ' is', ' ']
"""

20240605

  • 精神舒畅,起了个大早,身体又进入了正反馈调节。
  • AK这周六搬家到浦东,嘉伟这周六跑完资格赛也准备封笔,大约是要准备考研和实习事宜,有些失落,总觉得是茶歇人散了要,大家总是要忙正事,就很羡慕其他学校的氛围,我们这种体量小的学校就很难组织起中坚力量。
  • 中午开始飘小雨,于是下午提前训练,防止晚上操场关门。嘉伟有事来不了,我只得一个人拼强度,计划是跑一个40分以内的万米,但是低气压,天气湿闷,起手第一个1000米用时3’48",很快就感觉迈不开腿,没有能够完成预定目标,到5000米力竭,用时19’15",均配3’50",补5000米慢跑,均配4’44",这段平均心率只有150bpm,还是边打电话边跑的,是相当满意的心率水平。
  • 嘉伟问我周六资格赛3000米准备跑多少,我说能跑进11分钟就很满意了。不过就今天的情况来看,5000米全力都跑不进19分,大约是无法如愿。这几天,有氧水平确实恢复得很好(从6月1日开始,每天都维持了10km的量,很快就把心肺能力提上来了,最近一周的静息心率平均只有34bpm,但明显发现很容易就很饿),但是速度能力比之前差太多,也有天气的原因,总之身体很难维持340以内的配速,跑个半圈就不想再顶下去。
  • PS:最近那个台州石人峡的遇难事故,人还是要敬畏自然。想到大约两周前,SXY独自去苏州爬山,以前说爬山还是要结伴而行的,虽然苏州的山山水水对她来说可能算是如数家珍,但人走过的路,较于此片天地,还是太渺茫。
    在这里插入图片描述在这里插入图片描述在这里插入图片描述

将 token 转换为嵌入。

embedding_layer = torch.nn.Embedding (vocab_size, dim)
embedding_layer.weight.data.copy_(model ["tok_embeddings.weight"])
token_embeddings_unnormalized = embedding_layer (tokens).to (torch.bfloat16)
token_embeddings_unnormalized.shape
# torch.Size ([17, 4096])

将嵌入进行归一化。该研究使用均方根 RMS 算法进行归一化。不过,在这一步之后,张量形状不会改变,只是值进行了归一化。


# def rms_norm (tensor, norm_weights):
#     rms = (tensor.pow (2).mean (-1, keepdim=True) + norm_eps)**0.5
#     return tensor * (norm_weights /rms)
def rms_norm (tensor, norm_weights):
    return (tensor * torch.rsqrt (tensor.pow (2).mean (-1, keepdim=True) + norm_eps)) * norm_weights

构建 transformer 第一层。完成上述准备后,接着是构建 transformer 第一层:从模型文件中访问 layer.0(即第一层),归一化后嵌入维度仍然是 [17x4096]

token_embeddings = rms_norm (token_embeddings_unnormalized, model ["layers.0.attention_norm.weight"])
token_embeddings.shape
# torch.Size ([17, 4096])

从头开始实现注意力。加载第一层 transformer 的注意力头:

print (
    model ["layers.0.attention.wq.weight"].shape,
    model ["layers.0.attention.wk.weight"].shape,
    model ["layers.0.attention.wv.weight"].shape,
    model ["layers.0.attention.wo.weight"].shape
)
# torch.Size ([4096, 4096]) torch.Size ([1024, 4096]) torch.Size ([1024, 4096]) torch.Size ([4096, 4096])

展开查询。展开来自多个注意力头的查询,得到的形状是 [32x128x4096],这里,32 是 llama3 中注意力头的数量,128 是查询向量的大小,4096 是 token 嵌入的大小。

q_layer0 = model ["layers.0.attention.wq.weight"]
head_dim = q_layer0.shape [0] //n_heads
q_layer0 = q_layer0.view (n_heads, head_dim, dim)
q_layer0.shape
# torch.Size ([32, 128, 4096])

从头实现第一层的第一个头。访问第一层的查询权重矩阵,大小是 [128x4096]。

q_layer0_head0 = q_layer0 [0]
q_layer0_head0.shape
# torch.Size ([128, 4096])

将查询权重与 token 嵌入相乘,从而得到 token 的查询,在这里你可以看到结果大小是 [17x128]。


20240606

  • 状态突然莫名变差,中午补了一觉,一个多小时,睡得昏天黑地,起来依然四肢无力,走路都觉得累,不至于又发烧,因为胃口很好。感觉有天气的原因,也可能是身体不适应激增的负荷,总之就是使不上力,引体硬拉一两个也崩了。
  • 就昨天的情况来看,速度能力极度欠缺,即便AK和嘉伟今晚不跑间歇,我也想自己跑一下的。但是晚上还是去跟他们试试,因为感觉状态不行,就没换鞋子,反正也不准备跟他俩硬刚。结果AK决定在间歇训练前,先跑一个3’45"的5000米(实际情况是他们只用了18分整就跑完了5000米,均配3’37",就是把三月份的我拉过来也是死路一条),我觉得可以试试看吧,能跟下来是最好,结果只跟了1K就顶不下去了,极其拉胯,XR都跟了有五六圈,平心而论,我现在的水平可能还真不如XR,天赋怪是真的可怕
  • 后面补了一段慢跑到10K,心率维持得很好,但是3000米还是更需要无氧,明日休整,后天资格赛,尽力而为。怎么也是有过两把刷子的人,现在连新人都跑不过,属实不甘心。
    在这里插入图片描述

定位编码。现在处于这样一个阶段,即对提示符中的每个 token 都有一个查询向量,但是考虑单个查询向量,我们不知道其提示符中的位置。作者使用了 RoPE(旋转位置嵌入)来解决。

在这里插入图片描述

q_per_token_split_into_pairs = q_per_token.float ().view (q_per_token.shape [0], -1, 2)
q_per_token_split_into_pairs.shape
# torch.Size ([17, 64, 2])

在上面的步骤中,该研究将查询向量分成对,并对每对应用旋转角度移位。

使用复数点积来旋转向量。

zero_to_one_split_into_64_parts = torch.tensor (range (64))/64
zero_to_one_split_into_64_parts
tensor ([0.0000, 0.0156, 0.0312, 0.0469, 0.0625, 0.0781, 0.0938, 0.1094, 0.1250,
        0.1406, 0.1562, 0.1719, 0.1875, 0.2031, 0.2188, 0.2344, 0.2500, 0.2656,
        0.2812, 0.2969, 0.3125, 0.3281, 0.3438, 0.3594, 0.3750, 0.3906, 0.4062,
        0.4219, 0.4375, 0.4531, 0.4688, 0.4844, 0.5000, 0.5156, 0.5312, 0.5469,
        0.5625, 0.5781, 0.5938, 0.6094, 0.6250, 0.6406, 0.6562, 0.6719, 0.6875,
        0.7031, 0.7188, 0.7344, 0.7500, 0.7656, 0.7812, 0.7969, 0.8125, 0.8281,
        0.8438, 0.8594, 0.8750, 0.8906, 0.9062, 0.9219, 0.9375, 0.9531, 0.9688,
        0.9844])
freqs = 1.0 / (rope_theta ** zero_to_one_split_into_64_parts)
freqs
tensor ([1.0000e+00, 8.1462e-01, 6.6360e-01, 5.4058e-01, 4.4037e-01, 3.5873e-01,
        2.9223e-01, 2.3805e-01, 1.9392e-01, 1.5797e-01, 1.2869e-01, 1.0483e-01,
        8.5397e-02, 6.9566e-02, 5.6670e-02, 4.6164e-02, 3.7606e-02, 3.0635e-02,
        2.4955e-02, 2.0329e-02, 1.6560e-02, 1.3490e-02, 1.0990e-02, 8.9523e-03,
        7.2927e-03, 5.9407e-03, 4.8394e-03, 3.9423e-03, 3.2114e-03, 2.6161e-03,
        2.1311e-03, 1.7360e-03, 1.4142e-03, 1.1520e-03, 9.3847e-04, 7.6450e-04,
        6.2277e-04, 5.0732e-04, 4.1327e-04, 3.3666e-04, 2.7425e-04, 2.2341e-04,
        1.8199e-04, 1.4825e-04, 1.2077e-04, 9.8381e-05, 8.0143e-05, 6.5286e-05,
        5.3183e-05, 4.3324e-05, 3.5292e-05, 2.8750e-05, 2.3420e-05, 1.9078e-05,
        1.5542e-05, 1.2660e-05, 1.0313e-05, 8.4015e-06, 6.8440e-06, 5.5752e-06,
        4.5417e-06, 3.6997e-06, 3.0139e-06, 2.4551e-06])
freqs_for_each_token = torch.outer (torch.arange (17), freqs)
freqs_cis = torch.polar (torch.ones_like (freqs_for_each_token), freqs_for_each_token)
freqs_cis.shape
# viewing tjhe third row of freqs_cis
value = freqs_cis [3]
plt.figure ()
for i, element in enumerate (value [:17]):
    plt.plot ([0, element.real], [0, element.imag], color='blue', linewidth=1, label=f"Index: {i}")
    plt.annotate (f"{i}", xy=(element.real, element.imag), color='red')
    plt.xlabel ('Real')
    plt.ylabel ('Imaginary')
    plt.title ('Plot of one row of freqs_cis')
    plt.show ()

在这里插入图片描述
现在每个 token 查询都有了复数。

q_per_token_as_complex_numbers = torch.view_as_complex (q_per_token_split_into_pairs)
q_per_token_as_complex_numbers.shape
# torch.Size ([17, 64])
q_per_token_as_complex_numbers_rotated = q_per_token_as_complex_numbers * freqs_cis
q_per_token_as_complex_numbers_rotated.shape
# torch.Size ([17, 64])

旋转后的向量。

q_per_token_split_into_pairs_rotated = torch.view_as_real (q_per_token_as_complex_numbers_rotated)
q_per_token_split_into_pairs_rotated.shape
# torch.Size ([17, 64, 2])

20240607

  • 晚饭后去操场遛了会儿,遇胡哥,虽然计划跑休,但还是陪他小跑了3K出头。天气闷得太难受,心率不高,体感却极其吃力,令人头大。
  • 胡哥明天要把高考数学卷做一遍——两个小时把全国卷和上海卷都做完。他觉得现在学得东西太难,以后做老师根本用不上。半个月前,他回扬州去给高中同学做伴郎,回来后也有快两周没跑了。他同学谈了八年,终成正果,是为了等学医的对象毕业,确也是历久弥坚。
  • 总之,很不遂意,恼人的天气,摸不清的状态。目前,场地3000米,XR和YY都有破11分的能力(XR自不必提,昨晚YY也完成3000米自测,10’52",年轻人在中距离项目很强势)。好消息是,在我不行的时候,终于有人能顶上了,但是最后一年,尤其还是自以为最巅峰的一年,最后几场关键比赛,能发挥得出色些,无论如何。
  • 罢了,自有天意。
    在这里插入图片描述

现在有了一个新的查询向量 (旋转查询向量),形状为 [17x128],其中 17 是 token 数量,128 是查询向量的维度。

q_per_token_rotated = q_per_token_split_into_pairs_rotated.view (q_per_token.shape)
q_per_token_rotated.shape
# torch.Size ([17, 128])

键(几乎和查询一样),键也生成维度为 128 的键向量。键的权重只有查询的 1/4,这是因为键的权重在 4 个头之间共享,以减少所需的计算量,键也会被旋转以添加位置信息,就像查询一样。


k_layer0 = model ["layers.0.attention.wk.weight"]
k_layer0 = k_layer0.view (n_kv_heads, k_layer0.shape [0] //n_kv_heads, dim)
k_layer0.shape
torch.Size ([8, 128, 4096])
k_layer0_head0 = k_layer0 [0]
k_layer0_head0.shape
torch.Size ([128, 4096])
k_per_token = torch.matmul (token_embeddings, k_layer0_head0.T)
k_per_token.shape
torch.Size ([17, 128])
k_per_token_split_into_pairs = k_per_token.float ().view (k_per_token.shape [0], -1, 2)
k_per_token_split_into_pairs.shape
torch.Size ([17, 64, 2])
k_per_token_as_complex_numbers = torch.view_as_complex (k_per_token_split_into_pairs)
k_per_token_as_complex_numbers.shape
torch.Size ([17, 64])
k_per_token_split_into_pairs_rotated = torch.view_as_real (k_per_token_as_complex_numbers * freqs_cis)
k_per_token_split_into_pairs_rotated.shape
torch.Size ([17, 64, 2])
k_per_token_rotated = k_per_token_split_into_pairs_rotated.view (k_per_token.shape)
k_per_token_rotated.shape
torch.Size ([17, 128])

每个 token 查询和键的旋转值如下,每个查询和键现在的形状都是 [17x128]。

接下来一步是将查询和键矩阵相乘。注意力得分矩阵 (qk_per_token) 的形状为 [17x17],其中 17 是提示中 token 的数量。

qk_per_token = torch.matmul (q_per_token_rotated, k_per_token_rotated.T)/(head_dim)**0.5
qk_per_token.shape
torch.Size ([17, 17])

现在必须掩蔽查询键分数。

在 llama3 的训练过程中,未来 token 的 qk 分数被掩蔽。这是因为在训练期间,只学习使用过去的 token 来预测未来的 token。因此在推理过程中,将未来的 token 标记为零。


def display_qk_heatmap (qk_per_token):
    _, ax = plt.subplots ()
    im = ax.imshow (qk_per_token.to (float).detach (), cmap='viridis')
    ax.set_xticks (range (len (prompt_split_as_tokens)))
    ax.set_yticks (range (len (prompt_split_as_tokens)))
    ax.set_xticklabels (prompt_split_as_tokens)
    ax.set_yticklabels (prompt_split_as_tokens)
    ax.figure.colorbar (im, ax=ax)
    
display_qk_heatmap (qk_per_token)

在这里插入图片描述

mask = torch.full ((len (tokens), len (tokens)), float ("-inf"), device=tokens.device) mask = torch.triu (mask, diagonal=1) mask
tensor ([[0., -inf, -inf, -inf, -inf, -inf, -inf, -inf, -inf, -inf, -inf, -inf, -inf, -inf, -inf, -inf, -inf],
        [0., 0., -inf, -inf, -inf, -inf, -inf, -inf, -inf, -inf, -inf, -inf, -inf, -inf, -inf, -inf, -inf],
        [0., 0., 0., -inf, -inf, -inf, -inf, -inf, -inf, -inf, -inf, -inf, -inf, -inf, -inf, -inf, -inf],
        [0., 0., 0., 0., -inf, -inf, -inf, -inf, -inf, -inf, -inf, -inf, -inf, -inf, -inf, -inf, -inf],
        [0., 0., 0., 0., 0., -inf, -inf, -inf, -inf, -inf, -inf, -inf, -inf, -inf, -inf, -inf, -inf],
        [0., 0., 0., 0., 0., 0., -inf, -inf, -inf, -inf, -inf, -inf, -inf, -inf, -inf, -inf, -inf],
        [0., 0., 0., 0., 0., 0., 0., -inf, -inf, -inf, -inf, -inf, -inf, -inf, -inf, -inf, -inf],
        [0., 0., 0., 0., 0., 0., 0., 0., -inf, -inf, -inf, -inf, -inf, -inf, -inf, -inf, -inf],
        [0., 0., 0., 0., 0., 0., 0., 0., 0., -inf, -inf, -inf, -inf, -inf, -inf, -inf, -inf],
        [0., 0., 0., 0., 0., 0., 0., 0., 0., 0., -inf, -inf, -inf, -inf, -inf, -inf, -inf],
        [0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., -inf, -inf, -inf, -inf, -inf, -inf],
        [0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., -inf, -inf, -inf, -inf, -inf],
        [0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., -inf, -inf, -inf, -inf],
        [0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., -inf, -inf, -inf],
        [0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., -inf, -inf],
        [0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., -inf],
        [0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0.]])
qk_per_token_after_masking = qk_per_token + mask
display_qk_heatmap (qk_per_token_after_masking)

在这里插入图片描述

qk_per_token_after_masking_after_softmax = torch.nn.functional.softmax (qk_per_token_after_masking, dim=1).to (torch.bfloat16) display_qk_heatmap (qk_per_token_after_masking_after_softmax)

在这里插入图片描述
值(几乎在注意力结束时)

这些分数 (0-1) 被用于确定每个 token 使用了多少值矩阵。

就像键一样,值权重也在 4 个注意力头之间共享(以节省计算量)

结果,下面的值权重矩阵形状为 [8x128x4096]

v_layer0 = model ["layers.0.attention.wv.weight"] v_layer0 = v_layer0.view (n_kv_heads, v_layer0.shape [0] //n_kv_heads, dim) v_layer0.shape
# torch.Size ([8, 128, 4096])

20240608(完篇)

高百资格赛落幕,场地3000米,完赛成绩11分02秒,队内第二,嘉伟9分58秒(PB)第一。


最近两三天,状态急转直下,可能是前阵子被天气给背刺了。5号阴雨绵绵,早上短袖短裤出门冻得瑟瑟发抖,下午冒雨顶了10KM,晚上又淋雨骑去蜀地源,来回路上受足了凉;6号早上依然很冷,这回长了记性,换长袖长裤出门,结果中午出太阳,热得人发软,晚上连走路都觉得累,强撑着去跑间歇,完全使不上力气;7号,也就是昨天,我赌是会是晴天,结果又被冻一天,晚上已经开始头疼,赶紧回去洗了热水澡,早早熄灯睡觉。

六点自然醒,所幸,我亦随朝阳升起。


自2021年高百上海站首次举办以来,这是我的第四回高百,也是最后一回。

过去两个月里,经历有生以来最难熬的伤痛,一度觉得再也无法跑动。五月的停摆,身心双重打击,为了避免给右脚带来不必要的负担,大部分时间都只是在实验室坐着。不过,许久没有这么静下来做事,有些事还是得静静坐着才能做成,不是吗?

有失必有得。

六月开始重新训练,想也知道不可能恢复到巅峰。尽管坚持了一周每天10K的训练量,但无氧水平依然欠缺,何况,3000米本就不是我擅长的项目。明年我们几个老家伙都要毕业了,很希望其他人可以在今天超越我。

可惜全程也没有第二个人能跑到我的前面。全队无人跑出10分台成绩。说实话,有点丢人。


精神很兴奋,是适合比赛的状态。但是天气很热,早上六点太阳就已经挺晒的了,七点我过去陪LY遛了几圈,差不多530的配速,跑得全身湿透,到八点起跑时已经热得不行。

LY是做投资行为研究的,跟她聊了一些,因为这是我感兴趣的领域。我一直很喜欢和做运筹研究的人谈论,包括之前交接的RWT,因为这是个有趣且值得探究的问题,至少比我自己的研究更“科学”一些,尽管AI是现在的主流,却是个几乎没有科学性可言的主流。

恰逢这几天是高考,很久远的故事,却依然历历在目,作为一个从高一开学一直住校到高考最后一天的人,高考三天何止是放纵。江苏高考那时还是只算语数外三门,总分480。第一天,语文数学都考完了,大头结束,我也没有跟人对答案的习惯,晚上回去就和还住校的朋友打牌;第二天,英语考完,已是完全放纵,不过那天下大雨,老妈给我送了些饭菜来,还是小心翼翼地把老妈送走,再跟朋友继续打牌;第三天,物理和化学,纯过场,反正不算分,没啥意思,考完就开车去南京赶考领军计划,第二天又来现在的学校参加自招。

现在的人会觉得很离谱,但扬州中学真的就是这个样子。整个高中三年,几乎每个周末,我这届住校生都会聚众打牌,从掼蛋到三国杀,跟宿管斗智斗勇,从没被处分过。我们还经常邀请走读的同学来一起打牌。尽管如此,跟我打了三年牌的朋友,都去了中科大、上交、南大、复旦,而我却折戟在最擅长的数学科目上,两条填空题算错少了十分,沦落到这里混吃等死,跟高考期间打牌半毛钱关系没有。

所以,我始终觉得人没必要把自己逼得太紧,人生只有一回,胜负总是难免,人生不可能没有遗憾,车到山前必有路,船到桥头自然直。


枪响,虚幻重新拉回现实。赛前叮嘱两个目前最强的新人——XR和YY,3000米的策略是前冲后顶,有嘉伟领跑,第一个1000米尽量跟上,后面尽量顶住,争取都能跑进11分。

可是枪响后,依然只有我跟上嘉伟。

嘉伟第一圈69秒,几乎平了我的400米PB,第二圈我开始调整节奏,嘉伟渐行渐远。第一个1000米3分25秒;第五圈开始套圈队尾女生,跟她们讲天气很热,量力而行(这次女子第一郑芹芹,12分整,不愧是戈赛跟AK组队的女A选手,确实厉害)。第二个1000米3分48秒;第六圈已经极其吃力,转头看到后面空无一人,真的很想慢点下来,但是又很想跑进11分钟,不想给自己放弃的借口。第三个1000米3分46秒;冲过终点时,一下子瘫倒在长椅上,看着手表上的计时,无奈地扶着额头,摇头,极其痛苦地喘气。

11分02秒,我真的尽全力了,终究不得。


白驹过隙,忽然而已。四年了,我们不是长跑强校,但也好想能跟诸位,最后跑一回全国总决赛。

很想说小老弟们还得练,然总是希望各位能跑在我前面。未来是你们的。

两个月,太阳真的再度升起了。只可惜,阳光下挥洒过的汗水,是终将逝去的青春。

在这里插入图片描述


END

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值