跳至主要内容

为文本分类微调 Llama 3.1

开始使用全新的 Llama 模型,并自定义 Llama-3.1-8B-It,从文本中预测各类心理健康障碍。
· 13分钟 阅读

使用 AI 探索

在开始之前先提醒一下:在医疗健康领域探索 AI 用例时,务必要谨慎,关注对患者可能造成的有害结果。下面的示例虽然全面,但纯粹用于展示如何在真实世界数据集上进行微调。如果您正在医疗或其他敏感领域推进 AI 用例,我们强烈建议阅读我们的AI 伦理指南。

在本教程中,我们将了解 Llama 3.1 系列模型,并在心理健康数据集上对 Llama-3.1-8b-It 模型进行情感分析方向的微调。我们的目标是自定义模型,使其能够基于文本预测患者的心理健康状态。我们还将把适配器与基础模型合并,并将完整模型保存到 Hugging Face Hub。

我们将学习 Llama 3.1 模型、如何在 Kaggle 上访问它们,以及如何使用 Transformers 库运行模型推理。我们还会在心理健康分类数据集上微调 Llama-3.1-8b-It 模型。最后,我们将把保存的适配器与基础模型合并,并将完整模型推送到 Hugging Face Hub。

如果您是新手,可以先阅读我们的文章大型语言模型微调入门指南,了解微调背后的理论。

图片由作者提供

Llama 3.1 简介

Llama 3.1 是 Meta AI 最新推出的多语言大型语言模型(LLM)系列,进一步推动了语言理解与生成的边界。该系列提供 8B、70B 和 405B 三种规模。

Llama 3.1 模型基于自回归语言模型架构,配备优化的 Transformer,可针对各类自然语言处理任务与数据集进行微调。其训练数据涵盖多样的公开在线数据,支持八种语言(英语、德语、法语、意大利语、葡萄牙语、印地语、西班牙语和泰语),并具备 128k 的上下文长度。

Llama 3.1 模型向所有人开放,采用自定义商业许可,下载权重所需的个人信息极少。

Llama 3.1 针对多语言对话进行了优化,并在通用对话、编程、数学、推理等多项基准上优于 Gemma 2、GPT-3.5 Turbo 和 GPT-4o。它是迄今速度最快、精度最高的开源模型之一。

Llama 3.1 benchmark

来源:Llama 3.1 (meta.com)

您可以通过阅读什么是 Meta 的 Llama 3.1 405B?工作原理、用例与更多了解更多关于 Llama 3.1 的信息。

开始使用 Llama 3.1

本教程将使用 Kaggle Notebook 作为开发环境,因为它提供免费的 GPU 和 TPU。要在 Kaggle Notebook 中使用 Llama 3.1,请按以下步骤操作:

1. 在meta.com上填写表单,使用与您 Kaggle 账户相同的邮箱地址。

2. 访问 Kaggle 上的Meta | Llama 3.1模型仓库并点击“access the model”(获取访问)按钮。接受所有条款,几秒钟后您就能获得模型访问权限。

Accessing Llama 3.1 in Kaggle

3. 在模型页面右上方点击“Code”按钮,使用 Llama 3.1 模型启动 Kaggle Notebook。

Starting the Kaggle notebook.

4. 选择框架、变体与版本,然后点击“Add Model”。

Selecting the right version of the Llama 3.1 model.

5. 在 Kaggle Notebook 中使用以下命令安装所需的 Python 包:

%pip install -U transformers accelerate

6. 使用 Transformers 库从本地目录加载模型和分词器。

7. 使用模型、分词器、torch 类型和设备映射创建文本生成管线。

from transformers import AutoTokenizer,AutoModelForCausalLM,pipeline
import torch

base_model = "/kaggle/input/llama-3.1/transformers/8b-instruct/1"

tokenizer = AutoTokenizer.from_pretrained(base_model)

model = AutoModelForCausalLM.from_pretrained(
        base_model,
        return_dict=True,
        low_cpu_mem_usage=True,
        torch_dtype=torch.float16,
        device_map="auto",
        trust_remote_code=True,
)
pipe = pipeline(
    "text-generation",
    model=model,
    tokenizer=tokenizer,
    torch_dtype=torch.float16,
    device_map="auto",
)

8. 编写消息,并使用聊天模板将其转换为合适的提示。

