検索
連載

Lesson 7 関数 ― Python基礎文法入門機械学習&ディープラーニング入門(Python編)

Python言語の文法を、コードを書く流れに沿って説明していく連載。今回は、プログラム内の各処理を実現する関数について説明する。また、関連事項として、文字列フォーマット関数についても言及する。

PC用表示 関連情報
Share
Tweet
LINE
Hatena
「機械学習&ディープラーニング入門(Python編)」のインデックス

連載目次

ご注意:本記事は、@IT/Deep Insider編集部(デジタルアドバンテージ社)が「deepinsider.jp」というサイトから、内容を改変することなく、そのまま「@IT」へと転載したものです。このため用字用語の統一ルールなどは@ITのそれとは一致しません。あらかじめご了承ください。

 ここまでの連載では、Lessson 4で変数を、Lesson 5・6(前回)でデータの型を説明した。今回は、Pythonプログラミングの基本中の基本で、文法の中で「変数」の次に大事である「関数」の利用方法について解説する。「関数」の定義方法については、次回Lesson 7で紹介する。脚注や図、コードリストの番号は前回からの続き番号としている。

 本連載は、実際にライブラリ「TensorFlow」でディープラーニングのコードを書く流れに沿って、具体的にはLesson 1で掲載した図1-a/b/c/dのサンプルコードの順で、基礎文法が学んでいけるように目次を構成している。関数については、利用頻度が非常に高い。具体的には、そのすべての図内のコードで使われており、割合で言うと63%の行数と広範にわたっている。すべての関数を詳しく説明する必要はないので、まず本稿の前半では、図1-aに「関数」が登場する初出の1行のみを取り上げる。本稿の後半では、「さまざまな関数の使用例」というタイトルでそれ以外の関数の使用例をコードの意味紹介とともに提示するにとどめる。

 なお、本稿で示すサンプルコードの実行環境については、Lesson 1を一読してほしい。

 Lesson 1でも示したように、本連載のすべてのサンプルコードは、下記のリンク先で実行もしくは参照できる。


Google Colabで実行する
GitHubでソースコードを見る

Python言語の基礎文法

 これまでの連載の中で何度か言葉だけが登場して具体的な解説を先伸ばしにしてきた「関数」について説明していこう。

関数の利用

 関数Function)は、何らかの処理、例えば変換や、計算、操作などを行う機能を持つ。

 例えば、図1-aにある初出の「関数」のコードを見ると、リスト10-1のようになっている。

#import tensorflow as tf
#mnist = tf.keras.datasets.mnist
# 以下のコードを動かすためには、上記2行を事前に実行しておく必要がある
#---------------------------------------------------------------------

(x_train, y_train),(x_test, y_test) = mnist.load_data()

リスト10-1 関数を利用するコード例

 このコードについては、前回Lesson 6(リスト8-9)で、=の左辺が「タプル」で、右辺が「関数」だと説明済みである。今回は、この関数の内容について、より詳しく見ていく。

 まず、このmnistは(Lesson 2で説明したとおり)モジュールである。復習すると、.は「<モジュール階層>の中の……」という意味だった。それに続くload_data()の部分が関数である。つまりこのコードは、mnist<モジュール階層>の中にあるload_data()関数を呼び出しているというわけだ。

 なお、「load_data」という名前から推測すると、この関数は「データを読み込む(=ロードする)」という処理機能を持つ、と考えられるだろう。Lesson 3に倣ってAPIリファレンス「tf.keras.datasets.mnist.load_data」を調べてみると、「MNISTデータセットをロードする(Loads the MNIST dataset.)」と記載されている。MNISTデータセットとは、ディープラーニングの教材としてよく使われている手書き文字画像集である(MNISTやデータセットという用語については、『機械学習&ディープラーニング入門(概要編)』のLesson 3を参照してほしい)。

 実際にコードが実行されてload_data()関数が呼び出されると、その関数内部の処理(=MNISTデータセットをロードする)が実行され、その結果の値が返される。その値のことを戻り値(もどりち)もしくは返り値(かえりち)と呼ぶ。

 関数は「関数内部で処理実行 → 結果出力」という流れで動作する文法機能と言えるが、戻り値はその結果出力に相当する。

 この例では、入れ子構造のタプル値が返される(=出力される)ので、それをx_trainy_trainx_testy_testという4つの変数にアンパック代入している、と前回説明済みだ。

 確認のため、以上の内容をこれまで同様にイメージで表すと、図11-1のようになる。なお、入れ子構造の4つの変数は、シンプルにするため1つの箱にまとめて表現したので注意してほしい。

