Tensorflow之tf.split() 与 tf.squeeze()

本文详细解析了TensorFlow中tf.split函数的使用方法,包括参数解释、常见用例及错误处理,帮助读者深入理解如何通过该函数对张量进行分割。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

split(
    value,
    num_or_size_splits,
    axis=0,
    num=None,
    name='split'
)

输入: 
value: 输入的tensor 
num_or_size_splits: 如果是个整数n,就将输入的tensor分为n个子tensor。如果是个tensor T,就将输入的tensor分为len(T)个子tensor。 
axis: 默认为0,计算value.shape[axis], 一定要能被num_or_size_splits整除。

举例:

# num_or_size_splits是tensor T,len(T)为3,所以分为3个子tensor,
# axis为1,所以value.shape[1]为30,4+15+11正好为30
split0, split1, split2 = tf.split(value, [4, 15, 11], 1)

tf.shape(split0)  # [5, 4]
tf.shape(split1)  # [5, 15]
tf.shape(split2)  # [5, 11]

# num_or_size_splits是整数3, 分为3个tensor,value.shape[1]为30,能被3整除。
split0, split1, split2 = tf.split(value, num_or_size_splits=3, axis=1)
tf.shape(split0)  # [5, 10]

再举个实例:

>>> a=np.reshape(range(24),(4,2,3))
>>> a
array([[[ 0,  1,  2],
        [ 3,  4,  5]],

       [[ 6,  7,  8],
        [ 9, 10, 11]],

       [[12, 13, 14],
        [15, 16, 17]],

       [[18, 19, 20],
        [21, 22, 23]]])

>>> sess=tf.InteractiveSession()
# 将a分为两个tensor,a.shape(1)为2,可以整除,不会报错。
# 输出应该为2个shape为[4,1,3]的tensor
>>> b= tf.split(a,2,1)
>>> b
[<tf.Tensor 'split:0' shape=(4, 1, 3) dtype=int32>, <tf.Tensor 'split:1' shape=(4, 1, 3) dtype=int32>]
>>> sess.run(b)
[array([[[ 0,  1,  2]],

       [[ 6,  7,  8]],

       [[12, 13, 14]],

       [[18, 19, 20]]]), array([[[ 3,  4,  5]],

       [[ 9, 10, 11]],

       [[15, 16, 17]],

       [[21, 22, 23]]])]
>>> c= tf.split(a,2,0)
# a.shape(0)为4,被2整除,输出2个[2,2,3]的Tensor
>>> c
[<tf.Tensor 'split_1:0' shape=(2, 2, 3) dtype=int32>, <tf.Tensor 'split_1:1' shape=(2, 2, 3) dtype=int32>]
>>> sess.run(c)
[array([[[ 0,  1,  2],
        [ 3,  4,  5]],

       [[ 6,  7,  8],
        [ 9, 10, 11]]]), array([[[12, 13, 14],
        [15, 16, 17]],

       [[18, 19, 20],
        [21, 22, 23]]])]
>>>d= tf.split(a,2,2)
#  a.shape(2)为3,不被2整除,报错。
Traceback (most recent call last):
  File "D:\Anaconda2\envs\tensorflow\lib\site-packages\tensorflow\python\framework\common_shapes.py", line 671, in _call_cpp_shape_fn_impl
    input_tensors_as_shapes, status)
  File "D:\Anaconda2\envs\tensorflow\lib\contextlib.py", line 66, in __exit__
    next(self.gen)
  File "D:\Anaconda2\envs\tensorflow\lib\site-packages\tensorflow\python\framework\errors_impl.py", line 466, in raise_exception_on_not_ok_status
    pywrap_tensorflow.TF_GetCode(status))
tensorflow.python.framework.errors_impl.InvalidArgumentError: Dimension size must be evenly divisible by 2 but is 3
        Number of ways to split should evenly divide the split dimension for 'split_1' (op: 'Split') with input shapes: [], [4,2,3] and with computed input tensors: input[0] = <2>.
>>> d= tf.split(a,3,2)
# 改成3,a.shape(2)为3,整除,不报错,返回3个[4,2,1]的Tensor
>>> d
[<tf.Tensor 'split_2:0' shape=(4, 2, 1) dtype=int32>, <tf.Tensor 'split_2:1' shape=(4, 2, 1) dtype=int32>, <tf.Tensor 'split_2:2' shape=(4, 2, 1) dtype=int32>]
>>> sess.run(d)
[array([[[ 0],
        [ 3]],

       [[ 6],
        [ 9]],

       [[12],
        [15]],

       [[18],
        [21]]]), array([[[ 1],
        [ 4]],

       [[ 7],
        [10]],

       [[13],
        [16]],

       [[19],
        [22]]]), array([[[ 2],
        [ 5]],

       [[ 8],
        [11]],

       [[14],
        [17]],

       [[20],
        [23]]])]

