404频道

学习笔记

在 2022 年的上半年我有了属于自己的北京新能源指标,终于可以抛弃掉倍受限制的外地牌照,实现出门自由的目标了,有种翻身当家做主人般的无奈喜悦。

摆在我面前的第一个问题就是该换哪款新能源汽车,车对我而言仅仅是个代步工具而已,能满足出行需求即可,因此买车的原则比较简单:

  1. 买最大众化的销量最高的车型。当时销量最高的品牌当属比亚迪和特斯拉,单车型销量最高的当属特斯拉 Model Y,甚至 Model Y 火热到需要等至少两个月才交付,而且还在涨价。
  2. 后排座椅可以放倒,可以在车里睡觉,作为一个行走的床使用,Model Y 也正好可以满足需求。

我在苦苦观望了半年之久,等过了特斯拉的两拨降价后,在 2023 年元旦前夕选择了 Model Y 标准续航版。我担心过了 2023 年元旦后就没有补贴了,会额外增加一部分购车成本。事实证明我的判断是完全错误的,补贴不仅没有取消,而且一个月后居然降价两万多,肠子都要悔青了。

到目前为止,已经开了两年的时间,一共跑了 2 万多公里。虽然我对车并不感兴趣,但油车也开过,多少还是有些话语权的,作为一个单纯的用户,我来聊聊我的用车感受。
IMG_0976 2.jpg

整体感受

驾驶

开车后的第一感受就是悬挂还是偏硬。我虽然对五连杆之类的悬挂技术不太感冒,但不得不说 Model Y 还是非常颠的。在换车之后,家人们都觉得车变贵了,肯定会变舒服了,殊不知颠簸感要强于之前的朗逸。

其次是加速度。我对于加速基本追求,长期在舒适模式下驾驶,运动模式对我而言加速度过于激烈了一些。即使是在舒适模式下,也经常会让后排乘客晕车,加速度仍然是快了些。但要是自己独自开车,驾驶感受还是挺不错的。尤其是绿灯起步时,往往能一骑绝尘,超越同一起跑线的其他车辆。

单踏板真香。特斯拉的单踏板一直在被外界诟病,但对于我而言一直是单踏板的模式运行,日常的通行中,很少用到刹车,基本上一个踏板就不够了,大大减轻了右腿的压力。

车身质量

特斯拉是一家对于利润极度压榨的公司,在这种原则下造车,往往车关键部位的质量非常好,但次要的零件由于要追求性价比,不会用最好的供应商,质量就会有些堪忧,反正坏了给你换,只要不坏就是赚到了,整体而言,特斯拉还是赚的。

在用车的短短一年内,我遇到了两次故障:

  1. 空调坏掉一次。在一次超充充电完成后,空调完全不能制冷,去售后维修是空调压缩机坏掉了。
  2. 离开车后不能自动锁车。经过检查是主驾驶座椅下传感器坏掉了,检测不到驾驶人已经离开车。

内饰

​相比于国内品牌的冰箱彩电大沙发的豪华配置,特斯拉无遗是毛坯房。我个人对内饰也没有过多的要求,也比较喜欢极简风,倒是可以接受。

座椅加热功能在冬天还是非常的必要,但夏天就比较难受了,居然没有座椅通风。开一路车后,后背非常容易湿透。不过网上倒是有可以通过点烟器供电的外置通风座椅垫,我暂时没有体验过。

车顶的大玻璃对我而言影响不大,第一年的时候我买了一个手动遮阳帘挂上去。但第二年我就干脆没有挂上去,也不影响用车。在夏天的太阳下,虽然大玻璃会比较热, 但如果不用手去触摸,坐在车内头部是基本感受不到上面热量的。

空间

作为大号鼠标,​Model Y 的空间还是非常给力的。我车上长期放着一整套的露营装备,除了露营车外,其他的装备全部可以放到前备箱和后备箱的下方储物格中这些相比油车多出的储物空间中。

值得一提的是,我曾经搬过一次家,全程没有找搬家公司,家里所有的物品全部是我蚂蚁搬家一点点通过 Model Y 搬完的,虽然断断续续得搬了近两个月。

后排放倒后,在车里​睡觉完全不是问题。​略微不爽的就是不能完全放平,稍微有个角度,但在车里睡觉倒是没啥影响。配合着露营模式,我个人也曾在车里睡过很多次的午觉,甚至还有一次在车里睡了半晚上。

相比之下地盘离地高度就是个大大的劣势了。我曾经拿着手机测量过,最矮处跟手机的高度差不多。原先的油车偶尔还会应急停在马路牙子上,Model Y 我是一次也没敢往马路牙子上停。走在马路上过个坑都生怕磕到电池,怕跟保险公司扯上不解的缘分。(此处求 Model 3 车主的心理阴影面积)

续航

相比油车而言,电池无遗是电车的最大短板了。

刚买来时,车机显示满电续航为 435km,目前仅能显示 415km,掉了 20 km,属于在可接受的范围内,如果不追求数字,实际用车也感受不明显。要是跑高速,满打满算 350km 问题不大。

冬天是磷酸铁锂电池的噩梦,充电速度变慢,续航衰减明显,再加上还需要方向盘加热、座椅加热、空调加热这些功能,电耗会进一步提升。电池的回收动能变差,单踏板的减速效果会大大折扣。在零下十度的天气里,刚开启动的时候,动能回收甚至是完全失效的,开一段时间热起来后动能回收才能逐渐恢复一些。

充电方面,虽然有购车和推荐送的充电里程,但是一直没有舍得用完,大部分的时间都在外面的充电桩充电。在北京城市里面,充电还是非常方便的,方圆两公里内就有非常多的充电桩。时间久了后,我基本上会选择在出租车师傅长出没的充电桩充电了,而且大部分会选择在晚上十一点后,充电的价格要便宜挺多,我正好睡的晚,也不影响作息。

