[PHP]tanh関数で双曲線正接(ハイパボリックタンジェント)を計算する方法を解説

PHP

tanh() という関数名を見て「tan() のタイプミスかな?」と思った方もいるかもしれません。実はこれは全く別の数学関数で、双曲線関数(hyperbolic function) というグループに属する「ハイパボリックタンジェント(双曲線正接)」を計算するものです。

直接使う機会は多くありませんが、機械学習の活性化関数、信号処理、物理シミュレーションなど、特定の分野では非常に重要な役割を果たします。この記事では tanh() の基本仕様から、実践的な活用例、そして紛らわしい tan() との違いまでわかりやすく解説します。

関数概要

項目内容
関数名tanh()
読み方ハイパボリックタンジェント、ティーエイチタン
分類数学関数(双曲線関数)
対応バージョンPHP 4以降(全バージョンで使用可能)
引数任意の実数(float)
戻り値float(-1から1の範囲)
名前空間グローバル関数
関連拡張モジュール標準で組み込み(拡張モジュール不要)

構文

tanh(float $num): float

tanh() は数値を1つ受け取り、その双曲線正接の値を返します。tan() と違って引数は「角度(ラジアン)」ではなく、任意の実数を渡します。これは三角関数(円関数)とは異なる性質の関数だからです。

数式で表すと以下のようになります。

tanh(x) = sinh(x) / cosh(x) = (e^x - e^-x) / (e^x + e^-x)

tanh関数の最大の特徴:出力が必ず-1〜1の範囲に収まる

入力 x(任意の実数、-∞ ~ +∞)
        │
        │  tanh()
        ▼
出力(必ず -1 ~ 1 の範囲に収まる)

    x → -∞ のとき  tanh(x) → -1
    x = 0   のとき  tanh(x) =  0
    x → +∞ のとき  tanh(x) → +1

この「どんな大きな入力でも、出力が必ず-1から1の間に収まる」という性質こそが、tanh() が実務で使われる最大の理由です。

基本的な使い方

<?php
echo tanh(0) . PHP_EOL;
echo tanh(1) . PHP_EOL;
echo tanh(-1) . PHP_EOL;
echo tanh(10) . PHP_EOL;
echo tanh(-10) . PHP_EOL;

実行結果:

0
0.76159415595576
-0.76159415595576
0.9999999958777
-0.9999999958777

入力が 10 のような大きな値でも、出力は 1 にかなり近づくものの決して 1 を超えない、という性質が確認できます。

実践的なコード例

例1:任意の数値を-1〜1の範囲に正規化する

<?php
class ValueNormalizer
{
    /**
     * どんな範囲の数値でも-1〜1の範囲に収める
     * 値が大きいほど1に近づき、小さい(負の大きい)ほど-1に近づく
     */
    public function normalize(float $value, float $scale = 1.0): float
    {
        return tanh($value / $scale);
    }
}

$normalizer = new ValueNormalizer();

foreach ([-100, -10, -1, 0, 1, 10, 100] as $value) {
    $result = $normalizer->normalize($value, 10);
    printf("入力: %6d → 正規化後: %.4f\n", $value, $result);
}

実行結果:

入力:   -100 → 正規化後: -1.0000
入力:    -10 → 正規化後: -0.7616
入力:     -1 → 正規化後: -0.0997
入力:      0 → 正規化後: 0.0000
入力:      1 → 正規化後: 0.0997
入力:     10 → 正規化後: 0.7616
入力:    100 → 正規化後: 1.0000

異常値(極端に大きい値)が含まれていても、出力が暴れずに一定の範囲に収まるため、グラフ描画やスコアの可視化などで「外れ値を緩やかに圧縮する」用途に向いています。

例2:ニューラルネットワークの活性化関数として使う

<?php
class Neuron
{
    private array $weights;
    private float $bias;

    public function __construct(array $weights, float $bias)
    {
        $this->weights = $weights;
        $this->bias = $bias;
    }

