スマートフォンや衛星通信機の送信機を分解すると、最後の段にかならず「電力増幅器(Power Amplifier, PA)」が入っています。アンテナから電波を飛ばすには大きな電力が必要で、その電力を作るのがPAの役目です。ところがこのPA、電力効率を上げようとすると、入力が大きいところで出力が頭打ちになる「非線形」な振る舞いをします。波の山がつぶれてしまうのです。
山がつぶれると何が困るのでしょうか。第一に、自分の使っている周波数帯の外へ余計な電力が漏れ出します。これを隣接チャネル漏洩(ACLR: Adjacent Channel Leakage Ratio)と呼び、隣の通信を妨害するため法律(電波法・各種規格)で厳しく制限されています。第二に、自分自身の信号の星座点(コンスタレーション)がにじみ、復調誤りが増えます。これをEVM(Error Vector Magnitude)で測ります。
ではどうするか。PAが信号をゆがめるなら、あらかじめ逆向きにゆがめた信号を入れておけば、PAを通った後にちょうど元に戻る——これがデジタルプリディストーション(DPD)の発想です。PAの非線形関数を $f(\cdot)$ とすると、その逆関数 $f^{-1}(\cdot)$ をデジタル信号処理で前段に置き、$f(f^{-1}(x)) = x$ となるようにします。DPDは現代の基地局・スマホ・衛星トランスポンダのほぼ全てに入っている、電波通信で最も実用的な信号処理の一つです。
DPDが効くと、PAをより飽和点の近く(高効率な動作点)で使えるようになります。つまり同じ電力を出すのに消費電力が減り、バッテリーが長持ちし、基地局の電気代が下がるという直接的な恩恵があります。本記事では、PAの非線形をメモリ多項式でモデル化し、その逆特性を「間接学習構造(indirect learning)」と最小二乗法で同定する理論を省略なく導出し、Pythonで高PAPR信号にAM/AM・AM/PM歪みを与え、DPD前後のスペクトルと星座図を比較してACLRが実際に何dB改善するかを測ります。

