神经网络模型拟合曲线,先拟合低频信号,再拟合高频信号,以下是验证代码。这个代码实现了以下功能:
-
可视化功能:
-
每500个epoch保存一张图片
-
每张图片包含时域对比和频域对比
-
解决中文字符显示问题
-
共生成21张图片(包括epoch 0)
-
-
视频制作:
-
将21张图片合成为MP4视频
-
2帧/秒的播放速度
-
最后一张图片多停留5秒
-
-
PPT生成:
-
自动生成PPT报告
-
包含标题页、图片展示页和视频页
-
使用python-pptx库
-
python
import torch
import torch.nn as nn
import torch.optim as optim
import numpy as np
import matplotlib.pyplot as plt
from matplotlib import font_manager
import os
import cv2
from pptx import Presentation
from pptx.util import Inches
import warnings
warnings.filterwarnings('ignore')
# 1. 设置中文字体 - 更可靠的方法
def setup_chinese_font():
"""设置中文字体,使用绝对路径"""
try:
# 尝试多种方法设置中文字体
font_paths = [
'C:/Windows/Fonts/simhei.ttf', # Windows
'C:/Windows/Fonts/msyh.ttc', # Windows微软雅黑
'/System/Library/Fonts/PingFang.ttc', # Mac
'/usr/share/fonts/truetype/wqy/wqy-microhei.ttc', # Linux
]
font_added = False
for font_path in font_paths:
if os.path.exists(font_path):
font_prop = font_manager.FontProperties(fname=font_path)
font_manager.fontManager.addfont(font_path)
font_name = font_prop.get_name()
plt.rcParams['font.sans-serif'] = [font_name]
print(f"使用字体: {font_name}")
font_added = True
break
if not font_added:
# 如果找不到字体,使用默认英文字体
plt.rcParams['font.sans-serif'] = ['DejaVu Sans']
print("使用英文字体")
plt.rcParams['axes.unicode_minus'] = False
except Exception as e:
print(f"字体设置失败: {e},使用默认字体")
plt.rcParams['font.sans-serif'] = ['DejaVu Sans']
plt.rcParams['axes.unicode_minus'] = False
setup_chinese_font()
# 2. 创建保存目录
os.makedirs('training_figures', exist_ok=True)
os.makedirs('output', exist_ok=True)
# 3. 生成数据 - 修改为 y = sin(2πx) + sin(20πx) + sin(40πx)
def generate_data(n_samples=1000):
"""生成训练数据: y = sin(2πx) + sin(20πx) + sin(40πx)"""
x = torch.linspace(0, 2, n_samples).reshape(-1, 1) # 修改x范围为[0, 2]以看到完整的周期性
y = torch.sin(2*np.pi*x) + torch.sin(20*np.pi*x) + torch.sin(40*np.pi*x)
return x, y
# 4. 定义神经网络模型
class SignalNet(nn.Module):
def __init__(self, hidden_size=128):
super(SignalNet, self).__init__()
self.net = nn.Sequential(
nn.Linear(1, hidden_size),
nn.ReLU(),
nn.Linear(hidden_size, hidden_size),
nn.ReLU(),
nn.Linear(hidden_size, hidden_size),
nn.ReLU(),
nn.Linear(hidden_size, 1)
)
def forward(self, x):
return self.net(x)
# 5. 计算频域信号
def compute_frequency_domain(signal, sampling_rate=100):
"""计算信号的频域表示"""
n = len(signal)
# 对信号进行FFT
yf = np.fft.fft(signal.flatten())
xf = np.fft.fftfreq(n, 1/sampling_rate)
# 只取正频率部分
idx = np.where(xf >= 0)
xf_pos = xf[idx]
yf_pos = np.abs(yf[idx]) / n * 2
yf_pos[0] /= 2 # DC分量不需要乘以2
return xf_pos[:n//2], yf_pos[:n//2]
# 6. 分析信号的频率成分
def analyze_frequency_components():
"""分析目标信号的频率成分"""
x, y = generate_data(1000)
x_np = x.numpy().flatten()
y_np = y.numpy().flatten()
# 计算采样率
dx = x_np[1] - x_np[0] # 采样间隔
sampling_rate = 1 / dx # 采样频率
freq, mag = compute_frequency_domain(y_np, sampling_rate)
print(f"采样间隔: {dx:.6f}")
print(f"采样频率: {sampling_rate:.2f} Hz")
print(f"奈奎斯特频率: {sampling_rate/2:.2f} Hz")
# 找到主要频率成分
print("\n主要频率成分:")
freq_indices = np.argsort(mag)[-5:] # 找到幅度最大的5个频率
for idx in freq_indices:
if mag[idx] > 0.01: # 只显示幅度大于0.01的成分
print(f" 频率: {freq[idx]:.2f} Hz, 幅度: {mag[idx]:.4f}")
# 7. 简化频域绘图
def plot_frequency_domain_simple(ax, freq_true, mag_true, freq_pred, mag_pred, epoch):
"""使用线图绘制频域对比,避免stem问题"""
ax.plot(freq_true, mag_true, 'b-', linewidth=2, label='原始信号', alpha=0.7)
ax.plot(freq_pred, mag_pred, 'r--', linewidth=2, label='拟合信号', alpha=0.9)
ax.set_xlabel('频率 (Hz)', fontsize=12)
ax.set_ylabel('幅度', fontsize=12)
ax.set_title(f'频域对比 (Epoch: {epoch})', fontsize=14, pad=15)
ax.legend(fontsize=10)
ax.grid(True, alpha=0.3)
ax.set_xlim([0, 50]) # 调整为更高的频率范围
ax.set_ylim(bottom=0)
ax.tick_params(axis='both', which='major', labelsize=10)
return ax
# 8. 增强的训练函数
def train_model():
# 参数设置
epochs = 10000
save_interval = 500
learning_rate = 0.001 # 降低学习率以适应更复杂的信号
# 生成数据
x, y_true = generate_data(2000) # 增加采样点
# 初始化模型
model = SignalNet(hidden_size=256) # 增加隐藏层大小
criterion = nn.MSELoss()
optimizer = optim.Adam(model.parameters(), lr=learning_rate)
# 学习率调度器
scheduler = optim.lr_scheduler.StepLR(optimizer, step_size=2000, gamma=0.5)
# 记录损失
losses = []
# 计算采样率用于频域分析
x_np = x.numpy().flatten()
dx = x_np[1] - x_np[0]
sampling_rate = 1 / dx
print(f"采样频率: {sampling_rate:.2f} Hz")
print(f"目标频率: 1 Hz, 10 Hz, 20 Hz (对应 sin(2πx), sin(20πx), sin(40πx))")
# 训练循环
for epoch in range(epochs + 1):
# 前向传播
y_pred = model(x)
loss = criterion(y_pred, y_true)
# 反向传播
optimizer.zero_grad()
loss.backward()
optimizer.step()
scheduler.step()
losses.append(loss.item())
# 每500个epoch保存图片
if epoch % save_interval == 0 or epoch == 0:
print(f'Epoch [{epoch:5d}/{epochs}], Loss: {loss.item():.6f}, LR: {scheduler.get_last_lr()[0]:.6f}')
with torch.no_grad():
y_pred = model(x)
# 转换为numpy用于绘图
y_true_np = y_true.numpy().flatten()
y_pred_np = y_pred.numpy().flatten()
# 创建图形 - 增大图形尺寸
fig, axes = plt.subplots(1, 2, figsize=(16, 8))
# 设置主标题
fig.suptitle(f'信号拟合: y = sin(2πx) + sin(20πx) + sin(40πx)',
fontsize=20, y=0.98, fontweight='bold')
# 时域图
ax1 = axes[0]
ax1.plot(x_np, y_true_np, 'b-', label='原始信号', alpha=0.7, linewidth=1.5)
ax1.plot(x_np, y_pred_np, 'r--', label='拟合信号', alpha=0.9, linewidth=1.5)
ax1.set_xlabel('x', fontsize=14)
ax1.set_ylabel('y', fontsize=14)
ax1.set_title(f'时域对比 (Epoch: {epoch})', fontsize=16, pad=15)
ax1.legend(fontsize=12, loc='upper right')
ax1.grid(True, alpha=0.3)
ax1.set_xlim([0, 2])
ax1.tick_params(axis='both', which='major', labelsize=12)
# 频域图
ax2 = axes[1]
# 计算频域
freq_true, mag_true = compute_frequency_domain(y_true_np, sampling_rate)
freq_pred, mag_pred = compute_frequency_domain(y_pred_np, sampling_rate)
# 使用线图
ax2 = plot_frequency_domain_simple(ax2, freq_true, mag_true, freq_pred, mag_pred, epoch)
# 标记主要频率成分
target_freqs = [1, 10, 20] # 目标信号的频率
colors = ['green', 'orange', 'purple']
for i, freq in enumerate(target_freqs):
ax2.axvline(x=freq, color=colors[i], linestyle=':', alpha=0.5, linewidth=1)
ax2.text(freq+0.5, ax2.get_ylim()[1]*0.9, f'{freq}Hz',
color=colors[i], fontsize=10, alpha=0.7)
# 在图中添加损失信息
plt.figtext(0.5, 0.01, f'Epoch: {epoch}, Loss: {loss.item():.6f}, Learning Rate: {scheduler.get_last_lr()[0]:.6f}',
ha='center', fontsize=12, bbox=dict(boxstyle="round,pad=0.3",
facecolor='lightgray', alpha=0.7))
# 调整布局
plt.tight_layout(rect=[0, 0.05, 1, 0.95])
# 保存图片
filename = f'training_figures/epoch_{epoch:05d}.png'
plt.savefig(filename, dpi=150, bbox_inches='tight')
plt.close()
print(f' 已保存图片: {filename}')
return model, losses, sampling_rate
# 9. 创建视频
def create_video():
print("\n正在创建视频...")
# 获取所有图片文件
image_folder = 'training_figures'
video_name = 'output/training_progress.mp4'
# 获取并按epoch排序图片
image_files = []
for f in os.listdir(image_folder):
if f.endswith('.png') and f.startswith('epoch_'):
# 从文件名提取epoch数用于排序
try:
epoch = int(f.split('_')[1].split('.')[0])
image_files.append((epoch, f))
except:
pass
# 按epoch排序
image_files.sort()
image_files = [f[1] for f in image_files] # 只保留文件名
if not image_files:
print("没有找到图片文件!")
return
# 读取第一张图片获取尺寸
first_image_path = os.path.join(image_folder, image_files[0])
frame = cv2.imread(first_image_path)
if frame is None:
print("无法读取第一张图片!")
return
height, width, layers = frame.shape
# 创建视频写入器
fourcc = cv2.VideoWriter_fourcc(*'mp4v')
video = cv2.VideoWriter(video_name, fourcc, 2, (width, height)) # 2 fps
for i, image in enumerate(image_files):
img_path = os.path.join(image_folder, image)
img = cv2.imread(img_path)
if img is not None:
video.write(img)
print(f' 已添加帧: {image}')
# 最后一张图片多停留一会儿
if i == len(image_files) - 1:
for _ in range(20): # 多停留10秒
video.write(img)
video.release()
cv2.destroyAllWindows()
print(f"视频已保存: {video_name}")
# 10. 改进的PPT生成函数
def create_ppt():
print("\n正在创建PPT...")
# 创建新的PPT
prs = Presentation()
# 设置PPT尺寸为16:9
prs.slide_width = Inches(13.33) # 16:9的宽度
prs.slide_height = Inches(7.5) # 16:9的高度
# 标题幻灯片
title_slide_layout = prs.slide_layouts[0]
slide = prs.slides.add_slide(title_slide_layout)
# 设置标题
title = slide.shapes.title
title.text = "神经网络信号拟合训练报告"
title.text_frame.paragraphs[0].font.size = Inches(0.7)
# 设置副标题
subtitle = slide.placeholders[1]
subtitle.text = f"拟合函数: y = sin(2πx) + sin(20πx) + sin(40πx)\n训练轮数: 10000\n生成时间: 2026年5月25日\n\n使用框架: PyTorch\nPython 3.12.10"
# 获取所有图片并按epoch排序
image_files = []
for f in os.listdir('training_figures'):
if f.endswith('.png') and f.startswith('epoch_'):
try:
epoch = int(f.split('_')[1].split('.')[0])
image_files.append((epoch, f))
except:
pass
image_files.sort() # 按epoch排序
if not image_files:
print("没有找到训练图片!")
return None
print(f"找到 {len(image_files)} 张图片")
# 为每张图片创建单独的幻灯片
for epoch, img_file in image_files:
# 创建空白幻灯片
blank_slide_layout = prs.slide_layouts[6]
slide = prs.slides.add_slide(blank_slide_layout)
# 添加标题
title_box = slide.shapes.add_textbox(Inches(1), Inches(0.2), Inches(11.33), Inches(0.8))
title_frame = title_box.text_frame
title_frame.text = f"训练进度 - Epoch: {epoch}"
title_frame.paragraphs[0].font.size = Inches(0.5)
title_frame.paragraphs[0].font.bold = True
title_frame.paragraphs[0].alignment = 1 # 居中
# 添加图片
img_path = f'training_figures/{img_file}'
if os.path.exists(img_path):
# 计算图片大小和位置以使其居中
left = Inches(0.5)
top = Inches(1.0)
width = Inches(12.33)
height = Inches(6.0) # 稍微减小高度,为底部留出空间
slide.shapes.add_picture(img_path, left, top, width=width, height=height)
# 添加底部说明
footer_box = slide.shapes.add_textbox(Inches(1), Inches(7.1), Inches(11.33), Inches(0.4))
footer_frame = footer_box.text_frame
footer_frame.text = f"Epoch {epoch}/10000 - 神经网络拟合复合正弦信号: y = sin(2πx) + sin(20πx) + sin(40πx)"
footer_frame.paragraphs[0].font.size = Inches(0.2)
footer_frame.paragraphs[0].alignment = 1 # 居中
else:
print(f"警告: 图片不存在 {img_path}")
# 添加视频幻灯片
slide_layout = prs.slide_layouts[1]
slide = prs.slides.add_slide(slide_layout)
title = slide.shapes.title
title.text = "训练过程完整视频"
content = slide.placeholders[1]
content.text = "以下为完整的训练过程视频,展示了神经网络从随机初始化到逐步拟合目标函数的全过程。\n\n视频文件: training_progress.mp4\n\n双击下方图标播放视频"
# 添加视频占位符
left = Inches(4)
top = Inches(3)
width = Inches(5.33)
height = Inches(3)
# 添加矩形框作为占位符
from pptx.enum.shapes import MSO_SHAPE
from pptx.dml.color import RGBColor
shape = slide.shapes.add_shape(
MSO_SHAPE.ROUNDED_RECTANGLE, left, top, width, height
)
shape.text = f"▶ 播放视频"
shape.fill.solid()
shape.fill.fore_color.rgb = RGBColor(240, 240, 240)
shape.line.color.rgb = RGBColor(0, 120, 215)
shape.line.width = 2
# 添加视频文件路径
path_box = slide.shapes.add_textbox(Inches(2), Inches(6.5), Inches(9.33), Inches(0.5))
path_frame = path_box.text_frame
path_frame.text = "视频文件位置: output/training_progress.mp4"
path_frame.paragraphs[0].font.size = Inches(0.18)
path_frame.paragraphs[0].alignment = 1 # 居中
# 添加总结幻灯片
summary_slide = prs.slides.add_slide(title_slide_layout)
summary_title = summary_slide.shapes.title
summary_title.text = "训练总结"
summary_content = summary_slide.placeholders[1]
summary_content.text = "训练已完成!\n\n总结:\n• 总训练轮数: 10000\n• 生成图片数量: 21张\n• 训练过程已保存为视频\n• 最终损失值已收敛\n\n通过神经网络成功拟合了复合正弦波函数\n目标函数: y = sin(2πx) + sin(20πx) + sin(40πx)"
# 保存PPT
ppt_path = 'output/training_report.pptx'
prs.save(ppt_path)
print(f"PPT已保存: {ppt_path}")
print(f"PPT包含 {len(prs.slides)} 张幻灯片")
return ppt_path
# 11. 生成训练报告
def generate_report(losses, sampling_rate):
"""生成训练报告"""
report_path = 'output/training_report.txt'
with open(report_path, 'w', encoding='utf-8') as f:
f.write("=" * 60 + "\n")
f.write("信号拟合训练报告\n")
f.write("=" * 60 + "\n\n")
f.write(f"训练时间: 2026年5月25日\n")
f.write(f"训练环境:\n")
f.write(f" Python版本: 3.12.10\n")
f.write(f" PyTorch版本: {torch.__version__}\n")
f.write(f" CUDA版本: {torch.version.cuda if torch.cuda.is_available() else '未使用CUDA'}\n\n")
f.write("目标函数: y = sin(2πx) + sin(20πx) + sin(40πx)\n")
f.write("频率成分: 1Hz, 10Hz, 20Hz\n\n")
f.write("神经网络结构:\n")
f.write(" 输入层: 1个神经元\n")
f.write(" 隐藏层: 3层,每层256个神经元,使用ReLU激活函数\n")
f.write(" 输出层: 1个神经元\n\n")
f.write("训练参数:\n")
f.write(f" 训练轮数: 10000\n")
f.write(f" 学习率: 0.001 (带衰减)\n")
f.write(f" 优化器: Adam\n")
f.write(f" 损失函数: MSE\n")
f.write(f" 采样频率: {sampling_rate:.2f} Hz\n")
f.write(f" 采样点数: 2000\n\n")
f.write("训练结果:\n")
f.write(f" 初始损失: {losses[0]:.6f}\n")
f.write(f" 最终损失: {losses[-1]:.6f}\n")
f.write(f" 损失减少: {losses[0] - losses[-1]:.6f}\n")
f.write(f" 最终损失为初始损失的: {losses[-1]/losses[0]*100:.2f}%\n\n")
f.write("生成文件:\n")
f.write(" training_figures/ - 21张训练过程图片\n")
f.write(" output/training_progress.mp4 - 训练过程视频\n")
f.write(" output/training_report.pptx - PPT报告\n")
f.write(" output/training_report.txt - 文本报告\n")
f.write(" output/loss_curve.png - 损失曲线图\n")
f.write(" output/final_result.png - 最终拟合结果图\n")
print(f"训练报告已保存: {report_path}")
# 12. 主函数
def main():
print("信号频率分析:")
print("=" * 60)
analyze_frequency_components()
print("\n开始训练神经网络...")
print("=" * 60)
# 检查CUDA是否可用
if torch.cuda.is_available():
print(f"CUDA可用,使用GPU: {torch.cuda.get_device_name(0)}")
device = torch.device('cuda')
else:
print("使用CPU进行训练")
device = torch.device('cpu')
# 训练模型
model, losses, sampling_rate = train_model()
# 生成训练报告
generate_report(losses, sampling_rate)
# 创建视频
create_video()
# 创建PPT
ppt_path = create_ppt()
# 绘制损失曲线
plt.figure(figsize=(12, 6))
plt.subplot(1, 2, 1)
plt.plot(losses)
plt.xlabel('Epoch')
plt.ylabel('Loss')
plt.title('训练损失曲线')
plt.grid(True, alpha=0.3)
plt.subplot(1, 2, 2)
plt.semilogy(losses) # 使用对数坐标
plt.xlabel('Epoch')
plt.ylabel('Loss (log scale)')
plt.title('训练损失曲线(对数坐标)')
plt.grid(True, alpha=0.3)
plt.suptitle('训练损失曲线', fontsize=16, fontweight='bold')
plt.tight_layout()
plt.savefig('output/loss_curve.png', dpi=150, bbox_inches='tight')
# 绘制最终结果
with torch.no_grad():
x_test, y_true = generate_data(2000)
y_pred = model(x_test)
x_np = x_test.numpy().flatten()
y_true_np = y_true.numpy().flatten()
y_pred_np = y_pred.numpy().flatten()
fig, axes = plt.subplots(1, 2, figsize=(16, 8))
# 时域图
axes[0].plot(x_np, y_true_np, 'b-', label='原始信号', alpha=0.7, linewidth=1.5)
axes[0].plot(x_np, y_pred_np, 'r--', label='拟合信号', alpha=0.9, linewidth=1.5)
axes[0].set_xlabel('x', fontsize=14)
axes[0].set_ylabel('y', fontsize=14)
axes[0].set_title(f'最终拟合结果 (Epoch: 10000)', fontsize=16, pad=15)
axes[0].legend(fontsize=12, loc='upper right')
axes[0].grid(True, alpha=0.3)
axes[0].set_xlim([0, 2])
axes[0].tick_params(axis='both', which='major', labelsize=12)
# 频域图
axes[1] = plot_frequency_domain_simple(axes[1],
*compute_frequency_domain(y_true_np, sampling_rate),
*compute_frequency_domain(y_pred_np, sampling_rate),
10000)
fig.suptitle(f'最终拟合结果对比\nLoss: {losses[-1]:.6f}', fontsize=20, y=0.98, fontweight='bold')
plt.tight_layout(rect=[0, 0, 1, 0.95])
plt.savefig('output/final_result.png', dpi=150, bbox_inches='tight')
print("\n" + "=" * 60)
print("所有任务完成!")
print("=" * 60)
print("\n生成的文件:")
print("1. training_figures/ 目录: 包含21张训练过程图片")
print("2. output/training_progress.mp4: 训练过程视频")
print("3. output/training_report.pptx: PPT报告")
print("4. output/training_report.txt: 文本报告")
print("5. output/loss_curve.png: 损失曲线图")
print("6. output/final_result.png: 最终拟合结果图")
# 显示图表
plt.show()
# 13. 运行主程序
if __name__ == "__main__":
main()