【无线电控制与数据链探测系统】书籍配套源码-python 版本 第二章至第六章

「文智助手」全场景办公AI助理AI+OPC全流程实践方案(大赛作品) 阅读详情

第二章代码

#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""
Comprehensive executable Python examples for Chapter 2 (Sections 2.1 - 2.5)
- Target audience: graduate students
- Purpose: runnable, in-depth code samples (no theoretical exposition; comments only)

Dependencies:
  numpy, scipy, matplotlib
  install via: pip install numpy scipy matplotlib

Usage:
  python Chapter2_Sections_2.1-2.5_full_python_examples.py --all
  or choose sections: --s2_1 --s2_2 --s2_3 --s2_4 --s2_5

Each section exposes functions that can be imported individually.
"""

import argparse
import numpy as np
import matplotlib.pyplot as plt
from scipy.constants import c, k as k_B
from scipy.special import erfc
from scipy.signal import fftconvolve

# =========================
# 2.1 Frequency & Spectrum Utilities
# =========================

# Simple regulatory table (example, not exhaustive)
REG_TABLE = [
    # (name, f_min_Hz, f_max_Hz, category)
    ("HF", 3e6, 30e6, "licensed/managed"),
    ("VHF", 30e6, 300e6, "licensed/managed"),
    ("UHF", 300e6, 3e9, "licensed/managed"),
    ("ISM_2.4GHz", 2.400e9, 2.4835e9, "unlicensed"),
    ("ISM_5GHz", 5.150e9, 5.875e9, "unlicensed"),
    ("Cellular_low", 600e6, 960e6, "licensed"),
]


def classify_frequency(freq_hz):
    """Return matching band(s) and category for frequency or frequency array."""
    f = np.atleast_1d(freq_hz)
    out = []
    for fi in f:
        matches = [r for r in REG_TABLE if r[1] <= fi <= r[2]]
        out.append(matches if matches else None)
    return out if out.__len__() > 1 else out[0]


def simulate_spectrum_occupancy(center_hz=2.442e9, bw_hz=20e6, n_channels=20, seed=1):
    """Create a toy spectrum occupancy array for visualization (power spectral density)."""
    rng = np.random.default_rng(seed)
    freqs = np.linspace(center_hz - bw_hz, center_hz + bw_hz, n_channels*50)
    # baseline noise
    psd = np.ones_like(freqs) * (-120)  # dBm/Hz baseline
    # add some random active transmissions
    for _ in range(rng.integers(1, 6)):
        center = rng.uniform(freqs.min(), freqs.max())
        width = rng.uniform(bw_hz*0.01, bw_hz*0.2)
        amp = rng.uniform(-30, -60)
        psd += amp * np.exp(-0.5 * ((freqs - center)/ (width/2))**2)
    return freqs, psd

# =========================
# 2.2 Link Budget, Noise, SNR, BER
# =========================


def fspl_db(f_hz, d_m):
    """Free-space path loss in dB for frequency f_hz and distance d_m.
    Vectorized over d_m (can be scalar or array).
    """
    lambda_m = c / f_hz
    # FSPL (linear) = (4*pi*d / lambda)^2 -> dB form
    with np.errstate(divide='ignore'):
        pl_db = 20 * np.log10(4 * np.pi * d_m / lambda_m)
    return pl_db


def noise_floor_dbm(b_hz, t_k=290.0, nf_db=0.0):
    """Thermal noise floor (dBm) for bandwidth b_hz, temperature t_k and noise figure nf_db."""
    n_w = k_B * t_k * b_hz * (10**(nf_db/10))
    n_dbm = 10 * np.log10(n_w) + 30
    return n_dbm


def link_budget(Pt_dbm, Gt_dbi, Gr_dbi, f_hz, d_m, Ls_dB=0.0, noise_fig_dB=3.0, bandwidth_hz=1e6):
    """Compute Pr (dBm), SNR (linear and dB), and BER for BPSK.
    Returns dict with arrays matching d_m shape.
    """
    pl_db = fspl_db(f_hz, d_m)
    Pr_dbm = Pt_dbm + Gt_dbi + Gr_dbi - pl_db - Ls_dB
    nf_db = noise_fig_dB
    noise_dbm = noise_floor_dbm(bandwidth_hz, t_k=290.0, nf_db=nf_db)
    snr_db = Pr_dbm - noise_dbm
    snr_lin = 10**(snr_db/10)
    ber_bpsk = 0.5 * erfc(np.sqrt(snr_lin))
    return {
        'Pr_dbm': Pr_dbm,
        'SNR_db': snr_db,
        'SNR_lin': snr_lin,
        'BER_BPSK': ber_bpsk,
        'PL_db': pl_db,
        'Noise_dbm': noise_dbm
    }

# Additional useful helper: compute max distance for required sensitivity

def max_distance_for_sensitivity(Pt_dbm, Gt_dbi, Gr_dbi, f_hz, sensitivity_dbm, Ls_dB=0.0):
    """Solve for distance where received power equals sensitivity (dBm) using FSPL inversion.
    Works for scalar sensitivity. Returns distance in meters.
    """
    lambda_m = c / f_hz
    # Pr = Pt + Gt + Gr - 20*log10(4*pi*d / lambda) - Ls
    # rearrange for d
    exp = (Pt_dbm + Gt_dbi + Gr_dbi - Ls_dB - sensitivity_dbm) / 20.0
    d = (lambda_m / (4 * np.pi)) * (10**exp)
    return d

# =========================
# 2.3 Modulation, OFDM, FHSS, DSSS
# =========================

# Basic modulators/demodulators (waveform-level)

def bpsk_mod(bits, fc, fs, baud):
    """BPSK modulator producing real passband waveform.
    bits: {0,1} array
    fc: carrier freq
    fs: sampling freq
    baud: symbol rate
    """
    bits = np.asarray(bits).astype(int)
    sps = int(fs / baud)
    # map 0->-1, 1->+1
    symbols = 2*bits - 1
    # upsample
    base = np.repeat(symbols, sps)
    t = np.arange(base.size) / fs
    carrier = np.cos(2*np.pi*fc*t)
    return base * carrier, fs


def add_awgn(signal, snr_db, seed=None):
    rng = np.random.default_rng(seed)
    sig_pow = np.mean(np.abs(signal)**2)
    snr_lin = 10**(snr_db/10)
    noise_pow = sig_pow / snr_lin
    noise = np.sqrt(noise_pow/2) * rng.normal(size=signal.shape)
    return signal + noise

# QAM mapper/demapper

def qam16_map(bits):
    # expects length multiple of 4
    bits = np.asarray(bits).reshape(-1,4)
    # Gray map 16-QAM, normalised
    mapping = {
        (0,0,0,0): -3-3j,(0,0,0,1): -3-1j,(0,0,1,1): -3+1j,(0,0,1,0): -3+3j,
        (0,1,0,0): -1-3j,(0,1,0,1): -1-1j,(0,1,1,1): -1+1j,(0,1,1,0): -1+3j,
        (1,1,0,0):  1-3j,(1,1,0,1):  1-1j,(1,1,1,1): 1+1j,(1,1,1,0):  1+3j,
        (1,0,0,0):  3-3j,(1,0,0,1):  3-1j,(1,0,1,1): 3+1j,(1,0,1,0):  3+3j
    }
    symbols = np.array([mapping[tuple(b.tolist())] for b in bits])
    symbols = symbols / np.sqrt((np.abs(symbols)**2).mean())
    return symbols


def qam16_demod(symbols):
    # simple nearest-neighbor demap using same constellation
    # reconstruct constellation
    pts = np.array([-3-3j,-3-1j,-3+1j,-3+3j,-1-3j,-1-1j,-1+1j,-1+3j,
                    1-3j,1-1j,1+1j,1+3j,3-3j,3-1j,3+1j,3+3j], dtype=complex)
    bits_map = [(0,0,0,0),(0,0,0,1),(0,0,1,1),(0,0,1,0),(0,1,0,0),(0,1,0,1),(0,1,1,1),(0,1,1,0),
                (1,1,0,0),(1,1,0,1),(1,1,1,1),(1,1,1,0),(1,0,0,0),(1,0,0,1),(1,0,1,1),(1,0,1,0)]
    pts = pts / np.sqrt((np.abs(pts)**2).mean())
    out_bits = []
    for s in symbols:
        idx = np.argmin(np.abs(s - pts))
        out_bits.extend(bits_map[idx])
    return np.array(out_bits)

# OFDM transmitter/receiver (simple CP-based)

def ofdm_tx(bits, n_subcarriers=64, cp_len=16, qam_order=16):
    """Return complex baseband time-domain OFDM waveform and params.
    bits length must be multiple of log2(qam_order)*n_subcarriers.
    """
    k = int(np.log2(qam_order))
    assert len(bits) % (k * n_subcarriers) == 0, "bits length must fit OFDM frames"
    symbols = qam16_map(bits) if qam_order==16 else None
    # group into OFDM symbols
    ofdm_syms = symbols.reshape(-1, n_subcarriers)
    tx_time = []
    for sym in ofdm_syms:
        freq_domain = sym
        time_domain = np.fft.ifft(freq_domain)
        cp = time_domain[-cp_len:]
        tx_time.extend(np.concatenate([cp, time_domain]))
    tx = np.array(tx_time)
    return tx, {'n_subcarriers':n_subcarriers, 'cp_len':cp_len, 'q_order':qam_order}


def ofdm_rx(rx, params):
    n = params['n_subcarriers']; cp = params['cp_len']; q= params['q_order']
    sym_len = n + cp
    nsym = len(rx) // sym_len
    rx = rx[:nsym*sym_len]
    rx_mat = rx.reshape(nsym, sym_len)
    out_syms = []
    for r in rx_mat:
        td = r[cp:]
        fd = np.fft.fft(td)
        out_syms.append(fd)
    out = np.concatenate(out_syms)
    # demap
    bits = qam16_demod(out) if q==16 else None
    return bits

# FHSS simple simulator

def fhss_transmit(bits, carrier_list, hop_rate, fs, symbol_rate, seed=0):
    rng = np.random.default_rng(seed)
    sps = int(fs / symbol_rate)
    # simple BPSK baseband
    symbols = 2*np.array(bits) - 1
    base = np.repeat(symbols, sps)
    # hop pattern
    n_hops = int(np.ceil(len(base) / (sps*hop_rate)))
    hops = rng.choice(carrier_list, size=n_hops, replace=True)
    t = np.arange(len(base))/fs
    tx = np.zeros_like(base, dtype=float)
    idx=0
    for hi,hf in enumerate(hops):
        start = hi * hop_rate * sps
        end = min(len(base), start + hop_rate*sps)
        if start>=end: break
        carrier = np.cos(2*np.pi*hf*t[start:end])
        tx[start:end] = base[start:end] * carrier
    return tx

# DSSS simple: spread with m-sequence

def pn_sequence(length, seed=1):
    rng = np.random.default_rng(seed)
    return rng.choice([1,-1], size=length)


def dsss_tx(bits, pn, sps=8):
    # bits: 0/1 -> map 0->-1,1->1 ; spread
    chips = np.repeat(2*np.array(bits)-1, len(pn)) * np.tile(pn, len(bits))
    # upsample
    return np.repeat(chips, sps)


def dsss_rx(rx, pn, sps=8):
    # simple correlator
    chips = rx.reshape(-1, sps)
    chips = chips.mean(axis=1)  # collapse sps
    # regroup by pn length
    n_symbols = len(chips) // len(pn)
    chips = chips[:n_symbols*len(pn)].reshape(n_symbols, len(pn))
    out = np.sign(np.dot(chips, pn))
    bits = ((out + 1) // 2).astype(int)
    return bits

# =========================
# 2.4 Data Link: framing, CRC, ARQ, handshake
# =========================

# CRC-16-CCITT

def crc16_ccitt(data: bytes, poly=0x1021, init=0xFFFF):
    crc = init
    for b in data:
        crc ^= b << 8
        for _ in range(8):
            if crc & 0x8000:
                crc = ((crc << 1) ^ poly) & 0xFFFF
            else:
                crc = (crc << 1) & 0xFFFF
    return crc


def make_frame(payload: bytes, seq: int, ctrl: int=0):
    # simple frame: [SOF(0x7E)] [CTRL(1)] [SEQ(1)] [LEN(2)] [PAYLOAD] [CRC(2)] [EOF(0x7E)]
    sof = bytes([0x7E])
    eof = bytes([0x7E])
    ctrl_b = bytes([ctrl & 0xFF])
    seq_b = bytes([seq & 0xFF])
    length = len(payload).to_bytes(2,'big')
    core = ctrl_b + seq_b + length + payload
    crc = crc16_ccitt(core).to_bytes(2,'big')
    return sof + core + crc + eof


def parse_frame(frame: bytes):
    if frame[0]!=0x7E or frame[-1]!=0x7E:
        raise ValueError('Frame markers missing')
    core = frame[1:-3]
    crc_received = int.from_bytes(frame[-3:-1],'big')
    crc_calc = crc16_ccitt(core)
    if crc_received != crc_calc:
        raise ValueError('CRC mismatch')
    ctrl = core[0]
    seq = core[1]
    length = int.from_bytes(core[2:4],'big')
    payload = core[4:4+length]
    return {'ctrl':ctrl, 'seq':seq, 'payload':payload}

# Stop-and-wait ARQ simulator

def stop_and_wait_simulate(tx_payloads, p_loss=0.1, max_retries=5, seed=0):
    rng = np.random.default_rng(seed)
    acked = []
    events = []
    for seq,payload in enumerate(tx_payloads):
        retries = 0
        while retries<=max_retries:
            # frame transmitted
            frame = make_frame(payload, seq)
            lost = rng.uniform() < p_loss
            events.append(('tx', seq, retries, lost))
            if lost:
                retries += 1
                continue
            # receiver sends ACK (simulate possible loss)
            ack_lost = rng.uniform() < p_loss
            events.append(('ack', seq, retries, ack_lost))
            if ack_lost:
                retries += 1
                continue
            # success
            acked.append(seq)
            break
        if retries>max_retries:
            events.append(('fail', seq, retries, True))
    return acked, events

# =========================
# 2.5 Antenna & Front-End Utilities
# =========================


def dBi_to_linear(dbi):
    return 10**(dbi/10)


def linear_to_dBi(g):
    return 10*np.log10(g)

# Simple idealised isotropic pattern modifier: create pattern for given dBi and beamwidth

def antenna_pattern(dbi, beamwidth_deg=60, theta_res=361):
    # create a 2D-ish azimuth pattern (not full electromagnetic detail)
    theta = np.linspace(0, 360, theta_res)
    bw = beamwidth_deg
    # main-lobe modelled as raised cosine around 0 deg
    main = np.maximum(0, np.cos(np.deg2rad(theta)/ (bw/180.0) * np.pi/2))
    norm = main.max()
    main = main / (norm + 1e-12)
    # convert amplitude shape to dBi scaled to peak
    peak_lin = dBi_to_linear(dbi)
    pattern_db = linear_to_dBi(main * peak_lin + 1e-12)
    return theta, pattern_db

# Polarization loss (simplified): mismatch between two linear polarizations (angles in deg)

def polarization_loss_db(tx_angle_deg, rx_angle_deg):
    # loss = -20*log10(|cos(delta)|)
    delta = np.deg2rad(tx_angle_deg - rx_angle_deg)
    loss = -20*np.log10(np.abs(np.cos(delta)) + 1e-12)
    return loss

# Impedance match calculations: VSWR and return loss

def return_loss_db(z_load, z0=50.0):
    # reflection coeff
    gamma = (z_load - z0) / (z_load + z0)
    rl = -20*np.log10(np.abs(gamma) + 1e-12)
    return rl


def vswr(z_load, z0=50.0):
    gamma = np.abs((z_load - z0) / (z_load + z0))
    return (1 + gamma) / (1 - gamma)

# Simple LNA model (gain, noise figure, 1dB compression)
class LNA:
    def __init__(self, gain_dB=20.0, nf_dB=1.0, p1dB_dBm=-5.0):
        self.gain_dB = gain_dB
        self.nf_dB = nf_dB
        self.p1dB_dBm = p1dB_dBm
    def amplify_dbm(self, pin_dbm):
        # simulate compression: if pin near p1dB, compress
        # simple soft knee: above p1dB-10dB, apply slight compression
        delta = pin_dbm - (self.p1dB_dBm - 10)
        if delta <= 0:
            pout = pin_dbm + self.gain_dB
        else:
            # compress slope
            slope = max(0.2, 1.0 - delta/50.0)
            pout = pin_dbm + self.gain_dB * slope
        return pout

# =========================
# Demo / CLI
# =========================

def demo_2_1():
    print('=== Demo 2.1: Frequency classification & spectrum occupancy ===')
    freqs = [2.45e9, 900e6, 28e6]
    for f in freqs:
        print(f'{f/1e6:.2f} MHz ->', classify_frequency(f))
    ff, psd = simulate_spectrum_occupancy()
    plt.figure(figsize=(8,3))
    plt.plot((ff-ff.mean())/1e6, psd)
    plt.xlabel('Offset from center (MHz)')
    plt.ylabel('PSD (dBm/Hz)')
    plt.title('Toy spectrum occupancy')
    plt.grid(True)
    plt.tight_layout()
    plt.show()


def demo_2_2():
    print('=== Demo 2.2: Link budget sweep ===')
    Pt = 30.0; Gt=15.0; Gr=15.0; f=2.4e9; Ls=2.0
    dists = np.logspace(1,4,300)
    res = link_budget(Pt, Gt, Gr, f, dists, Ls_dB=Ls, noise_fig_dB=5.0, bandwidth_hz=1e6)
    plt.figure(figsize=(8,6))
    plt.subplot(2,1,1)
    plt.semilogx(dists, res['Pr_dbm'])
    plt.grid(True); plt.ylabel('Pr (dBm)')
    plt.subplot(2,1,2)
    plt.semilogx(dists, res['SNR_db'])
    plt.grid(True); plt.ylabel('SNR (dB)'); plt.xlabel('Distance (m)')
    plt.tight_layout(); plt.show()
    # find distance where BER > 1e-3
    idx = np.where(res['BER_BPSK']>1e-3)[0]
    if idx.size>0:
        print('BER exceed 1e-3 beyond approx distance (m):', dists[idx[0]])
    else:
        print('BER acceptable in range')


def demo_2_3():
    print('=== Demo 2.3: OFDM + FHSS + DSSS mini-simulations ===')
    # OFDM quick round-trip
    rng = np.random.default_rng(0)
    bits = rng.integers(0,2,size=64*4)
    tx, params = ofdm_tx(bits, n_subcarriers=64, cp_len=16, qam_order=16)
    # AWGN
    rx = add_awgn(tx, snr_db=20, seed=1)
    rx_bits = ofdm_rx(rx, params)
    ber = np.mean(bits != rx_bits)
    print('OFDM BER (AWGN 20 dB):', ber)

    # FHSS example
    bits_bpsk = rng.integers(0,2,size=256)
    carrier_list = [2.412e9, 2.437e9, 2.462e9]
    tx_fh = fhss_transmit(bits_bpsk, carrier_list, hop_rate=4, fs=1e6, symbol_rate=1e3)
    print('FHSS tx length samples:', len(tx_fh))

    # DSSS example
    pn = pn_sequence(16, seed=2)
    tx_dsss = dsss_tx(bits_bpsk[:32], pn, sps=4)
    # channel: add narrowband interferer in middle
    t = np.arange(len(tx_dsss))/1e4
    interferer = 0.5*np.sin(2*np.pi*500*t)
    rx_dsss = tx_dsss + interferer
    rx_bits = dsss_rx(rx_dsss, pn, sps=4)
    print('DSSS recovered symbols:', rx_bits.shape[0])


def demo_2_4():
    print('=== Demo 2.4: Framing, CRC and Stop-and-Wait ARQ simulation ===')
    payloads = [b'hello', b'world', b'this', b'is', b'test']
    acked, events = stop_and_wait_simulate(payloads, p_loss=0.2, seed=42)
    print('Acked sequences:', acked)
    print('Event sample (first 8):', events[:8])


def demo_2_5():
    print('=== Demo 2.5: Antenna patterns, polarization, LNA behavior ===')
    theta, pat = antenna_pattern(12.0, beamwidth_deg=45, theta_res=361)
    plt.figure(figsize=(6,3))
    plt.plot(theta, pat)
    plt.xlabel('Azimuth (deg)'); plt.ylabel('Gain (dBi)'); plt.title('Antenna pattern (toy)')
    plt.grid(True); plt.tight_layout(); plt.show()
    print('Polarization loss (0 vs 45 deg):', polarization_loss_db(0,45))
    print('Return loss for Z=30 Ohm:', return_loss_db(30.0))
    lna = LNA(gain_dB=20, nf_dB=1, p1dB_dBm=-2)
    print('LNA amplify -40 dBm ->', lna.amplify_dbm(-40), 'dBm')
    print('LNA amplify -5 dBm ->', lna.amplify_dbm(-5), 'dBm')


def main():
    parser = argparse.ArgumentParser()
    parser.add_argument('--all', action='store_true')
    parser.add_argument('--s2_1', action='store_true')
    parser.add_argument('--s2_2', action='store_true')
    parser.add_argument('--s2_3', action='store_true')
    parser.add_argument('--s2_4', action='store_true')
    parser.add_argument('--s2_5', action='store_true')
    args = parser.parse_args()
    if args.all or not any([args.s2_1,args.s2_2,args.s2_3,args.s2_4,args.s2_5]):
        demo_2_1(); demo_2_2(); demo_2_3(); demo_2_4(); demo_2_5()
    else:
        if args.s2_1: demo_2_1()
        if args.s2_2: demo_2_2()
        if args.s2_3: demo_2_3()
        if args.s2_4: demo_2_4()
        if args.s2_5: demo_2_5()

if __name__=='__main__':
    main()

第三章代码

#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""
Executable Python examples for Chapter 3: Classic Signal Detection Methods & Statistical Foundations
Sections: 3.1-3.5
- Target: graduate students
- Purely implementation code with detailed comments
- Dependencies: numpy, scipy, matplotlib, sklearn
"""

