DL学习笔记——变长数据的递归层(RNN)

0.python基础

https://www.codecademy.com/catalog

1.github

下载 GitHub - iamtrask/Grokking-Deep-Learning: this repository accompanies the book "Grokking Deep Learning"

2.AI

https://www.codecademy.com/catalog/subject/artificial-intelligence

3.The Unreasonable Effectiveness of Recurrent Neural Networks

The Unreasonable Effectiveness of Recurrent Neural Networks

———————————————————————————————————————————

12.1 平均词向量的神奇力量

它是做神经预测的特别强大的工具。

import numpy as np
norms = np.sum(weights_0_1 * weights_0_1,axis=1)
norms.resize(norms.shape[0],1)
normed_weights = weights_0_1 * norms

def make_sent_vect(words):
    indices = list(map(lambda x:word2index[x],filter(lambda x:x in word2index,words)))
    return np.mean(normed_weights[indices],axis=0)

reviews2vectors = list()
for review in tokens: # tokenized reviews 标记化评论
    reviews2vectors.append(make_sent_vect(review))
reviews2vectors = np.array(reviews2vectors)

def most_similar_reviews(review):
    v = make_sent_vect(review)
    scores = Counter()
    for i,val in enumerate(reviews2vectors.dot(v)):
        scores[i] = val
    most_similar = list()
    
    for idx,score in scores.most_common(3):
        most_similar.append(raw_reviews[idx][0:40])
    return most_similar

most_similar_reviews(['boring','awful'])

12.2 信息是如何存储在这些向量中的?

对词嵌入进行平均时,其平均时间能够保持。

当取一个句子中所有单词的向量平均时,这个句子最主要的意义会被保留下来,而由某些个别单词造成的噪声将会被平均掉。

12.3 神经网络是如何使用嵌入的?

神经网络检测出与目标标签具有相关性的向量

可以把词嵌入视作具有特性属性(曲线)的弯曲线条。这些曲线在训练过程中逐渐演变来达到目标。具有相似意义的单词经常以某种方式在曲线中具有同一种弯曲特征。

求取一个句子所对应的词向量的平均,会得到一个具有其中单词的特性的平均的向量。如果存在许多褒义词,最终嵌入看起来有些褒义,这些单词嵌入中包含的其他噪声一般会抵消。但要注意,这个方法容易变坏:给定足够多的单词后,它们所对应的不同曲线平均到一起后一般会成为一条直线。

缺点:将任意长度序列(句子)的信息存储为一个固定长度的向量时,如果你想存储的信息太多,最终的句子向量(大量词向量的平均)会平均成一条直线(元素都接近0的向量)。

12.4 词袋向量的局限

如果你对词嵌入进行平均,那么结果与单词顺序无关。

对词嵌入求和或求平均以形成一个短语或句子的嵌入的方法,传统上叫做词袋法,因为它很像把一堆单词扔进袋子里,不会保持他们的顺序。

需要寻求另外一种和单词顺序相关的形成句子向量的方法。且顺序相关的方式(或者叫顺序改变向量的模式)应该是通过学习得到的。

递归神经网络(RNN)

12.5 用单位向量求词嵌入之和

单位矩阵是唯一能够在向量-矩阵乘法中保证返回相同向量的矩阵。

如果使用另一个不同的矩阵会怎样?

12.6 学习转移矩阵

如果你允许单位矩阵发生变化来最小化损失会怎样?

如果我们使用与单位矩阵类似的方式生成句子向量,不过将单位矩阵换成一个不同的矩阵,使得生成的句子向量将因单词的不同顺序而不同。

12.7 学习创建有用的句子向量

创建句子向量做出预测,并通过它的各部分修改句子向量。

两部分组成:首先构建句子向量,然后使用向量预测接下来是哪个单词

通过允许(初始为单位矩阵的)矩阵变化(使其变成非单位矩阵),可以让神经网络学到如何创建这个矩阵,使得单词出现的顺序能够改变句子向量。但是,这种变化不是任意的。这个网络会以对预测下一个单词有用的方式,学习如何将单词顺序融合到句子向量中。这样一来,网络就学到如何融合比词向量之和更多的信息。

网络在一次转移中学到的任何逻辑都会被再次用到下一次转移,也就是说,只允许对每一步预测都有用的逻辑被网络学习。

12.8 python 下的前向传播

