Python/PyCharm 使用了 pytest 来执行代码,应该如何修改?
数据竞赛/Two Sigma Connect Rental Listing Inquiries

数据竞赛/Two Sigma Connect Rental Listing Inquiries

赛题描述

本文介绍的是 Kaggle 比赛:Two Sigma Connect: Rental Listing Inquiries。赛题要求是根据 RentHop 租房网站上的房屋的信息,预测受欢迎的程度,受欢迎的程度有 3 种:'high'、'medium'、 'low',因此可以看作是 3 分类的问题。

数据主要包括房屋的房间数量、洗手间数量、创建时间、价格、位置、房屋的照片、经纬度、街道、中介,以及一段文本介绍。

这道赛题数据量不大,但同时包含了结构化数据、文本数据和图像数据,因此非常适合用于入门学习。

本文主要针对这些数据,展开数据探索的流程。

阅读更多
NLP/台大 Bert、ELMO、GPT 学习笔记

NLP/台大 Bert、ELMO、GPT 学习笔记

这篇文章用于记录台大李宏毅老师的 ELMO,BERT,GPT 课程 的学习笔记。课程地址:https://www.bilibili.com/video/av64570585/

这篇文章用一句话概括,就是:如何让机器看懂人类的文字

为了让机器更好地理解人类的文字,学术界做了许多的研究,截至目前为止,在这个方向上最新的进展:ELMO、BERT、GPT。上一篇文章讲了 Transformer,而 BERT 是 Transformer 里的 Encoder,GPT 是 Transformer 里的 Decoder。

EMLO

上一篇文章讲了 Word Embedding,也就是每个词都使用一个向量来表示,向量中包含了词的语义信息。

通常一个词只有一个 Embedding 向量已经足够了,但是实际中,同一个词出现在不同的句子,可能有不同的含义。

例如:下面 4 个句子中,前 2 个句子的bank表示银行,后 2 个句子的bank表示河边。

1
2
3
4
Have you paid that money to the bank yet ?
It is safest to deposit your money in the bank.
The victim was found lying dead on the river bank.
They stood on the river bank to fish.

在之前的 Word Embedding 中,所有的bank的 Embedding 都是一样的。但是在上面 4 个句子中,前 2 个句子的bank和后 2 个句子的bank的 Embedding 应该是不一样的。我们希望,模型对于同一个词,能够根据不同的上下文,学习出不同的 Embedding。这种方法叫做 Contextualized Word Embedding 。

为了达到这个目的,一种叫 ELMO (Embedding from Language Model)的方法被提出来了。这是一种基于 RNN 的。

具体是使用两个 RNN:

  • 左边的 RNN 根据前一个词,输出后一个词,学习到了前文的信息
  • 右边的 RNN 根据后一个词,输出前一个词,学习到了后文的信息。

两个 RNN 训练完成后,取中间的 Hidden state,并拼接起来,得到最终的 Embedding,这个 Embedding 包含了上下文的信息。


上面的图中只有一层 RNN,在实际中,可以设置多层的 RNN ,这样就会得到多个层对应的 Embedding。那这时应该选择哪些层的 Embedding 呢?ELMO 的做法是这些 Embedding 全都要,将每一层的 Embedding 加权求和,得到最终的 Embedding。这里的权值,是和具体的 NLP 任务一起训练得到的,比如 Coreference(找出指代词对应的词)、SQuAD(问答系统)、SST-5(文本情感分类)。


BERT

Bert 就是 Transformer 的 Encoder 部分,全称是 Bidirectional Encoder Representations from Transformers。

在 transformer 中,Encoder 的输入是一句话的序列,输出是每个词的 Embedding 向量。

所以,BERT 的作用也是训练得到每个词的 Embedding。


训练 BERT 的方法包括两个:Masked LM 和 Next Sentence Prediction。

在实际中,这两个训练方法是同时应用到 BERT 中的。

Masked LM

  • 输入给 BERT 的句子中,会随机选择 15% 的字或者单词置换为一个特殊的 token,称为 MASK。
  • 然后把整句话输入到 BERT,得到每个词的 Embedding。
  • 把 MASK 位置的 Embedding,输入一个线性的分类器,根据 Embedding 预测这个位置的词是哪一个。


