【机器学习小记】【搭建循环神经网络及其应用】deeplearning.ai course5 1st week programming(keras)

参考自:
【中文】【吴恩达课后编程作业】Course 5 - 序列模型 - 第一周作业

流程总结

RNN前向传播/训练过程怎么样的?

请添加图片描述

  • 所谓多个时间步t,并不需要构建多个RNN神经元,只需要利用循环实现即可。

在训练时,我们拥有两个数据(序列) x , y x,y x,y

对于每个样本 x ( i ) x^{(i)} x(i),都满足:

  1. x < 1 > = 0 ⃗ x^{<1>} = \vec{0} x<1>=0
  2. y ( i ) < t > = x ( i ) < t + 1 > y^{(i)<t>} = x^{(i)<t+1>} y(i)<t>=x(i)<t+1>
  3. y ( i ) < T x > = E O S y^{(i)<T_x>} = EOS y(i)<Tx>=EOS T x T_x Tx表示最后一个时间步

这样,每个 y ( i ) < t > y^{(i)<t>} y(i)<t>都是 x ( i ) < t > x^{(i)<t>} x(i)<t>左移一个单位的版本,即 x ( i ) = { 0 , a , b , . . . } , y ( i ) = { a , b , . . . , E O S } x^{(i)} = \{0,a,b,...\},y^{(i)}=\{a,b,...,EOS\} x(i)={ 0,a,b,...},y(i)={ a,b,...,EOS}

也就是说,

  • 第一个神经元 n e u 1 neu_1 neu1,预测输出的是第一个字符 c 1 c_1 c1
  • 最后一个神经元 n e u T x neu_{T_x} neuTx,预测输出 E O S EOS EOS

  1. 传入 x < 1 > = 0 ⃗ , a < 0 > = 0 ⃗ x^{<1>} = \vec{0}, a^{<0>}=\vec{0} x<1>=0 ,a<0>=0 让其预测第一个字符 y ^ < 1 > \hat{y}^{<1>} y^<1>
  2. y ^ < 1 > \hat{y}^{<1>} y^<1> y < 1 > y^{<1>} y<1>计算得到一个损失 l o s s 1 loss_1 loss1
  3. 依次类推,计算 l o s s = l o s s 1 + l o s s 2 + . . . + l o s s T x loss = loss_1 + loss_2 + ... + loss_{T_x} loss=loss1+loss2+...+lossTx之后相加。
  4. 反向传播,得到梯度gradients
  5. 进行梯度修剪clip,将梯度固定在一定的范围内。因为RNN可能出现梯度爆炸的情况。
  6. 更新参数。
    请添加图片描述

采样sample()的过程是怎么样的?

请添加图片描述

  1. 传入 x < 1 > = 0 ⃗ , a < 0 > = 0 ⃗ x^{<1>} = \vec{0}, a^{<0>}=\vec{0} x<1>=0 ,a<0>=0 让其预测第一个字符 y ^ < 1 > \hat{y}^{<1>} y^<1>
  2. 在采样sample中, y ^ < t > \hat{y}^{<t>} y^<t>softmax的结果,是概率分布。利用np.random.choice根据概率,随机选择一个值(索引),作为结果传到下一个 x < t > x^{<t>} x<t>
  3. x < 2 > = y ^ < 1 > x^{<2>} = \hat{y}^{<1>} x<2>=y^<1>(采样后),以此类推 x < t > = y ^ < t − 1 > x^{<t>} = \hat{y}^{<t-1>} x<t>=y^<t1>这样意味着,输出 y ^ < t > \hat{y}^{<t>} y^<t>代替了真值 y < t > y^{<t>} y<t>进入到下一个时间步。
  4. 时间步不断循环,直到神经单元输出 y ^ < t > = E O S \hat{y}^{<t>}=EOS y^<t>=EOS或者达到循环上限,停止循环,得到输出序列 Y = { y ^ < 1 > , y ^ < 2 > , . . . , y ^ < t > } Y=\{ \hat{y}^{<1>},\hat{y}^{<2>},...,\hat{y}^{<t>} \} Y={ y^<1>,y^<2>,...,y^<t>}即为结果。

注: x < t > x^{<t>} x<t>one_hot向量 y ^ < t > \hat{y}^{<t>} y^<t>概率分布

遇到的一些问题

np.random.choice

python,numpy中np.random.choice()的用法详解及其参考代码

np.random.choice(
    a, #一维数组
    size=None, # 组成size大小的数组
    replace=True, #True表示可以取相同数字(有放回),False表示不可以取相同数字
    p=None #与数组a相对应,表示取数组a中每个元素的概率,默认为选取每个元素的概率相同。
)

