[初心者向け] Python NumPy zeros の完全ガイド

[初心者向け] Python NumPy zeros の完全ガイド:配列をゼロで初期化する強力な方法

プログラミングの世界、特にデータ分析や科学技術計算の分野では、効率的な数値計算が非常に重要になります。Pythonには、その強力な助っ人として「NumPy」というライブラリが存在します。NumPyは、多次元配列(ndarray)を高速に扱うための機能を提供し、Pythonをこれらの分野で欠かせないツールにしています。

NumPyを使い始める際、最初のステップの一つが「配列の作成」です。配列を作成する方法はいくつかありますが、その中でも非常に一般的で、多くの場面で利用されるのが「すべての要素がゼロである配列」を作成する方法です。このような配列は、計算結果を格納するための領域を確保したり、初期状態がゼロであるべきモデルを構築したりする際などに役立ちます。

NumPyでは、この「すべての要素がゼロである配列」を簡単に作成するための関数が用意されています。それが np.zeros() 関数です。

この記事は、NumPy初心者の方を対象に、この np.zeros() 関数について、その基本的な使い方から応用、知っておくべき詳細までを、約5000語のボリュームで徹底的に解説します。この記事を読み終える頃には、zeros 関数を自信を持って使いこなし、NumPyによる配列操作の第一歩をしっかりと踏み出せるようになっているはずです。

さあ、NumPyの世界へ、そして zeros 関数の魅力に触れてみましょう!


目次

  1. はじめに:NumPyと配列 (ndarray) の基本

    • NumPyとは何か?なぜ使うのか?
    • NumPy配列 (ndarray) の概念
    • 配列の初期化の重要性
    • np.zeros() 関数概要
  2. NumPy zeros 関数の基本を学ぶ

    • 基本的な使い方:np.zeros(shape)
    • shape 引数とは?(整数、タプル)
    • 生成される配列のデフォルトのデータ型 (dtype)
  3. データ型 (dtype) を理解し、使いこなす

    • データ型とは?なぜ重要なのか?
    • dtype 引数の使い方
    • 代表的なデータ型とその指定方法(整数、浮動小数点数、ブール値、複素数など)
    • データ型によるメモリ使用量の違い
  4. 高次元配列を zeros で作成する

    • 1次元配列の作成
    • 2次元配列の作成 (np.zeros((行数, 列数)))
    • 3次元配列の作成 (np.zeros((深さ, 行数, 列数)))
    • さらに高次元の配列と shape の解釈
    • shape タプルの各要素が表すもの
  5. NumPy zeros のさまざまな応用例

    • 数値計算における初期値の設定
    • 画像の表現と黒画像の作成
    • 機械学習におけるモデルパラメータの初期化(概念)
    • データのプレースホルダーとしての利用
    • 既存の配列と同じ形状・データ型でゼロ配列を作る:np.zeros_like() の紹介
  6. 他の初期化関数との比較:zeros はいつ使うべきか?

    • np.ones() との違い
    • np.empty() との違い(パフォーマンスと初期値)
    • np.full() との違い
    • 乱数配列生成関数との違い
  7. メモリ効率とパフォーマンス

    • zeros がメモリ上でどのように配置されるか(連続性)
    • 大規模配列作成時のメモリ管理の考慮事項
    • zeros が効率的な理由
  8. エラーハンドリングとよくある落とし穴

    • shape 引数の指定ミス
    • dtype 引数の指定ミス
    • 生成された配列の形状やデータ型を確認する方法
    • 可変なオブジェクトを shape に指定する際の注意
  9. 実践!zeros を使ったコード例集

    • シンプルなゼロ行列の作成と要素へのアクセス
    • 特定の位置に値を代入する
    • 3次元ゼロ配列のスライスと部分的な操作
    • 異なるデータ型でのゼロ配列作成例
  10. まとめ:zeros をマスターして次へ進もう


1. はじめに:NumPyと配列 (ndarray) の基本

NumPyとは何か?なぜ使うのか?

Pythonは非常に人気のあるプログラミング言語ですが、標準のリストなどを使った数値計算は、大量のデータを扱う際には効率が悪いという弱点があります。そこで登場するのが NumPy (Numerical Python) です。

NumPyは、Pythonで高速な数値計算を行うための基盤を提供するライブラリです。特に、多次元配列を効率的に扱うことに特化しており、CやFortranといったコンパイル言語に匹敵する速度で配列演算を行うことができます。これは、NumPyの内部が最適化されたC言語などで書かれているためです。

データサイエンス、機械学習、科学技術計算、画像処理など、数値データを扱うほとんどすべての分野でNumPyは必須のツールとなっています。Pythonを使ってこれらの分野に挑戦するなら、NumPyの習得は避けて通れません。

NumPy配列 (ndarray) の概念

NumPyが扱うデータの基本単位は ndarray (N-dimensional array) と呼ばれる多次元配列オブジェクトです。Pythonのリストと似ていますが、重要な違いがいくつかあります。

  1. データ型: ndarrayのすべての要素は、同じデータ型である必要があります。これにより、メモリの使用効率が上がり、高速な計算が可能になります。
  2. サイズ変更不可: 一度作成されたndarrayのサイズ(要素数や次元数)は基本的に変更できません。サイズを変更したい場合は、新しい配列を作成する必要があります(厳密には、特定の操作で形状が変わるように見えることもありますが、多くの場合、内部的には新しい配列が生成されています)。
  3. ベクトル化演算: ndarrayは、配列全体に対する演算(加算、乗算など)を非常に効率的に行えます。ループを使って要素ごとに処理するよりも、はるかに高速です。

例えば、[1, 2, 3] というPythonリストは、NumPyでは np.array([1, 2, 3]) のような1次元のndarrayとして表現されます。[[1, 2], [3, 4]] というリストは、np.array([[1, 2], [3, 4]]) のような2次元のndarray(行列)として表現されます。

ndarrayは「形状 (shape)」という属性を持ちます。これは、各次元にいくつの要素があるかを示すタプルです。例えば、np.array([[1, 2], [3, 4]]) の形状は (2, 2) です。これは「2行、2列」であることを意味します。

配列の初期化の重要性

プログラムを書く際には、「何かを計算するための入れ物」や「これから使うデータを格納するための領域」をあらかじめ準備しておく必要がよくあります。配列も同様で、使用する前に適切なサイズで作成し、初期値を設定しておく必要があります。

配列の初期化方法はいくつかあります。

  • 既存のリストなどから作成する (np.array())
  • すべての要素を特定の値にする (np.full())
  • ランダムな値で初期化する (np.random.rand(), np.random.randn())
  • すべての要素をゼロで初期化する (np.zeros())
  • すべての要素をイチで初期化する (np.ones())
  • 初期化せずにメモリ領域を確保する (np.empty())

これらの初期化関数は、プログラムの目的に応じて使い分けられます。例えば、計算結果をゼロから累積していく場合や、特定の構造を持つデータをゼロから構築していく場合には、すべての要素がゼロである配列が非常に便利です。

np.zeros() 関数概要

ここで本題となる np.zeros() 関数の登場です。

np.zeros() 関数は、指定した「形状 (shape)」と「データ型 (dtype)」を持つndarrayを作成し、そのすべての要素を 0 (ゼロ) で初期化します。

非常にシンプルですが、NumPyを使ったプログラミングでは頻繁に利用される、まさに基本中の基本と言える関数です。

この記事では、この np.zeros() 関数に焦点を当て、その使い方、様々なオプション、そして関連する重要な概念を詳しく掘り下げていきます。


2. NumPy zeros 関数の基本を学ぶ

それでは、実際に np.zeros() 関数の使い方を見ていきましょう。まずは最も基本的な形式からです。