DPDの全体像はこの一枚に集約されます。入力信号 $x$ をまずプリディストータ $g=f^{-1}$ に通してあらかじめ逆向きにひずませ、その出力を非線形なPA $f$ に入れます。PAが大振幅をつぶす分をプリディストータが先回りで持ち上げているため、両者の歪みが打ち消し合い、カスケード全体としては理想的な線形利得 $Gx$ が得られる、という構図です。
本記事の内容
- なぜPAは非線形になるのか — AM/AM歪みとAM/PM歪みの直感
- メモリ多項式によるPAモデル $y[n] = \sum_k \sum_q a_{k,q}\, x[n-q]\,|x[n-q]|^k$ の導出
- DPDの基本原理(逆関数による事前ひずませ)
- 間接学習構造(indirect learning architecture)と最小二乗同定の導出
- PythonによるPAシミュレーション・DPD係数推定・ACLR/EVMの定量評価
前提知識
この記事を読む前に、以下の記事を読んでおくと理解が深まります。
これらの記事で「増幅器が飽和すると何が起きるか」「PAPRが高い信号がなぜ厄介か」を押さえておくと、DPDが解決しようとしている問題がよりはっきり見えます。
電力増幅器(PA)の非線形とは
理想的な増幅器は、入力 $x$ を一定倍 $G$ して $y = Gx$ を出すだけの「ただの掛け算」です。入力が2倍になれば出力も2倍、入力の位相が30度回れば出力の位相も同じだけ回る——これが線形です。ところが現実のトランジスタには電源電圧という上限があり、入力をどんどん大きくしても出力はある値以上には増えません。グラフにすると、小さい入力では直線だったものが、大きい入力で寝てくる(飽和する)曲線になります。
このとき2種類の歪みが現れます。直感的に言うと、増幅器が疲れてくると「声が大きくならない(振幅の歪み)」だけでなく「声のタイミングもずれる(位相の歪み)」のです。
- AM/AM歪み: 入力振幅 $r = |x|$ に対する出力振幅 $|y|$ の関係が直線からずれる。大振幅で利得が落ちる(利得圧縮)。
- AM/PM歪み: 入力振幅 $r$ が大きいほど、出力の位相が余計に回る。振幅の情報が位相に化けてしまう。
AM/AMだけなら「音量が頭打ち」で済みますが、AM/PMが加わると振幅と位相が混ざるため、復調側で解きほぐすのが難しくなります。DPDはこの両方を同時に補正する必要があります。
ここで重要なのが、なぜ振幅 $r=|x|$ の偶数乗だけが効くのか、という点です。RF信号は搬送波 $e^{j\omega_c t}$ に乗ったベースバンド複素包絡線 $x(t)$ で表せます。増幅器の非線形を $x$ の多項式 $c_1 x + c_2 x^2 + c_3 x^3 + \cdots$ で展開すると、搬送波周波数の近く(通過帯域)に戻ってくるのは奇数次の項だけです。$x = r e^{j\theta}$ と書くと、奇数次項 $x^{2m+1}$ は
$$ x^{2m+1} = r^{2m+1} e^{j(2m+1)\theta} = \underbrace{r^{2m}}_{\text{偶数乗}} \cdot \underbrace{r\, e^{j(2m+1)\theta}}_{\text{帯域内成分}} $$
のように書けます。$\theta$ の高調波 $(2m+1)\theta$ はフィルタで落ちますが、$\theta$ そのものの成分(基本波)が帯域内に残り、その係数として振幅の偶数乗 $r^{2m} = |x|^{2m}$ が掛かります。だからベースバンドで非線形を書くと $x \cdot |x|^{2m}$ という形になるのです。この形が次のメモリ多項式モデルの心臓部です。
増幅器が単純な「その瞬間の入力だけで出力が決まる(無記憶)」なら話は簡単です。しかし実際のPAには熱や電源回路、整合回路の周波数依存性などにより「少し前の入力にも依存する」というメモリ効果があります。次の節では、この無記憶非線形とメモリ効果をまとめて表現するメモリ多項式モデルを導きます。
メモリ多項式によるPAモデル
まず最も単純な「無記憶多項式モデル」から始めます。前節の議論から、ベースバンドのPA出力は奇数次の歪みだけで構成され、
$$ y[n] = \sum_{m=0}^{M} c_{2m+1}\, x[n]\, |x[n]|^{2m} = c_1 x[n] + c_3 x[n]|x[n]|^2 + c_5 x[n]|x[n]|^4 + \cdots $$
と書けます。ここで $x[n]$ は離散時刻 $n$ における複素ベースバンド入力、$y[n]$ は出力、$c_{2m+1}$ は複素係数です。係数が複素数であることがポイントで、その大きさがAM/AM(振幅圧縮)、偏角がAM/PM(位相回転)を表します。
このモデルは「今の瞬間の入力だけ」で出力が決まる無記憶モデルです。メモリ効果を取り込むには、過去の入力 $x[n-q]$ も使えるよう拡張します。各遅延 $q$ について非線形多項式を用意し、それらを足し合わせるとメモリ多項式(Memory Polynomial, MP)モデルになります。
$$ \begin{equation} y[n] = \sum_{q=0}^{Q} \sum_{k=0}^{K-1} a_{k,q}\, x[n-q]\, |x[n-q]|^{k} \end{equation} $$
ここで $Q$ はメモリの深さ(何サンプル前まで見るか)、$K$ は非線形の次数(基底の本数)、$a_{k,q}$ は複素係数です。本記事では実装の都合上、$k$ を $0$ から $K-1$ まで連続的に動かす一般形で書いています(奇数次に限定したい場合は $k$ を偶数だけにすればよく、両者は表現力の上で大差ありません)。$Q=0$ とすれば無記憶多項式に戻ります。
このモデルの優れた点は、係数 $a_{k,q}$ について線形であることです。出力 $y[n]$ を係数のベクトルと「基底」のベクトルの内積として書けます。基底関数を
$$ \phi_{k,q}[n] = x[n-q]\, |x[n-q]|^{k} $$
と定義すると、メモリ多項式は
$$ y[n] = \sum_{q=0}^{Q}\sum_{k=0}^{K-1} a_{k,q}\, \phi_{k,q}[n] = \bm{\phi}[n]^{\mathsf{T}} \bm{a} $$
と内積一つで書けます。$\bm{\phi}[n]$ は基底を並べた $(Q+1)K$ 次元のベクトル、$\bm{a}$ は係数を並べたベクトルです。基底 $\phi_{k,q}[n]$ 自体は $x$ の非線形関数(だから歪みを表せる)ですが、未知数 $\bm{a}$ から見れば一次式です。この「非線形だが係数については線形」という性質が、次に述べる最小二乗法による同定を可能にします。
なぜこの線形性がそれほどありがたいのでしょうか。係数について線形なら、観測したデータを並べた行列方程式を立て、最小二乗法という閉じた式(行列演算一発)で最適な係数が求まるからです。反復的な非線形最適化に頼らずに済むため、リアルタイムで係数を更新する実装が現実的になります。では、このモデルを使ってDPDをどう構成するのかを次に見ていきます。
DPDの基本原理 — 逆関数で事前にひずませる
DPDの目標を一言で言えば、「PAの前にもう一つの非線形ブロック(プリディストータ)を置き、その縦続接続(カスケード)全体を線形にする」ことです。プリディストータの関数を $g(\cdot)$、PAの関数を $f(\cdot)$ とすると、入力 $x$ に対して送りたい理想出力は $G x$($G$ は目標とする線形利得)です。したがって
$$ f\bigl(g(x)\bigr) = G\, x $$
が成り立つように $g$ を設計します。両辺に $f$ の逆を作用させると
$$ g(x) = f^{-1}(G\, x) $$
つまりプリディストータはPAの逆関数(を利得 $G$ でスケールしたもの)です。PAが大振幅で利得を落とすなら、プリディストータは大振幅であらかじめ利得を持ち上げておく。PAが大振幅で位相を進めるなら、プリディストータは逆に位相を遅らせておく。PAを通った後にちょうど打ち消し合い、まっすぐな直線特性が残る、という仕組みです。
ここで一つ、物理的にとても大切な制約があります。逆関数が存在するのは、PAのAM/AM特性が単調(振幅について一対一)である範囲だけです。PAが完全に飽和して「入力をいくら増やしても出力が増えない」領域に入ると、その出力からは元の入力を一意に決められません。逆関数が定義できないのです。したがってDPDは「PAをある程度飽和に近づけて効率を稼ぎつつ、完全な飽和(クリッピング)は避ける」動作点で使います。後述のシミュレーションで、入力バックオフ(IBO)というパラメータでこの動作点を調整します。
問題は、$g(x) = f^{-1}(Gx)$ の逆関数を解析的に書き下すのが難しいことです。PAの正確な数式モデルを知っていても、その逆を閉じた形で求めるのは一般に困難で、ましてや実機のPAでは $f$ そのものが未知です。そこで「逆関数を直接解く」のではなく、「逆関数をメモリ多項式で近似し、その係数をデータから推定する」という方針を取ります。この推定を巧妙に行うのが、次に説明する間接学習構造です。
間接学習構造(indirect learning)と係数の最小二乗同定
逆関数 $f^{-1}$ をデータから学ぶには、ふつう「入力 $x$ と出力 $y=f(x)$ のペアを集めて、$y$ から $x$ を当てる関数を学習する」のが自然です。これがまさに間接学習構造(Indirect Learning Architecture, ILA)の核心です。図の流れを言葉で追ってみましょう。
PAに何らかの信号 $u$ を入れて出力 $z = f(u)$ を観測します。このとき、もし「$z$(を利得 $G$ で割ったもの)を入力すると $u$ を出す」ブロックが作れたら、それはまさに $f$ の逆 $f^{-1}$ です。このブロックをポストディストータ(post-inverse, 後置逆フィルタ)と呼びます。ポストディストータをメモリ多項式で表し、その係数を最小二乗で求めます。そして学習し終えたポストディストータの係数を、そっくりそのまま前段のプリディストータにコピーして使う——これがILAの巧妙なところです。後ろで学んだ逆特性を前に持ってくるわけです。
なぜこれで正しい逆関数が得られるのでしょうか。ポストディストータ $W$ が $W(f(u)/G) = u$ を満たすなら、$W = f^{-1}(G\,\cdot)$、つまり $W$ は求めたいプリディストータ $g$ そのものです。$W$ を前段に置けば $f(W(x)) = f(g(x)) = Gx$ となり、カスケードが線形化されます。後置で学んだものが前置でも使える、というこの対称性が間接学習の理論的根拠です。
最小二乗問題の定式化
それでは係数 $\bm{a}$ を求める最小二乗問題を導出します。ポストディストータの入力を $z[n] = y[n]/G$(PA出力を目標利得で割って正規化したもの)、出力の目標を $x[n]$(PAに入れた元信号)とします。ポストディストータをメモリ多項式で書くと
$$ \hat{x}[n] = \sum_{q=0}^{Q}\sum_{k=0}^{K-1} a_{k,q}\, z[n-q]\,|z[n-q]|^{k} $$
です。これが目標 $x[n]$ にできるだけ近づくよう、二乗誤差
$$ J(\bm{a}) = \sum_{n} \bigl|\, x[n] – \hat{x}[n]\,\bigr|^2 = \sum_n \Bigl|\, x[n] – \bm{\psi}[n]^{\mathsf{T}}\bm{a}\,\Bigr|^2 $$
を最小化します。ここで $\bm{\psi}[n]$ は $z$ から作った基底ベクトル、その第 $(k,q)$ 成分は $\psi_{k,q}[n] = z[n-q]|z[n-q]|^{k}$ です。$N$ サンプル分の基底を縦に積んで行列 $\bm{\Psi}$($N \times (Q+1)K$)、目標を縦ベクトル $\bm{x}$ にまとめると、誤差は行列形式で
$$ J(\bm{a}) = \| \bm{x} – \bm{\Psi}\bm{a} \|^2 $$
と書けます。これは典型的な線形最小二乗問題です。
正規方程式の導出
$J(\bm{a})$ を最小にする $\bm{a}$ を求めます。複素数の最小二乗では、$\bm{a}$ の複素共役で偏微分して0と置く(ウィルティンガー微分)のが定石です。$J = (\bm{x}-\bm{\Psi}\bm{a})^{\mathsf{H}}(\bm{x}-\bm{\Psi}\bm{a})$ を展開すると
$$ J = \bm{x}^{\mathsf{H}}\bm{x} – \bm{x}^{\mathsf{H}}\bm{\Psi}\bm{a} – \bm{a}^{\mathsf{H}}\bm{\Psi}^{\mathsf{H}}\bm{x} + \bm{a}^{\mathsf{H}}\bm{\Psi}^{\mathsf{H}}\bm{\Psi}\bm{a} $$
となります($\mathsf{H}$ は共役転置)。ここで $\bm{a}^{\mathsf{H}}$ について微分します。第1項は $\bm{a}$ を含まないので消え、第2項も $\bm{a}^{\mathsf{H}}$ を含まないので消えます。残る第3項と第4項を $\bm{a}^{\mathsf{H}}$ で微分すると、それぞれ $-\bm{\Psi}^{\mathsf{H}}\bm{x}$ と $\bm{\Psi}^{\mathsf{H}}\bm{\Psi}\bm{a}$ になります。これを0と置くと
$$ \frac{\partial J}{\partial \bm{a}^{\mathsf{H}}} = -\bm{\Psi}^{\mathsf{H}}\bm{x} + \bm{\Psi}^{\mathsf{H}}\bm{\Psi}\,\bm{a} = \bm{0} $$
整理すると正規方程式が得られます。
$$ \bm{\Psi}^{\mathsf{H}}\bm{\Psi}\,\bm{a} = \bm{\Psi}^{\mathsf{H}}\bm{x} $$
$\bm{\Psi}^{\mathsf{H}}\bm{\Psi}$ が正則なら、両辺に逆行列を掛けて係数の解析解が
$$ \begin{equation} \hat{\bm{a}} = \bigl(\bm{\Psi}^{\mathsf{H}}\bm{\Psi}\bigr)^{-1}\bm{\Psi}^{\mathsf{H}}\bm{x} \end{equation} $$
と求まります。右辺の $(\bm{\Psi}^{\mathsf{H}}\bm{\Psi})^{-1}\bm{\Psi}^{\mathsf{H}}$ は疑似逆行列(ムーア・ペンローズの一般化逆行列)で、実装では数値的に安定な numpy.linalg.lstsq(特異値分解ベース)を使います。反復計算なしに、行列演算一発で逆特性の係数が決まるわけです。これがメモリ多項式の「係数について線形」という性質の恩恵です。
反復による精度向上(ダンピング付き)
理屈の上では一回の最小二乗で逆特性が求まりますが、実際にはポストディストータを学習したときの信号 $z=y/G$ の振幅分布と、プリディストータが処理する信号 $x$ の振幅分布が少しずれるため、一回では完全には線形化されません。そこで、求めた係数で一度プリディストートした信号 $x_{\text{pd}}$ を実際にPAへ通し、その出力で再び係数を学習し直す——という反復で精度を上げます。
ただし高次の多項式は外挿に弱く、反復がそのままだと振動・発散することがあります。そこで更新を急激にせず、新しい解 $x_{\text{pd}}^{\text{new}}$ と前の解を混ぜるダンピング(緩和)
$$ x_{\text{pd}} \leftarrow (1-\mu)\, x_{\text{pd}} + \mu\, x_{\text{pd}}^{\text{new}}, \quad 0 < \mu \le 1 $$
を入れると安定して収束します。$\mu$ は学習率のようなもので、本記事では $\mu=0.4$ を使います。理論はここまでです。次は実際にPAを模擬し、DPDの係数を推定して、ACLRとEVMがどれだけ改善するかをPythonで測りましょう。
Pythonでの実装
1. 高PAPR信号の生成
まず、PAに通す信号を作ります。OFDMや多値変調された通信信号は、多数の成分が重なり合って瞬間的に大きなピークを持つ「高PAPR(Peak-to-Average Power Ratio)」信号です。ここではルートレイズドコサイン(RRC)フィルタで帯域制限したQPSK信号を使います。帯域がきれいに制限されているので、PAの歪みによる帯域外漏れ(スペクトル再成長)を明瞭に観測できます。
import numpy as np
import matplotlib, matplotlib.pyplot as plt
# 日本語フォント設定
for cand in ["Hiragino Sans", "Yu Gothic", "Noto Sans CJK JP", "IPAexGothic", "Meiryo"]:
if any(cand == f.name for f in matplotlib.font_manager.fontManager.ttflist):
plt.rcParams["font.family"] = cand
break
plt.rcParams["axes.unicode_minus"] = False
rng = np.random.default_rng(0)
def rrc(beta, sps, span):
"""ルートレイズドコサイン(RRC)フィルタ係数"""
N = span * sps
t = (np.arange(N + 1) - N / 2) / sps
h = np.zeros_like(t)
for i, ti in enumerate(t):
if abs(ti) < 1e-8:
h[i] = 1 - beta + 4 * beta / np.pi
elif beta > 0 and abs(abs(ti) - 1 / (4 * beta)) < 1e-8:
h[i] = (beta / np.sqrt(2)) * ((1 + 2 / np.pi) * np.sin(np.pi / (4 * beta))
+ (1 - 2 / np.pi) * np.cos(np.pi / (4 * beta)))
else:
h[i] = (np.sin(np.pi * ti * (1 - beta)) + 4 * beta * ti * np.cos(np.pi * ti * (1 + beta))) \
/ (np.pi * ti * (1 - (4 * beta * ti) ** 2))
return h / np.sqrt(np.sum(h ** 2))
sps, beta, Nsym = 8, 0.22, 4000 # 1シンボル8サンプル, ロールオフ0.22
syms = np.exp(1j * (np.pi / 4 + np.pi / 2 * rng.integers(0, 4, Nsym))) # QPSK
up = np.zeros(Nsym * sps, complex); up[::sps] = syms
x = np.convolve(up, rrc(beta, sps, 10), 'same')
x /= np.sqrt(np.mean(np.abs(x) ** 2)) # 平均電力を1に正規化
papr_db = 10 * np.log10(np.max(np.abs(x) ** 2) / np.mean(np.abs(x) ** 2))
print(f"信号のPAPR = {papr_db:.2f} dB")
このコードはQPSKシンボルをRRCフィルタで帯域制限した信号を生成します。出力されるPAPRは約 5.17 dB で、平均電力の3倍を超えるピークが時折現れることを意味します。このピークがPAの飽和領域に突っ込むと歪みが発生します。帯域制限された信号なので、後でスペクトルを見たときに通過帯域と隣接帯域がはっきり分かれ、漏洩の度合いが読み取りやすくなります。

