跳转至

09 微调:向量数据库+微调+规则引擎,定制营销文章内容生成

你好,我是金伟。

上节课说到,虽然使用微信AI的用户一开始增加了不少,但只是好玩,并不实用。然后我们意识到,文本AI聊天没有真实的客户价值。那问题来了,怎么才能在营销AI领域做出价值点呢?

我们选定了两个项目继续探索。一个是用AI生成营销文章,另一个是自动生成微信AI视频。事后证明,营销文章生成项目带来了几十倍的效率提升,而视频自动生成项目是一个几十万成本的教训。

我会在这节课着重分析营销文章的部分,分享向量数据库+微调+大模型三者之间的配合方式。同时,复盘图片视频AIGC产品的踩坑点。

营销文章AIGC

先说营销文章功能,他其实是整个AI营销系统的核心功能。下面有一张示意图,是我让AI写了一篇介绍极客时间的营销文章。产品的界面看起来很简单,不过呢,后端的实现其实总共经历了3个阶段,微调+信息储备,增加规则引擎以及GPT改写。

图片

V1:向量数据库方案,储备信息

我们一开始的想法是通过微调,把产品信息数据写入大模型。未来写营销文章的时候,可以直接让大模型根据产品信息生成文章。这肯定也是很多同学刚接触大模型的第一想法。

举个智能硬件产品的例子,下面是一个产品信息的格式示例。

1. 智能空气净化器 AirPure Pro
   - 产品名称: AirPure Pro 智能空气净化器
   - 产品型号: AP5000
   - 核心特点:
     - 四层过滤系统: 高效的四层过滤,包括初效滤网、活性炭滤网、HEPA滤网和负离子发生器,能够去除99.97%的空气污染物。
     - 空气质量检测传感器: 内置的高精度传感器实时监测空气中的PM2.5、甲醛、二氧化碳等有害物质,显示在LCD屏幕上,并自动调整净化模式。
     - 智能控制: 支持Wi-Fi连接,用户可通过专属手机App远程查看空气质量报告、设置定时开关机、调整净化速度等。
     - 节能与静音: 采用超静音风扇设计,夜间模式下噪音低至20分贝,并具备节能模式,自动调节风速以减少电能消耗。
     - 滤网寿命提示: 内置滤网更换提醒功能,根据使用情况智能提示滤网更换时间,确保净化效果最佳。
   - 技术参数:
     - 适用面积: 60-80平方米
     - CADR值: 500立方米/小时
     - 噪音水平: 20-50分贝
     - 功率: 45W
   - 目标市场: 
     - 城市家庭,特别是有婴幼儿、老人或宠物的家庭
     - 关注室内空气质量的用户,如过敏患者、呼吸道疾病患者
   - 促销策略:
     - 新用户优惠: 首次购买享受20%折扣
     - 赠品: 赠送一年的替换滤网
     - 推荐奖励: 推荐朋友购买可获得额外优惠券

要通过微调将这些产品信息内化到大模型里,最核心问题是,如何将产品信息整理成问答的数据集,也就是一个数据工程问题

我们的方法是假设自己是一个客户,整理出一系列的产品问题,然后根据产品信息回答这些问题。

比如最简单的一个问题 AirPure Pro 智能空气净化器的产品型号是多少?。下面是整理的数据集例子,每一条数据都是一问一答的形式。

问:AirPure Pro智能空气净化器的产品型号是多少?  
答:AP5000


.. ..


问:AirPure Pro智能空气净化器内置的传感器可以检测哪些空气中的有害物质?  
答:PM2.5、甲醛、二氧化碳等有害物质


问:AirPure Pro智能空气净化器的空气质量监测结果显示在哪个地方?  
答:LCD屏幕上
.. ..


问:AirPure Pro智能空气净化器的目标市场有哪些?  
答:城市家庭,特别是有婴幼儿、老人或宠物的家庭;关注室内空气质量的用户,如过敏患者、呼吸道疾病患者


问:购买AirPure Pro智能空气净化器的新用户可以享受什么优惠?  
答:首次购买享受20%折扣


.. ..

