TensorFlow学习笔记(1)——低级API

学习目标

  • 张量
  • 变量
  • 保存和恢复

基本概念

TensorFlow的核心数据单位是张量,TensorFlow Core程序可以看作是两个相互独立的部分组成:构建计算图,运行计算图。

张量

一个张量由一组阵列(任意维数)的原始值组成。张量的阶是它的维数,而它的的形状是一个整数元组,制定了阵列每个维度的长度。

1 #0阶张量(标量)
[1,2] #1阶张量(向量)
[[1,2],[3,4]] #2阶张量(矩阵)
......
tf.Tensor具有的属性:
  • 数据类型(float32,int,string)
  • 形状

张量中的每个元素都具有相同的数据类型,且数据类型一定是已知的。形状可能是部分已知。

a = tf.constant(3.0, dtype=tf.float32) #定义一个常量
b = tf.constant(4.0) # 数据类型也是tf.float32
常用的特殊张量:
  • tf.Variable
  • tf.constant
  • tf.placeholder
  • tf.SparseTensor
    除tf.Variable意外,张量的值不可变。但同一张量在读取随机数等情况下可能返回不同值。

tf.Tensor的阶就是它本身的维数,和数学中矩阵的阶并不是同一个概念。

数学实例
0 标量
1 向量
2 矩阵
3 数据立体
n n阶张量(脑补)
  • 0阶
mammal = tf.Variable("Elephant", tf.string)
ignition = tf.Variable(451, tf.int16)
floating = tf.Variable(3.14159265359, tf.float64)
its_complicated = tf.Variable(12.3 - 4.85j, tf.complex64)
  • 1阶
mystr = tf.Variable(["Hello"], tf.string)
cool_numbers  = tf.Variable([3.14159, 2.71828], tf.float32)
first_primes = tf.Variable([2, 3, 5, 7, 11], tf.int32)
its_very_complicated = tf.Variable([12.3 - 4.85j, 7.5 - 6.23j], tf.complex64)
  • 2阶
mymat = tf.Variable([[7],[11]], tf.int16)
myxor = tf.Variable([[False, True],[True, False]], tf.bool)
linear_squares = tf.Variable([[4], [9], [16], [25]], tf.int32)
squarish_squares = tf.Variable([ [4, 9], [16, 25] ], tf.int32)
rank_of_squares = tf.rank(squarish_squares)
mymatC = tf.Variable([[7],[11]], tf.int32)
获取阶
r = tf.rank(my_image) #计算图运行后,r将返回4
切片
my_scalar = my_vector[2] #1阶,返回标量
my_scalar = my_matrix[1, 2] #2阶,返回标量
my_row_vector = my_matrix[2] #2阶,返回行
my_column_vector = my_matrix[:, 3] #2阶,返回列
形状

张量的形状是每个维度中元素的数量

实例 形状 维数 说明
1 0 [] 0-D 0维(阶)张量
[1,2] 1 [2] 1-D 形状为[2]的1维(阶)张量
[[1,2],[3,4,5]] 2 [2,3] 2-D 形状为[2,3]的2维(阶)张量
[[[1,2],[3,4]],[[5,6,7],[8,9,10]]] 3 [2,2,2] 3-D 形状为[2,2,2]的3维(阶)张量
****** n [D0,...,Dn-1] n-D 形状为[D0,...,Dn-1]的n维(阶)张量
获取形状
c = tf.constant([1,2])
printf(c.shape) #方法1,返回(2,)

tf.shape(c) #方法2,返回TensorShapr目标,<tf.Tensor 'Shape:0' shape=(1,) dtype=int32>
改变形状

张量的元素数量是其所有形状大小的乘积,标量的元素数量永远是1。

rank_three_tensor = tf.ones([3, 4, 5]) #创建一个3维,形状为(3,4,5)的张量
matrix = tf.reshape(rank_three_tensor, [6, 10]) #重构成一个2维,形状为(6,10)的张量
matrixB = tf.reshape(matrix, [3, -1]) #重构成一个2维,形状为(3,20)的张量
matrixAlt = tf.reshape(matrixB, [4, 3, -1]) #重构成一个3维,形状为(4,3,,5)的张量
yet_another = tf.reshape(matrixAlt, [13, 2, -1]) #元素数量不匹配,报错

