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

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

関数の定義 ― Python基礎文法入門

2019年3月25日

Python言語の文法を、コードを書く流れに沿って説明していく連載。前回に続けて、今回は関数の定義方法を説明する。加えて、デフォルト引数やキーワード引数という重要機能、Python言語で特徴的なインデントについても説明する。

一色政彦 デジタルアドバンテージ 一色 政彦

 前回は「関数」の利用方法について紹介した。今回は、定義方法を説明する。脚注や図、コードリストの番号は前回からの続き番号としている。

 本連載は、実際にライブラリ「TensorFlow」でディープラーニングのコードを書く流れに沿って、具体的にはLesson 1で掲載した図1-a/b/c/dのサンプルコードの順で、基礎文法が学んでいけるように目次を構成している。関数の定義は3つほど含まれているが、すべてを詳しく説明する必要はないだろう。そこで本稿では、前回Lesson 7で説明した図1-aの初出の「関数を使用するコード」、具体的には

mnist.load_data()

という関数が、どのように定義されているかを見てみる。本稿の最後で、図1-b/dに含まれている「関数の定義例」も示す。

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

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

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

Python言語の基礎文法

 前回のLesson 7では「キーワード引数」という用語が何度も登場した。この言語機能は、「関数の定義」における「デフォルト引数」という仕様とも関係しているので、両方の言語機能をセットで理解する必要がある。そこで今回は、まず関数の定義方法を示し、その後でこの2つの言語機能を説明することとする。

関数の定義

 それでは、Lesson 7で「関数の利用方法」として説明したload_data()関数が、どのように定義されているのかを見てみよう。

 ライブラリ「TensorFlow」はオープンソースなので、インターネット上で該当箇所のソースコードを簡単に参照できる。例えばload_data()関数は、図12-1のようになっている(2018年10月時点。TensorFlowは頻繁にアップデートされているので、コード内容は随時変わる可能性がある)。

関数を定義するコード例
図12-1 関数を定義するコード例

 図12-1は少し長いので、一部を省略して短くしたリスト11-1も掲載しておこう。

Python
def load_data(path='mnist.npz'):
  # ……内部で何らかの処理を実行……
  x_train, y_train = ['x_train'], ['y_train']
  x_test, y_test = ['x_test'], ['y_test']
  
  return (x_train, y_train), (x_test, y_test)
リスト11-1 関数を定義するコード例

 ポイントは、図12-1で赤色の枠が書かれているところだ。defキーワードで始まる文で関数を定義し、returnキーワードで始まる文で戻り値を返している。図12-2は、関数定義のポイントと、引数と戻り値の関係を表現したイメージだ(前回の図11-2とほぼ同じ絵だが、return ……の文を追記している)。ただし、return文がない関数もある。そうした関数は画面にドットを表示するなど、処理結果を戻り値として返す代わりに別の形で処理結果を表すために使われる。

関数の定義、のイメージ
図12-2 関数の定義、のイメージ

 def文は、以下の構文になっている。

  • 構文: def <関数名>(<引数は0個~カンマ区切りで複数>):
  • コード例: def load_data(path='mnist.npz'):

インデントで明示するブロック/スコープ

 まずはdef文の最後にコロン:が付けられている点に注目してほしい。このコロンは、次行以降のインデント(後述)された複数行が関数のコードであることを明示するためのものだ。

 インデントとは、半角スペースで左に余白を作ることで、通常は4つの半角スペースで作る。半角スペースの数は、インデント幅と呼ばれる。

 ちなみに、Lesson 3でも説明したように、半角スペースの有無のようなルールはコーディング規約もしくはスタイルガイドと呼ばれる。スペース2つがルールになっているケースもあり、例えば本連載が対象としているTensorFlowのスタイルガイド「TensorFlow Style Guide」では、インデントは半角スペース2つが推奨されている。

 本連載では、横幅節約のため、2つの半角スペースにしている。ちなみに、Google Colabのインデント幅のデフォルト設定も、同じ半角スペース2つである(4つにしたい場合は、メニューバーの[ツール]-[設定]から開く[設定]ダイアログの[インデント幅(スペース)]欄の数値を2から変えることで変更できる)。

 Google Colabでインデントを作成するには、実際に[スペース]キーを2回押す方法だけでなく、行の先頭など行内Tabキーを押してもよい(後述の【注意】を参照)。

 逆にインデントを解除するには、ShiftTabキーを押すとよい。

 ただし、Lesson 3でTabキーのショートカットがあることを示したとおり、

  • 入力中の単語の最後Tabキーを押すと(インデントではなく)オートコンプリート
  • 関数の()の間Tabキーを押すと、ヘルプドキュメント(docstringヘルプ)