左の図は実際に生成したPA入力の瞬時振幅で、実効値(RMS、緑破線)に対して時折その2倍以上に跳ね上がるピーク(赤破線、最大約1.28)が立っていることがわかります。右のヒストグラムを見ると振幅は0付近から高振幅側へなだらかに裾を引いており、RMSとピークの差がそのままPAPR約5.17 dBに対応します。この稀に現れる大振幅ピークこそがPAを飽和に追い込み、歪みの主因になります。
2. メモリ多項式PAモデルの実装
次に、AM/AM・AM/PM歪みとメモリ効果を持つPAを、メモリ多項式で実装します。係数は「小信号で利得1、振幅が大きいほど利得圧縮(3次・5次の負の項)、振幅依存の位相回転(虚部の項)、1サンプル前の入力への依存(メモリ項)」を表すように設定しています。
def pa_model(u):
"""メモリ多項式によるPAモデル(AM/AM圧縮 + AM/PM + メモリ効果)"""
u1 = np.r_[0, u[:-1]] # 1サンプル前の入力 x[n-1]
return ( u # 線形項 (利得1)
- 0.12 * u * np.abs(u) ** 2 # 3次の利得圧縮 (AM/AM)
+ 0.015 * u * np.abs(u) ** 4 # 5次の補正
- 0.03 * u1 * np.abs(u1) ** 2 # メモリ効果(1サンプル遅延)
+ 0.05j * u * np.abs(u) ** 2 ) # AM/PM(振幅依存の位相回転)
# 入力バックオフ(IBO): 平均電力を飽和点からどれだけ下げて使うか
ibo_db = 3.0
scale = 10 ** (-ibo_db / 20)
x_in = x * scale # PAへの入力
y = pa_model(x_in) # DPDなしのPA出力
print(f"PA入力の最大振幅 |x_in| = {np.abs(x_in).max():.3f}")
入力バックオフ(IBO)を3 dBに設定しています。IBOが小さいほどPAを飽和近くで使う(高効率だが歪む)ことを意味します。出力されるPA入力の最大振幅は約 1.28 で、AM/AM特性が単調(逆関数が存在する)な範囲に収まりつつ、利得圧縮がしっかり効く動作点です。この「飽和の手前ぎりぎりで使う」設定こそ、DPDが活躍する典型的な場面です。
3. PAの特性曲線(AM/AM・AM/PM)を可視化
DPDの議論に入る前に、いま作ったPAがどれくらい歪むのかを目で確かめます。入力振幅 $r=|x|$ を横軸に、出力振幅と位相回転量を縦軸にプロットします。
r = np.linspace(0, 1.6, 400)
test = r.astype(complex) # 位相0の純振幅入力
out = pa_model(test)
amam = np.abs(out) # 出力振幅
ampm = np.angle(out / (test + 1e-12)) * 180 / np.pi # 位相回転[度]
fig, ax = plt.subplots(1, 2, figsize=(11, 4))
ax[0].plot(r, amam, 'b-', lw=2, label="PAの出力振幅")
ax[0].plot(r, r, 'k--', lw=1, label="理想(線形)")
ax[0].set_xlabel("入力振幅 $|x|$"); ax[0].set_ylabel("出力振幅 $|y|$")
ax[0].set_title("AM/AM特性(振幅の歪み)"); ax[0].legend(); ax[0].grid(alpha=0.3)
ax[1].plot(r, ampm, 'r-', lw=2)
ax[1].set_xlabel("入力振幅 $|x|$"); ax[1].set_ylabel("位相回転 [度]")
ax[1].set_title("AM/PM特性(位相の歪み)"); ax[1].grid(alpha=0.3)
plt.tight_layout(); plt.show()

