My Little World

learn and share


  • 首页

  • 分类

  • 标签

  • 归档

  • 关于
My Little World

解决过拟合问题

发表于 2024-09-17

什么是过拟合

训练得到的预测模型对于每个训练数据都非常吻合,导致对于新的测试数据无法正确评估的现象,就是过拟合
下面是线性回归和逻辑回归模型三种训练结果的展示

解决办法

  1. 收集更多的数据进行训练
  2. 选择和使用有价值的特性值参与运算
  3. 减小部分特征值(对于结果预测关系不大的特征值)的参数值(正则化)

正则化

在成本函数或者损失函数中增加正则化参数,避免过拟合

在梯度下降计算过程中,会使得每个参数在原来基础上乘以一个比1小的数据再去进行减法运算,从而使得梯度下降过程中实现参数进一步缩小

cost and gradient functions for both linear and logistic regression. Note:

Cost

The cost functions differ significantly between linear and logistic regression, but adding regularization to the equations is the same.

Gradient

The gradient functions for linear and logistic regression are very similar. They differ only in the implementation of $f_{wb}$.

线性回归正则化



逻辑回归正则化


实践案例

My Little World

逻辑回归模型

发表于 2024-09-17

背景&&解决问题

逻辑回归模型时一种解决二分类问题的算法

如果用线性回归去解决分类问题,会导致过拟合出现,每增加一个测试数据,都可能导致模型发生变化

通过使用sigmoid function函数,设置阈值,可以将线性回归产生的结果归类到两个结果上去

虽然目标是二分类,即结果只能是0 或者1
但是f(w,b) = g(w·x+b) 计算的结果值A只能无限接近这两个值
这里可以将A理解成结果是1 的可能性

决策边界

在逻辑回归模型f(w,b) = g(w·x+b) 中
z = w·x+b 又称为 决策边界,将不同结果的数据在坐标系中进行隔离
如果特征在z 中没有多项式运算,那么,得到的边界必定是直线的,但是如果有多项式,拿得到的边界线就是非线性的



成本函数

线性回归的平方误差算法的成本函数在应用到逻辑回归时,会产生多个局部最小值,再用梯度下降的算法去找参数时,无法找到最小值

逻辑回归引入逻辑损失函数,根据单个数据集随参数变化的趋势,判断整体的变化趋势
操作就是将原本平方误差除以2的操作移到求和之前,单独计算每个特征值的部分就是损失函数

Logistic Regression uses a loss function more suited to the task of categorization where the target is 0 or 1 rather than any number.

these definitions are used:

Loss is a measure of the difference of a single example to its target value while the

Cost is a measure of the losses over the training set

下面是推导过程和最终的式子





梯度下降找到w&b



My Little World

线性回归模型

发表于 2024-09-07

一些专业术语表达



x–> 训练数据入参,特征值,这里仅有一个,这个模型也被叫做 单变量线性回归模型 x(i) 第i个训练数据的x
y–> 训练数据入参,标记值, example 的lable ,y(i) 第i个训练数据的y
y-hat –> 出参,推测值, 模型(f(x)= wx + b) 的 结果值

成本函数

平方误差成本函数


在f(x) = wx+ b 的模型中
w: 斜率
b: 截距,intercepter 直接与Y轴交点距离远点距离

用J(w,b)表示成本函数,找到能够使J(w,b) 的值最小的w和b,就可以找到使f(x)最接近所有测试集的模型,最拟合训练数据的模型
先讨论在b = 0 的情况下,j(w) 随w 变化的趋势

找到使j(w) 最小的w,即U形线最凹的地方

现在把b的变化趋势也加入讨论,j(w,b) 随w,b 变化的趋势,将变成3d 的碗状

最凹的地方就是J(w,b)值最小的地方

利用等高线的表达方式,换一种视角找J(w,b)的最小值,就是将3D 图进行水平切割,得到关于w,b 的二维椭圆视图
不同(w,b) 组合可能会落在在同一条线上的,相同线上的J(w,b)值一样大
所以能够使J(w,b)值最小的(w,b)值,就是同心圆里面最里面那个圆上的多对(w,b), 当圆极限到一个点时,就只有一对(w,b)使J(w,b)最小

GradientDescentAlgorithm 梯度下降算法

是一种寻找使成本函数达到最小值参数的通用算法,现在不再局限与于f(x) = wx+b 单变量线性模型
对于多变量模型,也就意味着多个w, 这样J(w,b) 就会变成J(w0,…wi,b), J(w0, …wi) 的趋势不再是U形

梯度下降指的是,从一个点出发,环顾四周,找到能够下降的谷底的最快的方向,即梯度最陡的方向,下降一步,每走一步都按最陡方案下降,从而实现最快到达谷底的目的
达到谷底即意味着找到J(w0,…wi,b)最小值
但是梯度下降有一个特性,就是,虽然从同一点出发,如果第一步选择反向不同,或者走的方式不同,肯定会到达不同谷底
不同谷底意味着不同的J(w0,…wi,b)最小值,这些最小值,都叫做局部最小值

算法实现与理解