が表示される。このようにTabキーを押す場所で挙動が変わることに注意してほしい。

【注意】[Tab]キーで入力される文字について

 Google Colabでは、Tabキーを押すと、キーが表すタブ文字ではなく、半角スペース2つ(デフォルト設定)が入力される点に注意が必要だ。Tabキーの挙動がこのようになっており、逆に言うとタブ文字を入力するのは難しい。

 もちろんインデントに(半角スペースではなく)タブ文字を使うことも可能ではある。しかし、そもそもタブ文字は、環境によってインデント幅の見た目が異なる可能性があり、問題がある。よって極力、タブ文字は使わない方がいい。

 エディターによっては、Tabキーで(半角スペースによるインデントではなく)タブ文字が入力されてしまう。そのため、コード内でタブ文字と複数スペース(2つか4つ)が混在してしまう可能性がある(混在するとPython 3ではエラーになってしまう)。そうならないためにも、Tabキーを押す方法ではなく、実際に[スペース]キーを2回押す方法で、インデント入力の癖を付けておく方が無難かもしれない。ちなみに筆者は、[スペース]キーを2回押している(多くのコードエディターでは、一度、インデントを作ると、その後は改行のたびに同じインデント幅を維持してくれる。つまり、[スペース]キーを2回押すからといって、作業効率はそこまで悪くならない)。

 Pythonは、インデントによって関数の始まり~終りの範囲(プログラミング用語でブロックと呼び、関数のブロックはスコープとも呼ばれる)を定義するという特徴がある(図12-3)。インデント幅は、このブロックごと統一する必要がある。

インデントで明示する、ブロック/スコープの開始と終了
図12-3 インデントで明示する、ブロック/スコープの開始と終了

 なお、インデントされた複数行の途中に空行があったとしても、それ以降でインデントがそろっている限りは、「ブロック(ここでは関数のスコープ)は終了」と見なされない。よって、複数行の各行はすき間なく詰める必要はなく、見やすいように、適宜、改行で前後に余白を入れながら、ブロックのコードを書いていくことが可能だ(図12-4)。

インデントを継続できる空行と、末尾によるブロックの終了
図12-4 インデントを継続できる空行と、末尾によるブロックの終了

ブロック内の説明をシンプルにするため、load_data()関数ではなくsome_functionという別の関数の例にした。

 ちなみに、インデントを解除しなくても、コードセルやPython(.py)ファイルの末尾に到達すると、当然、ブロック(ここでは関数のスコープ)もそこで終了する(図12-4)。

 このインデントの仕様は、次回Lesson 9以降の「制御フロー文」や「クラス」でもまったく同じなので、ここで確実に押さえてほしい。さらに、「制御フロー文」の「ブロックとスコープの違い」の節で、ブロックとスコープの違いについてより詳しく説明するので、そちらも後で確認してほしい。

デフォルト引数

 再び、図12-1および図12-2の話に戻すと、load_data()関数の引数はpathである。ここで「おかしい」と気付いたかもしれない。先ほどの図10-3で、この関数は「引数なし」として説明した。しかし実際の定義には、引数pathが存在している。つまり、冒頭でも示した関数の使用例(リスト11-2)では、引数の指定を省略していたのである。

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

mnist.load_data()
リスト11-2 関数の呼び出しで引数を省略するコード例

 引数を省略せずに指定するには、リスト11-3のように記述すればよい。

Python
mnist.load_data('mnist.npz')
リスト11-3 関数の呼び出しで引数を省略しないコード例

 なぜ引数を省略できるかというと、前掲のリスト11-1を見ると分かるように、

def load_data(path='mnist.npz'):