左の図(AM/AM)を見ると、入力振幅が小さいうちは出力が理想直線(黒破線)に重なっていますが、振幅0.8あたりから直線より下に垂れ下がります。これが利得圧縮です。右の図(AM/PM)では、入力振幅が大きいほど位相が正方向に回っており(振幅1.6で約10度)、振幅情報が位相に漏れていることがわかります。DPDはこの2本の曲線を、ちょうど逆向きの特性で打ち消すのが仕事です。
4. 間接学習構造によるDPD係数の同定
いよいよ本丸です。メモリ多項式の基底を作る関数を用意し、間接学習構造で逆特性を最小二乗推定します。前述の通り、PA出力 $z=y/G$ を入力・元信号 $x_{\text{in}}$ を目標として係数を求め、それをプリディストータとして使います。ダンピング付きの反復で精度を上げます。
def build_basis(u, K, Q):
"""メモリ多項式の基底行列 Psi を作る (列: phi_{k,q} = u[n-q]|u[n-q]|^k)"""
cols = []
for q in range(Q + 1):
uq = np.r_[np.zeros(q, complex), u[:len(u) - q]] if q > 0 else u
for k in range(K):
cols.append(uq * np.abs(uq) ** k)
return np.column_stack(cols) # 形状 (N, (Q+1)*K)
K, Q = 7, 1 # 非線形次数K=7, メモリ深さQ=1 → 係数14個
G = 1.0 # 目標線形利得(小信号利得)
mu = 0.4 # ダンピング係数
x_pd = x_in.copy() # プリディストート信号(初期値はそのまま)
for it in range(12):
z = pa_model(x_pd) / G # PA出力を観測し正規化
Psi = build_basis(z, K, Q) # ポストディストータの基底
a_hat, *_ = np.linalg.lstsq(Psi, x_pd, rcond=None) # 正規方程式の解(疑似逆)
x_new = build_basis(x_in, K, Q) @ a_hat # 学習した逆特性で元信号を事前歪ませ
x_pd = (1 - mu) * x_pd + mu * x_new # ダンピング更新
y_dpd = pa_model(x_pd) # DPDありのPA出力
print(f"推定した係数の数: {(Q + 1) * K}")
print(f"プリディストート信号の最大振幅: {np.abs(x_pd).max():.3f}")
非線形次数 $K=7$、メモリ深さ $Q=1$ としたので係数は14個です。学習後のプリディストート信号の最大振幅は約 1.82 に増えています。これはDPDが意図的に大振幅を持ち上げて(プリエンファシスして)、PAの圧縮を先回りで相殺していることを示します。係数がわずか14個の閉形式解で、PAの非線形が補正できてしまうのがメモリ多項式とILAの強みです。

