.Net 9下使用Tensorflow.net---DNN_Eager
本示例演示通过Tensorflow.net训练的基本操作步骤:
一、数据加载,预处理
二、选择网络训练模型(本例使用 Eager的 DNN)
三、定义损失函数、优化函数
四、训练模型且通过优化函数优化网络权重参数
五、评估结果
1、创建控制台应用项目
首先,创建一个控制台应用:
选择.net9
其他选项先忽略,直接创建
2、导入Tensorflow.net等依赖
通过nuget包管理器,导入如下依赖:
2、TensorFlow.keras -- keras是一个神经网络的重要的API,在Tensorflow中用于加载数据、获取模型、生成优化器,生成损失函数等重要作用。
3、SciSharp.TensorFlow.Redist -- 这是TensorFlow的核心包,封装了TensorFlow的核心函数,分为GPU和CPU两个版本,具有跨平台、高性能的特点。
4、NumSharp--.net环境下实现的python中的numpy库,用于科学计算
3、开始示例
引入类库
using Tensorflow;
using Tensorflow.Keras.Optimizers;
using static Tensorflow.Binding;
using static Tensorflow.KerasApi;
using Tensorflow.Keras.Engine;
定义神经网络训练权重
说明:
//MNIST数据集还是一个手写数字识别的数据集,用于入门和测试的首选
//这个数据集包含70000张28x28像素的手写灰度数字图像,分为10类,分别对应0到9的数字。具体来说,数据集分为四个部分:
训练集图像:包含60000张训练图像。
训练集标签:包含60000个训练标签。
测试集图像:包含10000张测试图像。
测试集标签:包含10000个测试标签
int num_classes = 10;
//本示例中使用MNIST数字数据集作为数据集,该数据集中按字符(0--9)分类,所以分了10类
int num_features = 784;
//该数据集对应的图像像素尺寸为28*28=784
//--以下为训练使用参数--//
float learning_rate = 0.001f;//学习率
int training_steps = 1000;//训练轮数
int batch_size = 256; //训练集批次大小
int display_step = 100;//训练集数据显示周期
//--以下定义了神经网络相关的参数,因为本示例使用DNN模型,定义两层的神经网络
int n_dnn_1 = 128; //隐藏层1的神经元数量
int n_dnn_2 = 256;// 隐藏层2的神经元数量
IDatasetV2 train_data; //MNIST数据集
//定义训练变量
IVariableV1 w_dnn1, w_dnn2, wout, b_dnn1, b_dnn2, bout;
float accuracy_dnn_test = 0f; //测试机准确率输出
定义一个DNN的模型
本示例借助Eager方式(暂不使用Keras方式)搭建神经网络中经典的DNN模型,采用了两个隐藏层。
该函数作用其实还是遵循线性函数输出y=wout*x+bout的模式,得到最终的输出层的结果,
函数中包含几个关键因素:
1、layer_1第一层隐藏层
2、layer_2第二层隐藏层
3、激活函数tf.nn.sigmoid():
sigmoid函数的数学表达式为:
作用是该函数将输入映射到(0, 1)区间,常用于二分类问题中表示概率。
除此之外,还有 tf.nn.softmax()激活函数:
作用是:Softmax函数将每个元素映射到(0, 1)区间,并且所有元素的和为1,表示概率分布。
Tensor Dnn_neural_net(Tensor x)
{
//隐藏层1采用128个神经元
var layer_1=tf.add(tf.matmul(x,w_dnn1.AsTensor()),b_dnn1.AsTensor());
//使用Sigmoid激活函数,增加层输出的非线性特征
layer_1=tf.nn.sigmoid(layer_1);
var layer_2=tf.add(tf.matmul(layer_1, w_dnn2.AsTensor()),b_dnn2.AsTensor());
layer_2=tf.nn.sigmoid(layer_2);
//输出层的神经元数量和标签类型数量相同
var out_layer=tf.matmul(layer_2,wout.AsTensor())+bout.AsTensor();
return tf.nn.softmax(out_layer);
}
定义损失函数
本示例是一个分类的训练,所以损失函数依旧使用交叉熵损失函数
说明:
1、y_pred参数获取以上模型的输出层结果,y_true参数获取训练集中的标签结果,
2、tf.one_hot()处理函数,
该函数作用是将输入的数值转换为one-hot编码的输出。
one-hot编码指的是:将张量中的变量映射为0,1这种二进制的值,并且张量中只有一个变量是1,其余全为0,本例做的是仍然是分类训练,所以适合使用。
tf.clip_by_value(),函数的作用是将张量y_pred中的数值限制在一个范围之内。(避免一些运算错误)
Tensor cross_entroy(Tensor y_pred, Tensor y_true)
{
y_true=tf.one_hot(y_true,depth:num_classes);
y_pred = tf.clip_by_value(y_pred, 1e-9f, 1.0f);
return tf.reduce_mean(-tf.reduce_sum(y_true * tf.math.log(y_pred)));
}
定义一个运行优化器
TensorFlow具备自动求导的机制,该函数中,首先创建梯度记录器,自动跟踪神经网络中的梯度,自动求导并进行梯度下降和网络权重变化的更新优化。
说明:
1、参数:optimizer :梯度下降函数,本例中将会传入 kerasAPI中 keras.optimizers.SGD()
x:训练数据集,y:训练标签集,trainble_variables:需要通过该函数优化的网络权重参数
2、tf.GradientTape() 获取梯度计数器
3、g.gradient() 计算梯度
void run_optimization(IOptimizer optimizer,Tensor x,Tensor y, IVariableV1[] trainble_variables)
{
using var g = tf.GradientTape();
var pred = Dnn_neural_net(x);
var loss=cross_entroy(pred, y);
//计算梯度
var gradients = g.gradient(loss, trainble_variables);
//利用梯度下降函数更新模型权重和偏置项
var a = zip(gradients, trainble_variables.Select(x => x as ResourceVariable));
optimizer.apply_gradients(zip(gradients, trainble_variables.Select(x => x as ResourceVariable)));
}
定义一个获取最终模型预测准确率的函数
说明:
tf.reduce_mean 函数用于计算张量y_pred 沿着指定的数轴的均值,axis=-1表示不限制维度,0表示第一维度,即是行,1表示第二维度,即是列
Tensor accuary(Tensor y_pred, Tensor y_true)
{
var correct_prediction=tf.equal(tf.arg_max(y_pred,1), tf.cast(y_true,tf.int64));
return tf.reduce_mean(tf.cast(correct_prediction,tf.float32),axis:-1);
}
开始调用以上准备好的函数
1、加载MNIST数据,进行预处理
预处理步骤为 加载-->展平(通过resharp函数获取想要的维度)-->归一化(数据格式上需要统一)-->转为可以处理的格式(本例子中转化为Dataset格式)-->复制
public void FNN()
{
//准备基本数据
var ((x_train, y_train), (x_test, y_test)) = keras.datasets.mnist.load_data();//下载或加载本地MNIST
//步骤一:进行数据的整理,包括 1、数据集的维度--shape,2、数据集的数据格式--float
(x_train, x_test) = (x_train.reshape((-1, num_features)), x_test.reshape((-1, num_features)));//展平输入数据
(x_train, x_test) = (x_train / 255f, x_test / 255f);//归一化
//步骤二:进一步初始化,该函数作用很广泛:就是将数据集切片化,将一个张量切片为多个张量
//后续通过 tensorflow的API进行处理 from_tensor_slices--将数据集切片化,将一个张量切片为多个张量
//repeat()--复制,shuffle--打乱顺序,batch--按参数分成对应的批次,
train_data = tf.data.Dataset.from_tensor_slices(x_train, y_train);
train_data = train_data.repeat()
.shuffle(5000)
.batch(batch_size)
.prefetch(1)
.take(training_steps);
//以上预处理结束,获取到 后续要用到 训练集
}
2、初始化网络权重变量x,b,初始化优化器
说明:以下是通过随机函数得到隐藏层1、隐藏层2、并且定义 权重参数w,偏移值b的初始化值,
后续通过 优化函数进行迭代优化,
注意:dnn1和dnn2 需要满足矩阵的乘法要求。dnn1的列数=dnn2的行数
public void FNN()
{
//接上 上一段代码 : 以上预处理结束,获取到 后续要用到 训练集
//随机初始化网络权重变量并打包成数组,方便后续在梯度求导中作为参数
var random_normal = tf.random_normal_initializer();
w_dnn1 = tf.Variable(random_normal.Apply(new InitializerArgs((num_features, n_dnn_1),dtype:TF_DataType.TF_FLOAT)));
w_dnn2 = tf.Variable(random_normal.Apply(new InitializerArgs((n_dnn_1, n_dnn_2), dtype: TF_DataType.TF_FLOAT)));
wout = tf.Variable(random_normal.Apply(new InitializerArgs((n_dnn_2, num_classes), dtype: TF_DataType.TF_FLOAT)));
b_dnn1 = tf.Variable(tf.zeros(n_dnn_1));
b_dnn2 = tf.Variable(tf.zeros(n_dnn_2));
bout=tf.Variable(tf.zeros(num_classes));
var trainable_variables=new IVariableV1[]{w_dnn1, w_dnn2, wout,b_dnn1,b_dnn2,bout};
//采用keras的随机梯度下降优化器
var optimizer = keras.optimizers.SGD(learning_rate);
3、训练模型,并且在测试集上进行性能评估
public void FNN()
{
//接上 上一段代码 :
foreach (var (step, (batch_x, batch_y)) in enumerate(train_data, 1))
{
run_optimization(optimizer, batch_x, batch_y, trainable_variables);
if (step % display_step == 0)
{
var pred = Dnn_neural_net(batch_x);
var loss=cross_entroy(pred, batch_y);
var acc=accuary(pred, batch_y);
print($"step:{step},loss:{(float)loss},accuray:{acc}");
}
}
{
var pred= Dnn_neural_net(x_test);
accuracy_dnn_test=(float)accuary(pred, y_test);
print($"Test Accuracy:{accuracy_dnn_test}");
}
}
4、console的主函数中调用以上代码,获取输出结果
DNN_Eager eager = new DNN_Eager();
eager.FNN();
输出如下:
最终输出预测准确率为0.858,率低于0.863,基本合格。但是.net调用效率真的很高