Limour's Blog https://hexo.limour.top/ Tue, 06 Aug 2024 09:03:04 GMT http://hexo.io/ 【探索】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://github.com/milesmcc/shynet">Shynet</a> 是一款用 python 编写的现代、隐私友好、无需Cookie或JS即可工作的网络流量统计工具。</p> <p>相比 <a href="https://githu 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
]]>
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://github.com/soulteary/docker-flare">Flare</a> 是一款轻量、快速、美观的个人导航页面,适用于 HomeLab 或其他注重私密的场景。</p> <ul> <li><a href="/Docker-bu 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://github.com/mlc-ai/mlc-llm">MLC-LLM</a> 是一种大模型高性能通用部署解决方案,可以通过预编译加速使用本机API原生部署任何大型语言模型。该项目的使命是利用ML编译技术,使每个人都能在其设备上本地开发、优化 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
【转载】圈中人 https://hexo.limour.top/repost-in-circle https://hexo.limour.top/repost-in-circle Thu, 16 Nov 2023 06:59:47 GMT <blockquote> <p>《<a href="https://web.archive.org/web/20230909170041/https://blog.cxplay.org/works/in-circle/">圈中人</a>》 from <a href="https:

圈中人》 from CXPLAY World

外面很危险, 于是有人在地上画了一圈并对我说: “这个圈外很危险, 你不要随便出去, 我会帮你对付这些危险, 所以我很忙, 但我也会派人监督你.”;

监督人来了, 他对我说: "你知道要怎么做了吧?但为了防止你总是不小心碰到边界, 我会给你的粗心大意一些小小的惩罚, 好让你长记性, 毕竟我要监督的人可不止你一个. ", 于是监督人在圈内画了一个更小的圈.

最后, 我发现睡觉的时候翻个身也总是不小心超出监督人画的圈, 于是我自己给自己画了一个圈, 好让我只能站在原地不得动弹就再也不能睡觉, 也就不会出现无意识地超出圈外了;

但我好像忘记了我本来是可以出去的, 但是由于缺少保护自己的经验, 我也渐渐不敢出去了. 因为相比之下, 监督人的惩罚显得比圈外的危险来得更具体更具有危险性, 我也更有经验去应付.

正文

这里的人常说, 如今这个地方变成这个样子完全是由于那座灯塔.

在那个我还未曾触及到的时代, 这里的格局并不是这个样子, 这里和外面的世界都是一样的一马平川无所遮拦. 没有如今形同棋盘一样的森严布局, 更没有那座灯塔. 但是不知道什么时候, 似乎是从巨墙开始, 有一部分人开始为这个地方建起了障碍.

起初只是一道篱笆, 一个土沟, 再后来不约而同地全都变成了深红色质地的石块, 这种材料从来没有听说过是从什么地方开采出来, 但就现在的状态来看, 这墙就好像是从地底自己长出来一般: 整齐划一, 密不透风, 坚不可摧. 不过现在也有人觉察到, 曾经光滑细密的红墙上出现了裂缝, 但这裂缝实质上与这墙并没有多大关系, 因为裂缝时而出现时而消失, 没有人知道红墙是如何做到这种程度的自愈的, 就好像它是活的一样.

再说起那个灯塔, 它的材质和红墙并不一样, 但据说两者建立起来的时间都是相同的. 灯塔没有密不透风的样子, 它的表面就像积木拼接一般有很明显的缝隙, 甚至有的地方还有空缺, 不过这些缺陷也和墙上的缝隙一样时而消失时而出现. 但就我认知之中看来, 人人都说这座灯塔是这里的每个人亲手筑起的, 却没有人知道这墙的成因, 可能是因为人人心里都明白但都心照不宣, 也可能是因为它本来就是自己从这块大地上 “生长” 出来的, 因为它的材质从今天看始终都不像是这个大陆上应该存在的东西.


那片生长在红墙上的蓝色平原, 听说最开始的时候和墙的颜色一样, 是红色的, 后来平原的住民们逐渐发现所有的植物开始变成了蓝色, 土壤也变成了深蓝色. 由此原来单调的墙上多出来一片蓝色, 也吸引了更多的斗篷们过来在平原上定居. 一位从红色时期就开始在这里居住的斗篷曾经和我说, 目前来说这里变成蓝色和红色的时候并没有什么差别, 但其他斗篷们却都不约而同的住进了这里, 问起这些新来者理由却一个二个都含糊其词甚至回答不上来到底是为什么来到这里, 听得最多的一个理由就是: “因为大家都来这里了.” 事实真是这样的话, 那这个平原上早就已经人满为患了, 有相当部分人踏足这里之后就离去了, 还有部分人暂住过一段时间后也悄然离去, 他们留下的痕迹会很快地消失, 绵密的蓝色植被会再次长满被斗篷践踏的区域, 平原变得就好像从来没有人来过的一样.

当我问起这位斗篷他自己来这里的理由的时候, 它也沉默了, 但没有很久, 它问我: “你喜欢听故事吗?”, 我说: “是真相吗?”, “我也… 不知道.” 他回答我.

我接着说: “好吧, 虽然我不是来听故事的, 但如果是你愿意说给我听的故事, 我也愿闻其详.”

斗篷没有办法呈现出背后人的表情, 所以我在这面具上也办法没找到什么破绽, 它拿起了篝火边的一根细木柴在灰烬边画了起来.

它画了一个圆圈: “我们现在的位置应该是这里, 应该是.” 它在圆圈边上加了一个小圆圈, 指着它说.

“但如果我再把里面的「棋盘」画出来.” 它在大圆中画了几个小圆.