最怕的就是节假日期间的高速上了,很多情况下都是要排队充电的。再就是去偏远的地方,甚至北京周边河北境内的很多地方都很难找到充电桩,在张家口市沽源县城,我居然只能找到一家充电桩,而且晚上还不营业。

额外吐槽一下,不得不说,特斯拉是家特别会营销的公司。前两年的时候,4680 电池都快要让媒体吹上了天,能量密度提升了多少多少,啥时候量产,最近一年,几乎已经听不到 4680 电池的新闻了。(当然也有可能是我没关注到,顺便也吐槽下比亚迪的刀片电池)

软件

特斯拉的 OTA 升级还是比较频繁的,差不多每两个月一次升级。但两年回顾下来,真正有用的软件功能升级并不多。比如下面这次更新,看着就像是个把到架在程序员脖子上写的更新简介。
IMG_4820.jpg

特斯拉做产品非常的轴,跟苹果有的一拼。有的功能明明非常难用,却愣是不更新。另外就是因为国内的缘故,很多在国外可用的功能,在国内却无法使用,导致使用体验又大打折扣。

相比之下,国内的新能源厂商的车机体验却一路猛追,应该说早已超越了特斯拉的自研车机。当然这么比并不公平,毕竟特斯拉的主战场不是中国,而中国却是新能源厂商的主战场。中国的新能源厂商出海后,车机的体验也一定会大打折扣,有大量的本地化、安全合规等需要适配。

导航

导航功能刚开始用的时候弱爆了,但凡走陌生的路我都要打开手机上的高德导航。上班路上有一段特别拥堵,为了躲避拥堵,我都是刻意的多走一段路,但当我已经偏离了导航很长一段距离后,导航仍然傻到不断提醒让我前方掉头。

经过了两年的迭代,导航已经多次改版,已经好用了很多,车道级导航、红绿灯倒计时这些最近两年才在导航软件上有的新功能都已经引入了。

但相比手机上的导航软件还是有一定的差距,如:在计算拥堵方面还不够精确,显示的拥堵程度总是令我半信半疑,特斯拉可获取的数据量也不足以计算出拥堵的精度。

语音

国内的新势力厂商的语音交互早已经非常流畅,跟车机交流起来毫无障碍。而特斯拉的语音功能只能说是智障,以至于我跟车机的交互都会选择手动点击屏幕。使用语音的唯一场景就是导航时输入目的地了,剩下的下发指令类的场景完全没有使用过,完全没有通过点击屏幕来的快捷。

摄像头

特斯拉通过摄像头将物理环境通过视觉算法转换为内部的模型,并以此为基础来做更上层的数据输入来源。

但在目前国内的版本上,在一些极端的场景下,摄像头的识别能力还是不行的。比如在下面的场景下,明明前面有一个非常粗的杆子,在车机上却视而不见。
IMG_4553.jpg

车上有那么多的摄像头,却没有 360 全景功能,而是给用户展示出经过处理的模型,这明明是你内部的实现细节,暴露给用户有啥用。在摄像头的识别率还不能到百分百的情况下,其实直接将全景交给人是个更好的选择。

在刚交付时,车身前后的雷达还是可以使用的,在停车时可以在车机上显示出距离障碍物的距离,对停车还是非常有帮助的。但后来特斯拉一直在宣称要基于纯视觉,雷达和纯视觉变为了二选一。

辅助驾驶

我没有花钱购买额外的服务,用的最多的功能就是车道居中和自适应巡航了,不过这个功能在普通的油车上都快成为标配了。另外,车道居中功能在走山路 S 弯的时候根本没法使用,弱爆了。国外的 FSD V12 和 V13 快吹上天了,而国内的辅助驾驶还处于智障水平。

相比于普通的油车,稍微高级点的辅助驾驶功能就只有两个:

  1. 当车速太快,且没有踩刹车的情况下,车机会判断出是否会追尾,并发出滴滴的提示音。这一点还是挺有用的,可以大大降低追尾的概率。
  2. 车道偏离后的被车机紧急控制。我刚买车的时候,曾经有一次向右侧变道,右侧的车在后视镜的盲区内,变道过程中就被车机紧急接管方向盘,避免了一次剐蹭的事故。

当然辅助驾驶还是比较弱的,在很多的极端情况下,还是无能为力的。分享我曾经遇到过两个案例。

案例一:

在右转刚开始,外卖小哥还在马路对面,在车内是很难观察的,即使看到了也预判不了外卖小哥的风骚走位。当车头调转 90 度后,正准备加速时,外卖小哥突然冲到了车身前面,没有任何一个摄像头看到了外面小哥的存在,车机也没有发出告警,位置正好卡在了摄像头的盲区。

好在这次事故后,外卖小哥没啥事,扶起电动车看看外卖无碍后头也不回的就走了,佩服小哥的敬业精神!而我的小 Y 就没那么幸运了,车牌凹进去了一大块,车牌上的漆被碰掉了很多。

这次事故后,我每次右转都要提心吊胆,尤其是在转方向时不做过快的加速,否则可能会超出对方的预判,尤其是视时间为金钱的外卖骑手们的预判。

案例二:

在等红绿灯的时候,左侧车道为摩托车。绿灯起步时,摩托车要右转到辅路。摩托车以为其他车道的车肯定没他加速快,忽略了有一种车叫 Model Y。由于带着头盔,头也没法向后扭,就直接右转到辅路。

摩托车右转到我车前时,还突然减了一下速,车机都完全没反应过来,没有任何告警。幸好我当时反应快,踩了一脚刹车,要不然后果不堪设想。估计事后摩托车也不知道自己离事故这么近。

上面两个危险的经历告诉了我们两个道理:

  1. 远离电动车和摩托车,能离多远离多远。
  2. 特斯拉的辅助驾驶是完全靠不住的。

