第6章 内積:似ている度合いを測る

こんにちは。ゆうせいです。

第5章では、ベクトルの足し算とスカラー倍を扱いました。この二つの演算では、ベクトルどうしを計算しても、結果はベクトルのままでした。第6章で扱う内積は、性質が異なります。ベクトルとベクトルを計算して、答えが1個の数値になるのです。この複数の数値を1個にまとめるという性質が、内積を特別なものにしています。そして、内積が表しているのは、二つのベクトルがどれだけ似ているかという度合いです。なぜ掛けて足すだけの計算が、似ている度合いを測れるのでしょうか。この章では、その理由を三つの角度から解説します。

内積の計算方法

内積とは、対応する要素どうしを掛け合わせ、それらをすべて足し合わせた値です。

\mathbf{a} \cdot \mathbf{b} = a_1 b_1 + a_2 b_2 + \cdots + a_n b_n

具体例で確認します。

\mathbf{a} = [2, 3, 1]

\mathbf{b} = [4, 1, 5]

計算します。

\mathbf{a} \cdot \mathbf{b} = 2 \times 4 + 3 \times 1 + 1 \times 5 = 8 + 3 + 5 = 16

答えは16です。ベクトルではなく、1個の数値になっています。

計算そのものは、掛けて足すだけです。難しいところは一つもありません。

表記について

内積を表す記号には、いくつかの流儀があります。

ドットを使う記法です。\mathbf{a} \cdot \mathbf{b} と書きます。この連載では、これを使います。

山括弧を使う記法です。\langle \mathbf{a}, \mathbf{b} \rangle と書きます。数学の教科書でよく見かけます。

転置を使う記法です。\mathbf{a}^T\mathbf{b} と書きます。行列の掛け算として捉える立場です。第9章以降で、この見方が重要になります。

いずれも同じものを指しています。

形の制約

内積も、二つのベクトルの要素数が一致していなければ計算できません。

3次元のベクトルと4次元のベクトルの内積は、定義されません。

第5章の足し算と、同じ制約です。

アダマール積との違い

要素どうしを掛けるという点で、似た演算があります。アダマール積、あるいは要素ごとの積と呼ばれるものです。

\mathbf{a} \odot \mathbf{b} = [a_1b_1, a_2b_2, a_3b_3]

先ほどの例で言えば、[8, 3, 5] です。

内積は、この結果をさらに足し合わせて16にします。アダマール積は、足さずにベクトルのまま残します。

結果がスカラーになるか、ベクトルのままかという点が、決定的な違いです。

プログラムでも、演算子が異なります。この点は、混同しやすいので注意してください。

なぜ内積が「似ている度合い」を表すのか

ここが、この章の核心です。三つの角度から説明します。

角度1:符号の掛け算に注目する

掛け算には、次の性質があります。

同じ符号どうし、つまりプラスとプラス、またはマイナスとマイナスを掛けると、結果はプラスになります。

異なる符号どうし、つまりプラスとマイナスを掛けると、結果はマイナスになります。

この性質が、内積の意味を決めています。

二つのベクトルの各次元の値が、同じ方向に振れていれば、その次元の寄与はプラスになります。

逆方向に振れていれば、マイナスになります。

したがって、次のようになります。

すべての次元で同じ方向に振れていれば、プラスの値ばかりが積み上がり、内積は大きな正の値になります。

すべての次元で逆方向に振れていれば、マイナスの値ばかりが積み上がり、内積は大きな負の値になります。

方向がばらばらであれば、プラスとマイナスが打ち消し合い、内積は0に近い値になります。

具体例で確かめる

三つの組み合わせを比較します。

組み合わせA、符号が完全に一致する場合。

\mathbf{a} = [1, 2, -1]\mathbf{b} = [2, 3, -2]

1 \times 2 + 2 \times 3 + (-1) \times (-2) = 2 + 6 + 2 = 10

すべての項がプラスになり、大きな値です。

組み合わせB、符号が完全に逆の場合。

\mathbf{a} = [1, 2, -1]\mathbf{c} = [-2, -3, 2]

1 \times (-2) + 2 \times (-3) + (-1) \times 2 = -2 - 6 - 2 = -10

すべての項がマイナスになり、大きな負の値です。

組み合わせC、符号がばらばらの場合。

\mathbf{a} = [1, 2, -1]\mathbf{d} = [3, -1, -1]

1 \times 3 + 2 \times (-1) + (-1) \times (-1) = 3 - 2 + 1 = 2

プラスとマイナスが打ち消し合い、小さな値です。

角度2:幾何学的な意味

内積には、次の公式が成り立ちます。

\mathbf{a} \cdot \mathbf{b} = |\mathbf{a}| , |\mathbf{b}| \cos\theta

|\mathbf{a}| はベクトルaの長さ、\theta は二つのベクトルがなす角度です。

この式から、次のことが読み取れます。

角度が0度、つまり同じ方向を向いているとき、\cos 0° = 1 となり、内積は最大になります。

角度が90度、つまり直交しているとき、\cos 90° = 0 となり、内積は0になります。

角度が180度、つまり正反対を向いているとき、\cos 180° = -1 となり、内積は最小になります。

角度が近いほど内積が大きくなる。この性質が、そのまま似ている度合いの指標になります。

直交という重要な概念

内積が0であることを、直交すると言います。

2次元や3次元では、文字通り垂直に交わっている状態です。

高次元では、図に描けませんが、同じ言葉を使います。まったく関係がない、共通する成分がない、という意味だと理解してください。

第12章で扱いますが、この直交という性質は、次元を扱ううえで重要な役割を果たします。

角度3:長さの影響

先ほどの公式には、|\mathbf{a}||\mathbf{b}| 、つまりベクトルの長さも含まれています。

これは、内積が方向の一致度だけでなく、ベクトルの長さにも影響されることを意味します。

方向が同じでも、長いベクトルどうしの内積は、短いベクトルどうしの内積より大きくなります。

[1, 0] \cdot [1, 0] = 1

[10, 0] \cdot [10, 0] = 100

どちらも方向は完全に一致していますが、値は100倍違います。

コサイン類似度

純粋に方向の一致度だけを測りたい場合は、内積をベクトルの長さで割ります。

\cos\theta = \frac{\mathbf{a} \cdot \mathbf{b}}{|\mathbf{a}| , |\mathbf{b}|}

これをコサイン類似度と呼びます。

値の範囲は、マイナス1からプラス1です。1に近ければ方向が一致、0なら無関係、マイナス1に近ければ正反対、という解釈になります。

単語の意味の近さを測る場面などでは、長さの影響を除きたいため、コサイン類似度がよく使われます。

ニューロン1個の計算は内積である

ここで、この連載で最も重要な対応関係が現れます。

ニューラルネットワークのニューロン1個は、次の計算を行っています。

y = w_1 x_1 + w_2 x_2 + \cdots + w_n x_n + b

x_1, x_2, \ldots, x_n が入力、w_1, w_2, \ldots, w_n が重み、b がバイアスです。

この式をよく見てください。前半部分は、まさに内積の形をしています。

y = \mathbf{w} \cdot \mathbf{x} + b

つまり、ニューロン1個の計算とは、重みベクトルと入力ベクトルの内積に、バイアスを足したものです。

この対応が意味すること

内積が類似度を表すことを思い出してください。

ニューロンは、重みベクトルと入力ベクトルの類似度を計算していることになります。

言い換えると、重みベクトルは、このニューロンが反応したいパターンを表しています。

入力がそのパターンに近ければ、内積が大きくなり、ニューロンは強く反応します。

入力がそのパターンと無関係であれば、内積は0に近くなり、反応しません。

入力がそのパターンと正反対であれば、内積は大きな負の値になります。

具体例で考える

顧客が商品を購入するかを予測するモデルを考えます。

入力は、年齢、年収、過去の購入回数の三つです。

\mathbf{x} = [35, 550, 12]

あるニューロンの重みが、次の値だったとします。

\mathbf{w} = [0.1, 0.3, 2.0]

内積を計算します。

0.1 \times 35 + 0.3 \times 550 + 2.0 \times 12 = 3.5 + 165 + 24 = 192.5

このニューロンは、過去の購入回数に大きな重みである2.0を置いています。購入回数が多い顧客に強く反応するニューロン、ということです。

学習とは、この重みベクトルを、目的に合ったパターンに調整していく作業です。

画像認識での例

画像認識では、この対応がより視覚的に理解できます。

重みベクトルを、入力画像と同じ形に並べ直すと、画像として見ることができます。

学習後の重みを可視化すると、縦線に反応するニューロン、横線に反応するニューロン、特定の色の組み合わせに反応するニューロンなど、それぞれが異なるパターンを持っていることが観察されます。

入力画像が、そのパターンと似ていれば、内積が大きくなり、ニューロンが反応する。この仕組みで、特徴の検出が行われています。

単語の意味の近さを測る

第4章で、単語がベクトルとして表現されることを確認しました。

このベクトルどうしの内積、あるいはコサイン類似度を計算すると、単語の意味の近さが測れます。

学習が進んだモデルでは、「猫」と「犬」のベクトルは、「猫」と「自動車」のベクトルより、類似度が高くなる傾向があります。

似た文脈で使われる単語は、似たベクトルになるよう学習されるためです。

この性質を利用して、類義語の検索、文書の分類、検索エンジンの関連度計算など、さまざまな応用が行われています。

Pythonで確認する

import numpy as np

print("=== 内積の基本計算 ===")
a = np.array([2, 3, 1])
b = np.array([4, 1, 5])

print(f"a = {a}")
print(f"b = {b}")
print(f"内積(手動計算): {2*4 + 3*1 + 1*5}")
print(f"内積(np.dot): {np.dot(a, b)}")
print(f"内積(@演算子): {a @ b}")
print(f"内積(要素積の合計): {(a * b).sum()}")

print("\n=== アダマール積との違い ===")
print(f"アダマール積 a * b = {a * b}, 形{(a * b).shape}")
print(f"内積 a @ b = {a @ b}, 型{type(a @ b).__name__}")

print("\n=== 符号の一致と内積の関係 ===")
base = np.array([1, 2, -1])
patterns = {
    "符号が完全一致": np.array([2, 3, -2]),
    "符号が完全に逆": np.array([-2, -3, 2]),
    "符号がばらばら": np.array([3, -1, -1]),
    "直交": np.array([2, -1, 0])
}

print(f"基準ベクトル: {base}")
print(f"\n{'パターン':>16} | {'ベクトル':>16} | {'内積':>8}")
print("-" * 48)
for name, v in patterns.items():
    print(f"{name:>16} | {str(v):>16} | {base @ v:>8}")

print("\n=== 長さの影響 ===")
u1 = np.array([1.0, 0.0])
u2 = np.array([10.0, 0.0])
print(f"[1,0]・[1,0] = {u1 @ u1}")
print(f"[10,0]・[10,0] = {u2 @ u2}")
print("方向は同じでも、長さが10倍なら内積は100倍になります。")

print("\n=== コサイン類似度 ===")
def cosine_similarity(x, y):
    return (x @ y) / (np.linalg.norm(x) * np.linalg.norm(y))

print(f"[1,0]と[10,0]のコサイン類似度: {cosine_similarity(u1, u2):.4f}")
print("長さが違っても、方向が同じならコサイン類似度は1になります。")

print("\n=== 角度と内積の関係 ===")
import math
print(f"{'角度':>8} | {'cos値':>8} | {'内積(長さ1同士)':>18}")
print("-" * 40)
for deg in [0, 30, 45, 60, 90, 120, 180]:
    rad = math.radians(deg)
    v1 = np.array([1.0, 0.0])
    v2 = np.array([math.cos(rad), math.sin(rad)])
    print(f"{deg:>6}度 | {math.cos(rad):>8.4f} | {v1 @ v2:>18.4f}")

print("\n=== ニューロン1個の計算 ===")
x = np.array([35.0, 550.0, 12.0])   # 年齢、年収、購入回数
w = np.array([0.1, 0.3, 2.0])        # 重み
bias = -100.0

output = w @ x + bias
print(f"入力: {x}")
print(f"重み: {w}")
print(f"バイアス: {bias}")
print(f"内積: {w @ x}")
print(f"出力(内積+バイアス): {output}")

print("\n=== 複数のパターンへの反応を比較 ===")
neurons = {
    "購入回数重視": np.array([0.0, 0.0, 5.0]),
    "年収重視": np.array([0.0, 0.5, 0.0]),
    "年齢重視": np.array([2.0, 0.0, 0.0])
}

customers = {
    "顧客A(若年・低収入・頻繁購入)": np.array([25.0, 300.0, 30.0]),
    "顧客B(中年・高収入・低頻度)": np.array([50.0, 900.0, 2.0])
}

for cname, cx in customers.items():
    print(f"\n{cname}: {cx}")
    for nname, nw in neurons.items():
        print(f"  {nname:>12}ニューロンの反応: {nw @ cx:>8.1f}")