“如果我没猜错, 你我都是这棋盘里面的人, 你应该会明白.” 它接着在大圆和几个小圆之间用另外一个内圆隔开了, 出现了一个同心圆套一堆小圆圈的图画. 最后它在同心圆里面套了更多的圆, 为了套下更多的圆还把几个小圆擦去了, 几个小圆最后被数个大圆套在里面, 整个画面就好像一个靶子一样.

它放下了木柴, 对我说 “曾经我也是这样一个个小圆中的人, 总以为外面有什么好东西, 想要出去看看.”

“后来, 我真的出去了, 这个小圆不再是束缚, 但我发现外面的世界有更多的圆圈.” 它指着那些同心圆一路向外, 最后到了代表我们位置的那个大圆边缘上的小圆.

“最后我费尽千辛万苦, 终于从这些圆里出来了, 到了这里, 现在被叫做「蓝色平原」的地方.” 它又拿起细木柴握在手心.

“还是红色的那个时候, 我心里就想, 这里也许就是这世界的最后一个圆了吧?”

“说实话, 现在我们这个圆外面的景色从来就没变过, 我最后本来打算继续往外去亲眼看看那些五彩斑斓的风景的.”

它沉默下来, 用木柴继续在最外围的圆上加了一个更大的圆, 不过这次画布的面积不太够用了, 只画了一部分.

它指着新画的那个圆对我说: “有一天我在平原上看到一个蓝色的斗篷, 我一如既往地和它打着招呼, 但是它说着一口不是我们的语言, 它好像也听不懂我在说什么.”

“蓝色的斗篷好像也明白了我们两人语言不通, 就在脚下开始用手指在雪地上画画.”

“一个圈, 两个圈, 三个圈. 我已经记不清他当时画了多少个圈了, 最后同心圆的外围上也多出了一个代表着当时位置的小圆.”

“看起来就和现在这幅画几乎一模一样, 我现在都不知道它当时到底是在往圈外走还是进到一个新的圈里. 「蓝色平原」这个小圈又在什么位置呢?” 它又放下了木柴.

“蓝斗篷画完之后我明白了它的意思, 它好像要往我曾经来过的路上走, 我也没有阻拦它.”

“因为我知道, 它以为它正在向圈外走, 就和我当时一样, 也许是以为圈外有什么好东西.”

我说: “那你觉得你有找到什么 ‘好东西’ 吗?”

它说: “没什么好东西, 但比起最里面令人窒息的小圈, 这里确实呼吸以及行动上更加自由, 不过…”

“不过?” 我接他的话道.

“圈里来的人总是以为这里就是真正的圈外, 喜欢在这些相对自由的地方胡作非为, 大声喧哗. 不过最后它们留下的痕迹总是会被自然而然地抹除, 也无所谓了.” 它的语气变得轻松了起来.

我问: “你觉得这些 ‘圈’, 或者这些墙是从什么地方来的? 我从里面出来的时候好像没有遇到很多的这些 ‘圈’ ?”

它说: “我不知道, 我也和后来的人谈过几次这类话题, 他们的回答和你一样, 他们从里面 ‘出来’ 的时候遇到的墙确实和我曾经遇到的数量或者规模上都有很大差别.”

最后我和它又随便聊了一些有关墙边逐渐侵入的黑藤的事情, 不过由于它很久没有出过这个平原, 所以连黑藤是什么它都不清楚, 它也表示出一种漠不关心的态度, 所以我也没有继续问下去. 离开的时候, 它叮嘱我注意一下平原上的人, 因为它察觉到最近平原上的人越来越稀少了, 如果有什么发现, 希望能够和它分享一下.


我把这类驻守在「蓝色平原」或者墙上任何地方上的人成为 “守墙人”, 他们虽然身处大多数的圈之外, 但却一般不愿意接触新的事物, 极端的守墙人甚至不愿意和斗篷乃至其他守墙人交流, 还好我遇到的是一个尚且能和其他人交流的斗篷, 不然这个平原上的很多事情我也无从得知.

之于这个 “圈” 的问题, 在「棋盘」中的时候我就曾经发现, 大部分的小圈中人都不关心圈外的事情, 甚至都不知道有圈存在, 更不用说去突破这圈了, 和这类永远不会走出圈的人打交道总是要先自己去适应它自己的圈, 否则他们也会和守墙人一样把你拒之 “圈” 外.

但相对的越往外, 这些 “圈” 的意味逐渐变得模糊不清, 没有人知道是谁建起了它, 也从来没有人宣布过自己有关于它的事迹.

]]>
转载 https://hexo.limour.top/repost-in-circle#disqus_thread
【探索】基于WebSocket的内网穿透工具 https://hexo.limour.top/WebSocket-based-intranet-penetration-tool https://hexo.limour.top/WebSocket-based-intranet-penetration-tool Thu, 09 Nov 2023 11:38:50 GMT 国内的服务器备案麻烦,所以很多内网服务需要使用内网穿透工具。之前尝试使用QUIC来伪装,但不稳定。现在找到了一个特征少的内网穿透工具ProxyNT,可以通过NAT和防火墙将本地服务器暴露到公网上。使用Docker部署服务端和客户端,配置相应的参数后即可使用。 国内的服务器除了挂个备案,不想再要了。而许多内网的服务需要在外网访问,内网穿透是必不可少的。但是用国外的服务器的话,需要过一层未知的东西,难免被误伤,融入汪洋大海也是必须的。之前折腾了一下通过套一层QUIC来伪装,不知道为什么,总是不稳定。寻寻觅觅,又找到一个特征少的内网穿透工具:ProxyNT 。ProxyNT是一个用python编写的基于WebSocket的反向代理服务器,可以透过NAT和防火墙将本地服务器暴露到公网上,从原理看,套上一层CDN保护公网ip也是可以的。