numpy中的ravel() 和 flatten()

Numpy中扁平化函数ravel()和flatten()的区别

功能:都是讲数组扁平化(一维化),但是在使用过程中flatten()分配了新的内存,但ravel()返回的是一个数组的视图(在修改了ravel()返回的数组之后,原数组会发生改变)

python中的strip()

python中的strip()

strip() 方法用于移除字符串头尾指定的字符(默认为空格)

AttributeError: module ‘time’ has no attribute ‘clock’

time.clock()换成time.perf_counter(),其他的还是一样。

IPython无法播放音乐

由于在VScode环境下,IPython无法播放音乐

解决办法:Python播放音乐的五种方法

import os
# 这里需要 把【文件路径】放入【变量file】中
file = r".\\data\\30s_seq.mp3"
# 调用 播放软件,相当于双击文件= =
os.system(file)

python Lambda表达式

python之lambda表达式
Python学习系列之lambda表达式

形式:
lambda [arg1 [,arg2,.....argn]]:expression

返回lambda表达式中最后一个表达式的值。

如:

sum = lambda a,b : a+b
sum(1,3) # 4

等同于

def sum(a,b):
    return a+b
sum(1,3)

keras.layers.Lambda

Wraps arbitrary expressions(包装任意表达式)
把任意函数包装层Layer,之后就可以放入Model中了

以下为官方例子:

  # add a x -> x^2 layer
  model.add(Lambda(lambda x: x ** 2))

实验部分

如有特别(与原文不同),会在代码开头额外注明。


1. RNN前向传播

import numpy as np
import rnn_utils
import os

# 不使用GPU
os.environ["CUDA_VISIBLE_DEVICES"] = "-1"

# 一共m个样本
def rnn_cell_forward(xt, a_prev, parameters):
    """
    实现RNN单元的单步前向传播

    参数:
        xt -- 时间步t 输入的数据(n_x,m)
        a_prev -- 时间步t-1 的隐藏状态
        parameters -- 参数字典,包括:
            Wax -- 矩阵,与输入xt相乘(n_a,n_x)
            Waa -- 矩阵,与隐藏状态a_prev相乘(n_a,n_a)
            Wya -- 矩阵,与输出a_next相乘(n_y,n_a)
            ba -- 偏置,(n_a,1)
            by -- 偏置,(n_y,1)
    返回:
        a_next -- 下一个隐藏状态(n_a,m)
        yt_pred -- 时间步t的预测(n_y,m)
        cache -- 反向传播需要的元组,包含(a_next, a_prev, xt, parameters)
    """
    # 从parameters中获得参数
    Wax = parameters["Wax"]
    Waa = parameters["Waa"]
    Wya = parameters["Wya"]
    ba = parameters["ba"]
    by = parameters["by"]

    # 计算下一个隐藏状态a_next
    a_next = np.tanh(np.dot(Waa,a_prev) + np.dot(Wax,xt) + ba)

    # 计算输出yt_pred
    yt_pred = rnn_utils.softmax(np.dot(Wya,a_next) + by)

    # 保存反向传播需要的值
    cache = (a_next,a_prev,xt,parameters)

    return a_next,yt_pred,cache

# 测试
# np.random.seed(1)
# xt = np.random.randn(3,10)
# a_prev = np.random.randn(5,10)
# Waa = np.random.randn(5,5)
# Wax = np.random.randn(5,3)
# Wya = np.random.randn(2,5)
# ba = np.random.randn(5,1)
# by = np.random.randn(2,1)
# parameters = {"Waa": Waa, "Wax": Wax, "Wya": Wya, "ba": ba, "by": by}

# a_next, yt_pred, cache = rnn_cell_forward(xt, a_prev, parameters)
# print("a_next[4] = ", a_next[4])
# print("a_next.shape = ", a_next.shape)
# print("yt_pred[1] =", yt_pred[1])
# print("yt_pred.shape = ", yt_pred.shape)

