3. TensorFlow核心数据结构:张量
好,咱们今天来聊聊TensorFlow里最核心的东西——张量。说白了,张量就是TensorFlow中的数据载体。你喂给模型的数据是张量,模型吐出来的结果也是张量。我刚开始学的时候,总觉得这玩意儿有点抽象,后来用多了才发现,它其实就是个「装了数据的多维数组」。
3.1 张量的创建
创建张量的方式有很多,我挑几个最常用的给你说说。
3.1.1 从Python列表或NumPy数组创建
最直接的方式,就是用 tf.constant()。你给它一个Python列表,它就给你返回一个张量。
import tensorflow as tf
# 创建一个标量(0维张量)
scalar = tf.constant(3.14)
print(scalar)
# 创建一个向量(1维张量)
vector = tf.constant([1.0, 2.0, 3.0])
print(vector)
# 创建一个矩阵(2维张量)
matrix = tf.constant([[1, 2], [3, 4]])
print(matrix)
# 创建一个3维张量
tensor_3d = tf.constant([[[1, 2], [3, 4]], [[5, 6], [7, 8]]])
print(tensor_3d)
嗯,这里要注意:tf.constant() 创建的是不可变的张量。什么意思?就是一旦创建,里面的值就不能改了。我在项目中遇到过这种情况——一开始用 tf.constant() 存模型参数,结果训练时报错,才发现这玩意儿不能更新。后来改用 tf.Variable() 才搞定。
3.1.2 创建特殊张量
有时候我们需要一些特定值的张量,比如全0、全1或者随机数。TensorFlow提供了很方便的函数。
# 全0张量
zeros = tf.zeros([3, 4])
print(zeros)
# 全1张量
ones = tf.ones([2, 3])
print(ones)
# 填充指定值
filled = tf.fill([2, 2], 7)
print(filled)
# 随机正态分布
random_normal = tf.random.normal([3, 3], mean=0.0, stddev=1.0)
print(random_normal)
# 随机均匀分布
random_uniform = tf.random.uniform([3, 3], minval=0, maxval=10)
print(random_uniform)
我个人习惯在初始化权重时用 tf.random.normal(),配合 tf.Variable() 一起用。你想想看,如果全用0初始化,那所有神经元学到的梯度都一样,模型就废了。
3.1.3 从NumPy数组转换
TensorFlow和NumPy的兼容性很好。你可以直接把NumPy数组转成张量。
import numpy as np
# NumPy数组
np_array = np.array([[1, 2], [3, 4]], dtype=np.float32)
# 转成张量
tensor_from_np = tf.convert_to_tensor(np_array)
print(tensor_from_np)
# 反过来,张量也可以转成NumPy数组
back_to_np = tensor_from_np.numpy()
print(back_to_np)
我记得有一次做数据预处理,数据全是NumPy格式的,直接喂给模型报类型错误。后来发现用 tf.convert_to_tensor() 转一下就好了。这个小坑,你记住了。
3.2 张量的属性:shape、dtype、rank
每个张量都有三个基本属性:形状(shape)、数据类型(dtype)和维度(rank)。这三个属性决定了张量的「长相」和「脾气」。
3.2.1 shape(形状)
shape描述的是张量每个维度的大小。比如一个形状为 [3, 4] 的张量,表示它有3行4列。
tensor = tf.constant([[1, 2, 3], [4, 5, 6]])
print(tensor.shape) # 输出: (2, 3)
print(tensor.shape[0]) # 输出: 2
print(tensor.shape[1]) # 输出: 3
shape在模型调试时特别有用。我曾经遇到过模型输出形状和预期不匹配的情况,打印一下shape就发现问题了——原来是某个卷积层的padding设置不对。
3.2.2 dtype(数据类型)
dtype指定了张量中元素的类型。常见的有 tf.float32、tf.int32、tf.bool 等。
tensor_float = tf.constant([1.0, 2.0], dtype=tf.float32)
tensor_int = tf.constant([1, 2], dtype=tf.int32)
tensor_bool = tf.constant([True, False], dtype=tf.bool)
print(tensor_float.dtype) # 输出: <dtype: 'float32'>
print(tensor_int.dtype) # 输出: <dtype: 'int32'>
print(tensor_bool.dtype) # 输出: <dtype: 'bool'>
3.2.3 rank(维度)
rank指的是张量的维度数量。标量的rank是0,向量的rank是1,矩阵的rank是2,以此类推。
scalar = tf.constant(5)
vector = tf.constant([1, 2, 3])
matrix = tf.constant([[1, 2], [3, 4]])
tensor_3d = tf.constant([[[1, 2], [3, 4]], [[5, 6], [7, 8]]])
print(tf.rank(scalar)) # 输出: 0
print(tf.rank(vector)) # 输出: 1
print(tf.rank(matrix)) # 输出: 2
print(tf.rank(tensor_3d)) # 输出: 3
说白了,rank就是看你有几层方括号。一层是向量,两层是矩阵,三层就是三维张量。这个很好理解。
3.3 张量的索引与切片
索引和切片是操作张量的基本功。TensorFlow的索引方式和Python列表、NumPy数组很像,但也有一些自己的特点。
3.3.1 基本索引
你可以用方括号加索引来访问张量中的元素。索引从0开始。
tensor = tf.constant([[1, 2, 3], [4, 5, 6], [7, 8, 9]])
# 访问第1行第2列的元素
element = tensor[0, 1]
print(element) # 输出: 2
# 访问第2行
row = tensor[1]
print(row) # 输出: [4, 5, 6]
# 访问第3列
col = tensor[:, 2]
print(col) # 输出: [3, 6, 9]
这里用冒号 : 表示「所有行」或「所有列」。你想想看,tensor[:, 2] 就是取所有行的第3列,很直观。
3.3.2 切片操作
切片可以取张量的一部分。语法是 start:stop:step,和Python列表一样。
tensor = tf.constant([[1, 2, 3, 4],
[5, 6, 7, 8],
[9, 10, 11, 12]])
# 取前两行,第2到第4列
slice1 = tensor[0:2, 1:4]
print(slice1)
# 输出: [[2, 3, 4], [6, 7, 8]]
# 取所有行,每隔一列取一个
slice2 = tensor[:, ::2]
print(slice2)
# 输出: [[1, 3], [5, 7], [9, 11]]
# 取第1行和第3行,所有列
slice3 = tensor[::2, :]
print(slice3)
# 输出: [[1, 2, 3, 4], [9, 10, 11, 12]]
tf.constant() 创建的张量,你没法修改,所以不用担心。但如果是 tf.Variable(),就要小心了。
3.3.3 高级索引
有时候我们需要取不连续的行或列,这时候可以用列表或张量来索引。
tensor = tf.constant([[1, 2, 3], [4, 5, 6], [7, 8, 9]])
# 取第1行和第3行
rows = tf.gather(tensor, [0, 2])
print(rows)
# 输出: [[1, 2, 3], [7, 8, 9]]
# 取第1列和第3列
cols = tf.gather(tensor, [0, 2], axis=1)
print(cols)
# 输出: [[1, 3], [4, 6], [7, 9]]
# 布尔索引
mask = tf.constant([True, False, True])
masked = tf.boolean_mask(tensor, mask, axis=0)
print(masked)
# 输出: [[1, 2, 3], [7, 8, 9]]
我个人觉得 tf.gather() 在数据预处理时特别好用。比如你要从数据集中随机抽取一批样本,用 tf.gather() 配合随机索引就能轻松搞定。
3.3.4 维度变换与切片结合
有时候我们需要先改变张量的形状,再做切片。比如处理图像数据时,经常需要把 [batch, height, width, channels] 格式的张量切分成单张图片。
# 模拟一个batch的图片数据
batch_images = tf.random.normal([4, 28, 28, 3])
# 取第一张图片
first_image = batch_images[0]
print(first_image.shape) # 输出: (28, 28, 3)
# 取前两张图片
first_two = batch_images[:2]
print(first_two.shape) # 输出: (2, 28, 28, 3)
# 取所有图片的左上角14x14区域
cropped = batch_images[:, :14, :14, :]
print(cropped.shape) # 输出: (4, 14, 14, 3)
嗯,这里要注意:切片操作返回的张量维度可能会减少。比如从 [4, 28, 28, 3] 中取 batch_images[0],得到的形状是 [28, 28, 3],少了一维。如果你想保持4维,可以用 batch_images[0:1] 或者 tf.expand_dims()。
核心要点总结:
- 张量是TensorFlow的数据核心,用
tf.constant()、tf.Variable()等创建 - 三个关键属性:shape(形状)、dtype(数据类型)、rank(维度)
- 索引和切片操作与NumPy类似,支持基本索引、切片、高级索引
- 注意数据类型一致性,不同dtype不能直接运算
- 切片操作不复制数据,修改视图会影响原张量(仅对Variable有效)
好了,张量的基础知识就这些。你把这些练熟了,后面学模型构建、训练流程就会轻松很多。下一章咱们聊聊张量的运算,那才是真正开始「玩」TensorFlow的时候。