OK,信息整理好之后,就要开始微调了。具体的大模型微调可以采用 LLaMA-Factory 大模型微调框架,LLaMA-Factory 支持上百种大模型的微调,也支持简单的界面操作,比较好上手。

准备好数据之后,我们打开微调界面。

图片

从数据集中选择出我们刚才准备的数据集,查看数据,确保数据准确无误即可。其他微调参数不用改。

图片

基础模型注意选择ChatGLM,微调方式选择LoRA微调。LoRA训练成本较低,也更容易训练成功。在微调过程中注意观察loss损失曲线,损失曲线下降并趋于平稳就可以了。

图片

微调完成后,在这个界面的 Chat 中选择加载刚才微调后的LoRA模型。然后就可以针对产品提问测试了。

图片

是不是觉得大功告成了?微调大模型加入产品数据的方案看起来很美好,但是实践之后,我们发现效果不好。

这个方案输出的营销文章就像普通公用大模型的输出,太泛了,不集中,而且创造性太强了。还有一个问题在于数据更新不及时,产品信息或营销策略有更新都需要重新微调大模型,很麻烦,成本也高。

怎么办呢?我们的选择是改进为提示词+知识库的模式,解决产品数据更新的问题。实现生成营销文章的提示词分为三步。

  1. 根据用户的提问要求提取出产品名称。
  2. 借助产品名称向量查询产品库,找到对应信息。
  3. 将产品信息补充到提示词中。

图片

这个图中的产品库部分就是向量数据库。那么当然就要先创建向量数据库,把产品信息存到向量数据库中。

我们选用的是开源向量数据库 MilvusMilvus 是一个查询比较高效的向量数据库,开源版支持很好的私有性,适合我们的私有化场景,还具备分布式扩展能力,将来的扩展性很好。

那产品信息如何设计成向量数据库的格式呢?这里要注意一个问题,向量数据库本质上和传统数据库没有什么差别,也可以存储传统数据库的信息,只是为了方便将来做向量查询,需要对查询字段做向量存储。

Milvus 数据库的 Collection 就相当于传统数据库的表,因为 Milvus 数据库是非结构化的,因此可以把产品信息分成若干段结构存入Milvus

具体怎么把产品信息写入向量数据库呢?下面是一段完整的实现代码。在你看代码之前,我想先说说几处重点,方便你理解。

首先需要一个配置文件写入你的数据库连接配置,也就是代码 cfp.read('config.ini') 配置完成才可以进行数据库连接。

接下来的操作和普通数据库操作都很像。比如 collection_name = "product_info" 可以理解为表名称,schema = CollectionSchema 则是表结构。collection.insert(entities) 就是往数据表写数据。

import configparser
import time
from pymilvus import connections, utility, Collection, DataType, FieldSchema, CollectionSchema
from transformers import BertModel, BertTokenizer
import torch