数据类型
float_tensor = tf.cast(tf.constant([1, 2, 3]), dtype=tf.float32) #将int转换为float32
printf(tf.constant([1, 2, 3]).dtype) #返回数据类型
数据类型 Python类型 描述
DT_FLOAT tf.float32 32 位浮点数.
DT_DOUBLE tf.float64 64 位浮点数.
DT_INT64 tf.int64 64 位有符号整型.
DT_INT32 tf.int32 32 位有符号整型.
DT_INT16 tf.int16 16 位有符号整型.
DT_INT8 tf.int8 8 位有符号整型.
DT_UINT8 tf.uint8 8 位无符号整型.
DT_STRING tf.string 可变长度的字节数组.每一个张量元素都是一个字节数组.
DT_BOOL tf.bool 布尔型.
DT_COMPLEX64 tf.complex64 由两个32位浮点数组成的复数:实数和虚数.
DT_QINT32 tf.qint32 用于量化Ops的32位有符号整型.
DT_QINT8 tf.qint8 用于量化Ops的8位有符号整型.
DT_QUINT8 tf.quint8 用于量化Ops的8位无符号整型.
评估张量
t = tf.constant(42.0)
u = tf.constant(37.0)
tu = tf.mul(t, u)
ut = tf.mul(u, t)
with sess.as_default():
   tu.eval()  # 执行一步
   ut.eval()  # 执行一步
   sess.run([tu, ut])  # 一步执行两个张量
打印张量

调试用

x=tf.constant([2,3,4,5])  
x=tf.Print(x,[x,x.shape,'any thing i want'],message='Debug message:',summarize=100)   
with tf.Session() as sess:  
    sess.run(x)  


输出:Debug message:[2 3 4 5][4][any thing i want]

变量

创建变量
my_variable = tf.get_variable("my_variable", [1, 2, 3]) #初始值通过tf.glorot_uniform_initializer随机设置
my_int_variable = tf.get_variable("my_int_variable", [1, 2, 3], dtype=tf.int32,initializer=tf.zeros_initializer) #指定类型和初始化器
other_variable = tf.get_variable("other_variable", dtype=tf.int32,initializer=tf.constant([23, 42])) #使用张量的类型
变量集合

Tensorflow提供集合,放置变量。默认情况下,每个 tf.Variable 都放置在以下两个集合中:

  • tf.GraphKeys.GLOBAL_VARIABLES - 可以在多个设备共享的变量
  • tf.GraphKeys.TRAINABLE_VARIABLES - TensorFlow 将计算其梯度的变量。
my_local = tf.get_variable("my_local", shape=(),collections=[tf.GraphKeys.LOCAL_VARIABLES]) # 添加到 tf.GraphKeys.LOCAL_VARIABLES 集合中
my_non_trainable = tf.get_variable("my_non_trainable",shape=(),trainable=False) #  添加到 tf.GraphKeys.LOCAL_VARIABLES 集合中
tf.add_to_collection("my_collection_name", my_local) #添加到自己命名的my_collection_name集合中
tf.get_collection("my_collection_name") #检索该集合下所有变量
设备放置方式
###将变量放置在第二个GPU设备上
with tf.device("/device:GPU:1"):
   v = tf.get_variable("v", [1]) 
分布式设置

以后再更

初始化变量
### 初始化tf.GraphKeys.GLOBAL_VARIABLES 集合中所有变量
# 创建两个变量
weights = tf.Variable(tf.random_normal([784, 200], stddev=0.35),
                      name="weights")
biases = tf.Variable(tf.zeros([200]), name="biases")
...
# 添加用于初始化变量的节点
init_op = tf.global_variables_initializer()

# 然后,在加载模型的时候
with tf.Session() as sess:
  # 运行初始化操作
  sess.run(init_op)
  ...
  # 使用模型
  ...

### 自行初始化变量
session.run(my_variable.initializer)
print(session.run(tf.report_uninitialized_variables())) #查询哪些变量尚未初始化

默认的 tf.global_variables_initializer 不会指定变量的初始化顺序。因此,如果变量的初始值取决于另一变量的值,那么很有可能会出现错误。