変数への「関数の戻り値」の代入、のイメージ
図11-1 変数への「関数の戻り値」代入、のイメージ

 関数の呼び出し方について見ていこう。

関数の呼び出し方

 関数の呼び出し方法(基本)を図解したのが図11-2である。ここでの関数名は、仮にsome_function()とした。

関数を呼び出す方法(基本)
図11-2 関数を呼び出す方法(基本)

 関数を呼び出すには、関数名(例えば「load_data」)の最後に丸括弧()を付ける、という特徴がある。この丸括弧()の中にあるarg1arg2などは引数(arguments、ひきすう)と呼ばれる。

 ちなみに引数は、仮引数(パラメーター:parameter)と実引数(arguments)に分ける場合もあるが、用語の使い分けが非常に紛らわしく、かつ使い分ける必要性もあまりない。よって、「引数」という用語だけを覚えておき、「パラメーター」も同じ「引数」だと忘れないようにしておけば、実用上の問題は起きないだろう。

 関数がどんな引数を受け取るかは、関数を作成した人が定義する。例えば図11-2では、arg1arg2……という複数の引数が定義されている(これら関数定義時の引数のことを「パラメーター」や「仮引数」と呼ぶ)。

 関数を呼び出す際には、これらのarg1arg2……の場所に、数値や文字列値といったデータや値をそのまま記述するか、何らかのオブジェクトが格納された変数の名前などを記述する(これら関数使用時の引数のことを「実引数」と呼ぶ)。

 まとめると、関数は「(値入力) → 関数内部で処理実行 → 結果出力」という流れで動作する文法機能であり、引数はその値入力に相当するものである。

 「基本」とした引数ありの関数の呼び出し以外に、引数が0個(=なし)という場合も多い(図11-3)。先ほどのload_data()関数も引数なしのパターンだ。

関数を呼び出す方法(引数なし)
図11-3 関数を呼び出す方法(引数なし)

 また、戻り値がないパターンもある(図11-4)。この場合、関数の処理だけを実行することになる。戻り値がない代わりに、画面出力など別の形で処理結果を示すことが多い。

関数を呼び出す方法(戻り値なし)
図11-4 関数を呼び出す方法(戻り値なし)

 組み合わせを考えると次の4パターンがあり得る。

  • 引数あり、戻り値あり、の関数
  • 引数あり、戻り値なし、の関数
  • 引数なし、戻り値あり、の関数
  • 引数なし、戻り値なし、の関数

 なお、戻り値があっても、変数に代入しないで実行することも可能だ(リスト10-2)。

mnist.load_data()
# 戻り値は出力(=表示)されるのみで、変数に保存されない

リスト10-2 戻り値を無視するコード例

【コラム】print関数 vs. オブジェクト評価の自動出力

 Python言語で、オブジェクトの中身を出力したい場合、基本的にはprint()関数を使用する。print()関数は、Python言語に標準で用意されている組み込み関数(Built-in functions)なので、何もインポートしなくても利用できる。実際に使うには、このprint()関数の引数に、オブジェクトや変数名を指定すればよい。リストex2は、その利用例である。

print(y_train)
# [5 0 4 ... 5 6 8] のようにシンプルに出力される

リストex2 print()関数の使用例

 しかしここまでの連載では、そのprint()関数を使わず、変数の名前のみを記述することで、その変数に代入されているオブジェクトの中身を出力してきた。これは、本連載が前提としているGoogle Colabのようなインタラクティブシェルを用いた実行環境でのみ活用できる、「オブジェクト評価の自動出力」という機能である(バッチ処理などで使う.pyファイルに記述したコードを実行する方法では、このようなオブジェクト評価の自動出力は行われない)。

 オブジェクト評価の自動出力とは、リストex3のように、変数名(厳密にはexpression)だけの文を書いても、print()関数と同じような出力が自動的に表示される機能のことである。

