Skip to article frontmatterSkip to article content
Site not loading correctly?

This may be due to an incorrect BASE_URL configuration. See the MyST Documentation for reference.

NumPy入門

Open in Colab
Jupyter logo

概要

NumPy

NumPyとは,Pythonで利用できる科学計算ライブラリで,行列計算に特化した機能が豊富に収録されています. Pythonの組み込み型には,ListはあってもArray(配列)はありませんでした.NumPyの提供するNDArrayはPythonで使われる標準的な配列型であり,これを含めたNumPyの機能はCやFortranのような高速な言語で実装されているため,NumPyをうまく使いこなすことで「Pythonの手軽さを享受しつつ,チューニングされたC言語製プログラムのような速度で処理する」ことができます.

NumPyが高速な理由

NumPyが高速なのは,C/Fortranで実装されているからだけではありません.NumPyでの計算をより高速化するために,BLAS(Basic Linear Algebra Subprograms)LAPACK(Linear Algebra PACKage) というソフトウェアがリンクされ,利用されています.これがNumPyの処理が高速な理由です.

BLAS(Basic Linear Algebra Subprograms)

数値計算で頻出するベクトル・行列の基本演算を、名前と引数の仕様だけ決めた低レベル API の集合である。実装は CPU ベンダや OSS(OpenBLAS、BLIS、Apple Accelerate など)が提供し、最適化されたアセンブリや SIMD で書かれることが多い。

典型的なレベル分けは次のとおりである。

  • Level 1: ベクトルとベクトル(スカラー倍・内積・ノルム・axpy など)。メモリ参照は O(n) で、演算量も O(n) なので演算密度が低い

  • Level 2: 行列とベクトル(例: GEMV)。演算は O(n²)、データも O(n²) 程度なので、サイズによってはメモリ帯域に律速されやすい。

  • Level 3: 行列と行列(例: GEMM = 一般行列積)。演算 O(n³) に対しデータ O(n²) なので演算密度が高く、キャッシュブロック化と相性が良い。深層学習の線形層や dense LA の心臓部で、高速 GEMM が全体性能を左右することが多い。

要するに、「線形代数の足場となる乗算・加算・コピー・ノルムなどを、共通化された名前で呼べるようにした層」である。

LAPACK(Linear Algebra PACKage)

稠密行列向けのより高レベルな数値線形代数ルーチンの集合である。BLAS(主に Level 2/3)を内部で多用し、次のような問題全体の解法を担う。

  • 線形方程式 (Ax = b)(LU、Cholesky、QR などの分解と前進・後退代入)

  • 最小二乗・過剰決定系

  • 固有値・固有ベクトル(対称 / 非対称、一般化固有値)

  • 特異値分解(SVD)

  • 条件数推定、行列の性質(ランク、慣性など)に関わる補助ルーチン

アルゴリズムは安定性(ピボット、スケーリング)やバックワード誤差の解析が進んでおり、「分解 + 簡単な三角系」 に帰着させてから BLAS で速く回す設計が多い。

関係の整理

BLASLAPACK
抽象度基本演算(ブロック)分解・固有値・SVD などの手続き全体
典型例GEMM, TRSM, AXPY*getrf, *potrf, *geqrf, *gesvd など
役割速い「積み木」その積み木で組んだ「完成品」

NumPy の linalg や SciPy の線形代数、多くのフレームワークの CPU 版は、内部で BLAS/LAPACK(またはそれに相当するライブラリ)に依存していることが多い。

NumPyの設定を確認

NumPyはPython上でimport numpy as npと書くことでimportでき,np.hogehogeで利用できます.バージョンとコンフィグを確認しましょう.

import numpy as np
print(np.__version__)
print(np.__config__.show())
2.4.4
Build Dependencies:
  blas:
    detection method: system
    found: true
    include directory: unknown
    lib directory: unknown
    name: accelerate
    openblas configuration: unknown
    pc file directory: unknown
    version: unknown
  lapack:
    detection method: system
    found: true
    include directory: unknown
    lib directory: unknown
    name: accelerate
    openblas configuration: unknown
    pc file directory: unknown
    version: unknown
Compilers:
  c:
    commands: cc
    linker: ld64
    name: clang
    version: 15.0.0
  c++:
    commands: c++
    linker: ld64
    name: clang
    version: 15.0.0
  cython:
    commands: cython
    linker: cython
    name: cython
    version: 3.2.4
