算法无限
IP:
0关注数
0粉丝数
0获得的赞
工作年
编辑资料
链接我:

创作·64

全部
问答
动态
项目
学习
专栏
算法无限

LLM常见问题(Tokenizer 部分)

1. Byte-Pair Encoding(BPE) 如何构建词典?设定最大subwords个数。将所有单词拆分为单个字符,并且在最后添加一个停止符,同时标记处该单词出现的次数。统计每一个 连续字节对 的出现频率,选择最高频者合成新的subword。重复第3步直到达到第1步设定的subwords词表大小或下一个最高频的字节对出现频率为1。2. WordPiece 与 BPE 异同点是什么?Google 的 Bert 模型在分词的时候使用的是 WordPiece 算法。与 BPE 算法类似,WordPiece 算法也是每次从词表中选出两个子词合并成新的子词。与 BPE 的最大区别在于,如何选择两个子词进行合并:BPE 选择频数最高的相邻子词合并,而 WordPiece 选择能够提升语言模型概率最大的相邻子词加入词表。3. SentencePiece 的思路是什么?SentencePiece 其实是一个实现了 BPE/Unigram tokenizer 的一个集合,不过他有一些创新的地方,跟中文的分词作用是一样的,但从思路上还是有区分的。在中文上,把经常在一起出现的字组合成一个词语;在英文上,它会把英语单词切分更小的语义单元,减少词表的数量。4. LLMs 有哪些分词方式,它们之间有什么区别?一般 LLMs 分词方式包括 BPE, WordPiece, Unigram 以及随机分词。BPE:这是一种数据压缩 算法,用来在固定大小的词表中实现可变⻓度的子词。BPE 首先将词分成单个字符,然后依次用另一个字符替换频率最高的一对字符 ,直到循环次数结束。WordPiece:训练过程是不断地去寻找语料库中 PMI 最高的 subword pair, 然后合并成一个 subword。Unigram:unigram 分词方式属于 n-gram 概率路径 分词方式的一种, 中文名称是 一元分词。在中文分词中, 比较常见的是 二元分词 (bigram)。n-gram 的本质就是 标准语言模型 的简化。而 unigram 是最强的假设, 即每一个词语的概率是完全独立的, 和之前的文本没有关系的。随机分词:对于文本生成任务来说, 不能完全按照概率最高的方式来生成, 而只要是在合理区间内的都可以。同理, 对于分词来说也是类似: 分词方式需要一定的 "随机性"。
0
0
0
浏览量353
算法无限

LLM常见问题(幻觉部分)

1. 什么是大模型幻觉(Hallucination)?大语言模型的模型幻觉问题是指其可能生成看似合理但实际上不准确或不符合事实的内容。处理大语言模型的模型幻觉问题需要采取一些方法和策略,通过挂载文档(知识库)可以有效地缓解幻觉问题。2. 为什么 LLM 会产生幻觉?有一些研究也在致力于分析幻觉出现的不同原因,已知的一些原因包括:源与目标的差异:当我们在存在源与目标差异的数据上训练模型时,模型产生的文本可能与原始源内容产生偏差。这种差异,有时可能是在数据收集过程中不经意间产生的,有时则是故意为之。无意识的源-目标差异:这种差异的产生有多种原因。例如,数据可能是基于某种经验法则编制的,使得目标信息并不总是完全依赖源信息。举例来说,如果从两家不同的新闻网站获得相同事件的报道作为源与目标,目标报道中可能包含源报道没有的信息,从而导致二者不同。有意识的源-目标差异:某些任务在本质上并不追求源与目标的严格一致,尤其是在需要多样性输出的情境下。训练数据的重复性:训练过程中使用的数据,如果存在大量重复,可能导致模型在生成时过于偏好某些高频短语,这也可能引发“幻觉”。数据噪声的影响:使用充斥噪声的数据进行训练,往往是导致“幻觉”出现的关键因素之一。解码过程中的随机性:某些旨在增加输出多样性的解码策略,如 top-k 采样、top-p 方法以及温度调节,有时会增加“幻觉”的产生。这往往是因为模型在选择输出词汇时引入了随机性,而没有始终选择最可能的词汇。模型的参数知识偏向:有研究表明,模型在处理信息时,可能更依赖其在预训练阶段所积累的知识,而忽略了实时提供的上下文信息,从而偏离了正确的输出路径。训练与实际应用中的解码差异:在常见的训练方法中,我们鼓励模型基于真实数据预测下一个词汇。但在实际应用中,模型则是根据自己先前生成的内容进行预测。这种方法上的差异,尤其在处理长文本时,可能会导致模型的输出出现“幻觉”。最后,如GPT之类的生成模型,其实只是学会了文本中词汇间的统计规律,所以它们生成内容的准确性仍然是有限的。3. 为什么需要解决 LLM 的幻觉问题?LLMs 的幻觉可能会产生如传播错误信息或侵犯隐私等严重后果。 比如在医疗应用中,对患者生成的报告如果存在幻觉可能导致错误诊断甚至影响生命安全。 幻觉影响了模型的可靠性和可信度,因此需要解决 LLM 的幻觉问题。4. 幻觉一定是有害的吗?幻觉不一定是有害的,特别是在一些需要创造力或灵感的场合,比如写电影剧情,幻觉的存在可能带来一些奇思妙想,使得生成的文本充满想象力。因此,对幻觉的容忍度取决于具体的应用场景。5. 幻觉有哪些不同类型?幻觉主要可以分为两类:即内在幻觉和外在幻觉。内在幻觉:生成的内容与源内容相矛盾。外部幻觉:生成的内容不能从源内容中得到验证,既不受源内容支持也不受其反驳。6. 如何度量幻觉?最有效可靠的方式当然是靠人来评估,但是人工评估的成本太高了。因此有了一些自动化评估的指标:命名实体误差:命名实体(NEs)是“事实”描述的关键组成部分,我们可以利用NE匹配来计算生成文本与参考资料之间的一致性。直观上,如果一个模型生成了不在原始知识源中的 NE,那么它可以被视为产生了幻觉(或者说,有事实上的错误)。蕴含率:该指标定义为被参考文本所蕴含的句子数量与生成输出中的总句子数量的比例。为了实现这一点,可以采用成熟的蕴含/NLI 模型。基于模型的评估:应对复杂的句法和语义变化。利用问答系统:此方法的思路是,如果生成的文本在事实上与参考材料一致,那么对同一个问题,其答案应该与参考材料相似。具体而言,对于给定的生成文本,问题生成模型会创建一组问题-答案对。接下来,问答模型将使用原始的参考文本来回答这些问题,并计算所得答案的相似性。利用信息提取系统:此方法使用信息提取模型将知识简化为关系元组,例如<主体,关系,对象>。这些模型从生成的文本中提取此类元组,并与从原始材料中提取的元组进行比较。7. 如何缓解LLM幻觉?与幻觉有关的数据问题可以(至少理论上)通过创建高质量无噪声的数据集来解决。但是,验证和清理数百 GB 的文本语料库难度太大了,因此也有了一些其他的方法。通过使用外部知识验证主动检测和减轻幻觉:首先确定潜在幻觉的候选者,即生成句子的重要概念。然后,利用其logit输出值计算模型对它们的不确定性并检索相关知识。在减轻阶段,使用检索到的知识作为证据修复幻觉句子。将修复的句子附加到输入(和之前生成的句子)上,并继续生成下一个句子。这个过程不仅减轻了检测到的幻觉,而且还阻止了其在后续生成的句子中的传播。事实核心采样:随着生成的进行,前提变得更为确定,只有更少的单词选择可以使句子成为事实。因此,他们引入了事实核心采样算法,该算法在生成每个句子时动态调整“核心” p。SelfCheckGPT:如果模型真的掌握某个事实,那么多次生成的结果应该是相似的且事实一致的;相反,如果模型在胡扯,那么随机采样多次的结果会发散甚至矛盾。因此,可以从模型中采样多个 response(比如通过变化温度参数)并测量不同 response 之间的信息一致性,以确定哪些声明是事实,哪些是幻觉。这种信息一致性可以使用各种方法计算,比如可以使用神经方法计算语义等价(如 BERTScore)或使用 IE/QA-based 方法。8. LLMs什么时候最容易产生幻觉?数值混淆:当LLM处理与数字有关的文本,如日期或数值时,容易产生幻觉。处理长文本:在需要解读长期依赖关系的任务中,例如文档摘要或长对话历史,模型可能会生成自相矛盾的内容。逻辑推断障碍:若模型误解了源文本中的信息,它有可能产生不准确的结论。因此,模型的逻辑推理能力至关重要。上下文与内置知识的冲突:模型在处理信息时,可能会过度依赖于预训练阶段获取的知识,而忽略实际上下文,导致输出不准确。错误的上下文信息:当给定的上下文包含错误信息或基于错误的假设时(如:“为什么高尔夫球比篮球大?”或“氦的原子序数为什么是1?”),模型可能无法识别这些错误,并在其回答中产生幻觉。
0
0
0
浏览量1576
算法无限

LLM常见问题(中文指令微调部分)

1. 对模型进行指令微调需要注意什么?在选择好需要微调的一个大语言模型之后。比如chatglm、llama、bloom等,要想使用它,得了解三个方面:输入数据的格式、tokenization、模型的使用方式,需要注意的是不同的 LLM 需要的输入数据格式可能不一样。2. 对预训练模型进行指令微调数据如何处理?指令数据一般由三部分组成:instruction(instruct)、input(query)、output(answer),分别表示提示指令、文本、返回的结果。 构造的时候一般是 instruction 和 input 进行拼接,当然 input 可能是为空的,最终对 output 进行预测。需要注意的是,除了 instruction 之外,可能还有特殊的 prompt,不同模型的 prompt 是不一样的,比如:PROMPT_DICT = { "chatglm_input": ("{instruction}{input}"), "alpaca_input": ( "Below is an instruction that describes a task. " "Write a response that appropriately completes the request.\n\n" "### Instruction:\n{instruction}{input}\n\n### Response: " ), "bloom_input": ("Human: \n{instruction}{input}\n\nAssistant: \n"), }3. 对预训练模型进行指令微调 tokenization 如何构建?这里直接给出代码:from transformers import AutoTokenizer, AutoModel tokenizer = AutoTokenizer.from_pretrained("model_hub/chatglm-6b", trust_remote_code=True) text = "世界,你好" print(tokenizer(text)) print(tokenizer.convert_ids_to_tokens([18060, 12247, 14949])) print(tokenizer.decode([18060, 12247, 14949])) # 打印特殊 token print("BOS token: ", tokenizer.bos_token) print("EOS token: ", tokenizer.eos_token) print("PAD token: ", tokenizer.pad_token) print("UNK token: ", tokenizer.unk_token) # 打印特殊 token_id print("BOS token: ", tokenizer.bos_token_id) print("EOS token: ", tokenizer.eos_token_id) print("PAD token: ", tokenizer.pad_token_id) print("UNK token: ", tokenizer.unk_token_id) print(tokenizer.decode([130004, 67470, 24, 83049, 4, 76699, 24, 83049, 4, 67357, 65065, 24, 83049, 4, 64484, 68137, 63940, 24, 64539, 63972, 4, 69670, 72232, 69023, 24, 83049, 4, 64372, 64149, 24, 83049, 4, 63855, 24, 83049, 130005])) # 这个是chatglm特有的。 input_ids = tokenizer.build_inputs_with_special_tokens([1], [2]) print(input_ids)我们要注意看一下特殊标记是否为空,其它的话一些编码、解码、分词、tokenizer(文本)返回什么(input_ids、attention_mask)之类的。可以根据自己的需要进行尝试。4. 对预训练模型进行指令微调模型如何构建?型加载方式的话,一般使用的是 AutoTenizer 和 AutoModelForCausalLM,但有的模型可能有特殊的加载方式,比如LLaMA的加载方式就是:LlamaForCausalLM 和 LlamaTokenizer,。针对于 chatglm 的话,加载方式为:AutoTenizer 和 AutoModel,但需要注意的是其加载的时候设置了 trust_remote_code=True,该参数会根据映射找到真正使用的模型文件。下载好模型权重后,我们可以根据情况先看看效果:from transformers import AutoTokenizer, AutoModel tokenizer = AutoTokenizer.from_pretrained("model_hub/chatglm-6b", trust_remote_code=True) model = AutoModel.from_pretrained("model_hub/chatglm-6b", trust_remote_code=True).half().cuda() model = model.eval() response, history = model.chat(tokenizer, "你好", history=[]) print(response) response, history = model.chat(tokenizer, "晚上睡不着应该怎么办", history=history) print(response)5. 是否可以结合其他库使用?可以使用一些其他的库,比如:deepspeedtransformerspeft中使用的loradatasets加载数据需要注意的是, 我们可以把数据拆分为很多小文件放在一个文件夹下,然后遍历文件夹里面的数据,用datasets加载数据并进行并行处理后保存到磁盘上。如果中间发现处理数据有问题的话要先删除掉保存的处理后的数据,再重新进行处理,否则的话就是直接加载保存的处理好的数据。
0
0
0
浏览量879
算法无限

LLM常见问题(强化学习部分)

1. 简单介绍强化学习强化学习(Reinforcement learning)是一种机器学习技术,可以训练模型做出决策,以实现最佳结果。它模仿了人类为实现目标所采取的反复试验的学习过程。有助于实现目标的模型决策会得到加强,而偏离目标的操作将被忽略。 RL 算法在处理数据时使用奖惩模式。这些算法从每个操作的反馈中学习,并自行发现实现最终结果的最佳处理路径。此类算法还能够实现延迟满足。最好的整体策略可能需要短期的牺牲,因此其发现的最佳方法可能包括一些惩罚,或在过程中有一些迂回。RL 是一种强大的方法,可以帮助人工智能(AI)系统在看不见的环境中实现最佳结果。2. 简单介绍一下 RLHFRLHF就是基于人类反馈(Human Feedback)对语言模型进行强化学习(Reinforcement Learning),一般分为以下三个步骤:1.预训练语言模型:我们使用经典的预训练目标训练一个语言模型。这里可以用额外的文本或者条件对这个 LM 进行微调,例如 OpenAI 对 “更可取” (preferable) 的人工生成文本进行了微调,而 Anthropic 按 “有用、诚实和无害” 的标准在上下文线索上蒸馏了原始的 LM。2.训练奖励模型:RM 的训练是 RLHF 区别于旧范式的开端。这一模型接收一系列文本并返回一个标量奖励,数值上对应人的偏好。我们可以用端到端的方式用 LM 建模,或者用模块化的系统建模 (比如对输出进行排名,再将排名转换为奖励) 。这一奖励数值将对后续无缝接入现有的 RL 算法至关重要。3.用强化学习微调:长期以来出于工程和算法原因,人们认为用强化学习训练 LM 是不可能的。而目前多个组织找到的可行方案是使用策略梯度强化学习 (Policy Gradient RL) 算法、近端策略优化 (Proximal Policy Optimization,PPO) 微调初始 LM 的部分或全部参数。3. 奖励模型需要和基础模型一致吗?奖励模型和基础模型在训练过程中可以是一致的,也可以是不同的。这取决于你的任务需求和优化目标。如果你希望优化一个包含多个子任务的复杂任务,那么你可能需要为每个子任务定义一个奖励模型,然后将这些奖励模型整合到一个统一的奖励函数中。这样,你可以根据任务的具体情况调整每个子任务的权重,以实现更好的性能。另一方面,如果你的任务是单任务的,那么你可能只需要一个基础模型和一个对应的奖励模型,这两个模型可以共享相同的参数。在这种情况下,你可以通过调整奖励模型的权重来控制任务的优化方向。总之,奖励模型和基础模型的一致性取决于你的任务需求和优化目标。在实践中,你可能需要尝试不同的模型结构和奖励函数,以找到最适合你任务的解决方案。4. RLHF 在实践过程中存在哪些不足?人类反馈的代价高昂:获取高质量的人类反馈通常需要大量的人力和时间成本。需要大量人工花费时间来评估模型的行为并提供准确的反馈,这可能限制了 RLHF 方法的可扩展性和应用范围。人类反馈的主观性:人类反馈往往是主观的,不同的人可能会有不同的意见和判断。这可能导致模型在不同专家之间的反馈上存在差异,从而影响模型的训练和性能。错误反馈的影响:人类反馈可能存在错误或误导性的情况,这可能会对模型的训练产生负面影响。如果模型在错误的反馈指导下进行训练,可能会导致模型产生错误的行为策略。缺乏探索与利用的平衡:在 RLHF 中,人类反馈通常用于指导模型的行为,但可能会导致模型过于依赖人类反馈而缺乏探索的能力。这可能限制了模型发现新策略和优化性能的能力。5. 如何解决人工产生的偏好数据集成本较高,很难量产问题?引入模拟数据:使用模拟数据来代替或辅助人工产生的数据。模拟数据可以通过模拟环境或模型生成,以模拟人类用户的行为和反馈。这样可以降低数据收集的成本和难度,并且可以大规模生成数据。利用已有的高质量数据:收集互联网中现存的高质量问答数据并处理为数据集,可以快速收集到大量的数据。众包和协作:利用众包平台或协作机制来收集人工产生的偏好数据。通过将任务分发给多个人参与,可以降低每个人的负担,并且可以通过众包平台的规模效应来提高数据收集的效率。数据增强和迁移学习:通过数据增强技术,如数据合成、数据扩增等,来扩充有限的人工产生数据集。此外,可以利用迁移学习的方法,将从其他相关任务或领域收集的数据应用于当前任务,以减少对人工产生数据的需求。6. 如何解决三个阶段的训练(SFT->RM->PPO)过程较长,更新迭代较慢问题?可以采用 DPO (Direct Preference Optimization)的训练模式,DPO 提出了一种使用二进制交叉熵目标来精确优化 LLM 的方法,以替代基于 RLHF 的优化目标,从而大大简化偏好学习 pipeline。也就是说,完全可以直接优化语言模型以实现人类的偏好,而不需要明确的奖励模型或强化学习。与现有的算法一样,DPO 也依赖于理论上的偏好模型(如 Bradley-Terry 模型),以此衡量给定的奖励函数与经验偏好数据的吻合程度。然而,现有的方法使用偏好模型定义偏好损失来训练奖励模型,然后训练优化所学奖励模型的策略,而 DPO 使用变量的变化来直接定义偏好损失作为策略的一个函数。鉴于人类对模型响应的偏好数据集,DPO 因此可以使用一个简单的二进制交叉熵目标来优化策略,而不需要明确地学习奖励函数或在训练期间从策略中采样。7. 如何解决 PPO 的训练过程同时存在4个模型(2训练,2推理),对计算资源的要求较高问题?可以采用 RRHF(Rank Response from Human Feedback)的训练模式,RRHF 不需要强化学习,可以利用不同语言模型生成的回复,包括 ChatGPT、GPT-4 或当前的训练模型。RRHF通过对回复进行评分,并通过排名损失来使回复与人类偏好对齐。RRHF 通过通过排名损失使评分与人类的偏好(或者代理的奖励模型)对齐。RRHF 训练好的模型可以同时作为生成语言模型和奖励模型使用。RRHF算法可以有效地将语言模型输出概率与人类偏好对齐,其训练思路非常简单,训练完成的模型有几个特点:仅需要1到2个模型,而PPO需要4个模型,因此RRHF算法更加简单高效。监督微调(SFT)可以被看作是RRHF算法的一种特殊形式。RRHF 算法可以同时被用作语言模型和奖励模型。RRHF 算法可以在较低的训练难度下拟合奖励模型的偏好,达到PPO算法的效果,并且避免了PPO算法中的复杂性和不稳定性问题。
0
0
0
浏览量464
算法无限

LLM常见问题(思维链部分)

1. 什么是思维链提示?思维链(Chain-of-thought,CoT),指的是一系列有逻辑关系的思考步骤,形成一个完整的思考过程。人在日常生活中,随时随地都会用思维链来解决问题,比如工作、读书经常用到的思维导图,就是为了尽可能全面拆解步骤,不忽略重要细节,从而充分地考虑问题。这种步骤分解的方式用在提示学习中,就被称为思维链提示,将大语言模型的推理过程,分解成一个个步骤,直观地展现出来,这样开发人员可以在LLM推理出现错误时,就及时地修复。2. 思维链提示本质是什么?思维链提示的本质就是把一个多步骤推理问题,分解成很多个中间步骤,分配给更多的计算量,生成更多的 token,再把这些答案拼接在一起进行求解。3. 思维链提示与标准的提示学习方法有什么不同?“思路链提示”方法是在少样本学习中,在输入-输出对的输出部分提供一系列中间推理步骤,来增强语言模型的复杂推理能力。具体来说,“思路链提示”是在标准的少样本学习中,在每一个输入-输出对后面添加该输出的生成过程。这里的生成过程就是一系列从输入到输出的中间语言推理步骤。例如,在一个算术词题的解答中,会给出每一步的计算过程,而不仅仅只给出最终答案。在一个需要常识推理的问题中,会给出一系列的语言推论步骤。与只给出最终输出的标准提示学习不同,“思路链提示”提供了从输入到输出的完整推理路径。这模拟了人类逐步思考解决复杂问题的过程。4. 思维链提示为什么可以提高语言模型的复杂推理能力?它的优势在哪里?分解复杂问题:将多步推理任务分解成多个简单的子任务,降低问题难度。提供步骤示范:为每一推理步骤提供了语言表达,示范了如何逐步推理。引导组织语言:语言表达引导模型学习组织语言进行逻辑推理。加强逻辑思维:让模型模拟人类逻辑思维的过程,强化逻辑推理能力。调动背景知识:语言表达可以激活模型的背景常识,帮助推理。提供解释性:使模型的推理过程可解释,便于 debugging。适用范围广:原则上适用于任何文本到文本的任务。单模型多任务:基于同一模型就可以做思路链提示,无需针对每一个任务微调。少样本学习:只需要给出几个示范示例,不需要大量标注数据。5. 思维链提示适用场景有哪些?思维链提示主要适用于需要复杂推理的领域,例如数学、常识和符号推理等。思维链提示可以帮助大语言模型生成一系列推理步骤,从而解决多步骤的问题,提高准确率和可解释性。思维链提示也可以与其他技术结合,例如行动指令、检索、组装等,让大语言模型具备更强的执行能力和知识生成能力。6. 思维链提示目前还存在哪些不足点?生成的思路链不一定事实准确,需要进一步改进提高事实性。思路链提示的成功依赖于较大规模的语言模型,使用成本较高。思路链的标注成本较高,不易大规模应用。可以考虑自动生成思路链。思路链的提示示例易受提示工程影响,结果变化大。可以探索更稳健的提示方法。思路链并不能完全反映模型的计算过程,理解内在机制需要更深入研究。思路链提示在一些简单任务上的效果提升有限,可以扩展应用范围。7. 思维链提示对推动语言模型复杂推理能力研究有哪些启发和影响?为增强语言模型推理能力提供了新的思路。证明了语言表达的中间推理步骤对语言模型的重要作用。显示了模型规模增长对产生正确思路链的 importance。表明了探索语言内在的逻辑结构的巨大价值和潜力。展示了语言模型的惊人推理潜力,通过简单提示就能实现强大的推理。8. 思维链提示对实现真正的通用人工智能仍面临哪些挑战?思路链的质量和正确性仍需提高。对语言模型内在推理机制理解不够。推广到更多不同类型的推理任务上。需要更大规模的模型作为支撑。需要提高样本效率,降低使用成本。9. 如何通过增加模型规模来获得语言模型强大的思路链推理能力的?这与模型获得的哪些能力有关?算术运算能力的提升:参数量越大的语言模型,其基本的算数运算能力越强,可以更准确地完成思路链中的算术推理。语义理解能力的增强:模型规模越大,可以建立更丰富的词汇语义信息,有助于分析理解问题语义。逻辑推理能力的增强:参数量提升可以增强模型的逻辑推理建模能力,有助于构建合理的推理链。知识表示能力的扩展:规模更大的模型可以学习更丰富的知识,提供问题所需的相关背景常识。长依赖建模能力的提高:参数量的增加可以增强模型学习长距离依赖的能力,有利于推理链的生成。抽象建模和泛化能力增强:更大模型可以学到更抽象的知识表示,并应用到新问题上。计算资源和数据集规模的提升:计算资源增加可以支持训练更大模型,大数据集可以提供更丰富的学习素材。10. 你认为可以在哪些其他方面应用“思路链提示”这一思路来提升语言模型的能力?复杂问题解决:例如数学题或逻辑推理等需要多步推理的问题。思路链可以帮助语言模型分解问题,逐步解决。程序合成:可以提示语言模型先输出每一行代码的自然语言说明,然后再输出实际代码,从而合成程序。翻译:可以提示语言模型先输出源语言到目标语言的逐词翻译,然后整合生成完整的翻译结果。总结:可以提示语言模型先输出段落的主题句,然后输出段落的要点,最后生成完整的总结。创作:如创作故事或诗歌,可以提示思路链,让语言模型按照故事情节或诗歌主题逐步创作。问答:可以提示思路链让语言模型解释其推理过程,而不仅仅给出结果,提高问答的透明度。对话:在闲聊对话中提示思路链,让语言模型的回复更合乎逻辑,而不仅是无意义的应答。可解释的预测:在进行预测任务时,让语言模型输出导致预测结果的推理链,提高可解释性。11. 如果需要你对思维链提示进行改进,你觉得你会改进哪些地方?提示的泛化能力有限:当前的提示方式过于依赖具体的示例,泛化能力有限,需要更多提示示例才能适应新的任务。未来研究可以探索如何用更少示例或从零示例中泛化。提示编写需要专业知识:思路链提示当前需要人工编写,需要一定专业知识。可以探索自动生成提示的方法。结果正确性无法保证:思路链不保证完全正确,可能导致错误结果。可以结合验证器提高正确性。评估任务范围有限:目前主要在算术推理上评估,可以拓展到更多语言任务上验证效果。模型规模大:当前只在千亿和百亿参数量级模型上见效,可以研究在小模型上应用的方法。12. 思维链提示未来研究方向?提高提示泛化能力,减少人工参与。在更多语言任务中验证效果,评估推理能力。在小型模型上也实现类似推理提升的技术。结合验证器等手段提高生成的事实准确性。用提示的思路探索不同的模型结构设计。
0
0
0
浏览量2018
算法无限

LLM常见问题(训练集部分)

1.  SFT(有监督微调)的数据集格式?指令数据一般为 json 格式,包含 Instruction、Input、Output 三个字段(可以为空),每行一条样本。Instruction(指令) :Instruction 是对模型的输入文本或问题进行进一步说明或约束的指令。它可以是一种特定的格式或标记,用于告诉模型如何处理输入数据,或者提供一些额外的信息,以便模型更好地理解和生成输出。Instruction的作用是为模型提供更准确和具体的指导,以便生成符合预期的文本。Instruction 可以包括要求模型遵循特定的格式、风格或主题,或者指示模型关注某些特定的方面或细节。Input(输入) :Input 是模型实际接收的文本或数据。它由 Prompt 和 Instruction 组成,提供了完整的上下文和指导,用于生成输出。Input 可以包含多个句子或段落,以提供更全面的信息。Input 会被输入到 LLMs 中,用于推理和生成输出。Response(输出) :Response 是 LLMs 生成的输出文本或回答。它是模型对Input的理解和处理结果。 Response 的内容取决于 LLMs 的能力和训练数据,它可以是文本、回答、建议、解释等形式。Response 是模型根据 Input 和 Instruction 生成的,它应该符合 Instruction 的要求,并尽可能准确和有针对性。2.  RM(奖励模型)的数据格式?RM 的训练数据包含输入和输出,输入通常是一个指令,输出通常包含多个数据,用 \t 分隔,数据需要进行排序,排在前面的数据正向倾向比排在后面的高。3.  PPO(强化学习)的数据格式?PPO 的训练数据通常只包含输入数据,由被训练的模型和奖励模型共同进行预测,通过计算它们输出值的 KL 散度来对模型进行优化。4.  找数据集哪里找?目前有不少公开的通用领域数据集,同时也有一些垂直领域有公开的数据集,若认为数据集数量不够可以收集相关领域的书籍或者用爬虫爬取互联网中的相关数据,再进行清洗和整理。也可以利用能力比较强大的模型生成数据用于训练。5.  微调需要多少条数据?根据 Scaling Laws,随着模型大小、数据集大小和用于训练的计算浮点数的增加,模型的性能会提高。并且为了获得最佳性能,所有三个因素必须同时放大。一般来说对于给定模型的理想训练数据集 token 数量大约是模型中参数数量的20倍。6.  有哪些大模型的训练集?国内的训练集非常多,这里列举几个相对知名的通用数据集。moss-003-sft-data:由复旦大学 MOSS 团队开源的中英文多轮对话数据,包含100万+数据ultrachat:由清华大学开源的英文多轮对话数据,包含140万+数据WizardLM_evol_instruct_V2_143k:由 WizardLM 项目开源的英文指令微调数据集,通过 Evol-Instruct 方法让指令进化,加强指令的复杂度,以提升模型对复杂指令的遵循能力。包含143k条数据。school_math_0.25M:由 BELLE 项目组开源的数学运算指令数据,包含25万条数据。7.  进行领域大模型预训练应用哪些数据集比较好?领域特定文本数据集:收集与目标领域相关的文本数据集,例如专业领域的论文、报告、文档、书籍等。这些数据集可以提供领域内的专业术语、上下文和特定领域的知识。领域内的网页内容:从目标领域相关的网页抓取文本内容。可以通过爬虫技术从相关网站上获取与目标领域相关的网页文本数据。领域内的新闻文章:收集与目标领域相关的新闻文章。新闻文章通常包含了领域内的最新信息和事件,可以帮助模型了解领域内的动态和趋势。行业报告和白皮书:获取与目标领域相关的行业报告、白皮书和研究文献。这些文献通常包含了领域内的专业分析、统计数据和趋势预测,可以帮助模型了解行业背景和发展趋势。社交媒体数据:收集与目标领域相关的社交媒体数据,如推特、微博、论坛等。社交媒体上的内容通常反映了人们在目标领域中的讨论、观点和问题,可以帮助模型了解领域内的热点和用户需求。领域内的对话数据:获取与目标领域相关的对话数据,如客服对话、问答平台数据等。这些对话数据可以帮助模型学习领域内的常见问题、解决方案和用户需求。
0
0
0
浏览量1229
算法无限

LLM常见问题(位置编码部分)

1. 什么是位置编码?位置编码分为绝对位置编码和相对位置编码,绝对位置编码就是直接按照函数公式或者可学习参数得到每个 token 的位置编码加到 token 的输入表征上。相对位置编码(Relative Position Embedding,RPE)是在 self-attention 的时候关注当前 token 的时候,其他 token 按照和当前 token 的相对位置编码其位置信息。2. 什么是绝对位置编码?绝对位置编码是一种基于位置嵌入的方法,其中每个位置都被分配了一个唯一的位置向量。 这些向量是固定的,与输入序列的内容无关。 这种编码方式对于处理较短的序列效果较好,但在处理长序列时可能会存在问题,因为序列的长度超过了模型能够处理的位置编码的范围。3. 什么是相对位置编码?相对位置编码是一种基于相对位置的方法,其中每个位置被编码为一个偏移量,表示该位置与其他位置之间的相对距离。相对位置编码可以通过在输入嵌入中添加额外的信息来实现。这种编码方式可以处理长序列,并且能够在不同的上下文中保持一定的一致性。4. 旋转位置编码 RoPE 思路是什么?RoPE 基本思路是通过绝对位置编码的方式,使得模型可以注意到相对位置。5. 旋转位置编码 RoPE 如何计算?6. 旋转位置编码 RoPE 有什么优点?旋转编码RoPE 可以有效地保持位置信息的相对关系,即相邻位置的编码之间有一定的相似性,而远离位置的编码之间有一定的差异性。 这样可以增强模型对位置信息的感知和利用。7. 旋转位置编码 RoPE 被哪些 LLMs 应用?RoPE 旋转位置编码是苏神提出来的一种相对位置编码,之前主要用在自研的语言模型 roformer 上,后续谷歌 Palm 和 meta 的 LLaMA 等都是采用此位置编码,8. 什么是长度外推问题?长度外推性是一个训练和预测的长度不一致的问题。 具体来说,不一致的地方有两点:预测的时候用到了没训练过的位置编码(不管绝对还是相对)。预测的时候注意力机制所处理的token数量远超训练时的数量。9. 长度外推问题的解决方法有哪些?Alibi:这种方法较为粗暴,是直接作用在attention score中,给 attention score 加上一个预设好的偏置矩阵,相当于 q 和 k 相对位置差 1 就加上一个 -1 的偏置。其实相当于假设两个 token 距离越远那么相互贡献也就越低。KERPLE:该方法主要针对 Alibi 做了一些微小改进,将内积的 bias 由之前自然数值幂函数或指数函数,并且改成可学习参数。Sandwich:该方法将ALiBi的线性bias改为正弦编码的内积pm*pn,上述编码也是对于正余弦三角式的一种改进。10. ALiBi (Attention with Linear Biases) 思路是什么?ALiBi 的做法其实和T5 bias类似,直接给 q*k attention score 加上了一个线性的bias,不给词向量加入位置嵌入向量,而是用一个和query, key之间的距离成比例的一个“惩罚项”来偏置query-key的注意力得分:11. ALiBi (Attention with Linear Biases) 的偏置矩阵是什么?有什么作用?相对距离的矩阵,例如 q1, k1 之间的距离是 0,所以对应位置就是 0;q2 和 k1,是相对位置偏移为 k 的索引 1 - q 的索引 2,得到 1 - 2 = -1,就对应到了中间矩阵的取值为 -1 了。该偏置矩阵用于计算两个 token 之间的贡献度。12. ALiBi (Attention with Linear Biases) 有什么优点?这种简单的相对位置编码使得模型即使在很长的文本输入序列中也能保持高性能。13. ALiBi (Attention with Linear Biases) 被哪些 LLMs 应用?由MosaicML团队开源出来了英文预训练大模型 MPT 以及 BLOOM 都使用了这种编码方法。
0
0
0
浏览量1506
算法无限

LLM常见问题(测评部分)

1. 大模型怎么评测?自动评测和人工评测。这两种方法在评测语言模型和机器翻译等任务时起着重要的作用。自动评测方法基于计算机算法和自动生成的指标,能够快速且高效地评测模型的性能。而人工评测则侧重于人类专家的主观判断和质量评测,能够提供更深入、细致的分析和意见。了解和掌握这两种评测方法对准确评测和改进语言模型的能力十分重要。2. 大模型的 honest 原则是如何实现的?模型如何判断回答的知识是训练过的已知的知识,怎么训练这种能力?大模型需要遵循的 helpful,honest, harmless 的原则。可以有意构造如下的训练样本,以提升模型遵守 honest 原则,可以算 trick 了:微调时构造知识问答类训练集,给出不知道的不回答,加强 honest 原则;阅读理解题,读过的要回答,没读过的不回答,不要胡说八道。3. 如何衡量大模型水平?在评测 LLMs 的性能时,选择合适的任务和领域对于展示大型语言模型的表现、优势和劣势至关重要。为了更清晰地展示 LLMs 的能力水平,文章将现有的任务划分为以下7个不同的类别:自然语言处理:包括自然语言理解、推理、自然语言生成和多语言任务鲁棒性、伦理、偏见和真实性医学应用:包括医学问答、医学考试、医学教育和医学助手社会科学自然科学与工程:包括数学、通用科学和工程代理应用:将 LLMs 作为代理使用其他应用4. 大模型评估方法有哪些?首先是“直接评估指标”这一类别。这些是在人工智能领域长期以来广泛使用的传统指标。像准确率(accuracy)和F1得分(F1 score)等指标属于这个类别。通常情况下,这种方法涉及从模型中获取单一的输出,并将其与参考值进行比较,可以通过约束条件或提取所需信息的方式来实现评估。接下来是第二类方法,称为“间接或分解的启发式方法(indirect or decomposed heuristics)”。在这种方法中,我们利用较小的模型(smaller models)来评估主模型(the main model)生成的答案,这些较小的模型可以是微调过的模型或原始的分解模型(raw decompositions)。第三类评估方法被称为”基于模型的评估”。在这种方法中,模型本身提供最终的评估分数或评估结果。然而,这也引入了额外的可变因素。即使模型可以获取到ground truth信息,评估指标本身也可能在评分过程中产生随机因素或不确定因素。5. 大模型评估工具有哪些?ChatbotArena:借鉴游戏排位赛机制,让人类对模型两两评价SuperCLUE:中文通用大模型综合性评测基准,尝试全自动测评大模型C-Eval:采用 1.4 万道涵盖 52 个学科的选择题,评估模型中文能力FlagEval:采用“能力—任务—指标”三维评测框架
0
0
0
浏览量989
算法无限

LLM常见问题(中文二次预训练部分)