服务端

1
2
3
4
mkdir -p ~/app/proxynt && cd ~/app/proxynt && nano Dockerfile && nano docker-compose.yml
docker build -t limour/proxynt .
nano config.json
sudo docker-compose up -d
1
2
3
4
FROM python:3.9-alpine
RUN pip install -U python-snappy
RUN pip install -U https://github.com/sazima/proxynt/archive/refs/heads/snappy.zip
ENTRYPOINT ["nt_server", "-c", "/opt/config.json"]
1
2
3
4
5
6
7
8
9
10
11
12
13
version: '3.3'
services:
proxynt:
restart: unless-stopped
volumes:
- './config.json:/opt/config.json'
- '/etc/localtime:/etc/localtime:ro'
image: limour/proxynt

networks:
default:
external: true
name: ngpm
1
2
3
4
5
6
7
8
9
10
{
"port": 18888,
"log_file": "/dev/null",
"path": "/websocket_path",
"password": "helloworld",
"admin": {
"enable": true,
"admin_password": "new_password"
}
}

反代 proxynt:18888

客户端

1
2
3
4
5
6
mkdir -p ~/app/proxynt && cd ~/app/proxynt
# pip install --upgrade pip -i https://pypi.tuna.tsinghua.edu.cn/simple
# pip install --use-pep517 python-snappy -i https://pypi.tuna.tsinghua.edu.cn/simple
pip install -U python-snappy -i https://pypi.tuna.tsinghua.edu.cn/simple
pip install -U https://xxx.limour.top/token/https://github.com/sazima/proxynt/archive/refs/heads/snappy.zip
whereis nt_client
1
2
3
4
5
6
7
8
nano config.json
nt_client -c config.json # 测试
nano proxynt.sh && chmod +x proxynt.sh
nano proxynt.service
sudo mv proxynt.service /etc/systemd/system/proxynt.service
sudo systemctl enable proxynt
sudo systemctl start proxynt
sudo systemctl status proxynt
1
2
3
4
5
6
7
8
9
{
"server": {
"url": "wss://limour.top:443/websocket_path",
"password": "helloworld",
"compress": true
},
"client_name": "home_pc",
"log_file": "/home/limour/app/proxynt/nt.log"
}
1
2
3
#!/bin/sh
export PYTHONPATH=/home/limour/.local/lib/python3.10/site-packages
/home/limour/.local/bin/nt_client -c /home/limour/app/proxynt/config.json
1
2
3
4
5
6
7
8
9
[Unit]
Description=proxynt
After=network.target
[Service]
ExecStart=/home/limour/app/proxynt/proxynt.sh
ExecReload=/bin/kill -HUP $MAINPID
Restart=on-failure
[Install]
WantedBy=multi-user.target
  • 访问 https://limour.top:443/websocket_path/admin
  • 看到客户端上线后,新建配置即可

附加 WebSSH

和上面的内网穿透配合,连接时host填proxynt,可以保证内网ssh不暴露公网的同时,又能通过公网进行ssh连接。

1
2
mkdir -p ~/app/webssh && cd ~/app/webssh && 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.3'
services:
webssh:
restart: unless-stopped
environment:
- GIN_MODE=release
- savePass=true
volumes:
- '/etc/localtime:/etc/localtime:ro'
image: jrohy/webssh:latest

networks:
default:
external: true
name: ngpm

反代 webssh:5032

]]>
探索 docker ngpm 内网穿透 ssh ws https://hexo.limour.top/WebSocket-based-intranet-penetration-tool#disqus_thread
【记录】自建去广告的DoH服务器 https://hexo.limour.top/Self-built-ad-blocking-DoH-server https://hexo.limour.top/Self-built-ad-blocking-DoH-server Sat, 28 Oct 2023 12:56:54 GMT <h2 id="进行部署">进行部署</h2> <ul> <li><a href="/Docker-bu-shu-Nginx-Proxy-Manager">反代服务</a></li> </ul> <figure class="highlight bash"><table><tr> 进行部署
1
2
3
4
5
mkdir -p ~/app/adguard && cd ~/app/adguard && nano docker-compose.yml
sudo docker-compose up -d # 面板端口 3000
# /opt/adguardhome/letsencrypt/live/npm-1/fullchain.pem
# /opt/adguardhome/letsencrypt/live/npm-1/privkey.pem
sed -i 's/allow_unencrypted_doh: false/allow_unencrypted_doh: true/' ./conf/AdGuardHome.yaml && sudo docker-compose restart
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
version: '3.3'
services:
adguard:
restart: unless-stopped
volumes:
- './work:/opt/adguardhome/work'
- './conf:/opt/adguardhome/conf'
- '/root/base/NGPM/letsencrypt:/opt/adguardhome/letsencrypt'
- '/etc/localtime:/etc/localtime:ro'
image: adguard/adguardhome:latest

networks:
default:
external: true
name: ngpm

DNS设置

  • 导航栏-设置-DNS设置
  • DNS 服务配置中启用DNSSEC