という関数の定義になっているからだ。つまり関数定義の段階で、引数path'mnist.npz'*5という文字列値がデフォルトで代入されているためである。

  • *5 'mnist.npz'は、TensorFlowが提供するファイルであり、言語仕様とは関係がないので説明を割愛する。

 このように、デフォルト値を持った引数のことをデフォルト引数と呼ぶ。デフォルト引数があれば、その引数の指定は省略して呼び出せるのである。

 ちなみに復習として、関数の定義でデフォルト引数ではなく通常の引数にする場合は、

def load_data(path):

と、=以降のデフォルト値部分を記載しないようにすればよい。

 デフォルト引数は、引数の数が多いが、通常は決まった値を渡せばよいというときに特に役立つ。例えば関数が10個の引数を持つとしよう(リスト11-3)。

Python
def some_function(param1, param2=2, param3=3, param4=4, param5=5, param6=6, param7=7, param8=8, param9=9, param10=10):
  return param1 + param2 + param3 + param4 + param5 +param6 + param7 +param8 + param9 + param10
リスト11-3 10個の引数(第2引数以降はデフォルト引数あり)を持つ関数定義の例

=の前後には半角スペースを入れてもよい。デフォルト引数の指定では入れないことが一般的。

 この関数を呼び出す場合、通常であれば、リスト11-4のように、すべての引数を順番に指定しなければならない。

Python
some_function(1, 2, 3, 4, 5, 6, 7, 8, 9, 10)

# 55と出力される
リスト11-4 10個の引数をすべて指定して関数を呼び出す例

 しかし、この関数は第2引数以降がすべてデフォルト引数である。各引数に渡す値がデフォルト引数と全く同じ値(=第2引数以降に2345678910を順に渡す場合)であるなら、そのすべてが省略可能だ。実際に、第1引数のみ指定して、残りを省略すると、リスト11-5のようになる。

Python
some_function(1)

# 55と出力される
リスト11-5 デフォルト引数により9個の引数指定を省略して関数を呼び出す例

キーワード引数

 各引数に渡す値がそのデフォルト引数と同じであれば、その引数は省略できることが分かった。しかし例えば第9引数のみ、デフォルト値ではない値を指定したい場合、リスト11-6のように、第8引数まで全部指定しなければならないのだろうか。

Python
some_function(1, 2, 3, 4, 5, 6, 7, 8, 999)

# 1045と出力される
リスト11-6 第9引数の値を指定したい場合(第10引数のみ省略)

 ここで役立つのがキーワード引数である。キーワード引数とは、関数の呼び出し時に引数名(=パラメーター名)を明示的に指定することである。例えばリスト11-7は、キーワード引数を使って、関数の第9引数に値を渡している例である。

Python
some_function(1, param9=999)

# 1045と出力される
リスト11-7 キーワード引数で関数の第9引数に値を渡す場合(第2引数~第8引数、第10引数は省略)

=の前後には半角スペースを入れてもよい。キーワード引数の指定では入れないことが一般的。

 キーワード引数は、関数定義にある引数名を使って、

<引数名>=<データや値>

という構文で引数に値を指定して、関数を呼び出す。リスト11-7の例では、

some_function(<整数値>, param9=<整数値>)

というコードで、まずは順番どおりの指定で第1引数に整数値を、次にキーワード引数で第9引数param9に整数値を渡しながら、関数を呼び出している。その2つ以外の引数(すべてデフォルト引数)はデフォルト値のままでよいので、引数を省略している。なお、リスト11-7の関数呼び出しにおいて、第1引数はキーワードを指定していないが、このようにキーワード指定をせずに、関数定義で定められた順番で渡される引数のことを「位置引数」(positional argument)と呼ぶ。ただし、「some_function(param9=999, param1=1)」のような書き方も可能だ(この場合はparam1もキーワード引数となる)。

 複数の引数に対してデフォルト引数と同じ値が設定されている場合には、リスト11-6のように順番どおりにすべての引数に値を指定するのは、コードがムダに長くなって読みづらくなるし、そもそも面倒である。リスト11-7のように、使う引数だけを、個別に指定して値を渡した方が効率的である。もう一つだけ例を示そう。

 図12-5は、後述する図1-bにあるキーワード引数の活用例である。plt.imshow()関数のヘルプが表示されており、X, cmap=None, norm=None, aspect=None, interpolation=None, alpha=None, vmin=None, vmax=None, origin=None, extent=None, shape=None, filternorm=1, filterrad=4.0, imlim=None, resample=None, url=None, *, data=None, **kwargsと多数の引数が用意されていることが分かる。

