using System;
using System.IO;
using System.Threading;
using Tensorflow; //引入TensorFlow.NET核心命名空间(如 tf 对象)。
using Tensorflow.Keras;
using Tensorflow.Keras.ArgsDefinition;
using Tensorflow.Keras.Engine;
using Tensorflow.Keras.Layers;
using Tensorflow.NumPy;//引入NumPy兼容的数组操作(如 np.array )。
using static Tensorflow.Binding;//静态导入TensorFlow和Keras的API,避免重复写类名(如直接使用 tf 和 keras )。
using static Tensorflow.KerasApi;//应用模型层搭建
#region 安装包版本号
//numsharp 0.30.0
//TensorFlow.NET 0.150.0
//SciSharp.TensorFlow.Redist 2.16.0
//TensorFlow.Keras 0.15.0
#endregion
#region
//安装包对应表
//TensorFlow.Keras TensorFlow.NET SciSharp.TensorFlow.Redist
// 2.10.x 0.13.0 2.10.0
// 2.11.x 0.14.0 2.11.0
// 0.15.0 0.15.0+ 2.12.0+
#endregion
#region
// 运行结果说明,loss(均方误差) :逐渐下降,越低越好。说明模型对训练数据的拟合效果显著提升。
// mean_absolute_error(平均绝对误差) :预测值与真实值的平均绝对偏差逐渐下降,越低越好。模型预测精度提高。
// 验证集指标(val_loss/val_mean_absolute_error) :逐渐下降,越低越好。说明模型在未见过的数据上也能保持较好的泛化能力,未出现明显过拟合。
// Keras模型Functional API方式搭建线性回归
#endregion
namespace Keras.NET_Prediction_main_program
{
class Program
{
static void Main()
{
#region GPU显卡K80设置
Environment.SetEnvironmentVariable("CUDA_VISIBLE_DEVICES", "1"); //指定K80双12GPU 1号显卡,多张显卡时可用“,”隔开,如"0,1"(序号0对应1号显卡)
var gpus = tf.config.list_physical_devices("GPU"); // 获取系统中所有物理GPU设备列表(TensorFlow API)
// 判断是否满足双K80显卡条件:
// 1. gpus.Length >= 2:检测到至少2个GPU设备
// 2. gpus.All(...): 所有检测到的GPU设备名称包含"K80"(确保是K80型号)
bool hasDualK80 = gpus.Length >= 2 && gpus.All(gpu => gpu.DeviceName.Contains("K80"));
int gpuCount = gpus.Length; // 记录检测到的GPU总数量(可能是1或多个)
if (gpus.Length > 0) // 检查是否有可用的GPU设备
{
Console.WriteLine("CUDA 可用,检测到以下 GPU 设备:"); // 输出提示信息:CUDA可用,并列出检测到的GPU设备
foreach (var gpu in gpus) // 遍历每个检测到的GPU设备
{
Console.WriteLine(gpu.DeviceName); // 输出当前GPU的设备名称(例如:"Tesla K80")
tf.config.experimental.set_memory_growth(gpu, true); // 启用当前GPU的显存动态分配,避免TensorFlow一次性占满所有显存,按需分配内存。参数true表示允许显存动态增长(适合多卡/多任务场景)
//Console.WriteLine($"设备 {gpu.DeviceName} 已启用动态显存分配");
}
if (hasDualK80) // 如果满足双K80条件(hasDualK80为true)
{
Console.WriteLine($"检测到 {gpuCount} 个 K80 显卡,启用双卡显存优化"); // 输出提示信息:告知用户检测到双K80显卡并启用优化
}
}
else
{
Console.WriteLine("CUDA 不可用,仅使用 CPU 进行计算。"); // 无GPU设备时的提示信息:使用CPU计算
}
#endregion
string csvPath = @"D:\编程软件系列\VS2022社区版\文件\Functional DNNmodel\数据\SuperbigNumberBB.csv"; // 获取CSV文件路径
var (X, Y) = CSVDataLoader.LoadCSVData(csvPath); // 调用CSVDataLoader类LoadCSVData方法,从CSV文件中加载特征数据 X 和标签数据 Y
var (train_X, train_Y, val_X, val_Y, test_X, test_Y) = StratifiedSampler.StratifiedSplit(X, Y, 0.6f, 0.2f); // 调用StratifiedSampler类StratifiedSplit方法,分层抽样划分数据集,按6:2:2比例划分训练集、验证集、测试集
StratifiedSampler.ValidateStratifiedSplit(Y, train_Y, val_Y, test_Y); // 调用StratifiedSampler类ValidateStratifiedSplit方法,验证分层抽样法,正式运行时注释掉
# region
//验证集和测试集必须使用训练集计算的均值/标准差标准化,避免数据泄露。
//验证集的作用 :训练过程中通过验证集监控过拟合(如验证损失上升、准确率停滞),早停回调可提前终止无效训练。
//测试集的独立性 :测试集仅用于最终评估,不参与模型调参,确保结果真实反映模型泛化能力。
//正则化增强 :通过L2正则化( CustomL2Regularizer )和Dropout层抑制过拟合,提升模型泛化性。
// 1. 数据预处理(示例:标准化,训练集计算参数,验证/测试集复用)
Tensor trainXTensor = tf.constant(train_X); // 将NumPy数组train_X转换为TensorFlow张量
Tensor trainXMeanTensor = math_ops.reduce_mean(trainXTensor, axis: 0); // 按列计算均值(axis=0表示按列计算,axis=1表示按行计算)
NDArray trainXMean = trainXMeanTensor.numpy(); // 将均值张量转换为numpy数组
Tensor trainXStdTensor = math_ops.reduce_std(trainXTensor, axis: 0); // 按列计算标准差
NDArray trainXStd = trainXStdTensor.numpy(); // 将标准差张量转换为numpy数组
NDArray train_X_normalized = (train_X - trainXMean) / (trainXStd + 1e-8f); // 对训练集进行Z-score标准化:(x - 均值)/标准差,1e-8f防止除以0的情况
NDArray val_X_normalized = (val_X - trainXMean) / (trainXStd + 1e-8f); // 对验证集使用训练集的均值和标准差进行标准化,确保验证集和训练集使用相同的标准化参数
NDArray test_X_normalized = (test_X - trainXMean) / (trainXStd + 1e-8f); // 对测试集使用训练集的均值和标准差进行标准化,确保测试集和训练集使用相同的标准化参数
int singleGpuBatchSize = 512; // K80 单卡最大批次(建议单卡 96 → 双卡 192)
int k80BatchSize = hasDualK80 ? singleGpuBatchSize * gpuCount : singleGpuBatchSize; // 双K80显卡总批次大小,单卡*卡的数量
// 2. 构建tf.data数据集(支持批量、打乱、预加载)
var trainDataset = tf.data.Dataset.from_tensor_slices(train_X_normalized, train_Y) //构建tf.data数据集(训练集)
.shuffle(buffer_size: (int)train_X_normalized.shape[0]) // 训练集打乱
.batch(batch_size: k80BatchSize) // 批量大小
.cache() // 将数据缓存到内存,减少数据加载时间
.prefetch(buffer_size: tf.data.AUTOTUNE); // 预加载优化
var valDataset = tf.data.Dataset.from_tensor_slices(val_X_normalized, val_Y) //构建tf.data数据集(验证集)
.batch(batch_size: k80BatchSize) // 批量大小,验证集不打乱
.cache() // 将数据缓存到内存,减少数据加载时间
.prefetch(tf.data.AUTOTUNE); // 预加载优化
var testDataset = tf.data.Dataset.from_tensor_slices(test_X_normalized, test_Y) //构建tf.data数据集(测试集)
.batch(batch_size: k80BatchSize); // 批量大小,测试集不打乱
// 步骤2:使用Functional API方式构建模型
// 1. 配置 Dense 层参数(根据对象浏览器中 DenseArgs 的属性)
var denseArgs1 = new DenseArgs
{
Units = 256, // 神经元数量
Activation = tf.keras.activations.Relu, // Relu激活函数
KernelRegularizer = new CustomL2Regularizer(0.06f) // 自定义正则化类
};
var denseArgs2 = new DenseArgs
{
Units = 256, // 神经元数量
Activation = tf.keras.activations.Relu, // Relu激活函数
KernelRegularizer = new CustomL2Regularizer(0.1f) // 自定义正则化类
};
var denseArgs3 = new DenseArgs
{
Units = 256, // 神经元数量
Activation = tf.keras.activations.Relu, // Relu激活函数
KernelRegularizer = new CustomL2Regularizer(0.1f) // 自定义正则化类
};
var denseArgs4 = new DenseArgs
{
Units = 128, // 神经元数量
Activation = tf.keras.activations.Relu, // Relu激活函数
KernelRegularizer = new CustomL2Regularizer(0.1f) // 自定义正则化类
};
// 2. 实例化 Dense 层(根据对象浏览器中 Dense 的构造函数)
Dense denseLayer1 = new(denseArgs1);
Dense denseLayer2 = new(denseArgs2);
Dense denseLayer3 = new(denseArgs3);
Dense denseLayer4 = new(denseArgs4);
#endregion
// 步骤2:使用Functional API方式构建模型
var inputs = keras.Input(shape: 4); //输入层,shape 特征列数。
var x = denseLayer1.Apply(inputs); //应用自定义的 Dense 层,传入输入层 inputs,返回一个新的张量x。这里使用了之前配置的 DenseArgs 参数,包含输出维度、激活函数和正则化器等信息。
x = keras.layers.Dropout(rate: 0.5f).Apply(x); //Dropout率系数
x = denseLayer2.Apply(x);
x = keras.layers.Dropout(rate: 0.2f).Apply(x);
x = denseLayer3.Apply(x);
x = keras.layers.Dropout(rate: 0.3f).Apply(x);
x = denseLayer4.Apply(x);
x = keras.layers.Dropout(rate: 0.4f).Apply(x);
var outputs = keras.layers.Dense(5, activation: "softmax").Apply(x); //输出层,回归任务神经元数量为1;分类任务神经元数量等于类别数,配合Softmax激活函数。
var model = keras.Model(inputs, outputs); //使用 keras.Model方法将输入层 inputs 和输出层 outputs 组合成一个完整的神经网络模型
model.summary(); //调用 summary 方法打印模型的结构摘要,包括每一层的名称、输出形状和参数数量等信息,方便了解模型的基本情况。
// 步骤3:编译模型;
//损失函数,SparseCategoricalCrossentropy多分类损失函数(不需要One-Hot预处理)。
//优化器,Adam ,学习率为自适应 。
//评估表,accuracy 表示准确率。警告:准确率通常用于分类任务,线性回归任务中使用均方误差。
model.compile(loss: keras.losses.SparseCategoricalCrossentropy(),
optimizer: keras.optimizers.AdamW(learning_rate: 0.001f),
metrics: ["accuracy"]);
// 步骤4:训练模型
//train_X 对应训练集特征, train_Y 对应训练集标签,batch_siz批次大小,epochs 训练轮数(迭代次数),
//验证集数据可采用两种方式:1.validation_split : 0.2f将训练集划分出一定比例作为验证集。2.validation_data: (test_X, test_Y)指定已划分好的验证集进行验证。
model.fit(trainDataset,
epochs: 10,
validation_data: valDataset,
verbose: 0);
Console.WriteLine(); //换行
Console.Out.Flush(); // 强制刷新输出缓冲区,避免输出数据重叠
// 步骤5:评估模型
var testResult = model.evaluate(test_X_normalized, test_Y, //输入测试集数据,test_X对应特征, test_Y对应标签
verbose: 0); //详细度,0为最简模式,1为简单显示进度信息
// 从字典中提取测试集损失和准确率(Tensor 类型需通过 .numpy() 转换为数值)
double testLoss = (double)testResult["loss"]; // 通过键名 "loss" 获取损失值
double testAcc = (double)testResult["accuracy"]; // 通过键名 "accuracy" 获取准确率
// 步骤6:保存模型
model.save(filepath: "D:\\SavedModel\\SuperbignumberBB.model.tf", //模型保存路径(全英文且不能有空格,否则不能载入)
overwrite: true, //true,自动覆盖原有文件
include_optimizer: true, //true,将优化器信息保存一起
save_format: "tf"); //保存文件类型,"h5"模型为旧版本模型,目前不采用。
var weights = model.TrainableVariables; //获取训练参数,model.TrainableVariables 获取模型中所有可训练的变量,对于这个简单的线性回归模型,可训练变量包括权重和偏置。
print($"测试集损失test_Loss: {testLoss:F4}, 测试集准确率test_Acc: {testAcc:P2}"); //打印测试集指标
print($"权重weight: {weights[0].numpy()}");//打印训练后模型的权重和偏置。 weights[0] 对应权重, weights[1] 对应偏置,numpy() 方法将 TensorFlow 的张量转换为 NumPy 数组以便于打印输出。
print($"偏置bias: {weights[1].numpy()}");
}
}
}System.EntryPointNotFoundException
HResult=0x80131523
Message=Unable to find an entry point named 'TF_GetHandleShapeAndType' in DLL 'tensorflow'.
Source=Tensorflow.Binding
StackTrace:
在 Tensorflow.c_api.TF_GetHandleShapeAndType(SafeGraphHandle c_graph, TF_Output output)
在 Tensorflow.ops.get_resource_handle_data(Tensor graph_op)
在 Tensorflow.Operations.handle_data_util.get_resource_handle_data(Tensor graph_op)
在 Tensorflow.Eager.backprop_util._DTypeFromTensor(Tensor tensor)
在 Tensorflow.Eager.backprop_util.IsTrainable(Tensor tensor)
在 Tensorflow.Functions.TapeGradientFunctions.<>c.<.ctor>b__16_0(Tensor t)
在 System.Linq.Enumerable.WhereEnumerableIterator`1.GetCount(Boolean onlyIfCheap)
在 Tensorflow.Functions.TapeGradientFunctions..ctor(FuncGraph func_graph, Boolean need_gradients_for_jvps)
在 Tensorflow.Functions.DelayedRewriteGradientFunctions..ctor(FuncGraph func_graph, Dictionary`2 attrs)
在 Tensorflow.Functions.ConcreteFunction._set_infer_function()
在 Tensorflow.Functions.ConcreteFunction..ctor(Func`2 func, TF_DataType dtype)
在 Tensorflow.FlatMapDataset..ctor(IDatasetV2 input_dataset, Func`2 map_func)
在 Tensorflow.DatasetV2.flat_map(Func`2 map_func)
在 Tensorflow.Keras.Engine.DataAdapters.TensorLikeDataAdapter..ctor(DataAdapterArgs args)
在 Tensorflow.Keras.Engine.DataAdapters.DataHandler..ctor(DataHandlerArgs args)
在 Tensorflow.Keras.Engine.Model.evaluate(NDArray x, NDArray y, Int32 batch_size, Int32 verbose, NDArray sample_weight, Int32 steps, Int32 max_queue_size, Int32 workers, Boolean use_multiprocessing, Boolean return_dict, Boolean is_val)
在 Keras.NET_Prediction_main_program.Program.Main() 在 D:\编程软件系列\VS2022社区版\文件\Keras.NET Prediction main program\Program.cs 中: 第 165 行
最新发布