9. 使用该提示运行管线,并打印生成的输出。

messages = [{"role": "user", "content": "What is the tallest building in the world?"}]

prompt = tokenizer.apply_chat_template(messages, tokenize=False, add_generation_prompt=True)
outputs = pipe(prompt, max_new_tokens=120, do_sample=True)
print(outputs[0]["generated_text"])

答案准确且详尽。

Response generation output using the Llama 3.1 model

如果您在运行模型推理时遇到困难,请参阅 Kaggle Notebook: Llama 3.1 简单模型推理。

在心理健康障碍分类任务上微调 Llama 3.1

现在,我们需要加载数据集、进行处理,并对 Llama 3.1 模型进行微调。我们还将比较模型在微调前后的表现。

如果您刚接触 LLM,建议在进入本教程的微调部分之前,先学习掌握大型语言模型(LLM)概念课程。

1. 环境准备

首先,我们像上一节那样启动新的 Kaggle Notebook 并加载 Llama 3.1 模型。

然后按如下所示安装所需的 Python 包:

%%capture
%pip install -U bitsandbytes
%pip install -U transformers
%pip install -U accelerate
%pip install -U peft
%pip install -U trl

接下来,将情感分析(心理健康)数据添加到 Kaggle Notebook。为此,点击右上角的“Add Input”按钮,并将模型链接粘贴到搜索栏。然后点击加号(+)即可添加。

Adding the Sentimental analysis for Mental health dataset in Kaggle notebook.

我们将使用 Weights & Biases API 跟踪模型表现。要访问该 API,我们需要 API 密钥。请在 Kaggle 中通过 Secrets 设置并启用 API 密钥,如下所示。

Activating the w&b secrets so that it can be access in the notebook securely.

随后使用该 API 密钥初始化 Weights & Biases 项目。

import wandb

from kaggle_secrets import UserSecretsClient
user_secrets = UserSecretsClient()

wb_token = user_secrets.get_secret("wandb")

wandb.login(key=wb_token)
run = wandb.init(
    project='Fine-tune llama-3.1-8b-it on Sentiment Analysis Dataset', 
    job_type="training", 
    anonymous="allow"
)

接着导入所有必要的 Python 包与函数。

import numpy as np
import pandas as pd
import os
from tqdm import tqdm
import bitsandbytes as bnb
import torch
import torch.nn as nn
import transformers
from datasets import Dataset
from peft import LoraConfig, PeftConfig
from trl import SFTTrainer
from trl import setup_chat_format
from transformers import (AutoModelForCausalLM, 
                          AutoTokenizer, 
                          BitsAndBytesConfig, 
                          TrainingArguments, 
                          pipeline, 
                          logging)
from sklearn.metrics import (accuracy_score, 
                             classification_report, 
                             confusion_matrix)
from sklearn.model_selection import train_test_split

2. 加载并处理数据集

现在开始加载数据集、进行数据清洗,并移除三个含义不够明确的类别。

为简化处理,我们将删除“Suicidal(自杀倾向)”类别,因为 Llama 3.1 具备安全机制,会屏蔽某些触发性用词;“Stress(压力)”不被视为心理障碍;而“Personality Disorder(人格障碍)”与“Bipolar(双相障碍)”存在较多重叠。

因此,我们仅保留四个类别:“Normal(正常)”“Depression(抑郁)”“Anxiety(焦虑)”和“Bipolar(双相)”。

df = pd.read_csv("/kaggle/input/sentiment-analysis-for-mental-health/Combined Data.csv",index_col = "Unnamed: 0")
df.loc[:,'status'] = df.loc[:,'status'].str.replace('Bi-Polar','Bipolar')
df = df[(df.status != "Personality disorder") & (df.status != "Stress") & (df.status != "Suicidal")]
df.head()

Top 5 rows of the dataset

为节省训练时间,我们仅在 3000 条样本上进行微调。为此,我们将数据集打乱并选取 3000 行。

随后将数据集划分为训练集、验证集与测试集,用于训练与评估。

我们还将在训练集与验证集中使用 generate_prompt 函数创建“text”列,该函数将“statement”和“status”两列组合起来。

最后,我们将使用 generate_test_prompt 函数在测试集中创建“text”列,并用“status”列生成 y_true。我们将用其生成如下所示的模型评估报告。