DoH设置

  • 导航栏-设置-DNS设置
  • 加密中启用加密
  • 证书可以设置为npm自动申请的证书
  • 反代 /dns-query, token保密不要泄露
  • token后面没有/, dns-query后面有/
  • 在chrome的设置-隐私和安全-安全-DNS中填入https://my.com/token
  • 回到仪表盘,看看有没有记录到DNS查询

]]>
docker ngpm DoH https://hexo.limour.top/Self-built-ad-blocking-DoH-server#disqus_thread
【探索】基于QUIC的内网穿透协议 https://hexo.limour.top/Protocol-for-intranet-penetration-based-on-QUIC https://hexo.limour.top/Protocol-for-intranet-penetration-based-on-QUIC Fri, 27 Oct 2023 12:46:07 GMT <h2 id="环境和依赖">环境和依赖</h2> <ul> <li><a href="/-ji-lu--an-zhuang-npsfrp-fu-wu-duan-yu-ke-hu-duan">内网穿透服务</a></li> <li><a href="/Docker-bu-shu- 环境和依赖
1
2
3
4
5
6
mkdir -p ~/base/NPS && cd ~/base/NPS && mkdir conf
nano docker-compose.yml
nano conf/nps.conf
touch conf/{clients,hosts,tasks}.json
sudo docker-compose up -d
# 反代 dashboard 8080
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
version: '3.3'
services:
nps:
container_name: nps
restart: unless-stopped
ports:
- '8025:8025'
- '6000-6002:6000-6002/udp'
volumes:
- './conf:/conf'
- '/etc/localtime:/etc/localtime:ro'
image: yisier1/nps

networks:
default:
external: true
name: ngpm
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
appname = nps
#Boot mode(dev|pro)
runmode = pro

#HTTP(S) proxy port, no startup if empty
http_proxy_ip=0.0.0.0
http_proxy_port=18081

##bridge
bridge_type=tcp
bridge_port=8024
bridge_ip=0.0.0.0
tls_bridge_port=8025
tls_enable=true

#Traffic data persistence interval(minute)
#Ignorance means no persistence
#flow_store_interval=1

# log level LevelEmergency->0 LevelAlert->1 LevelCritical->2 LevelError->3 LevelWarning->4 LevelNotice->5 LevelInformational->6 LevelDebug->7
log_level=7
#log_path=nps.log

#Whether to restrict IP access, true or false or ignore
#ip_limit=true

#allow_ports=9001-9009,10001,11000-12000

#Web management multi-user login
allow_user_login=false
allow_user_register=false
allow_user_change_username=false

#extension
allow_flow_limit=false
allow_rate_limit=false
allow_tunnel_num_limit=false
allow_local_proxy=false
allow_connection_num_limit=false
allow_multi_ip=false
system_info_display=true

#cache
http_cache=false
http_cache_length=100

#get origin ip
http_add_origin_header=true

#pprof debug options
#pprof_ip=0.0.0.0
#pprof_port=9999

#client disconnect timeout
disconnect_timeout=60

# 以下的需要进行配置
# Public password, which clients can use to connect to the server
# After the connection, the server will be able to open relevant ports and parse related domain names according to its own configuration file.
public_vkey=<16个字符>

#Web API unauthenticated IP address(the len of auth_crypt_key must be 16)
#Remove comments if needed
auth_key=<24个字符>
auth_crypt_key=<16个字符>

#web
web_host=limour.top
web_username=Limour
web_password=<16个字符>
web_port = 8080
web_ip=0.0.0.0
web_open_ssl=false
web_base_url=
open_captcha=true
# if web under proxy use sub path. like http://host/nps need this.
#web_base_url=/nps

#p2p
p2p_ip=<写服务器的ip>
p2p_port=6000
# 设置为6000,请在控制台防火墙开放6000~6002(额外添加2个端口)udp端口

配置端口映射

1
2
3
nano Port-Hopping.sh && chmod +x Port-Hopping.sh
nano /etc/systemd/system/Port-Hopping.service
systemctl enable Port-Hopping && systemctl start Port-Hopping && systemctl status Port-Hopping && iptables -t nat -L
1
2
3
4
5
#!/bin/bash
# IPv4
/usr/sbin/iptables -t nat -A PREROUTING -i eth0 -p udp --dport 32768:61000 -j DNAT --to-destination :3234
# IPv6
/usr/sbin/ip6tables -t nat -A PREROUTING -i eth0 -p udp --dport 32768:61000 -j DNAT --to-destination :3234
1
2
3
4
5
6
7
8
[Unit]
Description=Port-Hopping
After=network.target docker.service
[Service]
ExecStart=/root/Port-Hopping.sh
Restart=on-failure
[Install]
WantedBy=multi-user.target
1
2
iptables -t nat -A DOCKER -p udp --dport 32768:61000 -j DNAT --to-destination `iptables -t nat -L| grep "udp dpt:3234" | grep -oP 'to:\K[^ ]+'` # 添加
iptables -t nat -D DOCKER -p udp --dport 32768:61000 -j DNAT --to-destination `iptables -t nat -L| grep "udp dpts:32768:61000"| tail -n 1 | grep -oP 'to:\K[^ ]+'` # 删除

配置quic

1
2
3
4
sudo docker network create sswitch
mkdir -p ~/app/quic && cd ~/app/quic && nano docker-compose.yml
nano hysteria.yaml
sudo docker-compose up -d && sudo docker-compose logs
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
version: '3.9'
services:
hysteria:
image: tobyxdd/hysteria
restart: always
extra_hosts:
- 'host.docker.internal:host-gateway'
ports:
- '3234:3234/udp'
volumes:
- ./hysteria.yaml:/etc/hysteria.yaml
- /root/base/NGPM/letsencrypt:/home/ubuntu/letsencrypt
command: ["server", "-c", "/etc/hysteria.yaml"]

networks:
default:
external: true
name: sswitch
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
listen: :3234 

tls:
cert: /home/ubuntu/letsencrypt/live/npm-1/fullchain.pem
key: /home/ubuntu/letsencrypt/live/npm-1/privkey.pem

