神经网络的频率原则:先学习低频,再学习高频

神经网络模型拟合曲线,先拟合低频信号,再拟合高频信号,以下是验证代码。这个代码实现了以下功能:

  • 可视化功能

    • 每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()
相关推荐
书生的梦20 小时前
《神经网络与深度学习》学习笔记(二)
深度学习·神经网络·学习
Zevalin爱灰灰2 天前
智能控制 第五章——神经网络控制论
人工智能·神经网络
MediaTea2 天前
AI 术语通俗词典:LSTM
人工智能·rnn·深度学习·神经网络·lstm
数据门徒2 天前
神经网络原理 第九章:自组织映射
人工智能·神经网络·机器学习
MediaTea2 天前
DL:生成对抗网络的基本原理与 PyTorch 实现
人工智能·pytorch·深度学习·神经网络·生成对抗网络
Zevalin爱灰灰2 天前
智能控制 第六章——集成智能控制系统
神经网络·智能·控制算法
MediaTea2 天前
PyTorch:神经网络模块
人工智能·pytorch·python·深度学习·神经网络
AI即插即用2 天前
即插即用系列 | SliMamba——空谱维度魔术转换,打造高光谱分类的超轻量级 Mamba 架构
人工智能·深度学习·神经网络·目标检测·计算机视觉·数据挖掘
ZhengEnCi2 天前
09a-斯坦福 CS336 作业一:BPE 分词器
python·神经网络