# 使用随机数创建一个变量
weights = tf.Variable(tf.random_normal([784, 200], stddev=0.35),name="weights")
# 创建另一个变量,它与weights拥有相同的初始值
w2 = tf.Variable(weights.initialized_value(), name="w2")
# 创建另一个变量,它的初始值是weights的两倍
w_twice = tf.Variable(weights.initialized_value() * 2.0, name="w_twice")
使用变量
v = tf.get_variable("v", shape=(), initializer=tf.zeros_initializer())
w = v + 1
###为变量赋值
v = tf.get_variable("v", shape=(), initializer=tf.zeros_initializer())
assignment = v.assign_add(1)
tf.global_variables_initializer().run()
sess.run(assignment)  # or assignment.op.run(), or assignment.eval()
v = tf.get_variable("v", shape=(), initializer=tf.zeros_initializer())
assignment = v.assign_add(1)
with tf.control_dependencies([assignment]):
    w = v.read_value() #w在assign_add操作后反映v的值
共享变量
###创建一个卷积层
def conv_relu(input, kernel_shape, bias_shape):
    # Create variable named "weights".
    weights = tf.get_variable("weights", kernel_shape,
        initializer=tf.random_normal_initializer())
    # Create variable named "biases".
    biases = tf.get_variable("biases", bias_shape,
        initializer=tf.constant_initializer(0.0))
    conv = tf.nn.conv2d(input, weights,
        strides=[1, 1, 1, 1], padding='SAME')
    return tf.nn.relu(conv + biases)
###由于期望的操作不清楚(创建新变量还是重新使用现有变量?),因此 TensorFlow 将会失败。
input1 = tf.random_normal([1,10,10,32])
input2 = tf.random_normal([1,20,20,32])
x = conv_relu(input1, kernel_shape=[5, 5, 32, 32], bias_shape=[32])
x = conv_relu(x, kernel_shape=[5, 5, 32, 32], bias_shape = [32])  # This fails.
用变量域实现共享参数