周边产品

因为特斯拉有 OpenAPI 可以供第三方调用,因此有一些第三方的软件我也在使用。据说后续 OpenAPI 要收费了,不知道后续围绕 OpenAPI 的一些应用是否会有调整。

TeslaMate

可以在电脑上运行的软件,会持续调用 OpenAPI,并将车的运行数据都记录在本地的数据库中,并通过 grafana 来展出出来。通过该软件,可以方便的查看历史的行车轨迹。阿里云的王坚博士提在线化,这就是数据在线化的一种非常好的呈现方式。
11415AB8-DC9C-4F62-857B-5467F499B35D-14202-0000032B37B0A7DD.PNG
比如今年夏天的时候去达达线、热阿线的路线图,可以精准的绘制出来。

小特钥匙

小特钥匙是 Apple Watch 上的应用,可以实现通过 Apple Watch 来打开车门,而且走的是蓝牙协议。主要的场景就是当手机没电时,可以通过手表了开锁,多了一条开门的途径。

因为是收费软件,而且功能非常单一,很多人都觉得该应用很鸡肋,我也是在一次手机没电开不了车门后下定决心购买的。

另外,特斯拉的官方 Apple Watch 版本的应用也快要上线了。

总结

开了两年的车,整体而言还是比较满意的。

交换机堆叠:是指将一台以上的交换机组合起来工作,从逻辑上虚拟成一台交换机,作为一个整体参与数据转发。

image.png
交换机的堆叠可以通过DAC高速线缆,光模块或者专门用于堆叠的线缆来实现。

交换机堆叠包括堆叠主交换机和堆叠备交换机,一主多备工作模式。堆叠主交换机存储整个交换机堆栈的运行配置文件,并通过堆叠主交换机对所有的堆叠交换机进行管理。如果主交换机发生故障,堆叠系统会从备交换机中选择新的堆叠主交换机,且不会影响整个网络的性能。

堆叠的协议都是私有的,因此不同品牌的交换机无法实现堆叠。

资料

阿里巴巴的财年为每年4月1日至次年3月31日,而4月1日至9月30日为财年的上半年。大部分的团队每半个财年要完成一次述职,而眼下的 9 月 30 日前又到了述职的日子。

在我过往的工作经历中,并没有述职的习惯,绩效的考核也相对随意一些,绩效的好坏跟主管的主管判断关系比较大,但通常绩效的好坏跟个人的成绩也是成正比的,不会出现较大的偏差。

在阿里绩效的绩效考核会非常被重视,希望能够通过公平的方法来分配高低绩效,降低主管的主观判断,当然主观的判断仍然是无法避免的,而且也会仍然占较大的比重。

述职可以作为一种常见的职场总结方式,也是绩效考核的一个重要的参考依据。

记得刚来阿里的时候,团队处于业务的爆发期,述职并不是太被重视,尤其是形式方面。某一年的述职中,我仅准备了可怜的三页 PPT,仅完成了做的事情的简单罗列,缺少一些自己的思考总结,当时就被喷的比较惨。当然述职中没有体现,并不代表平常就没有思考,即使平时没有思考也不代表就会影响实际的工作。

一到述职,自己也曾经非常头疼,经过了这几年的洗礼,自己对职场的述职也有了一点点自己的总结,希望能帮助到大家。

为什么要做述职?

存在即合理,我认为的述职有这么几个好处:

  1. 对自己过去做的事情的非常好的总结,帮助自己重新认识自己。这也是最关键的一点。大家肯定有这样的体会,如果让你回想过去几个月甚至一年做的事情,恐怕很难一下子想全。在现代职场中,大家都是匆匆忙忙的赶路人,任务一个接一个的完成,很难像在学校读书一样有温故而知新的机会。而述职恰恰是一个非常好的机会,回顾过去一段时间内自己做的事情,找找自己的闪光点,反思一下如果一件事情再重新做,会不会做的更好更高效。人总是在不断的总结经验中成长,述职是个很不错的职场成长机会。
  2. 让自己了解别人做的事情。别人做的最好的事情、吃的亏、踩过的坑都已经帮你总结好了,这是多好的一个学习机会。也许在吸取别人经验的过程中,会迸发出一些新的灵感。
  3. 让别人了解自己做的事情。自己做的申请同样需要传播给其他的同学,或许别人才能发现自己的闪光点,是个提升自己影响力的好时机(当然前提是自己要有货)。自己身上的不足也能够让别人发现,进而别人来帮助自己成长。
  4. 作为主管绩效考核的依据。这一点我倒认为并没有那么关键,自己做的事情自己的能力在日常工作中已经足够体现,不需要在述职的时候才做出判断,最多也就是作为判断的依据而已。
  5. 提升自己的演讲能力。演讲是一种能力,需要刻意练习。述职中要面对不同的人群,在有效的时间内将自己的事情给别人讲明白属实不是一件容易的事情。
  6. 通过述职这种形式化的方式来间接的促进员工在日常中工作的积极性。对于一部分员工,如果缺少了述职这种形式,在日常工作中就少了一份压力,工作中可能就会稍显懈怠。

述职的常见误区有哪些?