Machine Information:
  build:
    cpu: aarch64
    endian: little
    family: aarch64
    system: darwin
  host:
    cpu: aarch64
    endian: little
    family: aarch64
    system: darwin
Python Information:
  path: /private/var/folders/bb/km69qtt53nlbxc29ljr4c9d00000gn/T/build-env-8jz__88i/bin/python
  version: '3.12'
SIMD Extensions:
  baseline:
  - NEON
  - NEON_FP16
  - NEON_VFPV4
  - ASIMD
  found:
  - ASIMDHP
  - ASIMDDP
  not found:
  - ASIMDFHM

None
2.0.2
Build Dependencies:
  blas:
    detection method: pkgconfig
    found: true
    include directory: /opt/_internal/cpython-3.12.2/lib/python3.12/site-packages/scipy_openblas64/include
    lib directory: /opt/_internal/cpython-3.12.2/lib/python3.12/site-packages/scipy_openblas64/lib
    name: scipy-openblas
    openblas configuration: OpenBLAS 0.3.27  USE64BITINT DYNAMIC_ARCH NO_AFFINITY
      Zen MAX_THREADS=64
    pc file directory: /project/.openblas
    version: 0.3.27
  lapack:
    detection method: pkgconfig
    found: true
    include directory: /opt/_internal/cpython-3.12.2/lib/python3.12/site-packages/scipy_openblas64/include
    lib directory: /opt/_internal/cpython-3.12.2/lib/python3.12/site-packages/scipy_openblas64/lib
    name: scipy-openblas
    openblas configuration: OpenBLAS 0.3.27  USE64BITINT DYNAMIC_ARCH NO_AFFINITY
      Zen MAX_THREADS=64
    pc file directory: /project/.openblas
    version: 0.3.27
Compilers:
  c:
    commands: cc
    linker: ld.bfd
    name: gcc
    version: 10.2.1
  c++:
    commands: c++
    linker: ld.bfd
    name: gcc
    version: 10.2.1
  cython:
    commands: cython
    linker: cython
    name: cython
    version: 3.0.11
Machine Information:
  build:
    cpu: x86_64
    endian: little
    family: x86_64
    system: linux
  host:
    cpu: x86_64
    endian: little
    family: x86_64
    system: linux
Python Information:
  path: /tmp/build-env-8744k94k/bin/python
  version: '3.12'
SIMD Extensions:
  baseline:
  - SSE
  - SSE2
  - SSE3
  found:
  - SSSE3
  - SSE41
  - POPCNT
  - SSE42
  - AVX
  - F16C
  - FMA3
  - AVX2
  not found:
  - AVX512F
  - AVX512CD
  - AVX512_KNL
  - AVX512_KNM
  - AVX512_SKX
  - AVX512_CLX
  - AVX512_CNL
  - AVX512_ICL

None

NumPy・SciPy・scikit-learnの関係

NumPy は多次元配列(ndarray)とその周辺演算を提供し,Pythonにおける科学技術計算の土台になるライブラリです.線形代数の基本は numpy.linalg にまとまっていますが,より専門的な数値計算の多くは SciPy に置かれています.

SciPy は独自の「SciPy 配列」型を別に定義するのではなく,NumPy の配列を入出力の共通フォーマットとして用います.疎行列(scipy.sparse),最適化,信号処理,統計,より高機能な線形代数などがここに集約され,多くの処理は内部で(NumPy と同様に)BLAS / LAPACK 相当のライブラリに依存します.

scikit-learn(パッケージ名 scikit-learn,インポート名 sklearn)は,古典的な機械学習アルゴリズムや前処理,モデル選択用のユーティリティを統一的な API で提供します.実装は NumPy 配列(および一部 scipy.sparse)を入力として受け取り,内部で NumPy や SciPy の数値ルーチンを組み合わせて動作します.三者の関係は,「NumPy がデータ表現と基本的な数値演算 → SciPy がより広い科学計算の部品 → scikit-learn がそれらを束ねた学習・予測のインタフェース」という階層と捉えると分かりやすいです.

依存関係として,SciPy は NumPy を前提とし,scikit-learn は NumPy と SciPy を前提とします.実務では,配列操作や特徴量の準備に NumPy,補間や疎行列,一部の距離・統計処理に scipy のサブモジュール,学習器の fit / predict と評価に sklearn のように役割を分けて使うことが多いです.

NumPyと機械学習・深層学習の開発が活発であることの関係

