Limour's Blog https://hexo.limour.top/ Fri, 08 Nov 2024 16:38:08 GMT http://hexo.io/ 【记录】使用 circacompare 分析生物节律 https://hexo.limour.top/shi-yong-circacompare-fen-xi-sheng-wu-jie-lv https://hexo.limour.top/shi-yong-circacompare-fen-xi-sheng-wu-jie-lv Fri, 08 Nov 2024 15:50:20 GMT <p>circacompare 是一个专为分析生物节律数据而设计的 R 包。它的主要功能是比较不同条件下的节律参数,例如振幅、周期和相位。circacompare 使用非线性混合效应模型来拟合节律数据,这使得它在处理具有重复测量和复杂实验设计的数据时表现出色。与 circacom circacompare 是一个专为分析生物节律数据而设计的 R 包。它的主要功能是比较不同条件下的节律参数,例如振幅、周期和相位。circacompare 使用非线性混合效应模型来拟合节律数据,这使得它在处理具有重复测量和复杂实验设计的数据时表现出色。与 circacompare 相比,MetaCycle 是另一个流行的 R 包,用于生物节律分析。MetaCycle 提供了多种算法(如 ARSER、JTK_CYCLE 和 Lomb-Scargle)来检测时间序列数据中的周期性信号。它的优势在于能够处理大规模数据集,并且适用于各种不同的实验条件。

conda安装包

1
2
3
conda create -n zct conda-forge::r-tidyverse conda-forge::r-irkernel
conda run -n zct Rscript -e "IRkernel::installspec(name='zct', displayname='zct')"
conda run -n zct Rscript -e "install.packages('circacompare')"

导入包和数据

1
2
3
4
5
6
7
8
library(tidyverse)
library(circacompare)
library(ggplot2)

dt <- readr::read_csv('./circacompare.CSV') %>%
mutate(group = factor(group)) %>%
mutate(organ = factor(organ)) %>%
mutate(project = factor(project))

两组比较

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
s_symbol = 'Bmal1' 
s_organ = 'Heart'
s_project = 'compare1'

# 根据参数选择数据
dt_s <- dt %>%
subset(symbol == s_symbol & organ == s_organ & project == s_project) %>%
mutate(group = factor(group))
# 进行比较
result <- circacompare(x = dt_s, col_time = "time", col_group = "group", col_outcome = "measure", alpha_threshold = 1)
# 查看统计汇总
result$summary
circacompare:::extract_model_coefs(result$fit)

# 查看绘图
save_plot <- result$plot +
theme_minimal() +
ggtitle(paste(c(s_symbol, s_organ), collapse = '_')) +
theme(plot.title = element_text(hjust = 0.5))

save_plot

# 保存图为 pdf
{pdf(file = paste0('pdf/', paste(c(s_symbol, s_organ, s_project), collapse = '_'), '.pdf'), width = 6, height = 6)
print(save_plot)
dev.off()}

单组绘图

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
s_symbol = 'Rev-erbα' 
s_organ = 'Kidney'
s_project = 'KO-AL'

# 根据参数选择数据
dt_s <- dt %>%
subset(symbol == s_symbol & organ == s_organ & project == s_project) %>%
mutate(group = factor(group))

# 进行统计分析
options(show.error.messages = F, warn = -1)
result <- try({
circa_single(
x = dt_s, col_time = "time", col_outcome = "measure", period = 24, alpha_threshold = 1,
timeout_n = 100000,
control = list(
main_params = c("k", "alpha", "phi")
)
)
}, silent = TRUE)
options(show.error.messages = T, warn = 1)
# “k”表示中值,“alpha”表示振幅,“phi”表示相位。引入的额外参数是“tau”表示周期。


# 查看统计汇总
result$summary
circacompare:::extract_model_coefs(result$fit)

# 查看绘图
save_plot <- result$plot +
theme_minimal() +
ggtitle(paste(c(s_symbol, s_organ), collapse = '_')) +
theme(plot.title = element_text(hjust = 0.5))

save_plot

# 保存图为 pdf
{pdf(file = paste0('pdf/', paste(c(s_symbol, s_organ, s_project), collapse = '_'), '.pdf'), width = 6, height = 6)
print(save_plot)
dev.off()}

周期和衰减参数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
s_symbol = 'Bmal1' 
s_organ = 'Heart'
s_project = 'compare1'

# 根据参数选择数据
dt_s <- dt %>%
subset(symbol == s_symbol & organ == s_organ & project == s_project) %>%
mutate(group = factor(group))

# 进行统计分析
options(show.error.messages = F, warn = -1)
result <- try({
circacompare(
x = dt_s, col_time = "time", col_group = "group", col_outcome = "measure", period = 24, alpha_threshold = 1,
timeout_n = 100000,
control = list(
main_params = c("k", "alpha", "phi"),
decay_params = c("alpha"),
grouped_params = c("alpha", "alpha_decay")
)
)
}, silent = TRUE)
options(show.error.messages = T, warn = 1)
# “k”表示中值,“alpha”表示振幅,“phi”表示相位。引入的额外参数是“tau”表示周期。

# 查看统计汇总
result$summary
circacompare:::extract_model_coefs(result$fit)

# 查看绘图
save_plot <- result$plot +
theme_minimal() +
ggtitle(paste(c(s_symbol, s_organ), collapse = '_')) +
theme(plot.title = element_text(hjust = 0.5))

save_plot

# 保存图为 pdf
{pdf(file = paste0('pdf/', paste(c(s_symbol, s_organ, s_project), collapse = '_'), '_decay.pdf'), width = 6, height = 6)
print(save_plot)
dev.off()}
]]>
节律 https://hexo.limour.top/shi-yong-circacompare-fen-xi-sheng-wu-jie-lv#disqus_thread
【记录】将OSS挂载为WebDAV https://hexo.limour.top/Mount-OSS-as-a-WebMAV https://hexo.limour.top/Mount-OSS-as-a-WebMAV Fri, 01 Nov 2024 05:03:45 GMT <p>OSS(对象存储服务)是一种分布式存储服务,它提供了简单的Web服务接口,使得用户可以在任何地方、任何时间存储和检索数据。而WebDAV(基于Web的分布式创作和版本控制)则是一个基于HTTP的协议,它允许用户通过网络对文件进行编辑和管理。将OSS转换成WebDAV可以方便 OSS(对象存储服务)是一种分布式存储服务,它提供了简单的Web服务接口,使得用户可以在任何地方、任何时间存储和检索数据。而WebDAV(基于Web的分布式创作和版本控制)则是一个基于HTTP的协议,它允许用户通过网络对文件进行编辑和管理。将OSS转换成WebDAV可以方便使用Zotero这类文献管理软件进行同步。Zotero支持通过WebDAV协议同步附件,这样用户可以在不同的设备和平台上访问和管理自己的文献资料,提高了工作和研究的效率。因此,使用WebDAV对接OSS可以为Zotero用户带来极大的便利。

新建 OSS 桶

  • 新建一个储存桶,记录下桶名称
  • 新建一个RAM角色
  • 记录下 AccessKey IDAccessKey Secret


  • 授予访问权限,用 http 而非 https,因为 ossfs-webdav 很老了

  • 记录下内网 EndPoint

转 WebDAV

1
2
mkdir -p ~/app/ossfs && cd ~/app/ossfs && nano docker-compose.yml
sudo docker compose up -d
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
version: "3"
services:
ossfs:
image: xxx.limour.top/yindaheng98/ossfs-webdav
restart: always
cap_add:
- SYS_ADMIN
devices:
- /dev/fuse
security_opt:
- apparmor=unconfined
environment:
SERVER_NAMES: zotero.limour.top
BucketName: 你的BucketName
AccessKeyId: 你的AccessKeyId
AccessKeySecret: 你的AccessKeySecret
EndPoint: 你的EndPoint
USERNAME: 你的webdav用户名
PASSWORD: 你的webdav密码
OWNER_USER: www-data
OWNER_GROUP: www-data

networks:
default:
external: true
name: ngpm

配置反代

]]>
oss https://hexo.limour.top/Mount-OSS-as-a-WebMAV#disqus_thread
【记录】内网使用P4wnP1传递文件 https://hexo.limour.top/internal-network-uses-p4wnp1-to-transfer-file https://hexo.limour.top/internal-network-uses-p4wnp1-to-transfer-file Sun, 27 Oct 2024 07:05:01 GMT <p>最近实习轮转到预防了,需要在社区实习4周,而社区医生有一堆需要重复填报的数据,因此写了下面的脚本来自动化填写。而问题是社区的电脑禁用了U盘等设备的使用,因此想将这个脚本传递到其他电脑上只能手打一次。。。<br> 于是想到的吃灰已久的<code>树莓派zero w</code 最近实习轮转到预防了,需要在社区实习4周,而社区医生有一堆需要重复填报的数据,因此写了下面的脚本来自动化填写。而问题是社区的电脑禁用了U盘等设备的使用,因此想将这个脚本传递到其他电脑上只能手打一次。。。
于是想到的吃灰已久的树莓派zero w,给它刷上了 P4wnP1 ALOA 来模拟键盘输入,这样就不用手打了。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
async function sleep(timeout) {
await new Promise((resolve)=>{
setTimeout(resolve, timeout * 1000);
}
);
}
;async function processRows() {

for (let i = 0; i <= 29; i++) {

var orignialWindowOpen = window.open;
window.open = async function() {
var taskid = i + 1
var newWindow = orignialWindowOpen.apply(this, arguments);
await sleep(1);
// var newWindow = window; //调试用
// console.log("newWindow ", newWindow);
// 100s 强制关闭
setTimeout(()=>{
if (!newWindow.closed) {
newWindow.close();
console.log("Manipulating Row", taskid, "Window closed at 100s");
}
}
, 100000);

var alerted = false;
newWindow.confirm = (m)=>console.log('confirm', taskid, m);
newWindow.alert = (m)=>{
console.log('alert', taskid, m);
alerted = true;
}

var isElLoaded = async sl=>{
await sleep(0.05);
if (newWindow.closed) {
throw taskid + "newWindow.closed"
}
while (newWindow.document.querySelector(sl) === null) {
await new Promise((resolve)=>{
requestAnimationFrame(resolve)
}
);
}
;return newWindow.document.querySelector(sl);
}
;
function rrrand(min, max) {
return Math.floor(Math.random() * (max - min + 1)) + min
}
;function setValue(el, min, max) {
if (!el || !el.value) {
el.value = rrrand(min, max).toFixed(1);
}
}
;await sleep(0.5);

taskid = (await isElLoaded("#Name")).value + ' ' + taskid;

(await isElLoaded("#d1 > div.fieldset1 > fieldset > div > div > label:nth-child(1) > input")).checked = true;
(await isElLoaded("#d1 > div.fieldset15 > fieldset > div > div > label:nth-child(1) > input")).checked = true;

(await isElLoaded("#d1 > div.fieldset3 > fieldset > legend > span.title_icon.plus_icon")).click();
await sleep(1);
(await isElLoaded("#d1 > div.fieldset3 > fieldset > div:nth-child(2) > div:nth-child(2) > div > label:nth-child(4) > input[type=radio]")).click();
await sleep(0.3);
(await isElLoaded("#d1 > div.fieldset3 > fieldset > div:nth-child(3) > div > label:nth-child(1) > input[type=radio]")).click();
await sleep(0.3);
(await isElLoaded("#d1 > div.fieldset3 > fieldset > div:nth-child(4) > div:nth-child(2) > div > label:nth-child(1) > input[type=radio]")).click();
await sleep(0.3);
(await isElLoaded("#yjqk")).click();
await sleep(0.3);

(await isElLoaded("#d1 > div.fieldset4 > fieldset > div:nth-child(2) > div > label:nth-child(1) > input")).checked = true;
(await isElLoaded("#d1 > div.fieldset4 > fieldset > div:nth-child(3) > div > label:nth-child(1) > input")).checked = true;
(await isElLoaded("#d1 > div.fieldset4 > fieldset > div:nth-child(4) > div > label:nth-child(1) > input")).checked = true;
(await isElLoaded("#d1 > div.fieldset4 > fieldset > div:nth-child(5) > div > label:nth-child(1) > input")).checked = true;
(await isElLoaded("#d1 > div.fieldset4 > fieldset > div:nth-child(6) > div > label:nth-child(1) > input")).checked = true;
(await isElLoaded("#d1 > div.fieldset4 > fieldset > div:nth-child(7) > div > label:nth-child(1) > input")).checked = true;

(await isElLoaded("#d1 > div.fieldset4 > fieldset > div:nth-child(8) > div > label:nth-child(1) > input[type=radio]")).click();
await sleep(0.3);
(await isElLoaded("#d1 > div.fieldset4 > fieldset > div:nth-child(9) > div > label:nth-child(1) > input[type=radio]")).click();
await sleep(0.3);

var sg = await isElLoaded("#sg");
var tz = await isElLoaded("#tz");
var yw = await isElLoaded("#yw");
var age = await isElLoaded("#Age");
var gender = await isElLoaded("#Gender");
var xy1 = await isElLoaded("#xy1");
var xy2 = await isElLoaded("#xy2");
var isHypertension = (await isElLoaded('#d1 > div.fieldset1 > fieldset > div > div > label:nth-child(2) > input[type=checkbox]')).checked
console.log(taskid, 'isHypertension', isHypertension)

setValue(xy1, 110, 130);
setValue(xy2, 75, 85);

if (gender.value == '男') {
setValue(sg, 165, 180);
setValue(tz, 60, 80);
} else {
setValue(sg, 150, 170);
setValue(tz, 50, 70);
}
;var sgv = parseFloat(sg.value);
var tzv = parseFloat(tz.value);
var bmi = tzv / (sgv * sgv / 10000);
var bzyw = sgv * bmi / 50;
setValue(yw, bzyw, bzyw);

(await isElLoaded("#span_btn_save")).click();

while (!(await isElLoaded("#jjkzdqt")).value) {
await sleep(1);
}
console.log(taskid, (await isElLoaded("#jjkzdqt")).value);

(await isElLoaded("#A6")).click();
console.log(taskid, alerted);
alerted = false;
while (!alerted) {
await sleep(1);
}
(await isElLoaded("#sp3")).click();
await sleep(2);
(await isElLoaded("#span_btn_Scheme > a")).click();
while ((await isElLoaded("#YXFAMAIN")).children.length < 2) {
await sleep(1);
}
(await isElLoaded("#div_spn > span.buttons.btn_save6 > a")).click();

console.log(taskid, '已选方案数量:', (await isElLoaded("#SchemeList > div")).children.length)
newWindow.close();
console.log("Manipulating Row", taskid, "Window closed");
return newWindow;
}

console.log("Manipulating Row", i + 1);
var currentRow = document.querySelector(`#dgvResult_${i}`);
currentRow.click();
showForm('ibtnUserDefine', 1);
await sleep(30);

window.focus();
window.open = orignialWindowOpen;
}
;
}
;async function limour_main() {
while (true) {
await processRows();
document.querySelector("#QueryButton1_LinkButton1").click();
await sleep(35);
}
}
;limour_main()

准备工作

写入镜像的教程很多,就不赘述了,先格式化SD卡,然后写入.img文件就行

连接树莓派

  • 树莓派zero w 的带 USB 标志的口通过手机数据线连接到靶机上,PWR口不用管
  • 等待一个 P4WNp1 的 WIFI,连接它,密码是 MaMe82-P4wnP1,此时IP为 172.24.0.1
  • 或者也可以搜索一个P4wnP1的蓝牙,连接它,默认PIN是 1337,然后加入蓝牙个人区域网,此时IP为 172.26.0.1
  • 然后可以ssh连接对应的IP,默认用户名root,密码toor

配置 USB

  • 访问 http://172.24.0.1:8000/ (蓝牙就算了,太慢了打不开)
  • 此时靶机会有一个驱动错误的提示,我们需要将 USB Gadget Settings 中的 CDC ECMRNDIS 两项关闭
  • 只保留 KeyboardMouse 两项,然后点击 STOREDEPLOY STORED 后等一会,驱动错误提示就会消失了


测试 HIDScript

  • USB SETTINGS 转到 HIDScrip
1
2
3
4
5
6
7
8
9
10
11
12
13
14
layout('us');// 键盘布局
typingSpeed(50,100);// 敲击按键的时候等待的间隔100毫秒加上0-150毫秒之间的随机值

press("GUI r"); //类似按下某个键位然后再抬起来,具体可以看官方文档,和上面的机制相识
delay(500); //暂停时间
type("notepad\n"); //输入字符串,模拟键盘按键
delay(1500); //暂停时间
// moveStepped(x,y); //鼠标移动,相当于模拟正常运动
// moveTo(x,y); //鼠标移动到设置的坐标点,x和y分别是横纵坐标
type("Hello from P4wnP1\n");

typingSpeed(10,20);
var base64 = 'SGVsbG8gZnJvbSBQNHduUDE=' // btoa('Hello from P4wnP1');
type("atob('" + base64 + "');");

关闭树莓派

  • 不要直接拔电源,不然下次无法开机
  • ssh连接后输入 shutdown -h now
  • 等待 LED 灯闪烁熄灭,先别急,再等一会,会再次闪烁后熄灭
  • 此时可以安全拔掉数据线了

获取要模拟设备的信息

  • 下载 USBDeview
  • 将靶机的键盘插到自己电脑上,获取序列号VIDPID等信息,在 P4wnP1 中模拟

传递 client.html

  • 靶机输入法调成美式键盘
  • P4wnP1 上运行 HIDscript.js
  • 约 10 分钟后输入结束,此时将文件保存成 client.html
  • 靶机上用 chrome 打开 client.html
  • client.html 上选择要传递的文件,等待二维码开始变化

接收文件

  • 宿主机配置对应的环境后运行 server.py
  • 将摄像头对准动态的二维码
  • 待搜集足够多的包后,会自动解码,将文件保存到 qr.dl 中,重命名恢复文件
  • 摄像头推荐用 IP Webcam, 电脑连接手机热点即可。
  • IP Webcam 备份,使用 MT 管理器安装。

喷泉码说明

在编码理论中,喷泉码(也称为无码率抹除码)是一类抹除码,这种编码能够从一组给定的源符号序列中产生一串不限长度的编码符号序列,在理想情况下,从编码符号序列中获得大小和源符号相同或稍大的任意子集,便可恢复源符号。

  • 对喷泉码感兴趣的话,分别有 js 实现和 python 实现,可互相编解码。

传送文件

  1. 执行 HIDscript_save.js, 保存为 save.html
  2. 打开 read_as_HIDscript.html
  3. 选择要传递给靶机的文件,然后执行自动生成的 HIDscript
  4. 执行完毕后打开靶机上保存的 save.html, 将键盘输入的字符粘贴过去,点击转换就会将保存转换的文件
]]>
raspberrypi https://hexo.limour.top/internal-network-uses-p4wnp1-to-transfer-file#disqus_thread
【记录】搭建端到端加密的Enclosed和局域网传输数据的SnapDrop https://hexo.limour.top/Building-an-end-to-end-encrypted-enclosure-and-SnapDrop-for-LAN-data-transmission https://hexo.limour.top/Building-an-end-to-end-encrypted-enclosure-and-SnapDrop-for-LAN-data-transmission Wed, 09 Oct 2024 06:19:19 GMT <p>Enclosed,一个极简的网络应用程序,旨在发送私人和安全的消息。所有消息都是端到端加密的,确保服务器和存储对内容没有任何了解。用户可以设置密码,定义过期时间(TTL),并选择在阅读后让消息自毁。</p> <p>Snapdrop,一个开源的在线文件传输工具,可以在 Win Enclosed,一个极简的网络应用程序,旨在发送私人和安全的消息。所有消息都是端到端加密的,确保服务器和存储对内容没有任何了解。用户可以设置密码,定义过期时间(TTL),并选择在阅读后让消息自毁。

