0


使用自编码器进行数据的匿名化以保护数据隐私

使用自编码器可以保持预测能力的同时进行数据匿名化数据。

在这篇文章中,我们将看到如何使用自动编码器(一种特殊的人工神经网络)来匿名化数据。该方法所提取的数据的潜在表示可以在不影响原始数据性能的前提下用于下游的机器学习预测任务中。

本教程分为两个部分。在第一个例子中,我将展示一个自动编码器的结构。在第二部分中,我将展示如何使用自动编码器对表格数据进行编码,以匿名化数据,并将其用于其他机器学习任务,同时保护隐私。

Autoencoder

自动编码器是一种特殊的神经网络,由编码器和解码器两部分组成。编码器部分接收输入数据并将其转换为潜表示,而解码部分试图从潜表示中重构输入数据。损失是输入数据和重构数据之间的距离。

一个受过良好训练的自动编码器能够提供一个良好的潜在表示。这种表示与原始数据有很大的不同,但它拥有包含在输入层中的所有信息。

为了说明这一点,让我们尝试在众所周知的公共数据集MNIST上运行一个自动编码器。

让我们为本教程导入一些包。

from pandas import read_csv, set_option, get_dummies, DataFrame
from sklearn.preprocessing import MinMaxScaler
from sklearn.model_selection import train_test_split
from sklearn.ensemble import RandomForestClassifier
from sklearn.model_selection import cross_validate
from sklearn.inspection import permutation_importance
from numpy import mean, max, prod, array, hstack
from numpy.random import choice
from matplotlib.pyplot import barh, yticks, ylabel, xlabel, title, show, scatter, cm, figure, imshow
from tensorflow.keras.layers import Input, Dense, Dropout, Activation, BatchNormalization
from tensorflow.keras import Model
from tensorflow.keras.datasets import mnist
from tensorflow.keras.callbacks import EarlyStopping
from tensorflow.keras.utils import plot_model
from tqdm import tqdm

我们将构建和训练不同的自动编码器,因此,让我们定义一个函数。

def build_autoencoder(dim_input, dim_layer_1, dim_layer_2):
    
    input_layer = Input(shape=(dim_input,))
    x = Activation("relu")(input_layer)
    x = Dense(dim_layer_1)(x)
    x = Activation("relu")(x)
    bottleneck_layer = Dense(dim_layer_2)(x)
    x = Activation("relu")(bottleneck_layer)
    x = Dense(dim_layer_1)(x)
    x = Activation("relu")(x)    
    output_layer = Dense(dim_input, activation='relu')(x)
    
    encoder = Model(input_layer, bottleneck_layer)
    autoencoder = Model(input_layer, output_layer)
    autoencoder.compile(optimizer='adam', loss='mse')
    
    return autoencoder, encoder

上面定义的自动编码器有三个隐藏层。输入层和输出层具有相同的大小。当我们训练神经网络时,计算输入和输出的差值来反向传播损失和更新权值,而在预测阶段,我们只使用编码器部分的权值,因为我们只需要潜表示。

现在,加载已经分割成训练集和测试集的数据集。

(X_train, y_train), (X_test, y_test) = mnist.load_data()
X_train = X_train.astype('float32') / 255.
X_test = X_test.astype('float32') / 255.
X_train = X_train.reshape((len(X_train), prod(X_train.shape[1:])))
X_test = X_test.reshape((len(X_test), prod(X_test.shape[1:])))

构建我们的自动编码器。

autoencoder, encoder = build_autoencoder(
    dim_input=X_train.shape[1], 
    dim_layer_1=64,
    dim_layer_2=16
)

这个模型的结构如下:

我们用训练集来训练网络的权值。

callbacks = [
    EarlyStopping(
        monitor='val_loss', 
        min_delta=0.0001,
        patience=5, 
        restore_best_weights=True
    )
]autoencoder.fit(
    X_train, X_train,
    epochs=100,
    batch_size=256,
    shuffle=True,
    validation_split=0.3,
    callbacks=callbacks
)

一旦训练结束,我们就可以在测试集上测试自动编码器。

encoded = array(encoder(X_test))
decoded = array(autoencoder(X_test))

让我们绘制原始数据、编码的表示和重构的数据。

fig = figure(figsize=(16, 8))n_plots = 10
n_rows = int(n_plots/2)for j in range(n_plots):
    
    fig.add_subplot(n_rows, 6, 3*j+1)
    plot_tmp = imshow(X_test[j].reshape([28, 28]))
    plot_tmp.axes.get_xaxis().set_visible(False)
    plot_tmp.axes.get_yaxis().set_visible(False)
    
    fig.add_subplot(n_rows, 6, 3*j+2)
    plot_tmp = imshow(encoded[j].reshape([1, 16]))
    plot_tmp.axes.get_xaxis().set_visible(False)
    plot_tmp.axes.get_yaxis().set_visible(False)
    
    fig.add_subplot(n_rows, 6, 3*j+3)
    plot_tmp = imshow(decoded[j].reshape([28, 28]))
    plot_tmp.axes.get_xaxis().set_visible(False)
    plot_tmp.axes.get_yaxis().set_visible(False)
    