import numpy as np

def softmax(x_):
    x = np.atleast_2d(x_)
    temp = np.exp(x)
    return temp / np.sum(temp, axis=1, keepdims=True)

word_vects = {}
word_vects['yankees'] = np.array([[0.,0.,0.]])
word_vects['bears'] = np.array([[0.,0.,0.]])
word_vects['braves'] = np.array([[0.,0.,0.]])
word_vects['red'] = np.array([[0.,0.,0.]])
word_vects['socks'] = np.array([[0.,0.,0.]])
word_vects['lose'] = np.array([[0.,0.,0.]])
word_vects['defeat'] = np.array([[0.,0.,0.]])
word_vects['beat'] = np.array([[0.,0.,0.]])
word_vects['tie'] = np.array([[0.,0.,0.]])

sent2output = np.random.rand(3,len(word_vects)) #输出分类权重的句子嵌入,分类层

identity = np.eye(3)#权重转移矩阵

layer_0 = word_vects['red']
layer_1 = layer_0.dot(identity) + word_vects['socks']
layer_2 = layer_1.dot(identity) + word_vects['defeat']

pred = softmax(layer_2.dot(sent2output))
print(pred)

12.9 反向传播

y = np.array([1,0,0,0,0,0,0,0,0]) # target one-hot vector for "yankees"

pred_delta = pred - y
layer_2_delta = pred_delta.dot(sent2output.T)
defeat_delta = layer_2_delta * 1 # can ignore the "1" like prev. chapter
layer_1_delta = layer_2_delta.dot(identity.T)
socks_delta = layer_1_delta * 1 # again... can ignore the "1"
layer_0_delta = layer_1_delta.dot(identity.T)
alpha = 0.01
word_vects['red'] -= layer_0_delta * alpha
word_vects['socks'] -= socks_delta * alpha
word_vects['defeat'] -= defeat_delta * alpha
identity -= np.outer(layer_0,layer_1_delta) * alpha
identity -= np.outer(layer_1,layer_2_delta) * alpha
sent2output -= np.outer(layer_2,pred_delta) * alpha

12.10 训练

import sys,random,math
from collections import Counter
import numpy as np

f = open('tasksv11/en/qa1_single-supporting-fact_train.txt','r')
raw = f.readlines()
f.close()

tokens = list()
for line in raw[0:1000]:
    tokens.append(line.lower().replace("\n","").split(" ")[1:])

print(tokens[0:3])

12.11 设置

首先需要创建几个有用的计数器、列表和工具函数用于预测、比较和学习过程。

#创建一个词汇列表与一个字典
vocab = set()
for sent in tokens:
    for word in sent:
        vocab.add(word)

vocab = list(vocab)

word2index = {} #字典允许你在单词的文本与下标之间来回查找。你可以使用单词的下标,来选择词向量与预测矩阵的哪一行和列对应哪个单词。
for i,word in enumerate(vocab):
    word2index[word]=i
    
def words2indices(sentence):把单词列表转换的下标的工具函数
    idx = list()
    for word in sentence:
        idx.append(word2index[word])
    return idx

def softmax(x): #用于预测下一个单词
    e_x = np.exp(x - np.max(x))
    return e_x / e_x.sum(axis=0)

np.random.seed(1)
embed_size = 10

# word embeddings
embed = (np.random.rand(len(vocab),embed_size) - 0.5) * 0.1

# embedding -> embedding (initially the identity matrix)
recurrent = np.eye(embed_size)

# sentence embedding for empty sentence
start = np.zeros(embed_size)

# embedding -> output weights
decoder = (np.random.rand(embed_size, len(vocab)) - 0.5) * 0.1

# one hot lookups (for loss function)
one_hot = np.eye(len(vocab))

为了得到一致的结果,以下代码首先初始化了随机数种子,然后把向量大小设置为10,现在,你可以开始创建向量嵌入矩阵embed、递归向量recurrent和初始的start向量。这个嵌入向量建模了空白短语,对于网络建模句子倾向于以什么方式开始非常关键。最后,我们设计了一个decoder(就像向量嵌入矩阵embed一样)和one_hot工具矩阵。

np.random.seed(1)
embed_size = 10

# word embeddings
embed = (np.random.rand(len(vocab),embed_size) - 0.5) * 0.1 