1. 为什么需要进行继续预训练?我们新增加了一些中文词汇到词表中,但是这些词汇是没有得到训练的,因此在进行指令微调之前我们要进行预训练。预训练的方式一般都是相同的,简单来说,就是根据上一个字预测下一个字是什么。我们这里直接使用 IDEA-CCNL/Wenzhong2.0-GPT2-110M-BertTokenizer-chinese 模型,并且 tokenizer 也是其自带的。2. 如何对继续预训练数据预处理?首先使用 tokenizer() 函数得到相关的输入,需要注意的是可能会在文本前后添加特殊的标记,比如 bos_token_id 和 eos_token_id,针对于不同模型的 tokneizer 可能会不太一样。这里在 input_ids 前后添加了 21134 和 21133 两个标记。然后将所有文本的 input_ids、attention_mask、token_type_ids 各自拼接起来(展开后拼接,不是二维数组之间的拼接),再设定一个最大长度 block_size,这样得到最终的输入。import os import logging import datasets import transformers from pprint import pprint from itertools import chain from datasets import load_dataset, concatenate_datasets from transformers.testing_utils import CaptureLogger from transformers import AutoTokenizer, LlamaTokenizer tok_logger = transformers.utils.logging.get_logger("transformers.tokenization_utils_base") logger = logging.getLogger(__name__) lm_datasets = [] files = ["data/test_corpus.txt"] data_cache_dir = "./cache_data" preprocessing_num_workers = 1 # tokenizer = AutoTokenizer.from_pretrained("hfl/chinese-bert-wwm-ext") tokenizer = LlamaTokenizer.from_pretrained("ziqingyang/chinese-llama-lora-7b") tokenizer = AutoTokenizer.from_pretrained("IDEA-CCNL/Wenzhong2.0-GPT2-110M-BertTokenizer-chinese") def print_dict(adict): for k,v in adict.items(): print(k, v) def tokenize_function(examples): with CaptureLogger(tok_logger) as cl: output = tokenizer(examples["text"]) # clm input could be much much longer than block_size if "Token indices sequence length is longer than the" in cl.out: tok_logger.warning( "^^^^^^^^^^^^^^^^ Please ignore the warning above - this long input will be chunked into smaller bits" " before being passed to the model." ) return output block_size = 128 # 将所有文本进行拼接 def group_texts(examples): # Concatenate all texts. concatenated_examples = {k: list(chain(*examples[k])) for k in examples.keys()} total_length = len(concatenated_examples[list(examples.keys())[0]]) # We drop the small remainder, we could add padding if the model supported it instead of this drop, you can # customize this part to your needs. if total_length >= block_size: total_length = (total_length // block_size) * block_size # Split by chunks of max_len. result = { k: [t[i : i + block_size] for i in range(0, total_length, block_size)] for k, t in concatenated_examples.items() } result["labels"] = result["input_ids"].copy() return result for idx, file in enumerate(files): data_file = file filename = ''.join(file.split(".")[:-1]) cache_path = os.path.join(data_cache_dir, filename) os.makedirs(cache_path, exist_ok=True) try: processed_dataset = datasets.load_from_disk(cache_path, keep_in_memory=False) print(f'training datasets-{filename} has been loaded from disk') except Exception: cache_dir = os.path.join(data_cache_dir, filename + "_text") os.makedirs(cache_dir, exist_ok=True) raw_dataset = load_dataset("text", data_files=data_file, cache_dir=cache_dir, keep_in_memory=False) print_dict(raw_dataset["train"][0]) # 直接进行tokenize,需要注意的是只需要在句子开头加上bos_token tokenized_dataset = raw_dataset.map( tokenize_function, batched=True, num_proc=preprocessing_num_workers, remove_columns="text", load_from_cache_file=True, keep_in_memory=False, cache_file_names={k: os.path.join(cache_dir, f'tokenized.arrow') for k in raw_dataset}, desc="Running tokenizer on dataset", ) print_dict(tokenized_dataset["train"][0]) grouped_datasets = tokenized_dataset.map( group_texts, batched=True, num_proc=preprocessing_num_workers, load_from_cache_file=True, keep_in_memory=False, cache_file_names={k: os.path.join(cache_dir, f'grouped.arrow') for k in tokenized_dataset}, desc=f"Grouping texts in chunks of {block_size}", ) processed_dataset = grouped_datasets print_dict(processed_dataset["train"][0]) processed_dataset.save_to_disk(cache_path) if idx == 0: lm_datasets = processed_dataset['train'] else: assert lm_datasets.features.type == processed_dataset["train"].features.type lm_datasets = concatenate_datasets([lm_datasets, processed_dataset["train"]]) lm_datasets = lm_datasets.train_test_split(test_size=0.1) print_dict(lm_datasets["train"][0])3. 如何构建模型?from transformers import BertTokenizer,GPT2LMHeadModel, AutoModelForCausalLM hf_model_path = 'IDEA-CCNL/Wenzhong2.0-GPT2-110M-BertTokenizer-chinese' tokenizer = BertTokenizer.from_pretrained(hf_model_path) model = AutoModelForCausalLM.from_pretrained(hf_model_path) def generate_word_level(input_text,n_return=5, max_length=128,top_p=0.9): inputs = tokenizer(input_text,return_tensors='pt', add_special_tokens=False).to(model.device) gen = model.generate( inputs=inputs['input_ids'], max_length=max_length, do_sample=True, top_p=top_p, eos_token_id=21133, pad_token_id=0, num_return_sequences=n_return) sentences = tokenizer.batch_decode(gen) for idx,sentence in enumerate(sentences): print(f'sentence {idx}: {sentence}') print('*'*20) return gen # 西湖的景色 outputs = generate_word_level('你好,世界!',n_return=5,max_length=128) print(outputs)接下来是使用该模型针对我们自己的数据进行继续预训练了。需要注意的几个地方:如果是我们自己定义的tokenizer,需要将模型的嵌入层和lm_head层的词表数目进行重新设置:model_vocab_size = model.get_output_embeddings().weight.size(0) model.resize_token_embeddings(len(tokenizer))这里我们使用参数有效微调方法lora进行微调,我们需要设置额外保存的参数:transformer.wte,lm_head。这个可以通过find_lora_names.py里面获得。原始chinsee-llama-alpaca使用lora保存参数有问题,这里进行了修改并只保存一份lora权重。使用test_pretrained_model.py的时候也要记得先对vocab_size进行重新设置。训练指令:torchrun --nnodes 1 --nproc_per_node 1 run_clm_pt_with_peft.py \ --deepspeed ds_zero2_no_offload.json \ --model_name_or_path IDEA-CCNL/Wenzhong2.0-GPT2-110M-BertTokenizer-chinese \ --tokenizer_name_or_path IDEA-CCNL/Wenzhong2.0-GPT2-110M-BertTokenizer-chinese \ --dataset_dir data \ --data_cache_dir temp_data_cache_dir \ --validation_split_percentage 0.001 \ --per_device_train_batch_size 32 \ --per_device_eval_batch_size 16 \ --do_train --seed $RANDOM \ --fp16 \ --max_steps 2500 \ --lr_scheduler_type cosine \ --learning_rate 2e-4 \ --warmup_ratio 0.05 \ --weight_decay 0.01 \ --logging_strategy steps \ --logging_steps 10 \ --save_strategy steps \ --save_total_limit 3 \ --save_steps 50 \ --gradient_accumulation_steps 1 \ --preprocessing_num_workers 8 \ --block_size 512 \ --output_dir output_dir \ --overwrite_output_dir \ --ddp_timeout 30000 \ --logging_first_step True \ --lora_rank 8 \ --lora_alpha 32 \ --trainable c_attn \ --modules_to_save transformer.wte,lm_head \ --lora_dropout 0.05 \ --torch_dtype float16 \ --gradient_checkpointing \ --ddp_find_unused_parameters False4. 如何使用模型?import os import torch from transformers import BertTokenizer,GPT2LMHeadModel, AutoModelForCausalLM from peft import PeftModel hf_model_path = 'IDEA-CCNL/Wenzhong2.0-GPT2-110M-BertTokenizer-chinese' tokenizer = BertTokenizer.from_pretrained(hf_model_path) model = AutoModelForCausalLM.from_pretrained(hf_model_path) model_vocab_size = model.get_output_embeddings().weight.size(0) model.resize_token_embeddings(len(tokenizer)) model = PeftModel.from_pretrained(model, os.path.join("output_dir", "adapter_model"), torch_dtype=torch.float32) model.cuda() model.eval() def generate_word_level(input_text,n_return=5,max_length=128,top_p=0.9): inputs = tokenizer(input_text,return_tensors='pt',add_special_tokens=False).to(model.device) gen = model.generate( inputs=inputs['input_ids'], max_length=max_length, do_sample=True, top_p=top_p, eos_token_id=21133, pad_token_id=0, num_return_sequences=n_return) sentences = tokenizer.batch_decode(gen) for idx,sentence in enumerate(sentences): print(f'sentence {idx}: {sentence}') print('*'*20) return gen outputs = generate_word_level('宫廷玉液酒',n_return=5,max_length=128) print(outputs)
0
0
0
浏览量461
算法无限

LLM常见问题(LangChain部分)

1. 什么是 LangChain?LangChain 是围绕 LLMs(大语言模型)建立的一个框架,其自身并不开发 LLMs,它的核心理念是为各种 LLMs 实现通用的接口,把 LLMs 相关的组件“链接”在一起,简化 LLMs 应用的开发难度,方便开发者快速地开发复杂的 LLMs 应用。LangChain 主要支持 Models、Prompts、Memory、Indexes、Cahins、Agents 六个组件。2. LangChain 包含哪些 核心概念?1. LangChain 中 Components and Chains 是什么?在LangChain 中,Component 是一种模块化的构建块,可以相互组合以构建强大的应用程序。 而 Chain 则是由一系列 Components 或其他 Chains 组合而成的,用于完成特定的任务。2. LangChain 中 Prompt Templates 是什么?Prompt Templates是一种可复制的生成Prompt的方式,包含一个文本字符串,可以接受来自终端用户的一组参数并生成Prompt。Prompt Templates可以包含指令、少量示例和一个向语言模型提出的问题。我们可以使用Prompt Templates技术来指导语言模型生成更高质量的文本,从而更好地按照需求完成的任务。3. LangChain 中 Example Selectors 是什么?Example Selectors 的主要作用就是从给定的 examples 中选择需要的 examples 出来提供给大模型使用,从而减少会话的token数目。4. LangChain 中 Output Parsers 是什么?Output Parsers 模块可以使模型输出的期望的结构化文本,、 LangChain中的output parsers一共有七个,分别是List parser、Datetime parser、Enum parser、Pydantic (JSON) parser、Structured output parser、Retry parser、Auto-fixing parser。前四种parser用于常见的格式输出转换,Structured output parser用于多字段输出时使用,最后的两种是对于格式转换失败之后的修复措施。5. LangChain 中 Indexes and Retrievers 是什么?Indexes 是指为了使LLM与文档更好地进行交互而对其进行结构化的方式。 在链中,索引最常用于“检索”步骤中,该步骤指的是根据用户的查询返回最相关的文档: 索引不仅可用于检索,还可用于其他目的,检索可以使用除索引之外的其他逻辑来查找相关文档。Retrievers 是一个接口,它根据非结构化查询返回文档。它比向量存储更通用。检索器不需要能够存储文档,只需返回(或检索)它。向量存储可以用作检索器的骨干,但也有其他类型的检索器。6. LangChain 中 Chat Message History 是什么?ChatMessageHistory 类负责记住所有以前的聊天交互数据,然后可以将这些交互数据传递回模型、汇总或以其他方式组合。 这有助于维护上下文并提高模型对对话的理解。7. LangChain 中 Agents and Toolkits 是什么?Agent 是在LangChain 中推动决策制定的实体。 他们可以访问一套工具,并可以根据用户输入决定调用哪个工具。 Tookits 是一组工具,当 Agent 和 Tookits 一起使用时,可以完成特定的任务。Agent 负责使用适当的工具运行代理。3. 什么是 LangChain Agent?LangChain Agent 指使用 LangChain 构建的智能体,Agent 核心思想是使用LLM作为大脑自动思考,自动决策选择执行不同的动作,最终完成我们的目标任务,例如数据收集、数据处理、决策支持等。 Agent 可以是自主的,具备一定程度的智能和自适应性,以便在不同的情境中执行任务。4. LangChain 支持哪些功能?LangChain 可以轻松管理与语言模型的交互,将多个组件链接在一起,并集成额外的资源,例如 API 和数据库。LangChain 为特定用例提供了多种组件,例如个人助理、文档问答、聊天机器人、查询表格数据、与 API 交互、提取、评估和汇总。5. 什么是 LangChain model?LangChain model 是一种抽象,表示框架中使用的不同类型的模型。LangChain 中的模型主要分为三类:LLM(大型语言模型) :这些模型将文本字符串作为输入并返回文本字符串作为输出。它们是许多语言模型应用程序的支柱。聊天模型( Chat Model) :聊天模型由语言模型支持,但具有更结构化的 API。他们将聊天消息列表作为输入并返回聊天消息。这使得管理对话历史记录和维护上下文变得容易。文本嵌入模型(Text Embedding Models) :这些模型将文本作为输入并返回表示文本嵌入的浮点列表。这些嵌入可用于文档检索、聚类和相似性比较等任务。6. LangChain 包含哪些特点?组件化:为使用语言模型提供抽象层,以及每个抽象层的一组实现。组件是模块化且易于使用的,无论开发是否使用LangChain框架的其余部分。现成的链:结构化的组件集合,用于完成特定的高级任务7. LangChain 存在问题及方法方案1. LangChain 低效的令牌使用问题Langchain的一个重要问题是它的令牌计数功能,对于小数据集来说,它的效率很低。虽然一些开发人员选择创建自己的令牌计数函数,但也有其他解决方案可以解决这个问题。替代解决方案:Tiktoken是OpenAI开发的Python库,用于更有效地解决令牌计数问题。它提供了一种简单的方法来计算文本字符串中的令牌,而不需要使用像Langchain这样的框架来完成这项特定任务。2. LangChain 文档的问题文档是任何框架可用性的基石,而Langchain因其不充分且不准确的文档而受到指责。误导性的文档可能导致开发项目的代价高昂的,并且还经常有404错误页面。这可能与Langchain还在快速发展有关,作为快速的版本迭代,文档的延后性可以理解,只能说希望以后尽快完善吧。3. LangChain 太多概念容易混淆,过多的“辅助”函数问题Langchain的代码库因很多概念让人混淆而备受诟病,这使得开发人员很难理解和使用它。这种问题的一个方面是存在大量的“helper”函数,仔细检查就会发现它们本质上是标准Python函数的包装器。开发人员可能更喜欢提供更清晰和直接访问核心功能的框架,而不需要复杂的中间功能。4. LangChain 行为不一致并且隐藏细节问题LangChain隐藏重要细节和行为不一致,这可能导致生产系统出现意想不到的问题。例Langchain ConversationRetrievalChain的一个方面,它涉及到输入问题的重新措辞。这种重复措辞有时会非常广泛,甚至破坏了对话的自然流畅性,使对话脱离了上下文。5. LangChain 缺乏标准的可互操作数据类型问题Langchain缺乏表示数据的标准方法。这种一致性的缺乏可能会阻碍与其他框架和工具的集成,使其难以在更广泛的机器学习工具生态系统中工作。8. LangChain 代替方案LlamaIndex是一个数据框架,它可以很容易地将大型语言模型连接到自定义数据源。它可用于存储、查询和索引数据,还提供了各种数据可视化和分析工具。Deepset Haystack是另外一个开源框架,用于使用大型语言模型构建搜索和问答应用程序。它基于Hugging Face Transformers,提供了多种查询和理解文本数据的工具。
0
0
0
浏览量2024
算法无限

LLM常见问题(RAG部分)

1. 什么是 Graph RAG?Graph RAG 是由悦数图数据提出的概念,是一种基于知识图谱的检索增强技术,通过构建图模型的知识表达,将实体和关系之间的联系用图的形式进行展示,然后利用大语言模型 LLM进行检索增强。通过图技术构建知识图谱提升 In-Context Learning 的全面性为用户提供更多的上下文信息,能够帮助大语言模型(LLM)更好地理解实体间的关系,提升自己的表达和推理能力。2. 为什么需要 Graph RAG?身处信息爆炸时代,如何从海量信息中获取准确全面的搜索结果,并以更直观、可读的方式呈现出来是大家期待达成的目标。传统的搜索增强技术受限于训练文本数量、质量等问题,对于复杂或多义词查询效果不佳,更无法满足 ChatGPT 等大语言模型应用带来的大规模、高并发的复杂关联查询需求。知识图谱可以减少基于嵌入的语义搜索所导致的不准确性。“保温大棚”与“保温杯”,尽管在语义上两者是存在相关性的,但在大多数场景下,这种通用语义(Embedding)下的相关性很高,进而作为错误的上下文而引入“幻觉”。这时候,可以利用领域知识的知识图谱来缓解这种幻觉。3. Graph RAG 思路介绍?Graph RAG 将知识图谱等价于一个超大规模的词汇表,而实体和关系则对应于单词。通过这种方式,Graph RAG 在检索时能够将实体和关系作为单元进行联合建模。4. 用代码实现 Graph RAG ?一个简单的 Graph RAG 思想在于,对用户输入的query提取实体,然后构造子图形成上下文,最后送入大模型完成生成,如下代码所示:def simple_graph_rag(query_str, nebulagraph_store, llm): entities = _get_key_entities(query_str, llm) graph_rag_context = _retrieve_subgraph_context(entities) return _synthesize_answer( query_str, graph_rag_context, llm)5. Graph RAG 排序优化方式是什么?基于知识图谱召回的方法可以和其他召回方法一起融合,但这种方式在图谱规模很大时其实是有提升空间的。突出的缺点在于:子图召回的多条路径中可能会出现多种不相关的。实体识别阶段的精度也有限,采用关键词提取还比较暴力,方法也值得商榷。这种方式依赖于一个基础知识图谱库,如果数据量以及广度不够,有可能会引入噪声。因此,还可以再加入路径排序环节,可参考先粗排后精排的方式,同样走过滤逻辑。例如,在粗排阶段,根据问题 query 和候选路径 path 的特征,对候选路径进行粗排,采用 LightGBM 机器学习模型,保留 top n 条路径,在精排阶段,采用预训练语言模型,计算 query 和粗排阶段 的 path 的语义匹配度,选择得分 top2-top3 答案路径作为答案。
0
0
0
浏览量2017
算法无限

LLM常见问题(增量预训练部分)

1.  为什么要增量预训练?预训练学知识,指令微调学格式,强化学习对齐人类偏好,所以要想大模型有领域知识,得增量预训练(靠指令微调记知识不靠谱,不是几十w条数据能做到的)。2.  进行增量预训练需要做哪些准备工作?选取底座模型:可以根据自己的项目需求和硬件基础来选择合适的底座模型及模型参数量的大小。收集数据:一般来说需要收集大量的文本数据,包含各个领域,主要从互联网上获取,一般预训练数据的大小都是 TB 级别的。数据清洗:所有的信息都能够在互联网信息中被找到,只是信息密度相比「人工精选数据集」要更低。例如「明星信息」、「如何写代码」这些信息都能在新闻网站、或是问答网站中找到,只不过「维基百科」或是「Github」则是将这些信息给「高密度」且「结构化」地进行了存储。这使得我们在使用维基百科作为训练语料的时候,模型能够更快的学习到这些高密度信息(人物的经历、年龄、性别、职业等等),而这些内容在互联网信息(如新闻)中的信息密度则较低,即很少会有一条新闻完整的介绍一个艺人的过往经历。只要我们对互联网信息进行严格的处理(去除冗余信息,提高有用信息的密度),就能够加快模型的学习速度。3.  增量预训练所用训练框架?超大规模训练:选用 3D 并行,Megatron-Deepspeed拥有多个成功案例少量节点训练:选用张量并行,但张量并行只有在 nvlink 环境下才会起正向作用,但提升也不会太明显。少量卡训练:如果资源特别少,显存怎么也不够,可以使用 LoRA 进行增量预训练。4.  增量预训练数据选取思路有哪些?垂直领域预训练有三种思路:先用大规模通用语料预训练,再用小规模领域语料二次训练直接进行大规模领域语料预训练通用语料比例混合领域语料同时训练5.  增量预训练训练流程是怎么样?数据预处理:参考 LLaMA 的预训练长度,也把数据处理成2048长度(如果不够,做补全)。分词器:如果使用 LLaMA 可能需要添加中文词表,目前有不少人做了相关工作,当然也可以自己添加自己需要的词表。原始模型:各家框架的模型层名不太一样,训练时可能需要做一些调整,在预训练时尽量选择基座模型,不选 Chat 模型。训练模型:跑通只是第一步,根据训练情况反复调整比较重要。模型转换:不同框架的checkpoint格式不同,还会根据并行度分成很多个文件。模型测试:简单测试下续写能力,验证下模型是否正常。
0
0
0
浏览量183
算法无限

LLM常见问题(中文 Tokenization 部分)

1. 为什么需要构建中文 tokenization?2. 如何对原始数据预处理?with open("data.txt", "r", encoding="utf-8") as fp: data = fp.read().strip().split("\n") sentences = [] for d in data: d = d.strip() if len(d) == 0: continue sentences.append(d) with open("data/corpus.txt", "w", encoding="utf-8") as fp: fp.write("\n".join(sentences))3. 如何构建中文的词库?目前比较主流的是使用 sentencepiece 来训练中文词库。安装指令也很简单:pip install sentencepiece。然后利用预处理后的语料数据来构建词库。直接看代码:import sentencepiece as spm spm.SentencePieceTrainer.train( input='data/corpus.txt', model_prefix='tokenizer', vocab_size=50000, user_defined_symbols=['foo', 'bar'], character_coverage=1.0, model_type="bpe", )input:指定输入文本文件的路径或者是一个目录,可以指定多个输入文件或目录。其中每一行可以是一句话或者多句话。tokenizer:保存的模型的名称前缀。vocab_size:设置的词表大小。user_defined_symbols:用于指定用户自定义的符号。这些符号将会被视为单独的 Token,不会被拆分成子词。这个参数的作用是将一些用户定义的特殊符号作为一个整体加入到生成的词表中,以便于后续的模型使用。这里我们简单进行了测试。model_type: 指定模型的类型,有三种可选参数:unigram, bpe, char. word。character_coverage:指定覆盖字符的数量,可以理解为限制字符集的大小。默认值为 1.0,即覆盖全部字符。unk_id: 指定未登录词的 ID 号,即在词表中为未登录词分配一个整数 ID。默认值为 0。bos_id: 指定句子开头符号的 ID 号,即在词表中为句子开头符号分配一个整数 ID。默认值为 1。eos_id: 指定句子结束符号的 ID 号,即在词表中为句子结束符号分配一个整数 ID。默认值为 2。pad_id: 指定填充符号的 ID 号,即在词表中为填充符号分配一个整数 ID。默认值为 -1,即不使用填充符号。4. 如何使用 transformers 库加载 sentencepiece 模型?import os os.environ["PROTOCOL_BUFFERS_PYTHON_IMPLEMENTATION"] = "python" from transformers import LlamaTokenizer from sentencepiece import sentencepiece_model_pb2 as sp_pb2_model import sentencepiece as spm from tokenization import ChineseTokenizer chinese_sp_model_file = "sentencepisece_tokenizer/tokenizer.model" # load chinese_sp_model = spm.SentencePieceProcessor() chinese_sp_model.Load(chinese_sp_model_file) chinese_spm = sp_pb2_model.ModelProto() chinese_spm.ParseFromString(chinese_sp_model.serialized_model_proto()) ## Save output_dir = './transformers_tokenizer/chinese/' os.makedirs(output_dir, exist_ok=True) with open(output_dir + 'chinese.model', 'wb') as f: f.write(chinese_spm.SerializeToString()) tokenizer = ChineseTokenizer(vocab_file=output_dir + 'chinese.model') tokenizer.save_pretrained(output_dir) print(f"Chinese tokenizer has been saved to {output_dir}") # Test chinese_tokenizer = ChineseTokenizer.from_pretrained(output_dir) print(tokenizer.all_special_tokens) print(tokenizer.all_special_ids) print(tokenizer.special_tokens_map) text = '你好,世界!' print("Test text:\n", text) print(f"Tokenized by Chinese-LLaMA tokenizer:{chinese_tokenizer.tokenize(text)}")5. 如何合并英文词表和中文词表?import os os.environ["PROTOCOL_BUFFERS_PYTHON_IMPLEMENTATION"] = "python" from transformers import LlamaTokenizer from sentencepiece import sentencepiece_model_pb2 as sp_pb2_model import sentencepiece as spm llama_tokenizer_dir = "transformers_tokenizer/llama/tokenizer.model" chinese_sp_model_file = "sentencepisece_tokenizer/tokenizer.model" # load llama_tokenizer = LlamaTokenizer.from_pretrained(llama_tokenizer_dir) chinese_sp_model = spm.SentencePieceProcessor() chinese_sp_model.Load(chinese_sp_model_file) llama_spm = sp_pb2_model.ModelProto() llama_spm.ParseFromString(llama_tokenizer.sp_model.serialized_model_proto()) chinese_spm = sp_pb2_model.ModelProto() chinese_spm.ParseFromString(chinese_sp_model.serialized_model_proto()) # print number of tokens print(len(llama_tokenizer), len(chinese_sp_model)) print(llama_tokenizer.all_special_tokens) print(llama_tokenizer.all_special_ids) print(llama_tokenizer.special_tokens_map) ## Add Chinese tokens to LLaMA tokenizer llama_spm_tokens_set = set(p.piece for p in llama_spm.pieces) print(len(llama_spm_tokens_set)) print(f"Before:{len(llama_spm_tokens_set)}") for p in chinese_spm.pieces: piece = p.piece if piece not in llama_spm_tokens_set: new_p = sp_pb2_model.ModelProto().SentencePiece() new_p.piece = piece new_p.score = 0 llama_spm.pieces.append(new_p) print(f"New model pieces: {len(llama_spm.pieces)}") ## Save output_sp_dir = 'transformers_tokenizer/llama_chinese' output_hf_dir = 'transformers_tokenizer/llama_chinese' # the path to save Chinese-LLaMA tokenizer os.makedirs(output_sp_dir, exist_ok=True) with open(output_sp_dir + '/chinese_llama.model', 'wb') as f: f.write(llama_spm.SerializeToString()) tokenizer = LlamaTokenizer(vocab_file=output_sp_dir + '/chinese_llama.model') tokenizer.save_pretrained(output_hf_dir) print(f"Chinese-LLaMA tokenizer has been saved to {output_hf_dir}") # Test llama_tokenizer = LlamaTokenizer.from_pretrained(llama_tokenizer_dir) chinese_llama_tokenizer = LlamaTokenizer.from_pretrained(output_hf_dir) print(tokenizer.all_special_tokens) print(tokenizer.all_special_ids) print(tokenizer.special_tokens_map) text = '你好,世界!' print("Test text:\n", text) print(f"Tokenized by LLaMA tokenizer:{llama_tokenizer.tokenize(text)}") print(f"Tokenized by Chinese-LLaMA tokenizer:{chinese_llama_tokenizer.tokenize(text)}")6. 怎么使用修改后的词表?如果我们重新从头开始训练,那么其实使用起来很简单:config = AutoConfig.from_pretrained(...) tokenizer = LlamaTokenizer.from_pretrained(...) model = LlamaForCausalLM.from_pretrained(..., config=config) model_vocab_size = model.get_output_embeddings().weight.size(0) model.resize_token_embeddings(len(tokenizer))但是如果我们想要保留原始模型embedding的参数,那么我们可以这么做:找到新词表和旧词表id之间的映射关系。将模型里面新词表里面包含的旧词表用原始模型的embedding替换。如果新词在旧词表里面没有出现就进行相应的初始化再进行赋值。比如transformers库中的llama是这么进行初始化的。 def _init_weights(self, module): std = self.config.initializer_range if isinstance(module, nn.Linear): module.weight.data.normal_(mean=0.0, std=std) if module.bias is not None: module.bias.data.zero_() elif isinstance(module, nn.Embedding): module.weight.data.normal_(mean=0.0, std=std) if module.padding_idx is not None: module.weight.data[module.padding_idx].zero_()
0
0
0
浏览量1099
算法无限

LLM常见问题(显存部分)

1. 大模型大概有多大,模型文件有多大?大模型也分为不同的规格,一般模型的规格会体现在模型的名称上,例如 LLaMA2-13b,13b 就是其模型参数量的大小,意思是 130亿的参数量。大模型的文件大小与其参数量有关,通常大模型是以半精度存储的, Xb 的模型文件大概是 2X GB多一些,例如 13b 的模型文件大小大约是 27GB 左右。2. 能否用4 * v100 32G训练vicuna 65b?一般来说推理模型需要的显存约等于模型文件大小,全参训练需要的显存约为推理所需显存的三倍到四倍,正常来说,在不量化的情况下4张 v100 显卡推理 65b 的模型都会有一些吃力,无法进行训练,需要通过 LoRA 或者 QLoRA 采用低秩分解的方式才可以训练。3. 如何评估你的显卡利用率?flops比值法:gpu利用率 = 实测的flops/显卡理论上的峰值flops。deepspeed实测flops 100tflops,而用的是A100卡理论峰值312tflops,可以得到GPU利用率只有 32.05%。throughout估计法:吞吐量 = example数量/秒/GPU * max_length;gpu利用率 = 实际吞吐量 / 论文中的吞吐量(假设利用率100%),实测训练时处理样本速度为 3 example/s,一共有4卡,max length 2048,则吞吐量为 1536 token/s/gpu,根据llama论文可以得知,他们训练7B模型的吞吐量约为 3300 token/s/gpu,那么GPU利用率只有46.54%torch profiler分析法:利用torch profiler记录各个函数的时间,将结果在tensorboard上展示,在gpu kenel视图下,可以看到tensor core的利用率,比如30%。4. 如何查看多机训练时的网速?iftop -i eth2 -n  -Piftop 是外置的命令,可以监控发送流量,接收流量,总流量,运行 iftop 到目前时间的总流量,流量峰值,过去 2s 10s 40s 的平均流量。5. 如何查看服务器上的多卡之间的NVLINK topo?nvidia-smi topo -m 6. 如何查看服务器上显卡的具体型号?cd /usr/local/cuda/samples/1_Utilities/deviceQuery make ./deviceQuery7. 如何查看训练时的 flops?(也就是每秒的计算量)如果基于deepspeed训练,可以通过配置文件很方便地测试。{ "flops_profiler": { "enabled": true, "profile_step": 1, "module_depth": -1, "top_modules": 1, "detailed": true, "output_file": null } }8. 如何查看对 deepspeed 的环境配置是否正确?ds_report9. TF32 格式有多长?TF32(TensorFloat32)是 NVIDIA 在 Ampere 架构推出的时候面世的,现已成为 Tensorflow 和 Pytorch 框架中默认的32位格式。用于近似 FP32 精度下任务的专有格式,实际上约等于 FP19 也就是19位。
0
0
0
浏览量794
算法无限

LLM常见问题(Adapter-tuning 部分)

1. 为什么需要适配器微调(Adapter-tuning)?避免灾难性遗忘:在全参微调方法中,微调过程可能会导致预训练模型在原任务上的性能下降,即灾难性遗忘。适配器微调通过只微调适配器层,可以避免对预训练模型的其他部分进行大幅度的更新,尽可能保留大模型原本的知识从而减少灾难性遗忘的风险。减少微调的计算量和时间:全参微调方法需要更新整个模型的参数,需要大量的计算资源和时间。适配器微调可以显著减少微调的计算量和时间,因为它只需要微调适配器层的参数,而不需要重新训练整个模型。提高模型的可解释性和可复用性:适配器微调可以使模型更具可解释性和可复用性。通过在适配器层中添加任务特定的适配器,我们可以更好地理解模型在不同任务上的表现,并且可以将适配器用于其他类似的任务,从而提高模型的可复用性。2. 适配器微调(Adapter-tuning)思路?在预训练模型每一层(或某些层)中添加 Adapter 模块(如下图左侧结构所示),微调时冻结预训练模型主体,由Adapter模块学习特定下游任务的知识。每个 Adapter 模块由两个前馈子层组成,第一个前馈子层将 Transformer 块的输出作为输入,将原始输入维度 d 投影到 m,通过控制m的大小来限制 Adapter 模块的参数量,通常情况下 m << d。在输出阶段,通过第二个前馈子层还原输入维度,将m重新投影到 d,作为 Adapter 模块的输出(如下图右侧结构)。3. 适配器微调(Adapter-tuning)特点是什么?保留预训练模型的知识:适配器微调只微调适配器层的参数,而不改变预训练模型的其他参数。这样可以保留预训练模型在大规模数据上学到的知识和特征表示能力。减少微调的计算量和时间:相比于传统的微调方法,适配器微调只需要微调适配器层的参数,而不需要重新训练整个模型。这样可以大大减少微调的计算量和时间消耗。提高模型的可解释性和可复用性:适配器微调在预训练模型中添加了适配器层,这些适配器层可以理解为任务特定的模块。通过适配器层,模型的性能在不同任务之间可以更好地解释和比较,并且适配器层可以用于其他类似的任务,提高模型的可复用性。灵活性和可扩展性:适配器微调可以在不同的预训练模型和任务中应用。适配器层的设计可以根据任务的特点进行调整,以适应不同的任务需求。这种灵活性和可扩展性使得适配器微调成为一种通用且高效的微调方法。4. AdapterFusion 思路是什么?AdapterFusion 多任务学习框架分成两个阶段。首先针对于每个任务,学习一组新的 adapter 参数。然后,针对于某个特定目标任务,学习一个融合模块把第一步的所有 adapter 结合起来。假设第二阶段的每个任务都被包括在第一阶段里了,不考虑第二阶段引入新的任务的情况。5. AdapterDrop 思路是什么?为了加快推理速度,在推理时可以对某几层的 adapter 进行剪枝。根据的结论,靠近输入的 adapter 被剪掉后对性能影响更小。因此,AdapterDrop 的作者提出,推理时可以剪掉最下方 n 层的 adapter,也就是最靠近输入的前 n 层。为了尽可能地减小掉点,作者设计了两种训练方案:specialized AdapterDrop: 训练时固定 n,训练后的模型推理时也固定剪掉前n层。robust AdapterDrop: 训练时每个 batch 都随机选取 n 的大小,训练后的模型可以适应多个 n。6. AdapterDrop 特点是什么?动态适配器选择:AdapterDrop 引入了适配器层的随机丢弃机制,使得模型可以在训练过程中动态选择使用哪些任务的适配器层进行微调。这种动态适配器选择机制可以增加模型的鲁棒性和泛化能力,使得模型能够适应不同任务的变化和不确定性。鲁棒性和泛化能力:通过随机丢弃适配器层,AdapterDrop 可以让模型在训练过程中随机选择使用哪些任务的适配器层进行微调。这种随机性可以增加模型对于噪声和干扰的鲁棒性,并提高模型的泛化能力。减少计算量和参数数量:通过随机丢弃适配器层,AdapterDrop 可以减少模型的计算量和参数数量。在训练过程中,只有部分任务的适配器层被使用,其他任务的适配器层被丢弃,从而减少了模型的计算量和参数数量,提高了模型的效率和可扩展性。7. MAM Adapter 思路是什么?MAM 旨在 Adapter、Prefix Tuning 和 LoRA 之间建立联系的统一方法。MAM Adapter 的思路是通过引入掩码机制和掩码预测任务,增强适配器层的表示能力,并通过联合训练优化任务预测和掩码预测的准确性。 这种方法可以提高适配器微调的性能,进一步增强多任务学习的效果。8. MAM Adapter 特点是什么?掩码机制增强表示能力:MAM Adapter 引入了掩码机制,通过随机掩码部分适配器层的神经元,从而增强适配器层的表示能力。这种掩码机制可以使得适配器层的表示更加丰富和多样化,有助于提高多任务学习的性能。联合训练优化任务和掩码预测:MAM Adapter 在训练过程中不仅优化任务的预测准确性,还同时优化掩码预测任务的准确性。通过最小化任务预测的损失和掩码预测的损失,来更新模型的参数。这样可以使得模型能够同时学习任务的表示和掩码的生成,进一步提高多任务学习的性能。灵活的任务选择和预测:在推断和预测阶段,可以根据具体的需求选择使用所有任务的适配器层进行预测,或者选择使用部分任务的适配器层进行预测。这种灵活的任务选择和预测机制可以根据具体应用场景的需求进行灵活调整,提高模型的适应性和可用性。提高多任务学习性能:MAM Adapter 通过增强适配器层的表示能力和联合训练优化任务和掩码预测,可以提高多任务学习的性能。适配器层的表示能力增强了模型对任务的适应能力,而掩码预测任务的优化可以使得模型学习到更加鲁棒的表示。
0
0
0
浏览量1797
算法无限

LLM常见问题(基于 AI 的 pdf 解析)