np.zeros() 関数を使うには、まずNumPyライブラリをインポートする必要があります。慣例として、NumPyは np という短い別名でインポートされます。

python
import numpy as np

これで、np.zeros() のようにNumPyの関数を呼び出せるようになります。

基本的な使い方:np.zeros(shape)

np.zeros() 関数の最も基本的な使い方は、引数に shape だけを指定する形式です。

python
np.zeros(shape)

ここで shape は、作成したい配列の形状を指定します。形状の指定方法にはいくつかバリエーションがあります。

shape 引数とは?(整数、タプル)

shape 引数は、作成する配列の次元数と、各次元のサイズをNumPyに伝えます。

1. 整数で指定する場合:1次元配列を作成

shape に正の整数を一つ指定すると、その整数を要素数とする1次元配列が作成されます。

例:要素数 5 の1次元ゼロ配列を作成する

“`python
import numpy as np

要素数 5 の1次元ゼロ配列を作成

arr_1d = np.zeros(5)
print(arr_1d)
print(“形状:”, arr_1d.shape)
print(“次元数:”, arr_1d.ndim)
print(“データ型:”, arr_1d.dtype)
“`

実行結果:

[0. 0. 0. 0. 0.]
形状: (5,)
次元数: 1
データ型: float64

  • [0. 0. 0. 0. 0.]:すべての要素が 0. となっています。末尾の . は浮動小数点数であることを示しています。
  • 形状: (5,):タプル (5,) は、この配列が1つの次元を持ち、その次元に5つの要素があることを示しています。1要素のタプルは (要素,) のように末尾にカンマがつきます。
  • 次元数: 1:配列が1次元であることを示しています。
  • データ型: float64:要素のデータ型が float64 であることを示しています。これについては後で詳しく説明します。

2. タプルで指定する場合:高次元配列を作成

shape には、作成したい配列の各次元のサイズを指定するタプルを指定するのが一般的です。タプルの要素数が配列の次元数となり、タプルの各要素が対応する次元のサイズとなります。

例:3行 4列 の2次元ゼロ配列(行列)を作成する

“`python
import numpy as np

3行 4列 の2次元ゼロ配列を作成

arr_2d = np.zeros((3, 4))
print(arr_2d)
print(“形状:”, arr_2d.shape)
print(“次元数:”, arr_2d.ndim)
print(“データ型:”, arr_2d.dtype)
“`

実行結果:

[[0. 0. 0. 0.]
[0. 0. 0. 0.]
[0. 0. 0. 0.]]
形状: (3, 4)
次元数: 2
データ型: float64

  • [[0. 0. 0. 0.], ...]:3つの行があり、各行に4つの要素があります。これも浮動小数点数になっています。
  • 形状: (3, 4):タプル (3, 4) は、この配列が2つの次元を持ち、最初の次元(行)に3つの要素、2番目の次元(列)に4つの要素があることを示しています。
  • 次元数: 2:配列が2次元であることを示しています。
  • データ型: float64:要素のデータ型が float64 です。

例:2つの「3行 4列」の構造を持つ3次元ゼロ配列を作成する

“`python
import numpy as np

2つの 3行 4列 の構造を持つ3次元ゼロ配列を作成

arr_3d = np.zeros((2, 3, 4))
print(arr_3d)
print(“形状:”, arr_3d.shape)
print(“次元数:”, arr_3d.ndim)
print(“データ型:”, arr_3d.dtype)
“`

実行結果:

“`
[[[0. 0. 0. 0.]
[0. 0. 0. 0.]
[0. 0. 0. 0.]]

[[0. 0. 0. 0.]
[0. 0. 0. 0.]
[0. 0. 0. 0.]]]
形状: (2, 3, 4)
次元数: 3
データ型: float64
“`

  • [[[...], ...], [...]]:出力形式が入れ子になっています。最初の次元に2つのブロックがあり、各ブロックは3行4列の構造を持っています。
  • 形状: (2, 3, 4):タプル (2, 3, 4) は、最初の次元に2つの要素、2番目の次元に3つの要素、3番目の次元に4つの要素があることを示しています。これは「深さ2、高さ3、幅4」のようなイメージや、「2つの行列があり、それぞれの行列は3行4列である」というイメージで捉えることができます。
  • 次元数: 3:配列が3次元であることを示しています。
  • データ型: float64:要素のデータ型が float64 です。

このように、shape 引数には整数のタプルを指定することで、任意の次元数の配列を作成できます。タプルの各要素は、対応する次元のサイズ(その次元に沿って進むといくつの要素があるか)を表します。

生成される配列のデフォルトのデータ型 (dtype)

上記の例で気づいたかもしれませんが、np.zeros()dtype 引数を特に指定しなかった場合、作成される配列のデータ型はデフォルトで float64 になります。

float64 は、倍精度浮動小数点数と呼ばれるデータ型で、小数点以下の値や非常に大きな/小さな値を表現できます。NumPyの多くの演算はこの float64 をデフォルトとすることが多く、汎用性の高いデータ型です。

しかし、整数値だけを扱う場合や、メモリを節約したい場合など、目的によっては他のデータ型を使用したいことがあります。次のセクションでは、このデータ型 (dtype) について詳しく見ていき、np.zeros() で任意のデータ型を指定する方法を学びます。


3. データ型 (dtype) を理解し、使いこなす

NumPy配列において、要素のデータ型は非常に重要な概念です。np.zeros() を含め、ほとんどのNumPy配列作成関数では、このデータ型を指定することができます。

データ型とは?なぜ重要なのか?

データ型 (dtype または Data Type) とは、配列の各要素がどのような種類の値を保持できるか(整数なのか、小数点数なのか、真偽値なのか、など)と、その値を表現するためにどれだけのメモリ領域を使用するかを定義するものです。

例えば、

  • int 型:整数値を扱います。int32int64 など、使用するメモリサイズによって異なる型があります。
  • float 型:浮動小数点数(小数点以下の値)を扱います。float32float64 などがあります。
  • bool 型:真偽値(True/False)を扱います。
  • complex 型:複素数を扱います。

データ型が重要である理由は主に以下の2点です。

  1. メモリ効率: 同じ要素数の配列でも、データ型によって使用するメモリ量が大きく変わります。例えば、64ビット整数 (int64) は、8ビット整数 (int8) の8倍のメモリを使用します。大規模なデータを扱う場合、適切なデータ型を選ぶことでメモリを節約できます。
  2. 計算の精度と範囲: データ型によって、表現できる値の範囲や精度が異なります。例えば、浮動小数点数は整数よりも広い範囲の値を表現できますが、精度には限界があります。また、整数型では小数点以下の値を扱えません。計算目的に合ったデータ型を選ぶことが重要です。

NumPy配列は、すべての要素が同じデータ型である必要があります。これは、効率的なメモリ配置と高速な演算を実現するためのNumPyの設計思想に基づいています。

dtype 引数の使い方

np.zeros() 関数では、dtype というキーワード引数を使って、作成する配列のデータ型を指定できます。

python
np.zeros(shape, dtype=...)

dtype の値には、NumPyが認識する様々なデータ型を指定できます。文字列で指定することが多いですが、NumPyのデータ型オブジェクトそのものを指定することも可能です。

代表的なデータ型とその指定方法