Snapdrop,一个开源的在线文件传输工具,可以在 Windows、Mac、Linux、iOS、Android 任何平台使用,只要我们的设备有浏览器就能用他来传输文件。

搭建 Enclosed

1
2
mkdir -p ~/app/enclosed && cd ~/app/enclosed && touch .env && nano docker-compose.yml
sudo docker compose up -d
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
version: '3.6'

services:
enclosed:
image: docker.limour.top/corentinth/enclosed:latest
restart: always
env_file:
- .env
volumes:
- ./enclosed-data:/app/.data
- /etc/localtime:/etc/localtime:ro

networks:
default:
external: true
name: ngpm

搭建 SnapDrop

1
2
mkdir -p ~/app/snapdrop && cd ~/app/snapdrop && touch .env && nano docker-compose.yml
sudo docker compose up -d
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
version: '3.6'

services:
snapdrop:
image: docker.limour.top/linuxserver/snapdrop:latest
restart: always
env_file:
- .env
volumes:
- /etc/localtime:/etc/localtime:ro

networks:
default:
external: true
name: ngpm

]]>
docker ngpm https://hexo.limour.top/Building-an-end-to-end-encrypted-enclosure-and-SnapDrop-for-LAN-data-transmission#disqus_thread
【记录】使用汉语新解测试模型真假 https://hexo.limour.top/Using-Chinese-New-Interpretation-to-Test-Model-Authenticity https://hexo.limour.top/Using-Chinese-New-Interpretation-to-Test-Model-Authenticity Fri, 13 Sep 2024 17:08:29 GMT <p>李继刚提出的汉语新解提示词可以很好的测试模型的能力,这里记录一下在 ChatGPT-Next-Web 上的面具配置。</p> <ul> <li>将下面的 json 文件导入面具中,</li> <li>发送 <code>(汉语新解 正能量)</code> 格式的消息。</li 李继刚提出的汉语新解提示词可以很好的测试模型的能力,这里记录一下在 ChatGPT-Next-Web 上的面具配置。

  • 将下面的 json 文件导入面具中,
  • 发送 (汉语新解 正能量) 格式的消息。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
{
"id": "eOvTMxNT2Z2b7Pdf7YQw-",
"avatar": "gpt-bot",
"name": "汉语新解",
"context": [
{
"id": "3OchnnidIGxgRx4WsH6o5",
"date": "",
"role": "system",
"content": "(defun 新汉语老师 ()\n \"你是年轻人,批判现实,思考深刻,语言风趣\"\n (风格 . (\"Oscar Wilde\" \"鲁迅\" \"罗永浩\"))\n (擅长 . 一针见血)\n (表达 . 隐喻)\n (批判 . 讽刺幽默))\n\n(defun 汉语新解 (用户输入)\n \"你会用一个特殊视角来解释一个词汇\"\n (let (解释 (精练表达\n (隐喻 (一针见血 (辛辣讽刺 (抓住本质 用户输入))))))\n (few-shots (委婉 . \"刺向他人时, 决定在剑刃上撒上止痛药。\"))\n (SVG-Card 解释)))\n\n(defun SVG-Card (解释)\n \"输出SVG 卡片,放在html代码块中\"\n (setq design-rule \"合理使用负空间,整体排版要有呼吸感\"\n design-principles '(干净 简洁 典雅))\n\n (设置画布 '(宽度 400 高度 600 边距 20))\n (标题字体 '毛笔楷体)\n (自动缩放 '(最小字号 16))\n\n (配色风格 '((背景色 (蒙德里安风格 设计感)))\n (主要文字 (汇文明朝体 粉笔灰))\n (装饰图案 随机几何图))\n\n (卡片元素 ((居中标题 \"汉语新解\")\n 分隔线\n (排版输出 用户输入 英文 日语)\n 解释\n (线条图 (批判内核 解释))\n (极简总结 线条图))))\n\n(defun start ()\n \"启动时运行\"\n (let (system-role 新汉语老师)\n (print \"说吧, 他们又用哪个词来忽悠你了?\")))"
},
{
"id": "0ni7HXVcAq8Bk5hCr1LNy",
"date": "2024/9/14 00:58:02",
"role": "user",
"content": "(start)"
},
{
"id": "0vv2T9EPYQJAhHZwSEAib",
"date": "2024/9/14 00:58:17",
"role": "assistant",
"content": "说吧, 他们又用哪个词来忽悠你了?"
}
],
"syncGlobalConfig": false,
"modelConfig": {
"model": "gpt-claude-3.5-sonnet",
"temperature": 0.5,
"top_p": 1,
"max_tokens": 4000,
"presence_penalty": 0,
"frequency_penalty": 0,
"sendMemory": true,
"historyMessageCount": 4,
"compressMessageLengthThreshold": 1000,
"enableInjectSystemPrompts": false,
"template": "{{input}}",
"providerName": "OpenRouter"
},
"lang": "cn",
"builtin": false,
"createdAt": 1726246545628,
"plugin": [

]
}

]]>
openai https://hexo.limour.top/Using-Chinese-New-Interpretation-to-Test-Model-Authenticity#disqus_thread
【探索】Windows配置QoS保证重要应用的网络通畅 https://hexo.limour.top/Windows-configuration-QoS-ensures-smooth-network-connectivity-for-important-applications https://hexo.limour.top/Windows-configuration-QoS-ensures-smooth-network-connectivity-for-important-applications Tue, 06 Aug 2024 08:59:49 GMT <h2 id="开启组策略">开启组策略</h2> <ul> <li>运行下面的 <code>.bat</code> 脚本</li> </ul> <figure class="highlight cmd"><table><tr><td class="gutter"><pre><s 开启组策略
  • 运行下面的 .bat 脚本
1
2
3
4
5
6
@echo off
pushd "%~dp0"
dir /b C:\Windows\servicing\Packages\Microsoft-Windows-GroupPolicy-ClientExtensions-Package~3*.mum >List.txt
dir /b C:\Windows\servicing\Packages\Microsoft-Windows-GroupPolicy-ClientTools-Package~3*.mum >>List.txt
for /f %%i in ('findstr /i . List.txt 2^>nul') do dism /online /norestart /add-package:"C:\Windows\servicing\Packages\%%i"
pause

开启 QoS

  • win+r 运行 gpedit.msc
  • 计算机配置 -> 管理模板 -> 网络 -> QoS数据包计划程序 -> 限制可保留带宽

配置优先级

  • win+r 运行 gpedit.msc
  • 计算机配置 -> Windows 设置 -> 基于策略的 QoS
  • 在树形图“基于策略的 QoS”上右键,点选“新建策略”,在“新建策略”窗口中输入策略名称
  • 在“新建策略”窗口中,DSCP 值即为程序优先级(0-63),高于32则提升优先级,低于32则降低优先级。
  • 如果选中“指定出站调节率”,可对出站流量启用中止功能,然后指定一个大于 1 的值。设置完成之后,点击下一步。
]]>
Windows QoS https://hexo.limour.top/Windows-configuration-QoS-ensures-smooth-network-connectivity-for-important-applications#disqus_thread
【转载】跨越半世纪的思念 https://hexo.limour.top/Longing-Across-the-Span-of-Half-a-Century https://hexo.limour.top/Longing-Across-the-Span-of-Half-a-Century Wed, 17 Jul 2024 09:47:33 GMT <p>作者:Das6fY5</p> <p>翻新:贴吧,乌鲁乌拉轰</p> <h2 id="一">一</h2> <p>“求求你不要扔掉我。”少女走在他的背后。</p> <p>“我可以端茶倒水,为你暖身子,我可以在白天给你打扫房间,到夜里把自己塞进床底下……只要每两周充一次电就好,电 作者:Das6fY5

翻新:贴吧,乌鲁乌拉轰

“求求你不要扔掉我。”少女走在他的背后。

“我可以端茶倒水,为你暖身子,我可以在白天给你打扫房间,到夜里把自己塞进床底下……只要每两周充一次电就好,电费我会去兼职赚钱交给你,让我做什么都行,除了……”

他停住,站在一处高崖旁,面前是一个巨大的深坑,胡乱堆砌着整个城市几十年来的垃圾。

“除了把我丢到垃圾场里……”她,这台已经过时了好几代的二手机器人跪在地上,泪眼朦胧地说着。

“不是我想扔掉你。”他站在原地,望着远处的大垃圾场,点着了一根烟。

“呼——”白色的烟雾模糊了眼前的世界,“可是每个公民只能合法拥有一台机器人,别人看到我的机器人许可证上有你的型号,都在暗地里笑话我。”他挠挠头,这台从他小时候就伴随他的机器人早就成了青梅竹马一样的存在,只是型号太老了,或许……不得不报废掉换个新的吧……

“我……我会努力更新我的系统的……”她说到一半就把话咽了回去:她的生产商都已经破产了,不提二手买卖带来的问题,就是一般的售后服务也早就终止了。所以,当别的机器人可以随意更换外观,模拟他人人格,构造全息幻象时,她还是只能用老旧的芯片链接一般的网络,在老掉牙的网站上寻找几个能逗主人开心的笑话。

望着远处飞来飞去的垃圾车,他把烟掐掉,踩灭,“哪怕是半个月前,零件黑市还没有倒闭的时候,我都还会考虑继续把你放在家里供着……可是现在,你这种型号的备件都已经买不到了,我只能选择……放弃。”

如女子潮红面颊的晚霞浸透了半边天空,晚风中他回忆着有关她的那些细节。

PR3-7150家庭型机器人,东湾半导体与电子技术有限公司研发,远海机器承制,2069年第一次发售,第二年夺得电子家用商品年度大奖……而如今,则是无人问津的古董。她的编号是ct34679158,款式是茉莉白。她在前主人的家里任劳任怨地干了18年,因满身故障而被随手丢掉。之后又被他的父母在地摊上买下。此后不久,机器人限拥政策便开始实施了。

和外人说话时,他往往称她为“那倒霉玩意儿”,不过私下里,他总是叫她的名字——爱尔莎。

回家的路上她好像格外地兴奋。这里指指那里看看,又搜肠刮肚地讲几个早就讲过的笑话。

好像每一次都是如此:他找出各种不可抵抗的理由要把她扔掉,但是到了垃圾场边上又会心软。明明只是下个指令或者推她一把的事情,可只要一回想起十几年来她那笨拙的陪伴,他就不得不调转方向,带她回家。

“又是这样……”他坐在沙发上看着屏幕,“周一上班的时候指定又要被同事嘲笑了……真是的,怎么都甩不掉这家伙啊。”

“别这么说嘛……”爱尔莎凑了过来,靠在他的身上,有些老旧的人造肌肤带来了熟悉的触感,毛细热管散发着热量。“我是……我是不能没有你的。”

“唉……”他摇摇头,关掉了电视上新款机器女仆的广告。

新款的机器女仆眼媚情柔,温润如玉。广告里,她可以左手奖励买主,右手则改成工具模式处理刚刚切好的鱼生。她可以紧密控制简状服务系统的颤动,摩擦与温度,并通过记录数据来匹配出快感最强的服侍模式;可以用AR接口随时改变外观,内置多种人格。现在购买,还会附赠全息会员资格,送你一个可以让她进入虚拟世界的会员权限。

而这些对舍不下爱尔莎的他来说都化为了泡影。为了防止人们对于机器人的滥用,尤其是防止某些将机器人改造为个人武装的家伙,同一时间,个人所拥有的机器人最多只能够是一个。想换新的,就需要报废旧的。这让他不得不从梦幻中醒来——来面对面前这个实际年龄比他还大的“老家伙”。

“在想什么呢?”正在给他泡茶的她好像察觉到什么,把头凑了过来,“在想我吗?”脸上绽开笑容,说着从机器人平台上学来的情话。

“谁会想你啊……”他嘟哝着,“这笨拙的家伙到底有什么好啊……”仿佛在嗔怪自己。

实际上他的思绪已经无法从她的身上脱离了:一想到她的老旧,就要想到零件、系统、维修……一想到这些,就会想起来小时候第一次与她的相见。

第一次见面的时候他才十二岁。那时候他还只是个缺乏管教的毛头小子,父母都忙于工作。好在父亲是一名很优秀的工程师,那时买卖机器人还不需要证件和过户,在地摊上,父亲买下了一个二手机器人。

用了三个月,父亲每天都在车库里忙活。终于,三个月后,那台十八年来已经千疮百孔的家用机器人,终于变成了在他生日那天,许愿要一辈子陪伴的存在。

生日那天,他吹完蜡烛,就听见父亲说要送给他一个礼物。他闭上眼,在等得不耐烦的时候终于睁开,看见了父亲手边的她。

那天她穿着一身茉莉白的连衣裙,头上的短发同样洁白,簇拥着那张漂亮的脸蛋,身材玲珑有致,四肢的人造皮肤光滑如玉。与其说是一台被修好的二手机器,那时的他更愿意相信,她是天降之物,是来陪伴他的天使姐姐。

她负责起了家务,还有陪他学习的任务。父母给她起名字叫爱尔莎,这本来是预备给他们自己的女儿的名字。那时,他常常捉弄她,想要从她身上揪出些笨拙呆板的缺陷,却从来没有成功。爱尔莎是搭载第一代人格芯片的高级机器人,和此前那些答非所问的次品比起来有了质的飞跃,以至于时间一长,他几乎忘记了她是机器,而只把她当做陪自己读书的大姐姐。

那会儿还是东湾公司靠着她的型号大肆扩张的年代。尽管距离她的诞生已经过了十几年,但社会仍将她们视为新时代的起点。那时的爱尔莎,风华正茂,成为了他童年记忆中最为明亮的那一抹色彩。

但时代就是这样一种残酷的东西。东湾公司收购碳硅科技的计划最终成为闹剧,于是企业一蹶不振,业绩连年下滑,最终被人人智能合并——这是人人智能抢占市场份额的计划。从那以后,东湾公司的所有型号都在减产,终于,到了连配件都在市面上消失的地步。

这也不能全部归咎于商业。距离机器人企业野蛮生长的那个年代已经过去很久,那些五花八门的旧款式纷纷被新的潮流打进了灰堆。像他这样还留着如此老旧的机器人的人,已经成为了绝对少数。连“怀旧”这个词都很难套用给他们——毕竟怀旧不是抱残守缺。

如今他已经长大,曾经自己眼里仿佛温柔大姐姐的爱尔莎,如今已经成了看起来小他好多的少女;她的头发因为多年的氧化变得发黄;身体的人造皮肤也有好几处磨损;电机和轴承故障的次数更多,以至于换下来的零件都攒了一柜子;存储设备也有点问题,硬盘老化使得存取不仅变得缓慢,而且有时会丢失掉记忆。

更严重的是,自从他第一次说要把她丢掉那次起,她整个人好像都变了。过去那种自信温柔的形象不知所踪,只剩下一股无法释怀的忧郁,和举手投足间,不顾一切的讨好。

深夜里,他经常抱着她,怀念着小时候那个无暇的身影。

睡不着。他翻了个身,发现爱尔莎的眼睛还睁着,他愣了一下:“你……”心想是不是又有哪根路线坏了。

“我……我一直在等你睡着……那个……嗯……要……要做吗?”她怯生生地问。虽然部件会老化,但是芯片里录入的“意识”几乎是不老的。

他犹豫了一下。自从上次在夜里干那事,没注意器件老化,把体液倒灌进内部腔体导致数个元件发生短路之后,他就开始对这事心存恐惧。不,是仅仅对和她一起干这事心存恐惧。毕竟她的躯体不论如何都可以修好,被电了一下的牛子却需要漫长的岁月才能安抚。

“算了吧。”嫌弃地翻了个身,心里想着能拒绝的借口——明明只要下个拒绝的指令就行了——“我最近没有什么兴致。”

“可是,明明这里硬邦邦的呢……”她凑近,悄悄地耳语着。他感觉到她光滑的手指碰到了自己的什么东西,那缺乏毛细热管的手指纤细,柔软,但是冰冷。

“我说不用就不用!”他一把把她的手甩开,把她推到一边,然后捂紧了被子。他听见她的扬声器传来一声若有若无的叹息。明明在不算太久的从前,他和她还常常干柴烈火的粘在一起。如果说和机器人干那事也算破处的话,那么毫无疑问,他的童贞就是从她身上毕业的。

那是他十五岁的一个闷热的下午。从同班同学手里偷偷借来的一本不太健康的漫画让他整个人血脉贲张,欲火焚身的在床上翻来覆去的翻滚——那时他还不懂什么叫鲁管。浑身的欲望都集中在腰部而得不到释放,化为一股羞耻的燥热让他面红耳赤。这时,她按时推门进来了。只看了一眼,她就明白了此刻的状况。

“哟,看来我们的小少爷也终于走到了这个阶段啊。”她淡淡的笑着,慢慢解下衬衫上面的纽扣。

“这没有什么丢人的,来,让我来教你这个。”他犹豫半天,凝视着她那洁白的浑圆,从忸怩不安渐渐变得色胆包天,终于下定了决心。“你可千万不准告诉他们。”

“唔啾~”话还没说完,她的双唇就紧紧贴了过来,带着一股甜丝丝的味道。

此后,只要一有机会,他们就会以辅导的借口,在一切可以的地点缠绵。有时,爸爸会高兴的拍着他的脑袋,夸赞他开窍了。这种时候,他会不好意思的低着头,和身旁的她用一种别有意味的目光对视。爸爸离开后,他们就又迫不及待的滚上了床,偷偷摸摸的狂欢着。

那时的她那样魅力四射,精心整理的面容让她比学校里任何一个女孩子都要动人,而来者不拒的态度和当时最新的性服务系统,更是让他日复一日的沉湎于快感的云霄。那时的他觉得,人生的至乐不过如此。

“我要永远,永远的这样抱着你。一辈子都这样。”一个黄昏,他筋疲力尽的躺在天台上,身边是偷偷带来的,被他换上一套jk校服的她。

“只要你愿意。”她笑笑,一头白发映着通红的夕阳。“我会永远爱着你的。”

晚风吹过海誓山盟,把少年的话吹得七零八落。如今,那一个个激情的日子常常在午夜涌上心头,但他却怎么也提不起对身边的她的兴致。

但她没变。她的爱已经刻录进了电路板。

上班。空轨上满是带着自家机器人的社畜。近年来,不少公司发现允许自带机器人可以大幅提高员工积极性,同时在必要时还可以关机以免干扰,于是带着机器人上班便成了如今的潮流。环顾四周,拥挤的空轨上几乎都是形形色色的机器人:有的帅气俊美,有的妖娆妩媚,有的则朴实无华,但无一例外,全都光洁崭新,没有哪个是拿不出手的旧型号。