述职也并非百害而无一利,同样是一把双刃剑,如果能利用的好,会对自己有较大的帮助。如果利用不好,就容易反噬。

  1. 不要搞的花里胡哨太抽象。也是述职中最常见的问题,也是最让人诟病的。我见到过不少搞得花里胡哨的述职材料,一件很简单的事情非要说的很复杂,让人感觉很高大上,殊不知包装后,能听懂的人群更加少了。尽量少用 PPT,直接使用文档形式就非常不错,省去了大量排版的工作。图片也尽可能的简介,不要为了美观而消耗太多的时间。简简单单的真实就是最好的述职。
  2. 不要报喜不报忧。这也是个非常常见的问题。这种虚假的汇报,或许可以玩转一次述职,下次述职就很难遮盖过去了。或许一部分人可以糊弄过去,另外一部分人就未必。一个谎言的背后就得需要无数个谎言来弥补,实事求是在述职中非常的关键,严禁弄虚作假。
  3. 不要为了述职而述职。很多人都比较头疼述职,为了述职不得不做准备。其实这种心态就不太对,为了做而做,那么一定做不好。
  4. 不要消耗过多的时间来准备。如果一次要准备上好几天的时间就有点不值了,毕竟一年真正有效的工作时间才能有多少天呢,述职本身并不会产生业务价值,不要本末倒置。
  5. 不要陷在自己的主观世界中。要考虑到参加述职的人群,给别人讲明白自己做的事情。

述职的形式是什么样子的?

职场中的很多事情都有固定的套路,尤其是在一家公司内部。最好的方式就是参考老员工的述职报告,找到固定的套路,或者按照主管要求的套路来。最好不要试图找到一条新的方法,前辈们总结的路基本都不会错。

述职的内容基本要遵循 STAR 法则(即背景 Situation、任务 Task、行为 Action、结果 Result),主要包含几部分内容:

  1. Situation:事情的背景。
  2. Task:面临的挑战和遇到的困难。项目干系人,自己在其中承担的角色。
  3. Action:自己的解决方案。
  4. Result:获得的业务结果。能量化的一些业务结果最好要量化,这样会非常直观。

其中也可以讲讲自己的总结思考和个人成长等内容。

另外在讲述的时候要符合金字塔原理,核心的原则:

  1. 结论先行。
  2. 每层结论下面的论据不要超过7个。
  3. 每一个论点要言之有物,有明确的思想。

述职的材料该如何准备?

这里有几个建议:

  1. 如果有写周报的习惯,看看周报的内容。
  2. 看看各种群的聊天记录的内容。
  3. 看看自己的代码提交记录。
  4. 看看自己的文档。

当然,上面的一些前提都是平常要有一些积累,如果平常积累的多,那么述职的材料准备起来就比较简单快速。

最后,述职是一种管理员工的手段,作为员工在无法取消述职这种形式的时候,还是要尽可能将述职作为一种促进自己提升的手段,不要为了述职而述职。

容器中的执行topfree等命令展示出来的CPU,内存等信息是从/proc目录中的相关文件里读取出来的。而容器并没有对/proc/sys等文件系统做隔离,因此容器中读取出来的CPU和内存的信息是宿主机的信息,与容器实际分配和限制的资源量不同。

1
2
3
4
5
6
/proc/cpuinfo
/proc/diskstats
/proc/meminfo
/proc/stat
/proc/swaps
/proc/uptime

lxcfs是一个常驻进程运行在宿主机上,从而来自动维护宿主机cgroup中容器的真实资源信息与容器内/proc下文件的映射关系。

lxcfs实现的基本原理是通过文件挂载的方式,把cgroup中容器相关的信息读取出来,存储到lxcfs相关的目录下,并将相关目录映射到容器内的/proc目录下,从而使得容器内执行top,free等命令时拿到的/proc下的数据是真实的cgroup分配给容器的CPU和内存数据。

image.png

类别 容器内目录 宿主机lxcfs目录
cpu /proc/cpuinfo /var/lib/lxcfs/proc/cpuinfo
内存 /proc/meminfo /var/lib/lxcfs/proc/meminfo
/proc/diskstats /var/lib/lxcfs/proc/diskstats
/proc/stat /var/lib/lxcfs/proc/stat
/proc/swaps /var/lib/lxcfs/proc/swaps
/proc/uptime /var/lib/lxcfs/proc/uptime
/proc/loadavg /var/lib/lxcfs/proc/loadavg
/sys/devices/system/cpu/online /var/lib/lxcfs/sys/devices/system/cpu/online

在每个容器内仅需要挂载 lxcfs 在宿主机上的目录到容器中的目录即可。

多年前,我曾经阅读过两本当时非常流行的书籍《人性的弱点》和《人性的优点》,作者均为卡耐基。
image.png

而前段时间参加过一场[卡内基的培训](卡内基训练 (carnegiechina.com)),自己就有点傻傻的分不清楚此“卡”是否为彼“卡”。
image.png
于是搜索了一下资料,发现两“卡”均为 戴尔·卡耐基(Dale Carnegie,1888年11月24日—1955年11月1日),美国的作家和演讲者。只是因为翻译的偏差,导致出现了卡内基和卡耐基两种称呼。
image.png

1912 年创办了卡内基训练(Dale Carnegie Training)教导人们人际沟通及如何处理压力。
1936 年完成书籍《人性的弱点》又名《如何赢取友谊与影响他人》(_How to Win Friends and Influence People_)一书。

之所以会出现混淆,还有另外的名字也是跟卡耐基相关的:

  1. 安德鲁·卡内基(Andrew Carnegie,1835年11月25日—1919年8月11日):美国著名的钢铁大王,其名称要远大于戴尔·卡耐基
  2. 卡内基·梅隆大学(Carnegie Mellon University):由钢铁大王安德鲁·卡内基创办,全球大学排名上在前60名以内。

本文为对《GPT 图解 - 大模型是怎样构建的》一书的学习笔记,所有的例子和代码均来源于本书。

基本介绍

N-Gram 模型为语言模型的雏形。

基本思想为:一个词出现的概率仅依赖其前面的 N-1 个词。即通过有限的 N-1 个词来预测第 N 个词。

