0%

RAG OR 微调

检索增强生成(RAG)​是一种通过外部知识库增强大模型输出的技术架构。其核心思想是将领域专业知识存储在向量数据库中,在生成回答时先检索相关文档片段,再将检索到的内容与用户问题一起输入大模型生成最终回答。RAG的主要优势在于实现成本低、知识更新便捷且能有效避免模型幻觉问题。在证券行业应用中,RAG系统可以通过实时接入市场数据和研究报告,确保输出的分析建议基于最新信息。国金证券的实践表明,RAG在数据与上下文相关的情况下非常有效,例如在解释特定金融数据时,同时能产生比基础模型更简洁的响应。然而,RAG也存在局限性,如输入token数量会增加提示信息量,且输出token数量往往更详细且更难精确控制。

模型微调则是通过领域数据继续训练预训练大模型,使其内部参数适应目标领域。微调技术可分为全参数微调和参数高效微调(如LoRA)两类。微调的优势在于能够使模型真正”理解”证券领域知识,生成更专业、更简洁的输出。研究显示,微调非常有效,能提供在特定领域学习新技能的机会,例如改进投资决策分析或根据市场模式优化投资组合建议。国金证券的技术探索表明,采用LoRA等参数高效微调方法,仅需调整0.1%左右的参数即可显著提升模型在证券任务上的表现。但微调也面临挑战,包括需要大量工作来准备训练数据,以及可能导致模型出现”灾难性遗忘”现象(即在学习新任务后丧失原有知识)。

技术特性 检索增强生成(RAG) 模型微调(Fine-Tuning)
实现复杂度 相对较低,主要构建知识库和检索系统 较高,需要准备训练数据和计算资源
知识更新 实时便捷,只需更新知识库 需要重新训练或增量训练
输出质量 依赖检索结果,可能不够流畅 更专业、简洁的输出
计算成本 推理时成本较高(长上下文) 训练成本高,推理成本低
适用场景 知识密集型、需最新数据的任务 需要深度领域理解的任务

证券行业大模型的构建往往需要混合使用RAG和微调技术,以发挥各自优势。
结合外部知识库和提示工程对通用大模型调优的技术方案最适合证券行业特点。这种混合方法在保留良好对话效果的同时,训练成本非常低,能够有效避免模型微调后的灾难性遗忘问题以及减少模型出现事实错误(幻觉)的情况。
在实际应用中,证券机构可根据具体场景需求选择技术路线组合:

  • 对于需要实时市场数据的交易分析场景,可侧重RAG架构;
  • 对于需要深度行业知识的研究报告生成,可采用微调+RAG的组合;
  • 对于合规审查等专业化任务,则可依赖经过充分微调的领域专用模型

数据准备与处理:构建高质量金融语料库

构建证券行业垂直大模型的基础在于准备高质量、大规模的金融领域专业数据。

数据类型

  • 结构化数据
    • 市场行情数据
    • 公司财务数据
    • 交易记录
  • 非结构化数据
    • 研究报告
    • 财经新闻
    • 公司公告
    • 分析师评论
    • 金融论坛讨论等文本内容
数据类型 数据示例 处理难点 解决方案
市场交易数据 股价、成交量、融资融券数据 高频、时序性强 时间序列标准化、异常值检测
公司披露文件 年报、招股书、重大资产重组公告 格式复杂、专业术语密集 PDF解析、关键信息抽取
研究报告 券商行业分析、公司深度报告 含图表、观点隐含 结构化解析、观点挖掘
财经新闻 市场快讯、政策解读 质量参差不齐、观点混杂 来源可信度评估、情感分析
互动平台数据 投资者问答、股吧评论 非正式表达、噪声大 slang处理、情绪识别

数据处理

数据预处理是确保模型训练质量的关键环节,主要包括清洗、过滤和去重三个步骤。

  • 清洗过程需要处理文本编码问题、特殊字符、无关广告内容等噪声;
  • 过滤阶段则依据数据质量指标去除低质内容;
  • 去重操作确保语料库中不存在高度相似或重复的文档。

证券行业数据预处理还需特别关注时序性处理,因为金融市场规则和公司状况会随时间变化,过时信息可能误导模型产生错误知识。例如,某上市公司历史上的财务造假事件可能已被记录在网络文本中,但经过整改后当前该公司可能已合规经营,这就需要在大模型知识库中明确标注信息的时间有效性。

领域名词