def rnn_forward(x,a0,parameters):
    """
    实现RNN的前向传播

    参数:
        x -- 输入的全部数据(n_x,m,T_x)
        a0 -- 初始化隐藏状态(n_a,m)
        parameters -- 参数字典,包含:
            Wax -- 矩阵,输入乘以权重,维度为(n_a, n_x)
            Waa -- 矩阵,隐藏状态乘以权重,维度为(n_a, n_a)
            Wya -- 矩阵,隐藏状态与输出相关的权重矩阵,维度为(n_y, n_a)
            ba  -- 偏置,维度为(n_a, 1)
            by  -- 偏置,隐藏状态与输出相关的偏置,维度为(n_y, 1)
    
    返回:
        a -- 所有时间步的隐藏状态(n_a,m,T_x)
        y_pred -- 所有时间步的预测(n_y,m,T_x)
        caches -- 为反向传播保存的元组([列表类型]cache,x)
    """
    # 初始化caches
    caches = []

    # 获取x与Wya的维度信息
    n_x, m ,T_x = x.shape
    n_y, n_a = parameters["Wya"].shape

    # 使用0初始化a , y
    a = np.zeros([n_a,m,T_x])
    y_pred = np.zeros([n_y,m,T_x])

    # 初始化next
    a_next = a0

    # 遍历所有时间步
    for t in range(T_x):
        ## 1. 使用rnn_cell_forward更新a_next与cache
        a_next, yt_pred, cache = rnn_cell_forward(x[:,:,t],a_next,parameters)

        ## 2. 使用a来保存a_next隐藏状态(第t)个位置
        a[:,:,t] = a_next

        ## 3. 使用y来保存预测值
        y_pred[:,:,t] = yt_pred

        ## 4. 把cache保存到caches中
        caches.append(cache)
    
    # 保存反向传播需要的参数
    caches = (caches,x)

    return a, y_pred, caches

# 测试

np.random.seed(1)
x = np.random.randn(3,10,4)
a0 = np.random.randn(5,10)
Waa = np.random.randn(5,5)
Wax = np.random.randn(5,3)
Wya = np.random.randn(2,5)
ba = np.random.randn(5,1)
by = np.random.randn(2,1)
parameters = {
    
    "Waa": Waa, "Wax": Wax, "Wya": Wya, "ba": ba, "by": by}

a, y_pred, caches = rnn_forward(x, a0, parameters)
print("a[4][1] = ", a[4][1])
print("a.shape = ", a.shape)
print("y_pred[1][3] =", y_pred[1][3])
print("y_pred.shape = ", y_pred.shape)
print("caches[1][1][3] =", caches[1][1][3])
print("len(caches) = ", len(caches))


# if __name__ == "__main__":
#     main()

2. LSTM前向传播

import numpy as np
from numpy.core.function_base import add_newdoc
import rnn_utils
import os

# 不使用GPU
os.environ["CUDA_VISIBLE_DEVICES"] = "-1"

def lstm_cell_forward(xt,a_prev,c_prev,parameters):
    """
    实现LSTM单元的前向传播

    参数:
        xt -- 时间步t的输入数据(n_x,m)
        a_prev -- 时间步t-1的隐藏状态(n_a,m)
        c_prev -- 时间步t-1的记忆状态(n_a,m)
        parameters -- 参数字典,包括:
            Wf -- 遗忘门rf的权值(n_a,n_a + n_x)
            bf -- 遗忘门rf的偏置(n_a,1)
            Wi -- 更新门ru的权值(n_a,n_a + n_x)
            bi -- 更新门ru的偏置(n_a,1)
            Wc -- 保持值cct的权值(n_a,n_a + n_x)
            bc -- 保持值cct的偏置(n_a,1)
            Wo -- 输出门ro的权值(n_a,n_a + n_x)
            bo -- 输出门ro的偏置(n_a,1)
            Wy -- 隐藏状态 与 输出y的权值(n_y,n_a)
            by -- 隐藏状态 与 输出y的偏置,维度为(n_y, 1)
    
    返回:
        a_next -- 下一个隐藏状态(n_a,m)
        c_next -- 下一个记忆状态(n_a,m)
        yt_pred -- 时间步t的预测(n_y,m)
        cache -- 包含了反向传播需要的参数,包括(a_next, c_next, a_prev, c_prev, xt, parameters)
    
    注意:
        ft/it/ot表示遗忘/更新/输出门,cct表示候选值(c tilda),c表示记忆值。
    """
    # 从“parameters”中获取相关值
    Wf = parameters["Wf"]
    bf = parameters["bf"]
    Wi = parameters["Wi"]
    bi = parameters["bi"]
    Wc = parameters["Wc"]
    bc = parameters["bc"]
    Wo = parameters["Wo"]
    bo = parameters["bo"]
    Wy = parameters["Wy"]
    by = parameters["by"]

    # 获取 xt 与 Wy 的维度信息
    n_x, m = xt.shape
    n_y, n_a = Wy.shape

    # 1. 连接a_prev 与 xt
    contact = np.zeros([n_a + n_x , m])
    # [a:b] 左闭右开区间[a,b)
    contact[:n_a, :] = a_prev
    contact[n_a:, :] = xt

    # 2. 根据公式计算
    ## 遗忘门
    ft = rnn_utils.sigmoid(np.dot(Wf,contact) + bf)

    ## 更新门
    it = rnn_utils.sigmoid(np.dot(Wi,contact) + bi)

    ## 更新单元
    cct = np.tanh(np.dot(Wc,contact) + bc)

    ## 更新单元
    c_next = ft * c_prev + it * cct

    ## 输出门
    ot = rnn_utils.sigmoid(np.dot(Wo,contact) + bo)

    ## 输出门
    a_next = ot * np.tanh(c_next)

    # 3. 计算LSTM单元的预测值
    yt_pred = rnn_utils.softmax(np.dot(Wy,a_next) + by)

    # 保存包含了 反向传播需要的参数
    cache = (a_next, c_next, a_prev, c_prev, ft, it, cct, ot, xt, parameters)

    return a_next,c_next,yt_pred,cache