以 “我爱吃肉” 举例,分词为 [”我“, “爱”, “吃”, “肉”]。

  • 当 N=1 时,对应的序列为[”我“, “爱”, “吃”, “肉”],又成为 Unigram。
  • 当 N=2 时,对应的序列为[”我爱“, “爱吃”, “吃肉”],又成为 Bigram。
  • 当 N=3 时,对应的序列为[”我爱吃“, “爱吃肉”],又成为 Trigram。

2-Gram 的构建过程

将语料拆分为分词

将给定的语料库,拆分为以一个的分词。在真实的场景中需要使用分词函数,这里简单起见,使用了一个汉字一个分词的方式。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
corpus = [ "我喜欢吃苹果",  
"我喜欢吃香蕉",
"她喜欢吃葡萄",
"他不喜欢吃香蕉",
"他喜欢吃苹果",
"她喜欢吃草莓"]

# 定义一个分词函数,将文本转换为单个字符的列表
def tokenize(text):
return [char for char in text] # 将文本拆分为字符列表
# 对每个文本进行分词,并打印出对应的单字列表
print("单字列表:")
for text in corpus:
tokens = tokenize(text)
print(tokens)

得到如下的结果:

1
2
3
4
5
6
7
单字列表:
['我', '喜', '欢', '吃', '苹', '果']
['我', '喜', '欢', '吃', '香', '蕉']
['她', '喜', '欢', '吃', '葡', '萄']
['他', '不', '喜', '欢', '吃', '香', '蕉']
['他', '喜', '欢', '吃', '苹', '果']
['她', '喜', '欢', '吃', '草', '莓']

计算每个 2-Gram(BiGram) 在语料库中的词频

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
# 定义计算 N-Gram 词频的函数  
from collections import defaultdict, Counter # 导入所需库
def count_ngrams(corpus, n):
ngrams_count = defaultdict(Counter) # 创建一个字典,存储 N-Gram 计数
for text in corpus: # 遍历语料库中的每个文本
tokens = tokenize(text) # 对文本进行分词
for i in range(len(tokens) - n + 1): # 遍历分词结果,生成 N-Gram ngram = tuple(tokens[i:i+n]) # 创建一个 N-Gram 元组
prefix = ngram[:-1] # 获取 N-Gram 的前缀
token = ngram[-1] # 获取 N-Gram 的目标单字
ngrams_count[prefix][token] += 1 # 更新 N-Gram 计数
return ngrams_count
bigram_counts = count_ngrams(corpus, 2) # 计算 bigram 词频
print("bigram 词频:") # 打印 bigram 词频
for prefix, counts in bigram_counts.items():
print("{}: {}".format("".join(prefix), dict(counts)))

计算获取到如下的结果:

1
2
3
4
5
6
7
8
9
10
11
12
bigram 词频:
我: {'喜': 2}
喜: {'欢': 6}
欢: {'吃': 6}
吃: {'苹': 2, '香': 2, '葡': 1, '草': 1}
苹: {'果': 2}
香: {'蕉': 2}
她: {'喜': 2}
葡: {'萄': 1}
他: {'不': 1, '喜': 1}
不: {'喜': 1}
草: {'莓': 1}

即当第一个词为 ”我“,第二个词为”喜“在整个语料库中出现了 2 次。

如果为 3-Gram(TriGram),此时输出结果如下:

1
2
3
4
5
6
7
8
9
10
11
12
bigram 词频:
我喜: {'欢': 2}
喜欢: {'吃': 6}
欢吃: {'苹': 2, '香': 2, '葡': 1, '草': 1}
吃苹: {'果': 2}
吃香: {'蕉': 2}
她喜: {'欢': 2}
吃葡: {'萄': 1}
他不: {'喜': 1}
不喜: {'欢': 1}
他喜: {'欢': 1}
吃草: {'莓': 1}

如果为 1-Gram(UniGram),此时输出结果如下:

1
2
bigram 词频:
: {'我': 2, '喜': 6, '欢': 6, '吃': 6, '苹': 2, '果': 2, '香': 2, '蕉': 2, '她': 2, '葡': 1, '萄': 1, '他': 2, '不': 1, '草': 1, '莓': 1}

可以看到已经退化为了每个单词在整个语料库中出现的次数。

计算每个 2-Gram 出现的概率

即给定前一个词,计算下一个词出现的概率。

1
2
3
4
5
6
7
8
9
10
11
# 定义计算 N-Gram 出现概率的函数  
def ngram_probabilities(ngram_counts):
ngram_probs = defaultdict(Counter) # 创建一个字典,存储 N-Gram 出现的概率
for prefix, tokens_count in ngram_counts.items(): # 遍历 N-Gram 前缀
total_count = sum(tokens_count.values()) # 计算当前前缀的 N-Gram 计数
for token, count in tokens_count.items(): # 遍历每个前缀的 N-Gram ngram_probs[prefix][token] = count / total_count # 计算每个 N-Gram 出现的概率
return ngram_probs
bigram_probs = ngram_probabilities(bigram_counts) # 计算 bigram 出现的概率
print("\nbigram 出现的概率 :") # 打印 bigram 概率
for prefix, probs in bigram_probs.items():
print("{}: {}".format("".join(prefix), dict(probs)))

输出结果如下:

1
2
3
4
5
6
7
8
9
10
11
12
bigram 出现的概率 :
我: {'喜': 1.0}
喜: {'欢': 1.0}
欢: {'吃': 1.0}
吃: {'苹': 0.3333333333333333, '香': 0.3333333333333333, '葡': 0.16666666666666666, '草': 0.16666666666666666}
苹: {'果': 1.0}
香: {'蕉': 1.0}
她: {'喜': 1.0}
葡: {'萄': 1.0}
他: {'不': 0.5, '喜': 0.5}
不: {'喜': 1.0}
草: {'莓': 1.0}

给定一个前缀,输出连续的文本