证券行业充斥着专业术语(如”可转换债券”、”市盈率”、”量化宽松”等)和行业特定表达方式,常规的自然语言处理工具可能无法准确分割或理解这些内容。在构建语料库时,需要采用结合金融词典的分词技术,并可能需要对通用语言模型进行领域适配性微调,以提高文本处理的准确性。

模型训练与优化策略

参数高效微调技术

LoRA(Low-Rank Adaptation)的工作原理是在原始模型参数旁添加低秩分解的适配矩阵,训练时固定原始参数,仅更新这些适配矩阵。具体而言,假设预训练基座大模型的矩阵为W₀∈R^(d×k),其更新则表示为低秩分解:W₀ + ΔW = W₀ + BA,其中B∈R^(d×r),A∈R^(r×k),秩r << min(d,k)。在前向传递过程中,W₀与ΔW都会乘以相同的输入x,最后相加:h = W₀x + ΔWx = W₀x + BAx。

LoRA的微调流程:初始化预训练基座大模型后冻结底层Transformer层;然后通过低秩分解更新部分参数,在训练过程中,W₀固定不变,不参加梯度更新,只训练参数矩阵A和B,得到模型更新参数ΔW。这种方法使训练成本大幅降低,同时保持模型性能。国金证券的实践表明,受影响的参数量通常仅为全量参数的0.1%左右,大大减轻了计算负担。

多任务训练

​多任务指令微调是提升证券大模型业务适应性的重要手段。设置多种模型训练任务:证券行业相关问答、用户情感分析、研报观点生成、财报数据解读以及上市公司问答等领域性任务。根据不同任务设置指令微调格式化实例,构建对应的证券行业多任务微调数据集。这种方法使单一模型能够适应证券业务中多样化的应用场景。

Alpaca格式(适合指令微调)

1
2
3
4
5
6
7
8
9
[
{
"instruction": "分析该公司IPO前景",
"input": "公司主营光伏组件,2023年营收增长45%,行业平均PE为25",
"output": "基于行业PE和公司增长率,该公司IPO估值可能在...",
"system": "你是一位资深证券分析师",
"history": []
}
]

港股IPO微调示例格式

1
2
3
4
{
"question": "赤峰黄金招股信息概要",
"answer": "最终分配结果:...;暗盘表现:...;首日表现:..."
}

多轮对话格式(ShareGPT)

1
2
3
4
5
6
{
"conversations": [
{"from": "human", "value": "如何评价这只债券的风险?"},
{"from": "gpt", "value": "从信用评级、久期和收益率曲线分析..."}
]
}

对于证券分析,可加入复杂推理链条(CoT):

1
2
3
4
5
{
"Question": "生物制药公司IPO估值应注意什么?",
"Complex-CoT": "需考虑研发管线阶段、临床试验结果、专利保护期...",
"Response": "重点分析其临床三期药物成功率及市场竞争格局..."
}

知识遗忘

当大模型学习证券领域新知识时,可能会遗忘原有的通用知识或推理能力。缓解这一问题的策略包括:

  • 保留部分通用能力的训练数据
  • 采用渐进式微调(先通用任务后专业任务)
  • 调整学习率等超参数

此外,混合使用RAG技术也能在一定程度上弥补模型自身的知识遗忘问题。

典型应用场景分析

​智能投顾与客户服务是大模型在证券行业最直接的应用场景。传统证券服务面临海量零售客户需求与有限投顾资源的矛盾,大模型可通过智能问答系统提供7×24小时的个性化投资咨询服务。国金证券以AI助手为切入点,探索大语言模型提升工作效率赋能业务发展,通过自然语言交互降低系统使用门槛,使不熟悉专业系统的客户也能便捷获取服务。智能客服系统可处理账户查询、交易规则解释、市场概览等常见问题,复杂问题则转接人工服务,实现服务资源优化配置。

​投资研究与分析是大模型创造高价值的核心领域。证券研究涉及大量信息收集、数据处理和报告撰写工作,非常适合大模型辅助。大模型可自动提取上市公司财报关键信息,对比行业数据,生成初步分析;帮助研究员快速梳理行业脉络,制作产业链图谱;甚至基于历史模式识别潜在投资机会和风险。国金证券提出的”AI+RPA”模式中,RPA像强有力的机械手臂自动执行任务,AIGC则是机械大脑进行分析、整合、创造,两者结合可自动化处理研究分析中的重复和繁琐任务。

