Python/PyCharm 使用了 pytest 来执行代码,应该如何修改?
有时在 PyCharm 运行代码的时候,会出现类似如下 pytest
的错误。

有时在 PyCharm 运行代码的时候,会出现类似如下 pytest
的错误。
本文介绍的是 Kaggle 比赛:Two Sigma Connect: Rental Listing Inquiries
。赛题要求是根据 RentHop 租房网站上的房屋的信息,预测受欢迎的程度,受欢迎的程度有 3 种:'high'、'medium'、 'low',因此可以看作是 3 分类的问题。
数据主要包括房屋的房间数量、洗手间数量、创建时间、价格、位置、房屋的照片、经纬度、街道、中介,以及一段文本介绍。
这道赛题数据量不大,但同时包含了结构化数据、文本数据和图像数据,因此非常适合用于入门学习。
本文主要针对这些数据,展开数据探索的流程。
这篇文章用于记录台大李宏毅老师的 ELMO,BERT,GPT 课程 的学习笔记。课程地址:https://www.bilibili.com/video/av64570585/
这篇文章用一句话概括,就是:如何让机器看懂人类的文字。
为了让机器更好地理解人类的文字,学术界做了许多的研究,截至目前为止,在这个方向上最新的进展:ELMO、BERT、GPT。上一篇文章讲了 Transformer,而 BERT 是 Transformer 里的 Encoder,GPT 是 Transformer 里的 Decoder。
上一篇文章讲了 Word Embedding,也就是每个词都使用一个向量来表示,向量中包含了词的语义信息。
通常一个词只有一个 Embedding 向量已经足够了,但是实际中,同一个词出现在不同的句子,可能有不同的含义。
例如:下面 4 个句子中,前 2 个句子的bank
表示银行,后 2 个句子的bank
表示河边。
1 | Have you paid that money to the bank yet ? |
在之前的 Word Embedding 中,所有的bank
的 Embedding 都是一样的。但是在上面 4 个句子中,前 2 个句子的bank
和后 2 个句子的bank
的 Embedding 应该是不一样的。我们希望,模型对于同一个词,能够根据不同的上下文,学习出不同的 Embedding。这种方法叫做 Contextualized Word Embedding 。
为了达到这个目的,一种叫 ELMO (Embedding from Language Model)的方法被提出来了。这是一种基于 RNN 的。
具体是使用两个 RNN:
两个 RNN 训练完成后,取中间的 Hidden state,并拼接起来,得到最终的 Embedding,这个 Embedding 包含了上下文的信息。
上面的图中只有一层 RNN,在实际中,可以设置多层的 RNN ,这样就会得到多个层对应的 Embedding。那这时应该选择哪些层的 Embedding 呢?ELMO 的做法是这些 Embedding 全都要,将每一层的 Embedding 加权求和,得到最终的 Embedding。这里的权值,是和具体的 NLP 任务一起训练得到的,比如 Coreference(找出指代词对应的词)、SQuAD(问答系统)、SST-5(文本情感分类)。
Bert 就是 Transformer 的 Encoder 部分,全称是 Bidirectional Encoder Representations from Transformers。
在 transformer 中,Encoder 的输入是一句话的序列,输出是每个词的 Embedding 向量。
所以,BERT 的作用也是训练得到每个词的 Embedding。
训练 BERT 的方法包括两个:Masked LM 和 Next Sentence Prediction。
在实际中,这两个训练方法是同时应用到 BERT 中的。
因为最后的分类器是一个简单的线性分类器,拟合能力弱,而它的输入是从 BERT 中学习到的 Embedding,这就要求从 BERT 中学习到的 Embedding 是一种非常好的 representation。
第二种训练方法是:把两个句子拼接起来,中间添加一个 SEP 的分隔符,并在最前面添加一个 CLS 的 token,经过 BERT 得到每个位置的 Embedding,把 CLS 位置输出的 Embedding 输入一个线性 2 分类器,判断这两个句子是不是属于前后关系。
你可能有 一个疑问,为什么 CLS 的位置要放在句子的最开始,而不是最后面。这是因为 BERT 内部的 Self-Attention 可以连接整个上下文的信息。因此,CLS 放在句子的前面还是后面,是没有影响的。
在实际中,Bert 是可以和具体的任务,一起训练的。
下面是 4 个应用例子。
BERT 可以用来做情感分类,文档分类等任务。
具体是在文档最前面添加一个 CLS 的 token,然后整个文档经过 BERT 得到 Embedding,把 CLS 位置的 Embedding 输入到一个线性分类器做分类。其中 BERT 的网络可以使用别人已经训练好的参数做 fine-tune。
在 Slot filling 中,每个词都有一个类别。只需要把句子经过 BERT 得到的每一个 Embedding 都输入到线性分类器中,对每一个词做分类。
在 Natural Language Inference 任务中,输入是两个句子,前面一句是前提,后面一句是推论,判断根据前一个句子是否能够推导出后一个句子。这是一个 3 分类问题,输出有: True/False/ unknown。
和第一个问题类似,也是在句子前面添加一个 CLS 的 token,然后把 CLS 位置的 Embedding 输入一个线性分类器。
这是问答系统的任务,输入是一篇文章 \(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 和 BERT 类似,但是 ERNIE 是为中文设计的,全称是 Enhanced Representation through Knowledge Integration。
它和 BERT 的区别是:
论文地址:https://arxiv.org/abs/1904.09223
GPT 是 Generative Pre-Training,是 Transformer 的 Decoder 部分。也就是根据前一个词生成下一个词。
目前, GPT 已经发展到第 3代,称为 GPT 3,是目前最大的模型,使用的数据集在处理前容量达到了 45TB,具有 1750 亿参数,被赋予“最强 NLP 模型”的称号。GPT-3 可以做到非常多的事,它可以写诗、写剧本、写小说、甚至输出一些代码。GPT-3 的能力前所未有地强大。目前 GPT-3 已经开放了 API,你可以尝试申请体验一下。
如果你觉得这篇文章对你有帮助,不妨点个赞,让我有更多动力写出好文章。
我的文章会首发在公众号上,欢迎扫码关注我的公众号张贤同学。
我使用 Anaconda 创建了一个 python2.7
的环境,然后安装 opencv: pip install opencv-python
。
出现 TypeError: 'NoneType' object is not iterable
错误。
完整错误信息如下:
这篇文章用于记录台大李宏毅老师的Transformer 课程的学习笔记。课程地址:https://www.bilibili.com/video/BV1J441137V6
这篇文章用一句话概括,就是:如何让机器看懂人类的文字。
为了让机器更好地理解人类的文字,学术界做了许多的研究,截至目前为止,在这个方向上最新的进展就是 BERT、GPT dent,而 BERT 是 Transformer 里的 Encoder,GPT 是 Transformer 里的 Decoder。所以,这篇文章先谈谈 Transformer,下一篇文章讲 Bert 以及 GPT。
下面从历史发展来看词的向量表示的发展历程。
在最开始的时候,首先被提出来表示人类文字的方法叫做One-Hot Encoding
,也称为1-of-N Encoding
。
假设总共有 5 个词:apple
、bag
、cat
、dog
、elephant
,那么就使用长度为 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 | apple = [1,0,0,0,0] |
这种方法的缺点是:词和词之间没有语义上的关联。
比如:dog
和cat
都是动物,而apple
是植物,那么dog
和cat
的词向量的相似度,应该大于dog
和apple
的词向量的相似度,对于向量来说,衡量相似度的方法一般是余弦距离。
但是在One-Hot Encoding
的编码方式种,所有向量的余弦距离都是一样的,没有办法衡量两个词之间的相似性。因此,这种方式不能捕捉词的语义信息。
为了表示词中语义信息,出现了人为给词分类的方法。
但是这种方法是比较粗糙的,比如在class 1
中,虽然 3 个词都是动物,但是dog
和cat
都是哺乳类动物
,而bird
是非哺乳类的动物。不能区分更加细微的区别,如果想要一直穷尽这种无尽的分类,是不现实的,也过度依赖于人的经验和知识。
因此出现了Word Embedding
,这种方法是利用神经网络来学习出每个词的向量。那么语义相近的词向量就会靠得更近。而这种了向量是根据每个词的上下文训练出来的,主要有GloVe
与word2vec
。
word2vec
是predictive
的模型。又可分为:skip-gram
和cbow
两种方法。cbow
是根据上下文的词预测中心词,skip-gram
是根据中心词预测周围的词。Glove
是count-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
可以实现类似 RNN 的功能,在计算一个词的输出时,已经看过了前后上下文的单词。但相比于 RNN,Self-Attention
可以并行计算。
下图是 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 和每个 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}\) 是可以并行计算的。
下面,给出并行计算的过程。
首先,回到计算 query、key、value 的过程。其中 query、key、value 的计算,都可以使用矩阵来并行化计算。矩阵中每一列表示一个位置的值。
gif 动图如下:
### 2. 计算 Attention \(\hat{A}\)
下一步是计算 Attention ,这一步的并行化如下图所示。
向量中的每个元素,表示一个 key 和一个 value 之间的 Attention 分数。可以使用矩阵来进一步并行化,然后对矩阵 \(A\) 的每一列做 Softmax。
### 3. 计算输出 \(O\)
下一步是并行计算输出向量\(b^{i}\),如下图所示。
\(O = V \cdot \hat{A}\)
总结一下并行化计算的过程,如下:
Self-Attention
就是一系列的矩阵乘法,而矩阵乘法可以用使用 GPU 加速。
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-attention
的Seq2Seq
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 的可视化,两两单词之间的 Attention 值越大,颜色越深;Attention 值越小,颜色越浅。
下图是两句不同的话,把 Attention Layer 中间的 Hidden state 取出来做可视化的结果,单词之间的 Attention 值越大,颜色越深;Attention 值越小,颜色越浅。
左边是第一句话:
1 |
|
与第一句话只有一个单词的差别。
其中的it
指代的是street
,而 Attention 也正确学习到了it
和street
之间的关系。
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
如果你觉得这篇文章对你有帮助,不妨点个赞,让我有更多动力写出好文章。
我的文章会首发在公众号上,欢迎扫码关注我的公众号张贤同学。
可以将指令连接起来,前一个指令的输出作为后一个指令的输入 find ~ | grep "test"
的作用是在当前目录以及子目录中查找文件名包含 test
的文件以及文件夹,与 find ~ -name "test"
作用相同。
基本思路
在 Jupyter Notebook 中运行 Python 代码时,如果使用了太多的内存,那么会报Memory Error
的错误。这表示前面的变量使用了太多的内存。
在有一次数据建模中,由于前面进行了很多数据处理的工作,创建了非常多临时的DataFrame
,运行到后面的代码时,前面大部分的变量都已经用不上了,但是它们还占据着内存,因此产生了内存不足的错误。
这篇文章是我学习 PyTorch 过程中所记录的学习笔记汇总,包括 25 篇文章,是我学习深度之眼 PyTorch 框架版课程期间所记录的内容。课程地址:https://ai.deepshare.net/detail/p_5df0ad9a09d37_qYqVmt85/6。
学习笔记的结构遵循课程的顺序,共分为 8 周,循序渐进,力求通俗易懂。
配套代码:https://github.com/zhangxiann/PyTorch_Practice
所有代码均在 PyCharm 中通过测试,建议通过 git 克隆到本地运行。
在 Java5 之前,只有synchronized
一种锁,在 Java5 之后,增加了ReentrantLock
。ReentrantLock
位于 java.util.concurrent.locks 包,和 CountDownLatch、FutureTask、Semaphore 一样基于 AQS 实现,能够实现比synchronized
更细粒度的控制,如控制公平性(fairness)。使用ReentrantLock
需要注意的是在调用 lock() 方法之后,必须调用 unlock() 释放锁。在 Java6 后经过优化的synchronized
性能未必比ReentrantLock
低,并且synchronized
也是可重入的。