if __name__ == '__main__':
    # 连接到 Milvus
    cfp = configparser.RawConfigParser()
    cfp.read('config.ini')
    milvus_uri = cfp.get('example', 'uri')
    token = cfp.get('example', 'token')
    connections.connect("default",
                        uri=milvus_uri,
                        token=token)
    print(f"Connecting to DB: {milvus_uri}")


    # 检查集合是否存在
    collection_name = "product_info"

    # 定义集合的 schema
    dim = 64  # 向量的维度
    product_id_field = FieldSchema(name="product_id", dtype=DataType.INT64, is_primary=True, description="产品ID")
    product_name_field = FieldSchema(name="product_name_vector", dtype=DataType.FLOAT_VECTOR, dim=dim, description="产品名称向量")
    model_number_field = FieldSchema(name="model_number", dtype=DataType.VARCHAR, max_length=50, description="产品型号")
    features_field = FieldSchema(name="features", dtype=DataType.VARCHAR, max_length=1000, description="核心特点")
    tech_specs_field = FieldSchema(name="tech_specs", dtype=DataType.VARCHAR, max_length=1000, description="技术参数")
    target_market_field = FieldSchema(name="target_market", dtype=DataType.VARCHAR, max_length=1000, description="目标市场")
    promotion_strategy_field = FieldSchema(name="promotion_strategy", dtype=DataType.VARCHAR, max_length=1000, description="促销策略")


    schema = CollectionSchema(fields=[
        product_id_field,
        product_name_field,
        model_number_field,
        features_field,
        tech_specs_field,
        target_market_field,
        promotion_strategy_field
    ], auto_id=False, description="产品信息集合")

    print(f"Creating collection: {collection_name}")
    collection = Collection(name=collection_name, schema=schema)
    print(f"Schema: {schema}")
    print("Success!")


    # 产品信息
    product_name = "AirPure Pro 智能空气净化器"
    product_name_vector = generate_vector_from_text(product_name, dim)

    product_ids = [1]
    product_name_vectors = [product_name_vector]
    model_numbers = ["AP5000"]
    features = ["四层过滤系统: 高效的四层过滤,包括初效滤网、活性炭滤网、HEPA滤网和负离子发生器,能够去除99.97%的空气污染物。空气质量检测传感器: 内置的高精度传感器实时监测空气中的PM2.5、甲醛、二氧化碳等有害物质,显示在LCD屏幕上,并自动调整净化模式。智能控制: 支持Wi-Fi连接,用户可通过专属手机App远程查看空气质量报告、设置定时开关机、调整净化速度等。节能与静音: 采用超静音风扇设计,夜间模式下噪音低至20分贝,并具备节能模式,自动调节风速以减少电能消耗。滤网寿命提示: 内置滤网更换提醒功能,根据使用情况智能提示滤网更换时间,确保净化效果最佳。"]
    tech_specs = ["适用面积: 60-80平方米, CADR值: 500立方米/小时, 噪音水平: 20-50分贝, 功率: 45W"]
    target_markets = ["城市家庭,特别是有婴幼儿、老人或宠物的家庭,关注室内空气质量的用户,如过敏患者、呼吸道疾病患者"]
    promotion_strategies = ["新用户优惠: 首次购买享受20%折扣, 赠品: 赠送一年的替换滤网, 推荐奖励: 推荐朋友购买可获得额外优惠券"]


    entities = [
        product_ids,
        product_name_vectors,
        model_numbers,
        features,
        tech_specs,
        target_markets,
        promotion_strategies
    ]


    print("Inserting entities...")
    t0 = time.time()
    collection.insert(entities)
    total_rt = time.time() - t0
    print(f"Succeed in {round(total_rt, 4)} seconds!")

    ... ...
    # 断开连接
    connections.disconnect("default")

下面是这个数据存储到向量数据库的显示,只有产品名称字段是向量表示。

图片

现在测试一下向量数据库读取,我们的目标是根据产品名称获取所有信息。

import configparser
from pymilvus import connections, Collection
from transformers import BertModel, BertTokenizer
import torch


if __name__ == '__main__':
    # 连接到 Milvus
    cfp = configparser.RawConfigParser()
    cfp.read('config.ini')
    milvus_uri = cfp.get('example', 'uri')
    token = cfp.get('example', 'token')
    connections.connect("default",
                        uri=milvus_uri,
                        token=token)
    print(f"Connecting to DB: {milvus_uri}")


    ... ...

    # 输入产品名称
    product_name = "AirPure Pro"
    dim = 64  # 与存储时的向量维度一致

    # 生成产品名称的向量
    search_vector = generate_vector_from_text(product_name, dim)

    # 搜索相似产品
    search_params = {"metric_type": "L2", "params": {"level": 2}}
    topk = 1  # 搜索结果的数量

    print(f"Searching for: {product_name}")
    results = collection.search([search_vector],
                                anns_field="product_name_vector",
                                param=search_params,
                                limit=topk,
                                output_fields=["product_id", "model_number", "features", "tech_specs", "target_market", "promotion_strategy"])

    # 输出结果
    for result in results:
        for hit in result:
            print(f"Product ID: {hit.entity.get('product_id')}")
            print(f"Model Number: {hit.entity.get('model_number')}")
            print(f"Features: {hit.entity.get('features')}")
            print(f"Technical Specifications: {hit.entity.get('tech_specs')}")
            print(f"Target Market: {hit.entity.get('target_market')}")
            print(f"Promotion Strategy: {hit.entity.get('promotion_strategy')}")
            print(f"Distance: {hit.distance}")
            print("------")

    # 断开连接
    connections.disconnect("default")