import numpy as np
import matplotlib.pyplot as plt
from scipy.stats import chi2, norm
from scipy.signal import correlate
from sklearn.metrics import roc_curve, auc

# =========================
# 3.1 Hypothesis testing & Neyman-Pearson
# =========================

def np_test_statistic(y, sigma2=1.0, mu0=0.0, mu1=1.0):
    """Likelihood ratio test statistic for Gaussian signal detection."""
    # L(y) = f(y|H1)/f(y|H0)
    return np.exp(-(y-mu1)**2/(2*sigma2)) / np.exp(-(y-mu0)**2/(2*sigma2))

def np_threshold(alpha, sigma2=1.0, mu0=0.0, mu1=1.0):
    # Solve for threshold from false alarm probability alpha
    # Under H0, y ~ N(mu0, sigma2)
    # threshold = (sigma2/2) * log((1-alpha)/alpha) + (mu0+mu1)/2 ?? simplified
    return mu0 + np.sqrt(sigma2) * norm.ppf(1-alpha)

# =========================
# 3.2 Energy detection & matched filter
# =========================

def energy_detector(y, threshold=None):
    """Energy detector: y can be vector or 2D (samples x trials)."""
    e = np.sum(np.abs(y)**2, axis=0)
    if threshold is not None:
        return e > threshold
    return e


def matched_filter(y, s, sigma2=1.0, threshold=None):
    """Matched filter: correlate received y with known template s"""
    mf = np.real(np.dot(s.conj(), y)) / sigma2
    if threshold is not None:
        return mf > threshold
    return mf

# Compute threshold for given Pfa

def threshold_energy(N, sigma2, Pfa):
    # chi-squared distribution with 2N DOF (real+imag)
    return sigma2 * chi2.ppf(1-Pfa, 2*N)

def threshold_matched_filter(sigma2, Pfa, s_norm2):
    # MF output under H0 ~ N(0, s_norm2*sigma2)
    return norm.ppf(1-Pfa) * np.sqrt(s_norm2*sigma2)

# =========================
# 3.3 Cyclostationarity detection (pseudo code / example)
# =========================