NumPy は単なる高速配列ライブラリにとどまらず,Python で機械学習や深層学習の開発が活発になるうえでの共通言語の役割を果たしています.ndarray の dtype やメモリレイアウト(連続配列など)が広く共有されているため,データの読み込み,前処理,可視化,古典的 ML,さらに深層学習フレームワークの境界まで,同じ型のまま受け渡ししやすいことが,ライブラリ間の連携コストを下げ,試作や比較実験を速く回せる土台になっています.

深層学習側では,GPU 上の計算用に PyTorchTensorTensorFlowtf.Tensor など,フレームワーク固有のテンソル型が中心です.それでも学習済み重みの保存,メトリクスの集計,データセットのオフライン加工などでは NumPy が使われ,多くの API は NumPy 配列との相互変換(例: PyTorch の .numpy()torch.from_numpy,TensorFlow 2 の .numpy()tf.convert_to_tensor など)を公式に用意しています.つまり「学習・推論のコアは各フレームワークのテンソル,その前後の科学計算やデータパイプラインは NumPy」という分担が定着しており,エコシステム全体の接続部として NumPy が機能していると言えます.

また,数値コアが C / Fortran 側にありつつ Python から短いコードで呼べるという性質は,研究やプロダクト双方で試行錯誤のサイクルを短くするのに寄与します.チュートリアルや書籍,論文の再現コードでも numpy が前提とされることが多く,初学者から実務者まで同じ表現で議論できることが,人材・知識の流動性を高め,結果として開発コミュニティの活発さを支えている一面もあります.

np.ndarrayの使い方

ndarray(N-dimensional array、N次元配列)の使い方を見ていきます.

np.ndarrayの作り方

np.array関数にリスト型オブジェクトを渡すと配列が作成できます.

import numpy as np

def object_checker(obj):
    print("型:",type(obj))
    print("中身:",obj)
    if isinstance(obj, np.ndarray):
        print("行列のshape:",obj.shape)
list1 = [1,2,3]
object_checker(list1)
型: <class 'list'>
中身: [1, 2, 3]
arr1 = np.array(list1)
object_checker(arr1)
型: <class 'numpy.ndarray'>
中身: [1 2 3]
行列のshape: (3,)
list2 = [[1,2,3], [4,5,6]]
arr2 = np.array(list2)
object_checker(arr2)
型: <class 'numpy.ndarray'>
中身: [[1 2 3]
 [4 5 6]]
行列のshape: (2, 3)

numpy関数によるnp.ndarrayの初期化

numpyはPythonに配列(np.ndarray)を提供します。
このnp.arrayはリストやtupleを元に作るだけでなく、np.zeros、np.onesなどの関数からも作ることができます。

【問題1】 np.zerosを使って要素数10の配列を作ってください。

【問題2】 np.onesを使ってshape (10,5)の行列を作ってください。

【問題3】 np.arangeを使って0~9までの値を持ったベクトルを作ってください。

hint: np.arangeはrangeとほぼ同様の動作をします。

np.arrayのshapeの変更

np.arrayはデータを効率的に扱うために、numpyの関数だけで配列の形を変形する事ができます。

もっとも代表的な方法として、np.reshapeがあります。

arr3 = np.arange(20)
arr4 = np.reshape(arr3, (10,2)) # arr3.reshape((10,2))でも可

print(
    "オリジナル:\n",arr3,
    "\nreshape後:\n",arr4
)
オリジナル:
 [ 0  1  2  3  4  5  6  7  8  9 10 11 12 13 14 15 16 17 18 19] 
reshape後:
 [[ 0  1]
 [ 2  3]
 [ 4  5]
 [ 6  7]
 [ 8  9]
 [10 11]
 [12 13]
 [14 15]
 [16 17]
 [18 19]]

【問題4】 ゼロで初期化した要素数30の配列を、5x2x3の三階テンソルに変換してください。

行列の転置

arr5 = arr4.T
arr5
array([[ 0, 2, 4, 6, 8, 10, 12, 14, 16, 18], [ 1, 3, 5, 7, 9, 11, 13, 15, 17, 19]])

np.ndarrayの演算

numpy.ndarrayは高速な行列演算が可能です。

また、計算の結果は尤もらしい型になります。

arr1の要素はint32ですが、1.0などのfloat64と演算する事で、計算結果result1がfloat64として出力されます。

加算

print(result1 := arr1 +1.0)
# result1 = arr1 + 1.0
# print(result1) 
# を省略して書くと print(result1 := arr1 +1.0) になる
print("arr1.dtype:",arr1.dtype)
print("result1.dtype:", result1.dtype)
[2. 3. 4.]
arr1.dtype: int64
result1.dtype: float64