因为最后的分类器是一个简单的线性分类器,拟合能力弱,而它的输入是从 BERT 中学习到的 Embedding,这就要求从 BERT 中学习到的 Embedding 是一种非常好的 representation。

Next Sentence Prediction

第二种训练方法是:把两个句子拼接起来,中间添加一个 SEP 的分隔符,并在最前面添加一个 CLS 的 token,经过 BERT 得到每个位置的 Embedding,把 CLS 位置输出的 Embedding 输入一个线性 2 分类器,判断这两个句子是不是属于前后关系。

你可能有 一个疑问,为什么 CLS 的位置要放在句子的最开始,而不是最后面。这是因为 BERT 内部的 Self-Attention 可以连接整个上下文的信息。因此,CLS 放在句子的前面还是后面,是没有影响的。


在实际中,Bert 是可以和具体的任务,一起训练的。

下面是 4 个应用例子。

BERT 的应用例子

文章分类

BERT 可以用来做情感分类,文档分类等任务。

具体是在文档最前面添加一个 CLS 的 token,然后整个文档经过 BERT 得到 Embedding,把 CLS 位置的 Embedding 输入到一个线性分类器做分类。其中 BERT 的网络可以使用别人已经训练好的参数做 fine-tune。


Slot filling(每个词做分类)

在 Slot filling 中,每个词都有一个类别。只需要把句子经过 BERT 得到的每一个 Embedding 都输入到线性分类器中,对每一个词做分类。


Natural Language Inference

在 Natural Language Inference 任务中,输入是两个句子,前面一句是前提,后面一句是推论,判断根据前一个句子是否能够推导出后一个句子。这是一个 3 分类问题,输出有: True/False/ unknown。

和第一个问题类似,也是在句子前面添加一个 CLS 的 token,然后把 CLS 位置的 Embedding 输入一个线性分类器。


Extraction-based Question Answering

这是问答系统的任务,输入是一篇文章 \(D=\{ d_{1}, d_{2}, ...,d_{N}\}\),和一个问题 \(Q=\{ q_{1}, q_{2}, ...,q_{M}\}\),输出是答案在文章 \(D\) 中的位置 \((s, e)\)\(s\) 表示答案的开始位置,\(e\) 表示答案的结束位置。那么答案 \(A = =\{ d_{s}, ...,d_{e}\}\)

如下图所示,问题和答案的序列拼接起来,输入到 BERT,得到每个词的 Embedding。然后训练两个向量,与文档 \(D\) 里面的每一个词计算相似度,再经过 Softmax 层输出概率。

在下图左边,一个向量用于匹配答案的开始位置 \(s\)

在下图右边,另一个向量用于匹配答案的结束位置 \(e\)

那么最终的答案就是 \(A = =\{ d_{s}, ...,d_{e}\}\)


如果 \(s > e\),表示开始的位置比结束的位置还要大,意味着对应的问题没有答案。

在原始论文中,BERT 有 24 层网络,也就是会有 24 个向量输出,最终的向量是 24 个向量的加权求和,权重是和具体的任务一起训练得到的。有论文研究了不同任务对 BERT 中每一层 Embedding 的权重的分布图。

论文地址:https://arxiv.org/abs/1905.05950

ERNIE

你可以把 ERNIE 和 BERT 类似,但是 ERNIE 是为中文设计的,全称是 Enhanced Representation through Knowledge Integration。

它和 BERT 的区别是:

  • 在 BERT 的训练中,Masked LM 会随机选择 15% 的替换成特殊的 MASK。
  • 而在 ERNIE 的训练中,Masked LM 会随机选择 15% 的替换成特殊的 MASK。

论文地址:https://arxiv.org/abs/1904.09223


GPT

GPT 是 Generative Pre-Training,是 Transformer 的 Decoder 部分。也就是根据前一个词生成下一个词。


目前, GPT 已经发展到第 3代,称为 GPT 3,是目前最大的模型,使用的数据集在处理前容量达到了 45TB,具有 1750 亿参数,被赋予“最强 NLP 模型”的称号。GPT-3 可以做到非常多的事,它可以写诗、写剧本、写小说、甚至输出一些代码。GPT-3 的能力前所未有地强大。目前 GPT-3 已经开放了 API,你可以尝试申请体验一下。


如果你觉得这篇文章对你有帮助,不妨点个赞,让我有更多动力写出好文章。

