0%

上季度,我在学校辅助一门 Python 课程的教学,在此过程中学到了很多图像处理的知识。我希望通过本文分享一些关于边缘检测的知识,包括边缘检测的理论以及如何使用 Python 实现边缘检测。


为何检测边缘?

我们首先应该了解的问题是:“为什么要费尽心思去做边缘检测?”除了它的效果很酷外,为什么边缘检测还是一种实用的技术?为了更好地解答这个问题,请仔细思考并对比下面的风车图片和它的“仅含边缘的图”:

Image of pinwheel (left) and its edges (right)

可以看到,左边的原始图像有着各种各样的色彩、阴影,而右边的“仅含边缘的图”是黑白的。如果有人问,哪一张图片需要更多的存储空间,你肯定会告诉他原始图像会占用更多空间。这就是边缘检测的意义:通过对图片进行边缘检测,丢弃大多数的细节,从而得到“更轻量化”的图片。

因此,在无须保存图像的所有复杂细节,而 “只关心图像的整体形状” 的情况下,边缘检测会非常有用。


如何进行边缘检测 —— 数学

在讨论代码实现前,让我们先快速浏览一下边缘检测背后的数学原理。作为人类,我们非常擅长识别图像中的“边”,那如何让计算机做到同样的事呢?

首先,假设有一张很简单的图片,在白色背景上有一个黑色的正方形:

Our working image

在这个例子中,由于处理的是黑白图片,因此我们可以考虑将图中的每个像素的值都用 0(黑色)1(白色) 来表示。除了黑白图片,同样的理论也完全适用于彩色图像。

现在,我们需要判断上图中绿色高亮的像素是不是这个图像边缘的一部分。作为人类,我们当然可以认出它图像的边缘;但如何让计算机利用相邻的像素来得到同样的结果呢?

我们以绿色高亮的像素为中心,设定一个 3 x 3 像素大小的小框,在图中以红色示意。接着,对这个小方框“应用”一个过滤器(filter):

对局部像素框应用纵向过滤器

上图展示了我们将要“应用”的过滤器。乍一看上去很神秘,让我们仔细研究它做的事情:当我们说 “将过滤器应用于一小块局部像素块” 时,具体是指红色框中的每个像素与过滤器中与之位置对应的像素进行相乘。因此,红色框中左上角像素值为 1,而过滤器中左上角像素值为 -1,它们相乘得到 -1,这也就是结果图中左上角像素显示的值。结果图中的每个像素都是用这种方式得到的。

下一步是对过滤结果中的所有像素值求和,得到 -4。请注意,-4 其实是我们应用这个过滤器可获得的“最小”值(因为原始图片中的像素值只能在 0 到 1 之间)。因此,当获得 -4 这个最小值的时候,我们就能知道,对应的像素点是图像中正方形顶部竖直方向边缘的一部分。

为了更好地掌握这种变换,我们可以看看将此过滤器应用于图中正方形底边上的一个像素会发生什么:

可以看到,我们得到了与前文相似的结果,相加之后得到的结果是 4,这是应用此过滤器能得到的最大值。因此,由于我们得到了 4 这一最大值,可以知道这个像素是图像中正方形底部竖直方向边缘的一部分。

为了把这些值映射到 0-1 的范围内,我们可以简单地给其加上 4 再除以 8,这样就能把 -4 映射成 0(黑色),把 4 映射成 1(白色)。因此,我们将这种过滤器称为纵向 Sobel 过滤器,可以用它轻松检测图像中垂直方向的边缘。

那如何检测水平方向的边缘呢?只需简单地将纵向过滤器进行转置(按照其数值矩阵的对角线进行翻转)就能得到一个新的过滤器,可以用于检测水平方向的边缘。

如果需要同时检测水平方向、垂直方向以及介于两者之间的边缘,我们可以把纵向过滤器得分和横向过滤器得分进行结合,这个步骤在后面的代码中将有所体现。

希望上文已经讲清楚了这些理论!下面看一看代码是如何实现的。


如何进行边缘检测 —— 代码

首先进行一些设置:

1
%matplotlib inline
2
3
import numpy as np
4
import matplotlib.pyplot as plt
5
6
# 定义纵向过滤器
7
vertical_filter = [[-1,-2,-1], [0,0,0], [1,2,1]]
8
9
# 定义横向过滤器
10
horizontal_filter = [[-1,0,1], [-2,0,2], [-1,0,1]]
11
12
# 读取纸风车的示例图片“pinwheel.jpg”
13
img = plt.imread('pinwheel.jpg')
14
15
# 得到图片的维数
16
n,m,d = img.shape
17
18
# 初始化边缘图像
19
edges_img = img.copy()
  • 你可以把代码中的“pinwheel.jpg”替换成其它你想要找出边缘的图片文件!需要确保此文件和代码在同一工作目录中。

接着编写边缘检测代码本身:

1
%matplotlib inline
2
3
import numpy as np
4
import matplotlib.pyplot as plt
5
6
# 定义纵向过滤器
7
vertical_filter = [[-1,-2,-1], [0,0,0], [1,2,1]]
8
9
# 定义横向过滤器
10
horizontal_filter = [[-1,0,1], [-2,0,2], [-1,0,1]]
11
12
# 读取纸风车的示例图片“pinwheel.jpg”
13
img = plt.imread('pinwheel.jpg')
14
15
# 得到图片的维数
16
n,m,d = img.shape
17
18
# 初始化边缘图像
19
edges_img = img.copy()
20
21
# 循环遍历图片的全部像素
22
for row in range(3, n-2):
23
    for col in range(3, m-2):
24
        
25
        # 在当前位置创建一个 3x3 的小方框
26
        local_pixels = img[row-1:row+2, col-1:col+2, 0]
27
        
28
        # 应用纵向过滤器
29
        vertical_transformed_pixels = vertical_filter*local_pixels
30
        # 计算纵向边缘得分
31
        vertical_score = vertical_transformed_pixels.sum()/4
32
        
33
        # 应用横向过滤器
34
        horizontal_transformed_pixels = horizontal_filter*local_pixels
35
        # 计算横向边缘得分
36
        horizontal_score = horizontal_transformed_pixels.sum()/4
37
        
38
        # 将纵向得分与横向得分结合,得到此像素总的边缘得分
39
        edge_score = (vertical_score**2 + horizontal_score**2)**.5
40
        
41
        # 将边缘得分插入边缘图像中
42
        edges_img[row, col] = [edge_score]*3
43
44
# 对边缘图像中的得分值归一化,防止得分超出 0-1 的范围
45
edges_img = edges_img/edges_img.max()

有几点需要注意:

  • 在图片的边界像素上,我们无法创建完整的 3 x 3 小方框,因此在图片的四周会有一个细边框。
  • 既然是同时检测水平方向和垂直方向的边缘,我们可以直接将原始的纵向得分与横向得分分别除以 4(而不像前文描述的分别加 4 再除以 8)。这个改动无伤大雅,反而可以更好地突出图像的边缘。
  • 将纵向得分与横向得分结合起来时,有可能会导致最终的边缘得分超出 0-1 的范围,因此最后还需要重新对最终得分进行标准化。

在更复杂的图片上运行上述代码:

得到边缘检测的结果:


以上就是本文的全部内容了!希望你了解到了一点新知识,并继续关注更多数据科学方面的文章〜

掘金链接:https://juejin.im/post/5e3d4b53e51d4526c26fadd4

入门机器学习应用,尤其是需要对实际数据进行处理时,是很困难的。

一般来说,机器学习教程会推荐你或要求你,在开始拟合模型之前,先以特定的方式准备好数据。

其中,一个很好的例子就是对类别数据(Categorical data)进行 One-Hot 编码(又称独热编码)。

  • 为什么 One-Hot 编码是必要的?
  • 为什么你不能直接使用数据来拟合模型?

在本文中,你将得到上述重要问题的答案,并能更好地理解机器学习应用中的数据准备工作。

Read more »

本文作于 2018 年,被 AAAI 2019 接收。作者是浙大博士,在 Northwestern University 做博后期间做出了此工作。文章中开源了实现代码:https://github.com/yao8839836/text_gcn

概述

本文解决的是自然语言处理中最基础的任务 - 文本分类任务。利用近年大火的图神经网络,作者通过词与文章的共现信息和 TF-IDF 权重和互信息权重将无结构数据文本进行了构图,并利用 Graph Convolutional Network(GCN)捕获图中的文档-词、词-词、文档-文档关系,从而进行文本分类。

具体来说,本文主要有以下两个贡献点:

  1. 提出了使用图神经网络来解决文本分类问题,有效利用了文档、词等的异构信息
  2. 在 benchmark 上达到了 state-of-the-art 的效果

背景与相关工作

文本分类

传统的文本分类方法主要依靠特征工程,在深度学习兴起后,各种深度学习框架代替了这个步骤。人们利用文本的分布式表示(embedding),使用各种 CNN、RNN、LSTM 等神经网络来捕获 embedding 中的语义信息,进行分类。本文就是在此基础之上,用 GCN 来捕获 Graph 中的 语义信息从而实现准确分类。

图网络

近些年为了突破传统神经网络只能应用于对齐的 grid 数据的限制,出现了可以应用于 Graph 的图神经网络。其中,GCN 方法简单有效,在图的各个节点上计算其邻居的聚合信息表示。因此,作者 employ 了 GCN 方法,将其用于图结构的学习。

数据

作者在 5 个常用的公开数据集上进行了实验。这 5 个数据集的基本信息如下:

-w692

在实验前,作者利用 NLTK 去除了前 4 个数据集的停用词,并去除了频次小于 5 的低频次。MR 数据集因为句子太短了,没有必要再删。

方法

构图方法

作者最终构成的图结构如下图所示:

-w922