学習した係数が本当に「PAの逆特性」になっているかを2枚で確かめます。左の図はプリディストータ単体のAM/AM特性で、入力(黒破線)に対して大振幅側で出力を持ち上げており、図3左でPAが垂れ下がっていたのとちょうど逆向きの曲線になっています。右の図は実測したカスケード(プリディストータ→PA)の特性で、DPDなし(橙)が理想直線から大きく外れるのに対し、DPDあり(青)は理想直線(黒破線)にほぼ完全に重なっています。逆特性が正しく同定され、非線形がきれいに相殺されたことが目で確認できます。
5. スペクトルとACLRの比較
DPDの効果を最も雄弁に語るのがスペクトルです。DPDなしの出力 $y$ とDPDありの出力 $y_{\text{dpd}}$ のパワースペクトル密度(PSD)を重ねて描き、隣接チャネル漏洩(ACLR)を計算します。ACLRは「隣接帯域の電力 / 通過帯域の電力」で、小さい(負で絶対値が大きい)ほど良い指標です。
def psd_db(s):
"""正規化パワースペクトル密度 [dB]"""
S = np.fft.fftshift(np.fft.fft(s))
P = np.abs(S) ** 2
return 10 * np.log10(P / P.max() + 1e-12)
def aclr_db(s):
"""ACLR [dB] = 隣接帯域電力 / 通過帯域電力"""
f = np.fft.fftshift(np.fft.fftfreq(len(s)))
P = np.abs(np.fft.fftshift(np.fft.fft(s))) ** 2
B = (1 + beta) / sps # 占有帯域幅(正規化)
main = np.abs(f) <= B / 2 # 通過帯域
adjR = (f > B / 2) & (f <= 3 * B / 2) # 右隣接帯域
adjL = (f < -B / 2) & (f >= -3 * B / 2) # 左隣接帯域
return 10 * np.log10((P[adjR].sum() + P[adjL].sum()) / 2 / P[main].sum())
f = np.fft.fftshift(np.fft.fftfreq(len(y)))
plt.figure(figsize=(9, 5))
plt.plot(f, psd_db(y), 'r-', lw=1, alpha=0.8, label=f"DPDなし (ACLR={aclr_db(y):.1f} dB)")
plt.plot(f, psd_db(y_dpd), 'b-', lw=1, alpha=0.8, label=f"DPDあり (ACLR={aclr_db(y_dpd):.1f} dB)")
plt.xlabel("正規化周波数"); plt.ylabel("正規化PSD [dB]")
plt.title("DPD前後のスペクトル(隣接チャネル漏洩の比較)")
plt.ylim(-70, 2); plt.xlim(-0.4, 0.4); plt.legend(); plt.grid(alpha=0.3)
plt.tight_layout(); plt.show()
print(f"ACLR (DPDなし) = {aclr_db(y):.2f} dB")
print(f"ACLR (DPDあり) = {aclr_db(y_dpd):.2f} dB")
print(f"ACLR改善量 = {aclr_db(y) - aclr_db(y_dpd):.2f} dB")
実行すると、ACLRは DPDなしで約 -34.3 dB、DPDありで約 -40.4 dB となり、約6.1 dBの改善が得られます。