​风险管理与合规监控是大模型在证券行业的关键应用。金融市场波动大、监管要求复杂,传统风控系统主要依赖规则引擎,难以应对新型风险。大模型可分析交易模式、网络舆情和市场数据,识别潜在异常;实时监控客户交流内容,标记可能违规的对话;自动生成合规报告,减少人工工作量。大模型在反洗钱、内幕交易监控等方面也表现出色,能发现传统系统忽略的隐蔽关联模式。某证券公司的应用实践表明,大模型可将合规审查效率提升40%以上,同时提高风险识别准确率。

​内部知识管理与运营自动化是大模型提升证券企业效率的重要应用。证券公司积累了大量内部知识资产,但分散在不同系统和文档中。大模型可作为统一的知识中枢,帮助员工快速定位专业资料、业务规程和历史案例;自动生成会议纪要、操作指引和培训材料;甚至辅助编写代码和业务脚本。国金证券推动大模型与现有系统分级耦合并成为AI中台建设的突破口,为不同部门提供智能化支持。大模型与RPA结合还能自动化处理财务对账、报表生成等后台运营工作,显著降低运营成本。

大模型微调(Fine-tuning)是将预训练模型适配到特定任务或领域的关键技术,根据参数更新方式、资源需求和任务特性,主流方法可分为以下几类:


1. 全量微调(Full Fine-tuning)

  • 原理:调整预训练模型的所有参数,使其完全适应新任务的数据分布。
  • 优点:性能最优,适合与预训练目标差异大的任务。
  • 缺点:计算资源消耗大,需大量标注数据(通常数万条),易过拟合。
  • 适用场景:数据充足且资源丰富的任务(如医疗诊断、金融风险评估)。

2. 高效参数微调方法

(1) LoRA(低秩适配)

  • 原理:在权重矩阵中引入低秩分解矩阵(如秩为8的矩阵A和B),仅微调少量参数,保持原始权重不变。
  • 优点:参数效率高(仅更新0.1%参数),适合边缘设备部署。
  • 适用场景:资源受限或需快速迭代的任务(如设备故障诊断)。

其它方法都有各自的一些问题:

  • Adapter Tuning 增加了模型层数,引入了额外的推理延迟
  • Prefix-Tuning 难于训练,且预留给 Prompt 的序列挤占了下游任务的输入序列空间,影响模型性能
  • P-tuning v2 很容易导致旧知识遗忘,微调之后的模型,在之前的问题上表现明显变差

基于上述背景,LORA 得益于前人的一些关于内在维度(intrinsic dimension)的发现:

模型是过参数化的,它们有更小的内在维度,模型主要依赖于这个低的内在维度(low intrinsic dimension)去做任务适配。
假设模型在任务适配过程中权重的改变量是低秩(low rank)的,由此提出低秩自适应(LoRA)方法。

LoRA 允许我们通过优化适应过程中密集层变化的秩分解矩阵,来间接训练神经网络中的一些密集层,同时保持预先训练的权重不变。
alt text

LoRA 的思想很简单:

  • 在原始 PLM (Pre-trained Language Model) 旁边增加一个旁路,做一个降维再升维的操作,来模拟所谓的intrinsic rank。
  • 训练的时候固定 PLM 的参数,只训练降维矩阵A与升维矩阵B。而模型的输入输出维度不变,输出时将AB与 PLM 的参数叠加。
  • 用随机高斯分布初始化 A,用 0 矩阵初始化 B,保证训练的开始此旁路矩阵依然是 0 矩阵。

LoRA(Low-Rank Adaptation)之所以在微调大模型时效果显著,主要得益于其独特的低秩分解设计、高效的参数优化策略以及对模型知识的保护机制。以下是具体原因分析:

1. 低秩分解的数学优势

  • 核心原理:LoRA通过将权重矩阵的更新量 (\Delta W) 分解为两个低秩矩阵 (A) 和 (B)((W’ = W + BA)),其中 (A) 和 (B) 的秩 (r) 远小于原始矩阵维度(如 (r=8))。这种分解将参数量从 (d \times k) 压缩至 (r \times (d+k)),实现97%以上的参数压缩率,同时保留95%以上的任务性能。
  • 内在维度假设:大模型在适应新任务时,权重更新实际存在于一个低维子空间。实验证明,即使秩 (r=1),LoRA也能逼近全量微调的效果,验证了这一假设。

