基于神经网络的氧气浓度预测:数学模型与数据集构建
在科研项目中,利用神经网络预测氧气浓度是一个具有挑战性的任务。本文将详细介绍相关的数学模型、回归问题的解决方法以及数据集的准备过程。
1. 数学模型
我们首先来看一个可用于确定氧气浓度的数学模型。该模型描述了相位 $\theta$ 与氧气浓度 $O_2$ 之间的关系,公式如下:
$$\tan\theta(\omega, T, O_2) = \frac{f(\omega, T)}{1 + K_{SV1}(\omega, T) \times O_2} + \frac{1 - f(\omega, T)}{1 + K_{SV2}(\omega, T) \times O_2}$$
其中,$f(\omega, T)$、$K_{SV1}(\omega, T)$ 和 $K_{SV2}(\omega, T)$ 是参数,其解析形式未知,且与所使用的染料分子、传感器的构建方式等因素有关。我们的目标是在实验室中训练一个神经网络,然后将其部署到可在现场使用的传感器上。主要问题在于确定 $f$、$K_{SV1}$ 和 $K_{SV2}$ 与频率和温度相关的函数形式。商业传感器通常依赖多项式或指数近似,并通过拟合过程来确定这些参数的近似值。
2. 回归问题示例
为了更好地理解后续复杂问题的处理方法,我们先来看一个简单的回归问题。给定一个带有参数 $A$ 的函数 $L(x)$,我们希望训练一个神经网络从该函数的一组值中提取参数 $A$ 的值。具体来说,对于输入变量 $x_i$($i = 1, \ldots, N$),我们计算 $L_i = L(x_i)$,并将其作为神经网络的输入,训练网络输出 $A$。
以洛伦兹函数为例:
$$L(x) = \frac{A^2}{A^2 + x^2}$$
该函数在 $x = 0$ 处有最大值,且 $L(0) = 1$。我们可以通过经典的非线性拟合或求解简单的二次方程来确定 $A$,但这里我们希望教会神经网络进行非线性拟合。
以下是创建训练数据集的步骤:
1.
定义函数 $L(x)$
def L(x,A):
y = A**2/(A**2+x**2)
return y
- 生成 $x$ 点
number_of_x_points = 100
min_x = 0.0
max_x = 5.0
x = np.arange(min_x, max_x, (max_x-min_x)/number_of_x_points )
- 生成观测值
number_of_samples = 1000
np.random.seed(20)
A_v = np.random.normal(1.0, 0.4, number_of_samples)
for i in range(len(A_v)):
if A_v[i] <= 0:
A_v[i] = np.random.random_sample([1])
data = np.zeros((number_of_samples, number_of_x_points))
targets = np.reshape(A_v, [1000,1])
for i in range(number_of_samples):
data[i,:] = L(x, A_v[i])
在上述代码中,我们通过检查确保 $A$ 的值不为负,因为负的 $A$ 值会导致神经网络无法正确区分正负值,从而产生错误结果。
我们还需要一个开发数据集:
number_of_dev_samples = 1000
np.random.seed(42)
A_v_dev = np.random.normal(1.0, 0.4, number_of_samples)
for i in range(len(A_v_dev)):
if A_v_dev[i] <= 0:
A_v_dev[i] = np.random.random_sample([1])
data_dev = np.zeros((number_of_samples, number_of_x_points))
targets_dev = np.reshape(A_v_dev, [1000,1])
for i in range(number_of_samples):
data_dev[i,:] = L(x, A_v_dev[i])
接下来,我们构建一个简单的单层神经网络,包含 10 个神经元:
tf.reset_default_graph()
n1 = 10
nx = number_of_x_points
n2 = 1
W1 = tf.Variable(tf.random_normal([n1,nx]))/500.0
b1 = tf.Variable(tf.ones((n1,1)))/500.0
W2 = tf.Variable(tf.random_normal([n2,n1]))/500.0
b2 = tf.Variable(tf.ones((n2,1)))/500.0
X = tf.placeholder(tf.float32, [nx, None]) # Inputs
Y = tf.placeholder(tf.float32, [1, None]) # Labels
Z1 = tf.matmul(W1,X)+b1
A1 = tf.nn.sigmoid(Z1)
Z2 = tf.matmul(W2,A1)+b2
y_ = Z2
cost = tf.reduce_mean(tf.square(y_-Y))
learning_rate = 0.1
training_step = tf.train.AdamOptimizer(learning_rate).minimize(cost)
init = tf.global_variables_initializer()
训练网络 20000 个周期:
sess = tf.Session()
sess.run(init)
training_epochs = 20000
cost_history = np.empty(shape=[1], dtype = float)
train_x = np.transpose(data)
train_y = np.transpose(targets)
cost_history = []
for epoch in range(training_epochs+1):
sess.run(training_step, feed_dict = {X: train_x, Y: train_y})
cost_ = sess.run(cost, feed_dict={ X:train_x, Y: train_y})
cost_history = np.append(cost_history, cost_)
if (epoch % 1000 == 0):
print("Reached epoch",epoch,"cost J =", cost_)
该模型收敛速度很快,均方误差(MSE)从开始的 1.1 下降到 10000 个周期后的约 $2.5 \times 10^{-4}$,20000 个周期后达到 $10^{-6}$。然而,开发集上的 MSE 为 $3 \times 10^{-5}$,可能存在轻微的过拟合现象。这可能是因为我们考虑的 $x$ 范围较窄(仅从 0 到 5),以及训练数据中 $A$ 的值分布不均匀。如果将 $A$ 的值从均匀分布中选取:
A_v = np.random.random_sample([number_of_dev_samples])*3.0
经过 20000 个周期后,训练集的 MSE 为 $3.8 \times 10^{-6}$,开发集的 MSE 为 $1.7 \times 10^{-6}$,预测效果更好,且似乎没有过拟合现象。
这里我们可以用一个 mermaid 流程图来展示整个回归问题的处理流程:
graph TD;
A[定义函数 L(x)] --> B[生成 x 点];
B --> C[生成观测值];
C --> D[生成开发数据集];
D --> E[构建神经网络];
E --> F[训练神经网络];
F --> G[评估模型];
3. 数据集准备
现在我们开始为项目生成所需的数据集。实验数据包括 50 次 $\theta$ 测量、5 个温度值($5^{\circ}C$、$15^{\circ}C$、$25^{\circ}C$、$35^{\circ}C$ 和 $45^{\circ}C$)以及 10 个不同的氧气浓度值($0\%$、$4\%$、$8\%$、$15\%$、$20\%$、$30\%$、$40\%$、$60\%$、$80\%$ 和 $100\%$)。每次测量包含 22 个频率测量值。
对于训练数据集,我们仅使用 3000 Hz 到 100000 Hz 之间的频率,因为低于 3000 Hz 和高于 100000 Hz 会出现伪像和误差,使用全量数据会使网络性能变差。
以下是数据集准备的具体步骤:
1.
获取文件列表
files = os.listdir('./data')
- 提取温度和氧气浓度信息
def get_T_O2(filename):
T_ = float(filename[17:19])
O2_ = float(filename[24:-4].replace('_','.'))
return T_, O2_
- 将文件内容转换为 Pandas 数据框
def get_df(filename):
frame = pd.read_csv('./data/'+filename, header = 10, sep = '\t')
frame = frame.drop(frame.columns[6], axis=1)
frame.columns=['f', 'ref_r', 'ref_phi', 'raw_r', 'raw_phi', 'sample_phi']
return frame
- 循环处理文件
frame = pd.DataFrame()
df_list = []
T_list = []
O2_list = []
for file_ in files:
df = get_df(file_)
T_, O2_ = get_T_O2(file_)
df_list.append(df)
T_list.append(T_)
O2_list.append(O2_)
- 计算角频率 $\omega$ 和 $\tan\theta$
for df_ in df_list:
df_['w'] = df_['f']*2*np.pi
df_['tantheta'] = np.tan(df_['sample_phi']*np.pi/180.0)
- 筛选特定温度的数据
T = 45
Tdf = pd.DataFrame(T_list, columns = ['T'])
Odf = pd.DataFrame(O2_list, columns = ['O2'])
filesdf = pd.DataFrame(files, columns = ['filename'])
files45 = filesdf[Tdf['T'] == T]
filesref = filesdf[(Tdf['T']==T) & (Odf['O2']==0)]
fileref_idx = filesref.index[0]
O5 = Odf[Tdf['T'] == T]
dfref = df_list[fileref_idx]
- 选择特定温度和氧气浓度为 0% 的数据框
from itertools import compress
A = Tdf['T'] == T
data = list(compress(df_list, A))
B = (Tdf['T']==T) & (Odf['O2']==0)
dataref_ = list(compress(df_list, B))
- 使用非线性拟合确定 $f$、$K_{SV1}$ 和 $K_{SV2}$ 的值
def fitfunc(x, f, KSV, KSV2):
return (f/(1.0+KSV*x)+ (1.0-f)/(1+KSV2*x))
f = []
KSV = []
KSV2 = []
for w_ in wred:
O2x = []
tantheta = []
tantheta0 = float(dataref_[0][dataref_[0]['w']==w_]['tantheta'])
for idx, df_ in enumerate(data_train):
O2xvalue = float(Odf.loc[idx])
O2x.append(O2xvalue)
tanthetavalue = float(df_[df_['w'] == w_]['tantheta'])
tantheta.append(tanthetavalue)
popt, pcov = curve_fit(fitfunc_2, O2x, np.array(tantheta)/tantheta0, p0 = [0.4,0.06, 0.003])
f.append(popt[0])
KSV.append(popt[1])
KSV2.append(popt[2])
- 选择特定角频率范围的值
w_ = w[4:20]
f_ = f[4:20]
KSV_ = KSV[4:20]
- 使用插值计算任意 $\omega$ 对应的 $f$、$K_{SV1}$ 和 $K_{SV2}$ 的值
from scipy.interpolate import interp1d
finter = interp1d(wred, f, kind='cubic')
KSVinter = interp1d(wred, KSV, kind = 'cubic')
KSV2inter = interp1d(wred, KSV2, kind = 'cubic')
- 生成训练数据集
number_of_samples = 5000
number_of_x_points = len(w_)
np.random.seed(20)
O2_v = np.random.random_sample([number_of_samples])*100.0
def fitfunc2(x, O2, ffunc, KSVfunc, KSV2func):
output = []
for x_ in x:
KSV_ = KSVfunc(x_)
KSV2_ = KSV2func(x_)
f_ = ffunc(x_)
output_ = f_/(1.0+KSV_*O2)+(1.0-f_)/(1.0+KSV2_*O2)
output.append(output_)
return output
data = np.zeros((number_of_samples, number_of_x_points))
targets = np.reshape(O2_v, [number_of_samples,1])
for i in range(number_of_samples):
data[i,:] = fitfunc2(w_, float(targets[i]), finter, KSVinter, KSV2inter)
通过以上步骤,我们完成了数据集的准备工作。整个数据集准备过程可以用以下 mermaid 流程图表示:
graph TD;
A[获取文件列表] --> B[提取温度和氧气浓度信息];
B --> C[转换为 Pandas 数据框];
C --> D[循环处理文件];
D --> E[计算角频率和 tanθ];
E --> F[筛选特定温度的数据];
F --> G[选择特定数据框];
G --> H[非线性拟合确定参数值];
H --> I[选择特定角频率范围的值];
I --> J[插值计算参数值];
J --> K[生成训练数据集];
综上所述,我们通过详细的步骤展示了如何利用数学模型创建训练数据集,并通过简单的回归问题示例和复杂的氧气浓度预测数据集准备过程,说明了神经网络在科研项目中的应用方法。在实际应用中,我们需要注意训练数据的范围和分布,避免过拟合问题,以提高模型的预测性能。
基于神经网络的氧气浓度预测:数学模型与数据集构建
4. 模型训练与评估
在完成数据集的准备后,我们可以使用生成的训练数据来训练神经网络,以预测氧气浓度。以下是一个简单的神经网络训练和评估的示例代码:
import tensorflow as tf
import numpy as np
# 假设 data 和 targets 是前面生成的训练数据和目标值
number_of_x_points = data.shape[1]
# 重置图
tf.reset_default_graph()
# 定义网络结构
n1 = 10 # 第一层神经元数量
nx = number_of_x_points
n2 = 1 # 输出层神经元数量
# 初始化权重和偏置
W1 = tf.Variable(tf.random_normal([n1, nx])) / 500.0
b1 = tf.Variable(tf.ones((n1, 1))) / 500.0
W2 = tf.Variable(tf.random_normal([n2, n1])) / 500.0
b2 = tf.Variable(tf.ones((n2, 1))) / 500.0
# 定义输入和标签占位符
X = tf.placeholder(tf.float32, [nx, None]) # 输入
Y = tf.placeholder(tf.float32, [1, None]) # 标签
# 前向传播
Z1 = tf.matmul(W1, X) + b1
A1 = tf.nn.sigmoid(Z1)
Z2 = tf.matmul(W2, A1) + b2
y_ = Z2
# 定义损失函数
cost = tf.reduce_mean(tf.square(y_ - Y))
# 定义优化器
learning_rate = 0.1
training_step = tf.train.AdamOptimizer(learning_rate).minimize(cost)
# 初始化变量
init = tf.global_variables_initializer()
# 训练网络
sess = tf.Session()
sess.run(init)
training_epochs = 20000
cost_history = np.empty(shape=[1], dtype=float)
train_x = np.transpose(data)
train_y = np.transpose(targets)
cost_history = []
for epoch in range(training_epochs + 1):
sess.run(training_step, feed_dict={X: train_x, Y: train_y})
cost_ = sess.run(cost, feed_dict={X: train_x, Y: train_y})
cost_history = np.append(cost_history, cost_)
if (epoch % 1000 == 0):
print("Reached epoch", epoch, "cost J =", cost_)
# 评估模型
# 这里可以将数据分为训练集和测试集,使用测试集进行评估
# 假设我们有一个测试集 data_test 和 targets_test
data_test = np.zeros((1000, number_of_x_points)) # 示例测试数据
targets_test = np.random.random_sample([1000, 1]) * 100.0 # 示例测试目标
test_x = np.transpose(data_test)
test_y = np.transpose(targets_test)
test_cost = sess.run(cost, feed_dict={X: test_x, Y: test_y})
print("Test cost:", test_cost)
在上述代码中,我们使用 TensorFlow 构建了一个简单的两层神经网络,使用均方误差作为损失函数,Adam 优化器进行训练。训练过程中,我们记录了每个周期的损失值,并在每 1000 个周期打印一次损失。最后,我们使用一个示例测试集对模型进行评估,打印出测试集的损失。
我们可以用一个表格来总结训练过程中的关键参数和结果:
| 参数/结果 | 描述 | 值 |
| ---- | ---- | ---- |
| 第一层神经元数量 | 神经网络第一层的神经元个数 | 10 |
| 学习率 | 优化器的学习率 | 0.1 |
| 训练周期数 | 训练的总周期数 | 20000 |
| 训练集初始损失 | 训练开始时的损失 | 初始值较高,逐渐下降 |
| 训练集最终损失 | 训练结束时的损失 | 达到 $10^{-6}$ 左右 |
| 测试集损失 | 测试集上的损失 | 示例中未详细计算,可根据实际测试集得到 |
5. 注意事项与优化建议
在整个氧气浓度预测的项目中,有一些注意事项和优化建议可以帮助我们提高模型的性能:
-
训练数据的范围和分布 :
- 如前面回归问题示例中所示,训练数据的范围和分布对模型的性能有重要影响。如果训练数据中某些值的分布不均匀,模型可能在这些值上表现不佳。因此,我们应该尽量确保训练数据覆盖所有可能的情况,避免数据缺失或分布不均。
- 可以使用不同的分布来生成训练数据,如均匀分布,以提高模型的泛化能力。
-
过拟合问题 :
-
过拟合是机器学习中常见的问题,表现为模型在训练集上表现良好,但在测试集上表现不佳。为了避免过拟合,我们可以采取以下措施:
- 增加训练数据 :更多的训练数据可以帮助模型学习到更广泛的特征,减少过拟合的风险。
- 正则化 :可以在损失函数中添加正则化项,如 L1 或 L2 正则化,以限制模型的复杂度。
- 早停法 :在训练过程中,监控验证集的性能,当验证集的性能不再提升时,提前停止训练。
-
过拟合是机器学习中常见的问题,表现为模型在训练集上表现良好,但在测试集上表现不佳。为了避免过拟合,我们可以采取以下措施:
-
模型结构的选择 :
- 简单的两层神经网络可能无法捕捉到复杂的模式。可以尝试增加网络的层数或神经元数量,以提高模型的表达能力。
- 也可以尝试不同的激活函数,如 ReLU、Leaky ReLU 等,以改善模型的性能。
6. 总结
通过本文的介绍,我们详细了解了利用神经网络预测氧气浓度的整个过程。从数学模型的建立,到回归问题的示例,再到数据集的准备、模型的训练和评估,我们逐步展示了如何将神经网络应用于科研项目中。
在实际应用中,我们需要根据具体的问题和数据特点,选择合适的模型结构和训练方法,注意训练数据的范围和分布,避免过拟合问题,以提高模型的预测性能。同时,不断优化和改进模型,以适应不同的场景和需求。
整个项目的流程可以用以下 mermaid 流程图来总结:
graph TD;
A[数学模型建立] --> B[回归问题示例];
B --> C[数据集准备];
C --> D[模型训练];
D --> E[模型评估];
E --> F[注意事项与优化];
F --> G[持续改进与应用];
通过以上步骤和方法,我们可以利用神经网络有效地解决氧气浓度预测等科研问题,并为实际应用提供可靠的支持。
超级会员免费看
486

被折叠的 条评论
为什么被折叠?