我的文章会首发在公众号上,欢迎扫码关注我的公众号张贤同学


Python/python2 安装 opencv 出错

Python/python2 安装 opencv 出错

我使用 Anaconda 创建了一个 python2.7 的环境,然后安装 opencv: pip install opencv-python

出现 TypeError: 'NoneType' object is not iterable 错误。

完整错误信息如下:

阅读更多
NLP/台大 Transformer 学习笔记

NLP/台大 Transformer 学习笔记

这篇文章用于记录台大李宏毅老师的Transformer 课程的学习笔记。课程地址:https://www.bilibili.com/video/BV1J441137V6

这篇文章用一句话概括,就是:如何让机器看懂人类的文字

为了让机器更好地理解人类的文字,学术界做了许多的研究,截至目前为止,在这个方向上最新的进展就是 BERT、GPT dent,而 BERT 是 Transformer 里的 Encoder,GPT 是 Transformer 里的 Decoder。所以,这篇文章先谈谈 Transformer,下一篇文章讲 Bert 以及 GPT。

下面从历史发展来看词的向量表示的发展历程。

One-Hot Encoding

在最开始的时候,首先被提出来表示人类文字的方法叫做One-Hot Encoding,也称为1-of-N Encoding

假设总共有 5 个词:applebagcatdogelephant,那么就使用长度为 5 的向量来表示一个词,向量中每个位置表示一个词,每个位置的元素的取值有 2种:1 和 0,表示该位置上的词是否出现了。

如果第一个位置表示apple,那么apple的向量就是[1,0,0,0,0]

同理,所有词的向量表示如下表,每行表示一个词。

apple bag cat dog elephant
apple 1 0 0 0 0
bag 0 1 0 0 0
cat 0 0 1 0 0
dog 0 0 0 1 0
elephant 0 0 0 0 1

即:

1
2
3
4
5
apple   = [1,0,0,0,0]
bag = [0,1,0,0,0]
cat = [0,0,1,0,0]
dog = [0,0,0,1,0]
elephant= [0,0,0,0,1]

这种方法的缺点是:词和词之间没有语义上的关联。

比如:dogcat都是动物,而apple是植物,那么dogcat的词向量的相似度,应该大于dogapple的词向量的相似度,对于向量来说,衡量相似度的方法一般是余弦距离。

但是在One-Hot Encoding的编码方式种,所有向量的余弦距离都是一样的,没有办法衡量两个词之间的相似性。因此,这种方式不能捕捉词的语义信息。

Word Class

为了表示词中语义信息,出现了人为给词分类的方法。


但是这种方法是比较粗糙的,比如在class 1中,虽然 3 个词都是动物,但是dogcat都是哺乳类动物

,而bird是非哺乳类的动物。不能区分更加细微的区别,如果想要一直穷尽这种无尽的分类,是不现实的,也过度依赖于人的经验和知识。

Word Embedding

因此出现了Word Embedding,这种方法是利用神经网络来学习出每个词的向量。那么语义相近的词向量就会靠得更近。而这种了向量是根据每个词的上下文训练出来的,主要有GloVeword2vec

  • word2vecpredictive的模型。又可分为:skip-gramcbow两种方法。cbow是根据上下文的词预测中心词,skip-gram是根据中心词预测周围的词。
  • Glovecount-based的模型。本质上是对共现矩阵进行降维。首先,构建一个词汇的共现矩阵,每一行是一个 word,每一列是 context。共现矩阵就是计算每个 word 在每个 context 出现的频率。由于context 是多种词汇的组合,其维度非常大,我们希望像network embedding一样,在 context 的维度上降维,学习 word 的低维表示。这一过程可以视为共现矩阵的重构问题,即reconstruction loss

两个模型在并行化上有一些不同,即GloVe更容易并行化,所以对于较大的训练数据,GloVe更快。


# Seq2Seq

首先来讲下Seq2Seq的模型,一般是由 RNN 组成的。下图是一个双向的 RNN,其中\((a^{1}, a^{2}, a^{3}, a^{4})\)是输入的序列,\((b^{1}, b^{2}, b^{3}, b^{4})\)是输出的序列。但是 RNN 难以并行计算,因为每一个输出的词,都需要等待前面时间步后面时间步的词的hidden_state计算完成。


