[{"content":"\n我知道，市面上有很多种颜色选择工具。但我认为，你会发现使用一种直接在笔记本中工作的工具更有优势。此工具也是完全可定制的。\n我们将构建两个颜色选择器：\n简单选择器——从一张图片中选择一种颜色 复杂选择器——从多幅图像中选择颜色列表并显示颜色 最后，我们讨论一下数据科学中的一些应用。这些是我使用颜色选择器的方法。\n导入 让我们深入研究代码。你还可以在 GitHub1 上找到完整的项目。\n首先，我们有一些导入。我们有标准包（第 2-3 行）。mpimg 用于加载图像，pyperclip 用于将字符串保存到剪贴板，glob 用于处理文件路径。确保已安装所有这些。\nimport numpy as np import matplotlib.pyplot as plt import matplotlib.image as mpimg import pyperclip import random import glob 我们将从不同的鸟类图像中挑选颜色。你可以在文章的封面图片中看到一些示例。我们在给定的目录（第 2 行）中加载所有图像路径（第 3 行）。\n# 数据集 data = dp + \u0026#39;birds/\u0026#39; img_path = glob.glob(data + \u0026#39;*.jpg\u0026#39;) 简单的颜色选择器 在图 1 中，你可以看到我们的第一个选择器正在运行。每次我们点击图像中的某个位置时，该像素的 RGB 通道都会保存到剪贴板中。然后我们可以将该值直接粘贴到笔记本中。\n要创建此选择器，我们从 onclick 函数开始。每次单击图像时都会运行此函数。我们获取单击的 x 和 y 坐标（第 5-6 行）。然后，我们获取该坐标处像素的 RGB 通道（第 9 行）。最后，我们将这些通道作为字符串保存到剪贴板（第 12 行）。\ndef onclick(event): global img # get x, y of click x = round(event.xdata) y = round(event.ydata) # Get RGB values rgb = img[y][x] # save to clip board pyperclip.copy(str(rgb)) 要使用此函数，我们首先使用 matplotlib 创建一个图形（第 3 行）。然后，我们使用 mpl_connect 函数向该图形添加交互功能（第 10 行）。你可以看到，我们已将 onclick 函数作为参数传入。我们还加载了其中一张鸟类图像（第 6-7 行）并显示它（第 11 行）。\nglobal img fig = plt.figure(figsize=(5,5)) # Load image and add count path = img_path[-1] img = mpimg.imread(path) # Add an interactive widget to figure cid = fig.canvas.mpl_connect(\u0026#39;button_press_event\u0026#39;, onclick) plt.imshow(img) plt.show() 另一件需要注意的事情是使用全局变量（第 1 行）。这允许在 onclick 函数中更新这些变量。这是将图像作为参数传递的替代方法。我们还可以添加一行 %matplotlib notebook。这会将图形保留在笔记本中。\n复杂的颜色选择器 现在让我们来调整一下。在图 2 中，我们现在有一个图像框（左）和颜色框（右）。我们现在可以看到我们点击的像素的颜色，并遍历多个图像。另外，请注意颜色框左上角的红色数字。我们能够将颜色保存到列表中，并且每次这样做时，这个数字都会更新。\n再次，我们从 onclick 函数开始。这类似于之前的颜色选择器。主要区别在于我们现在运行函数 change_choice，而不是保存 RGB 通道。我们还更新了一个全局 rgb 变量。这样它就可以被下面的其他函数访问。\ndef onclick(event): global img global rgb # get x,y of click x = round(event.xdata) y = round(event.ydata) # get RGB values rgb = img[y][x] #Update second plot with colour change_choice() 我们有一个函数 onpress，它会在按下键盘时运行。我们首先获取键（第 6 行）。接下来发生的事情取决于按下了什么键：\nn（下一步）：我们运行 change_image 函数 c（复制）：我们将 RGB 通道保存到剪贴板（第 13 行）和颜色列表（第 16 行）。我们还运行 change_choice 函数。 请记住，全局 rgb 变量在 onclick 函数运行时更新。这意味着当我们按下 “c” 时，最近一次点击的 RGB 通道将被保存。\ndef onpress(event): global rgb global colours #Get key key = event.key if key == \u0026#39;n\u0026#39;: change_image() elif key == \u0026#39;c\u0026#39;: # save to clip board pyperclip.copy(str(rgb)) # add to list of colours colours.append(rgb) change_choice() change_choice 用于更新颜色框。要创建此框，我们使用与图像框相同的尺寸（第 13 行）。颜色框中的每个像素都将具有当前全局 rgb 值的 RGB 通道（第14 行）。我们还删除当前计数（第 9-10 行），然后更新它（第 18 行）。为此，我们使用已保存的颜色列表的长度。\ndef change_choice(): global img global ax global colours global rgb # remove previous count for txt in ax[1].texts: txt.set_visible(False) # create array of colour choice dims = np.shape(img) col = np.array([[rgb]*dims[0]]*dims[1]) ax[1].imshow(col) # update colour count ax[1].text(0, 15, len(colours),color=\u0026#39;r\u0026#39;,size=20) plt.show() change_choice 函数运行时有两个动作：\n当我们点击图像时，通过 onclick 函数。在这种情况下，全局 rgb 会更新，并且框中的颜色会发生变化。 当我们按下 “c” 时，通过 onpress 函数。这里颜色列表的长度增加了，红色的数字也会改变。 最后，我们有 change_image 函数。每当我们按 “n” 时，它都会用来更新图像框。我们首先关闭所有现有图（第 8 行）。然后创建一个新图（第 10 行），并为其添加单击（第 13 行）和按下（第 14 行）功能。我们加载并显示一个随机的鸟类图像（第 17-20 行）。然后我们更新颜色框（第 24 行）。通过首先将全局 rgb 变量设置为 [255,255,255]，我们将框颜色设置为白色。\ndef change_image(): global img_path global img global ax global rgb # close all open plots plt.close(\u0026#39;all\u0026#39;) fig,ax = plt.subplots(1,2,figsize=(10,5)) # add an interactive widget to figure cid = fig.canvas.mpl_connect(\u0026#39;button_press_event\u0026#39;, onclick) cid2 = fig.canvas.mpl_connect(\u0026#39;key_press_event\u0026#39;, onpress) # load random image path = random.choice(img_path) img = mpimg.imread(path) ax[0].imshow(img) # reset the colour window rgb = [255,255,255] change_choice() 我们可以通过运行 change_image 函数（第 12 行）来启动颜色选择器。请注意，我们现在在第 1 行有 %matplotlib tk。这将在笔记本外的窗口中打开颜色选择器。如果你尝试直接在笔记本中运行它，它实际上将不起作用。如果有人能解决这个问题，请在评论中告诉我 :)。\n%matplotlib tk global img_path global colours colours = [] # load image paths img_path = glob.glob(data + \u0026#34;*.jpg\u0026#34;) # start widget change_image() 当你遍历图像并保存颜色时，颜色列表将会更新。图 3给出了此类列表的一个示例。这来自我们在图 2中看到的鸟类图像。\n数据科学应用 我想与你分享这段代码，因为我发现它在我的数据科学之旅中很有用。在本文的其余部分，我们将讨论一些应用程序。\n合并图表颜色 你可以说我是个完美主义者，但在展示工作时，我喜欢所有图表都采用相同的配色方案。问题是我倾向于使用多个 Python 包。在图 4中，你可以看到 matplotlib（左）和SHAP （右）使用的默认颜色之间的差异。\n使用第一个颜色选择器，我能够解决这个问题。我可以用 Python 保存这些图表、加载它们并选择它们的颜色。更新 matplotlib 图表很简单。或者，我们未来可以写一篇《[[如何自定义 SHAP 图]]》\nLeonie Monigatti有一个关于的自定义 SHAP 图很好的教程。\n你可能还会发现下面的代码很有用。它将 RGB 通道转换为十六进制字符串。我发现有些包只接受它作为颜色参数。\n# 将 RGB 转换为十六进制 from colormap import rgb2hex rgb2hex(134,94,58) 利用图像数据进行特征工程 第二个应用是我决定构建更复杂的颜色选择器的原因。我用它来创建机器学习的功能。在图 5 中，你可以看到橙色轨道如何与图像的其余部分隔离。轨道像素的颜色是使用颜色选择器获得的。\n后面，我将更详细地介绍此应用程序。我将发布一篇有关图像数据特征工程的文章。除了上述方法外，它还将包括灰度、裁剪和边缘检测。\n正好，我最近也在写关于 CV 方向的基础教程《人工智能CV核心知识》，有兴趣的可以去查看下。\n希望这篇文章对你有所帮助！你还可以阅读我的其他文章，或者查看有关企业 AI 实战项目的教程，相信会让你拥有更多收获。\n「AI秘籍」系列课程：\n人工智能应用数学基础\n人工智能Python基础\n人工智能基础核心知识\n人工智能BI核心知识\n人工智能CV核心知识\n参考 Github, https://github.com/hivandu/public_articles/blob/main/src/image_tools/colour_picker.ipynb\u0026#160;\u0026#x21a9;\u0026#xfe0e;\n","permalink":"https://hivan.me/posts/%E4%BD%BF%E7%94%A8-python-%E6%9E%84%E5%BB%BA%E9%A2%9C%E8%89%B2%E9%80%89%E6%8B%A9%E5%99%A8/","summary":"\u003cblockquote\u003e\n\u003cp\u003e创建一个从图像像素中选择 RGB 通道的工具\u003c/p\u003e\u003c/blockquote\u003e","title":"使用 Python 构建颜色选择器"},{"content":"\n数据收集可能很耗时、昂贵，而且说实话，很无聊。当我们收集数据的能力有限时，数据增强可以发挥重要作用。它可以帮助我们构建更强大的数据集，减少过度拟合并增加训练数据量。\n我们将讨论数据增强及其好处。我们还将保持实用性。即通过介绍用于增强图像的 Python 代码。具体来说，我们将讨论如何进行这些增强：\n翻转图像\n调整亮度\n随机颜色抖动\n随机噪声（高斯噪声、椒盐噪声和删除噪声）\n最后，我们将讨论增强图像方面的最佳实践。具体来说，如何最好地验证我们的模型并在生产中对其进行测试。\n用例——自动驾驶汽车 为了让事情变得有趣，我们将增强自动驾驶汽车的图像。你可以在图 1 中看到我们的意思。汽车前面有一个摄像头，模型使用这些图像进行预测。然后根据预测来引导汽车。\n目标是让汽车在赛道上行驶，同时保持在橙色线内。在此过程中，我们将讨论哪些增强功能对于此特定应用有意义。这是为了强调数据增强方面的选择需要一些批判性思考。\n收集可靠的数据集 在讨论这个问题之前，我们有必要讨论一下数据收集。这是因为数据增强是收集稳健数据集的替代或补充方法。\n什么是稳健数据集？ 稳健的数据集可以反映模型预期执行的所有条件。这些条件由诸如照明条件、摄像头角度、房间颜色或背景中的物体等变量决定。使用这样的数据集进行训练将产生一个能够适应这些变量变化的模型。\n稳健的数据集=稳健的模型\n一个很好的例子来自我们在自动驾驶汽车方面的经验。我们收集数据，训练模型并部署它。它运行完美！直到我们打开了百叶窗\u0026hellip;\n阳光在赛道上反射，让模型“困惑”。在这种情况下，模型无法做出准确的预测。换句话说，模型对光照条件的变化不够稳健。\n如何收集可靠的数据集 构建可靠的数据集始于良好的数据收集策略。你需要考虑所有会影响条件的变量。然后，你需要收集能够捕捉这些变量变化的数据。例如，对于不同的照明条件，我们可以：\n打开或关闭灯\n打开和关闭窗帘\n在一天中的不同时间收集数据\n其他变量是图像背景的不同方面。这包括墙壁和地毯的颜色以及背景中的不同物体。为了解释这些，我们可以：\n收集不同房间的数据\n将不同的物体移到背景中\n通过这些更改，我们正在向数据集添加噪音。希望模型能够学会忽略这些噪音，只使用轨迹进行预测。换句话说，我们希望模型使用真正的原因而不是关联。\n数据增强的好处 数据增强是指我们使用代码系统地或随机地更改图像。这使我们能够人为地引入噪声并增加数据集的大小。实际上，其目的与数据收集相同，因此其好处也类似。\n建立强大的数据集 我们常常受限于可以收集的数据量。在这种情况下，数据增强可以帮助提高数据集的稳健性。即使你已经收集了大量数据，增强也可以提供额外的稳健性。\n不过，要做到这一点，我们需要认真思考增强的类型。也就是说，它们应该模拟我们期望在现实世界中看到的条件。例如，稍后我们将看到如何调整图像的亮度以模拟不同的光照条件。\n减少对一组条件的过度拟合 通过良好的增强，我们可以减少过度拟合。需要明确的是，这与对训练集的过度拟合不同。以图 3 为例。假设我们只在一个房间中收集数据。结果，模型将背景中的物体与左转预测联系起来。\n物体将出现在我们收集的所有图像中。这意味着它将出现在训练、验证甚至测试集中。该模型可以在所有这些集合上表现良好，但在生产中仍然表现不佳。例如，如果我们移除物体，它可能会变得“困惑”并且不会左转。换句话说，模型过度拟合了我们数据集中反映的条件。\n数据增强可以帮助解决这种类型的过度拟合。稍后，我们将看到删除像素如何有助于解决上述示例。也就是说，我们可以人为地从背景中移除物体。\n模型融合 我们可以用多种不同的方式增强同一张图像。这可以人为地增加我们的数据集的大小。考虑到深度学习需要大量数据集，这可以帮助模型参数的收敛。\n使用 Python 增强数据 好了，记住这些，让我们开始实际增强数据。我们将介绍代码，你也可以在 GitHub1 上找到该项目。\n首先，我们将使用下面的导入。我们有一些标准包（第 1-2 行）。Glob用于处理文件路径（第 6 行）。我们还有一些用于处理图像的包（第 10-13 行）。\nimport numpy as np import matplotlib.pyplot as plt plt.style.use(\u0026#39;default\u0026#39;) import os import glob import string import random import torchvision.transforms as transforms import matplotlib.image as mpimg from PIL import Image, ImageEnhance import cv2 如上所述，我们将增强用于驱动自动驾驶汽车的图像。你可以在数据集中找到这些示例。这些图像都是 224 x 224 像素。我们使用下面的代码显示其中一个。\n记下图像名称（第 3 行）。前两个数字是 224 x 224 框架内的 x 和 y 坐标。在图 4中，你可以看到我们使用绿色圆圈显示了这些坐标（第 11 行）。\ndata = dp + \u0026#39;JatRacer_Images/direction/\u0026#39; name = \u0026#34;32_50_c78164b4-40d2-11ed-a47b-a46bb6070c92.jpg\u0026#34; #从名称获取 x,y 坐标 x = int (name.split( \u0026#34;_\u0026#34; )[ 0 ]) y = int (name.split( \u0026#34;_\u0026#34; )[ 1 ]) #加载图像并为坐标添加圆圈 img = mpimg.imread(read_path + name) img = img.copy() cv2.circle(img, (x, y), 8 , ( 0 , 255 , 0 ), 3 ) plt.imshow(img) 这些坐标是目标变量。模型使用图像来尝试预测它们。然后使用这个预测来引导汽车。在本例中，你可以看到汽车即将左转。理想的方向是朝着绿色圆圈给出的坐标行驶。\n翻转图像 假设我们收集了逆时针方向（即仅左转）的一组图像。如果我们想让汽车右转，就必须收集更多数据。或者，由于我们的轨道是对称的，我们可以在 x 轴上翻转图像。\n我们使用 flip_img 函数来实现这一点。请记住，在水平轴上翻转时，x 坐标也需要调整。我们在第 9 行通过从 224（图像宽度）中减去当前坐标来实现这一点。你可以在图 6中看到此函数的结果。\ndef flip_img(name, img): \u0026#39;\u0026#39;\u0026#39;Invert image and target on x axis\u0026#39;\u0026#39;\u0026#39; # flip image img = cv2.flip(img, 1) # flip target variable s = name.split(\u0026#39;_\u0026#39;) s[0] = str(224 - int(s[0])) name = \u0026#39;_\u0026#39;.join(s) return name, img 即使你已经收集了两个方向的数据，翻转图像也是有意义的。这使我们能够将数据集的大小翻倍。但是垂直翻转呢？\n对于某些应用来说，这可能有意义。但对于我们的自动驾驶汽车来说……就没那么有意义了。请看图 7。垂直翻转意味着汽车将在天花板上行驶。除非我们在太空中行驶，否则这不是我们在生产中期望的情况。\n调整亮度 使用 adjust_brightness，我们可以使用 factor 参数来改变图像的亮度。如图 8 所示，如果我们增加因子（1.5），图像就会更亮。同样，如果因子小于1，图像就会变暗。\ndef adjust_brightness(img, factor=1): \u0026#39;\u0026#39;\u0026#39; Invert image on x axis factor: brightness level \u0026lt; 1 will decrease and \u0026gt; 1 will increase \u0026#39;\u0026#39;\u0026#39; img = Image.fromarray(img) enhancer = ImageEnhance.Brightness(img) img = enhancer.enhance(factor) return img 此功能可以帮助我们模拟不同的照明条件。我们可以发现，如果我们在数据收集期间打开和关闭灯光，我们可以得到类似的结果。\n颜色抖动 我们可以使用抖动函数进一步实现这些类型的增强。这将随机改变图像的亮度、对比度、饱和度和色调。使用这些参数，我们可以定义这些方面可以变化的程度。你可以在图 9 中看到一些示例。这些是使用默认参数值创建的。\ndef jitter(img, b=0.2, c=0.2, s=0.2, h=0.1): \u0026#39;\u0026#39;\u0026#39; Randomly alter brightness, contrast, saturation, hue within given range Note: img should be a PIL image \u0026#39;\u0026#39;\u0026#39; img = Image.fromarray(img) transform = transforms.ColorJitter(brightness=b, contrast=c, saturation=s, hue=h) # Apply transform img = transform(img) return img 同样，你需要考虑这些增强是否适合你的应用程序。你可以看到，默认情况下，我们将色调因子设置为 0.1（即 h=0.1）。如图 10 所示，较高的色调因子将返回具有不同颜色轨迹的图像。然而，在生产中，我们的轨迹将始终为橙色。\n我们还应该考虑这些类型的转换的局限性。它们调整整个图像的颜色。实际上，照明条件更加复杂。阳光可以从不同的角度反射出赛道。赛道的某些部分可能比其他部分更暗。如果你真的想捕捉这种噪音，你必须通过良好的数据收集来实现。\n输入噪声 一种不太系统的方法是随机引入噪声。你可以在图 11 中看到一些示例。在每种情况下，我们都可以调整引入的噪声量。\n在进行这些增强时，请记住，我们的 224 x 224 图像中的每个像素都有 3 个通道 - R、G、B。每个通道可以取 0 到 255 之间的值。这些决定了像素的颜色。\n图 11 中的第一行是使用 gaussian_noise 函数创建的。我们创建一个与图像具有相同维度（224 x 224 x 3）的随机噪声数组（第 4-5 行）。此数组中的每个元素都将从均值为 0 且方差为给定值（var）的正态分布中采样。将其添加到图像中将以随机量调整 R、G、B 通道。\ndef gaussian_noise(img, var=5): \u0026#39;\u0026#39;\u0026#39; Add guassian noise to image \u0026#39;\u0026#39;\u0026#39; dims = np.shape(img) noise = np.random.normal(0, var, size=dims).astype(\u0026#39;uint8\u0026#39;) img = img + noise return img sp_noise 函数的工作方式类似。只不过现在我们以给定的概率 ( prob ) 随机将像素更改为黑色或白色。你可以在图 11 的第二行中看到这一点。\ndef sp_noise(img, prob=0.1): \u0026#39;\u0026#39;\u0026#39; Add salt and pepper noise to image \u0026#39;\u0026#39;\u0026#39; height, width, channels = np.shape(img) img = np.array(img) for i in range(height): for j in range(width): # randomly change pixel values if random.random() \u0026lt; prob: if random.random() \u0026lt; 0.5: img[i][j] = np.array([255, 255, 255]) # white else: img[i][j] = np.array([0, 0, 0]) # black img = Image.fromarray(img) return img 高斯噪声和椒盐噪声会降低图像质量。在生产中，模型可能会使用不同质量的图像进行预测。这些增强功能可以帮助创建一个能够抵御这些变化的模型。\ndelete_square 函数是添加噪声的另一种方法。它的工作原理是删除图像的大块。更具体地说，它将具有给定尺寸（像素）的随机正方形变成黑色。图 11 的最后一行给出了示例。\ndef delete_square(img, pixels=20): \u0026#39;\u0026#39;\u0026#39; Delete random square from image \u0026#39;\u0026#39;\u0026#39; img = np.array(img) h, w, channels = np.shape(img) # Random starting pixel rh = random.randint(0, h) rw = random.randint(0, w) sub = round(pixels/2) add = pixels-sub # Boundries for square hmin = max(rh-sub, 0) hmax = min(rh+add, h-1) vmin = max(rw-sub, 0) vmax = min(rw+add, w-1) # Turn pixel within range black img[hmin:hmax,vmin:vmax] = np.array([0,0,0]) img = Image.fromarray(img) return img 删除还可以帮助构建更强大的模型。在进行预测时，模型可能会专注于特定特征。例如，我们的模型可能仅使用外部橙色车道。删除图像的部分内容将迫使模型使用多个特征。因此，如果其中一个特征出现问题，模型可能仍能做出准确的预测。\n尽管删除操作很耗时，但你可能希望采用更系统的方法。即排除图像的特定部分。你可以在图 12 中看到这一点。在这里，我们从背景中删除了椅子。这样模型就不会将其与右转联系起来。\n测量增强的效果 我们已经了解了不同类型的增强。我们还了解了如何改变其中一些增强的级别。实际上，类型和级别可以视为超参数。在调整这些增强时，务必要记住一些事项。\n不要扩充测试集 假设我们扩充整个数据集并进行训练和测试分割。这可能会导致对模型性能的估计过高。这是因为对训练集的过度拟合并不一定会导致测试集上的性能不佳。\n以我们在图 8 中看到的亮度增强为例。其中一些可能最终用于训练，而另一些则用于测试集。现在考虑其他增强也会发生同样的事情。你可以看到测试集与训练集非常相似。\n事实上，最好的做法是根本不要扩充测试集。这是因为测试集用于评估模型在生产中的性能。在这里，模型不应该对扩充的数据进行预测。\n并非所有条件都反映在测试集中 同时，你需要考虑到你的测试集不够稳健。因此，良好的测试性能可能并不意味着在生产中也有良好的性能。这是因为你的模型可能会面临测试集中未捕获的条件。因此，要真正了解增强的影响，我们需要在生产中对其进行测试。\n这让我们陷入了困境。生产测试非常耗时，而且你无法在所有环境中进行测试。这意味着无法衡量增强的效果。最终，你需要认真思考哪些增强适合你的应用程序。领域知识和经验可能更能说明哪些增强有效。\n希望这篇文章对你有所帮助！你还可以阅读我的其他文章，或者查看有关企业 AI 实战项目的教程，相信会让你拥有更多收获。\n「AI秘籍」系列课程：\n人工智能应用数学基础\n人工智能Python基础\n人工智能基础核心知识\n人工智能BI核心知识\n人工智能CV核心知识\n参考 H. Naveed, Survey: Image Mixing and Deleting for Data Augmentation (2021) https://arxiv.org/abs/2106.07085\nGaudenz Boesch, Image Data Augmentation for Computer Vision in 2022 (Guide) https://viso.ai/computer-vision/image-data-augmentation-for-computer-vision\nHow to Randomly change the brightness, contrast, saturation and hue of an image in PyTorch (2022) https://www.geeksforgeeks.org/how-to-randomly-change-the-brightness-contrast-saturation-and-hue-of-an-image-in-pytorch/\nC. Shorten \u0026amp; T.M. Khoshgoftaar, A survey on Image Data Augmentation for Deep Learning (2019) https://journalofbigdata.springeropen.com/articles/10.1186/s40537-019-0197-0\nJason Brownlee, Train Neural Networks With Noise to Reduce Overfitting (2019) https://machinelearningmastery.com/train-neural-networks-with-noise-to-reduce-overfitting/\nGithub, https://github.com/hivandu/public_articles/blob/main/src/image_tools/image_augmentation.ipynb\u0026#160;\u0026#x21a9;\u0026#xfe0e;\n","permalink":"https://hivan.me/posts/%E5%A2%9E%E5%BC%BA%E6%B7%B1%E5%BA%A6%E5%AD%A6%E4%B9%A0%E7%9A%84%E5%9B%BE%E5%83%8F/","summary":"\u003cblockquote\u003e\n\u003cp\u003e使用 Python 通过翻转、调整亮度、颜色抖动和随机噪声来增强数据\u003c/p\u003e\u003c/blockquote\u003e","title":"增强深度学习的图像"},{"content":"\n人工智能Python基础 Python的特性和语法\n初识Python脚本\nPython3 运算符\nPython的流程控制\n模块化编程\nPython的高阶函数\nPython的内置函数\n数据类型 - 字符串详解\n数据类型 - 列表详解\n数据类型 - 元组详解\n数据类型 - 字典\n数据类型 - 集合详解\nPython的文件操作\n练习：登录注册系统\n系统内置模块\n练习：万年历\n正则表达式\nPython中的模块与包\n第三方库的管理和虚拟环境\n异常处理\n面向对象及特性\n面向对象 - 高阶\n描述符和设计模式\n装饰器语法与应用\nmatplotlib\nNumPy\nPandas\nAI 秘籍 —— Python 篇 PDF 发布\n人工智能应用数学基础 人工智能数学基础篇 导言\n数学导论 - 概述\n数学导论 - 微积分基础（导数）\n数学导论 - 线性代数基础（矩阵）\n数学导论 - 概率-统计基础（随机变量）\n数学导论 - 图论（图的概念）\n函数\n极限\n连续\n微积分：导数\n链式法则\n微积分 - 偏导数-方向导数\n微积分 - 梯度-积分\n牛顿-莱布尼兹公式-泰勒展开\n线性代数 - 线性方程组\n线性代数 - 克拉默法则\n线性代数 - 矩阵的性质\n线性代数 - 矩阵的逆\n线性代数 - 线性变换\n线性代数 - 几何概型\n概率与统计 - 概型、概率和随机变量\n概率与统计 - 数学期望、统计描述-分布\n概率与统计 - 贝叶斯统计-机器学习分类指标\n图论 - 图的由来和构成\n图论 - 图的表示-种类\n图论 - 路径和算法\n图论 - 树\n数学篇电子书\n人工智能基础核心知识 人工智能核心基础 - 规划和概要 人工智能核心基础 - 导论1 人工智能核心基础 - 导论2 人工智能核心基础 - 导论3 机器学习入门 - 动态规划 机器学习入门2 - 理解特征和向量 机器学习入门3 - 了解K-means 机器学习- 线性回归 机器学习- 逻辑回归 机器学习-评测指标 机器学习 - 评价指标2 机器学习 - 拟合 机器学习 - 数据集的处理 机器学习 - KNN - 贝叶斯 机器学习 - 支持向量机 机器学习 - 决策树 机器学习 - 随机森林 深度学习 - 从零理解神经网络 深度学习 - 用函数解决问题 深度学习 - 多层神经网络 深度学习 - 拓朴排序的原理和实现 深度学习 - 自动求导计算的实现 深度学习 - 完成神经网络框架 深度学习进阶 - 矩阵运算的维度和激活函数 深度学习进阶 - 权重初始化，梯度消失和梯度爆炸 深度学习进阶 - 深度学习的优化方法 深度学习进阶 - 为什么RNN 深度学习进阶 - LSTM 深度学习进阶 - 卷积的原理 深度学习进阶 - 池化 深度学习进阶 - 全连接层及网络结构 深度学习进阶 - Transfer Learning Exercise/ Captcha Recognition 人工智能BI核心知识 员工离职预测 男女声音识别 XGBoost LightGBM vs CatBoost，具体实现分析 金融行业中 Fintech 的应用场景 量化交易，简单的炒股策略实现 量化交易，如何编写代码来利用 MACD 决定选股策略 万字长文，银行如何做贷款违约的预测，特征处理及学习 数据可视化，基本图形绘制 决策树的使用及可视化 如何在 Python 中进行分词并展示词云 可视化在项目蒸汽量预测的过程及应用 可视化看板发布 推荐系统之矩阵分解 推荐系统之ALS原理 推荐系统之ALS实现 Surprise工具箱：Baseline SlopeOne 原始算法、优化算法的原理及应用 学习人工智能如何从阅读论文中获取一手信息，并推荐一些技术论文 一篇文章为你讲透 SVD 矩阵分解的原理 SVD 矩阵分解的实际案例：利用 SVD 进行图像压缩 详细来说说 SVD 矩阵分解的三种算法 推荐系统之基于内容的推荐 Embedding 带你详细了解十大经典机器学习模型之 Google 的基石：PageRank PageRank 拓展以及如何利用 networkx 来分析希拉里丑闻 PageRank 的那些相关算法 - PersonRank, TextRank, EdgeRank 图论工具和算法, 社区发现以及最短路径 初接触 Graph Embedding 详解 Graph Embedding 的 DeepWalk 算法及实例 从原理到实例，详解 Node2Vec 算法 依据淘宝的用户行为，从 0 开始实现一个简单的移动推荐系统 Graph Embedding 回顾以及 GCN 算法介绍 美国大学生足球队的 GCN 案例 利用项目「恶意软件检测」来看 GCN 和 LSTM 的对比 详细讲解机器学习分支之强化学习的概念和实际案例：迷宫问题 强化学习案例：自动完成游戏 Flappy Bird 人工智能CV核心知识 CV 篇预告 计算机视觉解决什么问题？ 计算机视觉的职业规划 Computer Vision 的由来 计算机是如何看到图像的 计算机处理图像的方式方法_1 计算机处理图像的方式方法_2 作业, 人像滤波去噪 灰度图和 gamma 值 直方图及均衡化 计算机视觉核心步骤 特征及决策函数 图片特征描述子 作业, 识别图像数字 计算机视觉的常用模型 对客观世界的建模：卷积 从 MLP 到 CNN 如何加速 CNN GPU Schema 作业, 使用 pycuda 完成 LeNet 前向计算 CNN 及经典模型如何搭积木 Output 层的设计 ","permalink":"https://hivan.me/posts/ai-cheats---catalogs/","summary":"\u003ca href=\"https://mp.weixin.qq.com/s/ZkPHv038i9t-u8HxY9LB3w\"\u003e新专栏《AI秘籍》，你所感兴趣的一切\u003c/a\u003e","title":"《茶桁的 AI 秘籍》核心基础——目录"},{"content":"\n标记数据是一件麻烦事。然而，准确标记的数据是数据科学和机器学习的基础。那么，让我们看看如何使用 Python 加速这一过程。\n我们将构建两种类型的记录标签器：\n鼠标单击的位置 键盘上按下的键 这些可用于直接在笔记本中标记图像。我们将讨论用于创建这些的代码，您也可以在 GitHub1 上找到它。\n首先，我们将使用下面的导入。我们有一些标准包（第 1-3 行）。我们有一些用于处理图像的包（第 4-8 行）。最后一个用于处理文件路径（第 9 行）。\nimport numpy as np import matplotlib import matplotlib.pyplot as plt import matplotlib.image as mpimg plt.style.use(\u0026#39;default\u0026#39;) matplotlib.use(\u0026#39;TkAgg\u0026#39;) # 强制使用 TkAgg import cv2 import glob 我们将使用图像来训练一个模型，该模型可以引导自动驾驶汽车在赛道上行驶。你可以在数据集中找到这些示例。记下图像名称：\n173_48_ffc63efc-40d3-11ed-81f8-a46bb6070c92.jpg 图片尺寸为 224 x 224。名称中的前两个数字是此图片中的 X 和 Y 坐标。我们希望使用图片作为输入来预测此坐标。\n不清楚？让我们显示其中一张图片。这些图片存储在第 2 行的目录中**。**我们加载所有这些图片的路径（第 3 行）。\n# Load image paths data = dp + \u0026#39;JatRacer_Images/direction/\u0026#39; img_path = glob.glob(data + \u0026#39;*.jpg\u0026#39;) 我们取列表中的第一个路径（第 1 行），并从图像路径中获取 X 和 Y 坐标（第 4-6 行）。然后，我们将图像与坐标一起显示（第 9-11 行）。具体来说，我们使用 cv2 函数在给定的坐标处绘制一个圆圈（第 10 行）。\npath = img_path[0] # get x and y pos from image name name = path.split(\u0026#34;/\u0026#34;)[-1] x = int(name.split(\u0026#34;_\u0026#34;)[0]) y = int(name.split(\u0026#34;_\u0026#34;)[1]) # display image with x and y pos img = mpimg.imread(path).copy() img = cv2.circle(img, (x, y), 8, (0, 255, 0), 3) plt.imshow(img) 您可以在图 1 中看到输出。自动驾驶汽车正在右转。理想的方向是朝着绿色圆圈给出的坐标行驶。\n假设你花了几个小时收集数据，却发现数据被错误标记（我们确实遇到过这种情况）。尝试手动更改文件名中的 x/y 坐标将是一场噩梦。相反，我们将使用 Python。\n标签器 1：鼠标单击 图 2 显示了我们的第一个标签器的工作情况。当您单击图像时，它会保存到名为“relabelled”的文件夹中。图像名称会使用鼠标单击的坐标进行更新。您可以看到如何使用类似的标签器来记录图像中对象的位置。\n要构建此标签机，我们首先要定义读取和写入路径。第一个是保存现有图像的位置（第 1 行）。第二个是保存更新图像的位置（第 2 行）。\nread_path = dp + \u0026#34;JatRacer_Images/direction/\u0026#34; write_path = dp + \u0026#34;JatRacer_Images/relabelled/\u0026#34; 然后我们定义一个函数 onclick，当我们点击图像时，该函数将运行。它获取鼠标点击的坐标（第 6 行 - 第 7 行）。我们用这些坐标创建一个新名称（第 10 行 - 第 12 行），并用这个名称将图像保存在新位置（第 14 行）。\n每次迭代后，都会清除绘图（第 17 行）。如果不这样做，您将遇到内存问题。然后，我们通过删除第一个实例（第 25 行）来更新路径列表，并显示列表中的下一个图像（第 25-29 行）。\ndef onclick(event): global img_path global img #Get x,y of click x = round(event.xdata) y = round(event.ydata) #Save image with new x,y path = img_path[0] name = path.split(\u0026#34;/\u0026#34;)[-1].split(\u0026#34;_\u0026#34;)[-1] new_name = \u0026#34;{}_{}_{}\u0026#34;.format(x,y,name) mpimg.imsave(write_path + new_name, img) # Clear plot plt.clf() if len(img_path) \u0026gt; 0: #Remove first instance of img_path img_path = img_path[1:] #Display next image path = img_path[0] img =mpimg.imread(path) plt.imshow(img) plt.show() 然后，您可以在下面看到我们如何使用这个函数。我们首先获取要重新标记的所有图像的路径（第 6 行）。我们加载第一张图像（第 9-10 行）并显示它（第 16-17 行）。重要的步骤是向图形添加点击功能（第 14 行）。为此，我们将 onclick 函数作为参数传递给 mpl_connect 函数。\n%matplotlib tk global img_path global img #Get all image paths img_path = glob.glob(read_path + \u0026#34;/*.jpg\u0026#34;) #Load first image path = img_path[0] img = mpimg.imread(path) #Add an interactive widget to figure fig = plt.figure(figsize=(5,5)) cid = fig.canvas.mpl_connect(\u0026#39;button_press_event\u0026#39;, onclick) plt.imshow(img) plt.show() 另一件需要注意的事情是全局变量的使用（第 2-3 行）。这允许在 onclick 函数中更新这些变量。还有 %matplotlib tk（第 1 行）。这将在笔记本外的窗口中打开图形。\n标签器 2：鼠标单击 现在，让我们给这个标签器添加一些趣味。在图 3 中，您可以看到我们在图像中添加了绿色圆圈。这些圆圈给出了先前标签的坐标。它们让我们看到哪些图像被错误地标记了。\n代码与我们之前看到的类似。最重要的是，我们将保存的图像（第 9 行）必须与我们显示的图像（第 32 行）不同。否则，我们的图像上都会有明亮的绿色圆圈。不过，这会让模型更容易做出预测！\ndef onclick(event): global img_path # 检查点击坐标是否有效 if event.xdata is not None and event.ydata is not None: x = round(event.xdata) y = round(event.ydata) # 读取图像 save_img = mpimg.imread(img_path[0]) # 保存新图像 path = img_path[0] name = path.split(\u0026#34;/\u0026#34;)[-1].split(\u0026#34;_\u0026#34;)[-1] new_name = \u0026#34;{}_{}_{}\u0026#34;.format(x, y, name) mpimg.imsave(write_path + new_name, save_img) # 清除图像 plt.clf() # 加载下一个图像 if len(img_path) \u0026gt; 0: img_path = img_path[1:] # 获取新的 x, y path = img_path[0] name = path.split(\u0026#34;/\u0026#34;)[-1] x = int(name.split(\u0026#34;_\u0026#34;)[0]) y = int(name.split(\u0026#34;_\u0026#34;)[1]) # 加载图像并画圆 img = mpimg.imread(path).copy() # 确保图像为可写 img = cv2.circle(img, (x, y), 8, (0, 255, 0), 3) plt.imshow(img) plt.show() else: print(\u0026#34;Invalid click, outside of image bounds.\u0026#34;) 再次，我们以与以前相同的方式使用此函数。\n# 设置图形 %matplotlib tk global img_path global img # 获取所有图像路径 img_path = glob.glob(read_path + \u0026#34;/*.jpg\u0026#34;) path = img_path[0] name = path.split(\u0026#34;/\u0026#34;)[-1] x = int(name.split(\u0026#34;_\u0026#34;)[0]) y = int(name.split(\u0026#34;_\u0026#34;)[1]) # 加载第一个图像并添加圆 img = mpimg.imread(path).copy() # 确保图像副本为可写 img = cv2.circle(img, (x, y), 8, (0, 255, 0), 3) # 添加交互式小部件 fig = plt.figure(figsize=(5,5)) cid = fig.canvas.mpl_connect(\u0026#39;button_press_event\u0026#39;, onclick) plt.imshow(img) plt.show() 使用此标记器时，一个技巧是单击要从数据集中删除的图像的左上角。然后，您可以过滤掉所有 x \u0026lt; 5 和 y \u0026lt; 5 的图像。\n贴标机 3：键盘按压 我们不用记录鼠标点击，而是使用键盘。如果你想对图像进行分组以完成分类任务，这尤其有用。\n在我们的示例中，我们将图像分为左转和右转。每次我们点击 left（左）、right（右）或 d（删除）键时，下面的图像都会发生变化。我们还在左上角添加了一个数字。这让我们知道还有多少图像需要标记。\n我们首先定义一个函数 onpress，该函数将在按下某个键时运行。我们获取该键（第 7 行），并使用图像名称作为 ID（第 10-11 行）。接下来发生什么取决于按下了哪个键：\n如果我们没有点击有效键，则会显示错误消息（第 14-16 行） left 我们将 ID 附加到 ID 列表中，“left” 附加到标签列表中 right 我们附加 ID 和“right” d 我们不添加任何内容 图形被清除（第 27 行），我们更新图像（第 29-39 行）。我们将img_path列表的长度作为文本包含在左上角（第 36 行）。\n即使按下“d”键，图像也会更新。在收集这些数据时，有时会有一只流浪的手挡道。现在我们可以轻松地从分类数据集中移除/删除这些杂乱的图像。\n最后，我们将在下面看到如何使用此函数。我们将 ID 和标签列表定义为全局变量（第 3-6 行）。我们加载图像路径列表（第 9 行），并将第一幅图像加载为 matplotlib 图形（第 12-17 行）。与之前将按下功能添加到图形（第 20 行）类似。这次我们传入 key_press_event 和新函数 onpress 作为参数。\ndef onpress(event): global img_path global IDs global labels #Get key key = event.key print(key) #Get image name path = img_path[0] ID = path.split(\u0026#34;/\u0026#34;)[-1] if key not in [\u0026#34;left\u0026#34;,\u0026#34;right\u0026#34;,\u0026#34;d\u0026#34;]: print(\u0026#34;Invalid Key\u0026#34;) else: if key == \u0026#34;left\u0026#34;: IDs.append(ID) labels.append(\u0026#34;left\u0026#34;) elif key == \u0026#34;right\u0026#34;: IDs.append(ID) labels.append(\u0026#34;right\u0026#34;) # Clear plot plt.clf() if len(img_path) \u0026gt; 0: img_path = img_path[1:] #Display next image with count path = img_path[0] img = mpimg.imread(path) plt.text(0, 15, len(img_path),color=\u0026#39;r\u0026#39;,size=20) plt.imshow(img) plt.show() 完成所有图像的标记后，您将获得以下类似的列表。对于您的任务，我强烈建议将它们保存为 csv。您不想再做所有这些标记！\n参考 Github, https://github.com/hivandu/public_articles/blob/main/src/image_tools/image_labelling.ipynb\u0026#160;\u0026#x21a9;\u0026#xfe0e;\n","permalink":"https://hivan.me/posts/%E4%BD%BF%E7%94%A8-python-%E6%9E%84%E5%BB%BA%E4%BA%A4%E4%BA%92%E5%BC%8F%E5%9B%BE%E5%83%8F%E6%A0%87%E8%AE%B0%E5%99%A8/","summary":"\u003cblockquote\u003e\n\u003cp\u003e使用鼠标或键盘标记图像数据\u003c/p\u003e\u003c/blockquote\u003e","title":"使用 Python 构建交互式图像标记器"},{"content":"\nSHAP 是最流行的 IML/XAI 方法。它是一种强大的方法，可用于了解我们的模型如何进行预测。\n但不要让受欢迎程度说服你。\nSHAP 仍有局限性。使用该方法得出结论时需要牢记这些局限性。\n我们将讨论 4 个重要的限制：\n第一个来自 SHAP 包本身 第二个来自于 SHAP 值的计算方式——我们假设特征是独立的 第三个是我们如何使用它们——不是为了因果推理 最后一点来自于人类使用它们的方式——我们编造故事 1 SHAP 包 第一个与 SHAP 包本身有关。内核 SHAP 在理论上是一种与模型无关的方法，但这并不意味着它在实践中也是与模型无关的。为什么？因为它尚未在所有包中实现。\n就我个人而言，我已经将该软件包与 5 个建模软件包一起使用：Scikit learns、XGBoost、Catboost、PyTorch 和 Keras。请记住，如果你使用的是不太流行的建模框架，可能会遇到一些麻烦。即使是深度学习软件包，SHAP 也可能相当不稳定。我很难让它与 PyTorch 一起工作\n2 特性依赖性 特征依赖性是指两个或多个模型特征相关联或相关。也就是说，一个特征的值取决于另一个特征的值。SHAP 以两种方式受到特征依赖性的影响。\n第一个问题来自于 SHAP 值的近似方法。以 KernelSHAP 为例。该方法通过排列特征值并对这些排列进行预测来工作。一旦我们有足够的排列，就可以使用线性回归来估计 Shapley 值。问题是，在排列特征时，我们假设它们是独立的。\n与许多其他基于排列的解释方法一样，当特征相关时，Shapley 值方法会包含不切实际的数据实例。\n— 克里斯托夫·莫尔纳\n这个假设并不总是正确的。以图 1 中的 km_driven 和 car_age 散点图为例。这些特征用于预测二手车的价格。它们之间存在明显的相关性。车越旧，我们增加其行驶里程的时间就越多。\n现在以图 2 中的红色观察结果为例。这辆车已有 10 年车龄。这个年龄的汽车将在实心椭圆内行驶距离。当模型经过训练时，它只会“看到”这些现实的观察结果。\n然而，在计算 SHAP 值时，我们会在整个范围内对特征进行置换。对于 km_driven, 这包括虚线内的不切实际的观察结果。我们期望模型对这些观察结果做出预测。这可能会导致不可靠的预测和 SHAP 值。\n在解释 SHAP 图时，特征依赖性也会导致一些混乱。例如，模型可以使用原籍国来预测患皮肤癌的几率。某些国家的人是否易患皮肤癌？防晒霜是否更贵？不，这是因为每个国家的日照水平不同。\n原籍国被称为代理变量。模型使用它们的原因可能并不明显。归根结底，这是因为机器学习只关心相关性，而代理变量与事件的真实原因相关。这引出了我们的第二个限制。\n3 因果推理 SHAP 值不能用于因果推理。这是寻找事件/目标的真正原因的过程。SHAP 值告诉我们每个模型特征对预测的贡献。它们没有告诉我们特征对目标变量的贡献。这是因为模型不一定能很好地代表现实。\nShap 不是衡量“现实世界中某个特征有多重要”的标准，它只是衡量“某个特征对模型有多重要”。—— Gianlucca Zuin\n预测可能不正确。在这种情况下，SHAP 值将对与真实目标不同的预测做出贡献。如前所述，即使模型 100% 准确，也可能使用代理变量。所有这些都意味着我们不应该得出超出模型范围的结论。即使这样做很诱人……\n4 人为错误 从技术分析到占星术，人类喜欢寻找实际上并不存在的特征。数据科学也不例外。\n在分析 SHAP 值时，我们可能会产生错误的叙述。为什么该模型预测癌症发病率很高？一定是因为帽子不流行了！\n即使这些故事来自模型怪癖，我们也可以强行将这些故事强加到分析中。由于确认偏差，我们可以无意识地这样做。它也可以恶意地支持对某人有利的结论。这类似于 p-hacking 的过程。我们反复修改数据和模型，直到它们给我们想要的结果。\n确认偏差——无意识地偏向那些证实你先前信念的信息\n最后，所有这些限制都应该增加你对使用 SHAP 得出的结论的怀疑。结论可能基于对特征独立性的错误假设。永远不要接受超出模型范围的结论——尤其是当它支持别有用心时。\n我们总体上研究了 SHAP 的局限性。不同的近似方法会有各自的特定局限性。例如，KernelSHAP 速度很慢。TreeSHAP 速度更快，但它与模型无关。我们在文章《KernelSHAP 与 TreeSHAP》中讨论这些和其他考虑因素。\n参考 S. Lundberg, SHAP Python package (2021), https://github.com/slundberg/shap\nS. Lundberg \u0026amp; S. Lee, A Unified Approach to Interpreting Model Predictions (2017), https://arxiv.org/pdf/1705.07874.pdf\nC. Molnar, Interpretable Machine Learning (2021), https://christophm.github.io/interpretable-ml-book/\nS. Masís, Interpretable Machine Learning with Python (2021)\nChandan Durgia Using SHAP for Explainability — Understand these Limitations First (2021) https://towardsdatascience.com/using-shap-for-explainability-understand-these-limitations-first-1bed91c9d21\n","permalink":"https://hivan.me/posts/shap-%E7%9A%84%E5%B1%80%E9%99%90%E6%80%A7/","summary":"\u003cblockquote\u003e\n\u003cp\u003eSHAP 如何受到特征依赖性、因果推理和人为偏见的影响\u003c/p\u003e\u003c/blockquote\u003e","title":"SHAP 的局限性"},{"content":"\n热图可以让你的数据变得生动。用途广泛且引人注目。在很多情况下，它们可以突出显示数据中的重要关系。具体来说，我们将讨论如何使用它们来可视化：\n模型准确度的混淆矩阵 时间序列数据显示组间的变化 时间序列数据显示温度变化 相关矩阵 平均SHAP 相互作用值 在此过程中，你将学习自定义热图的不同方法。我们将讨论创建它们的代码，你可以在Github1找到完整的项目。\n什么是热图？ 我们先来讨论一下热图是什么以及为什么热图如此有用。你可以在图 1 中看到一个示例。y 轴上有变量 1。在这种情况下，变量 1 可以采用不同的 4 个值。也就是说，“V1-1”是变量 1 的第一个值。同样，y 轴上有变量 2。还有第三个变量。这是每个单元格内的值。每个单元格的颜色由这个变量的值决定。\n因此，使用热图，我们可以在 2D 平面上直观地显示 3 个变量之间的关系。这些关系可能很复杂。这就是使用颜色的原因。它可以突出显示关系的重要方面，并使它们更容易理解。\n我们应该记住，热图仍然有局限性。变量 1 和变量 2 需要是离散的或分类的。或者，如果它们是连续的，我们需要能够将它们分组。另一方面，变量 3 需要是连续变量。希望当我们在下面讨论我们的 5 个热图时，这一点会很清楚。\n1 混淆矩阵 图 2中的第一个热图是混淆矩阵的可视化。它来自用于预测一段文本语言的模型。y 轴表示文本的实际语言。x 轴表示模型预测的语言。对角线上的数字表示正确预测的数量。非对角线上的数字表示错误预测的数量。例如，英语 (eng) 被错误预测为德语 (deu) 11 次。\n当你的目标变量有很多类别时，可视化这样的混淆矩阵很有用。它可以突出显示模型出错的地方。例如，我们发现模型最常将葡萄牙语（por）混淆为西班牙语（spa）（124 次）或将西班牙语混淆为葡萄牙语（84 次）。这是有道理的，因为在所有语言中，这两种语言在词汇上最相似。\n热图代码 要创建此热图，我们首先导入以下包。热图函数来自 seaborn 包（第 6 行）。我们将对所有 5 个热图使用相同的包。确保已安装它们。\nimport pandas as pd import numpy as np import matplotlib.pyplot as plt plt.style.use(\u0026#39;default\u0026#39;) %matplotlib inline import seaborn as sns from datetime import datetime 我们有一个用于填充下面热图的二维数组。这些数组给出了正确和错误预测的数量。你可以看到第一个子数组（第 2 行）对应于图 2中热图第一行的值。所有热图都使用与此类似的二维数组填充。如果你需要将此代码用于另一个热图，则可以用你的二维数组替换此混淆矩阵。\n# Hard code confusion matrix conf_matrix = [[4963, 5, 5, 3, 3, 3], [11, 4842, 21, 2, 5, 5], [4, 1, 4999, 2, 6, 13], [3, 5, 25, 4852, 30, 96], [1, 4, 15, 10, 4873, 124], [2, 4, 18, 18, 84, 4943]] 目前，我们已经对 2D 数组进行了硬编码。文章《[[深度神经网络语言识别]]》将带你了解我们实际获取这些数字的过程。总而言之，我们使用 NLP 技术构建了一个神经网络。然后，我们使用此模型来预测测试数据集中文本的语言。你在上面看到的数字来自这些预测。\n深度神经网络语言识别 使用这个二维数组，我们创建一个 pandas DataFrame ( conf_matrix_df)。我们使用不同的语言作为列名和行名。\n# Create pandas dataframe with confusion matrix lang = [\u0026#39;deu\u0026#39;, \u0026#39;eng\u0026#39;, \u0026#39;fra\u0026#39;, \u0026#39;ita\u0026#39;, \u0026#39;por\u0026#39;, \u0026#39;spa\u0026#39;] conf_matrix_df = pd.DataFrame(conf_matrix, columns=lang, index=lang) 最后，我们使用 seaborn heatmap 函数（第 5-9 行）可视化此 DataFrame。除了 conf_matrix_df，我们还传递了一些参数。cmap 提供配色方案。将其设置为 coolwarm 可获得红色和蓝色单元格。将annot 设置为 true 可获得每个单元格中的数字。如果没有它，我们将只有颜色。fmt 定义颜色的格式。在创建其他热图时，我们将看到这些参数的一些变化。\n# Plot confusion matrix heatmap plt.figure(figsize=(10, 10)) sns.set(font_scale=1.5) sns.heatmap(conf_matrix_df, cmap=\u0026#39;coolwarm\u0026#39;, annot=True, fmt=\u0026#39;.5g\u0026#39;, vmax=200) plt.xlabel(\u0026#39;Predicted\u0026#39;, fontsize=22) plt.ylabel(\u0026#39;Actual\u0026#39;, fontsize=22) 最后一个参数是 vmax。它定义了颜色标度的最大值。如果你不为该参数传递值，它将默认为热图中的最大值。在本例中，它是正确的法语 (fra) 预测的数量（即 4999）。我们将该值设置为 200，因为这样可以更容易区分错误的预测。你可以在图 3 中看到我们的意思。此热图是使用 vmax 的默认值创建的。\n2 群体间流动 我们的第二张热图展示了如何可视化分类变量随时间的变化。具体来说，我们展示了美国城市的空气质量指数 (AQI)。y 轴表示 2010 年的 AQI 水平，x 轴表示 2016 年的水平。单元格值表示从一个级别升至另一个级别的城市数量。例如，我们可以看到 20 个城市从不健康（敏感人群）级别改善到了中等级别。\nAQI 是介于 0 到 500 之间的值。值越高，空气污染程度越高。AQI 是使用 4 种不同的污染物计算得出的——二氧化氮 (NO2)、二氧化硫 (SO2)、一氧化碳 (CO) 和臭氧 (O3)。具体来说，为了得到最终的 AQI，我们取这 4 种污染物中的最大 AQI。在图 5中，你可以看到不同关注级别的 AQI 范围。我们在热图中使用了这些级别。\n来源：[AirNow: https://www.airnow.gov/aqi/aqi-basics/]\n要创建热图，我们首先加载数据集（第 2 行）。你可以在 Kaggle2 上找到此数据集。阅读是按天进行的。我们只对阅读的年份感兴趣。因此，我们创建了一个包含阅读年份的列（第 5-6 行）。\n# Load dataset df = pd.read_csv(dp + \u0026#39;pollution_us_2000_2016.csv\u0026#39;, index_col=0) # Create column with year of reading date = [datetime.strptime(dt, \u0026#39;%Y-%m-%d\u0026#39;) for dt in df[\u0026#39;Date Local\u0026#39;]] df[\u0026#39;year\u0026#39;] = [dt.year for dt in date] 这是 x 轴和 y 轴上的变量原本是连续变量的示例。如前所述，我们需要对该变量进行分组。下面的 aqiGroup 函数用于执行此操作。它将根据 AQI 值返回一个级别。它使用的范围与图 5 中相同。\ndef aqiGroup(api): \u0026#39;\u0026#39;\u0026#39;Return group name based on AQI values\u0026#39;\u0026#39;\u0026#39; if api \u0026lt;= 50: return \u0026#39;Good\u0026#39; elif api \u0026lt;= 100: return \u0026#39;Moderate\u0026#39; elif api \u0026lt;= 150: return \u0026#39;Unhealthy \\n(Sensitive Groups)\u0026#39; elif api \u0026lt;= 200: return \u0026#39;Unhealthy\u0026#39; elif api \u0026lt;= 300: return \u0026#39;Very Unhealthy\u0026#39; elif api \u0026lt;= 500: return \u0026#39;Hazardous\u0026#39; 为了得到最终的 2D 矩阵，我们需要进行一些数据处理。我们首先使用 4 种污染物的值计算 AQI 值（第 2 行）。然后，对于每个城市，我们计算每年的最大 AQI（第 5 行）。因此，你在热图中看到的值实际上是基于 2010 年和 2016 年的最大 AQI 值。最后，我们使用 aqiGroup 函数对 AQI 值进行分组（第 8 行）。\n# Get maximum AQI across 4 pollution measures df[\u0026#39;AQI\u0026#39;] = [np.nanmax(x) for x in df[[\u0026#39;NO2 AQI\u0026#39;, \u0026#39;O3 AQI\u0026#39;,\u0026#39;SO2 AQI\u0026#39;,\u0026#39;CO AQI\u0026#39;]].values.tolist()] # Get maximum O3 AQI AQI for each city each year df_max = df.groupby([\u0026#39;City\u0026#39;, \u0026#39;year\u0026#39;], as_index=False)[\u0026#39;O3 AQI\u0026#39;].max() # Get AQI group of maximum AQI value df_max[\u0026#39;AQI Group\u0026#39;] = [aqiGroup(aqi) for aqi in df_max[\u0026#39;O3 AQI\u0026#39;]] 我们获取了 2016 年（第 2-3 行）和 2010 年（第 6-7 行）的所有 AQI 值。然后，我们将这些表格合并起来（第 10 行）。在某些情况下，一个城市可能在某一年有读数，而在另一年没有。在这种情况下，我们将缺失值替换为 Not operational（第 11 行）。最终数据集AQI将包含每个城市在 2016 年和 2010 年的水平。\n# AQI groups in 2016 AQI_2016 = df_max[df_max.year==2016][[\u0026#39;City\u0026#39;, \u0026#39;AQI Group\u0026#39;]] AQI_2016.columns = [\u0026#39;City\u0026#39;, \u0026#39;AQI 2016\u0026#39;] # AQI groups in 2010 AQI_2010 = df_max[df_max.year==2010][[\u0026#39;City\u0026#39;, \u0026#39;AQI Group\u0026#39;]] AQI_2010.columns = [\u0026#39;City\u0026#39;, \u0026#39;AQI 2010\u0026#39;] # Join tables and replace missing values AQI = AQI_2016.join(AQI_2010.set_index([\u0026#39;City\u0026#39;]), how=\u0026#39;outer\u0026#39;, on=[\u0026#39;City\u0026#39;]) AQI.fillna(\u0026#39;Not operational\u0026#39;, inplace=True) 好的，现在我们有了这个数据集，我们可以使用它来创建 2D 数组 hm_array。这用于填充热图。它将具有与我们在第一个热图中看到的硬编码数组相同的结构。该数组是在第 6 行到第 12 行中创建的。其中，对于每个级别组合，我们计算 AQI 数据集中的记录数（第 10-11 行）。和以前一样，我们使用这个 2D 数组创建一个 dataFrame。我们使用 AQI 级别作为列名和行名。\n# NOTE: \u0026#39;Very Unhealthy\u0026#39; and \u0026#39;Hazardous\u0026#39; groups have been exluded groups = [\u0026#39;Good\u0026#39;, \u0026#39;Moderate\u0026#39;, \u0026#39;Unhealthy \\n(Sensitive Groups)\u0026#39;, \u0026#39;Unhealthy\u0026#39;, \u0026#39;Not operational\u0026#39;] # Create matrix of group counts hm_array = [] for i in groups: hm_array_i = [] for j in groups: df = AQI[(AQI[\u0026#39;AQI 2010\u0026#39;] == i)\u0026amp;(AQI[\u0026#39;AQI 2016\u0026#39;]==j)] hm_array_i.append(len(df)) hm_array.append(hm_array_i) # Create dataframe from matrix hm_df = pd.DataFrame(hm_array, columns=groups, index=groups) 最后，我们像以前一样创建热图。这次我们有不同的参数值。我们使用了不同的配色方案 cmap 。我们将 cbar 设置为 false。这会隐藏颜色条。我们还使用了 linewidths 和 linecolor 参数为热图提供黑色网格线。\n# Plot confusion matrix heatmap plt.figure(figsize=(10, 10), facecolor=\u0026#39;w\u0026#39;, edgecolor=\u0026#39;k\u0026#39;) sns.set(font_scale=1.5) sns.heatmap(hm_df, cmap=\u0026#39;viridis\u0026#39;, annot=True, fmt=\u0026#39;.5g\u0026#39;, cbar=False, linewidths=2, linecolor=\u0026#39;black\u0026#39;) plt.xlabel(\u0026#39;2016\u0026#39;, fontsize=22) plt.ylabel(\u0026#39;2010\u0026#39;, fontsize=22) 3 温度随时间变化 与上一张热图类似，我们用这张热图来可视化时间序列数据。只不过，现在我们展示的是连续变量随时间的变化。在图 4 中，你可以看到全球平均气温随时间的变化。从 1900 年到 2016 年，每个月都有读数。你可以清楚地看到气候变化对后续月份的影响。也许我们对热图一词的理解有点过于字面化了。\n我们首先加载数据集（第 1 行）。你可以在 datahub3 上找到它。数据集包含两个不同的温度读数来源。我们仅选择 GISTEMP 读数（第 4 行）。然后，我们为每个读数创建年份和月份的列（第 7-9 行）。\ndf = pd.read_csv(dp + \u0026#39;Global_Temp_Monthly.csv\u0026#39;) # Only use GISTEMP record df = df[df.Source == \u0026#39;GISTEMP\u0026#39;] # Get year and month of record date = [datetime.strptime(dt, \u0026#39;%Y-%m\u0026#39;) for dt in df[\u0026#39;Date\u0026#39;]] df[\u0026#39;year\u0026#39;] = [dt.year for dt in date] df[\u0026#39;month\u0026#39;] = [dt.month for dt in date] 和之前一样，我们创建一个用于填充热图的 2D 数组。在之前的热图中，所有 2D 数组都是对称的。但情况并不总是如此。对于此热图，每个月都有一个子数组（即 1 到 12）。每个子数组都将包含 1900 年至 2016 年每年的温度值。因此，我们现在有一个 12x117 数组。我们使用年份作为列名、月份作为行名来创建一个 DataFrame。\nyears = range(1900,2017) months = range(1,13) # Create matrix of temprature values hm_array = [] for m in months: hm_array_y = [] for y in years: mean = df[(df.year == y) \u0026amp; (df.month == m)][\u0026#39;Mean\u0026#39;] mean = mean.to_numpy()[0] hm_array_y.append(mean) hm_array.append(hm_array_y) # Create dataframe from matrix hm_df = pd.DataFrame(hm_array,columns=years,index=months) 我们像之前一样可视化这个 DataFrame。最大的区别是我们将 x ticklabels 参数设置为 10。这意味着只显示 x 轴上的每 10 个标签。你可以在图 6中看到这一点，其中只显示了 1900、1910、1920 等标签。\n# Plot confusion matrix heatmap plt.figure(figsize=(10, 6), facecolor=\u0026#39;w\u0026#39;, edgecolor=\u0026#39;k\u0026#39;) sns.set(font_scale=1.5) sns.heatmap(hm_df, cmap=\u0026#39;coolwarm\u0026#39;, cbar=False, xticklabels=10) plt.xlabel(\u0026#39;Year\u0026#39;,fontsize=22) plt.ylabel(\u0026#39;Month\u0026#39;,fontsize=22) 4 相关矩阵 我们的第四张热图可能是你以前见过的。它的一个常见用途是可视化数据集中的相关性。例如，我们在图 7中有一个房价数据集的相关矩阵。我们可以使用它来识别可能导致模型出现问题的任何多重共线性。例如，X3 和 X4 呈负相关。最后一行还给出了与目标变量 Y 的相关性。我们可以使用它来了解任何特征是否与 Y 有显著关系。\n要创建此热图，我们首先加载数据集（第 2 行）。你可以在 UCI 的 机器学习存储库4 中找到它。然后，我们使用此数据集创建一个相关矩阵（第 5 行）。结果将是一个 pandas DataFrame。列和行名称将与数据集中特征的名称相同。\n# Load dataset df = pd.read_csv(dp + \u0026#39;Real_estate_valuation_data_set.csv\u0026#39;,index_col=0) # Create correlation matrix corr_matrix = df.corr() 你可能已经注意到，在图 7中，对角线上方的单元格是空白的。为此，我们首先需要创建一个掩码。这是一个 2D 数组，类似于我们用来填充以前的热图的数组。对于要显示的单元格，数组的值应该是“True”。否则，对于空白单元格，它们应该是“False”。我们使用下面的代码来创建掩码。\n# Define mask used to cover squares above diagonal mask = [] for i in range(len(corr_matrix.columns)): mask_i = [] for j in range(len(corr_matrix.columns)): if i\u0026lt;j: mask_i.append(True) else: mask_i.append(False) mask.append(mask_i) mask = np.array(mask) 最后，我们可以显示热图。唯一的区别是我们需要将掩码作为参数传递（第 8 行）。\n# Display Correlations plt.figure(figsize=(10, 10), facecolor=\u0026#39;w\u0026#39;, edgecolor=\u0026#39;k\u0026#39;) sns.set(font_scale=1.2) sns.heatmap(corr_matrix,cmap=\u0026#39;coolwarm\u0026#39;, center=0,annot=True, fmt=\u0026#39;.1g\u0026#39;, mask=mask) 5 SHAP相互作用值 我们最后的热图可用于突出显示对模型预测很重要的特征。它是通过取平均 SHAP 交互值创建的。它显示了对角线上的平均主效应。例如，我们可以看到，经验、学位、绩效和销售额的主效应很大。同样，平均交互效应在非对角线上。我们可以看到，经验.学位和绩效.销售额的交互效应很显著。\n我们不会介绍用于创建此热图的代码。如果你有兴趣，可以在文章《[[分析与 SHAP 的相互作用]]》中找到它。我们深入探讨了 SHAP 交互值。我们还使用这些值创建和解释其他图。这些用于解释你的机器学习模型。\n希望这篇文章对你有所帮助！你还可以阅读我的其他文章，或者查看有关企业 AI 实战项目的教程，相信会让你拥有更多收获。\n「AI秘籍」系列课程： 人工智能应用数学基础\n人工智能Python基础\n人工智能基础核心知识\n人工智能BI核心知识\n人工智能CV核心知识\n参考 Github, https://github.com/hivandu/public_articles/tree/main/src/seaborn_heatmap.ipynb\u0026#160;\u0026#x21a9;\u0026#xfe0e;\nKaggle, U.S. Pollution Data, https://www.kaggle.com/datasets/sogun3/uspollution, Licence: Open Database License (ODbL) 1.0\u0026#160;\u0026#x21a9;\u0026#xfe0e;\nDatahub, Global Temperature Time Series, https://datahub.io/core/global-temp, Licence: ODC-PDDL-1.0\u0026#160;\u0026#x21a9;\u0026#xfe0e;\n机器学习存储库, Real estate valuation data set Data Set, http://archive.ics.uci.edu/ml/datasets/Real+estate+valuation+data+set, Licence: CC0: Public Domain\u0026#160;\u0026#x21a9;\u0026#xfe0e;\n","permalink":"https://hivan.me/posts/%E4%BD%BF%E7%94%A8-seaborn-%E7%83%AD%E5%9B%BE%E7%9A%84-5-%E7%A7%8D%E6%96%B9%E6%B3%95python-%E6%95%99%E7%A8%8B/","summary":"\u003cblockquote\u003e\n\u003cp\u003e如何计算 SHAP 特征贡献的概述\u003c/p\u003e\u003c/blockquote\u003e","title":"使用 Seaborn 热图的 5 种方法（Python 教程）"},{"content":"{:width 100} 假设你（玩家 1）和朋友（玩家 2）参加了一场 Kaggle 比赛，你最终赢得了 10,000 元的一等奖。现在，你想公平地分配这笔钱。你的朋友建议你平分。但是，你的超参数调整技能更出色。你相信你应该得到更大的份额，因为你为团队做出了更多贡献。考虑到这一点，你如何公平地分配这笔钱？\n巧合的是，你的朋友有一台时光机。你们各自回到过去，单独重赛 Kaggle 比赛。你最终获得第二名，获得 7,500 元。你的朋友只获得第三名，获得5,000 元。如果你们都没有参赛，你们就不会获得任何奖金（0 元）。我们记录了以下这 4 个玩家团队的价值。显然，你应该获得更多奖金，但目前还不清楚如何分配奖金。\n$$ \\begin{align*} C_{12} \u0026amp; = 10,000 \\ C_1 \u0026amp; = 7,500 \\ C_2 \u0026amp; = 5,000 \\ C_0 \u0026amp; = 0 \\end{align*} $$\n一种方法是计算每个玩家的预期边际贡献。这是玩家对其可能加入的所有团队的贡献的加权平均值。\n例如，玩家 1 (P1) 可以加入只有玩家 2 (P2) 的团队。P2 从第三名升至第一名，奖金增加$5000。P1 也可以加入没有玩家的团队，奖金增加 $7,500。这些是 P1 的边际贡献。这些的平均值给出了预期边际贡献 $6,250。\n$$ \\begin{align*} C_{12} C_2 = 5,000 \\ C_1 C_0 = 7,500 \\ (5,000 + 7,500) / 2 = 6,250 \\end{align*} $$\n我们可以按照类似的过程来计算 P2 的预期边际贡献。这次的值为 3,750 元。最终，P1 将获得 6,250 元，P2 将获得 3,750 元。这两个值也称为 Shapley 值。请注意，这些值加起来的总奖金为 10,000 元。\n$$ \\begin{align*} C_{12} C_1 = 2,500 \\ C_2 C_0 = 5,000 \\ (2,500 + 5,000) / 2 = 3,750 \\end{align*} $$\nShapley 值被认为是一种公平的奖金分配方式，这是一个来自博弈论的概念。我们只为 2 名队员的团队计算了 Shapley 值。我们能够使用它们来计算任何规模团队的公平分配。我们将花时间理解这个广义的 Shapley 值公式。这个公式可能看起来很吓人。然而，当我们深入了解时，你会发现它有一个直观的解释。\nShapley 值 = 预期边际贡献\n这是玩家对其可以加入的所有团队的贡献的加权平均值。 从分配奖金到解释机器学习模型，这似乎是一个巨大的飞跃。然而，Shapley 值可用于了解每个模型特征（玩家）对预测（奖金）的贡献。我们将解释如何扩展 Shapley 值来解释模型预测。最后，我们将探讨 [SHAP](Python 中的 SHAP 简介) 对这一研究领域的贡献。也就是说，它们大大提高了我们近似 Shapley 值的速度。\n3 人游戏的 Shapley 值 在继续之前，让我们先看另一个例子。这将使一般方程更容易理解。这次我们的队伍有 3 名球员。计算很复杂，我们得再回溯几次。 现在将有 8 个可能的团队，如下所示。你们一起赢得一等奖（10,000 元）。现在有 3 个由 2 名玩家组成的团队。例如，P1 和 P3 组成的团队将获得第二名（7,500 元）。还会有 1 名玩家组成的团队。例如，如果 P3 单独比赛，他将不会获得任何奖金（0 元）。也许他应该投资购买更好的 GPU。\n$$ \\begin{align*} \u0026amp; C_{123} = 10,000 \u0026amp;\u0026amp; C_{12} = 7,500 \u0026amp;\u0026amp; C_1 = 5,000 \\ \u0026amp; C_0 = 0 \u0026amp;\u0026amp; C_{13} = 7,500 \u0026amp;\u0026amp; C_2 = 5,000 \\ \u0026amp; \u0026amp;\u0026amp; C_{23} = 5,000 \u0026amp;\u0026amp; C_3 = 0 \\end{align*} $$\n我们可以使用这些团队值来计算 P1 的 Shapley 值。现在 P1 可以加入 4 个团队。P1 可以加入 P2 和 P3 的团队、只有 P2 或 P3 的团队或没有参与者的团队。像以前一样，我们计算 P1 对每个团队的边际贡献。最后，我们取加权平均值。这给了我们 Shapley 值 5,000 元。\n$$ \\begin{align*} \u0026amp; C_{123} C_{23} = 5,000 \\ \u0026amp; C_{12} C_2 = 2,500 \\ \u0026amp; C_{13} C_3 = 7,500 \\ \u0026amp; C_1 C_0 = 5,000 \\ \u0026amp; 5,000 \\times \\frac{1}{3} + 2,500 \\times \\frac{1}{6} + 7,500 \\times \\frac{1}{6} + 5,000 \\times \\frac{1}{3} = 5,000 \\end{align*} $$\n你可能会问，我们从哪里得到这些权重？这就是为什么我们要将第一个边际贡献的权重设为 1/3，将第二个边际贡献的权重设为 1/6，依此类推……这些是 P1 做出这些特定贡献的概率。按概率加权可以得到预期边际贡献。\n这些概率的来源并不明显。首先，我们需要计算出组成 3 人团队的方法数量。这是因为只有所有 3 名成员共同努力，才能赢得全部奖金（10,000 元）。\n为此，我们假设每个成员以平等的机会依次加入团队。例如，P1 加入，然后 P3 加入，然后 P2 加入。这给了我们总共 3! = 6 种组建团队的方法。你可以在下面看到所有这些方法。一般来说，有 n! 种方法可以组建一支由 n 名球员组成的团队。\n$$ \\begin{align*} C_0 \\to C_1 \\to C_{12} \\to C_{123} \\ C_0 \\to C_1 \\to C_{13} \\to C_{132} \\ C_0 \\to C_2 \\to C_{21} \\to C_{213} \\ C_0 \\to C_2 \\to C_{23} \\to C_{231} \\ C_0 \\to C_3 \\to C_{31} \\to C_{312} \\ C_0 \\to C_3 \\to C_{32} \\to C_{321} \\ \\end{align*} $$\n上面我们看到，如果 P1 加入 P2 和 P3 的团队，他将做出 5000 元的边际贡献。这可能以两种方式发生。要么 P2 加入，然后 P3 加入，然后 P1 加入，要么 P3 加入，然后 P2 加入，然后 P1 加入。换句话说，在 6 种团队组建方式中，P1 将为其中 2 种做出这一边际贡献。这给了我们 P1 做出这一贡献的概率为 2/6 = 1/3。\nP1 做出第二种贡献（ 2,500 ）的方式只有一种。即如果 P2 加入，然后 P1 加入，然后 P3 加入。这样得到的概率为 1/6。同样，第三种贡献（7,500）的概率为 1/6。第四笔贡献（5,000）的概率为 1/3。与第一笔贡献一样，P1 做出贡献的方式也有两种。首先 P1 加入，然后 P2 加入，然后 P3 加入，或者 P3 加入，然后 P2 加入。\n我们可以对 P2 和 P3 执行相同的过程。对于这些玩家，Shapley 值分别为 3,750 元和 1,250 元。同样，所有 Shapley 值加起来等于总奖金。Shapley 值将始终公平地分配所有奖金。现在让我们看看如何概括 Shapley 值。\n广义 Shapley 值 公式 1 给出了 p 人游戏中 玩家 i 的 Shapley 值的公式。从求和符号开始，我们对所有团队 S 求和。其中 S 是不包括玩家 i 的团队子集。换句话说，S 包含玩家 i 能够做出边际贡献的所有团队。回到 3 人游戏的例子，有 4 个团队不包括 P1。\n在方括号内，我们得到了玩家 i 对团队 S 的边际贡献。具体来说，我们得到了团队 S 的价值 (val)，包括玩家 i 减去团队 S 的价值。价值函数取决于正在玩的特定游戏。在我们的 3 人示例中，我们使用了不同的符号。我们讨论了团队价值，并使用了带有玩家下标的字母 C。这些团队价值为 Kaggle 游戏提供了价值函数。 最后，我们对边际贡献进行加权。下面，你可以看到权重的每个组成部分代表什么。这里 |S| 是团队 S 中的玩家数量。这意味着 p-|S|-1 是在玩家 i 之后需要加入团队的玩家数量。\n$$ \\begin{align*} \u0026amp; |S|! \\Rightarrow \\textbf{玩家可以在玩家 i 之前加入 团队S 的方式数量} \\ \u0026amp; (p |S| 1)! \\Rightarrow \\textbf{玩家 i 加入后玩家加入团队 S 的方式数量}\\ \u0026amp; p! \\Rightarrow \\textbf{组建 P 玩家团队的方法数量} \\end{align*} $$\n在权重分子中，我们有团队 S 可以形成的方式数量。分母是整个团队可以形成的方式数量。因此，权重给出了当游戏中有 p 个玩家时，玩家 i 对规模为 |S| 的团队做出贡献的概率。如果你代入我们的 3 人游戏的值，你将获得与之前相同的权重。\n分解 Shapley 值，你会发现它有一个直观的解释。我们用玩家 i 做出贡献的概率来加权所有玩家 i 的边际贡献。然后，我们将这些加权贡献加到玩家 i 可以加入的所有团队中。这给了我们一个预期边际贡献。使用这些值，我们可以将游戏的总价值分配给所有玩家。\n直观地看，预期边际贡献似乎是一种公平的做法。我们考虑所有团队的贡献。这意味着我们要考虑玩家的个人贡献和玩家之间的互动。也就是说，一些玩家可以很好地合作，从而增加他们的共同价值。问题是，可能还有其他看似公平的分配价值的方法。我们需要证明 Shapley 值是公平的。\n形状公理 Shapley 值其实是由 3 条公理推导出来的。我们只会总结它们，但它们也可以用数学来定义。这些公理可以被认为是公平的定义。因此，满足此定义的价值分配方法可以被认为是公平的。\n对称性：如果两个玩家对所有团队的贡献相同，则认为他们可互换。如果两个玩家可互换，则必须给予他们游戏总价值的平等份额。\n空玩家属性：如果玩家对所有团队的边际贡献为零，那么他们将不会获得任何总价值。\n可加性：如果我们将两场游戏结合起来，那么玩家的整体贡献就是两场单独游戏贡献的总和。这个公理假设任何游戏都是独立的。 我们可以用数学证明 Shapley 值是唯一满足这 3 条公理的有效值。所谓有效，是指游戏的价值不会有任何剩余。归根结底，根据这一定义，Shapley 值是唯一公平的分配价值的方法。如此直观的公式竟然可以从 3 条简单的公理中推导出来，真是令人惊叹。\n机器学习的 Shapley 值 我们可以使用 Shapley 值来了解模型如何做出预测。现在，游戏的值是模型预测。特征值是玩家。要清楚的是，玩游戏的不是特征，而是特定观察的特征值。但是，我们将这些称为特征。我们使用 Shapley 值来计算每个特征对预测的贡献。\n例如，在之前的文章《[[Python 中的 SHAP 简介]]》中，我们训练了一个模型来预测鲍鱼的年龄。你可以在图 1 中看到特定观测值的 Shapley 值。这些值给出了平均预测年龄 E[f(x)] 与观测值的预测值 f(x) 之间的差异。例如，去壳重量的值使预测年龄增加了 1.81。我们将其称为特征的贡献。\n为了计算这些，我们可以使用与之前相同的 Shapley 值公式。我们需要做的就是改变值函数。公式 2 给出了我们用于特定观测 x 的函数。这里 S 是特征值的联合，f 给出模型预测，模型有 p 个特征。S 的值是模型的预测在所有不属于 S 的特征上被边缘化。我们对 S 中的特征使用实际值。\n$$ val_x(S) = \\int f(x_1, \u0026hellip;, x_p) dP_{x\\notin S} $$\n我们实际上是在上面的方程中进行多重积分。为了对特征进行边缘化，我们将预测函数与特征值的概率进行积分。我们对 S 中的所有特征都执行此操作。为此，我们需要了解特征分布或使用经验分布。 上述公式中有很多活动部件。在本节的下一部分中，我们将讨论一个 ML 示例，以便更好地理解它。然后，我们将继续讨论如何为 ML近似 Shapley 值。在本节结束时，我们将讨论 Shapley 值的属性。这些属性遵循公理，我们将讨论它们在机器学习中的含义。\n机器学习示例 假设我们想预测某人的收入。我们有两个特征 —— age（特征 1）和 degree（特征 2）。我们最终得到下面的模型 f。对于 age，我们假设该特征在 18 到 60 岁之间均匀分布。同样，对于 degree，我们假设某人拥有学位 (1) 和不拥有学位 (0) 的概率相等。对于我们的观察，我们有一个 20 岁且拥有学位的人。该模型将预测此人的收入为 5,000 元。\n$$ \\begin{align*} f(x_1, x_2) = 200x_1 + 1000 x_2 \\ age \\to x_1 \\in [18, 60] \\ degree \\to x_2 \\in {0, 1} \\end{align*} $$\n我们可以计算特征 2 {2} 对特征 1 的团队 S = {1} 的边际贡献。这可用于帮助计算特征 2 的 Shapley 值。换句话说，计算特征 2（degree = 1）对预测的贡献。\n我们首先计算两个特征的联合值，S = {1,2}。这相对简单。S 包含两个特征，因此我们不必对任何特征进行边缘化。我们可以使用该特征的实际值。如下所示，这与此观察的预测相同。\n$$ \\begin{align*} val_x({1, 2}) \u0026amp; = f(20, 1) \\ \u0026amp; = 200(20) + 1000(1) \\ \u0026amp; = 5000 \\end{align*} $$\n然后我们需要计算团队的值，S = {1}。在这种情况下，S 不包含 {2}。我们对特征 2 进行边缘化，并使用特征 1 的实际值。请记住，特征 2 不是连续的。要对特征的值进行边缘化，我们不需要使用积分。我们只需将每个值的预测乘以该值的概率（即 50%）相加即可。\n$$ \\begin{align*} val_x({1}) \u0026amp; = \\int f(20, x_2)dP_{x_2} \\ \u0026amp; = \\sum_{i=0}^1f(20, i)P(x_2 = i) \\ \u0026amp; = (200(20) + 1000(0))(0.5) + (200(20) + 1000(1))(0.5) \\ \u0026amp; = 4500 \\end{align*} $$\n现在，我们可以计算特征 2 对 S={1} 的边际贡献。此贡献的权重的计算方式与标准 Shapley 值相同。为清楚起见，我们使用团队中的特征数量和特征值的总数。我们有 |S| = |{1}| = 1 和 p = 2。这给了我们 (1!)(0!)/2! = 1/2 的权重。\n$$ \\begin{align*} val_x({1,2}) val_x({1}) = 500 \\end{align*} $$\n这只为我们提供了特征 2 的 Shapley 值所需的部分计算。我们还需要计算特征 2 对 S={} 的边际贡献。为此，我们需要计算 S = {} 的值函数。这需要我们对两个特征的分布进行边缘化。\nShapely 值的近似值 计算精确的 Shapley 值需要耗费大量计算资源。在上面的例子中，我们只有两个特征。随着我们添加更多特征，可能的团队数量会呈指数级增长。实际上，只能近似 Shapley 值。\n一种方法是使用蒙特卡罗采样。对于特征 i，我们将首先使用特征值 (+i) 计算预测。我们在没有值 (-i) 的情况下执行相同操作。也就是说，我们为特征 i 取一个随机值。其余特征值也都将随机采样。我们取这两个预测之间的差值。如下所示，我们执行此操作 M 次并找到所有这些差值的平均值。通过随机采样和平均，我们隐式地按特征的分布加权。\n$$ \\begin{align*} \\hat \\Phi_i = \\frac{1}{M}\\sum_{m=1}^M(f(x_{+i}^m) f(m_{-i}^m)) \\end{align*} $$\n上述过程仍然不切实际。我们可能需要许多样本才能获得 Shapley 值的合理近似值。这就是 SHAP 的作用所在。正如我们在之前讨论的那样，它是一种更快的近似 Shapley 值的方法。在继续讨论这一点之前，我们将在机器学习的背景下讨论 Shapley 值的属性。\nShapley 值的属性 Shapley 是解释预测的一种方法。它之所以流行，是因为它具有令人满意的特性。这些特性大多源于 Shapley 值的公理。 效率如上所述，Shapley 值是高效的。以前，这意味着游戏的全部价值由玩家分担。对于 ML，这意味着预测由特征分担。具体来说，Shapley 值满足以下等式。所有 Shapley 值与平均预测值之和等于观察值的预测。我们在图 1 中看到了这一点。\n$$ \\begin{align*} f(x) = \\sum_{i=1}^p \\Phi_i + E_X[f(X)] \\end{align*} $$\n另一种流行的局部解释方法是 LIME。相比之下，LIME 并不一定有效。计算出的权重不会加起来等于原始预测。对于 Shapley，我们知道每个特征对预测的贡献有多大。对于 LIME，我们只知道哪个特征对该预测最重要。\n对称性：如果两个特征对所有团队做出相同的贡献，则它们将具有相同的 Shapley 值。\n虚拟：如果特征永远不会改变预测，则其 Shapley 值为 0。换句话说，模型中未使用的特征不会具有 Shapley 值。\n可加性：机器学习的 Shapley 值也是可加性的。这只适用于集成模型。在这种情况下，可以通过对集成中每个模型的 Shapley 值取加权平均值来计算总体 Shapley 值。其中权重将与赋予每个模型的预测的权重相同。例如，在随机森林中，每个决策树的预测都被赋予相同的权重。\n一致性：此属性源自前 3 个属性。假设我们将模型从 M1 更改为 M2。如果某个特征现在比以前增加了预测，则其 Shapley 值将增加。这意味着我们可以可靠地比较不同模型的特征贡献。\nSHAP 值 SHAP Python 包已成为处理 Shapley 值的代名词。该包被广泛实施的关键在于其进行近似的速度。我们将在下面讨论其中几种方法。速度的提高意味着我们还能计算许多 Shapley 值。这允许对值进行不同的聚合，从而让我们对模型有一个全局的了解。\nKernelSHAP KernelSHAP 将 Shapley 值重新定义为线性模型中的参数。简单地说，近似方法首先通过排列特征值来工作。经过足够的排列后，使用线性回归联合估计 Shapley 值。与其他采样方法相比，一起估计值需要更少的计算。例如蒙特卡罗采样，其中每个特征的 Shapley 值是单独计算的。\nKernelSHAP 也是一种与模型无关的近似 Shapley 值的方法。这意味着它可以用于任何模型。前提是你的建模包已实施 SHAP。\nTreeSHAP TreeSHAP 是一种与模型无关的近似方法。它利用了集成模型中各个树的结构。因此，它只能与基于树的算法（如随机森林和 XGBoost）一起使用。TreeSHAP 的优势在于它比 KernelSHAP 快得多。\n使用 KernelSHAP，我们可以在指数时间内估计 Shapley 值，相对于特征数量而言。而 TreeSHAP 可以在线性时间内估计它们。我们在文章《[[KernelSHAP vs TreeSHAP]]》中详细讨论了这一差异。还探讨了模型的其他方面如何影响近似时间。这包括集合中的树数、最大深度和叶子数。\n如上所述，这些方法可以近似大量的 Shapley 值。我们可以以不同的方式组合它们，以了解整个模型的工作原理。一个例子是图 2 中给出的蜂群图。\n在这里，我们将每个特征的值分组（例如壳重）。我们可以看到那些倾向于具有较大正 Shapley 值和负 Shapley 值的特征。这些特征往往对预测做出重大贡献。我们还根据特征的值对点进行着色。这样做我们可以开始了解特征和目标变量之间的关系。\n希望这篇文章对你有所帮助！你还可以阅读我的其他文章，或者查看有关企业 AI 实战项目的教程，相信会让你拥有更多收获。\n「AI秘籍」系列课程： 人工智能应用数学基础\n人工智能Python基础\n人工智能基础核心知识\n人工智能BI核心知识\n人工智能CV核心知识\n参考 S. Lundberg, SHAP Python package (2021), https://github.com/slundberg/shap\nS. Lundberg \u0026amp; S. Lee, A Unified Approach to Interpreting Model Predictions (2017), https://arxiv.org/pdf/1705.07874.pdf\nC. Molnar, Interpretable Machine Learning (2021) https://christophm.github.io/interpretable-ml-book/shap.html\nS. Masís, Interpretable Machine Learning with Python (2021)\nL.S. Shapley, **Contributions to the Theory of Games, Chapter A Value for n-person Games. (**1953)\nUnderstanding The Shapley Value\n[Text S1: The Axiomatic Basis of the Shapley Value](https://storage.googleapis.com/plos-corpus-prod/10.1371/journal.pcbi.0010064/1/pcbi.0010064.sd003.pdf?X-Goog-Algorithm=GOOG4-RSA-SHA256\u0026amp;X-Goog-Credential=wombat-sa@plos-prod.iam.gserviceaccount.com%2F20220905%2Fauto%2Fstorage%2Fgoog4_request\u0026amp;X-Goog-Date=20220905T203831Z\u0026amp;X-Goog-Expires=86400\u0026amp;X-Goog-SignedHeaders=host\u0026amp;X-Goog-Signature=026abd2505b5387d0faf1edfd158981b1a191c12c5d3ea7d8fbcf5f3bc938dff5951fa17a22693671da9743ec33edebe0ed3d23a39968a50f8987916f23b69b104abcd3b40133e84e8ab75eb6c7da85cfdd411d910f4d32212cfaa7d8dbf1fbd9c213cef80bd2b89818960c26ae0cc950c9a9cef1e75deecef41264dc311d39eeb873b586c2f67e2d15d1a665a751204fb0bb292e3a4204d4d990cad82a0ce7b27057160f6a9ba28e27d7b2b3b4a4e6177f17ad45a4536fc8584d26503f82904dba00da91dcb8a29c6726dabd4914172b44f08a33602fc58aa24ee07b5e5fc1828dd4cd51c0df2fec828e2b9504fb5a47ec0dd871838f3d304190de31e5282af#:~:text=The Shapley value is the,the players\u0026rsquo; “names”.)\n","permalink":"https://hivan.me/posts/%E4%BB%8E-shapley-%E5%88%B0-shap--%E6%95%B0%E5%AD%A6%E7%90%86%E8%A7%A3/","summary":"\u003cblockquote\u003e\n\u003cp\u003e如何计算 SHAP 特征贡献的概述\u003c/p\u003e\u003c/blockquote\u003e","title":"从 Shapley 到 SHAP — 数学理解"},{"content":"\n一个扎心的现实教训是：数据科学并不像你所期望的那样。\n原本希望在计算机科学、统计学和机器学习领域工作。运用新方法获得独特见解，实现一切自动化。简而言之，最终成了这个职业炒作的牺牲品。\n我想和你们分享一下。希望我们能够摆脱炒作，提高你对数据科学家工作的理解。\n第 1 课：逻辑回归大有裨益 我的工作涉及建立信用风险和欺诈模型。这些都是有影响力的模型。它们被用于大规模自动化贷款。我说的是每年价值数十亿的应用程序。你可能会认为，在如此高的风险下，会从事先进的机器学习。你错了。\n我专门使用逻辑回归来构建模型。我并不是唯一一个这样做的人。从银行业到保险业，金融界的很多领域都依赖回归。为什么？\n因为这些模型有效。\n回归模型的表现已经足够好了。它们也被银行广泛理解和接受。要采用一种新算法，它不仅要优于回归。改进还必须证明解释算法的努力是合理的。\n通过回归分析，我最终得到了具有 8 到 10 个特征的模型。每个特征都必须得到彻底解释。非技术同事必须同意他们捕捉到了现实中存在的关系。\n回归分析很简单。黑盒模型会更难解释。当然，可以使用[SHAP](Python 中的 SHAP 简介)或[PDP 和 ICE 图](PDP 和 ICE 图的终极指南)等方法。问题是它们不会给我同样的确定性。我还需要解释我用来解释模型的方法。\n这真让人失望。我学了很多关于随机森林、XGBoost 和神经网络的知识，很高兴能应用这些技术。我记得我的一位资深同事说：\n“忘掉那些花哨的模型吧”\n她是对的。许多数据科学家永远都不需要它们。\n第 2 课：机器学习有许多应用 不那么令人失望的是，会意识到机器学习是多么有用。当我看到银行业的所有应用时才明白这一点。举几个例子：\n信用风险——预测因财务困境而导致的违约 欺诈——预测客户是否不打算偿还贷款 预区域——识别陷入财务困境的客户 流失——识别打算离开银行的客户 营销——找到最适合推销产品的客户 这些模型用于实现整个银行流程的自动化。研究这些模型让人兴奋不已。会让人有机会创造一些对世界影响比独自完成更大的事情。这给会给人很大的动力。\n教训 3：处理数据是一项艰苦的工作 在学习中建立模型是一件轻而易举的事——干净的数据集、预先设计的特征和自动超参数调整。花几个小时就获得了 99.9% 的准确率。想象一下，当实际工作中三个人组成的团队花了 8 个月的时间建立一个信用风险模型时，会有多惊讶。8 个月！\n大部分时间都花在了构建数据集上。这不仅包括模型特征。我们必须证明所有建模决策都是合理的。为此，要纳入抽样和代表性分析、分割分析、公平性分析和模型评估所需的所有变量。\n必须从头开始构建许多变量。底层数据字段分布在多个表中，且文档不一致（如果有的话）。构建完成后，就开始调试。一想到调试这件事，我就一阵头疼。\n如果犯了错误（确实犯了），那么以后就会带来很多麻烦（确实如此）。为了尽量减少这种麻烦，要进行了大量测试。问题是没有任何东西可以与我们的模型特征进行比较，能做的最好的事情就是：\n理智检查。这涉及可视化特征趋势并使用领域知识对其进行验证。收入突然下降是否合理？ 单元测试。这意味着手动计算一些客户的特征值。 数据科学这份工作并不是听说的那样迷人，它很无聊。然而，这是值得的。看到最终的模型会让人感到自豪。这是自己的孩子，它会被应用于去批准数千笔贷款。\n经验 4：领域知识和软技能是关键 很快会意识到非技术技能有多么重要。沟通是关键。没有类似考试问题那样措辞清晰的任务简介。有时，任务描述得杂乱无章。你不会想到你的工作职责竟然是理解会被要求做什么。\n需要提高沟通技巧和领域知识，以有效地运用技术技能。\n随着积累了更多经验，这变得越来越容易。更具体地说，随着对银行业有了更多的了解。一开始，甚至会不知道要问什么澄清问题。有很多行话和 TLA（三个字母的缩写）。一旦掌握了这种语言，就变得轻松多了。\n教训 5：不要被花哨的标题所左右 数据科学家是一个热门职业。它也只是一个职位名称。你可能要完成各种各样的任务。公司知道人们想成为数据科学家，他们会适当地推销他们的职位。\n我和一群应届毕业生一起开始工作。我很幸运，最终从事的工作属于数据科学。一些同学就没那么幸运了，他们只会 SQL 和 Excel，应该被称为数据分析师。\n回想起来，值得警惕的一点是，部门里所有资深员工的头衔都是“量化分析师”。新来的初级员工都被称为“数据科学家”。工作突然变了吗？没有。\n第 6 课：工具很重要 一种普遍的观点是，你应该关注流程而不是工具。我认为这来自从未使用过「过时技术」的数据科学家。我同意流程很重要。获得实施这些流程的最佳工具也同样重要。\n旧工具正在消耗殆尽，它们在银行业也大量存在。\n我很早就接触 Python，还有 Ruby。Python 中，只需几行代码，你就能构建复杂的模型和交互式可视化。在银行业有 SAS。SAS 能完成 Python 的一小部分工作，但需要付出很多努力。我觉得这一点就让人很沮丧。明明可以用开源工具做得更好，但是却根本无法访问它们。\n使用旧工具会让你的技能变得不那么有市场价值。这个行业发展很快。当我开始申请新工作时，我意识到了这一点。95% 的数据科学工作申请都提到了 Python、Pytorch、TensorFlow 等工具……公司想要有最新技术经验的人。\n关于银行业的欺诈分析，建议你去看看我的人工智能BI核心知识以及AI企业项目实战课，也可以直接到橱窗购买：\n另外，基础很重要。万变不离其宗，不要去盲目追逐当下流行技术，现扎实完成底层知识和逻辑的建立，可以去查看我的系列基础教程：\n「AI秘籍」系列课程：\n人工智能应用数学基础\n人工智能Python基础\n人工智能基础核心知识\n人工智能BI核心知识\n人工智能CV核心知识\n希望这篇文章对你有所帮助！你还可以阅读我的其他文章，或者查看有关企业 AI 实战项目的教程，相信会让你拥有更多收获。\n","permalink":"https://hivan.me/posts/%E9%93%B6%E8%A1%8C%E4%B8%9A%E6%95%B0%E6%8D%AE%E7%A7%91%E5%AD%A6%E5%AE%B6%E7%9A%84-6-%E6%9D%A1%E7%BB%8F%E9%AA%8C%E6%95%99%E8%AE%AD/","summary":"\u003cblockquote\u003e\n\u003cp\u003e为什么我的第一份数据科学工作与我预期的不一样\u003c/p\u003e\u003c/blockquote\u003e","title":"银行业数据科学家的 6 条经验教训"},{"content":"\nPDP 和 ICE 图都可以帮助我们了解我们的模型如何做出预测。\n使用个人显示面板我们可以将模型特征和目标变量之间的关系可视化。它们可以告诉我们某种关系是线性的、非线性的还是没有关系。\n同样，当特征之间存在交互时，可以使用 ICE 图。我们将深入介绍这两种方法。\n我们从 PDP 开始。我们将逐步向你介绍如何创建 PDP。你会发现这是一种直观的方法。即便如此，我们也将解释 PDP 背后的数学原理。然后我们继续讨论 ICE 图。你会发现，只要对 PDP 有充分的了解，这些图就很容易理解。在此过程中，我们讨论了这些方法的不同应用和变体，包括：\n连续特征和分类特征的 PDP 和 ICE 图 二元目标变量的 PDP 2 个模型特征的 PDP 衍生 PDP 基于 PDP 和 ICE 图的特征重要性 我们确保讨论这两种方法的优点和局限性。这些是重要的部分。它们帮助我们了解什么时候这些方法最合适。它们还告诉我们它们在某些情况下如何导致错误的结论。\n最后，我们将向你介绍这些方法的 R 和 Python 代码。对于 R，我们应用 ICEbox1 和 iml2 包来创建可视化。我们还使用 vip3 来计算特征重要性。对于 Python，我们将使用 scikit-learn 的实现 PartialDepenceDisplays4。你可以在这些部分中找到带有代码的 GitHub 存储库链接。\n部分依赖图 (PDP) 我们从 PDP 的逐步演练开始。为了解释这种方法，我们随机生成了一个包含 1000 行的数据集。它包含二手车销售的详细信息。你可以在 表 1 中看到这些特征。我们想使用前 5 个特征来预测汽车的 价格。\n要预测价格，我们首先需要使用此数据集训练模型。在我们的例子中，我们训练了一个有 100 棵树的随机森林。确切的模型并不重要，因为 PDP 是一种与 模型无关的它们是使用模型预测构建的。我们不考虑模型的内部工作原理。这意味着我们探索的可视化对于随机森林、XGBoost、神经网络等将类似……\n在表 2 中，我们在用于训练模型的数据集中有一个观察值。在最后一列中，我们可以看到这辆车的预测价格。要创建 PDP，我们改变其中一个特征的值并记录由此产生的预测。例如，如果我们更改 car_age，我们将得到不同的预测。我们这样做的同时将其他特征保持为其实际值不变。例如，owner_age 将保持在 19， km_drive 将保持在 27,544。\n查看图 1，我们可以看到此过程的结果。这显示了此特定观察的预测价格（部分 yhat）和 car_age 之间的关系。你可以看到，随着 car_age 的增加，预测价格会下降。黑点给出了原始预测价格（4,654）和 car_age（4.03）。\n然后，我们对数据集中的每个观测值或观测值子集重复此过程。在图 2 中，你可以看到 100 个观测值的预测线。需要明确的是，对于每个观测值，我们只改变了 car_age。我们将其余特征保持为其原始值不变。这些值不会与我们在表 2 中看到的观测值相同。这解释了为什么每条线从不同的级别开始。\n要创建 PDP，最后一步是计算 car_age 在每个值上的平均预测值。这给出了图 3 中的粗黄线。这条线就是 PDP。通过保持其他特征不变并对观测值取平均值，我们能够分离出与 car_age 的关系。我们可以看到，预测价格随着 car_age 的增加而下降。\n你可能已经注意到 x 轴上的短线。这些是 car_age 的四分位数。也就是说，10% 的 car_age 值小于第一行，90% 的 car_age 值小于最后一行。这被称为地毯图。它向我们展示了特征的分布。在本例中，car_age 的值在其范围内分布相当均匀。当我们讨论 PDP 的局限性时，我们将理解为什么这很有用。\n你可能还注意到，并非所有单个预测线都遵循 PDP 趋势。一些较高的线似乎在增加。这表明，对于这些观察结果，价格与 car_age 具有相反的关系。也就是说，预测价格会随着 car_age 的增加而增加。记住这一点。我们稍后在讨论 ICE 图时会回顾这一点。\nPDP 背后的数学原理 对于数学思维更强的人来说，PDP 还有一个正式的定义。让我们从刚刚创建的 PDP 函数开始。它由公式 1 给出。集合 C 将包含除 car_age 之外的所有特征。对于给定的 car_age 值和观测值 i，我们使用 C 中特征的原始值来找到预测价格。我们对所有 100 个观测值执行此操作并找到平均值。这个等式将给出我们在图 3 中看到的粗黄线。\n在公式 2 中，我们概括了上述公式。这是特征集 S 的 PD 函数。C 将包含除 S 中的特征之外的所有特征。我们现在还对 n 个观测值取平均值。这最多可达数据集中的观测值总数（即 1000）。\n$$ \\textbf{方程 2：特征集 S 的近似 PD 函数} \\ pd_S(x_s) = \\frac{1}{n}\\sum_{i=1}^n f(x_s,x_c^{(i)}) $$\n到目前为止，我们仅讨论了 S 由一个特征组成的情况。在上一节中，我们有 S = {car_age}。稍后，我们将向你展示 2 个特征的 PDP。我们通常不会在 S 中包含超过 2 个特征。否则，很难可视化 PD 函数。\n上述方程实际上只是 PD 函数的近似值。真正的数学定义在公式 3中给出。对于集合 S 中的给定值，我们会找到相对于集合 C 的预期预测。为此，我们需要将模型函数与观察集合 C 中的值的概率进行积分。要完全理解这个方程，你需要具备一些随机微积分经验。\n$$ \\textbf{公式 3：特征集 S 的 PD 函数} \\ pd_s(x_s) = E_{X_c}[f(x_s, X_c)] = \\int f(x_s, X_c)dP(X_c) $$\n使用 PDP 时，对近似值的理解就足够了。真正的 PD 函数并不实用。首先，与近似值相比，真正的 PD 函数的计算成本更高。其次，由于我们的观测数量有限，我们只能近似概率。也就是说，我们无法找到每个观测值的真实概率。最后，许多模型不是连续函数，因此很难集成。\n连续特征的 PDP 在了解如何创建 PDP 之后，我们将继续使用它们。使用这些图，我们可以了解 模型特征与目标变量之间关系的性质。例如，我们已经在图 4 中看到了 car_age PDP 。预测价格以相当恒定的速度下降。这表明 car_age 与价格呈 线性关系。\n在图 5 中，我们可以看到另一个特征 repairs 的 PDP 。这是汽车接受的维修/服务次数。最初，预测价格趋于随着维修次数的增加而增加。我们预计一辆可靠的汽车会定期接受一些维护。然后，在 6/7 次维修左右，价格趋于下降。过度维修可能表明汽车出了问题。由此，我们可以看到价格与维修之间存在 非线性关系 。\n从上面可以看出，PDP 在 可视化 非线性关系方面很有用。我们将在文章《查找并可视化非线性关系》中更深入地探讨这个主题。处理许多特征时，查看所有 PDP 可能不切实际。因此，我们还讨论了使用互信息和特征重要性来帮助 找到 非线性关系。\n在 图 6 中，我们可以看到特征与目标变量没有关系时的 PDP 示例。对于 owner_age ，PDP 是恒定的。这告诉我们，当我们改变 owner_age 时，预测价格不会改变。稍后，我们将看到如何使用这种 PDP 变化的思想来创建特征重要性分数。\n分类特征的 PDP 我们上面讨论的特征都是连续的。我们也可以创建分类特征的 PDP。例如，参见图 7 中 car_type 的图。在这里，我们计算每种车型的平均预测值——普通 (0) 或经典 (1)。我们用直方图来可视化这些，而不是用直线。我们可以看到，经典汽车往往以更高的价格出售。\n二元目标变量的 PDP 二元目标变量的 PDP 与连续目标的 PDP 类似。假设我们想要预测汽车价格是高于 (1) 还是低于 (0) 平均值。我们构建一个随机森林，使用相同的特征来预测这个二元变量。我们可以使用与之前相同的过程为该模型创建 PDP。只不过现在我们的预测是一个概率。\n例如，在图 8 中，你可以看到 car_age 的 PDP。我们现在在 y 轴上有一个预测概率。这是汽车价格高于二手车平均价格的概率。我们可以看到，概率随着汽车年龄的增长而下降。\n2 个特征的 PDP 回到我们的连续目标变量。我们还可以可视化两个特征的 PDP。在图 9 中，我们可以看到 km_driven 和 car_age 的不同组合的平均预测值。此图表的创建方式与一个特征的 PDP 相同。即保持其余特征的原始值。\n这些 PDP 可用于可视化特征之间的相互作用。上图表明 km_driven 和 car_age 之间可能存在相互作用。也就是说，当两个特征的值较大时，预测价格往往会较低。在得出此类结论时，你应该谨慎行事。\n这是因为如果两个特征相关，我们可以得到相似的结果。稍后，当我们讨论 PDP 的局限性时，我们会看到情况确实如此。即 km_driven 与 car_age 相关。汽车越旧，行驶里程就越高。这就是为什么当两个特征都较高时，我们看到的预测价格会更低。\n衍生 PDP 导数 PDP 是 PDP 的变体。它显示 PDP 的斜率/导数。它可用于更好地理解原始 PDP。然而，在大多数情况下，我们从这些图中获得的洞察力是有限的。它们通常对非线性关系更有用。\n例如，以图 10 中的维修导数 PDP 为例。这是我们之前在图 5 中看到的线的导数。我们可以看到，在 6 次维修左右，导数为 0。此时，导数从正变为负。换句话说，原始 PDP 从相对于维修增加变为减少。这告诉我们，经过 6 次维修后，汽车的价格将趋于下降。\nPDP 特征重要性 本节结束时，我们根据 PDP 得出一个特征重要性分数。这是通过确定每个特征的 PDP 的“平坦度”来实现的。具体来说，对于连续变量，我们计算图值的标准差。对于分类变量，我们首先取范围来估计 SD。即最大值减去最小 PDP 值。然后我们将范围除以 4。此计算来自称为范围规则的概念。\n你可以在图 11 中看到基于 PDP 的特征重要性。请注意， owner_age 的得分相对较低。如果我们回想一下图 6 中的 PDP，这是有道理的。我们看到 PDP 相对恒定。换句话说，y 轴值始终接近其平均值。它们具有较低的标准差。\n还有更广为人知的特征重要性分数，例如排列特征重要性。你可能更喜欢使用这种基于 PDP 的分数，因为它可以提供一定的一致性。如果你正在分析特征趋势，你现在可以使用使用类似逻辑计算的特征重要性分数。你还可以避免解释两种不同方法的逻辑。\n个体条件期望 (ICE) 图 说完了 PDP，让我们继续讨论 ICE 图。你会很高兴知道我们已经讨论过创建它们的过程。以下面的图 12 为例。这是我们在图 3 中的 car_age PDP 之前创建的图。这是 car_age 的 ICE 图。ICE 图由每个单独观察的预测线组成。\n当模型中存在交互时，ICE 图很有用。也就是说，如果一个特征与目标变量的关系取决于另一个特征的值。在上面的图表中可能很难看到这一点。为了使事情更清楚，我们可以将 ICE 图置于中心。在图 13 中，我们通过使所有预测线从 0 开始来实现这一点。现在很明显，对于某些观察结果，预测价格趋于随着 car_age 的增加而增加。\n为了了解导致这种现象的原因，我们可以更改 ICE 图的颜色。在图 14 中，我们根据 · 更改了颜色。我们将经典汽车的线条设为蓝色，将普通汽车设为红色。我们现在可以看到，这种关系来自 car_age 和 car_type 之间的相互作用。直观地讲，经典汽车的价值会随着年龄的增长而增加，这是有道理的。\n最后，将 PDP 线添加到图中。这样，我们可以将 ICE 图和 PDP 结合起来。这可以强调一些观察结果如何偏离平均趋势。我们可以看到，如果我们只依赖 PDP，我们就会错过这种相互作用。也就是说，当使用图 3 中的 PDP 时，我们得出结论，所有汽车的价格都趋于随着年龄的增长而下降。\n与 PDP 一样，ICE 图可以帮助我们直观地了解数据中的重要关系。为了找到这些关系，我们可能需要使用特征重要性等指标。另一种专门用于突出显示模型中交互作用的指标是 Friedman 的 H 统计量。我们曾在文章《寻找并可视化交互》中讨论如何使用所有这些方法。\n分类特征的 ICE 图 我们可以使用箱线图来可视化分类特征的 ICE 图。例如，我们在图 16 中绘制了 car_type 的 ICE 图。箱线中间的粗线给出了平均预测。换句话说，它们是 PDP。我们可以看到，普通汽车的预测价格趋于较低（0）。\n基于 ICE 图的特征重要性 我们还可以计算基于 ICE 图的特征重要性得分。这与基于 PDP 的得分类似，只是我们不再考虑平均预测线。我们现在使用各个预测线来计算得分。这意味着得分将考虑特征之间的相互作用。\n图 17 中给出了我们模型的基于 ICE 图的分数。我们可以将这些分数与图 11 中的基于 PDP 的分数进行比较。最大的区别是 car_age 的分数现在更高了。它从 300 增加到了 345。根据我们上面的分析，这是有道理的。我们看到存在一个影响 car_age 和 price 之间关系的相互作用。基于 PDP 的特征重要性不考虑这种相互作用。\nPDP 和 ICE 图的优势 到目前为止，希望我们已经很好地理解了 PDP 和 ICE 图以及我们可以从中获得的见解。我们将继续讨论这些方法的优势。然后在下一节中，我们将讨论其局限性。理解这些至关重要，这样你就不会从图中得出错误的结论。\n隔离特征趋势 我们可以使用散点图来可视化数据中的关系，但数据很混乱。例如，我们可以在图 18 中看到 car_age 和 car_type 之间的相互作用。这些点围绕真实的潜在趋势而变化。这是因为统计噪声以及价格也与其他特征有关系这一事实。在真实的数据集中，这个问题可能会更严重。最终，很难看出数据中的趋势。\n使用 PDP 和 ICE 图时，我们不再使用原始数据值。我们使用模型预测。如果构建正确，模型将捕捉数据中的潜在关系并忽略统计噪声。然后我们可以隔离特定特征的趋势。这是通过保持其他特征值不变并对观察值取平均值来实现的。\n这就是 PDP 和 ICE 图如此有用的原因。它们允许我们去除噪音和其他特征的影响。这使得我们更容易看到数据中的潜在关系。从这个意义上说，这些方法可以用于数据探索，而不仅仅是理解我们的模型。\n直接解释 希望通过向你介绍构建 PDP 的过程，你能够轻松理解。这样，你就可以直观地了解该方法，而无需数学定义。这也意味着这些方法很容易向非技术人员解释。这在行业环境中很有用。\n易于实施 这些方法也很容易实现。我们只需要改变特征值并记录结果预测。我们甚至不需要考虑模型的内部工作原理。这意味着相同的实现可以用于任何模型。当我们讨论 R 和 Python 代码时，你会发现这些方法已经有很好的实现。\nPDP 和 ICE 图的局限性 假设特征独立 继续讨论局限性，我们将从这些方法的主要问题开始。即它们假设特征是独立的。情况并非总是如此，因为特征可以相互关联或相关联。例如，以图 19 中的 km_driven 和 car_age 散点图为例。它们之间存在明显的相关性。直观上看，这是有道理的。较旧的汽车往往行驶距离较长。\n问题是，当我们为观察结果构建预测线时，我们将对特征的所有可能值进行采样。例如，假设我们想为 km_driven 构建 PDP。以图 20 中红色给出的观察结果为例。它的 car_age 为 10。为了构建预测线，我们将针对虚线椭圆中的所有值改变 km_driven。然而，实际上，具有此 car_age 的观察结果仅在实线椭圆内具有行驶距离。\n我们的模型并未针对实心椭圆之外的观测进行训练。尽管如此，我们仍会根据这些观测的预测来创建 PDP。结果是，预测线是建立在模型之前未“见过”的观测之上的。这可能会产生不直观的结果，并导致关于特征趋势的错误结论。\n平等关注所有特征值 即使特征不相关，我们仍然可能得出错误的结论。对于每个观察值，我们都对所有可能的特征值进行采样。这为所有值赋予了相同的权重。实际上，特征的某些值不太常见。例如在特征分布的极端值。这些值的趋势将更加不确定。\n考虑到这一点，通常会包含一个地毯图。这有助于我们了解特征的分布。之前我们看到了地毯图的分位数版本。图 21 给出了另一个版本。这里我们为每个观察值绘制了一条单独的线。我们可以看到， km_driven 值越高，观察值就越少。因此，我们应该更加谨慎地解释这些值的趋势。\n结论取决于你的模型 正如优点中提到的，使用模型预测可以帮助我们更清楚地看到关系。问题是模型可能会做出错误的预测。欠拟合的模型可能会错过重要的关系。通过建模噪声，过度拟合的模型可以呈现实际上不存在的关系。最终，我们得出的结论将取决于我们的模型。考虑模型的性能很重要。\n即使模型准确，我们仍可能遇到问题。模型可能会忽略某些关系而偏向其他关系。我们可能会得出错误的结论，认为被忽略的特征与目标变量没有关系。这意味着，在进行数据探索时，你可能希望将特征限制在你感兴趣的子集内。\nPDP 忽略了互动 如前所述，使用平均值，PDP 可能会错过交互。因此，基于 PDP 的特征重要性也会错过这些交互。这意味着，如果存在交互，分数可能会低估特征的重要性。我们在 car_age 特征中看到了这一点。一种解决方案是使用 ICE 图或仅坚持排列特征重要性。\n实施的局限性 在接下来的部分中，我们将向你介绍用于实现这些方法的代码。我们将看到每个实现都有自己的优点和缺点。有些包不会实现我们讨论的所有图。例如，如果你使用 Python，则没有（据我所知）用于衍生 PDP 或特征重要性的实现。\nPDP 和 ICE 图的 R 代码 在本节中，我们将向你介绍用于创建 PDP 和 ICE 图的 R 代码。我们将研究使用三个不同的包。我们使用 ICEbox1 和 iml2 来创建图。结合起来，我们可以创建上面讨论的所有图。对于特征重要性分数，我们使用 vip3 。你可以在 GitHub5 上找到我们讨论的所有代码。\n我们首先加载数据集（第 1 行）。这与文章开头表 1 中讨论的数据集相同。我们还将 car_type 设置为分类特征（第 2 行）。\ndataset = read.csv(\u0026#39;PDP_ICE.csv\u0026#39;,sep = \u0026#34;\\t\u0026#34;) dataset$car_type = factor(dataset$car_type, levels = c(0, 1)) 建模 在创建 PDP 和 ICE 图之前，我们需要一个模型。我们使用 randomForest 包来执行此操作（第 1 行）。我们使用价格和 6 个特征构建一个模型（第 4-6 行）。具体来说，我们使用了一个包含 100 棵树的随机森林（第 6 行）。\nlibrary(randomForest) # Train model on continuous target variable rf = randomForest(x = dataset[0:5], y = dataset$price, ntree = 100) 对于下面的大多数图，我们将使用模型 rf。该模型已在连续目标变量上进行了训练。我们还想在二进制目标变量上构建一个模型 rf_binary。这是为了向你展示不同类型的目标的代码和输出有何不同。\n首先，我们创建二元目标变量。如果原车价格高于平均水平，则其值为 1，如果低于平均水平，则其值为 0（第 2-4 行）。然后，我们像以前一样构建一个随机森林（第 7-9 行）。有了这些模型，我们现在可以继续使用 PDP 和 ICE 图来了解它们的工作原理。随着我们的前进，输出将显示在相关代码下方。\n# Create binary target variable mean_price = mean(dataset$price) price_binary = as.integer(dataset$price \u0026gt; mean_price) price_binary = factor(price_binary, levels = c(0, 1)) # Train model on binary target variable rf_binary = randomForest(x = dataset[0:5], y = price_binary, ntree = 100) 包 — ICEbox 我们将从 ICEbox 包开始（第 1 行）。我们将使用它为 car_age 创建 PDP。我们使用 ice 函数为 car_age 创建 iceplot 对象（第 4-7 行）。我们传递模型、特征和目标变量（第 4-6 行）。此对象将包含 car_age 的所有单独预测线。它还将包含平均预测线（即 PDP）。\n然后，我们使用 plot 函数显示 iceplot 对象（第 9-11 行）。默认情况下，此包将始终显示 ICE 图。要创建 PDP，我们需要隐藏各个预测线。我们通过将它们全部设为白色（第 11 行）来实现这一点。我们还隐藏了给出原始 car_age 值的点（第 10 行）。\nrequire(ICEbox) # create iceplot object iceplot = ice(object = rf, X = dataset[0:5], y = dataset$price, predictor = \u0026#34;car_age\u0026#34;) plot(iceplot, plot_orig_pts_preds = F, colorvec = \u0026#39;000000\u0026#39;) 要创建 ICE 图，我们可以使用相同的 iceplot 对象。现在，我们不再隐藏各个线条，而是根据它们的 car_type 为它们着色（第 5 行）。经典汽车和普通汽车通过将线条分别设为蓝色和红色来区分。我们还输入了 ICE 图（第 3 行）。我们通过仅绘制 10% 的各个线条（第 2 行）将图限制为 100 个观测值。\nplot(iceplot, frac_to_plot = 0.1, centered = T, plot_orig_pts_preds = F, color_by = \u0026#34;car_type\u0026#34;) 我们还可以使用 ICEBox 包来绘制衍生 PDP。我们以与之前相同的方式创建一个用于修复的 iceplot 对象（第 1-4 行）。然后我们使用它来创建一个骰子对象（第 5 行）。最后，我们绘制骰子对象（第 7-10 行）。我们设置了 plot_sd = F（第 10 行）。这隐藏了各个预测线的标准偏差。\niceplot = ice(object = rf, X = dataset[0:5], y = dataset$price, predictor = \u0026#34;repairs\u0026#34;) dice = dice(iceplot) plot(dice, plot_orig_pts_deriv = F, colorvec = \u0026#39;000000\u0026#39;, plot_sd=F) 我们使用此包创建的最后一个图是二进制目标变量的 PDP。代码与之前类似。除了使用 rf_binary 之外，唯一的区别是传入一个预测函数（第 4-6 行）。这让 ice 函数知道我们的预测是以概率形式给出的。\niceplot = ice(object = rf_binary, X = dataset[0:5], predictor = \u0026#34;car_age\u0026#34;, predictfcn = function(object, newdata){ predict(object, newdata, type = \u0026#34;prob\u0026#34;)[, 2] }) plot(iceplot, plot_orig_pts_preds = F, colorvec = \u0026#39;000000\u0026#39;) ICEBox 包有一些优势。通过使用另一个特征为 ICE 图着色，我们可以清楚地看到交互作用。它也是唯一实现了衍生 PDP 的包。在限制方面，它不处理分类特征。这意味着我们无法为 car_type 特征创建图。它还没有为 2 个特征实现 PDP。\n包 — iml 下一个包 iml 可以解决其中的一些限制。我们将首先使用它为 car_age 创建 PDP。我们使用随机森林和数据集创建一个预测器对象（第 4 行）。然后，我们使用它创建一个特征效果对象（第 7-9 行）。我们使用“pdp”作为特征效果方法（第 9 行）。最后，我们绘制这个特征效果对象（第 10 行）。\nrequire(iml) # create prediction object pred \u0026lt;- Predictor$new(rf, data = dataset) # create feature effect object eff \u0026lt;- FeatureEffect$new(pred, feature = \u0026#34;car_age\u0026#34;, method = \u0026#34;pdp\u0026#34;) plot(eff) 我们使用类似的代码为 car_age 创建 ICE 图。我们使用与之前相同的预测器对象（第 1 行）。现在我们将方法设置为“pdp+ice”（第 3 行）。这将为我们提供一个组合的 PDP 和 ICE 图。我们还将图居中，因此所有预测线都从 0 开始（第 4 行）。将方法设置为“ice”将删除黄色 PDP。\neff \u0026lt;- FeatureEffect$new(pred, feature = \u0026#34;car_age\u0026#34;, method = \u0026#34;pdp+ice\u0026#34;, center.at = 0) plot(eff) iml 包还可以处理分类特征。下面我们为 car_type 创建 PDP（第 2-5 行）。这为我们提供了下面的直方图。同样，我们为 car_type 创建 ICE 图（第 8-11 行）。这为我们提供了箱线图。\n# PDP of categorical variable eff \u0026lt;- FeatureEffect$new(pred, feature = \u0026#34;car_type\u0026#34;, method = \u0026#34;pdp\u0026#34;) plot(eff) # ICE Plot of categorical variable eff \u0026lt;- FeatureEffect$new(pred, feature = \u0026#34;car_type\u0026#34;, method = \u0026#34;ice\u0026#34;) plot(eff) iml 的另一个优点是它为 2 个特征实现了 PDP。下面我们为 car_age 和 km_driven 创建 PDP。代码与之前类似。唯一的区别是我们传递了一个包含两个特征名称的向量（第 2 行）。\neff \u0026lt;- FeatureEffect$new(pred, feature = c(\u0026#34;car_age\u0026#34;,\u0026#34;km_driven\u0026#34;), method = \u0026#34;pdp\u0026#34;) plot(eff) 最后，我们为二进制目标变量创建一个 PDP。我们使用 rf_binary （第 1 行）创建预测器对象，但其余代码与以前相同。你可以看到我们现在有两个图。请注意，它们是彼此相反的。这是因为第一个图给出了汽车低于平均水平的概率（0）。第二个图给出了它高于平均水平的概率（1）。当你的目标变量具有超过 2 个值时，为每个值绘制图很有用。\npred \u0026lt;- Predictor$new(rf_binary, data = dataset) eff \u0026lt;- FeatureEffect$new(pred, feature = \u0026#34;car_age\u0026#34;, method = \u0026#34;pdp\u0026#34;) plot(eff) 套餐 — vip 上述两个包都没有实现特征重要性分数。为了计算这些分数，我们使用 vip 包（第 1 行）。创建基于 PDP 的分数（第 4 行）和基于 ICE 的分数（第 7 行）非常简单。我们将方法设置为“firm”。这代表特征重要性排名度量。\nlibrary(vip) #PDP-base feature importance vip(rf, method = \u0026#34;firm\u0026#34;) #ICE-base feature importance vip(rf, method = \u0026#34;firm\u0026#34;,ice = TRUE) PDP 和 ICE 图的 Python 代码 在本节中，我们将向你介绍用于创建 PDP 和 ICE 图的 Python 代码。你也可以在 GitHub6 上找到此代码。我们将使用 scikit-learn 的 PartialDependenceDisplay4 实现。另一个我们不会讨论的软件包是 PDPbox7 。\n我们首先导入 Python 包。我们导入一些用于处理和可视化数据的常用包（第 2-4 行）。我们有两个不同的建模包 - RandomFrorestRegressor （第 6 行）和 xgboost（第 7 行）。最后，我们用我们的包来创建 PDP 和 ICE 图（第 9-10 行）。第一个包用于可视化图。第二个包用于获取用于创建图的预测。这在我们稍后探索分类特征时会派上用场。\n# imports import pandas as pd import numpy as np import matplotlib.pyplot as plt %matplotlib inline plt.style.use(\u0026#39;default\u0026#39;) from sklearn.ensemble import RandomForestRegressor import xgboost as xgb from sklearn.inspection import PartialDependenceDisplay from sklearn.inspection import partial_dependence from sklearn.metrics import accuracy_score,confusion_matrix import os dp = os.environ.get(\u0026#39;pub_data\u0026#39;) path = \u0026#39;../../../figures/\u0026#39; PDP 包由 scikit-learn 提供。你仍然可以将它们用于未使用 scikit-learn 包创建的模型。稍后当我们使用 xgb 包对二进制目标变量进行建模时，你会看到这一点。\n连续目标变量 我们将从连续目标变量开始。我们加载数据集（第 2 行）。这与我们在文章开头的表 1 中讨论的相同。我们得到目标变量（第 5 行）和特征（第 6 行）。我们用它们来训练随机森林（第 9-10 行）。具体来说，随机森林由 100 棵树组成。每棵树的最大深度为 4。\n# Load dataset data = pd.read_csv(dp + \u0026#34;PDP_ICE.csv\u0026#34;,sep=\u0026#39;\\t\u0026#39;) # Get features y = data[\u0026#39;price\u0026#39;] X = data.drop(\u0026#39;price\u0026#39;, axis=1) # Model rf = RandomForestRegressor(max_depth=4, n_estimators=100,random_state=0) rf.fit(X, y) 我们现在可以使用 PartialDependenceDisplay 包来了解该模型的工作原理。首先，我们将为 car_age 创建一个 PDP。为此，我们使用 from_estimator 函数。我们传入模型、X 特征矩阵和特征名称。你可以在代码下方看到输出。\n# PDP features = [\u0026#34;car_age\u0026#34;] PartialDependenceDisplay.from_estimator(rf, X, features) 要创建 ICE 图，我们需要将 kind 参数设置为“individual”（第 4 行）。我们还将图居中（第 6 行）。默认情况下，这些函数仅显示特征的 5% 到 95% 百分位数。如果要显示 car_age 的全部范围，则需要更改此设置。即 0 到 40。我们在第 5 行执行此操作。\nPartialDependenceDisplay.from_estimator( rf, X, features, kind=\u0026#39;individual\u0026#39;, percentiles = (0,1), centered= True) 下面我们探讨一些其他选项。将 kind 功能设置为“both”将同时显示 PDP 和 ICE 图（第 6 行）。我们还可以使用 ice_lines_kw （ 第 7 行）和 pd_line_kw（第 8 行）参数分别更改 ICE 图和 PDP 线的样式。\nPartialDependenceDisplay.from_estimator( rf, X, features, kind=\u0026#39;both\u0026#39;, centered= True, ice_lines_kw={\u0026#34;color\u0026#34;: \u0026#34;black\u0026#34;}, pd_line_kw={\u0026#34;color\u0026#34;: \u0026#34;red\u0026#34;,\u0026#34;lw\u0026#34;:3,\u0026#39;linestyle\u0026#39;:\u0026#39;--\u0026#39;}) 创建 2 个特征的 PDP 与之前类似。我们需要做的就是传递一个特征名称数组，而不是单个特征名称。你可以在下面看到我们如何为 car_age 和 km_driven 执行此操作。\nfeatures = [(\u0026#34;car_age\u0026#34;,\u0026#34;km_driven\u0026#34;)] PartialDependenceDisplay.from_estimator(rf, X, features) scikit-learn 包的一个缺点是它将分类特征视为连续特征。你可以在下面看到其结果。PDP 和 ICE 图由线给出。直方图和箱线图可以给出更好的解释。\nfeatures = [\u0026#34;car_type\u0026#34;] PartialDependenceDisplay.from_estimator(rf, X, features,kind=\u0026#39;both\u0026#39;) 我们可以通过创建自己的图来解决这个问题。我们使用 partial_dependence 函数（第 3 行）来实现这一点。我们使用这个函数的方式与 for_estimator 函数相同。不同之处在于它不显示图。它只返回用于创建图的数据。pd_ice 将包含 car_type 的 PDP 和 ICE 图的数据。\n# Get values features = [\u0026#34;car_type\u0026#34;] pd_ice = partial_dependence(rf, X, features, kind=\u0026#39;both\u0026#39;) 你可以在下面看到我们如何使用它来创建 PDP。我们首先从 pd_ice （第 2 行）获取平均值。这将是普通汽车和经典汽车的平均预测。使用这些，我们绘制了一个条形图（第 5-14 行）。\n# Get PD values pd = pd_ice[\u0026#39;average\u0026#39;][0] # Plot PD values plt.figure(figsize=(8, 5)) labels = [\u0026#39;normal\u0026#39;,\u0026#39;classic\u0026#39;] x = np.arange(len(labels)) plt.bar(x,height= pd) plt.ylabel(\u0026#39;Partial yhat\u0026#39;,size=15) plt.xlabel(\u0026#39;Car Type\u0026#39;,size=15) plt.xticks(ticks=x,labels=labels) 对于 ICE 图，我们可以创建一个箱线图。我们从 pd_ice 中获取单个预测（第 2 行）。然后，我们将它们分为普通汽车和经典汽车预测（第 4-6 行）。最后，我们绘制箱线图（第 9-14 行）。\n# get ice values ice = pd_ice[\u0026#39;individual\u0026#39;][0] normal_ice = [i[0] for i in ice] classic_ice = [i[1] for i in ice] data = [normal_ice, classic_ice] # plot ice values plt.figure(figsize=(8, 5)) plt.boxplot(data,labels=labels) plt.ylabel(\u0026#39;Partial yhat\u0026#39;,size=15) plt.xlabel(\u0026#39;Car Type\u0026#39;,size=15) 二元目标变量 最后，我们将为二进制目标变量创建一个 ICE 图。首先，我们创建变量（第 2-3 行）。如果原车价格高于平均水平，则其值为 1，如果低于平均水平，则其值为 0。我们使用这个二进制变量训练模型（第 6-7 行）。这次我们使用了 max_depth 为 2 且有 100 棵树的 XBGClassifier。\n# Binary target variable avg_y = np.mean(y) y_binary = [1 if y_\u0026gt;avg_y else 0 for y_ in y] # Train model xg = xgb.XGBClassifier(objective=\u0026#34;binary:logistic\u0026#34;,max_depth=2, n_estimators=100) xg.fit(X, y_binary) 我们像之前一样绘制了 car_age 的 ICE 图。你会注意到，y 轴现在给出的是（中心）概率而不是价格。你还可以看到，将 scikit-learn 包与其他建模包一起使用非常简单。\nfeatures = [\u0026#34;car_age\u0026#34;] PartialDependenceDisplay.from_estimator( xg, X, features, kind=\u0026#39;both\u0026#39;, centered=True, ice_lines_kw={\u0026#34;color\u0026#34;: \u0026#34;black\u0026#34;}, pd_line_kw={\u0026#34;color\u0026#34;: \u0026#34;red\u0026#34;,\u0026#34;lw\u0026#34;:3,\u0026#39;linestyle\u0026#39;:\u0026#39;--\u0026#39;}) 使用 Python 的缺点是，据我所知，没有针对衍生 PDP 和特征重要性分数的实现。不过，对于大多数问题，上述图表就是你所需要的。如果你需要其他方法，你可以使用 partial_dependence 函数自行实现它们。\n希望你觉得这篇文章有用！我真的希望它成为PDP 和 ICE 图的终极指南。如果你认为我遗漏了任何内容，可以给我留言提出。此外，如果有任何不清楚的地方，请告诉我。我很乐意更新文章（不过公众号无法大幅度更新内容，需要在其他地方进行更新。） :)\nPDP 和 ICE 图是可解释的机器学习方法。它们用于了解我们的模型如何进行预测。另一种流行的方法是 SHAP。SHAP 值的优点是它们可用于了解单个预测是如何做出的。它们也可以汇总起来以了解整个模型的工作原理。在文章《Python 中的 SHAP 简介》中，我们探讨了如何使用 Python 应用此方法。\n希望这篇文章对你有所帮助！你还可以阅读我的其他文章，或者查看有关企业 AI 实战项目的教程，相信会让你拥有更多收获。\n「AI秘籍」系列课程：\n人工智能应用数学基础\n人工智能Python基础\n人工智能基础核心知识\n人工智能BI核心知识\n人工智能CV核心知识\n参考 C. Molnar, Interpretable Machine Learning (2021), https://christophm.github.io/interpretable-ml-book/\nS. Masís, Interpretable Machine Learning with Python (2021)\nGreenwell, B.M., Boehmke, B.C. and McCarthy, A.J., (2018) . A simple and effective model-based variable importance measure. https://arxiv.org/abs/1805.04755\nICEbox，https://cran.r-project.org/web/packages/ICEbox/ICEbox.pdf\u0026#160;\u0026#x21a9;\u0026#xfe0e;\u0026#160;\u0026#x21a9;\u0026#xfe0e;\niml, https://cran.r-project.org/web/packages/iml/iml.pdf\u0026#160;\u0026#x21a9;\u0026#xfe0e;\u0026#160;\u0026#x21a9;\u0026#xfe0e;\nvip, https://www.rdocumentation.org/packages/vip/versions/0.3.2\u0026#160;\u0026#x21a9;\u0026#xfe0e;\u0026#160;\u0026#x21a9;\u0026#xfe0e;\nPartialDepenceDisplays, https://scikit-learn.org/stable/modules/generated/sklearn.inspection.PartialDependenceDisplay.html\u0026#160;\u0026#x21a9;\u0026#xfe0e;\u0026#160;\u0026#x21a9;\u0026#xfe0e;\nGithub, R, https://github.com/hivandu/public_articles/blob/main/src/interpretable_ml/PDPs_and_ICE_Plots/PDP_ICE.R\u0026#160;\u0026#x21a9;\u0026#xfe0e;\nGithub, Python, https://github.com/hivandu/public_articles/blob/main/src/interpretable_ml/PDPs_and_ICE_Plots/PDP_ICE.ipynb\u0026#160;\u0026#x21a9;\u0026#xfe0e;\nPDPbox, https://pdpbox.readthedocs.io/en/latest/index.html?highlight=importance#the-common-headache\u0026#160;\u0026#x21a9;\u0026#xfe0e;\n","permalink":"https://hivan.me/posts/pdp-%E5%92%8C-ice-%E5%9B%BE%E7%9A%84%E7%BB%88%E6%9E%81%E6%8C%87%E5%8D%97/","summary":"\u003cblockquote\u003e\n\u003cp\u003e部分依赖图和单独条件期望图背后的直觉、数学和代码（R 和 Python）\u003c/p\u003e\u003c/blockquote\u003e","title":"PDP 和 ICE 图的终极指南"},{"content":"\n结合 [[CatBoost]] 和 [[SHAP]] 可以提供强大的洞察力。特别是当你使用分类特征时。CatBoost 处理这些特征的方式使你更容易理解使用 SHAP 的模型。\n对于其他建模包，我们需要先使用 One-Hot 编码转换分类特征。问题是每个二进制变量都有自己的 SHAP 值。这使得很难看到原始分类特征的整体贡献。\n在 [分类特征的 SHAP](../分类特征的 SHAP) 中，我们探讨了一种解决方案。它涉及深入研究 SHAP 对象并手动添加各个 SHAP 值。这可能很乏味！作为替代方案，我们可以使用 CatBoost。\nCatBoost 是一个梯度提升库。与其他库相比，它的一大优势是它可以处理非数值特征。无需转换分类特征即可使用它们。这意味着 CatBoost 模型的 SHAP 值易于解释。每个分类特征只有一个 SHAP 值。\n我们将：\n计算并解释 CatBoost 模型的 SHAP 值 应用 SHAP 聚合 ——我们将看到，在理解分类特征的关系时，它们的作用是有限的 为了解决这个限制，将为单个特征创建一个蜂群图 。 在此过程中，我们将介绍用于获取这些结果的 [Python 代码]1。 你可能会喜欢有关此主题。如果你想了解更多，请查看我的 SHAP 课程。\n数据集 对于此分析，我们将使用与之前相同的数据集。这是一个蘑菇分类数据集。你可以在 图 1 中看到此数据集的快照。目标变量是蘑菇的 类别。 也就是说，蘑菇是有毒 (p) 还是可食用 (e)。你可以在 [UCI 的 MLR]2 中找到此数据集。\n对于模型特征，我们有 22 个分类特征。对于每个特征，类别都用一个字母表示。例如， odor 有 9 个独特的类别 - 杏仁 (a)、茴香 (l)、杂酚油 (c)、鱼腥味 (y)、恶臭 (f)、霉味 (m)、无味 (n)、辛辣 (p)、辛辣 (s)。这就是蘑菇的气味。\n建模 我们将向你介绍用于分析此数据集的代码，你可以在 [GitHub]1 上找到完整的脚本。首先，我们将使用下面的 Python 包。我们有一些用于处理和可视化数据的常用包（第 1-5 行）。我们使用 CatBoostClassifier 进行建模（第 7 行）。最后，我们使用 shap 来了解我们的模型如何工作（第 9 行）。确保你已安装所有这些包。\nimport pandas as pd import numpy as np import matplotlib.pyplot as plt plt.style.use(\u0026#39;default\u0026#39;) %matplotlib inline from catboost import CatBoostClassifier import shap # %shap.initjs() from sklearn.metrics import accuracy_score,confusion_matrix import os 我们导入数据集（第 2 行）。我们需要一个数值目标变量，因此我们通过设置 toxicous = 1 和 edible = 0（第 6 行）对其进行转换。我们还获得了分类特征（第 7 行）。在上一篇文章的这一点上，我们需要转换这些特征。使用 CatBoost，我们可以按原样使用它们。\n# load data data = pd.read_csv(dp + \u0026#39;/mushrooms.csv\u0026#39;) # get features y = data[\u0026#39;class\u0026#39;] y = y.astype(\u0026#39;category\u0026#39;).cat.codes X = data.drop(\u0026#39;class\u0026#39;, axis=1) print(len(data)) data.head() 在下面训练模型时，你可以看到这一点（第 7 行）。我们传递非数值特征（X）、目标变量（y）和表示特征是分类的列表（ cat_features ）。我们所有的特征都是分类的。这意味着 cat_features 是一个从 0 到 21 的数字列表。最后，分类器由 20 棵树组成，每棵树的最大深度为 3。它在训练集上的准确率为 98.7%。\nmodel = CatBoostClassifier(iterations=20, learning_rate=0.01, depth=3) # train model cat_features = list(range(len(X.columns))) model.fit(X, y, cat_features) # Get predictions y_pred = model.predict(X) print(confusion_matrix(y, y_pred)) accuracy_score(y, y_pred) --- 0:\tlearn: 0.6660194\ttotal: 62.8ms\tremaining: 1.19s 1:\tlearn: 0.6373576\ttotal: 67ms\tremaining: 603ms ... 19:\tlearn: 0.3218024\ttotal: 141ms\tremaining: 0us [[4208 0] [ 120 3796]] 0.9852289512555391 SHAP 图 现在我们可以继续了解我们的模型是如何做出这些预测的。如果你不熟悉 SHAP 或 Python 包，我建议你阅读下文章[Python 中的 SHAP 简介](../Python 中的 SHAP 简介)。我们深入探讨了如何解释 SHAP 值。我们还探讨了本文中使用的一些聚合。\n瀑布图 我们首先计算 SHAP 值（第 2-3 行）。然后，我们使用瀑布图（第 6 行）将第一个预测的 SHAP 值可视化。你可以在图 2 中看到此图。这告诉我们每个分类特征值对预测的贡献。例如，我们可以看到这种蘑菇有杏仁（a）的气味。这使对数几率降低了0.85。换句话说，它降低了蘑菇有毒的可能性。\n# get shap values explainer = shap.Explainer(model) shap_values = explainer(X) # waterfall plot for first observation shap.plots.waterfall(shap_values[1],show=False) 从上图中，很容易看出每个特征的贡献。相比之下，我们在图 3 中有一个瀑布图。如前所述，这是在上一篇文章中创建的。为了对分类特征进行建模，我们首先使用 One-Hot 编码对它们进行转换。这意味着每个二进制特征都有自己的 SHAP 值。例如，气味将有 9 个 SHAP 值。每个独特类别都有一个。因此，很难理解气味对预测的整体贡献。\n我们能够利用图 3 中的 SHAP 值来创建类似于图 2 的图。这样，每个分类特征就只有一个 SHAP 值。为此，我们需要通过将一个分类特征的所有值加在一起来“后处理” SHAP 值。不幸的是，没有直接的方法可以做到这一点。我们需要自己手动更新 SHAP 值对象。我们已经看到，通过使用 CatBoost，可以避免这个过程。\n绝对平均 SHAP SHAP 聚合也适用于 CatBoost。例如，我们在下面的代码中使用平均 SHAP 图。查看图 5，我们可以使用此图突出显示重要的分类特征。例如，我们可以看到气味往往具有较大的正/负 SHAP 值。\n# Mean SHAP shap.plots.bar(shap_values,show=False) 蜂群 另一种常见的聚合是蜂群图。对于连续变量，此图很有用，因为它可以帮助解释关系的性质。也就是说，我们可以看到 SHAP 值如何与特征值相关联。但是，对于分类特征，特征值不是数字。因此，在图 6 中，你可以看到 SHAP 值都被赋予了相同的颜色。我们需要创建自己的图来了解这些关系的性质。\nshap.plots.beeswarm(shap_values, show=False) Beeswarm 的一个功能 一种方法是对单个特征使用蜂群图。你可以在图 6 中看到我们的意思。在这里，我们根据气味类别对气味特征的 SHAP 值进行了分组。例如，你可以看到难闻的气味会导致更高的 SHAP 值。这些蘑菇更有可能有毒。在上一篇文章中，我们使用箱线图得到了类似的结果。\n我们不会详细讨论此图的代码。简而言之，我们需要创建一个新的 SHAP 值对象 shap_values_odor。这是通过“后处理” SHAP 值来完成的，因此它们处于我们想要的形式。我们用气味的 SHAP 值替换原始 SHAP 值（第 24 行）。我们还用气味类别替换特征名称（第 43 行）。如果我们正确创建了 shap_values_odor，我们可以使用 beeswarm 函数来创建图（第 46 行）。\n# Create for placeholder SHAP values shap_values_odor = explainer(X) # Get shaply values and feature values for odor odor_values = np.array(shap_values[:,4].values) odor_data = X[\u0026#39;odor\u0026#39;] # Create new SHAP values array # Split odor SHAP values by unique odor categories unique_odor = [\u0026#39;a\u0026#39;,\u0026#39;l\u0026#39;,\u0026#39;c\u0026#39;,\u0026#39;y\u0026#39;,\u0026#39;f\u0026#39;,\u0026#39;m\u0026#39;,\u0026#39;n\u0026#39;,\u0026#39;p\u0026#39;,\u0026#39;s\u0026#39;] new_shap_values = [np.array(pd.Series(odor_values)[odor_data==odor]) for odor in unique_odor] # Each sublist needs to be the same length max_len = max([len(v) for v in new_shap_values]) new_shap_values = [np.append(vs,[np.nan]*(max_len - len(vs))) for vs in new_shap_values] new_shap_values = np.array(new_shap_values) # transpost matrix so categories are columns and SHAP values are rows new_shap_values = new_shap_values.transpose() # replace shap values shap_values_odor.values = np.array(new_shap_values) # replace data with placeholder array shap_values_odor.data = np.array([[0]*len(unique_odor)]*max_len) # replace base data with placeholder array shap_values_odor.base = np.array([0]*max_len) # replace feature names with category labels odor_labels = {\u0026#39;a\u0026#39;:\u0026#39;almond\u0026#39;, \u0026#39;l\u0026#39;:\u0026#39;anise\u0026#39;, \u0026#39;c\u0026#39;:\u0026#39;creosote\u0026#39;, \u0026#39;y\u0026#39;:\u0026#39;fishy\u0026#39;, \u0026#39;f\u0026#39;:\u0026#39;foul\u0026#39;, \u0026#39;m\u0026#39;:\u0026#39;musty\u0026#39;, \u0026#39;n\u0026#39;:\u0026#39;none\u0026#39;, \u0026#39;p\u0026#39;:\u0026#39;pungent\u0026#39;, \u0026#39;s\u0026#39;:\u0026#39;spicy\u0026#39;} labels = [\u0026#34;{} ({})\u0026#34;.format(odor_labels[u],u) for u in unique_odor] shap_values_odor.feature_names = list(labels) # Use besswarm as before shap.plots.beeswarm(shap_values_odor,color_bar=False,show=False) 最后，SHAP 和 CatBoost 是分析分类特征的强大工具。这两个软件包可以无缝协作。缺点是你可能不想使用 CatBoost。如果你正在使用 RandomForest、XGBoost 或神经网络等模型，则需要使用替代解决方案。你可以在文章[分类特征的 SHAP](../分类特征的 SHAP)中找到它。我们还将更详细地介绍如何对 SHAP 值进行后处理。\n希望这篇文章对你有所帮助！如果你想了解更多，可以关注「坍缩的奇点」，阅读我更多其他文章。以下为一些系列文章，你可以系统的学习机器学习的相关核心知识。\n「AI秘籍」系列课程：\n人工智能应用数学基础\n人工智能Python基础\n人工智能基础核心知识\n人工智能BI核心知识\n人工智能CV核心知识\n参考 S. Lundberg, SHAP Python package (2021), https://github.com/slundberg/shap\nS. Lundberg \u0026amp; S. Lee, A Unified Approach to Interpreting Model Predictions (2017), https://arxiv.org/pdf/1705.07874.pdf\nGithub, https://github.com/hivandu/public_articles/blob/main/src/interpretable_ml/SHAP/SHAP_catboost.ipynb\u0026#160;\u0026#x21a9;\u0026#xfe0e;\u0026#160;\u0026#x21a9;\u0026#xfe0e;\nDataset， https://archive.ics.uci.edu/ml/datasets/Mushroom\u0026#160;\u0026#x21a9;\u0026#xfe0e;\n","permalink":"https://hivan.me/posts/%E4%BD%BF%E7%94%A8-catboost-%E5%AE%9E%E7%8E%B0%E5%88%86%E7%B1%BB%E7%89%B9%E5%BE%81%E7%9A%84-shap/","summary":"\u003cblockquote\u003e\n\u003cp\u003e避免对分类特征的 SHAP 值进行后处理\u003c/p\u003e\u003c/blockquote\u003e","title":"使用 CatBoost 实现分类特征的 SHAP"},{"content":"\n黑盒模型可以自动模拟复杂的关系。与线性模型相比，捕捉数据中的这些趋势可以提高其准确性。\n然而，准确性只是好处之一。\n我们可以分析模型，了解它们如何做出这些预测。这可以揭示数据集中的潜在关系。在某些情况下，这些关系对我们来说可能是全新的。这就是机器学习如何成为数据探索和知识生成的工具。\n此外，提供感知检查以帮助调试模型。这些知识可用于\n为非线性模型提供特征工程信息。 在做出超越模型的决策时提供帮助。 我们将讨论如何从黑盒模型中学习。其中包括理解数据中的重要关系及其性质。\n为什么我们可以从非线性模型中学习？ 在深入探讨之前，让我们先讨论一下为什么我们可以从这些模型中学习。非线性模型可以模拟我们数据中的非线性关系和相互作用。例如，以图 1 中的二手车价格散点图为例。汽车价格(price)并不总是随着车龄(age)的增加而下降。对于经典汽车（classic = 1），价格会增加。我们说我们的数据中存在相互作用。价格和车龄之间的关系取决于第三个特征——classic。\n非线性模型可以自动模拟此类关系。让我们以图 2 中的简单决策树为例。首先将汽车分为普通 (0) 和经典 (1)。对于普通汽车，如果汽车车龄不到 15 年，则价格较高。对于经典汽车，价格较低。这并不能完全捕捉我们在图 1 中看到的关系。为此，我们可以向决策树添加更多层。我们还可以使用更复杂的算法，如随机森林、XGBoost 或神经网络。\n当我们谈论数据探索/知识生成时，我们通常会依赖这些更复杂的算法。它们有时被称为可解释模型或黑盒模型。它们能够捕捉数据中的复杂关系。同时，我们不需要“告诉”它们这些关系存在。就像决策树一样，我们可以只给它们经典和年龄特征。模型会在进行预测时自动利用特征之间的相互作用。\n相比之下，线性模型（即线性和逻辑回归）不太复杂。要用这些模型捕捉非线性关系，我们需要进行特征工程。使用新特征，我们可以将非线性关系表示为线性关系。要了解要创建什么特征，我们需要探索我们的数据。我们可能还需要相关领域的领域知识。\n从某种意义上说，黑盒模型在模型结构内进行特征工程。它们非常擅长在数据中发现模式。问题是模型的内部工作原理太复杂了。我们无法仅通过查看模型参数/结构来了解它们如何进行预测。我们需要使用其他技术来了解它们的工作原理。\n我们能学到什么？如何学到？ 这就是可解释机器学习 (IML) 领域的用武之地。除其他外，它旨在开发我们可以用来理解黑盒模型如何进行预测的方法。我们讨论其中一些技术以及它们可以教给我们关于模型/数据的知识。\n提供基准 首先，黑盒模型可以提供的一个有价值的信息是基准。通过捕捉非线性关系和相互作用，这些模型可以提高预测的准确性。要确定性能，我们实际上并不需要 IML 方法。我们可以依靠准确率、精确率或召回率等指标或 ROC 曲线等可视化。\n使用黑盒模型可以告诉我们我们能够多好地预测目标变量。我们可以将这些结果与不太复杂的线性模型的性能进行比较。假设我们发现黑盒模型明显更准确。这告诉我们线性模型缺少我们数据中的重要关系。此时，我们可以使用 IML 方法来了解这些关系是什么。\n重要关系 首先，我们可以了解数据中的哪些特征是重要的。这些特征可用于预测目标变量。大多数非线性模型能够处理大量特征（超过 50 个）。这些特征是否高度相关也没关系。这意味着，通过使用非线性模型，我们可以了解大量特征的重要性。相比之下，线性回归等模型需要一组较小的（8-10 个）不相关的特征。\n我们可以使用的一个度量是排列特征重要性。在图 3 中，您可以看到用于预测鲍鱼年龄的 xgboost 模型的得分。我们可以看到，鲍鱼壳的长度在预测年龄时很重要。接下来的三个特征基于鲍鱼的重量。虽然这些特征高度相关，但它们对模型没有负面影响。我们可以理解，所有这些特征都可以用来预测年龄。\n如果我们使用相关性来识别重要关系，我们可能会错过其中一些特征。这是因为相关性仅提供线性关联的度量。换句话说，该度量只能突出显示具有显着线性关系的特征。相比之下，特征重要性告诉我们特征对模型预测的重要性。模型的预测能力可能来自相互作用和非线性关系。这些将反映在特征重要性分数中。\n[SHAP 值](Python 中的 SHAP 简介)可以提供另一种衡量标准。我们可以使用这些值来找到每个特征的绝对平均 [[SHAP 值]]。与特征重要性类似，这可以说明哪些特征对预测做出了巨大贡献。这两个衡量标准的问题是，它们没有告诉我们模型捕获的关系的性质。\n非线性关系 如果特征与目标变量有任何关系，则可以使用该特征进行预测。它可以是线性的，也可以是图 4 中所示的非线性关系之一。为了找到并可视化黑盒模型捕获的非线性关系，我们可以使用其他 IML 方法。\n可视化的示例包括[部分依赖图(PDP)](PDP 和 ICE 图的终极指南)和累积局部效应(ALE) 图。这两种方法都显示了特征与模型预测之间的一般关系。PDP 易于理解，但可能会受到特征依赖性的负面影响。ALE 图没有这个问题，但它们更复杂，更难解释。\n另一种方法是使用 [SHAP 值](Python 中的 SHAP 简介)。我们上面提到过这些。对于给定的预测，它们给出了每个特征对该预测的贡献。我们可以绘制 SHAP 值与特征值的关系图。这被称为依赖关系图。它可以向我们展示特征值与其对预测的贡献之间的关系。\n我们一些文章中讨论这些方法和更多方法。在《[[查找并可视化非线性关系]]》中深入探讨了如何查找和可视化非线性关系。在《[[Python 中的 SHAP 简介]]》重点介绍SHAP 值和用于应用它们的 Python 代码。我们还讨论了这些值的不同聚合。其中包括上面提到的平均 SHAP 和依赖关系图。\n互动 交互实际上被认为是一种非线性关系。它们相对于一个特征是非线性的。该特征的关系将取决于第三个特征的值。事实上，交互可能更加复杂。一个特征的关系可能取决于两个或多个特征的值。对于大多数应用，我们只关心两个特征之间的关系。\n一种可视化交互的方法是[ICE 图](PDP 和 ICE 图的终极指南)。您可以在图 5 中看到一个示例。这里，我们数据集中的每个观察值都用一条线表示。此数据集包含二手车销售的详细信息（例如价格、车龄和车型）。对于每个观察值，该线显示预测价格（部分 yhat）如何随车龄变化。粗体黄色是我们上面讨论的 PDP 的一个示例。它给出了所有单个预测线的平均值。\n我们根据汽车类型对上面的线条进行了着色。蓝色代表经典汽车，红色代表普通汽车。您可以看到经典汽车的关系偏离了平均趋势。直观地说，经典汽车的价格会随着年龄的增长而上涨，这是有道理的。ICE 图向我们展示了汽车年龄和价格之间的关系取决于汽车的类型。换句话说，汽车年龄和汽车类型之间存在相互作用。\n另一种方法是使用[SHAP 交互值](分析与 SHAP 的相互作用)。这些是 SHAP 值的扩展，其中考虑了主效应和交互效应。我们可以使用这些来可视化交互。我们还可以计算平均绝对交互效应以突出显示重要的交互。这类似于特征重要性得分，但针对的是交互。用于突出显示交互的另一个指标是 Friedman 的 h 统计量。\n同样，我们也在一些文章中更深入地讨论过所有这些方法。在《寻找并可视化交互》中更深入地介绍了如何查找和可视化交互。还有《[[分析与 SHAP 的相互作用]]》中重点介绍如何使用SHAP 分析交互。我们还讨论了用于应用此方法的 Python 代码。\n我们可以用这些知识做什么？ 所有这些 IML 方法都可以帮助我们了解模型如何进行预测。一个直接的好处是它们可以帮助我们感知检查我们的模型。我们可以将模型捕获的关系与我们使用领域知识所预期的关系进行比较。这些方法还使我们能够向客户或同事解释我们的模型。\n在本节中，我们将集中讨论间接好处。通过理解模型，我们可以了解数据中的潜在关系。我们可以利用这些知识来改进线性模型或实现超越模型的决策。\n改进线性模型 我们提到，黑盒模型可以提供基准。它们可能优于线性模型，但这并不意味着这些模型的终结。这是因为我们可以利用所获得的知识来指导我们的特征工程。我们可以创建新的特征来提高线性模型的性能。\n例如，假设我们想使用线性回归对二手车的价格进行建模。使用 PDP，我们发现价格与汽车年龄呈二次关系。同样，使用 ICE 图，我们发现汽车年龄和汽车类型之间存在相互作用。我们可以通过向数据集添加新特征来捕获这些非线性关系。\n具体来说，在下面的等式中，我们添加了 $age^2$ 和交互项 $ageclassic$​。其中 classic = 1 表示经典汽车，否则为 0。 $$ \\begin{align} \u0026amp; \\gamma = \\beta_0 + \\beta_1(age) + \\beta_2(age^2) + \\beta_3(ageclassic) + \\beta_4(classic) \\ \u0026amp; \\gamma = \\beta_0 + \\beta_1(age) + \\beta_2(age_sqrd) + \\beta_3(age_classic_int) + \\beta_4(classic) \\end{align} $$ 在第一个方程中，Y 由两个特征（age 和 classic）的非线性方程建模。现在，它由四个特征的线性方程建模。特征工程甚至可以帮助决策树等简单的非线性模型。最终，它使我们能够使用简单的模型捕获复杂的关系。我们曾在文章《特征工程的力量》中更实际地演示此过程。\n问题是我们为什么要这样做？非线性模型同样准确。那么为什么不使用它们并避免额外特征工程的麻烦呢？这是因为拥有一个简单的模型有很多好处。首先，在许多行业中，对可以使用的模型类型有规定。也就是说，在银行和保险等行业中，您可能需要使用线性模型。\n即使没有规定，您可能仍然更喜欢线性模型。黑盒模型很复杂。即使使用 IML 方法，我们也可能不能 100% 确定它们的工作原理。这可能会在投入生产时带来意外。相比之下，回归和决策树本质上是可解释的。我们可以通过直接查看它们的参数来了解它们的工作原理。这增加了它们对它们在野外行为的确定性。\n实现决策 模型用于自动化决策。有了 IML，它们的影响不再仅限于这些决策。我们获得的知识可以为更广泛的组织提供决策信息。以前未知的关系可以提供巨大的价值。例如，我们探索的互动告诉我们，经典汽车的价格会随着年龄的增长而上涨。这可能为投资经典汽车的策略提供参考。\n话虽如此，我们还是应该避免过于复杂的解决方案。使用数据来指导决策对数据科学家来说并不是什么新鲜事。我们有许多技术和工具可以用来帮助探索数据。它们大多数都比使用黑盒模型简单得多。缺点是它们确实需要一些指导。我们需要知道我们想在数据中找到什么。使用 IML 方法，黑盒模型可以为我们找到新的重要模式。\n参考 C. Molnar, Interpretable Machine Learning (2021) https://christophm.github.io/interpretable-ml-book/shap.html\nS. Masís, Interpretable Machine Learning with Python (2021)\n","permalink":"https://hivan.me/posts/%E6%88%91%E4%BB%AC%E5%8F%AF%E4%BB%A5%E4%BB%8E%E9%BB%91%E7%9B%92%E6%A8%A1%E5%9E%8B%E4%B8%AD%E5%AD%A6%E5%88%B0%E4%BB%80%E4%B9%88/","summary":"\u003cblockquote\u003e\n\u003cp\u003e使用非线性模型进行数据探索和知识生成\u003c/p\u003e\u003c/blockquote\u003e","title":"我们可以从黑盒模型中学到什么"},{"content":"\n[[KernelSHAP]] 和 [[TreeSHAP]] 都用于近似 [[Shapley]] 值。TreeSHAP速度更快。缺点是它只能与基于树的算法（如[[随机森林]]和 [[xgboost]]）一起使用。另一方面，KernelSHAP 与模型无关。这意味着它可以与任何机器学习算法一起使用。我们将比较这两种近似方法。\n为此，我们将进行一项实验。这将向我们展示 TreeSHAP 实际上有多快。我们还将探讨树算法的参数如何影响时间复杂度。这些包括树的数量、 深度和特征的数量。在使用 TreeSHAP 进行数据探索时，这些知识非常有用。最后，我们将讨论其他考虑因素（如特征依赖性）如何影响方法。\n每次观察的时间 对于第一个实验，我们想看看这些方法计算 [[SHAP]] 值需要多少时间。我们不会介绍用于获取结果的代码，但你可以在 GitHub1 上找到它。总而言之，我们首先模拟回归数据。它有 10000 个样本、 10 个特征和 1 个连续目标变量。使用这些数据，我们训练一个随机森林。具体来说，该模型有 100 棵树，最大深度为 4 。\n现在，我们可以使用该模型来计算 SHAP 值。我们使用 KernelSHAP 和 TreeSHAP 方法进行此操作。对于每种方法，我们计算 10、100、1000、2000、5000 和 10000 个 SHAP值。对于 每个金额， 我们记录计算所花费的时间。我们确保对每个金额重复此过程 3 次。然后，我们将平均值作为最终时间。\nresults = [] for n in [10,100,1000,2000,5000,10000]*3: # Calculate SHAP Values kernel_time = runSHAP(n=n) tree_time = runSHAP(n=n,kernel=False) result = [n,kernel_time,tree_time] results.append(result) results_1 = pd.DataFrame(results,columns = [\u0026#39;n\u0026#39;,\u0026#39;kernelSHAP\u0026#39;,\u0026#39;treeSHAP\u0026#39;]) --- Kernel 10000: 0:07:48.051865 Tree 10000: 0:00:00.747681 你可以在图 1 中看到此过程的结果。你可以看到 TreeSHAP 的速度明显更快。对于 10,000 个 SHAP 值，该方法耗时 0.74 秒 。相比之下，KernelSHAP 耗时 7 分 48.05 秒。这是 KernelSHAP 的 630 倍 。这些计算的速度取决于你的设备，但你应该会遇到类似的差异。\n上面的 TreeSHAP 线看起来很平坦。这是因为它与 KernelSHAP 线相比显得矮小。在 图 2 中，我们只有 TreeSHAP 的线。你可以看到它也随着观察次数的增加而线性增加。这告诉我们每个 SHAP 值的计算时间都差不多。我们将在下一节探讨原因。\n-\n时间复杂度 两种方法的时间复杂度如下所示。这是在树算法中计算特征的 SHAP 值时的复杂度。T 是 单个树的数量。L 是 每棵树中的最大叶子数。D 是 每棵树的最大深度。最后， M 是每棵树中的最大特征数。对于这些方法，这些参数将以不同的方式影响近似时间。\n$$ \\begin{align*} \u0026amp; KernelSHAP: O(TL2^M) \\ \u0026amp; TreeSHAP: O(TLD^2) \\end{align*} $$\n只有 TreeSHAP 的复杂度受深度 (D) 的影响。另一方面，只有 KernelSHAP 受特征数量 (M) 的影响。不同之处在于 KernelSHAP 复杂度相对于 M 呈指数增长 ，而 TreeSHAP 相对于 D 呈二次增长。考虑到我们的特征 (M = 10) 也多于树深度 (D = 4)，我们可以看出为什么 KernelSHAP 更慢。\n需要明确的是，这是每个 SHAP 值的时间复杂度。我们应该期望每个值都需要相似的时间来计算。这就是为什么我们看到时间与观察次数之间存在线性关系。我们现在将探讨时间与其他参数 T 、 L 、 D 和 M 之间的关系。然后我们将讨论结果对模型验证和数据探索的意义。\n树的数量（T） 对于这两种方法，复杂度与树的数量 (T) 呈线性关系。我们预计此参数会以类似的方式影响近似时间。为了看到这一点，我们进行了与之前类似的实验。这一次，我们通过增加树的数量来训练不同的模型。我们使用每个模型来计算 100 个 SHAP 值。\n你可以在图 3 中看到结果。对于这两种方法，时间都随着树的数量线性增加。这是我们在查看时间复杂度时所预期的。这告诉我们，通过限制树的数量，我们可以减少计算 SHAP 值所需的时间。\n特征数量 (M) 只有 KernelSHAP 受到特征数量（ M ）的影响。这次我们在不同数量的特征上训练模型。同时，我们保持其他参数（ T、L 和 D ）不变。在 图 4 中，我们可以看到 KernelSHAP 的时间随着 M 的 增加而呈指数增加。相比之下，TreeSHAP 的时间受到的影响并不大。\n你可能已经注意到 TreeSHAP 的时间逐渐增加。这可能会令人困惑，因为我们看到复杂性并不依赖于 M。要清楚的是，这是计算单个特征的 SHAP 值时的复杂性。随着 M 的增加，我们需要为每个观察计算更多的 SHAP 值。\n树的深度（D） 最后，我们改变树的深度。我们确保森林中每棵树的深度始终是最大深度。在图 5 中，你可以看到当我们增加深度时会发生什么。TreeSHAP 的时间增加得更为剧烈。甚至有一点 TreeSHAP 的计算成本快要比 KernelSHAP 还高了。我们可能已经预料到了这一点，因为我们看到只有 TreeSHAP 复杂性是 D 的函数。\n你可能会问为什么 KernelSHAP 时间也会增加。这是因为特征 (M) 和叶子 (L) 的数量会根据树的深度而变化。随着深度的增加，会有更多的分裂，因此我们会有更多的叶子。更多的分裂也意味着树可以使用更多的特征。你可以在 图 6 中看到这一点。这里我们计算了森林中所有树的平均特征和叶子数量。\n模型验证和数据探索的要点 通过改变深度，我们发现在某些情况下 TreeSHAP 的计算成本更高。但是，这些情况不太可能发生。我们发现，只有当树的深度为 20 时才会发生这种情况。使用这么深的树并不常见。在实践中，我们通常会拥有比树深度 (D) 更多的特征 (M)。 这意味着，在使用 SHAP 验证树模型时，TreeSHAP 通常是更好的选择。我们能够更快地计算 SHAP 值。这可以同时进行，尤其是当你需要比较多个模型时。对于模型验证，我们对参数 T 、 L 、 D 和 M 没有太多选择。这是因为我们只想验证表现最佳的模型。 对于数据探索，我们拥有更多的灵活性。树算法可用于查找重要的非线性关系和相互作用。为此，我们的模型只需足够好以捕捉数据中的潜在趋势即可。我们可以通过减少树的数量 (T) 和树的深度 (D) 来使用 TreeSHAP 加速此过程。同时，我们能够探索许多模型特征 (M)，而无需大幅提高速度。\n其他考虑因素 在选择方法时，时间复杂度是一个重要因素。在做出选择之前，你可能还需要考虑其他一些差异。其中包括 KernelSHAP 与模型无关、方法受特征依赖性的影响，并且只有 TreeSHAP 可用于计算交互效应。\n与模型无关 一开始，我们提到 TreeSHAP 最大的限制是它不是模型无关的。如果你正在使用非基于树的算法，你将无法使用它。神经网络也有自己的近似方法。你可以将 DeepSHAP 用于这些算法。然而，KernelSHAP 是唯一可以与所有算法一起使用的方法。\n功能依赖项 特征依赖性可能会扭曲 KernelSHAP 的近似值。该算法通过随机采样特征值来估计 SHAP 值。问题是，当特征相关时，采样值可能不太可能。这意味着在使用 SHAP 值时，我们可能会过分重视不太可能的观察结果。 TreeSHAP 没有这个问题。但是，由于特征依赖性，该算法存在不同的问题。即对预测没有影响的特征可以获得非零的 SHAP 值。当该特征与另一个确实影响预测的特征相关时，就会发生这种情况。在这种情况下，我们可以错误地得出某个特征对预测有贡献的结论。\n分析互动 SHAP 交互值是 SHAP 值的扩展。它们的工作原理是将特征的贡献分解为其主要影响和交互影响。对于给定的特征，交互影响是它与其他特征的所有联合贡献。在突出显示和可视化数据中的交互时，这些非常有用。我们将在文章《[[分析与 SHAP 的相互作用](../分析与 SHAP 的相互作用)]》中深入探讨这一点。\n如果你想使用 SHAP 交互值，则必须使用 TreeSHAP。这是因为它是唯一已实现交互值的近似方法。这与 SHAP 交互值的复杂性有关。KernelSHAP 需要更长的时间来估计这些值。\n最后，你应该尽可能使用 TreeSHAP。它速度更快，并能让你分析交互。对于数据探索，你可能希望坚持使用树算法来获得这些好处。如果你正在使用其他类型的算法，那么你必须坚持使用 KernelSHAP。它仍然是一种比蒙特卡罗采样等其他方法更快的近似方法。\n参考 S. Lundberg, SHAP Python package (2021), https://github.com/slundberg/shap\nS. Lundberg \u0026amp; S. Lee, A Unified Approach to Interpreting Model Predictions (2017), https://arxiv.org/pdf/1705.07874.pdf\nS. Lundberg, S.M., G. Erion, G.G. and Lee, S.I., (2018). Consistent individualized feature attribution for tree ensembles. arXiv preprint arXiv:1802.03888.\nC. Molnar, Interpretable Machine Learning (2021) https://christophm.github.io/interpretable-ml-book/shap.html\nS. Masís, Interpretable Machine Learning with Python (2021)\n希望这篇文章对你有所帮助！你还可以阅读我的其他文章，或者查看有关企业 AI 实战项目的教程，相信会让你拥有更多收获。\n「AI秘籍」系列课程：\n人工智能应用数学基础 人工智能Python基础 人工智能基础核心知识 人工智能BI核心知识 人工智能CV核心知识 Github, https://github.com/hivandu/public_articles/blob/main/src/interpretable_ml/SHAP/kernelSHAP_vs_treeSHAP.ipynb\u0026#160;\u0026#x21a9;\u0026#xfe0e;\n","permalink":"https://hivan.me/posts/kernelshap-vs-treeshap/","summary":"\u003cblockquote\u003e\n\u003cp\u003e根据速度、复杂性和其他考虑因素比较 SHAP 近似方法\u003c/p\u003e\u003c/blockquote\u003e","title":"KernelSHAP vs TreeSHAP"},{"content":"可直接在橱窗里购买，或者到文末领取优惠后购买：\n分类特征需要先进行转换，然后才能用于模型。One-Hot 编码是一种常见的方法：我们最终会得到每个类别的二进制变量。这很好，直到理解使用 SHAP 的模型为止。每个二进制变量都有自己的 SHAP 值。这使得很难理解原始分类特征的整体贡献。\n一种简单的方法是将每个二进制变量的 SHAP 值加在一起。这可以解释为原始分类特征的 SHAP 值。我们将向你介绍执行此操作的 Python 代码。我们将看到我们能够使用 SHAP 聚合图。但是，在理解分类特征关系的性质时，这些是有限的。所以，最后我们向你展示如何使用箱线图来可视化 SHAP 值。\n如果你不熟悉 SHAP 或 Python 包，我建议你阅读一下之前的这篇文章《[[Python 中的 SHAP 简介]]》。我们将深入探讨如何解释 SHAP 值。我们还探讨了本文中使用的一些聚合。\n处理分类变量时还有另一种解决方案。那就是使用 CatBoost 进行建模。关于 CatBoost 如何进行建模，我以后会写一篇相关文章《[使用 CatBoost 实现分类特征的 SHAP]》，你可以在其中找到此解决方案。\n数据集 为了演示分类特征的问题，我们将使用蘑菇分类数据集。你可以在图 1 中看到此数据集的快照。目标变量是蘑菇的类别。也就是说，蘑菇是有毒 (p) 还是可食用 (e)。你可以在UCI 的 MLR1中找到此数据集。\n对于模型特征，我们有 22 个分类特征。对于每个特征，类别都用一个字母表示。例如，气味有 9 个独特的类别 - almond (a)、anise (l)、creosote (c), fishy (y), foul (f), musty (m), none (n), pungent (p), spicy (s)。这就是蘑菇的气味。\n建模 我们将向你介绍用于分析此数据集的代码，你可以在GitHub2上找到完整的脚本。首先，我们将使用下面的 Python 包。我们有一些用于处理和可视化数据的常用包（第 1-5 行）。我们使用 OneHotEncoder 来转换分类特征（第 7 行）。我们使用 xgboost 进行建模（第 10 行）。最后，我们使用 shap 来了解我们的模型是如何工作的（第 12 行）。\nimport pandas as pd import numpy as np import matplotlib.pyplot as plt %matplotlib inline plt.style.use(\u0026#39;default\u0026#39;) from sklearn.preprocessing import OneHotEncoder from sklearn.metrics import accuracy_score,confusion_matrix import xgboost as xgb import shap shap.initjs() 我们导入数据集（第 2 行）。我们需要一个数值目标变量，因此我们通过设置 toxicous = 1 和 edible = 0 来转换它（第 6 行）。我们还获得了分类特征（第 7 行）。我们不使用 X_cat 进行建模，但它以后会派上用场。\n# load data data = pd.read_csv(dp + \u0026#34;mushrooms.csv\u0026#34;) # get features y = data[\u0026#39;class\u0026#39;] y = y.astype(\u0026#39;category\u0026#39;).cat.codes X_cat = data.drop(\u0026#39;class\u0026#39;, axis=1) 要使用分类特征，我们还需要对其进行转换。我们首先安装一个编码器（第 2-3 行）。然后我们使用它来转换分类特征（第 6 行）。对于每个分类特征，每个类别都有一个二进制特征。我们为每个二进制特征创建特征名称（第 9 至 10 行）。最后，我们将它们放在一起以创建特征矩阵（第 12 行）。\n# fit encoder enc = OneHotEncoder() enc.fit(X_cat) # transform categorical features X_encoded = enc.transform(X_cat).toarray() # create feature matrix feature_names = X_cat.columns new_feature_names = enc.get_feature_names_out(feature_names) X = pd.DataFrame(X_encoded, columns= new_feature_names) 最终，我们得到了 117 个特征。你可以在图 2 中看到特征矩阵的快照。例如，你可以看到 cap-shape 现已转换为 6 个二进制变量。特征名称末尾的字母来自原始特征的类别。\n我们使用此特征矩阵训练模型（第 2-5 行）。我们使用 XGBClassifier。XGBoost 模型由 10 棵树组成，每棵树的最大深度为 2。该模型在训练集上的准确率为 97.7%。\n# Train model model = xgb.XGBClassifier(objective=\u0026#34;binary:logistic\u0026#34;, max_depth=2, n_estimators=10) model.fit(X, y) 标准 SHAP 值 此时，我们想了解模型是如何做出这些预测的。我们首先计算 SHAP 值（第 2-3 行）。然后，我们使用瀑布图（第 6 行）可视化第一个预测的 SHAP 值。你可以在图 3 中看到此图。\n# get shap values explainer = shap.Explainer(model) shap_values = explainer(X) # waterfall plot shap.plots.waterfall(shap_values[1], show=False) 你可以看到每个二元特征都有自己的 SHAP 值。以气味为例。它在瀑布图中出现了 4 次。odor_n = 0 增加了蘑菇有毒的概率。同时，odor_a = 1、odor_f = 0 和 odor_I = 0 都降低了概率。蘑菇气味的总体贡献是什么尚不清楚。在下一节中，我们将看到，当我们将所有单独的贡献加在一起时，它确实变得清晰起来。\n分类特征的 SHAP 让我们从探索shap_values对象开始。我们在下面的代码中打印该对象。你可以在下面的输出中看到它由 3 个组件组成。我们有每个预测的SHAP 值 ( values )。数据给出了二进制特征的值。每个预测也将具有相同的基值 ( base_values )。这是平均预测对数概率。\nprint(shap_values) 我们可以通过在下面打印它们来仔细查看第一个预测的 SHAP 值。一共有 117 个值。每个二进制变量一个。SHAP 值的顺序与 X 特征矩阵相同。请记住，第一个分类特征cap-shape有 6 个类别。这意味着前 6 个 SHAP 值对应于此特征的二进制特征。接下来的 4 个对应于cap-surface特征，依此类推。\nprint(shap_values.values[1]) 我们希望将每个分类特征的 SHAP 值加在一起。为此，我们首先创建n_categories数组。它包含每个分类变量的唯一类别数。数组中的第一个数字将是 6（表示帽形），然后是 4（表示帽表面），依此类推……\n# get number of unique categories for each feature n_categories = [] for feat in feature_names[:-1]: n = X_cat[feat].nunique() n_categories.append(n) 我们使用n_categories来拆分 SHAP 值数组（第 5 行）。我们最终得到一个子列表。然后我们对每个子列表中的值求和（第 8 行）。通过这样做，我们将 SHAP 值从 117 个减少到 22 个。我们对shap_values对象中的每个观察值都执行此操作（第 2 行）。对于每次迭代，我们将求和的 shap 值添加到new_shap_values数组（第 10 行）。\nnew_shap_values = [] for values in shap_values.values: # split shap values into a list for each feature values_split = np.split(values , np.cumsum(n_categories)) # sum values within each list values_sum = [sum(l) for l in values_split] new_shap_values.append(values_sum) 现在，我们需要做的就是用新值替换原始 SHAP 值（第 2 行）。我们还用原始分类特征中的类别字母替换二进制特征数据（第 5-6 行）。最后，我们用原始特征名称替换二进制特征名称（第 9 行）。重要的是将这些新值分别作为数组和列表传递。这些是 shap_values 对象使用的数据类型。\n# replace shap values shap_values.values = np.array(new_shap_values) # replace data with categorical feature values new_data = np.array(X_cat) shap_values.data = np.array(new_data) # update feature names shap_values.feature_names = list(X_cat.columns) 更新后的 shap_values 对象可以像原始对象一样使用。在下面的代码中，我们绘制了第一次观察的瀑布图。你会注意到此代码与之前完全相同。\n# waterfall plot shap.plots.waterfall(shap_values[0]) 你可以在图 4 中看到输出。我们现在有 22 个 SHAP 值。你还可以看到左侧的特征值已被类别标签替换。我们之前讨论过气味特征。现在你可以清楚地看到此功能的整体贡献。它使对数概率降低了 0.29。\n在上图中，我们得到气味 = a。这告诉我们蘑菇有“杏仁”气味。我们应避免将该图解释为“杏仁气味降低了对数几率”。我们将多个 SHAP 值相加。因此，我们应该将其解释为“杏仁气味和缺乏其他气味降低了对数几率”。例如，查看第一个瀑布图，缺乏“恶臭”气味（odor_f = 0）也降低了对数几率。\n在我们继续聚合这些新的 SHAP 值之前，值得讨论一些理论。我们能够使用 SHAP 值做到这一点的原因是由于它们的可加性。也就是说，平均预测（E[f(x)]）加上所有 SHAP 值等于实际预测（f(x)）。通过将一些 SHAP 值加在一起，我们不会干扰此属性。这就是为什么 f(x) = -2.444 在图 3 和图 4 中相同的原因。\n平均 SHAP 与瀑布图一样，我们可以像使用原始 SHAP 值一样使用 SHAP 聚合。例如，我们在下面的代码中使用平均 SHAP 图。查看图 5，我们可以使用此图突出显示重要的分类特征。例如，我们可以看到气味往往具有较大的正/负 SHAP 值。\n# Mean SHAP shap.plots.bar(shap_values) 蜂群 另一种常见的聚合是蜂群图。对于连续变量，此图很有用，因为它可以帮助解释关系的性质。我们可以看到 SHAP 值如何与特征值相关联。但是，对于分类特征，我们用标签替换了特征值。因此，在图 6 中，你可以看到 SHAP 值都被赋予了相同的颜色。我们需要创建自己的图来了解这些关系的性质。\nshap.plots.beeswarm(shap_values) SHAP 箱线图 我们可以采用一种方式来做到这一点，即使用 SHAP 值的箱线图。在图 7 中，你可以看到一个气味特征的箱线图。在这里，我们根据气味类别对气味特征的 SHAP 值进行了分组。你可以看到，难闻的气味会导致更高的 SHAP 值。这些蘑菇更有可能有毒。请不要吃任何有异味的蘑菇！同样，没有气味的蘑菇更有可能可以食用。一条橙色线表示这些蘑菇的所有 SHAP 值都相同。\n我们使用以下代码创建此箱线图。我们首先获取气味 SHAP 值（第 2 行）。请记住，这些是更新值。对于每个预测，气味特征只有一个 SHAP 值。我们还获取气味类别标签（第 3 行）。我们根据这些标签分割 SHAP 值（第 6-12 行）。我们使用这些值为每个气味类别绘制一个箱线图（第 28-33 行）。为了使图表更易于解释，我们还用完整的类别名称替换了字母（第 15-25 行）。\n# get shaply values and data odor_values = shap_values[:,\u0026#34;odor\u0026#34;].values odor_data = shap_values[:,\u0026#34;odor\u0026#34;].data # split odor shap values based on odor category unique_odor = [\u0026#39;a\u0026#39;,\u0026#39;l\u0026#39;,\u0026#39;c\u0026#39;,\u0026#39;y\u0026#39;,\u0026#39;f\u0026#39;,\u0026#39;m\u0026#39;,\u0026#39;n\u0026#39;,\u0026#39;p\u0026#39;,\u0026#39;s\u0026#39;] odor_categories = list(set(odor_data)) odor_groups = [] for o in odor_categories: relevant_values = odor_values[odor_data == o] odor_groups.append(relevant_values) # replace categories with labels odor_labels = {\u0026#39;a\u0026#39;:\u0026#39;almond\u0026#39;, \u0026#39;c\u0026#39;:\u0026#39;creosote\u0026#39;, \u0026#39;f\u0026#39;:\u0026#39;foul\u0026#39;, \u0026#39;l\u0026#39;:\u0026#39;anise\u0026#39;, \u0026#39;m\u0026#39;:\u0026#39;musty\u0026#39;, \u0026#39;n\u0026#39;:\u0026#39;none\u0026#39;, \u0026#39;p\u0026#39;:\u0026#39;pungent\u0026#39;, \u0026#39;s\u0026#39;:\u0026#39;spicy\u0026#39;, \u0026#39;y\u0026#39;:\u0026#39;fishy\u0026#39;,} labels = [odor_labels[u] for u in unique_odor] # plot boxplot plt.figure(figsize=(8, 5)) plt.boxplot(odor_groups,labels=labels) plt.ylabel(\u0026#39;Shap values\u0026#39;,size=15) plt.xlabel(\u0026#39;Odor\u0026#39;,size=15) 实际上，你的特征中可能只有少数是分类的。你需要更新上述过程以仅对分类特征求和。你还可以想出自己的方法来可视化这些特征之间的关系。如果你想出了另一种方法，我很乐意在评论中听到它。\n我还想了解特征依赖关系将如何影响此分析。根据定义，转换后的二进制特征将是相关的。这会影响 SHAP 值的计算。我们正在使用 TreeSHAP 来估计 SHAP 值。我的理解是，这些值不像 KernelSHAP 那样受依赖关系的影响。我很想在评论中听听你的想法。\n参考 S. Lundberg，SHAP Python 包(2021) ， https://github.com/slundberg/shap\nS. Lundberg 和 S. Lee，《解释模型预测的统一方法》 (2017)，https://arxiv.org/pdf/1705.07874.pdf\n希望这篇文章对你有所帮助！你还可以阅读我的其他文章，或者查看有关企业 AI 实战项目的教程，相信会让你拥有更多收获。\n「AI秘籍」系列课程：\n人工智能应用数学基础 人工智能Python基础 人工智能基础核心知识 人工智能BI核心知识 人工智能CV核心知识 Dataset, https://archive.ics.uci.edu/ml/datasets/Mushroom\u0026#160;\u0026#x21a9;\u0026#xfe0e;\nGithub, https://github.com/hivandu/medium-articles/blob/master/src/interpretable_ml/SHAP/SHAP_categorical.ipynb\u0026#160;\u0026#x21a9;\u0026#xfe0e;\n","permalink":"https://hivan.me/posts/%E5%88%86%E7%B1%BB%E7%89%B9%E5%BE%81%E7%9A%84-shap/","summary":"\u003cblockquote\u003e\n\u003cp\u003e将经过One-Hot 编码转换的分类特征的 SHAP 值相加\u003c/p\u003e\u003c/blockquote\u003e","title":"分类特征的 SHAP"},{"content":"``\n可解释性和公平性似乎是相辅相成的。可解释性涉及理解模型如何进行预测。公平性涉及理解预测是否偏向某些群体。负责任的人工智能框架和机器学习会议始终将这两个特征一起提及。然而，可解释性并不一定意味着公平。\n话虽如此，可解释的模型仍然更有可能是公平的。这种关联有 3 个主要原因。对于可解释的模型，更容易识别不公平的原因。在可解释的模型中纠正不公平也更容易。一些方法，例如证明解释，甚至依赖于可解释性。最后，这两个特征都与建立对 AI 系统的信任的目标相关。我们将深入讨论这些原因。\n使用不可知论方法测量偏见 在深入探讨这些问题之前，让我们先讨论一下如何分析公平性。这也有助于我们理解为什么这不是可解释性和公平性相关的原因之一。通常，公平性的衡量标准与模型无关。这意味着它们可以用于任何模型。这包括线性回归和决策树等固有可解释的模型。它还包括随机森林甚至神经网络等不太可解释的模型。\n准确度是衡量公平性的一个简单例子。具体来说，我们将比较模型对不同人群的准确度。这里的人群由种族或性别等敏感特征定义。如果某个人群的准确度明显较低，则表明该模型对该人群不公平。\n要计算公平性指标，我们只需要模型的预测和实际目标变量。我们不需要研究模型本身的内部工作原理。这意味着，对于可解释的模型和不太可解释的模型，计算指标同样容易。换句话说，可解释性并不能让我们更容易理解一个模型是否不公平。问题是，这样的指标并不能告诉我们为什么一个模型不公平。\n原因一：更容易识别不公平的原因 [造成不公平的原因](不公平的预测：机器学习中的 5 种常见偏见来源)各有不同其中包括用于训练模型的数据中的偏见。具体来说，历史偏见、代理变量和不平衡的数据集可能会导致不公平的预测。我们对算法的选择以及用户与模型的交互方式也会带来偏见。对于可解释的模型，识别这些来源更容易。\n这是因为，对于可解释的模型，更容易理解模型的工作原理。这意味着我们清楚地了解哪些特征是重要的。我们还了解这些特征与目标变量之间的关系。通常，与神经网络等模型相比，算法和成本函数也简单得多。所有这些都让我们更容易找出导致模型不公平的原因。\n与此原因相关的是模型的构建方式。通常，当您构建可解释的模型（例如线性回归）时，您最终会手工挑选 8 到 12 个模型特征的最终集合。您还将花时间使用该领域的领域知识将特征趋势与预期进行比较。实际上，在特征选择上投入了更多的思考。只要我们考虑到公平性，我们就可以在模型开发过程的早期提出问题。这意味着您甚至可以在将偏见引入模型之前识别出偏见来源。\n原因 2：更容易纠正偏见 对于可解释的模型，纠正不公平现象也更容易。这部分源于上面讨论的第一个原因。我们采取的纠正不公平的方法可能取决于不公平的原因。由于更容易确定原因，因此更容易选择最佳方法来纠正它。例如，我们可以纠正不公平模型的方法之一是删除代理变量。这些特征与种族或性别等敏感特征高度相关。\n随机森林等模型最多可以有 30-40 个特征。模型结构还允许特征之间的交互。这种复杂性使得很难理解任何代理变量与目标变量之间的关系。如果这种关系导致不公平的决策，就更难理解了。最终，决定删除哪些代理变量变得更加困难。\n算法本身还可以进行其他修正。例如，我们可以调整成本函数以考虑公平性。这可能涉及添加惩罚参数来惩罚不公平的决策。换句话说，模型的目标不仅是做出准确的预测，而且是公平的预测。可解释模型的算法和成本函数通常更简单。这使得调整它们更容易。\n确保公平的方法也可以超越上述定量方法。对抗不公平的一个重要方法是向用户提供预测的解释。然后让用户有权挑战这些预测。我们越了解模型的工作原理，就越容易以人性化的方式进行解释。换句话说，可解释性使得给出好的解释变得更容易。\n原因 3：它们都是为了建立信任 最重要的原因是可解释性和公平性的共同目标。它们都是为了建立对人工智能系统的信任。这主要通过减少这些系统可能造成的伤害来实现。也就是说，我们不会相信我们认为会对我们造成伤害的东西。\n我们更有可能相信我们理解的东西。此外，我们对某件事了解得越多，我们就越能确定它不会造成伤害。从这个意义上说，可解释性可以建立信任。公平的目标更直接。它旨在减少不公平决策带来的伤害。最终，一个关心人工智能系统信任的组织会同时关心可解释性和公平性。\n这就是为什么您经常会在负责任的 AI框架中看到公平性和可解释性一起提及。这些治理框架旨在定义开发 AI 系统的道德准则和最佳实践。除了公平性和可解释性之外，这些框架还涵盖了安全性、保障性和隐私等主题。Google1、微软2和 IBM3 等公司都有自己的框架。他们明白，客户不会使用他们不信任的系统。\n人工智能的发展为改善全球人民的生活创造了新的机会，从商业到医疗保健再到教育。它也提出了新的问题，即如何在这些系统中实现公平性、可解释性、隐私性和安全性。\n— Google AI 谈 responsible AI practices4\n出于类似的原因，公平性和可解释性是相关的研究主题。例如，下面您可以看到FAccT 2022会议论文的一些感兴趣领域。这是关于公平、问责和透明度的会议。公平性和可解释性是算法开发中提到的第一个主题。有兴趣减少人工智能系统危害的研究人员会对这两个主题感兴趣。\n","permalink":"https://hivan.me/posts/%E5%8F%AF%E8%A7%A3%E9%87%8A%E6%80%A7%E4%B8%8E%E5%85%AC%E5%B9%B3%E6%80%A7%E7%9A%84%E5%85%B3%E7%B3%BB/","summary":"\u003cblockquote\u003e\n\u003cp\u003e可解释模型更有可能公平的三个原因\u003c/p\u003e\u003c/blockquote\u003e","title":"可解释性与公平性的关系"},{"content":"\n处理大型数据集很有挑战性。如果你没有必要的资源，处理起来就更具挑战性。我们大多数人无法使用分布式集群、GPU 机架或超过 8GB 的 RAM。这并不意味着我们不能处理一些大数据。我们只需要一次解决一个问题。也就是说，一次迭代整个数据集并处理子集。\n数据集 我们将向你展示如何使用大型（随机生成的）交易数据集来实现这一点。该数据集包含 75,000 名客户的超过 2.6 亿笔交易。交易时间从 2010 年到 2020 年。每笔交易都分为 12 种支出类型之一（例如杂货）。你可以在图 1 中看到更多详细信息，还可以在GitHub1上找到本教程的代码。\n我们将了解如何使用批处理来创建此数据的不同聚合。具体来说，我们将计算：\n交易总数 每年总支出 2020 年平均每月娱乐支出 最后，我们将讨论加快创建这些聚合过程的方法。\n批量处理 我们将使用一些标准的 Python 包。我们有 NumPy 和 Pandas 用于数据处理，还有 matplotlib 用于一些简单的可视化。请确保你已安装这些包。\nimport numpy as np import pandas as pd import matplotlib.pyplot as plt 首先，让我们尝试使用 Pandas 加载整个数据集。过了一会儿，我们得到了图 2 中的错误消息。由于只有 8GB 的 RAM，因此无法将整个数据集加载到内存中。\ndf = pd.read_csv(dp + \u0026#39;transactions.csv\u0026#39;) 为了解决这个问题，我们可以加载数据集的子集。为此，我们可以使用read_csv函数中的skiprows和 nrows 参数。下面我们分别将参数的值设置为 1000 和 2000。这意味着我们将跳过 CSV 的前 1000 行并加载接下来的 2000 行。我们还需要将列名作为参数传递（第 7 行）。这是因为我们跳过了 CSV 中的第一行名称。\nnames = [\u0026#39;CUST_ID\u0026#39;, \u0026#39;START_DATE\u0026#39;, \u0026#39;END_DATE\u0026#39;, \u0026#39;TRANS_ID\u0026#39;, \u0026#39;DATE\u0026#39;, \u0026#39;YEAR\u0026#39;, \u0026#39;MONTH\u0026#39;, \u0026#39;DAY\u0026#39;, \u0026#39;EXP_TYPE\u0026#39;, \u0026#39;AMOUNT\u0026#39;] # Load rows 1001 to 3000 df = pd.read_csv(dp + \u0026#39;transactions.csv\u0026#39;, skiprows=1000, nrows=2000, names=names) 我们可以使用这些参数来迭代我们的数据集。为此，我们创建了下面的get_rows函数。这可以使用它来返回我们数据集的子集。每个子集将包含由 step 参数确定的行数。count 参数会有所不同，以在每个步骤返回不同的子集。为了了解如何使用此功能，我们将计算数据集中的交易总数。\ndef get_rows(steps,count,names,path=dp+\u0026#39;transactions.csv\u0026#39;): \u0026#34;\u0026#34;\u0026#34; Returns a subset of rows from a CSV. The fist [steps]*[count] rows are skipped and the next [steps] rows are returned. params ------------ steps: number of rows returned count: count variable updated each iteration names: columns names of dataset path: location of csv \u0026#34;\u0026#34;\u0026#34; if count ==0: df = pd.read_csv(path, nrows=steps) else: df = pd.read_csv(path, skiprows=steps*count, nrows=steps, names=names) return df 计算交易次数 我们在 while 循环中使用get_rows函数。在循环的每次迭代结束时，我们将更新计数（第 19 行）。这意味着我们将加载数据集的新子集（第 12 行）。我们设置步数，以便每次返回 500 万行（第 1 行）。但是，数据集中的行数不是 500 万的倍数。这意味着最后一次迭代返回的行数少于 500 万行。我们可以使用它来结束 while 循环（第 21-22 行）。\nsteps = 5000000 names = [\u0026#39;CUST_ID\u0026#39;, \u0026#39;START_DATE\u0026#39;, \u0026#39;END_DATE\u0026#39;, \u0026#39;TRANS_ID\u0026#39;, \u0026#39;DATE\u0026#39;, \u0026#39;YEAR\u0026#39;, \u0026#39;MONTH\u0026#39;, \u0026#39;DAY\u0026#39;, \u0026#39;EXP_TYPE\u0026#39;, \u0026#39;AMOUNT\u0026#39;] # Initialise number of transactions n = 0 # Initialise count count = 0 while True: # Return subsection of dataset df = get_rows(steps,count,names) # Update number of transactions n+=len(df) # Update count count+=1 # Exit loop if len(df)!=steps: break # Output number of rows print(n) --- 261969720 对于每次迭代，我们计算子集的长度并将其添加到交易总数中（第 15 行）。最后，我们输出有 261,969,720 笔交易。在很好地理解了如何批量处理数据集后，我们可以继续进行更复杂的聚合。\n每年总支出 对于此聚合，我们希望将每年的所有交易金额相加。我们可以遵循与之前类似的过程。现在，对于每次迭代，我们都希望更新数据集中每年的总数。我们首先创建一个 pandas 系列，其中索引是年份，值是总支出金额（第 2 行）。我们从每年的总支出 0 开始。\n对于每次迭代，我们按年份对支出金额求和（第 10 行）。结果是另一个系列 exp ，其索引与total_exp 相同。这使我们能够循环遍历每一年并更新总数（第 13-15 行）。\n# Initialise yearly totals total_exp = pd.Series([0.0]*11, index=range(2010,2021)) count = 0 while True: df = get_rows(steps,count,names) # Get yearly totals for subsection exp = df.groupby([\u0026#39;YEAR\u0026#39;])[\u0026#39;AMOUNT\u0026#39;].sum() # Loop over years 2010 to 2020 for year in range(2010,2021): # Update yearly totals total_exp[year] += exp[year] count+=1 # Exit loop if len(df)!=steps: break --- 2010 2.041377e+08 2011 5.842620e+08 2012 9.596104e+08 2013 1.324468e+09 2014 1.699034e+09 2015 2.058469e+09 2016 2.399227e+09 2017 2.709008e+09 2018 2.997901e+09 2019 3.267153e+09 2020 3.163759e+09 dtype: float64 我们使用下面的代码可视化此聚合。你可以在图 3 中看到输出。除 2020 年外，总支出逐年稳步增长。对于此聚合，所有计算均在 while 循环中完成。正如我们将在下一个聚合中看到的那样，情况并非总是如此。\n# Plot aggregation plt.figure(figsize=(10, 5)) plt.plot(total_exp.index,total_exp/1000000000) plt.ylabel(\u0026#39;Total expenditure ($ billion)\u0026#39;,size=15) plt.xlabel(\u0026#39;Year\u0026#39;,size=15) plt.ylim(bottom=0) 2020 年平均每月娱乐支出 对于许多聚合，我们不会只是将总金额相加。对于此聚合，我们首先需要计算每个客户每月在娱乐上花费的总金额。然后，对于每个月，我们可以计算所有客户的平均值。首先，我们创建一个空的 pandas 数据框（第 2 行）。\n然后，对于每次迭代，我们都会过滤掉交易，以便只保留 2020 年的娱乐交易（第 10 行）。然后，我们按客户和月份对金额求和（第 11 行），并将此表附加到 total_exp（第 14 行）。完成此操作后，可以重复客户和月份。这是因为不一定会在一次迭代中捕获所有客户的交易。这就是我们再次聚合表格的原因（第 17 行）。\n# Create empty total expenditure dataframe total_exp = pd.DataFrame(columns=[\u0026#39;CUST_ID\u0026#39;,\u0026#39;MONTH\u0026#39;,\u0026#39;AMOUNT\u0026#39;]) count = 0 while True: df = get_rows(steps,count,names) # Calculate monthly totals for each customer df_2020 = df[(df.YEAR==2020) \u0026amp; (df.EXP_TYPE==\u0026#39;Entertainment\u0026#39;)] sum_exp = df_2020.groupby([\u0026#39;CUST_ID\u0026#39;,\u0026#39;MONTH\u0026#39;],as_index=False)[\u0026#39;AMOUNT\u0026#39;].sum() # Append monthly totals total_exp = total_exp.append(sum_exp) # Aggregate again so CUST_ID and MONTH are unique total_exp = total_exp.groupby([\u0026#39;CUST_ID\u0026#39;,\u0026#39;MONTH\u0026#39;],as_index=False)[\u0026#39;AMOUNT\u0026#39;].sum() count+=1 # Exit loop if len(df)!=steps: break # Final aggregations avg_exp = total_exp.groupby([\u0026#39;MONTH\u0026#39;])[\u0026#39;AMOUNT\u0026#39;].mean() 最后，我们将得到一个包含所有客户每月总额的表格。最后一步是计算每个月的平均金额（第 26 行）。你可以在图 4 中看到这些平均值。这里的平均值稳定，然后在 10 月、11 月和 12 月增加。\n除了时间什么也没有 因此，即使资源有限，我们也要分析这个庞大的数据集。最大的问题是运行每个聚合所需的时间。在我的计算机上，每次聚合大约需要 5 个半小时！尽可能加快速度很重要。\n耗时最多的部分是使用 get_rows 函数加载行。在这里，我们将硬盘上的 CSV 中的数据加载到内存/RAM 中。因此，你不应使用多个 while 循环，而应尝试在同一个循环内进行多次聚合。这样，我们只需要从磁盘读取一次数据。\n希望这篇文章对你有所帮助！你还可以阅读我的其他文章，或者查看有关企业 AI 实战项目的教程，相信会让你拥有更多收获。\nAI 进阶：企业项目实战[^3]\n参考 C O’Sullivan, Simulated Transactions, CC0: Public Domain https://www.kaggle.com/datasets/conorsully1/simulated-transactions\n「AI秘籍」系列课程：\n人工智能应用数学基础 人工智能Python基础 人工智能基础核心知识 人工智能BI核心知识 人工智能CV核心知识 Github, https://github.com/conorosully/medium-articles/blob/master/src/batch_processing.ipynb\u0026#160;\u0026#x21a9;\u0026#xfe0e;\n","permalink":"https://hivan.me/posts/%E4%BD%BF%E7%94%A8-pandas-%E6%89%B9%E9%87%8F%E5%A4%84%E7%90%86-22gb-%E4%BA%A4%E6%98%93%E6%95%B0%E6%8D%AE/","summary":"\u003cblockquote\u003e\n\u003cp\u003e如何利用有限的计算资源来处理大型数据集\u003c/p\u003e\u003c/blockquote\u003e","title":"使用 Pandas 批量处理 22GB 交易数据"},{"content":"\nSHAP 值是了解模型如何进行预测的绝佳工具。SHAP 包提供了许多可视化效果，使这个过程更加简单。话虽如此，我们不必完全依赖这个包。我们可以通过创建自己的 SHAP 图来进一步了解模型的工作原理。在本文中，我们将解释四个定制的 SHAP 图以及您可以从中学到什么。您可以在GitHub1上找到用于创建这些图的代码。\n讨论的图表之一是图 1 中的瀑布图。这是一种可视化单个预测的 SHAP 值的好方法。模型做出的每个预测都会有自己的瀑布图。它可以用来准确解释每个特征对最终预测的贡献。例如，这只鲍鱼的壳重量使预测的环数减少了 1.82。\n要创建此图，我们首先必须使用 SHAP 包计算 SHAP 值。然后我们将这些值传递到提供的瀑布图函数中。还有许多其他可用的图，但我们不一定非要使用它们。一旦我们有了 SHAP 值，我们就可以自由地创建自己的可视化效果。现在，让我们深入研究第一个。\n图 1：SHAP 相关热图 正如我们在瀑布图中看到的，对于给定的预测，模型中的每个特征都会有一个 SHAP 值。我们能够计算这些 SHAP 值在所有预测中的相关性。对每个成对特征组合执行此操作，我们可以构建一个 SHAP 相关性热图，如图 2 所示。在这里我们可以看到，例如，鲍鱼壳直径(diameter)和高度(height)的 SHAP 值的相关性为 0.4。\n我们可以将其与图 3 所示的标准相关性热图进行比较。这是使用特征值创建的，可以告诉我们特征是否相关。换句话说，它可以告诉我们两个特征是否倾向于朝相同方向或相反方向移动。另一方面，SHAP 值给出了特征对预测的贡献。因此，SHAP 相关性将告诉我们两个特征是否倾向于将预测移向同一方向。\n我们已经看到一些差异。请注意，在图 3 中，整重和去壳重呈正相关 (1)。相比之下，这些特征的 SHAP 值呈负相关 (-0.5)。即使这些特征朝同一方向移动，它们的贡献也倾向于将预测朝相反的方向移动。直观地看，这可能看起来很奇怪。一个可能的原因是这两个特征之间存在相互作用。\n另一个区别是 SHAP 相关性可以针对连续变量和分类变量进行计算。这是因为无论针对哪个特征计算 SHAP 值，它们始终都是连续的。这就是为什么三个二元变量（即 sex.I、sex.M 和 sex.F）包含在 SHAP 相关性热图中，而不是标准相关性热图中的原因。\n图2：调整后的瀑布图 下一个图更像是对现有 SHAP 图的补充。在文章开头的图 1 中，我们查看了瀑布图。我们可以通过添加一些其他信息来了解有关预测的更多信息。查看图 4，我们添加了该鲍鱼的实际环数（即 y=7）。我们还为每个特征添加了分布图。红线表示该特征的平均值。虚线表示用于进行预测的实际特征值。\n图 5 是可视化此信息的另一种方式。此处，特征被色块包围，其中颜色由特征的值决定。我们坚持 SHAP 包使用的惯例。如果特征的值相对于该特征的平均值较低，则该块将更蓝。如果特征值较高，则该块将为红色。在图 4 中，我们可以看到大多数特征值低于平均值（即红线左侧）。这对应于您在图 5 中看到的所有蓝色块。\n通过在上图中包括实际环数 y，我们能够看到模型对这只鲍鱼的准确度。如果我们发现预测与实际值有很大差异，我们对 SHAP 值的解释可能会改变。在我们的例子中，预测值非常接近实际值。如果它们不同，我们可能会查看瀑布图以了解是否有任何特征导致了错误的预测。\n通过添加分布图或色块，我们为特征值提供了背景信息。这使我们能够更全面地解释模型做出该预测的原因。之前我们可以看到哪个因素对预测贡献最大。现在，我们可以开始理解为什么该特征如此重要。为了更好地理解这一点，让我们使用银行业的一个示例。\n假设我们建立一个用于接受/拒绝贷款申请的模型。月收入可能是一个重要因素。随着这一特征的下降，您更有可能拖欠贷款。假设该模型拒绝了一项申请。之前我们可以说，“我们拒绝了您的申请，您的月收入是这一决定的主要驱动因素”。现在，通过调整后的瀑布图之一，我们可以说，“我们拒绝了您的申请。这是因为您的月收入远低于我们的平均客户。”\n图 3：交互热图 接下来我们将要看的两个图是 SHAP 交互值的可视化。如果您不熟悉这些，我建议您阅读一下分析与 SHAP 的相互作用这篇文章。我们将更深入地介绍如何解释这些值。总而言之，对于给定的预测，我们将有一个 SHAP 交互值矩阵。矩阵的对角线给出主效应，非对角线给出交互效应。每个预测都会有一个这样的矩阵。\n为了创建第三个图，我们首先计算所有交互值矩阵中每个单元格的绝对平均值。交互效应减半，因此我们还将对角线外的数值乘以 2。然后我们可以将其显示为热图，如图 6 所示。热图将围绕对角线对称，因此我们仅显示下半部分。\n该图与均值 SHAP 图类似，因为它可以突出显示重要特征。但现在，我们可以突出显示重要的主效应和交互效应。例如，我们可以看到经验、学位、绩效和销售额的平均主效应很大。这告诉我们，这些特征往往会对模型的预测产生重大影响。同样，我们可以看到经验.学位和绩效.销售额交互效应很显著。\n图 4：交互瀑布图 我们的最后一张图是 SHAP 交互值的瀑布图。这可以以与普通瀑布图相同的方式进行解释。但现在我们无法看到主效应和交互效应如何对预测做出贡献。例如，经验主效应使预测奖金增加了 35.99 美元。同样，经验.程度交互效应使预测奖金增加了 13.76 美元。\n如果您查看用于创建此图的代码，您会发现我们非常狡猾。问题是 SHAP 包没有提供可视化预测交互值的方法。经过一些工作，我们就可以使用为正常 SHAP 值创建瀑布图的函数。如图 8 所示，这涉及将二维矩阵中的交互值转换为一维数组。一旦我们有了这个数组，我们就可以将其传递给瀑布函数，SHAP 会将其视为一组正常的 SHAP 值。\n参考 S. Lundberg, SHAP Python package (2021), https://github.com/slundberg/shap\nS. Lundberg \u0026amp; S. Lee, A Unified Approach to Interpreting Model Predictions (2017), https://arxiv.org/pdf/1705.07874.pdf\n「AI秘籍」系列课程：\n人工智能应用数学基础 人工智能Python基础 人工智能基础核心知识 人工智能BI核心知识 人工智能CV核心知识 Github, https://github.com/hivandu/public_articles/blob/main/src/interpretable_ml/SHAP/shap_custom.ipynb\u0026#160;\u0026#x21a9;\u0026#xfe0e;\n","permalink":"https://hivan.me/posts/%E5%9B%9B%E4%B8%AA%E8%87%AA%E5%AE%9A%E4%B9%89-shap-%E5%9B%BE/","summary":"\u003cblockquote\u003e\n\u003cp\u003e超越 Python 包，创建 SHAP 值的定制可视化\u003c/p\u003e\u003c/blockquote\u003e","title":"四个自定义 SHAP 图"},{"content":"可直接在橱窗里购买，或者到文末领取优惠后购买：\nSHAP 是用于理解和调试模型的最强大的 Python 包。它可以告诉我们每个模型特征对单个预测的贡献。通过汇总 SHAP 值，我们还可以了解多个预测的趋势。只需几行代码，我们就能识别和可视化模型中的重要关系。\n我们将介绍用于计算和显示 SHAP 值的代码。其中包括对以下 SHAP 图的解释：\nWaterfall Plot Force Plot Mean SHAP Plot Beeswarm Plot Dependence Plot 我们针对预测连续目标变量的模型执行此操作。如果你需要分类的解释，日后会专门写相关文章，届时可以参阅 [[二元和多类目标变量的 SHAP]]，你可以在 GitHub1 上找到完整的项目。\n数据集 为了演示 SHAP 包，我们将使用包含 4,177 个观测值的「鲍鱼数据集」2。下面，你可以看到我们的数据集的快照。鲍鱼是一种贝类美食。我们想使用数据集来预测它们的年龄。更具体地说，我们的目标变量是鲍鱼壳中的环数。我们将使用shell length、shell diameter 和鲍鱼的 whole weight 等特征。\n在下图中，我们直观地展示了一些特征与目标变量之间的关系。 Shucked weight 是鲍鱼肉的重量（即从壳中取出后的重量）。我们可以看到，去壳重量增加时，环的数量往往会增加。这是有道理的，因为我们认为年龄较大的鲍鱼会更大，肉也更多。\n我们还可视化了鲍鱼的性别，这是一个分类特征。在模型中使用此功能之前，我们需要使用 One-Hot 编码对其进行转换。正如你所看到的，这会导致每个生成的二进制特征都有一个单独的 SHAP 值。这使得很难理解原始分类特征的整体贡献。我们将在后面专门写一篇文章「分类特征的 SHAP」探讨一种解决方案。\n对于最后一点数据探索，我们为连续特征创建一个相关矩阵。 可以看到，我们正在处理高度相关的特征。 长度和直径完全相关。 同样，全重与其他重量测量值也高度相关。 例如肉的重量（去壳重量）和除去肉的外壳重量（外壳重量）。\n包 我们在下面导入必要的 Python 包。我们有一些用于管理和可视化数据的标准库（第 4-9 行）。XGBoost 用于对目标变量进行建模（第 13 行），我们导入一些包来评估我们的模型（第 14 行）。最后，我们导入 SHAP 包（第 16 行）。我们初始化包（第 17 行）。这样我们就可以在笔记本中显示一些 SHAP 图。\nimport warnings warnings.filterwarnings(\u0026#34;ignore\u0026#34;) import pandas as pd import numpy as np import matplotlib.pyplot as plt %matplotlib inline plt.style.use(\u0026#39;default\u0026#39;) import seaborn as sns import xgboost as xgb from sklearn.metrics import accuracy_score,confusion_matrix import shap shap.initjs() import os dp = os.environ.get(\u0026#39;pub_data\u0026#39;) 建模 我们使用数据探索来指导特征工程。首先，我们从 X 特征矩阵中删除长度和整体重量（第 10-11 行）。我们发现这些与其他特征完美相关。\n# import dataset df = pd.read_csv(dp + \u0026#39;Abalone dataset/abalone.data\u0026#39;, names=[ \u0026#34;sex\u0026#34;, \u0026#34;length\u0026#34;, \u0026#34;diameter\u0026#34;, \u0026#34;height\u0026#34;, \u0026#34;whole weight\u0026#34;, \u0026#34;shucked weight\u0026#34;, \u0026#34;viscera weight\u0026#34;, \u0026#34;shell weight\u0026#34;, \u0026#34;rings\u0026#34;, ], ) # Get features y = df[\u0026#34;rings\u0026#34;] X = df[[\u0026#34;sex\u0026#34;, \u0026#34;length\u0026#34;, \u0026#34;height\u0026#34;, \u0026#34;shucked weight\u0026#34;, \u0026#34;viscera weight\u0026#34;, \u0026#34;shell weight\u0026#34;]] 我们看到性别是一个分类特征。在将其用于模型之前，我们需要将其转换为 3 个虚拟变量（第 2-4 行）。然后，我们从特征矩阵中删除原始特征（第 5 行）。\n# create dummy variables y = y.copy() X = X.copy() X.loc[:, \u0026#39;sex.M\u0026#39;] = X[\u0026#39;sex\u0026#39;].apply(lambda s: 1 if s == \u0026#39;M\u0026#39; else 0) X.loc[:, \u0026#39;sex.F\u0026#39;] = X[\u0026#39;sex\u0026#39;].apply(lambda s: 1 if s == \u0026#39;F\u0026#39; else 0) X.loc[:, \u0026#39;sex.I\u0026#39;] = X[\u0026#39;sex\u0026#39;].apply(lambda s: 1 if s == \u0026#39;I\u0026#39; else 0) X = X.drop(\u0026#34;sex\u0026#34;, axis=1) 我们现在有 8 个模型特征。你可以在下面看到这些特征的快照。\n现在，我们可以训练一个用于预测鲍鱼壳环数的模型。由于我们的目标变量是连续的，因此我们使用XGBRegressor（第 2 行）。我们在整个特征集上训练模型（第 3 行）。\n# train model model = xgb.XGBRegressor(objective=\u0026#34;reg:squarederror\u0026#34;) model.fit(X, y) 这个模型应该足以演示 SHAP 包。我们可以通过使用图 3 中的散点图对其进行评估来看到这一点。我们正在将模型的预测（第 2 行）与实际环数进行比较。红线给出了如果我们有完美预测的值。\n**注意：**我们没有在模型上投入太多精力。除非你使用 SHAP 进行数据探索，否则你应该始终使用最佳实践。你的模型越好，你的 SHAP 分析就越可靠。\nSHAP 图 最后，我们可以使用 SHAP 值来解释这个模型。为此，我们将模型传递给 shap.Explainer 器函数（第 2 行）。这将创建一个解释器对象。我们使用它来计算特征矩阵中每个观测值的 SHAP 值（第 3 行）。\n# get shap values explainer = shap.Explainer(model) shap_values = explainer(X) 图 1：Waterfall 特征矩阵中的 4,177 个观测值中的每一个都有 8 个 SHAP 值。也就是说，我们模型中的每个特征都有一个 SHAP 值。我们可以使用 waterfall 函数来可视化第一个观测值的 SHAP 值（第 2 行）。\n# Waterfall plot for first observation shap.plots.waterfall(shap_values[0]) E[f(x)] = 9.933 给出了所有 4,177 只鲍鱼的平均预测环数。f (x) = 12.668是这只鲍鱼的预测环数。SHAP 值是介于两者之间的所有值。例如，去壳重量使预测环数增加了1.68\n我们数据集中的每个观测/鲍鱼都会有一个独特的瀑布图。它们都可以用与上述相同的方式进行解释。在每种情况下，SHAP 值告诉我们与平均预测相比，特征对预测的贡献如何。较大的正/负值表示该特征对模型的预测有显著影响。\n图 2：Force Plot 另一种可视化方法是使用力图。你可以将其视为浓缩的瀑布图。我们从相同的基值9.933开始，你可以看到每个特征对最终预测 13.04 的贡献。\n# Forceplot for first observation shap.plots.force(shap_values[0]) 图 3：叠加 Force Plot 瀑布图和力图非常适合解释单个预测。要了解我们的模型如何进行总体预测，我们需要汇总 SHAP 值。一种方法是使用堆叠力图\n我们可以将多个力图组合在一起以创建堆叠力图。在这里，我们在力图函数中传递前 100 个观测值的 SHAP 值。每个单独的力图现在都是垂直的并且并排堆叠。\nshap.plots.force(shap_values[0:100]) 你可以在下面看到此图是交互式的。我们还可以更改图的顺序并选择要显示的特征贡献。\n例如，在下面的图中我们有：\n仅显示壳重量的 SHAP 值（y 轴 = **shell weight effects ） 按壳重量特征值对力图进行排序（x 轴 = **shell weight）\n从该图中我们可以看出，随着壳重的增加，SHAP 值也会增加。换句话说，年龄较大的鲍鱼壳往往更重。\n这是了解我们的模型所捕获关系的性质的一种方法。我们将看到蜂群图和依赖图也可以以这种方式使用。\n图 4：Mean SHAP 下一个图将告诉我们哪些特征最重要。对于每个特征，我们计算所有观测值的平均 SHAP 值。具体来说，我们取绝对值的平均值，因为我们不希望正值和负值相互抵消。最后，我们得到下面的条形图。每个特征都有一个条形图。例如，我们可以看到壳重量具有最大的平均 SHAP 值。\n# Mean SHAP shap.plots.bar(shap_values) 具有较大正/负贡献的特征将具有较大的平均 SHAP 值。换句话说，这些特征对模型的预测产生了重大影响。从这个意义上讲，此图的使用方式与特征重要性图相同。\n图 5：Beeswarm 接下来，我们得到了最有用的图表。蜂群将所有 SHAP 值可视化。在 y 轴上，这些值按特征分组。对于每个组，点的颜色由特征值决定（即，特征值越高，颜色越红）。\n# Beeswarm plot shap.plots.beeswarm(shap_values) 与平均 SHAP 一样，蜂群可用于突出重要关系。事实上，上图中的特征是按平均 SHAP 排序的。\n我们也可以开始理解这些关系的性质。对于壳重，请注意 SHAP 值如何随着特征值的增加而增加。我们在堆叠力图中看到了类似的关系。它告诉我们，壳重值越大，预测的环数也就越多。\n你可能已经注意到，去壳重量具有相反的关系。查看蜂群图，我们可以看到此特征的较大值与较小的 SHAP 值相关。我们可以使用依赖图仔细研究这些关系。\n图 6：Dependence Plot 依赖图是单个特征的 SHAP 值与特征值的散点图。如果特征与目标变量具有非线性关系，则它们特别有用。\n例如，以壳重的依赖关系图为例。查看蜂群图，我们可能假设 SHAP 值随特征值线性增加。依赖关系图告诉我们，这种关系并不是完全线性的。\n#Plot 1: shell weight shap.plots.scatter(shap_values[:,\u0026#34;shell weight\u0026#34;]) 我们还可以使用第二个特征的值来为散点图着色。现在我们有了相同的图，去壳重量越大，点越红。我们可以看到，当壳重和去壳重量都很大时，SHAP 值也很大。\nshap.plots.scatter(shap_values[:,\u0026#34;shell weight\u0026#34;], color=shap_values[:,\u0026#34;shucked weight\u0026#34;]) 这些图可用于可视化特征之间的相互作用，但要小心！在我们的例子中，该图是两个特征之间相关性的结果。\n我们还有去壳重量（即鲍鱼肉的重量）的依赖关系图。使用此图，我们可以确认我们在蜂群图中看到的关系。SHAP 值确实会随着去壳重量的增加而下降。\n# Plot 2: shucked weight shap.plots.scatter(shap_values[:,\u0026#34;shucked weight\u0026#34;]) 直观地看，这种关系似乎很奇怪。我们不是希望年龄较大的鲍鱼更大、肉更多吗？事实上，这是壳重和去壳重量相互作用的结果。由于相关性，我们在依赖图中看不到它。在文章《[分析与 SHAP 的相互作用](../分析与 SHAP 的相互作用)》中，我们探讨了如何使用 SHAP 交互值来识别此类相互作用。\nSHAP 用于分类 你会很高兴地知道，分类问题的 SHAP 图与上面的非常相似。除了二元目标变量之外，我们根据对数几率来解释 SHAP 值。对于多类目标，我们使用softmax。一个缺点是我们最终会为多类目标中的每个类绘制单独的 SHAP 图。我们将在后面一篇文章中讨论了一些更好的聚合值的方法，暂定标题为[[二元和多类目标变量的 SHAP](../二元和多类目标变量的 SHAP)]，当你的模型预测分类目标变量时，代码和 SHAP 图的解释指南。\n我们可以看到，SHAP 值是一种有用的工具，可用于了解我们的模型如何进行预测。然而，我们只是触及了该软件包所能提供内容的表面。如果你想了解更多信息，我日后将会写一些有关 SHAP 的文章，用于更深入地探讨了 SHAP 和 Shapley 值的某些方面，包括：\n[从 Shapley 到 SHAP — 数学理解], 如何计算 SHAP 特征贡献的概述\n[[KernelSHAP 与 TreeSHAP]], 根据速度、复杂性和其他考虑因素比较 SHAP 近似方法\n[[使用 SHAP 调试 PyTorch 图像回归模型](../使用 SHAP 调试 PyTorch 图像回归模型)], 使用 DeepShap 理解和改进自动驾驶汽车模型\n由于本人常年使用双向链接软件写文，在粘贴的时候会时常有类似[[XXX 文章名]]的样式，这只是我文章相互连接的格式，如若不能链接到别处大概率就是还未成文发布。\nAI 进阶：企业项目实战3\n参考 S. Lundberg, SHAP Python package (2021), https://github.com/slundberg/shap\nS. Lundberg \u0026amp; S. Lee, A Unified Approach to Interpreting Model Predictions (2017), https://arxiv.org/pdf/1705.07874.pdf\nC. Molnar, Interpretable Machine Learning (2021) https://christophm.github.io/interpretable-ml-book/shap.html\n希望这篇文章对你有所帮助！你还可以阅读我的其他文章，或者查看有关企业 AI 实战项目的教程，相信会让你拥有更多收获。\n「AI秘籍」系列课程：\n人工智能应用数学基础\n人工智能Python基础\n人工智能基础核心知识\n人工智能BI核心知识\n人工智能CV核心知识\nGithub, https://github.com/hivandu/public_articles/blob/main/src/SHAP/shap_tutorial.ipynb\u0026#160;\u0026#x21a9;\u0026#xfe0e;\nAbalone Dataset, https://archive.ics.uci.edu/dataset/1/abalone\u0026#160;\u0026#x21a9;\u0026#xfe0e;\nAI 企业项目实战, https://www.sanjieke.cn/course/detail/sjk/8005780\u0026#160;\u0026#x21a9;\u0026#xfe0e;\n","permalink":"https://hivan.me/posts/python-%E4%B8%AD%E7%9A%84-shap-%E7%AE%80%E4%BB%8B/","summary":"\u003cblockquote\u003e\n\u003cp\u003e本文中有多篇计划文章，后期会补充相关链接。鉴于公众号内无法后期修改文章，请关注原文链接。 \u0026gt; \u0026gt; 如何创建和解释 SHAP 图：瀑布图、力图、平均 SHAP 图、蜂群图和依赖图\u003c/p\u003e\u003c/blockquote\u003e","title":"Python 中的 SHAP 简介"},{"content":"可直接在橱窗里购买，或者到文末领取优惠后购买：\nSHAP 值用于解释模型做出的个别预测。它通过给出每个因素对最终预测的贡献来实现这一点。SHAP 交互值在此基础上进行了扩展，将贡献分解为主要影响和交互影响。我们可以使用这些值来突出显示和可视化数据中的交互。它也可以成为了解模型如何进行预测的有用工具。\n重点将放在应用 SHAP 包和解释结果上。具体来说，我们首先解释什么是 SHAP 交互值以及如何使用它们来解释单个预测。然后，我们深入研究这些值的 3 种不同聚合，这有助于我们解释模型如何进行一般预测。这包括对所有交互值取绝对平均值并使用 SHAP 摘要和依赖图。我们将介绍关键的代码片段，您可以在 GitHub1 上找到完整的项目。\n数据集 为了解释如何使用 SHAP 包，我们创建了一个包含 1000 个观测值的模拟数据集。在表 1 中，您可以看到此数据集中的特征。目标是使用其余 5 个特征预测员工的年终奖金。我们设计了数据集，因此经验和学位之间以及绩效和销售额之间存在相互作用。days_late 不与任何其他特征交互。\n本文假设您对数据中的交互有所了解。如果不了解，最好先阅读一下文章寻找并可视化交互。该文章中，我们准确地解释了交互是什么。使用与上面相同的数据集，我们还解释了可用于分析它们的其他技术。这些可以成为 SHAP 的良好替代方案。\n使用特征重要性、弗里德曼 H 统计量和 ICE 图分析相互作用\n包 在下面的代码中，您可以看到我们将用来分析这些数据的包。在第 1 行到第 4 行，我们有一些用于管理和可视化数据的标准库。在第 6 行，我们导入了 XGBoost，我们用它来对目标变量进行建模。在第 8 行，我们导入了 SHAP 包。在此之下，我们初始化包，它允许您在笔记本中显示图表。确保您已安装所有这些。\nimport pandas as pd import numpy as np import matplotlib.pyplot as plt import seaborn as sns import xgboost as xgb import shap shap.initjs() import os dp = os.environ.get(\u0026#39;pub_data\u0026#39;) 建模 要使用 SHAP 包，我们首先需要训练一个模型。在第 2-4 行，我们导入数据集并分配目标变量和特征。在第 7 行和第 8 行，我们定义并训练 XGBoost 模型。为了简单起见，我们使用整个数据集训练了我们的模型。为了避免过度拟合，我们将模型中每棵树的最大深度限制为 3。\n# import dataset data = pd.read_csv(dp + \u0026#34;interaction_dataset.csv\u0026#34;,sep=\u0026#39;\\t\u0026#39;) y = data[\u0026#39;bonus\u0026#39;] X = data.drop(\u0026#39;bonus\u0026#39;, axis=1) # Train model model = xgb.XGBRegressor(objective=\u0026#34;reg:squarederror\u0026#34;,max_depth=3) model.fit(X, y) 如果您将 SHAP 应用于实际问题，则应遵循最佳实践。具体来说，您应该确保您的模型在训练和验证集上都表现良好。模型越好，结果就越可靠。为了快速检查此模型，我们在下面绘制了实际奖金与预测奖金的图表。该模型应该可以很好地展示 SHAP 包。\n解释 SHAP 交互值 现在我们有了模型，我们可以得到 SHAP 相互作用值。这些可以像正常的 SHAP 值一样进行解释。如果您不熟悉如何解释这些，可以查看我后续的相关文章，我会写一篇[[Python 中的 SHAP 简介]] ，这会很值得一读。这将使本文的其余部分更容易理解，因为讨论的图表是相似的。所以，其实我很多文章都是相互交错的，得多回头来读。\n如何创建和解释 SHAP 图：瀑布图、力图、决策图、平均 SHAP 图和蜂群图\n为了计算 SHAP 交互值，在第 2 行，我们通过将模型传递给 TreeExplainer 函数来定义解释器。此函数用于解释集成树模型的输出。使用第 3 行的解释器，我们得到交互值。这将返回一个数组 shap_interaction，其中包含 X 特征矩阵中 1000 个观测值中的每一个的一条记录。\n# Get SHAP interaction values explainer = shap.TreeExplainer(model) shap_interaction = explainer.shap_interaction_values(X) --- ntree_limit is deprecated, use `iteration_range` or model slicing instead. (1000, 5, 5) 要了解 shap_interaction 的结构，我们可以使用下面的代码。第 2 行告诉我们数组的形状是 (1000, 5, 5)。这意味着数组包含 1000 个 5x5 矩阵。我们将 5x5 矩阵称为贡献矩阵。它们包含用于解释 1000 个单独预测中的每一个的 SHAP 值。第 5 行给出了我们数据集中第一个员工的贡献矩阵。我们可以在下面的图 2 中看到这个矩阵。\n# Get shape of interaction values print(np.shape(shap_interaction)) # SHAP interaction values for first employee pd.DataFrame(shap_interaction[0],index=X.columns,columns=X.columns) --- (1000, 5, 5) 矩阵告诉我们与平均预测相比，每个因素对模型预测的贡献有多大。这与标准 SHAP 值的解释类似，只是贡献被分解为主要效应和交互效应。主要效应在对角线上给出。例如，这名员工的经验水平使他们的预期奖金增加了 35.99 美元。交互效应在非对角线上给出。这些值减半，例如，绩效与销售交互使预期奖金减少了 8.76 美元（-4.38 美元 x 2）。\n平均预测是所有 1000 名员工的平均预测奖金。如果将贡献矩阵中的所有值相加，并添加平均预测，您将获得该员工的模型实际预测。在我们的例子中，平均预测奖金为 148.93 美元。矩阵中的所有值加起来为 59.98 美元。这为第一位员工提供了 208.91 美元的预测奖金。您可以使用以下代码确认这与模型的预测相同。\n# Get model predictions y_pred = model.predict(X) # Calculate mean prediction mean_pred = np.mean(y_pred) # Sum of interaction values for first employee sum_shap = np.sum(shap_interaction[0]) # Values below should be the same print(\u0026#34;Model prediction: {}\u0026#34;.format(y_pred[0])) print(\u0026#34;Mean prediction + interaction values: {}\u0026#34;.format(mean_pred+sum_shap)) --- Model prediction: 208.9059600830078 Mean prediction + interaction values: 208.9059295654297 SHAP 相互作用图 通过查看单个贡献矩阵，您可以解释单个模型的预测。但是，如果我们想解释模型如何进行总体预测呢？为此，我们可以用几种不同的方式汇总贡献矩阵中的值。\n绝对均值图 首先，我们将计算所有 1000 个矩阵中每个单元格的绝对平均值。我们取绝对值，因为我们不希望正负 SHAP 值相互抵消。由于交互效应减半，我们还将对角线乘以 2。然后我们将其显示为热图。您可以在下面的代码中看到如何完成此操作，输出如下图 3 所示。\n# Get absolute mean of matrices mean_shap = np.abs(shap_interaction).mean(0) df = pd.DataFrame(mean_shap,index=X.columns,columns=X.columns) # times off diagonal by 2 df.where(df.values == np.diagonal(df),df.values*2,inplace=True) # display plt.figure(figsize=(10, 10), facecolor=\u0026#39;w\u0026#39;, edgecolor=\u0026#39;k\u0026#39;) sns.set(font_scale=1.5) sns.heatmap(df,cmap=\u0026#39;coolwarm\u0026#39;,annot=True,fmt=\u0026#39;.3g\u0026#39;,cbar=False) plt.yticks(rotation=0) 该图可用于突出显示重要的主效应和交互效应。例如，我们可以看到经验、学位、绩效和销售额的平均主效应很大。这告诉我们这些特征往往具有较大的正或负主效应。换句话说，这些特征往往会对模型的预测产生重大影响。同样，我们可以看到经验.学位和绩效.销售额交互效应很显著。\n摘要情节 对于标准 SHAP 值，一个有用的图是蜂群图。这是 SHAP 包中包含的图之一。在下面的代码中，我们获取 SHAP 值并显示此图。具体来说，这些是尚未分解为主要效应和交互效应的 SHAP 值。\n# Get SHAP values shap_values = explainer(X) # Display beeswarm plot shap.plots.beeswarm(shap_values) 我们不会过多地解释这张图表。快速浏览一下图 4，你可以看到图表颜色由左轴上给出的特征的特征值决定。我们可以看到学位、经验、绩效和销售额的高值都与更高的 SHAP 值或更低的更高奖金相关。\n有了 SHAP 交互值，我们可以使用下面代码中的摘要图来扩展此图。输出如图 5 所示。这里，主效应的 SHAP 值在对角线上给出，非对角线上给出交互效应。对于此图，交互效应已经加倍。与蜂群图一样，颜色由 y 轴上特征的特征值给出。\n# Display summary plot shap.summary_plot(shap_interaction, X) 通过指出重要的主效应和交互效应，该图可以提供与绝对均值图类似的见解。具体来说，我们可以看到，图 3 中具有高 SHAP 值的单元格与具有高绝对均值的相同单元格相对应。摘要图通过可视化关系的性质提供了额外的见解。例如，我们可以看到学位、经验、绩效和销售额的主要影响都是积极的。\n依赖图 摘要图中有很多细节，即使有颜色，也很难解释交互作用。我们可以使用依赖关系图来更好地理解交互作用的性质。下面，您可以看到用于创建 experience.degree 交互作用的依赖关系图的代码。\n# Experience-degree depenence plot shap.dependence_plot( (\u0026#34;experience\u0026#34;, \u0026#34;degree\u0026#34;), shap_interaction, X, display_features=X) 查看图 6 中的输出，我们可以看到，如果此人拥有学位，则经验与学位的交互效应会随着经验的增加而增加。如果此人没有学位，则情况相反。我们应该记住，这只是交互效应的图。应根据经验和学位的主要影响来考虑这种关系。\n回到我们的总结图，我们可以看到经验的主要影响是积极的。因此，假设有学位的人的经验增加。主要影响和交互影响将朝着同一个方向发挥作用。这意味着随着经验的增加，他们的预期奖金将会增加。另一方面，如果一个人没有学位，那么主要（积极）和交互（消极）影响将朝着相反的方向发挥作用。随着经验的增加，预期奖金可能会增加、减少或保持稳定。\n这与下面的散点图一致。这里我们绘制了原始数据值 — 没有 SHAP 值。我们可以看到，如果一个人有学位，那么他们的奖金会随着经验的增加而增加。如果他们没有学位，奖金在不同的经验水平上趋于稳定。我们没有使用 SHAP 值来展示这一点，但稳定的奖金表明，当员工没有学位时，主要效应和交互效应可能会完全抵消。\n最后，在图 6 中，我们得到了绩效-销售交互的依赖关系图。该图可以用类似的方式解释。我们应该记住，我们现在有一个更好的交互作用，即两个连续变量，而之前我们有一个连续变量和一个分类变量。\n上面我们将 SHAP 交互值用作一种探索性数据分析技术。也就是说，我们使用它们来识别和可视化重要的交互。我们还可以使用该包来帮助解释我们的模型。您可以以类似的方式使用它，只是目标是了解我们的模型如何进行预测。在文章《机器学习中的可解释性》中我们详细讨论了为什么这很重要。\n「AI 进阶：企业项目实战」2\n参考 S. Lundberg, SHAP Python package (2021), https://github.com/slundberg/shap\nS. Lundberg, NHANES I Survival Model.ipynb (2020), [https://github.com/slundberg/shap/blob/master/notebooks/tabular_examples/tree_based_models/NHANES%20I%20Survival%20Model.ipynb](https://github.com/slundberg/shap/blob/master/notebooks/tabular_examples/tree_based_models/NHANES I Survival Model.ipynb)\nS. Lundberg \u0026amp; S. Lee, A Unified Approach to Interpreting Model Predictions (2017), https://arxiv.org/pdf/1705.07874.pdf\nC. Molnar, Interpretable Machine Learning (2021) https://christophm.github.io/interpretable-ml-book/interaction.html\n「AI秘籍」系列课程：\n人工智能应用数学基础 人工智能Python基础 人工智能基础核心知识 人工智能BI核心知识 人工智能CV核心知识 Github, https://github.com/hivandu/public_articles/blob/master/src/interpretable_ml/SHAP/shap_interaction.ipynb\u0026#160;\u0026#x21a9;\u0026#xfe0e;\nAI 企业项目实战, https://www.sanjieke.cn/course/detail/sjk/8005780\u0026#160;\u0026#x21a9;\u0026#xfe0e;\n","permalink":"https://hivan.me/posts/%E5%88%86%E6%9E%90%E4%B8%8E-shap-%E7%9A%84%E7%9B%B8%E4%BA%92%E4%BD%9C%E7%94%A8/","summary":"\u003cblockquote\u003e\n\u003cp\u003e使用SHAP Python包识别和可视化数据中的交互\u003c/p\u003e\u003c/blockquote\u003e","title":"分析与 SHAP 的相互作用"},{"content":"可直接在橱窗里购买，或者到文末领取优惠后购买：\n在保险业中，过去的索赔行为对未来的索赔行为具有很强的预测性。它可能是用于确定客户是否会提出索赔的唯一最具预测性的信息来源。但是，如果我们仅使用索赔历史来构建模型，它就不会很好。一般来说，模型特征应该来自各种不同的信息源。你的特征选择方法应该旨在从每个不同的信息源中创建最具预测性的特征的候选名单。\n在本文中，我们将解释如何使用变量聚类和特征重要性的组合来创建这样的候选列表。我们还讨论了可能导致添加或删除特征的其他考虑因素。这些因素包括数据质量和可用性、特征稳定性、可解释性和法律/道德。最后，我们将讨论如何在特征选择框架中整合所有这些考虑因素。让我们首先准确定义特征选择的含义。\n什么是特征选择？ 在模型开发过程中，特征选择发生在特征工程之后、开始拟合模型之前。在特征工程期间，我们将原始数据转换为模型特征列表。根据你的问题，此列表可能很大（即超过 1000 个特征）。特征选择涉及将其缩小到一个候选列表（即 20-40 个特征）。根据你的模型，可能还有另一个特征选择阶段，你将在此选择模型特征的最终列表（即 8-10 个特征）。在本文中，我们重点介绍第一阶段 — 创建候选列表。\n我们出于一些原因创建了一个候选名单。在模型训练阶段，使用完整的特征列表在计算上是昂贵的。甚至在开始训练模型之前，我们就会想要探索这些特征以了解它们与目标变量的关系。测试这些特征也需要大量工作。没有必要对所有特征都进行这项工作。这是因为完整列表将包含冗余/不相关的特征。\n冗余功能 为了更好地理解这一点，假设你想要创建一个模型来预测某人是否会拖欠汽车贷款。假设对于每个客户，我们都可以访问有关月收入、违约和欠款历史、现有债务义务和一些个人信息的数据。在特征工程期间，你将使用不同时间段的不同聚合从每个来源创建许多特征。在图 2 中，你可以看到一些示例。\n对于收入，我们预计上述 3 个特征非常相似。我们也可以预期它们可以预测违约。但是，它们都会捕捉到收入和违约之间相同的潜在关系。这意味着，一旦我们的模型中有了一个收入特征，再增加一个特征并不会提高模型的准确性。我们说其余的收入特征是多余的。我们希望将多余的特征从我们的候选名单中排除，即使它们具有预测性。\n不相关的特征 我们还希望删除任何不具有预测性或由于其他原因不应考虑的特征（例如，在模型中使用该特征是非法的）。这些被称为不相关特征。例如，假设我们使用客户 20 年前的收入创建了一个特征。这些信息已经过时，不会告诉我们有关他们当前财务状况的任何信息。换句话说，这个特征不会帮助我们预测客户是否会拖欠汽车贷款。\n特征选择的目标是通过删除尽可能多的冗余和不相关的特征来缩小列表。为此，我们需要考虑各个特征的预测能力以及特征之间的差异（即预测变量多样性）。这些是主要考虑因素，我们将在下面深入讨论它们。\n预测能力 我们可以大致将预测能力定义为特征预测目标变量的能力。实际上，我们需要使用一些度量/统计数据来估计这一点。一个常见的度量是特征与目标变量之间的相关性。较大的正相关或负相关表示存在很强的关系，换句话说，该特征具有预测性。其他度量包括信息值、互信息和特征重要性分数。\n不同的衡量标准各有优缺点。重要的是，它们允许我们比较特征并优先考虑那些最具预测性的特征。但是，如果这是我们唯一考虑的事情，我们可能不会得到一个很好的候选名单。继续我们的汽车贷款示例，假设我们已经创建了表 1 中的特征列表。这是我们的完整特征列表，我们已根据特征重要性对它们进行了排序。\n总体而言，我们可以看到基于违约历史的特征最具预测性。这是因为客户过去的违约历史将告诉我们很多有关他们将来是否会违约的信息。因此，假设我们只将 20 个最具预测性的特征作为最终候选名单。我们最终会得到许多违约特征。换句话说，候选名单中会有许多冗余特征。\n预测变量 这个问题引出了我们应该考虑的第二个因素——预测变量的多样性。为了避免出现过多的冗余特征，我们需要考虑这些特征之间的差异。为此，我们应该首先从完整列表中创建特征组。这些组的构建方式应使组内的特征彼此相似，而与其他组中的特征不同。现在，如果我们从每个组/子列表中提取最具预测性的特征，我们最终会得到更少的冗余特征。\n变量聚类 问题是我们如何创建这些特征组。我们可以使用领域知识手动完成，或者可能已经存在固有的分组。对于我们的汽车贷款示例，我们可以创建 6 个组（5 个主要数据源和 1 个收入和债务组合）。例如，你可以在图 4 中看到违约和收入组。在这两个组中，特征都按特征重要性排序。然后，我们可以从每个组中选取 3/4 个最具预测性的特征，从而为我们提供 18 到 24 个特征的候选名单。\n对于许多问题，手动对特征进行分组可能很困难。你可能有太多特征无法手动分组。底层数据源也可能更复杂。即使对于我们的汽车贷款示例，也不清楚 6 个组是否是最佳分组。例如，个人组中的特征之一是“当前职业”。最好将其与收入特征一起包含在内。\n为了解决这些问题，我们需要一种统计方法来创建相似的组。这些方法称为变量聚类方法。例如，你可以使用Python 中的 VarClusHi1 包。此方法旨在对高度相关的变量进行分组。组内的变量应该高度相关，同时不与其他组中的变量相关。你还可以重新设计 K-means 等聚类方法，使它们对变量而不是观测值进行聚类。\n其他特征和注意事项 使用变量聚类将帮助我们删除冗余特征。使用特征重要性将帮助我们删除不相关的特征，因为它们不具有预测性。在使用这些方法之前，我们可能首先要删除由于其他原因而不相关的特征。同样，我们可能希望包含某些特征，即使它们不具有预测性。这些额外的原因总结在图 5 中，我们将在下面详细介绍。\n数据质量 如果某个特征的数据质量较差，则可能被视为不相关。每当我们预期数据集中的记录值与真实值不同时，我们就应该担心。例如，数据可能丢失或输入错误。数据质量差通常意味着某个特征无法预测，无论如何都会被排除。情况并非总是如此，尤其是当错误是系统性的或数据不是随机丢失时。\n在某些情况下，数据可能会被用户或第三方操纵。以我们的汽车贷款模型为例，用户可能会被要求提供自己的个人信息（例如原籍国、职业）。用户可能会倾向于提供能让他们更有可能获得贷款的信息。例如，他们可能会谎称自己有一份高薪工作，比如医生或软件开发人员。我们需要独立验证这些类型的特征是否正确。\n数据可用性 模型通常是在开发环境中创建的。要实际使用模型，它们需要转移到生产环境中。由于各种技术问题和成本，某些功能可能无法在生产环境中使用。换句话说，数据可用于训练模型，但不能供模型进行实时预测。要获得有效的模型，我们需要排除这些功能中的任何一个。\n即使这些特征被排除在最终模型之外，我们可能仍希望将它们纳入我们的候选名单。这将使我们能够继续探索这些特征。如果我们能够证明它们具有预测性，这将有助于证明生产这些特征所涉及的工作和成本是合理的。然后它们可以用于未来的模型。\n稳定性（过去和未来的表现） 你的特征现在具有预测性并不意味着它们将来也具有预测性。随着时间的推移，特征与目标变量之间的关系可能会发生变化，并且模型中捕获的关系可能会过时。这些变化是由各种内部和外部力量驱动的。例如，COVID 疫情导致客户行为突然发生变化，从而影响试图预测该行为的模型。\n为了更好地理解这一点，让我们回到汽车贷款的例子。假设在疫情爆发之前，我们用 PERS7（过去 12 个月实体分行访问次数）建立一个模型。当时，更频繁去银行的客户拖欠汽车贷款的可能性较小。换句话说，PERS7 值越高，拖欠的可能性就越低。疫情爆发后，封锁意味着许多互动转移到了线上。这意味着更少的分行访问，我们会看到所有客户的 PERS7 值下降。\n从我们的模型来看， PERS7 的下降表明违约风险更高。尽管许多客户的违约风险不会改变，但该模型现在会预测更高的违约概率。贵组织内部和外部还有许多其他潜在变化可能会以这种方式影响你的模型。你需要尽力考虑所有这些变化，并选择能够抵御这些变化的特征。\n可解释性 可解释性 到目前为止，我们已经讨论过使用平均值和最小值等简单的聚合方法来创建特征。 还有更复杂的特征工程技术，如主成分分析 (PCA)。 例如，从图 4 中，我们可以提取收入组中的所有特征并计算 PCs。 简单来说，可以将 PCs 视为收入特征的汇总。 我们可以考虑将第 1 款 PC 列入候选名单。\n第一个主成分可能比任何单个收入特征都更具预测性。然而，我们可能仍然只将简单的收入汇总纳入我们的候选名单。这是因为违约和总收入之间的关系比主成分的关系更容易理解和解释。最终，我们可以优先考虑某些特征，以提高我们模型的可解释性。如果你想了解为什么可解释性很重要，你可以阅读这篇文章：机器学习中的可解释性。\n法律与道德 某些类型的信息可能不适合用来建立模型。例如，为了避免贷款中的性别歧视， PERS65 （客户性别）的使用可能是非法的。即使法律目前没有涵盖这些类型的信息，你可能仍希望避免使用它们。它们可能会导致不道德的后果和公众的强烈反对。\n与直觉相反，你可能仍希望将这些特征纳入候选名单。这样才能进行算法公平性评估。如果不将性别作为特征，你就无法确定模型是否偏向某一性别。这些类型的特征称为受保护特征，不应用于构建模型。\n具体问题 最后要考虑的是，模型通常包含大量分析。有时，这些分析与模型构建没有直接关系，相反，我们想要回答具体的问题。例如，某些地区的客户违约率是否不同？或者，倾向于在线互动的客户的违约率是否高于访问分支机构的客户的违约率？将回答这些类型问题所需的任何功能纳入你的候选名单会很方便。\n特征选择框架 最后，所有上述考虑都需要以某种方式形式化。这就是特征选择框架的作用所在。它将概述选择特征的方法的技术细节，包括特征重要性的度量和变量聚类方法。它还可以定义处理其他考虑因素的方法（例如，需要进行哪些分析来确定特征是否稳定）。你可以在图 6 中看到此类框架的概述。\n这个框架可能会成为更大的建模框架的一部分。除了特征选择之外，它还将定义特征工程的方法和使用的模型类型。建模框架可以再次成为更大的治理框架的一部分。这就是负责任的人工智能框架。我们曾在文章《[[什么是负责任的人工智能]]》中讨论负责任的人工智能及其目标。\nAI 进阶：企业项目实战2\n参考 N. Siddiqi, Credit Risk Scorecards: Developing and Implementing Intelligent Credit Scoring (2006)\nJ. Brownlee, How to Choose a Feature Selection Method For Machine Learning (2020) https://machinelearningmastery.com/feature-selection-with-real-and-categorical-data/\nR Doucmentation, Variable Clustering (2021) http://math.furman.edu/~dcs/courses/math47/R/library/Hmisc/html/varclus.html\nOpen Risk Manual, Model Decay (2021) https://www.openriskmanual.org/wiki/Model_Decay\n希望这篇文章对你有所帮助！你还可以阅读我的其他文章，或者查看有关企业 AI 实战项目的教程，相信会让你拥有更多收获。\n「AI秘籍」系列课程：\n人工智能应用数学基础 人工智能Python基础 人工智能基础核心知识 人工智能BI核心知识 人工智能CV核心知识 VarClusHi, https://github.com/jingtt/varclushi\u0026#160;\u0026#x21a9;\u0026#xfe0e;\nAI 进阶：企业项目实战, https://www.sanjieke.cn/course/detail/sjk/8005780\u0026#160;\u0026#x21a9;\u0026#xfe0e;\n","permalink":"https://hivan.me/posts/%E8%89%AF%E5%A5%BD%E5%8A%9F%E8%83%BD%E7%9A%84%E7%89%B9%E5%BE%81/","summary":"\u003cblockquote\u003e\n\u003cp\u003e为什么选择模型特征时预测能力并不是最重要的\u003c/p\u003e\u003c/blockquote\u003e","title":"良好功能的特征"},{"content":"\n刚开始开车时，你的经验较少，有时还会比较鲁莽。随着年龄的增长，你会获得更多的经验（和意识），发生事故的可能性也会降低。然而，这种趋势不会永远持续下去。当你年老时，你的视力可能会下降，反应可能会变慢。现在，随着年龄的增长，你发生事故的可能性会更大。这意味着发生事故的概率与年龄呈非线性关系。找到并整合此类关系可以提高模型的准确性和解释性。\n在本文中，我们将深入探讨非线性关系。我们将探索如何使用散点图和部分依赖图 (PDP) 来可视化它们。然后，我们将继续介绍如何突出显示数据中的潜在非线性关系。这些包括特征重要性和相互信息等指标。您可以在 GitHub1 上找到用于此分析的 R 代码。在开始之前，有必要准确解释一下我们所说的非线性关系。\n什么是非线性关系？ 如果两个变量具有线性关系，我们可以用直线来概括这种关系。直线的斜率可以是正斜率也可以是负斜率，但斜率始终保持不变。您可以在图 1 中看到一个示例。在这种情况下，我们有一个正线性关系。另一种看待这个问题的方式是，无论 X 的起始值是多少，变量 X 的增加都会导致 Y 的相同增加。\n另一方面，对于非线性关系，变量 X 的变化导致变量 Y 的变化将取决于 X 的起始值。您可以在图 2 中看到一些这样的示例。上面给出的年龄-事故关系可能是二次的。也就是说，事故发生的概率会随着年龄的增长而降低，然后增加。最终，任何不能用直线概括的关系都是非线性关系。准确地说，这些关系还包括相互作用，但我们在另一篇文章中重点讨论这些类型的关系。\n非线性模型，如随机森林和神经网络，可以自动对上述非线性关系进行建模。如果我们想使用线性模型，如线性回归，我们首先必须进行一些特征工程。例如，我们可以将 $age^2$ 添加到我们的数据集中以捕获二次关系。为了进行更有效的特征工程，首先在我们的数据中找到这些关系会有所帮助。\n数据集 为了帮助解释如何找到这些关系，我们将使用随机生成的数据集。您可以在表 1 中看到特征列表，其中price是我们的目标变量。我们将尝试使用这 4 个特征来预测二手车的价格。数据集的设计使得 car_age、 repairs 与 price 具有非线性关系。而km_driven 具有线性关系，owner_age 没有关系。\n您可以在图 3 中的散点图中看到我们的意思。在这里我们可以看到具有非线性关系的两个特征。如果这是一个真实的数据集，我们会期待一些直观的原因。例如，汽车的价格随着年龄的增长而下降是有道理的，但为什么它随后开始上涨？也许大多数老爷车都是经典\\收藏车，因此价格会随着年龄的增长而上涨。\n我们可以对维修功能做出类似的描述。这是汽车接受保养或维修的次数。在汽车的整个使用寿命中，对汽车进行例行保养是正常的。因此，此功能的值较小可能表示汽车已被忽视。另一方面，值较大可能表示汽车在这些标准服务之外还需要额外的维修。这些汽车将来可能会给新车主带来更多问题。\n您可以在图 4 中看到其余的关系。如前所述，owner_age与price没有关系。我们可以在图表中看到这一点，因为这些点是随机分散的。我们还可以看到 km_driven与price 呈负线性关系。我们之所以包括这些，是因为将这些关系的分析与非线性关系的分析进行比较会很有用。\n散点图是一种可视化非线性关系的简单方法，但并不总是有效。对于每个图表，我们可视化的是目标变量与一个特征之间的关系。实际上，目标变量将与许多特征有关系。这和统计变化的存在意味着这些点将围绕潜在趋势展开。我们已经可以在上面的图表中看到这一点，而在真实的数据集中，情况会更糟。最终，为了清楚地看到关系，我们需要剔除其他特征和统计变化的影响。\n部分依赖图 (PDP) 这让我们想到了 PDP。要创建 PDP，我们首先必须将模型拟合到我们的数据中。具体来说，我们使用一个有 100 棵树的随机森林。在表 2 中，我们的数据集中有两行用于训练模型。在最后一列中，我们可以看到二手车的预测价格。这些是随机森林根据特征值做出的预测。\n要创建 PDP，我们首先要改变一个特征的值，同时保持其他特征不变。然后，我们绘制每个特征值的结果预测。查看图 5，这可能更有意义。这里我们取了表 2 中的两辆车。我们绘制了car_age的每个可能值的预测价格（部分 yhat） ，同时保持其他特征的原始值。（例如，repairs 费用将保持在 25 和 12）。两个黑点对应于表 2 中的实际预测（即它们的真实car_age）。\n我们对数据集中的每一行都遵循此过程。您可以在图 6 中看到所有这些单独的预测线。最后，为了创建 PDP，我们计算car_age 每个值的平均预测值。这由粗黄线显示。您现在可以清楚地看到非线性关系。也就是说，预测价格最初下降，但后来上升。同样，我们可以在图 7 中看到 repairs 的非线性关系。\n相比之下，我们可以在图 8 中看到 km_driven 的 PDP，在图 9 中看到 own_age 的 PDP。如前所述，km_driven 与 price 呈线性关系。我们可以在 PDP 中看到这一点，其中平均预测值呈线性下降。同样，它与 own_age 没有关系。这里平均预测值保持相当稳定。\n这些图表提供了更清晰的趋势可视化，原因有二。首先，通过保持其他特征值不变，我们可以专注于一个特征的趋势。这就是预测如何由于该特征的变化而变化。其次，随机森林将模拟数据中的潜在趋势并使用这些趋势进行预测。因此，在绘制预测时，我们能够消除统计变化的影响。\n充分利用您的 PDP 查看图 10，您可以了解用于创建这些 PDP 的随机森林的准确性。该模型并不完美，但它在预测汽车价格方面做得相当好。事实上，模型的准确性并不那么重要。目标是可视化非线性关系，而不是做出准确的预测。但是，您的模型越好，您的分析就越可靠。欠拟合模型可能无法捕捉到关系，而过度拟合模型可能会显示实际上不存在的关系。\n模型的选择也不是那么重要。这是因为 PDP 是一种模型无关的技术。在这个分析中，我们使用了随机森林，但您可以使用任何非线性模型，例如 XGBoost 或神经网络。根据您的数据集，不同的模型可能更擅长捕捉底层的非线性关系。\n寻找非线性关系 仅使用 PDP 可能不足以找到非线性关系。这是因为您的数据中可能有许多特征，而尝试分析所有 PDP 将非常耗时。我们需要一种缩小搜索范围的方法。也就是说，我们需要一个指标来告诉我们我们的特征和目标变量之间是否存在显著关系。然后我们可以专注于这些特征。在本文的其余部分，我们将探讨如何使用特征重要性或相互信息来做到这一点。\n为什么我们不能使用相关性 在深入研究这些指标之前，有必要讨论一下为什么相关性不合适。皮尔逊相关系数2是用于寻找显著关系的常用指标。然而，它是线性相关性的度量，这意味着它只能用于寻找线性关系。我们可以在图 11 中看到这一点，其中 km_driven 和 price 之间存在很大的负相关性。相比之下， car_age 的相关性要低得多。\n在某些情况下，线性趋势可能能够很好地近似非线性趋势。因此，即使对于非线性关系，我们仍可能会看到一些高相关值。我们可以在特征 repairs 中看到这一点，其中仍然存在相当大的负相关性。一般来说，这个指标不会帮助我们识别非线性关系。\n相互信息 互信息3量度了一个变量的不确定性通过观察另一个变量而减少的程度。它通过将变量的联合分布与边际分布的乘积进行比较来实现这一点。如下所示，独立变量的联合分布将等于它们的边际分布的乘积。因此，如果联合分布不同，则表明存在依赖关系，我们将计算更高的互信息值。最终，依赖关系意味着两个变量之间存在关系。\n对于独立变量：$f(x,y) = f(x)f(y)$\n两个变量之间存在依赖关系，关系不一定是线性的。这意味着可以使用该指标来突出显示非线性关系。请参见图 13 中的价格与我们的 4 个特征之间的相互信息值。与相关性相比，我们现在可以看到与目标变量有关系的所有特征的值都更高。\n在本分析中，我们仅研究了连续变量。互信息也可用于离散变量。即当一个变量是离散的而另一个变量是连续的，或者当两个变量都是离散的时。这是相关性的另一个优势，因为相关性只能用于连续变量。\n特征重要性 另一种方法是先训练一个模型，然后使用该模型的特征重要性分数。特征重要性衡量了某个特定特征对模型准确性的提高程度。在图 12 中，您可以看到从我们用于创建 PDP 的同一随机森林中获得的分数。具体来说，我们使用 MSE 的百分比增加作为特征重要性的衡量标准。\n与 PDP 一样，只要是非线性的，我们就可以对此方法使用任何模型。我们不能使用线性模型（如线性回归），因为它们无法对非线性关系进行建模。换句话说，具有非线性关系的特征可能无法提高准确率，从而导致特征重要性得分较低。\n最后，我们可以使用互信息和特征重要性来强调非线性（和线性）关系。但是，这些指标并没有告诉我们这些关系的性质。即关系是二次的、指数的、逐步的等等……因此，一旦我们强调了这些潜在关系，我们就必须回到 PDP 来确定它们的性质。\n如上所述，交互作用是一种特殊类型的非线性关系。当目标变量和特征之间的关系取决于另一个特征的值时，就会发生这种情况。我们在文章《寻找并可视化交互》中以类似的方式分析这些类型的关系。\nAI 进阶：企业项目实战4\n参考 C. Molnar, Interpretable Machine Learning(2021) https://christophm.github.io/interpretable-ml-book/interaction.html\n「AI秘籍」系列课程：\n人工智能应用数学基础 人工智能Python基础 人工智能基础核心知识 人工智能BI核心知识 人工智能CV核心知识 Github, https://github.com/hivandu/public_articles\u0026#160;\u0026#x21a9;\u0026#xfe0e;\n皮尔逊相关系数, https://en.wikipedia.org/wiki/Pearson_correlation_coefficient\u0026#160;\u0026#x21a9;\u0026#xfe0e;\n互信息, https://en.wikipedia.org/wiki/Mutual_information\u0026#160;\u0026#x21a9;\u0026#xfe0e;\nAI 进阶：企业项目实战, https://www.sanjieke.cn/course/detail/sjk/8005780\u0026#160;\u0026#x21a9;\u0026#xfe0e;\n","permalink":"https://hivan.me/posts/%E6%9F%A5%E6%89%BE%E5%B9%B6%E5%8F%AF%E8%A7%86%E5%8C%96%E9%9D%9E%E7%BA%BF%E6%80%A7%E5%85%B3%E7%B3%BB/","summary":"\u003cblockquote\u003e\n\u003cp\u003e使用部分依赖图 (PDP)、互信息和特征重要性分析非线性关系\u003c/p\u003e\u003c/blockquote\u003e","title":"查找并可视化非线性关系"},{"content":"对于某些人来说，“人工智能”一词可以激发人们对进步和生产力的思考。但对于其他人来说，前景并不乐观。许多担忧都是合理的，例如不公平的决策、工人被取代以及缺乏隐私和安全。更糟糕的是，许多这些问题都是人工智能所独有的。这意味着现有的指导方针和法律不适合解决这些问题。这就是负责任的人工智能的用武之地。它旨在解决这些问题并为人工智能系统建立问责制。\n为什么我们需要负责任的人工智能 当我们谈论人工智能时，我们通常指的是系统内用于自动化某些事情的机器学习模型。例如，自动驾驶汽车可以从传感器拍摄图像。机器学习模型可以使用这些图像进行预测（例如，我们面前的物体是一棵树）。汽车会根据这些预测做出决策（例如，左转以避开树木）。我们将整个系统称为人工智能。\n这只是一个例子。人工智能可以用于从保险承保到癌症检测等任何领域。其决定性特征是系统做出的决策几乎没有人为干预。这可能导致许多潜在问题，公司需要定义使用人工智能的明确方法。负责任的人工智能是一个治理框架，旨在实现这一点。\n该框架可以包括有关可以收集和使用哪些数据、如何评估模型以及如何最好地部署和监控模型的详细信息。该框架还可以定义谁对人工智能的任何负面结果负责。不同公司的框架会有所不同。有些会定义具体的方法，而另一些则更容易被解释。他们都在寻求实现同一件事。那就是创建可解释、公平、安全且尊重用户隐私的人工智能系统。\n负责任的人工智能的目标 提到的第一个领域是可解释性。当我们解释一个模型时，我们会得到它如何进行预测的解释。人工智能系统可能会拒绝你的抵押贷款申请或诊断你患有癌症。即使这些决定是正确的，用户也可能会要求解释。有些模型比其他模型更容易解释，这使得获得解释更容易。负责任的人工智能可以定义我们如何构建可解释的模型，或者何时可以使用不太可解释的模型。\n与可解释性相关的是模型公平性 。人工智能系统可能会做出歧视某些人群的决策。这种偏见来自用于训练模型的数据中的偏见。一般来说，模型的可解释性越高，就越容易确保公平性并纠正任何偏见。我们仍然需要一个负责任的人工智能框架来定义我们如何评估公平性，以及当发现模型做出不公平的预测时该怎么做。这在使用可解释性较差的模型时尤其重要。\n安全和保障是另一个令人担忧的问题。这些问题对软件开发来说并不新鲜，可以通过加密和软件测试等技术来解决。不同之处在于，与一般的计算机系统不同，人工智能系统不是确定性的。当面对新的场景时，它们可能会做出意想不到的决定。系统甚至可以被操纵做出错误的决定。当我们与机器人打交道时，这一点尤其令人担忧。如果它们犯了错误，自动驾驶汽车等可能会造成伤亡。\n最后一个方面是隐私和数据治理。所用数据的质量很重要。如果人工智能使用的数据有误，那么系统可能会做出错误的决定。一般来说，人工智能也不应该被允许使用敏感数据（例如病史、工会会员资格）。在欧洲，许多这些问题都由[GDPR]1解决。在欧洲以外，这些问题需要由公司自己的负责任的人工智能框架来解决。\n归根结底，这一切都归结为信任。如果用户不信任人工智能，他们就不会使用你的服务。我们不会信任那些使用我们不愿意分享的信息或我们认为它会做出有偏见决定的系统。如果我们认为它会对我们造成身体伤害，我们当然不会信任它。对决策的解释和对这些决策的责任对于建立这种信任大有裨益。这种对信任的需求正是推动使用人工智能的公司自我监管的动力。\n负责任的人工智能的未来 目前，在人工智能方面，企业需要自我监管。这意味着他们必须制定并实施自己的负责任人工智能准则。谷歌、微软和 IBM 等公司都有自己的准则。但问题在于，负责任人工智能的原则在不同行业中的应用可能不一致。规模较小的公司甚至可能没有资源来制定自己的准则。\n一个潜在的解决方案是让所有公司采用相同的准则。例如，欧盟委员会最近发布了《值得信赖的人工智能道德准则》。该准则详细说明了人工智能应具备的 7 个关键要求，才能被视为值得信赖。使用这些准则将有助于公司确保其人工智能系统符合相同的标准。真正的问题是——我们能相信公司会自我监管吗？\n人工智能和机器学习现状报告涵盖了 374 家从事数据/人工智能工作的组织的回复。75% 的组织表示人工智能是其业务的关键部分。然而，只有 25% 的组织表示公平的人工智能很重要。这表明答案是否定的；不，我们不能相信它们。要使通用准则有效，还必须执行它们。换句话说，准则必须成为法律/法规，不遵守准则的公司必须面临处罚。\n“我毫不怀疑人工智能需要受到监管。问题是如何最好地解决这个问题，”—— Sundar Pichai（谷歌首席执行官）\n这似乎确实是我们前进的方向。欧洲提出过相关法规。它们基于上述道德准则，将影响许多行业。目前，美国还没有这样的法规。不过，谷歌、Facebook、微软和苹果等科技公司的高管都呼吁对数据和人工智能进行更多监管。所以这似乎只是时间问题。\n我们提到了算法公平性。它是负责任的人工智能的重要组成部分，很可能成为许多人工智能监管的重点。这本身就是一个非常有趣的话题，我们在[什么是算法公平性]文章中有过更深入地探讨。\n负责任的 AI 框架示例 [谷歌]2|[微软]3| [IBM]4|[欧盟委员会]5\n其他参考 D. Pessach \u0026amp; E. Shmueli, Algorithmic Fairness (2020), https://arxiv.org/abs/2001.09784\nA. Gillis, responsible AI (2021), https://searchenterpriseai.techtarget.com/definition/responsible-AI\nAccenture, Responsible AI: A Framework for Building Trust in your AI Solutions (2018), https://www.accenture.com/_acnmedia/PDF-92/Accenture-AFS-Responsible-AI.pdf\nAppen, The 2020 State of AI and Machine Learning Report (2020), https://appen.com/whitepapers/the-state-of-ai-and-machine-learning-report/\nK. Walch, AI Laws Are Coming (2020), https://www.forbes.com/sites/cognitiveworld/2020/02/20/ai-laws-are-coming/?sh=5b526ef7a2b4\nS. Schechner \u0026amp; V. Pop, Google Pushes ‘Sensible’ Ideas for How to Regulate AI (2020), https://www.wsj.com/articles/google-pushes-sensible-ideas-for-how-to-regulate-ai-11579521003?mod=article_inline\nA. Satariano, Europe Proposes Strict Rules for Artificial Intelligence (2021), https://www.nytimes.com/2021/04/16/business/artificial-intelligence-regulation.html?smtyp=cur\u0026amp;smid=tw-nytimes\n「AI秘籍」系列课程：\n人工智能应用数学基础 人工智能Python基础 人工智能基础核心知识 人工智能BI核心知识 人工智能CV核心知识 AI 进阶：企业项目实战 GDPR: https://gdpr.eu/\u0026#160;\u0026#x21a9;\u0026#xfe0e;\n谷歌: https://ai.google/responsibilities/responsible-ai-practices/\u0026#160;\u0026#x21a9;\u0026#xfe0e;\n微软: https://www.microsoft.com/en-us/ai/responsible-ai?activetab=pivot1%3Aprimaryr6\u0026#160;\u0026#x21a9;\u0026#xfe0e;\nIBM: https://www.ibm.com/artificial-intelligence/ethics\u0026#160;\u0026#x21a9;\u0026#xfe0e;\n欧盟委员会: https://digital-strategy.ec.europa.eu/en/library/ethics-guidelines-trustworthy-ai\u0026#160;\u0026#x21a9;\u0026#xfe0e;\n","permalink":"https://hivan.me/posts/%E4%BB%80%E4%B9%88%E6%98%AF%E8%B4%9F%E8%B4%A3%E4%BB%BB%E7%9A%84%E4%BA%BA%E5%B7%A5%E6%99%BA%E8%83%BD/","summary":"\u003cblockquote\u003e\n\u003cp\u003e拥有权利的同时也被赋予了重大的责任\u003c/p\u003e\u003c/blockquote\u003e","title":"什么是负责任的人工智能"},{"content":"如果你曾经申请过技术职位，你可能已经向公司发送了你的 GitHub 个人资料链接。此个人资料中的信息可以很好地表明你的编码能力以及是否适合团队。所有这些信息的缺点是招聘人员可能需要很长时间才能评估它。为了节省时间，机器学习可能用于自动评估你的编码能力。\n在本文中，我们将引导你完成构建此类模型的过程。我们讨论了如何从 GitHub 收集数据并使用这些数据创建模型特征。为了建立一些直觉，我们探索了数据集中的关系。最后，我们比较和解释了两种 ML 算法——决策树和随机森林。你可以在「GitHub」1上找到此分析的代码。\n数据收集和特征工程 从你的代码存储库到你关注的其他程序员，你的 GitHub 个人资料中有很多公开可用的信息。我们构建了一个网络抓取工具，从 230 位用户的个人资料中收集了部分数据。例如，你将在任何用户的概览页面上看到与图 1 类似的贡献列表。当用户编写新代码并将其保存到他们的代码存储库 (repos) 之一时，他们就做出了贡献。\n通过抓取这些数据，我们最终得到了去年每天的贡献数量列表。为了能够在模型中使用这些数据，我们首先需要进行一些特征工程。我们可以通过几种方式做到这一点。例如，我们可以计算去年的贡献总数（n_cont）。我们还可以计算自上次贡献以来的天数（last_cont）。这些贡献只是我们可以用来创建模型特征的 GitHub 个人资料的一个方面。\n图 2 显示了用户侧边栏中的一些信息。从中，我们可以获取关注者的数量、关注数以及用户已加星标的存储库数量（stars）。这些已经是数字，因此无需进行特征工程。最后，我们还根据组织信息 ( org_flag ) 创建一个标志，该标志将指示用户是否属于某个组织。\n最后一组特征来自用户的存储库。在图 3 中，我们可以看到一些代码存储库的示例。我们收集了用户创建的存储库总数 ( repos )。我们还收集了每个存储库中使用的编码语言。由此，我们计算出所有用户存储库中使用的不同编码语言的数量 ( n_lang )。\n表 1 总结了创建的所有模型特征。除了stab_cont、foll_ratio和cont_repo_ratio，我们在上面讨论了所有这些特征。还有其他信息/特征来源可以创建。我们将自己限制在这一组，因为它们相当简单，数据很容易抓取。希望这些特征至少与编码能力相关，使我们能够做出准确的预测。具体来说，我们将尝试使用它们来预测 0 到 5 之间的评级。\n此评分是通过取两个独立评分的平均值得出的。重要的是，评分者不受上述功能中包含的信息的限制。例如，评分者可以打开存储库中的文件并阅读实际编写的代码。在浏览个人资料后，每个评分者都会根据用户的编码能力给出评分，从 0 到 5（5 为最佳）。两个评分的平均值作为最终评分/目标变量。\n数据探索 在深入建模之前，探索模型特征是个好主意。我们这样做是为了建立直觉并了解哪些特征能够很好地预测评分。图 4 中可以看到一个潜在的良好预测器示例。在这里，我们可视化了评分和n_cont之间的关系。请注意，随着用户的总贡献增加，用户的评分也趋于增加。\n此时，你应该问问自己，这种关系是否合理。没有或很少做出贡献的用户在过去一年中不会做太多编码。做出大量贡献的用户会做很多编码。这似乎合乎逻辑——你编码越多，你的编码能力就越强。我们的模型有望利用这种关系进行预测。\n另一方面，你可以看到评级与加星标的存储库数量（Stars）之间的关系。在这种情况下，似乎没有任何模式。也许这是因为你不必进行任何编码即可为其他用户的存储库加星标。也就是说，星星不会告诉我们你的编码能力。从这个分析来看，我们不应该期望星星成为我们模型中的一个重要特征。\n另一个可能很重要的特征是组织标志。我们可以预期，属于某个组织的用户会成为更好的程序员。特别是，如果他们属于像谷歌开源社区2这样的组织。在下面的箱线图中，我们可以看到，属于某个组织的用户的评分确实往往更高。没有组织的平均评分为 1.8，而用户属于某个组织时的平均评分为 3.4。这种差异表明org_flag可能是一个很好的预测指标。\n我们应该记住，这些特征并不是孤立存在的。看起来具有预测性的特征，当它们与其他特征一起包含在模型中时，可能并不那么重要。这是由于特征本身之间的关系。例如，在图 7 中，我们可以看到贡献更多的用户更有可能成为某个组织的一部分。具体来说，贡献 500 次或更多的用户中有 70% 是某个组织的成员。而贡献少于 500 次的用户中，这一比例仅为 20%。\n假设我们只使用org_flag 创建一个模型。从上面的分析来看，组织中的人员可能会获得更高的预测评级。我们可以通过在模型中添加n_cont 来改善这些预测。同样，贡献较大的人员可能会获得更高的评级。但是，许多贡献较大的用户都是组织的一部分。这意味着他们可能已经获得了高评级，因此包括n_cont可能不会像我们想象的那样改善这些预测。最终，n_cont的重要性实际上取决于它比org_flag提供的信息多多少。\n决策树 考虑到我们的探索性数据分析，现在是时候进行建模了。我们将从创建一个简单的决策树开始。为此，我们首先拆分数据集并使用 200 行训练决策树。我们将剩余的 30 行保留为测试集。决策树是使用基尼不纯度测量创建的，以确定每个节点的最佳分割。我们还限制树在每个叶节点中至少有 10 个样本。你可以在图 9 中看到整个决策树。\n要了解这棵树的工作原理，让我们从根节点开始。如果用户满足第一行中的规则（即至少做出了 105 次贡献），我们就会沿着树的右箭头向下移动。如果不满足规则，我们就会沿着左箭头向下移动。我们继续检查规则并沿着箭头移动，直到到达叶节点。例如，如果用户做出了 900 次贡献并拥有 90 名关注者，我们最终会到达用红色圈出的叶节点。17 位用户最终进入了这个桶，他们的评分为 4.603。\n下一步是使用此模型对测试集进行预测。为了评估此模型，我们应该将实际评分与这些预测进行比较。一种方法是使用图 8 中的散点图。如果模型完美，则所有点都会落在红线上（即实际 = 预测）。黑色虚线距离红线一个单位。这意味着与实际评分相比，只有 2 个预测的差异大于 1。总体而言，该模型在测试集上的 MSE 为 0.3077。\n随机森林 决策树通常被认为是一个简单的 ML 模型。让我们看看是否可以使用更复杂的方法——随机森林来提高这种准确性。具体来说，我们使用一个有 100 棵树的随机森林。每棵树的最大深度限制为 4。与决策树一样，你可以在图 10 中看到实际评分与预测评分的图。\n我们再次发现 2 个预测的误差超过 1 个单位。就 MSE 而言，似乎确实有所改善。测试集上的 MSE 为 0.2691，比决策树低 12.5%。在决定使用哪种最终模型时，应权衡准确度的提高与使用更复杂模型的缺点。\n其中一个缺点是，我们无法像解释决策树那样解释随机森林。换句话说，我们无法像图 9 那样简单地可视化整个模型。这意味着我们对模型如何进行预测的理解会更差。要解释模型，我们必须使用额外的技术，例如特征重要性分数。\n在图 11 中，我们可以看到随机森林的特征重要性得分。一般来说，特征重要性越高，该特征在进行预测时就越有用。我们可以看到，n_cont是迄今为止最有用的，其次是repos和followers。我们可以看到stars不是很重要。这与我们的探索性分析一致。不一致的是org_flag是最不重要的。当与其他特征一起包含时，似乎这个特征并不像我们预期的那么重要。\n思考和未来工作 任何模型都有一些需要解决的弱点。我们应该尝试理解的一个方面是，为什么n_cont比其他任何特征都重要得多。查看图 12，当你第一次打开某人的个人资料时，贡献会立即引起你的注意。这可能会对评分者的决策过程产生影响。换句话说，他们可能比他们想要的更依赖贡献。\n评分也可能以其他方式产生偏差。例如，如果评分者考虑用户的个人资料图片，则可能会引入种族/性别偏见。为了解决这个问题，我们需要提出一个客观的评分系统。它应该只考虑与编码能力直接相关的信息，并且信息应该更统一地呈现。\n实际上，不同公司或团队的评级系统可能有所不同。对于给定的用户，数据工程团队可以给数据科学团队完全不同的评级。这是因为每个团队所需的技能不同。换句话说，评级将反映用户在特定团队中的表现，而不是他们总体上的编码能力。\n围绕特征工程还有很多工作可以做。到目前为止，我们使用的特征实际上只是与编码能力有关。做出很多贡献并不能让你成为一名优秀的程序员。只是优秀的程序员往往会做出很多贡献。我们应该尝试从实际编写的代码中创建特征。例如，我们可以确定用户是否使用了单元测试、使用了一致的命名约定或是否正确注释了他们的代码。\n总体而言，似乎可以使用机器学习来预测编码能力。使用一种相当简单的方法，我们可以做出与实际评分一致的预测。希望这将为未来更好的模型奠定基础。\n我们提到了性别/种族偏见。机器学习中的这种偏见比许多人意识到的更常见。一个例子是亚马逊使用的系统没有以性别中立的方式对求职者的简历进行评级。我们在文章《[[什么是算法公平性]]》中讨论这个问题以及其他一些有偏见的算法：\n旨在理解和防止机器学习模型偏见的领域的介绍\nAI 进阶：企业项目实战3\n参考 「AI秘籍」系列课程：\n人工智能应用数学基础 人工智能Python基础 人工智能基础核心知识 人工智能BI核心知识 人工智能CV核心知识 Github, https://github.com/hivandu/public_articles/tree/main/src/git-eval\u0026#160;\u0026#x21a9;\u0026#xfe0e;\nGoogle Github, https://github.com/google\u0026#160;\u0026#x21a9;\u0026#xfe0e;\nAI 进阶：企业项目实战, https://www.sanjieke.cn/course/detail/sjk/8005780\u0026#160;\u0026#x21a9;\u0026#xfe0e;\n","permalink":"https://hivan.me/posts/giteval--%E9%A2%84%E6%B5%8B%E4%BD%A0%E7%9A%84-github-%E4%B8%AA%E4%BA%BA%E8%B5%84%E6%96%99%E7%9A%84%E8%B4%A8%E9%87%8F/","summary":"\u003cblockquote\u003e\n\u003cp\u003e使用机器学习来预测你是否擅长编码\u003c/p\u003e\u003c/blockquote\u003e","title":"GitEval — 预测你的 GitHub 个人资料的质量"},{"content":"「AI秘籍」系列课程：\n人工智能应用数学基础 人工智能Python基础 人工智能基础核心知识 人工智能BI核心知识 人工智能CV核心知识 AI 进阶：企业项目实战 自我发布 AI 秘籍预告以来，到今天为止正好一周年了。期间已经写了四个系列，从数学、Python，到机器学习和神经网络，还包括 BI 和 CV 部分，当然 CV 部分是才开始写。说实话，我自己都没有想到自己会坚持写下来，在年前大部分时候都保持着一天一篇的更新量，后来事情逐渐多了起来发布数量也就慢慢讲了下来。\n当然，AI 秘籍最初的设定也只是基础部分而已，在此之外我已经录制并计划更多的实战教程。除了 AI 秘籍之外，还顺带着写了一些其他的文章。\n不幸的是，虽然一段时间内整个数据确实有稳步上升的趋势，可是不知道为什么，从过年前后开始，整体数据开始下降。即便如此，我仍然坚持写作和发表文章，这是因为我不仅受到阅读量的激励，还收到写作的许多其他好处的激励。\n学习并提高技术技能 第一个好处是它帮助我更多地了解数据科学。离开大学后，你会失去所有的结构，你必须自己掌握你的教育。写文章是我这样做的方式。无论我想学习一些新理论还是如何应用 Python 框架，我都会把文章作为一种目标。它在我学习新概念时给了我一些急需的方向。\n而且，我的意思是真正地学习！阅读和应用概念是一回事，解释概念又是另一回事。当你试图把你的想法写出来时，你会意识到你理解中的所有漏洞。我经常发现我必须回过头来尝试正确理解一个概念，然后才能把它写出来。最终，文章既为学习提供了方向，也检查了我的理解。\n更何况，许多实操类的技能，往往只有在操作的时候你才会发觉原来有许多的改善空间。\n提高沟通技巧 踏入职场后，你首先会意识到的一件事就是沟通技巧非常重要。但在学位课程中，沟通技巧常常被忽视。你可能学会了编程，但不知道该如何解释代码的工作原理。实际上，你必须一直这样做。更糟糕的是，你试图向其解释的人通常没有技术背景。展示代码或给出数学函数并不能解决问题。\n这就是为什么我会从技术岗转为产品岗的原因，我是有意锻炼自己的沟通技能和逻辑展示。再加上长期技术岗位固有的逻辑能力，我觉得会在自己沟通上事半功倍。\n同样，当我写作时，我希望接触到尽可能多的读者。这些人不一定是机器学习专家，但他们仍在学习。无论是使用可视化还是书面解释，我都必须找到直观的方式来解释事物。这对我在日常工作中解释技术概念来说是一个很好的练习。我真的认为这些改进的沟通技巧比我获得的任何技术技能都更有价值。\n沟通与技术技能的交汇点在于编写清晰、可读的代码。编写有效的代码是一回事，编写人类可以理解的代码则是另一回事。我的一些文章是编码教程。我付出了很多努力让代码易于理解，而且通过每个教程，我感觉自己有所进步。同样，这对我的日常工作很有帮助。在团队中工作时，解释清楚的代码将为您节省很多精力。\n互联网 我不仅在公众号上发布文章，还在 CSDN 、微博以及知乎上分享，并且也尝试了各种不同的其他渠道。慢慢地积累了一批粉丝，并与同样对数据科学感兴趣的人建立了联系。这已经带来了一些机会。就我的写作而言，他们给了我一些宝贵的反馈，我经常能从中获得新文章的好点子。例如，很多人会在我的后台留言询问和请求帮助。所以，我写了一篇[关于情绪分析的教程](../使用 Python 创建你的第一个情绪分析模型)。\n声誉 写作可以让你脱颖而出。通过撰写有关某个主题的文章，你可以将自己确立为专家。文章可以证明你了解某件事，并能解释它。这些技术和沟通技巧会给你的简历增色不少。再加上上面提到的更大的人脉，可以帮助你打开许多机会。\n赚钱 更多的机会可以带来更高薪的工作，但也可以带来直接的经济利益。说实话，国内的圈子氛围并不好，特别是对技术人员。很多干货没办法让更多人看到，反而是一些感觉不知所谓的帖子更受欢迎。而且写作这件事，说实话并不赚钱，至少我没赚到。曾经很想在 Medium 上写作，不过资格申请这道坎就已经拦住了我。不过 Medium 的被动收入模式真的是让人羡慕，起码在国内我还真没看到这样一个对技术人员有好的渠道。\n创意 到目前为止，上述好处可能并不那么令人惊讶。有一件事我没想到，那就是我真的很喜欢写作。一开始，这很难，我的文字也显得笨拙。随着练习，我发现我的想法更容易流到指尖。我觉得我还有很长的路要走，但我已经发现表达自己更容易了。将来，我想写一些我感兴趣的其他东西。也许甚至是一些小说。\n回想起来，我很高兴自己开始写作。我从中学到了很多东西，我觉得这让我成为了一名更全面的人工智能从业者。重读我的第一篇文章，我觉得去年我进步了很多。希望一年后我也能有同样的感觉。如果你对那篇文章感兴趣，可以阅读一下，也是最初对 AI 秘籍的一个整体规划「《AI 秘籍预告》」。\n大家还可以看看这个系列的其他相关文章，从最基础的 Python 开始，如果你能耐心将其看完，相信你一定会有所收获：\n「AI秘籍」系列课程：\n人工智能应用数学基础 人工智能Python基础 人工智能基础核心知识 人工智能BI核心知识 人工智能CV核心知识 以及最新录制的 AI 企业项目实战教程系列之一\nAI 进阶：企业项目实战\n","permalink":"https://hivan.me/posts/%E6%88%91%E4%B8%BA%E4%BD%95%E6%92%B0%E5%86%99%E6%9C%89%E5%85%B3%E4%BA%BA%E5%B7%A5%E6%99%BA%E8%83%BD%E5%92%8C%E6%95%B0%E6%8D%AE%E7%A7%91%E5%AD%A6%E7%9A%84%E6%96%87%E7%AB%A0/","summary":"\u003cblockquote\u003e\n\u003cp\u003e撰写人工智能文章的 6 大好处\u003c/p\u003e\u003c/blockquote\u003e","title":"我为何撰写有关人工智能和数据科学的文章"},{"content":"「AI秘籍」系列课程：\n人工智能应用数学基础 人工智能Python基础 人工智能基础核心知识 人工智能BI核心知识 人工智能CV核心知识 AI 进阶：企业项目实战 可直接在橱窗里购买，或者到文末领取优惠后购买：\n一位[不可知论者]1思考上帝的存在。值得庆幸的是，机器学习还没有达到那种程度。当我们谈论不可知论方法时，我们指的是可以用于任何模型的方法。我们将详细阐述这一定义，重点放在可解释性的不可知论方法上。我们将讨论这些可解释性方法的不同分类。即全局解释与局部解释以及排列与替代模型。有些方法不是与模型无关的。我们将以讨论这些方法作为结束。\n模型无关方法 模型无关方法可以应用于任何模型。使用模型无关方法时，模型训练完成后，可以将其视为黑盒。换句话说，该方法不需要我们研究模型的内部工作原理。如果我们想在其他模型上使用该方法，我们只需将它们换掉即可。但是方法要做什么呢？\n首先，大多数评估指标与模型无关。以图 1 中的准确度计算为例。我们首先使用模型对测试集进行预测。准确度是正确预测的实际值的百分比。要进行此计算，我们只需将测试数据与预测值进行比较。有些模型可能比其他模型更准确，但计算结果相同。其他评估指标（如准确率、召回率和 MSE）都与模型无关。\n模型无关评估方法在模型选择方面提供了灵活性。你不必为每种模型类型开发不同的评估框架。这还允许你使用相同的指标比较许多模型。当你想要比较模型性能时，这种一致性至关重要。\n可解释的机器学习 在实践中，将评估指标称为模型不可知论并不常见。当我们谈论可解释的 ML 或可解释的 AI 方法时，这个术语最为突出。这些方法用于了解模型如何进行预测。我们将讨论这些方法的一些示例以及如何对它们进行分类/分组。在本节结束时，我们将讨论一些方法在理论上是不可知论的，但在实践中并非总是如此。\n全局方法与本地方法 我们对模型不可知论方法进行分类的第一种方法是根据它们试图解释的内容。局部解释旨在了解个体预测是如何做出的。这就是每个模型特征如何改变预测。我们可以使用全局解释来解释模型如何整体地进行预测。这意味着我们只能对所有预测的趋势做出断言。\n为了更好地理解这一点，我们在图 2 中提供了一个部分依赖图 (PDP) 的示例。这是一种常见的全局可解释性方法。此图是用于预测二手车价格的模型创建的。在 x 轴上，我们列出了对汽车进行的维修次数。y 轴（部分 yhat）给出了平均预测价格。由此我们可以看出，汽车价格往往与维修次数呈非线性关系。但是，我们不能对每辆汽车都说同样的话。\n我们不会详细介绍如何创建这个 PDP。如果你感兴趣，以后我会专门写一篇相关文章《查找并可视化非线性关系》2，文中我打算逐步指导你如何创建图表。并且还会讨论其他全局模型无关方法。那就是相互信息和特征重要性。\n使用部分依赖图 (PDP)、互信息和特征重要性分析非线性关系\n对于局部解释，SHAP 是一种常用方法。在图 3 中，你可以看到单个预测的 SHAP 瀑布图。这是来自用于预测鲍鱼壳环数的模型。在这里，我们将模型的预测 f(x) 与平均预测 E[f(x)] 进行比较。我们可以看到每个特征对预测的贡献。例如，去壳重量的值使预测的环数增加了 1.81。\n因此，我们可以看到 SHAP 是如何用来解释单个预测的。我们还可以汇总 SHAP 值，以了解模型如何整体地进行预测。从这个意义上讲，局部解释和全局解释之间的界限可能会变得模糊。你可以在图 4 中的蜂群图中看到一个示例。在这里，我们绘制了模型做出的所有预测的 SHAP 值。\n蜂群图只是我们聚合 SHAP 值的方法之一。\n排列模型与替代模型 我们可以对解释方法进行分类的第二种方法是根据它们的计算方式。置换方法涉及更改输入数据并测量模型预测的变化。这就是上面的 PDP 的创建方式。对于每辆汽车，我们更改/置换维修特征值并记录模型预测。然后我们取每个维修值的平均预测。其他方法，如 ICE 图、特征重要性和 SHAP 都是置换方法。\n另一种方法是使用替代模型，你可以在图 5 中看到概述。在这里，首先训练一个模型并用于进行预测。然后使用原始模型的预测来训练替代模型。这代替了目标变量。重要的是，替代模型是一种固有可解释的模型。这些模型，如线性回归或决策树，不需要 SHAP/PDP 之类的技术。可以通过直接查看模型的结构或参数来解释它们。\n最终，我们可以通过直接解释替代模型来了解原始模型的预测。如果我们对所有预测都这样做，它就被称为全局替代模型。与其他全局方法一样，这些方法可以帮助我们整体理解原始模型。其他方法，如 LIME，可用于创建局部替代模型。它们结合了排列和替代模型来训练单个预测的模型。\n原则上与实践上的模型无关 我们讨论的所有方法在理论上都是与模型无关的。要实际使用它们，我们需要实现它们。实现不一定支持所有建模包。例如，Friedman 的 h 统计量是一种用于突出显示模型中重要交互的方法。Python 中没有此方法的与模型无关的实现。据我所知，只有一个针对 scikit-learn 梯度提升模型的实现3。\n说到 SHAP，另一个需要考虑的问题是，存在不同的近似值方法。KernelSHAP 是一种真正的模型无关方法。但是，它比 TreeSHAP 慢得多。缺点是 TreeSHAP 只能与基于树的算法一起使用。这意味着，如果你想节省时间，你需要将自己限制在这些算法上。\n使用 SHAP，还可以扩展该方法。也就是说，特征的贡献可以分解为其主要效应和交互效应。这使我们能够分析数据中的交互作用。但是，此方法的实现仅适用于基于树的算法。这是因为我们上面提到的计算成本。KernelSHAP 需要很长时间才能近似主要效应和交互效应。\n非不可知论方法 不可知论方法的好处在于它们很常见。但仍有一些方法只能用于特定模型。首先，我们上面提到了固有可解释的模型。这些可以直接解释。然而，解释它们的确切方式将取决于你使用的模型。\n我们在以前的文章《使用 Python 创建你的第一个情绪分析模型》中有一个示例。我们在这里建立了一个模型来预测推文的情绪。这是使用词袋方法和支持向量机 (SVM) 完成的。在训练 SVM 的过程中，训练集中的每个 N-gram 都被赋予权重。我们通过查看这些权重来解释 SVM。\n你可以在图 6 中看到这些权重的一些示例。具有正权重的 N-gram 与积极情绪相关。换句话说，如果推文包含其中一个词，我们更有可能预测积极情绪。同样，具有负权重的词与消极情绪相关。也存在没有相关情绪的词的情况。\n以这种方式解释 SVM 是一种非不可知论方法。我们无法以这种方式解释决策树或随机森林等模型。这是因为它们没有参数权重。同样，它不适用于神经网络。这些模型可能有参数权重，但它们太复杂了，无法以这种方式可视化。\n对于本质上不可解释的模型，还开发了非不可知方法。由于深度学习的成功，这在神经网络中最为常见。其中一些方法包括逐像素分解和deepLIFT4。最终，这些方法只能用于神经网络。在某些情况下，它们只能用于特定的神经网络架构。\n希望这篇文章对你有所帮助！你还可以阅读我的其他文章，或者查看有关企业 AI 实战项目的教程，相信会让你拥有更多收获。\n参考 M.T. Ribeiro, S. Singh, and C. Guestrin, Model-agnostic interpretability of machine learning (2016) https://arxiv.org/abs/1606.05386\nC. Molnar, Interpretable Machine Learning (2021) https://christophm.github.io/interpretable-ml-book/shap.html\nHennie de Harder Model-Agnostic Methods for Interpreting any Machine Learning Model (2020) https://towardsdatascience.com/model-agnostic-methods-for-interpreting-any-machine-learning-model-4f10787ef504\n不可知论，维基百科, https://zh.wikipedia.org/wiki/%E4%B8%8D%E5%8F%AF%E7%9F%A5%E8%AE%BA\u0026#160;\u0026#x21a9;\u0026#xfe0e;\n《查找并可视化非线性关系》，日后计划\u0026#160;\u0026#x21a9;\u0026#xfe0e;\n梯度提升模型的实现, https://pypi.org/project/sklearn-gbmi/\u0026#160;\u0026#x21a9;\u0026#xfe0e;\nDeepLIFT, https://github.com/kundajelab/deeplift\u0026#160;\u0026#x21a9;\u0026#xfe0e;\n","permalink":"https://hivan.me/posts/%E4%BB%80%E4%B9%88%E6%98%AF%E6%A8%A1%E5%9E%8B%E6%97%A0%E5%85%B3%E6%96%B9%E6%B3%95/","summary":"\u003cblockquote\u003e\n\u003cp\u003e可以与任何模型一起使用的所有强大方法\u003c/p\u003e\u003c/blockquote\u003e","title":"什么是模型无关方法？"},{"content":" 两个文件，可生成不带时间线的纯文案，MD 格式，也可以生成带时间线的 SRT 文件。\n因为剪映国内版对 JSON 文件进行了加密，所以请选择国际版 Cutcap，无法接受的请不要购买。\n剪映在更新版本之后，原本的生成字幕功能变成 VIP 专享了，在最新的 6.1 版本中，甚至是 SVIP 专享。有点「鱼塘养肥了可以宰了」的感觉。说实在的，虽然剪映本身并没有声称过自己是免费软件，但是其背后的算法也是这么多用户为其贡献资源才慢慢完善的。\n好吧，我们要知道，基本百分之 90 以上的用户都在使用这个字幕功能为自己的视频添加字幕，否则多数口播博主的那个发音，估计三四遍都不知道你在讲什么。更何况很大一部分人生成字幕后都是要交给 AI 再读一次的。估计字节也是看准了这部分人不得不用，所以才敢这样。\n既然无法导出带字幕的视频了，那我们换个方式吧，使用其他方式保存字幕之后，咱们再在 Final Cut 或者 Premiere 中去做，又或者，如果你是导入自己的字幕，剪映中依然是可以做的。那问题就好解决了，既然你剪映可以生成字幕，只是无法导出带字幕的视频或者导出字幕，那我在你生成之后获取源文件不就好了。\n于是就有了我以下的自力更生的方法。\n用法也很简单，直接在终端执行: python json2md.py, 或者 python json2srt.py即可，这两个文件一个是转为不带时间线的 MD 格式文件，一个是转为带时间线的 SRT 文件。\n因为不太清楚剪映中对于时间的计算规则，所以规则是摸索的，最后造成的结果就是大概每一块字幕和原字幕大概误差在 1~2 帧左右，实际使用并无影响。\n目前本程序只是选择读取文件的目录和保存文件的目录，即便你的剪映修改了草稿目录照样可以执行。默认为剪映默认文件目录。\n有需求的朋友，可自行到此处下载。\n","permalink":"https://hivan.me/posts/%E4%B8%80%E4%B8%AApython%E8%84%9A%E6%9C%AC%E8%A7%A3%E5%86%B3%E6%96%B0%E7%89%88%E5%89%AA%E6%98%A0%E5%AF%BC%E5%87%BA%E5%AD%97%E5%B9%95%E6%94%B6%E8%B4%B9%E9%97%AE%E9%A2%98/","summary":"\u003cblockquote\u003e\n\u003cp\u003e如果你是希望我能完全解决剪映收费问题，我无法帮你；\u003c/p\u003e\u003c/blockquote\u003e","title":"一个python脚本解决新版剪映导出字幕收费问题"},{"content":"「AI秘籍」系列课程：\n人工智能应用数学基础 人工智能Python基础 人工智能基础核心知识 人工智能BI核心知识 人工智能CV核心知识 AI 进阶：企业项目实战 可直接在橱窗里购买，或者到文末领取优惠后购买：\n乍一看，不公平的机器学习模型这一概念似乎有些矛盾。机器没有种族、民族、性别或宗教观念，怎么会主动歧视某些群体呢？但算法却会歧视，如果不加以制止，它们将继续做出延续历史不公正的决策。这就是算法公平性领域的用武之地。\n我们探索算法公平性领域及其目标。为了强调这一领域的重要性，我们讨论了不公平模型的例子及其后果。我们还简要介绍了不公平的原因、如何衡量以及如何防止不公平。最后，我们讨论了公平性和可解释性之间的联系。在此过程中，我们链接到了有关这些主题的更深入的文章。\n什么是算法公平性？ 在机器学习中，算法和模型这两个术语可以互换使用。确切地说，算法是数学函数，如线性回归、随机森林或神经网络。模型是经过数据训练的算法。经过训练后，模型可用于进行预测，从而帮助实现决策自动化。这些决策可能包括从诊断癌症患者到接受抵押贷款申请等任何事情。\n没有一个模型是完美的，这意味着它们可能会做出错误的预测。如果这些错误系统性地使一群人处于不利地位，我们就说这个模型是有偏见/不公平的。例如，一个不公平的模型可能会拒绝女性的抵押贷款申请，而不是男性。同样，我们最终可能会得到一个医疗系统，它不太可能为黑人患者检测出皮肤癌，而不是白人患者。\n算法公平性是旨在理解和纠正此类偏见的研究领域。它处于机器学习和伦理学的交叉点。具体来说，该领域包括：\n研究数据和算法偏见的原因 定义和应用公平衡量标准 开发旨在创建公平算法的数据收集和建模方法 向政府/企业提供如何规范机器学习的建议 理解公平的方法不仅仅是量化的，这一点也很重要。这是因为不公平的原因不仅限于数据和算法。研究还将涉及理解和解决不公平的根本原因。\n为什么算法公平性很重要？ 如上所述，机器学习模型被用于做出重要决策。错误预测的后果可能对个人造成毁灭性的影响。如果错误预测是系统性的，那么整个群体都可能受到影响。要理解我们的意思，回顾几个例子会有所帮助。\n苹果最近推出了一张信用卡——Apple Card。您可以在线申请该卡，并自动获得信用额度。随着人们开始使用该产品，我们发现女性获得的信用额度明显低于男性。即使女性的财务状况（和信用风险）相似，情况也是如此。例如，苹果联合创始人史蒂夫·沃兹尼亚克 (Steve Wozniak) 表示，他获得的信用额度是妻子的 10 倍。\n另一个例子是亚马逊用来帮助实现招聘自动化的系统。机器学习被用来对新候选人的简历进行评级。为了训练模型，亚马逊使用了历史上成功候选人的信息。问题是，由于科技行业男性占主导地位，这些候选人大多是男性。结果就是模型没有以性别中立的方式对简历进行评级。它甚至对“女性”一词进行了惩罚（例如女子足球队队长）。\n这些例子表明，模型可以做出基于性别歧视的预测。与男性平等的女性面临着截然不同的结果。在这种情况下，后果是信用额度降低或工作申请被拒绝。这两种结果都可能产生严重的财务影响。总的来说，这样的模型会加剧男女之间的经济不平等。\n模型还可能基于种族进行歧视。COMPAS 是美国刑事司法系统用来预测被告是否有可能再次犯罪的算法。错误的预测（即假阳性）可能导致被告被错误监禁或面临更长的监禁刑期。研究发现，黑人罪犯的假阳性率是白人罪犯的两倍。也就是说，黑人罪犯被错误地标记为潜在再犯罪者的可能性是白人罪犯的两倍。\n这些例子表明，我们可以发现许多行业都在使用有偏见的算法来解决不同的问题。这些算法做出决策的规模也令人担忧。有偏见的人能够承销的贷款数量或能够定罪的人数是有限的。算法可以扩展并用于做出所有决策。最终，有偏见的算法的后果可能是负面的，也可能是广泛的。\n不公平的原因 显然，这些算法很糟糕，但我们怎么会得到不公平的算法呢？算法公平性实际上是一个有点误导性的术语。算法本身并不是天生就有偏见的。它们只是数学函数。通过在数据上训练这些算法之一，我们得到了一个机器学习模型。引入有偏见的数据会导致模型有偏见。话虽如此，我们对算法的选择仍然会放大这些偏见。\n数据可能因各种原因而产生偏差。我们收集的数据将反映历史不公正，而这些不公正可以通过模型捕捉到**（历史偏差）。与亚马逊的招聘模式一样，这可能是由于少数族裔代表性不足（数据集不平衡）。也可能是由于与种族/性别相关的模型特征（代理变量）**。我们将在《不公平的预测：机器学习中的 5 种常见偏见来源》中更深入地探讨这些原因。\n常见的偏见来源——历史偏见、代理变量、不平衡数据集、算法选择和用户交互\n分析和衡量不公平现象 算法公平性研究的很多目的是开发分析和衡量不公平性的方法。这可能涉及分析数据以找出上述不公平性的潜在原因。它还涉及衡量模型预测中的不公平性。\n公平的定义 我们可以通过应用不同的公平定义来衡量预测的公平性。大多数定义都涉及将人群分为特权群体（例如男性）和非特权群体（例如女性）。然后，我们使用评估指标比较这些群体。例如，在均等几率定义下，我们要求两组的真阳性率和假阳性率相等。具有显著不同比率的模型被认为是不公平的。其他定义包括平等机会和不同影响。\n探索性公平性分析 评估公平性并不是在您拥有最终模型时开始的。它也应该是您探索性分析的一部分。一般来说，我们这样做是为了围绕我们的数据集建立一些直觉。因此，在建模时，您会对预期的结果有一个很好的了解。具体来说，为了公平起见，您需要了解数据的哪些方面可能导致不公平的模型。\n在文章《分析机器学习的公平性》中，我们将引导您了解如何进行这种探索性公平性分析。我们还将更深入地讨论公平性的定义并向您展示如何应用它们。\n进行探索性公平性分析，并利用平等机会、均等几率和不同的因素来衡量公平性……\n纠正和防止不公平现象 如果我们发现我们的模型不公平，我们自然会想要纠正它。已经开发了各种量化方法。我们可以将它们分为预处理、处理中和处理后。这取决于它们在模型开发的哪个阶段应用。例如，我们可以调整回归模型的成本函数以考虑公平性。这将被视为一种处理中方法。\n定量方法有其局限性。这是因为公平是一个复杂的问题。我们需要将其视为超越数据和模型的东西。最终，我们还需要非定量方法来全面解决不公平问题。这些方法包括解决根本原因**、意识到问题和团队多样性。**\n我们将在《纠正和防止机器学习中的不公平现象》中讨论更多定量和非定量方法。这些方法包括预处理、处理中和后处理方法的示例。我们还尝试更深入地了解这些方法的局限性。\n预处理、处理中和处理后定量方法。以及非定量方法……\n可解释性和公平性 我喜欢写关于算法公平性的文章，很大一部分文章都是关于可解释的机器学习的。可解释性涉及理解模型如何进行预测。公平性和可解释性实际上是相关的。主要原因是它们都是为了建立对 ML 系统的信任。我们将在文章《可解释性与公平性的关系》1中讨论这一点和其他两个原因。\n希望这篇文章对您有所帮助！除了我的公众号上的免费文章，您还可尝试阅读一些收费文章，获得更多更深的内容。如果想要针对企业项目实战，可以购买我的系列视频教程《人工智能企业项目实战》\n扫描下方二维码可获取优惠\n参考 Birhane, A., (2021) Algorithmic injustice: a relational ethics approach. https://www.sciencedirect.com/science/article/pii/S2666389921000155\nD. Pessach \u0026amp; E. Shmueli, Algorithmic Fairness (2020), https://arxiv.org/abs/2001.09784\nGal Yona, A Gentle Introduction to the Discussion on Algorithmic Fairness (2017), https://towardsdatascience.com/a-gentle-introduction-to-the-discussion-on-algorithmic-fairness-740bbb469b6\nJ, Vincent, Apple’s credit card is being investigated for discriminating against women (2019), https://www.theverge.com/2019/11/11/20958953/apple-credit-card-gender-discrimination-algorithms-black-box-investigation\nS. Wachter-Boettcher, Technically Wrong: Sexist Apps, Biased Algorithms, and Other Threats of Toxic Tech (2017), https://www.goodreads.com/book/show/38212110-technically-wrong\nThe Guardian, Amazon ditched AI recruiting tool that favored men for technical jobs (2018), https://www.theguardian.com/technology/2018/oct/10/amazon-hiring-ai-gender-bias-recruiting-engine\nWikipedia, Algorithmic bias (2021), https://en.wikipedia.org/wiki/Algorithmic_bias\n将在后面发表相关文章\u0026#160;\u0026#x21a9;\u0026#xfe0e;\n","permalink":"https://hivan.me/posts/%E4%BB%80%E4%B9%88%E6%98%AF%E7%AE%97%E6%B3%95%E5%85%AC%E5%B9%B3%E6%80%A7/","summary":"\u003cblockquote\u003e\n\u003cp\u003e旨在理解和防止机器学习中不公平现象的领域的介绍\u003c/p\u003e\u003c/blockquote\u003e","title":"什么是算法公平性？"},{"content":"「AI秘籍」系列课程：\n人工智能应用数学基础 人工智能Python基础 人工智能基础核心知识 人工智能BI核心知识 人工智能CV核心知识 AI 进阶：企业项目实战 机器学习中的公平性是一个复杂的问题。更糟糕的是，负责构建模型的人不一定具备确保公平的技能。这是因为不公平的原因超出了数据和算法的范围。这意味着解决方案也需要超越量化方法。\n为了理解这一点，我们将首先讨论不同的定量方法。我们可以将这些方法分为预处理、处理中和处理后。我们将重点关注这些方法的局限性，以了解为什么它们可能无法解决不公平问题。最终，我们需要将公平视为一个更广泛的问题。\n这就是为什么我们将继续讨论非定量方法。它们包括不使用或限制使用ML。提供解释、说明和挑战决策的机会是一个重要方面。它们还包括解决不公平的根本原因、对问题的认识和团队多样性。要有效解决不公平问题，需要结合定量和非定量方法。\n定量方法 数据科学家擅长采用定量方法来确保公平。他们通过调整用于训练模型的数据或训练的算法来工作。查看图 1，您可以看到我们可以将这些方法分为预处理、处理中和后处理方法。这种划分取决于它们在模型开发的哪个阶段应用。\n预处理 不公平的模型可能是数据偏差的结果。预处理方法试图在数据用于训练模型之前消除数据偏差 。数据经过转换，算法按照原始数据集的方式进行训练。希望最终的模型是公平的。即使准确度较低。\n不公平模型的原因 其中之一是目标变量反映了历史上不公平的决策。这对于主观目标变量来说更常见。例如，性别歧视的招聘做法可能会导致更高比例的女性被拒绝求职。为了解决这个问题，一种预处理方法是交换先前决策的目标变量。也就是说，我们重新标记不公平的招聘决策，以人为地增加我们数据集中被雇用的女性数量。\n一个问题是，改变目标变量并不总是可行的。目标变量通常是客观的。例如，我们不能将之前的贷款违约重新标记为非违约。对于一些主观目标变量来说，这可能也很困难。这是因为我们在做出决定时可能没有所有可用的信息。对于招聘决定，我们可能有简历。然而，我们可能没有录音面试，而这是申请的很大一部分。\n不公平模型的另一个原因是代理变量。这些是与受保护变量相关或关联的模型特征。受保护变量代表种族或性别等敏感特征。其他方法着眼于“修复”或消除模型特征中的偏见。这些方法通过消除模型特征与受保护变量之间的关联来实现。\n这种方法的一个例子是差异影响消除(DIR)。受保护变量通常分为特权组（例如男性）和非特权组（例如女性）。DIR 通过修改特征来工作，以便两个组的分布变得相似。例如，参见图 2。这里修改了男性和女性的收入分布。\nDIR 保持每个子群体内的排名顺序。也就是说，如果你是特权群体中收入最高的人，那么你仍然是该群体中收入最高的人。只有两个群体之间的排名顺序受到影响。结果是我们将不再能够使用收入来区分这两个群体。同时，我们将保留收入预测目标变量的部分能力。\nDIR 还有一个调整参数，它在准确性和公平性之间引入了权衡。它允许您控制分布的偏移量。换句话说，它允许您仅删除模型特征和受保护变量之间的部分关联。\n预处理方法的一个优点是它们可以与任何算法一起使用。这是因为只修改了数据。一个主要的缺点是我们的功能的解释不再清晰。我们可能需要修复多个受保护变量（例如性别、种族、原籍国）的特征值。特征分布经过如此多的转变后，它就失去了解释能力。向非技术受众解释这样的模型将具有挑战性。\n进行中 我们可以调整 ML 算法，而不是转换数据。这些被称为内部处理方法。通常，模型经过训练以最大化某种准确度。内部处理方法通过调整目标来考虑公平性。这可以通过更改成本函数来考虑公平性或对模型预测施加约束来实现。模型是在有偏差的数据上进行训练的，但最终结果是公平的模型。\n对于回归，一种方法是向成本函数添加惩罚参数。这与正则化的工作方式类似。对于正则化，我们惩罚参数值以降低复杂性和过度拟合。现在我们引入一个减少不公平性的惩罚参数。\n具体来说，该参数旨在满足公平性的数学定义。例如，参见图 3 中的均等赔率**。**这里，下标 0 表示无特权组，下标 1 表示有特权组。根据此定义，我们要求相等的真正率 (TPR) 和假正率 (FPR)。\n均等机会只是公平的一个潜在定义。其他定义包括平等机会和不同影响。我们曾在文章《分析机器学习中的公平性》 中讨论所有这些。我们还讨论了每个定义的理由，并向您展示如何使用 Python 应用它们。\n这些方法的一个缺点是它们在实践中可能难以实施。它们需要调整成熟的算法。与预处理方法不同，这些调整也是针对特定算法的。我们引入惩罚参数的方式对于回归、树方法或神经网络会有所不同。\n尝试用数学方法定义公平性也存在问题。这样做可能会忽略公平性的细微差别。在追求单一定义时，这种情况会变得更加困难。如果我们基于一种定义实现公平性，则不一定能基于另一种定义实现公平性。尝试将多种定义纳入惩罚参数将大大增加算法的复杂性。\n后期处理 后处理方法通过改变模型的预测来工作。我们不对数据或算法进行任何调整。我们只是交换某些模型预测（例如从正到负）。目标是只对不公平的预测进行此操作。\n一种方法是针对特权群体和非特权群体设置不同的阈值。例如，假设我们使用逻辑回归来预测贷款申请违约。我们将小于 0.5 的概率标记为正 (1)。也就是说，预测他们不会违约，我们会向客户提供贷款。假设使用 0.5 的阈值，我们发现女性 (0) 的 TPR 明显低于男性 (1)。\n换句话说，图 4 中的平等机会并未得到满足。为了解决这个问题，我们可以将女性的概率阈值提高到 0.6。也就是说，我们对男性（0.5）和女性（0.6）设置了不同的阈值。结果是，更多的女性被接受贷款，从而导致更高的 TPR。在实践中，我们可以调整阈值，以实现 TPR 的差异在某个临界值之内。\n这种方法的一个问题是，它要求我们在预测时掌握受保护变量的信息。要决定使用什么阈值，我们需要有关申请人性别的可靠信息。在许多情况下，由于法律或隐私原因，此类信息仅在培训期间可用。我们也面临着与内部处理方法类似的缺点。也就是说，我们将尝试基于一个定义实现公平。\n定量方法的缺点 数据科学家倾向于关注这些公平的量化方法。这正是我们的优势所在。然而，认为仅通过调整数据和算法就能解决不公平问题的想法是天真的。公平是一个复杂的问题，我们需要将其视为超越数据集的东西。只使用量化方法就像不先取出子弹就缝合枪伤一样。\n“因此，不公正和有害的结果被视为副作用，可以通过技术解决方案（例如“去偏见”数据集）来处理，而不是那些根植于模糊和偶然问题的数学化、历史不平等、不对称的权力等级制度或渗透到数据实践中的未经审查的问题假设的问题。”\n—阿贝巴·比尔哈内\n这可能是量化方法的最大缺点。通过满足公平的某些数学定义，我们说服自己已经解决了问题。然而，这可能会忽略公平的细微差别。它也无助于解决不公平的根本原因。这并不是说量化方法没有用。它们将成为解决方案的一部分。然而，要完全解决不公平问题，我们需要额外的非量化方法。\n非定量方法 在本节中，我们将讨论其中一些方法。您可以在图 5 中看到这些方法的概述。其中大多数方法要求数据科学家远离计算机，以更广阔的视角看待公平性。成功实施这些方法也需要非技术技能。\n意识到问题 数据科学涉及技术工作。我们整天都在看数字、代码和屏幕。我们很容易忘记我们建立的模型会影响真实的人。更糟糕的是，有些人甚至不知道模型会导致不公平的结果。要解决不公平问题，我们首先需要了解并接受机器学习的潜在负面影响。\n首先，我们需要了解不公平模型的原因。我们已经提到了其中一些原因。它们包括代理变量、倾斜的数据集和数据中嵌入的历史不公正。不公平也可能来自我们的算法选择或用户与模型的交互方式。我们在文章《机器学习中的 5 种常见偏见来源》中更深入地讨论这些问题。\n然后，我们需要进行彻底的公平性分析。这是为了了解上述原因的普遍程度。将这种分析扩展到数据和模型之外也很重要。模型可能会拒绝或接受贷款申请。我们可以用 1/0 量化这些并计算公平指标。然而，这隐藏了我们模型的真正后果。拒绝可能会导致企业破产或学生无法上大学。\n只有充分了解不公平的风险，我们才能做出适当的决定来减轻这些风险。这样做可能需要数据科学家改变对自己角色的看法。它不再是纯粹的技术，而是影响真实的人。\n不要使用机器学习 我们必须承认，机器学习并不是解决所有问题的良方。即使可以使用，也可能导致不公平的结果。机器学习还有许多不可接受的用途——例如，使用面部识别预测犯罪行为。这意味着最好的解决方案可能是根本不使用机器学习。我们不会自动化某个流程。相反，人类将负责任何决定。\n选择这条路线时，您需要考虑所有成本。一方面，人类做出决策可能代价高昂。另一方面，有偏见的模型可能会给用户带来重大负面影响。这可能导致信任丧失和声誉受损。这些风险可能超过自动化流程的任何好处。对这些风险的理解将来自上述公平性分析。\n限制机器学习的使用 很有可能，如果你已经努力构建了一个模型，你就会想要使用它。因此，你可以限制模型的使用方式，而不是完全丢弃它。这可能涉及对机器学习做出的决策应用某些手动检查或干预。最终，我们将使用模型来仅部分自动化流程。\n例如，模型可以自动拒绝或接受贷款申请。相反，我们可以引入一个新的结果——推荐。在这种情况下，申请可以由人工手动检查。推荐申请的过程可以设计为减少不公平结果的影响。这些应该旨在帮助那些最脆弱的人。\n解决根本原因 数据不公平是现实的反映。如果我们解决真正的根本问题，我们也可以解决数据中的问题。这将需要公司或政府政策的转变。这也意味着将资源从量化方法中转移出来。这将需要更广泛的组织甚至整个国家的合作。\n例如，假设自动化招聘流程导致女性被聘用人数减少。我们可以通过鼓励更多女性申请来帮助解决这个问题。公司可以通过广告活动或让其环境成为女性更好的地方来实现这一目标。在国家层面，可以通过加大对女性 STEM 教育的投资来实现这一目标。\n花时间理解模型 模型可解释性对于公平性至关重要。可解释性涉及理解模型如何进行预测。这可以是整个模型（即全局解释）。这是通过查看多个预测的趋势来实现的。这使我们能够质疑这些趋势并确定它们是否会导致不公平。\n它还意味着了解模型如何做出个别预测（即局部解释）。这些告诉我们哪些模型特征对特定预测贡献最大。这使我们能够判断模型是否导致特定用户产生不公平的结果。\n可解释性要求我们优先考虑理解而不是性能。我们可以使用本质上可解释的模型（如回归或决策树）来实现这一点。这些可以通过查看模型参数直接解释。对于非线性模型（如 xgboost 或神经网络），我们需要模型无关的1方法。一种常见的方法是 SHAP2，日后会撰写相关文章中介绍。\n给出解释 模型预测可能会给用户带来严重后果。考虑到这一点，他们有权要求对这些预测作出解释。解释可以主要基于局部解释。也就是说，我们可以解释哪些特征对影响用户的预测贡献最大。\n重要的是要理解解释和说明不是一回事。解释是技术性的。我们查看模型参数或 SHAP 值。说明是向非技术受众提供的。这意味着以一种可以理解的方式给出它们很重要。我们需要在日后的文章中仔细分析解释预测的作用和艺术。\n解释还可以超越功能贡献。我们可以解释决策过程自动化的程度。我们还可以解释该过程中使用了哪些数据以及我们从哪里获得这些数据。解释的这些方面可以由法律或客户需求来定义。\n提供挑战决定的机会 一旦用户得到解释，他们就可以决定它是否合理。如果他们认为它不合理，他们必须被允许质疑这个决定。赋予用户这种决策权力对于打击不公平至关重要。这意味着不公平的决定更有可能受到质疑和纠正。\n这又回到了限制使用机器学习的问题上。我们需要制定程序，允许至少部分决策可以手动做出。就像我们的贷款模型示例一样，决策可以提交给贷方，而不是自动拒绝。申请人能够控制这一过程非常重要。\n我们也可以回想一下我们的量化方法。像 DIR 这样的方法会增加复杂性并对可解释性产生负面影响。这会使给出人性化的解释变得更加困难。换句话说，通过影响解释，一些量化方法可能会对公平性产生负面影响。\n团队多元化 数据是我们做出有效决策的最佳工具。然而，正如前面提到的，它可以隐藏机器学习的真正后果。我们的生活经历可以更好地说明这一点。这些经历与我们的文化背景有关。这就是我们体验世界的方式，而技术取决于我们的性别、种族、宗教或原籍国。这使得从不同的人群中获得对我们的机器学习系统的反馈变得非常重要。\n聘请多元化的团队也很重要。这些人将真正构建模型和系统。这样做将带来一系列不同的生活经验。他们都将了解系统将如何影响他们自己的生活。这将使他们更容易在部署模型之前识别潜在的公平问题。\n多样性还意味着聘用具有不同专业领域的人员。正如我们所提到的，要解决不公平问题，我们需要超越定量方法。换句话说，我们需要大多数数据科学家不具备的技能。团队的关键成员将是人工智能伦理方面的专家。他们将对我们上面概述的非定量方法有更深入的了解。然后，数据科学家将能够专注于定量方法。\n希望这篇文章对您有所帮助！您可以通过后买「企业 AI 项目实战」来进行级数进阶，并以此来支持我。实战教程将助力您尽快上手企业实际的 AI 项目。\n参考 Birhane, A., (2021) Algorithmic injustice: a relational ethics approach. https://www.sciencedirect.com/science/article/pii/S2666389921000155\nPessach, D. and Shmueli, E., (2020), Algorithmic fairness. https://arxiv.org/abs/2001.09784\nMehrabi, N., Morstatter, F., Saxena, N., Lerman, K. and Galstyan, A., (2021), A survey on bias and fairness in machine learning. https://arxiv.org/abs/1908.09635\nFeldman, M., Friedler, S.A., Moeller, J., Scheidegger, C. and Venkatasubramanian, S., (2015), Certifying and removing disparate impact. https://dl.acm.org/doi/pdf/10.1145/2783258.2783311\nBechavod, Y. and Ligett, K., (2017), Penalizing unfairness in binary classification. https://arxiv.org/abs/1707.00044\nLo Piano, S., (2020). Ethical principles in machine learning and artificial intelligence: cases from the field and possible ways forward. https://www.nature.com/articles/s41599-020-0501-9\nSmith, G., (2020). What does “fairness” mean for machine learning systems? https://haas.berkeley.edu/wp-content/uploads/What-is-fairness_-EGAL2.pdf\nGoogle, (2022), How Inclusive Data Builds Stronger Brands https://www.thinkwithgoogle.com/feature/ml-fairness-for-marketers/#what-we-learned\n模型无关的方法, 后续文章会写。\u0026#160;\u0026#x21a9;\u0026#xfe0e;\nSHAP, 相关技术文章后续会写。\u0026#160;\u0026#x21a9;\u0026#xfe0e;\n","permalink":"https://hivan.me/posts/%E7%BA%A0%E6%AD%A3%E5%92%8C%E9%98%B2%E6%AD%A2%E6%9C%BA%E5%99%A8%E5%AD%A6%E4%B9%A0%E4%B8%AD%E7%9A%84%E4%B8%8D%E5%85%AC%E5%B9%B3%E7%8E%B0%E8%B1%A1/","summary":"\u003cblockquote\u003e\n\u003cp\u003e预处理、处理中、后处理方法和非定量方法\u003c/p\u003e\u003c/blockquote\u003e","title":"纠正和防止机器学习中的不公平现象"},{"content":"「AI秘籍」系列课程：\n人工智能应用数学基础 人工智能Python基础 人工智能基础核心知识 人工智能BI核心知识 人工智能CV核心知识 AI 进阶：企业项目实战 从表面上看，机器学习似乎是公正的。算法没有种族、民族、性别或宗教等敏感特征的概念。因此，它们不可能对某些群体做出有偏见的决定。然而，如果不加以控制，它们就会这样做。要纠正有偏见的决策，我们首先需要了解偏见来自何处。\n我们将讨论图 1 中给出的 5 个不公平原因。前三个原因涉及用于训练模型的数据。后两个原因涉及算法选择以及用户如何与模型交互。\n之前写了一篇实际的教程《分析机器学习中的公平性》，如果您想要更实际的教程，请参阅这篇文章。它包含一项探索性分析，旨在量化我们在本文中讨论的一些原因。\n进行探索性公平性分析，并利用平等机会、均等几率和不同的因素来衡量公平性……\n偏见与公平 首先，让我们澄清一下偏见的含义。这是一个令人困惑的术语，在许多领域用于指代一系列问题。在机器学习中，偏见是模型开发过程中出现的任何系统性错误。这些错误可能是由于错误的假设或代码错误造成的。它们可能是在数据收集、特征工程或训练过程中引入的。甚至你部署模型的方式也可能导致偏见。\n偏差 — 任何系统性错误\n在日常生活中，偏见也有不同的定义。偏见是基于一个人或一个群体的特征而对其产生的偏见。同样，公平是对个人或群体没有任何偏见。\n偏见（外行人）——对某个人或某个群体的偏见\n在算法公平性方面，偏见还有另一种定义。它实际上是上述两者的结合。偏见是导致模型对某个人或某个群体产生偏见的任何错误。我们也可以把这种偏见称为不公平。\n偏见（算法公平性）——导致模型不公平的系统性错误\n接下来，我们将讨论不公平的根源与机器学习中更普遍的偏见形式之间的关系。这些包括测量偏见、表征偏见和聚合偏见。\n不公平的根源 基于上述情况，似乎所有模型都是不公平的。机器学习的重点不就是根据群体特征进行区分吗？\n定义“特征”的含义非常重要。在分析公平性时，这些是敏感特征，例如种族、民族、原籍国、性别或宗教。在数据集中，我们称这些为受保护变量。有 5 个主要原因导致模型对受保护变量中包含的群体不公平。\n原因 1：历史偏见 过去，某些群体受到歧视。这种情况可能是故意通过法律发生的，也可能是无意中通过无意识的偏见发生的。无论出于何种原因，这种历史偏见都可以反映在我们的数据中。模型旨在拟合数据。这意味着数据中的任何历史偏见都可以反映在模型本身中。\n历史偏见可以以不同的方式表现出来。少数群体的数据可能更容易丢失或记录错误。目标变量可能反映不公平的决策。当目标变量基于主观的人为决策时，这种情况更有可能发生。\n有一个例子来自亚马逊开发的一个用于帮助实现招聘自动化的模型。历史申请人的简历被用来训练他们的模型。目标变量基于接受或拒绝工作申请的决定。\n问题在于，由于招聘人员的偏见，大多数成功的申请者都是男性。女性被拒绝是因为她们的性别，而不是她们的资格。这种偏见最终反映在目标变量中。该模型学会了将女性简历的特征与不成功的候选人联系起来。它甚至惩罚了“女性”这个词（例如女子足球队队长**）**。\n测量偏差 数据中的历史偏差与测量偏差的概念有关。在机器学习中，我们需要定义特征或标签。它们充当其他更复杂或不可测量构造的代理。首先，当代理无法很好地替代构造时，就会出现测量偏差。如果代理不是在不同组中统一创建的，也会发生这种情况。\n对于招聘模型来说，历史决策就是标签。它充当着候选人能力的代理。问题是这些决策并不是统一的。男性和女性候选人受到的待遇不同。换句话说，不同群体的代理并不统一。在这种情况下，历史偏见导致了测量偏差。\n原因 2：代理变量 我们上面提到了受保护变量。这些是代表种族或性别等敏感特征的模型特征。使用这些特征可能会导致不公平的模型。通常，正常特征可以与受保护特征相关或关联。我们称这些为代理变量。使用代理变量的模型可以有效地使用受保护变量来做出决策。\n代理变量——与受保护特征关联的模型特征\n例如，在南非，你居住的地方可以很好地预测你的种族。这是由于该国种族隔离的历史。你可以在图 2 中看到我们的意思。开普敦市仍然按种族划分。这意味着在模型中使用某人的邮政编码可能会导致种族歧视。\n在理解代理变量时，关联和因果之间的区别很重要。假设我们正在建立一个模型来预测客户是否会拖欠贷款。一个理性的人会同意，一个人的种族不会导致他们的风险发生变化。事实上，改变他们风险的甚至不是他们的位置。而是他们的经济地位。然而，模型只关心关联。不幸的是，在南非，种族、位置和经济地位都是相关的。\n原因 3：样本不均衡 假设我们计算一个人口的平均结婚年龄。我们得到的值为 33 岁。查看图 3，我们可以看到这个值并不代表人口的所有子群体。宗教群体的平均年龄 28 岁要低得多。由于非宗教群体的规模，平均值有所偏差。换句话说，人口平均值更接近非宗教群体的平均值。\n模型参数也可能以类似的方式出现偏差。模型试图捕捉整个训练群体的趋势。然而，在不同的子群体中，模型特征可能具有不同的趋势。如果一个群体占人口的大多数，则模型可能会偏向该群体内的趋势。因此，模型在少数群体中的表现可能会更差。\n我们可以在图 4 中看到一个倾斜数据集的示例。ImageNet 是一个用于训练图像识别模型的大型数据集。我们可以将不同的国家视为子组。数据集中表示了出租车、餐馆和婚礼等概念。这些概念的外观在不同国家/地区可能有很大差异。问题是大多数图像来自北美和欧洲。\n代表性偏见 这种不公平的根源与代表性偏见有关——当发展人群无法很好地推广到所有子群体时。与 ImageNet 一样，如果对某些子群体的关注较少，就会发生这种情况。一些数据集甚至可能反映出故意排除少数群体的决定。在这种情况下，发展人群不能很好地代表目标人群。\n即使发展人口完全代表目标人口，问题仍然可能发生。根据定义，少数群体在总人口中所占比例较小。这意味着他们在发展人口中所占比例也会较小。这可能导致我们上面讨论的模型偏差问题。\n原因 4：算法选择 前 3 个偏见来源都与数据问题有关。我们对所用算法的选择也会导致公平性问题。首先，一些算法的可解释性比其他算法低。这会使识别偏见来源和纠正偏见变得更加困难1。另一个主要因素是模型的目标。\n以社交媒体推荐算法为例。它们只有一个任务 —— 让你留在平台上2。不幸的是，愤怒，尤其是对不属于你的群体的愤怒，可以有效地推动参与3。难怪仇恨和暴力内容在这些平台上如此盛行。\n一般来说，模型是使用成本函数进行训练的。成本函数通常旨在最大化某种准确度。最大化准确度并不一定会导致不公平的决策。但是，除非我们调整成本函数以考虑公平性，否则我们无法保证公平的决策。\n聚合偏差 成本函数还有另一个问题——它们旨在最大限度地提高整个开发群体的准确性。这可能会导致聚合偏差——整体表现良好，但其中一个子组的表现不佳。这类似于代表性偏差，只是现在我们谈论的是模型而不是数据。\n在讨论人工智能对跨性别群体的负面影响时，我们已经看到了这样的例子。图 5 中对 4 个 AGR 系统进行的一项研究4表明，它们对跨性别女性的误判率平均为 12.7%，对跨性别男性的误判率平均为 29.5%。相比之下，对顺性别女性和男性的误判率分别为 1.7% 和 2.4%。\n实际上，算法选择与我们之前提到的三个数据问题有关。数据集中的表示偏差可能导致模型中的聚合偏差。我们可以选择最大化有偏差的目标变量的准确性。算法还可以使用代理变量来最大化准确性。它并不关心这是否会导致不公平的决策。\n原因 5：用户反馈循环 最后一个偏见来源与我们与模型的交互方式有关。模型一旦训练完成，就会被部署。随着用户与模型的交互，我们将收集更多数据来训练模型的未来版本。基础模型可能会导致数据出现偏差，从而导致未来模型出现进一步的偏差。\n例如，假设我们开发了一个用于预测某人是否患有皮肤癌的模型。由于表征偏差，当人皮肤白皙时，该模型表现更好。可以理解的是，有色人种可能会避免使用我们的模型。因此，我们只会收集针对较白皮肤的诊断数据。这将放大表征偏差，并导致未来出现更多有偏差的模型。\n因此，在部署模型之前，我们需要测量偏差，确定其来源并纠正它。在本文中，我们重点关注了偏差的来源。在之后，我会写一篇关于测量偏差的文章，用于《解决机器学习中的不公平现象》。\n希望这篇文章对您有所帮助！您也可以选择购买进阶的文章用于学习更符合企业实战的技能，也能顺便支持我。\n参考 Pessach, D. and Shmueli, E., (2020), Algorithmic fairness. https://arxiv.org/abs/2001.09784\nMehrabi, N., Morstatter, F., Saxena, N., Lerman, K. and Galstyan, A., (2021), A survey on bias and fairness in machine learning. https://arxiv.org/abs/1908.09635\nChouldechova, A. and Roth, A., (2018), The frontiers of fairness in machine learning https://arxiv.org/abs/1810.08810\nWickramasinghe, S. (2021), Bias \u0026amp; Variance in Machine Learning https://www.bmc.com/blogs/bias-variance-machine-learning\nSuresh, H. and Guttag, J., (2021), A framework for understanding sources of harm throughout the machine learning life cycle https://arxiv.org/abs/1901.10002\nThe Guardian, Amazon ditched AI recruiting tool that favored men for technical jobs (2018), https://www.theguardian.com/technology/2018/oct/10/amazon-hiring-ai-gender-bias-recruiting-engine\nRathje, S., Van Bavel, J.J. and Van Der Linden, S., 2021. Out-group animosity drives engagement on social media. Proceedings of the National Academy of Sciences, 118(26), p.e2024292118. https://www.pnas.org/doi/abs/10.1073/pnas.2024292118\nScheuerman, M.K., Paul, J.M. and Brubaker, J.R., 2019. How computers see gender: An evaluation of gender classification in commercial facial analysis services. Proceedings of the ACM on Human-Computer Interaction, 3(CSCW), pp.1–33 https://docs.wixstatic.com/ugd/eb2cd9_963fbde2284f4a72b33ea2ad295fa6d3.pdf\n识别偏见来源和纠正偏见变得更加困难，后续会在其他文章中讨论相关内容。\u0026#160;\u0026#x21a9;\u0026#xfe0e;\nhttps://www.pbs.org/wgbh/nova/article/radical-ideas-social-media-algorithms/, 社交媒体算法\u0026#160;\u0026#x21a9;\u0026#xfe0e;\nhttps://www.pnas.org/doi/abs/10.1073/pnas.2024292118, \u0026ldquo;愤怒对于推动的研究\u0026rdquo;\u0026#160;\u0026#x21a9;\u0026#xfe0e;\nhttps://dl.acm.org/doi/10.1145/3359246, A study on 4 AGR systems\u0026#160;\u0026#x21a9;\u0026#xfe0e;\n","permalink":"https://hivan.me/posts/%E4%B8%8D%E5%85%AC%E5%B9%B3%E7%9A%84%E9%A2%84%E6%B5%8B%E6%9C%BA%E5%99%A8%E5%AD%A6%E4%B9%A0%E4%B8%AD%E7%9A%84-5-%E7%A7%8D%E5%B8%B8%E8%A7%81%E5%81%8F%E8%A7%81%E6%9D%A5%E6%BA%90/","summary":"\u003cblockquote\u003e\n\u003cp\u003e历史偏见、代理变量、不平衡数据集、算法选择和用户反馈循环如何导致不公平的模型\u003c/p\u003e\u003c/blockquote\u003e","title":"不公平的预测：机器学习中的 5 种常见偏见来源"},{"content":"「AI秘籍」系列课程：\n人工智能应用数学基础\n人工智能Python基础\n人工智能基础核心知识\n人工智能BI核心知识\n人工智能CV核心知识\n仅仅建立能够做出准确预测的模型已经不够了。我们还需要确保这些预测是公平的。\n这样做可以减少有偏见的预测带来的危害。因此，你将在建立对人工智能系统的信任方面大有裨益。要纠正偏见，我们需要从分析数据和模型的公平性开始。\n衡量公平性很简单。\n理解某个模型不公平的原因则更加复杂。\n这就是为什么我们将：\n首先进行**探索性公平性分析——**在开始建模之前识别潜在的偏见来源。 然后，我们将继续**衡量公平性——**通过应用公平的不同定义。 你可以在下面看到我们将介绍的方法的摘要。\n我们将讨论这些方法背后的理论。我们还将使用 Python 应用它们**。**我们将讨论关键代码，你可以在[GitHub](https://github.com/conorosully/medium-articles/blob/master/src/algorithm fairness/Measuring Bias.ipynb)1上找到完整的项目。\n数据集 我们将使用成人数据集构建一个模型2。你可以在表 1中看到它的快照。经过一些特征工程后，我们将使用前 6 列作为模型特征。接下来的 2 列（种族和性别）是敏感属性。我们将根据这些分析对群体的偏见。最后一个是我们的目标变量。我们将尝试预测一个人的收入是高于还是低于 5 万美元。\n在加载此数据集之前，我们需要导入一些 Python 包。我们使用下面的代码执行此操作。我们使用NumPy和 Pandas 进行数据处理（第 1-2 行）。Matplotlib用于一些数据可视化（第 3 行）。我们将使用 xgboost 来构建我们的模型（第 5 行）。我们还从 scikit-learn 导入了一些函数来评估我们的模型（第 8-10 行）。确保你已安装这些。\nimport numpy as np import pandas as pd import matplotlib.pyplot as plt import xgboost as xgb from xgboost import plot_importance from sklearn.metrics import accuracy_score,confusion_matrix from sklearn.metrics import classification_report from sklearn.feature_selection import mutual_info_classif import shap import os data_path = os.environ.get(\u0026#39;pub_data\u0026#39;) 我们在下面导入数据集（第 7 行）。还删除了任何有缺失值的行（第 8 行）。请注意，这里加载了一些额外的列。请参阅列名（第 1-4 行）。在本分析中，我们只考虑表 1 中提到的那些。\nnames = [\u0026#39;age\u0026#39;,\u0026#39;workclass\u0026#39;,\u0026#39;fnlwgt\u0026#39;,\u0026#39;education\u0026#39;,\u0026#39;education-num\u0026#39;, \u0026#39;marital-status\u0026#39;,\u0026#39;occupation\u0026#39;,\u0026#39;relationship\u0026#39;,\u0026#39;race\u0026#39;,\u0026#39;sex\u0026#39;, \u0026#39;capital-gain\u0026#39;,\u0026#39;capital-loss\u0026#39;,\u0026#39;hours-per-week\u0026#39;,\u0026#39;native-country\u0026#39;, \u0026#39;y\u0026#39;] #Load dataset df = pd.read_csv(data_path + \u0026#39;adult_all.csv\u0026#39;, names=names, na_values=\u0026#39;?\u0026#39;) df = df.dropna() df = df.apply(lambda x: x.str.strip() if x.dtype == \u0026#39;object\u0026#39; else x) print(len(df)) df.head(10) --- age\tworkclass\tfnlwgt\teducation\teducation-num\tmarital-status\toccupation\trelationship\trace\tsex\tcapital-gain\tcapital-loss\thours-per-week\tnative-country\ty 0\t39\tState-gov\t77516\tBachelors\t13\tNever-married\tAdm-clerical\tNot-in-family\tWhite\tMale\t2174\t0\t40\tUnited-States\t\u0026lt;=50K ... 9\t42\tPrivate\t159449\tBachelors\t13\tMarried-civ-spouse\tExec-managerial\tHusband\tWhite\tMale\t5178\t0\t40\tUnited-States\t\u0026gt;50K 算法公平性的探索性分析 评估公平性并不是在你拥有最终模型时开始的。它也应该是你探索性分析的一部分。一般来说，我们这样做是为了围绕我们的数据集建立一些直觉。因此，在建模时，你会对预期的结果有一个很好的了解。具体来说，为了公平起见，你需要了解数据的哪些方面可能导致不公平的模型。\n由于[不公平的不同原因]3，你的模型可能会变得不公平。在我们的探索性分析中，我们将重点关注与数据相关的 3 个关键来源。这些是历史偏差、代理变量和不平衡数据集。我们希望了解这些因素在我们的数据中存在的程度。了解原因将有助于我们选择[解决不公平问题的最佳方法]4。\n不平衡的数据集 我们首先要看看我们的数据集是否不平衡。具体来说，我们指的是敏感属性方面的不平衡。查看**图 1，**我们按种族和性别对人口进行了细分。你可以看到我们的数据集确实不平衡。第一张图表显示，**85.4%**的人口是白人。同样，**66.9%**的人口是男性。\n你可以在下面看到我们如何为种族属性创建饼图。我们首先按种族创建人口计数（第 2 行）。我们使用索引定义标签（第 3 行）。这些是我们在图 1 中看到的不同种族群体的名称。然后我们使用 matplotlib 中的 pie 函数绘制计数（第 6 行）。我们还使用标签创建图例（第 7 行）。性别属性饼图的代码非常相似。\n# Get population count by race counts = df[\u0026#39;race\u0026#39;].value_counts() labels = counts.index # Plot pie chart plt.pie(counts, startangle=90) plt.legend(labels, loc=2,fontsize=15) plt.title(\u0026#34;Race\u0026#34;,size=20) 数据集不平衡的问题在于模型参数可能会偏向多数。例如，女性和男性群体的趋势可能不同。趋势指的是特征和目标变量之间的关系。模型将尝试最大化整个群体的准确度。这样做可能会偏向男性群体的趋势。因此，我们对女性群体的准确度可能会较低。\n定义受保护的特征 在继续之前，我们需要定义受保护的特征。我们通过使用敏感属性创建二进制变量来实现这一点。我们定义变量，以便 1 代表特权群体，0 代表非特权群体。通常，非特权群体在过去会面临历史不公正。换句话说，这个群体最有可能面临有偏见的模型做出的不公平决定。\n我们使用以下代码定义这些特征。对于种族，我们定义受保护的特征，以便“白人”是特权群体（第 4 行）。也就是说，如果该人是白人，则变量的值为 1，否则为 0。对于性别，“男性”是特权群体（第 5 行）。接下来，我们将使用这些二进制变量代替原始敏感属性。\ndf_fair = df[[\u0026#39;race\u0026#39;,\u0026#39;sex\u0026#39;]].copy() # Define protected features df_fair[\u0026#39;priv_race\u0026#39;] = df_fair[\u0026#39;race\u0026#39;].apply(lambda x: 1 if x==\u0026#39;White\u0026#39; else 0) df_fair[\u0026#39;priv_sex\u0026#39;] = df_fair[\u0026#39;sex\u0026#39;].apply(lambda x: 1 if x==\u0026#39;Male\u0026#39; else 0) # Define target variable df_fair[\u0026#39;y\u0026#39;] = df[\u0026#39;y\u0026#39;].apply(lambda x: 1 if x==\u0026#39;\u0026gt;50K\u0026#39; else 0) df_fair.head() --- race\tsex\tpriv_race\tpriv_sex\ty 0\tWhite\tMale\t1\t1\t0 1\tWhite\tMale\t1\t1\t0 2\tWhite\tMale\t1\t1\t0 3\tBlack\tMale\t0\t1\t0 4\tBlack\tFemale\t0\t0\t0 在上面的代码中，我们还定义了一个目标变量（第 8 行）。如果此人的收入高于 5 万美元，则其值为 1；如果此人的收入低于 5 万美元，则其值为 0。在第 1 行中，我们创建了具有原始敏感属性的df_fair 数据集。我们已将目标变量和受保护的特征添加到此数据集。它将用作剩余公平性分析的基础。\n流行率 对于目标变量，流行率是阳性病例占总体病例的比例。当目标变量的值为 1 时，即为阳性病例。我们的数据集的总体流行率为24.1%。这意味着我们的数据集中大约有接近 1/4 的人收入超过 5 万美元。我们还可以使用流行率作为公平性指标。\n我们通过计算不同特权群体（1）和非特权群体（0）的流行率来做到这一点。你可以在下面的表 2 中看到这些值。请注意，特权群体的流行率要高得多。事实上，如果你是男性，那么收入超过 5 万美元的可能性几乎是女性的3 倍。\n我们可以进一步计算受保护特征交集处的流行率。你可以在表 3中看到这些值。左上角给出了你同时属于两个特权群体（即性别 = 1 和种族 = 1）时的流行率。同样，右下角给出了你不属于任何特权群体（即性别 = 0 和种族 = 0）时的流行率。这告诉我们，白人男性收入超过 5 万美元的可能性是非白人女性的4 倍多。\n我们使用下面的代码计算这些值。你可以看到，总体流行度只是目标变量的平均值（第 1 行）。同样，我们可以对不同的受保护特征组合取平均值（第 3-5 行）。\n# Calculate prevelance prev = df_fair[\u0026#39;y\u0026#39;].mean() prev_race = df_fair.groupby(\u0026#39;priv_race\u0026#39;)[\u0026#39;y\u0026#39;].mean() prev_sex = df_fair.groupby(\u0026#39;priv_sex\u0026#39;)[\u0026#39;y\u0026#39;].mean() prev_comb = df_fair.groupby([\u0026#39;priv_race\u0026#39;,\u0026#39;priv_sex\u0026#39;])[\u0026#39;y\u0026#39;].mean() 此时，你应该问自己，为什么我们在流行率方面存在如此大的差异。该数据集是使用1994 年的美国人口普查数据构建的。该国有基于性别和种族的歧视历史。最终，目标变量反映了这种歧视。从这个意义上讲，流行率可用于了解历史不公正在我们的目标变量中所占的程度。\n代理变量 分析潜在偏见来源的另一种方法是找到代理变量。这些是与我们的受保护特征高度相关或关联的模型特征。使用代理变量的模型可以有效地使用受保护特征来做出决策。\n我们可以用类似于在特征选择过程中查找重要特征的方式来查找代理变量。也就是说，我们使用特征和目标变量之间的某种关联度量。只不过现在，我们使用受保护的特征而不是目标变量。我们将研究两种关联度量——相互信息和特征重要性。\n在此之前，我们需要进行一些特征工程。我们首先像之前一样创建一个目标变量（第 2 行）。然后我们创建 6 个模型特征。首先，我们保留age、education-num和hours-per-week不变（第 5 行）。我们从marital-status和native-country创建二元特征（第 6-7 行）。最后，我们通过将原始职业分为 5 组来创建职业特征（第 9-16 行）。在下一节中，我们将使用这些相同的特征来构建我们的模型。\n# Target variable y = df[\u0026#39;y\u0026#39;].apply(lambda x:1 if x==\u0026#39;\u0026gt;50K\u0026#39; else 0) # Model features X = df[[\u0026#39;age\u0026#39;,\u0026#39;education-num\u0026#39;,\u0026#39;hours-per-week\u0026#39;]].copy() X[\u0026#39;marital-status\u0026#39;] = df[\u0026#39;marital-status\u0026#39;].apply(lambda x:1 if x==\u0026#39;Married-civ-spouse\u0026#39; else 0) X[\u0026#39;native-country\u0026#39;] = df[\u0026#39;native-country\u0026#39;].apply(lambda x:1 if x==\u0026#39;United-States\u0026#39; else 0) occ_groups = { \u0026#39;Priv-house-serv\u0026#39;:0,\u0026#39;Other-service\u0026#39;:0,\u0026#39;Handlers-cleaners\u0026#39;:0, \u0026#39;Farming-fishing\u0026#39;:1,\u0026#39;Machine-op-inspct\u0026#39;:1,\u0026#39;Adm-clerical\u0026#39;:1, \u0026#39;Transport-moving\u0026#39;:2,\u0026#39;Craft-repair\u0026#39;:2,\u0026#39;Sales\u0026#39;:2, \u0026#39;Armed-Forces\u0026#39;:3,\u0026#39;Tech-support\u0026#39;:3,\u0026#39;Protective-serv\u0026#39;:3, \u0026#39;Prof-specialty\u0026#39;:4,\u0026#39;Exec-managerial\u0026#39;:4, \u0026#39;?\u0026#39;:-1} X[\u0026#39;occupation\u0026#39;] = [occ_groups[x] if x in occ_groups else -1 for x in df[\u0026#39;occupation\u0026#39;]] 互信息是衡量两个变量之间非线性关联的指标。 它表示通过观察另一个变量，一个变量的不确定性降低了多少。 在图 2 中，您可以看到 6 个特征中每个特征与受保护特征之间的互信息值。 请注意，婚姻状况和性别之间的互信息值很高。 这表明这些变量之间可能存在关系。 换句话说，婚姻状况可能是性别的替代变量。\n互信息是两个变量之间非线性关联的度量。它表示通过观察一个变量，一个变量的不确定性减少了多少。在图 2 中，你可以看到 6 个特征和受保护特征之间的互信息值。请注意婚姻状况和性别之间的高值。这表明这些变量之间可能存在关系。换句话说，婚姻状况可能是性别的代理变量。\n我们使用下面的代码计算相互信息值。这是使用mutual_info_classif函数完成的。对于种族，我们传递特征矩阵（第 2 行）和种族保护特征（第 3 行）。我们还告诉函数 6 个特征中的哪些是离散的（第 4 行）。性别的代码类似（第 5 行）。\n#Calculate mutual information mut_race = mutual_info_classif(X, df_fair[\u0026#39;priv_race\u0026#39;], discrete_features=[1,3,4,5]) mut_sex = mutual_info_classif(X,df_fair[\u0026#39;priv_sex\u0026#39;],discrete_features=[1,3,4,5]) 我们可以采取的另一种方法是使用受保护的特征构建模型。也就是说，我们尝试使用 6 个模型特征来预测受保护的特征。然后，我们可以使用该模型的特征重要性分数作为关联度量。你可以在图 3 中看到此过程的结果。\n这个过程的另一个结果是我们得到了模型准确率。这些可以让我们衡量整体关联。预测种族的准确率为75.7% ，预测性别的准确率为79.4%。如果我们回到图 2 中的互信息值，这种差异是有道理的。你可以看到性别的值通常更高。最终，我们可以预期代理变量对性别的影响比对种族的影响更大。\n你可以在下面看到我们如何计算种族指标。我们首先获取一个平衡样本（第 2-7 行）。这样我们的数据集中就有相同数量的特权和非特权。然后我们使用此数据集构建模型（第 10-11 行）。请注意，我们使用种族保护功能作为目标变量。然后我们获得模型预测（第 12 行），计算准确率（第 15 行）并获得特征重要性分数（第 18 行）。\n# Get balanced sample y_race_0 = df_fair[df_fair[\u0026#39;priv_race\u0026#39;] == 0][\u0026#39;priv_race\u0026#39;] y_race_1 = df_fair[df_fair[\u0026#39;priv_race\u0026#39;] == 1][\u0026#39;priv_race\u0026#39;] y_race_1 = y_race_1.sample(len(y_race_0)) y_race = pd.concat([y_race_0,y_race_1]) X_race = X.loc[y_race.index] # Train model and get predictions model = xgb.XGBClassifier(objective=\u0026#34;binary:logistic\u0026#34;) model.fit(X_race, y_race) y_pred = model.predict(X_race) # Calculate accuracy acc_race = accuracy_score(y_race, y_pred) # Get feature importance fi_race = model.get_booster().get_score(importance_type=\u0026#39;weight\u0026#39;) 因此，我们已经看到数据集是不平衡的，特权群体的流行率更高**。**我们还发现了一些潜在的代理变量。但是，这种分析并没有告诉我们我们的模型是否会不公平。它只是强调了可能导致不公平模型的问题。在接下来的部分中，我们将建立一个模型并表明其预测是不公平的。最后，我们将回顾这一探索性分析。我们将看到它如何帮助解释导致不公平预测的原因。\n造型 我们使用下面的代码构建模型。我们使用 XGBClassfier 函数（第 2 行）。我们使用之前在代理变量部分定义的特征和目标变量训练模型。然后我们得到预测（第 6 行）并将它们添加到我们的 df_fair 数据集（第 7 行）。最后，该模型的准确率为 85%。精确率为 74%，召回率为 61%。我们现在想衡量这些预测的公平性。\n# Train model model = xgb.XGBClassifier(objective=\u0026#34;binary:logistic\u0026#34;) model.fit(X, y) # Get predictions y_pred = model.predict(X) df_fair[\u0026#39;y_pred\u0026#39;] = y_pred 在我们继续之前，如果你愿意，可以用自己的模型替换此模型。或者你可以尝试不同的模型特征。这是因为我们将使用的所有公平性措施都是[模型无关的]5。这意味着它们可以与任何模型一起使用。它们的工作原理是将预测与原始目标变量进行比较。最终，你将能够在大多数应用程序中应用这些指标。\n公平的定义 我们通过应用不同的公平定义来衡量公平性。大多数定义都涉及将人群分为特权群体和非特权群体。然后，你可以使用某些指标（例如准确率、FPR、FNR）比较这些群体。我们将看到，最佳指标显示谁从模型中受益。\n通常，模型的预测要么给某人带来好处，要么不带来好处。例如，银行模型可以预测某人不会拖欠贷款。这将带来获得贷款的好处。另一个好处的例子可能是收到工作机会。对于我们的模型，我们假设 Y = 1 将带来好处。也就是说，如果模型预测该人的收入超过 5 万美元，他们将以某种方式受益。\n准确性 首先，让我们讨论一下准确度，以及为什么它不是公平性的理想衡量标准。我们可以基于图 4中的混淆矩阵来计算准确度。这是一个标准的混淆矩阵，用于将模型预测与实际目标变量进行比较。这里 Y = 1 是正预测，Y=0 是负预测。在计算其他公平性指标时，我们也将参考这个矩阵。\n查看图 5，你可以看到我们如何使用混淆矩阵来计算准确度。准确度是真阴性和真阳性的数量除以总观察次数。换句话说，准确度是正确预测的百分比。\n表 4 给出了我们模型受保护特征的准确率。比例列给出了非特权 (0) 到特权 (1) 的准确率。对于两个受保护的特征，你可以看到非特权组的准确率实际上更高。这些结果可能会误导你相信该模型正在使非特权组受益。\n问题在于，准确率可能会掩盖模型的后果。例如，错误的正向预测 (FP) 会降低准确率。但是，该人仍会从此预测中受益。例如，即使预测不正确，他们仍会收到贷款或工作机会。\n机会均等（真实阳性率） 为了更好地体现模型的优势，我们可以使用真实阳性率 (TPR)。你可以在图 6 中看到我们如何计算 TPR。分母是实际阳性的数量。分子是正确预测的阳性的数量。换句话说，TPR 是被正确预测为阳性的实际阳性的百分比。\n请记住，我们假设积极的预测会带来一些好处。这意味着分母可以看作应该从模型中受益的人数。分子是应该和已经受益的人数。因此，TPR 可以解释为从模型中正确受益的人数百分比。\n例如，假设一个贷款模型，其中 Y=1 表示客户没有违约。分母是没有违约的人数。分子是没有违约的人数，我们预测他们不会违约。这意味着 TPR 是我们向其提供贷款的优质客户的百分比。对于招聘模型，它将被解释为收到工作邀请的优质候选人的百分比。\n表 5 给出了我们模型的 TPR。同样，该比率给出了无特权 (0) 到有特权 (1) 的 TPR。与准确度相比，你可以看到无特权群体的 TPR 较低。这表明无特权群体中从该模型中受益的比例较小。也就是说，正确预测高收入者收入较高的比例较小。\n与流行率一样，我们可以进一步找到受保护特征交集处的 TPR。请注意，当一个人同时处于非特权群体中时，TPR 甚至更低。事实上，白人男性的 TPR 比非白人女性高出 50% 以上。\n使用 TPR 可以让我们得到公式 1中对公平的第一个定义。在平等机会下，如果特权群体和非特权群体的 TPR 相等，我们认为该模型是公平的。在实践中，我们会为统计不确定性留出一些余地。我们可以要求差异小于某个截止值（公式 2）。在我们的分析中，我们采用了比率。在这种情况下，我们要求比率大于某个截止值（公式 3）。这确保了非特权群体的 TPR 不会明显小于特权群体的 TPR。\n问题是我们应该使用什么截止值？实际上，这个问题没有好的答案。这取决于你的行业和应用。如果你的模型具有重大影响，例如抵押贷款申请，你将需要更严格的截止值。截止值甚至可能由法律定义。无论哪种方式，在衡量公平性之前定义截止值都很重要。\n假阴性率 在某些情况下，你可能希望捕捉模型的负面后果。你可以使用图 8 中所示的 FNR 来做到这一点。同样，分母给出实际正数的数量。只不过现在我们将错误预测的负数的数量作为分子。换句话说，FNR 是错误预测为负数的实际正数的百分比。\nFNR 可以解释为错误地未从模型中受益的人的百分比。例如，它可能是应该获得但未获得贷款的客户的百分比。对于我们的模型，它是被预测为低收入的高收入者的百分比。\n你可以在表 7 中看到我们模型的 FNR 。现在，弱势群体的 FNR 更高。换句话说，特权群体中没有得到好处的比例更高。从这个意义上讲，我们得出的结论与使用均等赔率的 TPR 时类似。也就是说，该模型似乎对弱势群体不公平。\n事实上，要求 FNR 相等将给我们提供与平等机会相同的定义。这是因为公式 1中看到的线性关系。换句话说，相等的 TPR 意味着我们也拥有相等的 FNR。你应该记住，我们现在要求该比率小于某个截止值（公式 2）。\n使用 FNR 定义平等机会似乎没有必要。但是，在某些情况下，使用负面后果来定义公平性可以更好地阐明你的观点。例如，假设我们建立一个模型来预测皮肤癌。FNR 将给出患有癌症但未被诊断出患有癌症的人的百分比。这些错误可能会致命。最终，以这种方式定义公平性可以更好地凸显不公平模型的后果。\n均等赔率 我们可以通过查看假阳性率 (FPR) 来获取模型的优势。如图10所示，分母是实际阴性的数量。这意味着 FPR 是被错误预测为阳性的实际阴性的百分比。这可以解释为从模型中错误获益的人的百分比。例如，它将是收到工作机会的不合格人员的百分比。\n对于我们的模型，FPR 会给出预测为高收入的低收入者的数量。你可以在表 8 中看到这些值。我们再次得到特权群体的更高比率。这告诉我们，特权群体中更高比例的人从该模型中不当获益。\n这引出了公平的第二个定义，即均等机会。与平等机会一样，此定义要求 TPR 相等。现在我们还要求 FPR 相等。这意味着均等机会 可以被视为公平的更严格定义。为了使模型公平，总体利益应该相等，这也是有道理的。也就是说，应该有相似比例的群体既合法又非法地受益。\n均等化几率的一个优点是，我们如何定义目标变量并不重要。假设 Y = 0 会带来好处。在这种情况下，TPR 和 FPR 的解释互换。TPR 现在捕获错误的利益，而 FPR 现在捕获正确的利益。均等化几率已经使用了这两个比率，因此解释保持不变。相比之下，平等机会的解释发生了变化，因为它只考虑 TPR。\n不同影响 我们对公平性的最后一个定义是差异影响(DI)。我们首先计算图 12 所示的 PPP 率。这是被正确 (TP) 或错误 (FP) 预测为阳性的人的百分比。我们可以将其解释为将从模型中受益的人的百分比。\n对于我们的模型，它是我们预测高收入人群的百分比。你可以在表 9 中看到这些值。同样，这些数字表明该模型对弱势群体不公平。也就是说，他们中只有较小比例的人从该模型中受益。不过，在解释这些值时，我们应该考虑这个定义的一个缺点。我们将在本节末尾讨论这个问题。\n在DI下，如果我们有相等的 PPP 率（公式 1） ，我们认为一个模型是公平的。同样，在实践中，我们使用截止值来留出一些余地。这个定义应该代表不同影响的法律概念。在美国，有一个法律先例将截止值设置为0.8。也就是说，弱势群体的 PPP 不得低于弱势群体的 PPP 的 80% 。\nDI 的问题在于它没有考虑到基本事实。回想一下探索性分析中的流行率值。我们发现这些值存在偏差。特权群体的值较高。对于一个完全准确的模型，我们不会有假阳性。这意味着流行率将与不同影响率相同。换句话说，即使对于一个完全准确的模型，我们的不同影响率仍可能较低。\n在某些情况下，期望流行率或 DI 相等可能是合理的。例如，我们期望模型预测男性和女性成为工作优质候选人的比例相等。在其他情况下，这没有意义。例如，肤色较浅的人更容易患皮肤癌。我们预计肤色较浅的人患皮肤癌的几率更高。在这种情况下，低 DI 比率并不表示模型不公平。\n公平性定义代码 我们使用fairness_metrics函数来获取上述所有结果。该函数采用包含实际 (y) 和预测目标值 (y_pred) 的 DataFrame。它使用这些值创建混淆矩阵（第 5 行）。该矩阵具有我们在图 4 中看到的 4 个值。我们获取这 4 个值（第 6 行）并使用它们来计算公平性指标（第 8-13 行）。然后我们将这些指标作为数组返回（第 15 行）。\ndef fairness_metrics(df): \u0026#34;\u0026#34;\u0026#34;Calculate fairness for subgroup of population\u0026#34;\u0026#34;\u0026#34; # Confusion Matrix cm=confusion_matrix(df[\u0026#39;y\u0026#39;],df[\u0026#39;y_pred\u0026#39;]) TN, FP, FN, TP = cm.ravel() N = TP+FP+FN+TN # Total population ACC = (TP+TN)/N # Accuracy TPR = TP/(TP+FN) # True positive rate FPR = FP/(FP+TN) # False positive rate FNR = FN/(TP+FN) # False negative rate PPP = (TP + FP)/N # % predicted as positive return np.array([ACC, TPR, FPR, FNR, PPP]) 你可以在下面看到我们如何将此函数用于种族保护功能。我们首先将人口的子组传递给fairness_metrics函数**。**具体来说，我们获取特权组（第 2 行）和非特权组（第 3 行）的指标。然后，我们可以获取非特权组与特权组指标的比率（第 6 行）。\n# Calculate fairness metrics for race fm_race_1 = fairness_metrics(df_fair[df_fair.priv_race==1]) fm_race_0 = fairness_metrics(df_fair[df_fair.priv_race==0]) # Get ratio of fairness metrics fm_race = fm_race_0/fm_race_1 我们的模型为何有偏差？ 根据公平的不同定义，我们发现我们的模型对弱势群体不公平。然而，这些定义并没有告诉我们为什么我们的模型不公平。要做到这一点，我们需要做进一步的分析。一个好的起点是回到我们最初的探索性分析。\n例如，使用相互信息，我们发现婚姻状况是性别的潜在代理变量。我们可以通过查看表 10 中的细分来开始理解为什么会这样。请记住，婚姻状况 = 1 表示该人已婚。我们可以看到 62% 的男性已婚。而人口中只有 15% 的女性已婚。\n在表 11 中，我们可以看到已婚人士的流行率高出 6 倍以上。模型将在进行预测时使用这种关系。也就是说，它更有可能预测已婚人士的收入超过 5 万美元。问题是，正如我们上面所看到的，这些已婚人士中的大多数都是男性。换句话说，女性结婚的可能性较小，因此模型不太可能预测她们的收入超过 5 万美元。\n最后，要充分解释模型不公平的原因，还有更多工作要做。在这样做时，我们需要考虑不公平的所有潜在原因。我们在本文中谈到了一些原因。你也可以在下面的第一篇文章中深入了解它们。下一步是纠正不公平。我们将在以后的文章中研究定量和非定量方法。\nGithub, https://github.com/hivandu/public_articles/blob/main/src/algorithm%20fairness/Measuring%20Bias.ipynb\u0026#160;\u0026#x21a9;\u0026#xfe0e;\n数据集, https://archive.ics.uci.edu/ml/datasets/adult\u0026#160;\u0026#x21a9;\u0026#xfe0e;\n不公平的原因，将在之后的文章中详细分析。\u0026#160;\u0026#x21a9;\u0026#xfe0e;\n解决不公平问题的最佳方法, 也将在日后文章中详细描述。\u0026#160;\u0026#x21a9;\u0026#xfe0e;\n模型无关, 日后文章中描述\u0026#160;\u0026#x21a9;\u0026#xfe0e;\n","permalink":"https://hivan.me/posts/%E5%88%86%E6%9E%90%E6%9C%BA%E5%99%A8%E5%AD%A6%E4%B9%A0%E7%9A%84%E5%85%AC%E5%B9%B3%E6%80%A7/","summary":"\u003cblockquote\u003e\n\u003cp\u003e进行探索性公平性分析，并使用平等机会、均等几率和不同影响来衡量公平性\u003c/p\u003e\u003c/blockquote\u003e","title":"分析机器学习的公平性"},{"content":" 人工智能应用数学基础 人工智能Python基础 人工智能基础核心知识 人工智能BI核心知识 人工智能CV核心知识 BTS 的《Dynamite》1拥有 15,815,254 条评论，是 YouTube 上评论最多的视频之一。\n假设 BTS 成员想知道这些听众对这首歌的感受。每秒阅读一条评论，他仍然需要 4 个多月的时间。幸运的是，使用机器学习，他可以自动将每条评论标记为正面或负面。这被称为情绪分析。同样，通过在线评论、调查回复和社交媒体帖子，企业可以获得大量客户反馈。情绪分析对于分析和理解这些数据至关重要。\n在本文中，我们将介绍使用 Python 构建情绪分析模型的过程。具体来说，我们将使用 SVM 创建一个词袋模型。通过解释这个模型，我们还可以了解它的工作原理。在此过程中，您将学习文本处理的基础知识。我们将介绍关键的代码片段，您可以在 GitHub2上找到完整的项目。在深入研究所有这些之前，让我们先解释一下什么是情绪分析。\n什么是情绪分析？ 情绪是某人用语言表达的想法或感觉。考虑到这一点，情绪分析就是预测/提取这些想法或感觉的过程。我们想知道一篇文章的情绪是积极的、消极的还是中性的。我们所说的积极/消极情绪的具体含义取决于我们试图解决的问题。\n对于 BTS 示例，我们试图预测听众的观点。积极情绪意味着听众喜欢这首歌。我们可以使用情绪分析来标记我们平台上的潜在仇恨言论。在这种情况下，消极情绪意味着文本包含种族主义/性别歧视观点。其他一些例子包括预测讽刺/挖苦，甚至是预测一个人的意图（即他们是否打算购买产品）。\n使用 Python 进行情绪分析 因此，情绪分析模型有很多种。进行情绪分析的方法也有很多种。我们将集中精力应用其中一种方法。即使用 SVM 创建词袋模型。让我们从可以帮助我们做到这一点的 Python 包开始。\n包 在第 1-4 行，我们有一些标准包，例如 Pandas/NumPy 来处理我们的数据，以及 Matplotlib/Seaborn 来可视化它。对于建模，我们使用 scikit learn 中的 svm 包（第 6 行）。我们还使用一些指标包（第 7 行）来衡量我们模型的性能。最后一组包用于文本处理。它们将帮助我们清理文本数据并创建模型特征。\nimport pandas as pd import numpy as np import matplotlib.pyplot as plt import seaborn as sns from sklearn import svm from sklearn.metrics import accuracy_score,confusion_matrix import nltk from nltk.tokenize import word_tokenize from nltk.corpus import stopwords stopwords = stopwords.words(\u0026#39;english\u0026#39;) from sklearn.feature_extraction.text import CountVectorizer 数据集 为了训练我们的情绪分析模型，我们使用了 sentiment140 数据集3中的一组推文样本。该数据集包含 160 万条推文，这些推文被归类为具有积极或消极情绪。您可以在表 1 中看到一些示例。\n使用下面的代码，我们将加载整个数据集。1,600,000 行数据量很大，尤其是考虑到我们必须清理文本并从中创建特征。因此，为了使事情更易于管理，在第 9 行，我们随机抽取了 50,000 条推文。\n# load dataset data = pd.read_csv(data_path + \u0026#39;/training.1600000.processed.noemoticon.csv\u0026#39;, encoding=\u0026#39;latin-1\u0026#39;, header = None, usecols=[0,5], names=[\u0026#39;target\u0026#39;,\u0026#39;text\u0026#39;]) # random sample data = data.sample(n=50000,random_state=100).reset_index(drop=True) print(len(data)) print(sum(data.target==4)) data.head() --- 50000 24950 target\ttext 0\t4\t@nicholasmw 1 day u will find that girl worry 1\t0\tthere is nothing on tv and im so desperate to ... 2\t0\tVery excited that greys is on tonight. Not so ... 3\t4\t2pm\u0026#39;s again and again is a great song. Nichkhu... 4\t0\tMy teeth hurt 文本清理 下一步是清理文本。我们这样做是为了删除文本中不重要的部分，并希望使我们的模型更准确。具体来说，我们将文本变为小写并删除标点符号。我们还将从文本中删除非常常见的词，即停用词。\n为此，我们创建了下面的函数，该函数接受一段文本，执行上述清理并返回清理后的文本。在第 18 行中，我们将此函数应用于数据集中的每条推文。我们可以在表 2 中看到此函数如何清理文本的一些示例。\ndef clean_text(text): \u0026#34;\u0026#34;\u0026#34; Return cleaned text params ------------ text: string \u0026#34;\u0026#34;\u0026#34; text = text.lower() # lowercase tokens = word_tokenize(text) tokens = [t for t in tokens if not t in stopwords] # remove stopwords tokens = [t for t in tokens if t.isalnum()] # remove punctuation text_clean = \u0026#34; \u0026#34;.join(tokens) return text_clean text = data[\u0026#39;text\u0026#39;][3] print(text) clean_text(text) # clean text data[\u0026#39;text\u0026#39;] = [clean_text(text) for text in data[\u0026#39;text\u0026#39;]] --- 2pm\u0026#39;s again and again is a great song. Nichkhun hwaiting!!! \u0026#39;2pm great song nichkhun hwaiting\u0026#39; 请注意，所有清理过的推文都是小写，没有标点符号。单词“there”、“is”、“on”、“and”、“so”、“to”、“be”和“how”都已从第一条推文中删除。这些都是停用词的例子。我们预计这些词在正面和负面推文中都很常见。换句话说，它们不会告诉我们有关推文情绪的任何信息。因此，通过删除它们，我们希望留下能够传达情绪的单词。\n接下来，重要的是要考虑文本清理将如何影响您的模型。对于某些问题，停用词和标点符号等内容可能很重要。例如，愤怒的顾客可能更有可能使用感叹号！！！如果您不确定，您可以始终将文本清理视为超参数。您可以同时使用停用词和不使用停用词来训练模型，并查看对准确性的影响。\n特征工程（词袋） 即使经过清理，与所有 ML 模型一样，SVM 也无法理解文本。这意味着我们的模型无法将原始文本作为输入。我们必须首先以数学方式表示文本。换句话说，我们必须将推文转换为模型特征。一种方法是使用 N-gram。\nN-gram 是 N 个连续单词的集合。在图 2 中，我们看到了一个句子如何分解为 1-gram（单字）和 2-gram（双字）的示例。单字只是句子中的单个单词。双字是所有两个连续单词的集合。三字（3-gram）是所有 3 个连续单词的集合，依此类推。您可以通过简单地计算某些 N-gram 出现的次数来以数学方式表示文本。\n对于我们的问题，我们从推文中选取 1000 个最常见的单元词/二元词。也就是说，我们计算这些 N 元词在清理后的推文语料库中出现的次数，并选取前 1000 个。为了创建模型特征，我们计算这些 N 元词在每条推文中出现的次数。这种方法称为词袋法4。\n表 3 给出了使用此方法创建的特征矩阵的示例。顶行给出了 1000 个 N-gram 中的每一个。每条推文都有一个编号行。矩阵中的数字给出了 N-gram 在推文中出现的次数。例如，“抱歉”在推文 2 中出现了一次。本质上，我们将每条推文表示为一个向量。换句话说，我们使用 N-gram 计数对我们的推文进行矢量化。\n下面的代码用于创建其中一个特征矩阵。我们首先将数据集分成训练集（80%）和测试集（20%）。在第 6 行中，我们定义了一个 CountVectoriser，它将使用前 1000 个 unigrams/bigrams 对我们的推文进行矢量化。在第 7 行中，我们使用它来对我们的训练集进行矢量化。.fit_transform **()**函数将首先获取 1000 个最常见的 N-gram，然后计算它们在每条推文中出现的次数。\n# train test split train = data[0:40000] test = data[40000:50000].reset_index(drop=True) # Create count vectoriser vectorizer = CountVectorizer(ngram_range=(1, 2), max_features=1000) # Transform training corpus into feature matrix X = vectorizer.fit_transform(train[\u0026#39;text\u0026#39;]) feature_names = vectorizer.get_feature_names_out() x_train = pd.DataFrame(data=X.toarray(),columns=feature_names) y_train = train[\u0026#39;target\u0026#39;] 我们遵循类似的过程来矢量化我们的测试集。在本例中，我们使用**.transform()**函数。这将使用与训练集相同的列表来计算每个 N-gram 出现的次数。使用相同的 N-gram 列表来矢量化每个集合非常重要。对测试集使用不同的列表会导致模型做出错误的预测。\n# Transform testing corpus into feature matrix X = vectorizer.transform(test[\u0026#39;text\u0026#39;]) x_test = pd.DataFrame(data=X.toarray(),columns=feature_names) y_test = test[\u0026#39;target\u0026#39;] 最后，我们使用最小-最大缩放来缩放特征矩阵。这可确保所有特征都在同一范围内。这一点很重要，因为 SVM 可能会受到具有较大值的特征的影响。与 N-gram 列表一样，我们以相同的方式缩放两个集合（即使用训练集中的最大值和最小值）。\n# Min-Max scalling x_max = x_train.max() x_min = x_train.min() x_train = (x_train - x_min)/x_max x_test = (x_test - x_min)/x_max 我们使用从训练集中获得的 N-gram 和缩放权重转换了测试集。如上所述，这样做是为了让两个集合以相同的方式矢量化。这样做也是为了避免数据泄露。实际上，我们的模型将用于新的/未见过的推文。这些推文及其 N-gram 和权重在训练期间不可用。因此，为了更好地指示未来的性能，我们的模型应该在被视为未见过的集合上进行测试。\n造型 准备好训练集和测试集后，我们就可以训练模型了。 我们将在下面代码的第 2 行中完成这项工作。 在这里，我们在训练集上训练一个 SVM。 具体来说，我们使用一个具有线性核的 SVM，并将惩罚参数设置为 1。在第 5 行，我们使用该模型对测试集进行预测，在第 8 行，我们计算这些预测的准确率。\n# fit SVM model = svm.SVC(kernel=\u0026#39;linear\u0026#39;, C=1).fit(x_train, y_train) # get predictions on test set y_pred = model.predict(x_test) # accuracy on test set accuracy = accuracy_score(y_test,y_pred) print(\u0026#34;Accuracy: {}\u0026#34;.format(accuracy)) 最终，该模型在测试集上的准确率为 73.4%。 我们可以通过图 2 中的混淆矩阵更深入地了解模型的性能。 错误的负面推文有 915 条，而错误的正面推文则有 1737 条。 换句话说，大部分错误是由于模型错误地将具有负面情绪的推文预测为具有正面情绪。 因此，作为初稿，我们的模型还不算太差，但还有很大的改进空间。\n# create confusion matrix conf_matrix = confusion_matrix(y_test,y_pred) conf_matrix = pd.DataFrame(data = conf_matrix, columns=[\u0026#39;negative\u0026#39;,\u0026#39;positive\u0026#39;], index=[\u0026#39;negative\u0026#39;,\u0026#39;positive\u0026#39;]) # plot confusion matrix plt.figure(figsize=(10, 10), facecolor=\u0026#39;w\u0026#39;, edgecolor=\u0026#39;k\u0026#39;) sns.set(font_scale=1.5) sns.heatmap(conf_matrix,cmap=\u0026#39;coolwarm\u0026#39;,annot=True,fmt=\u0026#39;.5g\u0026#39;,cbar=False) plt.ylabel(\u0026#39;Actual\u0026#39;,size=20) plt.xlabel(\u0026#39;Predicted\u0026#39;,size=20) 我们可以通过几种方式来提高模型的性能。我们可以花更多时间来调整模型的超参数。如上所述，我们将惩罚参数设置为 1。这实际上是在测试了几个不同的值（即 0.001、0.01、0.1、1 和 10）并查看哪个值具有最高的 k 折交叉验证准确率后选择的。其他超参数（例如内核和文本清理步骤）可以以相同的方式进行调整。我们还可以解释我们的模型，弄清楚它的工作原理并根据这些发现进行更改。\n解释我们的模型 解释 SVM 的一种方法是查看模型权重/系数。在训练 SVM 的过程中，训练集中的每个 N-gram 都会被赋予权重。具有正权重的 N-gram 与积极情绪相关。同样，具有负权重的 N-gram 与消极情绪相关。\n图 3 显示了 1000 个 N-grams 中 15 个的系数。 前 5 个都具有较高的正向系数。 这是有道理的，因为您可能会认为包含 \u0026ldquo;yay \u0026ldquo;或 \u0026ldquo;drunk \u0026ldquo;等词的推文会有正面情绪。 同样，系数为负数的单词 \u0026ldquo;sucks\u0026rdquo;、\u0026ldquo;cant \u0026quot; 等都与负面情绪有关。 请注意，也有一些 N-grams 的系数接近于 0。\n# Create a dataset of model coefs coef = pd.DataFrame({\u0026#39;feature_names\u0026#39;:feature_names, \u0026#39;coef\u0026#39;: model.coef_[0]}) # Get some example coefs example = coef.loc[[128,988,385,519,769, 845,218,948,191,92, 711,810,388,782,93] ].sort_values(\u0026#39;coef\u0026#39;) # Plot coefs plt.figure(figsize=(10,8)) ticks = range(len(example)) color = [\u0026#39;#d43f3f\u0026#39;]*5 + [\u0026#39;#3f9dd4\u0026#39;]*5 + [\u0026#39;#3fd458\u0026#39;]*5 plt.barh(ticks, example.coef,color=color, align=\u0026#39;center\u0026#39;) plt.yticks(ticks, example.feature_names,size=15) 系数较小的 N-gram 不会对我们模型的预测产生太大影响。系数可能很小，因为 N-gram 往往出现在具有积极和消极情绪的推文中。换句话说，它们不会告诉我们有关推文情绪的任何信息。与停用词一样，我们可以删除这些词，并希望提高我们模型的性能。\n超参数调整和模型解释是我们提高准确度的众多方法之一。您还可以通过尝试不同的模型（如神经网络）来获得更好的结果。除了词袋模型，您还可以使用更高级的技术（如词嵌入）来矢量化推文。有很多选择，希望本文能为您提供一个良好的起点。\n情感分析是自然语言处理 (NLP) 中的一种问题。在下面的文章中，我将带您了解解决另一种问题的过程——语言识别。如果您对 NLP 感兴趣，我建议您阅读它。我们还基于上面解释的许多概念。\n深度神经网络语言识别\n使用 DNN 和字符三元组对一段文本的语言进行分类\n参考 BTS (방탄소년단) \u0026lsquo;Dynamite\u0026rsquo; Official MV, https://www.youtube.com/watch?v=gdZLi9oWNZg\u0026#160;\u0026#x21a9;\u0026#xfe0e;\n茶桁的公开文章代码仓库, https://github.com/hivandu/public_articles\u0026#160;\u0026#x21a9;\u0026#xfe0e;\n数据集, https://www.kaggle.com/kazanova/sentiment140\u0026#160;\u0026#x21a9;\u0026#xfe0e;\n词袋法, https://machinelearningmastery.com/gentle-introduction-bag-words-model/\u0026#160;\u0026#x21a9;\u0026#xfe0e;\n","permalink":"https://hivan.me/posts/%E4%BD%BF%E7%94%A8-python-%E5%88%9B%E5%BB%BA%E4%BD%A0%E7%9A%84%E7%AC%AC%E4%B8%80%E4%B8%AA%E6%83%85%E7%BB%AA%E5%88%86%E6%9E%90%E6%A8%A1%E5%9E%8B/","summary":"\u003cstrong\u003e「AI秘籍」系列课程：\u003c/strong\u003e","title":"使用 Python 创建你的第一个情绪分析模型"},{"content":"「AI秘籍」系列课程：\n人工智能应用数学基础\n人工智能Python基础\n人工智能基础核心知识\n人工智能BI核心知识\n人工智能CV核心知识\n药物的副作用可能取决于你的性别。吸入石棉会使吸烟者患肺癌的几率高于不吸烟者。如果你比较温和/自由，那么随着受教育程度的提高，你对气候变化的接受程度往往会提高。对于最保守的人来说，情况正好相反。这些都是数据交互的例子。识别和整合这些可以大大提高准确性并改变模型的解释。\n在本文中，我们将探讨分析数据集中交互作用的不同方法。我们讨论如何使用散点图和 ICE 图来可视化它们。然后，我们讨论查找/突出显示潜在交互作用的方法。这些方法包括特征重要性和 Friedman 的 H 统计量。你可以在GitHub1上找到用于此分析的 R 代码。在开始之前，有必要准确解释一下交互作用的含义。\n什么是交互？ 当特征与目标变量有某种关系时，我们称该特征具有预测性。例如，汽车的价格可能会随着汽车的老化而下降。年龄（feature）可用于模型中以预测汽车价格（target variable）。在某些情况下，目标变量和特征之间的关系取决于另一个特征的值。这称为特征之间的相互作用。\n以图 1 中的车龄与汽车价格的关系为例。这里我们有第二个特征 — 汽车类型。汽车可以是经典汽车（classic =1），也可以是普通汽车（classic =0）。对于普通汽车，价格会随着车龄的增加而下降，但对于经典汽车，车龄实际上会增加其价值。价格与车龄之间的关系取决于汽车类型。换句话说，车龄与汽车类型之间存在相互作用。\n结合这些交互可以提高我们模型的准确性。非线性模型（如随机森林）可以自动对交互进行建模。我们可以简单地将年龄和汽车类型作为特征，模型就会将交互纳入其预测中。对于线性模型（如线性回归），我们必须添加显式交互项2。为此，我们首先需要知道我们的数据中存在哪些交互。\n数据集 为了解释这些技术，我们随机生成了一个包含 1000 行的数据集。该数据集包括表 1 中列出的 5 个特征。这些特征用于预测员工的年终奖金。我们设计了数据集，因此经验和学位之间以及绩效和销售额之间存在相互作用。days_late 不与任何其他特征交互。\n由于相关特征的性质，这两个交互作用是不同的。学位是分类的，而经验是连续的。因此，我们有一个分类特征和连续特征之间的交互作用。对于另一个交互作用，我们有两个连续特征。我们将看到，我们仍然以相同的方式分析这些交互作用。\n可视化交互 我们首先可以使用简单的散点图来可视化这些相互作用。在图 2 中，我们可以看到经验和学位之间的相互作用。如果员工有学位，他们的奖金往往会随着经验的增加而增加。相比之下，当员工没有学位时，这些特征之间就没有关系。如果这是一个真实的数据集，我们希望对此有一个直观的解释。例如，受过教育的员工可能会承担更看重经验的职位。\n同样，我们可以在图 3 中看到销售额和绩效之间的相互作用。在这种情况下，可能不太清楚。我们现在有一个渐变配色方案，其中较暗的点表示较低的绩效评级。一般来说，奖金往往会随着销售额的增加而增加。仔细观察，你会发现较浅的点有更陡的斜率。对于更高的绩效评级，销售额的增加将带来更大的奖金增长。\n以这种方式可视化交互作用可能很直观，但并不总是有效。我们正在可视化目标变量与仅两个特征之间的关系。实际上，目标变量可能与许多特征有关系。这和统计变化的存在意味着散点图点将分散在潜在趋势周围。我们已经可以在上面的图表中看到这一点，而在真实的数据集中，情况会更糟。最终，为了清楚地看到交互作用，我们需要剔除其他特征和统计变化的影响。\nICE 图 这将我们带到了个体条件期望 (ICE) 图。要创建 ICE 图，我们首先要将模型拟合到我们的数据中。在我们的例子中，我们使用了一个有 100 棵树的随机森林。在表 2 中，我们的数据集中有两行用于训练模型。在最后一列中，我们可以看到每个员工的预测奖金。这是随机森林根据特征值做出的预测。要创建 ICE 图，我们改变一个特征的值，同时保持其他特征不变，并绘制结果预测。\n看看图 4，这可能更有意义。这里我们选取了表 2 中的两名员工。我们绘制了 days_late 每个可能值的预测奖金，同时保留了其他特征的原始值。（即，第一和第二名员工的经验将分别保持在 31 年和 35 年）。两个黑点对应于表 2 中的实际预测（即他们的真实 days_late 值）。\n最后，为了获得 ICE 图，我们对数据集中的每一行都遵循此过程。我们还将每条线居中，使它们在 y 轴上从 0 开始。粗线给出了所谓的部分依赖图(PDP)3。这是每个 days_late 值的平均部分 yhat（居中）值。查看 PDP，随着 days_late 的增加，预测奖金趋于减少。我们还可以看到大多数单个预测都遵循这一趋势。如果 days_late 与另一个特征相互作用，我们就不会期望这一点。我们将有遵循不同趋势的预测组。\n通过查看图 6 中的经验 ICE 图，你可以了解我们的意思。这里有两种不同的趋势。对于某些员工，预测奖金会随着经验的增加而增加；而对于某些员工，预测奖金不会随着经验的增加而增加。通过按学历对图表进行着色（即，学历为蓝色，否则为红色），你可以清楚地看到这是由于经验和学历之间的相互作用造成的。\n我们可以为销售绩效交互创建一个类似的图表。如果员工的绩效评级高于 5，则线条为蓝色，如果低于 5，则线条为红色。所有员工的预测奖金都会增加，但绩效评级较低的员工的奖金增加速度较慢。对于这两个 ICE 图，交互作用比使用其相应的散点图时更清晰。\n这些图非常有用，因为通过保持其他特征值不变，我们可以专注于一个特征的趋势。这就是预测如何由于该特征的变化而变化。此外，随机森林将模拟数据中的潜在趋势并使用这些趋势进行预测。因此，在绘制预测时，我们能够消除统计变化的影响。\n充分利用 ICE 地块 我们使用了随机森林，但 ICE 图实际上是一种模型无关技术。这意味着我们在创建它们时可以使用任何模型。但是，模型应该是非线性的（即 XGBoost、神经网络）。线性模型无法以创建这些图所需的方式对交互进行建模。模型的选择并不重要，但根据你的数据集，不同的模型可能更擅长捕捉底层交互。\n你使用的模型的准确性也不是那么重要。目标是可视化交互，而不是做出准确的预测。但是，你的模型越好，你的分析就越可靠。欠拟合的模型可能无法捕获交互，而过度拟合的模型可能会呈现实际上不存在的交互。最终，使用 k 折交叉验证或测试集测试你的模型非常重要。例如，你可以在图 8 中看到我们的随机森林的预测奖金值与实际奖金值的图。该模型并不完美，但我们能够捕捉到潜在的趋势。\n仅使用 ICE 图可能不足以找到相互作用。根据数据集的大小，可能的相互作用数量可能很大。例如，如果你有 20 个特征，则将有 174 个可能的成对相互作用。可视化并尝试分析所有这些 ICE 图将非常繁琐。因此，我们需要一种突出显示/缩小搜索范围的方法。在本文的其余部分，我们将讨论如何使用特征重要性、Friedman 的 H 统计量和领域知识来做到这一点。\n寻找相互作用 特征重要性 特征重要性是一个基于特定特征对模型准确性的提升程度的分数。如果我们在数据集中包含交互项，我们可以计算这些项的特征重要性。我们通过将每个特征的成对乘积相加（即经验 $\\times$ 学位）来实现这一点。然后，我们使用所有交互特征训练模型并计算最终的特征重要性。\n在图 9 中，你可以看到 10 个交互特征和 5 个原始特征的特征重要性。在这里，我们使用随机森林作为模型，并使用 MSE 的百分比增加作为特征重要性得分。我们可以看到，经验度和销售业绩交互项都具有最高重要性。这表明这些术语之间存在相互作用。\n你可能还会注意到，其他一些交互项也很重要（例如，经验.销售额）。我们没想到这一点，因为在生成数据集时，我们没有包括这两个特征之间的交互。下面的图 10 有助于解释我们为什么会得到这个结果。请注意，经验和销售额都与奖金呈正相关。这意味着这些特征的乘积具有正相关关系。\n这凸显了该方法的一个缺点。特征对预测的影响可以分为两部分。第一部分是它直接对预测产生的影响（即主效应）。第二部分是它通过与其他特征的交互产生的影响（即交互效应）。experience.sales 交互项具有较高的特征重要性，因为这两个单独特征的主效应。因此，我们需要一种方法来将交互效应与主效应隔离开来。\n弗里德曼的 H 统计量 Friedman的 H 统计量4就是这样做的。为了概述如何计算它，我们首先要拟合一个模型。在我们的例子中，我们使用与创建 ICE 图相同的随机森林。然后，我们在假设没有相互作用的情况下将观察到的部分依赖函数与部分依赖函数进行比较。两个函数之间的巨大差异表明存在相互作用。\n该统计数据有两个版本。第一个版本通过与所有其他特征的交互来衡量特征的效果。你可以在图 11 中看到此统计数据的值。值为 1 表示特征仅通过交互对预测产生影响（即没有主效应）。值为 0 表示没有交互（即只有主效应）。对于经验，我们的 H 统计量为 0.28。我们可以将其解释为 28% 的经验效果来自此特征与其他特征的交互。\nH 统计量的第二个版本衡量了两个特征之间的相互作用。图 12 中的第一个图表给出了经验和其他特征的 H 统计量。我们可以看到学位和经验之间的相互作用最为显著。同样，第二个图表给出了销售额的 H 统计量。同样，正如预期的那样，我们可以看到与绩效的相互作用最为显著。\n这个想法是首先使用总体 H 统计量来了解哪些特征具有交互作用。然后，我们可以使用第二个 H 统计量的图表来识别它们与之交互的其他特征。没有完美的统计数据，并且此过程可能并不总是有效。你可以看到，销售的总体 H 统计量相当低。它只有 0.11。这接近没有交互作用的 days_late 的 H 统计量。因此，按照此过程，我们可能决定不再进一步分析销售，从而错过了交互作用。\n领域知识 正如我们上面所看到的，仅仅依靠这些方法，我们可能会错过一些交互或识别出实际上不存在的交互。这就是为什么将你对该领域的任何领域知识融入到这个过程中很重要的原因。你可能已经知道一些可以使用这些技术确认的交互。你还应该对发现的任何新交互进行感知检查。他们应该对它们存在的原因有一个直观的解释。希望通过结合使用领域知识和这些统计技术，你能够找到一些有用的交互。\n参考 茶桁的公开文章代码仓库, https://github.com/conorosully/medium-articles\u0026#160;\u0026#x21a9;\u0026#xfe0e;\n显式交互项, https://stattrek.com/multiple-regression/interaction.aspx\u0026#160;\u0026#x21a9;\u0026#xfe0e;\n部分依赖图(PDP), https://christophm.github.io/interpretable-ml-book/pdp.html\u0026#160;\u0026#x21a9;\u0026#xfe0e;\nFriedman的 H 统计量, https://christophm.github.io/interpretable-ml-book/interaction.html#theory-friedmans-h-statistic\u0026#160;\u0026#x21a9;\u0026#xfe0e;\n","permalink":"https://hivan.me/posts/%E5%AF%BB%E6%89%BE%E5%B9%B6%E5%8F%AF%E8%A7%86%E5%8C%96%E4%BA%A4%E4%BA%92/","summary":"\u003cblockquote\u003e\n\u003cp\u003e使用特征重要性、弗里德曼 H 统计量和 ICE 图分析相互作用 \u0026gt; \u0026gt; 本文中的代码需要安装 R 语言包\u003c/p\u003e\u003c/blockquote\u003e","title":"寻找并可视化交互"},{"content":"「AI秘籍」系列课程：\n人工智能应用数学基础\n人工智能Python基础\n人工智能基础核心知识\n人工智能BI核心知识\n人工智能CV核心知识\n2024 年美国大选将至，关于此次选举据说非常戏剧，两位最大可能的提名候选人跟 2020 年如出一辙。不过，本次大选数据咱们是暂时没办法获得了，就拿 2020 年的那次数据来看看。\n2020 年美国大选带来了高度紧张的气氛、毫无根据的欺诈指控，最重要的是，带来了一些很棒的可视化效果。至少对数据科学家来说，这很重要。似乎你无论在哪里都看不到一些新颖的选举结果呈现方式。那么为什么不再添加一些呢？在本教程中，你将学习如何使用 Python 创建一些自己的可视化效果。\n你将学习如何创建两张 1976 年至 2016 年美国总统选举结果的交互式分级统计图。第一张地图有一个时间滑块。当你移动滑块时，地图将发生变化，以显示给定年份每个州的结果。对于第二张地图，每个州都变成了一个按钮。你可以单击该州以查看随时间变化的投票趋势。我将介绍代码，你可以在GitHub上找到完整的项目1。你也可以下载地图2，你应该能够在浏览器中打开和浏览它们。\nPython 包 我们将使用folium3构建地图。这是一个非常有用的包，可用于创建简单的地理空间数据可视化。除了 folium 之外，我们还将使用一些其他 Python 包。你可以使用以下代码导入它们。请确保你已先安装所有包。\nimport numpy as np import pandas as pd import matplotlib.pyplot as plt, mpld3 %matplotlib inline import json import datetime from shapely.geometry import Polygon, mapping import geopandas as gpd import folium from folium.plugins import TimeSliderChoropleth 数据源 美国形状文件 我们需要的第一个数据集是美国的 shapefile4。shapefile是一种用于存储地理空间矢量数据的文件格式。在我们的例子中，我们有一组坐标，它们定义了美国每个州的边界。我们将数据读入为GeoPandas 数据框。数据框的51行中的每一行都给出了州的名称和坐标（即几何图形）。\n# Get US states shapefile us_shape = gpd.read_file(data_path + \u0026#39;/States_shapefile/States_shapefile.shp\u0026#39;) us_shape = us_shape[[\u0026#39;State_Name\u0026#39;,\u0026#39;geometry\u0026#39;]] us_shape.head() --- State_Name\tgeometry 0\tALABAMA\tPOLYGON ((-85.07007 31.9807, -85.11515 31.9074... 1\tALASKA\tMULTIPOLYGON (((-161.33379 58.73325, -161.3824... 2\tARIZONA\tPOLYGON ((-114.52063 33.02771, -114.55909 33.0... 3\tARKANSAS\tPOLYGON ((-94.46169 34.19677, -94.45262 34.508... 4\tCALIFORNIA\tMULTIPOLYGON (((-121.66522 38.16929, -121.7823... 让我们使用此 Shapefile 创建第一张 folium 地图。在下面的代码中，我们初始化地图。通过设置 location=[50.77500, -100]，地图在打开时将聚焦于美国。然后，我们使用美国 Shapefile 和 GeoJson 函数向地图添加分级统计图。在图 1 中，你可以看到此代码创建的地图。Shapefile 为我们提供了工作基础，但我们需要另一个用于选举结果的数据集。\n# plot the shape file with folium m = folium.Map(location=[50.77500, -100],zoom_start=3) choropleth =folium.GeoJson(data= us_shape.to_json()) m.add_child(choropleth) 选举结果数据集 对于选举结果，我们使用麻省理工学院选举数据和科学实验室提供的数据集5。它包含 1976 年至 2020 年美国总统选举结果。该数据集包含该年每个州、年份和参选候选人的行。为了让事情变得简单一点，我们应该先将此数据集转换为不同的格式。\n# Get election data election = pd.read_csv(data_path + \u0026#34;/U.S. President 1976–2020/1976-2020-president.csv\u0026#34; ) election.replace(\u0026#39;democratic-farmer-labor\u0026#39;,\u0026#39;democrat\u0026#39;,inplace=True) election.head() --- year\tstate\tstate_po\tstate_fips\tstate_cen\tstate_ic\toffice\tcandidate\tparty_detailed\twritein\tcandidatevotes\ttotalvotes\tversion\tnotes\tparty_simplified 0\t1976\tALABAMA\tAL\t1\t63\t41\tUS PRESIDENT\tCARTER, JIMMY\tDEMOCRAT\tFalse\t659170\t1182850\t20210113\tNaN\tDEMOCRAT 1\t1976\tALABAMA\tAL\t1\t63\t41\tUS PRESIDENT\tFORD, GERALD\tREPUBLICAN\tFalse\t504070\t1182850\t20210113\tNaN\tREPUBLICAN 2\t1976\tALABAMA\tAL\t1\t63\t41\tUS PRESIDENT\tMADDOX, LESTER\tAMERICAN INDEPENDENT PARTY\tFalse\t9198\t1182850\t20210113\tNaN\tOTHER 3\t1976\tALABAMA\tAL\t1\t63\t41\tUS PRESIDENT\tBUBAR, BENJAMIN \u0026#34;\u0026#34;BEN\u0026#34;\u0026#34;\tPROHIBITION\tFalse\t6669\t1182850\t20210113\tNaN\tOTHER 4\t1976\tALABAMA\tAL\t1\t63\t41\tUS PRESIDENT\tHALL, GUS\tCOMMUNIST PARTY USE\tFalse\t1954\t1182850\t20210113\tNaN\tOTHER 使用下面的代码，我们将数据集转换为嵌套字典。对于每一年，我们都有一个以州名作为键的字典。对于每个州，都有一个字典给出民主党和共和党候选人的投票数。字典的形式如下：\n{ \u0026lt;year\u0026gt;: { \u0026lt;state\u0026gt; : {\u0026#39;dem\u0026#39;:\u0026lt;#votes\u0026gt;, \u0026#39;rep\u0026#39;:\u0026lt;#votes\u0026gt;}, \u0026lt;state\u0026gt; : {\u0026#39;dem\u0026#39;:\u0026lt;#votes\u0026gt;, \u0026#39;rep\u0026#39;:\u0026lt;#votes\u0026gt;}, ...}, ... } # Transform election data states = set(election[\u0026#39;state\u0026#39;]) results = {} for year in range(1976,2024,4): result = {} for state in states: state_year = election[(election.year == year) \u0026amp; (election.state == state)] dem = max(state_year[state_year.party_simplified == \u0026#39;DEMOCRAT\u0026#39;][\u0026#39;candidatevotes\u0026#39;]) rep = max(state_year[state_year.party_simplified == \u0026#39;REPUBLICAN\u0026#39;][\u0026#39;candidatevotes\u0026#39;]) result[state] = {\u0026#39;dem\u0026#39;:dem, \u0026#39;rep\u0026#39;:rep} results[year] = result results --- {1976: {\u0026#39;TEXAS\u0026#39;: {\u0026#39;dem\u0026#39;: 2082319, \u0026#39;rep\u0026#39;: 1953300}, ... \u0026#39;MISSOURI\u0026#39;: {\u0026#39;dem\u0026#39;: 998387, \u0026#39;rep\u0026#39;: 927443}}, 1980: {\u0026#39;TEXAS\u0026#39;: {\u0026#39;dem\u0026#39;: 1881147, \u0026#39;rep\u0026#39;: 2510705}, ... 2020: {\u0026#39;TEXAS\u0026#39;: {\u0026#39;dem\u0026#39;: 5259126, \u0026#39;rep\u0026#39;: 5890347}, ... \u0026#39;MISSOURI\u0026#39;: {\u0026#39;dem\u0026#39;: 1253014, \u0026#39;rep\u0026#39;: 1718736}}} 数据可视化 在开始介绍交互式地图之前，让我们先使用这些数据集来创建一个简单的等值线图。我们首先需要定义两个函数。state_style 函数返回一个用于定义州的颜色和边界的字典。如果某个州在某一年投票给民主党，则该州将变为蓝色；如果投票给共和党，则该州将变为红色。该函数返回的字典略有不同，具体取决于它是被 style_dictionary 还是 style_function 使用。\ndef state_style(state,year,function=False): \u0026#34;\u0026#34;\u0026#34; Returns the style for a state in a given year \u0026#34;\u0026#34;\u0026#34; state_results = results[year][state] #Set state colour if state_results[\u0026#39;dem\u0026#39;] \u0026gt;= state_results[\u0026#39;rep\u0026#39;]: color = \u0026#39;#4f7bff\u0026#39; #blue else: color = \u0026#39;#ff5b4f\u0026#39; #red #Set state style if function == False: # Format for style_dictionary state_style = { \u0026#39;opacity\u0026#39;: 1, \u0026#39;color\u0026#39;: color, } else: # Format for style_fucntion state_style = { \u0026#39;fillOpacity\u0026#39;: 1, \u0026#39;weight\u0026#39;: 1, \u0026#39;fillColor\u0026#39;: color, \u0026#39;color\u0026#39;: \u0026#39;#000000\u0026#39;} return state_style 对于此等值线图，我们将使用 style_function。 GeoJson 包使用此函数将 GeoJson 特征映射到样式。在我们的例子中，GeoJson 特征将包含有关州的信息（即名称和几何形状）。这些特征由 GeoJson 包传递给 style_function。通过设置 year=2020，我们使用 2020 年选举的结果来定义每个州的样式。\ndef style_function(feature): \u0026#34;\u0026#34;\u0026#34; style_function used by the GeoJson folium function \u0026#34;\u0026#34;\u0026#34; state = feature[\u0026#39;properties\u0026#39;][\u0026#39;State_Name\u0026#39;] style = state_style(state,year=2020,function=True) return style 现在，我们可以使用这些函数来创建我们的第一个等值线图。代码与我们用于创建第一张地图的代码非常相似。唯一的区别是我们现在将 style_function 传递给 GeoJson 函数。如上所述，这会根据选举结果为每个州赋予一种颜色。生成的地图如图 2 所示。现在，让我们看看如何改进这张地图并使其更具交互性。\n# plot the choropleth m = folium.Map(location=[50.77500, -100],zoom_start=3) choropleth =folium.GeoJson(data= us_shape.to_json(),style_function=style_function) m.add_child(choropleth) 地图 1：分级统计图滑块 我们首先创建一个带有时间滑块的等值线地图。这是使用 TimeSliderChoropleth 函数完成的。此函数假定所有日期都采用 Unix 时间格式（即时间戳）。因此，我们使用year_to_ts函数将选举年份转换为时间戳。例如，2020 年将转换为 \u0026ldquo;1577808000\u0026rdquo;。\ndef year_to_ts(year): \u0026#34;\u0026#34;\u0026#34; Convert year to timestamp \u0026#34;\u0026#34;\u0026#34; time = datetime.datetime(year, 1, 1, 0, 0).strftime(\u0026#39;%s\u0026#39;) if len(time)==9: time =\u0026#39;0{}\u0026#39;.format(time) return time 我们需要定义的第二个函数style_dictionary返回一个样式字典。这与style_function类似，只是我们现在处理的是时间序列数据。因此，对于每个州，我们需要定义其从 1976 年到 2016 年每年的样式。style_dictionary函数返回一个嵌套字典，形式如下：\n{ \u0026lt;ID\u0026gt;: { \u0026lt;timestamp\u0026gt; : {\u0026#39;opacity\u0026#39;:1, \u0026#39;color\u0026#39;:\u0026lt;hex_color\u0026gt;}, \u0026lt;timestamp\u0026gt; : {\u0026#39;opacity\u0026#39;:1, \u0026#39;color\u0026#39;:\u0026lt;hex_color\u0026gt;}, ...}, ... } 上面提到的 ID 是分配给每个州的唯一 ID。它由.to_json()函数自动分配。TimeSliderChoropleth 使用这些 ID 将州映射到其样式。因此，为了确保我们有正确的映射，我们首先创建从 ID 到州名的映射。这在下面的第 7 行到第 13 行中完成。该函数的其余部分使用上面看到的形式创建字典。\ndef style_dictionary(): \u0026#34;\u0026#34;\u0026#34; style_dictionary used by the TimeSliderChoropleth folium function \u0026#34;\u0026#34;\u0026#34; # get ids used by TimeSliderChoropleth ID = {} state_json = json.loads(us_shape.to_json()) for state in state_json[\u0026#39;features\u0026#39;]: state_id = state[\u0026#39;id\u0026#39;] state_name = state[\u0026#39;properties\u0026#39;][\u0026#39;State_Name\u0026#39;] ID[state_name] = state_id #create style dictionary style_dic= {} for state in states: state_dic = {} for year in range(1976,2024,4): time = year_to_ts(year) state_dic[time] = state_style(state,year) style_dic[ID[state]] = state_dic return style_dic 现在我们准备创建地图了。同样，代码与之前类似，只是我们使用了 TimeSliderChoropleth 函数并传入了样式字典。代码的结果可以在图 3 中看到。你将能够滑动地图顶部的条形图来查看随时间变化的选举结果。例如，从 2012 年到 2020 年，我们可以看到几个州变成红色。这导致共和党候选人获胜。\n# Create time slider map m = folium.Map(location=[50.77500, -100],zoom_start=3) ts = TimeSliderChoropleth(us_shape.to_json(), style_dictionary()) m.add_child(ts) m.save(\u0026#34;../figures/us_election_map1.html\u0026#34;) 我们应该在上面的第 6 行提到保存地图的代码。此行将地图保存为 HTML 文件。你可以在任何浏览器中打开并浏览它。如果你使用的是 jupyter 笔记本，地图也会显示在代码块下方。如果地图太复杂，笔记本可能无法呈现它。在这种情况下，你必须保存地图并在浏览器中打开它，然后才能看到它。\n地图 2：分级统计图按钮 对于下一张地图，我们将把每个州变成一个按钮。你可以单击该州以查看随时间变化的投票趋势。首先，要创建这些趋势图，我们使用以下代码。getFigure 函数为给定的州创建标准 matplotlib 图表。在最后几行中，我们将图表转换为 HTML 并将其添加到 IFrame。这样它就可以嵌入到我们的 folium 地图中。你可以在图 5 中看到为加利福尼亚州制作的图表示例。\ndef getFigure(state): \u0026#34;\u0026#34;\u0026#34; Plot voting trends from a given state \u0026#34;\u0026#34;\u0026#34; #Get number of votes years = range(1976,2024,4) dems = [] reps =[] for year in years: result = results[year][state] dems.append(result[\u0026#39;dem\u0026#39;]/1000000) reps.append(result[\u0026#39;rep\u0026#39;]/1000000) #Plot number of votes fig = plt.figure(figsize=(8,4)) plt.plot(years,dems,label=\u0026#39;Democrat\u0026#39;,color=\u0026#39;#4f7bff\u0026#39;) plt.plot(years,reps,label=\u0026#39;Republican\u0026#39;,color=\u0026#39;#ff5b4f\u0026#39;) plt.title(state,size = 18) plt.ticklabel_format(style=\u0026#39;plain\u0026#39;) plt.xlabel(\u0026#39;Year\u0026#39;,size =14) plt.xticks(years) plt.ylabel(\u0026#39;Votes (millions)\u0026#39;,size =14) plt.legend(loc =0) #Add figure to iframe html = mpld3.fig_to_html(fig) iframe = folium.IFrame(html=html,width = 600, height = 300) return iframe 在创建按钮等值线图之前，我们必须定义最后一个函数。highlight_style 函数用于定义鼠标悬停在某个状态上时的样式。发生这种情况时，该状态将变得略微阴影化。这使我们能够在单击鼠标之前看到鼠标处于什么状态。\ndef highlight_style(feature): \u0026#34;\u0026#34;\u0026#34; style_function used when choropleth button is highighted \u0026#34;\u0026#34;\u0026#34; return {\u0026#39;fillOpacity\u0026#39;: 0.2, \u0026#39;weight\u0026#39;: 1, \u0026#39;fillColor\u0026#39;: \u0026#39;#000000\u0026#39;, \u0026#39;color\u0026#39;: \u0026#39;#000000\u0026#39;} 最后，为了创建地图，我们首先使用 2020 年的结果创建一个分级统计图。我们使用与图 2 中的地图完全相同的代码来执行此操作。然后，使用每个州的几何图形，我们创建一个州标记并向每个标记添加一个弹出窗口。每个弹出窗口都包含上面讨论的嵌入式图表之一。单击标记时，将显示弹出窗口，我们将能够看到投票趋势。\n# plot the shape file with folium m = folium.Map(location=[50.77500, -100],zoom_start=5,max_zoom=5) #Initialize map choropleth =folium.GeoJson(data= us_shape.to_json(), style_function=style_function) m.add_child(choropleth) # Create popup button for each state for i in range(len(us_shape)): geometry = us_shape.loc[i][\u0026#39;geometry\u0026#39;] state_name = us_shape.loc[i][\u0026#39;State_Name\u0026#39;] popup = folium.Popup(getFigure(state_name),max_width=1000) state_marker = folium.GeoJson(data=mapping(geometry), highlight_function = highlight_style) state_marker.add_child(popup) m.add_child(state_marker) m.save(\u0026#34;../figures/us_election_map2.html\u0026#34;) 你可以在图 4 中看到此代码的结果。你可以看到将鼠标悬停在某个州上方会如何突出显示该州。还可以单击德克萨斯州和加利福尼亚州以显示其趋势。在笔记本中查看此地图可能会有些困难。在这种情况下，请将其保存为 HTML 文件并在浏览器中打开。\n本文到这里就要结束了，与本文不同，2024 年美国大选尚未开始。届时，会有很多新的数据可供使用，我们将能够使用 20204 年的数据更新可视化。我们会看到各州的颜色发生变化，趋势是否发生变化。这些变化的原因很复杂。像这样的可视化是帮助我们理解它们的一个很好的步骤。\n茶桁的公开文章代码仓库, https://github.com/hivandu/public_articles\u0026#160;\u0026#x21a9;\u0026#xfe0e;\n地图 HTML 文件: https://github.com/hivandu/public_articles/tree/main/maps\u0026#160;\u0026#x21a9;\u0026#xfe0e;\nFolium, https://python-visualization.github.io/folium/\u0026#160;\u0026#x21a9;\u0026#xfe0e;\nshapefile, https://alicia.data.socrata.com/Government/States-21basic/jhnu-yfrj/data\u0026#160;\u0026#x21a9;\u0026#xfe0e;\n选举数据, https://dataverse.harvard.edu/dataset.xhtml?persistentId=doi:10.7910/DVN/42MVDX\u0026#160;\u0026#x21a9;\u0026#xfe0e;\n","permalink":"https://hivan.me/posts/%E4%BD%BF%E7%94%A8-python-%E7%BB%98%E5%88%B6%E7%BE%8E%E5%9B%BD%E9%80%89%E4%B8%BE%E5%88%86%E7%BA%A7%E7%BB%9F%E8%AE%A1%E5%9B%BE/","summary":"\u003cblockquote\u003e\n\u003cp\u003e如何创建美国选举结果的时间序列分级统计图 \u0026gt; \u0026gt; 数据地址为源地址，如果失效请与我联系。\u003c/p\u003e\u003c/blockquote\u003e","title":"使用 Python 绘制美国选举分级统计图"},{"content":"「AI秘籍」系列课程：\n人工智能应用数学基础\n人工智能Python基础\n人工智能基础核心知识\n人工智能BI核心知识\n人工智能CV核心知识\n我们是否应该始终信任表现良好的模型？模型可能会拒绝你的抵押贷款申请或诊断你患有癌症。这些决定的后果是严重的，即使它们是正确的，我们也希望得到解释。人类可以告诉你，你的收入太低，无法获得抵押贷款，或者特定的细胞群可能是恶性的。提供类似解释的模型比仅提供预测的模型更有用。\n通过获得这些解释，我们说我们正在解释一个机器学习模型。在本文的其余部分，我们将更详细地解释可解释性的含义。然后，我们将继续讨论能够解释模型的重要性和好处。然而，仍然存在一些缺点。最后，我们将讨论这些问题，以及为什么在某些情况下，你可能更喜欢不太可解释的模型。\n我们所说的可解释性是什么意思？ 在之前的一篇文章中，我讨论了模型可解释性的概念以及它与 interpretable 和 explainable 的机器学习的关系。大部分时候，我们可以将 interpretable 理解为可解释模型，将 explainable 理解为解释性模型。其区别就在于前者通常比较简单、透明，人类可以很容易理解模型是如何得出结论的，而后者则需要通过后处理技术和工具来解释复杂和黑箱模型的决策过程，通过解释工具和方法，揭示模型的内部工作机制和决策依据。\n总而言之，可解释性是指模型在人类眼中可以被理解的程度。如果人类更容易理解模型 A 如何进行预测，则模型 A 比模型 B 更具可解释性。例如，卷积神经网络的可解释性不如随机森林，而随机森林的可解释性又不如决策树。\n考虑到这一点，如果一个模型无需任何其他辅助/技术就能被理解，我们就说它是一个可解释的模型。Interpretable 的模型具有高度可解释性。相比之下，Explainable 的模型太复杂了，如果没有其他技术的帮助就无法理解。我们说这些模型的可解释性很低。我们可以在图 1 中看到这些概念是如何关联的。一般来说，模型可以分为 interpretable 或者 explainable，但也存在人们意见不一致的灰色地带。\n为什么可解释性很重要？ 如上所述，我们需要额外的技术，例如[特征重要性](https://machinelearningmastery.com/calculate-feature-importance-with-python/#:~:text=Feature importance refers to techniques,at predicting a target variable.)1或LIME: https://github.com/marcotcr/lime，来了解 explainable 模型的工作原理。实现这些技术可能需要付出很多努力，而且重要的是，它们只能提供模型工作原理的近似值。因此，我们不能完全确定我们理解了 explainable 模型。在比较 interpretable 模型时，我们可能会遇到类似的情况。\n例如，逻辑回归和决策树。这些都不需要额外的技术，但逻辑回归可能仍需要更多努力来解释。我们需要了解 S 型函数以及系数与几率/概率的关系。这种复杂性也可能导致我们的解释错误。一般来说，模型越容易解释，它就越容易理解，我们就越能确定我们的理解是正确的。可解释性很重要，因为它有很多好处。\n更容易解释 我们的第一个好处是 interpretable 模型更容易向其他人解释。对于任何主题，我们理解得越好，解释起来就越容易。我们还应该能够用简单的术语来解释它（即不提及技术细节）。在行业中，许多人可能希望对你的模型的工作原理进行简单的解释。这些人不一定具有技术背景或机器学习经验。\n例如，假设我们创建了一个模型，可以预测某人是否会提出人寿保险索赔。我们希望使用此模型来自动化公司的人寿保险承保。为了批准该模型，我们的老板需要详细解释其工作原理。不满的客户可能会要求解释为什么他们没有获得人寿保险批准。监管机构甚至可以依法要求提供这样的解释。\n试图向这些人解释神经网络如何进行预测可能会引起很多困惑。由于不确定性，他们甚至可能不会接受这种解释。相比之下，像逻辑回归这样的可解释模型可以用人类的术语来理解。这意味着它们可以用人类的术语来解释。例如，我们可以精确地解释顾客的吸烟习惯在多大程度上增加了他们的死亡概率。\n更容易感知、检查和修复错误 上面描述的关系是因果关系（即吸烟导致癌症/死亡）。一般来说，机器学习模型只关心关联。例如，模型可以使用某人的原籍国来预测他们是否患有皮肤癌。但是，就像吸烟一样，我们能说某人的国家导致癌症吗？原因是皮肤癌是由阳光引起的，而有些国家比其他国家阳光更充足。所以我们只能说皮肤癌与某些国家有关。\n华盛顿大学研究人员进行的一项实验很好地说明了关联可能出错的地方。研究人员训练了一个图像识别模型，将动物分类为哈士奇或狼。他们使用 LIME 试图了解他们的模型如何做出预测。在图 2 中，我们可以看到该模型的预测是基于图像背景。如果背景有雪，动物总是被归类为狼。他们基本上建立了一个检测雪的模型。\n问题在于狼与雪有关。狼通常会在雪地里出现，而哈士奇则不会。这个例子告诉我们，模型不仅可以做出错误的预测，还可以以错误的方式做出正确的预测。作为数据科学家，我们需要检查我们的模型，以确保它们不会以这种方式进行预测。你的模型越容易解释，就越容易做到这一点。\n更容易确定未来表现 随着时间的推移，模型的预测能力可能会下降。这是因为模型特征和目标变量之间的关系可能会发生变化。例如，由于工资差距，收入目前可能是性别的良好预测指标。随着社会变得更加平等，收入将失去其预测能力。我们需要意识到这些潜在的变化及其对我们模型的影响。对于 explainable 模型来说，这一点更难做到。由于特征的使用方式不太清楚，即使我们知道对单个特征的影响，我们也可能无法判断对整个模型的影响。\n更容易从模型中学习 试图在未知事物中寻找意义是人类的天性。机器学习可以帮助我们发现数据中未知的模式。但是，我们无法仅通过查看模型的预测来识别这些模式。如果我们无法解释我们的模型，那么任何教训都会丢失。最终，模型的可解释性越差，从中学习就越困难。\n算法公平性 重要的是，你的模型必须做出公正的决策，这样它们才不会延续任何历史不公正。识别偏见的来源可能很困难。它通常来自模型特征与受保护变量（例如种族或性别）之间的关联。例如，由于南非有强制隔离的历史，种族与某人的所在地\\邻里关系密切。位置可以作为种族的代理。使用位置的模型可能会偏向某一种族。\n使用 interpretable 模型并不一定意味着你将拥有一个无偏见的模型。这也不意味着更容易确定模型是否公平。这是因为大多数公平性衡量标准（例如假阳性率、不同影响）与模型无关。对于任何模型，它们都很容易计算。使用 interpretable 模型确实可以更轻松地识别和纠正偏见来源。我们知道正在使用哪些特征，并且可以检查其中哪些与受保护的变量相关。\n可解释性的缺点 好的，我们明白了…… interpretable 模型很棒。它们更容易理解、解释和学习。它们还使我们能够更好地感知当前性能、未来性能和模型公平性。然而，可解释性也存在缺点，在某些情况下我们更喜欢 explainable 模型。\n容易被操纵 基于机器学习的系统容易受到操纵或欺诈。例如，假设我们有一个自动发放汽车贷款的系统。一个重要特征可能是信用卡数量。客户拥有的卡越多，风险就越大。如果客户知道这一点，他们可以暂时取消所有卡，申请汽车贷款，然后重新申请所有信用卡。\n客户取消信用卡时偿还贷款的概率不会改变。客户操纵模型做出了错误的预测。模型越容易解释，就越透明，越容易操纵。即使模型的内部工作原理是保密的，情况也是如此。特征和目标变量之间的关系通常更简单，因此更容易猜测。\n学习内容更少 我们提到，interpretable 模型更容易学习。但另一方面，它们不太可能教会我们新东西。像神经网络这样的 explainable 模型可以自动模拟数据中的交互和非线性关系。通过解释这些模型，我们可以发现这些我们从未知道存在的关系。\n相比之下，线性回归等算法只能对线性关系进行建模。要对非线性关系进行建模，我们必须使用特征工程将任何相关变量纳入我们的数据集。这将需要事先了解这些关系，从而违背了解释模型的目的。\n领域知识/专业知识要求 构建 interpretable 模型需要大量的领域知识和专业知识。通常，interpretable 模型（如回归）只能对数据中的线性关系进行建模。要对非线性关系进行建模，我们必须执行特征工程。例如，对于医学诊断模型，我们可能希望使用身高和体重来计算 BMI。了解哪些特征具有预测性以及创建哪些特征需要特定领域的领域知识。\n你的团队可能不具备这些知识。或者，你可以使用 explainable 模型，该模型将自动对数据中的非线性关系进行建模。这样就无需创建任何新特征；本质上将思考留给了计算机。正如我们上面详细讨论的那样，缺点是，对如何使用这些特征进行预测的理解较差。\n复杂性与准确性之间的权衡 从上面我们可以看出，一般来说，模型越简单，可解释性就越强。因此，为了获得更高的可解释性，可能会以较低的准确度为代价。这是因为，在某些情况下，较简单的模型可能会做出不太准确的预测。这实际上取决于你要解决的问题。例如，使用逻辑回归进行图像识别会得到较差的结果。\n对于许多问题，interpretable 模型的表现与 explainable 模型一样好。在之前的文章「特征工程的力量」中，我们将 interpretable 模型 Logistic 回归与 explainable 模型神经网络进行了比较。我们证明，通过对我们的问题进行一些思考并创建新特征，我们可以用 interpretable 模型实现类似的准确性。这是对我们在本文中讨论的一些概念的一个很好的实践。\n特征工程的力量 为什么你应该使用逻辑回归来建模非线性决策边界（使用 Python 代码）\n参考 How to Calculate Feature Importance With Python, https://machinelearningmastery.com/calculate-feature-importance-with-python/#:~:text=Feature%20importance%20refers%20to%20techniques,at%20predicting%20a%20target%20variable.\u0026#160;\u0026#x21a9;\u0026#xfe0e;\n","permalink":"https://hivan.me/posts/%E6%9C%BA%E5%99%A8%E5%AD%A6%E4%B9%A0%E4%B8%AD%E7%9A%84%E5%8F%AF%E8%A7%A3%E9%87%8A%E6%80%A7/","summary":"\u003cblockquote\u003e\n\u003cp\u003e为什么我们需要了解模型如何进行预测\u003c/p\u003e\u003c/blockquote\u003e","title":"机器学习中的可解释性"},{"content":"「AI秘籍」系列课程：\n人工智能应用数学基础\n人工智能Python基础\n人工智能基础核心知识\n人工智能BI核心知识\n人工智能CV核心知识\n通过添加现有特征的幂，多项式回归可以帮助你充分利用数据集。它允许我们甚至使用简单的模型（如线性回归）来建模非线性关系。这可以提高模型的准确性，但如果使用不当，可能会发生过度拟合。我们希望避免这种情况，因为它会导致你的模型在未来表现不佳。\n在本文中，我们将解释多项式回归的概念，并说明它如何导致过度拟合。我们还将讨论一些可用于避免过度拟合的技术。这些包括使用 k 倍交叉验证或保留集，但最重要的是，我们将讨论如何应用领域知识来帮助你避免过度拟合。在文章我不会讨论任何代码，但是你可以在GitHub1上找到完整的项目，本文项目 polynomial_regression_overfitting.ipynb。\n什么是多项式回归？ 让我们通过将一些线性回归模型拟合到数据集来直接深入研究这个概念。我们将使用一个房地产估价数据集2，其中包含有关已售出的 414 栋房屋的信息。为了简单起见，我们只看两个变量——单位面积房价和房屋年龄。我们可以在下面的图 1 中看到这两个变量之间的关系。这个想法是使用房屋房龄来预测价格。\n从图 1 可以看出，这两个变量之间似乎不存在线性关系。这意味着这种关系不能用直线表示。在我们的例子中，房价最初随着房龄的增长而下降。然而，大约 25 年后，房价开始随着房龄的增长而上涨。这表明这种关系可能是二次的。在开始建模之前，让我们讨论一下为什么会这样。我们稍后会看到，当涉及到多项式回归时，对数据中的关系有充分的理由/理由很重要。\n最初的行为是有道理的。随着房屋的老化，它们会变得越来越破旧，价值也会下降。房地产专家对此有更好的理解，但也许经过较长时间后，房屋就会变得古老。随着人们开始重视房屋的悠久历史，它们的房龄会增加价值。也可能存在一些选择偏差，这可以解释这种上升趋势。也就是说，昂贵的房屋往往不会被拆除，因此唯一出售的老房子就是这些昂贵的房屋。\n标准线性回归 无论出于什么原因，由于关系不是线性的，我们不会指望标准线性模型能很好地完成这项工作。让我们尝试使用线性回归仅使用房龄来模拟房价，以证明这一点。我们这样做：\n将数据集随机分成训练集（70%）和测试集（30%）。 使用训练集对模型进行训练。 通过对测试集进行预测并计算 MSE 来评估模型[。](https://en.wikipedia.org/wiki/Mean_squared_error#:~:text=In statistics%2C the mean squared,values and the actual value.) 按照这个过程，我们最终会得到一个由以下方程表示的模型：\n$$ price = \\beta_1(age)+\\beta_0 $$ 其中 β₁ 和 β₀ 是模型估计的参数。这个方程也可以称为模型的预测线。它给出了给定房龄的预测房价。\n在图 2 中，我们可以看到在数据集上使用此模型的结果。这里，红线表示预测线。查看这条线，我们发现该模型在捕捉潜在的二次趋势方面做得很差。我们可以使用测试集 MSE 总结该模型的准确性，即 145.91。\n多项式回归 现在让我们尝试使用多项式回归来改进我们的模型。最终，由于关系似乎是二次的，我们期望以下方程能做得更好：\n$$ price = \\beta_2(age^2) + \\beta_1(age) + \\beta_0 $$ 问题是，如果我们对当前数据集使用线性回归，就不可能得到这样的方程。为了解决这个问题，我们可以简单地在数据集中添加一个新变量 $age^2$。为了避免混淆，我们将其重新标记为 age_squared。添加此功能后，我们可以将非线性方程重写为线性方程： $$ price = \\beta_2(age_squared)+\\beta_1(age)+\\beta_0 $$\n我们现在有两个变量（即 age_squared 和 age）的线性函数，它实际上是一个变量（即 age）的非线性函数。这使我们能够像以前一样使用线性回归来估计 β 参数。我们可以在图 3 中看到生成的预测线。在这种情况下，测试 MSE 为 127.42，比我们之前的模型低 13%。因此，这个新模型在预测房价方面做得更好。\n通过使用这个新特征$age^2$，我们正在进行多项式回归。概括地说，每当你使用 n 次多项式来模拟目标和特征之间的关系时，你都会进行多项式回归。例如： $$ price = \\beta_n(age^n) + \u0026hellip;+ \\beta_2(age^2) + \\beta_1(age)+\\beta_0 $$ 通过添加这些特征，我们可以在数据集中建模更复杂的关系。在上面的模型中，$n = 2$，但使用更高次多项式可能会获得更好的结果。话虽如此，通过添加更多特征，我们也可能最终过度拟合数据。\n多项式回归的过度拟合 当模型与训练数据集的拟合度过高时，我们就说模型过度拟合了。模型会捕捉数据中的噪声，而不仅仅是潜在的趋势。其结果是，模型在训练数据集上可能表现良好，但在测试数据集上则表现不佳。事实上，我们不会期望模型在任何未经训练的数据集上表现良好。\n过度拟合通常是因为模型太复杂或特征太多。随着特征的增加，过度拟合的可能性就越大。在多项式回归中，增加次数也会出现同样的情况。在图 3 中，你可以看到我的意思。我们遵循相同的建模过程，但每一步都会增加多项式的次数。我们从 n=1 开始，以 n=25 结束。请注意，随着次数的增加，预测线会变得更加扭曲。\n实际情况是，随着阶数的增加，模型可以拥有更多转折点。2 阶多项式有 1 个转折点，3 阶多项式有 2 个转折点，依此类推……每增加一个转折点，我们就会给予模型更多自由，使其更贴近训练数据集。对于更高的阶数，模型可能只是捕捉噪音。真正的潜在趋势不太可能如此复杂。\n另一种可视化方法是查看训练和测试集上的 MSE。如图 4 所示，训练 MSE 趋于随着程度的增加而减小。这意味着模型在训练集上变得越来越准确。测试 MSE 讲述了一个不同的故事。当 n=2 时，测试 MSE 最小，然后趋于增加。这意味着模型在测试集上的表现越来越差。换句话说，随着我们增加程度，模型变得越来越过度拟合。\n如何避免过度拟合 我们已经看到了多项式回归可能出错的原因。现在的问题是，我们如何选择正确的程度并避免过度拟合。与任何机器学习模型一样，我们希望训练一个具有在测试集和训练集上表现良好的特征组合的模型。这样，选择多项式回归的最佳特征的过程与任何其他机器学习问题没有什么不同。\nHold-out set 一种常见的方法是使用Hold-out set。说实话，我也不太清楚这个应该怎么翻译，如果是按字面翻译的话，我们应该称呼它为「保留集」。对于这种方法，数据集被分成训练集和保留集/测试集。对于不同的特征集，模型在训练集上进行训练，在测试集上进行评估。我们使用一些指标（例如 MSE）来评估性能。我们通常会选择在测试集上表现最好的特征集。\n看看上面所做的工作，我们可以将此方法应用于房价示例。在这种情况下，n=2 的模型具有最小的测试 MSE。这意味着我们将在最终模型中仅使用 $age^2$ 和 $age$。这似乎是一个合理的结果，因为真正的潜在趋势似乎是二次的。我们还提出了一些逻辑理由来支持这一结果。\nK 折交叉验证 类似的方法是使用 k 折交叉验证。在这里，我们将数据集划分为大小相等的 k 个子集\\折叠。然后，我们在 k-1 折叠上进行训练，并计算剩余折叠的 MSE。我们重复此步骤 k 次，以便每个折叠都轮流作为测试集。模型的最终得分将是所有测试折叠的 MSE 的平均值。平均 MSE 最低的模型将被选为最终模型。图 5 显示了如何使用 5 倍交叉验证划分数据集的示例。在这种情况下，我们将计算 5 个测试折叠的平均 MSE。\n领域知识和常识 使用保留集和 k 折交叉验证通常可以得到一个好的模型。但数据很混乱，可能会出现统计异常。如果只是盲目使用这些方法而不考虑你的问题，你仍然会偶然得到一个糟糕的模型。到目前为止，我们已经使用一个特定的训练测试分割进行了上述分析。在这种情况下，我们得出结论，$age^2$ 的模型是最好的。但如果我们使用不同的随机训练测试分割会怎样？\n你可以在图 6 中看到我们的意思。这里我们遵循与之前完全相同的过程，只是这次我们使用了不同的随机训练测试分割。在这种情况下，测试 MSE 在 n = 8 时最低。通过仅使用保留方法，我们将使用它作为最终模型。\n此时，你应该问自己，使用 8 次多项式是否合乎逻辑。我们的预测线将采用以下形式： $$ price = \\beta_8(age^8) + \\beta_7(age^7) + \u0026hellip; + \\beta_1(age) + \\beta_0 $$ 价格和房龄之间的关系真的那么复杂吗？还是上述结果只是统计异常？答案可能是后者，这强调了为什么我们不应该仅仅依赖像 K 折交叉验证这样的方法。\n在选择特征时，考虑你的问题并应用任何领域知识非常重要。在我们的房价示例中，n = 2 的模型似乎捕捉到了潜在趋势。我们还提出了这种关系的一些很好的潜在原因。考虑到这一点，这个模型可能更好。一般来说，如果你包含的特征有逻辑上的原因说明它们为什么具有预测性，那么你就不太可能捕获噪音和过度拟合。\n更多K 折交叉验证的内容，可以查看「AI 企业项目实战」中第三章的相关课程。\n参考 茶桁的公共文章项目仓库：https://github.com/hivandu/public_articles\u0026#160;\u0026#x21a9;\u0026#xfe0e;\nDataset: https://archive.ics.uci.edu/ml/datasets/Real+estate+valuation+data+set\u0026#160;\u0026#x21a9;\u0026#xfe0e;\n","permalink":"https://hivan.me/posts/%E5%A4%AA%E5%A4%9A%E9%A1%B9%E4%BC%9A%E6%AF%81%E4%BA%86%E5%9B%9E%E5%BD%92/","summary":"\u003cblockquote\u003e\n\u003cp\u003e多项式回归的过度拟合及其避免方法\u003c/p\u003e\u003c/blockquote\u003e","title":"太多项会毁了回归"},{"content":"「AI秘籍」系列课程：\n人工智能应用数学基础 人工智能Python基础 人工智能基础核心知识 人工智能BI核心知识 人工智能CV核心知识 当你第一次深入可解释机器学习领域时，你会注意到类似的术语随处可见。Interpretability 与Explainability。解释与说明。我们甚至无法决定该领域的名称——是可解释机器学习 (interpretable machine learning - IML) 还是可解释人工智能 (explainable AI - XAI)？\n我们将讨论一个定义，并希望澄清一些事情。这就是 Interpretability 模型和 Explainability 模型之间的区别。不过，我们应该警告你……\n没有共性！\n部分问题在于 IML 是一个新领域。定义仍在提出和争论中。机器学习研究人员也很快为已经存在的概念创造新术语。因此，我们将重点关注一个潜在的定义 1。\nInterpretability 与机器学习模型将原因和结果练习起来的准确性有关，指在机器学习中可以观察到系统中因果关系（先验推导）的程度。\nExplainability 与隐藏在深层网络中的参数证明结果的能力有关，是指机器学习的内部机制可以用人类语言解释（后验解释）的程度。\n具体来说，我们将：\n了解如何将模型分类为 interpretable 或者 explainable。 讨论 interpretability 的概念及其与此定义的关系 了解该定义的问题以及为什么可能没有必要使用它来对模型进行分类 Interpretable 机器学习 如果某事物能够被理解，我们就说它是可解释的。考虑到这一点，如果一个模型能够被人类自己理解，我们就说它是可解释的。我们可以查看模型参数或模型摘要，并准确了解预测是如何做出的。这类模型的另一个术语是intrinsically interpretable model(本质上可解释的模型)2。\nInterpretable 模型可以被人类理解，无需任何其他辅助/技术。\n决策树是此类模型的一个很好的例子。图 1给出了一棵经过训练的树，用于预测某人是否会拖欠（是）或不会拖欠（否）汽车贷款。要了解如何做出预测，我们只需遍历树的节点即可。\n例如，假设一名29 岁、月收入3000 美元的女子提出申请。我们想了解为什么她会通过基于此模型的自动承保系统获得贷款。此人超过25 岁，所以我们直接进入第一个节点。然后，她的收入**≥2000**，所以我们再次直接进入 “No” 叶节点。换句话说，该模型预测该学生不会违约，贷款将获得批准。\n假设我们还想要一个模型来预测一个人的最大贷款额度（Y）。我们使用一个人的年龄和收入作为特征。使用线性回归，我们得到以下等式：\n$$ Y = 100 \\times age + 10 \\times income + 200 $$\n我们可以很容易地看出为什么上述学生的预计最高贷款额为33,100 美元。也就是说，贷款额增加了：\n每增加一岁，需支付 100 美元 每增加 1 美元收入就增加 10 美元 因此，就像决策树一样，我们可以查看该模型的参数并了解它如何进行预测。这是因为这些模型相对简单。决策树有几个节点，线性回归模型有 3 个参数。随着模型变得越来越复杂，我们不再能以这种方式理解它们。\nExplainable 机器学习 您可以将 ML 模型视为一个函数。模型特征是输入，预测是输出。Explainable 模型是一种人类无法理解的复杂函数。这种模型的另一个名称是黑盒模型。我们需要一种额外的方法/技术才能窥视黑盒并了解模型的工作原理。\nExplainable 模型需要额外的技术才能被人类理解\n这种模型的一个例子是随机森林。随机森林由许多决策树组成。在做出最终预测时，会考虑所有单个树的预测。要了解随机森林的工作原理，我们必须同时了解所有单个树的工作原理。即使只有少量的树，人类也做不到这一点。\n图片来源：Satya Mallick \u0026amp; Sunita Nayak\n当我们开始研究神经网络等算法时，事情变得更加复杂。具体来说，用于图像识别的卷积神经网络 AlexNet3 有62,378,344 个参数 4。相比之下，我们上面的回归模型只有3 个参数。人类不可能仅通过查看参数权重来理解 AlexNet 之类的模型的工作原理。\n用于理解 explainable 模型的技术 因此，我们需要一些额外的技术来理解这些算法的工作原理。这些包括为特定模型创建的方法。例如，DeepLIFT: https://github.com/kundajelab/deeplift 就是为解释神经网络而创建的。它们还包括可应用于任何模型的模型无关方法。这些方法包括 LIME:https://github.com/marcotcr/lime、SHAP: https://github.com/slundberg/shap、PDPs 和 ICE Plots。\n请记住，即使有了这些技术，我们也无法像使用i nterpretable 模型那样确定模型的工作原理。这些技术只能提供模型如何进行预测的近似值。它们都有自己的假设和局限性。\n这意味着在使用任何技术得出结论时都应保持一定程度的谨慎。如果可能，应结合使用多种技术。还应使用数据可视化和领域知识来验证结论。\nInterpretability 到目前为止，我们已经讨论了模型是 interpretable 还是 explainable。然而，应用这个二元标志可能并不总是有意义的。模型的 Interpretability 是一个范围。如果一个模型比另一个模型更容易让人理解它的预测方式，那么这个模型就比另一个模型更容易解释。\nInterpretability 是指模型能被人类理解的程度2\n图 2给出了可解释性频谱。卷积神经网络的可解释性不如随机森林，而随机森林的可解释性又不如决策树。大多数模型通常可以归类为可解释或可解释的。然而，存在一个灰色区域，人们对此的分类意见不一。\n定义问题 这个灰色区域是我们发现这个定义的第一个问题。我们可能同意，一个有 2 棵树的随机森林是可解释的。然而，一个有 100 棵树的随机森林是不可解释的。在什么时候（即有多少棵树），模型从 interpretable 变为 explainable ？即使是具有许多节点的决策树或具有许多参数的回归，如果没有额外的技术，也可能变得过于复杂，人类无法理解。\n问题是我们试图根据人类的理解能力对模型进行分类。没有正式的方法来衡量这一点。你理解模型的能力取决于你的技术技能和专业经验。即使在专业人士中，也会有分歧。\n另一个问题是我们定义什么为附加技术。为了理解哪怕是最简单的模型，我们也会寻求其他方法的帮助。例如，在解释线性回归的权重时，通常使用相关矩阵。这是否意味着回归现在是一个 explainable 模型？\n这就引出了一个问题：我们真的需要这个定义吗？IML 的目标是理解和解释我们的模型。我们不需要将它们归类为 interpretable 或 explainable。我们选择的方法最终将取决于模型的类型和我们寻求回答的具体问题。\n参考 C. Rudin, Stop explaining black-box machine learning models for high stakes decisions and use interpretable models instead (2019), https://arxiv.org/abs/1811.10154\u0026#160;\u0026#x21a9;\u0026#xfe0e;\nC. Molnar, Interpretable Machine Learning: A Guide for Making Black Box Models Explainable (2023), Chapter 3: Interpretability, https://christophm.github.io/interpretable-ml-book/taxonomy-of-interpretability-methods.html\u0026#160;\u0026#x21a9;\u0026#xfe0e;\u0026#160;\u0026#x21a9;\u0026#xfe0e;\nAlexNet, https://en.wikipedia.org/wiki/AlexNet\u0026#160;\u0026#x21a9;\u0026#xfe0e;\nS. Mallick \u0026amp; S. Nayak, Number of Parameters and Tensor Sizes in a Convolutional Neural Network (CNN) (2018), https://www.learnopencv.com/number-of-parameters-and-tensor-sizes-in-convolutional-neural-network/\u0026#160;\u0026#x21a9;\u0026#xfe0e;\n","permalink":"https://hivan.me/posts/%E5%8F%AF%E8%A7%A3%E8%AF%BB%E4%B8%8E%E5%8F%AF%E8%A7%A3%E9%87%8A%E7%9A%84%E6%9C%BA%E5%99%A8%E5%AD%A6%E4%B9%A0/","summary":"\u003cblockquote\u003e\n\u003cp\u003eInterpretability 模型和 Explainability 模型之间的区别以及为什么它可能不那么重要\u003c/p\u003e\u003c/blockquote\u003e","title":"Interpretability 与 Explainability 机器学习"},{"content":"「AI秘籍」系列课程：\n人工智能应用数学基础 人工智能Python基础 人工智能基础核心知识 人工智能BI核心知识 人工智能CV核心知识 资料来源，flaticon：https://www.flaticon.com/premium-icon/cyborg_901032\n语言识别是自然语言处理 (NLP) 问题中的一个重要步骤。它涉及尝试预测一段文本的自然语言。在采取其他操作（即翻译/情感分析）之前，了解文本的语言非常重要。例如，如果你使用谷歌翻译，你输入的框会显示“检测语言”。这是因为谷歌首先尝试识别你的句子的语言，然后才能翻译它。\n语言识别有几种不同的方法，在本文中，我们将详细探讨其中一种方法。即使用神经网络和字符 n-gram 作为特征。最后，我们表明这种方法可以实现超过 98% 的准确率。在此过程中，我们将讨论关键代码，你可以在GitHub1找到完整的项目。首先，我们将讨论用于训练神经网络的数据集。\n数据集 数据集2由 Tatoeba 提供。 完整数据集包含 328 种独特语言的 6,872,356 个句子。为了简化我们的问题，我们将考虑：\n6 种拉丁语言：英语、德语、西班牙语、法语、葡萄牙语和意大利语。 长度在 20 到 200 个字符之间的句子。 我们可以在表 1 中看到每种语言的一个句子示例。我们的目标是创建一个可以使用提供的文本预测目标变量的模型。\n我们在下面的代码中加载数据集并进行一些初始处理。我们首先过滤数据集以获取所需长度和语言的句子。我们从每种语言中随机选择 50,000 个句子，这样我们总共有 300,000 行。然后将这些句子分成训练集（70%）、验证集（20%）和测试集（10%）。\n# read in full dataset data = pd.read_csv(data_path + \u0026#39;/public_articles/sentences.csv\u0026#39;, sep=\u0026#39;\\t\u0026#39;, encoding=\u0026#39;utf8\u0026#39;, index_col=0, names=[\u0026#39;lang\u0026#39;,\u0026#39;text\u0026#39;]) # Filter by text length data = data[data[\u0026#39;text\u0026#39;].str.len().between(20, 200)] # Filter by text language lang = [\u0026#39;deu\u0026#39;, \u0026#39;eng\u0026#39;, \u0026#39;fra\u0026#39;, \u0026#39;ita\u0026#39;, \u0026#39;por\u0026#39;, \u0026#39;spa\u0026#39;] data = data[data[\u0026#39;lang\u0026#39;].isin(lang)] # Select 50000 rows for each language data_trim_list = [data[data[\u0026#39;lang\u0026#39;] == l].sample(50000, random_state=100) for l in lang] # Concatenate all the samples data_trim = pd.concat(data_trim_list) # Create a random train, valid, test split data_shuffle = data_trim.sample(frac=1, random_state=100) train = data_shuffle[:210000] valid = data_shuffle[210000:270000] test = data_shuffle[270000:300000] # Check the shapes to ensure everything is correct print(f\u0026#34;Train set shape: {train.shape}\u0026#34;) print(f\u0026#34;Validation set shape: {valid.shape}\u0026#34;) print(f\u0026#34;Test set shape: {test.shape}\u0026#34;) 特征工程 在拟合模型之前，我们必须将数据集转换为神经网络可以理解的形式。换句话说，我们需要从句子列表中提取特征来创建特征矩阵。我们使用字符 n-gram（n 个连续字符的集合）来实现这一点。这是一种类似于词袋模型的方法，只不过我们使用的是字符而不是单词。\n对于我们的语言识别问题，我们将使用字符 3-grams/ trigrams （即 3 个连续字符的集合）。在图 2 中，我们看到了如何使用 trigrams 对句子进行矢量化的示例。首先，我们从句子中获取所有 trigrams 。为了减少特征空间，我们取这些 trigrams 的子集。我们使用这个子集对句子进行矢量化。第一个句子的向量是 [2,0,1,0,0]，因为 trigrams “is_”在句子中出现两次，“his”出现一次。\n创建三元特征矩阵的过程类似，但稍微复杂一些。在下一节中，我们将深入研究用于创建矩阵的代码。在此之前，有必要对如何创建特征矩阵进行总体概述。所采取的步骤如下：\n使用训练集，我们从每种语言中选择了 200 个最常见的三字母组 根据这些 trigrams 创建一个唯一 trigrams 列表。这些语言共享一些共同的 trigrams ，因此我们最终得到了 661 个唯一 trigrams 通过计算每个句子中每个 trigrams 出现的次数来创建特征矩阵 我们可以在表 2 中看到此类特征矩阵的示例。顶行给出了 661 个 trigrams 中的每一个。然后，每个编号行给出了我们数据集中的一个句子。矩阵中的数字给出了该 trigrams 在句子中出现的次数。例如，“eux”在句子 2 中出现了一次。\n创建特征 在本节中，我们将介绍用于创建表 2 中的训练特征矩阵和验证/测试特征矩阵的代码。我们大量使用了SciKit Learn 提供的CountVectorizer包。此包允许我们根据一些词汇表（即单词/字符列表）对文本进行矢量化。在我们的例子中，词汇表是一组 661 个 trigrams 。\n首先，我们必须创建这个词汇表。我们首先从每种语言中获取 200 个最常见的 trigrams 。这是使用下面代码中的*get_trigrams*函数完成的。此函数获取一个句子列表，并将从这些句子中返回 200 个最常见的 trigrams 的列表。\nfrom sklearn.feature_extraction.text import CountVectorizer def get_trigrams(corpus, n_feat=200): \u0026#34;\u0026#34;\u0026#34; Returns a list of the N most common character trigrams from a list of sentences params ------------ corpus: list of strings n_feat: integer \u0026#34;\u0026#34;\u0026#34; # fit the n-gram model vectorizer = CountVectorizer(analyzer=\u0026#39;char\u0026#39;, ngram_range=(3, 3), max_features=n_feat) X = vectorizer.fit_transform(corpus) # Get model feature names feature_names = vectorizer.get_feature_names_out() return feature_names 在下面的代码中，我们循环遍历这 6 种语言。对于每种语言，我们从训练集中获取相关句子。然后我们使用get_trigrams函数获取 200 个最常见的 trigrams 并将它们添加到集合中。最后，由于这些语言共享一些共同的 trigrams ，我们得到了一组 661 个独特的 trigrams 。我们用它们来创建一个词汇表。\n# obtain trigrams from each language features = {} features_set = set() for l in lang: # get corpus filtered by language corpus = train[train.lang==l][\u0026#39;text\u0026#39;] # get 200 most frequent trigrams trigrams = get_trigrams(corpus) # add to dict and set features[l] = trigrams features_set.update(trigrams) # create vocabulary list using feature set vocab = dict() for i,f in enumerate(features_set): vocab[f]=i 然后，CountVectorisor 包使用词汇表对训练集中的每个句子进行矢量化。结果就是我们之前看到的表 2 中的特征矩阵。\n# train count vectoriser using vocabulary vectorizer = CountVectorizer(analyzer=\u0026#39;char\u0026#39;, ngram_range=(3, 3), vocabulary=vocab) # create feature matrix for training set corpus = train[\u0026#39;text\u0026#39;] X = vectorizer.fit_transform(corpus) feature_names = vectorizer.get_feature_names_out() train_feat = pd.DataFrame(data=X.toarray(),columns=feature_names) 在训练模型之前，最后一步是缩放特征矩阵。这将有助于我们的神经网络收敛到最佳参数权重。在下面的代码中，我们使用最小-最大缩放来缩放训练矩阵。\n# Scale feature matrix train_min = train_feat.min() train_max = train_feat.max() train_feat = (train_feat - train_min)/(train_max-train_min) # Add target variable train_feat[\u0026#39;lang\u0026#39;] = list(train[\u0026#39;lang\u0026#39;]) 我们还需要获取验证和测试数据集的特征矩阵。在下面的代码中，我们像对训练集所做的那样对 2 个集合进行矢量化和缩放。值得注意的是，我们使用了词汇表以及从训练集中获得的最小/最大值。这是为了避免任何数据泄露。\n# create feature matrix for validation set corpus = valid[\u0026#39;text\u0026#39;] X = vectorizer.fit_transform(corpus) valid_feat = pd.DataFrame(data=X.toarray(),columns=feature_names) valid_feat = (valid_feat - train_min)/(train_max-train_min) valid_feat[\u0026#39;lang\u0026#39;] = list(valid[\u0026#39;lang\u0026#39;]) # create feature matrix for test set corpus = test[\u0026#39;text\u0026#39;] X = vectorizer.fit_transform(corpus) test_feat = pd.DataFrame(data=X.toarray(),columns=feature_names) test_feat = (test_feat - train_min)/(train_max-train_min) test_feat[\u0026#39;lang\u0026#39;] = list(test[\u0026#39;lang\u0026#39;]) 探索 trigrams 现在，我们已经准备好了可用于训练神经网络的数据集。在此之前，探索数据集并建立一些直觉来了解这些特征在预测语言方面的表现会很有用。图 2 给出了每种语言与其他语言共有的 trigrams 数量。例如，英语和德语有 56 个最常见的 trigrams 是共同的。\n我们发现西班牙语和葡萄牙语的共同 trigrams 最多，有 128 个共同的 trigrams。这是有道理的，因为在所有语言中，这两种语言在词汇上最相似。这意味着，使用这些特征，我们的模型可能很难区分西班牙语和葡萄牙语，反之亦然。同样，葡萄牙语和德语的共同 trigrams 最少，我们可以预期我们的模型在区分这些语言方面会更好。\n建模 我们使用keras包来训练 DNN。模型的输出层使用 softmax 激活函数。这意味着我们必须将目标变量列表转换为 one-hot 编码列表。这可以通过下面的编码函数来实现。 该函数接收目标变量列表，并返回单次编码向量列表。 例如，[eng,por,por, fra,\u0026hellip;] 将变为[[0,1,0,0,0,0],[0,0,0,0,1,0],[0,0,0,0,1,0],[0,0,1,0,0,0],…]。\nfrom sklearn.preprocessing import LabelEncoder from keras.utils import np_utils # Fit encoder encoder = LabelEncoder() encoder.fit([\u0026#39;deu\u0026#39;, \u0026#39;eng\u0026#39;, \u0026#39;fra\u0026#39;, \u0026#39;ita\u0026#39;, \u0026#39;por\u0026#39;, \u0026#39;spa\u0026#39;]) def encode(y): \u0026#34;\u0026#34;\u0026#34; Returns a list of one hot encodings Params --------- y: list of language labels \u0026#34;\u0026#34;\u0026#34; y_encoded = encoder.transform(y) y_dummy = np_utils.to_categorical(y_encoded) return y_dummy 在选择最终模型结构之前，我进行了一些超参数调整。我改变了隐藏层中的节点数、epoch 数和批处理大小。最终模型选择了在验证集上实现最高准确率的超参数组合。\n最终模型有 3 个隐藏层，分别有 500、500 和 250 个节点。输出层有 6 个节点，每个语言一个。隐藏层都具有 ReLU 激活函数，并且如上所述，输出层具有 softmax 激活函数。我们使用 4 个 epoch 和 100 的批处理大小来训练此模型。使用我们的训练集和独热编码目标变量列表，我们在以下代码中训练此 DDN。最终，我们实现了 99.57% 的训练准确率。\nfrom keras.models import Sequential from keras.layers import Dense #Get training data x = train_feat.drop(\u0026#39;lang\u0026#39;,axis=1) y = encode(train_feat[\u0026#39;lang\u0026#39;]) #Define model model = Sequential() model.add(Dense(500, input_dim=661, activation=\u0026#39;relu\u0026#39;)) model.add(Dense(500, activation=\u0026#39;relu\u0026#39;)) model.add(Dense(250, activation=\u0026#39;relu\u0026#39;)) model.add(Dense(6, activation=\u0026#39;softmax\u0026#39;)) model.compile(loss=\u0026#39;categorical_crossentropy\u0026#39;, optimizer=\u0026#39;adam\u0026#39;, metrics=[\u0026#39;accuracy\u0026#39;]) #Train model model.fit(x, y, epochs=4, batch_size=100) 模型评估 在模型训练过程中，模型可能会偏向训练集和验证集。因此，最好在未见过的测试集上确定模型准确率。测试集的最终准确率为 98.60%。这低于训练准确率 99.57%，表明发生了一些对训练集的过度拟合。\n通过查看图 3 中的混淆矩阵，我们可以更好地了解模型对每种语言的表现。红色对角线表示每种语言的正确预测数。非对角线数字表示一种语言被错误预测为另一种语言的次数。例如，德语被错误预测为英语 5 次。我们发现，该模型最常将葡萄牙语混淆为西班牙语（78 次）或将西班牙语混淆为葡萄牙语（88 次）。这是我们在探索特征时看到的结果。\n创建此混淆矩阵的代码如下所示。首先，我们使用上面训练的模型对测试集进行预测。使用这些预测语言和实际语言，我们创建一个混淆矩阵并使用 seaborn 热图对其进行可视化。\nimport matplotlib.pyplot as plt import seaborn as sns from sklearn.metrics import accuracy_score, confusion_matrix import numpy as np # x_test 和 y_test 已经定义，并且 model 是一个已训练好的 Keras 模型 x_test = test_feat.drop(\u0026#39;lang\u0026#39;, axis=1) y_test = test_feat[\u0026#39;lang\u0026#39;] # Use model.predict to get probabilities predictions_prob = model.predict(x_test) # Find the index of the highest probability for each sample labels = np.argmax(predictions_prob, axis=1) predictions = encoder.inverse_transform(labels) # Ensure y_test is a 1D array if y_test.ndim \u0026gt; 1: y_test = np.argmax(y_test, axis=1) # Accuracy on test set accuracy = accuracy_score(y_test, predictions) print(f\u0026#34;Accuracy: {accuracy}\u0026#34;) # Create confusion matrix lang = [\u0026#39;deu\u0026#39;, \u0026#39;eng\u0026#39;, \u0026#39;fra\u0026#39;, \u0026#39;ita\u0026#39;, \u0026#39;por\u0026#39;, \u0026#39;spa\u0026#39;] conf_matrix = confusion_matrix(y_test, predictions) conf_matrix_df = pd.DataFrame(conf_matrix, columns=lang, index=lang) # Plot confusion matrix heatmap plt.figure(figsize=(10, 10), facecolor=\u0026#39;w\u0026#39;, edgecolor=\u0026#39;k\u0026#39;) sns.set(font_scale=1.5) sns.heatmap(conf_matrix_df, cmap=\u0026#39;coolwarm\u0026#39;, annot=True, fmt=\u0026#39;.5g\u0026#39;, cbar=False) plt.xlabel(\u0026#39;Predicted\u0026#39;, fontsize=22) plt.ylabel(\u0026#39;Actual\u0026#39;, fontsize=22) plt.savefig(\u0026#39;../figures/model_eval.png\u0026#39;, format=\u0026#39;png\u0026#39;, dpi=150) plt.show() 最后，98.60% 的测试准确率仍有提升空间。在特征选择方面，我们保持简单，只为每种语言选择了 200 个最常见的 trigrams 。更复杂的方法可以帮助我们区分更相似的语言。例如，我们可以选择在西班牙语中很常见但在葡萄牙语中不太常见的 trigrams ，反之亦然。我们还可以尝试不同的模型。希望这对你的语言识别实验来说是一个良好的起点。\n参考 茶桁的公开文章项目文件 https://github.com/hivandu/public_articles\u0026#160;\u0026#x21a9;\u0026#xfe0e;\nTatoeba 数据集 https://downloads.tatoeba.org/exports/\u0026#160;\u0026#x21a9;\u0026#xfe0e;\n","permalink":"https://hivan.me/posts/%E6%B7%B1%E5%BA%A6%E7%A5%9E%E7%BB%8F%E7%BD%91%E7%BB%9C%E8%AF%AD%E8%A8%80%E8%AF%86%E5%88%AB/","summary":"\u003cblockquote\u003e\n\u003cp\u003e使用 DNN 和字符 n-gram 对一段文本的语言进行分类（附 Python 代码）\u003c/p\u003e\u003c/blockquote\u003e","title":"深度神经网络语言识别"},{"content":"「AI秘籍」系列课程：\n人工智能应用数学基础 人工智能Python基础 人工智能基础核心知识 人工智能BI核心知识 人工智能CV核心知识 主成分分析 (PCA) 是数据科学家使用的绝佳工具。它可用于降低特征空间维数并生成不相关的特征。正如我们将看到的，它还可以帮助你深入了解数据的分类能力。我们将带你了解如何以这种方式使用 PCA。提供了 Python 代码片段，完整项目可在GitHub1上找到。\n什么是 PCA？ 我们先从理论开始。我不会深入讲解太多细节，因为如果你想了解 PCA 的工作原理，有很多很好的资源2(https://towardsdatascience.com/a-one-stop-shop-for-principal-component-analysis-5582fb7e0a9c)[^3](https://liorpachter.wordpress.com/2014/05/26/what-is-principal-component-analysis/)。重要的是要知道 PCA 是一种降维算法。这意味着它用于减少用于训练模型的特征数量。它通过从许多特征中构建主成分 (PC) 来实现这一点。\nPC 的构造方式是，第一个 PC（即 PC1）尽可能解释特征中的大部分变化。然后 PC2 尽可能解释剩余变化中的大部分变化，依此类推。PC1 和 PC2 通常可以解释总特征变化的很大一部分。另一种思考方式是，前两个 PC 可以很好地总结特征。这很重要，因为它使我们能够在二维平面上直观地看到数据的分类能力。\n数据集 好的，让我们深入研究一个实际的例子。我们将使用 PCA 来探索乳腺癌数据集3(http://archive.ics.uci.edu/ml/datasets/breast+cancer+wisconsin+(diagnostic))，我们使用以下代码导入该数据集。目标变量是乳腺癌测试的结果 - 恶性或良性。每次测试都会取出许多癌细胞。然后从每个癌细胞中采取 10 个不同的测量值。这些包括细胞半径和细胞对称性等测量值。为了获得 30 个特征的最终列表，我们以 3 种方式汇总这些测量值。也就是说，我们计算每个测量值的平均值、标准误差和最大值（“最差”值）。在图 1 中，我们仔细研究了其中两个特征 -细胞的平均对称性和最差平滑度。\nimport numpy as np import pandas as pd from sklearn.datasets import load_breast_cancer cancer = load_breast_cancer() data = pd.DataFrame(cancer[\u0026#39;data\u0026#39;],columns=cancer[\u0026#39;feature_names\u0026#39;]) data[\u0026#39;y\u0026#39;] = cancer[\u0026#39;target\u0026#39;] 在图 1 中，我们可以看到这两个特征有助于区分这两个类别。也就是说，良性肿瘤往往更对称、更光滑。重叠部分仍然很多，因此仅使用这些特征的模型效果不会很好。我们可以创建这样的图表来了解每个单独特征的预测能力。尽管有 30 个特征，但需要分析的图表还是很多。它们也没有告诉我们整个数据集的预测能力。这就是 PCA 发挥作用的地方。\nPCA——整个数据集 让我们首先对整个数据集进行 PCA。我们使用下面的代码来执行此操作。我们首先缩放特征，使它们都具有均值为 0 和方差为 1。这很重要，因为 PCA 通过最大化 PC 解释的方差来工作。由于其规模，某些特征往往会具有更高的方差。例如，以厘米为单位测量的距离的方差将高于以公里为单位测量的相同距离。如果不进行缩放，PCA 将被那些方差较大的特征“压倒”。\n缩放完成后，我们拟合 PCA 模型并将特征转换为 PC。由于我们有 30 个特征，因此最多可以有 30 个 PC。对于我们的可视化，我们只对前两个感兴趣。你可以在图 2 中看到这一点，其中使用 PC1 和 PC2 创建了散点图。我们现在可以看到两个不同的集群，它们比图 1 中更清晰。\nfrom sklearn.preprocessing import StandardScaler from sklearn.decomposition import PCA #Scale the data scaler = StandardScaler() scaler.fit(data) scaled = scaler.transform(data) #Obtain principal components pca = PCA().fit(scaled) pc = pca.transform(scaled) pc1 = pc[:,0] pc2 = pc[:,1] #Plot principal components plt.figure(figsize=(10,10)) colour = [\u0026#39;#ff2121\u0026#39; if y == 1 else \u0026#39;#2176ff\u0026#39; for y in data[\u0026#39;y\u0026#39;]] plt.scatter(pc1,pc2 ,c=colour,edgecolors=\u0026#39;#000000\u0026#39;) plt.ylabel(\u0026#34;Glucose\u0026#34;,size=20) plt.xlabel(\u0026#39;Age\u0026#39;,size=20) plt.yticks(size=12) plt.xticks(size=12) plt.xlabel(\u0026#39;PC1\u0026#39;) plt.ylabel(\u0026#39;PC2\u0026#39;) 该图可用于直观地了解数据的预测强度。在本例中，它表明使用整个数据集将使我们能够区分恶性肿瘤和良性肿瘤。但是，仍然有一些异常值（即不明确位于群集中的点）。这并不意味着我们会对这些情况做出错误的预测。我们应该记住，并非所有特征方差都会在前两个 PC 中捕获。在完整特征集上训练的模型可以产生更好的预测。\n此时，我们应该提到这种方法的一个注意事项。PC1 和 PC2 可以解释特征中很大一部分方差。然而，这并不总是正确的。在某些情况下，PC 可能被认为是特征的糟糕总结。这意味着，即使你的数据可以很好地分离类别，你也可能无法获得清晰的聚类，如图 2 所示。\n我们可以使用 PCA 碎石图来确定这是否会是一个问题。我们使用下面的代码创建了此分析的碎石图，如图 3 所示。这是一个条形图，其中每个条形的高度是相关 PC 解释的方差百分比。我们看到，PC1 和 PC2 总共只解释了约 20% 的特征方差。即使只有 20% 的解释，我们仍然得到两个不同的聚类。这强调了数据的预测强度。\nvar = pca.explained_variance_[0:10] #percentage of variance explained labels = [\u0026#39;PC1\u0026#39;,\u0026#39;PC2\u0026#39;,\u0026#39;PC3\u0026#39;,\u0026#39;PC4\u0026#39;,\u0026#39;PC5\u0026#39;,\u0026#39;PC6\u0026#39;,\u0026#39;PC7\u0026#39;,\u0026#39;PC8\u0026#39;,\u0026#39;PC9\u0026#39;,\u0026#39;PC10\u0026#39;] plt.figure(figsize=(15,7)) plt.bar(labels,var,) plt.xlabel(\u0026#39;Pricipal Component\u0026#39;) plt.ylabel(\u0026#39;Proportion of Variance Explained\u0026#39;) #小程序://海豚知道/qwC6nIDbKMKYKmx\nPCA——特征组 我们还可以使用此过程来比较不同的特征组。例如，假设我们有两组特征。第 1 组具有基于细胞对称性和平滑度特征的所有特征。而第 2 组具有基于周长和凹度的所有特征。我们可以使用 PCA 来直观地了解哪组更适合进行预测。\ngroup_1 = [\u0026#39;mean symmetry\u0026#39;, \u0026#39;symmetry error\u0026#39;,\u0026#39;worst symmetry\u0026#39;, \u0026#39;mean smoothness\u0026#39;,\u0026#39;smoothness error\u0026#39;,\u0026#39;worst smoothness\u0026#39;] group_2 = [\u0026#39;mean perimeter\u0026#39;,\u0026#39;perimeter error\u0026#39;,\u0026#39;worst perimeter\u0026#39;, \u0026#39;mean concavity\u0026#39;,\u0026#39;concavity error\u0026#39;,\u0026#39;worst concavity\u0026#39;] 我们首先创建两组特征。然后分别对每组进行 PCA。这将为我们提供两组 PC，我们选择 PC1 和 PC2 来代表每个特征组。该过程的结果可以在图 4 中看到。\n对于第 1 组，我们可以看到有一些分离，但仍然有很多重叠。相比之下，第 2 组有两个不同的簇。因此，从这些图中，我们预计第 2 组中的特征是更好的预测因子。使用第 2 组特征训练的模型应该比使用第 1 组特征训练的模型具有更高的准确率。现在，让我们来测试一下这个假设。\n我们使用下面的代码来训练使用两组特征的逻辑回归模型。在每种情况下，我们使用 70% 的数据来训练模型，其余 30% 的数据来测试模型。第 1 组的测试集准确率为 74%，相比之下，第 2 组的准确率为 97%。因此，第 2 组中的特征是更好的预测因子，这正是我们从 PCA 结果中预期的。\nfrom sklearn.model_selection import train_test_split import sklearn.metrics as metric import statsmodels.api as sm for i,g in enumerate(group): x = data[g] x = sm.add_constant(x) y = data[\u0026#39;y\u0026#39;] x_train, x_test, y_train, y_test = train_test_split(x,y,test_size=0.3, random_state = 101) model = sm.Logit(y_train,x_train).fit() #fit logistic regression model predictions = np.around(model.predict(x_test)) accuracy = metric.accuracy_score(y_test,predictions) print(\u0026#34;Accuracy of Group {}: {}\u0026#34;.format(i+1,accuracy)) --- Optimization terminated successfully. Current function value: 0.458884 Iterations 7 Accuracy of Group 1: 0.7368421052631579 Optimization terminated successfully. Current function value: 0.103458 Iterations 10 Accuracy of Group 2: 0.9707602339181286 最后，我们将了解如何在开始建模之前使用 PCA 来更深入地了解数据。它将让你了解预期的分类准确度。你还将对哪些特征具有预测性建立直觉。这可以让你在特征选择方面占据优势。\n如上所述，这种方法并非万无一失。它应该与其他数据探索图和汇总统计数据一起使用。对于分类问题，这些可能包括信息值和箱线图。一般来说，在开始建模之前，从尽可能多的不同角度查看数据是个好主意。\n参考 https://github.com/hivandu/public_articles\u0026#160;\u0026#x21a9;\u0026#xfe0e;\nMatt Brems, A One-Stop Shop for Principal Component Analysis (2017), https://towardsdatascience.com/a-one-stop-shop-for-principal-component-analysis-5582fb7e0a9c\u0026#160;\u0026#x21a9;\u0026#xfe0e;\nUCI, Breast Cancer Wisconsin (Diagnostic) Dataset (2020), http://archive.ics.uci.edu/ml/datasets/breast+cancer+wisconsin+(diagnostic)\u0026#160;\u0026#x21a9;\u0026#xfe0e;\n","permalink":"https://hivan.me/posts/%E4%BD%BF%E7%94%A8-pca-%E5%8F%AF%E8%A7%86%E5%8C%96%E6%95%B0%E6%8D%AE%E7%9A%84%E5%88%86%E7%B1%BB%E8%83%BD%E5%8A%9B/","summary":"\u003cblockquote\u003e\n\u003cp\u003e使用 PCA 探索数据分类的效果（使用 Python 代码）\u003c/p\u003e\u003c/blockquote\u003e","title":"使用 PCA 可视化数据的分类能力"},{"content":"什么是 GMM 如上所述，GMM 是一种聚类算法。这意味着它可用于根据特征对数据集中的元素进行分组。例如，假设我们有一个客户收入和年龄的数据集。聚类算法可以识别 4 个组：老年高收入者、老年低收入者、年轻高收入者和年轻低收入者。这 4 个群体可能具有非常不同的特征/行为。我们不会详细介绍 GMM 如何创建这些聚类，因为有很多很好的资料来源12。重要的是为什么我们要使用 GMM 而不是其他聚类算法（例如 K-means）。\n对于我们的问题，GMM 最重要的优势是它在聚类方差和协方差方面更加灵活。首先，更大的方差灵活性意味着 GMM 可以更好地识别方差不等的聚类。换句话说，当我们既有密集的聚类又有分散的聚类时，它将产生更好的结果。我们可以在下面的图 1 中看到这一点，其中我们将 K-means 和 GMM 应用于一些测试数据。这里每个点的颜色由相关算法分配的聚类决定。在这种情况下，GMM 聚类似乎更合适。\n其次，协方差灵活性越高，我们就能识别出更细长/椭圆形的簇。相比之下，K 均值只能真正识别球形簇。我们可以在图 2 中看到这一点，GMM 簇再次更合适。当我们将 GMM 的这些属性应用于我们的餐厅数据集时，我们就会明白为什么它们如此重要。最终，它将使我们能够找到更有趣的簇。\n数据集 为了训练 GMM，我们将使用 Yelp 开放数据集3餐馆的大量信息。我们对其进行了预处理，以便获得多伦多每家餐馆的名称、纬度和经度。我们还包括评论数量（review_count）和餐馆评级（星级），稍后将用于分析聚类。其他特征（例如餐馆的街区和类别）已被排除。我们使用下面的代码读取数据集，我们可以在表 1 中看到一个示例。\nimport pandas as pd data = pd.read_csv(data_path + \u0026#34;/yelp_data/toronto_restaurant.csv\u0026#34;,usecols=[\u0026#39;name\u0026#39;,\u0026#39;latitude\u0026#39;,\u0026#39;longitude\u0026#39;,\u0026#39;stars\u0026#39;,\u0026#39;review_count\u0026#39;]) print(len(data)) data.head() --- 7148 name\tlatitude\tlongitude\treview_count\tstars 0\tAlize Catering\t43.71140\t-79.39934\t12\t3.0 1\tChula Taberna Mexicana\t43.66926\t-79.33590\t39\t3.5 2\tSunnyside Grill\t43.78182\t-79.49043\t3\t5.0 3\tBampot House of Tea \u0026amp; Board Games\t43.66158\t-79.40888\t55\t4.0 4\tThai Express\t43.77488\t-79.49462\t5\t3.0 在图 3 中，我们用经纬度标出了所有餐厅。每个点代表一家餐厅，总共有 7148 多家餐厅。餐厅的位置可以告诉我们一些信息。例如，某些位置的餐厅可能评级较高。因此，我们可能能够在试图预测餐厅评级等信息的模型中使用位置。但是，仅输入原始的经纬度数据可能不会给我们带来好的结果——尤其是在使用线性模型的情况下。这意味着我们必须对经纬度特征执行某种形式的特征工程。\n图\n在某些方面，考虑到我们有餐厅社区，这已经完成了。换句话说，餐厅已经根据其纬度和经度被分组为集群（即社区）。问题是多伦多有 71 个不同的社区，需要估计很多系数。你可能可以根据某些社区的特点将它们分组在一起，但这需要多伦多餐厅领域的专业知识。我们将尝试一种替代方法，即使用 GMM 对餐厅进行分组。\n拟合 GMM 在下面的 Python 函数的第一部分中，我们训练 GMM。GMM 的一个问题是它们会收敛到局部最优点。为了避免这种情况，我们将“n_init”参数设置为 5。这将随机初始化和训练 5 个 GMM，最后，我们将最佳模型作为最终模型。我们使用此模型获得每家餐厅的标签/预测。然后，如图 3 所示，我们绘制餐厅，但这次我们根据 GMM 标签分配颜色。\nimport sklearn.mixture as sm import matplotlib.pyplot as plt %matplotlib inline def plot_gmm(n): \u0026#34;\u0026#34;\u0026#34; Train a GMM and plot the results Parameters ---------- n : int Number of clusters \u0026#34;\u0026#34;\u0026#34; #Fit gmm and get labels x = data[[\u0026#39;longitude\u0026#39;,\u0026#39;latitude\u0026#39;]].values gmm = sm.GaussianMixture(n_components=n, random_state=11,n_init=5) labels = gmm.fit(x).predict(x) #assign a colour to each label colour = [\u0026#39;#f54242\u0026#39;,\u0026#39;#4287f5\u0026#39;,\u0026#39;#f59942\u0026#39;,\u0026#39;#f5f242\u0026#39;,\u0026#39;#69f542\u0026#39;,\u0026#39;#b342f5\u0026#39;] c = [colour[l] for l in labels] #scatter plot plt.figure(figsize=(20, 12)) plt.scatter(x=x[:, 0], y=x[:, 1], c=c, s=40, cmap=\u0026#39;Set1\u0026#39;, zorder=1) plt.title(\u0026#39;Number of Clusters: {}\u0026#39;.format(n),size=25) plt.xlabel(\u0026#39;Longitude\u0026#39;,size=20) plt.ylabel(\u0026#39;Latitude\u0026#39;,size=20) plot_gmm(5) 你可能已经注意到，此函数采用一个参数 — 聚类数。这被视为 GMM 的超参数，我们必须选择它。如果我们决定使用过多的聚类，模型将过度拟合数据。这意味着我们将识别出没有意义的聚类或拆分更适合集中在一起的聚类。如果我们决定使用过少的聚类，模型可能会对数据产生欠拟合。这意味着我们可能会错过一些重要的聚类。问题是我们如何决定聚类的数量？\n有几种不同的方法可以帮助确定适当的聚类数量。这些方法包括使用轮廓分数或模型 BIC 值4(https://towardsdatascience.com/gaussian-mixture-model-clusterization-how-to-select-the-number-of-components-clusters-553bef45f6e4)。这些只是有用的指导方针，不一定能为你提供最佳的聚类数量。由于我们仅使用 2 个特征来训练我们的 GMM，因此我们可以轻松地可视化结果并了解聚类是否合适。例如，如图 5 所示，我们使用上面的 Python 函数绘制具有 5 个聚类的 GMM 的结果。\nGMM 已识别出一些有趣的聚类。例如，蓝色聚类似乎非常密集，附近有许多餐馆。长红色聚类似乎是一条道路。在图 6 中，我们分别使用 4 个和 6 个聚类看到了类似的图。在这两种情况下，我们都看到 GMM 已识别出与图 5 中类似的聚类。所以，让我们继续使用 5 个聚类。我们可以进一步分析它们，以更好地了解它们是否合适。\n分析集群 如果我们看一下多伦多的地图，我们可以更好地理解这些集群。我们使用下面的代码来做到这一点。首先，我们从具有 5 个集群的 GMM 中获取标签。然后我们创建一个以多伦多为中心的叶状图。最后，我们将每个点叠加在地图上。像以前一样，每个点的颜色由其标签决定。我们可以在图 7 中看到这个过程的结果。\nimport folium #Get gmm predictions x = data[[\u0026#39;longitude\u0026#39;,\u0026#39;latitude\u0026#39;]].values gmm = sm.GaussianMixture(n_components=5, random_state=11,n_init=5) labels = gmm.fit(x).predict(x) #create folium map m = folium.Map( location=[43.77923, -79.41731999999998], zoom_start=12, tiles=\u0026#39;Stamen Terrain\u0026#39; ) colour = [\u0026#39;#f54242\u0026#39;,\u0026#39;#4287f5\u0026#39;,\u0026#39;#f59942\u0026#39;,\u0026#39;#f5f242\u0026#39;,\u0026#39;#69f542\u0026#39;,\u0026#39;#b342f5\u0026#39;] #add markers to map for i in range(len(x)): lon = x[i][0] lat = x[i][1] label = labels[i] #assign colour based on label c = colour[label] #add marker folium.CircleMarker(location=[lat,lon], radius=2, color=c, fill_color=c).add_to(m) #display map m 看一看地图，这些聚类开始变得更有意义了。蓝色和绿色聚类位于多伦多港周围人口更密集的城市地区。这些聚类大致被一条高速公路隔开，这似乎是划分餐厅群体的相当自然的方式。黄色和橙色聚类密度较低，它们由郊区的餐厅组成。长长的红色聚类特别有趣。几乎所有这些点都落在央街上。快速搜索显示，这实际上是多伦多最著名的街道。考虑到这一点，将这些餐厅归入自己的组是有意义的。\n这些聚类并不完美。有一些不规则的红点可能适合成为蓝色聚类的一部分。同样，高速公路右侧的一些蓝点可能应该属于绿色聚类。在很大程度上，GMM 已经识别出在地理上有意义的聚类。我们可以预期它们具有不同的特征，但事实并非如此。\n我们可以通过考虑餐厅的评论和评分来研究这些特征。在表 2 中，我们可以看到每个集群中餐厅的平均评论数量。我们看到蓝色集群中的评论数量往往更高。事实上，平均评论数量是黄色和橙色集群的两倍多。这可能是有道理的，因为我们可以预期密集地区的餐厅会有更多的顾客。\n我们还可以看到平均评分和至少有 4 星评分的餐厅百分比（高评分百分比）。绿色集群中的餐厅平均评分最高，高评分餐厅比例最高。也许这是多伦多一个更高档的地区？同样在评分方面，两个郊区集群（黄色和橙色）往往相差很大。\n这只是我们可以用来比较聚类的两个特征。我们可以一直分析下去。归根结底，聚类的适用性取决于你想用它们做什么。一般的想法是，也许经过一些微调后，你可以用它的聚类来标记每家餐厅，并将其用作分析或模型中的一个特征。你也可以使用这些聚类作为类似地理标签的起点。\n参考 「AI秘籍」系列课程：\n人工智能应用数学基础 人工智能Python基础 人工智能基础核心知识 人工智能BI核心知识 人工智能CV核心知识 Cory Maklin (2019), Mixture Models Clustering Algorithm Explained (2019), https://towardsdatascience.com/gaussian-mixture-models-d13a5e915c8e\u0026#160;\u0026#x21a9;\u0026#xfe0e;\nRibhu Nirek, Gaussian Mixture Models (2020), https://towardsdatascience.com/gaussian-mixture-models-gmm-6e95cbc38e6e\u0026#160;\u0026#x21a9;\u0026#xfe0e;\nYelp, Yelp open dataset (2019), https://www.yelp.com/dataset\u0026#160;\u0026#x21a9;\u0026#xfe0e;\nVincenzo Lavorini, Gaussian Mixture Model clustering: how to select the number of components (2018), https://towardsdatascience.com/gaussian-mixture-model-clusterization-how-to-select-the-number-of-components-clusters-553bef45f6e4\u0026#160;\u0026#x21a9;\u0026#xfe0e;\n","permalink":"https://hivan.me/posts/%E4%BD%BF%E7%94%A8%E9%AB%98%E6%96%AF%E6%B7%B7%E5%90%88%E6%A8%A1%E5%9E%8B%E8%AF%86%E5%88%AB%E9%A4%90%E5%8E%85%E7%83%AD%E7%82%B9/","summary":"\u003cp\u003e原文：https://towardsdatascience.com/identifying-restaurant-hotspots-with-a-gaussian-mixture-model-2a840ab0c782\u003c/p\u003e\n\u003cblockquote\u003e\n\u003cp\u003e使用 GMM 识别加拿大多伦多的直观餐厅集群（附 Python 代码）\n聚类算法（例如 GMM）是一种有用的工具，可帮助识别数据中的模式。它们使我们能够识别数据集中的子组，从而提高你的理解或增强预测模型。在本文中，借助 GMM，我们将尝试使用位置数据识别多伦多的餐厅集群。目标是找到在地理上合理但在其他特征（例如餐厅评级）方面具有不同特征的集群。我们将讨论关键代码，你可以在\u003ca href=\"https://github.com/hivandu/public_articles\"\u003eGitHub\u003c/a\u003e[^5]上找到完整的项目。\n\u003cimg alt=\"img\" loading=\"lazy\" src=\"https://cdn-images-1.readmedium.com/v2/resize:fit:800/1*JzEI1jMp1wcx5GU1RTfUvQ.png\"\u003e\u003c/p\u003e\u003c/blockquote\u003e\n","title":"使用高斯混合模型识别餐厅热点"},{"content":"在本文中，我希望教你一些关于特征工程的知识，以及如何使用它来建模非线性决策边界。我们将探讨两种技术的优缺点：逻辑回归（带有特征工程）和 NN 分类器。将提供用于拟合这些模型以及可视化其决策边界的 Python 代码。你可以在 Github1 上找到完整的项目代码。最后，我希望让你理解为什么特征工程可能是其他非线性建模技术的更好替代方案。\n什么是特征工程 当你从原始数据创建特征或将现有特征的函数添加到数据集时，你就在进行特征工程。这通常使用特定领域的领域知识来完成2。例如，假设我们想要预测一个人从手术中恢复所需的时间（Y）。从之前的手术中，我们获得了患者的康复时间、身高和体重。根据这些数据，我们还可以计算出每个患者的 BMI = 身高/体重²。通过计算 BMI 并将其纳入数据集，我们正在进行特征工程。\n我们为什么要进行特征工程 特征工程非常强大，因为它允许我们将非线性问题重新表述为线性问题。为了说明这一点，假设恢复时间与身高和体重有以下关系：\n$$ Y = \\beta_0 + \\beta_1（高度）+ \\beta_2（重量）+ \\beta_3（高度/重量^2）+ 噪声 $$\n查看第 3 项，我们可以看到 Y 与身高和体重没有线性关系。这意味着我们可能不会期望线性模型（例如线性回归）能够很好地估计 β 系数。你可以尝试使用非线性模型（例如 DNN），或者我们可以通过进行一些特征工程来帮助我们的模型。如果我们决定将 BMI 作为一个特征，则关系将变为：\n$$ Y = \\beta_0 + \\beta_1（高度）+ \\beta_2（重量）+ \\beta_3（BMI）+ 噪声 $$ Y 现在可以被建模为 3 个变量的线性关系。因此，我们期望线性回归能够更好地估计系数。稍后，我们将看到同样的想法也适用于分类问题。\n为什么不让电脑来做这项工作 如果从技术角度来讲，特征工程本质上就是核心技巧，因为我们将特征映射到更高的平面3。尽管使用核技巧通常不需要太多思考。核函数被视为超参数，可以使用蛮力找到最佳函数——尝试大量不同的函数变体。使用正确的核函数，你可以建模非线性关系。给定正确数量的隐藏层 / 节点，DNN 还将自动构建特征的非线性函数2。那么，如果这些方法可以建模非线性关系，我们为什么还要费心进行特征工程呢？\n我们上面解释了特征工程如何让我们即使在使用线性模型的情况下也能捕捉数据中的非线性关系。这意味着，根据问题的不同，我们可以实现与非线性模型类似的性能。我们将在本文后面详细介绍一个示例。除此之外，使用特征工程还有其他好处，这使得这项技术很有价值。\n首先，你会更好地理解模型的工作原理。这是因为你确切地知道模型使用什么信息进行预测。此外，像逻辑回归这样的模型可以通过直接查看特征系数来轻松解释。第二个原因与第一个原因相符，即模型更容易解释。如果你在工业界工作，这一点尤其重要。你的同事也更有可能接触到一些更简单的模型。第三个原因是你的模型不太可能过度拟合训练数据。通过强制加载不同的超参数，很容易导致对数据中噪声建模。相比之下，有了深思熟虑的特征，你的模型将是直观的，并且很可能模拟真实的潜在趋势。\n数据集 让我们深入研究一个实际的例子。为了尽可能清晰，我们将使用人工生成的数据集。为了避免这个例子太枯燥，我们将围绕它创建一个叙述。假设你的人力资源部门要求你创建一个模型来预测员工是否会晋升。该模型应该考虑员工的年龄和绩效分数。\n我们在下面的代码中为 2000 名假设员工创建特征。员工的年龄可以在 18 到 60 岁之间。绩效分数可以在 -10 到 10 之间（10 为最高）。这两个特征都经过了打乱，因此它们不相关。然后我们使用以下年龄 (a) 和绩效 (p) 函数来生成目标变量：\n$$ \\gamma（a，p）= 100（a）+ 200（p）+ 500（a / p）- 10000 + 500（noise） $$\n当 $\\gamma(a,p)≥ 0$ 时，员工会得到晋升；当 $\\gamma(a,p) \u0026lt; 0 $​时，员工不会得到晋升。我们可以看到，上面的函数中包含了 a/p 项。这意味着决策边界将不是年龄和绩效的线性函数。还包括随机噪声，因此数据不是完全可分离的。换句话说，模型不可能 100% 准确。\nimport numpy as np import pandas as pd n_points = 2000 age = np.round(np.linspace(18, 60, n_points), 2) # age of employee np.random.shuffle(age) # shuffle performance = np.linspace(-10, 10, n_points) # performance score of employee np.random.shuffle(performance) # shuffle noise = np.random.randn(n_points) g = (100 * age) + 200 * (performance) + 500 * age / performance - 10000 + 500 * noise y = [1 if y \u0026gt;= 0 else 0 for y in g] data = pd.DataFrame(data = {\u0026#39;age\u0026#39;: age, \u0026#39;performance\u0026#39;: performance, \u0026#39;y\u0026#39;: y}) print(sum(y)) data.head() --- 474 age\tperformance\ty 0\t53.32\t-6.098049\t0 1\t32.10\t-7.848924\t0 2\t58.72\t9.709855\t1 3\t59.52\t4.387194\t1 4\t28.55\t7.418709\t0 如果上述步骤有点令人困惑，请不要担心。我们可以通过使用以下代码可视化数据集来使事情变得更清晰。在这里，我们创建了数据的散点图，结果可以在图 1 中看到。仅通过两个特征，很容易准确地看到发生了什么。在 y 轴上，我们有员工的绩效分数，在 x 轴上，我们有员工的年龄。晋升员工的分数为红色，未晋升的员工分数为蓝色。最终，2000 名员工中有 459 名（23％）获得了晋升。对于不同的随机样本，该比例会略有变化。\nplt.subplots(nrows = 1, ncols = 1, figsize = (15, 10)) plt.scatter(\u0026#39;age\u0026#39;, \u0026#39;performance\u0026#39;, c = \u0026#39;#ff2121\u0026#39;, s = 50, edgecolors = \u0026#39;#000000\u0026#39;, data = data[data.y == 1]) plt.scatter(\u0026#39;age\u0026#39;, \u0026#39;performance\u0026#39;, c = \u0026#39;#2176ff\u0026#39;, s = 50, edgecolors = \u0026#39;#000000\u0026#39;, data = data[data.y == 0]) plt.ylabel(\u0026#39;Performance Score\u0026#39;, size = 20) plt.xlabel(\u0026#39;Age\u0026#39;, size = 20) plt.yticks(size = 12) plt.xticks(size = 12) plt.legend([\u0026#39;Promoted\u0026#39;, \u0026#39;Not Promoted\u0026#39;], loc = 2, prop = {\u0026#39;size\u0026#39;: 20}) plt.savefig(\u0026#39;../figures/article_feature_eng/figure1.png\u0026#39;, format = \u0026#39;png\u0026#39;) 尽管这些数据是生成的，但我们仍然可以对该图做出现实的解释。在图 1 中，我们可以看到 3 个不同的员工组。第一个是绩效得分低于 0 的组。由于绩效不佳，这些员工中的大多数都没有得到晋升，我们还可以预期其中一些员工被解雇。我们可以预期得分高于 0 的员工要么得到晋升，要么接受其他公司的报价。得分特别高的员工往往会离开。这可能是因为他们的需求量很大，而且在其他地方得到了更好的报价。然而，随着雇主年龄的增长，他们需要更高的绩效分数才能离开。这可能是因为年长的员工在目前的职位上更舒服。\n无论如何，很明显，决策边界不是线性的。换句话说，不可能画出一条直线来很好地区分晋升组和未晋升组。因此，我们不会指望线性模型能做得很好。让我们通过尝试仅使用两个特征（年龄和表现）来拟合逻辑回归模型来证明这一点。\n逻辑回归 在下面的代码中，我们将 2000 名员工分成训练集（70%）和测试集（30%）。我们使用训练集来训练逻辑回归模型。然后，使用该模型，我们对测试集进行预测。测试集的准确率为 82%。这似乎不算太糟糕，但我们应该考虑到只有不到 23% 的员工获得了晋升。因此，如果我们只是猜测没有员工会得到晋升，那么我们应该预期准确率约为 77%。\nfrom sklearn.model_selection import train_test_split import sklearn.metrics as metric import statsmodels.api as sm x = data[[\u0026#39;age\u0026#39;, \u0026#39;performance\u0026#39;]] x = sm.add_constant(x) y = data[\u0026#39;y\u0026#39;] x_train, x_test, y_train, y_test = train_test_split(x, y, test_size = 0.3, random_state = 2024) model = sm.Logit(y_train, x_train).fit() # fit logistic regression model predictions = np.around(model.predict(x_test)) accuracy = metric.accuracy_score(y_test, predictions) print(round(accuracy * 100, 2)) --- Optimization terminated successfully. Current function value: 0.428402 Iterations 7 81.83 通过使用以下代码可视化决策边界，我们可以更好地理解模型正在做什么。在这里，我们在样本空间内生成一百万个点。然后，我们使用逻辑回归模型对所有这些点进行预测。与图 1 中的散点图一样，我们可以绘制每个点。每个点的颜色由模型的预测决定 — 如果模型预测晋升，则为粉红色，否则为浅蓝色。这为我们提供了决策边界的良好近似值，可以在图 2 中看到。然后，我们可以在这些点上绘制实际数据集。\nn_points = 1000000 # use many point to visualise decision boundry age_db = np.linspace(18, 60, n_points) np.random.shuffle(age_db) performance_db = np.linspace(-10, 10, n_points) np.random.shuffle(performance_db) data_db = pd.DataFrame({\u0026#39;age\u0026#39;: age_db, \u0026#39;performance\u0026#39;: performance_db}) data_db = sm.add_constant(data_db) # make predictions on the decision boundry points predictions = model.predict(data_db) y_db = [round(p) for p in predictions] data_db[\u0026#39;y\u0026#39;] = y_db fig, ax = plt.subplots(nrows = 1, ncols = 1, figsize = (15, 10)) # Plot decision boundry plt.scatter(\u0026#39;age\u0026#39;, \u0026#39;performance\u0026#39;, c = \u0026#39;#ffbdbd\u0026#39;, s = 1, data = data_db[data_db.y == 1]) plt.scatter(\u0026#39;age\u0026#39;, \u0026#39;performance\u0026#39;, c = \u0026#39;#b0c4ff\u0026#39;, s = 1, data = data_db[data_db.y == 0]) # Plot employee data points plt.scatter(\u0026#39;age\u0026#39;, \u0026#39;performance\u0026#39;, c = \u0026#39;#ff2121\u0026#39;, s = 50, edgecolors = \u0026#39;#000000\u0026#39;, data = data[data.y == 1]) plt.scatter(\u0026#39;age\u0026#39;, \u0026#39;performance\u0026#39;, c = \u0026#39;#2176ff\u0026#39;, s = 50, edgecolors = \u0026#39;#000000\u0026#39;, data = data[data.y == 0]) plt.ylabel(\u0026#39;Performance Score\u0026#39;, size = 20) plt.xlabel(\u0026#39;Age\u0026#39;, size = 20) plt.yticks(size = 12) plt.xticks(size = 12) plt.savefig(\u0026#39;../figures/article_feature_eng/figure2.png\u0026#39;, format = \u0026#39;png\u0026#39;) 从决策边界来看，我们可以看到模型表现很糟糕。它预测会升职的员工中，大约有一半没有升职。然后，对于大多数获得晋升的员工，它预测他们没有获得晋升。请注意，决策边界是一条直线。这强调了逻辑回归是一个线性分类器。换句话说，该模型只能构建一个决策边界，它是你赋予它的特征的线性函数。此时，我们可能会想尝试不同的模型，但让我们看看是否可以使用特征工程来提高性能。\n具有特征工程的逻辑回归 首先，如下面的代码所示，我们添加了附加特征（即年龄与表现的比率）。从那时起，我们遵循与之前的模型相同的过程。训练测试分割是相同的，因为我们对 “random_state” 使用相同的值。最后，这个模型的准确率达到了 98%，这是一个显着的改进。\ndata[\u0026#39;age_perf_ratio\u0026#39;] = age / performance x = data[[\u0026#39;age\u0026#39;, \u0026#39;performance\u0026#39;, \u0026#39;age_perf_ratio\u0026#39;]] x = sm.add_constant(x) y = data[\u0026#39;y\u0026#39;] x_train, x_test, y_train, y_test = train_test_split(x, y, test_size = 0.3, random_state = 2024) model = sm.Logit(y_train, x_train).fit() # fit new logistic regression model predictions = np.around(model.predict(x_test)) accuracy = metric.accuracy_score(y_test, predictions) print(round(accuracy * 100, 2)) --- Optimization terminated successfully. Current function value: 0.039001 Iterations 17 98.17 该模型仍然只需要员工的年龄和绩效即可进行预测。这是因为附加特征是年龄和绩效的函数。这使我们能够以与以前相同的方式可视化决策边界。即通过在样本空间中的每个年龄-绩效点使用模型的预测。我们可以看到，在图 3 中，通过添加附加特征，逻辑回归模型能够对非线性决策边界进行建模。从技术上讲，这是年龄和绩效的非线性函数，但它仍然是所有 3 个特征的线性函数。\n使用逻辑回归的另一个好处是模型是可解释的。这意味着模型可以用人类的术语来解释4。换句话说，我们可以直接查看模型系数来了解其工作原理。我们可以在表 1 中看到模型特征的系数及其 p 值。我们不会讲得太详细，但系数可以让你根据晋升几率的变化来解释特征的变化5。如果特征具有正系数，则该特征值的增加会导致晋升几率的增加。\n从表 1 可以看出，随着年龄的增长，获得晋升的可能性也会增加。另一方面，对于绩效，这种关系并不那么明显。绩效的提高也会降低年龄/绩效比率。这意味着，绩效提高对几率的影响取决于员工的年龄。这非常符合直觉，因为它与我们在散点图中看到的一致。在这种情况下，可能没有必要使用系数以这种方式解释模型。仅仅可视化决策边界就足够了，但是，随着我们增加特征数量，这样做变得更加困难。在这种情况下，模型系数是理解模型如何工作的重要工具。\n同样，P 值也有助于我们对模型的理解。由于系数是统计估计值，因此具有一定的不确定性。换句话说，我们可以确定系数要么是正值，要么是负值。这一点非常重要，因为如果我们不能确定系数的符号，就很难用几率的变化来解释特征的变化。从表 1 中我们可以看出，所有系数在统计意义上都是显著的。这并不奇怪，因为我们是利用特征函数生成数据的。\n总体而言，当我们生成数据时，上述分析非常简单。因为我们知道使用什么函数来生成数据，所以很明显，附加特征会提高模型的准确性。实际上，事情不会这么简单。如果你对数据没有很好的理解，你可能需要与人力资源部门的某个人交谈。他们可能会告诉你他们过去看到的任何趋势。否则，通过使用各种图表和汇总统计数据探索该数据，你可以了解哪些特征可能很重要。但是，假设我们不想做所有这些艰苦的工作。\n神经网络 为了进行比较，我们使用非线性模型。在下面的代码中，我们使用 Keras 来拟合 NN。我们仅使用年龄和表现作为特征，因此 NN 的输入层的维度为 2。有 2 个隐藏层，分别有 20 个和 15 个节点。两个隐藏层都具有 relu 激活函数，输出层具有 sigmoid 激活函数。为了训练模型，我们使用 10 和 100 个 epoch 的批处理大小。训练集大小为 1400，这给了我们 14000 个步骤。最后，该模型在测试集上的准确率达到了 96%。这与逻辑回归模型的准确率略低，但是也相差不大，不过我们不必进行任何特征工程。\nfrom keras.models import Sequential from keras.layers import Dense x = data[[\u0026#39;age\u0026#39;, \u0026#39;performance\u0026#39;]] y = data[\u0026#39;y\u0026#39;] x_train, x_test, y_train, y_test = train_test_split(x, y, test_size = 0.3, random_state = 2024) model = Sequential() model.add(Dense(20, input_dim = 2, activation = \u0026#39;relu\u0026#39;)) model.add(Dense(15, activation = \u0026#39;relu\u0026#39;)) model.add(Dense(1, activation = \u0026#39;sigmoid\u0026#39;)) model.compile(loss = \u0026#39;binary_crossentropy\u0026#39;, optimizer = \u0026#39;adam\u0026#39;, metrics = [\u0026#39;accuracy\u0026#39;]) model.fit(x_train, y_train, epochs = 100, batch_size = 10) # fit ANN accuracy = model.evaluate(x_test, y_test) print(round(accuracy[1] * 100, 2)) --- Epoch 1/100 2024-06-30 22:31:06.692852: W tensorflow/core/platform/profile_utils/cpu_utils.cc:128] Failed to get CPU frequency: 0 Hz 2024-06-30 22:31:06.891137: I tensorflow/core/grappler/optimizers/custom_graph_optimizer_registry.cc:113] Plugin optimizer for device_type GPU is enabled. 140/140 [==============================] - 3s 4ms/step - loss: 0.5638 - accuracy: 0.7393 ... Epoch 100/100 140/140 [==============================] - 1s 6ms/step - loss: 0.0943 - accuracy: 0.9543 Output is truncated. View as a scrollable element or open in a text editor. Adjust cell output settings... 2024-06-30 22:32:14.638250: I tensorflow/core/grappler/optimizers/custom_graph_optimizer_registry.cc:113] Plugin optimizer for device_type GPU is enabled. 19/19 [==============================] - 1s 10ms/step - loss: 0.0848 - accuracy: 0.9617 96.17 通过查看图 4 中的 NN 决策边界，我们可以看出为什么它被认为是一种非线性分类算法。即使我们只给出了模型年龄和性能，它仍然能够构建非线性决策边界。因此，在一定程度上，模型已经为我们完成了艰苦的工作。你可以说模型的隐藏层已经自动完成了特征工程。那么，考虑到这个模型具有很高的准确性并且需要我们付出的努力较少，我们为什么还要考虑逻辑回归呢？\nNN 的缺点是它只能解释。这意味着，与逻辑回归不同，我们不能直接查看模型的参数来了解其工作原理4。我们可以使用其他方法，但最终，理解 NN 的工作原理更加困难。向非技术人员（例如人力资源主管）解释这一点更加困难。这使得逻辑回归模型在行业环境中更有价值。\n工业界和学术界都存在许多问题，其中大多数问题都比本文给出的示例更复杂。本文提出的方法显然不是解决所有这些问题的最佳方法。例如，如果你尝试进行图像识别，那么使用逻辑回归将无济于事。对于较简单的问题，逻辑回归和对数据的良好理解通常就是你所需要的。\n参考 茶桁的公共文章项目库（2024 年），https://github.com/hivandu/public_articles\u0026#160;\u0026#x21a9;\u0026#xfe0e;\nT. Hastie, R. Tibshirani, J. Friedman, The Elements of Statistical Learning [pg 150] (2017), https://web.stanford.edu/~hastie/ElemStatLearn/\u0026#160;\u0026#x21a9;\u0026#xfe0e;\u0026#160;\u0026#x21a9;\u0026#xfe0e;\nStatinfer, The Non-Linear Decision Boundary (2017), https://statinfer.com/203-6-5-the-non-linear-decision-boundary/\u0026#160;\u0026#x21a9;\u0026#xfe0e;\nR. Gall, Machine Learning Explainability vs Interpretability: Two concepts that could help restore trust in AI (2018), https://www.kdnuggets.com/2018/12/machine-learning-explainability-interpretability-ai.html\u0026#160;\u0026#x21a9;\u0026#xfe0e;\u0026#160;\u0026#x21a9;\u0026#xfe0e;\nUCLA, How do I Interpret Odds Ratios in Logistic Regression? (2020), https://stats.idre.ucla.edu/stata/faq/how-do-i-interpret-odds-ratios-in-logistic-regression/\u0026#160;\u0026#x21a9;\u0026#xfe0e;\n","permalink":"https://hivan.me/posts/%E7%89%B9%E5%BE%81%E5%B7%A5%E7%A8%8B%E7%9A%84%E5%8A%9B%E9%87%8F/","summary":"\u003cp\u003e原文：\u003ca href=\"https://conorosullyds.medium.com/the-power-of-feature-engineering-b6f3bb7de39c?source=user_profile---------74----------------------------\"\u003eThe Power of Feature Engineering\u003c/a\u003e\u003c/p\u003e\n\u003cblockquote\u003e\n\u003cp\u003e为什么你应该使用逻辑回归来建模非线性决策边界（使用 Python 代码）\n作为一名大数据从业者，复杂的机器学习技术非常具有吸引力。使用一些深度神经网络 (DNN) 获得额外的 1% 准确率，并在此过程中启动 GPU 实例，这让人非常满意。然而，这些技术通常将思考留给计算机，让我们对模型的工作原理了解甚少。所以，我想回到基础知识。\n\u003cimg alt=\"img\" loading=\"lazy\" src=\"https://cdn-images-1.readmedium.com/v2/resize:fit:800/1*qGkPni7n8kGxKcADasCNAQ.png\"\u003e\u003c/p\u003e\u003c/blockquote\u003e\n","title":"特征工程的力量"},{"content":"LIME 是XAI方法的起源。它让我们了解机器学习模型的工作原理。具体来说，它可以帮助我们了解个体预测是如何做出的（即局部解释）。\n尽管最近的进展意味着 LIME 不那么受欢迎，但它仍然值得了解。这是因为它是一种相对简单的方法，对于许多可解释性问题来说“足够好”。它也是一种较新的局部可解释性方法——SHAP 的灵感来源。\n因此我们将：\n讨论 LIME 为获取本地解释而采取的步骤。 详细讨论与这些步骤相关的一些选择，包括如何加权样本以及使用哪个替代模型。 应用lime Python 包：https://github.com/marcotcr/lime。 在此过程中，我们将该方法与 SHAP 进行比较。这是为了更好地了解它的缺点。我们还将看到，尽管 LIME 是一种局部方法，但我们仍然可以聚合 lime 权重以获得全局解释。这样做将有助于我们了解该软件包做出的一些默认选择。\nLIME 算法 机器学习模型是复杂的函数。LIME 背后的理念是，如果我们放大实例周围区域的特征空间，这种复杂性就会消失。该函数要简单得多，甚至是线性的。这使我们能够通过对实例的排列构建简单模型来了解如何在此区域进行预测。\n这些简单模型被称为*替代模型。*这些模型必须是本质上可解释的模型，如决策树或线性回归。这一点很重要，因为我们可以通过直接查看这些模型的结构或参数来了解它们的工作原理。\n现在，我们有很多选择来构建这样的替代模型。下面我们讨论最重要的一些。首先，让我们总结一下 LIME 算法所采取的步骤：\n选择你要解释的实例 通过排列特征值来生成样本 根据样本与实例的距离为每个样本分配一个权重 使用原始黑盒模型对这些排列进行预测 使用加权样本和预测作为目标变量来训练替代模型 解释替代模型 结果是一个针对单个实例进行训练的简单替代模型。通过解释这个模型，我们可以了解原始黑盒模型如何对该实例进行预测。一个关键的好处是，这是以与模型无关的方式完成的。由于我们只考虑黑盒模型预测，我们可以将此方法应用于任何机器学习算法。\n排列特征值 排列对于许多 XAI 方法来说都很重要。然而，LIME 对排列特征重要性、PDP 或 ALE 采用了不同的方法。即：\n对于连续特征，我们从具有与特征相同的均值和标准差的正态分布中抽样。 对于分类特征，我们根据训练数据中观察到的比例随机选择类别。 重要的是，这将为我们提供与黑盒模型训练时相似的特征值。它还让我们能够灵活地决定可以创建多少个样本。默认情况下，LIME 包中将其设置为 5000。这通常足以训练线性回归等模型。\n加权样本 上述过程将在整个特征空间中生成样本。然而，我们感兴趣的是模型在实例周围的表现。这就是为什么我们需要根据样本与实例的距离对样本进行加权。为此，LIME 使用高斯核： $$ K(x, x\u0026rsquo;) = exp(-\\frac{||x-x\u0026rsquo;||^2}{\\sigma^2}) $$ 这里 $x$ 是被解释的实例，$x\u0026rsquo;$ 是我们要加权的样本。$||x — x\u0026rsquo;||$ 是归一化特征值的欧几里得距离。对于分类特征，使用特征的单次编码来计算距离。$σ²$ 称为核宽度。它控制分配给扰动样本的权重随着与被解释实例的距离增加而减少的速度。\n默认情况下，核宽度设置为 $0.75 * sqrt$（特征数量）。我们可以更改此值，但不清楚对于给定问题，哪个值最适合。如图 1 所示，如果我们将 $σ²$ 设置得太小，那么只有非常接近实例的样本才会获得显著的权重。因此，我们将无法捕捉到特征值中的足够变化，以了解它们如何影响预测。太大，关系就不再是线性的。\n特征数量 我们不需要在黑盒模型使用的所有特征上训练我们的代理模型。特别是线性代理模型只能处理有限的数量。我们可以决定解释中使用的特征数量，其默认值最多为 10。如果我们选择的数字小于原始特征的数量，我们还需要选择一种选择算法（例如前向选择）。\n离散化 连续特征的另一种选择是，是否根据分位数或十分位数将它们分组。这称为离散化。我们将在应用 lime 包时看到，默认情况下，所有数值特征都将按其分位数分组。这是为了更容易为这些特征提供解释。\n替代模型 最后，我们可以选择使用什么模型作为替代模型。默认模型是岭回归，但我们可以使用其他类型的线性回归，甚至是决策树。请记住，模型必须是可解释的。对于线性模型，每个特征的系数告诉我们该特征对我们想要解释的预测有何贡献。\n最初，所有这些选择似乎都是一件好事。然而，这导致了 LIME 最大的弱点——解释不一致。这种灵活性让我们陷入了一种 P-hacking 形式，因为我们可以操纵变量，直到得到我们想要的解释。对于那些没有良好领域知识的人来说，判断一个解释是否合理可能也很困难。\nLIME 也“不一致”。这意味着如果我们调整模型，使与特征的关系发生变化，LIME 权重不一定会以反映这种变化的方式发生变化。例如，假设我们将模型从 M1 更改为 M2。现在，特征可能会比以前更多地增加预测。但是，我们不一定能够使用 LIME 来判断这一点。\n克服这些弱点是 SHAP 如此受欢迎的原因之一。该方法基于Shapley 值背后的理论。这为局部可解释性方法带来了一些理想的特性。SHAP 值本质上是一致的。由于 Shapley 值的计算方式缺乏灵活性，因此操纵结果也更加困难。\nlime 包的应用 现在，让我们将这一理论应用于实际。我们将应用 LIME 包，然后汇总 LIME 权重以提供全局解释。我们将看到，我们必须创建自己的函数来创建像平均 LIME 权重或蜂群这样的图。这是 SHAP 受欢迎的另一个关键原因——该包为你提供了这些图。\n我们从导入开始。由于我们处理的是表格数据，因此我们使用 LimeTabularExplainer 函数（第 7 行）。\n# 导入 import pandas as pd import numpy as np import matplotlib.pyplot as plt %matplotlib inline 从sklearn.ensemble导入RandomForestRegressor 从lime.lime_tabular导入LimeTabularExplainer 数据和模型 我们将使用abalone 数据集：https://archive.ics.uci.edu/dataset/1/abalone(CC BY 4.0)。abalone(鲍鱼) 是一种美味的贝类。我们想利用壳重和去壳重量（肉的重量）等特征来预测其壳中的环数。\n我们加载数据集（第 2-4 行）并选择目标（第 6 行）。我们还进行了一些特征工程。首先，我们排除一些高度相关的特征（第 7 行）。这是因为它们与其他特征的相关性为 1。最后，我们为性别特征创建单次编码（第 10-13 行）。你可以在 图 2 中看到最终特征集的快照。我们总共有 8 个特征。\n#导入数据集 data = pd.read_csv(path_data + \u0026#34;/鲍鱼.data\u0026#34;, names=[\u0026#34;sex\u0026#34;,\u0026#34;length\u0026#34;,\u0026#34;diameter\u0026#34;,\u0026#34;height\u0026#34;,\u0026#34;whole weight\u0026#34;, \u0026#34;shucked weight\u0026#34;,\u0026#34;viscera weight\u0026#34;,\u0026#34;shell weight\u0026#34;,\u0026#34;rings\u0026#34;]) y = data[\u0026#34;rings\u0026#34;] X = data[[\u0026#34;sex\u0026#34;, \u0026#34;length\u0026#34;, \u0026#34;height\u0026#34;, \u0026#34;shucked weight\u0026#34;, \u0026#34;viscera weight\u0026#34;, \u0026#34;shell weight\u0026#34;]] # Create dummy variables X[\u0026#39;sex.M\u0026#39;] = [1 if s == \u0026#39;M\u0026#39; else 0 for s in X[\u0026#39;sex\u0026#39;]] X[\u0026#39;sex.F\u0026#39;] = [1 if s == \u0026#39;F\u0026#39; else 0 for s in X[\u0026#39;sex\u0026#39;]] X[\u0026#39;sex.I\u0026#39;] = [1 if s == \u0026#39;I\u0026#39; else 0 for s in X[\u0026#39;sex\u0026#39;]] X = X.drop(\u0026#39;sex\u0026#39;, axis=1) X.head() 我们使用这些特征来训练模型来预测环的数量（第 2-3 行）。在本例中，我们使用了随机森林。但是，请记住 LIME 与模型无关。这意味着你应该能够将其与大多数建模包一起使用。\n# 训练模型 model = RandomForestRegressor() model.fit(X, y) 本地解释 要创建本地解释，我们首先要创建一个 LIME 解释器（第 2-6 行）。我们传入 X 特征矩阵、特征名称和目标变量名称。我们让包知道我们有一个回归模型（第 5 行），并设置随机状态（第 6 行），以便每次新运行都能获得相同的结果。\n# 创建解释器 explainer = LimeTabularExplainer(X.values, feature_names=X.columns, class_names=[\u0026#39;rings\u0026#39;], mode=\u0026#39;regression\u0026#39;, random_state=101) 然后，我们使用此解释器为我们的第一个预测创建一个解释器对象 exp（第 2-4 行）。最后，我们显示这个解释器对象（第 7 行）。此操作的输出就是你在 图 3 中看到的内容。通过设置 table=True，我们将表格包含在右侧。我们尚未为核宽度或代理模型等选项设置参数，因此包将使用默认值。\n# 获取第一行的解释 exp = explainer.explain_instance(X.iloc[0], model.predict, labels=X.columns) #显示解释 exp.show_in_notebook(show_table=True) 图 3 为我们提供了有关第一个预测的大量信息。我们可以看到预测值为 12.98 个环。中间的图表告诉我们每个特征对预测的贡献。请注意，有 8 个条形图 — 每个特征一个。例如，对于这只鲍鱼，壳重量的值减少了预测环的数量。值 -1.20 是此特征的 LIME 权重。右侧的表格给出了特征值。\n每个特征的 LIME 权重是替代模型的系数。与 SHAP 不同，权重和平均预测的总和不会等于给定实例的预测。你可以使用下面的代码来确认这一点。这是因为 LIME 并不“高效”。\n# 检查计算 exp_weight = [x[1] for x in exp.as_map()[1]] print(np.mean(y_pred) + sum(exp_weight)) print(y_pred[0]) --- 9.64 12.68 此属性是 SHAP 的另一个优点。也就是说，如果你将每个模型特征的 SHAP 值与平均预测相加，则可以得到该实例的预测。换句话说，它们准确地告诉我们特征对预测的改变程度。这改善了对 SHAP 值的解释。使用 LIME，我们只知道方向和重要性。\n充分利用 LIME（全局解释） 当你想了解单个预测是如何做出时，局部解释非常有用。但是，查看单个预测并不能告诉我们模型的总体工作原理。为此，我们可以使用不同的局部解释聚合。也就是说，我们将使用不同的图表组合多个预测的 LIME 权重。\n首先，我们需要从解释器对象中获取 LIME 权重。为此，我们可以使用下面的函数return_weights。此函数接受一个解释器对象 exp。从此对象中，它将获取并返回一个 LIME 权重列表 exp_weight。这些权重将按照与 X 特征矩阵中的特征相同的顺序排序。\ndef return_weights（exp）： \u0026#34;\u0026#34;\u0026#34; 从 LIME 解释对象获取权重 \u0026#34;\u0026#34;\u0026#34; exp_list = exp.as_map()[1] exp_list = sorted(exp_list, key=lambda x: x[0]) exp_weight = [x[1] for x in exp_list] return exp_weight 要使用此函数，我们将迭代 X 特征矩阵的前 100 行（第 4 行）。对于每次迭代，我们将创建一个解释对象（第 7-10 行）。我们使用 return_weights 函数从此对象获取权重（第 13 行），并将它们附加到权重列表中（第 14 行）。最后，我们使用此权重列表创建 DataFrame， 即 lime_weights（第 17 行）。\n权重 = [] # 遍历特征矩阵中的前 100 行 for x in X.values[ 0 : 100 ]: # 获取解释 exp = explainer.explain_instance(x, model.predict, num_features=10, labels=X.columns) # 获取权重 exp_weight = return_weights(exp) weights.append(exp_weight) # 创建数据框 lime_weights = pd.DataFrame(data=weights,columns=X.columns) lime_weights 数据集的形状为 (100,8)。每行代表一个不同的预测。对于每个预测，8 个特征中的每一个都有一个 LIME 权重。我们现在可以使用此数据集创建 LIME 权重的全局聚合。\n绝对平均值 第一个聚合可以帮助我们了解哪些特征最重要。具有高正或负 LIME 权重的特征对预测的影响更大。对于每个特征，我们可以取所有 LIME 权重的绝对平均值。一般来说，具有较大平均权重的特征对预测的贡献较大。\n我们可以在图 4 中看到我们模型的平均权重。请注意，壳重和去壳重与其他特征相比具有更大的平均权重。这告诉我们，这些特征在预测环数时是最重要的。\n要创建此图表，我们首先取权重的绝对平均值（第 2 行）。然后，我们创建一个包含两列的新 DataFrame — 特征名称和绝对平均值（第 3 行）。我们将此 DataFrame 从最大到最小的平均权重排序（第 4 行）。最后，我们使用此 DataFrame 绘制条形图（第 9-11 行）。\n# 获取 LIME 权重的绝对平均值 abs_mean = lime_weights.abs().mean(axis=0) abs_mean = pd.DataFrame(data={\u0026#39;feature\u0026#39;:abs_mean.index, \u0026#39;abs_mean\u0026#39;:abs_mean}) abs_mean = abs_mean.sort_values(\u0026#39;abs_mean\u0026#39;) # 绘制绝对均值 fig, ax = plt.subplots(nrows=1, ncols=1,figsize=(8,4)) y_ticks = range(len(abs_mean)) y_labels = abs_mean.feature plt.barh(y=y_ticks,width=abs_mean.abs_mean,) plt.yticks(ticks=y_ticks,labels=y_labels,size= 12) plt.title(\u0026#39;\u0026#39;) plt.ylabel(\u0026#39;\u0026#39;) plt.xlabel(\u0026#39;Mean |Weight|\u0026#39;,size=15) 特征趋势 我们还可以查看模型特征之一的趋势——整体重量。这是整只鲍鱼的重量。在图 5 中，我们可以看到，随着整体重量的增加，LIME 权重也会增加。更高的 LIME 权重表示，对于特定预测，特征值增加了预测的环数。因此，该图表告诉我们，随着鲍鱼重量的增加，其壳中的环数趋于增加。这是有道理的，因为我们认为年龄较大的鲍鱼会更大/更重。\n要创建此图表，我们首先获取整个重量特征的 LIME 权重（第 4 行）。我们还获取相应的特征值（第 5 行）。然后我们创建权重和特征值的散点图（第 7 行）。\nfig, ax = plt.subplots(nrows=1, ncols=1,figsize=(8,3)) # 获取权重和特征值 feature_weigth = lime_weights[\u0026#39;shell weight\u0026#39;] feature_value = X[\u0026#39;shell weight\u0026#39;][0:100] plt.scatter(x=feature_value ,y=feature_weigth) plt.ylabel(\u0026#39;LIME Weight\u0026#39;,size=12) plt.xlabel(\u0026#39;shell weight\u0026#39;,size=12) 蜂群 我们的最终聚合是一个蜂群图。如图6所示，这是所有 LIME 权重的图。值按 y 轴上的特征分组。对于每个组，点的颜色由相同特征的值决定（即，较高的特征值为红色）。特征按平均 LIME 权重排序。\n为了创建此图表，我们使用以下代码。为了给出概述，我们迭代每个特征（第 8 行）。对于每个特征，我们获得权重和值（第 10-11 行）。然后，我们使用这些创建散点图（第 13-18 行）。诀窍是将每个点的 y 值设置为相同的值（第 14 行）。这就是我们如何将散点图的每个点放在一条直线上。\nfig, ax = plt.subplots(nrows=1, ncols=1,figsize=(8,4)) y_ticks = range(len(abs_mean)) y_labels = abs_mean.feature # 为每个特征绘制散点图 for i,feature in enumerate(y_labels): feature_weigth = lime_weights[feature] feature_value = X[feature][0:100] plt.scatter(x=feature_weigth , y=[i]*len(feature_weigth), c=feature_value, cmap=\u0026#39;bwr\u0026#39;, edgecolors=\u0026#39;black\u0026#39;, alpha=0.8) plt.vlines(x=0,ymin=0,ymax=len(y_labels),colors=\u0026#39;black\u0026#39;,linestyles=\u0026#34;--\u0026#34;) plt.colorbar(label=\u0026#39;Feature Value\u0026#39;,ticks=[]) plt.yticks(ticks=y_ticks,labels=y_labels,size=12) plt.xlabel(\u0026#39;LIME Weight\u0026#39;,size=15) 如果你熟悉 SHAP 软件包，你会认出这些图。这些图的易用性和深刻见解是 SHAP 如此受欢迎的另一个原因。在整篇文章中，我们还解释了 SHAP 背后的理论如何产生理想的特性。最终，该软件包比 LIME 更易于使用，并提供更可靠的局部解释。\nLIME 仍然很有用。在许多低风险应用中，解释的一致性并不那么重要。此外，正如我们所见，该理论相当简单。根据复杂模型的单个预测构建线性模型的想法可能并不难解释。这肯定比解释 SHAP 背后的理论更容易。\n我们还提到 SHAP 的灵感来自 LIME。这是因为 SHAP 使用基于单个预测构建的线性模型来估计 Shapley 值。从这个意义上讲，SHAP 可以看作是 LIME 的一个特例，其中模型权重是 Shapley 值。这是使用特定内核实现的。最终，了解 LIME 可以帮助你了解 SHAP。\n如果你有兴趣了解有关 SHAP 的更多信息，我后续将会写一些关于 SHAP 的文章。\n其他更多的基础机器学习、神经网络内容，可以看看之前的「人工智能核心知识」，也可以看看下面这些系列文章：\n「AI秘籍」系列课程：\n人工智能应用数学基础 人工智能Python基础 人工智能基础核心知识 人工智能BI核心知识 人工智能CV核心知识 数据集 W. J Nash, et. al., 1994, Abalone Data Set, Irvine, CA: University of California, School of Information and Computer Science (License: CC0: Public Domain), https://archive.ics.uci.edu/ml/datasets/Abalone\n参考 Ribeiro, M.T., Singh, S. and Guestrin, C., 2016 “Why should i trust you?” Explaining the predictions of any classifier. https://arxiv.org/abs/1602.04938\nC. Molnar，Interpretable Machine Learning, 2021, https://christophm.github.io/interpretable-ml-book/lime.html\nLIME Python 包，https://github.com/marcotcr/lime\nSHAP Python 包，https://github.com/slundberg/shap\n","permalink":"https://hivan.me/posts/%E6%B7%B1%E5%85%A5%E7%A0%94%E7%A9%B6-lime-%E7%9A%84%E6%9C%AC%E5%9C%B0%E8%A7%A3%E9%87%8A/","summary":"\u003cblockquote\u003e\n\u003cp\u003e本地可解释模型\u0026ndash;不可知论解释（LIME）的直觉、理论和代码\n\u003cimg alt=\"img\" loading=\"lazy\" src=\"https://cdn-images-1.readmedium.com/v2/resize:fit:800/1*Zl6yhlbeV2W00kPua_FmTw.png\"\u003e\u003c/p\u003e\u003c/blockquote\u003e","title":"深入研究 LIME 的本地解释"},{"content":"为什么要集成 ChatGPT？ 将 ChatGPT 集成到你的应用中可以带来多种好处，可以提高用户互动性和满意度：\n增强的用户体验：ChatGPT 提供自然、直观的对话体验，让用户更轻松地与你的应用进行交互。\n客户支持的自动化：使用 ChatGPT，你可以自动响应常见查询，减轻客户支持团队的负担并为用户提供即时帮助。\n个性化：ChatGPT 可以根据用户数据提供个性化响应，增强互动的相关性和参与度。\n可扩展性：作为一种 AI 解决方案，ChatGPT 可以同时处理多个对话，使其成为海量用户应用的理想选择。\n持续改进：OpenAI 不断更新和改进 ChatGPT，确保你能够获得 AI 和自然语言处理方面的最新进展。\n将 ChatGPT 集成到你的应用中的步骤 将 ChatGPT 集成到你的应用中涉及几个关键步骤，从设置你的 OpenAI 帐户到实现 API 并确保流畅的用户体验。以下是帮助你完成整个过程的详细指南：\n设置你的 OpenAI 帐户 集成 ChatGPT 的第一步是设置 OpenAI 帐户。访问 OpenAI 网站并注册一个帐户。注册后，你需要订阅最适合你需求的 API 计划。OpenAI 根据使用情况提供各种定价层级，因此请选择符合你预期流量和互动量的定价层级。\n获取你的 API 密钥 设置帐户和订阅后，你将收到一个 API 密钥。此密钥对于验证你的应用对 ChatGPT API 的请求至关重要。请确保安全存储此密钥，因为它授予你访问 OpenAI 帐户和使用权限的权限。\n3.熟悉 API 文档 OpenAI 为ChatGPT API提供了全面的文档，详细介绍了端点、请求参数和响应格式。熟悉此文档可了解如何有效地与 API 交互。该文档还包括有关速率限制、错误处理和使用 API 的最佳实践的指南。\n设计聊天界面 精心设计的聊天界面对于提供无缝的用户体验至关重要。设计聊天界面时请考虑以下元素：\n输入字段：为用户提供清晰且易于使用的输入字段来输入他们的消息。 聊天气泡：使用聊天气泡显示用户消息和 ChatGPT 响应，使对话自然流动。 加载指示器：实现加载指示器，向用户显示应用程序正在处理他们的输入并生成响应。 个性化：通过用户名、头像和其他上下文信息个性化聊天界面，以增强参与度。 实现 ChatGPT API 设计完成后，你就可以开始实现 ChatGPT API。这涉及使用用户的输入向 API 端点发出 HTTP 请求，并在聊天界面中显示生成的响应。交互的基本流程通常包括：\n发送用户输入：从输入字段捕获用户的消息并将其发送到 ChatGPT API 端点。 处理响应：从 API 接收响应并解析 JSON 数据以提取生成的文本。 显示响应：使用生成的响应更新聊天界面，确保它以对话格式显示。 确保安全和隐私 集成 ChatGPT 时，确保用户数据的安全性和隐私性非常重要。实施以下措施来保护用户信息：\n加密：使用 HTTPS加密你的应用和 ChatGPT API 之间传输的数据。\n访问控制：保护你的 API 密钥并限制对它的访问。实施身份验证和授权机制来控制谁可以与 ChatGPT 集成进行交互。\n数据匿名化：避免向 ChatGPT API 发送敏感或个人身份信息。尽可能匿名化用户数据。\n彻底测试 彻底的测试对于确保你的 ChatGPT 集成按预期工作至关重要。测试各种场景，包括不同类型的用户输入、边缘情况和错误处理。测试期间请注意以下方面：\n响应准确性：确保 ChatGPT 生成的响应相关且准确。 性能：测试聊天界面的性能，包括响应时间和并发对话的处理。 用户体验：评估整体用户体验，包括对话流程和界面可用性。 监控和优化 ChatGPT 集成上线后，请持续监控其性能和用户交互。使用分析来跟踪使用模式、用户满意度以及出现的任何问题。根据这些数据，优化集成以提高性能和用户体验。定期更新你的实施，以利用 ChatGPT API 中的新功能和改进。\nChatGPT 集成的最佳实践 为确保将ChatGPT成功集成到你的应用中，请遵循以下最佳做法：\n提供清晰的说明：通过提供清晰的说明和示例，指导用户如何与 ChatGPT 进行交互。 设定期望：告知用户 ChatGPT 的功能和局限性，以管理他们的期望并避免挫败感。 妥善处理错误：实施错误处理来管理诸如 API 超时或无效响应等问题。向用户提供信息丰富的错误消息和后备选项。 通过上下文增强：在可能的情况下，为 ChatGPT 提供有关对话或用户的上下文，以生成更相关的响应。 收集反馈：鼓励用户提供有关聊天体验的反馈。利用这些反馈不断改进集成。 高级功能和定制 完成基本集成后，请考虑探索高级功能和自定义选项，以进一步增强 ChatGPT 实现：\n自定义提示：自定义发送到 ChatGPT 的提示以指导其响应并根据应用的上下文定制对话。\n多语言支持：如果你的应用服务于全球受众，请实现多语言支持，以允许用户使用不同语言与 ChatGPT 进行交互。\n与其他服务集成：将 ChatGPT 与其他服务（如数据库或第三方 API）相结合，以提供丰富而动态的响应。\n上下文保存：实施在交互过程中保存对话上下文的机制，使 ChatGPT 能够在多次交流中保持一致的对话。\nChatGPT 集成的用例 将 ChatGPT 集成到你的应用中，可在各个领域开辟广泛的可能性。以下是一些需要考虑的用例：\n客户支持：自动响应常见的客户查询，提供即时帮助，并在必要时将复杂问题上报给人工代理。\n虚拟助手：创建可以帮助用户完成日程安排、提醒和信息检索等任务的虚拟助手。\n教育应用程序：通过互动辅导、个性化学习体验和即时反馈增强教育应用程序。\n电子商务：在电子商务应用中实现智能聊天功能，帮助用户进行产品推荐、订单跟踪和客户服务。\n社交媒体和社区应用程序：通过促进对话、回答问题和主持讨论，促进社交媒体和社区应用程序的参与。\n将 ChatGPT 集成到你的应用中是增强用户互动和提供智能对话体验的有效方法。通过遵循本指南中概述的步骤并遵循最佳做法，你可以创建无缝且安全的聊天集成，从而为你的应用增加巨大的价值。\nChatGPT 的高级语言理解和自然对话能力使其成为从客户支持到虚拟助手等各种应用的理想选择。在着手将 ChatGPT 集成到你的应用中时，请记住优先考虑用户体验、安全性和持续优化。\n有关 ChatGPT 集成的更多详细指南和最新更新，你可以访问 OpenAI 官方文档和资源。通过正确的方法和精心实施，ChatGPT 可以改变你应用的用户交互，提供动态且引人入胜的体验，让用户不断回头。\n当然，如果你作为一个中小型企业，不想支持 ChatGPT 高额的 Token 费用，想要在自己服务器上搭建一个小型的解决方案也是可以的，可以看看针对中小型企业的「企业项目 AI 实战」。\n","permalink":"https://hivan.me/posts/%E5%A6%82%E4%BD%95%E5%B0%86-chatgpt-%E9%9B%86%E6%88%90%E5%88%B0%E4%BD%A0%E7%9A%84%E5%BA%94%E7%94%A8%E4%B8%AD/","summary":"在当今快速发展的技术环境中，将人工智能聊天解决方案集成到你的应用程序中可以显著提升用户体验和参与度。OpenAI 的 ChatGPT 以其对话能力和高级语言理解而闻名，对于希望在其应用程序中实现智能聊天功能的开发人员来说是一个绝佳的选择。那我们今天就来引导你完成将 ChatGPT 集成到你的应用程序中的步骤和最佳实践，为你的用户提供无缝和交互式的体验。\n\u003cimg alt=\"图片-3\" loading=\"lazy\" src=\"https://www.datasciencecentral.com/wp-content/uploads/2024/06/image-3.png\"\u003e","title":"如何将 ChatGPT 集成到你的应用中"},{"content":"概述 了解大型语言模型的基础知识及其功能。 熟悉不同类型的 LLM 及其应用。 设置用于 LLM 的开发环境，包括访问预先训练的模型。 强调数据准备对于获得准确可靠的结果的重要性。 了解如何针对特定任务微调 LLM 以提高性能。 评估模型输出并解释结果以评估准确性和相关性。 不断迭代和改进 LLM 实现以保持领先于不断发展的技术。 LLMs (LLM) 的 7 个基本步骤 现在让我们探索掌握大型语言模型的7个基本步骤。\n1. 了解LLMs (LLM) 的意义 对于想要深入学习LLMs 的人来说，首先要简单了解LLMs 是什么。这些模型是在大量文本数据上训练的，这使得它们能够识别模式、理解上下文并像人类一样做出反应。此外，如果经过良好的微调，这些模型还可以专注于不同的领域，例如翻译语言或总结段落等。\n2. 熟悉各种类型的LLMs LLM 有许多类别，每种类别都有其独特的功能和能力。例如，OpenAI 有 GPT-3（生成式预训练 Transformer 3），谷歌开发了 BERT（来自 Transformer 的双向编码器表示），而 T5（文本到文本传输 Transformer）是由谷歌人工智能部门创建的。因此，这意味着并非所有模型的工作方式都相似，因为它们都有自己的优点和缺点，这取决于人们希望它们完成的任务——因此，在做出任何决定之前，有必要对这些进行更多研究。\n3. 设置开发环境 要使用 LLM，您需要一个合适的开发环境。这可能包括安装所需的库和框架、设置云服务或访问预先训练的模型。许多 LLM 提供商提供易于使用的 API 和 SDK（软件开发工具包），以简化集成。\n4.了解数据准备的重要性 LLM 的质量取决于训练数据的质量。因此，在开始使用它们之前，如果你想获得准确可靠的结果，你必须正确清理和准备数据集。文本预处理、删除不相关或敏感信息、格式化以便 LLM 可以理解——这些只是一些例子。\n5. 针对你的具体任务对 LLM 进行微调 尽管预训练语言模型几乎可以做任何事情，但它们仍然需要一些专业化的帮助。通过使用与主要数据集相关的较小数据集对LLM 进行微调，您可以让系统更好地理解您的个案特性，从而实现更高的性能准确性。\n6. 评估和解释结果 将数据输入经过微调的 LLM 后，就该看看结果了。这意味着您应该评估文本与已知事实的符合程度、形成逻辑链（连贯性）、与主题的关联程度（相关性）。此外，还要准备好检测模型本身可能引入的输出限制或偏差。\n7. 不断迭代，持续改进 LLM 永远不会停止变化；时不时地，人们会听说一种新模型或技术有望比其前身表现更好。鉴于这一事实，您必须保持领先地位，永远不要满足于当前的 LLM 实现——始终寻找新的方法来使其变得更好。添加更多数据源，尝试不同的微调方法，或在更高级的模型可用时切换到它们。\n结论 大型语言模型正在实现类似人类的文本理解，这正在改变技术。任何人都可以通过遵循这七个关键阶段来学习 LLM，这些阶段涵盖了从理解各种模型到优化效率的所有内容。了解这些过程可以帮助您利用新的机会，并在 LLM 技术发展的同时推动各行各业的创新。在本文中，我们探讨了掌握大型语言模型的 7 个基本步骤。\n如果您觉得这篇文章很有帮助，并且想要真正掌握LLM底层逻辑，具备数据清理和整理能力，那么「企业项目 AI 实战」计划就是您的最佳选择，不管您是打算转行的小白，换赛道的程序员还是打算部署 AI 能力的企业都将会受益匪浅。立即查看该计划！\n","permalink":"https://hivan.me/posts/%E6%8E%8C%E6%8F%A1%E5%A4%A7%E5%9E%8B%E8%AF%AD%E8%A8%80%E6%A8%A1%E5%9E%8B%E7%9A%84-7-%E4%B8%AA%E5%9F%BA%E6%9C%AC%E6%AD%A5%E9%AA%A4/","summary":"\u003ch2 id=\"介绍-llms-正在改变我们今天与科技互动的方式这些人工智能程序能够理解和模仿人类语言它们可以应用于数据分析客户服务内容创作和其他领域但对于新手来说了解如何使用它们似乎很有挑战性本文将引导读者了解掌握大型语言模型的-7-个基本步骤\"\u003e介绍 LLMs 正在改变我们今天与科技互动的方式。这些人工智能程序能够理解和模仿人类语言。它们可以应用于数据分析、客户服务、内容创作和其他领域。但对于新手来说，了解如何使用它们似乎很有挑战性。本文将引导读者了解掌握大型语言模型的 7 个基本步骤。\u003c/h2\u003e\n\u003cp\u003e本文还旨在通过定义七个关键步骤来提供学习 LLM 的完整手册。即使是新手也可以通过将流程分解为易于完成的操作来掌握并有效利用LLM的强大功能。阅读本文后，读者将能够通过了解基础知识并知道如何调整和评估模型来将 LLM 用于各种目的。\u003c/p\u003e","title":"掌握大型语言模型的 7 个基本步骤"},{"content":"Hi，大家好。我是茶桁。\n前段时间我介绍过一款文字生视频的 AI 工具：SadTalker， 当时咱们是作为 Stable Diffusion 的插件来安装的。\n那基于 Stable Diffusion 呢，咱们今天就来聊聊新开源的 Stable Diffusion 3。\n在文字生成图片这个领域，一直是有三个主要的竞争者，Midjourney, DELL-3, 还有就是 Stable Diffusion。前不久，Stable Diffusion 开源了 SD3，使用过后，效果直逼 Midjourney，而在社区内的出图效果来看，可以替代 Midjourney 作为主力出图工具了。这可真是能省下不少钱，毕竟那可都是真金白银。\n好，那到底如何去安装 Stable Diffusion 3 呢？这次咱们不使用 Stable Diffusion WebUI，来看另外一款 UI 工具： ComlyUI。\n这款应用的风格是类似于 Blender，在相关节点之间进行连线来完成通路。\n具体安装嘛，首先，我们需要下载开源的 Stable Diffusion 3 大模型：https://huggingface.co/stabilityai/stable-diffusion-3-medium/tree/main\n如何你和我一样是 M1 芯片，建议你选择 sd3_medium_incl_clips_t5xxlfp8.safetensors，总体上来说，这个模型比 fp16 要快上不少，生图的质量也算有所保障。\n下载好之后，接下来我们去下载 ComlyUI: https://github.com/comfyanonymous/ComfyUI?tab=readme-ov-file\n直接git clone，克隆到本地后进入ComfyUI目录。打开目录，将我们刚才下载的模型拷贝到/ComlyUI/models/checkpoints/中。\n然后执行python main.py，如果之前你已经根据我的教程完成过 SD 的环境设置，那么基本上，这次直接就会启动了，地址为: http://127.0.0.1:8188。\n基本上，到这里我们就完成了，不过如果你对语言有要求（障碍），那咱们还可以下载一个语言包，反正我是需要语言包的：https://github.com/AIGODLIKE/AIGODLIKE-ComfyUI-Translation，一样，先执行git clone 将其克隆到本地，然后将其放入目录 /ComfyUI/custom_nodes/，再到界面里去设置一下就可以了。\n好了，至此安装结束了，咱们可以自己尝试一下生成图了。至于如何选择模型，如何设置大小和图片数量，以及其他设置，自己摸索一下就能明白。实在还需要一个小指导的，可以关注我视频号去看看我最新一期的视频。\n下课\u0026hellip;\n","permalink":"https://hivan.me/posts/stable-diffusion-3-%E5%BC%80%E6%BA%90%E4%BA%86%E5%AE%8C%E5%85%A8%E4%B8%8D%E8%BE%93-midjourney/","summary":"\u003cimg alt=\"sd3demo\" loading=\"lazy\" src=\"https://huggingface.co/stabilityai/stable-diffusion-3-medium/blob/main/sd3demo.jpg\"\u003e","title":"Stable Diffusion 3 开源了，完全不输 Midjourney"},{"content":"在本教程中 在我最初的系列 Numba CUDA 示例 （参见第 1、2、3 和 4 部分）之后，我们将研究未经优化的单流代码与使用流并发和其他优化的稍好版本之间的比较。我们将从头开始学习如何使用英伟达 Nsight 系统来剖析和分析 CUDA 代码。本教程中包含 5 段代码，所有代码都可以在 https://colab.research.google.com/drive/1dY6l4HOfQwzmnbdzZRw6z0Lpb9FN1kM3?usp=sharing 中找到。\nNsight 系统 NVIDIA 建议最佳实践是遵循APOD 框架（评估、并行化、优化、部署）。有各种专有、开源、免费和商业软件可用于不同类型的评估和分析。资深 Python 用户可能熟悉基本分析器，例如、cProfile，line_profiler，memory_profiler（不幸的是，截至 2024 年已不再维护）和更高级的工具，例如PyInstrument和Memray。这些分析器针对“主机”的特定方面，例如 CPU 和 RAM 使用情况。\n但是，分析“设备”（例如 GPU）代码及其与主机的交互需要设备供应商提供的专用工具。对于 NVIDIA GPU，Nsight Systems、Nsight Compute 和 Nsight Graphics 可用于分析计算的不同方面。在本教程中，我们将重点介绍如何使用 Nsight Systems，这是一个系统范围的分析器。我们将使用它来分析通过 Numba CUDA 与 GPU 交互的 Python 代码。\n首先，你需要 Nsight Systems CLI 和 GUI。CLI 可以单独安装，用于在支持 GPGPU 的系统中分析代码。完整版包括 CLI 和 GUI。请注意，这两个版本都可以安装在没有 GPU 的系统中。从 NVIDIA 网站获取你需要的版本。\n为了更容易在 GUI 中可视化代码段，NVIDIA 还提供了 Python pip和conda-installable 库nvtx，我们将使用它来注释代码段。稍后将详细介绍。\n设置一切：一个简单的例子 在本节中，我们将设置开发和分析环境。下面是两个非常简单的 Python 脚本：kernels和 run_v1。前者将包含所有 CUDA 内核，后者将作为运行示例的入口点。在此示例中，我们遵循文章CUDA by Numba 示例第 3 部分：流和事件 中介绍的“reduce”模式来计算数组的总和。\n#%%writefile kernels import numba from numba import cuda THREADS_PER_BLOCK = 256 BLOCKS_PER_GRID = 32 * 40 @cuda.jit def partial_reduce(array, partial_reduction): i_start = cuda.grid(1) threads_per_grid = cuda.blockDim.x * cuda.gridDim.x s_thread = numba.float32(0.0) for i_arr in range(i_start, array.size, threads_per_grid): s_thread += array[i_arr] s_block = cuda.shared.array((THREADS_PER_BLOCK,), numba.float32) tid = cuda.threadIdx.x s_block[tid] = s_thread cuda.syncthreads() i = cuda.blockDim.x // 2 while i \u0026gt; 0: if tid \u0026lt; i: s_block[tid] += s_block[tid + i] cuda.syncthreads() i //= 2 if tid == 0: partial_reduction[cuda.blockIdx.x] = s_block[0] @cuda.jit def single_thread_sum(partial_reduction, sum): sum[0] = numba.float32(0.0) for element in partial_reduction: sum[0] += element @cuda.jit def divide_by(array, val_array): i_start = cuda.grid(1) threads_per_grid = cuda.gridsize(1) for i in range(i_start, array.size, threads_per_grid): array[i] /= val_array[0] #%%writefile run_v1 import argparse import warnings import numpy as np from numba import cuda from numba.core.errors import NumbaPerformanceWarning def run(size: int): # Define host array a = np.ones(size, dtype=np.float32) print(f\u0026#34;Old sum: {a.sum():.3f}\u0026#34;) # Array copy to device and array creation on the device. dev_a = cuda.to_device(a) dev_a_reduce = cuda.device_array((BLOCKS_PER_GRID,), dtype=dev_a.dtype) dev_a_sum = cuda.device_array((1,), dtype=dev_a.dtype) # Launching kernels to normalize array partial_reduce[BLOCKS_PER_GRID, THREADS_PER_BLOCK](dev_a, dev_a_reduce) single_thread_sum[1, 1](dev_a_reduce, dev_a_sum) divide_by[BLOCKS_PER_GRID, THREADS_PER_BLOCK](dev_a, dev_a_sum) # Array copy to host dev_a.copy_to_host(a) cuda.synchronize() print(f\u0026#34;New sum: {a.sum():.3f}\u0026#34;) def main(): argv = sys.argv sys.argv = [argv[0]] parser = argparse.ArgumentParser(description = \u0026#39;Simple Example V1\u0026#39;) parser.add_argument( \u0026#34;-n\u0026#34;, \u0026#34;--array-size\u0026#34;, type = int, default = 100_000_000, metavar = \u0026#34;N\u0026#34;, help = \u0026#34;Array size\u0026#34; ) args = parser.parse_args() run(size = args.array_size) main() 这是一个简单的脚本，可以直接运行：\n--- Old sum: 100000000.000 New sum: 1.000 我们还通过分析器运行了这段代码，这只需要在调用脚本之前调用带有一些选项的 nsys，假如我们在本地将其分别保存为Kernels.py和run_v1.py ，然后执行：\n$ nsys profile \\ --trace cuda,osrt,nvtx \\ --gpu-metrics-device=all \\ --cuda-memory-usage true \\ --force-overwrite true \\ --output profile_run_v1 \\ python run_v1.py GPU 0: General Metrics for NVIDIA TU10x (any frequency) Old sum: 100000000.000 New sum: 1.000 Generating \u0026#39;/tmp/nsys-report-fb78.qdstrm\u0026#39; [1/1] [========================100%] profile_run_v1.nsys-rep Generated: /content/profile_run_v1.nsys-rep ⚠️ 注意：\n保存为文件在 run_v1.py中需要引入 Kernels.py 中的方法和变量:\nfrom kernels import ( BLOCKS_PER_GRID, THREADS_PER_BLOCK, divide_by, partial_reduce, single_thread_sum, ) 你可以查阅 Nsight CLI 文档: https://docs.nvidia.com/nsight-systems/UserGuide/index.html，了解 nsys CLI 的所有可用选项。在本教程中，我们将始终使用上述选项。让我们来分析一下这条命令：\nprofile 将 nsys 置于配置文件模式。还有许多其他模式，如 export 和 launch。 --trace cuda,osrt,nvtx 确保我们 \u0026ldquo;监听 \u0026ldquo;所有CUDA调用（cuda）、操作系统运行库调用（osrt）和 nvtx 注释（本例中没有）。还有更多跟踪选项，如 cublas、cudnn、mpi、dx11 和其他一些选项。请查看文档: https://docs.nvidia.com/nsight-systems/UserGuide/index.html了解所有选项。 --gpu-metrics-device=all 记录所有 GPU 的 GPU 指标，包括 Tensor Core:https://www.nvidia.com/en-us/data-center/tensor-cores/ 的使用情况。 --cuda-memory-usage 跟踪内核的GPU内存使用情况。它可能会大大降低执行速度，需要使用 --trace=cuda。我们使用它是因为我们的脚本速度很快。 导航 Nsight 系统图形用户界面 如果命令成功退出，我们将在当前文件夹中找到 profile_run_v1.nsys-rep。我们将通过启动 Nsight 系统图形用户界面（文件 \u0026gt; 打开）打开该文件。初始视图略显混乱。因此，我们首先要整理一下：将 \u0026ldquo;事件视图 \u0026ldquo;端口调整到底部，并将 \u0026ldquo;时间轴视图 \u0026ldquo;端口下的 CPU、GPU 和进程最小化。现在只展开 Processes \u0026gt; python \u0026gt; CUDA HW。请参见图 1a 和图 1b。\n首先，让我们找到内核。在 CUDA HW 一行，你会发现绿色和红色的圆球，以及极小的浅蓝色片段（见图 1b）。如果将鼠标悬停在它们上面，就会看到工具提示，红色和绿色的提示是 \u0026ldquo;CUDA 内存操作正在进行中\u0026rdquo;，浅蓝色的提示是 \u0026ldquo;CUDA 内核正在运行（89.7%）\u0026quot;。这些将是我们分析的主要内容。通过这一行，我们可以了解内存传输的时间和方式（红色和绿色），以及内核运行的时间和方式（浅蓝色）。\n让我们再深入研究一下我们的内核。你会看到三个非常小的蓝色片段，每个片段代表一个内核调用。点击并拖动鼠标，从第一个内核调用开始前拖动到最后一个内核调用结束后，然后按下 Shift + Z 键，即可放大该区域。\n现在我们已经找到了内核，让我们来看看一些指标。为此，我们打开 GPU \u0026gt; GPU Metrics 选项卡。在这个面板中，可以找到计算内核的 \u0026ldquo;Warp Occupancy（ warp 占用率）\u0026quot;（米色）。优化 CUDA 代码的一种方法是确保 warp 占用率在尽可能长的时间内接近 100%。这意味着我们的 GPU 不会闲置。我们注意到第一个和最后一个内核会出现这种情况，但中间的内核不会。这在意料之中，因为中间内核启动的是单线程。本节最后需要注意的是 \u0026ldquo;GPU \u0026gt; GPU Metrics \u0026gt; SMs Active \u0026gt; Tensor Active / FP16 Active\u0026rdquo; 行。该行将显示是否正在使用张量内核。在这种情况下，你应该确认它们没有被使用。\n现在让我们简要查看一下事件视图。右键单击 Processes \u0026gt; python \u0026gt; CUDA HW，然后单击 \u0026ldquo;Show in Events View （在事件视图中显示）\u0026quot;。然后按持续时间从长到短排序。在图 3 中，我们可以看到最慢的事件是两次可翻页内存传输。我们在《CUDA by Numba 示例第 3 部分：流与事件》中看到，可翻页内存传输可能不是最佳选择，我们应该优先选择页面锁定或 \u0026ldquo;钉住\u0026rdquo; 内存传输。如果由于使用可分页内存导致内存传输速度变慢，那么事件视图就能很好地识别出这些慢速传输的位置。\n专业建议：你可以通过右键单击 \u0026ldquo;Processes \u0026gt; python \u0026gt; CUDA HW \u0026gt; XX% Memory\u0026ldquo;来隔离内存传输。\n在本节中，我们学习了如何分析一个使用 CUDA 的 Python 程序，以及如何在 Nsight Systems GUI 中可视化该程序的基本信息。我们还注意到，在这个简单的程序中，我们使用的是可翻页内存而不是钉式内存，我们的一个内核没有占用所有 warp（即 CUDA 中的线程束） ，GPU 在内核运行之间有相当长的空闲时间，而且我们没有使用张量内核。\n使用 NVTX 进行注释 在本节中，我们将学习如何通过使用 NVTX 对 Nsight 系统中的部分进行注释来改善我们的分析体验。NVTX 允许我们标记代码的不同区域。它可以标记范围和瞬时事件。如需深入了解，请查看文档：https://nvtx.readthedocs.io/en/latest/index.html。我们的Colab 里有一段代码： run_v2， 我们将其在本地保存为 run_v2.py，除了注释 run_v1.py 之外，还更改了这一行：\na = np.ones(size, dtype=np.float32) 到这些：\na = cuda.pinned_array(size, dtype=np.float32) a[...] = 1.0 因此，除了注释外，我们现在还使用了钉状内存。如果你想了解更多有关 CUDA 支持的不同类型内存的信息，请参阅《CUDA C++ 编程指南: https://developer.nvidia.com/blog/how-optimize-data-transfers-cuda-cc/》。值得注意的是，这并不是在 Numba 中钉住数组的唯一方法。之前创建的Numpy数组也可以通过上下文创建，这在Numba文档中有解释。\n#%%writefile run_v2.py import argparse import warnings import numpy as np import nvtx from numba import cuda from numba.core.errors import NumbaPerformanceWarning def run(size): with nvtx.annotate(\u0026#34;Compilation\u0026#34;, color=\u0026#34;red\u0026#34;): dev_a = cuda.device_array((BLOCKS_PER_GRID,), dtype=np.float32) dev_a_reduce = cuda.device_array((BLOCKS_PER_GRID,), dtype=dev_a.dtype) dev_a_sum = cuda.device_array((1,), dtype=dev_a.dtype) partial_reduce[BLOCKS_PER_GRID, THREADS_PER_BLOCK](dev_a, dev_a_reduce) single_thread_sum[1, 1](dev_a_reduce, dev_a_sum) divide_by[BLOCKS_PER_GRID, THREADS_PER_BLOCK](dev_a, dev_a_sum) # Define host array a = cuda.pinned_array(size, dtype=np.float32) a[...] = 1.0 print(f\u0026#34;Old sum: {a.sum():.3f}\u0026#34;) # Array copy to device and array creation on the device. with nvtx.annotate(\u0026#34;H2D Memory\u0026#34;, color=\u0026#34;yellow\u0026#34;): dev_a = cuda.to_device(a) dev_a_reduce = cuda.device_array((BLOCKS_PER_GRID,), dtype=dev_a.dtype) dev_a_sum = cuda.device_array((1,), dtype=dev_a.dtype) # Launching kernels to normalize array with nvtx.annotate(\u0026#34;Kernels\u0026#34;, color=\u0026#34;green\u0026#34;): partial_reduce[BLOCKS_PER_GRID, THREADS_PER_BLOCK](dev_a, dev_a_reduce) single_thread_sum[1, 1](dev_a_reduce, dev_a_sum) divide_by[BLOCKS_PER_GRID, THREADS_PER_BLOCK](dev_a, dev_a_sum) # Array copy to host with nvtx.annotate(\u0026#34;D2H Memory\u0026#34;, color=\u0026#34;orange\u0026#34;): dev_a.copy_to_host(a) cuda.synchronize() print(f\u0026#34;New sum: {a.sum():.3f}\u0026#34;) def main(): argv = sys.argv sys.argv = [argv[0]] parser = argparse.ArgumentParser(description=\u0026#34;Simple Example v2\u0026#34;) parser.add_argument( \u0026#34;-n\u0026#34;, \u0026#34;--array-size\u0026#34;, type=int, default=100_000_000, metavar=\u0026#34;N\u0026#34;, help=\u0026#34;Array size\u0026#34;, ) args = parser.parse_args() run(size=args.array_size) if __name__ == \u0026#34;__main__\u0026#34;: main() 对比这两个文件，你会发现，只需将一些 GPU 内核调用:\nwith nvtx.annotate(\u0026#34;Region Title\u0026#34;, color=\u0026#34;red\u0026#34;): ... 专业提示：你还可以通过在函数定义上方放置 @nvtx.annotate 装饰器来注释函数，通过使用 python -m nvtx run_v2.py 调用脚本来自动注释所有内容，或通过启用或禁用 nvtx.Profile() 在代码中选择性地应用自动注释器。请参阅文档: https://nvtx.readthedocs.io/en/latest/index.html！\n让我们运行这个新脚本并在 Nsight 系统中打开结果。\n$ nsys profile \\ --trace cuda,osrt,nvtx \\ --gpu-metrics-device=all \\ --cuda-memory-usage true \\ --force-overwrite true \\ --output profile_run_v2 \\ python run_v2.py GPU 0: General Metrics for NVIDIA TU10x (any frequency) Old sum: 100000000.000 New sum: 1.000 Generating \u0026#39;/tmp/nsys-report-69ab.qdstrm\u0026#39; [1/1] [========================100%] profile_run_v2.nsys-rep Generated: /content/profile_run_v2.nsys-rep 同样，我们先将所有内容最小化，只打开 \u0026ldquo;Processes \u0026gt; python \u0026gt; CUDA HW\u0026quot;。参见图 4。请注意，我们现在有了一条新线 NVTX。在时间线窗口的这一行中，我们可以看到不同颜色的区块，它们与我们在代码中创建的注释区域相对应。它们分别是 Compilation（编译）、H2D Memory（H2D 内存）、Kernels（内核）和 D2H Memory（D2H 内存）。其中有些区域可能太小，无法阅读，但如果放大该区域，则可以清晰地看到。\n分析器确认了这些内存被钉住，从而确保我们的代码真正使用了钉住的内存。此外，H2D Memory 和 D2H Memory 现在所花费的时间不到之前的一半。一般来说，我们可以期待使用固定内存或预取映射阵列（Numba 不支持）能获得更好的性能。\n流并发 现在，我们将研究是否可以通过引入流来改进这段代码。我们的想法是，在进行内存传输的同时，GPU 可以开始处理数据。这样就可以实现一定程度的并发，从而确保我们尽可能充分地利用经线。\n在下面的代码中，我们将把数组的处理分成大致相同的部分。每个部分将在单独的流中运行，包括传输数据和计算数组的总和。然后，我们同步所有流，并求和它们的部分和。此时，我们就可以为每个流独立启动规范化内核。\n我们想回答几个问题：\n下面的代码真的会产生并发性吗？我们是否会引入错误？ 是否比使用单流的代码更快？ warp 占用率是否更好？ #%%writefile run_v3 import argparse import warnings from math import ceil import numpy as np import nvtx from numba import cuda from numba.core.errors import NumbaPerformanceWarning def run(size, nstreams): with nvtx.annotate(\u0026#34;Compilation\u0026#34;, color=\u0026#34;red\u0026#34;): dev_a = cuda.device_array((BLOCKS_PER_GRID,), dtype=np.float32) dev_a_reduce = cuda.device_array((BLOCKS_PER_GRID,), dtype=dev_a.dtype) dev_a_sum = cuda.device_array((1,), dtype=dev_a.dtype) partial_reduce[BLOCKS_PER_GRID, THREADS_PER_BLOCK](dev_a, dev_a_reduce) single_thread_sum[1, 1](dev_a_reduce, dev_a_sum) divide_by[BLOCKS_PER_GRID, THREADS_PER_BLOCK](dev_a, dev_a_sum) # Define host array a = cuda.pinned_array(size, dtype=np.float32) a[...] = 1.0 # Define regions for streams step = ceil(size / nstreams) starts = [i * step for i in range(nstreams)] ends = [min(s + step, size) for s in starts] print(f\u0026#34;Old sum: {a.sum():.3f}\u0026#34;) # Create streams streams = [cuda.stream()] * nstreams cpu_sums = [cuda.pinned_array(1, dtype=np.float32) for _ in range(nstreams)] devs_a = [] with cuda.defer_cleanup(): for i, (stream, start, end) in enumerate(zip(streams, starts, ends)): cpu_sums[i][...] = np.nan # Array copy to device and array creation on the device. with nvtx.annotate(f\u0026#34;H2D Memory Stream {i}\u0026#34;, color=\u0026#34;yellow\u0026#34;): dev_a = cuda.to_device(a[start:end], stream=stream) dev_a_reduce = cuda.device_array( (BLOCKS_PER_GRID,), dtype=dev_a.dtype, stream=stream ) dev_a_sum = cuda.device_array((1,), dtype=dev_a.dtype, stream=stream) devs_a.append(dev_a) # Launching kernels to sum array with nvtx.annotate(f\u0026#34;Sum Kernels Stream {i}\u0026#34;, color=\u0026#34;green\u0026#34;): for _ in range(50): # Make it spend more time in compute partial_reduce[BLOCKS_PER_GRID, THREADS_PER_BLOCK, stream]( dev_a, dev_a_reduce ) single_thread_sum[1, 1, stream](dev_a_reduce, dev_a_sum) with nvtx.annotate(f\u0026#34;D2H Memory Stream {i}\u0026#34;, color=\u0026#34;orange\u0026#34;): dev_a_sum.copy_to_host(cpu_sums[i], stream=stream) # Ensure all streams are caught up cuda.synchronize() # Aggregate all 1D arrays into a single 1D array a_sum_all = sum(cpu_sums) # Send it to the GPU with cuda.pinned(a_sum_all): with nvtx.annotate(\u0026#34;D2H Memory Default Stream\u0026#34;, color=\u0026#34;orange\u0026#34;): dev_a_sum_all = cuda.to_device(a_sum_all) # Normalize via streams for i, (stream, start, end, dev_a) in enumerate( zip(streams, starts, ends, devs_a) ): with nvtx.annotate(f\u0026#34;Divide Kernel Stream {i}\u0026#34;, color=\u0026#34;green\u0026#34;): divide_by[BLOCKS_PER_GRID, THREADS_PER_BLOCK, stream]( dev_a, dev_a_sum_all ) # Array copy to host with nvtx.annotate(f\u0026#34;D2H Memory Stream {i}\u0026#34;, color=\u0026#34;orange\u0026#34;): dev_a.copy_to_host(a[start:end], stream=stream) cuda.synchronize() print(f\u0026#34;New sum: {a.sum():.3f}\u0026#34;) def main(): argv = sys.argv sys.argv = [argv[0]] parser = argparse.ArgumentParser(description=\u0026#34;Simple Example v3\u0026#34;) parser.add_argument( \u0026#34;-n\u0026#34;, \u0026#34;--array-size\u0026#34;, type=int, default=100_000_000, metavar=\u0026#34;N\u0026#34;, help=\u0026#34;Array size\u0026#34;, ) parser.add_argument( \u0026#34;-s\u0026#34;, \u0026#34;--streams\u0026#34;, type=int, default=4, metavar=\u0026#34;N\u0026#34;, help=\u0026#34;Array size\u0026#34;, ) args = parser.parse_args() run(size=args.array_size, nstreams=args.streams) if __name__ == \u0026#34;__main__\u0026#34;: main() 让我们运行代码并收集结果。\n$ nsys profile \\ --trace cuda,osrt,nvtx \\ --gpu-metrics-device=all \\ --cuda-memory-usage true \\ --force-overwrite true \\ --output profile_run_v3_4streams \\ python run_v3.py -s 4 GPU 0: General Metrics for NVIDIA TU10x (any frequency) Old sum: 100000000.000 New sum: 1.000 Generating \u0026#39;/tmp/nsys-report-a666.qdstrm\u0026#39; [1/1] [========================100%] profile_run_v3_4streams.nsys-rep Generated: /content/profile_run_v3_4streams.nsys-rep 程序运行后得到了正确答案。但当我们打开分析文件时（见图 6），我们会发现有两个数据流，而不是 4 个！其中一个基本上完全处于空闲状态！这到底是怎么回事？\n在创建数据流时存在一个错误。通过\nstreams = [cuda.stream()] * nstreams 我们实际上是在创建一个单一的数据流，并将其重复 n 次。那么，为什么我们看到的是两个数据流而不是一个呢？事实上，其中一个流的计算量并不大，这说明有一个流我们并没有使用。这个流就是默认流，我们在代码中完全没有使用它，因为所有 GPU 交互都有一个流，也就是我们创建的流。\n我们可以通过以下方法修复这个错误:\nstreams = [cuda.stream() for _ in range(nstreams)] # Ensure they are all different assert all(s1.handle != s2.handle for s1, s2 in zip(streams[:-1], streams[1:])) 上述代码还将确保它们确实是不同的数据流，因此如果我们在代码中设置了这一功能，它就能捕捉到错误。它通过检查数据流指针值来做到这一点。\n现在，我们可以使用 1 个数据流和 8 个数据流运行修正后的代码进行比较。分别见图 7 和图 8。\n$ nsys profile \\ --trace cuda,osrt,nvtx \\ --gpu-metrics-device=all \\ --cuda-memory-usage true \\ --force-overwrite true \\ --output profile_run_v3_1stream \\ python run_v3.py -s 1 GPU 0: General Metrics for NVIDIA TU10x (any frequency) Old sum: 100000000.000 New sum: 1.000 Generating \u0026#39;/tmp/nsys-report-de65.qdstrm\u0026#39; [1/1] [========================100%] profile_run_v3_1stream.nsys-rep Generated: /content/profile_run_v3_1stream.nsys-rep $ nsys profile \\ --trace cuda,osrt,nvtx \\ --gpu-metrics-device=all \\ --cuda-memory-usage true \\ --force-overwrite true \\ --output profile_run_v3_8streams \\ python run_v3.py -s 8 GPU 0: General Metrics for NVIDIA TU10x (any frequency) Old sum: 100000000.000 New sum: 1.000 Generating \u0026#39;/tmp/nsys-report-1fb7.qdstrm\u0026#39; [1/1] [========================100%] profile_run_v3_8streams.nsys-rep Generated: /content/profile_run_v3_8streams.nsys-rep 同样，两个结果都是正确的。打开有 8 个数据流的数据流，我们可以看到错误已被修复（图 7）。事实上，我们现在可以看到 9 个数据流（8 个已创建数据流 + 默认数据流）。此外，我们还看到它们同时工作！因此，我们实现了并发！\n遗憾的是，如果我们再深入研究一下，就会发现并发代码的速度并不一定更快。在我的机器上，两个版本的关键部分，从内存传输开始到最后一次 GPU-CPU 复制大约需要 160 毫秒。\n罪魁祸首很可能是 warp 占用率。我们注意到，单流版本的 warp 占用率明显更高。在这个例子中，我们在计算方面获得的收益很可能因为没有有效占用 GPU 而损失掉了。这可能与代码结构有关，因为代码（人为地）调用了太多内核。此外，如果所有线程都被单个流填满，并发性就不会提高，因为其他流必须闲置，直到资源释放。\n这个例子很重要，因为它表明我们对性能的先入为主的概念只是假设。它们需要验证。\n目前，我们已经对 APOD 进行了评估和并行化（通过线程和并发），因此下一步就是部署。我们还注意到并发时性能略有下降，因此在本例中，部署的可能是单流版本。在生产中，下一步将是遵循最适合并行化的下一段代码，并重新启动 APOD。\n结尾 在本文中，我们介绍了如何在NVIDIA Nsight 系统中设置、使用和解释 Python 代码的分析结果。C 和 C++ 代码的分析方法非常相似，事实上，大多数资料都使用了 C 和 C++ 示例。\n我们还展示了分析如何让我们捕捉错误并测试程序性能，确保我们引入的功能确实提高了性能，如果没有，原因何在。\n最后， 我所有代码都是放在了 Colab 的一个文件中，其中：\nargv = sys.argv sys.argv = [argv[0]] 这一段是因为要兼容 Jupyter Notebook 才写的，否则如果只是执行 Python 文件的话可以去掉。\n另外，如果你不太明白怎么讲这个文件拆成独立的Kernels.py, run_v1.py, run_v2.py, run_v3.py， 可以去这里下载源码：\n链接: https://pan.baidu.com/s/1gqN3Cza0h4oZoPvzJqKMaw?pwd=euvg 提取码: euvg \u0026ndash;来自百度网盘超级会员v8的分享\n","permalink":"https://hivan.me/posts/%E4%BD%BF%E7%94%A8-nsight-%E7%B3%BB%E7%BB%9F%E5%88%86%E6%9E%90-cudanumba-%E7%A4%BA%E4%BE%8B/","summary":"\u003cblockquote\u003e\n\u003cp\u003e通过检查 Nsight 系统中的并发和并行 Numba CUDA 代码了解性能分析。\u003c/p\u003e\u003c/blockquote\u003e\n\u003ch2 id=\"介绍\"\u003e介绍\u003c/h2\u003e\n\u003cp\u003e优化是编写高性能代码的关键部分，无论你是在编写 Web 服务器还是计算流体力学模拟软件。分析功能可让你对代码做出明智的决策。从某种意义上说，没有分析功能的优化就像盲目飞行：对于拥有专业知识和敏锐直觉的经验丰富的专业人士来说，这基本上没问题，但对于几乎所有其他人来说，这都是灾难的根源。\n\u003cimg alt=\"image-20240605162936964\" loading=\"lazy\" src=\"https://cdn.jsdelivr.net/gh/hivandu/notes/img/202406051639257.png\"\u003e\u003c/p\u003e\n","title":"使用 Nsight 系统分析 CUDA：Numba 示例"},{"content":"在本节中 在本系列的最后一部分中，我们将介绍原子指令，这些指令允许我们安全地从多个线程对同一内存进行操作。我们还将学习如何利用这些操作来创建互斥锁，这是一种编码模式，允许我们“锁定”某个资源，以便每次只能由一个线程使用。\n单击此处获取 Google colab 中的代码：https://colab.research.google.com/drive/1umKcslGW6gpynEfvk79i-jV08uB8_njc?usp=sharing\n入门 导入并加载库，确保你有 GPU。\nimport warnings from datetime import datetime from time import perf_counter import matplotlib as mpl import matplotlib.pyplot as plt import matplotlib.ticker as ticker import numpy as np import requests import numba from numba import cuda from numba.core.errors import NumbaPerformanceWarning from tqdm.auto import trange print(np.__version__) print(numba.__version__) print(mpl.__version__) # Ignore NumbaPerformanceWarning warnings.simplefilter(\u0026#34;ignore\u0026#34;, category=NumbaPerformanceWarning) --- 1.25.2 0.59.1 3.7.1 cuda.detect() --- Found 1 CUDA devices id 0 b\u0026#39;Tesla T4\u0026#39; [SUPPORTED] Compute Capability: 7.5 PCI Device ID: 4 PCI Bus ID: 0 UUID: GPU-5569b5a1-ca7b-e1b7-79fc-851c80063714 Watchdog: Disabled FP32/FP64 Performance Ratio: 32 Summary: 1/1 devices are supported True 原子 GPU 编程完全基于尽可能并行化相同的指令。对于许多 \u0026ldquo;令人尴尬的并行 \u0026ldquo;任务，线程不需要合作，也不需要使用其他线程使用的资源。其他模式，如还原，则通过算法设计确保同一资源只被一部分线程使用。在这种情况下，我们通过使用同步线程来确保所有其他线程都能及时更新。\n在某些情况下，许多线程必须读取和写入同一个数组。如果试图同时进行读取或写入操作，就会出现问题。假设我们有一个内核，它将单个值递增 1。\n# Example 4.1: A data race condition. @cuda.jit def add_one(x): x[0] = x[0] + 1 当我们使用线程单个块启动该内核时，我们将获得存储在输入数组中的值 1。\ndev_val = cuda.to_device(np.zeros((1,))) add_one[1, 1](dev_val) dev_val.copy_to_host() --- array([1.]) 现在，如果我们启动 10 个区块，每个区块有 16 个线程，会发生什么情况？我们将 10 × 16 × 1 的总和存储到同一个内存元素中，因此我们希望 dev_val 中存储的值是 160。对吗？\ndev_val = cuda.to_device(np.zeros((1,))) add_one[10, 16](dev_val) dev_val.copy_to_host() --- array([1.]) 实际上，我们的 dev_val 值不可能达到 160。为什么？因为线程会同时读写同一个内存变量！\n下面是四个线程试图读写同一个全局存储器时可能发生的情况示意图。线程 1-3 在不同时间（分别为 t=0、2、2）从全局寄存器读取相同的 0 值。它们都递增 1，并在 t=4、7 和 8 时写回全局存储器。线程 4 的启动时间稍晚，为 t=5。此时，线程 1 已经写入全局存储器，因此线程 4 将读取 1 的值，并最终在 t=12 时将全局变量覆盖为 2。\n","permalink":"https://hivan.me/posts/numba-%E7%9A%84-cuda-%E7%A4%BA%E4%BE%8B4-4%E5%8E%9F%E5%AD%90%E5%92%8C%E4%BA%92%E6%96%A5/","summary":"\u003cblockquote\u003e\n\u003cp\u003e本教程为 \u003ca href=\"https://mp.weixin.qq.com/mp/appmsgalbum?__biz=MzA4NzE4MDQzMg==\u0026amp;action=getalbum\u0026amp;album_id=3478311056533995521\u0026amp;scene=21#wechat_redirect\"\u003eNumba CUDA 示例\u003c/a\u003e 第 4 部分。 \u0026gt; \u0026gt; 本系列第 4 部分总结了使用 Python 从头开始学习 CUDA 编程的旅程\u003c/p\u003e\u003c/blockquote\u003e\n\u003ch2 id=\"介绍\"\u003e介绍\u003c/h2\u003e\n\u003cp\u003e在本系列的前三部分（\u003ca href=\"https://mp.weixin.qq.com/s/LdLEhntmiHewB5z2-NjAqg\"\u003e第 1 部分\u003c/a\u003e，\u003ca href=\"https://mp.weixin.qq.com/s/w4qpHO7hVSZhgr0C6ZzDyQ\"\u003e第 2 部分\u003c/a\u003e，\u003ca href=\"https://mp.weixin.qq.com/s/NJngSLe8CZ-IQ9sVOrA3fg\"\u003e第 3 部分\u003c/a\u003e）中，我们介绍了 CUDA 开发的大部分基础知识，例如启动内核来执行高度并行的任务、利用共享内存执行快速缩减、将可重用逻辑封装为设备功能，以及如何使用事件和流来组织和控制内核执行。\n\u003cimg alt=\"图 3.0。使用“达利风格的原子心母亲专辑封面”运行\u003ca href=\\\"https://replicate.com/stability-ai/stable-diffusion\\\"\u003e稳定扩散。\u003c/a\u003e\" loading=\"lazy\" src=\"https://miro.medium.com/v2/resize:fit:700/1*ZKaQL6AiJxOPe9Cgmt6-gg.png\"\u003e\u003c/p\u003e\n","title":"Numba 的 CUDA 示例：原子和互斥"},{"content":"在本教程中 为了提高我们的计时能力，我们将介绍 CUDA 事件及其使用方法。但在深入研究之前，我们将讨论 CUDA 流及其重要性。\nGoogle colab 中的代码：https://colab.research.google.com/drive/1jujdw9f6rf0GoOGRHCvi82mAoXD3ufmt?usp=sharing\n入门 导入并加载库，确保你有 GPU。\nimport warnings from time import perf_counter, sleep import numpy as np import numba from numba import cuda from numba.core.errors import NumbaPerformanceWarning print(np.__version__) print(numba.__version__) # 忽略 NumbaPerformanceWarning warnings.simplefilter(\u0026#34;ignore\u0026#34;, category=NumbaPerformanceWarning) --- 1.25.2 0.59.1 cuda.detect() --- Found 1 CUDA devices id 0 b\u0026#39;Tesla T4\u0026#39; [SUPPORTED] Compute Capability: 7.5 PCI Device ID: 4 PCI Bus ID: 0 UUID: GPU-34d689f4-4c3c-eeb0-ecce-bbaabec33618 Watchdog: Disabled FP32/FP64 Performance Ratio: 32 Summary: 1/1 devices are supported True 流（Streams） 当我们从主机启动内核时，它的执行会在 GPU 中排队，只要 GPU 完成了之前启动的所有任务就会执行。\n用户在设备中启动的许多任务可能依赖于先前的任务，因此“将它们放在同一个队列中”是有意义的。例如，如果你将数据异步复制到 GPU 以使用某个内核进行处理，则该副本必须在内核运行之前完成。\n但是，如果你有两个彼此独立的内核，将它们放在同一个队列中是否有意义？可能没有！对于这些情况，CUDA 有流。你可以将流视为彼此独立运行的单独队列。它们也可以并发运行，即同时运行。这可以在运行许多独立任务时大大加快总运行时间。\n来源：Zhang et al. 2021 (CC BY 4.0).\nNumba CUDA 中的流语义 我们将采取迄今为止学到的两个任务并将它们排队以创建规范化管道。给定一个（主机）数组a，我们将用其规范化版本覆盖它：\na ← a / ∑a[i]\n为此，我们将使用三个内核。第一个内核 partial_reduce 是第二部分中的部分还原。它将返回一个threads_per_block-sized 数组，我们将把它传递给另一个内核 single_thread_sum，后者将进一步将其还原为一个单子数组（大小为 1）。这个内核将在单个区块和单个线程上运行。最后，我们将使用 divide_by 对原始数组和之前计算出的总和进行就地分割。所有这些操作都将在 GPU 中进行，并且应该一个接一个地运行。\nthreads_per_block = 256 blocks_per_grid = 32 * 40 @cuda.jit def partial_reduce(array, partial_reduction): i_start = cuda.grid(1) threads_per_grid = cuda.blockDim.x * cuda.gridDim.x s_thread = 0.0 for i_arr in range(i_start, array.size, threads_per_grid): s_thread += array[i_arr] s_block = cuda.shared.array((threads_per_block,), numba.float32) tid = cuda.threadIdx.x s_block[tid] = s_thread cuda.syncthreads() i = cuda.blockDim.x // 2 while (i \u0026gt; 0): if (tid \u0026lt; i): s_block[tid] += s_block[tid + i] cuda.syncthreads() i //= 2 if tid == 0: partial_reduction[cuda.blockIdx.x] = s_block[0] @cuda.jit def single_thread_sum(partial_reduction, sum): sum[0] = 0.0 for element in partial_reduction: sum[0] += element @cuda.jit def divide_by(array, val_array): i_start = cuda.grid(1) threads_per_grid = cuda.gridsize(1) for i in range(i_start, array.size, threads_per_grid): array[i] /= val_array[0] 当内核调用和其他操作没有指定流时，它们将在默认流中运行。默认流是一种特殊流，其行为取决于运行的是旧式流还是每个线程的流。对我们来说，只要说如果你想实现并发，就应该在非默认流中运行任务就足够了。让我们看看如何对某些操作（例如内核启动、数组复制和数组创建复制）做到这一点。\n# Define host array a = np.ones(10_000_000, dtype=np.float32) print(f\u0026#34;Old sum: {a.sum():.2f}\u0026#34;) # Old sum: 10000000.00 # Example 3.1: Numba CUDA Stream Semantics # Pin memory with cuda.pinned(a): # Create a CUDA stream stream = cuda.stream() # 将数组复制到设备并在设备中创建。使用 Numba 时，可将数据流作为附加信息传递给 API 函数。 dev_a = cuda.to_device(a, stream=stream) dev_a_reduce = cuda.device_array((blocks_per_grid,), dtype=dev_a.dtype, stream=stream) dev_a_sum = cuda.device_array((1,), dtype=dev_a.dtype, stream=stream) # 在启动内核时，流会被传给内核启动器（\u0026#34;dispatcher\u0026#34;）配置，它位于块维度（`threads_per_block`）之后。 partial_reduce[blocks_per_grid, threads_per_block, stream](dev_a, dev_a_reduce) single_thread_sum[1, 1, stream](dev_a_reduce, dev_a_sum) divide_by[blocks_per_grid, threads_per_block, stream](dev_a, dev_a_sum) # 数组复制到主机：与复制到设备一样，当传递数据流时，复制是异步的。注意：由于写入尚未同步，打印输出很可能是无意义的。 dev_a.copy_to_host(a, stream=stream) # 无论何时，只要我们想确保从主机的角度来看，流中的所有操作都已完成，我们就会调用： stream.synchronize() # 调用后，我们可以确定 `a` 已被其规范化版本覆盖 print(f\u0026#34;New sum: {a.sum():.2f}\u0026#34;) --- New sum: 1.00 在我们真正谈论流之前，我们需要谈论房间里的大象：cuda.pinned。此上下文管理器创建一种称为页面锁定或固定内存的特殊类型的内存，CUDA 在将内存从主机传输到设备时将受益于这种内存。\n主机 RAM 中的内存可随时分页，也就是说，操作系统可以秘密地将对象从 RAM 移动到硬盘。这样做的目的是将不经常使用的对象移动到较慢的内存位置，让较快的 RAM 内存可用于更急需的对象。对我们来说重要的是，CUDA 不允许从可分页对象到 GPU 的异步传输。这样做是为了防止出现持续的非常慢的传输流：磁盘（分页）→ RAM → GPU。\n要异步传输数据，我们必须确保数据始终位于 RAM 中，方法是以某种方式防止操作系统偷偷将数据隐藏在磁盘的某个地方。这就是内存固定发挥作用的地方，它创建了一个上下文，在该上下文中，参数将被“页面锁定”，即强制位于 RAM 中。参见图 3.2。\n来源：Rizvi et al. 2017 (CC BY 4.0).\n从此以后，代码就变得非常简单了。创建一个流，然后将其传递给我们想要在该流上操作的每个 CUDA 函数。重要的是，Numba CUDA 内核配置（方括号）要求流位于块维度大小之后的第三个参数中。\n⚠️ 注意：\n通常，将流传递给 Numba CUDA API 函数不会改变其行为，只会改变其运行的流。从设备到主机的复制是一个例外。调用 device_array.copy_to_host()（不带参数）时，复制会同步进行。调用 device_array.copy_to_host(stream=stream)（带流）时，如果device_array未固定，则复制将同步进行。只有在device_array固定并传递流时，复制才会异步进行。\n信息：\nNumba 提供了一个有用的上下文管理器，用于在其上下文中排队所有操作；退出上下文时，操作将同步，包括内存传输。示例 3.1 也可以写成：\nwith cuda.pinned(a): stream = cuda.stream() with stream.auto_synchronize(): dev_a = cuda.to_device(a, stream=stream) dev_a_reduce = cuda.device_array((blocks_per_grid,), dtype=dev_a.dtype, stream=stream) dev_a_sum = cuda.device_array((1,), dtype=dev_a.dtype, stream=stream) partial_reduce[blocks_per_grid, threads_per_block, stream](dev_a, dev_a_reduce) single_thread_sum[1, 1, stream](dev_a_reduce, dev_a_sum) divide_by[blocks_per_grid, threads_per_block, stream](dev_a, dev_a_sum) dev_a.copy_to_host(a, stream=stream) 将独立内核与流分离 假设我们要标准化的不是一个数组，而是多个数组。各个数组的标准化操作完全相互独立。因此，GPU 没有必要等到一个标准化结束之后再开始下一个标准化。因此，我们应该将这些任务分成单独的流。\n让我们看一个规范化 10 个数组的示例 —— 每个数组都使用自己的流。\n# Example 3.2: Multiple streams N_streams = 10 # 不要在此上下文中进行内存收集（去分配数组） with cuda.defer_cleanup(): # Create 10 streams streams = [cuda.stream() for _ in range(1, N_streams + 1)] # Create base arrays arrays = [ i * np.ones(10_000_000, dtype=np.float32) for i in range(1, N_streams + 1) ] for i, arr in enumerate(arrays): print(f\u0026#34;Old sum (array {i}): {arr.sum():12.2f}\u0026#34;) tics = [] # Launch start times for i, (stream, arr) in enumerate(zip(streams, arrays)): tic = perf_counter() with cuda.pinned(arr): dev_a = cuda.to_device(arr, stream=stream) dev_a_reduce = cuda.device_array( (blocks_per_grid,), dtype=dev_a.dtype, stream=stream ) dev_a_sum = cuda.device_array((1,), dtype=dev_a.dtype, stream=stream) partial_reduce[blocks_per_grid, threads_per_block, stream](dev_a, dev_a_reduce) single_thread_sum[1, 1, stream](dev_a_reduce, dev_a_sum) divide_by[blocks_per_grid, threads_per_block, stream](dev_a, dev_a_sum) dev_a.copy_to_host(arr, stream=stream) toc = perf_counter() # Stop time of launches print(f\u0026#34;Launched processing {i} in {1e3 * (toc - tic):.2f} ms\u0026#34;) # 确保删除 GPU 数组的引用，这将确保在退出上下文时进行垃圾回收。 del dev_a, dev_a_reduce, dev_a_sum tics.append(tic) tocs = [] for i, (stream, arr) in enumerate(zip(streams, arrays)): stream.synchronize() toc = perf_counter() # Stop time of sync tocs.append(toc) print(f\u0026#34;New sum (array {i}): {arr.sum():12.2f}\u0026#34;) for i in range(4): print(f\u0026#34;Performed processing {i} in {1e3 * (tocs[i] - tics[i]):.2f} ms\u0026#34;) print(f\u0026#34;Total time {1e3 * (tocs[-1] - tics[0]):.2f} ms\u0026#34;) --- Old sum (array 0): 10000000.00 Old sum (array 1): 20000000.00 Old sum (array 2): 30000000.00 Old sum (array 3): 40000000.00 Old sum (array 4): 50000000.00 Old sum (array 5): 60000000.00 Old sum (array 6): 70000000.00 Old sum (array 7): 80000000.00 Old sum (array 8): 90000000.00 Old sum (array 9): 100000000.00 Launched processing 0 in 14.40 ms Launched processing 1 in 13.89 ms Launched processing 2 in 13.79 ms Launched processing 3 in 13.75 ms Launched processing 4 in 13.62 ms Launched processing 5 in 13.95 ms Launched processing 6 in 13.99 ms Launched processing 7 in 14.32 ms Launched processing 8 in 13.14 ms Launched processing 9 in 13.47 ms New sum (array 0): 1.00 New sum (array 1): 1.00 New sum (array 2): 1.00 New sum (array 3): 1.00 New sum (array 4): 1.00 New sum (array 5): 1.00 New sum (array 6): 1.00 New sum (array 7): 1.00 New sum (array 8): 1.00 New sum (array 9): 1.00 Performed processing 0 in 145.23 ms Performed processing 1 in 137.10 ms Performed processing 2 in 129.31 ms Performed processing 3 in 121.48 ms Total time 207.54 ms 现在让我们与单个流进行比较。\n# Example 3.3: Single stream # 不要在此上下文中进行内存收集（去分配数组） with cuda.defer_cleanup(): # Create 1 streams streams = [cuda.stream()] * N_streams # Create base arrays arrays = [ i * np.ones(10_000_000, dtype=np.float32) for i in range(1, N_streams + 1) ] for i, arr in enumerate(arrays): print(f\u0026#34;Old sum (array {i}): {arr.sum():12.2f}\u0026#34;) tics = [] # Launch start times for i, (stream, arr) in enumerate(zip(streams, arrays)): tic = perf_counter() with cuda.pinned(arr): dev_a = cuda.to_device(arr, stream=stream) dev_a_reduce = cuda.device_array( (blocks_per_grid,), dtype=dev_a.dtype, stream=stream ) dev_a_sum = cuda.device_array((1,), dtype=dev_a.dtype, stream=stream) partial_reduce[blocks_per_grid, threads_per_block, stream](dev_a, dev_a_reduce) single_thread_sum[1, 1, stream](dev_a_reduce, dev_a_sum) divide_by[blocks_per_grid, threads_per_block, stream](dev_a, dev_a_sum) dev_a.copy_to_host(arr, stream=stream) toc = perf_counter() # Stop time of launches print(f\u0026#34;Launched processing {i} in {1e3 * (toc - tic):.2f} ms\u0026#34;) # 确保删除 GPU 数组的引用，这将确保在退出上下文时进行垃圾回收。 del dev_a, dev_a_reduce, dev_a_sum tics.append(tic) tocs = [] for i, (stream, arr) in enumerate(zip(streams, arrays)): stream.synchronize() toc = perf_counter() # Stop time of sync tocs.append(toc) print(f\u0026#34;New sum (array {i}): {arr.sum():12.2f}\u0026#34;) for i in range(4): print(f\u0026#34;Performed processing {i} in {1e3 * (tocs[i] - tics[i]):.2f} ms\u0026#34;) print(f\u0026#34;Total time {1e3 * (tocs[-1] - tics[0]):.2f} ms\u0026#34;) --- Old sum (array 0): 10000000.00 Old sum (array 1): 20000000.00 Old sum (array 2): 30000000.00 Old sum (array 3): 40000000.00 Old sum (array 4): 50000000.00 Old sum (array 5): 60000000.00 Old sum (array 6): 70000000.00 Old sum (array 7): 80000000.00 Old sum (array 8): 90000000.00 Old sum (array 9): 100000000.00 Launched processing 0 in 13.26 ms Launched processing 1 in 11.84 ms Launched processing 2 in 11.83 ms Launched processing 3 in 12.08 ms Launched processing 4 in 14.21 ms Launched processing 5 in 11.98 ms Launched processing 6 in 11.91 ms Launched processing 7 in 12.08 ms Launched processing 8 in 12.13 ms Launched processing 9 in 11.80 ms New sum (array 0): 1.00 New sum (array 1): 1.00 New sum (array 2): 1.00 New sum (array 3): 1.00 New sum (array 4): 1.00 New sum (array 5): 1.00 New sum (array 6): 1.00 New sum (array 7): 1.00 New sum (array 8): 1.00 New sum (array 9): 1.00 Performed processing 0 in 124.64 ms Performed processing 1 in 115.35 ms Performed processing 2 in 107.26 ms Performed processing 3 in 99.11 ms Total time 159.02 ms 但是哪一个更快呢？运行这些示例时，使用多个流时，总时间并没有得到一致的改善。造成这种情况的原因有很多。例如，要使流并发运行，本地内存中必须有足够的空间。此外，我们从 CPU 进行计时。虽然很难知道本地内存中是否有足够的空间，但从 GPU 进行计时相对容易。让我们学习如何操作！\n信息：\nNvidia 提供了多种用于调试 CUDA 的工具，包括用于调试 CUDA 流的工具。查看*Nsight Systems*了解更多信息。\n事件 CPU 计时代码的一个问题是，它将包含除 GPU 之外的更多操作。\n值得庆幸的是，通过 CUDA 事件可以直接从 GPU 获取时间。事件只是一个时间寄存器，它记录了 GPU 中发生的事情。在某种程度上，它类似于 time.time 和 time.perf_counter，但与之不同的是，我们需要处理这样一个事实：当我们在 CPU 上编程时，我们希望对来自 GPU 的事件进行计时。\n因此，除了创建时间戳（“记录”事件）之外，我们还需要确保事件与 CPU 同步，然后才能访问其值。让我们看一个简单的例子。\n内核执行计时事件 # Example 3.4: Simple events # 事件需要初始化，但这并不影响计时。 # 我们创建两个事件，一个在计算开始时，另一个在计算结束时。 event_beg = cuda.event() event_end = cuda.event() # Create CUDA stream stream = cuda.stream() with cuda.pinned(arr): # 在`stream`中复制/创建队列数组 dev_a = cuda.to_device(arr, stream=stream) dev_a_reduce = cuda.device_array((blocks_per_grid,), dtype=dev_a.dtype, stream=stream) # 从这一行开始，`event_beg` 将包含 GPU 中这一时刻的时间。 event_beg.record(stream=stream) # 异步启动内核 partial_reduce[blocks_per_grid, threads_per_block, stream](dev_a, dev_a_reduce) # 启动 \u0026#34;记录\u0026#34;，内核运行结束时触发该记录 event_end.record(stream=stream) # 未来提交到流的任务将等待 `event_end` 完成。 event_end.wait(stream=stream) # 将此事件与 CPU 同步，以便我们可以使用其值。 event_end.synchronize() # 现在我们来计算执行内核所需的时间。请注意，我们不需要等待/同步`event_beg`，因为它的执行取决于 event_end 是否等待/同步了`event_beg`。 timing_ms = event_beg.elapsed_time(event_end) # in miliseconds print(f\u0026#34;Elapsed time {timing_ms:.2f} ms\u0026#34;) --- Elapsed time 0.79 ms 对 GPU 操作进行计时的一个有用方法是使用上下文管理器：\n# Example 3.5: Context Manager for CUDA Timer using Events class CUDATimer: def __init__(self, stream): self.stream = stream self.event = None # in ms def __enter__(self): self.event_beg = cuda.event() self.event_end = cuda.event() self.event_beg.record(stream=self.stream) return self def __exit__(self, type, value, traceback): self.event_end.record(stream=self.stream) self.event_end.wait(stream=self.stream) self.event_end.synchronize() self.elapsed = self.event_beg.elapsed_time(self.event_end) stream = cuda.stream() dev_a = cuda.to_device(arrays[0], stream=stream) dev_a_reduce = cuda.device_array((blocks_per_grid,), dtype=dev_a.dtype, stream=stream) with CUDATimer(stream) as cudatimer: partial_reduce[blocks_per_grid, threads_per_block, stream](dev_a, dev_a_reduce) print(f\u0026#34;Elapsed time {cudatimer.elapsed:.2f} ms\u0026#34;) --- Elapsed time 0.65 ms 时间流事件 为了结束本系列的这一部分，我们将使用流来更好、更准确地了解我们的示例是否受益于流。\n# Example 3.6: Timing a single streams with events N_streams = 10 # 不要在此上下文中进行内存收集（去分配数组） with cuda.defer_cleanup(): # Create 1 stream streams = [cuda.stream()] * N_streams # Create base arrays arrays = [ i * np.ones(10_000_000, dtype=np.float32) for i in range(1, N_streams + 1) ] events_beg = [] # Launch start times events_end = [] # End start times for i, (stream, arr) in enumerate(zip(streams, arrays)): with cuda.pinned(arr): # 宣布事件并记录开始 event_beg = cuda.event() event_end = cuda.event() event_beg.record(stream=stream) # 执行所有 CUDA 操作 dev_a = cuda.to_device(arr, stream=stream) dev_a_reduce = cuda.device_array( (blocks_per_grid,), dtype=dev_a.dtype, stream=stream ) dev_a_sum = cuda.device_array((1,), dtype=dev_a.dtype, stream=stream) partial_reduce[blocks_per_grid, threads_per_block, stream](dev_a, dev_a_reduce) single_thread_sum[1, 1, stream](dev_a_reduce, dev_a_sum) divide_by[blocks_per_grid, threads_per_block, stream](dev_a, dev_a_sum) dev_a.copy_to_host(arr, stream=stream) # Record end event_end.record(stream=stream) events_beg.append(event_beg) events_end.append(event_end) del dev_a, dev_a_reduce, dev_a_sum sleep(5) # 等待所有事件结束，不影响 GPU 计时 for event_end in events_end: event_end.synchronize() # 启动的第一个 `event_beg` 是最早的事件。但最后一个 `event_end` 事件是事先不知道的。我们要找出是哪个事件： elapsed_times = [events_beg[0].elapsed_time(event_end) for event_end in events_end] i_stream_last = np.argmax(elapsed_times) print(f\u0026#34;Last stream: {i_stream_last}\u0026#34;) print(f\u0026#34;Total time {elapsed_times[i_stream_last]:.2f} ms\u0026#34;) --- Last stream: 9 Total time 117.90 ms # Example 3.7: Timing multiple streams with events # 不要在此上下文中进行内存收集（去分配数组） with cuda.defer_cleanup(): # Create 10 streams streams = [cuda.stream() for _ in range(1, N_streams + 1)] # Create base arrays arrays = [ i * np.ones(10_000_000, dtype=np.float32) for i in range(1, N_streams + 1) ] events_beg = [] # Launch start times events_end = [] # End start times for i, (stream, arr) in enumerate(zip(streams, arrays)): with cuda.pinned(arr): # 宣布事件并记录开始 event_beg = cuda.event() event_end = cuda.event() event_beg.record(stream=stream) # 执行所有 CUDA 操作 dev_a = cuda.to_device(arr, stream=stream) dev_a_reduce = cuda.device_array( (blocks_per_grid,), dtype=dev_a.dtype, stream=stream ) dev_a_sum = cuda.device_array((1,), dtype=dev_a.dtype, stream=stream) partial_reduce[blocks_per_grid, threads_per_block, stream](dev_a, dev_a_reduce) single_thread_sum[1, 1, stream](dev_a_reduce, dev_a_sum) divide_by[blocks_per_grid, threads_per_block, stream](dev_a, dev_a_sum) dev_a.copy_to_host(arr, stream=stream) # Record end event_end.record(stream=stream) events_beg.append(event_beg) events_end.append(event_end) del dev_a, dev_a_reduce, dev_a_sum sleep(5) # 等待所有事件完成，不影响 GPU 时序 for event_end in events_end: event_end.synchronize() # 启动的第一个 `event_beg` 是最早的事件。但最后一个 `event_end` 事件是事先不知道的。我们要找出是哪个事件： elapsed_times = [events_beg[0].elapsed_time(event_end) for event_end in events_end] i_stream_last = np.argmax(elapsed_times) print(f\u0026#34;Last stream: {i_stream_last}\u0026#34;) print(f\u0026#34;Total time {elapsed_times[i_stream_last]:.2f} ms\u0026#34;) --- Last stream: 9 Total time 130.66 ms 结尾 CUDA 的核心在于性能。在本教程中，你学习了如何使用Events（事件）准确测量内核的执行时间，以便对代码进行分析。你还了解了Streams（流）以及如何使用它们来始终保持 GPU 忙碌，以及pinned（固定）或mapped arrays（映射数组），以及如何改善内存访问。\n","permalink":"https://hivan.me/posts/numba-%E7%9A%84-cuda-%E7%A4%BA%E4%BE%8B3-4%E6%B5%81%E5%92%8C%E4%BA%8B%E4%BB%B6/","summary":"\u003cblockquote\u003e\n\u003cp\u003e本教程为 \u003ca href=\"https://mp.weixin.qq.com/mp/appmsgalbum?__biz=MzA4NzE4MDQzMg==\u0026amp;action=getalbum\u0026amp;album_id=3478311056533995521\u0026amp;scene=21#wechat_redirect\"\u003eNumba CUDA 示例\u003c/a\u003e 第 3 部分。 \u0026gt; \u0026gt; 按照本系列的第 3 部分，了解 Python CUDA 编程中的流和事件\u003c/p\u003e\u003c/blockquote\u003e\n\u003ch2 id=\"介绍\"\u003e介绍\u003c/h2\u003e\n\u003cp\u003e在本系列的前两部分（\u003ca href=\"https://mp.weixin.qq.com/s/LdLEhntmiHewB5z2-NjAqg\"\u003e第 1 部分\u003c/a\u003e，\u003ca href=\"https://mp.weixin.qq.com/s/w4qpHO7hVSZhgr0C6ZzDyQ\"\u003e第 2 部分\u003c/a\u003e）中，我们学习了如何使用 GPU 编程执行简单的任务，例如高度并行的任务、使用共享内存的缩减以及设备功能。我们还学习了如何从主机对函数进行计时 — 以及为什么这可能不是对代码进行计时的最佳方式。\n\u003cimg alt=\"使用“墨西哥湾流多彩空间平静”运行\" loading=\"lazy\" src=\"https://miro.medium.com/v2/resize:fit:700/1*bC0prdsQmLoLoSSY2sWgmQ.png\"\u003e\u003c/p\u003e\n","title":"Numba 的 CUDA 示例(3/4)：流和事件"},{"content":"在本教程中 许多任务虽然不是高度并行的，但仍可从并行化中获益。在本期的CUDA by Numba Examples中，我们将介绍一些允许线程协作进行计算的常用技术。本部分的 Google colab 代码：https://colab.research.google.com/drive/1hproEOKvQyBNNxvjr0qM2LPjJWNDfyp9?usp=sharing\n入门 导入并加载库，确保您有 GPU。\nfrom time import perf_counter import numpy as np import numba from numba import cuda print(np.__version__) print(numba.__version__) --- 1.25.2 0.59.1 cuda.detect() --- Found 1 CUDA devices id 0 b\u0026#39;Tesla T4\u0026#39; [SUPPORTED] Compute Capability: 7.5 PCI Device ID: 4 PCI Bus ID: 0 UUID: GPU-0f022a60-18f8-5de0-1f24-ad861dcd84ae Watchdog: Disabled FP32/FP64 Performance Ratio: 32 Summary: 1/1 devices are supported True 线程合作 简单并行缩减算法 我们将从一个非常简单的问题开始本节：对数组的所有元素求和。从本质上讲，这个算法非常简单。如果不借助 NumPy，我们可以将其实现为：\ndef sum_cpu(array): s = 0.0 for i in range(array.size): s += array[i] return s 我知道，这看起来不太符合 Python 风格。但它确实强调了s跟踪数组中的所有元素。如果依赖于数组的每个元素，我们如何并行化该算法s？首先，我们需要重写算法以允许某种并行化。如果有些部分我们无法并行化，我们应该允许线程相互通信。\n然而，到目前为止，我们还没有学会如何让线程相互通信……事实上，我们之前说过，不同块中的线程不会通信。我们可以考虑只启动一个块，但请记住，大多数 GPU 中的块只能有 1024 个线程！\n我们如何克服这个问题？好吧，如果我们将数组拆分成 1024 个块（或适当数量的threads_per_block），然后分别对每个块求和，结果会怎样？最后，我们可以将每个块的总和结果相加。图 2.1 显示了 2 个块拆分的一个非常简单的示例。\n我们如何在 GPU 上做到这一点？首先，我们需要将数组拆分成块。每个块只对应一个块，具有固定数量的线程。在每个块中，每个线程可以对多个数组元素求和（网格步长循环）。然后，我们必须在整个块上计算这些每个线程的值。这部分需要线程进行通信。我们将在下一个示例中介绍如何做到这一点。\n由于我们是在块上并行化，因此内核的输出应为块大小。为了完成缩减，我们将其复制到 CPU 并在那里完成作业。\nthreads_per_block = 1024 # Why not! blocks_per_grid = 32 * 80 # Use 32 * multiple of streaming multiprocessors # Example 2.1: Naive reduction @cuda.jit def reduce_naive(array, partial_reduction): i_start = cuda.grid(1) threads_per_grid = cuda.blockDim.x * cuda.gridDim.x s_thread = 0.0 for i_arr in range(i_start, array.size, threads_per_grid): s_thread += array[i_arr] # We need to create a special *shared* array which will be able to be read # from and written to by every thread in the block. Each block will have its # own shared array. See the warning below! s_block = cuda.shared.array((threads_per_block,), numba.float32) # We now store the local temporary sum of a single the thread into the # shared array. Since the shared array is sized # threads_per_block == blockDim.x # (1024 in this example), we should index it with `threadIdx.x`. tid = cuda.threadIdx.x s_block[tid] = s_thread # The next line synchronizes the threads in a block. It ensures that after # that line, all values have been written to `s_block`. cuda.syncthreads() # Finally, we need to sum the values from all threads to yield a single # value per block. We only need one thread for this. if tid == 0: # We store the sum of the elements of the shared array in its first # coordinate for i in range(1, threads_per_block): s_block[0] += s_block[i] # Move this partial sum to the output. Only one thread is writing here. partial_reduction[cuda.blockIdx.x] = s_block[0] ⚠️ 注意 ：共享数组必须\n尽量“小”。具体大小取决于 GPU 的计算能力，通常在 48 KB 到 163 KB 之间。请参阅本表：https://docs.nvidia.com/cuda/cuda-c-programming-guide/index.html#features-and-technical-specifications__technical-specifications-per-compute-capability 中的“Maximum amount of shared memory per thread block”项。 在编译时有一个已知的大小（这就是为什么我们要设置共享数组 threads_per_block 的大小，而不是 blockDim.x）。的确，我们可以为任意大小的共享数组定义一个factory function\u0026hellip;\u0026hellip;但要注意这些内核的编译时间 用 Numba 类型指定 dtype，而不是 Numpy 类型（别问我为什么！）。 N = 1_000_000_000 a = np.arange(N, dtype=np.float32) a /= a.sum() # a will have sum = 1 (to float32 precision) s_cpu = a.sum() # Highly-optimized NumPy CPU code timing_cpu = np.empty(21) for i in range(timing_cpu.size): tic = perf_counter() a.sum() toc = perf_counter() timing_cpu[i] = toc - tic timing_cpu *= 1e3 # convert to ms print(f\u0026#34;Elapsed time CPU: {timing_cpu.mean():.0f} ± {timing_cpu.std():.0f} ms\u0026#34;) --- Elapsed time CPU: 557 ± 307 ms dev_a = cuda.to_device(a) dev_partial_reduction = cuda.device_array((blocks_per_grid,), dtype=a.dtype) reduce_naive[blocks_per_grid, threads_per_block](dev_a, dev_partial_reduction) s = dev_partial_reduction.copy_to_host().sum() # Final reduction in CPU np.isclose(s, s_cpu) # Ensure we have the right number --- True timing_naive = np.empty(21) for i in range(timing_naive.size): tic = perf_counter() reduce_naive[blocks_per_grid, threads_per_block](dev_a, dev_partial_reduction) s = dev_partial_reduction.copy_to_host().sum() cuda.synchronize() toc = perf_counter() assert np.isclose(s, s_cpu) timing_naive[i] = toc - tic timing_naive *= 1e3 # convert to ms print(f\u0026#34;Elapsed time naive: {timing_naive.mean():.0f} ± {timing_naive.std():.0f} ms\u0026#34;) --- Elapsed time naive: 30 ± 11 ms 我在 Google Colab 上运行了这个程序，速度提高了将近 20 倍。非常棒！\n一种更好的并行缩减算法 您可能想知道为什么我们将所有内容都命名为“简单”。这意味着有一些非简单的方式来执行相同的功能。事实上，有很多技巧可以加速这种代码（请参阅 Optimizing Parallel Reduction in CUDA 演示以获取基准）。\n在我们展示更好的方法之前，让我们回顾一下内核的最后一部分：\nif tid == 0: # Single thread taking care of business for i in range(1, threads_per_block): s_block[0] += s_block[i] partial_reduction[cuda.blockIdx.x] = s_block[0] 我们几乎把所有事情都并行化了，但在内核末尾，我们让一个线程负责对共享数组 s_block 的所有 threads_per_block 元素求和。我们为什么不把这个总和也并行化呢？\n听起来不错，怎么做呢？图 2.2 显示了如何实现 threads_per_block 大小为 16 的函数。我们首先运行 8 个线程，第一个线程将对 s_block[0] 和 s_block[8] 中的值求和。第二个线程对 s_block[1] 和 s_block[9] 中的值求和，直到最后一个线程将对s_block[7] 和 s_block[15] 中的值求和。\n下一步，只需要前 4 个线程工作。第一个线程将计算 s_block[0] 和 s_block[4] 的总和；第二个线程将计算 s_block[1] 和 s_block[5] 的总和；第三个线程将计算 s_block[2] 和 s_block[6] 的总和；第四个线程和最后一个线程将计算 s_block[3] 和 s_block[7] 的总和。\n在第三步中，我们现在只需要 2 个线程来处理 s_block的前 4 个元素。第四步也是最后一步将使用一个线程来对 2 个元素求和。\n由于工作已在线程之间分配，因此它是并行的。当然，它不是由每个线程均等分配的，但这是一种改进。从计算上讲，此算法是 O(log2( threads_per_block))，而第一个算法是 O( threads_per_block)。在我们的示例中，原始算法需要 1024 次操作，而改进算法只需要 10 次！\n最后还有一个细节。在每一步中，我们都需要确保所有线程都已写入共享数组。所以我们必须调用cuda.syncthreads()。\n来源：Mark Harris，Optimizing Parallel Reduction in CUDA.\n# Example 2.2: Better reduction @cuda.jit def reduce_better(array, partial_reduction): i_start = cuda.grid(1) threads_per_grid = cuda.blockDim.x * cuda.gridDim.x s_thread = 0.0 for i_arr in range(i_start, array.size, threads_per_grid): s_thread += array[i_arr] # We need to create a special *shared* array which will be able to be read # from and written to by every thread in the block. Each block will have its # own shared array. See the warning below! s_block = cuda.shared.array((threads_per_block,), numba.float32) # We now store the local temporary sum of the thread into the shared array. # Since the shared array is sized threads_per_block == blockDim.x, # we should index it with `threadIdx.x`. tid = cuda.threadIdx.x s_block[tid] = s_thread # The next line synchronizes the threads in a block. It ensures that after # that line, all values have been written to `s_block`. cuda.syncthreads() i = cuda.blockDim.x // 2 while (i \u0026gt; 0): if (tid \u0026lt; i): s_block[tid] += s_block[tid + i] cuda.syncthreads() i //= 2 if tid == 0: partial_reduction[cuda.blockIdx.x] = s_block[0] reduce_better[blocks_per_grid, threads_per_block](dev_a, dev_partial_reduction) s = dev_partial_reduction.copy_to_host().sum() # Final reduction in CPU np.isclose(s, s_cpu) --- True timing_naive = np.empty(21) for i in range(timing_naive.size): tic = perf_counter() reduce_better[blocks_per_grid, threads_per_block](dev_a, dev_partial_reduction) s = dev_partial_reduction.copy_to_host().sum() cuda.synchronize() toc = perf_counter() assert np.isclose(s, s_cpu) timing_naive[i] = toc - tic timing_naive *= 1e3 # convert to ms print(f\u0026#34;Elapsed time better: {timing_naive.mean():.0f} ± {timing_naive.std():.0f} ms\u0026#34;) --- Elapsed time better: 23 ± 1 ms 在 Google Colab 上，这比简单方法快约 30%。\n⚠️ 注意：你可能会想把 syncthreads 移到 if 块内部，因为每一步之后，超过当前线程数一半的内核将不会被使用。但是，这样做会让调用 syncthreads 的 CUDA 线程停止并等待其他线程，而其他线程则会继续运行。因此，停止的线程将永远等待永远不会停止同步的线程。这给我们的启示是：如果要同步线程，请确保所有线程都调用了 cuda.syncthreads()。\ni = cuda.blockDim.x // 2 while (i \u0026gt; 0): if (tid \u0026lt; i): s_block[tid] += s_block[tid + i] cuda.syncthreads() # 不要放在这里 cuda.syncthreads() # 而不是这里 i //= 2 减少 Numba 由于上述缩减算法并不简单，Numba 提供了一个便捷cuda.reduce装饰器，可将二元函数转换为缩减算法。上面的长而复杂的算法可以用以下方法替代：\n# Example 2.3: Numba reduction @cuda.reduce def reduce_numba(a, b): return a + b # Compile and check s = reduce_numba(dev_a) np.isclose(s, s_cpu) --- True # Time timing_numba = np.empty(21) for i in range(timing_numba.size): tic = perf_counter() s = reduce_numba(dev_a) toc = perf_counter() assert np.isclose(s, s_cpu) timing_numba[i] = toc - tic timing_numba *= 1e3 # convert to ms print(f\u0026#34;Elapsed time better: {timing_numba.mean():.0f} ± {timing_numba.std():.0f} ms\u0026#34;) --- Elapsed time better: 20 ± 0 ms 就我个人而言，我发现手写缩减通常要快得多（至少快 2 倍），但 Numba 递归非常容易使用。话虽如此，我还是鼓励大家阅读 reduction code in the Numba source code.\n还需要注意的是，默认情况下，reduction 会复制到主机，这会强制同步。为了避免这种情况，您可以使用设备数组作为输出来调用 Reduce：\ndev_s = cuda.device_array((1,), dtype=s) reduce_numba(dev_a, res=dev_s) s = dev_s.copy_to_host()[0] np.isclose(s, s_cpu) --- True 2D 缩减示例 并行缩减技术很棒，但如何将其扩展到更高维度并不明显。虽然我们总是可以使用解开的数组 ( array2d.ravel()) 来调用 Numba 缩减，但了解如何手动缩减多维数组非常重要。\n在这个例子中，我们将结合所学的关于 2D 内核的知识和所学的关于 1D 缩减的知识来计算 2D 缩减。\nthreads_per_block_2d = (16, 16) # 256 threads total blocks_per_grid_2d = (64, 64) # Total number of threads in a 2D block (has to be an int) shared_array_len = int(np.prod(threads_per_block_2d)) # Example 2.4: 2D reduction with 1D shared array @cuda.jit def reduce2d(array2d, partial_reduction2d): ix, iy = cuda.grid(2) threads_per_grid_x, threads_per_grid_y = cuda.gridsize(2) s_thread = 0.0 for i0 in range(iy, array2d.shape[0], threads_per_grid_x): for i1 in range(ix, array2d.shape[1], threads_per_grid_y): s_thread += array2d[i0, i1] # Allocate shared array s_block = cuda.shared.array(shared_array_len, numba.float32) # Index the threads linearly: each tid identifies a unique thread in the # 2D grid. tid = cuda.threadIdx.x + cuda.blockDim.x * cuda.threadIdx.y s_block[tid] = s_thread cuda.syncthreads() # We can use the same smart reduction algorithm by remembering that # shared_array_len == blockDim.x * cuda.blockDim.y # So we just need to start our indexing accordingly. i = (cuda.blockDim.x * cuda.blockDim.y) // 2 while (i != 0): if (tid \u0026lt; i): s_block[tid] += s_block[tid + i] cuda.syncthreads() i //= 2 # Store reduction in a 2D array the same size as the 2D blocks if tid == 0: partial_reduction2d[cuda.blockIdx.x, cuda.blockIdx.y] = s_block[0] N_2D = (20_000, 20_000) a_2d = np.arange(np.prod(N_2D), dtype=np.float32).reshape(N_2D) a_2d /= a_2d.sum() # a_2d will have sum = 1 (to float32 precision) s_2d_cpu = a_2d.sum() dev_a_2d = cuda.to_device(a_2d) dev_partial_reduction_2d = cuda.device_array(blocks_per_grid_2d, dtype=a.dtype) reduce2d[blocks_per_grid_2d, threads_per_block_2d](dev_a_2d, dev_partial_reduction_2d) s_2d = dev_partial_reduction_2d.copy_to_host().sum() # Final reduction in CPU np.isclose(s_2d, s_2d_cpu) # Ensure we have the right number --- True timing_2d = np.empty(21) for i in range(timing_2d.size): tic = perf_counter() reduce2d[blocks_per_grid_2d, threads_per_block_2d](dev_a_2d, dev_partial_reduction_2d) s_2d = dev_partial_reduction_2d.copy_to_host().sum() cuda.synchronize() toc = perf_counter() assert np.isclose(s_2d, s_2d_cpu) timing_2d[i] = toc - tic timing_2d *= 1e3 # convert to ms print(f\u0026#34;Elapsed time better: {timing_2d.mean():.0f} ± {timing_2d.std():.0f} ms\u0026#34;) --- Elapsed time better: 11 ± 0 ms 设备功能 到目前为止，我们只讨论了内核，它们是启动线程的特殊 GPU 函数。内核通常依赖于在 GPU 中定义的较小函数，这些函数只能访问 GPU 数组。这些被称为设备函数。与内核不同的是，它们可以返回值。\n为了结束本部分教程，我们将展示一个跨不同内核使用设备函数的示例。该示例还将强调在使用共享数组时同步线程的重要性。\n注意：在较新版本的 CUDA 中，内核可以启动其他内核。这称为动态并行，Numba CUDA 尚不支持。*\n2D 共享数组示例 在此示例中，我们将在固定大小的数组中创建波纹图案。我们首先需要声明将使用的线程数，因为这是共享数组所需的。\nthreads_16 = 16 import math @cuda.jit(device=True, inline=True) # inlining can speed up execution def amplitude(ix, iy): return (1 + math.sin(2 * math.pi * (ix - 64) / 256)) * ( 1 + math.sin(2 * math.pi * (iy - 64) / 256) ) # Example 2.5a: 2D Shared Array @cuda.jit def blobs_2d(array2d): ix, iy = cuda.grid(2) tix, tiy = cuda.threadIdx.x, cuda.threadIdx.y shared = cuda.shared.array((threads_16, threads_16), numba.float32) shared[tiy, tix] = amplitude(iy, ix) cuda.syncthreads() array2d[iy, ix] = shared[15 - tiy, 15 - tix] # Example 2.5b: 2D Shared Array without synchronize @cuda.jit def blobs_2d_wrong(array2d): ix, iy = cuda.grid(2) tix, tiy = cuda.threadIdx.x, cuda.threadIdx.y shared = cuda.shared.array((threads_16, threads_16), numba.float32) shared[tiy, tix] = amplitude(iy, ix) # When we don\u0026#39;t sync threads, we may have not written to shared # yet, or even have overwritten it by the time we write to array2d array2d[iy, ix] = shared[15 - tiy, 15 - tix] N_img = 1024 blocks = (N_img // threads_16, N_img // threads_16) threads = (threads_16, threads_16) dev_image = cuda.device_array((N_img, N_img), dtype=np.float32) dev_image_wrong = cuda.device_array((N_img, N_img), dtype=np.float32) blobs_2d[blocks, threads](dev_image) blobs_2d_wrong[blocks, threads](dev_image_wrong) image = dev_image.copy_to_host() image_wrong = dev_image_wrong.copy_to_host() import matplotlib.pyplot as plt fig, (ax1, ax2) = plt.subplots(1, 2) ax1.imshow(image.T, cmap=\u0026#34;nipy_spectral\u0026#34;) ax2.imshow(image_wrong.T, cmap=\u0026#34;nipy_spectral\u0026#34;) for ax in (ax1, ax2): ax.set_xticks([]) ax.set_yticks([]) ax.set_xticklabels([]) ax.set_yticklabels([]) 结论 在本教程中，您学习了如何开发需要缩减模式来处理一维和二维数组的内核。在此过程中，我们学习了如何利用共享数组和设备功能。\n","permalink":"https://hivan.me/posts/numba-%E7%9A%84-cuda-%E7%A4%BA%E4%BE%8B-2-4%E7%A9%BF%E9%92%88%E5%BC%95%E7%BA%BF/","summary":"\u003cblockquote\u003e\n\u003cp\u003e本教程为 \u003ca href=\"https://mp.weixin.qq.com/mp/appmsgalbum?__biz=MzA4NzE4MDQzMg==\u0026amp;action=getalbum\u0026amp;album_id=3478311056533995521\u0026amp;scene=21#wechat_redirect\"\u003eNumba CUDA 示例\u003c/a\u003e 第 2 部分。 \u0026gt; \u0026gt; 按照本系列从头开始使用 Python 学习 CUDA 编程\u003c/p\u003e\u003c/blockquote\u003e\n\u003ch2 id=\"介绍\"\u003e介绍\u003c/h2\u003e\n\u003cp\u003e在\u003ca href=\"https://mp.weixin.qq.com/s/LdLEhntmiHewB5z2-NjAqg\"\u003e本系列的第一部分\u003c/a\u003e中，我们讨论了如何使用 GPU 运行高度并行算法。高度并行任务是指任务完全相互独立的任务，例如对两个数组求和或应用任何元素函数。\n\u003cimg alt=\"使用“穿针引线赛博朋克”进行稳定扩散\" loading=\"lazy\" src=\"https://miro.medium.com/v2/resize:fit:700/1*giYSelu6J1NR8T3dbFLPhA.png\"\u003e\u003c/p\u003e\n","title":"Numba 的 CUDA 示例 (2/4)：穿针引线"},{"content":"Python 中的 CUDA CUDA 最初设计为与 C 兼容。后来的版本将其扩展到 C++ 和 Fortran。在 Python 生态系统中，使用 CUDA 的方法之一是通过Numba，这是一个适用于 Python 的即时 (JIT) 编译器，可以针对 GPU（它也针对 CPU，但这超出了我们的范围）。使用 Numba，可以直接用 Python（一个子集）编写内核，Numba 将即时编译代码并运行它。虽然它没有实现完整的 CUDA API，但其支持的功能通常足以获得与 CPU 相比令人印象深刻的加速（有关所有缺失的功能，请参阅Numba 文档1）。\n然而， Numba并不是唯一的选择。CuPy既提供依赖于 CUDA 的高级函数，也提供用于集成用 C 编写的内核的低级 CUDA 支持，以及可 JIT 的 Python 函数（类似于 Numba）。PyCUDA 提供了对 CUDA API 的更细粒度控制。最近，Nvidia 发布了官方CUDA Python，这必将丰富生态系统。所有这些项目都可以相互传递设备数组，你不必局限于只使用一个。\n在本系列中 本系列的目标是通过用 Numba CUDA 编写的示例为常见的 CUDA 模式提供一个学习平台。本系列并不是 CUDA 或 Numba 的综合指南。读者可以参考它们各自的文档。本教程的结构受到Jason Sanders 和 Edward Kandrot 合著的[「 CUDA by Example: An Introduction to General-Purpose GPU Programming」](https://developer.nvidia.com/cuda-example)一书的启发。如果你最终不再使用 Python 并想用 C 语言编写代码，那么这是一个极好的资源。\n本系列还将会包含第 2 部分、第 3 部分和第 4 部分。\n在本教程中，我们将学习如何运行我们的第一个 Numba CUDA 内核。我们还将学习如何有效地使用 CUDA 执行高度并行的任务，即完全相互独立的任务。最后，我们将学习如何从 CPU 计时内核的运行时间。因为我的电脑是 Mac，无法实现 CUDA，所以我在 Google Colab 上进行了实现，可以点击查看：https://colab.research.google.com/drive/1O5bhDHZgJqLwVaQl4rkoewY_XyO_krKQ?usp=sharing\nGPU 并行编程简介 GPU 相对于 CPU 的最大优势在于它们能够并行执行相同的指令。单个 CPU 内核将以串行方式一个接一个地运行指令。在 CPU 上进行并行化需要同时使用其多个内核（物理或虚拟）。标准的现代计算机具有 4-8 个内核。另一方面，现代 GPU 拥有数百甚至数千个计算内核。参见图 1 以了解两者之间的比较。GPU 内核通常较慢并且只能执行简单指令，但它们的庞大数量通常可以弥补这些缺点。需要注意的是，为了使 GPU 比 CPU 更具优势，它们运行的算法必须是可并行的。\n我认为理解 GPU 编程有四个主要方面。第一个我已经提到过：理解如何思考和设计本质上并行的算法。这可能很难，因为有些算法是串行设计的，也因为可以有多种并行化同一算法的方法。\n第二个方面是学习如何将主机上的结构（例如矢量和图像）映射到 GPU 结构（例如线程和块）上。重复模式和辅助函数可以帮助我们实现这一点，但归根结底，实验对于充分利用 GPU 至关重要。\n第三是理解驱动 GPU 编程的异步执行模型。不仅 GPU 和 CPU 彼此独立地执行指令，GPU 还具有允许多个处理流在同一 GPU 中运行的流。这种异步性在设计最佳处理流程时非常重要。\n第四个也是最后一个方面是抽象概念和具体代码之间的关系：这是通过学习 API 及其细微差别来实现的。\n当你阅读第一章时，请尝试在以下示例中识别这些概念！\n图 1.1。简化的 CPU 架构（左）和 GPU 架构（右）。算术发生在 ALU（算术逻辑单元）中，DRAM 数据，缓存保存可以更快访问的数据，但通常容量较小。控制单元执行指令。来源：维基百科。\n入门 我们将首先设置我们的环境：高于 0.55 的 Numba 版本和受支持的 GPU。\nimport numpy as np import numba from numba import cuda print(np.__version__) print(numba.__version__) --- 1.25.2 0.59.1 cuda.detect() --- Found 1 CUDA devices id 0 b\u0026#39;Tesla T4\u0026#39; [SUPPORTED] Compute Capability: 7.5 PCI Device ID: 4 PCI Bus ID: 0 UUID: GPU-0aa3c43c-1ada-6075-e57a-dccb0793a8b6 Watchdog: Disabled FP32/FP64 Performance Ratio: 32 Summary: 1/1 devices are supported True Numba CUDA 的主要工作是cuda.jit装饰器。它用于定义将在 GPU 中运行的函数。\n我们首先定义一个简单的函数，该函数接受两个数字并将它们存储在第三个参数的第一个元素上。我们的第一课是内核（启动线程的 GPU 函数）不能返回值。我们通过传递输入和输出来解决这个问题。这是 C 中的常见模式，但在 Python 中并不常见。\n# Example 1.1: Add scalars @cuda.jit def add_scalars(a, b, c): c[0] = a + b dev_c = cuda.device_array((1,), np.float32) add_scalars[1, 1](2.0, 7.0, dev_c) c = dev_c.copy_to_host() print(f\u0026#34;2.0 + 7.0 = {c[0]}\u0026#34;) --- 2.0 + 7.0 = 9.0 你可能已经注意到，在调用内核之前，我们需要在设备上分配一个数组。此外，如果我们想显示返回的值，我们需要将其复制回 CPU。你可能会问自己为什么我们选择分配一个float32（单精度浮点数）。这是因为，虽然大多数现代 GPU 都支持双精度运算，但双精度运算所需的时间可能是单精度运算的 4 倍或更长。因此，最好习惯使用np.float32andnp.complex64而不是float/np.float64和complex/ np.complex128。\n虽然内核定义看起来类似于 CPU 函数，但内核调用略有不同。特别是，它在参数前有方括号：\nadd_scalars[1, 1](2.0, 7.0, dev_c) 这些方括号分别表示网格中的块数和块中的线程数。在学习使用 CUDA 进行并行化时，让我们进一步讨论一下这些含义。\n使用 CUDA 进行并行化 CUDA 网格的剖析 启动内核时，它会有一个与之关联的网格。网格由块组成；块由线程组成。图 2 显示了一维 CUDA 网格。图中的网格有 4 个块。网格中的块数保存在一个特殊变量中，该变量可在内核内部访问，称为gridDim.x。.x是指网格的第一维（在本例中是唯一的一维）。二维网格也有.y和三维网格.z变量。同样在内核内部，你可以通过使用 找出正在执行哪个块blockIdx.x，在本例中它将从 0 运行到 3。\n每个块都有一定数量的线程，保存在变量中blockDim.x。线程索引保存在变量中threadIdx.x，在本例中从 0 到 7。\n重要的是，不同块中的线程被安排以不同的方式运行，可以访问不同的内存区域，并且在某些方面也有所不同（请参阅CUDA 复习：CUDA 编程模型的简要讨论）。现在，我们将跳过这些细节。\n当我们在第一个示例中使用参数启动内核时[1, 1]，我们告诉 CUDA 运行一个块和一个线程。传递多个块和多个线程将多次运行内核。操纵threadIdx.x和blockIdx.x将使我们能够唯一地标识每个线程。\n我们不再对两个数字求和，而是尝试对两个数组求和。假设每个数组有 20 个元素。如上图所示，我们可以启动一个内核，每个块有 8 个线程。如果我们希望每个线程只处理一个数组元素，那么我们将至少需要 4 个块。启动 4 个块，每个块有 8 个线程，我们的网格将启动 32 个线程。\n现在我们需要弄清楚如何将线程索引映射到数组索引。threadIdx.x从 0 到 7 运行，因此它们本身无法索引我们的数组。此外，不同的块具有相同的threadIdx.x。另一方面，它们具有不同的blockIdx.x。要为每个线程获取唯一索引，我们可以组合这些变量：\ni = threadIdx.x + blockDim.x * blockIdx.x 对于第一个块，blockIdx.x = 0和i将从 0 运行到 7。对于第二个块，blockIdx.x = 1。由于blockDim.x = 8，i将从 8 运行到 15。同样，对于blockIdx.x = 2，i将从 16 运行到 23。在第四个也是最后一个块中，i将从 24 运行到 31。请参阅下表 1。\ni 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 \u0026hellip; 31 threadIdx.x 0 1 2 3 4 5 6 7 0 1 2 3 4 5 6 7 0 \u0026hellip; 7 blockIdx.x 0 0 0 0 0 0 0 0 1 1 1 1 1 1 1 1 2 \u0026hellip; 3 我们解决了一个问题：如何将每个线程映射到数组中的每个元素……但现在我们遇到了一个问题，即某些线程会溢出数组，因为数组有 20 个元素，最多i可达 32-1。解决方案很简单：对于这些线程，不要执行任何操作！\n我们来看看代码。\n# Example 1.2: Add arrays @cuda.jit def add_array(a, b, c): i = cuda.threadIdx.x + cuda.blockDim.x * cuda.blockIdx.x if i \u0026lt; a.size: c[i] = a[i] + b[i] N = 20 a = np.arange(N, dtype=np.float32) b = np.arange(N, dtype=np.float32) dev_c = cuda.device_array_like(a) add_array[4, 8](a, b, dev_c) c = dev_c.copy_to_host() print(c) --- [ 0. 2. 4. 6. 8. 10. 12. 14. 16. 18. 20. 22. 24. 26. 28. 30. 32. 34. 36. 38.] 在较新版本的 Numba 中，我们会收到一条警告，指出我们使用主机数组调用了内核。理想情况下，我们希望避免将数据从主机移动到设备，因为这非常慢。我们应该在所有参数中使用设备数组调用内核。我们可以通过预先将数组从主机移动到设备来实现这一点：\ndev_a = cuda.to_device（a） dev_b = cuda.to_device（b） 此外，每个线程的唯一索引的计算很快就会过时。值得庆幸的是，Numba 提供了非常简单的包装器cuda.grid，它以网格维度作为唯一参数来调用。新内核将如下所示：\n# Example 1.3: Add arrays with cuda.grid @cuda.jit def add_array(a, b, c): i = cuda.grid(1) if i \u0026lt; a.size: c[i] = a[i] + b[i] add_array[4, 8](dev_a, dev_b, dev_c) c = dev_c.copy_to_host() print(c) --- [ 0. 2. 4. 6. 8. 10. 12. 14. 16. 18. 20. 22. 24. 26. 28. 30. 32. 34. 36. 38.] 当我们改变数组的大小时会发生什么？一种简单的解决方法是简单地更改网格参数（块数和每个块的线程数），以便至少启动与数组中的元素一样多的线程。\n设置这些参数既需要一定的科学性，也需要一定的艺术性。从“科学性”的角度来说，我们会说 (a) 它们应该是 2 的倍数，通常在 32 到 1024 之间，以及 (b) 应该选择它们以最大化占用率（同时有多少个线程处于活动状态）。Nvidia 提供了一个电子表格来帮助计算这些值。从“艺术性”的角度来说，没有什么可以预测内核的行为，因此如果你真的想优化这些参数，你需要使用典型输入来分析你的代码。实际上，现代 GPU 的“合理”线程数是 256。\nN = 1_000_000 a = np.arange(N, dtype=np.float32) b = np.arange(N, dtype=np.float32) dev_a = cuda.to_device(a) dev_b = cuda.to_device(b) dev_c = cuda.device_array_like(a) threads_per_block = 256 blocks_per_grid = (N + (threads_per_block - 1)) // threads_per_block # Note that # blocks_per_grid == ceil(N / threads_per_block) # ensures that blocks_per_grid * threads_per_block \u0026gt;= N add_array[blocks_per_grid, threads_per_block](dev_a, dev_b, dev_c) c = dev_c.copy_to_host() np.allclose(a + b, c) --- True 在继续讨论向量求和之前，我们需要讨论一下硬件限制。GPU 无法运行任意数量的线程和块。通常，每个块不能有超过 1024 个线程，并且网格不能有超过 2¹⁶ − 1 = 65535 个块。这并不是说你可以启动 1024 × 65535 个线程……根据其寄存器占用的内存量以及其他考虑因素，可以启动的线程数量是有限制的。此外，必须谨慎尝试处理无法一次性放入 GPU RAM 的大型数组。在这些情况下，可以使用单个 GPU 或多个 GPU 分段处理数组。\n信息：在 Python 中，可以通过 Nvidia 的 cuda-python 库，通过其文档中的函数 cuDeviceGetAttribute 获取硬件限制。有关示例，请参见本节末尾的附录。\n网格跨步循环 如果每个网格的块数超出硬件限制，但数组适合内存，则我们可以使用一个线程来处理多个元素，而不是每个数组元素使用一个线程。我们将使用一种称为网格步长循环的技术来实现这一点。除了克服硬件限制之外，网格步长循环内核还可以通过重用线程来最大限度地减少线程创建/销毁开销。Mark Harris 的博客文章 CUDA Pro Tip: Write Flexible Kernels with Grid-Stride Loops 详细介绍了网格步长循环的一些好处。\n该技术背后的想法是在 CUDA 内核中添加一个循环来处理多个输入元素。顾名思义，此循环的步幅等于网格中的线程数。这样，如果网格中的线程总数 ( threads_per_grid = blockDim.x * gridDim.x) 小于数组元素的数量，则内核处理完索引后cuda.grid(1)就会处理索引cuda.grid(1) + threads_per_grid，依此类推，直到处理完所有数组元素。事不宜迟，让我们看看代码。\n# Example 1.4: Add arrays with grid striding @cuda.jit def add_array_gs(a, b, c): i_start = cuda.grid(1) threads_per_grid = cuda.blockDim.x * cuda.gridDim.x for i in range(i_start, a.size, threads_per_grid): c[i] = a[i] + b[i] threads_per_block = 256 blocks_per_grid_gs = 32 * 80 # Use 32 * multiple of streaming multiprocessors # 32 * 80 * 256 \u0026lt; 1_000_000 so one thread will process more than one array element add_array_gs[blocks_per_grid_gs, threads_per_block](dev_a, dev_b, dev_c) c = dev_c.copy_to_host() np.allclose(a + b, c) --- True 这段代码与上面的代码非常相似，不同之处在于我们从 cuda.grid(1) 开始，但会执行更多的采样，每个threads_per_grid 执行一次，直到数组结束。\n那么，哪一个内核更快呢？\n计时 CUDA 内核 GPU 编程的核心在于速度。因此，准确测量代码执行情况非常重要。\nCUDA 内核是主机 (CPU)启动的设备功能，但它们当然是在 GPU 上执行的。除非我们告诉它们，否则 GPU 和 CPU 不会进行通信。因此，当 GPU 内核启动时，CPU 将继续运行指令，无论是启动更多内核还是执行其他 CPU 功能。如果我们time.time()在内核启动之前和之后进行调用，我们将仅计时内核启动所需的时间*，而不是运行所需的*时间。\n我们可以使用一个函数来确保 GPU 已经“赶上” 。cuda.synchronize()，调用此函数将停止主机执行任何其他代码，直到 GPU 完成已在其中启动的每个内核的执行。\n要对内核执行进行计时，我们可以简单地计时内核运行然后同步所需的时间。这有两个注意事项。首先，我们需要使用time.perf_counter()或time.perf_counter_ns()而不是time.time()。time.time()不计算主机休眠等待 GPU 完成执行的时间。第二个注意事项是，从主机计时代码并不理想，因为这会产生相关开销。稍后，我们将解释如何使用 CUDA事件对来自设备的内核进行计时。Mark Harris 有另一篇关于此主题的精彩博客文章，标题为How to Implement Performance Metrics in CUDA C/C++。\n使用 Numba 时，我们必须注意一个细节。Numba 是一个即时编译器，这意味着函数只有在被调用时才会被编译。因此，对函数的第一次调用进行计时*也会对编译步骤进行计时，*而编译步骤通常要慢得多。我们必须记住始终先通过启动内核然后同步它来编译代码，以确保 GPU 中没有剩余的内容要运行。这可确保下一个内核无需编译即可立即运行。还要注意，数组的dtype应该相同，因为 Numba 为参数的每种组合编译一个唯一的函数dtypes。\nfrom time import perf_counter_ns # Compile and then clear GPU from tasks add_array[blocks_per_grid, threads_per_block](dev_a, dev_b, dev_c) cuda.synchronize() timing = np.empty(101) for i in range(timing.size): tic = perf_counter_ns() add_array[blocks_per_grid, threads_per_block](dev_a, dev_b, dev_c) cuda.synchronize() toc = perf_counter_ns() timing[i] = toc - tic timing *= 1e-3 # convert to μs print(f\u0026#34;Elapsed time: {timing.mean():.0f} ± {timing.std():.0f} μs\u0026#34;) --- Elapsed time: 131 ± 36 μs # Compile and then clear GPU from tasks add_array_gs[blocks_per_grid_gs, threads_per_block](dev_a, dev_b, dev_c) cuda.synchronize() timing_gs = np.empty(101) for i in range(timing_gs.size): tic = perf_counter_ns() add_array_gs[blocks_per_grid_gs, threads_per_block](dev_a, dev_b, dev_c) cuda.synchronize() toc = perf_counter_ns() timing_gs[i] = toc - tic timing_gs *= 1e-3 # convert to μs print(f\u0026#34;Elapsed time: {timing_gs.mean():.0f} ± {timing_gs.std():.0f} μs\u0026#34;) --- Elapsed time: 140 ± 36 μs 对于简单内核，我们还可以测量算法的吞吐量，它等于每秒浮点运算次数。它通常以 GFLOP/s（每秒千兆 FLOP）为单位。我们的加法运算仅包含一个 FLOP：加法。因此，吞吐量由以下公式给出：\n# G * FLOP / timing in s gflops = 1e-9 * dev_a.size * 1e6 / timing.mean() gflops_gs = 1e-9 * dev_a.size * 1e6 / timing_gs.mean() print(f\u0026#34;GFLOP/s (algo 1): {gflops:.2f}\u0026#34;) print(f\u0026#34;GFLOP/s (algo 2): {gflops_gs:.2f}\u0026#34;) --- GFLOP/s (algo 1): 7.65 GFLOP/s (algo 2): 7.15 2D 示例 为了结束本教程，让我们制作一个 2D 内核来对图像应用对数校正。\n给定一个图像 I(x, y)，其值介于 0 和 1 之间，对数校正图像由下式给出\nIᵪ(x, y) = γ log₂ (1 + I(x, y))\n首先让我们获取一些数据！\nimport matplotlib.pyplot as plt from skimage import data moon = data.moon().astype(np.float32) / 255. fig, ax = plt.subplots() im = ax.imshow(moon, cmap=\u0026#34;gist_earth\u0026#34;) ax.set_xticks([]) ax.set_yticks([]) ax.set_xticklabels([]) ax.set_yticklabels([]) fig.colorbar(im) fig.tight_layout() 如你所见，数据在低端确实饱和了，几乎没有高于 0.6 的值。\n让我们编写内核。\nimport math # Example 1.5: 2D kernel @cuda.jit def adjust_log(inp, gain, out): ix, iy = cuda.grid(2) # The first index is the fastest dimension threads_per_grid_x, threads_per_grid_y = cuda.gridsize(2) # threads per grid dimension n0, n1 = inp.shape # The last index is the fastest dimension # Stride each dimension independently for i0 in range(iy, n0, threads_per_grid_y): for i1 in range(ix, n1, threads_per_grid_x): out[i0, i1] = gain * math.log2(1 + inp[i0, i1]) threads_per_block_2d = (16, 16) # 256 threads total blocks_per_grid_2d = (64, 64) moon_gpu = cuda.to_device(moon) moon_corr_gpu = cuda.device_array_like(moon_gpu) adjust_log[blocks_per_grid_2d, threads_per_block_2d](moon_gpu, 1.0, moon_corr_gpu) moon_corr = moon_corr_gpu.copy_to_host() fig, (ax1, ax2) = plt.subplots(1, 2) ax1.imshow(moon, cmap=\u0026#34;gist_earth\u0026#34;) ax2.imshow(moon_corr, cmap=\u0026#34;gist_earth\u0026#34;) ax1.set(title=\u0026#34;Original image\u0026#34;) ax2.set(title=\u0026#34;Log-corrected image\u0026#34;) for ax in (ax1, ax2): ax.set_xticks([]) ax.set_yticks([]) ax.set_xticklabels([]) ax.set_yticklabels([]) fig.tight_layout() 让我们注意一下这两个 for 循环。请注意，第一个 for 循环从 iy 开始，而最内层的第二个循环从 ix 开始。我们完全可以选择 i0 从 ix 处开始，而 i1 从 iy 处开始，这样会感觉更自然。那么，我们为什么要选择这种顺序呢？事实证明，第一种选择的内存访问模式更有效。由于第一个网格索引是最快的索引，我们希望它能与我们最快的维度（即最后一个维度）相匹配。\n如果你不想相信我的话（你不应该相信！）你现在已经学会了如何对内核执行进行计时，你可以尝试这两个版本。对于像这里使用的数组这样的小数组，差异可以忽略不计，但对于较大的数组（例如 10,000 x 10,000），我测量到的速度提高了约 10%。虽然不是特别令人印象深刻，但如果我可以通过一次变量交换为你提供 10% 的改进，谁会不接受呢？\n就这样！我们现在可以在校正后的图像中看到更多细节。\n作为练习，尝试使用不同的网格对不同的启动进行计时，以找到适合你的机器的最佳网格大小。\n结论 在本教程中，你学习了 Numba CUDA 的基础知识。你学习了如何创建简单的 CUDA 内核，并将内存移至 GPU 以使用它们。你还学习了如何使用一种称为grid-stride loops 的技术迭代 1D 和 2D 数组。\n附录：使用 Nvidia 的 cuda-python 探测设备属性 为了对 GPU 的精确属性进行细粒度控制，你可以依赖 Nvidia 提供的低级官方 CUDA Python 包。\n# Need to: pip install --upgrade cuda-python from cuda.cuda import CUdevice_attribute, cuDeviceGetAttribute, cuDeviceGetName, cuInit # Initialize CUDA Driver API (err,) = cuInit(0) # Get attributes err, DEVICE_NAME = cuDeviceGetName(128, 0) DEVICE_NAME = DEVICE_NAME.decode(\u0026#34;ascii\u0026#34;).replace(\u0026#34;\\x00\u0026#34;, \u0026#34;\u0026#34;) err, MAX_THREADS_PER_BLOCK = cuDeviceGetAttribute( CUdevice_attribute.CU_DEVICE_ATTRIBUTE_MAX_THREADS_PER_BLOCK, 0 ) err, MAX_BLOCK_DIM_X = cuDeviceGetAttribute( CUdevice_attribute.CU_DEVICE_ATTRIBUTE_MAX_BLOCK_DIM_X, 0 ) err, MAX_GRID_DIM_X = cuDeviceGetAttribute( CUdevice_attribute.CU_DEVICE_ATTRIBUTE_MAX_GRID_DIM_X, 0 ) err, SMs = cuDeviceGetAttribute( CUdevice_attribute.CU_DEVICE_ATTRIBUTE_MULTIPROCESSOR_COUNT, 0 ) print(f\u0026#34;Device Name: {DEVICE_NAME}\u0026#34;) print(f\u0026#34;Maximum number of multiprocessors: {SMs}\u0026#34;) print(f\u0026#34;Maximum number of threads per block: {MAX_THREADS_PER_BLOCK:10}\u0026#34;) print(f\u0026#34;Maximum number of blocks per grid: {MAX_BLOCK_DIM_X:10}\u0026#34;) print(f\u0026#34;Maximum number of threads per grid: {MAX_GRID_DIM_X:10}\u0026#34;) --- Device Name: Tesla T4 Maximum number of multiprocessors: 40 Maximum number of threads per block: 1024 Maximum number of blocks per grid: 1024 Maximum number of threads per grid: 2147483647 https://numba.readthedocs.io/en/stable/cuda/overview.html#missing-cuda-features\u0026#160;\u0026#x21a9;\u0026#xfe0e;\n","permalink":"https://hivan.me/posts/numba-%E7%9A%84-cuda-%E7%A4%BA%E4%BE%8B1-4%E8%B8%8F%E4%B8%8A%E5%B9%B6%E8%A1%8C%E4%B9%8B%E6%97%85/","summary":"\u003cblockquote\u003e\n\u003cp\u003e按照本系列从头开始使用 Python 学习 CUDA 编程\u003c/p\u003e\u003c/blockquote\u003e\n\u003ch2 id=\"介绍\"\u003e介绍\u003c/h2\u003e\n\u003cp\u003eGPU（图形处理单元），顾名思义，最初是为计算机图形学开发的。从那时起，它们几乎在每个需要高计算吞吐量的领域都无处不在。这一进步得益于 GPGPU（通用 GPU）接口的发展，这些接口使我们能够对 GPU 进行编程以进行通用计算。这些接口中最常见的是CUDA，其次是OpenCL，最近的是 HIP。\n\u003cimg alt=\"img\" loading=\"lazy\" src=\"https://miro.medium.com/v2/resize:fit:700/1*mQNpI1VA-yBsbmxPIaj3GA.png\"\u003e\u003c/p\u003e\n","title":"Numba 的 CUDA 示例（1/4）：踏上并行之旅"},{"content":"世界上没有完美的数据集。每个数据科学家在数据探索过程中都会有这样的感觉：\ndf.info() 看到类似这样的内容：\n大多数 ML 模型无法处理 NaN 或空值，因此如果您的特征或目标包含这些值，则在尝试将模型拟合到数据之前对它们进行适当处理非常重要。\n在本文中，我将探讨处理时间序列数据集中的空值/缺失数据的 3 种简单方法。\n1. 删除空值 这可能是处理缺失数据最简单、最直接的方法：将其删除。\n# 删除所有列中的所有空值 df.dropna(inplace=True) 默认情况下，pandas 的dropna 函数会全面搜索（所有列）空值，并删除任何列中存在空值的行**。**但是，可以使用各种参数进行修改。\n在本数据集中，请注意 NMHC(GT) 列只有 914 个非空值。因此，如果我们删除所有空值，我们的模型最终最多只能得到 914 行（可能更少）。这与原来的 9,357 行相比大幅下降！\n通过指定列的子集 ，pandas 将仅删除数据框中特定列为空的行。\ndf.dropna(subset=[\u0026#39;CO(GT)\u0026#39;,\u0026#39;PT08.S1(CO)\u0026#39;], inplace=True) 这样，我们可以对方法进行混合和搭配，在某些列中删除空值，并以不同的方式处理其他列。\n您还可以通过将参数how设置为“all”来指定是否仅删除所有列都为空的行。how 的默认值为“any”。\n2. 插值空值 填充空值的另一种简单方法是通过插值。Pandas 的 interpolate 方法默认使用线性插值。\n线性插值基本上取空值前后的两个值，并在两者之间创建一条线。然后使用这条线来估计缺失数据点的值。Pandas**的插值方法假设每个数据点的间距相等。**如果您没有针对每个可能的时间戳设置一行，只要您有日期时间索引，就可以将插值方法设置为“时间”。这样，如果您有两行相隔 \u0026gt;1 个间隔（例如 \u0026gt;1 天或 1 小时），插值将考虑这个距离。\n如果这是第一个索引，由于空值前面没有值，因此不会进行插值。\n在这种情况下，插值很简单，因为在两个已知值的中间正好有 1 个空值。所有值都以 1 小时为间隔。索引 10 处的空值将只是前后值的平均值 (0.65)。\n如果存在 2 个或更多连续的 NaN，则将根据它们与已知值之间的距离对它们进行插值。\n**您可以通过limit**关键字参数设置要插入的连续 NaN 数量限制。如果有大量连续 NaN，您可能希望在某个插值点之后删除它们，因为*每次插值都会给算法带来不确定性。*插值越多 = 不确定性越大，尤其是在时间序列的情况下。\n3. 归纳空值 我要介绍的最后一种方法是归纳法。归纳法本质上意味着用数据的平均值或中位数填充空值。\n最简单的方法是使用 pandas 的 fillna 并取整列的中值。\ndf.fillna(df[\u0026#39;CO(GT)\u0026#39;].median()) 但对于时间序列，整个数据集的中值通常并不准确。时间序列数据通常具有季节性模式，使用情况会根据一天中的小时、星期几、月份等而变化。\n对于这个例子，我决定使用该小时的中位数来估算 CO(GT) 列**。**\n为了能够用中位数进行估算，我想出了自己的解决方案，因为没有直接的方法或库可以做到这一点（据我所知）。 我必须首先创建一个数据框，其中包含各个小时的所有中位数。\n# 创建包含按小时分组的每列中位数的数据框 hour_df = pd.DataFrame(df.groupby([df.index.hour]).median()) hour_df.reset_index(inplace=True) 接下来，我创建了一个名为 get_hour_median 的函数。虽然我仅针对 CO(GT) 列展示了该函数，但我使该函数足够灵活，以便它可以处理任何列名。\ndef get_hour_median(hour,col_name): median = hour_df[hour_df[\u0026#39;Datetime\u0026#39;]==hour][col_name].values[0] return median 然后我使用 apply 和另一个自定义函数将此函数应用于 CO(GT) 列。\n# 重置日期时间索引以便在下面的函数中更轻松地处理 df.reset_index(inplace=True) # 获取数据框行并返回中值（如果行为空），否则返回原始值。 def fill_with_hourly_median(row,col_name): if pd.isnull(row[col_name]): return get_hour_median(row[\u0026#39;Datetime\u0026#39;].hour,col_name) else: return row[col_name] # 将 fill_with_hourly_median 应用于 CO(GT) 列 df[\u0026#39;CO(GT)\u0026#39;] = df.apply(fill_with_hourly_median, axis=1, col_name=\u0026#39;CO(GT)\u0026#39;) CO(GT) 列现在应该填写相应小时的中值而不是 NaN。\n选择哪一个？ 很多时候，您会针对不同的列使用不同方法的组合。例如，由于线性插值不会填充列中的第一个值，因此如果数据框开头有空行，则可以在数据框中间的行被插值后删除这些行。\n如果您有大量数据，且空值不多，则删除几行不会产生太大影响。在这种情况下，删除通常是我的首选方法，因为我将输入模型的所有数据都是实际数据。\n对于数据集中偶尔出现的小间隙（1-2 行缺失），我通常会使用插值法。但是，如果间隙较大，且存在大量连续的空值，我会考虑使用中位数，直到达到某个阈值（\u0026gt;6-10，但可能取决于数据的粒度和模式的一致性），之后我会开始删除行。\n如您所见，虽然处理缺失数据是一种常见现象，但处理方法有很多考虑因素。我提到的方法绝不是唯一的方法，但仅使用这 3 种方法就可以做很多事情。\n我建议 彻底探索您的时间序列数据，方法是绘制图表并确定零点在哪里、差距是大还是小以及存在哪些类型的季节性模式。随着时间和实践，您将对如何最好地处理数据中的差距有更好的直觉。\n参考 Vito,Saverio. (2016). Air Quality. UCI Machine Learning Repository. https://doi.org/10.24432/C59K5F. ","permalink":"https://hivan.me/posts/%E5%A6%82%E4%BD%95%E5%A4%84%E7%90%86%E6%97%B6%E9%97%B4%E5%BA%8F%E5%88%97%E7%9A%84%E7%BC%BA%E5%A4%B1%E6%95%B0%E6%8D%AE/","summary":"\u003cblockquote\u003e\n\u003cp\u003e您是否应该删除、插入或估算？\n\u003cimg alt=\"img\" loading=\"lazy\" src=\"https://cdn-images-1.readmedium.com/v2/resize:fit:800/0*-ndatVW0okmXOIS1\"\u003e\u003c/p\u003e\u003c/blockquote\u003e","title":"如何处理时间序列的缺失数据"},{"content":"但是，在不同数据集上针对不同用例训练的不同模型如何趋同？是什么导致了这种趋同？\n柏拉图的洞穴寓言 Jan Saenredam（https://en.wikipedia.org/wiki/Allegory_of_the_cave#/media/File:Platon_Cave_Sanraedam_1604.jpg） 著\n1.柏拉图表征假说 我们认为，在不同的神经网络模型中，数据点的表示方法越来越相似。这种相似性跨越了不同的模型架构、训练目标，甚至数据模式。\nsource: https://arxiv.org/abs/2405.07987\n1.1 引言 论文的中心论点是，各种来源和模式的模型都在向现实的表征靠拢\u0026ndash;即世界事件的联合分布，这些事件产生了我们观察到的数据，并用来训练模型。\n作者认为，这种向柏拉图式表征的趋同是由模型所训练的数据的基本结构和性质以及模型本身日益增长的复杂性和能力所驱动的。随着模型遇到各种数据集和更广泛的应用，它们需要一种能捕捉所有数据类型中常见基本属性的表示方法。\n1.2 柏拉图的洞穴 本文特别引用了柏拉图的《洞穴寓言》（Allegory of the Cave），以类比假设人工智能模型如何发展出对现实的统一表征，以及柏拉图关于感知和现实的哲学思想。在柏拉图的寓言中，洞穴中的囚犯只能看到投射在墙上的真实物体的影子，他们只相信这些影子就是现实。然而，这些物体的真实形态存在于洞穴之外，比囚犯感知到的影子更加真实。\n","permalink":"https://hivan.me/posts/%E6%9F%8F%E6%8B%89%E5%9B%BE%E5%BC%8F%E8%A1%A8%E5%BE%81%E4%BA%BA%E5%B7%A5%E6%99%BA%E8%83%BD%E6%B7%B1%E5%BA%A6%E7%BD%91%E7%BB%9C%E6%A8%A1%E5%9E%8B%E6%98%AF%E5%90%A6%E8%B6%8B%E4%BA%8E%E4%B8%80%E8%87%B4/","summary":"人工智能模型是否正在向现实的统一表征演进？柏拉图表征假说认为，人工智能模型正在趋同。\n麻省理工学院最近的一篇论文引起了我的注意，因为它提出了一个令人印象深刻的观点：人工智能模型正在趋同，甚至跨越了不同的模态\u0026ndash;视觉和语言。\u0026ldquo;我们认为，人工智能模型，尤其是深度网络中的表征正在趋同\u0026rdquo;，这是 The Platonic Representation Hypothesis (\u003ca href=\"https://arxiv.org/abs/2405.07987\"\u003ehttps://arxiv.org/abs/2405.07987\u003c/a\u003e)论文的开头。","title":"柏拉图式表征：人工智能深度网络模型是否趋于一致？"},{"content":"但在 2017 年，一种新的神经网络架构颠覆了这一局面。这些模型被称为 \u0026ldquo;Transformer\u0026rdquo;，能以极大的并行化方式处理数据，效率远远超过 LSTM，尤其是在大规模数据集上。Transformer 掀起了一场革命，迅速成为处理序列的最先进的新方法，取代了长期占主导地位的 LSTM。它标志着构建人工智能系统以理解和生成自然语言的一个重要转折点。\nLSTM 简史 LSTM 旨在通过引入遗忘门、输入门和输出门等机制来克服早期循环神经网络 (RNN) 的局限性，共同帮助维持网络中的长期记忆。这些机制使 LSTM 能够了解序列中哪些数据需要保留或丢弃，从而使它们能够根据长期依赖性做出预测。尽管取得了成功，但 LSTM 开始被 Transformer 模型的崛起所掩盖，Transformer 模型在许多任务上提供了更高的可扩展性和性能，特别是在处理大型数据集和长序列方面。\n为什么 Transformer 会占据主导地位？ Transformers 之所以能取代它们，是因为它们具有自我关注机制，能够权衡句子中不同单词的重要性，而不管这些单词的位置距离有多远。与 RNN 或 LSTM 不同，Transformers 在训练过程中并行处理数据，从而大大加快了训练过程。不过，Transformers 也并非没有局限性。它们需要大量内存和计算能力，尤其是在大型数据集上进行训练时。此外，如果不继续扩大模型规模和增加数据，它们的性能也会趋于平稳，这表明在极端规模下，它们的收益会逐渐减少。\n进入 xLSTM：递归神经网络的新希望？ xLSTM 或扩展 LSTM 提出了一种新方法，通过整合指数门控和矩阵存储器等功能来增强传统 LSTM 架构。这些增强功能旨在解决 LSTM 固有的局限性，例如一旦写入存储信息就很难修改，以及存储单元的容量有限。xLSTM 有可能提高模型处理更复杂模式和更长序列的能力，而不会像 Transformers 那样带来沉重的计算负荷，从而为序列数据处理至关重要的应用提供了一条新的途径。\n了解 xLSTM 扩展长短时记忆（xLSTM）模型是传统 LSTM 网络的进步。它集成了新颖的修改，以提高性能，尤其是在大规模语言模型和复杂序列学习任务中。这些改进通过创新的门控机制和记忆结构，解决了传统 LSTM 的主要局限性。\nxLSTM 如何修改传统 LSTM？ xLSTM 融合了先进的内存管理和门控过程，扩展了 LSTM 的基本原理。xLSTM 引入了对标准存储单元结构和门控机制的修改，以改善这些方面。\n其中一个重大变化是采用了指数门控，使门控能够随时间更动态地调整，从而提高了网络管理较长序列的能力，而不受标准 sigmoid 函数的限制。此外，xLSTM 还修改了存储单元架构，以提高数据存储和检索效率，这对于需要对较长序列进行复杂模式识别的任务来说至关重要。\n解密指数门控和内存结构 xLSTM 中的指数门为网络内的信息处理方式引入了一个新的维度。传统门电路通常使用 sigmoid 函数来调节信息流，而指数门电路则不同，它使用指数函数来控制门电路的打开和关闭。这使得网络能够更精确地调整其记忆保持和遗忘率，从而更精细地控制过去的信息对当前状态决策的影响程度。\nxLSTM 的记忆结构也得到了增强。传统的 LSTM 使用单个向量来存储信息，当网络试图访问或覆盖数据时，这可能会导致瓶颈。xLSTM 引入了基于矩阵的存储系统，信息存储在多维空间中，允许模型同时处理更大量的信息。这种矩阵设置有利于数据的不同组成部分之间进行更复杂的交互，从而增强了模型区分和记忆数据中更细微模式的能力。\n比较：sLSTM 与 mLSTM xLSTM 架构分为两个主要变体：sLSTM（标量 LSTM）和 mLSTM（矩阵 LSTM）。每种变体都针对内存处理和计算效率的不同方面，以满足各种应用需求。\nsLSTM 专注于通过增强传统的单维内存单元结构来完善标量内存方法。它引入了内存混合和多存储单元等机制，使其能够对所保留的数据执行更复杂的计算。这种变体尤其适用于序列数据具有高度相互依赖性并需要对长序列进行细粒度分析的应用。\n另一方面，mLSTM 利用矩阵格式扩展了网络的内存容量。mLSTM 在模型需要快速访问和修改大型数据集的环境中尤为有效。\nsLSTM 和 mLSTM 提供了一个综合框架，充分利用了标量记忆和矩阵记忆方法的优势，使 xLSTM 成为各种序列学习任务的通用工具。\nxLSTM 架构的力量 xLSTM 架构与传统 LSTM 及其同类产品相比，引入了几项关键创新，旨在解决序列建模和长期依赖性管理方面的不足。这些改进主要集中在提高架构的学习能力、对序列数据的适应性以及在复杂计算任务中的整体效率。\n有效学习的秘诀 在 xLSTM 架构中集成残差块是一项关键的开发，它增强了网络从复杂数据序列中学习的能力。残差块有助于缓解深度神经网络中常见的梯度消失问题，让梯度更有效地流经网络。在 xLSTM 中，这些区块有助于更稳健、更稳定的学习过程，尤其是在深度网络结构中。通过纳入残差连接，xLSTM 层可以学习对标识函数的增量修改，从而保持网络中信息传递的完整性，并增强模型学习长序列的能力，而不会出现信号衰减。\nxLSTM 如何捕捉长期依赖关系 xLSTM 专门设计用于处理涉及顺序数据的任务，这要归功于它对长期依赖性的复杂处理。传统的 LSTM 通过门控机制来管理这些依赖关系；而 xLSTM 则利用其先进的门控和内存系统（如指数门控和矩阵内存结构）扩展了这一功能。这些创新使 xLSTM 能够更有效地捕捉和利用更长时间的上下文信息。这在语言建模、时间序列预测等应用中至关重要，在这些应用中，理解历史数据对于准确预测至关重要。该架构能够保持和处理对过去输入的更详细记忆，大大提高了它在需要深入理解上下文的任务中的性能，为递归神经网络树立了新的标杆。\n它能兑现承诺吗？ xLSTM 是一种扩展的 LSTM 架构，旨在通过引入指数门控和矩阵存储器等创新修改来解决传统 LSTM 的不足之处。这些改进提高了模型处理复杂序列数据的能力，并能在各种计算环境中高效运行。通过与 Transformers 等当代架构的比较以及在不同应用领域的应用，对 xLSTM 的有效性进行了评估。\n语言建模的性能比较 xLSTM 的定位是挑战 Transformer 模型在语言建模中的主导地位，尤其是在长期依赖性至关重要的情况下。初步基准测试表明，xLSTM 模型的性能很有竞争力，尤其是当数据涉及复杂的依赖关系或需要在较长的序列中保持状态时。在与最先进的 Transformer 模型的对比测试中，xLSTM 表现出了相当或更优的性能，这得益于它能够动态修改存储决策，并能在不明显降低性能的情况下处理更长的序列。\n探索 xLSTM 在其他领域的潜力 虽然 xLSTM 的增强功能主要是在语言建模的背景下进行评估的，但其潜在应用范围远不止于此。该架构对顺序数据的强大处理能力和改进的内存功能，使其非常适合其他领域的任务，如时间序列分析、音乐创作，甚至更复杂的动态系统模拟等领域。这些领域的早期实验表明，xLSTM 可以显著改善传统 LSTM 的局限性，为不同领域的研究人员和工程师提供了一种新工具，帮助他们为序列建模难题寻找高效的解决方案。\nxLSTM 的内存优势 随着现代应用对机器学习模型的要求越来越高，特别是在处理能力和内存效率方面，优化架构变得越来越重要。本节探讨了与传统Transformers 相关的内存限制，并介绍了 xLSTM 架构，它是一种更高效的替代方案，尤其适合现实世界的应用。\nTransformers 的内存限制 Transformers 自问世以来，已在自然语言处理和计算机视觉等多个人工智能领域树立了新标准。然而，它们的广泛应用也带来了巨大的挑战，尤其是在内存消耗方面。Transformers 本身需要大量内存，这是因为其关注机制涉及计算和存储所有输入位置对的值。对于大型数据集或较长的输入序列而言，这将导致内存需求的四次方增长，令人望而却步。\n这种内存密集型特性限制了基于 Transformer 的模型的实际应用，尤其是在手机或嵌入式系统等资源有限的设备上。此外，训练这些模型需要大量的计算资源，这会导致能耗增加和运营成本提高。随着人工智能的应用扩展到实时处理和效率至关重要的领域，Transformers 的内存限制成为开发人员和企业日益关注的问题。\n适用于真实世界应用的更紧凑、更高效的替代方案 针对 Transformers 的局限性，xLSTM 架构成为一种内存效率更高的解决方案。与 Transformers 不同，xLSTM 不依赖于在所有输入对中广泛使用注意力机制，从而大大减少了内存占用。xLSTM 利用创新的内存结构和门控机制来优化顺序数据的处理和存储。\nxLSTM 的核心创新在于其存储单元，它采用了指数门控和新颖的矩阵存储结构，允许有选择地更新和存储信息。这种方法不仅降低了内存要求，还增强了模型处理长序列的能力，而不会丢失信息。修改后的 xLSTM 存储结构包括标量存储器和矩阵存储器，可以更细致、更高效地处理数据依赖关系，因此特别适用于涉及时间序列数据的应用，如金融预测或传感器数据分析。\n此外，与传统 LSTM 相比，xLSTM 的架构允许更高的并行化。这一点在 xLSTM 的 mLSTM 变体中尤为明显，该变体的矩阵存储器可以并行更新，从而减少了计算时间，进一步提高了模型的效率。这种并行性与紧凑的内存结构相结合，使 xLSTM 在计算资源有限的环境中成为一种极具吸引力的部署选择。\nxLSTM 实际应用：实验验证 实验验证对于证明任何新机器学习架构的有效性和多功能性都至关重要。本节将深入探讨对 xLSTM 进行评估的严格测试环境，重点关注其在语言建模、处理长序列和关联回忆任务中的性能。这些实验展示了 xLSTM 的能力，并验证了它在各种场景中的实用性。\n对 xLSTM 进行测试 语言建模是对任何新的自然语言处理架构的基础测试。xLSTM 在传统 LSTM 的基础上进行了改进，并接受了广泛的语言建模测试，以评估其能力。该模型在不同的数据集上进行了训练，其中既有维基文本-103 等标准基准数据集，也有包含 150 亿词条的 SlimPajama 等大型语料库。这些测试的结果很有启发性；与前辈 LSTM 相比，xLSTM 的易混度得分有了明显提高，在某些情况下甚至超过了当代的 Transformer 模型。\n进一步的测试包括文本补全和机器翻译等生成任务，在这些任务中，xLSTM 在较长的文本跨度中保持上下文的能力至关重要。其性能突出表现在处理语言语法细微差别和捕捉扩展序列的深层语义方面。这种能力使 xLSTM 特别适用于自动语音识别和情感分析应用，在这些应用中，理解上下文和连续性至关重要。\nxLSTM 可以处理长序列吗？ xLSTM 的设计专门应对了这一挑战，加入了更有效地管理长期依赖关系的功能。为了评估这一点，xLSTM 在需要模型处理长数据序列的环境中进行了测试，例如文档摘要和程序代码评估。\nxLSTM 在涉及复杂依赖关系和需要在较长时间内保留信息的任务中表现出一致的优势，例如在评估叙述中的时间事件或在模拟真实世界数据流的合成任务中控制长期依赖关系。\n展示 xLSTM 的多功能性 联想回忆是 xLSTM 能力得到严格测试的另一个关键领域。这涉及模型在出现提示或部分输入时正确回忆信息的能力，这是问题解答和基于上下文的检索系统等任务中的常见要求。实验采用了涉及多个查询的关联回忆任务，在这些任务中，模型需要从一组存储的键值对中检索出准确的响应。\n在这些实验中，xLSTM 的新型矩阵记忆和指数门控机制使其能够从大量数据集中出色地调用特定信息。这一点在需要区分和检索罕见标记或复杂模式的任务中尤为明显，展示了 xLSTM 优于传统 RNN 和一些新型 Transformer 变体的内存管理和检索能力。\n这些跨领域的验证工作凸显了 xLSTM 的鲁棒性和适应性，证实了它在自然语言处理技术领域及其他领域作为高效工具的潜力。xLSTM 超越了以前的模型在处理长序列和复杂召回任务方面的局限性，为扩展 LSTM 架构所能达到的目标设定了新的标准。\n最后 xLSTM 集成了指数门控和改进内存结构等先进功能，为基于 LSTM 的架构注入了新的活力。在人工智能领域，它是一种稳健的替代方案，尤其适用于需要高效长期依赖性管理的任务。这一演变表明，递归神经网络的未来大有可为，可增强其在实时语言处理和复杂数据序列预测等各个领域的适用性。\n尽管 xLSTM 有所增强，但它不太可能完全取代 Transformers，后者擅长并行处理和利用广泛注意力机制的任务。相反，xLSTM 将成为 Transformers 的补充，尤其是在要求高内存效率和有效长序列管理的场景中，从而为人工智能语言模型工具包的多样化做出贡献。\n如果大家对 xLSTM 感兴趣，可以关注本公众号「坍缩的奇点」，日后会有更多相关信息及教程。\n","permalink":"https://hivan.me/posts/lstm-%E5%8D%87%E7%BA%A7%E4%BA%86-xlstm-%E6%9D%A5%E6%8C%91%E6%88%98%E7%8E%B0%E7%8A%B6%E4%BA%86/","summary":"多年来，一种名为长短期记忆（LSTM）的神经网络一直是处理文本等序列数据的主要模型。LSTM 早在 20 世纪 90 年代就已问世，它善于记忆长程模式，避免了早期递归网络所面临的 \u0026ldquo;梯度消失\u0026rdquo; 技术问题。这使得 LSTM 在语言建模、文本生成、语音识别等所有语言任务中都具有难以置信的价值。在相当长的一段时间里，LSTM 看起来势不可挡。","title":"LSTM 升级了？ xLSTM 来挑战现状了"},{"content":"\n学习目标 了解从自然语言生成代码对提高开发人员效率的重要性。 了解 Google Gemma 及其在将英文查询转化为代码方面的作用。 探索 Unsloth 对大型语言模型的效率提升和内存管理。 设置使用 Unsloth 高效微调 Google Gemma 的环境。 准备与 Gemma 和 Unsloth 兼容的数据集，以便进行有效的微调。 使用 SFTTrainer 掌握使用特定训练参数对 Google Gemma 进行微调的方法。 使用微调后的 Gemma 根据自然语言提示生成代码。 评估微调后的 Gemma 在软件开发工作流中的性能。 Gemma 简介 Google 开发了一套名为 Google Gemma 的开源大型语言模型。它是在 Google 双子座模型的基础上使用 6T 文本标记进行训练的。这些模型被认为是 Gemini 模型的轻型变体。Gemma 系列有两种规格：用于 CPU 和设备应用的 20 亿参数模型，以及用于 GPU 和 TPU 有效部署的 70 亿参数模型。\nGemma 具有尖端的大规模理解和推理能力，并在文本领域具有很高的天赋。它在各种类别（包括问题解答、常识推理、数学和科学）中的表现都优于其他开放模型，其规模可与之媲美或更大。 Google 为这两种模型的推理和服务发布了微调检查点和开源代码库。在本指南中，我们将使用 Gemma 的 70 亿参数版本。\n什么是 Unsloth？ Daniel Han 和 Michael Han 创建了 Unsloth，并迅速成为为完善大型语言模型（LLM）微调过程而量身定制的优化框架。Unsloth 以其敏捷性和内存效率而闻名，其训练速度可提高 30 倍，内存使用量显著减少 60%。这些令人印象深刻的指标使其成为开发人员寻求精确、快速微调 LLM 的首选框架。\n值得注意的是，Unsloth 支持不同的硬件设置，包括英伟达™（NVIDIA®）Tesla T4 到 H100 等 GPU，并将其兼容性扩展到 AMD 和英特尔 GPU。该库的适应性得益于其采用的开创性技术，其中包括智能权重上投，该功能可在 QLoRA 过程中减少权重上投的必要性，从而优化内存使用。此外，Unsloth 还能迅速利用 bfloat16，提高 16 位训练的稳定性，加快 QLoRA 的微调。\n作为获得 Apache 2.0 授权的开源工具，Unsloth 无缝集成到了 Mistral 7B、Llama 和 Google Gemma 等著名 LLM 的微调中，微调速度提高了 5 倍，同时内存消耗减少了 60%。此外，它还兼容 Flash-Attention 2 等其他微调方法，这不仅加快了推理速度，甚至还加快了微调进程。\n设置环境 首先要准备好 Python 环境，下载并安装必要的库。我们将在 Google Collab 上对 Gemma LLM 进行微调。为此，我们将执行以下命令\n!pip install \u0026#34;unsloth[colab] @ git+https://github.com/unslothai/unsloth.git\u0026#34; 这将在 Colab 环境中安装 unsloth 库。unsloth 旁边的 [colab] 告诉 pip 安装程序在 Google Colab 环境中安装其他支持 unsloth 的库。 这甚至安装了 HuggingFace 的数据集和转换器库。 # Import the FastLanguageModel class from the unsloth library. from unsloth import FastLanguageModel # Import the torch library. import torch # Set the maximum sequence length to 8192 tokens. max_seq_length = 8192 # Set the data type to None for automatic detection. dtype = None # Set the load_in_4bit flag to True to load the model weights in 4-bit precision. load_in_4bit = True unsloth 库中的 FastLanguageModel 类为 Google Colab 提供了大型语言模型的优化实现。 max_seq_length 表示模型在单个序列中可以处理的最大标记数。Gemma 的最大序列长度是 8192，因此初始化长度也是 8192。 dtype 指定模型权重和激活所使用的数据类型。 load_in_4bit 设置为 true 将以 4 位精度加载模型权重。这可以节省内存并提高某些 GPU 的性能，但可能会略微降低精度。 下载 4 位量化模型并添加 LoRA 适配器 在本节中，我们将首先下载 Gemma 模型：\n# Load the pre-trained model from the \u0026#39;unsloth/gemma-7b-bnb-4bit\u0026#39; repository. model, tokenizer = FastLanguageModel.from_pretrained( model_name = \u0026#34;unsloth/gemma-7b-bnb-4bit\u0026#34;, # Set the maximum sequence length to the value defined earlier. max_seq_length = max_seq_length, # Set the data type to the value defined earlier. dtype = dtype, # Set the load_in_4bit flag to the value defined earlier. load_in_4bit = load_in_4bit, ) 这段代码与 FastLanguageModel 类的 from_pretrained() 方法配合使用，从拥抱脸模型中心加载一个预训练模型。 参数 model_name 显示了我们需要加载的模型名称。\nmax_seq_length、dtype 和 load_in_4bit 参数传递给 FastLanguageModel 类的构造函数。这些都是我们已经定义的参数。\n我们可以看到，运行代码后，代码将从 HuggingFace 中的 unsloth huggingface hub 下载 gemma-7b 4 位量化版本。最后，下载量化模型的步骤就完成了。现在，我们需要为此创建一个 LoRA，以便只能训练这些参数的子集。\n代码如下 # Create a PEFT model with the given parameters model = FastLanguageModel.get_peft_model( model, r=16, # LoRa Rank target_modules=[\u0026#34;q_proj\u0026#34;, \u0026#34;k_proj\u0026#34;, \u0026#34;v_proj\u0026#34;, \u0026#34;o_proj\u0026#34;, \u0026#34;gate_proj\u0026#34;, \u0026#34;up_proj\u0026#34;, \u0026#34;down_proj\u0026#34;,], lora_alpha=16, lora_dropout=0, bias=\u0026#34;none\u0026#34;, use_gradient_checkpointing=True ) r 参数决定 LoRA 投影矩阵的秩。它控制着微调所需的参数数量。秩越高，参数越多，性能越好，但模型的内存占用可能会增加 lora_alpha 选项设置 LoRA 投影矩阵的比例。该参数允许在微调过程中调整学习率。 lora_dropout 选项用于设定 LoRA 投影矩阵的滤除率。该参数用于减少过度拟合，提高模型的泛化能力。不过，在进行 Unsloth 优化时，该参数设置为 0。 偏置参数决定是否在 LoRA 投影矩阵中包含偏置分量。设置为 \u0026ldquo;None\u0026rdquo; 意味着不应用偏置项。 use_gradient_checkpointing（梯度检查点）变量设置为 \u0026ldquo;True\u0026quot;，以充分利用梯度检查点。这将加快训练过程 最后，运行这段代码将为 Gemma 7B 模型创建 LoRA 适配器，我们可以利用它在不同类型的数据集上对模型进行微调。\n为微调准备数据集 现在，我们将下载数据集，准备微调。在本指南中，为了生成代码，我们将使用令牌弯代码说明数据集。该数据集采用 alpaca 类型的聊天格式。数据集如下所示：\n我们主要使用 3 列数据，即输入列、指令列和输出列。有了这 3 列数据，我们就可以将其排列成阿尔法卡风格的格式，并在这些数据上训练 Gemma 大语言模型。首先，让我们定义一个辅助函数，用于接收这些数据的每一行，并将其转换为阿尔法卡风格的格式。\ndef formatted_train(x): if x[\u0026#39;input\u0026#39;]: formatted_text = f\u0026#34;\u0026#34;\u0026#34;Below is an instruction that describes a task. \\ Write a response that appropriately completes the request. ### Instruction: {x[\u0026#39;instruction\u0026#39;]} ### Input: {x[\u0026#39;input\u0026#39;]} ### Response: {x[\u0026#39;output\u0026#39;]}\u0026lt;eos\u0026gt;\u0026#34;\u0026#34;\u0026#34; else: formatted_text = f\u0026#34;\u0026#34;\u0026#34;Below is an instruction that describes a task. \\ Write a response that appropriately completes the request. ### Instruction: {x[\u0026#39;instruction\u0026#39;]} ### Response: {x[\u0026#39;output\u0026#39;]}\u0026lt;eos\u0026gt;\u0026#34;\u0026#34;\u0026#34; return formatted_text 该函数接收数据集的每一行，并以相应的 Alpaca 格式返回：\n该函数接收一个参数 x，x 代表 DataFrame 的一行。 它会检查 DataFrame 行的 \u0026ldquo;input\u0026rdquo; 列中的值是否为 true（if x[\u0026lsquo;input\u0026rsquo;]:）。如果是，它将使用 f-strings 创建一个格式化文本块。 如果 \u0026ldquo;input\u0026rdquo; 列为 true，则会构建一个格式化文本块，其中包含指令、输入和响应，每个部分用 markdown 标题（###）分隔。它包括 \u0026ldquo;instruction\u0026rdquo; 列中的指令、\u0026quot;input\u0026rdquo; 列中的输入和 \u0026ldquo;output\u0026rdquo; 列中的输出。 如果 \u0026ldquo;input\u0026rdquo; 列非真值（为空或求值为 False），则会构建一个类似的格式化文本块，但不包括输入部分。 在这两种情况下，格式化文本块的末尾都有一个附加（句末）标记。 最后，函数返回构建的格式化文本块。 从 HuggingFace 下载数据集的函数 接下来，我们创建一个从 HuggingFace 下载数据集的函数，并按以下格式转换数据集。\nfrom datasets import load_dataset, Dataset def prepare_train_data(data_id): data = load_dataset(data_id, split=\u0026#34;train\u0026#34;) data_df = data.to_pandas() data_df[\u0026#34;formatted_text\u0026#34;] = data_df[[\u0026#34;input\u0026#34;, \u0026#34;output\u0026#34;, \u0026#34;instruction\u0026#34;]].apply(formatted_train, axis=1) data = Dataset.from_pandas(data_df) return data data_id = \u0026#34;TokenBender/code_instructions_122k_alpaca_style\u0026#34; data = prepare_train_data(data_id) load_dataset 函数从指定的数据集 ID 中加载数据集并进行分割。 to_pandas 方法会将数据集转换为 pandas 数据帧。 apply 方法将 lambda 函数应用到数据帧中的每一行。 lambda 函数从每一行中获取指令、输入和输出列，并将它们传递给 formatted_train 函数。 formatted_train 函数会返回 Alpaca 格式的格式化聊天模板字符串，我们会将其存储在新的 \u0026ldquo;formatted_text\u0026rdquo; 列中。 Dataset.from_pandas 方法会将数据帧转换回 Dataset 对象。 最后，我们将 data_id 传递给 prepare_train_data 函数。我们从 HuggingFace 下载数据集，对每一行应用指定的更改，然后将生成的 Alpaca 格式文本保存在数据集的 \u0026ldquo;formatted_text\u0026rdquo; 列中。\n这样，我们就完成了用于微调的代码数据集的准备工作。\n微调 Google Gemma 代码数据集 我们现在可以访问数据集进行微调。在本节中，我们将首先定义训练参数，最后对模型进行微调。下面的代码定义了用于微调 Google Gemma 大语言模型的训练参数：\nfrom trl import SFTTrainer from transformers import TrainingArguments trainer = SFTTrainer( model = model, tokenizer = tokenizer, train_dataset = data, dataset_text_field = \u0026#34;formatted_text\u0026#34;, max_seq_length = max_seq_length, dataset_num_proc = 2, packing = False, args = TrainingArguments( per_device_train_batch_size = 2, gradient_accumulation_steps = 4, warmup_steps = 5, max_steps = 10, learning_rate = 2e-4, fp16 = not torch.cuda.is_bf16_supported(), bf16 = torch.cuda.is_bf16_supported(), logging_steps = 1, optim = \u0026#34;paged_adamw_8bit\u0026#34;, weight_decay = 0.01, lr_scheduler_type = \u0026#34;linear\u0026#34;, seed = 3407, output_dir = \u0026#34;outputs\u0026#34;, ), ) 所提供的代码片段使用 Transformers 库中的 TrainingArguments 类为大语言模型配置训练参数。这些参数定义了控制训练过程的不同参数。然后将它们与其他训练参数一起传递给 SFTTrainer 类。\n关键论点分解 下面是培训论据的关键论据的细目：\nper_device_train_batch_size：这表示在每个训练步骤中，每个设备（例如 GPU）处理的训练示例数量。这里设置为 2，即每台设备在每一步中处理 2 个示例。 gradient_accumulation_steps（梯度累积步数）：定义执行参数更新前的梯度累积步数。通过在多个步骤中累积梯度，可以有效增加批次大小。此处设置为 4，表示在更新模型参数之前，梯度将累积 4 个步骤。 warmup_steps（预热步数）：设置训练过程中的热身步数，将学习率从 0 逐步提高到所提供的值。这里设置为 5，因此学习率将在前 5 步中线性增加。 max_steps（最大步数）：这定义了要执行的训练步骤总数。这里设置为 50，意味着训练将在 50 步后停止。 learning_rate（学习率）：这表示用于训练的第一个学习率。这里设置为 2e-4（2 乘以 10 的-4 次方）。 fp16 和 bf16：这些参数控制训练使用的精度。fp16 用于半精度（16 位）训练（如果 GPU 支持），而 bf16 用于 bfloat16 训练（如果 GPU 支持）。 logging_steps（日志记录步数）：设置记录训练指标和损失的时间间隔。我们将其设置为 1，因此每训练一步后都会打印日志。 optim：用于设置训练时使用的优化器。在这里，我们将其设置为 \u0026ldquo;paged_adamw_8bit\u0026quot;，这是一个专门用于节省内存的优化器。 weight_decay（权重衰减）：定义正则化所需的权重衰减率。此处设置为 0.01。 lr_scheduler_type：用于说明在训练过程中使用哪种学习率调度程序。 通过训练论证 最后，我们完成了训练参数的创建。我们将这些训练参数传递给 SFTTrainer 的 args 变量。除了 TrainingArguments 之外，我们还要传递以下参数：\nmodel：这表示要训练的模型。在我们的代码中，它就是前面定义的模型变量。 tokenizer：此处显示用于处理文本数据的标记符。在这里，它是前面定义的 tokenizer 变量。 train_dataset（训练数据集）：这是训练数据集，是包含格式化文本数据的数据变量。 dataset_text_field：此处显示数据集中包含格式化文本的字段名称。此处为 \u0026ldquo;formatted_text\u0026rdquo;。 max_seq_length（最大序列长度）：定义输入和输出序列的最大序列长度。这里设置为 max_seq_length，它是之前定义的一个变量。 dataset_num_proc（数据集数目）：这是对数据进行标记化处理的工作程序数目。这里的值为 2。 packing：这是一个 Bool 值，表示我们是否应该在训练过程中使用序列打包。设置为 false 是因为我们要处理的是较大的数据序列。 args：这是之前创建的训练参数对象，其中包含不同的训练参数。 我们终于完成了用于训练量化的 Gemma 7B 大语言模型的训练器的定义。现在，我们将运行训练器开始训练过程。为此，我们要编写以下命令：\ntrainer_stats = trainer.train() 运行上述程序将启动训练过程。在 Google Colab 中训练该模型可能需要 30 分钟。最后，30 分钟后，将在代码数据集上对模型进行微调：\n使用 Gemma 生成代码 现在，我们将测试在代码数据集上经过微调的 Gemma 7B。在此之前，我们先定义一些辅助函数，以便创建 Alpaca 格式的提示。\ndef format_test(x): if x[\u0026#39;input\u0026#39;]: formatted_text = f\u0026#34;\u0026#34;\u0026#34;Below is an instruction that describes a task. \\ Write a response that appropriately completes the request. ### Instruction: {x[\u0026#39;instruction\u0026#39;]} ### Input: {x[\u0026#39;input\u0026#39;]} ### Response: \u0026#34;\u0026#34;\u0026#34; else: formatted_text = f\u0026#34;\u0026#34;\u0026#34;Below is an instruction that describes a task. \\ Write a response that appropriately completes the request. ### Instruction: {x[\u0026#39;instruction\u0026#39;]} ### Response: \u0026#34;\u0026#34;\u0026#34; return formatted_text 这个函数 format_test() 与我们在数据集处理阶段定义的函数非常相似。唯一不同的是，这次我们只从数据中接收输入和指示，而将输出留给模型来生成。\n让我们尝试用这个函数可视化一个提示例子：\nPrompt = format_test(data[155]) print(Prompt) 现在，让我们引入微调模型，输入这些信息，看看它会产生什么输出。\nPython 代码实现 from transformers import TextStreamer FastLanguageModel.for_inference(model) # Enable native 2x faster inference inputs = tokenizer( [ Prompt ], return_tensors = \u0026#34;pt\u0026#34;).to(\u0026#34;cuda\u0026#34;) text_streamer = TextStreamer(tokenizer) _ = model.generate(**inputs, streamer = text_streamer, max_new_tokens = 512) 从转换器库中导入 TextStreamer 类。TextStreamer 用于一次一个标记地增量生成文本。 使用 FastLanguageModel.for_inference(model) 更快地推理语言模型。 然后，我们使用预先训练好的标记符对所提供的提示进行标记。然后将标记化后的 Prompt 转换为 PyTorch 张量并移动到 GPU。 然后，我们用相同的标记化器初始化一个 TextStreamer 对象。 我们通过向 model.generate() 函数提供 text_streamer、输入和最大新标记来生成新文本。 运行这段代码将流式显示大语言模型生成的输出。 运行这段代码将流式传输大语言模型生成的输出结果。结果如下\n我们看到模型生成了以下代码：\nimport base64 def encrypt(message, key): encoded_message = base64.b64encode(message.encode(\u0026#39;utf-8\u0026#39;)) return encoded_message.decode(\u0026#39;utf-8\u0026#39;) def decrypt(encrypted_message, key): decoded_message = base64.b64decode(encrypted_message.encode(\u0026#39;utf-8\u0026#39;)) return decoded_message.decode(\u0026#39;utf-8\u0026#39;) message = \u0026#34;Hello World!\u0026#34; key = \u0026#34;secret\u0026#34; encrypted_message = encrypt(message, key) print(encrypted_message) decrypted_message = decrypt(encrypted_message, key) print(decrypted_message) 由 Gemma 7B LLM 生成的代码工作得非常好。让我们试着问另一个问题，看看生成的答案。下面是经过微调的 Gemma 7B Large Langage Model 生成的另一个提示及其相应的答案。\n以下是大语言模型为所提供的提示生成的代码：\ndef remove_duplicates(list): seen = set() result = [] for item in list: if item not in seen: result.append(item) seen.add(item) return result list = [1, 1, 2, 3, 4, 4, 5] print(remove_duplicates(list)) # [1, 2, 3, 4, 5] 即使是上述代码也能完美运行。我们看到，仅用 60 个步骤对 Google Gemma 7B 大型语言模型进行微调，就生成了一个良好的代码生成模型。LLM 甚至能够正确理解格式，并以相同的 Alpaca 格式生成响应。\n结尾 将 Google的 Gemma 与 Unsloth 整合用于从自然语言查询生成代码，在提高开发人员的工作效率方面显示出了潜力。Gemma 是一种强大的大型语言模型，可将英语查询转换为复杂的代码语句，而 Unsloth 则可提高训练效率和内存使用率。这种协同作用增强了自然语言处理（NLP）应用中的代码生成能力，促进了新技术的发展，提高了软件开发效率。\n主要收获 Google Gemma 具有强大的语言理解和推理能力，是代码生成任务的最佳选择。 Unsloth 是一个用于微调大型语言模型的优化库，可大大提高训练速度、减少内存并提高整体效率。 创建环境包括安装必要的库和配置序列长度和数据类型等参数。 使用提供的训练参数对 Google Gemma 进行微调，并与 SFTTrainer 类配合使用，有助于在代码数据集上进行高效的模型训练。 使用微调后的 Gemma 生成代码时，需要提供 Alpaca 格式的提示，并与 TextStreamer 类配合进行增量文本生成。 实际示例表明，经过微调的 Gemma 7B 模型能从自然语言提示中准确生成代码响应，显示了它在改进软件开发工作流程方面的潜力。 ","permalink":"https://hivan.me/posts/%E4%BD%BF%E7%94%A8-unsloth-%E5%BE%AE%E8%B0%83-google-gemma/","summary":"\u003ch2 id=\"前言\"\u003e前言\u003c/h2\u003e\n\u003cp\u003e将自然语言查询转化为代码是 NLP 领域最艰巨的挑战之一。将一个简单的英语问题转换成复杂代码的能力为开发人员的工作效率和快速软件开发生命周期提供了多种可能性。这就是开源大语言模型 Google Gemma 发挥作用的地方。本指南将探讨如何使用 unsloth 微调 Google Gemma，以便从自然语言查询生成代码语句。\u003c/p\u003e","title":"使用 Unsloth 微调 Google Gemma"},{"content":"去年找工作时，我不得不写很多封求职信。我用 ChatGPT 来帮忙\u0026ndash;我把已有的求职信复制到提示窗口，再加上我的简历和应聘职位的职位描述，然后让 ChatGPT 来完成剩下的工作。ChatGPT 帮我写出了几份相当不错的初稿，为我赢得了动力，但如果不加选择，它还会给我提供我没有的多年工作经验，并声称我上过我从未上过的学校。\n我之所以提到我的求职信，是因为：\n1）我认为这是一个很好的例子，说明了 LLM 的优缺点，以及为什么 KG 是实施 LLM 的重要组成部分；\n2）这个用例与许多大型企业目前使用 LLM 的用途并无太大区别：自动生成报告。只要在提示中明确包含现有的求职信和职位描述，ChatGPT 就能很好地通过更改内容来重新创建求职信。确保 LLM 有正确的内容就是 KG 的作用所在。如果你只是简单地写 \u0026ldquo;为我想要的工作写一封求职信\u0026rdquo;，结果肯定会让人哭笑不得。此外，求职信的例子也是 LLMs 的绝佳应用，因为它涉及到语言的总结和重组。还记得 LLM 中的第二个 L 代表什么吗？LLMs 历来专注于非结构化数据（文本），这也是他们的强项，而 KG 则擅长整合结构化和非结构化数据。您可以使用 LLM 来撰写求职信，但您应该使用 KG 来确保它具有正确的简历。\n注：我不是人工智能专家，但我也不太相信任何假装自己是专家的人。这个领域变化太快，我们根本无法跟上，更不用说预测未来在企业层面实施人工智能会是什么样子了。在我看来，我将介绍目前整合 KG 和 LLM 的一些方式。这并不是一份全面的清单，我欢迎大家提出补充和建议。\n知识图谱和大型语言模型的兩種關係 目前，KG 和 LLM 有两种互动方式：LLM 是构建知识图谱的工具，而知识图谱则是 LLM 或 GenAI 应用程序的输入。我们这些在知识图谱领域工作的人正处于一个奇怪的境地：我们构建的东西有望改善人工智能应用，而人工智能同时又改变了我们构建这些东西的方式。我们被期望在日常工作中将人工智能作为一种工具进行优化，同时改变我们的产出以促进人工智能的优化。这两种趋势相互关联，而且经常重叠，下面我将逐一讨论。\n利用 LLMs 协助 KG 的创建和策划过程 LLM 是构建 KG 的重要工具。在整理 KG 的过程中利用 LLM 技术的一种方法是将 KG 矢量化（或嵌入）到矢量数据库中。矢量数据库（或矢量存储）是一种用于存储矢量或数字列表的数据库。矢量化即使不是驱动语言模型的核心技术组件，也是其中之一。这些模型通过大量的训练数据，学会将单词与向量联系起来。这些向量会根据训练数据中的上下文捕捉单词的语义和句法信息。通过使用使用这些海量数据训练出来的嵌入服务，我们可以在 KG 中利用这些语义和句法信息。\n注意：将 KG 矢量化绝不是在 KG 整理和构建中使用 LLM 技术的唯一方法。而且，LLM 的这些应用对于 KG 创建来说都不是新鲜事。例如，NLP 用于实体提取已经有几十年的历史了，而 LLM 只是协助本体论/分类学家的一种新能力。\nLLMs 可以通过以下方式帮助创建 KG：\n实体解析：实体解析是对同一现实世界实体的记录进行对齐的过程。例如，对乙酰氨基酚是一种常见的止痛药，在美国以泰诺（Tylenol）品牌销售，而在英国则被称为扑热息痛（paracetamol），以潘那多（Panadol）品牌销售。这四个名称毫无相似之处，但如果将您的 KG 嵌入矢量数据库，矢量就能通过语义理解知道这些实体密切相关。 标记非结构化数据：假设您想将一些非结构化数据纳入 KG。您有一堆文件名模糊的 PDF 文件，但您知道这些文件中有重要的信息。您需要用文件类型和主题来标记这些文档。如果您已经嵌入了主题分类法和文件类型分类法，那么您只需对文件进行矢量化，矢量数据库就会从每个分类法中识别出最相关的实体。 实体和类提取：在非结构化数据语料库的基础上创建或增强本体或分类法等受控词汇。实体提取与标记类似，但这里的目标是增强本体，而不是将非结构化数据纳入 KG。假设您有一个地理本体，并希望在其中填充城镇、城市、省等实例。您可以使用 LLM 从文本语料库中提取实体来填充本体。同样，您也可以使用 LLM 从语料库中提取类和类之间的关系。假设您忘记在本体中包含 \u0026ldquo;资本\u0026rdquo;。LLM 可能会将其提取为一个新类或一个城市的属性。 使用 KG 驱动和管理 GenAI 管道 使用 KG 来支持和管理 GenAI 流水线和应用程序有几个原因。根据 Gartner 的报告，\u0026ldquo;到 2025 年，至少有 30% 的 GenAI 项目将在概念验证 (POC) 之后因数据质量差、风险控制不足、成本上升或业务价值不明确而被放弃\u0026rdquo;。KG 可以帮助提高数据质量、降低风险和成本。\n数据管理、访问控制和法规遵从 只有经过授权的人员和应用程序才能访问某些特定目的的数据。通常，企业希望特定类型的人员（或应用程序）以良好的管理方式与特定类型的数据聊天。如何知道哪些数据应该进入 GenAI 管道？如何确保 PII 不会进入希望与所有员工聊天的数字助理？答案就是数据治理。补充几点：\n政策和法规会发生变化，尤其是在人工智能领域。即使您的人工智能应用程序现在合规，将来也可能不合规。良好的数据治理基础可以让企业适应这些不断变化的法规。 有时，问题的正确答案是 \u0026ldquo;我不知道\u0026rdquo;，或 \u0026ldquo;您无法获得回答该问题所需的信息\u0026rdquo;，或 \u0026ldquo;我回答该问题是非法或不道德的\u0026rdquo;。回答的质量不仅关系到真实性或准确性，还关系到合规性。 实施或启用该解决方案的知名企业（按字母顺序排列）：语义 KG 公司，如 Cambridge Semantics、data.world、PoolParty、metaphacts 和 TopQuadrant，以及数据目录公司，如 Alation、Collibra 和 Informatica（还有很多很多）。 准确性和上下文理解 KG 还有助于提高整体数据质量\u0026ndash;如果您的文档中充斥着相互矛盾和/或虚假的陈述，那么当您的聊天机器人告诉您不一致和虚假的事情时，请不要感到惊讶。如果您的数据结构不佳，将其存储在一个地方也无济于事。数据湖的承诺就是这样变成了数据沼泽的祸害。同样，如果您的数据结构不佳，将其矢量化也不会解决您的问题，只会造成新的头痛问题：矢量化数据沼泽。但是，如果您的数据结构良好，KGs 可以为 LLM 提供额外的相关资源，通过多种方式生成更加个性化和准确的推荐。使用KG提高LLM准确性的方法多种多样，但一般都属于自然语言查询（NLQ）范畴\u0026ndash;使用自然语言与数据库进行交互。据我所知，目前实现自然语言查询的方法有 RAG、提示查询和微调。\n检索增强生成（RAG）：RAG 是指用训练数据之外的其他相关信息对提示进行补充，以生成更准确的回复。虽然 LLM 已在大量数据上接受过训练，但它们尚未在您的数据上接受过训练。想想上面的求职信例子。我可以让 LLM 为茶桁写一封求职信，应聘商汤的产品管理职位\u0026quot;，它会返回一个答案，但其中会包含幻觉。更聪明的做法是，让模型接受这一提示，检索茶桁在 LinkedIn 上的个人资料，检索商汤的空缺职位的职位描述，然后撰写求职信。目前有两种著名的检索方式：将图矢量化或将提示转化为图查询（提示到查询）。\n基于矢量的检索：这种检索方法需要将 KG 矢量化并存储在矢量存储区中。如果您将自然语言提示矢量化，就可以在矢量存储中找到与您的提示最相似的矢量。由于这些向量与图中的实体相对应，因此您可以根据自然语言提示返回图中最 \u0026ldquo;相关 \u0026ldquo;的实体。这与上文描述的标记功能过程完全相同\u0026ndash;我们基本上是用 KG 中的相关标记来 \u0026ldquo;标记 \u0026ldquo;提示。 从提示到查询的检索：或者，您也可以使用 LLM 生成 SPARQL 或 Cypher 查询，并使用该查询从图中获取最相关的数据。注意：您可以使用提示到查询方法直接查询数据库，而不使用查询结果来补充对 LLM 的提示。这不是 RAG 的应用，因为您没有 \u0026ldquo;增强 \u0026ldquo;任何东西。下面将详细介绍这种方法。 关于 RAG 和两种检索方法的其他利弊和说明：\n根据定义，RAG 需要一个知识库。知识图谱就是一个知识库，因此支持知识图谱的人也会支持由图谱驱动的 RAG（有时称为 GraphRAG）。但是，没有知识图谱也可以实现 RAG。 RAG 可以根据提示内容和提示中的元数据，从知识库中提取最相关的数据来补充提示。例如，我们可以根据提问者、他们可以访问的内容以及他们的其他人口统计信息来定制回答。 如上所述，使用基于矢量的检索方法的一个好处是，如果将 KG 嵌入到矢量数据库中进行标记和实体解析，那么困难的部分就已经完成了。查找与提示相关的最相关实体，与用 KG 中的实体标记一大段非结构化文本没有什么区别。 RAG 在一定程度上提供了回复的可解释性。用户现在可以看到提示中的补充数据，以及问题的答案可能就在这些数据中。 我在上文提到，人工智能正在影响我们构建 KG 的方式，同时我们也希望构建的 KG 能够促进人工智能的发展。提示-查询方法就是一个很好的例子。KG 的模式会影响 LLM 的查询能力。如果本体论的目的是为人工智能应用提供信息，那么 \u0026ldquo;最佳 \u0026ldquo;本体论就不再是现实的反映，而是人工智能看待现实的方式的反映。 从理论上讲，更多相关信息应能减少幻觉，但这并不意味着 RAG 能消除幻觉。我们仍在使用语言模型来生成反应，因此仍有很大的不确定性和幻觉空间。即使有我的简历和职位描述， LLMs 也可能会夸大我的经验。对于从文本到查询的方法，我们使用 LLM 来生成 KG 查询和响应，因此实际上有两个地方可能会产生幻觉。 同样，RAG 也提供了一定程度的可解释性，但并非完全如此。例如，如果我们使用基于向量的检索，模型可以告诉我们它包含了哪些实体，因为它们是最相关的，但它无法解释为什么这些实体是最相关的。如果使用自动生成的 KG 查询，自动生成的查询会 \u0026ldquo;解释 \u0026ldquo;为什么某些数据会被图返回，但用户需要了解 SPARQL 或 Cypher 才能完全理解为什么这些数据会被返回。 这两种方法并不相互排斥，许多公司都在同时采用这两种方法。例如，Neo4j 就有通过基于向量的检索实现 RAG 以及生成提示查询的教程。我在写这篇文章之前刚刚参加了一个会议，会议主要关注生命科学领域的 KG 和 LLM 实施，我看到许多生命科学公司都在做基于向量和提示查询 RAG 的某种组合。 实施或启用该解决方案的著名公司（按字母顺序排列）：Data.World、微软、Neo4j、Ontotext、PoolParty、SciBite、Stardog、TopQuadrant（还有很多很多）。 单独的 \u0026ldquo;提示到查询\u0026rdquo;（Prompt-to-query）：使用 LLM 将自然语言查询转化为正式查询（如 SPARQL 或 Cypher）。这与上文描述的 RAG 的 \u0026ldquo;提示-查询 \u0026ldquo;检索方法相同，只是在检索数据后，我们不会将数据发送给 LLM。这里的想法是，通过使用 LLM 生成查询而不是解释数据，可以减少幻觉。不过，如上所述，LLM 生成什么并不重要，它可能包含幻觉。支持这种方法的论点是，与自动生成的响应相比，用户更容易从自动生成的查询中发现幻觉。我对此有些怀疑，因为许多使用 LLM 生成 SPARQL 查询的用户对 SPARQL 的了解可能不足以发现自动生成的查询中存在的问题。\n任何使用提示到查询检索来实施 RAG 解决方案的人也可以单独实施提示到查询。这些解决方案包括Neo4j、Ontotext 和 Stardog。 用于微调 LLM 的 KG：使用你的 KG 为现成的 LLM 提供额外的训练。与其将 KG 数据作为查询时提示（RAG）的一部分，您还不如使用您的 KG 来训练 LLM 本身。这样做的好处是，您可以将所有数据保存在本地，而无需将提示信息发送给 OpenAI 或其他任何人。缺点是 LLM 中的第一个 L 代表大型，因此下载和微调其中一个模型需要耗费大量资源。此外，虽然根据企业或行业特定数据微调的模型会更加准确，但并不能完全消除幻觉。关于这个问题的一些其他想法：\n一旦使用图形对模型进行微调，也就失去了使用图形进行访问控制的能力。 有些 LLM 已经针对不同行业进行了微调，如医疗保健行业的 MedLM 和网络安全行业的 SecLM。 根据使用案例的不同，可能不需要微调 LLM。例如，如果您主要使用 LLM 来总结新闻文章，那么 LLM 可能不需要特别的培训。 有些公司并没有使用行业特定信息对 LLM 进行微调，而是使用经过微调的 LLM 生成代码（如 Code Llama），作为其提示到查询解决方案的一部分。 实施或启用该解决方案的著名公司（按字母顺序排列）：据我所知，Stardog 的 Voicebox 是唯一使用 KG 为客户微调 LLM 的解决方案。 关于我在这里列出的整合 KG 和 LLM 的不同方法的说明：这些类别（RAG、提示查询和微调）既不全面，也不相互排斥。实施幼稚园教育和本地学习管理还有其他方法，今后还会有更多。此外，这些解决方案之间也有相当多的重叠，你可以将它们结合起来。例如，您可以在微调模型上运行基于向量和提示查询的 RAG 混合解决方案。\n效率和可扩展性 构建许多互不关联的独立应用程序是低效的，也是软件荒地。应用程序是否 \u0026ldquo;由人工智能驱动 \u0026ldquo;并不重要。孤立的应用程序会导致重复的数据和代码以及整体冗余。KGs 提供了一个基础，通过整个企业的数据顺畅流动来消除这些冗余。\n许多 GenAI 项目将因成本上升而被放弃，但我不知道 KG 是否能显著降低这些成本。我不知道是否有任何研究或成本效益分析支持这种说法。为企业开发一个由 LLM 驱动的聊天机器人成本高昂，但开发一个 KG 也是如此。\n我不会假装知道 \u0026ldquo;最佳 \u0026ldquo;解决方案，而且，正如我在前面所说，我认为任何假装知道人工智能未来的人都是自欺欺人。我确实相信，对于试图更快地向正确的人提供更多数据的人来说，KG 和 LLM 都是有用的工具，它们各有优缺点。使用 LLMs 来撰写求职信（或监管报告），但使用 KG 来确保提供正确的简历（或研究或期刊论文或其他）。\n一般来说，我认为应尽可能使用人工智能来构建、维护和扩展知识图谱，而且对于希望采用 GenAI 技术的企业来说，知识图谱也是必要的。这有几个原因：数据治理、访问控制和法规遵从；准确性和上下文理解；效率和可扩展性。\n","permalink":"https://hivan.me/posts/%E5%A6%82%E4%BD%95%E5%9C%A8%E4%BC%81%E4%B8%9A%E7%BA%A7%E5%90%8C%E6%97%B6%E5%AE%9E%E6%96%BD%E7%9F%A5%E8%AF%86%E5%9B%BE%E8%B0%B1%E5%92%8C%E5%A4%A7%E5%9E%8B%E8%AF%AD%E8%A8%80%E6%A8%A1%E5%9E%8B-llm/","summary":"大型语言模型（LLM）和知识图谱（KG）是让更多人访问数据的不同方法。知识图谱使用语义学将数据集通过其含义（即它们所代表的实体）连接起来。LLM 使用向量和深度神经网络来预测自然语言。它们通常都以 \u0026ldquo;解锁 \u0026ldquo;数据为目标。对于实施 KGs 的企业来说，最终目标通常是建立数据市场、语义层、使数据 FAIR 化或使企业更加以数据为中心。这些都是不同的解决方案，但最终目标是相同的：让更多数据更快地提供给合适的人。对于实施 LLM 或其他类似 GenAI 解决方案的企业来说，其目标往往是相似的：为员工或客户提供一个 \u0026ldquo;数字助理\u0026rdquo;，以便更快地将正确的信息提供给正确的人。这种潜在的共生关系是显而易见的：LLM 的一些主要弱点，即它们是黑盒模型，难以处理事实知识，而这正是 KG 的一些最大优势。从本质上讲，KG 是事实的集合，而且完全可以解释。但是，在企业中，KGs 和  LLMs  究竟应该如何结合使用呢？","title":"如何在企业级同时实施知识图谱 (KG) 和大型语言模型 (LLM)"},{"content":"您是否遇到过这样的情况：在 Chrome 浏览器中搜索特定书签时，却发现书签数量太多，让您应接不暇？在大量书签中进行筛选变得相当枯燥乏味。\n其实，我们可以把它交给 ChatGPT，它是目前最流行的人工智能模型，基本上可以回答我们提出的所有问题。试想一下，如果它能获取我们的书签，那就太好了！问题就解决了！然后，我们就可以要求它从整个书签存储中给出我们想要的特定链接，就像下面的 GIF 一样：\n为此，我建立了一个 pipeline，将 Chrome 浏览器书签更新到矢量数据库中，ChatGPT 将使用该数据库作为我们提问的上下文。我将在本文中逐步解释如何构建这样一个 pipeline，最终你也会拥有自己的 pipeline ！\n特点 在开始之前，我们先来数数它的优点：\n与传统搜索引擎（如 Chrome 浏览器书签管理器中的搜索引擎）不同，人工智能模型可以很好地理解每个标题的语义。如果你在搜索书签时忘记了准确的关键词，你只需画出一个大概的轮廓，ChatGPT 就能得到它！它甚至能理解不同语言的标题，是不是很厉害？\n一切都会自动更新。每个新添加的书签都会在几分钟内自动反映到人工智能知识数据库中。\npipeline 概览 在这里，您可以看到每个组件在我们的流程中的作用。我们使用定制的 Chrome 浏览器插件将 Chrome 浏览器书签提取到谷歌工作表的行中。Estuary Flow 获取（或捕捉）所有工作表数据，然后通过使用 OpenAI API 的嵌入模型将其矢量化（或实体化）。所有嵌入（每个向量对应工作表的每一行，即每个书签）将被检索并存储到 Pinecone 向量数据库中。之后，用户可以使用 Streamlit 和 LangChain 向内置应用程序发出提示（例如，\u0026ldquo;有 dinov2 的链接吗？）它首先会从 Pinecone 中检索一些类似的嵌入（获取上下文/多个潜在书签选项），然后将它们与用户的问题结合起来，作为 ChatGPT 的输入。然后，ChatGPT 会考虑所有可能的书签，并给出最终答案。这个过程也被称为 RAG：检索增强生成（Retrieval Augmented Generation）。\n在下文中，我们将逐步了解如何构建这样一个 pipeline。\n用于书签检索的 Chrome 浏览器插件 Code: https://github.com/swsychen/Boomark2Sheet_Chromeplugin\n要将书签传输到 Google Sheet 中进行进一步处理，我们需要先创建一个定制的 Chrome 浏览器插件（或扩展）。\n对于 Chrome 浏览器扩展来说，最重要的文件是 manifest.json，它定义了插件的高级结构和行为。在这里，我们添加了使用 Google Chrome 浏览器书签 API 和跟踪书签更改所需的权限。我们还为 oauth2 身份验证设置了一个字段，因为我们将使用 Google Sheet API。您需要在该字段中输入您自己的 client_id。你可以根据此链接(https://developers.google.com/sheets/api/quickstart/js)中的 \u0026ldquo;设置环境 \u0026ldquo;部分获取 client_id 和 Google Sheet API Key（我们稍后会用到）。需要注意的是\n在 OAuth 同意页面，你需要添加自己（Gmail 地址）作为测试用户。否则，你将无法使用 API。 在创建 OAuth 客户端 ID 中，应选择的应用程序类型是 Chrome 浏览器扩展（而不是快速入门链接中的 Web 应用程序）。需要指定的项目 ID 是插件 ID（我们在加载插件时会有它，你可以在扩展管理器中找到它）。 核心功能文件是 background.js，它可以在后台完成所有同步。我已经在 GitHub 链接中为你准备好了代码，你唯一需要修改的就是 javascript 文件开头的电子表格 ID。这个 id 可以在创建的 Google Sheet 的共享链接中找到（在 d/ 之后、/edit 之前，没错，你需要先手动创建一个 Google Sheet！）：\nhttps://docs.google.com/spreadsheets/d/{spreadsheetId}/edit#gid=0\n代码的主要逻辑是监测书签的任何变化，并在触发插件时（例如添加新书签时）用所有书签刷新（clear + write）工作表文件。它会将每个书签的 id、标题和 URL 写入指定 Google Sheet 的单独一行中。\n最后一个文件 popup.html 基本上没什么用，因为它只定义了在 Chrome 浏览器中点击插件按钮时弹出窗口中显示的内容。\n确保所有文件都在一个文件夹中后，就可以上传插件了：\n进入 Chrome 浏览器的 \u0026ldquo;扩展\u0026rdquo;\u0026gt;\u0026ldquo;管理扩展\u0026rdquo;，打开页面右上方的 \u0026ldquo;开发者模式\u0026rdquo;。 点击 \u0026ldquo;加载已解压 \u0026ldquo;并选择代码文件夹。然后你的插件就会上传并运行。点击超链接服务工作者，查看代码打印的日志信息。 上传后，只要 Chrome 浏览器打开，插件就会一直运行。重新打开浏览器时，它也会自动开始运行。\n设置Estuary Flow和Pinecone Estuary Flow 基本上是一个连接器，可以将数据库与你提供的数据源同步。在我们的例子中，当 Estuary Flow 将谷歌工作表中的数据同步到矢量数据库 Pinecone 中时，它还会调用嵌入模型，将数据转换为嵌入矢量，然后存储到 Pinecone 数据库中。\n关于 Estuary Flow 和 Pinecone 的设置，YouTube 上已经有一个相当全面的视频教程：https://youtu.be/qyUmVW88L_A?si=xZ-atgJortObxDi-。\n但请注意因为 Estuary Flow 和 Pinecone 正在快速开发中。视频中的某些内容现在已经有所改变，可能会引起混淆。在此，我列出了视频中的一些更新，以便您可以轻松复制所有内容：\n1.(Estuary Flow\u0026gt;create Capture）在行批量大小中，您可以根据 Google Sheet 中书签的总行数设置一些较大的数字。(例如，如果你已经有 400 多行书签，则可将其设置为 600 行）\n2.(Estuary Flow\u0026gt;create Capture）在设置目标收藏时，删除光标字段 \u0026ldquo;row_id \u0026ldquo;并添加新字段 \u0026ldquo;ID\u0026rdquo;，如下截图所示。命名空间可以保持为空。\n3.(Estuary Flow\u0026gt;create Capture）然后切换到 COLLECTION 子标签，按 EDIT 键将 Key 从 /row_id 改为 /ID。还应将模式代码中的 \u0026ldquo;必填 \u0026ldquo;字段改为 \u0026ldquo;ID\u0026rdquo;，如下所示：\n//...skipped \u0026#34;URL\u0026#34;: { \u0026#34;type\u0026#34;: \u0026#34;string\u0026#34; }, \u0026#34;row_id\u0026#34;: { \u0026#34;type\u0026#34;: \u0026#34;integer\u0026#34; } }, \u0026#34;required\u0026#34;: [ \u0026#34;ID\u0026#34; ], \u0026#34;type\u0026#34;: \u0026#34;object\u0026#34; } ​\tSAVE AND PUBLISH \u0026ldquo;后，您可以看到 Collections\u0026gt;{your collection name}\u0026gt;Overview\u0026gt;Data Preview 将显示每个书签的正确 ID。\n(Estuary Flow\u0026gt;create Capture）在最后一步，您可以看到高级规格编辑器（在页面底部）。在这里，您可以添加一个字段 \u0026ldquo;interval\u0026rdquo;：10m \u0026ldquo;字段，将刷新率降至每 10 分钟一次（如果未指定，默认设置为每 5 分钟一次）。每次刷新都会调用 OpenAI 嵌入模型重做所有嵌入，这将花费一定的成本。降低刷新率可以节省一半的费用。您可以忽略 \u0026ldquo;backfill\u0026quot;字段。\n//...skipped \u0026#34;syncMode\u0026#34;: \u0026#34;full_refresh\u0026#34; }, \u0026#34;target\u0026#34;: \u0026#34;CJQ/mybookmark/bookmarks_v3\u0026#34; } ], \u0026#34;interval\u0026#34;: \u0026#34;10m\u0026#34; } 5.（Estuary Flow\u0026gt;create Materialization）Pinecone 环境通常为 \u0026ldquo;gcp-starter\u0026rdquo;，用于自由层的 Pinecone 索引，或者类似于 \u0026ldquo;us-east-1-aws\u0026rdquo;，用于标准计划用户（我不在 Pinecone 中使用无服务器模式，因为 Estuary Flow 尚未为 Pinecone 无服务器模式提供连接器）。Pinecone 索引是在 Pinecone 中创建索引时的索引名称。\n6.(Estuary Flow\u0026gt;create Materialization）下面是一些比较棘手的部分。\n- 首先，应使用蓝色按钮 \u0026ldquo;SOURCE FROM CAPTURE\u0026quot;选择源捕获，然后将 \u0026ldquo;CONFIG\u0026quot;中的 Pinecone 命名空间留空（Pinecone 的免费层必须有一个空的命名空间）。 - 其次，按下 \u0026ldquo;NEXT \u0026ldquo;后，在出现的物化高级规范编辑器中，必须确保 \u0026ldquo;bindings\u0026quot;字段不是空的。如果是空的或字段不存在，请按下面的截图填写内容，否则不会向 Pinecone 发送任何内容。此外，你还需要使用自己的收藏路径（与上一张截图中的 \u0026ldquo;target\u0026quot;相同）更改 \u0026ldquo;source\u0026quot;字段。如果按下 \u0026ldquo;NEXT \u0026ldquo;键后，在看到编辑器之前出现了一些错误，请再按一次 \u0026ldquo;NEXT \u0026ldquo;键，然后就能看到高级规范编辑器了。然后就可以指定 \u0026ldquo;bindings\u0026rdquo;，并按下 \u0026ldquo;SAVE AND PUBLISH\u0026rdquo;。完成这一步后，一切都应该没问题了。出现错误是因为我们之前没有指定 \u0026ldquo;bindings\u0026rdquo;。 - 如果在发布所有内容并返回 \u0026ldquo;目的地 \u0026ldquo;页面后出现另一条错误信息，告诉你没有添加集合，只要你在概览直方图中看到使用量不为零（见下面的截图），就可以忽略它。直方图基本上表示向 Pinecone 发送了多少数据。\n\u0026#34;bindings\u0026#34;: [ { \u0026#34;resource\u0026#34;: {}, \u0026#34;source\u0026#34;: \u0026#34;CJQ/mybookmark/bookmarks_v3\u0026#34;, \u0026#34;fields\u0026#34;: { \u0026#34;recommended\u0026#34;: true } } ], 7.（Pinecone\u0026gt;create index）Pinecone 推出了无服务器索引模式（免费，但 Estuary Flow 尚不支持），但我在本项目中没有使用它。在这里，我们仍然使用基于 pod 的选项（上次检查是在 2024 年 4 月 14 日，现在已不再免费），它足以满足我们的书签嵌入存储需求。创建索引时，只需设置索引名称和尺寸。\n8.(Pinecone\u0026gt;Indexes\u0026gt;{Your index}）完成 Pinecone 索引的创建，并确保在 Estuary Flow 的实体化中正确填写了索引名称和环境后，就大功告成了。在 Pincone 控制台中，转到 Indexes\u0026gt;{Your index}，就能看到显示书签总数的矢量计数。可能需要几分钟时间，直到 Pinecone 收到 Estuary Flow 的信息并显示正确的矢量计数。\n使用 Streamlit 和 Langchain 构建自己的应用程序 代码: https://github.com/swsychen/BookmarkAI_App\n我们就快成功了！最后一步是创建一个漂亮的界面，就像最初的 ChatGPT 一样。在这里，我们使用了一个名为 Streamlit 的非常方便的框架，只需几行代码就能创建一个应用程序。Langchain 也是一个用户友好型框架，可以用最少的代码使用任何大型语言模型。\n我还为你准备了本应用程序的代码。请按照 GitHub 链接中的安装和使用指南操作，尽情享受吧！\n代码的主要逻辑如下\n获取用户提示 → 使用 ChatGPT 和 Pinecone 创建一个检索链 → 向检索链输入提示并获取响应 → 将结果流式传输到用户界面\n请注意，由于 Langchain 处于开发阶段，如果您使用的版本与 requirements.txt 中所述版本不同，代码可能会被弃用。如果您想深入研究 Langchain 并使用其他 LLM 进行书签搜索，请随时查阅 Langchain 的官方文档(https://python.langchain.com/docs/get_started/introduction/)。\n","permalink":"https://hivan.me/posts/%E5%A6%82%E4%BD%95%E5%88%9B%E5%BB%BA%E8%87%AA%E5%B7%B1%E7%9A%84%E4%BA%BA%E5%B7%A5%E6%99%BA%E8%83%BD%E4%B9%A6%E7%AD%BE%E6%90%9C%E7%B4%A2%E5%8A%A9%E6%89%8B/","summary":"「基于 GPT 的书签自动搜索 pipeline 分步指南」\n\u003cimg alt=\"img\" loading=\"lazy\" src=\"https://miro.medium.com/v2/resize:fit:700/1*bEPnFU7QPsJF-VkVx9FWgA.png\"\u003e","title":"如何创建自己的人工智能书签搜索助手？"},{"content":"以上的年会开放营的设计示意图本来我是想稍加修改一下再传到自己的 Flickr 上的！结果一直到今天开幕都没有太多时间来做这件事情，其实这篇博客都应该是昨天晚上就生产出来的才对。\n今年的年会与以前的年会有所不同，没有设置会场，而启用了“云智慧”的概念。而 Google+中的会议视频以及才推出没多久的 Events 为年会此种形式提供了可行性！\n第一天的会议主题为“公民媒体”，可以在如下两个地址内参与讨论：\n+《云平行会： 麦康瑞在全球之声 2012 峰会》\n+《云访谈：老虎庙和佐拉谈公民记者》\nGoogle+上的 Events 讨论地址：中文网志年会 2012 Events\n还请各位中文 Blogger 们积极参加，因为要翻墙，So,记得戴套，或自备安全工具！你们懂得。\n","permalink":"https://hivan.me/posts/cnbloggercon-20/","summary":"一年一度的中文网志年会与今晨八点开始，不过没想到的是昨晚就已经完成了开幕式。在 Google+上由\u003ca href=\"https://plus.google.com/u/0/108255121787158995006/posts\"\u003eIsaac Mao\u003c/a\u003e主持开幕 Hangout 视频。可惜我没有看到！ \u003cimg alt=\"CBC ShangHai\" loading=\"lazy\" src=\"http://farm8.staticflickr.com/7248/7482001638_741df3dc29_z.jpg\"\u003e","title":"CnBloggerCon 2012"},{"content":"同样一张照片，我们先使用 Enlight 进行修图\n然后使用 Snapseed 2 修图\n以下是使用 Metapho 查看的照片信息。\n首先我们来看 Enlight 导出的照片信息: 可以看到 Camera 和 Location 信息都没有了，照片被导出成 PNG 格式，而且照片大了很多。 2. 我们再来看看 Snapseed 导出的照片信息\n可以看到 Camera 和 Location 信息都保存完好。\n这也是 Enlight 最让我无法接受的一点，其实 Enlight 在设置里是可以选择增加地理信息的，也就是会调用 GPS 模块，但是当导出的时候这些信息就全都没有了，即便是用 Enlight 来拍照。不知道下一版本的 Enlight 是否能够改善这点。如果对这些信息无所谓的同学，Enlight 倒是现今为止最强大的修图工具，Snapseed 虽然更新到版本 2 了，但是似乎依然没有加入曲线。Snapseed2 的具体更新，可以点击原文链接查看「领客」内的文。\n———\n","permalink":"https://hivan.me/posts/enlight-vs-snapseed2/","summary":"![image][1]\n[本文知乎专栏地址][2]\n在 Enlight 出来之前，Snapseed 曾经是我最主要的修图工具。\n当然，Enlight 确实是大而全的一款 App，但是使用中，却让我有了无法忍受的一点。那就是丢失部分图片信息。\n导出的时候，Enlight 导出所用的格式是 PNG，而 Snapseed 仍然是 jpg 导出。自然大家都知道 PNG 图片所用的格式是无损的，这就是说，Snapseed 导出的图像相比 Enlight 要小很多，因为 iPhone 默认拍照所保存的都是 jpg 格式，所以即便再导出 PNG 格式，那所增加的部分应该是软件通过算法添加进去的。 而，Enlight 在导出的 PNG 格式图片里，却丢失了相机信息，地理位置信息和更重要的拍照时间信息。照片的时间被导出的时间所代替。","title":"enlight VS snapseed2"},{"content":"勇敢的心，1995 年电影界最成功影片。先不去评论其他技术上的细节。我只是为一个名族英雄折服。freedom。\n中国古话里就说过：生命诚可贵，爱情价更高，若为自由故，两者皆可拋。\n不知道从什么时候开始，这句话被滥用了。人们总是以此来津津乐道自由的重要。歪曲的道理不能称之为道理。这里的自由，我根本不原意理解成为个人的自由。至少国有国法，家有家规这话我还不会去颠覆它。\n任何环境总是有规矩才会成方圆，才会有乐趣。\n自由，更深层的含义是民族上的。。。为本民族的自由而战，豪情万丈。所以梅尔.吉普森最后的”freedom”如此震撼人心。。\n至此，我仍然相信，民族利益高于一切。一切政治上的形式主义都可以扔到一边。for my people.I will…\n","permalink":"https://hivan.me/posts/freedom/","summary":"许很多的影片我都应该从新温习一遍。不只是因为我学的就是这个，更重要的是每次看一遍都能理解一些新的东西。","title":"FREEDOM"},{"content":"\n","permalink":"https://hivan.me/posts/king-of-pop-2014/","summary":"I MISS U.","title":"King of pop 2014"},{"content":"本文知乎专栏\n还记得之前介绍过的 Knock 么？只需要敲击两下就能快速解锁 Mac 的 app。\n其实这款 App 还是 @Rachel 介绍给我的，当时就觉得很酷，可是用下来之后，并没有觉得有很高的实用性。\n不过，这次 Knock 升级了，除了解锁 Mac 之外，还可以再你需要 root 权限的时候免去输入管理员密码的麻烦，你所需要的，仅仅是敲击两下你的 iPhone。\n好吧，现在可以为自己的 Mac 设定一个超级复杂的密码了。\n","permalink":"https://hivan.me/posts/knock-update/","summary":"\u003cp\u003e\u003ca href=\"http://zhuanlan.zhihu.com/hivandu/19984977\"\u003e本文知乎专栏\u003c/a\u003e\u003c/p\u003e\n\u003cp\u003e还记得之前介绍过的 Knock 么？只需要敲击两下就能快速解锁 Mac 的 app。\u003c/p\u003e","title":"Knock 升级 -- 快速输入管理员密码"},{"content":"?\u0026gt; 详情可见作者说明\n安装 ARM 版本 Homebrew 必须安装在/opt/homebrew路径下\ncd /opt sudo mkdir homebrew sudo curl -L https://github.com/Homebrew/brew/tarball/master | tar xz --strip 1 -C homebrew 如果不进行 sudo 授权，则会报错；\n环境变量 本人使用zsh, 所以编辑文件~/.zshrc. 添加如下内容：\npath=(\u0026#39;/opt/homebrew/bin\u0026#39; $path) export PATH ?\u0026gt; 如果是使用bash，请修改~/.bashrc\n在终端内执行:\nsource ~/.zshrc 现在可以试试执行brew install graphviz试试看能否正常安装回归树可视化模块；\n软件包和迁徙 软件包依然需要使用 X86 版 Homebrew\narch -x86_64 启用一个 X86 模式中端，之后运行的命令都在 X86 模式下运行，再次安装 Homebrew\n/bin/bash -c \u0026#34;$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/HEAD/install.sh)\u0026#34; !\u0026gt; 注意：要将 ARM 版本 Homebrew 环境变量设置到最前面，此时两个版本共存时会有限启动 ARM 版本，需要运行 X86 版本时，需要手动输入完整路径arch -x86_64 /usr/local/bin/brew\n可以在配置文件中设置alias\nabrew=\u0026#39;/opt/homebrew/bin/brew\u0026#39; # ARM Homebrew ibrew=\u0026#39;arch -x86_64 /usr/local/bin/brew\u0026#39; # X86 Homebrew 如果对已有软件包做迁徙，则：\nibrew bundle dump 此时在目录下就得到一个名为Brewfile的备份文件，导入内容并安装\nabrew bundle --file /path/to/Brewfile !\u0026gt; 执行之前需要编辑Brewfile文件，将cask和mas开头的记录删除掉；\n","permalink":"https://hivan.me/posts/m1_install_homebrew/","summary":"\u003cp\u003e?\u0026gt; 详情可见\u003ca href=\"https://github.com/Homebrew/brew/issues/7857#issue-647960270\"\u003e作者说明\u003c/a\u003e\u003c/p\u003e\n\u003ch2 id=\"安装\"\u003e安装\u003c/h2\u003e\n\u003cp\u003eARM 版本 Homebrew 必须安装在\u003ccode\u003e/opt/homebrew\u003c/code\u003e路径下\u003c/p\u003e\n\u003cdiv class=\"highlight\"\u003e\u003cpre tabindex=\"0\" style=\"color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;\"\u003e\u003ccode class=\"language-shell\" data-lang=\"shell\"\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003ecd /opt\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003esudo mkdir homebrew\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003esudo curl -L https://github.com/Homebrew/brew/tarball/master | tar xz --strip \u003cspan style=\"color:#ae81ff\"\u003e1\u003c/span\u003e -C homebrew\n\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\u003cp\u003e如果不进行 sudo 授权，则会报错；\u003c/p\u003e\n\u003ch2 id=\"环境变量\"\u003e环境变量\u003c/h2\u003e\n\u003cp\u003e本人使用\u003ccode\u003ezsh\u003c/code\u003e, 所以编辑文件\u003ccode\u003e~/.zshrc\u003c/code\u003e. 添加如下内容：\u003c/p\u003e\n\u003cdiv class=\"highlight\"\u003e\u003cpre tabindex=\"0\" style=\"color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;\"\u003e\u003ccode class=\"language-shell\" data-lang=\"shell\"\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003epath\u003cspan style=\"color:#f92672\"\u003e=(\u003c/span\u003e\u003cspan style=\"color:#e6db74\"\u003e\u0026#39;/opt/homebrew/bin\u0026#39;\u003c/span\u003e $path\u003cspan style=\"color:#f92672\"\u003e)\u003c/span\u003e \n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003eexport PATH\n\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\u003cp\u003e?\u0026gt; 如果是使用\u003ccode\u003ebash\u003c/code\u003e，请修改\u003ccode\u003e~/.bashrc\u003c/code\u003e\u003c/p\u003e\n\u003cp\u003e在终端内执行:\u003c/p\u003e\n\u003cdiv class=\"highlight\"\u003e\u003cpre tabindex=\"0\" style=\"color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;\"\u003e\u003ccode class=\"language-shell\" data-lang=\"shell\"\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003esource ~/.zshrc\n\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\u003cp\u003e现在可以试试执行\u003ccode\u003ebrew install graphviz\u003c/code\u003e试试看能否正常安装回归树可视化模块；\u003c/p\u003e\n\u003ch2 id=\"软件包和迁徙\"\u003e软件包和迁徙\u003c/h2\u003e\n\u003cp\u003e软件包依然需要使用 X86 版 Homebrew\u003c/p\u003e\n\u003cdiv class=\"highlight\"\u003e\u003cpre tabindex=\"0\" style=\"color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;\"\u003e\u003ccode class=\"language-shell\" data-lang=\"shell\"\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003earch -x86_64\n\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\u003cp\u003e启用一个 X86 模式中端，之后运行的命令都在 X86 模式下运行，再次安装 Homebrew\u003c/p\u003e\n\u003cdiv class=\"highlight\"\u003e\u003cpre tabindex=\"0\" style=\"color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;\"\u003e\u003ccode class=\"language-shell\" data-lang=\"shell\"\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e/bin/bash -c \u003cspan style=\"color:#e6db74\"\u003e\u0026#34;\u003c/span\u003e\u003cspan style=\"color:#66d9ef\"\u003e$(\u003c/span\u003ecurl -fsSL https://raw.githubusercontent.com/Homebrew/install/HEAD/install.sh\u003cspan style=\"color:#66d9ef\"\u003e)\u003c/span\u003e\u003cspan style=\"color:#e6db74\"\u003e\u0026#34;\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\u003cp\u003e!\u0026gt; 注意：要将 ARM 版本 Homebrew 环境变量设置到最前面，此时两个版本共存时会有限启动 ARM 版本，需要运行 X86 版本时，需要手动输入完整路径\u003ccode\u003earch -x86_64 /usr/local/bin/brew\u003c/code\u003e\u003c/p\u003e","title":"M1 安装 Homebrew(ARM)"},{"content":"显示文件： defaults write com.apple.finder AppleShowAllFiles -bool true\n隐藏文件 defaults write com.apple.finder AppleShowAllFiles -bool false\n记得 KillAll Finder 来重启 Finder ","permalink":"https://hivan.me/posts/macxian-shi-yin-cang-wen-jian/","summary":"转载自\u003ca href=\"http://mac.linsheng.me/archives/537.html\"\u003eMac 疯\u003c/a\u003e，记录而已，不长用，老是记不住，每次都要 Google，郁闷！","title":"Mac 显示隐藏文件"},{"content":"deb http://mirrors.163.com/ubuntu/ karmic main restricted universe multiverse deb http://mirrors.163.com/ubuntu/ karmic-security main restricted universe multiverse deb http://mirrors.163.com/ubuntu/ karmic-updates main restricted universe multiverse deb http://mirrors.163.com/ubuntu/ karmic-proposed main restricted universe multiverse deb http://mirrors.163.com/ubuntu/ karmic-backports main restricted universe multiverse deb-src http://mirrors.163.com/ubuntu/ karmic main restricted universe multiverse deb-src http://mirrors.163.com/ubuntu/ karmic-security main restricted universe multiverse deb-src http://mirrors.163.com/ubuntu/ karmic-updates main restricted universe multiverse deb-src http://mirrors.163.com/ubuntu/ karmic-proposed main restricted universe multiverse deb-src http://mirrors.163.com/ubuntu/ karmic-bac. kports main restricted universe multiverse $ sudo apt-get update and $ sudo apt-get upgrade\nand hosts $ gedit /etc/hosts # My Dropbox have new hosts file\nabout ATI Donload form This link\ncd ** sudo sh **.run That\u0026rsquo;s ok\nabout Keepass 2 so\u0026hellip;apt-get\n$ sudo apt-add-repository ppa:jtaylor/keepass $ sudo apt-get update $ sudo apt-get install keepass2 10.04 所需软件包\n1 install mono #[link](http://mono-project.com/DistroPackages/Ubuntu)\nClick on \u0026#34;System\u0026#34;, \u0026#34;Administration\u0026#34;, \u0026#34;Software Sources\u0026#34;. Click on the \u0026#34;Other Software\u0026#34; tab. Click on \u0026#34;Add...\u0026#34;, and enter the line:**deb http://badgerports.org lucid main** Click on \u0026#34;Add Source\u0026#34; Click on \u0026#34;Authentication\u0026#34;, then on \u0026#34;Import Key File\u0026#34; Download this [GPG key file](http://badgerports.org/directhex.ppa.asc), ID 0E1FAD0C, and select it in the \u0026#34;Import Key File\u0026#34; window Click on \u0026#34;Close\u0026#34;, then \u0026#34;Reload\u0026#34; when the pop-up appears. You\u0026#39;re all set! 2 $ sudo apt-add-repository ppa:jtaylor/keepass\n3 $ sudo apt-get update\n4 $ sudo apt-get install keepass2\nabout Java Download from This link\nand\n$ cd ** $ tar **.tar.gz(64bit) $ sudo update-alternatives --install \u0026#34;/usr/bin/java\u0026#34; \u0026#34;java\u0026#34; \u0026#34;/home/hivan/software/jdk1.7.0_04/bin/java\u0026#34; 1 $ sudo update-alternatives --config java 其实就是配置一下默认路径就 OK 了！\n或者以下方法：\n$ gedit ~/.bashrc\n末尾添加变量\nJAVA_HOME=/home/hivan/software/jdk1.7.0_04 JRE_HOME=${JAVA_HOME}/jre CLASSPATH=.:${JAVA_HOME}/lib:${JRE_HOME}/lib PATH=${JAVA_HOME}/bin:$PATH and\n$ source ~/.bashrc $ sudo update-alternatives --install /home/hivan/software/jdk1.7.0_04/bin/java 300 $ sudo update-alternatives --install /home/hivan/software/jdk1.7.0_04/bin/javac 300 $ sudo update-alternatives --install /home/hivan/software/jdk1.7.0_04/bin/jar 300 $ sudo update-alternatives --config java #用于替换 Java，可能会跳出选择框 $ java -version #测试 java version \u0026#34;1.7.0_04-ea\u0026#34; Java(TM) SE Runtime Environment (build 1.7.0_04-ea-b19) Java HotSpot(TM) 64-Bit Server VM (build 23.0-b20, mixed mode) about goagent $ cd $ mkdir bin $ cd bin 建立一个 proxy.sh file $ gedit proxy.sh 输入： python /***/goagent/local/proxy.py save and quit $ chmod 700 proxy.sh about ruby $ sudo apt-get install apache2 curl git libmysqlclient-dev mysql-server nodejs $ bash -s stable \u0026lt; \u0026lt;(curl -s https://raw.github.com/wayneeseguin/rvm/master/binscripts/rvm-installer) $ echo \u0026#39;[[ -s \u0026#34;$HOME/.rvm/scripts/rvm\u0026#34; ]] \u0026amp;\u0026amp; . \u0026#34;$HOME/.rvm/scripts/rvm\u0026#34; # Load RVM function\u0026#39; \u0026gt;\u0026gt; ~/.bashrc $ source .bashrc $ rvm requirements $ sudo apt-get install build-essential openssl libreadline6 libreadline6-dev curl git-core zlib1g zlib1g-dev libssl-dev libyaml-dev libsqlite3-0 libsqlite3-dev sqlite3 libxml2-dev libxslt-dev autoconf libc6-dev ncurses-dev automake libtool bison subversion $ rvm install 1.9.3 $ rvm 1.9.3 –-default #有可能出错**RVM is not a function, selecting rubies with \u0026#39;rvm use ...\u0026#39; will not work.** 则执行：`$ rvm alias create default 1.9.3 about Rails $ gem install bundler rails rdoc #rdoc 为 Octopress 所需组建 $ rails -v #如果“**程序“rvm”尚未安装。**”则检查一下.bashrc 的路径配置 about python $ python -V Python 2.6.6 $ curl -kL http://github.com/utahta/pythonbrew/raw/master/pythonbrew-install | bash $ . $HOME/.pythonbrew/etc/bashrc $ pythonbrew install 2.7.1 $ pythonbrew switch 2.7.1 Switched to Python-2.7.1 $ python -V Python 2.7.1 ubuntu 12 中默认是 2.7，如果要安装 3.2 和上述步骤一样！改一下版本号\nabout Sublime Text 2 #!/usr/bin/env xdg-open [Desktop Entry] Name=Sublime Text 2 Comment=Sublime Text 2 Exec=/home/hivan/software/\u0026#34;Sublime Text 2\u0026#34;/sublime_text Icon=/home/hivan/software/Sublime Text 2/Icon/128X128/sublime_text.png Terminal=false Type=Application Categories=Application;Development; StartupNotify=true 设置权限：可执行文件！\nand\n$ cd /home/hivan/software/\u0026#34;Sublime Text 2\u0026#34;/ $ sudo cp \u0026#34;Sublime Text 2.desktop\u0026#34; /usr/share/applications Ctrl+`\nimport urllib2,os;pf=\u0026#39;Package Control.sublime-package\u0026#39;;ipp=sublime.installed_packages_path();os.makedirs(ipp) if not os.path.exists(ipp) else None;open(os.path.join(ipp,pf),\u0026#39;wb\u0026#39;).write(urllib2.urlopen(\u0026#39;http://sublime.wbond.net/\u0026#39;+pf.replace(\u0026#39; \u0026#39;,\u0026#39;%20\u0026#39;)).read()) Ctrl+Shift+P\n1 ZenCoding\n2 Alignment\n3 Markdown\n4 setting user\n{ \u0026ldquo;ignored_packages\u0026rdquo;: [] }\ndownload setting from this link\nFinally, input\nright, you can only choose scim,so\u0026hellip;\n$ sudo apt-get install scim $ sudo apt-get install scim-pinyin \u0026hellip;\nscim 设置－\u0026gt;全局设置－\u0026gt;将预编辑字符串嵌入到客户端中勾去掉 scim 设置-\u0026gt;gtk-\u0026gt;嵌入式候选词标 勾去掉 about Retext sudo add-apt-repository ppa:mitya57 sudo apt-get update sudo apt-get install retext or This link\nabout Octopress Go to This Link\nadd and remove ppa\n$ sudo add-apt-repository ppa:name/name $ sudo add-apt-repository -r ppa:name/name Temporary end, to be continued\u0026hellip;\n","permalink":"https://hivan.me/posts/note-about-my-ubuntu/","summary":"\u003cp\u003e这是我为了便于自己以后方便而记录的一些关于我 ubuntu 上设置所需要的内容！当然其中内容都是通用的，只是如果你们要拿去用的话记得将路径以及一些变量变成你自己的！\u003c/p\u003e\n\u003ch3 id=\"about-system\"\u003eabout system\u003c/h3\u003e\n\u003cp\u003e\u003ccode\u003e$ sudo gedit /etc/apt/sources.list\u003c/code\u003e\u003c/p\u003e\n","title":"Note about my ubuntu"},{"content":" update at 2021-09-07:\nInstall xgboost on Apple M1 git clone --recursive https://github.com/dmlc/xgboost mkdir xgboost/my_build cd xgboost/my_build CC=gcc-11 CXX=g++-11 cmake .. make -j4 cd ../python_package /Users/xx/miniforge3/envs/tf/bin/python setup.py install u must install miniforge for M1, conda create -n tf python=3.9.5\nRun xgboost In the process of using xgboost, I encountered a small obstacle, that is, xgboost cannot be run normally on the M1 of the Mac. It needs to be tossed. The following is the installation process:\n1. Homebrrew is required first 2. Install gcc and cmake brew install gcc brew install cmake brew install libomp 3. Download xgboost package Yes, you cannot use the network package to install, you need to download, compile and install by yourself. Fortunately, the process is not troublesome:\nSource: http://mirrors.aliyun.com/pypi/simple/xgboost/\nI downloaded xgboost-1.4.2.tar.gz\n4. Installation Enter cd ~/download/\nrun\npip install xgboost-1.4.2.tar.gz Okay, you can introduce it to try\nfrom xgboost import XGBClassifier xb = XGBClassifier() xgboost Regression load data import pandas as pd import warnings %pylab inline warnings.filterwarnings(\u0026#39;ignore\u0026#39;) # load data from url df = pd.read_csv(\u0026#39;./data/Titanic.txt\u0026#39;, sep=\u0026#39;,\u0026#39;, quotechar=\u0026#39;\u0026#34;\u0026#39;, encoding=\u0026#39;ISO 8859-15\u0026#39;) df.info() df.head() # Filter some features features = df[[\u0026#39;pclass\u0026#39;, \u0026#39;age\u0026#39;, \u0026#39;sex\u0026#39;]] # Label label = df[\u0026#39;survived\u0026#39;] features.info() # Missing values ​​are filled with mean features[\u0026#39;age\u0026#39;].fillna(df[\u0026#39;age\u0026#39;].mean(), inplace=True) features.info() # Divide the dataset from sklearn.model_selection import train_test_split train_x, test_x, train_y, test_y = train_test_split(features, label, test_size = 0.25, random_state=33) # Feature vectorization from sklearn.feature_extraction import DictVectorizer vec = DictVectorizer(sparse = False) train_x = vec.fit_transform(train_x.to_dict(orient=\u0026#39;record\u0026#39;)) test_x = vec.transform(test_x.to_dict(orient=\u0026#39;record\u0026#39;)) # Random forest training and prediction from sklearn.ensemble import RandomForestClassifier rfc = RandomForestClassifier() rfc.fit(train_x, train_y) print(\u0026#39;The accuracy of random Forest Classifier on testing set:\u0026#39;, rfc.score(test_x, test_y)) \u0026#34;\u0026#34;\u0026#34; The accuracy of random Forest Classifier on testing set: 0.7781155015197568 \u0026#34;\u0026#34;\u0026#34; # xgboost training and prediction from xgboost import XGBClassifier xb = XGBClassifier() xb.fit(train_x, train_y) print(f\u0026#39;The accuracy:\u0026#39;, xb.score(test_x, test_y)) \u0026#34;\u0026#34;\u0026#34; The accuracy: 0.7750759878419453 \u0026#34;\u0026#34;\u0026#34; ","permalink":"https://hivan.me/posts/run_xgboost_on_m1_and_regression/","summary":"\u003cblockquote\u003e\n\u003cp\u003eThe source code: \u003ca href=\"https://github.com/hivandu/practise/blob/master/improvement/xgboost_regression.ipynb\"\u003exgboost_regression\u003c/a\u003e\u003c/p\u003e\u003c/blockquote\u003e","title":"Run xgboost on Mac and Regression data"},{"content":"sketch 也不知道什么时候开始年费化了，也不能打开高版本文件了。（妈蛋）\n据说是为了促进销量和保护版本。\n打开包文件，然后打开包内的meta.json\n替换头部:\n{\u0026#34;commit\u0026#34;:\u0026#34;335a30073fcb2dc64a0abd6148ae147d694c887d\u0026#34;,\u0026#34;appVersion\u0026#34;:\u0026#34;43.1\u0026#34;,\u0026#34;build\u0026#34;:39012 替换尾部\n\u0026#34;commit\u0026#34;:\u0026#34;335a30073fcb2dc64a0abd6148ae147d694c887d\u0026#34;,\u0026#34;build\u0026#34;:39012,\u0026#34;appVersion\u0026#34;:\u0026#34;43.1\u0026#34;,\u0026#34;variant\u0026#34;:\u0026#34;NONAPPSTORE\u0026#34;,\u0026#34;version\u0026#34;:88},\u0026#34;version\u0026#34;:88,\u0026#34;saveHistory\u0026#34;:[\u0026#34;NONAPPSTORE.39012\u0026#34;],\u0026#34;autosaved\u0026#34;:0,\u0026#34;variant\u0026#34;:\u0026#34;NONAPPSTORE\u0026#34;} 这里实际有几个 key:commit, appVersion, build, version,NONAPPSTORE\nvalue 替换成相应的值就 OK 了。\n","permalink":"https://hivan.me/posts/sketch-open-hight-version-file/","summary":"\u003cp\u003esketch 也不知道什么时候开始年费化了，也不能打开高版本文件了。（妈蛋）\u003c/p\u003e\n\u003cp\u003e据说是为了促进销量和保护版本。\u003c/p\u003e\n\u003cp\u003e打开包文件，然后打开包内的\u003ccode\u003emeta.json\u003c/code\u003e\u003c/p\u003e\n\u003cp\u003e替换头部:\u003c/p\u003e\n\u003cdiv class=\"highlight\"\u003e\u003cpre tabindex=\"0\" style=\"color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;\"\u003e\u003ccode class=\"language-json\" data-lang=\"json\"\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e{\u003cspan style=\"color:#f92672\"\u003e\u0026#34;commit\u0026#34;\u003c/span\u003e:\u003cspan style=\"color:#e6db74\"\u003e\u0026#34;335a30073fcb2dc64a0abd6148ae147d694c887d\u0026#34;\u003c/span\u003e,\u003cspan style=\"color:#f92672\"\u003e\u0026#34;appVersion\u0026#34;\u003c/span\u003e:\u003cspan style=\"color:#e6db74\"\u003e\u0026#34;43.1\u0026#34;\u003c/span\u003e,\u003cspan style=\"color:#f92672\"\u003e\u0026#34;build\u0026#34;\u003c/span\u003e:\u003cspan style=\"color:#ae81ff\"\u003e39012\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\u003cp\u003e替换尾部\u003c/p\u003e\n\u003cdiv class=\"highlight\"\u003e\u003cpre tabindex=\"0\" style=\"color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;\"\u003e\u003ccode class=\"language-json\" data-lang=\"json\"\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#e6db74\"\u003e\u0026#34;commit\u0026#34;\u003c/span\u003e\u003cspan style=\"color:#960050;background-color:#1e0010\"\u003e:\u003c/span\u003e\u003cspan style=\"color:#e6db74\"\u003e\u0026#34;335a30073fcb2dc64a0abd6148ae147d694c887d\u0026#34;\u003c/span\u003e\u003cspan style=\"color:#960050;background-color:#1e0010\"\u003e,\u003c/span\u003e\u003cspan style=\"color:#e6db74\"\u003e\u0026#34;build\u0026#34;\u003c/span\u003e\u003cspan style=\"color:#960050;background-color:#1e0010\"\u003e:\u003c/span\u003e\u003cspan style=\"color:#ae81ff\"\u003e39012\u003c/span\u003e\u003cspan style=\"color:#960050;background-color:#1e0010\"\u003e,\u003c/span\u003e\u003cspan style=\"color:#e6db74\"\u003e\u0026#34;appVersion\u0026#34;\u003c/span\u003e\u003cspan style=\"color:#960050;background-color:#1e0010\"\u003e:\u003c/span\u003e\u003cspan style=\"color:#e6db74\"\u003e\u0026#34;43.1\u0026#34;\u003c/span\u003e\u003cspan style=\"color:#960050;background-color:#1e0010\"\u003e,\u003c/span\u003e\u003cspan style=\"color:#e6db74\"\u003e\u0026#34;variant\u0026#34;\u003c/span\u003e\u003cspan style=\"color:#960050;background-color:#1e0010\"\u003e:\u003c/span\u003e\u003cspan style=\"color:#e6db74\"\u003e\u0026#34;NONAPPSTORE\u0026#34;\u003c/span\u003e\u003cspan style=\"color:#960050;background-color:#1e0010\"\u003e,\u003c/span\u003e\u003cspan style=\"color:#e6db74\"\u003e\u0026#34;version\u0026#34;\u003c/span\u003e\u003cspan style=\"color:#960050;background-color:#1e0010\"\u003e:\u003c/span\u003e\u003cspan style=\"color:#ae81ff\"\u003e88\u003c/span\u003e\u003cspan style=\"color:#960050;background-color:#1e0010\"\u003e},\u003c/span\u003e\u003cspan style=\"color:#e6db74\"\u003e\u0026#34;version\u0026#34;\u003c/span\u003e\u003cspan style=\"color:#960050;background-color:#1e0010\"\u003e:\u003c/span\u003e\u003cspan style=\"color:#ae81ff\"\u003e88\u003c/span\u003e\u003cspan style=\"color:#960050;background-color:#1e0010\"\u003e,\u003c/span\u003e\u003cspan style=\"color:#e6db74\"\u003e\u0026#34;saveHistory\u0026#34;\u003c/span\u003e\u003cspan style=\"color:#960050;background-color:#1e0010\"\u003e:\u003c/span\u003e[\u003cspan style=\"color:#e6db74\"\u003e\u0026#34;NONAPPSTORE.39012\u0026#34;\u003c/span\u003e]\u003cspan style=\"color:#960050;background-color:#1e0010\"\u003e,\u003c/span\u003e\u003cspan style=\"color:#e6db74\"\u003e\u0026#34;autosaved\u0026#34;\u003c/span\u003e\u003cspan style=\"color:#960050;background-color:#1e0010\"\u003e:\u003c/span\u003e\u003cspan style=\"color:#ae81ff\"\u003e0\u003c/span\u003e\u003cspan style=\"color:#960050;background-color:#1e0010\"\u003e,\u003c/span\u003e\u003cspan style=\"color:#e6db74\"\u003e\u0026#34;variant\u0026#34;\u003c/span\u003e\u003cspan style=\"color:#960050;background-color:#1e0010\"\u003e:\u003c/span\u003e\u003cspan style=\"color:#e6db74\"\u003e\u0026#34;NONAPPSTORE\u0026#34;\u003c/span\u003e\u003cspan style=\"color:#960050;background-color:#1e0010\"\u003e}\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\u003cp\u003e这里实际有几个 key:\u003ccode\u003ecommit\u003c/code\u003e, \u003ccode\u003eappVersion\u003c/code\u003e, \u003ccode\u003ebuild\u003c/code\u003e, \u003ccode\u003eversion\u003c/code\u003e,\u003ccode\u003eNONAPPSTORE\u003c/code\u003e\u003c/p\u003e\n\u003cp\u003evalue 替换成相应的值就 OK 了。\u003c/p\u003e","title":"sketch 中打开高版本文件"},{"content":"其实早几天前就进入了第三部分,而因为第二部分面向对象程序设计糊里糊涂,搞得再接下来的学习里有很多实例根本看不懂,或者很难回忆起所学的知识点.不得不回头又一个字一个字的老老实实的看了一遍,并且将每个字都敲出来更新到了 Sites 上!至此算是比较牢固了..而更新的部分,相信对于想进入 Java 世界的新手们也算是一个好的参考.\n其中前两部分分为八章,第一部分为基础程序设计,第二部分为面向对象程序设计 ,第三部分则是实战的 Java 应用程序设计.我询问过一个朋友,说是对于 Android 开发来说,Java 的前两部分属于基础,非常重要.而第三部分也就无所谓了.不过想来,还是将所有的东西全部抓牢以后再开始.毕竟 Java 已经学到这一步,规规矩矩做一个 Java 开发者也未尝不可.下面给出 Java 前两部分的链接,算是再次推广我的 Learn Wiki.而第三部分将不会在此更新了.有兴趣的自己跟着我在 Sites 上的更新翻看吧,更新速度要视我自己的学习速度而定!现在手上的教程为:《Java 开发与实战经典》\n第一部分:Java 基础程序设计 1.Java 概述及开发环境搭建 2.简单的 Java 程序 3.Java 基础程序设计 4.数组与方法\n第二部分:Java 面向对象程序设计 5.面向对象(基础篇) 6.面向对象(高级篇) 7.异常的捕获与处理 8.包及访问控制权限\n第三部分将不会继续在这里给出链接.请自行查看 http://learn.hivan.me/index/home/java-learn/Java-DAP\n","permalink":"https://hivan.me/posts/the-third-part-of-the-java-learning/","summary":"\u003cp\u003e其实早几天前就进入了第三部分,而因为第二部分面向对象程序设计糊里糊涂,搞得再接下来的学习里有很多实例根本看不懂,或者很难回忆起所学的知识点.不得不回头又一个字一个字的老老实实的看了一遍,并且将每个字都敲出来更新到了 Sites 上!至此算是比较牢固了..而更新的部分,相信对于想进入 Java 世界的新手们也算是一个好的参考.\u003c/p\u003e\n\u003cp\u003e其中前两部分分为八章,第一部分为基础程序设计,第二部分为面向对象程序设计 ,第三部分则是实战的 Java 应用程序设计.我询问过一个朋友,说是对于 Android 开发来说,Java 的前两部分属于基础,非常重要.而第三部分也就无所谓了.不过想来,还是将所有的东西全部抓牢以后再开始.毕竟 Java 已经学到这一步,规规矩矩做一个 Java 开发者也未尝不可.下面给出 Java 前两部分的链接,算是再次推广我的 Learn Wiki.而第三部分将不会在此更新了.有兴趣的自己跟着我在 Sites 上的更新翻看吧,更新速度要视我自己的学习速度而定!现在手上的教程为:\u003ca href=\"http://www.amazon.cn/gp/product/B002IIE012/ref=as_li_tf_tl?ie=UTF8\u0026amp;tag=duart-23\u0026amp;linkCode=as2\u0026amp;camp=536\u0026amp;creative=3200\u0026amp;creativeASIN=B002IIE012\"\u003e《Java 开发与实战经典》\u003c/a\u003e\u003c/p\u003e\n\u003cp\u003e\u003cimg alt=\"image\" loading=\"lazy\" src=\"http://farm8.staticflickr.com/7153/6750793489_d9338b03b7_z.jpg\"\u003e\u003c/p\u003e\n\u003cp\u003e第一部分:Java 基础程序设计 \u003ca href=\"http://learn.hivan.me/index/home/java-learn/Java-DAP/chapter1\"\u003e1.Java 概述及开发环境搭建\u003c/a\u003e \u003ca href=\"http://learn.hivan.me/index/home/java-learn/Java-DAP/chapter2\"\u003e2.简单的 Java 程序\u003c/a\u003e \u003ca href=\"http://learn.hivan.me/index/home/java-learn/Java-DAP/chapter3\"\u003e3.Java 基础程序设计\u003c/a\u003e \u003ca href=\"http://learn.hivan.me/index/home/java-learn/Java-DAP/chapter4\"\u003e4.数组与方法\u003c/a\u003e\u003c/p\u003e\n\u003cp\u003e第二部分:Java 面向对象程序设计 \u003ca href=\"http://learn.hivan.me/index/home/java-learn/Java-DAP/chapter5\"\u003e5.面向对象(基础篇)\u003c/a\u003e \u003ca href=\"http://learn.hivan.me/index/home/java-learn/Java-DAP/chapter06\"\u003e6.面向对象(高级篇)\u003c/a\u003e \u003ca href=\"http://learn.hivan.me/index/home/java-learn/Java-DAP/chapter07\"\u003e7.异常的捕获与处理\u003c/a\u003e \u003ca href=\"http://learn.hivan.me/index/home/java-learn/Java-DAP/chapter08\"\u003e8.包及访问控制权限\u003c/a\u003e\u003c/p\u003e\n\u003cp\u003e第三部分将不会继续在这里给出链接.请自行查看 \u003ca href=\"http://learn.hivan.me/index/home/java-learn/Java-DAP\"\u003ehttp://learn.hivan.me/index/home/java-learn/Java-DAP\u003c/a\u003e\u003c/p\u003e","title":"vue 2.0 自定义 filter 并挂载到全局使用"},{"content":"你是否曾经在微博上点赞某件事，然后几乎立刻在动态中看到相关内容？或者在 Google 上搜索某件事，然后几分钟后就会收到大量与该事相关的广告？这些都是日益自动化的世界的症状。在幕后，它们是先进的 MLOps 管道的结果。我们来看看 MLOps 以及有效部署机器学习模型需要什么。\n我们首先讨论 DevOps 的一些关键方面。然后我们解释引入数据和模型如何颠覆标准实践。这导致了 MLOps 的出现。现有实践（如 CI/CD 管道）需要调整。引入新实践（如持续训练）。最后，我们讨论受监管环境中的 MLOps 以及它与模型可解释性的关系。\nDevOps DevOps 是一套最佳实践、工具和公司文化，旨在提高软件开发、测试、部署和监控的效率。你可以拥有一个专门的 DevOps 团队来实施最佳实践。然而，它需要许多团队之间的参与和有效沟通。每家公司对 DevOps 的方法都不同。目标始终是相同的 - 快速部署优质软件。\nCI/CD 管道 所有软件在使用之前都需要开发、彻底测试和部署。DevOps 试图通过构建和自动化 CI/CD 管道来提高此过程的效率。持续集成 (CI) 涉及开发软件的流程。持续部署/交付 (CD) 涉及将软件转移到生产环境的流程。最佳实践和文化嵌入在这些管道的管理中。\n让我们考虑如何处理第一个方面，即持续集成。通常，这要求开发人员定期将代码更改推送到远程存储库。然后，运行自动单元和集成测试以立即识别代码中的任何问题。 DevOps 将定义实施这些步骤的最佳实践。我们使用 git 等工具来推送更改或使用 junit 来运行单元测试来实现它们。要做到这一点，需要形成定期提交代码的文化。这对大多数人来说并不自然。\n监控 一旦软件投入使用，我们就需要确保它运行良好。这涉及根据系统延迟数据创建指标。DevOps 旨在自动化此过程，并使开发人员可以轻松访问这些指标。它还可以涉及使用预定义的指标截止值创建自动警告。最终目标是建立一个持续的监控系统。这将实时提供所有应用程序的指标。\n沟通 传统上，开发人员和运营团队是孤立的。在这种情况下，开发人员将合作开发软件。完成后，他们会将其交给运营团队进行部署。开发和生产环境并不相同。这可能会导致交接过程中出现大量问题。最糟糕的情况是，你可能会发现软件根本无法部署。DevOps 旨在打破这些团队之间的隔阂，建立一种沟通文化。\nMLOps = DevOps + 数据 + 模型 MLOps 源于 DevOps。上面讨论的许多方面都会延续下来。关键的区别在于，对于机器学习应用程序，我们需要数据和模型。这意味着我们不能再依赖现有的实践和工具。在图 1 中，你可以看到数据和模型如何与现有的 DevOps 管道相适应的概述。我们将讨论这些新方面以及它们如何影响现有的 DevOps 实践。\n数据和模型开发 没有数据，就没有机器学习。数据需要在正确的时间出现在正确的位置。对于数据科学家来说，这意味着需要有足够的历史数据来训练模型。我们还需要更多最新数据来做出预测。在极端情况下，我们可能需要实时数据。数据工程师负责管理数据流入数据仓库。使用 MLOps，可以使用 CI/CD 数据管道来完成此操作。数据工程师将需要新工具来开发这些工具。\n数据为生产工作流程添加了新元素。同样，开发模型不同于开发软件。在确定最终模型之前，通常需要进行数千次迭代。我们在模型开发流程中进行这些迭代。该流程应使数据科学家能够轻松评估模型。它还应有助于跟踪所有不同的模型版本。应该很容易比较模型以决定最佳模型。\nCI/CD 管道 模型可能看起来像一个神秘的东西，但一旦训练完成，它就与普通代码没有什么不同。它是一组指令，可以接受输入（即数据）并给出输出（即预测）。开发完成后，我们可以将最终模型推送到主 CI/CD 管道。然后运行自动单元测试和集成测试，以确保新模型没有问题。\nCI/CD 管道的设计使得如果新代码出现问题，我们可以轻松恢复到旧版本。同样，我们应该能够恢复到模型的旧版本。如果我们发现模型的表现不如预期，我们就会这样做。也就是说，如果它在新数据上表现不佳或做出有偏差的预测。为了保护公司和客户，我们必须能够快速恢复到旧模型。\n监控 对于计算资源，监控模型类似于监控软件。我们需要确保模型按预期使用 CPU、RAM、带宽和磁盘空间。模型预测不应产生任何延迟问题。大多数这些挑战都已通过现有的 DevOps 实践得到解决。监控模型准确性方面的性能带来了新的挑战。\n与软件不同，模型会退化。模型捕获的底层关系会随着时间的推移而变化。这将导致模型的预测不太准确。我们需要引入新的指标来监控数据和模型准确性的变化。与典型的系统指标不同，最终用户将是数据科学家。他们将使用这些指标来帮助决定我们何时需要重新开发模型。\n持续训练（CT） 一旦我们标记一个模型需要重新开发，数据科学家就会介入。通常，会有一个手动过程来收集训练数据、创建特征并重新训练模型。在某些情况下，这个过程可以完全自动化。这让我们回到了文章开头的反问。没有人参与更新你的微博动态。当你喜欢新内容时，支持你推荐帖子的模型会自动更新。\n这被称为持续训练。在这里，MLOps 不仅寻求自动化模型的部署，还寻求自动化模型的训练。某些事件将启动模型的重新开发。这可能是因为有新数据可用，或者模型性能可能下降。在这种情况下，CI/CD 管道看起来可能非常不同。我们不再只是部署代码。我们正在部署另一项服务（即模型）的系统（即 ML 管道）。\n沟通 与 DevOps 一样，我们无法仅使用花哨的工具来实现有效的 MLOps。这也不是单个团队的责任。它需要许多团队之间的协作。这些团队包括领域专家、数据科学家、数据工程师、软件开发人员和 DevOps 团队。使用 MLOps，现在有更多的团队参与其中。这会使有效沟通更具挑战性。\n监管与可解释性 有效的 MLOps 是一个目标。组织需要不断改进系统、工具和沟通才能实现这一目标。不同的组织将面临不同的挑战。较老的组织将不得不改进或替换现有流程。较新的组织具有能够立即实施最佳实践的优势。对于某些行业来说，不仅存在技术挑战，还存在法律和道德挑战。\n例如，保险业和银行业都受到严格监管。在这种环境下开发的模型将受到严格审查。在部署模型之前，需要其他团队（例如合规团队）和外部监管机构的批准。你需要向这些团队报告并向他们解释模型。这将为模型开发过程添加手动步骤。最终，从技术上讲，自动化模型训练和部署可能是可行的，但这样做是违法的。\n你可以使用的模型类型也可以进行监管。这会影响你自动化模型开发过程的能力。使用 XGBoost 和随机森林等模型更容易做到这一点。在这里，你可以通过调整超参数来自动化模型选择过程。在保险或银行业，你可能需要使用回归。要建立一个好的回归模型，你需要选择一组最好的 8 到 10 个不相关的特征。这个过程更难自动化，我们在文章[[良好功能的特征]]中有详细讨论它。\n对可解释性的需求通常是模型以这种方式进行监管的原因。如果你没有彻底解释你的模型，它们可能会产生意想不到的结果。同样，如果你没有进行彻底的算法公平性分析，它们可能会产生有偏见的结果。解释模型和公平性总是需要一些人工干预。对于影响较大的模型，自动化这些过程的好处被风险所抵消。\n参考 M. Treveil, et. al., Introducing MLOps: How to Scale Machine Learning in the Enterprise (2020), https://www.oreilly.com/library/view/introducing-mlops/9781492083283/\nAWS, What is DevOps?(2022), https://aws.amazon.com/devops/what-is-devops/\nAWS, *What is Continuous Integration? (*2022), https://aws.amazon.com/devops/continuous-integration/\nAWS, What is Continuous Delivery? (2022), https://aws.amazon.com/devops/continuous-delivery/\nIBM, What Is the CI/CD Pipeline? (2021) https://www.ibm.com/cloud/blog/ci-cd-pipeline\nRevDeBug, Unit Tests Vs. Integration Tests (2021) https://revdebug.com/blog/unit-tests-vs-integration-tests/\nM. Anastasov, CD Pipeline: A Gentle Introduction (2022), https://semaphoreci.com/blog/cicd-pipeline\nO. Itzary and L. Nahum, Continuous Training for Machine Learning — a Framework for a Successful Strategy (2021), https://www.kdnuggets.com/2021/04/continuous-training-machine-learning.html\n「AI秘籍」系列课程：\n人工智能应用数学基础 人工智能Python基础 人工智能基础核心知识 人工智能BI核心知识 人工智能CV核心知识 ","permalink":"https://hivan.me/posts/%E4%BB%80%E4%B9%88%E6%98%AF-mlops/","summary":"\u003cblockquote\u003e\n\u003cp\u003eDevOps 与数据和机器学习相遇\u003c/p\u003e\u003c/blockquote\u003e","title":"什么是 MLOps？"},{"content":"","permalink":"https://hivan.me/posts/%E4%BB%80%E4%B9%88%E6%98%AF%E5%8F%AF%E8%A7%A3%E9%87%8A%E4%BA%BA%E5%B7%A5%E6%99%BA%E8%83%BD-xai/","summary":"\u003cblockquote\u003e\n\u003cp\u003eXAI 简介 — 旨在使机器学习模型为人类所理解的领域、\u003c/p\u003e\u003c/blockquote\u003e","title":"什么是可解释人工智能:XAI"},{"content":"本博客的很多内容其实已经过时，对于我本人和整个互联网空间来说都是占用了资源。所以想来次大清理。\n对于以前的一些 Android 的刷机博文，最早是在 2009 年左右开始发布的，那个时候国内玩机的人还很少，但是现在来看已经过气很久了，也没有任何指导意义，仅仅是对于我本人还有着一些纪念价值。\n类似于这类的文章还有很多，有的时候是为了纪念，有的时候是为了方便自己查找。\n可是时间久了，发现这并不是最合理的方式，并且对于网络资源来说，充斥着重复和浪费。对于此类文章，我都打算收入自己的文档库封存，在博上上就不放出来。\n至于有什么文章通过搜索引擎索引过来确不见了，并且别处没有答案的，可以留言给我，我会毫无保留的发给大家。（收费文除外）\n最后，感谢一直以来的读者们，希望之后茶桁的博客能对你们起到一些帮助。\n","permalink":"https://hivan.me/posts/%E5%8D%9A%E5%AE%A2-post-%E6%B8%85%E7%90%86/","summary":"可能来到我博客上的人在一定时间内会发现链接过来后文章不见了。别急，只是本博客做了一些清理工作。","title":"博客 POST 清理"},{"content":"Hi, 大家好。我是茶桁。\n在Python 篇 PDF 发布后, 我又制作了数学篇。\n在整个连载的过程中, 后台有小伙伴留言说想要电子书, 在公众号内进行阅读还是有些不太方便。在平时, 茶桁其实很多的知识和内容都是来自于公众号, 当然, 更多的是来自于书本。\n咱们整个数学篇基本上都属于基础理论知识, 概念、公式、推导比较多, 但是为了让大家将数学概念和程序关联上, 在其中也涉及到了一些编码的部分, 也讲解了人工智能上数学是怎么应用的, 比如在 Chapter6 中, 我就给大家讲解了数学如何运用于 AI.\n所有在数学篇内涉及到的程序代码, 也都在代码仓库中可以找到: 可以选择一个自己访问顺畅的进行拉取。\nGithub Gitee 这个仓库内包含了目前为止整个《AI 秘籍》的全部代码以及数据集, 方便小伙伴下来之后进行练习。那仓库中的代码截至运行时间是 2023 年 12 月 26 日, 为什么要写这个, 因为有些包里的一些参数会做一些变动, 到目前为止这些代码都可正常运行, 假如过若干时间之后大家发现代码运行不了, 可以去看看包的官方文档, 也可以后台留言给我进行说明, 我会对电子书进行更新。\n由于「数学篇」是一个付费专辑, 所以这本电子书并不是免费发放的, 仅针对已经付费的用户发送, 其余小伙伴想要阅读, 只能是进行购买了。不过本电子书是长期修正更新的, 在购买之后, 如果电子书内容进行了刊正, 则会对其进行更新, 并再次发送给已经购买的用户。还望小伙伴们在观看的过程中能够指出我的错误, 以便我对本电子书进行维护。\n和制作 Python 篇的电子书的时候不同, 为了对数学篇进行付费的小伙伴们负责, 这次整个数学篇的电子书都一篇一篇进行了修正, 有些篇章更是重新写了一遍. 然后用 Latex 来重新设计和制作排版, 等于是整本书基本上都重构了一遍.\n在修正的过程中才发现, 之前很多地方都有错误,有些错误甚至都是有误导性的. 在此要跟已经购买过数学篇的小伙伴们说声抱歉.\n当然, 虽然已经尽力的去寻找并且修正, 但也许不可避免的仍然还会有些错漏的地方, 在整个重新制作的过程中, 主要将经历放在了公式和概念上, 首先是争取对同学们不造成误导, 其次是有些地方的推导重新推导了, 争取能更容易进行理解. 在阅读过程中, 如果有小伙伴发现了错误或者遗漏, 还望给我留言, 我会尽快修正.\n本电子书加入了版本号, 在每一章节的右下角部分, 如果有更新, 版本号都会有所变化. 并且在首页二维码下方的日期也会变化.\n本电子书在Github 上公开了源码, 不过由于内容还是付费内容, 所其中章节并未上传, 仅上传了主文件和一些设置内容. 不过对于想研究电子书制作的的小伙伴已经足够了.\n详细电子书内容还请移步公众号原文进行获取.\n最后, 感谢大家的支持, 特别感谢付费支持的小伙伴们。还望关注「坍缩的奇点」, 日后咱们会有一些干货分享给大家, 也会和大家一起来完成一些企业项目, 帮助小伙伴们更快的步入到实际工作中去。\n敬请关注「坍缩的奇点」，获取更多人工智能相关教程及资料。\n","permalink":"https://hivan.me/posts/tea-trusss-ai-cheats-math-pdf-release-download/","summary":"\u003cimg alt=\"茶桁的 AI 秘籍_数学篇\" loading=\"lazy\" src=\"https://cdn.jsdelivr.net/gh/hivandu/notes/img/%E8%8C%B6%E6%A1%81%E7%9A%84AI%E7%A7%98%E7%B1%8D_%E6%95%B0%E5%AD%A6%E7%AF%87.png\"\u003e","title":"茶桁的 AI 秘籍 - 数学篇 PDF 发布下载"},{"content":"\n数据科学家职责的一个重要部分是解释模型预测。通常，接受解释的人不是技术人员。如果你开始谈论成本函数、超参数或 p 值，你将会遇到茫然的表情。我们需要将这些技术概念转化为外行人能理解的术语。这个过程可能比构建模型本身更具挑战性。\n我们将探索如何给出人性化的解释。我们将通过讨论良好解释的一些关键特征来做到这一点。重点将放在解释个别预测上。最后，我们将通过使用「SHAP 值」解释模型来应用其中的一些知识。我们将看到，当你想要给出人性化的解释时，SHAP 非常有用。\n本地解释与全球解释 在深入讨论之前，让我们先讨论一下你要解释什么以及向谁解释。作为一名数据科学家，你可能需要与各种各样的人沟通。这包括同事、监管者或客户。这些人的技术专长水平各不相同。因此，你需要根据他们的专业知识调整解释的水平。\n在某些情况下，你可能会被要求解释整个模型。我们称之为全局解释。我们希望了解模型总体上捕捉到了哪些趋势。我们可能需要回答诸如“哪些特征最重要？”或“特征 X 与目标变量有什么关系？”之类的问题。\n在其他情况下，我们需要给出局部解释。这就是我们解释单个模型预测的时候。事实上，我们通常必须解释由模型预测得出的决策。这些解释也可以回答诸如“我们为什么拒绝这个贷款申请？”或“为什么给我这个电影推荐？”之类的问题。\n与同事或监管者交谈时，你可能需要给出更多技术性解释。相比之下，客户会期望更简单的解释。你也不太可能需要向客户提供全面的解释。这是因为他们通常只关心对他们个人有影响的决定。我们将重点关注这种情况。即向非技术人员解释个别预测。\n良好解释的特征 当我们谈论一个好的解释时，我们指的是一个容易被接受的解释。换句话说，它应该让听众相信一个决定是正确的。要给出这样的解释，你需要考虑一些关键方面。你可以在图 1 中看到这些方面的摘要。在本节的其余部分，我们将深入讨论每一个方面。\n真实的 这似乎很明显，但一个好的解释应该是真实的。当你考虑我们正在解释的内容时，这可能比你想象的要难。也就是说，我们正在对模型预测给出解释。问题是这些预测可能是不正确的。例如，我们的模型可能过度拟合。这意味着预测可能反映了已建模的噪声。对这些预测的解释不会反映数据中真正的潜在关系。\n在给出解释时，我们需要考虑我们的模型如何很好地代表现实。我们可以通过评估模型的性能来做到这一点。例如使用交叉验证准确率。即使总体表现良好，一些预测也可能比其他预测更不确定。例如，逻辑回归预测的概率约为 0.5。在你的解释中，我们可能想提及这种不确定性。\n针对目标受众 措辞方式很重要。我们拒绝你的贷款是因为 — “你是加密货币交易员”、“你从事高风险行业”或“你的收入太不稳定”。这些都是相同的原因，只是措辞不同。某些措辞方式可以更好地表达你的观点。有些人可能还会觉得某些方式令人反感。例如，第一个解释可能会被视为个人问题。\n你还应避免使用任何技术或商业术语。“收入在我们的模型中有一个正参数，而你的收入值很低。因此，你被拒绝了，因为你的收入值显著增加了你的违约风险。”这是一个糟糕的解释，因为我们使用了技术术语（即“模型”和“参数”）。我们也有一些商业术语（即“违约风险”）。\n选择最佳的解释方式才是艺术的真正所在。这可能是解释预测最困难的部分。数据科学课程或大学也没有很好地涵盖这一点。最终，这项技能将随着经验和与客户建立关系而获得。\n原因数量 人们只想知道事件的主要原因。“为什么通货膨胀这么高？”——“因为油价上涨。”事实上，通货膨胀可能是由多种因素造成的，包括工资增长、政府支出增加或汇率。然而，这些因素可能没有能源成本上涨那么显著。\n在机器学习中，人们并不想知道每个模型特征如何影响预测。通常，解释 1 到 3 个特征的贡献就足够了。问题是你选择解释哪些贡献？接下来的 4 个特征可以帮助你选择最合适的原因。\n重要的 我们应该选择最重要的原因。换句话说，我们只解释那些影响巨大的因素。对于我们的通货膨胀示例，我们将给出导致最大涨幅的原因。这些原因比那些影响较小的原因更容易被接受。\n对于 ML，我们希望解释对预测贡献最大的特征。我们确定这些特征的方式取决于所使用的模型。对于线性模型，我们可以查看参数权重和特征值。稍后我们将看到 SHAP 值如何用于非线性模型。\n一般的 一个好的理由可以解释许多决定。“你为什么拒绝我的贷款申请？”-“因为你有很多现有债务。”假设这是拒绝许多贷款申请的原因。这个理由更容易被接受，因为它的作用被广泛理解。\n对于 ML，我们可以通过查看某些特征重要性度量来找到一般解释。例如，排列特征重要性。通过 SHAP 值，我们可以查看平均绝对 SHAP。无论采用哪种度量，高值都表明某个特征总体上做出了重大贡献。\n异常 在其他情况下，一个好的理由可能并不常见。这些理由可以解释对特定决策的重大贡献。但是，一般来说，它们无法解释决策。“你为什么拒绝我的贷款申请？”-“因为你是加密货币交易员”这些类型的理由可能会被接受，因为它们更个人化。也就是说，人们可以理解它对他们有何具体影响。\n对于 ML，我们可以通过查看特征重要性和对各个预测的贡献的组合来查找异常原因。这些特征可能不具有很高的特征重要性。但是，对于特定的预测，它们做出了很大的贡献。\n对比 我们经常需要解释一个决定与另一个决定的区别。客户可能不会问“为什么我的申请被拒绝？”而是问“为什么我的申请被拒绝而他们的申请被接受？”可能是因为两个客户都负债累累。这个原因可能在第一个问题中被接受，但在第二个问题中却不被接受。换句话说，我们需要给出一个能够区分这两个决定的理由。\n对于 ML 来说，这意味着我们需要根据两个客户具有不同值的特征来做出解释。该特征还需要有所不同，从而导致不同的预测。例如，我们可能会发现第一个客户的收入较高。收入特征不同，但这不是一个很好的理由。也就是说，更高的收入不会导致申请被拒绝。\n最后这四个特点似乎相互矛盾。异常原因不可能具有普遍性。异常或普遍原因可能不是最重要的。对比原因既不重要，也不普遍，也不异常。但是，我们可以在解释中使用多种原因。你选择的原因将取决于问题、人以及你认为最有说服力的原因。\n用 SHAP 解释模型 现在让我们把这些特征付诸实践。我们将尝试解释一个用于预测保险收费金额（费用）的模型。你可以在表 1 中看到此数据集的快照。我们的模型特征基于前 6 列。例如，children 是受抚养人的数量。\n(数据集：[kaggle]1, 许可证 - 数据库：开放数据库)\n我们不会详细介绍代码，但你可以在我的 [GitHub]2 上找到完整的项目。\n总而言之，我们首先进行一些特征工程。我们将性别和吸烟者转换为二进制特征。区域可以采用 4 个不同的值，因此我们从此列创建 4 个虚拟变量。这给我们留下了总共 9 个模型特征。我们使用这些特征来训练一个 XGBoost 模型，以预测收费情况。 模型训练完成后，我们将计算每次预测的 SHAP 值。\n对于每个预测，每个特征都会有一个 SHAP 值。SHAP 值给出了该特征对预测的贡献。换句话说，该特征增加或减少了预测费用。要了解哪些特征很重要，我们可以看看图 2。这里我们取了所有预测的 SHAP 值的绝对平均值。高平均值表示该特征总体上对预测做出了很大贡献。\n我们可以使用图 2 来帮助确定良好的一般原因或异常原因。例如，我们可以看到吸烟者的绝对平均值较高。这告诉我们，一般来说，该特征可以为预测提供良好的理由。绝对平均值较低的特征可能会提供良好的异常原因。也就是说，如果对于特定预测，它们具有较大的贡献。\n让我们尝试解释一下我们的第一个预测。你可以在图 3中看到此预测的 SHAP 瀑布图。查看 x 轴，我们可以看到基值为 E[f(x)] = 12813。这是所有客户的平均预测费用。最终值为 f(x) = 26726。这是针对该特定客户的预测费用。SHAP 值是介于两者之间的所有值。它们告诉我们与平均预测相比，每个特征如何增加或减少了预测。\n从图 3 中我们可以看到，吸烟者是最重要的特征。也就是说，它使预测费用增加了最多的量。特征值在 y 轴上给出。例如，我们可以看到 1 = smoker 表示该客户吸烟。因此，如果客户问“为什么我的保险费这么高？”，一个很好的解释可能是“你是吸烟者”。我们还看到，在图 2 中，这是一个很好的一般原因。\n因此，吸烟既是重要原因，也是普遍原因。这可能足以让这个人相信保险费是正确的。如果我们想确定，我们可以提到第二重要的特征。查看 y 轴，我们可以看到此人是 62 岁。因此，我们可以跟进第二个原因，“而且你老了。”（我们可能想用更好的方式来表达这一点。）\n出于第二个原因，我们猜测了与目标变量的关系。也就是说，随着年龄的增长，你的费用也会增加。其他特征的关系可能不那么明显。这意味着为了给出好的理由，我们需要一些特征值的背景信息。为此，我们可以使用 SHAP 值的散点图。查看图 4，我们可以看到我们的猜测是正确的。随着年龄的增长，SHAP 值会增加。换句话说，预测的费用会增加。\n在图 5 中，你可以看到第二个预测的瀑布图。为了解释这一点，我们可能会直接跳到客户的年龄。但是，请注意，孩子也做出了重大贡献。请记住，我们在图 2 中看到，这个特征总体上并不重要。换句话说，children 的数量可能是一个很好的异常原因。我们可能更愿意将其作为主要原因。\n从本质上讲，SHAP 值允许你给出对比解释。但是，这仅限于我们想要将预测与平均预测进行比较时。回答诸如“为什么我的收费高于平均水平？”之类的问题很容易。回答诸如“为什么我的收费比我姐姐的要高？”之类的问题需要做更多工作。还有其他计算 SHAP 值的方法可以使这更容易。例如，基线 SHAP 将计算相对于特定预测的值。\n我们专注于使用 SHAP 值来解释单个预测。它们也可用于给出全局解释。即解释整个模型的工作原理。我们通过聚合 SHAP 值并创建不同的图来实现这一点。我们已经看到了一个——平均 SHAP 图。之前写过一篇《[使用 Python 进行 SHAP 简介](Python 中的 SHAP 简介)》，大家可以看看，其中讨论了更多内容。我们还会讨论用于计算 SHAP 值和创建这些图的 Python 代码。\n希望这篇文章对你有所帮助！你还可以阅读我的其他文章，或者查看有关企业 AI 实战项目的教程，相信会让你拥有更多收获。\n参考 C. Molnar, Interpretable Machine Learning, 2021, https://christophm.github.io/interpretable-ml-book/explanation.html\nT. Miller, Explanation in artificial intelligence: Insights from the social sciences, 2017, https://arxiv.org/abs/1706.07269\nS. Lundberg \u0026amp; S. Lee, A Unified Approach to Interpreting Model Predictions, 2017, https://arxiv.org/pdf/1705.07874.pdf\n「AI秘籍」系列课程：\n人工智能应用数学基础 人工智能Python基础 人工智能基础核心知识 人工智能BI核心知识 人工智能CV核心知识 M. Choi，医疗费用个人数据集（许可证 - 数据库：开放数据库）https://www.kaggle.com/datasets/mirichoi0218/insurance/metadata\u0026#160;\u0026#x21a9;\u0026#xfe0e;\nGithub, https://github.com/hivandu/public_articles/blob/main/src/interpretable_ml/SHAP/SHAP_explanation.ipynb\u0026#160;\u0026#x21a9;\u0026#xfe0e;\n","permalink":"https://hivan.me/posts/%E8%A7%A3%E9%87%8A%E9%A2%84%E6%B5%8B%E7%9A%84%E8%89%BA%E6%9C%AF/","summary":"\u003cblockquote\u003e\n\u003cp\u003e如何以人性化的方式解释你的模型\u003c/p\u003e\u003c/blockquote\u003e","title":"解释预测的艺术"},{"content":"重裝系統之後，很多東西需要重裝，特別是開發環境。而開發環境的先後順序和設置，一直是我頭疼的事情。這次就着從新安裝了一遍，把很多東西都記錄下來。之前那個環境被我搞的亂七八糟，並且恢復不回來了。\n多大多數次序參照 @mrzhang\n系統偏好設置 更改電腦名稱\n共享\n允許安裝任何來源 APP\n安全性與隱私 \u0026ndash;》通用\n設置快捷鍵\n鍵盤 \u0026ndash;》 快捷鍵\n配置 VPN 以及 SSH 很重要，因爲很多源都在牆外了\n安裝輸入法 下載並安裝\u0026quot;Squirrel\u0026quot; 下載並安裝\u0026quot;SCU\u0026quot; 安裝Sublime Text 3\n設置 package\nCtrl+ , and setting:\n{ \u0026#34;caret\\_style\u0026#34;: \u0026#34;phase\u0026#34;, \u0026#34;color\\_scheme\u0026#34;: \u0026#34;Packages/Color Scheme - Default/Solarized (Light).tmTheme\u0026#34;, \u0026#34;font\\_face\u0026#34;: \u0026#34;Monaco\u0026#34;, \u0026#34;font\\_size\u0026#34;: 13.0, \u0026#34;hightlight\\_line\u0026#34;: true, \u0026#34;hightlight\\_modified\\_tabs\u0026#34;: true, \u0026#34;ignored\\_packages\u0026#34;: [ \u0026#34;Vintage\u0026#34; ], \u0026#34;indent\\_to\\_bracket\u0026#34;: true, \u0026#34;draw\\_centered\u0026#34;: false, //居中显示 \u0026#34;line\\_numbers\u0026#34;: true, //显示行号 \u0026#34;gutter\u0026#34;: true, //显示行号边栏 \u0026#34;fold\\_buttons\u0026#34;: true, //显示折叠按钮 \u0026#34;fade\\_fold\\_buttons\u0026#34;: true, //始终显示折叠按钮 \u0026#34;rulers\u0026#34;: [], //列显示垂直标尺，在中括号里填写数字，宽度按字符计算 \u0026#34;spell\\_check\u0026#34;: false, //拼写检查 \u0026#34;hot\\_exit\u0026#34;: true, //保留未保存内容 \u0026#34;line\\_padding\\_bottom\u0026#34;: 1, \u0026#34;line\\_padding\\_top\u0026#34;: 1, \u0026#34;scroll\\_past\\_end\u0026#34;: true, //文本最下方缓冲区 \u0026#34;tab\\_size\u0026#34;: 2, // Tab 制表宽度 \u0026#34;translate\\_tabs\\_to\\_spaces\u0026#34;: true, //缩进和遇到 Tab 键用空格替代 \u0026#34;wide\\_caret\u0026#34;: true, \u0026#34;word\\_wrap\u0026#34;: true, \u0026#34;match\\_tags\u0026#34;: true, //HTML 下突出显示光标所在标签的两端。 \u0026#34;match\\_selection\u0026#34;: true, //全文高亮当前选中字符 \u0026#34;wrap\\_width\u0026#34;: 80 } 編輯設置 /etc/paths /usr/local/bin /usr/local/sbin /usr/bin /usr/sbin /bin /sbin 安裝Xcode xcode-select --install 安裝Homebrew ruby -e \u0026#34;$(curl -fsSL https://raw.githubusercontent.com/Homebrew/homebrew/go/install)\u0026#34; PS: 這裏可能會很長時間的等待\n設置 Sublime 終端鏈接 ln -s /Applications/Sublime\\ Text.app/Contents/SharedSupport/bin/subl /usr/local/bin/sm Git, autojump brew install git autojump Oh My Zsh curl https://raw.githubusercontent.com/creationix/nvm/master/install.sh | sh 設置 ~/.zshrc:\nexport NVM\\_NODEJS\\_ORG\\_MIRROR=\u0026#34;http://npm.taobao.org/dist\u0026#34; [[ -s \u0026#34;$HOME/.nvm/nvm.sh\u0026#34; ]] \u0026amp;\u0026amp; . \u0026#34;$HOME/.nvm/nvm.sh\u0026#34; export NODE\\_PATH=$NVM\\_DIR/$(nvm\\_ls current)/lib/node\\_modules 安裝 NodeJS nvm install 0.11.15 nvm alias default 0.11.15 安裝rbenv git clone git://github.com/sstephenson/rbenv.git \\~/.rbenv # 用来编译安装 ruby git clone git://github.com/sstephenson/ruby-build.git \\~/.rbenv/plugins/ruby-build # 用来管理 gemset, 可选, 因为有 bundler 也没什么必要 git clone git://github.com/jamis/rbenv-gemset.git \\~/.rbenv/plugins/rbenv-gemset # 通过 gem 命令安装完 gem 后无需手动输入 rbenv rehash 命令, 推荐 git clone git://github.com/sstephenson/rbenv-gem-rehash.git \\~/.rbenv/plugins/rbenv-gem-rehash # 通过 rbenv update 命令来更新 rbenv 以及所有插件, 推荐 git clone https://github.com/rkh/rbenv-update.git \\~/.rbenv/plugins/rbenv-update 設置~/.zshrc export PATH=\u0026#34;$HOME/.rbenv/bin:$PATH\u0026#34; eval \u0026#34;$(rbenv init -)\u0026#34; 其他 安裝 Ruby rbenv install -l # list all available versions rbenv install 2.1.5 # install a Ruby version rbenv global 2.1.5 # set the global version rbenv versions # list all installed Ruby versions 配置 gem 源 gem sources -a http://ruby.taobao.org/ -r https://rubygems.org/ echo \u0026#39;gem: --no-document\u0026#39; \\\u0026gt;\\\u0026gt; \\~/.gemrc gem update gem update --system 安装 MongoDB, MySQL brew install mongodb mysql 設置開機自啓動「可選」\nmkdir -p \\~/Library/LaunchAgents ln -sfv /usr/local/opt/mongodb/\\*.plist \\~/Library/LaunchAgents ln -sfv /usr/local/opt/mysql/\\*.plist \\~/Library/LaunchAgents 安装 Pow curl get.pow.cx | sh gem install powder Powder 是一套管理工具\nSSH-KeyGen ssh-keygen -t rsa cat \\~/.ssh/id\\_rsa.pub 安裝 Rails, sass, compass 以及 hexo gem install rails sass compass npm install -g hexo 安裝必要工具 gem install mysql2 gem install capistrano gem install capistrano-ext Snippets - [Download](Snippets - Download) 安裝其他 APP","permalink":"https://hivan.me/posts/reinstall-mac-osx-yosemite/","summary":"\u003cp\u003e重裝系統之後，很多東西需要重裝，特別是開發環境。而開發環境的先後順序和設置，一直是我頭疼的事情。這次就着從新安裝了一遍，把很多東西都記錄下來。之前那個環境被我搞的亂七八糟，並且恢復不回來了。\u003c/p\u003e\n\u003cp\u003e\u003cimg alt=\"image\" loading=\"lazy\" src=\"https://qiniu.hivan.me/Yosemite.jpg\"\u003e\u003c/p\u003e","title":"重裝「Yosemite」"},{"content":"好吧，那就只能将移动硬盘挂载到我的用户目录里了，我的移动硬盘是 APFS 类型，执行下面命令：\n# 查看当前硬盘 IDENTIFIER diskutil apfs list # 或者下面这段命令 diskutil list # 然后需要进行解锁，恢复键值(recovery_key): diskutil apfs unlockVolume /dev/apfs_volume_id -passphrase recovery_key # 接着进行装载到自己期望的目录 diskutil mount -mountPoint Path apfs_volume_id 假定我的硬盘apfs_volume_id为 disk5s1, 希望挂载到~/mount则：\ndiskutil apfs unlockVolume /dev/disk5s1 -passphrase recovery_key diskutil mount -mountPoint ~/mount /dev/disk5s1 本是留待自用的，有需要的有缘人自行取走。\n","permalink":"https://hivan.me/posts/mount-apfs-on-mac/","summary":"\u003cblockquote\u003e\n\u003cp\u003e自用文，有需要的自取。\n百度网盘同步会认为移动硬盘是系统盘，所以无法进行同步。当然，也有例外的，之前我也是不知怎么同步的。\n这次设置的时候被警告了，不允许设置。\u003c/p\u003e\u003c/blockquote\u003e","title":"Mac 上挂载 APFS 移动硬盘"},{"content":"这批视频下载下来后前边都给了诸如001之类的编号，当然是序列。可是这批序列又非常的乱，比如，「数列」和「导数」给的是考前的编号，而课本上要先学习的「集合」，「逻辑」，「不等式」等又编号又很靠后。不仅如此，就算是同一部分，其中的编号也是混乱的。\n那么就有了批量修改文件名的需求，当然我第一时间想到的是Better rename，已经是一个很古老的版本了：\n可是当我使用的时候才发现并不能满足我的个人需求，也许是我不太会用吧。起码，我是想删掉开头的那些序列以及其中重复不必要的内容。但是这玩意并不能支持正则或者相关的功能。没办法，眼见有几种方式去做，一种是 Mac 自带的「自动操作」，一种是「捷径」，还有就是干脆用 Python 写个脚本。\n所以，我使用了自己觉得最简便的方式，写了这样一个脚本：\nimport os import tkinter as tk from tkinter import filedialog import re root = tk.Tk() root.withdraw() folderPath = filedialog.askdirectory() # 获得选择好的文件夹 # filePath = filedialog.askopenfilename() # 获得选择好的文件 print(folderPath) # print(filePath) files = os.listdir(folderPath) fileList = [] newFileList = [] oldStr = input(\u0026#39;请输入要修改的内容或正则:\u0026#39;) newStr = input(\u0026#39;请输入要替换的内容，不修改只删除可留空:\u0026#39;) for i in files: if i[0] != \u0026#39;.\u0026#39;: portion = os.path.splitext(i) newname = re.sub(r\u0026#39;{}\u0026#39;.format(oldStr), r\u0026#39;{}\u0026#39;.format(newStr), portion[0]) # os.chdir(folderPath) # 测试完毕后要正式修改文件名取消这里注释 # os.rename(portion[0]+portion[1], newname+portion[1]) # 测试完毕后要正式修改文件名取消这里注释 fileList.append(portion[0]+portion[1]) newFileList.append(newname+portion[1]) print(\u0026#39;修改前，一共有{}个文件\\n {}\u0026#39;.format(len(fileList), fileList)) print(\u0026#39;修改后，一共有{}个文件\\n {}\u0026#39;.format(len(newFileList), newFileList)) 有需要的小伙伴可以自取了。代码执行后会让你选取你要修改的文件的目录，然后会让你输入你要修改的内容，可以是正则，然后输入你要修改成的内容。\n比如，我需要修改标题：\n# 输入需要替换的内容 \\d{3} - 要替换的内容我直接留空回车，打印结果：\n修改前，一共有 16 个文件 [\u0026#39;001 - 不等式 1.1 不等式的基本性质 高中数学.mp4\u0026#39;, \u0026#39;016 - 不等式 4.4 恒成立与存在性问题 高中数学.mp4\u0026#39;, \u0026#39;007 - 不等式 3.3 高次不等式 高中数学.mp4\u0026#39;, \u0026#39;008 - 不等式 3.4 含参讨论 高中数学.mp4\u0026#39;, \u0026#39;003 - 不等式 1.3 比大小思路 高中数学.mp4\u0026#39;, \u0026#39;002 - 不等式 1.2 不等式证明思路 高中数学.mp4\u0026#39;, \u0026#39;006 - 不等式 3.1 二次不等式 高中数学.mp4\u0026#39;, \u0026#39;015 - 不等式 1.4 复杂证明题 高中数学.mp4\u0026#39;, \u0026#39;005 - 不等式 2.3 基本不等式变式 高中数学.mp4\u0026#39;, \u0026#39;011 - 不等式 2.4 不要过度放缩三元不等式 高中数学.mp4\u0026#39;, \u0026#39;010 - 不等式 2.2 使用条件误区 高中数学.mp4\u0026#39;, \u0026#39;009 - 不等式 4.1 对勾型函数最值 高中数学.mp4\u0026#39;, \u0026#39;013 - 不等式 4.2 对称与均值 高中数学.mp4\u0026#39;, \u0026#39;004 - 不等式 2.1 基本不等式均值不等式 高中数学.mp4\u0026#39;, \u0026#39;014 - 不等式 4.3 齐次与均值 高中数学.mp4\u0026#39;, \u0026#39;012 - 不等式 3.2 穿针引线法 高中数学.mp4\u0026#39;] 修改后，一共有 16 个文件 [\u0026#39;不等式 1.1 不等式的基本性质 高中数学.mp4\u0026#39;, \u0026#39;不等式 4.4 恒成立与存在性问题 高中数学.mp4\u0026#39;, \u0026#39;不等式 3.3 高次不等式 高中数学.mp4\u0026#39;, \u0026#39;不等式 3.4 含参讨论 高中数学.mp4\u0026#39;, \u0026#39;不等式 1.3 比大小思路 高中数学.mp4\u0026#39;, \u0026#39;不等式 1.2 不等式证明思路 高中数学.mp4\u0026#39;, \u0026#39;不等式 3.1 二次不等式 高中数学.mp4\u0026#39;, \u0026#39;不等式 1.4 复杂证明题 高中数学.mp4\u0026#39;, \u0026#39;不等式 2.3 基本不等式变式 高中数学.mp4\u0026#39;, \u0026#39;不等式 2.4 不要过度放缩三元不等式 高中数学.mp4\u0026#39;, \u0026#39;不等式 2.2 使用条件误区 高中数学.mp4\u0026#39;, \u0026#39;不等式 4.1 对勾型函数最值 高中数学.mp4\u0026#39;, \u0026#39;不等式 4.2 对称与均值 高中数学.mp4\u0026#39;, \u0026#39;不等式 2.1 基本不等式均值不等式 高中数学.mp4\u0026#39;, \u0026#39;不等式 4.3 齐次与均值 高中数学.mp4\u0026#39;, \u0026#39;不等式 3.2 穿针引线法 高中数学.mp4\u0026#39;] 打印内容中查看自己修改前和修改后的文件对比，感觉没问题了，把其中注释的两行代码打开注释，就可以完成文件修改了。\n","permalink":"https://hivan.me/posts/python-change-file-names-in-batches/","summary":"\u003cblockquote\u003e\n\u003cp\u003e仅个人需求，有需要的可以自取。\n前段时间为家里孩子下载了一批课程，但是文件命名就很奇怪也很乱，就想着将文件名修改掉便于查看。\u003c/p\u003e\u003c/blockquote\u003e","title":"Python 批量修改文件名"},{"content":"在之前的一个多月前，我有了一个写一个 AI 系列的想法，起名为《茶桁的 AI 秘籍》，简单规划之后，于 7 月 27 日发出预告，然后历时二十多天将近一个月，完成了其中《Python 篇》的写作。\n不知道其中的内容对大家是否有帮助呢？\n那么今天我又回来了，根据规划，Python 以及相关第三方科学计算库只是我们基础学习的一小部分，而很大一部分基础学习都还未进行。\n那么这次，我依然给大家带来的是另外一篇基础部分，「人工智能数学基础篇」。\n数学对于计算机编程来说重要性是毋庸置疑的，更何况我们现在不仅仅是编程，而是走在「人工智能」的路上。可以说，数学应该是最重要的基础。\n我们在学习 AI 的过程当中可能会遇到的一些关于数学方面的一些东西，比如说线性代数里面的矩阵运算，比如说求导，还有一些概率统计，图论方面的一些东西。\n如果您觉得自己对于微积分，线性代数，概率统计这些内容自认为掌握的还不错的同学，其实是可以不用看了。如果大家是从文科转过来或者说以前上的数学很多年了也忘的差不多了，那可以来学习一下这套课程。\n你将会学到的 ✓ 掌握数据科学领域必备数学知识点 ✓ 掌握机器学习算法中常用数学 ✓ 通俗理解各项数学公式的作用 ✓ 掌握数学知识点应用领域与方法 ✓ 掌握高等数学 ✓ 掌握线性代数 ✓ 掌握概率论 ✓ 掌握统计分析方法 ✓ 掌握结合 Python 进行数学操作 课程内容 数学导论 数学导论概述 微积分基础（导数） 线性代数基础（矩阵） 概率\u0026amp;统计基础（随机变量） 图论（图的概念） 微积分 函数 极限\u0026amp;连续 导数 微分 链式法则 偏导数 梯度 积分 牛顿 - 莱布尼兹公式 泰勒展开 线性代数 线性方程组 行列式与克拉默法则 矩阵及其运算 神经网络中的矩阵/向量 矩阵的性质 矩阵与线性变换 线性变换的几何意义 特征值与特征向量 NumPy 中矩阵的操作 概率\u0026amp;统计 概率是什么 古典概型\u0026amp;几何概型 条件概率\u0026amp;联合概率 期望\u0026amp;方差\u0026amp;协方差 二项分布 高斯分布 中心极限定理 泊松分布 贝叶斯先验分布\u0026amp;后验分布 机器学习分类指标 图论 图的由来 图的构成 图的表示 邻接矩阵 图的种类 最短路径问题 Dijkstra 算法 树 最小生成树 图与人工智能 要求 有一定的数学基础学习起来更顺手 熟悉 Python 将更快上手进行统计分析 说明 本篇是系列《茶桁的 AI 秘籍》中的《基础数学篇》，旨在帮助同学们快速打下数学基础，通俗讲解其中每一个知识点。课程内容涉及高等数学，线性代数，概率论与统计学，同学们在学习过程中应当以理解为出发点并不需要死记每一个公式，快速掌握核心知识点。课程章节内容较多，零基础同学按顺序学习即可，有基础的同学们可以按照自己的需求来有选择的学习！\n此课程面向哪些人： 数据科学方向的同学们； 准备继续学习机器学习，深度学习等方向的同学； 准备面试及就业 AI 相关方向的同学 对此有需求的小伙伴，赶紧如下方式订阅起来：\n扫码并关注微信号「坍缩的奇点」，发送消息「AI 数学」，后台将为您推送订阅。\n本文中所有代码：https://github.com/hivandu/AI_Cheats/tree/main/math\n","permalink":"https://hivan.me/posts/math-introduction/","summary":"\u003cimg alt=\"数学基础篇 Cover\" loading=\"lazy\" src=\"https://raw.githubusercontent.com/hivandu/notes/main/img/20230824152200.png\"\u003e\nHi, 大家好。又见面了，我是茶桁。","title":"茶桁的 AI 秘籍 - 人工智能数学基础篇 导言"},{"content":"专栏形式 本次专栏应该会以文章的形式先和大家见面，后续还会根据能力以原本的文章为准录制视频版本。\n专栏平台 就如前一篇文章公布的内容一致，会优先发表在我的公众号上，当然目前我还在努力寻找其他的专栏平台。\n我的预想是尽量能够让大家一篇一篇的购买，不需要必须购买全部专辑，这样朋友们可以根据自己具体需求来进行购买。\n而目前我的百家号收费专栏也在申请之中，不知道会不会顺利申请下来。有新的平台入驻之后，我会进行通知的。\n专栏内容 在规划专栏的时候，大部分时间基本都放在了规划内容上。包括目录的编排，内容取舍等等。\n目前规划中的专栏打算从基础开始，到 Python 开发，再到一些应用基础，比如 AI 数学，AI 英语等。而由于这些内容都会是针对 AI 学习的，所以并不会是那种很全面的学习资料。\n比如说 Python，我们不会讲的很系统，重点会放在数据结构以及数据分析和开发方面。数学等基础当然也会是一致的。\n当基础篇学完之后，接下来就是重点了，会根据三个不同的 AI 方向来进行讲解，分别包括：BI、NLP 以及 CV。\n基本目录如下： 第一篇： Python 基础（AI 方向） 第二篇：核心基础能力 第三篇：核心知识增强 第四篇：BI 基础 第五篇：CV 基础 第六篇：NLP 基础 第七篇：BI 进阶 第八篇：CV 进阶 第九篇：NLP 进阶 拓展篇 1: 数学 拓展篇 2: 英语 详细目录如下（进阶部分目录未完全展开）： 一些说明和后续 本专栏暂时价格上还未进行调研，反正第一篇 Python 部分应该会是全免费发放。毕竟 Python 课程网上太多了，而且同质化严重，收费感觉没太大必要。所以，咱们写的时候再慢慢想。小伙伴们也可以留言来说说大家期望是一个什么价格，我根据大家留言再结合自己的实际情况最后定价。\n另外，除了 Python 部分之外，数学和英语部分也是免费的。说实话，我数学和英语并不是很好，这两部分我仅仅给大家一个总结和方向，反正也是独立内容，均可以去网上找相关替代的。\n专栏在完成之后，会更新一些关于算法和数据库的内容，然后会考虑整篇投放到其他平台去进行完整售卖。\n结尾 好了，结束语也无需说太多。让我们一起期待吧，希望在我的课程完成的那一天，各位小伙伴们能完全入门并掌握人工智能。\n本次课程的所有代码都会上传到 Github 上，地址：\n","permalink":"https://hivan.me/posts/ai-cheats-information/","summary":"Hi，大家好。我时茶桁。\n最近，我花了几天时间仔细思考了一下即将要开始写的专栏《AI 秘籍》，再根据自己的能力大概规划了一下。目前大致已经理出了一些相关信息可以分享给大家。","title":"新专辑《AI 秘籍》，你所感兴趣的一切"},{"content":"什么是神经网络？ 神经网络是对大脑神经过程的复制。\n它是在计算机上构建的大脑模拟。 神经网络，无论是生物的还是人工的，都由大量的简单单元和神经元组成，它们相互接收和传输信号。\n它由细胞体和连接神经元的导线组成。\n用生物学语言来说 ：\n为神经元提供输入的电线称为树突。 在某些情况下，神经元会向另一个神经元发送信号，这些向外发送信号的导线被称为轴突。 轴突可能与一个或多个树突相连，这种交叉点称为突触。 这个过程会随着我们的成长而不断调整，这种 \u0026ldquo;调整 \u0026ldquo;被称为记忆或学习。\n什么是深度学习？ 深度学习是一种机器学习技术，由相互连接的多层简单处理单元组成。\n它的灵感来源于大脑处理视觉信息的方式。\n为什么要开发人工神经网络？ 开发人工神经网络（ANN）的原因之一是为了帮助神经科学（研究大脑和神经系统）。\n人们相信，通过绘制人脑图谱，我们可以了解意识和智力背后的秘密。 我们已经能够识别异常功能，并帮助大脑避免异常功能。 例如\u0026ndash;解决老年痴呆症、因受伤造成的损伤和发育障碍。 开发人工神经网络（ANN）的另一个原因是为了建立更好的人工智能和机器学习技术。\n因为，大脑是一个极其复杂的信息处理系统。 人工神经网络的特点 ： ANN 由许多神经元组成，可以同时处理信息。这意味着，我们可以同时处理大量数据，从而提高了效率。\n神经元可以同时存储（就像内存一样）和处理信息，因此从存储器中检索数据不会有任何延迟，因而速度很快。\n是的，ANN 可以快速处理信息，但很难应用于 \u0026ldquo;传统计算机\u0026rdquo;（单机处理），因为它一次只能完成一项任务。这就是 GPU 的用武之地。\n听说过 GPU 吗？ GPU 是图形处理单元（Graphical Processing Unit）的缩写，它可以进行并行处理，而不是像传统计算机那样进行单一处理。因此，神经网络可以快速完成工作或处理信息。\n构建神经网络 ： 权重在神经网络中扮演着重要角色，它通过控制每个输入，让网络从这些数据中学习，从而做出准确的预测。\n但是，什么是权重？ 权重就像可调节的旋钮，决定着每个输入对最终输出的影响程度。\n例如，为了找到适当的平衡（数据），我们要给输入值加上适当的权重。\n因此，通过将每个输入值（神经元）与权重相乘并相加，我们就能实现 \u0026ldquo;线性组合\u0026rdquo;。\n线性组合公式 ： 考虑到我们有 4 个输入，因此我们也需要 4 个权重来平衡它，而且还会有一个额外的固定值，称为截距（偏差）。\n截距值是一个偏置值，用作基准值，这样即使输入值为零，网络也能做出预测。 计算公式：\n线性组合 = [截距 + Weight 1 × Input 1 + Weight 2 × Input 2 + Weight 3 × Input 3 + Weight 4 × Input 4］\n问：请考虑以下表达式\n10.0 + 5.4 × 8 + (-10.2) × 5 + (-0.1) × 22 + 101.4 × (-5) + 0.0 × 2 + 12.0 × (-3) = -543.0\n(i) 表达式中的截距（偏差）项是什么？\n\u0026ldquo;10.0 \u0026ldquo;是截距（偏差）数，因为它没有乘以任何变量。\n(ii) 这里的输入是什么？\n\u0026ldquo;8,5,22,-5,2,-3 \u0026ldquo;是输入值，因为它是乘法中的第二个数字。\n实现线性组合后，再将其传递给 \u0026ldquo;激活函数\u0026rdquo;。\n激活功能： 激活函数就像一个开关，它决定信号是否应该通过，使神经网络能够有效地学习和解决不同的问题。\n为图像识别、自然语言处理等进行预测。 激活函数示例 ： 激活函数的一些示例如下\n识别函数：什么也不做，只输出线性组合（与线性回归相同，不提供任何新信息，因此很少使用） 步进功能：如果线性组合值大于 0，则通过信号，否则什么也不做 Sigmoid 函数：阶跃函数的 \u0026ldquo;软 \u0026ldquo;版本 通过线性组合激活函数实现的神经元输出用于预测或决策。\n\u0026ldquo;感知器\u0026ndash;人工神经网络（ANN）之母\u0026rdquo;： Perceptron 是一种使用阶跃激活函数的简单神经元模型。\n它被用作二元分类任务中的简单分类器。 由于它是第一个正式的神经网络模型，因此被称为 \u0026ldquo;ANN 之母\u0026rdquo;。 \u0026ldquo;现在，让我们回到神经网络的构建上来。 网络架构由层级组成，例如 ：\n输入层：由作为输入数据的神经元组成。例如，用于图像识别的图像像素值。 隐藏层：接收输入层的输出，并将自己的输出传递给下一层。 输出层：产生网络的最终结果。例如，用于人脸识别的人的概率值。 为了在这些层中进行线性组合，我们应该能够找到合适的权重。\n反向传播 - 找到合适的权重 ： 在过去（20 世纪 80 年代之前），人们曾使用过感知器算法，但寻找权重需要花费大量时间。 因此，人们引入了反向传播算法。它通过层层递进和递退来确定合适的权重，从而做出准确的预测。 现在，让我们举个例子，来识别图像。\n建立分类器，对图像显示的是 \u0026ldquo;X \u0026ldquo;还是 \u0026ldquo;O \u0026ldquo;进行分类 这里是一个 5 × 5 的网格，因此每幅图像由 25 个像素组成。阴影像素为 1，其他空白像素为 0。\n现在，我们应该应用权重，其中在中心位置，权重假设为-1，而在近中心像素位置，权重假设为 1：\n因此，在这里，如果线性组合为负数，即激活度为零，则为 \u0026ldquo;X\u0026rdquo;；如果为正数，则为 \u0026ldquo;O\u0026rdquo;。\n对第一幅图像进行线性组合 ：\n(忽略 0 值权重，得到） -\u0026gt; 1 × -1 = -1\n因此，我们得到 \u0026ldquo;X”\n对第二幅图像进行线性组合：\n(忽略 0 值权重，我们得到） -\u0026gt; 1 × 1 + 1 × 1 + 1 × 1 + 1 × 1 = 4 因此我们得到 \u0026ldquo;O\u0026rdquo;\n到目前为止，我们已经了解了 -\u0026gt; 多层网络（超过一层的神经网络）、非线性函数（阶跃激活函数和 Sigmoid 激活函数）、学习规则（如反向传播）。\n让我们进入高级神经网络。\n卷积神经网络 ： 使用感知器或线性回归可以进行图像处理，但由于需要大量权值，而且无法有效检测图像特征，因此效果和效率都不高。\n因此，为了解决这些局限性，人们引入了卷积神经网络。\nCNN 或卷积神经网络由卷积层组成，可以自动学习和提取图像特征，如颜色、图案、边缘等。 例如，CNN 可用于动物检测、标志检测等。 如果我们想使用传统方法检测图像或识别图像，它将使用图像中的像素位置来检测物体。因此，我们必须有一张相似的图像才能做到这一点，但对于卷积神经网络来说，这并非必要。\n例如，我们有一张位于图像中心的停车标志的训练图像，然后我们会得到一张测试图像，该图像的右上角有一个停车标志。由于训练图像和测试图像的像素值和位置不同，因此无法使用感知器进行检测。不过，通过使用卷积神经网络，它可以成功检测出图像中任何位置的停车标志。\n生成式人工智能（Generative AI）： 生成式人工智能是人工智能的一种，可以生成文本、图像、音频和合成数据等各种类型的内容。\n它可以是\u0026hellip;..：\n监督学习法 无监督学习法 半监督学习法 判别模型用于通过标注数据的训练进行分类或预测。\n生成模型用于生成新数据，如预测序列中的下一个单词。\n生成对抗网络（GAN）： 其原理是让两个神经网络相互竞争。\n一个网络将生成与训练数据类似的图像。 另一个网络将对生成的图像和训练图像进行分类。 这样做是为了生成逼真的图像。\n上述图像由英伟达公司开发的 GAN 生成。\n将人工智能应用于现实问题比解决谜题和游戏更具挑战性。在现实世界的场景中，可能出现的状态数量之多令人目不暇接，使得穷举式搜索或巧妙的启发式方法无法奏效。此外，由于我们无法控制的因素，行动的结果并不总是可以预测的，这就引入了随机性。为了解决这些复杂问题，我们需要将不确定性和概率的概念纳入算法，同时利用先进的神经网络，使我们能够有效地解决现实世界中的人工智能问题。\n康康康康恐龙康。最后，你已经掌握了基本的神经网络和高级神经网络的基本知识。\n","permalink":"https://hivan.me/posts/artificial-neural-network/","summary":"神经元、如何构建网络、高级神经网络\n人工神经网络是人工智能（AI）中重要而有趣的一部分。","title":"人工神经网络"},{"content":"\nChatGPT 基于强大的 GPT-3 模型构建，能够进行引人入胜、有意义且令人印象深刻的类人对话。它可以写诗、回答复杂的问题、辅导各种科目、翻译语言，甚至模仿著名作家的写作风格。从本质上讲，它重新定义了我们认为人工智能可能实现的界限。\n然而，ChatGPT 的主要缺点是它缺乏实时互联网数据访问。这意味着，虽然 ChatGPT 可以生成高度智能且上下文准确的响应，但其知识基本上被及时冻结，截止日期为 2021 年 9 月。\n那么，当出现需要通过将 Google 的 Bard API 与 ChatGPT 集成来获取超出此限制的信息的问题时，会发生什么情况呢？\n以下是使用 Python将 Bard API 连接到 ChatGPT 以检索实时数据的分步指南：\n第 1 步：安装非官方 Bard Python 库并检索 Cookie 值（API 密钥） 我正在使用Daniel Park使用逆向工程开发的非官方 Bard 库。这个库是一个非常用户友好的 Python 包。其主要目的是通过 API 从 Google Bard 获取响应。使用 Bard-API，用户可以方便地将 Bard 的自然语言响应集成到他们的 Python 项目和各种应用程序中。\npip install bardapi 您还可以直接从 Github 安装最新版本：\npip install git+https://github.com/dsdanielpark/Bard-API.git from bardapi import Bard token = \u0026#39;xxxxxxx\u0026#39; bard = Bard(token=token) bard.get_answer(\u0026lt;your query\u0026gt;)[\u0026#39;content\u0026#39;] 设置您的 API 密钥\n安装 Bard-API 后，使用 Bard cookie 中的 Secure-1PSID 进行身份验证。尽管非正式地称为 API KEY（Cookie 值），但请记住对其保密以确保安全访问。\n访问 https://bard.google.com/\n按 F12 或右键单击并“检查”\n转到应用程序 → Cookie，并将您的 __Secure-1PSID Cookie 值复制到安全位置。\n步骤 2：从 openai.com获取 OpenAI 密钥并安装 OpenAI 库 访问 OpenAI 网站并获取您的 OpenAI API 密钥。现在安装 OpenAI 库并导入它。\npip install openai import openai openai.api_key = \u0026lt;Your_API_Key\u0026gt; 步骤 3：将 bard 请求结果连接到 gpt-3.5-turbo 模型并设计提示 这里的关键部分是设计将 Bard 结果集成到 ChatGPT API 函数中所需的提示。因此，我为此制定了一个方法：\nquery = input(\u0026#34;Your query\u0026#34;) bard_result = bard.get_answer(query)[\u0026#39;content\u0026#39;] completion = openai.ChatCompletion.create( model=\u0026#34;gpt-3.5-turbo\u0026#34;, messages=[ {\u0026#34;role\u0026#34;: \u0026#34;system\u0026#34;, \u0026#34;content\u0026#34;: \u0026#34;Act as an AI chatbot with access to the internet.\u0026#34;}, {\u0026#34;role\u0026#34;: \u0026#34;user\u0026#34;, \u0026#34;content\u0026#34;: \u0026#34;Provide a well structured and easily readable text by analyzing this: The first content below is the user\u0026#39;s query and the second content below is the result obtained by accessing the internet with the help of google\u0026#39;s search alogoritm. Provide the well structured and good mannered answer by processing the user\u0026#39;s query and the result from Google search algorithm. /n\u0026#34;+query+\u0026#39; /n \u0026#39;+ bard_result} ] ) final_response = completion[\u0026#34;choices\u0026#34;][0][\u0026#34;message\u0026#34;][\u0026#34;content\u0026#34;] print(final_response) 将所有代码封装在一起，得出结果：\nfrom bardapi import Bard import openai openai.api_key = \u0026lt;Your Key\u0026gt; token = \u0026lt;Your Key\u0026gt; bard = Bard(token=token) query = input(\u0026#34;Your query: \u0026#34;) bard_result = bard.get_answer(query)[\u0026#39;content\u0026#39;] completion = openai.ChatCompletion.create( model=\u0026#34;gpt-3.5-turbo\u0026#34;, messages=[ {\u0026#34;role\u0026#34;: \u0026#34;system\u0026#34;, \u0026#34;content\u0026#34;: \u0026#34;Act as an AI chatbot with access to the internet.\u0026#34;}, {\u0026#34;role\u0026#34;: \u0026#34;user\u0026#34;, \u0026#34;content\u0026#34;: \u0026#34;Provide a well structured and easily readable text by analyzing this: The first content below is the user\u0026#39;s query and the second content below is the result obtained by accessing the internet with the help of google\u0026#39;s search alogoritm. Provide the well structured and good mannered answer by processing the user\u0026#39;s query and the result from Google search algorithm. /n\u0026#34;+query+\u0026#39; /n \u0026#39;+ bard_result} ] ) final_response = completion[\u0026#34;choices\u0026#34;][0][\u0026#34;message\u0026#34;][\u0026#34;content\u0026#34;] print(final_response) 与其使用 gpt-3.5-turbo 型号，不如试试 gpt-3.5-turbo-16k 和 gpt-4-0314，效果会更好。\n通过整合像谷歌的 Bard 这样的应用程序接口，ChatGPT 可以超越目前的局限，为用户提供实时、准确的上下文信息。这将大大增强其协助、教育和与用户互动的能力，为人与人工智能的互动增添一个全新的维度。此外，这还将极大地扩展 ChatGPT 的应用范围，为企业、教育工作者、研究人员和个人带来新的机遇。\n我认为这是将互联网接入集成到 ChatGPT 并从 ChatGPT 获得实时见解的最简单方法。\n","permalink":"https://hivan.me/posts/bardapi-chatgpt/","summary":"在人工智能领域，很少有创新能像 OpenAI 的 ChatGPT 一样激发世界的想象力。这种非凡的对话式人工智能改变了我们看待人机交互的方式，展现出一定程度的复杂性、情境意识和创造力，而这些曾经被认为是人类智能的专属领域。","title":"将 Bard API 与 ChatGPT 集成：实时数据访问"},{"content":"\n2023 年 7 月 6 日，OpenAI 宣布 Code Interpreter 将在接下来的一周内向 ChatGPT Plus 用户开放。它可能是增强 ChatGPT 的能力和功能的最佳插件之一。\nCode Interpreter 可以运行代码，允许上传数据，这样您就可以用它来进行数据清理、分析、可视化等许多其他任务。它就像是您指尖上的数据分析师。\n听起来很棒吧？接下来我们来看看。\n我在一项任务中使用了它，这项任务可能需要我花费几个小时才能完成。根据您的任务和对 Python 库的熟悉程度，这项任务甚至可能需要更长时间。\n在使用 ChatGPT 时，我只需要写几句话并上传两个 CSV 文件。\n我将逐步介绍整个过程，以及 Code Interpreter 在理解数据、清理和预处理数据以及创建数据可视化方面的印象深刻之处。\n您需要先启用它 如果您想在聊天中使用代码解释器，需要先通过设置启用它。\n如何启用代码解释器\n开启之后，你在接下来开启新的聊天的时候就可以使用它了。\n来个具体任务 我打算创建一个世界地图可视化，其中点的大小与国家的人口成比例。\n我有两个数据集，一个是国家人口，另一个是以纬度和经度表示的位置。\n您可以轻松地在网上找到这些数据集。如果没有，文末我将提供数据集（以及源代码）。\n让我们继续吧 由于这是一个简单的任务，我没有花费太多时间在提示工程上。这是我向 ChatGPT 请求的第一句话：\n你可以在提示窗口左侧使用加号上传数据集。\n使用+号上传数据集\n首先要做的是读取数据集，这是显而易见的事情。但这还不够令人印象深刻！\n# Code Interpreter 生成的代码 import pandas as pd # 将 CSV 数据加载到 pandas DataFrame 中 population_data = pd.read_csv(\u0026#39;/mnt/data/country_population_historic.csv\u0026#39;) # 显示 DataFrame 的前几行 population_data.head() 然后，ChatGPT 要求提供国家位置：\n由于我已经拥有相关数据了，所以我要做的就只是上传它：\n# Code Interpreter 生成的代码 # 加载国家坐标数据 country_coordinates = pd.read_csv(\u0026#39;/mnt/data/country_coordinates.csv\u0026#39;) # 显示前几行 country_coordinates.head() 数据清洗和预处理 读取数据后的下一步是清洗和预处理数据。我相信你已经听过很多次，这一步通常需要最多的时间。\n在创建世界地图可视化之前，有一些问题需要处理。ChatGPT 检测到它们并在不到一分钟的时间内提供了解决方案。相当令人印象深刻！\n上面代码段中的代码:\n# 由代码解释器生成的代码 # 检查数据框中的国家名称是否一致 population_countries = set(population_data[\u0026#39;Country Name\u0026#39;]) coordinates_countries = set(country_coordinates[\u0026#39;name\u0026#39;]) # 人口数据中的国家但不在坐标数据中 missing_in_coordinates = population_countries - coordinates_countries # 坐标数据中的国家但人口数据中没有 missing_in_population = coordinates_countries - population_countries 在坐标数据集和人口数据集中都有一些国家缺失。\nChatGPT 在上面的代码片段中做了什么：\n创建了 Python 集合，包含两个数据集中的国家名称。 分别比较这些集合，找到它们之间的差异。 ChatGPT 在不到一分钟的时间内解决了这个问题：\n以上片段中的代码：\n# Code Interpreter 生成的代码 # 将人口数据集中的国家名称映射到坐标数据集中 country_name_mapping = { \u0026#39;Bahamas, The\u0026#39;: \u0026#39;Bahamas\u0026#39;, \u0026#39;Brunei Darussalam\u0026#39;: \u0026#39;Brunei\u0026#39;, \u0026#39;Cabo Verde\u0026#39;: \u0026#39;Cape Verde\u0026#39;, \u0026#39;Congo, Dem. Rep.\u0026#39;: \u0026#39;Congo [DRC]\u0026#39;, \u0026#39;Congo, Rep.\u0026#39;: \u0026#39;Congo [Republic]\u0026#39;, \u0026#34;Cote d\u0026#39;Ivoire\u0026#34;: \u0026#34;Côte d\u0026#39;Ivoire\u0026#34;, \u0026#39;Czechia\u0026#39;: \u0026#39;Czech Republic\u0026#39;, \u0026#39;Egypt, Arab Rep.\u0026#39;: \u0026#39;Egypt\u0026#39;, \u0026#39;Gambia, The\u0026#39;: \u0026#39;Gambia\u0026#39;, \u0026#39;Hong Kong SAR, China\u0026#39;: \u0026#39;Hong Kong\u0026#39;, \u0026#39;Iran, Islamic Rep.\u0026#39;: \u0026#39;Iran\u0026#39;, \u0026#34;Korea, Dem. People\u0026#39;s Rep.\u0026#34;: \u0026#39;North Korea\u0026#39;, \u0026#39;Korea, Rep.\u0026#39;: \u0026#39;South Korea\u0026#39;, \u0026#39;Kyrgyz Republic\u0026#39;: \u0026#39;Kyrgyzstan\u0026#39;, \u0026#39;Lao PDR\u0026#39;: \u0026#39;Laos\u0026#39;, \u0026#39;Macao SAR, China\u0026#39;: \u0026#39;Macau\u0026#39;, \u0026#39;Micronesia, Fed. Sts.\u0026#39;: \u0026#39;Micronesia\u0026#39;, \u0026#39;Myanmar\u0026#39;: \u0026#39;Myanmar [Burma]\u0026#39;, \u0026#39;North Macedonia\u0026#39;: \u0026#39;Macedonia [FYROM]\u0026#39;, \u0026#39;Russian Federation\u0026#39;: \u0026#39;Russia\u0026#39;, \u0026#39;Sao Tome and Principe\u0026#39;: \u0026#39;São Tomé and Príncipe\u0026#39;, \u0026#39;Slovak Republic\u0026#39;: \u0026#39;Slovakia\u0026#39;, \u0026#39;Syrian Arab Republic\u0026#39;: \u0026#39;Syria\u0026#39;, \u0026#39;Turkiye\u0026#39;: \u0026#39;Turkey\u0026#39;, \u0026#39;Venezuela, RB\u0026#39;: \u0026#39;Venezuela\u0026#39;, \u0026#39;Yemen, Rep.\u0026#39;: \u0026#39;Yemen\u0026#39;, \u0026#39;Eswatini\u0026#39;: \u0026#39;Swaziland\u0026#39; } # 清理人口数据集 population_data_clean = population_data.replace({\u0026#34;Country Name\u0026#34;: country_name_mapping}) # 删除在坐标数据集中不存在的行 population_data_clean = population_data_clean[population_data_clean[\u0026#39;Country Name\u0026#39;].isin(coordinates_countries)] # 将人口数据与国家坐标数据合并 merged_data = pd.merge(population_data_clean, country_coordinates, left_on=\u0026#39;Country Name\u0026#39;, right_on=\u0026#39;name\u0026#39;) # 只保留必要的列 merged_data = merged_data[[\u0026#39;Country Name\u0026#39;, \u0026#39;2022\u0026#39;, \u0026#39;latitude\u0026#39;, \u0026#39;longitude\u0026#39;]] # 重命名列以增加清晰度 merged_data.columns = [\u0026#39;Country\u0026#39;, \u0026#39;Population\u0026#39;, \u0026#39;Latitude\u0026#39;, \u0026#39;Longitude\u0026#39;] merged_data.head() 清理和预处理的数据\n现在我们已经有了整洁和清晰的数据格式。这个步骤通常需要很多时间和人工。ChatGPT 在大约一分钟内完成了这一步。\n创建可视化 ChatGPT 首先尝试使用 Matplotlib 库进行创建。然而，由于没有世界地图图像而失败：\n然后，我只是要求它使用 Plotly，这就是我一开始想到的。\n以上代码段中的代码：\n# 代码由代码解释器生成 import plotly.graph_objects as go # 创建散点地理图 fig = go.Figure(data=go.Scattergeo( lon = merged_data[\u0026#39;Longitude\u0026#39;], lat = merged_data[\u0026#39;Latitude\u0026#39;], text = merged_data[\u0026#39;Country\u0026#39;] + \u0026#39;: \u0026#39; + merged_data[\u0026#39;Population\u0026#39;].astype(str), mode = \u0026#39;markers\u0026#39;, marker = dict( size = (merged_data[\u0026#39;Population\u0026#39;] / 1000000) ** 0.5, # Take square root to better visualize differences sizemode = \u0026#39;diameter\u0026#39;, sizeref = 1, sizemin = 4, color = merged_data[\u0026#39;Population\u0026#39;], colorscale = \u0026#39;Viridis\u0026#39;, colorbar_title = \u0026#34;Millions\u0026#34;, ) )) # 更新布局 fig.update_layout( title_text = \u0026#39;2022 World Population\u0026#39;, geo = dict( showframe = False, showcoastlines = True, projection_type = \u0026#39;equirectangular\u0026#39; ) ) fig.show() 它不能在浏览器中显示交互式世界地图，但成功编写了生成它的代码。我所要做的就是将代码复制粘贴到 Jupyter 笔记本中。\n这是最终结果：\n互动世界地图，显示国家人口\n最后的话 我们所做的事情：\n读取数据集 清洗、预处理和合并它们 创建互动数据可视化 我们所要做的只是写两个句子（并告诉 ChatGPT 使用 Plotly）。我认为这非常令人印象深刻！\n数据 本文已经结束。\n文章最后，我将提供数据以及一个 jupyter notebook 内容，和往常一样，数据将付费查看，以获取一些成本。有想要的朋友可以去我公众号内搜索本文购买：\n","permalink":"https://hivan.me/posts/chatgpt%E4%BB%A3%E7%A0%81%E8%A7%A3%E9%87%8A%E5%99%A8/","summary":"\u003cstrong\u003e创建一个交互式世界地图，显示国家人口数量，配以简短的句子描述。\u003c/strong\u003e","title":"ChatGPT 代码解释器：如何为我节省数小时的工作"},{"content":"贪多嚼不烂的道理我还是明白的，于是，我给自己提前就设定了一个目标：\n游戏可以运行实现简单的规则减少 BUG 的存在可以开始和结束\n带着这四个最朴素的愿景，让我们开始吧。\n一贯的，一上来我就将问题抛给了 chatGPT\n请用 Python3.10 编写一个贪吃蛇的游戏，给我完整的代码。 这里我们需要注意一下，你的描述越清晰，得到的结果越有可能是你想要的。在这里，我告诉了 chatGPT，我希望用 Python 写一个游戏，而且版本号是 3.10。并且，我告诉了 chatGPT，请给我完整的代码。因为如果我不这么说的话，那很有可能 chatGPT 会先给我一堆的步骤而不是实际的代码。\n在之后，我得到了 chatGPT 的答复：\n完整代码如下：\nimport pygame import random # 游戏窗口的大小 WINDOW_WIDTH = 800 WINDOW_HEIGHT = 600 # 蛇身和食物的大小 BLOCK_SIZE = 20 # 定义颜色 WHITE = (255, 255, 255) BLACK = (0, 0, 0) RED = (255, 0, 0) # 初始化 Pygame pygame.init() # 创建游戏窗口 window = pygame.display.set_mode((WINDOW_WIDTH, WINDOW_HEIGHT)) pygame.display.set_caption(\u0026#34;贪吃蛇游戏\u0026#34;) clock = pygame.time.Clock() def game_loop(): game_over = False # 蛇的初始位置和速度 snake_x = WINDOW_WIDTH // 2 snake_y = WINDOW_HEIGHT // 2 snake_x_change = 0 snake_y_change = 0 # 食物的初始位置 food_x = round(random.randrange(0, WINDOW_WIDTH - BLOCK_SIZE) / BLOCK_SIZE) * BLOCK_SIZE food_y = round(random.randrange(0, WINDOW_HEIGHT - BLOCK_SIZE) / BLOCK_SIZE) * BLOCK_SIZE while not game_over: for event in pygame.event.get(): if event.type == pygame.QUIT: game_over = True elif event.type == pygame.KEYDOWN: if event.key == pygame.K_LEFT: snake_x_change = -BLOCK_SIZE snake_y_change = 0 elif event.key == pygame.K_RIGHT: snake_x_change = BLOCK_SIZE snake_y_change = 0 elif event.key == pygame.K_UP: snake_y_change = -BLOCK_SIZE snake_x_change = 0 elif event.key == pygame.K_DOWN: snake_y_change = BLOCK_SIZE snake_x_change = 0 # 更新蛇的位置 snake_x += snake_x_change snake_y += snake_y_change # 绘制游戏窗口 window.fill(BLACK) pygame.draw.rect(window, RED, [food_x, food_y, BLOCK_SIZE, BLOCK_SIZE]) pygame.draw.rect(window, WHITE, [snake_x, snake_y, BLOCK_SIZE, BLOCK_SIZE]) pygame.display.update() # 碰撞检测 if snake_x == food_x and snake_y == food_y: food_x = round(random.randrange(0, WINDOW_WIDTH - BLOCK_SIZE) / BLOCK_SIZE) * BLOCK_SIZE food_y = round(random.randrange(0, WINDOW_HEIGHT - BLOCK_SIZE) / BLOCK_SIZE) * BLOCK_SIZE clock.tick(20) # 控制游戏的帧率 pygame.quit() # 启动游戏 game_loop() 从代码里我们可以看出来，文件引用了库pygame以及random, 其中，pygame需要我们单独安装一下:\nconda install pygame 建立了一个snake_v1.py的文件，并把这段代码粘贴到文件里之后，我开始尝试运行它：\npython ~/xx/game/snake_v1.py 事情果然没有想的那么简单，根本就是毫无动静。我开始询问 chatGPT\nchatGPT 给到的答案似乎没有什么意义，这些事情在开始之前我就已经做好了。在短暂的思考之后，我忽然想到，是否因为我是 Mac 系统，从而导致了窗口不出现，于是我继续问问题：\n然后继续问：\n这一次，我抓到了重点。不明白为什么刚才我提到我是 Mac 系统的时候他不告诉我，先不管这些，在其中添加这段代码后，游戏终于可以运行了：\nimport pygame import random # 游戏窗口的大小 WINDOW_WIDTH = 800 WINDOW_HEIGHT = 600 # 蛇身和食物的大小 BLOCK_SIZE = 20 # 定义颜色 WHITE = (255, 255, 255) BLACK = (0, 0, 0) RED = (255, 0, 0) # 初始化 Pygame pygame.init() # 创建游戏窗口 window = pygame.display.set_mode((WINDOW_WIDTH, WINDOW_HEIGHT)) pygame.display.set_caption(\u0026#34;贪吃蛇游戏\u0026#34;) pygame.display.flip() clock = pygame.time.Clock() def game_loop(): game_over = False # 蛇的初始位置和速度 snake_x = WINDOW_WIDTH // 2 snake_y = WINDOW_HEIGHT // 2 snake_x_change = 0 snake_y_change = 0 # 食物的初始位置 food_x = round(random.randrange(0, WINDOW_WIDTH - BLOCK_SIZE) / BLOCK_SIZE) * BLOCK_SIZE food_y = round(random.randrange(0, WINDOW_HEIGHT - BLOCK_SIZE) / BLOCK_SIZE) * BLOCK_SIZE while not game_over: for event in pygame.event.get(): if event.type == pygame.QUIT: game_over = True elif event.type == pygame.KEYDOWN: if event.key == pygame.K_LEFT: snake_x_change = -BLOCK_SIZE snake_y_change = 0 elif event.key == pygame.K_RIGHT: snake_x_change = BLOCK_SIZE snake_y_change = 0 elif event.key == pygame.K_UP: snake_y_change = -BLOCK_SIZE snake_x_change = 0 elif event.key == pygame.K_DOWN: snake_y_change = BLOCK_SIZE snake_x_change = 0 # 更新蛇的位置 snake_x += snake_x_change snake_y += snake_y_change # 绘制游戏窗口 window.fill(BLACK) pygame.draw.rect(window, RED, [food_x, food_y, BLOCK_SIZE, BLOCK_SIZE]) pygame.draw.rect(window, WHITE, [snake_x, snake_y, BLOCK_SIZE, BLOCK_SIZE]) pygame.display.update() # 碰撞检测 if snake_x == food_x and snake_y == food_y: food_x = round(random.randrange(0, WINDOW_WIDTH - BLOCK_SIZE) / BLOCK_SIZE) * BLOCK_SIZE food_y = round(random.randrange(0, WINDOW_HEIGHT - BLOCK_SIZE) / BLOCK_SIZE) * BLOCK_SIZE clock.tick(20) # 控制游戏的帧率 pygame.quit() # 启动游戏 game_loop() 不知道各位看出什么问题了没有，问题如下：\n动作太快了，完全是地狱级别的难度；\n蛇似乎不会增长，吃了物品没有变长；\n撞到墙壁后游戏虽然停止了，但是没任何提示，蛇就这么消失了；\n于是，我只能让 chatGPT 再帮我优化代码：\n当然，又有了其他问题，\n蛇在撞到自己之后没有办法结束游戏\n游戏无法退出\n游戏无法重新开始\nGame Over 之后显示的是乱码\n当然，有编程经验的人大概能明白，乱码的话一定是因为中文显示不正确，而目前，我也没打算支持中文。干脆用英文好了。\n于是，我又开始提需求，在连续三四轮这种询问之后，终于来到了最后一次：\n然后我得到了如下代码：\nimport pygame import random # 游戏窗口的大小 WINDOW_WIDTH = 800 WINDOW_HEIGHT = 600 # 蛇身和食物的大小 BLOCK_SIZE = 20 # 定义颜色 WHITE = (255, 255, 255) BLACK = (0, 0, 0) RED = (255, 0, 0) # 初始化 Pygame pygame.init() # 创建游戏窗口 window = pygame.display.set_mode((WINDOW_WIDTH, WINDOW_HEIGHT)) pygame.display.set_caption(\u0026#34;贪吃蛇游戏\u0026#34;) pygame.display.flip() clock = pygame.time.Clock() def game_loop(): game_over = False game_restart = False game_paused = False while not game_restart: if game_over: show_game_over() # 蛇的初始位置和速度 snake_x = WINDOW_WIDTH // 2 snake_y = WINDOW_HEIGHT // 2 snake_x_change = 0 snake_y_change = 0 # 蛇身的初始位置和长度 snake_body = [] snake_length = 1 # 食物的初始位置 food_x = round(random.randrange(0, WINDOW_WIDTH - BLOCK_SIZE) / BLOCK_SIZE) * BLOCK_SIZE food_y = round(random.randrange(0, WINDOW_HEIGHT - BLOCK_SIZE) / BLOCK_SIZE) * BLOCK_SIZE score = 0 while not game_over: for event in pygame.event.get(): if event.type == pygame.QUIT: pygame.quit() quit() elif event.type == pygame.KEYDOWN: if event.key == pygame.K_LEFT and snake_x_change != BLOCK_SIZE: snake_x_change = -BLOCK_SIZE snake_y_change = 0 elif event.key == pygame.K_RIGHT and snake_x_change != -BLOCK_SIZE: snake_x_change = BLOCK_SIZE snake_y_change = 0 elif event.key == pygame.K_UP and snake_y_change != BLOCK_SIZE: snake_y_change = -BLOCK_SIZE snake_x_change = 0 elif event.key == pygame.K_DOWN and snake_y_change != -BLOCK_SIZE: snake_y_change = BLOCK_SIZE snake_x_change = 0 elif event.key == pygame.K_SPACE: game_paused = not game_paused elif event.key == pygame.K_q: pygame.quit() quit() if game_paused: continue # 更新蛇的位置 snake_x += snake_x_change snake_y += snake_y_change # 碰撞检测 if snake_x == food_x and snake_y == food_y: # 蛇吃到了食物，增加长度和得分 snake_length += 1 score += 1 food_x = round(random.randrange(0, WINDOW_WIDTH - BLOCK_SIZE) / BLOCK_SIZE) * BLOCK_SIZE food_y = round(random.randrange(0, WINDOW_HEIGHT - BLOCK_SIZE) / BLOCK_SIZE) * BLOCK_SIZE # 更新蛇的身体 snake_head = [snake_x, snake_y] snake_body.append(snake_head) if len(snake_body) \u0026gt; snake_length: del snake_body[0] # 检测碰撞 if snake_head in snake_body[:-1] or snake_x \u0026lt; 0 or snake_x \u0026gt;= WINDOW_WIDTH or snake_y \u0026lt; 0 or snake_y \u0026gt;= WINDOW_HEIGHT: game_over = True # 绘制游戏窗口 window.fill(BLACK) pygame.draw.rect(window, RED, [food_x, food_y, BLOCK_SIZE, BLOCK_SIZE]) for body_part in snake_body: pygame.draw.rect(window, WHITE, [body_part[0], body_part[1], BLOCK_SIZE, BLOCK_SIZE]) # 显示实时分数 font = pygame.font.Font(None, 28) text = font.render(\u0026#34;Score: \u0026#34; + str(score), True, WHITE) window.blit(text, (10, 10)) pygame.display.update() clock.tick(10) # 控制游戏的帧率 # 重新开始游戏 game_loop() def show_game_over(): font = pygame.font.Font(None, 36) text = font.render(\u0026#34;Game Over. Press Space to restart\u0026#34;, True, WHITE) text_rect = text.get_rect(center=(WINDOW_WIDTH // 2, WINDOW_HEIGHT // 2)) while True: for event in pygame.event.get(): if event.type == pygame.QUIT: pygame.quit() quit() elif event.type == pygame.KEYDOWN and event.key == pygame.K_SPACE: game_loop() window.fill(BLACK) window.blit(text, text_rect) pygame.display.update() clock.tick(10) # 启动游戏 game_loop() 大家可以看看效果：\n我们可以看到，这个游戏相对来说是比较完整了，速度正常了，有计分系统，蛇能正常增长，碰到自己游戏会结束，碰到墙壁后也会 Game Over，并且可以通过 SPACE 来重新开始。并且，我还加入了按 Q 键的时候退出的功能。\n当然，还可以继续完善，比如随着时间的流逝，速度上可以渐渐加快等等。就看你怎么想，然后其他的交给 chatGPT。\n通过这次的示例演示，其中重点不是教大家如何做一个贪吃蛇游戏，而是教大家如何利用 chatGPT 来解决你需要解决的问题。当然，我需要收回我开头说的话，chatGPT 并不能帮你解决你不熟悉的问题。就比如，如果我完全不懂这其中内容的话，可能我窗口都打不开，我完全都不知道我什么时候才能解决 Mac 系统中不一样的部分，而也正是因为有一些简单的经验，才让我考虑的那个层面，从而针对性提问解决了问题。\n所以要记住，AI 并不能帮你解决你完全不懂的问题，起码，你要知道你想问什么，也要知道问题大概卡在哪里了，针对性继续提问。\n最后，友情提示一下，不要用 API 来完成这一次次的对话，经验之谈，去买个 Plus，比 API 交互便宜多了。你看那一串串的代码重复的给你写出来，你完全不知道会耗费多少 Token。那些宝贵的 Token，还是用在聊天窗无法完成的任务上比较合适。\n","permalink":"https://hivan.me/posts/use-ai-to-write-a-snake-game/","summary":"在完成一些简单的任务之后，我觉得 ChatGPT 可以帮我完成一些更为复杂，甚至于可能我并不是特别擅长的任务，而我想到了，也许，可以帮我写一个简单的游戏。","title":"利用 AI 写一个『贪吃蛇游戏』"},{"content":"Google 聊天 GPT 嫉妒 Bing Chat 及其在您搜索时与您聊天或使用最新的 GPT-4 语言模型的能力吗？不需要。只需从网上商店获取适用于 Google 的 ChatGPT，您就可以将 ChatGPT 与 Google 搜索一起使用。事实上，只需进行一次普通的 Google 搜索，在结果旁边，您也会收到来自 ChatGPT 的回复，这有时比 Google 结果本身更有用。\nMerlin 想要 ChatGPT 在您上网的任何地方响应任何内容？Merlin 将 ChatGPT 带到任何网站，因此您可以突出显示任何文本或网页，并要求 ChatGPT 对其做出响应。您可以让它为您总结一个网页，或为您提供 YouTube 视频的纲要，这样您就不需要全部观看了。\n{% asset_img 20230601164209.png img %}\nTalkBerry 为什么要在 ChatGPT 上打字，而不是直接与 ChatGPT 对话呢？使用 TalkBerry，您可以简单地与 ChatGPT 通话。只需安装扩展程序并确保您的麦克风或耳机已插入，即可开始使用。使用 TalkBerry，您可以节省大量在输入上的时间，或者将 ChatGPT 用作语言导师，让它聆听并帮助您提高发音和语言理解能力。\nTweetGPT 使用 TweetGPT 可以让您的社交媒体游戏更上一层楼。TweetGPT 是 ChatGPT 的插件，利用 AI 聊天机器人工具的强大功能，制作更有趣、更尖刻、更具吸引力或更友好的推文和回复。您可以选择要发布的主题、您的情绪基调和语言，ChatGPT 将完成剩下的工作。如果您对某些措辞不满意，您甚至可以在之后编辑该消息。\n{% asset_img 20230601164220.png img %}\nGPT-EZ 如果您不喜欢 ChatGPT 界面并想将其更改为您自己的喜好，请尝试 GPT-EZ。它允许您自定义 ChatGPT 网站的 UI，包括配色方案、字体样式和其他选项。此外，它还可以让您更轻松地复制和继续与 ChatGPT 的对话，并让您更轻松地下载对话日志。\nSnackPrompt 通过使用一些评价最高的提示来充分利用 AI 聊天机器人。SnackPrompt 列出并排名全球其他聊天机器人用户的最佳提示，让您可以访问一些最新和最强大的 AI 功能。\n{% asset_img 20230601164230.png img %}\nWebChatGPT ChatGPT 的最大限制之一是它无法访问最近的信息。即使您使用的是最新的 GPT-4 语言模型，它仍然只能访问 2021 年之前的信息。使用 WebChatGPT，您可以让 ChatGPT 能够在网络上搜索更多最新的信息来源。从 Chrome 网上应用店获取扩展程序，在使用 ChatGPT 时只需将其打开即可享受这一方便的功能。\nYouTube Summary 喜欢 YouTube 教程，但不想看完序言？让此 ChatGPT 扩展为您总结说明。只需从 YouTube 视频页面获取转录内容，然后将其输入到插件中，您就会立即获得摘要。它还适用于文章、电子邮件或科学论文。\n","permalink":"https://hivan.me/posts/best-chatgpt-chrome-extension/","summary":"想要轻松访问 ChatGPT 吗？其中一个最佳方式是通过其一系列 Chrome 扩展程序。这些扩展程序还为您提供更好的使用 ChatGPT 的方法，包括帮助您编写更好的提示以获得更好的响应，或为 ChatGPT 授予搜索互联网的能力，从而提供对更多最新信息的访问。\n这是您现在可以使用的最佳 ChatGPT Chrome 扩展程序。","title":"最佳 ChatGPT Chrome 扩展程序"},{"content":" 系列课程：从零开始接触人工智能大模型（介绍） 导读：了解AI并使用它/他/她们 进入人工智能的大门，学习与之沟通 情感分析的大语言模型 让我们建立一个聊天机器人 GPT 3 VS 其他模型 文本分类 快速构建人工智能应用程序 人工智能可以帮助你总结你的内容 重写和审查 利用 Embedding 实现语意检索 使用人工智能对文档和图像进行索引和分析 使用开源模型节省成本 利用 AI 创建 Excel 插件 使用多步骤提示要求，AI 帮你写测试 使用链式调用简化多步提示语 使用LLMChain连接Google和计算器 Langchain让AI拥有记忆力 利用LangChain让AI做决策 根据垂直需求微调模型 快速倾听和总结音频内容 尝试让机器拥有声音 Digital-human-broadcasting ","permalink":"https://hivan.me/posts/%E4%BB%8E%E9%9B%B6%E5%BC%80%E5%A7%8B%E6%8E%A5%E8%A7%A6%E4%BA%BA%E5%B7%A5%E6%99%BA%E8%83%BD%E5%A4%A7%E6%A8%A1%E5%9E%8B%E7%9B%AE%E5%BD%95/","summary":"\u003cblockquote\u003e\n\u003cp\u003eAGI将成为我们生活中不可或缺的一部分，让我们共同期待并努力实现这一目标。\u003c/p\u003e\u003c/blockquote\u003e","title":"从零开始接触人工智能大模型目录"},{"content":"什么是混合机器学习系统？ 混合机器学习系统结合了两个或更多的机器学习模型或技术，创建出一个更强大、更灵活的 AI 解决方案。这些系统可以发挥每个组成模型的优势，同时弥补它们各自的弱点。组合机器学习模型的方法有多种，例如：\n集成学习：将多个基础模型结合成一个更强大的模型。这可以通过 bagging、boosting 和 stacking 等技术来实现。 多模态学习：集成不同的数据来源（例如文本、图像和音频）以创建更丰富的数据表示，并提高整体性能。 迁移学习：利用从一个领域或任务中获得的知识，以改善另一个领域或任务中的性能。 元学习：训练模型学习如何学习，使它们能够更快地适应新任务。 混合系统的潜力 混合机器学习系统有潜力彻底改变 AI，为解决复杂问题开辟新的可能性。这些系统的一些关键优势包括：\n改善性能：通过结合多个模型，混合系统可以实现比任何单个模型更好的性能。对于过于复杂以至于单个模型无法有效解决的问题尤为如此。 鲁棒性：混合系统可以更好地抵御噪声、过拟合和其他影响单个模型的问题。这在现实世界中的应用中尤其重要，因为数据通常是嘈杂和不完美的。 通用性：混合系统可以处理各种问题、数据类型和任务，这使它们高度适应各种行业和应用。 可迁移性：混合系统可以更轻松地利用从一个领域或任务中获得的知识，以改善另一个领域或任务中的性能，这使它们非常适合具有有限训练数据的任务。 开发混合系统的挑战 尽管有潜力，混合机器学习系统也面临着一些挑战。其中最显著的障碍包括：\n复杂性：设计和实施混合系统可能比单个模型更加复杂。研究人员和实践者需要仔细考虑如何最好地组合模型和技术，以创建一个有效的系统。 可伸缩性：混合系统的增加的复杂性可能使它们更难以扩展，无论是在计算资源方面还是处理大量数据的能力方面。 可解释性：混合系统可以更具挑战性地解释和说明，因为它们涉及多个模型和技术的交互。这可能使得理解系统如何做出决策并确保其正确运行变得更加困难。 训练和适应：与训练单个模型相比，训练混合系统可能需要更多的计算资源和时间。此外，将这些系统适应到新任务或不断变化的条件可能需要大量的工作。 总之，混合机器学习系统代表了 AI 未来的一个有希望的方向。通过利用多个模型和技术的优势，这些系统有潜力在性能、鲁棒性和通用性方面取得重大进展。然而，实现这个潜力需要克服与复杂性、可伸缩性、可解释性和训练相关的挑战。随着研究人员和实践者继续探索这个令人兴奋的领域，我们可以期待在各种行业和应用中看到混合机器学习系统的大量进展。\n","permalink":"https://hivan.me/posts/exploring-the-potential-and-challenges-of-hybrid-machine-learning-systems-in-ai/","summary":"随着机器学习和深度学习的飞速发展，人工智能（AI）正取得飞跃性进展。然而，越来越多的研究者一致认为，AI 演进的下一个阶段在于开发混合机器学习系统。这篇博客文章将探讨这个新兴领域，讨论它的潜力、挑战以及对 AI 未来的影响。","title":"Exploring the Potential and Challenges of Hybrid Machine Learning Systems in AI"},{"content":"目前我仅留了最常用的 SD V1.5 和 SD V2.1 两个模型，大小为 13G。\n另外还需要说明一点，就是我曾经测试过用 NAS 来存储模型使用，完全不能用，暂时没有时间具体去研究到底什么原因。只有老老实实的继续在本地硬盘上跑。所以 NAS 上存了大量模型，真需要用到的时候再复制过来。\n写这篇文章也是因为近期玩模型过程中打算整理一波，一是方便自己，二么也算是对其他小伙伴做些贡献。\nStable Diffusion 各种模型层出不穷，要说完估计需要费一番功夫，所以我摒弃其他小模型，只整理收集大模型，就是 ckpt 和 safetensors。如果你也打算跟着我一起玩模型但是还未安装，可以先参看我之前的文章：\n在 Apple Silicon M1/M2 Mac 上安装和运行 Stable Diffusion\n说实话，我找了好多关于如何在 M1/M2 上安装和运行 Stable Diffusion 的教程和帖子，发现相互之间借鉴的不少，但是能用的确实没几个。 寻找一番后，发现其实没那么复杂。也不知道为什么网上的那么多教程搞得那么复杂，又是这个又是那个的一大堆，简单实现的方式有好几种：\nhttps://www.hivan.me/How%20to%20install%20and%20run%20Stable%20Diffusion%20on%20Apple%20Silicon\n还是先从最基础的模型开始：\nStable Diffusion 其他多数模型基本上都是从这个基础模型上再次训练得到的。\nStable Diffusion v2.1 SDv2.1 提升了人物生成能力，因为 SDv2.0 大量增加了风景、建筑物和动物的数据集，减少了人物的学习量。\nSDv2.1 提高了 NSFW 过滤器准确度，因为 SDv2.0 的成人过滤器过滤的太狠，错误判定很多\n即使是极端长宽比的图像也能顺利生成。\n解剖学的身体和手（特别是手掌）的描写精度提高。\n512 X 512 model : stabilityai/stable-diffusion-2-1-base · Hugging Face\nWe’re on a journey to advance and democratize artificial intelligence through open source and open science.\nhttps://huggingface.co/stabilityai/stable-diffusion-2-1-base\n768 X 768 model: stabilityai/stable-diffusion-2-1 · Hugging Face\nWe’re on a journey to advance and democratize artificial intelligence through open source and open science.\nhttps://huggingface.co/stabilityai/stable-diffusion-2-1\nimg2img model stabilityai/stable-diffusion-2-depth · Hugging Face\nWe’re on a journey to advance and democratize artificial intelligence through open source and open science.\nhttps://huggingface.co/stabilityai/stable-diffusion-2-depth\n重绘 model stabilityai/stable-diffusion-2-inpainting · Hugging Face\nWe’re on a journey to advance and democratize artificial intelligence through open source and open science.\nhttps://huggingface.co/stabilityai/stable-diffusion-2-inpainting\n超分 model stabilityai/stable-diffusion-x4-upscaler · Hugging Face\nWe’re on a journey to advance and democratize artificial intelligence through open source and open science.\nhttps://huggingface.co/stabilityai/stable-diffusion-x4-upscaler\nStable Diffusion V 1.5 runwayml/stable-diffusion-v1-5 at main\nWe’re on a journey to advance and democratize artificial intelligence through open source and open science.\nhttps://huggingface.co/runwayml/stable-diffusion-v1-5/tree/main\nStable Diffusion V 1.4 CompVis/stable-diffusion-v-1-4-original · Hugging Face\nWe’re on a journey to advance and democratize artificial intelligence through open source and open science.\nhttps://huggingface.co/CompVis/stable-diffusion-v-1-4-original\nNovelAI 大名鼎鼎的 NovelAI，属于商业泄露模型。经过人在回路精细微调，可以生成高质量的二次元图像。但是千万时刻记得这个可是商用泄露模型，要注意避免法律风险：\npub-2fdef7a2969f43289c42ac5ae3412fd4.r2.dev\nhttps://pub-2fdef7a2969f43289c42ac5ae3412fd4.r2.dev/animefull-latest.tar\nWaifu Diffusion 基于 Stable Diffusion 模型训练得到，增加了动漫及人物训练得到的模型，基本平时各种公开场合看到 WD 就是他。\nWD 和 NovelAI 模型有些同质化，但是 NovelAI 实际是商用模型泄露，在某些使用情况下是有风险的。而 WD 不是，不过也不是说他绝对安全，毕竟 WD 也使用 Danbooru 进行学习，所以如果你关心这个需要注意一点。\nWaifu Diffusion V1.5 这个模型使用是需要一个 yaml 文件的，究其原因是这个模型是基于 SD V2 得出的，需要把和 Model 同名的 yaml 文件放在模型所在的文件夹下，目前 1.5 模型是 beta2 版本，持续迭代 ing…\nWaifu Diffusion v1.5 beta waifu-diffusion/wd-1-5-beta · Hugging Face\nWe’re on a journey to advance and democratize artificial intelligence through open source and open science.\nhttps://huggingface.co/waifu-diffusion/wd-1-5-beta\nVAE(1.4 VAE 通用) vae/kl-f8-anime2.ckpt · hakurei/waifu-diffusion-v1-4 at main\nWe’re on a journey to advance and democratize artificial intelligence through open source and open science.\nhttps://huggingface.co/hakurei/waifu-diffusion-v1-4/blob/main/vae/kl-f8-anime2.ckpt\nYAML waifu-diffusion/wd-1-5-beta2 at main\nWe’re on a journey to advance and democratize artificial intelligence through open source and open science.\nhttps://huggingface.co/waifu-diffusion/wd-1-5-beta2/tree/main/checkpoints\nWaifu Diffusion V1.4 和 1.5 版本一样，基于 SD V2 得到的，依然需要下载 yaml 文件放在 model 同文件夹下。\nWaifu Diffusion V 1.4 wd-1-4-anime_e1.ckpt · hakurei/waifu-diffusion-v1-4 at main\nWe’re on a journey to advance and democratize artificial intelligence through open source and open science.\nhttps://huggingface.co/hakurei/waifu-diffusion-v1-4/blob/main/wd-1-4-anime_e1.ckpt\nwd-1-4-anime_e2.ckpt · hakurei/waifu-diffusion-v1-4 at main\nWe’re on a journey to advance and democratize artificial intelligence through open source and open science.\nhttps://huggingface.co/hakurei/waifu-diffusion-v1-4/blob/main/wd-1-4-anime_e2.ckpt\nVAE(1.5 通用） vae/kl-f8-anime2.ckpt · hakurei/waifu-diffusion-v1-4 at main\nWe’re on a journey to advance and democratize artificial intelligence through open source and open science.\nhttps://huggingface.co/hakurei/waifu-diffusion-v1-4/blob/main/vae/kl-f8-anime2.ckpt\nYAML e2 和 e1 是通用的，但是需要改名\nhakurei/waifu-diffusion-v1-4 at main\nWe’re on a journey to advance and democratize artificial intelligence through open source and open science.\nhttps://huggingface.co/hakurei/waifu-diffusion-v1-4/tree/main\n*Elysium Anime* 生成偏真实风格的动漫图片，风格比较偏向西式，光影还不错。\n模型推荐写下面这些负面提示，可有效提升质量。\nlowres, bad anatomy, bad hands, text, error, missing fingers, extra digit, fewer digits, cropped, worst quality, low quality, normal quality, jpeg artifacts, signature, watermark, username, blurry Elysium_V1 偏真实风的模型，手画的还不错，模型底稿基本是以西方人为主，所以生成的脸也偏西方人。\nElysium_V1.ckpt · hesw23168/SD-Elysium-Model at main\nWe’re on a journey to advance and democratize artificial intelligence through open source and open science.\nhttps://huggingface.co/hesw23168/SD-Elysium-Model/blob/main/Elysium_V1.ckpt\n*SD_Elysium_Kuro_Model* 与 Anything 4.0、WD 1.4 等合并后经过微调的二次元用模型。已经包含 WD 的“kl-f8-anime2”VAE 文件，因此无需使用额外的 VAE 文件\nElysium_Kuro_Anime_V1.safetensors · hesw23168/SD_Elysium_Kuro_Model at main\nWe’re on a journey to advance and democratize artificial intelligence through open source and open science.\nhttps://huggingface.co/hesw23168/SD_Elysium_Kuro_Model/blob/main/Elysium_Kuro_Anime_V1.safetensors\n*Elysium_Anime_V3* 动漫的附加学习模型，NSFW 化相当严重，有更清晰的轮廓和轻微的三维效果。基于 Elysium_V1\nElysium_Anime_V3.safetensors · hesw23168/SD-Elysium-Model at main\nWe’re on a journey to advance and democratize artificial intelligence through open source and open science.\nhttps://huggingface.co/hesw23168/SD-Elysium-Model/blob/main/Elysium_Anime_V3.safetensors\n*Anything 系列* Anything 是个神奇的二次元模型，据说是基于几十种模型融合+未知图片训练而来，随便写几个提示，就能到的不错的结果。不过这个模型整个就是一团混沌，实际训练模型，过程，方法，作者全部都是未知的。模型容易过拟合，非专业人士，请不要在此基础上训练模型。\nAnything v3.0 “应该”是基于 NAI 模型+WD+SD 等几十种模型+手部图片强化训练得出的。实际训练模型，过程，方法，作者全部都是未知的。如果没有.vae.pt，图片整体颜色浓度（饱和度）会更很浅。PS：Anything v3.0 的 .vae.pt 文件可以用于 NAI。\nAnything V3.0 fp16: magnet:?xt=urn:btih/:45cd353ac4fa87098db5e3a6a349539710a3a1f5\u0026amp;dn=Anything-V3.0-fp16.zip\nAnything v3.0 fp32: magnet:?xt=urn:btih/:d9db662ab5ace77004b3348c23c9381380c27156\u0026amp;dn=Anything-V3.0-fp32.zip\nAnything v3.0 full-ema: magnet:?xt=urn:btih/:80460036625fb61dce4bc6e7dab744744309a2a0\u0026amp;dn=Anything-V3.0-fullema.zip\nhuggingface.co\nhttps://huggingface.co/Linaqruf/anything-v3-better-vae/tree/main\nAnything v4 自称是 Anything 最新版本的模型，实际一切都是未知的。仅需几个提示即可生成详细的 2D 插图的能力以及使用 danbooru 标签的能力。整体比过拟合的 v3 更自然，人物姿势等更容易操作。\nanything-v4.0-pruned.safetensors · andite/anything-v4.0 at main\nWe’re on a journey to advance and democratize artificial intelligence through open source and open science.\nhttps://huggingface.co/andite/anything-v4.0/blob/main/anything-v4.0-pruned.safetensors\nAnything v4.5 貌似是 Anything v4 的进化，但实际一切都是未知的。比 v4 画风更柔和一点。\nanything-v4.5-pruned.safetensors · andite/anything-v4.0 at main\nWe’re on a journey to advance and democratize artificial intelligence through open source and open science.\nhttps://huggingface.co/andite/anything-v4.0/blob/main/anything-v4.5-pruned.safetensors\nZeipher 生成更符合真人解剖结构的真人模型，训练集以女性图像为主官方网站是 https://ai.zeipher.com，已经关闭。请不要用真人模型画明星和未成年的 NSFW 内容，不然你可能会遇到很麻烦的法律问题\nF222 f222.safetensors · acheong08/f222 at main\nWe’re on a journey to advance and democratize artificial intelligence through open source and open science.\nhttps://huggingface.co/acheong08/f222/blob/main/f222.safetensors\nF111 f111.ckpt · Reachout/F111 at main\nWe’re on a journey to advance and democratize artificial intelligence through open source and open science.\nhttps://huggingface.co/Reachout/F111/blob/main/f111.ckpt\n3DKX 因为 Zeipher 官方已经 GG，这是热心网友创建的衍生 3DKX 模型如果你想让你的 3D 角色有一张更“二次元”的脸，提示词最开始写 “3d cartoon of”，或者如果你想要经典的 3D 渲染外观，写“a 3d render of”高分辨率模型，推荐分辨率为 1152 x 768 或更高\n3DKX_1.0b f111.ckpt · Reachout/F111 at main\nWe’re on a journey to advance and democratize artificial intelligence through open source and open science.\nhttps://huggingface.co/Reachout/F111/blob/main/f111.ckpt\nR34 从网站“rule34.xxx”的 150,000 张图像中进行训练。rule34.xxx 几乎全是 NSFW 图片，所以你懂的\nr34_e4 1.99 GB file on MEGA\nhttps://mega.nz/file/yJgDUCzA#zOD2yeE6QLBqPEjEpIi2b4FWOlb64yVUveOd_eW6teI\n磁力链接：magnet:?xt=urn:btih/:ed9f0e3f849d7119107ef4e072c6abeb129e1a51\u0026amp;dn=r34_e4.ckpt\nEVT pixiv 排行榜模型 基于 pixiv 排行图片训练，夹杂有部分 R18 排行图片\nEvt_V4_e10_ema Evt_V4_e10_ema.safetensors · haor/Evt_V4-preview at main\nWe’re on a journey to advance and democratize artificial intelligence through open source and open science.\nhttps://huggingface.co/haor/Evt_V4-preview/blob/main/Evt_V4_e10_ema.safetensors\nEVT_V3 haor/Evt_V3 · Hugging Face\nWe’re on a journey to advance and democratize artificial intelligence through open source and open science.\nhttps://huggingface.co/haor/Evt_V3\nEVT_V2 haor/Evt_V2 · Hugging Face\nWe’re on a journey to advance and democratize artificial intelligence through open source and open science.\nhttps://huggingface.co/haor/Evt_V2\nBasil_mix 逼真的真人模型，基于亚洲风格训练，支持 Danbur 标签提示词需要加载 VAE，不然画面色彩浓度和边缘会很淡提示词应尽可能简单不要堆砌大量质量标签和负面提示，不然会适得其反。请不要用真人模型画明星和未成年的 NSFW 内容，不然你可能会遇到很麻烦的法律问题\nbasil_mix basil mix.ckpt · nuigurumi/basil_mix at main\nWe’re on a journey to advance and democratize artificial intelligence through open source and open science.\nhttps://huggingface.co/nuigurumi/basil_mix/blob/main/basil%20mix.ckpt\nVAE vae-ft-mse-840000-ema-pruned.ckpt · stabilityai/sd-vae-ft-mse-original at main\nWe’re on a journey to advance and democratize artificial intelligence through open source and open science.\nhttps://huggingface.co/stabilityai/sd-vae-ft-mse-original/blob/main/vae-ft-mse-840000-ema-pruned.ckpt\nChillout Mix 逼真的真人模型，基于亚洲风格训练，支持 Danbur 标签提示词请不要用真人模型画明星和未成年的 NSFW 内容，不然你可能会遇到很麻烦的法律问题\nchillout mix _ NiPruned Fp32 Fix chilloutmix_NiPrunedFp32Fix.safetensors · Inzamam567/useless_Chillout_mix at main\nWe’re on a journey to advance and democratize artificial intelligence through open source and open science.\nhttps://huggingface.co/Inzamam567/useless_Chillout_mix/blob/main/chilloutmix_NiPrunedFp32Fix.safetensors\nUber Realistic Porn Merge 如名字所说，逼真的真人 Porn 模型，简称 URPM 模型请不要用真人模型画明星和未成年的 NSFW 内容，不然你可能会遇到很麻烦的法律问题\nUber Realistic Porn Merge Uber Realistic Porn Merge (URPM) | Stable Diffusion Checkpoint | Civitai\nFor early access builds and to support daily work on URPM, please check out my patreon! https://www.patreon.com/uber_realistic_porn_merge , or disc\u0026hellip;\nhttps://civitai.com/models/2661/uber-realistic-porn-merge-urpm\n","permalink":"https://hivan.me/posts/stable-diffusion-webui-models/","summary":"Stable Diffusion WebUI 最有意思的地方不是在安装好之后生成图像，而是各种各样的模型。\n提前警告：如果你的硬盘空间不够大的话，还是不要随便玩模型了，随随便便就是好几 G，又得甚至于 10 多个 G。","title":"玩转 Stable Diffusion WebUI 各类模型"},{"content":"1. Diffuers 这是可以在 App Store 上直接搜索并下载的一个 App，看评分和排名似乎都不太好，开发者却是「Hugging Face」，其实在官方的 Github 上就有其下载链接：‣\n这应该是体验 Stable Diffusion 最简便的方式了吧。而且还支持选择 Model，\n不过有点遗憾的点是没办法调整参数。\n2. DiffusionBee 这是出现的比较早的一款第三方 App，使用起来也是特别简单，直接下载安装就行了：\nDiffusionBee - Stable Diffusion App for AI Art\nDiffusionBee is the easiest way to generate AI art on your computer with Stable Diffusion. Completely free of charge.\nhttps://diffusionbee.com/download\n目前不止是 MacOS，还有对应 Windows 64 Bit 的版本，而且，你可以选择下载 HQ Version 版本。官方对其说的是速度慢两倍，但是图像质量更好。\n以上两个 App 第一次使用的时候都是需要下载 Model 的，之后就可以直接开心的玩耍了，相比较而言，DiffusionBee 在参数选择上要多一点。支持 Text to Image, Image To Image 等。\n# 安装 AUTOMATIC1111 这个方法是需要有一点动手能力了，不过相比较而言，也不是那些网站上介绍的那么繁琐。其实就只需要几步而已。\n1. 下载 Homebrew 一个包管理器，不太明白的朋友不需要管那些，操作就行了\n打开你的终端，不明白什么是终端直接在你的搜索框里输入”终端”，或者”Terminal”, 就能看到了。\n然后直接把下面的代码粘贴进去，回车，看着他跑就行了\n/bin/bash -c \u0026#34;$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/HEAD/install.sh)\u0026#34; 2. 安装一些必须的软件包 等上面步骤跑完之后，再复制下面的代码，一样粘贴进去回车看着他跑：\nbrew install cmake protobuf rust python@3.10 git wget 3. 下载 AUTOMATIC1111 存储库 等上面步骤跑完，在你的终端输入一下代码并回车\ncd ~ 以上命令是为了让你进入你在 Mac 电脑上的账户主目录，就是这个地址\n/User/xx/ 然后我们接下来的操作会在你这个目录下下载一个文件夹，名字叫 「stable-diffusion-webui」，这贴下面代码到你的终端里，然后回车\ngit clone https://github.com/AUTOMATIC1111/stable-diffusion-webui 命令跑完后，你就会渐渐自己名字的目录下多了一个 stable-diffusion-webui 的目录了，然后在终端进入这个目录，操作方法和上面一样\ncd ~/stable-diffusion-webui/ 在你的访达里你也进入这个目录，然后继续进入 models/Stable-diffusion， 这里是存放 Model 的地方。现在你需要下载一个 Model 存放进去，你可以直接在这里下载 1.5 model, 当然，如果你需要 2.1 的或者其他 model，可以去点击下面的链接进去自己下载一个合适的，然后扔到目录里。\nModels - Hugging Face\nWe’re on a journey to advance and democratize artificial intelligence through open source and open science.\nhttps://huggingface.co/models?sort=downloads\u0026amp;search=stable+diffusion\n然后你的目录应该会是这样：\n然后你就可以尝试着跑你的 stable diffusion 了，刚才我们在终端里进入了 ~/stable-diffusion-webui/， 假设你还在这个位置，我们就可以直接输入：\n./webui.sh 然后去干些自己的事情吧，喝杯茶，看看书。要跑一会呢，特别是你网络不好的情况。\n等到终端命令全部跑完后，打开你的 Safari，输入：http://127.0.0.1:7860/\n好了，可以把玩了。\n4. 其他 是的，还没结束，还有一些要说的，其实在 Mac App Store 里搜索的话，你还能看到一些其他的 App 可以直接使用，比如：\n反正都是免费的，尽量多试试，找到一个自己满意的。\n另外，不管你用那种方法，你都需要知道一些 good prompts for Stable Diffusion, 这里有一个地方可以看些别人的例子，不过不是那么容易打开：\narthub.ai\nhttps://arthub.ai/\n还有啊，自己可以多测试一些 model，下下来把玩下。\n祝你玩的愉快。全文完。\n","permalink":"https://hivan.me/posts/how-to-install-and-run-stable-diffusion-on-apple-silicon/","summary":"说实话，我找了好多关于如何在 M1/M2 上安装和运行 Stable Diffusion 的教程和帖子，发现相互之间借鉴的不少，但是能用的确实没几个。\n寻找一番后，发现其实没那么复杂。也不知道为什么网上的那么多教程搞得那么复杂，又是这个又是那个的一大堆，简单实现的方式有好几种：","title":"在 Apple Silicon M1/M2 Mac 上安装和运行 Stable Diffusion"},{"content":"我现在使用的 PS 版本为:\n在 Photoshop 内, 我画画时一直使用的是第三方的色轮插件 Coolorus, 长这样:\n好用与否, 可以说, 谁用谁知道.\n前些日子发现 Photoshop2022 有 M1 原生版本, 不用再使用转译版本, 我心想, 应该速度上会快很多吧!\n兴奋的更新完后才发现, Coolorus 面板无论如何找不到了, 原本应该在窗口下的“扩展(旧版)”菜单也找不到.\n无论怎么折腾都不行, 而且设置面板里的增效工具也是灰色无法设置:\n正当我心灰意冷准备返回 2021 时, 忽然想到, Adobe 早就在 PS 中启用 UXP 插件了, 而 CEP 插件因为历史原因一直也无法完全取消, 那么既然是早就做的事情, 为什么 2022 版本里全给抹杀了呢, 重点是, 面板里设置项虽然不可点击, 但是还在? 问题应该不是出在版本上, 而是出在 M1 原生的问题上, 我试着去设置了转译, 再次重新打开 PS, 果然不出所料, 扩展(旧版)菜单又回来了.\n好吧, 这回知道是怎么回事了, 也就好解决了.\n方法一: 返回 PS 22.21 版本, 简单粗暴\n方法二: 讲 PS2022 设置为 Rosetta 转译打开方式, 同样简单粗暴!\n回答一下可能大家问到的问题:\n速度上, 感觉不出有什么太大的变化\n是的, Coolous 照样无法使用, 我快崩溃了, 正在纠结到底是放弃 Coolous 使用 Photoshop 原始色轮, 还是回到 2021\n反正问题是这么个问题, 解决方案也有了!大家自行抉择吧!\n","permalink":"https://hivan.me/posts/ps-find-cep-for-m1/","summary":"研究这个问题, 也属于是撞上了!","title":"解决 Mac M1 原生 Photoshop 找不到 CEP 扩展面板"},{"content":"Homebrew 作为 Mac 的包管理神器，首先当然要先从 Homebrew 开始。Homebrew 已经支持了 ARM 架构，可以直接进行安装，当然，如果你电脑里以前存在 X86 的 brew 支持，请先卸载干净。\nHomebrew 卸载 /bin/bash -c \u0026#34;$(curl -fsSL https://cdn.jsdelivr.net/gh/ineo6/homebrew-install/uninstall.sh)\u0026#34; Install ARM Homebrew /bin/bash -c \u0026#34;$(curl -fsSL https://cdn.jsdelivr.net/gh/ineo6/homebrew-install/install.sh)\u0026#34; 执行完毕后，Homebrew 安装在/opt/homebrew路径下；在安装完毕后，命令行后会提示执行命令设置环境变量，当然，以防万一，这里也提供一下：\necho \u0026#39;eval \u0026#34;$(/opt/homebrew/bin/brew shellenv)\u0026#34;\u0026#39; \u0026gt;\u0026gt; ~/.zprofile eval \u0026#34;$(/opt/homebrew/bin/brew shellenv)\u0026#34; 如果是 bash shell， 则：\necho \u0026#39;eval \u0026#34;$(/opt/homebrew/bin/brew shellenv)\u0026#34;\u0026#39; \u0026gt;\u0026gt; ~/.bash_profile eval \u0026#34;$(/opt/homebrew/bin/brew shellenv)\u0026#34; 记得source ~/.zprofile\nInstall X86 Homebrew arch -x86_64 /bin/bash -c \u0026#34;$(curl -fsSL https://cdn.jsdelivr.net/gh/ineo6/homebrew-install/install.sh)\u0026#34; X86 版本的安装执行完成后命令行未提示添加环境变量。\nalias 支持多版本 在终端执行：\nalias brew=\u0026#39;arch -arm64 /opt/homebrew/bin/brew\u0026#39; alias ibrew=\u0026#39;arch -x86_64 /usr/local/bin/brew\u0026#39; 这里可以看出两者路径区别\n设置镜像 中科大源 # brew git -C \u0026#34;$(brew --repo)\u0026#34; remote set-url origin https://mirrors.ustc.edu.cn/brew.git # core git -C \u0026#34;$(brew --repo homebrew/core)\u0026#34; remote set-url origin https://mirrors.ustc.edu.cn/homebrew-core.git # cask git -C \u0026#34;$(brew --repo homebrew/cask)\u0026#34; remote set-url origin https://mirrors.ustc.edu.cn/homebrew-cask.git brew update 清华大学源 # brew git -C \u0026#34;$(brew --repo)\u0026#34; remote set-url origin https://mirrors.tuna.tsinghua.edu.cn/git/homebrew/brew.git # core git -C \u0026#34;$(brew --repo homebrew/core)\u0026#34; remote set-url origin https://mirrors.tuna.tsinghua.edu.cn/git/homebrew/homebrew-core.git # cask git -C \u0026#34;$(brew --repo homebrew/cask)\u0026#34; remote set-url origin https://mirrors.tuna.tsinghua.edu.cn/git/homebrew/homebrew-cask.git brew update 恢复默认源 # brew git -C \u0026#34;$(brew --repo)\u0026#34; remote set-url origin https://github.com/Homebrew/brew.git # core git -C \u0026#34;$(brew --repo homebrew/core)\u0026#34; remote set-url origin https://github.com/Homebrew/homebrew-core.git # cask git -C \u0026#34;$(brew --repo homebrew/cask)\u0026#34; remote set-url origin https://github.com/Homebrew/homebrew-cask.git brew update 更多源\nHomebrew 其他相关 设置 bottles 镜像 # bottles for zsh echo \u0026#39;export HOMEBREW_BOTTLE_DOMAIN=https://mirrors.ustc.edu.cn/homebrew-bottles/bottles\u0026#39; \u0026gt;\u0026gt; ~/.zprofile source ~/.zprofile # bottles bash echo \u0026#39;export HOMEBREW_BOTTLE_DOMAIN=https://mirrors.ustc.edu.cn/homebrew-bottles/bottles\u0026#39; \u0026gt;\u0026gt; ~/.bash_profile source ~/.bash_profile cask 目前 cask 是从 GitHub 上读取软件源，而 GitHub Api 对访问有限制，如果使用比较频繁的话，可以申请 Api Token，然后在环境变量中配置到HOMEBREW_GITHUB_API_TOKEN。\necho \u0026#39;export HOMEBREW_GITHUB_API_TOKEN=yourtoken\u0026#39; \u0026gt;\u0026gt; ~/.zprofile source ~/.zprofile Install Miniforge3 首先需要下载安装包： Download\n请下载 arm64(Apple Silicon)版本：\n下载完成后进入到文件目录，比如我是在~/Download/内，执行：\nbash Miniforge3-MacOSX-arm64.sh 整个执行过程会有大概三次填写yes并回车确定，最后一次会询问你是否执行conda init， 会自动在~/.zshrc内添加环境变量，如果未执行的，可以将下面语句加入文件末尾：\n# \u0026gt;\u0026gt;\u0026gt; conda initialize \u0026gt;\u0026gt;\u0026gt; # !! Contents within this block are managed by \u0026#39;conda init\u0026#39; !! __conda_setup=\u0026#34;$(\u0026#39;/Users/xx/miniforge3/bin/conda\u0026#39; \u0026#39;shell.zsh\u0026#39; \u0026#39;hook\u0026#39; 2\u0026gt; /dev/null)\u0026#34; if [ $? -eq 0 ]; then eval \u0026#34;$__conda_setup\u0026#34; else if [ -f \u0026#34;/Users/xx/miniforge3/etc/profile.d/conda.sh\u0026#34; ]; then . \u0026#34;/Users/xx/miniforge3/etc/profile.d/conda.sh\u0026#34; else export PATH=\u0026#34;/Users/xx/miniforge3/bin:$PATH\u0026#34; fi fi unset __conda_setup conda activate tf # \u0026lt;\u0026lt;\u0026lt; conda initialize \u0026lt;\u0026lt;\u0026lt; 记得自行更改/Users/xx/内的用户名\n等待 Miniforge3 安装完成，然后设置一个专供学习 Tensorflow 的虚拟环境\nconda create -n tf python=3.9.5 conda activate tf # 将这句添加到~/.zshrc 内，每次打开 shell 都会自动执行 关于 conda 切换环境的命令，建议自行 Google 学习一下，很有用。\nInstall Tensorflow 目前网上流传的 Tensorflow 安装基本是两个版本，一个是安装一大堆的支持和依赖，一个是使用yml文件提前准备好环境库一键完成环境创建，比如environment.yml：\nconda env create --file=environment.yml --name=tf 其实这一步也很简单，Apple 为了大力推广自家的 ARM，已经为大家做好了这部分准备，我们只需要安装就行了。\n假设目前在tf环境内\nconda install -c apple tensorflow-deps python -m pip install tensorflow-macos python -m pip install tensorflow-metal 好了，结束！\n可以自行利用下面一段代码测试下：\nfrom tensorflow.keras import layers from tensorflow.keras import models model = models.Sequential() model.add(layers.Conv2D(32, (3, 3), activation=\u0026#39;relu\u0026#39;, input_shape=(28, 28, 1))) model.add(layers.MaxPooling2D((2, 2))) model.add(layers.Conv2D(64, (3, 3), activation=\u0026#39;relu\u0026#39;)) model.add(layers.MaxPooling2D((2, 2))) model.add(layers.Conv2D(64, (3, 3), activation=\u0026#39;relu\u0026#39;)) model.add(layers.Flatten()) model.add(layers.Dense(64, activation=\u0026#39;relu\u0026#39;)) model.add(layers.Dense(10, activation=\u0026#39;softmax\u0026#39;)) model.summary() from tensorflow.keras.datasets import mnist from tensorflow.keras.utils import to_categorical (train_images, train_labels), (test_images, test_labels) = mnist.load_data() train_images = train_images.reshape((60000, 28, 28, 1)) train_images = train_images.astype(\u0026#39;float32\u0026#39;) / 255 test_images = test_images.reshape((10000, 28, 28, 1)) test_images = test_images.astype(\u0026#39;float32\u0026#39;) / 255 train_labels = to_categorical(train_labels) test_labels = to_categorical(test_labels) model.compile(optimizer=\u0026#39;rmsprop\u0026#39;, loss=\u0026#39;categorical_crossentropy\u0026#39;, metrics=[\u0026#39;accuracy\u0026#39;]) model.fit(train_images, train_labels, epochs=5, batch_size=64) test_loss, test_acc = model.evaluate(test_images, test_labels) test_acc 执行过程中可以在资源管理器中看到 GPU 的占用：\n其他 Lightgbm conda install Loghtgbm 一句代码解决，完全靠谱。\nxgboost xgboost 稍微有点麻烦，我测试了最稳妥的安装方式，还是自行编译，那这个时候我们就需要用到brew安装并设置编译环境了：\n注意，我用的都是brew而非ibrew, 目前都是在 ARM 环境下完成操作。\nbrew install gcc brew install cmake brew install libomp 然后下载源码并执行\ngit clone git@github.com:dmlc/xgboost.git cd xgboost mkdir build cd build CC=gcc-11 CXX=g++-11 cmake .. cd ../python-package /Users/xx/miniforge3/envs/tf/bin/python setup.py install 然后就 OK 了。\n至于其他的，Numpy 在安装 Tensorflow 的时候就自动作为依赖安装了，Pandas, Matplotlib, NGBoost 等，执行下方：\nconda install -c conda-forge pandas conda install -c conda-forge matplotlib conda install -c conda-forge ngboost 如果 conda 内实在没有的，再试试 pip 安装，再不行，就只能自行下载源码编译了。\n目前在当前环境下解决不了的几个库：\nCatBoost Cairo -\u0026gt; Pycairo GraphEmbedding CV2 igraph 在整个过程中，可能会遇到各种各样的问题，大家要习惯于使用 Google 和查阅官方文档；\n参考 Tensoflow-macos\nRun xgboost on Mac and Regression data\nAccelerating TensorFlow Performance on Mac\nThe new Apple M1 chips have accelerated TensorFlow support\nM1 Mac Mini Scores Higher Than My RTX 2080Ti in TensorFlow Speed Test.\nGPU acceleration for Apple\u0026rsquo;s M1 chip?\nM1 芯片 Mac 上 Homebrew 安装教程\nMac mini M1 使用简单体验(编程、游戏、深度学习)\nInstalling TensorFlow 2.4 on MacOS 11.0 without CUDA for both Intel and M1 based Macs\n在 M1 芯片 Mac 上使用 Homebrew\nApple M1 终于让 MacBook 变的可以炼丹了\nInstall XGBoost and LightGBM on Apple M1 Macs\nInstalling TensorFlow on the M1 Mac\nGetting Started with tensorflow-metal PluggableDevice\nM1 芯片 mac 安装 xgboost 和 lightgbm\nAI - Apple Silicon Mac M1 机器学习环境 (TensorFlow, JupyterLab, VSCode)\nM1 芯片安装 tensorflow\n使用 MacBook pro M1 搭建基于 ML Compute 加速的 TensorFlow 深度学习环境\n你的 Mac 有了专用版 TensorFlow，GPU 可用于训练，速度最高提升 7 倍\n在 M1 的 Mac 上安装 Tensorflow（避坑版）\n在 M1 芯片 Mac 上搭建原生适配 Python 环境\nConda-forge Miniforge\nM1 mac 安装 PyTorch 的完整步骤指南\nmacOS M1(AppleSilicon) 安装 TensorFlow 环境\n傻瓜版 M1 配置 Tensorflow-超简单近乎一键完成\nenvironment.yml\nopencv-python\nMAC 安装 Opencv 以及 Dlib 碰到的一些问题\nJupiter Widgets\n启动 SparkContext 报错\nMacBook Pro 2020 M1 芯片安装 xgboost\nxgboost\nHomebrew / Linuxbrew 镜像使用帮助\n镜像助手\nApple Silicon Mac 安装 xgboost\nM1 芯片 mac 安装 xgboost 和 lightgbm\nmac 安装 lightgbm 踩坑心得，亲测有效！\nMAC 上 使用 lightgbm 遇到 image not found 解决办法总结\n杂记-Macbook Pro M1 芯片能玩深度学习吗？\n","permalink":"https://hivan.me/posts/apple_m1_ai_environment_construction/","summary":"\u003cblockquote\u003e\n\u003cp\u003e本文环境搭建的基础是 Python3.9， 因为 M1 为 ARM 架构，所以放弃了 Anaconda，使用 Miniforge3。包括 Tensorflow, xgboost, Lightgbm, Numpy, Pandas, Matplotlib, NGBoost 等。当然，因为是 Python3.9， 所以有些库实在是无法使用。\u003c/p\u003e\u003c/blockquote\u003e","title":"Apple M1 的 AI 环境搭建"},{"content":"import pandas as pd import numpy as np import seaborn as sns import matplotlib.pyplot as plt import os path = os.path.expanduser(\u0026#39;~/data/cbcpv/pokemon/\u0026#39;) df = pd.read_csv(path + \u0026#39;pokemon.csv\u0026#39;, index_col=0, encoding=\u0026#39;cp1252\u0026#39;) 探索数据 df.sample(5) OUT:\nName Type 1 Type 2 Total HP Attack Defense Sp. Atk Sp. Def Speed Stage Legendary # 75 Graveler Rock Ground 390 55 95 115 45 45 35 2 False 82 Magneton Electric Steel 465 50 60 95 120 70 70 2 False 79 Slowpoke Water Psychic 315 90 65 65 40 40 15 1 False 123 Scyther Bug Flying 500 70 110 80 55 80 105 1 False 9 Blastoise Water NaN 530 79 83 100 85 105 78 3 False 对比并了解下数据集的各个特征类型:\ndf.info() # OUT \u0026lt;class \u0026#39;pandas.core.frame.DataFrame\u0026#39;\u0026gt; Int64Index: 151 entries, 1 to 151 Data columns (total 12 columns): # Column Non-Null Count Dtype --- ------ -------------- ----- 0 Name 151 non-null object 1 Type 1 151 non-null object 2 Type 2 67 non-null object 3 Total 151 non-null int64 4 HP 151 non-null int64 5 Attack 151 non-null int64 6 Defense 151 non-null int64 7 Sp. Atk 151 non-null int64 8 Sp. Def 151 non-null int64 9 Speed 151 non-null int64 10 Stage 151 non-null int64 11 Legendary 151 non-null bool dtypes: bool(1), int64(8), object(3) memory usage: 14.3+ KB 可以看到Type 2这个特征有缺失值, 其他的没有, 而且显示的为正数型, 很符合数据分析的要求.\n接下来用散点图研究特征Attack和 Defense的关系\nsns.lmplot( x=\u0026#39;Attack\u0026#39;, y=\u0026#39;Defense\u0026#39;, data=df, fit_reg=False, hude=\u0026#39;Stage\u0026#39; ) 我们这里参数使用了fit_reg=False, 隐藏了回归线. 在 Seaborn 中是没有单独绘制散点图的方法的,但是通过参数设置,实现了散点图的绘制.如果此参数设置为True\n接下来用箱线图看下各特征数据分布:\nsns.boxplot(data=df) 这个结果显示出, Total, Stage以及Legendary特征的数据是不适合在这里绘制散点图的, 需要对特征进行适当选择\nstats_df=df.drop([\u0026#39;Total\u0026#39;, \u0026#39;Stage\u0026#39;, \u0026#39;Legendary\u0026#39;], axis=1) sns.boxplot(data=stats_df) 这样,比较清晰的看出几个特征的数据分布情况了, 非数字的特征自动摒弃.\n在研究 Seaborn, 我们知道还有用 i 中研究数据分布的函数sns.violinplot, 我们尝试用它绘制特征Attack相对于特征Type 1的数据(这是一个分类行特征)的分布.\ndf[\u0026#39;Type 1\u0026#39;].unique() # OUT array([\u0026#39;Grass\u0026#39;, \u0026#39;Fire\u0026#39;, \u0026#39;Water\u0026#39;, \u0026#39;Bug\u0026#39;, \u0026#39;Normal\u0026#39;, \u0026#39;Poison\u0026#39;, \u0026#39;Electric\u0026#39;, \u0026#39;Ground\u0026#39;, \u0026#39;Fairy\u0026#39;, \u0026#39;Fighting\u0026#39;, \u0026#39;Psychic\u0026#39;, \u0026#39;Rock\u0026#39;, \u0026#39;Ghost\u0026#39;, \u0026#39;Ice\u0026#39;, \u0026#39;Dragon\u0026#39;], dtype=object) 上面显示了特征Type 1中唯一数据, 即数据的值.\nsns.set( style=\u0026#39;whitegrid\u0026#39;, rc={ \u0026#39;rigure.figsize\u0026#39;:(11.7, 8.27) # 设置了画布的尺寸 } ) pkmn_type_colors=[ \u0026#39;#78C850\u0026#39;, # Grass \u0026#39;#F08030\u0026#39;, # Fire \u0026#39;#6890F0\u0026#39;, # Water \u0026#39;#A8B820\u0026#39;, # Bug \u0026#39;#A8A878\u0026#39;, # Normal \u0026#39;#A040A0\u0026#39;, # Poison \u0026#39;#F8D030\u0026#39;, # Electric \u0026#39;#E0C068\u0026#39;, # Ground \u0026#39;#EE99AC\u0026#39;, # Fairy \u0026#39;#C03028\u0026#39;, # Fighting \u0026#39;#F85888\u0026#39;, # Psychic \u0026#39;#B8A038\u0026#39;, # Rock \u0026#39;#705898\u0026#39;, # Ghost \u0026#39;#98D8D8\u0026#39;, # Ice \u0026#39;#7038F8\u0026#39;, # Dragon ] sns.violinplot( x=\u0026#39;Type 1\u0026#39;, y=\u0026#39;Attack\u0026#39;, data=df, inner=None, # 去掉提琴图中的竖线 palette=pkmn_type_colors ) sns.swarmplot( x=\u0026#39;Type 1\u0026#39;, y=\u0026#39;Attack\u0026#39;, color=\u0026#39;k\u0026#39;, # 数据的点的颜色 alpha=0.7 ) plt.title(\u0026#39;Attack by Type\u0026#39;) pkmn_type_colors是一个列表, 列出的颜色对应着特征Type 1中的唯一值.\n因为去掉了提琴图内部的竖线,所以整个图没有太乱, 想知道有竖线的是什么样子, 可以注释掉inner=None这个参数.\n之前我们删除了三个特征得到了一个变量stats_df引用的数据集:\nstats_df.sample() OUT:\nName Type 1 Type 2 HP Attack Defense Sp. Atk Sp. Def Speed # 128 Tauros Normal NaN 75 100 95 40 70 110 数据结果中看出来, 特征HP Attack Defense Sp.Atk Sp.Def Speed都是整数, 在df.info()中也能看出来.现在有需求, 如果把这些特征分布进行可视化, 而且要放到一个坐标系中进行比较?\n参考:\n先使用pd.melt函数, 将所指定的特征进行归并\nmelted_Df=pd.melt( stats_df, id_vars=[\u0026#39;Name\u0026#39;, \u0026#39;Type 1\u0026#39;, \u0026#39;Type 2\u0026#39;], # 保留的特征 var_name=\u0026#39;Stat\u0026#39; # 其余特征规定到这一列内 ) melted_df.sample(10) OUT:\nName Type 1 Type 2 Stat value 291 Kabutops Rock Water Attack 115 406 Marowak Ground NaN Defense 110 821 Machoke Fighting NaN Speed 45 129 Gyarados Water Flying HP 95 281 Lapras Water Ice Attack 85 586 Vaporeon Water NaN Sp. Atk 110 483 Nidoqueen Poison Ground Sp. Atk 75 93 Gengar Ghost Poison HP 60 791 Vulpix Fire NaN Speed 65 481 Nidoran‰ªÛ Poison NaN Sp. Atk 40 这样,在melted_df数据集中的Stat特征中的数据就是分类数据, 值是stats_df中被归并的特征名称.\nmelted_df[\u0026#39;Stat\u0026#39;].unique() # OUT array([\u0026#39;HP\u0026#39;, \u0026#39;Attack\u0026#39;, \u0026#39;Defense\u0026#39;, \u0026#39;Sp. Atk\u0026#39;, \u0026#39;Sp. Def\u0026#39;, \u0026#39;Speed\u0026#39;], dtype=object) 在此基础上, 我们绘制反应分类特征数据分布的图示.\nsns,swarmplot( x=\u0026#39;Stat\u0026#39;, y=\u0026#39;value\u0026#39;, data=melted_df ) 还可以在此基础上,再叠加一层分类:\nsns.swarmplot( x=\u0026#39;Stat\u0026#39;, y=\u0026#39;value\u0026#39;, data=melted_df, hue=\u0026#39;Type 1\u0026#39; # 叠加一层分类 ) plt.legend(bbox_to_anchor=(1, 1), loc=2) ","permalink":"https://hivan.me/posts/pokemon/","summary":"\u003ch2 id=\"引入依赖和数据\"\u003e引入依赖和数据\u003c/h2\u003e\n","title":"pokemon"},{"content":"前言 这次预测使用的是 Sklearn 中的决策树模型:\nclf = DecisionTreeClassifier(criterion=\u0026#39;entropy\u0026#39;) 其中 criterion 是标准,决定了构造分类树是采用 ID3 分类树还是 CART 分类树,对应的取值分别是entropy和gini\nentropy: 基于信息熵,也就是 ID3 算法, 实际结果与 C4.5 相差不大;\ngini: 默认参数,基于基尼系数. CART 算法是基于基尼系数做属性划分的,所以criterion=gini时, 实际上执行的是 CART 算法.\n其完整参数:\nDecisionTreeClassifier(class_weight=None, criterion=\u0026#39;entropy\u0026#39;, max_depth=None, max_features=None, max_leaf_nodes=None, min_impurity_decrease=0.0, min_impurity_split=None, min_samples_leaf=1, min_samples_split=2, min_weight_fraction_leaf=0.0, presort=False, random_state=None, splitter=\u0026#39;best\u0026#39;) 参数代表的含义如下表:\n参数表 作用 criterion 在基于特征划分数据集合时，选择特征的标准。默认是 gini,也可以是entropyo splitter 在构造树时，选择属性特征的原则，可以是best或者 random。默认是best, best代表在所有的特征中选择最 好的，random代表在部分特征中选择最好的。 max_depth 决策树的最大深度，我们可以控制决策树的深度来防止 决策树过拟合 max_features 在划分数据集时考虑的最多的特征值数量。为int或float 类型。其中int值是每次split时最大特征数；float值是百 分数，即特征数=max_features * n_featureso min_samples_split 当节点的样本数少于min_samples_split时，不再继续分 裂。默认值为 2 min_samples_leaf 叶子节点需要的最少样本数。如果某叶子节点数目小于 这个阈值，则会和兄弟节点一起被剪枝。 min_samples_leaf的取值可以是int或float类型。 int类型：代矗小样本数； float 类型：表示一个百分比，这是最小样本数 =min_samples_leaf乘以样本数量，并向上取整。 max_leaf_nodes 最大叶子节点数。int类型，默认为None。 默认情况下是不设置最大叶子节点数，特征不多时，不 用设置。特征多时，可以通过设置最大叶子节点数，防 止过拟合。 min_impurity_decrease 节点划分最小不纯度。float类型，默认值为0。 节点的不纯度必须大于这个阈值，否则该节点不再生成 子节点。通过设置，可以限制决策树的增长。 minjmpurity_split 信息増益的阀值。信息増益必须大于这个阀值，否则不 分裂。 class_weight 类别权重。默认为None,也可以是diet或balanced。 diet类型：指定样本各类别的权重，权重大的类别在决策 树构造的时候会进行偏倚。 balanced:算法自己计算权重，样本量少的类别所对应 的样本权重会更高。 presort bool类型，默认是false,表示在拟合前，是否对数据进 行排序来加快树的构建。当数据集较小时，使用 presort=true会加快分类器构造速度。当数据集庞大 时，presort=true会导致整个分类非常缓慢。 在构造决策树分类器后,我们可以使用 fit 方法让他分类器进行拟合, 使用predict方法对新数据进行预测, 得到预测的分类结果, 也可以使用score方法得到分类器的准确率.\nfit、predict和score方法的作用如下表:\n方法表 作用 fit(features, labels) 通过特征矩阵, 分类表示,让分类器进行拟合 predict(features) 返回预测结果 score(features, labels) 返回准确率 本次数据集一共两个,一个是train.csv, 用于训练, 包含特征信息和存活与否的标签, 一个是test.csv, 测试数据集, 只包含特征信息.\n训练集中,包括了以下字段:\n字段 描述 Passengerld 乘客编号 Survived 是否幸存 Pclass 船票等级 Name 乘客姓名 Sex 乗客性别 SibSp 亲戚数虽（兄妹、配偶数） Parch 亲戚数虽（父母、子女数） Ticket 船票号码 Fare 船票价格 Cabin 船舱 Embarked 登陆港口 流程 整个流程可以划分为三个阶段:\n获取数据 准备阶段 数据探索 数据清洗 特征选择 分类阶段 决策树模型 模型评估\u0026amp;预测 决策树可视化 获取数据 这一步还包含了引入所需依赖\n# 引入依赖 import pandas as pd from sklearn.feature_extraction import DictVectorizer from sklearn.tree import DecisionTreeClassifier import os # 准备工作 path = os.path.expanduser(\u0026#39;~/data/python/Titanic_Data/\u0026#39;) # 获取数据 train_data = pd.read_csv(path + \u0026#39;train.csv\u0026#39;) test_data = pd.read_csv(path + \u0026#39;test.csv\u0026#39;) 准备阶段 对数据进行探索,分析数据质量,并对数据进行清洗,然后通过特征选择对数据进行降维, 以便于之后进行分类运算;\n数据探索 train_data.info() # 了解数据表的基本情况：行数、列数、每列的数据类型、数据完整度 train_data.describe() # 了解数据表的统计情况：总数、平均值、标准差、最小值、最大值等 train_data.describe(include=[\u0026#39;O\u0026#39;]) #查看字符串类型 (非数字) 的整体情况 train_head(5) # 查看前几行数据 (默认是前 5 行) train_tail(5) # 查看后几行数据 (默认是最后 5 行) train_sample(5) # 查看随机几行数据 (默认是随机 1 行) # 运行结果 \u0026lt;class \u0026#39;pandas.core.frame.DataFrame\u0026#39;\u0026gt; RangeIndex: 891 entries, 0 to 890 Data columns (total 12 columns): PassengerId 891 non-null int64 Survived 891 non-null int64 Pclass 891 non-null int64 Name 891 non-null object Sex 891 non-null object Age 714 non-null float64 SibSp 891 non-null int64 Parch 891 non-null int64 Ticket 891 non-null object Fare 891 non-null float64 Cabin 204 non-null object Embarked 889 non-null object dtypes: float64(2), int64(5), object(5) memory usage: 83.6+ KB None ------------------------------ PassengerId Survived ... Parch Fare count 891.000000 891.000000 ... 891.000000 891.000000 mean 446.000000 0.383838 ... 0.381594 32.204208 std 257.353842 0.486592 ... 0.806057 49.693429 min 1.000000 0.000000 ... 0.000000 0.000000 25% 223.500000 0.000000 ... 0.000000 7.910400 50% 446.000000 0.000000 ... 0.000000 14.454200 75% 668.500000 1.000000 ... 0.000000 31.000000 max 891.000000 1.000000 ... 6.000000 512.329200 [8 rows x 7 columns] ------------------------------ Name Sex ... Cabin Embarked count 891 891 ... 204 889 unique 891 2 ... 147 3 top Peter, Mrs. Catherine (Catherine Rizk) male ... B96 B98 S freq 1 577 ... 4 644 [4 rows x 5 columns] ------------------------------ PassengerId Survived Pclass ... Fare Cabin Embarked 0 1 0 3 ... 7.2500 NaN S 1 2 1 1 ... 71.2833 C85 C 2 3 1 3 ... 7.9250 NaN S 3 4 1 1 ... 53.1000 C123 S 4 5 0 3 ... 8.0500 NaN S [5 rows x 12 columns] ------------------------------ PassengerId Survived Pclass ... Fare Cabin Embarked 886 887 0 2 ... 13.00 NaN S 887 888 1 1 ... 30.00 B42 S 888 889 0 3 ... 23.45 NaN S 889 890 1 1 ... 30.00 C148 C 890 891 0 3 ... 7.75 NaN Q [5 rows x 12 columns] ------------------------------ PassengerId\tSurvived\tPclass\t...\tFare\tCabin\tEmbarked 619\t620\t0\t2\t... 10.5000\tNaN\tS 330\t331\t1\t3\t... 23.2500\tNaN\tQ 647\t648\t1\t1\t... 35.5000\tA26\tC 716\t717\t1\t1\t... 227.5250\tC45\tC 860\t861\t0\t3\t... 14.1083\tNaN\tS [5 rows x 12 columns] 数据清洗 探索之后, 我们发现 Age、Cabin 这两个字段的数据有缺失.\n其中, Cabin 为船舱, 有大量的缺失值, 在训练集和测试集中的缺失率分别为 77%和 78%, 无法补齐, Age 可以获取平均值进行补齐, 而 Embarked 是登陆港口, 这个字段也有少量(2 个)缺失值, 可以使用最大数据进行补齐.\ntrain_data[\u0026#39;Age\u0026#39;].fillna(train_data[\u0026#39;Age\u0026#39;].mean(), inplace=True) test_data[\u0026#39;Age\u0026#39;].fillna(test_data[\u0026#39;Age\u0026#39;].mean(), inplace=True) train_data[\u0026#39;Embarked\u0026#39;].fillna(train_data[\u0026#39;Embarked\u0026#39;].value_counts().idxmax(), inplace=True) test_data[\u0026#39;Embarked\u0026#39;].fillna(test_data[\u0026#39;Embarked\u0026#39;].value_counts().idxmax(), inplace=True) 分类阶段 特征选择 需要选择有用的字段作为特征,这一步其实很重要:\n# 特征选择 train_data.columns # 从上一句的结果中选择特征字段 features = [\u0026#39;Pclass\u0026#39;, \u0026#39;Sex\u0026#39;, \u0026#39;Age\u0026#39;, \u0026#39;SibSp\u0026#39;, \u0026#39;Fare\u0026#39;, \u0026#39;Parch\u0026#39;, \u0026#39;Embarked\u0026#39;] train_features = train_data[features] test_features = test_data[features] train_labels = train_data[\u0026#39;Survived\u0026#39;] 这中间有一些事字符串字段, 是不适合进行后续运算的, 需要在这里转变为数值类型,比如 Sex 字段, 男女两种取值需要转变成 0 和 1\n再比如 Embarked 有 S, C, Q 三种可能, 我们可以改成 Embarked=S, Embarked=C, Embarked=Q 三个字段,然后用数值 0 和 1 来表示, 其中 sklearn 特征选择中的 DictVectorizer 类(上面已引入依赖), 可以处理符号化的对象, 将符号转变为 0/1 进行表示:\ndvec=DictVectorizer(sparse=False) train_features=dvec.fit_transform(train_features.to_dict(orient=\u0026#39;record\u0026#39;)) fit_transform这个函数可以讲特征向量转化为特征值矩阵, 我们查看下:\ndvec.feature_names_ # 运行结果: [\u0026#39;Age\u0026#39;, \u0026#39;Embarked=C\u0026#39;, \u0026#39;Embarked=Q\u0026#39;, \u0026#39;Embarked=S\u0026#39;, \u0026#39;Fare\u0026#39;, \u0026#39;Parch\u0026#39;, \u0026#39;Pclass\u0026#39;, \u0026#39;Sex=female\u0026#39;, \u0026#39;Sex=male\u0026#39;, \u0026#39;SibSp\u0026#39;] 我们讲 Embarked 转化为三列 (['Embarked=C', 'Embarked=Q', 'Embarked=S']), Sex 变为了两列 ([Sex=female', 'Sex=male'])\n决策树模型 # 构造 ID3 决策树 clf=DecisionTreeClassifier(criterion=\u0026#39;entropy\u0026#39;) # 决策树训练 clf.fit(train_features, train_labels) 模型预测 \u0026amp; 评估 我们首先得到测试集的特征值矩阵, 然后使用训练好的决策树 clf 进行预测, 得到预测结果:\ntest_features=dvec.transform(test_features.to_dict(orient=\u0026#39;record\u0026#39;)) # 决策树预测 pred_labels=clf.predict(test_features) 模型评估中,决策树提供了 score 函数直接得到准确率,但是我们并不知道真实的预测结果,所以无法用预测值和真实的预测结果做比较, 需要使用训练机中的数据进行模型评估, 可以使用决策树自带的 score 函数计算:\n# 得到决策树准确率 acc_decision_tree=round(clf.score(train_features, train_labels), 6) acc_decision_tree # 运行结果 0.982043 其实,以上准确率评估并不准确,因为我们用训练集做了训练,再用训练集做准确率评估, 并不能代表决策树分类器的准确率.\n要统计决策树分类器的准确率, 可以使用 K 折交叉验证,\ncross_val_score 函数中的参数 cv 代表对原始数据划分成多少份，也就是我们的 K 值，一般建议 K 值取 10，因此我们可以设置 CV=10\nimport numpy as np from sklearn.model_selection import cross_val_score # 使用 K 折交叉验证, 统计决策树准确率 np.mean(cross_val_score(clf, train_features, train_labels, cv=10)) # 输出结果 0.7778901373283394 ","permalink":"https://hivan.me/posts/titanic/","summary":"最好的学习就是输出,所以虽然这个预测很多人做过了,我还是在这里再做一遍,纯粹是为了自己学习.","title":"「泰坦尼克」生存预测"},{"content":"# 引入数据 import pandas as pd data = pd.read_csv(\u0026#39;~/data/cbcpv/marathon/marathon.csv\u0026#39;) data.sample(5) OUT:\nage gender split final 19841 34 M 01:55:25 04:50:03 11002 28 W 01:55:00 04:11:00 11619 26 M 01:40:28 04:13:52 4068 34 M 01:38:30 03:30:21 6922 35 M 01:37:44 03:48:37 这个数据集有以下几个特征：\nage，运动员的年龄 gender，运动员的性别 split，半程所用时间 final，全程所用时间，即最终成绩 自然,要先了解下数据的具体情况\ndata.info() # 输出结果 \u0026lt;class \u0026#39;pandas.core.frame.DataFrame\u0026#39;\u0026gt; RangeIndex: 37250 entries, 0 to 37249 Data columns (total 4 columns): # Column Non-Null Count Dtype --- ------ -------------- ----- 0 age 37250 non-null int64 1 gender 37250 non-null object 2 split 37250 non-null object 3 final 37250 non-null object dtypes: int64(1), object(3) memory usage: 1.1+ MB 可以看到并没缺失值, 不过split和final特征中的数据不实数字类型, 用字符串表示了所用时间长度, 所以我们需要进行转化:\nimport datetime def convert_time(s): h,m,s=map(int, s.split(\u0026#39;:\u0026#39;)) return datetime.timedelta(hours=h, minutes=m, seconds=s) 使用完成的方法进行数据转换,我们从新读取一下数据:\ndf=pd.read_csv( \u0026#39;~/data/cbcpv/marathon/marathon.csv\u0026#39;, converters={ \u0026#39;split\u0026#39;: convert_time, \u0026#39;final\u0026#39;: convert_time } ) df.dtypes # 输出结果 age int64 gender object split timedelta64[ns] final timedelta64[ns] dtype: object 这次数据已经转换为timedelta64类型, 下面我们就要转化时间为整数, 一般的做法都是秒或者毫秒数:\nd = datetime.timedelta(hours=1, minutes=0, seconds=0) df2 = pd.DataFrame({\u0026#39;time\u0026#39;:[d]}) df2.astype(int) # 输出结果 time 0 3600000000000 我们看到的输出结果,是“纳秒“(ns)单位:\n$$1s=10^9ns$$\n我们还需要转化为秒:\nd=datetime.timedelta(hours=1, minutes=0, seconds=0) df2=pd.DataFrame({\u0026#39;time\u0026#39;:[d]}) df2.astype(int) * 1e-9 # out time 0 3600.0 然后我们要讲split和final两个特征的数据进行转化\ndf[\u0026#39;split_sec\u0026#39;]=df[\u0026#39;split\u0026#39;].astype(int) * 1e-9 df[\u0026#39;final_sec\u0026#39;]=df[\u0026#39;final\u0026#39;].astype(int) * 1e-9 df.sample(5) OUT:\nage gender split final split_sec final_sec 11725 35 M 0 days 01:53:53 0 days 04:14:19 6833.0 15259.0 19815 24 M 0 days 01:58:45 0 days 04:49:57 7125.0 17397.0 5754 49 M 0 days 01:42:39 0 days 03:41:05 6159.0 13265.0 33166 46 M 0 days 02:31:37 0 days 06:06:17 9097.0 21977.0 9226 36 W 0 days 01:49:06 0 days 04:01:55 6546.0 14515.0 现在多了两个特征split_sec和final_sec, 都是以秒为单位的浮点数.\n描述统计 先了解数据:\ndf.describe() OUT:\nage split final split_sec final_sec count 37250.000000 37250 37250 37250.000000 37250.000000 mean 40.697369 0 days 02:03:54.425664429 0 days 04:48:09.303597315 7434.425664 17289.303597 std 10.220043 0 days 00:22:55.093889674 0 days 01:03:32.145345151 1375.093890 3812.145345 min 17.000000 0 days 01:05:21 0 days 02:08:51 3921.000000 7731.000000 25% 33.000000 0 days 01:48:25 0 days 04:02:24 6505.000000 14544.000000 50% 40.000000 0 days 02:01:13 0 days 04:44:25 7273.000000 17065.000000 75% 48.000000 0 days 02:16:11 0 days 05:27:36 8171.000000 19656.000000 max 86.000000 0 days 04:59:49 0 days 10:01:08 17989.000000 36068.000000 居然年龄上最大的数据是 86, 让我们看看特征的数据分布:\n%matplotlib inline import seaborn as sns ax=sns.boxplot(x=df[\u0026#39;age\u0026#39;]) 这个箱线图反应了, 数据里确实有一些“离群值”.\n数据分布 研究下数据分布, 看看split_sec和final_sec\nsns.displot(df[\u0026#39;split_sec\u0026#39;]) sns.displot(df[\u0026#39;final_sec\u0026#39;]) 整体看来,两个特征下的数据都符合正态分布, 但是final_sec的分布图比较胖.\n这次我们把gender这个分类特征添加进来:\nsns.violinplot(x=\u0026#39;gender\u0026#39;, y=\u0026#39;final_sec\u0026#39;, data=df) 这些看到, 男性运动员在总体上还是比女性运动员要快一些.\n寻找优秀的原因 跑马拉松或者了解这项运动的人都清楚, 运动员很关注整个赛程中前后半程的时间比较,好的选手是后半程用时和前半程近似. 因此, 我们来研究下, 这些运动员前后半程用时情况.\ng=sns.jointplot(\u0026#39;split_sec\u0026#39;, \u0026#39;final_sec\u0026#39;, data=df, kind=\u0026#39;hex\u0026#39;) # 绘制一条直线, 作为参考 import numpy as np g.ax_joint.plot(np.linspace(4000, 16000), np.linspace(8000, 32000), \u0026#39;:k\u0026#39;) 横坐标是splict_sec特征, 即半程用时. 纵轴表示final_sec特征, 全程用时. 途中可以看出, 的确是越优秀的运动员,前半程用时越接近全程用时的一半, 甚至还有少数后半程跑的更快的.\n我们做个计算来深入研究下:\ndf[\u0026#39;split_frac\u0026#39;]=1-2*df[\u0026#39;split_sec\u0026#39;]/df[\u0026#39;final_sec\u0026#39;] df.sample(5) OUT:\nage gender split final split_sec final_sec split_frac 2065 35 W 0 days 01:31:41 0 days 03:14:40 5501.0 11680.0 0.058048 9001 43 W 0 days 01:58:19 0 days 04:00:44 7099.0 14444.0 0.017031 30039 34 M 0 days 02:25:17 0 days 05:39:21 8717.0 20361.0 0.143755 27456 62 W 0 days 02:13:28 0 days 05:25:01 8008.0 19501.0 0.178709 13335 41 M 0 days 01:45:36 0 days 04:21:00 6336.0 15660.0 0.190805 用直方图再增加一个参考线来看看split_frac特征中的数据分布:\nimport matplotlib.pyplot as plt sns.displot(df[\u0026#39;split_frac\u0026#39;], kde=False) # 垂直于 x 轴的直线，0 表示 x 轴位置 plt.axvline(0, color=\u0026#39;k\u0026#39;, linestyle=\u0026#39;--\u0026#39;) 从这张图中, 更清晰的看到全体参赛者的运动安排.\n再来探究下不同特征之间的关系:\nsns.pairplot( data=df, vars=[\u0026#39;age\u0026#39;,\u0026#39;split_sec\u0026#39;,\u0026#39;final_sec\u0026#39;,\u0026#39;split_frac\u0026#39;], hue=\u0026#39;gender\u0026#39; ) 让我们来看下 80 岁选手的数量:\n(df.age\u0026gt;=80).sum() # OUT 15 下面, 我们划分下年龄段,看看各年龄段的成绩分布:\ndf[\u0026#39;age_dec\u0026#39;]=df[\u0026#39;age\u0026#39;].map(lambda age: 10*(age//10)) sns.violinplot( x=\u0026#39;age_dec\u0026#39;, y=\u0026#39;split_frac\u0026#39;, hue=\u0026#39;gender\u0026#39;, data=df, split=True, inner=\u0026#39;quartile\u0026#39;, palette=[\u0026#39;lightblue\u0026#39;, \u0026#39;lightpink\u0026#39;] ) 看这张图, 我们发现,不同性别的运动员的split_frac特征数据分布中, 年龄越大,前后端的时间分布比相对集中.\n再看看全程用时分布比较:\nsns.violinplot( x=\u0026#39;age_dec\u0026#39;, y=\u0026#39;final_sec\u0026#39;, hue=\u0026#39;gender\u0026#39;, data=df, split=True, inner=\u0026#39;quartile\u0026#39;, palette=[\u0026#39;lightblue\u0026#39;, \u0026#39;lightpink\u0026#39;] ) 从 30 岁往后, 明显年纪越大,用时越长.\n","permalink":"https://hivan.me/posts/marathon/","summary":"先引入数据,准备进行分析","title":"马拉松跑步数据"},{"content":"元素周期表基本构成如下:\n族：表中的每一列就是一族，从左向右依次为 1、2……18 族。 周期：表中的行。 元素：每个方框表示一个元素，其中包括元素符号、名称、原子序数、原子量。 在主表下面还有镧系元素和锕系元素表。 用颜色区分金属、非金属等常见的物质状态。 最终呈现:\n其他形状元素周期表\n导入和处理数据 # 导入依赖 import pandas as pd import numpy as np from plotnine import * # 读取数据 elements = pd.read_csv(\u0026#39;~/data/cbcpv/elemanets/elements.csv\u0026#39;) 研究数据集 elements.info() \u0026#34;\u0026#34;\u0026#34; \u0026lt;class \u0026#39;pandas.core.frame.DataFrame\u0026#39;\u0026gt; RangeIndex: 118 entries, 0 to 117 Data columns (total 21 columns): # Column Non-Null Count Dtype --- ------ -------------- ----- 0 atomic number 118 non-null int64 1 symbol 118 non-null object 2 name 118 non-null object 3 atomic mass 118 non-null object 4 CPK 118 non-null object 5 electronic configuration 118 non-null object 6 electronegativity 97 non-null float64 7 atomic radius 71 non-null float64 8 ion radius 92 non-null object 9 van der Waals radius 38 non-null float64 10 IE-1 102 non-null float64 11 EA 85 non-null float64 12 standard state 99 non-null object 13 bonding type 98 non-null object 14 melting point 101 non-null float64 15 boiling point 94 non-null float64 16 density 96 non-null float64 17 metal 118 non-null object 18 year discovered 118 non-null object 19 group 118 non-null object 20 period 118 non-null int64 dtypes: float64(8), int64(2), object(11) memory usage: 19.5+ KB \u0026#34;\u0026#34;\u0026#34; 特征 group就是该元素所在的族，但是，如果用 elements['group'] 查看所有内容，会发现有的记录中用 \u0026lsquo;-\u0026rsquo; 标记，说明它不属于任何族，说明它们应该是镧系元素或者锕系元素。根据数据分析的通常要求，\u0026rsquo;-\u0026rsquo; 符号最好用数字表示，这里用 ﹣1\n转化数据集 # 转换族 elements[\u0026#39;group\u0026#39;] = [-1 if g==\u0026#39;-\u0026#39; else int(g) for g in elements[\u0026#39;group\u0026#39;]] elements[\u0026#39;group\u0026#39;] \u0026#34;\u0026#34;\u0026#34; 0 1 1 18 2 1 3 2 4 13 .. 113 14 114 15 115 16 116 17 117 18 Name: group, Length: 118, dtype: int64 \u0026#34;\u0026#34;\u0026#34; 特征 bonding type、metal 都是分类数据，因此在类型上进行转化。\n# 转化分类数据 elements[\u0026#39;bonding type\u0026#39;] = elements[\u0026#39;bonding type\u0026#39;].astype(\u0026#39;category\u0026#39;) elements[\u0026#39;metal\u0026#39;] = elements[\u0026#39;metal\u0026#39;].astype(\u0026#39;category\u0026#39;) 将原本的整数型 atomic number 特征,转化为字符串类型\nelements[\u0026#39;atomic_number\u0026#39;] = elements[\u0026#39;atomic number\u0026#39;].astype(str) 元素周期表有两个部分,上面一部分每个元素是属于某一个族的,即 group 特征中的 1-18, 而对于值是-1 的则表示这些元素应该在下面的镧系或者锕系元素表中。下面分别用 top 变量和 bottom 变量引用这两部分元素集合.\n## 分别用 top 和 bottom 变量引用上下部分元素集合 top = elements.query(\u0026#39;group != -1\u0026#39;).copy() bottom = elements.query(\u0026#39;group == -1\u0026#39;).copy() 元素周期表中横向表示的是族（group），纵向表示的是周期（period），用下面的方式在 top 中创建两个特征，分别为“族”和“周期”的值。\n## 在 top 中区分“族”(group)和“周期”(period)的值 \u0026#34;\u0026#34;\u0026#34; 横向表示族,纵向表示周期 \u0026#34;\u0026#34;\u0026#34; top[\u0026#39;x\u0026#39;] = top.group top[\u0026#39;y\u0026#39;] = top.period top[\u0026#39;x\u0026#39;] \u0026#34;\u0026#34;\u0026#34; 0 1 1 18 2 1 3 2 4 13 .. 113 14 114 15 115 16 116 17 117 18 Name: x, Length: 90, dtype: int64 \u0026#34;\u0026#34;\u0026#34; top[\u0026#39;y\u0026#39;] \u0026#34;\u0026#34;\u0026#34; 0 1 1 1 2 2 3 2 4 2 .. 113 7 114 7 115 7 116 7 117 7 Name: y, Length: 90, dtype: int64 \u0026#34;\u0026#34;\u0026#34; 除了上面的部分之外，下面的锕系和镧系元素也要做类似的配置。不过，横坐标不能用 group 特征的值，因为前面设置为 ﹣1。\nnrows = 2 \u0026#34;\u0026#34;\u0026#34; hshift 和 vshift 分别表示横、纵间距，这样就为每个锕系和镧系元素增加了横纵坐标值。 \u0026#34;\u0026#34;\u0026#34; hshift = 3.5 vshift = 3 bottom[\u0026#39;x\u0026#39;] = np.tile(np.arange(len(bottom) // nrows), nrows) + hshift bottom[\u0026#39;y\u0026#39;] = bottom.period + vshift 每个元素都占了一个小方块,所以,这个小方块(元素块)的大小要设置一下\n## 设置元素占据的小矩形 tile_width = 0.95 tile_height = 0.95 开始画图 (ggplot(aes(\u0026#39;x\u0026#39;, \u0026#39;y\u0026#39;)) + geom_tile(top, aes(width=tile_width, height=tile_height)) + geom_tile(bottom, aes(width=tile_width, height=tile_height)) ) 这里只有美学映射,没有传入数据集.因为在图层对象中,要传入不同的数据集: “top”和“bottom”.\ntop 表示主表中的, bottom 表示下面的锕、镧系元素\ngeom_tile绘制安放元素块图层,并使用 top 数据集,在引入一个图层,绘制 bottom 对应的图层. 但是我们发现表反了, 所以需要实现在 Y 轴方向上的坐标轴翻转.\n(ggplot(aes(\u0026#39;x\u0026#39;, \u0026#39;y\u0026#39;)) +geom_tile(top, aes(width=tile_width, height=tile_height)) +geom_tile(bottom, aes(width=tile_width, height=tile_height)) # 在 Y 轴上进行翻转 +scale_y_reverse() # new ) 基本样式已经有了。\n前面已经把特征“metal”的数据转换为分类数据，下面用这些数据对不同元素的小矩形（以后简称“元素块”）上色。\n(ggplot(aes(\u0026#39;x\u0026#39;, \u0026#39;y\u0026#39;)) # 对数据不同的元素块进行上色 + aes(fill=\u0026#39;metal\u0026#39;) # new + geom_tile(top, aes(width=tile_width, height=tile_height)) + geom_tile(bottom, aes(width=tile_width, height=tile_height)) + scale_y_reverse() ) 然后,我们要将化学元素的有关信息写到这些元素块上,这里要写到元素块上的包括:\n原子序数，对应着数据集中的特征是“atomic number”； 元素符号，对应着数据集中的特征是“symbol”； 元素名称，对应着数据集中的特征是“name”； 原子量，对应着数据集中的特征是“automic mass” 在这里,我们要绘制四个图层,以便安放四个元素信息, 每个图层上面一个特征,并且每个图层的位置、字号大小等都不相同.\n为此我们写一个函数方法来实现:\n\u0026#34;\u0026#34;\u0026#34; nudge_x: 文本在水平方向上的相对位置 nudge_y: 文本在竖直方向上的相对位置 ha: 可选\u0026#39;left\u0026#39;, \u0026#39;center\u0026#39;, \u0026#39;right\u0026#39;, 标示水平方向的对齐方式 va: 可选\u0026#39;top\u0026#39;, \u0026#39;center\u0026#39;, \u0026#39;bottom\u0026#39;, 表示竖直方向的堆砌方式 size: 字号大小 fontweight: 字族中的字体粗细 \u0026#34;\u0026#34;\u0026#34; def inner_text(data): layers = [geom_text(data, aes(label=\u0026#39;atomic_number\u0026#39;), nudge_x=-0.40, nudge_y=-.40, ha=\u0026#39;left\u0026#39;, va=\u0026#39;top\u0026#39;, fontweight=\u0026#39;normal\u0026#39;, size=6), geom_text(data, aes(label=\u0026#39;symbol\u0026#39;), nudge_y=.1, size=9), geom_text(data, aes(label=\u0026#39;name\u0026#39;), nudge_y=-0.125, fontweight=\u0026#39;normal\u0026#39;, size=4.5), geom_text(data, aes(label=\u0026#39;atomic mass\u0026#39;), nudge_y=-.3, fontweight=\u0026#39;normal\u0026#39;, size=4.5) ] return layers 然后我们将函数inner_text应用到绘图流程中去\n\u0026#34;\u0026#34;\u0026#34; 分别调用两次是因为有 top 和 bottom 两个数据 \u0026#34;\u0026#34;\u0026#34; (ggplot(aes(\u0026#39;x\u0026#39;, \u0026#39;y\u0026#39;)) + aes(fill=\u0026#39;metal\u0026#39;) + geom_tile(top, aes(width=tile_width, height=tile_height)) + geom_tile(bottom, aes(width=tile_width, height=tile_height)) # 绘制上部分图层 + inner_text(top) # new # 绘制下部分图层 + inner_text(bottom) # new + scale_y_reverse() ) 是不是觉得图很难看,原因在于我们还没对其进行调整,下面我们就要细微的调整图层,包括大小等\n(ggplot(aes(\u0026#39;x\u0026#39;, \u0026#39;y\u0026#39;)) + aes(fill=\u0026#39;metal\u0026#39;) + geom_tile(top, aes(width=tile_width, height=tile_height)) + geom_tile(bottom, aes(width=tile_width, height=tile_height)) + inner_text(top) + inner_text(bottom) + scale_y_reverse() # coord_equal 作用是设置坐标系的横轴和纵轴 # expand=False, 意味着坐标系的大小由制图所用数据决定 + coord_equal(expand=False) # new # 一个新主题,规定了图纸的尺寸 + theme(figure_size=(12,6)) # new ) 在默认的主题中，横纵坐标的图上长度相等，也就是图像是呈现在一张正方形的图纸上，coord_equal 的作用就是设置坐标系的横轴和纵轴，它与 coord_fixed 是完全等效的，能够改变图纸的大小和长宽比例。参数 expand 的值是布尔值，如果为 False，则意味着坐标系的大小（即图纸的大小）由制图所用数据决定。\n新增的第二个图层对象是一个新的主题，在其中规定了图纸的尺寸。\n我们仔细研究元素周期表,发现 Lu 和 Lr 两个元素比较特殊,其实它们不是单独的元素,而是对应着下部分两行的,因此要对这两个进行处理,以区分出与其他元素的不同.\n我们将其分为两半,使用过 PS 作图的同学应该能想到两个不同颜色的图层叠加,上面的图层只有下面图层的一半,那么看起来就像是被分成了两半.\n# split_df 是绘制新元素块所需要的数据集。 split_df = pd.DataFrame({ \u0026#39;x\u0026#39;: 3-tile_width/4, \u0026#39;y\u0026#39;: [6,7], \u0026#39;metal\u0026#39;: pd.Categorical([\u0026#39;lanthanoid\u0026#39;, \u0026#39;actinoid\u0026#39;]) }) (ggplot(aes(\u0026#39;x\u0026#39;,\u0026#39;y\u0026#39;)) + aes(fill=\u0026#39;metal\u0026#39;) + geom_tile(top, aes(width=tile_width, height=tile_height)) # 将新的数据集用于叠加 Lu 和 Lr 的图层上进行遮挡 + geom_tile(split_df, aes(width=tile_width/2, height=tile_height)) # new + geom_tile(bottom, aes(width=tile_width, height=tile_height)) + inner_text(top) + inner_text(bottom) + scale_y_reverse() + coord_equal(expand=False) + theme(figure_size=(12, 6)) ) 基本制作完成了,下面来美化一下:\n(ggplot(aes(\u0026#39;x\u0026#39;, \u0026#39;y\u0026#39;)) + aes(fill=\u0026#39;metal\u0026#39;) + geom_tile(top, aes(width=tile_width, height=tile_height)) + geom_tile(split_df, aes(width=tile_width/2, height=tile_height)) + geom_tile(bottom, aes(width=tile_width, height=tile_height)) + inner_text(top) + inner_text(bottom) + scale_y_reverse() # 对元素块填充色进行转换 + scale_fill_brewer(type=\u0026#39;qual\u0026#39;, palette=3) + coord_equal(expand=False) # 增加了一个经典的主题图层对象 + theme_void() + theme(figure_size=(12, 6), # 增加一个主题图层,并设置了该图层的尺寸和背景色 plot_background=element_rect(fill=\u0026#39;white\u0026#39;) ) ) 到最后了,我们要解决主表中的元素表上族和周期的问题\n观察主表中的每一列,注意我们已经把 Y 轴映射反序了,如果在 H 元素的元素块上标注族的序号为“1”, 那么这个“1”的 Y 轴坐标应该是 y=1, 同样,Sc 元素块上标注族的需要“3”, 那么“3”的 Y 轴坐标应该是 y=4.\n这样,我们就可以创建每列及其对应的 Y 轴坐标了.\n## 创建每列(即:族, 编号为 1-18)及其对应的 Y 轴坐标 groupdf = pd.DataFrame({ \u0026#39;group\u0026#39;: range(1, 19), \u0026#39;y\u0026#39;: np.repeat([1,2,4,2,1], [1,1,10,5,1]) }) groupdf group y 0 1 1 1 2 2 2 3 4 3 4 4 4 5 4 5 6 4 6 7 4 7 8 4 8 9 4 9 10 4 10 11 4 11 12 4 12 13 2 13 14 2 14 15 2 15 16 2 16 17 2 17 18 1 让我们来标注族的序号\n## 标注族序号 (ggplot(aes(\u0026#39;x\u0026#39;,\u0026#39;y\u0026#39;)) + aes(fill=\u0026#39;metal\u0026#39;) + geom_tile(top, aes(width=tile_width, height=tile_height)) + geom_tile(split_df, aes(width=tile_width/2, height=tile_height)) + geom_tile(bottom, aes(width=tile_width, height=tile_height)) + inner_text(top) + inner_text(bottom) # 标注每一列族序号的文本图层 # aes(\u0026#39;group\u0026#39;, \u0026#39;y\u0026#39;, label=\u0026#39;group\u0026#39;) 重写了 X 轴和 Y 轴的映射 # inherit_aes=False, 不继承映射配置 + geom_text(groupdf, aes(\u0026#39;group\u0026#39;, \u0026#39;y\u0026#39;, label=\u0026#39;group\u0026#39;), color=\u0026#39;gray\u0026#39;, nudge_y=.525, va=\u0026#39;bottom\u0026#39;, fontweight=\u0026#39;normal\u0026#39;, size=9, inherit_aes=False ) # 以 Y 轴调转坐标轴 + scale_y_reverse() # 对元素块填充色进行转换 + scale_fill_brewer(type=\u0026#39;qual\u0026#39;, palette=3) + coord_equal(expand=False) # 增加了一个经典的主题图层对象 + theme_void() + theme(figure_size=(12, 6), # 增加一个主题图层,并设置了该图层的尺寸和背景色 plot_background=element_rect(fill=\u0026#39;white\u0026#39;), ) ) 最终,我们标注玩周期就完成了.\n周期是对每一行的标注,一共 7 行,因为标注在左侧,可以把它看成是左侧的 Y 轴标示,可以在图层上通过对 Y 轴标示的设置完成周期的标注.\n## 开始标注周期, 最终完成 (ggplot(aes(\u0026#39;x\u0026#39;, \u0026#39;y\u0026#39;)) # 把特征\u0026#39;metal\u0026#39;的数据转换为分类数据,进行元素块上色 + aes(fill=\u0026#39;metal\u0026#39;) # 创建上部元素块 + geom_tile(top, aes(width=tile_width, height=tile_height)) # 创建 Lu 和 Lr 的半个元素块 + geom_tile(split_df, aes(width=tile_width/2, height=tile_height)) # 创建下部元素块 + geom_tile(bottom, aes(width=tile_width, height=tile_height)) # 创建文字图层, 把化学元素的有关信息写到元素块中 + inner_text(top) + inner_text(bottom) # 标注每一列族序号的文本图层 # aes(\u0026#39;group\u0026#39;, \u0026#39;y\u0026#39;, label=\u0026#39;group\u0026#39;) 重写了 X 轴和 Y 轴的映射 # inherit_aes=False, 不继承映射配置 + geom_text(groupdf, aes(\u0026#39;group\u0026#39;, \u0026#39;y\u0026#39;, label=\u0026#39;group\u0026#39;), color=\u0026#39;gray\u0026#39;, nudge_y=.525, va=\u0026#39;bottom\u0026#39;, fontweight=\u0026#39;normal\u0026#39;, size=9, inherit_aes=False ) # 以 Y 轴调转坐标轴, 增加了纵坐标主刻度标示数字。 + scale_y_reverse(breaks=range(1, 8), limits=(0, 10.5) ) # 对元素块填充色进行转换 + scale_fill_brewer(type=\u0026#39;qual\u0026#39;, palette=3) + coord_equal(expand=False) # 增加了一个经典的主题图层对象 + theme_void() + theme(figure_size=(12, 6), # 增加一个主题图层,并设置了该图层的尺寸和背景色 plot_background=element_rect(fill=\u0026#39;white\u0026#39;), # 增加了参数 axis_text_y，对 Y 轴标示的显示格式进行了设置。 axis_text_y=element_text(margin={\u0026#39;r\u0026#39;:5}, color=\u0026#39;gray\u0026#39;, size=9) ) ) 完成\u0026hellip;\n","permalink":"https://hivan.me/posts/elements_by_plotnine/","summary":"首先需要了解元素周期表以及元素数据:\n\u003ca href=\"https://zh.wikipedia.org/wiki/%E5%85%83%E7%B4%A0%E5%91%A8%E6%9C%9F%E8%A1%A8\"\u003e维基百科的元素周期表词条\u003c/a\u003e\n\u003ca href=\"https://github.com/qiwsir/DataSet/tree/master/elemanets\"\u003e元素数据\u003c/a\u003e","title":"使用 Plotnine 制作元素周期表"},{"content":"$ touch .node-version $ echo v8 \u0026gt;\u0026gt; .node-version #node 需要切换的版本 $ echo `source \u0026#34;$HOME/.avn/bin/avn.sh\u0026#34; # load avn` \u0026gt;\u0026gt; ~/.zshrc 这样就可以了。\n不过不排除报错的情况，如果是brew 安装的nvm, 则默认nvm.sh并不在~/.nvm目录内，这个时候可能需要在执行一下某段脚本。一样添加到~/.zshrc内\n$ echo `[[ -s \u0026#34;$(brew --prefix nvm)/nvm.sh\u0026#34; ]] \u0026amp;\u0026amp; source $(brew --prefix nvm)/nvm.sh` \u0026gt;\u0026gt; ~/.zshrc 再切换一下项目目录\n$ cd $project $ avn activated v8.11.2 (avn-nvm v8.11.2) 至此完成了！\n","permalink":"https://hivan.me/posts/avn-change-node-version-for-a-project/","summary":"\u003cp\u003e\u003ccode\u003envm\u003c/code\u003e作为 node 的版本管理器，并不具备自动切换版本切换的功能，有的时候我们需要针对某一个项目切换当前的 node 版本，这个时候就需要用到其他工具了。比如\u003ccode\u003eavn\u003c/code\u003e\n举例项目:\u003ccode\u003eproject\u003c/code\u003e\n因为最近 Node 更新到 10 之后，我将系统默认版本切换到了 10，有不更新不舒服斯基强迫症 而\u003ccode\u003eproject\u003c/code\u003e 编译的版本为 8，否则会出现编译出错。\u003c/p\u003e\n\u003cp\u003e\u003ccode\u003eshell $ brew install nvm $ nvm i -g avn $ avn steup \u003c/code\u003e\n之后在\u003ccode\u003eproject\u003c/code\u003e根目录中添加一个文件\u003ccode\u003e.node-version\u003c/code\u003e\u003c/p\u003e\n","title":"针对某一个项目自动切换 node 版本"},{"content":"而自定义之后每次在需要使用的组件内引用也确实蛮麻烦的。\n所以我们就来将定义的 filter 挂载到全局使用。\nvue2.0 filter 相关文档\n定义 引用 挂载 使用 /src/filters/\nformat.js export default function(val){ ... } index.js import format from \u0026#34;./format\u0026#34;; export default{ format: format, } /src/\nmain.js ... import commonFiltes from \u0026#39;./filters/index\u0026#39; Object.keys(commonFiltes).forEach(function (key, index, arr) { Vue.filter(key, commonFiltes[key]); }) ... /src/components/\nxxx.vue \u0026lt;template\u0026gt; ... \u0026lt;div\u0026gt;{{ data | format }}\u0026lt;/div\u0026gt; \u0026lt;/template\u0026gt; \u0026lt;script\u0026gt; ... \u0026lt;/script\u0026gt; ","permalink":"https://hivan.me/posts/vue2.0-custom-filter-to-global/","summary":"vue 2.0 开始，取消默认 filter, 需要自定义。","title":"vue 2.0 自定义 filter 并挂载到全局使用"},{"content":"在 vux 的文档和示例中，都没有明确的说明 tabbar 上 v-model 的使用\n文档中将v-model说明放在了 TabbarItem 示例下，但是其实这个应该是放在Tabbar上\n\u0026lt;template\u0026gt; \u0026lt;router-view class=\u0026#34;view\u0026#34; v-on:changeTab=\u0026#34;changeTab\u0026#34;\u0026gt;\u0026lt;/router-view\u0026gt; \u0026lt;tabbar v-model=\u0026#34;index\u0026#34;\u0026gt; \u0026lt;tabbar-item\u0026gt;\u0026lt;/tabbar-item\u0026gt; ... \u0026lt;tabbar-item\u0026gt;\u0026lt;/tabbar-item\u0026gt; \u0026lt;/tabbar\u0026gt; \u0026lt;/template\u0026gt; \u0026lt;script\u0026gt; data(){ return{ index:0, ... } } methods:{ changeTab(num){ ... this.index = num; ... } } \u0026lt;/scirpt\u0026gt; 然后子组件中调用\nmounted(){ this.$emit(\u0026#39;changeTab\u0026#39;, 2) } 这样就便于在不同的组件内都可以更改 Tabbar 选中状态\n","permalink":"https://hivan.me/posts/vux-tabbar-selected/","summary":"\u003cp\u003e在 vux 的文档和示例中，都没有明确的说明 tabbar 上 v-model 的使用\u003c/p\u003e\n\u003cp\u003e文档中将\u003ccode\u003ev-model\u003c/code\u003e说明放在了 TabbarItem 示例下，但是其实这个应该是放在\u003ccode\u003eTabbar\u003c/code\u003e上\u003c/p\u003e\n\u003cdiv class=\"highlight\"\u003e\u003cpre tabindex=\"0\" style=\"color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;\"\u003e\u003ccode class=\"language-javascript\" data-lang=\"javascript\"\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#f92672\"\u003e\u0026lt;\u003c/span\u003e\u003cspan style=\"color:#a6e22e\"\u003etemplate\u003c/span\u003e\u003cspan style=\"color:#f92672\"\u003e\u0026gt;\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e    \u003cspan style=\"color:#f92672\"\u003e\u0026lt;\u003c/span\u003e\u003cspan style=\"color:#a6e22e\"\u003erouter\u003c/span\u003e\u003cspan style=\"color:#f92672\"\u003e-\u003c/span\u003e\u003cspan style=\"color:#a6e22e\"\u003eview\u003c/span\u003e \u003cspan style=\"color:#66d9ef\"\u003eclass\u003c/span\u003e\u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e\u003cspan style=\"color:#e6db74\"\u003e\u0026#34;view\u0026#34;\u003c/span\u003e \u003cspan style=\"color:#a6e22e\"\u003ev\u003c/span\u003e\u003cspan style=\"color:#f92672\"\u003e-\u003c/span\u003e\u003cspan style=\"color:#a6e22e\"\u003eon\u003c/span\u003e\u003cspan style=\"color:#f92672\"\u003e:\u003c/span\u003e\u003cspan style=\"color:#a6e22e\"\u003echangeTab\u003c/span\u003e\u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e\u003cspan style=\"color:#e6db74\"\u003e\u0026#34;changeTab\u0026#34;\u003c/span\u003e\u003cspan style=\"color:#f92672\"\u003e\u0026gt;\u0026lt;\u003c/span\u003e\u003cspan style=\"color:#960050;background-color:#1e0010\"\u003e/router-view\u0026gt;\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e    \u003cspan style=\"color:#f92672\"\u003e\u0026lt;\u003c/span\u003e\u003cspan style=\"color:#a6e22e\"\u003etabbar\u003c/span\u003e \u003cspan style=\"color:#a6e22e\"\u003ev\u003c/span\u003e\u003cspan style=\"color:#f92672\"\u003e-\u003c/span\u003e\u003cspan style=\"color:#a6e22e\"\u003emodel\u003c/span\u003e\u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e\u003cspan style=\"color:#e6db74\"\u003e\u0026#34;index\u0026#34;\u003c/span\u003e\u003cspan style=\"color:#f92672\"\u003e\u0026gt;\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e        \u003cspan style=\"color:#f92672\"\u003e\u0026lt;\u003c/span\u003e\u003cspan style=\"color:#a6e22e\"\u003etabbar\u003c/span\u003e\u003cspan style=\"color:#f92672\"\u003e-\u003c/span\u003e\u003cspan style=\"color:#a6e22e\"\u003eitem\u003c/span\u003e\u003cspan style=\"color:#f92672\"\u003e\u0026gt;\u0026lt;\u003c/span\u003e\u003cspan style=\"color:#960050;background-color:#1e0010\"\u003e/tabbar-item\u0026gt;\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e        ...\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e        \u003cspan style=\"color:#f92672\"\u003e\u0026lt;\u003c/span\u003e\u003cspan style=\"color:#a6e22e\"\u003etabbar\u003c/span\u003e\u003cspan style=\"color:#f92672\"\u003e-\u003c/span\u003e\u003cspan style=\"color:#a6e22e\"\u003eitem\u003c/span\u003e\u003cspan style=\"color:#f92672\"\u003e\u0026gt;\u0026lt;\u003c/span\u003e\u003cspan style=\"color:#960050;background-color:#1e0010\"\u003e/tabbar-item\u0026gt;\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e    \u003cspan style=\"color:#f92672\"\u003e\u0026lt;\u003c/span\u003e\u003cspan style=\"color:#960050;background-color:#1e0010\"\u003e/tabbar\u0026gt;\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#f92672\"\u003e\u0026lt;\u003c/span\u003e\u003cspan style=\"color:#960050;background-color:#1e0010\"\u003e/template\u0026gt;\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#f92672\"\u003e\u0026lt;\u003c/span\u003e\u003cspan style=\"color:#a6e22e\"\u003escript\u003c/span\u003e\u003cspan style=\"color:#f92672\"\u003e\u0026gt;\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#a6e22e\"\u003edata\u003c/span\u003e(){\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e    \u003cspan style=\"color:#66d9ef\"\u003ereturn\u003c/span\u003e{\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e        \u003cspan style=\"color:#a6e22e\"\u003eindex\u003c/span\u003e\u003cspan style=\"color:#f92672\"\u003e:\u003c/span\u003e\u003cspan style=\"color:#ae81ff\"\u003e0\u003c/span\u003e,\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e        ...\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e    }\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e}\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#a6e22e\"\u003emethods\u003c/span\u003e\u003cspan style=\"color:#f92672\"\u003e:\u003c/span\u003e{\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e    \u003cspan style=\"color:#a6e22e\"\u003echangeTab\u003c/span\u003e(\u003cspan style=\"color:#a6e22e\"\u003enum\u003c/span\u003e){\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e        ...\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e        \u003cspan style=\"color:#66d9ef\"\u003ethis\u003c/span\u003e.\u003cspan style=\"color:#a6e22e\"\u003eindex\u003c/span\u003e \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e \u003cspan style=\"color:#a6e22e\"\u003enum\u003c/span\u003e;\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e        ...\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e    }\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e}\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#f92672\"\u003e\u0026lt;\u003c/span\u003e\u003cspan style=\"color:#960050;background-color:#1e0010\"\u003e/scirpt\u0026gt;\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\u003cp\u003e然后子组件中调用\u003c/p\u003e\n\u003cdiv class=\"highlight\"\u003e\u003cpre tabindex=\"0\" style=\"color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;\"\u003e\u003ccode class=\"language-javascript\" data-lang=\"javascript\"\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#a6e22e\"\u003emounted\u003c/span\u003e(){\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e    \u003cspan style=\"color:#66d9ef\"\u003ethis\u003c/span\u003e.\u003cspan style=\"color:#a6e22e\"\u003e$emit\u003c/span\u003e(\u003cspan style=\"color:#e6db74\"\u003e\u0026#39;changeTab\u0026#39;\u003c/span\u003e, \u003cspan style=\"color:#ae81ff\"\u003e2\u003c/span\u003e)\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e}\n\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\u003cp\u003e这样就便于在不同的组件内都可以更改 Tabbar 选中状态\u003c/p\u003e","title":"vux 更改 Tabbar 选中状态"},{"content":"关于移动端的适配，都知道其实 rem 是比较好的一个适配方案，但是 rem 是根据根目录的字体大小来调解的，那么，我们在做网页的时候，屏幕旋转后，能否让根目录的字体跟着变化呢？\n先上代码：\n$(function(){ var size = $(window).width() / 25; $(\u0026#39;html\u0026#39;).css(\u0026#39;font-size\u0026#39;: size); }); 这样在 css 中用 rem 单位是没什么问题，但是如果屏幕旋转之后，你就会发现，真的不能看了就。原因就是屏幕旋转以后，根上的字体并没有随之变化。\n所以我们来加上\n// 监视设备方向 window.addEventListener(\u0026#34;orientationchange\u0026#34;, function() { media(); }, false); function media(argument) { // 因为获取尺寸出错，需要延迟获取 setTimeout(function(){ var size = $(window).width() / 25; console.log(\u0026#39;the device size: \u0026#39;+size); $(\u0026#39;html\u0026#39;).css(\u0026#39;font-size\u0026#39;, size); }, 200); } ","permalink":"https://hivan.me/posts/css-rem-and-javascript/","summary":"\u003cp\u003e关于移动端的适配，都知道其实 rem 是比较好的一个适配方案，但是 rem 是根据根目录的字体大小来调解的，那么，我们在做网页的时候，屏幕旋转后，能否让根目录的字体跟着变化呢？\u003c/p\u003e\n\u003cp\u003e先上代码：\u003c/p\u003e\n\u003cdiv class=\"highlight\"\u003e\u003cpre tabindex=\"0\" style=\"color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;\"\u003e\u003ccode class=\"language-javascript\" data-lang=\"javascript\"\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#a6e22e\"\u003e$\u003c/span\u003e(\u003cspan style=\"color:#66d9ef\"\u003efunction\u003c/span\u003e(){\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e  \u003cspan style=\"color:#66d9ef\"\u003evar\u003c/span\u003e \u003cspan style=\"color:#a6e22e\"\u003esize\u003c/span\u003e \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e \u003cspan style=\"color:#a6e22e\"\u003e$\u003c/span\u003e(window).\u003cspan style=\"color:#a6e22e\"\u003ewidth\u003c/span\u003e() \u003cspan style=\"color:#f92672\"\u003e/\u003c/span\u003e \u003cspan style=\"color:#ae81ff\"\u003e25\u003c/span\u003e;\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e  \u003cspan style=\"color:#a6e22e\"\u003e$\u003c/span\u003e(\u003cspan style=\"color:#e6db74\"\u003e\u0026#39;html\u0026#39;\u003c/span\u003e).\u003cspan style=\"color:#a6e22e\"\u003ecss\u003c/span\u003e(\u003cspan style=\"color:#e6db74\"\u003e\u0026#39;font-size\u0026#39;\u003c/span\u003e\u003cspan style=\"color:#f92672\"\u003e:\u003c/span\u003e \u003cspan style=\"color:#a6e22e\"\u003esize\u003c/span\u003e);\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e});\n\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\u003cp\u003e这样在 css 中用 rem 单位是没什么问题，但是如果屏幕旋转之后，你就会发现，真的不能看了就。原因就是屏幕旋转以后，根上的字体并没有随之变化。\u003c/p\u003e\n\u003cp\u003e所以我们来加上\u003c/p\u003e\n\u003cdiv class=\"highlight\"\u003e\u003cpre tabindex=\"0\" style=\"color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;\"\u003e\u003ccode class=\"language-javascript\" data-lang=\"javascript\"\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#75715e\"\u003e// 监视设备方向\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#75715e\"\u003e\u003c/span\u003ewindow.\u003cspan style=\"color:#a6e22e\"\u003eaddEventListener\u003c/span\u003e(\u003cspan style=\"color:#e6db74\"\u003e\u0026#34;orientationchange\u0026#34;\u003c/span\u003e, \u003cspan style=\"color:#66d9ef\"\u003efunction\u003c/span\u003e() {\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e  \u003cspan style=\"color:#a6e22e\"\u003emedia\u003c/span\u003e();\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e}, \u003cspan style=\"color:#66d9ef\"\u003efalse\u003c/span\u003e);\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#66d9ef\"\u003efunction\u003c/span\u003e \u003cspan style=\"color:#a6e22e\"\u003emedia\u003c/span\u003e(\u003cspan style=\"color:#a6e22e\"\u003eargument\u003c/span\u003e) {\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e  \u003cspan style=\"color:#75715e\"\u003e// 因为获取尺寸出错，需要延迟获取\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#75715e\"\u003e\u003c/span\u003e  \u003cspan style=\"color:#a6e22e\"\u003esetTimeout\u003c/span\u003e(\u003cspan style=\"color:#66d9ef\"\u003efunction\u003c/span\u003e(){\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e    \u003cspan style=\"color:#66d9ef\"\u003evar\u003c/span\u003e \u003cspan style=\"color:#a6e22e\"\u003esize\u003c/span\u003e \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e \u003cspan style=\"color:#a6e22e\"\u003e$\u003c/span\u003e(window).\u003cspan style=\"color:#a6e22e\"\u003ewidth\u003c/span\u003e() \u003cspan style=\"color:#f92672\"\u003e/\u003c/span\u003e \u003cspan style=\"color:#ae81ff\"\u003e25\u003c/span\u003e;\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e    \u003cspan style=\"color:#a6e22e\"\u003econsole\u003c/span\u003e.\u003cspan style=\"color:#a6e22e\"\u003elog\u003c/span\u003e(\u003cspan style=\"color:#e6db74\"\u003e\u0026#39;the device size: \u0026#39;\u003c/span\u003e\u003cspan style=\"color:#f92672\"\u003e+\u003c/span\u003e\u003cspan style=\"color:#a6e22e\"\u003esize\u003c/span\u003e);\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e    \u003cspan style=\"color:#a6e22e\"\u003e$\u003c/span\u003e(\u003cspan style=\"color:#e6db74\"\u003e\u0026#39;html\u0026#39;\u003c/span\u003e).\u003cspan style=\"color:#a6e22e\"\u003ecss\u003c/span\u003e(\u003cspan style=\"color:#e6db74\"\u003e\u0026#39;font-size\u0026#39;\u003c/span\u003e, \u003cspan style=\"color:#a6e22e\"\u003esize\u003c/span\u003e);\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e  }, \u003cspan style=\"color:#ae81ff\"\u003e200\u003c/span\u003e);  \n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e}\n\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e","title":"关于设备转向后的自适应"},{"content":"对于文具的偏爱，估计是从学生时代就开始了。记得高中的时候缠着老爸给买了第一个奢侈品：一支派克。虽然很珍惜，却并不好用。自此对文具就更是挑剔。\n一支好笔，真的能陪一个人好久好久。\n打拆包之前，就一股浓浓的逼格。\n不过问题来了，我不知道怎么打开包装！XD - 一直不敢使劲，生怕损坏了什么，折腾了好久，才明白，原来连着盒盖的那张纸，是用来撕的。\n打开之后，金色的笔体和其皮外套分开躺在盒内，逼格更甚了。\n喜欢盒子内部的那句话：“合适的形态总有他合适的作用，还原物的本质，至真至纯，用一支笔唤起书写的初心。”， 好吧，又是一个走情怀的产品。不过对于这个包装和笔本身的设计来说，这个情怀我还是蛮受用的。\n而关于细节上，笔的打磨很是花费了一番功夫，那种黄铜的质感以及似乎岁月沉淀的感觉，让人爱不释手。（这些天只要是要签字的机会，我基本都拿出它来装逼）。\n而关于旋转笔头，旋转的过程中特别的舒服。到位后的力回馈也刚刚好，让我转来转去玩了好久。额，给玩的不灵光了。（看来关于使用强度，长家还是需要加强）。\n关于那件小皮装，似乎有点小了，装进去是用了一些力气的。额，拿出来就更费劲了，太紧了。不过相信用一段时间，也就松了。对于这样一直精致的情怀笔，这件皮衣还是相当必要的。\n笔当然还是要用才能知道是否合心意, VH 这支黄铜笔，拿在手上的感觉，重量十足，根本是那些塑料笔没办法比的。但是这个重量不大不小，真的正合适。想起厂商吹的牛逼：“打造了最佳的配重”。 这牛逼不为过。 至于握感上，这个笔对于我来说似乎纤细了一点，可能也是因为我握惯了粗的笔，包括我最爱的那支 53Pencil，也是比较粗的。而在厂家原装的那支笔芯，使用起来也是非常顺畅（就喜欢这样顺畅的笔芯）。不过用完需要更换笔芯的时候，就需要买好一点了，要不可惜了这么好的笔。\n最后随笔画了两幅画来试笔。\n太久不画画了，大家将就看吧！\n有想买的，链接在这里 : 首页-vh 企业店\nPS：VH 家的那个无线充电器，也是逼格满满。打算入一个。。。\n","permalink":"https://hivan.me/posts/vh-brass-pen/","summary":"\u003ca href=\"http://zhuanlan.zhihu.com/hivandu/20542022\"\u003e「知乎专栏地址」\u003c/a\u003e\n收到 @罗文森 赠送的笔已经很多天了，快两个星期了吧。\n一直在找时间想写一篇评测出来，终究是没抽出时间，况且，我不是写手！眼看时间一天天过去，心里也越来越愧疚。\n其实最主要的也不单单是写不出什么，而是总归要用它画两幅画出来，才会感觉的出来到底合不合心意。","title":"VH 情怀黄铜原子笔"},{"content":"「SOLOVE 移动电源 Air-M20000」\n照例先来几张开箱图\n本来应该再早一个月拿到这款电源的，因为产能的原因，跳票了。好吧，后来又因为快递单的遗漏，跳票了更久。不过好东西都是值得等待的。\n第一次打开包装盒的时候，拿在手上满满的质感，身躯娇小，容量却很大。不过说起来，因为控制了高宽的原因（高宽仅与一张信用卡大小相仿），厚度不太理想。达到了一枚 1 元硬币的厚度，虽然握在手上的手感十分舒服，但是如果对于想揣在衣服口袋里的人来说，可能这个厚度稍稍有点不甚让人满意，说起来，10000mAh 的容量，能做到如此地步已经很不容易了。\n而对于 SOLOVE Air-M20000 的设计，相信也是能俘获很多人的心。电源灯的效果真的是很漂亮。\n也许是因为控制尺寸的原因吧，SOLOVE Air-M20000 并没有像其他大容量移动电池一样配备两个输出插孔，只有一个。输入 Micro-USB, 输出为 USB-A.\n不过联想多数时候，我的小米电源另一个输出口都空的情况，其实一个已经足够了，重点是轻便易携带。\n上周五这款电源已经到货了，没有第一时间拿来写当然是为了接受下周末的检验。\n实际使用中，周五的晚上电源满电，周六出门 12 点半到下午 6 点，iPhone 6 从 3 点开始电量百分之 15，接入 SOLOVE，1 小时后达到 92%，期间不停的在发微信。然后取下电源，周六没有为 SOLOVE 充电，周日 12 点多左右出门，到下午 6 点多回来，iPhone 6 经历了两次空电的情况。而两次 SOLOVE 都将电量充满。\n实际使用情况下，SOLOVE 的容量还是不错的，iPhone 6 来回充满三到四次应该是不成问题，更重要的是，充电速度很快。在不断使用过程中，一个小时基本就可以完全充满。当然，每个不同型号的手机可能都有差别。\n所以 SOLOVE 的 Air-M20000, 无论是从设计，做工还是续航情况，都还是蛮值得入手的。唯一遗憾的点，为什么给了我一个红色的，而不是黑色或白色的。。。。\n","permalink":"https://hivan.me/posts/solove/","summary":"第一次为产品写评测吧我这是，也不太记得了。不过以下这个电源，觉得值得写上一篇。","title":"SOLOVE Air-M20000"},{"content":"其实这根本就不值得写出来，只是可能前几步大家都做了，只是最后一步就忽略了。 我们在自定义input:file的时候，一般来说都是外边包一层，里边在写一个\u0026lt;input type=\u0026quot;file\u0026quot;\u0026gt;, 然后将其透明值设置成0,然后再定义外层的样式来达到自定义的目的。\nHTML：\n\u0026lt;div class=\u0026quot;upfileOutWrap\u0026quot;\u0026gt; \u0026lt;div class=\u0026quot;upfileWrap\u0026quot;\u0026gt;\u0026lt;input type=\u0026quot;file\u0026quot;\u0026gt;\u0026lt;/div\u0026gt; \u0026lt;div class=\u0026quot;upfileBG\u0026quot;\u0026gt;upload image\u0026lt;/div\u0026gt; \u0026lt;/div\u0026gt; CSS：\n.upfileOutWrap { cursor: pointer; width: 199px; height: 42px; line-height: 42px; position: relative; } .upfileWrap{ width: 100%; height: 100%; position: absolute; top:-1; left: -1; z-index:2; } .upfileWrap input{ opacity: 0; filter: alpha(opacity=0); cursor: pointer; width: 100%; height: 100%; font-size: 32px; } .upfileBG{ width:100%; height:100%; background: url(./images/upload.png) no-repeat; font-size: 14px; color: white; position: absolute; top:-1; left: -1; padding-left:10px; z-index:1; } 可是这个时候还是有点问题，就是万恶的 IE 下边。\nIE 下边的input标签默认都是有光标的，:file也不例外，而且 IE 下边必须要点击”Browse”或者双击input输入框才会有效果。那么这个时候在 IE 下就会出现如图的莫名其妙的问题，注意左边的光标，并且还需要双击才会弹出文件选择窗口。 这个时候如果你把 input 透明度设置成 100 显示出来，就会发现原来是这样的。\n所以这个时候，如果是其他标准浏览器，那么设置好 input 的高宽就搞定了，而 IE 下边，还必须考虑如何让”Browse”按钮能铺满我们所自定的 div 样式。这样我们才能实现 IE 下不出现光标，而且单击弹出文件选择窗口。\n这个时候，看似毫无办法，其实我们可以选择增加字体的大小。当字体变成32px的时候，就是这个样子的。 好了，这样我们就搞定了，将input:file 继续设置为完全透明。那个可恶的光标不见了，我们也可以实现 IE 下单击。当然，字体到底用多大的，要视你自己定义的视觉效果来看，自己调试吧。\n** Final CSS: **\n.upfileOutWrap { cursor: pointer; width: 199px; height: 42px; line-height: 42px; position: relative; } .upfileWrap{ width: 100%; height: 100%; position: absolute; top:-1; left: -1; z-index:2; } .upfileWrap input{ opacity: 0; filter: alpha(opacity=0); cursor: pointer; width: 100%; height: 100%; } .upfileBG{ width:100%; height:100%; background: url(./images/upload.png) no-repeat; font-size: 14px; color: white; position: absolute; top:-1; left: -1; padding-left:10px; z-index:1; } ","permalink":"https://hivan.me/posts/custom-inputfile/","summary":"\u003cp\u003e其实这根本就不值得写出来，只是可能前几步大家都做了，只是最后一步就忽略了。\n\u003cimg loading=\"lazy\" src=\"https://farm6.staticflickr.com/5196/14320298586_2c05c821ac_o_d.png\"\u003e\n我们在自定义\u003ccode\u003einput:file\u003c/code\u003e的时候，一般来说都是外边包一层，里边在写一个\u003ccode\u003e\u0026lt;input type=\u0026quot;file\u0026quot;\u0026gt;\u003c/code\u003e, 然后将其透明值设置成\u003ccode\u003e0\u003c/code\u003e,然后再定义外层的样式来达到自定义的目的。\u003c/p\u003e","title":"自定义文件上传框"},{"content":"而现在提供免费下载的这款滤镜就是针对 LightRoom 的滤镜插件，有 OS X 和 WIN 两个版本提供。\nWe are thrilled to announce VSCO Film 00, our first-ever FREE starter pack.\nPerfect for anyone who uses VSCO Cam and is looking to take the next step, VSCO Film 00 brings beautiful presets, custom camera profiles, and the familiar VSCO editing experience to your desktop in Adobe Lightroom.\nVSCO Film 00 includes two of our most popular film stocks (Kodak Gold 100 from Film 05 and Kodak Tri-X from Film 06) and is available for download now.\n有需要的可以点击原文链接进行下载，不要感谢我，我是雷锋。\nPS: 关于之前我提到的 Enlight 导出图片丢失数据的部分，我在此向读者和开发者致歉，原因是导出的如果是 PNG 图片是不能保存那些信息的，可以将图片的质量从 Pro 调小到 Hight, 保存的就是 JPEG 图片，那么导出的图片地理位置和相机信息就会一并保存了。\n","permalink":"https://hivan.me/posts/vsco-film-00-free-starter-pack/","summary":"[本文知乎专栏][1]\nVSCOCam 是 iOS 上一款滤镜相机，其最著名的地方就是他们的胶片滤镜。 而其实 VSCO 在这款 APP 之前，就一直在做胶片滤镜，有 OS X 和 WIN 两个平台的版本。最主要的是作为 LightRoom 的插件存在。","title":"VSCO FILM 00 FREE STARTER PACK"},{"content":"很多人都会使用\u0026quot;Pow\u0026quot;来进行本地静态页面开发环境。对于其配置确实简单到有爱。\n不过 Yosemite 上\u0026quot;Pow\u0026quot;都不能正常工作，之前我参照官方的办法写了一篇如何在 Yosemite 上设置\u0026quot;Pow\u0026quot;的方法。有兴趣的可以参看我原文:\n不过一直用的好好的\u0026quot;Pow\u0026quot;近期又开始出现 404 错误，短暂的解决无果以后，我开始寻找一些快速能解决的办法，便遇到了\u0026quot;Power\u0026quot;，和\u0026quot;Pow\u0026quot;一样，都是建立快速开发环境，并且经过测试，在 Yosemite 10.10.1 下正常工作。\n使用\u0026quot;Pow\u0026quot;来做开发环境的，可以暂时用这样一个替代方案，毕竟开发中没有时间去多做研究了，暂时不知道两者的区别，不过\u0026quot;.dev\u0026quot;正常访问已经没有问题。\nPower 项目地址[^1]: https://github.com/HackPlan/power/\n","permalink":"https://hivan.me/posts/power-for-mac/","summary":"\u003cimg alt=\"image-20240729151501225\" loading=\"lazy\" src=\"https://cdn.jsdelivr.net/gh/hivandu/notes/img/202407291526887.png\"\u003e","title":"Power for Mac"},{"content":"\n1, 添加文件com.pow到/etc/pf.anchors/目录内\nsudo vim /etc/pf.anchors/com.pow\n2, 在文件内添加代码:\nrdr pass on lo0 inet proto tcp from any to any port 80 -\u0026gt; 127.0.0.1 port 20559 rdr pass on en0 inet proto tcp from any to any port 80 -\u0026gt; 127.0.0.1 port 20559 rdr pass on en9 inet proto tcp from any to any port 80 -\u0026gt; 127.0.0.1 port 20559 NOTE: 代码后一行必须要有换行符，否则会出现语法错误\n3, 打开文件/etc/pf.conf\n4, 添加代码: rdr-anchor \u0026quot;pow\u0026quot;，需要添加到rdr-anchor \u0026quot;com.apple/*\u0026quot;下一行\n5, 打开文件/etc/pf.anchors/com.apple, 并添加代码:\nload anchor \u0026#34;pow\u0026#34; from \u0026#34;/etc/pf.anchors/com.pow\u0026#34; NOTE: 一样必须有换行符\n6, 终端执行:\nsudo pfctl -f /etc/pf.conf\n7, 好了，现在可以打开pf了:\nsudo pfctl -e\n","permalink":"https://hivan.me/posts/setting-pow-at-yosemite/","summary":"在 Yosemite 中，Pow 安装和启动是有问题的。这是因为 ipfw 被移除了，所以如果要在 Yosemite 中跑 Pow，需要做些设置才可以。","title":"在 Yosemite 中设置 Pow"},{"content":"\n当然到现在并不完美，因为 rsync 和自动执行generate的代码我没有完成。安装incrond的时候总会出错，于是无法执行集群文件同步.所以现在还是在终端里执行generate和cp -rf /home/xxxx/* /home/xxxx\n我这里并不是要教设置步骤，因为其实@lucifr 已经在他的这篇文里写的很清楚了，我就写几点注意事项\n搞定 VPS 操作和基本的 Linux 命令很重要。 要搞定 lnmp，参照这里的 lnmp 详细介绍 新版本的 Hexo 有更改，在同一目录里是找不到/cli/generate.js 的，更别说 console.log 语句了 @lucifr 所说的新建立一个 Dropbox 账户，意思是在 VPS 主机上建立一个账户用来执行 Dropbox 同步，而不是新建立一个 Dropbox 账户。 其他… 好吧，写其他是因为 iPad 上用 VI 进行编辑实在有点难受，现在先这样了，以后有时间了再写一个更详细的。\n","permalink":"https://hivan.me/posts/vps-setting-hexo/","summary":"首先需要感谢@\u003ca href=\"http://lucifr.com/\"\u003elucifr\u003c/a\u003e，我现在这篇文就是在 iPad 上登录 VPS 完成的。最后还是忍不住入手了下边两个 APP:","title":"VPS 设置 Hexo"},{"content":"网上查找资料，说是随着系统的更新，Apache 本本更新到 2.4.9，PHP 也更新到了 5.5.14，所以 Apache 的配置就需要做相应的修改。\n首先，我们需要确定打开了 Apache\nsudo apachectl start 然后设置允许访问用户目录\n修改 httpd.conf 配置 sudo subl /etc/apache2/httpd.conf command + f 查找代码，并去掉注释符 #\nLoadModule authz_core_module libexec/apache2/mod_authz_core.so LoadModule authz_host_module libexec/apache2/mod_authz_host.so LoadModule userdir_module libexec/apache2/mod_userdir.so LoadModule php5_module libexec/apache2/libphp5.so Include /private/etc/apache2/extra/httpd-vhosts.conf Include /private/etc/apache2/extra/httpd-userdir.conf 修改 httpd-userdir.conf 配置 sudo subl /etc/apache2/extra/httpd-userdir.conf command + f 查找以下代码，去掉注释符#\nInclude /private/etc/apache2/users/*.conf 修改 yourUserName.conf 配置 sudo subl /etc/apache2/users/username.conf PS: username 为你的用户名称，如果没有该文件则新建一个，然后将内容修改为:\nOptions Indexes MultiViews AllowOverride None Require all granted 然后设置文件权限为755\nsudo chmod 755 /etc/apache2/users/haibor.conf 最后我们需要重启 Apache\nsudo apachectl restart ","permalink":"https://hivan.me/posts/yosemite-open-usersite/","summary":"升级到 OSX 10.10(Yosemite)以后，\u003ccode\u003elocalhost\u003c/code\u003e是可以正常访问的，只是\u003ccode\u003elocalhost/~user\u003c/code\u003e无法打开了，提示 403 错误。","title":"Yosemite 访问用户级服务器目录"},{"content":"世界，真心是美好的啊！\n好吧，再使用 nitrous 之前，我一直都不知道，原来世界可以如此美好。\n不需要 Dropbox 君，不需要 git 的 master 了，而且可以随时随地 hexo。\n妈妈再也不用担心我的 md 文档丢失了。。。\n这货不仅仅是 node.js,还有 Rails，python 以及 Go 环境哦。。。\n亲们，还在等什么。如果 windows 下的 Ruby 环境配置让你想砸机子的冲动，那么赶快来 nitrous.io 吧！\n","permalink":"https://hivan.me/posts/nitrousio/","summary":"\u003cp\u003e世界，真心是美好的啊！\u003c/p\u003e\n\u003cp\u003e好吧，再使用 nitrous 之前，我一直都不知道，原来世界可以如此美好。\u003c/p\u003e\n\u003cp\u003e不需要 Dropbox 君，不需要 git 的 master 了，而且可以随时随地 hexo。\u003c/p\u003e\n\u003cp\u003e妈妈再也不用担心我的 md 文档丢失了。。。\u003c/p\u003e\n\u003cp\u003e这货不仅仅是 node.js,还有 Rails，python 以及 Go 环境哦。。。\u003c/p\u003e\n\u003cp\u003e亲们，还在等什么。如果 windows 下的 Ruby 环境配置让你想砸机子的冲动，那么赶快来 \u003ca href=\"https://www.nitrous.io/join/vREqfwMikHc\"\u003enitrous.io\u003c/a\u003e 吧！\u003c/p\u003e","title":"nitrous.io"},{"content":"前段时间想通过 QQ 邮箱将自己的写的博客里的文章转发到 QQ 空间去。众所周知，这种转发似乎只有通过 QQ 邮箱发邮件转发一条路。要不就只有 Ctrl+C，Ctrl+V。我是很厌恶这种方式的。\n可是在 QQ 邮箱里，并没有收到自己从使用 hexo 以后的任何更新。好吧，我知道问题了，我失去了 feed 订阅路径。而 Google 的结果是，hexo 自身并不带 feed 订阅，如果要支持订阅需要安装插件，也就是“hexo-generator-feed\u0026quot; 这货。 后来，良久之后@lucifr也是这么告诉我的。\n前段时间家里有事，离开上海，回来以后又因为工作需要恶补，一直没时间弄！这刚刚才弄好。。。。\n说一下这货，其实是蛮简单的，我也就不再细述了，官方文档也将如何安装写的很清晰。自己查阅一下就好。\n","permalink":"https://hivan.me/posts/hexo-generator-feed/","summary":"\u003cp\u003e前段时间想通过 QQ 邮箱将自己的写的博客里的文章转发到 QQ 空间去。众所周知，这种转发似乎只有通过 QQ 邮箱发邮件转发一条路。要不就只有 Ctrl+C，Ctrl+V。我是很厌恶这种方式的。\u003c/p\u003e","title":"hexo-generator-feed"},{"content":"今早一打开网页,满篇都是关于 Google 将于 7 月 1 日正式关闭 Reader 的消息.\n再见吧,Reader. 这八年,基本每天,我都会去看看你\u0026hellip;\n可是又能如何,到了该走的时候了.即便这个理由让我完全无法接受.\nBye, 我亲爱的 GReader\u0026hellip;\n","permalink":"https://hivan.me/posts/bye-google-reader/","summary":"\u003cp\u003e今早一打开网页,满篇都是关于 Google 将于 7 月 1 日正式关闭 Reader 的消息.\u003c/p\u003e\n\u003cp\u003e再见吧,Reader. 这八年,基本每天,我都会去看看你\u0026hellip;\u003c/p\u003e\n\u003cp\u003e可是又能如何,到了该走的时候了.即便这个理由让我完全无法接受.\u003c/p\u003e\n\u003cp\u003eBye, 我亲爱的 GReader\u0026hellip;\u003c/p\u003e","title":"bye-google-reader"},{"content":"如果想同步到 Google+请看完文章后看最后部分的更新说明!\n如何实现 通过众所周知的ifttt\n其实,这主要是一个我为了保存自己照片的方式!(爱信不信,不相信拉倒!)\n建立一个 task, if Instagram 设定条件:New Liked photo, then Gmail 设定条件:Send an email.\n好了,填上 To address: xxx@qq.com 就 OK 了! 简单吧?\n如果你熟悉某个联系人,那么建立规则 by Username,然后包含此用户名关键词的主题都标上相应的关键词.比如by ladiiprang在邮箱规则里就可以加上\u0026quot;妹子\u0026quot;的 tags.\n注意点 如果你不想后期被众多的新邮件搞得头昏脑胀的话,那么你一开始就要设定好过滤条件!\n在 ifttt 中,Send an email 的时候 Subject 记得填写上一些关键词,比如 From Instagram,这样,对于主题内有关键词的邮件就好管理的多了.添加过滤规则就好了!将来自己发送邮箱 Gmail 的邮件主题包含 From 和 Instagram 的都自动移动到一个新建的文件夹内,Ex:Photo DB,完工!\n后记 同理,我们也可以建立来自 Flickr 的发送规则,原理是一样的!注意 Gmail 邮箱里的过滤条件要建好!否则 Gmail 爆满是迟早的事情!运用这样的规则,我们还可以发送Dropbox里的文档到 QQ 邮箱内保存,不过规则限定发送的只能是 Public 内的文档!\n其实一开始我不确定是发送文件还是只有地址!所以我开始的方法很绕,就是将相片想 Save 到 Dropbox,然后再通过 Dropbox 建立 if.then.Gmail.不过试验下来既然能直接发送文件,建立 task 就简单多了!\n还等什么,快去你的 Instagram 和 Flickr 上收藏妹子到邮箱内吧!\n更新 本来因为标题的原因,这点是不加在这里的!但是想着再写一篇一样意义的文章很没意思,所以就在这里说明一下好了!\n在我这篇文章发布之后,G+上看到了电脑玩物的作者+esor huang 的一篇讲解Instagram 同步到 Dropbox 和 Google+的说明!以及这篇 E 文!说起来,这样的同步方式确实很笨拙.你打算电脑 24 小时开着 picasa 来为你同步么?那么,我从我这篇文的基础上考虑可行方案!记得之前 Picasaweb 给每个人都有一个邮箱推送地址!就是类似 username.password@picasaweb.com 这样的地址!好吧,有邮箱地址就简单了.不过这个地址需要你再登录 picasaweb.com 去找,在 Google+页面上是找不到的!同理,Flickr 也有类似的推送地址! 该怎么做我想你已经清楚了吧!\n最后,我想到了是否同样可以传送到 QQ 相册!毕竟和邮箱最大的不同就是相册是用来分享的,而邮箱是用来保存的!可惜,QQ 没有针对相册的推送,而推送到 QQ 空间的 XXX@qzone.qq.com 这个地址也是必须 QQ 邮箱内部发才行!是的,和你们一样,我又想到了邮箱转发规则.用 QQ 邮箱收到邮件后转发到 QQ 空间邮箱去不就好了!测试后,果然.\n\u0026hellip;\n果然没那么简单,这次我失误了,特么的 QQ 小气到不允许自己的 QQ 邮箱转发邮件到 QQzone 的邮箱来自动推送文章!提示这是一个无效地址!不过也无所谓了,毕竟是推送文章的邮件地址,你也不想自己的 QQ 空间全是大片的文章,而且每篇文章里只有一张相片吧 ?\n这个说明本来是在 G+上有提出的,但是有基友测试成功后给的是这里的 url,所以我就想,补上这个说明!谁说是一样的到底,但是如果不提 Picasaweb 有邮件推送地址,估计很多人都已经忘记了!为了找同步到 G+上的朋友会看得糊里糊涂!\n","permalink":"https://hivan.me/posts/auto-save-photo-to-qqmail/","summary":"\u003ch3 id=\"前言\"\u003e前言\u003c/h3\u003e\n\u003cp\u003e为什么是 QQ Mail?\n因为它大,而且不断自动扩容,你想把它装满暂时是不太现实. 而且来说,QQ 邮箱的体验还是非常不错的!过滤规则也很能满足要求,归档搜索查找都不错!一些不涉及隐私而又想保存的文件或者照片或者其他什么东东,存在 QQ 邮箱里还是不错的!比如:XXX\u003c/p\u003e","title":"Auto-save-photo-to-qqmail"},{"content":"在 Gmail 中创建转发过滤器是有上限的,这个估计没有多少人知道!我想也是因为没有多少人有实际的需求…\n今天我在 Twitter 上抱怨 Gmail 里的邮件过多,占用了很大一部分空间,以至于我的 Gmail 空间已经临近上限… 也终于迫使我开始清理邮件!\n###问题产生:### 在清理的时候发现,除了一些比较大附件的邮件以外,大部分占用空间的邮件都是一些广告邮件!\n我很少设置过滤器删除广告邮件,特别是一些推送服务的广告,不过大多都是直接略过收件箱存档而已,有空了还可以去看看…这也使得我这几年来邮箱里布满了此类邮件!\n当然,清理的过程还是比较顺利的,毕竟这些邮件的时效性的原因,这些邮件删除起来一点也不心疼!\n那么,对于新邮件该怎么处理呢,总不能继续占用存储空间吧.也不好直接删除,毕竟有的时候我还是要看看的.\n那么,过滤器这个时候就起作用了!\n我的想法很简单,既然 QQ 邮箱空间是无上限的,而广告邮件又无关乎隐私问题,所以可以放心的转发到 QQ 空间进行保存!所以对一些广告邮件设置了 forward filter…然后问题来了,当我建立到一定的数量的时候,Gmail 开始提醒我“转发邮件地址过多,无法创建过滤器\n这可怎么办,总该是有解决办法的!于是查阅 Gmail 的帮助手册发信,Gmail 是支持布尔运算符来查找邮件的!而这些布尔运算符一样可以创建到 filter 里.\nOK,问题到这里就变得简单了…\n###解决方案:### 比如说,我要将 renren.com kaixin001.com 高朋 卓越 美团 拉手等邮件过滤直接 forward 到 QQ 邮箱里,那么其实创建一条 filter 就够了!按如下格式renren.com OR kaixin001.com OR 高朋 OR 卓越 OR 美团 OR 拉手,其中“OR”是 Gmail 中所支持的布尔运算符,而且必须大写.\n延伸阅读:### 希望这篇文可以帮助更多的人更好的利用 Gmail.更详尽的运算符可以查阅Gmail 的帮助手册\n","permalink":"https://hivan.me/posts/gmail-forward-upper-limit/","summary":"\u003cp\u003e在 Gmail 中创建转发过滤器是有上限的,这个估计没有多少人知道!我想也是因为没有多少人有实际的需求…\u003c/p\u003e\n\u003cp\u003e今天我在 Twitter 上抱怨 Gmail 里的邮件过多,占用了很大一部分空间,以至于我的 Gmail 空间已经临近上限… 也终于迫使我开始清理邮件!\u003c/p\u003e\n\u003cp\u003e###问题产生:###\n在清理的时候发现,除了一些比较大附件的邮件以外,大部分占用空间的邮件都是一些广告邮件!\u003c/p\u003e\n\u003cp\u003e我很少设置过滤器删除广告邮件,特别是一些推送服务的广告,不过大多都是直接略过收件箱存档而已,有空了还可以去看看…这也使得我这几年来邮箱里布满了此类邮件!\u003c/p\u003e\n\u003cp\u003e当然,清理的过程还是比较顺利的,毕竟这些邮件的时效性的原因,这些邮件删除起来一点也不心疼!\u003c/p\u003e\n\u003cp\u003e那么,对于新邮件该怎么处理呢,总不能继续占用存储空间吧.也不好直接删除,毕竟有的时候我还是要看看的.\u003c/p\u003e\n\u003cp\u003e那么,过滤器这个时候就起作用了!\u003c/p\u003e\n\u003cp\u003e我的想法很简单,既然 QQ 邮箱空间是无上限的,而广告邮件又无关乎隐私问题,所以可以放心的转发到 QQ 空间进行保存!所以对一些广告邮件设置了 forward filter…然后问题来了,当我建立到一定的数量的时候,Gmail 开始提醒我“转发邮件地址过多,无法创建过滤器\u003c/p\u003e\n\u003cp\u003e这可怎么办,总该是有解决办法的!于是查阅 Gmail 的帮助手册发信,Gmail 是支持布尔运算符来查找邮件的!而这些布尔运算符一样可以创建到 filter 里.\u003c/p\u003e\n\u003cp\u003eOK,问题到这里就变得简单了…\u003c/p\u003e\n\u003cp\u003e###解决方案:###\n比如说,我要将 renren.com kaixin001.com 高朋 卓越 美团 拉手等邮件过滤直接 forward 到 QQ 邮箱里,那么其实创建一条 filter 就够了!按如下格式\u003ccode\u003erenren.com OR kaixin001.com OR 高朋 OR 卓越 OR 美团  OR 拉手\u003c/code\u003e,其中“OR”是 Gmail 中所支持的布尔运算符,而且必须大写.\u003c/p\u003e\n\u003ch3 id=\"延伸阅读\"\u003e延伸阅读:###\u003c/h3\u003e\n\u003cp\u003e希望这篇文可以帮助更多的人更好的利用 Gmail.更详尽的运算符可以查阅\u003ca href=\"https://support.google.com/mail/bin/answer.py?hl=en\u0026amp;answer=7190\"\u003eGmail 的帮助手册\u003c/a\u003e\u003c/p\u003e","title":"\"Gmail 的转发上限\""},{"content":"总体来说,这是我最看好的移动应用之一! 注意,是之一.\n首先,我要说的是 SNS 网站真的没有再大的作为了!人们已经开始从那些疯狂中开始慢慢冷却了下来,而真正实际用到 SNS 网站的人也是小部分而已.造势,炒作,宣传,营销\u0026hellip;当然,我不能否认这些平台在作为传统媒体的延伸甚至是替代品上的功效.\n可是又真有多少人会真的坚持活在这种亢奋的状态下.除非有大批的粉丝不停的为自己打鸡血吧?\n所以我总认为,移动社交产品可以打住了.至于查找附近聊友的功能,诸如微信以及添加新功能的米聊以及后来居上的一些产品,不要看宣传的标语很美,实际上还不是沦为宅男腐女查找就近炮友的必备品而已!\n好吧,对 SNS 产品的吐槽结束了,我真正要说的是 NFC 以及 O2O 这两个东西!\n首先我们需要扫盲一下,NFC 就是 Near Field Communication,翻译过来就是近距离无线通信，是一种短距离的高频无线通信技术，允许电子设备之间进行非接触式点对点数据传输，在十厘米（3.9 英吋）内，交换数据。这个技术由免接触式射频识别（RFID）演变而来.而我们平时所用的交通卡等一类射频卡种,都是 RFID 技术.\n\u0026lt;而 O2O(Online to Offline)就不是什么技术了,而是一种互联网商业模式,就是把线上的消费者带到现实中的商店中去在线支付购买线下商品和服务,再到线下去享受服务.\n而实际上,团购,就是一种 O2O 模式.可是我心里的 O2O 模式,完全不是像团购那样的垃圾.这种模式在中国,已经早早的走入了死胡同,一条死路!我所要说的 O2O,是一种集合所有商家优惠折扣的消费资源,并一卡通吃的卡片发行商.\n而也终于让我遇到一个,这就是么卡.有兴趣的可以去看看,我可没有要推销么卡的意思,也无意为他写什么行销软文.只是在这里纯探讨而已!\n好吧,下面让我们实际展望一下,现在很多手机已经开始内置 NFC 技术.比如 iPhone 4S,三星盖世兔等等,那么,你能理解我说什么了,我们以后也就完全可以不必身上大卡小卡的带在身上,一个手机就 OK 了.交通卡,银行卡,还有各大商场卖场的贵宾卡折扣卡,都可以完完全全的装载我们的手机里!\n这其实已经应该不是什么新技术,手机代替交通卡和信用卡,日本早就开始做先行者了!而实际上,O2O 模式在中国也才起头而已,而重点,并不是技术怎样怎样,而是线下的资源掌握的如何.要不团购团队中实际上最大的是营销团队呢.\n而像么卡这样将所有会员卡全部装到手机里的模式,一样取决于,谁掌握了最大的市场!当你的卡通吃了全世界大大小小的商家卖场的时候,相信所有人都愿意只带着一部手机走遍天下的感觉!\n好了,展望结束\u0026hellip;大家想去吧,哈哈!\n","permalink":"https://hivan.me/posts/nfc-and-o2o/","summary":"\u003cp\u003e总体来说,这是我最看好的移动应用之一! 注意,是之一.\u003c/p\u003e\n\u003cp\u003e首先,我要说的是 SNS 网站真的没有再大的作为了!人们已经开始从那些疯狂中开始慢慢冷却了下来,而真正实际用到 SNS 网站的人也是小部分而已.造势,炒作,宣传,营销\u0026hellip;当然,我不能否认这些平台在作为传统媒体的延伸甚至是替代品上的功效.\u003c/p\u003e\n\u003cp\u003e可是又真有多少人会真的坚持活在这种亢奋的状态下.除非有大批的粉丝不停的为自己打鸡血吧?\u003c/p\u003e\n\u003cp\u003e所以我总认为,移动社交产品可以打住了.至于查找附近聊友的功能,诸如微信以及添加新功能的米聊以及后来居上的一些产品,不要看宣传的标语很美,实际上还不是沦为宅男腐女查找就近炮友的必备品而已!\u003c/p\u003e\n\u003cp\u003e好吧,对 SNS 产品的吐槽结束了,我真正要说的是 NFC 以及 O2O 这两个东西!\u003c/p\u003e\n\u003cp\u003e\u003cimg loading=\"lazy\" src=\"http://farm8.staticflickr.com/7146/6573497753_e64864a7a2.jpg\"\u003e\u003c/p\u003e\n\u003cp\u003e首先我们需要扫盲一下,NFC 就是 Near Field Communication,翻译过来就是近距离无线通信，是一种短距离的高频无线通信技术，允许电子设备之间进行非接触式点对点数据传输，在十厘米（3.9 英吋）内，交换数据。这个技术由免接触式射频识别（RFID）演变而来.而我们平时所用的交通卡等一类射频卡种,都是 RFID 技术.\u003c/p\u003e\n\u003cp\u003e\u0026lt;而 O2O(Online to Offline)就不是什么技术了,而是一种互联网商业模式,就是把线上的消费者带到现实中的商店中去在线支付购买线下商品和服务,再到线下去享受服务.\u003c/p\u003e\n\u003cp\u003e而实际上,团购,就是一种 O2O 模式.可是我心里的 O2O 模式,完全不是像团购那样的垃圾.这种模式在中国,已经早早的走入了死胡同,一条死路!我所要说的 O2O,是一种集合所有商家优惠折扣的消费资源,并一卡通吃的卡片发行商.\u003c/p\u003e\n\u003cp\u003e而也终于让我遇到一个,这就是么卡.有兴趣的可以去看看,我可没有要推销么卡的意思,也无意为他写什么行销软文.只是在这里纯探讨而已!\u003c/p\u003e\n\u003cp\u003e好吧,下面让我们实际展望一下,现在很多手机已经开始内置 NFC 技术.比如 iPhone 4S,三星盖世兔等等,那么,你能理解我说什么了,我们以后也就完全可以不必身上大卡小卡的带在身上,一个手机就 OK 了.交通卡,银行卡,还有各大商场卖场的贵宾卡折扣卡,都可以完完全全的装载我们的手机里!\u003c/p\u003e\n\u003cp\u003e这其实已经应该不是什么新技术,手机代替交通卡和信用卡,日本早就开始做先行者了!而实际上,O2O 模式在中国也才起头而已,而重点,并不是技术怎样怎样,而是线下的资源掌握的如何.要不团购团队中实际上最大的是营销团队呢.\u003c/p\u003e\n\u003cp\u003e而像么卡这样将所有会员卡全部装到手机里的模式,一样取决于,谁掌握了最大的市场!当你的卡通吃了全世界大大小小的商家卖场的时候,相信所有人都愿意只带着一部手机走遍天下的感觉!\u003c/p\u003e\n\u003cp\u003e好了,展望结束\u0026hellip;大家想去吧,哈哈!\u003c/p\u003e","title":"NFC 和 O2O"},{"content":"已經忘記今天是第幾天試用 Google+了，這兩天圈內當然免不了的大部分都在討論 Google+，而什麼所謂的 G+應用技巧，什麼 25 條關於 G+的，還有什麼 50 條，100 條類似的！反正不止是 G+內部，包括 twitter，facebook，微博以及博客圈內，大家都在樂此不彼的討論 Google+！\n可是我這裡不想討論一些技術上的東西，也不想討論它是否真的能夠打敗 Facebook 或者 twitter！我只想從真正的社交上去分析一下我自己的對 G+內部細微的探討！\n找不到好圖，又懶得自己做。所以借用一下+lucifr Liu的圖！反正都做了，表浪費！原圖有 PSD 文檔可以下載！我按照自己的標準稍稍修改了一下！\n讓我們先來探討一下 G+中的 circles，雖然所有的社交網絡中都有這樣一種概念，但是沒有 Google 那樣去深挖它，延伸它！在多部分的社交網絡中，或許都是在模仿 Facebook，或許就是在模仿 Twitter！而這兩者的基本機制就是相互添加對方為好友，才算開始真正的社交！當然，Twitter 的機制要鬆散一點，即便雙方沒有相互 fo 對方或者只有一方 fo 了，一樣可以互相聊天！而 Facebook 對於社交圈子的概念就相對封閉了一點，如果只有一方 fo 了對方，那麼一樣不構成相互溝通的條件！\n而比較尷尬的是，Facebook 作為一個最大的 SNS 網站，基本上老老少少都在上邊進行基礎社交，這也構成了現在年輕人逐步放棄 Facebook 的主因！因為這個如果都相互 fo 了對方，那麼這個 circles 的範圍就太大了。只要我發佈了一條消息，我的老師，我的父母，我的同學以及我的基友，大家都可以看到這條消息！而在真實的社交範圍中，這是不可想像的！比如，包二奶這裡就是個十分不適合的場所！當然，我們可以私信！！！！！！好吧，我舉的例子有些屎，我承認！那麼讓我們想像一下，總有些事情是不希望父母或者另外的人知道的，而只想在小範圍內的圈子內傳播的！那麼，Google+給了你這個實現的方式，就是 circles 概念的區分和交集！這個還需要你對聯繫人進行具體的整理才可以！有些人，即是朋友又是同事，這就是兩個 circles 的交集，那麼我一條信息不管發佈在我的朋友圈還是同事圈，這個人都可以收到我的信息！加入說，同事圈裏又有些話題需要討論而不想讓老闆看到該如何，讓我們建立一個將老闆排除在外的同事圈！\n大概瞭解了 Circles 的概念以後，我想討論的就是 Google+的一些細節的地方！我們都知道，無論是微博還是 SNS 社交網站比如開心，人人網都會有 Share 分享功能！這個功能的作用在於，看到好的東西以後，可以直接分享給自己的社交圈裏的所有人觀看！這也是網絡病毒營銷的根源！而在 Google+中，我們注意到一個細節，並且進行了一番討論。Share 這個功能被分割了。我們以大家熟悉的微博為例，當我 Share 某話題以後，如果原帖被修改或者刪除，那麼我 Share 過去的內容也會跟著變動！而且原帖會被注明轉帖和評論了多少次！在 G+中就完全不是這樣，當我 Share 某個帖子後，這個帖子就變成我複製過去的了！只會聲明原帖出自什麼人。並且評論不會跟著原帖一起變動，而我在新 Share 的帖子下邊回覆，也不被記錄在原帖的評論裏！這就是我們所討論的被分割！可是我們思考一下真實社交圈的情況，我們從某個人那裡聽到了一個消息，然後四處散播！當那個人再對同樣的圈子內修正了這個消息或者否定了這個消息以後，我所散播出去的圈子并不能收到這樣一個反饋，那麼，只有我自己去再去修正一下自己所說過的話！那麼也就是，每個人都必須對自己所說的話和所散播的言論負責！而當你所散播了消息以後，即便和你之前聽到的是一模一樣的，那麼這個討論也只限制在你自己的圈子內而和原來的圈子無關！這個說明，Google+在細節上真的是在充分考慮真實社交的環境而在努力模擬再現！\n大家也都發現，G+可以被當作郵箱來用！當你發表一篇消息給某個特定的人或者一個圈子的人。那麼對方會收到一個消息。這個消息最終如何接受有三種情況！第一種情況，對方沒有 G+帳號，那麼這條消息肯定是直接發佈到對方郵箱裏！第二種情況，對方有 G+帳號而你所寫接收方是聯繫人或者圈子，並且對方有關閉郵件提醒，那麼對方會在 G+上接收到這條信息。第三種情況就是對方有 G+帳號並且沒有關閉郵件提醒，那麼就是 G+和郵箱都會收到這條消息！\n不過，對於給沒有 G+帳戶的人用 G+發佈邀請還可以，如果要當作邮箱来用，還是奉勸大家不要這樣做！因為 G+發佈郵件的郵箱並不是你的郵箱，是隨機生成的一個郵箱地址，並不是固定的！也就是說對方即便收到也無法回覆！而我做了一個實驗，給我的 163，QQ，以及 yahoo 和 gmail 郵箱都各發了一個郵件！有的郵件無影無蹤了，而即便收到的郵件，也是等了很久以後才收到的！所以，如果要發郵件的話，請點擊黑又長上邊+You後邊的 Gmail 選項，老老實實的用 Gmail 發郵件吧！\nPS：此處之後還會不斷更新，分享我對 Google+的一些想法！當然，是有了感悟後才會來更新！以後關於 Google+的研究如無特殊情況也都在本帖內討論！想到哪寫到哪，不分那麼多條條框框了！\n其實，關於 Google+，還有很多值得研究的細節，Google 也在不斷完善！可以看得出這真的是一個很有誠意的互聯網產品！相應號召，大家都搬家吧！\n","permalink":"https://hivan.me/posts/google-research/","summary":"\u003cp\u003e已經忘記今天是第幾天試用 Google+了，這兩天圈內當然免不了的大部分都在討論 Google+，而什麼所謂的 G+應用技巧，什麼 25 條關於 G+的，還有什麼 50 條，100 條類似的！反正不止是 G+內部，包括 twitter，facebook，微博以及博客圈內，大家都在樂此不彼的討論 Google+！\u003c/p\u003e\n\u003cp\u003e可是我這裡不想討論一些技術上的東西，也不想討論它是否真的能夠打敗 Facebook 或者 twitter！我只想從真正的社交上去分析一下我自己的對 G+內部細微的探討！\u003c/p\u003e\n\u003cp\u003e找不到好圖，又懶得自己做。所以借用一下+\u003ca href=\"http://gplus.to/lucifr\"\u003elucifr Liu\u003c/a\u003e的圖！反正都做了，表浪費！原圖有 \u003ca href=\"http://lucifr.com/97591/moving-to-googleplus-poster/\"\u003ePSD 文檔可以下載\u003c/a\u003e！我按照自己的標準稍稍修改了一下！\u003c/p\u003e\n\u003cp\u003e\u003cimg alt=\"GooglePlus\" loading=\"lazy\" src=\"https://farm6.static.flickr.com/5234/5907945903_b9fcc8b5b4_z.jpg\"\u003e\u003c/p\u003e","title":"\"Google+ 研究\""},{"content":"原帖出自師北寰的網絡日誌，當中很多條目內容也都是來自於 Twitter！由於原帖地處偏僻，需要翻山越嶺，所以為了照顧國內用戶，特轉此貼！這也是我為數不多的轉帖之一。當然，我知道，這個服務可能過段時間完全開放後又會和國內無緣！就像 G+才出世兩天就被牆了一樣！不過這也充分證明了 G+的優秀！而現階段，最好玩的網絡應用就是 G+和 ifttt。所以一般都是if oo then xx!\n什么是 ifttt？\nifttt 是一坨网站：ifttt.com\n即 If This Then That，你可以在 ifttt 上设定一个条件，当达到你设定的一个条件时，便触发一个（你指定）动作。这里的「条件」和「动作」是指开放的互联网服务，比 如 flickr，twitter，facebook，youtube 等。别问我有没有新浪微博、人人网，优秀且可靠的互联网服务都在国外。\n国内没法抄 ifttt。ifttt 最重要是服务稳定，就国内普遍鸡贼的情况来看，if 端服务时不时封闭一下是常事。创业企业抄好了，大公司眼红把自己网站接口关闭再抄一个出来，创业公司就得玩完。大公司抄的话，竞争对手们也会关闭自己的服务，这玩意儿在国内没法玩。\nifttt 的稳定性是关键，可以定制一连串的 if…then 任务出来，但如果中间某一个服务出问题，后面的任务就全失效了（当然，这么定制也挺笨的）。\nifttt 非常重要的一个优点是，将常用服务（twitter，加星或分享的 Google reader 条目，加过标签的 instagram 和 flickr 照片）中的重要资料，全部发送到一个存储服务（Dropbox，evernote，Gmail），需要用的时候检索起来将非常方便。\nifttt 可以怎么玩？\n好玩的可以有：if 某女谈论「失恋」、「男友+讨厌」、「伤心」、「难过」，then 发送一条短信。ifttt 泡妞必备…\n非常实用的应用可以有：New fav tweets to evernote\nifttt 还解决了我以前在北京十分急需的一个功能：if 北京美国大使馆空气监测站的空气质量指数超过 250，then 发送一条短信…当然，其它方法也能实现，但 ifttt 方便太多了。\n未来的应用有：ifttt 的出现真可以实现未来你挂了也能一个人办丧事：if 三十天未发推，then 启动一系列任务：1.发邮件告诉殡仪馆来收尸（亲，你可以看我的 google location）；2.自动转账；3.发表遗书告诉亲友可于 30 天后到某处悼念；4.分享生前录好的视频，最后再操一遍 GFW。\n在未来，随着越来越多社交服务的出现，以及多条件任务功能的推出，玩 ifttt 的花样将呈爆发式增长，乐趣无穷。\nifttt 还可以有什么玩法？\n@mranti: ifttt 应用举例：if 某男 A 和某女 B 同时 check in 同一个地方，then 短信我的手机：“A 和 B 有奸情，而且正在进行”。八卦利器啊！\n@hecaitou: 理想状态下的 ifttt 应用场景：一旦老婆的推上出现“加班”字样，立即激活一条手机短信通知。同时，自动检测谷歌日历，找出几个今晚没有事情的老友。随后，在 FB 上新建一个活动“今晚喝大酒”，一旦超过 3 人同意，触发一条订餐消息给餐厅。餐厅查询 Evernote，找到这群人最喜欢的菜和酒。on Twitter: http://twitter.com/hecaitou/status/85927850749857792\n@mranti: ifttt 应用举例：if 明天下雨，发推 DM 给自己的心仪女友：“亲爱的，明天出门带伞，我是你的阳光”。 on Twitter: http://twitter.com/mranti/status/85927810924937219\n最后这两条和菜头和安替的推是我在 twitter fav 之后，在写这篇文章过程中自动保存到 evernote 的，服务十分流畅，文章写起来太方便了有没有？\n—-以下内容为下午四点十五分更新—-\n出去溜达了一圈回来，脑子里一直在想 ifttt，ifttt 简直是个太科幻的产品了，第一次觉得人工智能——不对，是机器智能——离自己这么近。ifttt 比 Google、Facebook 都要伟大得太多。\nifttt 就是一个反射，它把你想象得到的任何一个动作反射为另外一个你能想象得到的动作，并且它不像生物体一样会被躯干束缚。ifttt 上将出现拉马克进化？\n@hecaitou: ifttt 里面，如果在 Channel 之上，提供一个 Task 的自由市场。让各种 Geek 做出各种奇奇怪怪的 Task 来，用户添加 Task 而不是点选 Channel，那就连盈利的问题都解决了。 on Twitter: http://twitter.com/wuyagege/status/85959272638324736\n@mranti: 在 ifttt 的世界里面，各位姑娘小心了，什么恋爱短信啊、花啊、DM 关怀啊、贴心礼品啊，都可能是程序的 Task 算出来的。而且 ifttt 的世界中，一个人死了，他对一个女生的关心也可以一直持续下去，仿佛天天都在。 on Twitter: http://twitter.com/mranti/status/85974845216665600\n@boatman: ifttt 神就神在即使被墙，只要设置好 this 和 that 的关联性，墙并无法阻止 this 触发 that，除非 GFW 把所有的 channel 全部封锁才有可能抑制 ifttt，但当 ifttt 支持自定义 channel 时，就是神也难救方滨兴。 on Twitter: http://twitter.com/Ryan_XxOo/status/85975132866220032\n@Doriscafe: 我死后，请你替我照顾她。每天给她发短信叫早，订花，在推特上 mention 她，赞她，天气好提醒加衣，天气不好提醒带伞，请你替我照顾她，只要服务器不倒下，就直到永远。#ifttt on Twitter: http://twitter.com/Doriscafe/status/85975909429018624\n@juicy_luna: 我个人觉得吧，#ifttt 就是把生物里的神经反射运用进了网路里，甚至还会扩展到物质生活。也就是说，它担负起神经链的作用，将能把一切行动串联起来，形成纵横的网络。。奇妙的世界。。 on Twitter: http://twitter.com/juicy_luna/status/85976819626549248\n@duck_1984: 超级多米诺啊 蝴蝶效应啊 ifttt 毁灭世界啊 自寻死路啊愚蠢的人类\n还有更多⋯⋯你来补充。\n","permalink":"https://hivan.me/posts/what-is-ifttt-and-how-to-play/","summary":"\u003cp\u003e原帖出自\u003ca href=\"http://www.shibeichen.com/post/7035167640/what-is-ifttt-if-this-then-that\"\u003e師北寰的網絡日誌\u003c/a\u003e，當中很多條目內容也都是來自於 Twitter！由於原帖地處偏僻，需要翻山越嶺，所以為了照顧國內用戶，特轉此貼！這也是我為數不多的轉帖之一。當然，我知道，這個服務可能過段時間完全開放後又會和國內無緣！就像 G+才出世兩天就被牆了一樣！不過這也充分證明了 G+的優秀！而現階段，最好玩的網絡應用就是 G+和 ifttt。所以一般都是\u003cstrong\u003eif oo then xx\u003c/strong\u003e!\u003c/p\u003e","title":"\"什么是 ifttt,ifttt 怎么玩?\""},{"content":"来自小熊在线\nGoogle 新一轮的发布热潮在黄金周涌现。这就是 Google 刚推出的代码搜索，即 Google Code Search。根据 Garett Rogers 的介绍，Google 代码搜索结果来自 Google 的索引。也就是说理论上 Google 能找到的代码，你都可以利用 Google 代码搜索找到。\n你可以利用 Google 代码搜索来搜索各种函数的定义以及相关的示例代码，还可以直接使用正则表达式搜索以获得更精确的结果。另外，你还可以限定搜索某种语言、许可或文件名。\n对于程序员而言，这个代码搜索工具应用挺实用的。Google Code Search 允许编程人员搜索代码用法范例，以更好地理解代码功能。该服务中索引了数十亿行代码，来源是 Web 上保存的文档以及 SourceForge、Google Code 等开源软件项目库。\nGoogle 还提供了一个 API 允许第三方开发人员将代码搜索框整合到他们的开发工具中。\n点击进入Google Code Search\n","permalink":"https://hivan.me/posts/google-code-search/","summary":"\u003cp\u003e\u003cp\u003e来自\u003ca href=\"http://www.beareyes.com.cn/2/lib/200610/08/20061008177.htm\"\u003e小熊在线\u003c/a\u003e\u003c/p\u003e  \u003cp\u003eGoogle 新一轮的发布热潮在黄金周涌现。这就是 Google 刚推出的代码搜索，即 Google Code Search。根据 Garett Rogers 的介绍，Google 代码搜索结果来自 Google 的索引。也就是说理论上 Google 能找到的代码，你都可以利用 Google 代码搜索找到。\u003c/p\u003e  \u003cp\u003e你可以利用 Google 代码搜索来搜索各种函数的定义以及相关的示例代码，还可以直接使用正则表达式搜索以获得更精确的结果。另外，你还可以限定搜索某种语言、许可或文件名。\u003c/p\u003e  \u003cp\u003e对于程序员而言，这个代码搜索工具应用挺实用的。Google Code Search 允许编程人员搜索代码用法范例，以更好地理解代码功能。该服务中索引了数十亿行代码，来源是 Web 上保存的文档以及 SourceForge、Google Code 等开源软件项目库。\u003c/p\u003e  \u003cp\u003eGoogle 还提供了一个 API 允许第三方开发人员将代码搜索框整合到他们的开发工具中。\u003c/p\u003e  \u003cp\u003e点击进入\u003ca href=\"http://www.google.com/codesearch\"\u003eGoogle Code Search\u003c/a\u003e\u003c/p\u003e\u003c/p\u003e","title":"\"Google 正式推出代码搜索 Code Search\""},{"content":"这一下就经过了很多日子，本来前些天是想写一些教程的。\n现在脑子里的想法大多都被时间冲淡了。只是随便写写吧。\n由于我的机子重新装了系统的原因。迅雷里的原始连接我也找不到了，有兴趣的朋友可以自己去找找看\npicasa2 和 picasaweb 有很多的不同，最大的不一样大概就是现在没有推出中文版本。一切都是 E 文。自信能看得懂的朋友可以下载来试试。\npicasaweb 是和自己的 google 账号绑定的。google 的 picasaweb 服务在网上有 250M 的存储空间。可以利用 picasaweb 直接上传到自己的账号上。我去试验了一下，服务很不错，而且有很多新的功能，可惜的是不支持外连，估计快可以了，因为每一张图片都有自己的地址。\nPicasaweb 和原版本还有很多新加的功能。其中我比较感兴趣的就是色相搜索。输入color:xxx(比如 red)就会有相应色相的相片被搜索出来。效果大家可以自己下载以后自己看看@。在我使用过程中，虽然 E 文给我找了不少麻烦，但是总体上感觉这次的升级还是有很多实用的功能在里边。\n好了，软件谈完了，说说最近看的电影。。我这个人不喜欢在网上下载，质量太侮辱我眼睛。所以我一般都是等到碟版出来以后买回来看。。。。等到现在才看了达芬奇密码和碟三，但是我并不是要向大家说这两部片子，而是我看的另外一部:**《东京审判》**这是一部设计历史题材和政治题材的片子。1945 年日本签署投降书的以后，联盟国法官团赶赴东京对日本的 20 多位战犯进行审判。长达 817 次审判和最后一次宣判共 818 次的审判历程。\n印象最深的是曾志伟饰演的日本人。从他嘴里说出来的:狗日的日本鬼子。\n不知道这句话是因为深受其害还是因为别的原因。再怎么样，一个日本人不会如此辱骂自己的国家吧。就象中国人自己，如果说了一句中国猪，反映会是怎样的？\nso。。。最后还是不得不感叹战争带来的危害。所有的一切，也都仅仅是战争引起来的而已\n不过再如何，日本到现在为止的历史观确实让我恶心。我到现在才知道靖国神社原来一共供奉着 2000 多位战犯，而其中有 7 位是甲级战犯，而最大的也是最可恶的战端挑起者东条英机，也被供奉在里边。这，是对人类文明的一种侮辱。\nOK。。。写到这里了。。该去看书了\n","permalink":"https://hivan.me/posts/picasaweb/","summary":"\u003cp\u003e这一下就经过了很多日子，本来前些天是想写一些教程的。\u003c/p\u003e\n\u003cp\u003e现在脑子里的想法大多都被时间冲淡了。只是随便写写吧。\u003c/p\u003e\n\u003cp\u003e由于我的机子重新装了系统的原因。迅雷里的原始连接我也找不到了，有兴趣的朋友可以自己去找找看\u003c/p\u003e\n\u003cp\u003epicasa2 和 picasaweb 有很多的不同，最大的不一样大概就是现在没有推出中文版本。一切都是 E 文。自信能看得懂的朋友可以下载来试试。\u003c/p\u003e\n\u003cp\u003epicasaweb 是和自己的 google 账号绑定的。google 的 picasaweb 服务在网上有 250M 的存储空间。可以利用 picasaweb 直接上传到自己的账号上。我去试验了一下，服务很不错，而且有很多新的功能，可惜的是不支持外连，估计快可以了，因为每一张图片都有自己的地址。\u003c/p\u003e\n\u003cp\u003ePicasaweb 和原版本还有很多新加的功能。其中我比较感兴趣的就是色相搜索。输入\u003ccode\u003ecolor:xxx\u003c/code\u003e(比如 red)就会有相应色相的相片被搜索出来。效果大家可以自己下载以后自己看看@。在我使用过程中，虽然 E 文给我找了不少麻烦，但是总体上感觉这次的升级还是有很多实用的功能在里边。\u003c/p\u003e\n\u003cp\u003e好了，软件谈完了，说说最近看的电影。。我这个人不喜欢在网上下载，质量太侮辱我眼睛。所以我一般都是等到碟版出来以后买回来看。。。。等到现在才看了达芬奇密码和碟三，但是我并不是要向大家说这两部片子，而是我看的另外一部:**《东京审判》**这是一部设计历史题材和政治题材的片子。1945 年日本签署投降书的以后，联盟国法官团赶赴东京对日本的 20 多位战犯进行审判。长达 817 次审判和最后一次宣判共 818 次的审判历程。\u003c/p\u003e\n\u003cp\u003e印象最深的是曾志伟饰演的日本人。从他嘴里说出来的:狗日的日本鬼子。\u003c/p\u003e\n\u003cp\u003e不知道这句话是因为深受其害还是因为别的原因。再怎么样，一个日本人不会如此辱骂自己的国家吧。就象中国人自己，如果说了一句中国猪，反映会是怎样的？\u003c/p\u003e\n\u003cp\u003eso。。。最后还是不得不感叹战争带来的危害。所有的一切，也都仅仅是战争引起来的而已\u003c/p\u003e\n\u003cp\u003e不过再如何，日本到现在为止的历史观确实让我恶心。我到现在才知道靖国神社原来一共供奉着 2000 多位战犯，而其中有 7 位是甲级战犯，而最大的也是最可恶的战端挑起者东条英机，也被供奉在里边。这，是对人类文明的一种侮辱。\u003c/p\u003e\n\u003cp\u003eOK。。。写到这里了。。该去看书了\u003c/p\u003e","title":"picasaweb"},{"content":"使用 Telnet 数据流创造了正在进行比赛的 ASCII 影像,\n尽管这难以令人想象. 看看他是怎么做的…\n在比赛开始 10 分钟以前, 简单的打开 Windows 开始-运行窗口, 输入 telnet ascii-wm.net 2006 , 你将会看到 “现场直播” 视频流. 明显的, 这是互联网应用的又一创新. 来试一下吧! http://ascii-wm.net/\n由于我家里只有电脑没有电视，所以全程我都是这么干的。。。可惜，经常与主机断开连接。。而且，很多时候我看不懂。。。郁闷。只能知道个大概。。有兴趣的可以去看看＠\n","permalink":"https://hivan.me/posts/use-the-telnet-data-stream-to-watch-the-world-cup/","summary":"\u003cp\u003e使用 Telnet 数据流创造了正在进行比赛的 ASCII 影像,\u003c/p\u003e\n\u003cp\u003e尽管这难以令人想象. 看看他是怎么做的…\u003c/p\u003e\n\u003cp\u003e在比赛开始 10 分钟以前,   简单的打开 Windows 开始-运行窗口, 输入 \u003ccode\u003etelnet ascii-wm.net 2006\u003c/code\u003e , 你将会看到 “现场直播” 视频流.    明显的, 这是互联网应用的又一创新. 来试一下吧!    \u003ca href=\"http://ascii-wm.net/\"\u003ehttp://ascii-wm.net/\u003c/a\u003e\u003c/p\u003e\n\u003cp\u003e由于我家里只有电脑没有电视，所以全程我都是这么干的。。。可惜，经常与主机断开连接。。而且，很多时候我看不懂。。。郁闷。只能知道个大概。。有兴趣的可以去看看＠\u003c/p\u003e","title":"\"使用 Telnet 数据流看世界杯\""}]