J(w,b)关于w 导数 表征 U形趋势线上随w 变化的梯度值,也就是斜率
前面的系数α表征 下坡的步伐大小 是一个0-1 的正小数值,称为学习率
w,b 同时变化,同时更新

当梯度大于0时,temp_w 逐渐变小,J(w,b) 的值也逐渐变小
当梯度小于0 时,temp_w 逐渐变大(负斜率,绝对值在变小), J(w,b)的值逐渐变小
说明J(w,b) 的值随梯度的变化符合随w的变化趋势
当斜率绝对值逐渐变小时,就是都朝J(w,b) 最小值聚拢

实验

关于学习率

太小会增加计算步骤,从而使梯度算法变慢
太大可能导致过冲,永远无法到达最小值;甚至无法实现聚拢趋势,导致发散

局部最小值

如果当前参数已经使得成本函数到达一个局部最小值,那么J(w,b) 关于w 导数值将会是0,那么temp_w 会始终停留在一个固定的值,不再变化
梯度下降算法将不会再进行下一步的计算,保持当前参数在当前的这个一个局部最小值的状态

随这个梯度下降,我们可以知道我们正在朝成本函数最小值靠近,在学习率固定情况下,更新步骤也在下降(斜率本身朝0在逐渐变小),说明没有学习率的变化,也能到达局部最小值
但如果同时将学习率调小,降低下降的步伐,可以更小幅度的一点点接近最小值,最终找到成本函数最小值

小结

线性回归模型,成本函数,和梯度下降算法


上述梯度下降算法具体来说是批量梯度下降,因为每一步都用到了所有训练数据

多元线性回归

以上讨论的是只有一个特征值x 作为输入的情况,下面要讨论的是同时有n个特征值做输入的模型,被称为多元线性回归模型

一些符号的表示方法

模型表达式


向量化

好处

  1. 代码实现简洁
  2. 运算速度快



对于多元线性回归 的 梯度下降算法

w 相关的计算转成向量计算,不同权重值,取对应特征值进行计算

实验

实践技巧

如何更快的找到合适的参数去拟合训练集

特征缩放

参数大小与特征值大小关系

如果某一特征值(x1 属于[1000,5000]范围)相对其他特征值(x2,x3,..xn,属于[1,10]范围),数值范围较大,其对应参数w1 则相对其他较小
反之,如果特征值较小,则参数较大,这样的规律可以更快的找到适合的参数


如果特征值的取值范围非常不同时,会导致梯度下降速度变慢
但如果将所有特征值想办法归一到相同的范围内,就可以加快梯度下降过程,降低计算步骤

缩放计算方式

除以最大值

特征值除以各自范围的最大值

均值归一化

特征值 减去平均值,再除以极差值(范围最大值减去范围最小值)

Z-SCORE归一化

特征值 减去 标准差,除以 平均值

说明

并不一定非要落在-1 到1 之间,落在相同的数量级之间就可接受

如何找到成本函数最小值

学习曲线

绘制成本函数随迭代次数变化的趋势图,称为学习曲线,主要是根据曲线走势判断梯度是否在收敛
如果随迭代次数增加,成本函数一直呈下降趋势,说明梯度正在下降,是正常的
当下降到一定程度,随迭代次数成本函数不再有明显下降,说明梯度收敛到了极限,也就是找到了成本函数的最低点
但如果随次数增加,成本函数出现上升,这是不正常的,则说明学习率选取过大,需要重新选取,或者代码出现错误

自动收敛测试

自行定义一个极小的值,当某次迭代后成本函数值小于该值时,就认为梯度下降关闭,成本函数达到最小值,停止迭代,取当前迭代wb做模型参数
缺点就是极小值难以估计,且结果不太可靠

如何选择合适学习率

根据学习曲线调整学习率
学习率太大会导致学习曲线出现上升趋势
太小会导致迭代次数增加

可以先对不同量级的学习率进行测试,观察学习曲线变化快慢
然后进行N倍测试,再比较,通过多组测试观察学习曲线变化快慢进行选取

特征工程

通过转换或者合并直接(原始)的特征值,生成新的特征值进行模型训练

多项式回归

如果特征值只有一个的情况下,不希望得到线性回归的直线模型,希望用曲线去拟合训练集
可以采用下面两种方法去拟合

  1. 通乘方构建新特征值,但是在进行训练时要进行归一化处理,因为乘方处理后,各个特质值范围的数量级会变得不同
  2. 通过开方进行新特征值构建

如何在colab中运行github 上的jupiter文件

下面这个链接时梯度下降实现的.ipynb 文件

https://github.com/kaieye/2022-Machine-Learning-Specialization/blob/main/Supervised%20Machine%20Learning%20Regression%20and%20Classification/week1/work/C1_W1_Lab05_Gradient_Descent_Soln.ipynb

复制域名之后的path
kaieye/2022-Machine-Learning-Specialization/blob/main/
Supervised%20Machine%20Learning%20Regression%20and%20Classification/week1/work/C1_W1_Lab05_Gradient_Descent_Soln.ipynb
拼接到 https://colab.research.google.com/github/ 后面

得到访问链接

https://colab.research.google.com/github/kaieye/2022-Machine-Learning-Specialization/blob/main/Supervised%20Machine%20Learning%20Regression%20and%20Classification/week1/work/C1_W1_Lab05_Gradient_Descent_Soln.ipynb