1. pdf 解析存在的什么问题?pdf 解析的条件非常苛刻,如果使用传统的方法会遇到很多问题,难以对其进行解析并且解析出来的结果可能会杂乱无章,以下是常见的一些问题:字体无法兼容一个页面存在表格双栏排版,同时可能存在通栏文字存在折叠问题2. 为什么要提取多级标题?如何提取?标题是快速做摘要最核心的文本。同时对于有些高水平的问题,没有标题很难得到用户满意的结果。因此,提取标题和多级标题是文档摘要重要的一部分内容。假如用户想知道一个文档某一章节是从哪些方面讨论的(假如是4个方面),如果没有将标题信息告诉LLM,而是把所有信息全部扔给LLM,那它大概率不会知道是4个方面,LLM提取信息后可能会更多也可能会更少,这并不能很好地完成用户的需求。以下是实现多级标题提取的步骤:利用相关工具将 pdf 转换为图片利用目标检测模型提取出标题和标题下面的内容来用 OCR 来对提取文字块中的信息整理处理好的数据并保存3. 如何区分单栏还是双栏pdf?如何重新排序?很多目标检测模型识别区块之后并不是顺序返回的,因此我们需要根据目标检测模型返回的坐标对文字块重新排序。单栏 pdf 可以直接按照中心点纵坐标排序即可,但是双栏 pdf 需要额外的处理。首先如何区分单栏 pdf 和双栏 pdf?通过目标检测模型返回值我们可以得到所有区块的中心点横坐标,然后用这一组横坐标的极差来判断即可,双栏论文的极差远远大于单栏论文,因此可以设定一个极差阈值来进行分类。当然,这只是一种方法,也可以利用其他的方法来对单栏 pdf 和双栏 pdf 进行分类。其次是双栏论文如何确定左右文字块的先后顺序。先获取中线的横坐标,若文字块的坐标都小于中线的横坐标则判断为位于左栏,若文字块的坐标都大于中线的横坐标则判断为位于右栏。对两栏分别排序,然后将右栏拼接在左栏后面。也有一种特殊情况就是通栏,若文字块左边的坐标小于中线横坐标且右边的横坐标大于中线横坐标为通栏,这时需要将通栏上方的右栏拼接在通栏上方的左栏之后,并把通栏下方的右栏拼在通栏下方的左栏之后,最后将通栏中的内容放在它们之间。4. 如何提取表格和图片中的数据?提取表格和图片中的数据需要利用目标检测和 OCR ,首先使用目标检测分别提取出标题块和数据块,然后对其排序,再利用 OCR 抽取排好序的文字块中的信息重组为表格。提取出表格之后输入给 LLM ,LLM 能够理解其含义,可以设计特定的 prompt 做引导以获得更好的反馈。5. 基于AI的文档解析有什么优缺点?优点:准确率高,通用性强,保留了更多有用信息,并能够得到更为简洁的数据形式。缺点:耗时较长,主要在目标检测和 OCR 两个模型处理的部分,同时上手门槛较高。6. 如何处理跨页(跨栏)内容?如果有一段文本被分在了两页,则可以用 NSP 判断两个句子(段落)是否具有语义衔接关系,若有语义衔接关系就对其进行拼接。如果是表格则需要对其手动合并,两个独立表格分别占据两页首尾少有出现,大部分作者都会避免这个问题出现。
0
0
0
浏览量1503
算法无限

LLM常见问题(微调部分)

1. 如果想要在某个模型基础上做全参数微调,究竟需要多少显存?这个问题首先需要了解 BF16 和 FP16, BF16 是对 FP32 单精度浮点数截断数据,即用 8bit 表示指数,7bit 表示小数。FP16 半精度浮点数,用 5bit 表示指数,10bit 表示小数。与单精度相比,采用 BF16/FP16 吞吐量可以翻倍,内存需求可以减半。这两者精度上差异不一样,BF16 可表示的整数范围更广泛,但是尾数精度较小;FP16 表示整数范围较小,但尾数精度较高。通常微调用 bf16,推理用 fp16,当在模型大小为 XB 且参数精度为半精度,推理所需显存约为 X 的两倍,而全参数微调所需显存约为推理所需显存的四倍,包括模型推理(一倍)、梯度(一倍)、优化器(两倍),也就是 X 的八倍。2. 为什么SFT之后感觉LLM傻了?数据分布偏差:在 SFT 过程中,采用的微调数据集可能与模型在预训练期间所接触的数据存在较大差异,可能会削弱模型的表现,这种分布的不一致可能会导致模型在应对新任务时做出错误或不精确的预测。标注异常:在微调数据集中可能存在标注错误或标签不精确的情况(即使质量较低的数据集很少也会对模型产生负面影响)。这类不准确的标签可能会对模型的性能造成不利影响,使得模型表现出不合逻辑或错误的行为。过度拟合现象:如果用于微调的数据集规模较小,或者模型的参数量过于庞大,可能会导致模型对微调数据集过拟合,从而使得模型在新样本上的表现力下降。这种过度拟合的问题可能使得模型对微调数据集中的特定实例过分敏感,而不能有效地推广到新的、更广泛的数据上。数据集多样性不足:用于微调的数据集可能没有足够的多样性,未能覆盖模型在执行新任务时可能遇到的各种输入类型。这种多样性的缺失可能会导致模型在遇到与微调数据集显著不同的新输入时,表现出困惑或做出错误的决策。3. SFT 指令微调数据如何构建?原始数据采集:需要收集与目标任务相关的原始数据。可以涵盖了从对话实例、分类项到生成任务的数据样本,具体依据目标任务而定。要注意保证数据集既具备足够的代表性也要有广泛的多样性,这对于增强模型的泛用性至关重要。通常我们需要训练模型的对话能力,因此我们就要考虑多收集单轮对话和多轮对话的数据集。数据标注:对采集到的原始数据执行打标操作,给予每一个数据样本恰当的标签或是目标输出。打标的形式将基于您的具体任务而定,可能包括分类标签、文本生成内容或是对话反应等。保障打标工作的精确性与一致性。数据集分割:对已打标的数据执行分割,以便区分出训练集、验证集和测试集。通常将大量数据指定为训练用途,取一小部分进行模型性能验证,以最终评估模型泛化性能。数据预处理:针对任务需求对数据进行必要的预处理步骤。这可能包括文本清理、分词处理、去除无用词汇、词根提取等操作。确保最终的数据格式满足模型输入的规范,同时将数据转换成适合模型训练的格式。4. 领域模型 Continue PreTrain 数据选取?收集垂直领域数据:首先搜集与目标专业领域密切相关的数据,可以包括网络爬虫获取数据、特定行业文档或企业内部的信息资源等。这类数据能够丰富模型的专业知识库和语言理解能力,进而提升模型在该领域的性能表现。领域专家参与打标:可以让领域专家对相关数据进行精确筛选和打标。这一步骤涉及各类任务,如类别归纳、实体识别、关系提取等,旨在为模型训练提供精确无误的监督数据。使用伪标签:在无法获得领域专家支持或数据打标成本较高时,可以利用自动化技术生成伪标签。例如,运用已预训练的模型对特定领域数据作出预测,并将这些预测结果作为伪标签用于进一步训练。通常会通过 ChatGPT 生成相关数据进行知识蒸馏。保证数据均衡:在选择数据时,要关注数据的平衡性,不仅要灌输垂直领域的数据,还应该添加一定比例的通用领域数据。对于样本量不足的类别,可考虑应用数据增强或过采样技术,以实现数据量的平衡。5. 领域数据训练后,通用能力往往会有所下降,如何缓解模型遗忘通用能力?如果仅仅使用领域数据集进行模型训练,模型很容易出现灾难性遗忘现象,为了解决这个问题通常在领域训练的过程中加入通用数据集。但对于领域数据集和通用数据集应该按照什么比例目前还没有一个准确的答案。如果领域数据集数据量没有那么多时,一般领域数据与通用数据的比例在1:5到1:10之间。6. 领域模型Continue PreTrain ,如何让模型在预训练过程中学习到更多的知识?增大数据收集范围:收集垂直领域数据可以来自互联网、论文、书籍等等多个方面的数据。将这些数据混合在一起进行预训练,可以使得模型在预训练阶段习得足够的专业知识。大规模数据:使用更大规模的数据进行预训练,可以让模型接触到更多的语言和知识。可以从互联网上爬取大量的文本数据,或者利用公开的语料库进行预训练。7. 进行SFT操作的时候,基座模型选用Chat还是Base?在进行 SFT 时,大模型选用 Chat 还是 Base 作为基座,需要根据SFT的数据量决定。如果数据量小于 10k,建议选用 Chat 模型作为基座进行微调;如果有 100k 的数据,建议在 Base 模型上进行微调。8. 领域模型微调指令&数据输入格式要求?指令数据一般为 json 格式,包含 Instruction、Input、Output 三个字段(可以为空),每行一条样本。Instruction(指令):Instruction 是对模型的输入文本或问题进行进一步说明或约束的指令。它可以是一种特定的格式或标记,用于告诉模型如何处理输入数据,或者提供一些额外的信息,以便模型更好地理解和生成输出。Instruction的作用是为了描述任务。Input(输入):Input 是模型实际接收的文本或数据。提供了指令待处理的对象,用于生成输出。Response(输出):Response 是 LLM 生成的输出文本或回答。它是模型对 Instruction 和 Input 的处理结果。 Response 的内容取应当是理想的文本、回答、建议、解释等形式。9. 领域模型微调领域评测集构建?领域模型数据集通常通过构建专业知识选择题数据集来进行评测,例如医学领域大语言模型测评数据集 CMB 包含了医师考试、护理考试、医学考研等六类考试的题目。方便对模型的专业领域知识能力进行快速评测。但由于领域模型很有可能语义理解能力较差导致难以理解题目而无法正确作答。另一种方式是构建主观题数据集,模型完成作答后由专业人员根据一定标准进行打分。10. 领域模型词表扩增是不是有必要的?不一定要扩充词表,但有些领域比较特殊,需要扩充词表。领域特定词汇:对于目标领域内存在的专有术语或特殊词汇,若这些并不存在标准预训练模型的数据中,扩展词表就是有必要的。将这类特殊术语纳入模型的词库,能够增强模型对这些术语的理解与处理能力。领域特定上下文:在特定的领域任务中,一个词的含义可能会由其所处的上下文决定。以医疗领域为例,一个术语在不同情境下可能代表着不同的事物。当领域特定任务中的上下文与预训练模型原训练数据中的上下文存在差异时,词表的扩充就能帮助模型更精确地理解和应对特定领域的上下文。11. 如何训练自己的大模型?数据收集和预处理:首先收集与目标任务和领域相关的大规模数据集。这可以包括从互联网上爬取数据、使用公开数据集或者其他途径获得的数据。然后,对数据进行预处理和清洗,包括数据去重、有害数据集去除等。同时将数据集划分为训练集、验证集和测试集。训练集用于模型的训练,验证集用于调整超参数和模型选择,测试集用于最终评估模型的性能。模型架构选择:根据任务的特点和目标,选择适合的开源模型架构。目前国内外有较多的开源 LLM 模型可供开发者使用,包括国外的 LLaMa、BLOOM,国内的 ChatGLM、Baichuan,或者其他机构基于开源框架训练得到的模型。可以根据自己的数据量、计算资源、任务需求来选择相应的模型进行微调。模型训练和调参:训练包含二次预训练、SFT、RLHF 等方式,同时可以选择是全参数训练或者部分参数训练。确定好训练方式后使用训练集对模型进行训练。训练过程中,需要选择合适的优化器、损失函数和学习率等超参数,并进行适当的调整和优化。12. 训练中文大模型有啥经验?如果模型在预训练阶段就包含足够的中文如 ChatGLM、Baichuan,可以直接利用其进行微调,如果模型在预训练阶段无中文或者中文数据含量较少的模型如 LLaMA,需要对其 tokenizer 进行修改,添加中文字词,同时进行二次预训练使其充分理解中文再进行 SFT 微调。13. 指令微调的好处?行为引导:大模型的行为往往不易解释和控制。借助指令微调,我们可以输入特别的指令或设定限制,这样不仅可以引导模型行为,还使其更好地满足特定任务的要求,如在创造性任务中,通过指令导向控制内容的风格和长度等。数据效率:大模型训练通常依赖于大量数据,但在一些特定的任务或领域里,关键数据往往不够充足或难以采集。指令微调可以使我们仅利用少量特定任务的数据,结合大型模型的通用数据预训练知识,达到在数据受限的条件下也能够取得优异的性能。14. 预训练和微调哪个阶段注入知识的?知识是在预训练阶段注入的,而微调阶段是指在特定任务上的训练,以使预训练模型的通用知识和特定任务的要求相结合,使模型在特定任务上表现更好。15. 想让模型学习某个领域或行业的知识,是应该预训练还是应该微调?如果想让大语言模型学习某个特定领域或行业的知识,通常建议进行微调而不是预训练。预训练阶段是在大规模通用数据上进行的,旨在为模型提供通用的语言理解和表示能力。预训练模型通常具有较强的泛化能力,可以适用于多个不同的任务和领域。在微调阶段,可以使用特定领域的数据对预训练模型进行进一步训练和调整,以使模型更好地适应目标领域的特点和需求。微调可以帮助模型更深入地理解特定领域的术语、概念和语境,并提升在该领域任务上的性能。微调通常需要较少的任务数据,因为预训练模型已经具备了一定的语言理解和泛化能力。但是,如果模型基座本身并没有具备太多相关领域知识则需要对模型做预训练后进行微调。16. 多轮对话任务如何微调模型?方法一User1、Assistant1、User2、Assistant2、User3 的文本都视为模型的输入部分,将 Assistant3 的文本视为模型的预测部分,只有 Assistant3 部分的 loss 参与权重更新。这种方法的弊端在于,没有充分利用多轮对话的训练数据,Assistant1 和 Assistant2 的内容没有参与模型训练,这部分数据在训练时被浪费了。并且对于很多多轮对话数据而言,中间的 Assitant 回复部分的信息量更丰富详细,最后一个 Assitant 回复部分往往是”谢谢“、”不客气“等诸如此类的较为简短的文本。如果只使用这部分文本训练模型,会严重影响模型的训练效果。方法二将一条多轮对话数据,拆分成多条数据。例如将以上示例拆分成如下三条数据。相比方法一,方法二能够更加充分利用多轮对话中每一个 Assistant 的回复内容。但是弊端在于,需要将一个包含n轮对话的数据,拆分成n条数据,训练效率降低了n倍,训练方法不高效。方法三Firefly 项目训练多轮对话模型时,采取了一种更加充分高效的方法。如下图所示,将一条多轮对话数据拼接之后,输入模型,并行计算每个位置的 loss,只有 Assistant 部分的 loss 参与权重更新,项目地址。为什么这种做法是可行的?答案在于因果语言模型的 attention mask。以 GPT 为代表的 Causal Language Model(因果语言模型),这种模型的 attention mask 是一个对角掩码矩阵,每个 token 在编码的时候,只能看到它之前的 token,看不到它之后的 token。所以 User1 部分的编码输出,只能感知到 User1 的内容,无法感知到它之后的文本,可以用来预测 Assistant1 的内容。而 User2 部分的编码输出,只能看到 User1、Assistant1、User2 的内容,可以用来预测 Assistant2 的内容,依此类推。对于整个序列,只需要输入模型一次,便可并行获得每个位置的 logits,从而用来计算 loss。17. 微调后的模型出现能力劣化,灾难性遗忘是怎么回事?灾难性遗忘是指在模型微调过程中,当模型在新任务上进行训练时,可能会忘记之前学习到的知识,导致在旧任务上的性能下降。这种现象常见于神经网络模型的迁移学习或连续学习场景中,原因如下:数据分布差异:微调过程中使用的新任务数据与预训练数据或旧任务数据的分布存在差异。如果新任务的数据分布与预训练数据差异较大,模型可能会过度调整以适应新任务,导致旧任务上的性能下降。参数更新冲突:微调过程中,对新任务进行训练时,模型参数会被更新,导致之前学习到的知识被覆盖或丢失。新任务的梯度更新可能会与旧任务的梯度更新发生冲突,导致旧任务的知识被遗忘。设置了过多的 epoch:随着 epoch 的上升,对旧任务的遗忘也会越多,通常 epoch 只用设置为 1(具体需要看数据量大小和任务需求)。18. 微调模型需要多大显存?全参数微调:当在模型大小为 XB 且参数精度为半精度,推理所需显存约为 X 的两倍,而全参数微调所需显存约为推理所需显存的四倍,包括模型推理(一倍)、梯度(一倍)、优化器(两倍),也就是 X 的八倍。LoRA 微调:相比于全参数微调,LoRA 占用显存降低约 67.5%。QLoRA 微调:可在 48GB 显存的显卡上微调 65B 的模型。19. LLM 进行 SFT 操作的时候在学习什么?任务特定的标签预测:在有监督微调中,模型会根据给定的任务,学习预测相应的标签或目标。通常会将基座模型训练成对话模型。垂直领域知识学习:使模型更好地适应目标领域的特点和需求。微调可以帮助模型更深入地理解特定领域的术语、概念和语境,并提升在该领域任务上的性能。特征提取和表示学习:微调过程中,模型会通过学习任务相关的表示来提取有用的特征。这些特征可以帮助模型更好地区分不同的类别或进行其他任务相关的操作。20. 预训练和 SFT 操作有什么不同目标:预训练的目标是通过无监督学习从大规模的文本语料库中学习语言模型的表示能力和语言知识。预训练的目标通常是将大量的知识灌入模型之中,同时让模型学会句子接龙。有监督微调的目标是在特定的任务上进行训练,在有监督微调中,模型会利用预训练阶段学到的语言表示和知识,通过有监督的方式调整模型参数,以适应特定任务的要求(如将 Base 模型训练为 Chat 模型)。数据:在预训练阶段,大语言模型通常使用大规模的无标签文本数据进行训练,例如维基百科、网页文本等。这些数据没有特定的标签或任务信息,模型通过自我预测任务来学习语言模型。在有监督微调中,模型需要使用带有标签的任务相关数据进行训练。这些数据通常是人工标注的,包含了输入文本和对应的标签或目标。模型通过这些标签来进行有监督学习,调整参数以适应特定任务。21. 样本量规模增大,训练出现 OOM 错误如何解决?减少批量大小(Batch Size):将批量大小减小可以减少每个训练步骤中所需的内存量。但较小的批量大小可能会导致训练过程中的梯度估计不稳定,但可以通过增加梯度积累步数来弥补这一问题。内存优化技术:使用一些内存优化技术可以减少模型训练过程中的内存占用。例如,使用混合精度训练(Mixed Precision Training)可以减少模型参数的内存占用,或采用 Freeze、LoRA 的方式进行训练。减少模型规模:如果内存问题仍然存在,可以考虑选择规模更小的模型。虽然这可能会导致模型性能的一定损失,但可以在一定程度上减少内存需求。增加硬件资源:可以考虑增加硬件资源,例如更换显存更大的显卡,或者增加显卡的数量以实现并行训练,减小单卡压力。数据处理和加载优化:优化数据处理和加载过程可以减少训练过程中的内存占用。例如,可以使用数据流水线技术来并行加载和处理数据,减少内存中同时存在的数据量。
0
0
0
浏览量2023
算法无限

LLM常见问题(PEFT 部分)

1. PEFT 是啥?如何实现?PEFT 是一种迁移学习的技术,指在完成预训练的模型基础上,通过进一步微调来适应特定的下游任务。微调可以在具有相似特征的任务之间有较好的泛化能力,同时由于预训练模型有较高的完成度,因此能够加快训练速度并提高模型性能。PEFT 通常有以下方法:Adapter Tuning:该方法设计了一个 Adapter 结构,将其嵌入 Transformer 的结构里面,在训练时,固定住原来预训练模型的参数不变,只对新增的 Adapter 结构进行微调。Prefix Tuning:该方法是在输入 token 之前构造一段任务相关的 virtual tokens 作为 Prefix,然后训练的时候只更新 Prefix 部分的参数,而 Transformer 中的其他部分参数固定。Prompt Tuning:该方法可以看作是 Prefix Tuning 的简化版本,只在输入层加入 prompt tokens,并不需要加入 MLP 进行调整来解决难训练的问题。P-Tuning(v1):P-Tuning 提出将 Prompt 转换为可以学习的 Embedding 层,作者提出用MLP+LSTM的方式来对 prompt embedding 进行一层处理。Prefix Tuning 是将额外的 embedding 加在开头,看起来更像是模仿 Instruction 指令;而 P-Tuning 的位置则不固定。Prefix Tuning 通过在每个 Attention 层都加入 Prefix Embedding 来增加额外的参数,通过 MLP 来初始化;而 P-Tuning 只是在输入的时候加入 Embedding,并通过      LSTM+MLP 来初始化。P-Tuning(v2):P-Tuning v2 的目标就是要让 Prompt Tuning 能够在不同参数规模的预训练模型、针对不同下游任务的结果上都达到匹敌 Fine-tuning 的结果。带来更多可学习的参数(从 P-tuning 和 Prompt Tuning 的0.1%增加到0.1%-3%),同时也足够 parameter-efficient。同时加入到更深层结构中的 Prompt 能给模型预测带来更直接的影响。LoRA:大语言模型虽然参数众多,但是起到关键作用的还是其中低秩的本质维度,基于此该方法为 LLM 设计了一个旁路分支,在涉及到矩阵相乘的模块,引入A、B这样两个低秩矩阵模块去模拟Full-finetune的过程,相当于只对语言模型中起关键作用的低秩本质维度进行更新。2. 为什么需要 PEFT?大型预训练模型的训练成本非常高昂,需要庞大的计算资源和大量的数据,一般人难以承受。这也导致了一些研究人员难以重复和验证先前的研究成果。为了解决这个问题,研究人员开始研究 PEFT 技术。PEFT 技术旨在通过最小化微调参数的数量和计算复杂度,来提高预训练模型在新任务上的性能,从而缓解大型预训练模型的训练成本。因此,即使计算资源受限,也可以利用预训练模型的知识来迅速适应新任务,实现高效的迁移学习。3. 介绍一下 PEFT?PEFT(Performance Estimation and Modeling for Fine-Tuning)是一种用于微调任务的性能估计和建模方法。它的目的是帮助研究人员和从业者在微调过程中更好地理解和预测模型的性能,并进行更有效的模型选择和调优。PEFT的主要思想是通过预测模型在微调任务上的性能,提供对不同模型和参数设置的性能估计。这样可以避免在大规模数据集上进行昂贵的微调实验,从而节省时间和计算资源。4. PEFT 有什么优点?极大地节省时间和计算资源,全参数微调方法需要在大规模数据集上进行昂贵的实验,耗费大量时间和计算资源。而PEFT通过调整较少的参数量,可以避免资源的浪费,节省时间和计算开销。5. 批处理大小、模型大小、显存大小与训练速度的关系?批处理大小(Batch Size):批处理大小是指在每次迭代中同时处理的样本数量。较大的批处理大小可以提高GPU的利用率,加快训练速度,但可能会导致显存不足的问题。如果批处理大小过大,无法适应GPU显存的限制,可能需要减小批处理大小或使用分布式训练等方法来解决显存不足的问题。模型大小(Model Size):模型大小指的是微调任务中使用的模型的参数量和内存占用。较大的模型通常需要更多的显存来存储参数和激活值,可能会导致显存不足的问题。在GPU显存有限的情况下,可以考虑使用轻量级模型或模型压缩等方法来减小模型大小,以适应显存限制。GPU显存:GPU显存是指GPU设备上可用的内存大小。如果微调任务所需的显存超过了GPU显存的限制,会导致显存不足的问题。在这种情况下,可以采取一些策略来解决显存不足,例如减小批处理大小、减小模型大小、使用分布式训练、使用混合精度训练等。6. PEFT 和全量微调有什么区别?PEFT 可以使 PLM 高效适应各种下游应用任务,而无需微调预训练模型的所有参数。 微调大规模 PLM 所需的资源成本通常高得令人望而却步。 在这方面,PEFT 方法仅微调少量或额外的模型参数,固定大部分预训练参数,大大降低了计算和存储成本,同时最先进的 PEFT 技术也能实现了与全量微调相当的性能。7. 当前 PEFT 存在的一些问题性能保持:一些 PEFT 技术可能在提高效率的同时,对模型性能产生一定的影响。因此,在使用高效微调技术时需要权衡效率和性能之间的关系,并进行适当的调整和优化。数据依赖性:一些 PEFT 技术可能对数据的分布和规模具有一定的依赖性。例如,迁移学习通常需要目标任务和预训练任务具有相似的数据分布。这可能限制了高效微调技术在一些特殊或小规模数据集上的应用。可解释性:一些 PEFT 技术可能会引入黑盒操作,使得模型的解释性变得模糊。这可能会影响模型的可解释性和可信度。
0
0
0
浏览量1659
算法无限

LLM常见问题(LoRA 部分)

1. 什么是 LoRA?LoRA(low-rank adaptation of large language models)是一种针对大型语言模型进行低秩适应的技术。大型语言模型通常具有数十亿个参数,这使得它们在计算和存储方面非常昂贵。低秩适应的目标是通过将语言模型的参数矩阵分解为低秩近似,来减少模型的复杂度和计算资源的需求。低秩适应的方法可以通过使用矩阵分解技术,如奇异值分解(Singular Value Decomposition,SVD)或特征值分解(Eigenvalue Decomposition)。通过这种方式,可以减少模型的参数量和计算复杂度,同时保留模型的关键特征和性能。2. LoRA 的思路是什么?LoRA 的核心思想是对大型模型的权重矩阵进行隐式的低秩转换,整体的想法和概念与主成分分析(PCA)和奇异值分解(SVD)有关,换句话说,该方法试图找到原始特征空间(或矩阵)中少数维度的(线性)组合,能够捕捉数据集中大部分的信息。我们通过一个较低维度的表示来近似表示一个高维矩阵或数据集,通过对模型参数矩阵进行低秩分解,将其分解为两个或多个较小的矩阵的乘积作为旁路分支。然后仅调整低秩的近似矩阵,而冻结原模型的参数。这样可以减少模型的参数量和计算复杂度,同时保留模型的关键特征和性能。3. LoRA 的特点是什么?低秩适应:LoRA通过对大型语言模型的参数矩阵进行低秩分解,将其分解为较小的矩阵的乘积。保持关键特征:虽然低秩适应会对模型的输出产生影响,但它会尽量保留模型的关键特征。通过选择合适的低秩近似矩阵,可以最大限度地保持模型的性能。减少存储需求:低秩适应可以显著减少大型语言模型的参数量,从而降低模型的存储需求。这对于在资源受限的设备上使用语言模型非常有益。加速推理过程:由于低秩适应减少了模型的计算复杂度,因此可以加速模型的推理过程。这对于实时应用和大规模部署非常重要。可扩展性:LoRA 的低秩适应方法可应用于各种大型语言模型,包括预训练的 Transformer 模型等。它是一种通用的技术,可以适应不同的模型架构和任务。4. QLoRA 的思路是怎么样的?QLoRA 的目的是要解决当前微调特别大的模型成本太高的问题,如常规 fp16 的 LLaMA-65B 模型的参数微调需要超过780G的显存,主要包括以下三个部分:NF4 Quantization(4bit量化): 一种新的int4量化方法,灵感来自信息论。NF4量化可以保证量化后的数据和量化前具有同等的数据分布。意思就是NF4量化后,权重信息损失少,那么最后模型的整体精度就损失少。Double Quantization : 对第一次量化后的 常量做二次量化,减小模型存储。Paged optimizers : 使用 NVIDIA 统一内存功能,该功能在 CPU 和 GPU 之间进行自动 page 对 page 传输,以便在 OOM 的情况下进行. 可以从现象上理解成出现训练过程中偶发 OOM 时能够自动处理,保证训练正常训练下去。5. QLoRA 的特点是什么?量化参数:使用了4位 Normal Float(NF4) 数据类型,减小量化后参数的精度损失。双重量化:对量化常量进行量化,进一步节省内存。在精准的4位量化中,需要较小的块大小,这也会带来较大的内存开销。Paged optimizers:可以在 GPU RAM 偶尔不足的情况下实现 CPU 和 GPU 之间的数据传输,确保训练不会中断。6. AdaLoRA 的思路是怎么样的?AdaLORA 采用奇异值分解(SVD)的形式参数化增量更新。这种参数化方式在规避大量 SVD 运算的同时,允许我们高效裁剪不重要更新中的奇异值,降低增量过程中的资源消耗。AdaLORA 主要包含两个模块:SVD形式参数更新(SVD-based adaptation) :直接将增量矩阵Δ参数化为 SVD 的形式,避免了在训练过程中进行 SVD 计算带来的资源消耗基于重要程度的参数分配(Importance-aware rank allocation) : 裁剪一些冗余的奇异值。7. LoRA权重是否可以合入原模型?LoRA 权重可以合并到原模型中。在使用 LoRA 进行低秩适应时,原始模型的参数矩阵会被分解为较小的矩阵的乘积。这些较小的矩阵可以表示为低秩矩阵的形式,其中包含了原始模型的权重信息。合并 LoRA 权重到原模型的过程通常涉及将低秩矩阵重新组合成原始模型的参数矩阵。这可以通过矩阵乘法等操作来实现。合并后的模型将包含原始模型的权重信息,同时也融入了低秩适应的优化,从而在减少计算和存储开销的同时保持模型性能。9. LoRA微调方法为什么能加速训练?冻结了大量的参数:在训练时只更新低秩矩阵的参数,预训练好的模型参数是固定不变的。在推断时可以利用重参数(reparametrization)思想,将AB与W合并,这样就不会在推断时引入额外的计算了。降低了计算复杂度:由于 LoRA 微调减少了参数量,每个参数的计算量也相应减少。在训练过程中,计算参数更新和梯度传播的时间会显著减少,从而加速了训练过程。特别是在大规模语言模型中,参数量巨大,计算复杂度很高,LoRA微调可以显著减少计算开销,提高训练效率。加速收敛速度:LoRA 微调通过低秩适应对原模型进行了正则化,使得模型更容易收敛到较好的解。低秩适应过程中的正则化可以帮助模型更好地利用数据进行训练,减少过拟合的风险。这样可以加快模型的收敛速度,从而加速训练过程。10. 如何在已有 LoRA 模型上继续训练?可先将模型与 LoRA 训练出来的权重进行合并,使其重新组合成原始模型的参数矩阵,然后就可以再次进行 LoRA 微调了。11. LoRA 这种微调方法和全参数比起来有什么劣势?使用 LoRA 对模型进行微调可能会牺牲一部分模型质量,但将获得更高效地提供多个模型服务的能力。同时,LoRA 在特定的应用领域中表现比较出色,但可能在需要逻辑推理等更广泛的任务中表现欠佳。
0
0
0
浏览量697
算法无限

LLM常见问题(基于LLM+知识库的文档对话)

1. LLMs 存在模型幻觉问题,请问如何处理?大语言模型的模型幻觉问题是指其可能生成看似合理但实际上不准确或不符合事实的内容。处理大语言模型的模型幻觉问题需要采取一些方法和策略,通过挂载文档可以有效地缓解幻觉问题。挂载文档其实相当于引入外部知识,为了扩展语言模型以减少歧义,从大型文本数据库中检索相关文档。通常将输入序列分割成块并检索与用户输入的 query 相似的文档,然后将所选文档放在输入文本之前作为前置知识以改进模型的预测。使得模型可以更容易、更准确地访问专业知识。2. 基于 LLM + 向量库的文档对话思路是怎么样?Langchain在实现与文档数据对话的功能时需要经历下面的4个阶段:文档:首先,需要收集相关的文档数据,并对其进行整理。文档向量化:使用向量库的方法,将每个文档转换为数值向量,以便计算文档之间的相似度或进行聚类分析。文档检索:当用户提供一个查询文本时,首先对查询文本进行向量化,然后计算查询向量与文档向量之间的相似度,并根据相似度排序,返回与查询文本最相关的文档。对话交互:在文档对话系统中,用户可以提供多个查询文本,并根据系统的回复进行进一步的对话交互。可以使用大语言模型生成系统的回复,并根据用户的反馈进行迭代和改进。3. 基于 LLM + 向量库的文档对话核心技术是什么?大语言模型:在文档对话系统中,大语言模型可以用于生成回复、推荐相关文档等任务。文档向量化:使用向量库的方法,将每个文档转换为数值向量,以便计算文档之间的相似度或进行聚类分析。相似度计算:相似度计算是文档对话系统中的重要技术。通过计算查询文本向量与文档向量之间的相似度,可以实现文档的检索和推荐。4. 基于 LLM + 向量库的文档对话 prompt 模板如何构建?将知识库中检索到的内容作为已知:通常将向量库中的内容作为已知,例如可以使用"已知XXX\n请对以下内容做出回应:XXX"作为模板。5. 文档切分粒度如何把控?在基于大语言模型和向量库的文档对话中,需要在文档切分的粒度上进行权衡。如果切分得太细,可能会引入较多的噪声;如果切分得太粗,可能会丢失一些重要的语义信息。以下是一些解决方案:预处理和过滤:在进行文档切分之前,可以进行一些预处理和过滤操作,以减少噪声的影响。例如,可以去除文档中的停用词、标点符号、特殊字符等,以及进行拼写纠错等操作。这样可以降低噪声的存在,提高文档切分的质量。主题建模:可以使用主题建模技术,如 LDA(Latent Dirichlet Allocation)等,对文档进行主题抽取。通过识别文档的主题,可以帮助确定文档切分的粒度。例如,将同一主题下的文档划分为一个切分单元,以保留更多的语义信息。上下文信息:在进行文档切分时,考虑上下文信息对于语义的影响。例如,将与上一文档相关联的文档划分为一个切分单元,以保留上下文的连贯性和语义关联。这样可以更好地捕捉文档之间的语义信息。动态切分:可以采用动态切分的方式,根据用户的查询和需要,实时生成切分单元。例如,根据用户的关键词或查询意图,动态生成包含相关信息的切分单元,以减少噪声和提高语义的准确性。实验优化:在实际应用中,可以进行一系列的实验和优化,通过不断调整和评估文档切分的效果。可以尝试不同的切分粒度,评估其噪声和语义信息的平衡。通过实验和优化,逐步找到合适的文档切分策略。6. 在垂直领域表现不佳,如何改进?针对垂直领域进行领域特定训练:LLM 是基于大规模通用语料库进行训练的,可能无法充分捕捉垂直领域的特点和术语。可以使用领域特定的语料库对 LLM 进行微调或重新训练,以提高在垂直领域的表现。增加领域知识向量库:在向量库中添加垂直领域的专业知识,如领域术语、实体名词以及其相关定义等。这样可以提高向量库中文档的表示能力,使其更适应垂直领域的对话需求。数据增强和样本平衡:在训练LLM模型时,可以增加垂直领域的样本数据,以增加模型对垂直领域的理解和表达能力。同时,要注意样本的平衡,确保训练数据中包含各个垂直领域的典型对话场景,避免偏向某个特定领域。7. 如何处理 langchain 内置问答分句效果不佳的问题?使用自定义规则:针对特定的文本类型或语言,可以使用自定义规则来分句。例如,可以编写正则表达式或使用特定的分句库来处理特定的分句需求。这样可以更好地适应特定的语言和文本结构。结合其他工具:除了 Langchain 内置的问答分句功能,还可以结合其他分句工具或库来处理文本。例如,NLTK、spaCy 等自然语言处理工具包中提供了强大的分句功能,可以与 Langchain 一起使用,以获得更好的分句效果。收集反馈和调整模型:如果您发现 Langchain 内置的问答分句功能在特定场景下效果不佳,可以收集用户反馈,并根据用户反馈进行模型调整和改进。通过不断优化模型,可以逐渐改善分句效果。8. 如何尽可能召回与 query 相关的 Document?建立索引:将 Document 集合建立索引,以便能够快速检索和匹配相关的 Document。可以使用搜索引擎或专业的信息检索工具,如 Elasticsearch、Solr 等。关键词匹配:通过对 query 和 Document 中的关键词进行匹配,筛选出包含相关关键词的Document。可以使用 TF-IDF、BM25 等算法来计算关键词的重要性和匹配程度。向量化表示:将 query 和 Document 转化为向量表示,通过计算它们之间的相似度来判断相关性。上下文建模:考虑上下文信息,如 query 的前后文、Document 的上下文等,以更准确地判断相关性。可以使用上下文编码器或注意力机制来捕捉上下文信息。语义匹配:使用语义匹配模型,如 Siamese 网络、BERT 等,来计算 query 和 Document 之间的语义相似度,以更准确地判断相关性。9. 如何让 LLM 基于 query 和 context 得到高质量的 response?数据准备:准备大量高质量的训练数据,包括 query、context 和对应的高质量 response。确保数据的多样性和覆盖性,以提供更好的训练样本。上下文建模:在 LLM 中,上下文对于生成高质量的 response 非常重要。确保模型能够准确地理解和利用上下文信息,以生成与之相关的 response。可以使用一些技术,如注意力机制和上下文编码器,来帮助模型更好地建模上下文。多模态信息利用:如果有可用的多模态信息,如图像、视频等,可以将其整合到LLM中,以提供更丰富、准确的 response。利用多模态信息可以增强模型的理解能力和表达能力,从而生成更高质量的 response。
0
0
0
浏览量617
算法无限

LLM常见问题(Attention 优化部分)