注意: 
tf.split和reshape不同,不会改变数值之间的相对顺序。只能每个维度只能变小,不增大。 
取值时按照axis-1的顺序来取。

tf.squeeze
squeeze(
    input,
    axis=None,
    name=None,
    squeeze_dims=None
)

去掉维数为1的维度。 
举个例子:

# 't' is a tensor of shape [1, 2, 1, 3, 1, 1]
tf.shape(tf.squeeze(t))  # [2, 3]

也可以指定去掉哪个维度:

# 't' is a tensor of shape [1, 2, 1, 3, 1, 1]
tf.shape(tf.squeeze(t, [2, 4]))  # [1, 2, 3, 1]
using System; //提供了基本的数据类型、异常处理和其他常用功能。 using System.IO; //用于文件和目录的输入输出操作,这里主要用于读取 CSV 文件。 using System.Linq; //提供了 LINQ(Language Integrated Query)功能,方便对数据进行查询和处理。 using Tensorflow; using Tensorflow.Keras; //Keras 是高级神经网络 API,命名空间提供了构建和训练神经网络所需的类和方法。 using Tensorflow.Keras.Models; //Keras.Models 是高级神经网络 API,命名空间提供了构建和训练神经网络所需的类和方法。 using Tensorflow.Keras.Layers; //Keras.Layers 是高级神经网络 API,命名空间提供了构建和训练神经网络所需的类和方法。 using Tensorflow.Keras.Engine; //fit引用 using static Tensorflow.Binding;//tf引用 using Tensorflow.Keras.Optimizers;// using static Tensorflow.KerasApi; using Tensorflow.Keras.ArgsDefinition; //keras引用 using Tensorflow.NumPy; //尽量不用NumSharp using System.Collections.Generic; using Tensorflow.Keras.Losses; using static System.Runtime.InteropServices.JavaScript.JSType; using System.Text; using Tensorflow.Keras.Metrics; using PureHDF.Selections; using Tensorflow.Keras.Callbacks;// 回调函数命名空间 using Tensorflow.Data;// 新增:基础数据操作命名空间 using Tensorflow.Data.Experimental;// 新增:实验性数据操作命名空间 namespace DNN_Keras { // 3.model Subclassing 搭建模型类 // 2.模型搭建:model Subclassing构建深度神经网络。CustomModel 类 :继承自 keras.Model ,通过 Call 方法定义了模型的前向传播过程。模型包含两个隐藏层和一个输出层,隐藏层使用 ReLU 激活函数。 //自定义模型类 CustomModel public class CustomModel : Model { private readonly Layer hiddenLayer1; // 定义隐藏层 1 private readonly Layer hiddenLayer2; // 定义隐藏层 2 private readonly Layer hiddenLayer3; // 定义隐藏层 3 private readonly Layer hiddenLayer4; // 定义隐藏层 4 private readonly Layer outputLayer; // 定义输出层 //构造函数,接收 CustomModelArgs 参数并调用基类构造函数 //public CustomModel() : base(new ModelArgs()) public CustomModel() : base(new ModelArgs()) { // 初始化各层 hiddenLayer1 = (Layer)keras.layers.Dense(32, activation: "relu"); // 初始化隐藏层 1,有64个神经元,使用 ReLU 激活函数 hiddenLayer2 = (Layer)keras.layers.Dense(34, activation: "relu"); // 初始化隐藏层 2,有32个神经元,使用 ReLU 激活函数 hiddenLayer3 = (Layer)keras.layers.Dense(36, activation: "relu"); // 初始化隐藏层 2,有32个神经元,使用 ReLU 激活函数 hiddenLayer4 = (Layer)keras.layers.Dense(38, activation: "relu"); // 初始化隐藏层 2,有32个神经元,使用 ReLU 激活函数 outputLayer = (Layer)keras.layers.Dense(35, activation: "softmax"); // 初始化输出层,有1个输出单元,无激活函数 } //前向传播逻辑,定义模型如何从输入得到输出 protected virtual Tensor Call(Tensor inputs, Tensor? state = null, bool training = false) { Tensor x = hiddenLayer1.Apply(inputs); // 将输入数据传入隐藏层 1 x = hiddenLayer2.Apply(x); // 将隐藏层 1 的输出传入隐藏层 2 x = hiddenLayer3.Apply(x); // 将隐藏层 2 的输出传入隐藏层 3 x = hiddenLayer4.Apply(x); // 将隐藏层 3 的输出传入隐藏层 4 return outputLayer.Apply(x); // 将隐藏层 4 的输出传入输出层并返回结果 } } class Program { static void Main() { //一、获取CSV数据,配合数据处理函数LoadCSVData使用 // 1. 声明一个字符串变量 csvPath ,存储CSV文件的绝对路径,从CSV加载数据 string csvPath = @"D:\编程软件系列\VS2022社区版\文件\DNN_Keras\数据\大了Number.csv"; // 2.加载并拆分数据,调用 LoadCSVData 方法传入 csvPath 作为参数,方法内部读取CSV数据,返回训练集/测试集的特征和标签,并解包到四个变量中 var (x_train, y_train, x_test, y_test) = LoadCSVData(csvPath); //使用tf.data.Dataset.from_tensor_slices方法创建训练集数据集 var dataset1 = tf.data.Dataset.from_tensor_slices(x_train, y_train); //对dataset1 数据进行预处理 dataset1 = dataset1.shuffle(10) //打乱原始数据,10为每10个数据一组进行随机乱序.先shuffle再batch ,否则只能打乱批次顺序,无法保证批次内样本随机。 .repeat(1) //数据复制倍数,默认无限复制,1为1倍 .batch(4); //数据每批次内数据数量,4为1个批次内4个数据 //.take(2) //显示数据批次数量,2为显示2个批次,一般不使用(全显示) //.prefetch(1) //数据预取出,一般不使用 //.map(); //对数据集每个元素数据应用 Function 自定义函数或 Lambda 表达式,一般为数据类型转换函数,可在数据处理函数内使用其它方式处理 //使用tf.data.Dataset.from_tensor_slices方法创建测试集数据集 var dataset2 = tf.data.Dataset.from_tensor_slices(x_test, y_test); //对dataset2 数据进行预处理 dataset2 = dataset2.take(1); print(" 训练集输出 "); foreach (var(item_x1,item_y1)in dataset1) { print($"x:{item_x1.numpy()},y:{item_y1.numpy()}"); } print(" 测试集输出 "); foreach (var (item_x2, item_y2) in dataset2) { print($"x:{item_x2.numpy()},y:{item_y2.numpy()}"); } // 二. 调用模型类 var model = new CustomModel(); // 三、自定义损失函数(位置必须放在优化器前面) Func<Tensor, Tensor, Tensor> cross_entropy_loss = (logits, labels) => { labels = tf.cast(labels, tf.int64); // 将输入的 y 张量的数据类型转换为 int64 var loss = tf.nn.sparse_softmax_cross_entropy_with_logits(labels, logits); // 计算稀疏的 softmax 交叉熵损失 return tf.reduce_mean(loss); // 对计算得到的损失取平均值 }; //准确率评估函数 Func<Tensor, Tensor, Tensor> accuracy = (y_pred, y_true) => { //比较预测类别和真实类别是否一致。预测类别是预测向量中最高分值的索引(即argmax)。 //tf.equal 比较两个张量对应位置的元素是否相等,返回一个布尔类型的张量 correct_prediction ,其中每个元素表示对应样本的预测结果是否正确。 // tf.math.argmax 函数用于找出张量在指定轴上的最大值的索引。这里 y_pred 通常是模型输出的概率分布,1 表示在每个样本的类别维度上查找最大值的索引,也就是模型预测的类别。 //tf.cast 函数将 y_true 张量的数据类型转换为 tf.int64 。这是为了和 tf.math.argmax 的输出类型保持一致,方便后续比较。 var correct_prediction = tf.equal(tf.math.argmax(y_pred, 1), tf.cast(y_true, tf.int64)); // 计算准确率(正确预测的比例) //tf.reduce_mean 函数计算张量在指定轴上的平均值。 axis: -1 表示在最后一个轴上计算平均值。由于 correct_prediction 转换后每个元素为 1.0 或 0.0 ,所以计算平均值就得到了准确率。最后将这个准确率作为结果返回。 //tf.cast(correct_prediction, tf.float32) :将布尔类型的 correct_prediction 张量转换为 tf.float32 类型,其中 true 会被转换为 1.0 , false 会被转换为 0.0 。 return tf.reduce_mean(tf.cast(correct_prediction, tf.float32), axis: -1); }; // 四、创建随机梯度下降(AdamW)优化器keras.optimizers.AdamW实例,用于更新模型参数,学习率learning_rate:0.01f, 权重衰减weight_decay: 0.0001f var optimizer = keras.optimizers.SGD(learning_rate:0.01f); //定义run_optimization委托(无返回值),接受两个`Tensor`参数(输入特征x和标签y),用于执行一次优化步骤。 Action<Tensor, Tensor> run_optimization = (x, y) => { //创建tf.GradientTape求导记录器,记录后面所有计算步骤用于求导梯度。using确保资源正确释放,避免内存泄漏。 using var g = tf.GradientTape(); //调用神经网络模型model的Apply方法,传入输入x,并设置training: true表示处于训练模式(影响如Dropout、BatchNorm等层的行为)。这一步执行前向传播,得到模型的预测值pred。 var pred = model.Apply(x, training: true); //调用自定义损失函数cross_entropy_loss,计算预测值pred真实标签y之间的交叉熵损失。 var loss = cross_entropy_loss(pred, y); //调用GradientTape实例 g 的gradient方法计算损失相对于模型可训练变量(TrainableVariables)的梯度。这一步是反向传播的核心,计算各参数的梯度值。 var gradients = g.gradient(loss, model.TrainableVariables); //优化器应用计算得到的梯度来更新模型参数。zip函数将梯度和对应的可训练变量配对,ApplyGradients方法根据优化器的策略(如AdamW的学习率)更新参数。需要注意变量类型转换(as ResourceVariable),确保优化器能正确处理变量。 //optimizer.apply_gradients(zip(gradients, model.TrainableVariables.Select(x => x as ResourceVariable))); // 修正:使用 Enumerable.Zip 正确配对梯度和变量,并转换为列表 var grads_and_vars = Enumerable.Zip(gradients,model.TrainableVariables.Cast<ResourceVariable>(), (g, v) => (g, v)).ToList(); // 确保变量是 ResourceVariable 类型 optimizer.apply_gradients(grads_and_vars); // 应用梯度 }; // 五、自定义循环训练模型 // 遍历训练数据集(enumerate自动生成step索引) foreach (var (step, (batch_x, batch_y)) in enumerate(dataset1, 1)) { //调用 run_optimization 方法,将当前批次的输入数据 batch_x 和标签数据 batch_y 作为参数传入。这个方法的主要功能是执行一次优化步骤,也就是依据当前批次的数据计算梯度,然后更新神经网络的参数W和b的值。 run_optimization(batch_x, batch_y); //条件判断语句, step 是当前批次的序号, display_step=100 是一个预设的整数,表示每隔多少步输出一次训练信息。当 step 是 display_step 的整数倍时,就执行下面的代码块。 if (step % 100 == 0) { //调用 model 对象的 Apply 方法,把当前批次的输入数据 batch_x 作为输入, training: true 表明当前处于训练模式。该方法会让输入数据通过神经网络,得到预测结果 pred 。 var pred = model.Apply(batch_x, training: true); //调用 cross_entropy_loss 方法,将预测结果 pred 和真实标签 batch_y 作为参数传入,计算交叉熵损失。交叉熵损失是分类问题里常用的损失函数,用于衡量预测结果和真实标签之间的差异。 var loss = cross_entropy_loss(pred, batch_y); //调用 accuracy 方法,将预测结果 pred 和真实标签 batch_y 作为参数传入,计算准确率。准确率表示预测正确的样本数占总样本数的比例。 var acc = accuracy(pred, batch_y); //输出当前批次的序号、损失值和准确率。 (float)loss 和 (float)acc 是将 loss 和 acc 转换为 float 类型,方便输出。 print($"step: {step}, loss: {(float)loss}, accuracy: {(float)acc}"); } } // 使用训练好的神经网络模型在验证集上测试模型。对测试数据进行预测,并计算预测结果的准确率 { //调用 neural_net 这个神经网络模型对测试数据 x_test 进行预测,得到预测结果 pred 。 //x_test :这是测试数据集,通常是一个包含多个样本的输入数据,例如在图像分类任务中, x_test 可能是一批测试图像的像素值。 //training: false :该参数表明当前处于推理(测试)模式,而非训练模式。在神经网络中,有些层(如 Dropout 、 BatchNormalization 等)在训练和推理时的行为不同。将 training 设置为 false 可以确保这些层在测试时使用正确的行为。 //pred 是模型对 x_test 的预测结果,其数据类型和形状取决于模型的输出。例如,在分类任务中, pred 可能是每个样本属于各个类别的概率分布。 var pred = model.Apply(x_test, training: false); //计算预测结果 pred 相对于真实标签 y_test 的准确率,并将结果赋值给类的成员变量 this.accuracy 。 //accuracy 函数 :这是一个自定义的函数,用于计算预测结果和真实标签之间的准确率。具体实现可能因任务而异,例如在分类任务中,通常比较预测的类别和真实类别是否一致,然后计算正确预测的样本数占总样本数的比例。 //类型转换 : (float) 是显式类型转换,将 accuracy 函数的返回值转换为 float 类型,以便赋值给 this.accuracy 。 //pred :上一步得到的模型预测结果。y_test :测试数据的真实标签, x_test 中的样本一一对应。 var testaccuracy = (float)accuracy(pred, y_test); //打印测试集的准确率。 print($"Test Accuracy: {accuracy}"); } } // 定义静态方法 LoadCSVData,接收CSV文件路径,处理数据,返回(训练特征、训练标签、测试特征、测试标签)的元组 static (NDArray, NDArray, NDArray, NDArray) LoadCSVData(string filePath) { //1. 读取CSV文件所有行,使用File.ReadAllLines读取文件内容到字符串数组 var lines = File.ReadAllLines(filePath); // 2. 验证文件基本格式(至少包含标题行+1个数据行),行数不足时抛异常 if (lines.Length < 2) throw new ArgumentException("CSV文件至少需要包含标题行和一个数据行"); // 3. 初始化特征和标签存储容器 var features = new List<float[]>(); // 存储每个样本的特征数组(每个样本是float[]) var labels = new List<float>(); // 存储每个样本的标签(float类型 //4.遍历数据行(跳过第1行标题) foreach (var line in lines.Skip(1)) // Skip(1)跳过标题行,只处理数据行 { var values = line.Split(','); // 4.1 按逗号分割当前行内容 if (values.Length < 2) throw new FormatException($"数据行格式错误:{line}(至少需要1个特征和1个标签)"); // 4.2 验证数据行格式(至少1个特征+1个标签) var featureValues = new float[values.Length - 1]; // 解析特征(排除最后一列)// 4.3 解析特征值(排除最后一列标签),特征数组长度=总列数-1(排除标签列) for (int i = 0; i < values.Length - 1; i++) { if (!float.TryParse(values[i], out float feature)) throw new FormatException($"无法解析特征值:{values[i]}(行:{line})");// 尝试将字符串转换为float,失败时抛异常并提示具体值和行内容 featureValues[i] = feature; // 存储解析后的特征值 } features.Add(featureValues); // 将当前行特征添加到特征列表 // 4.4 解析标签值(最后一列) if (!float.TryParse(values.Last(), out float label)) throw new FormatException($"无法解析标签值:{values.Last()}(行:{line})"); // 尝试转换最后一列值为float labels.Add(label); // 存储解析后的标签值 } // 5. 验证所有样本的特征数是否一致(避免特征维度混乱) int featureCount = features[0].Length; // 以第一个样本的特征数为基准 if (features.Any(f => f.Length != featureCount)) throw new InvalidDataException("所有样本的特征数必须一致"); // 检查是否有样本特征数不一致 // 6. 将特征列表转换为二维矩形数组(兼容TensorFlow的NDArray) int sampleCount = features.Count; // 总样本数 var xRect = new float[sampleCount, featureCount]; // 二维矩形数组(样本数×特征数) for (int i = 0; i < sampleCount; i++) { for (int j = 0; j < featureCount; j++) { xRect[i, j] = features[i][j]; // 将List<float[]>转换为float } } /// 7. 转换为TensorFlow的NDArray(模型可处理的张量格式) var x = np.array(xRect); // 特征张量(形状:[样本数, 特征数]) var y = np.array(labels.ToArray()).reshape(new Shape(-1, 1)); // 标签张量(形状:[样本数, 1]) // 8. 按8:2比例拆分训练集和测试集(数据按照排列顺序拆分) int splitIndex = (int)(x.shape[0] * 0.8); // 80 % 样本作为训练集 return (x[new Slice(0, splitIndex)], // 训练特征 x_train(前80%样本) y[new Slice(0, splitIndex)], // 训练标签 y_train(前80%样本) x[new Slice(splitIndex)], // 测试特征 x_test(后20%样本) y[new Slice(splitIndex)]); // 测试标签 y_test(后20%样本) } } } 运行时提示 System.ArgumentException HResult=0x80070057 Message=Tensor must have 0 dimensions in order to convert to scalar Source=Tensorflow.Binding StackTrace: 在 Tensorflow.Tensor.EnsureScalar(Tensor tensor) 在 Tensorflow.Tensor.op_Explicit(Tensor tensor) 在 DNN_Keras.Program.Main() 在 D:\编程软件系列\VS2022社区版\文件\DNN_Keras\Program.cs 中: 第 151 行
最新发布
06-24
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值