    public function activate(array $inputs): float
    {
        $sum = $this->bias;
        foreach ($inputs as $i => $input) {
            $sum += $input * $this->weights[$i];
        }

        // tanhを活性化関数として使用
        return tanh($sum);
    }
}

$neuron = new Neuron([0.5, -0.3, 0.8], 0.1);

$output1 = $neuron->activate([1.0, 2.0, 0.5]);
$output2 = $neuron->activate([-1.0, -2.0, -0.5]);

printf("出力1: %.4f\n", $output1);
printf("出力2: %.4f\n", $output2);

実行結果:

出力1: 0.5370
出力2: -0.6044

tanh() はシグモイド関数と並んで、ニューラルネットワークの活性化関数としてよく使われます。出力が0を中心に対称(-1〜1)であるため、シグモイド関数(0〜1)よりも学習が安定しやすいという特徴があります。

例3:感情スコアやレーティングの圧縮表示

<?php
class SentimentScoreCompressor
{
    /**
     * 生の感情スコア(無限に大きくなりうる)を
     * 見やすい-1.0〜1.0の表示用スコアに変換する
     */
    public function compress(float $rawScore, float $sensitivity = 0.1): float
    {
        return tanh($rawScore * $sensitivity);
    }

    public function toLabel(float $compressedScore): string
    {
        if ($compressedScore > 0.5) {
            return '非常にポジティブ';
        }
        if ($compressedScore > 0.1) {
            return 'ポジティブ';
        }
        if ($compressedScore < -0.5) {
            return '非常にネガティブ';
        }
        if ($compressedScore < -0.1) {
            return 'ネガティブ';
        }
        return '中立';
    }
}

$compressor = new SentimentScoreCompressor();

foreach ([2, 8, 0, -3, -15] as $rawScore) {
    $compressed = $compressor->compress($rawScore);
    $label = $compressor->toLabel($compressed);
    printf("生スコア: %4d → 圧縮後: %.3f(%s)\n", $rawScore, $compressed, $label);
}

実行結果:

生スコア:    2 → 圧縮後: 0.197(ポジティブ)
生スコア:    8 → 圧縮後: 0.664(非常にポジティブ)
生スコア:    0 → 圧縮後: 0.000(中立)
生スコア:   -3 → 圧縮後: -0.291(ネガティブ)
生スコア:  -15 → 圧縮後: -0.905(非常にネガティブ)

口コミ分析やレコメンドエンジンで「無限に大きくなりうる生のスコア」を、人間が見やすい一定範囲のスコアに変換する際にも活用できます。

例4:S字カーブを描くアニメーションのイージング処理

<?php
class EasingFunction
{
    /**
     * tanhを利用したS字カーブのイージング
     * 進行度(0.0〜1.0)を受け取り、滑らかなS字カーブの値を返す
     */
    public function easeTanh(float $progress, float $steepness = 6.0): float
    {
        // progressを-steepness/2 〜 +steepness/2 の範囲にマッピング
        $x = ($progress - 0.5) * $steepness;
        // tanhの出力(-1〜1)を0〜1の範囲に変換
        return (tanh($x) + 1) / 2;
    }
}

$easing = new EasingFunction();

for ($i = 0; $i <= 10; $i++) {
    $progress = $i / 10;
    $eased = $easing->easeTanh($progress);
    printf("進行度: %.1f → イージング後: %.4f\n", $progress, $eased);
}

実行結果:

進行度: 0.0 → イージング後: 0.0474
進行度: 0.1 → イージング後: 0.0900
進行度: 0.2 → イージング後: 0.1674
進行度: 0.3 → イージング後: 0.2891
進行度: 0.4 → イージング後: 0.4504
進行度: 0.5 → イージング後: 0.5000
進行度: 0.6 → イージング後: 0.5496
進行度: 0.7 → イージング後: 0.7109
進行度: 0.8 → イージング後: 0.8326
進行度: 0.9 → イージング後: 0.9100
進行度: 1.0 → イージング後: 0.9526

