亚马逊AWS官方博客

使用 Amazon SageMaker 标记可疑的医疗保险索赔

Original URL:https://aws.amazon.com/blogs/machine-learning/flagging-suspicious-healthcare-claims-with-amazon-sagemaker/

美国国家医疗保险反欺诈协会 (NHCAA) 估算,医疗保险欺诈每年给美国造成大约 680 亿 USD 的损失,占国家医疗保险支出(2.26 万亿 USD)的 3%。这是一个保守的估算。有估算显示这项损失高达年度医疗保险支出的 10%,即 2300 亿 USD。

医疗保险欺诈必然会导致消费者保费和自付费用升高,利益受损或保险覆盖面缩小。

将索赔认定为欺诈可能需要复杂而详尽的调查。本文介绍了如何训练 Amazon SageMaker 模型来标记后付费医疗保险的住院异常索赔,并针对它们的欺诈嫌疑进行进一步调查。这个解决方案不需要标记数据;它使用非监督机器学习 (ML) 创建一个模型来定位可疑的索赔。

由于面临以下挑战,异常检测困难重重:

  • 正常和异常数据之间的区别往往并不明显。异常检测方法可能特定于应用场合。例如,在临床数据中,凭借一个小的偏差就可以判定为离群值;而在营销应用中,要判定为离群值则需要一个显著的偏差。
  • 数据中的噪声数据可能显示为属性值偏差或缺失值。噪声数据可能会掩盖离群值或将偏差标记为离群值。
  • 清晰合理地解释离群值可能会很困难。

此解决方案使用 Amazon SageMaker,它可以帮助开发人员和数据科学家构建、训练和部署 ML 模型。Amazon SageMaker 是一项完全托管的服务,涵盖了 ML 的整个工作流,可以标记和准备数据、选择算法、训练模型、调整和优化模型以便部署、预测和执行操作。

可以使用Amazon SageMaker Jupyter Notebook 端到端应用此解决方案。有关更多信息,请参阅 GitHub 存储库

解决方案概览

在此示例中,我们将使用 Amazon SageMaker 执行以下操作:(1) 使用 Jupyter Notebook 下载数据集并将其可视化;(2) 在 Jupyter Notebook 内进行本地数据清理并查看数据样本;(3) 使用 word2vec 对文本列执行特征工程;(4) 将主成分分析 (PCA) 模型拟合到预处理数据集;(5) 对整个数据集进行评分;(6) 对得分应用阈值,以识别任何可疑或异常的索赔。

使用 Jupyter Notebook 下载数据集并将其可视化

本文使用了 2008 年的医疗保险住院索赔数据集。该数据集名为 CMS 2008 BSA Inpatient Claims PUF,是可公开获取的基础版 (BSA) 住院公共使用文件 (PUF)。

本文的 Jupyter Notebook 说明了如何下载数据集。有关更多信息,请参阅 GitHub 存储库

数据集包含一个作为记录索引的索赔主键和六个分析变量。另外还有一些基础属性变量以及与索赔相关的变量。但是,由于文件没有提供受益人id,因此您无法关联来自同一受益人的索赔。不过,数据集包含了为此解决方案构建模型所需的充足信息

就特征而言,这是最小的数据集。数据集未提供所需的部分特征(例如治疗机构的邮政编码)。您可以添加更多数据来构建一组特征,从而不断提高此解决方案的准确性。

您可以下载数据集的副本,也可以通过 GitHub 存储库访问该数据集。

接下来将分析七个分析变量、通过修复空值清除每个变量中的数据,并用其对应的描述替换 ICD 9 诊断和治疗代码。

清除列名

请通过以下步骤清除列名。

  1. 打开文件 ColumnNames.csv
  2. 去掉所有空格和双引号

这会生成已编码列的相关名称,然后就可以开始处理数据集了。请参阅以下代码示例:

colnames = pd.read_csv("./data/ColumnNames.csv")
colnames[colnames.columns[-1]] = colnames[colnames.columns[-1]].map(lambda x: x.replace('"','').strip())
display(colnames)

下表显示了本数据集中本项目所使用到的列名。

列标签 列名
0 IP_CLM_ID  Encrypted PUF ID
1 BENE_SEX_IDENT_CD  Beneficiary gender code
2 BENE_AGE_CAT_CD  Beneficiary Age category code
3 IP_CLM_BASE_DRG_CD  Base DRG code
4 IP_CLM_ICD9_PRCDR_CD  ICD9 primary procedure code
5 IP_CLM_DAYS_CD  Inpatient days code
6 IP_DRG_QUINT_PMT_AVG  DRG quintile average payment amount
7 IP_DRG_QUINT_PMT_CD  DRG quintile payment amount code

