上一篇我们学会了用容器高效管理数据,今天要攻克的函数,是大模型工程化开发的核心 —— 把重复的数据预处理、损失计算、训练循环等逻辑 “打包” 成函数,不用每次写重复代码,还能让项目结构更清晰、调试更方便!大模型开发动辄上千行代码,函数就是帮你 “化繁为简” 的利器一、函数基础:定义与调用
函数就像一个 “黑盒子”,你给它输入(参数),它给你输出(返回值),内部逻辑封装好,不用重复写。用 def 关键字定义。
1. 基本语法
def 函数名(参数1, 参数2, ...): """函数文档字符串(说明功能、参数、返回值)""" 函数体(执行逻辑) return 返回值(可选)
2. 大模型场景示例:计算损失值
大模型训练中每次都要算损失值,封装成函数后直接调用就行:def calculate_loss(pred, target): """ 计算大模型预测值与真实值的损失(简化版均方误差) 参数: pred: 模型预测值(浮点数) target: 真实值(浮点数) 返回: loss: 损失值 """ loss = (pred - target) ** 2 # 简化的均方误差计算 return loss# 调用函数pred_value = 0.8 # 模型预测的准确率true_value = 0.9 # 真实准确率current_loss = calculate_loss(pred_value, true_value)print(f"当前损失值:{current_loss:.4f}") # 输出:当前损失值:0.0100
养成习惯:函数一定要写文档字符串(三引号包裹),说明功能、参数、返回值 —— 大模型项目多人协作时,这就是 “代码说明书”!二、函数参数:灵活传递数据
函数参数有多种类型,能满足大模型开发中不同的配置需求,比如固定参数、默认参数、可变参数。
1. 位置参数:按顺序传递
最基础的参数,调用时必须按定义的顺序传值:
def train_step(model, data, lr): """单步训练函数(位置参数:模型、数据、学习率)""" print(f"用学习率{lr}训练模型{model},处理数据{data}")# 调用时顺序不能错!train_step("LLM_Base", "batch_001", 0.001)
2. 默认参数:给参数设默认值
适合大模型中常用的固定配置(比如默认学习率、默认批次大小),调用时可以不传,用默认值:
def train_model(model_name, epochs=100, lr=0.001): """ 大模型训练函数(默认参数:训练轮数、学习率) 参数: model_name: 模型名称(必填) epochs: 训练轮数(默认100) lr: 学习率(默认0.001) """ print(f"开始训练模型:{model_name}") print(f"训练轮数:{epochs},学习率:{lr}")# 调用方式1:只传必填参数train_model("LLM_Small")# 调用方式2:覆盖默认参数train_model("LLM_Large", epochs=150, lr=0.0005)
3. 可变参数:传递任意数量数据
适合大模型中批量处理数据(比如传入多个损失值求平均),用 *args 接收任意数量的位置参数:
def avg_loss(*losses): """计算多轮训练的平均损失(可变参数:任意数量损失值)""" total = sum(losses) avg = total / len(losses) return avg# 调用时可以传任意多个参数print("平均损失:", avg_loss(0.32, 0.28, 0.25, 0.22)) # 输出:0.2675print("平均损失:", avg_loss(0.19, 0.17)) # 输出:0.18
三、函数返回值:输出处理结果
函数可以用 return 返回一个或多个结果,大模型中常用返回值传递训练后的损失值、准确率、模型状态等。
1. 返回单个值
def compute_accuracy(preds, targets): """计算模型准确率(返回单个值)""" correct = sum(p == t for p, t in zip(preds, targets)) accuracy = correct / len(preds) return accuracy# 调用predictions = [1, 0, 1, 1, 0]true_labels = [1, 0, 0, 1, 0]acc = compute_accuracy(predictions, true_labels)print(f"模型准确率:{acc:.2f}") # 输出:0.80
2. 返回多个值(用元组)
大模型训练常同时返回损失值和准确率,函数可以一次返回多个:
def train_epoch(model, data): """训练一轮,返回损失值和准确率(返回多个值)""" # 模拟训练过程 loss = 0.25 accuracy = 0.82 return loss, accuracy # 用逗号分隔,自动打包成元组# 调用时用多个变量接收epoch_loss, epoch_acc = train_epoch("LLM_Base", "train_data")print(f"本轮损失:{epoch_loss:.2f},准确率:{epoch_acc:.2f}")
四、变量作用域:区分全局与局部
大模型项目中要注意变量的 “作用范围”,避免全局变量和局部变量混淆导致 bug。
1. 局部变量:函数内部定义,仅函数内有效
def train_step(): lr = 0.001 # 局部变量:仅在train_step内有效 print(f"训练步学习率:{lr}")train_step()# print(lr) # 报错!外部无法访问局部变量
2. 全局变量:函数外部定义,函数内访问需加 global
大模型的全局配置(比如模型名称、最大轮数)常用全局变量:
# 全局变量:函数外部定义MODEL_NAME = "LLM_Global"MAX_EPOCHS = 100def train_model(): global MAX_EPOCHS # 声明要修改全局变量 MAX_EPOCHS = 150 # 修改全局变量 print(f"训练模型:{MODEL_NAME},最大轮数:{MAX_EPOCHS}")train_model()print(f"修改后的全局最大轮数:{MAX_EPOCHS}") # 输出:150
五、递归:函数自己调用自己
递归是指函数在内部调用自身的编程方式,适合解决可以分解为相同子问题的场景(比如处理嵌套数据结构、分治算法)。
1. 递归的两个关键要素
- 终止条件:递归必须有一个明确的结束条件,否则会无限递归导致栈溢出;
- 递归关系:问题可以分解为规模更小的相同子问题。
2. 大模型场景示例 1 :解析嵌套的模型配置
大模型的配置文件常是嵌套的 JSON 结构,用递归可以方便地遍历所有配置项:
def print_nested_config(config, indent=0): """ 递归打印嵌套的模型配置 参数: config: 配置字典(可能嵌套) indent: 缩进层级(用于美观打印) """ for key, value in config.items(): prefix = " " * indent if isinstance(value, dict): # 如果值是字典,递归处理 print(f"{prefix}{key}:") print_nested_config(value, indent + 1) else: # 否则直接打印 print(f"{prefix}{key}: {value}")# 嵌套的大模型配置llm_nested_config = { "model": { "name": "LLM_Recursive", "architecture": { "encoder_layers": 12, "decoder_layers": 12, "hidden_size": 768 } }, "training": { "lr": 0.001, "batch_size": 32 }}# 调用递归函数打印配置print("===== 嵌套模型配置 =====")print_nested_config(llm_nested_config)
3. 大模型场景示例 2:递归计算学习率衰减(斐波那契式)def fibonacci_lr(n): """ 递归计算斐波那契式学习率衰减(第n轮的学习率系数) 规律:第1、2轮系数为1,后续每轮为前两轮之和 """ # 终止条件 if n == 1 or n == 2: return 1.0 # 递归关系 return fibonacci_lr(n-1) + fibonacci_lr(n-2)# 计算前5轮的学习率系数base_lr = 0.001print("===== 斐波那契式学习率衰减 =====")for i in range(1, 6): coefficient = fibonacci_lr(i) lr = base_lr / coefficient # 系数越大,学习率越小 print(f"第{i}轮,学习率系数:{coefficient},学习率:{lr:.6f}")
六、匿名函数(Lambda):一行代码定义简单函数
匿名函数用 lambda 关键字定义,是一种没有函数名、一行代码就能写完的简单函数,适合配合 map()、filter()、sorted() 等函数使用,处理简单的逻辑。
1. 基本语法
Python使用lambda来定义匿名函数,所谓匿名,指其不用def的标准形式定义函数。
lambda参数列表: 表达式
Ølambda 只是一个表达式,函数体比def简单很多。
Ølambda的主体是一个表达式,而不是一个代码块,所以仅仅能在lambda表达式中封装有限的逻辑进去。
Ølambda函数拥有自己的命名空间,且不能访问自己参数列表之外或全局命名空间里的参数。
使用普通函数传参:
def operator(a, b): return a + b def function(a, b, operator): return operator(a, b)print(function(1, 2, operator))
使用匿名函数传参:
deffunction(a, b, operator): returnoperator(a, b)print(function(1, 2, lambdax, y: x+y))
2. 大模型场景示例 1:配合 map() 批量处理数据
# 模拟大模型的原始预测结果(概率值)raw_predictions = [0.1, 0.5, 0.8, 0.3, 0.9]# 用lambda+map将概率值转为二分类标签(>0.5为1,否则为0)binary_labels = list(map(lambda x: 1 if x > 0.5 else 0, raw_predictions))print("原始预测概率:", raw_predictions)print("二分类标签:", binary_labels) # 输出:[0, 0, 1, 0, 1]# 用lambda+map对所有预测值进行缩放(乘以100转为百分比)percentage_preds = list(map(lambda x: x * 100, raw_predictions))print("预测概率(百分比):", percentage_preds)
3. 大模型场景示例 2:配合 sorted()排序模型输出大模型常需要对生成的多个候选结果按置信度排序,lambda 可以方便地指定排序规则:
# 模拟大模型生成的候选回答(包含文本和置信度)candidate_answers = [ {"text": "答案A", "confidence": 0.78}, {"text": "答案B", "confidence": 0.92}, {"text": "答案C", "confidence": 0.85}]# 用lambda指定按置信度从高到低排序sorted_answers = sorted(candidate_answers, key=lambda x: x["confidence"], reverse=True)print("===== 按置信度排序的候选答案 =====")for ans in sorted_answers: print(f"{ans['text']},置信度:{ans['confidence']:.2f}")
4. 大模型场景示例 3:配合 filter() 筛选有效数据
filter() 可以筛选序列中满足条件的元素,配合 lambda 清洗大模型的训练数据:
# 模拟训练数据(包含文本和长度,需要过滤掉过短的文本)train_data = [ {"text": "你好", "length": 2}, {"text": "这是一个完整的训练样本", "length": 12}, {"text": "Hi", "length": 2}, {"text": "大模型开发需要掌握Python基础", "length": 14}]# 用lambda+filter筛选长度≥5的有效样本valid_data = list(filter(lambda x: x["length"] >= 5, train_data))print(f"原始数据量:{len(train_data)},有效数据量:{len(valid_data)}")print("有效样本:")for data in valid_data: print(f" {data['text']}")
"""实战案例:大模型简化训练流程封装功能:1. 数据预处理函数2. 单步训练函数3. 完整训练函数(整合流程)"""# 全局配置MODEL_CONFIG = { "model_name": "LLM_Practice", "lr": 0.001, "batch_size": 32}def preprocess_data(raw_data): """数据预处理:清洗并格式化数据""" processed = [d.strip().lower() for d in raw_data] return processeddef train_one_batch(model, batch_data, lr): """单批次训练:返回损失值""" # 模拟训练 loss = 0.3 - (lr * 10) return lossdef full_train(model_config, raw_data, epochs=5): """ 完整训练流程 参数: model_config: 模型配置字典 raw_data: 原始训练数据 epochs: 训练轮数 返回: loss_history: 每轮损失值列表 """ # 1. 数据预处理 data = preprocess_data(raw_data) print(f"预处理完成,数据量:{len(data)}") # 2. 训练循环 loss_history = [] for epoch in range(epochs): loss = train_one_batch(model_config["model_name"], data, model_config["lr"]) loss_history.append(loss) print(f"第{epoch+1}轮,损失值:{loss:.4f}") return loss_history# 调用完整训练函数raw_train_data = [" Hello LLM ", " Python Dev ", " Model Train "]history = full_train(MODEL_CONFIG, raw_train_data, epochs=3)print(f"\n训练完成,损失历史:{history}")
运行结果:
预处理完成,数据量:3第1轮,损失值:0.2900第2轮,损失值:0.2900第3轮,损失值:0.2900训练完成,损失历史:[0.29, 0.29, 0.29]
"""补充实战:整合递归与Lambda处理大模型数据"""# 1. 递归解析嵌套配置def parse_config(config, parent_key=""): """递归展平嵌套配置字典""" items = [] for k, v in config.items(): new_key = f"{parent_key}.{k}" if parent_key else k if isinstance(v, dict): items.extend(parse_config(v, new_key)) else: items.append((new_key, v)) return itemsnested_config = { "model": {"name": "LLM_Lambda", "size": "base"}, "train": {"lr": 0.001, "epochs": 100}}flat_config = dict(parse_config(nested_config))print("展平后的配置:", flat_config)# 2. 用Lambda处理配置值# 将所有数值型配置值转为字符串processed_config = {k: (lambda v: str(v) if isinstance(v, (int, float)) else v)(v) for k, v in flat_config.items()}print("处理后的配置:", processed_config)
展平后的配置: {'model.name': 'LLM_Lambda', 'model.size': 'base', 'train.lr': 0.001, 'train.epochs': 100}处理后的配置: {'model.name': 'LLM_Lambda', 'model.size': 'base', 'train.lr': '0.001', 'train.epochs': '100'}
下一篇预告
今天我们完整掌握了 Python 函数的全知识点,从基础的定义调用、参数返回值,到进阶的递归、匿名函数,再到工程化的逻辑封装,能把大模型开发中的复杂逻辑拆解为可复用的函数模块。下一篇我们将进入第 7 章 文件操作:学习如何用 Python 读取训练数据集、保存模型权重、写入训练日志、处理大模型常用的 txt、json、csv 等格式文件
如果在函数使用中遇到问题,评论区贴出你的代码和报错信息, 觉得干货有用,别忘了点赞 + 在看 + 转发,让更多想入门大模型的朋友一起学习!