【语言大模型微调】LoRA — 尖端的大模型微调技术_lora微调

在这里插入图片描述


文章目录
  • 前言
  • 一、什么是微调?
  • 二、LORA 简介
    • 2.1 微调作为参数变化
    • 2.2 参数变化压缩
  • 三、LoRA微调的流程
  • 四、LoRA在Transformer中的应用
  • 五、LoRA Rank
  • 六、Python代码实现
    • 6.1 安装依赖库:
    • 6.2 加载预训练模型
    • 6.3 设置LoRA
    • 6.4 检查内存的节省
    • 6.5 加载微调数据集
    • 6.6 使用LoRA在SQUAD上进行微调
    • 6.7 检查LoRA大小
    • 6.8 测试

前言

直接上干货:大语言模型可以做什么?
在这里插入图片描述


LoRA的核心思想:
**加粗样式**

提示:后面是主要内容:


一、什么是微调?

随着机器学习的最新发展,对模型性能的期望也在增加,需要更复杂的机器学习方法来满足对性能的需求。在机器学习的早期阶段,构建一个模型并在单次训练中训练它是可行的。

在这里插入图片描述

训练,在其最简单的意义上。您将一个未经训练的模型,提供给它数据,并获得一个高性能的模型。

对于简单问题来说,这仍然是一种流行的策略,但对于更复杂的问题,将训练分为两个部分,即“预训练”和“微调”,可能会很有用。总体思路是在一个大规模数据集上进行初始训练,并在一个定制的数据集上对模型进行优化
在这里插入图片描述

这种“预训练”然后“微调”的策略可以让数据科学家利用多种形式的数据,并使用大型预训练模型来完成特定任务。因此,预训练然后微调是一种常见且非常强大的范例。

最基本的微调形式 是使用与预训练模型相同的过程来微调新数据上的模型。例如,您可以在大量的通用文本数据上训练模型,然后使用相同的训练策略,在更具体的数据集上微调该模型。

以上策略十分昂贵 。LLMs绝对庞大,需要足够的内存来存储整个模型,以及模型中每个参数的梯度(梯度是让模型知道调整参数方向的东西)。参数和梯度都需要存在于GPU上,这就是为什么训练LLMs需要如此多的GPU显存的。

在这里插入图片描述

二、LORA 简介

“低秩适应”(LoRA)是一种“参数高效微调”(PEFT)的形式,它允许使用少量可学习参数对大型模型进行微调 。LoRA改善微调的几个点:

  1. 将微调视为学习参数的变化(▲w),而不是调整参数本身(w)。

  2. 通过删除重复信息,将这些变化压缩成较小的表示。

  3. 通过简单地将它们添加到预训练参数中来“加载”新的变化。

2.1 微调作为参数变化

正如之前讨论的,微调的最基本方法是迭代地更新参数。就像正常的模型训练一样,你让模型进行推理,然后根据推理的错误程度更新模型的参数。

与其将微调视为学习更好的参数,LoRA 将微调视为学习参数变化:冻结模型参数,然后学习使模型在微调任务中表现更好所需的这些参数的变化。

类似于训练,首先让模型推理,然后根据error进行更新。但是,不更新模型参数,而是更新模型参数的变化。

在这里插入图片描述

在LoRA中,我们冻结模型参数,并创建一组描述这些参数变化的新值。然后,我们学习必要的参数变化,以在微调任务上表现更好。LoRA添加更多的数据和额外的步骤,如何使微调变得更小、更快?

2.2 参数变化压缩

**加粗样式**

矩阵的秩是为了量化矩阵中的线性独立性。我们可以将一个矩阵分解为一些线性独立的向量;这种矩阵的形式被称为“行阶梯形式”。

在这里插入图片描述

因此,矩阵可以包含一定程度的“重复信息”,即线性相关性。如果你有一个大矩阵,具有显著的线性相关性(因此秩较低),可以将该矩阵表示为两个相对较小的矩阵的乘积。这种分解的思想使得LoRA占用了如此小的内存空间。

三、LoRA微调的流程