以下是所使用的数据集的特征。

  • 2008 年的医疗保险住院索赔
  • 每条记录都是由医疗保险受益人(5% 的样本)提出的住院索赔。
  • 未提供受益人身份
  • 未提供患者治疗机构的邮政编码
  • 该文件包含8个变量。一个主键和 7 个分析变量
  • 提供了解释数据集中的代码所需的数据字典

可视化数据集

从以下屏幕截图可以明显看出,通过目测难以分辨出异常和非异常记录。即使采用统计技术,也没那么容易。这是因为面临着以下挑战。

  • 有效地对正常对象和异常对象进行建模。数据正常与异常(离群值)之间的界限通常并不明确。
  • 离群值检测方法特定于应用场合。例如,在临床数据中,凭借小的偏差就可以判定为离群值;而在营销应用中,要判定为离群值则需要较大的偏差。
  • 数据中的噪声数据可能显示为属性值偏差,甚至是缺失值。噪声数据可能会掩盖离群值或将偏差标记为离群值。
  • 从可解释的观点合理地解释离群值可能会很困难。

以下屏幕截图显示了数据集中的示例记录:

在 Jupyter Notebook 内进行本地数据清理并查看数据样本

在数据集上生成列统计信息。

以下命令可识别带有空值的列:

# check null value for each column
display(df_cms_claims_data.isnull().mean())

您会在结果中看到一些“NaN”,以及 ICD9 主过程代码的均值 (0.469985)。“NaN”表示“非数字”– 如果您执行计算,但无法将计算结果表示为数字,则会获得此浮点值。这表明需要为 ICD9 主过程代码修复空值。

替换 ICD9 诊断代码

要替换空值,请执行以下代码并将类型从 float 更改为 int64。数据集将所有过程代码作为整数编码。

#Fill NaN with -1 for "No Procedure Performed"
procedue_na = -1
df_cms_claims_data['ICD9 primary procedure code'].fillna(procedue_na, inplace = True)

#convert procedure code from float to int64
df_cms_claims_data['ICD9 primary procedure code'] = df_cms_claims_data['ICD9 primary procedure code'].astype(np.int64)

分析性别和年龄数据

接下来对性别和年龄进行分布是否平衡分析。执行以下过程,以绘制各个性别和年龄字段的条形图。

  1. 读取性别/年龄字典 csv 文件
  2. 将受益人类别代码与年龄组/性别定义联接起来,并描述索赔数据集中不同年龄组的分布
  3. 将数据集中的性别/年龄分布投影到条形图上

以下屏幕截图显示了年龄组分布的条形图。您可以看到,索赔分布略有失衡,Under_6585_and_Older 的索赔比例更高。由于这两个类别代表的年龄组是开放式的,涉及的范围更广,因此您可以忽略这一失衡。

以下屏幕截图显示了性别条形图,其中同样略有失衡。女性的索赔比例略高一些。但是,由于只是略有失衡,因此可以忽略。

分析天数、付款代码和付款金额数据

在此阶段,您无需对住院天数代码、DRG 五分位数付款代码和 DRG 五分位数付款金额的数据进行任何转换。数据会被清晰地编码,而且任何失衡的数据都可能暗含模型用以捕获异常的信号,因此无需进行进一步的失衡分析。

使用 word2vec 对文本列执行特征工程

数据集中共有七个分析变量。在 7 个变量中,我们直接使用患者年龄、患者性别、住院天数、DRG 五分位数付款代码和 DRG 五分位数付款金额作为特征,而无需进一步进行任何转换。不需要对这些字段执行特征工程。这些字段被编码为整数,可以安全地应用数学运算。

但是,您仍然需要从诊断和过程描述中提取相关特征。诊断和过程字段被编码为整数,但是对编码值进行数学运算所产生的结果会歪曲其含义。例如,两个过程代码或诊断代码的平均值可能导致用于第三个过程/诊断的代码与用于计算平均值的两个过程/诊断代码完全不同。本文讨论的技术以更有意义的方式对数据集中的过程和诊断描述字段进行编码。该技术使用连续词袋模型 (CBOW),这是一种对词嵌入技术的特定 word2vec 实施。

词嵌入技术是将词转换为数字。将文本转换为数字的方法多种多样,例如频率计数和独热编码。大多数传统方法会生成一个稀疏矩阵,在语境中和计算上效率较低。