# 测试
# np.random.seed(1)
# xt = np.random.randn(3,10)
# a_prev = np.random.randn(5,10)
# c_prev = np.random.randn(5,10)
# Wf = np.random.randn(5, 5+3)
# bf = np.random.randn(5,1)
# Wi = np.random.randn(5, 5+3)
# bi = np.random.randn(5,1)
# Wo = np.random.randn(5, 5+3)
# bo = np.random.randn(5,1)
# Wc = np.random.randn(5, 5+3)
# bc = np.random.randn(5,1)
# Wy = np.random.randn(2,5)
# by = np.random.randn(2,1)

# parameters = {"Wf": Wf, "Wi": Wi, "Wo": Wo, "Wc": Wc, "Wy": Wy, "bf": bf, "bi": bi, "bo": bo, "bc": bc, "by": by}

# a_next, c_next, yt, cache = lstm_cell_forward(xt, a_prev, c_prev, parameters)
# print("a_next[4] = ", a_next[4])
# print("a_next.shape = ", c_next.shape)
# print("c_next[2] = ", c_next[2])
# print("c_next.shape = ", c_next.shape)
# print("yt[1] =", yt[1])
# print("yt.shape = ", yt.shape)
# print("cache[1][3] =", cache[1][3])
# print("len(cache) = ", len(cache))

def lstm_forward(x, a0, parameters):
    """
    实现LSTM单元组成的RNN
    
    参数:
        x -- 所有时间步的输入数据,维度为(n_x, m, T_x)
        a0 -- 初始化隐藏状态,维度为(n_a, m)
        parameters -- python字典,包含了以下参数:
                        Wf -- 遗忘门的权值,维度为(n_a, n_a + n_x)
                        bf -- 遗忘门的偏置,维度为(n_a, 1)
                        Wi -- 更新门的权值,维度为(n_a, n_a + n_x)
                        bi -- 更新门的偏置,维度为(n_a, 1)
                        Wc -- 第一个“tanh”的权值,维度为(n_a, n_a + n_x)
                        bc -- 第一个“tanh”的偏置,维度为(n_a, 1)
                        Wo -- 输出门的权值,维度为(n_a, n_a + n_x)
                        bo -- 输出门的偏置,维度为(n_a, 1)
                        Wy -- 隐藏状态与输出相关的权值,维度为(n_y, n_a)
                        by -- 隐藏状态与输出相关的偏置,维度为(n_y, 1)
        
    返回:
        a -- 所有时间步的隐藏状态,维度为(n_a, m, T_x)
        y -- 所有时间步的预测值,维度为(n_y, m, T_x)
        caches -- 为反向传播的保存的元组,维度为(【列表类型】cache, x))
    """

    # 初始化caches
    caches = []

    # 获取xt 与 Wy 的维度信息
    n_x, m, T_x = x.shape
    n_y, n_a = parameters["Wy"].shape

    # 使用0来初始化a,c,y
    a = np.zeros([n_a,m,T_x])
    c = np.zeros([n_a,m,T_x])
    y = np.zeros([n_y,m,T_x])

    # 初始化a_next , c_next
    a_next = a0
    c_next = np.zeros([n_a,m])

    # 遍历所有时间步
    for t in range(T_x):
        # 更新下一个状态
        a_next, c_next, yt_pred, cache = lstm_cell_forward(x[:,:,t],a_next,c_next,parameters)

        # 保存a,y,c
        a[:,:,t] = a_next
        y[:,:,t] = yt_pred
        c[:,:,t] = c_next
        # 把cache添加到caches中
        caches.append(cache)
    caches = caches,x

    return a,y,c,caches

