Keras快速构建神经网络模型

用Keras搭建神经网络的步骤:


搭建Keras 模型

深度学习框架Keras——像搭积木般构建神经网络,主要分为7个部分,每个部分只需要几个keras API函数就能实现,用户即可像搭积木般一层层构建神经网络模型。

1. 创建模型 Create model

2. 添加层级 Add Layer

3. 模型编译 Compile

4. 数据填充 Fit

5. 模型评估 Evaluate

6. 模型预测 Predict

7. 模型保存 Save model

下面章节会对每一部分具体来介绍。。。


1 创建模型

Keras 中主要有三类模型:Sequential model, Functional model, Subclass model

模型分类
  • Sequenctial API: 顺序模型是从头到尾的线性不分叉的结构使用单个输入、输出构建简单模型。

  • Function API: 是构建Keras模型最流行的方法。它允许多个输入、多个输出、分支和层共享。

  • 模型子类化:是为需要完全控制模型、层和训练过程的高级开发人员设计的。你需要创建一个定义模型的自定义类,而且你可能不需要它来执行日常任务。但是,如果你是一个有实验需求的研究人员,那么模型子类化可能是最好的选择,因为它会给你所有你需要的灵活性。

1.1 顺序模型

在开始创建模型之前,首先需要引入tensorflow和keras模块,然后再创建一个Sequential model

import tensorflow as tf
from tensorflow import keras
model = tf.keras.Sequential()

Sequential API定义如下:


tf.keras.Sequential(layers=None, name=None)

Args 参数
layers 添加到模型的网络层列表
name 模型名称

layers参数可以为空, 然后通过add method向模型中添加layer,相对应的通过pop method移除模型中layer。


# 向模型中添加一层layer,到layer栈的最顶端。

Sequential.add(layer) 

# 移除模型中最后一层的layer。

Squential.pop()


1.2 函数式模型

创建Function API模型,可以调用Keras.Model来指定多输入多数出。

Keras.Model定义:


tf.keras.Model(*args, **kwargs)

Args 参数
inputs 模型输入
outputs 模型输出
name 字符串,模型的名称
import numpy as np

import tensorflow as tf

from tensorflow import keras

model = keras.Model(inputs=inputs, outputs=outputs, name="mnist_model")

# 向模型中添加一层layer,到layer栈的最顶端。

model.add(layer) 

# 移除模型中最后一层的layer。

model.pop()



2 添加网络层

Layers是神经网络基本构建块。一个Layer包含了tensor-in/tensor-out的计算方法和一些状态,并保存在TensorFlow变量中(即layers的权重weights)。
Layers主要分为6个类别,基础层,核心层,卷基层,池化层,循环层,融合层。


addlayer.jpg

2.1 基础层The Base Layer


tf.keras.layers.Layer(

trainable=True, name=None, dtype=None, dynamic=False, **kwargs

)

Args 参数
trainable 布尔类型,变量是否可以被训练
name layer名字
dtype layer计算和权重的类型
dynamic True为动态图,False为静态图

对派生类的实现可以用以下方法:
** init(): 定义layer的属性,创建layer的静态变量。
** build(self, input_shape): 创建依赖于输入的变量,可以调用add_weight()。
** call(self, *args, **kwargs): 在确保已调用build()之后,在call中调用。
** get_config(self): 返回包含用于初始化此层的配置的字典类型。

派生类例子1:

创建SimpleDense派生类,在build()函数里添加trainable weights。实现y=input*w +b


class SimpleDense(tf.keras.layers.Layer):

    def __init__(self, units=32):

        super(SimpleDense, self).__init__()

        self.units = units

    def build(self, input_shape):  # add weights in build, the weights depends on input

        self.w = self.add_weight(shape=(input_shape[-1], self.units),

                                initializer='random_normal',

                                trainable=True)

        self.b = self.add_weight(shape=(self.units,),

                              initializer='random_normal',

                              trainable=True)

        print("input_shape[-1]: {},\n self.w:\n {}".format(input_shape[-1],self.w.numpy()))

    def call(self, inputs):

        return tf.matmul(inputs, self.w) + self.b

linear_layer = SimpleDense(3)

y = linear_layer(tf.ones((2, 2)))

print("y value:\n ",y.numpy())

assert len(linear_layer.weights) == 2

# These weights are trainable, so they're listed in `trainable_weights`:

assert len(linear_layer.trainable_weights) == 2

结果输出:


input_shape[-1]: 2,

self.w:

[[-0.03338138 -0.03652136  0.0161477 ]

[-0.02402006 -0.12441503 -0.01301991]]

y value:

  [[-0.02291155 -0.19580051  0.02063639]

[-0.02291155 -0.19580051  0.02063639]]


派生类例子2:

创建ComputeSum派生类,在init函数里添加 non-trainable weights。实现输入矩阵沿轴0元素相加后,x=x+self.total

class ComputeSum(tf.keras.layers.Layer):

    def __init__(self, input_dim):

        super(ComputeSum, self).__init__()

      # Create a non-trainable weight.

        self.total = tf.Variable(initial_value=tf.zeros((input_dim,)),

                              trainable=False)

    def call(self, inputs):

        self.total.assign_add(tf.reduce_sum(inputs, axis=0))

        return self.total

my_sum = ComputeSum(2)

x = tf.ones((2, 2))

print(x.numpy())

y = my_sum(x)

print(y.numpy())  # [2\. 2.]

y = my_sum(x)

print(y.numpy())  # [4\. 4.]

assert my_sum.weights == [my_sum.total]

assert my_sum.non_trainable_weights == [my_sum.total]

assert my_sum.trainable_weights == []

结果输出:

[[1. 1.]
 [1. 1.]]
[2. 2.]
[4. 4.]


2.2 核心网络层

核心层是最常用的层,涉及到数据的转换和处理的时候都会用到这些层。

2.2.1 Dense

Dense层就是所谓的全连接神经网络层,简称全连接层。全连接层中的每个神经元与其前一层的所有神经元进行全连接。


tf.keras.layers.Dense(

    units,

    activation=None,

    use_bias=True,

    kernel_initializer="glorot_uniform",

    bias_initializer="zeros",

    kernel_regularizer=None,

    bias_regularizer=None,

    activity_regularizer=None,

    kernel_constraint=None,

    bias_constraint=None,

    **kwargs

)