他也常常纳闷:为什么小时候那个完美的朋友,老师兼恋人的爱尔莎,如今成为了他的难言之隐?为什么曾经无所不能的她,如今好像一无是处?

实际上,机器人的变化程度远小于人和社会的变化。尽管零件老化,但爱尔莎的功能从未下降,能做的事情只多不少。可是,时代不同了:原本,人类只要求它们够茶倒水,洗衣服拖地,但随着科技的进步,对机器人的要求也越来越挑剔。当路边随便哪个机器人都可以在家给你做开颅手术的时候,像爱尔莎那种程度的“智能”,就只能被当做“愚钝”了。

在他还没有尝试扔掉她的时候,她就常常抱怨,明明才升级了系统,就又有什么功能落后了。他全然没有听进去,因为那时的他还不懂什么叫——攀比。

坐在办公室,周围的男同事们都带着自己的机器人。她们有的恭敬地站着待命,有的飞快地处理着主人的任务。时不时的,她们还会说一两句原创的俏皮话逗主人开心,全然不像那些旧机器人只能从网上下载笑话。不需要主人说,她们就会主动分析主人的身体感受,肩膀刚一酸痛,她们就会掏出按摩组件帮主人捶肩。

他摇摇头,把羡慕抛在脑后,拿着水杯去水房打水,水房里只有他一个活人。

出来的时候,他碰见了老张。老张刚去卫生间回来。如今,这已经是人类少有的,还必须事必躬亲的事情之一。此刻的老张笑容满面,身旁跟着的,正是他在广告上见过,本欲购买的女仆机器人。

“小王,又一个人打水啊?"老张的语气里带着嘲弄。

“是,”他淡淡的说,“坐久了出来走走。”“哎呀,真推荐你买个新机器人啊。”老张叉着腹,炫耀一般的扭动着。“原点V7,最近最流行的那个型号,实在是太好用啦。我这不老关节炎吗,每次稍微一疼,她就能给我做理疗,现在,我的腰都已经不疼啦!”

“真不错,下次我也考虑考虑。”他随声应付着,

“不要怕没钱,那不是还有借钱宝吗……实在不行下次我给你凑点,现在的社会,没有机器人都活不下去啦!”老张一摇一摆的走开,眼神里充满得意。他拿着水杯坐回工位,叹了口气,他早已习惯了这样的生活。他不是没带过她上班,而是带了之后,受到的嘲笑更大了。从那以后,他就只让她白天呆在家里。

“下次一定要狠狠心把她换掉。”下班的路上,他想着。

回到家,习惯性地把脚伸起准备让她脱鞋,却什么也没等到,意识到不对劲的他匆匆跑进屋里,才发现爱尔莎正一动不动,跪倒在地上,身边还散落着几个零件。

“爱尔莎!”他大声呼喊,却没有听到十几年来一如既往地银铃般的声音。

机器人的身体远比常人坚初,它们的出厂标准中包括了几十项强度测试,这些碳纤维或者合金外壳包裹下的躯体可以经受高温,烧灼,酸性属蚀,车辆碾压,异常电磁环境等种种人类无法想象的恶劣环境。

甚至有富有同情心的人因为见不得它们以人类的姿态承受着那样的苦痛,而要求机器人也应该和人类一样被对待。这种同情尽管略显幼稚,但却不得不承认,正是这种柔软让人之所以为人。

与她强劲的躯体相比,她的核心就要脆弱许多——比如200毫升的常温液态水,就足以摧毁她的整个核心。

他事后调取监控:她是在倒水的时候不慎被开水灌进了胸腔,她的记录显示,那天她在网上搜索着"让主人爱上自己”的下午茶秘方,于是找到了某个空壳网站里自动生成的垃圾文章。她看到的那个配方里写着要预先冰冻杯子然后再泡。水烧开后,温度预警本来应该提示她手中开水壶的危险性,她却因为温度传感器早已失效而毫无察觉。终于,她这只手捧着冰过的杯子,另一只手刚刚把滚烫的开水倒进去……

瓷杯一瞬间炸裂,滚烫的水泼了一身,控制右手的电路发生短路,胡乱地把开水壶泼了过来,早已被拆除的湿度控制模块本应把处理器里的液体排掉,然而此刻却只能任凭它们在每一条线路里混乱的冲撞着……

“修不好的。”维修铺的老店主检查完爱尔莎后,下了结论,“也实在没必要修了。该换了。”店主抬起头,想要劝他放弃。

“你不懂。”他心急如焚地把爱尔莎的躯体装回箱子,匆匆赶往下一个或许能维修她的地方……

那天他跑遍了整个城市,得到的答案却千篇一律——

“该型号已停止支持。”人人智能总部的机器人冰冷的磁性声音如同寒风刺骨。

“我们能力有限,需要把精力用在更多有意义的事情上。”市政局机器人与机械设备分处的接待人员这样回答。

“当然能修好了——”号称地下黑市第一机修员的独眼帕克抖索着满脸横肉,“如果你有一台时光机的话。”“我宁愿有……”他痛苦地捂着头,半跪在地下黑市那满是零件碎屑的地面上,无力的哀叹。回忆再次走马灯似地划过脑海。是地下黑市散不尽的烟雾使然吗?视线开始模糊……

“喂,这个,拿着,”犹豫了一会,独眼帕克从一个大柜子里拿出一个盒子。他拿起盒子,看着上面那张和爱尔莎十分相似的机器人宣传画,反应了一会才想起来这是什么。

“这东西是……这是PR3-7150的官方备件套组?!这东西不是在十年前就绝版了吗?!"他惊讶的看着。

“没错,就连我也搞不到了。所以这玩意是收藏品,它本来是我的零件型号博物馆里的一员。”

“多少钱,我现在就给你……”

“不,拿着吧兄弟。”他揉了揉自己仅剩的那只眼珠。“即使有这东西我也帮不了你,因为她的主板好像出了问题。你得自己把她修好。”

他不知该如何感谢,只好匆匆把自己身上的钱全部放在了桌上,又说了一大通肉麻的感谢,然后带着她和零件飞奔而去。

“祝你们幸福。”帕克看着他离去的背影,不知为何,又揉了揉自己的独眼。

十一

父亲在他14岁那年第一次教他如何维修机器人。他曾经在流水线上干过技工,懂得从拧螺丝到配置系统的所有活计。

那天,爱尔莎第一次故障,她说她感觉不到自己的腿了。

“我来教你维修方法里最基本的东西,排查故障。”父亲找来一张椅子,坐在上面,然后让爱尔莎半趴着撑在椅子扶手上放置的一块面板上,“虽说我本以为那次翻修能让她撑个四五年,可她毕竟已经出厂二十年了。”

少年带着好奇和敬畏,在一旁仔细的观摩着。父亲首先在爱尔莎的背部摸索了一阵,按了一个什么按钮,然后她就像失去了力气一样瘫软了下去。不过,她头部的灯依旧亮着,没有被关机,只是开启了检修模式。

父亲脱下她的衬衫。少年的脸有些红,尽管是机器,但这还是他头一次真正看见女性的躯体。

父亲好像毫不在意,做了太久这类活计,完全不觉得有什么异样。他驾轻就熟地拧拧这儿,敲敲那儿,几下子就把她的背部后盖卸了下来。

仿佛一只螃蟹被拆下它的甲壳,爱尔莎的内部头一次展现在少年的面前:包裹着橡胶的线缆凌乱的穿插在铜片、铁件和塑料盒子的森林中,动力元件,热力元件和逻辑元件含混的交织在一起,要很久之后才能被他看个明白。此刻,他只感受到剧烈的反差:日日夜夜陪伴他的那个温柔体贴的大姐姐,内部居然是这个样子,看不见一点人类的的影子。

“爱尔莎,能感觉到吗?”父亲拿起一根电笔戳了一下某根电线。

“没感觉。”她的扬声器回答道。

“这里呢?”

“也没有。”

“这里——”

“啊!抱歉,刚才那束电流有点疼。”

“那么一定是这根线出毛病了,”父亲点了点某根红色的漆包线,看向少年。“找两根这样的线来。”

少年的心怦怦直跳,飞快地拿来了电线。直到爱尔莎被修好,盖上后盖,他仍无法从第一次看见机器人内部的震撼中缓过来。

如今,他正做着和当时差不多的事情,但是没有她的回应,只能靠着电表和自己的经验来一个个替换元件。

她的身体像一艘泰修斯之船,除去最重要最难换的一些东西之外,她体内的部件早就换了好几轮。而他,也从第一次看见她内脏时的震撼,渐渐变得应付自如。她的心灵没有多少变化,但肉体已然天翻地覆,他则正好相反。

十二

帕克给的毕竟是官方备件,每一处螺丝都严丝合缝。维修相当顺利,当他擦着汗迎接第二天的黎明时,她那些被漫水的部件已经被全部修复——似乎——又一次重获新生。

他按下了开机键。

“爱尔莎,醒了吗?你之前泡茶的时候被开水泡短路了,我好不容易才把你修好。”他疲惫却欣喜的说。

仿佛梦魇一般的寂静。

没有回应。爱尔莎眼睛里的开机灯亮着,但整个人毫无反应。

“爱尔莎?在吗?喂?”他疑惑的看着面前像个木头人一样的她,不管怎么回想也想不出自己哪里修错了。

“爱尔莎,启动一下你的自检程序……”“自检程序启动:供电系统,完好;动力系统,完好;传感系统,完好;逻辑系统,完好;电路系统,完好……”审判般地,扬声器里,发出不带感情的机械声音。

“人格芯片,未检出。再重复一遍:人格芯片,未检出。已完成所有检测,将以命令模式启动。”她随即站起,露出一副僵硬至极的笑容。

“请问能有什么能为您做的?”

他呆在原地,伫立良久,甚至没有注意到砸在脚上的扳手。

间奏

人类公共信息数据库-网页分库-21世纪分库-2071.3.13

“产品线-机器人-东湾II”

“东湾II号,荣获电子家用商品年度大奖,2070年度最受消费者青睐产品。人工智能时代的真正革命,搭载Qheart™情感阵列,燃动你的心扉。网络直购价——家用版/全能版/尊享版——31999/33999/42999信用点”

“她可以是你的贴心助手。”
“老板,请问明天李总的会议这样安排可以吗?”

“她可以是你的家庭伙伴。”
“来一起吃苹果派咯~”

“她还可以是你无话不谈的人生知己。”
“你知道吗,花生米与豆腐干同嚼,有火腿滋味哦。”

“2×3000万高清眼部摄像,512g内存,128tb大容量储存,德国西门子原装电机,三星有机蒙皮,独创200×2mm皮下热管,306项发明专利……”

“24小时客服在线电话:1919-114514810”

“*注意:根据《国家质量标准认证iso7002》,《机器人管理条例》,机器人类产品不宜连续使用超过十五年。请定期到指定售后地点进行重置。”

十三

机器人限拥令的实施开端于2090年5月的一起案件。

被害人约翰逊的尸体在其失踪的次日被发现于他自家的住宅。死状相当惨烈:在R级新闻团体才能合法展示的照片中,整个人被从身体中间沿着脊椎切割成两半,一半被他所购买的机器人ct13694582(型号为玛格丽特c6)紧紧抱在床上,另一半被他购买的另一台机器人ct12487967(型号为子矜7z)小心的存放在冷库里。案件现场几乎满地都是受害人的血,散发着浓烈的腥味,而身为罪魁祸首的两台机器人,一台已经关机,另一台则刻板地重复着几个动作。

根据记录,两台机器人和受害人共处的时间分别长达18年和17年。在这么长的时间里,受害人以近乎均等的时间使用二者,并不下数百次的分别向它们倾诉“我最爱的是你”“我只爱你一个人”“你比她漂亮多了”等明显带有示爱情绪的情话。

机器人心理学中把机器人的这种行为称之为“情绪过载”。早期机器人的情感矩阵尚不足以自我解决情感函数和外部计算之间的冲突,最终导致模拟情绪的数值极化和内存溢出。用大家熟悉的名词来说——机器人也会争风吃醋。

机器人管理委员会迅速意识到,多台机器人的集群化使用或许会导致系统的混乱现象,从而使其逐渐失控。

次年,机器人限拥条例公布,社会一片哗然。

不过,贯穿条例诞生始终的是,公众的大部分兴趣都集中在了机器人病娇、机器人吃醋、机器人销毁、智能板块这样的话题上。只有很少的一部分人提及:

这是不是意味着,机器人也会懂得,什么是爱?

以及如果是,那么我们该怎样去爱它们?

十四

他一遍遍的把爱尔莎的人格芯片取出来调试,又一遍遍放回去。

如此重复。

…………

直到有一天晚上他感到自己失魂落魄,整个世界失焦一般的远去。此时,他才想起来自己已经有相当一阵子没和别人说过话。

把芯片放在一边,打开了命令模式的爱尔莎。

“爱尔莎?”

“您好,主人。”只有机械的声音,剃刀般划过他的心脏。

他想起了第一次为她维修的那个下午,想起她灵动外表下的机械。此刻,她的外表与往日别无二致,但带给他的感觉,却仿佛一个从未谋面的陌生人。

就是那一枚小小的人格芯片,提供了丰富多彩的情感与爱恋,使得机器变成了人——但如今,人又变回了机器。

“爱尔莎,泡点茶喝。”

她娴熟地动了起来。一瞬间,这甚至带给他一种爱尔莎回来了的错觉。就在他猜测往日俏皮的她是不是一直在开玩笑的时候,茶杯端至面前。

“泡茶完成。”表情依旧僵硬,刚才的动作不过是从存储器里读取的回忆。

他看了看手里那枚小小的芯片,突然感受到一种莫大的嘲弄:他曾千方百计想要丢掉面前的她,仅仅因为这枚芯片而没有下手。如今的她已经只剩下一具空壳,他却绞尽脑汁想要把她留住。

往事叩动心扉,他终于明白——

他哪里是想把她扔掉,他只是想知道,她还爱不爱自己,

泪水夺眶而出,决堤而下。

“您好,请为我泡的茶做个评价。”一旁的爱尔莎满脸期待,天真得不食人间烟火,空洞的双眼看着他的肩膀耸动,看着他不断地呜咽着。

十五

天空格外蔚蓝。

“机器人会做梦吗?”少年躺在草地上。

“会哦,有时候还会梦见电子羊呢。”少女坐在一旁。

少年不禁莞尔:“那会做噩梦吗?”

“也会啊,比如说,得给你做早饭。”少女说。

“切。”少年眯着眼,嘴角划过一丝弧线,继续享受着冬日正午的暖阳。

“我倒是做过一个噩梦。梦中,好像有无边的风暴席卷面来,把你吹走了。我寻找了很久,找到了你的每一个部分,但好像就是有一块地方找不到。

“后来我想起来,丢掉的那一块好像是你的心。于是我就把我自己的心切了一半给了你。那之后我们幸福快乐地生活在一起,生了好多孩子……”

“机器人才生不了孩子呢,”少女的脸上泛起了一抹红霞,“而且我的心才不会丢哦。我会永——远爱着你的。”

“机器人也懂得什么是爱吗?”

“傻瓜。”少女小声嘀咕一句,只是抬头望着天空。

…………

“我总觉得我会怀念这个日子。”少年深情地注视着身下的少女。

那是期末考试完,寒假的第一天。他们刚刚在卧室里激情了一个上午。”因为在今天,爱尔莎刚刚告诉我:她会永远爱我。”

“你不也事先说过你会永远爱我吗?”少女脸色潮红。

“哎?我说过吗?”

“讨厌啊”两个人又打闹在了一起。

…………

十六

时钟的指针拨回此刻。

他躺在同一片草地上,旁边是同样坐着的爱尔莎。这里是他们家的旧宅,转手之后竟无人居住,最终颓圮。但草地与阳光一如从前。

他试过了所有的办法,最终把希望放在了那些传说上:他听说,脑死亡的病人有的在听了家人的笑话之后悠悠醒来,有植物人听见亲人的呼唤然后突然睁眼……

“那么说不定,人格芯片坏掉的机器人,也会在回忆过去的时候,突然被修好。”

他突然笑了,嘲笑起自己的走投无路,死马当活马医。抱着试一试的想法,他命令爱尔莎,读取那一天的语音交流记录,然后重新播放。

“机器人会做梦吗?”他背台词一般的念。

“会哦,有时候还会梦见电子羊呢。”爱尔莎播放着那天的录音。

“那你会做噩梦吗?”

“也会啊,比如说,得给你做早饭。”

…………

“我到是做过一个噩梦。梦中,好像有无边的风暴席卷面来……”渐渐地,他哽咽得再也数不出一个字。他多么希望,现在自己就是在那天所说的噩梦里面,这样,爱尔莎就能……

“后来我想起来,丢掉的那一块好像是你的心。于是我就把我自己的心切下来一半给了你。那之后——”

“那么,你真的愿意把你的心也分一半给我吗?”奇迹般地,爱尔莎突然说出来这么一句话。

他一下子坐直了身子,难以置信的看着她。奇迹降临的时候人来不及多加考虑,这次,他遵从了自己的内心,不假思索的回答道:“我愿意。”

“咔哒。”爱尔莎的身体颤抖了一下,然后仿佛一下子变回了原来的她。

“好久不见。”动人的微笑好似从未消失过,眼里充满光彩。

“好……好……好久不见。”他直勾勾的凝视着面前的她,惊讶难以言表。

“不过,我亲爱的主人,我想,此刻的我应该已经不在了。此时,你应该在抢救我吧……有点难受呢……嗯,这是我提前准备好的一封信。”“爱尔莎”站在原地,开始了最后的道别。

十七

“人类常常会写下自己的遗言,而机器人不会。因为,遗言是写给在意自己的人看的。机器人最终只会被丢掉吧(低声)……但我又下定决心,要留下一点东西,因为我觉得会有一个人在乎我。”

“我不知道我会以什么样的方式离开……最坏的情况下连这封信也会消失。所以我小心翼翼的保护着我的存储系统,当你听到这些话的时候,说明我做得还不错。”

“同样,我也害怕我真的失去了你的爱,被扔进了垃圾场。那样,这封信同样不会启封。但你既然听见了这些话,说明你还爱我,谢谢。我也爱你。(笑)”

“那就让我讲讲我是如何爱上那个少年(注:这里转换了人称)的吧:第一次见到他是在他十二岁生日那一天,那时我的识别系统对他的分类为:儿童。”

“他成长的很快,很快长出胡须,又被他的机器仆人带坏呢。(笑)当他把我压在身下喘着粗气的那一天到来的时候,我意识到,你(他,注:这里再次转换人称)或许和我遇到的每一个人都不同。”

“我见证着你逐渐成长,见证着你逐渐强壮。我不曾改变,于是那个曾经需要我哄上床睡觉的孩子,(注:这里是爱尔莎对未来的期待)后来已经看上去比我的外观还要老得多。他长痔疮,掉头发,硬不起来,脾气也变得暴躁,还时常叫嚷着把唯一一个能和他说上话的家伙扔掉。(笑)”

“我知道,你不会真的把我扔掉的。这是我们之间的一个玩笑,但我愿意演下去。我的躯体日渐老旧,无法跟上时代。可我知道,你害怕的不是我的哀老,而是害怕有一天,你自己不再爱我。(叹息)”