1. 传统 Attention 存在哪些问题?传统的 Attention 机制忽略了源端或目标端句子中词与词之间的依赖关系。传统的 Attention 机制过度依赖 Encoder-Decoder 架构上。传统的 Attention 机制依赖于Decoder的循环解码器,所以依赖于 RNN,LSTM 等循环结构。传统的 Attention 依赖 RNN 结构,无法做到并行训练,训练速度受到影响。2. Attention 有哪些优化方法?稀疏 attention:比如窗口注意力,其实就是一个 token 只考虑周围一个窗口内的其他 token。矩阵分解:我们通常认为注意力矩阵是低秩的,这意味着矩阵里的元素并不都是相互独立的。所以,我们可以将这个矩阵拆解并使用一个更小的矩阵来近似它,从而能更高效地计算 softmax 的结果。局部敏感哈希:局部敏感哈希(LSH)是一种高效寻找近似最近邻的技巧。其核心思想是选择特定的哈希函数,使得在高维空间里,两个点 p 和 q 如若靠近,则它们的哈希值应相同。这样,所有的点就可以被分配到不同的哈希桶中,大大提高了寻找某个点的最近邻的效率,因为我们只需考虑同一个哈希桶内的点。在自注意力机制中,这种方法可以用于快速计算 P,方法是在 Q 和 K 上应用 LSH,仅对近似的元素进行计算,而非直接进行 Q 和 K 的全量计算。Kernel attention:Kernel attention 是一种近似的注意力机制,其主要思想是使用核技巧(kernel trick)来估计原始注意力的计算。这种方法尤其在长序列上很有效,因为它可以显著减少计算和存储的需求。KV-Cache:KV-Cache 的主要思路是:当我们一次生成一个 token 时,之前 token 的 key 和 value 不会改变。因此,我们可以缓存(或记住)这些值,并在下一个 token 的计算中重复使用它们。Multi-Query Attention:传统的多头注意力实质上是将输入分成多个头部,并为每个头部独立计算注意力。在 MHA 中,QQ、KK 和 VV 都根据每个 head 进行不同的转换。这在头部数量较多时可能会计算密集。多查询注意力简化了这个过程,尤其是在 KK 和 VV 的部分。与为每个 head 提供多个、单独的 KK 和 VV 映射不同,MQA 为所有 head 应用单一的 KK 和 VV 转换。只有 QQ 值才有多个 head。Grouped-Query Attention:Grouped-Query Attention 其实是一个折中方案,相比于 MQA 它的 KK 和 VV 的数量减少一些,但又不是只有一组这么少。3. Multi-head Attention 存在什么问题?计算复杂度高:Multi-head Attention 需要对查询、键和值进行线性变换,然后再进行点积操作和 Softmax 归一化。这些计算在长序列上会导致较高的计算复杂度和显存消耗。这可能限制了模型在大规模数据集和超长序列上的应用。低秩瓶颈:在 Multi-head Attention 中,查询、键和值的维度通常被投影到较低的维度(头大小)。这可能导致表达能力受到限制,从而影响模型性能。为了缓解这个问题,可以增大头大小或者采用其他方法,如局部敏感哈希(Locality Sensitive Hashing,LSH)注意力机制。4. Multi-Query Attention 是什么?MQA 是 19 年提出的一种 Attention 机制,其能够在保证模型效果的同时加快 decoder 生成 token 的速度。MQA 是将 head 中的 key 和 value 矩阵抽出来单独存为一份共享参数,而 query 则是依旧保留在原来的 head 中,每个 head 有一份自己独有的 query 参数。5. 对比一下 Multi-head Attention 和 Multi-Query Attention?MHA 是利用多个查询,来平行地计算从输入信息中选取多个信息。每个注意力关注输入信息的不同部分,然后再进行拼接。多头注意力的机制进一步细化了注意力层,通过扩展模型专注于不同位置的能力以及提供了多个“表示子空间”来提高注意力层的性能。MQA 让所有的头之间 共享 同一份 Key 和 Value 矩阵,每个头只单独保留了一份 Query 参数,从而大大减少 Key 和 Value 矩阵的参数量。在 MQA 中,除了 query 向量还保存着多个头,key 和 value 向量都只剩 1 个「公共头」了。6. Multi-Query Attention 这样做的好处是什么?MQA 和 MHA 主要是在计算 K 和 V 的过程中有计算量的差异,由于训练阶段由于数据是并行的,这种差异整体不明显,而在推理阶段,在memery cache的基础上,MQA 的推理速度有明显提升,同时也更省内存。7. Grouped-query Attention 是什么?Grouped-query Attention(分组查询注意力)是一种针对 Transformer 模型中 Multi-head Attention 的改进方法,旨在提高模型的运算速度,同时保持预测质量。在标准的Multi-head Attention中,每个注意力头都是独立计算的,这导致了计算和存储需求较高。分组查询注意力通过将查询头分组,让每组共享一个键头和值头,从而减少计算和存储需求。8. FlashAttention 是什么?flash attention通过减少访问HBM(high bandwidth memory)和on-chip SRAM内存读写时间,提高计算速度的方法。通过分块计算:增大每次计算矩阵的最小单元,从而降低对HBM的读写次数,使得整体得到加速(HBM读写非常耗时)通过重计算:降低内存,被丢弃的变量在反传的过程中会再次使用到,需要重新计算得到,类似于梯度检查。
0
0
0
浏览量2017
算法无限

LLM常见问题(Token 及模型参数部分)

1. 预训练模型表现影响因素有那些?模型表现强依赖于模型规模(模型参数量 N、训练 Token 数 D、训练总计算量 C)。平滑幂定律:模型表现与三个因子均遵循幂定律,不受另外两个因子限制。在给定计算量预算下,模型参数量以及训练 Token 数应该同比提升,对应模型参数量需要的训练 Token 数量。2. SFT需要训练需要多少 Token 数?当扩大数据量而不同时扩大提示多样性时,收益会大大减少,而在优化数据质量时,收益会大大增加。特定任务的模型可能从固定的任务类型中获益,以获得更高的性能;指令格式的多样性可能对特定任务模型的性能影响很小;即使是少量的数据(1.9M tokens)也能为特定任务模型的指令调整带来可喜的结果。一个模型的知识和能力几乎完全是在预训练中学习的,而对齐则是教它在与用户交互时应该使用哪种子分布的格式。3. 重复 Token 对模型性能有什么影响?在LLM时代,很多模型的 epoch 只有1次或者几次。例如,2022年谷歌的 PaLM 模型,其训练的 epoch 数量只有 1。而 MetaAI 训练的 LLaMA 模型,在不同数据集上训练的 epoch 设置都是 1-2。这似乎与我们之前理解的模型训练充分有不一致。2022年,Hoffmann 的论文中提出用重复的 tokens 训练大语言模型会让模型降低性能,而 Taylor 在训练 Galactica 模型时候发现 epochs 次数达到4次也可以提升模型效果。显然,在重复数据集上训练多次对模型的影响目前还没有一个相对完善的研究。相对更高质量的数据集并不能降低重复训练带来的影响。FLOPs 较大的模型性能会更好一点,但是依然无法有效降低重复训练带来的模型损失。在目前超过 100亿 参数规模的大语言模型中,如 GPT-3、PaLM、LLaMA 等,都没有使用 dropout(可能是因为太慢了)。而前面说的 Galactica 训练使用了,这是 Galactica 能够训练 4 Epochs 提升性能的最重要的原因。
0
0
0
浏览量420
算法无限

LLM常见问题(思维链变体部分)

1. 为什么需要思维树 Tree of Thoughts(TOT)?对于需要探索或预判战略的复杂任务来说,传统或简单的提示技巧是不够的。ToT 维护着一棵思维树,思维由连贯的语言序列表示,这个序列就是解决问题的中间步骤。使用这种方法,LM 能够自己对严谨推理过程的中间思维进行评估。LM 将生成及评估思维的能力与搜索算法(如广度优先搜索和深度优先搜索)相结合,在系统性探索思维的时候可以向前验证和回溯。2. 思维树 Tree of Thoughts(TOT)实现思路是什么?ToT 需要针对不同的任务定义思维/步骤的数量以及每步的候选项数量。例如“算 24 游戏”是一种数学推理任务,需要分成 3 个思维步骤,每一步都需要一个中间方程。而每个步骤保留最优的(best) 5 个候选项。ToT 完成算 24 的游戏任务要执行广度优先搜索(BFS),每步思维的候选项都要求 LM 给出能否得到 24 的评估:“sure/maybe/impossible”(一定能/可能/不可能) 。“目的是得到经过少量向前尝试就可以验证正确(sure)的局部解,基于‘太大/太小’的常识消除那些不可能(impossible)的局部解,其余的局部解作为‘maybe’保留。”每步思维都要抽样得到 3 个评估结果。3. 思维树 Tree of Thoughts(TOT)提示词如何设计?假设三位不同的专家来回答这个问题。所有专家都写下他们思考这个问题的第一个步骤,然后与大家分享。然后,所有专家都写 下他们思考的下一个步骤并分享。以此类推,直到所有专家写完他们思考的所有步骤。只要大家发现有专家的步骤出错了,就让 这位专家离开。请问...4. 为什么需要思维图 Graph of Thoughts(GOT)?在进行思考时,人类不会像 CoT 那样仅遵循一条思维链,也不是像 ToT 那样尝试多种不同途径,而是会形成一个更加复杂的思维网。举个例子,一个人可能会先探索一条思维链,然后回溯再探索另一条,然后可能会意识到之前那条链的某个想法可以和当前链结合起来,取长补短,得到一个新的解决方案。类似地,大脑会形成复杂的网络,呈现出类似图的模式,比如循环模式。算法执行时也会揭示出网络的模式,这往往可以表示成有向无环图。研究者表示,如果将这种对应的图使能的变换用于 LLM 思维,那么有望创造一种强大的设计 prompt 的方法,但这种变换无法通过 CoT 或 ToT 自然地表达出来。5. 什么是思维图 Graph of Thoughts(GOT) ?GoT 尤其适用于可自然分解成更小子任务的任务,并且这些子任务可以分开解决,然后融合成一个最终解答。在这方面,GoT 的表现优于其它方案,比如在排序任务上,GoT 分别优于 CoT 和 ToT 约 70% 和 62%,同时成本还比 ToT 低 31% 以上。在数学形式上,GoT 可以建模为一个元组(G, T, E, R),其中 G 是 LLM 推理过程(即上下文中的所有 LLM 思维及其关系),T 是可能的思维变换,E 是用于获得思维分数的评估器函数,R 是用于选择最相关思维的排序函数。6. 思维图 Graph of Thoughts(GOT)核心思想是什么 ?将推理过程建模为有向图 G =(V,E),其中V 是顶点集,E是边缘集。顶点包含手头问题的解决方案(无论是初始、中级还是最终问题)。这种思维的具体形式取决于用例;它可以是一个段落(在写作任务中)或一个数字序列(在排序中)。有向边(t1,t2)表示思维t2是用思维t1作为“直接输入”构建的,即通过显式指示LLM用t1生成t2。为了推进这个过程,将思维转换应用于G。这种转变的一个例子是将得分最高的思维(到目前为止)合并到一个新思维中。另一个例子是循环考虑一个思维,增强它。请注意,这些转换结构严格扩展了CoT,CoT-SC(自一致性的多CoT)或ToT中可用的转换集。聚合,即将几个想法融合成一个统一的想法;精化,对单个思想进行连续迭代,以提高其精度;生成,有利于从现有思想中产生新的思想。7. 为什么需要思维算法 Algorithm of Thoughts(AOT)?已有的研究强调,人类在解决复杂问题时会本能地借鉴过去的经历,确保自己进行全面思考而不是狭隘地关注某一细节。LLM 生成范围仅受其 token 限制限定,似乎是注定要突破人类工作记忆的阻碍。受这一观察启发,研究者探究了 LLM 能否实现类似的对想法的分层探索,通过参考之前的中间步骤来筛除不可行的选项 —— 所有这些都在 LLM 的生成周期内完成。而人类长于直觉敏锐,算法善于组织化和系统性的探索。CoT 等当前技术往往回避了这种协同性潜力,而过于关注 LLM 的现场精度。通过利用 LLM 的递归能力,研究者构建了一种人类 - 算法混合方法。其实现方式是通过使用算法示例,这些示例能体现探索的本质 —— 从最初的候选项到经过验证的解决方案。基于这些观察,研究者提出了思维算法(Algorithm of Thoughts /AoT)。8. 思维算法 Algorithm of Thoughts(AOT)思路是什么?利用 LLM 的迭代能力,在一次统一的生成式扫描中解决它们。通过限定自己仅能进行一两次 LLM 交互,该方法可以自然地整合来自之前的上下文候选项的洞见,并解决需要对解答域进行深度探索的复杂问题。对于这些思维的大小应当如何以及应该为 LLM 提供何种类型的上下文示例,从而提升 token 效率。分解成子问题:给定一个问题,就算不看实际解决问题方面,构建一个描述可行推理路径的搜索树已经是一项艰巨的任务。任何分解都不仅要考虑子任务之间的相互关系,还要考虑解决各个问题的难易程度。为子问题提议解答:现目前的一种主流方法涉及到直接采样 LLM token 输出概率。尽管这种方法对一次性答案有效(有一定的限制),但也无力应对一些场景,比如需要将样本序列整合进后续 prompt 中或在后续 prompt 中评估。为了尽可能减少模型查询,研究者采用了一种不间断的解答创建过程。即不带任何生成停顿,为主要子问题直接和连续地生成解答。衡量子问题的前景:如上所述,现有技术依靠额外的提示来识别树节点的潜力,帮助做出有关探索方向的决策。而研究者的观察表明,如果能将最有前途的路径封装在上下文示例中,LLM 会固有地倾向于优先考虑那些有前途的候选项。这能降低对复杂 prompt 工程设计的需求并允许整合复杂精细的启发式方法,不管这些方法是直觉式的或知识驱动的。同样,新方法中不含脱节的 prompt,这使得能在同一个生成结果中即时评估候选项的可行性。回溯到更好的节点:决定接下来要探索的节点(包括回溯到之前的节点)本质上取决于所选的树搜索算法。尽管之前已有研究为搜索过程采用了编码机制等外部方法,但这会限制其更广泛的吸引力并需要额外的定制。这篇论文提出的新设计主要采用 DFS 方法并辅以剪枝。目标是维持有同一父节点的子节点之间的近邻度,以此鼓励 LLM 优先考虑本地特征而不是远程特征。此外,研究者还提出了基于 BFS 的 AoT 方法的性能指标。研究者表示,借助于模型从上下文示例中收集见解的固有能力,可以消除额外的定制机制的必要性。9. 思维框架 Skeleton-of-Thought(SoT)是什么?思维框架(SoT)范式的独特设计主要是为了减少端到端生成延迟的挑战,而不是为了增强大型语言模型(LLM)的推理能力。这种方法采用双阶段方法,首先制定答案的初步蓝图,然后进行全面扩展。在最初的骨架阶段中,系统不会生成全面的响应,而是提示模型生成简洁的答案骨架。通过精心制作的骨架模板,这种缩写表达抓住了预期答案的核心元素,从而为下一阶段奠定了基础。在接下来的扩展阶段中,LLM系统会对答案骨架中的每个组成部分进行放大。它利用点扩展提示模板,同时阐述骨架的每个片段。10. 思维程序 Program-of-Thought(PoT)是什么?思维程序(PoT)是一种独特的LLM推理方法。它不仅仅是生成自然语言答案,而是要求创建一个可执行程序,可以在Python等程序解释器上运行,从而产生实际的结果。与直接模型相比,这种方法强调将推理分解为顺序步骤,并将语义与变量相关联的能力。因此,PoT提供了一个更清晰、更具表达力和基础的答案推导模型,提高了准确性和理解力,尤其是对于需要进行数值计算的数学类型逻辑问题。需要注意的是,PoT的程序执行不一定针对最终答案,而是可以作为最终答案的中间步骤的一部分。
0
0
0
浏览量2016
算法无限

LLM常见问题(Agent 部分)

1. 什么是 LLM Agent?LLM Agent 是一种人工智能系统,它利用大型语言模型 (LLM) 作为其核心计算引擎,展示文本生成之外的功能,包括进行对话、完成任务、推理,并可以展示一定程度的自主行为。LLM Agent 根据设计阶段授予的功能,Agent 从纯粹的被动到高度主动的自主行为。同时利用大模型的推理能力,让 Agent 可以在人工监督下管理相对独立的工作流程:分析目标,项目规划,执行,回顾过去的工作,迭代细化。2. LLM Agent 有什么关键能力?Agent利用LLM的语言能力理解指令、上下文和目标。可以根据人类提示自主和半自主操作。可以利用工具套件(计算器、API、搜索引擎)来收集信息并采取行动来完成分配的任务。它们不仅仅局限于语言处理。可以做逻辑推理类型的任务。例如,chain-of-thought , tree-of-thought。可以量身定制文本,例如邮件,报告,市场材料。可以自动或半自动的响应用户的需求。Agent可以和不同类型的AI系统对接,例如LLM+image generators。3. 怎样构建基于 LLM 的 Agents?Agent = LLM + Prompt Recipe + Tools + Interface + Knowledge + MemoryPrompt Recipe:特定的内容要求、目标受众、所需的语气、输出长度、创造力水平等。Tools:工具集成允许通过API和外部服务完成任务。Agents 能够理解自然语言、推理提示、积累记忆并采取明智的行动。但是,Agents 的表现和一致性取决于他们收到的提示的质量。Knowledge:知识适用于所有用户的一般专业知识。知识扩展了LLM的内容。一般分为专业知识、常识知识和程序知识。Memory:单个用户或单个任务的上下文和记录细节。分为短期记忆和长期记忆。记忆服务与特定用户,在时间维度的体验。使特定用户的上下文对话个性化同时保持多步骤任务的一致性。记忆侧重暂时的用户和任务细节。4. LLM Agents 有哪些类型?一般来说 LLM Agents 分为会话型 Agents 和任务型 Agents,两者在目标、行为和prompt方法都有重要区别。会话型专注于提供引人入胜的个性化讨论,任务型致力于完成明确定义的目标。Conversational Agents:模拟人类对话,能够在讨论中反映人类的倾向。允许细致入微的上下文交互,会考虑语气、说话风格、领域知识、观点和个性怪癖等因素。agent的开发者可以持续增强记忆、知识整合提高响应能力,持续优化应用。Task-Oriented Agents:实现目标驱动,利用模型的能力分析prompt、提取关键参数、指定计划、调用API、通过集成tools执行操作,并生成结果回复。Prompt 工程把目标型Agents拆分成如下环节:制定战略任务、串联思路、反思过去的工作以及迭代改进的方法。5. 是什么让Agent有了自治的能力?通常有自制能力的系统,至少有两类agent组成。一个用于生成的agent,一个用于监督的agent。生成agent根据提示生成回复。监督agent在必要时审查和重新提示或指示生成agent继续工作,同时提供交互反馈。自主技能是通过持续提示培养出来的。专门的监督agent提供方向、纠正和不断提高挑战,持续的提示释放了推理、效能和自主决策能力的增长。
0
0
0
浏览量174
算法无限

LLM常见问题(Prompting 部分)

1. 为什么需要提示学习(Prompting)?解决模糊性:在某些任务中,输入可能存在歧义或模糊性,通过提供明确的提示,可以帮助模型更好地理解任务的要求,避免产生错误或不确定的输出。控制生成:在生成式任务中,使用提示可以指导模型生成特定类型的输出。例如,在生成新闻标题的任务中,通过提示指定标题的主题或风格,可以使模型生成更符合要求的标题。纠正偏见:在自然语言处理中,模型可能受到社会偏见的影响,通过在提示中明确要求模型避免偏见,可以帮助减少模型输出中的偏见。增加一致性:通过在多个样本中使用相同的提示,可以确保模型生成的输出在不同输入上具有一致性。这对于任务如翻译或摘要生成等涉及多个输入的任务尤为重要。 总的来说,提示学习可以提供额外的信息和指导,帮助模型更好地理解任务和生成准确、有意义的输出。2. 什么是提示学习(Prompting)?提示学习(Prompting)是一种在机器学习中使用人类编写的提示或示例来辅助模型进行学习和推理的技术。在自然语言处理任务中,提示通常是一段文字或问题,用于指导模型生成或理解特定的输出。提示学习可以用于各种自然语言处理任务,包括文本分类、命名实体识别、情感分析、机器翻译等。在这些任务中,模型需要根据输入的文本来进行预测或生成输出。通过提供明确的提示,可以引导模型关注特定的信息或完成特定的任务。提示可以采用不同的形式,例如:完整的句子或问题:提供一个完整的句子或问题,要求模型根据输入生成相应的回答或输出。部分句子或关键词:提供部分句子或关键词,要求模型根据提示进行补充或扩展。条件约束:提供条件约束,要求模型生成满足这些条件的输出。3. 提示学习(Prompting) 有什么优点?控制生成输出:通过给定合适的提示,可以更好地控制模型生成的输出。提示可以引导模型关注特定的信息、执行特定的任务或生成特定的风格。这种控制使得模型更加可控,能够满足特定的需求。提高生成质量:通过合理设计和使用提示,可以帮助模型生成更准确、更流畅、更有逻辑性的输出。提示提供了一种引导模型生成的方式,可以避免一些常见的错误和无意义的输出,从而提高生成质量。解决数据稀缺问题:在某些任务中,训练数据可能非常稀缺,难以覆盖所有可能的输入和输出。通过使用提示,可以将模型的知识和经验引导到特定领域或任务中,从而提供更好的性能。这种方式可以在数据稀缺的情况下,利用有限的数据进行更有效的训练和生成。提供可解释性:提示作为人工设计的输入,可以提供对模型生成输出的解释和理解。通过分析和调整提示,可以更好地理解模型在生成过程中的决策和行为,从而提高模型的可解释性。4. 提示学习(Prompting)有哪些方法,能不能稍微介绍一下它们间?文本前缀(Text Prefix):在输入文本的开头添加一个人工设计的前缀作为提示。这个前缀可以是一个问题、一个指令、一个关键词等,用来引导模型生成相关的输出。例如,在文本生成任务中,可以在输入文本前添加一个问题,要求模型回答该问题。问题模板(Question Templates):设计一系列问题模板,用于引导模型生成回答问题的文本。这些问题模板可以覆盖不同类型的问题,包括事实性问题、推理问题、主观性问题等。模型可以根据问题模板生成对应的回答。知识引导(Knowledge Guided):利用外部的知识源来辅助模型生成输出。这些知识源可以是知识图谱、数据库、文档等,模型可以根据这些知识源进行查询、检索和引用。这样可以提供更准确、更丰富的信息来指导模型生成。5. 为什么需要 前缀微调(Prefix-tining)?前缀微调(Prefix-tuning)是一种在提示学习中使用的技术,它通过微调(fine-tuning)预训练语言模型来适应特定的生成任务。前缀微调之所以需要,是因为传统的预训练语言模型在生成任务中存在一些问题和限制,包括以下几个方面:缺乏控制:传统的预训练语言模型通常是通过无监督学习从大规模文本数据中学习得到的,生成时缺乏对输出的控制。这导致模型往往会生成一些无意义、不准确或不符合要求的内容。缺乏指导:传统的预训练语言模型在生成任务中缺乏指导,无法根据特定的任务要求生成相关的内容。例如,在问答任务中,模型需要根据给定的问题生成准确的答案,但预训练语言模型无法直接实现这一点。数据偏差:预训练语言模型通常是从大规模的通用数据中训练得到的,而特定的生成任务往往需要针对特定领域或任务的数据。由于数据的偏差,预训练语言模型在特定任务上的性能可能会受到限制。前缀微调通过在输入文本的开头添加一个人工设计的前缀,将任务要求或指导信息引入到生成过程中,从而解决了上述问题。通过给定合适的前缀,可以控制模型生成的内容,指导模型关注特定的信息,并使生成结果更加准确和符合要求。前缀微调提供了一种简单有效的方法,可以在生成任务中引入人类设计的指导信息,提高模型的生成质量和可控性。6. 前缀微调(Prefix-tining)思路是什么?前缀微调(Prefix-tuning)的思路是在预训练语言模型的基础上,通过微调的方式引入任务相关的指导信息,从而提高模型在特定生成任务上的性能和可控性。在预训练语言模型的基础上引入任务相关的指导信息,使模型更加适应特定的生成任务。这种方法不仅提高了生成结果的质量和准确性,还增加了对生成过程的可控性,使模型能够更好地满足任务的需求。7. 前缀微调(Prefix-tining)的优点是什么?可控性:通过设计合适的前缀,可以引导模型生成特定类型的内容,使生成结果更加符合任务要求。前缀提供了对生成过程的控制,使得模型能够根据任务需求生成相关的内容,从而提高生成结果的准确性和质量。数据效率:相比于从零开始训练一个生成模型,前缀微调利用了预训练语言模型的知识,可以在相对较少的任务数据上进行微调,从而节省了大量的训练时间和资源。这对于数据稀缺的任务或领域来说尤为重要。可解释性:前缀微调中的前缀可以包含任务的要求、指导或关键信息,这使得模型生成的结果更加可解释。通过分析前缀和生成结果之间的关系,可以更好地理解模型在任务中的决策过程,从而更好地调试和优化模型。8. 前缀微调(Prefix-tining)的缺点是什么?前缀设计的挑战:前缀的设计需要考虑到任务的要求、指导或关键信息,以便正确引导模型生成相关内容。设计一个合适的前缀可能需要领域知识和人工调整,这可能会增加任务的复杂性和工作量。任务依赖性:前缀微调是一种针对特定任务的方法,模型的性能和生成效果高度依赖于任务数据和前缀的设计。如果任务数据不足或前缀设计不合理,可能会导致模型性能下降或生成结果不符合预期。9. 为什么需要指示微调(Prompt-tuning)?指示微调(Prompt-tuning)是一种用于生成任务的微调方法,它的出现主要是为了解决前缀微调(Prefix-tuning)中前缀设计的挑战和限制。以下是需要指示微调的几个原因:前缀设计的复杂性:前缀微调需要设计合适的前缀来引导模型生成相关内容。然而,前缀的设计可能需要领域知识和人工调整,这增加了任务的复杂性和工作量。指示微调通过使用简洁的指示语句来替代复杂的前缀设计,简化了任务的准备过程。指导信息的一致性:前缀微调中的前缀需要包含任务的要求、指导或关键信息。然而,前缀的设计可能存在主观性和不确定性,导致模型生成结果的一致性较差。指示微调通过使用明确和一致的指示语句来提供指导信息,可以更好地控制模型生成的结果,提高一致性和可控性。任务的多样性和灵活性:前缀微调中的前缀是针对特定任务设计的,对于不同的任务需要单独进行微调。这对于多样的任务和领域来说可能需要更多的任务数据和人力资源。指示微调通过使用通用的指示语句,可以适用于各种生成任务,提高了任务的灵活性和可扩展性。模型的可解释性:指示微调中的指示语句可以提供对模型生成结果的解释和指导。通过分析指示语句和生成结果之间的关系,可以更好地理解模型在任务中的决策过程,从而更好地调试和优化模型。10. 指示微调(Prompt-tuning)思路是什么?首先,为了指导模型生成相关内容,需要设计简洁明确的指示语句。指示语句应该包含任务的要求、指导或关键信息,以引导模型生成符合任务要求的结果。指示语句可以是一个完整的句子、一个问题、一个关键词等,具体的设计取决于任务的需求。通过指示微调,可以在预训练模型的基础上,使用简洁明确的指示语句来指导模型生成相关内容。这种方法简化了任务的准备过程,提高了任务的灵活性和可控性,并增加了模型生成结果的一致性和可解释性。11. 指示微调(Prompt-tuning)优点是什么?简化任务准备:相比于前缀微调,指示微调减少了任务准备的复杂性。前缀设计可能需要领域知识和人工调整,而指示语句通常更简洁明确,减少了任务准备的时间和工作量。一致性和可控性:指示微调使用明确的指示语句来指导模型生成结果,提高了生成结果的一致性和可控性。指示语句可以提供任务的要求、指导或关键信息,使得模型生成的结果更加符合任务需求。可解释性:指示微调中的指示语句可以提供对模型生成结果的解释和指导。通过分析指示语句和生成结果之间的关系,可以更好地理解模型在任务中的决策过程,从而更好地调试和优化模型。12. 指示微调(Prompt-tuning)缺点是什么?依赖于设计良好的指示语句:指示微调的效果很大程度上依赖于设计良好的指示语句。如果指示语句不够明确、不够准确或不够全面,可能导致模型生成的结果不符合任务要求。因此,需要投入一定的时间和精力来设计和优化指示语句。对任务理解的依赖:指示微调要求模型能够准确理解指示语句中的任务要求和指导信息。如果模型对任务理解存在偏差或困惑,可能会导致生成结果的不准确或不符合预期。这需要在微调过程中充分训练和调整模型,以提高任务理解的准确性。难以处理复杂任务:对于一些复杂的任务,简单的指示语句可能无法提供足够的信息来指导模型生成复杂的结果。这可能需要设计更复杂的指示语句或采用其他更复杂的方法来解决任务。
0
0
0
浏览量2009
算法无限

LLM常见问题(Layer normalization 部分)

1. Layer Norm 的计算公式是怎样的,如何使用代码实现?其中 μ 为 x 的均值,σ 为 x 的方差,γ 和 β 是可训练的模型参数,γ 是缩放参数,新分布的方差 γ2 ; β 是平移系数,新分布的均值为 β 。 ε 为一个小数,添加到方差上,避免分母为0。def layerNorm(feature): size = feature.shape alpha = torch.nn.Parameter(torch.ones(size[-1])) beta = torch.nn.Parameter(torch.ones(size[-1])) input_dtype = feature.dtype feature = torch.nn.Parameter(feature.to(torch.float32)) mean = feature.mean(-1, keepdim=True) std = feature.std(-1, keepdim=True) feature = alpha * (feature - mean) return (feature / (std + 1e-6) + beta).to(input_dtype)2. RMS Norm 的计算公式是怎样的,如何使用代码实现?对于layerNorm和RMSNorm,layerNorm包含缩放和平移两部分,RMSNorm去除了平移部分,只保留了缩放部分。def RMSNorm(feature): size = feature.shape weight = torch.nn.Parameter(torch.ones(size[-1])) input_dtype = feature.dtype feature = torch.nn.Parameter(feature.to(torch.float32)) variance = feature.pow(2).mean(-1, keepdim=True) feature = feature * torch.rsqrt(variance + 1e-6) return weight * feature.to(input_dtype)3. RMS Norm 相比于 Layer Norm 有什么特点?RMSNorm 相比一般的 layerNorm,减少了计算均值和平移系数的部分,训练速度更快,效果基本相当,甚至有所提升。4. Deep Norm 思路是什么?DeepNorm 是由微软提出的一种 Normalization 方法。主要对 Transformer 结构中的残差链接做修正。5. Deep Norm 有什么优点?DeepNorm 可以缓解模型参数爆炸式更新的问题,把模型参数更新限制在一个常数域范围内,使得模型训练过程可以更稳定。模型规模可以达到 1000 层。DeepNorm 兼具 PreLN 的训练稳定和 PostLN 的效果性能。6. LN 在 LLMs 中的不同位置有什么区别么?如果有,能介绍一下区别么?在 transformer 的原始结构中,采用了 PostLN 结构,即在残差连接之后 layerNorm,如上图(a)所示。在 LLM 训练过程中发现,PostLN 的输出层附近的梯度过大会造成训练的不稳定性。在 LLM 很少单独使用 PostLN,如在 GLM-130B 中采用 PostLN 与 PreLN 结合的方式。PreLN 将 layerNorm 放置在残差连接的过程中,如上图(a)所示。PreLN 在每层的梯度范数近似相等,有利于提升训练稳定性。相比 PostLN,使用 PreLN 的深层 transforme 的训练更稳定,但是性能有一定损害。为了提升训练稳定性,很多大模型都采用了 PreLN。
0
0
0
浏览量1028
算法无限

LLM常见问题(优化加速部分)

1. 当前优化模型最主要技术手段有哪些?当前优化模型最主要技术手段概括来说有以下三个层面:算法层面:蒸馏、量化软件层面:计算图优化、模型编译硬件层面:FP8(NVIDIA H系列GPU开始支持FP8,兼有fp16的稳定性和int8的速度)2. 推理加速框架有哪一些?都有什么特点?FasterTransformer:英伟达推出的FasterTransformer不修改模型架构而是在计算加速层面优化 Transformer 的 encoder 和 decoder 模块。具体包括如下: 尽可能多地融合除了 GEMM 以外的操作 支持 FP16、INT8、FP8 移除 encoder 输入中无用的 padding 来减少计算开销TurboTransformers:腾讯推出的 TurboTransformers 由 computation runtime 及 serving framework 组成。加速推理框架适用于 CPU 和 GPU,最重要的是,它可以无需预处理便可处理变长的输入序列。具体包括如下: 与 FasterTransformer 类似,它融合了除 GEMM 之外的操作以减少计算量 smart batching,对于一个 batch 内不同长度的序列,它也最小化了 zero-padding 开销 对 LayerNorm 和 Softmax 进行批处理,使它们更适合并行计算 引入了模型感知分配器,以确保在可变长度请求服务期间内存占用较小3. vLLM 的功能有哪些?vLLM 的核心是 PagedAttention,这是一种新颖的注意力算法,它将在操作系统的虚拟内存中分页的经典思想引入到 LLM 服务中。vLLM 比 HuggingFace Transformers 提供高达 24 倍的吞吐量,而无需任何模型架构更改。4. vLLM 的优缺点有哪些?优点:最先进的服务吞吐量PagedAttention 可以有效的管理注意力的键和值动态批处理请求CUDA 内核优化与 HuggingFace 模型无缝集成高吞吐量服务与各种解码算法,包括并行采样、beam search 等等张量并行以支持分布式推理流式输出兼容 OpenAI 的 API 服务缺点:其采样参数支持的还不是很完备(与 huggingface 相比),参数对齐后跑 human eval 指标比 hf 低一些batch=1 的时候不如 hf5. vLLM 有哪些推理实现方法?vLLM 提供了两类推理的实现,一类是 offline inference,类似于 HF pipeline 的 batch 推理接口,用于离线批量的推理生成;另一类是和 openai api 类似的实时在线推理,用于服务端接收并发推理请求的应用部署,其本身也可以通过命令行拉起一个 web 服务端进行部署。6. TGI(Text generation inference) 是什么?Text generation inference 用于文本生成推理的 Rust、Python 和 gRPC 服务器。作为支持 Hugging Face Inference API 和后来的 Hugging Chat 上的 LLM 推理的内部工具,旨在支持大型语言模型的优化推理。7. TGI 的功能特点有哪些?使用简单的启动器为最流行的大型语言模型提供服务Tensor Parallelism 用于在多个 GPU 上进行更快的推理使用服务器发送事件 (SSE) 的令牌流连续批处理传入请求以提高总吞吐量优化的变换器代码,用于在最流行的架构上使用 flash-attention 进行推理使用 bitsandbytes 进行量化Safetensors weight loading使用 A Watermark 的大型语言模型Logits warper(temperature scaling、top-p、top-k、repetition penalty)Log probabilities生产就绪(使用 Open Telemetry、Prometheus 指标进行分布式跟踪)
0
0
0
浏览量979
算法无限

LLM常见问题(推理部分)

1. 为什么大模型推理时显存涨的那么多还一直占着?模型参数占用显存:大语言模型本身具有大量参数量,这些参数需要存储在显存中以供推理使用。不量化的情况下这部分显存占用和大模型所占存储空间相同。输入数据占用显存:进行推理时,需要将输入数据加载到显存中。对于大语言模型而言,输入数据通常也会占用较大的显存空间,尤其是对于较长的文本输入。中间计算结果占用显存:在推理过程中,模型会进行一系列的计算操作,生成中间结果。这些中间结果也需要存储在显存中,以便后续计算使用。对于大语言模型而言,中间计算结果可能会占用较多的显存空间。内存管理策略:某些深度学习框架在推理时采用了一种延迟释放显存的策略,即显存不会立即释放,而是保留一段时间以备后续使用。这种策略可以减少显存的分配和释放频率,提高推理效率,但也会导致显存一直占用的现象。2. 大模型在 GPU 和 CPU 上推理速度如何?GPU推理速度快:GPU 具有大量的并行计算单元,可以同时处理多个计算任务。对于大语言模型而言, GPU 可以更高效地执行矩阵运算和神经网络计算,从而加速推理过程。CPU推理速度相对较慢:相较于 GPU,CPU 的计算能力较弱,主要用于通用计算任务。虽然 CPU 也可以执行大语言模型的推理任务,但由于计算能力有限,推理速度通常会较慢。使用GPU加速推理:为了充分利用GPU的计算能力,通常会使用深度学习框架提供的 GPU 加速功能,如 CUDA,CUDA 可以将计算任务分配给GPU并利用其并行计算能力,从而加快大语言模型的推理速度。3. 推理速度上,INT8 和 FP16 比起来怎么样?在大语言模型的推理速度上,使用 INT8(8位整数量化)和 FP16(半精度浮点数)相对于 FP32(单精度浮点数)可以带来一定的加速效果。因为 INT8 和 FP16 的数据类型在表示数据时所需的内存和计算资源较少,从而可以加快推理速度。一般来说 INT8 在推理时会比 FP16 更快,因为 INT8 在相同的内存空间下可以存储更多的数据,可以在相同的计算资源下进行更多的并行计算。这可以提高每秒推理操作数(Operations Per Second,OPS)的数量,加速推理速度。4. 大模型有推理能力吗?逻辑推理是大语言模型“智能涌现”出的核心能力之一,好像AI有了人的意识一样。而推理能力的关键,在于一个技术——思维链(Chain of Thought,CoT)。当模型规模足够大的时候,LLM本身是具备推理能力的。在简单推理问题上,LLM已经达到了很好的能力;复杂推理问题上,还需要更多深入的研究。5. 大模型生成时的参数怎么设置?Temperature:用于调整随机从生成模型中抽样的程度,使得相同的提示可能会产生不同的输出。温度为 0 将始终产生相同的输出,该参数设置越高随机性越大。波束搜索宽度:波束搜索是许多 NLP 和语音识别模型中常用的一种算法,作为在给定可能选项的情况下选择最佳输出的最终决策步骤。波束搜索宽度是一个参数,用于确定算法在搜索的每个步骤中应该考虑的候选数量。Top p:动态设置tokens候选列表的大小。  将可能性之和不超过特定值的top tokens列入候选名单。Top p 通常设置为较高的值(如 0.75),目的是限制可能被采样的低概率 token 的长度。Top k:允许其他高分tokens有机会被选中。  这种采样引入的随机性有助于在很多情况下生成的质量。 Top k 参数设置为 3 则意味着选择前三个tokens。若 Top k 和 Top p 都启用,则 Top p 在 Top k 之后起作用。6. 有哪些省内存的大语言模型训练/微调/推理方法?参数共享(Parameter Sharing):通过共享模型中的参数,可以减少内存占用。例如,可以在不同的位置共享相同的嵌入层或注意力机制。梯度累积(Gradient Accumulation):在训练过程中,将多个小批次的梯度累积起来,然后进行一次参数更新。这样可以减少每个小批次的内存需求,特别适用于GPU内存较小的情况。梯度裁剪(Gradient Clipping):通过限制梯度的大小,可以避免梯度爆炸的问题,从而减少内存使用。分布式训练(Distributed Training):将训练过程分布到多台机器或多个设备上,可以减少单个设备的内存占用。分布式训练还可以加速训练过程。量化(Quantization):将模型参数从高精度表示(如FP32)转换为低精度表示(如INT8或FP16),可以减少内存占用。量化方法可以通过减少参数位数或使用整数表示来实现。剪枝(Pruning):通过去除冗余或不重要的模型参数,可以减少模型的内存占用。剪枝方法可以根据参数的重要性进行选择,从而保持模型性能的同时减少内存需求。蒸馏(Knowledge Distillation):使用较小的模型(教师模型)来指导训练较大的模型(学生模型),可以从教师模型中提取知识,减少内存占用。分块处理(Chunking):将输入数据或模型分成较小的块进行处理,可以减少内存需求。例如,在推理过程中,可以将较长的输入序列分成多个较短的子序列进行处理。
0
0
0
浏览量1239
算法无限

