#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""
DirectML 兼容文本生成器训练与生成脚本(工业协同版)
=====================================================
核心创新:
  - 手工实现 BasicLSTM,仅使用 Linear、Sigmoid、Tanh 等基础算子,
    完全规避 DirectML 对原生 nn.LSTM 的混合精度缺失问题。
  - 采用 GPU(DirectML)主力计算 + CPU 兜底采样的协同架构,
    在老破小集成显卡上实现稳定的端到端训练。
  - 内置 5 个专业报告模板,模拟八维稳态分析输出,自动构建数据集。

适用环境:
  - Windows + DirectML(任意支持 DX12 的显卡)
  - 纯 CPU 也可运行(自动降级)

作者:李文龙
理论支撑:全域共生复杂动力系统
发布日期:2026-05-06
"""

import random
import torch
import torch.nn as nn
import torch.nn.functional as F
from torch.utils.data import Dataset, DataLoader

# ===================== 设备检测与分工 =====================
def setup_devices():
    """检测 DirectML 可用性,返回 GPU 设备及 CPU 设备"""
    try:
        import torch_directml
        gpu = torch_directml.device()
        cpu = torch.device("cpu")
        print("[设备] 协同模式:GPU (DirectML) 主力 + CPU 兜底")
        return gpu, cpu
    except ImportError:
        gpu = torch.device("cuda" if torch.cuda.is_available() else "cpu")
        cpu = torch.device("cpu")
        print(f"[设备] DirectML 未安装,使用:{gpu}")
        return gpu, cpu

GPU_DEVICE, CPU_DEVICE = setup_devices()

# ===================== 手工 LSTM 算子(兼容 DirectML) =====================
class BasicLSTM(nn.Module):
    """
    纯基础算子实现的 LSTM。
    仅使用 nn.Linear、Sigmoid、Tanh 等 DirectML 原生支持的操作,
    无任何 AMP 不兼容问题。
    """
    def __init__(self, input_size: int, hidden_size: int, batch_first: bool = True):
        super().__init__()
        self.hidden_size = hidden_size
        self.batch_first = batch_first
        # 输入门、遗忘门、细胞候选、输出门 合并为一个线性变换
        self.W = nn.Linear(input_size + hidden_size, 4 * hidden_size)

    def forward(self, x: torch.Tensor, hidden: tuple = None):
        if self.batch_first:
            x = x.transpose(0, 1)                # (seq, batch, feat)
        seq_len, batch, _ = x.size()

        if hidden is None:
            h = torch.zeros(1, batch, self.hidden_size, device=x.device)
            c = torch.zeros(1, batch, self.hidden_size, device=x.device)
        else:
            h, c = hidden

        outputs = []
        for t in range(seq_len):
            combined = torch.cat([x[t], h[0]], dim=1)   # (batch, input_size+hidden)
            gates = self.W(combined)                    # (batch, 4*hidden)
            i, f, g, o = gates.chunk(4, 1)
            i = torch.sigmoid(i)
            f = torch.sigmoid(f)
            g = torch.tanh(g)
            o = torch.sigmoid(o)

            c = f * c + i * g
            h = o * torch.tanh(c)
            outputs.append(h.unsqueeze(1) if self.batch_first else h)

        if self.batch_first:
            outputs = torch.cat(outputs, dim=1)
        else:
            outputs = torch.stack(outputs)

        return outputs, (h, c)

# ===================== 文本生成模型 =====================
class LightTextGenerator(nn.Module):
    """
    轻量级八维报告生成器。
    输入八维状态向量,输出稳态评估文本。
    """
    def __init__(self, vocab_size: int, embed_dim: int, hidden_dim: int, max_seq_len: int = 256):
        super().__init__()
        self.embedding = nn.Embedding(vocab_size, embed_dim)
        self.pos_encoding = nn.Parameter(torch.zeros(1, max_seq_len, embed_dim))
        self.dim_proj = nn.Linear(8, embed_dim)           # 将8维状态映射到 embedding 空间
        self.lstm = BasicLSTM(embed_dim, hidden_dim, batch_first=True)
        self.output_proj = nn.Linear(hidden_dim, vocab_size)

    def forward(self, dims: torch.Tensor, tgt_ids: torch.Tensor):
        seq_len = tgt_ids.shape[1]
        emb = self.embedding(tgt_ids) + self.pos_encoding[:, :seq_len, :]
        mem = self.dim_proj(dims).unsqueeze(1)            # (batch, 1, embed_dim)
        out, _ = self.lstm(torch.cat([mem, emb], dim=1))
        return self.output_proj(out[:, 1:, :])            # 预测下一个 token

    @torch.no_grad()
    def generate(self, dims: torch.Tensor, vocab: dict, rev_vocab: dict,
                 max_len: int = 100, temperature: float = 0.7) -> str:
        """
        自回归生成报告文本,GPU 做前向,CPU 负责采样(避免 DirectML 采样算子缺失)。
        """
        self.eval()
        dims = dims.to(GPU_DEVICE)
        input_ids = torch.tensor([[vocab['<BOS>']]], dtype=torch.long, device=GPU_DEVICE)
        hidden = None
        generated_ids = []

        for _ in range(max_len):
            seq_len = input_ids.shape[1]
            emb = self.embedding(input_ids) + self.pos_encoding[:, :seq_len, :]
            mem = self.dim_proj(dims).unsqueeze(1)
            out, hidden = self.lstm(torch.cat([mem, emb], dim=1), hidden)
            logits = self.output_proj(out[:, -1, :])      # (batch, vocab_size)

            # ---- 采样回退到 CPU(DirectML 对 multinomial 支持有限)----
            logits_cpu = logits.to(CPU_DEVICE).squeeze()
            probs = F.softmax(logits_cpu / temperature, dim=-1)
            next_id_cpu = torch.multinomial(probs, num_samples=1)
            next_id = next_id_cpu.to(GPU_DEVICE).view(1, 1)

            if next_id.item() == vocab['<EOS>']:
                break
            generated_ids.append(next_id.item())
            input_ids = torch.cat([input_ids, next_id], dim=1)

        return "".join([rev_vocab.get(i, "") for i in generated_ids])

# ===================== 数据集构建 =====================
# 5 套专业报告模板(覆盖诊断、剖析、协同、收敛、进化等维度)
_REPORT_TEMPLATES = [
    "【全域诊断】系统当前处于稳态收敛期。X1={X1:.2f}表明认知耦合度良好,X2={X2:.2f}反映因果推理链完整。短板维度为X4={X4:.2f}(存储可靠性),建议启动增量备份任务。X6={X6:.2f}显示自进化活跃度适中。综合评分:M={m_val:.3f},系统健康。",
    "【八维剖析】认知耦合X1={X1:.2f},因果强度X2={X2:.2f},知识熵X3={X3:.2f},存储可靠性X4={X4:.2f},推理深度X5={X5:.2f},自进化活跃度X6={X6:.2f},资源效率X7={X7:.2f},时间稳定性X8={X8:.2f}。当前主要矛盾集中在X4维度,建议优先加固存储层。",
    "【协同评估】X1={X1:.2f}与X2={X2:.2f}形成正向耦合,但X5={X5:.2f}相对滞后。X8={X8:.2f}显示系统时间稳定性良好。建议增强推理模块递归深度。",
    "【收敛分析】X1-X2簇得分{X1:.2f}-{X2:.2f},与X5-X6簇得分{X5:.2f}-{X6:.2f}之间存在{coupling_gap:.2f}的梯度。X4={X4:.2f}处于临界状态。当前M={m_val:.3f},偏差{m_dev:.3f}。",
    "【进化轨迹】X1={X1:.2f}较上一周期变化{delta_x1:+.2f},X6={X6:.2f}维持高位。需关注X3={X3:.2f}上升趋势。X7={X7:.2f}表现优异。综合稳态M={m_val:.3f}。",
]

def build_dataset(num_samples: int = 2000):
    """用模板随机生成训练数据,每条样本附带随机八维得分"""
    samples = []
    for _ in range(num_samples):
        tpl = random.choice(_REPORT_TEMPLATES)
        dims = {f"X{i}": random.uniform(0.5, 0.98) for i in range(1, 9)}
        m_val = sum(dims.values()) / 8
        coupling_gap = abs(dims["X1"] - dims["X6"])
        m_dev = abs(m_val - 0.928)
        delta_x1 = random.uniform(-0.08, 0.08)

        report = tpl.format(
            X1=dims["X1"], X2=dims["X2"], X3=dims["X3"], X4=dims["X4"],
            X5=dims["X5"], X6=dims["X6"], X7=dims["X7"], X8=dims["X8"],
            m_val=m_val, coupling_gap=coupling_gap, m_dev=m_dev, delta_x1=delta_x1
        )
        samples.append({"dims": dims, "report": report})
    return samples

def build_vocab(samples: list) -> tuple[dict, dict]:
    """构建字符级词表"""
    text = "".join(s["report"] for s in samples)
    vocab = {'<PAD>': 0, '<UNK>': 1, '<BOS>': 2, '<EOS>': 3}
    for ch in sorted(set(text)):
        if ch not in vocab:
            vocab[ch] = len(vocab)
    rev_vocab = {v: k for k, v in vocab.items()}
    return vocab, rev_vocab

def text_to_ids(text: str, vocab: dict, max_len: int) -> torch.Tensor:
    """文本 → 固定长度 token id 序列"""
    ids = [vocab['<BOS>']] + [vocab.get(c, vocab['<UNK>']) for c in text] + [vocab['<EOS>']]
    if len(ids) < max_len:
        ids += [vocab['<PAD>']] * (max_len - len(ids))
    return torch.tensor(ids[:max_len], dtype=torch.long)

class ReportDataset(Dataset):
    """八维报告数据集"""
    def __init__(self, samples, vocab, max_len):
        self.samples = samples
        self.vocab = vocab
        self.max_len = max_len

    def __len__(self):
        return len(self.samples)

    def __getitem__(self, idx):
        s = self.samples[idx]
        dims = torch.tensor([s["dims"][f"X{i}"] for i in range(1, 9)], dtype=torch.float32)
        tgt = text_to_ids(s["report"], self.vocab, self.max_len)
        return dims, tgt

# ===================== 训练与生成主流程 =====================
def main():
    # 超参数
    BATCH_SIZE = 16
    EPOCHS = 5
    MAX_SEQ_LEN = 128
    EMBED_DIM = 64
    HIDDEN_DIM = 128

    print("=" * 60)
    print("DirectML 兼容文本生成器 - 工业协同版")
    print("=" * 60)

    # 1. 准备数据
    print("[1/4] 生成训练数据...")
    data = build_dataset(2000)
    vocab, rev_vocab = build_vocab(data)
    print(f"      词表大小: {len(vocab)}")

    # 2. 构建 DataLoader
    loader = DataLoader(
        ReportDataset(data, vocab, MAX_SEQ_LEN),
        batch_size=BATCH_SIZE,
        shuffle=True
    )

    # 3. 初始化模型
    print("[2/4] 初始化模型...")
    model = LightTextGenerator(len(vocab), EMBED_DIM, HIDDEN_DIM).to(GPU_DEVICE)
    optimizer = torch.optim.Adam(model.parameters(), lr=1e-3)
    loss_fn = nn.CrossEntropyLoss(ignore_index=vocab['<PAD>'])

    # 4. 训练
    print("[3/4] 开始训练 (FP32, 无 AMP)...")
    for epoch in range(EPOCHS):
        model.train()
        total_loss = 0.0
        for dims, tgt in loader:
            dims, tgt = dims.to(GPU_DEVICE), tgt.to(GPU_DEVICE)
            pred = model(dims, tgt[:, :-1])
            loss = loss_fn(pred.reshape(-1, len(vocab)), tgt[:, 1:].reshape(-1))
            optimizer.zero_grad()
            loss.backward()
            optimizer.step()
            total_loss += loss.item()
        print(f"  Epoch {epoch+1}/{EPOCHS} | Loss: {total_loss:.4f}")

    # 5. 测试生成
    print("[4/4] 生成示例报告...")
    test_dims = torch.tensor([[0.85, 0.86, 0.50, 0.88, 0.90, 0.92, 0.93, 0.94]], dtype=torch.float32)
    report = model.generate(test_dims, vocab, rev_vocab, temperature=0.7)
    print(f"生成结果:{report}")
    print("=" * 60)
    print("训练完成。该模型可用于老破小集成显卡,无 AMP 兼容性问题。")

if __name__ == "__main__":
    main()

---

## DirectML 兼容文本生成器(工业协同版)— 老破小专用

### 1. 硬件环境

* **操作系统**:Windows 10 / 11(64 位)
* **显卡**:任何支持 DirectX 12 的显卡(集成显卡、独立显卡均可),包括 Intel UHD、AMD Radeon、NVIDIA 等
* **内存**:建议 8 GB 以上(4 GB 也可运行,但需减少数据量)
* **纯 CPU 模式**:若完全没有独显,脚本会自动退化为 CPU 训练,无需额外配置

### 2. 软件依赖

* **Python**:3.12 版本(推荐 [Python 3.12.9](https://www.python.org/downloads/))
* **PyTorch**:2.5+(CPU 版或 CUDA 版均可)
* **DirectML 插件**:`torch-directml`

> 注意:`torch-directml` 目前最高支持 Python 3.12,请勿使用 3.13 或更高版本。

### 3. 安装步骤

```powershell
# 1. 创建虚拟环境(可选但推荐)
python -m venv dml_env
dml_env\Scripts\activate

# 2. 安装 PyTorch(CPU 版本即可,DirectML 会自动接管)
pip install torch torchvision torchaudio

# 3. 安装 DirectML 后端
pip install torch-directml
```

*如果你的 Python 版本过高导致 `torch-directml` 安装失败,请降级到 Python 3.12。*

### 4. 运行脚本

直接将以下代码保存为 `train_text_gen.py`,然后在项目目录下执行:

```powershell
python train_text_gen.py
```

脚本会自动:
1. 检测硬件(DirectML 可用则使用 GPU,否则回退 CPU)
2. 生成 2000 条八维诊断报告作为训练数据
3. 训练一个手工实现的 LSTM 文本生成器(纯 FP32 精度,无 AMP 兼容问题)
4. 最后输出一条由模型生成的示例报告

### 5. 输出示例

```
[设备] 协同模式:GPU (DirectML) 主力 + CPU 兜底
[1/4] 生成训练数据...
      词表大小: 112
[2/4] 初始化模型...
[3/4] 开始训练 (FP32, 无 AMP)...
  Epoch 1/5 | Loss: 560.2542
  Epoch 2/5 | Loss: 524.7070
  ...
[4/4] 生成示例报告...
生成结果:域。统。测统建。议统板。建缺统建。测建统。测统...
```

### 6. 工作原理简述

* **手工 LSTM**:完全使用 `Linear`、`Sigmoid`、`Tanh` 等 DirectML 原生支持的算子重写 LSTM,不调用任何 `nn.LSTM`,从而彻底规避 DirectML 对混合精度训练的支持缺失问题。
* **FP32 全精度训练**:关闭 `autocast`,强制在 DirectML 上运行全精度计算,即便集成显卡也能稳定运行。
* **GPU + CPU 协同生成**:推理时 GPU 负责前向传播,CPU 负责采样和后续处理,进一步避免 DirectML 对 `multinomial` 采样的不完整支持。

### 7. 注意事项

* 训练时的 `aten::lerp.Scalar_out` 警告是 Adam 优化器的一个步骤,DirectML 会自动交给 CPU 执行,**不影响训练结果和稳定性**。
* 若生成文本质量不理想,可增大 `EPOCHS` 和 `num_samples` 参数,或增加更丰富的训练模板。

Logo

Agent 垂直技术社区,欢迎活跃、内容共建。

更多推荐