OK, 可以看到下面结果中,这个查询已经把整个产品信息用向量的方式查询出来了,特别注意最后一个输出 Distance 表示这次查询的输入向量和结果向量的相似度,数字越小表示越相似。

图片

回过头看我们的流程图。

图片

其实刚才说的就是产品信息查询这一步。

按照设计图,第一步是根据用户提问的问题,用分词技术提取出产品名,这一步咱们还没说。具体怎么做呢?可以用Python的分词库 jieba 库,提取专有名词,也就是产品名称。

import jieba.posseg as pseg


def extract_english_names(sentence):
    words = pseg.cut(sentence)

    # 提取词性为英文的单词
    english_names = [word for word, flag in words if flag == 'eng']

    return " ".join(english_names)


# 示例
sentence = "帮我写一个AirPure Pro 智能空气净化器的文案"
english_name = extract_english_names(sentence)
print(f"提取的英文名称: {english_name}")

最后把名字提取和向量数据库查询结合起来,再把整个结果组织成大模型提示词就可以了。我再把这个流程用文字描述一遍。

  1. 提取产品名称。
  2. 向量查询产品信息。
  3. 将字段组合为提示词。
  4. 用提示词访问大模型输出文章。

V2:规则引擎方案,输出文章

其实,V1版本的向量数据库方案效果已经很好了,不过离人工写的文章还是有差距,还是有一股 “AI味”。因此我们开发了V2版本,也就是基于规则引擎的方案,在V1版本的基础上更进一步。

总体思路特别像用AI写代码。简单地说,就是先由人工提供一个文章架构,再由 AI 写具体的模块内容,实际上这也是目前用 AI 做内容的标准方法。

营销文章生成过程分为规则引擎 + 产品信息 + ChatGLM 改写。第一步,就是复用已有的营销模版,限定大模型的创造范围;第二步是利用大模型结合产品信息填写模版变量;第三步,再用大模型改写生成多个文章。

图片

下面是已经成型的传统营销文章模板例子,也就是文章的架构,可以限定大模型的创造范围。

标题
规则1:{产品名称} - {主要卖点}
规则2:{主要卖点},选择 {产品名称}
..
引言
..
规则2:{产品名称} 是{目标市场}的理想选择,其{特性1} 和 {特性2} 深受喜爱。
...
使用场景
规则1:无论是{场景1}还是{场景2},{产品名称} 都能提供最佳解决方案。
规则2:在{场景1} 和 {场景2} 中,{产品名称} 的表现无可挑剔。
..
购买信息
规则1:了解更多信息,请访问{购买渠道}或联系我们:{联系方式}。
规则2:欲了解详情,请访问{购买渠道}或拨打{联系方式}。
规则3:更多信息,请访问{购买渠道},或拨打{联系方式}与我们联系。

接着就是第二步,结合规则库和向量数据库生成文章。

图片

你看,规则库的核心是提供一种模版,向量数据库核心是读取产品数据,比如要用某个规则库写某个产品,那么输入就是规则和产品信息,输出就是按规则编写的文章。因为原来人工的方式也是按模版编写的,所以这种方式可以把大模型的创造性控制到我们想要的方向上。

下面还有一个智能硬件的案例,最关键的就是把这些信息用提示词组合到一起。

请写一篇营销文章,要求遵照营销文章模版并结合产品信息来写,文章要求是:突出产品的核心功能和与竞争对手的不同之处,通过具体的应用场景和实际用户的成功体验,展示产品的实用性和价值。
营销文章模版:
"""
标题
规则:{产品名称} - {主要卖点}


引言
规则:{产品名称} 是{目标市场}的理想选择,其{特性1} 和 {特性2} 深受喜爱。


使用场景
规则:无论是{场景1}还是{场景2},{产品名称} 都能提供最佳解决方案。


购买信息
规则:了解更多信息,请访问{购买渠道}或联系我们:{联系方式}。
"""
产品信息:
"""
1. 智能空气净化器 AirPure Pro
...
"""