Word2vec 是一个浅层神经网络,可将词映射到同样是词的目标变量。在训练过程中,神经网络学习充当词矢量表示的权重。

该 CBOW 模型在给定的语境(可以是句子等)中预测一个词。word2vec 学习的词的稠密矢量表示带有语义 。

对诊断和过程描述进行文本预处理

以下代码对诊断描述执行文本处理,以加强某些首字母缩略词对于词嵌入的意义。

  1. 更改为小写
    1. ‘&’ 替换为 ‘and’、
    2. ‘non-’ 替换为 ‘non’
    3. ‘w/o’ 替换为 ’without’
    4. ‘w’ 替换为 ‘with’
    5. ‘maj’ 替换为 ‘major’
    6. ‘proc’ 替换为 ‘procedure’
    7. ‘o.r.’ 替换为 ‘operating room’
  2. 将短语拆分为词
  3. 返回词矢量
# function to run pre processing on diagnosis descriptions
from nltk.tokenize import sent_tokenize, word_tokenize 

def text_preprocessing(phrase):
    phrase = phrase.lower()
    phrase = phrase.replace('&', 'and')
    #phrase = phrase.replace('non-', 'non') #This is to ensure non-critical, doesn't get handled as {'non', 'critical'}
    phrase = phrase.replace(',','')
    phrase = phrase.replace('w/o','without').replace(' w ',' with ').replace('/',' ')
    phrase = phrase.replace(' maj ',' major ')
    phrase = phrase.replace(' proc ', ' procedure ')
    phrase = phrase.replace('o.r.', 'operating room')
    sentence = phrase.split(' ')
    return sentence

在标记和预处理诊断描述之后,将输出传入到 word2vec 以生成词嵌入。

为单个词生成词嵌入

要在预处理过程和诊断描述中为单个词生成词嵌入,请完成以下步骤:

  1. 训练 word2vec 模型,将预处理的过程和诊断描述转换为特征,并使用名为 sns 的 Python 可视化库在 2D 空间中可视化结果。
  2. 使用 CBOW 从预处理的诊断和过程代码描述中提取特征矢量。
  3. 在 Amazon SageMaker Jupyter Notebook 实例上针对诊断和过程描述从本地训练 word2vec 模型。
  4. 使用该模型为过程和诊断描述中的每个词提取固定长度的词矢量。

本文使用 word2vec(可通过 gensim 软件包获取)。有关更多信息,请参阅 Python Package Index 网站上的 genism 3.0.0。完成以上步骤后,最终每个词的矢量包含 72 个浮点数。在诊断和过程描述中,将它用作标记词的特征矢量。

从过程和诊断描述短语生成词嵌入

在获得每个词的词矢量后,可以生成新的词嵌入。

  1. 使用过程和诊断描述中所有词矢量的均值,为描述诊断和过程的每个完整短语构建新的矢量。

新的矢量将成为数据集中诊断和过程描述字段的特征集。请参阅以下代码示例:

# traing wordtovec model on diagnosis description tokens
model_drg = Word2Vec(tmp_diagnosis_tokenized, min_count = 1, size = 72, window = 5, iter = 30)
  1. 获取短语中所有词矢量的平均值。

这将为完整的诊断描述短语生成词嵌入。请参阅以下代码示例:

#iterate through list of strings in each diagnosis phrase
for i, v in pd.Series(tmp_diagnosis_tokenized).items():
    #calculate mean of all word embeddings in each diagnosis phrase
    values.append(model_drg[v].mean(axis =0))
    index.append(i)
tmp_diagnosis_phrase_vector = pd.DataFrame({'Base DRG code':index, 'DRG_VECTOR':values})
  1. 将诊断描述矢量扩展为特征。请参阅以下代码示例:
# expand tmp_diagnosis_phrase_vector into dataframe
# every scalar value in phrase vector will be considered a feature
diagnosis_features = tmp_diagnosis_phrase_vector['DRG_VECTOR'].apply(pd.Series)

# rename each variable in diagnosis_features use DRG_F as prefix
diagnosis_features = diagnosis_features.rename(columns = lambda x : 'DRG_F' + str(x + 1))

# view the diagnosis_features dataframe
display(diagnosis_features.head())

以下屏幕截图显示了生成的词嵌入。但是,它们是抽象的,对可视化没有帮助。

  1. 对过程代码重复上面对诊断代码执行的过程。

最终,您会获得过程描述的特征集。见以下截图。

可视化诊断和过程描述矢量

