前言:最近刚入门摸索深度学习,在编辑网络时发现keras.preprocessing.text.Tokenizer
、keras.utils.to_catigorical()
、keras.layers.Embedding()
几个用于词的处理的方法经常弄混,于是单独找到这几个函数实验了一下,由于没有来得及看官方文档和函数编码,可能会有一些描述和用词不太准确的地方,希望大佬看到指出,我将积极改正
该篇分为三部分:
一、三者特点
二、总结(相同点、不同点)
三、通俗例子
备注:有一定基础,但是对三者功能比较迷惑,功能分不清的可以先把 三、通俗例子看了在回头看,当然按顺序也是极好的=w=
I、三者特点
一、keras.preprocessing.text.Tokenizer类:
keras.preprocessing.text.Tokenizer类:用于将[“str1”, “str2”]形式的字符串输入转换为整数编码key的形式(注:导入的Tokenizer为“类”,需要先对其进行实例化才行,否则无法调用!)
常见操作为用Tokenizer.fit_on_texts(["str1", "str2"])
喂字符串列表,得到一个{“单词”:数字值}
的对应字典,
tok = Tokenizer() # 实例化
tok.fit_on_texts(['wo lie kai','wo sha le'])
然后使用Tokenizer.texts_to_sequences(["str3", "str4"])
来得到目标整数编码,返回值形式为与输入对应嵌套列表形式[[ , ], [ , ]]
tok.texts_to_sequences(['wo kai le','wo sha lie']) # 编码为序列
>>>[[1, 3, 5], [1, 4, 2]]
此外,如果想查看第一步喂字符串得到的字典对应关系,可以使用Tokenizer.word_index
属性查看,返回对应字典(顺带,可以看出字典的编码是按照喂食的单词顺序来的-排序优先级相通情况下)
tok.word_index # 使用Tokenizer下的word_index属性查看编码字典
{'wo': 1, 'lie': 2, 'kai': 3, 'sha': 4, 'le': 5}
二、keras.utils.to_catigorical()
keras.utils.to_catigorical([], num_classes)
用于将整数编码转为one_hot矩阵的形式,每个元素为一行one_hot编码,输入可为其中元素为整数的numpy数组或为嵌套列表[[ , ], [ , ]]也看作一样的输入。输入np.array的每一行/每一个子列表为一个样本,其中每一个元素编码为一行one_hot编码, 一个样本中左右元素编码组成一个矩阵。m个[]->m个样本->转换后m个编码矩阵;[]中n个元素->转换后每个样本n行。
参数num_classes
为总类数、即ont_hot编码向量的长度。
# list输入
print(to_categorical([[1,2],[2,3]], num_classes=5))
>>>
[[[0. 1. 0. 0. 0.]
[0. 0. 1. 0. 0.]]
[[0. 0. 1. 0. 0.]
[0. 0. 0. 1. 0.]]]
np.array输入
import numpy as np
a = np.array([[1,2], [3, 4]])
print(to_categorical(a, num_classes=5))
>>>
[[[0. 1. 0. 0. 0.]
[0. 0. 1. 0. 0.]]
[[0. 0. 0. 1. 0.]
[0. 0. 0. 0. 1.]]]
备注:输出的都是默认float32浮点数
补充,keras.utils.to_categorical()在npl中常与keras.preprocessing.sequence.pad_sequences([], maxlen=)
配合使用,后者一般扩展补充张量为max_length长度,使张量统一长度格式后再参与网络运算,pad_sequences()中有参数调节插入位置(左或右)、插入值、截断方式等…
三、keras.layers.Embedding()层
keras.layers.Embedding(input_dim,output_dim,input_length)
层的作用是将经过词<–>整数转换的np.array数组,通过两步,
第一步转换为指定input_dim
维度的one_hot编码,这一步的性质与上一节中的keras.utils.to_categorical()类似,结果为:将每一个样本(一行)中的元素升维,嵌入到了一个大小为input_dim
长度的词向量中。
第二步,通过使用线性变换s*W+b=n将每个元素第一步得到的input_dim
维的one_hot向量嵌入到了一个维度为output_dim
维度的n中(想到于进行了一次降维/升维)。嵌入矩阵W和偏置矩阵b都会不断的学习改进。
不难看出其在输入/输出形式上和上面keras.utils.to_categorical()也是类似的,因为第二步操作只是对每个元素的one_hot编码进行了维度的变换:
from keras.models import Sequential
from keras.layers import Embedding
import numpy as np
ID_seq0 = np.array([1,0,0,1]) # 1维,4元素输入
ID_seq1 = np.array([[1, 0],[0, 1]]) # 2维,每个样本2元素输入(想象一下矩阵,每一行一个样本)
model = Sequential()
model.add(Embedding(5, 3))
#建立一个只有Embedding层的网络,设定为one_hot为5维,输出为3维
result0 = model.predict(ID_seq0)
result1 = model.predict(ID_seq1)
print("result0:\n", result0)
print("result1:\n", result1)
>>>
result0:
[[[-0.01626656 0.02572862 -0.00817467]] # 元素1的嵌入
[[-0.04854772 0.03120432 -0.00914316]] # 元素0的嵌入
[[-0.04854772 0.03120432 -0.00914316]] # 元素0嵌入
[[-0.01626656 0.02572862 -0.00817467]]] # 元素1的嵌入
result1:
[[[-0.01626656 0.02572862 -0.00817467] # 元素1的嵌入
[-0.04854772 0.03120432 -0.00914316]] # 元素0的嵌入
# 整个矩阵为array:[1,0]的输出
[[-0.04854772 0.03120432 -0.00914316] # 元素0的嵌入
[-0.01626656 0.02572862 -0.00817467]]] # 元素1的嵌入
# 整个矩阵为array:[0,1]的输出
II、总结:
相同点:
从功能上来说,keras.utils.to_catigorical()
和keras.layers.Embedding()层
比较相似,都是通过对元素的one_hot编码生维升维来为元素赋予特定的离散性、同时消除了特征间的关联性:比如将三个特征1,2,3,one_hot编码到我们常见的三维数轴[[1,0,0],[0,1,0],[0,0,1],各特征向之间相互垂直,且距离相等,使得特征之间相互独立、相关性被消除。
不同点:
keras.layers.Embedding()层
通过线性嵌入,调整了元素的维度,使得其有了更多的空间上的意义(强),且该相关性将通过学习不断调整,最终得到一个合适的 嵌入张量 使得one_hot编码过的特征能嵌入一个预期的特征空间;如果特征空间维度相较onr_hot维度低,则减少了维度,从而减少了计算量。
而keras.preprocessing.text.Tokenizer类
能预处理 词串 为 整数张量 从而为keras.utils.to_catigorical()
和keras.layers.Embedding()层
的处理做准备。
III、通俗实例
比方说我们要从下面两句话里的学习出意思,从而判断人物:“小明”“马大帅”与身份:“程序员”“资本家”的关系(这里是用英文句式类比,to_sequences也是没法直接用中文字符的,鄙人目前只接触过英文的,以后接触了中文的如果便于理解再更新):
stens = ["小明 每天 九九六 工作,查找 漏洞, 顿顿 外卖, 觉得 很累", "马大帅 到处 投资,认为 九九六 是 福报, 锦衣 玉食"]
首先,计算机是不认识每一个单词的,所以我们先使用Tokenizer.texts_to_sequences(["str1", "str2"])
对其进行编码,这会得到一个各个单词对应的字典:
{'996': 1, '小明': 2, '每天': 3, '工作': 4, '查找': 5, '漏洞': 6, '顿顿外卖': 7, '觉得': 8, '很累': 9, '马大帅': 10, '到处': 11, '投资': 12, '锦衣': 13, '玉食': 14, '认为': 15, '是': 16, '福报': 17}
这样编码出的字典,有了对应关系,相当于把每个词按照出现的顺序(这里996出现两次被优先编号了)嵌入了一个一维的向量中,这使得其顺序有了一定的语义联系,比如“觉得”“很累”以及“锦衣”“玉食”相邻,前者有“连贯动作”、“消极负面”、“主观”、“条件差”等意义,后者有“客观环境”、“积极正面”、“客观”、“条件好”等意义(注:这里说的意义只是我主观说的,我们深度学习很多情况下会学习出数据背后我们可能无法理解的意义)。
由于以上的原因,我们无法按照单词出现的次数和顺序正确的描述词汇在一个句子中所有的含义,one_hot编码的想法就是:那就让所有的词汇与他们的顺序都毫不相干,每个词汇都是独立存在的,使用keras.utils.to_catigorical([], num_classes)
对上述17个元素的单词进行编码得:(注一行为一个编码)
996 [1. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0.]
小明 [0. 1. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0.]
每天 [0. 0. 1. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0.]
工作 [0. 0. 0. 1. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0.]
查找 [0. 0. 0. 0. 1. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0.]
漏洞 [0. 0. 0. 0. 0. 1. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0.]
顿顿外卖 [0. 0. 0. 0. 0. 0. 1. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0.]
觉得 [0. 0. 0. 0. 0. 0. 0. 1. 0. 0. 0. 0. 0. 0. 0. 0. 0.]
很累 [0. 0. 0. 0. 0. 0. 0. 1. 0. 0. 0. 0. 0. 0. 0. 0. 0.]
马大帅 [0. 0. 0. 0. 0. 0. 0. 0. 1. 0. 0. 0. 0. 0. 0. 0. 0.]
到处 [0. 0. 0. 0. 0. 0. 0. 0. 0. 1. 0. 0. 0. 0. 0. 0. 0.]
投资 [0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 1. 0. 0. 0. 0. 0. 0.]
锦衣 [0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 1. 0. 0. 0. 0. 0.]
玉食 [0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 1. 0. 0. 0. 0.]
认为 [0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 1. 0. 0. 0.]
是 [0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 1. 0. 0.]
福报 [0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 1. 0.]
这样确保了每个元素之间是相互独立的,但是我们知道,真实情况中各个词汇/输入元素/特征之间不可能是毫无关系的,而且这里才两句话就已经使用了17维的词汇向量,如果学习那种有数万词的句子,这计算量难以想象。
那么如同机器学习中的降维方法,我们可以通过将已经one_hot编码的数据映射到另外一个(低维)空间的方法,使其降低计算量的同时,通过空间关系赋予其一定的语义关系(与之相对的,可以将低维的特征映射到高维空间中抽象出更多的特征),即使用keras.layers.Embedding()
通过keras.layers.Embedding()
,如果我们规定Embedding的映射空间维度为3维,通过深度学习,可能最终将["小明 每天 九九六 工作,查找 漏洞, 顿顿 外卖, 觉得 很累","马大帅 到处 投资,认为 九九六 是 福报, 锦衣 玉食"]
的one_hot编码映射到了学习出的特征“会编程”,“生活条件好”,“对工作满意”组成的3维语义空间中
假设学习出的该工作空间中的嵌入为
["会编程","生活条件好","对工作满意"]
996 [0.5. 0. -1.]
小明 [0.8. 0.1. 0.]
每天 [0. 0. 0.]
工作 [0. 0. 0.]
查找 [0.2. 0. 0.]
漏洞 [0.8. 0. 0.]
顿顿外卖 [0. -1. -1.]
觉得 [0. 0. 0.]
很累 [0. -1. -1.]
马大帅 [0. 0.8. 0.8.]
到处 [0. 0. 0.]
投资 [0. 0.8. 0.8.]
锦衣 [0. 0.8. 0.8.]
玉食 [0. 0.8. 0.8.]
认为 [0. 0. 0.]
是 [0. 0. 0.]
福报 [0. 0. 0.9.]
根据以上学习出编码特征及对应的结果,再根据后面的一系列全连接、激活等步骤的深度学习步骤,我们大概率可以分析出 人物 “小明”的 身份 是 “程序员”, “马大帅” 的 身份 是 “资本家” 了。