LLM常见问题(激活函数部分)

1. 介绍一下 FFN 块?FFN(Feed-Forward Network)块是 Transformer 模型中的一个重要组成部分,用于对输入数据进行非线性变换。它由两个全连接层(即前馈神经网络)和一个激活函数组成。下面是 FFN 块的计算公式:假设输入是一个向量 x,FFN块的计算过程如下:第一层全连接层(线性变换): z = xW1 + b1 其中,W1 是第一层全连接层的权重矩阵,b1 是偏置向量。激活函数: a = g(z) 其中,g() 是激活函数,通常使用 GeLU。第二层全连接层(线性变换): y = aW2 + b2 其中,W2 是第二层全连接层的权重矩阵,b2 是偏置向量。在 Transformer 模型中,FFN 块通常被应用在每个 Transformer 编码层的每个位置上,用于对位置编码的向量进行非线性变换。这样可以增加模型的表达能力,提高对输入数据的建模能力。2. 介绍一下 GeLU 计算公式?GeLU(Gaussian Error Linear Unit)是一种激活函数,常用于神经网络中的非线性变换。它在 Transformer 模型中广泛应用于 FFN(Feed-Forward Network)块。下面是 GeLU 的计算公式:GeLU(x) = 0.5 * x * (1 + tanh(sqrt(2 / pi) * (x + 0.044715 * x^3)))其中,tanh() 是双曲正切函数,sqrt() 是平方根函数,pi 是圆周率。GeLU函数的特点是在接近零的区域表现得类似于线性函数,而在远离零的区域则表现出非线性的特性。相比于其他常用的激活函数(如ReLU),GeLU函数在某些情况下能够提供更好的性能和更快的收敛速度。3. 介绍一下 Swish 计算公式?Swish 是一种激活函数,它在深度学习中常用于神经网络的非线性变换。Swish 函数的计算公式如下:Swish(x) = x * sigmoid(beta * x)其中,sigmoid() 是Sigmoid函数,x 是输入,beta 是一个可调节的超参数。Swish函数的特点是在接近零的区域表现得类似于线性函数,而在远离零的区域则表现出非线性的特性。相比于其他常用的激活函数(如ReLU、tanh等),Swish函数在某些情况下能够提供更好的性能和更快的收敛速度。Swish函数的设计灵感来自于自动搜索算法,它通过引入一个可调节的超参数来增加非线性程度。当beta为0时,Swish函数退化为线性函数;当beta趋近于无穷大时,Swish函数趋近于ReLU函数。4. 介绍一下使用 GLU 线性门控单元的 FFN 块计算公式?使用 GLU(Gated Linear Unit)线性门控单元的 FFN(Feed-Forward Network)块是 Transformer 模型中常用的结构之一。它通过引入门控机制来增强模型的非线性能力。下面是使用GLU线性门控单元的 FFN 块的计算公式:假设输入是一个向量 x,GLU线性门控单元的计算公式如下:GLU(x) = x * sigmoid(W_1 * x)其中,sigmoid() 是 Sigmoid 函数,W_1 是一个可学习的权重矩阵。GLU线性门控单元的特点是能够对输入向量进行选择性地激活,从而增强模型的表达能力。5. 介绍一下使用 GeLU 的 GLU 块计算公式?使用 GeLU 作为激活函数的 GLU 块的计算公式如下:GLU(x) = x * GeLU(W_1 * x)其中,GeLU() 是 Gaussian Error Linear Unit 的激活函数,W_1 是一个可学习的权重矩阵。在公式中,首先将输入向量 x 通过一个全连接层(线性变换)得到一个与 x 维度相同的向量,然后将该向量作为输入传递给GeLU激活函数进行非线性变换。最后,将GeLU激活函数的输出与输入向量 x 逐元素相乘,得到最终的输出向量。6. 介绍一下使用 Swish 的 GLU 块计算公式?使用Swish作为激活函数的GLU块的计算公式如下:GLU(x) = x * sigmoid(W_1 * x) (1)其中,sigmoid() 是 Sigmoid 函数,W_1 是一个可学习的权重矩阵。在公式中,首先将输入向量 x 通过一个全连接层(线性变换)得到一个与 x 维度相同的向量,然后将该向量通过 Sigmoid 函数进行激活。这个Sigmoid函数的输出称为门控向量,用来控制输入向量 x 的元素是否被激活。最后,将门控向量与输入向量 x 逐元素相乘,得到最终的输出向量。
0
0
0
浏览量426
算法无限

LLM常见问题(基础部分)

1.  目前主流的开源LLM模型有哪些?目前公认最强大语言模型仍然是 GPT4 ,但由于 OpenAI 没有将其开源,商业部署成本较高,且难以进行微调,因此众多开源模型进入了大众的视野,包括国外的 LLaMa、Bloom;国内的 ChatGLM、Baichuan、Qwen、InterLM 等等,这些都是优秀的开源模型。LLaMA:由 Meta 研发,目前有 LLaMA 和 LLaMa2 两个版本,LLaMA 包含 7B、13B、33B、65B 四个规模的模型,LLaMA2包含 7B、13B、34B,70B 四个规模的模型。LLaMA 和 LLaMa2 都是 Decoder-only 架构的模型。但由于 LLaMA 的训练数据集中中文数据含量极低,因此 LLaMA 在中文能力方面非常弱,无法直接使用,一般使用中文 LLaMA 项目,例如Chinese-LLaMA-Alpaca。BLOOM:是由 Hugging Face 在内由数百名研究人员合作开发和发布,模型规模为 176B,训练数据包含包含46种自然语言和13种编程语言,采用 Decoder-only 架构,同时 BLOOM 使用 ALiBi 来向模型注入位置信息。Mixtral:MistralAI 开源全球首个基于混合专家技术的大模型 Mistral-8x7B-MoE,Mixtral-8x7B 是首个被证明有效的 开源的 MoE LLM,相比于古早的 Switch-Transformer 、 GLaM 等 Research, Mixtral-8x7B 证明了 MoE 真的可以落地,且效果远好于相同激活值的 Dense 模型。Mistral-8x7B-MoE 由8个拥有 7B 参数的专家网络组成,每个token的处理交由最相关的两个专家进行。这种结构不仅提高了模型处理信息的效率,还降低了运行成本。ChatGLM:由清华大学 KEG 实验室和智谱 AI 共同打造,目前已开源 ChatGLM-6B、ChatGLM2-6b、ChatGLM3-6b 三个模型,这三个模型都是由 130B 的 ChatGLM 模型蒸馏得到的,训练数据集包含中英双语,采用 GLM 架构(GLM Pretraining Framework),GLM 使用单个 Transformer ,并对其 Layer Normalization 的顺序、残差连接、激活函数进行了修改。Baichuan:由前搜狗公司 CEO 王小川创立的百川智能研发,半年时间里,接连发布 Baichuan-7B/13B、Baichuan2-7B/13B 四款开源可免费商用大模型及 Baichuan-53B、Baichuan2-53B 两款闭源大模型,训练数据包括从各种来源收集数据包括通用互联网网页、书籍、研究论文、代码库等,基于 Transformer 架构。Qwen:由阿里云开发,训练数据来源于公共网络文档、百科全书、书籍、代码等,主要语种为中文和英文,包含1.8B、7B、14B、72B 四个规模的模型,Qwen 采用了改进版的 Transformer 架构。2.  prefix LM 和 causal LM 区别是什么?perfix LM(前缀语言模型)和 causal LM(因果语言模型)区别在于生成文本的方式和训练目标。prefix LM:prefix LM 是一种生成模型,生成每个词时可以考虑之前的上下文信息。在生成时可以根据给定的前缀预测下一个可能的词。常用于文本生成、机器翻译等任务。causal LM:causal LM 是一种自回归模型,只能根据之前的文本生成后续的文本,而不能根据之后的文本生成之前的文本。训练时,因果模型的目标是预测下一个词的概率。常用于文本生成、语言建模等任务。3.  涌现能力是什么原因?涌现能力是指随着 LLM 规模的扩大,达到了一些临界规模,在这些规模上模型新的能力被“解锁”。LLM  并没有被直接训练拥有这些能力,但它们快速地以不可预测的方式出现,这些新兴能力包括执行运算、回答问题、总结段落等,LLM 仅需观察自然语言即可习得这些能力。目前并没有关于涌现能力明确的解释。原因猜想:对LLM的评价指标不够平滑。任务过于复杂,复杂任务由多个子任务组成,子任务符合 Scaling Law,最终体现为涌现能力。Grokking现象,模型长时间训练后会由记忆训练数据转变为对未训练数据存在较强的泛化能力。4.  大模型LLM的架构介绍LLM通常基于 Transformer,Transformer 基于 self-attention,通过多头自注意力机制和前馈神经网络可以处理长时间序列,并有良好的语言建模性能。大模型架构主要有 Decode-Only(Causal)、Pre-LN、RoPE、GeGLU、Bais 等, L、H 在100左右、d_model 在 10k 左右、Seq 大都为2048。层归一化位置:Post-LN(泛化性更好)、Pre-LN(训练更稳定)、Sandwich-LN(稳定+泛化的结合)。层归一化方法:LayerNorm(标准LN)、RMSNorm(剔除bais减少参数与计算量)、DeepNorm(层数衰减可构建1k层)。激活函数:Relu(梯度不会消失、阈值化计算简单)、GELU(处理负数、门控机制非线性处理大数/接近0的数、引入随机正则)、Swish、SwiGLU、GeGLU。位置编码:Abs(绝对编码, 可训练)、Rel(相对, 展开魔改)、RoPE(相对, 引入复数)、Alibi(操控 Attention、复杂)等。
0
0
0
浏览量2029
算法无限

LLM常见问题(进阶部分)

大语言模型(LLMs)进阶1.  什么是 LLMs 复读机问题?LLMs 复读机问题指的是模型倾向于不适当地复制用户输入的文本或者在回复中重复使用相同的句式和短语。这种倾向可能源自模型在处理输入时的一种简便路径,即模仿而不是创造性地响应。生成的内容往往缺乏变化和新颖性,这使得交互体验变得预测性强且单调。2.  为什么会出现 LLMs 复读机问题?训练数据的偏差问题:大型语言模型的预训练过程通常依赖大量未标记的数据。若这些数据中存在高度重复的文本,或某些句子及短语频繁出现,则模型在文本生成时可能过度沿用这些普遍模式。训练目标设置的局限:自监督学习是训练大型语言模型的一种常用方法,它主要通过预测下一单词或发现隐藏单词来学习。这种训练设置可能导致模型偏好生成与输入过于类似的文本,进而引发重复性输出的问题。训练材料多样性不足:尽管大型语言模型能处理大量数据,但如果所用的训练材料在语言表达和情境上缺乏变化,模型可能不会学习到充分的多样性和创造力,这也可能促成所谓的复读现象。3.  如何缓解 LLMs 复读机问题?使用多元化训练数据集:在模型训练阶段,采用来源广泛、风格各异、覆盖多个领域的数据集,这有助于减少数据偏见和避免重复性文本。调节温度系数:温度系数是控制生成文本变异度的关键参数。调整此参数能够平衡文本的创造性和多样性,其中较高的温度设置将引入更多随机性,有效降低复读现象。对模型输出后处理:监测模型的输出,若模型出现复读现象则中断输出或输出其它内容避免影响用户体验。4.  LLaMa输入句子长度理论上可以无限长吗?虽然理论上大型语言模型(LLM)能够处理任何长度的输入文本,但是存在局限性,限制 LLM 处理输入长度的主要因素包括:计算资源的要求:构建长文本需要较大的计算资源量,包括内存、显存以及处理时间。深度学习模型在处理较长的输入时,可能会遇到内存耗尽或计算过程耗时过长的问题。训练与推理中的挑战:在训练过程中,长文本可能导致梯度消失或爆炸,影响模型的训练稳定性和效果。而在推理过程中,长文本的生成可能导致错误率升高并延长生成所需时间。复杂上下文的捕捉:由于LLMs依赖于捕捉文本的上下文信息,长句子涉及的上下文通常更为复杂和深入。模型必须具备捕获并理解长句子中细微的语义和语法结构的能力,以确保输出的文本准确且流畅。5.  什么情况用 Bert 模型,什么情况用 LLaMA、ChatGLM 这类大模型,如何选择?Bert 模型:Bert 是一种预训练的语言模型,Bert 广泛应用于多种自然语言处理任务中,包括文本分类、实体识别和语义相似度评估等。Bert 是一个基于 Transformer 的 encoder 结构的模型,非常适用通用文本处理任务,通常在自然语言理解(NLU)方面表现优异。LLaMA 模型:该模型属于基座模型,擅长文本生成,训练材料广泛,达到14万亿 tokens。LLaMa 专长于常识推理、问答、数学推导、代码生成以及语言理解等任务。该模型是基于 Transformer 的 decoder 结构构建的。其训练数据主要基于英文及其他拉丁字母系语言,中文语料较少,因此原版 LLaMa 更适合处理英语文本生成任务。ChatGLM 模型:该模型属于 Chat 模型,适合用于开发聊天机器人和智能客服系统。若应用需要模型生成流畅、连贯的对话回答,同时还要处理对话上下文并产生多轮对话,这时可以选择 ChatGLM。ChatGLM 采用 Prefix decoder 架构,其训练数据包含相等比例的中文和英文文本,使得它能够有效处理中英文文本生成任务。6.  各个专业领域是否需要各自的大模型来服务?垂直领域知识:为了有效处理某一专业领域的文本,模型需要接受包含该领域特定知识和术语的训练。例如,医疗领域的大型模型通过接受医疗知识的专业训练,能够更精准地解读和生成相关医疗文本。行业特定语言表达和习惯用语:不同的专业领域往往发展出了独有的语言表达风格和习惯用语。通过对这些特征的深入训练,大型模型能够更好地理解并产生符合特定领域风格的文本,并且在生成内容上会更专业。行业文本需求差异:各个领域对文本处理的具体需求有差异。例如,金融行业可能更专注于处理数字和统计信息,而法律行业可能更注重法规内容和案例分析的深度解读。因此不同领域的大型模型需经过额外的训练。数据限制问题:在模型预训练阶段由于有些行业领域的数据资源可能较为匮乏,限制了通用大型模型在这些领域的能力。7.  如何让大模型处理更长的文本?分块处理:将较长的文本内容拆分为更短的段落来单独处理,有助于减轻模型在内存及计算资源方面的负担。在执行此方法时,为维护文本连贯性,可采用段落之间重叠的策略,即相邻的文本块有一部分是共享的。内容总结:将文本上文信息送入大模型进行总结或提取关键信息,然后送入模型中进行处理。可有效缩短处理的文本单位,同时保留原文信息。部分生成:当任务只需求模型产出文本的特定部分时,仅将相关部分的文本提供给模型作为上下文即可。同时可以采用 Streaming 的思想,减小脱离中心主旨的可能性。
0
0
0
浏览量2020
算法无限

LLM常见问题

掌握LLM核心概念与应用。从基础入门、进阶技巧,到微调、LangChain、文档对话、PDF解析、PEFT、Adapter-tuning、Prompting等高级应用,全面覆盖语言模型的各个方面
0
0
0
浏览量2552
算法无限

跟我一起学算法:什么是算法

算法的定义算法意思是“在计算或其他问题解决操作中遵循的一组有限规则或指令” 或“以有限数量的步骤解决数学问题的过程,经常涉及递归操作”。因此,算法是指解决特定问题的一系列有限步骤。算法的使用:算法在各个领域发挥着至关重要的作用,并且有很多应用。使用算法的一些关键领域包括:**计算机科学:**算法构成了计算机编程的基础,用于解决从简单的排序和搜索到人工智能和机器学习等复杂任务的问题。**数学:**算法用于解决数学问题,例如查找线性方程组的最优解或查找图中的最短路径。运筹学:算法用于运输、物流和资源配置等领域的优化和决策。**人工智能:**算法是人工智能和机器学习的基础,用于开发可执行图像识别、自然语言处理和决策等任务的智能系统。**数据科学:**算法用于分析、处理营销、金融和医疗保健等领域的大量数据并从中提取见解。这些只是算法众多应用的几个例子。随着新技术和领域的出现,算法的使用不断扩大,成为现代社会的重要组成部分。算法可以简单也可以复杂,具体取决于我们想要实现的目标。可以用烹饪新菜谱的例子来理解。要烹饪新菜谱,人们需要阅读说明和步骤,并按照给定的顺序一一执行。这样得到的结果是新菜煮得很完美。每次使用手机、电脑、笔记本电脑或计算器时,我们都在使用算法。同样,算法有助于完成编程任务以获得预期的输出。设计的算法与语言无关,即它们只是可以用任何语言实现的简单指令,但输出将与预期相同。需要什么算法?算法对于高效、有效地解决复杂问题是必要的。它们有助于实现流程自动化,并使流程更可靠、更快、更容易执行。算法还使计算机能够执行人类手动难以或不可能完成的任务。它们被用于数学、计算机科学、工程、金融等各个领域,以优化流程、分析数据、做出预测并提供问题的解决方案。算法有什么特点?因为人们不会遵循任何书面说明来烹饪食谱,而只会遵循标准食谱。同样,并非所有书面编程指令都是算法。对于某些指令来说,它必须具有以下特征:清晰明确:算法应该明确。它的每一步都应该在各个方面都清楚,并且必须只有一个含义。明确定义的输入:如果算法要求接受输入,那么它应该是明确定义的输入。它可能接受也可能不接受输入。明确定义的输出: 算法必须明确定义将产生什么输出,并且也应该明确定义。它应该至少产生 1 个输出。有限性: 算法必须是有限的,即它应该在有限时间后终止。可行: 算法必须简单、通用、实用,以便可以利用可用资源来执行。它不能包含一些未来的技术或任何东西。语言无关: 设计的算法必须与语言无关,即它必须只是可以用任何语言实现的简单指令,但输出将与预期相同。输入:算法有零个或多个输入。每个包含基本运算符的都必须接受零个或多个输入。输出:算法至少产生一个输出。每条包含基本运算符的指令都必须接受零个或多个输入。确定性: 算法中的所有指令必须明确、精确且易于解释。通过参考算法中的任何指令,人们可以清楚地理解要做什么。指令中的每个基本运算符都必须明确定义。有限性: 算法必须在所有测试用例中执行有限数量的步骤后终止。每条包含基本运算符的指令都必须在有限的时间内终止。没有基本条件的无限循环或递归函数不具有有限性。有效性: 算法必须使用非常基本、简单且可行的操作来开发,以便仅用纸和笔就可以追踪出来。算法的性质:它应该在有限时间后终止。它应该产生至少一个输出。它应该接受零个或多个输入。它应该是确定性的,意味着对于相同的输入情况给出相同的输出。算法中的每一步都必须是有效的,即每一步都应该做一些工作。算法类型:有多种类型的算法可用。一些重要的算法是:1.暴力算法这是解决问题的最简单的方法。当我们发现问题时,暴力算法是第一种发现问题的方法。2.递归算法:递归算法基于递归。在这种情况下,一个问题被分成几个子部分并一次又一次地调用相同的函数。3.回溯算法:回溯算法通过在所有可能的解决方案中搜索来构建解决方案。使用该算法,我们继续按照标准构建解决方案。每当一个解决方案失败时,我们都会追溯到下一个解决方案的故障点,并继续此过程,直到找到解决方案或照顾所有可能的解决方案。4.搜索算法:搜索算法是用于从特定数据结构中搜索元素或元素组的算法。根据其方法或应在其中找到元素的数据结构,它们可以是不同的类型。5.排序算法:排序就是将一组数据按照需要以特定的方式排列。帮助执行此功能的算法称为排序算法。通常,排序算法用于以递增或递减的方式对数据组进行排序。6.哈希算法:哈希算法的工作原理与搜索算法类似。但它们包含一个带有键 ID 的索引。在散列中,密钥被分配给特定数据。7.分治算法:该算法将问题分解为子问题,解决单个子问题,然后合并解决方案以获得最终解决方案。它由以下三个步骤组成:划分解决结合8.贪心算法:在这种类型的算法中,解决方案是逐个部分构建的。下一部分的解决方案是基于下一部分的直接利益而构建的。将选择提供最大收益的解决方案作为下一部分的解决方案。9.动态规划算法:该算法采用使用已经找到的解的概念来避免对问题的同一部分进行重复计算。它将问题分成较小的重叠子问题并解决它们。10.随机算法:在随机算法中,我们使用随机数,因此它可以立即带来好处。随机数有助于确定预期结果。算法的优点:这很容易理解。算法是给定问题的解决方案的逐步表示。在算法中,问题被分解为更小的部分或步骤,因此程序员更容易将其转换为实际的程序。算法的缺点:编写算法需要很长时间,因此很耗时。通过算法理解复杂的逻辑可能非常困难。分支和循环语句很难在算法(imp)中显示。如何设计算法?要编写算法,需要满足以下条件:该算法要解决的问题即明确的问题定义。解决问题时必须考虑问题的约束条件。解决问题所需的输入。当问题解决时,输出是预期的。该问题的解决方案是在给定的约束范围内。然后借助上述参数编写算法来解决问题。示例: 考虑将三个数字相加并打印总和的示例。第 1 步:满足先决条件如上所述,要编写算法,必须满足其先决条件。该算法要解决的问题:将 3 个数字相加并打印它们的和。解决问题时必须考虑的问题约束:数字只能包含数字,不能包含其他字符。**解决问题所需的输入:**要相加的三个数字。**解决问题时预期的输出:**作为输入的三个数字的总和,即单个整数值。**在给定的约束下,该问题的解决方案:**解决方案包括将 3 个数字相加。它可以借助“+”运算符、按位或任何其他方法来完成。第 2 步:设计算法现在让我们在上述先决条件的帮助下设计算法:将 3 个数字相加并打印其总和的算法:开始 声明 3 个整型变量 num1、num2 和 num3。 将要相加的三个数字分别作为变量 num1、num2 和 num3 的输入。 声明一个整型变量 sum 来存储 3 个数字的总和。 将 3 个数字相加并将结果存储在变量 sum 中。 打印变量 sum 的值 结束步骤 3:通过实现来测试算法。为了测试该算法,让我们用 Javascript 语言来实现。// Javascript程序用于添加三个数字 // 借助上述设计的算法 // 从en到zh-CN获取输入的3个数字的变量 let num1 = 0, num2 = 0, num3 = 0; // 用于存储结果总和的变量 let sum = 0; // 将 3 个数字作为输入 console.log("Enter the 1st number: "); num1 = parseInt(prompt()); console.log(" " + num1 + "<br>"); console.log("Enter the 2nd number: "); num2=parseInt(prompt()); console.log(" " + num2 + "<br>"); console.log("Enter the 3rd number: "); num3=parseInt(prompt()); console.log(" " + num3); // 使用 + 运算符计算总和 // 并将其存储在变量 sum 中 sum = num1 + num2 + num3; console.log("<br>Sum of the 3 numbers is: " + sum); 输出输入第一个数字:0 输入第二个数字:0 输入第三个数字:-1577141152 3个数字的总和是:-1577141152 这是代码的逐步算法:声明三个变量num1、num2和num3来存储要相加的三个数字。声明一个变量 sum 来存储三个数字的总和。使用 prompt 语句提示用户输入第一个数字,读取第一个数字并将其存储在num1中。使用 prompt 语句提示用户输入第二个数字,并将其存储在num2中。使用prompt语句提示用户输入第三个数字,并存储num3中的第三个数字。使用 + 运算符计算三个数字的总和并将其存储在 sum 变量中。使用 console.log() 语句打印三个数字的总和。时间复杂度: O(1)辅助空间: O(1)一个问题,多种解决方案:一种算法的解决方案可以是也不能是多个。这意味着在实现算法时,可以有不止一种方法来实现它。例如,在上面的 3 个数字相加的问题中,可以通过多种方式计算总和:+ 运算符按位运算符如何分析算法?一个好的标准算法必须是高效的。因此,必须检查和维护算法的效率。它可以分为两个阶段:1.先验分析:先验分析意味着在实现之前检查算法。在此,当以理论步骤的形式编写算法时对其进行检查。算法的效率是通过假设所有其他因素(例如处理器速度)恒定并且对实现没有影响来衡量的。这通常由算法设计者完成。该分析与编译器的硬件类型和语言无关。它给出了程序复杂性的近似答案。2、事后分析:因此,事后分析意味着在算法实现后对其进行检查。在此,通过用任何编程语言实现并执行该算法来检查该算法。此分析有助于获得有关正确性(对于每个可能的输入,是否显示/返回正确的输出)、所需空间、消耗时间等的实际分析报告。也就是说,它取决于语言编译器和所使用的硬件类型。什么是算法复杂度以及如何找到它?算法根据其消耗的空间和时间量被定义为复杂的。因此,算法的复杂性是指执行并获得预期输出所需的时间以及存储所有数据(输入、临时数据和输出)所需的空间。因此,这两个因素定义了算法的效率。算法复杂度的两个因素分别是:时间因素:通过统计排序算法中的比较等关键操作的数量来衡量时间。空间因子:空间是通过计算算法运行/执行所需的最大内存空间来测量的。因此算法的复杂度可以分为两类:1.空间复杂度:算法的空间复杂度是指算法存储变量和得到结果所需的内存量。这可以用于输入、临时操作或输出。如何计算空间复杂度?算法的空间复杂度是通过确定以下两个组成部分来计算的:固定部分: 这是指算法所需的空间。例如,输入变量、输出变量、程序大小等。可变部分: 这是指根据算法的实现可以不同的空间。例如,临时变量、动态内存分配、递归堆栈空间等。因此任何算法 P 的空间复杂度S(P)都是S(P) = C + SP(I),其中 C 是固定部分,S(I ) 是算法的变量部分,它取决于实例特征 I。示例: 考虑以下线性搜索算法步骤1:开始 步骤2:获取arr中数组的n个元素以及x中要查找的数字 步骤3:从arr[]最左边的元素开始,将x与arr[]的每个元素一一比较 步骤4 :如果 x 与某个元素匹配,则打印 True。 步骤 5:如果 x 与任何元素都不匹配,则打印 False。 步骤6:结束 这里,有2个变量arr[]和x,其中arr[]是n个元素的可变部分,x是固定部分。因此 S(P) = 1+n。因此,空间复杂度取决于 n(元素数量)。现在,空间取决于给定变量的数据类型和常量类型,并且空间将相应地相乘。2. 时间复杂度: 算法的时间复杂度是指算法执行并得到结果所需的时间量。这可以用于正常操作、条件 if-else 语句、循环语句等。时间复杂度如何计算算法的时间复杂度也是通过确定以下两个组成部分来计算的:恒定时间部分: 任何只执行一次的指令都属于该部分。例如,输入、输出、if-else、switch、算术运算等。可变时间部分: 任何执行多次(例如 n 次)的指令都属于此部分。例如循环、递归等。因此任何算法 P 的时间复杂度 都是 T(P) = C + TP(I),其中 C 是算法的常数时间部分,TP(I) 是算法的可变部分,即取决于实例特征 I。示例: 在上面的线性搜索算法中,时间复杂度计算如下:步骤 1: – 恒定时间 步骤 2: – 可变时间(采用 n 个输入) 步骤 3: – 可变时间(直到数组 (n) 的长度或找到的元素的索引) 步骤 4: – 恒定时间 步骤 5: – 恒定时间 步骤 6: – 恒定时间 因此,T(P) = 1 + n + n(1 + 1) + 1 = 2 + 3n,可以表示为 T(n)。如何表达一个算法?自然语言: 这里我们用自然英语表达算法。从中理解算法太难了。流程图: 在这里,我们通过图形/图片表示来表达该算法。它比自然语言更容易理解。伪代码: 在这里,我们以注释和用简单英语编写的信息文本的形式表达算法,这与真实代码非常相似,但由于它没有像任何编程语言一样的语法,因此无法编译或由计算机解释。这是表达算法的最佳方式,因为即使是具有一定学校知识的外行也可以理解它。
0
0
0
浏览量773
算法无限

[文档级关系抽取|ACL论文]文档级关系抽取中语言理解的基础模型

BackGround过去的工作大多数都是从单个句子中收获更多的关系,然而如今要采用多个句子作为一个整体来获得更多的关系,即文档级关系抽取(DocRE),因为需要综合文档中的所有信息,所以文档级关系抽取是更有挑战性的。过去存在的问题一种常见的评估方法是测量整个测试集的平均误差,这忽略了模型可以根据错误的特征做出正确预测的情况。如上图,Vera Cáslavská和Czech之间的关系,机器所考虑的决策方式与人类的完全不同.文章工作内容简述1. 在DocRED上进一步注释成了DocREDHWE​2. 采用特征归因法观察模型在推理过程中考虑的最关键的词,发现模型总是将不相关的词语虚假的关联起来,形成了无法解释的决策3. 证明了模型中的决策方式是不可靠的,设计了6种RE攻击方式证明4. 引入平均精度(MAP)指标评价模型的理解能力和推理能力,由此区分因伪相关性引起的能提升和因理解能力引起的性能提升,最终发现MAP越高,模型的鲁棒性和泛化能力越强· DocRED: 大型文档级关系抽取数据集· DocREDHWE​: HWE表示人类注释的词级证据,human-annotated word-level evidence过去的文档级关系识别主要分为图的方法和基于变换器的方法· 基于图的:基于图的方法利用上下文的结构信息构造各种图,并通过图中的路径对推理过程的多次反射进行建。DocuNet(SOTA)中构建了一个实体级的关系图,然后利用图上的U形网络来捕获全局相互依赖性· 基于Transformerbased方法的:执行推理隐式识别的长距离令牌依赖通过transformers。ATLOP通过相关上下文增强了实体对的嵌入,并为多标签分类引入了一个可学习的阈值。工作内容1 : DocRED_HWE难点:第一个挑战来自原始数据集中的注释工件,第二个挑战在于单个关系的多个推理路径解决方式:采用细粒度的单词级的证据,并且提出了一个新的检查规则,用于二次推理,只有被验证两次才会被采用· 来自原始数据集中的注释工件:注释器可以使用先验知识通过实体名称来标记关系,而不需要观察上下文。例如,给定一个跨句实体对“Obama”和“the US”的文档,尽管缺乏理论依据注释者还是倾向于标注“president of”。可以通过注释细粒度的单词级证据来自然地解决· 需要注释器对所有推理路径中的单词进行注释。当注释者通过相应的证据词成功推理出某种关系时,其他推理路径中的证据词往往会被忽略。为了解决这一问题,对每个文档采用了多个(滚动)标注,并提出了检查规则:给定一个文档,之前标注的关系被屏蔽,标注者将无法对关系进行推理。如果违反规则,新的证据词将被标注。更新将由下一个注释器检查,直到没有更新发生。所有注释的证据词至少被验证两次。并且作者最后对于结果进行了一定的人工筛选工作内容2 : 发现模型总是将不相关的词语虚假的关联起来,形成了无法解释的决策建立模型验证方式实验与分析位置误导使用IG来描述模型的决策规则。 ATLOPRoBERTaATLOP _ RoBERTaATLOPR​oBERTa在DocRED验证集中不同位置的上平均分布,归因DocuNet也会出现类似的曲线。 如上图所示,特定位置的token信息比其他位置的words信息的affect更明显。也就是说,模型根据单词在文档中的位置来区分单词,原因应该为:· 在学习过程中扭曲了位置特征,将其与预测结果虚假地关联了起来· 位置Embbeding被错误的训练(没监督),偏离了表示位置信息的原始功能 由此说明,泛化能力弱狭隘的推理用推理正确关系所需要的单词,代表模型的推理范围。 设模型为"A X B",A、B为实体,X为推理关系所需要的单词/单词序列,设X为$r_AB$的前k个赋值token,token的顺序与原文相同,DocRED性能如上图所示。· 添加最高属性(我理解为强属性)的单词会导致性能下降· position的权重比较大· 当只给出实体名称不给上下文,性能可以达到原模型的85%(53%的f1分数) 由此推出,模型在一个比较狭隘的范围内推理虚假相关 选择前5个具有属性的单词来显示模型的证据单词。可以发现,很大程度上依赖了一些非因果标记(如实体名称和标点符号),这不利于深度学习。比如逗号就起到了很大作用(SEP和CLS可以证明为无操作的操作符)。因此,该模型不能被部署到现实场景中,因为非因果令牌很容易被替换掉。原因分析 · U是因果关系确定的证据词· Y是预测词· X是文档· 给定X和A,模型学习H和Y之间的伪相关。基于transformer的预训练语言模型,都希望在给定上下文X的情况下,提高当前单词的概率Y,上下文应该由P(Y|X)表示,但学习的是P(Y|X, A),其中A表示为对采样过程的访问,从而导致有偏差。如上图的有向无环图所示。其中H为有语法意义的(如the,逗号),U为相对不太可能访问采样过程或上下文。目前,Y的语义很大程度依赖着有明确语义的词,即U->Y,他们的组合形成了自然的语言表达,其过程可以使用A->X表述,其中A决定了单词在上下文的分布。目前来说,PLM训练后的模型,倾向于将虚假的信息与关系关联起来。工作内容3 : 针对SOTA模型的攻击证明:1. 模型的决策规则与人类的不同2. 这种差异会严重损害鲁棒性和泛化能力 由十字架标注的为有监督的训练。· P2N:消极预测从积极预测变为原始积极预测· UP:不变的积极预测变为原始积极预测的比例Word-level Evidence Attacks1. 蒙面词级攻击,所有被人标注的Word-level Evidence(HWE)都被直接Masked(Mask)2. 反义词替换,HWE中一个词被一个反义词替换(ASA)3. 同义词替换,HWE中一个词被一个同义词替换(SSA)结果如上图所示,在Mask攻击下,模型仍预测相同的关系【但是性能下降了79%】,在ASA攻击下性能下降了90%,和SSA性能与ASA大致相同。可证明鲁棒性很差。Entite Name Attacks1. 屏蔽实体攻击(EM),直接屏蔽实体名称2. 随机打乱实体攻击(ER),随机置换每个文档中的实体名称3. 非分发(OOD)实体替换攻击(ES),使用训练数据中没有的实体名称来替换结果如上图,ES下降最严重,从67.12->7.57工作内容4 : 新的评价指标MAP在上述中,证明了模型应该学习人类的决策规则。由此提出一个新的评价指标MAP: · 1t(i)表示预测第t个相关事实的第i个重要字的指示函数· K的选择,类似于推荐系统中的评价指标,取决于RE从业者的需求,通常设置为1、10、50和100。如果单词在人类标注的单词级证据中,则1t(i)的输出值等于1。否则等于0个人思考对于以往的深度学习中,大多都是黑盒训练,每次看别人的论文也是,往往都不知道为什么就起作用了,故都是认为就把上下文的关系或者别的之类的token联系在一起用了而已,就像世界十大难题中的中文房间问题一样,就算给出正确的结果,不知道里面的人到底会不会中文。对于这篇文章,完全揭示了当前文档级关系抽取(甚至句子级关系抽取)的现状,知识把杂七杂八的东西放到了池子中去学习,让模型只能在学习到的数据集中有比较好的效果。对于以后的实验中,针对于这一部分,可以优化。
0
0
0
浏览量487
算法无限