開始と終了がゆっくりで、中間が速く進む「S字カーブ」の動きを表現でき、UIアニメーションやゲームのモーション設計で利用できます。

例5:信号のソフトクリッピング(音声処理)

<?php
class SoftClipper
{
    /**
     * 音声信号などの値を、急激な歪みを避けながら
     * -1.0〜1.0の範囲にソフトに収める
     */
    public function softClip(float $sample, float $drive = 1.0): float
    {
        return tanh($sample * $drive);
    }
}

$clipper = new SoftClipper();

$samples = [0.2, 0.8, 1.5, 3.0, -2.5];

foreach ($samples as $sample) {
    $clipped = $clipper->softClip($sample, 1.0);
    printf("元の値: %5.1f → クリップ後: %.4f\n", $sample, $clipped);
}

実行結果:

元の値:   0.2 → クリップ後: 0.1974
元の値:   0.8 → クリップ後: 0.6640
元の値:   1.5 → クリップ後: 0.9051
元の値:   3.0 → クリップ後: 0.9951
元の値:  -2.5 → クリップ後: -0.9866

音声信号処理の分野では、tanh() を使ったソフトクリッピングが「歪みエフェクト(ディストーション)」の実装によく使われます。単純な値の切り捨て(ハードクリップ)と違い、滑らかに値が飽和していくのが特徴です。

例6:双曲線関数同士の関係を確認する

<?php
class HyperbolicRelationChecker
{
    public function verify(float $x): array
    {
        $sinhValue = sinh($x);
        $coshValue = cosh($x);
        $tanhValue = tanh($x);

        // tanh(x) = sinh(x) / cosh(x) の関係を確認
        $calculated = $sinhValue / $coshValue;

        return [
            'sinh'        => $sinhValue,
            'cosh'        => $coshValue,
            'tanh'        => $tanhValue,
            'sinh/cosh'   => $calculated,
            'is_matching' => abs($tanhValue - $calculated) < 0.0000001,
        ];
    }
}

$checker = new HyperbolicRelationChecker();
$result = $checker->verify(2.0);

printf("sinh(2)      = %.6f\n", $result['sinh']);
printf("cosh(2)      = %.6f\n", $result['cosh']);
printf("tanh(2)      = %.6f\n", $result['tanh']);
printf("sinh(2)/cosh(2) = %.6f\n", $result['sinh/cosh']);
printf("両者は一致: %s\n", $result['is_matching'] ? 'はい' : 'いいえ');

実行結果:

sinh(2)      = 3.626860
cosh(2)      = 3.762196
tanh(2)      = 0.964028
sinh(2)/cosh(2) = 0.964028
両者は一致: はい

tanh(x) = sinh(x) / cosh(x) という定義通りの関係が成り立っていることを確認できます。

例7:原点付近での線形近似との比較

<?php
class LinearApproximationComparer
{
    public function compare(float $x): array
    {
        $actualTanh = tanh($x);
        // x が0に近いとき、tanh(x) はおおよそ x に近似できる
        $linearApprox = $x;
        $error = abs($actualTanh - $linearApprox);

        return [
            'tanh'  => $actualTanh,
            'approx' => $linearApprox,
            'error' => $error,
        ];
    }
}

$comparer = new LinearApproximationComparer();

foreach ([0.01, 0.1, 0.5, 1.0, 2.0] as $x) {
    $result = $comparer->compare($x);
    printf(
        "x=%.2f: tanh(x)=%.4f, 線形近似=%.4f, 誤差=%.4f\n",
        $x,
        $result['tanh'],
        $result['approx'],
        $result['error']
    );
}

実行結果:

x=0.01: tanh(x)=0.0100, 線形近似=0.0100, 誤差=0.0000
x=0.10: tanh(x)=0.0997, 線形近似=0.1000, 誤差=0.0003
x=0.50: tanh(x)=0.4621, 線形近似=0.4622, 誤差=0.0004
x=2.00: tanh(x)=0.9640, 線形近似=2.0000, 誤差=1.0357

0付近では tanh(x) ≈ x という近似が成り立ちますが、xが大きくなるにつれて誤差が急激に拡大することがわかります。この性質を理解しておくと、数値計算の挙動を予測しやすくなります。

関連関数との比較

関数役割入力の制約出力範囲
tanh()双曲線正接を求める任意の実数-1 〜 1
tan()三角関数のタンジェントを求めるラジアン(角度)-∞ 〜 +∞(漸近線あり)
sinh()双曲線正弦を求める任意の実数-∞ 〜 +∞
cosh()双曲線余弦を求める任意の実数1 以上
atanh()tanhの逆関数-1 〜 1任意の実数
sigmoid(独自実装)0〜1に正規化(PHPに組み込みはない)任意の実数0 〜 1

最も混同しやすいのが tan()tanh() です。tan() は円(三角関数)に基づき、入力はラジアンで出力は無限大になりうるのに対し、tanh() は双曲線関数に基づき、入力は任意の実数で出力は必ず-1〜1に収まるという、性質がまったく異なる関数です。

よくある落とし穴・注意点

  1. tan() との名前の混同 1文字違いのため、tan() を書くつもりで tanh() と書いてしまう、あるいはその逆のタイプミスは非常によく起こります。tan() は「角度」を扱う三角関数、tanh() は「任意の実数」を扱う双曲線関数という、根本的に異なる関数であることを常に意識しましょう。
  2. 「角度」だと誤解してラジアン変換をしてしまう tan() の経験があると、tanh() にも deg2rad() を通してしまうミスが起こりがちです。tanh() の引数は角度ではなく、ただの実数です。変換は不要です。
  3. 出力が-1〜1に収まることを忘れて後続処理でエラーになる tanh() の出力を0〜1の範囲が必要な処理(例えば確率やパーセンテージ)にそのまま渡すと、負の値が原因でエラーや不正な計算結果になることがあります。0〜1の範囲が必要な場合は (tanh($x) + 1) / 2 のように変換しましょう。
  4. 大きな入力に対する勾配消失(機械学習での注意点) tanh() を活性化関数として使う場合、入力が非常に大きい(または小さい)と出力の変化がほぼ無くなり(傾きがほぼ0になる)、ニューラルネットワークの学習が停滞する「勾配消失問題」が起きやすくなります。入力値のスケーリングや正規化を事前に行うことが重要です。
  5. sinh()cosh()がオーバーフローする値での計算 tanh(x) = sinh(x) / cosh(x) という定義上、内部的に非常に大きな指数計算が行われます。極端に大きな x(数百以上など)を渡しても tanh() 自体は安定して-1または1を返しますが、自分で sinh()cosh() を個別に計算して割ろうとすると、オーバーフローして INFNAN になることがあります。可能な限り tanh() を直接使うほうが安全です。

まとめ

ポイント内容
役割双曲線正接(ハイパボリックタンジェント)を計算する
引数任意の実数(角度ではない)
戻り値-1〜1の範囲に収まるfloat
tan()との違い円関数ではなく双曲線関数。角度変換は不要
よく組み合わせる関数sinh(), cosh(), atanh()
主な用途値の正規化、活性化関数、ソフトクリッピング、S字カーブのイージング
注意点tan()との混同、出力範囲の誤解、勾配消失

tanh() は日常的なWeb開発で頻繁に使う関数ではありませんが、「どんな入力でも出力を-1〜1の範囲に滑らかに収めてくれる」という性質は、機械学習・音声処理・アニメーション制御など、専門分野では非常に強力な道具になります。tan() との違いをしっかり区別して使い分けられるようになりましょう。

タイトルとURLをコピーしました