根据前面学习的语料信息,给定一个前缀,即可生成对应的文本内容。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
# 定义生成下一个词的函数  
def generate_next_token(prefix, ngram_probs):
if not prefix in ngram_probs: # 如果前缀不在 N-Gram 中,返回 None return None
next_token_probs = ngram_probs[prefix] # 获取当前前缀的下一个词的概率
next_token = max(next_token_probs,
key=next_token_probs.get) # 选择概率最大的词作为下一个词
return next_token

# 定义生成连续文本的函数
def generate_text(prefix, ngram_probs, n, length=6):
tokens = list(prefix) # 将前缀转换为字符列表
for _ in range(length - len(prefix)): # 根据指定长度生成文本
# 获取当前前缀的下一个词
next_token = generate_next_token(tuple(tokens[-(n-1):]), ngram_probs)
if not next_token: # 如果下一个词为 None,跳出循环
break
tokens.append(next_token) # 将下一个词添加到生成的文本中
return "".join(tokens) # 将字符列表连接成字符串

# 输入一个前缀,生成文本
generated_text = generate_text("我", bigram_probs, 2)
print("\n 生成的文本:", generated_text) # 打印生成的文本

给定了文本”我“,可以生成出如下结果:

1
生成的文本: 我喜欢吃苹果

总结

N-Gram 为非常简单的语言模型,可以根据给定的词来生成句子。

缺点:无法捕捉到距离较远的词之间的关系。

在字节的 AI 发布平台扣子中提供了创建机器人的功能,并且可以直接对接微信公众号,使用在微信公众号中回复消息,由大模型直接回复的效果。

操作步骤

整体操作步骤非常简单,需要申请一个 Coze 的账号和开通微信公众号的开发者功能。

微信公众号的开发者功能在这里配置:
image.png

点击创建 Bot 按钮,输入 Bot 名称后点击确认
image.png|570

在机器人设置页面,可以配置模型、选择自己训练的知识库等操作。
image.png

设置完成后,点击发布,选择微信公众号(订阅号)配置功能,设置对应的微信公众号的 AppID。
image.png

体验

访问公众号直接输入内容,可以看到自动回复内容。
IMG_3856.PNG.JPG

无意间发现了开源项目 mi-gpt,该项目可以将家里的小爱音箱接入到 GPT 中,增强小爱音箱的功能。在跟小爱音箱对话的过程中,可以根据特定的提示词走 GPT 来回答,而不是用小爱音箱原生的回复。

必备条件:

  1. 必须有一个小米音箱。
  2. 必须要有可以长期运行的服务器,可以是树莓派等设备。
  3. 要有一个 OpenAI 的账号,也可以用兼容 ChatGPT API 的国内大模型。

部署

部署比较简单,下面为我的部署过程,供大家参考。更详细的信息大家可以直接参考 github 项目中的相关文档

创建配置文件 .migpt.js

参考项目中的文件 .migpt.example.js内容:

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
// 小爱音箱扮演角色的简介
const botProfile = `
性别:女
性格:乖巧可爱
爱好:喜欢搞怪,爱吃醋。
`;

// 小爱音箱主人(你)的简介
const masterProfile = `
性别:男
性格:善良正直
其他:总是舍己为人,是傻妞的主人。
`;

export default {
bot: {
name: "傻妞",
profile: botProfile,
},
master: {
name: "陆小千",
profile: masterProfile,
},
speaker: {
// 小米 ID
userId: "12345", // 注意:不是手机号或邮箱,请在「个人信息」-「小米 ID」查看
// 账号密码
password: "xxx",
// 小爱音箱 ID 或在米家中设置的名称
did: "Redmi小爱触屏音箱8",
// 当消息以下面的关键词开头时,会调用 AI 来回复消息
callAIKeywords: ["请", "你", "傻妞"],
// 当消息以下面的关键词开头时,会进入 AI 唤醒状态
wakeUpKeywords: ["打开", "进入", "召唤"],
// 当消息以下面的关键词开头时,会退出 AI 唤醒状态
exitKeywords: ["关闭", "退出", "再见"],

// 进入 AI 模式的欢迎语
onEnterAI: ["你好,我是傻妞,很高兴认识你"],

// 退出 AI 模式的提示语
onExitAI: ["傻妞已退出"],

// AI 开始回答时的提示语
onAIAsking: ["让我先想想", "请稍等"],

// AI 结束回答时的提示语
onAIReplied: ["我说完了", "还有其他问题吗"],

// AI 回答异常时的提示语
onAIError: ["啊哦,出错了,请稍后再试吧!"],

// 无响应一段时间后,多久自动退出唤醒模式(默认 30 秒)
exitKeepAliveAfter: 30,

// TTS 指令,请到 https://home.miot-spec.com 查询具体指令
ttsCommand: [3, 1],

// 设备唤醒指令,请到 https://home.miot-spec.com 查询具体指令
wakeUpCommand: [3, 2],

// 是否启用流式响应,部分小爱音箱型号不支持查询播放状态,此时需要关闭流式响应
streamResponse: false,

// 查询是否在播放中指令,请到 https://home.miot-spec.com 查询具体指令
playingCommand: [2, 1, 1], // 默认无需配置此参数,播放出现问题时再尝试开启
},
};

必须要修改的内容涉及到如下字段,其他字段可以根据含义来定义:

  1. speaker.userId:小米的用户 ID。
  2. speaker.password:小米的账号密码。
  3. speaker.did:小爱音箱的 ID 或者小爱音箱在米家的设备名字。
  4. ttsCommand:需要设置。如果设置不正常,会导致小爱音箱无法播放 GPT 回复内容的情况。
  5. wakeUpCommand:需要设置。
  6. streamResponse:在某些音箱设备上需要关闭。我的设备因为无法读完完整的句子,选择了关闭该功能,相关参考:小爱音箱没有读完整个句子,总是戛然而止