減算

print(result2 := result1 - 5.0)
[-3. -2. -1.]

乗算

print(result3 := result2 * 1.5)
[-4.5 -3.  -1.5]

除算

print(result4 := result3 / 3.0)
[-1.5 -1.  -0.5]

剰余

print(result5 := result4 % 2)
[0.5 1.  1.5]

【問題】result5の各要素を3乗してみよう.

等式

print(result6 := result5 ==1)
[False  True False]

不等式

print(result7 := result5 !=1)
[ True False  True]
print(result8 := result5 >=1)
[False  True  True]
print(result9 := result5 <1)
[ True False False]

要素が含まれているか確認する

1 in result5
True
2 in result5
False
1 not in result5
False
2 not in result5
True

【問題5】 以下の1~5までの処理をして答えを返す関数を作成して、実行してください。

  1. 1~20までの要素を持ったベクトルを用意

  2. ベクトルをreshapeし2x10行列に変換

  3. 配列の各要素を20倍

  4. 配列の各要素のmod 3をとる

  5. 配列の0をTrue、それ以外をFalseに変換

  6. 5の答えをreturn

配列同士の演算

配列同士の演算も、普通の演算子(+,-,*,/,%など)でできます。

加算

np.ones((10,5)) + np.arange(50).reshape((10,5))
array([[ 1., 2., 3., 4., 5.], [ 6., 7., 8., 9., 10.], [11., 12., 13., 14., 15.], [16., 17., 18., 19., 20.], [21., 22., 23., 24., 25.], [26., 27., 28., 29., 30.], [31., 32., 33., 34., 35.], [36., 37., 38., 39., 40.], [41., 42., 43., 44., 45.], [46., 47., 48., 49., 50.]])

減算

np.array([10,9,8]) - np.ones(3)
array([9., 8., 7.])

乗算

np.array([[10,9,8],[11,91,81]]) * np.array([2,3,4])
array([[ 20, 27, 32], [ 22, 273, 324]])
np.array([[10,9,8],[11,91,81]]) * np.array([[11,91,81],[10,9,8]]) 
array([[110, 819, 648], [110, 819, 648]])

除算

np.array([10,9,8]) / np.array([2,3,4])
array([5., 3., 2.])

剰余

np.array([10,9,8]) % np.array([3,4,5])
array([1, 1, 3])

【問題6】

以下の流れで演習用データを作成

  1. 10~19の要素を持つベクトルを用意

  2. 5x2配列にreshape

  3. これを変数xに代入

  4. 15~24の要素を持つベクトルを用意

  5. 5x2配列にreshape

  6. これを変数yに代入

この2つの配列に対して

(1) 加算
(2) 減算
(3) 乗算
(4) 除算
(5) 剰余

を計算

# (1)

# (2)

# (3)

# (4)

# (5)

等式と不等式

返り値はbool値(真偽値)の配列になります。

等式

np.array([5,7,9]) == np.array([3,7,11])
array([False, True, False])

不等式

np.array([5,7,9]) != np.array([3,7,11])
array([ True, False, True])
np.array([5,7,9]) >= np.array([3,7,11])
array([ True, True, False])
np.array([5,7,9]) < np.array([3,7,11])
array([False, False, True])

【問題7】 下の変数x,yについて

(1) 2つの変数で等しい養素をTrue, 等しくない養素をFalseにしたベクトルを出力
(2) x以上のyの養素をTrue, 未満をFalseにしたベクトルを出力

x = np.array([1,2,10,20])
y = np.array([5,-1,20,10])
# (1)

# (2)

内積と外積

内積 np.dot

a = np.arange(0,10).reshape((2,5))
b = np.arange(0,15).reshape((5,3))

print("a:")
print(a)
print("b:")
print(b)

np.dot(a,b)
a:
[[0 1 2 3 4]
 [5 6 7 8 9]]
b:
[[ 0  1  2]
 [ 3  4  5]
 [ 6  7  8]
 [ 9 10 11]
 [12 13 14]]
array([[ 90, 100, 110], [240, 275, 310]])

外積 np.outer

c = a[0]
d = b[0]

print("c:",c,"d:",d)

np.outer(c,d)
c: [0 1 2 3 4] d: [0 1 2]
array([[0, 0, 0], [0, 1, 2], [0, 2, 4], [0, 3, 6], [0, 4, 8]])

【問題8】 ニューラルネットワークの結合重み行列Wがある。