2. 资源效率与训练加速

  • 极低参数量:以GPT-3为例,LoRA仅需训练原模型0.01%的参数(约百万级),显存消耗降低3倍,使得RTX 3090等消费级GPU也能微调70亿参数模型。
  • 优化器效率:仅需维护低秩矩阵的梯度状态,减少优化器开销。例如,Adam优化器的内存占用大幅降低,训练速度比全量微调快3倍。
  • 零推理延迟:训练后可将 (BA) 合并到原权重中,不增加额外计算层,推理速度与原始模型一致。

3. 知识保留与抗过拟合

  • 冻结原权重:LoRA仅训练新增的低秩矩阵,预训练模型的核心知识不被破坏,避免了灾难性遗忘。例如,在医疗问答任务中,LoRA微调的LLaMA-7B模型准确率提升23%,同时保留通用语言能力。
  • 正则化效果:低秩约束天然抑制过拟合,尤其在小样本场景下表现优异。实验显示,LoRA在文本分类任务上的F1分数比全量微调高4%。

4. 灵活性与通用性

  • 模块化设计:支持多任务适配器叠加。例如,Stable Diffusion可通过不同LoRA模块生成赛博朋克或水墨风格,仅需20张图片训练。
  • 广泛适配性:适用于Transformer的任意线性层(如注意力层的 (W_q) 和 (W_v)),且与量化技术(如QLoRA)、分布式训练兼容。

5. 实际应用验证

  • 性能对比:在多项NLP任务中,LoRA与全量微调效果相当甚至更优。例如,GPT-3微调后ROUGE-L指标达89.65,而资源消耗仅为传统方法的1%。
  • 工业级扩展:阿里云的动态权重融合技术结合LoRA,实现异构适配器并行效率提升40%。

(2) QLoRA(量化LoRA)

  • 原理:结合4-bit量化和LoRA,进一步降低显存占用。
  • 优点:支持单GPU微调百亿参数模型。
  • 适用场景:超低资源环境(如移动端应用)。

(3) Adapter Tuning(适配器调整)

  • 原理:在模型层间插入小型神经网络模块(Adapter),仅训练这些模块。
  • 优点:模块化设计,支持多任务复用。
  • 缺点:轻微增加推理延迟。
  • 适用场景:多任务学习(如不同领域的文本分类)。
    alt text
    Adapter 结构,将其嵌入 Transformer 的结构里面,在训练时,固定住原来预训练模型的参数不变,只对新增的 Adapter 结构进行微调。同时为了保证训练的高效性(也就是尽可能少的引入更多参数),他们将 Adapter 设计为这样的结构:
  • 首先是一个 down-project 层将高维度特征映射到低维特征
  • 然后过一个非线形层之后,再用一个 up-project 结构将低维特征映射回原来的高维特征
  • 同时也设计了 skip-connection 结构,确保了在最差的情况下能够退化为identity(类似残差结构)。

从实验结果来看,该方法能够在只额外对增加的 3.6% 参数规模(相比原来预训练模型的参数量)的情况下取得和Full-Finetuning 接近的效果(GLUE指标在0.4%以内)。
alt text

(4) Prefix/Prompt Tuning(前缀/提示调整)

  • 原理:在输入中添加可学习的虚拟标记(Prefix或Prompt),通过调整这些标记引导模型输出。
  • 优点:几乎不修改模型参数,适合快速任务切换。
  • 缺点:效果依赖提示设计。
  • 适用场景:生成式任务(如文本生成、对话系统)。

Prefix:

在输入 token 之前构造一段任务相关的 virtual tokens 作为 Prefix,然后训练的时候只更新 Prefix 部分的参数,而 Transformer 中的其他部分参数固定。该方法其实和构造 Prompt 类似,只是 Prompt 是人为构造的“显式”的提示,并且无法更新参数,而Prefix 则是可以学习的“隐式”的提示。
alt text
同时,为了防止直接更新 Prefix 的参数导致训练不稳定的情况,他们在 Prefix 层前面加了 MLP 结构(相当于将Prefix 分解为更小维度的 Input 与 MLP 的组合后输出的结果),训练完成后,只保留 Prefix 的参数。

Prompt :