y_train
# array([5, 0, 4, ..., 5, 6, 8], dtype=uint8)のように出力される

リストex3 オブジェクト評価の自動出力機能の使用例

 ただし、自動出力機能とprint()関数の出力はまったく同じとは限らない。自動表示の場合は、あくまで式の評価結果(=開発やデバッグのために得る情報)が出力されているのに対し、print()関数の場合は、人が見るために綺麗に整形された情報が出力される、という違いがある。

 具体的には、リストex2のprint()関数の出力は、シンプルで見やすい。一方、リストex3の自動出力機能の出力には、「array()」(=配列)などとより細かい情報が表示されている。開発時やデバッグ時には、むしろより多くの情報が表示された方がありがたいケースも多々あるので、どちらの出力がより好ましいかは状況により異なるだろう。

 要は適材適所で使い分けるのがお勧めだが、本連載では、基本的には自動出力機能で、print()関数を使うべきところでのみprint()関数で記述していく。

 自動出力機能についてより詳しく知りたい場合は、『機械学習&ディープラーニング入門(データ構造編)』[Lesson 1]の「【コラム】インタラクティブシェルにおけるオブジェクト評価の自動出力」にも説明を書いているので、併せて参照してほしい。


さまざまな関数の使用例

 以上で関数の利用や呼び出し方の基本については、解説したことになる。

 関数の利用の解説としては、ここで終わりにしてもよいが、図1-a/b/c/d内には多数の関数のコードが含まれている。そこで以下では、「使用例」という形でコードを提示し、必要最小限で説明していくことにする。コードの掲載は長くなるが、同じことの繰り返しなので、軽く読み流すだけでも十分である。

 おまけとして、関数の処理内容・意味をコード内のコメントとして追記している。ただし、処理内容や意味を理解するには、ディープラーニングおよびニューラルネットワークの理解がある程度必要となるので、注意してほしい。よく分からない場合は、本稿では意味理解を追求せず、「そのような意味のコードらしい」という程度でスルーしてほしい。

TensorFlowの公式チュートリアルのサンプルコード(1)
図1-a【再掲】 TensorFlowの公式チュートリアルのサンプルコード(1)

TensorFlowの公式チュートリアルのサンプルコード
図1-b【再掲】 TensorFlowの公式チュートリアルのサンプルコード(2)

TensorFlowの公式チュートリアルのサンプルコード
図1-c【再掲】 TensorFlowの公式チュートリアルのサンプルコード(3)

TensorFlowの公式チュートリアルのサンプルコード
図1-d【再掲】 TensorFlowの公式チュートリアルのサンプルコード(4)

 以下で紹介するのは、図1-a/b/c/d【再掲】における赤枠内のコードのみとなる(図1-aの初出の関数は説明済み)。青枠内のコードは次回説明する。

ライブラリ「NumPy」とライブラリ「Matplotlib」

 本連載では、すでにLesson 2でライブラリ「TensorFlow」のインストール方法を示し、これまでのサンプルコードの実行でも活用してきた。しかし、図1-b/cを実行するには、ライブラリ「NumPy」とライブラリ「Matplotlib」もインストールしておく必要がある。

 それらのインストール方法の詳細な解説は、TensorFlowと同じになるので割愛する。本連載が前提とする実行環境「Google Colab」を使っているのであればリスト10-3とリスト10-4を実行すればよい。

!pip install numpy

リスト10-3 ライブラリ「NumPy」のインストール

!pip install matplotlib

リスト10-4 ライブラリ「Matplotlib」のインストール

 NumPyは数値計算ライブラリで、『機械学習&ディープラーニング入門(データ構造編)』[Lesson 2]で説明している。numpyモジュールを、慣例ではnpという名前でインポートする(リスト10-5)。

import numpy as np

リスト10-5 numpyモジュール(名前:np)のインポート

 Matplotlibはグラフ描画ライブラリで、『初めてのニューラルネットワーク&ディープラーニング実装(TensorFlow 2+Keras(tf.keras)入門)』で説明する。Matplotlibでは、matplotlib<モジュール階層>の中にpyplotモジュールが含まれており、グラフの描画を行う際にはこのモジュールをインポートして使うのが基本となる。よってmatplotlib.pyplotモジュールを、慣例ではpltという名前でインポートする(リスト10-6)。

