循环神经网络(一)

目录

1. Embedding与变长输入处理

1.1 Embedding

1.1.1 One-hot编码

1.1.2 Dense embedding

1.2 变长输入

1.2.1 Padding

1.2.2 cut

1.2.3 合并

1.3实战

1.3.1 载入数据集 

1.3.2 载入数据集的词表

1.3.3 对变长输入做处理

1.3.4 构建模型

1.4 变长输入处理——padding + 合并的缺点

1.4.1 信息丢失

1.4.2 无效计算太多,低效

2. 循环神经网路

2.1 为什么需要循环神经网络——序列式问题

2.2 循环神经网络的结构

2.3 循环神经网络举例 

2.3.1 字符语言模型

2.3.2 循环神经网络做文本分类

       2.4实战

2.4.1文本分类

2.4.2文本生成


1. Embedding与变长输入处理

1.1 Embedding

1.1.1 One-hot编码

Word——> index ——>[0,0,0,…,0,1,0,…,0]

1.1.2 Dense embedding

Word——>index——>[1.2, 4.2, 2.9, …,0.1]

才开始[1.2, 4.2, 2.9, …,0.1]是随机的,训练过之后就可以代表word了。

1.2 变长输入

假设要求输入的长度都10

1.2.1 Padding

Word index:[3, 2, 5, 9, 1, 8, 6, 3]

Padding: [3, 2, 5, 9, 1, 8, 6, 3, 0, 0]

1.2.2 cut

Word index:[3, 2, 5, 9, 1, 8, 6, 3, 2, 5, 9, 1]

cut: [3, 2, 5, 9, 1, 8, 6, 3, 2, 5]

1.2.3 合并

MLP:MultiLayer Perceptron,即多层的全连接神经网络。输入的长度需要是固定的。

例子中“慕课网”“TF2.0”“实战”“值得”“拥有”总共有5个词,假设每个词embedding之后是长度为10的向量,输出组合为5*10;假如有n个词呢?输出组合为n*10,此时,输出组合就不固定了。为了解决这个问题,可以使用合并(比如使用global average pooling),将每个词向量的每一个维度合并,最终输出一个长度为10的向量。

1.3实战

这里使用skleran中的一个电影评价的数据集

1.3.1 载入数据集 

num_words:通过设置 num_words来设置词表的个数,比如:把num_words限制在10000,会按照频次进行统计,统计出的词语前10000个作为词表,保留下来,后边的被当作特殊字符处理。

index_from 从几开始算

train_data中存储的是word的id,比如

index_from = 0,train_data[0] = [1, 11, 19, 13, 40, ......]

index_from = 3,train_data[0] = [1, 14, 22, 16, 43, ......]

注意:每个元素的第一个id都是1,不变

imdb = keras.datasets.imdb
vocab_size = 10000
index_from = 3

(train_data, train_labels), (test_data, test_labels) = imdb.load_data(
    num_words = vocab_size, index_from = index_from)

1.3.2 载入数据集的词表

word_index = imdb.get_word_index()

对词表进行修正,并输出train_data中的id输出单词

# 由于我们的index_from = 3, 所以word_index 中的value(index) 需要后移3
word_index = {k: (v+3) for k, v in word_index.items()}

# 将补充符<PAD>,开始符<START>,结束符<END>,未知符<UNK>填在前面3位
word_index['<PAD>'] = 0
word_index['<START>'] = 1
word_index['<UNK>'] = 2
word_index['<END>'] = 3

# 翻译train_data,因为train_data中存储的都是index
reverse_word_index = dict([(v, k) for k, v in word_index.items()])

def decode_review(text_ids):
    return " ".join(
        [reverse_word_index.get(word_id, "<UNK>") for word_id in text_ids])

print("train_data[0]", train_data[0])
print("decode train_data[0]", decode_review(train_data[0]))

1.3.3 对变长输入做处理

padding

# padding
max_length = 500

train_data = keras.preprocessing.sequence.pad_sequences(
    train_data, # list of list 
    value = word_index['<PAD>'],
    padding = 'post', # poost: 表示padding补在最后, pre:表示padding补充在前面
    maxlen= max_length)