# Shuffle the DataFrame and select only 3000 rows
df = df.sample(frac=1, random_state=85).reset_index(drop=True).head(3000)

# Split the DataFrame
train_size = 0.8
eval_size = 0.1

# Calculate sizes
train_end = int(train_size * len(df))
eval_end = train_end + int(eval_size * len(df))

# Split the data
X_train = df[:train_end]
X_eval = df[train_end:eval_end]
X_test = df[eval_end:]

# Define the prompt generation functions
def generate_prompt(data_point):
    return f"""
            Classify the text into Normal, Depression, Anxiety, Bipolar, and return the answer as the corresponding mental health disorder label.
text: {data_point["statement"]}
label: {data_point["status"]}""".strip()

def generate_test_prompt(data_point):
    return f"""
            Classify the text into Normal, Depression, Anxiety, Bipolar, and return the answer as the corresponding mental health disorder label.
text: {data_point["statement"]}
label: """.strip()

# Generate prompts for training and evaluation data
X_train.loc[:,'text'] = X_train.apply(generate_prompt, axis=1)
X_eval.loc[:,'text'] = X_eval.apply(generate_prompt, axis=1)

# Generate test prompts and extract true labels
y_true = X_test.loc[:,'status']
X_test = pd.DataFrame(X_test.apply(generate_test_prompt, axis=1), columns=["text"])

此时,我们想检查训练集中各类别的分布。

X_train.status.value_counts()

如下所示,“Normal”和“Depression”的分布几乎相当,其余标签为少数类。这意味着我们的数据集不平衡,模型在预测多数类时会优于少数类。

我们可以对数据集进行平衡处理,但这不是本教程的目标。

status
Normal        1028
Depression     938
Anxiety        258
Bipolar        176
Name: count, dtype: int64

接下来,我们将把训练集和验证集转换为 Hugging Face Datasets。

# Convert to datasets
train_data = Dataset.from_pandas(X_train[["text"]])
eval_data = Dataset.from_pandas(X_eval[["text"]])

然后展示“text”列中的第 4 个样本。

train_data['text'][3]

可以看到,“text”列包含系统提示、原始语句和作为标签的状态。

3. 加载模型与分词器

接下来,我们以 4 位量化方式加载 Llama-3.1-8b-instruct 模型,以节省 GPU 内存。

随后加载分词器并设置 pad token id。

base_model_name = "/kaggle/input/llama-3.1/transformers/8b-instruct/1"

bnb_config = BitsAndBytesConfig(
    load_in_4bit=True,
    bnb_4bit_use_double_quant=False,
    bnb_4bit_quant_type="nf4",
    bnb_4bit_compute_dtype="float16",
)

model = AutoModelForCausalLM.from_pretrained(
    base_model_name,
    device_map="auto",
    torch_dtype="float16",
    quantization_config=bnb_config, 
)

model.config.use_cache = False
model.config.pretraining_tp = 1

tokenizer = AutoTokenizer.from_pretrained(base_model_name)

tokenizer.pad_token_id = tokenizer.eos_token_id

4. 微调前的模型评估

这里我们创建 predict 函数,使用文本生成管线从“text”列预测标签。运行该函数将基于测试集中的各样本返回心理障碍类别列表。

def predict(test, model, tokenizer):
    y_pred = []
    categories = ["Normal", "Depression", "Anxiety", "Bipolar"]
    
    for i in tqdm(range(len(test))):
        prompt = test.iloc[i]["text"]
        pipe = pipeline(task="text-generation", 
                        model=model, 
                        tokenizer=tokenizer, 
                        max_new_tokens=2, 
                        temperature=0.1)
        
        result = pipe(prompt)
        answer = result[0]['generated_text'].split("label:")[-1].strip()
        
        # Determine the predicted category
        for category in categories:
            if category.lower() in answer.lower():
                y_pred.append(category)
                break
        else:
            y_pred.append("none")
    
    return y_pred

y_pred = predict(X_test, model, tokenizer)
100%|██████████| 300/300 [02:54<00:00,  1.72it/s]

随后我们创建 evaluate 函数,基于预测标签与真实标签计算模型总体准确率及各类别准确率,生成分类报告,并打印混淆矩阵。运行后将得到详细的评估摘要。