Dense 实现以下操作: output = activation(dot(input, kernel) + bias) 其中 activation 是按逐个元素计算的激活函数,kernel 是由网络层创建的权值矩阵,以及 bias 是其创建的偏置向量 (只在 use_bias 为 True 时才有用)。

Args 参数
units 正整数,输出空间维度
activation 激活函数
use_bias 布尔值,该层是否使用偏置向量
kernel_initializer kernel 权值矩阵的初始化器
bias_initializer 偏置向量的初始化器
kernel_regularizer 运用到 kernel 权值矩阵的正则化函数
bias_regularizer 运用到偏置向的的正则化函数
activity_regularizer 运用到层的输出的正则化函数
kernel_constraint 运用到 kernel 权值矩阵的约束函数
bias_constraint 运用到偏置向量的约束函数


2.2.2 Activation

将激活函数应用于输出。输入信号进入神经元后进行的运算处理。


tf.keras.layers.Activation(activation, **kwargs)

常见内置激活函数:
elu tf.keras.activations.elu(x, alpha=1.0) 指数线性单元
exponential tf.keras.activations.exponential(x) 指数激活函数
gelu tf.keras.activations.gelu(x, approximate=False) 高斯误差线性单元
linear tf.keras.activations.linear(x) 线性激活函数(即不做任何改变)
relu tf.keras.activations.relu(x, alpha=0.0, max_value=None, threshold=0) 整流线性单元
selu tf.keras.activations.selu(x) 可伸缩的指数线性单元
sigmoid tf.keras.activations.sigmoid(x) sigmoid(x) = 1 / (1 + exp(-x))
softplus tf.keras.activations.softplus(x) softplus(x) = log(exp(x) + 1)
softsign tf.keras.activations.softsign(x) softsign(x) = x / (abs(x) + 1)
swish tf.keras.activations.swish(x) swish(x) = x * sigmoid(x)
tanh tf.keras.activations.tanh(x) 双曲正切激活函数

sigmoid、tanh、ReLU、softplus的对比曲线如下图所示:

激活函数

激活函数可以通过设置单独的激活层Activation实现,也可以在构造层对象时通过传递 activation 参数实现:


# 方法一:激活函数可以通过设置单独的激活层Activation实现

import tensorflow as tf

from tensorflow import keras

model = tf.keras.Sequential()

model.add(tf.keras.layers.Dense(64))

model.add(tf.keras.layers.Activation('tanh'))

# 方法二:构造层对象时通过传递 activation 参数实现

import tensorflow as tf

from tensorflow import keras

model = tf.keras.Sequential()

model.add(tf.keras.layers.Dense(64, activation='tanh'))


2.2.3 Dropout

Dropout在训练中每次更新时,将输入单元的按比率随机设置为0,这有助于防止过拟合。未设置为0的输入将按1 /(1-rate)放大,以使所有输入的总和不变。


f.keras.layers.Dropout(rate, noise_shape=None, seed=None, **kwargs)

请注意,仅当训练设置为True时才应用Dropout层,以便在推理过程中不会丢弃任何值。 使用model.fit时,训练将自动适当地设置为True。

  • rate: 在 0 和 1 之间浮动。需要丢弃的输入比例。
  • noise_shape: 1D 整数张量, 表示将与输入相乘的二进制 dropout 掩层的形状。 例如,如果你的输入尺寸为 (batch_size, timesteps, features),然后 你希望 dropout 掩层在所有时间步都是一样的, 你可以使用 noise_shape=(batch_size, 1, features)。
  • seed: 一个作为随机种子的 Python 整数。


2.2.4 Flatten

将输入展平。不影响批量大小。注意:如果输入的形状是(batch,)没有特征轴,则展平会增加通道尺寸,而输出的形状是(batch, 1)。


tf.keras.layers.Flatten(data_format=None, **kwargs)


2.2.5 Reshape

将输入重新调整为特定的尺寸


tf.keras.layers.Reshape(target_shape, **kwargs)


2.2.6 Lambda

将任意表达式封装为Layer对象。在Lambda层,以便在构造模型时可以使用任意TensorFlow函数。 Lambda层最适合简单操作或快速实验。 Lambda层是通过序列化Python字节码来保存的。


# Lambda定义

tf.keras.layers.Lambda(

    function, output_shape=None, mask=None, arguments=None, **kwargs

)


# 举例说明 add a x -> x^2 layer

model.add(tf.keras.layers.Lambda(lambda x: x ** 2))


2.2.7 Masking

使用覆盖值覆盖序列,以跳过时间步。

对于输入张量的每一个时间步(张量的第一个维度),如果所有时间步中输入张量的值与mask_value相等,则将在所有下游层中屏蔽(跳过)该时间步。如果任何下游层不支持覆盖但仍然收到此类输入覆盖信息,会引发异常。


# Masking定义

tf.keras.layers.Masking(mask_value=0.0, **kwargs)

举例说明:


#考虑将要喂入一个 LSTM 层的 Numpy 矩阵 x, 尺寸为 (samples, timesteps, features)。

#你想要覆盖时间步#3 和 #5,因为你缺乏这几个时间步的数据。

#你可以:设置 x[:, 3, :]=0\. 以及 x[:, 5, :]=0\. 在 LSTM 层之前,插入一个 mask_value=0 的 Masking 层:

import tensorflow as tf

from tensorflow import keras

import numpy as np

samples, timesteps, features = 32, 10, 8

inputs = np.random.random([samples, timesteps, features]).astype(np.float32)

inputs[:, 3, :] = 0.

inputs[:, 5, :] = 0.

model = tf.keras.models.Sequential()

model.add(tf.keras.layers.Masking(mask_value=0.,

                                  input_shape=(timesteps, features)))

model.add(tf.keras.layers.LSTM(32))

output = model(inputs)

# The time step 3 and 5 will be skipped from LSTM calculation.


2.2.8 Embedding

Embedding 是一个将离散变量转为连续向量表示的一个方式。该层只能用作模型中的第一层。

Embedding 有以下3个主要目的: 在 embedding 空间中查找最近邻,这可以很好的用于根据用户的兴趣来进行推荐。 作为监督性学习任务的输入。 用于可视化不同离散变量之间的关系.


# Embedding定义

tf.keras.layers.Embedding(

    input_dim, output_dim, embeddings_initializer='uniform',

    embeddings_regularizer=None, activity_regularizer=None,

    embeddings_constraint=None, mask_zero=False, input_length=None, **kwargs)

  • input_dim: int > 0。词汇表大小,即最大整数 index + 1。
  • output_dim: int >= 0。词向量的维度。
  • embeddings_initializer: embeddings 矩阵的初始化方法。
  • embeddings_regularizer: embeddings matrix 的正则化方法。
  • embeddings_constraint: embeddings matrix 的约束函数。
  • mask_zero: 是否把 0 看作为一个应该被遮蔽的特殊的"padding"值。这对于可变长的循环神经网络层十分有用。如果设定为True,那么接下来的所有层都必须支持masking,否则就会抛出异常。如果mask_zero为True,作为结果,索引 0 就不能被用于词汇表中(input_dim 应该与 vocabulary + 1 大小相同)。
  • input_length: 输入序列的长度,当它是固定的时。如果你需要连接Flatten和Dense层,则这个参数是必须的。没有它,dense 层的输出尺寸就无法计算。

举例说明:


#输入尺寸为 (batch_size, sequence_length) 的 2D 张量。

#输出尺寸为 (batch_size, sequence_length, output_dim) 的 3D 张量。

model = tf.keras.Sequential()

model.add(tf.keras.layers.Embedding(1000, 64, input_length=10))

# The model will take as input an integer matrix of size (batch,

# input_length), and the largest integer (i.e. word index) in the input

# should be no larger than 999 (vocabulary size).

# Now model.output_shape is (None, 10, 64), where `None` is the batch

# dimension.

input_array = np.random.randint(1000, size=(32, 10))

model.compile('rmsprop', 'mse')

output_array = model.predict(input_array)

print(output_array.shape)

输出结果:


(32, 10, 64)


2.3 卷积层 Convolution layers

由维基百科的介绍我们可以得知,卷积是一种定义在两个函数(𝑓跟𝑔)上的数学操作,旨在产生一个新的函数。那么𝑓和𝑔的卷积就可以写成𝑓∗𝑔,数学定义如下:

卷积定义

对应到不同方面,卷积可以有不同的解释:𝑔 既可以看作我们在深度学习里常说的核(Kernel),也可以对应到信号处理中的滤波器(Filter)。而 𝑓 可以是我们所说的机器学习中的特征(Feature),也可以是信号处理中的信号(Signal)。f和g的卷积 (𝑓∗𝑔)就可以看作是对𝑓的加权求和。

一维时域卷积操作:

时域卷积

二维图像卷积操作:

图像卷积

卷积运算的目的是提取输入的不同特征,第一层卷积层可能只能提取一些低级的特征如边缘、线条和角等层级,更多层的网路能从低级特征中迭代提取更复杂的特征。

2.3.1 Conv1D

一维卷积层(即时域卷积),用以在一维输入信号上进行邻域滤波。


# Conv1D 定义

tf.keras.layers.Conv1D(

    filters, kernel_size, strides=1, padding='valid', data_format='channels_last',

    dilation_rate=1, groups=1, activation=None, use_bias=True,

    kernel_initializer='glorot_uniform', bias_initializer='zeros',

    kernel_regularizer=None, bias_regularizer=None, activity_regularizer=None,

    kernel_constraint=None, bias_constraint=None, **kwargs

)

参数
filters 输出空间的维度。即卷积中滤波器的输出数量
kernel_size 指明1D卷积窗口的长度
strides 指明卷积的步长
padding "valid", "causal" 或 "same" 之一
data_format 字符串, "channels_last" (默认) 或 "channels_first" 之一
dilation_rate 指定用于膨胀卷积的膨胀率
activation 要使用的激活函数
use_bias 布尔值,该层是否使用偏置向量
kernel_initializer kernel 权值矩阵的初始化器
bias_initializer 偏置向量的初始化器
kernel_regularizer 运用到 kernel 权值矩阵的正则化函数
bias_regularizer 运用到偏置向量的正则化函数
activity_regularizer 运用到层输出(它的激活值)的正则化函数
kernel_constraint 运用到 kernel 权值矩阵的约束函数
bias_constraint 运用到偏置向量的约束函数

举例说明:

import tensorflow as tf
from tensorflow import keras
# The inputs are 128-length vectors with 10 timesteps, and the batch size is 4.

input_shape = (4, 10, 128)  # 输入为 (batch_size, steps, input_dim)

x = tf.random.normal(input_shape)

y = tf.keras.layers.Conv1D(32, 3, activation='relu',input_shape=input_shape[1:])(x)  # filters=32, kernel_size=3

print(y.shape)  # 输出为 (batch_size, new_steps, filters)

结果输出:


(4, 8, 32)


2.3.2 Conv2D

2D 卷积层 (例如对图像的空间卷积)。


# Conv2D定义

tf.keras.layers.Conv2D(

    filters, kernel_size, strides=(1, 1), padding='valid', data_format=None,

    dilation_rate=(1, 1), groups=1, activation=None, use_bias=True,

    kernel_initializer='glorot_uniform', bias_initializer='zeros',

    kernel_regularizer=None, bias_regularizer=None, activity_regularizer=None,

    kernel_constraint=None, bias_constraint=None, **kwargs)

参数
filters 输出空间的维度
kernel_size 指明 2D 卷积窗口的宽度和高度
strides 指明卷积沿宽度和高度方向的步长
padding "valid" 或 "same"
data_format 字符串, "channels_last" (默认) 或 "channels_first" 之一
dilation_rate 指定膨胀卷积的膨胀率
activation 要使用的激活函数
use_bias 布尔值,该层是否使用偏置向量
kernel_initializer kernel 权值矩阵的初始化器
bias_initializer 偏置向量的初始化器
kernel_regularizer 运用到 kernel 权值矩阵的正则化函数
bias_regularizer 运用到偏置向量的正则化函数
activity_regularizer 运用到层输出(它的激活值)的正则化函数
kernel_constraint 运用到 kernel 权值矩阵的约束函数
bias_constraint 运用到偏置向量的约束函数

举例说明:

import tensorflow as tf
from tensorflow import keras
# The inputs are 28x28 RGB images with `channels_last` and the batch

# size is 4.

input_shape = (4, 28, 28, 3) #输入为(samples, rows, cols, channels)

x = tf.random.normal(input_shape)

y = tf.keras.layers.Conv2D(

2, 3, activation='relu', input_shape=input_shape[1:])(x)  # filters=2, kernel_size=3

print(y.shape)  #输出为(samples, new_rows, new_cols, filters)

结果输出:


(4, 26, 26, 2)


2.3.3 Conv3D

3D卷积层(例如体积上的空间卷积)


# Conv3D定义

tf.keras.layers.Conv3D(

    filters, kernel_size, strides=(1, 1, 1), padding='valid', data_format=None,

    dilation_rate=(1, 1, 1), groups=1, activation=None, use_bias=True,

    kernel_initializer='glorot_uniform', bias_initializer='zeros',

    kernel_regularizer=None, bias_regularizer=None, activity_regularizer=None,

    kernel_constraint=None, bias_constraint=None, **kwargs)

参数
filters 输出空间的维度
kernel_size 指明 2D 卷积窗口的宽度和高度以及深度
strides 指定沿每个空间维度的卷积步长
padding "valid" 或 "same"
data_format 字符串, "channels_last" (默认) 或 "channels_first" 之一
dilation_rate 指定膨胀卷积的膨胀率
activation 要使用的激活函数
use_bias 布尔值,该层是否使用偏置向量
kernel_initializer kernel 权值矩阵的初始化器
bias_initializer 偏置向量的初始化器
kernel_regularizer 运用到 kernel 权值矩阵的正则化函数
bias_regularizer 运用到偏置向量的正则化函数
activity_regularizer 运用到层输出(它的激活值)的正则化函数
kernel_constraint 运用到 kernel 权值矩阵的约束函数
bias_constraint 运用到偏置向量的约束函数

举例说明:

import tensorflow as tf
from tensorflow import keras
# The inputs are 28x28x28 volumes with a single channel, and the

# batch size is 4

input_shape =(4, 28, 28, 28, 1) #输入为 (batch_size, conv_dim1, conv_dim2, conv_dim3, channels)

x = tf.random.normal(input_shape)

y = tf.keras.layers.Conv3D(

2, 3, activation='relu', input_shape=input_shape[1:])(x) #filters=2, kernel_size=3

print(y.shape) #输出为 (batch_size, new_conv_dim1, new_conv_dim2, new_conv_dim3, filters)

结果输出:


(4, 26, 26, 26, 2)


2.3.4 SeparableConv1D

深度可分离1D卷积。该层执行分别作用在通道上的深度卷积,然后是混合通道的逐点卷积。 如果use_bias为True并提供了一个偏差初始值设定项,则它将偏差向量添加到输出中。 然后,它可选地应用激活函数以产生最终输出。


# SeparableConv1D定义

tf.keras.layers.SeparableConv1D(

    filters, kernel_size, strides=1, padding='valid', data_format=None,

    dilation_rate=1, depth_multiplier=1, activation=None, use_bias=True,

    depthwise_initializer='glorot_uniform', pointwise_initializer='glorot_uniform',

    bias_initializer='zeros', depthwise_regularizer=None,

    pointwise_regularizer=None, bias_regularizer=None, activity_regularizer=None,

    depthwise_constraint=None, pointwise_constraint=None, bias_constraint=None,

    **kwargs)


2.3.5 SeparableConv2D

深度可分离的2D卷积。可分离的卷积包括首先执行深度空间卷积(它分别作用于每个输入通道),然后是点向卷积,它将混合所得的输出通道。 depth_multiplier参数控制在深度步骤中每个输入通道生成多少个输出通道。

直观上,可分离的卷积可以理解为将卷积内核分解为两个较小内核的一种方式,或者是Inception块的一种极端版本。


# SeparableConv2D定义

tf.keras.layers.SeparableConv2D(

    filters, kernel_size, strides=(1, 1), padding='valid', data_format=None,

    dilation_rate=(1, 1), depth_multiplier=1, activation=None, use_bias=True,

    depthwise_initializer='glorot_uniform', pointwise_initializer='glorot_uniform',

    bias_initializer='zeros', depthwise_regularizer=None,

    pointwise_regularizer=None, bias_regularizer=None, activity_regularizer=None,

    depthwise_constraint=None, pointwise_constraint=None, bias_constraint=None,

    **kwargs)


2.3.6 Conv2DTranspose

转置卷积层 (有时被成为反卷积)。对转置卷积的需求一般来自希望使用 与正常卷积相反方向的变换,将具有卷积输出尺寸的东西 转换为具有卷积输入尺寸的东西, 同时保持与所述卷积相容的连通性模式。


# Conv2DTranspose定义

tf.keras.layers.Conv2DTranspose(

  filters, kernel_size, strides=(1, 1), padding='valid', output_padding=None,

data_format=None, dilation_rate=(1, 1), activation=None,use_bias=True,kernel_initializer='glorot_uniform', bias_initializer='zeros',      kernel_regularizer=None, bias_regularizer=None, activity_regularizer=None,

kernel_constraint=None, bias_constraint=None, **kwargs)



2.4 池化层 Pooling layers

池化层是模仿人的视觉系统对数据进行降维,用更高层次的特征表示图像。实施池化的目的:降低信息冗余;提升模型的尺度不变性、旋转不变性。 防止过拟合。

通常有最大池化层,平均池化层。

  • 最大池化层对每一个小区域选最最大值作为池化结果
  • 平均池化层选取平均值作为池化结果。
屏幕快照 2021-03-18 14.21.07.png

池化层有三种形态:1D 用于一维数据,2D 一般用于二维图像数据,3D 带时间序列数据的图像数据

池化层函数
MaxPooling1D 对于时序数据的最大池化
MaxPooling2D 对于空间数据的最大池化
MaxPooling3D 对于3D数据的最大池化
AveragePooling1D 对于时序数据的平均池化
AveragePooling2D 对于空间数据的平均池化
AveragePooling3D 对于3D数据的平均池化
GlobalMaxPooling1D 对于时序数据的全局最大池化
GlobalMaxPooling2D 对于空域数据的全局最大池化
GlobalAveragePooling1D 时序数据的全局平均池化
GlobalAveragePooling2D 空间数据的全局平均池化



2.5 循环层Recurrent layers

循环神经网络(Recurrent Neural Network, 简称 RNN),循环神经网络的提出便是基于记忆模型的想法,期望网络能够记住前面出现的特征,并依据特征推断后面的结果,而且整体的网络结构不断循环,因此得名为循环神经网络。

2.5.1 LSTM

长短期记忆网络(Long-Short Term Memory,LSTM)论文首次发表于1997年。由于独特的设计结构,LSTM适合于处理和预测时间序列中间隔和延迟非常长的重要事件。


# LSTM定义

tf.keras.layers.LSTM(

    units, activation='tanh', recurrent_activation='sigmoid', use_bias=True,

    kernel_initializer='glorot_uniform', recurrent_initializer='orthogonal',

    bias_initializer='zeros', unit_forget_bias=True, kernel_regularizer=None,

    recurrent_regularizer=None, bias_regularizer=None, activity_regularizer=None,

    kernel_constraint=None, recurrent_constraint=None, bias_constraint=None,

    dropout=0.0, recurrent_dropout=0.0, implementation=2, return_sequences=False,

    return_state=False, go_backwards=False, stateful=False, time_major=False,

    unroll=False, **kwargs)

参数
units 输出维度
activation 激活函数
recurrent_activation 为循环步施加的激活函数
use_bias 布尔值,是否使用偏置项
kernel_initializer 权值初始化方法,为预定义初始化方法名的字符串,或用于初始化权重的初始化器
recurrent_initializer 循环核的初始化方法,为预定义初始化方法名的字符串,或用于初始化权重的初始化器
bias_initializer 权值初始化方法,为预定义初始化方法名的字符串,或用于初始化权重的初始化器
kernel_regularizer 施加在权重上的正则项
bias_regularizer 施加在偏置向量上的正则项
recurrent_regularizer 施加在循环核上的正则项
activity_regularizer 施加在输出上的正则项
kernel_constraints 施加在权重上的约束项
recurrent_constraints 施加在循环核上的约束项
bias_constraints 施加在偏置上的约束项
dropout 0~1之间的浮点数,控制输入线性变换的神经元断开比例
recurrent_dropout 0~1之间的浮点数,控制循环状态的线性变换的神经元断开比例

举例说明:

import tensorflow as tf
from tensorflow  import keras
inputs = tf.random.normal([32, 10, 8])

lstm = tf.keras.layers.LSTM(4)

output = lstm(inputs)

print(output.shape)

lstm = tf.keras.layers.LSTM(4, return_sequences=True, return_state=True)

whole_seq_output, final_memory_state, final_carry_state = lstm(inputs)

print(whole_seq_output.shape)

print(final_memory_state.shape)

print(final_carry_state.shape)

结果输出:


(32, 4)

(32, 10, 4)

(32, 4)

(32, 4)


2.5.2 BRU

GRU门控循环单元- Cho et al. 2014.

在LSTM中引入了三个门函数:输入门、遗忘门和输出门来控制输入值、记忆值和输出值。而在GRU模型中只有两个门:分别是更新门和重置门。与LSTM相比,GRU内部少了一个”门控“,参数比LSTM少,但是却也能够达到与LSTM相当的功能。考虑到硬件的计算能力和时间成本,因而很多时候我们也就会选择更加”实用“的GRU。


# GRU定义

tf.keras.layers.GRU(

    units, activation='tanh', recurrent_activation='sigmoid', use_bias=True,

    kernel_initializer='glorot_uniform', recurrent_initializer='orthogonal',

    bias_initializer='zeros', kernel_regularizer=None, recurrent_regularizer=None,

    bias_regularizer=None, activity_regularizer=None, kernel_constraint=None,

    recurrent_constraint=None, bias_constraint=None, dropout=0.0,

    recurrent_dropout=0.0, implementation=2, return_sequences=False,

    return_state=False, go_backwards=False, stateful=False, unroll=False,

    time_major=False, reset_after=True, **kwargs )

参数
units 输出维度
activation 要使用的激活函数。默认:双曲正切 (tanh)。 如果传入 None,则不使用激活函数 (即 线性激活:a(x) = x)。
recurrent_activation 用于循环时间步的激活函数。默认:分段线性近似 sigmoid (hard_sigmoid)。 如果传入 None,则不使用激活函数 (即 线性激活:a(x) = x)。
use_bias 布尔值,该层是否使用偏置向量。
kernel_initializer kernel 权值矩阵的初始化器, 用于输入的线性转换
recurrent_initializer recurrent_kernel 权值矩阵 的初始化器,用于循环层状态的线性转换
bias_initializer 偏置向量的初始化器
kernel_regularizer 运用到 kernel 权值矩阵的正则化函数
recurrent_regularizer 运用到 recurrent_kernel 权值矩阵的正则化函数
bias_regularizer 运用到偏置向量的正则化函数
activity_regularizer 运用到层输出(它的激活值)的正则化函数
kernel_constraint 运用到 kernel 权值矩阵的约束函数
recurrent_constraint 运用到 recurrent_kernel 权值矩阵的约束函数
bias_constraint 运用到偏置向量的约束函数
dropout 在 0 和 1 之间的浮点数。 单元的丢弃比例,用于输入的线性转换
recurrent_dropout 在 0 和 1 之间的浮点数
implementation 实现模式,1 或 2。 模式 1 将把它的操作结构化为更多的小的点积和加法操作,而模式 2 将把它们分批到更少,更大的操作中
return_sequences 布尔值。是返回输出序列中的最后一个输出,还是全部序列
return_state 布尔值。除了输出之外是否返回最后一个状态
go_backwards 布尔值 (默认 False)。 如果为 True,则向后处理输入序列并返回相反的序列
stateful 布尔值 (默认 False)。 如果为 True,则批次中索引 i 处的每个样品的最后状态 将用作下一批次中索引 i 样品的初始状态
unroll 布尔值 (默认 False)。 如果为 True,则网络将展开,否则将使用符号循环。 展开可以加速 RNN,但它往往会占用更多的内存。 展开只适用于短序列
reset_after GRU公约 (是否在矩阵乘法之前或者之后使用重置门)。 False =「之前」(默认),Ture =「之后」( CuDNN 兼容)

举例说明:

improt tensorflow as tf
from tensorflow import keras
inputs = tf.random.normal([32, 10, 8])

gru = tf.keras.layers.GRU(4)

output = gru(inputs)

print(output.shape)

gru = tf.keras.layers.GRU(4, return_sequences=True, return_state=True)

whole_sequence_output, final_state = gru(inputs)

print(whole_sequence_output.shape)

print(final_state.shape)

结果输出:


(32, 4)

(32, 10, 4)

(32, 4)


2.5.3 RNN

循环神经网络层基类。


# RNN定义

tf.keras.layers.RNN(

    cell, return_sequences=False, return_state=False, go_backwards=False,

    stateful=False, unroll=False, time_major=False, **kwargs)

关于指定 RNN 初始状态的说明
您可以通过使用关键字参数 initial_state 调用它们来符号化地指定 RNN 层的初始状态。 initial_state 的值应该是表示 RNN 层初始状态的张量或张量列表。
可以通过调用带有关键字参数 states 的 reset_states 方法来数字化地指定 RNN 层的初始状态。 states 的值应该是一个代表 RNN 层初始状态的 Numpy 数组或者 Numpy 数组列表。

关于给 RNN 传递外部常量的说明
可以使用 RNN.call(以及 RNN.call)的 constants 关键字参数将「外部」常量传递给单元。 这要求 cell.call 方法接受相同的关键字参数 constants。 这些常数可用于调节附加静态输入(不随时间变化)上的单元转换,也可用于注意力机制。

参数
cell 一个 RNN 单元实例
return_sequences 布尔值。是返回输出序列中的最后一个输出,还是全部序列
return_state 布尔值。除了输出之外是否返回最后一个状态
go_backwards 布尔值 (默认 False)。 如果为 True,则向后处理输入序列并返回相反的序列
stateful 布尔值 (默认 False)。 如果为 True,则批次中索引 i 处的每个样品的最后状态将用作下一批次中索引 i 样品的初始状态
unroll 布尔值 (默认 False)。 如果为 True,则网络将展开,否则将使用符号循环
input_dim 输入的维度
input_length 输入序列的长度

举例说明:


# First, let's define a RNN Cell, as a layer subclass.

# 首先,让我们定义一个 RNN 单元,作为网络层子类。

import tensorflow as tf

from keras import backend as K

class MinimalRNNCell(keras.layers.Layer):

    def __init__(self, units, **kwargs):

        self.units = units

        self.state_size = units

        super(MinimalRNNCell, self).__init__(**kwargs)

    def build(self, input_shape):

        self.kernel = self.add_weight(shape=(input_shape[-1], self.units),

                                      initializer='uniform',

                                      name='kernel')

        self.recurrent_kernel = self.add_weight(

            shape=(self.units, self.units),

            initializer='uniform',

            name='recurrent_kernel')

        self.built = True

    def call(self, inputs, states):

        prev_output = states[0]

        h = K.dot(inputs, self.kernel)

        output = h + K.dot(prev_output, self.recurrent_kernel)

        return output, [output]

# 让我们在 RNN 层使用这个单元:

cell = MinimalRNNCell(32)

x = keras.Input((None, 5))

layer = keras.layers.RNN(cell)

y = layer(x)

# 以下是如何使用单元格构建堆叠的 RNN的方法:

cells = [MinimalRNNCell(32), MinimalRNNCell(64)]

x = keras.Input((None, 5))

layer = keras.layers.RNN(cells)

y = layer(x)



3 Compile编译

在训练模型之前,我们需要配置学习过程,这是通过compile方法完成的。

他接收三个参数:优化器 optimizer, 损失函数 loss, 评估标准 metrics


#Compile函数定义:

compile(

    optimizer='', loss=None, metrics=None, loss_weights=None,

    weighted_metrics=None, run_eagerly=None, **kwargs

)

  • 优化器 optimizer:它可以是现有优化器的字符串标识符,如 rmsprop 或 adagrad,也可以是 Optimizer 类的实例。

  • 损失函数 loss:模型试图最小化的目标函数。它可以是现有损失函数的字符串标识符,如 categorical_crossentropy 或 mse,也可以是一个目标函数。

  • 评估标准 metrics:对于任何分类问题,你都希望将其设置为 metrics = ['accuracy']。评估标准可以是现有的标准的字符串标识符,也可以是自定义的评估标准函数。

3.1 优化器 optimizer

优化器 会将计算出的梯度应用于模型的变量,以使 loss 函数最小化。您可以将损失函数想象为一个曲面(见图 3),我们希望通过到处走动找到该曲面的最低点。梯度指向最高速上升的方向,因此我们将沿相反的方向向下移动。我们以迭代方式计算每个批次的损失和梯度,以在训练过程中调整模型。模型会逐渐找到权重和偏差的最佳组合,从而将损失降至最低。损失越低,模型的预测效果就越好。

优化算法在三维空间中随时间推移而变化的可视化效果。

(来源: 斯坦福大学 CS231n 课程,MIT 许可证,Image credit: Alec Radford)

optimizer.gif
Adadelta

一种随机梯度下降方法,它基于每个维度的自适应学习率来解决两个缺点:
整个培训期间学习率的持续下降
需要手动选择的整体学习率

Adagrad

Adagrad是一种优化器,具有特定于参数的学习率,相对于训练期间更新参数的频率进行调整。 参数接收的更新越多,更新越小。

Adam

Adam优化是一种基于随机估计的一阶和二阶矩的随机梯度下降方法。

根据Kingma等人的说法(2014年),该方法“计算效率高,内存需求少,不影响梯度的对角线重缩放,并且非常适合数据/参数较大的问题”。

Adamax

Adamax优化是基于无穷范数的Adam的变体。 默认参数遵循本文提供的参数。 Adammax有时优于Adam,特别是在带有嵌入Embedding的模型中

FTRL

实现FTRL算法的优化程序。请参阅本文的算法。此版本同时支持在线L2(上面的论文中给出了L2损失)和收缩类型L2(这是对损失函数增加L2损失)的支持。

RMSprop

该优化器通常是面对递归神经网络时的一个良好选择。

RMSprop的要旨是:

  • 保持梯度平方的移动(折后)平均值
  • 将梯度除以该平均值的根
  • RMSprop的此实现使用简单动量,而不使用Nesterov动量。
  • 居中版本还保留了梯度的移动平均值,并使用该平均值来估计方差。
  • momentum即动量,它模拟的是物体运动时的惯性,即更新的时候在一定程度上保留之前更新的方向,同时利用当前batch的梯度微调最终的更新方向。这样一来,可以在一定程度上增加稳定性,从而学习地更快,并且还有一定摆脱局部最优的能力
Nesterov