首先冻结模型参数。使用这些参数进行推理,但不会更新它们。然后创建两个矩阵,当它们相乘时,它们的大小将与我们正在微调的模型的权重矩阵的大小相同。在一个大型模型中,有多个权重矩阵,为每个权重矩阵创建一个这样的配对。
在这里插入图片描述

LoRA将这些矩阵称为矩阵“A”和“B”。这些矩阵一起代表了LoRA微调过程中的可学习参数。
在这里插入图片描述

然后将输入通过冻结的权重和变化矩阵传递:
在这里插入图片描述

根据两个输出的组合计算损失,然后根据损失更新矩阵A和B:
在这里插入图片描述

这些变化矩阵是即时计算的,从未被存储,这就是为什么LoRA的内存占用如此小的原因。实际上,在训练期间只存储模型参数、矩阵A和B以及A和B的梯度。

我们执行此操作,直到我们优化了变化矩阵的因素以进行微调任务。更新矩阵A和B的反向传播步骤比更新完整模型参数集的过程要快得多,因为A和B要小得多。这就是为什么尽管训练过程中有更多的操作,LoRA仍然通常比传统微调更快的原因。

当我们最终想要使用这个微调模型进行推断时,我们只需计算变化矩阵,并将变化添加到权重中。这意味着LoRA不会改变模型的推断时间:

在这里插入图片描述

四、LoRA在Transformer中的应用

实际上许多大模型具有复杂的结构,不是一个整体结构。像Transformer这样的模型中的参数,如何应用LoRA?
在这里插入图片描述

对于Transformer模型,有两个要注意的事项:

  1. 通常,在Transformer的多头自注意力层中,密集网络(用于构建查询、键和值)的深度只有1。也就是说,只有一个输入层和一个由权重连接的输出层。

  2. 这些浅层密集网络是Transformer中大部分可学习参数,非常非常大。可能有超过100,000个输入神经元连接到100,000个输出神经元,这意味着描述其中一个网络的单个权重矩阵可能有10B个参数。因此,尽管这些网络的深度只有1,但它们非常宽,因此描述它们的权重矩阵非常大。

LoRA在Transformer模型上,要学习每个非常大但浅层的密集层的分解变化。

五、LoRA Rank

LoRA有一个超参数,称为Rank,它描述了用于构建之前讨论的变化矩阵的深度。较高的值意味着更大的和矩阵,这意味着它们可以在变化矩阵中编码更多的线性独立信息。
在这里插入图片描述

“r"参数可以被视为"信息瓶颈”。较小的r值意味着A和B可以用更小的内存占用编码较少的信息。较大的r值意味着A和B可以编码更多的信息,但内存占用更大
在这里插入图片描述

一个具有r值等于1和2的LoRA的概念图。在这两个例子中,分解的A和B矩阵导致相同大小的变化矩阵,但是r=2能够将更多线性独立的信息编码到变化矩阵中,因为A和B矩阵中包含更多信息。

事实证明,LoRA论文所做的核心假设,即模型参数的变化具有低隐式秩,是一个相当强的假设。微软(LoRA的出版商)的人员尝试了一些值,并发现即使是秩为一的矩阵也表现出色。

在这里插入图片描述

LoRA论文中建议:当数据与预训练中使用的数据相似时,较低的r值可能就足够了。当在非常新的任务上进行微调时,可能需要对模型进行重大的逻辑更改,这时可能需要较高的r值

六、Python代码实现

下面以HuggingFace的一个模块做例子,用LoRA对一个预训练模型进行微调,用于问题回答。代码 链接在这里:

6.1 安装依赖库:

pip install -q bitsandbytes datasets accelerate loralib
pip install -q git+https://github.com/huggingface/peft.git git+https://github.com/huggingface/transformers.git

# bitsandbytes:用于使用较小的数据类型表示模型,节省内存。
# datasets:用于下载数据集
# accelerate:一些模块的机器学习互操作性所需的依赖项
# loralib:LoRA实现
# peft:一般的“参数高效微调”模块,与LoRA的接口
# transformers:用于下载和使用HuggingFace的预训练transformers

6.2 加载预训练模型

我们将使用BLOOM,这是一个开源且许可证宽松的语言模型。我们将使用5.6亿参数版本以节省内存,也可以将相同的策略应用于更大版本的BLOOM。