为了能够并行化计算,一种使用 CNN 来替代 RNN 的方法被提出来了,如下图所示。


图中,每个三角形表示一个卷积核,而卷积运算是可以并行化的,每个卷积核的计算都不需要等待其他卷积核计算完成。但缺点是:每个卷积核的感受野就是比较小,每个卷积核不能看到所有的上下文的词。一种方法是堆叠多层的 CNN,这样高层的卷积核的感受野更大,可以看到更多的上下文单词。


这种方法虽然解决了上下文的范围问题,但是会增加计算量,因为必须堆叠足够多的卷积层,才能看到所有的上下文单词。

Self-Attention

简单总结一下:Self-Attention可以实现类似 RNN 的功能,在计算一个词的输出时,已经看过了前后上下文的单词。但相比于 RNN,Self-Attention可以并行计算。

1. 计算 query、key、value

下图是 Attention 的第一步。


其中\([x^{1}, x^{2}, x^{3}, x^{4}]\)是输入的序列,经过\(a^{i}=W x^{i}\),得到\([a^{1}, a^{2}, a^{3}, a^{4}]\),对于每一个\(a^{i}\),分别计算 3 个值:

  • query:\(q^{i}=W^{q} a^{i}\),接下来和 key 计算 Attention
  • key:\(k^{i}=W^{k} a^{i}\),接下来和 query 计算 Attention
  • value:接下来用于 Attention 相乘,抽取其中的 information

2. 计算 Attention

然后计算每个 query 和每个 key 两两之间的 Attention \(\alpha_{i,j}\)\(i\) 表示第 \(i\) 个 query,\(j\) 表示第 \(i\) 个 key。每个 Attention 是一个实数,而不是矩阵。

下图以计算第一个 query 的 Attention 为例,将第一个 query 和所有的 key 计算 Attention,得到\(\alpha_{1, j} = [\alpha_{1,1}, \alpha_{1,2}, \alpha_{1,3}, \alpha_{1,4}]\)


Attention 有很多计算方法,论文里使用向量内积来计算:\(\alpha_{1, j}=q^{1} \cdot k^{j} / \sqrt{d}\),其中 d 表示向量的维度。因为向量越长,内积可能越大;因此,内积的结果除以\(\sqrt{d}\),是为了平衡向量的维度对 Attention 的影响。

然后,把 Attention \(\alpha_{1, j}\)经过一个 Softmax 层,得到\(\hat{\alpha}_{1, j} = [\hat{\alpha}_{1,1}, \hat{\alpha}_{1,2}, \hat{\alpha}_{1,3}, \hat{\alpha}_{1,4}]\)

Softmax 层:\(\hat{\alpha}_{1, i}=\exp \left(\alpha_{1, i}\right) / \sum_{j} \exp \left(\alpha_{1, j}\right)\)

计算第一个输出

接下来,使用 Attention 和 value,计算第一个输出 \(b^{1}=\sum_{i} \hat{\alpha}_{1, i} \cdot v^{i} = \hat{\alpha}_{1,1} \cdot v_{1} + \hat{\alpha}_{1,2} \cdot v_{2} + \hat{\alpha}_{1,3} \cdot v_{3} + \hat{\alpha}_{1,4} \cdot v_{4}\)


可以看出,\(b^{1}\)是考虑了整个 sequence 的 value 而计算得到的,因此起到了类似 RNN 的作用,如果不想考虑整个 sequence 的上下文,只需要让某些 Attention 等于 0 即可。

同理,其他的输出 \(b^{2}, b^{3}, b^{4}\) 也可以计算出,并且 \(b^{1}, b^{2}, b^{3}, b^{4}\) 是可以并行计算的。

下面,给出并行计算的过程。

并行计算

1. 计算 Q、K、V

首先,回到计算 query、key、value 的过程。其中 query、key、value 的计算,都可以使用矩阵来并行化计算。矩阵中每一列表示一个位置的值。

  • \(I = [a^{1}, a^{2}, a^{3}, a^{4}]\)
  • \(Q = W^{q} \cdot I\)
  • \(K = W^{k} \cdot I\)
  • \(V = W^{v} \cdot I\)


gif 动图如下:


### 2. 计算 Attention \(\hat{A}\)

下一步是计算 Attention ,这一步的并行化如下图所示。