test_data = keras.preprocessing.sequence.pad_sequences(
    test_data, # list of list 
    value = word_index['<PAD>'],
    padding = 'post', # poost: 表示padding补在最后, pre:表示padding补充在前面
    maxlen= max_length)

print(train_data[0])

1.3.4 构建模型

对变长做处理——合并

embedding_dim = 16
batch_size = 128

model = keras.models.Sequential([
    # 1. define matrix: [vocab_size, embeding_dim]
    # 2. train_data[*], max_length * embedding_dim
    # 3. batch_size * max_length * embedding_dim
    keras.layers.Embedding(vocab_size, embedding_dim,
                           input_length = max_length),
    
    # 合并(注:输入已经是定长的了,可以将其变为batch_size * (max_length * embedding_dim)
    # 所以合并可要可不要,这里只是为了演示)
    # batch_size * max_length * embedding_dim ——> batch_size * embedding_dim
    keras.layers.GlobalAveragePooling1D(),
    keras.layers.Dense(64, activation = 'relu'),
    keras.layers.Dense(1, activation = 'sigmoid'),
])

model.summary()

 训练

model.compile(optimizer = 'adam', loss = 'binary_crossentropy', 
              metrics = ['accuracy'])

# validation_split = 0.2, 表示训练数据集中有20%的被用作验证数据集
history = model.fit(train_data, train_labels,
                    epochs = 30,
                    batch_size = batch_size,
                    validation_split = 0.2)

评估

# 将loss 和 acc 分开两个图画,plot_key 可取 loss 或 acc
def plot_learning_curves(history, plot_key, epochs, min_value, max_value):
    data = {}
    data[plot_key] = history.history[plot_key]
    data['val_' + plot_key] = history.history['val_' + plot_key]
    pd.DataFrame(data).plot(figsize=(8, 5))
    plt.grid(True)
    plt.axis([0, epochs, min_value, max_value])
    plt.show()
    
plot_learning_curves(history, 'accuracy', 30, 0.5, 1)
plot_learning_curves(history, 'loss', 30, 0, 1)

测试

model.evaluate(
    test_data, test_labels,
    batch_size = batch_size,
)

1.4 变长输入处理——padding + 合并的缺点

1.4.1 信息丢失

(1)pad噪音

在均值合并的时候,每个句子里的pad也都参与了计算,相当于真实的句子来说,它们即为噪音,噪音过多,会把真实的意思给稀释掉。

(2)多个embeding合并,无主次

即便句子中全部都是真实的词语,没有pad,直接去合并,没有主次,对于分类来说,某些词语对分类的结果,影响会更大。比如说一些表达情感的词语。类似于主语,或者连接词(the and等) 就没有那么重要,如果直接做合并,就会使它们没有主次,造成真实的信息被稀释掉,即造成信息的丢失,最后的结果可能会不准确。

1.4.2 无效计算太多,低效

太多的padding。比如做线上测试的时候,最长的句子是500,但其中有一个句子A的长度是20,为了保持句子的长度一样,故需要对句子A做480个padding,而这480个padding都是要参与到计算中去的,因此这个就大大的降低了计算的速度。

故需要引入循环神经网络.

2. 循环神经网路

2.1 为什么需要循环神经网络——序列式问题

序号

模型描述

输入输出形式

输入

输出

(1)

普通神经网络

(anilla neural networks)

1对1

一张图片

某个大类

(2)

图片生成描述

1对多

一张图片

一个句子

(3)

文本分类

多对1

一个文本

积极”/“消极”

(4)

机器翻译(非实时,输入完了,才会有输出)

多对多

中文句子

英文句子

(5)

视频解说(实时)

多对多

一帧一帧的图像

一个一个句子

注:机器翻译中的非实时即为输入完了,才有输出。

       视频解说中的实时为,有一帧图像之后,生成第一个句子,有第二帧图像之后,生成第二个句子。

2.2 循环神经网络的结构

注:每一步使用同样的激活函数和参数,即每一步的W和U都是一样的

2.3 循环神经网络举例 

2.3.1 字符语言模型

在做序列式预测的时候,会有一些风险,比如说在开头预测错的时候,后边就是全错的。