auth:
type: password
password: Se7RAuFZ8Lzg

bandwidth:
up: 3 mbps
down: 3 mbps

masquerade:
type: proxy
proxy:
url: https://hexo.limour.top/
rewriteHost: true

测试转发

  • 在客户端新建config.yaml, 写入以下内容
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
server: hexo.limour.top:32768-61000

auth: Se7RAuFZ8Lzg

bandwidth:
up: 3 mbps
down: 3 mbps

#socks5:
# listen: 127.0.0.1:1580

#http:
# listen: 127.0.0.1:8580

tcpForwarding:
- listen: 127.0.0.1:8025
remote: host.docker.internal:8025

测试穿透

1
.\npc.exe --server=127.0.0.1:8024 -vkey=<vkey> -type=tcp

客户端示例

1
2
3
mkdir -p ~/app/quic-npc && cd ~/app/quic-npc && nano docker-compose.yml
nano config.yaml
sudo docker-compose up -d && sudo docker-compose logs
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
version: '3.3'
services:
quic_nps:
image: tobyxdd/hysteria
network_mode: host
restart: always
volumes:
- ./config.yaml:/etc/config.yaml
command: ["--config", "/etc/config.yaml"]

npc_lk:
depends_on:
- quic_nps
network_mode: host
image: yisier1/npc
restart: unless-stopped
command: -server=127.0.0.1:8025 -vkey=<vkey> -tls_enable=true
1
2
3
4
5
6
7
8
9
10
11
server: hexo.limour.top:32768-61000

auth: Se7RAuFZ8Lzg

bandwidth:
up: 3 mbps
down: 3 mbps

tcpForwarding:
- listen: 127.0.0.1:8025
remote: host.docker.internal:8025
]]>
探索 docker ngpm 内网穿透 https://hexo.limour.top/Protocol-for-intranet-penetration-based-on-QUIC#disqus_thread
【学习】孟德尔随机化 https://hexo.limour.top/Mendelian-Randomization https://hexo.limour.top/Mendelian-Randomization Sat, 14 Oct 2023 09:18:54 GMT <h2 id="MR定义">MR定义</h2> <p>孟德尔随机化是一种基于全基因组测序数据(GWAS数据),利用单核首酸多态性(SNPs)作为工具变量(IV),用于揭示因果关系的新型流行病学方法,相较于队列研究等观察性研究,暴露在出生前便已确定,较少受到反向因果及混杂因素的影响 MR定义

孟德尔随机化是一种基于全基因组测序数据(GWAS数据),利用单核首酸多态性(SNPs)作为工具变量(IV),用于揭示因果关系的新型流行病学方法,相较于队列研究等观察性研究,暴露在出生前便已确定,较少受到反向因果及混杂因素的影响,因而能够有效减少偏倚。
RCT与MR的比较
MR的核心是运用遗传学数据作为桥梁,来探索某一暴露和某一结局之间的因果关联。与RCT将参与者随机分配到试验组或对照组类似,MR研究基于影响危险因素的一个或多个等位基因,对参与基因进行"随机化"(自然的随机化),以确定这些遗传变异的携带者与非携带者相比,是否具有不同的疾病发生风险,因此,孟德尔随机化可以被认为类似于"自然的随机对照试验"MR的相关术语

理论假设

  1. the variant is associated with the exposure
  2. the variant is not associated with the outcome via a confounding pathway
  3. the variant does not affect the outcome directly, only possibly indirectly via the exposure

孟德尔随机化框架的有向无环图表示

  1. 关联性假设:变异与暴露有关
  2. 独立性假设:变异与结果之间没有通过混杂途径相关
  3. 排他性假设:变异不直接影响结果,只可能通过暴露途径间接影响
  • 关联性假设:p值,F统计量,R^2
  • 排他性假设:与结局的相关性计算时,p值要大于0.05
  • MR-Egger回归相比线性回归可以弱化对排他性假设的要求

适用范围

  • 不确定先有鸡还是先有蛋,比如,到底是抑郁导致肺癌还是肺癌导致了抑郁?
  • 暴露因素难以测量,或者花费昂贵。例如,水溶性维生素等生物标志物的检测金标准可能成本太高,大样本无法承受,或者空腹血糖的测量需要隔夜空腹,可能不现实。
  • 暴露与结局数据来自同一人群,且不存在或存在少量可接受范围内的样本重叠

配置环境

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
conda create -n MR -c conda-forge r-devtools -y
conda activate MR
conda install -c conda-forge r-irkernel -y
Rscript -e "IRkernel::installspec(name='MR', displayname='MR')"
# Rscript -e "usethis::edit_r_environ()" # 设置 GITHUB_PAT
# nano ~/.Renviron # MRCIEU 真是超喜欢GITHUB,要访问一万次 api.github.com
conda install -c conda-forge r-rmarkdown -y
conda install -c conda-forge r-meta -y
wget https://github.com/MRCIEU/TwoSampleMR/archive/refs/heads/master.zip -O TwoSampleMR.zip
Rscript -e "devtools::install_local('TwoSampleMR.zip')"
wget https://github.com/MRCIEU/MRInstruments/archive/refs/heads/master.zip -O MRInstruments.zip
Rscript -e "devtools::install_local('MRInstruments.zip')"
conda install -c conda-forge r-susier -y
conda install -c bioconda bioconductor-variantannotation -y
wget https://github.com/MRCIEU/gwasglue/archive/refs/heads/master.zip -O gwasglue.zip
Rscript -e "devtools::install_local('gwasglue.zip')"
# wget https://github.com/MRCIEU/genetics.binaRies/archive/refs/heads/master.zip -O genetics.binaRies.zip
# Rscript -e "devtools::install_local('genetics.binaRies.zip')"
conda install -c bioconda plink -y
# whereis plink # /opt/conda/envs/MR/bin/plink
Rscript -e 'install.packages("MendelianRandomization")'