这里主要包括两个函数接口:`

  • tf.get_variable(<name>, <shape>, <initializer>) :根据指定的变量名实例化或返回一个 tensor 对象
  • tf.variable_scope(<scope_name>):管理 tf.get_variable() 变量的域名

tf.get_variable() 的机制跟 tf.Variable() 有很大不同,如果指定的变量名已经存在(即先前已经用同一个变量名通过 get_variable() 函数实例化了变量),那么 get_variable()只会返回之前的变量,否则才创造新的变量。

def conv_relu(input, kernel_shape, bias_shape):
    # Create variable named "weights".
    weights = tf.get_variable("weights", kernel_shape,
        initializer=tf.random_normal_initializer())
    # Create variable named "biases".
    biases = tf.get_variable("biases", bias_shape,
        initializer=tf.constant_initializer(0.0))
    conv = tf.nn.conv2d(input, weights,
        strides=[1, 1, 1, 1], padding='SAME')
    return tf.nn.relu(conv + biases)


def my_image_filter(input_images):
    with tf.variable_scope("conv1"):
        # Variables created here will be named "conv1/weights", "conv1/biases".
        relu1 = conv_relu(input_images, [5, 5, 32, 32], [32])
    with tf.variable_scope("conv2"):
        # Variables created here will be named "conv2/weights", "conv2/biases".
        return conv_relu(relu1, [5, 5, 32, 32], [32])

先定义一个 conv_relu() 函数,用 tf.variable_scope() 来分别处理两个卷积层的参数。正如注释中提到的那样,这个函数会在内部的变量名前面再加上一个「scope」前缀,比如:conv1/weights表示第一个卷积层的权值参数。这样一来,我们就可以通过域名来区分各个层之间的参数了。
不过,如果直接这样调用 my_image_filter,是会抛异常的:

result1 = my_image_filter(image1)
result2 = my_image_filter(image2)
# Raises ValueError(... conv1/weights already exists ...)

因为 tf.get_variable()虽然可以共享变量,但默认上它只是检查变量名,防止重复。要开启变量共享,你还必须指定在哪个域名内可以共用变量:

with tf.variable_scope("image_filters") as scope:
    result1 = my_image_filter(image1)
    scope.reuse_variables()
    result2 = my_image_filter(image2)

到这一步,共享变量的工作就完成了。你甚至都不用在函数外定义变量,直接调用同一个函数并传入不同的域名,就可以让 TensorFlow 来帮你管理变量了。

若部分变量共享,部分不共享:

def test(mode):
    w = tf.get_variable(name=mode+"w", shape=[1,2])
    u = tf.get_variable(name="u", shape=[1,2])
    return w, u

with tf.variable_scope("test", reuse=tf.AUTO_REUSE) as scope:
    w1, u1 = test("mode1")
    w2, u2 = test("mode2")

这里只是加了一个参数 reuse=tf.AUTO_REUSE,但正如名字所示,这是一种自动共享的机制,当系统检测到我们用了一个之前已经定义的变量时,就开启共享,否则就重新创建变量。

变量域的工作机理

首先,TensorFlow 会判断是否要共享变量,也就是判断 tf.get_variable_scope().reuse 的值,如果结果为 False(即你没有在变量域内调用scope.reuse_variables()),那么 TensorFlow 认为你是要初始化一个新的变量,紧接着它会判断这个命名的变量是否存在。如果存在,会抛出 ValueError 异常,否则,就根据 initializer 初始化变量:

with tf.variable_scope("foo"):
    v = tf.get_variable("v", [1])
assert v.name == "foo/v:0"

而如果 tf.get_variable_scope().reuse == True,那么 TensorFlow 会执行相反的动作,就是到程序里面寻找变量名为 scope name + name 的变量,如果变量不存在,会抛出 ValueError 异常,否则,就返回找到的变量:

with tf.variable_scope("foo"):
    v = tf.get_variable("v", [1])
with tf.variable_scope("foo", reuse=True):
    v1 = tf.get_variable("v", [1])
assert v1 is v
变量域的基本使用
  • 变量域可以嵌套使用
with tf.variable_scope("foo"):
    with tf.variable_scope("bar"):
        v = tf.get_variable("v", [1])
assert v.name == "foo/bar/v:0"

我们也可以通过 tf.get_variable_scope() 来获得当前的变量域对象,并通过 reuse_variables() 方法来设置是否共享变量。不过,TensorFlow 并不支持将 reuse 值设为 False,如果你要停止共享变量,可以选择离开当前所在的变量域,或者再进入一个新的变量域(比如,再进入一个 with 语句,然后指定新的域名)。
还需注意的一点是,一旦在一个变量域内将 reuse 设为 True,那么这个变量域的子变量域也会继承这个 reuse 值,自动开启共享变量:

with tf.variable_scope("root"):
    # At start, the scope is not reusing.
    assert tf.get_variable_scope().reuse == False
    with tf.variable_scope("foo"):
        # Opened a sub-scope, still not reusing.
        assert tf.get_variable_scope().reuse == False
    with tf.variable_scope("foo", reuse=True):
        # Explicitly opened a reusing scope.
        assert tf.get_variable_scope().reuse == True
        with tf.variable_scope("bar"):
            # Now sub-scope inherits the reuse flag.
            assert tf.get_variable_scope().reuse == True
    # Exited the reusing scope, back to a non-reusing one.
    assert tf.get_variable_scope().reuse == False
捕获变量域对象

如果一直用字符串来区分变量域,写起来容易出错。为此,TensorFlow 提供了一个变量域对象来帮助我们管理代码:

with tf.variable_scope("foo") as foo_scope:
    v = tf.get_variable("v", [1])
with tf.variable_scope(foo_scope)
    w = tf.get_variable("w", [1])
with tf.variable_scope(foo_scope, reuse=True)
    v1 = tf.get_variable("v", [1])
    w1 = tf.get_variable("w", [1])
assert v1 is v
assert w1 is w

记住,用这个变量域对象还可以让我们跳出当前所在的变量域区域:

with tf.variable_scope("foo") as foo_scope:
    assert foo_scope.name == "foo"
with tf.variable_scope("bar")
    with tf.variable_scope("baz") as other_scope:
        assert other_scope.name == "bar/baz"
        with tf.variable_scope(foo_scope) as foo_scope2:
            assert foo_scope2.name == "foo"  # Not changed.
在变量域内初始化变量

每次初始化变量时都要传入一个 initializer,这实在是麻烦,而如果使用变量域的话,就可以批量初始化参数了:

with tf.variable_scope("foo", initializer=tf.constant_initializer(0.4)):
    v = tf.get_variable("v", [1])
    assert v.eval() == 0.4  # Default initializer as set above.
    w = tf.get_variable("w", [1], initializer=tf.constant_initializer(0.3)):
    assert w.eval() == 0.3  # Specific initializer overrides the default.
    with tf.variable_scope("bar"):
        v = tf.get_variable("v", [1])
        assert v.eval() == 0.4  # Inherited default initializer.
    with tf.variable_scope("baz", initializer=tf.constant_initializer(0.2)):
        v = tf.get_variable("v", [1])
        assert v.eval() == 0.2  # Changed default initializer.

TensorFlow 使用数据流图将计算表示为独立的指令之间的依赖关系。这可生成低级别的编程模型,在该模型中,您首先定义数据流图,然后创建 TensorFlow 会话,以便在一组本地和远程设备上运行图的各个部分。

为什么使用数据流图

计算图是排列成一个图的一系列TensorFlow指令。
tf.Graph包含两类相关信息:

  • 图结构。由两种类型的对象组成:
    • 指令:图的节点。消耗和生成张量的计算。
    • 张量:图的边。代表流经图的值。
  • 图集合。
构建tf.Graph

tf.Graph 对象为其包含的 tf.Operation对象定义一个命名空间。TensorFlow 会自动为您的图中的每个指令选择一个唯一名称,但您也可以指定描述性名称,使您的程序阅读和调试起来更轻松。TensorFlow API 提供两种方法来改写指令的名称:

  • 每个创建新的 tf.Operation 或返回新的 tf.Tensor的 API 函数可以接受可选的 name参数。例如,tf.constant(42.0, name="answer") 创建一个名为 "answer" 的新 tf.Operation并返回一个名为 "answer:0" 的 tf.Tensor。如果默认图已包含名为 "answer"`的指令,则 TensorFlow 会在名称上附加 "_1"、"_2" 等字符,以便让名称具有唯一性。