2.3.2 循环神经网络做文本分类

和之前文本分类网络结构(见1.2.3合并)不一样的是,不需要对每一个的输出作合并,只是用最后一步的输出来做MLP的输入。因为在循环神经网路中,会维护一个state,这个state包含了句子的所有信息,因而,在最后的时候,会认为state是拥有整个句子的信息的。

箭头是双向的,表明其是双向的循环神经网络。双向循环神经网络和单项循环神经网络的区别是,会多维护一个状态。S1表明是从前到后的一个状态,S2表明是从后向前的一个状态。

这样,整个模型中就会包含上下文信息。

2.4实战

2.4.1文本分类

(1) 载入skleran中的数据集imdb(有关电影评价的一个数据集,分正面和负面)

imdb = keras.datasets.imdb
vocab_size = 10000
index_from = 3

# 通过设置 num_words来设置词表的个数,比如:把num_words限制在10000,会按照频次进行统计,
# 统计出的词语前10000个作为词表,保留下来,后边的被当作特殊字符处理。
# index_from 从几开始算. 
# train_data中存储的是word的id,比如index_from = 0,train_data[0] = [1, 11, 19, 13, 40, ......]
#                                index_from = 3,train_data[0] = [1, 14, 22, 16, 43, ......]
# 注意:每个元素的第一个id都是1,不变

(train_data, train_labels), (test_data, test_labels) = imdb.load_data(
    num_words = vocab_size, index_from = index_from)

载入数据集词表

word_index = imdb.get_word_index()

对词表进行修正

# 由于我们的index_from = 3, 所以word_index 中的value(index) 需要后移3
word_index = {k: (v+3) for k, v in word_index.items()}

# 将补充符<PAD>,开始符<START>,结束符<END>,未知符<UNK>填在前面3位
word_index['<PAD>'] = 0
word_index['<START>'] = 1
word_index['<UNK>'] = 2
word_index['<END>'] = 3

# 翻译train_data,因为train_data中存储的都是index
reverse_word_index = dict([(v, k) for k, v in word_index.items()])

def decode_review(text_ids):
    return " ".join(
        [reverse_word_index.get(word_id, "<UNK>") for word_id in text_ids])

print("train_data[0]", train_data[0])
print("decode train_data[0]", decode_review(train_data[0]))

对变长输入做处理

# padding
max_length = 500

train_data = keras.preprocessing.sequence.pad_sequences(
    train_data, # list of list 
    value = word_index['<PAD>'],
    padding = 'post', # poost: 表示padding补在最后, pre:表示padding补充在前面
    maxlen= max_length)

test_data = keras.preprocessing.sequence.pad_sequences(
    test_data, # list of list 
    value = word_index['<PAD>'],
    padding = 'post', # poost: 表示padding补在最后, pre:表示padding补充在前面
    maxlen= max_length)

(2)构建模型

单层单向RNN

embedding_dim = 16
batch_size = 128

single_rnn_model = keras.models.Sequential([
    # 1. define matrix: [vocab_size, embeding_dim]
    # 2. train_data[*], max_length * embedding_dim
    # 3. batch_size * max_length * embedding_dim
    keras.layers.Embedding(vocab_size, embedding_dim,
                           input_length = max_length),
    keras.layers.SimpleRNN(units = 64, return_sequences = False),
    keras.layers.Dense(64, activation = 'relu'),
    keras.layers.Dense(1, activation = 'sigmoid'),
])

single_rnn_model.summary()

训练

single_rnn_model.compile(optimizer = 'adam', 
                         loss = 'binary_crossentropy', 
                         metrics = ['accuracy'])

# validation_split = 0.2, 表示训练数据集中有20%的被用作验证数据集
history = single_rnn_model.fit(train_data, train_labels,
                               epochs = 30,
                               batch_size = batch_size,
                               validation_split = 0.2)

查看训练结果

# 将loss 和 acc 分开两个图画,plot_key 可取 loss 或 acc
def plot_learning_curves(history, plot_key, epochs, min_value, max_value):
    data = {}
    data[plot_key] = history.history[plot_key]
    data['val_' + plot_key] = history.history['val_' + plot_key]
    pd.DataFrame(data).plot(figsize=(8, 5))
    plt.grid(True)
    plt.axis([0, epochs, min_value, max_value])
    plt.show()
    
