欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页  >  IT编程

理解RNN_恐龙岛

程序员文章站 2022-03-16 10:14:14
n_x,n_y:对一个样本而言,在某一个时间步输入输出向量的维数na:对一个输入样本而言,在一个时间步隐藏状态的数目现在训练由字母生成合适的单词(恐龙命名):X为训练样本中某个单词的字母列表,如X[a,b,c,z],正式处理前需要将其转换为独热码X[[0000],[10000],[01000],[001000],[000001]] (即:X[None,1,2,3,26])X左移生成Y – 整数列表,与X完全相同,但向左移动了一个索引。初始化a-1等参数之后,现在通过for循环将X[0]、X[...

https://blog.csdn.net/u013733326/article/details/80890454

n_x,n_y:对一个样本而言,在某一个时间步输入输出向量的维数
na:对一个输入样本而言,在一个时间步隐藏状态的数目

现在训练由字母生成合适的单词(恐龙命名):
X为训练样本中某个单词的字母列表,如X[a,b,c,z],
正式处理前需要将其转换为独热码X[[0000],[10000],[01000],[001000],[000001]] (即:X[None,1,2,3,26])
X左移生成Y – 整数列表,与X完全相同,但向左移动了一个索引。
初始化a-1等参数之后,现在通过for循环将X[0]、X[1]、X[2]等依次喂进网络的每一个时间步,

所以此时的基本思路为,用xt预测x[t+1],x[t+1]即为y[t],当把27维的x[t]喂进网络时,得到一个27维的独热码,
此独热码即为y_hat[t],y_hat[t]与y[t]做损失计算,即为y_hat[t]与x[t+1]做损失计算。

但注意X长短不同,所以事先初始化的网络并没有规定网络的层数,到底前向传播多少个时间步是由某一个单词的字母个数决定的。
当单词的每一个字母被遍历一遍之后,时间步传播结束,继而喂入下一个单词

最后进行预测采样时候,是以 预测得到的27维向量y[t] 作为概率,依照此概率在1-27中取数字
假设取到2,那么就是[0,1,0,0,0,0,…]的独热码,译码之后得到对应的字母
注意预测时,时间步的结束是以"\n"或输出了50个字母作为标志的

import numpy as np
import random
import time
import cllm_utils

# n_x,n_y:对一个样本而言,在某一个时间步输入输出向量的维数
# na:对一个输入样本而言,在一个时间步隐藏状态的数目

# 现在训练由字母生成合适的单词(恐龙命名):
# X为训练样本中某个单词的字母列表,如X[a,b,c,z],
# 正式处理前需要将其转换为独热码X[[0000],[10000],[01000],[001000],[000001]]   (即:X[None,1,2,3,26])
# X左移生成Y -- 整数列表,与X完全相同,但向左移动了一个索引。
# 初始化a[-1](最开始的隐藏层激活值)等参数之后,现在通过for循环将X[0]、X[1]、X[2]等依次喂进网络的每一个时间步,

# 所以此时的基本思路为,用x[t](已转换为独热码)预测x[t+1],x[t+1]即为y[t],当把27维的x[t]喂进网络时,得到一个27维的独热码,
# 此独热码即为y_hat[t],y_hat[t]与y[t]做损失计算,即为y_hat[t]与x[t+1]做损失计算。

# 但注意X长短不同,所以事先初始化的网络并没有规定网络的层数,到底前向传播多少个时间步是由某一个单词的字母个数决定的。
# 当单词的每一个字母被遍历一遍之后,时间步传播结束,继而喂入下一个单词

# 最后进行预测采样时候,是以 预测得到的27维向量y[t] 作为概率,依照此概率在1-27中取数字
# 假设取到2,那么就是[0,1,0,0,0,0,...]的独热码,译码之后得到对应的字母
# 注意预测时,时间步的结束是以"\n"或输出了50个字母作为标志的


def clip(gradients, maxValue):
    """
    使用maxValue来修剪梯度

    参数:
        gradients -- 字典类型,包含了以下参数:"dWaa", "dWax", "dWya", "db", "dby"
        maxValue -- 阈值,把梯度值限制在[-maxValue, maxValue]内

    返回:
        gradients -- 修剪后的梯度
    """
    # 获取参数
    dWaa, dWax, dWya, db, dby = gradients['dWaa'], gradients['dWax'], gradients['dWya'], gradients['db'], gradients[
        'dby']

    # 梯度修剪
    for gradient in [dWaa, dWax, dWya, db, dby]:
        np.clip(gradient, -maxValue, maxValue, out=gradient)

    gradients = {"dWaa": dWaa, "dWax": dWax, "dWya": dWya, "db": db, "dby": dby}

    return gradients


def sample(parameters, char_to_is, seed):
    """
    根据RNN输出的概率分布序列对字符序列进行采样

    参数:
        parameters -- 包含了Waa, Wax, Wya, by, b的字典
        char_to_ix -- 字符映射到索引的字典
        seed -- 随机种子

    返回:
        indices -- 包含采样字符索引的长度为n的列表。
    """

    # 从parameters 中获取参数
    Waa, Wax, Wya, by, b = parameters['Waa'], parameters['Wax'], parameters['Wya'], parameters['by'], parameters['b']
    vocab_size = by.shape[0]
    n_a = Waa.shape[1]

    # 步骤1
    ## 创建独热向量x
    x = np.zeros((vocab_size, 1))

    ## 使用0初始化a_prev
    a_prev = np.zeros((n_a, 1))

    # 创建索引的空列表,这是包含要生成的字符的索引的列表。
    indices = []

    # IDX是检测换行符的标志,我们将其初始化为-1。
    idx = -1

    # 循环遍历时间步t。在每个时间步中,从概率分布中抽取一个字符,
    # 并将其索引附加到“indices”上,如果我们达到50个字符,
    # (我们应该不太可能有一个训练好的模型),我们将停止循环,这有助于调试并防止进入无限循环
    counter = 0
    newline_character = char_to_ix["\n"]

    while (idx != newline_character and counter < 50):
        # 步骤2:使用公式1、2、3进行前向传播
        a = np.tanh(np.dot(Wax, x) + np.dot(Waa, a_prev) + b)
        z = np.dot(Wya, a) + by
        y = cllm_utils.softmax(z)

        # 设定随机种子
        np.random.seed(counter + seed)

        # 步骤3:从概率分布y中抽取词汇表中字符的索引
        idx = np.random.choice(list(range(vocab_size)), p=y.ravel())

        # 添加到索引中
        indices.append(idx)

        # 步骤4:将输入字符重写为与采样索引对应的字符。
        x = np.zeros((vocab_size, 1))
        x[idx] = 1

        # 更新a_prev为a
        a_prev = a

        # 累加器
        seed += 1
        counter += 1

    if (counter == 50):
        indices.append(char_to_ix["\n"])

    return indices


def optimize(X, Y, a_prev, parameters, learning_rate=0.01):
    """
    执行训练模型的单步优化。

    参数:
        X -- 整数列表,其中每个整数映射到词汇表中的字符。
        Y -- 整数列表,与X完全相同,但向左移动了一个索引。
        a_prev -- 上一个隐藏状态
        parameters -- 字典,包含了以下参数:
                        Wax -- 权重矩阵乘以输入,维度为(n_a, n_x)
                        Waa -- 权重矩阵乘以隐藏状态,维度为(n_a, n_a)
                        Wya -- 隐藏状态与输出相关的权重矩阵,维度为(n_y, n_a)
                        b -- 偏置,维度为(n_a, 1)
                        by -- 隐藏状态与输出相关的权重偏置,维度为(n_y, 1)
        learning_rate -- 模型学习的速率

    返回:
        loss -- 损失函数的值(交叉熵损失)
        gradients -- 字典,包含了以下参数:
                        dWax -- 输入到隐藏的权值的梯度,维度为(n_a, n_x)
                        dWaa -- 隐藏到隐藏的权值的梯度,维度为(n_a, n_a)
                        dWya -- 隐藏到输出的权值的梯度,维度为(n_y, n_a)
                        db -- 偏置的梯度,维度为(n_a, 1)
                        dby -- 输出偏置向量的梯度,维度为(n_y, 1)
        a[len(X)-1] -- 最后的隐藏状态,维度为(n_a, 1)
    """
    # X = {list: 12}[None, 20, 21, 18, 9, 1, 19, 1, 21, 18, 21, 19]
    # Y = {list: 12}[20, 21, 18, 9, 1, 19, 1, 21, 18, 21, 19, 0]
    # 前向传播
    loss, cache = cllm_utils.rnn_forward(X, Y, a_prev, parameters)

    # 反向传播
    gradients, a = cllm_utils.rnn_backward(X, Y, parameters, cache)

    # 梯度修剪,[-5 , 5]
    gradients = clip(gradients, 5)

    # 更新参数
    parameters = cllm_utils.update_parameters(parameters, gradients, learning_rate)

    return loss, gradients, a[len(X) - 1]




# model(data, ix_to_char, char_to_ix, num_iterations=3500)
def model(data, ix_to_char, char_to_ix, num_iterations=3500,
          n_a=50, dino_names=7, vocab_size=27):
    """
    训练模型并生成恐龙名字

    参数:
        data -- 语料库
        ix_to_char -- 索引映射字符字典
        char_to_ix -- 字符映射索引字典
        num_iterations -- 迭代次数
        n_a -- RNN单元数量
        dino_names -- 每次迭代中采样的数量
        vocab_size -- 在文本中的唯一字符的数量

    返回:
        parameters -- 学习后了的参数
    """

    # 从vocab_size中获取n_x、n_y
    n_x, n_y = vocab_size, vocab_size # n_x,n_y都为27 n_x,n_y对一个样本而言,在时间步输入的向量维数

    # 初始化参数
    parameters = cllm_utils.initialize_parameters(n_a, n_x, n_y)

    # 初始化损失
    loss = cllm_utils.get_initial_loss(vocab_size, dino_names)

    # 构建恐龙名称列表
    with open("dinos.txt") as f:
        examples = f.readlines()
    examples = [x.lower().strip() for x in examples]
    #此时的examples变成为列表['aachenosaurus', 'aardonyx', 'abdallahsaurus', 'abelisaurus', 'abrictosaurus', ...
    # 打乱全部的恐龙名称
    np.random.seed(0)
    np.random.shuffle(examples)

    # 初始化LSTM隐藏状态
    a_prev = np.zeros((n_a, 1))

    # 循环
    for j in range(num_iterations):
        # 定义一个训练样本
        index = j % len(examples)
        X = [None] + [char_to_ix[ch] for ch in examples[index]] # examples[0]为turiasaurus X变为对应字母的索引[None,20,21,.....]
        Y = X[1:] + [char_to_ix["\n"]]
        #Y是在a的基础上去掉none并加回车(对应索引0)
        # 执行单步优化:前向传播 -> 反向传播 -> 梯度修剪 -> 更新参数
        # 选择学习率为0.01
        # X = {list: 12}[None, 20, 21, 18, 9, 1, 19, 1, 21, 18, 21, 19]
        # Y = {list: 12}[20, 21, 18, 9, 1, 19, 1, 21, 18, 21, 19, 0]
        curr_loss, gradients, a_prev = optimize(X, Y, a_prev, parameters)

        # 使用延迟来保持损失平滑,这是为了加速训练。
        loss = cllm_utils.smooth(loss, curr_loss)

        # 每2000次迭代,通过sample()生成“\n”字符,检查模型是否学习正确
        if j % 2000 == 0:
            print("第" + str(j + 1) + "次迭代,损失值为:" + str(loss))

            seed = 0
            for name in range(dino_names):
                # 采样
                sampled_indices = sample(parameters, char_to_ix, seed)
                cllm_utils.print_sample(sampled_indices, ix_to_char)

                # 为了得到相同的效果,随机种子+1
                seed += 1

            print("\n")
    return parameters



# 获取名称
data = open("dinos.txt", "r").read()

# 转化为小写字符
data = data.lower()

# 转化为无序且不重复的元素列表
chars = list(set(data))

# 获取大小信息
data_size, vocab_size = len(data), len(chars)

print(chars)
print("共计有%d个字符,唯一字符有%d个"%(data_size,vocab_size))
#  这些字符是a-z(26个英文字符)加上“\n”(换行字符),在这里换行字符起到了在视频中类似的EOS(句子结尾)的作用,
# 这里表示了名字的结束而不是句子的结尾。下面我们将创建一个字典,每个字符映射到0-26的索引,然后再创建一个字典,
# 它将该字典将每个索引映射回相应的字符字符,它会帮助我们找出softmax层的概率分布输出中的字符。我们来创建char_to_ix 与
# ix_to_char字典。

char_to_ix = {ch:i for i, ch in enumerate(sorted(chars))}#创建一个字典,每个字符映射到0-26的索引
ix_to_char = {i:ch for i, ch in enumerate(sorted(chars))}#将每个索引映射回相应的字符

print(char_to_ix)
print(ix_to_char)

#开始时间
start_time = time.clock()

#开始训练
parameters = model(data, ix_to_char, char_to_ix, num_iterations=3500)

#结束时间
end_time = time.clock()

#计算时差
minium = end_time - start_time

print("执行了:" + str(int(minium / 60)) + "分" + str(int(minium%60)) + "秒")


import numpy as np

def softmax(x):
    e_x = np.exp(x - np.max(x))
    return e_x / e_x.sum(axis=0)

def smooth(loss, cur_loss):
    return loss * 0.999 + cur_loss * 0.001

def print_sample(sample_ix, ix_to_char):
    txt = ''.join(ix_to_char[ix] for ix in sample_ix)
    txt = txt[0].upper() + txt[1:]  # capitalize first character 
    print ('%s' % (txt, ), end='')

def get_initial_loss(vocab_size, seq_length):
    return -np.log(1.0/vocab_size)*seq_length

def softmax(x):
    e_x = np.exp(x - np.max(x))
    return e_x / e_x.sum(axis=0)

def initialize_parameters(n_a, n_x, n_y):
    """
    Initialize parameters with small random values
    
    Returns:
    parameters -- python dictionary containing:
                        Wax -- Weight matrix multiplying the input, numpy array of shape (n_a, n_x)
                        Waa -- Weight matrix multiplying the hidden state, numpy array of shape (n_a, n_a)
                        Wya -- Weight matrix relating the hidden-state to the output, numpy array of shape (n_y, n_a)
                        b --  Bias, numpy array of shape (n_a, 1)
                        by -- Bias relating the hidden-state to the output, numpy array of shape (n_y, 1)
    """
    np.random.seed(1)
    Wax = np.random.randn(n_a, n_x)*0.01 # input to hidden
    Waa = np.random.randn(n_a, n_a)*0.01 # hidden to hidden
    Wya = np.random.randn(n_y, n_a)*0.01 # hidden to output
    b = np.zeros((n_a, 1)) # hidden bias
    by = np.zeros((n_y, 1)) # output bias
    
    parameters = {"Wax": Wax, "Waa": Waa, "Wya": Wya, "b": b,"by": by}
    
    return parameters

def rnn_step_forward(parameters, a_prev, x):
    #最开始传入 参数、a[-1](最开始初始化的)、None
    Waa, Wax, Wya, by, b = parameters['Waa'], parameters['Wax'], parameters['Wya'], parameters['by'], parameters['b']
    a_next = np.tanh(np.dot(Wax, x) + np.dot(Waa, a_prev) + b) # hidden state
    p_t = softmax(np.dot(Wya, a_next) + by) # unnormalized log probabilities for next chars # probabilities for next chars 
    
    return a_next, p_t

def rnn_step_backward(dy, gradients, parameters, x, a, a_prev):
    
    gradients['dWya'] += np.dot(dy, a.T)
    gradients['dby'] += dy
    da = np.dot(parameters['Wya'].T, dy) + gradients['da_next'] # backprop into h
    daraw = (1 - a * a) * da # backprop through tanh nonlinearity
    gradients['db'] += daraw
    gradients['dWax'] += np.dot(daraw, x.T)
    gradients['dWaa'] += np.dot(daraw, a_prev.T)
    gradients['da_next'] = np.dot(parameters['Waa'].T, daraw)
    return gradients

def update_parameters(parameters, gradients, lr):

    parameters['Wax'] += -lr * gradients['dWax']
    parameters['Waa'] += -lr * gradients['dWaa']
    parameters['Wya'] += -lr * gradients['dWya']
    parameters['b']  += -lr * gradients['db']
    parameters['by']  += -lr * gradients['dby']
    return parameters

def rnn_forward(X, Y, a0, parameters, vocab_size = 27):
    # X = {list: 12}[None, 20, 21, 18, 9, 1, 19, 1, 21, 18, 21, 19]
    # Y = {list: 12}[20, 21, 18, 9, 1, 19, 1, 21, 18, 21, 19, 0]
    # Initialize x, a and y_hat as empty dictionaries
    x, a, y_hat = {}, {}, {}
    
    a[-1] = np.copy(a0)#{-1:array([[0][0][0]...])} 字典哦
    
    # initialize your loss to 0
    loss = 0
    
    for t in range(len(X)):
        
        # Set x[t] to be the one-hot vector representation of the t'th character in X.
        # if X[t] == None, we just have x[t]=0. This is used to set the input for the first timestep to the zero vector. 
        x[t] = np.zeros((vocab_size,1)) 
        if (X[t] != None):
            x[t][X[t]] = 1#x[t]为独热码,当X(t)为none时,其为000..000,否则为001000000..00
        
        # Run one step forward of the RNN
        a[t], y_hat[t] = rnn_step_forward(parameters, a[t-1], x[t])#第一次 传入参数、a[-1](最开始初始化的)、None 返回a_next, p_t
        #单步返回的y_hat是27行1列的array,但其放在一个字典里
        #{0: array([[0.03703704],
        # [0.03703704],
        # ...27行
        #[0.03703704]])}
        # Update the loss by substracting the cross-entropy term of this time-step from it.
        loss -= np.log(y_hat[t][Y[t],0]) #t=0 Y[0]=20,此时为y_hat[0]的第20行,0列
        #注意Y -- 整数列表,与X完全相同,但向左移动了一个索引。
        #所以此时的基本思路为,用x[t](已转换为独热码)预测x[t+1],x[t+1]即为y[t],当把27维的x[t]喂进网络时,得到一个27维的独热码,
        # 此独热码即为y_hat[t],y_hat[t]与y[t]做损失计算,即为y_hat[t]与x[t+1]做损失计算。
    cache = (y_hat, a, x)
        
    return loss, cache

def rnn_backward(X, Y, parameters, cache):
    # Initialize gradients as an empty dictionary
    gradients = {}
    
    # Retrieve from cache and parameters
    (y_hat, a, x) = cache
    Waa, Wax, Wya, by, b = parameters['Waa'], parameters['Wax'], parameters['Wya'], parameters['by'], parameters['b']
    
    # each one should be initialized to zeros of the same dimension as its corresponding parameter
    gradients['dWax'], gradients['dWaa'], gradients['dWya'] = np.zeros_like(Wax), np.zeros_like(Waa), np.zeros_like(Wya)
    gradients['db'], gradients['dby'] = np.zeros_like(b), np.zeros_like(by)
    gradients['da_next'] = np.zeros_like(a[0])
    
    ### START CODE HERE ###
    # Backpropagate through time
    for t in reversed(range(len(X))):
        dy = np.copy(y_hat[t])
        dy[Y[t]] -= 1
        gradients = rnn_step_backward(dy, gradients, parameters, x[t], a[t], a[t-1])
    ### END CODE HERE ###
    
    return gradients, a


本文地址:https://blog.csdn.net/qq_43296197/article/details/107658693