c_0 = tf.constant(0, name="c")  # => operation named "c"

# Already-used names will be "uniquified".
c_1 = tf.constant(2, name="c")  # => operation named "c_1"

# Name scopes add a prefix to all operations created in the same context.
with tf.name_scope("outer"):
  c_2 = tf.constant(2, name="c")  # => operation named "outer/c"

  # Name scopes nest like paths in a hierarchical file system.
  with tf.name_scope("inner"):
    c_3 = tf.constant(3, name="c")  # => operation named "outer/inner/c"

  # Exiting a name scope context will return to the previous prefix.
  c_4 = tf.constant(4, name="c")  # => operation named "outer/c_1"

  # Already-used name scopes will be "uniquified".
  with tf.name_scope("inner"):
    c_5 = tf.constant(5, name="c")  # => operation named "outer/inner_1/c"
类似于张量的对象

默认情况下,每次您使用同一个类似于张量的对象时,TensorFlow 将创建新的 tf.Tensor。如果类似于张量的对象很大(例如包含一组训练示例的 numpy.ndarray),且您多次使用该对象,您可能会用光内存。要避免出现此问题,请在类似于张量的对象:

  • tf.Tensor
  • tf.Variable
  • numpy.ndarray
  • list
  • 标量 Python 类型:bool、float、int、str
tf.Session
# Create a default in-process session.
with tf.Session() as sess:
  # ...

# Create a remote session.
with tf.Session("grpc://example.org:2222"):
  # ...

由于 tf.Session 拥有物理资源(例如 GPU 和网络连接),它通常用作上下文管理器(在 with代码块中),该管理器可在您退出代码块时自动关闭会话。您也可以在不使用with代码块的情况下创建会话,但应在完成会话时明确调用 tf.Session.close以便释放资源

tf.Session.run

tf.Session.run方法是一种用于运行 tf.Operation 或对 tf.Tensor求值的主要机制。您可以将一个或多个 tf.Operation或 tf.Tensor对象传递到 tf.Session.run,TensorFlow 将执行计算结果所需的指令。

 = tf.constant([[37.0, -23.0], [1.0, 4.0]])