如果.ipynb有依赖其他py 文件,可以点击上传图标直接上传

但是要注意这里上传的文件都是运行时的状态,页面关闭即销毁,
如果想要保存自己修改后的.ipynb文件可以通过添加副本到google/drive 来实现

My Little World

机器学习定义

发表于 2024-09-02

什么是机器学习

filed of study that gives computers the ability to learn without being explicity programmed.

让计算机在没有明确编程的情况下学习的研究领域

– Arthur Samuel (1959)

Supervised Learning 监督学习

learns from being given ‘right answers’

learns from data labeled with ‘right answers’

regression algorithms 回归算法

从无限多可能数字中预测数字

predict a number 预测无限可能中的一种
infinitely many possible outputs

classify algorithms 分类算法

predict categories 预测有限分类中的一类
small number of possible outputs

Unsupervised Learning 无监督学习

find sth interesting in unlabeled data
data only comes with input x ,but not output labels y, algorithm has to find structure in the data

clustering algorithms 聚类算法

place the unlabeled data (automatically group) into different clusters

eg. google news, grouping customers

==> group similar data points together

Anomaly detection 异常检测

find unusal data points (events)

eg. 金融诈骗中的交易异常

Dimensionality reduction 降维算法

compressn data using fewer numbers

压缩大数据集到小数据集,同时丢失尽可能少的信息

Reinforcement Learning 强化学习

My Little World

Agent

发表于 2024-08-03

todo 下载 代码,看原理流程,运行耗token, 测试

My Little World

funtuning

发表于 2024-08-03

跑测试
重新听

My Little World

transformer

发表于 2024-08-03

本篇文档由浅入深的方式逐步说明transformer的实现原理
不做更深入的公式的解析详见

什么是模型

模型就是一个数学公式,所谓大模型,就是拥有n多个大量参数的复杂的数学公式

对于分类问题:给定一组输入数据,经过一系列数学公式计算后,输出n个概率,分别代表该用户对话属于某分类的概率
确定数学公式过程就是寻找参数过程
比如具备线性关系的数据,可以假设公式如下,然后寻找a,和 b 两个参数:
1、公式:y = ax + b
2、参数:a = 50, b = -100

如果有多个维度来代表输入信息(由单个值演变成向量),每个维度对结果的影响程度不同,则不同维度前的参数大小就不同,进而演变成常见的权重,从而尽可能计算出精确的结果,这种计算需要借助计算机实现

神经网络

输入向量x和输出向量y之间,增加了一层z向量,
并且用上述格式的计算公式去计算z向量和y向量中的每一个数值的结构 就成为神经网络

不同神经网络的层数和每层结构节点数一般不同,一般由开发者针对不同场景进行设计测试来寻找最佳方案






对于上述图片识别的例子,中间z层相当于在进行边缘计算,再通过进行大批量数字图片的训练,找出相同数字的边缘数字特点,从而控制激活函数的参数,最终给出0-9的数字的识别概率

在x层和y层之间,加入多层z向量,用以提取更深层特征,这种多层结构,叫做深度神经网络
而通过计算机完成大规模数学计算以找到相对更优的w参数组合的过程,就叫做机器学习,也就是我们所说的模型训练。

transformer

tokenization - 文本变成Token

通过将文字进一步切割成基础单位的语义信息,可以帮助模型进行更好的处理
⽐如:”subword”这个词,可以拆分成”sub”和”word”两个⼦词,”sub”是⼀个通⽤的前缀可以和其他组合词的”sub”前缀合并,这样⼤模型将会学会使⽤”sub”前缀。类似的,”encoded”可以拆解为”encod”+”ed”,“encoding”可以拆解为“encod”+”ing”,这样两个词的核⼼部分”encod”被提取出来了,⽽且还得到时态信息。
子词的处理方式,会让一段内容的Token数量多于单词数量

对于不同语种的分词形式不同分词模型的结果很可能差异较大,根本原因在于底层分词机制的不同

Embedding - Token变成向量

one-hot编码

one-hot编码:将每个Token映射为向量,向量的维度为Token数量,向量的值全为0,只有对应Token的索引为1

one-hot的一些问题

1、维度过高,过于稀疏:
容纳3000个汉字就需要3000个3000维向量,容纳5000个汉字则需要5000个5000维向量;
2、没有体现出“距离”概念:
如果两个字之间的意思相近,那两个对应的向量求“距离”的时候,就应该更相近;


3、没有数学或逻辑关系:
最好能满足:国王 - 男人 + 女人 = 女王

解决:将Token映射为embedding向量

Embedding

详见
我们可以通过一个模型把一个Token变成一个Embedding向量、把一个单词变成一个Embedding向量、把一句话变成一个Embedding向量、把一张图变成一个Embedding向量

这样在模型训练的时候,就可以根据输入值本身维度信息,根据相似度信息(距离远近),进行相关关联信息(亲密关系,是不是经常一起搭配使用,同时出现频率如何)提取,即语义提取,从而进行结果预测

几个Embedding模型
1、Word2Vec:Google在2013年提出的概念,也是一个预训练模型
2、OpenAI Embedding Models : OpenAI的API