def evaluate(y_true, y_pred):
    labels = ["Normal", "Depression", "Anxiety", "Bipolar"]
    mapping = {label: idx for idx, label in enumerate(labels)}
    
    def map_func(x):
        return mapping.get(x, -1)  # Map to -1 if not found, but should not occur with correct data
    
    y_true_mapped = np.vectorize(map_func)(y_true)
    y_pred_mapped = np.vectorize(map_func)(y_pred)
    
    # Calculate accuracy
    accuracy = accuracy_score(y_true=y_true_mapped, y_pred=y_pred_mapped)
    print(f'Accuracy: {accuracy:.3f}')
    
    # Generate accuracy report
    unique_labels = set(y_true_mapped)  # Get unique labels
    
    for label in unique_labels:
        label_indices = [i for i in range(len(y_true_mapped)) if y_true_mapped[i] == label]
        label_y_true = [y_true_mapped[i] for i in label_indices]
        label_y_pred = [y_pred_mapped[i] for i in label_indices]
        label_accuracy = accuracy_score(label_y_true, label_y_pred)
        print(f'Accuracy for label {labels[label]}: {label_accuracy:.3f}')
        
    # Generate classification report
    class_report = classification_report(y_true=y_true_mapped, y_pred=y_pred_mapped, target_names=labels, labels=list(range(len(labels))))
    print('\nClassification Report:')
    print(class_report)
    
    # Generate confusion matrix
    conf_matrix = confusion_matrix(y_true=y_true_mapped, y_pred=y_pred_mapped, labels=list(range(len(labels))))
    print('\nConfusion Matrix:')
    print(conf_matrix)

evaluate(y_true, y_pred)

即便未进行微调,Llama 3.1 也有非常出色的表现。79% 的准确率已相当不错。让我们看看在数据集上微调后模型能提升到何种程度。

Accuracy: 0.790
Accuracy for label Normal: 0.741
Accuracy for label Depression: 0.939
Accuracy for label Anxiety: 0.556
Accuracy for label Bipolar: 0.533

Classification Report:
              precision    recall  f1-score   support

      Normal       0.92      0.74      0.82       143
  Depression       0.70      0.94      0.80       115
     Anxiety       0.68      0.56      0.61        27
     Bipolar       0.89      0.53      0.67        15

    accuracy                           0.79       300
   macro avg       0.80      0.69      0.73       300
weighted avg       0.81      0.79      0.79       300


Confusion Matrix:
[[106  33   4   0]
 [  3 108   3   1]
 [  4   8  15   0]
 [  2   5   0   8]]

5. 构建模型

在构建模型时,我们先使用 bits and bytes 库从模型中提取线性模块名称。

然后基于目标模块、任务类型及其他参数配置 LoRA,并设置训练参数。这里的训练参数针对 Kaggle Notebook 进行了优化,如果在本地使用,您可能需要根据实际情况调整。

随后基于训练参数、模型、分词器、LoRA 配置与数据集创建训练器。

import bitsandbytes as bnb

def find_all_linear_names(model):
    cls = bnb.nn.Linear4bit
    lora_module_names = set()
    for name, module in model.named_modules():
        if isinstance(module, cls):
            names = name.split('.')
            lora_module_names.add(names[0] if len(names) == 1 else names[-1])
    if 'lm_head' in lora_module_names:  # needed for 16 bit
        lora_module_names.remove('lm_head')
    return list(lora_module_names)
modules = find_all_linear_names(model)
modules
['down_proj', 'gate_proj', 'o_proj', 'v_proj', 'up_proj', 'q_proj', 'k_proj']
output_dir="llama-3.1-fine-tuned-model"

peft_config = LoraConfig(
    lora_alpha=16,
    lora_dropout=0,
    r=64,
    bias="none",
    task_type="CAUSAL_LM",
    target_modules=modules,
)

training_arguments = TrainingArguments(
    output_dir=output_dir,                    # directory to save and repository id
    num_train_epochs=1,                       # number of training epochs
    per_device_train_batch_size=1,            # batch size per device during training
    gradient_accumulation_steps=8,            # number of steps before performing a backward/update pass
    gradient_checkpointing=True,              # use gradient checkpointing to save memory
    optim="paged_adamw_32bit",
    logging_steps=1,                         
    learning_rate=2e-4,                       # learning rate, based on QLoRA paper
    weight_decay=0.001,
    fp16=True,
    bf16=False,
    max_grad_norm=0.3,                        # max gradient norm based on QLoRA paper
    max_steps=-1,
    warmup_ratio=0.03,                        # warmup ratio based on QLoRA paper
    group_by_length=False,
    lr_scheduler_type="cosine",               # use cosine learning rate scheduler
    report_to="wandb",                  # report metrics to w&b
    eval_strategy="steps",              # save checkpoint every epoch
    eval_steps = 0.2
)