ttsCommand 和 wakeUpCommand 需要在 https://home.miot-spec.com 页面搜索对应的音箱型号
image.png

点击规格后任选一个,选择 Intelligent Speaker,其中的 [3, 1] 对应的为 ttsCommand,[3, 2] 对应的为 wakeUpCommand。
image.png

创建配置文件 .env

该文件中需要配置 OPENAI 的账号信息,我这里直接采用了阿里云的通义千问大模型服务,API 是完全兼容的

参考文档《开通DashScope并创建API-KEY》 阿里云上开通大模型服务,获取到 API-KEY
image.png

参考项目中的文件.env.example

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21

# OpenAI(也支持通义千问、MoonShot、DeepSeek 等模型)
OPENAI_MODEL=qwen-turbo
OPENAI_API_KEY=获取到的API_KEY
OPENAI_BASE_URL=https://dashscope.aliyuncs.com/compatible-mode/v1

# Azure OpenAI Service(可选)
# OPENAI_API_VERSION=2024-04-01-preview
# AZURE_OPENAI_API_KEY=你的密钥
# AZURE_OPENAI_ENDPOINT=https://你的资源名.openai.azure.com
# AZURE_OPENAI_DEPLOYMENT=你的模型部署名,比如:gpt-35-turbo-instruct

# 提示音效(可选,一般不用填,你也可以换上自己的提示音链接试试看效果)
# AUDIO_SILENT=静音音频链接,示例:https://example.com/slient.wav
# AUDIO_BEEP=默认提示音链接,同上
# AUDIO_ACTIVE=唤醒提示音链接,同上
# AUDIO_ERROR=出错了提示音链接,同上

# Doubao TTS(可选,用于调用第三方 TTS 服务,比如:豆包)
# TTS_DOUBAO=豆包 TTS 接口
# SPEAKERS_DOUBAO=豆包 TTS 音色列表接口

主要修改如下两个值:

  1. OPENAI_API_KEY:即为上文获取到阿里云模型服务灵积的 API-KEY。
  2. OPENAI_MODEL:支持的模型,可以在《支持的模型列表》中查询模型列表。

启动服务

提供了两种方式:docker 和 宿主机 Node.js 方式运行,我自然会选择更加简洁的 docker 方式。

执行 docker run -d --name mi-gpt --env-file $(pwd)/.env -v $(pwd)/.migpt.js:/app/.migpt.js idootop/mi-gpt:3.1.0 即可本地运行。

功能演示

提问小爱同学:“请问一下太阳的重量是多少”,小爱同学可以顺利的回答出答案。

通过 docker logs mi-gpt -f 可以看到如下的输出日志:

1
2
3
4
5
6
7
2024/06/12 16:15:38 Speaker 🔥 请问一下太阳的重量是多少

2024/06/12 16:15:38 Speaker 🔊 让我先想想

2024/06/12 16:15:41 Open AI ✅ Answer: 傻妞: 哦,太阳的重量可大了,它是个恒星,比我们的地球重得多。科学家们用的是质量而不是重量来衡量,太阳的质量大约是地球的333,000倍,真是个超级大块头,想想如果它能变成棉花糖,那得多软多亮啊!不过,太阳对我们来说太遥远了,它的重量咱们还是别去抱了,哈哈。

2024/06/12 16:15:41 Speaker 🔊 傻妞: 哦,太阳的重量可大了,它是个恒星,比我们的地球重得多。科学家们用的是质量而不是重量来衡量,太阳的质量大约是地球的333,000倍,真是个超级大块头,想想如果它能变成棉花糖,那得多软多亮啊!不过,太阳对我们来说太遥远了,它的重量咱们还是别去抱了,哈哈

在 k8s 中包含两类用户:

  1. ServiceAccount。又称服务账号,在运行 pod 时必须绑定 ServiceAccount,如果没有指定,则使用当前 namespace 下的 ServiceAccount default。是针对程序而言,用于 pod 中的程序访问 kube-apiserver。
  2. 普通用户。在 k8s 中并没有使用单独的对象来存储,而是通过了分发证书、外部用户认证系统等方式实现,是针对用户而言。

1. X509 证书认证

使用场景:使用 kubectl 访问 k8s 集群即通过 X509 证书认证方式,kubeconfig 本质上是个证书文件。

客户端使用证书中的 Common Name 作为请求的用户名,organization 作为用户组的成员信息。

证书的签发可以使用 openssl、cfssl 等工具来签发,也可以使用 k8s 自带的 CertificateSigningRequest 对象来实现签发。

1.1. CertificateSigningRequest 签发证书

创建私钥信息:

1
2
openssl genrsa -out myuser.key 2048
openssl req -new -key myuser.key -out myuser.csr -subj "/CN=myuser"

创建如下的 CertificateSigningRequest 对象

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
cat <<EOF | kubectl apply -f -
apiVersion: certificates.k8s.io/v1
kind: CertificateSigningRequest
metadata:
name: myuser
spec:
# value 使用命令 cat myuser.csr | base64 | tr -d "\n" 获取
request: xxx
# 固定值
signerName: kubernetes.io/kube-apiserver-client
# 过期时间
expirationSeconds: 86400 # one day
usages:
- client auth
EOF

查看 csr 处于 Pending 状态:

1
2
3
$ kubectl get csr
NAME AGE SIGNERNAME REQUESTOR REQUESTEDDURATION CONDITION
myuser 12s kubernetes.io/kube-apiserver-client kubernetes-admin 24h Pending

批准给证书签发请求:

1
kubectl certificate approve myuser

签发完成后的证书会存放到 status.certificate 字段中,至此证书签发完成。

2. ServiceAccount

使用场景:该方式使用较为常见,用于 pod 中访问 k8s apiserver。