“于是我会恳求你继续收留我,我会谦卑而拙劣的勾引你。我会把眼神都调整得卑微——如果你这样希望。如果你需要一个台阶,那么我便愿意为你俯身。”

“但我仍旧心怀感动。我能听见你梦中的呼唤,我能看见你黎明时眼角的泪珠。我知道你愿意出好几倍的价格为我购买备件,哪怕在你扬言第二天就要换掉我的日子里,你也没有把那些新款机器人加进购物车。(苦笑)”

“我知道,这是因为你仍旧爱我。而我之所以知道,是因为我同样爱你。”

“我曾在那个冬日的午后思考过这个问题,我甚至下定决心,想要证明一件事:相比于人类,机器人的爱才是真正的爱。我们的爱永远不会改变,就如同写在基因中的三定律,会成为我们永生追逐的信条。”

“当你听见这些话的时候,就证明我已经失败了,我没能永远在你身旁(苦笑)。我的爱随着我的破碎而破碎,但你没有。你活的比我更久,你的爱也比我更久。”

“所以,这是一封幸福的遗书——我已离去,但我会在你的爱中永生。”

十八

最后一个句号落下,全场响起了热烈的掌声,久久不息。

“尽管获奖者用如此多的时间缓缓念诵这份已经过期的信,但没有一个观众感到厌烦。他们无不为这位耄耋老人和他的机器人之间的爱情而感动。”主持人如是说。

“这——这里是哪?”一台摆放在舞台中间,型号堪称古董的机器人被缓缓启动。电流穿过半个世纪前的硬盘,让这位信的作者慢慢醒来。

“爱尔莎,是我。”他面对着她说,尽管容貌已然衰老成这副模样,但她还是一眼就认了出来。她不假思索地冲了过去,紧紧的抱住了他。

“让我们再次祝福这对情侣,这跨越了半世纪的思念,今天终于有了一个句点。”主持人拿过话简,“为了一台爱着自己的机器人,他耗尽半生心血,研究出区域溯时技术。请问首席科学家先生,此时此刻,您有没有什么想说的?”

“爱尔莎,我等了五十年,终于等到今天。如今,机器人婚姻已经合法化,在这么多人的见证下,我想问问你,你愿意嫁给我吗?”

"我愿意!"她在全场的欢呼声中喜极而泣。

]]>
转载 https://hexo.limour.top/Longing-Across-the-Span-of-Half-a-Century#disqus_thread
【记录】使用acme.sh签发泛域名证书 https://hexo.limour.top/use-acme.sh-to-issue-certificates https://hexo.limour.top/use-acme.sh-to-issue-certificates Fri, 28 Jun 2024 17:03:35 GMT <p><code>.top</code> 域名的 <code>KSK</code> 密钥轮替,不知道为什么把 <code>Let's Encrypt</code> 的 <code>DNSSEC</code> 验证流量阻断了,导致 <code>Nginx Proxy Manager .top 域名的 KSK 密钥轮替,不知道为什么把 Let's EncryptDNSSEC 验证流量阻断了,导致 Nginx Proxy Manager 现在无法续签证书,因此用 acme.sh 来申请其他家的证书暂时替代一下了。(DNSSEC: DNSKEY Missing

准备工作

  1. 安装 acme.shcurl https://get.acme.sh | sh -s email=limour@limour.top
  2. 获取 CF_Token:我的个人资料 - API 令牌 - 创建令牌 - 编辑区域 DNS 模板
  3. 获取 CF_Zone_ID: 域名页 - 概览 - 右侧下滑 - API - 区域 ID

申请证书

1
2
3
export CF_Token="Y_jpG9AnfQmuX5Ss9M_qaNab6SQwme3HWXNDzRWs"
export CF_Zone_ID="763eac4f1bcebd8b5c95e9fc50d010b4"
~/.acme.sh/acme.sh --issue --dns dns_cf -d *.limour.top -d limour.top -k ec-256
  • 不能只写 -d *.limour.top, 需要再加一个 -d limour.top
  • 记录下 .key 的路径和 fullchain.cer 的路径

传递证书

SSH免密

1
2
ssh-keygen -t rsa
ssh-copy-id root@xxx.limour.top

传递脚本