plot_learning_curves(history, 'accuracy', 30, 0.5, 1)
plot_learning_curves(history, 'loss', 30, 0, 1)

从图中可以看到,在验证集上,loss先下降后上升,说明有些过拟合

测试

single_rnn_model.evaluate(
    test_data, test_labels,
    batch_size = batch_size,
)

双层双向RNN

embedding_dim = 16
batch_size = 128

two_bi_rnn_model = keras.models.Sequential([
    # 1. define matrix: [vocab_size, embeding_dim]
    # 2. train_data[*], max_length * embedding_dim
    # 3. batch_size * max_length * embedding_dim
    keras.layers.Embedding(vocab_size, embedding_dim,
                           input_length = max_length),
    keras.layers.Bidirectional(
        keras.layers.SimpleRNN(
            units = 64, return_sequences = True)),
    keras.layers.Bidirectional(
        keras.layers.SimpleRNN(
            units = 64, return_sequences = False)),
    keras.layers.Dense(64, activation = 'relu'),
    keras.layers.Dense(1, activation = 'sigmoid'),
])

two_bi_rnn_model.summary()

训练

查看训练结果

测试

由上可以看出,模型显然是过拟合了。
防止过拟合————降低模型尺寸;加正则项;dropout等。

单层双向RNN

embedding_dim = 16
batch_size = 128

single_bi_rnn_model = keras.models.Sequential([
    # 1. define matrix: [vocab_size, embeding_dim]
    # 2. train_data[*], max_length * embedding_dim
    # 3. batch_size * max_length * embedding_dim
    keras.layers.Embedding(vocab_size, embedding_dim,
                           input_length = max_length),
    keras.layers.Bidirectional(
        keras.layers.SimpleRNN(
            units = 64, return_sequences = False)),
    keras.layers.Dense(64, activation = 'relu'),
    keras.layers.Dense(1, activation = 'sigmoid'),
])

single_bi_rnn_model.summary()

2.4.2文本生成

 (1)载入数据集

# 数据集下载

# https://storage.googleapis.com/download.tensorflow.org/data/shakespeare.txt

input_filepath = "/content/drive/MyDrive/data/shakespeare.txt"
text = open(input_filepath, 'r').read()

generate vacab

import pprint

vocab = sorted(set(text))
print(len(vocab))
print(vocab)

build mapping char->id

char2idx = {char:idx for idx, char in enumerate(vocab)}
print(char2idx)

idx2char = np.array(vocab)
print(idx2char)

data ——> id_data

text_as_int = np.array([char2idx[c] for c in text])
print(text_as_int[0:10])
print(text[0:10])

(2)构造数据集

def split_input_target(id_text):
    """
    abcde --> 输入:abcd,输出bcde
    """
    return id_text[0:-1], id_text[1:]

char_dataset = tf.data.Dataset.from_tensor_slices(text_as_int)
seq_length = 100
seq_dataset = char_dataset.batch(seq_length + 1,
                                 drop_remainder = True)

print("\nch_id")
for ch_id in char_dataset.take(2):
    print(ch_id, idx2char[ch_id.numpy()])

print("\nseq_id")
for seq_id in seq_dataset.take(2):
    print(seq_id, ''.join(idx2char[seq_id.numpy()]))

seq_dataset = seq_dataset.map(split_input_target)
print("\nitem_input, item_output")
for item_input, item_output in seq_dataset.take(2):
    print(item_input.numpy())
    print(item_output.numpy())
batch_size = 64
buffer_size = 10000

# 这里有个疑问,上边已经batch过一次了,为什么这里还要batch?
# drop_remainder = True 即最后一次数据不够的话,就把最后一次的数据给丢掉
seq_dataset = seq_dataset.shuffle(buffer_size).batch(
    batch_size, drop_remainder = True)

(3)构建模型

vocab_size = len(vocab)
embedding_dim = 256  # 因为vocab_size 比较小,所以embedding_dim,run_units可以大一些
run_units = 1024