是 Prefix Tuning 的简化版本,只在输入层加入 prompt tokens,并不需要加入 MLP 进行调整来解决难训练的问题,主要在 T5 预训练模型上做实验。似乎只要预训练模型足够强大,其他的一切都不是问题。作者也做实验说明随着预训练模型参数量的增加,Prompt Tuning的方法会逼近 Fine-tune 的结果。
固定预训练参数,为每一个任务额外添加一个或多个 embedding,之后拼接 query 正常输入 LLM,并只训练这些 embedding。左图为单任务全参数微调,右图为 Prompt tuning。
alt text
alt text

  • Prompt 长度影响:模型参数达到一定量级时,Prompt 长度为1也能达到不错的效果,Prompt 长度为20就能达到极好效果。
  • Prompt初始化方式影响:Random Uniform 方式明显弱于其他两种,但是当模型参数达到一定量级,这种差异也不复存在。
  • 预训练的方式:LM Adaptation 的方式效果好,但是当模型达到一定规模,差异又几乎没有了。
  • 微调步数影响:模型参数较小时,步数越多,效果越好。同样随着模型参数达到一定规模,zero shot 也能取得不错效果。
  • 当参数达到100亿规模与全参数微调方式效果无异。

(5) BitFit(偏置微调)

  • 原理:仅更新模型中的偏置(Bias)参数,冻结其他权重。
  • 优点:极低资源消耗(更新1%参数)。
  • 适用场景:简单分类任务或低资源场景。

3. 混合微调方法

  • MAM Adapter:结合LoRA和Adapter,在不同模块应用不同技术。
  • UniPELT:动态选择适配技术(如Adapter或Prefix Tuning)。
  • 适用场景:复杂多任务或动态任务环境。

4. 知识蒸馏(Knowledge Distillation)

  • 原理:通过小模型(学生)模仿大模型(教师)的行为,实现轻量化部署。
  • 优点:减少推理成本,保留大部分性能。
  • 适用场景:需高效推理的任务(如移动端问答系统)。

方法选择建议

场景 推荐方法
数据量大+资源充足 全量微调
小样本+低资源 LoRA/Prompt Tuning
多任务适配 Adapter/MAM Adapter
生成式任务 Prefix Tuning
超低资源 BitFit/QLoRA

transformer快速入门

统计语言模型

NNLM 模型

alt text
NNLM 模型首先从词表C中查询得到前面N-1个词语对应的词向量,然后将这些词向量拼接后输入到带有激活函数的隐藏层中,通过Softmax函数预测当前词语的概率。特别地,包含所有词向量的词表矩阵C也是模型的参数,需要通过学习获得。因此 NNLM 模型不仅能够能够根据上文预测当前词语,同时还能够给出所有词语的词向量(Word Embedding)。

word2vec

alt text
CBOW (Continuous Bag-of-Words)使用周围的词语w(t-2),w(t-1),w(t+1),w(t+2)来预测当前词w(t)。而 Skip-gram 则正好相反,它使用当前词w(t)来预测它的周围词语。
与严格按照统计语言模型结构设计的 NNLM 模型不同,Word2Vec 模型在结构上更加自由,训练目标也更多地是为获得词向量服务。特别是同时通过上文和下文来预测当前词语的 CBOW 训练方法打破了语言模型“只通过上文来预测当前词”的固定思维,为后续一系列神经网络语言模型的发展奠定了基础。

word2vec最大的问题是无法解决一词多义问题。后来自然语言处理的标准流程就是先将 Word2Vec 模型提供的词向量作为模型的输入,然后通过 LSTM、CNN 等模型结合上下文对句子中的词语重新进行编码,以获得包含上下文信息的词语表示。

ELMo 模型

ELMo 模型(Embeddings from Language Models)更好地解决多义词问题。与 Word2Vec 模型只能提供静态词向量不同,ELMo 模型会根据上下文动态地调整词语的词向量。
ELMo 模型首先对语言模型进行预训练,使得模型掌握编码文本的能力;然后在实际使用时,对于输入文本中的每一个词语,都提取模型各层中对应的词向量拼接起来作为新的词向量。ELMo 模型采用双层双向 LSTM 作为编码器,如图 1-10 所示,从两个方向编码词语的上下文信息,相当于将编码层直接封装到了语言模型中。
alt text
训练完成后 ELMo 模型不仅学习到了词向量,还训练好了一个双层双向的 LSTM 编码器。对于输入文本中的词语,可以从第一层 LSTM 中得到包含句法信息的词向量,从第二层 LSTM 中得到包含语义信息的词向量,最终通过加权求和得到每一个词语最终的词向量。

BERT模型