"""导入依赖项并下载预训练的bloom模型
"""

import torch
import torch.nn as nn
import bitsandbytes as bnb
from transformers import AutoTokenizer, AutoConfig, AutoModelForCausalLM

# 加载模型
model = AutoModelForCausalLM.from_pretrained(
    # "bigscience/bloom-3b",
    # "bigscience/bloom-1b1",
    "bigscience/bloom-560m",
    torch_dtype=torch.float16,
    device_map='auto',
)

# 加载用于此模型的分词器(将文本转换为模型的输入)
tokenizer = AutoTokenizer.from_pretrained("bigscience/tokenizer")


6.3 设置LoRA

"""使用参数高效微调设置LoRA"""

from peft import LoraConfig, get_peft_model

# 定义LoRA在这个特定示例中的工作方式
config = LoraConfig(
    r=8,                                 # LoRA的层数,即A和B矩阵的秩
    lora_alpha=8,                        # LoRA的alpha值,可视为缩放因子,默认等于r
    target_modules=["query_key_value"],  # 目标模块列表,即LoRA优化的模型部分
    lora_dropout=0.05,                   # LoRA的dropout率
    bias="none",                         # 偏置类型。例子中,我们只训练权重
    task_type="CAUSAL_LM"                # 任务类型
)

# 实际上,这会覆盖内存中的模型,所以重命名仅用于可读性。
peft_model = get_peft_model(model, config)  # 获取经过参数高效微调的LoRA模型


6.4 检查内存的节省

LoRA训练参数数量明显较少,即节省了内存消耗。

"""比较LoRA之前和之后的参数"""

trainable_params = 0  # 可训练参数的数量
all_param = 0  # 所有参数的数量

# 遍历所有参数
for _, param in peft_model.named_parameters():
    all_param += param.numel()  # 将参数数量添加到总数中
    if param.requires_grad:  # 如果参数需要梯度
        trainable_params += param.numel()  # 将参数数量添加到可训练参数中

# 打印结果
print(f"可训练参数数量:{trainable_params}")
print(f"所有参数数量:{all_param}")
print(f"可训练参数占比:{100 * trainable_params / all_param:.2f}%")

在这里插入图片描述

6.5 加载微调数据集

将使用SQUAD数据集 来提高语言模型在问答任务上的性能。斯坦福问答数据集(SQUAD)是一个高质量、常用且许可证宽松的数据集。

from datasets import load_dataset  # 导入load_dataset函数,用于加载数据集
qa_dataset = load_dataset("squad_v2")  # 使用load_dataset函数加载SQUAD数据集,并将结果赋值给qa_dataset变量

模型将在特定的数据结构上,对语言模型进行微调。模型将期望以以下一般形式的文本作为输入:

# 代码注释
'''
CONTEXT: 上下文
{context}

QUESTION: 问题
{question}

ANSWER: 答案
{answer}</s>
'''

们将向模型提供上下文和问题,模型将被期望向我们提供答案。因此,我们将重新格式化SQUAD中的数据以符合此格式。

"""
"""Reformatting SQUAD to respect our defined structure
"""  # 定义一个函数用于重新格式化SQUAD数据集

# 定义一个函数用于重新格式化
def create_prompt(context, question, answer):
  if len(answer["text"]) < 1:  # 如果答案的文本长度小于1
    answer = "Cannot Find Answer"  # 将答案设置为"Cannot Find Answer"
  else:
    answer = answer["text"][0]  # 否则将答案设置为答案文本的第一个元素
  prompt_template = f"CONTEXT:\n{context}\n\nQUESTION:\n{question}\n\nANSWER:\n{answer}</s>"  # 创建一个模板,包含上下文、问题和答案
  return prompt_template  # 返回模板

# 将重新格式化的函数应用于整个数据集
mapped_qa_dataset = qa_dataset.map(lambda samples: tokenizer(create_prompt(samples['context'], samples['question'], samples['answers'])))
"""

6.6 使用LoRA在SQUAD上进行微调

# 导入transformers库
import transformers