スペクトル図では、DPDなし(赤)で通過帯域の両脇(灰色で示した隣接帯域)に盛り上がっていた「肩(スペクトル再成長)」が、DPDあり(青)で明確に押し下げられているのが見て取れます。この肩こそがPAの非線形が生み出した帯域外漏洩で、DPDがそれを約6 dB(電力比でおよそ4分の1)に抑えたわけです。基地局の出力規格を満たすうえで、この6 dBは死活的に重要です。
DPDの効果は1回の最小二乗で頭打ちになるのではなく、間接学習の反復で段階的に改善します。次の図は反復回数に対するACLRとEVMの実測推移です(反復0がDPDなしの状態)。

反復0(DPDなし)では ACLR が約 -34 dB、EVM が約3.3%ですが、わずか1〜2回の反復で ACLR は -40 dB 付近まで急降下し、EVM は対数軸で見ると桁違いに小さくなります。3回目以降はほぼ平坦で、ダンピング付き反復が振動せず安定して収束していることが読み取れます。実装上もごく少ない反復で十分な性能が出ることを示しています。
6. 星座図(コンスタレーション)とEVMの比較
最後に、自分自身の信号の品質がどれだけ改善したかを星座図とEVMで確認します。EVMは送信シンボルが理想点からどれだけずれているかを表す指標で、小さいほど良好です。
def evm_pct(out, ref):
"""EVM [%] = 残差の二乗平均 / 信号電力 の平方根"""
g = np.vdot(ref, out) / np.vdot(ref, ref) # 最適な利得補正
return 100 * np.sqrt(np.mean(np.abs(out - g * ref) ** 2) / np.mean(np.abs(g * ref) ** 2))
# シンボル点(RRCマッチドフィルタ後のサンプリング点)を抜き出して描画
def symbols(s):
mf = np.convolve(s, rrc(beta, sps, 10), 'same') # マッチドフィルタ
return mf[::sps]
fig, ax = plt.subplots(1, 2, figsize=(10, 5))
for a, sig, ttl in [(ax[0], y, "DPDなし"), (ax[1], y_dpd, "DPDあり")]:
pts = symbols(sig)
pts = pts / np.sqrt(np.mean(np.abs(pts) ** 2)) * np.sqrt(2) # 正規化
a.scatter(pts.real, pts.imag, s=3, alpha=0.3, color='b')
a.scatter([1, -1, 1, -1], [1, 1, -1, -1], marker='+', s=200, color='r')
a.set_title(f"{ttl} (EVM={evm_pct(y if ttl=='DPDなし' else y_dpd, x_in):.2f} %)")
a.set_xlabel("I"); a.set_ylabel("Q"); a.grid(alpha=0.3); a.set_aspect('equal')
plt.tight_layout(); plt.show()
print(f"EVM (DPDなし) = {evm_pct(y, x_in):.2f} %")
print(f"EVM (DPDあり) = {evm_pct(y_dpd, x_in):.2f} %")