NumPyでよく使われる代表的なデータ型とその dtype 引数での指定方法をいくつか紹介します。

  • 浮動小数点数 (Floating-point numbers):

    • float64 (デフォルト): 倍精度浮動小数点数 (64ビット)。通常はこちらを使います。指定方法は 'float64' または 'f8' または float
    • float32: 単精度浮動小数点数 (32ビット)。メモリを節約したい場合や、特定の計算で十分な精度の場合に使用します。指定方法は 'float32' または 'f4'.
    • 例:float32 のゼロ配列を作成

    python
    import numpy as np
    arr_float32 = np.zeros((2, 3), dtype='float32')
    print(arr_float32)
    print("データ型:", arr_float32.dtype)

    実行結果:

    [[0. 0. 0.]
    [0. 0. 0.]]
    データ型: float32

  • 整数 (Integers):

    • int64: 64ビット整数。通常はこちらを使います。指定方法は 'int64' または 'i8' または int
    • int32: 32ビット整数。指定方法は 'int32' または 'i4'.
    • int16: 16ビット整数。指定方法は 'int16' または 'i2'.
    • int8: 8ビット整数。指定方法は 'int8' または 'i1'.
    • 符号なし整数 (uint8, uint16 など) もあります。これは負の値を扱えない代わりに、正の値の範囲が広くなります。画像データなどでよく使われる uint8 は、0から255までの値を表現できます。指定方法は 'uint8' または 'u1'.
    • 例:int32 のゼロ配列を作成

    python
    import numpy as np
    arr_int32 = np.zeros((2, 3), dtype='int32')
    print(arr_int32)
    print("データ型:", arr_int32.dtype)

    実行結果:

    [[0 0 0]
    [0 0 0]]
    データ型: int32

    (出力で小数点が付いていないことに注目してください)

    • 例:uint8 のゼロ配列を作成

    python
    import numpy as np
    arr_uint8 = np.zeros((2, 3), dtype='uint8')
    print(arr_uint8)
    print("データ型:", arr_uint8.dtype)

    実行結果:

    [[0 0 0]
    [0 0 0]]
    データ型: uint8

  • ブール値 (Boolean):

    • bool: 真偽値 (True/False) を扱います。内部的には1バイト(8ビット)が使用されます。指定方法は 'bool' または '?'.
    • 例:bool のゼロ配列を作成

    python
    import numpy as np
    arr_bool = np.zeros((2, 3), dtype='bool')
    print(arr_bool)
    print("データ型:", arr_bool.dtype)

    実行結果:

    [[False False False]
    [False False False]]
    データ型: bool

    (zerosで作成すると、bool型では False で初期化されます)

  • 複素数 (Complex numbers):

    • complex128: 倍精度複素数 (実部と虚部それぞれにfloat64を使用するため合計128ビット)。指定方法は 'complex128' または 'c16' または complex
    • complex64: 単精度複素数 (実部と虚部それぞれにfloat32を使用するため合計64ビット)。指定方法は 'complex64' または 'c8'.
    • 例:complex128 のゼロ配列を作成

    python
    import numpy as np
    arr_complex = np.zeros((2, 3), dtype='complex128')
    print(arr_complex)
    print("データ型:", arr_complex.dtype)

    実行結果:

    [[0.+0.j 0.+0.j 0.+0.j]
    [0.+0.j 0.+0.j 0.+0.j]]
    データ型: complex128

    (0.+0.j は複素数としてのゼロを表します)

他にも、文字列型、datetime型、timedelta型など、NumPyはさまざまなデータ型をサポートしています。ただし、初心者の方がまず理解しておくと良いのは、整数、浮動小数点数、ブール値、複素数の基本的な型でしょう。

データ型の名前は、上記のように文字列(例:'int32')や省略形(例:'i4')で指定できます。Pythonの組み込み型(int, float, bool, complex)を指定した場合、NumPyはデフォルトのビット数の型(通常は64ビット版)を選択します。例えば dtype=int とすると int64 に、dtype=float とすると float64 になります。

データ型によるメモリ使用量の違い

データ型によって要素あたりのメモリ使用量が異なるため、配列全体のメモリ使用量も変わります。これは特に大きな配列を扱う際に重要になります。

NumPy配列の要素あたりのメモリサイズは、.itemsize 属性で確認できます。配列全体のメモリサイズは、.nbytes 属性で確認できます (.size * .itemsize)。

例:異なるデータ型で同じ形状の配列を作成し、メモリ使用量を比較

“`python
import numpy as np

shape = (1000, 1000) # 100万要素の配列

arr_float64 = np.zeros(shape, dtype=’float64′)
arr_float32 = np.zeros(shape, dtype=’float32′)
arr_int64 = np.zeros(shape, dtype=’int64′)
arr_int8 = np.zeros(shape, dtype=’int8′)
arr_bool = np.zeros(shape, dtype=’bool’)

print(f”形状: {shape}”)
print(f”float64: 要素あたり {arr_float64.itemsize} バイト, 合計 {arr_float64.nbytes} バイト”)
print(f”float32: 要素あたり {arr_float32.itemsize} バイト, 合計 {arr_float32.nbytes} バイト”)
print(f”int64: 要素あたり {arr_int64.itemsize} バイト, 合計 {arr_int64.nbytes} バイト”)
print(f”int8: 要素あたり {arr_int8.itemsize} バイト, 合計 {arr_int8.nbytes} バイト”)
print(f”bool: 要素あたり {arr_bool.itemsize} バイト, 合計 {arr_bool.nbytes} バイト”)
“`

実行結果例:

形状: (1000, 1000)
float64: 要素あたり 8 バイト, 合計 8000000 バイト (約8MB)
float32: 要素あたり 4 バイト, 合計 4000000 バイト (約4MB)
int64: 要素あたり 8 バイト, 合計 8000000 バイト (約8MB)
int8: 要素あたり 1 バイト, 合計 1000000 バイト (約1MB)
bool: 要素あたり 1 バイト, 合計 1000000 バイト (約1MB)

このように、データ型によって使用するメモリ量が大きく異なることがわかります。特に int8bool はメモリ効率が非常に高いです。扱いたいデータの種類と必要な精度を考慮して、適切な dtype を選択することが、効率的なNumPyプログラミングにおいて重要になります。

np.zeros() は、指定された dtype にしたがって、すべての要素をその型における「ゼロに相当する値」で初期化します。
* 数値型 (int, float, complex): 実際の数値 0 または 0.0 または 0.+0.j
* bool型: False


4. 高次元配列を zeros で作成する

これまでに、np.zeros()shape 引数に整数やタプルを指定することで、1次元、2次元、3次元の配列を作成できることを見てきました。ここでは、高次元配列の作成について改めて整理し、shape タプルの意味をより深く理解しましょう。

NumPyでは、配列の次元を「軸 (axis)」と呼びます。1次元配列は1つの軸、2次元配列は2つの軸(通常は行と列に対応)、3次元配列は3つの軸などとなります。shape タプルは、これらの軸に沿った配列のサイズを示します。

  • shape = (d0,) : 1次元配列、軸0のサイズが d0
  • shape = (d0, d1) : 2次元配列、軸0のサイズが d0 (行数)、軸1のサイズが d1 (列数)
  • shape = (d0, d1, d2) : 3次元配列、軸0のサイズが d0 (深さやブロック数)、軸1のサイズが d1 (行数)、軸2のサイズが d2 (列数)
  • shape = (d0, d1, ..., dn-1) : n次元配列、軸kのサイズが dk

1次元配列の作成

最もシンプルです。shape に整数の N を指定すると、サイズ N の1次元配列ができます。タプル (N,) を指定しても同じ結果になります。

“`python
import numpy as np

整数で指定

arr1 = np.zeros(7)
print(arr1)
print(arr1.shape) # 出力: (7,)

タプルで指定

arr2 = np.zeros((7,))
print(arr2)
print(arr2.shape) # 出力: (7,)
“`

どちらの方法でも同じ (7,) という形状の1次元配列が作成されます。

2次元配列の作成 (np.zeros((行数, 列数)))

2次元配列は行列として考えることができ、最も頻繁に利用される形状の一つです。shape タプルは (行数, 列数) の形式になります。