数据来源

一些参考数据

1
2
3
wget http://fileserve.mrcieu.ac.uk/ld/1kg.v3.tgz
tar -zxvf 1kg.v3.tgz
# mkdir EUR && mv EUR.* EUR

示例结局数据

1
2
3
4
5
6
7
8
9
10
11
# zcat ADHD2022_iPSYCH_deCODE_PGC.meta.gz | head
CHR SNP BP A1 A2 FRQ_A_38691 FRQ_U_186843 INFO OR SE P Direction Nca Nco
8 rs62513865 101592213 C T 0.925 0.937 0.981 0.99631 0.0175 0.8325 +---+++0-++-+ 38691 186843
8 rs79643588 106973048 G A 0.91 0.917 1 1.00411 0.0159 0.7967 ++--++-+-+-++ 38691 186843
8 rs17396518 108690829 T G 0.561 0.577 0.998 0.99611 0.0096 0.6876 --++-++??-+-- 37367 184388
8 rs983166 108681675 A C 0.57 0.586 0.996 0.99491 0.0096 0.5956 --++-++++-+-- 38691 186843
8 rs28842593 103044620 T C 0.839 0.836 0.982 0.98314 0.0135 0.2081 ----++0+??--+ 37504 184525
8 rs7014597 104152280 G C 0.824 0.824 0.997 0.99950 0.0122 0.9679 +-++-+++++--- 38691 186843
8 rs3134156 100479917 T C 0.841 0.833 0.997 0.98866 0.0128 0.3762 -+----+--++-- 38691 186843
8 rs6980591 103144592 A C 0.783 0.79 1 1.01106 0.0108 0.3075 ++-++---+++++ 38691 186843
8 rs72670434 108166508 A T 0.642 0.623 0.983 1.00672 0.0103 0.5171 +++-+++--+++- 38691 186843
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
CHR Chromosome (hg19)
SNP Marker name
BP Base pair location (hg19)
A1 Reference allele for OR (may or may not be minor allele)
A2 Alternative allele
FRQ_A_38691 allele frequency of A1 in 38,691 ADHD cases
FRQ_U_186843 allele frequency of A1 in 38,691 controls
INFO Imputation information score (the reported imputation INFO score is a weighted average across the
cohorts contributing to the meta-analysis for that variant)
OR Odds ratio for the effect of the A1 allele
SE Standard error of the log(OR)
P P-value for association test in the meta-analysis
Direction direction of effect in the included cohorts
Nca number of cases with variant information
Nco number of controls with variant information

其中SNP,Effect allele,Beta(OR),SE,P这五列是必须的。遇到没有提供EAF的数据,可以匹配千人基因组数据的EAFget_eaf_from_1000G

示例暴露数据

1
wget -c https://gwas.mrcieu.ac.uk/files/ieu-a-2/ieu-a-2.vcf.gz
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
VCF_dat = VariantAnnotation::readVcf('~/upload/GWAS/IEU/ieu-a-2.vcf.gz')
exp_dat = gwasglue::gwasvcf_to_TwoSampleMR(vcf = VCF_dat)
saveRDS(file = 'ieu-a-2.exp_dat', exp_dat)
exp_dat = subset(exp_dat, pval.exposure < 5e-08) # 关联性假设
# 去除连锁不平衡
# exp_dat = TwoSampleMR::clump_data(dat = exp_dat, clump_kb = 10000, clump_r2 = 0.001) # MRCIEU太喜欢用cloud api了
fix_ld_clump_local = function (dat, tempfile, clump_kb, clump_r2, clump_p, bfile, plink_bin) {
shell <- ifelse(Sys.info()["sysname"] == "Windows", "cmd",
"sh")
write.table(data.frame(SNP = dat[["rsid"]], P = dat[["pval"]]),
file = tempfile, row.names = F, col.names = T, quote = F)
fun2 <- paste0(shQuote(plink_bin, type = shell), " --bfile ",
shQuote(bfile, type = shell), " --clump ", shQuote(tempfile,
type = shell), " --clump-p1 ", clump_p, " --clump-r2 ",
clump_r2, " --clump-kb ", clump_kb, " --out ", shQuote(tempfile,
type = shell))
print(fun2)
system(fun2)
res <- read.table(paste(tempfile, ".clumped", sep = ""), header = T)
unlink(paste(tempfile, "*", sep = ""))
y <- subset(dat, !dat[["rsid"]] %in% res[["SNP"]])
if (nrow(y) > 0) {
message("Removing ", length(y[["rsid"]]), " of ", nrow(dat),
" variants due to LD with other variants or absence from LD reference panel")
}
return(subset(dat, dat[["rsid"]] %in% res[["SNP"]]))
}
fuck = fix_ld_clump_local(
dat = dplyr::tibble(rsid=exp_dat$SNP, pval=exp_dat$pval.exposure),
tempfile = file.path(getwd(),'tmp.ld_clump.exp_dat'),
clump_kb = 10000, clump_r2 = 0.001, clump_p = 1,
# pop = "EUR", # Super-population. Options are "EUR", "SAS", "EAS", "AFR", "AMR"
plink_bin = '/opt/conda/envs/MR/bin/plink', # 千万别用什么 genetics.binaRies::get_plink_binary(),他们自己编译的文件有问题
bfile = "/home/jovyan/upload/GWAS/ld/EUR" # 前缀,不是文件夹也不是文件
)
exp_dat_clumped = exp_dat[exp_dat$SNP %in% fuck$rsid,]
saveRDS(file = 'ieu-a-2.exp_gwas', exp_dat_clumped)

