You can not select more than 25 topics Topics must start with a chinese character,a letter or number, can include dashes ('-') and can be up to 35 characters long.

tutorial_3_embedding.rst 21 kB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460
  1. =========================================
  2. 使用Embedding模块将文本转成向量
  3. =========================================
  4. 这一部分是一个关于在fastNLP当中使用embedding的教程。
  5. 教程目录:
  6. - `Part I: embedding介绍`_
  7. - `Part II: 使用预训练的静态embedding`_
  8. - `Part III: 使用随机初始化的embedding`_
  9. - `Part IV: ELMo Embedding`_
  10. - `Part V: Bert Embedding`_
  11. - `Part VI: 使用character-level的embedding`_
  12. - `Part VII: 叠加使用多个embedding`_
  13. - `Part VIII: Embedding的其它说明`_
  14. - `Part IX: StaticEmbedding的使用建议`_
  15. Part I: embedding介绍
  16. ---------------------------------------
  17. Embedding是一种词嵌入技术,可以将字或者词转换为实向量。目前使用较多的预训练词嵌入有word2vec, fasttext, glove, character embedding,
  18. elmo以及bert。
  19. 但使用这些词嵌入方式的时候都需要做一些加载上的处理,比如预训练的word2vec, fasttext以及glove都有着超过几十万个词语的表示,但一般任务大概
  20. 只会用到其中的几万个词,如果直接加载所有的词汇,会导致内存占用变大以及训练速度变慢,需要从预训练文件中抽取本次实验的用到的词汇;而对于英文的
  21. elmo和character embedding, 需要将word拆分成character才能使用;Bert的使用更是涉及到了Byte pair encoding(BPE)相关的内容。为了方便
  22. 大家的使用,fastNLP通过 :class:`~fastNLP.Vocabulary` 统一了不同embedding的使用。下面我们将讲述一些例子来说明一下
  23. Part II: 使用预训练的静态embedding
  24. ---------------------------------------
  25. 在fastNLP中,加载预训练的word2vec, glove以及fasttext都使用的是 :class:`~fastNLP.embeddings.StaticEmbedding` 。另外,为了方便大家的
  26. 使用,fastNLP提供了多种静态词向量的自动下载并缓存(默认缓存到~/.fastNLP/embeddings文件夹下)的功能,支持自动下载的预训练向量可以在
  27. `下载文档 <https://docs.qq.com/sheet/DVnpkTnF6VW9UeXdh?c=A1A0A0>`_ 查看。
  28. .. code-block:: python
  29. import torch
  30. from fastNLP.embeddings import StaticEmbedding
  31. from fastNLP import Vocabulary
  32. vocab = Vocabulary()
  33. vocab.add_word_lst("this is a demo .".split())
  34. embed = StaticEmbedding(vocab, model_dir_or_name='en-glove-6b-50d')
  35. words = torch.LongTensor([[vocab.to_index(word) for word in "this is a demo .".split()]]) # 将文本转为index
  36. print(embed(words).size()) # StaticEmbedding的使用和pytorch的nn.Embedding是类似的
  37. 输出为::
  38. torch.Size([1, 5, 50])
  39. fastNLP的StaticEmbedding在初始化之后,就和pytorch中的Embedding是类似的了。 :class:`~fastNLP.embeddings.StaticEmbedding` 的初始化
  40. 主要是从model_dir_or_name提供的词向量中抽取出 :class:`~fastNLP.Vocabulary` 中词语的vector。
  41. 除了可以通过使用预先提供的Embedding, :class:`~fastNLP.embeddings.StaticEmbedding` 也支持加载本地的预训练词向量,glove, word2vec以及
  42. fasttext格式的。通过将model_dir_or_name修改为本地的embedding文件路径,即可使用本地的embedding。
  43. Part III: 使用随机初始化的embedding
  44. ---------------------------------------
  45. 有时候需要使用随机初始化的Embedding,也可以通过使用 :class:`~fastNLP.embeddings.StaticEmbedding` 获得。只需要将model_dir_or_name
  46. 置为None,且传入embedding_dim,如下例所示
  47. .. code-block:: python
  48. from fastNLP.embeddings import StaticEmbedding
  49. from fastNLP import Vocabulary
  50. vocab = Vocabulary()
  51. vocab.add_word_lst("this is a demo .".split())
  52. embed = StaticEmbedding(vocab, model_dir_or_name=None, embedding_dim=30)
  53. words = torch.LongTensor([[vocab.to_index(word) for word in "this is a demo .".split()]])
  54. print(embed(words).size())
  55. 输出为::
  56. torch.Size([1, 5, 30])
  57. Part IV: ELMo Embedding
  58. -----------------------------------------------------------
  59. 在fastNLP中,我们提供了ELMo和BERT的embedding: :class:`~fastNLP.embeddings.ElmoEmbedding`
  60. 和 :class:`~fastNLP.embeddings.BertEmbedding` 。可自动下载的ElmoEmbedding可以
  61. 从 `下载文档 <https://docs.qq.com/sheet/DVnpkTnF6VW9UeXdh?c=A1A0A0>`_ 找到。
  62. 与静态embedding类似,ELMo的使用方法如下:
  63. .. code-block:: python
  64. from fastNLP.embeddings import ElmoEmbedding
  65. from fastNLP import Vocabulary
  66. vocab = Vocabulary()
  67. vocab.add_word_lst("this is a demo .".split())
  68. embed = ElmoEmbedding(vocab, model_dir_or_name='en-small', requires_grad=False)
  69. words = torch.LongTensor([[vocab.to_index(word) for word in "this is a demo .".split()]])
  70. print(embed(words).size())
  71. 输出为::
  72. torch.Size([1, 5, 256])
  73. 也可以输出多层的ELMo结果,fastNLP将在不同层的结果在最后一维上拼接,下面的代码需要在上面的代码执行结束之后执行
  74. .. code-block:: python
  75. embed = ElmoEmbedding(vocab, model_dir_or_name='en-small', requires_grad=False, layers='1,2')
  76. print(embed(words).size())
  77. 输出为::
  78. torch.Size([1, 5, 512])
  79. 另外,根据 `Deep contextualized word representations <https://arxiv.org/abs/1802.05365>`_ ,不同层之间使用可学习的权重可以使得ELMo的效果更好,在fastNLP中可以通过以下的初始化
  80. 实现3层输出的结果通过可学习的权重进行加法融合。
  81. .. code-block:: python
  82. embed = ElmoEmbedding(vocab, model_dir_or_name='en-small', requires_grad=True, layers='mix')
  83. print(embed(words).size()) # 三层输出按照权重element-wise的加起来
  84. 输出为::
  85. torch.Size([1, 5, 256])
  86. Part V: Bert Embedding
  87. -----------------------------------------------------------
  88. 虽然Bert并不算严格意义上的Embedding,但通过将Bert封装成Embedding的形式将极大减轻使用的复杂程度。可自动下载的Bert Embedding可以
  89. 从 `下载文档 <https://docs.qq.com/sheet/DVnpkTnF6VW9UeXdh?c=A1A0A0>`_ 找到。我们将使用下面的例子讲述一下
  90. BertEmbedding的使用
  91. .. code-block:: python
  92. from fastNLP.embeddings import BertEmbedding
  93. from fastNLP import Vocabulary
  94. vocab = Vocabulary()
  95. vocab.add_word_lst("this is a demo .".split())
  96. embed = BertEmbedding(vocab, model_dir_or_name='en-base-cased')
  97. words = torch.LongTensor([[vocab.to_index(word) for word in "this is a demo .".split()]])
  98. print(embed(words).size())
  99. 输出为::
  100. torch.Size([1, 5, 768])
  101. 可以通过申明使用指定层数的output也可以使用多层的output,下面的代码需要在上面的代码执行结束之后执行
  102. .. code-block:: python
  103. # 使用后面两层的输出
  104. embed = BertEmbedding(vocab, model_dir_or_name='en-base-cased', layers='10,11')
  105. print(embed(words).size()) # 结果将是在最后一维做拼接
  106. 输出为::
  107. torch.Size([1, 5, 1536])
  108. 在Bert中还存在两个特殊的字符[CLS]和[SEP],默认情况下这两个字符是自动加入并且在计算结束之后会自动删除,以使得输入的序列长度和输出的序列
  109. 长度是一致的,但是有些分类的情况,必须需要使用[CLS]的表示,这种情况可以通过在初始化时申明一下需要保留[CLS]的表示,如下例所示
  110. .. code-block:: python
  111. embed = BertEmbedding(vocab, model_dir_or_name='en-base-cased', layers='-1', include_cls_sep=True)
  112. print(embed(words).size()) # 结果将在序列维度上增加2
  113. # 取出句子的cls表示
  114. cls_reps = embed(words)[:, 0] # shape: [batch_size, 768]
  115. 输出为::
  116. torch.Size([1, 7, 768])
  117. 在英文Bert模型中,一个英文单词可能会被切分为多个subword,例如"fairness"会被拆分为 ``["fair", "##ness"]`` ,这样一个word对应的将有两个输出,
  118. :class:`~fastNLP.embeddings.BertEmbedding` 会使用pooling方法将一个word的subword的表示合并成一个vector,通过pool_method可以控制
  119. 该pooling方法,支持的有"first"(即使用fair的表示作为fairness的表示), "last"(使用##ness的表示作为fairness的表示), "max"(对fair和
  120. ##ness在每一维上做max),"avg"(对fair和##ness每一维做average)。
  121. .. code-block:: python
  122. embed = BertEmbedding(vocab, model_dir_or_name='en-base-cased', layers='-1', pool_method='max')
  123. print(embed(words).size())
  124. 输出为::
  125. torch.Size([1, 5, 768])
  126. 另外,根据 `BERT: Pre-training of Deep Bidirectional Transformers for Language Understanding <https://arxiv.org/abs/1810.04805>`_ ,
  127. Bert在针对具有两句话的任务时(如matching,Q&A任务),句子之间通过[SEP]拼接起来,前一句话的token embedding为0,
  128. 后一句话的token embedding为1。BertEmbedding能够自动识别句子中间的[SEP]来正确设置对应的token_type_id的。
  129. .. code-block:: python
  130. vocab = Vocabulary()
  131. vocab.add_word_lst("this is a demo . [SEP] another sentence .".split())
  132. embed = BertEmbedding(vocab, model_dir_or_name='en-base-cased', layers='-1', pool_method='max')
  133. words = torch.LongTensor([[vocab.to_index(word) for word in "this is a demo . [SEP] another sentence .".split()]])
  134. print(embed(words).size())
  135. 输出为::
  136. torch.Size([1, 9, 768])
  137. 在多个[SEP]的情况下,将会使token_type_id不断0,1循环。比如"first sentence [SEP] second sentence [SEP] third sentence", 它们的
  138. token_type_id将是[0, 0, 0, 1, 1, 1, 0, 0]。但请注意[SEP]一定要大写的,不能是[sep],否则无法识别。
  139. 更多 :class:`~fastNLP.embedding.BertEmbedding` 的使用,请参考 :doc:`/tutorials/extend_1_bert_embedding`
  140. Part VI: 使用character-level的embedding
  141. -----------------------------------------------------
  142. 除了预训练的embedding以外,fastNLP还提供了两种Character Embedding: :class:`~fastNLP.embeddings.CNNCharEmbedding` 和
  143. :class:`~fastNLP.embeddings.LSTMCharEmbedding` 。一般在使用character embedding时,需要在预处理的时候将word拆分成character,这
  144. 会使得预处理过程变得非常繁琐。在fastNLP中,使用character embedding也只需要传入 :class:`~fastNLP.Vocabulary` 即可,而且该
  145. Vocabulary与其它Embedding使用的Vocabulary是一致的,下面我们看两个例子。
  146. CNNCharEmbedding的使用例子如下:
  147. .. code-block:: python
  148. from fastNLP.embeddings import CNNCharEmbedding
  149. from fastNLP import Vocabulary
  150. vocab = Vocabulary()
  151. vocab.add_word_lst("this is a demo .".split())
  152. # character的embedding维度大小为50,返回的embedding结果维度大小为64。
  153. embed = CNNCharEmbedding(vocab, embed_size=64, char_emb_size=50)
  154. words = torch.LongTensor([[vocab.to_index(word) for word in "this is a demo .".split()]])
  155. print(embed(words).size())
  156. 输出为::
  157. torch.Size([1, 5, 64])
  158. 与CNNCharEmbedding类似,LSTMCharEmbedding的使用例子如下:
  159. .. code-block:: python
  160. from fastNLP.embeddings import LSTMCharEmbedding
  161. from fastNLP import Vocabulary
  162. vocab = Vocabulary()
  163. vocab.add_word_lst("this is a demo .".split())
  164. # character的embedding维度大小为50,返回的embedding结果维度大小为64。
  165. embed = LSTMCharEmbedding(vocab, embed_size=64, char_emb_size=50)
  166. words = torch.LongTensor([[vocab.to_index(word) for word in "this is a demo .".split()]])
  167. print(embed(words).size())
  168. 输出为::
  169. torch.Size([1, 5, 64])
  170. Part VII: 叠加使用多个embedding
  171. -----------------------------------------------------
  172. 单独使用Character Embedding往往效果并不是很好,需要同时结合word embedding。在fastNLP中可以通过 :class:`~fastNLP.embeddings.StackEmbedding`
  173. 来叠加embedding,具体的例子如下所示
  174. .. code-block:: python
  175. from fastNLP.embeddings import StaticEmbedding, StackEmbedding, CNNCharEmbedding
  176. from fastNLP import Vocabulary
  177. vocab = Vocabulary()
  178. vocab.add_word_lst("this is a demo .".split())
  179. word_embed = StaticEmbedding(vocab, model_dir_or_name='en-glove-6b-50d')
  180. char_embed = CNNCharEmbedding(vocab, embed_size=64, char_emb_size=50)
  181. embed = StackEmbedding([word_embed, char_embed])
  182. words = torch.LongTensor([[vocab.to_index(word) for word in "this is a demo .".split()]])
  183. print(embed(words).size()) # 输出embedding的维度为50+64=114
  184. 输出为::
  185. torch.Size([1, 5, 114])
  186. :class:`~fastNLP.embeddings.StaticEmbedding` , :class:`~fastNLP.embeddings.ElmoEmbedding` ,
  187. :class:`~fastNLP.embeddings.CNNCharEmbedding` , :class:`~fastNLP.embeddings.BertEmbedding` 等都可以互相拼接。
  188. :class:`~fastNLP.embeddings.StackEmbedding` 的使用也是和其它Embedding是一致的,即输出index返回对应的表示。但能够拼接起来的Embedding
  189. 必须使用同样的 :class:`~fastNLP.Vocabulary` ,因为只有使用同样的 :class:`~fastNLP.Vocabulary` 才能保证同一个index指向的是同一个词或字
  190. Part VIII: Embedding的其它说明
  191. -----------------------------------------------------------
  192. (1) 获取各种Embedding的dimension
  193. .. code-block:: python
  194. from fastNLP.embeddings import *
  195. vocab = Vocabulary()
  196. vocab.add_word_lst("this is a demo .".split())
  197. static_embed = StaticEmbedding(vocab, model_dir_or_name='en-glove-6b-50d')
  198. print(static_embed.embedding_dim) # 50
  199. char_embed = CNNCharEmbedding(vocab, embed_size=30)
  200. print(char_embed.embedding_dim) # 30
  201. elmo_embed_1 = ElmoEmbedding(vocab, model_dir_or_name='en-small', layers='2')
  202. print(elmo_embed_1.embedding_dim) # 256
  203. elmo_embed_2 = ElmoEmbedding(vocab, model_dir_or_name='en-small', layers='1,2')
  204. print(elmo_embed_2.embedding_dim) # 512
  205. bert_embed_1 = BertEmbedding(vocab, layers='-1', model_dir_or_name='en-base-cased')
  206. print(bert_embed_1.embedding_dim) # 768
  207. bert_embed_2 = BertEmbedding(vocab, layers='2,-1', model_dir_or_name='en-base-cased')
  208. print(bert_embed_2.embedding_dim) # 1536
  209. stack_embed = StackEmbedding([static_embed, char_embed])
  210. print(stack_embed.embedding_dim) # 80
  211. (2) 设置Embedding的权重是否更新
  212. .. code-block:: python
  213. from fastNLP.embeddings import *
  214. vocab = Vocabulary()
  215. vocab.add_word_lst("this is a demo .".split())
  216. embed = BertEmbedding(vocab, model_dir_or_name='en-base-cased', requires_grad=True) # 初始化时设定为需要更新
  217. embed.requires_grad = False # 修改BertEmbedding的权重为不更新
  218. (3) 各种Embedding中word_dropout与dropout的说明
  219. fastNLP中所有的Embedding都支持传入word_dropout和dropout参数,word_dropout指示的是以多大概率将输入的word置为unk的index,这样既可以
  220. 是的unk得到训练,也可以有一定的regularize效果; dropout参数是在获取到word的表示之后,以多大概率将一些维度的表示置为0。
  221. 如果使用 :class:`~fastNLP.embeddings.StackEmbedding` 且需要用到word_dropout,建议将word_dropout设置在 :class:`~fastNLP.embeddings.StackEmbedding` 上。
  222. Part IX: StaticEmbedding的使用建议
  223. -----------------------------------------------------------
  224. 在英文的命名实体识别(NER)任务中,由 `Named Entity Recognition with Bidirectional LSTM-CNNs <http://xxx.itp.ac.cn/pdf/1511.08308.pdf>`_ 指出,同时使用cnn character embedding和word embedding
  225. 会使得NER的效果有比较大的提升。正如你在上节中看到的那样,fastNLP支持将 :class:`~fastNLP.embeddings.CNNCharEmbedding`
  226. 与 :class:`~fastNLP.embeddings.StaticEmbedding` 拼成一个 :class:`~fastNLP.embeddings.StackEmbedding` 。如果通过这种方式使用,需要
  227. 在预处理文本时,不要将词汇小写化(因为Character Embedding需要利用词语中的大小写信息)且不要将出现频次低于某个阈值的word设置为unk(因为
  228. Character embedding需要利用字形信息);但 :class:`~fastNLP.embeddings.StaticEmbedding` 使用的某些预训练词嵌入的词汇表中只有小写的词
  229. 语, 且某些低频词并未在预训练中出现需要被剔除。即(1) character embedding需要保留大小写,而预训练词向量不需要保留大小写。(2)
  230. character embedding需要保留所有的字形, 而static embedding需要设置一个最低阈值以学到更好的表示。
  231. (1) fastNLP如何解决关于大小写的问题
  232. fastNLP通过在 :class:`~fastNLP.embeddings.StaticEmbedding` 增加了一个lower参数解决该问题。如下面的例子所示
  233. .. code-block:: python
  234. from fastNLP.embeddings import StaticEmbedding
  235. from fastNLP import Vocabulary
  236. vocab = Vocabulary().add_word_lst("The the a A".split())
  237. # 下面用随机的StaticEmbedding演示,但与使用预训练词向量时效果是一致的
  238. embed = StaticEmbedding(vocab, model_name_or_dir=None, embedding_dim=5)
  239. print(embed(torch.LongTensor([vocab.to_index('The')])))
  240. print(embed(torch.LongTensor([vocab.to_index('the')])))
  241. 输出为::
  242. tensor([[-0.4685, 0.4572, 0.5159, -0.2618, -0.6871]], grad_fn=<EmbeddingBackward>)
  243. tensor([[ 0.2615, 0.1490, -0.2491, 0.4009, -0.3842]], grad_fn=<EmbeddingBackward>)
  244. 可以看到"The"与"the"的vector是不一致的。但如果我们在初始化 :class:`~fastNLP.embeddings.StaticEmbedding` 将lower设置为True,效果将
  245. 如下所示
  246. .. code-block:: python
  247. from fastNLP.embeddings import StaticEmbedding
  248. from fastNLP import Vocabulary
  249. vocab = Vocabulary().add_word_lst("The the a A".split())
  250. # 下面用随机的StaticEmbedding演示,但与使用预训练时效果是一致的
  251. embed = StaticEmbedding(vocab, model_name_or_dir=None, embedding_dim=5, lower=True)
  252. print(embed(torch.LongTensor([vocab.to_index('The')])))
  253. print(embed(torch.LongTensor([vocab.to_index('the')])))
  254. 输出为::
  255. tensor([[-0.2237, 0.6825, -0.3459, -0.1795, 0.7516]], grad_fn=<EmbeddingBackward>)
  256. tensor([[-0.2237, 0.6825, -0.3459, -0.1795, 0.7516]], grad_fn=<EmbeddingBackward>)
  257. 可以看到"The"与"the"的vector是一致的。他们实际上也是引用的同一个vector。通过将lower设置为True,可以在 :class:`~fastNLP.embeddings.StaticEmbedding`
  258. 实现类似具备相同小写结果的词语引用同一个vector。
  259. (2) fastNLP如何解决min_freq的问题
  260. fastNLP通过在 :class:`~fastNLP.embeddings.StaticEmbedding` 增加了一个min_freq参数解决该问题。如下面的例子所示
  261. .. code-block:: python
  262. from fastNLP.embeddings import StaticEmbedding
  263. from fastNLP import Vocabulary
  264. vocab = Vocabulary().add_word_lst("the the the a".split())
  265. # 下面用随机的StaticEmbedding演示,但与使用预训练时效果是一致的
  266. embed = StaticEmbedding(vocab, model_name_or_dir=None, embedding_dim=5, min_freq=2)
  267. print(embed(torch.LongTensor([vocab.to_index('the')])))
  268. print(embed(torch.LongTensor([vocab.to_index('a')])))
  269. print(embed(torch.LongTensor([vocab.unknown_idx])))
  270. 输出为::
  271. tensor([[ 0.0454, 0.3375, 0.6758, -0.2026, -0.4715]], grad_fn=<EmbeddingBackward>)
  272. tensor([[-0.7602, 0.0149, 0.2733, 0.3974, 0.7371]], grad_fn=<EmbeddingBackward>)
  273. tensor([[-0.7602, 0.0149, 0.2733, 0.3974, 0.7371]], grad_fn=<EmbeddingBackward>)
  274. 其中最后一行为unknown值的vector,可以看到a的vector表示与unknown是一样的,这是由于a的频次低于了2,所以被指向了unknown的表示;而the由于
  275. 词频超过了2次,所以它是单独的表示。
  276. 在计算min_freq时,也会考虑到lower的作用,比如
  277. .. code-block:: python
  278. from fastNLP.embeddings import StaticEmbedding
  279. from fastNLP import Vocabulary
  280. vocab = Vocabulary().add_word_lst("the the the a A".split())
  281. # 下面用随机的StaticEmbedding演示,但与使用预训练时效果是一致的
  282. embed = StaticEmbedding(vocab, model_name_or_dir=None, embedding_dim=5, min_freq=2, lower=True)
  283. print(embed(torch.LongTensor([vocab.to_index('the')])))
  284. print(embed(torch.LongTensor([vocab.to_index('a')])))
  285. print(embed(torch.LongTensor([vocab.to_index('A')])))
  286. print(embed(torch.LongTensor([vocab.unknown_idx])))
  287. 输出为::
  288. tensor([[-0.7453, -0.5542, 0.5039, 0.6195, -0.4723]], grad_fn=<EmbeddingBackward>) # the
  289. tensor([[ 0.0170, -0.0995, -0.5743, -0.2469, -0.2095]], grad_fn=<EmbeddingBackward>) # a
  290. tensor([[ 0.0170, -0.0995, -0.5743, -0.2469, -0.2095]], grad_fn=<EmbeddingBackward>) # A
  291. tensor([[ 0.6707, -0.5786, -0.6967, 0.0111, 0.1209]], grad_fn=<EmbeddingBackward>) # unk
  292. 可以看到a不再和最后一行的unknown共享一个表示了,这是由于a与A都算入了a的词频,且A的表示也是a的表示。
  293. ----------------------------------
  294. 代码下载
  295. ----------------------------------
  296. `点击下载 IPython Notebook 文件 <https://sourcegraph.com/github.com/fastnlp/fastNLP@master/-/raw/tutorials/tutorial_3_embedding.ipynb>`_)