# embedding -> embedding (initially the identity matrix)
recurrent = np.eye(embed_size)

# sentence embedding for empty sentence
start = np.zeros(embed_size)

# embedding -> output weights
decoder = (np.random.rand(embed_size, len(vocab)) - 0.5) * 0.1

# one hot lookups (for loss function)
one_hot = np.eye(len(vocab))

12.12 任意长度的前向传播

这里,单位矩阵被替换成recurrent矩阵,后者初始化为全0元素(并会在训练中学习)。

此处,不是仅仅预测最后一个单词,还会基于由之前的单词生成的嵌入向量,在每一步做出预测(layer['pred'])。这样做会比在每次需要预测新项时,从短语的开头进行前向传播更高效。

def predict(sent):
    
    layers = list()
    layer = {}
    layer['hidden'] = start
    layers.append(layer)

    loss = 0

    # forward propagate
    preds = list()
    for target_i in range(len(sent)):

        layer = {}

        # try to predict the next term
        layer['pred'] = softmax(layers[-1]['hidden'].dot(decoder))

        loss += -np.log(layer['pred'][sent[target_i]])

        # generate the next hidden state
        layer['hidden'] = layers[-1]['hidden'].dot(recurrent) + embed[sent[target_i]]
        layers.append(layer)

    return layers, loss

如果sent的长度更大,你会做更多次前向传播,结果是,你不能像之前一样使用静态层变量。这一次,你需要根据需求不断向列表中增加新层。

12.13 任意长度的反向传播

# forward
for iter in range(30000):
    alpha = 0.001
    sent = words2indices(tokens[iter%len(tokens)][1:])
    layers,loss = predict(sent) 

    # back propagate
    for layer_idx in reversed(range(len(layers))):
        layer = layers[layer_idx]
        target = sent[layer_idx-1]

        if(layer_idx > 0):  # if not the first layer
            layer['output_delta'] = layer['pred'] - one_hot[target]
            new_hidden_delta = layer['output_delta'].dot(decoder.transpose())

            # if the last layer - don't pull from a later one becasue it doesn't exist
            if(layer_idx == len(layers)-1):
                layer['hidden_delta'] = new_hidden_delta
            else:
                layer['hidden_delta'] = new_hidden_delta + layers[layer_idx+1]['hidden_delta'].dot(recurrent.transpose())
        else: # if the first layer
            layer['hidden_delta'] = layers[layer_idx+1]['hidden_delta'].dot(recurrent.transpose())

12.14 任意长度的权重更新