Transformer Encoder & Decoder

RNN Encoder & Decoder

以RNN为核心的Encoder Decoder
将输入句子进行编码,编码后的结果作为解码器的输入,解码器进行解码,最终得到结果
Encoder一般用来做分析(语义分析,输入是否在Rag资料里面,翻译前后语义是否一致),Decoder一般用来做生成

从图中可以看出,只用最后一次生成的向量代表了之前的所有信息,加权求和过程中,前置文字的信息必然权重越来越小,未免会造成语义丢失,且处理过程循环依次进行,导致计算效率下降,解码结果的精确度也会随句子变长而下降

因此基于rnn 的编解码模型存在如下问题

1、信息丢失
2、无法处理较长句子
3、不能并行计算

Attention Encoder & Decoder

tansformer 采用 自注意力机制进行编解码,从而规避以上问题

Attention 注意力机制


根据一个输入的维度和已有数据的相关系数计算其他维度数据

根据多个维度的数据和相关系数,计算多个维度的数据


如果是一段文字用多维度表示,输入也是多维度信息,那么,多维度信息之间也用相关系数联系,就可以实现推理计算

自注意力机制

上述注意力机制的QKV的值都换成基于输入自身,视角变成输入的一个字和其他每个字之间的相似度,就相当于在计算一句话中的这个字与这段话中其他字的相关性情况,详见

跑一遍相当于计算句子中每个字与句子中其他字之间的相似性,一个向量代表2个字之间的相似性
再将两个字之间的相似性作为输入再计算一遍,相当于每个向量拥有4个字的相似性,一个向量代表4个字之间的相似性
多循环几次,一个向量就可以尽可能涵盖一个句子中尽可能多的语义信息,方便后续进行decode

解码器

通过自注意机制处理后的embedding可以携带更多语义信息进行推测解码
每次解码的时候包含的输入是上一步的输出和编码的所有信息,因此,解码器的输入是上一步的输出,输出是下一步的输入


对于多头注意力机制,详见
既然模型解码阶段都要进行自注意机制,那么,编码阶段的自注意力机制流程可以直接放在解码器的预测值处理阶段进行
整体流程本来就是在预测下一个字是什么,即续写,所以,当代大语言模型一般采用纯解码器的架构进行文字推测生成

小结

transformer 架构通过自注意力机制处理embedding后的文字信息,这样在推测解码阶段天然形成了语音关联信息,即解码前相关的语义信息解码器是可以直接拿到的,后续就是根据语义相关性大小进行概率计算
因此,如果prompt中的内容越多,即相关主题语义的信息量越大,解码器在推测时倾向于获取相应主题的语义文字的概率越大,准确率越高

to do
整理书中知识点

My Little World

LangSmith

发表于 2024-07-28

LangSmith

  1. 安装 LangSmith

    1
    pip install --upgrade langsmith
  2. 注册账号,并申请一个 LANGCHAIN_API_KEY

  3. 在环境变量中设置以下值

    1
    2
    3
    export LANGCHAIN_TRACING_V2=true
    export LANGCHAIN_PROJECT=YOUR_PROJECT_NAME #自定义项目名称(可选)
    export LANGCHAIN_API_KEY=LANGCHAIN_API_KEY # LangChain API Key
  4. 程序中的调用将自动被记录

    1
    2
    3
    4
    import os
    from datetime import datetime
    os.environ["LANGCHAIN_TRACING_V2"] = "true"
    os.environ["LANGCHAIN_PROJECT"] = "hello-world-"+datetime.now().strftime("%d/%m/%Y %H:%M:%S")

基本功能

  1. Traces
  2. LLM Calls
  3. Monitor
  4. Playground
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
from langchain.prompts import (
ChatPromptTemplate,
HumanMessagePromptTemplate,
)
from langchain_core.output_parsers import StrOutputParser
from langchain_openai import ChatOpenAI
from langchain_core.runnables import RunnablePassthrough

model = ChatOpenAI(model="gpt-3.5-turbo")

prompt = ChatPromptTemplate.from_messages([
HumanMessagePromptTemplate.from_template("Say hello to {input}!")
])

# 定义输出解析器
parser = StrOutputParser()

chain = (
{"input": RunnablePassthrough()}
| prompt
| model
| parser
)
chain.invoke("王卓然")

在线标注(在平台上进行标注)

上传已有数据集
定义评估函数
运行测试

基于 LLM 的评估函数

1
https://docs.smith.langchain.com/evaluation/faq/evaluator-implementations
My Little World

Langfuse

发表于 2024-07-28

维护一个生产级的 LLM 应用,我们需要做什么

  1. 各种指标监控与统计:访问记录、响应时长、Token 用量、计费等等
  2. 调试 Prompt
  3. 测试/验证系统的相关评估指标
  4. 数据集管理(便于回归测试)
  5. Prompt 版本管理(便于升级/回滚)

针对以上需求,目前有两个生产级 LLM App 维护平台

  1. LangFuse: 开源 + SaaS(免费/付费),LangSmith 平替,可集成 LangChain 也可直接对接 OpenAI API;
  2. LangSmith: LangChain 的官方平台,SaaS 服务(免费/付费),非开源,企业版支持私有部署;