EVMは DPDなしで約 3.32%、DPDありで約 0.09% へと劇的に改善します。星座図(左:DPDなし)では4つの理想点(赤い+)の周りにシンボルが雲のように広がってにじんでいますが、DPDあり(右)では各点にほぼ一点に収束しています。これは非線形歪みによる「自己干渉」が取り除かれた結果で、復調誤り率(BER)の改善に直結します。

ACLRとEVMの改善を並べた棒グラフです。左はACLRが -34.3 dB から -40.4 dB へ約6.1 dB下がり(漏洩電力が約4分の1)、右はEVMが3.32%から0.09%へとほぼ消えるまで低減しています。ACLR(他人への迷惑)とEVM(自分の品質)の両方が同時に改善するのが、DPDの大きな利点です。
7. 入力バックオフを変えたときのDPD効果
最後に、PAをどれくらい飽和近くで使うか(IBO)を変えたときに、DPDの効果がどう変わるかを見ます。これはDPDの「適用限界」を理解する重要な実験です。
results = []
for ibo in [2.0, 3.0, 4.0, 5.0]:
xi = x * 10 ** (-ibo / 20)
yn = pa_model(xi)
xp = xi.copy()
for _ in range(12):
Psi = build_basis(pa_model(xp) / G, K, Q)
a, *_ = np.linalg.lstsq(Psi, xp, rcond=None)
xp = 0.6 * xp + 0.4 * (build_basis(xi, K, Q) @ a)
results.append((ibo, aclr_db(yn), aclr_db(pa_model(xp))))
ibos = [r[0] for r in results]
plt.figure(figsize=(8, 5))
plt.plot(ibos, [r[1] for r in results], 'ro-', lw=2, label="DPDなし")
plt.plot(ibos, [r[2] for r in results], 'bo-', lw=2, label="DPDあり")
plt.xlabel("入力バックオフ IBO [dB]"); plt.ylabel("ACLR [dB]")
plt.title("バックオフとACLRの関係(DPD前後)")
plt.gca().invert_yaxis(); plt.legend(); plt.grid(alpha=0.3)
plt.tight_layout(); plt.show()
for ibo, a0, a1 in results:
print(f"IBO={ibo} dB: ACLRなし={a0:.1f} dB, ありで{a1:.1f} dB (改善{a0-a1:.1f} dB)")