# 测试
np.random.seed(1)
x = np.random.randn(3,10,7)
a0 = np.random.randn(5,10)
Wf = np.random.randn(5, 5+3)
bf = np.random.randn(5,1)
Wi = np.random.randn(5, 5+3)
bi = np.random.randn(5,1)
Wo = np.random.randn(5, 5+3)
bo = np.random.randn(5,1)
Wc = np.random.randn(5, 5+3)
bc = np.random.randn(5,1)
Wy = np.random.randn(2,5)
by = np.random.randn(2,1)

parameters = {
    
    "Wf": Wf, "Wi": Wi, "Wo": Wo, "Wc": Wc, "Wy": Wy, "bf": bf, "bi": bi, "bo": bo, "bc": bc, "by": by}

a, y, c, caches = lstm_forward(x, a0, parameters)
print("a[4][3][6] = ", a[4][3][6])
print("a.shape = ", a.shape)
print("y[1][4][3] =", y[1][4][3])
print("y.shape = ", y.shape)
print("caches[1][1[1]] =", caches[1][1][1])
print("c[1][2][1]", c[1][2][1])
print("len(caches) = ", len(caches))


# if __name__ == "__main__":
#     main()

3. 字符级别语言模型-恐龙岛

# 使用数据集dinos.txt包含所有恐龙的名称
from types import LambdaType
import numpy as np
import random
import time

from numpy.core.defchararray import count
from numpy.core.fromnumeric import trace
from numpy.lib.function_base import gradient
import cllm_utils

import os
# 不使用GPU
os.environ["CUDA_VISIBLE_DEVICES"] = "-1"

os.chdir('code\\deeplearning.ai\\5-sequence_model\\1-RNN_and_App')
from shakespeare_utils import *


def get_data():
    """
        获取数据:
            1. 恐龙名称  和  数据集
            2. 创建一个 唯一字符列表,计算数据集 和 词汇量大小
        返回:
            data -- 数据集
            chars --  字符集
            char_to_ix -- 字符对应的index
            ix_to_char -- index对应的字符
    """
    # 当前路径
    # print(os.getcwd())

    # 获取名称
    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))

    # 创建字典
    # 它会帮助我们找出softmax层的概率分布输出中的字符
    char_to_ix = {
    
    ch:i for i, ch in enumerate(sorted(chars))}
    ix_to_char = {
    
    i:ch for i, ch in enumerate(sorted(chars))}

    print(char_to_ix)
    print(ix_to_char)

    return data,chars,data_size,vocab_size,char_to_ix,ix_to_char

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

