Python言語の文法を、コードを書く流れに沿って説明していく連載。前回と今回は、値やデータの型を説明。今回はその後編として、list型/tuple型/dict型、それら以外のオブジェクトの型を取り上げる。
この記事は会員限定です。会員登録(無料)すると全てご覧いただけます。
ご注意:本記事は、@IT/Deep Insider編集部(デジタルアドバンテージ社)が「deepinsider.jp」というサイトから、内容を改変することなく、そのまま「@IT」へと転載したものです。このため用字用語の統一ルールなどは@ITのそれとは一致しません。あらかじめご了承ください。
前回は、基本的なデータの型である「bool型(ブール型)」「int型/float型(数値型)」「str型(文字列型)」について解説した。今回は、その後編として「list型(リスト型)」「tuple型(タプル型)」「dict型(辞書型)」、それら以外のモジュールや何らかのオブジェクトの型について説明する。※脚注や図、コードリストの番号は前回からの続き番号としている。
本連載は、実際にライブラリ「TensorFlow」でディープラーニングのコードを書く流れに沿って、具体的にはLesson 1で掲載した図1-a/b/c/dのサンプルコードの順で、基礎文法が学んでいけるように目次を構成している。今回は、図1-a/b/d内の一部コードを取り上げる。
今回、本稿で説明するのは、図1-a/b/d【再掲】における赤枠内のコードのみとなる。青枠内のコードは前回説明した内容となる。
なお、本稿で示すサンプルコードの実行環境については、Lesson 1を一読してほしい。
Lesson 1でも示したように、本連載のすべてのサンプルコードは、下記のリンク先で実行もしくは参照できる。
それでは、list型/tuple型/dict型を順に説明していこう。なお今回は、説明すべき文法要素が多く、これまでよりも少し長めの記事となっている。
変数には、前回Lesson 4で見たような「モジュール」だけでなく、「数値」「文章」「一覧データ」など、さまざまな値/データが代入できる。例えばリスト8は、前掲の図1-b/d内のサンプルコードの中にある「変数に対して何かを代入している行」(=冒頭で示した【再掲】画像の赤枠の内容)の中から、list型/tuple型/dict型に関するものだけを抜き出して並べたものだ。
#import tensorflow as tf
#mnist = tf.keras.datasets.mnist
# 以下のコードを動かすためには、上記2行を事前に実行しておく必要がある
#---------------------------------------------------------------------
# 変数への「タプル値」の代入
(x_train, y_train),(x_test, y_test) = mnist.load_data()
# 「タプル」の構文を応用して、複数の変数にまとめて代入
x_train, x_test = x_train / 255.0, x_test / 255.0
# 以下は「タプル」の構文を応用する同じ方法なので説明を省略
# predictions_array, true_label, img = predictions_array[i], true_label[i], img[i]
この例では、主にタプル値のデータ/値が変数に代入されている。
前回説明したbool型/int型/float型/str型は、単一のデータを表現するための型だった。一方、今回説明する下記の3つの型は、複数のデータをまとめて扱うための組み込み型(コレクションもしくはコンテナーと呼ばれる)である(※組み込み型はこれですべてではなく、今回紹介する分のみを記載している)。
それぞれ具体的なコードで示しながら説明していこう。ちなみにset型(集合型)というコレクションもあるが、これは機械学習では比較的出番が少ないので、本稿では説明を割愛する。
list型(リスト型)
list型は前掲のリスト8には含まれていなかったので、ここでは仮のサンプルコードを提示する(リスト8-1)。
abc_list = ['a', 'b', 'c', 'd', 'e']
abc_list # ['a', 'b', 'c', 'd', 'e']と出力される
変数への「リスト値」の代入をイメージ化すると、図10-1のようになる(※前回の絵と大差ないが、値の部分の表現が少し変わっている)。このようにリストは、複数の値が1列に順に並んでひとまとめになったものをイメージするとよい。
このコード例では、変数abc_listに、「'a'、'b'、'c'、'd'、'e'」という5個の文字列をまとめたリスト値を設定(=代入)している。
このようにリストは、角括弧[〜]で挟んで記述する。括弧の中はカンマ','で区切る(※括弧やカンマの間にある半角スペースはあってもなくてもよいが、一般的にはカンマの後に半角スペースを1つ入れる)。ここでは'a'などの文字列値にしているが、個々の値(=オブジェクト)の型は何でもよい。
機械学習/ディープラーニングのデータは、数学計算のためにもまとめて扱って管理する必要があるので、このリスト型(あるいはリストと同様に扱えるライブラリ「NumPy」のの配列など)を多用する。よってリスト型については、特によく理解しておく必要がある。より深しい内容を、『機械学習&ディープラーニング入門(データ構造編)』で説明しているので、併せて参照してほしい。
◇ インデクシング(要素データの取得)
前回説明したbool型/int型/float型/str型は単一のデータだったので、変数からデータを取得する場合は、単に変数名を書くだけだった。それらと違って、コレクションの場合は、複数のデータがまとまったコレクションの中から、単一のデータを取り出さなければならない場面がある。その取り出し方法(インデクシングと呼ぶ)について簡単に説明しておこう。
list型の場合は、変数名の後に続けた角括弧[〜]の中にインデックス番号(後述)を記載して、取り出すことになる。インデックス番号(index)とは、0からスタートして、1、2、3……と並ぶ整数値のことで、「リストの要素数分」(つまり0〜「リスト要素数 - 1」)の整数値が使える。例えば先ほどの変数abc_listであれば、「'a'のインデックス番号=0からスタートして、'b'のインデックス番号=1、'c'のインデックス番号=2、'd'のインデックス番号=3、'e'のインデックス番号=4」となる(図10-2)。
例を挙げると、変数abc_listに代入されているlist型オブジェクトから'b'という要素データを取り出したいのであれば、リスト8-2のように、変数名abc_listの後に続けた角括弧[〜]の中にインデックス番号の1を記載すればよい。
abc_list[1]
# 'b'と出力される
ちなみに、前掲のリスト8にpredictions_array[i]やtrue_label[i]というコードも含まれていたが、これらは厳密にはリストではなく、ライブラリ「NumPy」の多次元配列(ndarray型のオブジェクト)である。ndarrayは、リストと同じようにインデクシングすることができる。つまりpredictions_array[i]やtrue_label[i]というコードもリスト8-2と同じことを行っているというわけだ。
整数値ではなく変数名のiが記載されているが、このiには0や1などの整数値が代入されているのだ。インデックス番号を表す変数の名前は、「index」の頭文字からiと命名することが慣例となっているので覚えておいてほしい。
インデクシングは代入にも応用できる。例えばリスト8-3のように、abc_list[1]というコードで取得した個別要素に対して、数値や文字列などのオブジェクトを代入することで、その要素のみを変更することもできる。
abc_list[1] = 'x'
abc_list # ['a', 'x', 'c', 'd', 'e']と出力される
◇ スライシング(範囲データの取得)
単一のデータだけではなく、範囲指定で複数のデータを取得(=スライス)する方法(スライシングと呼ぶ)についても簡単に説明しておこう。例えば、インデックス番号2〜3の2つ(=list型オブジェクトになる)を取得したい場合は、リスト8-4のように記述すればよい。
abc_list[2:4]
# ['c', 'd']と出力される
コード例から推測できると思うが、角括弧[〜]の中に記載する範囲指定は、
<開始インデックス番号>:<終了インデックス番号>
という仕様になっている。「あれ、2〜3のはずが、2:4という記述になっている」という点に気付いた人は鋭い。<終了インデックス番号>の要素自体は、抽出範囲に含まれないことに注意が必要だ。さらに以下の応用テクニックもある。
応用テクニック1: <開始インデックス番号>を省略して、例えばabc_list[:4]と記述すると、「先頭からインデックス3までを取得」という意味になり、['a', 'x', 'c', 'd']が取得できる(リスト8-4-1)。
abc_list[:4] # 先頭からインデックス3までを取得
# ['a', 'x', 'c', 'd']と出力される
応用テクニック2: <終了インデックス番号>を省略して、例えばabc_list[2:]と記述すると、「インデックス2から末尾までを取得」という意味になり、['c', 'd', 'e']が取得できる(リスト8-4-2)。
abc_list[2:] # インデックス2から末尾までを取得+
# ['c', 'd', 'e']と出力される
応用テクニック3: <開始インデックス番号>:<終了インデックス番号>:<何個先ごと>という構文を使って、例えばabc_list[::2]と記載すると、「先頭から末尾まで2個先ごとに取得」という意味になり、['a', 'c', 'e']が取得できる(リスト8-4-3)。<何個先ごと>に2を指定しており、'a'の1個先が'x'で、2個先が'c'なので、次の取得値は'c'となる。その次は'd'を飛ばして'e'となるため、このようなリスト値が取得できるというわけだ。
abc_list[::2] # 先頭から末尾まで2個先ごとに取得
# ['a', 'c', 'e']と出力される
list型であるリストに関しては、リストの操作(要素の追加や削除、連結、ソート、コピーなど)系の関数があり、特定の場面ではよく使うのですべて紹介したいところだが、本連載は「TensorFlowチュートリアルの読み書き」にフォーカスを当てており、それらについてはあまり出番がないようだったので割愛する。
tuple型(タプル型)
次にtuple型は、前掲のリスト8に含まれていたが少し複雑なので、まずはシンプルな仮のサンプルコードで説明しよう(リスト8-5、図10-3)。
# タプル値は1つの変数に代入できる
tuple_data = ('Taro', 'Yamada')
# 丸括弧を省略して「tuple_data = 'Taro', 'Yamada'」と記述しても同じ意味
tuple_data # ('Taro', 'Yamada')と出力される
リスト8-5では、変数tuple_dataに、「'Taro'」と「'Yamada'」という2つの値を「1つの値」にパックした(=まとめた)タプル値を代入している。
このようにタプルは、丸括弧(〜)で挟んで記述する。括弧の中はカンマ','で区切る。個々の値の型は何でもよい。
タプルの作成方法は、基本的にはリストと同じである。もちろん違う部分もある。特にリストと違うのは、丸括弧(〜)は(見やすさなど必要に応じて)省略できるという点だ。具体的には('Taro', 'Yamada')の代わりに'Taro', 'Yamada'と記述してもよい(後述のリスト8-7で再度説明する)。
◇ リストとタプルの使い分け指針
リストは、データコレクションへの新たなデータの追加や削除ができる(この性質をミュータブルと呼ぶ)ので、大量のデータを取り扱うのには便利である。ひと言でいうと「データのリスト化」(=一覧データにすること)が得意である。
それに対してタプルは、動的にデータコレクションを追加・削除して変更する方法は提供されておらず(この性質をイミュータブルと呼ぶ)、2〜10個など比較的少ない個数分の値を「1つの値」のようにパックにして取り扱いたい際に便利である。ひと言でいうと「複数の値のグループ化」が得意ということだ。
これを聞いても「タプルの使いどころがよく分からない」という意見・感想は多いが、冒頭のリスト8で示したTensorFlowチュートリアルからのコードの抜粋でも分かるように、現場ではタプルは多用されている。タプルの良さはプログラミングを進めると分かってくるので、さらにタプルの特徴的な機能を紹介していこう。特に、次に説明するアンパック代入は、プログラミングをするうえで非常に便利である。実際に、冒頭のリスト8にある「タプル関連の行」のすべてがアンパック代入である。
◇ アンパック代入(複数の変数への分解代入)
タプル(=tuple型のオブジェクト)を分解して、複数の変数に代入することをアンパックもしくはアンパック代入と呼ぶ。
前掲のリスト8の説明に入る前に、ここでは余計な文法機能を含めない仮のサンプルコードを提示する(リスト8-6、図10-4)。
# 変数「tuple_data」の宣言とタプル値の代入は、リスト8-5で行っている
# tuple型で宣言した複数の変数に、タプル値をアンパック代入(=分解して代入)
(first_name, family_name) = tuple_data
family_name # 'Yamada'と出力される
リスト8-6のコード内容を説明すると、tuple_dataは、('Taro', 'Yamada')という値を持つタプルである。そのタプルの各要素を代入される側の変数群は、(first_name, family_name)となっている。
変数群の書き方は、タプルの作成方法とまったく同じである。当然ではあるが、代入元となるタプルの構造(この例では、2つの要素がある)と、代入先にある変数群の構造(この例では、2つの要素がある)は、完全に一致させる必要がある(この例では、どちらも2つの要素で一致している)。その構造に基づき、1つ目の変数には、タプル内にある1番目のオブジェクトが代入され、2つ目の変数にはタプル内2番目のオブジェクトが代入される。
その結果、図10-4のイメージのとおり、変数first_nameに'Taro'が、変数family_nameに'Yamada'が設定されることになるのである。
◇ 丸括弧の省略
すでに簡単に紹介済みであるが、タプルの重要な特徴として、丸括弧(〜)を省略してカンマ','で区切るだけでもタプルと見なされる、という点がある。リスト8-7は実際に省略したもの。
first_name, family_name = 'Taro', 'Yamada'
family_name # 'Yamada'と出力される
これを見て気付いただろうか。まるで「複数の変数の宣言と値の代入」を1行でまとめて行っているみたいだと。実際に、1行でまとめて変数を宣言するために、タプルは使われることがある。リスト8-8は、丸括弧の省略機能を活用して、実際に複数の変数をまとめて宣言して値を代入した例である。
is_b, EPOCHS, color = False, 500, 'blue'
is_b, EPOCHS, color # '(False, 500, 'blue')'と出力される
このようにタプルがあれば、グループ単位で変数を代入したり管理したりできるようなり、開発がしやすくなるのである。
タプルは、次回Lesson 7で説明する「関数」から出力される値(=厳密には「戻り値」や「返り値」と呼ばれる)として使われることも多い。関数によっては、出力する値は1つではなく、2〜10個ぐらいなど多数の値をまとめて出力したい場合があるからだ。その一例がリスト8-9のコードである(前掲のリスト8からの抜粋)。
#import tensorflow as tf
#mnist = tf.keras.datasets.mnist
# 以下のコードを動かすためには、上記2行を事前に実行しておく必要がある
#---------------------------------------------------------------------
(x_train, y_train),(x_test, y_test) = mnist.load_data()
# 右辺の「mnist.load_data()」は関数で、
# ((<訓練データ>, <訓練ラベル>), (<テストデータ>, <テストラベル>))
# という構造のタプルを出力する
リスト8-9では、(x_train, y_train)と(x_test, y_test)という2つのタプル形式の変数が宣言されている。
これらをAとBという仮の変数に置き換えると、代入=の左辺はA, Bとなり、この書き方は先ほど説明した「丸括弧が省略されたタプル」であることが分かる。
省略された丸括弧を(A, B)と戻して、そのAとBの中身を展開すると、((x_train, y_train), (x_test, y_test))というコードになる(図10-5)。
つまりリスト8-9は、タプルの中にタプルがある複雑な入れ子構造(=イメージとしてはマトリョーシカ人形のように箱の中に箱がある状態のこと。ネスト、階層構造)になっていることが分かる。
まとめるとリスト8-9は、関数が出力する「入れ子構造のタプル」を、x_train、y_train、x_test、y_testという4つの変数にアンパック代入しているコードである。先ほど述べた通り、「代入元となるタプルの構造と、代入先にある変数群の構造は、完全に一致させる必要」があり、この場合はmnist.load_data()関数の出力が「タプルを要素とするタプル」となっていて、それに合わせる必要があるため、x_train, y_train, x_test, y_test = mnist.load_data()(4要素のタプルへの代入)のような書き方はできないことには注意しよう。
また、前掲のリスト8にあった、
x_train, x_test = x_train / 255.0, x_test / 255.0
predictions_array, true_label = predictions_array[i], true_label[i]
も同様に、丸括弧が省略された、タプルのアンパック代入なのである。同じ説明になるので詳細は省略する。
x_train / 255.0は、割り算(Lesson 10「四則演算」で解説)ではあるが、厳密にはPython言語の機能ではなく、ライブラリ「NumPy」の計算機能である(※詳しくはデータ構造編[Lesson 3]を参照)。
predictions_array[i]もライブラリ「NumPy」の機能であるが、その意味は前述のリストの解説で言及済みである。
dict型(辞書型)
dict型も仮のサンプルコードで説明する(リスト8-10、図10-6)。
abc_dict = { 'a': 1.2, 'b': 2.4, 'c': 3.6 }
abc_dict # { 'a': 1.2, 'b': 2.4, 'c': 3.6 }と出力される
リスト8-10では、変数abc_dictに、「'a': 1.2」と「'b': 2.4」と「'c': 3.6」という3つの「キー(key):値(value)」のセット値を辞書データとして代入している。説明するまでもないと思うが、例えば1つ目のセット値は、'a'がキーで、1.2が値である。
このように辞書は、波括弧{〜}で挟んで記述する。括弧の中はカンマ','で区切る。個々の値の型は何でもよい。これまでとほぼ同じパターンなので、難しくはないだろう。
◇ キー指定による値の取得
そもそもPythonの辞書とは、キーを基に値が探せるコレクションのことである。例えばリスト8-11では、変数abc_dictに対して'a'というキーで値を取得している。この結果、1.2という値が得られることになる。
abc_dict['a']
# 1.2と出力される
キーによる値の取得は代入にも応用できる。例えばリスト8-12のように、abc_dict['a']というコードで取得した個別要素に対して、数値や文字列などのオブジェクトを代入することで、その要素のみを変更できる。また、存在しないキー名を指定して代入をした場合は、そのキー名と代入した値を使って新しい要素が追加される。
abc_dict['a'] = 12.3 # 代入による値の変更
abc_dict['x'] = 9.9 # 代入によるキーと値の追加
abc_dict # {'a': 12.3, 'b': 2.4, 'c': 3.6, 'x': 9.9}と出力される
dict型である辞書に関しても、辞書の操作(要素の削除、全部空にする、キーの有無チェックなど)系の関数などがあるが、本連載では説明を割愛する。
以上までが、Python言語の代表的な組み込み型である。もちろん、変数に代入されるオブジェクトの型は多種多様である。前回Lesson 4で見た「モジュール」だけでなく、Lesson 7で説明する「関数」や、Lesson 11で説明する「クラス」など、さらに標準に含まれている型から自作した型まで、さまざまなオブジェクトの型があり得る。
例えばリスト9は、前掲の図1-b/d内のサンプルコードの中にある「変数に対して何かを代入している行」(=冒頭で示した【再掲】画像の赤枠の内容)の中から、組み込み型以外のものだけを抜き出して並べたものだ。
#import tensorflow as tf # このコードを動かすためにインポートが必要
# 変数への「モジュール」の代入
mnist = tf.keras.datasets.mnist
# 変数への「何らかのオブジェクト」の代入(1)
#model = tf.keras.models.Sequential([
# tf.keras.layers.Flatten(),
# tf.keras.layers.Dense(512, activation=tf.nn.relu),
# tf.keras.layers.Dropout(0.2),
# tf.keras.layers.Dense(10, activation=tf.nn.softmax)
#])
# 「tf.keras.models.Sequential([〜])」の意味は、Lesson 11「クラス」の回で解説
# ここでは、次のコードに臨時的に置き換えて解説
model = '<関数(厳密にはクラスのコンストラクター。詳細省略)>が出力したオブジェクト'
# 変数への「何らかのオブジェクト」の代入(2)
#history = model.fit(train_data, train_labels, epochs=EPOCHS,
# validation_split = 0.2, verbose=0,
# callbacks=[PrintDot()])
# 「model.fit(〜)」の意味は、Lesson 7「関数」の回で解説
# ここでは、次のコードに臨時的に置き換えて解説
history = '<関数(詳細省略)>が出力したオブジェクト'
リスト9では、変数mnistに「モジュール」が代入され、変数modelには関数から出力された「何らかのオブジェクト」が代入されている(※変数historyも同様の関数なので、以降、説明を割愛)。Lesson 4で掲載したものと同じ絵になってしまうが、イメージ化すると、想像通り図10-7のようになる。データの型が何であろうが、常にワンパターンなのである。
Lessson 4〜6で、変数とデータ型の説明をした。ここまでの文法説明だけでも、TensorFlowチュートリアルにおける、かなり多くの行数のコードを説明できたことになる。
次回は、関数について説明する。関数は、変数の次に重要なPython言語の文法要素である。これを押さえることで、さらに大部分のTensorFlowチュートリアルのコードが読み書きできるようになる。
Copyright© Digital Advantage Corp. All Rights Reserved.