大模型轻松识别这些模版信息之后,就能写出一篇营销文章了。自动化的工作则可以用编程调用ChatGLM接口来解决。

V3:GPT改写方案,优化版本

最后,说一个真实经验:让GPT改写这篇文章,继续生成,继续提升内容生产效率。这里说的GPT改写是一个优化方案,就是在系统自动生成营销文章的基础之上,再针对特定用户单独生成文章。想要实现这个功能,我们只要在营销文章功能中加入一个改写模块即可。

GPT改写的好处是,保证质量情况下低成本地生成多个文章,其实这也是真实项目的标准做法。同样的文章还可以根据客户的特性做个性化处理,这是以前想到但做不到的。

图片

真实情况下,用ChatGLM改写也是可以完成,具体实现取决于的客户、成本的要求。

比如下面的文章在模版文章生成之后,根据用户年轻化的特点,再用大模型对文章改写,可以生成针对年轻人的营销文章。

图片

当然,真实场景下,最终还需要人工审核优化文章,确保生成内容符合规范,并根据SEO规则,继续优化关键词和内容结构。

运营小结

营销文章最终的运营效果还可以,基本可以达到原有营销系统客户效果,重点是将AI能力嵌入了原有业务,在原有业务上加入了AI的创造性,实现了自动生成营销文章的功能,让效率提升了10倍以上,可以说,我们实现了一个小型的营销文章生产工厂。

图片视频AIGC

最后我来说说图片视频的生成。我们是一个自研精神拉满的团队,几乎是从头开始搭建了整套系统。这也是你可以参考的地方。

图片视频,顾名思义,就是图片拼接组成的视频。所以我们先来看最基础的AI图片开发。这里需要做的是扩展AI指令支持图片。要注意,因为AI生成图片速度较慢,所以在实际的用户交互里,可以先回复一条消息让用户等待,避免用户产生焦虑。

图片

图片生成

市面上有两个文生图片的方案,SD是开源方案,但是从图片效果上来说,MJ效果更好。不过MJ官方不提供接口,所以我们选用了一个代理MJ接口,这样实现也简单,直接用微信图片指令调用MJ接口。

MJ文生图有两个核心的接口操作,_Imagine操作和Change操作,分别负责文生图和放大图片。下面是Imagine的具体参数和调用示例。

图片

这里有一个基本文生图代码例子,你可以参考。

import requests
import json


url = "https://mj.openai-next.com/mj/submit/imagine"


payload = json.dumps({
   "botType": "MID_JOURNEY",
   "prompt": "一个年轻女孩,长长的棕色波浪头发,穿着淡蓝色连衣裙,站在温馨的客厅里。她微笑着抱着两只猫,一只是橙色虎斑猫,另一只是灰色猫。背景是米色沙发和木质咖啡桌,阳光透过窗帘洒进来,房间里有几盆绿植。",
   "base64Array": [],
   "accountFilter": {
      "channelId": "",
      "instanceId": "",
      "modes": [],
      "remark": "",
      "remix": True,
      "remixAutoConsidered": True
   },
   "notifyHook": "",
   "state": ""
})
headers = {
   'User-Agent': 'Apifox/1.0.0 (https://apifox.com)',
   'Content-Type': 'application/json'
}


response = requests.request("POST", url, headers=headers, data=payload)


print(response.text)

注意, 第8行代码里 "prompt": "一个年轻女孩...", 是文生图操作的提示词。接收到提示词之后, Imagine 接口会返回一个Taskid,后续能用这个Taskid表示为同一次图片操作。

{
        "code": 1,
        "description": "提交成功",
        "properties": {},
        "result": 1320098173412546 #Taskid
}

比如对同一张图片用 Change 接口做 UPSCALE(放大) 操作,就需要传入Taskid。

图片

这个流程很简单,实现单个图片的微信AI指令是没问题的。你可以在自己的微信AI基础上接入图片功能。

好,再说说视频AIGC的开发。用一句话总结就是,理想很丰满,现实很骨感。我们是一步步由全自动AIGC方案,被逼着一步步往人工+ AI方向去实现的。做到最后,我们发现这个AI视频生成的步骤需要引入太多人工环节了,所以最后没有把生成视频的功能接入微信AI,但是成片在短视频平台上发布测试了,平均也有几千的播放量,从生产成本来说还算可以。