def cyclic_autocorr(x, alpha, maxlag=10):
    """Compute cyclic autocorrelation for cycle frequency alpha"""
    N = len(x)
    R_alpha = np.zeros(2*maxlag+1, dtype=complex)
    lags = np.arange(-maxlag, maxlag+1)
    for idx, tau in enumerate(lags):
        # cyclic autocorrelation: R_x(alpha, tau) = E[x[n]*x*[n-tau]*exp(-j2pi alpha n)]
        n_idx = np.arange(max(0, tau), min(N, N+tau))
        R_alpha[idx] = np.mean(x[n_idx] * np.conj(x[n_idx - tau]) * np.exp(-1j*2*np.pi*alpha*n_idx/N))
    return lags, R_alpha

# =========================
# 3.4 Multi-antenna covariance detection
# =========================

def sample_covariance_matrix(X):
    """X: samples x antennas"""
    return np.cov(X, rowvar=False, bias=True)

def eigenvalue_detection(X, threshold=None):
    """Use maximum eigenvalue as test statistic"""
    R = sample_covariance_matrix(X)
    eigvals = np.linalg.eigvalsh(R)
    T = np.max(eigvals)
    if threshold is not None:
        return T > threshold
    return T

# =========================
# 3.5 Performance evaluation: ROC, AUC, min detectable SNR
# =========================

def simulate_detection(detector_func, H0_samples, H1_samples, **kwargs):
    """Compute detection outputs for H0 and H1 samples"""
    y0 = np.array([detector_func(x, **kwargs) for x in H0_samples])
    y1 = np.array([detector_func(x, **kwargs) for x in H1_samples])
    labels = np.concatenate([np.zeros_like(y0), np.ones_like(y1)])
    scores = np.concatenate([y0, y1])
    fpr, tpr, _ = roc_curve(labels, scores)
    roc_auc = auc(fpr, tpr)
    return fpr, tpr, roc_auc

# Minimum detectable SNR for target Pd

def min_detectable_snr_energy(N, sigma2, Pfa, Pd_target):
    # threshold from Pfa
    th = threshold_energy(N, sigma2, Pfa)
    # Solve SNR such that P_D = Pd_target under non-central chi-squared
    # Approximation: use Gaussian approx
    mu_H1 = N*sigma2*(1 + 1.0) # assuming unit signal power
    var_H1 = N*sigma2**2*2
    # solve for snr: Pd = Q((th - mu_H1)/sqrt(var_H1))
    z = norm.ppf(Pd_target)
    snr = ( (th - N*sigma2) / np.sqrt(2*N*sigma2**2) - z )**2
    return snr

# =========================
# Demo for all sections
# =========================

def demo_3_1():
    print('=== Demo 3.1: Neyman-Pearson simple test ===')
    y = np.random.normal(0,1,1000)
    alpha = 0.05
    th = np.quantile(y, 1-alpha)
    decisions = y>th
    print('Decisions count (H1 detections):', np.sum(decisions))


def demo_3_2():
    print('=== Demo 3.2: Energy & matched filter detection ===')
    N = 64
    sigma2 = 1.0
    Pfa = 0.01
    # H0 and H1 signals
    H0 = np.random.normal(0, np.sqrt(sigma2), (N,1000))
    s = np.ones(N)
    H1 = H0 + s[:,None]
    th_energy = threshold_energy(N, sigma2, Pfa)
    det_H0 = energy_detector(H0, threshold=th_energy)
    det_H1 = energy_detector(H1, threshold=th_energy)
    print('Energy detector - H0 false alarms:', np.sum(det_H0)/det_H0.size)
    print('Energy detector - H1 detections:', np.sum(det_H1)/det_H1.size)


def demo_3_3():
    print('=== Demo 3.3: Cyclic autocorrelation ===')
    x = np.sin(2*np.pi*5*np.arange(100)/100) + np.random.randn(100)*0.1
    lags, R_alpha = cyclic_autocorr(x, alpha=5/100, maxlag=10)
    plt.figure()
    plt.stem(lags, np.abs(R_alpha))
    plt.xlabel('Lag'); plt.ylabel('|R_alpha|'); plt.title('Cyclic autocorrelation')
    plt.grid(True); plt.show()


def demo_3_4():
    print('=== Demo 3.4: Multi-antenna eigenvalue detection ===')
    X = np.random.randn(100,4) # 100 samples, 4 antennas
    stat = eigenvalue_detection(X)
    print('Max eigenvalue test statistic:', stat)


def demo_3_5():
    print('=== Demo 3.5: ROC and min detectable SNR ===')
    N = 32; sigma2=1.0; Pfa=0.05
    H0_samples = [np.random.randn(N) for _ in range(500)]
    H1_samples = [np.random.randn(N)+1.0 for _ in range(500)]
    fpr, tpr, roc_auc = simulate_detection(energy_detector, H0_samples, H1_samples)
    print('ROC AUC:', roc_auc)
    plt.figure()
    plt.plot(fpr, tpr, lw=2); plt.xlabel('FPR'); plt.ylabel('TPR'); plt.title('ROC Curve'); plt.grid(True)
    plt.show()
    # Minimum detectable SNR (example)
    snr_min = min_detectable_snr_energy(N, sigma2, Pfa, Pd_target=0.9)
    print('Approx. minimum detectable SNR (energy detector):', snr_min)

# =========================
# Main CLI
# =========================

def main():
    demo_3_1()
    demo_3_2()
    demo_3_3()
    demo_3_4()
    demo_3_5()

if __name__=='__main__':
    main()

第四章代码

#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""
Enhanced Python examples for Chapter 4: Spectrum Sensing & Wideband Detection (Sections 4.1-4.5)
- Graduate-level detailed implementation
- Full visualizations, iterative fusion, performance statistics
- Dependencies: numpy, scipy, matplotlib, cvxpy
"""

import numpy as np
import matplotlib.pyplot as plt
from scipy.signal import stft, get_window
import cvxpy as cp

# =========================
# 4.1 Narrowband & Wideband detection with adaptive thresholding
# =========================

def narrowband_detector(y, threshold=None):
    noise_power = np.var(y)
    energy = np.sum(np.abs(y)**2)
    if threshold is None:
        threshold = noise_power * len(y) * 1.5  # simple adaptive factor
    return energy > threshold, energy, threshold

def wideband_detector(y, band_indices, thresholds=None):
    results, energies = [], []
    if thresholds is None:
        thresholds = [np.var(y[start:end])*len(y[start:end])*1.5 for start,end in band_indices]
    for i, (start, end) in enumerate(band_indices):
        band = y[start:end]
        e = np.sum(np.abs(band)**2)
        energies.append(e)
        results.append(e > thresholds[i])
    return results, energies, thresholds

# =========================
# 4.2 FFT / STFT detection and window comparison
# =========================

def stft_analysis(y, fs=1e3, nperseg=128, windows=['hann','hamming','blackman']):
    plt.figure(figsize=(12,8))
    for i, w in enumerate(windows):
        f,t,Zxx = stft(y, fs=fs, nperseg=nperseg, window=w)
        E = np.abs(Zxx)**2
        plt.subplot(len(windows),1,i+1)
        plt.pcolormesh(t,f,10*np.log10(E+1e-12))
        plt.title(f'STFT Energy Spectrogram ({w} window)')
        plt.xlabel('Time'); plt.ylabel('Frequency'); plt.colorbar(label='dB')
    plt.tight_layout()
    plt.show()

# =========================
# 4.3 Compressive sensing & sparse reconstruction
# =========================

def cs_reconstruction_l1(Phi, y):
    N = Phi.shape[1]
    x = cp.Variable(N)
    obj = cp.Minimize(cp.norm1(x))
    constraints = [Phi @ x == y]
    prob = cp.Problem(obj, constraints)
    prob.solve(solver=cp.SCS)
    return x.value

def cs_reconstruction_omp(Phi, y, k):
    # Simple Orthogonal Matching Pursuit implementation
    M, N = Phi.shape
    residual = y.copy()
    idx_selected = []
    x_hat = np.zeros(N)
    for _ in range(k):
        correlations = np.abs(Phi.T @ residual)
        idx = np.argmax(correlations)
        idx_selected.append(idx)
        Phi_selected = Phi[:, idx_selected]
        x_ls = np.linalg.lstsq(Phi_selected, y, rcond=None)[0]
        residual = y - Phi_selected @ x_ls
    x_hat[idx_selected] = x_ls
    return x_hat

def generate_sparse_spectrum(N=1024, k=10, seed=None):
    rng = np.random.default_rng(seed)
    x = np.zeros(N)
    idx = rng.choice(N, k, replace=False)
    x[idx] = rng.normal(0,1,k)
    return x

# =========================
# 4.4 Cooperative sensing with iterative fusion
# =========================

def cooperative_fusion_iterative(results_matrix, iterations=3, method='majority'):
    fused = np.copy(results_matrix[0])
    history = [fused.copy()]
    for _ in range(iterations):
        if method=='majority':
            fused = np.mean(np.vstack([fused, results_matrix]), axis=0) >= 0.5
        elif method=='weighted':
            weights = np.linspace(0.5,1.0,results_matrix.shape[0])[:,None]
            fused = np.sum(np.vstack([fused, results_matrix])*weights, axis=0) > (0.5*weights.sum())
        elif method=='bayesian':
            probs = np.mean(np.vstack([fused, results_matrix]), axis=0)
            fused = probs > 0.5
        history.append(fused.copy())
    return fused, history

# =========================
# 4.5 Real-time system analysis: latency & energy
# =========================

def processing_load(samples_per_frame, fft_size, sensors, fs):
    ops_per_fft = fft_size * np.log2(fft_size)
    frames_per_sec = fs / samples_per_frame
    total_ops = ops_per_fft * frames_per_sec * sensors
    return total_ops

def energy_latency_tradeoff(frame_sizes, fft_size=128, sensors=4, fs=1e3, energy_per_op=1e-9):
    latencies = frame_sizes/fs
    loads = np.array([processing_load(f, fft_size, sensors, fs) for f in frame_sizes])
    energy = loads * energy_per_op
    return latencies, energy

# =========================
# Demonstrations
# =========================

def demo_4_1_4_2():
    print('=== Demo 4.1-4.2 ===')
    y = np.random.randn(1024)
    nb_res, nb_e, th_nb = narrowband_detector(y[:128])
    print('Narrowband detection:', nb_res, 'Threshold:', th_nb)
    bands = [(0,128),(128,256),(256,512)]
    wb_res, wb_e, th_wb = wideband_detector(y, bands)
    print('Wideband detection results:', wb_res, 'Thresholds:', th_wb)
    stft_analysis(y, nperseg=64, windows=['hann','hamming','blackman'])

def demo_4_3():
    print('=== Demo 4.3 Compressive Sensing ===')
    N, k, M = 512, 8, 128
    x_true = generate_sparse_spectrum(N=N, k=k)
    Phi = np.random.normal(0,1,(M,N))
    y = Phi @ x_true
    x_l1 = cs_reconstruction_l1(Phi, y)
    x_omp = cs_reconstruction_omp(Phi, y, k)
    plt.figure()
    plt.plot(x_true,'o',label='Original')
    plt.plot(x_l1,'x',label='L1 reconstruction')
    plt.plot(x_omp,'+',label='OMP reconstruction')
    plt.legend(); plt.show()

def demo_4_4():
    print('=== Demo 4.4 Cooperative Fusion ===')
    sensors, bands = 5, 3
    results_matrix = np.random.randint(0,2,(sensors,bands))
    fused, history = cooperative_fusion_iterative(results_matrix, iterations=5, method='majority')
    print('Results matrix:\n', results_matrix)
    print('Final fused result:', fused)
    for i, h in enumerate(history):
        print(f'Iteration {i}: {h}')

def demo_4_5():
    print('=== Demo 4.5 Real-time System Analysis ===')
    frame_sizes = np.array([64,128,256,512])
    lat, energy = energy_latency_tradeoff(frame_sizes)
    plt.figure()
    plt.plot(lat*1e3, energy*1e3,'-o')
    plt.xlabel('Latency (ms)'); plt.ylabel('Energy (mJ)'); plt.title('Energy vs Latency Tradeoff')
    plt.grid(True); plt.show()

# =========================
# Main
# =========================

def main():
    demo_4_1_4_2()
    demo_4_3()
    demo_4_4()
    demo_4_5()

if __name__=='__main__':
    main()

第五章代码

#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""
signal_suite.py
A single-file, runnable toolkit that demonstrates a complete pipeline for
Signal Classification & Protocol Recognition (from features to end-to-end).
Includes:
  - Synthetic IQ dataset generator (multiple modulations + simple "protocol" label)
  - Feature extraction: time-domain, frequency-domain, spectrogram (STFT),
    continuous wavelet transform (CWT), and higher-order statistics
  - Traditional ML pipeline: feature selection, scaling, PCA, cross-validated classifiers
  - Deep learning models: Spectrogram-CNN, Raw-IQ 1D-CNN, LSTM, Transformer
  - Evaluation: accuracy, confusion matrices, SNR curves, mixed-interference testing
  - Interpretability: t-SNE visualization and confusion matrix heatmaps
All code in one file; execute `python signal_suite.py`.
"""