获取暴露数据

自己的数据

1
2
3
4
5
6
7
8
df_gwas <- data.frame(
SNP = c("rs1", "rs2"),
beta = c(1, 2),
se = c(1, 2),
effect_allele = c("A", "T")
)
head(df_gwas)
exp_dat <- TwoSampleMR::format_data(df_gwas, type = "exposure")

gwas_catalog

1
2
3
4
5
6
df_gwas <-
subset(MRInstruments::gwas_catalog,
grepl("Speliotes", Author) &
Phenotype == "Body mass index")
head(df_gwas)
exp_dat <- TwoSampleMR::format_data(df_gwas)

metab_qtls

1
2
3
4
5
6
df_gwas <-
subset(MRInstruments::metab_qtls,
phenotype == "Ala"
)
head(df_gwas)
exp_dat <- TwoSampleMR::format_metab_qtls(df_gwas)

proteomic_qtls

1
2
3
4
5
6
df_gwas <-
subset(MRInstruments::proteomic_qtls,
analyte == "ApoH"
)
head(df_gwas)
exp_dat <- TwoSampleMR::format_proteomic_qtls(df_gwas)

某个基因

1
2
3
4
5
6
df_gwas <-
subset(MRInstruments::gtex_eqtl,
gene_name == "IRAK1BP1" & tissue == "Adipose Subcutaneous"
)
head(df_gwas)
exp_dat <- TwoSampleMR::format_gtex_eqtl(df_gwas)

某个性状的某个甲基化位点相关QTL

1
2
3
4
5
6
df_gwas <-
subset(MRInstruments::aries_mqtl,
cpg == "cg25212131" & age == "Birth"
)
head(df_gwas)
exp_dat <- TwoSampleMR::format_aries_mqtl(df_gwas)

IEU的ID

1
2
3
exp_gwas <- TwoSampleMR::extract_instruments(outcomes = 'ieu-a-2')
head(exp_gwas)
saveRDS(file = 'ieu-a-2.exp_gwas', exp_gwas) # 和自己从VCF开始经过clump得到的差不多

UK_Biobank

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
hyperten_tophits <- ieugwasr::tophits(id="ukb-b-12493", clump=0)
hyperten_gwas <- dplyr::rename(hyperten_tophits, c(
"SNP"="rsid",
"effect_allele.exposure"="ea",
"other_allele.exposure"="nea",
"beta.exposure"="beta",
"se.exposure"="se",
"eaf.exposure"="eaf",
"pval.exposure"="p",
"N"="n"))
fuck = fix_ld_clump_local(
dat = dplyr::tibble(rsid=hyperten_gwas$SNP, pval=hyperten_gwas$pval.exposure),
tempfile = file.path(getwd(),'tmp.ld_clump.exp_dat'),
clump_kb = 10000, clump_r2 = 0.001, clump_p = 1,
# pop = "EUR", # Super-population. Options are "EUR", "SAS", "EAS", "AFR", "AMR"
plink_bin = '/opt/conda/envs/MR/bin/plink', # 千万别用什么 genetics.binaRies::get_plink_binary(),他们自己编译的文件有问题
bfile = "/home/jovyan/upload/GWAS/ld/EUR" # 前缀,不是文件夹也不是文件
)
exp_dat_clumped = hyperten_gwas[hyperten_gwas$SNP %in% fuck$rsid,]
MR_calc_r2_F(
beta = exp_dat_clumped$beta.exposure, # Vector of Log odds ratio. beta = log(OR)
eaf = exp_dat_clumped$eaf.exposure, # Vector of allele frequencies.
N = exp_dat_clumped$N, # Array of sample sizes
se = exp_dat_clumped$se.exposure # Vector of SE.
) # 取 F>10 的

计算统计效力

1
2
3
4
5
6
7
8
9
10
11
12
13
# 分类变量
tmp_r2 =TwoSampleMR::get_r_from_lor(
lor = exp_dat_clumped$beta.exposure, # Vector of Log odds ratio. beta = log(OR)
af = exp_dat_clumped$eaf.exposure, # Vector of allele frequencies.
ncase = exp_dat_clumped$ncase.exposure, # Vector of Number of cases.
ncontrol = exp_dat_clumped$ncontrol.exposure, # Vector of Number of controls.
prevalence = 1, # Vector of Disease prevalence in the population.
)
# 连续变量
tmp_r2 =TwoSampleMR::get_r_from_pn(
p = exp_dat_clumped$pval.exposure, # Array of pvals
n = exp_dat_clumped$samplesize.exposure # Array of sample sizes
)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
MR_calc_r2_F = function(beta, eaf, N, se){
# https://doi.org/10.1038/s41467-020-14389-8
# https://doi.org/10.1371/journal.pone.0120758
r2 = (2 * (beta^2) * eaf * (1 - eaf)) /
(2 * (beta^2) * eaf * (1 - eaf) +
2 * N * eaf * (1 - eaf) * se^2)
F = r2 * (N - 2) / (1 - r2)
print(mean(F))
return(dplyr::tibble(r2=r2, F=F))
}
MR_calc_r2_F(
beta = exp_dat_clumped$beta.exposure, # Vector of Log odds ratio. beta = log(OR)
eaf = exp_dat_clumped$eaf.exposure, # Vector of allele frequencies.
N = exp_dat_clumped$samplesize.exposure, # Array of sample sizes
se = exp_dat_clumped$se.exposure # Vector of SE.
) # 取 F>10 的