trainer = SFTTrainer(
    model=model,
    args=training_arguments,
    train_dataset=train_data,
    eval_dataset=eval_data,
    peft_config=peft_config,
    dataset_text_field="text",
    tokenizer=tokenizer,
    max_seq_length=512,
    packing=False,
    dataset_kwargs={
    "add_special_tokens": False,
    "append_concat_token": False,
    }
)

6. 模型训练

现在开始启动模型训练:

trainer.train()

微调耗时约 1.5 小时,验证损失稳步下降。若想获得更佳表现,建议在完整数据集上训练至少五个 epoch。

Model training logs

接下来结束本次 Weights & Biases 运行。

wandb.finish()
model.config.use_cache = True

您将能够查看运行摘要,包括模型性能所需的各项指标。

w&b run summary

如需查看更详细的摘要,请前往您的 Weights & Biases 账户,在浏览器中打开该运行,您将看到交互式可视化。

Detailed visualization of the run summary at weights and biases website.

然后我们在本地保存模型适配器和分词器。下一节将用它来把适配器与基础模型合并。

# Save trained model and tokenizer
trainer.save_model(output_dir)
tokenizer.save_pretrained(output_dir)

7. 微调后的模型测试

接下来是关键部分。我们的模型在微调后会更好还是更差?要验证,需要在测试集上运行“predict”函数,然后运行“evaluate”函数生成评估报告。

y_pred = predict(X_test, model, tokenizer)
evaluate(y_true, y_pred)

如您在下方结果所见,模型性能大幅提升,准确率从 79% 提高到 91.3%。F1 分数也表现良好。

100%|██████████| 300/300 [03:24<00:00,  1.47it/s]
Accuracy: 0.913
Accuracy for label Normal: 0.972
Accuracy for label Depression: 0.913
Accuracy for label Anxiety: 0.667
Accuracy for label Bipolar: 0.800

Classification Report:
              precision    recall  f1-score   support

      Normal       0.92      0.97      0.95       143
  Depression       0.93      0.91      0.92       115
     Anxiety       0.75      0.67      0.71        27
     Bipolar       1.00      0.80      0.89        15

    accuracy                           0.91       300
   macro avg       0.90      0.84      0.87       300
weighted avg       0.91      0.91      0.91       300


Confusion Matrix:
[[139   3   1   0]
 [  5 105   5   0]
 [  6   3  18   0]
 [  1   2   0  12]]

现在我们可以保存 Kaggle Notebook,以保存结果和模型文件。点击右上角的“Save Version”按钮,选择版本类型“Quick Save”,并将输出保存类型设置为“Always save output when creating a Quick Save(创建 Quick Save 时始终保存输出)”。

如果您在微调模型时遇到问题,请参考 Kaggle笔记本获取更多帮助。

您也可以通过我们的指南本地微调 Llama 3 并在本地使用了解如何微调 Llama 3.0 模型。

合并并保存微调后的 Llama 3.1 模型

本节我们将把适配器与基础模型合并,并将完整版本保存到 Hugging Face Hub。

首先,启动一个启用 GPU 加速的新 Kaggle Notebook,并将已保存的 Notebook 添加为输入以访问模型文件。我们也可以像添加数据集一样,将其他 Kaggle Notebook 作为输入加入。

Adding the notebook as dataset in the Kaggle Notebook.

随后安装所需的 Python 包。

%%capture
%pip install -U bitsandbytes
%pip install -U transformers
%pip install -U accelerate
%pip install -U peft

接着使用 API 密钥登录 Hugging Face Hub API,以便将模型文件推送到对应的模型仓库。

from huggingface_hub import login
from kaggle_secrets import UserSecretsClient
user_secrets = UserSecretsClient()

hf_token = user_secrets.get_secret("HUGGINGFACE_TOKEN")
login(token = hf_token)

