使用同态加密实现对加密数据的情感分析
使用同态加密进行加密数据的情感分析
众所周知,情感分析模型可以确定一段文本是积极的、消极的还是中立的。然而,这个过程通常需要访问未加密的文本,这可能会引发隐私问题。
同态加密是一种允许对加密数据进行计算而无需先解密的加密类型。这使得它非常适用于用户个人和潜在敏感数据面临风险的应用领域(例如私人消息的情感分析)。
本博客文章使用了Concrete-ML库,使数据科学家能够在完全同态加密(FHE)环境中使用机器学习模型,而无需事先了解密码学知识。我们提供了一个实用的教程,介绍如何使用该库来构建一个基于加密数据的情感分析模型。
本文章涵盖以下内容:
- transformers
- 如何使用transformers与XGBoost进行情感分析
- 如何进行训练
- 如何使用Concrete-ML将预测转换为加密数据上的预测
- 如何使用客户端/服务器协议将模型部署到云端
最后,我们将以完整的Hugging Face Spaces演示来展示这个功能。
设置环境
首先确保您的pip和setuptools已经更新到最新版本,运行以下命令:
pip install -U pip setuptools
现在我们可以使用以下命令安装此博客所需的所有必要库。
pip install concrete-ml transformers datasets
使用公共数据集
我们在这个笔记本中使用的数据集可以在这里找到。
为了表示用于情感分析的文本,我们选择使用transformer隐藏表示,因为它以非常高效的方式为最终模型提供了高精度。与TF-IDF方法等更常见的过程相比,这种表示集具有更高的准确性,请参阅这个完整的笔记本进行比较。
我们可以开始打开数据集并可视化一些统计信息。
from datasets import load_datasets
train = load_dataset("osanseviero/twitter-airline-sentiment")["train"].to_pandas()
text_X = train['text']
y = train['airline_sentiment']
y = y.replace(['negative', 'neutral', 'positive'], [0, 1, 2])
pos_ratio = y.value_counts()[2] / y.value_counts().sum()
neg_ratio = y.value_counts()[0] / y.value_counts().sum()
neutral_ratio = y.value_counts()[1] / y.value_counts().sum()
print(f'积极样本比例:{round(pos_ratio * 100, 2)}%')
print(f'消极样本比例:{round(neg_ratio * 100, 2)}%')
print(f'中立样本比例:{round(neutral_ratio * 100, 2)}%')
输出结果如下:
积极样本比例:16.14%
消极样本比例:62.69%
中立样本比例:21.17%
积极和中立样本的比例相当接近,但消极样本明显更多。在选择最终评估指标时,请记住这一点。
现在我们可以将数据集拆分为训练集和测试集。我们将使用一个种子代码来确保它是完全可复现的。
from sklearn.model_selection import train_test_split
text_X_train, text_X_test, y_train, y_test = train_test_split(text_X, y,
test_size=0.1, random_state=42)
使用transformer进行文本表示
transformers是经常训练用于预测文本中下一个出现的单词的神经网络(这个任务通常被称为自监督学习)。它们还可以在一些特定的子任务上进行微调,以便在给定问题上获得更好的结果。
它们是各种自然语言处理任务的强大工具。实际上,我们可以利用它们的表示形式来处理任何文本,并将其馈送给更适合FHE的机器学习模型进行分类。在这个笔记本中,我们将使用XGBoost。
我们首先导入transformers所需的库。在这里,我们使用了Hugging Face的流行库来快速获取一个transformer。
我们选择的模型是一个BERT transformer,它在斯坦福情感树库数据集上进行了微调。
import torch
from transformers import AutoModelForSequenceClassification, AutoTokenizer
device = "cuda:0" if torch.cuda.is_available() else "cpu"
# 加载tokenizer(将文本转换为标记)
tokenizer = AutoTokenizer.from_pretrained("cardiffnlp/twitter-roberta-base-sentiment-latest")
# 加载预训练模型
transformer_model = AutoModelForSequenceClassification.from_pretrained(
"cardiffnlp/twitter-roberta-base-sentiment-latest"
)
这将会下载模型,现在可以使用了。
使用文本的隐藏表示对于初学者来说可能有些棘手,主要是因为我们可以采用许多不同的方法来解决这个问题。下面是我们选择的方法。
首先,我们对文本进行标记化。标记化意味着将文本分割成标记(一系列特定字符,也可以是单词)并用一个数字来替换每个标记。然后,我们将标记化的文本发送到transformer模型,该模型为每个单词输出一个隐藏表示(自注意力层的输出,通常用作分类层的输入)。最后,我们对每个单词的表示进行平均,得到一个文本级别的表示。
结果是一个形状为(样本数,隐藏大小)的矩阵。隐藏大小是隐藏表示中的维数数目。对于BERT,隐藏大小为768。隐藏表示是一个表示文本的数字向量,可以用于许多不同的任务。在这种情况下,我们将在之后的XGBoost分类中使用它。
import numpy as np
import tqdm
# 将文本列表转换为transformer学习到的表示的函数
def text_to_tensor(
list_text_X_train: list,
transformer_model: AutoModelForSequenceClassification,
tokenizer: AutoTokenizer,
device: str,
) -> np.ndarray:
# 逐个标记化列表中的每个文本
tokenized_text_X_train_split = []
tokenized_text_X_train_split = [
tokenizer.encode(text_x_train, return_tensors="pt")
for text_x_train in list_text_X_train
]
# 将模型送入设备
transformer_model = transformer_model.to(device)
output_hidden_states_list = [None] * len(tokenized_text_X_train_split)
for i, tokenized_x in enumerate(tqdm.tqdm(tokenized_text_X_train_split)):
# 将标记传递给transformer模型,并获取隐藏状态
# 目前只保留最后一个隐藏层状态
output_hidden_states = transformer_model(tokenized_x.to(device), output_hidden_states=True)[
1
][-1]
# 沿着标记轴求平均,得到文本级别的表示。
output_hidden_states = output_hidden_states.mean(dim=1)
output_hidden_states = output_hidden_states.detach().cpu().numpy()
output_hidden_states_list[i] = output_hidden_states
return np.concatenate(output_hidden_states_list, axis=0)
# 使用transformer对文本进行向量化
list_text_X_train = text_X_train.tolist()
list_text_X_test = text_X_test.tolist()
X_train_transformer = text_to_tensor(list_text_X_train, transformer_model, tokenizer, device)
X_test_transformer = text_to_tensor(list_text_X_test, transformer_model, tokenizer, device)
这种文本转换(文本到transformer表示)需要在客户端机器上执行,因为加密是在transformer表示上完成的。
使用XGBoost进行分类
现在我们已经正确构建了训练集和测试集来训练分类器,接下来是训练我们的FHE模型。在这里,我们可以使用诸如scikit-learn中的GridSearch等超参数调优工具。
from concrete.ml.sklearn import XGBClassifier
from sklearn.model_selection import GridSearchCV
# 构建我们的模型
model = XGBClassifier()
# 使用GridSearch等超参数调优工具来寻找最佳参数
parameters = {
"n_bits": [2, 3],
"max_depth": [1],
"n_estimators": [10, 30, 50],
"n_jobs": [-1],
}
# 现在我们对每个推文都有了一个表示,可以在这些表示上训练模型。
grid_search = GridSearchCV(model, parameters, cv=5, n_jobs=1, scoring="accuracy")
grid_search.fit(X_train_transformer, y_train)
# 检查最佳模型的准确率
print(f"最佳得分: {grid_search.best_score_}")
# 检查最佳超参数
print(f"最佳参数: {grid_search.best_params_}")
# 提取最佳模型
best_model = grid_search.best_estimator_
输出如下:
最佳得分:0.8378111718275654
最佳参数:{'max_depth': 1, 'n_bits': 3, 'n_estimators': 50, 'n_jobs': -1}
现在,让我们看看模型在测试集上的表现。
from sklearn.metrics import ConfusionMatrixDisplay
# 在测试集上计算指标
y_pred = best_model.predict(X_test_transformer)
y_proba = best_model.predict_proba(X_test_transformer)
# 计算并绘制混淆矩阵
matrix = confusion_matrix(y_test, y_pred)
ConfusionMatrixDisplay(matrix).plot()
# 计算准确率
accuracy_transformer_xgboost = np.mean(y_pred == y_test)
print(f"准确率:{accuracy_transformer_xgboost:.4f}")
输出如下:
准确率:0.8504
在加密数据上进行预测
现在让我们在加密文本上进行预测。这里的想法是我们将加密转换器给出的表示形式加密,而不是原始文本本身。在 Concrete-ML 中,您可以通过在预测函数中设置参数execute_in_fhe=True
来快速完成此操作。这仅是一种开发者功能(主要用于检查 FHE 模型的运行时间)。我们将在稍后的部署设置中看到如何使其工作。
import time
# 编译模型以获取 FHE 推断引擎
# (根据所选模型,这可能需要几分钟的时间)
start = time.perf_counter()
best_model.compile(X_train_transformer)
end = time.perf_counter()
print(f"编译时间:{end - start:.4f} 秒")
# 让我们编写一个自定义示例并在 FHE 中进行预测
tested_tweet = ["AirFrance 很棒,和 Zama 一样棒!"]
X_tested_tweet = text_to_tensor(tested_tweet, transformer_model, tokenizer, device)
clear_proba = best_model.predict_proba(X_tested_tweet)
# 现在让我们在单个推文上使用 FHE 进行预测并打印所需的时间
start = time.perf_counter()
decrypted_proba = best_model.predict_proba(X_tested_tweet, execute_in_fhe=True)
end = time.perf_counter()
fhe_exec_time = end - start
print(f"FHE 推断时间:{fhe_exec_time:.4f} 秒")
输出如下:
编译时间:9.3354 秒
FHE 推断时间:4.4085 秒
还需要检查 FHE 预测与清晰预测是否相同。
print(f"FHE 推断的概率:{decrypted_proba}")
print(f"清晰模型的概率:{clear_proba}")
该输出读取:
从 FHE 推断中的概率:[[0.08434131 0.05571389 0.8599448 ]]
从清晰模型中的概率:[[0.08434131 0.05571389 0.8599448 ]]
部署
此时,我们的模型已经完全训练和编译,准备好部署。在 Concrete-ML 中,您可以使用部署 API 轻松完成此操作:
# 将模型保存以备稍后推送到服务器
from concrete.ml.deployment import FHEModelDev
fhe_api = FHEModelDev("sentiment_fhe_model", best_model)
fhe_api.save()
这几行足以导出客户端和服务器所需的所有文件。您可以在这里查看详细说明此部署 API 的笔记本。
在 Hugging Face Space 中的完整示例
您还可以在 Hugging Face Space 上查看最终应用程序。客户端应用程序使用 Gradio 来开发,而服务器使用 Uvicorn 来运行,使用 FastAPI 来开发。
流程如下:
- 用户生成新的私钥/公钥
- 用户输入将被编码、量化和加密的消息
- 服务器接收到加密的数据,并使用公共评估密钥对加密数据进行预测
- 服务器发送回加密的预测结果,客户端可以使用私钥对其进行解密
结论
我们提出了一种利用transformers的方法,其中表示被用于:
- 训练一个机器学习模型来对推文进行分类,以及
- 使用这个模型和FHE对加密数据进行预测。
最终模型(Transformer表示+XGboost)的最终准确率为85%,高于Transformer本身的80%准确率(请参见此笔记本进行比较)。
每个示例的FHE执行时间为4.4秒,使用16核CPU。
部署文件用于一个情感分析应用程序,允许客户端在整个通信链路中保持数据加密,向服务器请求情感分析预测。
Concrete-ML(别忘了在Github上给我们点赞⭐️💛)可以简便地构建ML模型,并将其转换为FHE等效模型,以便能够对加密数据进行预测。
希望您喜欢这篇文章,并告诉我们您的想法/反馈!
特别感谢Abubakar Abid在构建我们的第一个Hugging Face Space时提供的宝贵建议!