根据自己的技术栈,选择:

  1. LangFuse:开源平台,支持 LangChain 和原生 OpenAI API
  2. LangSmith: LangChain 的原始管理平台
  3. Prompt Flow:开源平台,支持 Semantic Kernel

LangFuse

开源,支持 LangChain 集成或原生 OpenAI API 集成

官方网站:https://langfuse.com/

项目地址:https://github.com/langfuse

文档地址:https://langfuse.com/docs

API文档:https://api.reference.langfuse.com/

  1. Python SDK:
    https://python.reference.langfuse.com/
  2. JS SDK:
    https://js.reference.langfuse.com/

  3. 通过官方云服务使用:

    1. 注册: cloud.langfuse.com
    2. 创建 API Key
1
2
LANGFUSE_SECRET_KEY="sk-lf-..."
LANGFUSE_PUBLIC_KEY="pk-lf-..."
  1. 通过 Docker 本地部署
1
2
3
4
5
6
7
8
9
10
11
12
13
# Clone repository
git clone https://github.com/langfuse/langfuse.git
cd langfuse

# Run server and db
docker compose up -d

# 在自己部署的系统中生成上述两个 KEY
# 并在环境变量中指定服务地址

LANGFUSE_SECRET_KEY="sk-lf-..."
LANGFUSE_PUBLIC_KEY="pk-lf-.."
LANGFUSE_HOST="http://localhost:3000"

几个基本概念

  1. Trace 一般表示用户与系统的一次交互,其中记录输入、输出,也包括自定义的 metadata 比如用户名、session id 等;
  2. 一个 trace 内部可以包含多个子过程,这里叫 observarions;
  3. Observation 可以是多个类型:
    1. Event 是最基本的单元,用于记录一个 trace 中的每个事件;
    2. Span 表一个 trace 中的一个”耗时”的过程;
    3. Generation 是用于记录与 AI 模型交互的 span,例如:调用 embedding 模型、调用 LLM。
  4. Observation 可以嵌套使用。

通过装饰器记录(上报)

observe() 装饰器的参数

1
2
3
4
5
6
7
8
9
def observe(
self,
*,
name: Optional[str] = None, # Trace 或 Span 的名称,默认为函数名
as_type: Optional[Literal['generation']] = None, # 将记录定义为 Observation (LLM 调用)
capture_input: bool = True, # 记录输入
capture_output: bool = True, # 记录输出
transform_to_string: Optional[Callable[[Iterable], str]] = None # 将输出转为 string
) -> Callable[[~F], ~F]
1
2
3
4
5
6
7
8
9
10
11
12
13
from langfuse.decorators import observe
from langfuse.openai import openai # OpenAI integration

@observe()
def run():
return openai.chat.completions.create(
model="gpt-3.5-turbo",
messages=[
{"role": "user", "content": "对我说Hello, World!"}
],
).choices[0].message.content

print(run())

通过 langfuse_context 记录 User ID、Metadata 等

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
from langfuse.decorators import observe, langfuse_context
from langfuse.openai import openai # OpenAI integration

@observe()
def run():
langfuse_context.update_current_trace(
name="HelloWorld",
user_id="wzr",
metadata={"test":"test value"}
)
return openai.chat.completions.create(
model="gpt-3.5-turbo",
messages=[
{"role": "user", "content": "对我说Hello, World!"}
],
).choices[0].message.content

print(run())

通过 LangChain 的回调集成

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
from langfuse.decorators import langfuse_context, observe

@observe()
def run():
langfuse_context.update_current_trace(
name="LangChainDemo",
user_id="wzr",
)

# 获取当前 LangChain 回调处理器
langfuse_handler = langfuse_context.get_current_langchain_handler()

return chain.invoke(input="AGIClass", config={"callbacks": [langfuse_handler]})

print(run())

用 Trace 记录一个多次调用 LLM 的过程

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
import uuid
from langfuse.decorators import langfuse_context, observe

# 主流程
@observe()
def verify_question(
question: str,
outlines: str,
question_list: list,
user_id: str,
) -> bool:
langfuse_context.update_current_trace(
name="AGIClassAssistant",
user_id=user_id,
)
langfuse_handler = langfuse_context.get_current_langchain_handler()
# 判断是否需要回答
if need_answer_chain.invoke(
{"user_input": question, "outlines": outlines},
config={"callbacks": [langfuse_handler]}
) == 'Y':
# 判断是否为重复问题
if is_duplicated_chain.invoke(
{"user_input": question,
"question_list": "\n".join(question_list)},
config={"callbacks": [langfuse_handler]}
) == 'N':
question_list.append(question)
return True
return False

用 Session 记录一个用户的多轮对话

1
2
3
4
5
6
7
8
9
10
11
12
@observe()
def chat_one_turn(user_input, user_id, turn_id):
langfuse_context.update_current_trace(
name=f"ChatTurn{turn_id}",
user_id=user_id,
session_id="chat-"+now.strftime("%d/%m/%Y %H:%M:%S")
)
langfuse_handler = langfuse_context.get_current_langchain_handler()
messages.append(HumanMessage(content=user_input))
response = llm.invoke(messages, config={"callbacks": [langfuse_handler]})
messages.append(response)
return response.content