import os
import sys
import math
import time
import random
import argparse
from functools import partial

import numpy as np
import scipy.signal as sps
import scipy.stats as stats
from sklearn.model_selection import train_test_split, GridSearchCV, StratifiedKFold
from sklearn.preprocessing import StandardScaler, MinMaxScaler
from sklearn.decomposition import PCA
from sklearn.feature_selection import SelectKBest, f_classif
from sklearn.ensemble import RandomForestClassifier
from sklearn.svm import SVC
from sklearn.metrics import accuracy_score, confusion_matrix, classification_report
from sklearn.manifold import TSNE
import matplotlib.pyplot as plt
import seaborn as sns

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

# ---------------------------
# Reproducibility / device
# ---------------------------
SEED = 2025
np.random.seed(SEED)
random.seed(SEED)
torch.manual_seed(SEED)
if torch.cuda.is_available():
    DEVICE = torch.device("cuda")
    torch.cuda.manual_seed(SEED)
else:
    DEVICE = torch.device("cpu")

# ---------------------------
# Utilities
# ---------------------------
def ensure_dir(d):
    if not os.path.exists(d):
        os.makedirs(d)

PLOT_DIR = "plots_signal_suite"
ensure_dir(PLOT_DIR)

# ---------------------------
# Synthetic signal generator
# ---------------------------
def awgn(signal, snr_db):
    """Add AWGN noise to complex signal to achieve desired SNR in dB"""
    sig_power = np.mean(np.abs(signal)**2)
    snr_linear = 10**(snr_db/10.0)
    noise_power = sig_power / snr_linear
    noise = np.sqrt(noise_power/2) * (np.random.randn(*signal.shape) + 1j*np.random.randn(*signal.shape))
    return signal + noise

def generate_baseband(mod_type, num_samples=1024, sps_rate=8, freq_offset=0.0, phase_offset=0.0):
    """
    Generate a baseband complex IQ burst for a given modulation type.
    Supported mod_type: 'BPSK','QPSK','8PSK','16QAM','AM','FM','GFSK'
    Returns complex numpy array length num_samples
    """
    t = np.arange(num_samples) / sps_rate
    # symbol rate chosen so that many symbols fit in window
    if mod_type == 'BPSK':
        # random bits mapped to ±1
        bits = np.random.choice([1,-1], size=num_samples)
        s = bits.astype(np.complex64) * np.exp(1j*phase_offset)
    elif mod_type == 'QPSK':
        bits = np.random.randint(0,4,size=num_samples)
        mapping = {0:1+1j,1:-1+1j,2:-1-1j,3:1-1j}
        s = np.array([mapping[b] for b in bits], dtype=np.complex64)
        s /= np.sqrt(2)
    elif mod_type == '8PSK':
        bits = np.random.randint(0,8,size=num_samples)
        s = np.exp(1j*(2*np.pi*bits/8.0 + phase_offset))
    elif mod_type == '16QAM':
        # square 16-QAM normalized
        re = np.random.choice([-3,-1,1,3], size=num_samples)
        im = np.random.choice([-3,-1,1,3], size=num_samples)
        s = (re + 1j*im).astype(np.complex64)
        s /= np.sqrt((10)) # normalization
    elif mod_type == 'AM':
        # amplitude modulation onto a complex carrier (real amplitude mod)
        carrier = np.exp(1j*(2*np.pi*0.1*t + phase_offset))
        base = 0.7 + 0.3*np.random.randn(num_samples)
        s = base * carrier
    elif mod_type == 'FM':
        # frequency-modulated baseband: integrate frequency deviations
        freq_dev = 2*np.pi*0.05*np.random.randn(num_samples)  # instantaneous frequency noise
        inst_phase = np.cumsum(freq_dev) + 2*np.pi*0.1*t + phase_offset
        s = np.exp(1j*inst_phase)
    elif mod_type == 'GFSK':
        # gaussian filtered FSK-like: generate bits and approximate GFSK by filtering
        bits = np.random.choice([0,1], size=num_samples)
        # map to frequency impulses +/- delta
        delta = 0.3
        freq = (2*bits - 1) * delta
        phase = np.cumsum(freq)
        s = np.exp(1j*(phase + phase_offset))
    else:
        # default noise
        s = np.exp(1j*(2*np.pi*0.1*t + phase_offset))
    # apply small frequency offset if requested
    if freq_offset != 0.0:
        s *= np.exp(1j*2*np.pi*freq_offset*t)
    # apply simple pulse shaping: convolve with small raised-cosine-ish kernel
    window = np.hanning(7)
    s_real = np.convolve(np.real(s), window, mode='same')/np.sum(window)
    s_imag = np.convolve(np.imag(s), window, mode='same')/np.sum(window)
    s = s_real + 1j*s_imag
    # normalize power to 1
    s /= np.sqrt(np.mean(np.abs(s)**2) + 1e-12)
    return s.astype(np.complex64)

# Simple protocol label generator given a modulation type -> simulate 'protocol' by framing patterns
def generate_protocol_label(mod_type):
    # map mod types to simple "protocol" classes (e.g., framing patterns)
    mapping = {
        'BPSK': 'CTRL',
        'QPSK': 'DATA',
        '8PSK': 'VOICE',
        '16QAM': 'VIDEO',
        'AM': 'ANALOG',
        'FM': 'ANALOG',
        'GFSK': 'CTRL',
    }
    return mapping.get(mod_type, 'UNKNOWN')

# ---------------------------
# Dataset builder
# ---------------------------
MODS = ['BPSK','QPSK','8PSK','16QAM','AM','FM','GFSK']
MOD_TO_IDX = {m:i for i,m in enumerate(MODS)}
PROTOCOLS = ['CTRL','DATA','VOICE','VIDEO','ANALOG','UNKNOWN']
PROTO_TO_IDX = {p:i for i,p in enumerate(PROTOCOLS)}

def create_dataset(n_per_class=200, num_samples=1024, sps=8, snr_db=20, snr_jitter=2, interference_prob=0.2):
    """
    Create dataset with equal number per modulation class.
    Returns:
        X_complex: shape (N, num_samples) complex64
        y_mod: shape (N,) int labels for modulation
        y_proto: shape (N,) int labels for protocol
    """
    X = []
    y_mod = []
    y_proto = []
    for m in MODS:
        for _ in range(n_per_class):
            s = generate_baseband(m, num_samples=num_samples, sps_rate=sps,
                                   freq_offset=np.random.uniform(-0.05,0.05),
                                   phase_offset=np.random.uniform(0,2*np.pi))
            # maybe add an interfering emitter with some prob
            if np.random.rand() < interference_prob:
                interferer = generate_baseband(np.random.choice(MODS),
                                               num_samples=num_samples, sps_rate=sps)
                # mix with random power
                alpha = np.random.uniform(0.1,0.8)
                s = s + alpha * interferer
            # AWGN with jittered SNR
            snr_sample = snr_db + np.random.uniform(-snr_jitter, snr_jitter)
            s_noisy = awgn(s, snr_sample)
            X.append(s_noisy)
            y_mod.append(MOD_TO_IDX[m])
            y_proto.append(PROTO_TO_IDX[generate_protocol_label(m)])
    X = np.stack(X, axis=0)
    y_mod = np.array(y_mod)
    y_proto = np.array(y_proto)
    return X, y_mod, y_proto

# ---------------------------
# Feature extraction
# ---------------------------
def extract_time_features(x_complex):
    """
    Compute time-domain features for a single complex burst (1D array).
    Returns 1D feature vector (real-valued)
    """
    x = x_complex
    i = np.real(x)
    q = np.imag(x)
    amp = np.abs(x)
    phase = np.angle(x)
    inst_freq = np.gradient(np.unwrap(phase))
    feats = []
    # basic stats
    for v in [i, q, amp, inst_freq]:
        feats += [np.mean(v), np.std(v), stats.skew(v), stats.kurtosis(v)]
    # envelope features
    feats += [np.max(amp), np.min(amp), np.median(amp)]
    # RMS, crest factor
    feats += [np.sqrt(np.mean(np.abs(x)**2)), np.max(amp)/ (np.sqrt(np.mean(amp**2))+1e-12)]
    return np.asarray(feats, dtype=np.float32)

def extract_freq_features(x_complex, fs=1.0):
    """
    Frequency domain features: spectral centroid, bandwidth, spectral rolloff, top peaks
    """
    x = x_complex
    N = len(x)
    # compute PSD via Welch
    f, Pxx = sps.welch(x, fs=fs, nperseg=min(256,N))
    P = np.abs(Pxx) + 1e-12
    P_norm = P / np.sum(P)
    centroid = np.sum(f * P_norm)
    bw = np.sqrt(np.sum(((f-centroid)**2) * P_norm))
    # spectral rolloff 0.85
    cumsum = np.cumsum(P_norm)
    rolloff_idx = np.searchsorted(cumsum, 0.85)
    rolloff_freq = f[min(rolloff_idx, len(f)-1)]
    # top-3 peaks
    peaks, _ = sps.find_peaks(P, height=np.max(P)*0.05)
    peak_vals = sorted(P[peaks], reverse=True)[:3] if len(peaks)>0 else [0,0,0]
    # pack
    feats = [centroid, bw, rolloff_freq] + list(peak_vals) + [np.sum(P**2)]
    return np.asarray(feats, dtype=np.float32)

def extract_spectrogram(x_complex, nfft=256, noverlap=128, fs=1.0):
    """
    Return spectrogram magnitude (log-scaled) shape (freq_bins, time_bins)
    """
    x = x_complex
    f, t, Sxx = sps.stft(x, fs=fs, nperseg=nfft, noverlap=noverlap, padded=False)
    S = np.abs(Sxx)
    S = np.log10(S + 1e-12)
    # normalize
    S = (S - S.mean()) / (S.std()+1e-12)
    return S.astype(np.float32)

def extract_cwt(x_complex, widths=np.arange(1,64)):
    """
    Continuous Wavelet Transform (Ricker wavelet) magnitude features summarized
    We compute CWT and then pool to create fixed-length summary
    """
    x = np.real(x_complex)  # use real part for CWT
    try:
        cwtmat = sps.cwt(x, sps.ricker, widths)
    except Exception:
        # fallback small widths
        widths2 = np.arange(1,32)
        cwtmat = sps.cwt(x, sps.ricker, widths2)
    # summarize by mean/std across time for each scale
    means = cwtmat.mean(axis=1)
    stds = cwtmat.std(axis=1)
    feats = np.concatenate([means, stds]).astype(np.float32)
    # reduce dimensionality if too big by simple downsampling
    if feats.size > 128:
        idx = np.linspace(0, feats.size-1, 128).astype(int)
        feats = feats[idx]
    return feats

def extract_higher_order(x_complex):
    """
    Compute simple higher-order statistics: moments / cumulants approximations
    """
    x = x_complex
    r = np.real(x); im = np.imag(x)
    moments = []
    for v in [r, im, np.abs(x)]:
        moments.append(np.mean(v**3))
        moments.append(np.mean(v**4))
    # cross moments
    moments.append(np.mean(r*im))
    moments.append(np.mean(r**2 * im))
    return np.asarray(moments, dtype=np.float32)