BERT 模型采用和 GPT 模型类似的两阶段框架,首先对语言模型进行预训练,然后通过微调来完成下游任务。但是,BERT 不仅像 GPT 模型一样采用 Transformer 作为编码器,而且采用了类似 ELMo 模型的双向语言模型结构。由于 BERT 模型采用双向语言模型结构,因而无法直接用于生成文本。
alt text

Transformer模型

Transformer模型按模型结构将它们大致分为三类:

  • 纯 Encoder 模型(例如 BERT),又称自编码 (auto-encoding) Transformer 模型;
  • 纯 Decoder 模型(例如 GPT),又称自回归 (auto-regressive) Transformer 模型;
  • Encoder-Decoder 模型(例如 BART、T5),又称 Seq2Seq (sequence-to-sequence) Transformer 模型。

Transformer 模型本质上是预训练语言模型,大都采用自监督学习 (Self-supervised learning) 的方式在大量生语料上进行训练,训练这些 Transformer 模型完全不需要人工标注数据。
例如下面两个常用的预训练任务:

  • 基于句子的前n个词来预测下一个词,因为输出依赖于过去和当前的输入,因此该任务被称为因果语言建模 (causal language modeling);
  • 基于上下文(周围的词语)来预测句子中被遮盖掉的词语 (masked word),因此该任务被称为遮盖语言建模 (masked language modeling)。

结构

标准的 Transformer 模型主要由两个模块构成:
Encoder(左边):负责理解输入文本,为每个输入构造对应的语义表示(语义特征);
Decoder(右边):负责生成输出,使用 Encoder 输出的语义表示结合其他输入来生成目标序列。
alt text

这两个模块可以根据任务的需求而单独使用:

  • 纯 Encoder 模型:适用于只需要理解输入语义的任务,例如句子分类、命名实体识别;
  • 纯 Decoder 模型:适用于生成式任务,例如文本生成;
  • Encoder-Decoder 模型或 Seq2Seq 模型:适用于需要基于输入的生成式任务,例如翻译、摘要。

alt text

Attention

注意力层的作用就是让模型在处理文本时,将注意力只放在某些词语上。

例如要将英文“You like this course”翻译为法语,由于法语中“like”的变位方式因主语而异,因此需要同时关注相邻的词语“You”。同样地,在翻译“this”时还需要注意“course”,因为“this”的法语翻译会根据相关名词的极性而变化。对于复杂的句子,要正确翻译某个词语,甚至需要关注离这个词很远的词。

同样的概念也适用于其他 NLP 任务:虽然词语本身就有语义,但是其深受上下文的影响,同一个词语出现在不同上下文中可能会有完全不同的语义(例如“我买了一个苹果”和“我买了一个苹果手机”中的“苹果”)。

NLP神经网络模型的本质就是对输入文本进行编码,常规的做法是首先对句子进行分词,然后将每个词语 (token) 都转化为对应的词向量 (token embeddings),这样文本就转换为一个由词语向量组成的矩阵X=(x1,x2,…,xn),其中 xi就表示第i个词语的词向量,维度为d,故 X∈Rn*d。

在 Transformer 模型提出之前,对 token 序列 X 的常规编码方式是通过循环网络 (RNNs) 和卷积网络 (CNNs)。

  • RNN(例如 LSTM)的方案很简单,每一个词语 xt 对应的编码结果 yt通过递归地计算得到:yt=f(y(t-1),xt)。
    RNN 的序列建模方式虽然与人类阅读类似,但是递归的结构导致其无法并行计算,因此速度较慢。而且 RNN 本质是一个马尔科夫决策过程,难以学习到全局的结构信息;
  • CNN 则通过滑动窗口基于局部上下文来编码文本,例如核尺寸为 3 的卷积操作就是使用每一个词自身以及前一个和后一个词来生成嵌入式表示:yt=f(x(t-1),xt,x(t+1))。
    CNN 能够并行地计算,因此速度很快,但是由于是通过窗口来进行编码,所以更侧重于捕获局部信息,难以建模长距离的语义依赖。

Google《Attention is All You Need》提供了第三个方案:直接使用 Attention 机制编码整个文本。相比 RNN 要逐步递归才能获得全局信息(因此一般使用双向 RNN),而 CNN 实际只能获取局部信息,需要通过层叠来增大感受野,Attention 机制一步到位获取了全局信息:yt=f(xt,A,B)
其中A,B是另外的词语序列(矩阵),如果取A=B=X就称为 Self-Attention,即直接将xt与自身序列中的每个词语进行比较,最后算出yt。