# 测试
# np.random.seed(3)
# dWax = np.random.randn(5,3)*10
# dWaa = np.random.randn(5,5)*10
# dWya = np.random.randn(2,5)*10
# db = np.random.randn(5,1)*10
# dby = np.random.randn(2,1)*10
# gradients = {"dWax": dWax, "dWaa": dWaa, "dWya": dWya, "db": db, "dby": dby}
# gradients = clip(gradients, 10)
# print("gradients[\"dWaa\"][1][2] =", gradients["dWaa"][1][2])
# print("gradients[\"dWax\"][3][1] =", gradients["dWax"][3][1])
# print("gradients[\"dWya\"][1][2] =", gradients["dWya"][1][2])
# print("gradients[\"db\"][4] =", gradients["db"][4])
# print("gradients[\"dby\"][1] =", gradients["dby"][1])

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

    参数:
        parameters -- 包含了Waa, Wax, Wya, by, b的字典
        char_to_ix -- 字符映射到索引的字典
        seed -- 随机种子
    
    返回:
        indices -- 包含采样字符索引的长度为n的列表
    
    注:
        输入x 和 输出y 都是 one-hot向量
    """
    # 从parameters中 获取参数
    Waa, Wax, Wya, by, b = parameters['Waa'], parameters['Wax'], parameters['Wya'], parameters['by'], parameters['b']
    # 输出y的维度 == 字符字典的长度
    vocab_size = by.shape[0]
    # a的维度
    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是检测换行符"\n"的标志,我们将其初始化为-1
    idx = -1

    # 循环遍历时间步骤t。在每个时间步中,从概率分布中抽取一个字符,
    # 并将其索引附加到indices上,如果我们达到50个字符,
    # 我们将停止循环,可以防止进入无限循环
    counter = 0
    newline_character = char_to_ix["\n"]

    while(idx != newline_character and counter < 50):
        # 步骤2:前向传播
        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中抽取词汇表中字符的索引
        """
        np.random.choice(
            a, #一维数组
            size=None, # 组成size大小的数组
            replace=True, #True表示可以取相同数字(有放回),False表示不可以取相同数字
            p=None #与数组a相对应,表示取数组a中每个元素的概率,默认为选取每个元素的概率相同。
        )

        1. 表示,生成0~vocab_size-1的数组,根据y的概率,取1个
        2. ravel的作用 == flattern 但ravel返回的数组修改后,原数组会发生变化
        """
        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
    
    # idx == newline_character == \n 退出 循环
    if(counter == 50):
        indices.append(char_to_ix["\n"])
    
    return indices

# 测试
# np.random.seed(2)
# _, n_a = 20, 100
# Wax, Waa, Wya = np.random.randn(n_a, vocab_size), np.random.randn(n_a, n_a), np.random.randn(vocab_size, n_a)
# b, by = np.random.randn(n_a, 1), np.random.randn(vocab_size, 1)
# parameters = {"Wax": Wax, "Waa": Waa, "Wya": Wya, "b": b, "by": by}


# indices = sample(parameters, char_to_ix, 0)
# print("Sampling:")
# print("list of sampled indices:", indices)
# print("list of sampled characters:", [ix_to_char[i] for i in indices])

"""
    3.1 梯度下降
        一次训练一个样本

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

    参数:
        X -- 整数列表,其中每个整数映射到词汇表中的字符
        Y -- 整数列表,与X完全相同,但向左移动了一个索引
        a_prev -- 上一个隐藏状态
        parameters -- 参数字典,包含:
            Wax -- 权重矩阵 乘上 输出x,维度为(n_a,n_x)
            Waa -- 权重矩阵 乘上 隐藏状态a_prev,维度为(n_a, n_a)
            Wya -- 权重矩阵 乘上 隐藏状态a_next,维度(n_y, n_a)
            b -- 偏置,维度为(n_a, 1)
            by -- 隐藏状态a_next 与输出y的偏置,维度为(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] -- 最后一个隐藏状态a_next输出,维度为(n_a, 1)
    """
    # 前向传播
    loss, cache = cllm_utils.rnn_forward(X,Y,a_prev,parameters)

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

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

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

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

# 测试
# np.random.seed(1)
# vocab_size, n_a = 27, 100
# a_prev = np.random.randn(n_a, 1)
# Wax, Waa, Wya = np.random.randn(n_a, vocab_size), np.random.randn(n_a, n_a), np.random.randn(vocab_size, n_a)
# b, by = np.random.randn(n_a, 1), np.random.randn(vocab_size, 1)
# parameters = {"Wax": Wax, "Waa": Waa, "Wya": Wya, "b": b, "by": by}
# X = [12,3,5,11,22,3]
# Y = [4,14,11,22,25, 26]

# loss, gradients, a_last = optimize(X, Y, a_prev, parameters, learning_rate = 0.01)
# print("Loss =", loss)
# print("gradients[\"dWaa\"][1][2] =", gradients["dWaa"][1][2])
# print("np.argmax(gradients[\"dWax\"]) =", np.argmax(gradients["dWax"]))
# print("gradients[\"dWya\"][1][2] =", gradients["dWya"][1][2])
# print("gradients[\"db\"][4] =", gradients["db"][4])
# print("gradients[\"dby\"][1] =", gradients["dby"][1])
# print("a_last[4] =", a_last[4])

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

    # 初始化参数
    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()
    # strip() 方法用于移除字符串头尾指定的字符(默认为空格)
    examples = [x.lower().strip() for x in examples]

    # 打乱全部的恐龙名称
    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,Y 均使用 数字(索引)表示
            rnn_forward()会将 第一个None解释为x^<0> = 0向量
            Y = X 左移一个字符 + \n

            时间步t只不过是 神经元的循环次数,t的大小由 len(X)决定
        """
        X = [None] + [char_to_ix[ch] for ch in examples[index]]
        Y = X[1:] + [char_to_ix["\n"]]
        
        # 执行单步优化:前向传播 -> 反向传播 -> 梯度修剪 -> 更新参数
        # 选择学习率为0.01
        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

def train():
    data,chars,data_size,vocab_size,char_to_ix,ix_to_char = get_data()
    
    #开始时间
    start_time = time.perf_counter()

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

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

    #计算时差 得到秒
    minium = end_time - start_time

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

def main():
    train()
    # generate_output()
    pass

if __name__ == "__main__":
    main()

4. LSTM网络即兴爵士乐

错误1:load_music_utils读出来的n_values=90

修改文件data_utils.py添加n_values=90

