1 自然语言处理(NLP)
NLP 任务希望能做到以下三件事:
1、标记文本区域(如词性标注、情感分类或者命名实体识别)
2、链接两个以上的文本区域(识别表示同一个实体的名词短语或代词)
3、填补基于上下文的信息空缺(例如用完形填空的形式补齐单词缺失的部分)
2 监督NLP学习
1、由于神经网络只能将输入的数字映射到输出的数字,所以NLP的第一步是将文本转换为数字形式
2、将文本转换为数字的目标可以看作:使输入和输出之间的相关性对神经网络来说更明显。这将有助于更快的训练和更高的泛化
3、通常的做法是创建一个矩阵,其中每一行(向量)对应于每个电影评论,每一列表示评论汇中是否包含词汇表中的特定单词。在对应的位置插入1,剩余的位置插入0。这种存储形式称为 one-hot 编码,是二进制数据最常见的编码格式
3 引入嵌入层
1、下面是预测情绪的神经网络。第一层表示数据集(layer_0),然后是线性层(weights_0_1)和 relu 层(layer_1),之后是另一个线性层(weights_1_2),再后是输出层,也就是预测层。有点像之前的堆叠神经网络
2、可以使用一个嵌入层来替换第一个线性层(weights_0_1),从而快速切入layer_1
3、和一个只有1和0的向量进行乘积操作,在数学上等价于对矩阵中的几行进行求和(如下面 A*v 的例子)。对数据量更少的矩阵行进行求和要快得多
处理IMDB影评数据代码
import numpy as np
import sys
np.random.seed(1)
#https://github.com/iamtrask/grokking-deep-learning
f = open('reviews.txt')
raw_reviews = f.readlines()
f.close()
#https://github.com/iamtrask/grokking-deep-learning
f= open('labels.txt')
raw_labels = f.readlines()
f.close()
#[{'apple', 'orange', 'banana'}, {'apple', 'banana'}, {'orange', 'banana'}]
tokens = list(map(lambda x:set(x.split(" ")),raw_reviews))
vocab = set()
for sent in tokens:
for word in sent:
if(len(word)>0):
vocab.add(word)
vocab = list(vocab)
word2index = {}
for i,word in enumerate(vocab):
word2index[word] = i
#维度是(25000,影评单词相对应vocab中的索引)
input_dataset = list()
for sent in tokens:
sent_indices = list()
for word in sent:
try:
sent_indices.append(word2index[word])
except:
""
input_dataset.append(list(set(sent_indices)))
target_dataset = list()
for label in raw_labels:
if label in raw_labels:
if label == 'positive\n':
target_dataset.append(1)
else:
target_dataset.append(0)
def sigmoid(x):
return 1/(1+np.exp(-x))
alpha,iterations = (0.01,2)
hidden_size = 100
#vocab 影评中单词的数量大概是7w多,hidden_size是100
weights_0_1 = 0.2*np.random.random((len(vocab),hidden_size)) - 0.1
#维度是(100,1)
weights_1_2 = 0.2*np.random.random((hidden_size,1)) - 0.1
correct,total = (0,0)
for iter in range(iterations):
#选择前2.4万条记录进行训练
for i in range(len(input_dataset)-1000):
x,y = (input_dataset[i],target_dataset[i])
#weight_0_1 只筛选input_dataset有对应值的行,并按列进行求和
#维度(1,hidden_size),sigmoid激活函数,转化为0-1的概率
layer_1 = sigmoid(np.sum(weights_0_1[x],axis=0))
layer_2 = sigmoid(np.dot(layer_1,weights_1_2))
#预测值和真值比较
layer_2_delta = layer_2 - y
#反向传播
layer_1_delta = layer_2_delta.dot(weights_1_2.T)
#校正weights
weights_0_1[x] -= layer_1_delta * alpha
#np.outer 函数计算两个向量的外积,即返回一个矩阵,矩阵的元素是两个输入向量中每个元素的乘积
weights_1_2 -= np.outer(layer_1,layer_2_delta) * alpha
if (np.abs(layer_2_delta) <0.5):
correct += 1
total += 1
if(i %10 == 9):
progress = str(i/float(len(input_dataset)))
sys.stdout.write('\rIter:'+str(iter)\
+' Progress:'+progress[2:4]\
+'.' +progress[4:6]\
+'% Training Accuracy:'\
+ str(correct/float(total)) + '%' )
print()
correct,total = (0,0)
for i in range(len(input_dataset)-1000,len(input_dataset)):
x = input_dataset[i]
y = target_dataset[i]
layer_1 = sigmoid(np.sum(weights_0_1[x],axis=0))
layer_2 = sigmoid(np.dot(layer_1,weights_1_2))
if(np.abs(layer_2 -y) < 0.5):
correct += 1
total += 1
print("Test Accuracy:" + str(correct/float(total)))
复习一下外积的计算
4 神经网络学到了什么
1、两层神经网络学到的信息是:输入数据和目标数据之间的关联
2、含有隐藏层的神经网络:隐藏层是将前一层的数据点分为 n 个小组(其中n是隐藏层中的神经元数量)。每个隐藏的神经元接受一个数据点,并尝试回答这个数据点(每条影评)是否属于我这个小组。作为隐藏层,它搜索对输入来说有用的分组方式
3、有用的分组满足下面两个条件:(1)分组必须对输出标签的预测有用(2)分组结果是你所关心的数据中某种实际出现的现象,那么分组是有用的。例如一个神经元可以识别“可怕”和“不可怕”,可以识别“糟糕”和“不糟糕”就是有用的分组
4、隐藏层从根本上意味着对前一层的数据进行分组。在更小的颗粒度来看,每个神经元将数据点分类为属于或者不属于该组。在更高层次来看,如果两个数据点同时属于许多相同的组,它们就是相似的
5、具有类似预测功能的单词应该属于类似的组。与“good”相连的三项粗体权重构成了“good”这一单词的嵌入表达。它反映了“good”这个词属于每个分组(隐藏层神经元)的可能。具有类似预测能力的单词具有类似的单词嵌入表达(权重值)
5 输出嵌入表达(权重)相近的单词和神经元的含义
1、weights_0_1 的每行代表每个单词到每个隐藏神经元的权重。想要找到与目标单词类似的词,只需将两个条目的向量进行比较即可。可以用简单的欧式距离比较两个单词的权重
2、神经元的意义完全基于所预测的目标标签。例如与“beautiful”最相似的单词是“freedom”,但对于预测影评是正面和负面来说,这些词具有相同的意义。神经网络中的每一件事的推导都基于关联抽象,都致力于试图正确的做出预测
from collections import Counter
import math
def similar(target='beautiful'):
target_index = word2index[target]
socores = Counter()
for word,index in word2index.items():
raw_difference = weights_0_1[index] - weights_0_1[target_index]
squared_difference = raw_difference * raw_difference
scores[word] = -math.sqrt(sum(squared_difference))
return scores.most_common(10)
print(similar('beautiful'))
print(similar('terrible'))
6 完形填空
1、关于Word2Vec的背景知识参考:https://zhuanlan.zhihu.com/p/99397850
2、关于negative sampling的背景知识参考:https://zhuanlan.zhihu.com/p/456088223
3、这个是利用上下文去预测目标单词的例子,采用了负采样的方法
4、与上一个预测影评是正面和负面的例子相比,这次与 ‘terrible’ 聚类到一起是基于在同一个短语中出现的可能性(与情绪无关)
5、由此得出的结论是,即使上面的两个案例(预测目标词汇,预测情绪正负)中的网络在相同的数据上训练,其架构非常相似,我们依旧可以通过更改网络要预测的内容,来影响网络在权重允许的范围内能够学到的内容。这个选择想让网络学习内容的过程称为“智能定位(intelligence targeting)”
import sys,random,math
from collections import Counter
import numpy as np
np.random.seed(1)
random.seed(1)
f = open('reviews.txt')
raw_reviews = f.readlines()
f.close()
#[{'apple', 'orange', 'banana'}, {'apple', 'banana'}, {'orange', 'banana'}]
tokens = list(map(lambda x:set(x.split(" ")),raw_reviews))
wordcnt = Counter()
for sent in tokens:
for word in sent:
wordcnt[word] -= 1
#对word按照从大到小进行排列,但是因为是负数,其实是按照从小到大进行排列
vocab = list(set(map(lambda x:x[0],wordcnt.most_common())))
word2index = {}
for i,word in enumerate(vocab):
word2index[word]=i
#准备输入数据集
concatenated = list()
input_dataset = list()
for sent in tokens:
sent_indices = list()
for word in sent:
try:
sent_indices.append(word2index[word])
concatenated.append(word2index[word])
except:
""
#将每个词转换为索引,并将所有索引构建成 input_dataset 数据集
input_dataset.append(sent_indices)
#concatenated 是所有评论中词索引的平铺版本,用于负采样。这里没有去重,能展现上下文和目标词之间的关系
concatenated = np.array(concatenated)
#随机打乱输入数据集
random.shuffle(input_dataset)
alpha,iterations = (0.05,2)
#negative: 负采样的数量为5
hidden_size,window,negative = (50,2,5)
#词到隐藏层的权重,维度为 len(vocab) x hidden_size,表示每个词与隐藏层之间的连接
#维度(25000,50)
weights_0_1 = (np.random.rand(len(vocab),hidden_size)-0.5) * 0.2
#隐藏层到输出层的权重,初始化为零
weights_1_2 = np.random.rand(len(vocab),hidden_size)*0
layer_2_target = np.zeros(negative + 1)
#array([1., 0., 0., 0., 0., 0.])
layer_2_target[0] = 1
def similar(target='beautiful'):
target_index = word2index[target]
scores = Counter()
for word,index in word2index.items():
raw_difference = weights_0_1[index] - weights_0_1[target_index]
squared_difference = raw_difference * raw_difference
scores[word] = -math.sqrt(sum(squared_difference))
return scores.most_common(10)
def sigmoid(x):
return 1/(1+np.exp(-x))
for rev_i,review in enumerate(input_dataset*iterations):
#review 是每条评论的index
for target_i in range(len(review)):
#target_samples 包含了当前目标词(review[target_i])和从 concatenated 中随机采样的负样本。负样本是随机选择的与目标词无关的词
#np.random.rand(negative) 生成一个长度为 negative 的随机浮点数组,表示负样本的数量
#通过乘以 len(concatenated) 并转化为整数来随机选择词汇表中的词索引
#负样本的目的是帮助模型区分相关词和不相关的词,从而学习到更有意义的词向量
target_samples = [review[target_i]] + list(concatenated[(np.random.rand(negative)*len(concatenated)).astype('int').tolist()])
#left_context 获取目标词之前的上下文词,范围是 [target_i - window, target_i),确保索引不小于 0
#window 是上下文窗口的大小,表示考虑多少个单词作为上下文
left_context = review[max(0,target_i - window):target_i]
#right_context 获取目标词之后的上下文词,范围是 (target_i + 1, target_i + window),确保索引不超过句子长度
right_context = review[target_i+1:min(len(review),target_i+window)]
#weights_0_1 是输入层到隐藏层的权重矩阵。weights_0_1[left_context + right_context] 获取上下文词的词向量
#np.mean(..., axis=0) 对上下文词的词向量进行平均,得到一个表示上下文信息的单一向量(layer_1)
# mean instead of sum, interesting
# context words w/o target word
layer_1 = np.mean(weights_0_1[left_context+right_context],axis=0)
#weights_1_2 是隐藏层到输出层的权重矩阵。weights_1_2[target_samples] 获取负样本和正样本的词向量
layer_2 = sigmoid(layer_1.dot(weights_1_2[target_samples].T))
layer_2_delta = layer_2 - layer_2_target #array([1., 0., 0., 0., 0., 0.]) 第一个单词是目标单词的概率为1,负采样的概率为0
layer_1_delta = layer_2_delta.dot(weights_1_2[target_samples])
weights_0_1[left_context+right_context] -= layer_1_delta * alpha
weights_1_2[target_samples] -= np.outer(layer_2_delta,layer_1)*alpha
if(rev_i % 250 == 0):
sys.stdout.write('\rProgress:'+str(rev_i/float(len(input_dataset)*iterations)) + " " + str(similar('terrible')))
sys.stdout.write('\rProgress:'+str(rev_i/float(len(input_dataset)*iterations)))
print(similar('terrible'))
7 损失函数的意义
1、除了通过控制输入和预测目标值,还可以通过调整网络测量误差的方式、网络层的参数数量和类型,以及应用的正则化类型来改变网络的学习过程。这些所有技术都属于构建损失函数的范畴
2、损失函数决定了神经网络学到的知识。误差函数更正式的术语是损失函数或目标函数(这三个短语全部是可以互换的)
3、两个神经网络可以有相同的初始权重值,在相同的数据集上训练,但最终由于选择了不同的损失函数,而学到截然不同的模式
8 单词类比
1、完形填空的任务能够为词汇创建有趣的嵌入表达,这种现象称为 单词类比,意味着你可以基于不同单词的嵌入向量,对它们进行基本的代数运算。如 king 的向量,减去 man 的向量,加上 woman 的向量,然后搜索最相似的向量,最相似的向量通常是 queen
9 参考资料
《深度学习图解》