Nesterov 版本 Adam 优化器。 正像 Adam 本质上是 RMSProp 与动量 momentum 的结合, Nadam 是采用 Nesterov momentum 版本的 Adam 优化器。

SGD

随机梯度下降法,支持动量参数,支持学习衰减率,支持Nesterov动量。


3.2 损失函数 losses

损失函数的目的是计算模型在训练过程中寻求最小化。损失函数是模型优化的目标,所以又叫目标函数、优化评分函数。

损失函数分为三大类:概率损失,回归损失,合页损失。

概率损失 回归损失 合页损失
BinaryCrossentropy MeanSquaredError class Hinge class
CategoricalCrossentropy class MeanAbsoluteError class SquaredHinge class
SparseCategoricalCrossentropy class MeanAbsolutePercentageError class CategoricalHinge class
Poisson class CosineSimilarity class hinge function
binary_crossentropy function mean_squared_error function squared_hinge function
categorical_crossentropy function mean_absolute_error function categorical_hinge function
sparse_categorical_crossentropy function mean_absolute_percentage_error function
poisson function mean_squared_logarithmic_error function
KLDivergence class cosine_similarity function
kl_divergence function Huber class
huber function
LogCosh class
log_cosh function
BinaryCrossentropy

计算真实标签和预测标签之间的交叉熵损失。当只有两个标签类别(假定为0和1)时,请使用此交叉熵损失。

CategoricalCrossentropy

计算标签和预测之间的交叉熵损失。有两个或多个标签类别时,请使用此交叉熵损失函数。

CategoricalHinge

计算y_true和y_pred之间的分类合页损失。

CosineSimilarity

计算标签和预测之间的余弦相似度。

请注意,它是介于-1和0之间的负数,其中0表示正交性,而值接近-1则表示更大的相似性。 这使得它在尝试使预测值与目标值之间的接近度最大化的情况下可用作损失函数。

MeanAbsoluteError

计算标签和预测之间的绝对差的平均值。loss = abs(y_true - y_pred)

MeanAbsolutePercentageError

计算y_true和y_pred之间的平均绝对百分比误差。loss = 100 * abs(y_true - y_pred) / y_true

MeanSquaredError

计算标签和预测之间的误差平方的平均值。loss = square(y_true - y_pred)


3.3 评估标准 metrics

评价函数用于评估当前训练模型的性能。性能评估模块提供了一系列用于模型性能评估的函数,这些函数在模型编译时由metrics关键字设置 性能评估函数类似与目标函数, 只不过该性能的评估结果讲不会用于训练。

可以通过字符串来使用域定义的性能评估函数:


model.compile(loss='mean_squared_error',

              optimizer='sgd',

              metrics=['mae', 'acc'])

准确率评价 概率评价 回归评价 分类评价 合页评价
Accuracy class BinaryCrossentropy class MeanSquaredError class AUC class Hinge class
BinaryAccuracy class CategoricalCrossentropy class RootMeanSquaredError class Precision class SquaredHinge class
CategoricalAccuracy class SparseCategoricalCrossentropy class MeanAbsoluteError class TruePositives class CategoricalHinge class
TopKCategoricalAccuracy class KLDivergence class MeanAbsolutePercentageError class TrueNegatives class
SparseTopKCategoricalAccuracy class Poisson class MeanSquaredLogarithmicError class FalsePositives class
CosineSimilarity class FalseNegatives class
LogCoshError class PrecisionAtRecall class
SensitivityAtSpecificity class
SpecificityAtSensitivity class
Accuracy

计算预测等于标签的频率。

该度量创建两个局部变量,总计和计数,用于计算y_pred与y_true匹配的频率。 该频率最终以二进制精度返回:幂运算,将总数除以计数。

BinaryAccuracy

计算预测与二进制标签匹配的频率。该度量创建两个局部变量,总计和计数,用于计算y_pred与y_true匹配的频率。该频率最终以二进制精度返回:幂运算,将总数除以计数。

CategoricalAccuracy

计算预测与one-hot标签匹配的频率。该度量创建两个局部变量,总计和计数,用于计算y_pred与y_true匹配的频率。该频率最终以绝对精度返回:幂等运算,简单地将总数除以计数。

FalseNegatives

计算假阴性的数量。

FalsePositives

计算误报的数量。

Mean

计算给定值的(加权)平均值。

MeanAbsoluteError

计算标签和预测之间的平均绝对误差。

MeanSquaredError

计算y_true和y_pred之间的均方误差。


4 训练模型 fit

为模型训练固定的epochs(数据集上的迭代)。


# fit定义

fit(

    x=None, y=None, batch_size=None, epochs=1, verbose=1, callbacks=None,

    validation_split=0.0, validation_data=None, shuffle=True, class_weight=None,

    sample_weight=None, initial_epoch=0, steps_per_epoch=None,

    validation_steps=None, validation_batch_size=None, validation_freq=1,

    max_queue_size=10, workers=1, use_multiprocessing=False

)

  • x:输入数据。如果模型只有一个输入,那么x的类型是numpy array,如果模型有多个输入,那么x的类型应当为list,list的元素是对应于各个输入的numpy array
  • y:标签,numpy array
  • batch_size:整数,指定进行梯度下降时每个batch包含的样本数。训练时一个batch的样本会被计算一次梯度下降,使目标函数优化一步。
  • epochs:整数,训练终止时的epoch值,训练将在达到该epoch值时停止,当没有设置initial_epoch时,它就是训练的总轮数,否则训练的总轮数为epochs - inital_epoch
  • verbose:日志显示,0为不在标准输出流输出日志信息,1为输出进度条记录,2为每个epoch输出一行记录
  • callbacks:list,其中的元素是keras.callbacks.Callback的对象。这个list中的回调函数将会在训练过程中的适当时机被调用,参考回调函数
  • validation_split:0~1之间的浮点数,用来指定训练集的一定比例数据作为验证集。验证集将不参与训练,并在每个epoch结束后测试的模型的指标,如损失函数、精确度等。注意,validation_split的划分在shuffle之前,因此如果你的数据本身是有序的,需要先手工打乱再指定validation_split,否则可能会出现验证集样本不均匀。
  • validation_data:形式为(X,y)的tuple,是指定的验证集。此参数将覆盖validation_spilt。
  • shuffle:布尔值或字符串,一般为布尔值,表示是否在训练过程中随机打乱输入样本的顺序。若为字符串“batch”,则是用来处理HDF5数据的特殊情况,它将在batch内部将数据打乱。
  • class_weight:字典,将不同的类别映射为不同的权值,该参数用来在训练过程中调整损失函数(只能用于训练)
  • sample_weight:权值的numpy array,用于在训练时调整损失函数(仅用于训练)。可以传递一个1D的与样本等长的向量用于对样本进行1对1的加权,或者在面对时序数据时,传递一个的形式为(samples,sequence_length)的矩阵来为每个时间步上的样本赋不同的权。这种情况下请确定在编译模型时添加了sample_weight_mode='temporal'。
  • initial_epoch: 从该参数指定的epoch开始训练,在继续之前的训练时有用。
  • fit函数返回一个History的对象,其History.history属性记录了损失函数和其他指标的数值随epoch变化的情况,如果有验证集的话,也包含了验证集的这些指标变化情况。