# 定义trainer,用于训练模型
trainer = transformers.Trainer(
    model=peft_model, # 模型
    train_dataset=mapped_qa_dataset["train"], # 训练集
    args=transformers.TrainingArguments(
        per_device_train_batch_size=4, # 每个设备的训练批次大小
        gradient_accumulation_steps=4, # 梯度累积步数
        warmup_steps=100, # 热身步数
        max_steps=100, # 最大步数
        learning_rate=1e-3, # 学习率
        fp16=True, # 是否使用半精度浮点数
        logging_steps=1, # 日志记录步数
        output_dir='outputs', # 输出目录
    ),
    data_collator=transformers.DataCollatorForLanguageModeling(tokenizer, mlm=False) # 数据收集器
)

peft_model.config.use_cache = False  # 禁用缓存,以消除警告。在推理时请重新启用!
trainer.train() # 开始训练

在这里插入图片描述

6.7 检查LoRA大小

这个例子中,我们训练了100步,虽然在每一步中损失有一些随机变化,但总体上损失在训练过程中逐渐降低

"""将LoRA微调的模型保存到本地
"""
# 定义模型的标识符
model_id = "BLOOM-560m-LoRA"
# 将微调后的模型保存到指定的路径
peft_model.save_pretrained(model_id)

然后检查文件在我们的文件系统中的大小:

# 使用ls命令查看指定目录下的文件和文件夹的详细信息
!ls -lh {model_id}

在这里插入图片描述

6.8 测试

我们有一个经过微调的 LoRA 模型,让我们问它几个问题。首先,我们将定义一个辅助函数,该函数将接受一个上下文和问题,运行预测,并生成一个回答

"""用于比较结果的辅助函数"""

from IPython.display import display, Markdown

def make_inference(context, question):

    # 将输入转换为标记
    batch = tokenizer(f"**CONTEXT:**\n{context}\n\n**QUESTION:**\n{question}\n\n**ANSWER:**\n", return_tensors='pt', return_token_type_ids=False)
    # 将标记移动到GPU上进行推理
    batch = batch.to(device='cuda')

    # 使用经过微调的模型和原始模型进行推理
    with torch.cuda.amp.autocast():
        # 如果应用了LoRA,推理时间可能会更快,
        # 但是不应用LoRA可以让我同时进行微调前后的实验

        # 原始模型
        peft_model.disable_adapter_layers()
        output_tokens_raw = model.generate(**batch, max_new_tokens=200)

        # LoRA模型
        peft_model.enable_adapter_layers()
        output_tokens_qa = peft_model.generate(**batch, max_new_tokens=200)

    # 显示结果
    display(Markdown("# 原始模型\n"))
    display(Markdown((tokenizer.decode(output_tokens_raw[0], skip_special_tokens=True))))
    display(Markdown("\n# QA模型\n"))
    display(Markdown((tokenizer.decode(output_tokens_qa[0], skip_special_tokens=True))))

让我们来看几个例子:

context = "You are a monster, and you eat yellow legos."  # 定义一个字符串变量context,表示上下文信息
question = "What is the best food?"  # 定义一个字符串变量question,表示问题信息

make_inference(context, question)  # 调用make_inference函数,传入上下文信息和问题信息作为参数

在这里插入图片描述

示例2:

context = "you are a math wizard"  # 定义上下文,表示你是一个数学奇才
question = "what is 1+1 equal to?"  # 定义问题,询问1+1等于多少

make_inference(context, question)  # 调用make_inference函数进行推理

在这里插入图片描述
  因为只使用了一个560M参数的模型,所以它在基本推理方面并不出色。

context = "Answer the riddle"  # 上下文是一个谜语的问题
question = "What gets bigger the more you take away?"  # 问题是:越拿越多的东西会变得更大吗?

make_inference(context, question)  # 调用make_inference函数进行推理

在这里插入图片描述

d \sqrt{d} d ​ 1 8 \frac {1}{8} 81​ x ˉ \bar{x} xˉ x ^ \hat{x} x^ x ~ \tilde{x} x~ ϵ \epsilon ϵ
ϕ \phi ϕ


在大模型时代,我们如何有效的去学习大模型?

现如今大模型岗位需求越来越大,但是相关岗位人才难求,薪资持续走高,AI运营薪资平均值约18457元,AI工程师薪资平均值约37336元,大模型算法薪资平均值约39607元。
在这里插入图片描述