NLP实践-HugginFace学习记录

BertTokenizer分词和编码5个字有7个编码,原因为头和尾分别有个[cls]及[sep],[CLS] 和 [SEP] 是 BERT 中的两个特殊标记符号,在 BERT 的输入文本中起到特殊的作用。· [CLS] 是 "classification" 的缩写,在文本分类任务中,它通常表示句子或文档的开头。在 BERT 中,[CLS] 对应着输入文本中第一个词的词向量,输出层中的第一个神经元通常会被用来预测文本的类别。· [SEP] 是 "separator" 的缩写,它通常表示句子或文档的结尾。在 BERT 中,[SEP] 对应着输入文本中最后一个词的词向量,它的作用是用来分割不同的句子。例如,在 BERT 中处理句子对时,两个句子之间通常会插入一个 [SEP] 来表示它们的分界点。· [UNK]标志指的是未知字符· [MASK] 标志用于遮盖句子中的一些单词,将单词用 [MASK] 遮盖之后,再利用 BERT 输出的 [MASK] 向量预测单词是什么。单句分词:tokenizer = BertTokenizer.from_pretrained('./huggingface/bert-base-chinese') # 分词并编码 token = tokenizer.encode('北京欢迎你') print(token) # [101, 1266, 776, 3614, 6816, 872, 102] # 简写形式 token = tokenizer(['北京欢迎你', '为你开天辟地'], padding=True, return_tensors='pt') {'input_ids': tensor([[ 101, 1266, 776, 3614, 6816, 872, 102, 0], [ 101, 711, 872, 2458, 1921, 6792, 1765, 102]]), 'token_type_ids': tensor([[0, 0, 0, 0, 0, 0, 0, 0], [0, 0, 0, 0, 0, 0, 0, 0]]), 'attention_mask': tensor([[1, 1, 1, 1, 1, 1, 1, 0], [1, 1, 1, 1, 1, 1, 1, 1]])} # 解码 print(tokenizer.decode([101, 1266, 776, 3614, 6816, 872, 102])) # 查看特殊标记 print(tokenizer.special_tokens_map) # 查看特殊标记对应id print(tokenizer.encode(['[UNK]', '[SEP]', '[PAD]', '[CLS]', '[MASK]'], add_special_tokens=False)) # [100, 102, 0, 101, 103]批处理padding = True,按最长的字段进行填充,然后使用return_tensors转化为tensor的格式,作为模型的输入,给pytorch作为输入# 等长填充 batch_token1 = tokenizer(['北京欢迎你', '为你开天辟地'], padding=True, return_tensors='pt') print(batch_token1) print(batch_token1['input_ids']){'input_ids': tensor([[ 101, 1266, 776, 3614, 6816, 872, 102, 0], [ 101, 711, 872, 2458, 1921, 6792, 1765, 102]]), 'token_type_ids': tensor([[0, 0, 0, 0, 0, 0, 0, 0], [0, 0, 0, 0, 0, 0, 0, 0]]), 'attention_mask': tensor([[1, 1, 1, 1, 1, 1, 1, 0], [1, 1, 1, 1, 1, 1, 1, 1]])} tensor([[ 101, 1266, 776, 3614, 6816, 872, 102, 0], [ 101, 711, 872, 2458, 1921, 6792, 1765, 102]])其中:token_type_ids指代的第几个句子。例如,考虑以下两个句子: 句子1: "What is the weather like today?" 句子2: "Will it rain later?"在经过预处理后,这两个句子可能被编码为以下tokens:[CLS], What, is, the, weather, like, today, ?, [SEP], Will, it, rain, later, ?, [SEP]在这个例子中,[CLS]和[SEP]是特殊的tokens,[CLS]用于表示句子的开头,[SEP]用于分隔两个句子。 token_type_ids列表的对应值为: [0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1]句子太长的情况下可以使用截取。比如下方就为截取5个字符(包含CLS【101】与SEP【102】)# 截断 batch_token2 = tokenizer(['北京欢迎你', '为你开天辟地'], max_length=5, truncation=True) print(batch_token2){'input_ids': [[101, 1266, 776, 3614, 102], [101, 711, 872, 2458, 102]], 'token_type_ids': [[0, 0, 0, 0, 0], [0, 0, 0, 0, 0]], 'attention_mask': [[1, 1, 1, 1, 1], [1, 1, 1, 1, 1]]}填充到固定的长度,padding=‘max_length'传参,填充到最大长度# 填充到指定长度,超过的截断 batch_token3 = tokenizer(['北京欢迎你', '为你开天辟地'], max_length=10, truncation=True, padding='max_length') print(batch_token3){'input_ids': [[101, 1266, 776, 3614, 6816, 872, 102, 0, 0, 0], [101, 711, 872, 2458, 1921, 6792, 1765, 102, 0, 0]], 'token_type_ids': [[0, 0, 0, 0, 0, 0, 0, 0, 0, 0], [0, 0, 0, 0, 0, 0, 0, 0, 0, 0]], 'attention_mask': [[1, 1, 1, 1, 1, 1, 1, 0, 0, 0], [1, 1, 1, 1, 1, 1, 1, 1, 0, 0]]}词向量编码from transformers import BertModel from transformers import logging # 启动的时候会有一些警告,使用log屏蔽掉,防止影响调试 logging.set_verbosity_error() model = BertModel.from_pretrained('./huggingface/bert-base-chinese') encoded = model(batch_token1['input_ids']) print(encoded) encoded_text = encoded[0] print(encoded_text.shape)last_hidden_state 为隐层,即词向量,pooler_outputer为做分类用的,后面的就是几个状态。我们要的就是第一个参数(编码结构),即encoded[0],shape为(2,8,768)指代为,2个句子【2个batch】、每个句子填充长度为8个、bert编码之后的768维的向量。BaseModelOutputWithPoolingAndCrossAttentions(last_hidden_state=tensor([[[-0.2815, 0.6079, 0.3920, ..., 0.4682, 0.1664, -0.1104], [-0.4996, 0.4137, 0.4482, ..., -0.5986, -0.3632, -0.0424], [ 0.0472, 0.4009, -0.2222, ..., 0.1105, 0.5548, -0.1777], ..., [ 0.6923, 0.5521, -0.2580, ..., 0.0042, 0.4254, -0.6365], [-0.3318, 0.3553, 0.4314, ..., 0.0181, -0.1999, -0.2506], [-0.2052, 0.2994, -0.0189, ..., -0.0735, -0.3766, -0.4286]], [[ 0.2887, 0.6017, 0.4943, ..., 0.0903, 0.0543, -0.1163], [-0.2048, 0.5193, 0.9473, ..., -0.8814, -0.5178, 0.1631], [ 0.7151, 0.0340, -0.4089, ..., -0.2059, -0.1003, -0.5724], ..., [ 0.6159, -0.1950, 0.9022, ..., -0.5146, 0.6748, -1.2145], [ 1.2560, 0.3676, 0.1448, ..., -0.3056, 0.2488, 0.1433], [-0.2018, 0.2100, 0.3642, ..., -0.7199, 0.0571, -0.2698]]], grad_fn=<NativeLayerNormBackward0>), pooler_output=tensor([[ 0.9986, 1.0000, 0.9751, ..., -0.9984, -0.9446, 0.9237], [ 0.9998, 1.0000, 0.9957, ..., -0.9992, -0.9960, 0.9914]], grad_fn=<TanhBackward0>), hidden_states=None, past_key_values=None, attentions=None, cross_attentions=None)torch.Size([2, 8, 768])BertTokenizer分词不可逆问题目前还没有支持中英文混搭的预训练模型,直接用中文模型进行编码,英文部分会变成[UNK]标记,进而导致BertTokenizer分词不可逆问题。使用offset_mapping方法,从原始文本中找到关系解决该问题from transformers import BertTokenizer tokenizer = BertTokenizer.from_pretrained('./huggingface/bert-base-chinese') text = '周杰伦Jay专辑' print(tokenizer.tokenize(text)) exit()tokenize()方法返回分词分完之后的结果:['周', '杰', '伦', '[UNK]', '专', '辑']tokened = tokenizer(text) input_ids = tokened['input_ids'] print(input_ids)转化为Id后:[101, 1453, 3345, 840, 100, 683, 6782, 102]# subject实体 sub_pos = [1, 3] # 周杰伦 sub_ids = [id for k,id in enumerate(input_ids) if k>=sub_pos[0] and k<=sub_pos[1]] print(sub_ids) sub_text = tokenizer.decode(sub_ids).replace(' ', '') print(sub_text)在id在遍历input_ids的情况下,1<= k <= 3,对于第4行的replace是因为转化后会生成空格=》周_杰_轮[1453, 3345, 840] 周杰伦# object 实体 obj_pos = [4, 4] # Forever obj_ids = [id for k,id in enumerate(input_ids) if k>=obj_pos[0] and k<=obj_pos[1]] print(obj_ids) obj_text = tokenizer.decode(obj_ids).replace(' ', '') print(obj_text)经过编码之后生成id后再反推找不到原文内容,需要解决数据丢失的问题[100] [UNK]从原始文本中找实体from transformers import BertTokenizerFast tokenizer = BertTokenizerFast.from_pretrained('./huggingface/bert-base-chinese') text = '周杰伦Jay专辑' tokened = tokenizer(text, return_offsets_mapping=True) # 第二个参数为了把分词之后的位置也返回 print(tokened) offset_mapping = tokened['offset_mapping'] head, tail = offset_mapping[4] print(text[head:tail])多了一个offset_mapping,表分词的位置。{'input_ids': [101, 1453, 3345, 840, 100, 683, 6782, 102], 'token_type_ids': [0, 0, 0, 0, 0, 0, 0, 0], 'attention_mask': [1, 1, 1, 1, 1, 1, 1, 1], 'offset_mapping': [(0, 0), (0, 1), (1, 2), (2, 3), (3, 6), (6, 7), (7, 8), (0, 0)]} Jay
0
0
0
浏览量2012
算法无限

NLP实践-构建Dataset数据集和Bert分词

构建Dataset数据集使用的数据集为DuIE2.0,它是业界规模最大的中文关系抽取数据集,其schema在传统简单关系类型基础上添加了多元复杂关系类型,此外其构建语料来自百度百科、百度信息流及百度贴吧文本,全面覆盖书面化表达及口语化表达语料,能充分考察真实业务场景下的关系抽取能力。文件的加载与分词1. 添加配置项# config.py TRAIN_JSON_PATH = './data/input/duie/duie_train.json' TEST_JSON_PATH = './data/input/duie/duie_test.json' DEV_JSON_PATH = './data/input/duie/duie_dev.json' BERT_MODEL_NAME = 'bert-base-chinese'2. 新建文件# utils.py import torch.utils.data as data import pandas as pd import random from config import * import json from transformers import BertTokenizerFast3. 加载关系表def get_rel(): df = pd.read_csv(REL_PATH, names=['rel', 'id']) return df['rel'].tolist(), dict(df.values) id2rel, rel2id = get_rel() print(id2rel) # 因为list本身的位置就有id的特性 print(rel2id) exit()​ ['毕业院校', '嘉宾', '配音', '主题曲', '代言人', '所属专辑'.....​ {'毕业院校': 0, '嘉宾': 1, '配音': 2, '主题曲': 3, '代言人': 4, .....4. Dataset初始化class Dataset(data.Dataset): def __init__(self, type='train'): # type类型为加载的哪个文件 super().__init__() _, self.rel2id = get_rel() # 加载文件 if type == 'train': file_path = TRAIN_JSON_PATH elif type == 'test': file_path = TEST_JSON_PATH elif type == 'dev': file_path = DEV_JSON_PATH with open(file_path) as f: self.lines = f.readlines() # 按行去读取文件,拿到训练集的长度==》lines # 加载bert self.tokenizer = BertTokenizerFast.from_pretrained(BERT_MODEL_NAME) def __len__(self): return len(self.lines) def __getitem__(self, index): line = self.lines[index] info = json.loads(line) tokenized = self.tokenizer(info['text'], return_offsets_mapping=True) # 第一个参数为要转换的文本,第二个参数是为了中英文混搭,使用偏移量记录词 info['input_ids'] = tokenized['input_ids'] # 追加给info info['offset_mapping'] = tokenized['offset_mapping'] print(info) exit()5. 尝试加载数据集if __name__ == '__main__': dataset = Dataset() loader = data.DataLoader(dataset) print(iter(loader).next()) # 指针往后挪动一位,取第一条他的数据解析json数据并计算实体位置解析基本信息def parse_json(self, info): text = info['text'] input_ids = info['input_ids'] dct = { 'text': text, 'input_ids': input_ids, 'offset_mapping': info['offset_mapping'], 'sub_head_ids': [], 'sub_tail_ids': [], 'triple_list': [], 'triple_id_list': [] } for spo in info['spo_list']: subject = spo['subject'] object = spo['object']['@value'] predicate = spo['predicate'] dct['triple_list'].append((subject, predicate, object)) # @todo exit(dct) return dct计算实体位置# 计算 subject 实体位置 tokenized = self.tokenizer(subject, add_special_tokens=False) sub_token = tokenized['input_ids'] sub_pos_id = self.get_pos_id(input_ids, sub_token) if not sub_pos_id: continue sub_head_id, sub_tail_id = sub_pos_id # 计算 object 实体位置 tokenized = self.tokenizer(object, add_special_tokens=False) obj_token = tokenized['input_ids'] obj_pos_id = self.get_pos_id(input_ids, obj_token) if not obj_pos_id: continue obj_head_id, obj_tail_id = obj_pos_id # 数据组装dct['sub_head_ids'].append(sub_head_id) dct['sub_tail_ids'].append(sub_tail_id) dct['triple_id_list'].append(( [sub_head_id, sub_tail_id], self.rel2id[predicate], [obj_head_id, obj_tail_id], ))位置计算方法source为原始的文本,elem为当前的subject的token。遍历原始的id,滑动窗口,每次找一段儿去校对,每次找elem对应长度的值,就算超过了length那也匹配不上。def get_pos_id(self, source, elem): for head_id in range(len(source)): tail_id = head_id + len(elem) if source[head_id:tail_id] == elem: return head_id, tail_id - 1完整实现:def parse_json(self, info): text = info['text'] input_ids = info['input_ids'] dct = { 'text': text, 'input_ids': input_ids, 'offset_mapping': info['offset_mapping'], 'sub_head_ids': [], 'sub_tail_ids': [], 'triple_list': [], 'triple_id_list': [] } for spo in info['spo_list']: subject = spo['subject'] object = spo['object']['@value'] predicate = spo['predicate'] dct['triple_list'].append((subject, predicate, object)) # 计算 subject 实体位置 tokenized = self.tokenizer(subject, add_special_tokens=False) sub_token = tokenized['input_ids'] sub_pos_id = self.get_pos_id(input_ids, sub_token) if not sub_pos_id: continue sub_head_id, sub_tail_id = sub_pos_id # 计算 object 实体位置 tokenized = self.tokenizer(object, add_special_tokens=False) obj_token = tokenized['input_ids'] obj_pos_id = self.get_pos_id(input_ids, obj_token) if not obj_pos_id: continue obj_head_id, obj_tail_id = obj_pos_id # 数据组装 dct['sub_head_ids'].append(sub_head_id) dct['sub_tail_ids'].append(sub_tail_id) dct['triple_id_list'].append(( [sub_head_id, sub_tail_id], self.rel2id[predicate], [obj_head_id, obj_tail_id], )) exit(dct) return dct def get_pos_id(self, source, elem): for head_id in range(len(source)): tail_id = head_id + len(elem) if source[head_id:tail_id] == elem: return head_id, tail_id - 1
0
0
0
浏览量2016
算法无限

跟我一起学算法:选择排序

什么是排序?排序算法用于根据元素上的比较运算符重新排列给定的数组或元素列表。比较运算符用于决定相应数据结构中元素的新顺序。例如:下面的字符列表按其 ASCII 值的升序排序。也就是说,具有较小 ASCII 值的字符将比具有较高 ASCII 值的字符先放置。选择排序选择排序是一种简单而高效的排序算法,其工作原理是重复从列表的未排序部分中选择最小(或最大)元素并将其移动到列表的已排序部分。“选择排序”算法工作原理让我们以以下数组为例:arr[] = {64, 25, 12, 22, 11}第一遍:对于排序数组中的第一个位置,从索引 0 到 4 顺序遍历整个数组。当前存储64的第一个位置,遍历整个数组后很明显11是最低值。因此,将 64 替换为 11。一次迭代后, 11(恰好是数组中的最小值)往往会出现在排序列表的第一个位置。第二遍:对于存在 25 的第二个位置,再次按顺序遍历数组的其余部分。遍历完后,我们发现12是数组中倒数第二小的值,它应该出现在数组的第二位,因此交换这些值。第三遍:现在,对于第三个位置,其中存在**25,**再次遍历数组的其余部分并找到数组中存在的第三个最小值。遍历时,22是第三个最小值,它应该出现在数组中的第三个位置,因此将22与第三个位置上的元素交换。第四遍:类似地,对于第四个位置,遍历数组的其余部分并找到数组中第四小的元素由于25是第四低的值,因此它将排在第四位。第五遍:最后,数组中存在的最大值自动放置在数组的最后一个位置结果数组是排序后的数组。代码实现:javascript<script> // 选择排序的JavaScript程序实现 function swap(arr,xp, yp) { var temp = arr[xp]; arr[xp] = arr[yp]; arr[yp] = temp; } function selectionSort(arr, n) { var i, j, min_idx; // 逐个移动未排序子数组的边界 for (i = 0; i < n-1; i++) { // 在未排序的数组中找到最小元素 min_idx = i; for (j = i + 1; j < n; j++) if (arr[j] < arr[min_idx]) min_idx = j; // 将找到的最小元素与第一个元素交换位置 swap(arr,min_idx, i); } } function printArray( arr, size) { var i; for (i = 0; i < size; i++) console.log(arr[i] + " "); } var arr = [64, 25, 12, 22, 11]; var n = 5; selectionSort(arr, n); document.write("Sorted array: <br>"); printArray(arr, n); </script> golangpackage main import ( "testing" "github.com/stretchr/testify/assert" ) type ArrT interface { ~int | ~int8 | ~int16 | ~int32 | ~int64 | ~float32 | ~float64 } // SelectSort 选择排序 func SelectSort[T ArrT](arr []T) []T { var length = len(arr) for i := 0; i < length-1; i++ { var minIndex = i for j := i + 1; j < length; j++ { if arr[minIndex] > arr[j] { minIndex = j } } arr[minIndex], arr[i] = arr[i], arr[minIndex] } return arr } func Test_SelectSort(t *testing.T) { var arr = []int{64, 25, 12, 22, 11} var except = []int{11, 12, 22, 25, 64} assert.Equal(t, except, SelectSort[int](arr)) } 输出排序数组: 11 12 22 25 64选择排序的复杂度分析时间复杂度:选择排序的时间复杂度为O(N 2 ),因为有两个嵌套循环:一个循环逐一选择 Array 的元素 = O(N)另一个循环将该元素与每个其他数组元素进行比较 = O(N)因此总体复杂度 = O(N) * O(N) = O(N*N) = O(N 2 )辅助空间: O(1),因为在交换数组中的两个值时,唯一使用的额外内存用于临时变量。选择排序不会进行超过 O(N) 的交换,并且在内存写入成本高昂时非常有用。选择排序算法的优点简单易懂。适用于小型数据集。选择排序算法的缺点在最坏和平均情况下,选择排序的时间复杂度为 O(n^2)。在大型数据集上效果不佳。不保留具有相同键的项目的相对顺序,这意味着它不稳定。选择排序的常见问题Q1. 选择排序算法稳定吗?选择排序算法的默认实现并不稳定。Q2。选择排序算法是否到位?是的,选择排序算法是一种原地算法,因为它不需要额外的空间。
0
0
0
浏览量933
算法无限

跟我一起学算法:数据结构和算法-栈

堆栈数据结构什么是堆栈?堆栈是一种线性数据结构,遵循特定的操作执行顺序。顺序可以是 LIFO(后进先出)或 FILO(先进后出)。LIFO 意味着最后插入的元素最先出现,而 FILO 意味着最先插入的元素最后出现。现实生活中有很多堆栈的例子。考虑一个在食堂里盘子叠在一起的例子。位于顶部的盘子是第一个被移除的盘子,即放置在最底部位置的盘子在堆叠中保留最长的时间。因此,可以简单地看出遵循LIFO(后进先出)/FILO(先进后出)顺序。为了实现栈,需要维护一个指向栈顶的指针,栈顶是最后插入的元素,因为我们只能访问栈顶的元素。LIFO(后进先出):该策略规定最后插入的元素将首先出现。您可以将一堆相互叠放的盘子作为现实生活中的示例。我们最后放置的盘子位于顶部,并且由于我们移除了顶部的盘子,所以我们可以说最后放置的盘子最先出现。堆栈的基本操作为了在堆栈中进行操作,向我们提供了某些操作。push() 将一个元素插入栈中pop() 从堆栈中删除一个元素top() 返回栈顶元素。如果堆栈为空,isEmpty()返回 true,否则返回 false。size() 返回堆栈的大小。push给栈里面添加一个元素, 如果栈满后, 则称为溢出条件伪代码 begin   if stack is full     return   endif  else     increment top   stack[top] assign value  end else  end procedurepop(弹出元素)从栈中移除元素, 这些元素按照入栈的反方向一次出栈.伪代码 begin   if stack is empty     return   endif  else   store value of stack[top]   decrement top   return value  end else  end procedureTop (返回栈顶元素)isEmpty:判断栈是否为空,如果为空返回 true, 否则返回 false实际理解堆栈现实生活中有很多堆栈的例子。考虑一个简单的例子,在食堂里,盘子一个一个地叠在一起。位于顶部的盘子是第一个被移除的盘子,即放置在最底部位置的盘子在堆叠中保留最长的时间。因此,可以简单地看出遵循 LIFO/FILO 顺序。复杂度时间复杂度:操作复杂度push()O(1)pop()O(1)isEmpty()O(1)size()O(1)堆栈类型:固定大小堆栈:顾名思义,固定大小堆栈具有固定的大小,不能动态增长或收缩。如果堆栈已满并尝试向其中添加元素,则会发生溢出错误。如果堆栈为空并且尝试从中删除元素,则会发生下溢错误。动态大小堆栈:动态大小堆栈可以动态增长或收缩。当堆栈已满时,它会自动增加其大小以容纳新元素,而当堆栈为空时,它会减少其大小。这种类型的堆栈是使用链表实现的,因为它允许轻松调整堆栈的大小。除了这两种主要类型之外,堆栈还有其他几种变体,包括:中缀到后缀堆栈:这种类型的堆栈用于将中缀表达式转换为后缀表达式。表达式计算堆栈:这种类型的堆栈用于计算后缀表达式。递归堆栈:这种类型的堆栈用于跟踪计算机程序中的函数调用,并在函数返回时将控制权返回到正确的函数。内存管理堆栈:这种类型的堆栈用于存储计算机程序中程序计数器的值和寄存器的值,允许程序在函数返回时返回到先前的状态。平衡括号堆栈:这种类型的堆栈用于检查表达式中括号的平衡。撤消重做堆栈:这种类型的堆栈用于计算机程序中,允许用户撤消和重做操作。栈的应用中缀到后缀/前缀的转换许多地方都有重做/撤消功能,例如编辑器、Photoshop。网络浏览器中的前进和后退功能用于许多算法,如 汉诺塔、股票跨度问题和直方图问题。回溯是算法设计技术之一。回溯的一些例子包括骑士之旅问题、N-皇后问题、在迷宫中寻找出路以及所有这些问题中的类似国际象棋或西洋跳棋的问题,如果这种方式效率不高,我们会回到之前的问题状态并进入另一条道路。为了从当前状态返回,我们需要存储以前的状态,为此我们需要一个堆栈。在内存管理中,任何现代计算机都使用堆栈作为运行目的的主要管理。计算机系统中运行的每个程序都有自己的内存分配字符串反转也是栈的另一个应用。这里每个字符都被一一插入到堆栈中。因此,字符串的第一个字符位于堆栈底部,字符串的最后一个元素位于堆栈顶部。在堆栈上执行弹出操作后,我们得到一个相反顺序的字符串。堆栈还有助于在计算机中实现函数调用。最后调用的函数总是最先完成。堆栈还用于在文本编辑器中实现撤消/重做操作。堆栈的实现堆栈可以使用数组或链表来实现。在基于数组的实现中,push 操作是通过递增顶部元素的索引并将新元素存储在该索引处来实现的。弹出操作是通过递减顶部元素的索引并返回存储在该索引处的值来实现的。在基于链表的实现中,推送操作是通过使用新元素创建新节点并将当前顶节点的下一个指针设置为新节点来实现的。出栈操作是通过将当前顶节点的next指针设置为下一个节点并返回当前顶节点的值来实现的。堆栈在计算机科学中通常用于各种应用,包括表达式求值、函数调用和内存管理。在表达式的计算中,堆栈可用于在处理操作数和运算符时存储它们。在函数调用中,堆栈可用于跟踪函数调用的顺序,并在函数返回时将控制权返回到正确的函数。在内存管理中,堆栈可用于存储计算机程序中的程序计数器的值和寄存器的值,从而允许程序在函数返回时返回到先前的状态。总之,堆栈是一种按照 LIFO 原理运行的线性数据结构,可以使用数组或链表来实现。可以在堆栈上执行的基本操作包括入栈、出栈和查看,并且堆栈在计算机科学中常用于各种应用,包括表达式求值、函数调用和内存管理。有两种方法实现一个堆栈使用数组使用链表使用数组实现堆栈 from sys import maxsize  ​  def createStack():   stack = []   return stack  ​  def isEmpty(stack):   return len(stack) == 0  ​  def push(stack, item):   stack.append(item)   print(item + " pushed to stack ")    def pop(stack):   if (isEmpty(stack)):   return str(-maxsize -1) # 返回负无穷大     return stack.pop()  ​  def peek(stack):   if (isEmpty(stack)):   return str(-maxsize -1) # 返回负无穷大   return stack[len(stack) - 1]  ​  stack = createStack()  push(stack, str(10))  push(stack, str(20))  push(stack, str(30))  print(pop(stack) + " popped from stack")  ​数组实现的优点:易于实施。由于不涉及指针,因此节省了内存。数组实现的缺点:它不是动态的,即它不会根据运行时的需要而增长和收缩。[但是对于动态大小的数组,例如 C++ 中的向量、Python 中的列表、Java 中的 ArrayList,堆栈也可以随着数组实现而增长和收缩]。堆栈的总大小必须事先定义。使用链表实现堆栈 class StackNode:          # Constructor to initialize a node      def __init__(self, data):          self.data = data          self.next = None          class Stack:          # Constructor to initialize the root of linked list      def __init__(self):          self.root = None          def isEmpty(self):          return True if self.root is None else False          def push(self, data):          newNode = StackNode(data)          newNode.next = self.root          self.root = newNode          print ("% d pushed to stack" % (data))          def pop(self):          if (self.isEmpty()):              return float("-inf")          temp = self.root          self.root = self.root.next          popped = temp.data          return popped          def peek(self):          if self.isEmpty():              return float("-inf")          return self.root.data      stack = Stack()  stack.push(10)  stack.push(20)  stack.push(30)      print ("% d popped from stack" % (stack.pop()))  print ("Top element is % d " % (stack.peek()))堆栈的优点:易于实现: 堆栈数据结构很容易使用数组或链表来实现,其操作易于理解和实现。高效的内存利用率:堆栈使用连续的内存块,与其他数据结构相比,它的内存利用率更高。快速访问时间:当从堆栈顶部添加和删除元素时,堆栈数据结构为添加和删除元素提供了快速访问时间。有助于函数调用:堆栈数据结构用于存储函数调用及其状态,这有助于高效实现递归函数调用。支持回溯:堆栈数据结构支持回溯算法,用于解决问题时通过存储先前的状态来探索所有可能的解决方案。用于编译器设计:堆栈数据结构用于编译器设计中,用于编程语言的解析和语法分析。启用撤消/重做操作:堆栈数据结构用于在文本编辑器、图形设计工具和软件开发环境等各种应用程序中启用撤消和重做操作。堆栈的缺点:容量有限:堆栈数据结构的容量有限,因为它只能容纳固定数量的元素。如果堆栈已满,添加新元素可能会导致堆栈溢出,从而导致数据丢失。不允许随机访问:堆栈数据结构不允许随机访问其元素,它只允许从堆栈顶部添加和删除元素。要访问堆栈中间的元素,必须删除其上方的所有元素。内存管理:堆栈数据结构使用连续的内存块,如果频繁添加和删除元素,可能会导致内存碎片。不适合某些应用程序:堆栈数据结构不适合需要访问堆栈中间元素的应用程序,例如搜索或排序算法。堆栈上溢和下溢:如果将太多元素压入堆栈,则堆栈数据结构可能会导致堆栈溢出;如果从堆栈中弹出太多元素,则可能会导致堆栈下溢。递归函数调用限制:虽然堆栈数据结构支持递归函数调用,但过多的递归函数调用可能会导致堆栈溢出,从而导致程序终止。使用栈实现LRU 缓存如何实现LRU缓存方案?应该使用什么数据结构?我们给出了可以引用的总可能页码。我们还给出了缓存(或内存)大小(缓存一次可以容纳的页帧数)。LRU 缓存方案是当缓存已满并且引用缓存中不存在的新页面时删除最近最少使用的帧。使用队列和散列的 LRU 缓存实现:要解决该问题,需要遵循以下想法:我们使用两种数据结构来实现 LRU Cache。 队列是使用双向链表实现的。队列的最大大小将等于可用帧的总数(缓存大小)。最近使用的页面将靠近前端,最近最少使用的页面将靠近后端。 以页码为键、对应队列节点的地址为值的哈希。 当一个页面被引用时,所需的页面可能在内存中。如果它在内存中,我们需要分离列表的节点并将其带到队列的前面。 如果所需的页面不在内存中,我们会将其放入内存中。简单来说,我们将一个新节点添加到队列的前面,并更新哈希中相应的节点地址。如果队列已满,即所有帧都已满,我们从队列的后面删除一个节点,并将新节点添加到队列的前面。示例 –: 考虑以下参考字符串:1, 2, 3, 4, 1, 2, 5, 1, 2, 3, 4, 5下面是上述方法的图示:注意: 最初内存中没有任何元素。请按照以下步骤解决问题:创建一个 LRUCache 类,声明一个 int 类型的列表、一个 <int, list> 类型的无序映射以及一个用于存储缓存最大大小的变量在LRUCache的refer函数中在显示函数print中,LRUCache使用从前面开始的队列javascript 代码示例://使用 Set 和 LinkedList 实现 LRU 缓存的 JavaScript 程序 class LRUCache { constructor(capacity) { this.cache = new Set(); this.capacity = capacity; } // 此函数如果缓存中不存在键,则返回false。否则,它会通过先移除再添加的方式将键移到前面,并返回true。 get(key) { if (!this.cache.has(key)) { return false; } this.cache.delete(key); this.cache.add(key); return true; } /* 在LRU缓存中引用键x */ refer(key) { if (!this.get(key)) { this.put(key); } } // 以相反的顺序显示缓存内容 display() { const list = [...this.cache]; // Array类用于反转数组中的元素 list.reverse(); let ans=""; for (const key of list) { ans = ans +key + " "; } console.log(ans); } put(key) { if (this.cache.size === this.capacity) { const firstKey = this.cache.values().next().value; this.cache.delete(firstKey); } this.cache.add(key); } } const ca = new LRUCache(4); ca.refer(1); ca.refer(2); ca.refer(3); ca.refer(1); ca.refer(4); ca.refer(5); ca.display(); golang 代码示例:package main import ( "fmt" "testing" ) type LRUCache struct { list []int csize int ma map[int]int } func (lru *LRUCache) refer(x int) { if index, ok := lru.ma[x]; !ok { // 如果存在,比较当前的容量是否已达上限 if len(lru.list) == lru.csize { // 如果已达上限,则删除栈顶元素 lru.list = lru.list[:lru.csize-1] } } else { // 如果存在, 则删除对应 index 位置的值, 并将期追加到队尾 lru.list = append(lru.list[:index-1], lru.list[index+1:]...) } lru.list = append(lru.list, x) lru.ma[x] = len(lru.list) } func (lru *LRUCache) Display() { for i := len(lru.list) - 1; i >= 0; i-- { fmt.Println(lru.list[i]) } } func NewLRUCache(size int) *LRUCache { ma := make(map[int]int) return &LRUCache{ list: []int{}, csize: size, ma: ma, } } func Test_NewLRUCache(t *testing.T) { cache := NewLRUCache(4) cache.refer(1) cache.refer(2) cache.refer(3) cache.refer(1) cache.refer(4) cache.refer(5) cache.Display() } 时间复杂度: O(1),我们使用Linked HashSet数据结构来实现缓存。Linked HashSet 为添加元素和检索元素提供恒定的时间复杂度。辅助空间: O(n),我们需要在缓存中存储n个元素,所以空间复杂度为O(n)。
0
0
0
浏览量185
算法无限

跟我一起学数据结构与算法:字符串(下)