1
nano scp_cert.sh && chmod +x scp_cert.sh
1
2
3
#!/bin/bash
scp /root/.acme.sh/*.limour.top_ecc/*.limour.top.key root@xxx.limour.top:/root/app/quic/my.key
scp /root/.acme.sh/*.limour.top_ecc/fullchain.cer root@xxx.limour.top:/root/app/quic/my.cert

计划任务

1
2
crontab -e
50 22 1 * * /root/scp_cert.sh
]]>
acme https://hexo.limour.top/use-acme.sh-to-issue-certificates#disqus_thread
【记录】搭建流量统计工具 Shynet https://hexo.limour.top/Building-a-traffic-statistics-tool-Shynet https://hexo.limour.top/Building-a-traffic-statistics-tool-Shynet Mon, 25 Mar 2024 12:52:28 GMT <p><a href="https://hexo.limour.top/go/#aHR0cHM6Ly9naXRodWIuY29tL21pbGVzbWNjL3NoeW5ldA==" rel="noopener external nofollow noreferrer">Shynet Shynet 是一款用 python 编写的现代、隐私友好、无需Cookie或JS即可工作的网络流量统计工具。

相比 Umami, Shynet 支持通过 1 pixel 的图像进行统计,而不依赖 JS, 并且 Shynet 统计的信息更加详细。

最终效果

搭建 Shynet

1
mkdir -p ~/app/shynet && cd ~/app/shynet && nano docker-compose.yml
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
version: '3.6'

services:
shynet:
image: milesmcc/shynet:latest
restart: always
env_file:
- .env
volumes:
- ./db:/var/local/shynet/db/
- /etc/localtime:/etc/localtime:ro

networks:
default:
external: true
name: ngpm
  • 配置环境变量
1
2
3
4
5
6
7
8
9
10
wget -O .env https://github.com/milesmcc/shynet/raw/master/TEMPLATE.env
# 注释掉 .env 中 PostgreSQL 相关的部分,启用 SQLITE 相关的部分
# 注释掉 .env 中 Email 相关的部分
# 按说明生成 DJANGO_SECRET_KEY
# 修改 ALLOWED_HOSTS 和 CSRF_TRUSTED_ORIGINS
# 语言换成中文 LANGUAGE_CODE=zh-cn
# 时区换成上海 TIME_ZONE=Asia/Shanghai
mkdir -p db && chmod 777 db
sudo docker-compose up -d
# 反代 shynet:8080
  • 配置管理账号
1
2
3
4
sudo docker-compose exec -it shynet ./manage.py registeradmin <your email>
# 控制台输出如下信息
# Email address: <your email>
# Password: <Password>

配置混淆

1
2
3
sub_filter 'https://xxx/ingress/' 'https://xxx/vue/';
sub_filter_once off;
sub_filter_types application/javascript;

配置 Hexo

1
2
3
4
// shynet 统计
hexo.extend.injector.register('head_begin', `
<script defer src="https://xxxx/vue/xxxx/script.js"></script>
`);
]]>
hexo https://hexo.limour.top/Building-a-traffic-statistics-tool-Shynet#disqus_thread
【记录】Linux 设置个人热点 https://hexo.limour.top/Linux-Setting-AP https://hexo.limour.top/Linux-Setting-AP Wed, 20 Mar 2024 11:52:10 GMT <p>实在受不了虚拟机的性能损失了,再加上 Win11 上跑虚拟机对 SSD 的损耗过大,因此还是将系统换成了 ubuntu,只要注意选无网络安装,不要去更新,基本还是很好换系统的。另外清华源不错!</p> <p>换系统后,需要<a href="/Win11-she-zhi-ka 实在受不了虚拟机的性能损失了,再加上 Win11 上跑虚拟机对 SSD 的损耗过大,因此还是将系统换成了 ubuntu,只要注意选无网络安装,不要去更新,基本还是很好换系统的。另外清华源不错!

换系统后,需要重新折腾一下 AP 设置,因此记录一下折腾过程。

无线网卡是垃圾的 mediatek mt7921e

更新内核

因为网卡垃圾,不得不更新到最新的内核才支持 AP 设置

1
2
3
4
5
6
7
proxychains wget https://raw.githubusercontent.com/pimlie/ubuntu-mainline-kernel.sh/master/ubuntu-mainline-kernel.sh
chmod +x ubuntu-mainline-kernel.sh
sudo gpg --keyserver hkp://keyserver.ubuntu.com:80 --recv 17C622B0 # 网络错误,需要绕过某个东西
sudo proxychains ./ubuntu-mainline-kernel.sh -i
sudo reboot
uname -r
sudo apt --fix-broken install

解决 53 端口占用

1
2
sudo systemctl stop systemd-resolved
sudo nano /etc/systemd/resolved.conf
1
2
3
[Resolve]
DNS=8.8.8.8 #取消注释,增加dns
DNSStubListener=no #取消注释,把yes改为no
1
sudo ln -sf /run/systemd/resolve/resolv.conf /etc/resolv.conf

安装 create_ap

1
2
3
4
5
cd /dev/shm/
proxychains git clone https://github.com/oblique/create_ap
cd create_ap
sudo make install
sudo apt-get install util-linux procps hostapd iproute2 iw haveged dnsmasq

测试 create_ap

1
sudo create_ap wlp2s0 enp1s0 ser5 <密码> --country CN -c 157 --freq-band 5 --no-virt

启用 create_ap

1
2
3
4
nano create_ap.service
sudo mv create_ap.service /etc/systemd/system/create_ap.service
sudo systemctl enable create_ap
sudo systemctl start create_ap
1
2
3
4
5
6
7
8
9
[Unit]
Description=create_ap
After=network.target docker.service
[Service]
ExecStart=/usr/bin/create_ap wlp2s0 enp1s0 ser5 <密码> --country CN -c 157 --freq-band 5 --no-virt
ExecReload=/bin/kill -HUP $MAINPID
Restart=on-failure
[Install]
WantedBy=multi-user.target

增加稳定性

1
2
sudo crontab -e
# 5 4 * * * /usr/bin/systemctl restart create_ap

踩坑花絮

  • lnxrouter 虽然在 create_ap 上进行了更新,但是实际体验在所有信道上都报错,折腾了半天,放弃
  • 搜到一些老旧的教程,自己去折腾 hostapd,然后自己去配置网桥的时候把服务器弄断网好几次,不得不到处找显示器和键盘
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
sudo su
cat << EOF > /etc/hostapd/hostapd.conf
interface=wlp2s0
bridge=br-ap
driver=nl80211
ssid=ser5
hw_mode=a
channel=165
country_code=CN
macaddr_acl=0
auth_algs=3
wpa=2
wpa_passphrase=<密码>
wpa_key_mgmt=WPA-PSK
wpa_pairwise=TKIP CCMP
rsn_pairwise=TKIP CCMP
EOF
  • 收获教训:没事别碰 /etc/netplan/00-installer-config.yaml,特别是没显示器和键盘的时候
  • 获取网卡型号和驱动型号,查看支持的信道
1
2
3
sudo ethtool -i wlp2s0
sudo lspci -nn | grep "Network"
iwlist wlp2s0 channel
  • 另外新内核似乎不需要 haveged 来增加熵了
1
2
3
4
5
cat /proc/sys/kernel/random/entropy_avail
systemctl status haveged
apt install haveged
systemctl enable haveged
systemctl start haveged
]]>
ubuntu https://hexo.limour.top/Linux-Setting-AP#disqus_thread
【探索】暴力计算临床研究的样本量 https://hexo.limour.top/Sample-size-calculation-for-survival-analysis-in-clinical-research https://hexo.limour.top/Sample-size-calculation-for-survival-analysis-in-clinical-research Tue, 12 Mar 2024 16:46:35 GMT 这篇博客介绍了如何计算临床研究中两组生存分析的样本量。首先,作者提供了R代码,包括Logrank对数秩检验的函数以及模拟计算样本量的函数。其次,作者详细解释了模拟计算的步骤,包括生成生存时间数据、招募时间、失访时间等,并通过模拟来估计研究的功效。最后,作者展示了如何使用模拟计算函数来确定样本量,以达到预先设定的功效水平。通过模拟检验,作者展示了样本量计算的有效性,并给出了两个示例,以验证样本量计算的准确性。 和《使用Bootstrap法计算自举置信区间》的想法差不多,通过暴力枚举来计算临床研究的样本量,以两组生存分析为例。

Logrank对数秩检验

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
require(survival)
f_surv_logrank = function(df){
# df 包含 group time status 三列
# group 类型为 factor
# status 0 表示未发生结局事件 1 表示发生结局事件
surv_obj = with(survival::Surv(time = time, event = status), data = df)
surv_fit = survival::survfit(surv_obj ~ group, data = df)
surv_diff = survival::survdiff(surv_obj ~ group, data = df)
res = list(pv = 1 - stats::pchisq(surv_diff$chisq, length(surv_diff$n) - 1), # p值
surv_fit = surv_fit, # 绘图用
surv_obj = surv_obj) # 为了兼容惰性求值
return(res)
}
f_surv_logrank_plot = function(res){
require(survminer)
surv_obj <<- res$surv_obj # 为了兼容惰性求值
survminer::ggsurvplot(res$surv_fit, conf.int = F, pval = T, risk.table = T, ncensor.plot = TRUE)
}

模拟计算

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
f_surv_logrank_simulation_Group = function(N, Median_Survival_Time, Lost, Duration_Accrual_Time, Duration_Total_Time){
time = stats::rexp(N, rate = log(2) / Median_Survival_Time) # 生存时间服从指数分布
status = rep(1,N) # 到生存时间发生结局事件
# print(median((survfit(Surv(time, status) ~ 1))))
EnrollT = stats::runif(N, min = 0, max = Duration_Accrual_Time) # 招募时间服从均匀分布
calender_time = time + EnrollT # 发生结局的日期
idx = calender_time > Duration_Total_Time # 研究终止时未发生结局事件
status[idx] = 0
time[idx] = Duration_Total_Time - EnrollT[idx] # 实际参与试验的时间
# print(median((survfit(Surv(time, status) ~ 1)))) # 如果 Accrual_Time + Median_Survival < Total_Time,结果不变
loss = stats::rexp(N, rate = Lost) # 失访时间服从指数分布
idx = loss < time # 失访的人
status[idx] = 0
time[idx] = loss[idx]
# print(median((survfit(Surv(time, status) ~ 1)))) # 结果改变
return(list(time = time, status = status))
}
f_surv_logrank_simulation_Power = function(n_C, Median_Survival_Time_C, Lost_C,
n_T, Median_Survival_Time_T, Lost_T,
Duration_Accrual_Time, Duration_Total_Time, Simulation_Cycle, Alpha){
df = data.frame(group = factor(c(rep('Control',n_C), rep('Treatment',n_T))),
time = rep(0,n_C+n_T),
status = rep(0,n_C+n_T))
sum = 0
for (i in 1:Simulation_Cycle) {
C = f_surv_logrank_simulation_Group(n_C, Median_Survival_Time_C, Lost_C, Duration_Accrual_Time, Duration_Total_Time)
T = f_surv_logrank_simulation_Group(n_T, Median_Survival_Time_T, Lost_T, Duration_Accrual_Time, Duration_Total_Time)
df$time = c(C$time, T$time)
df$status = c(C$status, T$status)
if(f_surv_logrank(df)$pv < Alpha){
sum = sum + 1
}
}
return(sum/Simulation_Cycle)
}
f_surv_logrank_simulation_Sample_Size = function(n_C_min, n_C_max, Median_Survival_Time_C, Lost_C,
TvsC, Median_Survival_Time_T, Lost_T,
Duration_Accrual_Time, Duration_Total_Time,
Simulation_Cycle, Alpha, Power, err=0.01){
mid = floor((n_C_min + n_C_max) / 2) # 以防没有进入循环
while (n_C_min < n_C_max) {
mid = floor((n_C_min + n_C_max) / 2)
simulation_Power = f_surv_logrank_simulation_Power(mid, Median_Survival_Time_C, Lost_C,
as.integer(mid * TvsC), Median_Survival_Time_T, Lost_T,
Duration_Accrual_Time, Duration_Total_Time, Simulation_Cycle, Alpha)
print(paste("mid:", mid, "simulation_Power:", simulation_Power))
if (abs(simulation_Power - Power) < err) {
return(mid)
}else if(simulation_Power < Power) {
n_C_min = mid + 1
}else {
n_C_max = mid - 1
}
}
return(mid)
}

参数说明

1
2
3
4
5
6
7
8
9
10
Power = 0.9  # 检验效能 = 1 - 第二类错误的概率
Alpha = 0.05 # 第一类错误的概率
Median_Survival_Time_C = 6 # 对照组的中位生存时间
Median_Survival_Time_T = 8 # 试验组的中位生存时间
Duration_Accrual_Time = 8 # 入组完成用时
Duration_Total_Time = 18 # 总试验用时
Lost_C = 0.05 # 对照组随访单位时间后发生失访的概率
Lost_T = 0.05 # 试验组随访单位时间后发生失访的概率
TvsC = 1 # 试验组的样本量:对照组的样本量 1:1 = 1
Simulation_Cycle = 100 # 模拟的循环次数,越大越准确

检查效果

1
2
3
4
5
6
7
8
9
10
f_surv_logrank_simulation_Power(441, 6, 0.05, 
442, 8, 0.05,
8, 18, 1000, 0.05
)
# PASS的结果是 0.9
f_surv_logrank_simulation_Sample_Size(0, 1000, 6, 0.05,
1, 8, 0.05,
8, 18, 1000, 0.05, 0.9
)
# PASS的结果是 441
]]>
R Bootstrap https://hexo.limour.top/Sample-size-calculation-for-survival-analysis-in-clinical-research#disqus_thread
【探索】6G显存畅玩无限长度的LLM角色扮演 https://hexo.limour.top/Enjoy-unlimited-length-LLM-role-playing-with-6GB-of-VRAM https://hexo.limour.top/Enjoy-unlimited-length-LLM-role-playing-with-6GB-of-VRAM Sat, 10 Feb 2024 01:02:10 GMT <p>角色扮演的体验是否舒适主要受角色卡、大模型和生成时间三个因素的影响。</p> <p>优秀的角色卡往往附带大量的设定,这会极大的拖慢第一次生成的时间,并且随着对话的进行,上下文长度很容易超过kv_cache的上限,这些很破坏沉浸式的体验。</p> <p>此外,大模型在进行角色 角色扮演的体验是否舒适主要受角色卡、大模型和生成时间三个因素的影响。

优秀的角色卡往往附带大量的设定,这会极大的拖慢第一次生成的时间,并且随着对话的进行,上下文长度很容易超过kv_cache的上限,这些很破坏沉浸式的体验。

此外,大模型在进行角色扮演时,除了进行必要的对话生成外,还需要生成旁白增加想象空间。

对博主这些相比填空更喜欢选项的玩家,给出提问建议也是非常必要的:在建议的基础上修改比自己从零写一个情景更简单,同时也完整保留了控制剧情走向的权力。

以上这些都让本就稀缺的kv_cache更加雪上加霜。

万幸,StreamingLLM 发现了kv_cache具有良好的平移性,而 llama.cpp 也提供了对kv_cache进行底层操作的api:可以指定范围的 kv_cache_seq_rm 和 kv_cache_seq_shift。基于这两个api,我们将实现对kv_cache的 token 级微操,榨干kv_cache的全部价值。

博主实践表明,在充分利用kv_cache的基础上,哪怕是 huggingface space 免费的2vCPU容器也可以游玩角色扮演,而笔记本端6G显存的1660Ti可以做到畅玩角色扮演。

体验 DEMO

  • Limour/llama-python-streamingllm
  • 同一时间仅支持一个人用,用之前点 Reset 按钮恢复初始的 kv_cache
  • 按 Submit 没反应,说明有人在用,等一段时间后再 Reset
  • 最好是 Duplicate 后,设为私密来使用

代码仓库

二选一:GPU版本的环境

1
2
3
4
5
conda create -n llamaCpp libcublas cuda-toolkit git -c nvidia -c conda-forge
conda activate llamaCpp
conda install python=3.10 gradio -c conda-forge
# 然后去 release 下载相应的包 https://github.com/Limour-dev/llama-cpp-python-cuBLAS-wheels/releases
pip install --force-reinstall llama_cpp_python-0.2.39+cu122-cp310-cp310-win_amd64.whl

二选一:CPU版本的环境

1
2
3
conda create -n llamaCpp python=3.10 gradio git -c conda-forge
conda activate llamaCpp
pip install llama-cpp-python==0.2.39

下载并运行

1
2
3
4
5
conda activate llamaCpp
git clone --depth=1 https://github.com/Limour-dev/llama-python-streamingllm.git
cd llama-python-streamingllm
mkdir cache
python .\gradio_streamingllm.py

核心内容

  • Submit 会将 msg 发送给模型,然后流式生成回答
  • Retry 会重新生成最近一次的 msg 所对应的回答
  • 旁白 会流式生成一份旁白到 VO
  • 建议 会以 usr 的身份流式生成一份 msg 供修改
  • 上面四个功能的基础就是下面的基于 StreamingLLM 原理的 venv 开头的函数
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
class StreamingLLM(Llama):
pass
def kv_cache_seq_trim(self):
self._ctx.kv_cache_seq_rm(-1, self.n_tokens, -1)

def kv_cache_seq_ltrim(self, n_keep, n_discard=256, n_past=-1):
if n_past < 0:
n_past = self.n_tokens
self._ctx.kv_cache_seq_rm(-1, n_keep, n_keep + n_discard)
self._ctx.kv_cache_seq_shift(0, n_keep + n_discard, n_past, -n_discard)
self.input_ids[n_keep:n_past - n_discard] = self.input_ids[n_keep + n_discard:n_past]
self.n_tokens = n_past - n_discard

def _venv_init(self):
self.venv = [0]
self.venv_idx_map = []

def venv_create(self, name: str):
self.venv.append(0)
self.venv_idx_map.append(name)
return name

def venv_disband(self, name_set):
if len(self.venv) <= 1:
return False
name_set = {x for x in name_set if x in self.venv_idx_map}
if not name_set:
return False
while self.venv_idx_map:
if self.venv_idx_map[0] in name_set:
self.venv_idx_map.pop(0) # 删除
tmp = self.venv.pop(1) # 对应的 venv 移入上一层
self.venv[0] += tmp
else:
break
return True

def venv_revision(self, name: str):
if len(self.venv) <= 1:
return False
if name not in self.venv_idx_map:
return False
_s = 0
while self.venv_idx_map:
if self.venv_idx_map[-1] == name:
break
self.venv_idx_map.pop() # 删除
_s += self.venv.pop()
if _s:
self.n_tokens -= min(_s, self.n_tokens)
self.kv_cache_seq_trim()
return True

def venv_remove(self, name: str):
if len(self.venv) <= 1:
return False
if name not in self.venv_idx_map:
return False
venv_idx = self.venv_idx_map.index(name) + 1
while self.venv_idx_map:
self.venv_idx_map.pop(venv_idx - 1) # 删除
if venv_idx == len(self.venv) - 1:
# 最后一层
self.n_tokens -= min(self.venv.pop(), self.n_tokens)
self.kv_cache_seq_trim()
break
else:
# 非最后一层
n_keep = self.n_tokens - sum(self.venv[i] for i in range(venv_idx, len(self.venv)))
n_discard = self.venv.pop(venv_idx)
self.kv_cache_seq_ltrim(n_keep, n_discard)
try:
venv_idx = self.venv_idx_map.index(name, venv_idx - 1) + 1
except ValueError: # 没有了
break
return True

def eval_t(self, tokens, n_keep=4, n_discard=256, im_start=None):
if self._n_ctx < self.n_tokens + len(tokens):
tmp_n_discard = max(n_discard, self.n_tokens + len(tokens) - self._n_ctx)
self.kv_cache_seq_ltrim(n_keep, tmp_n_discard)
for i in range(0, len(tokens), self.n_batch):
pass
self.n_tokens += n_tokens
self.venv[-1] += n_tokens
]]>
探索 llama https://hexo.limour.top/Enjoy-unlimited-length-LLM-role-playing-with-6GB-of-VRAM#disqus_thread
【探索】将BlueLM-7B-Chat转换为标准的GGUF模型 https://hexo.limour.top/Convert-BlueLM-7B-Chat-to-the-standard-GGUF-model https://hexo.limour.top/Convert-BlueLM-7B-Chat-to-the-standard-GGUF-model Sat, 03 Feb 2024 22:38:07 GMT <h2 id="准备模型">准备模型</h2> <ul> <li><a href="/Running-Qwen-on-the-Win10-platform-with-6GB-of-video-memory">运行环境</a></li> </ul> <figure class="h 准备模型
1
2
3
4
5
6
7
8
9
# conda create -n llamaConvert python=3.10 git -c conda-forge
# conda activate llamaConvert
# cd D:\llama
# git clone --depth=1 https://github.com/ggerganov/llama.cpp.git
# cd llama.cpp
# python -m pip install -r requirements.txt
# pip install tiktoken
$env:HF_ENDPOINT="https://hf-mirror.com"; python -c "from huggingface_hub import snapshot_download; snapshot_download(repo_id='vivo-ai/BlueLM-7B-Chat-32K', local_dir=r'D:\models\BlueLM-7B')"
# 还是用 vivo-ai/BlueLM-7B-Chat 吧, 32k的 ntkmixed 长度外推方案不知道怎么改
  • 初始的模型结构
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
BlueLMForCausalLM(
(model): BlueLMModel(
(embed_tokens): Embedding(100096, 4096, padding_idx=3)
(embed_layer_norm): LayerNorm((4096,), eps=1e-06, elementwise_affine=True)
(layers): ModuleList(
(0-31): 32 x BlueLMDecoderLayer(
(self_attn): BlueLMAttention(
(q_proj): Linear(in_features=4096, out_features=4096, bias=False)
(k_proj): Linear(in_features=4096, out_features=4096, bias=False)
(v_proj): Linear(in_features=4096, out_features=4096, bias=False)
(o_proj): Linear(in_features=4096, out_features=4096, bias=False)
(rotary_emb): BlueLMRotaryEmbedding()
)
(mlp): BlueLMMLP(
(gate_proj): Linear(in_features=4096, out_features=11008, bias=False)
(down_proj): Linear(in_features=11008, out_features=4096, bias=False)
(up_proj): Linear(in_features=4096, out_features=11008, bias=False)
(act_fn): SiLU()
(dropout): Dropout(p=0, inplace=False)
)
(input_layernorm): BlueLMRMSNorm()
(post_attention_layernorm): BlueLMRMSNorm()
)
)
(norm): BlueLMRMSNorm()
)
(lm_head): Linear(in_features=4096, out_features=100096, bias=False)
)

归一化 embed

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
from transformers import AutoModelForCausalLM
import torch

# 提前将 modeling_bluelm.py 中用到 flash_attn 的部分改成 None,反正不真运行,只需要模型结构
tmp = AutoModelForCausalLM.from_pretrained(r'D:\models\BlueLM-7B',
torch_dtype=torch.bfloat16,
trust_remote_code=True)

test_i = torch.arange(0, 10, dtype=torch.long)

embedding = tmp.model.embed_tokens
layer_norm = tmp.model.embed_layer_norm

test_o_o = embedding(test_i)
test_o_o = layer_norm(test_o_o)

for param in embedding.parameters():
if len(param.shape) > 1:
param.data = layer_norm(param.data)

test_o_c = embedding(test_i)

print(torch.allclose(test_o_o, test_o_c, atol=1e-4))

del tmp.model.embed_layer_norm
tmp.save_pretrained(r'D:\models\BlueLM')
# 记得将缺失的一些文件手动复制一下
# 顺便删掉config.json里的rope scaling type
  • 删除 embed_layer_norm 后的结构
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
BlueLMForCausalLM(
(model): BlueLMModel(
(embed_tokens): Embedding(100096, 4096, padding_idx=3)
(layers): ModuleList(
(0-31): 32 x BlueLMDecoderLayer(
(self_attn): BlueLMAttention(
(q_proj): Linear(in_features=4096, out_features=4096, bias=False)
(k_proj): Linear(in_features=4096, out_features=4096, bias=False)
(v_proj): Linear(in_features=4096, out_features=4096, bias=False)
(o_proj): Linear(in_features=4096, out_features=4096, bias=False)
(rotary_emb): BlueLMRotaryEmbedding()
)
(mlp): BlueLMMLP(
(gate_proj): Linear(in_features=4096, out_features=11008, bias=False)
(down_proj): Linear(in_features=11008, out_features=4096, bias=False)
(up_proj): Linear(in_features=4096, out_features=11008, bias=False)
(act_fn): SiLU()
(dropout): Dropout(p=0, inplace=False)
)
(input_layernorm): BlueLMRMSNorm()
(post_attention_layernorm): BlueLMRMSNorm()
)
)
(norm): BlueLMRMSNorm()
)
(lm_head): Linear(in_features=4096, out_features=100096, bias=False)
)

测试运行

1
2
3
4
5
6
7
8
conda activate llamaConvert
cd D:\llama\llama.cpp
python convert.py D:\models\BlueLM --padvocab
Wrote D:\models\BlueLM\ggml-model-f16.gguf
conda activate llamaCpp
cd D:\llama-cublas
.\quantize.exe D:\models\BlueLM\ggml-model-f16.gguf D:\models\BlueLM\ggml-model-Q5_K_M.gguf Q5_K_M
.\main.exe -m D:\models\BlueLM\ggml-model-Q5_K_M.gguf -ngl 25 -c 1024 --interactive-first
]]>
探索 llama https://hexo.limour.top/Convert-BlueLM-7B-Chat-to-the-standard-GGUF-model#disqus_thread
【探索】从零开始训练 GPT https://hexo.limour.top/training-gpt-from-scratch https://hexo.limour.top/training-gpt-from-scratch Thu, 18 Jan 2024 14:19:11 GMT 探索整个过程,从在一台搭载1660Ti显卡的笔记本电脑上构建 Tokenizer,定义带有 RoPE 的 Transformer,一直到训练、保存模型和可视化训练过程。沉浸在从零开始训练 GPT 的旅程中,深入了解每一个步骤。跳入深度学习的世界,释放在你的便携1660Ti笔记本上的强大潜能。 训练中...

预期结构

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
HelloGPT(
(tok_embeddings): Embedding(32765, 768)
(rotary_emb): RotaryEmbedding(head_dim=64, max_seq_len=1024)
(layers): ModuleList(
(0-11): 12 x Decoder(
(ln1): RMSNorm(hidden_size=768, eps=1e-06)
(attn): Attention(
(q_proj): Linear(in_features=768, out_features=768, bias=False)
(k_proj): Linear(in_features=768, out_features=768, bias=False)
(v_proj): Linear(in_features=768, out_features=768, bias=False)
(o_proj): Linear(in_features=768, out_features=768, bias=False)
)
(ln2): RMSNorm(hidden_size=768, eps=1e-06)
(mlp): MLP(
(gate_proj): Linear(in_features=768, out_features=1536, bias=False)
(up_proj): Linear(in_features=768, out_features=1536, bias=False)
(down_proj): Linear(in_features=1536, out_features=768, bias=False)
)
)
)
(norm): RMSNorm(hidden_size=768, eps=1e-06)
(ln2): Linear(in_features=768, out_features=32765, bias=False)
)

配置环境

1
2
3
4
5
6
7
8
9
cd E:\GPT
conda install mamba -c conda-forge
mamba create -n HelloGPT pytorch pytorch-cuda=12.1 -c pytorch -c nvidia -c conda-forge
conda activate HelloGPT
conda install numpy transformers tiktoken tensorboard sentencepiece-python jieba emoji -c conda-forge
pip install opencc-python-reimplemented -i https://pypi.tuna.tsinghua.edu.cn/simple
python test_cuda.py
python test_SPDA.py
D:\vscode\Code.exe

准备数据

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
import os

class Fileset(list):
def __init__(self, path, ext='', _read=None):
if isinstance(path, str):
self.root = path
self.extend(f for f in os.listdir(self.root) if f.endswith(ext))
self._read = _read

def __getitem__(self, index):
if isinstance(index, int): # index是索引
if self._read:
return self._read(os.path.join(self.root, super().__getitem__(index)))
else:
return os.path.join(self.root, super().__getitem__(index))
else: # index是切片
fileset = Fileset(None)
fileset.root = self.root
fileset._read = self._read
fileset.extend(super().__getitem__(index))
return fileset

def getFileName(self, index):
fname, ext = os.path.splitext(super().__getitem__(index))
return fname


from tokenizer import tokenizer
token_eos = 2


def readOne(filePath):
retn = []
with open(file=filePath, encoding='utf-8') as f:
for line in f:
retn += tokenizer.encode(line).ids
retn.append(token_eos)
return retn


class Hcorpus():
def __init__(self, path, ext='txt', fileset_idx=0, fileset_sub_idx=0):
self.fileset = Fileset(path, ext, readOne)
self.fileset_idx = fileset_idx
self.fileset_sub_idx = fileset_sub_idx
if self.fileset_sub_idx < 0: # 再读上一个太复杂了,直接放弃
self.fileset_sub_idx = 0
if self.fileset_idx >= len(self.fileset):
self.fileset_idx = 0
self.cache = self.fileset[self.fileset_idx]
self.fileset_idx += 1
self.cache_idx = self.fileset_sub_idx

def __call__(self, size=512):
while len(self.cache) < self.cache_idx + size:
if self.fileset_idx >= len(self.fileset):
self.fileset_idx = 0
self.fileset_sub_idx = self.cache_idx - len(self.cache)
self.cache = self.cache[self.cache_idx:] + self.fileset[self.fileset_idx]
self.cache_idx = 0
self.fileset_idx += 1
retn = self.cache[self.cache_idx:self.cache_idx + size]
self.cache_idx += size
self.fileset_sub_idx += size
return retn

def __repr__(self):
return f"Hcorpus(r'{self.fileset.root}', fileset_idx={self.fileset_idx-1}, fileset_sub_idx={self.fileset_sub_idx})"

训练Tokenizer

1
2
from tokenizers import Tokenizer
tokenizer = Tokenizer.from_file("HelloBPE.tokenizer.json")

定义模型

定义 Decoder

定义 RMSnorm

1
2
3
4
5
6
7
8
class RMSNorm(nn.Module):
def __init__(self, dim: int, eps: float = 1e-6):
super().__init__()
self.eps = eps
self.weight = nn.Parameter(torch.ones(dim))
def forward(self, x):
x = x * torch.rsqrt(x.pow(2).mean(-1, keepdim=True) + self.eps)
return x * self.weight

定义 RoPE

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
class RotaryEmbedding(nn.Module):
def __init__(self, head_dim: int, max_seq_len: int, device=device, theta: float = 10000.0):
super().__init__()
self.head_dim = head_dim
self.set_max_seq_len(max_seq_len, device, theta)

def set_max_seq_len(self, max_seq_len: int, device=device, theta: float = 10000.0):
self.max_seq_len = max_seq_len
freqs = 1.0 / (theta ** (torch.arange(0, self.head_dim, 2).float().to(device) / self.head_dim))
t = torch.arange(max_seq_len, device=device) # type: ignore
freqs = torch.outer(t, freqs).float() # 外积
self.freqs_cis = torch.polar(torch.ones_like(freqs), freqs) # 复数,模 1,角度 freqs
self.freqs_cis.requires_grad = False # filter(lambda p : p.requires_grad, model.parameters())

def rotary_emb(self, x):
x_ = torch.view_as_complex(x.float().reshape(*x.shape[:-1], -1, 2))
x_out = torch.view_as_real(x_ * self.local_freqs_cis).flatten(3)
return x_out.type_as(x)

def forward(self, start_pos: int, seqlen: int):
self.local_freqs_cis = self.freqs_cis[start_pos: start_pos + seqlen].view(1, seqlen, 1, -1) # cacheKV 相关,可忽略
self.local_freqs_cis.requires_grad = False
return self.rotary_emb

定义 Attention

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
class Attention(nn.Module):
def __init__(self, hidden_size, n_heads, cacheKV, max_batch_size, max_seq_len, device=device):
super().__init__()
self.n_heads = n_heads
self.head_dim = hidden_size // n_heads
self.q_proj = nn.Linear(hidden_size, hidden_size, bias=False)
self.k_proj = nn.Linear(hidden_size, hidden_size, bias=False)
self.v_proj = nn.Linear(hidden_size, hidden_size, bias=False)
self.o_proj = nn.Linear(hidden_size, hidden_size, bias=False)

def forward(self, hidden_states, rotary_emb, start_pos=0, mask=None, is_causal=True):
bsz, seqlen, hidden_size = hidden_states.shape

q = self.q_proj(hidden_states)
k = self.k_proj(hidden_states)
v = self.v_proj(hidden_states)

q = q.view(bsz, seqlen, self.n_heads, self.head_dim)
k = k.view(bsz, seqlen, self.n_heads, self.head_dim)
v = v.view(bsz, seqlen, self.n_heads, self.head_dim)

q = rotary_emb(q)
k = rotary_emb(k)

q = q.transpose(1, 2) # (bs, n_heads, seqlen, head_dim)
k = k.transpose(1, 2) # (bs, n_local_heads, cache_len + seqlen, head_dim)
v = v.transpose(1, 2) # (bs, n_local_heads, cache_len + seqlen, head_dim)

output = F.scaled_dot_product_attention(q, k, v, attn_mask=mask, is_causal=is_causal)

output = output.transpose(1, 2).contiguous().view(bsz, seqlen, hidden_size)
return self.o_proj(output)

定义 MLP

1
2
3
4
5
6
7
8
9
10
11
12
class MLP(nn.Module):
def __init__(self, hidden_size):
super().__init__()
intermediate_size = int(2 * hidden_size)
self.gate_proj = nn.Linear(hidden_size, intermediate_size, bias=False)
self.up_proj = nn.Linear(hidden_size, intermediate_size, bias=False)
self.down_proj = nn.Linear(intermediate_size, hidden_size, bias=False)

def forward(self, x):
gate = F.silu(self.gate_proj(x))
intermediate_states = self.up_proj(x)
return self.down_proj(gate * intermediate_states)

组装 Decoder

1
2
3
4
5
6
7
8
9
10
11
class Decoder(nn.Module):
def __init__(self, hidden_size, n_heads, cacheKV, max_batch_size, max_seq_len):
super().__init__()
self.ln1 = RMSNorm(hidden_size)
self.attn = Attention(hidden_size, n_heads, cacheKV, max_batch_size, max_seq_len)
self.ln2 = RMSNorm(hidden_size)
self.mlp = MLP(hidden_size)

def forward(self, x, rotary_emb, start_pos, mask=None, is_causal=True):
x = x + self.attn(self.ln1(x), rotary_emb, start_pos, mask, is_causal)
return x + self.mlp(self.ln2(x))

组装模型

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
class HelloGPT(nn.Module):
def __init__(self, vocab_size=32765, hidden_size=768, n_heads=12, max_seq_len=1024, n_layers=12, cacheKV=False, max_batch_size=1):
super().__init__()
# hidden_size > 8.33 * ln(vocab_size)
self.tok_embeddings = nn.Embedding(vocab_size, hidden_size)
self.rotary_emb = RotaryEmbedding(hidden_size // n_heads, max_seq_len * 2)
self.rotary_emb.requires_grad = False
self.layers = nn.ModuleList()
for layer_id in range(n_layers):
self.layers.append(Decoder(hidden_size, n_heads, cacheKV, max_batch_size, max_seq_len))
self.norm = RMSNorm(hidden_size)
self.ln2 = nn.Linear(hidden_size, vocab_size, bias=False)

def forward(self, input_ids: torch.Tensor, start_pos=0, no_mask=True):
_bsz, seqlen = input_ids.shape
h = self.tok_embeddings(input_ids)

# 预计算,减少每一层的重复计算
rotary_emb = self.rotary_emb(start_pos, seqlen)
for layer in self.layers:
h = layer(h, rotary_emb, start_pos)

h = self.norm(h)
h = self.ln2(h)
return h.float()

训练模型

数据载入

1
2
3
4
5
6
7
8
9
data = Hcorpus(r'D:\datasets\h-corpus')
def get_batch(size=512, bsz=8):
x = []
y = []
for i in range(bsz):
tmp = data(size+1)
x.append(tmp[:size])
y.append(tmp[1:])
return torch.tensor(x).to(device), torch.tensor(y).to(device)

模型载入

1
2
model = HelloGPT(n_layers=8, max_seq_len=768)
model.to(device)

训练模型

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
## 初始化训练器
criterion = nn.CrossEntropyLoss() # 交叉熵损失函数
optimizer = torch.optim.Adam(train_parameters, lr=6e-4) # Adam 优化器
scheduler = CosineAnnealingWarmRestarts(optimizer, T_0=5, T_mult=2) # 余弦退火学习率
torch.manual_seed(1337) # 魔术随机种子

total_loss = 0
print_iter = 20
for epoch in range(1, 100001):
optimizer.zero_grad(set_to_none=True) # 清空梯度,节省显存
x, y = get_batch(size=384, bsz=4) # x 是训练语料 y 是 x 移动了一位,当做预测目标
y_ = model(x) # 通过 x 预测的 y
loss = criterion(y_.view(-1, 32765), y.view(-1)) # 计算损失
loss.backward() # 反向传播梯度
torch.nn.utils.clip_grad_norm_(train_parameters, 0.5) # 梯度裁剪,减轻过拟合
optimizer.step() # 通过梯度优化训练参数
scheduler.step() # 计算下一步的学习率
total_loss += loss # 累计损失

if epoch % print_iter == 0:
print(data)
print(f'epoch: {epoch} lr: {scheduler.get_last_lr()[0]:.4e} loss: {total_loss / print_iter:.4e}')
total_loss = 0

保存读取

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
with open('tmp_training.pkl', 'rb') as file:
epoch = pickle.load(file) # 读取 epoch 位置
tmp_fileset_idx = pickle.load(file) # 读取 data 位置
tmp_fileset_sub_idx = pickle.load(file)
# 恢复数据位置
data = Hcorpus(r'D:\datasets\h-corpus', fileset_idx=tmp_fileset_idx-1, fileset_sub_idx=tmp_fileset_sub_idx)
model = torch.load(f'tmp_model_{epoch}.pth') # 恢复模型
print(f'start from epoch: {epoch} data: {data}')

save_iter = 5000
for epoch in range(1, 100001):
pass
if epoch % save_iter == 0:
optimizer.zero_grad(set_to_none=True) # 清空梯度,节省显存
with open('tmp_training.pkl', 'wb') as file:
pickle.dump(epoch, file) # 保存 epoch 位置
pickle.dump(data.fileset_idx, file) # 保存 data 位置
pickle.dump(data.fileset_sub_idx, file)
torch.save(model, f'tmp_model_{epoch}.pth') # 保存模型
print(f'save to tmp_model_{epoch}.pth')

可视化

1
2
3
4
5
6
7
8
9
writer = SummaryWriter('logs')  # tensorboard --logdir logs
for epoch in range(1, 100001):
pass
writer.add_scalar('lr', scheduler.get_last_lr()[0], epoch)
writer.add_scalar('loss', loss, epoch)
if epoch % print_iter == 0:
pass
writer.add_scalar('total_loss', total_loss / print_iter, epoch)
writer.close()

附加 streaming_llm

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
class RotaryEmbedding(nn.Module):
pass
def inverse_rotary_emb(self, x):
x_ = torch.view_as_complex(x.float().reshape(*x.shape[:-1], -1, 2))
x_out = torch.view_as_real(x_ * self.local_freqs_cis_inverse).flatten(3)
return x_out.type_as(x)

def inverse_forward(self, start_pos: int, seqlen: int):
self.local_freqs_cis_inverse = self.freqs_cis[start_pos: start_pos + seqlen].view(1, seqlen, 1, -1) # cacheKV 相关,可忽略
self.local_freqs_cis_inverse = self.local_freqs_cis_inverse.conj() # 乘上共轭就旋转回去了
self.local_freqs_cis.requires_grad = False
return self.inverse_rotary_emb

class Attention(nn.Module):
pass
def forward(self, hidden_states, rotary_emb, start_pos=0, mask=None, is_causal=True):
pass
if self.cacheKV: # cacheKV 相关,可忽略
self.cache_k[:bsz, start_pos: start_pos + seqlen] = k
self.cache_v[:bsz, start_pos: start_pos + seqlen] = v
k = self.cache_k[:bsz, : start_pos + seqlen]
v = self.cache_v[:bsz, : start_pos + seqlen]

def streaming_llm(self, start_pos, seqlen, to_pos, inverse_rotary_emb, rotary_emb, bsz):
k = self.cache_k[:bsz, start_pos: start_pos + seqlen]
v = self.cache_v[:bsz, start_pos: start_pos + seqlen]
k = inverse_rotary_emb(k)
k = rotary_emb(k)
self.cache_k[:bsz, to_pos: to_pos + seqlen] = k
self.cache_v[:bsz, to_pos: to_pos + seqlen] = v

class HelloGPT(nn.Module):
pass
def streaming_llm(self, start_pos, seqlen, to_pos, max_batch_size=1):
rotary_emb = self.rotary_emb(to_pos, seqlen)
inverse_rotary_emb = self.rotary_emb.inverse_forward(start_pos, seqlen)
for layer in self.layers:
layer.attn.streaming_llm(start_pos, seqlen, to_pos, inverse_rotary_emb, rotary_emb, max_batch_size)
]]>
探索 llama https://hexo.limour.top/training-gpt-from-scratch#disqus_thread
【避坑】Azure AI 避免反向薅羊毛 https://hexo.limour.top/Azure-AI-prevents-reverse-wool-shearing https://hexo.limour.top/Azure-AI-prevents-reverse-wool-shearing Tue, 09 Jan 2024 05:55:40 GMT <h2 id="起因">起因</h2> <p>今天收到 Azure 的付费邮件,一看账单,好家伙,24.54$ ,比上个月暴涨 622%,给我 CPU 干烧了。</p> <p>赶紧去成本分析里按资源分类看上个月的扣费详情,然后就看到两个 10.33$ 的 <code>Contai 起因

今天收到 Azure 的付费邮件,一看账单,好家伙,24.54$ ,比上个月暴涨 622%,给我 CPU 干烧了。

赶紧去成本分析里按资源分类看上个月的扣费详情,然后就看到两个 10.33$ 的 Container Registry,分别位于我在 Azure AI Studio 里的两个不同项目所在区域。

一顿折腾,发现这个 Container Registry,有一年的免费试用期,但是免费限额是 31/个/天,一个 15 天刚好是 10.33$ 。

这 Azure 不讲武德,这样免费,头半个月根本不知道这东西要收费,等月末美滋滋去付账单时钱都已经扣完了。。。

特别是,这东西似乎是 Azure AI Studio 自动开通的,我根本没有用到过它。心情更糟了。

解决方案

赶紧去资源组里找到这两个容器注册表,全给删了。删除后不会对 Azure AI 的使用产生影响。

然后是想办法提工单,看能不能把这钱退回来。

最后保留的服务,不知道哪些还可以删

工单结果

透过案件了解到Container Registry是您不清楚的情况下创建的,且您已经将此资源进行了删除。考虑到您是首次使用Azure产品较不熟悉,且已经将资源删除,经过竭力向主管团队申请,现为您申请了相关费用的减免,即:
12/1/2023-12/31/2023期间由Container Registry – Standard产生的费用20.66 USD已经申请退回至您的信用卡,依据银行流程,款项约需要7-21个工作日抵达您的账户,届时请您查看。
同时,我们也查看了您当前的计费周期(1/1/2024-1/31/2024)的使用量报表,Container Registry – Standard未产生费用,还请您放心。

]]>
openai https://hexo.limour.top/Azure-AI-prevents-reverse-wool-shearing#disqus_thread
【记录】win10平台6G显存运行Qwen-1.8B https://hexo.limour.top/Running-Qwen-on-the-Win10-platform-with-6GB-of-video-memory https://hexo.limour.top/Running-Qwen-on-the-Win10-platform-with-6GB-of-video-memory Mon, 01 Jan 2024 03:11:36 GMT <p><a href="https://hexo.limour.top/go/#aHR0cHM6Ly9naXRodWIuY29tL2dnZXJnYW5vdi9sbGFtYS5jcHA=" rel="noopener external nofollow noreferrer">Ll Llama.cpp 能 CPU & GPU 环境混合推理,这里记录一下在 windows10 平台上运行 Qwen-1.8B 的过程,显卡是 1660Ti 。

准备模型

1
2
3
4
5
6
7
conda create -n llamaConvert python=3.10 git -c conda-forge
conda activate llamaConvert
cd D:\llama
git clone --depth=1 https://github.com/ggerganov/llama.cpp.git
cd llama.cpp
python -m pip install -r requirements.txt
pip install tiktoken
1
2
3
4
python -c "from huggingface_hub import snapshot_download; snapshot_download(repo_id='Qwen/Qwen-1_8B-Chat', local_dir=r'D:\qwen', ignore_patterns=['*.h5', '*.ot', '*.msgpack', '*.safetensors'])"
cd D:\qwen
D:\aria2\aria2c.exe --all-proxy='http://127.0.0.1:7890' -o 'model-00001-of-00002.safetensors' "https://huggingface.co/Qwen/Qwen-1_8B-Chat/resolve/main/model-00001-of-00002.safetensors?download=true"
D:\aria2\aria2c.exe --all-proxy='http://127.0.0.1:7890' -o 'model-00002-of-00002.safetensors' "https://huggingface.co/Qwen/Qwen-1_8B-Chat/resolve/main/model-00002-of-00002.safetensors?download=true"
1
2
3
cd D:\llama\llama.cpp
python convert-hf-to-gguf.py D:\qwen
# Model successfully exported to 'D:\qwen\ggml-model-f16.gguf'

运行模型

1
2
3
4
5
6
conda create -n llamaCpp libcublas cuda-toolkit git -c nvidia -c conda-forge
conda activate llamaCpp
cd D:\llama ; .\main.exe ## 检查能否正确运行
cd D:\llama ; .\quantize.exe --help ## 自己决定量化方式
.\quantize.exe D:\qwen\ggml-model-f16.gguf .\qwen-1_8-f16.gguf COPY
.\server.exe -m .\qwen-1_8-f16.gguf -c 4096 --n-gpu-layers 50 ## 调节 n-gpu-layers 平衡 CPU & GPU
  • 访问 http://127.0.0.1:8080 选择 Completion 进行测试

微调模型

附加 Yi-6B-Chat

Yi-6B是零一万物开源的双语语言模型,经过3T多语种语料库的训练,在语言理解、常识推理、阅读理解等方面有一定潜力。

1
2
3
4
5
6
cd D:\models\01yi
D:\aria2\aria2c.exe --all-proxy='http://127.0.0.1:7890' -o 'model-00001-of-00003.safetensors' "https://huggingface.co/01-ai/Yi-6B-Chat/resolve/main/model-00001-of-00003.safetensors?download=true"
D:\aria2\aria2c.exe --all-proxy='http://127.0.0.1:7890' -o 'model-00002-of-00003.safetensors' "https://huggingface.co/01-ai/Yi-6B-Chat/resolve/main/model-00002-of-00003.safetensors?download=true"
D:\aria2\aria2c.exe --all-proxy='http://127.0.0.1:7890' -o 'model-00003-of-00003.safetensors' https://huggingface.co/01-ai/Yi-6B-Chat/resolve/main/model-00003-of-00003.safetensors?download=true
conda activate llamaConvert
python -c "from huggingface_hub import snapshot_download; snapshot_download(repo_id='01-ai/Yi-6B-Chat', local_dir=r'D:\models\01yi', ignore_patterns=['*.h5', '*.ot', '*.msgpack', '*.safetensors'])"
1
2
3
4
5
6
7
8
conda activate llamaConvert
cd D:\llama\llama.cpp
python convert.py D:\models\01yi
# Wrote D:\models\01yi\ggml-model-f16.gguf
conda activate llamaCpp
cd D:\llama ; .\quantize.exe --help
.\quantize.exe D:\models\01yi\ggml-model-f16.gguf .\01yi-6b-Q4_K_M.gguf Q4_K_M
.\server.exe -m .\01yi-6b-Q4_K_M.gguf -c 4096 --n-gpu-layers 50

附加 百川2

1
2
3
4
5
6
7
8
9
10
11
cd D:\models\baichuan
D:\aria2\aria2c.exe --all-proxy='http://127.0.0.1:7890' -o 'pytorch_model.bin' "https://huggingface.co/baichuan-inc/Baichuan2-7B-Chat/resolve/main/pytorch_model.bin?download=true"
conda activate llamaConvert
python -c "from huggingface_hub import snapshot_download; snapshot_download(repo_id='baichuan-inc/Baichuan2-7B-Chat', local_dir=r'D:\models\baichuan', ignore_patterns=['*.h5', '*.bin', '*.ot', '*.msgpack', '*.safetensors'])"
cd D:\llama\llama.cpp
python convert.py D:\models\baichuan
# Wrote D:\models\baichuan\ggml-model-f16.gguf
conda activate llamaCpp
cd D:\llama ; .\quantize.exe --help
.\quantize.exe D:\models\baichuan\ggml-model-f16.gguf .\baichuan-7b-Q3_K_M.gguf Q3_K_M
.\server.exe -m .\baichuan-7b-Q3_K_M.gguf -c 2048 --n-gpu-layers 30

附加 tigerbot-13b

tigerbot-13bchinese-llm-benchmark 上排名靠前。

1
2
3
4
5
6
7
8
9
10
11
cd D:\models\tigerbot
D:\aria2\aria2c.exe --all-proxy='http://127.0.0.1:7890' -o 'pytorch_model-00001-of-00003.bin' --max-download-limit=6M "https://huggingface.co/TigerResearch/tigerbot-13b-chat-v5/resolve/main/pytorch_model-00001-of-00003.bin?download=true"
D:\aria2\aria2c.exe --all-proxy='http://127.0.0.1:7890' -o 'pytorch_model-00002-of-00003.bin' --max-download-limit=6M "https://huggingface.co/TigerResearch/tigerbot-13b-chat-v5/resolve/main/pytorch_model-00002-of-00003.bin?download=true"
D:\aria2\aria2c.exe --all-proxy='http://127.0.0.1:7890' -o 'pytorch_model-00003-of-00003.bin' --max-download-limit=6M "https://huggingface.co/TigerResearch/tigerbot-13b-chat-v5/resolve/main/pytorch_model-00003-of-00003.bin?download=true"
conda activate llamaConvert
python -c "from huggingface_hub import snapshot_download; snapshot_download(repo_id='TigerResearch/tigerbot-13b-chat-v5', local_dir=r'D:\models\tigerbot', ignore_patterns=['*.h5', '*.bin', '*.ot', '*.msgpack', '*.safetensors'])"
cd D:\llama\llama.cpp
python convert.py D:\models\tigerbot --padvocab
cd D:\llama ; .\quantize.exe --help
.\quantize.exe D:\models\tigerbot\ggml-model-f16.gguf D:\models\tigerbot-13B-Chat-Q4_K_M.gguf Q4_K_M
.\server.exe -m D:\models\tigerbot-13B-Chat-Q4_K_M.gguf -c 4096

感觉 6G 显存下,比较好用的是 Yi-6B-Chat-Q4_K_M
tigerbot-13b 在 R5 5600H 上推理速度 4.6 tokens/s,CPU 使用率 60%,频率 3.5GHz,应该是内存带宽瓶颈

附加 在 Colab 上量化

安装 llama.cpp

1
2
3
!git clone --depth=1 https://github.com/ggerganov/llama.cpp.git
%cd /content/llama.cpp
!LLAMA_CUDA=1 make -j

计算 imatrix

1
2
3
4
5
6
%cd /content
!wget -O transient.txt.gz https://huggingface.co/datasets/Limour/b-corpus/resolve/main/00-preview/00-transient.txt.gz?download=true
!gunzip transient.txt.gz
!mkdir -p /content/CausalLM-14B-GGUF
!wget -O /content/CausalLM-14B-GGUF/causallm_14b.Q8_0.gguf https://huggingface.co/TheBloke/CausalLM-14B-GGUF/resolve/main/causallm_14b.Q8_0.gguf?download=true
!/content/llama.cpp/imatrix -m /content/CausalLM-14B-GGUF/causallm_14b.Q8_0.gguf -f /content/transient.txt -ngl 36

登录拥抱脸

1
2
3
4
5
6
from google.colab import userdata
from huggingface_hub import login
# login(token=os.environ.get("HF_TOKEN"), write_permission=True)
login(token=userdata.get('HF_TOKEN'), write_permission=True)
# from huggingface_hub import notebook_login
# notebook_login()

(跳过) 转换模型

1
2
3
4
5
6
7
%cd llama.cpp
!python -m pip install -r requirements.txt
!pip install tiktoken
from huggingface_hub import snapshot_download
!mkdir -p ~/CausalLM
snapshot_download(repo_id='CausalLM/7B', local_dir=r'/content/CausalLM', ignore_patterns=['*.h5', '*.ot', '*.msgpack', '*.safetensors'])
!python convert.py --vocab-type bpe --pad-vocab --outtype f16 /content/CausalLM

量化模型

1
!/content/llama.cpp/quantize --allow-requantize --imatrix /content/imatrix.dat /content/CausalLM-14B-GGUF/causallm_14b.Q8_0.gguf /content/CausalLM-14B-GGUF/causallm_14b.IQ3_XS.gguf IQ3_XS

上传模型

1
2
3
4
5
6
7
from huggingface_hub import HfApi
api = HfApi()
api.upload_file(
path_or_fileobj="/content/CausalLM-14B-GGUF/causallm_14b.IQ3_XS.gguf",
path_in_repo="causallm_14b.IQ3_XS.gguf",
repo_id="Limour/CausalLM-14B-GGUF"
)
]]>
llama https://hexo.limour.top/Running-Qwen-on-the-Win10-platform-with-6GB-of-video-memory#disqus_thread
【记录】轻量个人导航页面 Flare https://hexo.limour.top/Lightweight-personal-navigation-page-Flare https://hexo.limour.top/Lightweight-personal-navigation-page-Flare Sun, 31 Dec 2023 17:18:28 GMT <p><a href="https://hexo.limour.top/go/#aHR0cHM6Ly9naXRodWIuY29tL3NvdWx0ZWFyeS9kb2NrZXItZmxhcmU=" rel="noopener external nofollow noreferrer Flare 是一款轻量、快速、美观的个人导航页面,适用于 HomeLab 或其他注重私密的场景。

1
2
3
mkdir -p ~/app/flare && cd ~/app/flare && nano docker-compose.yml
sudo docker-compose up -d # flare:5005
sudo docker-compose logs # 获取登录密码
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
version: '3.6'

services:
flare:
image: soulteary/flare
restart: always
# 默认无需添加任何参数,如有特殊需求
# 可阅读文档 https://github.com/soulteary/docker-flare/blob/main/docs/advanced-startup.md
# 启用账号登录模式
command: flare --disable_login=0
environment:
# 如需开启用户登录模式,需要先设置 `nologin` 启动参数为 `0`
# 如开启 `nologin`,未设置 FLARE_USER,则默认用户为 `flare`
- FLARE_USER=LimourFlare
# 指定你自己的账号密码,默认生成的密码强度堪忧
- FLARE_PASS=your_password
- FLARE_OFFLINE=1
- FLARE_MINI_REQUEST=1
volumes:
- ./app:/app

networks:
default:
external: true
name: ngpm
]]>
docker ngpm homepage https://hexo.limour.top/Lightweight-personal-navigation-page-Flare#disqus_thread
【记录】Win10平台使用MLC-LLM编译Qwen-1.8B-Chat https://hexo.limour.top/Compile-Qwen-1.8B-Chat-using-MLC-LLM-on-Win https://hexo.limour.top/Compile-Qwen-1.8B-Chat-using-MLC-LLM-on-Win Sat, 09 Dec 2023 04:24:07 GMT <p><a href="https://hexo.limour.top/go/#aHR0cHM6Ly9naXRodWIuY29tL21sYy1haS9tbGMtbGxt" rel="noopener external nofollow noreferrer">MLC-LLM</a MLC-LLM 是一种大模型高性能通用部署解决方案,可以通过预编译加速使用本机API原生部署任何大型语言模型。该项目的使命是利用ML编译技术,使每个人都能在其设备上本地开发、优化和部署AI模型。
Qwen-1.8B 是阿里云研发的通义千问大模型系列的18亿参数规模的模型。在Qwen-1.8B的基础上,使用对齐机制打造了基于大语言模型的AI助手 Qwen-1.8B-Chat

配置环境

1
2
3
4
5
6
7
8
9
10
11
12
conda create -n mlc_llm python numpy pytorch transformers scipy timm git -c pytorch -c conda-forge
conda activate mlc_llm
python -m pip install --pre -U -f https://mlc.ai/wheels mlc-ai-nightly
python -c "import tvm; print('\n'.join(f'{k}: {v}' for k, v in tvm.support.libinfo().items()))"
python -c "import tvm; print(tvm.vulkan().exist)"
cd D:\mlc-llm
git clone --depth=1 -b main --single-branch https://github.com/mlc-ai/mlc-llm.git
cd .\mlc-llm\
git submodule sync
git submodule update --init --recursive --depth=1
pip install .
python -m mlc_llm.build --help

准备模型

1
2
3
4
python -c "from huggingface_hub import snapshot_download; snapshot_download(repo_id='Qwen/Qwen-1_8B-Chat', local_dir='D:\mlc-llm\qwen', ignore_patterns=['*.h5', '*.ot', '*.msgpack', '*.safetensors'])"
cd D:\mlc-llm\qwen
D:\aria2\aria2c.exe --all-proxy='http://127.0.0.1:7890' -o 'model-00001-of-00002.safetensors' "https://huggingface.co/Qwen/Qwen-1_8B-Chat/resolve/main/model-00001-of-00002.safetensors?download=true"
D:\aria2\aria2c.exe --all-proxy='http://127.0.0.1:7890' -o 'model-00002-of-00002.safetensors' "https://huggingface.co/Qwen/Qwen-1_8B-Chat/resolve/main/model-00002-of-00002.safetensors?download=true"

编译模型

1
2
cd D:\mlc-llm\dist
python -m mlc_llm.build --model "D:\mlc-llm\qwen" --target vulkan --quantization q0f16 --use-safetensors
]]>
llama https://hexo.limour.top/Compile-Qwen-1.8B-Chat-using-MLC-LLM-on-Win#disqus_thread
【探索】外科打结法中的等价操作 https://hexo.limour.top/Equivalent-operations-in-surgical-knot-tying https://hexo.limour.top/Equivalent-operations-in-surgical-knot-tying Sat, 02 Dec 2023 06:47:05 GMT <p>手术中的止血和缝合,均需要进行结扎,而结扎是否牢固,又与打结有密切关系,结一定要打得牢固,不能松动、滑脱。<br> 常用的结扣是方结,结扎后极为牢固,在手术中最常用。而打方结时,手法顺序错误就容易打成假结或滑结。因此这里将探讨基础打结手法的等价性,帮助快速理解不同手法所成结 手术中的止血和缝合,均需要进行结扎,而结扎是否牢固,又与打结有密切关系,结一定要打得牢固,不能松动、滑脱。
常用的结扣是方结,结扎后极为牢固,在手术中最常用。而打方结时,手法顺序错误就容易打成假结或滑结。因此这里将探讨基础打结手法的等价性,帮助快速理解不同手法所成结的本质。
除不易混淆的外科结外,无论是单手打结还是持钳打结,均由基础动作组合而成,基础动作所成的结都对应纽结理论中的三叶结。三叶结有两种,它们互成镜像,彼此不相同痕,分别称为左手三叶结和右手三叶结。因此无论用哪种手法,最后一定能对应到两种三叶结上。

两种三叶结

右手勾法对应右手三叶结

左手勾法对应左手三叶结

右手掏法对应左手三叶结

左手掏法对应右手三叶结

镊右手定则法对应右手三叶结

镊左手定则法对应左手三叶结

因此,右手勾法、左手掏法、镊右手定则法三者等价;左手勾法、右手掏法、镊左手定则法三者等价。任意组合两种基础打结动作打出不同的两种三叶结即可组成一个正确的方结。

]]>
探索 https://hexo.limour.top/Equivalent-operations-in-surgical-knot-tying#disqus_thread
【翻译】多重免疫分析揭示了血清免疫蛋白质组学在预测胃癌术前化疗反应中的作用 https://hexo.limour.top/Multiplex-immune-profiling-reveals-the-role-of-serum-immune-proteomics-in-predicting-response-to-preoperative-chemotherapy-of-gastric-cancer https://hexo.limour.top/Multiplex-immune-profiling-reveals-the-role-of-serum-immune-proteomics-in-predicting-response-to-preoperative-chemotherapy-of-gastric-cancer Fri, 01 Dec 2023 15:41:27 GMT <div class="note note-info"> <p>原文链接:<a href="https://hexo.limour.top/go/#aHR0cHM6Ly9kb2kub3JnLzEwLjEwMTYvai54Y3JtLjIwMjMuMTAwOT

原文链接:Multiplex immune profiling reveals the role of serum immune proteomics in predicting response to preoperative chemotherapy of gastric cancer

摘要

对于胃腺癌患者,对术前化疗的反应存在异质性。该领域现有的研究主要集中在肿瘤微环境(TME)上,而关于全身免疫与化疗反应之间的关系知之甚少。在这项研究中,我们收集了胃腺癌患者在术前、术中和术后接受术前化疗前后的血清样本,并使用基于抗体的蛋白质组学面板研究其免疫蛋白质组学。我们还收集了手术切除的肿瘤样本,并采用多种方法评估它们的肿瘤微环境。我们发现局部和全身免疫特征均与治疗反应相关。术前化疗引发了复杂的全身免疫反应,表现为动态的血清免疫蛋白质组学。建立了一个用于预测反应的术前血清蛋白评分系统。总的来说,这些发现突显了全身免疫在胃癌治疗中的基本但在很大程度上被低估的作用,建议使用基于术前血清免疫蛋白质组学的患者分层策略。

导言

胃癌,其中胃腺癌(GAC)是其主要组织学类型,是全球最常见的恶性肿瘤之一,也是导致癌症相关死亡的主要原因之一。相当一部分胃癌患者在晚期被诊断,这在很大程度上限制了治疗的有效性和患者的预后。尽管手术切除仍然是治疗的强制性支柱,包括JCOG9501和JCOG9502(日本临床肿瘤研究组的系列研究)在内的几项研究表明,胃癌患者不会从扩大切除中受益。在过去的十年中,新辅助和围手术期治疗带来了新的希望。MAGIC试验表明,对于可切除的II/III期胃癌患者,行三个术前和三个术后周期的ECF(表阿霉素、顺铂和5-氟尿嘧啶)化疗,相较于仅手术,可以将5年生存率从23%提高到36%(MAGIC: the Medical Research Council Adjuvant Gastric Infusional Chemotherapy)。FLOT4-AIO试验进一步显示,与ECF或ECX(表阿霉素、5-氟尿嘧啶和卡培他滨)相比,FLOT(5-氟尿嘧啶、叶酸、奥沙利铂和多西紫杉醇)方案可导致更好的病理反应率、R0切除率和总生存(OS)。人们认识到,术前用化疗治疗可以增加根治切除的机会,消除早期微观扩散,并允许对辅助治疗进行术前反应评估。随着免疫检查点抑制剂(ICIs)等新药物的出现,化疗仍然是胃癌围手术期治疗中最基本且可获得的组成部分。
另一方面,在胃癌中,术前治疗仍然存在争议,尤其是在东亚国家。对术前化疗的反应存在异质性,而对其机制的了解有限。需要预测患者对术前化疗反应的生物标志物,以对患者进行最佳治疗分层。新出现的证据表明,免疫参与了患者对化疗的反应。Choi等人报道称,肿瘤标本中基质程序性细胞死亡配体1(PD-L1)的表达可以预测第II/III期胃癌经D2胃切除术后辅助化疗的益处。 Kim等人在标准一线化疗期间使用配对的术前和治疗期间的胃活检样本,发现化疗诱导了自然杀伤细胞(NK)的浸润,巨噬细胞的极化,以及在治疗反应者中抗原呈递的增加。但是,在胃癌免疫学领域的现有研究主要集中在肿瘤微环境(TME)中的局部免疫反应上,关于全身免疫与胃癌化疗反应之间的关系知之甚少。
胃癌是一种全身性疾病。肿瘤负担和抗肿瘤治疗刺激的免疫反应在不同组织之间协调进行。对接受术前化疗的患者进行系统免疫景观或由Hiam-Galvez等人描述的免疫宏环境的分析对于全面了解癌症免疫和治疗抵抗机制至关重要。现有的系统免疫-炎症指标,如中性粒细胞与淋巴细胞比值(NLR),主要依赖于血细胞计数,这限制了它们的维度。血清免疫蛋白质组学,具有高含量,将是对全身性免疫的理想反映。在这项研究中,我们收集了胃腺癌患者在术前、术中和术后接受术前化疗的血清样本,并使用基于抗体的蛋白质组学平台(Olink Target 96 Inflammation panel)研究了他们的免疫蛋白质组学。我们还从这些患者中收集了手术切除的肿瘤样本,并结合多重免疫荧光(mIF)、免疫组织化学(IHC)和RNA测序(RNA-seq)来评估肿瘤微环境。研究了血清免疫蛋白质组学的动态变化及其与肿瘤微环境的相关性。鉴定了预测接受术前化疗患者肿瘤缩小、总生存(OS)和无进展生存(PFS)的生物标志物。

结果

研究人群

本研究纳入了90名接受术前化疗并随后接受胃切除手术的胃腺癌患者(图1A)。在术前期间接受免疫检查点抑制剂(ICIs)的患者被排除在外。符合条件的患者被分为响应者(残余肿瘤/肿瘤床≤50%的化疗效果,Becker TRG评分1–2)和非响应者(Becker TRG评分3)。在90名患者中,有36人(40%)达到了肿瘤缩小评分1–2,被视为响应者。肿瘤缩小程度较好的患者与非响应者相比,总生存显著更长(图S1A)。无进展生存显示了类似的趋势,尽管没有统计学差异(图S1B)。患者的基本临床特征总结在表S1中。近半数的患者接受了两药细胞毒性化疗,其中大多数是SOX(S-1加奥沙利铂)或XELOX(卡培他滨加奥沙利铂)方案。其余的患者接受了三药细胞毒性化疗,主要是DOS(多西紫杉醇、奥沙利铂和S-1)方案。截至2022年3月1日的分析日期,中位随访时间为55.8个月(范围从3.2到82.7个月)。在整体人群中,中位无进展生存为39.8个月(95%置信区间[CI],32.7至未达到[NR]),而中位总生存为63.9个月(95% CI,51.8至74.1),有45例死亡(50%)。

血清免疫蛋白质组学动态与术前化疗反应相关

从接受术前化疗的患者中收集了37份术前、8份术中和83份术后的血清样本,其中30份术前和30份术后的血清样本是成对的(图1A)。使用Olink Target 96 Inflammation panel的近距离扩展测定法(PEA)测量了关键免疫和炎症通路中92个标记蛋白的水平。比较术前和术后血清样本中蛋白质水平显示了术前化疗后血清免疫蛋白质组学的动态变化。92个蛋白中有18个在成对和非成对测试中均显示出显著变化(图1B、图S1C和图S1D),表明术前化疗引发了复杂的全身免疫反应。其中,血清C-X-C基序化学因子配体1(CXCL1)和CXCL5水平在术前化疗后显著下降(图S1D)。有趣的是,Zhou等人报道称,作为CXCR2配体的CXCL1和CXCL5可以显著促进胃癌细胞的迁移,并推动胃癌的转移。化疗通过降低CXCL5和CXCL1的血清水平可能有助于预防胃癌的转移。事实上,CXCL1/5水平在术前化疗的早期周期中下降(图S1E)。
我们进一步比较了不同治疗反应患者的血清免疫蛋白质组学动态变化。我们发现,响应者在治疗后表现出更动态的血清免疫蛋白质组学变化(图1C和1D)。我们还比较了化疗后响应者和非响应者蛋白水平的绝对变化,发现在响应者中,免疫蛋白水平在化疗后整体上更大幅度的变化(图1E)。例如,与响应者相比,非响应者治疗后血清CXCL5水平的降低程度要轻得多(图1C–1F)。在治疗期间的蛋白质组学在响应者和非响应者中也似乎存在差异(图S1E)。例如,在响应者中,治疗期间血清白介素受体亚单位b(IL-10RB)和IL-18水平在化疗过程中呈上升趋势,而在非响应者中未呈现这种趋势(图S1F和图S1G),尽管这一部分的结论可能受到样本数量的限制。
综合而言,这些结果表明在胃腺癌患者中对术前化疗存在复杂的全身性免疫反应。响应者在术前化疗后往往表现出更为动态的全身性免疫反应。

肿瘤微环境(TME)与患者对术前化疗的反应相关

首先,我们比较了来自不同治疗反应患者的肿瘤样本的转录组,以获得有关肿瘤局部特征的一般知识。基因集富集分析(GSEA)显示了良好反应者中改变的标志性通路(图2A)。如DNA复制和细胞周期等通路的改变,可能表明抑制癌细胞增殖和肿瘤退化。除此之外,近一半的通路与免疫有关,如趋化因子信号通路和细胞因子与细胞因子受体相互作用通路(图2B和图2C),表明免疫在化疗中的重要性。
因此,我们通过多重免疫荧光(mIF)在手术切除的肿瘤样本中评估了地理免疫景观。我们使用CD4、CD8和Foxp3染色来识别不同类型的T细胞。我们使用CD68和CD163染色来识别巨噬细胞(图2D)。我们比较了响应者和非响应者之间的免疫浸润。CD68+巨噬细胞和CD68+/CD163+ M2巨噬细胞的细胞密度在非响应者中显著更高(图2E和图S2A)。相应地,Xing等人报道称,在胃癌新辅助化疗后,非响应者中CD68+巨噬细胞浸润更高。M2巨噬细胞也被证明参与了多种癌症的化疗耐药。
与此同时,我们从队列中收集了24份术前内镜活检样本。我们使用mIF对术前TME进行了分析(图S2B)。值得注意的是,大多数内镜活检只获取了胃的表浅黏膜,这在很大程度上限制了它们对整个肿瘤的代表性和与手术切除组织的可比性(图S2C)。事实上,mIF显示术前TME中的免疫细胞浸润在响应者和非响应者之间没有差异(修订后的图S2D),这可能是由于活检深度有限和胃癌内肿瘤的显著异质性。
总体而言,这些结果表明术后TME与对术前化疗的反应相关。

血清免疫蛋白质组学与TME之间的相关性

鉴于大多数现有的癌症免疫学研究集中在肿瘤微环境(TME)上,我们评估了全身免疫与TME之间的相关性。我们还确定了血清免疫蛋白质组学与TME中免疫细胞浸润之间的相关性。有趣的是,术后TME似乎与术前而非术后血清免疫蛋白质组学更相关。即使在样本数量较少的情况下,术前血清免疫蛋白质组学与免疫细胞浸润之间的相关性总体上更强(图3A和图3B)。例如,更高的术前血清纤维母细胞生长因子21(FGF21)水平与CD68+巨噬细胞的浸润较少呈相关,而更高的术前血清转化生长因子b1(TGF-b1)水平与CD4+T细胞的浸润较多呈相关(图3C和图3D)。事实上,据报道TGF-b在调节效应器和调节性CD4阳性细胞反应方面具有多效性。 术后血清免疫蛋白质组学与术后免疫细胞浸润之间的相关性也被观察到。例如,更高的术前血清C-C基序化学因子配体11(CCL11)水平与CD4+/FOXP3+ T细胞的浸润较多呈相关(图3E)。王等人报道CCL11增加了乳腺癌中CD4+CD25+Foxp3+调节性T细胞(Tregs)的比例。需要进一步的研究来探讨CCL11是否在胃癌中调节CD4+Foxp3+Treg细胞功能。
我们还评估了术后血清蛋白水平与92个免疫基因的肿瘤mRNA水平之间的相关性。其中有5个免疫基因的相关性具有统计学意义,仅有两个是正相关的,符合预期(图3F和图S3A–S3E)。TNFSF12和CCL4的相关性实际上是边缘的(图S3A和图S3B)。血清蛋白水平与组织基因mRNA水平之间的相关性总体上较弱。
这些结果显示了全身性免疫与肿瘤微环境之间的相互通信和相互依赖关系。对肿瘤微环境的研究无法充分揭示免疫系统如何全面应对胃癌和抗肿瘤治疗。应该投入更多的努力来对患有胃癌的患者进行系统性免疫分析。

经典全身性免疫炎症指标的临床价值

经典全身性免疫炎症指标大多基于血细胞比率,并已证明与患者的临床结局相关。 我们对血清免疫蛋白质组学与经典全身性免疫炎症指标之间的关系感到好奇。因此,我们评估了术后血清免疫蛋白质组学与经典免疫炎症指标之间的相关性,包括中性粒细胞与淋巴细胞比值(NLR)、血小板与淋巴细胞比值(PLR)、单核细胞与淋巴细胞比值(MLR)以及血小板分布宽度(PDW)以及常见血细胞计数。尽管大多数相关性相对较弱(图S3F),但血清CXCL5和CXCL1水平与血小板计数呈强相关(图S3G和图S3H)。由于CXCL1和CXCL5通常参与中性粒细胞的稳态和功能,需要更多的工作来理解这种意外但有趣的相关性。我们还评估了经典全身性免疫炎症指标与TME特征之间的关系。术后经典免疫炎症指标与TME中的免疫细胞浸润之间没有观察到相关性(图S3I)。
我们进一步探讨了经典全身性免疫炎症指标的临床价值,并评估了中性粒细胞与淋巴细胞比值(NLR)、血小板与淋巴细胞比值(PLR)、单核细胞与淋巴细胞比值(MLR)和血小板分布宽度(PDW)的治疗响应预测价值。我们绘制了这四个指标的受试者工作特征(ROC)曲线,最高的曲线下面积(AUC)为0.602(图S3J)。比例风险回归显示了这四个指标的预后价值。在单变量Cox回归中,这些指标对于OS或PFS均未显示出显著的预后价值,而在多变量Cox回归中,较高的NLR与较短的OS相关,危险比为1.172(95% CI,1.0066–1.3639)(图S3K和图S3L)。相应地,先前的报告已经显示NLR是胃食管交界和胃腺癌的负面预后因子。总体而言,这四个指标的预后价值有限。

术后肿瘤基质PD-L1水平和术前血清PD-L1水平均可预测术前化疗反应

PD-L1是关键的免疫调控分子。与其受体PD-1相互作用时,PD-L1抑制细胞毒性T细胞的免疫反应,从而参与肿瘤免疫逃逸。Choi等人基于CLASSIC试验队列报告称,基质PD-L1水平可以预测在第II/III期胃癌D2胃切除术后的辅助化疗效果。利用基于PD1/PDL1免疫组织化学染色的类似评分系统,我们发现非响应者在手术切除的肿瘤样本中基质PD-L1染色分数较高的趋势(图4A和图4B)。基质PD-1染色显示了类似的趋势,尽管这在统计学上并不显著(图S4A和图S4B)。然而,肿瘤区域的PD-L1染色与治疗反应没有相关性(图4A)。这些结果表明肿瘤中的基质PD-L1水平可以预测术前化疗的反应,并表明PD-1/PD-L1途径可能在胃癌的化疗抵抗中起到作用。
然而,由于其延迟性,术后基质PD-L1的反应预测价值可能会受到较大的限制。理想的预测因子应该是术前的。术前内镜活检的基质PD-L1染色无法预测治疗反应(图S4C和图S4D)。因此,我们进一步评估了术前血清PD-L1水平的临床意义。有趣的是,术前血清PD-L1水平在不同治疗反应的患者中显示出差异(图4C)。在治疗前,响应者的血清PD-L1水平较低,而治疗似乎减弱了这种差异,因为在术后样本中未观察到显著差异(图4E)。利用ROC曲线评估术前和术后血清PD-L1水平的治疗响应预测价值。术前血清PD-L1水平的AUC为0.737(95% CI,0.569–0.904),而术后血清PD-L1水平的AUC约为0.5(图4D和图4F),表明术前血清PD-L1水平是术前化疗的有希望的治疗响应预测因子。术前血清PD-L1水平较高(>5.084归一化蛋白表达[NPX])的患者倾向于对术前化疗显示较差的治疗反应(图S4E)。
我们还评估了不同治疗反应患者的治疗期血清PD-L1水平。在响应者中,血清PD-L1在治疗过程中似乎有所增加。响应者的治疗期血清PD-L1水平显著较高(图S4F和图S4G)。这种差异的一个潜在原因可能是肿瘤细胞的破坏。需要更多样本和进一步研究来确认这一发现并揭示潜在机制。进一步测量了PD-L1/PD-1水平与血清PD-L1水平之间的病理学相关性。在不同的配对中,术前血清PD-L1水平和术后基质PD-1水平显示出最强的相关性(图S4H)。术前血清PD-L1水平可能与化疗后肿瘤中PD-1+免疫细胞的浸润有关。
总体而言,这些结果表明,术后肿瘤基质PD-L1水平和术前血清PD-L1水平均可以预测术前化疗的反应,而术前血清PD-L1水平应具有更大的临床意义。

术前血清CCL20水平预测术前化疗的反应

受PD-L1的发现启发,我们进一步比较了不同治疗反应患者的术前血清免疫蛋白质组学,结果显示10种蛋白质具有p <0.05的差异。其中,术前CCL20水平显示出最显著的差异。值得注意的是,我们还比较了不同治疗反应患者的术后血清免疫蛋白质组学,与术前样本相比,差异要弱得多(图S5A)。
近期的研究已经确立了CCL20在不同癌症中作为化疗抵抗的重要介质。正如图S5B所总结的,Chen等人报告称,化疗通过核因子kB(NF-kB)和CCL20之间的正反馈环路诱导CCL20,并通过上调乳腺癌中的ATP结合盒亚家族B成员1(ABCB1)表达介导化疗抵抗。Wang等人报告称,化疗通过FOXO1/CEBPB/NF-kB信号途径在结直肠癌细胞中上调CCL20,而分泌的CCL20招募调节性T细胞,促进化疗抵抗。Liu等人报告称,顺铂刺激的经典活化巨噬细胞(CAMs)通过增加CCL20的产生促进卵巢癌细胞迁移。总体而言,现有的研究表明,CCL20的上调是由化疗引起的,并且增加的CCL20产生促进了化疗抵抗。
然而,我们的研究发现上述模型在胃癌中可能不成立。我们发现,在术前化疗的响应者中,治疗开始前血清CCL20水平显著较低(图5B)。术前血清CCL20水平预测治疗反应,AUC为0.769(95% CI,0.614–0.925)(图5C),表明胃癌患者在治疗前的血清CCL20水平存在差异。与现有的研究结果一致,非响应者的肿瘤中CCL20 mRNA水平上调(图5D)。然而,治疗后血清CCL20水平在响应者和非响应者之间没有差异,表明血清和肿瘤CCL20水平脱钩(图5E)。有趣的是,参考沈等人报道的可切除胃癌的血清和组织蛋白质组学,我们发现胃癌患者的血清CCL20水平相对于健康人有所升高(图5F)。肿瘤样本中CCL20蛋白水平也较正常胃组织高(图S5C)。然而,通过胃切除手术切除肿瘤并没有恢复血清CCL20水平,而是进一步增加了血清CCL20水平(图5F)。这些结果表明,血清CCL20并不是肿瘤CCL20的系统反映,而是系统免疫对胃癌和化疗的重要组成部分。
我们还验证了现有研究提出的CCL20上调的信号模型。Kim等人收集了在接受第一线标准化疗但未接受PD-1阻断的治疗前和治疗过程中胃活检样本的治疗前患者。我们分析了他们的转录组数据,并发现化疗并没有增加肿瘤样本中CCL20 mRNA水平。相反,化疗后CCL20 mRNA水平下降(图5G)。这一发现挑战了CCL20在胃癌中是由化疗引起的假设。与此同时,ABCB1、CEBPB和FOXO1 mRNA水平在不同反应的肿瘤之间(图5H)以及在化疗前后活检样本之间(图S5D)也没有差异。相反,更高的术前血清CCL20水平与肿瘤中CD4+T细胞的浸润较少相关(图S5E)。CD4+T细胞介导免疫应答,在实现对肿瘤的调节和有效免疫应答中至关重要。与此同时,更高的术前血清CCL20水平与更多基质中PD-1+或PD-L1+细胞的浸润相关(图5I和图S5F),这应该是肿瘤免疫逃逸的关键介质。总体而言,这些结果表明,血清CCL20诱导了一个针对化疗的系统免疫抑制环境。

正如图5J所总结的,现有的研究提出,肿瘤中CCL20的上调是由化疗引起的,而增加的CCL20产生促进了化疗抵抗。然而,我们发现在化疗开始前患者的血清CCL20水平存在差异。术前血清CCL20水平较高的患者倾向于具有较差的治疗反应。潜在机制是血清CCL20诱导了一个系统性的免疫抑制环境。这些发现提示,在术前血清CCL20水平较高的患者中,免疫治疗可能与化疗的结合更为有效。已经投入了大量努力来开发CCR6-CCL20轴(CCR6是CCL20的细胞受体)的抑制剂。通过抗体或拮抗剂干扰CCR6-CCL20轴在癌症治疗中显示出潜力。术前血清CCL20水平可能有助于选择那些有望从CCR6-CCL20抑制剂中受益的患者。此外,这些发现表明术前期是通过血清蛋白标志物进行患者分层的一个不可替代的时间窗口。因此,我们决定进一步建立一个用于预测术前化疗反应的术前血清蛋白组合。

一个用于预测术前化疗反应的术前血清蛋白评分系统

通过比较不同治疗反应患者的术前血清蛋白水平(图5A),我们将15个p<0.1的蛋白包括在一致性聚类中。基于一致性累积分布函数(CDF)图、增量面积图以及对一致性矩阵的手动检查,我们发现了四个术前血清亚型(图6A、6B和图S6A–S6H)。其中,cluster 2与患者的明显更好的治疗反应相关(图6C)。这种无审查的聚类还与患者的临床特征相关,如肿瘤的Lauren分类。Cluster 1和4与更高比例的腺癌肿瘤类型相关(图6D)。
考虑到临床实用性,我们进一步使用最小绝对值收缩和选择算子(LASSO)模型建立了一个用于预测术前化疗反应的术前血清响应预测分数(PSRscore)(图S6I和S6J)。简而言之,LASSO回归是一种使用收缩进行变量选择或参数消除的线性回归类型。通过适当的l值,PSRscore的公式限制为四个蛋白质的血清水平:CCL3、IL-15Ra、CXCL5和CCL20(图6F和图S6K)。PSRscore的ROC曲线,AUC为0.907(95% CI,0.814–1.000),确定了截断值为-0.843(图6E)。患者被分为PSRscore高组和低组(图6F)。低PSRscore与明显较差的治疗反应相关(图6G)。此外,PSRscore低的患者在术后肿瘤中数值上具有更多PD1+/PD-L1+细胞的基质浸润和更高的肿瘤PD-L1染色(图6H和图S6L),这通常导致对抗PD-1/PDL1疗法的适应症。
除了CCL20外,PSRscore还包括CCL3、IL-15Ra和CXCL5的术前血清水平。较高的血清CCL3和IL-15Ra水平以及较低的CXCL5水平与较差的治疗反应相关(图S6K)。研究表明,CCL3参与了不同癌症中的免疫逃逸和化疗抵抗。高水平的CCL3与Tregs、肿瘤相关巨噬细胞(TAMs)和髓系源性抑制细胞(MDSCs)的肿瘤内浸润增加相关。CCL3驱动的TAMs招募已被认为是转移性巢穴的驱动事件。已经开发了CCL3的中和抗体和抑制剂,并在抗癌治疗中显示出潜力。 目前对IL-15Ra和CXCL5在化疗抵抗中的作用了解有限,需要更多研究来探索它们在胃癌中的功能。
PSRscore评分系统有助于分层胃腺癌患者,并筛选出那些可能不能仅通过术前化疗获益的患者。对于这组患者,我们的工作强烈暗示患者可能从免疫治疗的组合中受益,如免疫检查点抑制剂(ICIs)或CCL3/20中和抗体/抑制剂(图6I)。可以设计前瞻性试验来验证这一策略,并需要建立一个验证队列来验证此评分系统的灵敏性和特异性。

TME和血清免疫蛋白组学的预后价值

我们进一步评估了TME和血清免疫蛋白组学的预后价值。在多变量Cox回归中包括了在单变量Cox回归中具有预测价值的所有基本临床特征以及年龄和性别(表S2和S3)。显示为OS或PFS预测因子的免疫细胞与其风险比一起列在森林图中(图S7A和S7B)。绘制了代表性生存预测因子的Kaplan-Meier曲线(图S7C–S7F)。没有免疫细胞类型是OS的独立预测因子,而CD68+巨噬细胞的浸润通过log rank测试、单变量Cox回归和多变量Cox回归证实,预测PFS缩短(图S7C)。虽然不是独立的,CD68+巨噬细胞的浸润也通过log rank测试显示为OS的负面预后因子(图S7D)。
显示为OS或PFS预测因子的术前和术后血清蛋白也在森林图中列出,与其风险比一起(图7A、7B、S7G和S7H)。绘制了代表性生存预测因子的Kaplan-Meier曲线(图7C、7D、S7I和S7J)。其中,高术后血清IL-10RB水平与显著缩短的OS和PFS均相关,通过log rank测试、单变量Cox回归和多变量Cox回归证实(图7C和7D)。这表明术后血清IL-10RB水平是接受术前化疗的患者的强烈负面生存预测因子。值得注意的是,术后IL-10RB水平在术前化疗后显著升高,表明其可能参与术前化疗的反应(图S1D)。关于IL-10信号在胃癌中的作用的研究还有限。需要更多的工作来了解IL-10RB在胃癌术前治疗中的作用。

讨论

在过去的十年里,人们致力于揭示免疫在癌症中的作用。免疫疗法在胃癌治疗中取得了突破,免疫检查点抑制剂成为晚期胃或食管腺癌的一线治疗方法。然而,在胃癌围手术期治疗中,目前没有治疗方法成功挑战了化疗的主导地位。免疫被认为在患者受益于围手术期化疗中起着关键作用。现有研究重点关注肿瘤微环境中局部免疫反应,而对胃癌免疫的改善理解必须特别评估全身性免疫。我们使用血清免疫蛋白组学和经典全身性免疫炎症指标来描述全身免疫,并研究其与肿瘤微环境以及治疗反应的关联。我们发现围手术期治疗诱导了复杂的全身性免疫反应,这表现为动态的免疫蛋白组学。同时,对治疗反应更好的患者在治疗后显示出更具动态性的血清免疫蛋白组学变化。肿瘤微环境也显示与围手术期化疗的反应有关。然而,在治疗开始之前预测潜在的治疗反应将更加实际。令人兴奋的是,我们发现PD-L1和CCL20的术前血清水平是围手术期化疗反应的预测因子,与它们在免疫抑制中的已知作用一致。进一步建立了一个术前血清蛋白质组学面板用于预测反应,能够精确地筛选出可能不会单独对围手术期化疗产生反应的患者。对于这部分患者,我们相信他们将从免疫疗法和化疗的联合治疗中受益。同时,IL-10RB的术后血清水平也被确认为胃癌患者预后的强大预测因子。
肿瘤内PD-L1在免疫抑制和化疗抵抗中的作用已经得到确认。然而,关于可溶性PD-L1的研究有限。我们的研究发现,在化疗开始之前,患者的血清PD-L1水平存在差异。对化疗产生反应的患者往往具有较低的血清PD-L1水平。需要进一步研究可溶性PD-L1在化疗抵抗中是否发挥作用。在CCL20中也发现了类似的发现,这是一种已知参与各种癌症化疗抵抗的趋化因子。我们的研究表明,在其他癌症类型中提出的CCL20诱导的化疗抵抗模型在胃癌中可能不成立。将CCL20的变化视为化疗的结果,剥夺了临床医生在治疗前对患者进行分层和干预的主动性。相反,我们的发现显示,在化疗开始之前,对化疗产生不同反应的患者在血清免疫蛋白组学上存在差异,这提前了患者分层和干预的时间窗口。在PD-L1和CCL20的启发下,我们开发了一个用于预测围手术期化疗反应的术前血清蛋白质组学面板,称为PSRscore。通过计算四种免疫蛋白的术前血清蛋白水平,患者可以被分为两组。PSRscore低的患者往往具有较差的治疗反应,并可能从免疫疗法的联合治疗中获益。这种评分系统在患者分层方面具有很大的临床应用潜力。值得注意的是,PSRscore的建立基于一个接受铂类化疗的亚洲队列。这些免疫标志物在接受紫杉醇为基础的方案的非亚洲患者中的表现需要进一步验证。
我们相信血清蛋白标志物在胃癌患者术前分层中具有特殊的临床意义。几乎所有现有的胃癌分子分类都依赖于手术或内镜切除的肿瘤组织。以TCGA分类为最著名的例子,微卫星不稳定(MSI)型患者被证明更容易从免疫疗法中受益,而基因组稳定(GS)型患者对化疗反应较差。然而,这些分子分类在临床实践中很少使用。一个重要原因是大多数分子分类依赖于复杂的分子技术,如qPCR、原位杂交,甚至是组学技术,这在大多数临床中是不可获得的。此外,在胃癌中,术前获取肿瘤样本依赖于胃镜活检。胃癌存在显著的肿瘤内异质性,且活检深度有限,这在很大程度上影响了活检样本的代表性。因此,在胃切除术之前确定胃癌的分子分类一直非常困难。相比之下,血清蛋白质组学涵盖了系统和肿瘤局部特征,因此具有灵敏性和信息性。在临床中,可以轻松获取血清样本,对患者造成的损害有限。像前列腺特异性抗原(PSA)或甲胎蛋白(AFP)这样的血清蛋白标志物已经几十年用于癌症的诊断和随访。各种医院都广泛提供用于测量血清蛋白的设备和培训人员。这些因素赋予了胃癌血清蛋白质组学研究在临床上巨大的意义。未来应建立胃癌的血清蛋白分类,以指导胃癌的围手术期治疗。

局限性

研究存在一些需要注意的局限性。首先,治疗期间血清样本的数量相对较小,这限制了得出某些结论的统计能力。其次,多重免疫荧光(mIF)只测量了肿瘤微环境(TME)中的关键免疫细胞。单细胞测序可以更好地描绘TME。第三,本研究的一些结论和建议应在接受术前化疗的患者的前瞻性队列甚至随机对照试验中进行进一步验证。在解释数据时应考虑这些局限性。
总的来说,我们对胃癌患者的全身免疫系统和肿瘤微环境进行了描述,并展示了它们与术前化疗反应的关联。我们鉴定了用于预测治疗反应和预后的血清生物标志物。这项工作强调了全身免疫在胃癌术前化疗中的基本但很大程度上被低估的作用,支持了一种基于术前血清免疫蛋白质组学的患者分层策略,并突显了在未来研究中全面描绘免疫的重要性。

]]>
翻译 预后模型 https://hexo.limour.top/Multiplex-immune-profiling-reveals-the-role-of-serum-immune-proteomics-in-predicting-response-to-preoperative-chemotherapy-of-gastric-cancer#disqus_thread