掌握大模型技术你还能拥有更多可能性

• 成为一名全栈大模型工程师,包括Prompt,LangChain,LoRA等技术开发、运营、产品等方向全栈工程;

• 能够拥有模型二次训练和微调能力,带领大家完成智能对话、文生图等热门应用;

• 薪资上浮10%-20%,覆盖更多高薪岗位,这是一个高需求、高待遇的热门方向和领域;

• 更优质的项目可以为未来创新创业提供基石。

可能大家都想学习AI大模型技术,也_想通过这项技能真正达到升职加薪,就业或是副业的目的,但是不知道该如何开始学习,因为网上的资料太多太杂乱了,如果不能系统的学习就相当于是白学。为了让大家少走弯路,少碰壁,这里我直接把都打包整理好,希望能够真正帮助到大家_。

零基础如何学习AI大模型

领取方式在文末

为什么要学习大模型?

学习大模型课程的重要性在于它能够极大地促进个人在人工智能领域的专业发展。大模型技术,如自然语言处理和图像识别,正在推动着人工智能的新发展阶段。通过学习大模型课程,可以掌握设计和实现基于大模型的应用系统所需的基本原理和技术,从而提升自己在数据处理、分析和决策制定方面的能力。此外,大模型技术在多个行业中的应用日益增加,掌握这一技术将有助于提高就业竞争力,并为未来的创新创业提供坚实的基础。

大模型典型应用场景

AI+教育:智能教学助手和自动评分系统使个性化教育成为可能。通过AI分析学生的学习数据,提供量身定制的学习方案,提高学习效果。
AI+医疗:智能诊断系统和个性化医疗方案让医疗服务更加精准高效。AI可以分析医学影像,辅助医生进行早期诊断,同时根据患者数据制定个性化治疗方案。
AI+金融:智能投顾和风险管理系统帮助投资者做出更明智的决策,并实时监控金融市场,识别潜在风险。
AI+制造:智能制造和自动化工厂提高了生产效率和质量。通过AI技术,工厂可以实现设备预测性维护,减少停机时间。

这些案例表明,学习大模型课程不仅能够提升个人技能,还能为企业带来实际效益,推动行业创新发展。

学习资料领取

如果你对大模型感兴趣,可以看看我整合并且整理成了一份AI大模型资料包,需要的小伙伴文末免费领取哦,无偿分享!!!
vx扫描下方二维码即可
加上后会一个个给大家发

在这里插入图片描述

部分资料展示

一、 AI大模型学习路线图

整个学习分为7个阶段
在这里插入图片描述
在这里插入图片描述

二、AI大模型实战案例

涵盖AI大模型的理论研究、技术实现、行业应用等多个方面。无论您是科研人员、工程师,还是对AI大模型感兴趣的爱好者,皆可用。
在这里插入图片描述

在这里插入图片描述
在这里插入图片描述

三、视频和书籍PDF合集

从入门到进阶这里都有,跟着老师学习事半功倍。
在这里插入图片描述

在这里插入图片描述
在这里插入图片描述

四、LLM面试题

在这里插入图片描述
在这里插入图片描述

五、AI产品经理面试题

在这里插入图片描述

😝朋友们如果有需要的话,可以V扫描下方二维码联系领取~

在这里插入图片描述

👉[优快云大礼包🎁:全网最全《LLM大模型入门+进阶学习资源包》免费分享(安全链接,放心点击)]👈