chords, abstract_grammars = get_musical_data('data/original_metheny.mid')
corpus, tones, tones_indices, indices_tones = get_corpus_data(abstract_grammars)
N_tones = len(set(corpus))
n_a = 64
n_values = 90
x_initializer = np.zeros((1, 1, n_values))
a_initializer = np.zeros((1, n_a))
c_initializer = np.zeros((1, n_a))

在之后的处理中,n_values均要修改为90

out:
    shape of X: (60, 30, 90)
    number of training examples: 60
    Tx (length of sequence): 30
    total # of unique values: 90
    Shape of Y: (30, 60, 78)

错误2: model.fit()默认batch_size=32
原文中,使用了batch_size=1

在此修改
model.fit([X, a0, c0], list(Y), epochs=500, batch_size=32)

但是,由于不是n_values=78的文件,n_values=90的文件训练100个epochs之后,总损失loss=75左右。(不知道是不是正常现象)


import os
import keras
import time
from numpy.core.fromnumeric import shape
from tensorflow.python.ops.gen_array_ops import shape_eager_fallback
# 不使用GPU
os.environ["CUDA_VISIBLE_DEVICES"] = "-1"

os.chdir('code\\deeplearning.ai\\5-sequence_model\\1-RNN_and_App')

from keras.models import load_model, Model
from keras.layers import Dense, Activation, Dropout, Input, LSTM, Reshape, Lambda, RepeatVector
from keras.initializers import glorot_uniform
from keras.utils import to_categorical
from keras.optimizers import Adam
from keras import backend as K
import numpy as np
import sys
from music21 import *
from grammar import *
from qa import *
from preprocess import * 
from music_utils import *
from data_utils import *

def listen_music():
    file = r".\\data\\30s_seq.mp3"
    os.system(file)


"""
X:(m,T_x,78)的独热向量,m个样本,T_x = 30个时间片
Y:基本与X相同,但是左移了一个单位
n_values:音符数量
indices_values:字典,index -> tones
"""
def get_data():
    X, Y, n_values, indices_values = load_music_utils()
    print('shape of X:', X.shape)
    print('number of training examples:', X.shape[0])
    print('Tx (length of sequence):', X.shape[1])
    print('total # of unique values:', n_values)
    print('Shape of Y:', Y.shape)

    return X, Y, n_values, indices_values

def djmodel(Tx, n_a, n_values):
    """
    实现模型

    参数:
        Tx -- 语料库的长度(x)  时间片长度(√)
        n_a -- 激活值的数量
        n_values -- 音符种类
    
    返回:
        model -- keras模型对象
    """
    # 定义输入数据的维度
    X = Input(shape=(Tx,n_values),name="x0")

    # 定义a0,初始化隐藏状态
    a0 = Input(shape=(n_a,),name="a0")
    c0 = Input(shape=(n_a,),name="c0")
    a = a0
    c = c0
    
    # 定义前向传播层
    """
        为什么要单独定义?
        在循环中,它们需要循环T_x次,但是所有层应该是具有相同的权重。
        而不是在每次循环的时候重新初始化。
    """
    reshapor = Reshape(target_shape=(1,n_values))
    LSTM_cell = LSTM(n_a,return_state=True)
    densor = Dense(n_values,activation="softmax")

    # 第一步:创建一个空的outputs列表来保存LSTM的所有时间步的输出。
    outputs = []

    # 第二步:循环
    for t in range(Tx):
        ## 2.A:从X中选择第t个时间步向量
        x = Lambda(function=lambda x:x[:,t,:])(X)

        ## 2.B:使用reshapor来对x进行重构为(1, n_values)
        x = reshapor(x)

        ## 2.C:单步传播
        a, _, c = LSTM_cell(x,initial_state=[a,c])

        ## 2.D:使用densor()应用于LSTM_Cell的隐藏状态输出
        out = densor(a)

        ## 2.E:把预测值添加到outputs列表中
        outputs.append(out)
    
    # 第三步:创建模型对象
    model = Model(inputs=[X,a0,c0], outputs=outputs)

    return model