向量中的每个元素,表示一个 key 和一个 value 之间的 Attention 分数。可以使用矩阵来进一步并行化,然后对矩阵 \(A\) 的每一列做 Softmax。

  • \(A=K^{T} \cdot Q\)
  • \(\hat{A} = Softmax(A)\)


### 3. 计算输出 \(O\)

  • 下一步是并行计算输出向量\(b^{i}\),如下图所示。

  • \(O = V \cdot \hat{A}\)


总结一下并行化计算的过程,如下:


Self-Attention就是一系列的矩阵乘法,而矩阵乘法可以用使用 GPU 加速。

Multi- -HeadSelf-Attention

Multi- -HeadSelf-Attention的思想是每个输入都会分别有两个q、k、v,分别得到两组 Attention,然后得到两个输出的向量 \(b^{i,1}\)\(b^{i,2}\)。一种解释是:不同的 Attention 可能会聚焦到不同范围的上下文。


拼接起来得到最终输出,如果维度太长,可以乘以一个矩阵降维,得到\(b^{i}\)


## Positional Encoding

Self-Attention中,没有编码位置的信息,在矩阵乘法中,并不考虑输入的 sequence 的顺序。

因此一种想法是:在计算\(a_{i}=w_{i} \cdot x_{i}\),加入表示位置的向量:\(a_{i}=a_{i}+ e_{i}\),其中 \(e_{i}\) 是表示位置的向量。


另一种做法是把$a_{i} $ 和 \(e_{i}\) 拼接起来,得到\([a_{i}, e_{i}]\)。从矩阵角度来看,这两种做法是一样的。

\(p^{i}\) 是表示位置的向量,那么 \(W \cdot \left[ \begin{array}{c|c} x^{i} \\ \hline p^{i} \end{array} \right]= \left[ \begin{array}{c|c} W^{I} & W^{P} \\ \end{array} \right] \cdot \left[ \begin{array}{c|c} x^{i} \\ \hline p^{i} \end{array} \right] = W^{I} \cdot x_{i} + W^{P} \cdot p_{i} = a^{i} + e^{i}\)


因此,Seq2Seq 的 Encoder 和 Decoder 部分,都可以使用 Self-Attention Layer 来实现。


## 动画演示

在 Encoder 部分,有 3 层 Attention,可以并行计算。在 Decoder 部分,也有 3 层 Attention,这里不能并行计算,因为在计算输出时,每一个输出的词,除了依赖 Encoder 的输出,还依赖于前面输出的词。


# Transformer

Transformer实际上就是添加了Self-attentionSeq2Seq model。

结构图如下所示:


左边是 Encoder 部分,右边是 Decoder 部分。

  • Encoder:输入的句子首先经过 Embedding layer 得到句子的词向量,加上 Positional Encoding 得到 \(a\),经过一个 Multi-Head-Attention Layer,得到 \(b\);然后 \(a\)\(b\) 相加,得到 \(b^{\prime}\),经过 Layer Norm。接下来经过 Feed Forward 层,Add & Norm 层,得到输出序列。

  • Decoder:输入是前面已经生成输出的词,首先经过 Masked Multi- Head-Attention,Masked 的意思是只关注已经产生的单词序列。然后和 Encoder 的输出一起,输入到 Multi-Head-Attention 中,接下来经过 Feed Forward 层,Add & Norm 层,Linear 层,Softmax 层,得到一个单词的输出。

Attention 可视化

  1. 下图是 Attention 的可视化,两两单词之间的 Attention 值越大,颜色越深;Attention 值越小,颜色越浅。


  2. 下图是两句不同的话,把 Attention Layer 中间的 Hidden state 取出来做可视化的结果,单词之间的 Attention 值越大,颜色越深;Attention 值越小,颜色越浅。

    • 左边是第一句话:

      animal didn`t cross the street because **it** was too tired.```
      1
      2
      3
      4
      5
      6

      其中的`it`指代的是`animal`,而 Attention 也正确学习到了`it`和`animal`之间的关系。

      - 左边是第一句话:

      ```The animal didn`t cross the street because **it** was too tired.

      与第一句话只有一个单词的差别。

      其中的it指代的是street,而 Attention 也正确学习到了itstreet之间的关系。


  3. Multi-Head Attention

    下面两张图都是 Multi-Head Attention 的可视化。

    第一张图的 Multi-Head Attention 更加关注全局的上下文信息。


    第二张图的 Multi-Head Attention 更加关注局部的上下文信息。