数据集与测试

在线标注(在平台上进行标注)

上传已有数据集

定义评估函数

运行测试

Prompt 调优与回归测试

Prompt 版本管理

目前只支持 Langfuse 自己的 SDK

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
# 按名称加载
prompt = langfuse.get_prompt("need_answer_v1")

# 按名称和版本号加载
prompt = langfuse.get_prompt("need_answer_v1", version=2)

# 对模板中的变量赋值
compiled_prompt = prompt.compile(input="老师好", outlines="test")

print(compiled_prompt)

# 获取 config

prompt = langfuse.get_prompt("need_answer_v1", version=5)

print(prompt.config)

如何比较两个句子的相似性:一些经典 NLP 的评测方法(选)

用途:比较llm 返回值和预期值,从而进行打分计算

  1. 编辑距离:也叫莱文斯坦距离(Levenshtein),是针对二个字符串的差异程度的量化量测,量测方式是看至少需要多少次的处理才能将一个字符串变成另一个字符串。
    1. 具体计算过程是一个动态规划算法:https://zhuanlan.zhihu.com/p/164599274
    2. 衡量两个句子的相似度时,可以以词为单位计算
  2. BLEU Score:
    1. 计算输出与参照句之间的 n-gram 准确率(n=1…4)
    2. 对短输出做惩罚
    3. 在整个测试集上平均下述值
    4. 函数库:https://www.nltk.org/_modules/nltk/translate/bleu_score.html
  3. Rouge Score:
    1. Rouge-N:将模型生成的结果和标准结果按 N-gram 拆分后,只计算召回率;
    2. Rouge-L: 利用了最长公共子序列(Longest Common Sequence)
    3. 函数库:https://pypi.org/project/rouge-score/
    4. 对比 BLEU 与 ROUGE:
      1. BLEU 能评估流畅度,但指标偏向于较短的翻译结果(brevity penalty 没有想象中那么强)
      2. ROUGE 不管流畅度,所以只适合深度学习的生成模型:结果都是流畅的前提下,ROUGE 反应参照句中多少内容被生成的句子包含(召回)
  4. METEOR: 另一个从机器翻译领域借鉴的指标。与 BLEU 相比,METEOR 考虑了更多的因素,如同义词匹配、词干匹配、词序等,因此它通常被认为是一个更全面的评价指标。
    1. 对语言学和语义词表有依赖,所以对语言依赖强。

此类方法常用于对文本生成模型的自动化评估。实际使用中,我们通常更关注相对变化而不是绝对值(调优过程中指标是不是在变好)。

基于 LLM 的测试方法

LangFuse 集成了一些原生的基于 LLM 的自动测试标准。

具体参考:https://langfuse.com/docs/scores/model-based-evals

划重点:此类方法,对于用于评估的 LLM 自身能力有要求。需根据具体情况选择使用。

My Little World

LangChain

发表于 2024-07-28

LangChain 也是一套面向大模型的开发框架(SDK)

  1. LangChain 是 AGI 时代软件工程的一个探索和原型
  2. 学习 LangChain 要关注接口变更

LangChain 的核心组件

  1. 模型 I/O 封装
    • LLMs:大语言模型
    • Chat Models:一般基于 LLMs,但按对话结构重新封装
    • PromptTemple:提示词模板
    • OutputParser:解析输出
  2. 数据连接封装
    • Document Loaders:各种格式文件的加载器
    • Document Transformers:对文档的常用操作,如:split, filter, translate, extract metadata, etc
    • Text Embedding Models:文本向量化表示,用于检索等操作
    • Verctorstores: (面向检索的)向量的存储
    • Retrievers: 向量的检索
  3. 记忆封装
    • Memory:这里不是物理内存,从文本的角度,可以理解为”上文”、”历史记录”或者说”记忆力”的管理
  4. 架构封装
    • Chain:实现一个功能或者一系列顺序功能组合
    • Agent:根据用户输入,自动规划执行步骤,自动选择每步需要的工具,最终完成用户指定的功能
      • Tools:调用外部功能的函数,例如:调 google 搜索、文件 I/O、Linux Shell 等等
      • Toolkits:操作某软件的一组工具集,例如:操作 DB、操作 Gmail 等等
  5. Callbacks

模型 I/O 封装

通过模型封装,实现不同模型的统一接口调用

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
from langchain_openai import ChatOpenAI
from langchain.schema import (
AIMessage, # 等价于OpenAI接口中的assistant role
HumanMessage, # 等价于OpenAI接口中的user role
SystemMessage # 等价于OpenAI接口中的system role
)
from langchain_community.chat_models import QianfanChatEndpoint
from langchain_core.messages import HumanMessage

# OpenAI 模型封装
llm = ChatOpenAI(model="gpt-3.5-turbo") # gpt-4o 默认是gpt-3.5-turbo
response = llm.invoke("你是谁")
print(response.content)

# 多轮对话 Session 封装
messages = [
SystemMessage(content="你是AGIClass的课程助理。"),
HumanMessage(content="我是学员,我叫王卓然。"),
AIMessage(content="欢迎!"),
HumanMessage(content="我是谁")
]