import matplotlib.pyplot as plt

リスト10-6 matplotlib.pyplotモジュール(名前:plt)のインポート

 それでは必要なライブラリのインストールは完了したので、図1-a/b/c/d内のコードをまとめて説明していこう。

図1-aに含まれる「関数」のコード

 図1-aに含まれている「関数」のコードは、ほぼすべてとなり長いので、まずは前半をリスト10-7に示す。

#import tensorflow as tf
#mnist = tf.keras.datasets.mnist
#(x_train, y_train),(x_test, y_test) = mnist.load_data()
#x_train, x_test = x_train / 255.0, x_test / 255.0
# 以下のコードを動かすためには、上記4行を事前に実行しておく必要がある
#---------------------------------------------------------------------

# ニューラルネットワークで使うSequentialモデルを作成する
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)
])

リスト10-7 図1-aにおける「関数」(前半)
一文が複数行にわたっているが、これについては後述のコラム「【応用テクニック】丸括弧内を複数行にする方法」で説明する。

 tf.keras.layers.〜という関数が4つあるが、これらは後で説明する。まずは、その上位にある、

tf.keras.models.Sequential([<関数1>, <関数2>, <関数3>, <関数4>])

というコードに注目してほしい。tf.keras.models.Sequentialは、tf.keras.modelsモジュール階層の中にあるSequentialクラスを意味し(「クラス」はLesson 11で説明)、そのクラス名の後に()を付けて関数のようにして使っている。この関数は「コンストラクター」と呼ばれるが、詳しくはLesson 11で説明する。ここでは「関数の一種である」と捉えてほしい。

 これを関数として捉えると、(値入力として)引数にリスト値([<関数1>, <関数2>, <関数3>, <関数4>])が指定され、tf.keras.models.Sequential()関数内部で処理が実行され、その結果出力としてSequentialモデル(具体的にはSequential型のオブジェクト)が返されていると言える。そして、戻り値であるSequentialモデルは、変数modelに代入され、後述の処理で使われていく。

 先ほど省略した<関数1〜4>の意味についても言及しておこう。上記のSequentialモデルもそうだが、これらの関数の処理内容・意味の理解は、前述のとおりニューラルネットワークの知識が必要なので、分からなければスルーしてほしい。なお、activation=〜という構文は、次回Lesson 8で説明する「キーワード引数」という機能で、この例ではactivationという引数名を指して、引数を指定している。

  • <関数1>: tf.keras.layers.Flatten()は、平滑化を行うオブジェクトを返す
  • <関数2>: tf.keras.layers.Dense(512, activation=tf.nn.relu)は、512ユニットで、活性化関数にReLUを使う層のオブジェクトを返す
  • <関数3>: tf.keras.layers.Dropout(0.2)は、20%の率でドロップアウトを行うオブジェクトを返す
  • <関数4>: tf.keras.layers.Dense(10, activation=tf.nn.softmax)は、10ユニットで、活性化関数にSoftmaxを使う層のオブジェクトを返す

 4つの関数はそれぞれオブジェクトを返し、それらが[<関数1>, <関数2>, <関数3>, <関数4>]という構文でリスト値としてまとめられているというわけだ。

 さて、図1-aに含まれる「関数」のコードの後半を見てみよう。リスト10-8のようになっている。なお、optimizerlossmetricsepochsは、先ほどのactivationと同じキーワード引数である。

# モデルをコンパイルする(最適化手法に「Adam」、損失関数に「クロスエントロピー(の一種)」、測定項目に「正確度」を指定)
model.compile(optimizer='adam',
              loss='sparse_categorical_crossentropy',
              metrics=['accuracy'])

# モデルをフィット(=学習)させる(訓練データ、訓練ラベル、エポック数は5回)
model.fit(x_train, y_train, epochs=5)

# モデルの出来を評価する(テストデータ、テストラベル)
model.evaluate(x_test, y_test)

リスト10-8 図1-aにおける「関数」(後半)

 コードの意味は、本文中に記載しているのを参考にしてほしい。関数の使い方に関しては難しいところはないだろう。