本文使用一项称之为 t-SNE 的技术以 2D 或 3D 形式可视化词嵌入的结果(多维空间)。以下屏幕截图显示了 t-SNE 图,它绘制了 word2vec 算法生成的词矢量的 2D 投影。

即使用于训练模型的参数相同,word2vec 和 t-SNE 图也不一定相同。这是因为在开始每个新训练会话时都进行了随机初始化。

t-SNE 图不存在理想的形状。但是,请避免使用以下模式,即所有词都出现在一个聚类中,而且彼此非常接近。下图效果不错。

为过程描述重复上述过程。以下屏幕截图显示了在处理和应用 word2vec 后的 2D 投影。同样地,此图效果不错。

汇聚所有特征集并组成最终的训练特征集

接下来,汇聚从六个分析变量中提取的所有特征,并组成最终的特征集。您可以使用适用于数据科学的标准 Python 库。

将主成分分析 (PCA) 模型拟合到预处理的数据集

下一步演示如何使用 PCA 进行异常检测。我使用 A Novel Anomaly Detection Scheme Based on Principal Component Classifier(基于主成分分类器的新型异常检测方案)中描述的技术来演示基于 PCA 的异常检测方法。

将数据拆分为训练用数据和测试用数据

在使用 PCA 进行异常检测之前,您需要将数据拆分为训练用数据和测试用数据。确保此随机拆分的样本可以覆盖所有规模的付款分布。本文对 DRG 五分位数付款金额代码执行分层混洗拆分,将 30% 的数据用于测试,70% 的数据用于训练。请参阅以下代码示例:

from sklearn.model_selection import StratifiedShuffleSplit

sss = StratifiedShuffleSplit(n_splits=1, test_size=0.3, random_state=0)
splits = sss.split(X, strata)
for train_index, test_index in splits:
    X_train, X_test = X.iloc[train_index], X.iloc[test_index]

下一步是对数据进行标准化,以避免被高尺度变量主导。

根据训练样本标准化数据

由于您随后用于训练的 PCA 算法会最大化数据中的正交方差,因此请在执行 PCA 之前将训练数据标准化,让其具有零均值和单位方差。通过这样做,您可以确保 PCA 算法与这种尺度变换是幂等的,并防止高尺度变量主导 PCA 投影。请参阅以下代码示例:

from sklearn.preprocessing import StandardScaler
n_obs, n_features = X_train.shape
scaler = StandardScaler()
scaler.fit(X_train)
X_stndrd_train = scaler.transform(X_train)

现在,您已经完成从数据集中提取特征并将其标准化。您可以使用 Amazon SageMaker PCA 进行异常检测。我使用 Amazon SageMaker PCA 减少变量数,并确保您的变量彼此独立。

Amazon SageMaker PCA 是一种非监督式 ML 算法,可减少数据集中的维数(特征数量),同时仍保留尽可能多的信息。它通过查找一组称为成分的新特征来这样做,这些成分是由彼此不相关的原始特征组合而成的。它们还受到约束,以便第一个成分解释数据中最大的潜在可变性,第二个成分解释第二大可变性,以此类推。

利用 Amazon SageMaker PCA 基于数据训练的输出模型通过计算每个变量如何彼此关联(协方差矩阵)、数据分散的方向(特征向量)以及这些不同方向的相对重要性(特征值)完成。

将数据转换为二进制数据流并上传到 Amazon S3

启动 Amazon SageMaker 训练作业之前,先将数据转换为二进制数据流并上传到 Amazon S3。请参阅以下代码示例:

# Convert data to binary stream.
matrx_train = X_stndrd_train.as_matrix().astype('float32')
import io
import sagemaker.amazon.common as smac
buf_train = io.BytesIO()
smac.write_numpy_to_dense_tensor(buf_train, matrx_train)
buf_train.seek(0)

调用 Amazon SageMaker fit 函数以启动训练作业

下一步是调用 Amazon SageMaker fit 函数以启动训练作业。请参阅以下代码示例:

#Initiate an Amazon SageMaker Session
sess = sagemaker.Session()
#Create an Amazon SageMaker Estimator for Amazon SageMaker PCA.
#Container parameter has the image of Amazon SageMaker PCA algorithm #embedded in it.
pca = sagemaker.estimator.Estimator(container,
                                    role,
                                    train_instance_count=num_instances,
                                    train_instance_type=instance_type,
                                    output_path=output_location,
                                    sagemaker_session=sess)
#Specify hyperparameter
pca.set_hyperparameters(feature_dim=feature_dim,
                        num_components=num_components,
                        subtract_mean=False,
                        algorithm_mode='regular',
                        mini_batch_size=200)