ret = llm.invoke(messages)

print(ret.content)

# 国产模型 其它模型分装在 langchain_community 底包中
import os

llm = QianfanChatEndpoint(
qianfan_ak=os.getenv('ERNIE_CLIENT_ID'),
qianfan_sk=os.getenv('ERNIE_CLIENT_SECRET')
)

messages = [
HumanMessage(content="你是谁")
]

ret = llm.invoke(messages)

print(ret.content)

Prompt 模板封装

1
2
3
4
5
6
7
from langchain.prompts import (
ChatPromptTemplate,
HumanMessagePromptTemplate,
SystemMessagePromptTemplate,
MessagesPlaceholder,
PromptTemplate
)
  1. PromptTemplate 可以在模板中自定义变量
  2. ChatPromptTemplate 用模板表示的对话上下文
  3. MessagesPlaceholder 把多轮对话变成模板
  4. 从文件加载 Prompt 模板: PromptTemplate.from_file

把Prompt模板看作带有参数的函数

输出封装 OutputParser

自动把 LLM 输出的字符串按指定格式加载。
LangChain 内置的 OutputParser 包括:

  1. ListParser
  2. DatetimeParser
  3. EnumParser
  4. JsonOutputParser
  5. PydanticParser
  6. XMLParser

等等

Pydantic (JSON) Parser

自动根据 Pydantic 类的定义,生成输出的格式说明

Auto-Fixing Parser

利用 LLM 自动根据解析异常修复并重新解析

小结

  1. LangChain 统一封装了各种模型的调用接口,包括补全型和对话型两种
  2. LangChain 提供了 PromptTemplate 类,可以自定义带变量的模板
  3. LangChain 提供了一些列输出解析器,用于将大模型的输出解析成结构化对象;额外带有自动修复功能。
  4. 上述模型属于 LangChain 中较为优秀的部分;美中不足的是 OutputParser 自身的 Prompt 维护在代码中,耦合度较高。

数据连接封装

1
2
3
4
5
6
7
8
9
10
# 文档加载器:Document Loaders
from langchain_community.document_loaders import PyMuPDFLoader
# 文档处理器 TextSplitter
from langchain_text_splitters import RecursiveCharacterTextSplitter
# 向量数据库与向量检索
from langchain_openai import OpenAIEmbeddings
from langchain_text_splitters import RecursiveCharacterTextSplitter
from langchain_community.vectorstores import FAISS
from langchain_openai import ChatOpenAI
from langchain_community.document_loaders import PyMuPDFLoader

类似 LlamaIndex,LangChain 也提供了丰富的 Document Loaders 和 Text Splitters

小结

  1. 文档处理部分,建议在实际应用中详细测试后使用
  2. 与向量数据库的链接部分本质是接口封装,向量数据库需要自己选型

记忆封装:Memory

1
2
3
4
5
6
7
8
# 对话上下文:ConversationBufferMemory
from langchain.memory import ConversationBufferMemory, ConversationBufferWindowMemory

# 只保留一个窗口的上下文:ConversationBufferWindowMemory
from langchain.memory import ConversationBufferWindowMemory

# 通过 Token 数控制上下文长度:ConversationTokenBufferMemory
from langchain.memory import ConversationTokenBufferMemory

更多类型

  1. ConversationSummaryMemory: 对上下文做摘要
    • https://python.langchain.com/docs/modules/memory/types/summary
  2. ConversationSummaryBufferMemory: 保存 Token 数限制内的上下文,对更早的做摘要
    • https://python.langchain.com/docs/modules/memory/types/summary_buffer
  3. VectorStoreRetrieverMemory: 将 Memory 存储在向量数据库中,根据用户输入检索回最相关的部分
    • https://python.langchain.com/docs/modules/memory/types/vectorstore_retriever_memory

小结

  1. LangChain 的 Memory 管理机制属于可用的部分,尤其是简单情况如按轮数或按 Token 数管理;
  2. 对于复杂情况,它不一定是最优的实现,例如检索向量库方式,建议根据实际情况和效果评估;
  3. 但是它对内存的各种维护方法的思路在实际生产中可以借鉴。

LangChain Expression Language(LCEL)是一种声明式语言,可轻松组合不同的调用顺序构成 Chain。LCEL 自创立之初就被设计为能够支持将原型投入生产环境,无需代码更改,从最简单的”提示+LLM”链到最复杂的链(已有用户成功在生产环境中运行包含数百个步骤的 LCEL Chain)。

LCEL 的一些亮点包括:

  1. 流支持
  2. 异步支持
  3. 优化的并行执行
  4. 重试和回退
  5. 访问中间结果
  6. 输入和输出模式
  7. 无缝 LangSmith 跟踪集成
  8. 无缝 LangServe 部署集成

原文:https://python.langchain.com/docs/expression_language/

Pipeline 式调用 PromptTemplate, LLM 和 OutputParser

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
from langchain_openai import ChatOpenAI
from langchain.prompts import ChatPromptTemplate
from langchain_core.output_parsers import StrOutputParser, PydanticOutputParser
from langchain_core.runnables import RunnablePassthrough
from langchain_core.pydantic_v1 import BaseModel, Field, validator
from typing import List, Dict, Optional
from enum import Enum
import json