このWと入力行列Xの内積を取り、バイアスベクトルbを足し合わせた値を出力してください。

ただし、W,X,bは以下の値を使ってください。

np.random.seed(seed=1234)

# 入力データ
X = np.random.random((32, 20))

n_feature = X.shape[1]
n_hidden = 5

W = np.random.random((n_feature, n_hidden))

b = np.random.uniform(n_hidden)

配列自体の操作

初期化して作成した配列に対して,インデクシング,reshape,転置,値の代入(上書き),スライスなどができます.

インデクシング(要素アクセス)

配列の要素にアクセスする方法はlistと同じです.

print(x := np.arange(20))
x[1]
[ 0  1  2  3  4  5  6  7  8  9 10 11 12 13 14 15 16 17 18 19]
np.int64(1)

Booleanマスク

元の配列と同じ形状の配列を使うことで,必要な要素だけを取り出すことができる.

x
array([[ 100, 1000, 2, 3, 4], [ 5, 1000, 7, 8, 9], [ 10, 1000, 12, 13, 14], [1000, 1001, 1002, 1003, 1004]])
mask = np.array([[True, False, False, False, False],
                 [False, True, False, False, False],
                 [False, False, True, False, False],
                 [False, False, False, True, False],]
            )
x[mask]
array([ 100, 1000, 12, 1003])

条件式を使えばブールマスクが作れる.

mask = x > 10
mask
array([[ True, True, False, False, False], [False, True, False, False, False], [False, True, True, True, True], [ True, True, True, True, True]])
x[mask]
array([ 100, 1000, 1000, 1000, 12, 13, 14, 1000, 1001, 1002, 1003, 1004])

つまり[]の中に直接条件式を書けばOK

x[x<100]
array([ 2, 3, 4, 5, 7, 8, 9, 10, 12, 13, 14])

reshape

行列の形を変える

print(x := x.reshape((4,5)))
print("形状:", x.shape)
[[ 0  1  2  3  4]
 [ 5  6  7  8  9]
 [10 11 12 13 14]
 [15 16 17 18 19]]
形状: (4, 5)
print(x2 := x.astype(np.float16))
print("型:", x2.dtype)
[[ 0.  1.  2.  3.  4.]
 [ 5.  6.  7.  8.  9.]
 [10. 11. 12. 13. 14.]
 [15. 16. 17. 18. 19.]]
型: float16

【問題9】以下の処理を実装せよ.

  1. 100から119までの要素を持ったベクトルaを作る

  2. aを2x10行列に変形

  3. aを単精度浮動小数点数にキャスト変換

転置

print(x2 := x.T)
print("形状:", x2.shape)
[[ 0  5 10 15]
 [ 1  6 11 16]
 [ 2  7 12 17]
 [ 3  8 13 18]
 [ 4  9 14 19]]
形状: (5, 4)

値の代入

特定要素を置き換え

x2[0,0] = 100

x2
array([[100, 5, 10, 15], [ 1, 6, 11, 16], [ 2, 7, 12, 17], [ 3, 8, 13, 18], [ 4, 9, 14, 19]])

配列の1行目を全て上書き (インデックスは0スタートであることに注意)

上書きしたい要素数と同じ要素数の配列を使えば,その通りに上書きできる.

x2[1] = np.arange(100,104)

x2
array([[100, 5, 10, 15], [100, 101, 102, 103], [ 2, 7, 12, 17], [ 3, 8, 13, 18], [ 4, 9, 14, 19]])

上書きしたい要素数に関係なく,値を一つだけ使えば,それで全ての要素を上書きできる.

x2[1] = 1000
x2
array([[ 100, 5, 10, 15], [1000, 1000, 1000, 1000], [ 2, 7, 12, 17], [ 3, 8, 13, 18], [ 4, 9, 14, 19]])

最後の列だけを上書き

x2[:,-1] = np.arange(1000,1005)
x2
array([[ 100, 5, 10, 1000], [1000, 1000, 1000, 1001], [ 2, 7, 12, 1002], [ 3, 8, 13, 1003], [ 4, 9, 14, 1004]])

【問題10】

x2の0行3列目の要素を99に上書き

スライス

複数の行をスライス

x2[0:2]
array([[ 100, 5, 10, 1000], [1000, 1000, 1000, 1001]])

複数の列をスライス

x2[:,1:3]
array([[ 5, 10], [1000, 1000], [ 7, 12], [ 8, 13], [ 9, 14]])

【問題11】

x2から2~3行目の3列目をスライス