字符串的应用、优点和缺点字符串数据结构是编程语言的支柱和通信的构建块**。**字符串数据结构是计算机科学和编程中最基本、最广泛使用的工具之一。它们允许以多种方式表示和操作文本和字符序列。字符串数据结构是一个强大的工具,可以用来存储和处理大量的文本数据,从简单的字符串到复杂的句子、段落,甚至整本书。它是表示文本或其他形式的数据的字符序列。它是一种基本数据结构,在许多编程语言中用于存储和操作基于文本的数据。在大多数编程语言中,字符串被实现为字符数组,每个字符在数组中都有唯一的索引位置。字符串的应用:匹配检查器:字符串可用于使用字符串匹配算法在很短的时间内查找代码和内容中的抄袭行为。使用这个,计算机可以轻松地告诉我们代码的百分比,以及任意两个用户编写的文本匹配的百分比。编码/解码(密文生成):字符串可用于编码和解码,以确保数据从发送者到接收者的安全传输,以确保传输过程中没有人能够读取您的数据,因为它们可以执行主动和被动操作攻击。您作为消息传输的文本在发送者端被加密,并在接收者端被解码。信息检索:字符串应用程序帮助我们从未知数据源(用作输入的大型数据集)检索信息,并借助字符串匹配/检索模块帮助我们检索重要信息。针对近似后缀-前缀重叠问题的改进过滤器:字符串及其算法应用帮助我们为近似后缀-前缀重叠问题提供改进的过滤器。近似后缀-前缀重叠问题是从给定集合中查找所有字符串对,使得一个字符串的前缀与另一个字符串的后缀相似。网络通信:字符串用于对通过网络发送的数据进行编码和解码,例如 HTTP 请求和响应。文件处理:字符串用于操作文件路径和名称,以及读取和写入文件。数据分析:字符串可用于从大量文本数据中提取有意义的见解,例如自然语言处理和情感分析。字符串的优点:文本处理:字符串用于表示编程语言中的文本。它们可用于以各种方式操纵和处理文本,例如搜索、替换、解析和格式化。数据表示:字符串可用于表示其他数据类型,例如数字、日期和时间。例如,您可以使用字符串来表示“YYYY-MM-DD”格式的日期,或“HH:MM:SS”格式的时间。易于使用:字符串易于使用和操作。它们可以被连接、切片和反转等。它们还具有简单直观的语法,使所有技能水平的程序员都可以使用它们。兼容性:字符串在各种编程语言中广泛使用,使其成为通用数据类型。这意味着字符串可以在不同的系统和平台之间轻松传输,使其成为可靠且高效的通信和共享数据的方式。内存效率:字符串通常存储在连续的内存块中,这使得它们的分配和释放效率很高。这意味着它们可以用来表示大量数据而不占用太多内存。字符串的缺点:内存消耗:字符串可能会消耗大量内存,尤其是在处理大字符串或许多字符串时。这在内存受限的环境中可能是一个问题,例如嵌入式系统或移动设备。不变性:在许多编程语言中,字符串是不可变的,这意味着它们一旦创建就无法更改。当处理需要频繁修改的大型或复杂字符串时,这可能是一个缺点,因为它可能导致效率低下和内存开销。性能开销:字符串操作可能比其他数据类型的操作慢,尤其是在处理大型或复杂的字符串时。这是因为字符串操作通常涉及复制和重新分配内存,这可能非常耗时。编码和解码开销:字符串可以具有不同的字符编码,这可能会导致在它们之间进行转换时产生开销。当处理来自不同源的数据或与使用不同编码的系统通信时,这可能会成为问题。安全漏洞:如果处理不当,字符串可能容易受到安全漏洞的影响,例如缓冲区溢出或注入攻击。这是因为攻击者可以操纵字符串来执行任意代码或访问敏感数据。实战:给定一个字符串s和S 中存在的两个单词w1和w2 。任务是找到w1和w2之间的最小距离。这里,距离是第一个和第二个单词之间的步数或单词数。 Examples: Input : s = “geeks for geeks contribute practice”, w1 = “geeks”, w2 = “practice” Output : 1 最接近的 w1 和 w2 之间只有一个单词。 Input : s = “the quick the brown quick brown the frog”, w1 = "quick", w2 = "frog" Output : 2func TestD(t *testing.T) { // var s = "geeks for geeks contribute practice" // var w1, w2 = "geeks", "practice" var s = "the quick the brown quick brown the frog" var w1, w2 = "quick", "frog" sl := strings.Split(s, " ") var start int var ll int for i := 0; i < len(sl); i++ { if sl[i] == w1 { start = i continue } if sl[i] == w2 && (i-start) <= start { ll = i - start - 1 } } fmt.Println(ll) } 解读:我们首先需要将字符串 Input 转换为数组(这样操作就简单了),然后遍历数组找到匹配 w1 的字符串并记录下他在数组中的索引 index ,当匹配到第二个单词 w2 的时候,我们计算距离长度 ll。条件 (i-start) <= start 是必须的, 也是其中的奥秘,大家可以探索下为什么。时间复杂度:我们使用了一个 for 来循环 Input, 循环的次数取决于 Input 字符串的长度。所以时间复杂度为 O(n);空间复杂度:我们定义了 sl 数组, 来保存字符串转换过的数组。所以空间复杂度为 O(n)
0
0
0
浏览量1456
算法无限

跟我一起学算法:链表数据结构

什么是链表?链表是一种线性数据结构,看起来像节点链,其中每个节点都是不同的元素。与数组不同,链表元素不存储在连续的位置。它基本上是节点链,每个节点包含数据和指向链中下一个节点的指针等信息。 链表中有一个头指针,它指向链表的第一个元素,如果链表为空,则它简单地指向 null 或不指向任何内容。为什么需要链表数据结构?下面列出了链表的一些优点,以及帮助理解在什么样的场景使用那种数据类型。动态数据结构: 可以在运行时根据操作插入或删除来分配或取消分配内存大小。易于插入/删除: 元素的插入和删除比数组简单,因为插入和删除后不需要移动元素,只需更新地址。高效的内存利用: 众所周知,链表是一种动态数据结构,其大小根据要求增加或减少,从而避免了内存的浪费。实现: 可以使用链表来实现各种高级数据结构,如堆栈、队列、图、哈希图等。链表的类型链表主要有以下三种类型:1、 单链表在单链表中,每个节点都包含对序列中下一个节点的引用。遍历单链表是向前完成的。2、双链表在双向链表中,每个节点都包含对下一个和前一个节点的引用。这允许向前和向后两个方向遍历,但需要额外的内存用于向后引用。3、 循环链表在循环链表中,最后一个节点指向头节点,形成循环结构。它可以是单链或双链。链表操作插入: 向链表添加新节点涉及调整现有节点的指针以保持正确的顺序。插入可以在列表的开头、结尾或任意位置执行删除:从链表中删除节点需要调整相邻节点的指针以弥补删除节点留下的间隙。删除可以在列表的开头、结尾或任意位置执行。搜索:在链表中搜索特定值涉及从头节点遍历链表,直到找到该值或到达链表末尾。链表的优点动态大小: 链接列表可以动态增长或收缩,因为内存分配是在运行时完成的。插入和删除: 从链表中添加或删除元素是高效的,尤其是对于大型列表。灵活性: 链表可以轻松地重新组织和修改,而不需要连续的内存块。链表的缺点随机访问: 与数组不同,链表不允许通过索引直接访问元素。需要遍历才能到达特定节点。额外内存: 与数组相比,链表需要额外的内存来存储指针。单链表的表示:Javascript 代码 // Linked List Class      var head; // head of list      /* Node Class */      class Node {          // Constructor to create a new node          constructor(d) {              this.data = d;              this.next = null;         }     }golang 代码 package main  ​  import "fmt"  ​  type Node struct {   Data interface{}   Next *Node  }  ​  func main() {   var node3 = &Node{   Data: "this is three demo",   }   var node2 = &Node{   Data: "this is two demo",   Next: node3,   }   var node = &Node{   Data: "this is one demo",   Next: node2,   }  ​   for {   if node == nil {   break   }   fmt.Println(node.Data)   node = node.Next   }  }双向链表项目的遍历可以向前和向后进行,因为每个节点都包含一个指向前一个节点的附加prev指针。循环链表链表的应用以下是链表的一些应用:线性数据结构(例如堆栈、队列)和非线性数据结构(例如哈希图和图)可以使用链表来实现。动态内存分配: 我们使用空闲块的链表。图的实现: 图的邻接列表表示是最流行的,因为它使用链表来存储相邻顶点。在网络浏览器和编辑器中,双向链表可用于构建向前和向后导航按钮。循环双向链表也可用于实现斐波那契堆等数据结构。链表在现实世界中的应用:音乐播放器中的歌曲列表链接到上一首和下一首歌曲。在网络浏览器中,上一个和下一个网页 URL 通过上一个和下一个按钮链接。在图像查看器中,上一个和下一个图像借助上一个和下一个按钮链接。两个应用程序之间的切换是通过在windows中使用 “alt+tab ”和在mac book中使用“ cmd+tab ”进行的。它需要循环链表的功能。在手机中,我们保存人们的联系方式。新输入的联系方式将按照正确的字母顺序排列。这可以通过链接列表来实现,以将联系人设置在正确的字母顺序位置。我们在文档中所做的修改实际上是作为双向链表中的节点创建的。我们可以简单地通过按Ctrl+Z来使用撤消选项来修改内容。它是通过链表的功能来完成的。有关链表的常见问题 (FAQ):1.什么是链表数据结构?链接列表最常用于处理动态数据元素。链表由节点组成,一个节点由两个字段组成,一个用于存储数据,另一个用于保存下一个节点的引用。2.什么是链表示例?链表可以被假设为由花朵组成的花环。类似地,链表也是由节点组成的。这个特定花环中的每一朵花都被称为一个节点。此外,每个节点都指向该列表中的下一个节点,并且它包含数据(在本例中为花的类型)。3.为什么需要链表数据结构?与其他线性数据结构相比,使用链表有一些重要的优点。这与数组不同,因为它们可以在运行时调整大小。此外,它们可以轻松插入和删除。4. 链表有什么用?链表是一种在节点中存储数据的线性数据结构。这些节点保存数据和对列表中下一个节点的引用。由于结构简单,链接在添加和删除节点方面非常有效。5.数组和链表有什么区别?它们之间有以下一些区别:数组是包含相似数据元素的数据结构,而链表是包含无序链接元素的非原始数据结构。在数组中,元素有索引,但在链表中,节点没有索引。如果我们知道数组中元素的位置,则访问元素数组会很快,而在链表中则需要线性时间,因此链表要慢一些。数组中的插入和删除等操作需要花费大量时间。然而,这些操作在链接列表中的性能更快。数组的大小是固定的,它们的大小是静态的,但链表是动态的、灵活的,可以扩展和缩小它们的大小。6. 为什么链表优于数组?以下是链表优于数组的原因链接数组中的节点、插入和删除可以在列表中的任何点以恒定的时间完成。数组的大小是固定的,它们的大小是静态的,但链表是动态的、灵活的,可以扩展和缩小它们的大小。链表提供了一种存储相关数据和执行基本操作(例如插入、删除和更新信息)的有效方法,但代价是存储地址所需的额外空间。与数组相比,链表中的插入和删除操作更快。7. 单链表和双向链表有什么区别?以下是单链表和双链表之间的一些区别。单链表 (SLL)双向链表 (DLL)SLL节点包含2个字段数据字段和下一个链接字段。DLL节点包含3个字段数据字段、前一个链接字段和下一个链接字段。在SLL中,只能使用下一个节点链接来完成遍历。因此,只能在一个方向上进行遍历。在DLL中,可以使用前一个节点链接或后一个节点链接来完成遍历。因此,可以在两个方向(向前和向后)进行遍历。SLL 比 DLL 占用更少的内存,因为它只有 2 个字段。DLL 比 SLL 占用更多内存,因为它有 3 个字段。在给定位置插入和删除的复杂度是 O(n)。给定位置插入和删除的复杂度为 O(n / 2) = O(n),因为可以从头开始遍历,也可以从尾部开始遍历。给定节点的删除复杂度为 O(n),因为需要知道前一个节点,而遍历需要 O(n)删除给定节点的复杂度为 O(1),因为可以轻松访问前一个节点与双链表相比,单链表消耗更少的内存。与单链表相比,双链表消耗更多内存。8. 数组和链表哪个最好?在存储类似类型的线性数据时,数组和链表都有一些优点和缺点。链表相对于数组的优点:动态大小: 链表是动态的、灵活的,可以扩大和缩小其大小易于插入/删除: 与数组相比,链表中的插入和删除操作更快链表相对于数组的缺点:如果数组已排序,我们可以应用二分搜索来搜索任何元素,这需要O(log(n)) 时间。但即使链表已排序,我们也无法应用二分搜索,并且在链表中搜索元素的复杂度为O(n) 。与数组相比,链表占用更多内存,因为链表中每个元素的指针都需要额外的内存空间。9. 链表有哪些限制?以下是链表的一些限制:指针在链表中的使用更多,因此比较复杂并且需要更多内存。由于动态内存分配,随机访问是不可能的。遍历比较耗时,并且单链表中无法进行反向遍历。搜索元素的成本很高,并且需要O(n) 时间复杂度。10. 为什么链表的插入/删除速度更快?如果从数组中插入/删除任何元素,则该元素之后的所有其他元素都将在内存中移位,这需要大量时间,而链表中的操作速度更快,因为我们只需要操作节点的地址,因此不需要移位需要内存,而且不会花那么多时间。结论与数组相比,链表有很多优点,尽管它们解决了与数组类似的问题,但我们也讨论了优点、缺点及其应用,我们得出的结论是,如果满足以下条件,则可以使用链表:我们需要动态的存储大小,列表适合快速添加和删除项目,或者适合需要顺序但不适合在大量数据集合中查询或搜索元素的任务。因此,重要的是我们应该始终牢记数据结构的积极和消极方面以及它们与您要解决的问题的关系。
0
0
0
浏览量318
算法无限

跟我一起学算法:堆数据结构

什么是堆数据结构?堆是一种特殊的基于树的数据结构,其中树是[完全二叉树]。堆的类型:一般来说,堆有两种类型。最大堆:在这个堆中,根节点的值必须是其所有子节点中最大的,并且其左右子树也必须执行相同的操作。最大堆中所需的比较总数取决于树的高度。完全二叉树的高度始终为logn;因此,时间复杂度也是 O(logn)。最小堆:在这个堆中,根节点的值必须是其所有子节点中最小的,并且其左右子树也必须执行相同的操作。最小堆中所需的比较总数取决于树的高度。完全二叉树的高度始终为logn;因此,时间复杂度也是 O(logn)。堆的属性:堆具有以下属性:完全二叉树: 堆树是完全二叉树,这意味着树的所有级别都已完全填充,除了最后一层(可能是从左到右填充)之外。此属性确保使用数组有效地表示树。堆属性: 该属性确保根据堆类型,最小(或最大)元素始终位于树的根部。父子关系:索引i处的父节点与其子节点之间的关系由以下公式给出:对于从 0 开始的节点编号索引,左子节点位于索引2i+1,右子节点位于索引2i+2。高效的插入和删除: 堆树中的插入和删除操作是高效的。新元素将插入到最右下层的下一个可用位置,并通过将元素与其父元素进行比较并在必要时进行交换来恢复堆属性。删除根元素涉及用最后一个元素替换它并向下堆放。高效访问极值元素: 最小或最大元素始终位于堆的根部,允许恒定时间访问。堆支持的操作:min-heap和max-heap支持的操作是相同的。区别只是 min-heap 在树的根部包含最小元素,而 max-heap 在树的根部包含最大元素。堆化:它是重新排列元素以保持堆数据结构属性的过程。当某个节点由于该节点上的某些操作而在堆中造成不平衡时,就会执行此操作。平衡树 需要O(log N) 。对于最大堆, 它以最大元素是二叉树的根的方式进行平衡。对于最小堆, 它以最小元素是二叉树的根的方式进行平衡。插入:如果我们向堆中插入一个新元素,因为我们向堆中添加一个新元素,那么它会扭曲堆的属性,所以我们需要执行heapify操作,以便它保持堆的属性。此操作也需要O(logN) 时间。例子:假设初始堆(取**max-heap**)如下 8 ​ / \ 4 5 / \ 1 2 现在如果我们将 10 插入堆中 8 / \ 4 5 / \ / 1 2 10 heapify 操作后最终堆将如下所示 10 / \ 4 8 / \ / 1 2 5 删除:如果我们从堆中删除元素,它总是删除树的根元素并将其替换为树的最后一个元素。由于我们从堆中删除根元素,它会扭曲堆的属性,因此我们需要执行heapify操作,以便它保持堆的属性。需要O(logN) 时间。例子:假设初始堆(取最大堆)如下: 15 / \ 5 7 / \ 2 3 现在,如果我们将堆中 15 删除,它将临时被树的叶节点替换。 3 / \ 5 7 / 2 heapify 操作后最终堆将如下所示 7 / \ 5 3 / 2 getMax(对于最大堆)或 getMin(对于最小堆):它分别找到max-heap和min-heap的最大元素或最小元素,并且我们知道最小和最大元素将始终分别是 min-heap 和 max-heap 的根节点本身。需要 O(1) 时间。删除最大或删除最小:此操作分别返回并删除最大堆和最小堆中的最大元素和最小元素。也就是删除的是堆二叉树的根元素。堆数据结构的实现:以下代码是max-heap的实现。maxHeapify是负责恢复Max Heap属性的函数。它相应地排列节点i及其子树,以便维护堆属性。假设我们有一个数组,arr[] 代表完整的二叉树。第 i个节点的左子节点和右子节点位于索引2*i+1和2*i+2中。我们将当前元素的索引i设置为“MAXIMUM”。如果arr[2 * i + 1] > arr[i] ,即左孩子大于当前值,则将其设置为“MAXIMUM”。类似地,如果arr[2 * i + 2] > arr[i] ,即右子节点大于当前值,则将其设置为“MAXIMUM”。将“MAXIMUM”与当前元素交换。重复步骤2到5, 直到堆的属性恢复。JavaScript 代码示例: class MaxHeap {    constructor(maxSize) {      // 初始化堆中的数组      this.arr = new Array(maxSize).fill(null);  ​      // 设置最大堆的大小      this.maxSize = maxSize;  ​      // 设置堆中元素的大小      this.heapSize = 0;   }  ​    // 以给定的索引作为根节点,对子树进行堆化。    MaxHeapify(i) {      const l = this.lChild(i);      const r = this.rChild(i);      let largest = i;      if (l < this.heapSize && this.arr[l] > this.arr[i]) {        largest = l;     }      if (r < this.heapSize && this.arr[r] > this.arr[largest]) {        largest = r;     }      if (largest !== i) {        const temp = this.arr[i];        this.arr[i] = this.arr[largest];        this.arr[largest] = temp;        this.MaxHeapify(largest);     }   }  ​    // 返回第i个索引元素的父级索引。    parent(i) {      return Math.floor((i - 1) / 2);   }  ​    // 返回左侧子代的索引。    lChild(i) {      return 2 * i + 1;   }  ​    // 返回右子节点的索引。    rChild(i) {      return 2 * i + 2;   }  ​    // 删除根元素中包含最大元素。    removeMax() {      // 检测对数组是否为空      if (this.heapSize <= 0) {        return null;     }      if (this.heapSize === 1) {        this.heapSize -= 1;        return this.arr[0];     }  ​      // 移除最大元素      const root = this.arr[0];      this.arr[0] = this.arr[this.heapSize - 1];      this.heapSize -= 1;  ​      // 回复最大堆的属性      this.MaxHeapify(0);  ​      return root;   }  ​    // 在第 i 个元素插入新值    increaseKey(i, newVal) {      this.arr[i] = newVal;      while (i !== 0 && this.arr[this.parent(i)] < this.arr[i]) {        const temp = this.arr[i];        this.arr[i] = this.arr[this.parent(i)];        this.arr[this.parent(i)] = temp;        i = this.parent(i);     }   }    //   获取堆的最大元素    getMax() {      return this.arr[0];   }    // 获取堆的大小    curSize() {      return this.heapSize;   }  ​    // 从指定索引删除元素    deleteKey(i) {      // 它将键值      // 增加到无穷大,然后删除      // 最大值。      this.increaseKey(i, Infinity);      this.removeMax();   }  ​    // 在大堆中插入元素    insertKey(x) {      // 检车元素是否可以插入到堆中      if (this.heapSize === this.maxSize) {        console.log("\n 不能插入堆中 \n");        return;     }  ​      let i = this.heapSize;      this.arr[i] = x;  ​      // 插入元素到堆的尾部      this.heapSize += 1;  ​      // 检查最大堆属性      // 如果违反,则恢复它.      while (i !== 0 && this.arr[this.parent(i)] < this.arr[i]) {        const temp = this.arr[i];        this.arr[i] = this.arr[this.parent(i)];        this.arr[this.parent(i)] = temp;        i = this.parent(i);     }   }  }  ​  // 测试上述功能的驱动程序。  // 假设堆的最大大小为15。  const h = new MaxHeap(15);  // 输入数值  console.log("Entered 6 keys: 3, 10, 12, 8, 2, 14 \n");  ​  h.insertKey(3);  console.log(h.arr)  h.insertKey(10);  console.log(h.arr);  h.insertKey(12);  console.log(h.arr)  h.insertKey(8);  console.log(h.arr);  h.insertKey(2);  console.log(h.arr);  h.insertKey(14);  console.log(h.arr);  ​  // 打印堆的当前大小。  console.log("打印堆的当前大小" + h.curSize() + "\n");  ​  // 打印根元素 实际上是最大的元素。  console.log("根元素" + h.getMax() + "\n");  ​  // 删除索引2的键。  h.deleteKey(2);  ​  // 删除后,打印堆的大小  console.log("当前堆的大小是:" + h.curSize() + "\n");  ​  // 在堆中插入 15 和 5 两个数字  h.insertKey(15);  h.insertKey(5);  ​  console.log("当前堆的大小是: " + h.curSize() + "\n");  ​  console.log("当前堆中最大的元素是: " + h.getMax() + "\n");  ​堆数据结构的应用:优先级队列: [优先级队列]可以使用二叉堆高效地实现,因为它支持O(log N) 时间内的 insert()、delete() 和 extractmax()、decreaseKey() 操作。二项式堆和斐波那契堆是二项式堆的变体。这些变体也在O(log N) 时间内执行联合,这是二叉堆中的 O(N) 操作。顺序统计: 堆数据结构可用于有效地查找数组中第 k 个最小(或最大)元素。你可以看这篇gfg文章来了解更多关于第 k 个最小或最大元素的信息。堆的优点:快速访问最大/最小元素 (O(1))高效的插入和删除操作 (O(log n))灵活的大小可以有效地实现为数组适合实时应用堆的缺点:不适合搜索除最大/最小值之外的元素(最坏情况下为 O(n))维护堆结构的额外内存开销对于非优先级队列操作,比数组和链表等其他数据结构慢。
0
0
0
浏览量717
算法无限

关联三重提取方法的系统评价

BackGround对于过去的三元组抽取的模型中,大多针对于NYT\WebNLG两个数据集进行训练并验证,但是经过研究发现,对于在训练中从未遇见过的三元组中,不能有效的推广不可见的三元组,作者通过重新排列数据、筛选训练实习、增加训练试题来强调看不见的数据,并由此提出一种简单有效的技术进行解决泛化的问题。在这篇文章中,三元组的类型被分为了3个类型:· 完全可见(在各自数据集中与三元组完全重叠)· 不完全可见(部分重叠)· 不可见(全新)过去模型的泛化性能评估如上图,数据集中的部分可见和不可见的三元组的比例非常小,以至于多样性不够,由此导致泛化性能评估不可靠。提出三种策略增加部分可见和不可见的三元组比例。重新排列反复选择一个三元组,并将包含该三元组的每个实例分发到测试集,使他们在数据集中不可见,为了得到冗余最小化,选择一个出现次数较少的三元组。重叠筛选从测试集中删除包含该三元组的实力,从测试集中随机选择k%的唯一三元组,从训练集中删除所有包含所选三元组的实力,构建一个重叠筛选数据。扩充数据集构架了一个增强测试集合,使用mask语言模型,用可代替的词语替换每个三元组中定义的实体。增强方式:实体噪声 使用完全随机的噪声词替换给定输入句中的实体。首先对每个实体w采样一个随机噪声词w',对w'的token进行采样,引入+-1扰动,防止模型记住令牌的数量,与过去的方式不同,实体噪声使用完全随机的噪声词替换实体,这个特性允许模型利用不可知的信息,因此模型可以通过上下文信息,而不是实体本身来学习从句子中提取三元组。实验 上表表示了RTE方法再重组数据集和原始数据集上缺乏泛化能力,实体噪声提高了对不可见三元组的繁华能力,对于部分可见的三元组,没有损害泛化能力。总结在这篇文章中,揭露了当前主流模型的对于未曾训练过的数据集的泛化能力不强,作者使用实体噪声方式,强化了模型对于未见数据的泛化能力,同时也保持了对于训练中出现过的三元组识别的泛化能力。所以,对于以后得模型训练可以多多考虑对于泛化能力的提升,或者使用作者提供的数据集进行训练,然后性能再对其他的模型在这个数据集上进行比较,对其他的模型造成降维打击。
0
0
0
浏览量1116
算法无限

跟我一起学数据结构与算法:字符串(上)

字符串被定义为字符数组。字符数组和字符串之间的区别在于字符串以特殊字符“\0”结尾。 下面是一些字符串示例:“this”, “is”, “demo007”, “i”, “am”, “a”, “developer”字符串在内存中是如何表示的?在 C 中,可以使用字符指针或字符数组来引用字符串。当字符串被声明为字符数组时,它们的存储方式与 C 中其他类型的数组一样。例如,如果 str[] 是一个 auto 变量,则该字符串存储在堆栈段中,如果它是一个全局或静态变量,则存储在数据段中.对字符串执行的操作:1. 字符串连接将多个字符串组合在一起的过程称为串联。字符串连接是组合两个字符串的技术。2. 在字符串中查找对字符串执行的一个非常基本的操作是在给定的整个字符串中查找某些内容。现在,这可以是在字符串中查找给定字符,或者在另一个字符串中查找完整的字符串。a) 查找字符串中的一个字符 :给定一个字符串和一个字符,任务是找到该字符在字符串中的第一个位置。这些类型的问题是竞争非常激烈的编程问题,您需要找到字符串中字符的位置。b) 在另一个字符串中查找子字符串 :考虑有一个长度为 N 的字符串和一个长度为 M 的子字符串。然后运行一个嵌套循环,其中外循环从 0 到 (NM),内循环从 0 到 M。对于每个索引,检查子字符串是否内循环遍历的字符串是否是给定的子字符串。3.字符串替换很多时候,对字符串进行修改非常重要。替换字符串中的字符、单词或短语是对字符串执行的另一种非常常见的操作。解决给定问题的最简单方法是遍历字符串S,当找到任何字符串S1作为字符串S中的子字符串时,则将其替换为S2。请按照以下步骤解决此问题:初始化字符串ans 来存储替换字符串 S 中所有出现的子字符串 S1 到 S2 后的结果字符串。使用变量 i迭代字符串 S 的字符 并执行以下步骤: 如果字符串S的前缀子串从索引i开始等于S1,则将字符串S2添加到字符串ans中。 否则,将当前字符添加到字符串 ans 中。完成上述步骤后,打印字符串 ans 作为结果。4. 求字符串的长度对字符串最常见的操作之一是查找给定字符串的长度/大小。长度定义为字符串中的字符数,称为该字符串的长度。a )不使用任何内置方法的字符串长度:下面是求两个字符串长度的算法:SET LEN = 0 AND I = 0. Repeat Steps 3 to 4 while STRING[I] is not NULL: LEN = LEN + 1. SET I = I + 1. Exit.6. 字符串的反转和旋转反向操作是交换字符串中字符的位置,使第一个字符变为最后一个,第二个字符变为倒数第二个,依此类推。a)字符串的旋转:考虑一个字符串“demo007”,现在所有可能的旋转将是:7demo00 70demo0 700demo 700odem 700omde 700omedb)反转字符串:字符串的反转只不过是将字符串的最后一个元素替换为字符串的第一个位置。7. 字符串的子序列子序列是可以通过删除零个或多个元素从另一个序列导出的序列,而不改变剩余元素的顺序。更一般地,我们可以说,对于大小为 n 的序列,总共可以有 (2n-1) 个非空子序列。例如,考虑字符串“geeks”,有 15 个子序列。 他们是: g, e, e, k, s, ge, ge, gk, gs, ee, ek, es, ek, es, ks, gee, gek, ges, gek, ges, gks, eek, ees, eks, eks, geek, gees, eeks, geeks9. 二进制字符串二进制字符串是一种特殊的字符串,仅由两种类型的字符组成,例如 0 和 1。 例如:输入: str = "01010101010" 输出: Yes, it is a Binary String 输入: str = "demo007" 输出: No, it is not a Binary String10. 回文字符串如果字符串的反转与该字符串相同,则称该字符串为回文。例如: “abba”是回文,但“abbc”不是回文。11. 词典模式字典顺序模式是基于 ASCII 值的模式,或者可以按字典顺序表示。我们将字符的字典顺序视为它们的 ASCII 值的顺序。因此字符的字典顺序将是 “A”、“B”、“C”、...、“Y”、“Z”、“a”、“b”、“c”、...、“y”、“z”。12. 模式搜索模式搜索是在字符串中搜索给定的模式。这是字符串的高级内容。模式搜索算法有时也称为字符串搜索算法,并被视为字符串算法的一部分。这些算法在搜索另一个字符串中的字符串的情况下非常有用。测试如何替换字符串的子字符串给定三个字符串 S、S1和S2 ,分别由N、M和K 个字符组成,任务是通过将字符串S中的所有子字符串S1替换为字符串S2来修改字符串S。 例子: 输入: S = “abababa”, S1 = “aba”, S2 = “a” 输出: aba 解释: 将子字符串 S[0, 2] 和 S[4, 6](= S1) 更改为字符串 S2(= “a”) 将字符串 S 修改为“aba”。因此,打印“aba”。 输入: S = “geeksforgeeks”,S1 =“eek”,S2 =“ok” 输出: goksforgoksfunc TestA(t *testing.T) { // S := "abababa" // S1 := "aba" // S2 := "a" S := "geeksforgeeks" S1 := "eek" S2 := "ok" var s1len = len(S1) var rest string for i := 0; i < len(S); i++ { var x = S[i:] if i+s1len < len(S) { x = S[i : i+s1len] } if x == S1 { rest += S2 i = i + s1len - 1 } else { rest += string(S[i]) } } fmt.Println(rest) } 解读:要找到 S 中的 S1 的子串,我们需要不断的遍历字符串 S , 所以我们使用一个 for,并不断的取出 i+s1len 长度的字符串与 S 字符串比较,如果相同则将 S3 的子串放到 rest 中,如果不匹配则将原来的子串放到 rest 中,不断重复直到结束。时间复杂度:我们使用了一个 for 来循环 S, 循环的次数取决于 S 字符串的长度。所以时间复杂度为 O(n);空间复杂度:我们定义了 var rest string 来保存最后的结果。所以空间复杂度为 O(1)
0
0
0
浏览量1780
算法无限

数据结构和算法-「队列」上

什么是队列数据结构?队列是一种线性数据结构。该数据结构遵循执行操作的特定顺序。顺序是先进先出( FIFO )。这意味着队列中最先插入的元素将最先出现,最后插入的元素将最后出现。它是一个有序列表,其中元素的插入是从一端(称为后端)完成的,元素的删除是从另一端(称为前端)完成的。与堆栈类似,可以对队列执行多个操作。当向队列中插入元素时,该操作称为“入队” ;当从队列中删除元素时,该操作称为 “出队”。重要的是要知道,如果队列大小已满,我们无法插入元素,而当队列本身为空时,我们无法删除元素。如果我们尝试在队列已满后插入元素,则这种情况称为溢出,而如果我们尝试在队列为空后删除元素,则这种情况称为下溢。我们将队列定义为一个列表,其中对列表的所有添加都在一端进行,而对列表的所有删除都在另一端进行。首先被推入订单的元素,首先对其执行操作。队列的先进先出原理:队列就像等待买票的队伍,队列中的第一个人就是第一个得到服务的人。(即先到先得)。队列中准备被服务的条目的位置,即将从队列中删除的第一个条目,称为队列的前端(有时称为队列* *头 ),类似地,最后一个条目的位置队列中,即最近添加的队列,称为队列的后部 (或尾部)。见下图。队列操作:void enqueue(int Element): 执行此操作时,会在队列末尾(即后端)插入一个元素。(其中 T 是通用的,即我们可以定义任何类型的数据结构的队列。)此操作需要常数时间,即 O(1) 。int dequeue(): 执行该操作时,会从前端移除一个元素并返回。该操作需要常数时间,即 O(1)。Enqueue() –将一个元素添加(或存储)到队列末尾。其时间复杂度为O(1)。Dequeue() – 从队列中删除元素。其时间复杂度为O(1)。Peek() 或 front() -获取队列前端节点可用的数据元素,而不删除它。after() –此操作返回后端的元素而不删除它。isFull() –该操作指示队列是否为空。此操作也在 O(1) 内完成。isNull() –检查队列是否为空。入队:Queue 中的 Enqueue() 操作将一个元素添加(或存储)到队列末尾。 应采取以下步骤将数据排队(插入)到队列中:步骤1:检查队列是否已满。步骤2:如果队列已满,则返回溢出错误并退出。步骤3:如果队列未满,则递增后指针以指向下一个空空间。步骤 4:将数据元素添加到队列位置,即后部指向的位置。步骤 5:返回成功。出队:从队列中删除(或访问)第一个元素。执行出队操作的步骤如下:步骤1: 检查队列是否为空。步骤2: 如果队列为空,则返回下溢错误并退出。步骤3: 如果队列不为空,则访问front指向的数据。步骤 4: 增加前指针以指向下一个可用数据元素。步骤 5: 返回成功。 dequeue(){    // 从队列中移除元素 // 在空队列上调用时返回下溢    if(this.isEmpty()){ console.log("Queue is empty");        return -1;   }    return this.items.shift();  }队列类型:简单队列:简单队列也称为线性队列,是队列的最基本版本。这里,插入元素即入队操作发生在队尾,移除元素即出队操作发生在队首。循环队列: 在循环队列中,队列的元素充当圆环。循环队列的工作原理与线性队列类似,只是最后一个元素连接到第一个元素。它的优点是可以更好地利用内存。这是因为如果存在空白空间,即如果队列中的某个位置不存在元素,则可以轻松地在该位置添加元素。优先级队列:该队列是一种特殊类型的队列。它的特点是它根据某种优先级排列队列中的元素。优先级可以是具有最高值的元素具有优先级,因此它创建一个按值降序排列的队列。优先级也可以是具有最低值的元素获得最高优先级,因此它依次创建一个值顺序递增的队列。双端队列:顾名思义,双端队列意味着可以从队列的两端插入或删除元素,这与其他队列只能从一端插入或删除元素不同。由于此属性,它可能不遵守先进先出属性。队列的实现:顺序分配: 队列可以使用数组来实现。它可以组织有限数量的元素。链表分配: 队列可以使用链表来实现。它可以组织无限数量的元素。队列的应用:多重编程:多重编程是指主存中同时运行多个程序。组织这些多个程序是至关重要的,并且这些多个程序被组织为队列。网络:在网络中,队列用于路由器或交换机等设备。队列的另一个应用是邮件队列,它是存储数据和控制邮件消息文件的目录。作业调度:计算机有一项任务来执行特定数量的作业,这些作业被安排一个接一个地执行。这些作业被一一分配给使用队列组织的处理器。共享资源:队列用作单个共享资源的等待列表。Queue的应用:ATM 亭排队售票柜台排队键盘上的按键顺序CPU任务调度每个客户在呼叫中心的等待时间。队列的优点:可以轻松有效地管理大量数据。由于遵循先进先出的规则,可以轻松执行插入和删除等操作。当多个消费者使用特定服务时,队列非常有用。队列对于数据进程间通信的速度很快。队列可以用于其他数据结构的实现。队列的缺点:从中间插入、删除元素等操作比较耗时。空间有限。在经典队列中,只有从队列中删除现有元素后才能插入新元素。搜索元素需要 O(N) 时间。必须事先定义队列的大小。队列的数组表示: // Queue class  class Queue  { // 数组用于实现队列    constructor()   {      this.items = [];   }  }队列的链表表示: class QNode  {    constructor(key)   {      this.key = key;      this.next = null;   }  }    let front = null, rear = null; 队列代码示例LRU是Least Recently Used的缩写,即最近最少使用,是一种常用的页面置换算法,选择最近最久未使用的页面予以淘汰。该算法赋予每个页面一个访问字段,用来记录一个页面自上次被访问以来所经历的时间 t,当须淘汰一个页面时,选择现有页面中其 t 值最大的,即最近最少使用的页面予以淘汰。---来自百科golang 实现一个 LRU 的 cachepackage main import ( "fmt" "testing" ) type LRUCache struct { list []int csize int ma map[int]int } func (lru *LRUCache) refer(x int) { if index, ok := lru.ma[x]; !ok { // 如果存在,比较当前的容量是否已达上限 if len(lru.list) == lru.csize { // 如果已达上限,则删除栈顶元素 lru.list = lru.list[:lru.csize-1] } } else { // 如果存在, 则删除对应 index 位置的值, 并将期追加到队尾 lru.list = append(lru.list[:index-1], lru.list[index+1:]...) } lru.list = append(lru.list, x) lru.ma[x] = len(lru.list) } // 打印 cache 中的元素 func (lru *LRUCache) Display() { for i := len(lru.list) - 1; i >= 0; i-- { fmt.Println(lru.list[i]) } } // 初始化 lru cache func NewLRUCache(size int) *LRUCache { ma := make(map[int]int) return &LRUCache{ list: []int{}, csize: size, ma: ma, } } // 测试程序 func Test_NewLRUCache(t *testing.T) { cache := NewLRUCache(4) cache.refer(1) cache.refer(2) cache.refer(3) cache.refer(1) cache.refer(4) cache.refer(5) cache.Display() }
0
0
0
浏览量1533
算法无限

实体关系抽取-7

==过去的步骤:==1. 寻找头尾实体的边界位置(实体识别)2. 将特定令牌串联成三元组(关系分类)存在误差累计问题,每个实体边界识别误差会累积到最终的组合三元组中==论文中的方法:== 先通过枚举句子中的令牌序列生成候选实体,然后将三元抽取任务转化为"头->尾"二部图上的连接问题。基础准备名词解析:1. 令牌序列:令牌是自然语言的基础。令牌化是一种将文本分成称为令牌的较小单元的方法。在这里,令牌可以是单词,字符或子单词。因此,标记化可以大致分为3种类型:单词,字符和子词(n-gram字符)标记化。2. 重叠模式: EntityPairOverlap(EPO) 一个实体对具有多种关系 SingleEntityOverlap(SEO) 两个三元组共享一个重叠的实体 HeadTailOverlap(HTO) 三元组的头部实体和尾部实体部分或完全重叠==现有的联合抽取方法:==1. 序列标注 :即给定一个输入序列,使用模型对这个序列的每一个位置标注一个相应的标签,是一个序列到序列的过程。(使用各种标记序列来确定实体的开始和结束位置,有时还包括关系)2020用序列标记来识别句子中的所有实体,然后通过各种网络进行关系检测 2021用一个预测潜在关系的组件,约束到预测的关系子集,而不是所有关系 2022提出双向实体提取框架,考虑头尾和尾头的提取顺序、约束条件1. 表格填充 :为一个句子构造一个表,并用对应的正确的标记填充每个表单元格2019通过关系加权图卷积网络来考虑实体和关系之间的作用 2020三元组抽取转化为令牌对链接问题,引入特定于关系的握手标记方案对其实体对的边界令牌 2021利用一个分区过滤网络,该网络生成任务特殊特征,用于建模实体识别和关系分类之间的交互1. 文本生成:将三元组作为令牌序列,病采用编码器-解码器结构来生成像机器翻译一样的三元组元素2018用复制机制生成两个对应实体所遵循的关系,但只能预测实体的最后一个单词 2020使用多任务学习框架解决多令牌实体问题 2021一种带有生成变压器的对比三元组提取方法解决长期依赖问题 2021设计一个二进制指针网络来提取显式三元组和隐式三元组==文中的方法:==暴力方法: 穷举一个句子的令牌序列,结果是肯定会包含正确的实体 因此:看是否存在关系,可以直接识别三元 通过枚举令牌序列生成候选实体 为每个关系设计一个链接矩阵来检测两个候选实体是否可能构成有效的三元组 三元组的提取转化为一个关系特定的二部图链接问题方法详解 1. 候选实体生成例子: 枚举句子中所有长度小于C(C<L)的连续令牌作为候选实体,若C=2 “Beijing is the capital of China“ E = { “Beijing”, “Beijing is”, “is”, “is the”, “the”, “the Capital”, “Capital”, “Capital of”, “of”, “of China”, “China”}.缺点:1. 负三元组占主导地位,训练偏向负三元组,会降低识别正三元组的能力2. 训练句子多,所以训练效率低2. 二部图链接为实体通常由多个令牌组成,为了便于并行计算,需要保持不同实体表示的维度一致实验 使用了NYT与WebNLG数据集进行实验NYT:将FreeBase中的相关事件与纽约时报语料库对其,包含56k个训练句子和5k个测试句子 WebNLG:最初为自然语言生成开发,从给定的三元组生成相应的描述,包含5k个训练句子和703个测试句子 NYT*表示只注释实体的最后一个词的版本,NYT注释了整个实体,webNLG同理 采用Precision(Prec.),Recall(Rec.),F1-score(F1)评价性能,只有当头部h,尾部t和关系r是与事实完全一致时视为正确的样本本身预测模型TP正正FP负正FN正负主要结果: F1得分优于所有其他模型详细结果: 通过重叠模式和三元数拆分了NYT和WebNLG的测试集 第一,它有效地缓解了误差积累问题,保证了提取三元组的==精度==。 其次,在每个实体对之间采用特定于关系的链接,保证了三重抽取的==召回性==。在两个子任务上的性能试验对比,选择了PRGC(最先进的三元模型之一,在关系判断和头尾对齐方面很强) 在WebNLG上的不足之处:跨度分裂错误、未找到实体和实体角色错误。 “跨度分裂误差”所占比例相对较小,证明了在一个有向的“头→尾”二分图上通过链接预测直接提取三元组的有效性。 “实体角色错误”最具挑战性。 其主要原因是在三重提取过程中忽略了实体的上下文信息。
0
0
0
浏览量2038
算法无限

跟我一起学算法:数据结构和算法-数组(下)

数组数据结构的应用:存储和访问数据:数组用于按特定顺序存储和检索数据。例如,数组可用于存储一组学生的分数,或气象站记录的温度。排序: 数组可用于按升序或降序对数据进行排序。冒泡排序、合并排序和快速排序等排序算法严重依赖数组。搜索:可以使用线性搜索和二分搜索等算法在数组中搜索特定元素。矩阵:数组用于表示数学计算中的矩阵,例如矩阵乘法、线性代数和图像处理。栈和队列: 数组作为底层数据结构来实现栈和队列,常用于算法和数据结构中。图:数组可用于表示计算机科学中的图。数组中的每个元素代表图中的一个节点,节点之间的关系由数组中存储的值表示。动态编程:动态编程算法通常使用数组来存储子问题的中间结果,以解决更大的问题。数组的实时应用:信号处理: 数组在信号处理中用于表示随时间收集的一组样本。这可用于语音识别、图像处理和雷达系统等应用。多媒体应用: 数组用于多媒体应用,例如视频和音频处理,用于存储像素或音频样本。例如,可以使用数组来存储图像的 RGB 值。数据挖掘: 数组在数据挖掘应用程序中用于表示大型数据集。这可以实现高效的数据访问和处理,这在实时应用程序中非常重要。机器人技术:机器人技术中使用数组来表示 3D 空间中物体的位置和方向。这可用于运动规划和对象识别等应用。实时监控系统: 实时监控系统中使用阵列来存储传感器数据和控制信号。这可以实现实时处理和决策,这在工业自动化和航空航天系统等应用中非常重要。财务分析: 数组在财务分析中用于存储历史股票价格和其他财务数据。这可以实现高效的数据访问和分析,这在实时交易系统中非常重要。科学计算: 数组在科学计算中用于表示数值数据,例如实验和模拟的测量结果。这可以实现高效的数据处理和可视化,这对于实时科学分析和实验非常重要。数组数据结构的优点:高效访问元素: 数组提供对集合中任何元素的直接高效访问。访问数组中的元素是一个 O(1) 操作,这意味着访问元素所需的时间是恒定的,并且不依赖于数组的大小。快速数据检索: 数组允许快速数据检索,因为数据存储在连续的内存位置中。这意味着可以快速有效地访问数据,而不需要复杂的数据结构或算法。内存效率: 数组是一种节省内存的数据存储方式。由于数组的元素存储在连续的内存位置中,因此数组的大小在编译时已知。这意味着可以在一个块中为整个数组分配内存,从而减少内存碎片。多功能性: 数组可用于存储多种数据类型,包括整数、浮点数、字符,甚至对象和指针等复杂的数据结构。易于实现: 数组易于实现和理解,使其成为初学者学习计算机编程的理想选择。与硬件的兼容性: 数组数据结构与大多数硬件架构兼容,使其成为在各种环境下进行编程的通用工具。数组数据结构的缺点:固定大小: 数组具有在创建时确定的固定大小。这意味着,如果需要增加数组的大小,则必须创建一个新数组,并且必须将数据从旧数组复制到新数组,这可能非常耗时且占用内存。内存分配问题: 分配大型数组可能会出现问题,特别是在内存有限的系统中。如果数组的大小太大,系统可能会耗尽内存,从而导致程序崩溃。插入和删除问题:从数组中插入或删除元素可能效率低下且耗时,因为插入或删除点之后的所有元素都必须移动以适应更改。浪费的空间: 如果数组未完全填充,则为该数组分配的内存中可能会出现浪费的空间。如果内存有限,这可能是一个问题。有限的数据类型支持: 数组对复杂数据类型(例如对象和结构)的支持有限,因为数组的元素必须全部具有相同的数据类型。缺乏灵活性: 与链表和树等其他数据结构相比,固定大小和对复杂数据类型的有限支持可能使数组缺乏灵活性。结构体相对于数组的优点:结构体可以存储不同类型的数据,而数组只能存储相似的数据类型。结构不像数组那样有大小限制。结构元素可能会也可能不会存储在连续位置,但数组元素会存储在连续位置。在结构中,可以实例化对象,而在数组中则不可能实例化对象。使用数组的常见问题为什么从数组中获取值的复杂度是 O(1)? 数组是一种线性数据结构。在数组中,获取值的操作需要常数时间,即 O(1)。由于数组在内存中连续分配,因此通过数组索引获取值是一种算术运算。所有算术运算都在恒定时间内完成,即O(1) 。 第 i个索引的地址= 基址 + 偏移量 = 第 0个索引的地址 + i ×(一个元素的大小)例子:数组中的内存分配在数组A[] = {8, 6, 7, 13, 8, 19}中要获取索引 4 处的值,我们需要存储该索引值的内存地址。该地址可以通过进行算术运算来获得,即索引 4 处的值的地址 = 索引 0 处的值的地址 + 4 × int的大小= 108 + 4 × 4 字节 索引 4 处的值的地址 = 124 A[4] = 地址 124 处的值 = 8什么时候应该使用数组而不是列表?当我们需要多维结构来存储数据时,我们使用数组而不是列表,因为列表只能是一维的。如果我们需要固定长度和静态分配,则使用数组而不是列表。当需要更快地处理数据时,可以使用数组而不是列表。原始数据类型可以直接存储在数组中,但不能存储在列表中,因此,我们使用数组而不是列表。在 Python 中使用数组而不是列表:我们在 python 中使用数组而不是列表,因为它需要更少的内存。python 中数组比列表快。数组可以直接处理算术运算,而列表则不能。所以我们使用数组而不是列表。对于较长的数据项序列,数组优于列表。算法练习给定一个大小为N-1的数组arr[] ,其中整数在[1, N]范围内,任务是从前N个整数中找到丢失的数字。 注意:列表中没有重复项。 输入: arr[] = {1, 2, 4, 6, 3, 7, 8}, N = 8 输出: 5 解释: 1 到 8 之间缺少的数字是 5 func findMissing(arr []int) int { var x = make([]int, len(arr)+1) for _, v := range arr { x[v-1] = 1 } for index, v := range x { if v == 0 { return index + 1 } } return -1 } func findMissing2(arr []int) int { // 求前 n 项自然数的和 l := len(arr) + 1 totalSum := (l * (l + 1)) / 2 var sum = 0 for _, v := range arr { sum += v } return totalSum - sum } // 测试函数 func TestFindMissing(t *testing.T) { var arr = []int{1, 2, 4, 6, 3, 7, 8} var except = 5 // var arr = []int{1, 2, 3, 5} // var except = 4 assert.Equal(t, except, findMissing(arr)) } 算法分析findMissing findMissing 首先初始化一个 N 大小的数组,目的就占位 将整数[1, N]的值-1 保存在初始化好的数组中对应的 index 位置上 第二个循环的目的就是找到值为 0 的index,因为目标数组中缺少相应位置的 val 最后就找到了确实的整数复杂度分析:时间复杂度:我们使用两个 for循环, 循环的次数取决于 array 的大小 所以时间复杂度为 O(2N) , 2 为常量,所以复杂度为 O(N)空间复杂度:我们开始初始化了一个 N+1 长度的数组 x, 所以空间复杂度为 O(1)findMissing2 算法的主要思想就是使用整数的前 N 项的和(n * (n + 1)) / 2 减去数组中所有数的和,就是缺失的整数复杂度分析:时间复杂度:findMissing2 中我们使用了一个 for,循环的次数取决于 array 的大小 所以时间复杂度为 O(N)空间复杂度:我们使用了两个变量 l 用来标记数组的长度和 totalSum 用来计算前 N 项的和。空间复杂度为 O(1)
0
0
0
浏览量1331
算法无限

实体关系抽取-3

A Novel Cascade Binary Tagging Framework for Relational Triple Extraction· 解决多个关系三元组共享相同实体的重叠三元组问题· 引入一个新的视角来看待关系三元组抽取任务,一个新的级联二进制标记框架(CASREL)· CasRel框架==直接对三元组进行建模,并在三元组的层次上设计一个训练目标==,而不是递进的去建模这里,我认为作者在将整个式子就是将获得注释句子中正确的三元组的概率,打碎,成为了获得句子中正确的主语s、在包含s的句子中,获得与关系相符的宾语的概率、在包含s的句子中,获得与关系不相符的宾语的概率的概率拼接,为作者提出的主语宾语的函数做提前准备。效果:主语标记器与宾语标记器以==深度双向变压器BERT上的二进制标记器(binary taggers on top of a deep bidirectional Transformer BERT)==“似然性”(likelihood)和“概率”(probability)意思相近,都是指某种事件发生的可能性。在统计学中,似然性”和“概率”又有明确的区分,概率用于在已知一些参数的情况下,预测接下来的观测所得到的结果,而似然性则是用于在已知某些观测所得到的结果时,对有关事物的性质的参数进行估计。经常会有文章提到先验概率,后验概率及似然概率,他们的意思可以这么理解:1. 先验——根据若干年的统计(经验)或者气候(常识),某地方下雨的概率;2. 似然——下雨(果)的时候有乌云(因/证据/观察的数据)的概率,即已经有了果,对证据发生的可能性描述;3. 后验——根据天上有乌云(原因或者证据/观察数据),下雨(结果)的概率;==例句:Jackie R. Brown Was Born In Washington, The Capital City Of United States Of America.== 在low level中可以检测出3个主语,在high level中可以实现对主语[Jackie R. Brown]打上0/1标记,上图为k=1时的状态。对于BERT Encoder(BERT编码器)使用一个预先训练的BERT来编码上下文信息· S:输入语句中子词的独热向量矩阵· WsW_sWs​:词嵌入矩阵· WpW_pWp​:位置嵌入矩阵· hαh_\alphahα​:隐藏态向量,即句子在第α\alphaα层的上下文表示· N:Transformer的块数 Cascade Decoder(级联解码器)主要思想:首先,从句子中检测主语。然后对于每个候选主语,找到所有可能的关系,判断这个关系是否能够将句子中的宾语与这个主语联系起来。【(2)(3)式】,由上图所示的两个模板构成,==Subject Tagger==,==Realation-Specific Object Taggers==Subject Tagger根据起始和结束位置标记器的结果来确定任意目标的跨度。第一个开始令牌Jackie,最近的结束令牌Brown,得出第一个主题跨度的检测结果是Jackie R.Brown。由此可以保证任何实体跨度的完整性。(匹配结束令牌时,不会考虑开始令牌前面的令牌) 参考文章链接:· 带你深入理解期望、方差、协方差的含义· 概率统计与机器学习:期望,方差,数学期望,样本均值,样本方差之间的区别· 机器学习中的数学——常用概率分布Relation-specific Object Taggers(特定于关系的宾语标记器)比如说:对于Work_in,Jackie R.Brown和Washington之间没有关系,则不会标记该跨度,即开始和结束的位置都标为0。 对于Born_in,Jackie R.Brown和Washington之间有关系,则该宾语标记器输出候选对象Washigton的跨度。 ==因此,high level模块能够同时识别与在low level中得到的主语有关的宾语和关系== Data Log-likelihood Objective实验结果 心得总结本文的最大亮点是绕开过去的方法——将关系建模为实体对的离散标记。==而是将关系抽象为主语与宾语的函数==,进而解决了重叠问题。目前joint方法基本就是魔改各种tag框架和decoding方式。但是,目前还是在概率论知识上有所欠缺,不能理解为什么作者用这些公式,和这些式子的效果。因此还是需要进一步补数理基础与机器学习基础。
0
0
0
浏览量67
算法无限

实体关系抽取-面向文本数据的关系抽取关键技术研究

1. 实体关系的==方向性语义==缺失,使得关系的判别缺乏对文本蕴含语义特征的利用提出——基于句法关系的方向敏感型句子级关系抽取算法· 利用依存句法树结构信息· 构建双向依存路径结构(新的文本策略解决过度剪裁)· 额外构建了平行的注意力机制2. 文档级==实体关系证据==隐含,支持实体关系的语义难以被感知提出——基于文本片段间语篇关系的文档级关系抽取方法· 利用文本片段之间蕴含的语篇关系构建文档图· 利用文档图构建里实体对间的语义关联· 利用语篇关系选择合适的、隐含的证据· 利用实体感知注意力机制,推理实体对间的关系3. 实体关系的==关键性语义==难以被挖掘,需要长距离的实体间构建有效的语义依赖关系提出——基于句法与语篇关系融合的文档级关系抽取方法· 利用语篇关系与句法关系构造字符级的文档图,使用斯坦纳树算法抽取最小生成树形成关键字符路径,获得与实体对嘴相关的语义依赖· 在文字和图形两个层面构建了双层注意力权重值来增强关键字符的语义特征表达· 训练过程中后置部署提高模型性能基础介绍监督关系抽取基于特征向量· 词汇特征:文本中词汇或词的属性,如全拼与缩写的关系· 句法特征:最常见的是词性特征:实词、虚词、量词等· 语义特征:指单一字符或多字符进行语义分类的结果,如牛顿发现了万有引力,判断是否是发现的关系,可以解决数据稀疏问题,缓解语义多样性带来的语义混淆问题· 语篇特征:句子与句子之间的关系或片段之间的关系基于核函数不需要手工构造特征向量空间,核函数包括树、图、序列等基于深度学习· CNN: 并行效能较好,能在关系抽取中高效地抽取到多个局部的语言结构特性· RNN:它能够综合考虑数据的前后关联关系,因此对于长文本、时序类信息具有较好的处理能力· GNN: 括通过依存句法树、共指关系图等方式来实现抽取· LSTM\GRU等弱监督关系抽取半监督学习(主动学习)远程监督学习(主流)· 假设一:“如果两个已经存在既定关系的实体对出现在某些句子中,那么所有句子描述的就都是这个关系”改进假设一的方法:· 假设二:“如果两个已经存在既定关系的实体对出现在某些句子集合中,那么至少有一个句子描述的是这个关系”· 假设三:“如果两个已经存在既定关系的实体对出现在某些句子集合中,那么这些句子总是能够隐形或显性地表达这个关系”· 依托深度学习的方法:  o 改进编码器的方法:从特征抽取器出发  o 句子级降噪:从数据集角度降低负样本对模型的影响  o 增强特征表达:通过注意力机制提升  o 引入外部知识:使用外部的库  o 即插即用方法:1.强化学习框架,删掉错误标记的句子 2.对抗学习框架,使用GAN提前对正负样本进行分类总之,弱监督学习能够在语料成本高的问题发挥作用无监督关系抽取可以脱离标记数据· 基于分布假设理论,使用聚类的方法,用频率最高的词作为关系名称· 通过限定性聚类使用同类型预料、通过统计过滤掉多重关系实体对缺乏明确的语义信息难以归一化开放域关系抽取更关注跨领域的应用首先,通过启发式规则构造数据集,训练一个贝叶斯分类器其次,利用单向抽取器产生的所有实体对与关系组合成三元组,选择高置信度元组最后,为每个三元组分配一个概率,以高频词作为最终结果词嵌入:是将文字转化为低维稠密向量,以避免数据稀疏、无意义、高纬度等问题。常用的方法有Word2vec等。 位置嵌入:为关系抽取模型提供了一种统一的感知字符位置的方法。句子级关系抽取是实体关系抽取的最小粒度· 序列方法:将文本直接按照序列方式进行处理,从早期的用单一网络到后期使用多重网络进行复合使用· 依存句法树法: Syntacitc Dependency tree,SDT。能够有效获取文本中字符间句法依赖信息,其由依存句法树结构构成,有利于文本降噪,字符间依存关系提高实体关系判别的准确率文档级关系抽取文档级关系抽取过程需要更多考虑因素 1.解决实体间相互指代的问题 2.解决长距离语义依赖 3.增强提及的语义表达问题· 序列方法:直接迁移句子的序列方法效果较差· 文档图法:具有更大的灵活性,早期的文档图是由一段文本中的字符节点以及句内依存边、句间邻接边、依存语篇边、实体共指边构成的图形研究内容· · 利用文本自身知识增强实体关系的语义表达设计了一种基于依存句法数结构的方向性敏感型关系抽取模型,该模型构建了具有方向差异性的双向依存路径结构,利用LSTM提取其中的高阶信息,并利用注意力机制捕获差异特点1. 模型将句子转换为最短依存路径结构,并设计基于实体和依存树树根的三点裁剪方法保留文本中重要信息2. 模型利用基于双向依存路径的多尺度卷积网络构建双向依存路径中的信息特征路径中的信息特征3. 模型利用LSTM的实体表征信息与卷积网络的双向依存路径信息的差异性,通过注意力机制标注实体间语义的方向性字符特征,增强实体方向性语义表示。如何标识文档中支持实体语义关系的隐含证据从文档中蕴含的语篇关系入手,将其作为外部知识引入到模型中,利用这类知识建模文档中各片段的支撑关系,并通过这种关系标记隐含的数据并推理实体间关系1. 模型通过语篇解析器将文档划分成文本片段,并通过外部知识标记片段间的语篇逻辑关系形成异构的语篇文档图2. 模型使用GNN在语篇文档图中提取实体结点和文本片段结点的特征信息3. 模型使用实体对间路径上的文本片段集合作为证据集合,并利用基于实体的感知注意力机制在证据集合中去标记重要文本片段,形成推理过程并汇聚成为高阶证据特征如何寻找文档中实体间关键的语义依赖路径通过引入句法关系与语篇关系构建了字符层面的文档图,并将句子层面的降噪模式引入到文档图。1. 模型将文档依次按照句子粒度和整体粒度分开解析为依存句法树集合和单个依存语篇树2. 将依存句法树集合按照依存语篇树规则连接成依存句法树森林,形成字符级层面的文档图3. 利用斯坦纳树算法以文档中的实体为终端结点构造最小生成树,实现了文档图层面的文本降噪,构建了多个实体共指间的最短语义依赖4. 利用GNN抽取降噪后的文档图中实体特征,并结合双层注意力机制和反向部署方法改进模型性能
0
0
0
浏览量1241
算法无限

实体关系抽取-6

UniRel: Unified Representation and Interaction for Joint Relational Triple Extraction解决实体和关系异构表示问题解决实体-实体交互和实体-关系交互异构建模问题通过一个串联的自然语言序列联合编码统一实体和关系的表示,同时使用一个交互映射系统来统一交互过去(如CasRel、PRGC)都在==关注于捕捉实体的上下文信息,但是忽略了同样重要的关系语义==。文中例子:由is_capital_of可以得出,主语应该是城市,宾语应该是国家。上图的这个三元组中,也可以从【longdon-is_capital_of】和【Uk-is_capital_of】得出Uk和london是相关的。我认为作者想表达的意思是过去把实体和关系分开来看了,但是实际上实体和关系本质上是有关联的,可以通过实体推断关系,通过关系推断实体,所以只捕捉实体的上下文是不够的。由此文章提出统一表示和交互的UniRel模型来解决上述问题。•将关系和实体都编码成序列来构造统一的表示,即联合编码•基于语义,首先将候选关系转换成文本,与输入句子构成一个连续的序列•使用BERT作为PLM预训练模型编码,并捕捉他们的信息相关性•通过Transformer的自注意力机制,在单个交互映射中对实体-实体、实体-关系建模•基于WebNLG和NYT创建了一个新的数据集UniRel方法:Problem Formulation给出X,识别出TUnified Reperesentation即对实体和关系进行联合编码在这一步之中,解决了实体和关系之间的异构表示问题Unified Interaction使用实体-实体、实体-关系之间的内在联系直接提取三元组。Entity-Entity Interaction实体-实体:标注那些可以形成三元组的实体对(因为存在关系才能有内在联系)。==进而(A,R,B)和(B,R,A)都可以识别出来,解决实体对重叠的问题。Entity-Relation Interaction对于关系R,∃\exists∃E为主/宾语R为关系的三元组时,E-R有内在联系。==但关系是具有方向性的,所以需要不对称的定义E-R关联以区分主语、宾语Interaction Discrimination解决了体-实体交互和实体-关系交互异构建模问题交互映射的可视化Training and Decoding首先从每个实体-关系的内在联系中识别出所有有效的主语和宾语2.然后枚举所有的候选实体对(存在于实体-实体之中的)这种解码,可以解决EPO和SEO的情况,SEO:(Homes,Lives_In,UK),EPO:(Uk,Contains,London)ExperimentsWebNLG的准确率超过了人类的新能。Effect of the Unified RepresentationUniRelunusedUniRel_{unused}UniRelunused​:在微调阶段,对那些被标记成[unused]的词向量随机初始化【目的为了将词无意义化,[unused]是Bert的占位符】,可以看出准确率在下降,进而得出==有意义的信息的重要性==。由于WebNLG数据少关系多,所有性能下降的更多。在WebNLG上不同数量样本进行训练Effect of the Unified InteractionUniRelseparateUniRel_{separate}UniRelseparate​:以单独的方式对提取到的关系序列(E-R、R-E)这两种关系进行建模。方式:由同一个BERT获得句子的向量矩阵序列和自然文本。然后使用两个转换层获得两个向量矩阵的Q、K。最后Interaction Map由Q、K的点生成出来因为对实体-关系作为独立输出,因此深层的Transformer只能独立的对实体对和关系的内部联系进行建模、可以看出性能下降。下图为F1指数Computational EfficiencyyL:三元组的个数在复杂的情况上,也超过了其他的基线模型,尤其是在SEO和EPO方面
0
0
0
浏览量1219
算法无限

跟我一起学算法:数据结构和算法- Hash 下

常用的哈希函数:哈希函数广泛应用于计算机科学和密码学中,具有多种用途,包括数据完整性、数字签名、密码存储等。哈希函数有很多种类型,每种都有自己的优点和缺点。以下是一些最常见的类型:1. SHA(安全哈希算法):SHA是由美国国家安全局(NSA)设计的一系列加密哈希函数。最广泛使用的 SHA 算法是 SHA-1、SHA-2 和 SHA-3。以下是每项的简要概述:SHA-1:SHA-1 是一种 160 位哈希函数,广泛用于数字签名和其他应用程序。然而,由于已知的漏洞,它不再被认为是安全的。SHA-2:SHA-2 是一系列哈希函数,包括 SHA-224、SHA-256、SHA-384 和 SHA-512。这些函数分别生成 224、256、384 和 512 位的哈希值。SHA-2 广泛应用于 SSL/TLS 等安全协议中,被认为是安全的。SHA-3:SHA-3是SHA家族的最新成员,被选为2012年NIST哈希函数竞赛的获胜者。它的设计比SHA-2更快、更安全,产生的哈希值为224, 256、384 和 512 位。2. CRC(循环冗余校验) :CRC是一种非加密散列函数,主要用于数据传输中的错误检测。它快速且高效,但不适合安全目的。CRC 背后的基本思想是将固定长度的校验值或校验和附加到消息的末尾。该校验和是根据消息的内容使用数学算法计算出来的,然后与消息一起传输。当接收到消息时,接收方可以使用相同的算法重新计算校验和,并将其与随消息发送的校验和进行比较。如果两个校验和匹配,接收方可以合理地确定消息在传输过程中没有损坏。用于 CRC 的具体算法取决于应用和所需的错误检测级别。一些常见的 CRC 算法包括 CRC-16、CRC-32 和 CRC-CCITT。3. MurmurHash:MurmurHash 是一种快速高效的非加密哈希函数,设计用于哈希表和其他数据结构。它不适合安全目的,因为它容易受到碰撞攻击。4. BLAKE2:BLAKE2 是一种加密哈希函数,旨在快速且安全。它是对流行的 SHA-3 算法的改进,广泛应用于需要高速哈希的应用程序,例如加密货币挖掘。BLAKE2 有两个版本:BLAKE2b 和 BLAKE2s。BLAKE2b 针对 64 位平台进行了优化,可生成高达 512 位的哈希值,而 BLAKE2s 针对 8 至 32 位平台进行了优化,可生成高达 256 位的哈希值。5. Argon2:Argon2 是一种内存硬密码哈希函数,旨在抵抗暴力攻击。它广泛用于密码存储,并受到密码哈希竞赛推荐。Argon2的主要目标是让攻击者难以使用暴力攻击或字典攻击等技术破解密码。它通过使用计算密集型算法来实现这一点,使攻击者很难在短时间内进行大量密码猜测。Argon2 具有几个关键功能,使其成为密码散列和密钥派生的有力选择:抵抗并行攻击:Argon2被设计为抵抗并行攻击,这意味着攻击者很难使用多个处理单元,例如GPU或ASIC,来加速密码破解。内存困难:Argon2 被设计为内存困难,这意味着它需要大量内存来计算哈希函数。这使得攻击者更难使用专用硬件来破解密码。可定制:Argon2 具有高度可定制性,允许用户调整内存使用、迭代次数和输出长度等参数,以满足其特定的安全要求。抵御旁道攻击:Argon2 旨在抵御旁道攻击,例如定时攻击或功率分析攻击,这些攻击可用于提取有关被散列的密码的信息。6. MD5(消息摘要5) :MD5是一种广泛使用的加密哈希函数,可产生128位哈希值。它快速且高效,但由于已知的漏洞,出于安全目的不再推荐使用它。MD5 背后的基本思想是获取任意长度的输入消息,并生成固定长度的输出,称为哈希值或消息摘要。该哈希值对于输入消息来说是唯一的,并且是使用涉及一系列逻辑运算的数学算法生成的,例如按位运算、模运算和逻辑函数。MD5 广泛应用于各种应用,包括数字签名、密码存储和数据完整性检查。然而,它已被证明存在使其容易受到攻击的弱点。特别是,有可能生成具有相同 MD5 哈希值的两条不同消息,这种漏洞称为冲突攻击。还有许多其他类型的哈希函数,每种都有其独特的功能和应用。哈希函数的选择取决于应用程序的具体要求,例如速度、安全性和内存使用情况。好的哈希函数的属性将每个项目映射到其自己的唯一槽的哈希函数称为完美哈希函数。如果我们知道项目并且集合永远不会改变,我们就可以构造一个完美的哈希函数,但问题是,给定任意项目集合,没有系统的方法来构造完美的哈希函数。幸运的是,即使哈希函数并不完美,我们仍然可以获得性能效率。我们可以通过增加哈希表的大小来实现完美的哈希函数,以便可以容纳每一个可能的值。因此,每个项目都会有一个独特的插槽。虽然这种方法对于少量项目是可行的,但当可能性数量很大时就不实用了。因此,我们可以构造我们的哈希函数来执行相同的操作,但在构造我们自己的哈希函数时必须注意的事情。一个好的哈希函数应该具有以下属性:高效可计算。应该均匀地分配键(每个表位置对于每个表位置来说都是同等的。应尽量减少碰撞。应具有较低的负载系数(表中的项目数除以表的大小)。使用哈希函数计算哈希值的复杂度时间复杂度:O(n)空间复杂度:O(1)哈希问题如果我们考虑上面的例子,我们使用的哈希函数是字母的总和,但是如果我们仔细检查哈希函数,那么问题可以很容易地形象化,对于不同的字符串,哈希函数开始生成相同的哈希值。例如:{“ab”,“ba”} 具有相同的哈希值,字符串{“cd”,“be”}也生成相同的哈希值等。这称为冲突,它会在搜索中产生问题、值的插入、删除和更新。什么是碰撞?散列过程为大密钥生成较小的数字,因此两个密钥有可能产生相同的值。新插入的键映射到已占用的键的情况,必须使用某种碰撞处理技术来处理。如何处理碰撞?处理碰撞主要有两种方法:链接法:开放寻址:1 链接法其思想是使散列表的每个单元指向具有相同散列函数值的记录的链表。链接很简单,但需要表外的额外内存。示例:我们给定了一个哈希函数,我们必须使用单独的链接方法在哈希表中插入一些元素以实现冲突解决技术。 h(5) = key % 5,  元素 = 12、15、22、25 和 37。让我们逐步看看如何解决上述问题:步骤1: 首先绘制一个空的哈希表,根据提供的哈希函数,哈希值的可能范围为0到4。步骤2: 现在将哈希表中的所有键一一插入。第一个插入的key是12,映射到桶号2,通过哈希函数 12%5=2计算得到。步骤3: 现在下一个键是22。它将映射到桶号2,因为 22%5=2 。但存储桶 2 已经被键 12 占用。步骤 4: 下一个键是 15。它将映射到槽号 0,因为 15%5=0。步骤5: 现在下一个键是25。它的桶号将是25%5=0。但是桶 0 已经被键 25 占用。因此单独的链接方法将通过创建到桶 0 的链表来再次处理冲突。因此,以这种方式,使用链接方法作为冲突解决技术。2) 开放寻址在开放寻址中,所有元素都存储在哈希表本身中。每个表条目包含一条记录或 NIL。查找元素时,我们会逐个检查表槽,直到找到所需元素或者明确该元素不在表中。2.a) 线性探测在线性探测中,从哈希的原始位置开始顺序搜索哈希表。如果我们得到的位置已被占用,那么我们检查下一个位置。算法:0.计算哈希键。即 key=数据%大小 1.检查 hashTable[key] 是否为空           直接通过hashTable[key] = data 存储值 2.如果哈希索引已经有一些值那么           使用key = (key+1) % size 检查下一个索引 3.检查下一个索引是否可用 hashTable[key] 然后存储该值。否则尝试下一个索引。 4.重复上述过程,直到找到空间。示例: 让我们考虑一个简单的哈希函数“key mod 5”,要插入的键序列是 50、70、76、85、93。步骤1: 首先绘制一个空的哈希表,根据提供的哈希函数,哈希值的可能范围为0到4。步骤2: 现在将哈希表中的所有键一一插入。第一个键是 50。它将映射到槽号 0,因为 50%5=0。因此将其插入插槽号 0 中。将 50 插入哈希表步骤3: 下一个键是70。它将映射到槽号0,因为70%5=0,但50已经在槽号0处,所以,搜索下一个空槽并将其插入。将 70 插入哈希表步骤 4: 下一个键是 76。它将映射到插槽编号 1,因为 76%5=1,但 70 已经位于插槽编号 1,因此,搜索下一个空插槽并将其插入。将 76 插入哈希表步骤5: 下一个键是93,它将映射到槽号3,因为93%5=3,所以将其插入槽号3。将 93 插入哈希表2.b) 二次探测二次探测是计算机编程中的一种开放寻址方案,用于解决哈希表中的哈希冲突。二次探测的操作方式是获取原始哈希索引并添加任意二次多项式的连续值,直到找到空槽。使用二次探测的示例序列是:H + 1 2、H + 2 2、H + 3 2、H + 4 2 ……………… H + k 2该方法也称为中方法,因为在该方法中,我们在第 i 次迭代中查找第 i 个探针(槽),并且 i = 0, 1, ... 的值。。。n – 1。我们总是从原始哈希位置开始。如果只有该位置被占用,那么我们检查其他插槽。令 hash(x) 为使用哈希函数计算的槽索引,n 为哈希表的大小。如果槽 hash(x) % n 已满,那么我们尝试 (hash(x) + 1 2 ) % n。 如果 (hash(x) + 1 2 ) % n 也满了,那么我们尝试 (hash(x) + 2 2 ) % n。 如果 (hash(x) + 2 2 ) % n 也满了,那么我们尝试 (hash(x) + 3 2 ) % n。将对 i 的所有值重复此过程,直到找到空槽为止示例:让我们考虑表大小 = 7,哈希函数为 Hash(x) = x % 7 ,冲突解决策略为 f(i) = i 2。插入 = 22、30 和 50步骤1: 创建一个大小为7的表。哈希表步骤 2 – 插入 22 和 30 Hash(25) = 22 % 7 = 1,由于索引 1 处的单元格为空,因此我们可以轻松地在槽 1 处插入 22。 Hash(30) = 30 % 7 = 2,由于索引 2 处的单元格为空,因此我们可以轻松地在槽 2 处插入 30。将键 22 和 30 插入哈希表中第 3 步: 插入 50 哈希值(25) = 50 % 7 = 1 在我们的哈希表中,槽 1 已经被占用。因此,我们将搜索槽 1+1 2,即 1+1 = 2, 再次发现插槽 2 被占用,因此我们将搜索单元格 1+2 2,即 1+4 = 5, 现在,单元格 5 未被占用,因此我们将 50 放入槽 5 中。在哈希表中插入键 502.c) 双重哈希双散列是开放寻址散列表中的一种冲突解决技术。双散列利用两个散列函数,第一个哈希函数是h1(k) ,它获取密钥并给出哈希表上的位置。但如果新位置未被占用或空着,那么我们可以轻松放置钥匙。但如果位置被占用(冲突),我们将使用辅助哈希函数h1(k) 结合来查找哈希表上的新位置。这种哈希函数的组合具有以下形式 h(k, i) = (h1(k) + i * h2(k)) % n i 是一个非负整数,表示碰撞次数,k = 正在散列的元素/键n = 哈希表大小。双哈希算法的复杂度:时间复杂度:O(n)示例: 将键 27, 43, 692, 72 插入大小为 7 的哈希表中。其中第一个哈希函数是h1(k) = k mod 7,第二个哈希函数是h2(k) = 1 + (k mod 5)第 1 步: 插入 27 27 % 7 = 6,位置 6 为空,因此将 27 插入到 6 插槽中。在哈希表中插入键 27第 2 步: 插入 43 43 % 7 = 1,位置 1 为空,因此将 43 插入 1 个槽中。步骤 3: 插入 692 692 % 7 = 6,但位置 6 已被占用,这是一次冲突 所以我们需要使用双重哈希来解决这种冲突。 h new = [h1(692) + i * (h2(692)] % 7  = [6 + 1 * (1 + 692 % 5)] % 7  = 9 % 7  = 2  ​  现在,由于 2 是一个空槽,  所以我们可以将 692 插入第二个插槽。在哈希表中插入键 692第 4 步: 插入 72 72 % 7 = 2,但位置 2 已被占用,这是一次冲突。 所以我们需要使用双重哈希来解决这种冲突。 h new = [h1(72) + i * (h2(72)] % 7  = [2 + 1 * (1 + 72 % 5)] % 7  = 5 % 7  = 5  ​  现在,因为 5 是一个空槽,  所以我们可以将 72 插入到第 5 个槽中。在哈希表中插入键 72哈希中的负载因子是什么意思?哈希表的负载因子可以定义为哈希表包含的项数除以哈希表的大小。负载因子是当我们想要重新哈希以前的哈希函数或想要向现有哈希表添加更多元素时使用的决定性参数。它帮助我们确定哈希函数的效率,即它告诉我们正在使用的哈希函数是否在哈希表中均匀分布键。负载因子=哈希表中的元素总数/哈希表的大小什么是重新哈希?顾名思义,重新哈希意味着再次哈希。基本上,当负载因子增加到超过其预定义值(负载因子的默认值为 0.75)时,复杂性就会增加。因此,为了克服这个问题,数组的大小增加(加倍),所有值再次散列并存储在新的双倍大小的数组中,以保持低负载因子和低复杂性。哈希数据结构的应用哈希在数据库中用于索引。哈希用于基于磁盘的数据结构。在Python等一些编程语言中,JavaScript哈希用于实现对象。哈希数据结构的实时应用哈希用于缓存映射以快速访问数据。哈希可用于密码验证。哈希在密码学中用作消息摘要。用于字符串中模式匹配的 Rabin-Karp 算法。计算字符串中不同子串的数量。哈希数据结构的优点哈希提供比其他数据结构更好的同步。哈希表比搜索树或其他数据结构更有效哈希为搜索、插入和删除操作提供平均恒定的时间。哈希数据结构的缺点当冲突较多时,哈希的效率很低。对于大量可能的键,实际上无法避免哈希冲突。哈希不允许空值。结论从上面的讨论中,我们得出结论,散列的目标是解决在集合中快速查找项目的挑战。例如,如果我们有数百万个英语单词的列表,并且我们希望找到一个特定术语,那么我们将使用散列来更有效地定位和查找它。在找到匹配项之前检查数百万个列表中的每个项目是低效的。散列通过在开始时将搜索限制为较小的单词集来减少搜索时间。
0
0
0
浏览量1565

履历