show()

正如你所看到的,重构的图像(来自潜在的表现)与输入的非常相似。这意味着由自动编码器学习的瓶颈表示(编码)是原始数据的良好表示,即使它不能被人理解。

我们将在一个表格数据集上重用这个想法,通过在潜在空间中得到它的表示来匿名化原始数据。

数据集

在这个实验中,我们将使用银行营销数据集。该数据是关于一家葡萄牙银行机构的直接电话营销活动(https://archive.ics.uci.edu/ml/datasets/Bank+Marketing)。机器学习的任务是分类,我们需要建立一个模型来预测客户是否会订阅定期存款。

我在这里介绍这个数据集中的变量的描述。你可以在网站上找到他们。

我们加载数据集并执行非常简单的处理。我删除了列“duration”,因为在执行调用之前它是未知的。

df = read_csv("data/bank-additional-full.csv", sep=";")
y = (df.y == "yes") + 0
del df["duration"]
del df["y"]
X = get_dummies(df)
array_columns = X.columns
min_max_scaler = MinMaxScaler()
X = min_max_scaler.fit_transform(X)

数据集概述:

基于原始数据的基准性能

在匿名化数据之前,我们可以尝试使用一个基本的随机森林进行交叉验证,以评估基线性能。需要指出的是,我们并不是要在这里找到最好的模型,我们关心的是在原始数据上训练的模型和在编码(匿名)数据上训练的模型之间的差异。

rf = RandomForestClassifier(
    n_estimators=500, 
    max_depth=2, 
    n_jobs=8, 
    random_state=42
)
dict_performance = cross_validate(
    estimator=rf, 
    X=X, y=y, 
    cv=10, 
    n_jobs=4,
    return_train_score=True,
    scoring=[
        "balanced_accuracy", 
        "f1_weighted", 
        "roc_auc", 
        "average_precision"
    ]
)
df_performance = DataFrame(
    {"ORIGINAL": [mean(dict_performance[k]) \
                  for k in dict_performance.keys()]}, 
    index=dict_performance.keys()
)

我们还可以绘制特征的重要性,以了解哪个特征影响目标变量。

rf = RandomForestClassifier(
    n_estimators=500, 
    max_depth=2, 
    n_jobs=8, 
    random_state=42
)
rf.fit(X, y)
fi = permutation_importance(
    estimator=rf, 
    X=X, 
    y=y, 
    n_repeats=10, 
    n_jobs=8, 
    random_state=42
).importances_mean

figure(figsize=(16,8))
barh(
    y=range(10, 0, -1), 
    width=sorted(fi, reverse=True)[:10], 
    alpha=0.9
)
ylabel("Feature")
yticks(
    range(10, 0, -1), 
    array_columns[fi.argsort()[::-1][:10]]
)
xlabel("Importance")
title("Original features importance")
show()

正如你所注意到的,重要的特征大多是与以前的竞选结果和总体经济情况有关的。

数据匿名化与自动编码器

现在,我们准备对数据集进行匿名化。首先,我们构建了一个瓶颈层只有输入层一半大小的自动编码器。

dim_layer_input = X.shape[1]
dim_layer_1 = max((int(3*dim_layer_input/4), 1))
dim_layer_2 = max((int(dim_layer_input/2), 1))
autoencoder, encoder = build_autoencoder(
    dim_input=dim_layer_input, 
    dim_layer_1=dim_layer_1, 
    dim_layer_2=dim_layer_2
)

训练

autoencoder.fit(
    X, X,
    epochs=100,
    batch_size=256,
    shuffle=True,
    validation_split=0.3,
    callbacks=callbacks
)

并提取编码表示作为随机森林分类器的输入

encoded = array(encoder(X))
rf = RandomForestClassifier(
    n_estimators=500, 
    max_depth=2, 
    n_jobs=8, 
    random_state=42
)
dict_performance = cross_validate(
    estimator=rf, 
    X=encoded, y=y, 
    cv=10, 
    n_jobs=4,
    return_train_score=True,
    scoring=[
        "balanced_accuracy", 
        "f1_weighted", 
        "roc_auc", 
        "average_precision"
    ]
)
df_performance["ENCODED"] = [
    mean(dict_performance[k]) \
    for k in dict_performance.keys()
]

结果还不错。然而我们还不能绘制出特征的重要性,因为潜在的表示是原始的线性组合。当然,我们可以从自动编码器中提取权重,然后返回去了解哪些输入特征会影响更重要的潜在特征,但这只有当自动编码器有一个简单的结构时才可行,就像我们的例子一样。在其他情况下,我们可以对特征进行组编码。

Group-encode特性匿名化

为了在匿名化的数据中保留某种业务知识,我们可以将原始特征按区域分组,然后对每一组应用自动编码器的匿名化。例如,在我们的例子中,我们可以将特性划分为:

个人信息,财务状况,之前的竞选结果,以及总体经济形势。

feat_pers = ['age', 'marital_divorced', 'marital_married', 'marital_single', 'marital_unknown', 'education_basic.4y', 'education_basic.6y', 'education_basic.9y', 'education_high.school', 'education_illiterate', 'education_professional.course', 'education_university.degree', 'education_unknown','job_admin.', 'job_blue-collar', 'job_entrepreneur', 'job_housemaid', 'job_management', 'job_retired', 'job_self-employed', 'job_services', 'job_student', 'job_technician', 'job_unemployed', 'job_unknown']

feat_fina = ['default_no', 'default_unknown', 'default_yes', 'housing_no', 'housing_unknown', 'housing_yes', 'loan_no', 'loan_unknown', 'loan_yes']

feat_camp = ['campaign', 'pdays', 'previous', 'contact_cellular', 'contact_telephone', 'month_apr', 'month_aug', 'month_dec', 'month_jul', 'month_jun', 'month_mar', 'month_may', 'month_nov', 'month_oct', 'month_sep', 'day_of_week_fri', 'day_of_week_mon', 'day_of_week_thu', 'day_of_week_tue', 'day_of_week_wed', 'poutcome_failure', 'poutcome_nonexistent', 'poutcome_success']

feat_econ = ['emp.var.rate', 'cons.price.idx', 'cons.conf.idx', 'euribor3m', 'nr.employed']

然后,我们应用一个独立的自动编码器来匿名化每一组特征。

feat_groups = [
    feat_pers, 
    feat_fina, 
    feat_camp, 
    feat_econ
]
encoded = []
for g in tqdm(feat_groups):
    
    dim_layer_input = len(g)
    dim_layer_1 = max((int(3*dim_layer_input/4), 1))
    dim_layer_2 = max((int(dim_layer_input / 2), 1))
    
    autoencoder, encoder = build_autoencoder(
        dim_input=dim_layer_input, 
        dim_layer_1=dim_layer_1, 
        dim_layer_2=dim_layer_2
    )
    
    X_tmp = X[:, array_columns.isin(g)]
    autoencoder.fit(
        X_tmp, X_tmp,
        epochs=100,
        batch_size=256,
        shuffle=True,
        validation_split=0.3,
        callbacks=callbacks, 
        verbose=0
    )
    
    encoded.append(array(encoder(X_tmp)))
X_encoded = hstack(encoded)

我们可以为每个匿名特征分配感兴趣的区域,因为我们之前已经对它们进行了分组。

array_encoded_features = array(
    ["pers_"+str(j) for j in range(encoded[0].shape[1])] + \
    ["fina_"+str(j) for j in range(encoded[1].shape[1])] + \
    ["camp_"+str(j) for j in range(encoded[2].shape[1])] + \
    ["econ_"+str(j) for j in range(encoded[3].shape[1])]
)

这样,匿名数据集看起来是这样的:

让我们测试一下匿名化给后的预测能力。

rf = RandomForestClassifier(n_estimators=500, max_depth=2, n_jobs=8, random_state=42)
dict_performance = cross_validate(
    estimator=rf, 
    X=X_encoded, y=y, 
    cv=10, 
    n_jobs=4,
    return_train_score=True,
    scoring=[
        "balanced_accuracy", 
        "f1_weighted", 
        "roc_auc", 
        "average_precision"
    ]
)
df_performance["GROUP_ENCODED"] = [mean(dict_performance[k]) for k in dict_performance.keys()]

在这种情况下,我们能够绘制特征的重要性,因为我们知道每个匿名特征是从哪个兴趣区域创建的。

rf = RandomForestClassifier(
    n_estimators=500, 
    max_depth=2, 
    n_jobs=8, 
    random_state=42
)
rf.fit(X_encoded, y)
fi = permutation_importance(
    estimator=rf, 
    X=X_encoded, 
    y=y, 
    n_repeats=10, 
    n_jobs=8, 
    random_state=42
).importances_mean

figure(figsize=(16,8))
barh(
    y=range(10, 0, -1), 
    width=sorted(fi, reverse=True)[:10], 
    alpha=0.9
)
ylabel("Feature")
yticks(
    range(10, 0, -1), 
    array_encoded_features[fi.argsort()[::-1][:10]]
)
xlabel("Importance")
title("Group-encoded features importance")
show()

跟我们的常识差不多,来自竞选和经济形势的编码特征是最重要的,这与对非匿名数据的分析是一致的。为了获得更多的细节,我们可以创建具有更细粒度划分的特性组。

总结

在本教程中,我们看到了如何应用自动编码器来匿名化数据集,以便将编码的数据传递给下游的机器学习任务。在数据应该传递到外部以在其他预测机器学习平台上进行测试的情况下,这可能非常有用(想象一下在云上测试模型)。一个受过良好训练的自动编码器保留了原始数据的预测能力。

作者:Shuyi Yang

deephub翻译组

原文链接:https://towardsdatascience.com/data-anonymization-with-autoencoders-75d076bcbea6

标签:

“使用自编码器进行数据的匿名化以保护数据隐私”的评论:

还没有评论