然后设置基础模型与微调模型的目录。

base_model = "/kaggle/input/llama-3.1/transformers/8b-instruct/1"
fine_tuned_model = "/kaggle/input/fine-tune-llama-3-1-for-text-classification/llama-3.1-fine-tuned-model/"

随后加载分词器与基础模型。

from transformers import AutoModelForCausalLM, AutoTokenizer, pipeline
from peft import PeftModel
import torch


# Reload tokenizer and model
tokenizer = AutoTokenizer.from_pretrained(base_model)

base_model_reload = AutoModelForCausalLM.from_pretrained(
        base_model,
        return_dict=True,
        low_cpu_mem_usage=True,
        torch_dtype=torch.float16,
        device_map="auto",
        trust_remote_code=True,
)

接着将基础模型与微调适配器进行合并,如下所示。

# Merge adapter with base model
model = PeftModel.from_pretrained(base_model_reload, fine_tuned_model)
model = model.merge_and_unload()

在保存模型之前,先检查其是否正常工作。使用模型和分词器创建文本生成管线,并提供示例提示。

text = "I'm trapped in a storm of emotions that I can't control, and it feels like no one understands the chaos inside me"
prompt = f"""Classify the text into Normal, Depression, Anxiety, Bipolar, and return the answer as the corresponding mental health disorder label.
text: {text}
label: """.strip()

pipe = pipeline(
    "text-generation",
    model=model,
    tokenizer=tokenizer,
    torch_dtype=torch.float16,
    device_map="auto",
)

outputs = pipe(prompt, max_new_tokens=2, do_sample=True, temperature=0.1)
print(outputs[0]["generated_text"].split("label: ")[-1].strip())

我们的模型工作正常。

Depression

现在可以在本地保存模型和分词器。

model_dir = "Llama-3.1-8B-Instruct-Mental-Health-Classification"
model.save_pretrained(model_dir)
tokenizer.save_pretrained(model_dir)

然后将模型与分词器推送到 Hugging Face Hub。

model.push_to_hub(model_dir, use_temp_dir=False)
tokenizer.push_to_hub(model_dir, use_temp_dir=False)

这将会在 Hugging Face 上创建仓库,并推送所有模型与分词器文件。

CommitInfo(commit_url='https://huggingface.co/kingabzpro/Llama-3.1-8B-Instruct-Mental-Health-Classification/commit/e1244abeaac159e0a48439095200a4190c2b493c', commit_message='Upload tokenizer', commit_description='', oid='e1244abeaac159e0a48439095200a4190c2b493c', pr_url=None, pr_revision=None, pr_num=None)

您可以访问 Hugging Face 网站查看所有模型文件。

Fine-tuned model repository on the Hugging Face.

来源:kingabzpro/Llama-3.1-8B-Instruct-Mental-Health-Classification · Hugging Face

如果您在将适配器与基础模型合并时遇到问题,请参考该Kaggle 笔记本。

如果您觉得微调 LLM 较为困难,可以参考微调 OpenAI GPT-4:一步一步指南教程,学习如何用 OpenAI API 以极少代码在任意数据集上微调模型。

结语

微调不仅限于基于数据集的定制。我们可以在诸如机器翻译、聚类、分类、问答、嵌入等各类自然语言任务上微调大型语言模型。

在本教程中,我们学习了如何在心理健康分类数据集上微调 Llama 3.1。该模型可用于识别在日常生活中面临挑战的患者,甚至员工。

如果您想了解如何入门 LLM 并开始自行微调模型,建议学习开发大型语言模型技能路径。该系列课程将帮助您夯实 LLM 基础,迈入全新的 AI 驱动时代。

Abid Ali Awan

作者

Abid Ali Awan

作为一名持证的数据科学家,我热衷于利用前沿技术打造创新的机器学习应用。凭借在语音识别、数据分析与报告、MLOps、对话式人工智能以及自然语言处理方面的扎实背景,我不断打磨构建智能系统的能力,力求带来切实影响。除技术专长外,我也擅长沟通,能够将复杂概念提炼为清晰、简明的表述。因此,我成为数据科学领域备受关注的博主,与不断壮大的数据专业人士社区分享洞见与实践经验。目前,我专注于内容创作与编辑,借助大语言模型打造有力且吸引人的内容,帮助企业与个人更好地发挥数据价值。

主题