# forward
for iter in range(30000): #进行30000次迭代训练
    alpha = 0.001         #设置学习率,用于控制权重更新的幅度
    sent = words2indices(tokens[iter%len(tokens)][1:]) #将当前句子的单词转换为索引,tokens是所有句子的集合,iter % len(tokens) 确保循环使用不同的句子。
 
    layers,loss = predict(sent)  #通过前向传播计算当前输入的输出(layers)和损失(loss)
 
    # back propagate
    for layer_idx in reversed(range(len(layers))): #从最后一层开始,逐层向前计算梯度
        layer = layers[layer_idx] #获取当前层
        target = sent[layer_idx-1] #获取目标值的索引
 
        if(layer_idx > 0):
            layer['output_delta'] = layer['pred'] - one_hot[target] #计算输出误差,表示预测值与真实值之间的差距
            new_hidden_delta = layer['output_delta'].dot(decoder.transpose()) #根据输出误差计算新的隐含误差
 
            # if the last layer - don't pull from a 
            # later one becasue it doesn't exist
            if(layer_idx == len(layers)-1):
                layer['hidden_delta'] = new_hidden_delta #如果是最后一层,直接使用计算得到的隐含误差
            else:
                layer['hidden_delta'] = new_hidden_delta + layers[layer_idx+1]['hidden_delta'].dot(recurrent.transpose()) #如果不是最后一层,累计来自下一层的隐含误差
        else:
            layer['hidden_delta'] = layers[layer_idx+1]['hidden_delta'].dot(recurrent.transpose()) #如果是第一层,直接使用下一层隐含误差
 
    # update weights
    start -= layers[0]['hidden_delta'] * alpha / float(len(sent)) #更新起始权重
    for layer_idx,layer in enumerate(layers[1:]): 
        
        decoder -= np.outer(layers[layer_idx]['hidden'], layer['output_delta']) * alpha / float(len(sent))# np.outer 是 NumPy 中的一个函数,用于计算两个向量的外积。
                                                                                                        #layers[layer_idx]['hidden']:表示当前层的隐藏状态(激活值)
                                                                                                        #layer['output_delta']:表示当前层的输出误差,反映了模型预测与真实值之间的差距。
                                                                                                        #这一步计算出一个矩阵,表示当前层隐藏状态与输出误差的关系,得到的矩阵可以用于更新解码器的权重
                                                                                                        # alpha 是学习率,控制权重更新的步幅。乘以学习率后,确保权重更新的幅度不会过大。
                                                                                                        # len(sent) 是当前输入句子的长度,使用句子长度进行归一化。这确保了权重更新与句子长度无关,避免在长句子和短句子中产生不一致的更新效果。
        
        embed_idx = sent[layer_idx] # 获取当前句子的嵌入索引并更新嵌入层权重
        embed[embed_idx] -= layers[layer_idx]['hidden_delta'] * alpha / float(len(sent)) # 从当前的嵌入权重中减去计算得到的更新值。这意味着将新计算的误差调整应用到该嵌入向量上。
                                                                                         # layers[layer_idx]['hidden_delta'] 是当前层的隐含误差(gradient),表示在反向传播过程中计算得到的该层对损失函数的影响程度。这反映了当前隐藏状态对模型输出的贡献。
                                                                                         # alpha 是学习率,控制权重更新的幅度。这是为了确保更新不是过大,以免导致训练不稳定。
                                                                                         # len(sent) 是当前输入句子的长度。通过句子长度进行归一化,确保更新与句子长度无关,这样在处理不同长度的句子时,不会造成更新幅度的不一致。
        recurrent -= np.outer(layers[layer_idx]['hidden'], layer['hidden_delta']) * alpha / float(len(sent)) # 表示用计算出的更新值减去当前的递归层权重。这意味着将新计算的误差调整应用到递归层的权重上。
                                                                                                             # recurrent 是递归层的权重矩阵,通常用于连接当前时刻的隐藏状态与下一个时刻的隐藏状态。在循环神经网络(RNN)中,这个矩阵用于捕捉序列数据中的时间依赖关系。
                                                                                                             # layers[layer_idx]['hidden'] 表示当前层的隐藏状态(激活值)。它是经过激活函数处理后的当前层的输出,通常反映了当前时刻的输入信息。
                                                                                                             # layer['hidden_delta'] 是当前层的隐含误差,表示在反向传播过程中计算得到的该层对损失函数的影响程度。这反映了该层隐藏状态对输出的贡献。
                                                                                                             # np.outer 是 NumPy 中的一个函数,用于计算两个向量的外积,结果是一个矩阵。在这里,它将当前层的隐藏状态与隐含误差的外积计算出来,得到一个矩阵,这个矩阵将用于更新递归层的权重。
                                                                                                             # alpha 是学习率,用于控制权重更新的幅度,以避免过大的更新导致训练不稳定。
                                                                                                             # len(sent) 是当前输入句子的长度。通过句子长度进行归一化,确保更新幅度与句子长度无关,避免在处理不同长度句子时产生不一致的更新效果。
        
    if(iter % 1000 == 0): #每迭代1000词print一次
        print("Perplexity:" + str(np.exp(loss/len(sent)))) # 用于定期输出模型的困惑度(perplexity)
                                                           # loss 是在前向传播中计算得到的损失值。这个损失值通常是模型预测输出与真实标签之间的差距。
                                                           # len(sent) 是当前输入句子的长度,将损失值进行归一化,确保困惑度与句子长度无关。
                                                           # np.exp(...) 是 NumPy 的指数函数,用于计算损失的指数值。困惑度的计算通常是通过对损失取指数得到的。
                                                           # 使用 print 输出困惑度的值。str(...) 是将计算出的困惑度转换为字符串,以便能够在输出中显示。

本章小结

递归神经网络能够在任意长度的序列上做预测。

神经网络是如何把一堆变长信息放入一个定长的盒子里的?

事实上,句子向量没有编码句子的所有内容。递归神经网络的主要目的不仅在于这些向量记住了什么,也在于它们忘记了什么。在预测下一个单词的例子中,大多数RNN学到了最近的几个单词是必要的,以及学会了忘记更远的单词历史。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值