Scaled Dot-product Attention

Scaled Dot-product Attention是最常见的attention实现
alt text

Scaled Dot-product Attention 共包含 2 个主要步骤:

  1. 计算注意力权重:使用某种相似度函数度量每一个 query 向量和所有 key 向量之间的关联程度。对于长度为 m 的 Query 序列和长度为 n 的 Key 序列,该步骤会生成一个尺寸为 m*n 的注意力分数矩阵。
    特别地,Scaled Dot-product Attention 使用点积作为相似度函数,这样相似的 queries 和 keys 会具有较大的点积。
    由于点积可以产生任意大的数字,这会破坏训练过程的稳定性。因此注意力分数还需要乘以一个缩放因子来标准化它们的方差,然后用一个 softmax 标准化。这样就得到了最终的注意力权重 w(ij),表示第 i 个 query 向量与第 j 个 key 向量之间的关联程度。
  2. 更新 token embeddings:将权重 w(ij) 与对应的 value 向量 v1,…,vn 相乘以获得第 i 个 query 向量更新后的语义表示 。

Multi-head Attention

关于BBR的介绍参见 BBR (很久以前写过详细文档,后来丢了,只找到了一小部分……)

在一些比较老的OpenVZ的vps(宿主机内核低于4.9版本)上,是无法开启bbr的。tcp的拥塞控制,在网络链路存在严重丢包时,会将拥塞窗口乘性减少,所以进行文件传输时,速度会越来越慢。以下介绍一下我面临scp速度慢的问题的解决办法

haproxy-lkl代理ssh端口

市面上有很多代替BBR的方案,大多都大差不差,都是使用lkl(linux kernal library)内核库绕过内核协议处理网络数据,再使用端口代理,实现在某几个端口的传输层能达到bbr的效果。

参考了ovz架构安装bbr内核一文,很方便,一建安装。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
恭喜!BBR 安装完成并成功启动

已加速的端口: 2222

你可以通过修改文件:
/usr/local/haproxy-lkl/etc/port-rules

来配置需要加速的端口或端口范围。

请使用 systemctl {start|stop|restart} haproxy-lkl
来 {开启|关闭|重启} 服务

服务已自动加入开机启动,请放心使用。

如果这个脚本帮到了你,你可以请作者喝瓶可乐:
https://blog.kuoruan.com/donate

享受加速的快感吧!

但也有缺点,这个端口代理只能影响外部访问该端口的,不能影响由内向外的。我尝试过打开某个本地端口,再通过以下代码将文件scp到外面,但是并没有用

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
# 指定本地端口
# 没有用,haproxy-lkl并监听由本地端口发起的连接
def transfer_and_delete_file_using_key_and_local_port(local_file_path, remote_host, remote_user, private_key_path,
remote_file_path, local_port):
private_key = paramiko.RSAKey.from_private_key_file(private_key_path)
local_host = "0.0.0.0" # 表示从任意本地网络接口连接

# 创建 SSH 客户端对象
ssh = paramiko.SSHClient()
# 自动添加主机密钥
ssh.set_missing_host_key_policy(paramiko.AutoAddPolicy())
sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
sock.bind((local_host, local_port))
sock.connect((remote_host, 22))
transport = paramiko.Transport(sock)
try:
transport.start_client()
transport.auth_publickey(remote_user, private_key)
sftp = paramiko.SFTPClient.from_transport(transport)
# 检查远程文件路径的目录是否存在,如果不存在则创建
remote_dir = os.path.dirname(remote_file_path)
try:
sftp.listdir(remote_dir)
except IOError:
sftp.mkdir(remote_dir)
sftp.put(local_file_path, remote_file_path)
sftp.close()
logging.info(f"local file scp finish, {local_file_path}")
# 删除本地文件
os.remove(local_file_path)
logging.info(f"local file deleted: {local_file_path}")
except Exception as e:
traceback.print_exc()
logging.error(f"exception on {local_file_path} with error {e}")
finally:
# 关闭 Transport 连接
transport.close()

http请求呼叫远端pull

我干脆就不push了,让远端pull。这里面只有一个问题,就是网络链路不稳定容易导致('Connection aborted.', RemoteDisconnected('Remote end closed connection without response'))错误

解决方案是pull接口幂等+重试

本地:

1
2
3
4
5
6
7
8
9
10
11
def ask_remote_scp_local_file_and_remote():
while True:
try:
resp = session.post("http://xxx", json=req,timeout=3600)
if resp:
break
except Exception as e:
logging.error("ask remote scp fail," + e.__str__())
if resp.status_code == 200 and resp.text == 'success':
os.remove(file_path)
return

远端:
使用python-redis-lock包

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
def scp_from_remote_using_key(local_file_path, remote_host, remote_user, private_key_path, remote_file_path, port):
# 创建 SSH 客户端
ssh = paramiko.SSHClient()
ssh.set_missing_host_key_policy(paramiko.AutoAddPolicy())
try:
# 加载私钥
private_key = paramiko.RSAKey.from_private_key_file(private_key_path)
# 连接到远程服务器
ssh.connect(remote_host, port=port, username=remote_user, pkey=private_key)
# 使用 sftp 传输文件
sftp = ssh.open_sftp()
# 检查远程文件路径的目录是否存在,如果不存在则创建
local_dir = os.path.dirname(remote_file_path)
try:
os.listdir(local_dir)
except IOError:
os.mkdir(local_dir)

sftp.get(remote_file_path, local_file_path)
sftp.close()
logging.info(f"remote file scp finish, {remote_file_path}")
except Exception as e:
logging.error(f"exception on {remote_file_path} with error {e}")
finally:
# 关闭 SSH 连接
ssh.close()
def lock_and_scp(local_file_path, remote_host, remote_user, private_key_path, remote_file_path, port):
m2 = hashlib.md5()
m2.update(remote_file_path.encode('utf-8'))
md5 = m2.hexdigest()
lock = redis_lock.Lock(redis_connect, "lock_" + md5, expire=600)

if lock.acquire(blocking=True):
if redis_connect.get("finished_" + md5) is None:
scp_from_remote_using_key(local_file_path, remote_host, remote_user, private_key_path, remote_file_path,
port)
redis_connect.set("finished_" + md5, "1")
redis_connect.expire("finished_" + md5, 600)
lock.release()

起因

在truenas的nextcloud升级后,可能是由于容器拉取混乱,k3s里的nextcloud空间也乱了。陆续出现了几个问题:

  1. 原本只有3个pod,结果多了nextcloud-postgres-ncnextcloud-nc两个deployment,而且都是比我设置版本低的,读的同一个映射路径(直接删掉多余的deploy)
  2. 在nextcloud-postgres容器中出现了2025-01-01T00:10:27.747127138+08:00 2024-12-31 16:10:27.747 UTC [169] FATAL: role "postgres" does not exist报错。虽然没找到这个错误的起因,但还是尝试解决了一下。
  3. app_api插件更新卡住不动(在将/html/apps/app_api目录先移出目录在移进后就莫名其妙好了)

网上说解决第2个问题的方法都语焉不详。postgres应该是默认的超级管理员用户,现在超级管理员直接没了,所以很多操作实际上是执行不了的。一下说一下我的解决办法

创建临时pg容器

在原有容器中,postgres进程是不能关的,关了后容器会重建,尝试设置了一下pod的restartPolicy为Never也没用。因此创建了一个临时容器来处理,新容器删掉了探针,加了一个tail -f /dev/null保持容器不关闭。

配置postgres.yml:

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
apiVersion: v1
kind: Pod
metadata:
name: postgres-13.1
labels:
app: postgres
namespace: default
spec:
restartPolicy: Never
containers:
- name: postgres
image: postgres:13.1
command: ["tail", "-f", "/dev/null"]
ports:
- containerPort: 5432
name: postgres
volumeMounts:
- name: postgres-data
mountPath: /var/lib/postgresql/data
volumes:
- name: postgres-data
hostPath:
path: /xxxx # 宿主机挂载到容器的pg根目录
type: DirectoryOrCreate
---
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
name: postgres-pvc
namespace: default
spec:
accessModes:
- ReadWriteOnce
resources:
requests:
storage: 10Gi
storageClassName: standard

接着启动容器

1
2
k3s kubectl apply -f postgres2.yaml
k3s kubectl delete pod postgres-13.1 -n default

进入single模式,增加postgres用户

1
2
3
4
5
6
7
8
pg_ctl stop -D /var/lib/postgresql/data/

postgres --single -D /var/lib/postgresql/data/
>CREATE ROLE postgres SUPERUSER LOGIN CREATEDB CREATEROLE INHERIT REPLICATION BYPASSRLS;

pg_ctl start -D /var/lib/postgresql/data/

k3s kubectl delete pod postgres-13.1 -n default