図1-bに含まれる「関数」のコード

 続いて図1-bに含まれている「関数」のコードは、リスト10-9のようになっている。

#import tensorflow as tf
#import numpy as np
#import matplotlib.pyplot as plt
#mnist = tf.keras.datasets.mnist
#(x_train, y_train),(x_test, y_test) = mnist.load_data()
#x_train, x_test = x_train / 255.0, x_test / 255.0
# 以下のコードを動かすためには、上記6行を事前に実行しておく必要がある
#---------------------------------------------------------------------

# 実行に必要な変数を仮で用意---ここから---
img = x_test[0]             # テストデータの1番目の画像
predictions_array = x_test  # 予測結果データの代わりにテストデータを使用
# -----------------------------ここまで---

# グラフに対してグリッド線の「有無(False: なし)」を設定
plt.grid(False)

# グラフに対してx軸とy軸の「目盛り値([]: 空のリスト値)」を設定
plt.xticks([])
plt.yticks([])

# グラフに対してx軸のラベルを設定
plt.xlabel("xlabel")
# 仮に「"xlabel"」という文字列値に置き換えているが、ここには後述する「文字列フォーマット関数」があった

# グラフに対して画像(img)を、白黒(plt.cm.binary)で表示する
plt.imshow(img, cmap=plt.cm.binary)

# NumPy配列値(predictions_array)の中から最大値となる要素のインデックス集を取得して、変数predicted_labelに代入
predicted_label = np.argmax(predictions_array)

# NumPy配列値(predictions_array)の中から最大値を取得する
np.max(predictions_array)
# 1.0 と出力

リスト10-9 図1-bにおける「関数」
plt.xlabel()関数の登場位置は、上の方に移動させて変わっているので注意してほしい。

 cmapは、キーワード引数である。その引数にはplt.cm.binaryという値が指定されているが、これはカラーマップ(=使用可能な色をまとめたもの)の一つで、白黒の画像にするという意味である。

図1-cに含まれる「関数」のコード

 図1-cの「関数」は、リスト10-10のとおり。先ほどの説明との重複もかなりあり、難しくはないだろう。

#import tensorflow as tf
#import matplotlib.pyplot as plt
#mnist = tf.keras.datasets.mnist
#(x_train, y_train),(x_test, y_test) = mnist.load_data()
#x_train, x_test = x_train / 255.0, x_test / 255.0
# 以下のコードを動かすためには、上記5行を事前に実行しておく必要がある
#---------------------------------------------------------------------