w = tf.Variable(tf.random_uniform([2, 2]))
y = tf.matmul(x, w)
output = tf.nn.softmax(y)
init_op = w.initializer

with tf.Session() as sess:
  # Run the initializer on `w`.
  sess.run(init_op)

  # Evaluate `output`. `sess.run(output)` will return a NumPy array containing
  # the result of the computation.
  print(sess.run(output))

  # Evaluate `y` and `output`. Note that `y` will only be computed once, and its
  # result used both to return `y_val` and as an input to the `tf.nn.softmax()`
  # op. Both `y_val` and `output_val` will be NumPy arrays.
  y_val, output_val = sess.run([y, output])

tf.Session.run也可以视情况接受Feed字典

# Define a placeholder that expects a vector of three floating-point values,
# and a computation that depends on it.
x = tf.placeholder(tf.float32, shape=[3])
y = tf.square(x)

with tf.Session() as sess:
  # Feeding a value changes the result that is returned when you evaluate `y`.
  print(sess.run(y, {x: [1.0, 2.0, 3.0]}))  # => "[1.0, 4.0, 9.0]"
  print(sess.run(y, {x: [0.0, 0.0, 5.0]}))  # => "[0.0, 0.0, 25.0]"

  # Raises `tf.errors.InvalidArgumentError`, because you must feed a value for
  # a `tf.placeholder()` when evaluating a tensor that depends on it.
  sess.run(y)

  # Raises `ValueError`, because the shape of `37.0` does not match the shape
  # of placeholder `x`.
  sess.run(y, {x: 37.0})
使用多个图进行编程
g_1 = tf.Graph()
with g_1.as_default():
  # Operations created in this scope will be added to `g_1`.
  c = tf.constant("Node in g_1")

  # Sessions created in this scope will run operations from `g_1`.
  sess_1 = tf.Session()

g_2 = tf.Graph()
with g_2.as_default():
  # Operations created in this scope will be added to `g_2`.
  d = tf.constant("Node in g_2")

# Alternatively, you can pass a graph when constructing a `tf.Session`:
# `sess_2` will run operations from `g_2`.
sess_2 = tf.Session(graph=g_2)

assert c.graph is g_1
assert sess_1.graph is g_1

# Print all of the operations in the default graph.
g = tf.get_default_graph()
print(g.get_operations())

保存和恢复变量

保存变量
# Create some variables.
v1 = tf.get_variable("v1", shape=[3], initializer = tf.zeros_initializer)
v2 = tf.get_variable("v2", shape=[5], initializer = tf.zeros_initializer)

inc_v1 = v1.assign(v1+1)
dec_v2 = v2.assign(v2-1)

# Add an op to initialize the variables.
init_op = tf.global_variables_initializer()

# Add ops to save and restore all the variables.
saver = tf.train.Saver()

# Later, launch the model, initialize the variables, do some work, and save the
# variables to disk.
with tf.Session() as sess:
  sess.run(init_op)
  # Do some work with the model.
  inc_v1.op.run()
  dec_v2.op.run()
  # Save the variables to disk.
  save_path = saver.save(sess, "/tmp/model.ckpt")
  print("Model saved in path: %s" % save_path)
恢复变量
tf.reset_default_graph()

# Create some variables.
v1 = tf.get_variable("v1", shape=[3])
v2 = tf.get_variable("v2", shape=[5])

# Add ops to save and restore all the variables.
saver = tf.train.Saver()

# Later, launch the model, use the saver to restore variables from disk, and
# do some work with the model.
with tf.Session() as sess:
  # Restore variables from disk.
  saver.restore(sess, "/tmp/model.ckpt")
  print("Model restored.")
  # Check the values of the variables
  print("v1 : %s" % v1.eval())
  print("v2 : %s" % v2.eval())
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 199,636评论 5 468
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 83,890评论 2 376
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 146,680评论 0 330
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 53,766评论 1 271
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 62,665评论 5 359
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,045评论 1 276
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,515评论 3 390
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,182评论 0 254
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,334评论 1 294
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,274评论 2 317
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,319评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,002评论 3 315
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,599评论 3 303
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,675评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 30,917评论 1 255
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 42,309评论 2 345
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 41,885评论 2 341

推荐阅读更多精彩内容