### LoRA 微调 示例代码与使用指南 以下内容详细介绍了如何使用 LoRA 技术微调语言模型,包括代码示例和关键注意事项。 #### 1. LoRA 理论基础 LoRA(Low-Rank Adaptation)通过引入低秩矩阵分解,在保持模型性能的同时显著降低训练成本和显存占用。这种方法特别适合于资源受限的场景,同时能够高效地适配特定任务[^1]。 #### 2. 环境准备 在开始之前,请确保安装了必要的依赖库: ```bash pip install torch transformers peft datasets ``` #### 3. LoRA 微调代码示例 以下是一个完整的 LoRA 微调代码示例,适用于 DeepSeek 或其他基于 Hugging Face 的预训练模型。 ##### 3.1 数据准备 数据文件通常以 JSON 格式存储,包含 `instruction`、`input` 和 `output` 字段。以下是数据文件的结构示例: ```json [ {"instruction": "解释人工智能的概念", "input": "", "output": "人工智能是模拟人类智能的技术..."}, {"instruction": "如何实现模型微调?", "input": "", "output": "通过提供标注数据并优化模型参数..."} ] ``` ##### 3.2 训练脚本 (`train.py`) 以下是使用 LoRA 微调模型的完整代码示例: ```python from transformers import AutoTokenizer, AutoModelForCausalLM, TrainingArguments, Trainer from peft import LoraConfig, get_peft_model, TaskType import torch from datasets import load_dataset # 加载预训练模型和分词器 model_name = "deepseek/lite-base" tokenizer = AutoTokenizer.from_pretrained(model_name) model = AutoModelForCausalLM.from_pretrained(model_name) # 配置 LoRA 参数 lora_config = LoraConfig( r=16, # 低秩矩阵的秩 lora_alpha=32, # 缩放因子 target_modules=["q_proj", "v_proj"], # 需要调整的模块 lora_dropout=0.05, # Dropout 概率 bias="none", task_type=TaskType.CAUSAL_LM # 任务类型 ) model = get_peft_model(model, lora_config) # 加载数据集 dataset = load_dataset("json", data_files="data/custom_data.json")["train"] def tokenize_function(example): prompt = f"Instruction: {example['instruction']}\nInput: {example['input']}\nOutput: {example['output']}" return tokenizer(prompt, truncation=True, max_length=512, padding="max_length") tokenized_dataset = dataset.map(tokenize_function, batched=True) # 定义训练参数 training_args = TrainingArguments( output_dir="./outputs", num_train_epochs=3, per_device_train_batch_size=4, gradient_accumulation_steps=2, learning_rate=1e-4, logging_dir="./logs", logging_steps=10, save_steps=100, save_total_limit=2, evaluation_strategy="steps", eval_steps=50, warmup_steps=50, weight_decay=0.01, fp16=True # 使用混合精度加速训练 ) # 使用 Trainer 进行训练 trainer = Trainer( model=model, args=training_args, train_dataset=tokenized_dataset, tokenizer=tokenizer ) trainer.train() ``` ##### 3.3 推理脚本 (`inference.py`) 推理脚本用于加载微调后的模型并生成文本: ```python from transformers import AutoTokenizer, AutoModelForCausalLM import torch # 加载模型和分词器 model_name = "deepseek/lite-base" tokenizer = AutoTokenizer.from_pretrained(model_name) model = AutoModelForCausalLM.from_pretrained("./outputs/checkpoint-xxx") # 推理函数 def generate_text(prompt, max_length=100): inputs = tokenizer(prompt, return_tensors="pt").to("cuda") outputs = model.generate(**inputs, max_new_tokens=max_length) return tokenizer.decode(outputs[0], skip_special_tokens=True) # 示例输入 prompt = "解释什么是人工智能?" response = generate_text(prompt) print(f"Prompt: {prompt}\nResponse: {response}") ``` #### 4. 关键注意事项 - **矩阵秩选择**:矩阵秩 `r` 是 LoRA 的核心超参数,决定了额外参数的数量和模型表达能力。通常建议从较小值(如 8 或 16)开始实验。 - **学习率调整**:由于 LoRA 只更新少量参数,学习率通常需要设置为较低值(如 1e-4 或更低)以避免破坏预训练权重。 - **任务适配性**:对于复杂任务或跨模态任务,LoRA 的效果可能不如全参数微调。在这种情况下,可以考虑结合其他技术(如 P-Tuning 或 Prompt Tuning)进行补充[^4]。 #### 5. 性能评估 微调后模型的质量可以通过以下指标进行评估: - **困惑度(Perplexity)**:衡量模型生成文本的概率分布质量。 - **BLEU 分数**:评估生成文本与参考文本的相似性。 - **人工评测**:针对特定任务,邀请领域专家对生成结果进行主观评价。 ---
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

程序员一粟

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值