#Start training by calling fit function
pca.fit({'train': s3_train_data})

调用 pca.fit 函数将触发单独训练实例的创建。这可让您选择不同的实例类型来进行训练以及构建和测试。

对整个数据集进行评分

下载并解压经过训练的 PCA 模型

完成训练作业后,Amazon SageMaker 将模型构件写入指定的 S3 输出位置。您可以下载并解压返回的 PCA 模型构件,以降低维数。

Amazon SageMaker PCA 构件包含 ?、特征向量主成分(按 ? 的升序排列)和它们的特征值。成分的特征值等于成分解释的标准偏差。例如,单一成分的特征值平方等于该成分解释的方差。因此,要计算每个成分解释的数据的方差比例,请求出特征的平方,然后除以所有特征值平方的总和。

如果希望解释最多方差的成分首先出现,请颠倒此返回的顺序。

绘制 PCA 成分图以进一步降低维数

您可以使用 PCA 来降低问题的维数。您具有 ? 个特征和 ?−1 个成分,但是在下图中,您可以看到许多成分对解释数据的方差用处不大。仅保留 ? 个主要成分,这些成分可以解释 95% 的数据方差。

十三个成分解释了 95.08% 的数据方差。下图中的红色虚线重点指示了 95% 的数据方差所需的截断值。

计算马氏距离以对每个索赔进行异常评分

本文使用每个点的马氏距离作为其异常得分。将这些点中最高的 ?% 视为离群值,其中 ? 取决于您所需的检测敏感度。本文取最高的 1%,即 ?=0.01。因此,计算分布 ? 的 (1−?) 分位数,将其作为判定数据点异常的阈值。

下图是根据从特征集中得出的马氏距离生成的,该特征集是 Amazon SageMaker PCA 算法的输出。红线根据 ? 定义的敏感度描述异常检测的阈值。

 

您可以使用通过马氏距离和敏感度得出的异常分数,将索赔标记为 “is anomaly” TRUE/FALSE。具有 “anomalous” TRUE 的记录会达到异常阈值,应被视为可疑。“anomalous” FALSE 记录不会达到阈值,因此不被视为可疑。这将异常索赔与标准索赔分隔开来。

对得分应用阈值以识别任何可疑或异常的索赔

绘制异常记录图并进行分析

您可以按照在 CMS 索赔数据集上执行的操作顺序,单纯地使用数学技术(无需使用未标记的数据)来标记异常的索赔记录。

以下屏幕截图显示了标准记录示例。

以下屏幕截图显示了异常记录示例。

既然已将标准数据与异常数据分隔开来,现在您可以把任何将 “anomalous” 标记为 TRUE 的数据点视为可疑数据,并对它们进行深入调查。

经过专家调查,可以确认索赔是否确实存在异常。如果您对此感兴趣并希望提出自己的解释、假设或模式,则可以在不同变量(例如年龄、性别、住院天数、五分位数代码、五分位数付款方式、过程和诊断代码)之间绘制两两特征图。

对于基本分析,您可以使用 seaborn 库绘制两两特征图。以下屏幕截图显示了一幅图形中的两两特征图,标准索赔为蓝色,异常索赔为橙色,它们相互重叠。您可以看到,橙色点或者与蓝色点不对称,或者孤立存在(附近没有蓝色点)。

以红色突出显示的两两特征图显示了不对称图案。蓝色和橙色之间是一些孤立的区域,其中有橙色点,但没有蓝色点。您可以更深入地研究这些图形和分析突出显示的图形背后的数据,以找到模式或提出假设。由于本文没有提供标签数据,因此很难检验假设。然而,随着时间的推移,您的标签数据可能会不断增多,它们可用来检验您的假设和提高模型的准确性。

 

小结

本文演示了如何构建模型以标记可疑的索赔。在构建支持支付完整性的流程时,您可以使用该模型作为起点。您可以从现有源引入更多数据或添加更多数据源,以进一步扩展该模型。本文中的模型可以扩展并可以吸收更多数据,以改善结果和性能。

使用此模型有助于最大程度地减少欺诈案件。由于害怕被标记,虚报的索赔将得到遏止,用户的医保费用也会降低。如果您想试用本文介绍的这种技术,请使用您自己的 Amazon SageMaker Jupyter Notebook。GitHub 存储库提供了相关说明和构件。


关于作者

Vikrant Kahlir,AWS 解决方案架构部战略解决方案架构师

Elena Ehrlich,AWS 专业服务部高级数据科学家

Hanif Mahboobi,AWS 专业服务部高级数据科学家