流程总结
RNN前向传播/训练过程怎么样的?
- 所谓多个时间步t,并不需要构建多个RNN神经元,只需要利用循环实现即可。
在训练时,我们拥有两个数据(序列) x , y x,y x,y。
对于每个样本 x ( i ) x^{(i)} x(i),都满足:
- x < 1 > = 0 ⃗ x^{<1>} = \vec{0} x<1>=0
- y ( i ) < t > = x ( i ) < t + 1 > y^{(i)<t>} = x^{(i)<t+1>} y(i)<t>=x(i)<t+1>
- 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。
- 传入 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>。
- 让 y ^ < 1 > \hat{y}^{<1>} y^<1>与 y < 1 > y^{<1>} y<1>计算得到一个损失 l o s s 1 loss_1 loss1。
- 依次类推,计算 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之后相加。
- 反向传播,得到梯度gradients。
- 进行梯度修剪clip,将梯度固定在一定的范围内。因为RNN可能出现梯度爆炸的情况。
- 更新参数。
采样sample()的过程是怎么样的?
- 传入 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>。
- 在采样sample中, y ^ < t > \hat{y}^{<t>} y^<t>是
softmax
的结果,是概率分布。利用np.random.choice
根据概率,随机选择一个值(索引),作为结果传到下一个 x < t > x^{<t>} x<t>。 - 令 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^<t−1>。这样意味着,输出 y ^ < t > \hat{y}^{<t>} y^<t>代替了真值 y < t > y^{<t>} y<t>进入到下一个时间步。
- 时间步不断循环,直到神经单元输出 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
np.random.choice(
a, #一维数组
size=None, # 组成size大小的数组
replace=True, #True表示可以取相同数字(有放回),False表示不可以取相同数字
p=None #与数组a相对应,表示取数组a中每个元素的概率,默认为选取每个元素的概率相同。
)
numpy中的ravel() 和 flatten()
功能:都是讲数组扁平化(一维化),但是在使用过程中flatten()分配了新的内存,但ravel()返回的是一个数组的视图(在修改了ravel()返回的数组之后,原数组会发生改变)
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表达式
形式:
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
分为长度相同的 与 长度不同的 两种。