キーワード引数
図12-5 キーワード引数

 これらすべての引数を逐一、指定するのが面倒であることは容易に想像できるだろう。実際に図12-5の関数呼び出しでは、第1引数Xと、キーワード引数cmap(前回説明)の2つしか指定しておらず、かなりシンプルな呼び出しになっているのが分かる。

 なお、キーワード引数の指定順序は自由にカスタマイズできる。ということは、「順番は気にせずに、Tabキーでヘルプドキュメントを見ながら、必要な引数から好きに書いていけばよい」ということである。例えば引数が10個も20個もある場合には、意外に重宝する機能である。

さまざまな関数の定義例

 以上、関数の定義方法の基本について解説した。

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

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

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

 以下で紹介するのは、図1-b/d【再掲】における赤枠内のコードのみとなる。各関数定義の中身については、別の回に説明済み、もしくは説明予定なので、記載を省略する。

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

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

Python
# 画像をプロット(=描画)する関数
def plot_image(i, predictions_array, true_label, img):
  # 予測結果と正解と画像を、インデックス番号(i)を指定してNumPy配列データから1つ取得
  predictions_array, true_label, img = predictions_array[i], true_label[i], img[i]
  # ……何らかの処理(一部は前回説明)……
リスト11-8 図1-bにおける「関数定義」
図1-dに含まれる「関数定義」のコード

 図1-dの「関数定義」は、リスト11-19に示す。これも難しくはないだろう。これはreturn文を持たない関数の例といえる。

Python
# ドット(.)を出力するクラスの定義
#class PrintDot(keras.callbacks.Callback):  # Lesson 12で説明
  # 各エポックの最後に呼び出されるクラスのメソッド(=関数の一種)の定義
  def on_epoch_end(self, epoch, logs):
    # ……何らかの処理(前回説明)……
    print('.', end='')
リスト11-9 図1-dにおける「関数定義」

 on_epoch_end()関数の上位階層にclass PrintDotと記載されている。つまりこれはクラスの関数(正確には「メソッド」と呼ぶ)を定義している例である。

つづく

 以上、「関数の定義方法」を説明した。次回は、「制御構文(条件分岐)」を説明する。プログラムの流れを、条件によって分岐させるための大事な文法である。

  • このエントリーをはてなブックマークに追加

※以下では、本稿の前後を合わせて5回分(第5回~第9回)のみ表示しています。
連載の全タイトルを参照するには、[この記事の連載目次]を参照してください。

機械学習 & ディープラーニング入門(Python編)[Lesson 8]
5. データ型(ブール/数値/文字列) ― Python基礎文法入門

Python言語の文法を、コードを書く流れに沿って説明していく連載。今回と次回は、値やデータの型を説明。今回はその前編として、bool型/int型/float型/str型を取り上げる。

2019年2月14日(木)
機械学習 & ディープラーニング入門(Python編)[Lesson 8]
6. データ型(リスト/タプル/辞書/各種オブジェクト) ― Python基礎文法入門

Python言語の文法を、コードを書く流れに沿って説明していく連載。前回と今回は、値やデータの型を説明。今回はその後編として、list型/tuple型/dict型、それら以外のオブジェクトの型を取り上げる。

2019年2月18日(月)
機械学習 & ディープラーニング入門(Python編)[Lesson 8]
7. 関数 ― Python基礎文法入門

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

2019年3月1日(金)
機械学習 & ディープラーニング入門(Python編)[Lesson 8]
8. 【現在、表示中】≫ 関数の定義 ― Python基礎文法入門

Python言語の文法を、コードを書く流れに沿って説明していく連載。前回に続けて、今回は関数の定義方法を説明する。加えて、デフォルト引数やキーワード引数という重要機能、Python言語で特徴的なインデントについても説明する。

2019年3月25日(月)
機械学習 & ディープラーニング入門(Python編)[Lesson 8]
9. 条件分岐 ― Python基礎文法入門

Python言語の文法を、コードを書く流れに沿って説明していく連載。今回は制御構文のうち、条件分岐について説明する。ロジックや処理フローを定義するための大事な文法である。

2019年4月8日(月)
Deep Insider の SNS :