このグラフから重要な傾向が読み取れます。第一に、DPDなし(橙)はバックオフを小さくする(飽和に近づける)ほどACLRが急速に悪化します。第二に、DPDあり(青)はバックオフによらずほぼ一定の良好なACLR(約 -40 dB)を保ちます。図中の数値は各点の改善量で、IBO=2 dBでは約7.6 dB、IBO=5 dBでは約3.5 dBと、PAを飽和近くで酷使するほどDPDの恩恵が大きくなります。つまりDPDは「PAをより飽和近く(高効率)で使えるようにする」技術である、という冒頭の主張がここで定量的に裏付けられます。ただしIBOをさらに下げてピークが完全飽和領域に入ると、逆関数が存在しなくなりDPDも破綻します。この限界の存在を理解しておくことが実務では重要です。
8. モデル容量(次数K・メモリ深さQ)とDPD性能
最後に、メモリ多項式の表現力をどこまで上げれば十分なのかを実験で確かめます。非線形次数 $K$(基底の本数)とメモリ深さ $Q$(何サンプル前まで見るか)を変えながらDPD後のACLRを測ります。
Ks, Qs = [1, 3, 5, 7, 9], [0, 1, 2]
for Qv in Qs:
row = []
for Kv in Ks:
xp = x_in.copy()
for _ in range(12):
Psi = build_basis(pa_model(xp) / G, Kv, Qv)
a, *_ = np.linalg.lstsq(Psi, xp, rcond=None)
xp = 0.6 * xp + 0.4 * (build_basis(x_in, Kv, Qv) @ a)
row.append(aclr_db(pa_model(xp)))
print(f"Q={Qv}: " + ", ".join(f"K={k}:{a:.1f}dB" for k, a in zip(Ks, row)))

このグラフから2つのことが読み取れます。第一に、$K=1$(線形のみ)では非線形を補正できず、DPDなし(橙破線、約 -34 dB)からほとんど改善しませんが、$K=3$ に上げた途端 -39 dB 以下へ一気に改善し、$K=5$ 以降は飽和してそれ以上増やしても効果が頭打ちになります。第二に、メモリ効果を取り込む $Q=1$ は $Q=0$(無記憶)より約0.8 dB良く、PAのメモリ効果を補正できていることがわかります。$Q=2$ はほぼ $Q=1$ と同じで、このPAのメモリ深さは1サンプルで十分捉えられることを示しています。むやみに次数を上げても効果はなく、PAの非線形に見合った最小限のモデルを選べばよい、という実務上の指針が得られます。
まとめ
本記事では、電力増幅器(PA)の非線形歪みを補正するデジタルプリディストーション(DPD)について、理論の導出から実装・評価まで一貫して解説しました。
- PAの非線形: 飽和によるAM/AM歪み(利得圧縮)とAM/PM歪み(振幅依存の位相回転)が、帯域外漏洩(ACLR悪化)と星座点のにじみ(EVM悪化)を引き起こす
- メモリ多項式モデル: $y[n] = \sum_q \sum_k a_{k,q}\, x[n-q]\,|x[n-q]|^k$。奇数次非線形がベースバンドで $x|x|^{k}$ の形になること、メモリ効果を遅延項で取り込むこと、そして係数について線形であることがポイント
- DPDの原理: PAの逆関数 $g(x)=f^{-1}(Gx)$ を前段に置き、カスケード $f(g(x))=Gx$ を線形化する。逆関数が存在するのはAM/AMが単調な範囲だけ
- 間接学習構造(ILA): PA出力からPA入力を当てるポストディストータを最小二乗で同定し、その係数をプリディストータに流用する。正規方程式 $\bm{\Psi}^{\mathsf{H}}\bm{\Psi}\,\bm{a}=\bm{\Psi}^{\mathsf{H}}\bm{x}$ の閉形式解で係数が一発で求まる
- 実測効果: IBO=3 dBでACLRが -34.3 dB から -40.4 dB へ約6.1 dB改善、EVMが 3.32% から 0.09% へ改善。バックオフを小さくする(高効率にする)ほどDPDの恩恵が大きい
DPDは「PAの非線形をモデル化し、その逆をデータから学んで打ち消す」という、システム同定と適応信号処理の美しい応用例です。本記事の最小二乗+間接学習が基本形で、ここから一般化メモリ多項式(GMP)やニューラルネットワークDPDへと発展していきます。
次のステップとして、以下の記事も参考にしてください。