应用

Transformer 的应用非常广泛,只要能使用Seq2Seq的地方,都可以使用 Transformer。

下面的例子来源于https://arxiv.org/abs/1801.10198,输入多篇文章,输出是一篇综述。这种任务的输入一般是多篇文章,包括 \(10^2-10^6\) 个词,输出是一篇文章,长度在 \(10^1-10^3\) 个词,使用 RNN 训练一般难以收敛,但是使用 Transformer 来训练,可以得到不错的效果。


一种变种是:Universal Transformer。

本来在深度上,可以有多层不同的 Transformer。但是 Universal Transformer 在深度上,使用类似 RNN 的方式,把输出再循环连接到输入,相当于有多层相同的 Transformer。关于 Universal Transformer 更多细节参考:https://ai.googleblog.com/2018/08/moving-beyond-translation-with.html


Attention 还可以用在图像上,如下图所示。计算每两个像素之间的 Attention,可以看出像素之间的关联性强弱。论文地址:https://arxiv.org/abs/1805.08318



如果你觉得这篇文章对你有帮助,不妨点个赞,让我有更多动力写出好文章。

我的文章会首发在公众号上,欢迎扫码关注我的公众号张贤同学


Linux/Linux 下如何检索文件内容

Linux/Linux 下如何检索文件内容

管道操作符:|

可以将指令连接起来,前一个指令的输出作为后一个指令的输入 find ~ | grep "test" 的作用是在当前目录以及子目录中查找文件名包含 test 的文件以及文件夹,与 find ~ -name "test" 作用相同。

阅读更多
Python/Jupyter Notebook Python 减少内存消耗的方法

Python/Jupyter Notebook Python 减少内存消耗的方法

基本思路

在 Jupyter Notebook 中运行 Python 代码时,如果使用了太多的内存,那么会报Memory Error的错误。这表示前面的变量使用了太多的内存。

事后减少内存

在有一次数据建模中,由于前面进行了很多数据处理的工作,创建了非常多临时的DataFrame,运行到后面的代码时,前面大部分的变量都已经用不上了,但是它们还占据着内存,因此产生了内存不足的错误。

阅读更多
PyTorch/[PyTorch 学习笔记] 汇总 - 完结撒花

PyTorch/[PyTorch 学习笔记] 汇总 - 完结撒花

PyTorch 学习笔记

GitHub Website

这篇文章是我学习 PyTorch 过程中所记录的学习笔记汇总,包括 25 篇文章,是我学习深度之眼 PyTorch 框架版课程期间所记录的内容。课程地址:https://ai.deepshare.net/detail/p_5df0ad9a09d37_qYqVmt85/6

学习笔记的结构遵循课程的顺序,共分为 8 周,循序渐进,力求通俗易懂

代码

配套代码:https://github.com/zhangxiann/PyTorch_Practice

所有代码均在 PyCharm 中通过测试,建议通过 git 克隆到本地运行。

阅读更多
Java/synchronized 和 ReentrantLock

Java/synchronized 和 ReentrantLock

在 Java5 之前,只有synchronized一种锁,在 Java5 之后,增加了ReentrantLockReentrantLock位于 java.util.concurrent.locks 包,和 CountDownLatch、FutureTask、Semaphore 一样基于 AQS 实现,能够实现比synchronized更细粒度的控制,如控制公平性(fairness)。使用ReentrantLock需要注意的是在调用 lock() 方法之后,必须调用 unlock() 释放锁。在 Java6 后经过优化的synchronized性能未必比ReentrantLock低,并且synchronized也是可重入的。

阅读更多
Linux/Linux 下 使用 find 命令 查找文件以及文件夹

Linux/Linux 下 使用 find 命令 查找文件以及文件夹

find 后面第一个参数是要查找的目录,find 命令会在指定的目录以及子目录下面去查询文件 -name 后面接文件名,表示按照名字来查找文件和文件夹。 例如我在 ~/test/ 目录下面有一个文件 test3.java,那就可以在 ~ 目录下面 执行如下查找语句,查找出所有名字为 test3.java 的文件
阅读更多