def build_model(vocab_size, embedding_dim, run_units, batch_size):
    model = keras.models.Sequential([
        keras.layers.Embedding(vocab_size, embedding_dim,
                               batch_input_shape = [batch_size, None]),
        keras.layers.SimpleRNN(units = run_units, return_sequences = True),
        keras.layers.Dense(vocab_size)
    ])
    return model

model = build_model(
    vocab_size = vocab_size,
    embedding_dim = embedding_dim,
    run_units = run_units,
    batch_size = batch_size
)

model.summary()

可视化输入输出

for input_example_batch, target_example_batch in seq_dataset.take(1):
    example_batch_predictions = model(input_example_batch)
    print(example_batch_predictions.shape) # 64 * 100 * 65
    print(example_batch_predictions)


sample_indices = tf.random.categorical(
    logits = example_batch_predictions[0], num_samples = 1)
print(sample_indices) # 100 * 1

# 将sample_indices 变成一个向量
sample_indices = tf.squeeze(sample_indices, axis = -1)
print(sample_indices)

print("\nInput: ", repr(''.join(idx2char[input_example_batch[0]])))
print("\nOutput: ", repr(''.join(idx2char[target_example_batch[0]])))
print("\nPrediction: ", repr(''.join(idx2char[tf.reshape(sample_indices, [100])])))

计算loss

def loss(labels, logits):
    return keras.losses.sparse_categorical_crossentropy(
        labels, logits, from_logits=True)

example_loss = loss(target_example_batch, example_batch_predictions)
print(example_loss.shape)
print(example_loss.numpy().mean())

(4)训练

import os

output_dir = "/content/drive/MyDrive/result/text_generation_checkpoints"
if not os.path.exists(output_dir):
    os.mkdir(output_dir)

checkpoint_prefix = os.path.join(output_dir, 'ckpt_{epoch}')
checkpoint_callback = keras.callbacks.ModelCheckpoint(
    filepath = checkpoint_prefix,
    save_weights_only = True)

epochs = 100
model.compile(optimizer = 'adam', loss=loss)
history = model.fit(seq_dataset, epochs = epochs,
                    callbacks = [checkpoint_callback])

(5)从checkpoint中载入最新存储的模型

model2 = build_model(vocab_size,
                     embedding_dim,
                     run_units,
                     batch_size = 1)

model2.load_weights(tf.train.latest_checkpoint(output_dir))
model2.build(tf.TensorShape([1, None])) # 1指的是一个样本,None表示该样本可以是一个变长的序列
model2.summary()

(6)文本生成

文本生成流程
start char sequence A,
A --> model --> b.
A.append(b) --> B.
B(Ab) --> model --> c.
B.append(c) --> moodel --> c.
B.append(c) --> C.
C(Abc) --> model --> ...

def generate_text(model, start_string, num_gengrate = 1000):
    input_eval = [char2idx[ch] for ch in start_string]
    # 因为输入的是[1, None] 二维的,故需要做维度的扩展 
    input_eval = tf.expand_dims(input_eval, 0)

    text_generated = []
    model.reset_states()

    for _ in range(num_generate):
        # model inference --> predictions
        # sample --> ch --> text_generated
        # update input_eval

        # predictions: [batch_size, input_eval_len, vocab_size]
        predictions = model(input_eval)
        # batch_size 总是1(why?)所以可以将batch_size 那个维度消掉
        # predictions: [input_eval_len, vocab_size]
        predictions = tf.squeeze(predictions, 0)
        # predicted_ids: [input_eval_len, 1]
        # a b c --> b c d (其中,b是对a的预测,c是对ab的预测,d是对abc的预测,所以只有最后一个d是有用的)
        predicted_id = tf.random.categorical(predictions, num_sample = 1)[-1, 0].numpy()
        text_generated.append(idx2char[predicted_id])

        # 更新input_eval
        # 循环神经网络模型 s, x --> run --> s', y
        input_eval = tf.expand_dims([predicted_id], 0)
    
    return start_string + ''.join(text_generated)

new_text = generated_text(model2, "All: ")
print(new_text)

  • 1
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值