# LCEL 表达式
runnable = (
{"text": RunnablePassthrough()} | prompt | model | parser
)

# 直接运行
ret = runnable.invoke("不超过100元的流量大的套餐有哪些")

# 流式输出
for s in runnable.stream("不超过100元的流量大的套餐有哪些"):
print(s, end="")

使用 LCEL 的价值,也就是 LangChain 的核心价值。

官方从不同角度给出了举例说明:https://python.langchain.com/docs/expression_language/why

通过 LCEL,还可以实现

  1. 配置运行时变量:https://python.langchain.com/docs/expression_language/how_to/configure
  2. 故障回退:https://python.langchain.com/docs/expression_language/how_to/fallbacks
  3. 并行调用:https://python.langchain.com/docs/expression_language/how_to/map
  4. 逻辑分支:https://python.langchain.com/docs/expression_language/how_to/routing
  5. 调用自定义流式函数:https://python.langchain.com/docs/expression_language/how_to/generators
  6. 链接外部 Memory:https://python.langchain.com/docs/expression_language/how_to/message_history

更多例子:https://python.langchain.com/docs/expression_language/cookbook/

什么是智能体(Agent)

将大语言模型作为一个推理引擎, 给定一个任务,智能体自动生成完成任务所需的步骤,执行相应动作(例如选择并调用工具),直到任务完成。

先定义一些工具:Tools

  1. 可以是一个函数或三方 API
  2. 也可以把一个 Chain 或者 Agent 的 run()作为一个 Tool

下载一个现有的 Prompt 模板

1
2
react_prompt = hub.pull("hwchase17/react")
print(react_prompt.template)

直接定义执行执行调用

1
2
3
4
5
6
7
8
9
10
11
12
from langchain_openai import ChatOpenAI
from langchain.agents import AgentExecutor, create_react_agent

llm = ChatOpenAI(model_name='gpt-4o', temperature=0, model_kwargs={"seed":23})

# 定义一个 agent: 需要大模型、工具集、和 Prompt 模板
agent = create_react_agent(llm, tools, react_prompt)
# 定义一个执行器:需要 agent 对象 和 工具集
agent_executor = AgentExecutor(agent=agent, tools=tools, verbose=True)

# 执行
agent_executor.invoke({"input": "2024年周杰伦的演唱会星期几"})

LangServe

LangServe 用于将 Chain 或者 Runnable 部署成一个 REST API 服务。

1
2
3
4
5
6
# 安装 LangServe
# !pip install --upgrade "langserve[all]"

# 也可以只安装一端
# !pip install "langserve[client]"
# !pip install "langserve[server]"

LangChain.js

Python 版 LangChain 的姊妹项目,都是由 Harrison Chase 主理。

项目地址:https://github.com/langchain-ai/langchainjs

文档地址:https://js.langchain.com/docs/

特色:

  1. 可以和 Python 版 LangChain 无缝对接
  2. 抽象设计完全相同,概念一一对应
  3. 所有对象序列化后都能跨语言使用,但 API 差别挺大,不过在努力对齐

支持环境:

  1. Node.js (ESM and CommonJS) - 18.x, 19.x, 20.x
  2. Cloudflare Workers
  3. Vercel / Next.js (Browser, Serverless and Edge functions)
  4. Supabase Edge Functions
  5. Browser
  6. Deno

安装:

1
npm install langchain

当前重点:

  1. 追上 Python 版的能力(甚至为此做了一个基于 gpt-3.5-turbo 的代码翻译器)
  2. 保持兼容尽可能多的环境
  3. 对质量关注不多,随时间自然能解决

LangChain 与 LlamaIndex 的错位竞争

  1. LangChain 侧重与 LLM 本身交互的封装
    • Prompt、LLM、Memory、OutputParser 等工具丰富
    • 在数据处理和 RAG 方面提供的工具相对粗糙
    • 主打 LCEL 流程封装
    • 配套 Agent、LangGraph 等智能体与工作流工具
    • 另有 LangServe 部署工具和 LangSmith 监控调试工具
  2. LlamaIndex 侧重与数据交互的封装
    • 数据加载、切割、索引、检索、排序等相关工具丰富
    • Prompt、LLM 等底层封装相对单薄
    • 配套实现 RAG 相关工具
    • 有 Agent 相关工具,不突出
  3. LlamaIndex 为 LangChain 提供了集成
    • 在 LlamaIndex 中调用 LangChain 封装的 LLM 接口:https://docs.llamaindex.ai/en/stable/api_reference/llms/langchain/
    • 将 LlamaIndex 的 Query Engine 作为 LangChain Agent 的工具:https://docs.llamaindex.ai/en/v0.10.17/community/integrations/using_with_langchain.html
    • LangChain 也曾经集成过 LlamaIndex,目前相关接口仍在:https://api.python.langchain.com/en/latest/retrievers/langchain_community.retrievers.llama_index.LlamaIndexRetriever.html
1…345…27
YooHannah

YooHannah

269 日志
1 分类
23 标签
RSS
© 2026 YooHannah
由 Hexo 强力驱动
主题 - NexT.Pisces