# 実行に必要な変数を仮で用意---ここから---
train_images = x_train  # 訓練データ(画像)
train_labels = y_train  # 訓練ラベル
class_names = ['0', '1', '2', '3', '4', '5', '6', '7', '8', '9'# 分類名
i = 0  # インデックス番号。1番目
# -----------------------------ここまで---

# グラフに対して幅10×高さ10インチの図を新規作成する
plt.figure(figsize=(10,10))

# 現在の図に5行×5列の枠組みを作って、左上のi+1番目の場所にサブプロット(=枠内の描画領域)を追加する
plt.subplot(5,5,i+1)

# グラフに対してx軸とy軸の「目盛り値([]: 空のリスト値)」を設定
plt.xticks([])
plt.yticks([])

# グラフに対してグリッド線の「有無(False: なし)」を設定
plt.grid(False)

# グラフに対して画像(train_images[i])を、白黒(plt.cm.binary)で表示する
plt.imshow(train_images[i], cmap=plt.cm.binary)

# グラフに対してx軸のラベルを設定
plt.xlabel(class_names[train_labels[i]])

リスト10-10 図1-cにおける「関数」
range(25)も関数であるが、Lesson 10の「制御フロー文:ループ処理」で説明する。

図1-dに含まれる「関数」のコード

 図1-dの「関数」は、リスト10-11に示す。これも難しくはないだろう。endepochsvalidation_splitverbosecallbacksはキーワード引数である。

#import tensorflow as tf
#import matplotlib.pyplot as plt
#mnist = tf.keras.datasets.mnist
#(x_train, y_train),(x_test, y_test) = mnist.load_data()
#x_train, x_test = x_train / 255.0, x_test / 255.0
# 以下のコードを動かすためには、上記5行を事前に実行しておく必要がある
#---------------------------------------------------------------------

# さらに、リスト10-7とリスト10-8を事前に実行しておく必要がある
#---------------------------------------------------------------------

# 実行に必要な変数を仮で用意---ここから---
from tensorflow import keras  # tf.kerasと同じモジュールのインポート
train_data = x_train          # 訓練データ
train_labels = y_train        # 訓練ラベル
EPOCHS = 5                    # エポック数を5回に
# -----------------------------ここまで---

# 空行を出力する
print('')

# 末尾を改行にしない(end='')で「.」を出力する
print('.', end='')

# モデルをフィット(=学習)させて(訓練データ、訓練ラベル、エポック数は5回、精度検証データ用に20%分使って、詳細情報の出力はせず、コールバックもなし)、その結果を変数historyに代入する
history = model.fit(train_data, train_labels, epochs=EPOCHS,
                    validation_split = 0.2, verbose=0,
                    callbacks=[])

リスト10-11 図1-dにおける「関数」

 なお、callbacks=[]の部分は、実際にはcallbacks=[PrintDot()]というコードになっていた。PrintDotは自作したクラスで、そのクラス名の後に()を付けているので、これも関数のようにして使える「コンストラクター」である。クラスの自作についてはLesson 12で説明するので、ここでは省略した。

【応用テクニック】丸括弧内を複数行にする方法

 Lesson 2では「【応用テクニック】1文を複数行にする方法」を紹介したが、ここでは別のテクニックを紹介する。

 これまでの連載では、基本的に文を1行で記述してきた。しかし前掲のリスト10-7/8/11など(例えばリスト10-11のfit()関数を参照)では複数行で記載されている。これはどういうことかというと、関数の()内の先頭/末尾や、その中の区切り','の前後では改行できる仕様になっているということだ。このようにして文が複数行にわたって継続されている場合は、)の終りが来るまでの継続行のインデント(=左端に作る余白)はそろえる必要がない。

 関数の引数が長くなる場合、リスト10-11の例のようにカンマ','の直後で改行して、引数の先頭位置をそろえて見やすく書くのがお勧めだ。次に示すコード(後述のリスト10-12)もこのスタイルになっているので、注意して見てみてほしい。


文字列フォーマット関数

 最後に、説明を後回しにしていた文字列フォーマット関数について説明しておこう。図1-bには、""の文字列に.format()関数(厳密にはLesson 11で説明するクラスのメソッド)が付いたコードがある。内容を少し加工して抜き出すと、リスト10-12のようになっている。

msg = "{} {:2.0f}% ({})".format('予測結果の分類内容',
                                72.5,
                                '正解ラベルの分類内容')

msg  # '予測結果の分類内容 72% (正解ラベルの分類内容)' と出力される

リスト10-12 文字列をフォーマットするコード例

 文字列内に{}{:2.0f}{}という3つのプレースホルダー(=場所確保)があり、そこに順番にformat()関数の引数値が出力され、文字列が完成する。変数に格納された値を使って文字列を動的に加工することを、フォーマット(format、整形する)と呼ぶ。

 {:2.0f}は、float値の整数部分が2桁で少数部分が0桁を意味する。加工方法はいろいろとあるので、とりあえず今回は、このような感じで文字列をフォーマットする、と知ってもらうだけで十分だ。筆者もそうだがformat()関数の細かい機能仕様を覚えてもすぐに忘れてしまうので、実際に使う時点で「Python 文字列フォーマット」などのキーワードでGoogle検索するなどして調べながら関数を使えばよい。

つづく

 以上、変数と関数が理解できれば、Pythonプログラミングの最重要ポイントを押さえたことになる。あともう一つ、必須学習項目として挙げるなら「制御構文」となるが、すでに山は越えたので、あともう少しだけ頑張ってほしい。

 次回は、関数を自分で定義する方法を解説する。また、今回すでに何度も登場した「キーワード引数」や、同様に重要な「デフォルト引数」という関数の機能についても紹介する。

「機械学習&ディープラーニング入門(Python編)」のインデックス

機械学習&ディープラーニング入門(Python編)

Copyright© Digital Advantage Corp. All Rights Reserved.

[an error occurred while processing this directive]
ページトップに戻る