因为这不是一个成功的经验,我把具体的实现过程和代码放社群资料库了,对视频自动AIGC感兴趣的同学可以去领取并实验。

下面是一张生产环境的流程图,给你参考。

图片

Sora为什么能成

可以说,我们在AI视频生成中最大的教训就是低估了这个模块的难度。当Sora类视频AI出现的时候,我们才感受到大模型能力迭代得非常快。自有开发在这个级别的能力上不应该选择和巨人团队竞争。而且视频创作的核心并不在技术,而在于运营人员,因此技术上后期我们偏向应用创新。

另外一点是,我们的AI视频效果不好。怎么不好?一是不流畅,转场粗糙,画面衔接不足,达不到商用级别。二是人工参与过多,最终不能集成到微信AI里。既然这是一个暂时离不开人的事,那最终也就选择废弃。

直到Sora这类视频生成AI发布,我们团队才意识到原来对AI视频的认知太粗浅了,一开始我们以为AI做视频就是要让AI自动生成整个视频,实际上视频的创造性决定了不能通过这种控制的方式来完成,因为控制的方式无法实现画面的语义转化。

图片

要用控制的方式制作视频,就必须退回到逐帧绘制视频画面才能完成,否则语义是不连贯的。而真正Sora类的视频平台的原理是什么呢?注意,Sora的核心就是真正把一个镜头的语义完整地表示出来,底层是通过视频大模型的训练完成的。而其目的也不是生成整个视频,只是生成一个镜头,最终的完整视频制作还需要人工制作。换句话说,Sora类视频AI是替换了原工业流程里的逐帧绘画。

小结

在营销场景下,用AI生产内容,是AIGC是一个非常普遍的需求,我们选择了文章的AIGC和视频AIGC作为切入点,有经验也有教训。

在营销文章AIGC重,我们经历了V1版本的大模型微调方案、向量数据库方案,V2版本的规则引擎方案和V3版本的GPT改写方案。整个开发过程是一步步递进的。总结来说,用AI写文章和用AI写代码非常类似,都是先由人工写一个框架,再由AI写具体细节。

图片

用AI做视频其实也是同样的套路。本来我们预想的方案是要实现完整的视频内容创作和输出,支持全自动生成,但是开发过程中才发现即使Sora类的生成式AI产品也只能做到一个很短的镜头生成,整体还是离不开人。

我把这些经验形成了一张“营销文章工厂化生产流程”图,对AI做内容的项目会有参考意义。

图片

思考题

在营销文章模块,最后一步是AI改写文章,请你想一下这一步的改写使用什么提示词?要注意什么?

欢迎你在留言区和我交流。如果觉得有所收获,也可以把课程分享给更多的朋友一起学习。我们下节课见!

>>戳此加入课程交流群

精选留言(4)
  • 一点点就好 👍(1) 💬(1)

    您好老师,请教一哈从输入文本中提取产品名称,因为用户输入的文本是任意形式的,就会导致提取名称拿不到预期。向量搜索出的产品也就不对,这种情况您们是怎么处理的

    2024-08-30

  • daydaygo 👍(0) 💬(1)

    有代码仓库么?

    2024-10-12

  • 大魔王汪汪 👍(1) 💬(0)

    从2024年底这个时间点往回看,问下老师,想要提升模型准确度是不是就以下几种方式: 1)选择参数更大的模型; 2)数据微调; 3)RAG; 4)写好Prompt; 5)如果可能的话,做些工程上的事情,比如借助规则引擎重写Prompt;

    2024-12-30

  • V 👍(0) 💬(1)

    这里的规则引擎没太明白,文中所描述的是指规则引擎是否是运营或开发定义好提示词的模板,通过占位符配置,保存在库中。模板中的占位符内容的信息,通过用户语义提取或引导用户指定输入相关占位符对应的内容,然后把模板对应的占位符进行替代,生成对应的提示词模块。最后调用LLM ?

    2025-02-05