“`python
import numpy as np

4行 5列 のゼロ行列を作成

matrix = np.zeros((4, 5))
print(matrix)
print(matrix.shape) # 出力: (4, 5)
print(matrix.ndim) # 出力: 2
“`

出力される配列は、4つのリスト(行)があり、それぞれのリストの中に5つのゼロ要素が含まれている構造になります。

3次元配列の作成 (np.zeros((深さ, 行数, 列数)))

3次元配列は、例えば複数の画像データ(各画像は2次元の行列)をまとめて扱いたい場合などに利用されます。shape タプルは (軸0のサイズ, 軸1のサイズ, 軸2のサイズ) となります。一般的なイメージとしては (ブロック数, 各ブロックの行数, 各ブロックの列数) のように捉えることが多いです。

“`python
import numpy as np

3つの「2行 3列」のブロックを持つ3次元ゼロ配列を作成

arr_3d = np.zeros((3, 2, 3))
print(arr_3d)
print(arr_3d.shape) # 出力: (3, 2, 3)
print(arr_3d.ndim) # 出力: 3
“`

出力を見ると、外側の [ が3つあり、それぞれが [[0. 0. 0.], [0. 0. 0.]] という2行3列の構造を持っていることがわかります。shape (3, 2, 3) は、「3つの『面』があり、それぞれの『面』は2行3列である」と解釈できます。

さらに高次元の配列と shape の解釈

理論上、NumPyは非常に高い次元数の配列を扱うことができます(実際にはメモリの制限などがありますが)。shape タプルにさらに多くの要素を指定するだけです。

例:2つの「4つの『2行 3列』の構造」を持つ4次元ゼロ配列を作成

“`python
import numpy as np

arr_4d = np.zeros((2, 4, 2, 3))
print(arr_4d) # 出力はさらに複雑になります
print(arr_4d.shape) # 出力: (2, 4, 2, 3)
print(arr_4d.ndim) # 出力: 4
“`

shape (2, 4, 2, 3) の解釈は以下のようになります。

  • 軸0のサイズが2:全体が2つの大きなブロックに分かれている。
  • 軸1のサイズが4:それぞれの大きなブロックが、さらに4つのサブブロックに分かれている。
  • 軸2のサイズが2:それぞれのサブブロックが、2つの行を持っている。
  • 軸3のサイズが3:それぞれの行が、3つの要素を持っている。

つまり、「2つの『4つの行列の集合』があり、各行列は2行3列である」のような構造になります。

高次元配列を扱う際には、タプルの各要素がどの軸のサイズを表しているのか、そしてそれぞれの次元がどのような意味を持っているのか(例:画像データの場合は (画像の枚数, 高さ, 幅, 色チャンネル数) など)を明確に理解することが重要です。

shape タプルの各要素が表すもの

改めてまとめると、shape タプル (d0, d1, d2, ..., dn-1) において:

  • d0 は一番外側の次元(軸0)のサイズ。
  • d1 はその次の次元(軸1)のサイズ。
  • dn-1 は一番内側の次元(軸n-1)のサイズ。

配列の要素にアクセスする際は、この形状に沿ってインデックスを指定します。例えば、arr_3d[i, j, k] は、軸0のインデックス i、軸1のインデックス j、軸2のインデックス k の位置にある要素を指します。

np.zeros() は、この指定された shape に従って、必要なメモリ領域を確保し、すべての要素をゼロ(指定された dtype におけるゼロ相当値)で埋めるという、非常に明確な役割を果たします。


5. NumPy zeros のさまざまな応用例

np.zeros() 関数は、単にゼロを並べた配列を作るだけでなく、さまざまな実際のプログラミングタスクにおいて役立ちます。ここではいくつかの応用例を紹介します。

数値計算における初期値の設定

線形代数、数値積分、微分方程式の数値解法など、多くの数値計算アルゴリズムでは、計算結果を格納するための配列や、反復計算の初期値を保持するための配列が必要になります。これらの配列をゼロで初期化することはよく行われます。

例:行列演算の結果を格納するゼロ行列

2つの行列 A と B の積 C = A * B を計算する際、結果行列 C のサイズがあらかじめわかっていれば、まず C をゼロで初期化しておき、そこに計算結果を順次書き込んでいくという方法をとることがあります。

“`python
import numpy as np

A = np.array([[1, 2], [3, 4]]) # 2×2 行列
B = np.array([[5, 6], [7, 8]]) # 2×2 行列

AとBの積は2×2になるので、2×2のゼロ行列で結果配列Cを初期化

C = np.zeros((2, 2))

行列積を計算し、結果をCに格納 (実際にはnp.dot()や@演算子を使いますが、ここでは概念説明)

シンプルな要素ごとの加算の例として

for i in range(A.shape[0]):
for j in range(B.shape[1]):
for k in range(A.shape[1]): # または B.shape[0]
C[i, j] += A[i, k] * B[k, j] # 行列積の計算

print(“行列 A:\n”, A)
print(“行列 B:\n”, B)
print(“計算結果 C (ゼロから初期化して計算):\n”, C)

NumPyの行列積演算子 (@) を使った確認

print(“NumPyで行列積を確認:\n”, A @ B)
“`

この例のように、計算前に結果を格納する配列を適切なサイズでゼロ初期化しておくことは、多くの数値アルゴリズムで一般的なパターンです。

画像の表現と黒画像の作成

デジタル画像は、多くの場合、ピクセル値の配列としてコンピュータ内で表現されます。例えば、グレースケール画像は2次元配列、カラー画像(RGB形式)は3次元配列(高さ x 幅 x チャンネル数)として表現されます。ピクセル値が0に近いほど暗く、255(または1.0)に近いほど明るい色になります。

したがって、すべてのピクセル値がゼロである配列は、完全に黒い画像を表現します。np.zeros() 関数は、特定のサイズ(解像度)を持つ黒画像を簡単に作成するために使用できます。

例:256×512ピクセルのグレースケール黒画像を作成

“`python
import numpy as np
import matplotlib.pyplot as plt

256×512 ピクセルのグレースケール黒画像 (画素値 0-255, uint8型)

画像の高さ=256, 幅=512

height = 256
width = 512
black_image_gray = np.zeros((height, width), dtype=’uint8′)

print(“グレースケール黒画像の形状:”, black_image_gray.shape)
print(“グレースケール黒画像のデータ型:”, black_image_gray.dtype)

print(black_image_gray) # 配列全体を表示すると非常に大きくなる

画像として表示 (matplotlibを使用)

plt.imshow(black_image_gray, cmap=’gray’, vmin=0, vmax=255)

plt.title(“Grayscale Black Image”)

plt.axis(‘off’) # 軸を表示しない

plt.show()

(※このコードを実行するには matplotlib が必要です。表示はコメントアウトしています)

“`

例:128×128ピクセルのRGBカラー黒画像を作成

カラー画像は、通常、赤(R)、緑(G)、青(B)の3つのカラーチャンネルを持ちます。形状は (高さ, 幅, チャンネル数) となり、チャンネル数の次元のサイズは通常3(RGBの場合)または4(RGBAの場合)です。画素値は uint8 (0-255) または float32/float64 (0.0-1.0) で表現されます。

“`python
import numpy as np

import matplotlib.pyplot as plt

128×128 ピクセルのRGBカラー黒画像 (画素値 0-255, uint8型)

画像の高さ=128, 幅=128, チャンネル数=3 (RGB)

height = 128
width = 128
channels = 3 # RGB
black_image_rgb = np.zeros((height, width, channels), dtype=’uint8′)

print(“RGBカラー黒画像の形状:”, black_image_rgb.shape)
print(“RGBカラー黒画像のデータ型:”, black_image_rgb.dtype)

print(black_image_rgb) # 配列全体を表示すると非常に大きくなる

画像として表示 (matplotlibを使用)

plt.imshow(black_image_rgb) # デフォルトでRGBとして解釈

plt.title(“RGB Black Image”)

plt.axis(‘off’)

plt.show()

(※このコードを実行するには matplotlib が必要です。表示はコメントアウトしています)

“`

np.zeros() は、このように画像処理における「まっさらな状態」(つまり完全な黒)の画像を生成するのに非常に便利です。

機械学習におけるモデルパラメータの初期化(概念)

機械学習の多くのモデル、特にニューラルネットワークでは、学習を開始する前にモデルのパラメータ(重みやバイアスなど)を初期化する必要があります。多くの場合、パラメータは小さなランダムな値で初期化されますが、特定の層やバイアスにおいては、ゼロで初期化されることもあります。

例えば、バイアス項はしばしばゼロで初期化されます。これは、初期状態で特定の出力が偏るのを防ぐためです。

“`python

(これは概念的な例です。実際のフレームワークは内部でこれを処理します)

例: ある層の重み行列とバイアスベクトルを初期化

num_input_features = 10
num_output_features = 5

重みはランダムに初期化されることが多い

weights = np.random.randn(num_input_features, num_output_features) * 0.01

バイアスはゼロで初期化されることが多い

bias = np.zeros(num_output_features)

print(“バイアスの形状:”, bias.shape)
print(“バイアスの初期値:”, bias)
“`

この例は非常に単純化されていますが、機械学習フレームワーク(TensorFlow, PyTorchなど)の内部では、np.zeros() のような関数がパラメータの初期化に利用されています。

データのプレースホルダーとしての利用

プログラムの中で、後で計算結果や読み込んだデータを格納するための配列を、あらかじめサイズだけ決めて確保しておきたい場合があります。しかし、確保した時点ではまだ有効なデータが入っていないため、一時的にすべての要素をゼロで埋めておくという使い方もできます。これは「プレースホルダー(placeholder)」としての利用です。

“`python
import numpy as np

後で100件のデータポイント(各5次元)を格納するための配列を準備

まだデータはないので、ゼロで初期化しておく

data_placeholder = np.zeros((100, 5))

print(“プレースホルダーの形状:”, data_placeholder.shape)
print(“初期状態:\n”, data_placeholder[:5]) # 先頭5行だけ表示
“`

このように、np.zeros() は、メモリ領域を確保しつつ、その内容が「無効なデータではない」ことを示すためにゼロで埋めるという、実用的な役割も果たします。

既存の配列と同じ形状・データ型でゼロ配列を作る:np.zeros_like() の紹介

np.zeros() と非常によく似た関数に np.zeros_like() があります。この関数は、引数として与えられた「既存の配列」と同じ形状 (shape) と同じデータ型 (dtype) を持つ新しいゼロ配列を作成します。

これは、「この配列と同じ構造の、要素がすべてゼロの配列が欲しい」という場合に非常に便利です。shapedtype を手動で調べる手間が省けます。

“`python
import numpy as np

existing_array = np.array([[1.5, 2.3], [4.1, 5.9]], dtype=’float32′)

existing_array と同じ形状・データ型でゼロ配列を作成

zeros_like_array = np.zeros_like(existing_array)

print(“既存配列:\n”, existing_array)
print(“既存配列の形状:”, existing_array.shape)
print(“既存配列のデータ型:”, existing_array.dtype)

print(“\nzeros_like で作成した配列:\n”, zeros_like_array)
print(“zeros_like で作成した配列の形状:”, zeros_like_array.shape)
print(“zeros_like で作成した配列のデータ型:”, zeros_like_array.dtype)
“`

実行結果:

“`
既存配列:
[[1.5 2.3]
[4.1 5.9]]
既存配列の形状: (2, 2)
既存配列のデータ型: float32

zeros_like で作成した配列:
[[0. 0.]
[0. 0.]]
zeros_like で作成した配列の形状: (2, 2)
zeros_like で作成した配列のデータ型: float32
“`

zeros_like() は、元の配列の shapedtype を自動的に引き継いでくれるため、コードがより簡潔で読みやすくなります。特に、関数の引数として渡された配列に対して、同じ構造のゼロ配列を返したい場合などに重宝します。

np.zeros_like() は、dtype 引数を別途指定することも可能です。その場合、形状は元の配列と同じになりますが、データ型は指定した dtype になります。

“`python
import numpy as np

existing_array = np.array([[1, 2], [3, 4]], dtype=’int64′)

形状は existing_array と同じ、データ型は float32 にしてゼロ配列を作成

zeros_like_with_dtype = np.zeros_like(existing_array, dtype=’float32′)

print(“既存配列の形状:”, existing_array.shape)
print(“既存配列のデータ型:”, existing_array.dtype)
print(“\nzeros_like (dtype指定) で作成した配列:\n”, zeros_like_with_dtype)
print(“zeros_like (dtype指定) で作成した配列の形状:”, zeros_like_with_dtype.shape)
print(“zeros_like (dtype指定) で作成した配列のデータ型:”, zeros_like_with_dtype.dtype)
“`

このように、zeros_like()zeros() の便利なバリエーションとして、既存配列を基準にゼロ配列を作成したい場合に役立ちます。


6. 他の初期化関数との比較:zeros はいつ使うべきか?

NumPyには np.zeros() 以外にも配列を作成・初期化するための関数がいくつかあります。それぞれの関数の特徴を理解することで、np.zeros() を使うべき場面がより明確になります。

代表的な初期化関数として、np.ones(), np.empty(), np.full() があります。

np.ones() との違い

np.ones() 関数は、np.zeros() と同じように shapedtype を引数にとりますが、作成される配列のすべての要素が 1 (イチ) で初期化されます。

“`python
import numpy as np

3×3 のゼロ行列

zero_matrix = np.zeros((3, 3))
print(“ゼロ行列:\n”, zero_matrix)

3×3 のイチ行列

one_matrix = np.ones((3, 3))
print(“イチ行列:\n”, one_matrix)

データ型を指定してイチ行列を作成

one_int_matrix = np.ones((3, 3), dtype=’int32′)
print(“int型のイチ行列:\n”, one_int_matrix)
“`

実行結果:

ゼロ行列:
[[0. 0. 0.]
[0. 0. 0.]
[0. 0. 0.]]
イチ行列:
[[1. 1. 1.]
[1. 1. 1.]
[1. 1. 1.]]
int型のイチ行列:
[[1 1 1]
[1 1 1]
[1 1 1]]

zeros()ones() は非常によく似ており、それぞれ「すべてゼロ」「すべてイチ」で埋めたい場合に使い分けます。例えば、何らかの計算で「単位行列」に近いものを初期状態として使いたい場合などに ones() が使われることがあります。

np.empty() との違い(パフォーマンスと初期値)

np.empty() 関数も、shapedtype を引数にとり、指定された形状とデータ型を持つ新しい配列を作成します。しかし、np.empty() は配列の要素を特定の初期値で埋めません。 メモリを確保するだけで、そのメモリ領域にたまたま入っていた「ゴミ」のような値がそのまま配列の要素として表示されます。

“`python
import numpy as np

3×3 のゼロ配列

zero_matrix = np.zeros((3, 3))
print(“ゼロ配列 (zeros):\n”, zero_matrix)

3×3 の空配列 (empty)

empty_matrix = np.empty((3, 3))
print(“空配列 (empty):\n”, empty_matrix)
“`

実行結果例:

ゼロ配列 (zeros):
[[0. 0. 0.]
[0. 0. 0.]
[0. 0. 0.]]
空配列 (empty):
[[1.12340000e-309 0.00000000e+000 4.56780000e-310]
[9.87650000e-311 2.34560000e-309 7.89010000e-310]
[5.43210000e-309 6.54320000e-310 8.76540000e-311]]

empty で作成された配列には、全く予測できない値が入っていることがわかります。これらの値は、その時々にメモリのその場所に存在していた内容です。

empty() を使うメリットは、zeros()ones() よりも高速に配列を作成できる可能性がある点です。 なぜなら、zeros()ones() はメモリ領域を確保した後にすべての要素にゼロやイチを書き込むという追加の作業を行いますが、empty() はメモリを確保するだけでその後の書き込みを行わないためです。

empty() を使うべき場面:

  • 配列を作成した後、すぐにすべての要素を自分で計算した値で上書きする場合。初期値が何であっても関係なく、上書きする方が速いからです。
  • パフォーマンスが非常に重要で、初期値が不要な場合。

zeros() を使うべき場面:

  • 配列の初期値が必ずゼロである必要がある場合。
  • 計算結果を累積していく場合など、初期状態がゼロであることが前提となる場合。
  • 配列を作成後、すぐにすべての要素を上書きするわけではない場合。empty() で作成した配列の要素を読もうとすると、不定な値(ゴミ)を読んでしまう危険性があるためです。

初心者の方は、まず zeros()ones() を使うことをお勧めします。empty() は、パフォーマンスが本当にボトルネックになっていることが分かった上で、初期値が不要な場合に検討すべき関数です。不用意に empty() を使うと、意図しない「ゴミ」の値を使った計算を行ってしまい、バグの原因となる可能性があります。

np.full() との違い

np.full() 関数は、指定した shapedtype を持つ配列を作成し、そのすべての要素を指定した任意の値で初期化します。

“`python
import numpy as np

3×3 のゼロ行列 (zeros)

zero_matrix = np.zeros((3, 3))
print(“ゼロ行列 (zeros):\n”, zero_matrix)

3×3 のイチ行列 (ones)

one_matrix = np.ones((3, 3))
print(“イチ行列 (ones):\n”, one_matrix)

3×3 のすべての要素が 7 である配列 (full)

full_matrix = np.full((3, 3), 7)
print(“要素が7の配列 (full):\n”, full_matrix)

データ型を指定して要素が 3.14 の配列を作成

full_float_matrix = np.full((2, 4), 3.14, dtype=’float32′)
print(“要素が3.14のfloat32配列 (full):\n”, full_float_matrix)
“`

実行結果:

ゼロ行列 (zeros):
[[0. 0. 0.]
[0. 0. 0.]
[0. 0. 0.]]
イチ行列 (ones):
[[1. 1. 1.]
[1. 1. 1.]
[1. 1. 1.]]
要素が7の配列 (full):
[[7 7 7]
[7 7 7]
[7 7 7]]
要素が3.14のfloat32配列 (full):
[[3.14 3.14 3.14 3.14]
[3.14 3.14 3.14 3.14]]

np.full() は、np.zeros()np.ones() のより一般的なバージョンと言えます。np.zeros(shape, dtype)np.full(shape, 0, dtype) とほぼ等価です(ただし、zeros の方がゼロでの初期化に特化している分、わずかに効率的な場合があります)。同様に np.ones(shape, dtype)np.full(shape, 1, dtype) とほぼ等価です。

  • zeros() は、初期値がゼロである場合に特化し、コードの意図が明確になります。
  • ones() は、初期値がイチである場合に特化し、コードの意図が明確になります。
  • full() は、初期値が任意の値である場合に使用します。

目的に応じてこれらの関数を使い分けることで、より分かりやすく効率的なコードを書くことができます。初期値がゼロであるという目的が明確であれば、迷わず np.zeros() を使うのが良いでしょう。

乱数配列生成関数との違い

NumPyの random モジュールには、乱数で配列を初期化するための関数があります(例:np.random.rand(), np.random.randn(), np.random.randint())。これらの関数は、初期値が予測不能な乱数である必要がある場合(例:シミュレーション、機械学習の重み初期化の一部など)に使用されます。

一方、np.zeros() は、初期値が厳密に 0 であることが保証されています。これは、計算の基点としてゼロが必要な場合や、特定のパターンを持つ配列をゼロから構築していく場合などに利用されます。

乱数配列は「初期状態にランダムな変動を与えたい」場合、zerosonesfull は「初期状態を完全に制御したい」場合にそれぞれ使われます。


7. メモリ効率とパフォーマンス

NumPy配列、特に zeros で作成される配列は、効率的なメモリ使用と高速な処理という点で優れています。その理由を簡単に説明します。

zeros がメモリ上でどのように配置されるか(連続性)

NumPy配列の大きな特徴の一つは、その要素がコンピュータのメモリ上で連続して配置されることが多いという点です。Pythonの標準リストは、要素自体はメモリのあちこちに分散して配置され、リストオブジェクトはそれらの要素への参照(メモリ上の場所を示す情報)を保持しています。これに対し、NumPy配列は、すべての要素がメモリ上で隣り合うように格納されます。

np.zeros() で配列を作成する際、NumPyは指定された shapedtype に基づいて、必要な合計メモリサイズ(要素数 × 各要素のバイト数)を計算し、そのサイズの連続したメモリブロックを確保します。そして、その確保されたメモリブロックのすべてをビットパターンとしてのゼロで埋めます。

この「連続したメモリ配置」にはいくつかのメリットがあります。

  • 高速なアクセス: CPUがメモリからデータを読み込む際に、連続した領域であれば効率的にまとめて読み込むことができます(キャッシュ効率が良い)。
  • ベクトル化演算: NumPyの多くの演算は、この連続したメモリを利用して、CやFortranで実装された最適化されたコードパスを通じて実行されます。これにより、Pythonのループよりも格段に高速な一括演算が可能になります。

np.zeros() は、この連続したメモリブロックを確保し、効率的に初期値を設定するための関数です。

大規模配列作成時のメモリ管理の考慮事項

np.zeros() は指定された shapedtype に基づいてメモリを確保します。もし非常に大きな shape を指定した場合、コンピュータの利用可能なメモリ量を超える可能性があります。その場合、MemoryError が発生する可能性があります。

例:非常に大きな配列を作成しようとする(注意:実行するとPCの動作が遅くなったりエラーになる可能性があります)

“`python

import numpy as np

非常に大きな配列を作成しようとする例 (注意: 実行には大量のメモリが必要)

shape = (30000, 30000) # 9億要素

dtype = ‘float64’ # 8バイト/要素

total_bytes = 900000000 * 8 # 約 7.2 GB

try:

big_array = np.zeros(shape, dtype=dtype)

print(f”サイズ {shape} の配列を作成できました。”)

except MemoryError:

print(f”サイズ {shape} の配列を作成できませんでした。メモリが足りません。”)

print(f”必要なメモリの目安: 約 {total_bytes / (1024**3):.2f} GB”)

“`

大規模な配列を扱う場合は、コンピュータに搭載されているメモリ量を意識することが重要です。必要なメモリサイズは、shape の要素をすべて掛け合わせたもの(要素数)に dtype の要素あたりのバイト数 (.itemsize) を掛けた値で計算できます。

もし必要な配列サイズがメモリに収まらない場合は、データを分割して処理したり、よりメモリ効率の良いデータ型 (int8float32 など) を使用したり、ディスク上にデータを一時保存したりするといった工夫が必要になります。

zeros が効率的な理由

np.zeros() が効率的である主な理由は、以下の通りです。

  1. 連続メモリ確保: オペレーティングシステムに対して、必要なサイズの連続メモリブロックを一度に要求するため、断片化が少なく効率的です。
  2. 最適化された初期化: 確保したメモリ領域全体をゼロで埋める処理は、低レベル言語(Cなど)で最適化されており、非常に高速に行われます。要素ごとにPythonのループで 0 を代入するよりも圧倒的に速いです。

np.zeros() は、NumPyの配列作成関数の中でも特にシンプルで基本的な操作(ゼロ埋め)を行うため、非常に効率的に実装されています。配列を作成するだけであれば、np.empty() が理論上は最速ですが、前述の通り初期値が不定であるリスクがあります。初期値をゼロにしたいという明確な目的がある場合は、np.zeros() が速度と安全性のバランスの取れた選択肢となります。


8. エラーハンドリングとよくある落とし穴

np.zeros() は比較的シンプルな関数ですが、使い方を間違えるとエラーが発生したり、意図しない結果になったりすることがあります。ここでは、よくある落とし穴とその対処法について説明します。

shape 引数の不正な指定

shape 引数には、配列の各次元のサイズを示す正の整数を指定する必要があります。負の値や非整数(浮動小数点数)、あるいはNumPyが形状として解釈できないオブジェクトを指定するとエラーになります。

“`python
import numpy as np

例1: shape に負の整数を指定

try:
arr = np.zeros(-5)
except ValueError as e:
print(f”エラー例1: {e}”)

例2: shape に浮動小数点数を指定

try:
arr = np.zeros(5.5)
except TypeError as e:
print(f”エラー例2: {e}”)

例3: shape タプルに負の整数を含む

try:
arr = np.zeros((2, -3))
except ValueError as e:
print(f”エラー例3: {e}”)

例4: shape タプルの要素に非整数を含む

try:
arr = np.zeros((2, 3.0)) # 3.0 は整数として解釈されるためエラーにならない
arr = np.zeros((2, 3.5))
except TypeError as e:
print(f”エラー例4: {e}”)

例5: shape に None を指定 (多くの場合はエラー)

try:
arr = np.zeros(None)
except TypeError as e:
print(f”エラー例5: {e}”)
“`

出力例:

エラー例1: negative dimensions are not allowed
エラー例2: 'float' object cannot be interpreted as an integer
エラー例3: negative dimensions are not allowed
エラー例4: 'float' object cannot be interpreted as an integer
エラー例5: 'NoneType' object cannot be interpreted as an integer

これらのエラーは、shape 引数に適切な整数または整数のタプルが渡されていない場合に発生します。shape は、作成したい配列のサイズを正確に定義するものであるため、必ず正の整数で指定するように注意してください。

dtype の不正な指定

dtype 引数には、NumPyが認識できる有効なデータ型を指定する必要があります。存在しないデータ型名や、NumPyが配列要素の型として扱えないオブジェクトを指定するとエラーになります。

“`python
import numpy as np

例1: 存在しないデータ型名を指定

try:
arr = np.zeros((2, 2), dtype=’my_custom_type’)
except TypeError as e:
print(f”エラー例1: {e}”)

例2: 配列要素として不適切なオブジェクトを指定

try:
arr = np.zeros((2, 2), dtype=object()) # 適当なオブジェクト
except TypeError as e:
print(f”エラー例2: {e}”)
“`

出力例:

エラー例1: data type 'my_custom_type' not understood
エラー例2: data type not understood

dtype を指定する際は、NumPyがサポートする標準的なデータ型名(文字列)またはNumPyのデータ型オブジェクトを使用してください。よく使うデータ型については、セクション3で紹介したものを参考にしてください。

生成された配列の形状やデータ型の確認方法

意図した通りに配列が作成されたかを確認するには、作成したNumPy配列オブジェクトの .shape 属性と .dtype 属性を参照するのが確実です。

“`python
import numpy as np

arr = np.zeros((5, 10), dtype=’uint8′)

print(“作成された配列の形状:”, arr.shape)
print(“作成された配列のデータ型:”, arr.dtype)
“`

この確認を習慣づけることで、shapedtype の指定ミスに早期に気づくことができます。

ミュータブルなオブジェクト(リストなど)を shape に入れる場合の注意(タプル推奨)

np.zeros()shape 引数は、内部的にはタプルに変換されて処理されます。shape を指定する際に、リスト [m, n] のようにリストで指定することも可能ですが、一般的にはタプル (m, n) で指定することが推奨されます。

リストは作成後に要素を変更できる(ミュータブルな)オブジェクトですが、タプルは変更できない(イミュータブルな)オブジェクトです。配列の形状は通常、一度作成したら変更されない静的な情報とみなされます。このため、変更される可能性のあるリストよりも、変更されないことが保証されているタプルを使用する方が、コードの意図が明確になり、予期せぬバグを防ぐ上で好ましいとされています。

“`python
import numpy as np

タプルでshapeを指定 (推奨)

arr_tuple = np.zeros((2, 3))
print(“タプルで作成した配列:”, arr_tuple.shape)

リストでshapeを指定 (可能だが非推奨)

arr_list = np.zeros([2, 3])
print(“リストで作成した配列:”, arr_list.shape)

非推奨な例:shapeとして使ったリストを後で変更しても、配列の形状は変わらない

my_shape = [2, 3]
arr_mutable_shape = np.zeros(my_shape)
print(“作成時のshapeリスト:”, my_shape)
print(“作成した配列の形状:”, arr_mutable_shape.shape)

my_shape[0] = 5 # リストを変更
print(“変更後のshapeリスト:”, my_shape)
print(“配列の形状は変更されない:”, arr_mutable_shape.shape) # 配列の形状は (2, 3) のまま
“`

np.zeros() に渡された時点の shape の値(リストの内容)が使用されて配列が作成され、それ以降、元のリストオブジェクトを変更しても、既に作成された配列の形状には影響しません。混乱を避けるためにも、shape 引数にはタプルを使用するのが良い習慣です。

これらの落とし穴に注意することで、np.zeros() をより安全かつ効果的に使いこなすことができるようになります。


9. 実践!zeros を使ったコード例集

これまでに学んだ np.zeros() の使い方、データ型、高次元配列の作成方法などを組み合わせた、より実践的なコード例を見てみましょう。実際にコードを入力して試してみることをお勧めします。

シンプルなゼロ行列の作成と要素へのアクセス

まず、基本的な2次元ゼロ行列を作成し、その要素にアクセスする方法を確認します。

“`python
import numpy as np

4行 6列 のゼロ行列を作成 (デフォルトは float64)

zero_matrix = np.zeros((4, 6))
print(“元のゼロ行列:\n”, zero_matrix)
print(“形状:”, zero_matrix.shape)
print(“データ型:”, zero_matrix.dtype)

特定の要素にアクセス

行インデックスは 0 から 3, 列インデックスは 0 から 5

print(“\n(0, 0) の要素:”, zero_matrix[0, 0])
print(“(1, 2) の要素:”, zero_matrix[1, 2])
print(“(3, 5) の要素:”, zero_matrix[3, 5])

行全体にアクセス

print(“\n1行目 (インデックス0):”, zero_matrix[0, :]) # または zero_matrix[0]
print(“3行目 (インデックス2):”, zero_matrix[2])

列全体にアクセス

print(“\n1列目 (インデックス0):”, zero_matrix[:, 0])
print(“4列目 (インデックス3):”, zero_matrix[:, 3])

部分的な領域 (スライス) にアクセス

行インデックス 1から2まで (2を含む), 列インデックス 2から4まで (4を含む)

print(“\n部分領域 (1:3行, 2:5列):\n”, zero_matrix[1:3, 2:5])
“`

np.zeros() で作成した配列は、他のNumPy配列と同様にインデックスやスライスを使って要素にアクセスできます。初期値がすべてゼロなので、アクセスした要素はすべて 0. (float64の場合) となります。

特定の位置に値を代入する

作成したゼロ配列は、後から特定の要素や領域に値を代入して内容を変更できます。

“`python
import numpy as np

5×5 のゼロ行列を作成 (int型)

zero_matrix_int = np.zeros((5, 5), dtype=’int32′)
print(“初期状態:\n”, zero_matrix_int)

特定の要素に値を代入

zero_matrix_int[2, 2] = 99 # 中央の要素
print(“\n(2, 2) に 99 を代入後:\n”, zero_matrix_int)

行全体に値を代入

zero_matrix_int[0, :] = 1 # 0行目をすべて 1 に

zero_matrix_int[0] = 1 # こちらも同じ結果

print(“\n0行目に 1 を代入後:\n”, zero_matrix_int)

列全体に値を代入

zero_matrix_int[:, 4] = 5 # 4列目をすべて 5 に
print(“\n4列目に 5 を代入後:\n”, zero_matrix_int)

部分的な領域 (スライス) に値を代入

行インデックス 1から3まで (3を含まない), 列インデックス 1から3まで (3を含まない)

すなわち、元の配列の右上2×2の領域に 77 を代入

zero_matrix_int[1:3, 1:3] = 77
print(“\n領域 (1:3, 1:3) に 77 を代入後:\n”, zero_matrix_int)

領域に別の配列を代入 (形状が一致する必要がある)

sub_matrix = np.array([[10, 11], [12, 13]])
zero_matrix_int[3:5, 0:2] = sub_matrix
print(“\n領域 (3:5, 0:2) にサブ行列を代入後:\n”, zero_matrix_int)
“`

このように、np.zeros() で「入れ物」だけ先に作っておき、後から必要なデータを書き込んでいくという使い方が一般的です。

3次元ゼロ配列のスライスと部分的な操作

3次元以上の配列でも、同様にインデックスやスライスを使って部分的にアクセス・操作できます。

“`python
import numpy as np

2つの 3行 4列 のブロックを持つ3次元ゼロ配列を作成

arr_3d = np.zeros((2, 3, 4), dtype=’int32′)
print(“初期状態 (3次元):\n”, arr_3d)

最初のブロック (軸0 のインデックス 0) にアクセス

block1 = arr_3d[0, :, :] # または arr_3d[0]
print(“\n最初のブロック:\n”, block1)
print(“形状:”, block1.shape)

最初のブロックの特定要素に値を代入 (元の配列も変更される)

arr_3d[0, 1, 2] = 100 # 最初のブロックの 2行目(idx 1), 3列目(idx 2)

2番目のブロック (軸0 のインデックス 1) の 2行目 (軸1 のインデックス 1) 全体に値を代入

arr_3d[1, 1, :] = 200

2番目のブロックの 特定の列 (軸2 のインデックス 0) 全体に値を代入

arr_3d[1, :, 0] = 300

最初のブロックの部分領域に値を代入 (2×2)

arr_3d[0, 0:2, 0:2] = 50

print(“\n操作後の 3次元配列:\n”, arr_3d)
“`

3次元配列のスライスは慣れるまで少し難しいかもしれませんが、shape タプル (深さ, 行数, 列数) を意識すると理解しやすくなります。arr_3d[i, j, k]i 番目のブロックの j 行目の k 列目の要素を指します。スライス : はその次元のすべての要素を選択することを意味します。

異なるデータ型でのゼロ配列作成例

最後に、いくつかの異なるデータ型でゼロ配列を作成する例をまとめて示します。

“`python
import numpy as np

float64 (デフォルト)

arr_float64 = np.zeros((2, 2))
print(f”\nfloat64:\n{arr_float64}\ndtype: {arr_float64.dtype}”)

int8

arr_int8 = np.zeros((2, 2), dtype=’int8′)
print(f”\nint8:\n{arr_int8}\ndtype: {arr_int8.dtype}”)

uint16 (符号なし16ビット整数)

arr_uint16 = np.zeros((2, 2), dtype=’uint16′)
print(f”\nuint16:\n{arr_uint16}\ndtype: {arr_uint16.dtype}”)

bool

arr_bool = np.zeros((2, 2), dtype=’bool’)
print(f”\nbool:\n{arr_bool}\ndtype: {arr_bool.dtype}”)

complex64

arr_complex64 = np.zeros((2, 2), dtype=’complex64′)
print(f”\ncomplex64:\n{arr_complex64}\ndtype: {arr_complex64.dtype}”)

Python標準の型名を使用 (多くの場合64ビット版になる)

arr_int = np.zeros((2, 2), dtype=int)
arr_float = np.zeros((2, 2), dtype=float)
arr_complex = np.zeros((2, 2), dtype=complex)
print(f”\nint (Python): dtype: {arr_int.dtype}”) # int64 になることが多い
print(f”float (Python): dtype: {arr_float.dtype}”) # float64 になることが多い
print(f”complex (Python): dtype: {arr_complex.dtype}”) # complex128 になることが多い
“`

これらの例からわかるように、dtype 引数を適切に指定することで、必要に応じた様々な種類のゼロ配列を作成できます。扱うデータの性質に合わせて最適なデータ型を選択することが、メモリ効率と計算精度の両面で重要です。


10. まとめ:zeros をマスターして次へ進もう

この記事では、Pythonの数値計算ライブラリNumPyにおける np.zeros() 関数について、初心者の方にも理解できるように詳細に解説しました。

重要なポイントを改めて振り返りましょう。

  • np.zeros() は、指定した形状 (shape) とデータ型 (dtype) を持つNumPy配列を作成し、すべての要素をゼロで初期化する関数です。
  • shape 引数には、作成したい配列の次元ごとのサイズを、整数のタプルとして指定します(1次元の場合は単一の整数でも可)。例: (5,), (3, 4), (2, 3, 4)
  • dtype 引数には、配列の要素のデータ型を指定します。指定しない場合のデフォルトは float64 です。整数 (int), 浮動小数点数 (float), ブール値 (bool), 複素数 (complex) など、様々なデータ型を指定できます。適切な dtype の選択は、メモリ効率や計算精度に影響します。
  • np.zeros() で作成された配列は、数値計算、画像処理、機械学習など、多くの場面で初期値やプレースホルダーとして利用されます。
  • 既存の配列と同じ形状・データ型でゼロ配列を作成したい場合は、np.zeros_like() 関数が便利です。
  • NumPyには他にも np.ones(), np.empty(), np.full() など、様々な初期化関数がありますが、np.zeros() は初期値がゼロであることを明確に示す、非常に一般的で基本的な関数です。
  • np.empty() は初期値が不定である代わりに配列作成が高速になる可能性がありますが、初心者の方はまず zeros を使うのが安全です。
  • np.zeros() は、NumPyの内部的な最適化により、連続したメモリ領域を確保し高速に初期化を行います。ただし、非常に大きな配列を作成する場合はメモリ容量に注意が必要です。
  • shapedtype の指定ミスはエラーの原因になります。作成した配列の .shape.dtype を確認する習慣をつけましょう。shape にはタプルを使うのが推奨です。

np.zeros() 関数は、NumPyを使ったデータ操作の本当に最初のステップです。この関数をしっかりと理解し使いこなせるようになることは、NumPyの学習を進める上で非常に強固な基盤となります。

この記事が、あなたが np.zeros() を自信を持って使いこなし、さらにNumPyの奥深い世界へ踏み出すための一助となれば幸いです。

NumPyには zeros 以外にも、配列を扱うための強力な機能がたくさんあります。配列のインデックス操作、スライス、ブールインデックス、ユニバーサル関数 (ufunc)、ブロードキャスティング、線形代数、乱数生成など、学ぶべきことはたくさんあります。

この記事で得た知識を活かして、ぜひ実際にさまざまなコードを書いて、NumPyの配列操作をさらに深く探求してみてください。練習あるのみです!

Happy Coding!


(総文字数: 約 5000語)

コメントする

メールアドレスが公開されることはありません。 が付いている欄は必須項目です

上部へスクロール