去除灰尘:卷积神经网络和迁移学习如何检测太阳能板上的灰尘
借助卷积神经网络和迁移学习,可以建立一个分类器来判断太阳能板是否干净或有灰尘
·
关注 发表在 Towards Data Science ·15 分钟阅读·2023 年 3 月 15 日
–
图片由 Moritz Kindler 提供,来源于 Unsplash
太阳能面板已成为各种行业中一种流行的可再生能源来源,从农业和交通到建筑和酒店业。通过利用太阳能,我们可以在不损害环境的情况下生成电力。然而,使用太阳能面板也面临挑战,其中之一就是尘埃在其表面上的积累。这会显著降低它们的效率,并限制它们在能源生产和其他应用中的有效性。
为了解决这个问题,自动化可以在确保太阳能面板定期和及时维护方面发挥关键作用。通过自动化清洁过程,我们可以提高生产力和效率,同时减少能源生成的环境影响。总体而言,太阳能面板的潜在好处广泛而多样化,借助自动化,我们可以克服与其使用相关的挑战,并继续推动这一令人兴奋且快速发展的领域的进步。
借助深度学习和强大的计算资源,可以在太阳能面板上积累尘埃时提醒相关部门。**卷积神经网络(CNNs)**以其图像识别能力而闻名。迁移学习是一种利用预训练权重处理复杂任务的方法,适用于我们的太阳能面板尘埃检测任务。因此,可以利用这些方法提高深度学习模型的准确性和 f1-score。
我们将在本文中实施一个关于构建太阳能面板尘埃检测分类器的项目。测试了大量的神经网络配置,最终确定了最佳架构以实时部署,帮助检测太阳能面板上的尘埃。
阅读库
我们将查看一份用于构建太阳能面板尘埃检测分类器的库列表。
在构建深度学习应用时,我们有丰富的库可供使用,包括 TensorFlow、NumPy、Pandas 和 OS。虽然一开始可能会觉得不知所措,但理解如何在代码中使用这些库可以大大简化开发过程,并使我们的模型更有效。
通过利用这些强大的工具,我们可以简化数据处理、特征工程、模型训练和部署。掌握这些库及其能力后,我们可以更轻松高效地构建更复杂、更准确的模型。
在本文中,我们将广泛地使用这些库来构建我们的太阳能面板尘埃检测分类器。通过实际示例和逐步的说明,你将学会如何利用这些工具的强大功能,并将其应用于现实世界的问题。
阅读数据
要开始构建我们的太阳能电池板灰尘检测分类器,第一步是从预定义路径中加载图像到本地计算机。然而,这些图像的确切位置可能会因用户的计算机配置而有所不同。
为了执行此加载操作,我们定义了一个单独的函数,该函数从指定路径中提取图像,同时丢弃任何低分辨率的图像。这确保了我们的数据集仅包含适合训练我们深度学习模型的高质量图像。
注意: 数据集来自于太阳能电池板灰尘检测 | Kaggle,使用的是知识共享 — CC0 1.0 通用许可
我们将干净的和有灰尘的太阳能电池板存储为一组数组,用于计算。请注意,由于我们处理的是一个小数据集,因此没有诸如内存溢出错误等问题。如果处理大数据集,建议使用 ImageDataGenerator,因为它会以批次的方式从磁盘中加载数据。
探索性数据分析(EDA)
这是机器学习生命周期中的一个重要部分,在这一阶段,我们检查 ML 模型使用的数据集,以查看数据中是否存在差异和异常值。通过这种方式,可以采取特征工程步骤来去除这些数据点,帮助建立一个强大的分类器。
太阳能电池板图像(图片来源:作者)
上面是我们将用于分类器的一组图像,以确定面板是干净的还是有灰尘的。需要注意的是,有些图像包含文本,还有其他图像包含白色背景或裁剪不当。因此,在特征工程阶段,会采取步骤去除这些图像,因为它们可能会干扰我们的分类器做出准确的预测。
特征工程
为了确保仅使用高质量图像进行训练,我们采取措施丢弃数据集中具有白色背景的图像。这是通过实现以下代码来完成的,该代码识别并移除任何具有主要白色背景的图像。通过这样做,我们可以提高模型训练过程的整体准确性和可靠性。
白色背景的太阳能电池板(图片来源:作者)
从输出结果来看,白色背景的图像被准确识别。然而,数据中存在一些假阳性。但我们可以继续使用这种方法收集没有白色背景的图像。
模型训练
让我们看一下所有可能用于训练太阳能电池板尘埃分类器的模型列表。初始配置是一个具有不错层数的卷积神经网络。有卷积层、最大池化层和扁平化层等层。以下是代码实现。
配置 1
模型性能,模型架构和分类报告(图片作者)
有一个函数被设计用来绘制所有指标的列表,并通过这些指标使我们对模型的性能有一个良好的理解。这里有分类报告、混淆矩阵和其他图表等信息,这些信息有助于指导我们确定要用于生产的最佳模型。
随着时代数量的增加,准确性提高,错误减少。此外,需要注意的是,交叉验证错误也会随着额外的训练而减少。这意味着仍然有更多的空间进行进一步的训练。必须小心,不要让模型过拟合训练数据。我们还可以查看其他配置,以确定要在实时中部署的最佳模型。
配置 2
模型性能,模型架构和分类报告(图片作者)
新的配置如上面的代码所示。对数据集的性能进行了指标追踪。由于训练准确度有很大提高,而交叉验证准确度要么下降要么保持稳定,因此此配置倾向于过拟合数据。损失曲线也反映了这一点,随着时代数量的增加,训练损失减少而交叉验证损失增加。因此,该模型在测试集上没有太多改善的情况下过拟合训练数据。
配置 3
模型性能,模型架构和分类报告(图片作者)
此配置的行为与先前的配置类似,在过拟合方面存在问题。但是,这些曲线显示,与先前的配置相比,该模型在训练数据上的过拟合不是太严重。模型在测试数据上的准确率约为68%,正类别的精确度也很低。因此,可以使用其他配置和迁移学习方法来大幅提高模型的性能。
配置 4
模型性能、模型架构和分类报告(图片来源:作者)
该模型的性能与之前测试的两种配置相似。在训练数据上存在过拟合现象,如训练和损失曲线所示。定义自定义 CNN 配置未能达到预期效果,尤其是导致了较低的准确率和正类的较低 F1 分数。可以使用复杂度更高的额外模型,因为它们应该能够发现数据中的潜在模式并做出良好的预测。
转移学习模型
我们可以继续查看一系列转移学习模型,并确定在测试集上的表现。这些模型在包含大量样本的ImageNet数据上进行了预训练。我们提取这些网络的权重用于我们的太阳能板尘埃检测任务,并重新训练最后几层以节省计算。
VGG 16
模型性能、模型架构和分类报告(图片来源:作者)
该架构在测试数据上的准确率约为70 percent,表现良好。然而,在交叉验证数据上存在一定的过拟合现象。因此,随着训练轮数(遍历整个数据集的迭代次数)的增加,准确率会有所下降。让我们还考虑其他架构的列表,并确定最佳的模型进行部署。
VGG 19
模型性能、模型架构和分类报告(图片来源:作者)
相较于 VGG 16,VGG 19 网络的表现较差。这是因为前者网络更复杂,导致过拟合的可能性更高。当我们探索 VGG 16 网络时,它也容易出现过拟合。通过增加网络的复杂性,模型过拟合训练数据的可能性会更高。
InceptionNet
模型性能、模型架构和分类报告(作者提供的图片)
InceptionNet的架构如上所示,相当复杂,具有较大的深度和许多隐藏单元。由于网络已经在“ImageNet”上进行过训练,我们可以从中提取有用的权重,只考虑训练最后几层以加快过程。总体来说,InceptionNet 在测试数据上的表现最好,准确率约为77 percent。
MobileNet
模型性能、模型架构和分类报告(作者提供的图片)
MobileNet 在测试数据上表现出色,总体准确率约为79 percent。准确率曲线和损失曲线也表明,该模型经过良好的训练,不仅在训练集上,甚至在交叉验证数据上性能也有所提升。此外,该模型可以进一步训练,并通过超参数调整来提高测试数据(未见数据)的性能和泛化能力。请注意推理所需的计算复杂性。这表明它在较小的配置下也能很好地泛化,并且能够提供良好的性能。
Xception Network
模型性能、模型架构和分类报告(作者提供的图片)
Xception 架构也如上所示,非常复杂。最后几层被修改以确保它们可以用于太阳能电池板尘埃检测任务。它在测试数据上的表现良好,准确率约为71 percent。然而,随着训练轮次的增加,训练准确率和交叉验证准确率之间的差距在扩大,显示出过拟合的趋势。MobileNet 在所有模型中表现最好。但我们还应探索潜在的模型列表,以确定最佳的模型进行预测。
MobileNetV2
模型性能、模型架构和分类报告(作者提供的图像)
上面的图展示了 MobileNetV2 架构在图像识别任务中的表现,任务是预测太阳能板是否干净或有灰尘。这个架构在某种程度上较为复杂。在最后几层中,添加了额外的层和单元,以优化我们的任务的权重。模型的整体表现不如之前提到的初始 MobileNet 模型。此外,这个架构更复杂,需要良好的计算能力以确保低延迟应用。因此,我们可以使用 MobileNet 作为实时预测部署的最佳模型之一。
ResNet 50
模型性能、模型架构和分类报告(作者提供的图像)
在查看如准确率曲线和损失曲线等图时,ResNet 架构中存在很多随机性。总体来看,交叉验证数据的准确率有上升的趋势。然而,模型未能捕捉训练数据中的重要区别,从而在测试数据上做出良好预测。结果是,它在测试集上的表现不佳。可以通过进一步训练来改善性能。考虑到计算复杂性,我们可以在进行超参数优化后使用 MobileNet 架构进行部署。ResNet 在其他与图像相关的任务中可能表现良好,但对于这个任务,MobileNet 表现最佳。
超参数调整
在计算机视觉中,这一步很重要,其中选取最佳模型并调整超参数,以确定模型性能的变化。这可以大大提高模型性能。现在让我们专注于从最佳模型中调整几个超参数。学习率和批量大小是一些可以提高模型性能的超参数。我们将使用这些超参数来提升性能。由于 MobileNet 在测试数据上的表现最佳,我们使用该模型并进行超参数调整,以获得最佳可实现结果。
学习率
模型性能、模型架构和分类报告(作者提供的图像)
在进行超参数调优并确定最佳学习率后,我们选择的模型(MobileNet)在测试数据集上的性能提高了显著的1%。值得注意的是,我们保持了之前相同的架构,同时专注于识别最佳学习率以实现最佳结果。
虽然我们不会深入探讨超参数调优的具体细节,但值得注意的是,还有另一个关键超参数可以探索,以最大化对未见数据点的性能。通过考虑这个额外的超参数,我们可以确保我们的模型在预测训练数据集之外的结果时更加有效。
批量大小
模型性能、模型架构和分类报告(图片来源:作者)
在我们成功进行超参数调优后,我们利用了最佳学习率来确定深度学习模型的最佳批量大小。在这种情况下,批量大小为128带来了最大的性能提升,使测试数据集的表现提高了显著的2%。这进一步强调了超参数调优的重要性,它可以成为提升深度学习模型准确性和可靠性的强大工具。
展望未来,我们的下一步是保存最终的超参数调优模型,并在实时环境中部署它,例如在相机模块或网页接口中,用户可以上传太阳能面板的图像。通过利用深度学习的力量,我们的模型可以准确识别面板是干净还是有灰尘,为用户提供有价值的见解。这个项目强调了超参数调优在各种问题和应用中提升性能的潜力。
保存最佳模型
现在我们已经投入精力开发、训练和测试一系列复杂的深度学习模型,是时候保存表现最佳的模型以备未来使用。我们通过以便于后续检索的方式存储模型,支持实时或批量推断,以满足开发者的具体需求。
通过保存最佳模型,我们可以确保优化模型性能的努力不会白费,并且我们的辛勤工作能以准确、可靠的结果获得回报。这是深度学习过程中的一个重要步骤,突显了这些技术在推动各种应用和领域改进方面的力量。
结论
通过阅读这篇文章,你现在应该对机器学习项目中涉及的各个阶段有了全面的理解,包括数据收集、特征工程、模型训练、模型选择、超参数调整和模型部署。这些步骤每一个都对项目的成功至关重要,需要仔细的关注和考虑,以实现最佳结果。
然而,模型部署后工作并未结束。重要的是要持续监控其性能,特别是在处理实时数据时。这使你能够识别潜在的问题,如模型漂移、数据漂移或安全问题,并采取措施及时解决这些问题。
总的来说,这篇文章提供了对深度学习过程的宝贵概述,突出了在构建各种应用的准确可靠模型时涉及的众多挑战和机会。希望你觉得这篇文章信息丰富且有帮助,我期待未来继续探索这个激动人心且快速发展的领域。感谢你抽出时间阅读这篇文章。
这里是项目完整工作代码的 GitHub 仓库链接。
Link: https://tinyurl.com/ycyybf55
以下是你可以联系我或查看我工作的方式。
GitHub:suhasmaddali (Suhas Maddali ) (github.com)
YouTube:https://www.youtube.com/channel/UCymdyoyJBC_i7QVfbrIs-4Q
LinkedIn:(1) Suhas Maddali, Northeastern University, Data Science | LinkedIn
Medium: Suhas Maddali — Medium
Kaggle:Suhas Maddali | 贡献者 | Kaggle
临床试验结果预测
第二部分:使用 XGBoost 预测临床试验结果
·
关注 发表在 Towards Data Science · 5 分钟阅读 · 2023 年 10 月 4 日
–
在本系列的第一部分中,我重点讨论了如何嵌入从ClinicalTrials.gov获取的多模态现实世界数据。在这篇文章中,我将实现一个基本的 XGBoost 模型,用我们在第一部分中创建的嵌入进行训练,并将其性能与 HINT 模型(一个分层图神经网络)的性能进行比较,HINT 模型是本项目的灵感来源。
工作流程示意图(图像由作者提供)
这是我在本文中将遵循的步骤:
-
加载训练、验证和测试数据集
-
嵌入药物分子、纳入/排除标准、疾病指示、试验赞助商和参与者人数
-
定义评估指标
-
训练 XGBoost 模型并简要比较与 HINT 模型性能
本系列第二部分的重点:基于在第一部分中创建的特征嵌入预测临床试验结果(图片由作者提供)
你可以按照这个 Jupyter notebook 中的所有步骤操作:临床试验嵌入教程。
加载训练、验证和测试数据集
import os
import pandas as pd
import numpy as np
import pickle
# Import toy dataset
toy_df = pd.read_pickle('data/toy_df_full.pkl')
train_df = toy_df[toy_df['split'] == 'train']
val_df = toy_df[toy_df['split'] == 'valid']
test_df = toy_df[toy_df['split'] == 'test']
y_train = train_df['label']
y_val = val_df['label']
y_test = test_df['label']
print(train_df.shape, val_df.shape, test_df.shape)
print(y_train.shape, y_val.shape, y_test.shape)
### Output:
# (1028, 14) (146, 14) (295, 14)
# (1028,) (146,) (295,)
嵌入药物分子、方案、指示和试验赞助商
在本节中,我们加载了在第一部分中创建的字典,并使用它们将训练、验证和测试集中的值映射到相应的嵌入中。
def embed_all(df):
print('input shape: ', df.shape)
### EMBEDDING MOLECULES ###
print('embedding drug molecules..')
nctid2molecule_embedding_dict = load_nctid2molecule_embedding_dict()
h_m = np.stack(df['nctid'].map(nctid2molecule_embedding_dict))
print(f"drug molecules successfully embedded into {h_m.shape} dimensions")
### EMBEDDING PROTOCOLS ###
print('embedding protocols..')
nctid2protocol_embedding_dict = load_nctid2protocol_embedding_dict()
h_p = np.stack(df['nctid'].map(nctid2protocol_embedding_dict))
print(f"protocols successfully embedded into {h_p.shape} dimensions")
### EMBEDDING DISEASE INDICATIONS ###
print('embedding disease indications..')
nctid2disease_embedding_dict = load_nctid2disease_embedding_dict()
h_d = np.stack(df['nctid'].map(nctid2disease_embedding_dict))
print(f"disease indications successfully embedded into {h_d.shape} dimensions")
### EMBEDDING TRIAL SPONSORS ###
print('embedding sponsors..')
sponsor2embedding_dict = load_sponsor2embedding_dict()
h_s = np.stack(df['lead_sponsor'].map(sponsor2embedding_dict))
print(f"sponsors successfully embedded into {h_s.shape} dimensions")
### EMBEDDING ENROLLMENT ###
print('normalizing enrollment numbers..')
enrollment = pd.to_numeric(df['enrollment'] , errors='coerce')
if enrollment.isna().sum() != 0:
print(f"filling {enrollment.isna().sum()} NaNs with median value")
enrollment.fillna(int(enrollment.median()), inplace=True)
print(f"succesfully filled NaNs with median value: {enrollment.isna().sum()} NaNs left")
enrollment = enrollment.astype(int)
h_e = np.array((enrollment - enrollment.mean())/enrollment.std()).reshape(len(df),-1)
print(f"enrollment successfully embedded into {h_e.shape} dimensions")
### COMBINE ALL EMBEDDINGS ###
embedded_df = pd.DataFrame(data=np.column_stack((h_m, h_p, h_d, h_s, h_e)))
print('output shape: ', embedded_df.shape)
return embedded_df
# Embed data
X_train = embed_all(train_df)
X_val = embed_all(val_df)
X_test = embed_all(test_df)
定义评估指标
我们将使用与HINT 文章中提出的相同的评估指标:ROC AUC、F1、PR-AUC、精确度、召回率和准确率。
训练 XGBoost 模型,并预测训练、验证和测试标签
import xgboost as xgb
# Create an XGBoost classifier with specified hyperparameters
xgb_classifier = xgb.XGBClassifier(
learning_rate=0.1,
max_depth=3,
n_estimators=200,
objective='binary:logistic', # for binary classification
random_state=42
)
# Train the XGBoost model
xgb_classifier.fit(X_train, y_train)
# Make predictions
y_train_pred = xgb_classifier.predict(X_train)
y_val_pred = xgb_classifier.predict(X_val)
y_test_pred = xgb_classifier.predict(X_test)
print('-----------Results on training data:-----------')
print_results(y_train_pred, y_train)
print('-----------Results on validation data:-----------')
print_results(y_val_pred, y_val)
print('-----------Results on test data:-----------')
print_results(y_test_pred, y_test)
### Output:
#-----------Results on training data:-----------
# ROC AUC: 1.0
# F1: 1.0
# PR-AUC: 1.0
# Precision: 1.0
# recall: 1.0
# accuracy: 1.0
# predict 1 ratio: 0.661
# label 1 ratio: 0.661
# -----------Results on validation data:-----------
# ROC AUC: 0.765
# F1: 0.817
# PR-AUC: 0.799
# Precision: 0.840
# recall: 0.795
# accuracy: 0.773
# predict 1 ratio: 0.602
# label 1 ratio: 0.636
# -----------Results on test data:-----------
# ROC AUC: 0.742
# F1: 0.805
# PR-AUC: 0.757
# Precision: 0.790
# recall: 0.821
# accuracy: 0.759
# predict 1 ratio: 0.630
# label 1 ratio: 0.606
与 HINT 模型比较性能
这个简单的 XGBoost 模型在药物分子、纳入/排除标准、疾病指示、试验赞助商和参与者人数的特征嵌入上进行了训练,而 HINT 作者没有使用最后两个特征:试验赞助商和参与者人数。我们使用了几个大型语言模型嵌入工具,如 BioBERT 和 SBERT,并采用了 Morgan 编码进行药物表示,而 HINT 作者使用了多种神经网络进行所有的嵌入。
从下面的图中可以看到,我们的特征嵌入在简单的 XGBoost 模型下的表现相比于更复杂的 HINT 模型相当好。我们的项目在这个数据集上的精确度和准确性更高,但召回率较低。
本项目性能与 HINT 项目的比较(图片由作者提供)
结论
下一步可能包括分析以确定添加特征试验赞助商和参与者人数在提高性能(在某些指标上)方面的贡献程度,相较于其他因素,如模型选择和嵌入技术。直观上,这些特征似乎可以提高预测性能,因为某些赞助商的历史表现优于其他人,而且也可以预期试验规模与结果之间存在一定的关系。
现在你可能会想:“这样的预测模型有什么用处?我们不能仅凭这种模型而放弃进行试验吗?”你的想法是正确的(尽管一些公司正在创建患者的数字双胞胎,以期实现虚拟试验)。例如,本系列中展示的模型可以用于改进临床试验的效能分析,这是一种相关的统计实践。效能分析用于确定特定试验中招募参与者的最佳数量,并且需要对治疗效果做出强假设才能进行这种分析。利用试验信息如药物分子结构、疾病指征和试验资格标准的预测模型(例如我们在此实现的模型)可以有助于创建更准确的效能分析。
参考文献
- Fu, Tianfan, 等. “提示:用于临床试验结果预测的层级交互网络。” Patterns 3.4 (2022).
临床试验结果预测
第一部分:多模态健康数据嵌入
·
关注 发表在 Towards Data Science ·8 分钟阅读·2023 年 10 月 4 日
–
我最近遇到了一篇文章:HINT: 临床试验结果预测的层次交互网络 由 Fu 等人撰写。这是一个有趣的真实数据科学应用,激发了我创建自己的项目,在该项目中,我尝试基于来自ClinicalTrials.gov的公开信息预测临床试验结果。
项目的目标是预测临床试验的结果(二元结果:失败与成功),无需实际进行试验。我们将使用来自 ClinicalTrials.gov 的公开临床试验信息,如药物分子、疾病指示、试验方案、赞助商和参与者数量,并使用不同的工具,如 BioBERT、SBERT 和 DeepPurpose,将其嵌入(转换为向量表示)。
工作流程示意图(图片由作者提供)
在本系列的第一部分,我专注于嵌入多模态临床试验数据。在 第二部分 中,我使用 XGBoost 模型预测试验结果(二元预测:失败与成功),并简要比较我的简单 XGBoost 模型与 文章 中的 HINT 模型的性能。
本系列第一部分的重点:将多模态临床试验数据嵌入向量(图片由作者提供)
本文中我将遵循以下步骤:
-
从 ClinicalTrials.gov 收集所有临床试验记录
-
阅读和解析获得的 XML 文件
-
使用 tiny-biobert 嵌入疾病指示,这是 Rohanian et al 的紧凑版本,BioBERT
-
使用 tiny-biobert 嵌入临床试验纳入/排除标准
-
使用 all-MiniLM-L6-v2 嵌入赞助商信息,这是来自 SentenceBERT 的强大预训练句子编码器
-
将药物名称转换为其 SMILES 表示形式,然后使用 DeepPurpose 和 Huang et al. 转换为其 Morgan 指纹
你可以在此 Jupyter notebook 中遵循所有步骤:临床试验嵌入教程。
从 ClinicalTrials.gov 收集临床试验记录
我建议在命令行中运行整个过程,因为它既耗时又占用空间。如果你的系统上没有安装 wget,可以查看 如何安装 wget。打开命令行/终端并输入以下命令:
# 0\. Clone repository
# Navigate to the directory where you want to clone the repository and type:
git clone https://github.com/lenlan/clinical-trial-prediction.git
cd clinical-trial-prediction
# 1\. Download data
mkdir -p raw_data
cd raw_data
wget https://clinicaltrials.gov/AllPublicXML.zip # This will take 10-20 minutes to download
# 2\. Unzip the ZIP file.
# The unzipped file occupies approximately 11 GB. Please make sure you have enough space.
unzip AllPublicXML.zip # This might take over an hour to run, depending on your system
cd ../
# 3\. Collect and sort all the XML files and put output in all_xml
find raw_data/ -name NCT*.xml | sort > data/all_xml
head -3 data/all_xml
### Output:
# raw_data/NCT0000xxxx/NCT00000102.xml
# raw_data/NCT0000xxxx/NCT00000104.xml
# raw_data/NCT0000xxxx/NCT00000105.xml
# NCTID is the identifier of a clinical trial. `NCT00000102`, `NCT00000104`, `NCT00000105` are all NCTIDs.
# 4\. Remove ZIP file to recover some disk space
rm raw_data/AllPublicXML.zip
阅读和解析获得的 XML 文件
现在你已经将临床试验作为单独的文件保存在硬盘上,我们将通过解析 XML 文件提取所需信息。
from xml.etree import ElementTree as ET
# function adapted from https://github.com/futianfan/clinical-trial-outcome-prediction
def xmlfile2results(xml_file):
tree = ET.parse(xml_file)
root = tree.getroot()
nctid = root.find('id_info').find('nct_id').text ### nctid: 'NCT00000102'
print("nctid is", nctid)
study_type = root.find('study_type').text
print("study type is", study_type)
interventions = [i for i in root.findall('intervention')]
drug_interventions = [i.find('intervention_name').text for i in interventions \
if i.find('intervention_type').text=='Drug']
print("drug intervention:", drug_interventions)
### remove 'biologics',
### non-interventions
if len(drug_interventions)==0:
return (None,)
try:
status = root.find('overall_status').text
print("status:", status)
except:
status = ''
try:
why_stop = root.find('why_stopped').text
print("why stop:", why_stop)
except:
why_stop = ''
try:
phase = root.find('phase').text
print("phase:", phase)
except:
phase = ''
conditions = [i.text for i in root.findall('condition')] ### disease
print("disease", conditions)
try:
criteria = root.find('eligibility').find('criteria').find('textblock').text
print('found criteria')
except:
criteria = ''
try:
enrollment = root.find('enrollment').text
print("enrollment:", enrollment)
except:
enrollment = ''
try:
lead_sponsor = root.find('sponsors').find('lead_sponsor').find('agency').text
print("lead_sponsor:", lead_sponsor)
except:
lead_sponsor = ''
data = {'nctid':nctid,
'study_type':study_type,
'drug_interventions':[drug_interventions],
'overall_status':status,
'why_stopped':why_stop,
'phase':phase,
'indications':[conditions],
'criteria':criteria,
'enrollment':enrollment,
'lead_sponsor':lead_sponsor}
return pd.DataFrame(data)
### Output:
# nctid is NCT00040014
# study type is Interventional
# drug intervention: ['exemestane']
# status: Terminated
# phase: Phase 2
# disease ['Breast Neoplasms']
# found criteria
# enrollment: 100
# lead_sponsor: Pfizer
使用句子变换器嵌入信息 — 示例
首先我们需要安装 sentence-transformers 库。
pip install -U sentence-transformers
from sentence_transformers import SentenceTransformer
sentences = ["This is an example sentence", "Each sentence is converted"]
#all-MiniLM-L6-v2 encodes each sentence into a 312-dimensional vector
model = SentenceTransformer('all-MiniLM-L6-v2')
embeddings = model.encode(sentences)
print(embeddings.shape)
### Output:
# (2, 312)
我们成功地将两个句子转换为一个 312 维的向量表示。
使用 tiny-biobert 嵌入疾病指示
首先我们创建一个字典,将每个指示映射到其 312 维的嵌入表示,使用tiny-biobert。然后,我们创建一个直接将每个试验标识符映射到其疾病嵌入表示的字典。当试验包含多个指示时,我们取它们的均值作为向量表示。
def create_indication2embedding_dict():
# Import toy dataset
toy_df = pd.read_pickle('data/toy_df.pkl')
# Create list with all indications and encode each one into a 312-dimensional vector
all_indications = sorted(set(reduce(lambda x, y: x + y, toy_df['indications'].tolist())))
# Using 'nlpie/tiny-biobert', a smaller version of BioBERT
model = SentenceTransformer('nlpie/tiny-biobert')
embeddings = model.encode(all_indications, show_progress_bar=True)
# Create dictionary mapping indications to embeddings
indication2embedding_dict = {}
for key, row in zip(all_indications, embeddings):
indication2embedding_dict[key] = row
pickle.dump(indication2embedding_dict, open('data/indication2embedding_dict.pkl', 'wb'))
embedding = []
for indication_lst in tqdm(toy_df['indications'].tolist()):
vec = []
for indication in indication_lst:
vec.append(indication2embedding_dict[indication])
print(np.array(vec).shape) # DEBUG
vec = np.mean(np.array(vec), axis=0)
print(vec.shape) # DEBUG
embedding.append(vec)
print(np.array(embedding).shape)
dict = zip(toy_df['nctid'], np.array(embedding))
nctid2disease_embedding_dict = {}
for key, row in zip(toy_df['nctid'], np.array(embedding)):
nctid2disease_embedding_dict[key] = row
pickle.dump(nctid2disease_embedding_dict, open('data/nctid2disease_embedding_dict.pkl', 'wb'))
create_indication2embedding_dict()
使用 tiny-biobert 嵌入临床试验的纳入/排除标准
以非常类似的方式,我们编码临床试验的纳入/排除标准。需要进行一些额外的数据清理,以使文本格式正确。我们分别编码纳入标准和排除标准,每个标准的嵌入表示是其包含的句子的均值向量表示。
def create_nctid2protocol_embedding_dict():
# Import toy dataset
toy_df = pd.read_pickle('data/toy_df.pkl')
# Using 'nlpie/tiny-biobert', a smaller version of BioBERT
model = SentenceTransformer('nlpie/tiny-biobert')
def criteria2vec(criteria):
embeddings = model.encode(criteria)
# print(embeddings.shape) # DEBUG
embeddings_avg = np.mean(embeddings, axis=0)
# print(embeddings_avg.shape) # DEBUG
return embeddings_avg
nctid_2_protocol_embedding = dict()
print(f"Embedding {len(toy_df)*2} inclusion/exclusion criteria..")
for nctid, protocol in tqdm(zip(toy_df['nctid'].tolist(), toy_df['criteria'].tolist())):
# if(nctid == 'NCT00003567'): break #DEBUG
split = split_protocol(protocol)
if len(split)==2:
embedding = np.concatenate((criteria2vec(split[0]), criteria2vec(split[1])))
else:
embedding = np.concatenate((criteria2vec(split[0]), np.zeros(312)))
nctid_2_protocol_embedding[nctid] = embedding
# for key in nctid_2_protocol_embedding: #DEBUG
# print(f"{key}:{nctid_2_protocol_embedding[key].shape}") #DEBUG
pickle.dump(nctid_2_protocol_embedding, open('data/nctid_2_protocol_embedding_dict.pkl', 'wb'))
return
create_nctid2protocol_embedding_dict()
使用 all-MiniLM-L6-v2 嵌入赞助商信息,这是 SentenceBERT 提供的一个强大的预训练句子编码器
我选择使用句子嵌入(SBERT)来编码试验赞助商。使用 Label 或 One-Hot 编码等简单方法也可以,但我希望能够捕捉赞助商名称之间的相似性,以防有拼写错误或多个不同的拼写。我使用预训练的all-MiniLM-L6-v2模型,它在基准数据集上具有高速和高性能。它将每个赞助机构转换为一个 384 维的向量。
def create_sponsor2embedding_dict():
# Import toy dataset
toy_df = pd.read_pickle('data/toy_df.pkl')
# Create list with all indications and encode each one into a 384-dimensional vector
all_sponsors = sorted(set(toy_df['lead_sponsor'].tolist()))
# Using 'all-MiniLM-L6-v2', a pre-trained model with excellent performance and speed
model = SentenceTransformer('all-MiniLM-L6-v2')
embeddings = model.encode(all_sponsors, show_progress_bar=True)
print(embeddings.shape)
# Create dictionary mapping indications to embeddings
sponsor2embedding_dict = {}
for key, row in zip(all_sponsors, embeddings):
sponsor2embedding_dict[key] = row
pickle.dump(sponsor2embedding_dict, open('data/sponsor2embedding_dict.pkl', 'wb'))
create_sponsor2embedding_dict()
将药物名称转换为 SMILES 表示,然后使用 DeepPurpose 转换为 Morgan 指纹
分子可以用 SMILES 字符串表示。SMILES 是一种编码分子结构的线性符号表示法。药物分子数据从ClinicalTrials.gov提取,并通过CACTUS链接到其分子结构(SMILES 字符串)。
import requests
def get_smiles(drug_name):
# URL for the CIR API
base_url = "https://cactus.nci.nih.gov/chemical/structure"
url = f"{base_url}/{drug_name}/smiles"
try:
# Send a GET request to retrieve the SMILES representation
response = requests.get(url)
if response.status_code == 200:
smiles = response.text.strip() # Get the SMILES string
print(f"Drug Name: {drug_name}")
print(f"SMILES: {smiles}")
else:
print(f"Failed to retrieve SMILES for {drug_name}. Status code: {response.status_code}")
smiles = ''
except requests.exceptions.RequestException as e:
print(f"An error occurred: {e}")
return smiles
# Define the drug name you want to convert
drug_name = "aspirin" # Replace with the drug name of your choice
get_smiles(drug_name)
### Output:
# Drug Name: aspirin
# SMILES: CC(=O)Oc1ccccc1C(O)=O
DeepPurpose可以用来编码分子化合物。它目前支持 15 种不同的编码。我们将使用 Morgan 编码,它将化学的原子组编码为一个二进制向量,以长度和半径作为两个参数。首先我们需要安装 DeepPurpose 库。
pip install DeepPurpose
DeepPurpose 编码器概述(图片来自Huang et al.,CC 许可)
我们创建一个将 SMILES 映射到 Morgan 表示的字典,以及一个将临床试验标识符(NCTIDs)直接映射到其 Morgan 表示的字典。
def create_smiles2morgan_dict():
from DeepPurpose.utils import smiles2morgan
# Import toy dataset
toy_df = pd.read_csv('data/toy_df.csv')
smiles_lst = list(map(txt_to_lst, toy_df['smiless'].tolist()))
unique_smiles = set(reduce(lambda x, y: x + y, smiles_lst))
morgan = pd.Series(list(unique_smiles)).apply(smiles2morgan)
smiles2morgan_dict = dict(zip(unique_smiles, morgan))
pickle.dump(smiles2morgan_dict, open('data/smiles2morgan_dict.pkl', 'wb'))
def create_nctid2molecule_embedding_dict():
# Import toy dataset
toy_df = pd.read_csv('data/toy_df.csv')
smiles_lst = list(map(txt_to_lst, toy_df['smiless'].tolist()))
smiles2morgan_dict = load_smiles2morgan_dict()
embedding = []
for drugs in tqdm(smiles_lst):
vec = []
for drug in drugs:
vec.append(smiles2morgan_dict[drug])
# print(np.array(vec).shape) # DEBUG
vec = np.mean(np.array(vec), axis=0)
# print(vec.shape) # DEBUG
embedding.append(vec)
print(np.array(embedding).shape)
dict = zip(toy_df['nctid'], np.array(embedding))
nctid2molecule_embedding_dict = {}
for key, row in zip(toy_df['nctid'], np.array(embedding)):
nctid2molecule_embedding_dict[key] = row
pickle.dump(nctid2molecule_embedding_dict, open('data/nctid2molecule_embedding_dict.pkl', 'wb'))
create_nctid2molecule_embedding_dict()
结论
通过使用特征嵌入,我们可以使用公开的临床试验信息为机器学习模型创建有用的输入。总结一下,我们:
-
使用tiny-biobert嵌入疾病指征,由Rohanian et al提供,该版本是BioBERT的精简版。
-
使用tiny-biobert嵌入临床试验纳入/排除标准。
-
使用all-MiniLM-L6-v2嵌入赞助商信息,这是来自SentenceBERT的强大预训练句子编码器。
-
将药物名称转换为其 SMILES 表示,然后使用DeepPurpose通过Huang et al.转换为其 Morgan 指纹。
在本系列的第二部分中,我将运行一个简单的 XGBoost 模型,以预测临床试验结果,基于我们在这里创建的嵌入向量表示。我将其性能与 HINT 模型进行比较。
参考文献
-
Fu, Tianfan, 等。“Hint: 用于临床试验结果预测的层次互动网络。” Patterns 3.4 (2022)。
-
Huang, Kexin, 等。“DeepPurpose: 用于药物-靶标相互作用预测的深度学习库。” 生物信息学 36.22–23 (2020): 5545–5547。
CLIP:无需数据即可创建图像分类器
这是一个实践教程,解释如何使用预训练的 CLIP 模型生成自定义的 Zero-Shot 图像分类器,而无需进行训练。完整代码包含在内。
·
关注 发表在 Towards Data Science ·7 分钟阅读·2023 年 2 月 22 日
–
图像由作者使用 Midjourney 生成
介绍
想象一下你需要分类判断人们是否戴眼镜,但你没有数据或资源来训练自定义模型。在本教程中,你将学习如何使用预训练的 CLIP 模型创建一个自定义分类器,无需任何训练。这个方法被称为零样本图像分类,它使得对原 CLIP 模型训练过程中没有明确见过的类别图像进行分类成为可能。下面提供了一个易于使用的 Jupyter 笔记本,包含完整代码,供你方便使用。
CLIP: 理论背景
CLIP(对比语言-图像预训练)模型,由 OpenAI 开发,是一个多模态视觉和语言模型。它将图像和文本描述映射到同一潜在空间,从而能够确定图像和描述是否匹配。CLIP 以对比方式进行训练,以预测数据集中超过 4 亿对图像-文本对中哪个描述对应哪个图像[1]。令人惊讶的是,预训练 CLIP 生成的分类器显示出与监督模型基准相当的竞争结果,在本教程中,我们将利用这一预训练模型来生成一个眼镜检测器。
CLIP 对比训练
CLIP 模型由图像编码器和文本编码器组成(图 1)。在训练过程中,一批图像通过图像编码器(ResNet 变体或 ViT)处理,以获得图像表示张量(嵌入)。与此同时,它们的对应描述通过文本编码器(Transformer)处理,以获得文本嵌入。CLIP 模型的训练目的是预测图像嵌入属于哪个文本嵌入。通过联合训练图像编码器和文本编码器来最大化真实配对的图像和文本嵌入的余弦相似度[2](图 1,对角轴上的蓝色方块),同时最小化错误配对的嵌入之间的余弦相似度(图 1,白色方块)。优化是通过对这些相似度分数进行对称交叉熵损失来实现的。
图 1 — CLIP 训练过程的迷你批次示意图。T1 是 class1 的嵌入向量,I1 是 image1 的嵌入向量,等等。| 图片来源于 Radford 等人,2021 [1]
创建自定义分类器
要使用 CLIP 创建自定义分类器,首先将类别名称转换为文本嵌入向量,由预训练的文本编码器完成,同时图像则由预训练的图像编码器嵌入(图 2)。然后计算图像嵌入与每个文本嵌入之间的余弦相似度,并将图像分配给具有最高余弦相似度分数的类别。
图 2 — 使用 CLIP 的零样本分类 | 图像来自 Radford 等人,2021 [1],由作者编辑。人脸图像取自 Kaggle 上的‘有眼镜还是没有眼镜’数据集 [3]。
代码实现
数据集
在本教程中,我们将创建一个图像分类器,用于检测人们是否戴眼镜,并使用 Kaggle 上的‘有眼镜还是没有眼镜’数据集 [3] 来评估我们分类器的性能。尽管数据集包含 5000 张图像,但我们仅使用前 100 张以加快演示。数据集包含一个包含所有图像的文件夹和一个包含标签的 CSV 文件。为了方便加载图像路径和标签,我们将自定义 Pytorch Dataset
类来创建 CustomDataset()
类。您可以在提供的 notebook 中找到相应的代码。
来自 Kaggle 上‘有眼镜还是没有眼镜’数据集的随机图像 [3]
加载 CLIP 模型
在安装和导入 CLIP 及相关库之后,我们加载模型和指定模型所需的 torchvision 转换管道。文本编码器是 Transformer,而图像编码器可以是 Vision Transformer (ViT) 或 ResNet 变体,如 ResNet50。要查看可用的图像编码器,可以使用命令 clip.available_models()
。
print( clip.available_models() )
model, preprocess = clip.load("RN50")
提取文本嵌入
文本标签首先由文本分词器 (clip.tokenize()
) 处理,将标签词转换为数值。这生成一个大小为 N x 77 的填充张量(N 是类别数,二分类中为 2 x 77),作为文本编码器的输入。文本编码器将张量转换为 N x 512 的文本嵌入张量,其中每个类别由一个向量表示。要编码文本并检索嵌入,您可以使用 model.encode_text()
方法。
preprocessed_text = clip.tokenize(['no glasses','glasses'])
text_embedding = model.encode_text(preprocessed_text)
提取图像嵌入
在输入到图像编码器之前,每张图像会经过预处理,包括中心裁剪、归一化和调整大小,以满足图像编码器的要求。一旦预处理完成,图像将传递给图像编码器,生成 1 x 512 的图像嵌入张量作为输出。
preprocessed_image = preprocess(Image.open(image_path)).unsqueeze(0)
image_embedding = model.encode_image(preprocessed_image)
相似性结果
为了衡量图像编码与每个文本标签编码之间的相似性,我们将使用余弦相似度距离度量。model()
接收预处理后的图像和文本输入,将它们通过图像和文本编码器,并计算对应图像和文本特征之间的余弦相似度,乘以 100(image_logits
)。然后使用 Softmax 将 logits 归一化为每个类别的概率分布列表。由于我们不训练模型,我们将使用torch.no_grad()
禁用梯度计算。
with torch.no_grad():
image_logits, _ = model(preprocessed_image, preprocessed_text)
proba_list = image_logits.softmax(dim=-1).cpu().numpy()[0]
具有最高概率的类别被设置为预测类别,并提取其索引、概率和对应的标记。
y_pred = np.argmax(proba_list)
y_pred_proba = np.max(proba_list)
y_pred_token = ['no glasses','glasses'][y_pred_idx]
封装代码
我们可以创建一个名为 CustomClassifier
的 Python 类来封装这些代码。初始化时,预训练的 CLIP 模型会被加载,并为每个标签生成嵌入的文本表示向量。我们将定义一个 classify()
方法,该方法接受一个图像路径作为输入,并返回预测的标签及其概率分数(存储在名为df_results
的 DataFrame 中)。为了评估模型的性能,我们将定义一个 validate()
方法,该方法使用 PyTorch 数据集实例(CustomDataset()
)来检索图像和标签,然后通过调用 classify()
方法来预测结果,并评估模型的性能。该方法返回一个包含所有图像预测标签和概率分数的 DataFrame。max_images
参数用于限制图像数量为 100。
class CustomClassifier:
def __init__(self, prompts):
self.class_prompts = prompts
self.device = "cuda" if torch.cuda.is_available() else "cpu"
self.model, self.preprocess = clip.load("RN50", device=self.device) # "ViT-B/32"
self.preprocessed_text = clip.tokenize(self.class_prompts).to(self.device)
print(f'Classes Prompts: {self.class_prompts}')
def classify(self, image_path, y_true = None):
preprocessed_image = self.preprocess(Image.open(image_path)).unsqueeze(0).to(self.device)
with torch.no_grad():
image_logits, _ = self.model(preprocessed_image, self.preprocessed_text)
proba_list = image_logits.softmax(dim=-1).cpu().numpy()[0]
y_pred = np.argmax(proba_list)
y_pred_proba = np.max(proba_list)
y_pred_token = self.class_prompts[y_pred]
results = pd.DataFrame([{'image': image_path, 'y_true': y_true, 'y_pred': y_pred, 'y_pred_token': y_pred_token, 'proba': y_pred_proba}])
return results
def validate (self, dataset, max_images):
df_results = pd.DataFrame()
for sample in tqdm(range(max_images)):
image_path, class_idx = dataset[sample]
image_results = self.classify(image_path, class_idx)
df_results = pd.concat([df_results, image_results])
accuracy = accuracy_score(df_results.y_true, df_results.y_pred)
print(f'Accuracy - {round(accuracy,2)}')
return accuracy, df_results
可以使用 classify()
方法对单张图像进行分类:
prompts = ['no glasses','glasses']
image_results = CustomClassifier(prompts).classify(image_path)
分类器的性能可以通过 validate()
方法进行评估:
accuracy, df_results = CustomClassifier(prompts).validate(glasses_dataset, max_images =100)
值得注意的是,使用原始的 [‘无眼镜’,‘眼镜’] 类别标签,我们在没有训练任何模型的情况下取得了 0.82 的不错准确率,并且我们可以通过提示工程进一步提高结果。
提示工程
CLIP 分类器将文本标签(称为提示)编码到一个学习到的潜在空间中,并将其与图像潜在空间进行比较。修改提示的措辞可能会导致不同的文本嵌入,这会影响分类器的性能。为了提高预测准确率,我们将通过反复试验来探索多个提示,选择结果最好的那个。例如,使用提示‘没有眼镜的男人的照片’和‘戴眼镜的男人的照片’的准确率为 0.94。
prompts = ['photo of a man with no glasses', 'photo of a man with glasses']
accuracy, df_results = CustomClassifier(prompts).validate(glasses_dataset, max_images =100)
分析多个提示产生了以下结果:
-
[‘无眼镜’,‘眼镜’] — 0.82 的准确率
-
[‘无眼镜的脸’,‘戴眼镜的脸’] — 0.89 的准确率
-
[‘没有眼镜的男人的照片’,‘戴眼镜的男人的照片’] — 0.94 的准确率
正如我们所看到的,调整措辞可以显著提升性能。通过分析多个提示,我们将准确率从 0.82 提升到了 0.94。然而,需要注意的是,避免对提示进行过拟合。
总结
CLIP 模型是开发零样本分类器的一个非常强大的工具,适用于各种任务。使用 CLIP,我能够轻松地在我的项目中生成实时分类器,并取得了非常满意的准确率。然而,CLIP 在细粒度分类、抽象或系统性任务(如计数物体)以及预测真正不在其预训练数据集中覆盖的图像时可能会遇到困难。因此,在进行新任务之前应对其性能进行评估。
使用下面提供的 Jupyter notebook,你可以轻松创建自己的自定义分类器。只需按照说明进行操作,添加你的数据,你就能快速启动并运行一个个性化的分类器。
感谢阅读!
想了解更多?
完整的 Jupyter Notebook 代码
教程的完整代码可以在第一个参考文献 [0] 中找到。
参考文献
[0] 代码: gist.github.com/Lihi-Gur-Arie/844a4c3e98a7561d4e0ddb95879f8c11
[1] CLIP 文章: arxiv.org/pdf/2103.00020v1.pdf
[2] 余弦相似度回顾: towardsdatascience.com/understanding-cosine-similarity-and-its-application-fd42f585296a
[3] 来自 Kaggle 的‘眼镜与非眼镜’数据集,许可证 CC BY-SA 4.0: www.kaggle.com/datasets/jeffheaton/glasses-or-no-glasses
CLIP — 直观且详尽的解释
原文:
towardsdatascience.com/clip-intuitively-and-exhaustively-explained-1d02c07dbf40
为一般机器学习任务创建强大的图像和语言表示。
·发表于 Towards Data Science ·阅读时间 17 分钟·2023 年 10 月 20 日
–
由 Daniel Warfield 使用 MidJourney 制作的“对比模式”。除非另有说明,所有图片均由作者提供。
在这篇文章中,你将学习到“对比语言-图像预训练”(CLIP),一种创建视觉和语言表示的策略,使得这些表示足够好,可以用于创建高度特定且性能优越的分类器,而无需任何训练数据。我们将探讨理论,了解 CLIP 与更传统方法的不同,然后逐步讲解其架构。
CLIP 为分类任务预测高度特定的标签,这些任务从未直接进行过训练。来源
谁会觉得这篇文章有用? 任何对计算机视觉、自然语言处理(NLP)或多模态建模感兴趣的人。
这篇文章有多高级? 这篇文章对初学数据科学的读者应该是容易理解的。后面的一些部分稍微复杂一点(特别是当我们深入探讨损失函数时)。
前提条件: 对计算机视觉和自然语言处理有一些基础了解。
典型的图像分类器
在训练一个模型以检测一张图片是猫还是狗时,常见的方法是向模型展示猫和狗的图片,然后根据模型的错误逐步调整,直到模型学会区分这两者。
监督学习可能是什么样子的概念图。假设我们有一个对图像一无所知的新模型。我们可以将一张图像输入给它,让它预测图像的类别,然后根据预测的错误程度来更新模型的参数。我们可以重复这个过程多次,直到模型开始在任务上表现良好。我在这篇文章中探讨了反向传播,这是使这种情况一般可能的机制。
这种传统的监督学习形式在许多使用场景中完全可以接受,并且在各种任务中表现良好。然而,这种策略也会导致高度专业化的模型,这些模型只在其初始训练的范围内表现良好。
将 CLIP 与更传统的监督模型进行比较。每个模型都在 ImageNet(一个流行的图像分类数据集)上进行训练并表现良好,但当暴露于包含相同类别但不同表示形式的类似数据集时,监督模型的性能会显著下降,而 CLIP 则不会。这表明 CLIP 中的表示比其他方法更具鲁棒性和可泛化性。来源
为了解决过度专业化的问题,CLIP 以一种根本不同的方式处理分类;通过尝试通过对比学习来学习图像及其注释之间的关联。我们将在下一部分探讨这意味着什么。
CLIP,简而言之
如果我们不是创建一个可以预测图像是否属于某个类别的模型,而是创建一个预测图像是否属于某个任意说明的模型呢?这是一个微妙的思维转变,它为完全新的训练策略和模型应用打开了大门。
CLIP 的最终结果是一个可以预测任意文本是否与任意图像匹配的模型
CLIP 的核心思想是利用从互联网上抓取的带有说明文字的图像来创建一个模型,该模型可以预测文本是否与图像兼容。
CLIP 应用于它之前未见过的各种数据集的示例。虽然并不完全完美(它预测了错误类型的飞机),但 CLIP 表现出一种令人瞩目的能力,能够理解各种不同的分类问题。来源
CLIP 通过学习如何对图像和文本进行编码,使得当比较文本和图像的编码时,匹配的图像具有高值而不匹配的图像具有低值。本质上,该模型学习将图像和文本映射到一个空间,使得匹配的对接近在一起,而不匹配的对则远离。 这种学习预测事物是否属于同一组的策略通常被称为“对比学习”。
从 CLIP 的角度来看,对比学习的概念图。本质上,我们将每个图像和每个标题放置在某个任意空间中。然后我们学习将这些图像和标题放置在该空间中,使得匹配的对接近在一起,而不匹配的对则远离。
在 CLIP 中,通过学习一个文本编码器和一个图像编码器来进行对比学习,这两个编码器学习将输入放置在向量空间中的某个位置。CLIP 然后在训练过程中比较这些位置,并尝试最大化正对的接近度,同时最小化负对的接近度。
CLIP 的示意图。我们将一堆图像及其相应的描述进行编码,使得匹配值较大,不匹配值较小。在上面的示意图中,蓝色高亮区域对应正匹配对,而矩阵的其余部分对应需要最小化的负匹配对。 来源
CLIP 采用的总体策略允许我们做各种事情:
-
我们可以通过只询问模型哪些文本,如“猫的照片”和“狗的照片”,最有可能与图像相关,来构建图像分类器。
-
我们可以构建一个图像搜索系统,用于找到与输入文本最相关的图像。例如,我们可以查看各种图像,并找到最可能与文本“狗的照片”对应的图像。
-
我们可以单独使用图像编码器来提取与文本相关的图像的抽象信息。编码器可以根据图像的内容将图像定位在空间中,这些信息可以被其他机器学习模型使用。
-
我们可以单独使用文本编码器来提取与图像相关的文本的抽象信息。编码器可以根据文本的整体内容将文本定位在空间中,这些信息可以被其他机器学习模型使用。
回想一下,我们在学习如何将图像和文本定位到相似的东西靠近在一起。通过这个过程,我们找到了一种将文本和图像放置在有意义位置的方法。为了有效地做到这一点,我们必须构建对图像和文本有强大理解的编码器。我们需要能够理解图像中的“猫性”,或者理解“fabulous”这个词在修饰“jacket”这个词。因此,CLIP 中使用的编码器可以单独使用以从特定输入中提取意义。
虽然零样本分类相当酷(零样本指的是能够在未见过的数据类型上表现良好。例如,询问模型“这个人快乐吗”而模型从未被明确训练去检测快乐),提取和使用 CLIP 中的文本或图像编码器变得更加流行。由于 CLIP 模型被训练来创建文本和图像的微妙而强大的编码,这些编码可以表示复杂的关系,因此 CLIP 编码器生成的高质量嵌入可以被用于其他任务;例如,我有一篇文章使用 CLIP 的图像编码器来使语言模型理解图像:
与 LLMs 谈论图像,而不对 LLMs 进行图像训练。
towardsdatascience.com
所以,现在我们对 CLIP 有了一个高层次的理解。如果你还没完全明白也没关系;在下一节中,我们将逐个组件拆解 CLIP,以建立对其功能的直观理解。
CLIP 的组件
CLIP 是一个高层次的架构,可以使用各种不同的子组件来实现相同的一般结果。我们将遵循CLIP 论文,并拆解其中一种可能的方法。
文本编码器
CLIP 中的文本编码器,source
在最高层次上,文本编码器将输入文本转换为一个表示文本含义的向量(一个数字列表)。
文本编码器的目的,本质上
CLIP 中的文本编码器是标准的变压器编码器,我在另一篇文章中直观而详尽地介绍了它。为了本文的目的,变压器可以被认为是一个系统,它接收整个输入序列的词,然后重新表示和比较这些词,以创建整个输入的抽象化、情境化表示。变压器中的自注意机制是创建这种情境化表示的主要机制。
多头自注意力,变压器中的主要操作,将输入序列的词转换为抽象表示。最终结果可以被概念化为包含“情境化意义”,而不是一个词列表。如果你不知道什么是词向量嵌入,我在这篇文章中进行了介绍。
CLIP 对通用变压器策略的一项修改是,它生成的是向量而不是矩阵,旨在表示整个输入序列。它通过简单地提取输入序列中最后一个标记的向量来实现。这是有效的,因为自注意机制旨在将每个输入与其他输入进行情境化。因此,经过多层自注意力机制后,变压器可以学习将所有必要的意义编码到一个单一的向量中。
CLIP 文本编码器的概念图;多个多头自注意力层将输出操控为最终的向量,该向量表示整个输入。这个最终向量被用来将文本输入表示为空间中的一个点,这个点最终用于计算字幕与图像之间的“接近度”。
随时参阅我关于变压器的文章,以获得更深入的信息。在下一节中,我们将讨论图像编码器,它将图像转换为代表性向量。
探索现代机器学习的潮流:一步步拆解变压器
towardsdatascience.com
图像编码器
CLIP 中的图像编码器,source
在最高层次上,图像编码器将图像转换为代表图像含义的向量(数字列表)。
图像编码器的目的,基本上
CLIP 论文中讨论了几种图像编码器的方法。在这篇文章中,我们将考虑 ResNET-50,这是一种经过时间考验的卷积方法,已应用于多个通用图像任务。我将在未来的文章中详细介绍 ResNET,但在本文中,我们可以将 ResNET 视为经典的卷积神经网络。
卷积神经网络是一种图像建模策略,通过一个称为卷积核的小矩阵对图像进行滤波。它将卷积核在图像中滑动,并根据卷积核和输入图像计算每个像素的新值。
通过卷积核转换图像的概念图。卷积核被放置在图像的某个位置,并将周围的像素乘以某个值。然后,它将这些乘积值相加,以计算图像在该位置的新值。每个卷积核的值是可学习的,卷积网络使用许多卷积核。最终结果是一个网络,通过多种不同方式重新表示输入图像,以从中提取意义。
卷积网络的整个想法是,通过对图像进行卷积和下采样的组合,你可以提取越来越微妙的特征表示。一旦图像被压缩成少量高质量的抽象特征,就可以使用密集网络将这些特征转换为最终输出。我在另一篇文章中深入讨论了这个问题,特别是最后的密集网络的角色,即投影头。
YOLO 论文中的经典卷积架构,一个具有里程碑意义的目标检测模型。这些框描述了输入图像的水平和垂直尺寸,以及它们的“深度”,以特征数量来衡量。输入图像是 RGB 图像,因此它的深度为 3。下一个框的“深度”为 192,这对应于有 192 个卷积核从输入图像中提取不同的信息。通过提取越来越多的特征,并通过最大池化对图像进行下采样,网络将图像提炼成一个抽象表示,并训练其对图像的某些含义。Source
从 CLIP 的角度来看,最终结果是一个向量,可以被视为输入图像的摘要。这个向量以及文本的摘要向量将用于下一部分,以构建多模态嵌入空间,我们将在下一部分中讨论。
CLIP 中使用的图像编码器简要说明。图像编码器通过卷积网络将图像转换为一组抽象特征,然后使用密集的全连接神经网络生成最终输出。在这种情况下,最终输出向量可以被认为是对整个输入的总结。
多模态嵌入空间与 CLIP 训练
CLIP 训练过程中的一个组件,它将文本和图像的嵌入进行联合对齐,来源
在前两节中,我们讨论了可以将文本和图像总结为向量的建模策略。在这一节中,我们将讨论 CLIP 如何利用这些向量构建强大的图像和语言表示。
将复杂事物总结为抽象向量的想法通常被称为“嵌入”。我们将图像和文本等事物“嵌入”到向量中,以总结它们的一般意义。
图像和文本编码器将每个输入“嵌入”。注意:这些嵌入的长度通常非常长,可能长达 256 个元素。
我们可以将这些嵌入向量视为高维空间中某一点的表示。为了演示的目的,我们可以想象创建将输入嵌入到长度为二的向量的编码器。这些向量可以被视为二维空间中的点,我们可以绘制它们的位置。
CLIP 训练前的示例,使用二维嵌入进行演示。每张图像都通过图像编码器生成一个长度为 2 的向量,每个输入文本都通过文本编码器生成一个长度为 2 的向量。
我们可以将这个空间视为多模态嵌入空间,我们可以训练 CLIP(通过训练图像和文本编码器)使这些点的位置安排得使得正样本对彼此接近。
CLIP 训练后的示例,使用二维嵌入进行演示。注意,一旦编码器经过训练,正样本对最终会接近在一起。
在机器学习中定义“接近”的方式有很多种。可以说最常见的方法是余弦相似度,这是 CLIP 使用的方法。余弦相似度的思想是,如果两个向量之间的角度很小,我们可以说它们是相似的。
如果基于余弦相似度计算相似性,则 A 和 B 之间的角度较小,因此 A 和 B 是相似的。C 将被认为与 A 和 B 都非常不同。
“余弦”这个术语来源于余弦函数,它是一种三角函数,根据某个角度计算直角三角形中邻边与斜边的比值。如果这听起来像是胡言乱语,也没关系:如果两个向量之间的角度很小,则它们之间的余弦值接近 1。如果向量之间的角度为 90 度,余弦值为零。如果向量指向相反的方向,余弦值为-1。结果是,当向量朝同一方向时,你会得到大的数值,而当它们不朝同一方向时,你会得到小的数值。
这是一个关于余弦波(底部的波形)如何与直角三角形中的角度相关的概念图。对本文而言,完全理解余弦函数并不是非常重要,只需了解当两个事物之间的角度为 0 时,余弦值最大,而当两个事物朝相反方向时,余弦值最小。如果你对学习更多关于三角学和波动的内容感兴趣,可以参考我写的这篇文章,讲述了频率分析如何与机器学习相关。
两个向量之间的夹角的余弦值可以通过测量它们之间的角度,然后将该角度通过余弦函数计算来得到。不过,打印出所有向量并使用量角器测量它们之间的角度可能会拖慢我们的训练时间。幸运的是,我们可以使用以下恒等式来计算两个向量之间夹角的余弦值:
来源
如果你已经觉得数学很复杂,你现在可能会觉得更复杂。但是我会将其拆解:
-
短语A•B表示向量 A 和 B 之间的点积。点积是指将 A 中的每个元素与 B 中对应的元素相乘,然后将所有结果相加。所以如果 A=[1,2,3],B=[2,3,4],则 A•B = (1x2) + (2x3) + (3x4)。
-
短语“||(某个向量)||”,如**||A||或||B||**,表示向量的范数计算。这只是向量的大小或长度。向量的长度可以通过计算向量中各个分量的平方和的平方根来得到。所以,对于 A=[1,2,3],||A|| = sqrt(1² + 2² + 3²)。
-
从概念上讲,可以将分子A•B视为相似性,而分母**||A||||B||**将相似性除以向量的长度。这种除法使得余弦相似性仅根据向量之间的角度变化,而不依赖于它们的大小。如果没有分母进行调整,A•B 的值会随着 A 和 B 的长度增加而增大,而不管它们的方向。
如果我们回顾原始图示,可能会注意到我们正在计算图像和文本的嵌入之间的点积。
CLIP 中图像和文本表示之间距离的计算。注意点积,即余弦相似度的分子,是如何在文本和图像的每个嵌入之间计算的。来源。
由于损失的计算方式,所有图像和文本向量的长度都将为 1,因此我们可以省略除以向量大小的步骤。因此,虽然我们没有除以分母,但这仍然是通过余弦相似度进行概念上的训练(我们将在下一部分详细讨论)。
现在我们对如何将图像和文本转换为嵌入向量以及如何使用这些嵌入向量来计算相似度有了一定了解,我们可以更深入地探讨训练实际情况,了解 CLIP 如何使用对比损失。
CLIP 和对比损失
对比损失的整体思想是,不是单独查看每个示例以尝试在每对样本上提升性能,而是将问题视为逐步提高正样本对的相似度,同时保持负样本对的距离。
左侧是传统的监督方法,右侧是对比方法。监督方法处理单个输入-输出对(通常以小批量的形式出现,但仍然是一对一的分组),而对比方法将所有可能的对配对在一起,并尝试提高正样本对的接近性(用蓝色突出显示),同时减少负样本对的接近性。
在 CLIP 中,这是通过计算编码文本和图像表示之间的点积来完成的,正如我们之前讨论的,这可以用来量化“接近性”。
这种转变的思维方式使得对比学习真正赋予 CLIP 强大的能力。图像的描述可以有很多种方式;一张图片可以被描述为“猫躺下了”、“猫咪放松中”、“小可爱乔治在打盹”等等。虽然很难预测图像的实际描述,但通过使用“接近性”和“远离性”,CLIP 能够优雅地处理这个问题。
CLIP 在每个批次中使用 32,768 对图像-文本,这比传统方法的批次大小(通常为 16–64)要大得多。因此,CLIP 必须非常擅长将正样本对从大量负样本对中分离开来,这也是 CLIP 如此强大的原因。
为了训练神经网络,你需要一个性能的单一值,称为“损失”,你可以用它来更新模型。在每次训练步骤中,你更新模型的参数,使得模型在该步骤输出一个更小的损失值。CLIP 论文中包括了以下伪代码,描述了 CLIP 中损失的计算方式:
# image_encoder - ResNet or Vision Transformer
# text_encoder - CBOW or Text Transformer
# I[n, h, w, c] - minibatch of aligned images
# T[n, l] - minibatch of aligned texts
# W_i[d_i, d_e] - learned proj of image to embed
# W_t[d_t, d_e] - learned proj of text to embed
# t - temperature parameter
# 1) get a batch of aligned images and text
I, T = get_mini_batch()
# 2) extract feature representations of each modality
I_f = image_encoder(I) #[n, d_i]
T_f = text_encoder(T) #[n, d_t]
# 3) joint multimodal embedding [n, d_e]
I_e = l2_normalize(np.dot(I_f, W_i), axis=1)
T_e = l2_normalize(np.dot(T_f, W_t), axis=1)
# 4) scaled pairwise cosine similarities [n, n]
logits = np.dot(I_e, T_e.T) * np.exp(t)
# 5) symmetric loss function
labels = np.arange(n)
loss_i = cross_entropy_loss(logits, labels, axis=0)
loss_t = cross_entropy_loss(logits, labels, axis=1)
loss = (loss_i + loss_t)/2
将这一点分解成组件:
-
首先,我们获取一批维度为
[batch size, image height, image width, number of colors]
的图像和一批维度为[batch size, sequence length]
的文本。这些批次彼此对齐,使得图像批次中的每个图像与文本批次中的每一段文本对应。 -
我们将这些通过我们的编码器,这样每个图像和每段文本在各自的批次中就会生成一个向量。
-
为了将图像和文本放置在相同的嵌入空间中,这些向量通过线性投影。这可以被视为一个没有偏差或激活函数的单层全连接网络。CLIP 论文提到,这些细节并不是特别重要,重要的是图像和文本向量的长度最终相同。这些向量使用 l2 归一化进行归一化,这使得向量保持指向相同的方向,但将所有向量压缩为长度为一。
-
由于所有嵌入向量的长度为 1,因此不需要通过其大小来计算余弦相似度。因此,嵌入向量之间的点积等同于余弦相似度。余弦相似度乘以一个温度参数,该参数控制相似度在给定训练周期中的影响强度。
-
损失是通过交叉熵损失在文本和图像之间对称地计算的。这在 CLIP 论文中有提到,但细节可能有些复杂。
在研究这篇文章时,我发现了以下交叉熵损失的表达式:
交叉熵损失,其中“t”是某个真实标签的值,“p”是某个预测的值。
这个想法很好,但正如我们之前讨论的,余弦相似度的范围是 -1 到 1,而你不能对负数取对数。CLIP 论文提到如下内容:
这些嵌入的余弦相似度随后被计算,通过温度参数 τ 进行缩放,并通过 softmax 归一化为概率分布。— CLIP
利用这些信息,以及伪代码中的信息:
# 5) symmetric loss function
# logits = nxn matrix of cosin similarity predictions
# labels = nxn matrix of true values
labels = np.arange(n)
loss_i = cross_entropy_loss(logits, labels, axis=0)
loss_t = cross_entropy_loss(logits, labels, axis=1)
loss = (loss_i + loss_t)/2
我们可以推断出 cross_entropy_loss
函数(作为伪代码中指定的函数)包含在指定轴上的 softmax 操作。这是一个细节,但它在使 CLIP 有效训练中很重要。
对于那些可能不太了解的人,softmax 函数将一组值的向量转换为一个正向量,其组件的总和等于一。
softmax 函数。使用 exp 具有各种良好的属性,但本质上,softmax 函数将输入向量压缩,使得向量中的元素在 0 和 1 之间,且向量中所有元素的总和为 1。
这已经有点数学复杂了,我认为对 softmax 的完整数学理解并不是根本性的关键。让我们看看几个例子:
Softmax 函数的四个示例应用。Softmax 函数将向量中的所有元素映射到 0 和 1 之间,并确保所有映射元素的总和等于 1。softmax 函数的结果可以看作是一组概率。(第三个例子中有一个错字,应为 [0.67, 0.24, 0.09],感谢 Anna 提醒我并在评论中告知我。)
Softmax 函数允许我们将余弦距离值在 -1 和 1 之间的“接近度”转换为概率向量,概率值在 0 和 1 之间,可以解释为“属于一组”。
这种“属于一组”的概率可以通过两种方式计算:
-
我们可以对文本轴上的余弦距离进行 softmax,从而计算文本属于图像的概率。
-
我们可以对图像轴上的余弦距离进行 softmax,从而计算图像属于文本的概率。
计算概率的两种方法。我们可以计算某段文本属于图像的概率(上图),或者我们可以计算某张图像属于一段文本的概率(下图)。
这就是 CLIP 伪代码中的 cross_entropy_loss
函数包含 axis
参数的原因;softmax 可以水平或垂直计算,以进行两种损失计算之一。
现在我们得到的概率范围在 0 和 1 之间,我们可以使用交叉熵函数来计算损失。这将是我们的训练目标,我们将尝试最小化它。
交叉熵损失,其中“t”是某个真实标签的值,“p”是某个预测的值。这个特定的表达式适用于真实值向量和预测向量。这个表达式也可以扩展为矩阵或两个矩阵的损失之和,正如我们在这里的情况。
我们可以逐个计算每个矩阵中的元素的损失。然后我们可以将两个矩阵中的所有损失加起来以计算总损失。
通过两种 softmax 方法将概率转换为单一损失值。
聪明的人可能会意识到这里的一个特殊不兼容性。对比学习的整个要点是你正在学习优化正对和负对。我们希望将正对推得更近,同时将负对推得更远。如果负对的损失(我们优化的内容)无论我们做什么都是零,我们怎么能学习将负对推得更远呢?(负对被识别为真实值为零,这使得负对的对数损失总是为零)
这是 softmax 函数的一个巧妙但极其重要的特性:当负对的概率增大时,正对的概率会直接减少。 结果,通过优化正对的概率尽可能接近 1,我们也在优化负对之间的余弦距离尽可能小。
使用 CLIP
我之前提到过 CLIP 的这些使用场景,但现在我们对 CLIP 有了更深入的了解,我想重申一下。
用法 1: 图像分类器
给定一个输入图像,我们可以将各种文本描述传递给 CLIP,计算哪个描述最能代表图像。我们可以通过将图像传递通过图像编码器,将所有文本传递通过文本编码器,并通过它们的嵌入的点积计算图像和所有文本输入之间的余弦相似度。然后,我们可以计算所有相似度值的 softmax,以计算一段文本属于某个图像的概率。
用法 2: 图像搜索
类似于构建图像分类器,我们可以将一些短语传递到文本编码器,将多张图像传递到图像编码器,计算点积和 softmax,从而获得哪个图像与一段文本最相关的概率。
用法 3: 图像编码器
由于 CLIP 在一般情况下能够很好地表示图像内容,我们可以将图像编码器用于下游任务。我在这里介绍了一个示例。
用法 4: 文本编码器
由于 CLIP 在理解语言短语的哪些方面与图像相关方面表现出色,我们可以将文本编码器用于下游任务。
附件
查看这篇文章的附件,其中我使用 CLIP 风格的模型实现了两种类型的图像搜索
前沿的图像搜索,简单而迅速
[towardsdatascience.com
结论
就这些了!做得好,坚持下来了。CLIP 是非常迷人和强大的,但因为它在本质上与更直接的方法差异很大,所以可能很难理解。
在这篇文章中,我们讨论了 CLIP 存在的高层原因以及它的一般功能。然后我们将 CLIP 分解为三个组件:图像编码器、文本编码器以及用于将两者连接在一起的共同对齐嵌入空间。我们讲解了 CLIP 如何利用大量的批次创建众多正负样本的高层直觉,然后深入探讨了用于优化 CLIP 的损失函数。
关注获取更多内容!
我描述了机器学习领域的论文和概念,重点在于实用和直观的解释。我计划在未来的文章中从头实现 CLIP。
高质量的数据科学文章直接送到你的邮箱。每当丹尼尔·沃菲尔德发布新文章时,你都会收到邮件。通过注册,你…
medium.com](https://medium.com/@danielwarfield1/subscribe?source=post_page-----1d02c07dbf40--------------------------------)
版权声明: 本文档中的所有资源均由丹尼尔·沃菲尔德创建,除非另有来源说明。你可以将本文中的任何资源用于个人非商业用途,只要你引用了这篇文章,danielwarfield.dev
,或两者都引用。应要求可以提供明确的商业许可。
CLIP 模型及其多模态嵌入的重要性
·
关注 发表在 Towards Data Science ·10 分钟阅读·2023 年 12 月 11 日
–
CLIP,即对比语言-图像预训练,是 OpenAI 在 2021 年开发的深度学习模型。CLIP 的图像和文本嵌入共享同一空间,使得两种模态之间可以直接进行比较。这是通过训练模型使相关的图像和文本更接近,同时将不相关的图像和文本推远来实现的。本文将解释 CLIP 的工作原理,并指导你如何使用 flikker 和 COCO 数据集训练 CLIP 模型。
你可以在这个 GitHub 仓库中找到代码:
github.com/RustamyF/clip-multimodal-ml
CLIP 的应用
CLIP 的一些应用包括:
-
图像分类和检索:CLIP 可以用于图像分类任务,通过将图像与自然语言描述关联起来。它允许更为多样和灵活的图像检索系统,用户可以通过文本查询来搜索图像。
-
内容审查:CLIP 可以通过分析图像及其附带的文本来识别和过滤不适当或有害的内容,从而用于在线平台上的内容审查。
原始 CLIP 模型旨在将图像和文本模态统一到一个共享的嵌入空间中。这个概念及其技术不仅限于图像和文本,还扩展到其他模态。Netflix 在这篇博客文章中通过在共享嵌入空间中结合视频和文本模态来训练模型,以增强视频应用中的搜索功能。对比语言-音频预训练 (CLAP)是另一种将文本和音频模态集成在同一嵌入空间中的模型,有助于改善音频应用中的搜索功能。
CLIP 的基础技术非常简单但却非常强大,为许多多模态机器学习技术打开了大门。Meta AI 最近发布了ImageBind,该技术在六种模态——图像、文本、音频、深度、热成像和 IMU 数据之间学习联合嵌入。CLIP 是第一个接受两种模态的大规模 AI 模型,它是理解 ImageBind 和其他多模态 AI 系统的前提。
META AI 的 Imagebind 接受六种不同的模态作为输入(取自ImageBind 的官方 GitHub 页面)。
什么是 CLIP
CLIP 旨在预测批次中的 N × N 潜在(图像、文本)配对中哪些是真实匹配的。为此,CLIP 通过图像编码器和文本编码器的联合训练建立了一个多模态嵌入空间。CLIP 损失的目标是最大化批次中 N 个真实配对的图像和文本嵌入之间的余弦相似度,同时最小化 N² − N 个错误配对的余弦相似度。 优化过程涉及使用对称交叉熵损失函数,该函数作用于这些相似度得分。以下展示了伪代码(取自原始论文),概述了 CLIP 的核心实现。
# image_encoder - ResNet or Vision Transformer
# text_encoder - CBOW or Text Transformer
# I[n, h, w, c] - minibatch of aligned images
# T[n, l] - minibatch of aligned texts
# W_i[d_i, d_e] - learned proj of image to embed
# W_t[d_t, d_e] - learned proj of text to embed
# t - learned temperature parameter
# extract feature representations of each modality
I_f = image_encoder(I) #[n, d_i]
T_f = text_encoder(T) #[n, d_t]
# joint multimodal embedding [n, d_e]
I_e = l2_normalize(np.dot(I_f, W_i), axis=1)
T_e = l2_normalize(np.dot(T_f, W_t), axis=1)
# scaled pairwise cosine similarities [n, n]
logits = np.dot(I_e, T_e.T) * np.exp(t)
# symmetric loss function
labels = np.arange(n)
loss_i = cross_entropy_loss(logits, labels, axis=0)
loss_t = cross_entropy_loss(logits, labels, axis=1)
loss = (loss_i + loss_t)/2
以下是每一行伪代码的逐步描述及其在 PyTorch 中的实现:
模型架构:
CLIP 使用两种独立的架构作为视觉和文本数据集编码的骨干:
-
image_encoder
:表示负责编码图像的神经网络架构(例如,ResNet 或 Vision Transformer)。 -
text_encoder
:表示神经网络架构(如 CBOW、BERT 或 Text Transformer),负责对文本信息进行编码。
原始 CLIP 模型从头开始训练,没有用预训练权重初始化图像编码器和文本编码器,因为他们用于训练 CLIP 模型的数据集体积巨大(4 亿对图像-文本)。在本博客文章中的示例中,我们会有所不同。我们将从 resnet(用于图像)和 distilbert(用于文本)模型的预训练权重开始,以初始化这些部分。
CLIP 模型的架构(取自原始论文)
输入数据:
模型接收一个包含 n 对图像和文本的小批量作为输入,其中:
-
I[n, h, w, c]
:表示对齐图像的小批量,其中n
是批量大小,h
是图像高度,w
是图像宽度,c
是通道数。 -
T[n, l]
:表示对齐文本的小批量,其中n
是批量大小,l
是文本序列的长度。
一批图像和标题对,批量大小为 128
特征提取:
-
I_f = image_encoder(I)
:从图像编码器中提取特征表示(I_f
)。I_f
的形状为[n, d_i]
,其中d_i
是图像特征的维度。 -
T_f = text_encoder(T)
:从文本编码器中提取特征表示(T_f
)。T_f
的形状为[n, d_t]
,其中d_t
是文本特征的维度。
I_f = models.resnet34(pretrained=True) # for encoding images
T_f= AutoModel.from_pretrained("distilbert-base-multilingual-cased") # for encoding captions
学习到的投影:
-
W_i[d_i, d_e]
:表示用于将图像特征(I_f
)映射到嵌入空间(I_e
)的学习投影矩阵。W_i
的形状为[d_i, d_e]
,其中d_e
是联合嵌入空间的期望维度。 -
W_t[d_t, d_e]
:表示用于将文本特征(T_f
)映射到相同嵌入空间(T_e
)的学习投影矩阵。W_t
的形状为[d_t, d_e]
。
投影操作可以通过一个包含两个线性层的神经网络来编码,这些层的权重就是学习到的投影矩阵。在大多数情况下,投影权重是唯一具有活跃梯度的权重,可以在新数据集上进行训练。此外,投影层在对齐图像和文本嵌入的维度方面起着至关重要的作用,确保它们具有相同的大小。
class Projection(nn.Module):
def __init__(self, d_in: int, d_out: int, p: float=0.5) -> None:
super().__init__()
self.linear1 = nn.Linear(d_in, d_out, bias=False)
self.linear2 = nn.Linear(d_out, d_out, bias=False)
self.layer_norm = nn.LayerNorm(d_out)
self.drop = nn.Dropout(p)
def forward(self, x: torch.Tensor) -> torch.Tensor:
embed1 = self.linear1(x)
embed2 = self.drop(self.linear2(F.gelu(embed1)))
embeds = self.layer_norm(embed1 + embed2)
return embeds
嵌入和归一化:
-
I_e = l2_normalize(np.dot(I_f, W_i), axis=1)
:在联合嵌入空间中嵌入并归一化图像特征(I_e
)。 -
T_e = l2_normalize(np.dot(T_f, W_t), axis=1)
:在联合嵌入空间中嵌入并归一化文本特征(T_e
)。
下面的代码展示了图像和文本数据的顺序处理过程。最初,数据经过基本编码器处理,然后通过投影层,最后生成归一化的嵌入并返回。
class VisionEncoder(nn.Module):
def __init__(self, d_out: int) -> None:
super().__init__()
base = models.resnet34(pretrained=True)
d_in = base.fc.in_features
base.fc = nn.Identity()
self.base = base
self.projection = Projection(d_in, d_out)
for p in self.base.parameters():
p.requires_grad = False
def forward(self, x):
projected_vec = self.projection(self.base(x))
projection_len = torch.norm(projected_vec, dim=-1, keepdim=True)
return projected_vec / projection_len
class TextEncoder(nn.Module):
def __init__(self, d_out: int) -> None:
super().__init__()
self.base = AutoModel.from_pretrained(Config.text_model)
self.projection = Projection(Config.transformer_embed_dim, d_out)
for p in self.base.parameters():
p.requires_grad = False
def forward(self, x):
out = self.base(x)[0]
out = out[:, 0, :] # get CLS token output
projected_vec = self.projection(out)
projection_len = torch.norm(projected_vec, dim=-1, keepdim=True)
return projected_vec / projection_len
vision_encoder = VisionEncoder(Config.embed_dim)
I_e = vision_encoder(images)
caption_encoder = TextEncoder(Config.embed_dim)
T_e = caption_encoder(text["input_ids"])
余弦相似度:
logits = np.dot(I_e, T_e.T) * np.exp(t)
:计算图像和文本嵌入之间的成对余弦相似度,按学习到的温度参数t
进行缩放。
在本示例中,我们以与原始论文中相同的方式交替使用相似度和 logits。我们将在本博客中不包含温度参数t
。
logits = I_e @ T_e.T
对称损失函数:
CLIP 使用对比损失(首次在对比预测编码的表示学习中引入)来将相关的图像和文本拉近,同时将不相关的图像和文本分开。
-
labels = np.arange(n)
:生成表示批次索引的标签。 -
loss_i = cross_entropy_loss(logits, labels, axis=0)
:计算图像轴上的交叉熵损失。 -
loss_t = cross_entropy_loss(logits, labels, axis=1)
:计算文本轴上的交叉熵损失。 -
loss = (loss_i + loss_t)/2
:计算图像和文本损失的对称平均。
def CLIP_loss(logits: torch.Tensor) -> torch.Tensor:
n = logits.shape[1] # number of samples
labels = torch.arange(n) # Create labels tensor
# Calculate cross entropy losses along axis 0 and 1
loss_i = F.cross_entropy(logits.transpose(0, 1), labels, reduction="mean")
loss_t = F.cross_entropy(logits, labels, reduction="mean")
# Calculate the final loss
loss = (loss_i + loss_t) / 2
return loss
最终自定义 CLIP 模型
将所有不同的部分组合在一起,最终的自定义 CLIP 模型如下所示:
class CustomModel(nn.Module):
def __init__(self, lr: float = 1e-3) -> None:
super().__init__()
self.vision_encoder = VisionEncoder(Config.embed_dim)
self.caption_encoder = TextEncoder(Config.embed_dim)
self.tokenizer = Tokenizer(AutoTokenizer.from_pretrained(Config.text_model))
self.lr = lr
self.device = "cuda" if torch.cuda.is_available() else "cpu"
def forward(self, images, text):
text = self.tokenizer(text).to(self.device)
image_embed = self.vision_encoder(images)
caption_embed = self.caption_encoder(text["input_ids"])
similarity = caption_embed @ image_embed.T
loss = CLIP_loss(similarity)
img_acc, cap_acc = metrics(similarity)
return loss, img_acc, cap_acc
示例
本示例演示了创建图像标题数据集和训练自定义 CLIP 模型的过程。目标是联合训练视觉编码器和文本编码器,将图像及其标题的表示投影到相同的嵌入空间,使标题嵌入位于它们描述的图像的嵌入附近。此项目的代码在我的GitHub 存储库中。
数据集和数据加载器
我们的自定义 CLIP 模型将使用flickr30k 数据集进行训练。该数据集包含超过 31,000 张图像,每张图像至少有 5 个独立的人类生成的标题。我们将在本示例中使用每张图像的两个标题,共有 62,000 对图像和文本用于训练。尽管传统上用于图像标题任务,但我们打算将图像-标题对适配到我们的双编码器模型,专门用于图像搜索。 GitHub 存储库中还包括了用于在 MS-COCO 数据集上训练模型的代码,其中包含 164,000 对图像和文本。
from torch.utils.data import DataLoader
from datasets import load_dataset
from torchvision import transforms
from PIL import Image
import torch
from torchvision import transforms
from PIL import Image
# Define a custom dataset class for Flickr30k
class Flickr30kDataset(torch.utils.data.Dataset):
def __init__(self):
self.dataset = load_dataset("nlphuji/flickr30k", cache_dir="./huggingface_data")
self.transform = transforms.Compose([
transforms.Resize((224, 224)),
transforms.ToTensor(),
])
self.cap_per_image = 2
def __len__(self):
return self.dataset.num_rows["test"] * self.cap_per_image
def __getitem__(self, idx):
original_idx = idx // self.cap_per_image
image = self.dataset["test"][original_idx]["image"].convert("RGB")
image = self.transform(image)
# labels
caption = self.dataset["test"][original_idx]["caption"][idx % self.cap_per_image]
return {"image": image, "caption": caption}
# Create an instance of the custom dataset
flickr30k_custom_dataset = Flickr30kDataset()
关键模型常量包括embed_dim
用于学习的表示,transformer_embed_dim
用于变换器层特征,以及max_len
用于文本输入长度。选择的text_model
是“distilbert-base-multilingual-cased”。训练跨度为 3epochs
,batch_size
为 128,这些常量将输入到模型构建和训练中。
from dataclasses import dataclass
@dataclass
class Config:
"""
Configuration class for the CLIP training script.
"""
embed_dim: int = 512 # Embedding dimension
transformer_embed_dim: int = 768 # Transformer embedding dimension
max_len: int = 32 # Maximum text length
text_model: str = "distilbert-base-multilingual-cased" # Text model name
epochs: int = 3 # Number of training epochs
batch_size: int = 128 # Batch size
DataLoader 被设置为在训练期间高效迭代,提供对图像-标题对的有组织访问。
# Create the DataLoader
clip_dataloader = DataLoader(flickr30k_custom_dataset, batch_size=BATCH_SIZE, shuffle=True, num_workers=4)
这是数据集中一个批次中图像标题对的示例。
import numpy as np
import matplotlib.pyplot as plt
# Create an iterator from the dataloader
data_iter = iter(clip_dataloader)
# Get one batch
batch = next(data_iter)
image = batch["image"][0] # get one image from the batch
caption = batch["caption"][0] # get one text from the batch
# Convert the image tensor to a NumPy array and permute dimensions
image_np = np.transpose(image.numpy(), (1, 2, 0))
# Display the image and caption
plt.imshow(image_np)
plt.title(f"Caption: {caption}")
plt.show()
在这里,我们初始化我们的 CustomModel 并将其发送到设备(CPU 或 GPU)。此外,我们指定了在训练过程中需要优化的参数。由于我们已经固定了文本和图像编码器的基础层,因此只有与投影层相关的参数将在新数据集上进行训练。
# Create an instance of your model
model = CustomModel().to(device)
# Define optimizer
optimizer = torch.optim.Adam([
{'params': model.vision_encoder.parameters()},
{'params': model.caption_encoder.parameters()}
], lr=model.lr)
模型训练
训练是在一台 Tesla T4 (g4dn-xlarge) GPU 机器上进行了 3 个训练 epoch。该 Jupyter Notebook 可在项目的 GitHub 仓库 中找到,并包含训练循环的代码。
batch_zero = True
for epoch in range(start_epoch, num_epochs):
model.train()
for batch in clip_dataloader:
image = batch["image"].to(device)
text = batch["caption"]
# images, text = batch
loss, img_acc, cap_acc = model.common_step((image, text))
# Backward pass and optimization
optimizer.zero_grad()
loss.backward()
optimizer.step()
if batch_zero:
print(f"Epoch [{0}/{num_epochs}], Batch Loss: {loss.item()}")
batch_zero = False
# Print training statistics
print(f"Epoch [{epoch+1}/{num_epochs}], Batch Loss: {loss.item()}")
print("Training complete.")
以下是使用 flicker30k 数据集进行每个 epoch 的训练循环结果。有关更多详细信息,请参阅此笔记本。
Epoch [0/3], Batch Loss: 4.854558944702148
Epoch [1/3], Batch Loss: 3.187166690826416
Epoch [2/3], Batch Loss: 3.0981950759887695
Epoch [3/3], Batch Loss: 3.164858818054199
Training complete.
以下是使用 COCO2017 数据集进行每个 epoch 的训练循环结果。与 flickr30k 数据集中的 62,000 张图像对相比,COCO 数据集提供了超过 160,000 张图像-文本对,使模型在 COCO 数据集上的收敛速度更快。有关更多详细信息,请参阅此笔记本。
Epoch [0/3], Batch Loss: 4.852224349975586
Epoch [1/3], Batch Loss: 2.7819151878356934
Epoch [2/3], Batch Loss: 2.727229118347168
Epoch [3/3], Batch Loss: 2.717097759246826
Training complete.
结论
总结来说,本博客文章探讨了 CLIP 模型,揭示了其广泛应用的潜力。随着我们对 CLIP 应用的理解变得更加深入,其影响显然超出了最初的预期,为各个领域的创新解决方案铺平了道路。CLIP 是第一个成功地弥合了不同模态之间差距的模型,并开启了跨学科创新的途径。
关闭的 AI 模型不适合作为基准
·
关注 发表在 Towards Data Science · 18 分钟阅读 · 2023 年 4 月 25 日
–
这篇文章的作者是 Anna Rogers,在 Niranjan Balasubramanian、Leon Derczynski、Jesse Dodge、Alexander Koller、Sasha Luccioni、Maarten Sap、Roy Schwartz、Noah A. Smith 和 Emma Strubell(按字母顺序排列)的宝贵帮助和反馈下完成。封面图片来源:Sasha Luccioni
以下内容是尝试汇总 ChatGPT 之后 NLP 研究现状的一些讨论。¹ 我们是 NLP 研究人员,至少我们的工作是维护科学方法的基本原则。这篇文章主要针对初级 NLP 研究人员,但也对社区的其他成员相关,他们在考虑这种模型的存在应该如何改变他们的下一篇论文。我们认为,就研究和科学出版物而言,“封闭”模型(如下面定义的)不能被有意义地研究,它们不应成为“通用基准”,像 BERT 曾一度被广泛认为的那样。这篇文章的 TLDR 是对审稿人和主席的一个简单建议规则(类似于贝德规则),要求命名所研究的语言):
不可开放且合理可重复的内容不能被视为必要的基准。
这里所说的“开放”意味着模型可以下载,可以离线运行(即使需要非平凡的计算资源),并且即使原始提供者不再提供下载,仍可以与其他用户共享。“开放”的模型支持版本控制,并为每个模型版本记录所使用的训练数据。如果模型不是开放的,它就是“封闭”的。
这里所说的“合理可重复”意味着创建者公开了足够的信息,使得模型可以通过提供的代码、数据和指定的计算资源来重复,尽管由于硬件/软件差异、数据流失因素和神经网络的非确定性,存在一定的变化。例如,重复 BLOOM 需要超级计算机——但至少从理论上讲,这是可能的,考虑到开源代码、收集和记录数据的措施。因此,根据我们的定义,它是“合理可重复的”,尽管不是每个人都能做到。
相关性 != 流行度
许多 NLP 研究生最近都在问一个问题:
这种焦虑似乎部分源于我们的领域中,“相关性”一直受到极端流行趋势的驱动。在过去的十年里,总会有一个大家都在谈论的事物:一个模型或方法,成为一个标尺,一个基准,大家都明智地在他们的论文中展示,以证明他们所做的工作是有意义的改进。这是可以理解的,因为机器学习社区的一个驱动价值观是改进过去的工作——否则,我们怎么知道自己在取得进展,对吧?2013 年之后,我们有了 word2vec/GloVe,接着是 BERT 的类似热潮。然后是 GPT-3。现在——ChatGPT 和 GPT-4。
这为什么会发生?背后有两种推理:
-
大家都在谈论的事物要么是我所做的事情中真正最先进的,要么是一个合理的基准,所以我最好在我的论文中提到它,并用我的模型超越它。
-
作为作者,我的发表机会部分依赖于审稿人对我工作的喜爱,因此对我来说最安全的做法是谈论大多数人可能感兴趣的事物——也就是大家都在谈论的事物。
(b) 实际上是自我实现的预言:作者们越是这样想,他们就会越多地使用大家都在谈论的事物,这反过来又强化了审稿人对这一事物确实是必需品的信念。我们看到这种循环表现为个体社区成员的信念与他们对其他人对应优先研究方向(例如,专注于基准测试或规模)的看法之间的差异,正如NLP 社区元调查中所记录的那样。尽管这需要付出努力,研究社区的成员可以抵制这种循环(我们将在下面讨论具体的策略)。至于 (a) — 当大家都在谈论的事物实际上是可以有意义比较的东西时,这是有道理的。
我们想要表达的主要观点是,这种推理方式对于那些未披露足够架构、训练设置、数据和推理时操作信息的封闭模型已经不再适用。即使有多少人说它们效果很好也无关紧要。即使不涉及商业 LLM 的可疑伦理问题,已经有涉及代码和艺术的版权侵权诉讼,以及不道德来源的标记数据——基本的研究方法论都要求如此。许多人提出,作为研究人员,我们现在面临着一个不可能的境地。
-
我们对这些模型的训练内容或方式知之甚少:
-
所谓的黑箱正在不断变化:
-
我们的输入提示和输出答案可能会通过不明确的机制进行不明确的编辑。例如,ChatGPT 通过内容过滤器进行“自我审查”,人们乐于绕过这些过滤器,并且拥有专有的提示前缀:
是的,这些模型在实践中确实对许多人来说很令人印象深刻——但作为研究人员,我们的工作不是盲目跟随炒作。训练这些模型的公司有权选择完全商业化,因此不接受独立审查——这是以利润为主要目的的实体的预期。然而,这必然意味着它们放弃了科学研究者的角色。正如 Gary Marcus 所说,
我不期望可口可乐公布其秘密配方。但我也不打算为他们声称的进展提供科学可信度,这些进展我们一无所知。
为什么封闭模型作为必要基准会破坏 NLP 研究叙事
为了使事情更具体,让我们考虑一些 NLP 论文中常见的“研究叙事”,以及使用这种“封闭”模型作为基准时它们将如何受到影响。我们将以 GPT-4 作为一个“封闭”模型的实例,尽管它发布时几乎没有 技术细节,但它的确得到了 100 页的报告称赞,但这些观点同样适用于其他类似模型。
“我们提出了一种在技术上超越现有水平的机器学习模型”:
-
要声称我们的算法比商业模型所做的任何事情更优秀,我们至少需要知道我们在做一些质的不同的事情。如果我们提议对当前流行的方法(例如,Transformers)进行某些修改,而没有文档,我们根本不能排除“封闭”模型可能在做类似的事情。
-
即使我们相信我们正在做一些质的不同的事情,我们仍然需要能够声称任何改进都是由于我们提出的修改,而不是模型大小、数据的类型和数量、重要的超参数、“幸运”的随机种子等。由于我们没有这些信息,无法对封闭“基准”进行有意义的比较。
-
即使我们忽略所有上述因素——为了在某些性能指标上与这些模型进行公平比较,我们至少要知道我们的模型没有观察到测试数据。对于“封闭”模型,我们同样不知道。即使是 OpenAI 自身最初也对 GPT-3 的测试数据污染 感到担忧,尤其是在全世界都乐意测试 ChatGPT 几个月之后,这种情况是不可能改善的。而且它 没有改善。
我们作为模型开发者从 GPT-4 的存在中能学到的唯一一点是,这是一种通过某种未指定的当前方法和数据组合可以获得的性能。这是一个上限或存在证明,看起来高于现有的替代方案。上限很重要,可以作为我们工作的动力来源,但不能用作比较的依据。
“我们提出了一个新的挑战性任务/基准/指标”:
构建良好的评估数据是一项非常艰难且昂贵的工作,当我们相信这些数据可以作为公开基准来衡量 NLP 模型的进展时,投资其中是有意义的,至少可以维持几个月。过去推动 NLP 研究的基准示例包括SQuAD、GLUE和BigBench。但是,公开基准只有在测试数据保持隐藏的情况下才能有效(即使如此,最终人们还是会评估太多次并开始隐性过拟合)。这显然与流行的“封闭”模型开发者的场景不兼容,该开发者通过 API 访问模型,保留我们的提交数据并可能用于训练。除非模型明确描述并分享其训练数据,否则我们无法进行审计。
这意味着就开发者的模型而言,我们的努力基本上是一次性的。下一次迭代可能会“表现出色”(但原因不一定正确)。
让我们考虑一下 OpenAI 在这方面的政策:
-
ChatGPT 默认会保留你的数据并可能用于训练。据说提供了退出数据收集的选项。
-
OpenAI API 政策 于 2023 年 3 月 1 日更新,目前规定默认情况下数据不会被保留或用于训练。任何在此日期之前提交的数据都可以使用,因此我们可以安全地假设,自 2020 年以来,现有的大部分甚至全部公共基准数据已经提交给 GPT-3,包括标签或“黄金”答案——至少那些被用作少量示例提示的内容。有趣的是,OpenAI 随后使用数据污染作为排除某些评估的理由,但不排除其他评估:GPT-4 技术报告称由于数据污染未对 BIG-bench 进行评估(在版本 3的报告中是第 6 页的脚注 5),尽管他们确实展示了 100%污染的 GRE 写作考试的结果(表 9)。
总体问题在于,选择退出甚至选择加入在作为公共基准的数据集的情况下是不足够的:作为数据集创建者,我们工作的未来可能不仅会受到我们自己使用数据的影响,还会受到其他人使用数据的影响!只需要一个没有小心选择退出的研究者,或无法选择退出的研究者,我们的数据就会被那个开发者“污染”对未来模型的影响。即使只提交了一些少量样本,它们也可能被用来以某种方式自动增强类似的用户提示。最后但同样重要的是,如果我们将数据公开,模型开发者自己也可能主动将其加入训练数据,以期改善他们的模型。如果标签或“黄金”答案对于一个重要基准数据集不公开,他们可能会创建一些类似的数据。
目前还不清楚如何解决这个问题。也许很快会出现某种特殊版本的 robots.txt,它不仅禁止用于 AI 训练,还要求任何重新分享这些数据的行为保留相同的标记。并且,希望大型公司最终会被要求遵守,并接受审计。在短期内,唯一的选择似乎是简单地不信任或不产生无法进行测试-训练重叠分析的模型的基准结果。
“我们展示了模型 X 是否做 Y:(模型分析和可解释性)”
由于我们只能通过 API 访问 GPT-4,我们只能探查模型输出。如果计划使用现有的探查数据集或构建新的数据集,我们将面临上述相同的资源问题(之前使用的探查数据集可能已经被训练,之前使用的技术可能已经被优化,新工作将是一次性的,并且仍然存在未确定程度的训练测试重叠问题)。
此外,至少有些模型似乎故意在使用相同的探针和设置时不产生相同的输出(可能通过随机种子或不同版本的模型并行使用)。在这种情况下,我们获得的结果可能对其他人已经不同,这使我们的基本结论面临风险。这可能包括论文的审稿人,他们可能会合理地得出我们的报告可能不真实的结论。而且,如果开发者在我们写论文的过程中不断调整模型,那么当我们完成论文时,模型可能会发生变化(甚至基于我们自己的数据)。这不仅会使我们的工作在审查前就已经过时,还可能不正确。
这个问题可以通过“冻结”模型的给定版本并承诺保持其对研究人员的可用性来解决,但对于盈利公司来说几乎没有任何激励[²]去这样做。例如,一些流行的模型包括 Codex/code-davinci-002 已经被弃用。我们也没有公开的信息说明哪些变化会导致或不会导致新的版本号(而且很可能至少过滤器会不断更新,因为用户在尝试突破模型)。
最后但同样重要的是,考虑展示模型 X 是否做/不做 Y 的影响:
-
“模型做 Y”:没有测试和训练重叠的保证,这不一定是对模型的声明。例如,ChatGPT 被报告能够下棋(表现不好)。这对于你认为是语言模型的东西来看似乎有些意外,但如果你知道它看过大量的棋局数据——那么语言模型能预测出看起来合理的棋步序列几乎不值得惊讶。基本上,我们发现的不是语言模型的属性(这可能是一个研究发现),而是我们发现它所训练的互联网数据中包含了一些棋局数据。
-
“模型不做 Y”:通过收集模型似乎失败的案例,我们在隐性地帮助控制该模型的商业实体“修复”这些特定案例,同时进一步模糊了“突现”语言模型属性和训练中泄露的测试案例之间的界限。事实上,GPT-4 已经在 ChatGPT 的大规模测试中收集了用户交互数据,这为 Open AI 提供了数百万个免费的示例,包括用户提交的提示的“修正”回应。从长远来看,我们的工作会使下一位研究人员更难以检查下一个“封闭”模型。更糟糕的是,这将减少那些可能防止普通用户受到Eliza 效应影响的明显错误,从而增加他们对这些系统的信任(尽管它们仍然从根本上不可靠)。
总之,通过展示一个封闭模型 X 是否做/不做 Y,我们可能不会对这类模型的总体理解有所贡献,和/或加剧评估问题。
“我们展示模型 X 是(不)公平/有偏见等”:(AI 伦理)
比如说,我们以某种方式展示了封闭模型产生了某种特定类型的虚假信息或对某个身份群体的误代表(例如,关于GPT-3 中的反穆斯林偏见)。这种工作的最可能结果是,这种特定类型的输出会被迅速“修补”,可能在我们甚至发布论文之前。结果是(a)我们的辛勤工作短暂,这可能对研究者的职业生涯有影响,(b)我们积极帮助公司使他们的模型看起来更具伦理性,而他们的训练数据可能并未根本改变,因此模型可能仍然编码着可能以其他方式显现的有害刻板印象。考虑到在Dall-E 2中,性别和身份术语被随机添加以使输出看起来更具多样性,而不是显示默认的身份群体(即:白人男性)。
那么,我们是否应该从伦理角度放弃研究“封闭”模型?当然不是:对商业系统的独立分析是严格必要的。但我们需要找出在不向公司提供免费数据的情况下进行分析的方法,以免掩盖潜在问题的症状。 这里有一些可能依赖于 NLP 研究人员仍在发展的技能集的替代方案,并可能通过与 HCI 和社会科学专家的合作得到强化:
-
用户研究是否人们信任过于简化的聊天机器人回答,他们验证信息的可能性,学生是否以实际改善学习成果的方式使用它,以及促进更安全使用实践的干预措施。这类工作关注这些模型的潜在影响,鉴于已知的自动化偏见现象,任何负面发现只有通过公开用户研究才能被反驳。
-
讨论和记录实际世界中伤害的实例,这些伤害可以追溯到模型(类似于随机鹦鹉论文)。理想情况下,这些案例不仅需要修复,还需要公开承认,并希望得到赔偿。
-
对各种人口统计群体进行用户研究,查看系统在不同实际任务中是否对他们同样有效:这需要定性评估,修复可能需要为该群体获取更好的训练数据。但这类工作需要在某种程度上避免产生过多具体证据,这些证据可能被用来简单地“修补”输出。
-
不仅仅是这些系统的研究,还包括它们对社会的预期和实际影响。我们需要大量的关于系统级问题的研究,其中“修复”可能需要对商业模式和/或这些系统的展示和营销方式进行改变。一个明显的例子是那些过于危险而不能用我们当前不可靠、带有偏见、容易产生幻觉的系统来自动化的工作。例如,政策制定者是否会利用机会减少教师数量?哪些类型的学校更可能走上这条路?
“我们开发了一种比模型 X 更高效的解决方案”:
评审者可能(并且正确地)期望我们在保持类似性能水平的同时提高效率,这意味着我们继承了上述所有评估问题。此外,我们可能连“基线”训练的详细信息,包括其计算成本、投入的能源量和来源等,都不够充分。
我们确实有选择!
亲爱的 NLP 社区成员:好消息是,如果你想进行……你知道的……实际的研究语言模型,你确实有开放的选择,而且随着训练成本的降低,可能会有更多的选择。以下是一些不仅提供合理训练数据描述,还有查询工具的模型示例。
-
BLOOM(多语言 LLM),大小 560M-176B: 文档工作,ROOTS 语料库描述,训练数据搜索工具
-
GPT-Neo 模型(主要是英文 LLM),大小 125M-2.7B: Pile 语料库数据表,The PileThe Pile 数据肖像
那些可能会说“那 GPT-4 在哪里?”的评审者怎么办? 你可以这样做:
-
在你的论文提交之前,预先讨论为什么不提供例如 ChatGPT 结果作为基线。如果需要,在回复评审者时使用这篇文章中的论点。
-
提前向你计划提交的会议主席提出这一问题,询问他们是否有反对这种表面化、受流行趋势驱动的审稿政策。ACL 2023 的政策并没有涵盖这一点,因为问题在提交截止日期后才显现出来,但未来的主席可以扩展此政策。我们会关注与此相关的 ACL 会议政策讨论;如果你有任何意见,或有重大进展且希望我们将你纳入讨论——请使用这个表格。
-
作为审稿人或主席,如果你看到有人坚持使用封闭的基线——站在作者的一边并提出反对意见。
-
在你的社区中公开讨论这些问题;作为审稿人,我们可以继续教育和影响彼此,将我们的规范推向更好的方向。
另一个超出本文范围的问题,但未来可能会引发社区讨论的是,是否应该将“封闭”模型接受为常规会议提交(与“开放”工作直接竞争以获得会议接受和最佳论文奖)——或者是否是时候重新考虑“行业”轨道的角色。
我们的社区正处于转折点,你可以帮助引导新的社区规范,遵循科学而不是炒作——无论是作为作者还是审稿人。引用和研究最佳可用开放解决方案的人越多,我们就越能激励开放和透明的研究,下一步的开放解决方案也更有可能更好。毕竟,正是我们开放研究的传统使我们的社区如此成功。
附录:反对意见
训练-测试重叠和未检查的训练数据一直是一个问题,自我们开始使用 word2vec 进行迁移学习以来就是如此。为什么现在要抗议呢?
事实上,人们之前已经多次提出过这个问题。再次,即使是 OpenAI 自己也在 GPT-3 论文中花了很大篇幅讨论基准数据污染的问题。问题的陈旧性并不会使它不再成为问题;相反,它使我们成为一个有十年方法学债务的领域,这种情况没有意义,仅仅不断累积。
“大家谈论的封闭模型确实在这个任务上表现得比我的模型或开放替代方案更好,我怎么能忽视它并声称自己是最先进的呢?
不要。“最先进”的声明通常在几个月内就会过时。请更具体地说明,仅展示相对于最佳公开解决方案的改进。假设在你的任务中,ChatGPT 显然、明显优于公开的替代方案,基于你自己用自己示例的小规模测试。你不知道的是,这是否主要由于某种巧妙的模型架构或某些专有数据。在后者的情况下,你的科学发现将是……模型在与其训练数据相似的数据上表现最好。这并不完全具有革命性。
此外,问问自己:你确定你观察到的令人印象深刻的行为是纯粹的泛化结果吗?如上所述,我们无法判断你的测试示例与训练数据的相似程度。而且那些训练数据可能包括由其他在这个话题上工作的研究者提交的示例,这些示例并不是任何公开数据集的一部分。
封闭模型(The-Closed-Model-Everybody-Is-Talking-About)在这项任务中的表现确实比我的模型或开放的替代方案更好,我怎么能忽视它而不在其基础上进行构建呢?
确实,以往许多 NLP 论文的路径是这样的:拿一个现有的问题和最新的“大家都在谈论的事物”,将它们结合起来,展示相对于以前方法的改进,进行发表。问题在于,对于一个 API 访问的封闭模型,你实际上并没有“构建”在其上;顶多你是制定新的提示(并希望它们能在不同的模型版本中转移)。如果你的目标是工程,如果你只是需要一个有效的方案——这可能就足够了。但如果你追求的是对机器学习理论或方法的科学贡献——这必然会降低你工作在评审者眼中的价值。如果声称你发现了一些新的“行为”使你的解决方案得以实现,并且之前没有被注意到——你仍然需要证明这种“行为”不能通过训练数据来解释。
无论我们怎么说,大家都在讨论的封闭模型(The-Closed-Model-Everybody-Is-Talking-About)已经成为每个人心中的焦点。人们对此很感兴趣。如果我不发表相关内容,其他人会发布,并且获得比我更多的认可。
好吧,这个问题是个人选择:你想要什么样的认可,想从谁那里获得认可?在“最热门”的话题上发表可能短期内有效,但正如上面所示,如果我们仅仅跟随这些模型作为 BERT 的新的必备基准的传统 NLP 研究叙事,我们的工作将要么从研究方法学的基本原则中根本脱节,要么极其短暂,要么两者兼而有之。想象一下十年后看你的发表论文列表:你希望它更长,还是包含更多你长期以来引以为豪的东西?
有没有其他方式来研究这些模型,不会遇到这些问题?我们讨论过一些针对伦理研究的方法,也许还有其他选择。
我们不能仅仅研究那些不太可能出现在训练数据中的虚构示例吗?
首先,如果目的是了解该模型在真实数据上是如何运作的——某些非常人工的示例可能会以一些定性不同的方式处理。
其次,在这一点上,你需要确保你比那些测试 ChatGPT 几个月的其他人更具原创性。尤其是因为用于 RLHF 的数据来自与 GPT3 的互动——可能甚至是你自己的!
第三,你仍然需要知道哪些部分实际上未被看到。例如,ChatGPT 被报道用 King James Bible 风格写了一则关于粘在 VCR 里的花生酱三明治的寓言,这个例子随后在数十篇媒体文章中被分享。这确实是一个很酷的例子,但我们认为令人印象深刻的到底是什么?风格转移、对物品被卡在 VCR 里的知识、还是可行的指示?这些中的每一个令人印象深刻的程度取决于训练数据中包含了什么。甚至将这些东西结合在一起的能力的印象深刻程度仍然取决于训练中看到了哪些“技能”的组合,以及这是否实际上是纯语言模型行为,而不是某些管道组件的组合。
我们尝试重现那个答案,但不小心输入了“CVR”而不是“VCR”。结果非常有启发性。我们得到了一些通用的指示,这些指示可能来自类似 WikiHow 的内容:如何擦掉电器上的粘性物质。显然,这在这里没有用处:三明治包括一大块面包,你需要用手而不是擦拭的方式将其移除。但最有趣的是,模型后来“承认”它不知道“CVR”是什么!(实际上,大型语言模型本质上并不“知道”任何关于世界的事情)。然后,当被提示“VCR”时,显然保持对话一致性的指令覆盖了它对‘VCR’可能说的任何内容……所以我们得到了相同的错误指示。
顺利进行的是 King James 风格的释义。但很难想象释义不是一个预期的且经过训练的“能力”,或者这种风格在大型基于网络的语料库中没有得到充分代表——哦,你们这些信心不足的人。它效果好吗?是的。它是一个神奇的“涌现”属性吗?不是。我们可以开发另一个释义系统并有意义地将其与这个系统进行比较吗?也不能。这就是它对 NLP 研究变得不相关的地方。那种不公开且合理可重复的东西不能被视为必要的基准。
说明
¹ 这篇文章的工作开始于一段时间前,与长期主义或GPU 资源民主化请求无关。
² 实际上,确实有激励措施促使这些公司关闭和弃用其模型的旧版本,以(a)减少攻击面,(b)限制技术债务。这些对于商业实体来说是合理的担忧,但它们与模型作为科学研究对象的本质上存在矛盾。
接近中心性与社区:使用 Python 和 NetworkX 分析社交网络 — 第三部分
了解社交网络分析中的社区和接近中心性,使用 Python 和 NetworkX
·发表于 Towards Data Science ·阅读时间 6 分钟·2023 年 6 月 26 日
–
在 第二部分,我们通过绘制 Smashing Pumpkins 和 Zwan 成员之间的关系图,扩展了对社交网络分析的理解。然后,我们考察了度中心性和中介中心性等指标,以调查不同乐队成员之间的关系。同时,我们讨论了领域知识如何帮助我们理解结果。
在第三部分,我们将涵盖接近中心性的基础知识及其计算方法。然后,我们将展示如何使用 NetworkX 计算接近中心性,以比利·科根(Billy Corgan)的网络为例。
获取生成此图的代码,请访问我的 GitHub。 ⭐️ 以便于参考。
开始之前…
-
你有基本的 Python 知识吗?如果没有,从这里开始。*
-
你熟悉 社交网络分析中的基础概念,比如节点和边吗?如果不熟悉,从这里开始*。*
-
你对 度中心性 和 中介中心性 感到熟悉吗?如果不熟悉,从这里开始。*
亲密中心性与社区
亲密中心性
亲密中心性是社会网络分析中的一种度量,量化了一个节点在网络中与所有其他节点的最短路径距离。
亲密中心性关注的是网络中信息或资源流动的效率。其理念是,具有较高亲密中心性的节点能够更快、更高效地到达其他节点,因为它们与网络中其他部分的平均距离更短。
节点的亲密中心性是通过从该节点到网络中所有其他节点的最短路径距离(SPD)之和的倒数来计算的。
亲密中心性 = 1 / (从节点到所有其他节点的 SPD 之和)
更高的值表示网络中信息流动的中心性和效率更高。
计算亲密中心性
让我们用一个简单的八节点网络来解析一下。
- 计算节点 A 到所有其他节点的最短路径距离(SPD)。在我们的示例中,我们将使用简单的示例距离。实际中,这将使用诸如[广度优先搜索](https://www.hackerearth.com/practice/algorithms/graphs/breadth-first-search/tutorial/)或戴克斯特拉算法的最短路径算法来完成。
2. 计算从节点 A 到所有其他节点的最短路径距离之和。
3. 应用亲密中心性公式。
亲密性和社区
我们可以将社区视为在自身内部更密集连接的节点组,相比之下与组外节点的连接较少。社区体现了网络中粘合的子组或模块的概念,其中同一社区内的节点彼此有更强的连接。社区的特征是社区内部连接密集而社区之间连接相对稀疏。
获取在我的 GitHub上生成此图的代码。⭐️ 以便于参考!
当我们考虑乐队 Smashing Pumpkins 和 Zwan 的成员时,很容易想象这些乐队是如何通过共享的成员相互连接的。这展示了每个乐队内部成员之间的组内连接,以及两个乐队之间的组间连接。
尽管接近中心性衡量了单个节点的重要性和信息流动效率,但社区捕捉了具有密集连接的紧密子群体。它们共同有助于理解信息流动的动态和网络的组织结构。
让我们讨论几种使用接近中心性和社区来解读网络动态的方法。
- 社区内的接近中心性
属于同一社区的节点通常在社区内具有较高的接近中心性值。这表明社区内的节点彼此紧密连接,并且在最短路径距离方面可以迅速到达对方。社区内较高的接近中心性反映了子群体内信息流动和沟通的高效性。
获取生成此图的代码在 我的 GitHub。⭐️ 以便于参考!
2. 用接近中心性桥接社区
连接不同社区或在社区之间充当桥梁的节点可能具有比单个社区内节点更高的接近中心性。这些节点在连接分离的社区、促进它们之间的沟通和信息流动中发挥着至关重要的作用。
获取生成此图的代码在 我的 GitHub。⭐️ 以便于参考!
3. 使用接近中心性进行社区层级分析
接近中心性也可以在社区层面上用于分析网络中社区的重要性。通过聚合社区内节点的接近中心性值,可以评估社区内信息流动的整体效率。具有较高平均接近中心性的社区可能被认为在网络中更具中心性和影响力,因为它们能够更有效地访问和传播信息。
获取生成此图的代码在 我的 GitHub。⭐️ 以便于参考!
接近中心性衡量了单个节点的重要性和信息流动效率,而社区捕捉了具有密集连接的紧密子群体。它们共同有助于理解信息流动的动态和网络的组织结构。
在考虑 Billy Corgan 的影响范围时,接近中心性可以提供 Smashing Pumpkins 和 Zwan 的成员如何直接和间接影响 Billy Corgan 网络中其他音乐家的见解。我们可以用社区的概念来描述每个乐队,也可以用来描述两个乐队的总和。实际上,1990 年代的另类摇滚音乐圈非常庞大,当我们将更多乐队添加到网络中时,会出现更多的社区。
Billy Corgan 大约在 1991 年 — 由 Barb Vest, CC BY-SA 4.0
使用 Python 和 NetworkX 计算接近中心性
- 就像 我们在第二部分做的那样,我们将创建一个函数来生成每个乐队所有成员的组合。
2. 接下来,我们定义每个乐队,并应用函数生成元组列表。然后,我们将列表合并,并使用列表推导式去除重复项。
3. 现在我们可以绘制图形了。
它应该看起来像这样:
4. 最后,让我们计算接近中心性并分析这些值。
输出应该看起来像这样:
那么我们对这些数值能说些什么呢?
-
Billy Corgan 和 Jimmy Chamberlin 的接近中心性为 1.00,表明他们在迅速联系其他成员方面是最中心的成员。
-
James Iha、Katie Cole、D’arcy Wretzky、Melissa Auf der Maur、Ginger Pooley、Mike Byrne 和 Nicole Fiorentino 的接近中心性值相同,为 0.785714\。这表明这些成员紧密相连,可以快速相互联系。
-
Paz Lenchantin、David Pajo 和 Matt Sweeney 的接近中心性略低,为 0.611111\。这表明他们在联系其他成员方面可能不如前一组,但他们在网络中仍然相对较好地连接。
由于我们仍在处理相对简单的网络,这些结果并没有揭示比计算 Billy Corgan 网络的度中心性和中介中心性时更多的信息。在第四部分,我们将通过引入更多乐队和音乐家到网络中来增加复杂性。作为奖励,我们将介绍一些 Matplotlib 的高级技巧,使你的 NetworkX 图形更具吸引力!
如果你想要 完整注释的 Python 教程,请访问我的 GitHub!
👩🏻💻 Christine Egan | medium | github | linkedin
[## 通过我的推荐链接加入 Medium - Christine Egan
成为 Medium 会员后,你的会员费用的一部分将用于支持你阅读的作者,同时你可以获得对每个故事的全面访问权限……
medium.com](https://medium.com/@christineegan42/membership?source=post_page-----c19feeb38223--------------------------------)
云优先的数据科学:分析和建模数据的现代方法
使用云端进行数据科学工作流程每一步的指南
·发布于 Towards Data Science ·阅读时长 11 分钟·2023 年 11 月 28 日
–
照片由 Myriams-Fotos 提供,发布在 Piaxabay
数据科学是全球增长最快的行业之一,利用现代前沿技术来改善我们使用数据的方式。然而,如果你曾经从事数据科学工作,你可能知道有一天你会不可避免地面对一个 Excel 表格。Excel 没有问题,只是它不是你在一个现代化行业中期望使用的工具。
许多组织已经开始利用现代云基础设施,但并没有充分利用它。因此,许多数据科学家发现自己在从云数据仓库提取数据后,只能在本地系统上训练模型。这样做也没有问题,但如果我们能够将整个数据科学工作流程都搬到云端,那将会怎样?实际上,我们可以做到!
从数据清理到模型部署,有一个基于云的工具可以帮助你现代化你的工作流程。在这篇文章中,我将逐步介绍数据科学工作流程的每一个步骤,展示如何将其迁移到云端,并在过程中提供一些示例。如果你已经现代化了工作流程的一部分,可以随意跳过,但如果你想获得 100%云端的数据科学体验,请继续关注!
云端的数据收集与存储
你可能已经对存储数据在云中的好处很熟悉,但以防你没有听说过:这真的很棒!将你的数据存储在云中可以让你从任何有互联网连接的地方访问数据,轻松与其他云服务集成,根据需要扩展存储容量,创建备份以便恢复,还有许多其他非常有用的功能。
无论你是否需要数据仓库、数据湖还是对象存储,如果你想将数据部署到其他应用程序,你的数据必须存储在某个地方。有很多提供云数据存储的服务;一些更受欢迎的包括:
-
AWS S3
-
Azure Blob 存储
-
Google Cloud 存储
-
Hadoop
-
Snowflake
这甚至不是云数据存储服务的完整列表,但如果你从事数据科学工作,那么很有可能你会最终使用到这些服务中的某些,甚至是全部。每种服务和云存储类型都有其优点和缺点,因此你应该选择你认为最适合你项目的那一个!
无论你使用哪种服务进行云数据存储,收集和存储数据的过程都有相同的一般步骤。你通常需要在服务提供商处注册账户,创建存储容器或桶,然后你就可以上传数据了。根据你使用的服务,这可以通过网络界面、命令行工具、SDK 或 API 完成。
存储数据在云中的最佳实践之一是设置权限和访问控制。如果你在做一个个人项目,这一点不是特别相关,但如果你在团队中工作,这就至关重要了。管理你的数据也很重要,包括数据结构、元数据、更新频率和保留。加密也可以确保你的数据安全和隐私,创建备份将保护你免于丢失任何进展,并提高数据的可用性!
云中的数据清洗和转换
既然你的数据已经存储在云中,那么继续在云中执行所有必要的清洗步骤是有意义的!这样做的好处与上述讨论的类似;可以从任何地方访问、可扩展性、易于集成等,但你还会得到一个额外的好处:无需下载你的云数据、清洗它再重新上传。如果操作得当,工作流程应该是非常流畅的!
这里有一些你可以用来进行云数据清洗和转换的工具示例,我将保持与上面部分中列出的五个工具一致,但请记住,还有很多其他工具可供选择!
-
AWS Glue
-
Azure 数据工厂
-
Google Cloud Dataflow
-
Apache Hive
-
Snowflake 数据集成
一些服务通过提供 ETL(提取、转换、加载)前后的数据样本,使得清洗过程变得简单。还有一些工具提供“无代码”体验,你可以通过拖放命令来操作,而其他工具则提供高度可定制的编码体验。你可以根据自己的偏好选择!一般来说,这些工具可以与多个云存储提供商兼容,因此整个过程非常灵活。
关于在线数据转换工具,我最喜欢的一点是其可视化组件,大多数工具都会有一个界面,逐步展示数据转换过程,如下所示:
图片来自 Google Cloud Dataflow 文档(CC BY 4.0)
我的经验是,当你向经理或观众展示数据转换时,有一个这样的可视化图像会大大简化解释过程。展示和解释原始的 python 代码可能相当困难,但逐步解释每一步发生的事情则容易得多。
如果你在 Snowflake 中进行这个过程,可能会是这样:一旦你的账户设置好并且数据加载到 Snowflake 中,探索你的数据集——你可以查看原始数据或使用其 Snowsight 工具更好地了解数据的结构和特征。一旦你了解了数据的样子,你可以使用内置工具或 SQL 轻松清理数据。然后根据你的项目需求,你也可以添加新的列以便进一步分析。例如,如果你在对客户评价进行情感分析,你可以写一个这样的快速脚本:
-- Sentiment Analysis
CREATE OR REPLACE TABLE sentiment_scores AS
SELECT
product_id,
customer_id,
review_text,
CASE
WHEN sentiment_score > 0.6 THEN 'Positive'
WHEN sentiment_score < 0.4 THEN 'Negative'
ELSE 'Neutral'
END AS sentiment
FROM your_dataset;
-- Aggregation
CREATE OR REPLACE TABLE aggregated_sentiments AS
SELECT
product_id,
AVG(sentiment_score) AS avg_sentiment
FROM sentiment_scores
GROUP BY product_id;
然后,一旦数据被清理和/或转换,你可以将其保存为新的数据集,并继续进行下一步!
基于云的数据分析
现在我们已经上传、清理并准备好数据进行分析了!我们有很多分析选项,从笔记本到仪表板,但无论你的偏好是什么;都有一个选项可以让你的工作流程保持在云端。
如果你停留在我们提到的五大云服务提供商的生态系统中,你的选择包括:
-
AWS Redshift
-
Azure Synapse Analytics
-
Google BigQuery
-
Apache Spark
-
Snowflake 数据仓库
市面上还有许多其他工具,但这五个应该可以完成工作,特别是当你的清理数据已经存在于各自的平台上时。根据你选择的工具,你将拥有广泛的数据分析能力,就像清理一样,无论你对 python 或 R 的熟练程度如何,你都可以有许多不同的方法。和往常一样,你应该使用你最喜欢的工具以及与项目兼容的工具。
根据你的项目复杂性,使用这些工具进行数据分析可能相当简单。例如,在 BigQuery 中,你可以编写自定义 SQL 查询来分析数据,此外你还可以快速生成视觉效果并进一步探索数据。如果你喜欢在笔记本上工作,你也可以将数据直接从 BigQuery 发送到 Google Colab 笔记本中进行分析,如果决定进行更改,你还可以将其作为单独的数据集发送回来。
现在你的数据已经被分析,你可能对如何展示数据有了一个好的想法——幸运的是,接下来的步骤,可视化,也可以完全在云端完成!
云端数据可视化
你可能会在本文中注意到一个主题,那就是每一步工作流的集成都非常简单。我们已经上传了数据,清理了数据,分析了数据,现在我们准备好进行可视化,整个过程都没有下载任何文件!
有许多工具可以用来创建令人惊叹的云端数据可视化。我们跟踪的五个云平台每个平台都有自己的一套可视化工具,但以下是一些可以轻松与我们的数据管理系统集成的其他工具:
-
Tableau Online
-
Power BI
-
Looker
-
Qlik Sense
-
Plotly Dash
根据你的需求,你可以轻松创建一个干净、信息丰富的视觉效果或创建一个交互式仪表板。例如,Tableau Online 还有一个很棒的创作者社区,分享他们的可视化作品。查看他们在 Tableau Public 上的今日视觉一直是我一些视觉效果的灵感来源。
过程非常简单,你只需将所选的可视化工具与所选的数据存储工具连接,然后你就可以在网上创建令人惊叹的视觉效果!这些工具通常会有令人惊叹的视觉库,既有信息性又视觉吸引!你通常还可以与这些视觉效果互动,并在你的云托管数据更新时获得实时更新。如果你愿意,你也可以将你的视觉效果嵌入其他网页应用或网站;整个过程非常可定制。
基于云的机器学习和建模
这可能是数据科学中利用云计算最有意义的领域。训练和测试模型对计算机的要求很高,那么为什么不将这些工作转移到专用服务器上呢?这只是云端机器学习(ML)和建模的一些优势之一。
云平台通常还会提供预构建模型,以便于你只需快速获取模型,如果你不是机器学习专家,还有 AutoML 服务会给出建议——全部无需编写一行代码。当然,对于机器学习工程师来说,还有高度可定制的应用程序,提供超参数调整和 MLOps 功能,以确保你的模型完全符合你的规格。
以下是一些你可以用于机器学习和建模的云工具示例:
-
AWS SageMaker
-
Azure Machine Learning
-
Google Cloud AI Platform
-
Databricks
-
Kubeflow
如果你喜欢为自己的模型编写代码,SageMaker 的过程大致如下。首先,你将从 S3 加载数据,然后创建 SageMaker 笔记本以编写代码。SageMaker 内置了像 XGBoost 这样的算法,但你也可以使用经典的 Scikit-Learn 库创建自定义模型。你可以在代码中指定模型的算法并调整超参数。当你准备好训练和测试模型时,SageMaker 将处理所有计算资源——这将为你节省大量时间。这个过程最酷的部分之一是,一旦完成,你可以通过 API 使训练后的模型可访问,并在任何你想要的地方使用该模型!
如果你不喜欢编写代码或需要一个工具为你建议模型,Azure Machine Learning 有一个叫做 Azure AutoML 的工具,非常适合你。与上述示例类似,你将从相应的数据仓库加载数据,但在建模部分,你可以让 Azure 为你建议一个模型,或从他们的算法库中选择以创建自己的模型。这个过程高度可定制,但仍然可以通过无代码界面完成。
无论你想如何创建机器学习模型,可能都有适合你的云端工具。同时,无论你使用哪个工具,它都有很大可能与我们在过程早期讨论过的其他工具集成。
在云上部署数据科学解决方案
现在我们已经训练了我们的模型,可以利用云将我们的见解和算法转化为现实世界的解决方案。在这里,你可以真正看到使用云的好处,因为你的解决方案将可以从任何地方访问,并且可以在大规模上扩展以回答各种问题。使用云还意味着你的训练模型可以继续学习和改进,当你从模型中获得结果时,你可以上传这些结果,清理它们,并使用我们在整篇文章中讨论的方法进行可视化。
如你所见,我非常喜欢这种云工作流,以及所有内容如何完美集成在一起。
部署你的模型有很多选项,但以下是几个值得考虑的:
-
Kubernetes (AWS, Azure, GCP)
-
AWS Lambda
-
Azure App Service
-
Google Cloud App Engine
-
Heroku
根据你的数据科学解决方案,你选择的工具会有所不同。例如,如果你正在设计一个使用你在线构建的模型的网络应用,你可以使用 Kubernetes 在该网络应用上部署和改进你的解决方案。过程将从将你的应用和模型打包到一个 Docker 容器中开始,Docker 容器是一个包含运行应用所需的一切的可执行包。你可以将这个容器存储在 Kubernetes 可以访问的容器注册中心(GCP、AWS 和 Azure 都有!)中。然后你可以在云中创建一个集群,并编写一个简单的配置文件(YAML)来告诉 Kubernetes 如何从 Docker 容器中运行你的网络应用。
一旦一切按你的喜好配置完成,你就可以开始将你的网络应用运行到所需的用户数量!你可以获得关于你的模型的实时反馈和分析,这些都可以存储在云端并进行可视化。无论你使用什么 Kubernetes 服务,都能顺利运行并处理所有计算任务,你还能向模型中添加额外的数据以持续改进它!
那真是很多内容——这无疑是过程中的最复杂步骤。不幸的是,如果你想要一个可视化的指导,你还可以查看mildlyoverfitted 的这段 youtube 视频,他很好地展示了通过 Kubernetes 进行部署的过程。
其他考虑事项
尽管我在整篇文章中讨论了将工作流程迁移到云端的所有好处,但在你完全转向 100%基于云的工具之前,还有一些事项需要牢记。
首先需要知道的是,如果你完全依赖基于云的工具,可能会变得非常昂贵。虽然有很多免费的选项,但如果你扩展你的工作,迟早会遇到存储或计算能力的大额账单。还有一个问题是依赖于互联网连接,你的工作流程严重依赖互联网的质量。一些系统也会出现故障,从而打断你的工作和生产力。在系统或服务发生变化或突然终止的情况下,重要的是要多样化你的技能,这样你仍然可以继续工作。
然而,这些缺点并不适用于所有基于云的工具,重要的是要记住这些事项,以便你能做出明智的决定,关于你希望如何完成工作。
结论
我们已经从上传数据到部署机器学习模型,并且在整个过程中使用了现代基于云的工具。我认为这非常棒!我希望阅读这篇文章能激励你现代化你的某些或所有工作流程,或者即使没有,我也希望我至少展示了在云上进行数据科学是可能的。现代行业的现代工作流程——这感觉很对!
本文仅概述了可能的内容,如果你有兴趣了解更多关于这个话题的信息,以下是一些你可以查看的资源:
感谢阅读!
想要更多我的内容?
-
通过使用我的推荐链接在 Medium 上支持我的写作
-
查看我在benchamblee.blog上的《用 Python 进行数据科学》指南
致力于数据科学家的聚类分析
数据科学家如何进行和执行聚类分析的逐步案例研究
·
关注 发表在 Towards Data Science ·11 分钟阅读·2023 年 2 月 23 日
–
图片由 israel palacio 提供,Unsplash
回到我本科统计学的时期,有一天特别突出。那是多变量分析模块的第一天。这个课程当时是新的,我们惊讶地发现教授决定做些不同的事情。教授没有讲解学期议程,而是关掉灯宣布今天我们将以不同的方式学习这个模块——通过观看《数字追凶》第一集。
该系列剧集关注 FBI 特工唐·埃普斯及其弟弟查尔斯·埃普斯,一位利用数据科学追踪最狡猾罪犯的杰出数学家。更具体地说,在首集里,查尔斯利用聚类分析来找出罪犯的起源地。不用说,我们都被吸引住了。
快进到今天,我经历了从电子商务零售到咨询和游戏的不同行业,所有业务利益相关者的一个共同愿望是对他们的客户或用户进行细分(聚类)。看来,聚类引起了数据科学家和业务利益相关者(以及观众)的兴趣。
在这篇文章中,我为希望将聚类分析加入工具箱、并朝着获得首个数据科学工作迈出一步的有志数据科学家创建了一个指南。该指南将分为两个部分:
-
聚类介绍: 理解聚类分析的三个基本构建块
-
逐步案例研究:R 中的聚类: 数据可视化、数据处理、计算相似度矩阵(距离)、选择簇的数量和描述结果
1. 聚类介绍
图片来源:Hans-Peter Gauster via Unsplash
1.1. 聚类的目标是什么?
聚类用于根据定义的一组特征将实体分组。例如,我们可以使用聚类来根据客户的购物行为(特征)对客户(实体)进行分组。
1.2. 聚类是如何工作的?
在监督式机器学习技术(如线性回归)中,算法在带标签的数据集上进行训练,目标是最大化对新未标记数据的预测准确性。例如,可以在包含房屋特征(如面积、卧室数量等)及其对应标签(销售价格)的带标签数据集上训练算法,目标是创建一个模型,该模型可以根据房屋的特征高准确度地预测新房屋的销售价格。
聚类,另一方面,是一种无监督的机器学习技术。数据集没有标签(因此称为无监督)。相反,目标是以簇分配的形式创建一个新标签。每次簇分析都会有所不同,但在每种情况下,主要有三个构建块或步骤:
-
创建一个每行唯一的数据集,以便对所需聚类的实体进行操作。因此,如果你想对客户进行聚类,每一行必须代表一个不同的客户。对于这些行中的每一行,你将拥有描述特定特征的属性(列),例如过去一年中的收入、最喜欢的产品、距离上次购买的天数等。
-
计算数据集中每一行与所有其他行的相似性(基于可用的属性)。因此,我们将得到第一行和第二行之间、第一行和第三行之间、第二行和第三行之间等等的相似性值。最常用的相似性函数是距离。我们将在下一个章节的案例研究中更详细地讨论这些,因为它们通过示例解释得更清楚。
-
将相似性矩阵输入到聚类算法中。R 和 Python 中有许多可用的聚类算法,如 k-means、层次聚类和 PAM。聚类算法的简单目的就是将观察值分配到簇中,以便簇内的观察值尽可能相似,而不同簇之间的观察值尽可能不同。最后,你将获得每一行(观察值)的簇分配。
2. R 中的逐步聚类案例研究
图片来源于Chris Lawton在Unsplash
尽管我没有找到在系列节目“Numb3rs”首集中使用的数据集,但我认为使用类似的数据集进行案例研究是合适的,所以我选择了US Arrests 数据集,从datasets包中加载(属于 R 中的base库)。
数据集包含 50 行和 4 个属性。每一行是一个美国州(实体)。这四个属性描述了每个州的以下特征:
-
谋杀: 1975 年每 10 万人中的谋杀逮捕人数
-
袭击: 1975 年每 10 万人中的袭击逮捕人数
-
强奸: 1975 年每 10 万人中的强奸逮捕人数
-
城市人口: 1975 年生活在城市地区的人口百分比
-
我们还有州名作为行名。这不会被用作聚类算法的输入。
##############################
# Loading libraries
##############################
library("dplyr") # summarizing data
library("ggplot2") # visualization
library("cluster") # gower distance and PAM
library("ggalluvial") # alluvial plots
##############################
# Loading the data set
##############################
data("USArrests")
##############################
# Examine data set
##############################
head(USArrests) # head -> header
R 中 USArrests 数据集概述 [作者提供的图片]
2.1. 数据可视化
从下面的箱线图来看,所有四个属性似乎都大致对称(UrbanPop 略微右偏,Rape 略微左偏)。如果某个属性严重偏斜或存在异常值,我们可以考虑应用不同的转换(例如对数转换)。但我们的数据集不需要这样做。
##############################
# Box plot for each attribute
##############################
USArrests %>%
select(c(Murder, Assault, Rape, UrbanPop)) %>%
boxplot(.,
boxwex = 0.7,
alpha = 0.2,
col = c("red3", "lightgreen", "lightblue", "purple"),
horizontal = TRUE
)
美国逮捕数据集每个属性的箱线图 [作者提供的图片]
你还可以看到Assault属性的值远高于其他三个。在聚类中,将所有属性标准化到相同尺度是一个良好的实践。这是为了确保大尺度的属性不会过度贡献(Assault在几百范围,而其他三个属性在几十范围)。幸运的是,我们将使用的相似度函数会处理这一点,因此我们在此阶段不需要进行任何重标定。
2.2. 计算相似度矩阵
在我们的案例研究中,我们将使用 R 中的daisy函数(cluster 包)中的 Gower 距离来计算相似度矩阵。更具体地说,Gower 使用不相似度作为距离函数,但概念是相同的(我们最小化不相似度而不是最大化相似度)。
Gower 是少数几种可以处理混合型数据(数值和分类属性)的距离函数之一。另一个优点是 Gower 将所有距离缩放到 0 和 1 之间(高尺度的属性不会过度贡献)。
因此,对于我们有 50 行(州)的数据集,我们将拥有一个 50x50 的矩阵,包含 1225 个独特的值(n(n-1)/2*)。对角线是不需要的(一个州与自身的距离),上三角和下三角是相同的(矩阵是对称的)。
##############################
# Get distance
##############################
gower_dist <-
USArrests %>%
select(c(Murder, Assault, Rape, UrbanPop)) %>%
daisy(.,metric = "gower")
gower_dist
Gower 距离矩阵快照 [作者提供的图片]
2.3. 选择簇的数量
在我们的案例研究中,我们将使用 PAM(Partitioning Around Medoids)聚类算法,具体来说是pam函数(cluster 包)。
PAM(以及所有其他 K-媒介算法)是 k-means 的一个稳健替代方案,因为它使用媒介作为簇中心,而不是均值。因此,相比 k-means,算法对噪声和异常值的敏感度较低(但不是免疫的!)。
首先,我们需要选择簇的数量。确定簇数量的方法非常简单。我们将使用在前一步计算的 Gower 相似度矩阵运行 PAM 算法,并在每次运行中选择不同的簇数量(从 2 到 10)。然后,我们将计算每次运行的平均轮廓宽度(ASW)。我们将使用以下函数来完成这一步。
##############################
# Function to compute ASW
##############################
get_asw_using_pam <- function(distance, min_clusters, max_clusters) {
average_sil_width <- c(NA)
for (i in min_clusters:max_clusters){
pam_fit <-
pam(
distance,
diss = TRUE,
k=i)
average_sil_width[i] <- pam_fit$silinfo$avg.width
}
return(average_sil_width)
}
############################
## Get ASW from each run
############################
sil_width <- get_asw_using_pam(gower_dist, 2, 10)
ASW 表示每个观察值与其当前聚类相比与最近邻聚类的匹配程度。其范围从-1 到 1,较高的值(接近 1)表示更好的聚类结果。我们将使用轮廓图(y 轴上的 ASW 和 x 轴上的聚类数)来直观地检查我们聚类的有前景的候选者。
##############################
# Visualize Silhouette Plot
##############################
silhouette_plot <- sil_width %>% as.data.frame()
names(silhouette_plot)[names(silhouette_plot) == '.'] <- 'sil_width'
silhouette_plot %>%
mutate(number = c(1:10)) %>%
filter(number > 1) %>%
ggplot(aes(x = number , y = sil_width)) +
geom_line( color="turquoise3", size=1) +
geom_point(color="darkgrey",fill = "black", size=3) +
scale_x_continuous(breaks = seq(2, 10, 1) ) +
ylab("Average Silhouette Width") +
xlab("No of Clusters") +
theme_classic()
使用 PAM 和 Gower 距离的轮廓图 [Image by the author]
ASW 最高的方案是两个聚类,在急剧下降后,我们有三个和四个聚类,然后 ASW 再次急剧下降。我们可能的候选方案是这三个选项(2、3 和 4 个聚类)。我们将把每个运行的聚类分配保存为 USArrests 数据集中的三个新属性(cluster_2、cluster_3 和 cluster_4)。
## Set the seed of R's random number generator
## which is useful for creating reproducible simulations
## like in cases of clustering assignment
set.seed(5)
##############################
# Saving PAM results
##############################
pam_2 <- pam(gower_dist, diss = TRUE, k= 2 )
pam_3 <- pam(gower_dist, diss = TRUE, k= 3 )
pam_4 <- pam(gower_dist, diss = TRUE, k= 4 )
##############################
# Adding assignment as columns
##############################
USArrests <-
USArrests %>%
mutate(cluster_2 = as.factor(pam_2$clustering)) %>%
mutate(cluster_3 = as.factor(pam_3$clustering)) %>%
mutate(cluster_4 = as.factor(pam_4$clustering))
2.4. 描述聚类方案
由于聚类是一种无监督技术,因此重要的是要记住,它本质上是加了“兴奋剂”的探索性数据分析(EDA)。因此,与有监督技术不同,我们无法评估我们的聚类方案的准确性(数据集中没有用于比较的标签)。相反,您的聚类分析的“好坏”将基于不同的标准来评估:
结果聚类是否以反映业务利益相关者所考虑的方面的方式进行分离?
因此,最终的决定是基于除最高 ASW 方案之外的其他因素。它是通过检查每个聚类方案的特征来做出的。你应该始终选择对业务更具可操作性的聚类,而不是仅仅选择 ASW 最高的那个。
首先,我们希望探讨不同聚类方案之间的关系。我们将使用全 uvial 图来可视化在三个运行之间观察值(状态)的流动。
##############################
# Alluvial plot
##############################
aluv <-
USArrests %>%
group_by(cluster_2, cluster_3, cluster_4) %>%
summarise(Freq = n())
alluvial(
aluv[,1:3],
freq=aluv$Freq,
border="lightgrey",
gap.width=0.2,
alpha=0.8,
col =
ifelse(
aluv$cluster_4 == 1, "red",
ifelse(aluv$cluster_4 == 2, "lightskyblue",
ifelse(aluv$cluster_4 == 3 & aluv$cluster_3 == 2, "lightskyblue4",
ifelse(aluv$cluster_4 == 3 & aluv$cluster_3 == 2, "purple",
"orange")))),
cex=0.65
)
PAM 不同运行的聚类分配的全 uvial 图 [Image by the author]
在上述全 uvial 图中,每一列代表一个不同的运行,不同颜色的带子表示固定的状态块,当它们在三个聚类运行之间分裂或重新分配时。
例如,红色带子代表阿拉巴马州、乔治亚州、路易斯安那州、密西西比州、北卡罗来纳州、南卡罗来纳州和田纳西州(见下方代码)。这些州在两个和三个聚类的运行(cluster_2 和 cluster_3)中与蓝色带子的州分配到了相同的聚类(“1”),但在四个聚类的运行(cluster_4)中被分开。
##################################################
# Filter for the red ribbon (cluster_4 = 1)
##################################################
USArrests %>% filter(cluster_4 == "1")
在全 uvial 图中红色带子的状态 [Image by the author]
接下来,我们希望更好地理解每个聚类的特征以及它们在三个解决方案之间的差异。
##################################################
# Summarizing (average) attributes, 2 clusters
##################################################
USArrests %>%
group_by(cluster_2) %>%
summarise(
count = n(),
mean_murder = mean(Murder),
mean_assault = mean(Assault),
mean_rape = mean(Rape),
mean_urbanpop = mean(UrbanPop)
)
##################################################
# Summarizing (average) attributes, 3 clusters
##################################################
USArrests %>%
group_by(cluster_3) %>%
summarise(
count = n(),
mean_murder = mean(Murder),
mean_assault = mean(Assault),
mean_rape = mean(Rape),
mean_urbanpop = mean(UrbanPop)
)
##################################################
# Summarizing (average) attributes, 4 clusters
##################################################
USArrests %>%
group_by(cluster_4) %>%
summarise(
count = n(),
mean_murder = mean(Murder),
mean_assault = mean(Assault),
mean_rape = mean(Rape),
mean_urbanpop = mean(UrbanPop)
)
按聚类运行的描述性统计 [Image by the author]
两个聚类的运行:
-
聚类“1”在所有犯罪类别中的平均逮捕次数较高
-
平均城市人口百分比没有明显差异
使用三个簇的运行:
-
这三个簇似乎分隔得很好
-
与使用两个簇的运行中的高(簇“1”)和低(簇“2”)逮捕相比,我们现在有高(簇“1”)、中等(簇“2”)和低逮捕(簇“3”)
-
簇“3”也有明显更低的平均城市人口百分比
使用四个簇的运行:
-
分隔不如预期清晰
-
使用三个簇运行得到的中等(簇“2”)和低(簇“3”)簇保持不变(仅分别重命名为“3”和“4”)
-
使用三个簇的运行中的高(簇“1”)被分成了两个簇。簇“1”具有明显较低的平均城市人口百分比,并且强奸和攻击的逮捕率较低
根据簇分析的需求,我们会选择合适的聚类解决方案。记住,不是 ASW 最高的那个,而是对业务利益相关者更具影响力的那个(可能是在 3 个和 4 个簇之间选择)。
摘要
🚀🚀 完成最后一步后,我们已经到达了指南的终点。下面,你还可以找到步骤的快速总结:
✅ 创建一个数据集,其中每一行代表一个实体,属性反映你聚类感兴趣的方面
✅ 可视化数据以检查偏斜、离群值和缩放问题
✅ 使用轮廓图选择簇的数量
✅ 检查每个聚类解决方案的特征,并选择对业务更具操作性的那个(更符合业务目标)
在下面的链接中,你还可以找到一个关于使用数据科学技术和最佳实践在现实商业场景中完成客户簇分析的更深入的免费 PDF 指南。 👇
我是一名拥有 7 年以上分析经验的数据科学家,目前在英国伦敦的一家游戏公司工作。我的…
www.aspiringdatascientist.net](https://www.aspiringdatascientist.net/community?source=post_page-----5ad88b700a1a--------------------------------)
保持联系!
如果你喜欢阅读这篇文章并想了解更多,请不要忘记订阅,以便直接将我的故事发送到你的收件箱。
参考文献
R 文档和数据集来自 R 项目,并且是 GPL 许可证。OpenIntro 文档是 Creative Commons BY-SA 3.0 许可证。