简介:这份PDF面向旅游行业从业者、数据分析师及算法工程师,聚焦如何借助DeepSeek大模型实现动态定价。内容从动态定价概念与酒店、航空、旅游套餐等应用场景切入,系统讲解DeepSeek模型架构与训练基础,并逐步展开多维度数据收集与预处理、特征拼接与深度学习等数据融合策略、模型微调步骤与代码示例、MSE/MAE/R²等评估指标及优化方法,最后落到系统集成部署与完整实战案例。资源包共1个PDF文件,大小约1.85MB,共23页,文字、图表与目录均显示正常,可放心查阅。目前已有66人学习。读者可借此掌握从数据融合到微调落地的完整链路,获得可复用的代码思路与评估优化经验,适合希望将大模型应用于收益管理与智能定价的进阶学习者。
1. 旅游动态定价为什么值得用 DeepSeek 微调重做一遍
同一家酒店,同一个房型,周一上午和周五深夜的挂牌价能差出 40%,而真正决定这个价差的不只是入住率。旅游行业的定价团队每天要盯竞品价格、历史成交、节假日日历、天气、航班运力、搜索热度、退改政策,这些数据散落在 OTA 后台、PMS 系统、Excel 报表和第三方接口里。传统做法是规则引擎加人工调价,规则写死了就僵化,写松了就乱价。这两年大家开始用 DeepSeek 这类大模型做多维度数据融合微调,把结构化指标和非结构化文本一起喂进去,让模型学会在特定场景下给出定价建议。这篇笔记讲的就是这条路径怎么落地:数据怎么融、LoRA 微调怎么配、DeepSeek API 怎么接、本地部署和 vLLM 推理怎么选、翻车点在哪。适合已经有基础 Python 和一点模型微调经验、想把这套东西用到旅游定价场景的工程师。
2. 多维度数据融合:把 OTA 报表、日历和点评文本拼成一条训练样本
2.1 旅游定价的数据维度到底有哪些
做动态定价,第一步不是选模型,是搞清楚哪些数据真正影响价格。我一般把维度分成四组。第一组是供需类:当前入住率、可售房量、过去 7 天同房型成交均价、取消率。第二组是竞品类:同商圈 3 公里内竞品挂牌价、竞品调价频率、竞品满房信号。第三组是场景类:日期类型(工作日/周末/节假日)、距入住天数、当地天气、大型活动或展会排期。第四组是文本类:用户点评里的价格敏感词、OTA 搜索关键词热度、客服对话里提到的价格异议。
这四组里,前三组是结构化数值,第四组是非结构化文本。DeepSeek 微调的价值就在于能把第四组也纳入定价决策,而不是只靠数值回归。常见做法是把结构化字段转成自然语言描述,和文本字段拼成一段 prompt,让模型输出建议价格或价格调整方向。
2.2 融合成训练样本的字段设计
训练样本的格式直接决定微调效果。我一般用 JSONL,每行一条样本,包含 instruction、input、output 三个字段。input 里把多维度数据写成一段结构化描述,output 是人工审核过的定价建议。下面是一个最小示例。
import json sample = { "instruction": "你是旅游行业定价助手,根据以下多维度数据给出今晚该房型的建议挂牌价,并说明调整理由。", "input": ( "房型:高级大床房;当前入住率:82%;可售房量:6;" "过去7天同房型成交均价:486元;取消率:11%;" "同商圈竞品均价:512元;竞品满房信号:无;" "日期类型:周五;距入住天数:1;天气:小雨;" "近期点评关键词:'性价比''隔音差'出现频次上升;" "OTA搜索热度:环比上升18%。" ), "output": "建议挂牌价:498元。理由:入住率偏高且可售房量少,竞品未满房但均价高于本店,搜索热度上升说明需求端有支撑;点评中'性价比'提及上升,不宜大幅提价,小幅上调12元试探。" } with open("train.jsonl", "a", encoding="utf-8") as f: f.write(json.dumps(sample, ensure_ascii=False) + "\n")这段代码的关键不在写入动作,而在字段设计。instruction 要固定,避免模型每次理解不同任务。input 里的字段顺序要稳定,模型会隐式学习字段位置和含义。output 必须包含价格和理由,理由部分能让模型学到「为什么这样调」,而不只是背价格。参数上,入住率、竞品均价、搜索热度这些数值建议保留原始量纲,不要过早归一化,因为大模型对「82%」和「512元」这种自然语言数值有更好的语义理解。
2.3 数据清洗和样本平衡的实操
旅游数据有个特点:节假日样本少但价格波动大,工作日样本多但价格平稳。如果直接按原始分布训练,模型会偏向给平稳价格,遇到节假日就保守。我一般做两件事。一是按日期类型分层采样,保证节假日、周末、工作日样本比例接近 1:1:1。二是对极端价格样本做保留,不要因为「离群」就删掉,那些恰恰是模型最该学会的场景。
清洗时重点查三类脏数据:入住率超过 100% 或为负、竞品价格缺失但被填成 0、点评关键词提取错误。下面这段清洗逻辑可以直接套。
import pandas as pd def clean_pricing_df(df: pd.DataFrame) -> pd.DataFrame: # 入住率合法区间 df = df[(df["occupancy"] >= 0) & (df["occupancy"] <= 100)] # 竞品均价缺失不填 0,直接丢弃该样本 df = df[df["competitor_avg_price"].notna() & (df["competitor_avg_price"] > 0)] # 成交均价异常值处理:超过同房型中位数 3 倍的标记复核 median_price = df.groupby("room_type")["avg_price_7d"].transform("median") df["price_outlier"] = df["avg_price_7d"] > median_price * 3 return df逻辑说明:入住率过滤掉系统对接错误;竞品价格缺失填 0 会让模型学到「竞品免费」这种荒谬关联,必须丢弃;价格异常值不直接删,而是打标,后续人工复核或作为高波动样本保留。参数上,3 倍中位数是我在几个项目里试出来的经验阈值,太松会漏掉真异常,太紧会误杀节假日高价样本。
3. DeepSeek 微调实战:LoRA 配置、数据格式和训练命令
3.1 为什么选 LoRA 而不是全量微调
旅游定价场景的数据量通常在几千到几万条,全量微调 DeepSeek 这种量级的模型,显存和时间成本都不划算。LoRA 微调只训练低秩矩阵,显存占用能降到全量的三分之一左右,单卡 24G 就能跑起来。而且 LoRA 权重文件小,方便按业务线切换——酒店、机票、景区各训一个 LoRA,推理时动态加载。
常见做法是用 LLaMA-Factory 或 PEFT 库做 LoRA 微调。下面以 LLaMA-Factory 的配置文件为例,这是目前比较省心的路径。
# train_lora.yaml model_name_or_path: deepseek-ai/deepseek-llm-7b-chat stage: sft do_train: true finetuning_type: lora lora_rank: 16 lora_alpha: 32 lora_dropout: 0.05 lora_target: all dataset: travel_pricing template: deepseek cutoff_len: 1024 max_samples: 10000 overwrite_cache: true preprocessing_num_workers: 8 output_dir: outputs/travel_pricing_lora logging_steps: 20 save_steps: 200 plot_loss: true overwrite_output_dir: true per_device_train_batch_size: 2 gradient_accumulation_steps: 8 learning_rate: 1.0e-4 num_train_epochs: 3.0 lr_scheduler_type: cosine warmup_ratio: 0.1 bf16: true参数说明:lora_rank 设 16 是平衡效果和显存的常用值,数据量上万可以提到 32;lora_alpha 一般是 rank 的两倍;lora_target 设 all 表示对所有线性层加 LoRA,定价任务对数值敏感,全层适配比只调 q_proj、v_proj 更稳。cutoff_len 设 1024 是因为我们的 input 描述通常在 300 到 600 token,留足 output 空间。learning_rate 用 1e-4,LoRA 比全量微调能承受更大学习率,但再大容易震荡。batch_size 2 加梯度累积 8,等效 batch 16,单卡 24G 能跑。
3.2 训练命令和显存观察
配置文件写好之后,启动命令很直接。
llamafactory-cli train train_lora.yaml跑起来之后重点看两个东西。一是 loss 曲线,前 100 步下降快是正常的,如果 500 步后还在 2.0 以上震荡,多半是数据格式或 template 没对齐。二是显存占用,用 nvidia-smi 看,如果接近 24G 上限,先把 per_device_train_batch_size 降到 1,gradient_accumulation_steps 提到 16,等效 batch 不变但峰值显存会降。
训练完成后,LoRA 权重会存在 output_dir 下,通常是一个 adapter_model.bin 加 adapter_config.json。推理时把基座模型和 LoRA 权重一起加载即可。
3.3 用 vLLM 部署 DeepSeek 加 LoRA 做批量推理
训练完要验证效果,单条推理太慢,我一般用 vLLM 部署。vLLM 支持动态加载 LoRA,适合多业务线场景。
python -m vllm.entrypoints.openai.api_server \ --model deepseek-ai/deepseek-llm-7b-chat \ --enable-lora \ --lora-modules travel_pricing=outputs/travel_pricing_lora \ --max-lora-rank 16 \ --dtype bfloat16 \ --gpu-memory-utilization 0.9 \ --port 8000启动后就可以用 OpenAI 兼容接口调用,模型名填 travel_pricing。参数上,gpu-memory-utilization 0.9 是留 10% 给系统,设太高容易 OOM;max-lora-rank 要和训练时的 lora_rank 一致,否则加载失败。批量验证时把测试集转成同样的 input 格式,逐条请求,记录模型输出价格和人工标注价格的偏差。
4. 避坑与排查:微调旅游定价模型最容易翻车的 5 个地方
4.1 现象:模型输出价格全是同一个数
原因:训练数据里 output 价格分布过于集中,或者 instruction 太笼统,模型学到了「平均价」这个偷懒解。解决:检查 output 的方差,如果标准差小于均值的 5%,说明样本多样性不够;按日期类型和入住率分层补充样本,并在 instruction 里明确要求「根据输入差异给出不同价格」。
4.2 现象:loss 降到很低但实际推理完全不能用
原因:数据泄漏。训练集和验证集里有同一天同一房型的样本,模型记住了答案而不是学会推理。解决:按日期切分数据集,不要随机切分。比如用前 8 个月训练,后 2 个月验证,确保验证集里的日期在训练集里没出现过。
4.3 现象:模型给的价格理由和价格本身矛盾
原因:output 里的理由和价格是分开标注的,标注员可能先写理由再拍价格,两者不一致。解决:在数据标注规范里要求先定价格再写理由,且理由必须引用 input 里的具体字段。训练后做一致性检查,用规则抽取理由中的关键词和价格方向做比对。
4.4 现象:vLLM 加载 LoRA 报 rank 不匹配
原因:训练时 lora_rank 设了 16,部署时 max-lora-rank 设了 8,或者反过来。解决:部署命令里的 max-lora-rank 必须大于等于训练时的 lora_rank,建议直接设成一样。如果同时加载多个 LoRA,取最大的那个 rank。
4.5 现象:模型对节假日样本预测偏差特别大
原因:节假日样本在训练集里占比太低,模型没见过足够多的高波动场景。解决:对节假日样本做过采样,或者用数据增强生成更多节假日场景的组合。另一个办法是在 loss 里给节假日样本更高权重,但这需要改训练代码,不如过采样直接。
5. 把定价建议接进业务系统:API 封装和效果验证的一个技巧
模型训完只是半成品,真正要用起来得封装成 API 接进定价系统。我一般用 FastAPI 包一层,把 vLLM 的接口再封装成业务字段。
from fastapi import FastAPI from pydantic import BaseModel import httpx app = FastAPI() class PricingRequest(BaseModel): room_type: str occupancy: float competitor_avg_price: float date_type: str days_to_checkin: int review_keywords: str @app.post("/pricing/suggest") async def suggest_price(req: PricingRequest): prompt = ( f"房型:{req.room_type};当前入住率:{req.occupancy}%;" f"同商圈竞品均价:{req.competitor_avg_price}元;" f"日期类型:{req.date_type};距入住天数:{req.days_to_checkin};" f"近期点评关键词:{req.review_keywords}。" "请给出建议挂牌价和理由。" ) async with httpx.AsyncClient() as client: resp = await client.post( "http://localhost:8000/v1/completions", json={ "model": "travel_pricing", "prompt": prompt, "max_tokens": 256, "temperature": 0.3 }, timeout=30.0 ) return {"suggestion": resp.json()["choices"][0]["text"]}这段代码的逻辑是把业务字段拼成训练时一致的 input 格式,再转发给 vLLM。temperature 设 0.3 是为了让输出稳定,定价场景不需要创意。max_tokens 256 够输出价格和简短理由。
效果验证有个技巧:不要只看价格偏差,要看「方向准确率」。也就是模型判断该涨价还是该降价,和人工决策是否一致。价格绝对值受市场波动影响大,但方向判断更能反映模型有没有学到定价逻辑。我一般要求方向准确率到 85% 以上才考虑上线,绝对值偏差控制在 5% 以内算合格。
上线后还要留后悔药:模型建议不要直接生效,先走人工审核或影子模式,和现有规则引擎并行跑两周,对比差异。差异大的样本回流做增量训练。这个习惯帮我避免过好几次因为数据管道延迟导致的批量错价。希望帮到你。
本文还有配套的精品资源,点击获取