def music_inference_model(LSTM_cell, densor, n_values=90, n_a=64, Ty=100):
    """
    参数:
        LSTM_cell -- 来自model()的训练过后的LSTM单元,是keras层对象
        densor -- 来自model()的训练过后的densor,是keras对象
        n_values -- 整数,唯一值的数量
        n_a -- LSTM单元的数量
        Ty -- 整数,生成的是时间步的数量

    返回:
        inference_model -- keras 模型对象
    """

    # 定义模型的输入维度
    x0 = Input(shape=(1,n_values),name="x0")

    # 定义s0,初始化隐藏状态
    a0 = Input(shape=(n_a,),name="a0")
    c0 = Input(shape=(n_a,),name="c0")
    a = a0
    c = c0
    x = x0
    
    # 步骤1:创建一个空的outputs列表来保存预测值
    outputs = []

    # 步骤2:遍历Ty,生成所有时间步的输出
    for t in range(Ty):
        
        # 步骤2.A:在LSTM中单步传播
        a, _, c = LSTM_cell(x, initial_state=[a,c])

        # 步骤2.B:使用densor()应用于LSTM_cell的隐藏状态输出
        out = densor(a)

        # 步骤2.C:预测值添加到outputs列表中
        outputs.append(out)

        # 根据out选择下一个值,并将x设置为所选值得独热编码形式
        # 将该值在下一步作为输入 传给LSTM_cell。我们已经提供了执行此操作所需的代码
        x = Lambda(one_hot)(out)
    
    # 创建模型实体
    inference_model = Model(inputs=[x0,a0,c0], outputs=outputs)

    return inference_model


def predict_and_sample(inference_model, 
                        x_initializer = x_initializer, 
                        a_initializer = a_initializer, 
                        c_initializer = c_initializer):
    """
    使用模型预测 当前值 的 下一个值
    参数:
        inference_mdoel -- keras实体模型
        x_initializer -- 初始化的独热编码,维度为(1,1,90)
        a_initializer -- LSTM单元的隐藏状态初始化,维度为(1, n_a)
        c_initializer -- LSTM单元的状态初始化,维度为(1, n_a)
    
    返回:
        results -- 生成值 的 独热编码向量,维度为(Ty, 90)
        indices -- 所生成值 的索引矩阵,维度为(Ty, 1)
    """
    
    # 步骤1:模型来预测给定x_initializer, a_initializer and c_initializer的输出序列
    pred = inference_model.predict([x_initializer, a_initializer, c_initializer])

    # 步骤2:将pred转换为具有 最大概率的 索引 数组np.array()
    indices = np.argmax(pred, axis=-1)

    # 步骤3:将索引转换为 他们的一个 独热编码
    results = to_categorical(indices,num_classes=90)

    return results, indices


def train2():
    n_values = 90
    LSTM_cell = LSTM(n_a,return_state=True)
    densor = Dense(n_values,activation="softmax")
    inference_model = music_inference_model(LSTM_cell=LSTM_cell,densor=densor,n_values=n_values)
    results, indices = predict_and_sample(inference_model, x_initializer, a_initializer, c_initializer)
    print("np.argmax(results[12]) =", np.argmax(results[12]))
    print("np.argmax(results[17]) =", np.argmax(results[17]))
    print("list(indices[12:18]) =", list(indices[12:18]))


def train():
    X, Y, n_values, indices_values = get_data()
    """
    out:
        shape of X: (60, 30, 90)
        number of training examples: 60
        Tx (length of sequence): 30
        total # of unique values: 90
        Shape of Y: (30, 60, 78)
    """
    # n_a = 64
    # Tx = 30
    # """
    #     获取模型,这里Tx=30, n_a=64,n_values=改为90
    # """
    # model = djmodel(Tx = Tx , n_a = n_a, n_values = 90)

    # # 编译模型,我们使用Adam优化器与分类熵损失。
    # opt = Adam(lr=0.01, beta_1=0.9, beta_2=0.999, decay=0.01)
    # model.compile(optimizer=opt, loss='categorical_crossentropy', metrics=['accuracy'])

    # # 初始化a0和c0,使LSTM的初始状态为零。
    # m = 60
    # a0 = np.zeros((m, n_a))
    # c0 = np.zeros((m, n_a))

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

    # #开始拟合
    # """
    #     batch_size 默认是32,现在改为1,随机梯度下降
    # """
    # model.fit([X, a0, c0], list(Y), epochs=500, batch_size=32)

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

    # #计算时差
    # minium = end_time - start_time

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

def main():
    # listen_music()
    # train()
    train2()

if __name__ == "__main__":
    main()

课程部分

1.2 数学符号

请添加图片描述

X ( i ) < t > X^{(i)<t>} X(i)<t>表示输入数据中,第 i i i个样本的,第 t t t序列
T x ( i ) T_x^{(i)} Tx(i)表示输入数据中,第 i i i个样本的长度

Y Y Y与上面的类似。

1.5 RNN的不同结构

请添加图片描述

  • one to one就是普通的神经元
  • many to many分为长度相同的长度不同的 两种。

猜你喜欢

转载自blog.csdn.net/LittleSeedling/article/details/119646509