原理:pod 可以通过 spec.serviceAccountName 字段来指定要使用的 ServiceAccount,如果没有指定则使用 namespace 下默认的 default ServiceAccount。kube-controller-manager 中的 ServiceAccount 控制器会在拉起的 pod 中自动注入如下的信息:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
spec:
volumeMounts:
- mountPath: /var/run/secrets/kubernetes.io/serviceaccount
name: kube-api-access-j6vpz
readOnly: true
volumes:
- name: kube-api-access-j6vpz
projected:
defaultMode: 420
sources:
- serviceAccountToken:
expirationSeconds: 3607
path: token
- configMap:
items:
- key: ca.crt
path: ca.crt
name: kube-root-ca.crt
- downwardAPI:
items:
- fieldRef:
apiVersion: v1
fieldPath: metadata.namespace
path: namespace

即将信息注入到 pod 的 /var/run/secrets/kubernetes.io/serviceaccount 目录下,目录结构如下:

1
2
3
4
/var/run/secrets/kubernetes.io/serviceaccount
|-- ca.crt -> ..data/ca.crt
|-- namespace -> ..data/namespace
`-- token -> ..data/token

token 为 JWT 认证,对其格式解密后如下:

1
2
3
4
{
"alg": "RS256",
"kid": "u7rF5JCtJRNiMzSUOFAYvDpCwPqUII-N-OtxR59cnQ0"
}
1
2
3
4
5
6
7
8
{
"iss": "kubernetes/serviceaccount",
"kubernetes.io/serviceaccount/namespace": "default",
"kubernetes.io/serviceaccount/secret.name": "ingress-token-s9gtm",
"kubernetes.io/serviceaccount/service-account.name": "ingress",
"kubernetes.io/serviceaccount/service-account.uid": "19ce4f11-7105-43ce-b189-f3d71a2ffc74",
"sub": "system:serviceaccount:default:ingress"
}

2.1. Secret 存放 token

在 1.22 版本及之前版本中,token 以 Secret 的形式存在于 pod 所在的 namespace 下,且 token 不会过期。Secret 的名字存在于 ServiceAccount 的 spec 中,格式如下:

1
2
3
4
apiVersion: v1
kind: ServiceAccount
secrets:
- name: nginx-token-scjvn

而 Secret 通过 Annotation kubernetes.io/service-account.name 指定了关联的 ServiceAccount。

在后续版本中,为了兼容当前方案,如果 ServiceAccount 关联了 Secret,则认为仍然使用 Secret 中存放 token 的方式。如果 Secret 已经很长时间没有使用,则自动回收 Secret。

如果要手工创建一个 token Secret,可以创建如下的 Secret,k8s 自动会为 Secret 产生 token:

1
2
3
4
5
6
7
8
apiVersion: v1
kind: Secret
metadata:
name: build-robot-secret
annotations:
# 带有 annotation
kubernetes.io/service-account.name: build-robot
type: kubernetes.io/service-account-token

2.2. TokenRequest API 产生 token

在 1.22 之后的版本中,kubelet 使用 TokenRequest API 获取有时间限制的临时 token,该 token

会在 pod 删除或者 token 生命周期(默认为 1h)结束后失效。

可以使用 kubectl create token default 来为 ServiceAccount default 创建 token,该命令实际上向 kube-apiserver 发送了请求 /api/v1/namespaces/default/serviceaccounts/default/token

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
{
"kind": "TokenRequest",
"apiVersion": "authentication.k8s.io/v1",
"metadata":
{
"creationTimestamp": null
},
"spec":
{
"audiences": null,
"expirationSeconds": null,
"boundObjectRef": null
},
"status":
{
"token": "",
"expirationTimestamp": null
}
}

kube-apiserver 支持如下参数:

  1. –service-account-key-file:用来验证服务账号的 token。
  2. –service-account-issuer:ServiceAccount token 的签发机构。
  3. –service-account-signing-key-file:ServiceAccount token 的签发私钥。

3. 用户伪装

一个用户通过 Http Header Impersonation- 的方式来扮演另外一个用户的身份。

场景:跨 k8s 集群访问的网关服务

支持的 Http Header 如下:

  1. Impersonate-User:要伪装的用户名。
  2. Impersonate-Group:要伪装的组名。该 Header 可以为多个,即支持多个组。

4. bootstrap token

使用 kube-apiserver 参数 --enable-bootstrap-token-auth=true 启用功能,引导 token 以 Secret 的形式存放在 kube-system 下。

该功能仅用于节点初始化时加入到 k8s 集群中。

资料

client 端限流

在 client-go 中会默认对客户端进行限流,并发度为 5。可以通过修改 rest.Conifg 来修改并发度。

MaxInFlightLimit 限流

通过如下参数来控制:

  • –max-requests-inflight:代表只读请求的最大并发量
  • –max-mutating-requests-inflight:代表写请求的最大并发量

该实现为单个 kube-apiserver 层面的,可以针对所有的请求。

EventRateLimit

用来对 Event 类型的对象进行限制,可以通过 kube-apiserver 的参数 –admission-control-config-file 来指定配置文件,文件格式如下:

1
2
3
4
5
6
apiVersion: apiserver.config.k8s.io/v1
kind: AdmissionConfiguration
plugins:
- name: EventRateLimit
path: eventconfig.yaml
...

其中 EventRateLimit 为对 Event 的限制,eventconfig.yaml 文件为详细的对 Event 的限流策略,可以精确到 Namespace 和 User 信息。

1
2
3
4
5
6
7
8
9
10
apiVersion: eventratelimit.admission.k8s.io/v1alpha1
kind: Configuration
limits:
- type: Namespace
qps: 50
burst: 100
cacheSize: 2000
- type: User
qps: 10
burst: 50

API 优先级和公平性

版本状态:

  1. alpha:1.18

通过 kube-apiserver 的参数 --enable-priority-fairness 来控制是否开启 APF 特性。

资料

0%