def extract_features_all(X_complex, fs=1.0):
    """
    Given X_complex shape (N, L), compute an aggregated feature matrix and
    spectrograms and cwt arrays.
    Returns:
      feat_matrix: (N, F)
      specs_list: list of spectrogram arrays (per-sample)
      cwt_list: list of cwt-feature arrays (per-sample)
    """
    N, L = X_complex.shape
    feat_list = []
    specs = []
    cwts = []
    for i in range(N):
        x = X_complex[i]
        ftime = extract_time_features(x)
        ffreq = extract_freq_features(x, fs=fs)
        fh = extract_higher_order(x)
        fcwt = extract_cwt(x)
        feat_vec = np.concatenate([ftime, ffreq, fh, fcwt])
        feat_list.append(feat_vec)
        specs.append(extract_spectrogram(x))
        cwts.append(fcwt)
    feat_matrix = np.vstack(feat_list).astype(np.float32)
    return feat_matrix, specs, cwts

# ---------------------------
# Traditional ML pipeline
# ---------------------------
def traditional_pipeline(X_feat, y, n_select=40, do_pca=True, pca_dim=10, cv_folds=5, random_state=SEED):
    """
    Standard pipeline: scaling -> feature selection -> classifier (RF + SVM gridsearch)
    Returns best model, scaler, selector, pca (may be None), and cv history.
    """
    scaler = StandardScaler()
    Xs = scaler.fit_transform(X_feat)

    selector = SelectKBest(score_func=f_classif, k=min(n_select, Xs.shape[1]))
    Xs2 = selector.fit_transform(Xs, y)

    pca = None
    if do_pca:
        pca = PCA(n_components=min(pca_dim, Xs2.shape[1]), random_state=random_state)
        Xs2 = pca.fit_transform(Xs2)

    # classifiers to try
    param_grid_svc = {'C':[0.1,1,10], 'kernel':['rbf'], 'gamma':['scale']}
    param_grid_rf = {'n_estimators':[50,100], 'max_depth':[10,20,None]}

    svc = GridSearchCV(SVC(), param_grid_svc, cv=cv_folds, scoring='accuracy', n_jobs=1)
    rf = GridSearchCV(RandomForestClassifier(random_state=random_state), param_grid_rf, cv=cv_folds, scoring='accuracy', n_jobs=1)

    # run both and pick best
    svc.fit(Xs2, y)
    rf.fit(Xs2, y)
    best = svc.best_estimator_ if svc.best_score_ >= rf.best_score_ else rf.best_estimator_
    history = {'svc':svc, 'rf':rf}
    return best, scaler, selector, pca, history

# ---------------------------
# PyTorch datasets & models
# ---------------------------
class IQDataset(Dataset):
    def __init__(self, X_complex, y, transform=None):
        self.X = X_complex
        self.y = y
        self.transform = transform

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

    def __getitem__(self, idx):
        x = self.X[idx]
        # represent complex as 2 channels (I,Q)
        x_iq = np.stack([np.real(x), np.imag(x)], axis=0).astype(np.float32)  # (2, L)
        if self.transform:
            x_iq = self.transform(x_iq)
        return x_iq, int(self.y[idx])

class SpecDataset(Dataset):
    def __init__(self, specs, y, transform=None):
        # specs: list of 2D arrays (freq_bins, time_bins)
        self.specs = specs
        self.y = y
        self.transform = transform
    def __len__(self):
        return len(self.y)
    def __getitem__(self, idx):
        s = self.specs[idx].astype(np.float32)
        # add channel dim
        s = np.expand_dims(s, 0)
        return s, int(self.y[idx])

# Models
class RawIQ_1DCNN(nn.Module):
    def __init__(self, in_channels=2, seq_len=1024, n_classes=7):
        super().__init__()
        self.conv1 = nn.Conv1d(in_channels, 32, kernel_size=7, padding=3)
        self.bn1 = nn.BatchNorm1d(32)
        self.conv2 = nn.Conv1d(32,64,kernel_size=5,padding=2)
        self.bn2 = nn.BatchNorm1d(64)
        self.conv3 = nn.Conv1d(64,128,kernel_size=3,padding=1)
        self.bn3 = nn.BatchNorm1d(128)
        self.pool = nn.AdaptiveAvgPool1d(1)
        self.fc1 = nn.Linear(128, 128)
        self.fc2 = nn.Linear(128, n_classes)
    def forward(self, x):
        # x: (B,2,L)
        x = F.relu(self.bn1(self.conv1(x)))
        x = F.relu(self.bn2(self.conv2(x)))
        x = F.relu(self.bn3(self.conv3(x)))
        x = self.pool(x).squeeze(-1)
        x = F.relu(self.fc1(x))
        x = self.fc2(x)
        return x

class SpecCNN(nn.Module):
    def __init__(self, in_ch=1, n_classes=7):
        super().__init__()
        # small image CNN
        self.conv1 = nn.Conv2d(in_ch,16,kernel_size=3,padding=1)
        self.conv2 = nn.Conv2d(16,32,kernel_size=3,padding=1)
        self.conv3 = nn.Conv2d(32,64,kernel_size=3,padding=1)
        self.pool = nn.MaxPool2d(2)
        self.fc1 = nn.Linear(64*8*8, 128)  # adapt depending on input size
        self.fc2 = nn.Linear(128, n_classes)
    def forward(self, x):
        # x: (B,1,H,W)
        x = F.relu(self.conv1(x))
        x = self.pool(F.relu(self.conv2(x)))
        x = self.pool(F.relu(self.conv3(x)))
        x = x.view(x.size(0), -1)
        x = F.relu(self.fc1(x))
        x = self.fc2(x)
        return x

class LSTMClassifier(nn.Module):
    def __init__(self, in_ch=2, hidden=128, n_layers=2, n_classes=7):
        super().__init__()
        self.rnn = nn.LSTM(input_size=in_ch, hidden_size=hidden, num_layers=n_layers, batch_first=True, bidirectional=True)
        self.fc = nn.Linear(hidden*2, n_classes)
    def forward(self, x):
        # x: (B,2,L) -> transpose to (B,L,2)
        x = x.permute(0,2,1)
        out, _ = self.rnn(x)
        # take last time step
        h = out[:,-1,:]  # (B, hidden*2)
        return self.fc(h)

class TransformerClassifier(nn.Module):
    def __init__(self, in_ch=2, d_model=64, n_heads=4, num_layers=3, n_classes=7):
        super().__init__()
        self.input_proj = nn.Linear(in_ch, d_model)
        encoder_layer = nn.TransformerEncoderLayer(d_model=d_model, nhead=n_heads, dim_feedforward=128)
        self.transformer = nn.TransformerEncoder(encoder_layer, num_layers=num_layers)
        self.fc = nn.Linear(d_model, n_classes)
    def forward(self, x):
        # x: (B,2,L) -> (L,B,2)
        x = x.permute(2,0,1)
        x = self.input_proj(x)  # (L,B,d_model)
        x = self.transformer(x)
        x = x.mean(dim=0)  # (B,d_model)
        return self.fc(x)

# ---------------------------
# Training & evaluation helpers
# ---------------------------
def train_torch_model(model, train_loader, val_loader, n_epochs=20, lr=1e-3, weight_decay=1e-4, device=DEVICE, verbose=True):
    model = model.to(device)
    opt = torch.optim.Adam(model.parameters(), lr=lr, weight_decay=weight_decay)
    criterion = nn.CrossEntropyLoss()
    best_val = 0.0
    best_state = None
    history = {'train_loss':[], 'val_acc':[]}
    for epoch in range(1, n_epochs+1):
        model.train()
        loss_accum = 0.0
        n_total = 0
        for xb, yb in train_loader:
            xb = xb.to(device)
            yb = yb.to(device)
            opt.zero_grad()
            logits = model(xb)
            loss = criterion(logits, yb)
            loss.backward()
            opt.step()
            loss_accum += loss.item()*xb.size(0)
            n_total += xb.size(0)
        train_loss = loss_accum / n_total
        # validation
        model.eval()
        correct = 0
        total = 0
        with torch.no_grad():
            for xb, yb in val_loader:
                xb = xb.to(device); yb = yb.to(device)
                logits = model(xb)
                preds = torch.argmax(logits, dim=1)
                correct += (preds==yb).sum().item()
                total += yb.size(0)
        val_acc = correct/total if total>0 else 0.0
        history['train_loss'].append(train_loss)
        history['val_acc'].append(val_acc)
        if verbose:
            print(f"Epoch {epoch}/{n_epochs} - train_loss {train_loss:.4f} - val_acc {val_acc:.4f}")
        if val_acc > best_val:
            best_val = val_acc
            best_state = model.state_dict()
    if best_state is not None:
        model.load_state_dict(best_state)
    return model, history

def evaluate_model_torch(model, loader, device=DEVICE):
    model = model.to(device)
    model.eval()
    y_true = []
    y_pred = []
    with torch.no_grad():
        for xb, yb in loader:
            xb = xb.to(device)
            logits = model(xb)
            preds = torch.argmax(logits, dim=1).cpu().numpy()
            y_pred.extend(preds.tolist())
            y_true.extend(yb.numpy().tolist())
    return np.array(y_true), np.array(y_pred)

# ---------------------------
# Visualization helpers
# ---------------------------
def plot_confusion(y_true, y_pred, labels, title, fname):
    cm = confusion_matrix(y_true, y_pred, labels=range(len(labels)))
    cmn = cm.astype('float') / (cm.sum(axis=1)[:, np.newaxis] + 1e-12)
    plt.figure(figsize=(8,6))
    sns.heatmap(cmn, annot=True, fmt='.2f', xticklabels=labels, yticklabels=labels, cmap='Blues')
    plt.ylabel('True'); plt.xlabel('Pred'); plt.title(title)
    plt.tight_layout()
    plt.savefig(fname)
    plt.close()

def plot_snr_curve(snr_list, acc_list_dict, fname):
    plt.figure(figsize=(8,5))
    for k,v in acc_list_dict.items():
        plt.plot(snr_list, v, marker='o', label=k)
    plt.xlabel('SNR (dB)'); plt.ylabel('Accuracy'); plt.grid(True); plt.legend()
    plt.tight_layout()
    plt.savefig(fname)
    plt.close()

def plot_tsne(features, labels, label_names, fname, perplexity=30):
    tsne = TSNE(n_components=2, perplexity=perplexity, random_state=SEED)
    X2 = tsne.fit_transform(features)
    plt.figure(figsize=(8,6))
    for lab in np.unique(labels):
        mask = labels==lab
        plt.scatter(X2[mask,0], X2[mask,1], label=label_names[lab], alpha=0.6, s=20)
    plt.legend()
    plt.tight_layout()
    plt.savefig(fname)
    plt.close()

