Kaggle 笔记本开发指南(三)
原文:
annas-archive.org/md5/24dfa97e36ead23596f0ef3b74ce3f05译者:飞龙
第九章:你能找出哪部电影是深度伪造的吗?
在前几章中,我们探讨了各种数据格式:表格、地理空间、文本、图像和声学,同时使用 Kaggle 数据集,了解 shapefile 可视化,构建图像或文本分类模型,以及声学信号分析。
在本章中,我们将介绍视频数据分析。我们将首先描述一个 Kaggle 比赛,深度伪造检测挑战。这个挑战要求参与者对哪些视频是人工生成以创造逼真的虚假内容进行分类。接下来,我们将快速探索最常用的视频格式,然后介绍用于数据分析的两个实用脚本。首先是一个具有操作视频内容功能的实用脚本,即读取、从视频中可视化图像和播放视频文件。其次是一个具有身体、面部和面部元素检测功能的实用脚本。我们将继续从竞赛数据集中的元数据探索,然后应用所介绍的实用脚本分析竞赛数据集中的视频数据。
简而言之,本章将涵盖以下主题:
-
深度伪造检测挑战比赛的介绍
-
用于视频数据操作和视频数据中目标检测的实用脚本
-
竞赛数据集的元数据分析和视频数据分析
介绍比赛
在本章中,我们考察了来自知名 Kaggle 比赛深度伪造检测挑战(DFDC)的数据。该比赛在参考文献 1中有详细描述,于 2019 年 12 月 11 日开始,并于 2020 年 3 月 31 日结束。它吸引了 2,265 个团队,共有 2,904 名参与者,他们共同提交了 8,951 份作品。竞争者争夺总额为 100 万美元的奖金池,其中一等奖为 50 万美元。
该活动是 AWS、Facebook、Microsoft、人工智能媒体完整性指导委员会的合作伙伴关系以及多个学术实体共同协作的结果。当时,技术行业领袖和学者们普遍认为媒体内容操纵的技术复杂性和快速变化性质。比赛的目的是鼓励全球研究人员设计创新和有效的技术来检测深度伪造和媒体操纵。与后来专注于代码的比赛不同,这次比赛要求获奖者在一个“黑盒”环境中测试他们的代码。测试数据不在 Kaggle 上可用,需要更长的处理过程,导致私人排行榜比平时晚公布,正式公布日期为 2020 年 6 月 12 日,尽管比赛已于 2020 年 4 月 24 日结束。
DFDC 吸引了众多高排名的 Kaggle 大师,他们参与数据分析并开发了提交的模型。值得注意的是,最初的第一名获奖者后来被组织者取消资格。这个团队以及其他排名靠前的参与者,通过使用公开数据扩展了他们的训练集。虽然他们遵守了关于使用外部数据的比赛规则,但他们未能满足获奖提交的文档要求。这些规则包括从所有出现在额外训练数据中的图像中的人物获得书面同意。
竞赛数据提供了两个单独的集合。在第一个集合中,提供了 400 个用于训练的视频样本和 400 个用于测试的视频,分别放在两个文件夹中,一个用于训练数据,一个用于测试数据。这些文件是 MP4 格式,这是最常用的视频格式之一。
为了训练,提供了一个超过 470 GB 的大型数据集作为下载链接。或者,相同的数据也以 50 个大约 10 GB 的小文件的形式提供。对于当前的分析,我们只会使用第一组的数据(包含 400 个训练文件和 400 个测试文件,格式为.mp4)。
视频数据格式
视频格式指的是用于编码、压缩和存储视频数据的标准。目前,存在多种并行使用的格式。其中一些视频格式是由微软、苹果和 Adobe 等科技公司创建和推广的。他们决定开发专有格式可能与其控制自身设备或运行其操作系统的设备上的渲染质量的需求有关。
此外,专有格式可以给你带来竞争优势和更大的对许可和与格式相关的版税的控制。其中一些格式包含了之前使用的格式中不存在的新颖功能和实用功能。与技术领导者的开发并行,其他格式是在技术进步、市场需求和对齐行业标准的需求的响应下开发的。
仅举几个常用格式的例子,我们可以提到Windows Media Video(WMV)和Audio Video Interleave(AVI),这两者都是由微软开发的。MOV(QuickTime Movie)格式是由苹果开发的,用于在其 macOS 和 iOS 平台上运行。所有这些格式都支持多种音频和视频编解码器。然后,我们还有由 Adobe 开发的Flash Video(FLV)。此外,一个广泛采用的格式是MPEG-4 Part 14(MP4),它是开源的,也可以包含许多视频和音频编解码器。运动图像专家组(MPEG)指的是一组开发音频和视频压缩以及编码/解码标准的行业专家。从 MPEG-1 到 MPEG-4 的后续标准,对媒体行业的发展产生了巨大影响。
介绍竞赛实用脚本
让我们先从两个 Kaggle 实用脚本中分组具有视频操作可重用功能的 Python 模块。第一个实用脚本将加载和显示视频或播放视频文件的功能分组。第二个则专注于视频中的对象检测——更具体地说,是检测人脸和身体——使用几种替代方法。
视频数据工具
我们开发了一个实用脚本,帮助我们操作视频数据。让我们介绍一个实用脚本,我们将使用与当前章节相关的笔记本来读取视频数据,以及可视化视频文件的帧。
video_utils实用脚本包括加载、转换和显示视频图像的函数。此外,它还包含一个播放视频内容的函数。对于视频操作,我们将使用 OpenCV 库。OpenCV 是一个开源的计算机视觉库,广泛用于图像和视频处理。OpenCV 是用 C 和 C++开发的,也提供了一个 Python 接口。
以下代码块展示了包含的库以及从视频文件中显示一张图片的功能:
import os
import cv2 as cv
import matplotlib.pyplot as plt
from IPython.display import HTML
from base64 import b64encode
def display_image_from_video(video_path):
'''
Display image from video
Process
1\. perform a video capture from the video
2\. read the image
3\. display the image
Args:
video_path - path for video
Returns:
None
'''
capture_image = cv.VideoCapture(video_path)
ret, frame = capture_image.read()
fig = plt.figure(figsize=(10,10))
ax = fig.add_subplot(111)
frame = cv.cvtColor(frame, cv.COLOR_BGR2RGB)
ax.imshow(frame)
在前面的代码中,函数display_image_from_video接收一个参数,即视频文件的路径,从视频中捕获图像,读取图像,创建一个 Matplotlib Pyplot 图像,将其从 BGR(蓝绿红)转换为 RGB(红绿蓝),并显示。RGB 是一种用于在数字图像中表示颜色的颜色模型。RGB 和 BGR 之间的区别在于颜色信息存储的顺序。在 RGB 的情况下,蓝色存储为最低有效位,然后是绿色,最后是红色。在 BGR 的情况下,顺序相反。
接下来,我们定义一个函数来表示从视频文件列表中捕获的一组图像:
def display_images_from_video_list(video_path_list, data_folder, video_folder):
'''
Display images from video list
Process:
0\. for each video in the video path list
1\. perform a video capture from the video
2\. read the image
3\. display the image
Args:
video_path_list: path for video list
data_folder: path for data
video_folder: path for video folder
Returns:
None
'''
plt.figure()
fig, ax = plt.subplots(2,3,figsize=(16,8))
# we only show images extracted from the first 6 videos
for i, video_file in enumerate(video_path_list[0:6]):
video_path = os.path.join(data_folder, video_folder,video_file)
capture_image = cv.VideoCapture(video_path)
ret, frame = capture_image.read()
frame = cv.cvtColor(frame, cv.COLOR_BGR2RGB)
ax[i//3, i%3].imshow(frame)
ax[i//3, i%3].set_title(f"Video: {video_file}")
ax[i//3, i%3].axis('on')
函数display_images_from_video_list接收一个参数,即视频文件名列表的路径,相对于其文件夹的路径,以及数据集的路径。该函数将对列表中的前六个视频文件执行与display_image_from_video相同的处理。我们限制从视频文件中捕获的图像数量以方便操作。
实用脚本还包括一个播放视频的函数。该函数使用 IPython display模块中的HTML函数。代码如下:
def play_video(video_file, data_folder, subset):
'''
Display video given by composed path
Args
video_file: the name of the video file to display
data_folder: data folder
subset: the folder where the video file is located
Returns:
a HTML objects running the video
'''
video_url = open(os.path.join(data_folder, subset,video_file),'rb').read()
data_url = "data:video/mp4;base64," + b64encode(video_url).decode()
return HTML("""<video width=500 controls><source src="img/%s" type="video/mp4"></video>""" % data_url)
函数play_video接收要播放的视频文件名、数据文件夹、数据文件夹中的文件夹以及视频文件所在位置作为参数。该函数使用base64库中的b64encode函数解码 MP4 视频格式,并将解码后的内容以 500 像素的受控宽度显示在视频帧中,使用HTML控件。
我们引入了用于视频图像处理的实用脚本,它可以加载视频,从视频中可视化图像,并播放视频文件。在下一节中,我们将介绍更多用于图像中对象检测的实用脚本。这些 Python 模块包含用于对象检测的专业类。这些模块实现了两种人脸对象检测的替代方案,两者都基于计算机视觉算法。
人脸和身体检测工具
在深度伪造视频的检测中,分析视频特征,如声音与唇部动作不同步或视频中人物面部部分的不自然动作,在这次比赛时,是训练模型识别深度伪造视频的有价值元素。因此,我们在此包括专门用于检测身体和脸部的实用脚本。
第一个用于人脸检测的模块使用了Haar 级联算法。Haar 级联是一种轻量级的机器学习算法,用于对象检测。它通常被训练来识别特定对象。该算法使用 Haar-like 特征和 Adaboost 分类器来创建一个强大的分类器。算法在滑动窗口上操作,应用一系列弱分类器,拒绝图像中不太可能包含感兴趣对象的部分。在我们的案例中,我们希望使用该算法来识别视频图像中的细节,这些细节在深度伪造的情况下通常会被改变,例如面部表情、眼神和嘴型。此模块包括两个类。我们从其中一个类开始。CascadeObjectDetector是一个用于使用Haar 级联算法检测对象的通用类。从参考文献 3中的代码修改而来的CascadeObjectDetector类有一个init函数,其中我们使用存储训练模型的特定Haar 级联对象初始化对象。该类还有一个detect函数。以下是CascadeObjectDetector的代码。在init函数中,我们初始化cascade对象:
import os
import cv2 as cv
import matplotlib.pyplot as plt
class CascadeObjectDetector():
'''
Class for Cascade Object Detection
'''
def __init__(self,object_cascade_path):
'''
Args:
object_cascade_path: path for the *.xml defining the parameters
for {face, eye, smile, profile} detection algorithm
source of the haarcascade resource is:
https://github.com/opencv/opencv/tree/master/data/haarcascades
Returns:
None
'''
self.object_cascade=cv.CascadeClassifier(object_cascade_path)
detect function of the CascadeObjectDetector class. This function returns the rectangle coordinates of the object detected in the image:
def detect(self, image, scale_factor=1.3,
min_neighbors=5,
min_size=(20,20)):
'''
Function return rectangle coordinates of object for given image
Args:
image: image to process
scale_factor: scale factor used for object detection
min_neighbors: minimum number of parameters considered during object detection
min_size: minimum size of bounding box for object detected
Returns:
rectangle with detected object
'''
rects=self.object_cascade.detectMultiScale(image,
scaleFactor=scale_factor,
minNeighbors=min_neighbors,
minSize=min_size)
return rects
为了这次比赛,我创建了一个专门的 Kaggle 数据集,该数据集来源于在github.com/opencv/opencv/tree/master/data/haarcascades定义的 Haar 级联算法,作为 OpenCV 库分发的一部分。这个数据库的链接,称为Haar 级联人脸检测,在参考文献 2中给出。init函数接收数据库中包含的一个目标检测模型的路径。detect函数接收用于对象提取的图像和处理检测的几个参数,这些参数可以用来调整检测。这些参数是缩放因子、检测中使用的最小邻居数以及用于对象检测的最小边界框大小。在detect函数内部,我们调用 Haar 级联模型中的detectMultiscale函数。
在实用脚本中定义的下一个类是FaceObjectDetector。这个类初始化了四个CascadeObjectDetector对象,用于面部、面部侧面、眼睛和微笑检测。下面的代码块显示了带有init函数的类定义,其中定义了这些对象。
对于每个面部元素,即一个人的正面视图、侧面视图、眼睛视图和微笑视图,我们首先使用到 Haar 级联资源的路径初始化一个专用变量。然后,对于每个资源,我们初始化一个CascadeObjectDetector对象(参见上面关于CascadeObjectDetector类的代码解释):
class FaceObjectDetector():
'''
Class for Face Object Detection
'''
def __init__(self, face_detection_folder):
'''
Args:
face_detection_folder: path for folder where the *.xmls
for {face, eye, smile, profile} detection algorithm
Returns:
None
'''
self.path_cascade=face_detection_folder
self.frontal_cascade_path= os.path.join(self.path_cascade,'haarcascade_frontalface_default.xml')
self.eye_cascade_path= os.path.join(self.path_cascade,'haarcascade_eye.xml')
self.profile_cascade_path= os.path.join(self.path_cascade,'haarcascade_profileface.xml')
self.smile_cascade_path= os.path.join(self.path_cascade,'haarcascade_smile.xml')
#Detector object created
# frontal face
self.face_detector=CascadeObjectDetector(self.frontal_cascade_path)
# eye
self.eyes_detector=CascadeObjectDetector(self.eye_cascade_path)
# profile face
self.profile_detector=CascadeObjectDetector(self.profile_cascade_path)
# smile
self.smile_detector=CascadeObjectDetector(self.smile_cascade_path)
这些对象存储为成员变量face_detector、eyes_detector、profile_detector和smile_detector。
detect_object function of the FaceObjectDetector class, and the detect function of the CascadeObjectDetector object initialized with the eyes Haar cascade object. Then, we use the OpenCV Circle function to mark on the initial image, with a circle, the position of the eyes detected in the image:
def detect_objects(self,
image,
scale_factor,
min_neighbors,
min_size,
show_smile=False):
'''
Objects detection function
Identify frontal face, eyes, smile and profile face and display the detected objects over the image
Args:
image: the image extracted from the video
scale_factor: scale factor parameter for `detect` function of CascadeObjectDetector object
min_neighbors: min neighbors parameter for `detect` function of CascadeObjectDetector object
min_size: minimum size parameter for f`detect` function of CascadeObjectDetector object
show_smile: flag to activate/deactivate smile detection; set to False due to many false positives
Returns:
None
'''
image_gray=cv.cvtColor(image, cv.COLOR_BGR2GRAY)
eyes=self.eyes_detector.detect(image_gray,
scale_factor=scale_factor,
min_neighbors=min_neighbors,
min_size=(int(min_size[0]/2), int(min_size[1]/2)))
for x, y, w, h in eyes:
#detected eyes shown in color image
cv.circle(image,(int(x+w/2),int(y+h/2)),(int((w + h)/4)),(0, 0,255),3)
接下来,我们将相同的方法应用于图像中的smile对象。我们首先检测微笑,如果检测到,我们使用opencv函数在检测到的对象的边界框上绘制矩形来显示它。因为这个函数倾向于给出很多误报,所以默认情况下,这个功能是禁用的,使用一个设置为False的标志:
# deactivated by default due to many false positive
if show_smile:
smiles=self.smile_detector.detect(image_gray,
scale_factor=scale_factor,
min_neighbors=min_neighbors,
min_size=(int(min_size[0]/2), int(min_size[1]/2)))
for x, y, w, h in smiles:
#detected smiles shown in color image
cv.rectangle(image,(x,y),(x+w, y+h),(0, 0,255),3)
最后,我们使用专门的 Haar 级联算法提取profile和face对象。如果检测到,我们绘制矩形来标记检测到的对象的边界框:
profiles=self.profile_detector.detect(image_gray,
scale_factor=scale_factor,
min_neighbors=min_neighbors,
min_size=min_size)
for x, y, w, h in profiles:
#detected profiles shown in color image
cv.rectangle(image,(x,y),(x+w, y+h),(255, 0,0),3)
faces=self.face_detector.detect(image_gray,
scale_factor=scale_factor,
min_neighbors=min_neighbors,
min_size=min_size)
for x, y, w, h in faces:
#detected faces shown in color image
cv.rectangle(image,(x,y),(x+w, y+h),(0, 255,0),3)
# image
fig = plt.figure(figsize=(10,10))
ax = fig.add_subplot(111)
image = cv.cvtColor(image, cv.COLOR_BGR2RGB)
ax.imshow(image)
对于四个专门的对象检测器(面部、面部侧面、眼睛和微笑),我们调用检测函数,并获取结果(一个包含检测到的对象边界框的矩形列表),然后在初始图像的上下文中绘制围绕检测到的对象的圆(用于眼睛)或矩形(用于微笑、面部和面部侧面)。最后,该函数显示图像,叠加的层标记了检测到的对象的边界框。因为smile模型的误报很多,我们设置了一个额外的参数,一个标志,用来决定我们是否显示带有微笑的提取边界框。
接下来,这个类有一个用于提取图像对象的功能。该函数接收一个视频路径,从视频中捕获图像,并在图像捕获上应用detect_objects函数以检测该图像中的面部和面部细节(眼睛、微笑等)。下面的代码块显示了提取函数:
def extract_image_objects(self,
video_file,
data_folder,
video_set_folder,
show_smile=False
):
'''
Extract one image from the video and then perform face/eyes/smile/profile detection on the image
Args:
video_file: the video from which to extract the image from which we extract the face
data_folder: folder with the data
video_set_folder: folder with the video set
show_smile: show smile (False by default)
Returns:
None
'''
video_path = os.path.join(data_folder, video_set_folder,video_file)
capture_image = cv.VideoCapture(video_path)
ret, frame = capture_image.read()
#frame = cv.cvtColor(frame, cv.COLOR_BGR2RGB)
self.detect_objects(image=frame,
scale_factor=1.3,
min_neighbors=5,
min_size=(50, 50),
show_smile=show_smile)
我们引入了一个使用 Haar 级联算法进行面部检测的模块。接下来,我们将回顾一种替代方法,其中我们使用MTCNN模型进行面部检测。我们想测试多种方法以决定哪种方法更适合面部检测。MTCNN代表多任务级联卷积网络,它基于在论文使用多任务级联卷积网络进行联合面部检测和校准中首先提出的概念(见参考文献 4)。在另一篇题为使用 MTCNN 进行面部检测的文章中,作者提出了一种“使用子模型不同特征的级联多任务框架”(见参考文献 5)。使用 MTCNN 方法进行面部元素提取的实现是在实用脚本face_detection_mtcnn中完成的。
在此模块中,我们定义了MTCNNFaceDetector类。在下一个代码块中,我们展示了带有init函数的类定义:
class MTCNNFaceDetector():
'''
Class for MTCNN Face Detection
Detects the face and the face keypoints: right & left eye,
nose, right and left lips limits
Visualize a image capture from a video and marks the
face boundingbox and the features
On top of the face boundingbox shows the confidence score
'''
def __init__(self, mtcnn_model):
'''
Args:
mtcnn_model: mtcnn model instantiated already
Returns:
None
'''
self.detector = mtcnn_model
self.color_face = (255,0,0)
self.color_keypoints = (0, 255, 0)
self.font = cv.FONT_HERSHEY_SIMPLEX
self.color_font = (255,0,255)
init函数接收 MTCNN 模型的一个实例作为参数,该实例在调用应用程序中从mtcnn库导入并实例化。类成员变量 detector 用此对象初始化。其余的类变量用于检测到的对象的可视化。
该类还有一个detect函数。下一个代码块显示了detect函数的实现:
def detect(self, video_path):
'''
Function plot image
Args:
video_path: path to the video from which to capture
image and then apply detector
Returns:
rectangle with detected object
'''
capture_image = cv.VideoCapture(video_path)
ret, frame = capture_image.read()
image = cv.cvtColor(frame, cv.COLOR_BGR2RGB)
results = self.detector.detect_faces(image)
if results:
for result in results:
print(f"Extracted features: {result}")
x, y, w, h = bounding_box = result['box']
keypoints = result['keypoints']
confidence = f"{round(result['confidence'], 4)}"
cv.rectangle(image, (x, y),(x+w,y+h), self.color_face, 3)
# add all the internal features
for key in keypoints:
xk, yk = keypoints[key]
cv.rectangle(image, (xk-2, yk-2), (xk+2, yk+2), self.color_keypoints, 3)
image = cv.putText(image, confidence, (x, y-2),
self.font, 1,
self.color_font, 2,
cv.LINE_AA)
fig = plt.figure(figsize=(15, 15))
ax = fig.add_subplot(111)
ax.imshow(image)
plt.show()
函数接收视频文件的路径作为参数。从视频文件中捕获图像后,我们读取它并将其从 BGR 格式转换为 RGB 格式。这种转换是必要的,因为我们想使用期望 RGB 颜色顺序的库函数。在将 MTCNN 模型的detect_faces函数应用于转换后的图像后,检测器返回一个提取的 JSON 列表。每个提取的 JSON 具有以下格式:
{
'box': [906, 255, 206, 262],
'confidence': 0.9999821186065674,
'keypoints':
{
'left_eye': (965, 351),
'right_eye': (1064, 354),
'nose': (1009, 392),
'mouth_left': (966, 453),
'mouth_right': (1052, 457)
}
}
在'box'字段中是检测到的面部区域的边界框。在'``keypoints'字段中是五个检测到的对象的键和坐标:左眼、右眼、鼻子、最左侧的嘴部限制和最右侧的嘴部限制。还有一个额外的字段'confidence',它给出了模型的置信因子。
对于真实的人脸,置信因子高于 0.99(最大值为 1)。如果模型检测到伪影,或者像带有面部图像的海报这样的东西,这个因子可能高达 0.9。低于 0.9 的置信因子最可能是与伪影检测(或假阳性)相关。
在我们的实现中(见前面的代码),我们解析检测 JSON 列表,并为每个面部添加一个矩形,并为五个面部特征中的每一个添加一个点(或一个非常小的矩形)。在面部边界框矩形的顶部,我们写下置信因子(四舍五入到小数点后四位)。
除了用于从视频捕获图像和播放视频的实用脚本,以及用于从视频数据中检测对象的实用脚本之外,我们还将重用我们在第四章中开始使用的用于数据质量和绘图的数据质量实用脚本。
在下一节中,我们开始进行一些准备工作,并继续对竞赛数据进行元数据探索。在本节中,我们将介绍导入库、对数据文件进行一些检查以及元数据文件的统计分析。
元数据探索
我们首先从数据质量、绘图工具、视频工具和面部对象检测实用脚本中导入实用函数和类。以下代码块显示了从实用脚本中导入的内容:
from data_quality_stats import missing_data, unique_values, most_frequent_values
from plot_style_utils import set_color_map, plot_count
from video_utils import display_image_from_video, display_images_from_video_list, play_video
from face_object_detection import CascadeObjectDetector, FaceObjectDetector
from face_detection_mtcnn import MTCNNFaceDetector
在加载完数据文件(训练和测试样本)后,我们就可以开始我们的分析了。以下代码块检查 TRAIN_SAMPLE_FOLDER 中文件的类型:
train_list = list(os.listdir(os.path.join(DATA_FOLDER, TRAIN_SAMPLE_FOLDER)))
ext_dict = []
for file in train_list:
file_ext = file.split('.')[1]
if (file_ext not in ext_dict):
ext_dict.append(file_ext)
print(f"Extensions: {ext_dict}")
结果显示有两种类型的文件,JSON 文件和 MP4 文件。以下代码检查 TRAIN_SAMPLE_FOLDER 中存在的 JSON 文件的内容。它从包含在 JSON 文件中的 TRAIN_SAMPLE_FOLDER 中的文件中采样前五条记录:
json_file = [file for file in train_list if file.endswith('json')][0]
def get_meta_from_json(path):
df = pd.read_json(os.path.join(DATA_FOLDER, path, json_file))
df = df.T
return df
meta_train_df = get_meta_from_json(TRAIN_SAMPLE_FOLDER)
meta_train_df.head()
在 图 9.1 中,我们展示了从 JSON 文件创建 DataFrame meta_train_df 时获得的数据样本。索引是文件名。label 是 FAKE(用于深度伪造视频)或 REAL(用于真实视频)。split 字段给出了视频所属的集合(train)。original 是初始视频的名称,深度伪造是从该视频创建的。
图 9.1:训练样本文件夹中的文件样本
我们还使用来自数据质量、绘图工具、视频工具和面部对象检测实用脚本的一些统计函数来检查元数据的一些统计信息。这些函数在 第三章 中介绍。
图 9.2 展示了 meta_train_df 中的缺失值。如图所示,19.25% 的原始字段是缺失的。
图 9.2:样本训练数据中的缺失值
在 图 9.3 中,我们展示了 meta_train_df 中的唯一值。有 323 个原始值,其中 209 个是唯一的。其他两个字段 label 和 split 有 400 个值,其中 label 有 2 个唯一值(伪造和真实),split 有 1 个(训练)。
图 9.3:样本训练数据中的唯一值
图 9.4 展示了 meta_train_df 中最频繁的值。在总共 400 个标签中,323 个或 80.75% 是伪造的。最频繁的 原始 值是 atvmxvwyns.mp4,频率为 6(即,它在 6 个伪造视频中使用了)。split 列中的所有值都是 train。
图 9.4:样本训练数据中的最频繁值
在这次分析中,我们将使用自定义颜色方案,包括蓝色和灰度的色调。以下代码块显示了生成自定义颜色图的代码:
color_list = ['#4166AA', '#06BDDD', '#83CEEC', '#EDE8E4', '#C2AFA8']
cmap_custom = set_color_map(color_list)
在图 9.5中,我们展示了颜色图。
图 9.5:样本训练数据中最频繁的值
图 9.6显示了样本训练数据中的标签分布。有 323 条记录带有FAKE标签,其余标签的值为REAL。
图 9.6:样本训练数据中最频繁的值
在下一节中,我们将开始分析视频数据。
视频数据探索
在本节中,我们将可视化一些文件样本,然后我们将开始执行目标检测,试图从图像中捕获在创建深度伪造时可能出现的异常特征。这些主要是眼睛、嘴巴和身体。
我们将首先可视化样本文件,包括真实图像和深度伪造图像。然后,我们将应用之前介绍的第一种算法,用于人脸、眼睛和嘴巴检测,即基于 Haar 级联的算法。接着,我们将使用基于 MTCNN 的替代算法。
可视化样本文件
以下代码块从一组假视频中选取一些视频文件,然后使用来自实用脚本video_utils的display_image_from_video函数可视化它们的图像捕获:
fake_train_sample_video = list(meta_train_df.loc[meta_train_df.label=='FAKE'].sample(3).index)
for video_file in fake_train_sample_video:
display_image_from_video(os.path.join(DATA_FOLDER, TRAIN_SAMPLE_FOLDER, video_file))
前面的代码将为三个视频中的每一个绘制一个图像捕获。在图 9.7中,我们只展示这些图像捕获中的一个,即第一个视频的:
图 9.7:伪造视频的图像捕获示例
下一个代码块选择三个真实视频的样本,然后为每个选定的视频创建并绘制一个图像捕获:
real_train_sample_video = list(meta_train_df.loc[meta_train_df.label=='REAL'].sample(3).index)
for video_file in real_train_sample_video:
display_image_from_video(os.path.join(DATA_FOLDER, TRAIN_SAMPLE_FOLDER, video_file))
在图 9.8中,我们展示了从第一段真实视频中捕获的一张图像:
图 9.8:来自真实视频的图像捕获示例
我们还希望检查所有都源自同一原始视频的视频。我们将从同一原始视频中选取六个视频,并展示每个视频的一个图像捕获。以下代码块执行此操作:
same_original_fake_train_sample_video = \
list(meta_train_df.loc[meta_train_df.original=='meawmsgiti.mp4'].index)
display_images_from_video_list(video_path_list=same_original_fake_train_sample_video,
data_folder=DATA_FOLDER,
video_folder=TRAIN_SAMPLE_FOLDER)
在图 9.9中,我们展示了来自几个不同视频的这些图像捕获中的两个,其中我们使用了相同的原始文件进行深度伪造。
图 9.9:从同一原始文件修改的伪造视频的图像捕获
我们还对测试集的视频进行了类似的检查。当然,在测试集的情况下,我们无法事先知道哪个视频是真实的还是伪造的。以下代码从数据中的两个样本视频中选择了图像捕获:
display_images_from_video_list(test_videos.sample(2).video, DATA_FOLDER, TEST_FOLDER)
图 9.10 显示了这些选定的图像:
图 9.10:从同一原始文件修改的伪造视频中的图像捕获
让我们现在开始使用人脸和身体检测工具部分中介绍的人脸检测算法。
执行对象检测
首先,让我们使用来自face_object_detection模块的Haar 级联算法。我们使用FaceObjectDetector对象提取面部、面部轮廓、眼睛和微笑。CascadeObjectDetector类初始化上述人员属性的专用级联分类器(使用专用导入的资源)。detect函数使用 OpenCV 中的CascadeClassifier方法在图像中检测对象。对于每个属性,我们将使用不同的形状和颜色来标记/突出显示提取的对象,如下所示:
-
正面面部:绿色矩形
-
眼睛:红色圆圈
-
微笑:红色矩形
-
侧面面部:蓝色矩形
注意,由于大量误报,我们已禁用了微笑检测器。
我们将人脸检测函数应用于训练样本视频中的一些图像。以下代码块执行此操作:
same_original_fake_train_sample_video = \
list(meta_train_df.loc[meta_train_df.original=='kgbkktcjxf.mp4'].index)
for video_file in same_original_fake_train_sample_video[1:4]:
print(video_file)
face_object_detector.extract_image_objects(video_file=video_file,
data_folder=DATA_FOLDER,
video_set_folder=TRAIN_SAMPLE_FOLDER,
show_smile=False
)
上述代码运行将生成三个不同视频的三个图像捕获。每个图像都装饰了提取的突出显示对象。以下图示显示了带有提取对象的三个图像捕获。在图 9.11a中,我们看到了检测到的正面和侧面面部以及一个检测到的眼睛。图 9.11b显示了检测到的正面和侧面面部以及两个检测到的眼睛。图 9.11c显示了检测到的正面和侧面面部,正确检测到的两个眼睛,以及一个误报(其中一个鼻孔被检测为眼睛)。在这种情况下,微笑检测未激活(误报太多)。
a
b
c
图 9.11:从三个不同视频的图像捕获中检测到的面部、面部轮廓和眼睛
使用其他图像运行这些算法,我们可以看到它们并不非常稳健,并且经常产生误报以及不完整的结果。在图 9.12中,我们展示了这种不完整检测的两个示例。在图 9.12a中,只检测到了面部。在图 9.12b中,只检测到了一个面部轮廓,尽管场景中有两个人。
a
b
图 9.12:从两个不同视频捕获的图像中的面部、面部轮廓和眼部检测
在前面的图像中,也存在一种奇怪的检测;天花板上的消防喷淋头被检测为眼睛,远左边的烛台也是如此。这类错误检测(假阳性)在这些过滤器中相当常见。一个常见问题是眼睛、鼻子或嘴唇等物体在没有人脸的区域被检测到。由于这些不同物体的搜索是独立进行的,因此出现这种假阳性的可能性相当大。
我们在face_detection_mtcnn中实施的替代解决方案使用了一个独特的框架来同时检测人脸边界框和面部元素(如眼睛、鼻子和嘴唇)的位置。让我们比较使用 Haar 级联算法获得的与使用 MTCNN 算法获得的相同图像的结果,如图图 9.11和图 9.12所示。
在图 9.13中,我们展示了一张身穿黄色衣服的人的图像;这次,我们使用的是我们的MTCNNFaceDetector进行人脸检测:
图 9.13:MTCNN 人脸检测:一个真实人脸和一个人工制品的检测
检测到两个面部对象。一个是正确的,另一个是人工制品。检测的 JSON 如下:
Extracted features: {'box': [906, 255, 206, 262], 'confidence': 0.9999821186065674, 'keypoints': {'left_eye': (965, 351), 'right_eye': (1064, 354), 'nose': (1009, 392), 'mouth_left': (966, 453), 'mouth_right': (1052, 457)}}
Extracted features: {'box': [882, 966, 77, 84], 'confidence': 0.871575653553009, 'keypoints': {'left_eye': (905, 1003), 'right_eye': (926, 985), 'nose': (919, 1002), 'mouth_left': (921, 1024), 'mouth_right': (942, 1008)}}
在我们对大量样本进行的实验中,我们得出结论,真实的人脸将有一个非常接近 1 的置信因子。因为第二个检测到的“人脸”置信度为0.87,我们可以很容易地将其排除。只有置信因子高于0.99的人脸才是可信的。
让我们再看另一个例子。在图 9.14中,我们比较了图 9.12中相同图像的结果。在这两个图中,场景中所有人的面部都被正确识别。在所有情况下,置信度得分都高于 0.999。没有错误地将人工制品提取为人像。该算法似乎比使用 Haar 级联的替代实现更稳健。
a
b
图 9.14:MTCNN 人脸检测:一个人和一个两个人的场景
对于下一个例子,我们选择了一个案例,如果视频中存在两个人,那么从视频中捕获的图像中的人脸被正确识别,置信度得分也较高。在同一图像中,还识别了一个被误认为是人脸的人工制品:
图 9.15:MTCNN 面部检测:两人场景
除了两个真实人物,其置信度因子分别为 0.9995 和 0.9999(四舍五入为 1)之外,场景中第一人 T 恤上的Dead Alive角色面部也被检测为面部。边界框被正确检测,所有面部元素也被正确检测。唯一表明这是一个误报的迹象是较低的置信度因子,在这种情况下为 0.9075。这样的例子可以帮助我们正确校准我们的面部检测方法。只有置信度高于 0.95 或甚至 0.99 的面部检测应该被考虑。
在与本章相关的笔记本中,Deepfake Exploratory Data Analysis (www.kaggle.com/code/gpreda/deepfake-exploratory-data-analysis),我们提供了使用这里介绍的方法进行面部提取的更多示例。
摘要
在本章中,我们首先介绍了一系列实用脚本,这些是 Kaggle 上可重用的 Python 模块,用于视频数据处理。其中一个脚本video_utils用于可视化视频中的图像并播放它们。另一个脚本face_object_detection使用 Haar 级联模型进行面部检测。
第三段脚本face_detection_mtcnn使用了 MTCNN 模型来识别面部以及如眼睛、鼻子和嘴巴等关键点。然后我们检查了 DFDC 竞赛数据集的元数据和视频数据。在这个数据集中,我们将上述面部检测方法应用于训练和测试视频中的图像,发现 MTCNN 模型方法更稳健、更准确,且误报率更低。
随着我们接近数据探索的尾声,我们将反思我们通过各种数据格式(包括表格、文本、图像、声音和现在视频)的旅程。我们深入研究了多个 Kaggle 数据集和竞赛数据集,学习了如何进行探索性数据分析、创建可重用代码、为我们的笔记本建立视觉身份,以及用数据编织故事。在某些情况下,我们还引入了特征工程元素并建立了模型基线。在一个案例中,我们展示了逐步细化我们的模型以增强验证指标的过程。前几章和当前章节的重点是制作高质量的 Kaggle 笔记本。
在下一章中,我们将探讨使用 Kaggle 的大型语言模型,可能还会结合其他技术,如 LangChain 和向量数据库。这将展示生成式 AI 在各个应用中的巨大潜力。
参考文献
-
深度伪造检测挑战,Kaggle 竞赛,识别带有面部或声音操纵的视频:
www.kaggle.com/competitions/deepfake-detection-challenge -
人脸检测的 Haar 级联,Kaggle 数据集:
www.kaggle.com/datasets/gpreda/haar-cascades-for-face-detection -
Serkan Peldek – 使用 OpenCV 进行人脸检测,Kaggle 笔记本:
www.kaggle.com/code/serkanpeldek/face-detection-with-opencv/ -
张凯鹏,张占鹏,李志锋,乔宇 – 使用多任务级联卷积网络进行人脸检测与对齐:
arxiv.org/abs/1604.02878 -
Justin Güse – 使用 MTCNN 进行人脸检测 — 专注于速度的人脸提取指南:
towardsdatascience.com/face-detection-using-mtcnn-a-guide-for-face-extraction-with-a-focus-on-speed-c6d59f82d49
加入我们书籍的 Discord 空间
加入我们的 Discord 社区,与志同道合的人相聚,并在以下地点与超过 5000 名成员一起学习:
第十章:利用 Kaggle 模型释放生成式人工智能的力量
在前面的章节中,我们主要关注掌握分析不同数据类型和制定解决各种问题的策略。我们深入研究了数据探索和可视化的各种工具和方法,丰富了我们在这些领域的技能。其中一些早期章节专门用于构建基线模型,特别是在竞争场景中的参与。
现在,在本章中,我们将把注意力转向利用 Kaggle 模型。我们的目标是将这些模型集成到 Kaggle 应用中,以便原型化在实用应用中使用最新的生成式人工智能技术。这类现实世界应用的例子包括个性化营销、聊天机器人、内容创作、定向广告、回答客户咨询、欺诈检测、医学诊断、患者监测、药物发现、个性化医疗、金融分析、风险评估、交易、文件起草、诉讼支持、法律分析、个性化推荐和合成数据生成。
本章将涵盖以下主题:
-
Kaggle 模型简介 – 如何访问和使用它们
-
激活一个大型语言模型(LLM)
-
将 LLM 与任务链解决方案(如 Langchain)结合使用,为 LLM 创建一系列(或链)提示
-
使用 LangChain、LLM 和向量数据库构建检索增强生成(RAG)系统
介绍 Kaggle 模型
Kaggle 模型代表了 Kaggle 平台上的最新创新之一。这一功能在代码竞赛的引入后尤其受到关注,在竞赛中,参与者通常在本地硬件或云中训练模型。训练完成后,他们将模型作为数据集上传到 Kaggle。这种做法允许 Kagglers 在他们推理笔记本中使用这些预训练模型,简化了代码竞赛提交的过程。这种方法显著减少了推理笔记本的运行时间,符合竞赛严格的时间和内存限制。Kaggle 对这种方法的认可与现实世界的生产系统相吻合,在现实世界的生产系统中,模型训练和推理通常在独立的管道中发生。
这种策略对于基于 Transformer 架构的大型模型至关重要,因为这些模型在微调时需要巨大的计算资源。像 HuggingFace 这样的平台进一步民主化了大型模型的访问,提供了在线使用或下载协作开发模型的选择。Kaggle 引入的模型功能,可以像数据集一样添加到笔记本中,是一项重大进步。这些模型可以直接在笔记本中用于迁移学习或进一步微调等任务。然而,在撰写本文时,Kaggle 不允许用户以与数据集相同的方式上传模型。
Kaggle 的模型库提供了浏览和搜索功能,使用户可以根据名称、元数据、任务、数据类型等多种标准找到模型。在撰写本文时,该库拥有由 Google、TensorFlow、Kaggle、DeepMind、Meta 和 Mistral 等知名组织发布的 269 个模型和 1,997 个变体。
随着 GPT-3、ChatGPT、GPT-4 等模型的出现,生成式 AI 领域引起了极大的兴趣。Kaggle 提供了访问多个强大的 LLM(大型语言模型)或基础模型的机会,例如 Llama、Alpaca 和 Llama 2。该平台的集成生态系统使用户能够迅速测试新出现的模型。例如,Meta 的 Llama 2 自 2023 年 7 月 18 日起可用,是一系列生成文本模型,参数量从 70 亿到 700 亿不等。这些模型,包括适用于聊天应用的专用版本,与其他平台相比,在 Kaggle 上相对容易访问。
Kaggle 通过允许用户直接从模型页面启动笔记本,类似于从比赛或数据集启动笔记本,进一步简化了流程。
这种简化的方法,如以下截图所示,增强了用户体验,并促进了模型实验和应用中更高效的流程。
图 10.1:Mistral 模型的主页,右上角有添加笔记本的按钮
一旦在编辑器中打开笔记本,模型就已经添加进去了。对于模型来说,还需要额外一步,这是因为模型也有变体、版本和框架。在笔记本编辑窗口的右侧面板中,您可以设置这些选项。设置完这些选项后,我们就可以在笔记本中使用模型了。以下截图显示了 Mistral AI(见参考文献 2)的一个模型 Mistral 的选项,在菜单中选择了所有内容:
图 10.2:Mistral AI 的 Mistral 模型添加到笔记本中,并选择了所有选项
激活基础模型
LLMs 可以直接用于诸如摘要、问答和推理等任务。由于它们是在非常大的数据集上训练的,因此它们可以很好地回答许多主题的多种问题,因为它们在训练数据集中有可用的上下文。
在许多实际情况下,这样的 LLM 可以在第一次尝试中正确回答我们的问题。在其他情况下,我们需要提供一些澄清或示例。这些零样本或少样本方法中答案的质量高度依赖于用户为 LLM 编写的提示能力。在本节中,我们将展示在 Kaggle 上与一个 LLM 交互的最简单方法,使用提示。
模型评估和测试
在开始在 Kaggle 上使用 LLM 之前,我们需要进行一些准备工作。我们首先加载模型,然后定义一个分词器。接下来,我们创建一个模型管道。在我们的第一个代码示例中,我们将使用 transformers 中的 AutoTokenizer 作为分词器并创建一个管道,也使用 transformers pipeline。以下代码(来自参考 3中的笔记本摘录)说明了这些步骤:
def load_model_tokenize_create_pipeline():
"""
Load the model
Create a
Args
Returns:
tokenizer
pipeline
"""
# adapted from https://huggingface.co/blog/llama2#using-transformers
time_1 = time()
model = "/kaggle/input/llama-2/pytorch/7b-chat-hf/1"
tokenizer = AutoTokenizer.from_pretrained(model)
time_2 = time()
print(f"Load model and init tokenizer: {round(time_2-time_1, 3)}")
pipeline = transformers.pipeline(
"text-generation",
model=model,
torch_dtype=torch.float16,
device_map="auto",)
time_3 = time()
print(f"Prepare pipeline: {round(time_3-time_2, 3)}")
return tokenizer, pipeline
前面的代码返回了分词器和管道。然后我们实现了一个测试模型的功能。该函数接收分词器、管道以及我们想要测试模型的提示。以下代码是测试函数:
def test_model(tokenizer, pipeline, prompt_to_test):
"""
Perform a query
print the result
Args:
tokenizer: the tokenizer
pipeline: the pipeline
prompt_to_test: the prompt
Returns
None
"""
# adapted from https://huggingface.co/blog/llama2#using-transformers
time_1 = time()
sequences = pipeline(
prompt_to_test,
do_sample=True,
top_k=10,
num_return_sequences=1,
eos_token_id=tokenizer.eos_token_id,
max_length=200,)
time_2 = time()
print(f"Test inference: {round(time_2-time_1, 3)}")
for seq in sequences:
print(f"Result: {seq['generated_text']}")
现在,我们准备提示模型。我们使用的模型具有以下特点:Llama 2 模型(7b)、来自 HuggingFace 的聊天版本(版本 1)以及 PyTorch 框架。我们将用数学问题提示模型。在下一个代码摘录中,我们初始化分词器和管道,然后用一个简单的算术问题提示模型,这个问题是用普通语言表述的:
tokenizer, pipeline = load_model_tokenize_create_pipeline()
prompt_to_test = 'Prompt: Adrian has three apples. His sister Anne has ten apples more than him. How many apples has Anne?'
test_model(tokenizer, pipeline, prompt_to_test)
让我们看看模型是如何推理的。以下截图,我们绘制了推理时间、提示和答案:
图 10.3:使用 Llama 2 模型对数学问题的提示、答案和推理时间
对于这个简单的数学问题,模型的推理似乎很准确。让我们尝试一个不同的问题。在以下代码摘录中,我们提出了一个几何问题:
prompt_to_test = 'Prompt: A circle has the radius 5\. What is the area of the circle?'
test_model(tokenizer, pipeline, prompt_to_test)
以下截图显示了使用前面的几何问题提示模型的成果:
图 10.4:Llama 2 模型对一个基本几何问题的回答
对于简单的数学问题,模型的回答并不总是正确的。在以下示例中,我们使用第一个代数问题的变体提示了模型。你可以看到,在这种情况下,模型采取了一条复杂且错误的路径来得出错误的解决方案:
图 10.5:Llama 2 模型在代数问题上的解决方案是错误的
模型量化
在先前的实验中,我们用一系列简单的问题测试了模型。这个过程强调了精心设计、结构良好的提示在引发准确和相关信息中的关键作用。虽然 Kaggle 慷慨地提供了大量的免费计算资源,但 LLMs 的规模本身就是一个挑战。这些模型需要大量的 RAM 和 CPU/GPU 功率来加载和推理。
为了减轻这些需求,我们可以采用一种称为模型量化的技术。这种方法有效地减少了模型的内存和计算需求。它通过使用低精度数据类型(如 8 位或 4 位整数)来表示模型的权重和激活函数,而不是标准的 32 位浮点格式,来实现这一点。这种方法不仅节省了资源,而且在效率和性能之间保持了平衡(见参考文献 4)。
在我们即将提供的示例中,我们将演示如何使用可用的技术之一,即 llama.cpp 库,量化 Kaggle 上的模型。我们选择了 Llama 2 模型来完成这个目的。截至写作时,Llama 2 是你可以下载(经 Meta 批准)并免费使用的最成功的 LLM 之一。它也在各种任务上表现出可观的准确性,与其他许多可用模型相当。量化将使用 llama.cpp 库执行。
llama.cpp, import the necessary functions from the package, execute the quantization process, and subsequently load the quantized model. It’s important to note that, in this instance, we will not utilize the latest, more advanced quantization option available in llama.cpp. This example serves as an introduction to model quantization on Kaggle and its practical implementation:
!CMAKE_ARGS="-DLLAMA_CUBLAS=on" pip install llama-cpp-python
!git clone https://github.com/ggerganov/llama.cpp.git
!python llama.cpp/convert.py /kaggle/input/llama-2/pytorch/7b-chat-hf/1 \
--outfile llama-7b.gguf \
--outtype q8_0
from llama_cpp import Llama
llm = Llama(model_path="/kaggle/working/llama-7b.gguf")
让我们看看几个测试量化模型的例子。我们将首先用地理问题提示它:
output = llm("Q: Name three capital cities in Europe? A: ", max_tokens=38, stop=["Q:", "\n"], echo=True)
提示的结果如下:
图 10.6:使用地理问题提示量化 Llama 2 模型的结果
在下一个屏幕截图中,我们展示了模型对一个简单几何问题的回答。答案非常直接,表述清晰。提示模型和打印结果的代码如下:
output = llm("If a circle has the radius 3, what is its area?")
print(output['choices'][0]['text'])
图 10.7:使用几何问题提示量化 Llama 2 模型的结果
展示第一个量化 Llama 2 模型方法的笔记本,我们从其中提取了代码和结果,详见参考文献 5。该笔记本是在 GPU 上运行的。在参考文献 6中给出的另一个笔记本中,我们运行了相同的模型,但是在 CPU 上。值得注意的是,使用量化模型在 CPU 上执行推理的时间远小于在 GPU 上(使用相同的量化模型)。有关更多详细信息,请参阅参考文献 5和6。
我们还可以使用其他模型量化的方法。例如,在参考文献 7中,我们使用了 bitsandbytes 库进行模型量化。为了使用这种量化选项,我们需要安装 accelerate 库和 bitsandbytes 的最新版本。以下代码片段展示了如何初始化量化模型配置并使用此配置加载模型:
model_1_id = '/kaggle/input/llama-2/pytorch/7b-chat-hf/1'
device = f'cuda:{cuda.current_device()}' if cuda.is_available() else 'cpu'
# set quantization configuration to load large model with less GPU memory
# this requires the `bitsandbytes` library
bnb_config = transformers.BitsAndBytesConfig(
load_in_4bit=True,
bnb_4bit_quant_type='nf4',
bnb_4bit_use_double_quant=True,
bnb_4bit_compute_dtype=bfloat16
)
我们还定义了一个管道:
time_1 = time()
query_pipeline_1 = transformers.pipeline(
"text-generation",
model=model_1,
tokenizer=tokenizer_1,
torch_dtype=torch.float16,
device_map="auto",)
time_2 = time()
print(f"Prepare pipeline #1: {round(time_2-time_1, 3)} sec.")
llm_1 = HuggingFacePipeline(pipeline=query_pipeline_1)
我们可以用一个简单的提示来测试模型:
llm_1(prompt="What is the most popular food in France for tourists? Just return the name of the food.")
答案看起来似乎是正确的:
图 10.8:对简单地理问题的回答(使用 bitsandbytes 库量化的 Llama 2)
到目前为止,我们已经尝试了提示模型。我们直接使用了 Kaggle Models 中的模型,或者经过量化处理。我们使用了两种不同的方法进行量化。然而,在下一节中,我们将看到如何使用 Langchain 这样的任务链框架来扩展 LLM(大型语言模型)的能力,并创建一系列操作,其中 LLM 的初始查询答案作为下一个任务的输入。
使用 Langchain 构建多任务应用
Langchain 是最受欢迎的任务链框架(参考文献 8)。任务链是我们在上一节中阐述的提示工程概念的扩展。链是一系列预定的操作,旨在以更易于管理和理解的方式组织复杂的过程。这些链遵循特定的动作顺序。它们非常适合具有固定步骤数量的工作流程。使用任务链,您可以创建一系列提示,其中框架执行的前一个任务的输出作为下一个任务的输入。
除了 Langchain 之外,现在还有其他几种任务链选项可用,如 LlamaIndex 或来自微软的 Semantic Kernel。Langchain 提供了多种功能,包括专门的数据摄取或结果输出工具、智能代理,以及通过定义自己的任务、工具或代理来扩展它的可能性。代理将根据感知到的上下文选择并执行任务,以实现其目标。为了执行任务,它将使用通用或定制的工具。
让我们从定义一个两步序列开始使用 Langchain。我们将在一个自定义函数中定义这个序列,该函数将接收一个参数并形成一个参数化的初始提示,该提示以输入参数为参数。根据第一个提示的答案,我们组装下一个任务的提示。这样,我们可以创建我们迷你应用的动态行为。定义此函数的代码如下(参考文献 7):
def sequential_chain(country, llm):
"""
Args:
country: country selected
Returns:
None
"""
time_1 = time()
template = "What is the most popular food in {country} for tourists? Just return the name of the food."
# first task in chain
first_prompt = PromptTemplate(
input_variables=["country"],
template=template)
chain_one = LLMChain(llm = llm, prompt = first_prompt)
# second step in chain
second_prompt = PromptTemplate(
input_variables=["food"],
template="What are the top three ingredients in {food}. Just return the answer as three bullet points.",)
chain_two = LLMChain(llm=llm, prompt=second_prompt)
# combine the two steps and run the chain sequence
overall_chain = SimpleSequentialChain(chains=[chain_one, chain_two], verbose=True)
overall_chain.run(country)
time_2 = time()
print(f"Run sequential chain: {round(time_2-time_1, 3)} sec.")
预期的输入参数是一个国家的名称。第一个提示将获取那个国家最受欢迎的食物。下一个提示将使用第一个问题的答案来构建第二个问题,这个问题是关于那种食物的前三种成分。
让我们用两个例子来检查代码的功能。首先,让我们尝试使用France参数:
final_answer = sequential_chain("France", llm_1)
图 10.9:两步顺序链执行(法国最著名食物的成分)
答案看起来相当令人信服。确实,法国的游客更喜欢蜗牛,而且,这种美味食物的前三种成分确实列得正确。让我们再检查一次,用另一个以其美味食物而闻名的国家意大利。提示将是:
final_answer = sequential_chain("Italy", llm_1)
因此,结果将是:
图 10.10:意大利最受欢迎的食物及其成分
我们用一个直观的例子说明了如何使用 LangChain 与 LLM 结合,通过链式多个提示来扩展 LLMs 的能力,例如,在业务流程自动化的自动化中。在下一节中,我们将看到如何使用 LLMs 来完成另一个重要任务,即代码生成自动化,以提高编码过程中的生产力。
使用 Kaggle Models 进行代码生成
对于代码生成,我们将实验 Code Llama 模型,13b 版本。在撰写本文时,在 Kaggle 平台上可用的 LLMs 中,这个模型在目的(它是一个专门用于代码生成的模型)和大小(即我们可以使用它与 Kaggle Notebooks)方面对于代码生成任务来说是最合适的。用于演示代码生成的笔记本在参考 9中给出。模型被加载,使用bitsandbytes量化,并且以与参考 7中相同的方式初始化了 tokenizer。我们使用以下代码定义了一个提示和一个管道(使用 transformers 函数):
prompt = 'Write the code for a function to compute the area of circle.'
sequences = pipeline(
prompt,
do_sample=True,
top_k=10,
temperature=0.1,
top_p=0.95,
num_return_sequences=1,
eos_token_id=tokenizer.eos_token_id,
max_length=200,
)
执行前面代码的结果如下所示。代码看起来功能正常,但答案包含比预期更多的信息。我们通过打印所有输出的序列获得了这些信息。如果我们只选择第一个,答案将是正确的(只有圆面积的计算代码)。
图 10.11:代码生成:计算圆面积的函数
在参考 9的笔记本中,有更多的例子;我们这里不会给出所有细节。你可以通过更改提示来修改笔记本并生成更多答案。
在下一节中,让我们看看如何通过创建一个系统来进一步扩展 LLMs 的功能,该系统可以检索存储在特殊数据库(向量数据库)中的信息,通过将初始查询与检索到的信息(上下文)结合来组装提示,并通过仅使用检索步骤的结果来提示 LLM 回答初始查询。这样的系统被称为检索增强生成(RAG)。
创建一个 RAG 系统
在前面的章节中,我们探讨了与基础模型交互的各种方法——更确切地说,是来自 Kaggle Models 的可用 LLMs。首先,我们通过提示直接使用模型进行了实验。然后,我们用两种不同的方法量化了模型。我们还展示了我们可以使用模型来生成代码。一个更复杂的应用包括将LangChain与 LLM 结合以创建一系列连接的操作,或任务序列。
在所有这些情况下,LLM 的答案都是基于在训练模型时模型已经拥有的信息。如果我们希望 LLM 回答关于从未向 LLM 展示过的信息的查询,模型可能会通过虚构来提供误导性的答案。为了对抗模型在没有正确信息时虚构的倾向,我们可以使用自己的数据微调模型。这种方法的缺点是成本高昂,因为微调大型模型所需的计算资源非常大。它也不一定能完全消除虚构。
与此方法不同的选择是将向量数据库、任务链框架和 LLM(大型语言模型)结合起来创建一个 RAG 系统(参见参考文献 10)。在下面的图中,我们展示了这样一个系统的功能:
图 10.12:RAG 系统解释
在使用 RAG 系统之前,我们必须将文档导入向量数据库(图 10.12 中的步骤 1)。文档可以是任何格式,包括 Word、PowerPoint、文本、Excel、图片、视频、电子邮件等。我们首先将文本格式的每种模态转换(例如,使用 Tesseract 从图片中提取文本,或使用 OpenAI Whisper 将视频转换为文本)。在我们将所有格式/模态转换为文本之后,我们必须将较大的文本分割成固定大小的块(部分重叠,以避免丢失可能分布在多个块中的上下文)。
然后,我们在将预处理过的文档添加到向量数据库之前,使用其中一种选项对信息进行编码。向量数据库存储使用文本嵌入编码的数据,并且它还使用非常高效的索引来支持这种编码类型,这将使我们能够根据相似性搜索快速搜索和检索信息。我们有多个向量数据库选项,如 ChromaDB、Weaviate、Pinecone 和 FAISS。在我们的 Kaggle 应用程序中,我们使用了 ChromaDB,它有一个简单的界面,与 Langchain 插件兼容,易于集成,有选项用于内存以及持久存储。
一旦数据在向量数据库中转换为、分割、编码和索引,我们就可以开始查询我们的系统。查询通过 Langchain 的专业任务传递——问答检索(图 10.12 中的步骤 2)。查询用于在向量数据库中执行相似性搜索。检索到的文档与查询一起使用(图 10.12 中的步骤 3)来组成 LLM 的提示(图 10.12 中的步骤 4)。LLM 将仅根据我们提供的上下文来回答查询——来自存储在向量数据库中的数据的上下文。
实现 RAG 系统的代码在参考文献 11中给出。我们将使用 2023 年国情咨文的文本(来自 Kaggle 数据集)作为文档。让我们首先直接使用 LLM 通过提示来回答关于国情咨文的一般问题:
llm = HuggingFacePipeline(pipeline=query_pipeline)
# checking again that everything is working fine
llm(prompt="Please explain what is the State of the Union address. Give just a definition. Keep it in 100 words.")
答案在以下屏幕截图中给出。我们可以观察到 LLM 具有相关信息,并且答案是正确的。当然,如果我们询问的是最近的信息,答案可能就是错误的。
图 10.13:提示的结果(一个不带背景的一般问题)
现在我们来看一些关于我们摄入到向量数据库中的信息的问题的答案。
数据转换、分块和编码使用以下代码完成。由于我们摄入的数据是纯文本,我们将使用 Langchain 的TextLoader。我们将使用ChromaDB作为向量数据库,并使用 Sentence Transformer 进行嵌入:
# load file(s)
loader = TextLoader("/kaggle/input/president-bidens-state-of-the-union-2023/biden-sotu-2023-planned-official.txt",
encoding="utf8")
documents = loader.load()
# data chunking
text_splitter = RecursiveCharacterTextSplitter(chunk_size=1000, chunk_overlap=20)
all_splits = text_splitter.split_documents(documents)
# embeddings model: Sentence Transformer
model_name = "sentence-transformers/all-mpnet-base-v2"
model_kwargs = {"device": "cuda"}
embeddings = HuggingFaceEmbeddings(model_name=model_name, model_kwargs=model_kwargs)
# add documents to the ChromaDB database
vectordb = Chroma.from_documents(documents=all_splits, embedding=embeddings, persist_directory="chroma_db")
我们定义了问题和答案检索链:
retriever = vectordb.as_retriever()
qa = RetrievalQA.from_chain_type(
llm=llm,
chain_type="stuff",
retriever=retriever,
verbose=True
)
我们还定义了一个函数来测试前面的链:
def test_rag(qa, query):
print(f"Query: {query}\n")
time_1 = time()
result = qa.run(query)
time_2 = time()
print(f"Inference time: {round(time_2-time_1, 3)} sec.")
print("\nResult: ", result)
让我们来测试这个系统的功能。我们将针对主题制定查询 – 在这个例子中,是 2023 年国情咨文:
query = "What were the main topics in the State of the Union in 2023? Summarize. Keep it under 200 words."
test_rag(qa, query)
运行上述查询的结果将是:
图 10.14:使用 RAG 系统进行查询和答案(示例 1)
接下来,我们展示同一内容上的不同查询的答案(包含在打印输出中的查询):
图 10.15:使用 RAG 系统进行查询和答案(示例 2)
我们还可以检索用于创建答案背景的文档。以下代码正是如此:
docs = vectordb.similarity_search(query)
print(f"Query: {query}")
print(f"Retrieved documents: {len(docs)}")
for doc in docs:
doc_details = doc.to_json()['kwargs']
print("Source: ", doc_details['metadata']['source'])
print("Text: ", doc_details['page_content'], "\n")
RAG 是一种强大的方法,可以发挥 LLM 推理能力,同时控制信息来源。LLM 给出的答案仅来自通过相似性搜索提取的上下文(问答检索链的第一步),以及我们存储信息的向量数据库。
摘要
在本章中,我们探讨了如何利用 Kaggle 模型中 LLMs 的潜力。我们首先关注了使用此类基础模型的最简单方法——直接提示它们。我们了解到构建提示很重要,并尝试了简单的数学问题。我们使用了 Kaggle 模型中可用的模型以及量化模型,并采用了两种方法进行量化:使用 Llama.cpp 和 bitsandbytes 库。然后,我们将 Langchain 与 LLM 结合起来创建了一系列链式任务,其中一项任务的输出被框架用来为下一项任务创建输入(或提示)。使用 Code Llama 2 模型,我们在 Kaggle 上测试了代码生成的可行性。结果并不完美,除了预期的序列外,还生成了多个序列。最后,我们学习了如何创建一个 RAG 系统,该系统结合了向量数据库的速度、多功能性和易用性,以及 Langchain 的链式功能和 LLMs 的推理能力。
在下一章,也就是我们这本书的最后一章,你将学习一些有用的食谱,这将帮助你使你在平台上的高质量工作更加引人注目和受到赞赏。
参考文献
-
Llama 2,Kaggle 模型:
www.kaggle.com/models/metaresearch/llama-2 -
Mistral,Kaggle 模型:
www.kaggle.com/models/mistral-ai/mistral/ -
Gabriel Preda – 使用数学测试 Llama v2,Kaggle 笔记本:
www.kaggle.com/code/gpreda/test-llama-v2-with-math -
模型量化,HuggingFace:
huggingface.co/docs/optimum/concept_guides/quantization -
Gabriel Preda – 使用 Llama.cpp 量化 Llama 2 的测试,Kaggle 笔记本:
www.kaggle.com/code/gpreda/test-llama-2-quantized-with-llama-cpp -
Gabriel Preda – 使用 llama.cpp 在 CPU 上量化 Llama 2 的测试,Kaggle 笔记本:
www.kaggle.com/code/gpreda/test-of-llama-2-quantized-with-llama-cpp-on-cpu -
Gabriel Preda – 使用 Llama 2 和 Langchain 的简单顺序链,Kaggle 笔记本:
www.kaggle.com/code/gpreda/simple-sequential-chain-with-llama-2-and-langchain/ -
Langchain,维基百科页面:
en.wikipedia.org/wiki/LangChain -
Gabriel Preda – 使用 Code Llama 生成 Python 代码(13b),Kaggle 笔记本:
www.kaggle.com/code/gpreda/use-code-llama-to-generate-python-code-13b -
加布里埃尔·普雷达 – 检索增强生成,结合 LLMs、任务链和向量数据库,Endava 博客:
www.endava.com/en/blog/engineering/2023/retrieval-augmented-generation-combining-llms-task-chaining-and-vector-databases -
加布里埃尔·普雷达 – 使用 Llama 2、Langchain 和 ChromaDB 进行 RAG,Kaggle 笔记本:
www.kaggle.com/code/gpreda/rag-using-llama-2-langchain-and-chromadb
加入我们书籍的 Discord 空间
加入我们的 Discord 社区,与志同道合的人相聚,并和超过 5000 名成员一起学习,详情请见:
第十一章:结束我们的旅程:如何保持相关性和领先地位
我们接近了通过数据科学领域的启迪之旅的尾声,我们已经穿越了多样化的挑战领域,从地理空间分析到自然语言处理,再到图像分类和时间序列预测。这次探险丰富了我们对如何巧妙结合各种尖端技术的理解。我们深入研究了大型语言模型,例如 Kaggle 开发的模型,探索了向量数据库,并发现了任务链框架的效率,所有这些都是为了利用生成式 AI 的变革潜力。
我们的学习之旅还包括处理各种数据类型和格式。我们参与了特征工程,构建了几个基线模型,并掌握了迭代优化这些模型的能力。这个过程对于掌握综合数据分析所必需的众多工具和技术至关重要。
除去技术层面,我们已拥抱数据可视化的艺术。我们不仅学习了技术,还学会了如何根据每个独特的数据集和分析调整风格和视觉效果。此外,我们还探索了如何围绕数据构建引人入胜的故事,从而超越单纯的报告技术,让数据生动起来。
在本章中,我打算分享一些有见地的想法、技巧和窍门。这些不仅可以帮助你在创建有价值且影响深远的数据科学笔记本方面达到精通,还可以帮助你获得对你工作的认可。通过这些指南,你可以确保你的工作脱颖而出,帮助你保持在不断发展的数据科学领域的领先地位。
向最佳学习:观察成功的宗师
在本书的前几章中,我们探讨了各种分析方法、可视化工具和定制选项。这些技术被我和许多其他尊敬的 Kaggle 笔记本宗师有效地利用。我成为第 8 位笔记本宗师并长期保持前三名排名,不仅仅是因为深入分析、高质量的视觉或在我的笔记本中构建引人入胜的故事。这同样是对坚持少数最佳实践的证明。
当我们深入研究这些最佳实践时,我们将了解是什么让成功的 Kagglers 与众不同,特别是 Kaggle 笔记本大师和宗师。让我们从一份迷人的数据集的硬证据开始:Meta Kaggle 大师成就快照。这个数据集(见参考文献 1)包含两个文件:一个详细描述成就,另一个描述用户:
-
在成就文件中,我们看到 Kagglers 在竞赛、数据集、笔记本和讨论类别中达到的层级,以及他们在所有这些类别中的最高排名。此文件仅包括在 Kaggle 四个类别中至少达到 Master 层级的用户。
-
第二个文件提供了这些用户的详细资料,包括头像、地址、国家、地理坐标以及从他们的个人资料中提取的元数据。这些元数据提供了他们对 Kaggle 的任期以及他们在平台上最近的活动情况,例如“13 年前加入,过去一天内最后一次出现。”
我们将分析这些用户在平台上的“最后看到”的天数,并检查 Notebooks 类别中的 Masters 和 Grandmasters 对此指标的分布。解析和提取此信息的代码在此提供,为我们提供了深入了解顶级 Kagglers 习惯和参与度的宝贵窗口:
profiles_df["joined"] = profiles_df["Metadata"].apply(lambda x: x.split(" · ")[0])
profiles_df["last_seen"] = profiles_df["Metadata"].apply(lambda x: x.split(" · ")[1])
def extract_last_seen(last_seen):
"""
Extract and return when user was last time seen
Args:
last_seen: the text showing when user was last time seen
Returns:
number of days from when the user was last time seen
"""
multiplier = 1
last_seen = re.sub("last seen ", "", last_seen)
if last_seen == "in the past day":
return 0
last_seen = re.sub(" ago", "", last_seen)
quantity, metric = last_seen.split(" ")
if quantity == "a":
quantity = 1
else:
quantity = int(quantity)
if metric == "year" or metric == "years":
multiplier = 356
elif metric == "month" or metric == "months":
multiplier = 30
return quantity * multiplier
profiles_df["tenure"] = profiles_df["joined"].apply(lambda x: extract_tenure(x))
profiles_df["last_seen_days"] = profiles_df["last_seen"].apply(lambda x: extract_last_seen(x))
我们以天为单位给出结果。让我们可视化 Notebooks 类别中 Masters 和 Contributors 最后被看到的天数分布。为了清晰起见,我们移除了在过去 6 个月内没有出现过的用户。我们认为这些用户目前不活跃,其中一些用户在平台上最后一次出现的时间长达 10 年前。
同时,过去 6 个月内没有出现过的用户(来自 Notebooks 类别的 Masters 和 Grandmasters)的比例为 6%。对于 Notebooks 类别中其余 94%的 Masters 和 Grandmasters,我们展示了与最后看到的天数相关的分布,如图11.1所示。
图 11.1:Kaggle 平台上用户最后出现的天数的分布
我们可以很容易地看出,Notebooks 类别中的大多数 Masters 和 Grandmasters 每天都会访问平台(最后看到的天数为 0 意味着他们在当天也是活跃的)。因此,每天在线是大多数成功 Masters 和 Grandmasters 的一个属性。我可以从我个人的经验中证实,在我成为 Master 然后成为 Grandmaster 的过程中,我几乎每天都在平台上活跃,创建新的笔记本并使用它们来分析数据集,准备比赛提交以及进行详细分析。
定期回顾和改进你的工作
当我创建一个笔记本时,仅仅把它放在一边然后开始研究新的主题是非常不寻常的。大多数时候,我会多次回到它,并添加新的想法。在笔记本的第一版中,我试图专注于数据探索,真正理解各自数据集(或数据集)的独特特征。在下一版中,我专注于细化图形,并可能为数据准备、分析和可视化提取函数。我更好地组织代码,消除重复部分,并将通用部分保存在实用脚本中。使用实用脚本的最佳部分是,你现在有了可重用的代码,可以在多个笔记本中使用。当我创建实用脚本时,我会采取措施使代码更通用、可定制和健壮。
接下来,我也对笔记本的视觉身份进行了细化。我检查了构图的整体性,对风格进行了调整,以更好地适应我想创造的故事。当然,随着笔记本的成熟和接近稳定版本,我会进一步努力提高可读性,并真正尝试创造一个好的叙述。修订没有限制。
我也会查看评论,并试图回应批评者,同时采纳改进建议,包括新的叙事元素。一个好的故事需要一个好的评论者,大多数时候,笔记本的读者都是优秀的评论者。即使评论无关紧要,甚至负面,我们仍然需要保持冷静和沉着,试图找到问题的根源:我们在分析中是否遗漏了重要方面?我们是否未能对所有数据细节给予足够的关注?我们是否使用了正确的可视化工具?我们的叙述是否连贯?对评论的最佳回应,除了表达你的感激之情外,就是当评论者的建议合理时,采纳这些建议。
让我们将这个原则应用到本书包含的一个项目中。在第四章中,我们学习了如何构建具有多个叠加层的复杂图形,包括市区的多边形和酒吧或星巴克咖啡店的位置。在图 11.2中,我们展示了一张原始地图。在选择星巴克咖啡店之一后,会弹出一个显示商店名称和地址的弹出窗口。这张地图很棒,但弹出窗口看起来并不完全合适,不是吗?文本没有对齐,弹出窗口的大小小于显示所有信息所需的尺寸,而且外观和感觉似乎与地图的质量不相符。
图 11.2:伦敦市区轮廓与星巴克商店位置及弹出窗口(之前的设计)
Chapter 4 to define a CircleMarker with a popup:
for _, r in coffee_df.iterrows():
folium.CircleMarker(location=[r['Latitude'], r['Longitude']],
fill=True,
color=color_list[2],
fill_color=color_list[2],
weight=0.5,
radius=4,
popup="<strong>Store Name</strong>: <font color='red'>{}</font><br><strong>Ownership Type</strong>:{}<br>\
<strong>Street Address</strong>: {}".format(r['Store Name'], r['Ownership Type'],r['Street Address'])).add_to(m)
我们可以使用更复杂的 HTML 代码来定义弹出窗口的布局和内容。以下代码片段用于添加星巴克的店铺标志,将店铺名称作为标题,并在与星巴克颜色协调的 HTML 表格中,显示品牌、店铺编号、所有权类型、地址、城市和邮编。我们将函数代码分成几个部分来分别解释每个部分。
函数的第一部分定义了星巴克图像的 URL。我们使用维基百科的图像作为标志,但在即将到来的屏幕截图中有意将其模糊处理,以遵守版权法。然后,我们定义了一些变量来保持我们将包含在表格中的每列的值。我们还定义了表格背景的颜色。下一行代码用于星巴克图像的可视化:
def popup_html(row):
store_icon = "https://upload.wikimedia.org/wikipedia/en/3/35/Starbucks_Coffee_Logo.svg"
name = row['Store Name']
brand = row['Brand']
store_number = row['Store Number']
ownership_type = row['Ownership Type']
address = row['Street Address']
city = row['City']
postcode = row['Postcode']
left_col_color = "#00704A"
right_col_color = "#ADDC30"
html = """<!DOCTYPE html>
<html>
<head>
<center><img src=\"""" + store_icon + """\" alt="logo" width=100
=100 ></center>
<h4 style="margin-bottom:10"; width="200px">{}</h4>""".format(name) + """
接下来,我们定义表格。每条信息都在表格的单独一行中显示,右列包含变量的名称,左列包含实际值。我们包含在表格中的信息是名称、品牌、店铺编号、地址、城市和邮编:
</head>
<table style="height: 126px; width: 350px;">
<tbody>
<tr>
<td style="background-color: """+ left_col_color +""";"><span style="color: #ffffff;">Brand</span></td>
<td style="width: 150px;background-color: """+ right_col_color +""";">{}</td>""".format(brand) + """
</tr>
<tr>
<td style="background-color: """+ left_col_color +""";"><span style="color: #ffffff;">Store Number</span></td>
<td style="width: 150px;background-color: """+ right_col_color +""";">{}</td>""".format(store_number) + """
</tr>
<tr>
<td style="background-color: """+ left_col_color +""";"><span style="color: #ffffff;">Ownership Type</span></td>
<td style="width: 150px;background-color: """+ right_col_color +""";">{}</td>""".format(ownership_type) + """
</tr>
<tr>
<td style="background-color: """+ left_col_color +""";"><span style="color: #ffffff;">Street Address</span></td>
<td style="width: 150px;background-color: """+ right_col_color +""";">{}</td>""".format(address) + """
</tr>
<tr>
<td style="background-color: """+ left_col_color +""";"><span style="color: #ffffff;">City</span></td>
<td style="width: 150px;background-color: """+ right_col_color +""";">{}</td>""".format(city) + """
</tr>
<tr>
<td style="background-color: """+ left_col_color +""";"><span style="color: #ffffff;">Postcode</span></td>
<td style="width: 150px;background-color: """+ right_col_color +""";">{}</td>""".format(postcode) + """
</tr>
</tbody>
</table>
</html>
"""
return html
接下来,以下代码用于定义要添加到CircleMarker的弹出窗口小部件,以替换之前用字符串格式定义的弹出窗口。请注意,我们用对新定义的popup函数的调用替换了之前的弹出窗口代码:
for _, r in coffee_df.iterrows():
html = popup_html(r)
popup = folium.Popup(folium.Html(html, script=True), max_width=500)
folium.CircleMarker(location=[r['Latitude'], r['Longitude']],
fill=True,
color=color_list[2],
fill_color=color_list[2],
weight=0.5,
radius=4,
popup = popup).add_to(m)
在图 11.3中,我们展示了弹出窗口的改进版本,其中我们使用 HTML 代码生成一个更高质量的弹出窗口:
图 11.3:伦敦地区轮廓图,星巴克店铺位置和弹出窗口(当前,新设计)
认识到他人的贡献,并加入你个人的风格
要在 Kaggle 等平台上提升你的笔记本,持续进行改进和创新至关重要,既要借鉴社区反馈,也要借鉴他人的工作。根据建设性评论定期回顾和更新你的笔记本,表明你对卓越的承诺。你还可以看看别人都做了什么。仅仅复制他们的工作不会给你带来太多的点赞。然而,如果你从其他用户的工作开始,通过扩展他们的观察并改进可视化或结果解释来带来新的见解,这可以帮助你在排名中上升。
此外,正确地说明你从别人的工作中开始,并清楚地了解你自己的贡献,这一点非常重要。如果你想结合来自不同来源的笔记本想法,建议从包含最多内容的来源进行分支。仔细为你在自己的笔记本中包含的他们工作的部分进行致谢。
当从多个来源采纳想法时,花些时间对齐符号、编程约定、函数、类和实用脚本,确保你不会创建出一个弗兰肯斯坦式的笔记本,而是一个代码感觉统一的笔记本。
当然,更重要的是要努力在可视化的外观和感觉以及笔记本的风格上创造一致性。平台用户会返回并欣赏你的工作,不仅是因为其质量,还因为你的个人风格。即使是从其他用户的笔记本开始,通过分叉它,也要保持你的个人风格。
要迅速:不要等待完美
一些快速崛起的新 Kaggle 笔记本大师有一些共同点:他们在新的竞赛启动后仅用几天,有时甚至几小时,就开始分析数据并发布探索性数据分析或基线模型解决方案。他们是第一批在 Kaggle 不断变化的数据探索领域中占据新领域的人。通过这样做,他们将追随者的注意力集中在他们的工作上,他们收到了最多的评论,这有助于他们改进工作,并且他们的工作会被许多人分叉(为了方便)。这反过来又增加了他们笔记本的病毒性。
然而,如果你等待太久,你可能会发现你的分析想法也被其他人想到了,等你最终将其完善到足以满足你的标准时,一大群人已经对其进行了探索、发表并获得了认可。有时,关键在于速度;有时,则在于原创性。在许多情况下,成功的 Kaggle 笔记本大师在处理新的竞赛数据方面都是早起的鸟儿。
数据集和模型也是如此:首先发布,然后根据之前的建议继续完善和改进原始工作的人会获得更多的追随者和来自评论的反馈,他们可以将这些反馈应用于进一步的改进,并从平台上的病毒性因素中受益。
要慷慨:分享你的知识
一些最受欢迎的 Kaggle 笔记本大师的崛起不仅归功于他们能够创建出精美叙述的笔记本,还归功于他们愿意分享重要的知识。通过提供高质量、解释详尽的模型基线,这些大师赢得了追随者的广泛赞誉,获得了稳固的地位,并在笔记本类别中攀升排名。
在 Kaggle 平台上,用户多次分享了关于数据的见解,这些见解对于显著提高竞赛提交的模型至关重要。通过提供有用的起点、突出重要的数据特征或提出解决新类型问题的方法,这些用户加强了社区,并帮助他们的追随者提高技能。除了通过 notebooks 获得认可,这些 Grandmasters 还通过讨论和数据集传播有价值的信息。他们创建并发布额外的数据集,帮助竞争者完善他们的模型,并在与特定竞赛或数据集相关的讨论主题中提供建议。
许多成功的 Kaggle Notebook Grandmasters,如 Bojan Tunguz、Abhishek Thakur 和 Chris Deotte,他们是四重 Grandmasters(在所有类别中都达到了最高级别),在讨论和数据集中广泛分享他们的知识。在长期担任 Kaggle Grandmasters 的典范人物中,有 Gilberto Titericz(Giba),他曾是竞赛中的第一名,以通过 notebooks 分享见解和在备受瞩目的特色竞赛中提供新视角而闻名。这些顶级 Kagglers 表明,在所有类别中保持活跃不仅增强了他们在每个单独类别中的个人档案,而且对他们的整体成功做出了重大贡献。他们持续的平台存在,加上他们的谦逊、愿意回答问题和在讨论部分帮助他人,体现了慷慨和协作的精神。记住他们在自己通往顶峰的旅程中获得的帮助,他们发现帮助他人进步是一种满足感,这是维持他们在 Kaggle 社区中显赫地位的关键因素。
走出你的舒适区
保持领先比达到目标更困难。Kaggle 是一个极具竞争性的协作和竞赛平台,它位于信息技术行业中增长最快、变化最大的领域之一,即机器学习。这个领域的变革速度难以跟上。
在 Kaggle 最高排名者中保持位置可能是一项艰巨的任务。特别是在 Notebooks 中,进步可以比在竞赛中更快(而且竞争非常激烈),非常才华横溢的新用户经常出现,挑战那些位于最高位置的人。要保持领先,你需要不断革新自己,而除非你走出舒适区,否则无法做到这一点。试着每天学习新事物,并且立即付诸实践。
挑战自己,保持动力,投身于你认为困难的事情。你还需要探索平台上的新功能,这为你提供了为对生成 AI 最新应用感兴趣的 Kagglers 创建教育性和吸引性内容的新机会。
现在,您可以使用笔记本将数据集和模型结合起来,创建原创且富有信息量的笔记本,例如,展示如何创建一个检索增强生成系统(见参考文献 2)。这样一个系统结合了大型语言模型的强大“语义大脑”、从向量数据库索引和检索信息的灵活性,以及 LangChain 或 LlamaIndex 等任务链框架的通用性。在第十章中,我们探讨了 Kaggle 模型在构建此类强大应用方面所提供的丰富潜力。
感恩
感恩在通过 Kaggle 等级晋升至笔记本大师级并登上排行榜前列的过程中起着至关重要的作用,尽管这往往被忽视。这不仅仅关乎创作出具有吸引力的叙事的优秀内容;对社区支持的感激同样重要。
当你在 Kaggle 上变得活跃并赢得通过点赞和有见地的评论支持你工作的追随者时,承认并表达对这种支持的感激至关重要。深思熟虑地回应评论,认可有价值的建议,向那些分支你数据的人提供建设性的反馈,这些都是表达感激的有效方式。虽然分支可能不会直接像点赞那样直接贡献于赢得奖牌,但它们增加了你工作的可见性和影响力。将模仿视为真诚的赞赏形式,并对它带来的社区参与表示感谢,这加强了你在平台上的存在,并培养了一个支持性和协作的环境。
摘要
在本章的最后,我们回顾了 Kaggle 上优秀笔记本内容作者的“秘诀”。他们有几个共同点:他们在平台上保持持续活跃,早期开始处理新的数据集或竞赛数据集,不断改进他们的工作,认可并赞赏他人创作的优质内容,是持续学习者,谦逊,分享他们的知识,并且不断在舒适区之外工作。这些并非目标本身,而是对分析数据和创建优秀预测模型所知一切充满热情和持续兴趣的体现。
当我们结束这本书,让你开始 Kaggle 笔记本冒险之旅时,我祝愿你一路平安。希望你喜欢阅读,请记住,数据科学的世界是持续变化的。继续实验,保持好奇心,带着自信和技能深入数据。愿你的未来 Kaggle 笔记本充满惊人的洞察力,灵光一现的时刻,也许还有一些令人挠头的挑战。快乐编码!
参考文献
-
Meta Kaggle-Master Achievements Snapshot,Kaggle 数据集:
www.kaggle.com/datasets/steubk/meta-kagglemaster-achievements-snapshot -
Gabriel Preda,使用 Llama 2、LangChain 和 ChromaDB 的 RAG,Kaggle 笔记本:
www.kaggle.com/code/gpreda/rag-using-llama-2-langchain-and-chromadb
加入我们书籍的 Discord 空间
加入我们的 Discord 社区,与志同道合的人相聚,并在以下地点与超过 5000 名成员一起学习: