第5章 ベクトルの足し算とスカラー倍:情報を混ぜる、情報を強める

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

第4章までで、データが数値の並びになることを確認しました。第5章から、いよいよ計算に入ります。最初に扱うのは、ベクトルの足し算とスカラー倍です。この二つは、線形代数で最も単純な演算であり、計算方法を覚えるだけなら5分もかかりません。しかし、単純だからといって重要でないわけではありません。ディープラーニングの中では、この二つの操作が驚くほど頻繁に現れます。バイアスを加える処理、残差接続、複数の情報を統合する処理。いずれも、その正体はベクトルの足し算です。この章では、計算方法を確認したうえで、それが何を意味しているのかを掘り下げます。

ベクトルの足し算

計算方法

ベクトルの足し算は、対応する要素どうしを足すだけです。

\mathbf{a} + \mathbf{b} = \begin{pmatrix} a_1 \\ a_2 \\ a_3 \end{pmatrix} + \begin{pmatrix} b_1 \\ b_2 \\ b_3 \end{pmatrix} = \begin{pmatrix} a_1 + b_1 \\ a_2 + b_2 \\ a_3 + b_3 \end{pmatrix}

具体例で確認します。

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

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

\mathbf{a} + \mathbf{b} = [3+2, 1+5, 4+(-1)] = [5, 6, 3]

引き算も同様です。対応する要素どうしを引きます。

重要な制約:形が一致していなければならない

ベクトルの足し算には、絶対的な条件があります。二つのベクトルの要素数が、完全に一致していなければなりません。

3次元のベクトルと4次元のベクトルは、足せません。1番目どうし、2番目どうし、3番目どうしは足せても、4番目に対応する相手が存在しないからです。

この制約は、実務で頻繁にエラーとして現れます。

ValueError: operands could not be broadcast together with shapes (3,) (4,)

このエラーを見たら、足し合わせようとしている二つのものの形が違う、と即座に判断できます。

なお、形が違っていても計算が通ってしまう場合があります。ブロードキャストという仕組みが働くためです。これについては第10章で詳しく扱いますが、意図しない計算が行われる原因にもなるため、注意が必要です。

矢印としての意味

2次元で図示すると、ベクトルの足し算の意味が見えてきます。

\mathbf{a} = [3, 1] は、原点から右に3、上に1進んだ点への矢印です。

\mathbf{b} = [1, 2] は、原点から右に1、上に2進んだ点への矢印です。

これを足した [4, 3] は、\mathbf{a} の矢印の先端から、さらに \mathbf{b} の分だけ進んだ点になります。

つまり、矢印を継ぎ足す操作です。2本の矢印をつないだ結果が、足し算の答えになります。

この図形的な意味は、2次元や3次元でしか描けませんが、高次元でも同じ性質が成り立ちます。

スカラー倍

計算方法

スカラー倍とは、ベクトルのすべての要素に、同じ数値を掛ける操作です。

c\mathbf{a} = c\begin{pmatrix} a_1 \\ a_2 \\ a_3 \end{pmatrix} = \begin{pmatrix} ca_1 \\ ca_2 \\ ca_3 \end{pmatrix}

具体例で確認します。

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

2\mathbf{a} = [6, 2, 8]

0.5\mathbf{a} = [1.5, 0.5, 2]

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

矢印としての意味

スカラー倍は、矢印の長さを変える操作です。

2倍すれば、同じ方向を向いたまま、長さが2倍になります。

0.5倍すれば、長さが半分になります。

マイナス1倍すれば、長さは同じまま、向きが正反対になります。

重要な点は、方向が変わらない、あるいは正反対になることです。スカラー倍では、矢印の向いている直線から外れることはありません。

負の値を掛けたときだけ、向きが逆転します。

ディープラーニングでの用途

スカラー倍は、次のような場面で使われます。

学習率を掛ける処理です。勾配ベクトルに学習率という小さな値を掛けて、パラメータの更新量を決めます。

正規化も、ベクトルを自身の長さで割るというスカラー倍の一種です。

\hat{\mathbf{v}} = \frac{\mathbf{v}}{|\mathbf{v}|}

線形結合という考え方

足し算とスカラー倍を組み合わせると、線形結合という操作になります。

c_1\mathbf{v}_1 + c_2\mathbf{v}_2 + \cdots + c_n\mathbf{v}_n

複数のベクトルに、それぞれ係数を掛けて、足し合わせたものです。

この操作は、複数の情報を、重みを付けて混ぜ合わせることに相当します。

具体例

三つのベクトルがあるとします。

\mathbf{v}_1 = [2, 0, 1]

\mathbf{v}_2 = [0, 4, 2]

\mathbf{v}_3 = [1, 1, 0]

これらを、係数0.5、0.3、0.2で線形結合します。

0.5\mathbf{v}_1 + 0.3\mathbf{v}_2 + 0.2\mathbf{v}_3

各次元ごとに計算します。

第1次元:0.5 \times 2 + 0.3 \times 0 + 0.2 \times 1 = 1.0 + 0 + 0.2 = 1.2

第2次元:0.5 \times 0 + 0.3 \times 4 + 0.2 \times 1 = 0 + 1.2 + 0.2 = 1.4

第3次元:0.5 \times 1 + 0.3 \times 2 + 0.2 \times 0 = 0.5 + 0.6 + 0 = 1.1

結果は [1.2, 1.4, 1.1] です。

係数の合計が1である場合

上の例では、係数の合計が次のようになっています。

0.5 + 0.3 + 0.2 = 1.0

このような線形結合を、加重平均と呼びます。

加重平均には、次の性質があります。結果は、元のベクトルたちが作る領域の内側に収まります。極端に大きな値や小さな値にはなりません。

この性質は、ディープラーニングで重要な意味を持ちます。次に述べる注意機構では、まさにこの加重平均が使われています。

ディープラーニングに現れる足し算とスカラー倍

ここからが、この章の核心です。単純な足し算が、実際のモデルのどこで使われているかを見ていきます。

用途1:バイアスの加算

全結合層の計算は、次の式で表されます。

\mathbf{y} = W\mathbf{x} + \mathbf{b}

この式の最後にある + \mathbf{b} が、ベクトルの足し算です。

バイアスとは、各ニューロンに与えられる下駄のようなものです。入力がすべて0であっても、バイアスの分だけ出力が生じます。

なぜバイアスが必要なのでしょうか。

バイアスがなければ、入力が0のとき、出力も必ず0になります。これは、モデルが表現できる関数に、不必要な制約を課すことになります。

バイアスを加えることで、出力の基準点を自由に設定できるようになり、表現力が増します。

用途2:残差接続

第1章で少し触れた、Transformerで使われる仕組みです。

\mathbf{y} = \mathbf{x} + F(\mathbf{x})

ある処理 F の出力に、その処理への入力をそのまま足し戻します。

これも、ベクトルの足し算そのものです。学習されるパラメータは一切なく、純粋な足し算だけです。

この単純な足し算が、深いネットワークの学習を可能にしています。層を何十層と重ねても学習が進むのは、この仕組みのおかげです。

理由は微分の性質にあり、第15章で詳しく扱います。

なお、残差接続を成立させるには、\mathbf{x}F(\mathbf{x}) の形が一致していなければなりません。だからこそ、Transformerでは各層の入出力の次元数が768で統一されているのです。

用途3:位置情報の付加

言語モデルでは、単語の意味を表すベクトルに、位置を表すベクトルを足し合わせます。

$latex \mathbf{x}{input} = \mathbf{e}{token} + \mathbf{e}_{position}$

「猫が犬を追いかけた」と「犬が猫を追いかけた」は、含まれる単語が同じです。単語の意味を表すベクトルだけでは、この二つを区別できません。

そこで、これは何番目の単語かという情報を、ベクトルとして足し合わせます。

ここで、疑問が生じるかもしれません。意味の情報と位置の情報を足してしまったら、混ざって区別できなくなるのではないか、という疑問です。

答えは、768次元という空間が十分に広いため、両者を重ね合わせても、モデルが学習によって分離できる、というものです。

イメージとしては、音の重ね合わせに近いものです。二つの音を同時に鳴らしても、人間の耳は、そこに含まれる音を聞き分けられます。

なお、横につなげて1536次元にする、つまり連結するという方法も考えられますが、次元が増えると計算量も増えるため、足し算が選ばれています。

用途4:注意機構における情報の統合

先ほど扱った線形結合が、そのまま使われる場面です。

言語モデルの注意機構では、各単語が周囲の単語から情報を集めます。このとき、どの単語にどれだけ注目するかという重みを計算し、その重みで各単語のベクトルを線形結合します。

\mathbf{o} = 0.2 \mathbf{v}_1 + 0.5 \mathbf{v}_2 + 0.3 \mathbf{v}_3

重みの合計は1になるよう設計されているため、これは加重平均です。

先ほど計算した線形結合の例が、まさにこの処理に対応しています。

用途5:パラメータの更新

学習の過程では、パラメータを少しずつ更新します。

$latex \mathbf{w}{new} = \mathbf{w}{old} - \eta \mathbf{g}$

\mathbf{g} は勾配ベクトル、\eta は学習率というスカラーです。

勾配ベクトルにスカラーを掛け、それを元のパラメータから引く。スカラー倍と引き算の組み合わせです。

ディープラーニングの学習とは、突き詰めればこの操作の繰り返しです。何百万回も、この足し引きが行われます。

Pythonで確認する

import numpy as np

print("=== ベクトルの足し算 ===")
a = np.array([3, 1, 4])
b = np.array([2, 5, -1])
print(f"a = {a}")
print(f"b = {b}")
print(f"a + b = {a + b}")
print(f"a - b = {a - b}")

print("\n=== スカラー倍 ===")
print(f"2 * a = {2 * a}")
print(f"0.5 * a = {0.5 * a}")
print(f"-1 * a = {-1 * a}")

print("\n=== 形が合わないとどうなるか ===")
c = np.array([1, 2, 3, 4])
try:
    result = a + c
except ValueError as e:
    print(f"エラー: {e}")

print("\n=== 線形結合(加重平均)===")
v1 = np.array([2.0, 0.0, 1.0])
v2 = np.array([0.0, 4.0, 2.0])
v3 = np.array([1.0, 1.0, 0.0])
weights = [0.5, 0.3, 0.2]

result = weights[0] * v1 + weights[1] * v2 + weights[2] * v3
print(f"係数: {weights}(合計{sum(weights)})")
print(f"結果: {result}")

print("\n各次元が、元の値の範囲内に収まっているか確認:")
for i in range(3):
    values = [v1[i], v2[i], v3[i]]
    print(f"  第{i+1}次元: 元の値 {values}, 結果 {result[i]:.1f}, "
          f"範囲内か {min(values) <= result[i] <= max(values)}")

print("\n=== 残差接続のシミュレーション ===")
x = np.array([1.0, 2.0, 3.0])

def some_process(v):
    """何らかの処理を模擬"""
    return v * 0.1 - 0.05

f_x = some_process(x)
y_without_residual = f_x
y_with_residual = x + f_x

print(f"入力 x: {x}")
print(f"処理の出力 F(x): {f_x.round(3)}")
print(f"残差接続なし: {y_without_residual.round(3)}")
print(f"残差接続あり: {y_with_residual.round(3)}")
print("残差接続があると、元の情報が保持されていることが分かります。")

print("\n=== 位置情報の付加 ===")
np.random.seed(0)
token_embedding = np.random.randn(8).round(3)
position_embedding_0 = np.random.randn(8).round(3)
position_embedding_5 = np.random.randn(8).round(3)

print(f"単語の埋め込み: {token_embedding}")
print(f"位置0の埋め込み: {position_embedding_0}")
print(f"位置5の埋め込み: {position_embedding_5}")
print(f"\n位置0での入力: {(token_embedding + position_embedding_0).round(3)}")
print(f"位置5での入力: {(token_embedding + position_embedding_5).round(3)}")
print("同じ単語でも、位置が違えば異なるベクトルになります。")

print("\n=== パラメータ更新のシミュレーション ===")
w = np.array([0.5, -0.3, 0.8])
gradient = np.array([0.1, -0.2, 0.05])
learning_rate = 0.01

w_new = w - learning_rate * gradient
print(f"更新前の重み: {w}")
print(f"勾配: {gradient}")
print(f"学習率: {learning_rate}")
print(f"更新後の重み: {w_new.round(6)}")
print(f"変化量: {(w_new - w).round(6)}")

print("\n=== 連結との違い ===")
p = np.array([1, 2, 3])
q = np.array([4, 5, 6])
print(f"p = {p}, 形{p.shape}")
print(f"q = {q}, 形{q.shape}")
print(f"足し算 p + q = {p + q}, 形{(p + q).shape}")
print(f"連結 = {np.concatenate([p, q])}, 形{np.concatenate([p, q]).shape}")
print("足し算では次元数が変わらず、連結では次元数が倍になります。")

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

つまずき1:足し算とスカラー倍を軽視する

計算が単純であるため、これくらいは分かっていると流してしまいがちです。

しかし、残差接続や位置エンコーディングが単なる足し算であると理解できていないと、後の章で混乱します。複雑な仕組みだと思い込んで、必要以上に難しく考えてしまうのです。

単純な操作が、組み合わさることで強力な仕組みになる。この感覚を持っておくことが重要です。

つまずき2:連結と足し算を混同する

二つのベクトルを組み合わせると聞いたとき、横につなげる操作を想像する人がいます。

3次元のベクトル二つを足すと、3次元のベクトルになります。

3次元のベクトル二つを連結すると、6次元のベクトルになります。

まったく別の操作です。ディープラーニングでは、どちらも使われますが、位置エンコーディングや残差接続で使われるのは、足し算のほうです。

つまずき3:形が合わないのにエラーにならない場合がある

NumPyやPyTorchには、ブロードキャストという機能があります。

形が違っていても、一定の条件を満たせば、自動的に形を合わせて計算してくれます。

便利な機能ですが、意図しない計算が行われる原因にもなります。エラーにならないため、間違いに気づきにくいのです。

この点は第10章で詳しく扱いますが、現時点では、形が違うのに計算が通ったら疑ってみる、という意識を持っておいてください。

演習

演習1:手計算する

次の計算を行ってください。

\mathbf{a} = [4, -2, 3]\mathbf{b} = [1, 5, -3] とします。

\mathbf{a} + \mathbf{b}

\mathbf{a} - \mathbf{b}

3\mathbf{a}

2\mathbf{a} + \mathbf{b}

演習2:加重平均を計算する

次の三つのベクトルを、係数0.6、0.3、0.1で線形結合してください。

\mathbf{v}_1 = [10, 0, 5]

\mathbf{v}_2 = [0, 10, 5]

\mathbf{v}_3 = [5, 5, 0]

また、この結果の各要素が、元の三つのベクトルの対応する要素の最小値と最大値の間に収まっていることを確認してください。

演習3:残差接続の効果を確認する

import numpy as np

def shrinking_layer(x):
    """値を小さくする処理"""
    return x * 0.5

x_initial = np.array([1.0, 2.0, 3.0, 4.0])

print("=== 残差接続なし ===")
x = x_initial.copy()
print(f"入力    : {x}")
for i in range(1, 6):
    x = shrinking_layer(x)
    print(f"{i}層通過後: {x.round(6)}")

print("\n=== 残差接続あり ===")
x = x_initial.copy()
print(f"入力    : {x}")
for i in range(1, 6):
    x = x + shrinking_layer(x)
    print(f"{i}層通過後: {x.round(6)}")

print("\n残差接続がないと、層を重ねるごとに情報が消えていきます。")
print("残差接続があると、元の情報が保たれたまま、変化が積み上がります。")

演習の解答例と解説

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

\mathbf{a} + \mathbf{b} = [4+1, -2+5, 3+(-3)] = [5, 3, 0]

\mathbf{a} - \mathbf{b} = [4-1, -2-5, 3-(-3)] = [3, -7, 6]

3\mathbf{a} = [12, -6, 9]

2\mathbf{a} + \mathbf{b} = [8, -4, 6] + [1, 5, -3] = [9, 1, 3]

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

第1次元:0.6 \times 10 + 0.3 \times 0 + 0.1 \times 5 = 6 + 0 + 0.5 = 6.5

第2次元:0.6 \times 0 + 0.3 \times 10 + 0.1 \times 5 = 0 + 3 + 0.5 = 3.5

第3次元:0.6 \times 5 + 0.3 \times 5 + 0.1 \times 0 = 3 + 1.5 + 0 = 4.5

結果は [6.5, 3.5, 4.5] です。

各要素を確認します。

第1次元の元の値は、10、0、5です。最小0、最大10であり、結果の6.5はこの範囲内です。

第2次元の元の値は、0、10、5です。最小0、最大10であり、結果の3.5はこの範囲内です。

第3次元の元の値は、5、5、0です。最小0、最大5であり、結果の4.5はこの範囲内です。

係数の合計が1である加重平均では、結果が必ず元の値の範囲内に収まります。この性質があるため、注意機構で情報を統合しても、値が発散することがありません。

演習3では、残差接続がない場合、5層通過後に値が32分の1になることが確認できます。

0.5^5 = 0.03125

一方、残差接続がある場合は、次のようになります。

1.5^5 \approx 7.59

値が保たれるどころか、増加します。

実際のモデルでは、正規化などの仕組みによって値が過度に増大しないよう制御されますが、情報が消えないという効果は、この実験のとおりです。

講師向けの補足

この章では、計算の説明に時間をかけすぎないことが重要です。足し算とスカラー倍の計算自体は、5分あれば説明できます。

時間を割くべきは、この単純な操作が、実際のモデルのどこで使われているかという部分です。バイアス、残差接続、位置エンコーディング、注意機構、パラメータ更新。この五つの用途を、具体的に示してください。

特に効果的なのは、演習3の残差接続の実験です。層を重ねると情報が消えるという現象を、数値で見ると、なぜ足し算が必要なのかが実感されます。

また、位置エンコーディングについて、足したら混ざってしまうのではないかという質問は、ほぼ必ず出ます。この疑問は自然なものであり、良い問いです。音の重ね合わせの例えを用意しておいてください。

理解度を確認する問い

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

ベクトルの足し算が成立するための条件は何でしょうか。

スカラー倍を行うと、矢印としてのベクトルはどう変化するでしょうか。方向と長さのそれぞれについて答えてください。

係数の合計が1である線形結合には、どのような性質があるでしょうか。

ディープラーニングにおいて、ベクトルの足し算が使われている場面を、五つ挙げてください。

残差接続を成立させるために、各層の入出力が満たすべき条件は何でしょうか。

まとめ

ベクトルの足し算は、対応する要素どうしを足す操作です。二つのベクトルの要素数が完全に一致していなければ、計算できません。図形的には、矢印を継ぎ足す操作にあたります。

スカラー倍は、すべての要素に同じ数値を掛ける操作です。矢印の長さを変えますが、方向は変わりません。負の値を掛けた場合のみ、向きが逆転します。

足し算とスカラー倍を組み合わせたものを、線形結合と呼びます。複数の情報を、重みを付けて混ぜ合わせる操作です。係数の合計が1である場合は加重平均となり、結果が元の値の範囲内に収まるという性質を持ちます。

ディープラーニングでは、この単純な操作が、あらゆる場面で使われています。全結合層におけるバイアスの加算、深いネットワークの学習を可能にする残差接続、単語の意味に位置情報を加える処理、注意機構における情報の統合、そして学習時のパラメータ更新です。

単純な操作が組み合わさることで、強力な仕組みが構成されている。これが、ディープラーニングの構造を理解するうえでの、重要な視点です。

次の第6章では、内積を扱います。足し算とスカラー倍に続く、三つ目の基本演算です。そして、この内積こそが、ニューロン1個の計算そのものであることが明らかになります。線形代数とニューラルネットワークが、次の章で初めて直接つながります。

第6章に進む前に、演習3のコードを実行し、残差接続の有無で情報の保持がどう変わるかを確認しておいてください。単純な足し算の効果を数値で見た経験が、この後の理解を支えます。

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

投稿者プロフィール

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

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