5 评估模型 evaluate

在测试模式下返回模型的误差值和评估标准值。计算逐批次进行。


# evaluate定义

evaluate(

    x=None, y=None, batch_size=None, verbose=1, sample_weight=None, steps=None,

    callbacks=None, max_queue_size=10, workers=1, use_multiprocessing=False,

    return_dict=False)

  • x:输入数据。它可能是:Numpy数组(或类似数组的数组)或数组列表(如果模型具有多个输入)。TensorFlow张量或张量列表(如果模型具有多个输入)。

  • y:目标数据。像输入数据x一样,它可以是Numpy数组或TensorFlow张量。它应该与x一致

  • batch_size:整数或无。 每批计算的样本数。 如果未指定,batch_size将默认为32。如果数据是以数据集,生成器或keras.utils.Sequence实例的形式(因为它们生成批次),则不要指定batch_size。

  • sample_weight:测试样本的可选Numpy权重数组,用于加权损失函数。

  • steps:整数或无。宣布评估阶段结束之前的步骤总数(样本批次)。

  • callbacks:评估期间要应用的回调列表。

  • max_queue_size:整数。仅用于generator或keras.utils.Sequence输入。生成器队列的最大大小。如果未指定,max_queue_size将默认为10。

  • workers:整数。仅用于generator或keras.utils.Sequence输入。使用基于进程的线程时,要启动的最大进程数。 如果未指定,worker将默认为1。如果为0,将在主线程上执行生成器。

  • use_multiprocessing:布尔值。仅用于generator或keras.utils.Sequence输入。如果为True,则使用基于进程的线程。 如果未指定,则use_multiprocessing将默认为False。 请注意,由于此实现依赖于多处理,因此不应将不可拾取的参数传递给生成器,因为它们无法轻易传递给子进程

  • return_dict:如果为True,则将损失和指标结果作为dict返回,每个键都是指标的名称。 如果为False,则将它们作为列表返回。


6 预测模型 predict

生成输入样本的输出预测。


# predict定义

predict(

    x, batch_size=None, verbose=0, steps=None,

    callbacks=None, max_queue_size=10,

    workers=1, use_multiprocessing=False

)

  • x:输入样本。它可能是:Numpy数组(或类似数组的数组)或数组列表(如果模型具有多个输入)。TensorFlow张量或张量列表(如果模型具有多个输入)。

  • batch_size:整数或无。 每批样品数。 如果未指定,batch_size将默认为32

  • verbose:详细模式,0或1。

  • steps:宣布预测回合完成之前的步骤总数(样本批次)。

  • callbacks:预测期间要应用的回调列表。

  • max_queue_size:整数。 仅用于generator或keras.utils.Sequence输入。 生成器队列的最大值。如果未指定,max_queue_size将默认为10。

  • workers: 仅用于generator或keras.utils.Sequence输入。 使用基于进程的线程时,要启动的最大进程数。 如果未指定,worker将默认为1。如果为0,将在主线程上执行生成器。

  • use_multiprocessing:布尔值。 仅用于generator或keras.utils.Sequence输入。 如果为True,则使用基于进程的线程。 如果未指定,则use_multiprocessing将默认为False。 请注意,由于此实现依赖于多处理,因此不应将不可拾取的参数传递给生成器,因为它们无法轻易传递给子进程。


7 模型保存

模型保存可以在训练期间和训练之后。这意味着模型可以从中断的地方继续进行,避免了长时间的训练。 保存还意味着您可以共享模型,其他人可以重新创建您的作品。

保存整个模型

您可以调用save_model函数 将整个模型保存到单个工件中。它将包括:

  • 模型的架构/配置

  • 模型的权重值(在训练过程中学习)

  • 模型的编译信息(如果调用了 compile())

  • 优化器及其状态(如果有的话,使您可以从上次中断的位置重新开始训练)


tf.keras.models.save_model(

    model, filepath, overwrite=True, include_optimizer=True, save_format=None,

    signatures=None, options=None

)

例子:


import tensorflow as tf

model = tf.keras.Sequential([tf.keras.layers.Dense(5, input_shape=(3,)),tf.keras.layers.Softmax()])

model.save('/tmp/model')

model.save("my_h5_model.h5")

保存架构

调用to_json()函数保存指定模型包含的层,以及这些层的连接方式。


from tensorflow import keras

model = keras.Sequential([keras.Input((32,)), keras.layers.Dense(1)])

json_config = model.to_json()

new_model = keras.models.model_from_json(json_config)

保存权重值

save_weights()函数可以选择仅保存和加载模型的权重。


# Runnable example

from tensorflow import keras

sequential_model = keras.Sequential(

    [

        keras.Input(shape=(784,), name="digits"),

        keras.layers.Dense(64, activation="relu", name="dense_1"),

        keras.layers.Dense(64, activation="relu", name="dense_2"),

        keras.layers.Dense(10, name="predictions"),

    ]

)

sequential_model.save_weights("weights.h5")

sequential_model.load_weights("weights.h5")

实际例子代码下载:https://github.com/wennaz/Deep_Learning

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 157,298评论 4 360
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 66,701评论 1 290
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 107,078评论 0 237
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 43,687评论 0 202
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 52,018评论 3 286
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,410评论 1 211
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,729评论 2 310
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,412评论 0 194
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,124评论 1 239
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,379评论 2 242
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 31,903评论 1 257
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,268评论 2 251
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 32,894评论 3 233
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,014评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,770评论 0 192
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,435评论 2 269
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,312评论 2 260

推荐阅读更多精彩内容