在图中,左边是文本构成的图,右边是经过 GCN 得到的图表示。在左图中,以“O”开头的节点是文档节点,白色圈里有单词的节点是单词节点,黑色的线是文档-单词关系,灰色的线是单词-单词关系。右图中的$R(x)$表示文档或单词$x$的表示。

具体来说,在这个情景中,构图主要在于如何对文档-单词和单词-单词的边赋权。作者使用了下面公式所示的构图方式:

$A_{ij}$表示从节点 i 连到节点 j 的边的权重。简单来说,就是对文档-单词的边算 TF-IDF 作为权重,对单词-单词的边使用 PMI 做权重。PMI 是单词与单词的互信息,具体计算方式是:

其中,#W 是滑动窗口,具体来说,PMI 就是算单词 i 和单词 j 同时出现的概率比上单词 i 和单词 j 单独出现的概率。

分类算法

在 GCN 框架内,使用 BP 算法来优化节点表示,并在 GCN 后加一层 Dense 层和激活层,利用 softmax 来进行分类。作者将其表示如下:

其中,$ \tilde { A } X W _ { 0 } $ 和前面的公式 $L ^ { ( 1 ) } = \rho \left( \tilde { A } X W _ { 0 } \right)$ 一致,都是通过对 W 的优化来进行节点的表示。对上面的公式进一步拆解,可以记为:

$E_1$ 就是对单词和文档节点的表示。

$E_2$ 就是对节点的第二层级表示。因此,本文相当于用了 2 层 GCN 进行图表示,然后用 softmax 进行分类。在分类优化时,采用了交叉熵损失函数:

实验

baseline 设置

作者设置了多种 baseline,包括:

  • TF-IDF + 线性分类器
  • CNN 文本分类(Convolutional neural networks for sentence classification,EMNLP)
  • LSTM 文本分类(Recurrent neural network for text classification with multi-task learning,IJCAI)
  • Bi-LSTM
  • PV-DBOW(Distributed representations of sentences and documents,ICML)
  • PV-DM(同上)
  • PTE(Automatic lymphoma classification with sentence subgraph mining from pathology reports)
  • FastText(Bag of tricks for efficient text classification,EACL)
  • SWEM(Baseline needs more love: On simple wordembedding-based models and associated pooling mechanisms,ACL)
  • LEAM(Joint embedding of words and labels for text classification,ACL)
  • Graph-CNN-C(Convolutional neural networks on graphs with fast localized spectral filtering,NIPS)
  • Graph-CNN-S(Spectral networks and locally connected networks on graphs,ICLR)
  • Graph-CNN-F(Deep convolutional networks on graphstructured data)

可以看到,作者的实验非常完善且置信,应用了当时几乎全部的文本分类方法来进行对比。

实验设置

作者用了 200 维作为 embedding 维数,20 作为滑动窗口大小,学习率设为 0.02,Dorpout 设为 0.5,分别随机采样 10 % 数据作为验证集和测试集。

实验结果

最终,得到了如下表所示的实验结果:

-w1067

该表有两个维度,数据集和模型。从此也可以看出,作者实验做的非常充分。

结果分析

从上表可以看到,除了 MR 数据集外,作者提出的 Text GCN 方法在其余全部数据集上都得到了最好的结果。猜测可能是由于 MR 数据集中数据过于短,构图效果不佳造成的。

此外,作者利用 t-SNE 方法(Visualizing data using t-sne,JMLR)对结果进行了可视化,用于分析训练得到的 embedding 的效果。结果如下:

-w513

可以看到,作者提出的 Text GCN 方法得到的文档表示在 t-SNE 表现是可分的,类间距离较大,优于用来对比的其余两种方法。

总结

作者提出的 Text GCN 方法在文本分类任务中,在多个数据集上得到了最好的结果。我认为其最大创新点在于:1、引入了 GCN 来做文本分类 2、提出了这种构建带权边图的方式。整个工作非常完备,应该要做的实验基本都做了,令人信服,我们做文本分类应当也要学习本文的实验方式。此外,文章最后的节点表示可视化也很有说服力。

对于后续工作,我觉得一个是可以 follow 一些新的构图方式和 GNN 框架,再有就是在 loss 方面进行改进,优化表示的空间分布。此外,可以考虑结合一些最新的语言模型方法(BERT、XLNET 等)改善结果。以及,可以对分类器那块进行一些改进,比如引入 Attention 等方法可能可以提升效果。

本系列翻译自 Rodion Chachura 发布于 Medium 的系列文章 Linear Algebra with Javascript,旨在帮助复习线性代数的基本概念与运算,并了解如何使用 React、SVG、ThreeJS 等技术栈对线性代数的二维、三维向量、矩阵、线性变换进行可视化。

本系列共包含 5 篇文章:

  1. 用 React 制作线性代数教程示例:网格与箭头
  2. JavaScript 线性代数:向量
  3. JavaScript 线性代数:线性变换与矩阵
  4. JavaScript 线性代数:使用 ThreeJS 制作线性变换动画
  5. 线性代数:矩阵基本运算