print("\n=== 単語ベクトルの類似度 ===")
vectors = {
    "猫":       np.array([0.8, 0.6, 0.1, -0.2]),
    "犬":       np.array([0.7, 0.7, 0.2, -0.1]),
    "自動車":   np.array([-0.3, 0.1, 0.9, 0.5]),
    "トラック": np.array([-0.4, 0.0, 0.8, 0.6])
}

words = list(vectors.keys())
print(f"{'':>10}", end="")
for w_ in words:
    print(f"{w_:>10}", end="")
print()

for w1 in words:
    print(f"{w1:>10}", end="")
    for w2 in words:
        sim = cosine_similarity(vectors[w1], vectors[w2])
        print(f"{sim:>10.4f}", end="")
    print()

初学者がつまずきやすい点

つまずき1:内積とアダマール積を混同する

計算の途中まで同じであるため、混同しやすい演算です。

内積は結果がスカラー、アダマール積は結果がベクトルです。

プログラムでは、演算子が異なります。NumPyでは、@ や np.dot が内積、* がアダマール積です。

この違いを間違えると、形が合わないというエラーが出るか、あるいはエラーにならないまま誤った計算が進みます。

つまずき2:長さの影響を忘れる

内積が大きいイコール似ている、と単純に覚えると、誤解を招きます。

方向がまったく違っていても、両方のベクトルが非常に長ければ、内積はある程度の大きさになり得ます。

純粋に方向の近さを測りたい場合は、コサイン類似度を使ってください。

なお、ディープラーニングの内部では、コサイン類似度ではなく、そのままの内積が使われることが多くあります。長さの情報も、モデルにとって意味のある情報として扱われているためです。

つまずき3:内積が0イコール無関係だと決めつける

内積が0であることは、直交していることを意味します。

ただし、これは線形の関係がないという意味であり、まったく無関係とは限りません。

たとえば、[1, 1][1, -1] の内積は0ですが、この二つのベクトルの間に、何らかの関係を見出すことは可能です。

内積は、あくまで線形の関係を測る指標である、という点は押さえておいてください。

演習

演習1:内積を手計算する

次の内積を計算してください。

[3, 1, 2] \cdot [2, 4, -1]

[1, 0, -1] \cdot [1, 5, 1]

[2, 2] \cdot [3, -3]

三つ目の結果から、この二つのベクトルの関係について何が言えるでしょうか。

演習2:コサイン類似度を計算する

次の二つの組み合わせについて、内積とコサイン類似度をそれぞれ計算してください。

\mathbf{a} = [3, 4]\mathbf{b} = [6, 8]

\mathbf{a} = [3, 4]\mathbf{c} = [4, -3]

結果から、それぞれの組み合わせの関係を説明してください。

演習3:ニューロンの反応を計算する

入力が \mathbf{x} = [2, 5, 1] であるとき、次の三つのニューロンの出力を計算してください。バイアスはすべて0とします。

ニューロン1の重み:[1, 0, 0]

ニューロン2の重み:[0, 1, 0]

ニューロン3の重み:[1, 1, 1]

それぞれのニューロンが、どのような性質を持っているか説明してください。

演習の解答例と解説

演習1の解答は、次のとおりです。

[3, 1, 2] \cdot [2, 4, -1] = 6 + 4 - 2 = 8

[1, 0, -1] \cdot [1, 5, 1] = 1 + 0 - 1 = 0

[2, 2] \cdot [3, -3] = 6 - 6 = 0

二つ目と三つ目は、内積が0です。つまり、これらのベクトルは直交しています。

三つ目について言えば、[2, 2] は右斜め上45度の方向、[3, -3] は右斜め下45度の方向を向いており、両者は90度の角度をなしています。図に描くと、確かに垂直に交わっていることが確認できます。

演習2の解答は、次のとおりです。

一つ目の組み合わせについて。

内積は次のようになります。

3 \times 6 + 4 \times 8 = 18 + 32 = 50

ベクトルの長さは、次のようになります。

|\mathbf{a}| = \sqrt{9 + 16} = 5

|\mathbf{b}| = \sqrt{36 + 64} = 10

コサイン類似度は、次のようになります。

\frac{50}{5 \times 10} = \frac{50}{50} = 1

コサイン類似度が1ということは、二つのベクトルの方向が完全に一致していることを意味します。実際、\mathbf{b}\mathbf{a} の2倍です。

二つ目の組み合わせについて。

内積は次のようになります。

3 \times 4 + 4 \times (-3) = 12 - 12 = 0

コサイン類似度も0です。

内積が0ですから、直交しています。90度の角度をなす関係です。

演習3の解答は、次のとおりです。