# ---------------------------
# End-to-end experiment orchestration
# ---------------------------
def run_full_experiment():
    """
    This function runs:
      - dataset creation (small sizes for runnable demo)
      - extract features + spectrograms
      - train traditional ML pipeline
      - train DL models (RawIQ CNN & Spec CNN & LSTM)
      - evaluate and produce SNR curves and visualizations
    """
    start = time.time()
    print("===== Creating dataset =====")
    n_per = 120  # per modulation - small so demo runs quickly
    num_samples = 1024
    snr_for_train = 10  # train at moderate SNR with jitter internally
    X, y_mod, y_proto = create_dataset(n_per_class=n_per, num_samples=num_samples, sps=8,
                                       snr_db=snr_for_train, snr_jitter=6, interference_prob=0.25)
    N = X.shape[0]
    print("Dataset shape:", X.shape, "Mod labels:", np.unique(y_mod).shape)

    # split train/test
    X_train, X_test, y_train, y_test = train_test_split(X, y_mod, test_size=0.3, stratify=y_mod, random_state=SEED)
    print("Train/test sizes:", X_train.shape[0], X_test.shape[0])

    print("===== Extracting features (may take a bit) =====")
    feat_train, specs_train, cwts_train = extract_features_all(X_train, fs=1.0)
    feat_test, specs_test, cwts_test = extract_features_all(X_test, fs=1.0)
    print("Feature dims:", feat_train.shape, feat_test.shape)

    # Traditional ML pipeline
    print("===== Training traditional ML pipeline (RandomForest/SVC) =====")
    best_model, scaler, selector, pca, history = traditional_pipeline(feat_train, y_train, n_select=40, do_pca=True, pca_dim=10, cv_folds=3)
    # prepare test features same transform
    Xs_test = scaler.transform(feat_test)
    Xs_test2 = selector.transform(Xs_test)
    if pca is not None:
        Xs_test2 = pca.transform(Xs_test2)
    y_pred_trad = best_model.predict(Xs_test2)
    acc_trad = accuracy_score(y_test, y_pred_trad)
    print("Traditional model test acc:", acc_trad)
    plot_confusion(y_test, y_pred_trad, MODS, "Trad ML Confusion", os.path.join(PLOT_DIR, "conf_trad.png"))

    # Deep Learning: prepare datasets
    print("===== Preparing PyTorch datasets =====")
    # Raw IQ datasets
    ds_train_iq = IQDataset(X_train, y_train)
    ds_test_iq = IQDataset(X_test, y_test)
    batch = 64
    dl_train_iq = DataLoader(ds_train_iq, batch_size=batch, shuffle=True, drop_last=True)
    dl_val_iq = DataLoader(ds_test_iq, batch_size=batch, shuffle=False)

    # Spectrogram datasets (need to resize spectrograms to fixed small size)
    # We'll pad/crop spectrograms to shape (freq_bins=64, time_bins=64) for SpecCNN
    def spec_preproc(spec):
        # center crop or pad to 64x64
        H,W = spec.shape
        Ht, Wt = 64,64
        out = np.zeros((Ht,Wt), dtype=np.float32)
        h0 = max(0,(Ht-H)//2)
        w0 = max(0,(Wt-W)//2)
        h1 = min(H, Ht); w1 = min(W, Wt)
        out[h0:h0+h1, w0:w0+w1] = spec[:h1, :w1]
        return out
    specs_train_proc = [spec_preproc(s) for s in specs_train]
    specs_test_proc = [spec_preproc(s) for s in specs_test]
    ds_train_spec = SpecDataset(specs_train_proc, y_train)
    ds_test_spec = SpecDataset(specs_test_proc, y_test)
    dl_train_spec = DataLoader(ds_train_spec, batch_size=64, shuffle=True, drop_last=True)
    dl_val_spec = DataLoader(ds_test_spec, batch_size=64, shuffle=False)

    # Define models
    print("===== Building models =====")
    raw_model = RawIQ_1DCNN(in_channels=2, seq_len=num_samples, n_classes=len(MODS))
    spec_model = SpecCNN(in_ch=1, n_classes=len(MODS))
    lstm_model = LSTMClassifier(in_ch=2, hidden=128, n_layers=2, n_classes=len(MODS))

    # Train lightweight for demo
    print("===== Training RawIQ CNN =====")
    raw_model, hist_raw = train_torch_model(raw_model, dl_train_iq, dl_val_iq, n_epochs=12, lr=1e-3, verbose=True)
    print("===== Training Spec CNN =====")
    spec_model, hist_spec = train_torch_model(spec_model, dl_train_spec, dl_val_spec, n_epochs=12, lr=1e-3, verbose=True)
    print("===== Training LSTM =====")
    lstm_model, hist_lstm = train_torch_model(lstm_model, dl_train_iq, dl_val_iq, n_epochs=12, lr=1e-3, verbose=True)

    print("===== Evaluating DL models =====")
    y_t_raw, y_p_raw = evaluate_model_torch(raw_model, dl_val_iq)
    print("RawIQ CNN acc:", accuracy_score(y_t_raw, y_p_raw))
    plot_confusion(y_t_raw, y_p_raw, MODS, "RawIQ CNN Confusion", os.path.join(PLOT_DIR, "conf_raw.png"))

    y_t_spec, y_p_spec = evaluate_model_torch(spec_model, dl_val_spec)
    print("Spec CNN acc:", accuracy_score(y_t_spec, y_p_spec))
    plot_confusion(y_t_spec, y_p_spec, MODS, "Spec CNN Confusion", os.path.join(PLOT_DIR, "conf_spec.png"))

    y_t_lstm, y_p_lstm = evaluate_model_torch(lstm_model, dl_val_iq)
    print("LSTM acc:", accuracy_score(y_t_lstm, y_p_lstm))
    plot_confusion(y_t_lstm, y_p_lstm, MODS, "LSTM Confusion", os.path.join(PLOT_DIR, "conf_lstm.png"))

    # -------------------------------
    # SNR curve: evaluate models across SNRs
    # -------------------------------
    print("===== Computing SNR curves (this may take a while) =====")
    snr_vals = list(range(-5, 21, 5))  # -5,0,5,...20 dB
    accs = {'Traditional':[], 'RawCNN':[], 'SpecCNN':[], 'LSTM':[]}
    for s in snr_vals:
        print("Evaluating SNR:", s)
        X_snr, y_snr, _ = create_dataset(n_per_class=30, num_samples=num_samples, sps=8, snr_db=s, snr_jitter=1, interference_prob=0.25)
        # traditional model eval (use feature pipeline)
        feat_s, specs_s, _ = extract_features_all(X_snr, fs=1.0)
        Xs_s = scaler.transform(feat_s)
        Xs_s2 = selector.transform(Xs_s)
        if pca is not None:
            Xs_s2 = pca.transform(Xs_s2)
        y_pred_t = best_model.predict(Xs_s2)
        accs['Traditional'].append(accuracy_score(y_snr, y_pred_t))
        # raw cnn eval
        ds_s_iq = IQDataset(X_snr, y_snr)
        dl_s_iq = DataLoader(ds_s_iq, batch_size=64, shuffle=False)
        _, y_p_raw_s = evaluate_model_torch(raw_model, dl_s_iq)
        accs['RawCNN'].append(accuracy_score(y_snr, y_p_raw_s))
        # spec cnn eval
        specs_s_proc = [spec_preproc(sx) for sx in specs_s]
        ds_s_spec = SpecDataset(specs_s_proc, y_snr)
        dl_s_spec = DataLoader(ds_s_spec, batch_size=64, shuffle=False)
        _, y_p_spec_s = evaluate_model_torch(spec_model, dl_s_spec)
        accs['SpecCNN'].append(accuracy_score(y_snr, y_p_spec_s))
        # lstm eval
        _, y_p_lstm_s = evaluate_model_torch(lstm_model, dl_s_iq)
        accs['LSTM'].append(accuracy_score(y_snr, y_p_lstm_s))

    plot_snr_curve(snr_vals, accs, os.path.join(PLOT_DIR, "snr_curves.png"))
    print("SNR curve saved to", os.path.join(PLOT_DIR, "snr_curves.png"))

    # -------------------------------
    # Interpretability: t-SNE on traditional features & CNN embeddings
    # -------------------------------
    print("===== t-SNE visualization =====")
    # traditional features t-SNE
    X_feat_all = np.vstack([feat_train, feat_test])
    y_all = np.concatenate([y_train, y_test])
    plot_tsne(X_feat_all[:1000], y_all[:1000], MODS, os.path.join(PLOT_DIR, "tsne_features.png"))
    print("t-SNE saved (features)")

    # get embeddings from raw_model (before final FC)
    def get_raw_embeddings(model, X_complex):
        model = model.to(DEVICE)
        model.eval()
        embs = []
        with torch.no_grad():
            for i in range(0, len(X_complex), 64):
                batch = X_complex[i:i+64]
                xb = []
                for x in batch:
                    xb.append(np.stack([np.real(x), np.imag(x)], axis=0))
                xb = np.array(xb).astype(np.float32)
                xb_t = torch.tensor(xb, device=DEVICE)
                # forward until pool layer
                xout = F.relu(model.bn1(model.conv1(xb_t)))
                xout = F.relu(model.bn2(model.conv2(xout)))
                xout = F.relu(model.bn3(model.conv3(xout)))
                xout = model.pool(xout).squeeze(-1).cpu().numpy()
                embs.append(xout)
        embs = np.vstack(embs)
        return embs
    emb_raw = get_raw_embeddings(raw_model, X_test[:1000])
    plot_tsne(emb_raw, y_test[:1000], MODS, os.path.join(PLOT_DIR, "tsne_rawcnn.png"))
    print("t-SNE saved (rawcnn)")

    # -------------------------------
    # Confusion matrix for final comparisons saved above
    # -------------------------------
    # Save sample spectrograms
    plt.figure(figsize=(10,6))
    for i in range(6):
        plt.subplot(2,3,i+1)
        plt.imshow(specs_train_proc[i], aspect='auto', origin='lower')
        plt.title(f"Spec: {MODS[y_train[i]]}")
        plt.axis('off')
    plt.tight_layout()
    plt.savefig(os.path.join(PLOT_DIR, "sample_specs.png"))
    plt.close()

    end = time.time()
    print("Experiment finished in {:.1f}s. Plots saved to {}".format(end-start, PLOT_DIR))

# ---------------------------
# CLI
# ---------------------------
if __name__ == "__main__":
    parser = argparse.ArgumentParser(description="Signal Classification & Protocol Recognition Suite")
    parser.add_argument('--run', type=str, default='full', help='Which experiment to run: full (default)')
    args = parser.parse_args()
    if args.run == 'full':
        run_full_experiment()
    else:
        run_full_experiment()

第六章代码

#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""
rf_fingerprint.py
第 6 章 无线电指纹 (RF Fingerprinting) 与抗欺骗 实验代码示例。

包含:
 - 多发射器合成 IQ 信号(模拟硬件非理想性:瞬态、IQ 不平衡、噪声、相噪)
 - 指纹特征提取(稳态特征 + 瞬态特征 + 融合)
 - 深度学习分类网络(1D-CNN / LSTM)
 - 对抗样本攻击 (FGSM) + 对抗训练 (认证)
 - 基本性能评测、混淆矩阵

注意:仅为示范用途,可扩展到真实硬件数据。
"""

import os
import numpy as np
import random
import math
import matplotlib.pyplot as plt

import torch
import torch.nn as nn
import torch.optim as optim
from torch.utils.data import Dataset, DataLoader

from scipy.signal import butter, lfilter

# -----------------------
# 固定随机种子和设备
# -----------------------
SEED = 2025
np.random.seed(SEED)
random.seed(SEED)
torch.manual_seed(SEED)
if torch.cuda.is_available():
    DEVICE = torch.device("cuda")
    torch.cuda.manual_seed(SEED)
else:
    DEVICE = torch.device("cpu")

# -----------------------
# 模拟多个发射器 (devices) 的 IQ 信号生成
# -----------------------
def generate_device_signal(device_id, length=2048, fs=1.0):
    """
    生成一个设备发射器的IQ信号,模拟硬件非理想性。
    device_id: 整数,用于区分不同发射器特性
    length: 信号长度
    fs: 采样率 (单位化)
    返回:complex ndarray 长度 length
    """
    t = np.arange(length) / fs

    # 正常载波频率
    fc = 0.1  
    # 每个设备一个独特幅度失真、IQ 不平衡和相噪特性
    amp_error = 1.0 + 0.01 * (device_id - 2)  # 幅度误差
    phase_error = 0.01 * device_id            # 固定相偏差
    iq_imbalance = 0.005 * (device_id - 3)    # IQ 不平衡比例
    phase_noise_strength = 0.001 * device_id  # 相噪强度

    # 基础正弦载波
    carrier = amp_error * np.exp(1j * (2 * np.pi * fc * t + phase_error))

    # 模拟 IQ 不平衡:对 I 分量和 Q 分量施加不同增益
    i = np.real(carrier) * (1 + iq_imbalance)
    q = np.imag(carrier) * (1 - iq_imbalance)

    # 将非理想性叠加:加入相噪
    # 相噪用累积小扰动
    phase_noise = np.cumsum(phase_noise_strength * np.random.randn(length))
    signal = (i + 1j * q) * np.exp(1j * phase_noise)

    # 模拟瞬态部分 (transient): 前几部分注入不稳定增益
    transient_len = length // 10
    transient = np.linspace(1.5, 1.0, transient_len)
    signal[:transient_len] *= transient

    # 添加高斯噪声
    noise_power = 0.001
    noise = np.sqrt(noise_power / 2) * (np.random.randn(length) + 1j * np.random.randn(length))
    signal += noise

    # 归一化功率
    signal = signal / np.sqrt(np.mean(np.abs(signal)**2) + 1e-12)
    return signal.astype(np.complex64)

def generate_dataset(n_devices=5, samples_per_device=200, length=2048):
    """
    为多个设备生成数据集。
    返回:
      X: complex ndarray shape (n_devices * samples_per_device, length)
      y: int 标签 (device_id)
    """
    X = []
    y = []
    for dev in range(n_devices):
        for _ in range(samples_per_device):
            sig = generate_device_signal(dev, length=length)
            X.append(sig)
            y.append(dev)
    X = np.stack(X, axis=0)
    y = np.array(y, dtype=np.int64)
    return X, y

# -----------------------
# 特征提取:稳态 + 瞬态 + 融合
# -----------------------
def extract_steady_features(x, fs=1.0):
    """
    提取稳态特征 (steady-state),从最后的大部分样本区段提取统计量
    返回特征向量 (real)
    """
    # 取尾部 50% 作为稳态
    L = len(x)
    steady = x[L//2:]
    i = np.real(steady)
    q = np.imag(steady)
    amp = np.abs(steady)
    phase = np.angle(steady)
    features = [
        np.mean(i), np.std(i),
        np.mean(q), np.std(q),
        np.mean(amp), np.std(amp),
        np.mean(phase), np.std(phase)
    ]
    return np.array(features, dtype=np.float32)

def extract_transient_features(x, fs=1.0):
    """
    提取瞬态 (transient) 特征,使用前面一个小截断段
    """
    L = len(x)
    transient = x[:L//10]  # 前 10%
    i = np.real(transient)
    q = np.imag(transient)
    amp = np.abs(transient)
    phase = np.unwrap(np.angle(transient))
    # 计算瞬态斜率 / 动态变化
    amp_slope = np.polyfit(np.arange(len(amp)), amp, 1)[0]
    phase_slope = np.polyfit(np.arange(len(phase)), phase, 1)[0]
    features = [amp_slope, phase_slope,
                np.mean(amp), np.std(amp),
                np.mean(i), np.mean(q)]
    return np.array(features, dtype=np.float32)

def extract_features(X):
    """
    对所有信号 X(complex)提取特征融合
    返回特征矩阵 shape (N, D)
    """
    feats = []
    for x in X:
        fs = 1.0
        f1 = extract_steady_features(x, fs)
        f2 = extract_transient_features(x, fs)
        feats.append(np.concatenate([f1, f2]))
    feats = np.vstack(feats)
    return feats

# -----------------------
# PyTorch 数据集与模型
# -----------------------
class RFDataset(Dataset):
    def __init__(self, X, y):
        self.X = X  # complex
        self.y = y
    def __len__(self):
        return len(self.y)
    def __getitem__(self, idx):
        x = self.X[idx]
        # 构造 real 和 imag 两通道
        xi = np.stack([np.real(x), np.imag(x)], axis=0).astype(np.float32)
        return xi, self.y[idx]

class CNN1DFingerprint(nn.Module):
    def __init__(self, in_ch=2, seq_len=2048, n_classes=5):
        super().__init__()
        self.conv1 = nn.Conv1d(in_ch, 32, kernel_size=31, padding=15)
        self.bn1 = nn.BatchNorm1d(32)
        self.conv2 = nn.Conv1d(32, 64, kernel_size=15, padding=7)
        self.bn2 = nn.BatchNorm1d(64)
        self.conv3 = nn.Conv1d(64, 128, kernel_size=7, padding=3)
        self.bn3 = nn.BatchNorm1d(128)
        self.pool = nn.AdaptiveAvgPool1d(1)
        self.fc1 = nn.Linear(128, 64)
        self.fc2 = nn.Linear(64, n_classes)
    def forward(self, x):
        x = torch.relu(self.bn1(self.conv1(x)))
        x = torch.relu(self.bn2(self.conv2(x)))
        x = torch.relu(self.bn3(self.conv3(x)))
        x = self.pool(x).squeeze(-1)
        x = torch.relu(self.fc1(x))
        out = self.fc2(x)
        return out

# -----------------------
# 对抗攻击 (FGSM) + 认证训练
# -----------------------
def fgsm_attack(x, grads, eps=0.01):
    """
    对输入 x 执行 FGSM 对抗扰动
    x: tensor (B, 2, L)
    grads: gradient of loss wrt x
    eps: 扰动强度
    """
    sign = grads.sign()
    x_adv = x + eps * sign
    return x_adv.detach()

# -----------------------
# 训练 & 对抗训练函数
# -----------------------
def train_classifier(model, loader, optimizer, criterion, device):
    model.train()
    running_loss = 0.0
    for xb, yb in loader:
        xb = xb.to(device)
        yb = yb.to(device)
        optimizer.zero_grad()
        logits = model(xb)
        loss = criterion(logits, yb)
        loss.backward()
        optimizer.step()
        running_loss += loss.item() * xb.size(0)
    return running_loss / len(loader.dataset)

def train_with_adversary(model, loader, optimizer, criterion, device, eps=0.01):
    """
    对抗训练:对每个 minibatch 生成 FGSM 样本并一起训练
    """
    model.train()
    running_loss = 0.0
    for xb, yb in loader:
        xb = xb.to(device)
        yb = yb.to(device)

        # 正常前向
        optimizer.zero_grad()
        logits = model(xb)
        loss = criterion(logits, yb)
        loss.backward()

        # 计算梯度对输入
        grads = xb.grad if xb.requires_grad else None
        if grads is None:
            xb_adv = fgsm_attack(xb, torch.autograd.grad(loss, xb, retain_graph=True)[0], eps)
        else:
            xb_adv = fgsm_attack(xb, grads, eps)

        # 对抗样本前向
        logits_adv = model(xb_adv)
        loss_adv = criterion(logits_adv, yb)

        # 合并损失
        loss_total = 0.5 * loss + 0.5 * loss_adv
        optimizer.zero_grad()
        loss_total.backward()
        optimizer.step()

        running_loss += loss_total.item() * xb.size(0)
    return running_loss / len(loader.dataset)

def evaluate(model, loader, device):
    model.eval()
    correct = 0
    total = 0
    all_pred = []
    all_true = []
    with torch.no_grad():
        for xb, yb in loader:
            xb = xb.to(device)
            yb = yb.to(device)
            logits = model(xb)
            preds = torch.argmax(logits, dim=1)
            correct += (preds == yb).sum().item()
            total += xb.size(0)
            all_pred.extend(preds.cpu().numpy().tolist())
            all_true.extend(yb.cpu().numpy().tolist())
    return correct / total, np.array(all_true), np.array(all_pred)

# -----------------------
# 主流程
# -----------------------
def main():
    print("== 生成指纹数据集 ==")
    X, y = generate_dataset(n_devices=5, samples_per_device=300, length=2048)
    print("数据集大小:", X.shape, y.shape)

    # 划分训练和测试
    idx = np.arange(len(y))
    np.random.shuffle(idx)
    split = int(0.7 * len(y))
    train_idx = idx[:split]
    test_idx = idx[split:]
    X_train, y_train = X[train_idx], y[train_idx]
    X_test, y_test = X[test_idx], y[test_idx]

    print("提取特征 (稳态 + 瞬态) 用于传统分类")
    feats_train = extract_features(X_train)
    feats_test = extract_features(X_test)

    print("传统分类 (随机森林) ")
    from sklearn.ensemble import RandomForestClassifier
    clf = RandomForestClassifier(n_estimators=100, random_state=SEED)
    clf.fit(feats_train, y_train)
    acc_trad = clf.score(feats_test, y_test)
    print("传统随机森林分类器准确率:", acc_trad)

    # 深度学习:构建 PyTorch 数据集
    ds_train = RFDataset(X_train, y_train)
    ds_test = RFDataset(X_test, y_test)
    loader_train = DataLoader(ds_train, batch_size=32, shuffle=True)
    loader_test = DataLoader(ds_test, batch_size=32, shuffle=False)

    model = CNN1DFingerprint(in_ch=2, seq_len=2048, n_classes=len(np.unique(y)))
    model = model.to(DEVICE)
    optimizer = optim.Adam(model.parameters(), lr=1e-3)
    criterion = nn.CrossEntropyLoss()

    print("训练指纹分类器 (普通训练) …")
    for epoch in range(10):
        loss = train_classifier(model, loader_train, optimizer, criterion, DEVICE)
        acc, _, _ = evaluate(model, loader_test, DEVICE)
        print(f"Epoch {epoch+1}/10, Loss {loss:.4f}, Test Acc {acc:.4f}")

    print("评估训练后模型 …")
    acc, y_true, y_pred = evaluate(model, loader_test, DEVICE)
    print("测试准确率 (干净样本):", acc)

    # 对抗训练
    print("对抗训练 (FGSM) …")
    model_adv = CNN1DFingerprint(in_ch=2, seq_len=2048, n_classes=len(np.unique(y)))
    model_adv = model_adv.to(DEVICE)
    optimizer_adv = optim.Adam(model_adv.parameters(), lr=1e-3)
    for epoch in range(10):
        loss_adv = train_with_adversary(model_adv, loader_train, optimizer_adv, criterion, DEVICE, eps=0.02)
        acc_adv, _, _ = evaluate(model_adv, loader_test, DEVICE)
        print(f"Adv Epoch {epoch+1}/10, Loss {loss_adv:.4f}, Test Acc after attack {acc_adv:.4f}")

    print("评估对抗训练模型 …")
    acc_clean, _, _ = evaluate(model_adv, loader_test, DEVICE)
    print("对抗训练模型测试准确率 (干净):", acc_clean)

    # 使用 FGSM 在测试集上攻击
    print("对抗测试:生成对抗样本并评估 …")
    # 简单地对测试批次生成 FGSM
    model_adv.eval()
    total = 0
    correct_adv = 0
    for xb, yb in loader_test:
        xb = xb.to(DEVICE)
        yb = yb.to(DEVICE)
        xb.requires_grad = True
        logits = model_adv(xb)
        loss = criterion(logits, yb)
        model_adv.zero_grad()
        loss.backward()
        xb_adv = fgsm_attack(xb, xb.grad, eps=0.02)
        preds_adv = torch.argmax(model_adv(xb_adv), dim=1)
        correct_adv += (preds_adv == yb).sum().item()
        total += yb.size(0)
    acc_adv_test = correct_adv / total
    print("对抗样本测试准确率:", acc_adv_test)

    # 混淆矩阵可视化
    from sklearn.metrics import confusion_matrix
    import seaborn as sns
    cm = confusion_matrix(y_true, y_pred)
    plt.figure(figsize=(6,5))
    sns.heatmap(cm, annot=True, fmt='d')
    plt.xlabel("预测标签"); plt.ylabel("真实标签"); plt.title("混淆矩阵 (干净样本)")
    plt.savefig("confusion_clean.png")
    print("混淆矩阵 (干净样本) 已保存到 confusion_clean.png")

    print("全部流程结束。")

if __name__ == '__main__':
    main()

船载 OpenHarmony AIS 航海工作台:告警与防碰撞预警系统 船载OpenHarmony AIS航海工作台的告警与防碰撞系统,集成十余类告警(超速、偏航、会遇、SOS等),通过网格空间索引实现高效邻近判定,保障实时性。告警管理器统一调度规则、去重、通知与日志,支持悬浮球、语音、跑马灯、弹窗多渠道触达,确保航行安全。SOS紧急通道独立运行,避免干扰。系统设计强调性能优化与多场景适配,为航海安全提供核心支撑。 阅读详情

相关推荐

AI 应用开发:四维架构落地实战——以 HR Agent 为例拆解感知·推理·记忆·执行【意图路由、规划编排、长期记忆、工具执行、面试题】

本文基于四维架构(感知·推理·记忆·执行),以HR Agent为例,系统拆解大模型应用从“聊天”到“生产可用”的落地路径。通过Spring AI实现意图路由、规划编排、RAG检索与工具调用,结合真实踩坑复盘,揭示架构化设计对安全、成本与可维护性的关键价值,为AI Agent工程化提供可复用的实战框架。

王大师企业官方博客 1209

视频隐私脱敏工程化:CSRT/KCF追踪算法在本地推理中的实现与边界

在短视频内容合规审查中,动态遮挡(Dynamic Masking)是高频且高耗时的环节。传统手动关键帧打码不仅效率低下,且在目标快速移动或遮挡时极易出现遮罩漂移。本文从计算机视觉工程角度,解析基于CSRT与KCF算法的目标追踪方案在视频隐私处理中的技术路径、性能权衡及落地实践。

Mysssone的博客 353

ISCE2+MintPy环境搭建实战:用Anaconda五分钟搞定InSAR处理全家桶

本文提供了一份详细的ISCE2+MintPy环境安装教程,旨在帮助InSAR处理初学者快速搭建开发环境。通过使用Anaconda创建虚拟环境并配置国内镜像,用户可在五分钟内一键安装ISCE2和MintPy全家桶,有效解决依赖冲突和版本兼容性问题,让用户能专注于数据处理本身。

2y3u4i5o6p的博客 1005

AI 问数真的能替代 BI 报表吗?

AI问数赋能自然语言查询,提升临时分析效率,但无法替代传统报表。报表适用于稳定输出与持续监控,AI则擅长探索性提问。企业用好AI问数,关键在于夯实数据模型、统一指标口径、健全权限体系与结果校验机制。合理组合报表、仪表板与AI,才能实现高效、准确的业务洞察。

葡萄城技术团队博客 280

手把手带你搭建一个语音对话机器人:5分钟定制个人AI小助手

通过本文的简单步骤,你已经掌握了如何用 Python 搭建一个语音对话机器人。我们使用了实现语音识别,利用 OpenAI API 生成对话内容,并通过gTTS将文本转换为语音。整个流程非常简洁,你可以根据自己的需求进一步扩展机器人功能,甚至将其集成到实际的应用场景中。

小蜗牛的珍贵百宝箱 4807

Uibot RPA中级认证实战:手把手教你用流程创造者搞定客户数据迁移(附验证码容错技巧)

本文详细介绍了Uibot RPA中级认证实战中的客户数据迁移工程化解决方案,特别针对验证码容错技巧和动态表格处理等核心挑战。通过环境准备、登录模块设计、数据提取技术及容错体系的系统讲解,帮助RPA实施工程师掌握高效稳定的自动化流程开发方法,提升企业数据迁移效率。

weixin_30505225的博客 230

AI Agent之核心框架

A: 显式 Thought 有三类价值:可解释性(便于人类审计)、可调试性(定位哪一步选错工具)、学习信号(可做监督微调或评估中间步骤质量)。ReAct 把推理与 可执行行动 绑定,每一步行动后都有 真实 Observation 反馈,从而用工具结果约束生成,降低闭卷幻觉。做完不等于结束——还要 评估做得好不好,把 教训 记下来,下次 带着教训重试,反思作为 记忆,影响后续尝试的策略。工作流程:Action → Evaluation → Reflection → Retry。A: 普通 CoT 只在。

剑雨巍巍的博客 254

Fluent实战——残差震荡分析与收敛优化策略

本文深入探讨了Fluent仿真中残差震荡的成因与优化策略。残差震荡是流场物理或数值问题的关键信号,文章系统分析了网格质量、边界条件、物理模型非定常性及求解设置四大主因,并提供了库朗数与松弛因子的调参心法,以及针对超音速流、共轭传热等典型场景的实战优化方案,旨在帮助用户有效提升计算收敛性与结果可靠性。

w1x2y3的博客 313

ChatGPT、Perplexity、Gemini 与 AI Overviews 引用对比:谁的引用更可信

AI 引擎的引用机制差异很大:ChatGPT 以行内引用加 Sources 列表呈现来源,Perplexity 几乎逐句可溯源,Gemini 在回答中内嵌相关来源链接,AI Overviews 则把多源聚合答案与来源列表一起放在搜索结果顶部。本文从引用透明度、来源偏好、可干预性 3 个维度对比这 4 个引擎,并给出 GEO 的 3 点启示,帮企业在多引擎环境下提升被引用的概率。

淡然一笑 的专栏 191

协方差矩阵自适应进化策略(CMA-ES)详解:从基础原理到优化算法

CMA-ES是一种先进的无导数优化算法,基于生物进化原理解决连续域非线性优化问题。该算法通过自适应调整多元正态分布的均值、协方差矩阵和步长参数,自动学习搜索空间结构,无需梯度信息即可高效优化。核心创新包括协方差矩阵的秩1和秩μ更新策略,以及基于进化路径的步长控制机制。CMA-ES具有旋转不变性、标度不变性和全局探索能力,特别适合3-300维的黑盒优化问题,在复杂系统仿真、超参数调优等领域表现优异。算法实现中关键参数如种群大小、学习率等均有自适应设置规则,使其成为连续优化领域的基准算法之一。

qq_42212808的博客 525

科研级YOLOv8 Neck革新:BiFPN双向特征金字塔赋能,让多尺度融合更高效的实战指南

在文件夹下新建BiFPN.py,将上述BiFPNLayer和BiFPN类的代码写入其中。这一步是为了让YOLOv8的代码架构能识别我们的自定义Neck模块。

m0_64661770的博客 153

【从零理解 Spring AI · 12】MCP 的两种角色:做客户端还是做服务端

上一篇讲了 MCP 的概念:标准协议、三大角色、M+N 替代 M×N。

nice_lcj520的博客 563

人工蜂群优化算法(ABC)详解:原理、实现与应用

人工蜂群算法(Artificial Bee Colony, ABC)是Karaboga于2005年提出的一种群体智能优化算法,其灵感来源于蜜蜂群体在自然界中高效的采蜜行为。该算法通过模拟雇佣蜂、观察蜂和侦察蜂三种角色的分工协作来寻找最优解。在ABC算法中,每个潜在的解决方案被视为一个“食物源”,其质量由适应度函数评估。雇佣蜂负责在已知优质食物源附近进行局部开采;观察蜂根据雇佣蜂分享的信息,以一定概率选择并跟进优质食物源;侦察蜂则随机探索新区域,防止种群陷入局部最优。

qq_42212808的博客 393

AI 应用开发工程师技术实践:大模型接入、通用组件封装与性能优化

本文提出AI应用从PoC到生产的核心挑战:模型调用的持续可用、可控与可优化。通过构建统一LLM网关实现多供应商路由与故障转移,封装对话、工具调用与RAG组件以解耦业务逻辑,结合前缀缓存、批处理与流式背压治理等性能优化手段,形成“接通→接稳→接快”的工程路径,最终以工程化能力决定AI应用的生产可用性。

2301_79189962的博客 619

揭秘LLM如何预测下一个词

Transformer结构的大语言模型(LLM)通过自回归训练学习预测下一个词汇,其核心能力源自权重矩阵的动态优化。自注意力层的Wq/Wk/Wv矩阵捕捉语义关联,前馈网络的W1/W2矩阵进行非线性特征提取,LM Head的W_{lm}矩阵将语义映射到词汇概率。训练过程中,这些矩阵通过反向传播不断调整,最终使模型能够根据上下文(如"我"→"爱")生成合理的后续词汇。这种基于矩阵变换的数学机制是LLM智能生成文本的本质原因。(150字)

ZhangJiqun&191cm&85kg 377

win10 跳过U8环境检测的IE WebControls.rar

win10 安装u8的时候 提示IE WebControls 不通过,这个工具可以欺骗u8的检查。 跳过U8环境检测的IE WebControls。提示导入失败后,重新检测通过。

抽象主义人工智能和生命体AI的内在联系

彭军辉提出“抽象主义人工智能”作为第四大AI范式,批判符号主义、连接主义、行为主义剥离生命本体的误区,确立“智能是现象,生命是本体”的核心公理。主张原生智能必须依附具备存续诉求的生命本体,提出“生命体AI理论”,构建七层演化阶梯与自闭环工程框架,强调智能不可跳过底层存续机制直接模拟语言。该体系为生成式AI提供科学边界,推动从“仿真表象”向“原生智能”的范式跃迁,开辟硅基人工生命新赛道。

抽象主义人工智能和生命体AI 160

华为的C语言编程规范.pdf

华为技术有限公司内部技术规范。本规范制定了编写C语言程序的基本原则、规则和建议。从代码的清晰、简洁、可测试、安全、程序效率、可移植各个方面对C语言编程作出了具体指导。

OpenClaw 深度调查:把 AI 助手做成你自己的控制平面

对 openclaw/openclaw 的完整技术尽调:Gateway 架构、Agent 运行时、工具与沙箱、30 个消息通道、181 个插件、安全信任边界、工程代价与采用建议。17 章 / 10 张图,全部数字标注口径,数据截至 2026-10-31。

厚积薄发 294

visio安装包

Microsoft Office Visio 是一款专业的制图软件。利用经过更新的形状、协作工具和数据链接图表,创建专业级图表以简化复杂的信息。

android 蓝牙通讯demo

android 下蓝牙通讯demo,实现了发现蓝牙,使能蓝牙,通过蓝牙发送消息。

AI赋能智慧港口建设|造形家快速生成港口全要素三维场景

随着智慧港口、港口数字孪生项目的持续推进,港口三维场景的需求日益旺盛。传统港口三维建模,往往需要航拍倾斜摄影采集数据,再人工搭建吊机、岸桥、船舶、集装箱等大量工业设施模型,项目周期长、人力成本高,修改迭代繁琐,制约港口可视化项目落地效率。国产AI三维建模工具,依托卫星影像 AI 识别能力,无需航拍作业,一键快速搭建港口完整三维场景,为港口数字孪生、方案汇报、投标演示提供高效的场景解决方案。

m0_69539167的博客 51

企业微信iPad协议详解:接口接入、实时消息同步与语音转码实践

深入解析企业微信iPad协议的技术架构与开发思路,涵盖API接口接入、长连接通信、实时消息同步、Silk语音转码、异常重连及高并发优化,帮助开发者了解企业微信自动化系统的设计与实现。

欢迎来到祖安! 429

Product Hunt 热榜 2026-11-13:Plurai Simulation、YouArt Desktop App、Omnigraph

2026-11-13 Product Hunt 新产品精选:Plurai Simulation、YouArt Desktop App、Omnigraph。汇总 15 款产品的中文介绍、功能关键词和产品链接。

LaughingZhu的博客 489

AI作图软件技术选型:从4个技术维度出发的实测对比

AI作图软件技术选型:从4个技术维度出发的实测对比

Vbaoke的博客 252

【AI Agent面试系列】AI Agent可观测、评测、安全相关的知识点

之前的文章介绍了和,本文主要介绍AI Agent的测评、可观测等知识。

328

Docling 原理解读:是什么、为什么、怎么样

从论文视角拆解 Docling 的整体架构、DoclingDocument 数据模型、PDF 预处理、版面分析、OCR、TableFormer 双解码器、阅读顺序与装配流程,并解释版面框与 OCR 框的区别。

PLANTTHESON的博客 369

WorkBuddy 实战:一句话需求 → 七屏可交互原型,户外徒步 App 的 AI 设计全流程

WorkBuddy 实战:一句话需求 → 七屏可交互原型,户外徒步 App 的 AI 设计全流程,跟着博主一起来还原整个过程。

夜郎King的博客 5029

从认识人工智能到理解机器学习:我的 AI 入门第一课

正式开始学习人工智能之前,我对 AI 的认识其实比较模糊:知道 ChatGPT、知道大模型,也知道现在很多岗位都在谈 AI,但如果有人问我“人工智能到底是什么”“机器是怎么学会识别一只猫的”,我未必能从头到尾讲清楚。上午的课程更偏向行业全景:人工智能的发展、研究方向、NLP、岗位分工、学习路线和 AI Coding 工具。下午的课程更偏向技术基础:机器学习的原理、有监督学习、深度学习、模型训练和评估方法。

chh563的博客 506

影视场景怎么用AI生成3D镜头预演?从概念图、场景白模到网页审片

概念图有氛围,不代表能直接用于镜头预演。本文围绕剧本拆镜、资料与尺度假设、场景白模、机位走位、动作节拍和网页审片,介绍 AI 生成 3D 场景的预演流程。通过稳定对象与相机编号、统一镜头表、意见关联版本和三轮审核,让讨论落到具体镜头,同时明确白模与网页预演不能替代真实场地勘景及现场安全判断。

Solange__的博客 591
上一篇: 【图神经网络:原理与实战】第九章 异构图神经网络
下一篇: 【图神经网络:原理与实战】第十章 可解释性与可证实性
VectorShift
博客等级 码龄1年 1839粉丝 1109原创
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

个

红包个数最小为10个

元

红包金额最低5元

当前余额3.43元 前往充值 >
需支付:10.00元
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

VectorShift

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付元
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值