获取结局数据

IEU

1
out_gwas = TwoSampleMR::extract_outcome_data(snps = exp_gwas$SNP, outcomes = 'ieu-a-7')

UK_Biobank

1
anxiety_hyperten_liberal <- TwoSampleMR::extract_outcome_data(snps = exp_dat_clumped$SNP, outcomes = "ukb-b-11311")

PGC的示例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
df_gwas = read.table(gzfile('~/upload/GWAS/PGC/ADHD2022_iPSYCH_deCODE_PGC.meta.gz'), header = T)
head(df_gwas)
df_gwas = df_gwas[df_gwas$SNP %in% exp_gwas$SNP,]
out_gwas = data.frame(
SNP = df_gwas$SNP,
chr = as.character(df_gwas$CHR),
pos = df_gwas$BP,
beta.outcome = log(df_gwas$OR),
se.outcome = df_gwas$SE,
samplesize.outcome = df_gwas$Nca + df_gwas$Nco,
pval.outcome = df_gwas$P,
eaf.outcome = with(df_gwas, (FRQ_A_38691*Nca+FRQ_U_186843*Nco)/(Nca+Nco)),
effect_allele.outcome = df_gwas$A1,
other_allele.outcome = df_gwas$A2,
outcome = 'ADHD',
id.outcome = 'ADHD2022_iPSYCH_deCODE_PGC'
)
out_gwas = subset(out_gwas, pval.outcome > 5e-08) # 排他性假设

附加 代理SNP

一部分暴露的SNPs在结局中找不到,可以找和这部分SNPs连锁不平衡的SNPs来代替。相关网站:snipa

Harmonization

  • 将Exposure-SNP及Outcome-SNP等位基因方向协同
  • 根据EAF大小,剔除不能判断方向的回文SNP
  • 剔除incompatible SNP
1
2
3
4
dat <- TwoSampleMR::harmonise_data(
exposure_dat = exp_gwas,
outcome_dat = out_gwas
)

附加 一键报告

1
TwoSampleMR::mr_report(dat, output_type = "md")

MR分析

回归分析

1
2
3
4
5
6
TwoSampleMR::mr_method_list() # 查看mr支持的MR分析方法
mr_regression = TwoSampleMR::mr(dat, method_list = c('mr_ivw', 'mr_egger_regression', 'mr_weighted_median'))
mr_regression_or = TwoSampleMR::generate_odds_ratios(mr_res = mr_regression) # 分类变量
{pdf(file = 'MR.BMIvsADHD.plot.pdf', width = 6, height = 6); # 导出 PDF 开始
print(TwoSampleMR::mr_scatter_plot(mr_results = mr_regression, dat = dat)); # 返回的是一个ggplot2对象
dev.off()} # 导出 PDF 结束

mr_scatter_plot

异质性检测

  • 有异质性用随机效应模型ivw,无异质性用固定效应模型(也可以用随机效应模型,两者结果一致)
  • 异质性可能带来多效性,如果没有多效性,则可以说异质性没有带来多效性
1
2
3
4
TwoSampleMR::mr_heterogeneity(dat) # ivw的 Q_pval < 0.05 则说明有异质性
heterogeneity_presso = TwoSampleMR::run_mr_presso(dat, NbDistribution = 3000) # NbDistribution越高分辨率越高,找不到离群的SNP时需要提高
heterogeneity_presso[[1]]$`MR-PRESSO results`$`Global Test`$Pvalue # < 0.05 说明有异质性
heterogeneity_presso[[1]]$`MR-PRESSO results`$`Distortion Test`$`Outliers Indices` # 显示离群的SNP,将其剔除后重新分析

水平多效性

  • P < 0.05 说明不满足独立性假设,建议放弃继续做这个课题
  • P < 0.05 拒绝了截距为0的假设,说明SNP效应为0时依然有影响(截距存在),有其他因素在起作用
1
TwoSampleMR::mr_pleiotropy_test(dat)

敏感性分析

  • Leave-one-out analysis
  • 所有结果都不应该存在跨过0的情况,否则说明结果不稳定,不再能说明因果关系
1
2
3
4
mr_loo <- TwoSampleMR::mr_leaveoneout(dat)
{pdf(file = 'MR.BMIvsADHD.leaveoneout.plot.pdf', width = 6, height = 6); # 导出 PDF 开始
print(TwoSampleMR::mr_leaveoneout_plot(leaveoneout_results = mr_loo)); # 返回的是一个ggplot2对象
dev.off()} # 导出 PDF 结束

单SNP分析

  • 对每个暴露-结果组合进行多次分析,每次使用不同的单 SNP 进行分析
1
2
3
mr_res_single <- TwoSampleMR::mr_singlesnp(dat)
TwoSampleMR::mr_funnel_plot(mr_res_single)
TwoSampleMR::mr_forest_plot(mr_res_single)

方向性检测

1
TwoSampleMR::directionality_test(dat) # TRUE表示确实是暴露导致了结果

附加 稳健回归

1
2
3
4
dat2 <- TwoSampleMR::dat_to_MRInput(dat)
mr_ivw_robust <- MendelianRandomization::mr_ivw(dat2[[1]], model= "default", # “random”指的就是随机效应模型,“fixed”指的是固定效应模型
robust = TRUE, penalized = TRUE,correl = FALSE, # 参数penalized代表下调异常值的权重
weights ="simple", psi = 0,distribution = "normal",alpha = 0.05)

附加 绘制森林图

附加 计算Power

]]>
SNP MR https://hexo.limour.top/Mendelian-Randomization#disqus_thread