ニューロン1:1 \times 2 + 0 \times 5 + 0 \times 1 = 2

ニューロン2:0 \times 2 + 1 \times 5 + 0 \times 1 = 5

ニューロン3:1 \times 2 + 1 \times 5 + 1 \times 1 = 8

ニューロン1は、1番目の特徴量だけを見ています。他の特徴量は無視します。

ニューロン2は、2番目の特徴量だけを見ています。

ニューロン3は、すべての特徴量を等しく見ており、合計を計算していることになります。

重みが0であることは、その特徴量を無視することを意味します。学習の結果、不要な特徴量の重みが0に近づくことがあります。

講師向けの補足

この章は、連載全体の中で最も重要な回です。線形代数とニューラルネットワークが、初めて直接つながる箇所だからです。

ニューロン1個の計算は内積である、という対応は、必ず時間をかけて説明してください。この一点が理解できれば、以降の章は、その拡張として理解できます。

内積が類似度を表す理由については、受講者の数学的な背景によって、響く説明が異なります。

高校数学の内積を覚えている受講者には、|\mathbf{a}||\mathbf{b}|\cos\theta の公式から入るのが最短です。

数学に苦手意識がある受講者には、符号の掛け算の話から入ってください。同じ方向に振れていればプラスが積み上がるという説明は、公式を知らなくても直感的に理解できます。

また、2次元で実際に矢印を描き、角度を変えながら内積を計算する演習を入れると、理解が定着します。768次元は想像できませんが、2次元なら目で見えます。

理解度を確認する問い

第6章の内容を、次の問いで確認してみてください。

内積とアダマール積の違いを、結果の形という観点から説明してみてください。

内積が似ている度合いを表す理由を、符号の掛け算に着目して説明してみてください。

内積が0であることは、二つのベクトルのどのような関係を表すでしょうか。

ニューロン1個の計算が内積であるとすると、重みベクトルは何を表していると解釈できるでしょうか。

内積とコサイン類似度は、何が違うでしょうか。どのような場面で使い分けるべきでしょうか。

まとめ

内積とは、二つのベクトルについて、対応する要素どうしを掛け合わせ、すべて足し合わせた値です。結果はベクトルではなく、1個の数値になります。

内積が似ている度合いを表す理由は、符号の掛け算の性質にあります。各次元の値が同じ方向に振れていればプラスの値が積み上がり、逆方向に振れていれば打ち消し合います。幾何学的には、二つのベクトルがなす角度が小さいほど、内積が大きくなります。

内積が0であることを直交すると言い、二つのベクトルに線形の関係がないことを意味します。

内積は、方向の一致度だけでなく、ベクトルの長さにも影響されます。純粋に方向だけを測りたい場合は、内積をベクトルの長さで割ったコサイン類似度を使います。

そして、この章で最も重要な点は、ニューロン1個の計算が、まさに内積であるということです。

y = \mathbf{w} \cdot \mathbf{x} + b

重みベクトルは、そのニューロンが反応したいパターンを表しています。入力がそのパターンに近ければ内積が大きくなり、ニューロンが強く反応します。学習とは、この重みベクトルを目的に合ったパターンへと調整していく作業です。

次の第7章では、ニューロンを1個から多数へと拡張します。100個のニューロンがあれば、内積を100回計算することになります。この100回の計算を、どうやって1つの操作としてまとめるのか。その考え方を扱います。第8章で登場する行列とベクトルの積へ向けた、橋渡しの章になります。

第7章に進む前に、Pythonのコードを実行し、ベクトルの類似度が意味の近さを反映する様子を確認しておいてください。内積という単純な計算が、実際に何を実現しているかを実感できるはずです。

セイ・コンサルティング・グループでは新人エンジニア研修のアシスタント講師を募集しています。

投稿者プロフィール

山崎講師
山崎講師代表取締役
セイ・コンサルティング・グループ株式会社代表取締役。
岐阜県出身。
海外放浪の末、2000年創業、2004年会社設立。
IT企業向け人材育成研修歴業界歴20年以上。
すべての無駄を省いた費用対効果の高い「筋肉質」な研修を提供します!
この記事に間違い等ありましたらぜひお知らせください。

学生時代は趣味と実益を兼ねてリゾートバイトにいそしむ。長野県白馬村に始まり、志賀高原でのスキーインストラクター、沖縄石垣島、北海道トマム。高じてオーストラリアのゴールドコーストでツアーガイドなど。現在は野菜作りにはまっている。